diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.yml b/.github/ISSUE_TEMPLATE/1_bug_report.yml new file mode 100644 index 000000000..612432307 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_bug_report.yml @@ -0,0 +1,120 @@ +name: Report a bug +description: | + You have discovered a bug in our widgets that is causing your application to crash + or throw an exception. It appears that a widget is buggy, or something is not functioning correctly. +body: + - type: markdown + attributes: + value: | + Thank you for using Syncfusion widgets! + + If you are looking for support, please check out our documentation, knowledge base, help bot, + and feedbacks. If you are unable to find a solution, please report the bug here. + + - [User Guide Documentation](https://help.syncfusion.com/flutter/introduction/overview) + - [Knowledge Base](https://support.syncfusion.com/kb/cross-platforms/category/79) + - [HelpBot](https://helpbot.syncfusion.com) + - [Feedbacks](https://stackoverflow.com/questions/tagged/flutter?sort=frequent) + + You can also contact us through the following methods. + - [Support portal](https://support.syncfusion.com) + - [Support forums](https://www.syncfusion.com/forums) + - [Feedback portal](https://www.syncfusion.com/feedback/flutter) + - type: textarea + attributes: + label: Bug description + description: Clearly and concisely describe the problem. + placeholder: | + A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + attributes: + label: Steps to reproduce + description: Kindly provide us with detailed steps to replicate the issue you are experiencing. + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: true + - type: textarea + attributes: + label: Code sample + description: | + Kindly create a simplified, reproducible example that demonstrates the issue + and include it within the code block delineated by backticks. + Note: Refrain from posting screenshots containing code. + value: | +
Code sample + + ```dart + [Add your code here] + ``` + +
+ validations: + required: true + - type: textarea + attributes: + label: Screenshots or Video + description: | + Upload any screenshots or video of the bug if applicable. + value: | +
+ Screenshots / Video demonstration + + [Upload media here] + +
+ validations: + required: true + - type: textarea + attributes: + label: Stack Traces + description: | + If it is a runtime exception, kindly provide the stack trace from the console. + If the traces are too large to be uploaded to GitHub, you may upload them as a `zip` file. + Note: If no stack trace is available, please mention it. + value: | +
Stack Traces + + ```dart + [Add the Stack Traces here] + ``` + +
+ validations: + required: true + - type: dropdown + id: target_platforms + attributes: + label: On which target platforms have you observed this bug? + multiple: true + options: + - Android + - iOS + - Web + - Web (Android browser) + - Web (iOS browser) + - Windows + - macOS + - Linux + validations: + required: true + - type: textarea + attributes: + label: Flutter Doctor output + description: | + Kindly share the complete details generated by executing the command + `flutter doctor -v` in your terminal. + value: | +
Doctor output + + ```console + [Add your output here] + ``` + +
+ validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/2_feature_request.yml b/.github/ISSUE_TEMPLATE/2_feature_request.yml new file mode 100644 index 000000000..9e1444e04 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_feature_request.yml @@ -0,0 +1,41 @@ +name: Feature request +description: Propose a new idea. +body: + - type: markdown + attributes: + value: | + Thank you for using Syncfusion widgets! + + If you are looking for support, please check out our documentation, knowledge base, help bot + and feedbacks. If you are unable to find a solution, please report the bug here. + + - [User Guide Documentation](https://help.syncfusion.com/flutter/introduction/overview) + - [Knowledge Base](https://support.syncfusion.com/kb/cross-platforms/category/79) + - [HelpBot](https://helpbot.syncfusion.com) + - [Feedbacks](https://stackoverflow.com/questions/tagged/flutter?sort=frequent) + + You can also contact us through the following methods. + - [Support portal](https://support.syncfusion.com) + - [Support forums](https://www.syncfusion.com/forums) + - [Feedback portal](https://www.syncfusion.com/feedback/flutter) + - type: textarea + attributes: + label: Use case + description: | + Kindly inform us of the issue you're encountering which has prompted you to suggest a new feature. + If your proposed feature is based on an existing problem, + could you please provide a detailed and concise explanation of the issue? + validations: + required: true + - type: textarea + attributes: + label: Proposal + description: | + Please provide a concise and precise description of your proposal. + + Include a visual representation of your concept. + * code samples + * images (illustrations, diagrams, screenshots) + * videos + validations: + required: true diff --git a/README.md b/README.md index 1b82004d0..8505d1caf 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,10 @@ # Syncfusion Flutter Widgets -Syncfusion Flutter widgets libraries include high-quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base. +Syncfusion Flutter widgets libraries include high-quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base. Please find the [supported platforms](https://help.syncfusion.com/flutter/system-requirements#supported-platforms) for our Flutter widgets. **Disclaimer:** This is a commercial package. To use our packages, you need to have either the 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. - @@ -32,22 +30,23 @@ Also, you can view the samples code from [this repository](https://github.com/sy | Package/Plugin | Available widgets/libraries | Pub | Points | Popularity | Likes | |----------------|-----------------------------|-----|--------|------------|-------| -| [syncfusion_flutter_charts](./packages/syncfusion_flutter_charts/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_charts.svg)](https://pub.dev/packages/syncfusion_flutter_charts) | [![pub points](https://badges.bar/syncfusion_flutter_charts/pub%20points)](https://pub.dev/packages/syncfusion_flutter_charts/score) | [![popularity](https://badges.bar/syncfusion_flutter_charts/popularity)](https://pub.dev/packages/syncfusion_flutter_charts/score) | [![likes](https://badges.bar/syncfusion_flutter_charts/likes)](https://pub.dev/packages/syncfusion_flutter_charts/score) | -| [syncfusion_flutter_calendar](./packages/syncfusion_flutter_calendar/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_calendar.svg)](https://pub.dev/packages/syncfusion_flutter_calendar) | [![pub points](https://badges.bar/syncfusion_flutter_calendar/pub%20points)](https://pub.dev/packages/syncfusion_flutter_calendar/score) | [![popularity](https://badges.bar/syncfusion_flutter_calendar/popularity)](https://pub.dev/packages/syncfusion_flutter_calendar/score) | [![likes](https://badges.bar/syncfusion_flutter_calendar/likes)](https://pub.dev/packages/syncfusion_flutter_calendar/score) | -| [syncfusion_flutter_datagrid](./packages/syncfusion_flutter_datagrid/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_datagrid.svg)](https://pub.dev/packages/syncfusion_flutter_datagrid) | [![pub points](https://badges.bar/syncfusion_flutter_datagrid/pub%20points)](https://pub.dev/packages/syncfusion_flutter_datagrid/score) | [![popularity](https://badges.bar/syncfusion_flutter_datagrid/popularity)](https://pub.dev/packages/syncfusion_flutter_datagrid/score) | [![likes](https://badges.bar/syncfusion_flutter_datagrid/likes)](https://pub.dev/packages/syncfusion_flutter_datagrid/score) | -| [syncfusion_flutter_pdfviewer](./packages/syncfusion_flutter_pdfviewer/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_pdfviewer.svg)](https://pub.dev/packages/syncfusion_flutter_pdfviewer) | [![pub points](https://badges.bar/syncfusion_flutter_pdfviewer/pub%20points)](https://pub.dev/packages/syncfusion_flutter_pdfviewer/score) | [![popularity](https://badges.bar/syncfusion_flutter_pdfviewer/popularity)](https://pub.dev/packages/syncfusion_flutter_pdfviewer/score) | [![likes](https://badges.bar/syncfusion_flutter_pdfviewer/likes)](https://pub.dev/packages/syncfusion_flutter_pdfviewer/score) | -| [syncfusion_flutter_pdf](./packages/syncfusion_flutter_pdf/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_pdf.svg)](https://pub.dev/packages/syncfusion_flutter_pdf) | [![pub points](https://badges.bar/syncfusion_flutter_pdf/pub%20points)](https://pub.dev/packages/syncfusion_flutter_pdf/score) | [![popularity](https://badges.bar/syncfusion_flutter_pdf/popularity)](https://pub.dev/packages/syncfusion_flutter_pdf/score) | [![likes](https://badges.bar/syncfusion_flutter_pdf/likes)](https://pub.dev/packages/syncfusion_flutter_pdf/score) | -| [syncfusion_flutter_xlsio](./packages/syncfusion_flutter_xlsio/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_xlsio.svg)](https://pub.dev/packages/syncfusion_flutter_xlsio) | [![pub points](https://badges.bar/syncfusion_flutter_xlsio/pub%20points)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | [![popularity](https://badges.bar/syncfusion_flutter_xlsio/popularity)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | [![likes](https://badges.bar/syncfusion_flutter_xlsio/likes)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | -| [syncfusion_flutter_datepicker](./packages/syncfusion_flutter_datepicker/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_datepicker.svg)](https://pub.dev/packages/syncfusion_flutter_datepicker) | [![pub points](https://badges.bar/syncfusion_flutter_datepicker/pub%20points)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | [![popularity](https://badges.bar/syncfusion_flutter_datepicker/popularity)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | [![likes](https://badges.bar/syncfusion_flutter_datepicker/likes)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | -| [syncfusion_flutter_maps](./packages/syncfusion_flutter_maps/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_maps.svg)](https://pub.dev/packages/syncfusion_flutter_maps) | [![pub points](https://badges.bar/syncfusion_flutter_maps/pub%20points)](https://pub.dev/packages/syncfusion_flutter_maps/score) | [![popularity](https://badges.bar/syncfusion_flutter_maps/popularity)](https://pub.dev/packages/syncfusion_flutter_maps/score) | [![likes](https://badges.bar/syncfusion_flutter_maps/likes)](https://pub.dev/packages/syncfusion_flutter_maps/score) | -| [syncfusion_flutter_gauges](./packages/syncfusion_flutter_gauges/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_gauges.svg)](https://pub.dev/packages/syncfusion_flutter_gauges) | [![pub points](https://badges.bar/syncfusion_flutter_gauges/pub%20points)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![popularity](https://badges.bar/syncfusion_flutter_gauges/popularity)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![likes](https://badges.bar/syncfusion_flutter_gauges/likes)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | -| [syncfusion_flutter_sliders](./packages/syncfusion_flutter_sliders/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_sliders.svg)](https://pub.dev/packages/syncfusion_flutter_sliders) | [![pub points](https://badges.bar/syncfusion_flutter_sliders/pub%20points)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | [![popularity](https://badges.bar/syncfusion_flutter_sliders/popularity)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | [![likes](https://badges.bar/syncfusion_flutter_sliders/likes)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | -| [syncfusion_flutter_signaturepad](./packages/syncfusion_flutter_signaturepad/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_signaturepad.svg)](https://pub.dev/packages/syncfusion_flutter_signaturepad) | [![pub points](https://badges.bar/syncfusion_flutter_signaturepad/pub%20points)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | [![popularity](https://badges.bar/syncfusion_flutter_signaturepad/popularity)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | [![likes](https://badges.bar/syncfusion_flutter_signaturepad/likes)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | -| [syncfusion_flutter_barcodes](./packages/syncfusion_flutter_barcodes/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_barcodes.svg)](https://pub.dev/packages/syncfusion_flutter_barcodes) | [![pub points](https://badges.bar/syncfusion_flutter_barcodes/pub%20points)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | [![popularity](https://badges.bar/syncfusion_flutter_barcodes/popularity)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | [![likes](https://badges.bar/syncfusion_flutter_barcodes/likes)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | -| [syncfusion_officechart](./packages/syncfusion_officechart/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_officechart.svg)](https://pub.dev/packages/syncfusion_officechart) | [![pub points](https://badges.bar/syncfusion_officechart/pub%20points)](https://pub.dev/packages/syncfusion_officechart/score) | [![popularity](https://badges.bar/syncfusion_officechart/popularity)](https://pub.dev/packages/syncfusion_officechart/score) | [![likes](https://badges.bar/syncfusion_officechart/likes)](https://pub.dev/packages/syncfusion_officechart/score) | -| [syncfusion_officecore](./packages/syncfusion_officecore/) | This package is a dependecy package for `Office chart` library. | [![pub package](https://img.shields.io/pub/v/syncfusion_officecore.svg)](https://pub.dev/packages/syncfusion_officecore) | [![pub points](https://badges.bar/syncfusion_officecore/pub%20points)](https://pub.dev/packages/syncfusion_officecore/score) | [![popularity](https://badges.bar/syncfusion_officecore/popularity)](https://pub.dev/packages/syncfusion_officecore/score) | [![likes](https://badges.bar/syncfusion_officecore/likes)](https://pub.dev/packages/syncfusion_officecore/score) | -| [syncfusion_flutter_core](./packages/syncfusion_flutter_core/) | This package is a dependecy package for all the Syncfusion Flutter widgets and libraries. | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_core.svg)](https://pub.dev/packages/syncfusion_flutter_core) | [![pub points](https://badges.bar/syncfusion_flutter_core/pub%20points)](https://pub.dev/packages/syncfusion_flutter_core/score) | [![popularity](https://badges.bar/syncfusion_flutter_core/popularity)](https://pub.dev/packages/syncfusion_flutter_core/score) | [![likes](https://badges.bar/syncfusion_flutter_core/likes)](https://pub.dev/packages/syncfusion_flutter_core/score) | -| [syncfusion_localizations](./packages/syncfusion_localizations/) | This package contains localized text for 77 cultures for all the applicable Syncfusion Flutter Widgets.| [![pub package](https://img.shields.io/pub/v/syncfusion_localizations.svg)](https://pub.dev/packages/syncfusion_localizations) | [![pub points](https://badges.bar/syncfusion_localizations/pub%20points)](https://pub.dev/packages/syncfusion_localizations/score) | [![popularity](https://badges.bar/syncfusion_localizations/popularity)](https://pub.dev/packages/syncfusion_localizations/score) | [![likes](https://badges.bar/syncfusion_localizations/likes)](https://pub.dev/packages/syncfusion_localizations/score) | +| [syncfusion_flutter_chat](./packages/syncfusion_flutter_chat/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_chat.svg)](https://pub.dev/packages/syncfusion_flutter_chat) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_chat)](https://pub.dev/packages/syncfusion_flutter_chat/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_chat)](https://pub.dev/packages/syncfusion_flutter_chat/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_chat)](https://pub.dev/packages/syncfusion_flutter_chat/score) | +| [syncfusion_flutter_charts](./packages/syncfusion_flutter_charts/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_charts.svg)](https://pub.dev/packages/syncfusion_flutter_charts) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_charts)](https://pub.dev/packages/syncfusion_flutter_charts/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_charts)](https://pub.dev/packages/syncfusion_flutter_charts/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_charts)](https://pub.dev/packages/syncfusion_flutter_charts/score) | +| [syncfusion_flutter_calendar](./packages/syncfusion_flutter_calendar/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_calendar.svg)](https://pub.dev/packages/syncfusion_flutter_calendar) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_calendar)](https://pub.dev/packages/syncfusion_flutter_calendar/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_calendar)](https://pub.dev/packages/syncfusion_flutter_calendar/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_calendar)](https://pub.dev/packages/syncfusion_flutter_calendar/score) | +| [syncfusion_flutter_datagrid](./packages/syncfusion_flutter_datagrid/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_datagrid.svg)](https://pub.dev/packages/syncfusion_flutter_datagrid) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_datagrid)](https://pub.dev/packages/syncfusion_flutter_datagrid/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_datagrid)](https://pub.dev/packages/syncfusion_flutter_datagrid/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_datagrid)](https://pub.dev/packages/syncfusion_flutter_datagrid/score) | +| [syncfusion_flutter_pdfviewer](./packages/syncfusion_flutter_pdfviewer/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_pdfviewer.svg)](https://pub.dev/packages/syncfusion_flutter_pdfviewer) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_pdfviewer)](https://pub.dev/packages/syncfusion_flutter_pdfviewer/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_pdfviewer)](https://pub.dev/packages/syncfusion_flutter_pdfviewer/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_pdfviewer)](https://pub.dev/packages/syncfusion_flutter_pdfviewer/score) | +| [syncfusion_flutter_pdf](./packages/syncfusion_flutter_pdf/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_pdf.svg)](https://pub.dev/packages/syncfusion_flutter_pdf) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_pdf)](https://pub.dev/packages/syncfusion_flutter_pdf/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_pdf)](https://pub.dev/packages/syncfusion_flutter_pdf/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_pdf)](https://pub.dev/packages/syncfusion_flutter_pdf/score) | +| [syncfusion_flutter_xlsio](./packages/syncfusion_flutter_xlsio/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_xlsio.svg)](https://pub.dev/packages/syncfusion_flutter_xlsio) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_xlsio)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_xlsio)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_xlsio)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | +| [syncfusion_flutter_datepicker](./packages/syncfusion_flutter_datepicker/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_datepicker.svg)](https://pub.dev/packages/syncfusion_flutter_datepicker) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_datepicker)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_datepicker)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_datepicker)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | +| [syncfusion_flutter_maps](./packages/syncfusion_flutter_maps/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_maps.svg)](https://pub.dev/packages/syncfusion_flutter_maps) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_maps)](https://pub.dev/packages/syncfusion_flutter_maps/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_maps)](https://pub.dev/packages/syncfusion_flutter_maps/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_maps)](https://pub.dev/packages/syncfusion_flutter_maps/score) | +| [syncfusion_flutter_gauges](./packages/syncfusion_flutter_gauges/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_gauges.svg)](https://pub.dev/packages/syncfusion_flutter_gauges) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_gauges)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_gauges)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_gauges)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | +| [syncfusion_flutter_sliders](./packages/syncfusion_flutter_sliders/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_sliders.svg)](https://pub.dev/packages/syncfusion_flutter_sliders) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_sliders)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_sliders)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_sliders)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | +| [syncfusion_flutter_signaturepad](./packages/syncfusion_flutter_signaturepad/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_signaturepad.svg)](https://pub.dev/packages/syncfusion_flutter_signaturepad) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_signaturepad)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_signaturepad)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_signaturepad)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | +| [syncfusion_flutter_barcodes](./packages/syncfusion_flutter_barcodes/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_barcodes.svg)](https://pub.dev/packages/syncfusion_flutter_barcodes) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_barcodes)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_barcodes)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_barcodes)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | +| [syncfusion_officechart](./packages/syncfusion_officechart/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_officechart.svg)](https://pub.dev/packages/syncfusion_officechart) | [![pub points](https://img.shields.io/pub/points/syncfusion_officechart)](https://pub.dev/packages/syncfusion_officechart/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_officechart)](https://pub.dev/packages/syncfusion_officechart/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_officechart)](https://pub.dev/packages/syncfusion_officechart/score) | +| [syncfusion_officecore](./packages/syncfusion_officecore/) | This package is a dependecy package for `Office chart` library. | [![pub package](https://img.shields.io/pub/v/syncfusion_officecore.svg)](https://pub.dev/packages/syncfusion_officecore) | [![pub points](https://img.shields.io/pub/points/syncfusion_officecore)](https://pub.dev/packages/syncfusion_officecore/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_officecore)](https://pub.dev/packages/syncfusion_officecore/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_officecore)](https://pub.dev/packages/syncfusion_officecore/score) | +| [syncfusion_flutter_core](./packages/syncfusion_flutter_core/) | This package is a dependecy package for all the Syncfusion Flutter widgets and libraries. | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_core.svg)](https://pub.dev/packages/syncfusion_flutter_core) | [![pub points](https://img.shields.io/pub/points/syncfusion_flutter_core)](https://pub.dev/packages/syncfusion_flutter_core/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_flutter_core)](https://pub.dev/packages/syncfusion_flutter_core/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_flutter_core)](https://pub.dev/packages/syncfusion_flutter_core/score) | +| [syncfusion_localizations](./packages/syncfusion_localizations/) | This package contains localized text for 77 cultures for all the applicable Syncfusion Flutter Widgets.| [![pub package](https://img.shields.io/pub/v/syncfusion_localizations.svg)](https://pub.dev/packages/syncfusion_localizations) | [![pub points](https://img.shields.io/pub/points/syncfusion_localizations)](https://pub.dev/packages/syncfusion_localizations/score) | [![popularity](https://img.shields.io/pub/popularity/syncfusion_localizations)](https://pub.dev/packages/syncfusion_localizations/score) | [![likes](https://img.shields.io/pub/likes/syncfusion_localizations)](https://pub.dev/packages/syncfusion_localizations/score) | ## How to use @@ -78,7 +77,33 @@ $ flutter pub get **Step 4** -Run your application either using `F5` or `Run > Start Debugging`. +Run your application using the following commands: + +**Windows & Linux** + +Run code: Ctrl + F5 + +Run with debugging: F5 or Run > Start Debugging + +Stop debugging: Shift + F5 + +Restart debugging: Ctrl + Shift + F5 + +**MacOS** + +Run code: Cmd + F5 + +Run with debugging: F5 or Run > Start Debugging + +Stop debugging: Shift + Cmd + F5 + +Restart debugging: Cmd + Shift + F5 + +**Web (VS Code in the Browser)** + +Run code: Ctrl + F5 + +Run with debugging: F5 or Run > Start Debugging ### Running the available example @@ -115,12 +140,13 @@ Run your application either using `F5` or `Run > Start Debugging`. Explore the full capabilities of our Flutter widgets on your device by installing our sample browser applications from the below app stores, and view samples code in GitHub.

- - -

-

- - + + + +

+

+ +

## Useful links @@ -141,4 +167,4 @@ Take a look at the following to learn more about Syncfusion Flutter widgets: 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_barcodes/CHANGELOG.md b/packages/syncfusion_flutter_barcodes/CHANGELOG.md index 97fa8292d..2533f0f63 100644 --- a/packages/syncfusion_flutter_barcodes/CHANGELOG.md +++ b/packages/syncfusion_flutter_barcodes/CHANGELOG.md @@ -1,3 +1,73 @@ +## Unreleased + +**General** + +* The compatible version of our Flutter barcodes widget has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter barcodes widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter barcodes example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter barcodes example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 24/12/2024 + +**General** + +* The compatible version of our Flutter barcodes widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter barcodes widget has been updated to Flutter SDK 3.24.0. + +## [25.1.35] - 03/15/2024 + +**General** +* Provided th​e Material 3 themes support. + +## [22.2.10] 08/22/2023 + +**Bugs** +* #FB45676 - Now, the QR code generated for all kinds of the input values with 07 [codeVersion](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/codeVersion.html), medium [errorCorrectionLevel](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/errorCorrectionLevel.html), and alphaNumeric [inputMode](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/inputMode.html) will be scannable. + +## [22.1.36] 06/28/2023 + +**Bugs** +* #FB44237 - Now, the QR code generated for all kinds of input values with 07 [codeVersion](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/codeVersion.html) will be scannable. + +## [21.1.37] - 03/29/2023 + +**Bugs** +* #FB41210 - Now, the QR code generated for the input string contains 17 continuous spaces with medium [errorCorrectionLevel](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/errorCorrectionLevel.html), and 07 [codeVersion](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/codeVersion.html) will be scannable. + +## [20.3.50] - 10/18/2022 + +**Bugs** +* #FB38178 - Now, the QR code generated for the input string contains spaces with `medium` [errorCorrectionLevel](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/errorCorrectionLevel.html) and `07` [codeVersion](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/codeVersion.html) will be scannable. + ## [18.3.35] - 10/01/2020 No changes. @@ -99,4 +169,4 @@ Initial release. * **One-dimensional barcodes** - Barcode Generator supports different one-dimensional barcode symbologies such as Code128, EAN8, EAN13, UPA-C, UPA-E, Code39, Code39 Extended, Code93 and Codabar. * **Two-dimensional barcode** - Barcode Generator supports popular QR Code and Data Matrix. * **Barcode customization** - Customize the visual appearance of barcodes using the backgroundColor and barColor properties, and adjust the size of smallest line or dot of the code using the module property. -* **Text customization** -Configure to display the barcode value and customize the position and style of the barcode text. \ No newline at end of file +* **Text customization** -Configure to display the barcode value and customize the position and style of the barcode text. diff --git a/packages/syncfusion_flutter_barcodes/LICENSE b/packages/syncfusion_flutter_barcodes/LICENSE index dbac38fe4..40b03cd64 100644 --- a/packages/syncfusion_flutter_barcodes/LICENSE +++ b/packages/syncfusion_flutter_barcodes/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Barcodes 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. +Syncfusion® Flutter Barcodes 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_barcodes/README.md b/packages/syncfusion_flutter_barcodes/README.md index 0a6ae4f48..f02e79483 100644 --- a/packages/syncfusion_flutter_barcodes/README.md +++ b/packages/syncfusion_flutter_barcodes/README.md @@ -4,7 +4,7 @@ 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 [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. +**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) @@ -16,7 +16,7 @@ Flutter Barcode Generator package is a data visualization widget used to generat - [Add barcode symbology](#add-barcode-symbology) - [Show value to the barcode](#show-value-to-the-barcode) - [Support and feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Barcode Generator features @@ -36,17 +36,14 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- -

-

## Other useful links -Take a look at the following to learn more about Syncfusion Flutter guages: +Take a look at the following to learn more about Syncfusion® Flutter guages: -* [Syncfusion Flutter Barcode product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter Barcode product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation](https://help.syncfusion.com/flutter/introduction/overview) * [Knowledge base](https://www.syncfusion.com/kb) @@ -127,11 +124,11 @@ The following screenshot illustrates the result of the previous code sample. ### 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +### 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_barcodes/example/README.md b/packages/syncfusion_flutter_barcodes/example/README.md new file mode 100644 index 000000000..719403ebb --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/README.md @@ -0,0 +1,3 @@ +# Barcode example + +How to render Syncfusion Flutter Barcode generator widget? diff --git a/packages/syncfusion_flutter_barcodes/example/android/.gitignore b/packages/syncfusion_flutter_barcodes/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/.gitignore +++ b/packages/syncfusion_flutter_barcodes/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/build.gradle b/packages/syncfusion_flutter_barcodes/example/android/app/build.gradle index 5fe3c929f..b5511a9a3 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_barcodes/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_barcodes/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_barcodes/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/AndroidManifest.xml index 3f41384db..74a78b939 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000d..70f8f08f2 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_barcodes/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_barcodes/example/android/build.gradle b/packages/syncfusion_flutter_barcodes/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/build.gradle +++ b/packages/syncfusion_flutter_barcodes/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_barcodes/example/android/gradle.properties b/packages/syncfusion_flutter_barcodes/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/gradle.properties +++ b/packages/syncfusion_flutter_barcodes/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_barcodes/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_barcodes/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_barcodes/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_barcodes/example/android/settings.gradle b/packages/syncfusion_flutter_barcodes/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_barcodes/example/android/settings.gradle +++ b/packages/syncfusion_flutter_barcodes/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_barcodes/example/ios/.gitignore b/packages/syncfusion_flutter_barcodes/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/.gitignore +++ b/packages/syncfusion_flutter_barcodes/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_barcodes/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_barcodes/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.pbxproj index 0d45b982d..a6a988ee6 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,22 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -26,8 +34,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -37,14 +43,14 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; 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 = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; 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 = ""; }; @@ -57,20 +63,25 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -84,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -91,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -102,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -111,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -137,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -147,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -157,7 +187,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -165,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -191,20 +232,23 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -220,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -231,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -253,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -285,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -293,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -310,15 +371,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -328,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -362,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -376,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -386,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -418,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -426,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -444,15 +550,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -471,15 +572,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -492,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -513,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_barcodes/example/ios/Runner/AppDelegate.swift index 70693e4a8..626664468 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/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_barcodes/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Info.plist index a060db61e..5458fc418 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Runner-Bridging-Header.h index 7335fdf90..308a2a560 100644 --- a/packages/syncfusion_flutter_barcodes/example/ios/Runner/Runner-Bridging-Header.h +++ b/packages/syncfusion_flutter_barcodes/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_barcodes/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_barcodes/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_barcodes/example/lib/main.dart b/packages/syncfusion_flutter_barcodes/example/lib/main.dart index 8d333b1eb..ddec1a8fe 100644 --- a/packages/syncfusion_flutter_barcodes/example/lib/main.dart +++ b/packages/syncfusion_flutter_barcodes/example/lib/main.dart @@ -11,18 +11,18 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: AppBar( - title: const Text('Barcode Generator Demo'), - ), - body: Center( - child: Container( + appBar: AppBar(title: const Text('Barcode Generator Demo')), + body: Center( + child: Container( height: 200, child: SfBarcodeGenerator( value: 'www.syncfusion.com', symbology: QRCode(), showValue: true, ), - ))), + ), + ), + ), ); } } diff --git a/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_barcodes/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_barcodes/example/macos/.gitignore b/packages/syncfusion_flutter_barcodes/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_barcodes/example/macos/.gitignore +++ b/packages/syncfusion_flutter_barcodes/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..fca5bdc5c 100644 --- a/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,6 +61,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -106,6 +134,7 @@ isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,6 +218,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..9963aa361 100644 --- a/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_barcodes/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - + + + + + + - - Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..dda9752f6 100644 --- a/packages/syncfusion_flutter_barcodes/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_barcodes/example/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = example PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_barcodes/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_barcodes/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_barcodes/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_barcodes/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_barcodes/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_barcodes/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_barcodes/example/pubspec.yaml b/packages/syncfusion_flutter_barcodes/example/pubspec.yaml index f9456b13a..deef8cd6a 100644 --- a/packages/syncfusion_flutter_barcodes/example/pubspec.yaml +++ b/packages/syncfusion_flutter_barcodes/example/pubspec.yaml @@ -1,20 +1,10 @@ name: example 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 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: @@ -22,53 +12,12 @@ dependencies: syncfusion_flutter_barcodes: path: ../ - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.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_barcodes/example/web/index.html b/packages/syncfusion_flutter_barcodes/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_barcodes/example/web/index.html +++ b/packages/syncfusion_flutter_barcodes/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_barcodes/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_barcodes/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_barcodes/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_barcodes/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) 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 ab0e27cdb..9c1d85f4e 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 @@ -31,6 +31,7 @@ import '../renderers/one_dimensional/upca_renderer.dart'; import '../renderers/one_dimensional/upce_renderer.dart'; import '../renderers/two_dimensional/datamatrix_renderer.dart'; import '../renderers/two_dimensional/qr_code_renderer.dart'; +import '../theme.dart'; import '../two_dimensional/datamatrix_symbology.dart'; import '../two_dimensional/qr_code_symbology.dart'; @@ -83,18 +84,18 @@ class SfBarcodeGenerator extends StatefulWidget { /// must not be null or empty. /// /// Default symbology is [Code128]. - SfBarcodeGenerator( - {Key? key, - required this.value, - Symbology? symbology, - this.barColor, - this.backgroundColor, - this.showValue = false, - this.textSpacing = 2, - this.textAlign = TextAlign.center, - this.textStyle = const TextStyle()}) - : symbology = symbology ?? Code128(), - super(key: key); + SfBarcodeGenerator({ + Key? key, + required this.value, + Symbology? symbology, + this.barColor, + this.backgroundColor, + this.showValue = false, + this.textSpacing = 2, + this.textAlign = TextAlign.center, + this.textStyle, + }) : symbology = symbology ?? Code128(), + super(key: key); /// Defines the value of the barcode to be rendered. /// @@ -247,7 +248,7 @@ class SfBarcodeGenerator extends StatefulWidget { /// textStyle: TextStyle(fontSize: 16)); ///} /// ```dart - final TextStyle textStyle; + final TextStyle? textStyle; @override State createState() { @@ -258,7 +259,7 @@ class SfBarcodeGenerator extends StatefulWidget { /// Represents the barcode generator state class _SfBarcodeGeneratorState extends State { /// Specifies the theme data - late SfBarcodeThemeData _barcodeTheme; + late SfBarcodeThemeData _barcodeThemeData; /// Specifies the text size Size? _textSize; @@ -267,16 +268,51 @@ class _SfBarcodeGeneratorState extends State { @override void didChangeDependencies() { - _barcodeTheme = SfBarcodeTheme.of(context); + _barcodeThemeData = _updateThemeData( + Theme.of(context), + SfBarcodeTheme.of(context), + ); super.didChangeDependencies(); } + SfBarcodeThemeData _updateThemeData( + ThemeData themeData, + SfBarcodeThemeData barcodeThemeData, + ) { + final SfBarcodeThemeData effectiveThemeData = BarcodeThemeData(context); + barcodeThemeData = barcodeThemeData.copyWith( + backgroundColor: + barcodeThemeData.backgroundColor ?? + widget.backgroundColor ?? + effectiveThemeData.backgroundColor, + barColor: + barcodeThemeData.barColor ?? + widget.barColor ?? + effectiveThemeData.barColor, + textStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: barcodeThemeData.textColor ?? effectiveThemeData.textColor, + ) + .merge(barcodeThemeData.textStyle) + .merge(widget.textStyle), + ); + return barcodeThemeData; + } + @override void didUpdateWidget(SfBarcodeGenerator oldWidget) { + _barcodeThemeData = _updateThemeData( + Theme.of(context), + SfBarcodeTheme.of(context), + ); + if (widget.showValue && (oldWidget.value != widget.value || oldWidget.textStyle != widget.textStyle)) { - _textSize = measureText(widget.value.toString(), widget.textStyle); + _textSize = measureText( + widget.value.toString(), + _barcodeThemeData.textStyle!, + ); } if (widget.symbology != oldWidget.symbology) { @@ -327,29 +363,26 @@ class _SfBarcodeGeneratorState extends State { @override Widget build(BuildContext context) { if (widget.showValue && _textSize == null) { - _textSize = measureText(widget.value.toString(), widget.textStyle); + _textSize = measureText( + widget.value.toString(), + _barcodeThemeData.textStyle!, + ); } _symbologyRenderer.getIsValidateInput(widget.value!); _symbologyRenderer.textSize = _textSize; return Container( - color: widget.backgroundColor ?? _barcodeTheme.backgroundColor, + color: widget.backgroundColor ?? _barcodeThemeData.backgroundColor, child: SfBarcodeGeneratorRenderObjectWidget( - value: widget.value!, - symbology: widget.symbology, - foregroundColor: widget.barColor ?? _barcodeTheme.barColor, - showText: widget.showValue, - textSpacing: widget.textSpacing, - textStyle: TextStyle( - backgroundColor: widget.textStyle.backgroundColor, - color: widget.textStyle.color ?? _barcodeTheme.textColor, - fontSize: widget.textStyle.fontSize, - fontFamily: widget.textStyle.fontFamily, - fontStyle: widget.textStyle.fontStyle, - fontWeight: widget.textStyle.fontWeight, - textBaseline: widget.textStyle.textBaseline), - symbologyRenderer: _symbologyRenderer, - textSize: _textSize, - textAlign: widget.textAlign), + value: widget.value!, + symbology: widget.symbology, + foregroundColor: widget.barColor ?? _barcodeThemeData.barColor, + showText: widget.showValue, + textSpacing: widget.textSpacing, + textStyle: _barcodeThemeData.textStyle!, + symbologyRenderer: _symbologyRenderer, + textSize: _textSize, + textAlign: widget.textAlign, + ), ); } } 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 408e65e8d..80760587e 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 @@ -11,10 +11,11 @@ abstract class Symbology { /// The arguments [module] must be non-negative and greater than 0. /// Symbology({this.module}) - : assert( - (module != null && module > 0) || module == null, - 'Module must' - ' not be a non-negative value or else it must be equal to null.'); + : assert( + (module != null && module > 0) || module == null, + 'Module must' + ' not be a non-negative value or else it must be equal to null.', + ); /// Specifies the size of the smallest line or dot of the barcode. /// 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 fa961a972..d9bb362f2 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 @@ -5,18 +5,18 @@ import '../renderers/one_dimensional/symbology_base_renderer.dart'; /// Represents the render object widget class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { /// Creates the render object widget - const SfBarcodeGeneratorRenderObjectWidget( - {Key? key, - this.value, - this.symbology, - this.foregroundColor, - this.showText, - this.textSpacing, - this.textStyle, - this.textSize, - this.symbologyRenderer, - this.textAlign}) - : super(key: key); + const SfBarcodeGeneratorRenderObjectWidget({ + Key? key, + this.value, + this.symbology, + this.foregroundColor, + this.showText, + this.textSpacing, + this.textStyle, + this.textSize, + this.symbologyRenderer, + this.textAlign, + }) : super(key: key); ///Defines the value of the barcode to be rendered. final String? value; @@ -49,15 +49,16 @@ class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) { return _RenderBarcode( - value: value!, - symbology: symbology!, - foregroundColor: foregroundColor!, - showText: showText!, - textSpacing: textSpacing!, - symbologyRenderer: symbologyRenderer!, - textStyle: textStyle!, - textSize: textSize, - textAlign: textAlign!); + value: value!, + symbology: symbology!, + foregroundColor: foregroundColor!, + showText: showText!, + textSpacing: textSpacing!, + symbologyRenderer: symbologyRenderer!, + textStyle: textStyle!, + textSize: textSize, + textAlign: textAlign!, + ); } @override @@ -79,25 +80,25 @@ class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { /// Represents the RenderBarcode class class _RenderBarcode extends RenderBox { /// Creates the RenderBarcode - _RenderBarcode( - {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, - _foregroundColor = foregroundColor, - _showText = showText, - _textSpacing = textSpacing, - _textStyle = textStyle, - _textSize = textSize, - _textAlign = textAlign; + _RenderBarcode({ + 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, + _foregroundColor = foregroundColor, + _showText = showText, + _textSpacing = textSpacing, + _textStyle = textStyle, + _textSize = textSize, + _textAlign = textAlign; /// Represents the barcode value String _value; @@ -245,20 +246,21 @@ class _RenderBarcode extends RenderBox { @override void paint(PaintingContext context, Offset offset) { symbologyRenderer.renderBarcode( - context.canvas, - Size( - size.width, - size.height - - (showText - ? (_textSpacing + - (_textSize != null ? _textSize!.height : 0)) - : 0)), - offset, - value, - foregroundColor, - textStyle, - textSpacing, - textAlign, - showText); + context.canvas, + Size( + size.width, + size.height - + (showText + ? (_textSpacing + (_textSize != null ? _textSize!.height : 0)) + : 0), + ), + offset, + value, + foregroundColor, + textStyle, + textSpacing, + textAlign, + showText, + ); } } 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 729d578e3..e6a412ba6 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,8 +18,5 @@ 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}) - : super( - module: module, - ); + Code128({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 305fc0f65..59775e72f 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 @@ -18,5 +18,5 @@ class Code39Extended extends Code39 { /// 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}) - : super(module: module, enableCheckSum: enableCheckSum ?? true); + : super(module: module, enableCheckSum: enableCheckSum ?? true); } 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 e7901fed3..2d83daaba 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 @@ -27,7 +27,7 @@ class CodabarRenderer extends SymbologyRenderer { 'A': '1011001001', 'B': '1001001011', 'C': '1010010011', - 'D': '1010011001' + 'D': '1010011001', }; } @@ -42,7 +42,9 @@ class CodabarRenderer extends SymbologyRenderer { value[i] == 'B' || value[i] == 'C' || value[i] == 'D') { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; @@ -50,24 +52,34 @@ class CodabarRenderer extends SymbologyRenderer { @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(value); final int barTotalLength = _getTotalLength(code); - double left = symbology!.module == null - ? offset.dx - : getLeftPosition( - barTotalLength, symbology!.module!, size.width, offset.dx); + double left = + symbology!.module == null + ? offset.dx + : getLeftPosition( + barTotalLength, + symbology!.module!, + size.width, + offset.dx, + ); final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology!.module != null) { ratio = symbology!.module!.toDouble(); @@ -90,7 +102,11 @@ class CodabarRenderer extends SymbologyRenderer { if (canDraw && (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); + left, + offset.dy, + left + ratio, + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } left += ratio; @@ -123,8 +139,10 @@ class CodabarRenderer extends SymbologyRenderer { /// Returns the encoded value of the provided input value List _getCodeValues(String value) { valueWithStartAndStopSymbol = _getValueWithStartAndStopSymbol(value); - final List codeBarValues = - List.filled(valueWithStartAndStopSymbol.length, null); + 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 ecfbf6ad4..ebdb6b788 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 @@ -421,12 +421,13 @@ class Code128Renderer extends SymbologyRenderer { [2, 1, 1, 4, 1, 2], [2, 1, 1, 2, 1, 4], [2, 1, 1, 2, 3, 2], - [2, 3, 3, 1, 1, 1, 2] + [2, 3, 3, 1, 1, 1, 2], ]; } @override bool getIsValidateInput(String value) { + // ignore: dead_code for (int i = 0; i < value.length; i++) { final int currentCharacter = value[i].codeUnitAt(0); if (currentCharacter == _fnc1.codeUnitAt(0) || @@ -437,7 +438,9 @@ class Code128Renderer extends SymbologyRenderer { } else if (currentCharacter < 127) { return true; } else { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return false; @@ -452,8 +455,11 @@ class Code128Renderer extends SymbologyRenderer { int codeTypeValue = 0; int currentPosition = 0; while (currentPosition < value.length) { - final int currentCodeType = - _getValidatedCode(currentPosition, codeTypeValue, value); + final int currentCodeType = _getValidatedCode( + currentPosition, + codeTypeValue, + value, + ); int currentIndex; if (currentCodeType == codeTypeValue) { final int currentValue = value[currentPosition].codeUnitAt(0); @@ -482,7 +488,8 @@ class Code128Renderer extends SymbologyRenderer { value[currentPosition].codeUnitAt(0) - ' '.codeUnitAt(0); } else { currentIndex = int.parse( - value.substring(currentPosition, currentPosition + 2)); + value.substring(currentPosition, currentPosition + 2), + ); currentPosition++; } } @@ -524,8 +531,12 @@ 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 = - _getValidatedCodeTypes(start, previousCodeSet, value, codeType); + final int? currentCodeType = _getValidatedCodeTypes( + start, + previousCodeSet, + value, + codeType, + ); if (currentCodeType != null) { return currentCodeType; } @@ -567,7 +578,11 @@ class Code128Renderer extends SymbologyRenderer { /// Method to get the validated types int? _getValidatedCodeTypes( - int start, int previousCodeSet, String value, CodeType codeType) { + int start, + int previousCodeSet, + String value, + CodeType codeType, + ) { if (codeType == CodeType.singleDigit) { if (previousCodeSet == _codeA) { return _codeA; @@ -627,22 +642,28 @@ class Code128Renderer extends SymbologyRenderer { @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { final Paint paint = getBarPaint(foregroundColor); final List> encodedValue = _getEncodedValue(value); final int totalBarLength = _getTotalBarLength(encodedValue); - double left = symbology?.module == null - ? offset.dx - : getLeftPosition( - totalBarLength, symbology?.module, size.width, offset.dx); + double left = + symbology?.module == null + ? offset.dx + : getLeftPosition( + totalBarLength, + symbology?.module, + size.width, + offset.dx, + ); double ratio = 0; if (symbology?.module != null) { ratio = symbology!.module!.toDouble(); @@ -663,7 +684,11 @@ class Code128Renderer extends SymbologyRenderer { for (int k = 0; k < currentValue; k++) { if (canDraw) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + size.height); + left, + offset.dy, + left + ratio, + offset.dy + size.height, + ); canvas.drawRect(individualBarRect, paint); } left += ratio; 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 b23107edc..84713cf41 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 @@ -10,7 +10,9 @@ class Code128ARenderer extends Code128Renderer { bool getIsValidateInput(String value) { for (int i = 0; i < value.length; i++) { if (!code128ACharacterSets.contains(value[i])) { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; 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 5866107d4..83203783d 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 @@ -10,7 +10,9 @@ class Code128BRenderer extends Code128Renderer { bool getIsValidateInput(String value) { for (int i = 0; i < value.length; i++) { if (!code128BCharacterSets.contains(value[i])) { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; 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 a7ea75b8d..3a8a7fcd5 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 @@ -10,7 +10,9 @@ class Code128CRenderer extends Code128Renderer { bool getIsValidateInput(String value) { for (int i = 0; i < value.length; i++) { if (!code128CCharacterSets.contains(value[i])) { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; 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 34b75768a..203f6207c 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 @@ -145,7 +145,9 @@ class Code39ExtendedRenderer extends Code39Renderer { bool getIsValidateInput(String value) { for (int i = 0; i < value.length; i++) { if (value[i].codeUnitAt(0) > 127) { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; 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 73f041250..565b2cd0b 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 @@ -52,7 +52,7 @@ class Code39Renderer extends SymbologyRenderer { '121212111', '121211121', '121112121', - '111212121' + '111212121', ]; _character = _getCode39Character(); @@ -68,7 +68,9 @@ class Code39Renderer extends SymbologyRenderer { bool getIsValidateInput(String value) { for (int i = 0; i < value.length; i++) { if (!_character.contains(value[i])) { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; @@ -92,7 +94,9 @@ class Code39Renderer extends SymbologyRenderer { /// Returns the pattern collection based on the provided input List _getPatternCollection( - String providedValue, String code39Characters) { + String providedValue, + String code39Characters, + ) { final List code39Values = []; for (int i = 0; i < providedValue.length; i++) { final int currentIndex = code39Characters.indexOf(providedValue[i]); @@ -103,24 +107,34 @@ class Code39Renderer extends SymbologyRenderer { @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { final Paint paint = getBarPaint(foregroundColor); final List code = getCodeValues(value); final int barTotalLength = _getTotalLength(code); - double left = symbology?.module == null - ? offset.dx - : getLeftPosition( - barTotalLength, symbology?.module, size.width, offset.dx); + double left = + symbology?.module == null + ? offset.dx + : getLeftPosition( + barTotalLength, + symbology?.module, + size.width, + offset.dx, + ); final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology?.module != null) { ratio = symbology!.module!.toDouble(); @@ -143,8 +157,12 @@ class Code39Renderer extends SymbologyRenderer { if (canDraw && (left >= barCodeRect.left && left + (currentValue * ratio) < barCodeRect.right)) { - final Rect individualBarRect = Rect.fromLTRB(left, offset.dy, - left + (currentValue * ratio), offset.dy + barHeight); + final Rect individualBarRect = Rect.fromLTRB( + left, + offset.dy, + left + (currentValue * ratio), + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } left += currentValue * ratio; 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 b2629f9bb..ba2fce3bd 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 @@ -17,7 +17,9 @@ class Code93Renderer extends SymbologyRenderer { bool getIsValidateInput(String value) { for (int i = 0; i < value.length; i++) { if (!_character.contains(value[i])) { - throw 'The provided input cannot be encoded : ${value[i]}'; + throw ArgumentError( + 'The provided input cannot be encoded : ${value[i]}', + ); } } return true; @@ -131,24 +133,34 @@ class Code93Renderer extends SymbologyRenderer { @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(value); final int barTotalLength = _getTotalLength(code); - double left = symbology?.module == null - ? offset.dx - : getLeftPosition( - barTotalLength, symbology?.module, size.width, offset.dx); + double left = + symbology?.module == null + ? offset.dx + : getLeftPosition( + barTotalLength, + symbology?.module, + size.width, + offset.dx, + ); final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology?.module != null) { ratio = symbology!.module!.toDouble(); @@ -170,7 +182,11 @@ class Code93Renderer extends SymbologyRenderer { if (canDraw && (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); + left, + offset.dy, + left + ratio, + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } left += ratio; @@ -182,8 +198,11 @@ class Code93Renderer extends SymbologyRenderer { } /// Represents the pattern collection based on the provided input - List _getPatternCollection(String givenCharacter, - Map codes, List encodingValue) { + List _getPatternCollection( + String givenCharacter, + Map codes, + List encodingValue, + ) { final List codeKey = codes.keys.toList(); for (int i = 0; i < givenCharacter.length; i++) { final int index = codeKey.indexOf(givenCharacter[i]); 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 de8e57d78..427c51c20 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 @@ -18,16 +18,20 @@ class EAN13Renderer extends SymbologyRenderer { if (int.parse(value[12]) == _getCheckSumData(value)) { _encodedValue = value; } else { - throw 'Invalid check digit at the trailing end. ' - 'Provide the valid check digit or remove it. ' - 'Since, it has been calculated automatically.'; + throw ArgumentError( + 'Invalid check digit at the trailing end. ' + 'Provide the valid check digit or remove it. ' + 'Since, it has been calculated automatically.', + ); } } else if (value.contains(RegExp(r'^(?=.*?[0-9]).{12}$'))) { _encodedValue = value + _getCheckSumData(value).toString(); } else { - throw 'EAN13 supports only numeric characters. ' - 'The provided value should have 12 digits (without check digit) or' - ' with 13 digits.'; + throw ArgumentError( + 'EAN13 supports only numeric characters. ' + 'The provided value should have 12 digits (without check digit) or' + ' with 13 digits.', + ); } return true; } @@ -37,15 +41,16 @@ class EAN13Renderer extends SymbologyRenderer { /// to be passed @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { /// _singleDigitValues[0] specifies left value of start digit /// _singleDigitValues[1] specifies width of start digit final List singleDigitValues = List.filled(2, null); @@ -63,12 +68,21 @@ class EAN13Renderer extends SymbologyRenderer { const int additionalWidth = 2; 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]!); + 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); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology?.module != null) { ratio = symbology!.module!.toDouble(); @@ -85,12 +99,13 @@ class EAN13Renderer extends SymbologyRenderer { final String codeValue = code[i]; final bool hasExtraHeight = getHasExtraHeight(i, code); final double additionalHeight = i == 2 ? 0.4 : 0.5; - final double barHeight = hasExtraHeight - ? size.height + - (showValue - ? (textSize!.height * additionalHeight) + textSpacing - : 0) - : size.height; + final double barHeight = + hasExtraHeight + ? size.height + + (showValue + ? (textSize!.height * additionalHeight) + textSpacing + : 0) + : size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { // Draw the barcode when the current code value is 1 @@ -98,7 +113,11 @@ class EAN13Renderer extends SymbologyRenderer { if (canDraw && (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); + left, + offset.dy, + left + ratio, + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } left += ratio; @@ -118,8 +137,17 @@ class EAN13Renderer extends SymbologyRenderer { } } if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions, singleDigitValues); + _paintText( + canvas, + offset, + size, + _encodedValue, + textStyle, + textSpacing, + textAlign, + positions, + singleDigitValues, + ); } } @@ -149,28 +177,28 @@ class EAN13Renderer extends SymbologyRenderer { 'L': [ // The L (left) type of encoding '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' + '0110001', '0101111', '0111011', '0110111', '0001011', ], 'G': [ // The G type of encoding '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' + '0111001', '0000101', '0010001', '0001001', '0010111', ], 'R': [ // The R (right) type of encoding '1110010', '1100110', '1101100', '1000010', '1011100', - '1001110', '1010000', '1000100', '1001000', '1110100' + '1001110', '1010000', '1000100', '1001000', '1110100', ], 'O': [ // The O (odd) encoding for UPC-E '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' + '0110001', '0101111', '0111011', '0110111', '0001011', ], 'E': [ // The E (even) encoding for UPC-E '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] + '0111001', '0000101', '0010001', '0001001', '0010111', + ], }; } @@ -196,20 +224,22 @@ class EAN13Renderer extends SymbologyRenderer { '6': 'LGGGLL', '7': 'LGLGLG', '8': 'LGLGGL', - '9': 'LGGLGL' + '9': 'LGGLGL', }; } /// Method to calculate the check sum digit int _getCheckSumData(String value) { - final int sum1 = 3 * + final int sum1 = + 3 * (int.parse(value[11]) + int.parse(value[9]) + int.parse(value[7]) + int.parse(value[5]) + int.parse(value[3]) + int.parse(value[1])); - final int sum2 = int.parse(value[10]) + + final int sum2 = + int.parse(value[10]) + int.parse(value[8]) + int.parse(value[6]) + int.parse(value[4]) + @@ -250,15 +280,16 @@ class EAN13Renderer extends SymbologyRenderer { /// Method to render the input value of the barcode void _paintText( - Canvas canvas, - Offset offset, - Size size, - String value, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - List positions, - List singleDigitValues) { + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + List positions, + List singleDigitValues, + ) { final String value1 = value[0]; final String value2 = value.substring(1, 7); final String value3 = value.substring(7, 13); @@ -267,38 +298,41 @@ class EAN13Renderer extends SymbologyRenderer { // Renders the first digit of the input drawText( - canvas, - Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), - Size(singleDigitValues[1]!, size.height), - value1, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[1]!, size.height), + value1, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the first six digits of encoded text drawText( - canvas, - Offset(positions[0]!, offset.dy), - Size(secondTextWidth, size.height), - value2, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[0]!, offset.dy), + Size(secondTextWidth, size.height), + value2, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the second six digits of encoded text drawText( - canvas, - Offset(positions[2]!, offset.dy), - Size(thirdTextWidth, size.height), - value3, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[2]!, offset.dy), + Size(thirdTextWidth, size.height), + value3, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); } } 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 aca3b4aa2..c5b8321f2 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 @@ -17,31 +17,36 @@ class EAN8Renderer extends SymbologyRenderer { if (int.parse(value[7]) == _getCheckSumData(value)) { _encodedValue = value; } else { - throw 'Invalid check digit at the trailing end. ' - 'Provide the valid check digit or remove it. ' - 'Since, it has been calculated automatically.'; + throw ArgumentError( + 'Invalid check digit at the trailing end. ' + 'Provide the valid check digit or remove it. ' + 'Since, it has been calculated automatically.', + ); } } else if (value.contains(RegExp(r'^(?=.*?[0-9]).{7}$'))) { _encodedValue = value + _getCheckSumData(value).toString(); } else { - throw 'EAN8 supports only numeric characters.' - ' The provided value should have 7 digits (without check digit)' - ' or with 8 digits.'; + throw ArgumentError( + 'EAN8 supports only numeric characters.' + ' The provided value should have 7 digits (without check digit)' + ' or with 8 digits.', + ); } return true; } @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { /// _positions[0] specifies end position of start bar /// _positions[1] specifies start position of middle bar /// _positions[2] specifies end position of middle bar @@ -50,12 +55,21 @@ class EAN8Renderer extends SymbologyRenderer { final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(); final int barTotalLength = _getTotalLength(code); - double left = symbology?.module == null - ? offset.dx - : getLeftPosition( - barTotalLength, symbology?.module, size.width, offset.dx); + double left = + symbology?.module == null + ? offset.dx + : getLeftPosition( + barTotalLength, + symbology?.module, + size.width, + offset.dx, + ); final Rect barCodeRect = Rect.fromLTRB( - offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology?.module != null) { @@ -72,12 +86,13 @@ class EAN8Renderer extends SymbologyRenderer { final String codeValue = code[i]; final bool hasExtraHeight = getHasExtraHeight(i, code); final double additionalHeight = i == 2 ? 0.4 : 0.5; - final double barHeight = hasExtraHeight - ? size.height + - (showValue - ? (textSize!.height * additionalHeight) + textSpacing - : 0) - : size.height; + final double barHeight = + hasExtraHeight + ? size.height + + (showValue + ? (textSize!.height * additionalHeight) + textSpacing + : 0) + : size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { // Draw the barcode when the current code value is 1 @@ -85,7 +100,11 @@ class EAN8Renderer extends SymbologyRenderer { if (canDraw && (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); + left, + offset.dy, + left + ratio, + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } left += ratio; @@ -105,8 +124,16 @@ class EAN8Renderer extends SymbologyRenderer { } } if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions); + _paintText( + canvas, + offset, + size, + _encodedValue, + textStyle, + textSpacing, + textAlign, + positions, + ); } } @@ -138,9 +165,11 @@ class EAN8Renderer extends SymbologyRenderer { /// Represents the encoded value for the first 6 digits of the input value String _getLeftValue(Map codes, bool isLeft) { String code = ''; - for (int i = isLeft ? 0 : _encodedValue.length - 4; - i < (isLeft ? _encodedValue.length - 4 : _encodedValue.length); - i++) { + for ( + int i = isLeft ? 0 : _encodedValue.length - 4; + i < (isLeft ? _encodedValue.length - 4 : _encodedValue.length); + i++ + ) { final int currentValue = int.parse(_encodedValue[i]); if (i == 0 || i == 4) { code = codes.entries.elementAt(currentValue).value; @@ -153,10 +182,12 @@ class EAN8Renderer extends SymbologyRenderer { /// Method to calculate the input data int _getCheckSumData(String value) { + // ignore: dead_code for (int i = 0; i < value.length; i++) { final int sum1 = int.parse(value[1]) + int.parse(value[3]) + int.parse(value[5]); - final int sum2 = 3 * + final int sum2 = + 3 * (int.parse(value[0]) + int.parse(value[2]) + int.parse(value[4]) + @@ -195,7 +226,7 @@ class EAN8Renderer extends SymbologyRenderer { '6': '1010000', '7': '1000100', '8': '1001000', - '9': '1110100' + '9': '1110100', }; } return codes; @@ -203,14 +234,15 @@ class EAN8Renderer extends SymbologyRenderer { /// Method to render the input value of the barcode void _paintText( - Canvas canvas, - Offset offset, - Size size, - String value, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - List positions) { + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + List positions, + ) { final String value1 = value.substring(0, 4); final String value2 = value.substring(4, 8); final double firstTextWidth = positions[1]! - positions[0]!; @@ -218,26 +250,28 @@ class EAN8Renderer extends SymbologyRenderer { // Renders the first four digits of input drawText( - canvas, - Offset(positions[0]!, offset.dy), - Size(firstTextWidth, size.height), - value1, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[0]!, offset.dy), + Size(firstTextWidth, size.height), + value1, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the last four digits of input drawText( - canvas, - Offset(positions[2]!, offset.dy), - Size(secondTextWidth, size.height), - value2, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[2]!, offset.dy), + Size(secondTextWidth, size.height), + value2, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); } } 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 52a29d4e0..78619c40a 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 @@ -28,15 +28,16 @@ abstract class SymbologyRenderer { /// Method to render the barcode value void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue); + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ); /// Renders the paint for the bar code Paint getBarPaint(Color foregroundColor) { @@ -48,7 +49,11 @@ abstract class SymbologyRenderer { /// Calculates the left value of the initial bar code double getLeftPosition( - int barWidth, int? module, double width, double offsetX) { + 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 @@ -58,12 +63,23 @@ 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]) { + void drawText( + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, [ + Offset? actualOffset, + Size? actualSize, + ]) { final TextSpan span = TextSpan(text: value, style: textStyle); final TextPainter textPainter = TextPainter( - text: span, textDirection: TextDirection.ltr, textAlign: textAlign); + text: span, + textDirection: TextDirection.ltr, + textAlign: textAlign, + ); textPainter.layout(); double x; double y; 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 e0eff7300..cc45289ad 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 @@ -20,14 +20,18 @@ class UPCARenderer extends SymbologyRenderer { if (int.parse(value[11]) == _getCheckSumData(value)) { _encodedValue = value; } else { - throw 'Invalid check digit at the trailing end.' - ' Provide the valid check digit or remove it.' - ' Since, it has been calculated automatically.'; + throw ArgumentError( + 'Invalid check digit at the trailing end.' + ' Provide the valid check digit or remove it.' + ' Since, it has been calculated automatically.', + ); } } else { - throw 'UPCA supports only numeric characters. ' - 'The provided value should have 11 digits (without check digit) ' - 'or with 12 digits.'; + throw ArgumentError( + 'UPCA supports only numeric characters. ' + 'The provided value should have 11 digits (without check digit) ' + 'or with 12 digits.', + ); } return true; } @@ -37,15 +41,16 @@ class UPCARenderer extends SymbologyRenderer { /// to be passed @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(); final int barTotalLength = _getTotalLength(code); @@ -73,12 +78,21 @@ class UPCARenderer extends SymbologyRenderer { } 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]!); + 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); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology?.module != null) { ratio = symbology!.module!.toDouble(); @@ -96,19 +110,24 @@ class UPCARenderer extends SymbologyRenderer { final String codeValue = code[i]; final bool hasExtraHeight = getHasExtraHeight(i, code); final double additionalHeight = i == code.length - 4 ? 0.4 : 0.5; - final double barHeight = hasExtraHeight - ? size.height + - (showValue - ? (textSize!.height * additionalHeight) + textSpacing - : 0) - : size.height; + final double barHeight = + hasExtraHeight + ? size.height + + (showValue + ? (textSize!.height * additionalHeight) + textSpacing + : 0) + : size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { final bool canDraw = codeValue[j] == '1'; if (canDraw && (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); + left, + offset.dy, + left + ratio, + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } left += ratio; @@ -136,8 +155,17 @@ class UPCARenderer extends SymbologyRenderer { } if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions, singleDigitValues); + _paintText( + canvas, + offset, + size, + _encodedValue, + textStyle, + textSpacing, + textAlign, + positions, + singleDigitValues, + ); } } @@ -179,7 +207,7 @@ class UPCARenderer extends SymbologyRenderer { '0101111', '0111011', '0110111', - '0001011' + '0001011', ], 'R': [ '1110010', @@ -191,8 +219,8 @@ class UPCARenderer extends SymbologyRenderer { '1010000', '1000100', '1001000', - '1110100' - ] + '1110100', + ], }; return codes; @@ -222,14 +250,16 @@ class UPCARenderer extends SymbologyRenderer { /// Method to calculate the check sum digit int _getCheckSumData(String value) { - final int sum1 = 3 * + final int sum1 = + 3 * (int.parse(value[0]) + int.parse(value[2]) + int.parse(value[4]) + int.parse(value[6]) + int.parse(value[8]) + int.parse(value[10])); - final int sum2 = int.parse(value[9]) + + final int sum2 = + int.parse(value[9]) + int.parse(value[7]) + int.parse(value[5]) + int.parse(value[3]) + @@ -240,15 +270,16 @@ class UPCARenderer extends SymbologyRenderer { /// Method to render the input value of the barcode void _paintText( - Canvas canvas, - Offset offset, - Size size, - String value, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - List positions, - List singleDigitValues) { + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + List positions, + List singleDigitValues, + ) { final String value1 = value[0]; final String value2 = value.substring(1, 6); final String value3 = value.substring(6, 11); @@ -258,50 +289,54 @@ class UPCARenderer extends SymbologyRenderer { // Renders the first digit of encoded value drawText( - canvas, - Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), - Size(singleDigitValues[1]!, size.height), - value1, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[1]!, size.height), + value1, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the first five digits of encoded input value drawText( - canvas, - Offset(positions[0]!, offset.dy), - Size(secondTextWidth, size.height), - value2, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[0]!, offset.dy), + Size(secondTextWidth, size.height), + value2, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the second five digits of encoded input value drawText( - canvas, - Offset(positions[2]!, offset.dy), - Size(thirdTextWidth, size.height), - value3, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[2]!, offset.dy), + Size(thirdTextWidth, size.height), + value3, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the last digit of the encoded input value drawText( - canvas, - Offset(singleDigitValues[2]!, offset.dy + size.height + textSpacing), - Size(singleDigitValues[3]!, size.height), - value[value.length - 1], - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(singleDigitValues[2]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[3]!, size.height), + value[value.length - 1], + textStyle, + textSpacing, + textAlign, + offset, + size, + ); } } 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 c93d77a9d..38b944db8 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 @@ -17,8 +17,10 @@ class UPCERenderer extends SymbologyRenderer { if (value.contains(RegExp(r'^(?=.*?[0-9]).{6}$'))) { return true; } - throw 'UPCE supports only numeric characters. ' - 'The provided value should have 6 digits.'; + throw ArgumentError( + 'UPCE supports only numeric characters. ' + 'The provided value should have 6 digits.', + ); } /// This is quite a large method. This method could not be @@ -26,15 +28,16 @@ class UPCERenderer extends SymbologyRenderer { /// to be passed @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(value); final int barTotalLength = _getTotalLength(code); @@ -61,12 +64,21 @@ class UPCERenderer extends SymbologyRenderer { final double width = size.width - (singleDigitValues[1]! + singleDigitValues[3]!); - double left = symbology?.module == null - ? offset.dx - : getLeftPosition(barTotalLength, symbology?.module, width, - offset.dx + singleDigitValues[1]!); + double left = + symbology?.module == null + ? offset.dx + : 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); + offset.dx, + offset.dy, + offset.dx + size.width, + offset.dy + size.height, + ); double ratio = 0; if (symbology?.module != null) { ratio = symbology!.module!.toDouble(); @@ -81,17 +93,22 @@ class UPCERenderer extends SymbologyRenderer { for (int i = 0; i < code.length; i++) { final String codeValue = code[i]; final bool hasExtraHeight = getHasExtraHeight(i, code); - final double barHeight = hasExtraHeight - ? size.height + - (showValue ? (textSize!.height * 0.5) + textSpacing : 0) - : size.height; + final double barHeight = + hasExtraHeight + ? size.height + + (showValue ? (textSize!.height * 0.5) + textSpacing : 0) + : size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { final bool canDraw = codeValue[j] == '1'; if (canDraw && (left >= barCodeRect.left && left + ratio < barCodeRect.right)) { final Rect individualBarRect = Rect.fromLTRB( - left, offset.dy, left + ratio, offset.dy + barHeight); + left, + offset.dy, + left + ratio, + offset.dy + barHeight, + ); canvas.drawRect(individualBarRect, paint); } @@ -113,22 +130,32 @@ class UPCERenderer extends SymbologyRenderer { } } if (showValue) { - _paintText(canvas, offset, size, _encodedValue, textStyle, textSpacing, - textAlign, positions, singleDigitValues); + _paintText( + canvas, + offset, + size, + _encodedValue, + textStyle, + textSpacing, + textAlign, + positions, + singleDigitValues, + ); } } /// Method to render the input value of the barcode void _paintText( - Canvas canvas, - Offset offset, - Size size, - String value, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - List positions, - List singleDigitValues) { + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + List positions, + List singleDigitValues, + ) { const String value1 = '0'; final String value2 = value.substring(1, 7); @@ -136,37 +163,40 @@ class UPCERenderer extends SymbologyRenderer { // Renders the first digit of encoded value drawText( - canvas, - Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), - Size(singleDigitValues[1]!, size.height), - value1, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[1]!, size.height), + value1, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); //Renders the middle six digits of encoded value drawText( - canvas, - Offset(positions[0]!, offset.dy), - Size(secondTextWidth, size.height), - value2, - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(positions[0]!, offset.dy), + Size(secondTextWidth, size.height), + value2, + textStyle, + textSpacing, + textAlign, + offset, + size, + ); // Renders the last digit of encoded value drawText( - canvas, - Offset(singleDigitValues[2]!, offset.dy + size.height + textSpacing), - Size(singleDigitValues[3]!, size.height), - value[value.length - 1], - textStyle, - textSpacing, - textAlign, - offset, - size); + canvas, + Offset(singleDigitValues[2]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[3]!, size.height), + value[value.length - 1], + textStyle, + textSpacing, + textAlign, + offset, + size, + ); } /// Calculate the total length from given value @@ -203,7 +233,7 @@ class UPCERenderer extends SymbologyRenderer { '6': 'EOOOEE', '7': 'EOEOEO', '8': 'EOEOOE', - '9': 'EOOEOE' + '9': 'EOOEOE', }; return upceSymbology; } @@ -220,7 +250,7 @@ class UPCERenderer extends SymbologyRenderer { 'XXXXX00006', 'XXXXX00007', 'XXXXX00008', - 'XXXXX00009' + 'XXXXX00009', ]; } @@ -266,13 +296,13 @@ class UPCERenderer extends SymbologyRenderer { '0101111', '0111011', '0110111', - '0001011' + '0001011', ], 'E': [ // The E (even) encoding for UPC-E '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] + '0111001', '0000101', '0010001', '0001001', '0010111', + ], }; } 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 d40d4fd4a..b4781413b 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 @@ -1,7 +1,7 @@ import 'dart:convert' show utf8; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_barcodes/barcodes.dart'; +import '../../../../barcodes.dart'; import '../../utils/helper.dart'; import '../one_dimensional/symbology_base_renderer.dart'; @@ -54,277 +54,307 @@ class DataMatrixRenderer extends SymbologyRenderer { void _initialize() { _symbolAttributes = <_DataMatrixSymbolAttribute>[ _DataMatrixSymbolAttribute( - symbolRow: 10, - symbolColumn: 10, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 3, - correctionCodeWords: 5, - interLeavedBlock: 1, - interLeavedDataBlock: 3), + symbolRow: 10, + symbolColumn: 10, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 3, + correctionCodeWords: 5, + interLeavedBlock: 1, + interLeavedDataBlock: 3, + ), _DataMatrixSymbolAttribute( - symbolRow: 12, - symbolColumn: 12, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 5, - correctionCodeWords: 7, - interLeavedBlock: 1, - interLeavedDataBlock: 5), + symbolRow: 12, + symbolColumn: 12, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 5, + correctionCodeWords: 7, + interLeavedBlock: 1, + interLeavedDataBlock: 5, + ), _DataMatrixSymbolAttribute( - symbolRow: 14, - symbolColumn: 14, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 8, - correctionCodeWords: 10, - interLeavedBlock: 1, - interLeavedDataBlock: 8), + symbolRow: 14, + symbolColumn: 14, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 8, + correctionCodeWords: 10, + interLeavedBlock: 1, + interLeavedDataBlock: 8, + ), _DataMatrixSymbolAttribute( - symbolRow: 16, - symbolColumn: 16, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 12, - correctionCodeWords: 12, - interLeavedBlock: 1, - interLeavedDataBlock: 12), + symbolRow: 16, + symbolColumn: 16, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 12, + correctionCodeWords: 12, + interLeavedBlock: 1, + interLeavedDataBlock: 12, + ), _DataMatrixSymbolAttribute( - symbolRow: 18, - symbolColumn: 18, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 18, - correctionCodeWords: 14, - interLeavedBlock: 1, - interLeavedDataBlock: 18), + symbolRow: 18, + symbolColumn: 18, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 18, + correctionCodeWords: 14, + interLeavedBlock: 1, + interLeavedDataBlock: 18, + ), _DataMatrixSymbolAttribute( - symbolRow: 20, - symbolColumn: 20, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 22, - correctionCodeWords: 18, - interLeavedBlock: 1, - interLeavedDataBlock: 22), + symbolRow: 20, + symbolColumn: 20, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 22, + correctionCodeWords: 18, + interLeavedBlock: 1, + interLeavedDataBlock: 22, + ), _DataMatrixSymbolAttribute( - symbolRow: 22, - symbolColumn: 22, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 30, - correctionCodeWords: 20, - interLeavedBlock: 1, - interLeavedDataBlock: 30), + symbolRow: 22, + symbolColumn: 22, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 30, + correctionCodeWords: 20, + interLeavedBlock: 1, + interLeavedDataBlock: 30, + ), _DataMatrixSymbolAttribute( - symbolRow: 24, - symbolColumn: 24, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 36, - correctionCodeWords: 24, - interLeavedBlock: 1, - interLeavedDataBlock: 36), + symbolRow: 24, + symbolColumn: 24, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 36, + correctionCodeWords: 24, + interLeavedBlock: 1, + interLeavedDataBlock: 36, + ), _DataMatrixSymbolAttribute( - symbolRow: 26, - symbolColumn: 26, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 44, - correctionCodeWords: 28, - interLeavedBlock: 1, - interLeavedDataBlock: 44), + symbolRow: 26, + symbolColumn: 26, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 44, + correctionCodeWords: 28, + interLeavedBlock: 1, + interLeavedDataBlock: 44, + ), _DataMatrixSymbolAttribute( - symbolRow: 32, - symbolColumn: 32, - horizontalDataRegion: 2, - verticalDataRegion: 2, - dataCodeWords: 62, - correctionCodeWords: 36, - interLeavedBlock: 1, - interLeavedDataBlock: 62), + symbolRow: 32, + symbolColumn: 32, + horizontalDataRegion: 2, + verticalDataRegion: 2, + dataCodeWords: 62, + correctionCodeWords: 36, + interLeavedBlock: 1, + interLeavedDataBlock: 62, + ), _DataMatrixSymbolAttribute( - symbolRow: 36, - symbolColumn: 36, - horizontalDataRegion: 2, - verticalDataRegion: 2, - dataCodeWords: 86, - correctionCodeWords: 42, - interLeavedBlock: 1, - interLeavedDataBlock: 86), + symbolRow: 36, + symbolColumn: 36, + horizontalDataRegion: 2, + verticalDataRegion: 2, + dataCodeWords: 86, + correctionCodeWords: 42, + interLeavedBlock: 1, + interLeavedDataBlock: 86, + ), _DataMatrixSymbolAttribute( - symbolRow: 40, - symbolColumn: 40, - horizontalDataRegion: 2, - verticalDataRegion: 2, - dataCodeWords: 114, - correctionCodeWords: 48, - interLeavedBlock: 1, - interLeavedDataBlock: 114), + symbolRow: 40, + symbolColumn: 40, + horizontalDataRegion: 2, + verticalDataRegion: 2, + dataCodeWords: 114, + correctionCodeWords: 48, + interLeavedBlock: 1, + interLeavedDataBlock: 114, + ), _DataMatrixSymbolAttribute( - symbolRow: 44, - symbolColumn: 44, - horizontalDataRegion: 2, - verticalDataRegion: 2, - dataCodeWords: 144, - correctionCodeWords: 56, - interLeavedBlock: 1, - interLeavedDataBlock: 144), + symbolRow: 44, + symbolColumn: 44, + horizontalDataRegion: 2, + verticalDataRegion: 2, + dataCodeWords: 144, + correctionCodeWords: 56, + interLeavedBlock: 1, + interLeavedDataBlock: 144, + ), _DataMatrixSymbolAttribute( - symbolRow: 48, - symbolColumn: 48, - horizontalDataRegion: 2, - verticalDataRegion: 2, - dataCodeWords: 174, - correctionCodeWords: 68, - interLeavedBlock: 1, - interLeavedDataBlock: 174), + symbolRow: 48, + symbolColumn: 48, + horizontalDataRegion: 2, + verticalDataRegion: 2, + dataCodeWords: 174, + correctionCodeWords: 68, + interLeavedBlock: 1, + interLeavedDataBlock: 174, + ), _DataMatrixSymbolAttribute( - symbolRow: 52, - symbolColumn: 52, - horizontalDataRegion: 2, - verticalDataRegion: 2, - dataCodeWords: 204, - correctionCodeWords: 84, - interLeavedBlock: 2, - interLeavedDataBlock: 102), + symbolRow: 52, + symbolColumn: 52, + horizontalDataRegion: 2, + verticalDataRegion: 2, + dataCodeWords: 204, + correctionCodeWords: 84, + interLeavedBlock: 2, + interLeavedDataBlock: 102, + ), _DataMatrixSymbolAttribute( - symbolRow: 64, - symbolColumn: 64, - horizontalDataRegion: 4, - verticalDataRegion: 4, - dataCodeWords: 280, - correctionCodeWords: 112, - interLeavedBlock: 2, - interLeavedDataBlock: 140), + symbolRow: 64, + symbolColumn: 64, + horizontalDataRegion: 4, + verticalDataRegion: 4, + dataCodeWords: 280, + correctionCodeWords: 112, + interLeavedBlock: 2, + interLeavedDataBlock: 140, + ), _DataMatrixSymbolAttribute( - symbolRow: 72, - symbolColumn: 72, - horizontalDataRegion: 4, - verticalDataRegion: 4, - dataCodeWords: 368, - correctionCodeWords: 144, - interLeavedBlock: 4, - interLeavedDataBlock: 92), + symbolRow: 72, + symbolColumn: 72, + horizontalDataRegion: 4, + verticalDataRegion: 4, + dataCodeWords: 368, + correctionCodeWords: 144, + interLeavedBlock: 4, + interLeavedDataBlock: 92, + ), _DataMatrixSymbolAttribute( - symbolRow: 80, - symbolColumn: 80, - horizontalDataRegion: 4, - verticalDataRegion: 4, - dataCodeWords: 456, - correctionCodeWords: 192, - interLeavedBlock: 4, - interLeavedDataBlock: 114), + symbolRow: 80, + symbolColumn: 80, + horizontalDataRegion: 4, + verticalDataRegion: 4, + dataCodeWords: 456, + correctionCodeWords: 192, + interLeavedBlock: 4, + interLeavedDataBlock: 114, + ), _DataMatrixSymbolAttribute( - symbolRow: 88, - symbolColumn: 88, - horizontalDataRegion: 4, - verticalDataRegion: 4, - dataCodeWords: 576, - correctionCodeWords: 224, - interLeavedBlock: 4, - interLeavedDataBlock: 144), + symbolRow: 88, + symbolColumn: 88, + horizontalDataRegion: 4, + verticalDataRegion: 4, + dataCodeWords: 576, + correctionCodeWords: 224, + interLeavedBlock: 4, + interLeavedDataBlock: 144, + ), _DataMatrixSymbolAttribute( - symbolRow: 96, - symbolColumn: 96, - horizontalDataRegion: 4, - verticalDataRegion: 4, - dataCodeWords: 696, - correctionCodeWords: 272, - interLeavedBlock: 4, - interLeavedDataBlock: 174), + symbolRow: 96, + symbolColumn: 96, + horizontalDataRegion: 4, + verticalDataRegion: 4, + dataCodeWords: 696, + correctionCodeWords: 272, + interLeavedBlock: 4, + interLeavedDataBlock: 174, + ), _DataMatrixSymbolAttribute( - symbolRow: 104, - symbolColumn: 104, - horizontalDataRegion: 4, - verticalDataRegion: 4, - dataCodeWords: 816, - correctionCodeWords: 336, - interLeavedBlock: 6, - interLeavedDataBlock: 136), + symbolRow: 104, + symbolColumn: 104, + horizontalDataRegion: 4, + verticalDataRegion: 4, + dataCodeWords: 816, + correctionCodeWords: 336, + interLeavedBlock: 6, + interLeavedDataBlock: 136, + ), _DataMatrixSymbolAttribute( - symbolRow: 120, - symbolColumn: 120, - horizontalDataRegion: 6, - verticalDataRegion: 6, - dataCodeWords: 1050, - correctionCodeWords: 408, - interLeavedBlock: 6, - interLeavedDataBlock: 175), + symbolRow: 120, + symbolColumn: 120, + horizontalDataRegion: 6, + verticalDataRegion: 6, + dataCodeWords: 1050, + correctionCodeWords: 408, + interLeavedBlock: 6, + interLeavedDataBlock: 175, + ), _DataMatrixSymbolAttribute( - symbolRow: 132, - symbolColumn: 132, - horizontalDataRegion: 6, - verticalDataRegion: 6, - dataCodeWords: 1304, - correctionCodeWords: 496, - interLeavedBlock: 8, - interLeavedDataBlock: 163), + symbolRow: 132, + symbolColumn: 132, + horizontalDataRegion: 6, + verticalDataRegion: 6, + dataCodeWords: 1304, + correctionCodeWords: 496, + interLeavedBlock: 8, + interLeavedDataBlock: 163, + ), _DataMatrixSymbolAttribute( - symbolRow: 144, - symbolColumn: 144, - horizontalDataRegion: 6, - verticalDataRegion: 6, - dataCodeWords: 1558, - correctionCodeWords: 620, - interLeavedBlock: 10, - interLeavedDataBlock: 156), + symbolRow: 144, + symbolColumn: 144, + horizontalDataRegion: 6, + verticalDataRegion: 6, + dataCodeWords: 1558, + correctionCodeWords: 620, + interLeavedBlock: 10, + interLeavedDataBlock: 156, + ), // Rectangle matrix _DataMatrixSymbolAttribute( - symbolRow: 8, - symbolColumn: 18, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 5, - correctionCodeWords: 7, - interLeavedBlock: 1, - interLeavedDataBlock: 5), + symbolRow: 8, + symbolColumn: 18, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 5, + correctionCodeWords: 7, + interLeavedBlock: 1, + interLeavedDataBlock: 5, + ), _DataMatrixSymbolAttribute( - symbolRow: 8, - symbolColumn: 32, - horizontalDataRegion: 2, - verticalDataRegion: 1, - dataCodeWords: 10, - correctionCodeWords: 11, - interLeavedBlock: 1, - interLeavedDataBlock: 10), + symbolRow: 8, + symbolColumn: 32, + horizontalDataRegion: 2, + verticalDataRegion: 1, + dataCodeWords: 10, + correctionCodeWords: 11, + interLeavedBlock: 1, + interLeavedDataBlock: 10, + ), _DataMatrixSymbolAttribute( - symbolRow: 12, - symbolColumn: 26, - horizontalDataRegion: 1, - verticalDataRegion: 1, - dataCodeWords: 16, - correctionCodeWords: 14, - interLeavedBlock: 1, - interLeavedDataBlock: 16), + symbolRow: 12, + symbolColumn: 26, + horizontalDataRegion: 1, + verticalDataRegion: 1, + dataCodeWords: 16, + correctionCodeWords: 14, + interLeavedBlock: 1, + interLeavedDataBlock: 16, + ), _DataMatrixSymbolAttribute( - symbolRow: 12, - symbolColumn: 36, - horizontalDataRegion: 2, - verticalDataRegion: 1, - dataCodeWords: 22, - correctionCodeWords: 18, - interLeavedBlock: 1, - interLeavedDataBlock: 22), + symbolRow: 12, + symbolColumn: 36, + horizontalDataRegion: 2, + verticalDataRegion: 1, + dataCodeWords: 22, + correctionCodeWords: 18, + interLeavedBlock: 1, + interLeavedDataBlock: 22, + ), _DataMatrixSymbolAttribute( - symbolRow: 16, - symbolColumn: 36, - horizontalDataRegion: 2, - verticalDataRegion: 1, - dataCodeWords: 32, - correctionCodeWords: 24, - interLeavedBlock: 1, - interLeavedDataBlock: 32), + symbolRow: 16, + symbolColumn: 36, + horizontalDataRegion: 2, + verticalDataRegion: 1, + dataCodeWords: 32, + correctionCodeWords: 24, + interLeavedBlock: 1, + interLeavedDataBlock: 32, + ), _DataMatrixSymbolAttribute( - symbolRow: 16, - symbolColumn: 48, - horizontalDataRegion: 2, - verticalDataRegion: 1, - dataCodeWords: 49, - correctionCodeWords: 28, - interLeavedBlock: 1, - interLeavedDataBlock: 49) + symbolRow: 16, + symbolColumn: 48, + horizontalDataRegion: 2, + verticalDataRegion: 1, + dataCodeWords: 49, + correctionCodeWords: 28, + interLeavedBlock: 1, + interLeavedDataBlock: 49, + ), ]; _createLogList(); @@ -393,9 +423,10 @@ class DataMatrixRenderer extends SymbologyRenderer { if (v == 1 || v > 7 && (codeword[(v >> 3) - 1]! & (1 << (v & 7))) != 0) { matrix[(1 + y + 2 * (y ~/ (fieldHeight - 2))) * width + - 1 + - x + - 2 * (x ~/ (fieldWidth - 2))] = 1; + 1 + + x + + 2 * (x ~/ (fieldWidth - 2))] = + 1; } } } @@ -409,7 +440,9 @@ class DataMatrixRenderer extends SymbologyRenderer { symbolRow = _symbolAttribute.symbolRow!; final List> tempArray = List>.generate( - symbolColumn, (int j) => List.filled(symbolRow, null)); + symbolColumn, + (int j) => List.filled(symbolRow, null), + ); for (int m = 0; m < symbolColumn; m++) { for (int n = 0; n < symbolRow; n++) { @@ -418,7 +451,9 @@ class DataMatrixRenderer extends SymbologyRenderer { } final List> tempArray2 = List>.generate( - symbolRow, (int j) => List.filled(symbolColumn, null)); + symbolRow, + (int j) => List.filled(symbolColumn, null), + ); for (int i = 0; i < symbolRow; i++) { for (int j = 0; j < symbolColumn; j++) { @@ -434,8 +469,10 @@ class DataMatrixRenderer extends SymbologyRenderer { 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.filled(h, null)); + _dataMatrixList = List>.generate( + w, + (int j) => List.filled(h, null), + ); // Top quietzone. for (int i = 0; i < h; i++) { _dataMatrixList[0][i] = 0; @@ -458,8 +495,15 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method to encode the error correcting code word - void _errorCorrectingCode200PlacementBit(List array, int numOfRows, - int numOfColumns, int row, int column, int place, String character) { + void _errorCorrectingCode200PlacementBit( + List array, + int numOfRows, + int numOfColumns, + int row, + int column, + int place, + String character, + ) { if (row < 0) { row += numOfRows; column += 4 - ((numOfRows + 4) % 8); @@ -475,112 +519,417 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerA( - List array, int numOfRows, int numOfColumns, int place) { - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, 0, place, String.fromCharCode(7)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, 1, place, String.fromCharCode(6)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, 2, place, String.fromCharCode(5)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 2, place, String.fromCharCode(4)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 1, place, String.fromCharCode(3)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 1, - numOfColumns - 1, place, String.fromCharCode(2)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 2, - numOfColumns - 1, place, String.fromCharCode(1)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 3, - numOfColumns - 1, place, String.fromCharCode(0)); + List array, + int numOfRows, + int numOfColumns, + int place, + ) { + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + 0, + place, + String.fromCharCode(7), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + 1, + place, + String.fromCharCode(6), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + 2, + place, + String.fromCharCode(5), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 2, + place, + String.fromCharCode(4), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 1, + place, + String.fromCharCode(3), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 1, + numOfColumns - 1, + place, + String.fromCharCode(2), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 2, + numOfColumns - 1, + place, + String.fromCharCode(1), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 3, + numOfColumns - 1, + place, + String.fromCharCode(0), + ); } /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerB( - List array, int numOfRows, int numOfColumns, int place) { - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 3, 0, place, String.fromCharCode(7)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 2, 0, place, String.fromCharCode(6)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, 0, place, String.fromCharCode(5)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 4, place, String.fromCharCode(4)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 3, place, String.fromCharCode(3)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 2, place, String.fromCharCode(2)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 1, place, String.fromCharCode(1)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 1, - numOfColumns - 1, place, String.fromCharCode(0)); + List array, + int numOfRows, + int numOfColumns, + int place, + ) { + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 3, + 0, + place, + String.fromCharCode(7), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 2, + 0, + place, + String.fromCharCode(6), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + 0, + place, + String.fromCharCode(5), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 4, + place, + String.fromCharCode(4), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 3, + place, + String.fromCharCode(3), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 2, + place, + String.fromCharCode(2), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 1, + place, + String.fromCharCode(1), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 1, + numOfColumns - 1, + place, + String.fromCharCode(0), + ); } /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerC( - List array, int numOfRows, int numOfColumns, int place) { - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 3, 0, place, String.fromCharCode(7)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 2, 0, place, String.fromCharCode(6)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, 0, place, String.fromCharCode(5)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 2, place, String.fromCharCode(4)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 1, place, String.fromCharCode(3)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 1, - numOfColumns - 1, place, String.fromCharCode(2)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 2, - numOfColumns - 1, place, String.fromCharCode(1)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 3, - numOfColumns - 1, place, String.fromCharCode(0)); + List array, + int numOfRows, + int numOfColumns, + int place, + ) { + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 3, + 0, + place, + String.fromCharCode(7), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 2, + 0, + place, + String.fromCharCode(6), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + 0, + place, + String.fromCharCode(5), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 2, + place, + String.fromCharCode(4), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 1, + place, + String.fromCharCode(3), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 1, + numOfColumns - 1, + place, + String.fromCharCode(2), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 2, + numOfColumns - 1, + place, + String.fromCharCode(1), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 3, + numOfColumns - 1, + place, + String.fromCharCode(0), + ); } /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerD( - List array, int numOfRows, int numOfColumns, int place) { - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, 0, place, String.fromCharCode(7)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, - numOfRows - 1, numOfColumns - 1, place, String.fromCharCode(6)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 3, place, String.fromCharCode(5)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 2, place, String.fromCharCode(4)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 0, - numOfColumns - 1, place, String.fromCharCode(3)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 1, - numOfColumns - 3, place, String.fromCharCode(2)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 1, - numOfColumns - 2, place, String.fromCharCode(1)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, 1, - numOfColumns - 1, place, String.fromCharCode(0)); + List array, + int numOfRows, + int numOfColumns, + int place, + ) { + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + 0, + place, + String.fromCharCode(7), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + numOfRows - 1, + numOfColumns - 1, + place, + String.fromCharCode(6), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 3, + place, + String.fromCharCode(5), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 2, + place, + String.fromCharCode(4), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 0, + numOfColumns - 1, + place, + String.fromCharCode(3), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 1, + numOfColumns - 3, + place, + String.fromCharCode(2), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 1, + numOfColumns - 2, + place, + String.fromCharCode(1), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + 1, + numOfColumns - 1, + place, + String.fromCharCode(0), + ); } /// Method to encode the error correcting code word - 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)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row - 2, - column - 1, place, String.fromCharCode(6)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row - 1, - column - 2, place, String.fromCharCode(5)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row - 1, - column - 1, place, String.fromCharCode(4)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row - 1, - column, place, String.fromCharCode(3)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row, - column - 2, place, String.fromCharCode(2)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row, - column - 1, place, String.fromCharCode(1)); - _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row, - column, place, String.fromCharCode(0)); + 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), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row - 2, + column - 1, + place, + String.fromCharCode(6), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row - 1, + column - 2, + place, + String.fromCharCode(5), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row - 1, + column - 1, + place, + String.fromCharCode(4), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row - 1, + column, + place, + String.fromCharCode(3), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row, + column - 2, + place, + String.fromCharCode(2), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row, + column - 1, + place, + String.fromCharCode(1), + ); + _errorCorrectingCode200PlacementBit( + array, + numOfRows, + numOfColumns, + row, + column, + place, + String.fromCharCode(0), + ); } /// 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++) { @@ -594,24 +943,40 @@ class DataMatrixRenderer extends SymbologyRenderer { do { if (row == numOfRows && !(column != 0)) { _errorCorrectingCode200PlacementCornerA( - array, numOfRows, numOfColumns, place++); + array, + numOfRows, + numOfColumns, + place++, + ); } if ((row == numOfRows - 2) && !(column != 0) && ((numOfColumns % 4) != 0)) { _errorCorrectingCode200PlacementCornerB( - array, numOfRows, numOfColumns, place++); + array, + numOfRows, + numOfColumns, + place++, + ); } if (row == numOfRows - 2 && !(column != 0) && (numOfColumns % 8) == 4) { _errorCorrectingCode200PlacementCornerC( - array, numOfRows, numOfColumns, place++); + array, + numOfRows, + numOfColumns, + place++, + ); } if (row == numOfRows + 4 && column == 2 && !((numOfColumns % 8) != 0)) { _errorCorrectingCode200PlacementCornerD( - array, numOfRows, numOfColumns, place++); + array, + numOfRows, + numOfColumns, + place++, + ); } // enocoding placement (up/right) do { @@ -619,7 +984,13 @@ class DataMatrixRenderer extends SymbologyRenderer { column >= 0 && !(array[row * numOfColumns + column] != 0)) { _errorCorrectingCode200PlacementBlock( - array, numOfRows, numOfColumns, row, column, place++); + array, + numOfRows, + numOfColumns, + row, + column, + place++, + ); } row -= 2; @@ -633,7 +1004,13 @@ class DataMatrixRenderer extends SymbologyRenderer { column < numOfColumns && !(array[row * numOfColumns + column] != 0)) { _errorCorrectingCode200PlacementBlock( - array, numOfRows, numOfColumns, row, column, place++); + array, + numOfRows, + numOfColumns, + row, + column, + place++, + ); } row += 2; @@ -690,6 +1067,7 @@ class DataMatrixRenderer extends SymbologyRenderer { length = codewords.length; while (length < dataCWLength) { + // ignore: no_leading_underscores_for_local_identifiers int _value = 129 + (((length + 1) * 149) % 253) + 1; if (_value > 254) { _value -= 254; @@ -709,8 +1087,10 @@ class DataMatrixRenderer extends SymbologyRenderer { num++; } - final List result = - List.filled((1 + num) + dataCodeword.length, null); + final List result = List.filled( + (1 + num) + dataCodeword.length, + null, + ); result[0] = 231; if (dataCodeword.length <= 249) { result[1] = dataCodeword.length; @@ -754,13 +1134,16 @@ class DataMatrixRenderer extends SymbologyRenderer { } } - final List result = - List.filled(destinationArray.length ~/ 2, null); + 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; } else { - result[i] = (((destinationArray[2 * i]! - 48) * 10) + + result[i] = + (((destinationArray[2 * i]! - 48) * 10) + (destinationArray[(2 * i) + 1]! - 48)) + 130; } @@ -814,7 +1197,9 @@ class DataMatrixRenderer extends SymbologyRenderer { final int numCorrectionCodeword = _symbolAttribute.correctionCodeWords!; // Create error correction array. final List correctionCodeWordArray = List.filled( - numCorrectionCodeword + _symbolAttribute.dataCodeWords!, null); + numCorrectionCodeword + _symbolAttribute.dataCodeWords!, + null, + ); for (int i = 0; i < correctionCodeWordArray.length; i++) { correctionCodeWordArray[i] = 0; } @@ -835,10 +1220,14 @@ 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); @@ -858,18 +1247,22 @@ class DataMatrixRenderer extends SymbologyRenderer { } if (bIndex != 0) { - throw 'Error in error correction code generation!'; + throw ArgumentError('Error in error correction code generation!'); } } } return _getCorrectionCodeWordArray( - correctionCodeWordArray, numCorrectionCodeword); + correctionCodeWordArray, + numCorrectionCodeword, + ); } /// Method to get the correction code word List _getCorrectionCodeWordArray( - List correctionCodeWordArray, int numCorrectionCodeword) { + List correctionCodeWordArray, + int numCorrectionCodeword, + ) { if (correctionCodeWordArray.length > numCorrectionCodeword) { final List tmp = correctionCodeWordArray; correctionCodeWordArray = List.filled(numCorrectionCodeword, null); @@ -900,8 +1293,11 @@ class DataMatrixRenderer extends SymbologyRenderer { } } } else { - _symbolAttribute = _symbolAttributes[ - getDataMatrixSize(_dataMatrixSymbology.dataMatrixSize) - 1]; + _symbolAttribute = + _symbolAttributes[getDataMatrixSize( + _dataMatrixSymbology.dataMatrixSize, + ) - + 1]; } List temp; @@ -912,11 +1308,13 @@ class DataMatrixRenderer extends SymbologyRenderer { _encodedCodeword = List.from(temp); dataLength = codeword.length; } else if (_symbolAttribute.dataCodeWords == 0) { - throw 'Data cannot be encoded as barcode'; + throw ArgumentError('Data cannot be encoded as barcode'); } 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.'; + throw ArgumentError( + 'Data too long for $symbolRow x $symbolColumn barcode.', + ); } } @@ -934,8 +1332,10 @@ class DataMatrixRenderer extends SymbologyRenderer { for (int j = i - 1; j >= 0; j--) { _polynomial[j] = _getErrorCorrectingCodeDoublify(_polynomial[j]!, i); if (j > 0) { - _polynomial[j] = - _getErrorCorrectingCodeSum(_polynomial[j]!, _polynomial[j - 1]!); + _polynomial[j] = _getErrorCorrectingCodeSum( + _polynomial[j]!, + _polynomial[j - 1]!, + ); } } } @@ -944,10 +1344,13 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to get the code word List _getCodeword(List dataCodeword) { _encodedCodeword = _getDataCodeword(dataCodeword); - final List correctCodeword = - _getComputedErrorCorrection(_encodedCodeword!); + final List correctCodeword = _getComputedErrorCorrection( + _encodedCodeword!, + ); final List finalCodeword = List.filled( - _encodedCodeword!.length + correctCodeword.length, null); + _encodedCodeword!.length + correctCodeword.length, + null, + ); for (int i = 0; i < _encodedCodeword!.length; i++) { finalCodeword[i] = _encodedCodeword![i]; } @@ -991,7 +1394,9 @@ class DataMatrixRenderer extends SymbologyRenderer { if (actualEncoding == DataMatrixEncoding.asciiNumeric && _dataMatrixSymbology.encoding != actualEncoding) { - throw 'Data contains invalid characters and cannot be encoded as ASCIINumeric.'; + throw ArgumentError( + 'Data contains invalid characters and cannot be encoded as ASCIINumeric.', + ); } _encoding = actualEncoding; @@ -1027,15 +1432,16 @@ class DataMatrixRenderer extends SymbologyRenderer { @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { _inputValue = value; _buildDataMatrix(); @@ -1050,7 +1456,8 @@ class DataMatrixRenderer extends SymbologyRenderer { getDataMatrixSize(_dataMatrixSymbology.dataMatrixSize) < 25; int dimension = minSize ~/ _dataMatrixList.length; final int rectDimension = minSize ~/ _dataMatrixList[0].length; - final int xDimension = _dataMatrixSymbology.module ?? + final int xDimension = + _dataMatrixSymbology.module ?? (isSquareMatrix ? dimension : rectDimension); dimension = _dataMatrixSymbology.module ?? dimension; for (int i = 0; i < w; i++) { @@ -1070,7 +1477,11 @@ class DataMatrixRenderer extends SymbologyRenderer { for (int j = 0; j < h; j++) { if (_dataMatrixList[i][j] == 1) { final Rect matrixRect = Rect.fromLTRB( - x, yPosition, x + xDimension, yPosition + dimension); + x, + yPosition, + x + xDimension, + yPosition + dimension, + ); canvas.drawRect(matrixRect, paint); } @@ -1083,24 +1494,40 @@ class DataMatrixRenderer extends SymbologyRenderer { if (showValue) { final Offset textOffset = Offset(offset.dx, yPosition.toDouble()); drawText( - canvas, textOffset, size, value, textStyle, textSpacing, textAlign); + canvas, + textOffset, + size, + value, + textStyle, + textSpacing, + textAlign, + ); } } /// Method to render the input value of the barcode @override - void drawText(Canvas canvas, Offset offset, Size size, String value, - TextStyle textStyle, double textSpacing, TextAlign textAlign, - [Offset? actualOffset, Size? actualSize]) { + void drawText( + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, [ + Offset? actualOffset, + Size? actualSize, + ]) { final TextSpan span = TextSpan(text: value, style: textStyle); final TextPainter textPainter = TextPainter( - maxLines: 1, - ellipsis: '.....', - text: span, - textDirection: TextDirection.ltr, - textAlign: textAlign); - textPainter.layout(minWidth: 0, maxWidth: size.width); + maxLines: 1, + ellipsis: '.....', + text: span, + textDirection: TextDirection.ltr, + textAlign: textAlign, + ); + textPainter.layout(maxWidth: size.width); double x; double y; switch (textAlign) { @@ -1138,15 +1565,16 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Represents the data matrix symbol attribute class _DataMatrixSymbolAttribute { /// Creates the data matrix symbol attribute - _DataMatrixSymbolAttribute( - {this.symbolRow, - this.symbolColumn, - this.horizontalDataRegion, - this.verticalDataRegion, - this.dataCodeWords, - this.correctionCodeWords, - this.interLeavedBlock, - this.interLeavedDataBlock}); + _DataMatrixSymbolAttribute({ + this.symbolRow, + this.symbolColumn, + this.horizontalDataRegion, + this.verticalDataRegion, + this.dataCodeWords, + this.correctionCodeWords, + this.interLeavedBlock, + this.interLeavedDataBlock, + }); /// Defines the symbol row final int? symbolRow; 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 a2d8ee406..ccb2da71c 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,7 +6,9 @@ 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; } @@ -272,7 +274,7 @@ class ErrorCorrectionCodeWords { 216, 173, 71, - 142 + 142, ]; /// Specifies the error corrcetion code word @@ -325,7 +327,7 @@ class ErrorCorrectionCodeWords { 218, 206, 140, - 78 + 78, ]; break; case 15: @@ -345,7 +347,7 @@ class ErrorCorrectionCodeWords { 124, 5, 99, - 105 + 105, ]; break; case 16: @@ -366,7 +368,7 @@ class ErrorCorrectionCodeWords { 182, 194, 225, - 120 + 120, ]; break; case 17: @@ -388,7 +390,7 @@ class ErrorCorrectionCodeWords { 39, 243, 163, - 136 + 136, ]; break; case 18: @@ -411,7 +413,7 @@ class ErrorCorrectionCodeWords { 5, 98, 96, - 153 + 153, ]; break; case 20: @@ -436,7 +438,7 @@ class ErrorCorrectionCodeWords { 212, 212, 188, - 190 + 190, ]; break; case 22: @@ -463,7 +465,7 @@ class ErrorCorrectionCodeWords { 160, 105, 165, - 231 + 231, ]; break; case 24: @@ -492,7 +494,7 @@ class ErrorCorrectionCodeWords { 87, 96, 227, - 21 + 21, ]; break; case 26: @@ -523,7 +525,7 @@ class ErrorCorrectionCodeWords { 153, 145, 218, - 70 + 70, ]; break; case 28: @@ -556,7 +558,7 @@ class ErrorCorrectionCodeWords { 242, 37, 9, - 123 + 123, ]; break; case 30: @@ -591,7 +593,7 @@ class ErrorCorrectionCodeWords { 238, 40, 192, - 180 + 180, ]; break; case 32: @@ -628,7 +630,7 @@ class ErrorCorrectionCodeWords { 254, 185, 220, - 241 + 241, ]; break; case 34: @@ -667,7 +669,7 @@ class ErrorCorrectionCodeWords { 98, 62, 129, - 51 + 51, ]; break; case 36: @@ -708,7 +710,7 @@ class ErrorCorrectionCodeWords { 113, 233, 30, - 120 + 120, ]; break; case 40: @@ -753,7 +755,7 @@ class ErrorCorrectionCodeWords { 232, 53, 35, - 15 + 15, ]; break; case 42: @@ -800,7 +802,7 @@ class ErrorCorrectionCodeWords { 194, 117, 50, - 96 + 96, ]; break; case 44: @@ -849,7 +851,7 @@ class ErrorCorrectionCodeWords { 113, 102, 73, - 181 + 181, ]; break; case 46: @@ -900,7 +902,7 @@ class ErrorCorrectionCodeWords { 223, 19, 82, - 15 + 15, ]; break; case 48: @@ -953,7 +955,7 @@ class ErrorCorrectionCodeWords { 163, 39, 34, - 108 + 108, ]; break; case 50: @@ -1008,7 +1010,7 @@ class ErrorCorrectionCodeWords { 215, 232, 133, - 205 + 205, ]; break; case 52: @@ -1065,7 +1067,7 @@ class ErrorCorrectionCodeWords { 239, 254, 116, - 51 + 51, ]; break; case 54: @@ -1124,7 +1126,7 @@ class ErrorCorrectionCodeWords { 198, 76, 31, - 156 + 156, ]; break; case 56: @@ -1185,7 +1187,7 @@ class ErrorCorrectionCodeWords { 207, 20, 61, - 10 + 10, ]; break; case 58: @@ -1248,7 +1250,7 @@ class ErrorCorrectionCodeWords { 233, 125, 148, - 123 + 123, ]; break; case 60: @@ -1313,7 +1315,7 @@ class ErrorCorrectionCodeWords { 89, 7, 33, - 240 + 240, ]; break; case 62: @@ -1380,7 +1382,7 @@ class ErrorCorrectionCodeWords { 36, 186, 110, - 106 + 106, ]; break; case 64: @@ -1449,7 +1451,7 @@ class ErrorCorrectionCodeWords { 217, 156, 213, - 231 + 231, ]; break; case 66: @@ -1520,7 +1522,7 @@ class ErrorCorrectionCodeWords { 132, 93, 45, - 105 + 105, ]; break; case 68: @@ -1593,7 +1595,7 @@ class ErrorCorrectionCodeWords { 5, 8, 163, - 238 + 238, ]; break; } @@ -1616,7 +1618,7 @@ class ErrorCorrectionCodeWords { /// Converts decimal to binary value List _convertDecimalToBinary(List decimalRepresentation) { final List toBinary = []; - for (int i = 0; i < eccw; i++) { + for (int i = 0; i < decimalRepresentation.length; i++) { final String temp = decimalRepresentation[i].toRadixString(2); String text = ''; @@ -1641,14 +1643,17 @@ class ErrorCorrectionCodeWords { Map generatorPolynom = {}; for (int i = 0; i < _gx.length; i++) { - generatorPolynom[_gx.length - 1 - i] = - _getElementFromAlpha(_gx[i], _alpha); + generatorPolynom[_gx.length - 1 - i] = _getElementFromAlpha( + _gx[i], + _alpha, + ); } Map tempMessagePolynom = {}; for (int i = 0; i < messagePolynom.length; i++) { - final MapEntry currentEntry = - messagePolynom.entries.elementAt(i); + final MapEntry currentEntry = messagePolynom.entries.elementAt( + i, + ); tempMessagePolynom[currentEntry.key + eccw] = currentEntry.value; } @@ -1657,8 +1662,8 @@ class ErrorCorrectionCodeWords { tempMessagePolynom = {}; for (int i = 0; i < generatorPolynom.length; i++) { - final MapEntry currentEntry = - generatorPolynom.entries.elementAt(i); + final MapEntry currentEntry = generatorPolynom.entries + .elementAt(i); tempMessagePolynom[currentEntry.key + leadTermFactor] = currentEntry.value; } @@ -1669,17 +1674,24 @@ class ErrorCorrectionCodeWords { final int largestExponent = _getLargestExponent(leadTermSource); if (leadTermSource[largestExponent] == 0) { leadTermSource.remove(largestExponent); + if (i == 0) { + leadTermSource = _updateLeadTermSource( + i, + leadTermSource, + generatorPolynom, + ); + } } else { - final Map alphaNotation = _getAlphaNotation(leadTermSource); - Map resPoly = _getGeneratorPolynomByLeadTerm(generatorPolynom, - alphaNotation[_getLargestExponent(alphaNotation)]!, i); - resPoly = _getDecimalNotation(resPoly); - resPoly = _getXORPolynoms(leadTermSource, resPoly); - leadTermSource = resPoly; + leadTermSource = _updateLeadTermSource( + i, + leadTermSource, + generatorPolynom, + ); } } - eccw = leadTermSource.length; + // QR code is not scannable when the input string contains spaces. + // eccw = leadTermSource.length; final List returnValue = []; for (int i = 0; i < leadTermSource.length; i++) { returnValue.add(leadTermSource.entries.elementAt(i).value); @@ -1688,9 +1700,28 @@ class ErrorCorrectionCodeWords { return returnValue; } + /// Updates the lead term source value + Map _updateLeadTermSource( + int index, + Map leadTermSource, + Map generatorPolynom, + ) { + final Map alphaNotation = _getAlphaNotation(leadTermSource); + Map resPoly = _getGeneratorPolynomByLeadTerm( + generatorPolynom, + alphaNotation[_getLargestExponent(alphaNotation)]!, + index, + ); + resPoly = _getDecimalNotation(resPoly); + resPoly = _getXORPolynoms(leadTermSource, resPoly); + return resPoly; + } + /// Calculates the polynomial value Map _getXORPolynoms( - Map messagePolynom, Map resPolynom) { + Map messagePolynom, + Map resPolynom, + ) { final Map resultPolynom = {}; Map longPoly = {}; Map shortPoly = {}; @@ -1708,10 +1739,11 @@ class ErrorCorrectionCodeWords { for (int i = 0; i < longPoly.length; i++) { resultPolynom.putIfAbsent( - messagePolyExponent - i, - () => - longPoly.entries.elementAt(i).value ^ - (shortPoly.length > i ? shortPoly[shortPolyExponent - i]! : 0)); + messagePolyExponent - i, + () => + longPoly.entries.elementAt(i).value ^ + (shortPoly.length > i ? shortPoly[shortPolyExponent - i]! : 0), + ); } final int resultPolyExponent = _getLargestExponent(resultPolynom); @@ -1721,12 +1753,17 @@ class ErrorCorrectionCodeWords { /// Calculates the polynomial value Map _getGeneratorPolynomByLeadTerm( - Map genPolynom, int leadTermCoefficient, int lowerExponentBy) { + Map genPolynom, + int leadTermCoefficient, + int lowerExponentBy, + ) { final Map tempPolynom = {}; for (int i = 0; i < genPolynom.length; i++) { final MapEntry currentEntry = genPolynom.entries.elementAt(i); - tempPolynom.putIfAbsent(currentEntry.key - lowerExponentBy, - () => (currentEntry.value + leadTermCoefficient) % 255); + tempPolynom.putIfAbsent( + currentEntry.key - lowerExponentBy, + () => (currentEntry.value + leadTermCoefficient) % 255, + ); } return tempPolynom; @@ -1737,8 +1774,10 @@ class ErrorCorrectionCodeWords { final Map tempPolynom = {}; for (int i = 0; i < poly.length; i++) { final MapEntry currentEntry = poly.entries.elementAt(i); - tempPolynom[currentEntry.key] = - _getIntValFromAlphaExp(currentEntry.value, _alpha); + tempPolynom[currentEntry.key] = _getIntValFromAlphaExp( + currentEntry.value, + _alpha, + ); } return tempPolynom; @@ -1750,8 +1789,10 @@ class ErrorCorrectionCodeWords { for (int i = 0; i < polynom.length; i++) { final MapEntry currentEntry = polynom.entries.elementAt(i); if (currentEntry.value != 0) { - tempPolynom.putIfAbsent(currentEntry.key, - () => _getElementFromAlpha(currentEntry.value, _alpha)); + tempPolynom.putIfAbsent( + currentEntry.key, + () => _getElementFromAlpha(currentEntry.value, _alpha), + ); } } @@ -1767,7 +1808,6 @@ class ErrorCorrectionCodeWords { largeExponent = currentEntry.key; } } - return largeExponent; } 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 c9dce01fb..0ce548cd0 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 @@ -91,7 +91,7 @@ class QRCodeRenderer extends SymbologyRenderer { '258C', '2590', '2580', - '25A0' + '25A0', ]; /// Specifies the latin2 character set @@ -152,7 +152,7 @@ class QRCodeRenderer extends SymbologyRenderer { '16F', '171', '163', - '2D9' + '2D9', ]; /// Specifies the latin3 character set @@ -182,7 +182,7 @@ class QRCodeRenderer extends SymbologyRenderer { '121', '11D', '16D', - '15D' + '15D', ]; /// Specifies the latin4 character set @@ -235,7 +235,7 @@ class QRCodeRenderer extends SymbologyRenderer { '137', '173', '169', - '16B' + '16B', ]; /// Specifies the windows 1250 character set @@ -249,7 +249,7 @@ class QRCodeRenderer extends SymbologyRenderer { '15F', '13D', '13E', - '17C' + '17C', ]; /// Specifies the windows 1251 character set @@ -283,7 +283,7 @@ class QRCodeRenderer extends SymbologyRenderer { '458', '405', '455', - '457' + '457', ]; /// Specifies the windows 1252 character set @@ -314,7 +314,7 @@ class QRCodeRenderer extends SymbologyRenderer { '203A', '153', '17E', - '178' + '178', ]; /// Specifies the QR code value @@ -516,7 +516,7 @@ class QRCodeRenderer extends SymbologyRenderer { '153', '6BA', '6BE', - '6C1' + '6C1', ]; if (windows1256CharSet.contains(inputChar)) { @@ -717,9 +717,10 @@ 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( - noOfBlocks, - (int i) => - List.filled(encodeData.length ~/ 8 ~/ noOfBlocks, null)); + noOfBlocks, + (int i) => + List.filled(encodeData.length ~/ 8 ~/ noOfBlocks, null), + ); String stringValue = ''; int j = 0; @@ -746,7 +747,10 @@ class QRCodeRenderer extends SymbologyRenderer { /// Method to the split code word List _splitCodeWord( - List> encodeData, int block, int count) { + 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]; @@ -820,7 +824,6 @@ class QRCodeRenderer extends SymbologyRenderer { _eciAssignmentNumber = 2; break; } - //Check for ISO/IEC 8859-2 else if (_getIsISO8859_2CharacterSet(_encodedText[i].codeUnitAt(0))) { _eciAssignmentNumber = 4; @@ -861,7 +864,6 @@ class QRCodeRenderer extends SymbologyRenderer { _eciAssignmentNumber = 13; break; } - //Check for Windows1250 else if (_getIsWindows1250Character(_encodedText[i].codeUnitAt(0))) { _eciAssignmentNumber = 21; @@ -938,16 +940,24 @@ class QRCodeRenderer extends SymbologyRenderer { int capacity = 0; if (_inputMode == QRInputMode.alphaNumeric) { capacity = QRCodeValue.getAlphaNumericDataCapacity( - _errorCorrectionLevel, _codeVersion); + _errorCorrectionLevel, + _codeVersion, + ); } else if (_inputMode == QRInputMode.numeric) { capacity = QRCodeValue.getNumericDataCapacity( - _errorCorrectionLevel, _codeVersion); + _errorCorrectionLevel, + _codeVersion, + ); } else if (_inputMode == QRInputMode.binary) { capacity = QRCodeValue.getBinaryDataCapacity( - _errorCorrectionLevel, _codeVersion); + _errorCorrectionLevel, + _codeVersion, + ); } if (capacity < _encodedText.length) { - throw 'The input value length is greater than version capacity'; + throw ArgumentError( + 'The input value length is greater than version capacity', + ); } } else { int capacityLow = 0, @@ -956,31 +966,55 @@ class QRCodeRenderer extends SymbologyRenderer { capacityHigh = 0; if (_inputMode == QRInputMode.alphaNumeric) { capacityLow = QRCodeValue.getAlphaNumericDataCapacity( - ErrorCorrectionLevel.low, _codeVersion); + ErrorCorrectionLevel.low, + _codeVersion, + ); capacityMedium = QRCodeValue.getAlphaNumericDataCapacity( - ErrorCorrectionLevel.medium, _codeVersion); + ErrorCorrectionLevel.medium, + _codeVersion, + ); capacityQuartile = QRCodeValue.getAlphaNumericDataCapacity( - ErrorCorrectionLevel.quartile, _codeVersion); + ErrorCorrectionLevel.quartile, + _codeVersion, + ); capacityHigh = QRCodeValue.getAlphaNumericDataCapacity( - ErrorCorrectionLevel.high, _codeVersion); + ErrorCorrectionLevel.high, + _codeVersion, + ); } else if (_inputMode == QRInputMode.numeric) { capacityLow = QRCodeValue.getNumericDataCapacity( - ErrorCorrectionLevel.low, _codeVersion); + ErrorCorrectionLevel.low, + _codeVersion, + ); capacityMedium = QRCodeValue.getNumericDataCapacity( - ErrorCorrectionLevel.medium, _codeVersion); + ErrorCorrectionLevel.medium, + _codeVersion, + ); capacityQuartile = QRCodeValue.getNumericDataCapacity( - ErrorCorrectionLevel.quartile, _codeVersion); + ErrorCorrectionLevel.quartile, + _codeVersion, + ); capacityHigh = QRCodeValue.getNumericDataCapacity( - ErrorCorrectionLevel.high, _codeVersion); + ErrorCorrectionLevel.high, + _codeVersion, + ); } else if (_inputMode == QRInputMode.binary) { capacityLow = QRCodeValue.getBinaryDataCapacity( - ErrorCorrectionLevel.low, _codeVersion); + ErrorCorrectionLevel.low, + _codeVersion, + ); capacityMedium = QRCodeValue.getBinaryDataCapacity( - ErrorCorrectionLevel.medium, _codeVersion); + ErrorCorrectionLevel.medium, + _codeVersion, + ); capacityQuartile = QRCodeValue.getBinaryDataCapacity( - ErrorCorrectionLevel.quartile, _codeVersion); + ErrorCorrectionLevel.quartile, + _codeVersion, + ); capacityHigh = QRCodeValue.getBinaryDataCapacity( - ErrorCorrectionLevel.high, _codeVersion); + ErrorCorrectionLevel.high, + _codeVersion, + ); } if (capacityHigh > _encodedText.length) { @@ -992,7 +1026,9 @@ class QRCodeRenderer extends SymbologyRenderer { } else if (capacityLow > _encodedText.length) { _errorCorrectionLevel = ErrorCorrectionLevel.low; } else { - throw 'The input value length is greater than version capacity'; + throw ArgumentError( + 'The input value length is greater than version capacity', + ); } } } @@ -1128,9 +1164,10 @@ class QRCodeRenderer extends SymbologyRenderer { /// uses single for loop for calculating the data allocation values void _dataAllocationAndMasking(List data) { _dataAllocationValues = List>.generate( - _noOfModules, - (int i) => - List.generate(_noOfModules, (int j) => ModuleValue())); + _noOfModules, + (int i) => + List.generate(_noOfModules, (int j) => ModuleValue()), + ); int point = 0; @@ -1419,12 +1456,14 @@ class QRCodeRenderer extends SymbologyRenderer { void _generateValues() { _initialize(); _qrCodeValues = QRCodeValue( - qrCodeVersion: _codeVersion, - errorCorrectionLevel: _errorCorrectionLevel); + qrCodeVersion: _codeVersion, + errorCorrectionLevel: _errorCorrectionLevel, + ); _moduleValues = List>.generate( - _noOfModules, - (int i) => - List.generate(_noOfModules, (int j) => ModuleValue())); + _noOfModules, + (int i) => + List.generate(_noOfModules, (int j) => ModuleValue()), + ); _drawPDP(0, 0); _drawPDP(_noOfModules - 7, 0); _drawPDP(0, _noOfModules - 7); @@ -1473,8 +1512,10 @@ class QRCodeRenderer extends SymbologyRenderer { _encodeDataCodeWords.add(true); //Add ECI assignment number - final List numberInBool = - _getStringToBoolArray(_eciAssignmentNumber.toString(), 8); + final List numberInBool = _getStringToBoolArray( + _eciAssignmentNumber.toString(), + 8, + ); for (int i = 0; i < numberInBool.length; i++) { _encodeDataCodeWords.add(numberInBool[i]!); } @@ -1626,7 +1667,9 @@ class QRCodeRenderer extends SymbologyRenderer { _encodedText[i].codeUnitAt(0) <= 382) { /// European Encoding } else { - throw 'The provided input value contains non-convertible characters'; + throw ArgumentError( + 'The provided input value contains non-convertible characters', + ); } final List numberInBool = _getIntToBoolArray(number, 8); @@ -1692,8 +1735,10 @@ class QRCodeRenderer extends SymbologyRenderer { 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) { @@ -1705,33 +1750,45 @@ class QRCodeRenderer extends SymbologyRenderer { } List> dsOne = List>.generate( - _blocks![0], - (int i) => List.filled( - testEncodeData.length ~/ 8 ~/ _blocks![0], null)); + _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++) { - ds1[i] = - _splitCodeWord(dsOne, i, testEncodeData.length ~/ 8 ~/ _blocks![0]); + ds1[i] = _splitCodeWord( + dsOne, + i, + testEncodeData.length ~/ 8 ~/ _blocks![0], + ); } if (_blocks!.length == 6) { testEncodeData = []; - for (int i = _blocks![0] * _blocks![2] * 8; - i < _encodeDataCodeWords.length; - i++) { + 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.filled( - testEncodeData.length ~/ 8 ~/ _blocks![3], null)); + _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++) { ds1[i] = _splitCodeWord( - dsTwo, count++, testEncodeData.length ~/ 8 ~/ _blocks![3]); + dsTwo, + count++, + testEncodeData.length ~/ 8 ~/ _blocks![3], + ); } } @@ -1768,7 +1825,9 @@ class QRCodeRenderer extends SymbologyRenderer { final List numberOfBitsInCharacterCountIndicatorInBool = _getIntToBoolArray( - _encodedText.length, numberOfBitsInCharacterCountIndicator); + _encodedText.length, + numberOfBitsInCharacterCountIndicator, + ); for (int i = 0; i < numberOfBitsInCharacterCountIndicator; i++) { _encodeDataCodeWords.add(numberOfBitsInCharacterCountIndicatorInBool[i]!); @@ -1792,10 +1851,14 @@ 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); + codeVersion: _codeVersion, + correctionLevel: _errorCorrectionLevel, + ); _dataBits = _qrCodeValues.noOfDataCodeWord; final int eccw = _qrCodeValues.noOfErrorCorrectionCodeWord; @@ -1810,8 +1873,10 @@ class QRCodeRenderer extends SymbologyRenderer { errorCorrectionCodeWord.eccw = eccw ~/ totalBlockSize; - final List> polynomial = - List>.generate(totalBlockSize, (int i) => []); + final List> polynomial = List>.generate( + totalBlockSize, + (int i) => [], + ); int count = 0; for (int i = 0; i < _blocks![0]; i++) { @@ -1853,15 +1918,16 @@ class QRCodeRenderer extends SymbologyRenderer { @override void renderBarcode( - Canvas canvas, - Size size, - Offset offset, - String value, - Color foregroundColor, - TextStyle textStyle, - double textSpacing, - TextAlign textAlign, - bool showValue) { + Canvas canvas, + Size size, + Offset offset, + String value, + Color foregroundColor, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, + bool showValue, + ) { _encodedText = value; _generateValues(); @@ -1896,8 +1962,12 @@ class QRCodeRenderer extends SymbologyRenderer { } } - final Rect rect = Rect.fromLTRB(x.toDouble(), yPosition.toDouble(), - (x + dimension).toDouble(), (yPosition + dimension).toDouble()); + final Rect rect = Rect.fromLTRB( + x.toDouble(), + yPosition.toDouble(), + (x + dimension).toDouble(), + (yPosition + dimension).toDouble(), + ); canvas.drawRect(rect, paint); x = (x + dimension).toInt(); @@ -1909,24 +1979,40 @@ class QRCodeRenderer extends SymbologyRenderer { if (showValue) { final Offset textOffset = Offset(offset.dx, yPosition.toDouble()); drawText( - canvas, textOffset, size, value, textStyle, textSpacing, textAlign); + canvas, + textOffset, + size, + value, + textStyle, + textSpacing, + textAlign, + ); } } /// Method to render the input value of the barcode @override - void drawText(Canvas canvas, Offset offset, Size size, String value, - TextStyle textStyle, double textSpacing, TextAlign textAlign, - [Offset? actualOffset, Size? actualSize]) { + void drawText( + Canvas canvas, + Offset offset, + Size size, + String value, + TextStyle textStyle, + double textSpacing, + TextAlign textAlign, [ + Offset? actualOffset, + Size? actualSize, + ]) { final TextSpan span = TextSpan(text: value, style: textStyle); final TextPainter textPainter = TextPainter( - maxLines: 1, - ellipsis: '.....', - text: span, - textDirection: TextDirection.ltr, - textAlign: textAlign); - textPainter.layout(minWidth: 0, maxWidth: size.width); + maxLines: 1, + ellipsis: '.....', + text: span, + textDirection: TextDirection.ltr, + textAlign: textAlign, + ); + textPainter.layout(maxWidth: size.width); double x; double y; switch (textAlign) { 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 f39ed6d87..3f948a99b 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 @@ -1,3 +1,5 @@ +// ignore_for_file: no_default_cases + import '../../utils/enum.dart'; import '../../utils/helper.dart'; @@ -198,7 +200,7 @@ class QRCodeValue { 750, 1372, 2040, - 2430 + 2430, ]; /// Specifies the value of numeric data capacity of low error correction level @@ -377,7 +379,7 @@ class QRCodeValue { 2625, 2735, 2927, - 3057 + 3057, ]; /// Specifies the value of alpha numeric data capacity of low error @@ -422,7 +424,7 @@ class QRCodeValue { 3729, 3927, 4087, - 4296 + 4296, ]; /// Specifies the value of alpha numeric data capacity of high error @@ -467,7 +469,7 @@ class QRCodeValue { 2894, 3054, 3220, - 3391 + 3391, ]; /// Specifies the value of alpha numeric data capacity of quartile error @@ -512,7 +514,7 @@ class QRCodeValue { 2071, 2181, 2298, - 2420 + 2420, ]; /// Specifies the value of alpha numeric data capacity of high error @@ -557,7 +559,7 @@ class QRCodeValue { 1591, 1658, 1774, - 1852 + 1852, ]; /// Specifies the value of binary data capacity of low error correction level @@ -601,7 +603,7 @@ class QRCodeValue { 2563, 2699, 2809, - 2953 + 2953, ]; /// Specifies the value of binary data capacity of medium error correction @@ -646,7 +648,7 @@ class QRCodeValue { 1989, 2099, 2213, - 2331 + 2331, ]; /// Specifies the value of binary data capacity of quartile error correction @@ -691,7 +693,7 @@ class QRCodeValue { 1423, 1499, 1579, - 1663 + 1663, ]; /// Specifies the value of binary data capacity of high error correction level @@ -735,12 +737,14 @@ class QRCodeValue { 1093, 1139, 1219, - 1273 + 1273, ]; /// Returns the numeric data capacity static int getNumericDataCapacity( - ErrorCorrectionLevel level, QRCodeVersion codeVersion) { + ErrorCorrectionLevel level, + QRCodeVersion codeVersion, + ) { List capacity; switch (level) { case ErrorCorrectionLevel.low: @@ -763,7 +767,9 @@ class QRCodeValue { /// Specifies the alpha numeric data capcity static int getAlphaNumericDataCapacity( - ErrorCorrectionLevel level, QRCodeVersion codeVersion) { + ErrorCorrectionLevel level, + QRCodeVersion codeVersion, + ) { List capacity; switch (level) { case ErrorCorrectionLevel.low: @@ -780,13 +786,16 @@ class QRCodeValue { break; } + // ignore: no_leading_underscores_for_local_identifiers final int _version = getVersionNumber(codeVersion); return capacity[_version - 1]; } /// Specifies the bunary data capacity static int getBinaryDataCapacity( - ErrorCorrectionLevel level, QRCodeVersion codeVersion) { + ErrorCorrectionLevel level, + QRCodeVersion codeVersion, + ) { List capacity; switch (level) { case ErrorCorrectionLevel.low: @@ -3077,7 +3086,7 @@ class QRCodeValue { 1, 0, 0, - 0 + 0, ]; break; case 8: @@ -3099,7 +3108,7 @@ class QRCodeValue { 0, 1, 0, - 0 + 0, ]; break; case 9: @@ -3121,7 +3130,7 @@ class QRCodeValue { 0, 1, 0, - 0 + 0, ]; break; case 10: @@ -3143,7 +3152,7 @@ class QRCodeValue { 0, 1, 0, - 0 + 0, ]; break; case 11: @@ -3165,7 +3174,7 @@ class QRCodeValue { 0, 1, 0, - 0 + 0, ]; break; case 12: @@ -3187,7 +3196,7 @@ class QRCodeValue { 1, 1, 0, - 0 + 0, ]; break; case 13: @@ -3209,7 +3218,7 @@ class QRCodeValue { 1, 1, 0, - 0 + 0, ]; break; case 14: @@ -3231,7 +3240,7 @@ class QRCodeValue { 1, 1, 0, - 0 + 0, ]; break; case 15: @@ -3253,7 +3262,7 @@ class QRCodeValue { 1, 1, 0, - 0 + 0, ]; break; case 16: @@ -3275,7 +3284,7 @@ class QRCodeValue { 0, 0, 1, - 0 + 0, ]; break; case 17: @@ -3297,7 +3306,7 @@ class QRCodeValue { 0, 0, 1, - 0 + 0, ]; break; case 18: @@ -3319,7 +3328,7 @@ class QRCodeValue { 0, 0, 1, - 0 + 0, ]; break; case 19: @@ -3341,7 +3350,7 @@ class QRCodeValue { 0, 0, 1, - 0 + 0, ]; break; case 20: @@ -3363,7 +3372,7 @@ class QRCodeValue { 1, 0, 1, - 0 + 0, ]; break; case 21: @@ -3385,7 +3394,7 @@ class QRCodeValue { 1, 0, 1, - 0 + 0, ]; break; case 22: @@ -3407,7 +3416,7 @@ class QRCodeValue { 1, 0, 1, - 0 + 0, ]; break; case 23: @@ -3429,7 +3438,7 @@ class QRCodeValue { 1, 0, 1, - 0 + 0, ]; break; case 24: @@ -3451,7 +3460,7 @@ class QRCodeValue { 0, 1, 1, - 0 + 0, ]; break; case 25: @@ -3473,7 +3482,7 @@ class QRCodeValue { 0, 1, 1, - 0 + 0, ]; break; case 26: @@ -3495,7 +3504,7 @@ class QRCodeValue { 0, 1, 1, - 0 + 0, ]; break; case 27: @@ -3517,7 +3526,7 @@ class QRCodeValue { 0, 1, 1, - 0 + 0, ]; break; case 28: @@ -3539,7 +3548,7 @@ class QRCodeValue { 1, 1, 1, - 0 + 0, ]; break; case 29: @@ -3561,7 +3570,7 @@ class QRCodeValue { 1, 1, 1, - 0 + 0, ]; break; case 30: @@ -3583,7 +3592,7 @@ class QRCodeValue { 1, 1, 1, - 0 + 0, ]; break; case 31: @@ -3605,7 +3614,7 @@ class QRCodeValue { 1, 1, 1, - 0 + 0, ]; break; case 32: @@ -3627,7 +3636,7 @@ class QRCodeValue { 0, 0, 0, - 1 + 1, ]; break; case 33: @@ -3649,7 +3658,7 @@ class QRCodeValue { 0, 0, 0, - 1 + 1, ]; break; case 34: @@ -3671,7 +3680,7 @@ class QRCodeValue { 0, 0, 0, - 1 + 1, ]; break; case 35: @@ -3693,7 +3702,7 @@ class QRCodeValue { 0, 0, 0, - 1 + 1, ]; break; case 36: @@ -3715,7 +3724,7 @@ class QRCodeValue { 1, 0, 0, - 1 + 1, ]; break; case 37: @@ -3737,7 +3746,7 @@ class QRCodeValue { 1, 0, 0, - 1 + 1, ]; break; case 38: @@ -3759,7 +3768,7 @@ class QRCodeValue { 1, 0, 0, - 1 + 1, ]; break; case 39: @@ -3781,7 +3790,7 @@ class QRCodeValue { 1, 0, 0, - 1 + 1, ]; break; case 40: @@ -3803,7 +3812,7 @@ class QRCodeValue { 0, 1, 0, - 1 + 1, ]; break; } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/theme.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/theme.dart new file mode 100644 index 000000000..f356a8300 --- /dev/null +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/theme.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [BarcodeThemeData] this class provides themeData. +/// BarcodeThemeData class extends the 'SfBarcodeThemeData' class and customize +/// the appearance of a mapping component based on th colorScheme obtained from +/// the provided [BuildContext]. +class BarcodeThemeData extends SfBarcodeThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the colorScheme of the current theme. + BarcodeThemeData(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the colorScheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get backgroundColor => colorScheme.transparent; + + @override + Color? get barColor => colorScheme.onSurface[70]; + + @override + Color? get textColor => colorScheme.onSurface[70]; +} 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 ffa636fef..bfbf02525 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 @@ -16,11 +16,11 @@ class DataMatrix extends Symbology { /// /// The arguments [module] must be non-negative and greater than 0. /// - DataMatrix( - {int? module, - this.dataMatrixSize = DataMatrixSize.auto, - this.encoding = DataMatrixEncoding.auto}) - : super(module: module); + DataMatrix({ + int? module, + this.dataMatrixSize = DataMatrixSize.auto, + this.encoding = DataMatrixEncoding.auto, + }) : super(module: module); /// Define the size that is used to encode the amount of data. /// 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 66a97945a..7bb95df42 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 @@ -30,12 +30,12 @@ class QRCode extends Symbology { /// /// The arguments [module] must be non-negative and greater than 0. /// - QRCode( - {this.codeVersion, - this.errorCorrectionLevel = ErrorCorrectionLevel.high, - this.inputMode = QRInputMode.binary, - int? module}) - : super(module: module); + QRCode({ + this.codeVersion, + this.errorCorrectionLevel = ErrorCorrectionLevel.high, + this.inputMode = QRInputMode.binary, + int? module, + }) : super(module: module); /// Define the version that is used to encode the amount of data. /// diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/enum.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/enum.dart index 3fd6a47c9..3f1a8988e 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/enum.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/enum.dart @@ -10,7 +10,7 @@ enum CodeType { doubleDigit, ///CodeType.fnc1 represents the input symbol is special character. - fnc1 + fnc1, } /// Versions that is used to encode the amount of data. @@ -136,7 +136,7 @@ enum QRCodeVersion { version39, ///QRCodeVersion.version40 specifies the qr code version is 40. - version40 + version40, } /// Encode recovery capacity of the barcode. @@ -152,7 +152,7 @@ enum ErrorCorrectionLevel { quartile, ///ErrorCorrectionLevel.high is recover approximately 30% of the codewords. - high + high, } /// Specific set of input mode characters. @@ -164,7 +164,7 @@ enum QRInputMode { alphaNumeric, ///QRInputMode.binary represents the input data as binary. - binary + binary, } /// Encoding type for the [DataMatrix] code. @@ -179,7 +179,7 @@ enum DataMatrixEncoding { asciiNumeric, /// DataMatrixEncoding.base256 represents the base256 encoding. - base256 + base256, } /// Sizes that is used to encode the amount of [DataMatrix] data. @@ -275,5 +275,5 @@ enum DataMatrixSize { size16x36, /// DataMatrixSize.size16x48 represents the 16x48 matrix. - size16x48 + size16x48, } diff --git a/packages/syncfusion_flutter_barcodes/pubspec.yaml b/packages/syncfusion_flutter_barcodes/pubspec.yaml index b6e73d839..aa8e7860e 100644 --- a/packages/syncfusion_flutter_barcodes/pubspec.yaml +++ b/packages/syncfusion_flutter_barcodes/pubspec.yaml @@ -1,16 +1,28 @@ name: syncfusion_flutter_barcodes description: Flutter Barcodes generator library is used to generate and display data in the machine-readable, industry-standard 1D and 2D barcodes. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_barcodes +screenshots: + - description: 'This screenshot shows the 6 one dimensional symbology type image.' + + - description: 'This screenshot shows the another 6 one dimensional symbology image.' + + - description: 'This screenshot shows two dimensional symbology of the qrcode and data matrix image.' + + environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: sdk: flutter syncfusion_flutter_core: - path: ../syncfusion_flutter_core + path: ../syncfusion_flutter_core + + + + flutter: diff --git a/packages/syncfusion_flutter_barcodes/screenshots/qr_code_syncfusion_flutter_barcodes.png b/packages/syncfusion_flutter_barcodes/screenshots/qr_code_syncfusion_flutter_barcodes.png new file mode 100644 index 000000000..4cac62ce1 Binary files /dev/null and b/packages/syncfusion_flutter_barcodes/screenshots/qr_code_syncfusion_flutter_barcodes.png differ diff --git a/packages/syncfusion_flutter_barcodes/screenshots/syncfusion_flutter_barcodes_code1.png b/packages/syncfusion_flutter_barcodes/screenshots/syncfusion_flutter_barcodes_code1.png new file mode 100644 index 000000000..9af1cc903 Binary files /dev/null and b/packages/syncfusion_flutter_barcodes/screenshots/syncfusion_flutter_barcodes_code1.png differ diff --git a/packages/syncfusion_flutter_barcodes/screenshots/syncfusion_flutter_barcodes_code2.png b/packages/syncfusion_flutter_barcodes/screenshots/syncfusion_flutter_barcodes_code2.png new file mode 100644 index 000000000..4b65329a3 Binary files /dev/null and b/packages/syncfusion_flutter_barcodes/screenshots/syncfusion_flutter_barcodes_code2.png differ diff --git a/packages/syncfusion_flutter_calendar/CHANGELOG.md b/packages/syncfusion_flutter_calendar/CHANGELOG.md index a101d056f..6a0ed1d4f 100644 --- a/packages/syncfusion_flutter_calendar/CHANGELOG.md +++ b/packages/syncfusion_flutter_calendar/CHANGELOG.md @@ -1,4 +1,120 @@ -## [20.1.0.47] +## Unreleased + +**General** + +* Upgraded the `timezone` package to the latest version 0.10.1. + +* The compatible version of our Flutter calendar widget has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +**Bugs** + +* \#FB66714 - Now, the font size is no longer calculated with a negative value in [SfCalendar](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar-class.html) when placed in a constrained space. + +* \#FB66695 - Now, the [headerDateFormat](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar/headerDateFormat.html) is applied correctly in the resource view of [SfCalendar](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar-class.html), displaying the date in the expected format. + +* \#FB66695 - Now, the week number in the resource view of [SfCalendar](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar-class.html), is fully visible and no longer gets cut off when sufficient space is available. + +* \#FB66690 - Now, the calendar cell overlay position updates correctly in the month view when the [timeRulerSize](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/TimeSlotViewSettings/timeRulerSize.html) is added in the [timeSlotViewSettings](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar/timeSlotViewSettings.html) of SfCalendar. + +## [29.1.37] - 08/04/2025 + +**Bugs** + +* \#FB66610 - Now, the interactive widgets will work on the [appointmentBuilder](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar/appointmentBuilder.html) in SfCalendar. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter calendar widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter calendar example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter calendar example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +**Features** + +* #FR46931 - Provided touch support for the [monthCellBuilder](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar/monthCellBuilder.html) in the Calendar. + +## [28.2.7] - 25/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.39] - 01/15/2025 + +**Enhancements** + +* Now, improved the spanned appointment rendering behavior in the timelineMonth view when the when a start and end times fall within the same month and end date includes partial 24-hour periods in the Flutter event calendar. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter calendar widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter calendar widget has been updated to Flutter SDK 3.24.0. + +**Bug fixes** +* Now, the horizontal scroll bar is draggable in the timeline view on both web and desktop platforms. + +## [25.2.7] - 06/04/2024 +**Bugs** +* \#FB57253 - +Now, the appointment details are passed correctly to the [monthCellBuilder](https://pub.dev/documentation/syncfusion_flutter_calendar/latest/calendar/SfCalendar/monthCellBuilder.html) callback when navigating to the months using the navigation buttons. + +## [25.2.3] - 05/13/2024 +**General** +* Upgraded the `timezone` package to the latest version 0.9.3. + +**Bug fixes** +* \#FB51676 - Now, the handleLoadMore method is called once when the CalendarDataSourceAction is set to reset. + +## [25.1.35] - 03/15/2024 +**General** +* Provided th​e Material 3 themes support. + +**Bug fixes** +* \#FB50948 - Now, the 'setState() or markNeedsBuild() called during the build' exception will not be thrown when tapping today's button after swiping the `timelineMonth` view. +* \#FB50846 - Now, text size remains consistent when the app state or themes gets changed. + +## [24.1.46] - 17/01/2024 +**General** +* Upgraded the `intl` package to the latest version 0.19.0. + +## [22.2.5] +**Features** +* Provided support to accessibility for builders in the Flutter event calendar. + +## [20.3.56] +**Features** +* Provided text style customization support for the text in the placeholder (`No events` and `No selected date`) in the month agenda view and (`No events`) in the schedule view of the flutter event calendar. + +## [20.2.43] +**Enhancements** +* Now, we have improved the behavior of the `appointmentBuilder` details `date` value to hold the start date of the appointment view in the Flutter event calendar. + +## [20.2.36] +**Features** +* Now, we have improved the behavior of the month header of ScheduleView with a minimum date in the Flutter event calendar. + +## [20.1.47] **Features** * Provided support for the number of days in view, it is used to customize the days count in the flutter event calendar. * Provided support for recurring appointments on the last day of month in the flutter event calendar. diff --git a/packages/syncfusion_flutter_calendar/LICENSE b/packages/syncfusion_flutter_calendar/LICENSE index c36eab156..4f8fc74c6 100644 --- a/packages/syncfusion_flutter_calendar/LICENSE +++ b/packages/syncfusion_flutter_calendar/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Calendar 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. +Syncfusion® Flutter Calendar 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_calendar/README.md b/packages/syncfusion_flutter_calendar/README.md index f38a450c8..f9f8d52d2 100644 --- a/packages/syncfusion_flutter_calendar/README.md +++ b/packages/syncfusion_flutter_calendar/README.md @@ -4,7 +4,7 @@ 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 [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. +**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 @@ -21,7 +21,7 @@ The Flutter Calendar widget has built-in configurable views such as day, week, w - [Add month agenda view](#add-month-agenda-view) - [Add data source](#add-data-source) - [Support and feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Calendar features @@ -91,7 +91,7 @@ The Flutter Calendar widget has built-in configurable views such as day, week, w * **Week numbers** - Display the week numbers of the year in the month, week, and work week views of the Calendar. -![week_numbers](https://cdn.syncfusion.com/content/images/FTControl/Calendar/calendar_weeknumber.png) +![week_numbers](https://cdn.syncfusion.com/content/images/FTControl/Calendar/calendar-weeknumber.png) * **Quick view navigation** - Navigate among calendar views easily using the header date picker views button in the calendar header and clicking month cell and view headers. @@ -105,7 +105,7 @@ The Flutter Calendar widget has built-in configurable views such as day, week, w ![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 Globalization** - 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) @@ -133,23 +133,20 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about the Syncfusion Flutter Calendar. +Take a look at the following to learn more about the Syncfusion® Flutter Calendar. -* [Syncfusion Flutter widgets product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter widgets product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation](https://help.syncfusion.com/flutter/introduction/overview) * [Knowledge base](https://www.syncfusion.com/kb) @@ -316,11 +313,11 @@ class Meeting { ## 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +## 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_calendar/analysis_options.yaml b/packages/syncfusion_flutter_calendar/analysis_options.yaml index 07fa312a6..03286a00b 100644 --- a/packages/syncfusion_flutter_calendar/analysis_options.yaml +++ b/packages/syncfusion_flutter_calendar/analysis_options.yaml @@ -1,6 +1 @@ include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore - invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_calendar/assets/fonts/Roboto-Medium.ttf b/packages/syncfusion_flutter_calendar/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 000000000..d629e9848 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/fonts/Roboto-Medium.ttf differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/May.png b/packages/syncfusion_flutter_calendar/assets/images/May.png new file mode 100644 index 000000000..568ef2af5 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/May.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle11.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle11.png new file mode 100644 index 000000000..657adc38f Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle11.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle13.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle13.png new file mode 100644 index 000000000..0f7a0da09 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle13.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle15.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle15.png new file mode 100644 index 000000000..b8c08e671 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle15.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle18.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle18.png new file mode 100644 index 000000000..4f31688b0 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle18.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle20.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle20.png new file mode 100644 index 000000000..6a13793d2 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle20.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle23.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle23.png new file mode 100644 index 000000000..d7a12ee96 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle23.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle24.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle24.png new file mode 100644 index 000000000..d59cc5781 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle24.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle25.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle25.png new file mode 100644 index 000000000..e047277d6 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle25.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle26.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle26.png new file mode 100644 index 000000000..f88ab438e Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle26.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle27.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle27.png new file mode 100644 index 000000000..4359bd60b Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle27.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle5.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle5.png new file mode 100644 index 000000000..9a1f4b34a Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle5.png differ diff --git a/packages/syncfusion_flutter_calendar/assets/images/People_Circle8.png b/packages/syncfusion_flutter_calendar/assets/images/People_Circle8.png new file mode 100644 index 000000000..f4fd0f82c Binary files /dev/null and b/packages/syncfusion_flutter_calendar/assets/images/People_Circle8.png differ diff --git a/packages/syncfusion_flutter_calendar/example/README.md b/packages/syncfusion_flutter_calendar/example/README.md new file mode 100644 index 000000000..db3fb8de1 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter application. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_flutter_calendar/example/android/.gitignore b/packages/syncfusion_flutter_calendar/example/android/.gitignore index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_flutter_calendar/example/android/.gitignore +++ b/packages/syncfusion_flutter_calendar/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_calendar/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_calendar/example/android/app/build.gradle.kts new file mode 100644 index 000000000..21ecea973 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_flutter_calendar/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_calendar/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_calendar/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_calendar/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_calendar/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_calendar/example/android/app/src/main/AndroidManifest.xml index 3f41384db..74a78b939 100644 --- a/packages/syncfusion_flutter_calendar/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_calendar/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/packages/syncfusion_flutter_calendar/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_calendar/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000d..ac81bae64 100644 --- a/packages/syncfusion_flutter_calendar/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/syncfusion_flutter_calendar/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_calendar/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_calendar/example/android/app/src/main/res/values-night/styles.xml index 449a9f930..06952be74 100644 --- a/packages/syncfusion_flutter_calendar/example/android/app/src/main/res/values-night/styles.xml +++ b/packages/syncfusion_flutter_calendar/example/android/app/src/main/res/values-night/styles.xml @@ -3,14 +3,14 @@ diff --git a/packages/syncfusion_flutter_calendar/example/android/build.gradle.kts b/packages/syncfusion_flutter_calendar/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_calendar/example/android/gradle.properties b/packages/syncfusion_flutter_calendar/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_flutter_calendar/example/android/gradle.properties +++ b/packages/syncfusion_flutter_calendar/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_calendar/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_calendar/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_flutter_calendar/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_calendar/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_calendar/example/android/settings.gradle.kts b/packages/syncfusion_flutter_calendar/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_calendar/example/ios/.gitignore b/packages/syncfusion_flutter_calendar/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/.gitignore +++ b/packages/syncfusion_flutter_calendar/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_calendar/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_calendar/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_calendar/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_calendar/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_calendar/example/ios/Flutter/Debug.xcconfig index e8efba114..592ceee85 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_calendar/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_calendar/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_calendar/example/ios/Flutter/Release.xcconfig index 399e9340e..592ceee85 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_calendar/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_calendar/example/ios/Podfile b/packages/syncfusion_flutter_calendar/example/ios/Podfile new file mode 100644 index 000000000..6697f0a53 --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/ios/Podfile @@ -0,0 +1,87 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + use_frameworks! + use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.pbxproj index 5f1bf4c32..a6a988ee6 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -125,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -179,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -208,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -298,15 +371,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -316,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -432,15 +550,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -459,15 +572,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -480,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_calendar/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_calendar/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_calendar/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_calendar/example/lib/main.dart b/packages/syncfusion_flutter_calendar/example/lib/main.dart index 6b9875a99..f7eef85b8 100644 --- a/packages/syncfusion_flutter_calendar/example/lib/main.dart +++ b/packages/syncfusion_flutter_calendar/example/lib/main.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; void main() { - return runApp(CalendarApp()); + return runApp(const CalendarApp()); } /// The app which hosts the home page which contains the calendar on it. class CalendarApp extends StatelessWidget { + const CalendarApp({super.key}); + @override Widget build(BuildContext context) { return const MaterialApp(title: 'Calendar Demo', home: MyHomePage()); @@ -27,25 +29,27 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SfCalendar( - view: CalendarView.month, - dataSource: MeetingDataSource(_getDataSource()), - // by default the month appointment display mode set as Indicator, we can - // change the display mode as appointment using the appointment display - // mode property - monthViewSettings: const MonthViewSettings( - appointmentDisplayMode: MonthAppointmentDisplayMode.appointment), - )); + body: SfCalendar( + view: CalendarView.month, + dataSource: MeetingDataSource(_getDataSource()), + // by default the month appointment display mode set as Indicator, we can + // change the display mode as appointment using the appointment display + // mode property + monthViewSettings: const MonthViewSettings( + appointmentDisplayMode: MonthAppointmentDisplayMode.appointment, + ), + ), + ); } List _getDataSource() { final List meetings = []; final DateTime today = DateTime.now(); - final DateTime startTime = - DateTime(today.year, today.month, today.day, 9, 0, 0); + final DateTime startTime = DateTime(today.year, today.month, today.day, 9); final DateTime endTime = startTime.add(const Duration(hours: 2)); - meetings.add(Meeting( - 'Conference', startTime, endTime, const Color(0xFF0F8644), false)); + meetings.add( + Meeting('Conference', startTime, endTime, const Color(0xFF0F8644), false), + ); return meetings; } } diff --git a/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_calendar/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_calendar/example/macos/.gitignore b/packages/syncfusion_flutter_calendar/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_calendar/example/macos/.gitignore +++ b/packages/syncfusion_flutter_calendar/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..fca5bdc5c 100644 --- a/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,6 +61,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -106,6 +134,7 @@ isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,6 +218,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..9963aa361 100644 --- a/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_calendar/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - + + + + + + - - Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_calendar/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_calendar/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_calendar/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_calendar/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_calendar/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..dda9752f6 100644 --- a/packages/syncfusion_flutter_calendar/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_calendar/example/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = example PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_calendar/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_calendar/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_calendar/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_calendar/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_calendar/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_calendar/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_calendar/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_calendar/example/pubspec.yaml b/packages/syncfusion_flutter_calendar/example/pubspec.yaml index 2ce47036f..8b21f28ed 100644 --- a/packages/syncfusion_flutter_calendar/example/pubspec.yaml +++ b/packages/syncfusion_flutter_calendar/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Syncfusion calendar example. version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: @@ -11,7 +11,7 @@ dependencies: cupertino_icons: ^1.0.2 syncfusion_flutter_calendar: - path: ../../syncfusion_flutter_calendar + path: ../ dev_dependencies: flutter_test: @@ -19,4 +19,4 @@ dev_dependencies: flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/packages/syncfusion_flutter_calendar/example/web/index.html b/packages/syncfusion_flutter_calendar/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_calendar/example/web/index.html +++ b/packages/syncfusion_flutter_calendar/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_calendar/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_calendar/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_calendar/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_calendar/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) 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 8de70c329..91ea50cd9 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 @@ -75,7 +75,7 @@ class Appointment with Diagnosticable { this.endTimeZone, this.recurrenceRule, this.isAllDay = false, - this.notes, + String? notes, this.location, this.resourceIds, this.recurrenceId, @@ -85,12 +85,18 @@ class Appointment with Diagnosticable { this.subject = '', this.color = Colors.lightBlue, this.recurrenceExceptionDates, - }) { + }) : notes = + notes != null && notes.contains('isOccurrenceAppointment') + ? notes.replaceAll('isOccurrenceAppointment', '') + : notes, + _notes = notes { recurrenceRule = recurrenceId != null ? null : recurrenceRule; _appointmentType = _getAppointmentType(); id = id ?? hashCode; } + String? _notes; + /// The start time for an [Appointment] in [SfCalendar]. /// /// Defaults to `DateTime.now()`. @@ -937,8 +943,8 @@ class Appointment with Diagnosticable { if (recurrenceId != null) { return AppointmentType.changedOccurrence; } else if (recurrenceRule != null && recurrenceRule!.isNotEmpty) { - if (notes != null && notes!.contains('isOccurrenceAppointment')) { - notes = notes!.replaceAll('isOccurrenceAppointment', ''); + if (_notes != null && _notes!.contains('isOccurrenceAppointment')) { + _notes = _notes!.replaceAll('isOccurrenceAppointment', ''); return AppointmentType.occurrence; } @@ -949,7 +955,7 @@ class Appointment with Diagnosticable { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -980,14 +986,17 @@ class Appointment with Diagnosticable { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return hashValues( + return Object.hash( startTimeZone, endTimeZone, recurrenceRule, isAllDay, notes, location, - hashList(resourceIds), + + /// Below condition is referred from text style class + /// https://api.flutter.dev/flutter/painting/TextStyle/hashCode.html + resourceIds == null ? null : Object.hashAll(resourceIds!), recurrenceId, id, appointmentType, @@ -995,7 +1004,9 @@ class Appointment with Diagnosticable { endTime, subject, color, - hashList(recurrenceExceptionDates), + recurrenceExceptionDates == null + ? null + : Object.hashAll(recurrenceExceptionDates!), ); } @@ -1011,14 +1022,21 @@ class Appointment with Diagnosticable { properties.add(ColorProperty('color', color)); properties.add(DiagnosticsProperty('recurrenceId', recurrenceId)); properties.add(DiagnosticsProperty('id', id)); - properties - .add(EnumProperty('appointmentType', appointmentType)); + properties.add( + EnumProperty('appointmentType', appointmentType), + ); 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( + 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 805061d22..8a48020b0 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,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart' show DateFormat; -import 'package:syncfusion_flutter_calendar/src/calendar/common/date_time_engine.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:timezone/timezone.dart'; import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; import '../common/enums.dart'; import '../sfcalendar.dart'; import 'appointment.dart'; @@ -17,7 +17,7 @@ import 'recurrence_helper.dart'; 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); + return DateTime(date.year, date.month, date.day); } /// Return the date with end time value for the date value. @@ -27,7 +27,7 @@ class AppointmentHelper { /// Return the start date of the month specified in date. static DateTime getMonthStartDate(DateTime date) { - return DateTime(date.year, date.month, 1); + return DateTime(date.year, date.month); } /// Return the end date of the month specified in date. @@ -48,11 +48,23 @@ class AppointmentHelper { /// 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 = - DateTimeHelper.getDateTimeValue(addDays(date, days)); + DateTime date, + int days, + int hour, + int minute, + int second, + ) { + final DateTime newDate = DateTimeHelper.getDateTimeValue( + addDays(date, days), + ); return DateTime( - newDate.year, newDate.month, newDate.day, hour, minute, second); + newDate.year, + newDate.month, + newDate.day, + hour, + minute, + second, + ); } /// Check whether the data source has calendar appointment type or not. @@ -72,21 +84,27 @@ class AppointmentHelper { appointment.actualStartTime.month && appointment.actualEndTime.year == appointment.actualStartTime.year) && - getDifference(appointment.actualStartTime, appointment.actualEndTime) - .inDays > + getDifference( + appointment.actualStartTime, + appointment.actualEndTime, + ).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}) { + 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 viewEndDate = convertToEndTime( + visibleDates[visibleDates.length - 1], + ); final DateTime appStartTime = appointment.exactStartTime; final DateTime appEndTime = appointment.exactEndTime; @@ -122,10 +140,10 @@ class AppointmentHelper { if (appStartTime.isAfter(viewStartDate)) { final int appointmentStartWeek = getDifference(viewStartDate, appStartTime).inDays ~/ - DateTime.daysPerWeek; + DateTime.daysPerWeek; final int appointmentEndWeek = getDifference(viewStartDate, appEndTime).inDays ~/ - DateTime.daysPerWeek; + DateTime.daysPerWeek; return appointmentStartWeek != appointmentEndWeek; } } @@ -136,29 +154,37 @@ class AppointmentHelper { /// Returns recurrence icon details for appointment view. static TextSpan getRecurrenceIcon( - Color color, double textSize, bool isRecurrenceAppointment) { + Color color, + double textSize, + bool isRecurrenceAppointment, + ) { final IconData recurrenceIconData = isRecurrenceAppointment ? Icons.autorenew : Icons.sync_disabled; return TextSpan( - text: String.fromCharCode(recurrenceIconData.codePoint), - style: TextStyle( - color: color, - fontSize: textSize, - fontFamily: recurrenceIconData.fontFamily, - )); + 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) { + 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)) ~/ + final int iconStartPosition = + (textPainter.height - + (textPainter.textScaler.scale(icon.style!.fontSize!))) ~/ 1.5; return rect.top - ((textPainter.height - rect.height) / 2) - @@ -169,15 +195,21 @@ class AppointmentHelper { /// 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 DateTime exactStartTime = - convertToStartTime(appointment.exactStartTime); - final String totalDays = (getDifference( - exactStartTime, convertToEndTime(appointment.exactEndTime)) - .inDays + - 1) - .toString(); + static String getSpanAppointmentText( + CalendarAppointment appointment, + DateTime date, + SfLocalizations localization, + ) { + final DateTime exactStartTime = convertToStartTime( + appointment.exactStartTime, + ); + final String totalDays = + (getDifference( + exactStartTime, + convertToEndTime(appointment.exactEndTime), + ).inDays + + 1) + .toString(); final String currentDate = (getDifference(exactStartTime, convertToEndTime(date)).inDays + 1) .toString(); @@ -190,35 +222,74 @@ class AppointmentHelper { /// 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', - )); + 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) { + 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) { + static bool canAddBackwardSpanIcon( + DateTime appStartTime, + DateTime appEndTime, + DateTime viewStartDate, + DateTime viewEndDate, + ) { return appStartTime.isBefore(viewStartDate) && isSameOrBeforeDate(viewEndDate, appEndTime); } + /// Returns the specific date appointment collection by filtering the + /// appointments from passed visible appointment collection. + static List getSpecificDateVisibleAppointment( + 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 CalendarAppointment appointment = visibleAppointments[j]; + if (isAppointmentWithinVisibleDateRange( + appointment, + startDate, + endDate, + )) { + appointmentCollection.add(appointment); + } + } + + return appointmentCollection; + } + /// Return appointment collection based on the date. static List getSelectedDateAppointments( - List? appointments, - String? timeZone, - DateTime? date) { + List? appointments, + String? timeZone, + DateTime? date, + ) { final List appointmentCollection = []; if (appointments == null || appointments.isEmpty || date == null) { @@ -232,16 +303,25 @@ class AppointmentHelper { for (int j = 0; j < count; j++) { final CalendarAppointment appointment = appointments[j]; appointment.actualStartTime = convertTimeToAppointmentTimeZone( - appointment.startTime, appointment.startTimeZone, timeZone); + appointment.startTime, + appointment.startTimeZone, + timeZone, + ); appointment.actualEndTime = convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, timeZone); + appointment.endTime, + appointment.endTimeZone, + timeZone, + ); appointment.exactStartTime = appointment.actualStartTime; appointment.exactEndTime = appointment.actualEndTime; if (appointment.recurrenceRule == null || appointment.recurrenceRule == '') { if (isAppointmentWithinVisibleDateRange( - appointment, startDate, endDate)) { + appointment, + startDate, + endDate, + )) { appointmentCollection.add(appointment); } @@ -249,29 +329,50 @@ class AppointmentHelper { } _getRecurrenceAppointments( - appointment, appointmentCollection, startDate, endDate, timeZone); + appointment, + appointmentCollection, + startDate, + endDate, + timeZone, + ); } return appointmentCollection; } + /// Return calendar appointment text style. + static TextStyle getAppointmentTextStyle( + TextStyle appointmentTextStyle, + CalendarView view, + ThemeData themeData, + ) { + if (appointmentTextStyle.fontSize != -1) { + return themeData.textTheme.bodyMedium!.merge(appointmentTextStyle); + } + + return themeData.textTheme.bodyMedium!.merge( + appointmentTextStyle.copyWith(fontSize: 12), + ); + } + 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: CalendarViewHelper.cloneList(appointment.resourceIds), - recurrenceId: appointment.recurrenceId, - id: appointment.id); + 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: CalendarViewHelper.cloneList(appointment.resourceIds), + recurrenceId: appointment.recurrenceId, + id: appointment.id, + ); copyAppointment.actualStartTime = appointment.actualStartTime; copyAppointment.actualEndTime = appointment.actualEndTime; copyAppointment.data = appointment.data; @@ -282,16 +383,25 @@ class AppointmentHelper { /// 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); + 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) { + static bool isDateRangeWithinVisibleDateRange( + DateTime startDate, + DateTime endDate, + DateTime visibleStart, + DateTime visibleEnd, + ) { if (startDate.isAfter(visibleStart)) { if (startDate.isBefore(visibleEnd)) { return true; @@ -307,8 +417,11 @@ class AppointmentHelper { return false; } - static bool _isAppointmentInVisibleDateRange(CalendarAppointment appointment, - DateTime visibleStart, DateTime visibleEnd) { + static bool _isAppointmentInVisibleDateRange( + CalendarAppointment appointment, + DateTime visibleStart, + DateTime visibleEnd, + ) { final DateTime appStartTime = appointment.actualStartTime; final DateTime appEndTime = appointment.actualEndTime; if ((appStartTime.isAfter(visibleStart) || @@ -336,7 +449,10 @@ class AppointmentHelper { /// 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) { + DateTime appointmentDate, + DateTime visibleStart, + DateTime visibleEnd, + ) { if (appointmentDate.isAfter(visibleStart)) { if (appointmentDate.isBefore(visibleEnd)) { return true; @@ -467,11 +583,36 @@ class AppointmentHelper { return getLocation(windowsTimeZoneId); } + /// Method returns the date time of the provided timezone. + static DateTime convertTimezone(DateTime dateTime, String? targetTimezone) { + final DateTime convertedDate = dateTime; + final DateTime actualConvertedDate; + + if (targetTimezone == 'Dateline Standard Time') { + actualConvertedDate = convertedDate.toUtc().subtract( + const Duration(hours: 12), + ); + } else { + final Location location = _timeZoneInfoToOlsonTimeZone(targetTimezone!); + actualConvertedDate = TZDateTime.from(convertedDate, location); + } + + return DateTime( + actualConvertedDate.year, + actualConvertedDate.month, + actualConvertedDate.day, + actualConvertedDate.hour, + actualConvertedDate.minute, + actualConvertedDate.second, + ); + } + /// 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]; + List appointmentCollection, + ) { + for (int i = 0; i < appointmentCollection.length; i++) { + final AppointmentView obj = appointmentCollection[i]; obj.canReuse = true; obj.appointment = null; obj.position = -1; @@ -484,8 +625,12 @@ class AppointmentHelper { /// 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 / + SfCalendar calendar, + DateTime date, + double timeIntervalHeight, + ) { + final double singleIntervalHeightForAnHour = + (60 / CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings)) * timeIntervalHeight; @@ -496,25 +641,30 @@ class AppointmentHelper { } /// Returns the appointment height from the duration passed. - static double getAppointmentHeightFromDuration(Duration? minimumDuration, - SfCalendar calendar, double timeIntervalHeight) { + static double getAppointmentHeightFromDuration( + Duration? minimumDuration, + SfCalendar calendar, + double timeIntervalHeight, + ) { if (minimumDuration == null || minimumDuration.inMinutes <= 0) { return 0; } - final double hourHeight = (60 / + final double hourHeight = + (60 / CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings)) * timeIntervalHeight; return minimumDuration.inMinutes * (hourHeight / 60); } static bool _isIntersectingAppointmentInDayView( - SfCalendar calendar, - CalendarView view, - CalendarAppointment currentApp, - AppointmentView appView, - CalendarAppointment appointment, - int timeIntervalMinutes) { + SfCalendar calendar, + CalendarView view, + CalendarAppointment currentApp, + AppointmentView appView, + CalendarAppointment appointment, + int timeIntervalMinutes, + ) { if (currentApp == appointment) { return false; } @@ -527,11 +677,14 @@ class AppointmentHelper { int minimumAppointmentMinutes = calendar.timeSlotViewSettings.minimumAppointmentDuration != null ? calendar - .timeSlotViewSettings.minimumAppointmentDuration!.inMinutes + .timeSlotViewSettings + .minimumAppointmentDuration! + .inMinutes : 0; - minimumAppointmentMinutes = minimumAppointmentMinutes > timeIntervalMinutes - ? timeIntervalMinutes - : minimumAppointmentMinutes; + minimumAppointmentMinutes = + minimumAppointmentMinutes > timeIntervalMinutes + ? timeIntervalMinutes + : minimumAppointmentMinutes; if (minimumAppointmentMinutes > 0 && !isTimelineMonth) { final int timeIntervalMinutes = calendar.timeSlotViewSettings.timeInterval.inMinutes; @@ -539,17 +692,21 @@ class AppointmentHelper { minimumAppointmentMinutes > timeIntervalMinutes ? timeIntervalMinutes : minimumAppointmentMinutes; - if (getDifference(currentAppointmentStartTime, currentAppointmentEndTime) - .inMinutes < + if (getDifference( + currentAppointmentStartTime, + currentAppointmentEndTime, + ).inMinutes < minimumAppointmentMinutes) { - currentAppointmentEndTime = currentAppointmentStartTime - .add(Duration(minutes: minimumAppointmentMinutes)); + currentAppointmentEndTime = currentAppointmentStartTime.add( + Duration(minutes: minimumAppointmentMinutes), + ); } if (getDifference(appointmentStartTime, appointmentEndTime).inMinutes < minimumAppointmentMinutes) { - appointmentEndTime = appointmentStartTime - .add(Duration(minutes: minimumAppointmentMinutes)); + appointmentEndTime = appointmentStartTime.add( + Duration(minutes: minimumAppointmentMinutes), + ); } } @@ -573,14 +730,21 @@ class AppointmentHelper { /// condition and returned that it's a intercept appointment or not. if (isTimelineMonth) { return isSameDate( - currentApp.actualStartTime, appointment.actualStartTime) || - isSameDate(currentApp.actualEndTime, appointment.actualEndTime); + currentApp.actualStartTime, + appointment.actualStartTime, + ) || + isSameDate(currentApp.actualStartTime, appointment.actualEndTime) || + isSameDate(currentApp.actualEndTime, appointment.actualStartTime); } if (CalendarViewHelper.isSameTimeSlot( - currentAppointmentStartTime, appointmentStartTime) || + currentAppointmentStartTime, + appointmentStartTime, + ) || CalendarViewHelper.isSameTimeSlot( - currentAppointmentEndTime, appointmentEndTime)) { + currentAppointmentEndTime, + appointmentEndTime, + )) { return true; } @@ -588,7 +752,10 @@ class AppointmentHelper { } static bool _iterateAppointment( - CalendarAppointment app, bool isTimeline, bool isAllDay) { + CalendarAppointment app, + bool isTimeline, + bool isAllDay, + ) { if (isAllDay) { if (!isTimeline && app.isAllDay) { app.actualEndTime = convertToEndTime(app.actualEndTime); @@ -642,8 +809,11 @@ class AppointmentHelper { return boolValue1.compareTo(boolValue2); } - static AppointmentView _getAppointmentView(CalendarAppointment appointment, - List appointmentCollection, int? resourceIndex) { + static AppointmentView _getAppointmentView( + CalendarAppointment appointment, + List appointmentCollection, + int? resourceIndex, + ) { AppointmentView? appointmentRenderer; for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView view = appointmentCollection[i]; @@ -670,56 +840,71 @@ class AppointmentHelper { /// Update the appointment view collection position and its max position /// details. static void setAppointmentPositionAndMaxPosition( - List appointmentCollection, - SfCalendar calendar, - CalendarView view, - List visibleAppointments, - bool isAllDay, - [int? resourceIndex]) { + List appointmentCollection, + SfCalendar calendar, + CalendarView view, + List visibleAppointments, + bool isAllDay, [ + int? resourceIndex, + ]) { final bool isTimeline = CalendarViewHelper.isTimelineView(view); - final List normalAppointments = visibleAppointments - .where((CalendarAppointment app) => - _iterateAppointment(app, isTimeline, isAllDay)) - .toList(); + final List normalAppointments = + visibleAppointments + .where( + (CalendarAppointment app) => + _iterateAppointment(app, isTimeline, isAllDay), + ) + .toList(); normalAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); if (!isTimeline) { normalAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - _orderAppointmentsDescending(app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + _orderAppointmentsDescending(app1.isSpanned, app2.isSpanned), + ); normalAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - _orderAppointmentsDescending(app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + _orderAppointmentsDescending(app1.isAllDay, app2.isAllDay), + ); } else { normalAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + orderAppointmentsAscending(app1.isAllDay, app2.isAllDay), + ); normalAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - orderAppointmentsAscending(app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + orderAppointmentsAscending(app1.isSpanned, app2.isSpanned), + ); } final Map> dict = >{}; final List processedViews = []; int maxColsCount = 1; - final int timeIntervalMinutes = - CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); + final int timeIntervalMinutes = CalendarViewHelper.getTimeInterval( + calendar.timeSlotViewSettings, + ); 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)) { + calendar.timeSlotViewSettings.nonWorkingDays.contains( + currentAppointment.actualStartTime.weekday, + ) && + calendar.timeSlotViewSettings.nonWorkingDays.contains( + currentAppointment.actualEndTime.weekday, + )) { continue; } List? intersectingApps; final AppointmentView currentAppView = _getAppointmentView( - currentAppointment, appointmentCollection, resourceIndex); + currentAppointment, + appointmentCollection, + resourceIndex, + ); for (int position = 0; position < maxColsCount; position++) { bool isIntersecting = false; @@ -730,12 +915,13 @@ class AppointmentHelper { } if (_isIntersectingAppointmentInDayView( - calendar, - view, - currentAppointment, - previousApp, - previousApp.appointment!, - timeIntervalMinutes)) { + calendar, + view, + currentAppointment, + previousApp, + previousApp.appointment!, + timeIntervalMinutes, + )) { isIntersecting = true; if (intersectingApps == null) { @@ -770,13 +956,18 @@ class AppointmentHelper { intersectingApps = []; dict[dict.keys.length] = intersectingApps; } else if (intersectingApps.isNotEmpty) { - position = intersectingApps - .reduce((AppointmentView currentAppview, - AppointmentView nextAppview) => - currentAppview.maxPositions > nextAppview.maxPositions - ? currentAppview - : nextAppview) - .maxPositions; + position = + intersectingApps + .reduce( + ( + AppointmentView currentAppview, + AppointmentView nextAppview, + ) => + currentAppview.maxPositions > nextAppview.maxPositions + ? currentAppview + : nextAppview, + ) + .maxPositions; } intersectingApps.add(currentAppView); @@ -794,13 +985,18 @@ class AppointmentHelper { intersectingApps = []; dict[dict.keys.length] = intersectingApps; } else if (intersectingApps.isNotEmpty) { - maxPosition = intersectingApps - .reduce((AppointmentView currentAppview, - AppointmentView nextAppview) => - currentAppview.maxPositions > nextAppview.maxPositions - ? currentAppview - : nextAppview) - .maxPositions; + maxPosition = + intersectingApps + .reduce( + ( + AppointmentView currentAppview, + AppointmentView nextAppview, + ) => + currentAppview.maxPositions > nextAppview.maxPositions + ? currentAppview + : nextAppview, + ) + .maxPositions; if (currentAppView.position == maxPosition) { maxPosition++; @@ -827,7 +1023,10 @@ class AppointmentHelper { /// 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) { + DateTime date, + String? appTimeZoneId, + String? calendarTimeZoneId, + ) { if (((appTimeZoneId == null || appTimeZoneId == '') && (calendarTimeZoneId == null || calendarTimeZoneId == '')) || calendarTimeZoneId == appTimeZoneId) { @@ -843,24 +1042,26 @@ class AppointmentHelper { //// 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); + 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); + _timeZoneInfoToOlsonTimeZone(appTimeZoneId), + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second, + ); final Duration offset = DateTime.now().timeZoneOffset; @@ -874,12 +1075,13 @@ class AppointmentHelper { /// 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); + localTimeZoneDate.year, + localTimeZoneDate.month, + localTimeZoneDate.day, + localTimeZoneDate.hour, + localTimeZoneDate.minute, + localTimeZoneDate.second, + ); } } @@ -887,36 +1089,38 @@ class AppointmentHelper { DateTime actualConvertedDate; //// Convert the converted date with calendar time zone if (calendarTimeZoneId == 'Dateline Standard Time') { - actualConvertedDate = - convertedDate.toUtc().subtract(const Duration(hours: 12)); + 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); + 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); + 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); + actualConvertedDate.year, + actualConvertedDate.month, + actualConvertedDate.day, + actualConvertedDate.hour, + actualConvertedDate.minute, + actualConvertedDate.second, + ); } } @@ -926,36 +1130,62 @@ class AppointmentHelper { /// 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}) { + 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); + final CalendarAppointment calendarAppointment = appointments[j]; + calendarAppointment.actualStartTime = convertTimeToAppointmentTimeZone( + calendarAppointment.startTime, + calendarAppointment.startTimeZone, + calendarTimeZone, + ); + calendarAppointment.actualEndTime = convertTimeToAppointmentTimeZone( + calendarAppointment.endTime, + calendarAppointment.endTimeZone, + calendarTimeZone, + ); + + final List tempAppointments = + []; - 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; + /// 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. + calendarAppointment.exactStartTime = calendarAppointment.actualStartTime; + calendarAppointment.exactEndTime = calendarAppointment.actualEndTime; + if (calendarAppointment.recurrenceRule != null && + calendarAppointment.recurrenceRule != '') { + _getRecurrenceAppointments( + calendarAppointment, + tempAppointments, + startDate, + endDate, + calendarTimeZone, + ); + } else { + tempAppointments.add(calendarAppointment); + } + final int appointmentLength = tempAppointments.length; + for (int i = 0; i < appointmentLength; i++) { + final CalendarAppointment appointment = tempAppointments[i]; + if (isAppointmentWithinVisibleDateRange( + appointment, + startDate, + endDate, + )) { /// 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. @@ -970,8 +1200,9 @@ class AppointmentHelper { appointment.exactEndTime.month) && appointment.exactStartTime.isBefore(appointment.exactEndTime) && getDifference( - appointment.exactStartTime, appointment.exactEndTime) - .inDays == + appointment.exactStartTime, + appointment.exactEndTime, + ).inDays == 0 && (appointment.exactEndTime.hour != 0 || appointment.exactEndTime.minute != 0) && @@ -981,37 +1212,45 @@ class AppointmentHelper { final CalendarAppointment spannedAppointment = _copy(appointment); if (i == 0) { spannedAppointment.actualEndTime = DateTime( - appointment.exactStartTime.year, - appointment.exactStartTime.month, - appointment.exactStartTime.day, - 23, - 59, - 59); + 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); + appointment.exactEndTime.year, + appointment.exactEndTime.month, + appointment.exactEndTime.day, + ); } - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment.actualStartTime - : convertTimeToAppointmentTimeZone( - appointment.actualStartTime, - calendarTimeZone, - appointment.startTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment.actualEndTime - : convertTimeToAppointmentTimeZone(appointment.actualEndTime, - calendarTimeZone, appointment.endTimeZone); + spannedAppointment.startTime = + spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + calendarTimeZone, + appointment.startTimeZone, + ); + spannedAppointment.endTime = + spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + calendarTimeZone, + appointment.endTimeZone, + ); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate if (isAppointmentWithinVisibleDateRange( - spannedAppointment, startDate, endDate)) { + spannedAppointment, + startDate, + endDate, + )) { appointmentColl.add(spannedAppointment); } } @@ -1026,128 +1265,206 @@ class AppointmentHelper { //// 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, + startDate, + endDate, + )) { appointment.isSpanned = _isSpanned(appointment); appointmentColl.add(appointment); } else if (_isAppointmentDateWithinVisibleDateRange( - appointment.actualStartTime, startDate, endDate)) { + 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); + final CalendarAppointment spannedAppointment = _copy( + appointment, + ); if (i == 0) { spannedAppointment.actualEndTime = DateTime( - endDate.year, endDate.month, endDate.day, 23, 59, 59); + endDate.year, + endDate.month, + endDate.day, + 23, + 59, + 59, + ); } else { spannedAppointment.actualStartTime = DateTime( - endDate.year, endDate.month, endDate.day, 0, 0, 0); + endDate.year, + endDate.month, + endDate.day, + ); } - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment.actualStartTime - : convertTimeToAppointmentTimeZone( - appointment.actualStartTime, - calendarTimeZone, - appointment.startTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment.actualEndTime - : convertTimeToAppointmentTimeZone( - appointment.actualEndTime, - calendarTimeZone, - appointment.endTimeZone); + spannedAppointment.startTime = + spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + calendarTimeZone, + appointment.startTimeZone, + ); + spannedAppointment.endTime = + spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + calendarTimeZone, + appointment.endTimeZone, + ); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate if (_isAppointmentInVisibleDateRange( - spannedAppointment, startDate, endDate)) { + spannedAppointment, + startDate, + endDate, + )) { spannedAppointment.isSpanned = _isSpanned(spannedAppointment); appointmentColl.add(spannedAppointment); } } } else if (_isAppointmentDateWithinVisibleDateRange( - appointment.actualEndTime, startDate, endDate)) { + 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); + final CalendarAppointment spannedAppointment = _copy( + appointment, + ); if (i == 0) { spannedAppointment.actualStartTime = appointment.actualStartTime; - final DateTime date = - DateTimeHelper.getDateTimeValue(addDays(startDate, -1)); - spannedAppointment.actualEndTime = - DateTime(date.year, date.month, date.day, 23, 59, 59); + final DateTime date = DateTimeHelper.getDateTimeValue( + 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); + startDate.year, + startDate.month, + startDate.day, + ); } - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment.actualStartTime - : convertTimeToAppointmentTimeZone( - appointment.actualStartTime, - calendarTimeZone, - appointment.startTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment.actualEndTime - : convertTimeToAppointmentTimeZone( - appointment.actualEndTime, - calendarTimeZone, - appointment.endTimeZone); + spannedAppointment.startTime = + spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + calendarTimeZone, + appointment.startTimeZone, + ); + spannedAppointment.endTime = + spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + calendarTimeZone, + appointment.endTimeZone, + ); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate if (_isAppointmentInVisibleDateRange( - spannedAppointment, startDate, endDate)) { + spannedAppointment, + startDate, + endDate, + )) { spannedAppointment.isSpanned = _isSpanned(spannedAppointment); appointmentColl.add(spannedAppointment); } } } else if (!_isAppointmentDateWithinVisibleDateRange( - appointment.actualEndTime, startDate, endDate) && + appointment.actualEndTime, + startDate, + endDate, + ) && !_isAppointmentDateWithinVisibleDateRange( - appointment.actualStartTime, startDate, endDate)) { + 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); + final CalendarAppointment spannedAppointment = _copy( + appointment, + ); if (i == 0) { - final DateTime date = - DateTimeHelper.getDateTimeValue(addDays(startDate, -1)); - spannedAppointment.actualEndTime = - DateTime(date.year, date.month, date.day, 23, 59, 59); + final DateTime date = DateTimeHelper.getDateTimeValue( + 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); + startDate.year, + startDate.month, + startDate.day, + ); spannedAppointment.actualEndTime = DateTime( - endDate.year, endDate.month, endDate.day, 23, 59, 59); + endDate.year, + endDate.month, + endDate.day, + 23, + 59, + 59, + ); } else { - final DateTime date = - DateTimeHelper.getDateTimeValue(addDays(endDate, 1)); - spannedAppointment.actualStartTime = - DateTime(date.year, date.month, date.day, 0, 0, 0); + final DateTime date = DateTimeHelper.getDateTimeValue( + addDays(endDate, 1), + ); + spannedAppointment.actualStartTime = DateTime( + date.year, + date.month, + date.day, + ); } - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment.actualStartTime - : convertTimeToAppointmentTimeZone( - appointment.actualStartTime, - calendarTimeZone, - appointment.startTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment.actualEndTime - : convertTimeToAppointmentTimeZone( - appointment.actualEndTime, - calendarTimeZone, - appointment.endTimeZone); + spannedAppointment.startTime = + spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + calendarTimeZone, + appointment.startTimeZone, + ); + spannedAppointment.endTime = + spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + calendarTimeZone, + appointment.endTimeZone, + ); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate if (_isAppointmentInVisibleDateRange( - spannedAppointment, startDate, endDate)) { + spannedAppointment, + startDate, + endDate, + )) { spannedAppointment.isSpanned = _isSpanned(spannedAppointment); appointmentColl.add(spannedAppointment); } @@ -1160,51 +1477,53 @@ class AppointmentHelper { appointmentColl.add(appointment); } } - - 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); } return appointmentColl; } static CalendarAppointment _cloneRecurrenceAppointment( - CalendarAppointment appointment, - DateTime recursiveDate, - String? calendarTimeZone) { + CalendarAppointment appointment, + DateTime recursiveDate, + String? calendarTimeZone, + ) { final CalendarAppointment occurrenceAppointment = _copy(appointment); occurrenceAppointment.actualStartTime = recursiveDate; - occurrenceAppointment.startTime = occurrenceAppointment.isAllDay - ? occurrenceAppointment.actualStartTime - : convertTimeToAppointmentTimeZone( - occurrenceAppointment.actualStartTime, - calendarTimeZone, - occurrenceAppointment.startTimeZone); + occurrenceAppointment.startTime = + occurrenceAppointment.isAllDay + ? occurrenceAppointment.actualStartTime + : convertTimeToAppointmentTimeZone( + occurrenceAppointment.actualStartTime, + calendarTimeZone, + occurrenceAppointment.startTimeZone, + ); final int minutes = - getDifference(appointment.actualStartTime, appointment.actualEndTime) - .inMinutes; + getDifference( + appointment.actualStartTime, + appointment.actualEndTime, + ).inMinutes; occurrenceAppointment.actualEndTime = DateTimeHelper.getDateTimeValue( - addDuration( - occurrenceAppointment.actualStartTime, Duration(minutes: minutes))); - occurrenceAppointment.endTime = occurrenceAppointment.isAllDay - ? occurrenceAppointment.actualEndTime - : convertTimeToAppointmentTimeZone(occurrenceAppointment.actualEndTime, - calendarTimeZone, occurrenceAppointment.endTimeZone); - occurrenceAppointment.isSpanned = _isSpanned(occurrenceAppointment) && - getDifference(occurrenceAppointment.startTime, - occurrenceAppointment.endTime) - .inDays > + addDuration( + occurrenceAppointment.actualStartTime, + Duration(minutes: minutes), + ), + ); + occurrenceAppointment.endTime = + occurrenceAppointment.isAllDay + ? occurrenceAppointment.actualEndTime + : convertTimeToAppointmentTimeZone( + occurrenceAppointment.actualEndTime, + calendarTimeZone, + occurrenceAppointment.endTimeZone, + ); + occurrenceAppointment.isSpanned = + _isSpanned(occurrenceAppointment) && + getDifference( + occurrenceAppointment.startTime, + occurrenceAppointment.endTime, + ).inDays > 0; occurrenceAppointment.exactStartTime = occurrenceAppointment.actualStartTime; @@ -1216,8 +1535,10 @@ class AppointmentHelper { /// Generate the calendar appointment collection from custom appointment /// collection. static List generateCalendarAppointments( - CalendarDataSource? calendarData, String? calendarTimeZone, - [List? appointments]) { + CalendarDataSource? calendarData, + String? calendarTimeZone, [ + List? appointments, + ]) { final List calendarAppointmentCollection = []; if (calendarData == null) { @@ -1239,29 +1560,42 @@ class AppointmentHelper { 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; + 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) && + item.isSpanned = + _isSpanned(item) && getDifference(appStartTime, appEndTime).inDays > 0; } } else { for (int i = 0; i < dataSource.length; i++) { final dynamic item = dataSource[i]; - final CalendarAppointment app = - _createAppointment(item, calendarData, calendarTimeZone); + final CalendarAppointment app = _createAppointment( + item, + calendarData, + calendarTimeZone, + ); final DateTime appStartTime = app.startTime; final DateTime appEndTime = app.endTime; - app.isSpanned = _isSpanned(app) && + app.isSpanned = + _isSpanned(app) && getDifference(appStartTime, appEndTime).inDays > 0; calendarAppointmentCollection.add(app); } @@ -1270,79 +1604,101 @@ class AppointmentHelper { return calendarAppointmentCollection; } - static CalendarAppointment _createAppointment(Object appointmentObject, - CalendarDataSource calendarData, String? calendarTimeZone) { + 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, - recurrenceId: appointmentObject.recurrenceId, - id: appointmentObject.id); + 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, + recurrenceId: appointmentObject.recurrenceId, + id: appointmentObject.id, + ); } 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), - recurrenceId: calendarData.getRecurrenceId(index), - id: calendarData.getId(index)); + 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), + recurrenceId: calendarData.getRecurrenceId(index), + id: calendarData.getId(index), + ); } 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; + 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) { + CalendarAppointment appointment, + String? scheduleTimeZone, + ) { if (appointment.actualEndTime.isBefore(appointment.actualStartTime) && !appointment.isAllDay) { appointment.endTime = convertTimeToAppointmentTimeZone( - addDuration(appointment.actualStartTime, const Duration(minutes: 30)), - scheduleTimeZone, - appointment.endTimeZone); - appointment.actualEndTime = !appointment.isAllDay - ? convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, scheduleTimeZone) - : appointment.endTime; + addDuration(appointment.actualStartTime, const Duration(minutes: 30)), + scheduleTimeZone, + appointment.endTimeZone, + ); + appointment.actualEndTime = + !appointment.isAllDay + ? convertTimeToAppointmentTimeZone( + appointment.endTime, + appointment.endTimeZone, + scheduleTimeZone, + ) + : appointment.endTime; } } static void _getRecurrenceAppointments( - CalendarAppointment appointment, - List appointments, - DateTime visibleStartDate, - DateTime visibleEndDate, - String? scheduleTimeZone) { + CalendarAppointment appointment, + List appointments, + DateTime visibleStartDate, + DateTime visibleEndDate, + String? scheduleTimeZone, + ) { final DateTime appStartTime = appointment.actualStartTime; if (appStartTime.isAfter(visibleEndDate)) { return; @@ -1358,11 +1714,35 @@ class AppointmentHelper { final List recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - rule, appointment.actualStartTime, - recurrenceDuration: getDifference( - appointment.exactStartTime, appointment.exactEndTime), - specificStartDate: visibleStartDate, - specificEndDate: visibleEndDate); + rule, + appointment.actualStartTime, + recurrenceDuration: getDifference( + appointment.exactStartTime, + appointment.exactEndTime, + ), + specificStartDate: visibleStartDate, + specificEndDate: visibleEndDate, + ); + + List recDates = []; + if (recursiveDates.isNotEmpty) { + String countRule = recurrenceRule; + + /// To check whether the appointment is pattern or not, we need to get + /// the first appointment of the rrule, hence added count as 1 in rrule, + /// if the count is not given in the rrule, we didn't change + /// the appointment's rrule we used a separate property internally + /// for our purpose. + if (!countRule.contains('COUNT')) { + countRule = '$countRule;COUNT=1'; + } + + recDates = RecurrenceHelper.getRecurrenceDateTimeCollection( + countRule, + appointment.actualStartTime, + specificStartDate: appointment.startTime, + ); + } for (int j = 0; j < recursiveDates.length; j++) { final DateTime recursiveDate = recursiveDates[j]; @@ -1370,7 +1750,10 @@ class AppointmentHelper { bool isDateContains = false; for (int i = 0; i < appointment.recurrenceExceptionDates!.length; i++) { final DateTime date = convertTimeToAppointmentTimeZone( - appointment.recurrenceExceptionDates![i], '', scheduleTimeZone); + appointment.recurrenceExceptionDates![i], + '', + scheduleTimeZone, + ); if (date.year == recursiveDate.year && date.month == recursiveDate.month && date.day == recursiveDate.day) { @@ -1385,30 +1768,20 @@ class AppointmentHelper { final CalendarAppointment occurrenceAppointment = _cloneRecurrenceAppointment( - appointment, recursiveDate, scheduleTimeZone); - String recurrenceRule = appointment.recurrenceRule!; - - /// To check whether the appointment is pattern or not, we need to get - /// the first appointment of the rrule, hence added count as 1 in rrule, - /// if count not given in the rrule, here we didn't change - /// the appointment's rrule we used a separate property internally - /// for our purpose. - if (!recurrenceRule.contains('COUNT')) { - recurrenceRule = '$recurrenceRule;COUNT=1'; - } - final List recDates = - RecurrenceHelper.getRecurrenceDateTimeCollection( - recurrenceRule, appointment.actualStartTime, - specificStartDate: appointment.startTime); + appointment, + recursiveDate, + scheduleTimeZone, + ); /// Here we used isOccurrenceAppointment keyword to identify the /// occurrence appointment When we clone the pattern appointment for /// occurrence appointment we have append the string in the notes and /// here we identify based on the string and removed the appended string. - occurrenceAppointment.notes = recDates.isNotEmpty && - isSameDate(occurrenceAppointment.startTime, recDates[0]) - ? appointment.notes - : appointment.notes == null + occurrenceAppointment.notes = + recDates.isNotEmpty && + isSameDate(occurrenceAppointment.startTime, recDates[0]) + ? appointment.notes + : appointment.notes == null ? 'isOccurrenceAppointment' : '${appointment.notes!}isOccurrenceAppointment'; appointments.add(occurrenceAppointment); 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 919239f9e..8c746bce8 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,13 +1,12 @@ 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_core/core.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart' show IterableDiagnostics; import '../../../calendar.dart'; import '../common/calendar_view_helper.dart'; +import 'appointment_helper.dart'; /// An object that maintains the data source for [SfCalendar]. /// @@ -282,8 +281,10 @@ abstract class CalendarDataSource /// } /// ``` List getVisibleAppointments( - DateTime startDate, String calendarTimeZone, - [DateTime? endDate]) { + DateTime startDate, + String calendarTimeZone, [ + DateTime? endDate, + ]) { endDate ??= startDate; /// Converts the given appointment type to calendar appointment, to handle @@ -294,14 +295,20 @@ abstract class CalendarDataSource AppointmentHelper.generateCalendarAppointments(this, calendarTimeZone); calendarAppointments = AppointmentHelper.getVisibleAppointments( - startDate, endDate, calendarAppointments, calendarTimeZone, false, - canCreateNewAppointment: false); + startDate, + endDate, + calendarAppointments, + calendarTimeZone, + false, + canCreateNewAppointment: false, + ); final List visibleAppointments = []; for (int i = 0; i < calendarAppointments.length; i++) { - visibleAppointments - .add(calendarAppointments[i].convertToCalendarAppointment()); + visibleAppointments.add( + calendarAppointments[i].convertToCalendarAppointment(), + ); } return visibleAppointments; @@ -395,7 +402,10 @@ abstract class CalendarDataSource /// /// ``` Appointment? getOccurrenceAppointment( - Object? patternAppointment, DateTime date, String calendarTimeZone) { + Object? patternAppointment, + DateTime date, + String calendarTimeZone, + ) { if (patternAppointment == null) { return null; } @@ -403,7 +413,10 @@ abstract class CalendarDataSource final List patternAppointmentColl = [patternAppointment]; final List patternAppointments = AppointmentHelper.generateCalendarAppointments( - this, calendarTimeZone, patternAppointmentColl); + this, + calendarTimeZone, + patternAppointmentColl, + ); final CalendarAppointment patternCalendarAppointment = patternAppointments[0]; @@ -411,10 +424,14 @@ abstract class CalendarDataSource patternCalendarAppointment.recurrenceRule!.isEmpty) { return null; } else if (CalendarViewHelper.isDateInDateCollection( - patternCalendarAppointment.recurrenceExceptionDates, date)) { + patternCalendarAppointment.recurrenceExceptionDates, + date, + )) { final List dataSourceAppointments = AppointmentHelper.generateCalendarAppointments( - this, calendarTimeZone); + this, + calendarTimeZone, + ); for (int i = 0; i < dataSourceAppointments.length; i++) { final CalendarAppointment dataSourceAppointment = dataSourceAppointments[i]; @@ -427,8 +444,13 @@ abstract class CalendarDataSource } else { final List occurrenceAppointments = AppointmentHelper.getVisibleAppointments( - date, date, patternAppointments, calendarTimeZone, false, - canCreateNewAppointment: false); + date, + date, + patternAppointments, + calendarTimeZone, + false, + canCreateNewAppointment: false, + ); if (occurrenceAppointments.isEmpty) { return null; @@ -520,16 +542,21 @@ abstract class CalendarDataSource /// /// ``` Object? getPatternAppointment( - Object? occurrenceAppointment, String calendarTimeZone) { + Object? occurrenceAppointment, + String calendarTimeZone, + ) { if (occurrenceAppointment == null) { return null; } final List occurrenceAppointmentColl = [ - occurrenceAppointment + occurrenceAppointment, ]; final List occurrenceAppointments = AppointmentHelper.generateCalendarAppointments( - this, calendarTimeZone, occurrenceAppointmentColl); + this, + calendarTimeZone, + occurrenceAppointmentColl, + ); final CalendarAppointment occurrenceCalendarAppointment = occurrenceAppointments[0]; if ((occurrenceCalendarAppointment.recurrenceRule == null || @@ -539,7 +566,10 @@ abstract class CalendarDataSource } final List dataSourceAppointments = AppointmentHelper.generateCalendarAppointments( - this, calendarTimeZone, appointments); + this, + calendarTimeZone, + appointments, + ); for (int i = 0; i < dataSourceAppointments.length; i++) { final CalendarAppointment dataSourceAppointment = @@ -1034,10 +1064,16 @@ abstract class CalendarDataSource @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(IterableDiagnostics(appointments) - .toDiagnosticsNode(name: 'appointments')); - properties.add(IterableDiagnostics(resources) - .toDiagnosticsNode(name: 'resources')); + properties.add( + IterableDiagnostics( + appointments, + ).toDiagnosticsNode(name: 'appointments'), + ); + properties.add( + IterableDiagnostics( + resources, + ).toDiagnosticsNode(name: 'resources'), + ); } } @@ -1047,8 +1083,8 @@ abstract class CalendarDataSource /// See also: /// [CalendarDataSourceAction], the actions which can be performed using the /// calendar. -typedef CalendarDataSourceCallback = void Function( - CalendarDataSourceAction, List); +typedef CalendarDataSourceCallback = + void Function(CalendarDataSourceAction, List); /// Notifier used to notify the action performed in the [CalendarDataSource] class CalendarDataSourceChangeNotifier with Diagnosticable { 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 c7208ec4d..646109fed 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,7 +1,7 @@ -import 'package:syncfusion_flutter_calendar/src/calendar/common/date_time_engine.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; import 'appointment_helper.dart'; // ignore: avoid_classes_with_only_static_members @@ -9,11 +9,12 @@ import 'appointment_helper.dart'; /// month view. class MonthAppointmentHelper { static void _createVisibleAppointments( - List appointmentCollection, - List visibleAppointments, - List visibleDates, - int startIndex, - int endIndex) { + 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; @@ -30,12 +31,15 @@ class MonthAppointmentHelper { appointment.actualStartTime.day == appointment.actualEndTime.day && appointment.actualStartTime.month == appointment.actualEndTime.month) { - final AppointmentView appointmentView = - _createAppointmentView(appointmentCollection); + final AppointmentView appointmentView = _createAppointmentView( + appointmentCollection, + ); appointmentView.appointment = appointment; appointmentView.canReuse = false; - appointmentView.startIndex = - getDateIndex(appointment.actualStartTime, visibleDates); + appointmentView.startIndex = getDateIndex( + appointment.actualStartTime, + visibleDates, + ); /// Check the index value before the view start index then assign the /// start index as visible start index @@ -50,8 +54,10 @@ class MonthAppointmentHelper { appointmentView.startIndex = startIndex; } - appointmentView.endIndex = - getDateIndex(appointment.actualEndTime, visibleDates); + appointmentView.endIndex = getDateIndex( + appointment.actualEndTime, + visibleDates, + ); /// Check the index value after the view end index then assign the /// end index as visible end index @@ -70,12 +76,15 @@ class MonthAppointmentHelper { appointmentCollection.add(appointmentView); } } else { - final AppointmentView appointmentView = - _createAppointmentView(appointmentCollection); + final AppointmentView appointmentView = _createAppointmentView( + appointmentCollection, + ); appointmentView.appointment = appointment; appointmentView.canReuse = false; - appointmentView.startIndex = - getDateIndex(appointment.actualStartTime, visibleDates); + appointmentView.startIndex = getDateIndex( + appointment.actualStartTime, + visibleDates, + ); /// Check the index value before the view start index then assign the /// start index as visible start index @@ -90,8 +99,10 @@ class MonthAppointmentHelper { appointmentView.startIndex = startIndex; } - appointmentView.endIndex = - getDateIndex(appointment.actualEndTime, visibleDates); + appointmentView.endIndex = getDateIndex( + appointment.actualEndTime, + visibleDates, + ); /// Check the index value after the view end index then assign the /// end index as visible end index @@ -107,34 +118,40 @@ class MonthAppointmentHelper { } _createAppointmentInfoForSpannedAppointment( - appointmentView, appointmentCollection); + appointmentView, + appointmentCollection, + ); } } } static void _createAppointmentInfoForSpannedAppointment( - AppointmentView appointmentView, - List appointmentCollection) { + AppointmentView appointmentView, + List appointmentCollection, + ) { if (appointmentView.startIndex ~/ DateTime.daysPerWeek != appointmentView.endIndex ~/ DateTime.daysPerWeek) { final int endIndex = appointmentView.endIndex; appointmentView.endIndex = (((appointmentView.startIndex ~/ DateTime.daysPerWeek) + 1) * - DateTime.daysPerWeek) - - 1; + DateTime.daysPerWeek) - + 1; appointmentView.isSpanned = true; if (!appointmentCollection.contains(appointmentView)) { appointmentCollection.add(appointmentView); } - final AppointmentView appointmentView1 = - _createAppointmentView(appointmentCollection); + final AppointmentView appointmentView1 = _createAppointmentView( + appointmentCollection, + ); appointmentView1.appointment = appointmentView.appointment; appointmentView1.canReuse = false; appointmentView1.startIndex = appointmentView.endIndex + 1; appointmentView1.endIndex = endIndex; _createAppointmentInfoForSpannedAppointment( - appointmentView1, appointmentCollection); + appointmentView1, + appointmentCollection, + ); } else { appointmentView.isSpanned = true; if (!appointmentCollection.contains(appointmentView)) { @@ -144,9 +161,10 @@ class MonthAppointmentHelper { } static void _setAppointmentPosition( - List appointmentViewCollection, - AppointmentView appointmentView, - int viewIndex) { + List appointmentViewCollection, + AppointmentView appointmentView, + int viewIndex, + ) { for (int j = 0; j < appointmentViewCollection.length; j++) { //// Break when the collection reaches current appointment if (j >= viewIndex) { @@ -163,14 +181,19 @@ class MonthAppointmentHelper { appointmentView.maxPositions = appointmentView.position; prevAppointmentView.maxPositions = appointmentView.position; _setAppointmentPosition( - appointmentViewCollection, appointmentView, viewIndex); + appointmentViewCollection, + appointmentView, + viewIndex, + ); break; } } } static bool _isInterceptAppointments( - AppointmentView appointmentView1, AppointmentView appointmentView2) { + AppointmentView appointmentView1, + AppointmentView appointmentView2, + ) { if (appointmentView1.startIndex <= appointmentView2.startIndex && appointmentView1.endIndex >= appointmentView2.startIndex || appointmentView2.startIndex <= appointmentView1.startIndex && @@ -192,7 +215,9 @@ class MonthAppointmentHelper { /// 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) { + AppointmentView appointmentView1, + AppointmentView appointmentView2, + ) { if (appointmentView1.appointment == null || appointmentView2.appointment == null) { return 0; @@ -202,12 +227,14 @@ class MonthAppointmentHelper { 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; + 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. @@ -220,12 +247,14 @@ class MonthAppointmentHelper { return startTime1.compareTo(startTime2); } - final DateTime endTime1 = appointment1.isAllDay - ? AppointmentHelper.convertToEndTime(appointment1.exactEndTime) - : appointment1.exactEndTime; - final DateTime endTime2 = appointment2.isAllDay - ? AppointmentHelper.convertToEndTime(appointment2.exactEndTime) - : appointment2.exactEndTime; + 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. @@ -249,17 +278,18 @@ class MonthAppointmentHelper { /// 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 (AppointmentHelper.getDifference(endTime2, startTime2) - .inMinutes - .abs()) - .compareTo(AppointmentHelper.getDifference(endTime1, startTime1) - .inMinutes - .abs()); + return AppointmentHelper.getDifference( + endTime2, + startTime2, + ).inMinutes.abs().compareTo( + AppointmentHelper.getDifference(endTime1, startTime1).inMinutes.abs(), + ); } static void _updateAppointmentPosition( - List appointmentCollection, - Map> indexAppointments) { + List appointmentCollection, + Map> indexAppointments, + ) { appointmentCollection.sort(_orderAppointmentViewBySpanned); for (int j = 0; j < appointmentCollection.length; j++) { @@ -274,9 +304,11 @@ class MonthAppointmentHelper { /// 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++) { + 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. @@ -297,9 +329,11 @@ class MonthAppointmentHelper { 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) { + for ( + int i = count - DateTime.daysPerWeek; + i >= 0; + i -= DateTime.daysPerWeek + ) { final DateTime currentDate = visibleDates[i]; if (currentDate.isBefore(date) || (currentDate.day == date.day && @@ -311,11 +345,12 @@ class MonthAppointmentHelper { } } - final DateTime endDateTime = - DateTimeHelper.getDateTimeValue(addDays(dateTime, 6)); + final DateTime endDateTime = DateTimeHelper.getDateTimeValue( + addDays(dateTime, 6), + ); int currentViewIndex = 0; - while ( - dateTime.isBefore(endDateTime) || isSameDate(dateTime, endDateTime)) { + while (dateTime.isBefore(endDateTime) || + isSameDate(dateTime, endDateTime)) { if (isSameDate(dateTime, date)) { return (row * DateTime.daysPerWeek) + currentViewIndex; } @@ -328,7 +363,8 @@ class MonthAppointmentHelper { } static AppointmentView _createAppointmentView( - List appointmentCollection) { + List appointmentCollection, + ) { AppointmentView? appointmentView; for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView view = appointmentCollection[i]; @@ -352,14 +388,20 @@ class MonthAppointmentHelper { /// 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); + 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 2b94325c8..1e61a1a1c 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,7 +1,7 @@ import 'package:intl/intl.dart' show DateFormat; -import 'package:syncfusion_flutter_calendar/src/calendar/common/date_time_engine.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../common/date_time_engine.dart'; import '../common/enums.dart' show RecurrenceType, RecurrenceRange, WeekDays; import 'appointment_helper.dart'; import 'recurrence_properties.dart'; @@ -10,29 +10,52 @@ 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 = - DateTimeHelper.getDateTimeValue(addDuration(appointmentDate, duration)); + static bool _isRecurrenceInBetweenSpecificRange( + DateTime appointmentDate, + Duration duration, + DateTime visibleStartDate, + DateTime visibleEndTime, + ) { + final DateTime appointmentEndDate = DateTimeHelper.getDateTimeValue( + addDuration(appointmentDate, duration), + ); /// ignore: lines_longer_than_80_chars - return isDateWithInDateRange(visibleStartDate, visibleEndTime, appointmentDate) || + return isDateWithInDateRange( + visibleStartDate, + visibleEndTime, + appointmentDate, + ) || isDateWithInDateRange( - visibleStartDate, visibleEndTime, appointmentEndDate) || + visibleStartDate, + visibleEndTime, + appointmentEndDate, + ) || isDateWithInDateRange( - appointmentDate, appointmentEndDate, visibleStartDate); + appointmentDate, + appointmentEndDate, + visibleStartDate, + ); } static List _getDailyRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {Duration? recurrenceDuration, - DateTime? specificStartDate, - DateTime? specificEndDate}) { + String rRule, + DateTime recurrenceStartDate, { + Duration? recurrenceDuration, + DateTime? specificStartDate, + DateTime? specificEndDate, + }) { final List recDateCollection = []; if (specificEndDate != null) { - specificEndDate = DateTime(specificEndDate.year, specificEndDate.month, - specificEndDate.day, 23, 59, 59); + specificEndDate = DateTime( + specificEndDate.year, + specificEndDate.month, + specificEndDate.day, + 23, + 59, + 59, + ); } recurrenceDuration ??= Duration.zero; @@ -69,16 +92,16 @@ class RecurrenceHelper { DateTime? endDate; if (rRule.contains('UNTIL')) { /// Set the end date value from until date value. - endDate = DateTime.parse(untilValueString); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + endDate = getUntilEndDate(untilValueString); if (isSpecificDateRange) { final DateTime startTime = DateTime( - endDate.year, - endDate.month, - endDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + endDate.year, + endDate.month, + endDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final DateTime endTime = startTime.add(recurrenceDuration); /// Check the visible start date after of recurrence end date, if @@ -92,11 +115,12 @@ class RecurrenceHelper { /// Set the end date value from recurrence start date with /// count and interval values. endDate = AppointmentHelper.addDaysWithTime( - recurrenceStartDate, - (recurrenceCount - 1) * dailyDayGap, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + recurrenceStartDate, + (recurrenceCount - 1) * dailyDayGap, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); /// Return empty collection when the recurrence end date after of visible /// start date. @@ -112,9 +136,10 @@ class RecurrenceHelper { /// NoEndDate specified rule returns empty collection issue fix. if (isSpecificDateRange) { - endDate = endDate == null || endDate.isAfter(specificEndDate) - ? specificEndDate - : endDate; + endDate = + endDate == null || endDate.isAfter(specificEndDate) + ? specificEndDate + : endDate; } int recurrenceIncrementCount = 0; @@ -124,16 +149,24 @@ class RecurrenceHelper { /// recurrence start date is before of visible start date if (isSpecificDateRange && recurrenceStartDate.isBefore(specificStartDate)) { - final DateTime recurrenceInitialDate = DateTime(recurrenceStartDate.year, - recurrenceStartDate.month, recurrenceStartDate.day); - final DateTime visibleInitialDate = DateTime(specificStartDate.year, - specificStartDate.month, specificStartDate.day); + final DateTime recurrenceInitialDate = DateTime( + recurrenceStartDate.year, + recurrenceStartDate.month, + recurrenceStartDate.day, + ); + final DateTime visibleInitialDate = DateTime( + specificStartDate.year, + specificStartDate.month, + specificStartDate.day, + ); /// Total days difference between the visible start date and recurrence /// start date. - final int difference = AppointmentHelper.getDifference( - recurrenceInitialDate, visibleInitialDate) - .inDays; + final int difference = + AppointmentHelper.getDifference( + recurrenceInitialDate, + visibleInitialDate, + ).inDays; final int dayDifference = difference % dailyDayGap; /// Valid recurrences in between the visible start date and recurrence @@ -144,35 +177,43 @@ class RecurrenceHelper { /// and check the previous recurrence date have long recurrence duration. if (dayDifference == 0) { addDate = DateTime( - specificStartDate.year, - specificStartDate.month, - specificStartDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + specificStartDate.year, + specificStartDate.month, + specificStartDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); } else { /// If day difference is not 0 then initial start date is after the /// visible start date so calculate the recurrence from the date before /// the visible start date. check the previous recurrence date have /// long recurrence duration. addDate = AppointmentHelper.addDaysWithTime( - visibleInitialDate, - -dayDifference, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + visibleInitialDate, + -dayDifference, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); } final DateTime recurrenceEndDate = addDate.add(recurrenceDuration); if (incrementCount > 0 && !isSameDate(addDate, recurrenceEndDate)) { - final int durationDifference = recurrenceEndDate.hour > addDate.hour - ? recurrenceDuration.inDays - : recurrenceDuration.inDays + 1; + final int durationDifference = + recurrenceEndDate.hour > addDate.hour + ? recurrenceDuration.inDays + : recurrenceDuration.inDays + 1; final int intervalCount = ((durationDifference ~/ dailyDayGap) * dailyDayGap) + - (durationDifference % dailyDayGap == 0 ? 0 : dailyDayGap); - addDate = AppointmentHelper.addDaysWithTime(addDate, -intervalCount, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + (durationDifference % dailyDayGap == 0 ? 0 : dailyDayGap); + addDate = AppointmentHelper.addDaysWithTime( + addDate, + -intervalCount, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); incrementCount -= intervalCount ~/ dailyDayGap; } @@ -192,10 +233,14 @@ class RecurrenceHelper { while (recurrenceIncrementCount < recurrenceCount || (endDate != null && - (addDate.isBefore(endDate) || isSameDate(addDate, endDate)))) { + (addDate.isBefore(endDate) || addDate == endDate))) { if (isSpecificDateRange) { if (_isRecurrenceInBetweenSpecificRange( - addDate, recurrenceDuration, specificStartDate, specificEndDate)) { + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate, + )) { recDateCollection.add(addDate); } @@ -206,8 +251,13 @@ class RecurrenceHelper { recDateCollection.add(addDate); } - addDate = AppointmentHelper.addDaysWithTime(addDate, dailyDayGap, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + addDate = AppointmentHelper.addDaysWithTime( + addDate, + dailyDayGap, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); recurrenceIncrementCount++; } @@ -215,15 +265,23 @@ class RecurrenceHelper { } static List _getWeeklyRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {Duration? recurrenceDuration, - DateTime? specificStartDate, - DateTime? specificEndDate}) { + String rRule, + DateTime recurrenceStartDate, { + Duration? recurrenceDuration, + DateTime? specificStartDate, + DateTime? specificEndDate, + }) { final List recDateCollection = []; if (specificEndDate != null) { - specificEndDate = DateTime(specificEndDate.year, specificEndDate.month, - specificEndDate.day, 23, 59, 59); + specificEndDate = DateTime( + specificEndDate.year, + specificEndDate.month, + specificEndDate.day, + 23, + 59, + 59, + ); } recurrenceDuration ??= Duration.zero; @@ -243,7 +301,7 @@ class RecurrenceHelper { 'WE', 'TH', 'FR', - 'SA' + 'SA', ]; final List ruleArray = splitRule(rRule, ruleSeparator); if (ruleArray.isEmpty) { @@ -266,9 +324,10 @@ class RecurrenceHelper { recurrenceCountString.isNotEmpty ? int.parse(recurrenceCountString) : 0; int tempCount = 0; - final int weeklyWeekGap = ruleArray.length > 4 && rRule.contains('INTERVAL') - ? int.parse(intervalCountString) - : 1; + final int weeklyWeekGap = + ruleArray.length > 4 && rRule.contains('INTERVAL') + ? int.parse(intervalCountString) + : 1; assert(weeklyByDayPos != -1, 'Invalid weekly recurrence rule'); final List weekDays = []; final String ruleDaysString = weeklyRule[weeklyByDayPos]; @@ -288,16 +347,16 @@ class RecurrenceHelper { DateTime? endDate; if (rRule.contains('UNTIL')) { /// Set the end date value from until date value. - endDate = DateTime.parse(untilValueString); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + endDate = getUntilEndDate(untilValueString); if (isSpecificDateRange) { final DateTime startTime = DateTime( - endDate.year, - endDate.month, - endDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + endDate.year, + endDate.month, + endDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final DateTime endTime = startTime.add(recurrenceDuration); /// Check the visible start date after of recurrence end date, if @@ -338,7 +397,8 @@ class RecurrenceHelper { /// Add initial week days from recurrence start date to week end and /// add interval time after the initial week. - totalDays += DateTime.daysPerWeek - + totalDays += + DateTime.daysPerWeek - weekDay + (DateTime.daysPerWeek * (weeklyWeekGap - 1)); @@ -410,11 +470,12 @@ class RecurrenceHelper { } endDate = AppointmentHelper.addDaysWithTime( - recurrenceStartDate, - totalDays, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + recurrenceStartDate, + totalDays, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); /// Return empty collection when the recurrence end date after of visible /// start date. @@ -430,16 +491,20 @@ class RecurrenceHelper { /// NoEndDate specified rule returns empty collection issue fix. if (isSpecificDateRange) { - endDate = endDate == null || endDate.isAfter(specificEndDate) - ? specificEndDate - : endDate; + endDate = + endDate == null || endDate.isAfter(specificEndDate) + ? specificEndDate + : endDate; } DateTime addDate = recurrenceStartDate; if (isSpecificDateRange && recurrenceStartDate.isBefore(specificStartDate)) { - final DateTime startDate = - DateTime(addDate.year, addDate.month, addDate.day); + final DateTime startDate = DateTime( + addDate.year, + addDate.month, + addDate.day, + ); /// Calculate the total days between the recurrence start and visible /// start date. @@ -448,9 +513,10 @@ class RecurrenceHelper { final DateTime recurrenceEndDate = addDate.add(recurrenceDuration); /// Calculate day difference between the recurrence start and end date. - final int durationDifference = isSameDate(recurrenceEndDate, addDate) - ? 0 - : recurrenceEndDate.hour > addDate.hour + final int durationDifference = + isSameDate(recurrenceEndDate, addDate) + ? 0 + : recurrenceEndDate.hour > addDate.hour ? recurrenceDuration.inDays : recurrenceDuration.inDays + 1; @@ -486,9 +552,10 @@ class RecurrenceHelper { /// Calculate and remove the interval week after the initial week on /// total days difference. - final int initialWeekGap = isOccurrenceInInitialWeek - ? 0 - : DateTime.daysPerWeek * (weeklyWeekGap - 1); + final int initialWeekGap = + isOccurrenceInInitialWeek + ? 0 + : DateTime.daysPerWeek * (weeklyWeekGap - 1); daysDifference -= initialWeekGap; /// Calculate the total valid weeks(sunday to saturday) in between the @@ -503,15 +570,16 @@ class RecurrenceHelper { /// recurrence start date week remaining days to week end /// (DateTime.daysPerWeek - weekDay) + initialWeekGap. addDate = AppointmentHelper.addDaysWithTime( - addDate, - (totalWeeks * DateTime.daysPerWeek * weeklyWeekGap) + - (isOccurrenceInInitialWeek - ? daysDifference - : DateTime.daysPerWeek - weekDay) + - initialWeekGap, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + addDate, + (totalWeeks * DateTime.daysPerWeek * weeklyWeekGap) + + (isOccurrenceInInitialWeek + ? daysDifference + : DateTime.daysPerWeek - weekDay) + + initialWeekGap, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); tempCount = tempRecurrenceCount; } @@ -524,11 +592,16 @@ class RecurrenceHelper { while ((tempCount < recurrenceCount && isWeeklySelected) || (endDate != null && (addDate.isBefore(endDate) || addDate == endDate))) { - final bool isRecurrenceDate = - weekDays.contains(addDate.weekday % DateTime.daysPerWeek); + final bool isRecurrenceDate = weekDays.contains( + addDate.weekday % DateTime.daysPerWeek, + ); if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate) && + if (_isRecurrenceInBetweenSpecificRange( + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate, + ) && isRecurrenceDate) { recDateCollection.add(addDate); } @@ -544,30 +617,45 @@ class RecurrenceHelper { tempCount++; } - addDate = addDate.weekday == DateTime.saturday - ? AppointmentHelper.addDaysWithTime( - addDate, - ((weeklyWeekGap - 1) * DateTime.daysPerWeek) + 1, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond) - : AppointmentHelper.addDaysWithTime(addDate, 1, recurrenceStartHour, - recurrenceStartMinute, recurrenceStartSecond); + addDate = + addDate.weekday == DateTime.saturday + ? AppointmentHelper.addDaysWithTime( + addDate, + ((weeklyWeekGap - 1) * DateTime.daysPerWeek) + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ) + : AppointmentHelper.addDaysWithTime( + addDate, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); } return recDateCollection; } static List _getMonthlyRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {Duration? recurrenceDuration, - DateTime? specificStartDate, - DateTime? specificEndDate}) { + String rRule, + DateTime recurrenceStartDate, { + Duration? recurrenceDuration, + DateTime? specificStartDate, + DateTime? specificEndDate, + }) { final List recDateCollection = []; if (specificEndDate != null) { - specificEndDate = DateTime(specificEndDate.year, specificEndDate.month, - specificEndDate.day, 23, 59, 59); + specificEndDate = DateTime( + specificEndDate.year, + specificEndDate.month, + specificEndDate.day, + 23, + 59, + 59, + ); } recurrenceDuration ??= Duration.zero; @@ -603,21 +691,23 @@ class RecurrenceHelper { recCount = int.parse(recurCount); } - final int monthlyMonthGap = ruleArray.length > 4 && intervalCount.isNotEmpty - ? int.parse(intervalCount) - : 1; + final int monthlyMonthGap = + ruleArray.length > 4 && intervalCount.isNotEmpty + ? int.parse(intervalCount) + : 1; DateTime? endDate; if (rRule.contains('UNTIL')) { - endDate = DateTime.parse(untilValue); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + endDate = getUntilEndDate(untilValue); + if (isSpecificDateRange) { final DateTime startTime = DateTime( - endDate.year, - endDate.month, - endDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + endDate.year, + endDate.month, + endDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final DateTime endTime = startTime.add(recurrenceDuration); /// Check the visible start date after of recurrence end date, if @@ -631,9 +721,10 @@ class RecurrenceHelper { /// NoEndDate specified rule returns empty collection issue fix. if (isSpecificDateRange && !rRule.contains('COUNT')) { - endDate = endDate == null || endDate.isAfter(specificEndDate) - ? specificEndDate - : endDate; + endDate = + endDate == null || endDate.isAfter(specificEndDate) + ? specificEndDate + : endDate; final int addedDateMonth = addDate.month; final int addedDateYear = addDate.year; @@ -644,14 +735,17 @@ class RecurrenceHelper { viewStartDateYear == addedDateYear)) { /// Calculate the total months between the recurrence start date and /// visible start date. - final int totalMonths = (viewStartDateMonth - addedDateMonth) + + final int totalMonths = + (viewStartDateMonth - addedDateMonth) + ((viewStartDateYear - addedDateYear) * 12); /// Calculate the valid month count between the recurrence start date /// and visible start date. final int validMonths = totalMonths ~/ monthlyMonthGap; addDate = DateTime( - addedDateYear, addedDateMonth + (validMonths * monthlyMonthGap)); + addedDateYear, + addedDateMonth + (validMonths * monthlyMonthGap), + ); if (addDate.isBefore(recurrenceStartDate)) { addDate = recurrenceStartDate; } @@ -664,8 +758,14 @@ class RecurrenceHelper { if (byMonthDay == -1) { monthDate = AppointmentHelper.getMonthEndDate(addDate).day; } - final DateTime temp = DateTime(addDate.year, addDate.month, monthDate, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + 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. @@ -682,15 +782,22 @@ class RecurrenceHelper { /// 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 + monthlyMonthGap, 1, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + addDate = DateTime( + addDate.year, + addDate.month + monthlyMonthGap, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final DateTime tempDate = DateTime( - addDate.year, - addDate.month, - monthDate, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + addDate.year, + addDate.month, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); if (tempDate.day == monthDate) { addDate = tempDate; } @@ -708,18 +815,23 @@ class RecurrenceHelper { /// March 1 or 2 based on leap year. monthValue += monthlyMonthGap; addDate = DateTime( - yearValue, - monthValue, - monthDate, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + yearValue, + monthValue, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); continue; } if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate)) { + if (_isRecurrenceInBetweenSpecificRange( + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate, + )) { recDateCollection.add(addDate); } @@ -731,13 +843,20 @@ class RecurrenceHelper { } monthValue += monthlyMonthGap; - monthDate = byMonthDay == -1 - ? AppointmentHelper.getMonthEndDate( - DateTime(yearValue, monthValue, 1)) - .day - : monthDate; - addDate = DateTime(yearValue, monthValue, monthDate, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + monthDate = + byMonthDay == -1 + ? AppointmentHelper.getMonthEndDate( + DateTime(yearValue, monthValue), + ).day + : monthDate; + addDate = DateTime( + yearValue, + monthValue, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); tempCount++; } @@ -746,16 +865,23 @@ class RecurrenceHelper { final int nthWeekDay = _getWeekDay(byDayValue) % DateTime.daysPerWeek; final int bySetPosValue = int.parse(bySetPosCount); - void _updateValidDate() { - final DateTime monthStart = DateTime(addDate.year, addDate.month, 1, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + void updateValidDate() { + final DateTime monthStart = DateTime( + addDate.year, + addDate.month, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final int monthStartWeekday = monthStart.weekday % DateTime.daysPerWeek; final DateTime weekStartDate = AppointmentHelper.addDaysWithTime( - monthStart, - -monthStartWeekday, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + monthStart, + -monthStartWeekday, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); int nthWeek = bySetPosValue; if (monthStartWeekday <= nthWeekDay) { nthWeek = bySetPosValue - 1; @@ -763,30 +889,44 @@ class RecurrenceHelper { if (bySetPosValue.isNegative) { addDate = _getRecurrenceDateForNegativeValue( - bySetPosValue, monthStart, nthWeekDay); + bySetPosValue, + monthStart, + nthWeekDay, + ); } else { addDate = AppointmentHelper.addDaysWithTime( - weekStartDate, - (nthWeek * DateTime.daysPerWeek) + nthWeekDay, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + weekStartDate, + (nthWeek * DateTime.daysPerWeek) + nthWeekDay, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); } } - _updateValidDate(); + updateValidDate(); if (addDate.isBefore(recurrenceStartDate)) { - addDate = DateTime(addDate.year, addDate.month + monthlyMonthGap, 1, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); - _updateValidDate(); + addDate = DateTime( + addDate.year, + addDate.month + monthlyMonthGap, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); + updateValidDate(); } while (tempCount < recCount || (endDate != null && (addDate.isBefore(endDate) || addDate == endDate))) { if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate)) { + if (_isRecurrenceInBetweenSpecificRange( + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate, + )) { recDateCollection.add(addDate); } @@ -797,9 +937,15 @@ class RecurrenceHelper { recDateCollection.add(addDate); } - addDate = DateTime(addDate.year, addDate.month + monthlyMonthGap, 1, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); - _updateValidDate(); + addDate = DateTime( + addDate.year, + addDate.month + monthlyMonthGap, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); + updateValidDate(); tempCount++; } } @@ -808,15 +954,23 @@ class RecurrenceHelper { } static List _getYearlyRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {Duration? recurrenceDuration, - DateTime? specificStartDate, - DateTime? specificEndDate}) { + String rRule, + DateTime recurrenceStartDate, { + Duration? recurrenceDuration, + DateTime? specificStartDate, + DateTime? specificEndDate, + }) { final List recDateCollection = []; if (specificEndDate != null) { - specificEndDate = DateTime(specificEndDate.year, specificEndDate.month, - specificEndDate.day, 23, 59, 59); + specificEndDate = DateTime( + specificEndDate.year, + specificEndDate.month, + specificEndDate.day, + 23, + 59, + 59, + ); } recurrenceDuration ??= Duration.zero; @@ -855,16 +1009,16 @@ class RecurrenceHelper { DateTime? endDate; if (rRule.contains('UNTIL')) { - endDate = DateTime.parse(untilValue); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + endDate = getUntilEndDate(untilValue); if (isSpecificDateRange) { final DateTime startTime = DateTime( - endDate.year, - endDate.month, - endDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + endDate.year, + endDate.month, + endDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final DateTime endTime = startTime.add(recurrenceDuration); /// Check the visible start date after of recurrence end date, if @@ -876,15 +1030,17 @@ class RecurrenceHelper { } } - final int yearlyYearGap = ruleArray.length > 4 && intervalCount.isNotEmpty - ? int.parse(intervalCount) - : 1; + final int yearlyYearGap = + ruleArray.length > 4 && intervalCount.isNotEmpty + ? int.parse(intervalCount) + : 1; /// NoEndDate specified rule returns empty collection issue fix. if (isSpecificDateRange && !rRule.contains('COUNT')) { - endDate = endDate == null || endDate.isAfter(specificEndDate) - ? specificEndDate - : endDate; + endDate = + endDate == null || endDate.isAfter(specificEndDate) + ? specificEndDate + : endDate; final int addedDateYear = addDate.year; final int viewStartDateYear = specificStartDate.year; @@ -905,31 +1061,40 @@ class RecurrenceHelper { int dayIndex = int.parse(byMonthDayCount); final int byMonthDay = dayIndex; if (byMonthDay == -1) { - dayIndex = AppointmentHelper.getMonthEndDate( - DateTime(addDate.year, monthIndex, 1)) - .day; + dayIndex = + AppointmentHelper.getMonthEndDate( + DateTime(addDate.year, monthIndex), + ).day; } if (monthIndex < 0 || monthIndex > 12) { return recDateCollection; } - final int daysInMonth = DateTimeHelper.getDateTimeValue( - addDays(DateTime(addDate.year, addDate.month + 1, 1), -1)) - .day; + final int daysInMonth = + DateTimeHelper.getDateTimeValue( + addDays(DateTime(addDate.year, addDate.month + 1), -1), + ).day; if (daysInMonth < dayIndex) { return recDateCollection; } - final DateTime specificDate = DateTime(addDate.year, monthIndex, dayIndex, - recurrenceStartHour, recurrenceStartMinute, recurrenceStartSecond); + final DateTime specificDate = DateTime( + addDate.year, + monthIndex, + dayIndex, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); if (specificDate.isBefore(recurrenceStartDate)) { addDate = DateTime( - specificDate.year + yearlyYearGap, - specificDate.month, - specificDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + specificDate.year + yearlyYearGap, + specificDate.month, + specificDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); } else { addDate = specificDate; } @@ -939,8 +1104,12 @@ class RecurrenceHelper { (endDate != null && (addDate.isBefore(endDate) || addDate == endDate))) { if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate)) { + if (_isRecurrenceInBetweenSpecificRange( + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate, + )) { recDateCollection.add(addDate); } @@ -951,19 +1120,21 @@ class RecurrenceHelper { recDateCollection.add(addDate); } - dayIndex = byMonthDay == -1 - ? AppointmentHelper.getMonthEndDate( - DateTime(addDate.year + yearlyYearGap, monthIndex, 1)) - .day - : addDate.day; + dayIndex = + byMonthDay == -1 + ? AppointmentHelper.getMonthEndDate( + DateTime(addDate.year + yearlyYearGap, monthIndex), + ).day + : addDate.day; addDate = DateTime( - addDate.year + yearlyYearGap, - addDate.month, - dayIndex, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + addDate.year + yearlyYearGap, + addDate.month, + dayIndex, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); tempCount++; } } else if (byDay == 'BYDAY') { @@ -972,23 +1143,25 @@ class RecurrenceHelper { final int bySetPosValue = int.parse(bySetPosCount); final int nthWeekDay = _getWeekDay(byDayValue) % DateTime.daysPerWeek; - void _updateValidNextDate() { + void updateValidNextDate() { while (true) { DateTime monthStart = DateTime( - addDate.year, - monthIndex, - 1, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + addDate.year, + monthIndex, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); final int monthStartWeekday = monthStart.weekday % DateTime.daysPerWeek; final DateTime weekStartDate = AppointmentHelper.addDaysWithTime( - monthStart, - -monthStartWeekday, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + monthStart, + -monthStartWeekday, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); int nthWeek = bySetPosValue; if (monthStartWeekday <= nthWeekDay) { nthWeek = bySetPosValue - 1; @@ -996,25 +1169,30 @@ class RecurrenceHelper { if (bySetPosValue.isNegative) { monthStart = _getRecurrenceDateForNegativeValue( - bySetPosValue, monthStart, nthWeekDay); + bySetPosValue, + monthStart, + nthWeekDay, + ); } else { monthStart = AppointmentHelper.addDaysWithTime( - weekStartDate, - (nthWeek * DateTime.daysPerWeek) + nthWeekDay, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + weekStartDate, + (nthWeek * DateTime.daysPerWeek) + nthWeekDay, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); } if (monthStart.month != monthIndex || monthStart.isBefore(recurrenceStartDate)) { addDate = DateTime( - monthStart.year + yearlyYearGap, - monthStart.month, - monthStart.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + monthStart.year + yearlyYearGap, + monthStart.month, + monthStart.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); continue; } @@ -1023,13 +1201,17 @@ class RecurrenceHelper { } } - _updateValidNextDate(); + updateValidNextDate(); while (tempCount < recCount || (endDate != null && (addDate.isBefore(endDate) || addDate == endDate))) { if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate)) { + if (_isRecurrenceInBetweenSpecificRange( + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate, + )) { recDateCollection.add(addDate); } @@ -1041,15 +1223,16 @@ class RecurrenceHelper { } addDate = DateTime( - addDate.year + yearlyYearGap, - addDate.month, - addDate.day, - recurrenceStartHour, - recurrenceStartMinute, - recurrenceStartSecond); + addDate.year + yearlyYearGap, + addDate.month, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond, + ); tempCount++; - _updateValidNextDate(); + updateValidNextDate(); } } @@ -1058,35 +1241,49 @@ class RecurrenceHelper { /// Returns the date time collection of recurring appointment. static List getRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {Duration? recurrenceDuration, - DateTime? specificStartDate, - DateTime? specificEndDate}) { + String rRule, + DateTime recurrenceStartDate, { + Duration? recurrenceDuration, + DateTime? specificStartDate, + DateTime? specificEndDate, + }) { /// Return empty collection when recurrence rule is empty. if (rRule.isEmpty) { return []; } if (rRule.contains('DAILY')) { - return _getDailyRecurrenceDateTimeCollection(rRule, recurrenceStartDate, - recurrenceDuration: recurrenceDuration, - specificStartDate: specificStartDate, - specificEndDate: specificEndDate); + return _getDailyRecurrenceDateTimeCollection( + rRule, + recurrenceStartDate, + recurrenceDuration: recurrenceDuration, + specificStartDate: specificStartDate, + specificEndDate: specificEndDate, + ); } else if (rRule.contains('WEEKLY')) { - return _getWeeklyRecurrenceDateTimeCollection(rRule, recurrenceStartDate, - recurrenceDuration: recurrenceDuration, - specificStartDate: specificStartDate, - specificEndDate: specificEndDate); + return _getWeeklyRecurrenceDateTimeCollection( + rRule, + recurrenceStartDate, + recurrenceDuration: recurrenceDuration, + specificStartDate: specificStartDate, + specificEndDate: specificEndDate, + ); } else if (rRule.contains('MONTHLY')) { - return _getMonthlyRecurrenceDateTimeCollection(rRule, recurrenceStartDate, - recurrenceDuration: recurrenceDuration, - specificStartDate: specificStartDate, - specificEndDate: specificEndDate); + return _getMonthlyRecurrenceDateTimeCollection( + rRule, + recurrenceStartDate, + recurrenceDuration: recurrenceDuration, + specificStartDate: specificStartDate, + specificEndDate: specificEndDate, + ); } else if (rRule.contains('YEARLY')) { - return _getYearlyRecurrenceDateTimeCollection(rRule, recurrenceStartDate, - recurrenceDuration: recurrenceDuration, - specificStartDate: specificStartDate, - specificEndDate: specificEndDate); + return _getYearlyRecurrenceDateTimeCollection( + rRule, + recurrenceStartDate, + recurrenceDuration: recurrenceDuration, + specificStartDate: specificStartDate, + specificEndDate: specificEndDate, + ); } return []; @@ -1095,8 +1292,9 @@ class RecurrenceHelper { /// Returns the recurrence properties based on the given recurrence rule and /// the recurrence start date. static RecurrenceProperties parseRRule(String rRule, DateTime recStartDate) { - final RecurrenceProperties recProp = - RecurrenceProperties(startDate: recStartDate); + final RecurrenceProperties recProp = RecurrenceProperties( + startDate: recStartDate, + ); if (rRule.isEmpty) { return recProp; @@ -1134,9 +1332,7 @@ class RecurrenceHelper { recurCount.isNotEmpty ? int.parse(recurCount) : 0; } else if (rRule.contains('UNTIL')) { recProp.recurrenceRange = RecurrenceRange.endDate; - final DateTime endDate = DateTime.parse(untilValue); - recProp.endDate = - DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + recProp.endDate = getUntilEndDate(untilValue); } else { recProp.recurrenceRange = RecurrenceRange.noEndDate; } @@ -1204,13 +1400,23 @@ class RecurrenceHelper { return recProp; } + /// Returns the until rule based on the end date value. + static String _getUntilRRule(DateTime endDate, String rRule) { + endDate = endDate.toUtc(); + final String dateString = DateFormat('yyyyMMdd').format(endDate); + final String timeString = DateFormat('HHmmss').format(endDate); + rRule = '$rRule;UNTIL=${'${dateString}T${timeString}Z'}'; + return rRule; + } + static String _getRRuleForDaily( - int recCount, - RecurrenceProperties recurrenceProperties, - DateTime startDate, - DateTime? endDate, - bool isValidRecurrence, - Duration diffTimeSpan) { + int recCount, + RecurrenceProperties recurrenceProperties, + DateTime startDate, + DateTime? endDate, + bool isValidRecurrence, + Duration diffTimeSpan, + ) { String rRule = ''; if ((recCount > 0 && recurrenceProperties.recurrenceRange == RecurrenceRange.count) || @@ -1231,8 +1437,7 @@ class RecurrenceHelper { rRule = '$rRule;COUNT=$recCount'; } else if (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = '$rRule;UNTIL=${format.format(endDate!)}'; + rRule = RecurrenceHelper._getUntilRRule(endDate!, rRule); } } @@ -1244,13 +1449,14 @@ class RecurrenceHelper { } static String _getRRuleForWeekly( - DateTime startDate, - DateTime? endDate, - int recCount, - RecurrenceProperties recurrenceProperties, - bool isValidRecurrence, - Duration diffTimeSpan, - DateTime prevDate) { + DateTime startDate, + DateTime? endDate, + int recCount, + RecurrenceProperties recurrenceProperties, + bool isValidRecurrence, + Duration diffTimeSpan, + DateTime prevDate, + ) { String rRule = ''; DateTime addDate = startDate; if ((recCount > 0 && @@ -1347,8 +1553,10 @@ class RecurrenceHelper { if (dayKey.isNotEmpty) { if (count != 0) { - final Duration tempTimeSpan = - AppointmentHelper.getDifference(prevDate, addDate); + final Duration tempTimeSpan = AppointmentHelper.getDifference( + prevDate, + addDate, + ); if (tempTimeSpan <= diffTimeSpan) { isValidRecurrence = false; } else { @@ -1371,9 +1579,10 @@ class RecurrenceHelper { } else { prevDate = addDate; count++; - byDay = byDay.isNotEmpty && byDay.substring(byDay.length - 1) == 'A' - ? '$byDay,$dayKey' - : byDay + dayKey; + byDay = + byDay.isNotEmpty && byDay.substring(byDay.length - 1) == 'A' + ? '$byDay,$dayKey' + : byDay + dayKey; dayCount++; } @@ -1430,16 +1639,23 @@ class RecurrenceHelper { dayKey = ''; } - addDate = addDate.weekday == DateTime.saturday - ? AppointmentHelper.addDaysWithTime( - addDate, - ((recurrenceProperties.interval - 1) * DateTime.daysPerWeek) + - 1, - startDate.hour, - startDate.minute, - startDate.second) - : AppointmentHelper.addDaysWithTime( - addDate, 1, startDate.hour, startDate.minute, startDate.second); + addDate = + addDate.weekday == DateTime.saturday + ? AppointmentHelper.addDaysWithTime( + addDate, + ((recurrenceProperties.interval - 1) * DateTime.daysPerWeek) + + 1, + startDate.hour, + startDate.minute, + startDate.second, + ) + : AppointmentHelper.addDaysWithTime( + addDate, + 1, + startDate.hour, + startDate.minute, + startDate.second, + ); i = i + 1; } @@ -1454,8 +1670,7 @@ class RecurrenceHelper { rRule = '$rRule;COUNT=$recCount'; } else if (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = '$rRule;UNTIL=${format.format(endDate!)}'; + rRule = RecurrenceHelper._getUntilRRule(endDate!, rRule); } } @@ -1486,11 +1701,15 @@ class RecurrenceHelper { rRule = '$rRule;BYMONTHDAY=${recurrenceProperties.dayOfMonth}'; } else { final DateTime firstDate = DateTimeHelper.getDateTimeValue( - addDays(DateTime.now(), -(DateTime.now().weekday - 1))); + 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))) + .map( + (int value) => DateFormat( + DateFormat.ABBR_WEEKDAY, + ).format(addDays(firstDate, value)), + ) .toList(); final String byDayString = dayNames[recurrenceProperties.dayOfWeek - 1]; @@ -1510,19 +1729,20 @@ class RecurrenceHelper { rRule = '$rRule;COUNT=$recCount'; } else if (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = '$rRule;UNTIL=${format.format(endDate!)}'; + rRule = RecurrenceHelper._getUntilRRule(endDate!, rRule); } if (AppointmentHelper.getDifference( - startDate, - DateTime( - startDate.year, - startDate.month + recurrenceProperties.interval, - startDate.day, - startDate.hour, - startDate.minute, - startDate.second)) < + startDate, + DateTime( + startDate.year, + startDate.month + recurrenceProperties.interval, + startDate.day, + startDate.hour, + startDate.minute, + startDate.second, + ), + ) < diffTimeSpan) { isValidRecurrence = false; } @@ -1536,12 +1756,13 @@ class RecurrenceHelper { } static String _getRRuleForYearly( - int recCount, - RecurrenceProperties recurrenceProperties, - DateTime startDate, - DateTime? endDate, - Duration diffTimeSpan, - bool isValidRecurrence) { + int recCount, + RecurrenceProperties recurrenceProperties, + DateTime startDate, + DateTime? endDate, + Duration diffTimeSpan, + bool isValidRecurrence, + ) { String rRule = ''; if ((recCount > 0 && recurrenceProperties.recurrenceRange == RecurrenceRange.count) || @@ -1556,11 +1777,15 @@ class RecurrenceHelper { '$rRule;BYMONTHDAY=${recurrenceProperties.dayOfMonth};BYMONTH=${recurrenceProperties.month}'; } else { final DateTime firstDate = DateTimeHelper.getDateTimeValue( - addDays(DateTime.now(), -(DateTime.now().weekday - 1))); + 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))) + .map( + (int value) => DateFormat( + DateFormat.ABBR_WEEKDAY, + ).format(addDays(firstDate, value)), + ) .toList(); final String byDayString = dayNames[recurrenceProperties.dayOfWeek - 1]; @@ -1580,19 +1805,20 @@ class RecurrenceHelper { rRule = '$rRule;COUNT=$recCount'; } else if (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = '$rRule;UNTIL=${format.format(endDate!)}'; + rRule = RecurrenceHelper._getUntilRRule(endDate!, rRule); } if (AppointmentHelper.getDifference( - startDate, - DateTime( - startDate.year + recurrenceProperties.interval, - startDate.month, - startDate.day, - startDate.hour, - startDate.minute, - startDate.second)) < + startDate, + DateTime( + startDate.year + recurrenceProperties.interval, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + startDate.second, + ), + ) < diffTimeSpan) { isValidRecurrence = false; } @@ -1607,16 +1833,22 @@ class RecurrenceHelper { /// 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) { + 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 startDate = + appStartTime.isAfter(recPropStartDate) + ? appStartTime + : recPropStartDate; final DateTime? endDate = recPropEndDate; - final Duration diffTimeSpan = - AppointmentHelper.getDifference(appStartTime, appEndTime); + final Duration diffTimeSpan = AppointmentHelper.getDifference( + appStartTime, + appEndTime, + ); int recCount = 0; final DateTime prevDate = DateTime.utc(1); const bool isValidRecurrence = true; @@ -1624,17 +1856,42 @@ class RecurrenceHelper { recCount = recurrenceProperties.recurrenceCount; switch (recurrenceProperties.recurrenceType) { case RecurrenceType.daily: - return _getRRuleForDaily(recCount, recurrenceProperties, startDate, - endDate, isValidRecurrence, diffTimeSpan); + return _getRRuleForDaily( + recCount, + recurrenceProperties, + startDate, + endDate, + isValidRecurrence, + diffTimeSpan, + ); case RecurrenceType.weekly: - return _getRRuleForWeekly(startDate, endDate, recCount, - recurrenceProperties, isValidRecurrence, diffTimeSpan, prevDate); + return _getRRuleForWeekly( + startDate, + endDate, + recCount, + recurrenceProperties, + isValidRecurrence, + diffTimeSpan, + prevDate, + ); case RecurrenceType.monthly: - return _getRRuleForMonthly(recCount, recurrenceProperties, startDate, - endDate, diffTimeSpan, isValidRecurrence); + return _getRRuleForMonthly( + recCount, + recurrenceProperties, + startDate, + endDate, + diffTimeSpan, + isValidRecurrence, + ); case RecurrenceType.yearly: - return _getRRuleForYearly(recCount, recurrenceProperties, startDate, - endDate, diffTimeSpan, isValidRecurrence); + return _getRRuleForYearly( + recCount, + recurrenceProperties, + startDate, + endDate, + diffTimeSpan, + isValidRecurrence, + ); } } @@ -1748,7 +2005,7 @@ class RecurrenceHelper { byMonthCount, weeklyByDay, byMonthDayPosition.toString(), - byDayPosition.toString() + byDayPosition.toString(), ]; } @@ -1768,11 +2025,15 @@ class RecurrenceHelper { static int _getWeekDay(String weekDay) { int index = 1; final DateTime firstDate = DateTimeHelper.getDateTimeValue( - addDays(DateTime.now(), -(DateTime.now().weekday - 1))); + 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))) + .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]; @@ -1812,15 +2073,37 @@ class RecurrenceHelper { return result; } + /// Returns the date time value of until end date based on given string. + static DateTime getUntilEndDate(String untilString) { + /// Checks the time value of T is provided in the until end date value. + if (untilString.contains('T')) { + /// Z specifies UTC timezone offset, Checks the time zone is provided in + /// the until end date value. + final DateTime endDate = + untilString.contains('Z') + ? DateTime.parse(untilString).toLocal() + : DateTime.parse(untilString); + return endDate; + } else { + DateTime endDate = DateTime.parse(untilString); + endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + return endDate; + } + } + /// Returns the recurrence start date for negative recurrence value. static DateTime _getRecurrenceDateForNegativeValue( - int bySetPosCount, DateTime date, int weekDay) { + int bySetPosCount, + DateTime date, + int weekDay, + ) { DateTime? lastDate; if (bySetPosCount == -1) { lastDate = AppointmentHelper.getMonthEndDate(date); } else if (bySetPosCount == -2) { - lastDate = AppointmentHelper.getMonthEndDate(date) - .subtract(const Duration(days: DateTime.daysPerWeek)); + lastDate = AppointmentHelper.getMonthEndDate( + date, + ).subtract(const Duration(days: DateTime.daysPerWeek)); } if (lastDate == null) { @@ -1829,9 +2112,16 @@ class RecurrenceHelper { } return _getLastWeekDay( - DateTime(lastDate.year, lastDate.month, lastDate.day, date.hour, - date.minute, date.second), - weekDay); + DateTime( + lastDate.year, + lastDate.month, + lastDate.day, + date.hour, + date.minute, + date.second, + ), + weekDay, + ); } /// Returns the last week date for the given weekday. @@ -1854,7 +2144,7 @@ class RecurrenceHelper { 'WE', 'TH', 'FR', - 'SA' + 'SA', ]; String weeklyDayString = ''; int count = 0; 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 d805255fc..c66dfac08 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,5 +1,4 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart' show IterableDiagnostics; @@ -73,29 +72,33 @@ class RecurrenceProperties with Diagnosticable { /// An object contains properties that hold data for the creation of /// [Appointment.recurrenceRule] for [Appointment] using the /// [SfCalendar.generateRRule] method. - RecurrenceProperties( - {this.recurrenceType = RecurrenceType.daily, - this.recurrenceCount = 0, - 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}) - : weekDays = weekDays ?? [], - assert(recurrenceCount >= 0), - assert(endDate == null || - CalendarViewHelper.isSameOrBeforeDateTime(endDate, startDate)), - assert(endDate == null || - CalendarViewHelper.isSameOrAfterDateTime(startDate, endDate)), - assert(interval >= 1), - assert(week >= -2 && week <= 5), - assert(dayOfMonth >= 1 && dayOfMonth <= 31), - assert(dayOfWeek >= 1 && dayOfWeek <= 7), - assert(month >= 1 && month <= 12); + RecurrenceProperties({ + this.recurrenceType = RecurrenceType.daily, + this.recurrenceCount = 0, + 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, + }) : weekDays = weekDays ?? [], + assert(recurrenceCount >= 0), + assert( + endDate == null || + CalendarViewHelper.isSameOrBeforeDateTime(endDate, startDate), + ), + assert( + endDate == null || + CalendarViewHelper.isSameOrAfterDateTime(startDate, endDate), + ), + assert(interval >= 1), + assert(week >= -2 && week <= 5), + assert(dayOfMonth >= 1 && dayOfMonth <= 31), + assert(dayOfWeek >= 1 && dayOfWeek <= 7), + assert(month >= 1 && month <= 12); /// Defines the recurrence type of an [Appointment]. /// @@ -690,7 +693,7 @@ class RecurrenceProperties with Diagnosticable { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -718,18 +721,19 @@ class RecurrenceProperties with Diagnosticable { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return hashValues( - recurrenceType, - recurrenceCount, - startDate, - endDate, - interval, - recurrenceRange, - hashList(weekDays), - week, - dayOfMonth, - dayOfWeek, - month); + return Object.hash( + recurrenceType, + recurrenceCount, + startDate, + endDate, + interval, + recurrenceRange, + Object.hashAll(weekDays), + week, + dayOfMonth, + dayOfWeek, + month, + ); } @override @@ -743,11 +747,16 @@ class RecurrenceProperties with Diagnosticable { 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')); + 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 8fc26127c..e6ae270d2 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 @@ -14,23 +14,27 @@ class AgendaViewLayout extends StatefulWidget { /// Constructor to create the agenda appointment layout that holds the agenda /// appointment views in calendar widget. const AgendaViewLayout( - this.monthViewSettings, - this.scheduleViewSettings, - this.selectedDate, - this.appointments, - this.isRTL, - this.locale, - this.localizations, - this.calendarTheme, - this.agendaViewNotifier, - this.appointmentTimeTextFormat, - this.timeLabelWidth, - this.textScaleFactor, - this.isMobilePlatform, - this.appointmentBuilder, - this.width, - this.height, - this.calendar); + this.monthViewSettings, + this.scheduleViewSettings, + this.selectedDate, + this.appointments, + this.isRTL, + this.locale, + this.localizations, + this.calendarTheme, + this.themeData, + this.agendaViewNotifier, + this.appointmentTimeTextFormat, + this.timeLabelWidth, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentBuilder, + this.width, + this.height, + this.placeholderTextStyle, + this.calendar, { + super.key, + }); /// Defines the month view customization details. final MonthViewSettings? monthViewSettings; @@ -53,6 +57,9 @@ class AgendaViewLayout extends StatefulWidget { /// Holds the theme data of the calendar widget. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data details. + final ThemeData themeData; + /// Holds the hovering details of the agenda view widget. final ValueNotifier agendaViewNotifier; @@ -84,6 +91,9 @@ class AgendaViewLayout extends StatefulWidget { /// Defines the calendar widget. final SfCalendar calendar; + /// Defines the text style of the no events and no selected date. + final TextStyle placeholderTextStyle; + @override // ignore: library_private_types_in_public_api _AgendaViewLayoutState createState() => _AgendaViewLayoutState(); @@ -131,35 +141,41 @@ class _AgendaViewLayoutState extends State { continue; } final CalendarAppointmentDetails details = CalendarAppointmentDetails( - widget.selectedDate!, - List.unmodifiable([ - CalendarViewHelper.getAppointmentDetail( - view.appointment!, widget.calendar.dataSource) - ]), - view.appointmentRect!.outerRect); + widget.selectedDate!, + List.unmodifiable([ + CalendarViewHelper.getAppointmentDetail( + view.appointment!, + widget.calendar.dataSource, + ), + ]), + view.appointmentRect!.outerRect, + ); final Widget child = widget.appointmentBuilder!(context, details); _children.add(RepaintBoundary(child: child)); } } return _AgendaViewRenderWidget( - widget.monthViewSettings, - widget.scheduleViewSettings, - widget.selectedDate, - widget.appointments, - widget.isRTL, - widget.locale, - widget.localizations, - widget.calendarTheme, - widget.agendaViewNotifier, - widget.appointmentTimeTextFormat, - widget.timeLabelWidth, - widget.textScaleFactor, - widget.isMobilePlatform, - _appointmentCollection, - widget.width, - widget.height, - widgets: _children); + widget.monthViewSettings, + widget.scheduleViewSettings, + widget.selectedDate, + widget.appointments, + widget.isRTL, + widget.locale, + widget.localizations, + widget.calendarTheme, + widget.themeData, + widget.agendaViewNotifier, + widget.appointmentTimeTextFormat, + widget.timeLabelWidth, + widget.textScaleFactor, + widget.isMobilePlatform, + _appointmentCollection, + widget.width, + widget.height, + widget.placeholderTextStyle, + widgets: _children, + ); } void _updateAppointmentDetails() { @@ -168,7 +184,9 @@ class _AgendaViewLayoutState extends State { final double totalAgendaViewWidth = widget.width + widget.timeLabelWidth; final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( - totalAgendaViewWidth, widget.isMobilePlatform); + totalAgendaViewWidth, + widget.isMobilePlatform, + ); AppointmentHelper.resetAppointmentView(_appointmentCollection); _children.clear(); if (widget.selectedDate == null || @@ -181,36 +199,52 @@ class _AgendaViewLayoutState extends State { widget.scheduleViewSettings != null && !useMobilePlatformUI; widget.appointments!.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); widget.appointments!.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, + app2.isAllDay, + ), + ); widget.appointments!.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, + app2.isSpanned, + ), + ); final double agendaItemHeight = CalendarViewHelper.getScheduleAppointmentHeight( - widget.monthViewSettings, widget.scheduleViewSettings); + widget.monthViewSettings, + widget.scheduleViewSettings, + ); final double agendaAllDayItemHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - widget.monthViewSettings, widget.scheduleViewSettings); + 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.isSpanned; final double appointmentHeight = (appointment.isAllDay || isSpanned) && !isLargerScheduleUI ? agendaAllDayItemHeight : agendaItemHeight; final Rect rect = Rect.fromLTWH( - padding, yPosition, widget.width - (2 * padding), appointmentHeight); + padding, + yPosition, + widget.width - (2 * padding), + appointmentHeight, + ); final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 5 ? 5 : (appointmentHeight * 0.1)); + (appointmentHeight * 0.1) > 5 ? 5 : (appointmentHeight * 0.1), + ); yPosition += appointmentHeight + padding; AppointmentView? appointmentRenderer; for (int i = 0; i < _appointmentCollection.length; i++) { @@ -230,32 +264,36 @@ class _AgendaViewLayoutState extends State { appointmentRenderer.canReuse = false; appointmentRenderer.appointment = appointment; - appointmentRenderer.appointmentRect = - RRect.fromRectAndRadius(rect, cornerRadius); + appointmentRenderer.appointmentRect = RRect.fromRectAndRadius( + rect, + cornerRadius, + ); } } } class _AgendaViewRenderWidget extends MultiChildRenderObjectWidget { - _AgendaViewRenderWidget( - this.monthViewSettings, - this.scheduleViewSettings, - this.selectedDate, - this.appointments, - this.isRTL, - this.locale, - this.localizations, - this.calendarTheme, - this.agendaViewNotifier, - this.appointmentTimeTextFormat, - this.timeLabelWidth, - this.textScaleFactor, - this.isMobilePlatform, - this.appointmentCollection, - this.width, - this.height, - {List widgets = const []}) - : super(children: widgets); + const _AgendaViewRenderWidget( + this.monthViewSettings, + this.scheduleViewSettings, + this.selectedDate, + this.appointments, + this.isRTL, + this.locale, + this.localizations, + this.calendarTheme, + this.themeData, + this.agendaViewNotifier, + this.appointmentTimeTextFormat, + this.timeLabelWidth, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentCollection, + this.width, + this.height, + this.placeholderTextStyle, { + List widgets = const [], + }) : super(children: widgets); final MonthViewSettings? monthViewSettings; final ScheduleViewSettings? scheduleViewSettings; @@ -264,6 +302,7 @@ class _AgendaViewRenderWidget extends MultiChildRenderObjectWidget { final bool isRTL; final String locale; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final ValueNotifier agendaViewNotifier; final SfLocalizations localizations; final double timeLabelWidth; @@ -273,31 +312,37 @@ class _AgendaViewRenderWidget extends MultiChildRenderObjectWidget { final List appointmentCollection; final double width; final double height; + final TextStyle placeholderTextStyle; @override _AgendaViewRenderObject createRenderObject(BuildContext context) { return _AgendaViewRenderObject( - monthViewSettings, - scheduleViewSettings, - selectedDate, - appointments, - isRTL, - locale, - localizations, - calendarTheme, - agendaViewNotifier, - appointmentTimeTextFormat, - timeLabelWidth, - textScaleFactor, - isMobilePlatform, - appointmentCollection, - width, - height); + monthViewSettings, + scheduleViewSettings, + selectedDate, + appointments, + isRTL, + locale, + localizations, + calendarTheme, + themeData, + agendaViewNotifier, + appointmentTimeTextFormat, + timeLabelWidth, + textScaleFactor, + isMobilePlatform, + appointmentCollection, + width, + height, + placeholderTextStyle, + ); } @override void updateRenderObject( - BuildContext context, _AgendaViewRenderObject renderObject) { + BuildContext context, + _AgendaViewRenderObject renderObject, + ) { renderObject ..monthViewSettings = monthViewSettings ..scheduleViewSettings = scheduleViewSettings @@ -307,34 +352,39 @@ class _AgendaViewRenderWidget extends MultiChildRenderObjectWidget { ..locale = locale ..localizations = localizations ..calendarTheme = calendarTheme + ..themeData = themeData ..agendaViewNotifier = agendaViewNotifier ..appointmentTimeTextFormat = appointmentTimeTextFormat ..timeLabelWidth = timeLabelWidth ..textScaleFactor = textScaleFactor ..appointmentCollection = appointmentCollection ..width = width - ..height = height; + ..height = height + ..placeholderTextStyle = placeholderTextStyle; } } class _AgendaViewRenderObject extends CustomCalendarRenderObject { _AgendaViewRenderObject( - this._monthViewSettings, - this._scheduleViewSettings, - this._selectedDate, - this._appointments, - this._isRTL, - this._locale, - this._localizations, - this._calendarTheme, - this._agendaViewNotifier, - this._appointmentTimeTextFormat, - this._timeLabelWidth, - this._textScaleFactor, - this.isMobilePlatform, - this._appointmentCollection, - this._width, - this._height); + this._monthViewSettings, + this._scheduleViewSettings, + this._selectedDate, + this._appointments, + this._isRTL, + this._locale, + this._localizations, + this._calendarTheme, + this._themeData, + this._agendaViewNotifier, + this._appointmentTimeTextFormat, + this._timeLabelWidth, + this._textScaleFactor, + this.isMobilePlatform, + this._appointmentCollection, + this._width, + this._height, + this._placeholderTextStyle, + ); final bool isMobilePlatform; @@ -351,6 +401,19 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { markNeedsLayout(); } + TextStyle _placeholderTextStyle; + + TextStyle get placeholderTextStyle => _placeholderTextStyle; + + set placeholderTextStyle(TextStyle value) { + if (_placeholderTextStyle == value) { + return; + } + + _placeholderTextStyle = value; + markNeedsPaint(); + } + double _width; double get width => _width; @@ -560,6 +623,18 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { markNeedsPaint(); } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + } + ValueNotifier _agendaViewNotifier; ValueNotifier get agendaViewNotifier => @@ -614,8 +689,10 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { continue; } - final Offset offset = Offset(appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top); + final Offset offset = Offset( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + ); final bool isHit = result.addWithPaintOffset( offset: offset, position: position, @@ -636,8 +713,10 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView appointmentView = appointmentCollection[i]; @@ -647,15 +726,20 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { continue; } - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: appointmentView.appointmentRect!.height, maxHeight: appointmentView.appointmentRect!.height, minWidth: appointmentView.appointmentRect!.width, - maxWidth: appointmentView.appointmentRect!.width)); + maxWidth: appointmentView.appointmentRect!.width, + ), + ); final CalendarParentData childParentData = child.parentData! as CalendarParentData; - childParentData.offset = Offset(appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top); + childParentData.offset = Offset( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + ); child = childAfter(child); } } @@ -666,7 +750,9 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { final bool isNeedDefaultPaint = childCount == 0; final double totalAgendaViewWidth = size.width + timeLabelWidth; final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( - totalAgendaViewWidth, isMobilePlatform); + totalAgendaViewWidth, + isMobilePlatform, + ); final bool isLargerScheduleUI = scheduleViewSettings != null && !useMobilePlatformUI; if (isNeedDefaultPaint) { @@ -686,7 +772,12 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { if (agendaViewNotifier.value != null && isSameDate(agendaViewNotifier.value!.hoveringDate, selectedDate)) { _addMouseHovering( - context.canvas, size, rect, isLargerScheduleUI, padding); + context.canvas, + size, + rect, + isLargerScheduleUI, + padding, + ); } child = childAfter(child); @@ -711,9 +802,10 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { 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 SemanticsNode newChild = + _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); @@ -754,45 +846,70 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; + + final RenderBox? child = firstChild; + if (child != null) { + return semanticsBuilder; + } + if (selectedDate == null) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Offset.zero & size, - properties: const SemanticsProperties( - label: 'No selected date', - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Offset.zero & size, + properties: const SemanticsProperties( + label: 'No selected date', + textDirection: TextDirection.ltr, + ), ), - )); + ); } else if (selectedDate != null && (appointments == null || appointments!.isEmpty)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Offset.zero & size, - properties: SemanticsProperties( - label: - // ignore: lines_longer_than_80_chars - '${DateFormat('EEEEE').format(selectedDate!)}${DateFormat('dd MMMM yyyy').format(selectedDate!)}, No events', - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Offset.zero & size, + properties: SemanticsProperties( + label: + // ignore: lines_longer_than_80_chars + '${DateFormat('EEEEE').format(selectedDate!)}${DateFormat('dd MMMM yyyy').format(selectedDate!)}, No events', + textDirection: TextDirection.ltr, + ), ), - )); + ); } else if (selectedDate != null) { for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null) { continue; } - semanticsBuilder.add(CustomPainterSemantics( - rect: appointmentView.appointmentRect!.outerRect, - properties: SemanticsProperties( - label: CalendarViewHelper.getAppointmentSemanticsText( - appointmentView.appointment!), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: appointmentView.appointmentRect!.outerRect, + properties: SemanticsProperties( + label: CalendarViewHelper.getAppointmentSemanticsText( + appointmentView.appointment!, + ), + textDirection: TextDirection.ltr, + ), ), - )); + ); } } return semanticsBuilder; } + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderBox? child = firstChild; + if (child == null) { + return; + } + while (child != null) { + visitor(child); + child = childAfter(child); + } + } + void _drawDefaultUI(Canvas canvas, bool isLargerScheduleUI, Offset offset) { _rectPainter.isAntiAlias = true; double yPosition = offset.dy + 5; @@ -804,18 +921,30 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { return; } - final TextStyle appointmentTextStyle = monthViewSettings != null - ? monthViewSettings!.agendaStyle.appointmentTextStyle ?? - const TextStyle( - color: Colors.white, fontSize: 13, fontFamily: 'Roboto') - : scheduleViewSettings!.appointmentTextStyle ?? - TextStyle( - color: isLargerScheduleUI && - calendarTheme.brightness == Brightness.light - ? Colors.black87 - : Colors.white, - fontSize: 13, - fontFamily: 'Roboto'); + final TextStyle appointmentTextStyle = + monthViewSettings != null + ? themeData.textTheme.bodyMedium! + .copyWith(color: Colors.white, fontSize: 13) + .merge(monthViewSettings!.agendaStyle.appointmentTextStyle) + : themeData.textTheme.bodyMedium! + .copyWith( + color: + isLargerScheduleUI && + themeData.brightness == Brightness.light + ? Colors.black87 + : Colors.white, + fontSize: 13, + ) + .merge(scheduleViewSettings!.appointmentTextStyle); + + final List appointmentStringFormats = + appointmentTimeTextFormat == null + ? [] + : CalendarViewHelper.getListFromString(appointmentTimeTextFormat!); + final List sameDateAppointmentStringFormats = + CalendarViewHelper.getListFromString('hh:mm a'); + final List diffDateAppointmentStringFormats = + CalendarViewHelper.getListFromString('MMM dd, hh:mm a'); //// Draw Appointments for (int i = 0; i < appointmentCollection.length; i++) { @@ -828,7 +957,7 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { _rectPainter.color = appointment.color; final bool isSpanned = appointment.actualEndTime.day != appointment.actualStartTime.day || - appointment.isSpanned; + appointment.isSpanned; final double appointmentHeight = appointmentView.appointmentRect!.height; final RRect rect = appointmentView.appointmentRect!.shift(offset); xPosition = rect.left; @@ -839,109 +968,147 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { canvas.drawRRect(rect, _rectPainter); } - final TextSpan span = - TextSpan(text: appointment.subject, style: appointmentTextStyle); + final TextSpan span = TextSpan( + text: appointment.subject, + style: appointmentTextStyle, + ); _updateTextPainterProperties(span); double timeWidth = isLargerScheduleUI ? (size.width - (2 * padding)) * 0.3 : 0; timeWidth = timeWidth > 200 ? 200 : timeWidth; xPosition += timeWidth; - final bool isRecurrenceAppointment = appointment.recurrenceRule != null && + final bool isRecurrenceAppointment = + appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty; - final double textSize = - _getTextSize(rect, appointmentTextStyle, isMobilePlatform); + final double textSize = _getTextSize( + rect, + appointmentTextStyle, + isMobilePlatform, + ); double topPadding = 0; + double spanIconWidth = 0; /// Draw web schedule view. if (isLargerScheduleUI) { topPadding = _addScheduleViewForWeb( + canvas, + size, + padding, + xPosition, + yPosition, + timeWidth, + appointmentHeight, + isSpanned, + isRecurrenceAppointment, + textSize, + appointment, + appointmentTextStyle, + appointmentStringFormats, + sameDateAppointmentStringFormats, + offset, + ); + if (isSpanned) { + final TextSpan icon = AppointmentHelper.getSpanIcon( + appointmentTextStyle.color!, + isMobilePlatform ? textSize : textSize / 1.5, + !isRTL, + ); + spanIconWidth = _drawIcon( canvas, size, + textSize, + rect, padding, - xPosition, - yPosition, - timeWidth, + isLargerScheduleUI, + rect.tlRadius, + icon, appointmentHeight, - isSpanned || isRecurrenceAppointment, - textSize, - appointment, - appointmentTextStyle, - offset); - if (isSpanned) { - final TextSpan icon = AppointmentHelper.getSpanIcon( - appointmentTextStyle.color!, - isMobilePlatform ? textSize : textSize / 1.5, - !isRTL); - _drawIcon(canvas, size, textSize, rect, padding, isLargerScheduleUI, - rect.tlRadius, icon, appointmentHeight, topPadding, true, false); + topPadding, + true, + false, + spanIconWidth, + ); } } else { /// Draws spanning appointment UI for schedule view. if (isSpanned) { - _drawSpanningAppointmentForScheduleView( - canvas, - size, - xPosition, - yPosition, - padding, - appointment, - appointmentTextStyle, - appointmentHeight, - rect, - isMobilePlatform, - isLargerScheduleUI, - rect.tlRadius); + final List iconPositions = + _drawSpanningAppointmentForScheduleView( + canvas, + size, + xPosition, + yPosition, + padding, + appointment, + appointmentTextStyle, + appointmentHeight, + rect, + isMobilePlatform, + isLargerScheduleUI, + rect.tlRadius, + ); + spanIconWidth = iconPositions[0]; + topPadding = iconPositions[1]; } //// Draw Appointments except All day appointment else if (!appointment.isAllDay) { topPadding = _drawNormalAppointmentUI( - canvas, - size, - xPosition, - yPosition, - padding, - timeWidth, - isRecurrenceAppointment, - textSize, - appointment, - appointmentHeight, - appointmentTextStyle); + canvas, + size, + xPosition, + yPosition, + padding, + timeWidth, + isRecurrenceAppointment, + textSize, + appointment, + appointmentHeight, + appointmentTextStyle, + appointmentStringFormats, + sameDateAppointmentStringFormats, + diffDateAppointmentStringFormats, + ); } else { //// Draw All day appointment - _updatePainterLinesCount(appointmentHeight, - isAllDay: true, isSpanned: false); + _updatePainterLinesCount(appointmentHeight, isAllDay: true); final double iconSize = isRecurrenceAppointment ? textSize + 10 : 0; - _textPainter.layout( - minWidth: 0, maxWidth: size.width - 10 - padding - iconSize); + _textPainter.layout(maxWidth: size.width - 10 - padding - iconSize); if (isRTL) { xPosition = size.width - _textPainter.width - (padding * 3); } topPadding = (appointmentHeight - _textPainter.height) / 2; _textPainter.paint( - canvas, Offset(xPosition + 5, yPosition + topPadding)); + canvas, + Offset(xPosition + 5, yPosition + topPadding), + ); } } if (isRecurrenceAppointment || appointment.recurrenceId != null) { final TextSpan icon = AppointmentHelper.getRecurrenceIcon( - appointmentTextStyle.color!, textSize, isRecurrenceAppointment); + appointmentTextStyle.color!, + textSize, + isRecurrenceAppointment, + ); _drawIcon( - canvas, - size, - textSize, - rect, - padding, - isLargerScheduleUI, - rect.tlRadius, - icon, - appointmentHeight, - topPadding, - false, - appointment.isAllDay); + canvas, + size, + textSize, + rect, + padding, + isLargerScheduleUI, + rect.tlRadius, + icon, + appointmentHeight, + topPadding, + false, + appointment.isAllDay, + spanIconWidth, + ); } if (agendaViewNotifier.value != null && @@ -952,14 +1119,18 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { } double _getTextSize( - RRect rect, TextStyle appointmentTextStyle, bool isMobilePlatform) { + RRect rect, + TextStyle appointmentTextStyle, + bool isMobilePlatform, + ) { // The default font size if none is specified. // The value taken from framework, for text style when there is no font // size given they have used 14 as the default font size. const double defaultFontSize = 14; - final double textSize = isMobilePlatform - ? appointmentTextStyle.fontSize ?? defaultFontSize - : appointmentTextStyle.fontSize != null + final double textSize = + isMobilePlatform + ? appointmentTextStyle.fontSize ?? defaultFontSize + : appointmentTextStyle.fontSize != null ? appointmentTextStyle.fontSize! * 1.5 : defaultFontSize * 1.5; if (rect.width < textSize || rect.height < textSize) { @@ -969,39 +1140,52 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { return textSize; } - void _drawIcon( - Canvas canvas, - Size size, - double textSize, - RRect rect, - double padding, - bool isLargerScheduleUI, - Radius cornerRadius, - TextSpan icon, - double appointmentHeight, - double yPosition, - bool isSpan, - bool isAllDay) { + double _drawIcon( + Canvas canvas, + Size size, + double textSize, + RRect rect, + double padding, + bool isLargerScheduleUI, + Radius cornerRadius, + TextSpan icon, + double appointmentHeight, + double yPosition, + bool isSpan, + bool isAllDay, + double spanIconWidth, + ) { _textPainter.text = icon; - _textPainter.textScaleFactor = textScaleFactor; - _textPainter.layout( - minWidth: 0, maxWidth: size.width - (2 * padding) - padding); + _textPainter.textScaler = TextScaler.linear(textScaleFactor); + _textPainter.layout(maxWidth: size.width - (2 * padding) - padding); final double iconSize = textSize + 8; if (!isLargerScheduleUI) { if (isRTL) { canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - rect.left, rect.top, rect.left + iconSize, rect.bottom), - cornerRadius), - _rectPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + rect.left + spanIconWidth, + rect.top, + rect.left + spanIconWidth + iconSize, + rect.bottom, + ), + cornerRadius, + ), + _rectPainter, + ); } else { canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - rect.right - iconSize, rect.top, rect.right, rect.bottom), - cornerRadius), - _rectPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + rect.right - spanIconWidth - iconSize, + rect.top, + rect.right - spanIconWidth, + rect.bottom, + ), + cornerRadius, + ), + _rectPainter, + ); } } @@ -1012,7 +1196,8 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { /// 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 - + iconStartPosition = + (_textPainter.height - (icon.style!.fontSize! * textScaleFactor) / 2) / 2; } @@ -1021,17 +1206,26 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { // agenda view if (isRTL) { _textPainter.paint( - canvas, Offset(8, rect.top + yPosition - iconStartPosition)); + canvas, + Offset(8 + spanIconWidth, rect.top + yPosition - iconStartPosition), + ); } else { _textPainter.paint( - canvas, - Offset(rect.right - _textPainter.width - 8, - rect.top + yPosition - iconStartPosition)); + canvas, + Offset( + rect.right - _textPainter.width - 8 - spanIconWidth, + rect.top + yPosition - iconStartPosition, + ), + ); } + return _textPainter.width + 8; } - double _updatePainterLinesCount(double appointmentHeight, - {bool isSpanned = false, bool isAllDay = false}) { + double _updatePainterLinesCount( + double appointmentHeight, { + bool isSpanned = false, + bool isAllDay = false, + }) { final double lineHeight = _textPainter.preferredLineHeight; /// Top and bottom padding 5 @@ -1047,24 +1241,27 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { } double _drawNormalAppointmentUI( - Canvas canvas, - Size size, - double xPosition, - double yPosition, - double padding, - double timeWidth, - bool isRecurrence, - double recurrenceTextSize, - CalendarAppointment appointment, - double appointmentHeight, - TextStyle appointmentTextStyle) { - _textPainter.textScaleFactor = textScaleFactor; - final double lineHeight = _updatePainterLinesCount(appointmentHeight, - isAllDay: false, isSpanned: false); + Canvas canvas, + Size size, + double xPosition, + double yPosition, + double padding, + double timeWidth, + bool isRecurrence, + double recurrenceTextSize, + CalendarAppointment appointment, + double appointmentHeight, + TextStyle appointmentTextStyle, + List appointmentFormatString, + List sameDateAppointmentFormatString, + List diffDateAppointmentFormatString, + ) { + _textPainter.textScaler = TextScaler.linear(textScaleFactor); + final double lineHeight = _updatePainterLinesCount(appointmentHeight); final double iconSize = isRecurrence ? recurrenceTextSize + 10 : 0; _textPainter.layout( - minWidth: 0, - maxWidth: size.width - (2 * padding) - xPosition - iconSize); + maxWidth: size.width - (2 * padding) - xPosition - iconSize, + ); final double subjectHeight = _textPainter.height; final double topPadding = (appointmentHeight - (subjectHeight + lineHeight)) / 2; @@ -1073,95 +1270,144 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { } _textPainter.paint( - canvas, Offset(xPosition + padding, yPosition + topPadding)); + canvas, + Offset(xPosition + padding, yPosition + topPadding), + ); - final String format = appointmentTimeTextFormat ?? - (isSameDate(appointment.actualStartTime, appointment.actualEndTime) - ? 'hh:mm a' - : 'MMM dd, hh:mm a'); + final List format = + appointmentFormatString.isEmpty + ? (isSameDate( + appointment.actualStartTime, + appointment.actualEndTime, + ) + ? sameDateAppointmentFormatString + : diffDateAppointmentFormatString) + : appointmentFormatString; + final String startDateText = CalendarViewHelper.getLocalizedString( + appointment.actualStartTime, + format, + locale, + ); + final String endDateText = CalendarViewHelper.getLocalizedString( + appointment.actualEndTime, + format, + locale, + ); final TextSpan span = TextSpan( - text: - // ignore: lines_longer_than_80_chars - '${DateFormat(format, locale).format(appointment.actualStartTime)} - ${DateFormat(format, locale).format(appointment.actualEndTime)}', - style: appointmentTextStyle); + text: '$startDateText - $endDateText', + style: appointmentTextStyle, + ); _textPainter.text = span; _textPainter.maxLines = 1; _textPainter.layout( - minWidth: 0, maxWidth: size.width - (2 * padding) - padding - iconSize); + maxWidth: size.width - (2 * padding) - padding - iconSize, + ); if (isRTL) { xPosition = size.width - _textPainter.width - (3 * padding); } - _textPainter.paint(canvas, - Offset(xPosition + padding, yPosition + topPadding + subjectHeight)); + _textPainter.paint( + canvas, + Offset(xPosition + padding, yPosition + topPadding + subjectHeight), + ); return topPadding; } - double _drawSpanningAppointmentForScheduleView( - Canvas canvas, - Size size, - double xPosition, - double yPosition, - double padding, - CalendarAppointment appointment, - TextStyle appointmentTextStyle, - double appointmentHeight, - RRect rect, - bool isMobilePlatform, - bool isLargerScheduleUI, - Radius cornerRadius) { + List _drawSpanningAppointmentForScheduleView( + Canvas canvas, + Size size, + double xPosition, + double yPosition, + double padding, + CalendarAppointment appointment, + TextStyle appointmentTextStyle, + double appointmentHeight, + RRect rect, + bool isMobilePlatform, + bool isLargerScheduleUI, + Radius cornerRadius, + ) { final TextSpan span = TextSpan( - text: AppointmentHelper.getSpanAppointmentText( - appointment, selectedDate!, localizations), - style: appointmentTextStyle); + text: AppointmentHelper.getSpanAppointmentText( + appointment, + selectedDate!, + localizations, + ), + style: appointmentTextStyle, + ); _updateTextPainterProperties(span); - _updatePainterLinesCount(appointmentHeight, - isAllDay: false, isSpanned: true); + _updatePainterLinesCount(appointmentHeight, isSpanned: true); final bool isNeedSpanIcon = !isSameDate(appointment.exactEndTime, selectedDate); - final double textSize = - _getTextSize(rect, appointmentTextStyle, isMobilePlatform); + final double textSize = _getTextSize( + rect, + appointmentTextStyle, + isMobilePlatform, + ); /// Icon padding 8 and 2 additional padding final double iconSize = isNeedSpanIcon ? textSize + 10 : 0; double maxTextWidth = size.width - 10 - padding - iconSize; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); if (isRTL) { xPosition = size.width - _textPainter.width - (padding * 3); } final double topPadding = (appointmentHeight - _textPainter.height) / 2; _textPainter.paint( - canvas, Offset(xPosition + padding, yPosition + topPadding)); + canvas, + Offset(xPosition + padding, yPosition + topPadding), + ); if (!isNeedSpanIcon) { - return topPadding; + return [0, topPadding]; } final TextSpan icon = AppointmentHelper.getSpanIcon( - appointmentTextStyle.color!, - isMobilePlatform ? textSize : textSize / 1.5, - !isRTL); - _drawIcon(canvas, size, textSize, rect, padding, isLargerScheduleUI, - cornerRadius, icon, appointmentHeight, topPadding, true, false); - return topPadding; + appointmentTextStyle.color!, + isMobilePlatform ? textSize : textSize / 1.5, + !isRTL, + ); + return [ + _drawIcon( + canvas, + size, + textSize, + rect, + padding, + isLargerScheduleUI, + cornerRadius, + icon, + appointmentHeight, + topPadding, + true, + false, + 0, + ), + topPadding, + ]; } - void _drawDefaultView(Canvas canvas, Size size, double xPosition, - double yPosition, double padding) { + void _drawDefaultView( + Canvas canvas, + Size size, + double xPosition, + double yPosition, + double padding, + ) { final TextSpan span = TextSpan( - text: selectedDate == null - ? localizations.noSelectedDateCalendarLabel - : localizations.noEventsCalendarLabel, - style: const TextStyle( - color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), + text: + selectedDate == null + ? localizations.noSelectedDateCalendarLabel + : localizations.noEventsCalendarLabel, + style: themeData.textTheme.bodyMedium!.merge(placeholderTextStyle), ); _updateTextPainterProperties(span); - _textPainter.layout(minWidth: 0, maxWidth: size.width - 10); + _textPainter.layout(maxWidth: size.width - 10); if (isRTL) { xPosition = size.width - _textPainter.width; } @@ -1171,50 +1417,63 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { void _updateTextPainterProperties(TextSpan span) { _textPainter.text = span; _textPainter.maxLines = 1; - _textPainter.textDirection = TextDirection.ltr; + _textPainter.textDirection = + CalendarViewHelper.getTextDirectionBasedOnLocale(locale); _textPainter.textAlign = TextAlign.left; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); } double _addScheduleViewForWeb( - Canvas canvas, - Size size, - double padding, - double xPosition, - double yPosition, - double timeWidth, - double appointmentHeight, - bool isNeedIcon, - double textSize, - CalendarAppointment appointment, - TextStyle appointmentTextStyle, - Offset offset) { - _textPainter.textScaleFactor = textScaleFactor; + Canvas canvas, + Size size, + double padding, + double xPosition, + double yPosition, + double timeWidth, + double appointmentHeight, + bool isNeedSpanIcon, + bool isNeedRecurrenceIcon, + double textSize, + CalendarAppointment appointment, + TextStyle appointmentTextStyle, + List appointmentFormatString, + List sameDateAppointmentFormatString, + Offset offset, + ) { + _textPainter.textScaler = TextScaler.linear(textScaleFactor); final double centerYPosition = appointmentHeight / 2; final double circleRadius = centerYPosition > padding ? padding : centerYPosition - 2; final double circleStartPosition = offset.dx + (3 * circleRadius); canvas.drawCircle( - Offset(isRTL ? size.width - circleStartPosition : circleStartPosition, - yPosition + centerYPosition), - circleRadius, - _rectPainter); + Offset( + isRTL ? size.width - circleStartPosition : circleStartPosition, + yPosition + centerYPosition, + ), + circleRadius, + _rectPainter, + ); final double circleWidth = 5 * circleRadius; xPosition += circleWidth; - _updatePainterLinesCount(appointmentHeight, - isAllDay: true, isSpanned: true); + _updatePainterLinesCount( + appointmentHeight, + isAllDay: true, + isSpanned: true, + ); /// Icon padding 8 and 2 additional padding - final double iconSize = isNeedIcon ? textSize + 10 : 0; + double iconSize = isNeedSpanIcon ? textSize + 10 : 0; + iconSize += isNeedRecurrenceIcon ? textSize + 10 : 0; _textPainter.layout( - minWidth: 0, - maxWidth: size.width - (2 * padding) - xPosition - iconSize); + maxWidth: offset.dx + size.width - (2 * padding) - xPosition - iconSize, + ); if (isRTL) { - xPosition = size.width - + xPosition = + size.width - _textPainter.width - (3 * padding) - timeWidth - @@ -1223,61 +1482,90 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { final double topPadding = (appointmentHeight - _textPainter.height) / 2; _textPainter.paint( - canvas, Offset(xPosition + padding, yPosition + topPadding)); - final DateFormat format = - DateFormat(appointmentTimeTextFormat ?? 'hh:mm a', locale); + canvas, + Offset(xPosition + padding, yPosition + topPadding), + ); + final List format = + appointmentFormatString.isEmpty + ? sameDateAppointmentFormatString + : appointmentFormatString; + final String startDateText = CalendarViewHelper.getLocalizedString( + appointment.actualStartTime, + format, + locale, + ); + final String endDateText = CalendarViewHelper.getLocalizedString( + appointment.actualEndTime, + format, + locale, + ); final TextSpan span = TextSpan( - text: appointment.isAllDay || appointment.isSpanned - ? _localizations.allDayLabel - // ignore: lines_longer_than_80_chars - : '${format.format(appointment.actualStartTime)} - ${format.format(appointment.actualEndTime)}', - style: appointmentTextStyle); + text: + appointment.isAllDay || appointment.isSpanned + ? _localizations.allDayLabel + : '$startDateText - $endDateText', + style: appointmentTextStyle, + ); _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: timeWidth - padding); + _textPainter.layout(maxWidth: timeWidth - padding); xPosition = offset.dx + padding + circleWidth; if (isRTL) { xPosition = size.width - _textPainter.width - (3 * padding) - circleWidth; } _textPainter.paint( - canvas, - Offset(xPosition + padding, - yPosition + ((appointmentHeight - _textPainter.height) / 2))); + canvas, + Offset( + xPosition + padding, + yPosition + ((appointmentHeight - _textPainter.height) / 2), + ), + ); return topPadding; } - void _addMouseHovering(Canvas canvas, Size size, RRect rect, - bool isLargerScheduleUI, double padding) { + void _addMouseHovering( + Canvas canvas, + Size size, + RRect rect, + bool isLargerScheduleUI, + double padding, + ) { 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); + _rectPainter.color = Colors.grey.withValues(alpha: 0.1); const double viewPadding = 2; canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH( - rect.left - padding, - rect.top + viewPadding, - size.width - (isRTL ? viewPadding : padding), - rect.height - (2 * viewPadding)), - const Radius.circular(4)), - _rectPainter); + RRect.fromRectAndRadius( + Rect.fromLTWH( + rect.left - padding, + rect.top + viewPadding, + size.width - (isRTL ? viewPadding : padding), + rect.height - (2 * viewPadding), + ), + const Radius.circular(4), + ), + _rectPainter, + ); } else { - _rectPainter.color = - calendarTheme.selectionBorderColor!.withOpacity(0.4); + _rectPainter.color = calendarTheme.selectionBorderColor!.withValues( + alpha: 0.4, + ); _rectPainter.style = PaintingStyle.stroke; _rectPainter.strokeWidth = 2; if (childCount == 0) { final Radius cornerRadius = Radius.circular( - (rect.outerRect.height * 0.1) > 5 - ? 5 - : (rect.outerRect.height * 0.1)); + (rect.outerRect.height * 0.1) > 5 + ? 5 + : (rect.outerRect.height * 0.1), + ); canvas.drawRRect( - RRect.fromRectAndRadius(rect.outerRect, cornerRadius), - _rectPainter); + RRect.fromRectAndRadius(rect.outerRect, cornerRadius), + _rectPainter, + ); } else { canvas.drawRect(rect.outerRect, _rectPainter); } 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 9305a9ab6..8acb9b83d 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 @@ -13,24 +13,27 @@ class AllDayAppointmentLayout extends StatefulWidget { /// Constructor to create the all day appointment layout that holds the /// all day appointment views in calendar widget. const AllDayAppointmentLayout( - this.calendar, - this.view, - this.visibleDates, - this.visibleAppointments, - this.timeLabelWidth, - this.allDayPainterHeight, - this.isExpandable, - this.isExpanding, - this.isRTL, - this.calendarTheme, - this.repaintNotifier, - this.allDayHoverPosition, - this.textScaleFactor, - this.isMobilePlatform, - this.width, - this.height, - this.localizations, - this.updateCalendarState); + this.calendar, + this.view, + this.visibleDates, + this.visibleAppointments, + this.timeLabelWidth, + this.allDayPainterHeight, + this.isExpandable, + this.isExpanding, + this.isRTL, + this.calendarTheme, + this.themeData, + this.repaintNotifier, + this.allDayHoverPosition, + this.textScaleFactor, + this.isMobilePlatform, + this.width, + this.height, + this.localizations, + this.updateCalendarState, { + super.key, + }); /// Holds the calendar instance used the get the properties of calendar. final SfCalendar calendar; @@ -46,7 +49,7 @@ class AllDayAppointmentLayout extends StatefulWidget { /// Holds the selection details and user to trigger repaint to draw the /// selection. - final ValueNotifier repaintNotifier; + final ValueNotifier repaintNotifier; /// Used to get the calendar state details. final UpdateCalendarState updateCalendarState; @@ -65,6 +68,9 @@ class AllDayAppointmentLayout extends StatefulWidget { /// Holds the theme data of the calendar widget. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data values. + final ThemeData themeData; + /// Used to hold the all day appointment hovering position. final ValueNotifier allDayHoverPosition; @@ -145,6 +151,7 @@ class _AllDayAppointmentLayoutState extends State { Widget build(BuildContext context) { /// Create the widgets when appointment builder is not null. if (_children.isEmpty && widget.calendar.appointmentBuilder != null) { + final DateTime initialVisibleDate = widget.visibleDates[0]; for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; @@ -156,24 +163,34 @@ class _AllDayAppointmentLayoutState extends State { continue; } - final DateTime date = DateTime( - appointmentView.appointment!.actualStartTime.year, - appointmentView.appointment!.actualStartTime.month, - appointmentView.appointment!.actualStartTime.day); + final DateTime appStartTime = DateTime( + appointmentView.appointment!.actualStartTime.year, + appointmentView.appointment!.actualStartTime.month, + appointmentView.appointment!.actualStartTime.day, + ); + final DateTime date = + appStartTime.isBefore(initialVisibleDate) + ? initialVisibleDate + : appStartTime; + final Widget child = widget.calendar.appointmentBuilder!( - context, - CalendarAppointmentDetails( - date, - List.unmodifiable([ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ]), - Rect.fromLTWH( - appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top, - appointmentView.appointmentRect!.width, - appointmentView.appointmentRect!.right), - isMoreAppointmentRegion: false)); + context, + CalendarAppointmentDetails( + date, + List.unmodifiable([ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ]), + Rect.fromLTWH( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + appointmentView.appointmentRect!.width, + appointmentView.appointmentRect!.right, + ), + ), + ); _children.add(RepaintBoundary(child: child)); } @@ -188,7 +205,9 @@ class _AllDayAppointmentLayoutState extends State { (widget.width - widget.timeLabelWidth) / widget.visibleDates.length; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - widget.calendar.cellEndPadding, widget.isMobilePlatform); + widget.calendar.cellEndPadding, + widget.isMobilePlatform, + ); /// Calculate the maximum appointment width based on cell end padding. final double maxAppointmentWidth = cellWidth - cellEndPadding; @@ -208,20 +227,26 @@ class _AllDayAppointmentLayoutState extends State { moreAppointments.add(currentAppointment.appointment!); } final Widget child = widget.calendar.appointmentBuilder!( - context, - CalendarAppointmentDetails( - date, - List.unmodifiable( - CalendarViewHelper.getCustomAppointments( - moreAppointments, widget.calendar.dataSource)), - Rect.fromLTWH( - widget.isRTL - ? widget.width - xPosition - maxAppointmentWidth - : xPosition, - widget.height - kAllDayAppointmentHeight, - maxAppointmentWidth, - kAllDayAppointmentHeight - 1), - isMoreAppointmentRegion: true)); + context, + CalendarAppointmentDetails( + date, + List.unmodifiable( + CalendarViewHelper.getCustomAppointments( + moreAppointments, + widget.calendar.dataSource, + ), + ), + Rect.fromLTWH( + widget.isRTL + ? widget.width - xPosition - maxAppointmentWidth + : xPosition, + widget.height - kAllDayAppointmentHeight, + maxAppointmentWidth, + kAllDayAppointmentHeight - 1, + ), + isMoreAppointmentRegion: true, + ), + ); _children.add(RepaintBoundary(child: child)); } @@ -238,6 +263,7 @@ class _AllDayAppointmentLayoutState extends State { widget.isExpanding, widget.isRTL, widget.calendarTheme, + widget.themeData, widget.repaintNotifier, widget.allDayHoverPosition, widget.textScaleFactor, @@ -266,12 +292,15 @@ class _AllDayAppointmentLayoutState extends State { _updateCalendarStateDetails.allDayAppointmentViewCollection; final double cellWidth = (widget.width - widget.timeLabelWidth) / widget.visibleDates.length; - const double cornerRadius = (kAllDayAppointmentHeight * 0.1) > 2 - ? 2 - : kAllDayAppointmentHeight * 0.1; + const double cornerRadius = + (kAllDayAppointmentHeight * 0.1) > 2 + ? 2 + : kAllDayAppointmentHeight * 0.1; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - widget.calendar.cellEndPadding, widget.isMobilePlatform); + widget.calendar.cellEndPadding, + widget.isMobilePlatform, + ); /// Calculate the maximum position of the appointment this widget can hold. final int position = widget.allDayPainterHeight ~/ kAllDayAppointmentHeight; @@ -284,35 +313,40 @@ class _AllDayAppointmentLayoutState extends State { RRect rect; if (widget.isRTL) { rect = RRect.fromRectAndRadius( - Rect.fromLTRB( - ((widget.visibleDates.length - appointmentView.endIndex) * - cellWidth) + - cellEndPadding, - kAllDayAppointmentHeight * appointmentView.position, - (widget.visibleDates.length - appointmentView.startIndex) * - cellWidth, - (kAllDayAppointmentHeight * appointmentView.position) + - kAllDayAppointmentHeight - - 1), - const Radius.circular(cornerRadius)); + Rect.fromLTRB( + ((widget.visibleDates.length - appointmentView.endIndex) * + cellWidth) + + cellEndPadding, + kAllDayAppointmentHeight * appointmentView.position, + (widget.visibleDates.length - appointmentView.startIndex) * + cellWidth, + (kAllDayAppointmentHeight * appointmentView.position) + + kAllDayAppointmentHeight - + 1, + ), + const Radius.circular(cornerRadius), + ); } else { rect = RRect.fromRectAndRadius( - Rect.fromLTRB( - widget.timeLabelWidth + - (appointmentView.startIndex * cellWidth), - kAllDayAppointmentHeight * appointmentView.position, - (appointmentView.endIndex * cellWidth) + - widget.timeLabelWidth - - cellEndPadding, - (kAllDayAppointmentHeight * appointmentView.position) + - kAllDayAppointmentHeight - - 1), - const Radius.circular(cornerRadius)); + Rect.fromLTRB( + widget.timeLabelWidth + (appointmentView.startIndex * cellWidth), + kAllDayAppointmentHeight * appointmentView.position, + (appointmentView.endIndex * cellWidth) + + widget.timeLabelWidth - + cellEndPadding, + (kAllDayAppointmentHeight * appointmentView.position) + + kAllDayAppointmentHeight - + 1, + ), + const Radius.circular(cornerRadius), + ); } - for (int j = appointmentView.startIndex; - j < appointmentView.endIndex; - j++) { + for ( + int j = appointmentView.startIndex; + j < appointmentView.endIndex; + j++ + ) { List appointmentViews; if (_indexAppointments.containsKey(j)) { appointmentViews = _indexAppointments[j]!; @@ -348,13 +382,15 @@ class _AllDayAppointmentLayoutState extends State { if (_appointmentCollection.isNotEmpty) { /// Calculate the maximum appointment position of all the appointment /// views in the widget. - maxPosition = _appointmentCollection - .reduce( - (AppointmentView currentAppView, AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + maxPosition = + _appointmentCollection + .reduce( + (AppointmentView currentAppView, AppointmentView nextAppView) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; } if (maxPosition == -1) { @@ -372,13 +408,18 @@ class _AllDayAppointmentLayoutState extends State { int count = 0; if (appointmentViews.isNotEmpty) { /// Calculate the current index appointments max position. - maxPosition = appointmentViews - .reduce((AppointmentView currentAppView, - AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + maxPosition = + appointmentViews + .reduce( + ( + AppointmentView currentAppView, + AppointmentView nextAppView, + ) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; } if (maxPosition <= position) { continue; @@ -412,37 +453,39 @@ class _AllDayAppointmentLayoutState extends State { } class _AllDayAppointmentRenderWidget extends MultiChildRenderObjectWidget { - _AllDayAppointmentRenderWidget( - this.calendar, - this.view, - this.visibleDates, - this.visibleAppointments, - this.timeLabelWidth, - this.allDayPainterHeight, - this.isExpandable, - this.isExpanding, - this.isRTL, - this.calendarTheme, - this.repaintNotifier, - this.allDayHoverPosition, - this.textScaleFactor, - this.isMobilePlatform, - this.width, - this.height, - this.localizations, - this.appointmentCollection, - this.moreAppointmentIndex, - {List widgets = const []}) - : super(children: widgets); + const _AllDayAppointmentRenderWidget( + this.calendar, + this.view, + this.visibleDates, + this.visibleAppointments, + this.timeLabelWidth, + this.allDayPainterHeight, + this.isExpandable, + this.isExpanding, + this.isRTL, + this.calendarTheme, + this.themeData, + this.repaintNotifier, + this.allDayHoverPosition, + this.textScaleFactor, + this.isMobilePlatform, + this.width, + this.height, + this.localizations, + this.appointmentCollection, + this.moreAppointmentIndex, { + List widgets = const [], + }) : super(children: widgets); final SfCalendar calendar; final CalendarView view; final List visibleDates; final List? visibleAppointments; - final ValueNotifier repaintNotifier; + final ValueNotifier repaintNotifier; final double timeLabelWidth; final double allDayPainterHeight; final bool isRTL; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final ValueNotifier allDayHoverPosition; final double textScaleFactor; final bool isMobilePlatform; @@ -457,34 +500,39 @@ class _AllDayAppointmentRenderWidget extends MultiChildRenderObjectWidget { @override _AllDayAppointmentRenderObject createRenderObject(BuildContext context) { return _AllDayAppointmentRenderObject( - calendar, - view, - visibleDates, - visibleAppointments, - timeLabelWidth, - allDayPainterHeight, - isExpandable, - isExpanding, - isRTL, - calendarTheme, - repaintNotifier, - allDayHoverPosition, - textScaleFactor, - isMobilePlatform, - width, - height, - localizations, - appointmentCollection, - moreAppointmentIndex); + calendar, + view, + visibleDates, + visibleAppointments, + timeLabelWidth, + allDayPainterHeight, + isExpandable, + isExpanding, + isRTL, + calendarTheme, + themeData, + repaintNotifier, + allDayHoverPosition, + textScaleFactor, + isMobilePlatform, + width, + height, + localizations, + appointmentCollection, + moreAppointmentIndex, + ); } @override void updateRenderObject( - BuildContext context, _AllDayAppointmentRenderObject renderObject) { + BuildContext context, + _AllDayAppointmentRenderObject renderObject, + ) { renderObject ..appointmentCollection = appointmentCollection ..moreAppointmentIndex = moreAppointmentIndex ..calendar = calendar + ..themeData = themeData ..view = view ..visibleDates = visibleDates ..visibleAppointments = visibleAppointments @@ -506,25 +554,27 @@ class _AllDayAppointmentRenderWidget extends MultiChildRenderObjectWidget { class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { _AllDayAppointmentRenderObject( - this.calendar, - this._view, - this._visibleDates, - this._visibleAppointments, - this._timeLabelWidth, - this._allDayPainterHeight, - this._isExpandable, - this.isExpanding, - this._isRTL, - this._calendarTheme, - this._selectionNotifier, - this._allDayHoverPosition, - this._textScaleFactor, - this.isMobilePlatform, - this._width, - this._height, - this._localizations, - this.appointmentCollection, - this.moreAppointmentIndex); + this.calendar, + this._view, + this._visibleDates, + this._visibleAppointments, + this._timeLabelWidth, + this._allDayPainterHeight, + this._isExpandable, + this.isExpanding, + this._isRTL, + this._calendarTheme, + this._themeData, + this._selectionNotifier, + this._allDayHoverPosition, + this._textScaleFactor, + this.isMobilePlatform, + this._width, + this._height, + this._localizations, + this.appointmentCollection, + this.moreAppointmentIndex, + ); SfCalendar calendar; bool isMobilePlatform; @@ -695,6 +745,18 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { markNeedsPaint(); } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + } + List _visibleDates; List get visibleDates => _visibleDates; @@ -739,11 +801,12 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { _allDayHoverPosition.addListener(markNeedsPaint); } - ValueNotifier _selectionNotifier; + ValueNotifier _selectionNotifier; - ValueNotifier get selectionNotifier => _selectionNotifier; + ValueNotifier get selectionNotifier => + _selectionNotifier; - set selectionNotifier(ValueNotifier value) { + set selectionNotifier(ValueNotifier value) { if (_selectionNotifier == value) { return; } @@ -762,12 +825,16 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { List? _cacheNodes; final Paint _rectPainter = Paint(); final TextPainter _textPainter = TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textAlign: TextAlign.left, - textWidthBasis: TextWidthBasis.longestLine); + textDirection: TextDirection.ltr, + maxLines: 1, + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + ); final TextPainter _expanderTextPainter = TextPainter( - textDirection: TextDirection.ltr, textAlign: TextAlign.left, maxLines: 1); + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + maxLines: 1, + ); late BoxPainter _boxPainter; bool _isHoveringAppointment = false; int _maxPosition = 0; @@ -838,7 +905,9 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - calendar.cellEndPadding, isMobilePlatform); + calendar.cellEndPadding, + isMobilePlatform, + ); final List keys = moreAppointmentIndex.keys.toList(); for (int i = 0; i < keys.length; i++) { if (child == null) { @@ -846,9 +915,11 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { } final int index = keys[i]; - final double leftPosition = isRTL - ? ((visibleDates.length - index - 1) * _cellWidth) + cellEndPadding - : timeLabelWidth + (index * _cellWidth); + final double leftPosition = + isRTL + ? ((visibleDates.length - index - 1) * _cellWidth) + + cellEndPadding + : timeLabelWidth + (index * _cellWidth); final Offset offset = Offset(leftPosition, maximumBottomPosition); final bool isHit = result.addWithPaintOffset( offset: offset, @@ -878,8 +949,10 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; final int position = allDayPainterHeight ~/ kAllDayAppointmentHeight; final double maximumBottomPosition = @@ -909,22 +982,29 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { continue; } - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: appointmentRect.height, maxHeight: appointmentRect.height, minWidth: appointmentRect.width, - maxWidth: appointmentRect.width)); + maxWidth: appointmentRect.width, + ), + ); final CalendarParentData childParentData = child.parentData! as CalendarParentData; - childParentData.offset = - Offset(appointmentRect.left, appointmentRect.top); + childParentData.offset = Offset( + appointmentRect.left, + appointmentRect.top, + ); child = childAfter(child); } _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; const double appointmentHeight = kAllDayAppointmentHeight - 1; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - calendar.cellEndPadding, isMobilePlatform); + calendar.cellEndPadding, + isMobilePlatform, + ); final double maxAppointmentWidth = _cellWidth - cellEndPadding; final List keys = moreAppointmentIndex.keys.toList(); for (int i = 0; i < keys.length; i++) { @@ -932,17 +1012,22 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { continue; } - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: appointmentHeight, maxHeight: appointmentHeight, minWidth: maxAppointmentWidth, - maxWidth: maxAppointmentWidth)); + maxWidth: maxAppointmentWidth, + ), + ); final CalendarParentData childParentData = child.parentData! as CalendarParentData; final int index = keys[i]; - final double leftPosition = isRTL - ? ((visibleDates.length - index - 1) * _cellWidth) + cellEndPadding - : timeLabelWidth + (index * _cellWidth); + final double leftPosition = + isRTL + ? ((visibleDates.length - index - 1) * _cellWidth) + + cellEndPadding + : timeLabelWidth + (index * _cellWidth); childParentData.offset = Offset(leftPosition, maximumBottomPosition); child = childAfter(child); } @@ -950,60 +1035,73 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { @override void paint(PaintingContext context, Offset offset) { - _textPainter.textScaleFactor = _textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); double leftPosition = 0, rightPosition = size.width; if (CalendarViewHelper.isDayView( - view, - calendar.timeSlotViewSettings.numberOfDaysInView, - calendar.timeSlotViewSettings.nonWorkingDays, - calendar.monthViewSettings.numberOfWeeksInView)) { + view, + calendar.timeSlotViewSettings.numberOfDaysInView, + calendar.timeSlotViewSettings.nonWorkingDays, + calendar.monthViewSettings.numberOfWeeksInView, + )) { _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. context.canvas.drawLine( - Offset( - isRTL ? size.width - timeLabelWidth + 0.5 : timeLabelWidth - 0.5, - 0), - Offset( - isRTL ? size.width - timeLabelWidth + 0.5 : timeLabelWidth - 0.5, - size.height), - _rectPainter); + Offset( + isRTL ? size.width - timeLabelWidth + 0.5 : timeLabelWidth - 0.5, + 0, + ), + Offset( + isRTL ? size.width - timeLabelWidth + 0.5 : timeLabelWidth - 0.5, + size.height, + ), + _rectPainter, + ); leftPosition = isRTL ? 0 : timeLabelWidth; rightPosition = isRTL ? size.width - timeLabelWidth : size.width; final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - calendar.viewHeaderHeight, view); - _rectPainter.color = calendar.timeSlotViewSettings.allDayPanelColor ?? + calendar.viewHeaderHeight, + view, + ); + _rectPainter.color = + calendar.timeSlotViewSettings.allDayPanelColor ?? calendarTheme.allDayPanelColor!; context.canvas.drawRect( - Rect.fromLTRB( - isRTL ? size.width - timeLabelWidth : 0, - viewHeaderHeight, - isRTL ? size.width : timeLabelWidth, - size.height), - _rectPainter); + Rect.fromLTRB( + isRTL ? size.width - timeLabelWidth : 0, + viewHeaderHeight, + isRTL ? size.width : timeLabelWidth, + size.height, + ), + _rectPainter, + ); } - _rectPainter.color = calendar.timeSlotViewSettings.allDayPanelColor ?? + _rectPainter.color = + calendar.timeSlotViewSettings.allDayPanelColor ?? calendarTheme.allDayPanelColor!; context.canvas.drawRect( - Rect.fromLTRB(leftPosition, 0, rightPosition, size.height), - _rectPainter); + Rect.fromLTRB(leftPosition, 0, rightPosition, size.height), + _rectPainter, + ); _rectPainter.isAntiAlias = true; _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; const double textPadding = 3; _maxPosition = 0; if (appointmentCollection.isNotEmpty) { - _maxPosition = appointmentCollection - .reduce( - (AppointmentView currentAppView, AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + _maxPosition = + appointmentCollection + .reduce( + (AppointmentView currentAppView, AppointmentView nextAppView) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; } if (_maxPosition == -1) { @@ -1046,90 +1144,7 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { context.paintChild(child, Offset(rect.left, rect.top)); child = childAfter(child); } else { - final CalendarAppointment appointment = appointmentView.appointment!; - _rectPainter.color = appointment.color; - context.canvas.drawRRect(rect, _rectPainter); - final TextSpan span = TextSpan( - text: _getAllDayAppointmentText(appointment), - style: calendar.appointmentTextStyle, - ); - - _textPainter.text = span; - _textPainter.layout( - minWidth: 0, - maxWidth: - rect.width - textPadding >= 0 ? rect.width - textPadding : 0); - if (_textPainter.maxLines == 1 && _textPainter.height > rect.height) { - continue; - } - - final bool canAddSpanIcon = - AppointmentHelper.canAddSpanIcon(visibleDates, appointment, view); - bool canAddForwardIcon = false; - bool canAddBackwardIcon = false; - - double xPosition = isRTL - ? rect.right - _textPainter.width - textPadding - : rect.left + textPadding; - - if (canAddSpanIcon) { - 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! * - _textPainter.textScaleFactor); - if (AppointmentHelper.canAddForwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate)) { - canAddForwardIcon = true; - iconSize = null; - } else if (AppointmentHelper.canAddBackwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate)) { - canAddBackwardIcon = true; - } else { - canAddForwardIcon = true; - canAddBackwardIcon = true; - } - - if (iconSize != null) { - if (isRTL) { - xPosition -= iconSize + (isMobilePlatform ? 0 : 2); - } else { - xPosition += iconSize + (isMobilePlatform ? 0 : 2); - } - } - } - - _textPainter.paint( - context.canvas, - Offset( - xPosition, rect.top + (rect.height - _textPainter.height) / 2)); - final bool isRecurrenceAppointment = - appointment.recurrenceRule != null && - appointment.recurrenceRule!.isNotEmpty; - if (isRecurrenceAppointment || appointment.recurrenceId != null) { - _addRecurrenceIcon( - context.canvas, rect, textPadding, isRecurrenceAppointment); - } - - if (canAddSpanIcon) { - if (canAddForwardIcon && canAddBackwardIcon) { - _addForwardSpanIconForAllDay( - context.canvas, rect, textPadding, isMobilePlatform); - _addBackwardSpanIconForAllDay( - context.canvas, rect, textPadding, isMobilePlatform); - } else if (canAddBackwardIcon) { - _addBackwardSpanIconForAllDay( - context.canvas, rect, textPadding, isMobilePlatform); - } else { - _addForwardSpanIconForAllDay( - context.canvas, rect, textPadding, isMobilePlatform); - } - } + _drawAllDayAppointmentView(context, offset, appointmentView); } _addMouseHoveringForAppointment(context.canvas, rect); @@ -1154,17 +1169,20 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { final double endYPosition = allDayPainterHeight - kAllDayAppointmentHeight; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - calendar.cellEndPadding, isMobilePlatform); + calendar.cellEndPadding, + isMobilePlatform, + ); final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { if (child == null) { continue; } - final double xPosition = isRTL - ? ((visibleDates.length - index - 1) * _cellWidth) + - cellEndPadding - : timeLabelWidth + (index * _cellWidth); + final double xPosition = + isRTL + ? ((visibleDates.length - index - 1) * _cellWidth) + + cellEndPadding + : timeLabelWidth + (index * _cellWidth); context.paintChild(child, Offset(xPosition, endYPosition)); child = childAfter(child); } @@ -1188,16 +1206,20 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { /// appointment. String _getAllDayAppointmentText(CalendarAppointment appointment) { if (!CalendarViewHelper.isDayView( - view, - calendar.timeSlotViewSettings.numberOfDaysInView, - calendar.timeSlotViewSettings.nonWorkingDays, - calendar.monthViewSettings.numberOfWeeksInView) || + view, + calendar.timeSlotViewSettings.numberOfDaysInView, + calendar.timeSlotViewSettings.nonWorkingDays, + calendar.monthViewSettings.numberOfWeeksInView, + ) || !appointment.isSpanned) { return appointment.subject; } return AppointmentHelper.getSpanAppointmentText( - appointment, visibleDates[0], _localizations); + appointment, + visibleDates[0], + _localizations, + ); } double _getTextSize(RRect rect, double textSize) { @@ -1209,70 +1231,229 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { } void _addForwardSpanIconForAllDay( - Canvas canvas, RRect rect, double textPadding, bool isMobilePlatform) { - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + Canvas canvas, + RRect rect, + double iconTextSize, + double forwardIconSize, + TextStyle appointmentTextStyle, + ) { final TextSpan icon = AppointmentHelper.getSpanIcon( - calendar.appointmentTextStyle.color!, textSize, !isRTL); - final double leftPadding = isMobilePlatform ? 1 : 2; + appointmentTextStyle.color!, + iconTextSize, + !isRTL, + ); _textPainter.text = icon; - _textPainter.layout( - minWidth: 0, - maxWidth: rect.width - textPadding >= 0 ? rect.width - textPadding : 0); + _textPainter.layout(maxWidth: rect.width >= 0 ? rect.width : 0); - final double yPosition = - AppointmentHelper.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, - isRTL ? rect.left : rect.right, rect.bottom), - rect.brRadius), - _rectPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL ? rect.left : rect.right - forwardIconSize, + rect.top, + isRTL ? rect.left + forwardIconSize : rect.right, + rect.bottom, + ), + rect.brRadius, + ), + _rectPainter, + ); + double iconPadding = (forwardIconSize - _textPainter.width) / 2; + if (iconPadding < 0) { + iconPadding = 0; + } + _textPainter.paint( - canvas, - Offset( - isRTL - ? rect.left + leftPadding - : rect.right - - (textSize * _textPainter.textScaleFactor) - - leftPadding, - yPosition)); + canvas, + Offset( + (isRTL ? rect.left : rect.right - forwardIconSize) + iconPadding, + yPosition, + ), + ); + } + + void _drawAllDayAppointmentView( + PaintingContext context, + Offset offset, + AppointmentView appointmentView, + ) { + final CalendarAppointment appointment = appointmentView.appointment!; + final RRect rect = appointmentView.appointmentRect!; + + _rectPainter.color = appointment.color; + context.canvas.drawRRect(rect, _rectPainter); + + final TextStyle appointmentTextStyle = + AppointmentHelper.getAppointmentTextStyle( + calendar.appointmentTextStyle, + view, + themeData, + ); + final double iconTextSize = _getTextSize( + rect, + _textPainter.textScaler.scale(appointmentTextStyle.fontSize!), + ); + const double iconPadding = 2; + //// Padding 4 is left and right 2 padding. + final double iconSize = iconTextSize + (2 * iconPadding); + final bool isRecurrenceAppointment = + appointment.recurrenceRule != null && + appointment.recurrenceRule!.isNotEmpty; + final double recurrenceIconSize = + isRecurrenceAppointment || appointment.recurrenceId != null + ? iconSize + : 0; + double forwardSpanIconSize = 0; + double backwardSpanIconSize = 0; + + final bool canAddSpanIcon = AppointmentHelper.canAddSpanIcon( + visibleDates, + appointment, + view, + ); + if (canAddSpanIcon) { + 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], + ); + if (AppointmentHelper.canAddForwardSpanIcon( + appStartTime, + appEndTime, + viewStartDate, + viewEndDate, + )) { + forwardSpanIconSize = iconSize; + } else if (AppointmentHelper.canAddBackwardSpanIcon( + appStartTime, + appEndTime, + viewStartDate, + viewEndDate, + )) { + backwardSpanIconSize = iconSize; + } else { + forwardSpanIconSize = iconSize; + backwardSpanIconSize = iconSize; + } + } + + final TextSpan span = TextSpan( + text: _getAllDayAppointmentText(appointment), + style: appointmentTextStyle, + ); + _textPainter.text = span; + final double totalIconSize = + recurrenceIconSize + forwardSpanIconSize + backwardSpanIconSize; + const double textPadding = 1; + _textPainter.layout( + maxWidth: + rect.width - totalIconSize - (2 * textPadding) >= 0 + ? rect.width - totalIconSize - (2 * textPadding) + : 0, + ); + if (_textPainter.maxLines == 1 && _textPainter.height > rect.height) { + return; + } + final double xPosition = + isRTL + ? rect.right - + _textPainter.width - + backwardSpanIconSize - + textPadding + : rect.left + backwardSpanIconSize + textPadding; + _textPainter.paint( + context.canvas, + Offset(xPosition, rect.top + (rect.height - _textPainter.height) / 2), + ); + + if (backwardSpanIconSize != 0) { + _addBackwardSpanIconForAllDay( + context.canvas, + rect, + iconTextSize, + backwardSpanIconSize, + appointmentTextStyle, + ); + } + + if (recurrenceIconSize != 0) { + _addRecurrenceIcon( + context.canvas, + rect, + isRecurrenceAppointment, + iconTextSize, + recurrenceIconSize, + forwardSpanIconSize, + appointmentTextStyle, + ); + } + + if (forwardSpanIconSize != 0) { + _addForwardSpanIconForAllDay( + context.canvas, + rect, + iconTextSize, + forwardSpanIconSize, + appointmentTextStyle, + ); + } } void _addBackwardSpanIconForAllDay( - Canvas canvas, RRect rect, double textPadding, bool isMobilePlatform) { - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + Canvas canvas, + RRect rect, + double iconTextSize, + double backwardIconSize, + TextStyle appointmentTextStyle, + ) { final TextSpan icon = AppointmentHelper.getSpanIcon( - calendar.appointmentTextStyle.color!, textSize, isRTL); - final double leftPadding = isMobilePlatform ? 1 : 2; + appointmentTextStyle.color!, + iconTextSize, + isRTL, + ); _textPainter.text = icon; - _textPainter.layout( - minWidth: 0, - maxWidth: rect.width - textPadding >= 0 ? rect.width - textPadding : 0); + _textPainter.layout(maxWidth: rect.width >= 0 ? rect.width : 0); - final double yPosition = - AppointmentHelper.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, - isRTL ? rect.right : rect.left, rect.bottom), - rect.brRadius), - _rectPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL ? rect.right - backwardIconSize : rect.left, + rect.top, + isRTL ? rect.right : rect.left + iconTextSize, + rect.bottom, + ), + rect.brRadius, + ), + _rectPainter, + ); + double iconPadding = (backwardIconSize - _textPainter.width) / 2; + if (iconPadding < 0) { + iconPadding = 0; + } + _textPainter.paint( - canvas, - Offset( - isRTL - ? rect.right - - (textSize * _textPainter.textScaleFactor) - - leftPadding - : rect.left + leftPadding, - yPosition)); + canvas, + Offset( + (isRTL ? rect.right - backwardIconSize : rect.left) + iconPadding, + yPosition, + ), + ); } void _addExpanderText(Canvas canvas, int position, double textPadding) { - final TextStyle textStyle = calendar.viewHeaderStyle.dayTextStyle ?? - calendarTheme.viewHeaderDayTextStyle!; + final TextStyle textStyle = calendarTheme.viewHeaderDayTextStyle!; final double endYPosition = allDayPainterHeight - kAllDayAppointmentHeight; final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { @@ -1282,53 +1463,55 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { ); _textPainter.text = span; _textPainter.layout( - minWidth: 0, - maxWidth: - _cellWidth - textPadding >= 0 ? _cellWidth - textPadding : 0); + maxWidth: _cellWidth - textPadding >= 0 ? _cellWidth - textPadding : 0, + ); _textPainter.paint( - canvas, - Offset( - isRTL - ? ((visibleDates.length - index) * _cellWidth) - - _textPainter.width - - textPadding - : timeLabelWidth + (index * _cellWidth) + textPadding, - endYPosition + - ((kAllDayAppointmentHeight - _textPainter.height) / 2))); + canvas, + Offset( + isRTL + ? ((visibleDates.length - index) * _cellWidth) - + _textPainter.width - + textPadding + : timeLabelWidth + (index * _cellWidth) + textPadding, + endYPosition + ((kAllDayAppointmentHeight - _textPainter.height) / 2), + ), + ); } } void _addExpandOrCollapseIcon(Canvas canvas, Size size, int position) { - final int iconCodePoint = _maxPosition <= position - ? Icons.expand_less.codePoint - : Icons.expand_more.codePoint; + final int iconCodePoint = + _maxPosition <= position + ? Icons.expand_less.codePoint + : Icons.expand_more.codePoint; final TextSpan icon = TextSpan( - text: String.fromCharCode(iconCodePoint), - style: TextStyle( - color: calendar.viewHeaderStyle.dayTextStyle != null && - calendar.viewHeaderStyle.dayTextStyle!.color != null - ? calendar.viewHeaderStyle.dayTextStyle!.color - : calendarTheme.viewHeaderDayTextStyle!.color, - fontSize: calendar.viewHeaderStyle.dayTextStyle != null && - calendar.viewHeaderStyle.dayTextStyle!.fontSize != null - ? calendar.viewHeaderStyle.dayTextStyle!.fontSize! * 2 - : kAllDayAppointmentHeight + 5, - fontFamily: 'MaterialIcons', - )); - _expanderTextPainter.textScaleFactor = textScaleFactor; + text: String.fromCharCode(iconCodePoint), + style: TextStyle( + color: calendarTheme.viewHeaderDayTextStyle!.color, + fontSize: + calendar.viewHeaderStyle.dayTextStyle != null && + calendar.viewHeaderStyle.dayTextStyle!.fontSize != null + ? calendar.viewHeaderStyle.dayTextStyle!.fontSize! * 2 + : kAllDayAppointmentHeight + 5, + fontFamily: 'MaterialIcons', + ), + ); + _expanderTextPainter.textScaler = TextScaler.linear(textScaleFactor); _expanderTextPainter.text = icon; - _expanderTextPainter.layout(minWidth: 0, maxWidth: timeLabelWidth); + _expanderTextPainter.layout(maxWidth: timeLabelWidth); _expanderTextPainter.paint( - canvas, - Offset( - isRTL - ? (size.width - timeLabelWidth) + - ((timeLabelWidth - _expanderTextPainter.width) / 2) - : (timeLabelWidth - _expanderTextPainter.width) / 2, - allDayPainterHeight - - kAllDayAppointmentHeight + - (kAllDayAppointmentHeight - _expanderTextPainter.height) / 2)); + canvas, + Offset( + isRTL + ? (size.width - timeLabelWidth) + + ((timeLabelWidth - _expanderTextPainter.width) / 2) + : (timeLabelWidth - _expanderTextPainter.width) / 2, + allDayPainterHeight - + kAllDayAppointmentHeight + + (kAllDayAppointmentHeight - _expanderTextPainter.height) / 2, + ), + ); } void _addMouseHoveringForAllDayPanel(Canvas canvas, Size size) { @@ -1337,20 +1520,26 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { } final int rowIndex = (allDayHoverPosition.value!.dx - (isRTL ? 0 : timeLabelWidth)) ~/ - _cellWidth; + _cellWidth; final double leftPosition = (rowIndex * _cellWidth) + (isRTL ? 0 : timeLabelWidth); - _rectPainter.color = Colors.grey.withOpacity(0.1); + _rectPainter.color = Colors.grey.withValues(alpha: 0.1); canvas.drawRect( - Rect.fromLTWH(leftPosition, 0, _cellWidth, size.height), _rectPainter); + Rect.fromLTWH(leftPosition, 0, _cellWidth, size.height), + _rectPainter, + ); } void _addSelectionForAllDayPanel(Canvas canvas, Size size) { final int index = DateTimeHelper.getIndex( - visibleDates, selectionNotifier.value!.selectedDate!); + visibleDates, + selectionNotifier.value!.selectedDate!, + ); Decoration? selectionDecoration = calendar.selectionDecoration; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - calendar.cellEndPadding, isMobilePlatform); + calendar.cellEndPadding, + isMobilePlatform, + ); /// Set the default selection decoration background color with opacity /// value based on theme brightness when selected date hold all day @@ -1361,13 +1550,15 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { appointmentView.startIndex <= index && appointmentView.endIndex > index) { selectionDecoration ??= BoxDecoration( - color: calendarTheme.brightness == Brightness.light - ? Colors.white.withOpacity(0.3) - : Colors.black.withOpacity(0.4), - border: - Border.all(color: calendarTheme.selectionBorderColor!, width: 2), + color: + themeData.brightness == Brightness.light + ? Colors.white.withValues(alpha: 0.3) + : Colors.black.withValues(alpha: 0.4), + border: Border.all( + color: calendarTheme.selectionBorderColor!, + width: 2, + ), borderRadius: const BorderRadius.all(Radius.circular(2)), - shape: BoxShape.rectangle, ); break; @@ -1380,24 +1571,35 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { color: Colors.transparent, border: Border.all(color: calendarTheme.selectionBorderColor!, width: 2), borderRadius: const BorderRadius.all(Radius.circular(2)), - shape: BoxShape.rectangle, ); Rect rect; double xValue = timeLabelWidth + (index * _cellWidth); if (isRTL) { xValue = size.width - xValue - _cellWidth; - rect = Rect.fromLTRB(xValue + cellEndPadding, 0, xValue + _cellWidth, - kAllDayAppointmentHeight - 1); + rect = Rect.fromLTRB( + xValue + cellEndPadding, + 0, + xValue + _cellWidth, + kAllDayAppointmentHeight - 1, + ); } else { - rect = Rect.fromLTRB(xValue, 0, xValue + _cellWidth - cellEndPadding, - kAllDayAppointmentHeight - 1); + rect = Rect.fromLTRB( + xValue, + 0, + xValue + _cellWidth - cellEndPadding, + kAllDayAppointmentHeight - 1, + ); } - _boxPainter = - selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); - _boxPainter.paint(canvas, Offset(rect.left, rect.top), - ImageConfiguration(size: rect.size)); + _boxPainter = selectionDecoration.createBoxPainter( + _updateSelectionDecorationPainter, + ); + _boxPainter.paint( + canvas, + Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size), + ); selectionDecoration = null; } @@ -1405,27 +1607,33 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { /// 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 = AllDayPanelSelectionDetails( + selectionNotifier.value!.appointmentView, + selectionNotifier.value!.selectedDate, + ); } void _addSelectionForAppointment( - Canvas canvas, AppointmentView appointmentView) { + Canvas canvas, + AppointmentView appointmentView, + ) { Decoration? selectionDecoration = calendar.selectionDecoration; selectionDecoration ??= BoxDecoration( color: Colors.transparent, 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.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)); + _boxPainter = selectionDecoration.createBoxPainter( + _updateSelectionDecorationPainter, + ); + _boxPainter.paint( + canvas, + Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size), + ); } void _addMouseHoveringForAppointment(Canvas canvas, RRect rect) { @@ -1437,37 +1645,65 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { rect.right > allDayHoverPosition.value!.dx && rect.top < allDayHoverPosition.value!.dy && rect.bottom > allDayHoverPosition.value!.dy) { - _rectPainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); + _rectPainter.color = calendarTheme.selectionBorderColor!.withValues( + alpha: 0.4, + ); _rectPainter.strokeWidth = 2; _rectPainter.style = PaintingStyle.stroke; - canvas.drawRect(rect.outerRect, _rectPainter); + canvas.drawRRect(rect, _rectPainter); _rectPainter.style = PaintingStyle.fill; _isHoveringAppointment = true; } } - void _addRecurrenceIcon(Canvas canvas, RRect rect, double textPadding, - bool isRecurrenceAppointment) { - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + void _addRecurrenceIcon( + Canvas canvas, + RRect rect, + bool isRecurrenceAppointment, + double iconTextSize, + double recurrenceIconSize, + double forwardSpanIconSize, + TextStyle appointmentTextStyle, + ) { final TextSpan icon = AppointmentHelper.getRecurrenceIcon( - calendar.appointmentTextStyle.color!, - textSize, - isRecurrenceAppointment); + appointmentTextStyle.color!, + iconTextSize, + isRecurrenceAppointment, + ); _textPainter.text = icon; - _textPainter.layout( - minWidth: 0, - maxWidth: rect.width - textPadding >= 0 ? rect.width - textPadding : 0); + _textPainter.layout(maxWidth: rect.width >= 0 ? rect.width : 0); + canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(isRTL ? rect.left : rect.right - textSize, rect.top, - isRTL ? rect.left : rect.right, rect.bottom), - rect.brRadius), - _rectPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL + ? rect.left + forwardSpanIconSize + : rect.right - recurrenceIconSize - forwardSpanIconSize, + rect.top, + isRTL + ? rect.left + recurrenceIconSize + forwardSpanIconSize + : rect.right - forwardSpanIconSize, + rect.bottom, + ), + rect.brRadius, + ), + _rectPainter, + ); + double iconPadding = (recurrenceIconSize - _textPainter.width) / 2; + if (iconPadding < 0) { + iconPadding = 0; + } + _textPainter.paint( - canvas, - Offset(isRTL ? rect.left + 1 : rect.right - textSize - 1, - rect.top + (rect.height - _textPainter.height) / 2)); + canvas, + Offset( + (isRTL + ? rect.left + forwardSpanIconSize + : rect.right - recurrenceIconSize - forwardSpanIconSize) + + iconPadding, + rect.top + (rect.height - _textPainter.height) / 2, + ), + ); } @override @@ -1487,9 +1723,10 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { 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 SemanticsNode newChild = + _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); @@ -1530,6 +1767,12 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; + + final RenderBox? child = firstChild; + if (child != null) { + return semanticsBuilder; + } + if (appointmentCollection.isEmpty) { return semanticsBuilder; } @@ -1539,16 +1782,23 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { if (isExpandable) { final double left = isRTL ? size.width - timeLabelWidth : 0; 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', - textDirection: TextDirection.ltr, + 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', + textDirection: TextDirection.ltr, + ), ), - )); + ); } if (isExpandable && @@ -1556,19 +1806,22 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { !isExpanding) { final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH( + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( isRTL ? ((visibleDates.length - index) * _cellWidth) - _cellWidth : timeLabelWidth + (index * _cellWidth), bottom, _cellWidth, - kAllDayAppointmentHeight), - properties: SemanticsProperties( - label: '+${moreAppointmentIndex[index]}', - textDirection: TextDirection.ltr, + kAllDayAppointmentHeight, + ), + properties: SemanticsProperties( + label: '+${moreAppointmentIndex[index]}', + textDirection: TextDirection.ltr, + ), ), - )); + ); } } @@ -1582,16 +1835,31 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { continue; } - semanticsBuilder.add(CustomPainterSemantics( - rect: view.appointmentRect!.outerRect, - properties: SemanticsProperties( - label: - CalendarViewHelper.getAppointmentSemanticsText(view.appointment!), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: view.appointmentRect!.outerRect, + properties: SemanticsProperties( + label: CalendarViewHelper.getAppointmentSemanticsText( + view.appointment!, + ), + textDirection: TextDirection.ltr, + ), ), - )); + ); } return semanticsBuilder; } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderBox? child = firstChild; + if (child == null) { + return; + } + while (child != null) { + visitor(child); + child = childAfter(child); + } + } } 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 c7af2f434..a51c09531 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 @@ -21,24 +21,25 @@ class AppointmentLayout extends StatefulWidget { /// Constructor to create the appointment layout that holds the appointment /// views in calendar widget. const AppointmentLayout( - this.calendar, - this.view, - this.visibleDates, - this.visibleAppointments, - this.timeIntervalHeight, - this.calendarTheme, - this.isRTL, - this.appointmentHoverPosition, - this.resourceCollection, - this.resourceItemHeight, - this.textScaleFactor, - this.isMobilePlatform, - this.width, - this.height, - this.localizations, - this.updateCalendarState, - {Key? key}) - : super(key: key); + this.calendar, + this.view, + this.visibleDates, + this.visibleAppointments, + this.timeIntervalHeight, + this.calendarTheme, + this.themeData, + this.isRTL, + this.appointmentHoverPosition, + this.resourceCollection, + this.resourceItemHeight, + this.textScaleFactor, + this.isMobilePlatform, + this.width, + this.height, + this.localizations, + this.updateCalendarState, { + Key? key, + }) : super(key: key); /// Holds the calendar instance used the get the properties of calendar. final SfCalendar calendar; @@ -62,6 +63,9 @@ class AppointmentLayout extends StatefulWidget { /// Holds the theme data of the calendar widget. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data values. + final ThemeData themeData; + /// Used to hold the appointment layout hovering position. final ValueNotifier appointmentHoverPosition; @@ -138,7 +142,10 @@ class _AppointmentLayoutState extends State { void initState() { widget.updateCalendarState(_updateCalendarStateDetails); _weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, widget.width, widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); _updateAppointmentDetails(); widget.visibleAppointments.addListener(_updateVisibleAppointment); @@ -157,9 +164,10 @@ class _AppointmentLayoutState extends State { (widget.resourceCollection != oldWidget.resourceCollection || widget.resourceItemHeight != oldWidget.resourceItemHeight))) { _weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); isAppointmentDetailsUpdated = true; _updateAppointmentDetails(); } @@ -168,8 +176,9 @@ class _AppointmentLayoutState extends State { oldWidget.visibleAppointments.removeListener(_updateVisibleAppointment); widget.visibleAppointments.addListener(_updateVisibleAppointment); if (!CalendarViewHelper.isCollectionEqual( - widget.visibleAppointments.value, - oldWidget.visibleAppointments.value) && + widget.visibleAppointments.value, + oldWidget.visibleAppointments.value, + ) && !isAppointmentDetailsUpdated) { _updateAppointmentDetails(); } @@ -178,9 +187,10 @@ class _AppointmentLayoutState extends State { if (widget.calendar.showWeekNumber != oldWidget.calendar.showWeekNumber && widget.view == CalendarView.month) { _weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); _updateAppointmentDetails(); } @@ -198,6 +208,7 @@ class _AppointmentLayoutState extends State { Widget build(BuildContext context) { /// Create the widgets when appointment builder is not null. if (_children.isEmpty && widget.calendar.appointmentBuilder != null) { + final DateTime initialVisibleDate = widget.visibleDates[0]; for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; @@ -208,24 +219,36 @@ class _AppointmentLayoutState extends State { continue; } - final DateTime date = DateTime( - appointmentView.appointment!.actualStartTime.year, - appointmentView.appointment!.actualStartTime.month, - appointmentView.appointment!.actualStartTime.day); + final DateTime appStartTime = DateTime( + appointmentView.appointment!.actualStartTime.year, + appointmentView.appointment!.actualStartTime.month, + appointmentView.appointment!.actualStartTime.day, + ); + final DateTime date = + appointmentView.startIndex != -1 + ? widget.visibleDates[appointmentView.startIndex] + : appStartTime.isBefore(initialVisibleDate) + ? initialVisibleDate + : appStartTime; + final Widget child = widget.calendar.appointmentBuilder!( - context, - CalendarAppointmentDetails( - date, - List.unmodifiable([ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ]), - Rect.fromLTWH( - appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top, - appointmentView.appointmentRect!.width, - appointmentView.appointmentRect!.height), - isMoreAppointmentRegion: false)); + context, + CalendarAppointmentDetails( + date, + List.unmodifiable([ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ]), + Rect.fromLTWH( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + appointmentView.appointmentRect!.width, + appointmentView.appointmentRect!.height, + ), + ), + ); _children.add(RepaintBoundary(child: child)); } @@ -252,15 +275,24 @@ class _AppointmentLayoutState extends State { final DateTime date = widget.visibleDates[index]; final RRect moreRegionRect = _monthAppointmentCountViews[index]!; final Widget child = widget.calendar.appointmentBuilder!( - context, - CalendarAppointmentDetails( - date, - List.unmodifiable( - CalendarViewHelper.getCustomAppointments( - moreAppointments, widget.calendar.dataSource)), - Rect.fromLTWH(moreRegionRect.left, moreRegionRect.top, - moreRegionRect.width, moreRegionRect.height), - isMoreAppointmentRegion: true)); + context, + CalendarAppointmentDetails( + date, + List.unmodifiable( + CalendarViewHelper.getCustomAppointments( + moreAppointments, + widget.calendar.dataSource, + ), + ), + Rect.fromLTWH( + moreRegionRect.left, + moreRegionRect.top, + moreRegionRect.width, + moreRegionRect.height, + ), + isMoreAppointmentRegion: true, + ), + ); /// Throw exception when builder return widget is null. _children.add(RepaintBoundary(child: child)); @@ -269,26 +301,28 @@ class _AppointmentLayoutState extends State { } return _AppointmentRenderWidget( - widget.calendar, - widget.view, - widget.visibleDates, - widget.visibleAppointments.value, - widget.timeIntervalHeight, - widget.calendarTheme, - widget.isRTL, - widget.appointmentHoverPosition, - widget.resourceCollection, - widget.resourceItemHeight, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.width, - widget.height, - widget.localizations, - _appointmentCollection, - _indexAppointments, - _monthAppointmentCountViews, - _weekNumberPanelWidth, - widgets: _children); + widget.calendar, + widget.view, + widget.visibleDates, + widget.visibleAppointments.value, + widget.timeIntervalHeight, + widget.calendarTheme, + widget.themeData, + widget.isRTL, + widget.appointmentHoverPosition, + widget.resourceCollection, + widget.resourceItemHeight, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.width, + widget.height, + widget.localizations, + _appointmentCollection, + _indexAppointments, + _monthAppointmentCountViews, + _weekNumberPanelWidth, + widgets: _children, + ); } AppointmentView? _getAppointmentViewOnPoint(double x, double y) { @@ -347,7 +381,8 @@ class _AppointmentLayoutState extends State { /// Remove the appointments before the calendar time slot start date and /// after the calendar time slot end date. List _getValidAppointments( - List visibleAppointments) { + List visibleAppointments, + ) { if (visibleAppointments.isEmpty || widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth) { @@ -357,12 +392,14 @@ class _AppointmentLayoutState extends State { final List appointments = []; final int viewStartHour = widget.calendar.timeSlotViewSettings.startHour.toInt(); - final int viewStartMinutes = (viewStartHour * 60) + + 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) + + final int viewEndMinutes = + (viewEndHour * 60) + ((widget.calendar.timeSlotViewSettings.endHour - viewEndHour) * 60) .toInt(); @@ -374,14 +411,16 @@ class _AppointmentLayoutState extends State { 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(const Duration(days: 1)))) { + if (isSameDate( + appointment.actualEndTime, + appointment.actualStartTime.add(const Duration(days: 1)), + )) { final int appointmentStartMinutes = (appointment.actualStartTime.hour * 60) + - appointment.actualStartTime.minute; + appointment.actualStartTime.minute; final int appointmentEndMinutes = (appointment.actualEndTime.hour * 60) + - appointment.actualEndTime.minute; + appointment.actualEndTime.minute; if (appointmentStartMinutes >= viewEndMinutes && appointmentEndMinutes <= viewStartMinutes) { continue; @@ -393,8 +432,9 @@ class _AppointmentLayoutState extends State { } final int appointmentStartMinutes = (appointment.actualStartTime.hour * 60) + - appointment.actualStartTime.minute; - final int appointmentEndMinutes = (appointment.actualEndTime.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 @@ -428,8 +468,9 @@ class _AppointmentLayoutState extends State { return; } - final List visibleAppointments = - _getValidAppointments(widget.visibleAppointments.value!); + final List visibleAppointments = _getValidAppointments( + widget.visibleAppointments.value!, + ); switch (widget.view) { case CalendarView.month: { @@ -461,7 +502,8 @@ class _AppointmentLayoutState extends State { } void _updateMonthAppointmentDetails( - List visibleAppointments) { + List visibleAppointments, + ) { final double cellWidth = (widget.width - _weekNumberPanelWidth) / DateTime.daysPerWeek; final double cellHeight = @@ -471,46 +513,62 @@ class _AppointmentLayoutState extends State { return; } - double xPosition = widget.isRTL - ? widget.width - cellWidth - _weekNumberPanelWidth - : _weekNumberPanelWidth; + double xPosition = + widget.isRTL + ? widget.width - cellWidth - _weekNumberPanelWidth + : _weekNumberPanelWidth; double yPosition = 0; final int count = widget.visibleDates.length; - DateTime visibleStartDate = - AppointmentHelper.convertToStartTime(widget.visibleDates[0]); - DateTime visibleEndDate = - AppointmentHelper.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 = count - 1; final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates); + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + ); if (!showTrailingLeadingDates) { final DateTime currentMonthDate = widget.visibleDates[count ~/ 2]; visibleStartDate = AppointmentHelper.convertToStartTime( - AppointmentHelper.getMonthStartDate(currentMonthDate)); + AppointmentHelper.getMonthStartDate(currentMonthDate), + ); visibleEndDate = AppointmentHelper.convertToEndTime( - AppointmentHelper.getMonthEndDate(currentMonthDate)); - visibleStartIndex = - DateTimeHelper.getIndex(widget.visibleDates, visibleStartDate); - visibleEndIndex = - DateTimeHelper.getIndex(widget.visibleDates, visibleEndDate); + AppointmentHelper.getMonthEndDate(currentMonthDate), + ); + visibleStartIndex = DateTimeHelper.getIndex( + widget.visibleDates, + visibleStartDate, + ); + visibleEndIndex = DateTimeHelper.getIndex( + widget.visibleDates, + visibleEndDate, + ); } MonthAppointmentHelper.updateAppointmentDetails( - visibleAppointments, - _appointmentCollection, - widget.visibleDates, - _indexAppointments, - visibleStartIndex, - visibleEndIndex); - final TextStyle style = - widget.calendar.todayTextStyle ?? widget.calendarTheme.todayTextStyle!; - final TextSpan dateText = - TextSpan(text: DateTime.now().day.toString(), style: style); + visibleAppointments, + _appointmentCollection, + widget.visibleDates, + _indexAppointments, + visibleStartIndex, + visibleEndIndex, + ); + final TextStyle style = widget.calendarTheme.todayTextStyle!; + final TextSpan dateText = TextSpan( + text: DateTime.now().day.toString(), + style: style, + ); _textPainter = _updateTextPainter( - dateText, _textPainter, widget.isRTL, widget.textScaleFactor); + dateText, + _textPainter, + widget.isRTL, + widget.textScaleFactor, + ); /// cell padding and start position calculated by month date cell /// rendering padding and size. @@ -529,7 +587,9 @@ class _AppointmentLayoutState extends State { // right side padding used to add padding on appointment view right side // in month view final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - widget.calendar.cellEndPadding, widget.isMobilePlatform); + widget.calendar.cellEndPadding, + widget.isMobilePlatform, + ); for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { @@ -541,15 +601,16 @@ class _AppointmentLayoutState extends State { appointmentView.maxPositions == maximumDisplayCount)) { final double appointmentWidth = (appointmentView.endIndex - appointmentView.startIndex + 1) * - cellWidth; + cellWidth; if (widget.isRTL) { xPosition = (6 - (appointmentView.startIndex % DateTime.daysPerWeek)) * - cellWidth; + cellWidth; xPosition -= appointmentWidth - cellWidth; } else { - xPosition = ((appointmentView.startIndex % DateTime.daysPerWeek) * + xPosition = + ((appointmentView.startIndex % DateTime.daysPerWeek) * cellWidth) + _weekNumberPanelWidth; } @@ -557,26 +618,31 @@ class _AppointmentLayoutState extends State { yPosition = (appointmentView.startIndex ~/ DateTime.daysPerWeek) * cellHeight; if (appointmentView.position <= maximumDisplayCount) { - yPosition = yPosition + + yPosition = + yPosition + startPosition + (appointmentHeight * (appointmentView.position - 1)); } else { - yPosition = yPosition + + yPosition = + yPosition + startPosition + (appointmentHeight * (maximumDisplayCount - 1)); } final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); + (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1), + ); final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH( - widget.isRTL ? xPosition + cellEndPadding : xPosition, - yPosition, - appointmentWidth - cellEndPadding > 0 - ? appointmentWidth - cellEndPadding - : 0, - appointmentHeight > 1 ? appointmentHeight - 1 : 0), - cornerRadius); + Rect.fromLTWH( + widget.isRTL ? xPosition + cellEndPadding : xPosition, + yPosition, + appointmentWidth - cellEndPadding > 0 + ? appointmentWidth - cellEndPadding + : 0, + appointmentHeight > 1 ? appointmentHeight - 1 : 0, + ), + cornerRadius, + ); appointmentView.appointmentRect = rect; } @@ -585,59 +651,72 @@ class _AppointmentLayoutState extends State { final List keys = _indexAppointments.keys.toList(); for (int i = 0; i < keys.length; i++) { final int index = keys[i]; - final int maxPosition = _indexAppointments[index]! - .reduce( - (AppointmentView currentAppView, AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + final int maxPosition = + _indexAppointments[index]! + .reduce( + (AppointmentView currentAppView, AppointmentView nextAppView) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; if (maxPosition <= maximumDisplayCount) { continue; } if (widget.isRTL) { xPosition = (6 - (index % DateTime.daysPerWeek)) * cellWidth; } else { - xPosition = ((index % DateTime.daysPerWeek) * cellWidth) + + xPosition = + ((index % DateTime.daysPerWeek) * cellWidth) + _weekNumberPanelWidth; } - yPosition = ((index ~/ DateTime.daysPerWeek) * cellHeight) + + yPosition = + ((index ~/ DateTime.daysPerWeek) * cellHeight) + cellHeight - appointmentHeight; final RRect moreRegionRect = RRect.fromRectAndRadius( - Rect.fromLTWH( - widget.isRTL ? xPosition + cellEndPadding : xPosition, - yPosition, - cellWidth - cellEndPadding > 0 ? cellWidth - cellEndPadding : 0, - appointmentHeight - 1), - Radius.zero); + Rect.fromLTWH( + widget.isRTL ? xPosition + cellEndPadding : xPosition, + yPosition, + cellWidth - cellEndPadding > 0 ? cellWidth - cellEndPadding : 0, + appointmentHeight - 1, + ), + Radius.zero, + ); _monthAppointmentCountViews[index] = moreRegionRect; } } void _updateDayAppointmentDetails( - List visibleAppointments) { + List visibleAppointments, + ) { final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final double width = widget.width - timeLabelWidth; AppointmentHelper.setAppointmentPositionAndMaxPosition( - _appointmentCollection, - widget.calendar, - widget.view, - visibleAppointments, - false); + _appointmentCollection, + widget.calendar, + widget.view, + visibleAppointments, + false, + ); final int count = widget.visibleDates.length; final double cellWidth = width / count; final double cellHeight = widget.timeIntervalHeight; double xPosition = timeLabelWidth; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - widget.calendar.cellEndPadding, widget.isMobilePlatform); + widget.calendar.cellEndPadding, + widget.isMobilePlatform, + ); final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); final int viewStartHour = widget.calendar.timeSlotViewSettings.startHour.toInt(); final double viewStartMinutes = @@ -653,8 +732,8 @@ class _AppointmentLayoutState extends State { int column = -1; for (int j = 0; j < count; j++) { - final DateTime _date = widget.visibleDates[j]; - if (isSameDate(_date, appointment.actualStartTime)) { + final DateTime date = widget.visibleDates[j]; + if (isSameDate(date, appointment.actualStartTime)) { column = widget.isRTL ? count - 1 - j : j; break; } @@ -663,8 +742,9 @@ class _AppointmentLayoutState extends State { if (column == -1 || appointment.isSpanned || AppointmentHelper.getDifference( - appointment.startTime, appointment.endTime) - .inDays > + appointment.startTime, + appointment.endTime, + ).inDays > 0 || appointment.isAllDay) { continue; @@ -677,27 +757,36 @@ class _AppointmentLayoutState extends State { final double appointmentWidth = (cellWidth - cellEndPadding) / appointmentView.maxPositions; if (widget.isRTL) { - xPosition = column * cellWidth + + xPosition = + column * cellWidth + (appointmentView.position * appointmentWidth) + cellEndPadding; } else { - xPosition = column * cellWidth + + xPosition = + column * cellWidth + (appointmentView.position * appointmentWidth) + timeLabelWidth; } Duration difference = AppointmentHelper.getDifference( - appointment.actualStartTime, appointment.actualEndTime); + appointment.actualStartTime, + appointment.actualEndTime, + ); final double minuteHeight = cellHeight / timeInterval; 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 + widget + .calendar + .timeSlotViewSettings .minimumAppointmentDuration! && difference.inMinutes * minuteHeight < widget.calendar.timeSlotViewSettings.timeIntervalHeight) { @@ -724,6 +813,17 @@ class _AppointmentLayoutState extends State { /// 8 PM to 9 PM and the calendar end time is 6 PM then skip the /// rendering). continue; + } + + if (yPosition < 0 && yPosition + height > widget.height) { + /// Change the start position and height when appointment start time + /// before the calendar start time and appointment end time after the + /// calendar end time.(Eg., appointment start and end date as 6 AM to + /// 9 PM and the calendar start time is 8 AM and end time is 6 PM then + /// calculate the new size from 8 AM to 6 MM, if we does not calculate + /// the new size then the appointment text drawn on hidden place). + height = widget.height; + yPosition = 0; } 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 @@ -741,23 +841,29 @@ class _AppointmentLayoutState extends State { yPosition = 0; } - final Radius cornerRadius = - Radius.circular((height * 0.1) > 2 ? 2 : (height * 0.1)); + final Radius cornerRadius = Radius.circular( + (height * 0.1) > 2 ? 2 : (height * 0.1), + ); final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH( - xPosition, - yPosition, - appointmentWidth > 1 ? appointmentWidth - 1 : 0, - height > 1 ? height - 1 : 0), - cornerRadius); + Rect.fromLTWH( + xPosition, + yPosition, + appointmentWidth > 1 ? appointmentWidth - 1 : 0, + height > 1 ? height - 1 : 0, + ), + cornerRadius, + ); appointmentView.appointmentRect = rect; } } void _updateTimelineMonthAppointmentDetails( - List visibleAppointments) { + List visibleAppointments, + ) { final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + widget.calendar.dataSource, + widget.view, + ); /// Filters the appointment for each resource from the visible appointment /// collection, and assign appointment views for all the collections. @@ -770,26 +876,30 @@ class _AppointmentLayoutState extends State { /// appointment collection. final List appointmentForEachResource = visibleAppointments - .where((CalendarAppointment app) => - app.resourceIds != null && - app.resourceIds!.isNotEmpty && - app.resourceIds!.contains(resource.id)) + .where( + (CalendarAppointment app) => + app.resourceIds != null && + app.resourceIds!.isNotEmpty && + app.resourceIds!.contains(resource.id), + ) .toList(); AppointmentHelper.setAppointmentPositionAndMaxPosition( - _appointmentCollection, - widget.calendar, - widget.view, - appointmentForEachResource, - false, - i); - } - } else { - AppointmentHelper.setAppointmentPositionAndMaxPosition( _appointmentCollection, widget.calendar, widget.view, - visibleAppointments, - false); + appointmentForEachResource, + false, + i, + ); + } + } else { + AppointmentHelper.setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + visibleAppointments, + false, + ); } final int visibleDatesLength = widget.visibleDates.length; @@ -798,11 +908,15 @@ class _AppointmentLayoutState extends State { double xPosition = 0; double yPosition = 0; final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - widget.calendar.cellEndPadding, widget.isMobilePlatform); + widget.calendar.cellEndPadding, + widget.isMobilePlatform, + ); final double slotHeight = isResourceEnabled ? widget.resourceItemHeight! : widget.height; final double timelineAppointmentHeight = _getTimelineAppointmentHeight( - widget.calendar.timeSlotViewSettings, widget.view); + widget.calendar.timeSlotViewSettings, + widget.view, + ); for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { @@ -813,8 +927,10 @@ class _AppointmentLayoutState extends State { int column = -1; final DateTime startTime = appointment.actualStartTime; - int index = - DateTimeHelper.getVisibleDateIndex(widget.visibleDates, startTime); + int index = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, + startTime, + ); if (index == -1 && startTime.isBefore(widget.visibleDates[0])) { index = 0; } @@ -847,38 +963,73 @@ class _AppointmentLayoutState extends State { } final DateTime endTime = appointment.actualEndTime; - final Duration difference = - AppointmentHelper.getDifference(startTime, endTime); + final Duration difference = AppointmentHelper.getDifference( + startTime, + endTime, + ); /// 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; + /// Handles multi-span appointments that fall within the same month. + /// If the end date includes any partial time periods beyond full days, + /// the remaining time is calculated and used to extend the appointment + /// rendering into the next day by adding an additional width for an appointment. + /// Example Case: + /// Start: 2025-01-01 02:00 + /// End: 2025-01-04 01:00 + /// Total Duration: 71 hours + /// - Remaining Time: 71 hours % 24 = 23 hours. + /// - The remaining time beyond full days includes hours, minutes, and seconds. + /// - Adds an extra width for partial durations to ensure the appointment renders correctly. + final DateTime exactStartTime = appointment.exactStartTime; + final DateTime exactEndTime = appointment.exactEndTime; + final bool isSameHour = exactStartTime.hour == exactEndTime.hour; + final bool isMultiDaySpanWithinSameMonth = + exactStartTime.year == exactEndTime.year && + exactStartTime.month == exactEndTime.month && + (exactStartTime.hour > exactEndTime.hour || + (isSameHour && exactStartTime.minute > exactEndTime.minute) || + (isSameHour && + exactStartTime.minute == exactEndTime.minute && + exactStartTime.second > exactEndTime.second)) && + difference.inSeconds % (24 * 60 * 60) > 0; + /// 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) { + final bool isSingleDaySpan = + difference.inDays == 0 && endTime.day != startTime.day; + + if (isSingleDaySpan || isMultiDaySpanWithinSameMonth) { width += cellWidth; } width = width - cellEndPadding; final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); + (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); + 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) { + List visibleAppointments, + ) { final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + widget.calendar.dataSource, + widget.view, + ); /// Filters the appointment for each resource from the visible appointment /// collection, and assign appointment views for all the collections. @@ -891,26 +1042,30 @@ class _AppointmentLayoutState extends State { /// appointment collection. final List appointmentForEachResource = visibleAppointments - .where((CalendarAppointment app) => - app.resourceIds != null && - app.resourceIds!.isNotEmpty && - app.resourceIds!.contains(resource.id)) + .where( + (CalendarAppointment app) => + app.resourceIds != null && + app.resourceIds!.isNotEmpty && + app.resourceIds!.contains(resource.id), + ) .toList(); AppointmentHelper.setAppointmentPositionAndMaxPosition( - _appointmentCollection, - widget.calendar, - widget.view, - appointmentForEachResource, - false, - i); - } - } else { - AppointmentHelper.setAppointmentPositionAndMaxPosition( _appointmentCollection, widget.calendar, widget.view, - visibleAppointments, - false); + appointmentForEachResource, + false, + i, + ); + } + } else { + AppointmentHelper.setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + visibleAppointments, + false, + ); } final int count = widget.visibleDates.length; @@ -919,18 +1074,24 @@ class _AppointmentLayoutState extends State { double xPosition = 0; double yPosition = 0; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); final double cellEndPadding = CalendarViewHelper.getCellEndPadding( - widget.calendar.cellEndPadding, widget.isMobilePlatform); + widget.calendar.cellEndPadding, + widget.isMobilePlatform, + ); final int viewStartHour = widget.calendar.timeSlotViewSettings.startHour.toInt(); final double viewStartMinutes = (widget.calendar.timeSlotViewSettings.startHour - viewStartHour) * 60; final double timelineAppointmentHeight = _getTimelineAppointmentHeight( - widget.calendar.timeSlotViewSettings, widget.view); - final double slotHeight = isResourceEnabled - ? widget.resourceItemHeight! - cellEndPadding - : widget.height - cellEndPadding; + widget.calendar.timeSlotViewSettings, + widget.view, + ); + final double slotHeight = + isResourceEnabled + ? widget.resourceItemHeight! - cellEndPadding + : widget.height - cellEndPadding; for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { @@ -948,7 +1109,7 @@ class _AppointmentLayoutState extends State { break; } else if (startTime.isBefore(date)) { column = j; - startTime = DateTime(date.year, date.month, date.day, 0, 0, 0); + startTime = DateTime(date.year, date.month, date.day); break; } } @@ -983,17 +1144,24 @@ class _AppointmentLayoutState extends State { /// Assign the end date time value to visible end date and time value /// when the appointment end date value after the visible end date /// value. - endTime = DateTime(visibleEndDate.year, visibleEndDate.month, - visibleEndDate.day, 23, 59, 59); + endTime = DateTime( + visibleEndDate.year, + visibleEndDate.month, + visibleEndDate.day, + 23, + 59, + 59, + ); } if (column == -1 || endColumn == -1) { continue; } - int totalMinutes = (((startTime.hour - viewStartHour) * 60) + - (startTime.minute - viewStartMinutes)) - .toInt(); + int totalMinutes = + (((startTime.hour - viewStartHour) * 60) + + (startTime.minute - viewStartMinutes)) + .toInt(); final double minuteHeight = cellWidth / timeInterval; @@ -1021,9 +1189,10 @@ class _AppointmentLayoutState extends State { yPosition += appointmentView.resourceIndex * widget.resourceItemHeight!; } - totalMinutes = (((endTime.hour - viewStartHour) * 60) + - (endTime.minute - viewStartMinutes)) - .toInt(); + totalMinutes = + (((endTime.hour - viewStartHour) * 60) + + (endTime.minute - viewStartMinutes)) + .toInt(); double endXPosition = endColumn * viewWidth; timePosition = totalMinutes * minuteHeight; if (timePosition < 0) { @@ -1040,35 +1209,46 @@ class _AppointmentLayoutState extends State { null && widget.calendar.timeSlotViewSettings.minimumAppointmentDuration! > AppointmentHelper.getDifference( - appointment.actualStartTime, appointment.actualEndTime)) { + appointment.actualStartTime, + appointment.actualEndTime, + )) { final double minWidth = AppointmentHelper.getAppointmentHeightFromDuration( - widget.calendar.timeSlotViewSettings.minimumAppointmentDuration, - widget.calendar, - widget.timeIntervalHeight); + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration, + widget.calendar, + widget.timeIntervalHeight, + ); width = width > minWidth ? width : minWidth; } final Radius cornerRadius = Radius.circular( - (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); + (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1), + ); width = width > 1 ? width - 1 : 0; final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH(widget.isRTL ? xPosition - width : xPosition, yPosition, - width, appointmentHeight > 1 ? appointmentHeight - 1 : 0), - cornerRadius); + Rect.fromLTWH( + widget.isRTL ? xPosition - width : xPosition, + yPosition, + width, + 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) { + TimeSlotViewSettings settings, + CalendarView view, + ) { if (settings.timelineAppointmentHeight != -1) { return settings.timelineAppointmentHeight; } if (view == CalendarView.timelineMonth) { - return 20; + return 25; } return 60; @@ -1076,28 +1256,29 @@ class _AppointmentLayoutState extends State { } class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { - _AppointmentRenderWidget( - this.calendar, - this.view, - this.visibleDates, - this.visibleAppointments, - this.timeIntervalHeight, - this.calendarTheme, - this.isRTL, - this.appointmentHoverPosition, - this.resourceCollection, - this.resourceItemHeight, - this.textScaleFactor, - this.isMobilePlatform, - this.width, - this.height, - this.localizations, - this.appointmentCollection, - this.indexAppointments, - this.monthAppointmentCountViews, - this.weekNumberPanelWidth, - {List widgets = const []}) - : super(children: widgets); + const _AppointmentRenderWidget( + this.calendar, + this.view, + this.visibleDates, + this.visibleAppointments, + this.timeIntervalHeight, + this.calendarTheme, + this.themeData, + this.isRTL, + this.appointmentHoverPosition, + this.resourceCollection, + this.resourceItemHeight, + this.textScaleFactor, + this.isMobilePlatform, + this.width, + this.height, + this.localizations, + this.appointmentCollection, + this.indexAppointments, + this.monthAppointmentCountViews, + this.weekNumberPanelWidth, { + List widgets = const [], + }) : super(children: widgets); final SfCalendar calendar; final double weekNumberPanelWidth; @@ -1106,6 +1287,7 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { final double timeIntervalHeight; final bool isRTL; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final ValueNotifier appointmentHoverPosition; final List? resourceCollection; final double? resourceItemHeight; @@ -1122,30 +1304,34 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { @override _AppointmentRenderObject createRenderObject(BuildContext context) { return _AppointmentRenderObject( - calendar, - view, - visibleDates, - visibleAppointments, - timeIntervalHeight, - calendarTheme, - isRTL, - appointmentHoverPosition, - resourceCollection, - resourceItemHeight, - textScaleFactor, - isMobilePlatform, - width, - height, - localizations, - appointmentCollection, - indexAppointments, - monthAppointmentCountViews, - weekNumberPanelWidth); + calendar, + view, + visibleDates, + visibleAppointments, + timeIntervalHeight, + calendarTheme, + themeData, + isRTL, + appointmentHoverPosition, + resourceCollection, + resourceItemHeight, + textScaleFactor, + isMobilePlatform, + width, + height, + localizations, + appointmentCollection, + indexAppointments, + monthAppointmentCountViews, + weekNumberPanelWidth, + ); } @override void updateRenderObject( - BuildContext context, _AppointmentRenderObject renderObject) { + BuildContext context, + _AppointmentRenderObject renderObject, + ) { renderObject ..calendar = calendar ..view = view @@ -1153,6 +1339,7 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { ..visibleAppointments = visibleAppointments ..timeIntervalHeight = timeIntervalHeight ..calendarTheme = calendarTheme + ..themeData = themeData ..isRTL = isRTL ..appointmentHoverPosition = appointmentHoverPosition ..resourceCollection = resourceCollection @@ -1171,25 +1358,27 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { class _AppointmentRenderObject extends CustomCalendarRenderObject { _AppointmentRenderObject( - this._calendar, - this._view, - this._visibleDates, - this._visibleAppointments, - this._timeIntervalHeight, - this._calendarTheme, - this._isRTL, - this._appointmentHoverPosition, - this._resourceCollection, - this._resourceItemHeight, - this._textScaleFactor, - this.isMobilePlatform, - this._width, - this._height, - this._localizations, - this.appointmentCollection, - this.indexAppointments, - this.monthAppointmentCountViews, - this._weekNumberPanelWidth); + this._calendar, + this._view, + this._visibleDates, + this._visibleAppointments, + this._timeIntervalHeight, + this._calendarTheme, + this._themeData, + this._isRTL, + this._appointmentHoverPosition, + this._resourceCollection, + this._resourceItemHeight, + this._textScaleFactor, + this.isMobilePlatform, + this._width, + this._height, + this._localizations, + this.appointmentCollection, + this.indexAppointments, + this.monthAppointmentCountViews, + this._weekNumberPanelWidth, + ); List? _visibleAppointments; @@ -1326,6 +1515,18 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { markNeedsPaint(); } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + } + List _visibleDates; List get visibleDates => _visibleDates; @@ -1441,6 +1642,12 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; + + final RenderBox? child = firstChild; + if (child != null) { + return semanticsBuilder; + } + if (appointmentCollection.isEmpty) { return semanticsBuilder; } @@ -1457,14 +1664,17 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { continue; } - semanticsBuilder.add(CustomPainterSemantics( - rect: rect, - properties: SemanticsProperties( - label: CalendarViewHelper.getAppointmentSemanticsText( - appointmentView.appointment!), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: rect, + properties: SemanticsProperties( + label: CalendarViewHelper.getAppointmentSemanticsText( + appointmentView.appointment!, + ), + textDirection: TextDirection.ltr, + ), ), - )); + ); } if (view != CalendarView.month || @@ -1481,18 +1691,32 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { continue; } - semanticsBuilder.add(CustomPainterSemantics( - rect: rect, - properties: const SemanticsProperties( - label: 'More', - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: rect, + properties: const SemanticsProperties( + label: 'More', + textDirection: TextDirection.ltr, + ), ), - )); + ); } return semanticsBuilder; } + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderBox? child = firstChild; + if (child == null) { + return; + } + while (child != null) { + visitor(child); + child = childAfter(child); + } + } + @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { RenderBox? child = firstChild; @@ -1508,8 +1732,10 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { continue; } - final Offset offset = Offset(appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top); + final Offset offset = Offset( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + ); final bool isHit = result.addWithPaintOffset( offset: offset, position: position, @@ -1558,8 +1784,10 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView appointmentView = appointmentCollection[i]; @@ -1569,15 +1797,20 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { continue; } - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: appointmentView.appointmentRect!.height, maxHeight: appointmentView.appointmentRect!.height, minWidth: appointmentView.appointmentRect!.width, - maxWidth: appointmentView.appointmentRect!.width)); + maxWidth: appointmentView.appointmentRect!.width, + ), + ); final CalendarParentData childParentData = child.parentData! as CalendarParentData; - childParentData.offset = Offset(appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top); + childParentData.offset = Offset( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + ); child = childAfter(child); } @@ -1594,11 +1827,14 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]!; - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: moreRegionRect.height, maxHeight: moreRegionRect.height, minWidth: moreRegionRect.width, - maxWidth: moreRegionRect.width)); + maxWidth: moreRegionRect.width, + ), + ); final CalendarParentData childParentData = child.parentData! as CalendarParentData; @@ -1623,11 +1859,16 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } context.paintChild( - child, - Offset(appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top)); + child, + Offset( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + ), + ); _updateAppointmentHovering( - appointmentView.appointmentRect!, context.canvas); + appointmentView.appointmentRect!, + context.canvas, + ); child = childAfter(child); } @@ -1646,7 +1887,9 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]!; context.paintChild( - child, Offset(moreRegionRect.left, moreRegionRect.top)); + child, + Offset(moreRegionRect.left, moreRegionRect.top), + ); _updateAppointmentHovering(moreRegionRect, context.canvas); child = childAfter(child); @@ -1700,26 +1943,33 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } } - void _drawMonthAppointmentView(Canvas canvas, Size size, double cellWidth, - double cellHeight, Paint paint) { - double xPosition = isRTL - ? size.width - cellWidth - weekNumberPanelWidth - : weekNumberPanelWidth; + void _drawMonthAppointmentView( + Canvas canvas, + Size size, + double cellWidth, + double cellHeight, + Paint paint, + ) { final int count = visibleDates.length; - DateTime visibleStartDate = - AppointmentHelper.convertToStartTime(visibleDates[0]); - DateTime visibleEndDate = - AppointmentHelper.convertToEndTime(visibleDates[count - 1]); + DateTime visibleStartDate = AppointmentHelper.convertToStartTime( + visibleDates[0], + ); + DateTime visibleEndDate = AppointmentHelper.convertToEndTime( + visibleDates[count - 1], + ); final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates); + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates, + ); if (!showTrailingLeadingDates) { final DateTime currentMonthDate = visibleDates[count ~/ 2]; visibleStartDate = AppointmentHelper.convertToStartTime( - AppointmentHelper.getMonthStartDate(currentMonthDate)); + AppointmentHelper.getMonthStartDate(currentMonthDate), + ); visibleEndDate = AppointmentHelper.convertToEndTime( - AppointmentHelper.getMonthEndDate(currentMonthDate)); + AppointmentHelper.getMonthEndDate(currentMonthDate), + ); } final int maximumDisplayCount = @@ -1727,8 +1977,6 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { double textSize = -1; // right side padding used to add padding on appointment view right side // in month view - final bool useMobilePlatformUI = - CalendarViewHelper.isMobileLayoutUI(size.width, isMobilePlatform); for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || @@ -1743,86 +1991,92 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { appointmentView.maxPositions == maximumDisplayCount)) { final CalendarAppointment appointment = appointmentView.appointment!; final bool canAddSpanIcon = AppointmentHelper.canAddSpanIcon( - visibleDates, appointment, view, - visibleStartDate: visibleStartDate, - visibleEndDate: visibleEndDate, - showTrailingLeadingDates: showTrailingLeadingDates); + visibleDates, + appointment, + view, + visibleStartDate: visibleStartDate, + visibleEndDate: visibleEndDate, + showTrailingLeadingDates: showTrailingLeadingDates, + ); paint.color = appointment.color; - TextStyle style = calendar.appointmentTextStyle; + TextStyle style = AppointmentHelper.getAppointmentTextStyle( + calendar.appointmentTextStyle, + view, + themeData, + ); TextSpan span = TextSpan(text: appointment.subject, style: style); - _textPainter = - _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); + _textPainter = _updateTextPainter( + span, + _textPainter, + isRTL, + _textScaleFactor, + ); if (textSize == -1) { //// left and right side padding value 2 subtracted in appointment width double maxTextWidth = appointmentRect.width - 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; for (double j = style.fontSize! - 1; j > 0; j--) { - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); if (_textPainter.height >= appointmentRect.height) { style = style.copyWith(fontSize: j); span = TextSpan(text: appointment.subject, style: style); _textPainter = _updateTextPainter( - span, _textPainter, isRTL, _textScaleFactor); + span, + _textPainter, + isRTL, + _textScaleFactor, + ); } else { textSize = j + 1; break; } } + if (textSize < 0) { + textSize = 0; + } } else { span = TextSpan( - text: appointment.subject, - style: style.copyWith(fontSize: textSize)); - _textPainter = - _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); + text: appointment.subject, + style: style.copyWith(fontSize: textSize), + ); + _textPainter = _updateTextPainter( + span, + _textPainter, + isRTL, + _textScaleFactor, + ); } canvas.drawRRect(appointmentRect, paint); final bool isRecurrenceAppointment = appointment.recurrenceRule != null && - 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 = - appointmentRect.width - (isRecurrenceAppointment ? textSize : 1); - _textPainter.layout( - minWidth: 0, maxWidth: textWidth > 0 ? textWidth : 0); - xPosition = appointmentRect.left; - final double yPosition = appointmentRect.top + - ((appointmentRect.height - _textPainter.height) / 2); - if (isRTL && !canAddSpanIcon) { - xPosition += appointmentRect.width - _textPainter.width - 2; - } - - if (canAddSpanIcon) { - xPosition += (appointmentRect.width - _textPainter.width) / 2; - } - - _textPainter.paint( - canvas, Offset(xPosition + (isRTL ? 0 : 2), yPosition)); - - if (isRecurrenceAppointment || appointment.recurrenceId != null) { - _drawRecurrenceIconForMonth( - canvas, - size, - style, - textSize, - appointmentRect, - appointmentRect.tlRadius, - paint, - useMobilePlatformUI, - isRecurrenceAppointment); - } + appointment.recurrenceRule!.isNotEmpty; + final double iconTextSize = _getTextSize( + appointmentRect, + _textPainter.textScaler.scale(textSize), + ); + const double iconPadding = 2; + //// Padding 4 is left and right 2 padding. + final double iconSize = iconTextSize + (2 * iconPadding); + final double recurrenceIconSize = + isRecurrenceAppointment || appointment.recurrenceId != null + ? iconSize + : 0; + double forwardSpanIconSize = 0; + double backwardSpanIconSize = 0; if (canAddSpanIcon) { final int appStartIndex = MonthAppointmentHelper.getDateIndex( - appointment.exactStartTime, visibleDates); + appointment.exactStartTime, + visibleDates, + ); final int appEndIndex = MonthAppointmentHelper.getDateIndex( - appointment.exactEndTime, visibleDates); + appointment.exactEndTime, + visibleDates, + ); if (appStartIndex == appointmentView.startIndex && appEndIndex == appointmentView.endIndex) { continue; @@ -1830,33 +2084,28 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { if (appStartIndex != appointmentView.startIndex && appEndIndex != appointmentView.endIndex) { - _drawForwardSpanIconForMonth( - canvas, - size, - style, - textSize, - appointmentRect, - appointmentRect.tlRadius, - paint, - useMobilePlatformUI); - _drawBackwardSpanIconForMonth(canvas, style, textSize, - appointmentRect, appointmentRect.tlRadius, paint); + forwardSpanIconSize = iconSize; + backwardSpanIconSize = iconSize; } else if (appEndIndex != appointmentView.endIndex) { - _drawForwardSpanIconForMonth( - canvas, - size, - style, - textSize, - appointmentRect, - appointmentRect.tlRadius, - paint, - useMobilePlatformUI); + forwardSpanIconSize = iconSize; } else { - _drawBackwardSpanIconForMonth(canvas, style, textSize, - appointmentRect, appointmentRect.tlRadius, paint); + backwardSpanIconSize = iconSize; } } + const double textPadding = 1; + _drawSingleLineAppointmentView( + canvas, + appointmentRect, + textPadding, + style, + textSize, + isRecurrenceAppointment, + recurrenceIconSize, + forwardSpanIconSize, + backwardSpanIconSize, + paint, + ); _updateAppointmentHovering(appointmentRect, canvas); } } @@ -1875,16 +2124,20 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { radius = 3; } } - double startXPosition = isRTL - ? moreRegionRect.right - startPadding - : moreRegionRect.left + startPadding; + double startXPosition = + isRTL + ? moreRegionRect.right - startPadding + : moreRegionRect.left + startPadding; paint.color = Colors.grey[600]!; for (int j = 0; j < 3; j++) { canvas.drawCircle( - Offset(startXPosition, - moreRegionRect.top + (moreRegionRect.height / 2)), - radius, - paint); + Offset( + startXPosition, + moreRegionRect.top + (moreRegionRect.height / 2), + ), + radius, + paint, + ); if (isRTL) { startXPosition -= padding + (2 * radius); } else { @@ -1896,93 +2149,210 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } } - void _drawForwardSpanIconForMonth( - Canvas canvas, - Size size, - TextStyle style, - double textSize, - RRect rect, - Radius cornerRadius, - Paint paint, - bool useMobilePlatformUI) { - final TextSpan icon = - AppointmentHelper.getSpanIcon(style.color!, textSize, !isRTL); - _textPainter.text = icon; + void _drawSingleLineAppointmentView( + Canvas canvas, + RRect appointmentRect, + double textPadding, + TextStyle style, + double textSize, + bool isRecurrenceAppointment, + double recurrenceIconSize, + double forwardSpanIconSize, + double backwardSpanIconSize, + Paint paint, + ) { + final double totalIconsWidth = + recurrenceIconSize + forwardSpanIconSize + backwardSpanIconSize; + final double textWidth = appointmentRect.width - totalIconsWidth; _textPainter.layout( - minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); - + maxWidth: + textWidth - (2 * textPadding) > 0 ? textWidth - (2 * textPadding) : 0, + ); final double yPosition = - AppointmentHelper.getYPositionForSpanIcon(icon, _textPainter, rect); - final double rightPadding = useMobilePlatformUI ? 0 : 2; - final double xPosition = isRTL - ? rect.left + rightPadding - : rect.right - _textPainter.width - rightPadding; - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(xPosition, rect.top, xPosition + _textPainter.width, - rect.bottom), - cornerRadius), - paint); + appointmentRect.top + + ((appointmentRect.height - _textPainter.height) / 2); + final double xPosition = + isRTL + ? appointmentRect.right - + _textPainter.width - + backwardSpanIconSize - + textPadding + : appointmentRect.left + backwardSpanIconSize + textPadding; + _textPainter.paint(canvas, Offset(xPosition, yPosition)); + + if (backwardSpanIconSize != 0) { + _drawBackwardSpanIconForMonth( + canvas, + style, + textSize, + appointmentRect, + backwardSpanIconSize, + appointmentRect.tlRadius, + paint, + ); + } + + if (recurrenceIconSize != 0) { + _drawRecurrenceIconForMonth( + canvas, + style, + textSize, + appointmentRect, + appointmentRect.tlRadius, + paint, + isRecurrenceAppointment, + recurrenceIconSize, + forwardSpanIconSize, + ); + } + + if (forwardSpanIconSize != 0) { + _drawForwardSpanIconForMonth( + canvas, + style, + textSize, + appointmentRect, + forwardSpanIconSize, + appointmentRect.tlRadius, + paint, + ); + } } - void _drawBackwardSpanIconForMonth(Canvas canvas, TextStyle style, - double textSize, RRect rect, Radius cornerRadius, Paint paint) { - final TextSpan icon = - AppointmentHelper.getSpanIcon(style.color!, textSize, isRTL); + void _drawForwardSpanIconForMonth( + Canvas canvas, + TextStyle style, + double textSize, + RRect rect, + double iconSize, + Radius cornerRadius, + Paint paint, + ) { + final TextSpan icon = AppointmentHelper.getSpanIcon( + style.color!, + textSize, + !isRTL, + ); _textPainter.text = icon; - _textPainter.layout( - minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); + _textPainter.layout(maxWidth: rect.width > 0 ? rect.width : 0); + + final double yPosition = AppointmentHelper.getYPositionForSpanIcon( + icon, + _textPainter, + rect, + ); + final double xPosition = isRTL ? rect.left : rect.right - iconSize; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTRB(xPosition, rect.top, xPosition + iconSize, rect.bottom), + cornerRadius, + ), + paint, + ); + double iconPadding = (iconSize - _textPainter.width) / 2; + if (iconPadding < 0) { + iconPadding = 0; + } + + _textPainter.paint(canvas, Offset(xPosition + iconPadding, yPosition)); + } + + void _drawBackwardSpanIconForMonth( + Canvas canvas, + TextStyle style, + double textSize, + RRect rect, + double iconSize, + Radius cornerRadius, + Paint paint, + ) { + final TextSpan icon = AppointmentHelper.getSpanIcon( + style.color!, + textSize, + isRTL, + ); + _textPainter.text = icon; + _textPainter.layout(maxWidth: rect.width > 0 ? rect.width : 0); + + final double yPosition = AppointmentHelper.getYPositionForSpanIcon( + icon, + _textPainter, + rect, + ); + final double xPosition = isRTL ? rect.right - iconSize : rect.left; + double iconPadding = (iconSize - _textPainter.width) / 2; + if (iconPadding < 0) { + iconPadding = 0; + } - final double yPosition = - AppointmentHelper.getYPositionForSpanIcon(icon, _textPainter, rect); - const double rightPadding = 2; - final double xPosition = - isRTL ? rect.right - textSize - rightPadding : rect.left + rightPadding; canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - xPosition, rect.top, xPosition + textSize, rect.bottom), - cornerRadius), - paint); - _textPainter.paint(canvas, Offset(xPosition, yPosition)); + RRect.fromRectAndRadius( + Rect.fromLTRB(xPosition, rect.top, xPosition + iconSize, rect.bottom), + cornerRadius, + ), + paint, + ); + _textPainter.paint(canvas, Offset(xPosition + iconPadding, yPosition)); } void _drawRecurrenceIconForMonth( - Canvas canvas, - Size size, - TextStyle style, - double textSize, - RRect rect, - Radius cornerRadius, - Paint paint, - bool useMobilePlatformUI, - bool isRecurrenceAppointment) { + Canvas canvas, + TextStyle style, + double textSize, + RRect rect, + Radius cornerRadius, + Paint paint, + bool isRecurrenceAppointment, + double iconSize, + double forwardSpanIconSize, + ) { final TextSpan icon = AppointmentHelper.getRecurrenceIcon( - style.color!, textSize, isRecurrenceAppointment); + style.color!, + textSize, + isRecurrenceAppointment, + ); _textPainter.text = icon; - _textPainter.layout( - minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); + _textPainter.layout(maxWidth: rect.width > 0 ? rect.width : 0); final double yPosition = rect.top + ((rect.height - _textPainter.height) / 2); - final double rightPadding = useMobilePlatformUI ? 0 : 2; - final double recurrenceStartPosition = isRTL - ? rect.left + rightPadding - : rect.right - _textPainter.width - rightPadding; + final double recurrenceStartPosition = + isRTL + ? rect.left + forwardSpanIconSize + : rect.right - iconSize - forwardSpanIconSize; canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(recurrenceStartPosition, yPosition, - recurrenceStartPosition + _textPainter.width, rect.bottom), - cornerRadius), - paint); - _textPainter.paint(canvas, Offset(recurrenceStartPosition, yPosition)); + RRect.fromRectAndRadius( + Rect.fromLTRB( + recurrenceStartPosition, + yPosition, + recurrenceStartPosition + iconSize, + rect.bottom, + ), + cornerRadius, + ), + paint, + ); + double iconPadding = (iconSize - _textPainter.width) / 2; + if (iconPadding < 0) { + iconPadding = 0; + } + + _textPainter.paint( + canvas, + Offset(recurrenceStartPosition + iconPadding, yPosition), + ); } void _drawMonthAppointmentIndicator( - Canvas canvas, double cellWidth, double cellHeight, Paint paint) { - double xPosition = isRTL - ? size.width - cellWidth - weekNumberPanelWidth - : weekNumberPanelWidth; + Canvas canvas, + double cellWidth, + double cellHeight, + Paint paint, + ) { + double xPosition = + isRTL + ? size.width - cellWidth - weekNumberPanelWidth + : weekNumberPanelWidth; double yPosition = 0; const double radius = 2.5; const double diameter = radius * 2; @@ -1992,8 +2362,9 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { final int currentMonth = visibleDates[visibleDatesCount ~/ 2].month; final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates); + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates, + ); for (int i = 0; i < visibleDatesCount; i++) { final DateTime currentVisibleDate = visibleDates[i]; @@ -2003,22 +2374,33 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } final List appointmentLists = - _getSpecificDateVisibleAppointment(currentVisibleDate); + AppointmentHelper.getSpecificDateVisibleAppointment( + currentVisibleDate, + visibleAppointments, + ); appointmentLists.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); appointmentLists.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isAllDay, app2.isAllDay)); + (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 - : calendar.monthViewSettings.appointmentDisplayCount; + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, + app2.isSpanned, + ), + ); + final int count = + appointmentLists.length <= + calendar.monthViewSettings.appointmentDisplayCount + ? appointmentLists.length + : calendar.monthViewSettings.appointmentDisplayCount; const double indicatorPadding = 2; final double indicatorWidth = count * diameter + (count - 1) * indicatorPadding; @@ -2042,7 +2424,10 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { for (int j = 0; j < count; j++) { paint.color = appointmentLists[j].color; canvas.drawCircle( - Offset(xPosition, yPosition - bottomPadding), radius, paint); + Offset(xPosition, yPosition - bottomPadding), + radius, + paint, + ); xPosition += diameter + indicatorPadding; if (startXPosition + cellWidth < xPosition + diameter) { break; @@ -2051,29 +2436,6 @@ 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; if (hoverPosition == null) { @@ -2084,11 +2446,11 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { rect.right > hoverPosition.dx && rect.top < hoverPosition.dy && rect.bottom > hoverPosition.dy) { - _appointmentPainter.color = - calendarTheme.selectionBorderColor!.withOpacity(0.4); + _appointmentPainter.color = calendarTheme.selectionBorderColor! + .withValues(alpha: 0.4); _appointmentPainter.strokeWidth = 2; _appointmentPainter.style = PaintingStyle.stroke; - canvas.drawRect(rect.outerRect, _appointmentPainter); + canvas.drawRRect(rect, _appointmentPainter); _appointmentPainter.style = PaintingStyle.fill; } } @@ -2096,8 +2458,10 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { void _drawDayAppointments(Canvas canvas, Size size, Paint paint) { const int textStartPadding = 3; - final bool useMobilePlatformUI = - CalendarViewHelper.isMobileLayoutUI(size.width, isMobilePlatform); + final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + size.width, + isMobilePlatform, + ); for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || @@ -2113,31 +2477,54 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { double xPosition = appointmentRect.left; double yPosition = appointmentRect.top; - final bool canAddSpanIcon = - AppointmentHelper.canAddSpanIcon(visibleDates, appointment, view); + final bool canAddSpanIcon = AppointmentHelper.canAddSpanIcon( + visibleDates, + appointment, + view, + ); bool canAddForwardIcon = false; + final TextStyle appointmentTextStyle = + AppointmentHelper.getAppointmentTextStyle( + calendar.appointmentTextStyle, + view, + themeData, + ); if (canAddSpanIcon) { if (CalendarViewHelper.isSameTimeSlot( - appointment.exactStartTime, appointment.actualStartTime) && + appointment.exactStartTime, + appointment.actualStartTime, + ) && !CalendarViewHelper.isSameTimeSlot( - appointment.exactEndTime, appointment.actualEndTime)) { + appointment.exactEndTime, + appointment.actualEndTime, + )) { canAddForwardIcon = true; } else if (!CalendarViewHelper.isSameTimeSlot( - appointment.exactStartTime, appointment.actualStartTime) && + appointment.exactStartTime, + appointment.actualStartTime, + ) && CalendarViewHelper.isSameTimeSlot( - appointment.exactEndTime, appointment.actualEndTime)) { - yPosition += _getTextSize(appointmentRect, - calendar.appointmentTextStyle.fontSize! * textScaleFactor); + appointment.exactEndTime, + appointment.actualEndTime, + )) { + yPosition += _getTextSize( + appointmentRect, + appointmentTextStyle.fontSize! * textScaleFactor, + ); } } final TextSpan span = TextSpan( text: appointment.subject, - style: calendar.appointmentTextStyle, + style: appointmentTextStyle, ); - _textPainter = - _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); + _textPainter = _updateTextPainter( + span, + _textPainter, + isRTL, + _textScaleFactor, + ); final double totalHeight = appointmentRect.height - textStartPadding; _updatePainterMaxLines(totalHeight); @@ -2145,7 +2532,7 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { //// left and right side padding value 2 subtracted in appointment width double maxTextWidth = appointmentRect.width - textStartPadding; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); /// minIntrinsicWidth property in text painter used to get the /// minimum text width of the text. @@ -2157,9 +2544,7 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { final double textWidth = appointmentRect.width - textStartPadding; if (textWidth < _textPainter.minIntrinsicWidth && textWidth < _textPainter.width && - textWidth < - (calendar.appointmentTextStyle.fontSize ?? 15) * - textScaleFactor) { + textWidth < (appointmentTextStyle.fontSize ?? 15) * textScaleFactor) { _updateAppointmentHovering(appointmentRect, canvas); continue; @@ -2177,55 +2562,92 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } _textPainter.paint( - canvas, - Offset(xPosition + (isRTL ? 0 : textStartPadding), - yPosition + textStartPadding)); - final bool isRecurrenceAppointment = appointment.recurrenceRule != null && + canvas, + Offset( + xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding, + ), + ); + final bool isRecurrenceAppointment = + appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty; - if (isRecurrenceAppointment || appointment.recurrenceId != null) { - _addRecurrenceIconForDay( - canvas, - size, - appointmentRect, - appointmentRect.width, - textStartPadding, - paint, - appointmentRect.tlRadius, - useMobilePlatformUI, - isRecurrenceAppointment); - } if (canAddSpanIcon) { if (canAddForwardIcon) { _addForwardSpanIconForDay( - canvas, appointmentRect, size, appointmentRect.tlRadius, paint); + canvas, + appointmentRect, + size, + appointmentRect.tlRadius, + paint, + appointmentTextStyle, + ); } else { _addBackwardSpanIconForDay( - canvas, appointmentRect, size, appointmentRect.tlRadius, paint); + canvas, + appointmentRect, + size, + appointmentRect.tlRadius, + paint, + appointmentTextStyle, + ); } } + if (isRecurrenceAppointment || appointment.recurrenceId != null) { + _addRecurrenceIconForDay( + canvas, + size, + appointmentRect, + appointmentRect.width, + textStartPadding, + paint, + appointmentRect.tlRadius, + useMobilePlatformUI, + isRecurrenceAppointment, + appointmentTextStyle, + ); + } + _updateAppointmentHovering(appointmentRect, canvas); } } void _addBackwardSpanIconForDay( - Canvas canvas, RRect rect, Size size, Radius cornerRadius, Paint paint) { + Canvas canvas, + RRect rect, + Size size, + Radius cornerRadius, + Paint paint, + TextStyle appointmentTextStyle, + ) { canvas.save(); const double bottomPadding = 2; - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final double textSize = _getTextSize(rect, 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); + appointmentTextStyle.color!, + textSize, + false, + ); + _textPainter = _updateTextPainter( + icon, + _textPainter, + isRTL, + _textScaleFactor, + ); + _textPainter.layout(maxWidth: rect.width); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - rect.left, rect.top, rect.right, rect.top + _textPainter.width), - cornerRadius), - paint); + RRect.fromRectAndRadius( + Rect.fromLTRB( + rect.left, + rect.top, + rect.right, + rect.top + _textPainter.width, + ), + cornerRadius, + ), + paint, + ); final double xPosition = _getXPositionForSpanIconForDay(icon, rect); @@ -2252,22 +2674,40 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } void _addForwardSpanIconForDay( - Canvas canvas, RRect rect, Size size, Radius cornerRadius, Paint paint) { + Canvas canvas, + RRect rect, + Size size, + Radius cornerRadius, + Paint paint, + TextStyle appointmentTextStyle, + ) { canvas.save(); const double bottomPadding = 2; - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final double textSize = _getTextSize(rect, 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); + appointmentTextStyle.color!, + textSize, + true, + ); + _textPainter = _updateTextPainter( + icon, + _textPainter, + isRTL, + _textScaleFactor, + ); + _textPainter.layout(maxWidth: rect.width); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(rect.left, rect.bottom - _textPainter.width, - rect.right, rect.bottom), - cornerRadius), - paint); + RRect.fromRectAndRadius( + Rect.fromLTRB( + rect.left, + rect.bottom - _textPainter.width, + rect.right, + rect.bottom, + ), + cornerRadius, + ), + paint, + ); final double xPosition = _getXPositionForSpanIconForDay(icon, rect); final double yPosition = @@ -2292,51 +2732,62 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } void _addRecurrenceIconForDay( - Canvas canvas, - Size size, - RRect rect, - double appointmentWidth, - int textPadding, - Paint paint, - Radius cornerRadius, - bool useMobilePlatformUI, - bool isRecurrenceAppointment) { + Canvas canvas, + Size size, + RRect rect, + double appointmentWidth, + int textPadding, + Paint paint, + Radius cornerRadius, + bool useMobilePlatformUI, + bool isRecurrenceAppointment, + TextStyle appointmentTextStyle, + ) { final double xPadding = useMobilePlatformUI ? 1 : 2; const double bottomPadding = 2; - double textSize = calendar.appointmentTextStyle.fontSize!; + double textSize = appointmentTextStyle.fontSize!; if (rect.width < textSize || rect.height < textSize) { textSize = rect.width > rect.height ? rect.height : rect.width; } final TextSpan icon = AppointmentHelper.getRecurrenceIcon( - calendar.appointmentTextStyle.color!, - textSize, - isRecurrenceAppointment); + appointmentTextStyle.color!, + textSize, + isRecurrenceAppointment, + ); _textPainter.text = icon; double maxTextWidth = appointmentWidth - textPadding - 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - isRTL - ? rect.left + textSize + xPadding - : rect.right - textSize - xPadding, - rect.bottom - bottomPadding - textSize, - isRTL ? rect.left : rect.right, - rect.bottom), - cornerRadius), - paint); + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL + ? rect.left + textSize + xPadding + : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + isRTL ? rect.left : rect.right, + rect.bottom, + ), + cornerRadius, + ), + paint, + ); _textPainter.paint( - canvas, - Offset(isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, - rect.bottom - bottomPadding - textSize)); + canvas, + Offset( + isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + ), + ); } void _drawTimelineAppointments(Canvas canvas, Size size, Paint paint) { - const int textStartPadding = 3; - final bool useMobilePlatformUI = - CalendarViewHelper.isMobileLayoutUI(size.width, isMobilePlatform); + const double textStartPadding = 2; + final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + size.width, + isMobilePlatform, + ); for (int i = 0; i < appointmentCollection.length; i++) { final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || @@ -2349,57 +2800,78 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { paint.color = appointment.color; final RRect appointmentRect = appointmentView.appointmentRect!; canvas.drawRRect(appointmentRect, paint); - final bool canAddSpanIcon = - AppointmentHelper.canAddSpanIcon(visibleDates, appointment, view); - bool canAddForwardIcon = false; - bool canAddBackwardIcon = false; - - double xPosition = isRTL ? appointmentRect.right : appointmentRect.left; - double maxWidth = appointmentRect.width - textStartPadding; - maxWidth = maxWidth > 0 ? maxWidth : 0; + final bool canAddSpanIcon = AppointmentHelper.canAddSpanIcon( + visibleDates, + appointment, + view, + ); + double forwardSpanIconSize = 0; + double backwardSpanIconSize = 0; + const double iconPadding = 2; + final TextStyle appointmentTextStyle = + AppointmentHelper.getAppointmentTextStyle( + calendar.appointmentTextStyle, + view, + themeData, + ); + final double iconSize = + _getTextSize( + appointmentRect, + appointmentTextStyle.fontSize! * textScaleFactor, + ) + + (2 * iconPadding); if (canAddSpanIcon) { final DateTime appStartTime = appointment.exactStartTime; final DateTime appEndTime = appointment.exactEndTime; - final DateTime viewStartDate = - AppointmentHelper.convertToStartTime(visibleDates[0]); + 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; + visibleDates[visibleDates.length - 1], + ); if (AppointmentHelper.canAddForwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate)) { - canAddForwardIcon = true; - iconSize = null; + appStartTime, + appEndTime, + viewStartDate, + viewEndDate, + )) { + forwardSpanIconSize = iconSize; } else if (AppointmentHelper.canAddBackwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate)) { - canAddBackwardIcon = true; + appStartTime, + appEndTime, + viewStartDate, + viewEndDate, + )) { + backwardSpanIconSize = iconSize; } else { - canAddForwardIcon = true; - canAddBackwardIcon = true; - } - - if (iconSize != null) { - if (isRTL) { - xPosition -= iconSize; - } else { - xPosition += iconSize; - } + forwardSpanIconSize = iconSize; + backwardSpanIconSize = iconSize; } } + double maxWidth = + appointmentRect.width - + (2 * textStartPadding) - + backwardSpanIconSize - + forwardSpanIconSize; + maxWidth = maxWidth > 0 ? maxWidth : 0; final TextSpan span = TextSpan( text: _getTimelineAppointmentText(appointment, canAddSpanIcon), - style: calendar.appointmentTextStyle, + style: appointmentTextStyle, ); - _textPainter = - _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); - final double totalHeight = appointmentRect.height - textStartPadding - 2; + _textPainter = _updateTextPainter( + span, + _textPainter, + isRTL, + _textScaleFactor, + ); + final double totalHeight = + appointmentRect.height - (2 * textStartPadding); _updatePainterMaxLines(totalHeight); - /// In RTL, when the text wraps into multiple line the tine width is + /// In RTL, when the text wraps into multiple line the line width is /// smaller than the expected when we use the /// 'TextWidthBasis.longestLine]` which renders the subject text out of /// the appointment rect, hence to overcome this we have added checked @@ -2409,52 +2881,85 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } //// left and right side padding value 2 subtracted in appointment width - _textPainter.layout(minWidth: 0, maxWidth: maxWidth); + _textPainter.layout(maxWidth: maxWidth); if ((_textPainter.maxLines == null || _textPainter.maxLines == 1) && _textPainter.height > totalHeight) { _updateAppointmentHovering(appointmentRect, canvas); continue; } - if (isRTL) { - if (canAddSpanIcon) { - xPosition -= textStartPadding; - } - - xPosition -= _textPainter.width + textStartPadding + 2; - } + final double xPosition = + isRTL + ? appointmentRect.right - + backwardSpanIconSize - + _textPainter.width - + textStartPadding + : appointmentRect.left + backwardSpanIconSize + textStartPadding; + final int maxLines = + (appointmentRect.height / _textPainter.preferredLineHeight).floor(); + final bool isRecurrenceAppointment = + appointment.recurrenceRule != null && + appointment.recurrenceRule!.isNotEmpty; - _textPainter.paint( + if (maxLines == 1) { + _drawSingleLineAppointmentView( canvas, - Offset(xPosition + textStartPadding, - appointmentRect.top + textStartPadding)); - final bool isRecurrenceAppointment = appointment.recurrenceRule != null && - appointment.recurrenceRule!.isNotEmpty; + appointmentRect, + textStartPadding, + appointmentTextStyle, + appointmentTextStyle.fontSize!, + isRecurrenceAppointment, + isRecurrenceAppointment || appointment.recurrenceId != null + ? iconSize + : 0, + forwardSpanIconSize, + backwardSpanIconSize, + paint, + ); + } else { + _textPainter.paint( + canvas, + Offset(xPosition, appointmentRect.top + textStartPadding), + ); - if (isRecurrenceAppointment || appointment.recurrenceId != null) { - _addRecurrenceIconForTimeline( + if (forwardSpanIconSize != 0) { + _addForwardSpanIconForTimeline( canvas, size, appointmentRect, maxWidth, appointmentRect.tlRadius, paint, - useMobilePlatformUI, - isRecurrenceAppointment); - } + isMobilePlatform, + appointmentTextStyle, + ); + } - if (canAddSpanIcon) { - if (canAddForwardIcon && canAddBackwardIcon) { - _addForwardSpanIconForTimeline(canvas, size, appointmentRect, - maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); - _addBackwardSpanIconForTimeline(canvas, size, appointmentRect, - maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); - } else if (canAddForwardIcon) { - _addForwardSpanIconForTimeline(canvas, size, appointmentRect, - maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); - } else { - _addBackwardSpanIconForTimeline(canvas, size, appointmentRect, - maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); + if (backwardSpanIconSize != 0) { + _addBackwardSpanIconForTimeline( + canvas, + size, + appointmentRect, + maxWidth, + appointmentRect.tlRadius, + paint, + isMobilePlatform, + appointmentTextStyle, + ); + } + + if (isRecurrenceAppointment || appointment.recurrenceId != null) { + _addRecurrenceIconForTimeline( + canvas, + size, + appointmentRect, + maxWidth, + appointmentRect.tlRadius, + paint, + useMobilePlatformUI, + isRecurrenceAppointment, + appointmentTextStyle, + ); } } @@ -2467,13 +2972,18 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { /// for timeline day view we display the current date, and total dates of the /// spanning appointment. String _getTimelineAppointmentText( - CalendarAppointment appointment, bool canAddSpanIcon) { + CalendarAppointment appointment, + bool canAddSpanIcon, + ) { if (view != CalendarView.timelineDay || !canAddSpanIcon) { return appointment.subject; } return AppointmentHelper.getSpanAppointmentText( - appointment, visibleDates[0], _localizations); + appointment, + visibleDates[0], + _localizations, + ); } double _getTextSize(RRect rect, double textSize) { @@ -2485,7 +2995,11 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { } double _getYPositionForSpanIconInTimeline( - TextSpan icon, RRect rect, double xPadding, bool isMobilePlatform) { + TextSpan icon, + RRect rect, + double xPadding, + bool isMobilePlatform, + ) { /// 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 2 used since the space on top and bottom of icon is not even, @@ -2493,117 +3007,165 @@ class _AppointmentRenderObject extends CustomCalendarRenderObject { /// device. final double iconStartPosition = (_textPainter.height - (icon.style!.fontSize! * textScaleFactor) / 2) / - 2; + 2; return rect.top - iconStartPosition + (isMobilePlatform ? 1 : xPadding); } void _addForwardSpanIconForTimeline( - Canvas canvas, - Size size, - RRect rect, - double maxWidth, - Radius cornerRadius, - Paint paint, - bool isMobilePlatform) { + Canvas canvas, + Size size, + RRect rect, + double maxWidth, + Radius cornerRadius, + Paint paint, + bool isMobilePlatform, + TextStyle appointmentTextStyle, + ) { const double xPadding = 2; - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); - + final double textSize = _getTextSize(rect, appointmentTextStyle.fontSize!); final TextSpan icon = AppointmentHelper.getSpanIcon( - calendar.appointmentTextStyle.color!, textSize, !isRTL); - _textPainter = - _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); - _textPainter.layout(minWidth: 0, maxWidth: maxWidth); - final double xPosition = isRTL - ? rect.left + xPadding - : rect.right - _textPainter.width - xPadding; + appointmentTextStyle.color!, + textSize, + !isRTL, + ); + _textPainter = _updateTextPainter( + icon, + _textPainter, + isRTL, + _textScaleFactor, + ); + _textPainter.layout(maxWidth: maxWidth); + final double xPosition = + isRTL + ? rect.left + xPadding + : rect.right - _textPainter.width - xPadding; final double yPosition = _getYPositionForSpanIconInTimeline( - icon, rect, xPadding, isMobilePlatform); + icon, + rect, + xPadding, + isMobilePlatform, + ); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(xPosition, rect.top + 1, - isRTL ? rect.left : rect.right, rect.bottom), - cornerRadius), - paint); + RRect.fromRectAndRadius( + Rect.fromLTRB( + xPosition, + rect.top + 1, + isRTL ? rect.left : rect.right, + rect.bottom, + ), + cornerRadius, + ), + paint, + ); _textPainter.paint(canvas, Offset(xPosition, yPosition)); } void _addBackwardSpanIconForTimeline( - Canvas canvas, - Size size, - RRect rect, - double maxWidth, - Radius cornerRadius, - Paint paint, - bool isMobilePlatform) { + Canvas canvas, + Size size, + RRect rect, + double maxWidth, + Radius cornerRadius, + Paint paint, + bool isMobilePlatform, + TextStyle appointmentTextStyle, + ) { const double xPadding = 2; - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); - + final double textSize = _getTextSize(rect, appointmentTextStyle.fontSize!); final TextSpan icon = AppointmentHelper.getSpanIcon( - calendar.appointmentTextStyle.color!, textSize, isRTL); - _textPainter = - _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); - _textPainter.layout(minWidth: 0, maxWidth: maxWidth); - final double xPosition = isRTL - ? rect.right - _textPainter.width - xPadding - : rect.left + xPadding; + appointmentTextStyle.color!, + textSize, + isRTL, + ); + _textPainter = _updateTextPainter( + icon, + _textPainter, + isRTL, + _textScaleFactor, + ); + _textPainter.layout(maxWidth: maxWidth); + final double xPosition = + isRTL + ? rect.right - _textPainter.width - xPadding + : rect.left + xPadding; final double yPosition = _getYPositionForSpanIconInTimeline( - icon, rect, xPadding, isMobilePlatform); + icon, + rect, + xPadding, + isMobilePlatform, + ); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(xPosition, rect.top + 1, - isRTL ? rect.right : rect.left, rect.bottom), - cornerRadius), - paint); + RRect.fromRectAndRadius( + Rect.fromLTRB( + xPosition, + rect.top + 1, + isRTL ? rect.right : rect.left, + rect.bottom, + ), + cornerRadius, + ), + paint, + ); _textPainter.paint(canvas, Offset(xPosition, yPosition)); } void _addRecurrenceIconForTimeline( - Canvas canvas, - Size size, - RRect rect, - double maxWidth, - Radius cornerRadius, - Paint paint, - bool useMobilePlatformUI, - bool isRecurrenceAppointment) { + Canvas canvas, + Size size, + RRect rect, + double maxWidth, + Radius cornerRadius, + Paint paint, + bool useMobilePlatformUI, + bool isRecurrenceAppointment, + TextStyle appointmentTextStyle, + ) { final double xPadding = useMobilePlatformUI ? 1 : 2; const double bottomPadding = 2; - final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final double textSize = _getTextSize(rect, appointmentTextStyle.fontSize!); final TextSpan icon = AppointmentHelper.getRecurrenceIcon( - calendar.appointmentTextStyle.color!, - textSize, - isRecurrenceAppointment); + appointmentTextStyle.color!, + textSize, + isRecurrenceAppointment, + ); _textPainter.text = icon; - _textPainter.layout(minWidth: 0, maxWidth: maxWidth); + _textPainter.layout(maxWidth: maxWidth); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, - rect.bottom - bottomPadding - textSize, - isRTL ? rect.left : rect.right, - rect.bottom), - cornerRadius), - paint); + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + isRTL ? rect.left : rect.right, + rect.bottom, + ), + cornerRadius, + ), + paint, + ); _textPainter.paint( - canvas, - Offset(isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, - rect.bottom - bottomPadding - textSize)); + canvas, + Offset( + isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + ), + ); } } -TextPainter _updateTextPainter(TextSpan span, TextPainter textPainter, - bool isRTL, double textScaleFactor) { +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; + textPainter.textScaler = TextScaler.linear(textScaleFactor); return textPainter; } 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 bb1e3ad27..759ad3968 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 @@ -13,10 +13,13 @@ import 'date_time_engine.dart'; /// All day appointment height const double kAllDayAppointmentHeight = 20; +/// Timezone loaded value. +bool timeZoneLoaded = false; + /// Signature for callback that used to get and update the calendar /// state details. -typedef UpdateCalendarState = void Function( - UpdateCalendarStateDetails _updateCalendarStateDetails); +typedef UpdateCalendarState = + void Function(UpdateCalendarStateDetails updateCalendarStateDetails); //// Extra small devices (phones, 600px and down) //// @media only screen and (max-width: 600px) {...} @@ -70,12 +73,109 @@ class CalendarViewHelper { return false; } + /// Return the text direction for the text widget based on locale. + static TextDirection getTextDirectionBasedOnLocale(String locale) { + if (locale == 'ar') { + return TextDirection.rtl; + } + + return TextDirection.ltr; + } + + /// Return format string list from format string.s + static List getListFromString(String format) { + final List timeFormatStrings = []; + if (format.isNotEmpty) { + String currentString = format[0]; + for (int i = 1; i < format.length; i++) { + final String value = format[i]; + if (value == format[i - 1]) { + currentString += value; + } else { + timeFormatStrings.add(currentString); + currentString = value; + } + } + + timeFormatStrings.add(currentString); + } + + return timeFormatStrings; + } + + /// Return the localized date value based on locale and string format list. + static String getLocalizedString( + DateTime date, + List stringFormatList, + String locale, + ) { + String localizedString = ''; + for (int i = 0; i < stringFormatList.length; i++) { + final String value = stringFormatList[i]; + final String valueString = getFormattedString(date, value, locale); + localizedString = '$localizedString$valueString'; + } + + return localizedString; + } + + /// Return the localized date value based on locale and string format. + static String getFormattedString( + DateTime date, + String currentStringFormat, + String locale, + ) { + String formattedString = currentStringFormat; + if (formattedString.isEmpty) { + return formattedString; + } + + final String currentFormatText = currentStringFormat[0]; + bool isAlphabetCharacter(String character) { + final int codeUnit = character.codeUnitAt(0); + return (codeUnit >= 65 && codeUnit <= 90) || + (codeUnit >= 97 && codeUnit <= 122); + } + + if (!isAlphabetCharacter(currentFormatText)) { + return currentStringFormat; + } + + /// Format specified on Date Format class + /// https://api.flutter.dev/flutter/intl/DateFormat-class.html. + if (currentFormatText == 'h' || + currentFormatText == 'H' || + currentFormatText == 'd' || + currentFormatText == 'D' || + currentFormatText == 'y' || + currentFormatText == 'c' || + currentFormatText == 'm' || + currentFormatText == 's' || + currentFormatText == 'S' || + currentFormatText == 'k' || + currentFormatText == 'K' || + currentStringFormat == 'MM' || + currentStringFormat == 'M') { + formattedString = DateFormat(currentStringFormat).format(date); + } else { + formattedString = DateFormat(currentStringFormat, locale).format(date); + } + + return formattedString; + } + /// 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) { + static bool isCurrentMonthDate( + int weekRowCount, + bool showLeadingTrailingDates, + int currentMonth, + DateTime date, + ) { if (isLeadingAndTrailingDatesVisible( - weekRowCount, showLeadingTrailingDates)) { + weekRowCount, + showLeadingTrailingDates, + )) { return true; } @@ -88,13 +188,17 @@ class CalendarViewHelper { /// Check the leading and trailing dates visible or not. static bool isLeadingAndTrailingDatesVisible( - int weekRowCount, bool showLeadingTrailingDates) { + int weekRowCount, + bool showLeadingTrailingDates, + ) { return weekRowCount != 6 || showLeadingTrailingDates; } /// Check both the dates collection dates are equal or not. static bool isDateCollectionEqual( - List? originalDates, List? copyDates) { + List? originalDates, + List? copyDates, + ) { if (originalDates == copyDates) { return true; } @@ -171,8 +275,9 @@ class CalendarViewHelper { /// Return schedule view appointment height and its value based on /// schedule view settings and month view settings. static double getScheduleAppointmentHeight( - MonthViewSettings? monthViewSettings, - ScheduleViewSettings? scheduleViewSettings) { + MonthViewSettings? monthViewSettings, + ScheduleViewSettings? scheduleViewSettings, + ) { return monthViewSettings != null ? (monthViewSettings.agendaItemHeight == -1 ? 50 @@ -186,8 +291,9 @@ class CalendarViewHelper { /// 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) { + MonthViewSettings? monthViewSettings, + ScheduleViewSettings? scheduleViewSettings, + ) { return monthViewSettings != null ? (monthViewSettings.agendaItemHeight == -1 ? 25 @@ -201,10 +307,11 @@ class CalendarViewHelper { /// 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) { + 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; @@ -238,7 +345,9 @@ class CalendarViewHelper { /// Check and returns whether the resource panel can be added or not in the /// calendar. static bool isResourceEnabled( - CalendarDataSource? dataSource, CalendarView view) { + CalendarDataSource? dataSource, + CalendarView view, + ) { return isTimelineView(view) && dataSource != null && dataSource.resources != null && @@ -252,8 +361,9 @@ class CalendarViewHelper { return '${appointment.subject}All day'; } else if (appointment.isSpanned || AppointmentHelper.getDifference( - appointment.startTime, appointment.endTime) - .inDays > + appointment.startTime, + appointment.endTime, + ).inDays > 0) { // ignore: lines_longer_than_80_chars return '${appointment.subject}${DateFormat('hh mm a dd/MMMM/yyyy').format(appointment.startTime)}to${DateFormat('hh mm a dd/MMMM/yyyy').format(appointment.endTime)}'; @@ -265,13 +375,14 @@ class CalendarViewHelper { /// Get the today text color based on today highlight color and today text /// style. - static Color? getTodayHighlightTextColor(Color? todayHighlightColor, - TextStyle? todayTextStyle, SfCalendarThemeData calendarTheme) { + static Color? getTodayHighlightTextColor( + Color? todayHighlightColor, + TextStyle? todayTextStyle, + SfCalendarThemeData calendarTheme, + ) { Color? todayTextColor = todayHighlightColor; if (todayTextColor != null && todayTextColor == Colors.transparent) { - todayTextColor = todayTextStyle != null - ? todayTextStyle.color - : calendarTheme.todayTextStyle!.color; + todayTextColor = calendarTheme.todayTextStyle!.color; } return todayTextColor; @@ -279,12 +390,16 @@ class CalendarViewHelper { /// Get the exact the time from the position and the date time includes /// minutes value. - static double getTimeToPosition(Duration duration, - TimeSlotViewSettings timeSlotViewSettings, double minuteHeight) { + static double getTimeToPosition( + Duration duration, + TimeSlotViewSettings timeSlotViewSettings, + double minuteHeight, + ) { final int startHour = timeSlotViewSettings.startHour.toInt(); final Duration startDuration = Duration( - hours: startHour, - minutes: ((timeSlotViewSettings.startHour - startHour) * 60).toInt()); + hours: startHour, + minutes: ((timeSlotViewSettings.startHour - startHour) * 60).toInt(), + ); final Duration difference = duration - startDuration; return difference.isNegative ? 0 : difference.inMinutes * minuteHeight; } @@ -323,7 +438,9 @@ class CalendarViewHelper { /// Returns the horizontal lines count for a single day in day/week/workweek and time line view static double getHorizontalLinesCount( - TimeSlotViewSettings settings, CalendarView view) { + TimeSlotViewSettings settings, + CalendarView view, + ) { if (view == CalendarView.timelineMonth) { return 1; } @@ -369,8 +486,10 @@ class CalendarViewHelper { /// Return time label size based on calendar view of calendar widget. static double getTimeLabelWidth( - double timeLabelViewWidth, CalendarView view) { - if (view == CalendarView.timelineMonth) { + double timeLabelViewWidth, + CalendarView view, + ) { + if (view == CalendarView.timelineMonth || view == CalendarView.month) { return 0; } @@ -396,7 +515,9 @@ class CalendarViewHelper { /// Return the view header height based on calendar view of calendar widget. static double getViewHeaderHeight( - double viewHeaderHeight, CalendarView view) { + double viewHeaderHeight, + CalendarView view, + ) { if (viewHeaderHeight != -1) { return viewHeaderHeight; } @@ -419,10 +540,18 @@ class CalendarViewHelper { } /// Check the calendar view is day or not. - static bool isDayView(CalendarView view, int numberOfDays, - List? nonWorkingDays, int numberOfWeeks) { + static bool isDayView( + CalendarView view, + int numberOfDays, + List? nonWorkingDays, + int numberOfWeeks, + ) { final int daysCount = DateTimeHelper.getViewDatesCount( - view, numberOfWeeks, numberOfDays, nonWorkingDays); + view, + numberOfWeeks, + numberOfDays, + nonWorkingDays, + ); if ((view == CalendarView.day || view == CalendarView.week || view == CalendarView.workWeek) && @@ -447,7 +576,8 @@ class CalendarViewHelper { /// method to check whether the view changed callback can triggered or not. static bool shouldRaiseViewChangedCallback( - ViewChangedCallback? onViewChanged) { + ViewChangedCallback? onViewChanged, + ) { return onViewChanged != null; } @@ -458,102 +588,128 @@ class CalendarViewHelper { /// method to check whether the long press callback can triggered or not. static bool shouldRaiseCalendarLongPressCallback( - CalendarLongPressCallback? onLongPress) { + CalendarLongPressCallback? onLongPress, + ) { return onLongPress != null; } //// method to check whether the selection changed callback can triggered or not. static bool shouldRaiseCalendarSelectionChangedCallback( - CalendarSelectionChangedCallback? onSelectionChanged) { + CalendarSelectionChangedCallback? onSelectionChanged, + ) { return onSelectionChanged != null; } /// Method to check whether the appointment resize start callback can trigger /// or not. static bool shouldRaiseAppointmentResizeStartCallback( - AppointmentResizeStartCallback? onAppointmentResizeStart) { + AppointmentResizeStartCallback? onAppointmentResizeStart, + ) { return onAppointmentResizeStart != null; } /// Method to check whether the appointment resize update callback can trigger /// or not. static bool shouldRaiseAppointmentResizeUpdateCallback( - AppointmentResizeUpdateCallback? onAppointmentResizeUpdate) { + AppointmentResizeUpdateCallback? onAppointmentResizeUpdate, + ) { return onAppointmentResizeUpdate != null; } /// Method to check whether the appointment resize end callback can trigger /// or not. static bool shouldRaiseAppointmentResizeEndCallback( - AppointmentResizeEndCallback? onAppointmentResizeEnd) { + AppointmentResizeEndCallback? onAppointmentResizeEnd, + ) { return onAppointmentResizeEnd != null; } /// method that raise the calendar tapped callback with the given parameters static void raiseCalendarTapCallback( - SfCalendar calendar, - DateTime? date, - List? appointments, - CalendarElement element, - CalendarResource? resource) { + SfCalendar calendar, + DateTime? date, + List? appointments, + CalendarElement element, + CalendarResource? resource, + ) { calendar.onTap!(CalendarTapDetails(appointments, date, element, resource)); } /// Method that raise the calendar long press callback with given parameters. static void raiseCalendarLongPressCallback( - SfCalendar calendar, - DateTime? date, - List? appointments, - CalendarElement element, - CalendarResource? resource) { + SfCalendar calendar, + DateTime? date, + List? appointments, + CalendarElement element, + CalendarResource? resource, + ) { calendar.onLongPress!( - CalendarLongPressDetails(appointments, date, element, resource)); + CalendarLongPressDetails(appointments, date, element, resource), + ); } /// method that raise the calendar selection changed callback /// with the given parameters static void raiseCalendarSelectionChangedCallback( - SfCalendar calendar, DateTime? date, CalendarResource? resource) { + SfCalendar calendar, + DateTime? date, + CalendarResource? resource, + ) { calendar.onSelectionChanged!(CalendarSelectionDetails(date, resource)); } /// method that raises the visible dates changed callback with the given /// parameters static void raiseViewChangedCallback( - SfCalendar calendar, List visibleDates) { + SfCalendar calendar, + List visibleDates, + ) { calendar.onViewChanged!(ViewChangedDetails(visibleDates)); } /// Method that raises the appointment resize start callback with the given /// parameters. static void raiseAppointmentResizeStartCallback( - SfCalendar calendar, dynamic appointment, CalendarResource? resource) { + SfCalendar calendar, + dynamic appointment, + CalendarResource? resource, + ) { calendar.onAppointmentResizeStart!( - AppointmentResizeStartDetails(appointment, resource)); + AppointmentResizeStartDetails(appointment, resource), + ); } /// Method that raises the appointment resize update callback with the given /// parameters. static void raiseAppointmentResizeUpdateCallback( - SfCalendar calendar, - dynamic appointment, - CalendarResource? resource, - DateTime? resizingTime, - Offset resizingOffset) { - calendar.onAppointmentResizeUpdate!(AppointmentResizeUpdateDetails( - appointment, resource, resizingTime, resizingOffset)); + SfCalendar calendar, + dynamic appointment, + CalendarResource? resource, + DateTime? resizingTime, + Offset resizingOffset, + ) { + calendar.onAppointmentResizeUpdate!( + AppointmentResizeUpdateDetails( + appointment, + resource, + resizingTime, + resizingOffset, + ), + ); } /// Method that raises the appointment resize end callback with the given /// parameters. static void raiseAppointmentResizeEndCallback( - SfCalendar calendar, - dynamic appointment, - CalendarResource? resource, - DateTime? startTime, - DateTime? endTime) { + SfCalendar calendar, + dynamic appointment, + CalendarResource? resource, + DateTime? startTime, + DateTime? endTime, + ) { calendar.onAppointmentResizeEnd!( - AppointmentResizeEndDetails(appointment, resource, startTime, endTime)); + AppointmentResizeEndDetails(appointment, resource, startTime, endTime), + ); } /// Check the calendar view is timeline view or not. @@ -576,7 +732,9 @@ class CalendarViewHelper { /// converts the given schedule appointment collection to their custom /// appointment collection static List getCustomAppointments( - List? appointments, CalendarDataSource? dataSource) { + List? appointments, + CalendarDataSource? dataSource, + ) { final List customAppointments = []; if (appointments == null) { return customAppointments; @@ -591,7 +749,9 @@ class CalendarViewHelper { /// Returns the appointment details with given appointment type. static dynamic getAppointmentDetail( - CalendarAppointment appointment, CalendarDataSource? dataSource) { + CalendarAppointment appointment, + CalendarDataSource? dataSource, + ) { if (appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty) { final Appointment appointmentObject = @@ -600,7 +760,9 @@ class CalendarViewHelper { return appointmentObject; } else { return dataSource!.convertAppointmentToObject( - appointment.data, appointmentObject) ?? + appointment.data, + appointmentObject, + ) ?? appointmentObject; } } else { @@ -611,18 +773,25 @@ class CalendarViewHelper { /// Returns the index of the passed id's resource from the passed resource /// collection. static int getResourceIndex( - List? resourceCollection, Object id) { + List? resourceCollection, + Object id, + ) { if (resourceCollection == null || resourceCollection.isEmpty) { return -1; } - return resourceCollection - .indexWhere((CalendarResource resource) => resource.id == id); + return resourceCollection.indexWhere( + (CalendarResource resource) => resource.id == id, + ); } /// Check the date in between first and last date static bool isDateTimeWithInDateTimeRange( - DateTime startDate, DateTime endDate, DateTime date, int timeInterval) { + DateTime startDate, + DateTime endDate, + DateTime date, + int timeInterval, + ) { if (startDate.isAfter(endDate)) { final dynamic temp = startDate; startDate = endDate; @@ -658,13 +827,16 @@ class CalendarViewHelper { } /// Method to switch the views based on the keyboard interaction. - static KeyEventResult handleViewSwitchKeyBoardEvent(RawKeyEvent event, - CalendarController controller, List? allowedViews) { + static KeyEventResult handleViewSwitchKeyBoardEvent( + KeyEvent event, + CalendarController controller, + List? allowedViews, + ) { /// Ctrl + and Ctrl - used by browser to zoom the page, hence as referred /// EJ2 scheduler, we have used alt + numeric to switch between views in /// calendar web and windows CalendarView view = controller.view!; - if (event.isAltPressed) { + if (HardwareKeyboard.instance.isAltPressed) { if (event.logicalKey == LogicalKeyboardKey.digit1) { view = CalendarView.day; } else if (event.logicalKey == LogicalKeyboardKey.digit2) { @@ -698,7 +870,10 @@ class CalendarViewHelper { /// Check the showWeekNumber is true or not and returns the position. static double getWeekNumberPanelWidth( - bool showWeekNumber, double width, bool isMobilePlatform) { + bool showWeekNumber, + double width, + bool isMobilePlatform, + ) { return showWeekNumber ? (width / (DateTime.daysPerWeek + 1)) / (isMobilePlatform ? 1.3 : 4) : 0; @@ -707,17 +882,18 @@ class CalendarViewHelper { /// Method to check that the current dragging appointment range contains any /// disabled date time in it. static bool isDraggingAppointmentHasDisabledCell( - List timeRegions, - List blackoutDates, - DateTime appStartTime, - DateTime appEndTime, - bool isTimelineView, - bool isMonthView, - DateTime minDate, - DateTime maxDate, - int timeInterval, - int resourceIndex, - List? resources) { + List timeRegions, + List blackoutDates, + DateTime appStartTime, + DateTime appEndTime, + bool isTimelineView, + bool isMonthView, + DateTime minDate, + DateTime maxDate, + int timeInterval, + int resourceIndex, + List? resources, + ) { /// Condition added to check and restrict the appointment rescheduling when /// it exceeds the min/max dates in the calendar. if ((isMonthView && @@ -725,9 +901,17 @@ class CalendarViewHelper { !isDateWithInDateRange(minDate, maxDate, appEndTime))) || (!isMonthView && (!CalendarViewHelper.isDateTimeWithInDateTimeRange( - minDate, maxDate, appStartTime, timeInterval) || + minDate, + maxDate, + appStartTime, + timeInterval, + ) || !CalendarViewHelper.isDateTimeWithInDateTimeRange( - minDate, maxDate, appEndTime, timeInterval)))) { + minDate, + maxDate, + appEndTime, + timeInterval, + )))) { return true; } @@ -877,8 +1061,8 @@ class CalendarAppointment { this.color = Colors.lightBlue, this.isSpanned = false, this.recurrenceExceptionDates, - }) : actualStartTime = startTime, - actualEndTime = endTime; + }) : actualStartTime = startTime, + actualEndTime = endTime; /// The start time for an [CalendarAppointment] in [SfCalendar]. /// @@ -987,25 +1171,26 @@ class CalendarAppointment { /// the passed calendar appointment value Appointment convertToCalendarAppointment() { return Appointment( - startTime: startTime, - endTime: endTime, - subject: subject, - color: color, - recurrenceRule: recurrenceRule, - isAllDay: isAllDay, - resourceIds: resourceIds, - recurrenceId: recurrenceId, - id: id, - startTimeZone: startTimeZone, - endTimeZone: endTimeZone, - notes: notes, - location: location, - recurrenceExceptionDates: recurrenceExceptionDates); + startTime: startTime, + endTime: endTime, + subject: subject, + color: color, + recurrenceRule: recurrenceRule, + isAllDay: isAllDay, + resourceIds: resourceIds, + recurrenceId: recurrenceId, + id: id, + startTimeZone: startTimeZone, + endTimeZone: endTimeZone, + notes: notes, + location: location, + recurrenceExceptionDates: recurrenceExceptionDates, + ); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1019,20 +1204,28 @@ class CalendarAppointment { } return CalendarViewHelper.isSameTimeSlot( - otherAppointment.startTime, startTime) && + otherAppointment.startTime, + startTime, + ) && CalendarViewHelper.isSameTimeSlot(otherAppointment.endTime, endTime) && CalendarViewHelper.isSameTimeSlot( - otherAppointment.actualStartTime, actualStartTime) && + otherAppointment.actualStartTime, + actualStartTime, + ) && CalendarViewHelper.isSameTimeSlot( - otherAppointment.actualEndTime, actualEndTime) && + otherAppointment.actualEndTime, + actualEndTime, + ) && otherAppointment.isSpanned == isSpanned && otherAppointment.startTimeZone == startTimeZone && otherAppointment.endTimeZone == endTimeZone && otherAppointment.isAllDay == isAllDay && otherAppointment.notes == notes && otherAppointment.location == location && - !CalendarViewHelper.isCollectionEqual( - otherAppointment.resourceIds, resourceIds) && + CalendarViewHelper.isCollectionEqual( + otherAppointment.resourceIds, + resourceIds, + ) && otherAppointment.recurrenceId == recurrenceId && otherAppointment.id == id && otherAppointment.data == data && @@ -1040,21 +1233,25 @@ class CalendarAppointment { otherAppointment.color == color && otherAppointment.recurrenceRule == recurrenceRule && CalendarViewHelper.isDateCollectionEqual( - otherAppointment.recurrenceExceptionDates, - recurrenceExceptionDates); + otherAppointment.recurrenceExceptionDates, + recurrenceExceptionDates, + ); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return hashValues( + return Object.hash( startTimeZone, endTimeZone, recurrenceRule, isAllDay = false, notes, location, - hashList(resourceIds), + + /// Below condition is referred from text style class + /// https://api.flutter.dev/flutter/painting/TextStyle/hashCode.html + resourceIds == null ? null : Object.hashAll(resourceIds!), recurrenceId, id, data, @@ -1062,7 +1259,9 @@ class CalendarAppointment { endTime, subject, color, - hashList(recurrenceExceptionDates), + recurrenceExceptionDates == null + ? null + : Object.hashAll(recurrenceExceptionDates!), ); } } @@ -1078,20 +1277,20 @@ class CalendarTimeRegion { /// /// 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; + 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; @@ -1140,37 +1339,39 @@ class CalendarTimeRegion { /// 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}) { + 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); + 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, + ); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1186,14 +1387,20 @@ class CalendarTimeRegion { CalendarViewHelper.isSameTimeSlot(region.startTime, startTime) && CalendarViewHelper.isSameTimeSlot(region.endTime, endTime) && CalendarViewHelper.isSameTimeSlot( - region.actualStartTime, actualStartTime) && + region.actualStartTime, + actualStartTime, + ) && CalendarViewHelper.isSameTimeSlot( - region.actualStartTime, actualStartTime) && + region.actualStartTime, + actualStartTime, + ) && region.color == color && region.recurrenceRule == recurrenceRule && region.enablePointerInteraction == enablePointerInteraction && CalendarViewHelper.isDateCollectionEqual( - region.recurrenceExceptionDates, recurrenceExceptionDates) && + region.recurrenceExceptionDates, + recurrenceExceptionDates, + ) && region.iconData == iconData && region.timeZone == timeZone && region.resourceIds == resourceIds && @@ -1203,18 +1410,24 @@ class CalendarTimeRegion { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return hashValues( - startTime, - endTime, - color, - recurrenceRule, - textStyle, - enablePointerInteraction, - hashList(recurrenceExceptionDates), - hashList(resourceIds), - text, - iconData, - timeZone); + return Object.hash( + startTime, + endTime, + color, + recurrenceRule, + textStyle, + enablePointerInteraction, + + /// Below condition is referred from text style class + /// https://api.flutter.dev/flutter/painting/TextStyle/hashCode.html + recurrenceExceptionDates == null + ? null + : Object.hashAll(recurrenceExceptionDates!), + resourceIds == null ? null : Object.hashAll(resourceIds!), + text, + iconData, + timeZone, + ); } } @@ -1235,9 +1448,9 @@ class ScheduleViewHoveringDetails { /// if all day panel appointment selected then [appointmentView] holds /// appointment details, else [selectedDate] holds selected region date value. @immutable -class SelectionDetails { +class AllDayPanelSelectionDetails { /// Constructor to create the selection details. - const SelectionDetails(this.appointmentView, this.selectedDate); + const AllDayPanelSelectionDetails(this.appointmentView, this.selectedDate); /// Holds the selected appointment view details. final AppointmentView? appointmentView; 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 26e049d0e..f8df67538 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 @@ -6,22 +6,28 @@ import 'enums.dart'; /// 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, - int daysCount, List? nonWorkingDays) { + static int getViewDatesCount( + CalendarView calendarView, + int numberOfWeeks, + int daysCount, + List? nonWorkingDays, + ) { switch (calendarView) { case CalendarView.month: return DateTime.daysPerWeek * numberOfWeeks; case CalendarView.week: case CalendarView.timelineWeek: - return daysCount == -1 ? DateTime.daysPerWeek : daysCount; + return (daysCount >= 1 && daysCount <= 7) + ? daysCount + : DateTime.daysPerWeek; case CalendarView.workWeek: case CalendarView.timelineWorkWeek: - return daysCount == -1 - ? DateTime.daysPerWeek - nonWorkingDays!.length - : daysCount; + return (daysCount >= 1 && daysCount <= 7) + ? daysCount + : DateTime.daysPerWeek - nonWorkingDays!.length; case CalendarView.timelineDay: case CalendarView.day: - return daysCount == -1 ? 1 : daysCount; + return (daysCount >= 1 && daysCount <= 7) ? daysCount : 1; case CalendarView.schedule: return 1; case CalendarView.timelineMonth: @@ -51,25 +57,28 @@ class DateTimeHelper { /// Calculate the next view visible start date based on calendar view. static DateTime getNextViewStartDate( - CalendarView calendarView, - int numberOfWeeksInView, - DateTime date, - int visibleDatesCount, - List? nonWorkingDays) { + CalendarView calendarView, + int numberOfWeeksInView, + DateTime date, + int visibleDatesCount, + List? nonWorkingDays, + ) { switch (calendarView) { case CalendarView.month: { return numberOfWeeksInView == 6 ? DateTimeHelper.getDateTimeValue(getNextMonthDate(date)) : DateTimeHelper.getDateTimeValue( - addDays(date, numberOfWeeksInView * DateTime.daysPerWeek)); + addDays(date, numberOfWeeksInView * DateTime.daysPerWeek), + ); } case CalendarView.timelineMonth: return DateTimeHelper.getDateTimeValue(getNextMonthDate(date)); case CalendarView.week: case CalendarView.timelineWeek: return DateTimeHelper.getDateTimeValue( - addDays(date, visibleDatesCount)); + addDays(date, visibleDatesCount), + ); case CalendarView.workWeek: case CalendarView.timelineWorkWeek: { @@ -77,7 +86,8 @@ class DateTimeHelper { nonWorkingDays == null ? 0 : nonWorkingDays.length; if (visibleDatesCount + nonWorkingDaysCount == 7) { return DateTimeHelper.getDateTimeValue( - addDays(date, visibleDatesCount + nonWorkingDaysCount)); + addDays(date, visibleDatesCount + nonWorkingDaysCount), + ); } for (int i = 0; i <= visibleDatesCount; i++) { @@ -88,12 +98,14 @@ class DateTimeHelper { } } return DateTimeHelper.getDateTimeValue( - addDays(date, visibleDatesCount)); + addDays(date, visibleDatesCount), + ); } case CalendarView.day: case CalendarView.timelineDay: return DateTimeHelper.getDateTimeValue( - addDays(date, visibleDatesCount)); + addDays(date, visibleDatesCount), + ); case CalendarView.schedule: return DateTimeHelper.getDateTimeValue(addDays(date, 1)); } @@ -101,25 +113,28 @@ class DateTimeHelper { /// Calculate the previous view visible start date based on calendar view. static DateTime getPreviousViewStartDate( - CalendarView calendarView, - int numberOfWeeksInView, - DateTime date, - int visibleDatesCount, - List? nonWorkingDays) { + CalendarView calendarView, + int numberOfWeeksInView, + DateTime date, + int visibleDatesCount, + List? nonWorkingDays, + ) { switch (calendarView) { case CalendarView.month: { return numberOfWeeksInView == 6 ? DateTimeHelper.getDateTimeValue(getPreviousMonthDate(date)) : DateTimeHelper.getDateTimeValue( - addDays(date, -numberOfWeeksInView * DateTime.daysPerWeek)); + addDays(date, -numberOfWeeksInView * DateTime.daysPerWeek), + ); } case CalendarView.timelineMonth: return DateTimeHelper.getDateTimeValue(getPreviousMonthDate(date)); case CalendarView.week: case CalendarView.timelineWeek: return DateTimeHelper.getDateTimeValue( - addDays(date, -visibleDatesCount)); + addDays(date, -visibleDatesCount), + ); case CalendarView.workWeek: case CalendarView.timelineWorkWeek: { @@ -127,7 +142,8 @@ class DateTimeHelper { nonWorkingDays == null ? 0 : nonWorkingDays.length; if (visibleDatesCount + nonWorkingDaysCount == 7) { return DateTimeHelper.getDateTimeValue( - addDays(date, -visibleDatesCount - nonWorkingDaysCount)); + addDays(date, -visibleDatesCount - nonWorkingDaysCount), + ); } for (int i = 1; i <= visibleDatesCount; i++) { final dynamic currentDate = addDays(date, -i); @@ -137,21 +153,26 @@ class DateTimeHelper { } } return DateTimeHelper.getDateTimeValue( - addDays(date, -visibleDatesCount)); + addDays(date, -visibleDatesCount), + ); } case CalendarView.day: case CalendarView.timelineDay: return DateTimeHelper.getDateTimeValue( - addDays(date, -visibleDatesCount)); + addDays(date, -visibleDatesCount), + ); case CalendarView.schedule: return DateTimeHelper.getDateTimeValue(addDays(date, -1)); } } static DateTime _getPreviousValidDate( - DateTime prevViewDate, List nonWorkingDays) { - DateTime previousDate = - DateTimeHelper.getDateTimeValue(addDays(prevViewDate, -1)); + DateTime prevViewDate, + List nonWorkingDays, + ) { + DateTime previousDate = DateTimeHelper.getDateTimeValue( + addDays(prevViewDate, -1), + ); while (nonWorkingDays.contains(previousDate.weekday)) { previousDate = DateTimeHelper.getDateTimeValue(addDays(previousDate, -1)); } @@ -159,9 +180,12 @@ class DateTimeHelper { } static DateTime _getNextValidDate( - DateTime nextDate, List nonWorkingDays) { - DateTime nextViewDate = - DateTimeHelper.getDateTimeValue(addDays(nextDate, 1)); + DateTime nextDate, + List nonWorkingDays, + ) { + DateTime nextViewDate = DateTimeHelper.getDateTimeValue( + addDays(nextDate, 1), + ); while (nonWorkingDays.contains(nextViewDate.weekday)) { nextViewDate = DateTimeHelper.getDateTimeValue(addDays(nextViewDate, 1)); } @@ -212,31 +236,40 @@ class DateTimeHelper { /// 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]) { + 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 canMoveToNextView( + calendarView, + numberOfWeeksInView, + minDate, + maxDate, + visibleDates, + nonWorkingDays, + ); } switch (calendarView) { case CalendarView.month: { if (numberOfWeeksInView != 6) { - final DateTime prevViewDate = - DateTimeHelper.getDateTimeValue(addDays(visibleDates[0], -1)); + final DateTime prevViewDate = DateTimeHelper.getDateTimeValue( + addDays(visibleDates[0], -1), + ); if (!isSameOrAfterDate(minDate, prevViewDate)) { return false; } } else { final DateTime currentDate = visibleDates[visibleDates.length ~/ 2]; final DateTime previousDate = DateTimeHelper.getDateTimeValue( - getPreviousMonthDate(currentDate)); + getPreviousMonthDate(currentDate), + ); if ((previousDate.month < minDate.month && previousDate.year == minDate.year) || previousDate.year < minDate.year) { @@ -247,8 +280,9 @@ class DateTimeHelper { break; case CalendarView.timelineMonth: { - final DateTime prevViewDate = - DateTimeHelper.getDateTimeValue(addDays(visibleDates[0], -1)); + final DateTime prevViewDate = DateTimeHelper.getDateTimeValue( + addDays(visibleDates[0], -1), + ); if (!isSameOrAfterDate(minDate, prevViewDate)) { return false; } @@ -260,8 +294,9 @@ class DateTimeHelper { case CalendarView.timelineWeek: { DateTime prevViewDate = visibleDates[0]; - prevViewDate = - DateTimeHelper.getDateTimeValue(addDays(prevViewDate, -1)); + prevViewDate = DateTimeHelper.getDateTimeValue( + addDays(prevViewDate, -1), + ); if (!isSameOrAfterDate(minDate, prevViewDate)) { return false; } @@ -270,8 +305,10 @@ class DateTimeHelper { case CalendarView.timelineWorkWeek: case CalendarView.workWeek: { - final DateTime previousDate = - _getPreviousValidDate(visibleDates[0], nonWorkingDays); + final DateTime previousDate = _getPreviousValidDate( + visibleDates[0], + nonWorkingDays, + ); if (!isSameOrAfterDate(minDate, previousDate)) { return false; } @@ -286,16 +323,23 @@ class DateTimeHelper { /// 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]) { + 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 canMoveToPreviousView( + calendarView, + numberOfWeeksInView, + minDate, + maxDate, + visibleDates, + nonWorkingDays, + ); } switch (calendarView) { @@ -303,14 +347,16 @@ class DateTimeHelper { { if (numberOfWeeksInView != 6) { final DateTime nextViewDate = DateTimeHelper.getDateTimeValue( - addDays(visibleDates[visibleDates.length - 1], 1)); + addDays(visibleDates[visibleDates.length - 1], 1), + ); if (!isSameOrBeforeDate(maxDate, nextViewDate)) { return false; } } else { final DateTime currentDate = visibleDates[visibleDates.length ~/ 2]; - final DateTime nextDate = - DateTimeHelper.getDateTimeValue(getNextMonthDate(currentDate)); + final DateTime nextDate = DateTimeHelper.getDateTimeValue( + getNextMonthDate(currentDate), + ); if ((nextDate.month > maxDate.month && nextDate.year == maxDate.year) || nextDate.year > maxDate.year) { @@ -322,7 +368,8 @@ class DateTimeHelper { case CalendarView.timelineMonth: { final DateTime nextViewDate = DateTimeHelper.getDateTimeValue( - addDays(visibleDates[visibleDates.length - 1], 1)); + addDays(visibleDates[visibleDates.length - 1], 1), + ); if (!isSameOrBeforeDate(maxDate, nextViewDate)) { return false; } @@ -334,7 +381,8 @@ class DateTimeHelper { case CalendarView.timelineWeek: { final DateTime nextViewDate = DateTimeHelper.getDateTimeValue( - addDays(visibleDates[visibleDates.length - 1], 1)); + addDays(visibleDates[visibleDates.length - 1], 1), + ); if (!isSameOrBeforeDate(maxDate, nextViewDate)) { return false; } @@ -344,7 +392,9 @@ class DateTimeHelper { case CalendarView.timelineWorkWeek: { final DateTime nextDate = _getNextValidDate( - visibleDates[visibleDates.length - 1], nonWorkingDays); + visibleDates[visibleDates.length - 1], + nonWorkingDays, + ); if (!isSameOrBeforeDate(maxDate, nextDate)) { return false; } 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 9b7f01a45..62a5a0118 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart @@ -8,7 +8,7 @@ enum MonthNavigationDirection { vertical, /// - MonthNavigationDirection.horizontal, Navigates in left and top direction - horizontal + horizontal, } /// Available views for [SfCalendar]. @@ -46,7 +46,7 @@ enum CalendarView { timelineMonth, /// - CalendarView.schedule, displays the schedule view. - schedule + schedule, } /// Available appointment display mode for [SfCalendar] month cell. @@ -60,7 +60,7 @@ enum MonthAppointmentDisplayMode { appointment, /// - MonthAppointmentDisplayMode.none, doesn't display appointment on view. - none + none, } /// Available recurrence types for [Appointment] in [SfCalendar] @@ -94,7 +94,7 @@ enum RecurrenceRange { /// - RecurrenceRange.count, indicates the appointment occurrence repeated /// with specified count times. - count + count, } /// The week days occurrence of [Appointment]. @@ -118,7 +118,7 @@ enum WeekDays { friday, /// - WeekDays.saturday, indicates the appointment occurred in saturday. - saturday + saturday, } /// The available calendar elements for the [CalendarTapDetails] in [SfCalendar] @@ -145,7 +145,7 @@ enum CalendarElement { /// all-day panel will be added if the view must contain at least one /// appointment. /// - /// _Note:_ The all day panel doesn't available in the [CalendarView.month], + /// _Note:_ The all day panel isn't available in the [CalendarView.month], /// [CalendarView.timelineDay], [CalendarView.timelineWeek] and /// [CalendarView.timelineWorkWeekView]. allDayPanel, @@ -167,7 +167,7 @@ enum CalendarElement { /// /// _Note: _This element applies to the timeline views with resource assigned /// to the calendar data source. - resourceHeader + resourceHeader, } /// Action performed in data source @@ -208,7 +208,7 @@ enum CalendarDataSourceAction { /// the resource collection will be reset. /// /// _Note:_ This is applicable only when the resource collection reset. - resetResource + resetResource, } /// Available view navigation modes for [SfCalendar]. @@ -227,7 +227,7 @@ enum ViewNavigationMode { /// It will not impact scrolling timeslot views, /// [controller.forward], [controller.backward] /// and [showNavigationArrow]. - none + none, } /// Available Appointment types for [Appointment] @@ -247,5 +247,5 @@ enum AppointmentType { /// - AppointmentType.pattern, this specifies the pattern appointment /// which has the RRule value and it is the pattern for the /// occurrence appointment in the recurrence series. - pattern + pattern, } 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 34759febf..51bc89c9e 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,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; +import '../../../calendar.dart'; /// The dates that visible on the view changes in [SfCalendar]. /// @@ -32,9 +32,12 @@ class ViewChangedDetails { @immutable class CalendarTapDetails extends CalendarTouchDetails { /// Creates details for [CalendarTapCallback]. - const CalendarTapDetails(List? appointments, DateTime? date, - CalendarElement element, CalendarResource? resource) - : super(appointments, date, element, resource); + const CalendarTapDetails( + List? appointments, + DateTime? date, + CalendarElement element, + CalendarResource? resource, + ) : super(appointments, date, element, resource); } /// The element that long pressed on view in [SfCalendar] @@ -50,9 +53,12 @@ class CalendarTapDetails extends CalendarTouchDetails { @immutable class CalendarLongPressDetails extends CalendarTouchDetails { /// Creates details for [CalendarLongPressCallback] - const CalendarLongPressDetails(List? appointments, DateTime? date, - CalendarElement element, CalendarResource? resource) - : super(appointments, date, element, resource); + const CalendarLongPressDetails( + List? appointments, + DateTime? date, + CalendarElement element, + CalendarResource? resource, + ) : super(appointments, date, element, resource); } /// The element that tapped on view in [SfCalendar] @@ -92,7 +98,11 @@ class CalendarSelectionDetails { class CalendarTouchDetails { /// Creates details for [CalendarTapCallback] and [CalendarLongPressCallback]. const CalendarTouchDetails( - this.appointments, this.date, this.targetElement, this.resource); + this.appointments, + this.date, + this.targetElement, + this.resource, + ); /// The collection of appointments that return from the date. final List? appointments; @@ -141,7 +151,11 @@ class AppointmentResizeStartDetails { class AppointmentResizeUpdateDetails { /// Creates details for [AppointmentResizeUpdateCallback]. const AppointmentResizeUpdateDetails( - this.appointment, this.resource, this.resizingTime, this.resizingOffset); + this.appointment, + this.resource, + this.resizingTime, + this.resizingOffset, + ); /// The appointment that resizing on view in [SfCalendar]. final dynamic appointment; @@ -169,7 +183,11 @@ class AppointmentResizeUpdateDetails { class AppointmentResizeEndDetails { /// Creates details for [AppointmentResizeUpdateCallback]. const AppointmentResizeEndDetails( - this.appointment, this.resource, this.startTime, this.endTime); + this.appointment, + this.resource, + this.startTime, + this.endTime, + ); /// The appointment that resized on view in [SfCalendar]. final dynamic appointment; @@ -220,8 +238,13 @@ class AppointmentDragStartDetails { @immutable class AppointmentDragUpdateDetails { /// Creates details for [AppointmentDragUpdateCallback]. - const AppointmentDragUpdateDetails(this.appointment, this.sourceResource, - this.targetResource, this.draggingPosition, this.draggingTime); + const AppointmentDragUpdateDetails( + this.appointment, + this.sourceResource, + this.targetResource, + this.draggingPosition, + this.draggingTime, + ); /// The dragging appointment. final Object? appointment; @@ -251,8 +274,12 @@ class AppointmentDragUpdateDetails { /// in calendar. class AppointmentDragEndDetails { /// Creates details for [AppointmentDragEndCallback]. - const AppointmentDragEndDetails(this.appointment, this.sourceResource, - this.targetResource, this.droppingTime); + const AppointmentDragEndDetails( + this.appointment, + this.sourceResource, + this.targetResource, + this.droppingTime, + ); /// The dropping appointment. final Object? appointment; @@ -272,9 +299,12 @@ class AppointmentDragEndDetails { @immutable class CalendarDetails extends CalendarTouchDetails { /// creates details for [CalendarDetailsCallback]. - const CalendarDetails(List? appointments, DateTime? date, - CalendarElement element, CalendarResource? resource) - : super(appointments, date, element, resource); + const CalendarDetails( + List? appointments, + DateTime? date, + CalendarElement element, + CalendarResource? resource, + ) : super(appointments, date, element, resource); } /// Signature for a function that creates a widget based on month @@ -283,32 +313,38 @@ class CalendarDetails extends CalendarTouchDetails { /// See also: /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef ScheduleViewMonthHeaderBuilder = Widget Function( - BuildContext context, ScheduleViewMonthHeaderDetails details); +typedef ScheduleViewMonthHeaderBuilder = + Widget Function( + BuildContext context, + ScheduleViewMonthHeaderDetails details, + ); /// Signature for a function that creates a widget based on month cell details. /// /// See also: /// * [SfCalendar.monthCellBuilder], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef MonthCellBuilder = Widget Function( - BuildContext context, MonthCellDetails details); +typedef MonthCellBuilder = + Widget Function(BuildContext context, MonthCellDetails details); /// Signature for a function that creates a widget based on appointment details. /// /// See also: /// * [SfCalendar.appointmentBuilder], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef CalendarAppointmentBuilder = Widget Function(BuildContext context, - CalendarAppointmentDetails calendarAppointmentDetails); +typedef CalendarAppointmentBuilder = + Widget Function( + BuildContext context, + CalendarAppointmentDetails calendarAppointmentDetails, + ); /// Signature for a function that creates a widget based on time region details. /// /// See also: /// * [SfCalendar.timeRegionBuilder], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef TimeRegionBuilder = Widget Function( - BuildContext context, TimeRegionDetails timeRegionDetails); +typedef TimeRegionBuilder = + Widget Function(BuildContext context, TimeRegionDetails timeRegionDetails); /// Signature for the function that create the widget based on load /// more details. @@ -316,8 +352,11 @@ typedef TimeRegionBuilder = Widget Function( /// See also: /// * [SfCalendar.loadMoreWidgetBuilder], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef LoadMoreWidgetBuilder = Widget Function( - BuildContext context, LoadMoreCallback loadMoreAppointments); +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. @@ -333,8 +372,8 @@ typedef LoadMoreCallback = Future Function(); /// See also: /// * [SfCalendar.resourceViewHeaderBuilder], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef ResourceViewHeaderBuilder = Widget Function( - BuildContext context, ResourceViewHeaderDetails details); +typedef ResourceViewHeaderBuilder = + Widget Function(BuildContext context, ResourceViewHeaderDetails details); /// Contains the details that needed on month cell builder. /// @@ -347,7 +386,11 @@ typedef ResourceViewHeaderBuilder = Widget Function( 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; @@ -386,8 +429,12 @@ class ScheduleViewMonthHeaderDetails { /// * [SfCalendar], which passes the information to one of its receiver. 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; @@ -455,8 +502,8 @@ class ResourceViewHeaderDetails { /// See also: /// * [SfCalendar.onViewChanged], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef ViewChangedCallback = void Function( - ViewChangedDetails viewChangedDetails); +typedef ViewChangedCallback = + void Function(ViewChangedDetails viewChangedDetails); /// Signature for callback that reports that a calendar element tapped on view. /// @@ -468,8 +515,8 @@ typedef ViewChangedCallback = void Function( /// See also: /// * [SfCalendar.onTap], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef CalendarTapCallback = void Function( - CalendarTapDetails calendarTapDetails); +typedef CalendarTapCallback = + void Function(CalendarTapDetails calendarTapDetails); /// Signature for callback that reports that a calendar element long pressed /// on view. @@ -482,8 +529,8 @@ typedef CalendarTapCallback = void Function( /// See also: /// * [SfCalendar.onLongPress], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef CalendarLongPressCallback = void Function( - CalendarLongPressDetails calendarLongPressDetails); +typedef CalendarLongPressCallback = + void Function(CalendarLongPressDetails calendarLongPressDetails); /// Signature for callback that reports that /// a calendar view selection changed on view. @@ -497,8 +544,8 @@ typedef CalendarLongPressCallback = void Function( /// See also: /// * [SfCalendar.onSelectionChanged], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef CalendarSelectionChangedCallback = void Function( - CalendarSelectionDetails calendarSelectionDetails); +typedef CalendarSelectionChangedCallback = + void Function(CalendarSelectionDetails calendarSelectionDetails); /// Signature for callback that reports that a appointment starts resizing in /// [SfCalendar]. @@ -511,8 +558,8 @@ typedef CalendarSelectionChangedCallback = void Function( /// See also: /// * [SfCalendar.onAppointmentResizeStart], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef AppointmentResizeStartCallback = void Function( - AppointmentResizeStartDetails appointmentResizeStartDetails); +typedef AppointmentResizeStartCallback = + void Function(AppointmentResizeStartDetails appointmentResizeStartDetails); /// Signature for callback that reports that a appointment resizing in /// [SfCalendar]. @@ -525,8 +572,10 @@ typedef AppointmentResizeStartCallback = void Function( /// See also: /// * [SfCalendar.onAppointmentResizeUpdate], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef AppointmentResizeUpdateCallback = void Function( - AppointmentResizeUpdateDetails appointmentResizeUpdateDetails); +typedef AppointmentResizeUpdateCallback = + void Function( + AppointmentResizeUpdateDetails appointmentResizeUpdateDetails, + ); /// Signature for callback that reports that a appointment resizing completed in /// [SfCalendar]. @@ -540,8 +589,8 @@ typedef AppointmentResizeUpdateCallback = void Function( /// See also: /// * [SfCalendar.onAppointmentResizeEnd], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef AppointmentResizeEndCallback = void Function( - AppointmentResizeEndDetails appointmentResizeEndDetails); +typedef AppointmentResizeEndCallback = + void Function(AppointmentResizeEndDetails appointmentResizeEndDetails); /// Signature for callback that reports that a appointment starts dragging in /// [SfCalendar]. @@ -554,8 +603,8 @@ typedef AppointmentResizeEndCallback = void Function( /// See also: /// * [SfCalendar.onDragStart], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef AppointmentDragStartCallback = void Function( - AppointmentDragStartDetails appointmentDragStartDetails); +typedef AppointmentDragStartCallback = + void Function(AppointmentDragStartDetails appointmentDragStartDetails); /// Signature for callback that reports that a appointment dragging in /// [SfCalendar]. @@ -569,8 +618,8 @@ typedef AppointmentDragStartCallback = void Function( /// See also: /// * [SfCalendar.onDragUpdate], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef AppointmentDragUpdateCallback = void Function( - AppointmentDragUpdateDetails appointmentDragUpdateDetails); +typedef AppointmentDragUpdateCallback = + void Function(AppointmentDragUpdateDetails appointmentDragUpdateDetails); /// Signature for callback that reports that a appointment dragging completed in /// [SfCalendar]. @@ -584,5 +633,5 @@ typedef AppointmentDragUpdateCallback = void Function( /// See also: /// * [SfCalendar.onDragEnd], which matches this signature. /// * [SfCalendar], which uses this signature in one of it's callback. -typedef AppointmentDragEndCallback = void Function( - AppointmentDragEndDetails appointmentDragEndDetails); +typedef AppointmentDragEndCallback = + void Function(AppointmentDragEndDetails appointmentDragEndDetails); 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 1033c8fc3..20aa4f436 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,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; +import '../../../calendar.dart'; /// The resource data for calendar. /// @@ -82,11 +82,12 @@ class CalendarResource with Diagnosticable { /// /// 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, - this.color = Colors.lightBlue}); + CalendarResource({ + this.displayName = '', + required this.id, + this.image, + this.color = Colors.lightBlue, + }); /// The name which displayed on the [CalendarResource] view of [SfCalendar]. /// @@ -243,7 +244,7 @@ class CalendarResource with Diagnosticable { final ImageProvider? image; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -263,7 +264,7 @@ class CalendarResource with Diagnosticable { @override int get hashCode { - return hashValues(displayName, id, image, color); + return Object.hash(displayName, id, image, color); } @override 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 d35a2915a..76aff4c11 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,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; -import 'package:syncfusion_flutter_calendar/src/calendar/common/calendar_view_helper.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../../../calendar.dart'; import '../common/calendar_view_helper.dart'; /// Used to hold the resource view on all timeline views. @@ -11,19 +10,22 @@ class ResourceViewWidget extends StatefulWidget { /// Constructor to create the resource view widget to holds resource view on /// all timeline views. const ResourceViewWidget( - this.resources, - this.resourceViewSettings, - this.resourceItemHeight, - this.cellBorderColor, - this.calendarTheme, - this.notifier, - this.isRTL, - this.textScaleFactor, - this.mouseHoverPosition, - this.imagePainterCollection, - this.width, - this.panelHeight, - this.resourceViewHeaderBuilder); + this.resources, + this.resourceViewSettings, + this.resourceItemHeight, + this.cellBorderColor, + this.calendarTheme, + this.themeData, + this.notifier, + this.isRTL, + this.textScaleFactor, + this.mouseHoverPosition, + this.imagePainterCollection, + this.width, + this.panelHeight, + this.resourceViewHeaderBuilder, { + super.key, + }); /// Holds the resources of the calendar. final List? resources; @@ -40,6 +42,9 @@ class ResourceViewWidget extends StatefulWidget { /// Hols the theme data of the calendar. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data values. + final ThemeData themeData; + /// Used to trigger repaint while resource decoration image loaded. final ValueNotifier notifier; @@ -79,11 +84,17 @@ class _ResourceViewWidgetState extends State { for (int i = 0; i < resourceLength; i++) { final CalendarResource currentResource = widget.resources![i]; final Widget child = widget.resourceViewHeaderBuilder!( - context, - ResourceViewHeaderDetails( - currentResource, - Rect.fromLTWH( - 0, yPosition, widget.width, widget.resourceItemHeight))); + context, + ResourceViewHeaderDetails( + currentResource, + Rect.fromLTWH( + 0, + yPosition, + widget.width, + widget.resourceItemHeight, + ), + ), + ); children.add(RepaintBoundary(child: child)); yPosition += widget.resourceItemHeight; } @@ -95,6 +106,7 @@ class _ResourceViewWidgetState extends State { widget.resourceItemHeight, widget.cellBorderColor, widget.calendarTheme, + widget.themeData, widget.notifier, widget.isRTL, widget.textScaleFactor, @@ -108,27 +120,29 @@ class _ResourceViewWidgetState extends State { } class _ResourceViewRenderObjectWidget extends MultiChildRenderObjectWidget { - _ResourceViewRenderObjectWidget( - this.resources, - this.resourceViewSettings, - this.resourceItemHeight, - this.cellBorderColor, - this.calendarTheme, - this.notifier, - this.isRTL, - this.textScaleFactor, - this.mouseHoverPosition, - this.imagePainterCollection, - this.width, - this.panelHeight, - {List children = const []}) - : super(children: children); + const _ResourceViewRenderObjectWidget( + this.resources, + this.resourceViewSettings, + this.resourceItemHeight, + this.cellBorderColor, + this.calendarTheme, + this.themeData, + this.notifier, + this.isRTL, + this.textScaleFactor, + this.mouseHoverPosition, + this.imagePainterCollection, + this.width, + this.panelHeight, { + List children = const [], + }) : super(children: children); final List? resources; final ResourceViewSettings resourceViewSettings; final double resourceItemHeight; final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final ValueNotifier notifier; final bool isRTL; final double textScaleFactor; @@ -140,29 +154,34 @@ class _ResourceViewRenderObjectWidget extends MultiChildRenderObjectWidget { @override _ResourceViewRenderObject createRenderObject(BuildContext context) { return _ResourceViewRenderObject( - resources, - resourceViewSettings, - resourceItemHeight, - cellBorderColor, - calendarTheme, - notifier, - isRTL, - textScaleFactor, - mouseHoverPosition, - imagePainterCollection, - width, - panelHeight); + resources, + resourceViewSettings, + resourceItemHeight, + cellBorderColor, + calendarTheme, + themeData, + notifier, + isRTL, + textScaleFactor, + mouseHoverPosition, + imagePainterCollection, + width, + panelHeight, + ); } @override void updateRenderObject( - BuildContext context, _ResourceViewRenderObject renderObject) { + BuildContext context, + _ResourceViewRenderObject renderObject, + ) { renderObject ..resources = resources ..resourceViewSettings = resourceViewSettings ..resourceItemHeight = resourceItemHeight ..cellBorderColor = cellBorderColor ..calendarTheme = calendarTheme + ..themeData = themeData ..notifier = notifier ..isRTL = isRTL ..textScaleFactor = textScaleFactor @@ -175,18 +194,20 @@ class _ResourceViewRenderObjectWidget extends MultiChildRenderObjectWidget { class _ResourceViewRenderObject extends CustomCalendarRenderObject { _ResourceViewRenderObject( - this._resources, - this._resourceViewSettings, - this._resourceItemHeight, - this._cellBorderColor, - this._calendarTheme, - this._notifier, - this._isRTL, - this._textScaleFactor, - this._mouseHoverPosition, - this._imagePainterCollection, - this._width, - this._panelHeight); + this._resources, + this._resourceViewSettings, + this._resourceItemHeight, + this._cellBorderColor, + this._calendarTheme, + this._themeData, + this._notifier, + this._isRTL, + this._textScaleFactor, + this._mouseHoverPosition, + this._imagePainterCollection, + this._width, + this._panelHeight, + ); List? _resources; @@ -269,6 +290,18 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { markNeedsPaint(); } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + } + ValueNotifier _notifier; ValueNotifier get notifier => _notifier; @@ -377,11 +410,8 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { } _panelHeight = value; - if (childCount == 0) { - markNeedsPaint(); - } else { - markNeedsLayout(); - } + markNeedsLayout(); + markNeedsPaint(); } @override @@ -399,23 +429,28 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? panelHeight : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? panelHeight : widgetSize.height, + ); for (dynamic child = firstChild; child != null; child = childAfter(child)) { - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minWidth: width, minHeight: resourceItemHeight, maxWidth: width, - maxHeight: resourceItemHeight)); + maxHeight: resourceItemHeight, + ), + ); } } @override void paint(PaintingContext context, Offset offset) { - final bool _isNeedCustomPaint = childCount != 0; + final bool isNeedCustomPaint = childCount != 0; - if (!_isNeedCustomPaint) { + if (!isNeedCustomPaint) { _resourceViewHeader(context.canvas, size); } else { double yPosition = 0; @@ -426,11 +461,11 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { child = childAfter(child); if (mouseHoverPosition != null) { - final Color resourceHoveringColor = - (calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); + final Color resourceHoveringColor = (themeData.brightness == + Brightness.dark + ? Colors.white + : Colors.black87) + .withValues(alpha: 0.04); _addHovering(context.canvas, size, yPosition, resourceHoveringColor); } @@ -453,42 +488,65 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { final double actualItemHeight = resourceItemHeight * 0.80; double yPosition = 0; _circlePainter.isAntiAlias = true; - final double radius = actualItemHeight < actualItemWidth - ? actualItemHeight / 2 - : actualItemWidth / 2; + final double radius = + actualItemHeight < actualItemWidth + ? actualItemHeight / 2 + : actualItemWidth / 2; final Color resourceCellBorderColor = cellBorderColor ?? calendarTheme.cellBorderColor!; - final Color resourceHoveringColor = - (calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); - final TextStyle displayNameTextStyle = - resourceViewSettings.displayNameTextStyle ?? - calendarTheme.displayNameTextStyle!; + final Color resourceHoveringColor = (themeData.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withValues(alpha: 0.04); + final TextStyle displayNameTextStyle = calendarTheme.displayNameTextStyle!; _circlePainter.color = resourceCellBorderColor; _circlePainter.strokeWidth = 0.5; _circlePainter.style = PaintingStyle.stroke; final double lineXPosition = isRTL ? 0.5 : size.width - 0.5; - canvas.drawLine(Offset(lineXPosition, 0), - Offset(lineXPosition, size.height), _circlePainter); + canvas.drawLine( + Offset(lineXPosition, 0), + Offset(lineXPosition, size.height), + _circlePainter, + ); final int count = resources!.length; if (resourceViewSettings.showAvatar) { for (int i = 0; i < count; i++) { canvas.save(); final CalendarResource resource = resources![i]; _drawResourceBorder( - resource, canvas, size, actualItemHeight, yPosition, radius); - _drawDisplayName(resource, displayNameTextStyle, canvas, size, - yPosition, actualItemHeight, radius); + resource, + canvas, + size, + actualItemHeight, + yPosition, + radius, + ); + _drawDisplayName( + resource, + displayNameTextStyle, + canvas, + size, + yPosition, + actualItemHeight, + radius, + ); _circlePainter.style = PaintingStyle.fill; - _drawInnerCircle(resource, canvas, size, actualItemWidth, - actualItemHeight, yPosition); + _drawInnerCircle( + resource, + canvas, + size, + actualItemWidth, + actualItemHeight, + yPosition, + ); _circlePainter.color = resourceCellBorderColor; _circlePainter.strokeWidth = 0.5; _circlePainter.style = PaintingStyle.stroke; - canvas.drawLine(Offset(0, yPosition), Offset(size.width, yPosition), - _circlePainter); + canvas.drawLine( + Offset(0, yPosition), + Offset(size.width, yPosition), + _circlePainter, + ); if (mouseHoverPosition != null) { _addHovering(canvas, size, yPosition, resourceHoveringColor); @@ -501,8 +559,15 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { for (int i = 0; i < count; i++) { final CalendarResource resource = resources![i]; _drawResourceBackground(canvas, size, resource, yPosition); - _drawDisplayName(resource, displayNameTextStyle, canvas, size, - yPosition, actualItemHeight, radius); + _drawDisplayName( + resource, + displayNameTextStyle, + canvas, + size, + yPosition, + actualItemHeight, + radius, + ); _addHovering(canvas, size, yPosition, resourceHoveringColor); yPosition += resourceItemHeight; } @@ -510,7 +575,11 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { } void _addHovering( - Canvas canvas, Size size, double yPosition, Color resourceHoveringColor) { + Canvas canvas, + Size size, + double yPosition, + Color resourceHoveringColor, + ) { if (mouseHoverPosition != null && mouseHoverPosition!.dy > yPosition && mouseHoverPosition!.dy < (yPosition + resourceItemHeight)) { @@ -518,19 +587,25 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { _circlePainter.color = resourceHoveringColor; const double padding = 0.5; canvas.drawRect( - Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight - padding), - _circlePainter); + Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight - padding), + _circlePainter, + ); } } void _drawResourceBackground( - Canvas canvas, Size size, CalendarResource resource, double yPosition) { + Canvas canvas, + Size size, + CalendarResource resource, + double yPosition, + ) { const double padding = 0.5; _circlePainter.color = resource.color; _circlePainter.style = PaintingStyle.fill; canvas.drawRect( - Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight - padding), - _circlePainter); + Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight - padding), + _circlePainter, + ); } /// Updates the text painter with the passed span. @@ -539,16 +614,23 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { _namePainter.textDirection = TextDirection.ltr; _namePainter.maxLines = 1; _namePainter.textWidthBasis = TextWidthBasis.longestLine; - _namePainter.textScaleFactor = textScaleFactor; + _namePainter.textScaler = TextScaler.linear(textScaleFactor); } /// Draws the outer circle border for the resource view. - void _drawResourceBorder(CalendarResource resource, Canvas canvas, Size size, - double actualItemHeight, double yPosition, double radius) { + void _drawResourceBorder( + CalendarResource resource, + Canvas canvas, + Size size, + double actualItemHeight, + double yPosition, + double radius, + ) { /// When the large text size given, the text must be cliped instead of /// overflow into next resource view, hence cliped the canvas. - canvas - .clipRect(Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight)); + canvas.clipRect( + Rect.fromLTWH(0, yPosition, size.width, resourceItemHeight), + ); _circlePainter.color = resource.color; _circlePainter.strokeWidth = _borderThickness; _circlePainter.style = PaintingStyle.stroke; @@ -556,49 +638,59 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { final double startYPosition = (_borderThickness / 2) + yPosition + actualItemHeight / 2; canvas.drawCircle( - Offset(startXPosition, startYPosition), radius, _circlePainter); + Offset(startXPosition, startYPosition), + radius, + _circlePainter, + ); } /// Draws the display name of the resource under the circle. void _drawDisplayName( - CalendarResource resource, - TextStyle displayNameTextStyle, - Canvas canvas, - Size size, - double yPosition, - double actualItemHeight, - double radius) { - final TextSpan span = - TextSpan(text: resource.displayName, style: displayNameTextStyle); + CalendarResource resource, + TextStyle displayNameTextStyle, + Canvas canvas, + Size size, + double yPosition, + double actualItemHeight, + double radius, + ) { + final TextSpan span = TextSpan( + text: resource.displayName, + style: displayNameTextStyle, + ); _updateNamePainter(span); - _namePainter.layout(minWidth: 0, maxWidth: size.width); + _namePainter.layout(maxWidth: size.width); final double startXPosition = (size.width - _namePainter.width) / 2; - final double startYPosition = resourceViewSettings.showAvatar - ? (yPosition + (actualItemHeight / 2)) + - _borderThickness + - radius + - (_borderThickness / 2) - : yPosition + ((resourceItemHeight - _namePainter.height) / 2); + final double startYPosition = + resourceViewSettings.showAvatar + ? (yPosition + (actualItemHeight / 2)) + + _borderThickness + + radius + + (_borderThickness / 2) + : yPosition + ((resourceItemHeight - _namePainter.height) / 2); _namePainter.paint(canvas, Offset(startXPosition, startYPosition)); } /// Draws the image assigned to the resource, in the inside circle. void _drawImage( - Canvas canvas, - Size size, - CalendarResource resource, - double innerCircleYPosition, - double innerCircleXPosition, - double innerCircleWidth, - double innerCircleHeight) { + Canvas canvas, + Size size, + CalendarResource resource, + double innerCircleYPosition, + double innerCircleXPosition, + double innerCircleWidth, + double innerCircleHeight, + ) { final Offset offset = Offset(innerCircleXPosition, innerCircleYPosition); final Size size = Size(innerCircleWidth, innerCircleHeight); final ImageConfiguration configuration = ImageConfiguration(size: size); final Rect rect = offset & size; /// To render the image as circle. - final Rect square = - Rect.fromCircle(center: rect.center, radius: rect.shortestSide / 2.0); + 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) { @@ -611,15 +703,17 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { DecorationImagePainter? _getImagePainter(CalendarResource resource) { if (imagePainterCollection.isEmpty || !imagePainterCollection.containsKey(resource.id)) { - return DecorationImage(image: resource.image!) - .createPainter(_onPainterChanged); + return DecorationImage( + image: resource.image!, + ).createPainter(_onPainterChanged); } else if (imagePainterCollection.containsKey(resource.id) && - !imagePainterCollection[resource.id] - .toString() - .contains(resource.image.toString())) { + !imagePainterCollection[resource.id].toString().contains( + resource.image.toString(), + )) { imagePainterCollection[resource.id]!.dispose(); - return DecorationImage(image: resource.image!) - .createPainter(_onPainterChanged); + return DecorationImage( + image: resource.image!, + ).createPainter(_onPainterChanged); } return imagePainterCollection[resource.id]; @@ -638,43 +732,62 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { /// Draws the inner circle for the resource with the short term of the /// display name, and fills the resources color in background. - void _drawInnerCircle(CalendarResource resource, Canvas canvas, Size size, - double actualItemWidth, double actualItemHeight, double yPosition) { + void _drawInnerCircle( + CalendarResource resource, + Canvas canvas, + Size size, + double actualItemWidth, + double actualItemHeight, + double yPosition, + ) { const double padding = 0.3; final double innerCircleWidth = actualItemWidth - (_borderThickness * 2) - (padding * 2); final double innerCircleHeight = actualItemHeight - (_borderThickness * 2) - (padding * 2); - final double innerRadius = innerCircleWidth > innerCircleHeight - ? innerCircleHeight / 2 - : innerCircleWidth / 2; + final double innerRadius = + innerCircleWidth > innerCircleHeight + ? innerCircleHeight / 2 + : innerCircleWidth / 2; final double innerCircleXPosition = (size.width - actualItemWidth) / 2 + _borderThickness + padding; final double innerCircleYPosition = (_borderThickness / 2) + yPosition + _borderThickness + padding; if (resource.image != null) { - _drawImage(canvas, size, resource, innerCircleYPosition, - innerCircleXPosition, innerCircleWidth, innerCircleHeight); + _drawImage( + canvas, + size, + resource, + innerCircleYPosition, + innerCircleXPosition, + innerCircleWidth, + innerCircleHeight, + ); return; } double startXPosition = innerCircleXPosition + (innerCircleWidth / 2); double startYPosition = innerCircleYPosition + (innerCircleHeight / 2); canvas.drawCircle( - Offset(startXPosition, startYPosition), innerRadius, _circlePainter); + Offset(startXPosition, startYPosition), + innerRadius, + _circlePainter, + ); final List splitName = resource.displayName.split(' '); - final String shortName = splitName.length > 1 - ? splitName[0].substring(0, 1) + splitName[1].substring(0, 1) - : splitName[0].substring(0, 1); + final String shortName = + splitName.length > 1 + ? splitName[0].substring(0, 1) + splitName[1].substring(0, 1) + : splitName[0].substring(0, 1); final TextSpan span = TextSpan( - text: shortName, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w500, - fontFamily: 'Roboto')); + text: shortName, + style: themeData.textTheme.bodyLarge!.copyWith( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w500, + ), + ); _updateNamePainter(span); - _namePainter.layout(minWidth: 0, maxWidth: innerCircleWidth); + _namePainter.layout(maxWidth: innerCircleWidth); startXPosition = innerCircleXPosition + ((innerCircleWidth - _namePainter.width) / 2); startYPosition = @@ -691,13 +804,15 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { double top = 0; 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( - label: resource.displayName + resource.id.toString(), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH(0, top, size.width, resourceItemHeight), + properties: SemanticsProperties( + label: resource.displayName + resource.id.toString(), + textDirection: TextDirection.ltr, + ), ), - )); + ); top += resourceItemHeight; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart index edf8ac267..3312fa44d 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart @@ -213,7 +213,7 @@ class DragAndDropSettings with Diagnosticable { final Duration autoNavigateDelay; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -236,22 +236,33 @@ class DragAndDropSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('allowNavigation', allowNavigation)); + properties.add( + DiagnosticsProperty('allowNavigation', allowNavigation), + ); properties.add(DiagnosticsProperty('allowScroll', allowScroll)); - properties - .add(DiagnosticsProperty('showTimeIndicator', showTimeIndicator)); - properties.add(DiagnosticsProperty( - 'timeIndicatorStyle', timeIndicatorStyle)); - properties.add(DiagnosticsProperty( - 'indicatorTimeFormat', indicatorTimeFormat)); properties.add( - DiagnosticsProperty('autoNavigateDelay', autoNavigateDelay)); + DiagnosticsProperty('showTimeIndicator', showTimeIndicator), + ); + properties.add( + DiagnosticsProperty('timeIndicatorStyle', timeIndicatorStyle), + ); + properties.add( + DiagnosticsProperty('indicatorTimeFormat', indicatorTimeFormat), + ); + properties.add( + DiagnosticsProperty('autoNavigateDelay', autoNavigateDelay), + ); } @override int get hashCode { - return hashValues(allowNavigation, allowScroll, showTimeIndicator, - timeIndicatorStyle, indicatorTimeFormat, autoNavigateDelay); + return Object.hash( + allowNavigation, + allowScroll, + showTimeIndicator, + timeIndicatorStyle, + indicatorTimeFormat, + autoNavigateDelay, + ); } } 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 864e28fcc..4697767ba 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 @@ -35,8 +35,11 @@ class CalendarHeaderStyle with Diagnosticable { /// Creates a header style for calendar. /// /// The properties allows to customize the header view of [SfCalendar]. - const CalendarHeaderStyle( - {this.textAlign = TextAlign.start, this.backgroundColor, this.textStyle}); + const CalendarHeaderStyle({ + this.textAlign = TextAlign.start, + this.backgroundColor, + this.textStyle, + }); /// The text style for the text in the [SfCalendar] header view. /// @@ -115,7 +118,7 @@ class CalendarHeaderStyle with Diagnosticable { final Color? backgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -142,6 +145,6 @@ class CalendarHeaderStyle with Diagnosticable { @override int get hashCode { - return hashValues(textStyle, textAlign, backgroundColor); + return Object.hash(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 2e902d542..1453cf8ee 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,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; +import '../../../calendar.dart'; /// The settings have properties which allow to customize the month view of /// the [SfCalendar]. @@ -56,22 +56,22 @@ class MonthViewSettings with Diagnosticable { /// Creates a Month view settings for calendar. /// /// The properties allows to customize the month view of [SfCalendar]. - const MonthViewSettings( - {this.appointmentDisplayCount = 4, - this.numberOfWeeksInView = 6, - this.appointmentDisplayMode = MonthAppointmentDisplayMode.indicator, - this.showAgenda = false, - this.navigationDirection = MonthNavigationDirection.horizontal, - this.dayFormat = 'EE', - this.agendaItemHeight = -1, - this.showTrailingAndLeadingDates = true, - this.agendaViewHeight = -1, - this.monthCellStyle = const MonthCellStyle(), - this.agendaStyle = const AgendaStyle()}) - : assert(appointmentDisplayCount >= 0), - assert(numberOfWeeksInView >= 1 && numberOfWeeksInView <= 6), - assert(agendaItemHeight >= -1), - assert(agendaViewHeight >= -1); + const MonthViewSettings({ + this.appointmentDisplayCount = 3, + this.numberOfWeeksInView = 6, + this.appointmentDisplayMode = MonthAppointmentDisplayMode.indicator, + this.showAgenda = false, + this.navigationDirection = MonthNavigationDirection.horizontal, + this.dayFormat = 'EE', + this.agendaItemHeight = -1, + this.showTrailingAndLeadingDates = true, + this.agendaViewHeight = -1, + this.monthCellStyle = const MonthCellStyle(), + this.agendaStyle = const AgendaStyle(), + }) : assert(appointmentDisplayCount >= 0), + assert(numberOfWeeksInView >= 1 && numberOfWeeksInView <= 6), + assert(agendaItemHeight >= -1), + assert(agendaViewHeight >= -1); /// Formats the text in the [SfCalendar] month view view header. /// @@ -413,7 +413,7 @@ class MonthViewSettings with Diagnosticable { /// The number of appointments to be displayed in month cell of [SfCalendar]. /// - /// Defaults to `4`. + /// Defaults to `3`. /// /// _Note:_ if the appointment count is less than the value set to this /// property in a particular day, then the month cell will display the @@ -617,7 +617,7 @@ class MonthViewSettings with Diagnosticable { final MonthNavigationDirection navigationDirection; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -650,22 +650,35 @@ class MonthViewSettings with Diagnosticable { 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( + 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)); + properties.add( + DiagnosticsProperty( + 'showTrailingAndLeadingDates', + showTrailingAndLeadingDates, + ), + ); } @override int get hashCode { - return hashValues( + return Object.hash( dayFormat, monthCellStyle, agendaStyle, @@ -739,11 +752,16 @@ class AgendaStyle with Diagnosticable { /// /// The properties allows to customize the agenda view in month view of /// [SfCalendar]. - const AgendaStyle( - {this.appointmentTextStyle, - this.dayTextStyle, - this.dateTextStyle, - this.backgroundColor}); + const AgendaStyle({ + this.appointmentTextStyle, + this.dayTextStyle, + this.dateTextStyle, + this.backgroundColor, + this.placeholderTextStyle = const TextStyle( + color: Colors.grey, + fontSize: 15, + ), + }); /// The text style for the text in the [Appointment] view in [SfCalendar] /// month agenda view. @@ -798,6 +816,34 @@ class AgendaStyle with Diagnosticable { /// ``` final TextStyle? appointmentTextStyle; + /// The text style for the text in the placeholder (no event text and + /// no selected date text) of the [SfCalendar] month agenda view. + /// + /// See also: + /// * [MonthViewSettings], to customize the month view of the calendar. + /// * [ScheduleViewSettings], to customize the schedule view of the calendar. + /// * [AgendaStyle], to customize the month agenda view of the calendar. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.month, + /// monthViewSettings: const MonthViewSettings(showAgenda: true, + /// agendaStyle: AgendaStyle( + /// placeholderTextStyle:TextStyle( + /// color: Colors.white, + /// fontSize: 20, + /// backgroundColor: + /// Colors.red),)), + /// ), + /// ); + /// } + /// + /// + final TextStyle placeholderTextStyle; + /// The text style for the text in the day text of [SfCalendar] month agenda /// view. /// @@ -952,7 +998,7 @@ class AgendaStyle with Diagnosticable { final Color? backgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -967,28 +1013,42 @@ class AgendaStyle with Diagnosticable { return otherStyle.appointmentTextStyle == appointmentTextStyle && otherStyle.dayTextStyle == dayTextStyle && otherStyle.dateTextStyle == dateTextStyle && - otherStyle.backgroundColor == backgroundColor; + otherStyle.backgroundColor == backgroundColor && + otherStyle.placeholderTextStyle == placeholderTextStyle; } @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( + DiagnosticsProperty( + 'appointmentTextStyle', + appointmentTextStyle, + ), + ); + properties.add( + DiagnosticsProperty('dateTextStyle', dateTextStyle), + ); + properties.add( + DiagnosticsProperty('dayTextStyle', dayTextStyle), + ); properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add( + DiagnosticsProperty( + 'placeholderTextStyle', + placeholderTextStyle, + ), + ); } @override int get hashCode { - return hashValues( + return Object.hash( appointmentTextStyle, dayTextStyle, dateTextStyle, backgroundColor, + placeholderTextStyle, ); } } @@ -1084,10 +1144,12 @@ class MonthCellStyle with Diagnosticable { this.trailingDatesBackgroundColor, this.leadingDatesBackgroundColor, this.textStyle, - @Deprecated('Moved the same [todayTextStyle] to SfCalendar class, ' - 'use [todayTextStyle] property from SfCalendar class') - // ignore: deprecated_member_use_from_same_package, deprecated_member_use - this.todayTextStyle, + @Deprecated( + 'Moved the same [todayTextStyle] to SfCalendar class, ' + 'use [todayTextStyle] property from SfCalendar class', + ) + // ignore: deprecated_member_use_from_same_package, deprecated_member_use + this.todayTextStyle, this.trailingDatesTextStyle, this.leadingDatesTextStyle, }); @@ -1183,8 +1245,10 @@ class MonthCellStyle with Diagnosticable { /// ); /// } /// ``` - @Deprecated('Moved the same [todayTextStyle] to SfCalendar class, use ' - '[todayTextStyle] property from SfCalendar class') + @Deprecated( + 'Moved the same [todayTextStyle] to SfCalendar class, use ' + '[todayTextStyle] property from SfCalendar class', + ) final TextStyle? todayTextStyle; /// The text style for the text in the trailing dates cell of [SfCalendar] @@ -1518,7 +1582,7 @@ class MonthCellStyle with Diagnosticable { final Color? leadingDatesBackgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1544,21 +1608,34 @@ class MonthCellStyle with Diagnosticable { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('textStyle', textStyle)); - properties.add(DiagnosticsProperty( - 'trailingDatesTextStyle', trailingDatesTextStyle)); - properties.add(DiagnosticsProperty( - 'leadingDatesTextStyle', leadingDatesTextStyle)); + 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)); + properties.add( + ColorProperty( + 'trailingDatesBackgroundColor', + trailingDatesBackgroundColor, + ), + ); + properties.add( + ColorProperty('leadingDatesBackgroundColor', leadingDatesBackgroundColor), + ); } @override int get hashCode { - return hashValues( + return Object.hash( textStyle, trailingDatesTextStyle, leadingDatesTextStyle, 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 b75e601bb..eeb1fdbf5 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 @@ -68,13 +68,13 @@ class ResourceViewSettings with Diagnosticable { /// Creates a resource view settings for calendar. /// /// The properties allows to customize the resource view of [SfCalendar]. - const ResourceViewSettings( - {this.size = 75, - this.visibleResourceCount = -1, - this.showAvatar = true, - this.displayNameTextStyle}) - : assert(size >= 0), - assert(visibleResourceCount >= -1); + const ResourceViewSettings({ + this.size = 75, + this.visibleResourceCount = -1, + this.showAvatar = true, + this.displayNameTextStyle, + }) : assert(size >= 0), + assert(visibleResourceCount >= -1); /// The number of resources to be displayed in the available screen height in /// [SfCalendar] @@ -222,7 +222,7 @@ class ResourceViewSettings with Diagnosticable { final bool showAvatar; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -243,8 +243,12 @@ class ResourceViewSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(DiagnosticsProperty( - 'displayNameTextStyle', displayNameTextStyle)); + properties.add( + DiagnosticsProperty( + 'displayNameTextStyle', + displayNameTextStyle, + ), + ); properties.add(DoubleProperty('size', size)); properties.add(DiagnosticsProperty('showAvatar', showAvatar)); properties.add(IntProperty('visibleResourceCount', visibleResourceCount)); @@ -252,7 +256,11 @@ class ResourceViewSettings with Diagnosticable { @override int get hashCode { - return hashValues( - size, visibleResourceCount, showAvatar, displayNameTextStyle); + return Object.hash( + 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 8567b733c..06d1f20ac 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 @@ -46,14 +46,18 @@ class ScheduleViewSettings with Diagnosticable { /// Creates a schedule view settings for calendar. /// /// The properties allows to customize the schedule view of [SfCalendar]. - const ScheduleViewSettings( - {this.appointmentTextStyle, - this.appointmentItemHeight = -1, - this.hideEmptyScheduleWeek = false, - this.monthHeaderSettings = const MonthHeaderSettings(), - this.weekHeaderSettings = const WeekHeaderSettings(), - this.dayHeaderSettings = const DayHeaderSettings()}) - : assert(appointmentItemHeight >= -1); + const ScheduleViewSettings({ + this.appointmentTextStyle, + this.appointmentItemHeight = -1, + this.hideEmptyScheduleWeek = false, + this.monthHeaderSettings = const MonthHeaderSettings(), + this.weekHeaderSettings = const WeekHeaderSettings(), + this.dayHeaderSettings = const DayHeaderSettings(), + this.placeholderTextStyle = const TextStyle( + color: Colors.grey, + fontSize: 15, + ), + }) : assert(appointmentItemHeight >= -1); /// Sets the style to customize month label in [SfCalendar] schedule view. /// @@ -223,6 +227,32 @@ class ScheduleViewSettings with Diagnosticable { /// ``` final TextStyle? appointmentTextStyle; + /// The text style for the text in the placeholder (no event + /// text) of the [SfCalendar] schedule view. + /// + /// See also: + /// * [MonthViewSettings], to customize the month view of the calendar. + /// * [ScheduleViewSettings], to customize the schedule view of the calendar. + /// * [AgendaStyle], to customize the month agenda view of the calendar. + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.schedule, + /// scheduleViewSettings: const ScheduleViewSettings( + /// placeholderTextStyle: TextStyle( + /// color: Colors.white, + /// fontSize: 20, + /// backgroundColor: Colors.red)), + /// ), + /// ); + /// } + /// + /// + final TextStyle placeholderTextStyle; + /// The height for each appointment view to layout within this in schedule /// view of [SfCalendar],. /// @@ -287,7 +317,7 @@ class ScheduleViewSettings with Diagnosticable { final bool hideEmptyScheduleWeek; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -304,35 +334,53 @@ class ScheduleViewSettings with Diagnosticable { otherStyle.hideEmptyScheduleWeek == hideEmptyScheduleWeek && otherStyle.monthHeaderSettings == monthHeaderSettings && otherStyle.weekHeaderSettings == weekHeaderSettings && - otherStyle.dayHeaderSettings == dayHeaderSettings; + otherStyle.dayHeaderSettings == dayHeaderSettings && + otherStyle.placeholderTextStyle == placeholderTextStyle; } @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)); + 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), + ); + properties.add( + DiagnosticsProperty( + 'placeholderTextStyle', + placeholderTextStyle, + ), + ); } @override int get hashCode { - return hashValues( - appointmentTextStyle, - appointmentItemHeight, - hideEmptyScheduleWeek, - monthHeaderSettings, - weekHeaderSettings, - dayHeaderSettings); + return Object.hash( + appointmentTextStyle, + appointmentItemHeight, + hideEmptyScheduleWeek, + monthHeaderSettings, + weekHeaderSettings, + dayHeaderSettings, + placeholderTextStyle, + ); } } @@ -382,13 +430,13 @@ class MonthHeaderSettings with Diagnosticable { /// /// The properties allows to customize the month header in schedule view of /// [SfCalendar]. - const MonthHeaderSettings( - {this.monthFormat = 'MMMM yyyy', - this.height = 150, - this.textAlign = TextAlign.start, - this.backgroundColor = const Color.fromRGBO(17, 178, 199, 1), - this.monthTextStyle}) - : assert(height >= -1); + const MonthHeaderSettings({ + this.monthFormat = 'MMMM yyyy', + this.height = 150, + this.textAlign = TextAlign.start, + this.backgroundColor = const Color.fromRGBO(17, 178, 199, 1), + this.monthTextStyle, + }) : assert(height >= -1); /// Formats the month label text in the month label [SfCalendar] /// schedule view. @@ -565,7 +613,7 @@ class MonthHeaderSettings with Diagnosticable { final TextStyle? monthTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -587,8 +635,9 @@ class MonthHeaderSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('monthTextStyle', monthTextStyle)); + properties.add( + DiagnosticsProperty('monthTextStyle', monthTextStyle), + ); properties.add(DoubleProperty('height', height)); properties.add(EnumProperty('textAlign', textAlign)); properties.add(ColorProperty('backgroundColor', backgroundColor)); @@ -597,8 +646,13 @@ class MonthHeaderSettings with Diagnosticable { @override int get hashCode { - return hashValues( - monthFormat, height, textAlign, backgroundColor, monthTextStyle); + return Object.hash( + monthFormat, + height, + textAlign, + backgroundColor, + monthTextStyle, + ); } } @@ -649,14 +703,14 @@ class WeekHeaderSettings with Diagnosticable { /// /// The properties allows to customize the week header in schedule view of /// [SfCalendar]. - const WeekHeaderSettings( - {this.startDateFormat, - this.endDateFormat, - this.height = 30, - this.textAlign = TextAlign.start, - this.backgroundColor = Colors.transparent, - this.weekTextStyle}) - : assert(height >= -1); + const WeekHeaderSettings({ + this.startDateFormat, + this.endDateFormat, + this.height = 30, + this.textAlign = TextAlign.start, + this.backgroundColor = Colors.transparent, + this.weekTextStyle, + }) : assert(height >= -1); /// Formats the week start date text in the week label of [SfCalendar] /// schedule view. @@ -883,7 +937,7 @@ class WeekHeaderSettings with Diagnosticable { final TextStyle? weekTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -906,8 +960,9 @@ class WeekHeaderSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('weekTextStyle', weekTextStyle)); + properties.add( + DiagnosticsProperty('weekTextStyle', weekTextStyle), + ); properties.add(DoubleProperty('height', height)); properties.add(EnumProperty('textAlign', textAlign)); properties.add(ColorProperty('backgroundColor', backgroundColor)); @@ -917,8 +972,14 @@ class WeekHeaderSettings with Diagnosticable { @override int get hashCode { - return hashValues(startDateFormat, endDateFormat, height, textAlign, - backgroundColor, weekTextStyle); + return Object.hash( + startDateFormat, + endDateFormat, + height, + textAlign, + backgroundColor, + weekTextStyle, + ); } } @@ -968,12 +1029,12 @@ class DayHeaderSettings with Diagnosticable { /// /// The properties allows to customize the day header in schedule view of /// [SfCalendar]. - const DayHeaderSettings( - {this.dayFormat = 'EEE', - this.width = -1, - this.dayTextStyle, - this.dateTextStyle}) - : assert(width >= -1); + const DayHeaderSettings({ + this.dayFormat = 'EEE', + this.width = -1, + this.dayTextStyle, + this.dateTextStyle, + }) : assert(width >= -1); /// Formats the day text in the day label of [SfCalendar] schedule view. /// @@ -1120,7 +1181,7 @@ class DayHeaderSettings with Diagnosticable { final TextStyle? dateTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1141,16 +1202,18 @@ class DayHeaderSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('dayTextStyle', dayTextStyle)); - properties - .add(DiagnosticsProperty('dateTextStyle', dateTextStyle)); + 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); + return Object.hash(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 f6ec5d814..d4a191cd6 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,10 +1,11 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart' show IterableDiagnostics; +import '../../../calendar.dart'; + /// 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. @@ -53,18 +54,19 @@ class TimeRegion with Diagnosticable { /// /// The time region used to highlight and block the specific timeslots in /// timeslots view of [SfCalendar]. - TimeRegion( - {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}); + TimeRegion({ + 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, + }); /// Used to specify the start time of the [TimeRegion]. /// @@ -541,36 +543,38 @@ class TimeRegion with Diagnosticable { /// 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}) { + TimeRegion copyWith({ + 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, - 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); + 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, + ); } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -597,27 +601,39 @@ class TimeRegion with Diagnosticable { @override int get hashCode { - return hashValues( - startTime, - endTime, - color, - recurrenceRule, - textStyle, - enablePointerInteraction, - hashList(recurrenceExceptionDates), - hashList(resourceIds), - text, - iconData, - timeZone); + return Object.hash( + startTime, + endTime, + color, + recurrenceRule, + textStyle, + enablePointerInteraction, + + /// Below condition is referred from text style class + /// https://api.flutter.dev/flutter/painting/TextStyle/hashCode.html + recurrenceExceptionDates == null + ? null + : Object.hashAll(recurrenceExceptionDates!), + resourceIds == null ? null : Object.hashAll(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( + 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)); @@ -626,7 +642,11 @@ class TimeRegion with Diagnosticable { properties.add(DiagnosticsProperty('endTime', endTime)); properties.add(DiagnosticsProperty('textStyle', textStyle)); properties.add(DiagnosticsProperty('iconData', iconData)); - properties.add(DiagnosticsProperty( - 'enablePointerInteraction', enablePointerInteraction)); + 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 0954a2231..bd3c5e1fb 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,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_calendar/calendar.dart'; +import '../../../calendar.dart'; /// The settings have properties which allow to customize the time slot views /// of the [SfCalendar]. @@ -57,29 +57,29 @@ class TimeSlotViewSettings with Diagnosticable { /// Creates a timeslot view settings for calendar. /// /// The properties allows to customize the timeslot views of [SfCalendar]. - const TimeSlotViewSettings( - {this.startHour = 0, - this.endHour = 24, - this.nonWorkingDays = const [DateTime.saturday, DateTime.sunday], - this.timeFormat = 'h a', - this.timeInterval = const Duration(minutes: 60), - this.timeIntervalHeight = 40, - this.timeIntervalWidth = -2, - this.timelineAppointmentHeight = -1, - this.minimumAppointmentDuration, - this.dateFormat = 'd', - this.dayFormat = 'EE', - this.timeRulerSize = -1, - this.timeTextStyle, - this.allDayPanelColor, - this.numberOfDaysInView = -1}) - : assert(startHour >= 0 && startHour <= 24), - assert(endHour >= 0 && endHour <= 24), - assert(timeIntervalHeight >= -1), - assert(timeIntervalWidth >= -2), - assert(timelineAppointmentHeight >= -1), - assert(timeRulerSize >= -1), - assert(numberOfDaysInView >= -1); + const TimeSlotViewSettings({ + this.startHour = 0, + this.endHour = 24, + this.nonWorkingDays = const [DateTime.saturday, DateTime.sunday], + this.timeFormat = 'h a', + this.timeInterval = const Duration(minutes: 60), + this.timeIntervalHeight = 40, + this.timeIntervalWidth = -2, + this.timelineAppointmentHeight = -1, + this.minimumAppointmentDuration, + this.dateFormat = 'd', + this.dayFormat = 'EE', + this.timeRulerSize = -1, + this.timeTextStyle, + this.allDayPanelColor, + this.numberOfDaysInView = -1, + }) : assert(startHour >= 0 && startHour <= 24), + assert(endHour >= 0 && endHour <= 24), + assert(timeIntervalHeight >= -1), + assert(timeIntervalWidth >= -2), + assert(timelineAppointmentHeight >= -1), + assert(timeRulerSize >= -1), + assert(numberOfDaysInView >= -1); /// The start hour for the time slot views in [SfCalendar]. /// @@ -675,6 +675,9 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `-1`. /// + /// The calendar supports customizing the day count only for valid values. + /// It ranges from 1 to 7 and shows default behavior for invalid values. + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -690,7 +693,7 @@ class TimeSlotViewSettings with Diagnosticable { final int numberOfDaysInView; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -720,8 +723,9 @@ class TimeSlotViewSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('timeTextStyle', timeTextStyle)); + properties.add( + DiagnosticsProperty('timeTextStyle', timeTextStyle), + ); properties.add(DoubleProperty('startHour', startHour)); properties.add(DoubleProperty('endHour', endHour)); properties.add(IterableProperty('nonWorkingDays', nonWorkingDays)); @@ -729,9 +733,14 @@ class TimeSlotViewSettings with Diagnosticable { properties.add(DoubleProperty('timeIntervalHeight', timeIntervalHeight)); properties.add(DoubleProperty('timeIntervalWidth', timeIntervalWidth)); properties.add( - DoubleProperty('timelineAppointmentHeight', timelineAppointmentHeight)); - properties.add(DiagnosticsProperty( - 'minimumAppointmentDuration', minimumAppointmentDuration)); + DoubleProperty('timelineAppointmentHeight', timelineAppointmentHeight), + ); + properties.add( + DiagnosticsProperty( + 'minimumAppointmentDuration', + minimumAppointmentDuration, + ), + ); properties.add(DoubleProperty('timeRulerSize', timeRulerSize)); properties.add(StringProperty('timeFormat', timeFormat)); properties.add(StringProperty('dateFormat', dateFormat)); @@ -741,19 +750,20 @@ class TimeSlotViewSettings with Diagnosticable { @override int get hashCode { - return hashValues( - startHour, - endHour, - hashList(nonWorkingDays), - timeInterval, - timeIntervalHeight, - timeIntervalWidth, - timeFormat, - timelineAppointmentHeight, - minimumAppointmentDuration, - dateFormat, - dayFormat, - timeRulerSize, - timeTextStyle); + return Object.hash( + startHour, + endHour, + Object.hashAll(nonWorkingDays), + timeInterval, + timeIntervalHeight, + timeIntervalWidth, + timeFormat, + timelineAppointmentHeight, + minimumAppointmentDuration, + dateFormat, + dayFormat, + timeRulerSize, + timeTextStyle, + ); } } 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 0c13a6dc8..a82bae084 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 @@ -35,8 +35,11 @@ class ViewHeaderStyle with Diagnosticable { /// Creates a view header style for calendar. /// /// The properties allows to customize the view header view of [SfCalendar]. - const ViewHeaderStyle( - {this.backgroundColor, this.dateTextStyle, this.dayTextStyle}); + const ViewHeaderStyle({ + this.backgroundColor, + this.dateTextStyle, + this.dayTextStyle, + }); /// The color which fills the background of [SfCalendar] view header view. /// @@ -132,7 +135,7 @@ class ViewHeaderStyle with Diagnosticable { final TextStyle? dayTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -152,19 +155,17 @@ class ViewHeaderStyle with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(DiagnosticsProperty('dayTextStyle', dayTextStyle)); - properties - .add(DiagnosticsProperty('dateTextStyle', dateTextStyle)); + properties.add( + DiagnosticsProperty('dayTextStyle', dayTextStyle), + ); + properties.add( + DiagnosticsProperty('dateTextStyle', dateTextStyle), + ); properties.add(ColorProperty('backgroundColor', backgroundColor)); } @override int get hashCode { - return hashValues( - backgroundColor, - dayTextStyle, - dateTextStyle, - ); + return Object.hash(backgroundColor, dayTextStyle, dateTextStyle); } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart index c2a6196d1..c0930a588 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart @@ -81,7 +81,7 @@ class WeekNumberStyle with Diagnosticable { final TextStyle? textStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -106,9 +106,6 @@ class WeekNumberStyle with Diagnosticable { @override int get hashCode { - return hashValues( - backgroundColor, - textStyle, - ); + return Object.hash(backgroundColor, textStyle); } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart index abd5c6453..c93e49568 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart @@ -36,6 +36,7 @@ import 'settings/time_region.dart'; import 'settings/time_slot_view_settings.dart'; import 'settings/view_header_style.dart'; import 'settings/week_number_style.dart'; +import 'theme.dart'; import 'views/calendar_view.dart'; /// Specifies the unconfirmed ripple animation duration used on custom splash. @@ -195,12 +196,13 @@ class SfCalendar extends StatefulWidget { DateTime? minDate, DateTime? maxDate, this.appointmentTextStyle = const TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.w500, - fontFamily: 'Roboto'), + color: Colors.white, + fontSize: -1, + fontWeight: FontWeight.w500, + ), this.showNavigationArrow = false, this.showDatePickerButton = false, + this.showTodayButton = false, this.allowViewNavigation = false, this.showCurrentTimeIndicator = true, this.cellEndPadding = -1, @@ -221,18 +223,24 @@ class SfCalendar extends StatefulWidget { this.onDragStart, this.onDragUpdate, this.onDragEnd, - }) : assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), - assert(headerHeight >= 0), - assert(viewHeaderHeight >= -1), - assert(minDate == null || maxDate == null || minDate.isBefore(maxDate)), - assert(minDate == null || maxDate == null || maxDate.isAfter(minDate)), - assert(cellEndPadding >= -1), - 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), - super(key: key); + }) : assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), + assert(headerHeight >= 0), + assert(viewHeaderHeight >= -1), + assert(minDate == null || maxDate == null || minDate.isBefore(maxDate)), + assert(minDate == null || maxDate == null || maxDate.isAfter(minDate)), + assert(cellEndPadding >= -1), + initialDisplayDate = + initialDisplayDate ?? + DateTime( + DateTime.now().year, + DateTime.now().month, + DateTime.now().day, + 08, + 45, + ), + minDate = minDate ?? DateTime(01), + maxDate = maxDate ?? DateTime(9999, 12, 31), + super(key: key); /// A builder that sets the widget to display on the calendar widget when /// the appointments are being loaded. @@ -351,7 +359,6 @@ class SfCalendar extends StatefulWidget { /// Displays the date picker when the [SfCalendar] header date is tapped. /// /// The date picker will be used for quick date navigation in [SfCalendar]. - /// It also shows Today navigation button on header view. /// /// Defaults to `false`. /// @@ -375,6 +382,26 @@ class SfCalendar extends StatefulWidget { /// ``` final bool showDatePickerButton; + /// Displays the today button on the header view of [SfCalendar]. + /// + /// If the property set as true then the header view will show the today + /// button which is used to navigate the today view. + /// + /// Defaults to `false`. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.day, + /// showTodayButton: true, + /// ), + /// ); + /// } + /// + /// ``` + final bool showTodayButton; + /// Displays an indicator that shows the current time in the time slot views /// of [SfCalendar]. By default, the indicator color matches the /// [todayHighlightColor]. @@ -627,7 +654,7 @@ class SfCalendar extends StatefulWidget { /// startTime: date, /// endTime: date.add(Duration(hours: 2)), /// enablePointerInteraction: false, - /// color: Colors.grey.withOpacity(0.2), + /// color: Colors.grey.withValues(alpha:0.2), /// text: 'Break')); /// /// return regions; @@ -1904,7 +1931,7 @@ class SfCalendar extends StatefulWidget { /// startTime: DateTime.now(), /// endTime: DateTime.now().add(Duration(hours: 1)), /// enablePointerInteraction: false, - /// color: Colors.grey.withOpacity(0.2), + /// color: Colors.grey.withValues(alpha:0.2), /// text: 'Break')); /// /// return regions; @@ -1937,6 +1964,8 @@ class SfCalendar extends StatefulWidget { /// ), /// ); /// } + /// + /// ``` final bool showWeekNumber; /// Defines the text style for the text in the week number panel of the @@ -1966,6 +1995,8 @@ class SfCalendar extends StatefulWidget { /// ), /// ); /// } + /// + /// ``` final WeekNumberStyle weekNumberStyle; /// Defines the builder that builds a widget and replaces the header @@ -2013,6 +2044,7 @@ class SfCalendar extends StatefulWidget { /// ), /// ); /// } + /// ``` final ResourceViewHeaderBuilder? resourceViewHeaderBuilder; /// Allows to drag and drop the appointment, to reschedule this into @@ -2050,6 +2082,7 @@ class SfCalendar extends StatefulWidget { /// ), /// ); /// } + /// ``` final bool allowDragAndDrop; /// Allows to customize the drag and drop environment. @@ -2077,6 +2110,7 @@ class SfCalendar extends StatefulWidget { /// ), /// ); /// } + /// ``` final DragAndDropSettings dragAndDropSettings; /// Called whenever the appointment starts to drag in the [SfCalendar]. @@ -2104,6 +2138,7 @@ class SfCalendar extends StatefulWidget { /// }), /// ); /// } + /// ``` final AppointmentDragStartCallback? onDragStart; /// Called whenever the appointment is dragging in the [SfCalendar]. @@ -2134,6 +2169,7 @@ class SfCalendar extends StatefulWidget { /// }), /// ); /// } + /// ``` final AppointmentDragUpdateCallback? onDragUpdate; /// Called when the dragging appointment is dropped in the [SfCalendar]. @@ -2163,6 +2199,7 @@ class SfCalendar extends StatefulWidget { /// }), /// ); /// } + /// ``` final AppointmentDragEndCallback? onDragEnd; /// An object that used for programmatic date navigation and date selection @@ -2264,7 +2301,7 @@ class SfCalendar extends StatefulWidget { /// ); /// } /// - /// ```` + /// ``` final bool allowAppointmentResize; /// Called whenever the appointment starts to resizing in [SfCalendar]. @@ -2393,19 +2430,33 @@ class SfCalendar extends StatefulWidget { /// /// ``` static List getRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {DateTime? specificStartDate, DateTime? specificEndDate}) { - assert(specificStartDate == null || - specificEndDate == null || - CalendarViewHelper.isSameOrBeforeDateTime( - specificEndDate, specificStartDate)); - assert(specificStartDate == null || - specificEndDate == null || - CalendarViewHelper.isSameOrAfterDateTime( - specificStartDate, specificEndDate)); + String rRule, + DateTime recurrenceStartDate, { + DateTime? specificStartDate, + DateTime? specificEndDate, + }) { + assert( + specificStartDate == null || + specificEndDate == null || + CalendarViewHelper.isSameOrBeforeDateTime( + specificEndDate, + specificStartDate, + ), + ); + assert( + specificStartDate == null || + specificEndDate == null || + CalendarViewHelper.isSameOrAfterDateTime( + specificStartDate, + specificEndDate, + ), + ); return RecurrenceHelper.getRecurrenceDateTimeCollection( - rRule, recurrenceStartDate, - specificStartDate: specificStartDate, specificEndDate: specificEndDate); + rRule, + recurrenceStartDate, + specificStartDate: specificStartDate, + specificEndDate: specificEndDate, + ); } /// Returns the recurrence properties based on the given recurrence rule and @@ -2478,13 +2529,19 @@ class SfCalendar extends StatefulWidget { /// DateTime(2019, 12, 16, 10), DateTime(2019, 12, 16, 12))); /// /// ``` - static String generateRRule(RecurrenceProperties recurrenceProperties, - DateTime appStartTime, DateTime appEndTime) { + static String generateRRule( + RecurrenceProperties recurrenceProperties, + DateTime appStartTime, + DateTime appEndTime, + ) { assert(CalendarViewHelper.isSameOrBeforeDateTime(appEndTime, appStartTime)); assert(CalendarViewHelper.isSameOrAfterDateTime(appStartTime, appEndTime)); return RecurrenceHelper.generateRRule( - recurrenceProperties, appStartTime, appEndTime); + recurrenceProperties, + appStartTime, + appEndTime, + ); } @override @@ -2497,89 +2554,177 @@ class SfCalendar extends StatefulWidget { properties.add(headerStyle.toDiagnosticsNode(name: 'headerStyle')); properties.add(viewHeaderStyle.toDiagnosticsNode(name: 'viewHeaderStyle')); properties.add( - timeSlotViewSettings.toDiagnosticsNode(name: 'timeSlotViewSettings')); + timeSlotViewSettings.toDiagnosticsNode(name: 'timeSlotViewSettings'), + ); + properties.add( + resourceViewSettings.toDiagnosticsNode(name: 'resourceViewSettings'), + ); properties.add( - resourceViewSettings.toDiagnosticsNode(name: 'resourceViewSettings')); - properties - .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); + monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings'), + ); properties.add( - scheduleViewSettings.toDiagnosticsNode(name: 'scheduleViewSettings')); + 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( + 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)); + DiagnosticsProperty('allowViewNavigation', allowViewNavigation), + ); + properties.add( + DiagnosticsProperty('showNavigationArrow', showNavigationArrow), + ); + properties.add( + DiagnosticsProperty( + 'viewNavigationMode', + viewNavigationMode, + ), + ); + properties.add( + DiagnosticsProperty('showDatePickerButton', showDatePickerButton), + ); + properties.add( + DiagnosticsProperty('showTodayButton', showTodayButton), + ); properties.add( - DiagnosticsProperty('showNavigationArrow', showNavigationArrow)); - properties.add(DiagnosticsProperty( - 'viewNavigationMode', viewNavigationMode)); - properties.add(DiagnosticsProperty( - 'showDatePickerButton', showDatePickerButton)); - properties.add(DiagnosticsProperty( - 'showCurrentTimeIndicator', showCurrentTimeIndicator)); + 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)); + 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('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( + 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( + 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')); - properties.add(DiagnosticsProperty( - 'resourceViewHeaderBuilder', resourceViewHeaderBuilder)); - properties - .add(DiagnosticsProperty('allowDragAndDrop', allowDragAndDrop)); properties.add( - dragAndDropSettings.toDiagnosticsNode(name: 'dragAndDropSettings')); - properties.add(DiagnosticsProperty( - 'onDragStart', onDragStart)); - properties.add(DiagnosticsProperty( - 'onDragUpdate', onDragUpdate)); - properties.add(DiagnosticsProperty( - 'onDragEnd', onDragEnd)); + IterableDiagnostics( + blackoutDates, + ).toDiagnosticsNode(name: 'blackoutDates'), + ); + properties.add( + IterableDiagnostics( + allowedViews, + ).toDiagnosticsNode(name: 'allowedViews'), + ); + properties.add( + IterableDiagnostics( + specialRegions, + ).toDiagnosticsNode(name: 'specialRegions'), + ); + properties.add( + DiagnosticsProperty( + 'resourceViewHeaderBuilder', + resourceViewHeaderBuilder, + ), + ); + properties.add( + DiagnosticsProperty('allowDragAndDrop', allowDragAndDrop), + ); + properties.add( + dragAndDropSettings.toDiagnosticsNode(name: 'dragAndDropSettings'), + ); + properties.add( + DiagnosticsProperty( + 'onDragStart', + onDragStart, + ), + ); + properties.add( + DiagnosticsProperty( + 'onDragUpdate', + onDragUpdate, + ), + ); + properties.add( + DiagnosticsProperty('onDragEnd', onDragEnd), + ); } } @@ -2597,12 +2742,17 @@ class _SfCalendarState extends State late ValueNotifier _agendaSelectedDate, _timelineMonthWeekNumberNotifier; - ValueNotifier _headerUpdateNotifier = - ValueNotifier(null); + ValueNotifier _headerUpdateNotifier = ValueNotifier( + null, + ); late String _locale; late SfLocalizations _localizations; late double _minWidth, _minHeight, _textScaleFactor; late SfCalendarThemeData _calendarTheme; + + /// Holds the framework theme data values and the value assigned on build + /// method. + late ThemeData _themeData; late ValueNotifier _headerHoverNotifier, _resourceHoverNotifier; late ValueNotifier _agendaDateNotifier, _agendaViewNotifier; @@ -2721,7 +2871,9 @@ class _SfCalendarState extends State @override void initState() { + _textScaleFactor = 1; _timeZoneLoaded = false; + timeZoneLoaded = _timeZoneLoaded; _showHeader = false; _calendarViewWidth = 0; initializeDateFormatting(); @@ -2739,37 +2891,44 @@ class _SfCalendarState extends State _selectedDate = _controller.selectedDate; _agendaSelectedDate = ValueNotifier(_selectedDate); _agendaSelectedDate.addListener(_agendaSelectedDateListener); - _currentDate = DateTimeHelper.getDateTimeValue(getValidDate(widget.minDate, - widget.maxDate, _controller.displayDate ?? widget.initialDisplayDate)); + _currentDate = DateTimeHelper.getDateTimeValue( + getValidDate( + widget.minDate, + widget.maxDate, + _controller.displayDate ?? widget.initialDisplayDate, + ), + ); _controller.displayDate = _currentDate; _scheduleDisplayDate = _controller.displayDate!; _controller.view ??= widget.view; _view = _controller.view!; - _timelineMonthWeekNumberNotifier = - ValueNotifier(_controller.displayDate); + _timelineMonthWeekNumberNotifier = ValueNotifier( + _controller.displayDate, + ); if (_selectedDate != null) { _updateSelectionChangedCallback(); } _updateCurrentVisibleDates(); widget.dataSource?.addListener(_dataSourceChangedListener); - _resourceCollection = - CalendarViewHelper.cloneList(widget.dataSource?.resources); + _resourceCollection = CalendarViewHelper.cloneList( + widget.dataSource?.resources, + ); if (_view == CalendarView.month && widget.monthViewSettings.showAgenda) { - _agendaScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _agendaScrollController = ScrollController(); } if (CalendarViewHelper.isResourceEnabled(widget.dataSource, _view)) { - _resourcePanelScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _resourcePanelScrollController = ScrollController(); } _controller.addPropertyChangedListener(_calendarValueChangedListener); if (_view == CalendarView.schedule && CalendarViewHelper.shouldRaiseViewChangedCallback( - widget.onViewChanged)) { - CalendarViewHelper.raiseViewChangedCallback( - widget, [_controller.displayDate!]); + widget.onViewChanged, + )) { + CalendarViewHelper.raiseViewChangedCallback(widget, [ + _controller.displayDate!, + ]); } _initScheduleViewProperties(); @@ -2783,16 +2942,13 @@ class _SfCalendarState extends State @override void didChangeDependencies() { - _textScaleFactor = MediaQuery.of(context).textScaleFactor; + _textScaleFactor = MediaQuery.textScalerOf(context).scale(1); // default width value will be device width when the widget placed inside a // infinity width widget _minWidth = MediaQuery.of(context).size.width; // default height for the widget when the widget placed inside a infinity // height widget _minHeight = 300; - final SfCalendarThemeData calendarThemeData = SfCalendarTheme.of(context); - _calendarTheme = - _getThemeDataValue(calendarThemeData, Theme.of(context).colorScheme); //// localeOf(context) returns the locale from material app when SfCalendar locale value as null _locale = Localizations.localeOf(context).toString(); _localizations = SfLocalizations.of(context); @@ -2808,8 +2964,9 @@ class _SfCalendarState extends State @override void didUpdateWidget(SfCalendar oldWidget) { if (oldWidget.controller != widget.controller) { - oldWidget.controller - ?.removePropertyChangedListener(_calendarValueChangedListener); + oldWidget.controller?.removePropertyChangedListener( + _calendarValueChangedListener, + ); _controller.removePropertyChangedListener(_calendarValueChangedListener); _controller = widget.controller ?? CalendarController(); if (widget.controller != null) { @@ -2821,8 +2978,13 @@ class _SfCalendarState extends State _controller.view = widget.controller!.view ?? _view; } else { _controller.selectedDate = widget.initialSelectedDate; - _currentDate = DateTimeHelper.getDateTimeValue(getValidDate( - widget.minDate, widget.maxDate, widget.initialDisplayDate)); + _currentDate = DateTimeHelper.getDateTimeValue( + getValidDate( + widget.minDate, + widget.maxDate, + widget.initialDisplayDate, + ), + ); _controller.displayDate = _currentDate; _controller.view = widget.view; } @@ -2842,16 +3004,23 @@ class _SfCalendarState extends State _view != widget.controller!.view) { final CalendarView oldView = _view; _view = _controller.view ?? widget.view; - _currentDate = DateTimeHelper.getDateTimeValue(getValidDate( - widget.minDate, widget.maxDate, _updateCurrentDate(oldView))); + _currentDate = DateTimeHelper.getDateTimeValue( + getValidDate( + widget.minDate, + widget.maxDate, + _updateCurrentDate(oldView), + ), + ); _canScrollTimeSlotView = false; _controller.displayDate = _currentDate; _canScrollTimeSlotView = true; if (_view == CalendarView.schedule) { if (CalendarViewHelper.shouldRaiseViewChangedCallback( - widget.onViewChanged)) { - CalendarViewHelper.raiseViewChangedCallback( - widget, [_controller.displayDate!]); + widget.onViewChanged, + )) { + CalendarViewHelper.raiseViewChangedCallback(widget, [ + _controller.displayDate!, + ]); } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); @@ -2866,7 +3035,7 @@ class _SfCalendarState extends State _nextDates.clear(); _backwardWidgetHeights.clear(); _forwardWidgetHeights.clear(); - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _handleScheduleViewScrolled(); }); } @@ -2875,8 +3044,9 @@ class _SfCalendarState extends State widget.controller != null && oldWidget.controller!.displayDate != widget.controller!.displayDate) { if (_controller.displayDate != null) { - _currentDate = DateTimeHelper.getDateTimeValue(getValidDate( - widget.minDate, widget.maxDate, _controller.displayDate)); + _currentDate = DateTimeHelper.getDateTimeValue( + getValidDate(widget.minDate, widget.maxDate, _controller.displayDate), + ); } _controller.displayDate = _currentDate; @@ -2897,8 +3067,8 @@ class _SfCalendarState extends State } _forwardWidgetHeights = {}; _backwardWidgetHeights = {}; - _agendaScrollController = ScrollController() - ..addListener(_handleScheduleViewScrolled); + _agendaScrollController = + ScrollController()..addListener(_handleScheduleViewScrolled); _scheduleMaxDate = null; _scheduleMinDate = null; _minDate = null; @@ -2906,7 +3076,9 @@ class _SfCalendarState extends State } if (!CalendarViewHelper.isDateCollectionEqual( - widget.blackoutDates, _blackoutDates)) { + widget.blackoutDates, + _blackoutDates, + )) { _blackoutDates = CalendarViewHelper.cloneList(widget.blackoutDates); } @@ -2920,14 +3092,17 @@ class _SfCalendarState extends State if (widget.monthViewSettings.numberOfWeeksInView != oldWidget.monthViewSettings.numberOfWeeksInView) { - _currentDate = DateTimeHelper.getDateTimeValue(getValidDate( - widget.minDate, widget.maxDate, _updateCurrentDate(_view))); + _currentDate = DateTimeHelper.getDateTimeValue( + getValidDate(widget.minDate, widget.maxDate, _updateCurrentDate(_view)), + ); _controller.displayDate = _currentDate; if (_view == CalendarView.schedule) { if (CalendarViewHelper.shouldRaiseViewChangedCallback( - widget.onViewChanged)) { - CalendarViewHelper.raiseViewChangedCallback( - widget, [_controller.displayDate!]); + widget.onViewChanged, + )) { + CalendarViewHelper.raiseViewChangedCallback(widget, [ + _controller.displayDate!, + ]); } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); @@ -2936,8 +3111,7 @@ class _SfCalendarState extends State } if (CalendarViewHelper.isResourceEnabled(widget.dataSource, _view)) { - _resourcePanelScrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _resourcePanelScrollController ??= ScrollController(); } if (_view == CalendarView.month && @@ -2953,8 +3127,7 @@ class _SfCalendarState extends State widget.dataSource?.addListener(_dataSourceChangedListener); if (CalendarViewHelper.isResourceEnabled(widget.dataSource, _view)) { - _resourcePanelScrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _resourcePanelScrollController ??= ScrollController(); } _removeScheduleViewAppointmentDates(); @@ -2962,15 +3135,19 @@ class _SfCalendarState extends State } if (!CalendarViewHelper.isCollectionEqual( - widget.dataSource?.resources, _resourceCollection)) { - _resourceCollection = - CalendarViewHelper.cloneList(widget.dataSource?.resources); + widget.dataSource?.resources, + _resourceCollection, + )) { + _resourceCollection = CalendarViewHelper.cloneList( + widget.dataSource?.resources, + ); } if (oldWidget.minDate != widget.minDate || oldWidget.maxDate != widget.maxDate) { _currentDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, _currentDate)); + getValidDate(widget.minDate, widget.maxDate, _currentDate), + ); if (_view == CalendarView.schedule) { _minDate = null; _maxDate = null; @@ -2978,9 +3155,11 @@ class _SfCalendarState extends State _scheduleMinDate != null && _scheduleMaxDate != null) { _scheduleMinDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, _scheduleMinDate)); + getValidDate(widget.minDate, widget.maxDate, _scheduleMinDate), + ); _scheduleMaxDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, _scheduleMaxDate)); + getValidDate(widget.minDate, widget.maxDate, _scheduleMaxDate), + ); } } } @@ -2988,8 +3167,7 @@ class _SfCalendarState extends State if (_view == CalendarView.month && widget.monthViewSettings.showAgenda && _agendaScrollController == null) { - _agendaScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _agendaScrollController = ScrollController(); } _showHeader = false; @@ -3005,76 +3183,86 @@ class _SfCalendarState extends State Widget build(BuildContext context) { double height; _isRTL = CalendarViewHelper.isRTLLayout(context); + _themeData = Theme.of(context); + _calendarTheme = _getThemeDataValue( + SfCalendarTheme.of(context), + _themeData, + ); return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - _minWidth = constraints.maxWidth == double.infinity - ? _minWidth - : constraints.maxWidth; - _minHeight = constraints.maxHeight == double.infinity - ? _minHeight - : constraints.maxHeight; - - _isMobilePlatform = - CalendarViewHelper.isMobileLayout(Theme.of(context).platform); - _useMobilePlatformUI = - CalendarViewHelper.isMobileLayoutUI(_minWidth, _isMobilePlatform); - - _fadeInController ??= AnimationController( + builder: (BuildContext context, BoxConstraints constraints) { + _minWidth = + constraints.maxWidth == double.infinity + ? _minWidth + : constraints.maxWidth; + _minHeight = + constraints.maxHeight == double.infinity + ? _minHeight + : constraints.maxHeight; + + _isMobilePlatform = CalendarViewHelper.isMobileLayout( + Theme.of(context).platform, + ); + _useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + _minWidth, + _isMobilePlatform, + ); + + _fadeInController ??= AnimationController( duration: Duration(milliseconds: _isMobilePlatform ? 500 : 600), - vsync: this) - ..addListener(_updateFadeAnimation); - _fadeIn ??= Tween( - begin: 0.1, - end: 1, - ).animate(CurvedAnimation( - parent: _fadeInController!, - curve: Curves.easeIn, - )); - - /// Check the schedule view changes from mobile view to web view or - /// web view to mobile view. - if (_view == CalendarView.schedule && - _actualWidth != null && - _useMobilePlatformUI != - CalendarViewHelper.isMobileLayoutUI( - _actualWidth!, _isMobilePlatform) && - _nextDates.isNotEmpty) { - _agendaScrollController?.removeListener(_handleScheduleViewScrolled); - _initScheduleViewProperties(); - } + vsync: this, + )..addListener(_updateFadeAnimation); + _fadeIn ??= Tween(begin: 0.1, end: 1).animate( + CurvedAnimation(parent: _fadeInController!, curve: Curves.easeIn), + ); + + /// Check the schedule view changes from mobile view to web view or + /// web view to mobile view. + if (_view == CalendarView.schedule && + _actualWidth != null && + _useMobilePlatformUI != + CalendarViewHelper.isMobileLayoutUI( + _actualWidth!, + _isMobilePlatform, + ) && + _nextDates.isNotEmpty) { + _agendaScrollController?.removeListener(_handleScheduleViewScrolled); + _initScheduleViewProperties(); + } - _actualWidth = _minWidth; - height = _minHeight; + _actualWidth = _minWidth; + height = _minHeight; - _agendaDateViewWidth = _minWidth * 0.15; + _agendaDateViewWidth = _minWidth * 0.15; - /// Restrict the maximum agenda date view width to 60 on web view. - if (_agendaDateViewWidth > 60 && !_isMobilePlatform) { - _agendaDateViewWidth = 60; - } + /// Restrict the maximum agenda date view width to 60 on web view. + if (_agendaDateViewWidth > 60 && !_isMobilePlatform) { + _agendaDateViewWidth = 60; + } - height -= widget.headerHeight; - final double agendaHeight = - _view == CalendarView.month && widget.monthViewSettings.showAgenda - ? _getMonthAgendaHeight() - : 0; + height -= widget.headerHeight; + final double agendaHeight = + _view == CalendarView.month && widget.monthViewSettings.showAgenda + ? _getMonthAgendaHeight() + : 0; - return GestureDetector( - child: Container( - width: _minWidth, - height: _minHeight, - color: widget.backgroundColor ?? _calendarTheme.backgroundColor, - child: _view == CalendarView.schedule - ? widget.loadMoreWidgetBuilder == null - ? addAgenda(height, _isRTL) - : addAgendaWithLoadMore(height, _isRTL) - : _addChildren(agendaHeight, height, _minWidth, _isRTL), - ), - onTap: () { - _removeDatePicker(); - }, - ); - }); + return GestureDetector( + child: Container( + width: _minWidth, + height: _minHeight, + color: widget.backgroundColor ?? _calendarTheme.backgroundColor, + child: + _view == CalendarView.schedule + ? widget.loadMoreWidgetBuilder == null + ? addAgenda(height, _isRTL) + : addAgendaWithLoadMore(height, _isRTL) + : _addChildren(agendaHeight, height, _minWidth, _isRTL), + ), + onTap: () { + _removeDatePicker(); + }, + ); + }, + ); } /// Get the calendar details by using the [getCalendarDetailsAtOffset] in @@ -3084,7 +3272,11 @@ class _SfCalendarState extends State /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on header. return CalendarDetails( - null, _getTappedHeaderDate(), CalendarElement.header, null); + null, + _getTappedHeaderDate(), + CalendarElement.header, + null, + ); } else if (position.dy > _minHeight && (position.dx < 0 || position.dx > _minWidth)) { // Return null value when the position placed on @@ -3092,8 +3284,10 @@ class _SfCalendarState extends State return null; } - final Offset updatedPosition = - Offset(position.dx, position.dy - widget.headerHeight); + final Offset updatedPosition = Offset( + position.dx, + position.dy - widget.headerHeight, + ); switch (_controller.view!) { case CalendarView.day: @@ -3111,34 +3305,48 @@ class _SfCalendarState extends State if (position.dy > _minHeight - agendaHeight) { DateTime? currentSelectedDate; if (_selectedDate != null) { - currentSelectedDate = isDateWithInDateRange( - widget.minDate, widget.maxDate, _selectedDate) && - !CalendarViewHelper.isDateInDateCollection( - _blackoutDates, _selectedDate!) - ? _selectedDate - : null; + currentSelectedDate = + isDateWithInDateRange( + widget.minDate, + widget.maxDate, + _selectedDate, + ) && + !CalendarViewHelper.isDateInDateCollection( + _blackoutDates, + _selectedDate!, + ) + ? _selectedDate + : null; } if (currentSelectedDate == null) { return const CalendarDetails( - null, null, CalendarElement.agenda, null); + null, + null, + CalendarElement.agenda, + null, + ); } final List selectedAppointments = _getSelectedAppointments( - Offset(position.dx, - position.dy - (_minHeight - agendaHeight)), - currentSelectedDate); + Offset( + position.dx, + position.dy - (_minHeight - agendaHeight), + ), + currentSelectedDate, + ); /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on agenda in month view. return CalendarDetails( - selectedAppointments, - currentSelectedDate, - selectedAppointments.isNotEmpty - ? CalendarElement.appointment - : CalendarElement.agenda, - null); + selectedAppointments, + currentSelectedDate, + selectedAppointments.isNotEmpty + ? CalendarElement.appointment + : CalendarElement.agenda, + null, + ); } } @@ -3151,17 +3359,23 @@ class _SfCalendarState extends State case CalendarView.timelineWorkWeek: case CalendarView.timelineMonth: { - final bool isResourceEnabled = - CalendarViewHelper.isResourceEnabled(widget.dataSource, _view); + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.dataSource, + _view, + ); final double resourceViewSize = isResourceEnabled ? widget.resourceViewSettings.size : 0; if ((!_isRTL && updatedPosition.dx < resourceViewSize) || (_isRTL && updatedPosition.dx > _minWidth - resourceViewSize)) { final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.viewHeaderHeight, _view); + widget.viewHeaderHeight, + _view, + ); final double timeLabelSize = CalendarViewHelper.getTimeLabelWidth( - widget.timeSlotViewSettings.timeRulerSize, _view); + widget.timeSlotViewSettings.timeRulerSize, + _view, + ); final double top = viewHeaderHeight + timeLabelSize; // Return null value when the position placed on empty space // on view header above the resource view. @@ -3170,29 +3384,39 @@ class _SfCalendarState extends State } final double resourceItemHeight = CalendarViewHelper.getResourceItemHeight( - resourceViewSize, - _minHeight - top, - widget.resourceViewSettings, - _resourceCollection!.length); + resourceViewSize, + _minHeight - top, + widget.resourceViewSettings, + _resourceCollection!.length, + ); final CalendarResource resource = _getTappedResource( - updatedPosition.dy - top, resourceItemHeight); + updatedPosition.dy - top, + resourceItemHeight, + ); final List resourceAppointments = _getSelectedResourceAppointments(resource); /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on resource header in timeline views. - return CalendarDetails(resourceAppointments, null, - CalendarElement.resourceHeader, resource); + return CalendarDetails( + resourceAppointments, + null, + CalendarElement.resourceHeader, + resource, + ); } return (_customScrollViewKey.currentWidget! as CustomCalendarScrollView) - .getCalendarDetails(Offset( + .getCalendarDetails( + Offset( _isRTL ? updatedPosition.dx : updatedPosition.dx - resourceViewSize, - updatedPosition.dy)); + updatedPosition.dy, + ), + ); } case CalendarView.schedule: return _getScheduleViewDetails(updatedPosition); @@ -3258,49 +3482,62 @@ class _SfCalendarState extends State /// next dates collection then get the start date from previous dates. /// If the index as last index of previous dates collection then calculate /// by subtract the 7 days to get previous date. - final DateTime prevDate = yPosition >= 0 && index == 0 - ? _previousDates.isEmpty - ? DateTimeHelper.getDateTimeValue( - addDays(startDate, -DateTime.daysPerWeek)) - : _previousDates[0] - : (yPosition >= 0 && index > 0 - ? _nextDates[index - 1] - : index >= _previousDates.length - 1 + final DateTime prevDate = + yPosition >= 0 && index == 0 + ? _previousDates.isEmpty ? DateTimeHelper.getDateTimeValue( - addDays(startDate, -DateTime.daysPerWeek)) + addDays(startDate, -DateTime.daysPerWeek), + ) + : _previousDates[0] + : (yPosition >= 0 && index > 0 + ? _nextDates[index - 1] + : index >= _previousDates.length - 1 + ? DateTimeHelper.getDateTimeValue( + addDays(startDate, -DateTime.daysPerWeek), + ) : _previousDates[index + 1]); - final DateTime prevEndDate = - DateTimeHelper.getDateTimeValue(addDays(prevDate, 6)); - final DateTime endDate = - DateTimeHelper.getDateTimeValue(addDays(startDate, 6)); + final DateTime prevEndDate = DateTimeHelper.getDateTimeValue( + addDays(prevDate, 6), + ); + final DateTime endDate = DateTimeHelper.getDateTimeValue( + addDays(startDate, 6), + ); /// Get the visible week appointment and split the appointments based on /// date. final List appointmentCollection = AppointmentHelper.getVisibleAppointments( - isSameOrAfterDate(_minDate, startDate) ? startDate : _minDate!, - isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate!, - _appointments, - widget.timeZone, - false, - canCreateNewAppointment: false); + 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)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); /// Get the collection of appointment collection listed by date. final Map> dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, startDate, endDate); + appointmentCollection, + startDate, + endDate, + ); final List dateAppointmentKeys = dateAppointments.keys.toList(); const double padding = 5; /// Check the current week view show display date or current date view. - bool isNeedDisplayDateHighlight = - isDateWithInDateRange(startDate, endDate, scheduleDisplayDate); + bool isNeedDisplayDateHighlight = isDateWithInDateRange( + startDate, + endDate, + scheduleDisplayDate, + ); bool isNeedCurrentDateHighlight = isDateWithInDateRange(startDate, endDate, scheduleCurrentDate) && - !isSameDate(scheduleDisplayDate, scheduleCurrentDate); + !isSameDate(scheduleDisplayDate, scheduleCurrentDate); /// Check the schedule display date have appointments if display date /// in between the week @@ -3330,7 +3567,9 @@ class _SfCalendarState extends State /// calculate the day label(eg., May 25) width based on schedule setting. final double viewPadding = _getAgendaViewDayLabelWidth( - widget.scheduleViewSettings, _useMobilePlatformUI); + widget.scheduleViewSettings, + _useMobilePlatformUI, + ); final double viewTopPadding = _useMobilePlatformUI ? padding : 0; @@ -3339,16 +3578,21 @@ class _SfCalendarState extends State /// Check the week date needs month header at first or before of appointment /// view. - final bool isNeedMonthBuilder = _useMobilePlatformUI && + final bool isNeedMonthBuilder = + _useMobilePlatformUI && (prevEndDate.month != startDate.month || prevEndDate.year != startDate.year); final double appointmentViewHeight = CalendarViewHelper.getScheduleAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); /// Calculate the divider height and color when it is web view. final double dividerHeight = _useMobilePlatformUI ? 0 : 1; @@ -3357,41 +3601,43 @@ class _SfCalendarState extends State if (_useMobilePlatformUI) { if (isNeedMonthBuilder) { /// Add the height of month label to total height of view. - double _currentViewHeight = + double currentViewHeight = widget.scheduleViewSettings.monthHeaderSettings.height; /// Add the week label padding value to top position and total height. /// padding only added between the month and week header. - _currentViewHeight += viewTopPadding; + currentViewHeight += viewTopPadding; if (topPosition <= yPosition && - topPosition + _currentViewHeight > yPosition) { + topPosition + currentViewHeight > yPosition) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on header in schedule view. return CalendarDetails( - null, - DateTime(startDate.year, startDate.month), - CalendarElement.header, - null); + null, + DateTime(startDate.year, startDate.month), + CalendarElement.header, + null, + ); } - topPosition += _currentViewHeight; + topPosition += currentViewHeight; } /// Add the height of week label to update the top position of next view. - final double _weekHeaderHeight = + final double weekHeaderHeight = widget.scheduleViewSettings.weekHeaderSettings.height; if (topPosition <= yPosition && - topPosition + _weekHeaderHeight > yPosition) { + topPosition + weekHeaderHeight > yPosition) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on view header in schedule view. return CalendarDetails( - null, - DateTime(startDate.year, startDate.month, startDate.day), - CalendarElement.viewHeader, - null); + null, + DateTime(startDate.year, startDate.month, startDate.day), + CalendarElement.viewHeader, + null, + ); } - topPosition += _weekHeaderHeight; + topPosition += weekHeaderHeight; } /// Calculate the day label(May, 25) height based on appointment height and @@ -3404,7 +3650,8 @@ class _SfCalendarState extends State /// Check the week date needs month header at in between the appointment /// views. - bool isNeedInBetweenMonthBuilder = _useMobilePlatformUI && + bool isNeedInBetweenMonthBuilder = + _useMobilePlatformUI && (startDate.month != (isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate!) .month); @@ -3412,14 +3659,16 @@ class _SfCalendarState extends State /// Check the end date month have appointments or not. bool isNextMonthHasNoAppointment = false; if (isNeedInBetweenMonthBuilder) { - final DateTime? lastAppointmentDate = dateAppointmentKeys.isNotEmpty - ? dateAppointmentKeys[dateAppointmentKeys.length - 1] - : null; - final DateTime? nextWeekDate = index == -1 - ? _nextDates[0] - : (index < 0 - ? _previousDates[-index - 2] - : index >= _nextDates.length - 1 + final DateTime? lastAppointmentDate = + dateAppointmentKeys.isNotEmpty + ? dateAppointmentKeys[dateAppointmentKeys.length - 1] + : null; + final DateTime? nextWeekDate = + index == -1 + ? _nextDates[0] + : (index < 0 + ? _previousDates[-index - 2] + : index >= _nextDates.length - 1 ? null : _nextDates[index + 1]); @@ -3431,13 +3680,15 @@ class _SfCalendarState extends State /// 3. If the week have appointments but next month dates does not have /// an appointments but [hideEmptyScheduleWeek] enabled so the next view /// date month as different with current week end date week. - isNextMonthHasNoAppointment = lastAppointmentDate == null || + isNextMonthHasNoAppointment = + lastAppointmentDate == null || (lastAppointmentDate.month != endDate.month && nextWeekDate != null && nextWeekDate.month == endDate.month && nextWeekDate.year == endDate.year); - isNeedInBetweenMonthBuilder = isNextMonthHasNoAppointment || + isNeedInBetweenMonthBuilder = + isNextMonthHasNoAppointment || lastAppointmentDate.month != startDate.month; } @@ -3449,7 +3700,7 @@ class _SfCalendarState extends State /// current date view added or not. bool isCurrentDateHighlightAdded = !isNeedCurrentDateHighlight; - CalendarDetails _triggerDisplayDateDetails(DateTime date) { + CalendarDetails triggerDisplayDateDetails(DateTime date) { if ((!_isRTL && viewPadding >= position.dx) || (_isRTL && _minWidth - viewPadding < position.dx)) { /// Return calendar details while the [getCalendarDetailsAtOffset] @@ -3492,14 +3743,14 @@ class _SfCalendarState extends State /// Add intersect value with appointment height and divider height /// because display date view height as single appointment view height - final double _displayDateHeight = + final double displayDateHeight = appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && - topPosition + _displayDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleDisplayDate); + topPosition + displayDateHeight > yPosition) { + return triggerDisplayDateDetails(scheduleDisplayDate); } - topPosition += _displayDateHeight; + topPosition += displayDateHeight; } /// Check the current date view not added in widget and appointment @@ -3517,13 +3768,12 @@ class _SfCalendarState extends State /// Add intersect value with appointment height and divider height /// because display date view height as single appointment view height - final double _todayHeight = appointmentViewHeaderHeight + dividerHeight; - if (topPosition <= yPosition && - topPosition + _todayHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleCurrentDate); + final double todayHeight = appointmentViewHeaderHeight + dividerHeight; + if (topPosition <= yPosition && topPosition + todayHeight > yPosition) { + return triggerDisplayDateDetails(scheduleCurrentDate); } - topPosition += _todayHeight; + topPosition += todayHeight; } /// Check if the view intersection point not set and the current week date @@ -3533,21 +3783,22 @@ class _SfCalendarState extends State _useMobilePlatformUI && (startDate.month != currentDate.month || startDate.year != currentDate.year)) { - final double _monthHeaderHeight = + final double monthHeaderHeight = widget.scheduleViewSettings.monthHeaderSettings.height + - viewTopPadding; + viewTopPadding; if (topPosition <= yPosition && - topPosition + _monthHeaderHeight > yPosition) { + topPosition + monthHeaderHeight > yPosition) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on header in schedule view. return CalendarDetails( - null, - DateTime(currentDate.year, currentDate.month), - CalendarElement.header, - null); + null, + DateTime(currentDate.year, currentDate.month), + CalendarElement.header, + null, + ); } - topPosition += _monthHeaderHeight; + topPosition += monthHeaderHeight; isMonthHeaderAddedOnView = true; } @@ -3559,14 +3810,14 @@ class _SfCalendarState extends State /// Add intersect value with appointment height and divider height /// because display date view height as single appointment view height - final double _displayDateHeight = + final double displayDateHeight = appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && - topPosition + _displayDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleDisplayDate); + topPosition + displayDateHeight > yPosition) { + return triggerDisplayDateDetails(scheduleDisplayDate); } - topPosition += _displayDateHeight; + topPosition += displayDateHeight; } /// Check the current date view not added in widget and appointment @@ -3581,14 +3832,15 @@ class _SfCalendarState extends State appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && topPosition + currentDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleCurrentDate); + return triggerDisplayDateDetails(scheduleCurrentDate); } topPosition += currentDateHeight; } final double totalPadding = (eventsCount + 1) * padding; - final double panelHeight = totalPadding + + final double panelHeight = + totalPadding + ((eventsCount - allDayEventCount) * appointmentViewHeight) + (allDayEventCount * allDayAppointmentHeight); double appointmentViewPadding = 0; @@ -3603,38 +3855,47 @@ class _SfCalendarState extends State appointmentViewPadding + dividerHeight) { currentAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); currentAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, + app2.isAllDay, + ), + ); currentAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, + app2.isSpanned, + ), + ); if ((!_isRTL && viewPadding >= position.dx) || (_isRTL && _minWidth - viewPadding < position.dx)) { - final List _currentAppointments = + final List currentAppointments = []; for (int i = 0; i < currentAppointments.length; i++) { final CalendarAppointment appointment = currentAppointments[i]; - _currentAppointments.add(appointment); + currentAppointments.add(appointment); } /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on view header in schedule view. return CalendarDetails( - widget.dataSource != null && - !AppointmentHelper.isCalendarAppointment( - widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - _currentAppointments, widget.dataSource) - : _currentAppointments, - DateTime(currentDate.year, currentDate.month, currentDate.day), - CalendarElement.viewHeader, - null); + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment(widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments( + currentAppointments, + widget.dataSource, + ) + : currentAppointments, + DateTime(currentDate.year, currentDate.month, currentDate.day), + CalendarElement.viewHeader, + null, + ); } else { /// Calculate the touch position appointment from its collection. double currentYPosition = topPosition + padding; @@ -3642,9 +3903,9 @@ class _SfCalendarState extends State final CalendarAppointment appointment = currentAppointments[k]; final double currentAppointmentHeight = (_useMobilePlatformUI && _isAllDayAppointmentView(appointment) - ? allDayAppointmentHeight - : appointmentViewHeight) + - padding; + ? allDayAppointmentHeight + : appointmentViewHeight) + + padding; if (currentYPosition <= yPosition && currentYPosition + currentAppointmentHeight > yPosition) { final List selectedAppointment = @@ -3653,16 +3914,19 @@ class _SfCalendarState extends State /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on appointment in schedule view. return CalendarDetails( - widget.dataSource != null && - !AppointmentHelper.isCalendarAppointment( - widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - selectedAppointment, widget.dataSource) - : selectedAppointment, - DateTime( - currentDate.year, currentDate.month, currentDate.day), - CalendarElement.appointment, - null); + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.dataSource!, + ) + ? CalendarViewHelper.getCustomAppointments( + selectedAppointment, + widget.dataSource, + ) + : selectedAppointment, + DateTime(currentDate.year, currentDate.month, currentDate.day), + CalendarElement.appointment, + null, + ); } currentYPosition += currentAppointmentHeight; @@ -3680,14 +3944,14 @@ class _SfCalendarState extends State /// Add the top height value with display date view height because the /// month header added after the display date view added and its /// intersect point calculated based on top height. - final double _displayDateHeight = + final double displayDateHeight = appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && - topPosition + _displayDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleDisplayDate); + topPosition + displayDateHeight > yPosition) { + return triggerDisplayDateDetails(scheduleDisplayDate); } - topPosition += _displayDateHeight; + topPosition += displayDateHeight; isDisplayDateHighlightAdded = true; } @@ -3698,14 +3962,14 @@ class _SfCalendarState extends State /// Add the top height value with current date view height because the /// month header added after the current date view added and its /// intersect point calculated based on top height. - final double _currentDateHeight = + final double currentDateHeight = appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && - topPosition + _currentDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleCurrentDate); + topPosition + currentDateHeight > yPosition) { + return triggerDisplayDateDetails(scheduleCurrentDate); } - topPosition += _currentDateHeight; + topPosition += currentDateHeight; isCurrentDateHighlightAdded = true; } @@ -3721,44 +3985,48 @@ class _SfCalendarState extends State /// view holds next month label. if scrolling reaches this position /// then we update the header date so add the location to intersecting /// point. - final double _monthHeaderHeight = + final double monthHeaderHeight = widget.scheduleViewSettings.monthHeaderSettings.height + - viewTopPadding; + viewTopPadding; if (topPosition <= yPosition && - topPosition + _monthHeaderHeight > yPosition) { + topPosition + monthHeaderHeight > yPosition) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on header in schedule view. - return CalendarDetails(null, DateTime(endDate.year, endDate.month), - CalendarElement.header, null); + return CalendarDetails( + null, + DateTime(endDate.year, endDate.month), + CalendarElement.header, + null, + ); } - topPosition += _monthHeaderHeight; + topPosition += monthHeaderHeight; } /// Add the display date view at end of week view when /// it does not added to widget. if (!isDisplayDateHighlightAdded) { - final double _displayDateHeight = + final double displayDateHeight = appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && - topPosition + _displayDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleDisplayDate); + topPosition + displayDateHeight > yPosition) { + return triggerDisplayDateDetails(scheduleDisplayDate); } - topPosition += _displayDateHeight; + topPosition += displayDateHeight; isDisplayDateHighlightAdded = true; } /// Add the current date view at end of week view /// when it does not added to widget. if (!isCurrentDateHighlightAdded) { - final double _currentDateHeight = + final double currentDateHeight = appointmentViewHeaderHeight + dividerHeight; if (topPosition <= yPosition && - topPosition + _currentDateHeight > yPosition) { - return _triggerDisplayDateDetails(scheduleCurrentDate); + topPosition + currentDateHeight > yPosition) { + return triggerDisplayDateDetails(scheduleCurrentDate); } - topPosition += _currentDateHeight; + topPosition += currentDateHeight; isCurrentDateHighlightAdded = true; } @@ -3806,88 +4074,163 @@ class _SfCalendarState extends State } SfCalendarThemeData _getThemeDataValue( - SfCalendarThemeData calendarThemeData, ColorScheme colorScheme) { + SfCalendarThemeData calendarThemeData, + ThemeData themeData, + ) { + final ColorScheme colorScheme = themeData.colorScheme; + final SfCalendarThemeData effectiveThemeData = SfCalendarThemeColors( + context, + ); + final bool isMaterial3 = themeData.useMaterial3; return calendarThemeData.copyWith( - brightness: calendarThemeData.brightness ?? colorScheme.brightness, - backgroundColor: - calendarThemeData.backgroundColor ?? Colors.transparent, - headerBackgroundColor: - calendarThemeData.headerBackgroundColor ?? Colors.transparent, - agendaBackgroundColor: - calendarThemeData.agendaBackgroundColor ?? Colors.transparent, - activeDatesBackgroundColor: - calendarThemeData.activeDatesBackgroundColor ?? Colors.transparent, - todayBackgroundColor: - calendarThemeData.todayBackgroundColor ?? Colors.transparent, - trailingDatesBackgroundColor: - calendarThemeData.trailingDatesBackgroundColor ?? - Colors.transparent, - leadingDatesBackgroundColor: - calendarThemeData.leadingDatesBackgroundColor ?? Colors.transparent, - // ignore: lines_longer_than_80_chars - viewHeaderBackgroundColor: - calendarThemeData.viewHeaderBackgroundColor ?? Colors.transparent, - allDayPanelColor: - calendarThemeData.allDayPanelColor ?? Colors.transparent, - weekNumberBackgroundColor: - calendarThemeData.weekNumberBackgroundColor ?? - colorScheme.onSurface.withOpacity(0.04), - cellBorderColor: calendarThemeData.cellBorderColor ?? - colorScheme.onSurface.withOpacity(0.16), - todayHighlightColor: - calendarThemeData.todayHighlightColor ?? colorScheme.primary, - selectionBorderColor: - calendarThemeData.selectionBorderColor ?? colorScheme.primary, - blackoutDatesTextStyle: calendarThemeData.blackoutDatesTextStyle, - trailingDatesTextStyle: calendarThemeData.trailingDatesTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, - fontFamily: 'Roboto'), - leadingDatesTextStyle: calendarThemeData.leadingDatesTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, - fontFamily: 'Roboto'), - agendaDayTextStyle: calendarThemeData.agendaDayTextStyle ?? - // ignore: lines_longer_than_80_chars - TextStyle(color: colorScheme.onSurface.withOpacity(0.54), fontWeight: FontWeight.w500, fontSize: 10, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - agendaDateTextStyle: calendarThemeData.agendaDateTextStyle ?? TextStyle(color: colorScheme.onSurface, fontSize: 18, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - todayTextStyle: calendarThemeData.todayTextStyle ?? TextStyle(color: colorScheme.onPrimary, fontSize: 13, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - headerTextStyle: calendarThemeData.headerTextStyle ?? TextStyle(color: colorScheme.onSurface.withOpacity(0.87), fontSize: 18, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - activeDatesTextStyle: calendarThemeData.activeDatesTextStyle ?? TextStyle(color: colorScheme.onSurface.withOpacity(0.87), fontSize: 13, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - timeTextStyle: calendarThemeData.timeTextStyle ?? TextStyle(color: colorScheme.onSurface.withOpacity(0.54), fontWeight: FontWeight.w500, fontSize: 10), - // ignore: lines_longer_than_80_chars - viewHeaderDateTextStyle: calendarThemeData.viewHeaderDateTextStyle ?? TextStyle(color: colorScheme.onSurface.withOpacity(0.87), fontSize: 15, fontWeight: FontWeight.w400, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - viewHeaderDayTextStyle: calendarThemeData.viewHeaderDayTextStyle ?? TextStyle(color: colorScheme.onSurface.withOpacity(0.87), fontSize: 11, fontWeight: FontWeight.w400, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - displayNameTextStyle: calendarThemeData.displayNameTextStyle ?? TextStyle(color: colorScheme.onSurface, fontSize: 10, fontWeight: FontWeight.w500, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - weekNumberTextStyle: calendarThemeData.weekNumberTextStyle ?? TextStyle(color: colorScheme.onSurface.withOpacity(0.87), fontSize: 13, fontFamily: 'Roboto'), - // ignore: lines_longer_than_80_chars - timeIndicatorTextStyle: calendarThemeData.timeIndicatorTextStyle ?? TextStyle(color: colorScheme.primary, fontWeight: FontWeight.w500, fontSize: 10)); - } - - 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 = - await rootBundle.load('packages/timezone/data/2020a.tzf'); + backgroundColor: + calendarThemeData.backgroundColor ?? + effectiveThemeData.backgroundColor, + headerBackgroundColor: + calendarThemeData.headerBackgroundColor ?? + effectiveThemeData.headerBackgroundColor, + agendaBackgroundColor: + calendarThemeData.agendaBackgroundColor ?? + effectiveThemeData.agendaBackgroundColor, + activeDatesBackgroundColor: + calendarThemeData.activeDatesBackgroundColor ?? + effectiveThemeData.activeDatesBackgroundColor, + todayBackgroundColor: + calendarThemeData.todayBackgroundColor ?? + effectiveThemeData.todayBackgroundColor, + trailingDatesBackgroundColor: + calendarThemeData.trailingDatesBackgroundColor ?? + effectiveThemeData.trailingDatesBackgroundColor, + leadingDatesBackgroundColor: + calendarThemeData.leadingDatesBackgroundColor ?? + effectiveThemeData.leadingDatesBackgroundColor, + viewHeaderBackgroundColor: + calendarThemeData.viewHeaderBackgroundColor ?? + effectiveThemeData.viewHeaderBackgroundColor, + allDayPanelColor: + calendarThemeData.allDayPanelColor ?? + effectiveThemeData.allDayPanelColor, + weekNumberBackgroundColor: + calendarThemeData.weekNumberBackgroundColor ?? + effectiveThemeData.weekNumberBackgroundColor, + cellBorderColor: + calendarThemeData.cellBorderColor ?? + effectiveThemeData.cellBorderColor, + todayHighlightColor: + calendarThemeData.todayHighlightColor ?? + effectiveThemeData.todayHighlightColor, + selectionBorderColor: + calendarThemeData.selectionBorderColor ?? + effectiveThemeData.selectionBorderColor, + blackoutDatesTextStyle: + calendarThemeData.blackoutDatesTextStyle == null + ? widget.blackoutDatesTextStyle + : calendarThemeData.blackoutDatesTextStyle?.merge( + widget.blackoutDatesTextStyle, + ), + trailingDatesTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: isMaterial3 ? 14 : 13, + ) + .merge(calendarThemeData.trailingDatesTextStyle) + .merge( + widget.monthViewSettings.monthCellStyle.trailingDatesTextStyle, + ), + leadingDatesTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: isMaterial3 ? 14 : 13, + ) + .merge(calendarThemeData.leadingDatesTextStyle) + // ignore: lines_longer_than_80_chars + .merge(widget.monthViewSettings.monthCellStyle.leadingDatesTextStyle), + todayTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onPrimary, + fontSize: isMaterial3 ? 14 : 13, + ) + .merge(calendarThemeData.todayTextStyle) + .merge(widget.todayTextStyle), + headerTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: isMaterial3 ? 16 : 18, + fontWeight: FontWeight.w400, + ) + .merge(calendarThemeData.headerTextStyle) + .merge(widget.headerStyle.textStyle), + activeDatesTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(calendarThemeData.activeDatesTextStyle) + .merge(widget.monthViewSettings.monthCellStyle.textStyle), + timeTextStyle: themeData.textTheme.bodySmall! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: isMaterial3 ? 12 : 10, + fontWeight: FontWeight.w500, + ) + .merge(calendarThemeData.timeTextStyle) + .merge(widget.timeSlotViewSettings.timeTextStyle), + viewHeaderDateTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: isMaterial3 ? 14 : 15, + ) + .merge(calendarThemeData.viewHeaderDateTextStyle) + .merge(widget.viewHeaderStyle.dateTextStyle), + viewHeaderDayTextStyle: themeData.textTheme.bodySmall! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: isMaterial3 ? 12 : 11, + ) + .merge(calendarThemeData.viewHeaderDayTextStyle) + .merge(widget.viewHeaderStyle.dayTextStyle), + displayNameTextStyle: themeData.textTheme.bodySmall! + .copyWith( + color: colorScheme.onSurface, + fontSize: 10, + fontWeight: FontWeight.w500, + ) + .merge(calendarThemeData.displayNameTextStyle) + .merge(widget.resourceViewSettings.displayNameTextStyle), + weekNumberTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: isMaterial3 ? 14 : 13, + ) + .merge(calendarThemeData.weekNumberTextStyle) + .merge(widget.weekNumberStyle.textStyle), + timeIndicatorTextStyle: themeData.textTheme.bodySmall! + .copyWith( + color: colorScheme.primary, + fontSize: 10, + fontWeight: FontWeight.w500, + ) + .merge(calendarThemeData.timeIndicatorTextStyle) + .merge(widget.dragAndDropSettings.timeIndicatorStyle), + ); + } + + 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 = await rootBundle.load( + 'packages/timezone/data/latest_all.tzf', + ); initializeDatabase(byteData.buffer.asUint8List()); _timeZoneLoaded = true; + timeZoneLoaded = true; return true; } @@ -3895,7 +4238,9 @@ class _SfCalendarState extends State /// time zone details void _getAppointment() { _appointments = AppointmentHelper.generateCalendarAppointments( - widget.dataSource, widget.timeZone); + widget.dataSource, + widget.timeZone, + ); _updateVisibleAppointments(); } @@ -3911,8 +4256,9 @@ class _SfCalendarState extends State DateTime viewEndDate = _currentViewVisibleDates[visibleDatesCount - 1]; if (_view == CalendarView.month && !CalendarViewHelper.isLeadingAndTrailingDatesVisible( - widget.monthViewSettings.numberOfWeeksInView, - widget.monthViewSettings.showTrailingAndLeadingDates)) { + widget.monthViewSettings.numberOfWeeksInView, + widget.monthViewSettings.showTrailingAndLeadingDates, + )) { final DateTime currentMonthDate = _currentViewVisibleDates[visibleDatesCount ~/ 2]; viewStartDate = AppointmentHelper.getMonthStartDate(currentMonthDate); @@ -3920,22 +4266,26 @@ class _SfCalendarState extends State } final List tempVisibleAppointment = - // ignore: await_only_futures - await AppointmentHelper.getVisibleAppointments( - viewStartDate, - viewEndDate, - _appointments, - widget.timeZone, - _view == CalendarView.month || - CalendarViewHelper.isTimelineView(_view)); + AppointmentHelper.getVisibleAppointments( + viewStartDate, + viewEndDate, + _appointments, + widget.timeZone, + _view == CalendarView.month || + CalendarViewHelper.isTimelineView(_view), + ); if (CalendarViewHelper.isCollectionEqual( - _visibleAppointments, tempVisibleAppointment)) { - if (mounted) { - setState(() { - // Updates the calendar widget because it trigger to change the - // header view text. - }); - } + _visibleAppointments, + tempVisibleAppointment, + )) { + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { + if (mounted) { + setState(() { + // Updates the calendar widget because it trigger to change the + // header view text. + }); + } + }); return; } @@ -3949,11 +4299,13 @@ class _SfCalendarState extends State //// mounted property in state return false when the state disposed, //// restrict the async method set state after the state disposed. - if (mounted) { - setState(() { - /* Updates the visible appointment collection */ - }); - } + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { + if (mounted) { + setState(() { + /* Updates the visible appointment collection */ + }); + } + }); } void _updateViewHeaderHover() { @@ -3990,6 +4342,10 @@ class _SfCalendarState extends State void _handleScheduleViewScrolled() { _removeDatePicker(); + if (_view != CalendarView.schedule) { + return; + } + double widgetPosition = 0; final double scrolledPosition = _agendaScrollController!.position.pixels; @@ -4019,7 +4375,8 @@ class _SfCalendarState extends State } final DateTime currentViewDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, date)); + getValidDate(widget.minDate, widget.maxDate, date), + ); _currentDate = currentViewDate; if (currentViewDate.month != _headerUpdateNotifier.value!.month || currentViewDate.year != _headerUpdateNotifier.value!.year) { @@ -4058,7 +4415,8 @@ class _SfCalendarState extends State } final DateTime currentViewDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, date)); + getValidDate(widget.minDate, widget.maxDate, date), + ); _currentDate = currentViewDate; if (currentViewDate.month != _headerUpdateNotifier.value!.month || currentViewDate.year != _headerUpdateNotifier.value!.year) { @@ -4083,7 +4441,8 @@ class _SfCalendarState extends State if (_agendaScrollController!.position.pixels == _agendaScrollController!.position.minScrollExtent) { DateTime date = AppointmentHelper.getMonthStartDate( - DateTime(_scheduleMinDate!.year, _scheduleMinDate!.month - 1)); + DateTime(_scheduleMinDate!.year, _scheduleMinDate!.month - 1), + ); if (!isSameOrAfterDate(widget.minDate, date)) { date = widget.minDate; @@ -4097,7 +4456,8 @@ class _SfCalendarState extends State } } else { DateTime date = AppointmentHelper.getMonthEndDate( - DateTime(_scheduleMaxDate!.year, _scheduleMaxDate!.month + 1)); + DateTime(_scheduleMaxDate!.year, _scheduleMaxDate!.month + 1), + ); if (!isSameOrBeforeDate(widget.maxDate, date)) { date = widget.maxDate; @@ -4117,22 +4477,28 @@ class _SfCalendarState extends State /// when selected date changed programmatically. void _updateSelectionChangedCallback() { if (!CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( - widget.onSelectionChanged)) { + widget.onSelectionChanged, + )) { return; } - final bool isResourceEnabled = - CalendarViewHelper.isResourceEnabled(widget.dataSource, _view); + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.dataSource, + _view, + ); CalendarViewHelper.raiseCalendarSelectionChangedCallback( - widget, - _controller.selectedDate, - isResourceEnabled ? widget.dataSource!.resources![0] : null); + widget, + _controller.selectedDate, + isResourceEnabled ? widget.dataSource!.resources![0] : null, + ); } void _calendarValueChangedListener(String property) { _removeDatePicker(); if (property == 'selectedDate') { if (CalendarViewHelper.isSameTimeSlot( - _selectedDate, _controller.selectedDate)) { + _selectedDate, + _controller.selectedDate, + )) { return; } _updateSelectionChangedCallback(); @@ -4149,8 +4515,13 @@ class _SfCalendarState extends State setState(() { final CalendarView oldView = _view; _view = _controller.view!; - _currentDate = DateTimeHelper.getDateTimeValue(getValidDate( - widget.minDate, widget.maxDate, _updateCurrentDate(oldView))); + _currentDate = DateTimeHelper.getDateTimeValue( + getValidDate( + widget.minDate, + widget.maxDate, + _updateCurrentDate(oldView), + ), + ); if (!isSameDate(_currentDate, _controller.displayDate)) { _canScrollTimeSlotView = false; _controller.displayDate = _currentDate; @@ -4159,8 +4530,8 @@ class _SfCalendarState extends State _fadeInController!.reset(); _fadeInController!.forward(); - _agendaScrollController = ScrollController(initialScrollOffset: 0); - SchedulerBinding.instance?.addPostFrameCallback((_) { + _agendaScrollController = ScrollController(); + SchedulerBinding.instance.addPostFrameCallback((_) { final Widget? currentWidget = _customScrollViewKey.currentWidget; /// When view switched from schedule view to other views we need to @@ -4172,22 +4543,25 @@ class _SfCalendarState extends State if (_view == CalendarView.schedule) { _scheduleDisplayDate = _controller.displayDate!; if (CalendarViewHelper.shouldRaiseViewChangedCallback( - widget.onViewChanged)) { - CalendarViewHelper.raiseViewChangedCallback( - widget, [_controller.displayDate!]); + widget.onViewChanged, + )) { + CalendarViewHelper.raiseViewChangedCallback(widget, [ + _controller.displayDate!, + ]); } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _initScheduleViewProperties(); - SchedulerBinding.instance?.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { if (!_focusNode.hasFocus) { _focusNode.requestFocus(); } }); } else if (CalendarViewHelper.isResourceEnabled( - widget.dataSource, _view)) { - _resourcePanelScrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + widget.dataSource, + _view, + )) { + _resourcePanelScrollController ??= ScrollController(); } }); } @@ -4218,8 +4592,9 @@ class _SfCalendarState extends State _currentDate = _controller.displayDate!; _scheduleDisplayDate = _currentDate; _updateCurrentVisibleDates(); - _agendaScrollController - ?.removeListener(_handleScheduleViewScrolled); + _agendaScrollController?.removeListener( + _handleScheduleViewScrolled, + ); _agendaScrollController!.dispose(); _initScheduleViewProperties(); }); @@ -4229,15 +4604,17 @@ class _SfCalendarState extends State { if (isSameDate(_currentDate, _controller.displayDate) || (isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1], - _controller.displayDate) && + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - + 1], + _controller.displayDate, + ) && (widget.monthViewSettings.numberOfWeeksInView != 6 || (widget.monthViewSettings.numberOfWeeksInView == 6 && _controller.displayDate!.month == - _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ 2] + _currentViewVisibleDates[_currentViewVisibleDates + .length ~/ + 2] .month)))) { _currentDate = _controller.displayDate!; return; @@ -4261,9 +4638,10 @@ class _SfCalendarState extends State { if (isSameDate(_currentDate, _controller.displayDate) || isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1], - _controller.displayDate)) { + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + _controller.displayDate, + )) { if (_canScrollTimeSlotView && _customScrollViewKey.currentWidget != null) { // ignore: avoid_as @@ -4286,23 +4664,30 @@ class _SfCalendarState extends State } void _updateCurrentVisibleDates() { - final List? nonWorkingDays = (_view == CalendarView.workWeek || - _view == CalendarView.timelineWorkWeek) - ? widget.timeSlotViewSettings.nonWorkingDays - : null; + final List? nonWorkingDays = + (_view == CalendarView.workWeek || + _view == CalendarView.timelineWorkWeek) + ? widget.timeSlotViewSettings.nonWorkingDays + : null; final int visibleDatesCount = DateTimeHelper.getViewDatesCount( - _view, - widget.monthViewSettings.numberOfWeeksInView, - widget.timeSlotViewSettings.numberOfDaysInView, - nonWorkingDays); + _view, + widget.monthViewSettings.numberOfWeeksInView, + widget.timeSlotViewSettings.numberOfDaysInView, + nonWorkingDays, + ); - _currentViewVisibleDates = getVisibleDates(_currentDate, nonWorkingDays, - widget.firstDayOfWeek, visibleDatesCount) - .cast(); + _currentViewVisibleDates = + getVisibleDates( + _currentDate, + nonWorkingDays, + widget.firstDayOfWeek, + visibleDatesCount, + ).cast(); if (_view == CalendarView.timelineMonth) { - _currentViewVisibleDates = - DateTimeHelper.getCurrentMonthDates(_currentViewVisibleDates); + _currentViewVisibleDates = DateTimeHelper.getCurrentMonthDates( + _currentViewVisibleDates, + ); if (widget.showWeekNumber) { _timelineMonthWeekNumberNotifier.value = _currentViewVisibleDates[0]; } @@ -4311,7 +4696,9 @@ class _SfCalendarState extends State //// Perform action while data source changed based on data source action. void _dataSourceChangedListener( - CalendarDataSourceAction type, List data) { + CalendarDataSourceAction type, + List data, + ) { if (!_timeZoneLoaded || !mounted) { return; } @@ -4326,7 +4713,7 @@ class _SfCalendarState extends State } if (_isNeedLoadMore || _isScheduleStartLoadMore) { - SchedulerBinding.instance?.addPostFrameCallback((Duration timeStamp) { + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { setState(() { _isNeedLoadMore = false; _isScheduleStartLoadMore = false; @@ -4346,7 +4733,10 @@ class _SfCalendarState extends State { final List collection = AppointmentHelper.generateCalendarAppointments( - widget.dataSource, widget.timeZone, data); + widget.dataSource, + widget.timeZone, + data, + ); _loadScheduleViewAppointmentDates(collection); if (_view != CalendarView.schedule) { @@ -4356,23 +4746,27 @@ class _SfCalendarState extends State _currentViewVisibleDates[visibleDatesCount - 1]; if (_view == CalendarView.month && !CalendarViewHelper.isLeadingAndTrailingDatesVisible( - widget.monthViewSettings.numberOfWeeksInView, - widget.monthViewSettings.showTrailingAndLeadingDates)) { + widget.monthViewSettings.numberOfWeeksInView, + widget.monthViewSettings.showTrailingAndLeadingDates, + )) { final DateTime currentMonthDate = _currentViewVisibleDates[visibleDatesCount ~/ 2]; - viewStartDate = - AppointmentHelper.getMonthStartDate(currentMonthDate); + viewStartDate = AppointmentHelper.getMonthStartDate( + currentMonthDate, + ); viewEndDate = AppointmentHelper.getMonthEndDate(currentMonthDate); } visibleAppointmentCollection.addAll( - AppointmentHelper.getVisibleAppointments( - viewStartDate, - viewEndDate, - collection, - widget.timeZone, - _view == CalendarView.month || - CalendarViewHelper.isTimelineView(_view))); + AppointmentHelper.getVisibleAppointments( + viewStartDate, + viewEndDate, + collection, + widget.timeZone, + _view == CalendarView.month || + CalendarViewHelper.isTimelineView(_view), + ), + ); } for (int i = 0; i < collection.length; i++) { @@ -4420,8 +4814,9 @@ class _SfCalendarState extends State if (resourceCollection.isNotEmpty) { _disposeResourceImagePainter(); setState(() { - _resourceCollection = - CalendarViewHelper.cloneList(widget.dataSource?.resources); + _resourceCollection = CalendarViewHelper.cloneList( + widget.dataSource?.resources, + ); /* To render the modified resource collection */ if (CalendarViewHelper.isTimelineView(_view)) { _isNeedLoadMore = true; @@ -4453,39 +4848,57 @@ class _SfCalendarState extends State /// hideEmptyScheduleWeek disabled in web UI with schedule view. final DateTime startDate = _previousDates.isEmpty ? _nextDates[0] : _previousDates[0]; - final DateTime endDate = addDuration(_nextDates[_nextDates.length - 1], - const Duration(days: DateTime.daysPerWeek - 1)) as DateTime; + final DateTime endDate = + addDuration( + _nextDates[_nextDates.length - 1], + const Duration(days: DateTime.daysPerWeek - 1), + ) + as DateTime; for (int i = 0; i < collection.length; i++) { /// recursiveDates list used to store the newly added appointments dates List recursiveDates = []; final CalendarAppointment appointment = collection[i]; final List recursiveExceptionDates = []; final Duration difference = AppointmentHelper.getDifference( - appointment.actualEndTime, appointment.actualStartTime); + appointment.actualStartTime, + appointment.actualEndTime, + ); if (appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty) { /// Calculate the occurrence dates collection. recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - appointment.recurrenceRule!, appointment.actualStartTime, - recurrenceDuration: difference, - specificStartDate: startDate, - specificEndDate: endDate); + appointment.recurrenceRule!, + appointment.actualStartTime, + recurrenceDuration: difference, + specificStartDate: startDate, + specificEndDate: endDate, + ); if (appointment.recurrenceExceptionDates != null) { - for (int i = 0; - i < appointment.recurrenceExceptionDates!.length; - i++) { + for ( + int i = 0; + i < appointment.recurrenceExceptionDates!.length; + i++ + ) { recursiveExceptionDates.add( - AppointmentHelper.convertTimeToAppointmentTimeZone( - appointment.recurrenceExceptionDates![i], - '', - widget.timeZone)); + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.recurrenceExceptionDates![i], + '', + widget.timeZone, + ), + ); } } } else { if (!isDateWithInDateRange( - startDate, endDate, appointment.actualStartTime) && + startDate, + endDate, + appointment.actualStartTime, + ) && !isDateWithInDateRange( - startDate, endDate, appointment.actualEndTime)) { + startDate, + endDate, + appointment.actualEndTime, + )) { continue; } @@ -4508,13 +4921,20 @@ class _SfCalendarState extends State DateTime appEndDate = addDuration(appointmentDate, difference) as DateTime; - appEndDate = - DateTime(appEndDate.year, appEndDate.month, appEndDate.day); + appEndDate = DateTime( + appEndDate.year, + appEndDate.month, + appEndDate.day, + ); DateTime appDate = DateTime( - appointmentDate.year, appointmentDate.month, appointmentDate.day); + appointmentDate.year, + appointmentDate.month, + appointmentDate.day, + ); /// Calculate start date of the week. - int value = -(appDate.weekday % DateTime.daysPerWeek) + + int value = + -(appDate.weekday % DateTime.daysPerWeek) + widget.firstDayOfWeek - DateTime.daysPerWeek; if (value.abs() >= DateTime.daysPerWeek) { @@ -4522,7 +4942,8 @@ class _SfCalendarState extends State } appDate = addDuration(appDate, Duration(days: value)) as DateTime; - value = -(appEndDate.weekday % DateTime.daysPerWeek) + + value = + -(appEndDate.weekday % DateTime.daysPerWeek) + widget.firstDayOfWeek - DateTime.daysPerWeek; if (value.abs() >= DateTime.daysPerWeek) { @@ -4531,7 +4952,7 @@ class _SfCalendarState extends State appEndDate = addDuration(appEndDate, Duration(days: value)) as DateTime; - void _addNewAppointmentWeekDate(DateTime date) { + void addNewAppointmentWeekDate(DateTime date) { /// Add newly added appointment dates into previous and next date /// collection based on the DateTime range. And it will ignore /// the dates which is same as previous date or next date. @@ -4547,10 +4968,13 @@ class _SfCalendarState extends State DateTime appCurrentDate = appDate; while (appCurrentDate.isBefore(appEndDate) || isSameDate(appCurrentDate, appEndDate)) { - _addNewAppointmentWeekDate(appCurrentDate); - appCurrentDate = addDuration( - appCurrentDate, const Duration(days: DateTime.daysPerWeek)) - as DateTime; + addNewAppointmentWeekDate(appCurrentDate); + appCurrentDate = + addDuration( + appCurrentDate, + const Duration(days: DateTime.daysPerWeek), + ) + as DateTime; } } } @@ -4572,16 +4996,24 @@ class _SfCalendarState extends State for (int i = 0; i < _previousDates.length; i++) { final DateTime weekStartDate = _previousDates[i]; - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(weekStartDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + addDays(weekStartDate, 6), + ); /// Remove the week date when it does not have appointments /// when [hideEmptyAgendaDays] as enabled. if (!_isAppointmentBetweenDates( - _appointments, weekStartDate, weekEndDate, widget.timeZone) && + _appointments, + weekStartDate, + weekEndDate, + widget.timeZone, + ) && !isDateWithInDateRange(weekStartDate, weekEndDate, DateTime.now()) && !isDateWithInDateRange( - weekStartDate, weekEndDate, _scheduleDisplayDate)) { + weekStartDate, + weekEndDate, + _scheduleDisplayDate, + )) { _previousDates.remove(weekStartDate); i--; } @@ -4590,16 +5022,24 @@ class _SfCalendarState extends State for (int i = 0; i < _nextDates.length; i++) { final DateTime weekStartDate = _nextDates[i]; - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(weekStartDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + addDays(weekStartDate, 6), + ); /// Remove the week date when it does not have appointments /// when [hideEmptyAgendaDays] as enabled. if (!_isAppointmentBetweenDates( - _appointments, weekStartDate, weekEndDate, widget.timeZone) && + _appointments, + weekStartDate, + weekEndDate, + widget.timeZone, + ) && !isDateWithInDateRange(weekStartDate, weekEndDate, DateTime.now()) && !isDateWithInDateRange( - weekStartDate, weekEndDate, _scheduleDisplayDate)) { + weekStartDate, + weekEndDate, + _scheduleDisplayDate, + )) { _nextDates.remove(weekStartDate); i--; } @@ -4620,9 +5060,10 @@ class _SfCalendarState extends State /// 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) { - SchedulerBinding.instance?.addPostFrameCallback((Duration timeStamp) { + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { setState(() { /// Update the view when the appointment collection changed. }); @@ -4631,7 +5072,9 @@ class _SfCalendarState extends State } if (CalendarViewHelper.isCollectionEqual( - _visibleAppointments, visibleAppointmentCollection)) { + _visibleAppointments, + visibleAppointmentCollection, + )) { return; } @@ -4640,7 +5083,7 @@ class _SfCalendarState extends State /// Update all day appointment related implementation in calendar, /// because time label view needs the top position. _updateAllDayAppointment(); - SchedulerBinding.instance?.addPostFrameCallback((Duration timeStamp) { + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { setState(() { /// Update the UI. }); @@ -4677,28 +5120,36 @@ class _SfCalendarState extends State view == CalendarView.month || view == CalendarView.timelineMonth; if (_selectedDate != null && isDateWithInDateRange( - visibleStartDate, visibleEndDate, _selectedDate)) { + visibleStartDate, + visibleEndDate, + _selectedDate, + )) { if (isMonthView) { 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!; } } else if (isDateWithInDateRange( - visibleStartDate, visibleEndDate, DateTime.now())) { + visibleStartDate, + visibleEndDate, + DateTime.now(), + )) { final DateTime date = DateTime.now(); return DateTime( - date.year, - date.month, - date.day, - _controller.displayDate!.hour, - _controller.displayDate!.minute, - _controller.displayDate!.second); + date.year, + date.month, + date.day, + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second, + ); } else { if (isMonthView) { if (widget.monthViewSettings.numberOfWeeksInView > 0 && @@ -4706,21 +5157,23 @@ class _SfCalendarState extends State return visibleStartDate; } return DateTime( - _currentDate.year, - _currentDate.month, - 1, - _controller.displayDate!.hour, - _controller.displayDate!.minute, - _controller.displayDate!.second); + _currentDate.year, + _currentDate.month, + 1, + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second, + ); } else { final DateTime date = visibleStartDate; return DateTime( - date.year, - date.month, - date.day, - _controller.displayDate!.hour, - _controller.displayDate!.minute, - _controller.displayDate!.second); + date.year, + date.month, + date.day, + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second, + ); } } } @@ -4747,10 +5200,15 @@ class _SfCalendarState extends State continue; } - final int startIndex = DateTimeHelper.getIndex(_currentViewVisibleDates, - appointmentView.appointment!.actualStartTime); - final int endIndex = DateTimeHelper.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; @@ -4763,7 +5221,8 @@ class _SfCalendarState extends State } void _updateAppointmentPositionAndMaxPosition( - List> allDayAppointmentView) { + List> allDayAppointmentView, + ) { for (int i = 0; i < allDayAppointmentView.length; i++) { final List intersectingAppointments = allDayAppointmentView[i]; @@ -4773,7 +5232,9 @@ class _SfCalendarState extends State currentView.position = 0; for (int k = 0; k < j; k++) { final AppointmentView? intersectView = _getAppointmentOnPosition( - currentView, intersectingAppointments); + currentView, + intersectingAppointments, + ); if (intersectView != null) { currentView.position++; } else { @@ -4784,12 +5245,17 @@ class _SfCalendarState extends State } if (intersectingAppointments.isNotEmpty) { - final int maxPosition = intersectingAppointments - .reduce((AppointmentView currentAppView, - AppointmentView nextAppView) => - currentAppView.position > nextAppView.position - ? currentAppView - : nextAppView) + final int maxPosition = + intersectingAppointments + .reduce( + ( + AppointmentView currentAppView, + AppointmentView nextAppView, + ) => + currentAppView.position > nextAppView.position + ? currentAppView + : nextAppView, + ) .position + 1; @@ -4805,7 +5271,9 @@ class _SfCalendarState extends State } AppointmentView? _getAppointmentOnPosition( - AppointmentView? currentView, List? views) { + AppointmentView? currentView, + List? views, + ) { if (currentView == null || currentView.appointment == null || views == null || @@ -4823,7 +5291,8 @@ class _SfCalendarState extends State } void _updateIntersectAppointmentViewCollection( - List> allDayAppointmentView) { + List> allDayAppointmentView, + ) { for (int i = 0; i < _currentViewVisibleDates.length; i++) { final List intersectingAppointments = []; @@ -4863,8 +5332,9 @@ class _SfCalendarState extends State for (final CalendarAppointment appointment in _visibleAppointments) { if (appointment.isAllDay || AppointmentHelper.getDifference( - appointment.actualStartTime, appointment.actualEndTime) - .inDays > + appointment.actualStartTime, + appointment.actualEndTime, + ).inDays > 0) { allDayAppointments.add(appointment); } @@ -4877,13 +5347,19 @@ class _SfCalendarState extends State _updateAppointmentViewPosition(); //// Sort the appointment view based on appointment view width. - _allDayAppointmentViewCollection - .sort((AppointmentView app1, AppointmentView app2) { + _allDayAppointmentViewCollection.sort(( + AppointmentView app1, + AppointmentView app2, + ) { if (app1.appointment != null && app2.appointment != null) { return AppointmentHelper.getDifference( - app2.appointment!.startTime, app2.appointment!.endTime) > + app2.appointment!.startTime, + app2.appointment!.endTime, + ) > AppointmentHelper.getDifference( - app1.appointment!.startTime, app1.appointment!.endTime) + app1.appointment!.startTime, + app1.appointment!.endTime, + ) ? 1 : 0; } @@ -4892,8 +5368,10 @@ class _SfCalendarState extends State }); //// Sort the appointment view based on appointment view start position. - _allDayAppointmentViewCollection - .sort((AppointmentView app1, AppointmentView app2) { + _allDayAppointmentViewCollection.sort(( + AppointmentView app1, + AppointmentView app2, + ) { if (app1.appointment != null && app2.appointment != null) { return app1.startIndex.compareTo(app2.startIndex); } @@ -4915,13 +5393,15 @@ class _SfCalendarState extends State void _updateAllDayPanelHeight() { int maxPosition = 0; if (_allDayAppointmentViewCollection.isNotEmpty) { - maxPosition = _allDayAppointmentViewCollection - .reduce( - (AppointmentView currentAppView, AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + maxPosition = + _allDayAppointmentViewCollection + .reduce( + (AppointmentView currentAppView, AppointmentView nextAppView) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; } if (maxPosition == -1) { @@ -4954,13 +5434,14 @@ class _SfCalendarState extends State } void _updateMouseHoverPosition( - Offset globalPosition, - bool isScheduleDisplayDate, - bool isRTL, - DateTime? currentDate, - double? startPosition, - [double padding = 0, - bool isResourceEnabled = false]) { + Offset globalPosition, + bool isScheduleDisplayDate, + bool isRTL, + DateTime? currentDate, + double? startPosition, [ + double padding = 0, + bool isResourceEnabled = false, + ]) { if (_isMobilePlatform) { return; } @@ -4980,7 +5461,8 @@ class _SfCalendarState extends State localPosition.dy > startPosition! && (CalendarViewHelper.shouldRaiseCalendarTapCallback(widget.onTap) || CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress))) { + widget.onLongPress, + ))) { if (_headerHoverNotifier.value != null) { _headerHoverNotifier.value = null; } @@ -4999,7 +5481,7 @@ class _SfCalendarState extends State final double yPosition = (_resourcePanelScrollController!.offset + localPosition.dy) - - startPosition; + startPosition; _resourceHoverNotifier.value = Offset(localPosition.dx, yPosition); } @@ -5011,7 +5493,9 @@ class _SfCalendarState extends State double yPosition = localPosition.dy; double xPosition = localPosition.dx; double dateViewWidth = _getAgendaViewDayLabelWidth( - widget.scheduleViewSettings, _useMobilePlatformUI); + widget.scheduleViewSettings, + _useMobilePlatformUI, + ); if (_view == CalendarView.month) { currentDate = _selectedDate; final double agendaHeight = _getMonthAgendaHeight(); @@ -5025,7 +5509,8 @@ class _SfCalendarState extends State dateViewWidth = 60; } } else { - yPosition = (_agendaScrollController!.offset + localPosition.dy) - + yPosition = + (_agendaScrollController!.offset + localPosition.dy) - startPosition! - widget.headerHeight; } @@ -5053,7 +5538,9 @@ class _SfCalendarState extends State xPosition = isRTL ? _minWidth - xPosition : xPosition; _agendaDateNotifier.value = ScheduleViewHoveringDetails( - currentDate!, Offset(xPosition, yPosition)); + 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 @@ -5083,29 +5570,51 @@ class _SfCalendarState extends State return; } _agendaViewNotifier.value = ScheduleViewHoveringDetails( - currentDate!, Offset(xPosition, yPosition)); + currentDate!, + Offset(xPosition, yPosition), + ); } } } void _pointerEnterEvent( - PointerEnterEvent event, bool isScheduleDisplayDate, bool isRTL, - [DateTime? currentDate, - double? startPosition, - double padding = 0, - bool resourceEnabled = false]) { - _updateMouseHoverPosition(event.position, isScheduleDisplayDate, isRTL, - currentDate, startPosition, padding, resourceEnabled); + PointerEnterEvent event, + bool isScheduleDisplayDate, + bool isRTL, [ + DateTime? currentDate, + double? startPosition, + double padding = 0, + bool resourceEnabled = false, + ]) { + _updateMouseHoverPosition( + event.position, + isScheduleDisplayDate, + isRTL, + currentDate, + startPosition, + padding, + resourceEnabled, + ); } void _pointerHoverEvent( - PointerHoverEvent event, bool isScheduleDisplayDate, bool isRTL, - [DateTime? currentDate, - double? startPosition, - double padding = 0, - bool resourceEnabled = false]) { - _updateMouseHoverPosition(event.position, isScheduleDisplayDate, isRTL, - currentDate, startPosition, padding, resourceEnabled); + PointerHoverEvent event, + bool isScheduleDisplayDate, + bool isRTL, [ + DateTime? currentDate, + double? startPosition, + double padding = 0, + bool resourceEnabled = false, + ]) { + _updateMouseHoverPosition( + event.position, + isScheduleDisplayDate, + isRTL, + currentDate, + startPosition, + padding, + resourceEnabled, + ); } void _pointerExitEvent(PointerExitEvent event) { @@ -5118,12 +5627,13 @@ class _SfCalendarState extends State /// 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) { + 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; @@ -5138,9 +5648,12 @@ class _SfCalendarState extends State /// 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); + appointment + .actualEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.endTime, + appointment.endTimeZone, + timeZone, + ); if (appointment.recurrenceRule == null || appointment.recurrenceRule == '') { @@ -5162,11 +5675,21 @@ class _SfCalendarState extends State if (appointment.recurrenceRule!.contains('UNTIL')) { final List ruleSeparator = ['=', ';', ',']; final List rRule = RecurrenceHelper.splitRule( - appointment.recurrenceRule!, ruleSeparator); + 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); + DateTime recurrenceEndDate = RecurrenceHelper.getUntilEndDate( + untilValue, + ); + recurrenceEndDate = DateTime( + recurrenceEndDate.year, + recurrenceEndDate.month, + recurrenceEndDate.day, + 23, + 59, + 59, + ); if (recurrenceEndDate.isAfter(currentMaxDate)) { currentMaxDate = recurrenceEndDate; continue; @@ -5175,9 +5698,9 @@ class _SfCalendarState extends State final List recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - appointment.recurrenceRule!, - appointment.actualStartTime, - ); + appointment.recurrenceRule!, + appointment.actualStartTime, + ); if (recursiveDates.isEmpty) { continue; @@ -5193,14 +5716,18 @@ class _SfCalendarState extends State } final Duration duration = AppointmentHelper.getDifference( - appointment.actualStartTime, appointment.actualEndTime); + appointment.actualStartTime, + appointment.actualEndTime, + ); 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++) { + for ( + int i = 0; + i < appointment.recurrenceExceptionDates!.length; + i++ + ) { final DateTime exceptionDate = appointment.recurrenceExceptionDates![i]; if (isSameDate(recurrenceDate, exceptionDate)) { @@ -5211,7 +5738,8 @@ class _SfCalendarState extends State if (!isExceptionDate) { final DateTime recurrenceEndDate = DateTimeHelper.getDateTimeValue( - addDuration(recurrenceDate, duration)); + addDuration(recurrenceDate, duration), + ); if (recurrenceEndDate.isAfter(currentMaxDate)) { currentMaxDate = recurrenceEndDate; break; @@ -5226,13 +5754,14 @@ class _SfCalendarState extends State /// 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 + 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; } @@ -5246,9 +5775,12 @@ class _SfCalendarState extends State /// 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); + appointment + .actualStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.startTime, + appointment.startTimeZone, + timeZone, + ); if (appointment.actualStartTime.isBefore(currentMinDate)) { currentMinDate = appointment.actualStartTime; @@ -5262,8 +5794,12 @@ class _SfCalendarState extends State /// Check any appointment in appointments collection in between /// the start and end date. - bool _isAppointmentBetweenDates(List appointments, - DateTime startDate, DateTime endDate, String? timeZone) { + bool _isAppointmentBetweenDates( + List appointments, + DateTime startDate, + DateTime endDate, + String? timeZone, + ) { startDate = AppointmentHelper.convertToStartTime(startDate); endDate = AppointmentHelper.convertToEndTime(endDate); if (appointments.isEmpty) { @@ -5272,17 +5808,26 @@ class _SfCalendarState extends State 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); + 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)) { + appointment, + startDate, + endDate, + )) { return true; } @@ -5301,13 +5846,23 @@ class _SfCalendarState extends State } final List ruleSeparator = ['=', ';', ',']; - final List rRule = - RecurrenceHelper.splitRule(rule, 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); + DateTime recurrenceEndDate = RecurrenceHelper.getUntilEndDate( + untilValue, + ); + recurrenceEndDate = DateTime( + recurrenceEndDate.year, + recurrenceEndDate.month, + recurrenceEndDate.day, + 23, + 59, + 59, + ); if (recurrenceEndDate.isBefore(startDate)) { continue; } @@ -5315,11 +5870,15 @@ class _SfCalendarState extends State final List recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - rule, appointment.actualStartTime, - recurrenceDuration: AppointmentHelper.getDifference( - appointment.actualStartTime, appointment.actualEndTime), - specificStartDate: startDate, - specificEndDate: endDate); + rule, + appointment.actualStartTime, + recurrenceDuration: AppointmentHelper.getDifference( + appointment.actualStartTime, + appointment.actualEndTime, + ), + specificStartDate: startDate, + specificEndDate: endDate, + ); if (recursiveDates.isEmpty) { continue; @@ -5370,17 +5929,21 @@ class _SfCalendarState extends State /// Return the collection of appointment collection listed by /// start date of the appointment. Map> _getAppointmentCollectionOnDateBasis( - List appointmentCollection, - DateTime startDate, - DateTime endDate) { + 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)) { + if (!isDateWithInDateRange( + appointment.actualStartTime, + appointment.actualEndTime, + startDate, + )) { continue; } @@ -5423,45 +5986,50 @@ class _SfCalendarState extends State /// in [ScheduleViewSettings] disabled else it return min /// start date of the appointment collection. _minDate ??= _getMinAppointmentDate( - _appointments, - widget.timeZone, - widget.minDate, - scheduleDisplayDate, - widget.scheduleViewSettings, - _useMobilePlatformUI); + _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!.isAfter(scheduleDisplayDate) + ? scheduleDisplayDate + : _minDate; _minDate = _minDate!.isBefore(widget.minDate) ? widget.minDate : _minDate; final DateTime viewMinDate = DateTimeHelper.getDateTimeValue( - addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek))); + 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); + _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!.isBefore(scheduleCurrentDate) + ? scheduleCurrentDate + : _maxDate; _maxDate = _maxDate!.isAfter(widget.maxDate) ? widget.maxDate : _maxDate; final bool hideEmptyAgendaDays = widget.scheduleViewSettings.hideEmptyScheduleWeek || - !_useMobilePlatformUI; + !_useMobilePlatformUI; if (index >= 0) { /// Add next 100 dates to next dates collection when index @@ -5475,7 +6043,8 @@ class _SfCalendarState extends State while (count < 20) { for (int i = 1; i <= 100; i++) { final DateTime updateDate = DateTimeHelper.getDateTimeValue( - addDays(date, i * DateTime.daysPerWeek)); + addDays(date, i * DateTime.daysPerWeek), + ); /// Skip the weeks after the max date. if (!isSameOrBeforeDate(_maxDate, updateDate)) { @@ -5483,19 +6052,30 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(updateDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + 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) || + _isAppointmentBetweenDates( + _appointments, + updateDate, + weekEndDate, + widget.timeZone, + ) || isDateWithInDateRange( - updateDate, weekEndDate, scheduleDisplayDate) || + updateDate, + weekEndDate, + scheduleDisplayDate, + ) || isDateWithInDateRange( - updateDate, weekEndDate, scheduleCurrentDate)) { + updateDate, + weekEndDate, + scheduleCurrentDate, + )) { _nextDates.add(updateDate); count++; } @@ -5518,7 +6098,8 @@ class _SfCalendarState extends State while (count < 20) { for (int i = 1; i <= 100; i++) { final DateTime updatedDate = DateTimeHelper.getDateTimeValue( - addDays(date, -i * DateTime.daysPerWeek)); + addDays(date, -i * DateTime.daysPerWeek), + ); /// Skip the weeks before the min date. if (!isSameOrAfterDate(viewMinDate, updatedDate)) { @@ -5526,19 +6107,30 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(updatedDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + 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) || + _isAppointmentBetweenDates( + _appointments, + updatedDate, + weekEndDate, + widget.timeZone, + ) || isDateWithInDateRange( - updatedDate, weekEndDate, scheduleDisplayDate) || + updatedDate, + weekEndDate, + scheduleDisplayDate, + ) || isDateWithInDateRange( - updatedDate, weekEndDate, scheduleCurrentDate)) { + updatedDate, + weekEndDate, + scheduleCurrentDate, + )) { _previousDates.add(updatedDate); count++; } @@ -5566,49 +6158,69 @@ class _SfCalendarState extends State /// next dates collection then get the start date from previous dates. /// If the index as last index of previous dates collection then calculate /// by subtract the 7 days to get previous date. - final DateTime prevDate = index == 0 - ? _previousDates.isEmpty - ? DateTimeHelper.getDateTimeValue( - addDays(startDate, -DateTime.daysPerWeek)) - : _previousDates[0] - : (index > 0 - ? _nextDates[index - 1] - : -index > _previousDates.length - 1 + final DateTime prevDate = + index == 0 + ? _previousDates.isEmpty + ? DateTimeHelper.getDateTimeValue( + addDays(startDate, -DateTime.daysPerWeek), + ) + : _previousDates[0] + : (index > 0 + ? _nextDates[index - 1] + : -index > _previousDates.length - 1 ? DateTimeHelper.getDateTimeValue( - addDays(startDate, -DateTime.daysPerWeek)) + addDays(startDate, -DateTime.daysPerWeek), + ) : _previousDates[-index]); - final DateTime prevEndDate = - DateTimeHelper.getDateTimeValue(addDays(prevDate, 6)); - final DateTime endDate = - DateTimeHelper.getDateTimeValue(addDays(startDate, 6)); + final DateTime prevEndDate = DateTimeHelper.getDateTimeValue( + addDays(prevDate, 6), + ); + final DateTime endDate = DateTimeHelper.getDateTimeValue( + addDays(startDate, 6), + ); + + bool initialMonthHeader = false; + if (((index == 0 && _previousDates.isEmpty) || + (index < 0 && -index > _previousDates.length - 1)) && + startDate.month == _minDate!.month) { + initialMonthHeader = true; + } /// Get the visible week appointment and split the appointments based on /// date. final List appointmentCollection = AppointmentHelper.getVisibleAppointments( - isSameOrAfterDate(_minDate, startDate) ? startDate : _minDate!, - isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate!, - _appointments, - widget.timeZone, - false, - canCreateNewAppointment: false); + 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)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); /// Get the collection of appointment collection listed by date. final Map> dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, startDate, endDate); + appointmentCollection, + startDate, + endDate, + ); final List dateAppointmentKeys = dateAppointments.keys.toList(); const double padding = 5; /// Check the current week view show display date or current date view. - bool isNeedDisplayDateHighlight = - isDateWithInDateRange(startDate, endDate, scheduleDisplayDate); + bool isNeedDisplayDateHighlight = isDateWithInDateRange( + startDate, + endDate, + scheduleDisplayDate, + ); bool isNeedCurrentDateHighlight = isDateWithInDateRange(startDate, endDate, scheduleCurrentDate) && - !isSameDate(scheduleDisplayDate, scheduleCurrentDate); + !isSameDate(scheduleDisplayDate, scheduleCurrentDate); /// Check the schedule display date have appointments if display date /// in between the week @@ -5638,24 +6250,30 @@ class _SfCalendarState extends State /// calculate the day label(eg., May 25) width based on schedule setting. final double viewPadding = _getAgendaViewDayLabelWidth( - widget.scheduleViewSettings, _useMobilePlatformUI); + widget.scheduleViewSettings, + _useMobilePlatformUI, + ); final double viewTopPadding = _useMobilePlatformUI ? padding : 0; /// calculate the total height using height variable /// web view does not have week label. - double height = _useMobilePlatformUI - ? widget.scheduleViewSettings.weekHeaderSettings.height - : 0; + double height = + _useMobilePlatformUI + ? widget.scheduleViewSettings.weekHeaderSettings.height + : 0; /// It is used to current view top position inside the collection of views. double topHeight = 0; /// Check the week date needs month header at first or before of appointment /// view. - bool isNeedMonthBuilder = _useMobilePlatformUI && + + bool isNeedMonthBuilder = + _useMobilePlatformUI && (prevEndDate.month != startDate.month || - prevEndDate.year != startDate.year); + prevEndDate.year != startDate.year || + initialMonthHeader); /// Check the start date have month header and the current week have /// different month dates(like Feb 27 - Mar 5). This scenario raised when @@ -5683,43 +6301,49 @@ class _SfCalendarState extends State } /// Web view does not have month label. - height += isNeedMonthBuilder - ? widget.scheduleViewSettings.monthHeaderSettings.height - : 0; + height += + isNeedMonthBuilder + ? widget.scheduleViewSettings.monthHeaderSettings.height + : 0; final double appointmentViewHeight = CalendarViewHelper.getScheduleAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); /// Calculate the divider height and color when it is web view. final double dividerHeight = _useMobilePlatformUI ? 0 : 1; Color dividerColor = widget.cellBorderColor ?? _calendarTheme.cellBorderColor!; - dividerColor = dividerColor.withOpacity(dividerColor.opacity * 0.5); + dividerColor = dividerColor.withValues(alpha: dividerColor.a * 0.5); int numberOfEvents = 0; double appointmentHeight = 0; /// Calculate the total height of appointment views of week. for (int i = 0; i < dateAppointmentKeys.length; i++) { - final List _currentDateAppointment = + final List currentDateAppointment = dateAppointments[dateAppointmentKeys[i]]!; - final int eventsCount = _currentDateAppointment.length; + final int eventsCount = currentDateAppointment.length; int allDayEventCount = 0; /// Web view does not differentiate all day and normal appointment. if (_useMobilePlatformUI) { - allDayEventCount = _getAllDayCount(_currentDateAppointment); + allDayEventCount = _getAllDayCount(currentDateAppointment); } double panelHeight = ((eventsCount - allDayEventCount) * appointmentViewHeight) + - (allDayEventCount * allDayAppointmentHeight); - panelHeight = panelHeight > appointmentViewHeight - ? panelHeight - : appointmentViewHeight; + (allDayEventCount * allDayAppointmentHeight); + panelHeight = + panelHeight > appointmentViewHeight + ? panelHeight + : appointmentViewHeight; appointmentHeight += panelHeight + dividerHeight; numberOfEvents += eventsCount; } @@ -5745,13 +6369,15 @@ class _SfCalendarState extends State /// Get the previous view end position used to find the next view end /// position. if (currentIndex >= 0) { - previousHeight = currentIndex == 0 - ? 0 - : _forwardWidgetHeights[currentIndex - 1]!._height; + previousHeight = + currentIndex == 0 + ? 0 + : _forwardWidgetHeights[currentIndex - 1]!._height; } else { - previousHeight = currentIndex == -1 - ? 0 - : _backwardWidgetHeights[-currentIndex - 2]!._height; + previousHeight = + currentIndex == -1 + ? 0 + : _backwardWidgetHeights[-currentIndex - 2]!._height; } final List widgets = []; @@ -5768,8 +6394,16 @@ class _SfCalendarState extends State height += viewTopPadding; } - widgets.add(_getMonthOrWeekHeader(startDate, endDate, isRTL, false, - viewPadding: viewPadding, isNeedTopPadding: isNeedMonthBuilder)); + widgets.add( + _getMonthOrWeekHeader( + startDate, + endDate, + isRTL, + false, + viewPadding: viewPadding, + isNeedTopPadding: isNeedMonthBuilder, + ), + ); /// Add the height of week label to update the top position of next view. topHeight += widget.scheduleViewSettings.weekHeaderSettings.height; @@ -5786,7 +6420,8 @@ class _SfCalendarState extends State /// Check the week date needs month header at in between the appointment /// views. - bool isNeedInBetweenMonthBuilder = _useMobilePlatformUI && + bool isNeedInBetweenMonthBuilder = + _useMobilePlatformUI && (startDate.month != (isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate!) .month); @@ -5794,14 +6429,16 @@ class _SfCalendarState extends State /// Check the end date month have appointments or not. bool isNextMonthHasNoAppointment = false; if (isNeedInBetweenMonthBuilder) { - final DateTime? lastAppointmentDate = dateAppointmentKeys.isNotEmpty - ? dateAppointmentKeys[dateAppointmentKeys.length - 1] - : null; - final DateTime? nextWeekDate = index == -1 - ? _nextDates[0] - : (index < 0 - ? _previousDates[-index - 2] - : index >= _nextDates.length - 1 + final DateTime? lastAppointmentDate = + dateAppointmentKeys.isNotEmpty + ? dateAppointmentKeys[dateAppointmentKeys.length - 1] + : null; + final DateTime? nextWeekDate = + index == -1 + ? _nextDates[0] + : (index < 0 + ? _previousDates[-index - 2] + : index >= _nextDates.length - 1 ? null : _nextDates[index + 1]); @@ -5813,13 +6450,15 @@ class _SfCalendarState extends State /// 3. If the week have appointments but next month dates does not have /// an appointments but [hideEmptyScheduleWeek] enabled so the next view /// date month as different with current week end date week. - isNextMonthHasNoAppointment = lastAppointmentDate == null || + isNextMonthHasNoAppointment = + lastAppointmentDate == null || (lastAppointmentDate.month != endDate.month && nextWeekDate != null && nextWeekDate.month == endDate.month && nextWeekDate.year == endDate.year); - isNeedInBetweenMonthBuilder = isNextMonthHasNoAppointment || + isNeedInBetweenMonthBuilder = + isNextMonthHasNoAppointment || lastAppointmentDate.month != startDate.month; } @@ -5828,26 +6467,32 @@ class _SfCalendarState extends State /// next month dates does not have appointments and is before max date. if (isNeedInBetweenMonthBuilder && (!isNextMonthHasNoAppointment || - isSameOrBeforeDate(_maxDate, endDate))) { + isSameOrBeforeDate( + _maxDate, + DateTime(endDate.year, endDate.month), + ))) { /// Add the height of month label to total height of view and /// Add the month header top padding value to height when in between /// week needs month header - height += widget.scheduleViewSettings.monthHeaderSettings.height + + height += + widget.scheduleViewSettings.monthHeaderSettings.height + viewTopPadding; } /// Add appointment height to height when the view have display date view. if (isNeedDisplayDateHighlight) { - height += _useMobilePlatformUI - ? appointmentViewHeaderHeight - : appointmentViewHeaderHeight + dividerHeight; + height += + _useMobilePlatformUI + ? appointmentViewHeaderHeight + : appointmentViewHeaderHeight + dividerHeight; } /// Add appointment height to height when the view have current date view. if (isNeedCurrentDateHighlight) { - height += _useMobilePlatformUI - ? appointmentViewHeaderHeight - : appointmentViewHeaderHeight + dividerHeight; + height += + _useMobilePlatformUI + ? appointmentViewHeaderHeight + : appointmentViewHeaderHeight + dividerHeight; } /// display date highlight added boolean variable used to identify the @@ -5871,41 +6516,51 @@ class _SfCalendarState extends State allDayEventCount = _getAllDayCount(currentAppointments); } - void _addMonthHeaderView() { + void addMonthHeaderView() { /// Assign the intersection point based on previous view end position. - scheduleViewDetails._intersectPoint = currentIndex >= 0 - ? previousHeight + interSectPoint + viewTopPadding - : previousHeight + height - interSectPoint - viewTopPadding; + scheduleViewDetails._intersectPoint = + currentIndex >= 0 + ? previousHeight + interSectPoint + viewTopPadding + : previousHeight + height - interSectPoint - viewTopPadding; /// Web view does not have month label; if (_useMobilePlatformUI) { interSectPoint += widget.scheduleViewSettings.monthHeaderSettings.height + - viewTopPadding; - widgets.add(_getMonthOrWeekHeader(currentDate, null, isRTL, true, - isNeedTopPadding: true)); + viewTopPadding; + widgets.add( + _getMonthOrWeekHeader( + currentDate, + null, + isRTL, + true, + isNeedTopPadding: true, + ), + ); } } - void _addDisplayOrCurrentDateView({bool isDisplayDate = true}) { - final double highlightViewStartPosition = currentIndex >= 0 - ? previousHeight + interSectPoint - : -(previousHeight + height - interSectPoint); - widgets.add(_getDisplayDateView( + void addDisplayOrCurrentDateView({bool isDisplayDate = true}) { + final double highlightViewStartPosition = + currentIndex >= 0 + ? previousHeight + interSectPoint + : -(previousHeight + height - interSectPoint); + widgets.add( + _getDisplayDateView( isRTL, isDisplayDate ? scheduleDisplayDate : scheduleCurrentDate, highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding)); + padding, + ), + ); /// Add divider at end of each of the week days in web view. if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); + widgets.add( + Divider(height: dividerHeight, thickness: 1, color: dividerColor), + ); } /// Add intersect value with appointment height and divider height @@ -5930,7 +6585,7 @@ class _SfCalendarState extends State if (!isDisplayDateHighlightAdded && currentDate.isAfter(scheduleDisplayDate) && currentDate.month != scheduleDisplayDate.month) { - _addDisplayOrCurrentDateView(isDisplayDate: true); + addDisplayOrCurrentDateView(); } /// Check the current date view not added in widget and appointment @@ -5944,7 +6599,7 @@ class _SfCalendarState extends State if (!isCurrentDateHighlightAdded && currentDate.isAfter(scheduleCurrentDate) && currentDate.month != scheduleCurrentDate.month) { - _addDisplayOrCurrentDateView(isDisplayDate: false); + addDisplayOrCurrentDateView(isDisplayDate: false); } /// Check if the view intersection point not set and the current week date @@ -5953,25 +6608,26 @@ class _SfCalendarState extends State if (scheduleViewDetails._intersectPoint == -1 && (startDate.month != currentDate.month || startDate.year != currentDate.year)) { - _addMonthHeaderView(); + addMonthHeaderView(); } /// 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); + addDisplayOrCurrentDateView(); } /// 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); + addDisplayOrCurrentDateView(isDisplayDate: false); } final double totalPadding = (eventsCount + 1) * padding; - final double panelHeight = totalPadding + + final double panelHeight = + totalPadding + ((eventsCount - allDayEventCount) * appointmentViewHeight) + (allDayEventCount * allDayAppointmentHeight); double appointmentViewTopPadding = 0; @@ -5981,77 +6637,106 @@ class _SfCalendarState extends State appointmentViewTopPadding = appointmentViewPadding / 2; } - final double viewStartPosition = currentIndex >= 0 - ? previousHeight + interSectPoint - : -(previousHeight + height - interSectPoint); + final double viewStartPosition = + currentIndex >= 0 + ? previousHeight + interSectPoint + : -(previousHeight + height - interSectPoint); interSectPoint += appointmentViewPadding; currentAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); currentAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, + app2.isAllDay, + ), + ); currentAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, + app2.isSpanned, + ), + ); /// Add appointment view to the current views collection. - widgets.add(MouseRegion( + widgets.add( + MouseRegion( onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, false, isRTL, currentDate, - viewStartPosition, appointmentViewTopPadding); + _pointerEnterEvent( + event, + false, + isRTL, + currentDate, + viewStartPosition, + appointmentViewTopPadding, + ); }, onExit: _pointerExitEvent, onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, false, isRTL, currentDate, - viewStartPosition, appointmentViewTopPadding); + _pointerHoverEvent( + event, + false, + isRTL, + currentDate, + viewStartPosition, + appointmentViewTopPadding, + ); }, child: GestureDetector( child: _ScheduleAppointmentView( - header: CustomPaint( - painter: _AgendaDateTimePainter( - currentDate, - null, - widget.scheduleViewSettings, - widget.todayHighlightColor ?? - _calendarTheme.todayHighlightColor, - widget.todayTextStyle, - _locale, - _calendarTheme, - _agendaDateNotifier, - _minWidth, - isRTL, - _textScaleFactor, - _isMobilePlatform), - size: Size(viewPadding, appointmentViewHeaderHeight)), - content: Container( - padding: EdgeInsets.fromLTRB( - isRTL ? 0 : viewPadding, - appointmentViewTopPadding, - isRTL ? viewPadding : 0, - appointmentViewTopPadding), - child: AgendaViewLayout( - null, - widget.scheduleViewSettings, - currentDate, - currentAppointments, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - viewPadding, - _textScaleFactor, - _isMobilePlatform, - widget.appointmentBuilder, - _minWidth - viewPadding, - panelHeight, - widget), - )), + header: CustomPaint( + painter: _AgendaDateTimePainter( + currentDate, + null, + widget.scheduleViewSettings, + widget.todayHighlightColor ?? + _calendarTheme.todayHighlightColor, + widget.todayTextStyle, + _locale, + _calendarTheme, + _themeData, + _agendaDateNotifier, + _minWidth, + isRTL, + _textScaleFactor, + _isMobilePlatform, + ), + size: Size(viewPadding, appointmentViewHeaderHeight), + ), + content: Container( + padding: EdgeInsets.fromLTRB( + isRTL ? 0 : viewPadding, + appointmentViewTopPadding, + isRTL ? viewPadding : 0, + appointmentViewTopPadding, + ), + child: AgendaViewLayout( + null, + widget.scheduleViewSettings, + currentDate, + currentAppointments, + isRTL, + _locale, + _localizations, + _calendarTheme, + _themeData, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + viewPadding, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + _minWidth - viewPadding, + panelHeight, + widget.monthViewSettings.agendaStyle.placeholderTextStyle, + widget, + ), + ), + ), onTapUp: (TapUpDetails details) { _removeDatePicker(); if (widget.allowViewNavigation && @@ -6064,12 +6749,19 @@ class _SfCalendarState extends State } if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.onTap)) { + widget.onTap, + )) { return; } - _raiseCallbackForScheduleView(currentDate, details.localPosition, - currentAppointments, viewPadding, padding, true); + _raiseCallbackForScheduleView( + currentDate, + details.localPosition, + currentAppointments, + viewPadding, + padding, + true, + ); }, onLongPressStart: (LongPressStartDetails details) { _removeDatePicker(); @@ -6083,24 +6775,31 @@ class _SfCalendarState extends State } if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress)) { + widget.onLongPress, + )) { return; } - _raiseCallbackForScheduleView(currentDate, details.localPosition, - currentAppointments, viewPadding, padding, false); - }, - ))); + _raiseCallbackForScheduleView( + currentDate, + details.localPosition, + currentAppointments, + viewPadding, + padding, + false, + ); + }, + ), + ), + ); interSectPoint += panelHeight + dividerHeight; /// Add divider at end of each of the week days in web view. if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); + widgets.add( + Divider(height: dividerHeight, thickness: 1, color: dividerColor), + ); } } @@ -6108,24 +6807,26 @@ class _SfCalendarState extends State /// end date month value. if (!isDisplayDateHighlightAdded && endDate.month != scheduleDisplayDate.month) { - final double highlightViewStartPosition = currentIndex >= 0 - ? previousHeight + topHeight + appointmentHeight - : previousHeight + height - topHeight - appointmentHeight; - widgets.add(_getDisplayDateView( + final double highlightViewStartPosition = + currentIndex >= 0 + ? previousHeight + topHeight + appointmentHeight + : previousHeight + height - topHeight - appointmentHeight; + widgets.add( + _getDisplayDateView( isRTL, scheduleDisplayDate, highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding)); + padding, + ), + ); /// Add divider at end of each of the week days in web view. if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); + widgets.add( + Divider(height: dividerHeight, thickness: 1, color: dividerColor), + ); } /// Add the top height value with display date view height because the @@ -6139,24 +6840,26 @@ class _SfCalendarState extends State /// end date month value. if (!isCurrentDateHighlightAdded && endDate.month != scheduleCurrentDate.month) { - final double highlightViewStartPosition = currentIndex >= 0 - ? previousHeight + topHeight + appointmentHeight - : previousHeight + height - topHeight - appointmentHeight; - widgets.add(_getDisplayDateView( + final double highlightViewStartPosition = + currentIndex >= 0 + ? previousHeight + topHeight + appointmentHeight + : previousHeight + height - topHeight - appointmentHeight; + widgets.add( + _getDisplayDateView( isRTL, scheduleCurrentDate, highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding)); + padding, + ), + ); /// Add divider at end of each of the week days in web view. if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); + widgets.add( + Divider(height: dividerHeight, thickness: 1, color: dividerColor), + ); } /// Add the top height value with current date view height because the @@ -6173,45 +6876,56 @@ class _SfCalendarState extends State if (_useMobilePlatformUI && isNeedInBetweenMonthBuilder && isNextMonthHasNoAppointment && - isSameOrBeforeDate(_maxDate, endDate)) { + isSameOrBeforeDate(_maxDate, DateTime(endDate.year, endDate.month))) { /// Calculate and assign the intersection point because the current /// view holds next month label. if scrolling reaches this position /// then we update the header date so add the location to intersecting /// point. - scheduleViewDetails._intersectPoint = currentIndex >= 0 - ? previousHeight + topHeight + appointmentHeight + viewTopPadding - : previousHeight + - height - - topHeight - - appointmentHeight - - viewTopPadding; - topHeight += widget.scheduleViewSettings.monthHeaderSettings.height + + scheduleViewDetails._intersectPoint = + currentIndex >= 0 + ? previousHeight + topHeight + appointmentHeight + viewTopPadding + : previousHeight + + height - + topHeight - + appointmentHeight - + viewTopPadding; + topHeight += + widget.scheduleViewSettings.monthHeaderSettings.height + viewTopPadding; - widgets.add(_getMonthOrWeekHeader(endDate, endDate, isRTL, true, - isNeedTopPadding: true)); + widgets.add( + _getMonthOrWeekHeader( + endDate, + endDate, + isRTL, + true, + isNeedTopPadding: true, + ), + ); } /// Add the display date view at end of week view when /// it does not added to widget. if (!isDisplayDateHighlightAdded) { - final double highlightViewStartPosition = currentIndex >= 0 - ? previousHeight + topHeight + appointmentHeight - : previousHeight + height - topHeight - appointmentHeight; - widgets.add(_getDisplayDateView( + final double highlightViewStartPosition = + currentIndex >= 0 + ? previousHeight + topHeight + appointmentHeight + : previousHeight + height - topHeight - appointmentHeight; + widgets.add( + _getDisplayDateView( isRTL, scheduleDisplayDate, highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding)); + padding, + ), + ); /// Add divider at end of each of the week days in web view. if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); + widgets.add( + Divider(height: dividerHeight, thickness: 1, color: dividerColor), + ); } isDisplayDateHighlightAdded = true; } @@ -6219,24 +6933,26 @@ class _SfCalendarState extends State /// Add the current date view at end of week view /// when it does not added to widget. if (!isCurrentDateHighlightAdded) { - final double highlightViewStartPosition = currentIndex >= 0 - ? previousHeight + topHeight + appointmentHeight - : previousHeight + height - topHeight - appointmentHeight; - widgets.add(_getDisplayDateView( + final double highlightViewStartPosition = + currentIndex >= 0 + ? previousHeight + topHeight + appointmentHeight + : previousHeight + height - topHeight - appointmentHeight; + widgets.add( + _getDisplayDateView( isRTL, scheduleCurrentDate, highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, - padding)); + padding, + ), + ); /// Add divider at end of each of the week days in web view. if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); + widgets.add( + Divider(height: dividerHeight, thickness: 1, color: dividerColor), + ); } isCurrentDateHighlightAdded = true; } @@ -6254,206 +6970,261 @@ class _SfCalendarState extends State } Widget _getMonthOrWeekHeader( - DateTime startDate, DateTime? endDate, bool isRTL, bool isMonthLabel, - {double viewPadding = 0, bool isNeedTopPadding = false}) { + DateTime startDate, + DateTime? endDate, + bool isRTL, + bool isMonthLabel, { + double viewPadding = 0, + bool isNeedTopPadding = false, + }) { const double padding = 5; Widget? headerWidget; if (isMonthLabel && widget.scheduleViewMonthHeaderBuilder != null) { final ScheduleViewMonthHeaderDetails details = ScheduleViewMonthHeaderDetails( - DateTime(startDate.year, startDate.month, 1), - Rect.fromLTWH(0, 0, _minWidth, - widget.scheduleViewSettings.monthHeaderSettings.height)); + DateTime(startDate.year, startDate.month), + Rect.fromLTWH( + 0, + 0, + _minWidth, + widget.scheduleViewSettings.monthHeaderSettings.height, + ), + ); headerWidget = widget.scheduleViewMonthHeaderBuilder!(context, details); } return GestureDetector( - child: Container( - padding: isMonthLabel + child: Container( + padding: + isMonthLabel ? EdgeInsets.fromLTRB(0, isNeedTopPadding ? padding : 0, 0, 0) - : EdgeInsets.fromLTRB(isRTL ? 0 : viewPadding, - isNeedTopPadding ? padding : 0, isRTL ? viewPadding : 0, 0), - child: RepaintBoundary( - child: headerWidget != null - ? SizedBox( - width: _minWidth, - height: widget - .scheduleViewSettings.monthHeaderSettings.height, - child: headerWidget, - ) - : CustomPaint( - painter: _ScheduleLabelPainter( - startDate, - endDate, - widget.scheduleViewSettings, - isMonthLabel, - isRTL, - _locale, - _useMobilePlatformUI, - _agendaViewNotifier, - _calendarTheme, - _localizations, - _textScaleFactor), - size: isMonthLabel + : EdgeInsets.fromLTRB( + isRTL ? 0 : viewPadding, + isNeedTopPadding ? padding : 0, + isRTL ? viewPadding : 0, + 0, + ), + child: RepaintBoundary( + child: + headerWidget != null + ? SizedBox( + width: _minWidth, + height: + widget.scheduleViewSettings.monthHeaderSettings.height, + child: headerWidget, + ) + : CustomPaint( + painter: _ScheduleLabelPainter( + startDate, + endDate, + widget.scheduleViewSettings, + isMonthLabel, + isRTL, + _locale, + _useMobilePlatformUI, + _agendaViewNotifier, + _calendarTheme, + _themeData, + _localizations, + _textScaleFactor, + ), + size: + isMonthLabel ? Size( - _minWidth, - widget.scheduleViewSettings.monthHeaderSettings - .height) + _minWidth, + widget + .scheduleViewSettings + .monthHeaderSettings + .height, + ) : Size( - _minWidth - viewPadding - (2 * padding), - widget.scheduleViewSettings.weekHeaderSettings - .height), - ))), + _minWidth - viewPadding - (2 * padding), + widget + .scheduleViewSettings + .weekHeaderSettings + .height, + ), + ), + ), + ), + onTapUp: (TapUpDetails details) { + _removeDatePicker(); + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback(widget.onTap)) { + return; + } + + CalendarViewHelper.raiseCalendarTapCallback( + widget, + DateTime(startDate.year, startDate.month, startDate.day), + null, + isMonthLabel ? CalendarElement.header : CalendarElement.viewHeader, + null, + ); + }, + onLongPressStart: (LongPressStartDetails details) { + _removeDatePicker(); + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress, + )) { + return; + } + + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, + DateTime(startDate.year, startDate.month, startDate.day), + null, + isMonthLabel ? CalendarElement.header : CalendarElement.viewHeader, + null, + ); + }, + ); + } + + Widget _getDisplayDateView( + bool isRTL, + DateTime currentDisplayDate, + double highlightViewStartPosition, + double viewHeaderWidth, + double displayDateHighlightHeight, + double padding, + ) { + return MouseRegion( + onEnter: (PointerEnterEvent event) { + _pointerEnterEvent( + event, + true, + isRTL, + currentDisplayDate, + highlightViewStartPosition, + ); + }, + onExit: _pointerExitEvent, + onHover: (PointerHoverEvent event) { + _pointerHoverEvent( + event, + true, + isRTL, + currentDisplayDate, + highlightViewStartPosition, + ); + }, + child: GestureDetector( + child: _ScheduleAppointmentView( + header: CustomPaint( + painter: _AgendaDateTimePainter( + currentDisplayDate, + null, + widget.scheduleViewSettings, + widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor, + widget.todayTextStyle, + _locale, + _calendarTheme, + _themeData, + _agendaDateNotifier, + _minWidth, + isRTL, + _textScaleFactor, + _isMobilePlatform, + ), + size: Size(viewHeaderWidth, displayDateHighlightHeight), + ), + content: Container( + padding: EdgeInsets.fromLTRB( + isRTL ? 0 : viewHeaderWidth, + 0, + isRTL ? viewHeaderWidth : 0, + 0, + ), + child: CustomPaint( + painter: _ScheduleLabelPainter( + currentDisplayDate, + null, + widget.scheduleViewSettings, + false, + isRTL, + _locale, + _useMobilePlatformUI, + _agendaViewNotifier, + _calendarTheme, + _themeData, + _localizations, + _textScaleFactor, + isDisplayDate: true, + ), + size: Size( + _minWidth - viewHeaderWidth, + displayDateHighlightHeight, + ), + ), + ), + ), onTapUp: (TapUpDetails details) { _removeDatePicker(); + if (widget.allowViewNavigation && + ((!_isRTL && details.localPosition.dx < viewHeaderWidth) || + (_isRTL && + details.localPosition.dx > + _minWidth - viewHeaderWidth))) { + _controller.view = CalendarView.day; + _controller.displayDate = currentDisplayDate; + } + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.onTap)) { + widget.onTap, + )) { return; } - CalendarViewHelper.raiseCalendarTapCallback( - widget, - DateTime(startDate.year, startDate.month, startDate.day), - null, - isMonthLabel - ? CalendarElement.header - : CalendarElement.viewHeader, - null); + _raiseCallbackForScheduleView( + currentDisplayDate, + details.localPosition, + [], + viewHeaderWidth, + padding, + true, + isDisplayDate: true, + ); }, onLongPressStart: (LongPressStartDetails details) { _removeDatePicker(); + if (widget.allowViewNavigation && + ((!_isRTL && details.localPosition.dx < viewHeaderWidth) || + (_isRTL && + details.localPosition.dx > + _minWidth - viewHeaderWidth))) { + _controller.view = CalendarView.day; + _controller.displayDate = currentDisplayDate; + } + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress)) { + widget.onLongPress, + )) { return; } - CalendarViewHelper.raiseCalendarLongPressCallback( - widget, - DateTime(startDate.year, startDate.month, startDate.day), - null, - isMonthLabel - ? CalendarElement.header - : CalendarElement.viewHeader, - null); - }); - } - - Widget _getDisplayDateView( - bool isRTL, - DateTime currentDisplayDate, - double highlightViewStartPosition, - double viewHeaderWidth, - double displayDateHighlightHeight, - double padding) { - return MouseRegion( - onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, true, isRTL, currentDisplayDate, - highlightViewStartPosition); - }, - onExit: _pointerExitEvent, - onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, true, isRTL, currentDisplayDate, - highlightViewStartPosition); + _raiseCallbackForScheduleView( + currentDisplayDate, + details.localPosition, + [], + viewHeaderWidth, + padding, + false, + isDisplayDate: true, + ); }, - child: GestureDetector( - child: _ScheduleAppointmentView( - header: CustomPaint( - painter: _AgendaDateTimePainter( - currentDisplayDate, - null, - widget.scheduleViewSettings, - widget.todayHighlightColor ?? - _calendarTheme.todayHighlightColor, - widget.todayTextStyle, - _locale, - _calendarTheme, - _agendaDateNotifier, - _minWidth, - isRTL, - _textScaleFactor, - _isMobilePlatform), - size: Size(viewHeaderWidth, displayDateHighlightHeight)), - content: Container( - padding: EdgeInsets.fromLTRB(isRTL ? 0 : viewHeaderWidth, 0, - isRTL ? viewHeaderWidth : 0, 0), - child: CustomPaint( - painter: _ScheduleLabelPainter( - currentDisplayDate, - null, - widget.scheduleViewSettings, - false, - isRTL, - _locale, - _useMobilePlatformUI, - _agendaViewNotifier, - _calendarTheme, - _localizations, - _textScaleFactor, - isDisplayDate: true), - size: Size(_minWidth - viewHeaderWidth, - displayDateHighlightHeight)), - )), - onTapUp: (TapUpDetails details) { - _removeDatePicker(); - if (widget.allowViewNavigation && - ((!_isRTL && details.localPosition.dx < viewHeaderWidth) || - (_isRTL && - details.localPosition.dx > - _minWidth - viewHeaderWidth))) { - _controller.view = CalendarView.day; - _controller.displayDate = currentDisplayDate; - } - - if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.onTap)) { - return; - } - - _raiseCallbackForScheduleView( - currentDisplayDate, - details.localPosition, - [], - viewHeaderWidth, - padding, - true, - isDisplayDate: true); - }, - onLongPressStart: (LongPressStartDetails details) { - _removeDatePicker(); - if (widget.allowViewNavigation && - ((!_isRTL && details.localPosition.dx < viewHeaderWidth) || - (_isRTL && - details.localPosition.dx > - _minWidth - viewHeaderWidth))) { - _controller.view = CalendarView.day; - _controller.displayDate = currentDisplayDate; - } - - if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress)) { - return; - } - - _raiseCallbackForScheduleView( - currentDisplayDate, - details.localPosition, - [], - viewHeaderWidth, - padding, - false, - isDisplayDate: true); - }, - )); + ), + ); } void _raiseCallbackForScheduleView( - DateTime currentDate, - Offset offset, - List appointments, - double viewHeaderWidth, - double padding, - bool isTapCallback, - {bool isDisplayDate = false}) { + DateTime currentDate, + Offset offset, + List appointments, + double viewHeaderWidth, + double padding, + bool isTapCallback, { + bool isDisplayDate = false, + }) { /// Check the touch position on day label if ((!_isRTL && viewHeaderWidth >= offset.dx) || (_isRTL && _minWidth - viewHeaderWidth < offset.dx)) { @@ -6466,50 +7237,62 @@ class _SfCalendarState extends State if (isTapCallback) { CalendarViewHelper.raiseCalendarTapCallback( - widget, - DateTime(currentDate.year, currentDate.month, currentDate.day), - widget.dataSource != null && - !AppointmentHelper.isCalendarAppointment(widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - currentAppointments, widget.dataSource) - : currentAppointments, - CalendarElement.viewHeader, - null); + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment(widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments( + currentAppointments, + widget.dataSource, + ) + : currentAppointments, + CalendarElement.viewHeader, + null, + ); } else { CalendarViewHelper.raiseCalendarLongPressCallback( - widget, - DateTime(currentDate.year, currentDate.month, currentDate.day), - widget.dataSource != null && - !AppointmentHelper.isCalendarAppointment(widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - currentAppointments, widget.dataSource) - : currentAppointments, - CalendarElement.viewHeader, - null); + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment(widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments( + currentAppointments, + widget.dataSource, + ) + : currentAppointments, + CalendarElement.viewHeader, + null, + ); } } else { /// Calculate the touch position appointment from its collection. double currentYPosition = padding; final double itemHeight = CalendarViewHelper.getScheduleAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); final double allDayItemHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); if (isDisplayDate) { if (isTapCallback) { CalendarViewHelper.raiseCalendarTapCallback( - widget, - DateTime(currentDate.year, currentDate.month, currentDate.day), - null, - CalendarElement.calendarCell, - null); + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + null, + CalendarElement.calendarCell, + null, + ); } else { CalendarViewHelper.raiseCalendarLongPressCallback( - widget, - DateTime(currentDate.year, currentDate.month, currentDate.day), - null, - CalendarElement.calendarCell, - null); + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + null, + CalendarElement.calendarCell, + null, + ); } return; @@ -6519,37 +7302,45 @@ class _SfCalendarState extends State final CalendarAppointment appointment = appointments[k]; final double currentAppointmentHeight = (_useMobilePlatformUI && _isAllDayAppointmentView(appointment) - ? allDayItemHeight - : itemHeight) + - padding; + ? allDayItemHeight + : itemHeight) + + padding; if (currentYPosition <= offset.dy && currentYPosition + currentAppointmentHeight > offset.dy) { final List selectedAppointment = [appointment]; if (isTapCallback) { CalendarViewHelper.raiseCalendarTapCallback( - widget, - DateTime(currentDate.year, currentDate.month, currentDate.day), - widget.dataSource != null && - !AppointmentHelper.isCalendarAppointment( - widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - selectedAppointment, widget.dataSource) - : selectedAppointment, - CalendarElement.appointment, - null); + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.dataSource!, + ) + ? CalendarViewHelper.getCustomAppointments( + selectedAppointment, + widget.dataSource, + ) + : selectedAppointment, + CalendarElement.appointment, + null, + ); } else { CalendarViewHelper.raiseCalendarLongPressCallback( - widget, - DateTime(currentDate.year, currentDate.month, currentDate.day), - widget.dataSource != null && - !AppointmentHelper.isCalendarAppointment( - widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - selectedAppointment, widget.dataSource) - : selectedAppointment, - CalendarElement.appointment, - null); + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.dataSource!, + ) + ? CalendarViewHelper.getCustomAppointments( + selectedAppointment, + widget.dataSource, + ) + : selectedAppointment, + CalendarElement.appointment, + null, + ); } break; } @@ -6562,7 +7353,7 @@ class _SfCalendarState extends State Widget addAgenda(double height, bool isRTL) { final bool hideEmptyAgendaDays = widget.scheduleViewSettings.hideEmptyScheduleWeek || - !_useMobilePlatformUI; + !_useMobilePlatformUI; /// return empty view when [hideEmptyAgendaDays] enabled and /// the appointments as empty. @@ -6571,7 +7362,8 @@ class _SfCalendarState extends State } final DateTime scheduleDisplayDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, _scheduleDisplayDate)); + getValidDate(widget.minDate, widget.maxDate, _scheduleDisplayDate), + ); final DateTime scheduleCurrentDate = DateTime.now(); final DateTime currentMaxDate = scheduleDisplayDate.isAfter(scheduleCurrentDate) @@ -6587,12 +7379,13 @@ class _SfCalendarState extends State /// in [ScheduleViewSettings] disabled else it return min /// start date of the appointment collection. _minDate = _getMinAppointmentDate( - _appointments, - widget.timeZone, - widget.minDate, - currentMinDate, - widget.scheduleViewSettings, - _useMobilePlatformUI); + _appointments, + widget.timeZone, + widget.minDate, + currentMinDate, + widget.scheduleViewSettings, + _useMobilePlatformUI, + ); /// Assign minimum date value to current minimum date when the minimum /// date is before of current minimum date @@ -6600,19 +7393,21 @@ class _SfCalendarState extends State _minDate = _minDate!.isBefore(widget.minDate) ? widget.minDate : _minDate; final DateTime viewMinDate = DateTimeHelper.getDateTimeValue( - addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek))); + 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] /// in [ScheduleViewSettings] disabled else it return max /// end date of the appointment collection. _maxDate = _getMaxAppointmentDate( - _appointments, - widget.timeZone, - widget.maxDate, - currentMaxDate, - widget.scheduleViewSettings, - _useMobilePlatformUI); + _appointments, + widget.timeZone, + widget.maxDate, + currentMaxDate, + widget.scheduleViewSettings, + _useMobilePlatformUI, + ); /// Assign maximum date value to current maximum date when the maximum /// date is before of current maximum date @@ -6621,14 +7416,19 @@ class _SfCalendarState extends State final double appointmentViewHeight = CalendarViewHelper.getScheduleAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); /// Get the view first date based on specified /// display date and first day of week. - int value = -(scheduleDisplayDate.weekday % DateTime.daysPerWeek) + + int value = + -(scheduleDisplayDate.weekday % DateTime.daysPerWeek) + widget.firstDayOfWeek - DateTime.daysPerWeek; if (value.abs() >= DateTime.daysPerWeek) { @@ -6638,10 +7438,12 @@ class _SfCalendarState extends State if (_previousDates.isEmpty) { /// Calculate the start date from display date if next view dates /// collection as empty. - DateTime date = _nextDates.isNotEmpty - ? _nextDates[0] - : DateTimeHelper.getDateTimeValue( - addDays(scheduleDisplayDate, value)); + DateTime date = + _nextDates.isNotEmpty + ? _nextDates[0] + : DateTimeHelper.getDateTimeValue( + addDays(scheduleDisplayDate, value), + ); int count = 0; /// Using while for calculate dates because if [hideEmptyAgendaDays] as @@ -6649,7 +7451,8 @@ class _SfCalendarState extends State while (count < 50) { for (int i = 1; i <= 100; i++) { final DateTime updatedDate = DateTimeHelper.getDateTimeValue( - addDays(date, -i * DateTime.daysPerWeek)); + addDays(date, -i * DateTime.daysPerWeek), + ); /// Skip week dates before min date if (!isSameOrAfterDate(viewMinDate, updatedDate)) { @@ -6657,18 +7460,29 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(updatedDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + 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) && + _appointments, + updatedDate, + weekEndDate, + widget.timeZone, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleDisplayDate) && + updatedDate, + weekEndDate, + scheduleDisplayDate, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleCurrentDate)) { + updatedDate, + weekEndDate, + scheduleCurrentDate, + )) { continue; } @@ -6703,8 +7517,9 @@ class _SfCalendarState extends State if (_nextDates.isEmpty) { /// Calculate the start date from display date - DateTime date = - DateTimeHelper.getDateTimeValue(addDays(scheduleDisplayDate, value)); + DateTime date = DateTimeHelper.getDateTimeValue( + addDays(scheduleDisplayDate, value), + ); int count = 0; /// Using while for calculate dates because if [hideEmptyAgendaDays] as @@ -6712,7 +7527,8 @@ class _SfCalendarState extends State while (count < 50) { for (int i = 0; i < 100; i++) { final DateTime updatedDate = DateTimeHelper.getDateTimeValue( - addDays(date, i * DateTime.daysPerWeek)); + addDays(date, i * DateTime.daysPerWeek), + ); /// Skip week date after max date if (!isSameOrBeforeDate(_maxDate, updatedDate)) { @@ -6720,18 +7536,29 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(updatedDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + 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) && + _appointments, + updatedDate, + weekEndDate, + widget.timeZone, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleDisplayDate) && + updatedDate, + weekEndDate, + scheduleDisplayDate, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleCurrentDate)) { + updatedDate, + weekEndDate, + scheduleCurrentDate, + )) { continue; } @@ -6769,33 +7596,37 @@ class _SfCalendarState extends State /// appointment fills the view port. DateTime viewStartDate = _nextDates[0]; DateTime viewEndDate = DateTimeHelper.getDateTimeValue( - addDays(_nextDates[_nextDates.length - 1], 6)); + addDays(_nextDates[_nextDates.length - 1], 6), + ); List appointmentCollection = AppointmentHelper.getVisibleAppointments( - viewStartDate, - isSameOrBeforeDate(_maxDate, viewEndDate) - ? viewEndDate - : _maxDate!, - _appointments, - widget.timeZone, - false); + viewStartDate, + isSameOrBeforeDate(_maxDate, viewEndDate) ? viewEndDate : _maxDate!, + _appointments, + widget.timeZone, + false, + ); const double padding = 5; Map> dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, viewStartDate, viewEndDate); + appointmentCollection, + viewStartDate, + viewEndDate, + ); List dateAppointmentKeys = dateAppointments.keys.toList(); double labelHeight = 0; if (_useMobilePlatformUI) { - DateTime previousDate = - DateTimeHelper.getDateTimeValue(addDays(viewStartDate, -1)); + DateTime previousDate = DateTimeHelper.getDateTimeValue( + 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; + padding; } previousDate = nextDate; @@ -6830,26 +7661,26 @@ class _SfCalendarState extends State isNewDatesAdded = true; viewStartDate = currentDate; - viewEndDate = - DateTimeHelper.getDateTimeValue(addDays(currentDate, 6)); + viewEndDate = DateTimeHelper.getDateTimeValue( + 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); + 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; + padding; } labelHeight += @@ -6857,7 +7688,10 @@ class _SfCalendarState extends State } dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, viewStartDate, viewEndDate); + appointmentCollection, + viewStartDate, + viewEndDate, + ); dateAppointmentKeys = dateAppointments.keys.toList(); for (int i = 0; i < dateAppointmentKeys.length; i++) { final List currentDateAppointment = @@ -6870,7 +7704,8 @@ class _SfCalendarState extends State } } - totalHeight = ((numberOfEvents + 1) * padding) + + totalHeight = + ((numberOfEvents + 1) * padding) + ((numberOfEvents - allDayCount) * appointmentViewHeight) + (allDayCount * allDayAppointmentHeight) + labelHeight; @@ -6880,7 +7715,8 @@ class _SfCalendarState extends State /// dates at initial position. if (_nextDates.isNotEmpty && isNewDatesAdded) { _headerUpdateNotifier.value = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, _nextDates[0])); + getValidDate(widget.minDate, widget.maxDate, _nextDates[0]), + ); } } @@ -6893,175 +7729,201 @@ class _SfCalendarState extends State _agendaScrollController!.initialScrollOffset == 0 && !_agendaScrollController!.hasClients) { final DateTime viewStartDate = _nextDates[0]; - final DateTime viewEndDate = - DateTimeHelper.getDateTimeValue(addDays(viewStartDate, 6)); + final DateTime viewEndDate = DateTimeHelper.getDateTimeValue( + addDays(viewStartDate, 6), + ); if (viewStartDate.isBefore(scheduleDisplayDate) && !isSameDate(viewStartDate, scheduleDisplayDate) && isSameOrBeforeDate(viewEndDate, scheduleDisplayDate)) { - final DateTime viewEndDate = - DateTimeHelper.getDateTimeValue(addDays(scheduleDisplayDate, -1)); + final DateTime viewEndDate = DateTimeHelper.getDateTimeValue( + addDays(scheduleDisplayDate, -1), + ); final double initialScrollPosition = _getInitialScrollPosition( - viewStartDate, - viewEndDate, - scheduleCurrentDate, - appointmentViewHeight, - allDayAppointmentHeight); + viewStartDate, + viewEndDate, + scheduleCurrentDate, + appointmentViewHeight, + allDayAppointmentHeight, + ); if (initialScrollPosition != 0) { _agendaScrollController?.removeListener(_handleScheduleViewScrolled); - _agendaScrollController = - ScrollController(initialScrollOffset: initialScrollPosition) - ..addListener(_handleScheduleViewScrolled); + _agendaScrollController = ScrollController( + initialScrollOffset: initialScrollPosition, + )..addListener(_handleScheduleViewScrolled); } } else if (viewStartDate.isBefore(scheduleDisplayDate)) { DateTime visibleStartDate = viewStartDate; double initialScrollPosition = 0; while (visibleStartDate.isBefore(scheduleDisplayDate) && !isSameDate(visibleStartDate, scheduleDisplayDate)) { - final DateTime viewEndDate = - DateTimeHelper.getDateTimeValue(addDays(visibleStartDate, 6)); + final DateTime viewEndDate = DateTimeHelper.getDateTimeValue( + addDays(visibleStartDate, 6), + ); final DateTime appStartDate = isSameOrAfterDate(_minDate, visibleStartDate) ? visibleStartDate : _minDate!; - DateTime appEndDate = isSameOrBeforeDate(_maxDate, viewEndDate) - ? viewEndDate - : _maxDate!; + DateTime appEndDate = + isSameOrBeforeDate(_maxDate, viewEndDate) + ? viewEndDate + : _maxDate!; if (appEndDate.isAfter(scheduleDisplayDate) || isSameDate(appEndDate, scheduleDisplayDate)) { appEndDate = DateTimeHelper.getDateTimeValue( - addDays(scheduleDisplayDate, -1)); + addDays(scheduleDisplayDate, -1), + ); } initialScrollPosition += _getInitialScrollPosition( - appStartDate, - appEndDate, - scheduleCurrentDate, - appointmentViewHeight, - allDayAppointmentHeight); + appStartDate, + appEndDate, + scheduleCurrentDate, + appointmentViewHeight, + allDayAppointmentHeight, + ); visibleStartDate = DateTimeHelper.getDateTimeValue( - addDays(visibleStartDate, DateTime.daysPerWeek)); + addDays(visibleStartDate, DateTime.daysPerWeek), + ); } if (initialScrollPosition != 0) { _agendaScrollController?.removeListener(_handleScheduleViewScrolled); - _agendaScrollController = - ScrollController(initialScrollOffset: initialScrollPosition) - ..addListener(_handleScheduleViewScrolled); + _agendaScrollController = ScrollController( + initialScrollOffset: initialScrollPosition, + )..addListener(_handleScheduleViewScrolled); } } } - return RawKeyboardListener( + return KeyboardListener( focusNode: _focusNode, - onKey: _onKeyDown, - child: Stack(children: [ - Positioned( - top: 0, - right: 0, - left: 0, - height: widget.headerHeight, - child: GestureDetector( - child: Container( - color: widget.headerStyle.backgroundColor ?? + onKeyEvent: _onKeyDown, + child: Stack( + 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, - true, - widget.todayTextStyle, - widget.showWeekNumber, - widget.weekNumberStyle, - _timelineMonthWeekNumberNotifier, - widget.cellBorderColor, - widget.timeSlotViewSettings.numberOfDaysInView)), + _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, + widget.showTodayButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + true, + widget.todayTextStyle, + widget.showWeekNumber, + widget.weekNumberStyle, + _timelineMonthWeekNumberNotifier, + widget.cellBorderColor, + widget.timeSlotViewSettings.numberOfDaysInView, + ), + ), + ), ), - ), - Positioned( + Positioned( top: widget.headerHeight, left: 0, right: 0, height: height, child: _OpacityWidget( - opacity: _opacity, - child: CustomScrollView( - key: _scrollKey, - physics: const AlwaysScrollableScrollPhysics(), - controller: _agendaScrollController, - center: _scheduleViewKey, - slivers: [ - SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (_previousDates.length <= index) { - return null; - } + opacity: _opacity, + child: CustomScrollView( + key: _scrollKey, + physics: const AlwaysScrollableScrollPhysics(), + 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; - } + /// 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(), - ]), + return _getItem(context, index, isRTL); + }), + key: _scheduleViewKey, + ), + ], + ), + ), + ), + _addDatePicker(widget.headerHeight, isRTL), + _getCalendarViewPopup(), + ], + ), ); } double _getInitialScrollPosition( - DateTime viewStartDate, - DateTime viewEndDate, - DateTime scheduleCurrentDate, - double appointmentViewHeight, - double allDayAppointmentHeight) { + DateTime viewStartDate, + DateTime viewEndDate, + DateTime scheduleCurrentDate, + double appointmentViewHeight, + double allDayAppointmentHeight, + ) { double initialScrolledPosition = 0; /// Calculate the appointment between the week start date and /// previous date of display date to calculate the scrolling position. final List appointmentCollection = AppointmentHelper.getVisibleAppointments( - viewStartDate, viewEndDate, _appointments, widget.timeZone, false); + viewStartDate, + viewEndDate, + _appointments, + widget.timeZone, + false, + ); const double padding = 5; @@ -7079,7 +7941,10 @@ class _SfCalendarState extends State if (appointmentCollection.isNotEmpty) { final Map> dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, viewStartDate, viewEndDate); + appointmentCollection, + viewStartDate, + viewEndDate, + ); final List dateAppointmentKeys = dateAppointments.keys.toList(); double totalAppointmentHeight = 0; for (int i = 0; i < dateAppointmentKeys.length; i++) { @@ -7096,10 +7961,11 @@ class _SfCalendarState extends State double panelHeight = ((eventsCount - allDayEventCount) * appointmentViewHeight) + - (allDayEventCount * allDayAppointmentHeight); - panelHeight = panelHeight > appointmentViewHeight - ? panelHeight - : appointmentViewHeight; + (allDayEventCount * allDayAppointmentHeight); + panelHeight = + panelHeight > appointmentViewHeight + ? panelHeight + : appointmentViewHeight; /// event count + 1 denotes the appointment padding and end padding. totalAppointmentHeight += panelHeight + ((eventsCount + 1) * padding); @@ -7112,9 +7978,9 @@ class _SfCalendarState extends State } } - initialScrolledPosition = todayNewEventHeight + + initialScrolledPosition = + todayNewEventHeight + totalAppointmentHeight + - /// Add the divider height when it render on web. (!_useMobilePlatformUI ? dateAppointmentKeys.length : 0) + (!_useMobilePlatformUI @@ -7130,7 +7996,8 @@ class _SfCalendarState extends State } else if ((viewStartDate.month != _scheduleDisplayDate.month && _useMobilePlatformUI) || todayNewEventHeight != 0) { - initialScrolledPosition = (!_useMobilePlatformUI + initialScrolledPosition = + (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.weekHeaderSettings.height + padding) + @@ -7143,7 +8010,7 @@ class _SfCalendarState extends State Widget addAgendaWithLoadMore(double height, bool isRTL) { final bool hideEmptyAgendaDays = widget.scheduleViewSettings.hideEmptyScheduleWeek || - !_useMobilePlatformUI; + !_useMobilePlatformUI; /// return empty view when [hideEmptyAgendaDays] enabled and /// the appointments as empty. @@ -7152,7 +8019,8 @@ class _SfCalendarState extends State } final DateTime scheduleDisplayDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, _scheduleDisplayDate)); + getValidDate(widget.minDate, widget.maxDate, _scheduleDisplayDate), + ); final DateTime scheduleCurrentDate = DateTime.now(); _scheduleMinDate ??= scheduleDisplayDate; @@ -7165,18 +8033,24 @@ class _SfCalendarState extends State } final DateTime viewMinDate = DateTimeHelper.getDateTimeValue( - addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek))); + addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek)), + ); final double appointmentViewHeight = CalendarViewHelper.getScheduleAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); /// Get the view first date based on specified /// display date and first day of week. - int value = -(scheduleDisplayDate.weekday % DateTime.daysPerWeek) + + int value = + -(scheduleDisplayDate.weekday % DateTime.daysPerWeek) + widget.firstDayOfWeek - DateTime.daysPerWeek; if (value.abs() >= DateTime.daysPerWeek) { @@ -7187,12 +8061,14 @@ class _SfCalendarState extends State !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] - : DateTimeHelper.getDateTimeValue( - addDays(scheduleDisplayDate, value))); + DateTime date = + _previousDates.isNotEmpty + ? _previousDates[_previousDates.length - 1] + : (_nextDates.isNotEmpty + ? _nextDates[0] + : DateTimeHelper.getDateTimeValue( + addDays(scheduleDisplayDate, value), + )); int count = 0; /// Using while for calculate dates because if [hideEmptyAgendaDays] as @@ -7200,7 +8076,8 @@ class _SfCalendarState extends State while (count < 50) { for (int i = 1; i <= 100; i++) { final DateTime updatedDate = DateTimeHelper.getDateTimeValue( - addDays(date, -i * DateTime.daysPerWeek)); + addDays(date, -i * DateTime.daysPerWeek), + ); /// Skip week dates before min date if (!isSameOrAfterDate(viewMinDate, updatedDate)) { @@ -7208,18 +8085,29 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(updatedDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + 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) && + _appointments, + updatedDate, + weekEndDate, + widget.timeZone, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleDisplayDate) && + updatedDate, + weekEndDate, + scheduleDisplayDate, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleCurrentDate)) { + updatedDate, + weekEndDate, + scheduleCurrentDate, + )) { continue; } @@ -7252,16 +8140,26 @@ class _SfCalendarState extends State } } - final DateTime viewMaxDate = DateTimeHelper.getDateTimeValue(addDays( + final DateTime viewMaxDate = DateTimeHelper.getDateTimeValue( + addDays( _maxDate, - (DateTime.daysPerWeek - _maxDate!.weekday) % DateTime.daysPerWeek)); + (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 - ? DateTimeHelper.getDateTimeValue(addDays(scheduleDisplayDate, value)) - : DateTimeHelper.getDateTimeValue( - addDays(_nextDates[_nextDates.length - 1], DateTime.daysPerWeek)); + DateTime date = + _nextDates.isEmpty + ? DateTimeHelper.getDateTimeValue( + addDays(scheduleDisplayDate, value), + ) + : DateTimeHelper.getDateTimeValue( + addDays( + _nextDates[_nextDates.length - 1], + DateTime.daysPerWeek, + ), + ); int count = 0; /// Using while for calculate dates because if [hideEmptyAgendaDays] as @@ -7269,7 +8167,8 @@ class _SfCalendarState extends State while (count < 50) { for (int i = 0; i < 100; i++) { final DateTime updatedDate = DateTimeHelper.getDateTimeValue( - addDays(date, i * DateTime.daysPerWeek)); + addDays(date, i * DateTime.daysPerWeek), + ); /// Skip week date after max date if (!isSameOrBeforeDate(_maxDate, updatedDate)) { @@ -7277,18 +8176,29 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - DateTimeHelper.getDateTimeValue(addDays(updatedDate, 6)); + final DateTime weekEndDate = DateTimeHelper.getDateTimeValue( + 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) && + _appointments, + updatedDate, + weekEndDate, + widget.timeZone, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleDisplayDate) && + updatedDate, + weekEndDate, + scheduleDisplayDate, + ) && !isDateWithInDateRange( - updatedDate, weekEndDate, scheduleCurrentDate)) { + updatedDate, + weekEndDate, + scheduleCurrentDate, + )) { continue; } @@ -7326,33 +8236,37 @@ class _SfCalendarState extends State /// appointment fills the view port. DateTime viewStartDate = _nextDates[0]; DateTime viewEndDate = DateTimeHelper.getDateTimeValue( - addDays(_nextDates[_nextDates.length - 1], 6)); + addDays(_nextDates[_nextDates.length - 1], 6), + ); List appointmentCollection = AppointmentHelper.getVisibleAppointments( - viewStartDate, - isSameOrBeforeDate(_maxDate, viewEndDate) - ? viewEndDate - : _maxDate!, - _appointments, - widget.timeZone, - false); + viewStartDate, + isSameOrBeforeDate(_maxDate, viewEndDate) ? viewEndDate : _maxDate!, + _appointments, + widget.timeZone, + false, + ); const double padding = 5; Map> dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, viewStartDate, viewEndDate); + appointmentCollection, + viewStartDate, + viewEndDate, + ); List dateAppointmentKeys = dateAppointments.keys.toList(); double labelHeight = 0; if (_useMobilePlatformUI) { - DateTime previousDate = - DateTimeHelper.getDateTimeValue(addDays(viewStartDate, -1)); + DateTime previousDate = DateTimeHelper.getDateTimeValue( + 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; + padding; } previousDate = nextDate; @@ -7387,26 +8301,26 @@ class _SfCalendarState extends State isNewDatesAdded = true; viewStartDate = currentDate; - viewEndDate = - DateTimeHelper.getDateTimeValue(addDays(currentDate, 6)); + viewEndDate = DateTimeHelper.getDateTimeValue( + 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); + 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; + padding; } labelHeight += @@ -7414,7 +8328,10 @@ class _SfCalendarState extends State } dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, viewStartDate, viewEndDate); + appointmentCollection, + viewStartDate, + viewEndDate, + ); dateAppointmentKeys = dateAppointments.keys.toList(); for (int i = 0; i < dateAppointmentKeys.length; i++) { final List currentDateAppointment = @@ -7427,7 +8344,8 @@ class _SfCalendarState extends State } } - totalHeight = ((numberOfEvents + 1) * padding) + + totalHeight = + ((numberOfEvents + 1) * padding) + ((numberOfEvents - allDayCount) * appointmentViewHeight) + (allDayCount * allDayAppointmentHeight) + labelHeight; @@ -7438,26 +8356,34 @@ class _SfCalendarState extends State if (_nextDates.isNotEmpty && isNewDatesAdded) { final DateTime date = _nextDates[0]; _headerUpdateNotifier.value = DateTimeHelper.getDateTimeValue( - getValidDate(_minDate, _maxDate, 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); + 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 && + final bool isMaxDisplayDate = isSameDate( + _scheduleMaxDate, + scheduleDisplayDate, + ); + final bool isInitialLoadMore = + isMinDisplayDate && isMaxDisplayDate && widget.loadMoreWidgetBuilder != null; - DateTime visibleMinDate = - AppointmentHelper.getMonthStartDate(scheduleDisplayDate); - DateTime visibleMaxDate = - AppointmentHelper.getMonthEndDate(scheduleDisplayDate); + DateTime visibleMinDate = AppointmentHelper.getMonthStartDate( + scheduleDisplayDate, + ); + DateTime visibleMaxDate = AppointmentHelper.getMonthEndDate( + scheduleDisplayDate, + ); if (!isSameOrBeforeDate(widget.maxDate, visibleMaxDate)) { visibleMaxDate = widget.maxDate; @@ -7483,10 +8409,14 @@ class _SfCalendarState extends State const double padding = 5; final double appointmentViewHeight = CalendarViewHelper.getScheduleAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - null, widget.scheduleViewSettings); + null, + widget.scheduleViewSettings, + ); /// Calculate the day label(May, 25) height based on appointment height /// and assign the label maximum height as 60. @@ -7501,9 +8431,10 @@ class _SfCalendarState extends State final double dividerHeight = _useMobilePlatformUI ? 0 : 1; /// Holds the height of 'No Events' label view. - final double displayEventHeight = _useMobilePlatformUI - ? appointmentViewHeaderHeight - : appointmentViewHeaderHeight + dividerHeight; + 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. @@ -7517,53 +8448,74 @@ class _SfCalendarState extends State /// loading. while (isSameOrBeforeDate(_maxDate, viewStartDate)) { final DateTime viewEndDate = DateTimeHelper.getDateTimeValue( - addDays(viewStartDate, DateTime.daysPerWeek - 1)); - final DateTime appStartDate = isSameOrAfterDate(_minDate, viewStartDate) - ? viewStartDate - : _minDate!; + 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; + double todayNewEventHeight = + isDateWithInDateRange( + viewStartDate, + viewEndDate, + scheduleCurrentDate, + ) + ? displayEventHeight + : 0; /// Display date view height. - double displayNewEventHeight = isDateWithInDateRange( - viewStartDate, viewEndDate, scheduleDisplayDate) - ? displayEventHeight - : 0; + 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); + appStartDate, + appEndDate, + _appointments, + widget.timeZone, + false, + canCreateNewAppointment: false, + ); /// Check the week date needs month header or not. - final bool isNeedMonthBuilder = _useMobilePlatformUI && + final bool isNeedMonthBuilder = + _useMobilePlatformUI && ((viewStartDate.month != appEndDate.month || viewStartDate.year != appEndDate.year) || viewStartDate.day == 1); /// Web view does not have month label. - double currentWeekHeight = isNeedMonthBuilder - ? widget.scheduleViewSettings.monthHeaderSettings.height - : 0; + 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; + 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); + appointmentCollection, + appStartDate, + appEndDate, + ); final List dateAppointmentKeys = dateAppointments.keys.toList(); @@ -7574,7 +8526,7 @@ class _SfCalendarState extends State /// 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 = + final List currentDateAppointment = dateAppointments[currentDateKey]!; /// Assign today no event label height as 0 when today date have @@ -7591,20 +8543,21 @@ class _SfCalendarState extends State displayNewEventHeight = 0; } - final int eventsCount = _currentDateAppointment.length; + final int eventsCount = currentDateAppointment.length; int allDayEventCount = 0; /// Web view does not differentiate all day and normal appointment. if (_useMobilePlatformUI) { - allDayEventCount = _getAllDayCount(_currentDateAppointment); + allDayEventCount = _getAllDayCount(currentDateAppointment); } double panelHeight = ((eventsCount - allDayEventCount) * appointmentViewHeight) + - (allDayEventCount * allDayAppointmentHeight); - panelHeight = panelHeight > appointmentViewHeight - ? panelHeight - : appointmentViewHeight; + (allDayEventCount * allDayAppointmentHeight); + panelHeight = + panelHeight > appointmentViewHeight + ? panelHeight + : appointmentViewHeight; appointmentHeight += panelHeight + dividerHeight; numberOfEvents += eventsCount; } @@ -7629,7 +8582,8 @@ class _SfCalendarState extends State totalHeight += currentWeekHeight; heights.add(currentWeekHeight); viewStartDate = DateTimeHelper.getDateTimeValue( - addDays(viewStartDate, DateTime.daysPerWeek)); + addDays(viewStartDate, DateTime.daysPerWeek), + ); } /// Get the current display date week index from next dates collection. @@ -7637,9 +8591,13 @@ class _SfCalendarState extends State for (int i = 0; i < _nextDates.length; i++) { final DateTime visibleStartDate = _nextDates[i]; final DateTime visibleEndDate = DateTimeHelper.getDateTimeValue( - addDays(visibleStartDate, DateTime.daysPerWeek)); + addDays(visibleStartDate, DateTime.daysPerWeek), + ); if (!isDateWithInDateRange( - visibleStartDate, visibleEndDate, scheduleDisplayDate)) { + visibleStartDate, + visibleEndDate, + scheduleDisplayDate, + )) { continue; } @@ -7658,33 +8616,45 @@ class _SfCalendarState extends State /// Calculate the scroll position with current display date week. while (viewStartDate.isBefore(scheduleDisplayDate) && !isSameDate(viewStartDate, scheduleDisplayDate)) { - final DateTime viewEndDate = - DateTimeHelper.getDateTimeValue(addDays(viewStartDate, 6)); - final DateTime appStartDate = isSameOrAfterDate(_minDate, viewStartDate) - ? viewStartDate - : _minDate!; + final DateTime viewEndDate = DateTimeHelper.getDateTimeValue( + 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 = - DateTimeHelper.getDateTimeValue(addDays(scheduleDisplayDate, -1)); + appEndDate = DateTimeHelper.getDateTimeValue( + addDays(scheduleDisplayDate, -1), + ); } /// Today date view height. double todayNewEventHeight = !isSameDate(scheduleCurrentDate, scheduleDisplayDate) && isDateWithInDateRange( - appStartDate, appEndDate, scheduleCurrentDate) + appStartDate, + appEndDate, + scheduleCurrentDate, + ) ? displayEventHeight : 0; final List appointmentCollection = AppointmentHelper.getVisibleAppointments( - appStartDate, appEndDate, _appointments, widget.timeZone, false, - canCreateNewAppointment: false); + appStartDate, + appEndDate, + _appointments, + widget.timeZone, + false, + canCreateNewAppointment: false, + ); /// Check the week date needs month header or not. - final bool isNeedMonthBuilder = _useMobilePlatformUI || + final bool isNeedMonthBuilder = + _useMobilePlatformUI && ((viewStartDate.month != appEndDate.month || viewStartDate.year != appEndDate.year) || viewStartDate.day == 1); @@ -7693,20 +8663,26 @@ class _SfCalendarState extends State /// Get the collection of appointment collection listed by date. final Map> dateAppointments = _getAppointmentCollectionOnDateBasis( - appointmentCollection, appStartDate, appEndDate); + 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; + initialScrolledPosition += + _useMobilePlatformUI + ? widget.scheduleViewSettings.weekHeaderSettings.height + : 0; /// Web view does not have month label. - initialScrolledPosition += isNeedMonthBuilder - ? widget.scheduleViewSettings.monthHeaderSettings.height - : 0; + initialScrolledPosition += + isNeedMonthBuilder + ? widget.scheduleViewSettings.monthHeaderSettings.height + + padding + : 0; int numberOfEvents = 0; @@ -7715,26 +8691,27 @@ class _SfCalendarState extends State /// 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 = + final List currentDateAppointment = dateAppointments[currentDateKey]!; if (isSameDate(scheduleCurrentDate, currentDateKey)) { todayNewEventHeight = 0; } - final int eventsCount = _currentDateAppointment.length; + final int eventsCount = currentDateAppointment.length; int allDayEventCount = 0; /// Web view does not differentiate all day and normal appointment. if (_useMobilePlatformUI) { - allDayEventCount = _getAllDayCount(_currentDateAppointment); + allDayEventCount = _getAllDayCount(currentDateAppointment); } double panelHeight = ((eventsCount - allDayEventCount) * appointmentViewHeight) + - (allDayEventCount * allDayAppointmentHeight); - panelHeight = panelHeight > appointmentViewHeight - ? panelHeight - : appointmentViewHeight; + (allDayEventCount * allDayAppointmentHeight); + panelHeight = + panelHeight > appointmentViewHeight + ? panelHeight + : appointmentViewHeight; appointmentHeight += panelHeight + dividerHeight; numberOfEvents += eventsCount; } @@ -7749,14 +8726,12 @@ class _SfCalendarState extends State appointmentHeight += (numberOfEvents + dateAppointmentKeys.length) * padding; - /// Add appointment height and week view end padding to scroll - /// position. - initialScrolledPosition += - appointmentHeight + (_useMobilePlatformUI ? padding : 0); - - initialScrolledPosition += todayNewEventHeight; + /// Add appointment height and today new event view height to + /// scroll position. + initialScrolledPosition += appointmentHeight + todayNewEventHeight; } else if (isNeedMonthBuilder || todayNewEventHeight != 0) { - initialScrolledPosition += (!_useMobilePlatformUI + initialScrolledPosition += + (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.weekHeaderSettings.height + padding) + @@ -7764,7 +8739,8 @@ class _SfCalendarState extends State } viewStartDate = DateTimeHelper.getDateTimeValue( - addDays(viewStartDate, DateTime.daysPerWeek)); + addDays(viewStartDate, DateTime.daysPerWeek), + ); } if (initialScrolledPosition != 0) { @@ -7779,9 +8755,9 @@ class _SfCalendarState extends State } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); - _agendaScrollController = - ScrollController(initialScrollOffset: initialScrolledPosition) - ..addListener(_handleScheduleViewScrolled); + _agendaScrollController = ScrollController( + initialScrollOffset: initialScrolledPosition, + )..addListener(_handleScheduleViewScrolled); _scrollKey = UniqueKey(); } } @@ -7808,186 +8784,211 @@ class _SfCalendarState extends State 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, - widget.todayTextStyle, - widget.showWeekNumber, - widget.weekNumberStyle, - _timelineMonthWeekNumberNotifier, - widget.cellBorderColor, - widget.timeSlotViewSettings.numberOfDaysInView)), + 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, + widget.showTodayButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + !_isScheduleStartLoadMore && !_isNeedLoadMore, + widget.todayTextStyle, + widget.showWeekNumber, + widget.weekNumberStyle, + _timelineMonthWeekNumberNotifier, + widget.cellBorderColor, + widget.timeSlotViewSettings.numberOfDaysInView, + ), + ), ), ), 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; - } + 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 (isSameDate(_scheduleMaxDate, date)) { - 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; + } - setState(() { - _isNeedLoadMore = true; - _scheduleMaxDate = date; - }); + /// 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 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, - ), - ], - )))), + + 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( + 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); - })))); + alignment: loadMoreAlignment, + color: Colors.transparent, + child: widget.loadMoreWidgetBuilder!(context, () async { + await loadMoreAppointments(visibleStartDate, visibleEndDate); + }), + ), + ), + ); } - return RawKeyboardListener( + return KeyboardListener( focusNode: _focusNode, - onKey: _onKeyDown, + onKeyEvent: _onKeyDown, child: Stack(children: children), ); } /// Method to handle keyboard navigation for schedule view in calendar. - void _onKeyDown(RawKeyEvent event) { - if (event.runtimeType != RawKeyDownEvent) { + void _onKeyDown(KeyEvent event) { + if (event.runtimeType != KeyDownEvent) { return; } CalendarViewHelper.handleViewSwitchKeyBoardEvent( - event, _controller, widget.allowedViews); + event, + _controller, + widget.allowedViews, + ); } Future loadMoreAppointments( - DateTime visibleStartDate, DateTime visibleEndDate) async { + DateTime visibleStartDate, + DateTime visibleEndDate, + ) async { if (_isLoadMoreLoaded) { return; } @@ -8020,19 +9021,19 @@ class _SfCalendarState extends State const 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 Color headerTextColor = + _calendarTheme.headerTextStyle!.color ?? Colors.black87; final TextStyle style = TextStyle(color: headerTextColor, fontSize: 12); int selectedIndex = -1; final Color? todayColor = CalendarViewHelper.getTodayHighlightTextColor( - widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor, - widget.todayTextStyle, - _calendarTheme); + widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor, + widget.todayTextStyle, + _calendarTheme, + ); - final Map calendarViews = - _getCalendarViewsText(_localizations); + final Map calendarViews = _getCalendarViewsText( + _localizations, + ); final Alignment alignment = _isRTL ? Alignment.centerRight : Alignment.centerLeft; @@ -8042,32 +9043,38 @@ class _SfCalendarState extends State for (int i = 0; i < allowedViewLength; i++) { final CalendarView view = widget.allowedViews![i]; final String text = calendarViews[view]!; - final double textWidth = _getTextWidgetWidth( - text, calendarViewTextHeight, _minWidth, context, - style: style) - .width; + final double textWidth = + _getTextWidgetWidth( + text, + calendarViewTextHeight, + _minWidth, + context, + style: style, + ).width; width = width < textWidth ? textWidth : width; final bool isSelected = view == _view; if (isSelected) { selectedIndex = i; } - children.add(InkWell( - onTap: () { - _viewChangeNotifier.value = false; - _controller.view = view; - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 0.0), - height: calendarViewTextHeight, - alignment: alignment, - child: Text( - text, - style: isSelected ? style.copyWith(color: todayColor) : style, - maxLines: 1, + children.add( + InkWell( + onTap: () { + _viewChangeNotifier.value = false; + _controller.view = view; + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + height: calendarViewTextHeight, + alignment: alignment, + child: Text( + text, + style: isSelected ? style.copyWith(color: todayColor) : style, + maxLines: 1, + ), ), ), - )); + ); } /// Restrict the pop up height with max height(200) @@ -8090,41 +9097,49 @@ class _SfCalendarState extends State arrowWidth = iconWidth; } - double? headerIconTextWidth = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle!.fontSize - : _calendarTheme.headerTextStyle!.fontSize; - headerIconTextWidth ??= 14; + final double headerIconTextWidth = + _calendarTheme.headerTextStyle!.fontSize ?? 14; final double totalArrowWidth = 2 * arrowWidth; - final bool isCenterAlignment = !_isMobilePlatform && + 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; + 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; + if (widget.showTodayButton) { + 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 - + double headerWidth = + _minWidth - totalArrowWidth - calendarViewWidth - todayWidth - @@ -8141,34 +9156,39 @@ class _SfCalendarState extends State Alignment popupAlignment; if (_isMobilePlatform) { /// icon width specifies the today button width and calendar view width. - left = _isRTL - ? totalArrowWidth - : headerWidth + todayWidth + iconWidth - 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; + 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; + left = + _isRTL + ? arrowWidth + : headerWidth + arrowWidth + todayWidth + iconWidth - width; } } else { - left = _isRTL - ? calendarViewWidth - width - : headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1; + 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; + 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; @@ -8176,20 +9196,22 @@ class _SfCalendarState extends State /// 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 - + final double leftStartPosition = + (_minWidth - totalArrowWidth - calendarViewWidth - dividerWidth - todayWidth - headerWidth) / 2; - left = _isRTL - ? leftStartPosition + calendarViewWidth - width - : leftStartPosition + - totalArrowWidth + - headerWidth + - todayWidth + - dividerWidth; + left = + _isRTL + ? leftStartPosition + calendarViewWidth - width + : leftStartPosition + + totalArrowWidth + + headerWidth + + todayWidth + + dividerWidth; } } @@ -8204,47 +9226,58 @@ class _SfCalendarState extends State scrollPosition = selectedIndex * calendarViewTextHeight; final double maxScrollPosition = allowedViewLength * calendarViewTextHeight; - scrollPosition = (maxScrollPosition - scrollPosition) > height - ? scrollPosition - : maxScrollPosition - height; + scrollPosition = + (maxScrollPosition - scrollPosition) > height + ? scrollPosition + : maxScrollPosition - height; } - final bool _showScrollbar = totalHeight > height; - final ScrollController _calendarViewPopupScrollController = - ScrollController(initialScrollOffset: scrollPosition); + final bool showScrollbar = totalHeight > height; + final ScrollController calendarViewPopupScrollController = ScrollController( + initialScrollOffset: scrollPosition, + ); return Positioned( - top: widget.headerHeight, - left: left, - height: height, - width: width, - child: _PopupWidget( - alignment: popupAlignment, - child: Container( + top: widget.headerHeight, + left: left, + height: height, + width: width, + child: _PopupWidget( + alignment: popupAlignment, + child: Container( + padding: EdgeInsets.zero, + decoration: BoxDecoration( + color: + _themeData.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: kElevationToShadow[6], + borderRadius: BorderRadius.circular(2.0), + ), + child: Material( + type: MaterialType.transparency, + child: Scrollbar( + thumbVisibility: showScrollbar, + controller: calendarViewPopupScrollController, + child: ListView( padding: EdgeInsets.zero, - 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: Scrollbar( - isAlwaysShown: _showScrollbar, - controller: _calendarViewPopupScrollController, - child: ListView( - padding: EdgeInsets.zero, - controller: _calendarViewPopupScrollController, - children: children)), - )))); + controller: calendarViewPopupScrollController, + 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) { + Widget _addResourcePanel( + bool isResourceEnabled, + double resourceViewSize, + double height, + bool isRTL, + ) { if (!isResourceEnabled) { return Positioned( left: 0, @@ -8255,16 +9288,21 @@ class _SfCalendarState extends State ); } - final double viewHeaderHeight = - CalendarViewHelper.getViewHeaderHeight(widget.viewHeaderHeight, _view); + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.viewHeaderHeight, + _view, + ); final double timeLabelSize = CalendarViewHelper.getTimeLabelWidth( - widget.timeSlotViewSettings.timeRulerSize, _view); + widget.timeSlotViewSettings.timeRulerSize, + _view, + ); final double top = viewHeaderHeight + timeLabelSize; final double resourceItemHeight = CalendarViewHelper.getResourceItemHeight( - resourceViewSize, - height - top, - widget.resourceViewSettings, - _resourceCollection!.length); + resourceViewSize, + height - top, + widget.resourceViewSettings, + _resourceCollection!.length, + ); final double panelHeight = resourceItemHeight * _resourceCollection!.length; final Widget verticalDivider = VerticalDivider( @@ -8274,101 +9312,152 @@ class _SfCalendarState extends State ); return Positioned( - left: isRTL ? _minWidth - resourceViewSize : 0, - width: resourceViewSize, - top: 0, - bottom: 0, - child: Stack(children: [ + 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, + top: + _controller.view == CalendarView.timelineMonth + ? widget.headerHeight + : widget.headerHeight + viewHeaderHeight, + height: + _controller.view == CalendarView.timelineMonth + ? viewHeaderHeight + : timeLabelSize, 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: EdgeInsets.zero, - physics: const ClampingScrollPhysics(), - controller: _resourcePanelScrollController, - scrollDirection: Axis.vertical, - children: [ - ResourceViewWidget( - _resourceCollection, - widget.resourceViewSettings, - resourceItemHeight, - widget.cellBorderColor, - _calendarTheme, - _resourceImageNotifier, - isRTL, - _textScaleFactor, - _resourceHoverNotifier.value, - _imagePainterCollection, - resourceViewSize, - panelHeight, - widget.resourceViewHeaderBuilder), - ]), - onTapUp: (TapUpDetails details) { - _handleOnTapForResourcePanel(details, resourceItemHeight); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForResourcePanel( - details, resourceItemHeight); - }, - ))) - ])); + 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: ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: ListView( + padding: EdgeInsets.zero, + physics: const ClampingScrollPhysics(), + controller: _resourcePanelScrollController, + children: [ + ResourceViewWidget( + _resourceCollection, + widget.resourceViewSettings, + resourceItemHeight, + widget.cellBorderColor, + _calendarTheme, + _themeData, + _resourceImageNotifier, + isRTL, + _textScaleFactor, + _resourceHoverNotifier.value, + _imagePainterCollection, + resourceViewSize, + panelHeight, + widget.resourceViewHeaderBuilder, + ), + ], + ), + ), + 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) { + LongPressStartDetails details, + double resourceItemHeight, + ) { if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress)) { + widget.onLongPress, + )) { return; } - final CalendarResource tappedResource = - _getTappedResource(details.localPosition.dy, resourceItemHeight); - final List resourceAppointments = - _getSelectedResourceAppointments(tappedResource); - CalendarViewHelper.raiseCalendarLongPressCallback(widget, null, - resourceAppointments, CalendarElement.resourceHeader, tappedResource); + 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) { + 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); + 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 @@ -8385,7 +9474,8 @@ class _SfCalendarState extends State app.resourceIds!.isNotEmpty && app.resourceIds!.contains(resource.id)) { selectedResourceAppointments.add( - CalendarViewHelper.getAppointmentDetail(app, widget.dataSource)); + CalendarViewHelper.getAppointmentDetail(app, widget.dataSource), + ); } } @@ -8394,66 +9484,78 @@ class _SfCalendarState extends State /// Returns the tapped resource details, based on the tapped position. CalendarResource _getTappedResource( - double tappedPosition, double resourceItemHeight) { + double tappedPosition, + double resourceItemHeight, + ) { final int index = (_resourcePanelScrollController!.offset + tappedPosition) ~/ - resourceItemHeight; + 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) { + 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, - _timelineMonthWeekNumberNotifier, - _updateCalendarState, - _getCalendarStateDetails, - key: _customScrollViewKey, - )), + opacity: _opacity, + child: CustomCalendarScrollView( + widget, + _view, + width - resourceViewSize, + height - agendaHeight, + _agendaSelectedDate, + isRTL, + _locale, + _calendarTheme, + _themeData, + _timeZoneLoaded ? widget.specialRegions : null, + _blackoutDates, + _controller, + _removeDatePicker, + _resourcePanelScrollController, + _resourceCollection, + _textScaleFactor, + _isMobilePlatform, + _fadeInController, + widget.minDate, + widget.maxDate, + _localizations, + _timelineMonthWeekNumberNotifier, + _updateCalendarState, + _getCalendarStateDetails, + key: _customScrollViewKey, + ), + ), ); } Widget _addChildren( - double agendaHeight, double height, double width, bool isRTL) { - final bool isResourceEnabled = - CalendarViewHelper.isResourceEnabled(widget.dataSource, _view); + 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 DateTime currentViewDate = + _currentViewVisibleDates[(_currentViewVisibleDates.length / 2) + .truncate()]; final List children = [ Positioned( @@ -8462,62 +9564,82 @@ class _SfCalendarState extends State 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, - widget.todayTextStyle, - widget.showWeekNumber, - widget.weekNumberStyle, - _timelineMonthWeekNumberNotifier, - widget.cellBorderColor, - widget.timeSlotViewSettings.numberOfDaysInView)), + 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, + widget.showTodayButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + !_isNeedLoadMore, + widget.todayTextStyle, + widget.showWeekNumber, + widget.weekNumberStyle, + _timelineMonthWeekNumberNotifier, + widget.cellBorderColor, + widget.timeSlotViewSettings.numberOfDaysInView, + ), + ), ), _addResourcePanel(isResourceEnabled, resourceViewSize, height, isRTL), - _addCustomScrollView(widget.headerHeight, resourceViewSize, isRTL, - isResourceEnabled, width, height, agendaHeight), - _addAgendaView(agendaHeight, widget.headerHeight + height - agendaHeight, - width, 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( + children.add( + Container( color: Colors.transparent, child: widget.loadMoreWidgetBuilder!(context, () async { - await loadMoreAppointments(_currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1]); - }))); + await loadMoreAppointments( + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + ); + }), + ), + ); } return Stack(children: children); } @@ -8554,13 +9676,14 @@ class _SfCalendarState extends State double pickerWidth = 0; double pickerHeight = 0; - final TextStyle datePickerStyle = - widget.monthViewSettings.monthCellStyle.textStyle ?? - _calendarTheme.activeDatesTextStyle!; + final TextStyle datePickerStyle = _calendarTheme.activeDatesTextStyle!; final Color? todayColor = widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor; final Color? todayTextColor = CalendarViewHelper.getTodayHighlightTextColor( - todayColor, widget.todayTextStyle, _calendarTheme); + todayColor, + widget.todayTextStyle, + _calendarTheme, + ); double left = 0; if (_isMobilePlatform) { pickerWidth = _minWidth; @@ -8585,12 +9708,14 @@ class _SfCalendarState extends State 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; + 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; @@ -8613,152 +9738,158 @@ class _SfCalendarState extends State left = (_minWidth - containerSize) / 2; } else { headerViewWidth = headerViewWidth > 200 ? 200 : headerViewWidth; - final double leftPadding = (_minWidth - + 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; + left = + _isRTL + ? leftPadding + + arrowWidth + + calendarViewWidth + + headerViewWidth - + containerSize + : leftPadding + arrowWidth + headerPadding; } } } return Positioned( - top: top, - left: left, - width: pickerWidth, - height: pickerHeight, - child: _PopupWidget( - child: Container( - margin: EdgeInsets.zero, - padding: const EdgeInsets.all(5), - decoration: _isMobilePlatform - ? BoxDecoration( - color: _calendarTheme.brightness == Brightness.dark + top: top, + left: left, + width: pickerWidth, + height: pickerHeight, + child: _PopupWidget( + child: Container( + margin: EdgeInsets.zero, + padding: const EdgeInsets.all(5), + decoration: + _isMobilePlatform + ? BoxDecoration( + color: + _themeData.brightness == Brightness.dark ? Colors.grey[850] : Colors.white, - boxShadow: const [ - 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 + boxShadow: const [ + BoxShadow( + offset: Offset(0.0, 3.0), + blurRadius: 2.0, + color: Color(0x24000000), + ), + ], + ) + : BoxDecoration( + color: + _themeData.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: todayTextColor, - //// For disabling the picker dates based on the calendar non working days. - selectableDayPredicate: _view != CalendarView.workWeek && - _view != CalendarView.timelineWorkWeek - ? null - : (DateTime dateTime) { - for (int i = 0; - i < - widget.timeSlotViewSettings.nonWorkingDays - .length; - i++) { - if (dateTime.weekday == - widget.timeSlotViewSettings.nonWorkingDays[i]) { - return false; - } - } - return true; - }, - headerStyle: DateRangePickerHeaderStyle( - textAlign: - _isMobilePlatform ? TextAlign.center : TextAlign.left, - ), - monthViewSettings: DateRangePickerMonthViewSettings( - viewHeaderHeight: pickerHeight / 8, - firstDayOfWeek: widget.firstDayOfWeek, + boxShadow: kElevationToShadow[6], + borderRadius: BorderRadius.circular(2.0), ), - monthCellStyle: DateRangePickerMonthCellStyle( - textStyle: datePickerStyle, - todayTextStyle: - datePickerStyle.copyWith(color: todayTextColor)), - yearCellStyle: DateRangePickerYearCellStyle( - textStyle: datePickerStyle, - todayTextStyle: - datePickerStyle.copyWith(color: todayTextColor), - 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; - } + child: SfDateRangePicker( + showNavigationArrow: true, + initialSelectedDate: _currentDate, + initialDisplayDate: _currentDate, + todayHighlightColor: todayColor, + minDate: widget.minDate, + maxDate: widget.maxDate, + selectionColor: todayTextColor, + //// For disabling the picker dates based on the calendar non working days. + selectableDayPredicate: + _view != CalendarView.workWeek && + _view != CalendarView.timelineWorkWeek + ? null + : (DateTime dateTime) { + for ( + int i = 0; + i < widget.timeSlotViewSettings.nonWorkingDays.length; + i++ + ) { + if (dateTime.weekday == + widget.timeSlotViewSettings.nonWorkingDays[i]) { + return false; + } + } + return true; + }, + headerStyle: DateRangePickerHeaderStyle( + textAlign: _isMobilePlatform ? TextAlign.center : TextAlign.left, + ), + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderHeight: pickerHeight / 8, + firstDayOfWeek: widget.firstDayOfWeek, + ), + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: datePickerStyle, + todayTextStyle: datePickerStyle.copyWith(color: todayTextColor), + ), + yearCellStyle: DateRangePickerYearCellStyle( + textStyle: datePickerStyle, + todayTextStyle: datePickerStyle.copyWith(color: todayTextColor), + 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; + } - if (isSameDate(_currentDate, _controller.displayDate) || - isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1], - _controller.displayDate)) { - _removeDatePicker(); - } + if (isSameDate(_currentDate, _controller.displayDate) || + isDateWithInDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - + 1], + _controller.displayDate, + )) { + _removeDatePicker(); + } - _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(); - } + _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(); + } - _showHeader = false; - _controller.displayDate = DateTime( - details.value.year, - details.value.month, - details.value.day, - _controller.displayDate!.hour, - _controller.displayDate!.minute, - _controller.displayDate!.second); - }, - )))); + _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) { @@ -8775,7 +9906,8 @@ class _SfCalendarState extends State if (details.currentDate != null && !isSameDate(details.currentDate, _currentDate)) { _currentDate = DateTimeHelper.getDateTimeValue( - getValidDate(widget.minDate, widget.maxDate, details.currentDate)); + getValidDate(widget.minDate, widget.maxDate, details.currentDate), + ); _canScrollTimeSlotView = false; _controller.displayDate = _currentDate; _canScrollTimeSlotView = true; @@ -8790,11 +9922,13 @@ class _SfCalendarState extends State _isNeedLoadMore = widget.loadMoreWidgetBuilder != null; _updateVisibleAppointments(); if (CalendarViewHelper.shouldRaiseViewChangedCallback( - widget.onViewChanged)) { + widget.onViewChanged, + )) { final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - widget.monthViewSettings.numberOfWeeksInView, - widget.monthViewSettings.showTrailingAndLeadingDates); + widget.monthViewSettings.numberOfWeeksInView, + widget.monthViewSettings.showTrailingAndLeadingDates, + ); List visibleDates = _currentViewVisibleDates; if (!showTrailingLeadingDates) { visibleDates = DateTimeHelper.getCurrentMonthDates(visibleDates); @@ -8805,7 +9939,9 @@ class _SfCalendarState extends State } if (!CalendarViewHelper.isSameTimeSlot( - details.selectedDate, _selectedDate)) { + details.selectedDate, + _selectedDate, + )) { _selectedDate = details.selectedDate; _controller.selectedDate = details.selectedDate; } @@ -8820,7 +9956,12 @@ class _SfCalendarState extends State } CalendarViewHelper.raiseCalendarTapCallback( - widget, _getTappedHeaderDate(), null, CalendarElement.header, null); + widget, + _getTappedHeaderDate(), + null, + CalendarElement.header, + null, + ); } //// Handles the on long press callback for header @@ -8828,20 +9969,26 @@ class _SfCalendarState extends State _calendarViewWidth = width; _updateDatePicker(); if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress)) { + widget.onLongPress, + )) { return; } CalendarViewHelper.raiseCalendarLongPressCallback( - widget, _getTappedHeaderDate(), null, CalendarElement.header, null); + widget, + _getTappedHeaderDate(), + null, + CalendarElement.header, + null, + ); } DateTime _getTappedHeaderDate() { if (_view == CalendarView.month) { - return DateTime(_currentDate.year, _currentDate.month, 01, 0, 0, 0); + return DateTime(_currentDate.year, _currentDate.month); } else { final DateTime date = _currentViewVisibleDates[0]; - return DateTime(date.year, date.month, date.day, 0, 0, 0); + return DateTime(date.year, date.month, date.day); } } @@ -8860,22 +10007,27 @@ class _SfCalendarState extends State return; } - final List selectedAppointments = - _getSelectedAppointments(details.localPosition, selectedDate); + final List selectedAppointments = _getSelectedAppointments( + details.localPosition, + selectedDate, + ); CalendarViewHelper.raiseCalendarTapCallback( - widget, - selectedDate, - selectedAppointments, - selectedAppointments.isNotEmpty - ? CalendarElement.appointment - : CalendarElement.agenda, - null); + widget, + selectedDate, + selectedAppointments, + selectedAppointments.isNotEmpty + ? CalendarElement.appointment + : CalendarElement.agenda, + null, + ); } //// Handles the onLongPress callback for agenda view. void _handleLongPressForAgenda( - LongPressStartDetails details, DateTime? selectedDate) { + LongPressStartDetails details, + DateTime? selectedDate, + ) { _removeDatePicker(); if (widget.allowViewNavigation && ((!_isRTL && details.localPosition.dx < _agendaDateViewWidth) || @@ -8886,25 +10038,31 @@ class _SfCalendarState extends State } if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.onLongPress)) { + widget.onLongPress, + )) { return; } - final List selectedAppointments = - _getSelectedAppointments(details.localPosition, selectedDate); + final List selectedAppointments = _getSelectedAppointments( + details.localPosition, + selectedDate, + ); CalendarViewHelper.raiseCalendarLongPressCallback( - widget, - selectedDate, - selectedAppointments, - selectedAppointments.isNotEmpty - ? CalendarElement.appointment - : CalendarElement.agenda, - null); + widget, + selectedDate, + selectedAppointments, + selectedAppointments.isNotEmpty + ? CalendarElement.appointment + : CalendarElement.agenda, + null, + ); } List _getSelectedAppointments( - Offset localPosition, DateTime? selectedDate) { + Offset localPosition, + DateTime? selectedDate, + ) { /// Return empty collection while tap the agenda view with no selected date. if (selectedDate == null) { return []; @@ -8918,7 +10076,10 @@ class _SfCalendarState extends State List agendaAppointments = AppointmentHelper.getSelectedDateAppointments( - _appointments, widget.timeZone, selectedDate); + _appointments, + widget.timeZone, + selectedDate, + ); /// Return empty collection while tap the agenda view does /// not have appointments. @@ -8927,16 +10088,23 @@ class _SfCalendarState extends State } agendaAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); agendaAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, + app2.isAllDay, + ), + ); agendaAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, + app2.isSpanned, + ), + ); int index = -1; //// Agenda appointment view top padding as 5. @@ -8946,15 +10114,20 @@ class _SfCalendarState extends State _agendaScrollController!.offset + localPosition.dy; final double actualAppointmentHeight = CalendarViewHelper.getScheduleAppointmentHeight( - widget.monthViewSettings, null); + widget.monthViewSettings, + null, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - widget.monthViewSettings, null); + widget.monthViewSettings, + null, + ); for (int i = 0; i < agendaAppointments.length; i++) { - final CalendarAppointment _appointment = agendaAppointments[i]; - final double appointmentHeight = _isAllDayAppointmentView(_appointment) - ? allDayAppointmentHeight - : actualAppointmentHeight; + final CalendarAppointment appointment = agendaAppointments[i]; + final double appointmentHeight = + _isAllDayAppointmentView(appointment) + ? allDayAppointmentHeight + : actualAppointmentHeight; if (tappedYPosition >= xPosition && tappedYPosition < xPosition + appointmentHeight + padding) { index = i; @@ -8974,7 +10147,9 @@ class _SfCalendarState extends State if (widget.dataSource != null && !AppointmentHelper.isCalendarAppointment(widget.dataSource!)) { return CalendarViewHelper.getCustomAppointments( - agendaAppointments, widget.dataSource); + agendaAppointments, + widget.dataSource, + ); } return agendaAppointments; @@ -8982,7 +10157,11 @@ class _SfCalendarState extends State // Returns the agenda view as a child for the calendar. Widget _addAgendaView( - double height, double startPosition, double width, bool isRTL) { + double height, + double startPosition, + double width, + bool isRTL, + ) { if (_view != CalendarView.month || !widget.monthViewSettings.showAgenda) { return Positioned( left: 0, @@ -8997,67 +10176,90 @@ class _SfCalendarState extends State /// disabled or black out date. DateTime? currentSelectedDate; if (_selectedDate != null) { - currentSelectedDate = isDateWithInDateRange( - widget.minDate, widget.maxDate, _selectedDate) && - !CalendarViewHelper.isDateInDateCollection( - _blackoutDates, _selectedDate!) - ? _selectedDate - : null; + currentSelectedDate = + isDateWithInDateRange( + widget.minDate, + widget.maxDate, + _selectedDate, + ) && + !CalendarViewHelper.isDateInDateCollection( + _blackoutDates, + _selectedDate!, + ) + ? _selectedDate + : null; } 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: AgendaViewLayout( - widget.monthViewSettings, - null, - currentSelectedDate, - null, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - 0, - _textScaleFactor, - _isMobilePlatform, - widget.appointmentBuilder, - width, - height, - widget), - onTapUp: (TapUpDetails details) { - _handleTapForAgenda(details, null); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleLongPressForAgenda(details, null); - }, - )))); + top: startPosition, + right: 0, + left: 0, + height: height, + child: _OpacityWidget( + opacity: _opacity, + child: Container( + color: + widget.monthViewSettings.agendaStyle.backgroundColor ?? + _calendarTheme.agendaBackgroundColor, + child: GestureDetector( + child: AgendaViewLayout( + widget.monthViewSettings, + null, + currentSelectedDate, + null, + isRTL, + _locale, + _localizations, + _calendarTheme, + _themeData, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + 0, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + width, + height, + widget.monthViewSettings.agendaStyle.placeholderTextStyle, + widget, + ), + onTapUp: (TapUpDetails details) { + _handleTapForAgenda(details, null); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleLongPressForAgenda(details, null); + }, + ), + ), + ), + ); } final List agendaAppointments = AppointmentHelper.getSelectedDateAppointments( - _appointments, widget.timeZone, currentSelectedDate); + _appointments, + widget.timeZone, + currentSelectedDate, + ); agendaAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - app1.actualStartTime.compareTo(app2.actualStartTime)); + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime), + ); agendaAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isAllDay, app2.isAllDay)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, + app2.isAllDay, + ), + ); agendaAppointments.sort( - (CalendarAppointment app1, CalendarAppointment app2) => - AppointmentHelper.orderAppointmentsAscending( - app1.isSpanned, app2.isSpanned)); + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, + app2.isSpanned, + ), + ); /// Each appointment have top padding and it used to show the space /// between two appointment views @@ -9068,94 +10270,114 @@ class _SfCalendarState extends State const double bottomPadding = 5; final double appointmentHeight = CalendarViewHelper.getScheduleAppointmentHeight( - widget.monthViewSettings, null); + widget.monthViewSettings, + null, + ); final double allDayAppointmentHeight = CalendarViewHelper.getScheduleAllDayAppointmentHeight( - widget.monthViewSettings, null); + widget.monthViewSettings, + null, + ); double painterHeight = height; if (agendaAppointments.isNotEmpty) { final int count = _getAllDayCount(agendaAppointments); - painterHeight = ((count * (allDayAppointmentHeight + topPadding)) + + painterHeight = + ((count * (allDayAppointmentHeight + topPadding)) + ((agendaAppointments.length - count) * (appointmentHeight + topPadding))) + bottomPadding; } 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: EdgeInsets.zero, - controller: _agendaScrollController, - children: [ - AgendaViewLayout( - widget.monthViewSettings, - null, - currentSelectedDate, - agendaAppointments, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - _agendaDateViewWidth, - _textScaleFactor, - _isMobilePlatform, - widget.appointmentBuilder, - width - _agendaDateViewWidth, - painterHeight, - widget), - ], - ), + 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, + _themeData, + _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: EdgeInsets.zero, + controller: _agendaScrollController, + children: [ + AgendaViewLayout( + widget.monthViewSettings, + null, + currentSelectedDate, + agendaAppointments, + isRTL, + _locale, + _localizations, + _calendarTheme, + _themeData, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + _agendaDateViewWidth, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + width - _agendaDateViewWidth, + painterHeight, + widget + .monthViewSettings + .agendaStyle + .placeholderTextStyle, + widget, ), - ]), - onTapUp: (TapUpDetails details) { - _handleTapForAgenda(details, _selectedDate); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleLongPressForAgenda(details, _selectedDate); - }, - ))))); + ], + ), + ), + ], + ), + onTapUp: (TapUpDetails details) { + _handleTapForAgenda(details, _selectedDate); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleLongPressForAgenda(details, _selectedDate); + }, + ), + ), + ), + ), + ); } } @@ -9206,8 +10428,10 @@ class _OpacityWidgetState extends State<_OpacityWidget> { /// Widget used to show the pop up animation to the child. class _PopupWidget extends StatefulWidget { - const _PopupWidget( - {required this.child, this.alignment = Alignment.topCenter}); + const _PopupWidget({ + required this.child, + this.alignment = Alignment.topCenter, + }); /// Widget that animated like popup. final Widget child; @@ -9230,9 +10454,13 @@ class _PopupWidgetState extends State<_PopupWidget> @override void initState() { _animationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 200)); - _animation = - CurvedAnimation(parent: _animationController, curve: Curves.easeInOut); + vsync: this, + duration: const Duration(milliseconds: 200), + ); + _animation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ); super.initState(); } @@ -9250,52 +10478,55 @@ class _PopupWidgetState extends State<_PopupWidget> /// Start the animation. _animationController.forward(); return ScaleTransition( - alignment: widget.alignment, - scale: _animation, - child: FadeTransition(opacity: _animation, child: widget.child)); + alignment: widget.alignment, + scale: _animation, + child: FadeTransition(opacity: _animation, child: widget.child), + ); } } @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, - this.todayTextStyle, - this.showWeekNumber, - this.weekNumberStyle, - this.timelineMonthWeekNumberNotifier, - this.cellBorderColor, - this.numberOfDaysInView); + 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.showTodayButton, + 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, + this.todayTextStyle, + this.showWeekNumber, + this.weekNumberStyle, + this.timelineMonthWeekNumberNotifier, + this.cellBorderColor, + this.numberOfDaysInView, + ); final List visibleDates; final TextStyle? todayTextStyle; @@ -9321,6 +10552,7 @@ class _CalendarHeaderView extends StatefulWidget { final _CalendarHeaderCallback headerTapCallback; final _CalendarHeaderCallback headerLongPressCallback; final bool showDatePickerButton; + final bool showTodayButton; final SfLocalizations localizations; final ValueNotifier valueChangeNotifier; final ValueNotifier viewChangeNotifier; @@ -9345,8 +10577,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { @override void initState() { widget.valueChangeNotifier.addListener(_updateHeaderChanged); - widget.timelineMonthWeekNumberNotifier - .addListener(_updateWeekNumberChangedForTimelineMonth); + widget.timelineMonthWeekNumberNotifier.addListener( + _updateWeekNumberChangedForTimelineMonth, + ); _calendarViews = _getCalendarViewsText(widget.localizations); super.initState(); } @@ -9360,10 +10593,12 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { if (widget.timelineMonthWeekNumberNotifier != oldWidget.timelineMonthWeekNumberNotifier) { - oldWidget.timelineMonthWeekNumberNotifier - .removeListener(_updateWeekNumberChangedForTimelineMonth); - widget.timelineMonthWeekNumberNotifier - .addListener(_updateWeekNumberChangedForTimelineMonth); + oldWidget.timelineMonthWeekNumberNotifier.removeListener( + _updateWeekNumberChangedForTimelineMonth, + ); + widget.timelineMonthWeekNumberNotifier.addListener( + _updateWeekNumberChangedForTimelineMonth, + ); } _calendarViews = _getCalendarViewsText(widget.localizations); @@ -9373,7 +10608,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { @override Widget build(BuildContext context) { final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( - widget.width, widget.isMobilePlatform); + widget.width, + widget.isMobilePlatform, + ); double arrowWidth = 0; double headerWidth = widget.width; @@ -9403,37 +10640,39 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { double weekNumberPanelWidth = 0, weekNumberPanelHeight = 0; double weekNumberTextWidth = 0; final List children = []; - Color? headerTextColor = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle!.color - : (widget.calendarTheme.headerTextStyle!.color); - final Color headerBackgroundColor = widget.headerStyle.backgroundColor ?? + final Color headerTextColor = + widget.calendarTheme.headerTextStyle!.color ?? Colors.black87; + final Color headerBackgroundColor = + widget.headerStyle.backgroundColor ?? widget.calendarTheme.headerBackgroundColor!; - headerTextColor ??= Colors.black87; - final Color arrowColor = - headerTextColor.withOpacity(headerTextColor.opacity * 0.6); + final Color arrowColor = headerTextColor.withValues( + alpha: headerTextColor.a * 0.6, + ); Color prevArrowColor = arrowColor; Color nextArrowColor = arrowColor; final TextStyle style = TextStyle(color: arrowColor); const double defaultCalendarViewTextSize = 12; Widget calendarViewIcon = const SizedBox(width: 0, height: 0); - const double padding = 5; - double? headerIconTextWidth = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle!.fontSize - : widget.calendarTheme.headerTextStyle!.fontSize; - headerIconTextWidth ??= 14; + const double padding = 8; + final double headerIconTextWidth = + widget.calendarTheme.headerTextStyle!.fontSize ?? 14; final String todayText = widget.localizations.todayLabel; double maxHeaderHeight = 0; const double allowedViewsPadding = 10; /// Today icon shown when the date picker enabled on calendar. - if (widget.showDatePickerButton) { + if (widget.showTodayButton) { todayIconWidth = iconWidth; if (!useMobilePlatformUI) { /// 5 as padding for around today text view. final Size todayButtonSize = _getTextWidgetWidth( - todayText, widget.height, widget.width - totalArrowWidth, context, - style: const TextStyle(fontSize: defaultCalendarViewTextSize)); + todayText, + widget.height, + widget.width - totalArrowWidth, + context, + style: const TextStyle(fontSize: defaultCalendarViewTextSize), + ); maxHeaderHeight = todayButtonSize.height + allowedViewsPadding; todayIconWidth = todayButtonSize.width + allowedViewsPadding; } @@ -9441,36 +10680,54 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { double headerTextWidth = 0; final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - final TextStyle weekNumberTextStyle = widget.weekNumberStyle.textStyle ?? + final TextStyle weekNumberTextStyle = widget.calendarTheme.weekNumberTextStyle!; final Color? weekNumberBackgroundColor = widget.weekNumberStyle.backgroundColor ?? - widget.calendarTheme.weekNumberBackgroundColor; + widget.calendarTheme.weekNumberBackgroundColor; - final bool weekNumberEnabled = widget.showWeekNumber && + final bool weekNumberEnabled = + widget.showWeekNumber && (widget.view == CalendarView.day || isTimelineView); if (!widget.isMobilePlatform || (widget.isMobilePlatform && weekNumberEnabled)) { final Size headerTextSize = _getTextWidgetWidth( - headerString, - widget.height, - widget.width - totalArrowWidth - todayIconWidth - padding, - context, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme.headerTextStyle); - headerTextWidth = headerTextSize.width + + headerString, + widget.height, + widget.width - totalArrowWidth - todayIconWidth - padding, + context, + style: widget.calendarTheme.headerTextStyle, + ); + + headerTextWidth = + headerTextSize.width + padding + (widget.showDatePickerButton ? headerIconTextWidth : 0); - maxHeaderHeight = maxHeaderHeight > headerTextSize.height - ? maxHeaderHeight - : headerTextSize.height; + maxHeaderHeight = + maxHeaderHeight > headerTextSize.height + ? maxHeaderHeight + : headerTextSize.height; + } + + if (weekNumberEnabled) { + final Size weekNumberPanelSize = _getTextWidgetWidth( + '${widget.localizations.weeknumberLabel}$weekNumberString ', + widget.height, + widget.width, + context, + style: weekNumberTextStyle, + ); + weekNumberTextWidth = weekNumberPanelSize.width + padding; + weekNumberPanelWidth = weekNumberTextWidth; + weekNumberPanelHeight = weekNumberPanelSize.height; } final Color? highlightColor = CalendarViewHelper.getTodayHighlightTextColor( - widget.todayHighlightColor ?? widget.calendarTheme.todayHighlightColor, - widget.todayTextStyle, - widget.calendarTheme); + widget.todayHighlightColor ?? widget.calendarTheme.todayHighlightColor, + widget.todayTextStyle, + widget.calendarTheme, + ); if (isNeedViewSwitchOption) { calendarViewWidth = iconWidth; @@ -9482,28 +10739,33 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { /// Render allowed views icon on mobile view. calendarViewIcon = _getCalendarViewWidget( - headerBackgroundColor, - useMobilePlatformUI, - false, - calendarViewWidth, - maxHeaderHeight, - style, - arrowColor, - headerTextColor, - widget.view, - // ignore: avoid_bool_literals_in_conditional_expressions - widget.isMobilePlatform ? false : widget.viewChangeNotifier.value, - highlightColor, - defaultCalendarViewTextSize, - semanticLabel: 'CalendarView'); + headerBackgroundColor, + useMobilePlatformUI, + false, + calendarViewWidth, + maxHeaderHeight, + style, + arrowColor, + headerTextColor, + widget.view, + // ignore: avoid_bool_literals_in_conditional_expressions + widget.isMobilePlatform ? false : widget.viewChangeNotifier.value, + highlightColor, + defaultCalendarViewTextSize, + semanticLabel: 'CalendarView', + ); } else { /// Assign divider width when today icon text shown. - dividerWidth = widget.showDatePickerButton ? 10 : 0; + dividerWidth = widget.showTodayButton ? 10 : 0; - double totalWidth = - widget.width - totalArrowWidth - dividerWidth - todayIconWidth; + final double totalWidth = + widget.width - + totalArrowWidth - + dividerWidth - + todayIconWidth - + headerTextWidth - + weekNumberPanelWidth; - totalWidth -= headerTextWidth; final Map calendarViewsWidth = {}; double allowedViewsWidth = 0; @@ -9515,8 +10777,12 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { for (int i = 0; i < allowedViewsLength; i++) { final CalendarView currentView = widget.allowedViews![i]; final Size calendarViewSize = _getTextWidgetWidth( - _calendarViews[currentView]!, widget.height, totalWidth, context, - style: const TextStyle(fontSize: defaultCalendarViewTextSize)); + _calendarViews[currentView]!, + widget.height, + totalWidth, + context, + style: const TextStyle(fontSize: defaultCalendarViewTextSize), + ); final double currentViewTextWidth = calendarViewSize.width + allowedViewsPadding; maxCalendarViewHeight = @@ -9531,14 +10797,16 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { /// render the allowed views as children. if (allowedViewsWidth < totalWidth) { calendarViewWidth = allowedViewsWidth; - maxHeaderHeight = maxCalendarViewHeight > maxHeaderHeight - ? maxCalendarViewHeight - : maxHeaderHeight; + 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( + children.add( + _getCalendarViewWidget( headerBackgroundColor, useMobilePlatformUI, false, @@ -9550,27 +10818,33 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { currentView, widget.view == currentView, highlightColor, - defaultCalendarViewTextSize)); + 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: const TextStyle(fontSize: defaultCalendarViewTextSize)); + _calendarViews[widget.view]!, + widget.height, + widget.width - totalArrowWidth, + context, + style: const TextStyle(fontSize: defaultCalendarViewTextSize), + ); maxCalendarViewHeight = calendarViewSize.height + allowedViewsPadding; - maxHeaderHeight = maxCalendarViewHeight > maxHeaderHeight - ? maxCalendarViewHeight - : maxHeaderHeight; + maxHeaderHeight = + maxCalendarViewHeight > maxHeaderHeight + ? maxCalendarViewHeight + : maxHeaderHeight; maxHeaderHeight = maxHeaderHeight > widget.height ? widget.height : maxHeaderHeight; - calendarViewWidth = calendarViewSize.width + + calendarViewWidth = + calendarViewSize.width + allowedViewsPadding + headerIconTextWidth; - children.add(_getCalendarViewWidget( + children.add( + _getCalendarViewWidget( headerBackgroundColor, useMobilePlatformUI, true, @@ -9583,7 +10857,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { widget.viewChangeNotifier.value, highlightColor, defaultCalendarViewTextSize, - semanticLabel: 'CalendarView')); + semanticLabel: 'CalendarView', + ), + ); } } } @@ -9593,65 +10869,66 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { ? maxHeaderHeight : widget.height; - headerWidth = widget.width - - calendarViewWidth - - todayIconWidth - - dividerWidth - - totalArrowWidth; - if (weekNumberEnabled) { - weekNumberPanelWidth = (headerWidth - headerTextWidth) + padding; - final double minimumWeekNumberWidth = headerWidth / 4; - if (weekNumberPanelWidth < minimumWeekNumberWidth) { - weekNumberPanelWidth = minimumWeekNumberWidth + padding; - headerWidth = headerTextWidth - weekNumberPanelWidth; - } else { - headerWidth = headerTextWidth; - } - final Size weekNumberPanelSize = _getTextWidgetWidth( - '${widget.localizations.weeknumberLabel}$weekNumberString ', - headerHeight, - weekNumberPanelWidth, - context, - style: weekNumberTextStyle); - weekNumberTextWidth = weekNumberPanelSize.width > weekNumberPanelWidth - ? weekNumberPanelWidth - : weekNumberPanelSize.width + padding; - weekNumberPanelHeight = weekNumberPanelSize.height; - final double occupiedWidth = widget.width - + /// Header will render based on its text width while week number enabled. + /// because the week number panel occupies the empty space to align the + /// today button and calendar view button on right side end. + headerWidth = headerTextWidth; + final double remainingWidth = + widget.width - calendarViewWidth - todayIconWidth - dividerWidth - totalArrowWidth - weekNumberPanelWidth - headerWidth; - if (occupiedWidth < 0) { - headerWidth += occupiedWidth; + if (remainingWidth < 0) { + /// Clip the header text while the calendar view button, today button, + /// arrows and week number does not have space to render. + headerWidth += remainingWidth; + } else { + /// Assign the week number panel width is available space other than + /// header text width, calendar view button width, today button, + /// arrows width to align the today button and calendar view button + /// on right side end. + weekNumberPanelWidth += remainingWidth; } + } else { + /// Assign the header panel width is available space other than + /// calendar view button width, today button, arrows width to align the + /// today button and calendar view button on right side end. + headerWidth = + widget.width - + calendarViewWidth - + todayIconWidth - + dividerWidth - + totalArrowWidth; } 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); + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + widget.maxDate, + dates, + widget.nonWorkingDays, + )) { + nextArrowColor = nextArrowColor.withValues(alpha: nextArrowColor.a * 0.5); } if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.numberOfWeeksInView, - widget.minDate, - widget.maxDate, - dates, - widget.nonWorkingDays)) { - prevArrowColor = prevArrowColor.withOpacity(prevArrowColor.opacity * 0.5); + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + widget.maxDate, + dates, + widget.nonWorkingDays, + )) { + prevArrowColor = prevArrowColor.withValues(alpha: prevArrowColor.a * 0.5); } - MainAxisAlignment _getAlignmentFromTextAlign() { + MainAxisAlignment getAlignmentFromTextAlign() { if (widget.headerStyle.textAlign == TextAlign.left || widget.headerStyle.textAlign == TextAlign.start) { return MainAxisAlignment.start; @@ -9667,12 +10944,13 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { headerHeight == widget.height ? headerHeight * 0.6 : headerHeight * 0.8; arrowSize = arrowSize > 25 ? 25 : arrowSize; arrowSize = arrowSize * widget.textScaleFactor; - final bool isCenterAlignment = !widget.isMobilePlatform && + final bool isCenterAlignment = + !widget.isMobilePlatform && (navigationArrowEnabled || isNeedViewSwitchOption) && (widget.headerStyle.textAlign == TextAlign.center || widget.headerStyle.textAlign == TextAlign.justify); - Alignment _getHeaderAlignment() { + Alignment getHeaderAlignment() { if (widget.headerStyle.textAlign == TextAlign.left || widget.headerStyle.textAlign == TextAlign.start) { return widget.isRTL ? Alignment.centerRight : Alignment.centerLeft; @@ -9688,16 +10966,16 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { !widget.showDatePickerButton || !widget.enableInteraction ? Colors.transparent : null; - final TextStyle headerTextStyle = - widget.headerStyle.textStyle ?? widget.calendarTheme.headerTextStyle!; - final Widget headerText = widget.isMobilePlatform - ? Container( - alignment: Alignment.center, - color: headerBackgroundColor, - width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( + final TextStyle headerTextStyle = widget.calendarTheme.headerTextStyle!; + final Widget headerText = + widget.isMobilePlatform + ? Container( + alignment: Alignment.center, + color: headerBackgroundColor, + width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( color: headerBackgroundColor, child: InkWell( //// set splash color as transparent when header does not have @@ -9711,62 +10989,83 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { return; } widget.headerTapCallback( - calendarViewWidth + dividerWidth + todayIconWidth); + calendarViewWidth + dividerWidth + todayIconWidth, + ); }, onLongPress: () { if (!widget.enableInteraction) { return; } widget.headerLongPressCallback( - calendarViewWidth + dividerWidth + todayIconWidth); + calendarViewWidth + dividerWidth + todayIconWidth, + ); }, child: Container( - width: isCenterAlignment && headerWidth > 200 - ? 200 - : headerWidth, - height: headerHeight, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Row( - mainAxisAlignment: _getAlignmentFromTextAlign(), - children: widget.showDatePickerButton + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration(color: Colors.transparent), + width: + isCenterAlignment && headerWidth > 200 + ? 200 + : headerWidth, + height: headerHeight, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Row( + mainAxisAlignment: getAlignmentFromTextAlign(), + children: + widget.showDatePickerButton ? [ - Flexible( - child: Text(headerString, - style: headerTextStyle, - maxLines: 1, - semanticsLabel: - // ignore: lines_longer_than_80_chars - '$headerString ${widget.isPickerShown ? 'hide date picker' : 'show date picker'}', - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)), - Icon( - widget.isPickerShown - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: headerTextStyle.fontSize ?? 14, + Flexible( + child: Text( + headerString, + style: headerTextStyle, + maxLines: 1, + semanticsLabel: + // ignore: lines_longer_than_80_chars + '$headerString ${widget.isPickerShown ? 'hide date picker' : 'show date picker'}', + overflow: TextOverflow.clip, + softWrap: false, + textDirection: + CalendarViewHelper.getTextDirectionBasedOnLocale( + widget.locale, + ), ), - ] + ), + Icon( + widget.isPickerShown + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: headerTextStyle.fontSize ?? 14, + ), + ] : [ - Flexible( - child: Text(headerString, - style: headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)) - ])), - )), - ) - : Container( - alignment: _getHeaderAlignment(), - color: headerBackgroundColor, - width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( + Flexible( + child: Text( + headerString, + style: headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: + CalendarViewHelper.getTextDirectionBasedOnLocale( + widget.locale, + ), + ), + ), + ], + ), + ), + ), + ), + ) + : Container( + alignment: getHeaderAlignment(), + color: headerBackgroundColor, + width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( color: headerBackgroundColor, child: InkWell( //// set splash color as transparent when header does not have @@ -9779,39 +11078,57 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { return; } widget.headerTapCallback( - calendarViewWidth + dividerWidth + todayIconWidth); + calendarViewWidth + dividerWidth + todayIconWidth, + ); }, onLongPress: () { if (!widget.enableInteraction) { return; } widget.headerLongPressCallback( - calendarViewWidth + dividerWidth + todayIconWidth); + calendarViewWidth + dividerWidth + todayIconWidth, + ); }, child: Container( - color: widget.showDatePickerButton && widget.isPickerShown - ? Colors.grey.withOpacity(0.3) - : headerBackgroundColor, - width: isCenterAlignment && headerTextWidth > 200 - ? 200 - : headerTextWidth, - height: headerHeight, - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 5), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: widget.showDatePickerButton - ? [ + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: + widget.showDatePickerButton && widget.isPickerShown + ? Colors.grey.withValues(alpha: 0.3) + : headerBackgroundColor, + ), + + /// Padding value is from parent container padding + /// value const EdgeInsets.all(2). + width: + (isCenterAlignment && headerTextWidth > 200 + ? 200 + : headerTextWidth) - + padding, + height: headerHeight, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: + widget.showDatePickerButton + ? [ Flexible( - child: Text(headerString, - style: headerTextStyle, - maxLines: 1, - semanticsLabel: - // ignore: lines_longer_than_80_chars - '$headerString ${widget.isPickerShown ? 'hide date picker' : 'show date picker'}', - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)), + child: Text( + headerString, + style: headerTextStyle, + maxLines: 1, + semanticsLabel: + // ignore: lines_longer_than_80_chars + '$headerString ${widget.isPickerShown ? 'hide date picker' : 'show date picker'}', + overflow: TextOverflow.clip, + softWrap: false, + textDirection: + CalendarViewHelper.getTextDirectionBasedOnLocale( + widget.locale, + ), + ), + ), Icon( widget.isPickerShown ? Icons.arrow_drop_up @@ -9820,70 +11137,86 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { size: headerTextStyle.fontSize ?? 14, ), ] - : [ + : [ Flexible( - child: Text(headerString, - style: headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)), + child: Text( + headerString, + style: headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: + CalendarViewHelper.getTextDirectionBasedOnLocale( + widget.locale, + ), + ), + ), ], - )), - )), - ); - - final Widget weekNumberWidget = weekNumberEnabled - ? Container( - width: - isCenterAlignment ? weekNumberTextWidth : weekNumberPanelWidth, - height: weekNumberPanelHeight, - alignment: _getHeaderAlignment(), - child: Container( + ), + ), + ), + ), + ); + + final Widget weekNumberWidget = + weekNumberEnabled + ? Container( + width: + isCenterAlignment + ? weekNumberTextWidth + : weekNumberPanelWidth, + height: weekNumberPanelHeight, + alignment: getHeaderAlignment(), + child: Container( decoration: BoxDecoration( - borderRadius: - const BorderRadius.all(Radius.circular(padding)), - shape: BoxShape.rectangle, - color: weekNumberBackgroundColor), + borderRadius: const BorderRadius.all( + Radius.circular(padding), + ), + color: weekNumberBackgroundColor, + ), alignment: Alignment.center, width: weekNumberTextWidth, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Flexible( - child: Text( - widget.localizations.weeknumberLabel, - textAlign: TextAlign.center, - textScaleFactor: widget.textScaleFactor, - style: weekNumberTextStyle, - overflow: TextOverflow.ellipsis, - maxLines: 1, - )), + child: Text( + widget.localizations.weeknumberLabel, + textAlign: TextAlign.center, + textScaler: TextScaler.linear(widget.textScaleFactor), + style: weekNumberTextStyle, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), Flexible( - child: Text( - ' $weekNumberString', - textAlign: TextAlign.center, - style: weekNumberTextStyle, - textScaleFactor: widget.textScaleFactor, - maxLines: 1, - )) + child: Text( + ' $weekNumberString', + textAlign: TextAlign.center, + style: weekNumberTextStyle, + textScaler: TextScaler.linear(widget.textScaleFactor), + maxLines: 1, + ), + ), ], - )), - ) - : Container(); + ), + ), + ) + : Container(); final Color? leftArrowSplashColor = prevArrowColor != arrowColor || !widget.enableInteraction ? Colors.transparent : null; - final Container leftArrow = navigationArrowEnabled - ? Container( - alignment: Alignment.center, - color: headerBackgroundColor, - width: arrowWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( + final Container leftArrow = + navigationArrowEnabled + ? Container( + alignment: Alignment.center, + color: headerBackgroundColor, + width: arrowWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( color: headerBackgroundColor, child: InkWell( //// set splash color as transparent when arrow reaches min date(disabled) @@ -9895,34 +11228,41 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { 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, - )), + width: arrowWidth, + height: headerHeight, + alignment: Alignment.center, + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration( + color: Colors.transparent, + ), + child: Icon( + widget.navigationDirection == + MonthNavigationDirection.horizontal + ? Icons.chevron_left + : Icons.keyboard_arrow_up, + color: prevArrowColor, + size: arrowSize, + ), + ), ), - )), - ) - : Container(); + ), + ), + ) + : Container(); final Color? rightArrowSplashColor = nextArrowColor != arrowColor || !widget.enableInteraction ? Colors.transparent : null; - final Container rightArrow = navigationArrowEnabled - ? Container( - alignment: Alignment.center, - color: headerBackgroundColor, - width: arrowWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( + final Container rightArrow = + navigationArrowEnabled + ? Container( + alignment: Alignment.center, + color: headerBackgroundColor, + width: arrowWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( color: headerBackgroundColor, child: InkWell( //// set splash color as transparent when arrow reaches max date(disabled) @@ -9934,32 +11274,39 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { 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, - )), + width: arrowWidth, + height: headerHeight, + alignment: Alignment.center, + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration( + color: Colors.transparent, + ), + child: Icon( + widget.navigationDirection == + MonthNavigationDirection.horizontal + ? Icons.chevron_right + : Icons.keyboard_arrow_down, + color: nextArrowColor, + size: arrowSize, + ), + ), ), - )), - ) - : Container(); + ), + ), + ) + : Container(); final Color? todaySplashColor = !widget.enableInteraction ? Colors.transparent : null; - final Widget todayIcon = widget.showDatePickerButton - ? Container( - alignment: Alignment.center, - color: headerBackgroundColor, - width: todayIconWidth, - height: headerHeight, - padding: EdgeInsets.all(useMobilePlatformUI ? 2 : 4), - child: Material( + final Widget todayIcon = + widget.showTodayButton + ? Container( + alignment: Alignment.center, + color: headerBackgroundColor, + width: todayIconWidth, + height: headerHeight, + padding: EdgeInsets.all(useMobilePlatformUI ? 2 : 4), + child: Material( color: headerBackgroundColor, child: InkWell( splashColor: todaySplashColor, @@ -9976,55 +11323,60 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { }, 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( - decoration: BoxDecoration( - border: Border.all( - color: widget.cellBorderColor ?? - widget.calendarTheme.cellBorderColor!), - borderRadius: BorderRadius.circular(5.0), - ), - width: todayIconWidth, - alignment: Alignment.center, - child: Text( - todayText, - style: TextStyle( + child: + useMobilePlatformUI + ? Container( + decoration: const BoxDecoration( + color: Colors.transparent, + ), + clipBehavior: Clip.antiAlias, + width: todayIconWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + Icons.today, + color: style.color, + size: style.fontSize, + ), + ) + : Container( + decoration: BoxDecoration( + border: Border.all( + color: + widget.cellBorderColor ?? + widget.calendarTheme.cellBorderColor!, + ), + borderRadius: BorderRadius.circular(5.0), + ), + width: todayIconWidth, + alignment: Alignment.center, + child: Text( + todayText, + style: TextStyle( color: headerTextColor, - fontSize: defaultCalendarViewTextSize), - maxLines: 1, - textDirection: TextDirection.ltr, - )), + fontSize: defaultCalendarViewTextSize, + ), + maxLines: 1, + textDirection: TextDirection.ltr, + ), + ), ), - )), - ) - : Container(); - - final Widget dividerWidget = widget.showDatePickerButton && - isNeedViewSwitchOption && - !useMobilePlatformUI - ? Container( - alignment: Alignment.center, - color: headerBackgroundColor, - width: dividerWidth, - height: headerHeight, - padding: const EdgeInsets.symmetric(vertical: 5), - child: const VerticalDivider( - color: Colors.grey, - thickness: 0.5, - )) - : const SizedBox( - width: 0, - height: 0, - ); + ), + ), + ) + : Container(); + + final Widget dividerWidget = + widget.showTodayButton && isNeedViewSwitchOption && !useMobilePlatformUI + ? Container( + alignment: Alignment.center, + color: headerBackgroundColor, + width: dividerWidth, + height: headerHeight, + padding: const EdgeInsets.symmetric(vertical: 5), + child: const VerticalDivider(color: Colors.grey, thickness: 0.5), + ) + : const SizedBox(width: 0, height: 0); List rowChildren = []; @@ -10054,9 +11406,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: rowChildren); + mainAxisAlignment: MainAxisAlignment.center, + children: rowChildren, + ); } else if (widget.headerStyle.textAlign == TextAlign.right || widget.headerStyle.textAlign == TextAlign.end) { if (widget.isMobilePlatform) { @@ -10082,9 +11434,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: rowChildren); + mainAxisAlignment: MainAxisAlignment.center, + children: rowChildren, + ); } else { if (widget.isMobilePlatform) { rowChildren = [ @@ -10111,17 +11463,18 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: rowChildren); + mainAxisAlignment: MainAxisAlignment.center, + children: rowChildren, + ); } } @override void dispose() { widget.valueChangeNotifier.removeListener(_updateHeaderChanged); - widget.valueChangeNotifier - .removeListener(_updateWeekNumberChangedForTimelineMonth); + widget.valueChangeNotifier.removeListener( + _updateWeekNumberChangedForTimelineMonth, + ); super.dispose(); } @@ -10134,7 +11487,10 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { return; } - setState(() {}); + // To avoid the unnecessary build while the timeline month view building + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { + setState(() {}); + }); } void _backward() { @@ -10154,26 +11510,28 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } Widget _getCalendarViewWidget( - Color headerBackgroundColor, - bool useMobilePlatformUI, - bool isNeedIcon, - double width, - double height, - TextStyle style, - Color arrowColor, - Color headerTextColor, - CalendarView view, - bool isHighlighted, - Color? highlightColor, - double defaultCalendarViewTextSize, - {String? semanticLabel}) { + Color headerBackgroundColor, + bool useMobilePlatformUI, + bool isNeedIcon, + double width, + double height, + TextStyle style, + Color arrowColor, + Color headerTextColor, + CalendarView view, + bool isHighlighted, + Color? highlightColor, + double defaultCalendarViewTextSize, { + String? semanticLabel, + }) { final String text = _calendarViews[view]!; final Color? calendarViewSplashColor = !widget.enableInteraction ? Colors.transparent : null; - final Color? allowedViewsColor = isHighlighted - ? highlightColor - : widget.cellBorderColor ?? widget.calendarTheme.cellBorderColor; + final Color? allowedViewsColor = + isHighlighted + ? highlightColor + : widget.cellBorderColor ?? widget.calendarTheme.cellBorderColor; return Container( alignment: Alignment.center, color: headerBackgroundColor, @@ -10181,86 +11539,105 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { height: height, padding: EdgeInsets.all(useMobilePlatformUI ? 2 : 4), child: Material( - color: isHighlighted && (isNeedIcon || useMobilePlatformUI) - ? Colors.grey.withOpacity(0.3) - : headerBackgroundColor, + color: + isHighlighted && (isNeedIcon || useMobilePlatformUI) + ? Colors.grey.withValues(alpha: 0.3) + : headerBackgroundColor, child: InkWell( - splashColor: calendarViewSplashColor, - highlightColor: calendarViewSplashColor, - hoverColor: calendarViewSplashColor, - 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 + splashColor: calendarViewSplashColor, + highlightColor: calendarViewSplashColor, + hoverColor: calendarViewSplashColor, + 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, - )) + width: width, + height: height, + alignment: Alignment.center, + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration( + color: Colors.transparent, + ), + child: Icon( + Icons.more_vert, + color: style.color, + size: style.fontSize, + ), + ) : (isNeedIcon ? Container( - width: width, - height: height, - alignment: Alignment.center, - decoration: BoxDecoration( - border: Border.all(color: highlightColor!), - borderRadius: BorderRadius.circular(5.0)), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - text, - style: TextStyle( - color: highlightColor, - 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( - decoration: BoxDecoration( - border: Border.all(color: allowedViewsColor!), - borderRadius: BorderRadius.circular(5.0)), - width: width, - height: height, - alignment: Alignment.center, - child: Text(text, - textAlign: TextAlign.center, + width: width, + height: height, + alignment: Alignment.center, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + border: Border.all(color: highlightColor!), + borderRadius: BorderRadius.circular(5.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + text, style: TextStyle( - color: isHighlighted - ? highlightColor - : headerTextColor, - fontSize: defaultCalendarViewTextSize), + color: highlightColor, + fontSize: defaultCalendarViewTextSize, + ), maxLines: 1, - textDirection: TextDirection.ltr), - )))), + textDirection: TextDirection.ltr, + ), + Icon( + widget.viewChangeNotifier.value + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: + widget + .calendarTheme + .headerTextStyle! + .fontSize ?? + 14, + ), + ], + ), + ) + : Container( + decoration: BoxDecoration( + border: Border.all(color: allowedViewsColor!), + borderRadius: BorderRadius.circular(5.0), + ), + width: width, + height: height, + alignment: Alignment.center, + child: Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + color: + isHighlighted + ? highlightColor + : headerTextColor, + fontSize: defaultCalendarViewTextSize, + ), + maxLines: 1, + textDirection: TextDirection.ltr, + ), + )), + ), + ), ), ); } @@ -10270,8 +11647,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { case CalendarView.day: case CalendarView.timelineDay: { - return DateTimeHelper.getWeekNumberOfYear(widget.visibleDates[0]) - .toString(); + return DateTimeHelper.getWeekNumberOfYear( + widget.visibleDates[0], + ).toString(); } case CalendarView.week: case CalendarView.workWeek: @@ -10294,20 +11672,21 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } else if (widget.nonWorkingDays.contains(DateTime.monday)) { final int midDate = widget.visibleDates.length ~/ 2; return DateTimeHelper.getWeekNumberOfYear( - widget.visibleDates[midDate]) - .toString(); + widget.visibleDates[midDate], + ).toString(); } } break; case CalendarView.timelineMonth: { return DateTimeHelper.getWeekNumberOfYear( - widget.timelineMonthWeekNumberNotifier.value!) - .toString(); + widget.timelineMonthWeekNumberNotifier.value!, + ).toString(); } } - return DateTimeHelper.getWeekNumberOfYear(widget.visibleDates[0]) - .toString(); + return DateTimeHelper.getWeekNumberOfYear( + widget.visibleDates[0], + ).toString(); } String _getHeaderText() { @@ -10316,17 +11695,25 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { widget.headerDateFormat != null && widget.headerDateFormat!.isNotEmpty ? widget.headerDateFormat : null; + final List headerFormatString = + headerDateFormat == null + ? [] + : CalendarViewHelper.getListFromString(headerDateFormat); final int visibleDatesCount = DateTimeHelper.getViewDatesCount( - widget.view, - widget.numberOfDaysInView, - widget.numberOfDaysInView, - widget.nonWorkingDays); + widget.view, + widget.numberOfDaysInView, + widget.numberOfDaysInView, + widget.nonWorkingDays, + ); switch (widget.view) { case CalendarView.schedule: { if (headerDateFormat != null) { - return DateFormat(headerDateFormat, widget.locale) - .format(widget.valueChangeNotifier.value!); + return CalendarViewHelper.getLocalizedString( + widget.valueChangeNotifier.value!, + headerFormatString, + widget.locale, + ); } // ignore: lines_longer_than_80_chars return '${DateFormat(monthFormat, widget.locale).format(widget.valueChangeNotifier.value!)} ${widget.valueChangeNotifier.value!.year}'; @@ -10341,7 +11728,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { startDate.month != endDate.month) { if (headerDateFormat != null) { // ignore: lines_longer_than_80_chars - return '${DateFormat(headerDateFormat, widget.locale).format(startDate)} - ${DateFormat(headerDateFormat, widget.locale).format(endDate)}'; + return '${CalendarViewHelper.getLocalizedString(startDate, headerFormatString, widget.locale)} - ${CalendarViewHelper.getLocalizedString(endDate, headerFormatString, widget.locale)}'; } monthFormat = 'MMM'; // ignore: lines_longer_than_80_chars @@ -10349,8 +11736,12 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } if (headerDateFormat != null) { - return DateFormat(headerDateFormat, widget.locale) - .format(widget.currentDate!); + // ignore: lines_longer_than_80_chars + return CalendarViewHelper.getLocalizedString( + widget.currentDate!, + headerFormatString, + widget.locale, + ); } // ignore: lines_longer_than_80_chars return '${DateFormat(monthFormat, widget.locale).format(widget.currentDate!)} ${widget.currentDate!.year}'; @@ -10361,8 +11752,12 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { { final DateTime headerDate = widget.visibleDates[0]; if (headerDateFormat != null) { - return DateFormat(headerDateFormat, widget.locale) - .format(headerDate); + // ignore: lines_longer_than_80_chars + return CalendarViewHelper.getLocalizedString( + headerDate, + headerFormatString, + widget.locale, + ); } // ignore: lines_longer_than_80_chars return '${DateFormat(monthFormat, widget.locale).format(headerDate)} ${headerDate.year}'; @@ -10376,19 +11771,26 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { widget.visibleDates[widget.visibleDates.length - 1]; if (visibleDatesCount == 1) { if (headerDateFormat != null) { - return DateFormat(headerDateFormat, widget.locale) - .format(startDate); + // ignore: lines_longer_than_80_chars + return CalendarViewHelper.getLocalizedString( + startDate, + headerFormatString, + widget.locale, + ); } // ignore: lines_longer_than_80_chars return '${DateFormat(monthFormat, widget.locale).format(startDate)} ${startDate.year}'; } else { if (headerDateFormat != null) { // ignore: lines_longer_than_80_chars - return '${DateFormat(headerDateFormat, widget.locale).format(startDate)} - ${DateFormat(headerDateFormat, widget.locale).format(endDate)}'; + return '${CalendarViewHelper.getLocalizedString(startDate, headerFormatString, widget.locale)} - ${CalendarViewHelper.getLocalizedString(endDate, headerFormatString, widget.locale)}'; } + monthFormat = 'MMM'; - String startText = - DateFormat(monthFormat, widget.locale).format(startDate); + String startText = DateFormat( + monthFormat, + widget.locale, + ).format(startDate); startText = '${startDate.day} $startText - '; final String endText = // ignore: lines_longer_than_80_chars @@ -10403,19 +11805,20 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { /// 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); + this.startDate, + this.endDate, + this.scheduleViewSettings, + this.isMonthLabel, + this.isRTL, + this.locale, + this.useMobilePlatformUI, + this.agendaViewNotifier, + this.calendarTheme, + this.themeData, + this._localizations, + this.textScaleFactor, { + this.isDisplayDate = false, + }) : super(repaint: isDisplayDate ? agendaViewNotifier : null); final DateTime startDate; final DateTime? endDate; @@ -10427,6 +11830,7 @@ class _ScheduleLabelPainter extends CustomPainter { final bool useMobilePlatformUI; final ValueNotifier agendaViewNotifier; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final bool isDisplayDate; final double textScaleFactor; final TextPainter _textPainter = TextPainter(); @@ -10453,24 +11857,27 @@ class _ScheduleLabelPainter extends CustomPainter { /// 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'), + style: themeData.textTheme.bodyMedium!.merge( + scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? + scheduleViewSettings.placeholderTextStyle, + ), ); double xPosition = 10; _updateTextPainter(span); _textPainter.layout( - minWidth: 0, - maxWidth: size.width - xPosition > 0 ? size.width - xPosition : 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)); + canvas, + Offset(xPosition, (size.height - _textPainter.height) / 2), + ); /// Add hovering effect on display date view. if (isDisplayDate && @@ -10479,9 +11886,13 @@ class _ScheduleLabelPainter extends CustomPainter { 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); + 0, + padding, + size.width - 2, + size.height - (2 * padding), + ); + _backgroundPainter.color = calendarTheme.selectionBorderColor! + .withValues(alpha: 0.4); _backgroundPainter.style = PaintingStyle.stroke; _backgroundPainter.strokeWidth = 2; canvas.drawRect(rect, _backgroundPainter); @@ -10489,14 +11900,16 @@ class _ScheduleLabelPainter extends CustomPainter { } 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); + 0, + padding + viewPadding, + size.width - (isRTL ? viewPadding : padding), + size.height - (2 * (viewPadding + padding)), + ); + _backgroundPainter.color = Colors.grey.withValues(alpha: 0.1); canvas.drawRRect( - RRect.fromRectAndRadius(rect, const Radius.circular(4)), - _backgroundPainter); + RRect.fromRectAndRadius(rect, const Radius.circular(4)), + _backgroundPainter, + ); } } } @@ -10513,27 +11926,42 @@ class _ScheduleLabelPainter extends CustomPainter { } endDateFormat ??= 'MMM dd'; - final String firstDate = - DateFormat(startDateFormat, locale).format(startDate); - final String lastDate = DateFormat(endDateFormat, locale).format(endDate!); + final List startDateStringFormats = + CalendarViewHelper.getListFromString(startDateFormat); + final List endDateStringFormats = + CalendarViewHelper.getListFromString(endDateFormat); + final String firstDate = CalendarViewHelper.getLocalizedString( + startDate, + startDateStringFormats, + locale, + ); + final String lastDate = CalendarViewHelper.getLocalizedString( + endDate!, + endDateStringFormats, + locale, + ); final TextSpan span = TextSpan( text: '$firstDate - $lastDate', - style: scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? - const TextStyle( - color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), + style: themeData.textTheme.bodyMedium! + .copyWith(color: Colors.grey, fontSize: 15) + .merge(scheduleViewSettings.weekHeaderSettings.weekTextStyle), ); _backgroundPainter.color = scheduleViewSettings.weekHeaderSettings.backgroundColor; /// Draw week label background. canvas.drawRect( - Rect.fromLTWH(0, yPosition, size.width, - scheduleViewSettings.weekHeaderSettings.height), - _backgroundPainter); + 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); + _textPainter.layout(maxWidth: size.width - 10 > 0 ? size.width - 10 : 0); if (scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.right || scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.end) { @@ -10556,12 +11984,14 @@ class _ScheduleLabelPainter extends CustomPainter { /// Draw week label text _textPainter.paint( - canvas, - Offset( - xPosition, - yPosition + - (scheduleViewSettings.weekHeaderSettings.height / 2 - - _textPainter.height / 2))); + canvas, + Offset( + xPosition, + yPosition + + (scheduleViewSettings.weekHeaderSettings.height / 2 - + _textPainter.height / 2), + ), + ); } void _addMonthLabel(Canvas canvas, Size size) { @@ -10569,24 +11999,38 @@ class _ScheduleLabelPainter extends CustomPainter { const double yPosition = 0; final String monthFormat = scheduleViewSettings.monthHeaderSettings.monthFormat; + final List monthStringFormats = + CalendarViewHelper.getListFromString(monthFormat); + final String dateString = CalendarViewHelper.getLocalizedString( + startDate, + monthStringFormats, + locale, + ); final TextSpan span = TextSpan( - text: DateFormat(monthFormat, locale).format(startDate), - style: scheduleViewSettings.monthHeaderSettings.monthTextStyle ?? - const TextStyle( - color: Colors.white, fontSize: 20, fontFamily: 'Roboto'), + text: dateString, + style: themeData.textTheme.bodyLarge! + .copyWith( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w400, + ) + .merge(scheduleViewSettings.monthHeaderSettings.monthTextStyle), ); _backgroundPainter.shader = null; _backgroundPainter.color = scheduleViewSettings.monthHeaderSettings.backgroundColor; - final Rect rect = Rect.fromLTWH(0, yPosition, size.width, - scheduleViewSettings.monthHeaderSettings.height); + 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); + _textPainter.layout(maxWidth: size.width - 10 > 0 ? size.width - 10 : 0); final double viewPadding = size.width * 0.15; xPosition = viewPadding; @@ -10619,7 +12063,7 @@ class _ScheduleLabelPainter extends CustomPainter { _textPainter.maxLines = 1; _textPainter.textDirection = TextDirection.ltr; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); } @override @@ -10650,13 +12094,15 @@ class _ScheduleLabelPainter extends CustomPainter { accessibilityText = DateFormat('MMMM yyyy', locale).format(startDate); } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, size.width, cellHeight), - properties: SemanticsProperties( - label: accessibilityText, - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, size.width, cellHeight), + properties: SemanticsProperties( + label: accessibilityText, + textDirection: TextDirection.ltr, + ), ), - )); + ); return semanticsBuilder; } @@ -10687,13 +12133,13 @@ class _ScheduleAppointmentView extends Stack { AlignmentDirectional? alignment, Key? key, }) : super( - key: key, - children: [ - RepaintBoundary(child: content), - RepaintBoundary(child: header) - ], - alignment: alignment ?? AlignmentDirectional.topStart, - ); + key: key, + children: [ + RepaintBoundary(child: content), + RepaintBoundary(child: header), + ], + alignment: alignment ?? AlignmentDirectional.topStart, + ); @override RenderStack createRenderObject(BuildContext context) => @@ -10722,18 +12168,14 @@ class _AppointmentViewHeaderRenderObject extends RenderStack { AlignmentGeometry alignment = AlignmentDirectional.topStart, TextDirection? textDirection, StackFit fit = StackFit.loose, - }) : _scrollableState = scrollableState, - super( - alignment: alignment, - textDirection: textDirection, - fit: 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)!; + RenderAbstractViewport get _stackViewPort => RenderAbstractViewport.of(this); ScrollableState? get scrollableState => _scrollableState; @@ -10792,13 +12234,18 @@ class _AppointmentViewHeaderRenderObject extends RenderStack { final StackParentData headerParentData = // ignore: avoid_as headerView.parentData! as StackParentData; - final double headerYOffset = - _getHeaderOffset(contentSize, offset, headerSize); + 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); + headerParentData.offset = Offset( + headerParentData.offset.dx, + headerYOffset, + ); } } @@ -10896,34 +12343,46 @@ class _CustomSplash extends InteractiveInkFeature { 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, - )); + }) : _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.a * 255).toInt(), end: 0), + ); controller.addInkFeature(this); } @@ -10962,9 +12421,10 @@ class _CustomSplash extends InteractiveInkFeature { /// 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; + duration = + duration > _kUnconfirmedRippleSplashDuration + ? _kUnconfirmedRippleSplashDuration + : duration; _radiusController ..duration = duration ..forward(); @@ -11000,8 +12460,11 @@ class _CustomSplash extends InteractiveInkFeature { /// 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); + center = Offset.lerp( + center, + referenceBox.size.center(Offset.zero), + _radiusController.value, + ); } /// Get the offset needs to translate, if it not specified then it @@ -11018,7 +12481,7 @@ class _CustomSplash extends InteractiveInkFeature { if (_clipCallback != null) { /// Clip and draw the rect with fade animation value on canvas. - final Rect rect = _clipCallback!(); + final Rect rect = _clipCallback(); if (_borderRadius != BorderRadius.zero) { final RRect roundedRect = RRect.fromRectAndCorners( rect, @@ -11043,19 +12506,20 @@ class _CustomSplash extends InteractiveInkFeature { 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); + this.selectedDate, + this.monthViewSettings, + this.scheduleViewSettings, + this.todayHighlightColor, + this.todayTextStyle, + this.locale, + this.calendarTheme, + this.themeData, + this.agendaDateNotifier, + this.viewWidth, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform, + ) : super(repaint: agendaDateNotifier); final DateTime? selectedDate; final MonthViewSettings? monthViewSettings; @@ -11065,6 +12529,7 @@ class _AgendaDateTimePainter extends CustomPainter { final String locale; final ValueNotifier agendaDateNotifier; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final double viewWidth; final bool isRTL; final double textScaleFactor; @@ -11081,58 +12546,103 @@ class _AgendaDateTimePainter extends CustomPainter { return; } - final bool useMobilePlatformUI = - CalendarViewHelper.isMobileLayoutUI(viewWidth, isMobilePlatform); + final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + viewWidth, + isMobilePlatform, + ); final bool isToday = isSameDate(selectedDate, DateTime.now()); TextStyle? dateTextStyle, dayTextStyle; + + /// Holds the default agenda day text style from framework text theme. + final TextStyle agendaDayThemeTextStyle = themeData.textTheme.bodySmall! + .copyWith( + color: themeData.colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: 10, + fontWeight: FontWeight.w500, + ); + + /// Holds the default agenda date text style from framework text theme. + final TextStyle agendaDateThemeTextStyle = themeData.textTheme.bodyMedium! + .copyWith(color: themeData.colorScheme.onSurface, fontSize: 18); if (monthViewSettings != null) { - dayTextStyle = monthViewSettings!.agendaStyle.dayTextStyle ?? - calendarTheme.agendaDayTextStyle; - dateTextStyle = monthViewSettings!.agendaStyle.dateTextStyle ?? - calendarTheme.agendaDateTextStyle; + dayTextStyle = agendaDayThemeTextStyle + .merge(calendarTheme.agendaDayTextStyle) + .merge(monthViewSettings!.agendaStyle.dayTextStyle); + dateTextStyle = agendaDateThemeTextStyle + .merge(calendarTheme.agendaDateTextStyle) + .merge(monthViewSettings!.agendaStyle.dateTextStyle); } 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)); + if (useMobilePlatformUI) { + dayTextStyle = agendaDayThemeTextStyle + .merge(calendarTheme.agendaDayTextStyle) + .merge(scheduleViewSettings!.dayHeaderSettings.dayTextStyle); + dateTextStyle = agendaDateThemeTextStyle + .merge(calendarTheme.agendaDateTextStyle) + .merge(scheduleViewSettings!.dayHeaderSettings.dateTextStyle); + } else { + dayTextStyle = agendaDayThemeTextStyle.merge( + scheduleViewSettings!.dayHeaderSettings.dayTextStyle ?? + TextStyle( + color: calendarTheme.agendaDayTextStyle?.color, + fontSize: 9, + fontWeight: FontWeight.w500, + ), + ); + dateTextStyle = agendaDateThemeTextStyle.merge( + scheduleViewSettings!.dayHeaderSettings.dateTextStyle ?? + TextStyle( + color: calendarTheme.agendaDateTextStyle?.color, + fontSize: 18, + fontWeight: FontWeight.normal, + ), + ); + } } if (isToday) { - final Color? todayTextStyleColor = todayTextStyle != null - ? todayTextStyle!.color - : calendarTheme.todayTextStyle!.color; + final Color? todayTextStyleColor = calendarTheme.todayTextStyle!.color; final Color? todayTextColor = CalendarViewHelper.getTodayHighlightTextColor( - todayHighlightColor, todayTextStyle, calendarTheme); - dayTextStyle = todayTextStyle != null - ? todayTextStyle! - .copyWith(fontSize: dayTextStyle!.fontSize, color: todayTextColor) - : dayTextStyle!.copyWith(color: todayTextColor); - dateTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: dateTextStyle!.fontSize, color: todayTextStyleColor) - : dateTextStyle!.copyWith(color: todayTextStyleColor); + todayHighlightColor, + todayTextStyle, + calendarTheme, + ); + dayTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: dayTextStyle.fontSize, + color: todayTextColor, + ) + : dayTextStyle.copyWith(color: todayTextColor); + dateTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: dateTextStyle.fontSize, + color: todayTextStyleColor, + ) + : dateTextStyle.copyWith(color: todayTextStyleColor); } /// Draw day label other than web schedule view. if (scheduleViewSettings == null || useMobilePlatformUI) { - _addDayLabelForMobile(canvas, size, padding, dayTextStyle!, - dateTextStyle!, isToday, isMobilePlatform); + _addDayLabelForMobile( + canvas, + size, + padding, + dayTextStyle, + dateTextStyle, + isToday, + isMobilePlatform, + ); } else { _addDayLabelForWeb( - canvas, size, padding, dayTextStyle!, dateTextStyle!, isToday); + canvas, + size, + padding, + dayTextStyle, + dateTextStyle, + isToday, + ); } } @@ -11142,41 +12652,45 @@ class _AgendaDateTimePainter extends CustomPainter { _textPainter.textDirection = TextDirection.ltr; _textPainter.textAlign = TextAlign.left; _textPainter.textWidthBasis = TextWidthBasis.parent; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); } void _addDayLabelForMobile( - Canvas canvas, - Size size, - double padding, - TextStyle dayTextStyle, - TextStyle dateTextStyle, - bool isToday, - bool isMobile) { + 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'; + final String dayTextFormat = + scheduleViewSettings != null + ? scheduleViewSettings!.dayHeaderSettings.dayFormat + : 'EEE'; TextSpan span = TextSpan( - text: DateFormat(dayTextFormat, locale) - .format(selectedDate!) - .toUpperCase(), - style: dayTextStyle); + text: + DateFormat(dayTextFormat, locale).format(selectedDate!).toUpperCase(), + style: dayTextStyle, + ); _updateTextPainter(span); - _textPainter.layout(minWidth: 0, maxWidth: size.width); + _textPainter.layout(maxWidth: size.width); _textPainter.paint( - canvas, - Offset( - padding + ((size.width - (2 * padding) - _textPainter.width) / 2), - padding)); + 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); + _textPainter.layout(maxWidth: size.width); /// The padding value provides the space between the date and day text. const int inBetweenPadding = 2; @@ -11202,12 +12716,13 @@ class _AgendaDateTimePainter extends CustomPainter { 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); + _linePainter.color = + isToday + ? Colors.black.withValues(alpha: 0.1) + : (themeData.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withValues(alpha: 0.04); _drawTodayCircle(canvas, xPosition, yPosition, padding); } } @@ -11215,25 +12730,31 @@ class _AgendaDateTimePainter extends CustomPainter { _textPainter.paint(canvas, Offset(xPosition, yPosition)); } - void _addDayLabelForWeb(Canvas canvas, Size size, double padding, - TextStyle dayTextStyle, TextStyle dateTextStyle, bool isToday) { + 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. const String maxWidthDateText = '30'; - final String dayText = DateFormat( - isRTL - ? '${scheduleViewSettings!.dayHeaderSettings.dayFormat}, MMM' - : 'MMM, ${scheduleViewSettings!.dayHeaderSettings.dayFormat}', - locale) - .format(selectedDate!) - .toUpperCase(); + final String dayText = + DateFormat( + isRTL + ? '${scheduleViewSettings!.dayHeaderSettings.dayFormat}, MMM' + : 'MMM, ${scheduleViewSettings!.dayHeaderSettings.dayFormat}', + locale, + ).format(selectedDate!).toUpperCase(); //// Draw Weekday TextSpan span = TextSpan(text: maxWidthDateText, style: dateTextStyle); _updateTextPainter(span); - _textPainter.layout(minWidth: 0, maxWidth: size.width); + _textPainter.layout(maxWidth: size.width); /// Calculate the start padding value for web schedule view date time label. double startXPosition = size.width / 5; @@ -11243,7 +12764,7 @@ class _AgendaDateTimePainter extends CustomPainter { final double painterWidth = _textPainter.width; span = TextSpan(text: dateText, style: dateTextStyle); _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: size.width); + _textPainter.layout(maxWidth: size.width); double dateTextPadding = (painterWidth - _textPainter.width) / 2; if (dateTextPadding < 0) { dateTextPadding = 0; @@ -11268,12 +12789,13 @@ class _AgendaDateTimePainter extends CustomPainter { 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); + _linePainter.color = + isToday + ? Colors.black.withValues(alpha: 0.1) + : (themeData.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withValues(alpha: 0.04); _drawTodayCircle(canvas, dateTextStartPosition, yPosition, padding); } } @@ -11284,32 +12806,43 @@ class _AgendaDateTimePainter extends CustomPainter { span = TextSpan(text: dayText, style: dayTextStyle); _textPainter.text = span; if (isRTL) { - _textPainter.layout(minWidth: 0, maxWidth: startXPosition); + _textPainter.layout(maxWidth: startXPosition); startXPosition -= _textPainter.width + (3 * padding); if (startXPosition > 0) { - _textPainter.paint(canvas, - Offset(startXPosition, (dateHeight - _textPainter.height) / 2)); + _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)); + _textPainter.layout(maxWidth: size.width - startXPosition); + _textPainter.paint( + canvas, + Offset(startXPosition, (dateHeight - _textPainter.height) / 2), + ); } } void _drawTodayCircle( - Canvas canvas, double xPosition, double yPosition, double padding) { + 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); + Offset( + xPosition + (_textPainter.width / 2), + yPosition + (_textPainter.height / 2), + ), + _textPainter.width > _textPainter.height + ? (_textPainter.width / 2) + padding + : (_textPainter.height / 2) + padding, + _linePainter, + ); } @override @@ -11339,14 +12872,17 @@ class _AgendaDateTimePainter extends CustomPainter { if (selectedDate == null) { return semanticsBuilder; } else if (selectedDate != null) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Offset.zero & size, - properties: SemanticsProperties( - label: DateFormat('EEEEE').format(selectedDate!) + - DateFormat('dd MMMM yyyy').format(selectedDate!), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Offset.zero & size, + properties: SemanticsProperties( + label: + DateFormat('EEEEE').format(selectedDate!) + + DateFormat('dd MMMM yyyy').format(selectedDate!), + textDirection: TextDirection.ltr, + ), ), - )); + ); } return semanticsBuilder; @@ -11361,8 +12897,12 @@ class _ScheduleViewDetails { } /// Returns the maximum radius value calculated based on input touch position. -double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, - RectCallback? rectCallback, Offset 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; @@ -11379,7 +12919,10 @@ double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, /// Return the rect callback value based on its argument value. RectCallback? _getClipCallback( - RenderBox referenceBox, bool containedInkWell, RectCallback? rectCallback) { + RenderBox referenceBox, + bool containedInkWell, + RectCallback? rectCallback, +) { if (rectCallback != null) { /// If `containedInkWell` is false, then `rectCallback` should be null. assert(containedInkWell); @@ -11392,8 +12935,12 @@ RectCallback? _getClipCallback( } Size _getTextWidgetWidth( - String text, double height, double width, BuildContext context, - {TextStyle? style}) { + String text, + double height, + double width, + BuildContext context, { + TextStyle? style, +}) { /// Create new text with it style. final Widget richTextWidget = Text( text, @@ -11411,16 +12958,19 @@ Size _getTextWidgetWidth( /// 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, - )); + 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)); + TextSelection(baseOffset: 0, extentOffset: text.length), + ); double textWidth = 0; double textHeight = 0; for (final TextBox box in textBox) { @@ -11453,7 +13003,9 @@ Map _getCalendarViewsText(SfLocalizations localizations) { /// Return day label width based on schedule view setting. double _getAgendaViewDayLabelWidth( - ScheduleViewSettings scheduleViewSettings, bool useMobilePlatformUI) { + ScheduleViewSettings scheduleViewSettings, + bool useMobilePlatformUI, +) { if (scheduleViewSettings.dayHeaderSettings.width == -1) { return useMobilePlatformUI ? 50 : 150; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart new file mode 100644 index 000000000..92928fbdb --- /dev/null +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/theme.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [SfCalendarThemeColors] this class provides material 2 +/// and material 3 themeData. +/// SfCalendarThemeColors class extends the 'SfCalendarThemeData' class and +/// customize the appearance of a mapping component based on th color scheme +/// obtained from the provided [BuildContext]. +class SfCalendarThemeColors extends SfCalendarThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfCalendarThemeColors(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + @override + Color? get backgroundColor => Colors.transparent; + + @override + Color? get headerBackgroundColor => + Theme.of(context).useMaterial3 + ? (Theme.of(context).brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1)) + : Colors.transparent; + + @override + Color? get agendaBackgroundColor => Colors.transparent; + + @override + Color? get activeDatesBackgroundColor => Colors.transparent; + + @override + Color? get todayBackgroundColor => Colors.transparent; + + @override + Color? get trailingDatesBackgroundColor => Colors.transparent; + + @override + Color? get leadingDatesBackgroundColor => Colors.transparent; + + @override + Color? get viewHeaderBackgroundColor => Colors.transparent; + + @override + Color? get allDayPanelColor => Colors.transparent; + + @override + Color? get weekNumberBackgroundColor => colorScheme.onSurface[11]; + + @override + Color? get cellBorderColor => colorScheme.onSurface[42]; + + @override + Color? get todayHighlightColor => colorScheme.primary; + + @override + Color? get selectionBorderColor => colorScheme.primary; +} 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 5371fd3da..78eccbe1b 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 @@ -34,31 +34,32 @@ class CustomCalendarScrollView extends StatefulWidget { /// view(time slot, month, timeline and appointment views) widgets of /// calendar widget. const CustomCalendarScrollView( - 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.resourceCollection, - this.textScaleFactor, - this.isMobilePlatform, - this.fadeInController, - this.minDate, - this.maxDate, - this.localizations, - this.timelineMonthWeekNumberNotifier, - this.updateCalendarState, - this.getCalendarState, - {Key? key}) - : super(key: key); + this.calendar, + this.view, + this.width, + this.height, + this.agendaSelectedDate, + this.isRTL, + this.locale, + this.calendarTheme, + this.themeData, + this.specialRegions, + this.blackoutDates, + this.controller, + this.removePicker, + this.resourcePanelScrollController, + this.resourceCollection, + this.textScaleFactor, + this.isMobilePlatform, + this.fadeInController, + this.minDate, + this.maxDate, + this.localizations, + this.timelineMonthWeekNumberNotifier, + this.updateCalendarState, + this.getCalendarState, { + Key? key, + }) : super(key: key); /// Holds the calendar instance used to get the calendar properties. final SfCalendar calendar; @@ -81,6 +82,9 @@ class CustomCalendarScrollView extends StatefulWidget { /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data value. + final ThemeData themeData; + /// Holds the calendar controller for the calendar widget. final CalendarController controller; @@ -265,29 +269,31 @@ class _CustomCalendarScrollViewState extends State late ValueNotifier<_DragPaintDetails> _dragDetails; Offset? _dragDifferenceOffset; Timer? _timer; + late double? _viewPortHeight; @override void initState() { _dragDetails = ValueNotifier<_DragPaintDetails>( - _DragPaintDetails(position: ValueNotifier(null))); - widget.controller.forward = widget.isRTL - ? _moveToPreviousViewWithAnimation - : _moveToNextViewWithAnimation; - widget.controller.backward = widget.isRTL - ? _moveToNextViewWithAnimation - : _moveToPreviousViewWithAnimation; + _DragPaintDetails(position: ValueNotifier(null)), + ); + 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); + duration: const Duration(milliseconds: 250), + vsync: this, + ); + _animation = _tween.animate( + CurvedAnimation(parent: _animationController, curve: Curves.ease), + )..addListener(animationListener); _timeRegions = CalendarViewHelper.cloneList(widget.specialRegions); @@ -297,18 +303,23 @@ class _CustomCalendarScrollViewState extends State @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) || + 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)) { + _updateCalendarStateDetails.selectedDate, + widget.controller.selectedDate, + )) { _selectResourceProgrammatically(); } } @@ -361,7 +372,9 @@ class _CustomCalendarScrollViewState extends State /// Check and re renders the views if the resource collection changed. if (CalendarViewHelper.isTimelineView(widget.view) && !CalendarViewHelper.isCollectionEqual( - oldWidget.resourceCollection, widget.resourceCollection)) { + oldWidget.resourceCollection, + widget.resourceCollection, + )) { _updateSelectedResourceIndex(); _position = 0; _children.clear(); @@ -394,19 +407,24 @@ class _CustomCalendarScrollViewState extends State oldWidget.calendar.selectionDecoration != widget.calendar.selectionDecoration || oldWidget.calendar.weekNumberStyle != widget.calendar.weekNumberStyle) { - final bool isTimelineView = - CalendarViewHelper.isTimelineView(widget.view); + 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) || + .calendar + .timeSlotViewSettings + .timeIntervalHeight) || (isTimelineView && oldWidget.calendar.timeSlotViewSettings.timeIntervalWidth != widget - .calendar.timeSlotViewSettings.timeIntervalWidth))) { + .calendar + .timeSlotViewSettings + .timeIntervalWidth))) { if (_currentChildIndex == 0) { _previousViewKey.currentState!._retainScrolledDateTime(); } else if (_currentChildIndex == 1) { @@ -419,14 +437,20 @@ class _CustomCalendarScrollViewState extends State _position = 0; } - if (widget.calendar.monthViewSettings.numberOfWeeksInView != - oldWidget.calendar.monthViewSettings.numberOfWeeksInView || - !CalendarViewHelper.isCollectionEqual( - widget.calendar.timeSlotViewSettings.nonWorkingDays, - oldWidget.calendar.timeSlotViewSettings.nonWorkingDays) || + if ((widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView != + oldWidget.calendar.monthViewSettings.numberOfWeeksInView) || widget.calendar.firstDayOfWeek != oldWidget.calendar.firstDayOfWeek || - widget.calendar.timeSlotViewSettings.numberOfDaysInView != - oldWidget.calendar.timeSlotViewSettings.numberOfDaysInView || + (widget.view != CalendarView.month && + (!CalendarViewHelper.isCollectionEqual( + widget.calendar.timeSlotViewSettings.nonWorkingDays, + oldWidget.calendar.timeSlotViewSettings.nonWorkingDays, + ) || + widget.calendar.timeSlotViewSettings.numberOfDaysInView != + oldWidget + .calendar + .timeSlotViewSettings + .numberOfDaysInView)) || widget.isRTL != oldWidget.isRTL) { _updateVisibleDates(); _position = 0; @@ -454,8 +478,10 @@ class _CustomCalendarScrollViewState extends State if (widget.controller == oldWidget.controller) { if (oldWidget.controller.displayDate != widget.controller.displayDate || - !isSameDate(_updateCalendarStateDetails.currentDate, - widget.controller.displayDate)) { + !isSameDate( + _updateCalendarStateDetails.currentDate, + widget.controller.displayDate, + )) { widget.getCalendarState(_updateCalendarStateDetails); _updateCalendarStateDetails.currentDate = widget.controller.displayDate; widget.updateCalendarState(_updateCalendarStateDetails); @@ -470,11 +496,14 @@ class _CustomCalendarScrollViewState extends State _position = 0; } - if (!CalendarViewHelper.isSameTimeSlot(oldWidget.controller.selectedDate, - widget.controller.selectedDate) || + if (!CalendarViewHelper.isSameTimeSlot( + oldWidget.controller.selectedDate, + widget.controller.selectedDate, + ) || !CalendarViewHelper.isSameTimeSlot( - _updateCalendarStateDetails.selectedDate, - widget.controller.selectedDate)) { + _updateCalendarStateDetails.selectedDate, + widget.controller.selectedDate, + )) { widget.getCalendarState(_updateCalendarStateDetails); _updateCalendarStateDetails.selectedDate = widget.controller.selectedDate; @@ -490,6 +519,7 @@ class _CustomCalendarScrollViewState extends State @override Widget build(BuildContext context) { + _viewPortHeight = MediaQuery.of(context).size.height; if (!CalendarViewHelper.isTimelineView(widget.view) && widget.view != CalendarView.month) { _updateScrollPosition(); @@ -501,8 +531,8 @@ class _CustomCalendarScrollViewState extends State bottomPosition = 0; final bool isHorizontalNavigation = widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month; + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month; if (isHorizontalNavigation) { leftPosition = -widget.width; rightPosition = -widget.width; @@ -512,69 +542,78 @@ class _CustomCalendarScrollViewState extends State } final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - final bool isNeedDragAndDrop = widget.calendar.allowDragAndDrop && + final bool isNeedDragAndDrop = + widget.calendar.allowDragAndDrop && widget.view != CalendarView.schedule && (!widget.isMobilePlatform || (widget.view != CalendarView.month && widget.view != CalendarView.timelineMonth)); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); - final double resourceItemHeight = isResourceEnabled - ? CalendarViewHelper.getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height - viewHeaderHeight - timeLabelWidth, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource!.resources!.length) - : 0; + widget.calendar.dataSource, + widget.view, + ); + final double resourceItemHeight = + isResourceEnabled + ? CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height - viewHeaderHeight - timeLabelWidth, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length, + ) + : 0; final double resourceViewSize = isResourceEnabled ? widget.calendar.resourceViewSettings.size : 0; - final bool isMonthView = widget.view == CalendarView.month || + final bool isMonthView = + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); final Widget customScrollWidget = GestureDetector( - child: CustomScrollViewerLayout( - _addViews(), - isHorizontalNavigation - ? CustomScrollDirection.horizontal - : CustomScrollDirection.vertical, - _position, - _currentChildIndex), onTapDown: (TapDownDetails details) { if (!_focusNode.hasFocus) { _focusNode.requestFocus(); } }, - onHorizontalDragStart: isTimelineView - ? null - : (DragStartDetails dragStartDetails) { - _onHorizontalStart( + onHorizontalDragStart: + isTimelineView + ? null + : (DragStartDetails dragStartDetails) { + _onHorizontalStart( dragStartDetails, isResourceEnabled, isTimelineView, viewHeaderHeight, timeLabelWidth, - isNeedDragAndDrop); - }, - onHorizontalDragUpdate: isTimelineView - ? null - : (DragUpdateDetails dragUpdateDetails) { - _onHorizontalUpdate( + isNeedDragAndDrop, + ); + }, + onHorizontalDragUpdate: + isTimelineView + ? null + : (DragUpdateDetails dragUpdateDetails) { + _onHorizontalUpdate( dragUpdateDetails, isResourceEnabled, isMonthView, @@ -583,12 +622,14 @@ class _CustomCalendarScrollViewState extends State timeLabelWidth, resourceItemHeight, weekNumberPanelWidth, - isNeedDragAndDrop); - }, - onHorizontalDragEnd: isTimelineView - ? null - : (DragEndDetails dragEndDetails) { - _onHorizontalEnd( + isNeedDragAndDrop, + ); + }, + onHorizontalDragEnd: + isTimelineView + ? null + : (DragEndDetails dragEndDetails) { + _onHorizontalEnd( dragEndDetails, isResourceEnabled, isTimelineView, @@ -596,23 +637,27 @@ class _CustomCalendarScrollViewState extends State viewHeaderHeight, timeLabelWidth, weekNumberPanelWidth, - isNeedDragAndDrop); - }, - onVerticalDragStart: isHorizontalNavigation - ? null - : (DragStartDetails dragStartDetails) { - _onVerticalStart( + isNeedDragAndDrop, + ); + }, + onVerticalDragStart: + isHorizontalNavigation + ? null + : (DragStartDetails dragStartDetails) { + _onVerticalStart( dragStartDetails, isResourceEnabled, isTimelineView, viewHeaderHeight, timeLabelWidth, - isNeedDragAndDrop); - }, - onVerticalDragUpdate: isHorizontalNavigation - ? null - : (DragUpdateDetails dragUpdateDetails) { - _onVerticalUpdate( + isNeedDragAndDrop, + ); + }, + onVerticalDragUpdate: + isHorizontalNavigation + ? null + : (DragUpdateDetails dragUpdateDetails) { + _onVerticalUpdate( dragUpdateDetails, isResourceEnabled, isMonthView, @@ -621,12 +666,14 @@ class _CustomCalendarScrollViewState extends State timeLabelWidth, resourceItemHeight, weekNumberPanelWidth, - isNeedDragAndDrop); - }, - onVerticalDragEnd: isHorizontalNavigation - ? null - : (DragEndDetails dragEndDetails) { - _onVerticalEnd( + isNeedDragAndDrop, + ); + }, + onVerticalDragEnd: + isHorizontalNavigation + ? null + : (DragEndDetails dragEndDetails) { + _onVerticalEnd( dragEndDetails, isResourceEnabled, isTimelineView, @@ -634,18 +681,34 @@ class _CustomCalendarScrollViewState extends State viewHeaderHeight, timeLabelWidth, weekNumberPanelWidth, - isNeedDragAndDrop); - }, + isNeedDragAndDrop, + ); + }, + child: CustomScrollViewerLayout( + _addViews(), + isHorizontalNavigation + ? CustomScrollDirection.horizontal + : CustomScrollDirection.vertical, + _position, + _currentChildIndex, + ), ); return GestureDetector( onLongPressStart: (LongPressStartDetails details) { - _handleLongPressStart(details, isNeedDragAndDrop, isTimelineView, - isResourceEnabled, viewHeaderHeight, timeLabelWidth); + _handleLongPressStart( + details, + isNeedDragAndDrop, + isTimelineView, + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + ); }, - onLongPressMoveUpdate: isNeedDragAndDrop - ? (LongPressMoveUpdateDetails details) { - _handleLongPressMove( + onLongPressMoveUpdate: + isNeedDragAndDrop + ? (LongPressMoveUpdateDetails details) { + _handleLongPressMove( details.localPosition, isTimelineView, isResourceEnabled, @@ -653,109 +716,128 @@ class _CustomCalendarScrollViewState extends State viewHeaderHeight, timeLabelWidth, resourceItemHeight, - weekNumberPanelWidth); - } - : null, - onLongPressEnd: isNeedDragAndDrop - ? (LongPressEndDetails details) { - _handleLongPressEnd( + weekNumberPanelWidth, + ); + } + : null, + onLongPressEnd: + isNeedDragAndDrop + ? (LongPressEndDetails details) { + _handleLongPressEnd( details.localPosition, isTimelineView, isResourceEnabled, isMonthView, viewHeaderHeight, timeLabelWidth, - weekNumberPanelWidth); - } - : null, + weekNumberPanelWidth, + ); + } + : null, child: Stack( children: [ Positioned( - left: leftPosition, - right: rightPosition, - bottom: bottomPosition, - top: topPosition, - child: FocusScope( - node: _focusNode, - onKey: _onKeyDown, - child: isTimelineView - ? Listener( + left: leftPosition, + right: rightPosition, + bottom: bottomPosition, + top: topPosition, + child: FocusScope( + node: _focusNode, + onKeyEvent: _onKeyDown, + child: + isTimelineView + ? Listener( onPointerSignal: _handlePointerSignal, child: RawGestureDetector( - gestures: { - HorizontalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers< - HorizontalDragGestureRecognizer>( - () => HorizontalDragGestureRecognizer(), - (HorizontalDragGestureRecognizer instance) { - instance.onUpdate = - (DragUpdateDetails details) { + gestures: { + HorizontalDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + HorizontalDragGestureRecognizer + >(() => HorizontalDragGestureRecognizer(), ( + HorizontalDragGestureRecognizer instance, + ) { + instance.onUpdate = ( + DragUpdateDetails details, + ) { _handleDragUpdate( - details, - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - resourceItemHeight, - weekNumberPanelWidth, - isNeedDragAndDrop, - resourceViewSize); + details, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + isNeedDragAndDrop, + resourceViewSize, + ); }; - instance.onStart = - (DragStartDetails details) { + instance.onStart = ( + DragStartDetails details, + ) { _handleDragStart( - details, - isNeedDragAndDrop, - isTimelineView, - isResourceEnabled, - viewHeaderHeight, - timeLabelWidth, - resourceViewSize); + details, + isNeedDragAndDrop, + isTimelineView, + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + resourceViewSize, + ); }; instance.onEnd = (DragEndDetails details) { _handleDragEnd( - details, - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - weekNumberPanelWidth, - isNeedDragAndDrop); + details, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + isNeedDragAndDrop, + ); }; instance.onCancel = _handleDragCancel; - }, - ) - }, - behavior: HitTestBehavior.opaque, - child: customScrollWidget), + }), + }, + behavior: HitTestBehavior.opaque, + child: customScrollWidget, + ), ) - : customScrollWidget, - )), + : customScrollWidget, + ), + ), Positioned( - left: 0, - right: 0, - bottom: 0, - top: 0, - child: IgnorePointer( - child: RepaintBoundary( - child: _DraggingAppointmentWidget( - _dragDetails, - widget.isRTL, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.calendar.appointmentTextStyle, - widget.calendar.dragAndDropSettings, - widget.view, - _updateCalendarStateDetails.allDayPanelHeight, - viewHeaderHeight, - timeLabelWidth, - resourceItemHeight, - widget.calendarTheme, - widget.calendar, - widget.width, - widget.height)))) + left: 0, + right: 0, + bottom: 0, + top: 0, + child: IgnorePointer( + child: RepaintBoundary( + child: _DraggingAppointmentWidget( + _dragDetails, + widget.isRTL, + widget.textScaleFactor, + widget.isMobilePlatform, + AppointmentHelper.getAppointmentTextStyle( + widget.calendar.appointmentTextStyle, + widget.view, + widget.themeData, + ), + widget.calendar.dragAndDropSettings, + widget.view, + _updateCalendarStateDetails.allDayPanelHeight, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + widget.calendarTheme, + widget.calendar, + widget.width, + widget.height, + ), + ), + ), + ), ], ), ); @@ -770,45 +852,51 @@ class _CustomCalendarScrollViewState extends State } void _handleAppointmentDragStart( - AppointmentView appointmentView, - bool isTimelineView, - Offset details, - bool isResourceEnabled, - double viewHeaderHeight, - double timeLabelWidth) { + AppointmentView appointmentView, + bool isTimelineView, + Offset details, + bool isResourceEnabled, + double viewHeaderHeight, + double timeLabelWidth, + ) { final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; currentState._updateDraggingMouseCursor(true); _dragDetails.value.timeIntervalHeight = currentState._getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - currentState.widget.visibleDates.length, - currentState._allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + currentState.widget.visibleDates.length, + widget.isMobilePlatform, + ); _dragDetails.value.appointmentView = appointmentView; _dragDifferenceOffset = null; final Offset appointmentPosition = Offset( - widget.isRTL - ? appointmentView.appointmentRect!.right - : appointmentView.appointmentRect!.left, - appointmentView.appointmentRect!.top); + widget.isRTL + ? appointmentView.appointmentRect!.right + : appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + ); double xPosition; double yPosition; if (isTimelineView) { - xPosition = (appointmentPosition.dx - + xPosition = + (appointmentPosition.dx - currentState._scrollController!.position.pixels) - details.dx; if (widget.isRTL) { - xPosition = currentState._scrollController!.offset + + xPosition = + currentState._scrollController!.offset + currentState._scrollController!.position.viewportDimension; - xPosition = xPosition - + xPosition = + xPosition - ((currentState._scrollController!.position.viewportDimension + currentState._scrollController!.position.maxScrollExtent) - appointmentPosition.dx); xPosition -= details.dx; } - yPosition = appointmentPosition.dy + + yPosition = + appointmentPosition.dy + viewHeaderHeight + timeLabelWidth - details.dy; @@ -822,11 +910,13 @@ class _CustomCalendarScrollViewState extends State yPosition = yPosition - details.dy; _dragDifferenceOffset = Offset(xPosition, yPosition); } else { - final double allDayHeight = currentState._isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : currentState._allDayHeight; + final double allDayHeight = + currentState._isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : currentState._allDayHeight; xPosition = appointmentPosition.dx - details.dx; - yPosition = appointmentPosition.dy + + yPosition = + appointmentPosition.dy + viewHeaderHeight + allDayHeight - currentState._scrollController!.position.pixels; @@ -839,15 +929,18 @@ class _CustomCalendarScrollViewState extends State } CalendarResource? selectedResource; - int _selectedResourceIndex = -1; + int selectedResourceIndex = -1; if (isResourceEnabled) { yPosition = details.dy - viewHeaderHeight - timeLabelWidth; yPosition += currentState._timelineViewVerticalScrollController!.offset; - _selectedResourceIndex = currentState._getSelectedResourceIndex( - yPosition, viewHeaderHeight, timeLabelWidth); + selectedResourceIndex = currentState._getSelectedResourceIndex( + yPosition, + viewHeaderHeight, + timeLabelWidth, + ); selectedResource = - widget.calendar.dataSource!.resources![_selectedResourceIndex]; + widget.calendar.dataSource!.resources![selectedResourceIndex]; } _dragDetails.value.position.value = details + _dragDifferenceOffset!; _dragDetails.value.draggingTime = @@ -855,23 +948,29 @@ class _CustomCalendarScrollViewState extends State ? null : _dragDetails.value.appointmentView!.appointment!.actualStartTime; final dynamic dragStartAppointment = _getCalendarAppointmentToObject( - appointmentView.appointment, widget.calendar); + appointmentView.appointment, + widget.calendar, + ); if (widget.calendar.onDragStart != null) { widget.calendar.onDragStart!( - AppointmentDragStartDetails(dragStartAppointment, selectedResource)); + AppointmentDragStartDetails(dragStartAppointment, selectedResource), + ); } } void _handleLongPressStart( - LongPressStartDetails details, - bool isNeedDragAndDrop, - bool isTimelineView, - bool isResourceEnabled, - double viewHeaderHeight, - double timeLabelWidth) { + LongPressStartDetails details, + bool isNeedDragAndDrop, + bool isTimelineView, + bool isResourceEnabled, + double viewHeaderHeight, + double timeLabelWidth, + ) { final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; - AppointmentView? appointmentView = - _getDragAppointment(details, currentState); + AppointmentView? appointmentView = _getDragAppointment( + details, + currentState, + ); if (!isNeedDragAndDrop || appointmentView == null) { _dragDetails.value.position.value = null; return; @@ -879,19 +978,23 @@ class _CustomCalendarScrollViewState extends State currentState._removeAllWidgetHovering(); appointmentView = appointmentView.clone(); _handleAppointmentDragStart( - appointmentView, - isTimelineView, - details.localPosition, - isResourceEnabled, - viewHeaderHeight, - timeLabelWidth); + appointmentView, + isTimelineView, + details.localPosition, + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + ); } AppointmentView? _getDragAppointment( - LongPressStartDetails details, _CalendarViewState currentState) { + LongPressStartDetails details, + _CalendarViewState currentState, + ) { if (CalendarViewHelper.isTimelineView(widget.view)) { return currentState._handleTouchOnTimeline(null, details); } else if (widget.view == CalendarView.month) { + //// return null while the drag operation on mobile platform. return currentState._handleTouchOnMonthView(null, details); } @@ -899,98 +1002,105 @@ class _CustomCalendarScrollViewState extends State } void _handleLongPressMove( - Offset details, - bool isTimelineView, - bool isResourceEnabled, - bool isMonthView, - double viewHeaderHeight, - double timeLabelWidth, - double resourceItemHeight, - double weekNumberPanelWidth) { + Offset details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double resourceItemHeight, + double weekNumberPanelWidth, + ) { if (_dragDetails.value.appointmentView == null) { return; } final Offset appointmentPosition = details + _dragDifferenceOffset!; final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; - final double allDayHeight = currentState._isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : currentState._allDayHeight; + final double allDayHeight = + currentState._isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : currentState._allDayHeight; final double timeIntervalHeight = currentState._getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - currentState.widget.visibleDates.length, - currentState._allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + currentState.widget.visibleDates.length, + widget.isMobilePlatform, + ); if (isTimelineView) { _updateAutoScrollDragTimelineView( - currentState, - appointmentPosition, - viewHeaderHeight, - timeIntervalHeight, - resourceItemHeight, - isResourceEnabled, - details, - isMonthView, - allDayHeight, - isTimelineView, - timeLabelWidth, - weekNumberPanelWidth); + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + ); } else { _updateNavigationDayView( - currentState, - appointmentPosition, - viewHeaderHeight, - allDayHeight, - timeIntervalHeight, - timeLabelWidth, - isResourceEnabled, - isTimelineView, - isMonthView, - details, - weekNumberPanelWidth); - } - - _dragDetails.value.position.value = appointmentPosition; - _updateAppointmentDragUpdateCallback( - isTimelineView, + currentState, + appointmentPosition, viewHeaderHeight, - timeLabelWidth, allDayHeight, - appointmentPosition, - isMonthView, timeIntervalHeight, - currentState, - details, + timeLabelWidth, isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + isMonthView, + details, + weekNumberPanelWidth, + ); + } + + _dragDetails.value.position.value = appointmentPosition; + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); } Future _updateNavigationDayView( - _CalendarViewState currentState, - Offset appointmentPosition, - double viewHeaderHeight, - double allDayHeight, - double timeIntervalHeight, - double timeLabelWidth, - bool isResourceEnabled, - bool isTimelineView, - bool isMonthView, - Offset details, - double weekNumberPanelWidth) async { + _CalendarViewState currentState, + Offset appointmentPosition, + double viewHeaderHeight, + double allDayHeight, + double timeIntervalHeight, + double timeLabelWidth, + bool isResourceEnabled, + bool isTimelineView, + bool isMonthView, + Offset details, + double weekNumberPanelWidth, + ) async { if (_dragDetails.value.appointmentView == null) { return; } double navigationThresholdValue = 0; final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); if (isDayView) { navigationThresholdValue = _dragDetails.value.appointmentView!.appointmentRect!.width * 0.1; @@ -1003,8 +1113,8 @@ class _CustomCalendarScrollViewState extends State final bool isHorizontalNavigation = widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month; + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month; if (widget.calendar.dragAndDropSettings.allowScroll && widget.view != CalendarView.month && @@ -1018,10 +1128,10 @@ class _CustomCalendarScrollViewState extends State _dragDetails.value.position.value!.dy <= viewHeaderHeight + allDayHeight && currentState._scrollController!.position.pixels != 0) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = currentState._scrollController!.position.pixels - - timeIntervalHeight; + timeIntervalHeight; if (scrollPosition < 0) { scrollPosition = 0; } @@ -1032,30 +1142,31 @@ class _CustomCalendarScrollViewState extends State ); _updateAppointmentDragUpdateCallback( - isTimelineView, - viewHeaderHeight, - timeLabelWidth, - allDayHeight, - appointmentPosition, - isMonthView, - timeIntervalHeight, - currentState, - details, - isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dy <= viewHeaderHeight + allDayHeight && currentState._scrollController!.position.pixels != 0) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; @@ -1075,14 +1186,17 @@ class _CustomCalendarScrollViewState extends State if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dy + _dragDetails - .value.appointmentView!.appointmentRect!.height >= + .value + .appointmentView! + .appointmentRect! + .height >= widget.height && currentState._scrollController!.position.pixels != currentState._scrollController!.position.maxScrollExtent) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = currentState._scrollController!.position.pixels + - timeIntervalHeight; + timeIntervalHeight; if (scrollPosition > currentState._scrollController!.position.maxScrollExtent) { scrollPosition = @@ -1096,33 +1210,37 @@ class _CustomCalendarScrollViewState extends State ); _updateAppointmentDragUpdateCallback( - isTimelineView, - viewHeaderHeight, - timeLabelWidth, - allDayHeight, - appointmentPosition, - isMonthView, - timeIntervalHeight, - currentState, - details, - isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dy + _dragDetails - .value.appointmentView!.appointmentRect!.height >= + .value + .appointmentView! + .appointmentRect! + .height >= widget.height && currentState._scrollController!.position.pixels != currentState._scrollController!.position.maxScrollExtent) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; @@ -1131,41 +1249,53 @@ class _CustomCalendarScrollViewState extends State } else if (widget.calendar.dragAndDropSettings.allowNavigation && ((isHorizontalNavigation && (appointmentPosition.dx + - _dragDetails.value.appointmentView!.appointmentRect! + _dragDetails + .value + .appointmentView! + .appointmentRect! .width) - rtlValue >= widget.width) || (!isHorizontalNavigation && (appointmentPosition.dy + _dragDetails - .value.appointmentView!.appointmentRect!.height >= + .value + .appointmentView! + .appointmentRect! + .height >= widget.height)))) { if (_timer != null) { return; } - _timer = - Timer.periodic(widget.calendar.dragAndDropSettings.autoNavigateDelay, - (Timer timer) async { - if (_dragDetails.value.position.value != null && - ((isHorizontalNavigation && - (_dragDetails.value.position.value!.dx + - _dragDetails.value.appointmentView! - .appointmentRect!.width) - - rtlValue >= - widget.width + navigationThresholdValue) || - (!isHorizontalNavigation && - _dragDetails.value.position.value!.dy + - _dragDetails.value.appointmentView!.appointmentRect! - .height >= - widget.height))) { - if (widget.isRTL) { - _moveToPreviousViewWithAnimation(); - } else { - _moveToNextViewWithAnimation(); - } - currentState = _getCurrentViewByVisibleDates()!; - currentState._updateDraggingMouseCursor(true); - _updateAppointmentDragUpdateCallback( + _timer = Timer.periodic( + widget.calendar.dragAndDropSettings.autoNavigateDelay, + (Timer timer) async { + if (_dragDetails.value.position.value != null && + ((isHorizontalNavigation && + (_dragDetails.value.position.value!.dx + + _dragDetails + .value + .appointmentView! + .appointmentRect! + .width) - + rtlValue >= + widget.width + navigationThresholdValue) || + (!isHorizontalNavigation && + _dragDetails.value.position.value!.dy + + _dragDetails + .value + .appointmentView! + .appointmentRect! + .height >= + widget.height))) { + if (widget.isRTL) { + _moveToPreviousViewWithAnimation(); + } else { + _moveToNextViewWithAnimation(); + } + currentState = _getCurrentViewByVisibleDates()!; + currentState._updateDraggingMouseCursor(true); + _updateAppointmentDragUpdateCallback( isTimelineView, viewHeaderHeight, timeLabelWidth, @@ -1176,12 +1306,14 @@ class _CustomCalendarScrollViewState extends State currentState, details, isResourceEnabled, - weekNumberPanelWidth); - } else if (_timer != null) { - _timer!.cancel(); - _timer = null; - } - }); + weekNumberPanelWidth, + ); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }, + ); } else if (widget.calendar.dragAndDropSettings.allowNavigation && ((isHorizontalNavigation && (appointmentPosition.dx + navigationThresholdValue) - @@ -1210,17 +1342,18 @@ class _CustomCalendarScrollViewState extends State currentState = _getCurrentViewByVisibleDates()!; currentState._updateDraggingMouseCursor(true); _updateAppointmentDragUpdateCallback( - isTimelineView, - viewHeaderHeight, - timeLabelWidth, - allDayHeight, - appointmentPosition, - isMonthView, - timeIntervalHeight, - currentState, - details, - isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); } else if (_timer != null) { _timer!.cancel(); _timer = null; @@ -1230,18 +1363,19 @@ class _CustomCalendarScrollViewState extends State } Future _updateAutoScrollDragTimelineView( - _CalendarViewState currentState, - Offset appointmentPosition, - double viewHeaderHeight, - double timeIntervalHeight, - double resourceItemHeight, - bool isResourceEnabled, - Offset details, - bool isMonthView, - double allDayHeight, - bool isTimelineView, - double timeLabelWidth, - double weekNumberPanelWidth) async { + _CalendarViewState currentState, + Offset appointmentPosition, + double viewHeaderHeight, + double timeIntervalHeight, + double resourceItemHeight, + bool isResourceEnabled, + Offset details, + bool isMonthView, + double allDayHeight, + bool isTimelineView, + double timeLabelWidth, + double weekNumberPanelWidth, + ) async { if (_dragDetails.value.appointmentView == null) { return; } @@ -1266,15 +1400,18 @@ class _CustomCalendarScrollViewState extends State ((widget.isRTL && currentState._scrollController!.position.pixels != currentState - ._scrollController!.position.maxScrollExtent) || + ._scrollController! + .position + .maxScrollExtent) || (!widget.isRTL && currentState._scrollController!.position.pixels != 0))) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = currentState._scrollController!.position.pixels - - timeIntervalHeight; + timeIntervalHeight; if (widget.isRTL) { - scrollPosition = currentState._scrollController!.position.pixels + + scrollPosition = + currentState._scrollController!.position.pixels + timeIntervalHeight; } if (!widget.isRTL && scrollPosition < 0) { @@ -1292,72 +1429,80 @@ class _CustomCalendarScrollViewState extends State ); _updateAppointmentDragUpdateCallback( - isTimelineView, - viewHeaderHeight, - timeLabelWidth, - allDayHeight, - appointmentPosition, - isMonthView, - timeIntervalHeight, - currentState, - details, - isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dx - rtlValue <= 0 && ((widget.isRTL && currentState._scrollController!.position.pixels != currentState - ._scrollController!.position.maxScrollExtent) || + ._scrollController! + .position + .maxScrollExtent) || (!widget.isRTL && currentState._scrollController!.position.pixels != 0))) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; _updateAutoViewNavigationTimelineView( - currentState, - appointmentPosition, - viewHeaderHeight, - timeIntervalHeight, - resourceItemHeight, - isResourceEnabled, - details, - isMonthView, - allDayHeight, - isTimelineView, - timeLabelWidth, - weekNumberPanelWidth, - rtlValue); + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue, + ); } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; _updateAutoViewNavigationTimelineView( - currentState, - appointmentPosition, - viewHeaderHeight, - timeIntervalHeight, - resourceItemHeight, - isResourceEnabled, - details, - isMonthView, - allDayHeight, - isTimelineView, - timeLabelWidth, - weekNumberPanelWidth, - rtlValue); + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue, + ); } }); } else if (widget.calendar.dragAndDropSettings.allowScroll && (appointmentPosition.dx + _dragDetails - .value.appointmentView!.appointmentRect!.width) - + .value + .appointmentView! + .appointmentRect! + .width) - rtlValue >= widget.width && ((widget.isRTL && @@ -1365,7 +1510,9 @@ class _CustomCalendarScrollViewState extends State (!widget.isRTL && currentState._scrollController!.position.pixels != currentState - ._scrollController!.position.maxScrollExtent))) { + ._scrollController! + .position + .maxScrollExtent))) { if (_timer != null) { return; } @@ -1373,7 +1520,10 @@ class _CustomCalendarScrollViewState extends State if (_dragDetails.value.position.value != null && (_dragDetails.value.position.value!.dx + _dragDetails - .value.appointmentView!.appointmentRect!.width) - + .value + .appointmentView! + .appointmentRect! + .width) - rtlValue >= widget.width && ((widget.isRTL && @@ -1381,13 +1531,16 @@ class _CustomCalendarScrollViewState extends State (!widget.isRTL && currentState._scrollController!.position.pixels != currentState - ._scrollController!.position.maxScrollExtent))) { - Future _updateScrollPosition() async { + ._scrollController! + .position + .maxScrollExtent))) { + Future updateScrollPosition() async { double scrollPosition = currentState._scrollController!.position.pixels + - timeIntervalHeight; + timeIntervalHeight; if (widget.isRTL) { - scrollPosition = currentState._scrollController!.position.pixels - + scrollPosition = + currentState._scrollController!.position.pixels - timeIntervalHeight; } if (!widget.isRTL && @@ -1406,21 +1559,25 @@ class _CustomCalendarScrollViewState extends State ); _updateAppointmentDragUpdateCallback( - isTimelineView, - viewHeaderHeight, - timeLabelWidth, - allDayHeight, - appointmentPosition, - isMonthView, - timeIntervalHeight, - currentState, - details, - isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); if (_dragDetails.value.position.value != null && (_dragDetails.value.position.value!.dx + - _dragDetails.value.appointmentView!.appointmentRect! + _dragDetails + .value + .appointmentView! + .appointmentRect! .width) - rtlValue >= widget.width && @@ -1428,65 +1585,70 @@ class _CustomCalendarScrollViewState extends State currentState._scrollController!.position.pixels != 0) || (!widget.isRTL && currentState._scrollController!.position.pixels != - currentState._scrollController!.position + currentState + ._scrollController! + .position .maxScrollExtent))) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; _updateAutoViewNavigationTimelineView( - currentState, - appointmentPosition, - viewHeaderHeight, - timeIntervalHeight, - resourceItemHeight, - isResourceEnabled, - details, - isMonthView, - allDayHeight, - isTimelineView, - timeLabelWidth, - weekNumberPanelWidth, - rtlValue); + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue, + ); } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; _updateAutoViewNavigationTimelineView( - currentState, - appointmentPosition, - viewHeaderHeight, - timeIntervalHeight, - resourceItemHeight, - isResourceEnabled, - details, - isMonthView, - allDayHeight, - isTimelineView, - timeLabelWidth, - weekNumberPanelWidth, - rtlValue); + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue, + ); } }); } _updateAutoViewNavigationTimelineView( - currentState, - appointmentPosition, - viewHeaderHeight, - timeIntervalHeight, - resourceItemHeight, - isResourceEnabled, - details, - isMonthView, - allDayHeight, - isTimelineView, - timeLabelWidth, - weekNumberPanelWidth, - rtlValue); + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue, + ); if (_dragDetails.value.appointmentView == null) { return; @@ -1507,38 +1669,45 @@ class _CustomCalendarScrollViewState extends State timeIntervalHeight <= 0 && currentState - ._timelineViewVerticalScrollController!.position.pixels != + ._timelineViewVerticalScrollController! + .position + .pixels != 0) { - Future _updateScrollPosition() async { - double scrollPosition = currentState - ._timelineViewVerticalScrollController!.position.pixels - + Future updateScrollPosition() async { + double scrollPosition = + currentState + ._timelineViewVerticalScrollController! + .position + .pixels - resourceItemHeight; if (scrollPosition < 0) { scrollPosition = 0; } await currentState._timelineViewVerticalScrollController!.position .moveTo( - scrollPosition, - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - ); + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dy - viewHeaderHeight - timeIntervalHeight <= 0 && - currentState._timelineViewVerticalScrollController!.position + currentState + ._timelineViewVerticalScrollController! + .position .pixels != 0) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; @@ -1549,7 +1718,9 @@ class _CustomCalendarScrollViewState extends State _dragDetails.value.appointmentView!.appointmentRect!.height >= widget.height && currentState._timelineViewVerticalScrollController!.position.pixels != - currentState._timelineViewVerticalScrollController!.position + currentState + ._timelineViewVerticalScrollController! + .position .maxScrollExtent) { if (_timer != null) { return; @@ -1558,49 +1729,69 @@ class _CustomCalendarScrollViewState extends State if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dy + _dragDetails - .value.appointmentView!.appointmentRect!.height >= + .value + .appointmentView! + .appointmentRect! + .height >= widget.height && currentState - ._timelineViewVerticalScrollController!.position.pixels != - currentState._timelineViewVerticalScrollController!.position + ._timelineViewVerticalScrollController! + .position + .pixels != + currentState + ._timelineViewVerticalScrollController! + .position .maxScrollExtent) { - Future _updateScrollPosition() async { - double scrollPosition = currentState - ._timelineViewVerticalScrollController!.position.pixels + + Future updateScrollPosition() async { + double scrollPosition = + currentState + ._timelineViewVerticalScrollController! + .position + .pixels + resourceItemHeight; if (scrollPosition > - currentState._timelineViewVerticalScrollController!.position + currentState + ._timelineViewVerticalScrollController! + .position .maxScrollExtent) { - scrollPosition = currentState - ._timelineViewVerticalScrollController! - .position - .maxScrollExtent; + scrollPosition = + currentState + ._timelineViewVerticalScrollController! + .position + .maxScrollExtent; } await currentState._timelineViewVerticalScrollController!.position .moveTo( - scrollPosition, - duration: const Duration(milliseconds: 100), - curve: Curves.easeInOut, - ); + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); if (_dragDetails.value.position.value != null && _dragDetails.value.position.value!.dy + _dragDetails - .value.appointmentView!.appointmentRect!.height >= + .value + .appointmentView! + .appointmentRect! + .height >= widget.height && - currentState._timelineViewVerticalScrollController!.position + currentState + ._timelineViewVerticalScrollController! + .position .pixels != - currentState._timelineViewVerticalScrollController! - .position.maxScrollExtent) { - _updateScrollPosition(); + currentState + ._timelineViewVerticalScrollController! + .position + .maxScrollExtent) { + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_timer != null) { _timer!.cancel(); _timer = null; @@ -1611,23 +1802,27 @@ class _CustomCalendarScrollViewState extends State } void _updateAutoViewNavigationTimelineView( - _CalendarViewState currentState, - Offset appointmentPosition, - double viewHeaderHeight, - double timeIntervalHeight, - double resourceItemHeight, - bool isResourceEnabled, - dynamic details, - bool isMonthView, - double allDayHeight, - bool isTimelineView, - double timeLabelWidth, - double weekNumberPanelWidth, - double rtlValue) { + _CalendarViewState currentState, + Offset appointmentPosition, + double viewHeaderHeight, + double timeIntervalHeight, + double resourceItemHeight, + bool isResourceEnabled, + dynamic details, + bool isMonthView, + double allDayHeight, + bool isTimelineView, + double timeLabelWidth, + double weekNumberPanelWidth, + double rtlValue, + ) { if (widget.calendar.dragAndDropSettings.allowNavigation && (appointmentPosition.dx + _dragDetails - .value.appointmentView!.appointmentRect!.width) - + .value + .appointmentView! + .appointmentRect! + .width) - rtlValue >= widget.width && ((!widget.isRTL && @@ -1637,29 +1832,34 @@ class _CustomCalendarScrollViewState extends State if (_timer != null) { return; } - _timer = - Timer.periodic(widget.calendar.dragAndDropSettings.autoNavigateDelay, - (Timer timer) async { - if (_dragDetails.value.position.value != null && - (_dragDetails.value.position.value!.dx + - _dragDetails - .value.appointmentView!.appointmentRect!.width) - - rtlValue >= - widget.width && - ((!widget.isRTL && - currentState._scrollController!.offset == - currentState - ._scrollController!.position.maxScrollExtent) || - (widget.isRTL && - currentState._scrollController!.offset == 0))) { - if (widget.isRTL) { - _moveToPreviousViewWithAnimation(isScrollToEnd: true); - } else { - _moveToNextViewWithAnimation(); - } - currentState = _getCurrentViewByVisibleDates()!; - currentState._updateDraggingMouseCursor(true); - _updateAppointmentDragUpdateCallback( + _timer = Timer.periodic( + widget.calendar.dragAndDropSettings.autoNavigateDelay, + (Timer timer) async { + if (_dragDetails.value.position.value != null && + (_dragDetails.value.position.value!.dx + + _dragDetails + .value + .appointmentView! + .appointmentRect! + .width) - + rtlValue >= + widget.width && + ((!widget.isRTL && + currentState._scrollController!.offset == + currentState + ._scrollController! + .position + .maxScrollExtent) || + (widget.isRTL && + currentState._scrollController!.offset == 0))) { + if (widget.isRTL) { + _moveToPreviousViewWithAnimation(isScrollToEnd: true); + } else { + _moveToNextViewWithAnimation(); + } + currentState = _getCurrentViewByVisibleDates()!; + currentState._updateDraggingMouseCursor(true); + _updateAppointmentDragUpdateCallback( isTimelineView, viewHeaderHeight, timeLabelWidth, @@ -1670,12 +1870,14 @@ class _CustomCalendarScrollViewState extends State currentState, details, isResourceEnabled, - weekNumberPanelWidth); - } else if (_timer != null) { - _timer!.cancel(); - _timer = null; - } - }); + weekNumberPanelWidth, + ); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }, + ); } else if (widget.calendar.dragAndDropSettings.allowNavigation && ((appointmentPosition.dx) - rtlValue).truncate() <= 0 && ((widget.isRTL && @@ -1692,7 +1894,9 @@ class _CustomCalendarScrollViewState extends State ((widget.isRTL && currentState._scrollController!.position.pixels == currentState - ._scrollController!.position.maxScrollExtent) || + ._scrollController! + .position + .maxScrollExtent) || (!widget.isRTL && currentState._scrollController!.offset == 0))) { if (widget.isRTL) { @@ -1703,17 +1907,18 @@ class _CustomCalendarScrollViewState extends State currentState = _getCurrentViewByVisibleDates()!; currentState._updateDraggingMouseCursor(true); _updateAppointmentDragUpdateCallback( - isTimelineView, - viewHeaderHeight, - timeLabelWidth, - allDayHeight, - appointmentPosition, - isMonthView, - timeIntervalHeight, - currentState, - details, - isResourceEnabled, - weekNumberPanelWidth); + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth, + ); } else if (_timer != null) { _timer!.cancel(); _timer = null; @@ -1723,17 +1928,18 @@ class _CustomCalendarScrollViewState extends State } void _updateAppointmentDragUpdateCallback( - bool isTimelineView, - double viewHeaderHeight, - double timeLabelWidth, - double allDayHeight, - Offset appointmentPosition, - bool isMonthView, - double timeIntervalHeight, - _CalendarViewState currentState, - Offset details, - bool isResourceEnabled, - double weekNumberPanelWidth) { + bool isTimelineView, + double viewHeaderHeight, + double timeLabelWidth, + double allDayHeight, + Offset appointmentPosition, + bool isMonthView, + double timeIntervalHeight, + _CalendarViewState currentState, + Offset details, + bool isResourceEnabled, + double weekNumberPanelWidth, + ) { if (_dragDetails.value.appointmentView == null) { return; } @@ -1783,21 +1989,24 @@ class _CustomCalendarScrollViewState extends State xPosition = widget.width - 1; } - final double overAllWidth = isTimelineView - ? currentState._timeIntervalHeight * - (currentState._horizontalLinesCount! * - currentState.widget.visibleDates.length) - : widget.width; - final double overAllHeight = isTimelineView || isMonthView - ? widget.height - : currentState._timeIntervalHeight * - currentState._horizontalLinesCount!; + final double overAllWidth = + isTimelineView + ? currentState._timeIntervalHeight * + (currentState._horizontalLinesCount! * + currentState.widget.visibleDates.length) + : widget.width; + final double overAllHeight = + isTimelineView || isMonthView + ? widget.height + : currentState._timeIntervalHeight * + currentState._horizontalLinesCount!; if (isTimelineView && overAllWidth < widget.width && xPosition + _dragDetails.value.appointmentView!.appointmentRect!.width > overAllWidth) { - xPosition = overAllWidth - + xPosition = + overAllWidth - _dragDetails.value.appointmentView!.appointmentRect!.width; } else if (!isTimelineView && !isMonthView && @@ -1805,63 +2014,85 @@ class _CustomCalendarScrollViewState extends State yPosition + _dragDetails.value.appointmentView!.appointmentRect!.height > overAllHeight) { - yPosition = overAllHeight - + yPosition = + overAllHeight - _dragDetails.value.appointmentView!.appointmentRect!.height; } - draggingTime = currentState._getDateFromPosition( - xPosition, yPosition, timeLabelWidth)!; + draggingTime = + currentState._getDateFromPosition( + xPosition, + yPosition, + timeLabelWidth, + )!; if (!isMonthView) { if (isTimelineView) { - final DateTime time = _timeFromPosition( - draggingTime, - widget.calendar.timeSlotViewSettings, - xPosition, - currentState, - timeIntervalHeight, - isTimelineView)!; - - draggingTime = DateTime(draggingTime.year, draggingTime.month, - draggingTime.day, time.hour, time.minute); - } else { - if (yPosition < 0) { - draggingTime = DateTime( - draggingTime.year, draggingTime.month, draggingTime.day, 0, 0, 0); - } else { - draggingTime = _timeFromPosition( + final DateTime time = + _timeFromPosition( draggingTime, widget.calendar.timeSlotViewSettings, - yPosition, + xPosition, currentState, timeIntervalHeight, - isTimelineView)!; + isTimelineView, + )!; + + draggingTime = DateTime( + draggingTime.year, + draggingTime.month, + draggingTime.day, + time.hour, + time.minute, + ); + } else { + if (yPosition < 0) { + draggingTime = DateTime( + draggingTime.year, + draggingTime.month, + draggingTime.day, + ); + } else { + draggingTime = + _timeFromPosition( + draggingTime, + widget.calendar.timeSlotViewSettings, + yPosition, + currentState, + timeIntervalHeight, + isTimelineView, + )!; } } } _dragDetails.value.position.value = Offset( - _dragDetails.value.position.value!.dx, - _dragDetails.value.position.value!.dy - 0.1); + _dragDetails.value.position.value!.dx, + _dragDetails.value.position.value!.dy - 0.1, + ); _dragDetails.value.draggingTime = yPosition <= 0 && widget.view != CalendarView.month && !isTimelineView ? null : draggingTime; _dragDetails.value.position.value = Offset( - _dragDetails.value.position.value!.dx, - _dragDetails.value.position.value!.dy + 0.1); + _dragDetails.value.position.value!.dx, + _dragDetails.value.position.value!.dy + 0.1, + ); final dynamic draggingAppointment = _getCalendarAppointmentToObject( - _dragDetails.value.appointmentView!.appointment, widget.calendar); + _dragDetails.value.appointmentView!.appointment, + widget.calendar, + ); CalendarResource? selectedResource, previousResource; int targetResourceIndex = -1; int sourceSelectedResourceIndex = -1; if (isResourceEnabled) { targetResourceIndex = currentState._getSelectedResourceIndex( - appointmentPosition.dy + - currentState._timelineViewVerticalScrollController!.offset, - viewHeaderHeight, - timeLabelWidth); + appointmentPosition.dy + + currentState._timelineViewVerticalScrollController!.offset, + viewHeaderHeight, + timeLabelWidth, + ); if (targetResourceIndex > widget.calendar.dataSource!.resources!.length - 1) { targetResourceIndex = widget.calendar.dataSource!.resources!.length - 1; @@ -1869,48 +2100,57 @@ class _CustomCalendarScrollViewState extends State selectedResource = widget.calendar.dataSource!.resources![targetResourceIndex]; sourceSelectedResourceIndex = currentState._getSelectedResourceIndex( - _dragDetails.value.appointmentView!.appointmentRect!.top, - viewHeaderHeight, - timeLabelWidth); + _dragDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth, + ); previousResource = widget.calendar.dataSource!.resources![sourceSelectedResourceIndex]; } - final int currentMonth = currentState.widget - .visibleDates[currentState.widget.visibleDates.length ~/ 2].month; + final int currentMonth = + currentState + .widget + .visibleDates[currentState.widget.visibleDates.length ~/ 2] + .month; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); final DateTime updateStartTime = draggingTime; - final Duration appointmentDuration = _dragDetails - .value.appointmentView!.appointment!.isAllDay && - widget.view != CalendarView.month && - !isTimelineView - ? const Duration(hours: 1) - : _dragDetails.value.appointmentView!.appointment!.endTime.difference( - _dragDetails.value.appointmentView!.appointment!.startTime); + final Duration appointmentDuration = + _dragDetails.value.appointmentView!.appointment!.isAllDay && + widget.view != CalendarView.month && + !isTimelineView + ? const Duration(hours: 1) + : _dragDetails.value.appointmentView!.appointment!.endTime + .difference( + _dragDetails.value.appointmentView!.appointment!.startTime, + ); final DateTime updatedEndTime = updateStartTime.add(appointmentDuration); if (CalendarViewHelper.isDraggingAppointmentHasDisabledCell( - _getTimeRegions(), - _getBlackoutDates(), - updateStartTime, - updatedEndTime, - isTimelineView, - isMonthView, - widget.calendar.minDate, - widget.calendar.maxDate, - timeInterval, - targetResourceIndex, - widget.resourceCollection) || + _getTimeRegions(), + _getBlackoutDates(), + updateStartTime, + updatedEndTime, + isTimelineView, + isMonthView, + widget.calendar.minDate, + widget.calendar.maxDate, + timeInterval, + targetResourceIndex, + widget.resourceCollection, + ) || (widget.view == CalendarView.month && !CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - draggingTime))) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + draggingTime, + ))) { currentState._updateDisabledCellMouseCursor(true); } else { currentState._updateDisabledCellMouseCursor(false); @@ -1921,23 +2161,27 @@ class _CustomCalendarScrollViewState extends State } if (widget.calendar.onDragUpdate != null) { - widget.calendar.onDragUpdate!(AppointmentDragUpdateDetails( + widget.calendar.onDragUpdate!( + AppointmentDragUpdateDetails( draggingAppointment, previousResource, selectedResource, appointmentPosition, - _dragDetails.value.draggingTime)); + _dragDetails.value.draggingTime, + ), + ); } } void _handleLongPressEnd( - Offset details, - bool isTimelineView, - bool isResourceEnabled, - bool isMonthView, - double viewHeaderHeight, - double timeLabelWidth, - double weekNumberPanelWidth) { + Offset details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double weekNumberPanelWidth, + ) { if (_dragDetails.value.appointmentView == null) { return; } @@ -1949,17 +2193,18 @@ class _CustomCalendarScrollViewState extends State final Offset appointmentPosition = details + _dragDifferenceOffset!; final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; - final double allDayHeight = currentState._isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : currentState._allDayHeight; + final double allDayHeight = + currentState._isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : currentState._allDayHeight; final double timeIntervalHeight = currentState._getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - currentState.widget.visibleDates.length, - currentState._allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + currentState.widget.visibleDates.length, + widget.isMobilePlatform, + ); double xPosition = details.dx; double yPosition = appointmentPosition.dy; if (isTimelineView) { @@ -1998,21 +2243,24 @@ class _CustomCalendarScrollViewState extends State xPosition = widget.width - 1; } - final double overAllWidth = isTimelineView - ? currentState._timeIntervalHeight * - (currentState._horizontalLinesCount! * - currentState.widget.visibleDates.length) - : widget.width; - final double overAllHeight = isTimelineView || isMonthView - ? widget.height - : currentState._timeIntervalHeight * - currentState._horizontalLinesCount!; + final double overAllWidth = + isTimelineView + ? currentState._timeIntervalHeight * + (currentState._horizontalLinesCount! * + currentState.widget.visibleDates.length) + : widget.width; + final double overAllHeight = + isTimelineView || isMonthView + ? widget.height + : currentState._timeIntervalHeight * + currentState._horizontalLinesCount!; if (isTimelineView && overAllWidth < widget.width && xPosition + _dragDetails.value.appointmentView!.appointmentRect!.width > overAllWidth) { - xPosition = overAllWidth - + xPosition = + overAllWidth - _dragDetails.value.appointmentView!.appointmentRect!.width; } else if (!isTimelineView && !isMonthView && @@ -2020,33 +2268,45 @@ class _CustomCalendarScrollViewState extends State yPosition + _dragDetails.value.appointmentView!.appointmentRect!.height > overAllHeight) { - yPosition = overAllHeight - + yPosition = + overAllHeight - _dragDetails.value.appointmentView!.appointmentRect!.height; } final CalendarAppointment? appointment = _dragDetails.value.appointmentView!.appointment; - DateTime? dropTime = - currentState._getDateFromPosition(xPosition, yPosition, timeLabelWidth); + DateTime? dropTime = currentState._getDateFromPosition( + xPosition, + yPosition, + timeLabelWidth, + ); if (!isMonthView) { if (isTimelineView) { - final DateTime time = _timeFromPosition( - dropTime!, - widget.calendar.timeSlotViewSettings, - xPosition, - currentState, - timeIntervalHeight, - isTimelineView)!; - dropTime = DateTime(dropTime.year, dropTime.month, dropTime.day, - time.hour, time.minute); + final DateTime time = + _timeFromPosition( + dropTime!, + widget.calendar.timeSlotViewSettings, + xPosition, + currentState, + timeIntervalHeight, + isTimelineView, + )!; + dropTime = DateTime( + dropTime.year, + dropTime.month, + dropTime.day, + time.hour, + time.minute, + ); } else { dropTime = _timeFromPosition( - dropTime!, - widget.calendar.timeSlotViewSettings, - yPosition, - currentState, - timeIntervalHeight, - isTimelineView); + dropTime!, + widget.calendar.timeSlotViewSettings, + yPosition, + currentState, + timeIntervalHeight, + isTimelineView, + ); } } @@ -2055,10 +2315,11 @@ class _CustomCalendarScrollViewState extends State int sourceSelectedResourceIndex = -1; if (isResourceEnabled) { targetResourceIndex = currentState._getSelectedResourceIndex( - (details.dy - viewHeaderHeight - timeLabelWidth) + - currentState._timelineViewVerticalScrollController!.offset, - viewHeaderHeight, - timeLabelWidth); + (details.dy - viewHeaderHeight - timeLabelWidth) + + currentState._timelineViewVerticalScrollController!.offset, + viewHeaderHeight, + timeLabelWidth, + ); if (targetResourceIndex > widget.calendar.dataSource!.resources!.length - 1) { targetResourceIndex = widget.calendar.dataSource!.resources!.length - 1; @@ -2066,72 +2327,90 @@ class _CustomCalendarScrollViewState extends State selectedResource = widget.calendar.dataSource!.resources![targetResourceIndex]; sourceSelectedResourceIndex = currentState._getSelectedResourceIndex( - _dragDetails.value.appointmentView!.appointmentRect!.top, - viewHeaderHeight, - timeLabelWidth); + _dragDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth, + ); previousResource = widget.calendar.dataSource!.resources![sourceSelectedResourceIndex]; } - final int currentMonth = currentState.widget - .visibleDates[currentState.widget.visibleDates.length ~/ 2].month; + final int currentMonth = + currentState + .widget + .visibleDates[currentState.widget.visibleDates.length ~/ 2] + .month; - bool _isAllDay = appointment!.isAllDay; + bool isAllDay = appointment!.isAllDay; if (!isTimelineView && widget.view != CalendarView.month) { - _isAllDay = yPosition < 0; + isAllDay = yPosition < 0; if (_dragDetails.value.appointmentView!.appointment!.isSpanned && !appointment.isAllDay) { - _isAllDay = appointment.isAllDay; + isAllDay = appointment.isAllDay; } } else { - _isAllDay = appointment.isAllDay; + isAllDay = appointment.isAllDay; } - DateTime updateStartTime = _isAllDay - ? DateTime(dropTime!.year, dropTime.month, dropTime.day, 0, 0, 0) - : dropTime!; + DateTime updateStartTime = + isAllDay + ? DateTime(dropTime!.year, dropTime.month, dropTime.day) + : dropTime!; - final Duration appointmentDuration = appointment.isAllDay && - widget.view != CalendarView.month && - !isTimelineView - ? const Duration(hours: 1) - : appointment.endTime.difference(appointment.startTime); + final Duration appointmentDuration = + appointment.isAllDay && + widget.view != CalendarView.month && + !isTimelineView + ? const Duration(hours: 1) + : appointment.endTime.difference(appointment.startTime); DateTime updatedEndTime = - _isAllDay ? updateStartTime : updateStartTime.add(appointmentDuration); + isAllDay ? updateStartTime : updateStartTime.add(appointmentDuration); final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); final DateTime callbackStartDate = updateStartTime; updateStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - updateStartTime, widget.calendar.timeZone, appointment.startTimeZone); + updateStartTime, + widget.calendar.timeZone, + appointment.startTimeZone, + ); updatedEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - updatedEndTime, widget.calendar.timeZone, appointment.endTimeZone); + updatedEndTime, + widget.calendar.timeZone, + appointment.endTimeZone, + ); if (CalendarViewHelper.isDraggingAppointmentHasDisabledCell( - _getTimeRegions(), - _getBlackoutDates(), - updateStartTime, - updatedEndTime, - isTimelineView, - isMonthView, - widget.calendar.minDate, - widget.calendar.maxDate, - timeInterval, - targetResourceIndex, - widget.resourceCollection) || + _getTimeRegions(), + _getBlackoutDates(), + updateStartTime, + updatedEndTime, + isTimelineView, + isMonthView, + widget.calendar.minDate, + widget.calendar.maxDate, + timeInterval, + targetResourceIndex, + widget.resourceCollection, + ) || (widget.view == CalendarView.month && !CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - dropTime))) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + dropTime, + ))) { if (widget.calendar.onDragEnd != null) { - widget.calendar.onDragEnd!(AppointmentDragEndDetails( + widget.calendar.onDragEnd!( + AppointmentDragEndDetails( _getCalendarAppointmentToObject(appointment, widget.calendar), previousResource, previousResource, - appointment.exactStartTime)); + appointment.exactStartTime, + ), + ); } _resetDraggingDetails(currentState); return; @@ -2141,9 +2420,11 @@ class _CustomCalendarScrollViewState extends State if ((appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty) || appointment.recurrenceId != null) { - for (int i = 0; - i < _updateCalendarStateDetails.appointments.length; - i++) { + for ( + int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++ + ) { final CalendarAppointment app = _updateCalendarStateDetails.appointments[i]; if (app.id == appointment.id || app.id == appointment.recurrenceId) { @@ -2154,49 +2435,66 @@ class _CustomCalendarScrollViewState extends State final List recurrenceDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - parentAppointment!.recurrenceRule ?? '', + parentAppointment!.recurrenceRule ?? '', + parentAppointment.exactStartTime, + recurrenceDuration: AppointmentHelper.getDifference( parentAppointment.exactStartTime, - recurrenceDuration: AppointmentHelper.getDifference( - parentAppointment.exactStartTime, - parentAppointment.exactEndTime), - specificStartDate: currentState.widget.visibleDates[0], - specificEndDate: currentState.widget - .visibleDates[currentState.widget.visibleDates.length - 1]); - - for (int i = 0; - i < _updateCalendarStateDetails.appointments.length; - i++) { + parentAppointment.exactEndTime, + ), + specificStartDate: currentState.widget.visibleDates[0], + specificEndDate: + currentState + .widget + .visibleDates[currentState.widget.visibleDates.length - 1], + ); + + for ( + int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++ + ) { final CalendarAppointment calendarApp = _updateCalendarStateDetails.appointments[i]; if (calendarApp.recurrenceId != null && calendarApp.recurrenceId == parentAppointment.id) { recurrenceDates.add( - AppointmentHelper.convertTimeToAppointmentTimeZone( - calendarApp.startTime, - calendarApp.startTimeZone, - widget.calendar.timeZone)); + AppointmentHelper.convertTimeToAppointmentTimeZone( + calendarApp.startTime, + calendarApp.startTimeZone, + widget.calendar.timeZone, + ), + ); } } if (parentAppointment.recurrenceExceptionDates != null) { - for (int i = 0; - i < parentAppointment.recurrenceExceptionDates!.length; - i++) { + for ( + int i = 0; + i < parentAppointment.recurrenceExceptionDates!.length; + i++ + ) { recurrenceDates.remove( - AppointmentHelper.convertTimeToAppointmentTimeZone( - parentAppointment.recurrenceExceptionDates![i], - '', - widget.calendar.timeZone)); + AppointmentHelper.convertTimeToAppointmentTimeZone( + parentAppointment.recurrenceExceptionDates![i], + '', + widget.calendar.timeZone, + ), + ); } } recurrenceDates.sort(); - bool canAddRecurrence = - isSameDate(appointment.exactStartTime, callbackStartDate); + bool canAddRecurrence = isSameDate( + appointment.exactStartTime, + callbackStartDate, + ); if (!CalendarViewHelper.isDateInDateCollection( - recurrenceDates, callbackStartDate)) { - final int currentRecurrenceIndex = - recurrenceDates.indexOf(appointment.exactStartTime); + recurrenceDates, + callbackStartDate, + )) { + final int currentRecurrenceIndex = recurrenceDates.indexOf( + appointment.exactStartTime, + ); if (currentRecurrenceIndex == 0 || currentRecurrenceIndex == recurrenceDates.length - 1) { canAddRecurrence = true; @@ -2207,8 +2505,12 @@ class _CustomCalendarScrollViewState extends State recurrenceDates[currentRecurrenceIndex - 1]; final DateTime nextRecurrence = recurrenceDates[currentRecurrenceIndex + 1]; - canAddRecurrence = (isDateWithInDateRange( - previousRecurrence, nextRecurrence, callbackStartDate) && + canAddRecurrence = + (isDateWithInDateRange( + previousRecurrence, + nextRecurrence, + callbackStartDate, + ) && !isSameDate(previousRecurrence, callbackStartDate) && !isSameDate(nextRecurrence, callbackStartDate)) || canAddRecurrence; @@ -2217,11 +2519,14 @@ class _CustomCalendarScrollViewState extends State if (!canAddRecurrence) { if (widget.calendar.onDragEnd != null) { - widget.calendar.onDragEnd!(AppointmentDragEndDetails( + widget.calendar.onDragEnd!( + AppointmentDragEndDetails( _getCalendarAppointmentToObject(appointment, widget.calendar), previousResource, previousResource, - appointment.exactStartTime)); + appointment.exactStartTime, + ), + ); } _resetDraggingDetails(currentState); return; @@ -2232,41 +2537,55 @@ class _CustomCalendarScrollViewState extends State appointment.recurrenceRule!.isEmpty)) { widget.calendar.dataSource!.appointments!.remove(appointment.data); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [appointment.data]); + CalendarDataSourceAction.remove, + [appointment.data], + ); } else { - widget.calendar.dataSource!.appointments! - .remove(parentAppointment.data); + widget.calendar.dataSource!.appointments!.remove( + parentAppointment.data, + ); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [parentAppointment.data]); + CalendarDataSourceAction.remove, + [parentAppointment.data], + ); final DateTime exceptionDate = AppointmentHelper.convertTimeToAppointmentTimeZone( - appointment.exactStartTime, widget.calendar.timeZone, ''); + appointment.exactStartTime, + widget.calendar.timeZone, + '', + ); parentAppointment.recurrenceExceptionDates != null ? parentAppointment.recurrenceExceptionDates!.add(exceptionDate) : parentAppointment.recurrenceExceptionDates = [ - exceptionDate - ]; + exceptionDate, + ]; appointment.id = appointment.recurrenceId != null ? appointment.id : null; appointment.recurrenceId = appointment.recurrenceId ?? parentAppointment.id; appointment.recurrenceRule = null; - final dynamic newParentAppointment = - _getCalendarAppointmentToObject(parentAppointment, widget.calendar); + final dynamic newParentAppointment = _getCalendarAppointmentToObject( + parentAppointment, + widget.calendar, + ); widget.calendar.dataSource!.appointments!.add(newParentAppointment); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.add, [newParentAppointment]); + CalendarDataSourceAction.add, + [newParentAppointment], + ); } } else { widget.calendar.dataSource!.appointments!.remove(appointment.data); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [appointment.data]); + CalendarDataSourceAction.remove, + [appointment.data], + ); } appointment.startTime = updateStartTime; appointment.endTime = updatedEndTime; - appointment.isAllDay = _isAllDay; + appointment.isAllDay = isAllDay; if (isResourceEnabled) { if (appointment.resourceIds != null && appointment.resourceIds!.isNotEmpty) { @@ -2280,17 +2599,27 @@ class _CustomCalendarScrollViewState extends State } } - final dynamic newAppointment = - _getCalendarAppointmentToObject(appointment, widget.calendar); + final dynamic newAppointment = _getCalendarAppointmentToObject( + appointment, + widget.calendar, + ); widget.calendar.dataSource!.appointments!.add(newAppointment); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.add, [newAppointment]); + CalendarDataSourceAction.add, + [newAppointment], + ); _resetDraggingDetails(currentState); if (widget.calendar.onDragEnd != null) { - widget.calendar.onDragEnd!(AppointmentDragEndDetails(newAppointment, - previousResource, selectedResource, callbackStartDate)); + widget.calendar.onDragEnd!( + AppointmentDragEndDetails( + newAppointment, + previousResource, + selectedResource, + callbackStartDate, + ), + ); } } @@ -2363,13 +2692,14 @@ class _CustomCalendarScrollViewState extends State /// 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, - bool isNeedDragAndDrop, - bool isTimelineView, - bool isResourceEnabled, - double viewHeaderHeight, - double timeLabelWidth, - double resourceViewSize) { + DragStartDetails details, + bool isNeedDragAndDrop, + bool isTimelineView, + bool isResourceEnabled, + double viewHeaderHeight, + double timeLabelWidth, + double resourceViewSize, + ) { if (!CalendarViewHelper.isTimelineView(widget.view)) { return; } @@ -2378,13 +2708,16 @@ class _CustomCalendarScrollViewState extends State !widget.isMobilePlatform && isNeedDragAndDrop) { _handleAppointmentDragStart( - viewKey._hoveringAppointmentView!.clone(), - isTimelineView, - Offset(details.localPosition.dx - widget.width, - details.localPosition.dy), - isResourceEnabled, - viewHeaderHeight, - timeLabelWidth); + viewKey._hoveringAppointmentView!.clone(), + isTimelineView, + Offset( + details.localPosition.dx - widget.width, + details.localPosition.dy, + ), + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + ); return; } _timelineScrollStartPosition = viewKey._scrollController!.position.pixels; @@ -2414,16 +2747,17 @@ class _CustomCalendarScrollViewState extends State /// then pass the touch to custom scroll view and set the timeline view /// drag as null; void _handleDragUpdate( - DragUpdateDetails details, - bool isTimelineView, - bool isResourceEnabled, - bool isMonthView, - double viewHeaderHeight, - double timeLabelWidth, - double resourceItemHeight, - double weekNumberPanelWidth, - bool isNeedDragAndDrop, - double resourceViewSize) { + DragUpdateDetails details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double resourceItemHeight, + double weekNumberPanelWidth, + bool isNeedDragAndDrop, + double resourceViewSize, + ) { if (!CalendarViewHelper.isTimelineView(widget.view)) { return; } @@ -2433,15 +2767,18 @@ class _CustomCalendarScrollViewState extends State !widget.isMobilePlatform && isNeedDragAndDrop) { _handleLongPressMove( - Offset(details.localPosition.dx - widget.width, - details.localPosition.dy), - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - resourceItemHeight, - weekNumberPanelWidth); + Offset( + details.localPosition.dx - widget.width, + details.localPosition.dy, + ), + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + ); return; } @@ -2495,25 +2832,27 @@ class _CustomCalendarScrollViewState extends State /// Handle the scroll end to update the timeline view scroll or custom scroll /// view scroll based on [_isNeedTimelineScrollEnd] value void _handleDragEnd( - DragEndDetails details, - bool isTimelineView, - bool isResourceEnabled, - bool isMonthView, - double viewHeaderHeight, - double timeLabelWidth, - double weekNumberPanelWidth, - bool isNeedDragAndDrop) { + DragEndDetails details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double weekNumberPanelWidth, + bool isNeedDragAndDrop, + ) { if (_dragDetails.value.appointmentView != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleLongPressEnd( - _dragDetails.value.position.value! - _dragDifferenceOffset!, - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - weekNumberPanelWidth); + _dragDetails.value.position.value! - _dragDifferenceOffset!, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + ); return; } @@ -2543,13 +2882,30 @@ class _CustomCalendarScrollViewState extends State void _handlePointerSignal(PointerSignalEvent event) { final _CalendarViewState? viewKey = _getCurrentViewByVisibleDates(); if (event is PointerScrollEvent && viewKey != null) { - final double scrolledPosition = - widget.isRTL ? -event.scrollDelta.dy : event.scrollDelta.dy; + double scrolledPosition = + widget.isRTL ? -event.scrollDelta.dx : event.scrollDelta.dx; + + /// Check the scrolling is vertical and timeline view does not have + /// vertical scroll view then scroll the vertical movement on + /// Horizontal direction. + if (widget.height <= _viewPortHeight! && + event.scrollDelta.dy.abs() > event.scrollDelta.dx.abs() && + viewKey + ._timelineViewVerticalScrollController! + .position + .maxScrollExtent == + 0) { + scrolledPosition = + widget.isRTL ? -event.scrollDelta.dy : event.scrollDelta.dy; + } + final double targetScrollOffset = math.min( - math.max( - viewKey._scrollController!.position.pixels + scrolledPosition, - viewKey._scrollController!.position.minScrollExtent), - viewKey._scrollController!.position.maxScrollExtent); + math.max( + viewKey._scrollController!.position.pixels + scrolledPosition, + viewKey._scrollController!.position.minScrollExtent, + ), + viewKey._scrollController!.position.maxScrollExtent, + ); if (targetScrollOffset != viewKey._scrollController!.position.pixels) { viewKey._scrollController!.position.jumpTo(targetScrollOffset); } @@ -2558,51 +2914,67 @@ class _CustomCalendarScrollViewState extends State void _updateVisibleDates() { widget.getCalendarState(_updateCalendarStateDetails); - final List? nonWorkingDays = (widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek) - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null; + 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, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + nonWorkingDays, + ); final DateTime currentDate = DateTime( - _updateCalendarStateDetails.currentDate!.year, - _updateCalendarStateDetails.currentDate!.month, - _updateCalendarStateDetails.currentDate!.day); + _updateCalendarStateDetails.currentDate!.year, + _updateCalendarStateDetails.currentDate!.month, + _updateCalendarStateDetails.currentDate!.day, + ); final DateTime prevDate = DateTimeHelper.getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentDate, - visibleDatesCount, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentDate, + visibleDatesCount, + nonWorkingDays, + ); final DateTime nextDate = DateTimeHelper.getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentDate, - visibleDatesCount, - nonWorkingDays); - - _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(); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentDate, + visibleDatesCount, + nonWorkingDays, + ); + + _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); + _previousViewVisibleDates = DateTimeHelper.getCurrentMonthDates( + _previousViewVisibleDates, + ); + _nextViewVisibleDates = DateTimeHelper.getCurrentMonthDates( + _nextViewVisibleDates, + ); } _currentViewVisibleDates = _visibleDates; @@ -2625,41 +2997,50 @@ class _CustomCalendarScrollViewState extends State void _updateNextViewVisibleDates() { DateTime currentViewDate = _currentViewVisibleDates[0]; - final List? nonWorkingDays = (widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek) - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null; + 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, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + nonWorkingDays, + ); if (widget.view == CalendarView.month && widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; + currentViewDate = + _currentViewVisibleDates[(_currentViewVisibleDates.length / 2) + .truncate()]; } if (widget.isRTL) { currentViewDate = DateTimeHelper.getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate, - visibleDatesCount, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate, + visibleDatesCount, + nonWorkingDays, + ); } else { currentViewDate = DateTimeHelper.getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate, - visibleDatesCount, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate, + visibleDatesCount, + nonWorkingDays, + ); } - List dates = getVisibleDates(currentViewDate, nonWorkingDays, - widget.calendar.firstDayOfWeek, visibleDatesCount) - .cast(); + List dates = + getVisibleDates( + currentViewDate, + nonWorkingDays, + widget.calendar.firstDayOfWeek, + visibleDatesCount, + ).cast(); if (widget.view == CalendarView.timelineMonth) { dates = DateTimeHelper.getCurrentMonthDates(dates); @@ -2676,41 +3057,50 @@ class _CustomCalendarScrollViewState extends State void _updatePreviousViewVisibleDates() { DateTime currentViewDate = _currentViewVisibleDates[0]; - final List? nonWorkingDays = (widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek) - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null; + 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, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + nonWorkingDays, + ); if (widget.view == CalendarView.month && widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; + currentViewDate = + _currentViewVisibleDates[(_currentViewVisibleDates.length / 2) + .truncate()]; } if (widget.isRTL) { currentViewDate = DateTimeHelper.getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate, - visibleDatesCount, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate, + visibleDatesCount, + nonWorkingDays, + ); } else { currentViewDate = DateTimeHelper.getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate, - visibleDatesCount, - nonWorkingDays); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate, + visibleDatesCount, + nonWorkingDays, + ); } - List dates = getVisibleDates(currentViewDate, nonWorkingDays, - widget.calendar.firstDayOfWeek, visibleDatesCount) - .cast(); + List dates = + getVisibleDates( + currentViewDate, + nonWorkingDays, + widget.calendar.firstDayOfWeek, + visibleDatesCount, + ).cast(); if (widget.view == CalendarView.timelineMonth) { dates = DateTimeHelper.getCurrentMonthDates(dates); @@ -2769,31 +3159,48 @@ class _CustomCalendarScrollViewState extends State return regionCollection; } - final DateTime startDate = - AppointmentHelper.convertToStartTime(visibleStartDate); + 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); + 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.endTime, + region.timeZone, + widget.calendar.timeZone, + ); region.data = timeRegion; if (region.recurrenceRule == null || region.recurrenceRule == '') { if (AppointmentHelper.isDateRangeWithinVisibleDateRange( - region.actualStartTime, region.actualEndTime, startDate, endDate)) { + region.actualStartTime, + region.actualEndTime, + startDate, + endDate, + )) { regionCollection.add(region); } continue; } - getRecurrenceRegions(region, regionCollection, startDate, endDate, - widget.calendar.timeZone); + getRecurrenceRegions( + region, + regionCollection, + startDate, + endDate, + widget.calendar.timeZone, + ); } return regionCollection; @@ -2801,11 +3208,12 @@ class _CustomCalendarScrollViewState extends State /// Get the recurrence time regions in between the visible date range. void getRecurrenceRegions( - CalendarTimeRegion region, - List regions, - DateTime visibleStartDate, - DateTime visibleEndDate, - String? calendarTimeZone) { + CalendarTimeRegion region, + List regions, + DateTime visibleStartDate, + DateTime visibleEndDate, + String? calendarTimeZone, + ) { final DateTime regionStartDate = region.actualStartTime; if (regionStartDate.isAfter(visibleEndDate)) { return; @@ -2820,11 +3228,15 @@ class _CustomCalendarScrollViewState extends State final List recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - rule, region.actualStartTime, - recurrenceDuration: AppointmentHelper.getDifference( - region.actualStartTime, region.actualEndTime), - specificStartDate: visibleStartDate, - specificEndDate: visibleEndDate); + rule, + region.actualStartTime, + recurrenceDuration: AppointmentHelper.getDifference( + region.actualStartTime, + region.actualEndTime, + ), + specificStartDate: visibleStartDate, + specificEndDate: visibleEndDate, + ); for (int j = 0; j < recursiveDates.length; j++) { final DateTime recursiveDate = recursiveDates[j]; @@ -2833,7 +3245,10 @@ class _CustomCalendarScrollViewState extends State for (int i = 0; i < region.recurrenceExceptionDates!.length; i++) { final DateTime date = AppointmentHelper.convertTimeToAppointmentTimeZone( - region.recurrenceExceptionDates![i], '', calendarTimeZone); + region.recurrenceExceptionDates![i], + '', + calendarTimeZone, + ); if (isSameDate(date, recursiveDate)) { isDateContains = true; break; @@ -2844,29 +3259,46 @@ class _CustomCalendarScrollViewState extends State } } - final CalendarTimeRegion occurrenceRegion = - cloneRecurrenceRegion(region, recursiveDate, calendarTimeZone); + final CalendarTimeRegion occurrenceRegion = cloneRecurrenceRegion( + region, + recursiveDate, + calendarTimeZone, + ); regions.add(occurrenceRegion); } } /// Used to clone the time region with new values. - CalendarTimeRegion cloneRecurrenceRegion(CalendarTimeRegion region, - DateTime recursiveDate, String? calendarTimeZone) { - final int minutes = AppointmentHelper.getDifference( - region.actualStartTime, region.actualEndTime) - .inMinutes; + CalendarTimeRegion cloneRecurrenceRegion( + CalendarTimeRegion region, + DateTime recursiveDate, + String? calendarTimeZone, + ) { + final int minutes = + AppointmentHelper.getDifference( + region.actualStartTime, + region.actualEndTime, + ).inMinutes; final DateTime actualEndTime = DateTimeHelper.getDateTimeValue( - addDuration(recursiveDate, Duration(minutes: minutes))); + addDuration(recursiveDate, Duration(minutes: minutes)), + ); final DateTime startDate = AppointmentHelper.convertTimeToAppointmentTimeZone( - recursiveDate, calendarTimeZone, region.timeZone); + recursiveDate, + calendarTimeZone, + region.timeZone, + ); final DateTime endDate = AppointmentHelper.convertTimeToAppointmentTimeZone( - actualEndTime, calendarTimeZone, region.timeZone); + actualEndTime, + calendarTimeZone, + region.timeZone, + ); - final TimeRegion occurrenceTimeRegion = - region.data.copyWith(startTime: startDate, endTime: endDate); + final TimeRegion occurrenceTimeRegion = region.data.copyWith( + startTime: startDate, + endTime: endDate, + ); final CalendarTimeRegion occurrenceRegion = _getCalendarTimeRegionFromTimeRegion(occurrenceTimeRegion); occurrenceRegion.actualStartTime = recursiveDate; @@ -2877,7 +3309,9 @@ class _CustomCalendarScrollViewState extends State /// Return date collection which falls between the visible date range. List _getDatesWithInVisibleDateRange( - List? dates, List visibleDates) { + List? dates, + List visibleDates, + ) { final List visibleMonthDates = []; if (dates == null) { return visibleMonthDates; @@ -2890,17 +3324,22 @@ class _CustomCalendarScrollViewState extends State for (int i = 0; i < datesCount; i++) { final DateTime currentDate = dates[i]; if (!isDateWithInDateRange( - visibleStartDate, visibleEndDate, currentDate)) { + visibleStartDate, + visibleEndDate, + currentDate, + )) { continue; } if (dateCollection.keys.contains( - currentDate.day.toString() + currentDate.month.toString())) { + currentDate.day.toString() + currentDate.month.toString(), + )) { continue; } dateCollection[currentDate.day.toString() + - currentDate.month.toString()] = currentDate; + currentDate.month.toString()] = + currentDate; visibleMonthDates.add(currentDate); } @@ -2918,9 +3357,12 @@ class _CustomCalendarScrollViewState extends State widget.agendaSelectedDate, widget.locale, widget.calendarTheme, + widget.themeData, _getRegions(_previousViewVisibleDates), _getDatesWithInVisibleDateRange( - widget.blackoutDates, _previousViewVisibleDates), + widget.blackoutDates, + _previousViewVisibleDates, + ), _focusNode, widget.removePicker, widget.calendar.allowViewNavigation, @@ -2951,6 +3393,7 @@ class _CustomCalendarScrollViewState extends State widget.agendaSelectedDate, widget.locale, widget.calendarTheme, + widget.themeData, _getRegions(_visibleDates), _getDatesWithInVisibleDateRange(widget.blackoutDates, _visibleDates), _focusNode, @@ -2983,9 +3426,12 @@ class _CustomCalendarScrollViewState extends State widget.agendaSelectedDate, widget.locale, widget.calendarTheme, + widget.themeData, _getRegions(_nextViewVisibleDates), _getDatesWithInVisibleDateRange( - widget.blackoutDates, _nextViewVisibleDates), + widget.blackoutDates, + _nextViewVisibleDates, + ), _focusNode, widget.removePicker, widget.calendar.allowViewNavigation, @@ -3016,11 +3462,20 @@ class _CustomCalendarScrollViewState extends State widget.getCalendarState(_updateCalendarStateDetails); final _CalendarView previousView = _updateViews( - _previousView, _previousViewKey, _previousViewVisibleDates); - final _CalendarView currentView = - _updateViews(_currentView, _currentViewKey, _visibleDates); - final _CalendarView nextView = - _updateViews(_nextView, _nextViewKey, _nextViewVisibleDates); + _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 @@ -3038,8 +3493,11 @@ class _CustomCalendarScrollViewState extends State } // method to check and update the views and appointments on the swiping end - _CalendarView _updateViews(_CalendarView view, - GlobalKey<_CalendarViewState> viewKey, List visibleDates) { + _CalendarView _updateViews( + _CalendarView view, + GlobalKey<_CalendarViewState> viewKey, + List visibleDates, + ) { final int index = _children.indexOf(view); final AppointmentLayout appointmentLayout = @@ -3055,6 +3513,7 @@ class _CustomCalendarScrollViewState extends State widget.agendaSelectedDate, widget.locale, widget.calendarTheme, + widget.themeData, _getRegions(visibleDates), _getDatesWithInVisibleDateRange(widget.blackoutDates, visibleDates), _focusNode, @@ -3080,10 +3539,12 @@ class _CustomCalendarScrollViewState extends State ); _children[index] = view; - } // check and update the visible appointments in the view + } + // check and update the visible appointments in the view else if (!CalendarViewHelper.isCollectionEqual( - appointmentLayout.visibleAppointments.value, - _updateCalendarStateDetails.visibleAppointments)) { + appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails.visibleAppointments, + )) { if (widget.view != CalendarView.month && !CalendarViewHelper.isTimelineView(widget.view)) { view = _CalendarView( @@ -3095,6 +3556,44 @@ class _CustomCalendarScrollViewState extends State widget.agendaSelectedDate, widget.locale, widget.calendarTheme, + widget.themeData, + 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, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + _children[index] = view; + } else if (view.calendar != widget.calendar) { + /// Update the calendar view when calendar properties like appointment + /// text style dynamically changed. + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + widget.themeData, view.regions, view.blackoutDates, _focusNode, @@ -3118,17 +3617,20 @@ class _CustomCalendarScrollViewState extends State }, 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))) { + (!_updateCalendarStateDetails.visibleAppointments.contains( + viewKey + .currentState! + ._selectionPainter! + .appointmentView! + .appointment, + ))) { viewKey.currentState!._selectionPainter!.appointmentView = null; viewKey.currentState!._selectionPainter!.repaintNotifier.value = !viewKey.currentState!._selectionPainter!.repaintNotifier.value; @@ -3158,6 +3660,43 @@ class _CustomCalendarScrollViewState extends State widget.agendaSelectedDate, widget.locale, widget.calendarTheme, + widget.themeData, + 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, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + + _children[index] = view; + } else if (timeZoneLoaded || widget.calendar.timeZone != '') { + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + widget.themeData, view.regions, view.blackoutDates, _focusNode, @@ -3196,7 +3735,9 @@ class _CustomCalendarScrollViewState extends State /// Check both the region collection as equal or not. bool _isTimeRegionsEquals( - List? regions1, List? regions2) { + List? regions1, + List? regions2, + ) { /// Check both instance as equal /// eg., if both are null then its equal. if (regions1 == regions2) { @@ -3232,7 +3773,9 @@ class _CustomCalendarScrollViewState extends State // ignore: avoid_as _children[i].key! as GlobalKey<_CalendarViewState>; if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { viewKey.currentState!._selectedResourceIndex = 0; viewKey.currentState!._selectionPainter!.selectedResourceIndex = 0; } else { @@ -3256,7 +3799,9 @@ class _CustomCalendarScrollViewState extends State final Object selectedResourceId = widget.resourceCollection![selectedResourceIndex].id; final int newIndex = CalendarViewHelper.getResourceIndex( - widget.calendar.dataSource?.resources, selectedResourceId); + widget.calendar.dataSource?.resources, + selectedResourceId, + ); viewKey.currentState!._selectedResourceIndex = newIndex; } } @@ -3292,7 +3837,7 @@ class _CustomCalendarScrollViewState extends State return; } - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { if (_currentChildIndex == 0) { _previousViewKey.currentState?._scrollToPosition(); } else if (_currentChildIndex == 1) { @@ -3341,8 +3886,10 @@ class _CustomCalendarScrollViewState extends State widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { final DateTime currentMonthDate = _currentViewVisibleDates[_currentViewVisibleDates.length ~/ 2]; - _updateCalendarStateDetails.currentDate = - DateTime(currentMonthDate.year, currentMonthDate.month, 01); + _updateCalendarStateDetails.currentDate = DateTime( + currentMonthDate.year, + currentMonthDate.month, + ); } else { _updateCalendarStateDetails.currentDate = _currentViewVisibleDates[0]; } @@ -3378,9 +3925,12 @@ class _CustomCalendarScrollViewState extends State } else if (_currentChildIndex == 2) { _currentChildIndex = 0; } + + // resets position to zero on the swipe end to avoid the + // unwanted date updates. + _position = 0; }); - _resetPosition(); _updateAppointmentPainter(); } @@ -3412,9 +3962,12 @@ class _CustomCalendarScrollViewState extends State } else if (_currentChildIndex == 2) { _currentChildIndex = 1; } + + // resets position to zero on the swipe end to avoid the + // unwanted date updates. + _position = 0; }); - _resetPosition(); _updateAppointmentPainter(); } @@ -3425,13 +3978,14 @@ class _CustomCalendarScrollViewState extends State } if (!DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + )) { return; } @@ -3462,9 +4016,9 @@ class _CustomCalendarScrollViewState extends State } _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _animationController.forward().then( + (dynamic value) => _updateNextView(), + ); /// updates the current view visible dates when the view swiped _updateCurrentViewVisibleDates(isNextView: true); @@ -3477,13 +4031,14 @@ class _CustomCalendarScrollViewState extends State } if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + )) { return; } @@ -3514,9 +4069,9 @@ class _CustomCalendarScrollViewState extends State } _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + _animationController.forward().then( + (dynamic value) => _updatePreviousView(), + ); /// updates the current view visible dates when the view swiped. _updateCurrentViewVisibleDates(); @@ -3524,13 +4079,14 @@ class _CustomCalendarScrollViewState extends State void _moveToPreviousWebViewWithAnimation({bool isScrollToEnd = false}) { if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + )) { return; } @@ -3577,13 +4133,14 @@ class _CustomCalendarScrollViewState extends State void _moveToNextWebViewWithAnimation() { if (!DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + )) { return; } @@ -3629,17 +4186,8 @@ class _CustomCalendarScrollViewState extends State _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((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { if (_previousViewKey.currentState == null || _currentViewKey.currentState == null || _nextViewKey.currentState == null || @@ -3672,16 +4220,23 @@ class _CustomCalendarScrollViewState extends State if (_previousViewKey.currentState!._scrollController!.offset != scrolledPosition && _previousViewKey - .currentState!._scrollController!.position.maxScrollExtent >= + .currentState! + ._scrollController! + .position + .maxScrollExtent >= scrolledPosition) { - _previousViewKey.currentState!._scrollController! - .jumpTo(scrolledPosition); + _previousViewKey.currentState!._scrollController!.jumpTo( + scrolledPosition, + ); } if (_currentViewKey.currentState!._scrollController!.offset != scrolledPosition && _currentViewKey - .currentState!._scrollController!.position.maxScrollExtent >= + .currentState! + ._scrollController! + .position + .maxScrollExtent >= scrolledPosition) { _currentViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); } @@ -3689,7 +4244,10 @@ class _CustomCalendarScrollViewState extends State if (_nextViewKey.currentState!._scrollController!.offset != scrolledPosition && _nextViewKey - .currentState!._scrollController!.position.maxScrollExtent >= + .currentState! + ._scrollController! + .position + .maxScrollExtent >= scrolledPosition) { _nextViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); } @@ -3718,67 +4276,95 @@ class _CustomCalendarScrollViewState extends State return -1; } - DateTime _updateSelectedDateForRightArrow(_CalendarView currentView, - _CalendarViewState currentViewState, DateTime? selectedDate) { + 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], selectedDate)) { + currentView.visibleDates[visibleDatesCount - 1], + selectedDate, + )) { _moveToNextViewWithAnimation(); } - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate!, + 1, + selectedDate.hour, + selectedDate.minute, + 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)) { + 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); + 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, selectedDate!, - currentViewState._timeIntervalHeight); - final int rowIndex = - _getRowOfDate(currentView.visibleDates, selectedDate!); - final double singleChildWidth = - _getSingleViewWidthForTimeLineView(currentViewState); + final double xPosition = + widget.view == CalendarView.timelineMonth + ? 0 + : AppointmentHelper.timeToPosition( + widget.calendar, + selectedDate!, + currentViewState._timeIntervalHeight, + ); + final int rowIndex = _getRowOfDate( + currentView.visibleDates, + selectedDate!, + ); + final double singleChildWidth = _getSingleViewWidthForTimeLineView( + currentViewState, + ); if ((rowIndex * singleChildWidth) + xPosition + currentViewState._timeIntervalHeight >= currentViewState._scrollController!.offset + widget.width) { currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.offset + - currentViewState._timeIntervalHeight); + currentViewState._scrollController!.offset + + currentViewState._timeIntervalHeight, + ); } if (widget.view == CalendarView.timelineDay && selectedDate .add(widget.calendar.timeSlotViewSettings.timeInterval) .day != currentView - .visibleDates[currentView.visibleDates.length - 1].day) { + .visibleDates[currentView.visibleDates.length - 1] + .day) { _moveToNextViewWithAnimation(); } @@ -3788,7 +4374,7 @@ class _CustomCalendarScrollViewState extends State currentViewState._scrollController!.position.maxScrollExtent + currentViewState._scrollController!.position.viewportDimension) { _moveToNextViewWithAnimation(); - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { _moveToSelectedTimeSlot(); }); } @@ -3798,22 +4384,36 @@ class _CustomCalendarScrollViewState extends State /// 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(selectedDate, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate, + 1, + selectedDate.hour, + selectedDate.minute, + selectedDate.second, + ); } else { - selectedDate = - selectedDate.add(widget.calendar.timeSlotViewSettings.timeInterval); + selectedDate = 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); + 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; } @@ -3824,53 +4424,78 @@ class _CustomCalendarScrollViewState extends State return selectedDate!; } - DateTime _updateSelectedDateForLeftArrow(_CalendarView currentView, - _CalendarViewState currentViewState, DateTime? selectedDate) { + DateTime _updateSelectedDateForLeftArrow( + _CalendarView currentView, + _CalendarViewState currentViewState, + DateTime? selectedDate, + ) { if (!CalendarViewHelper.isTimelineView(widget.view)) { if (isSameDate(currentViewState.widget.visibleDates[0], selectedDate)) { _moveToPreviousViewWithAnimation(); } - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate!, + -1, + selectedDate.hour, + selectedDate.minute, + 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)) { + 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); + 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, selectedDate!, - currentViewState._timeIntervalHeight); - final int rowIndex = - _getRowOfDate(currentView.visibleDates, selectedDate!); - final double singleChildWidth = - _getSingleViewWidthForTimeLineView(currentViewState); + final double xPosition = + widget.view == CalendarView.timelineMonth + ? 0 + : AppointmentHelper.timeToPosition( + widget.calendar, + selectedDate!, + currentViewState._timeIntervalHeight, + ); + final int rowIndex = _getRowOfDate( + currentView.visibleDates, + selectedDate!, + ); + final double singleChildWidth = _getSingleViewWidthForTimeLineView( + currentViewState, + ); if ((rowIndex * singleChildWidth) + xPosition == 0) { _moveToPreviousViewWithAnimation(isScrollToEnd: true); - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { _moveToSelectedTimeSlot(); }); } @@ -3878,8 +4503,9 @@ class _CustomCalendarScrollViewState extends State if ((rowIndex * singleChildWidth) + xPosition <= currentViewState._scrollController!.offset) { currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.offset - - currentViewState._timeIntervalHeight); + currentViewState._scrollController!.offset - + currentViewState._timeIntervalHeight, + ); } /// For timeline month view each column represents a single day, and for @@ -3888,22 +4514,36 @@ class _CustomCalendarScrollViewState extends State /// a day and for other timeline views we must subtract the given time /// interval. if (widget.view == CalendarView.timelineMonth) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate, + -1, + selectedDate.hour, + selectedDate.minute, + selectedDate.second, + ); } else { - selectedDate = selectedDate - .subtract(widget.calendar.timeSlotViewSettings.timeInterval); + selectedDate = 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); + 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; } @@ -3915,65 +4555,88 @@ class _CustomCalendarScrollViewState extends State } DateTime? _updateSelectedDateForUpArrow( - _CalendarView currentView, - _CalendarViewState currentViewState, - DateTime? selectedDate, - DateTime? appointmentSelectedDate) { + _CalendarView currentView, + _CalendarViewState currentViewState, + DateTime? selectedDate, + DateTime? appointmentSelectedDate, + ) { if (widget.view == CalendarView.month) { - final int rowIndex = - _getRowOfDate(currentView.visibleDates, selectedDate!); + final int rowIndex = _getRowOfDate( + currentView.visibleDates, + selectedDate!, + ); if (rowIndex == 0) { return selectedDate; } selectedDate = AppointmentHelper.addDaysWithTime( - selectedDate, - -DateTime.daysPerWeek, - selectedDate.hour, - selectedDate.minute, - selectedDate.second); + selectedDate, + -DateTime.daysPerWeek, + selectedDate.hour, + selectedDate.minute, + 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)) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate, + )) { selectedDate = AppointmentHelper.getMonthStartDate( - currentViewState._selectionPainter!.selectedDate ?? - appointmentSelectedDate!); + currentViewState._selectionPainter!.selectedDate ?? + appointmentSelectedDate!, + ); if (CalendarViewHelper.isDateInDateCollection( - currentView.blackoutDates, selectedDate)) { + currentView.blackoutDates, + selectedDate, + )) { do { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate!, + 1, + selectedDate.hour, + selectedDate.minute, + selectedDate.second, + ); } while (CalendarViewHelper.isDateInDateCollection( - currentView.blackoutDates, selectedDate)); + currentView.blackoutDates, + selectedDate, + )); } } return selectedDate; } else if (!CalendarViewHelper.isTimelineView(widget.view)) { final double yPosition = AppointmentHelper.timeToPosition( - widget.calendar, selectedDate!, currentViewState._timeIntervalHeight); + widget.calendar, + selectedDate!, + currentViewState._timeIntervalHeight, + ); if (yPosition < 1) { return selectedDate; } if (yPosition - 1 <= currentViewState._scrollController!.offset) { - currentViewState._scrollController! - .jumpTo(yPosition - currentViewState._timeIntervalHeight); + currentViewState._scrollController!.jumpTo( + yPosition - currentViewState._timeIntervalHeight, + ); } - return selectedDate - .subtract(widget.calendar.timeSlotViewSettings.timeInterval); + return selectedDate.subtract( + widget.calendar.timeSlotViewSettings.timeInterval, + ); } else if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { final double resourceItemHeight = CalendarViewHelper.getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource!.resources!.length); + widget.calendar.resourceViewSettings.size, + widget.height, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length, + ); currentViewState._selectedResourceIndex -= 1; @@ -3986,10 +4649,11 @@ class _CustomCalendarScrollViewState extends State currentViewState._timelineViewVerticalScrollController!.offset) { double scrollPosition = currentViewState._timelineViewVerticalScrollController!.offset - - resourceItemHeight; + resourceItemHeight; scrollPosition = scrollPosition > 0 ? scrollPosition : 0; - currentViewState._timelineViewVerticalScrollController! - .jumpTo(scrollPosition); + currentViewState._timelineViewVerticalScrollController!.jumpTo( + scrollPosition, + ); } return selectedDate; @@ -3999,50 +4663,70 @@ class _CustomCalendarScrollViewState extends State } DateTime? _updateSelectedDateForDownArrow( - _CalendarView currentView, - _CalendarViewState currentViewState, - DateTime? selectedDate, - DateTime? selectedAppointmentDate) { + _CalendarView currentView, + _CalendarViewState currentViewState, + DateTime? selectedDate, + DateTime? selectedAppointmentDate, + ) { if (widget.view == CalendarView.month) { - final int rowIndex = - _getRowOfDate(currentView.visibleDates, selectedDate!); + final int rowIndex = _getRowOfDate( + currentView.visibleDates, + selectedDate!, + ); if (rowIndex == widget.calendar.monthViewSettings.numberOfWeeksInView - 1) { return selectedDate; } selectedDate = AppointmentHelper.addDaysWithTime( - selectedDate, - DateTime.daysPerWeek, - selectedDate.hour, - selectedDate.minute, - selectedDate.second); + selectedDate, + DateTime.daysPerWeek, + selectedDate.hour, + selectedDate.minute, + 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)) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate, + )) { selectedDate = AppointmentHelper.getMonthEndDate( - currentViewState._selectionPainter!.selectedDate ?? - selectedAppointmentDate!); + currentViewState._selectionPainter!.selectedDate ?? + selectedAppointmentDate!, + ); if (CalendarViewHelper.isDateInDateCollection( - currentView.blackoutDates, selectedDate)) { + currentView.blackoutDates, + selectedDate, + )) { do { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate!, + -1, + selectedDate.hour, + selectedDate.minute, + selectedDate.second, + ); } while (CalendarViewHelper.isDateInDateCollection( - currentView.blackoutDates, selectedDate)); + currentView.blackoutDates, + selectedDate, + )); } } return selectedDate; } else if (!CalendarViewHelper.isTimelineView(widget.view)) { final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); final double yPosition = AppointmentHelper.timeToPosition( - widget.calendar, selectedDate!, currentViewState._timeIntervalHeight); + widget.calendar, + selectedDate!, + currentViewState._timeIntervalHeight, + ); if (selectedDate .add(widget.calendar.timeSlotViewSettings.timeInterval) @@ -4055,7 +4739,9 @@ class _CustomCalendarScrollViewState extends State (widget.height - viewHeaderHeight) < currentViewState._scrollController!.position.viewportDimension + currentViewState - ._scrollController!.position.maxScrollExtent && + ._scrollController! + .position + .maxScrollExtent && yPosition + currentViewState._timeIntervalHeight + widget.calendar.headerHeight + @@ -4063,22 +4749,29 @@ class _CustomCalendarScrollViewState extends State currentViewState._scrollController!.offset + widget.height && currentViewState._scrollController!.offset + currentViewState - ._scrollController!.position.viewportDimension != + ._scrollController! + .position + .viewportDimension != currentViewState._scrollController!.position.maxScrollExtent) { currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.offset + - currentViewState._timeIntervalHeight); + currentViewState._scrollController!.offset + + currentViewState._timeIntervalHeight, + ); } - return selectedDate - .add(widget.calendar.timeSlotViewSettings.timeInterval); + return selectedDate.add( + widget.calendar.timeSlotViewSettings.timeInterval, + ); } else if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { final double resourceItemHeight = CalendarViewHelper.getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource!.resources!.length); + 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) { @@ -4089,19 +4782,27 @@ class _CustomCalendarScrollViewState extends State if (currentViewState._selectedResourceIndex * resourceItemHeight >= currentViewState._timelineViewVerticalScrollController!.offset + - currentViewState._timelineViewVerticalScrollController!.position + currentViewState + ._timelineViewVerticalScrollController! + .position .viewportDimension) { double scrollPosition = currentViewState._timelineViewVerticalScrollController!.offset + - resourceItemHeight; - scrollPosition = scrollPosition > - currentViewState._timelineViewVerticalScrollController!.position + resourceItemHeight; + scrollPosition = + scrollPosition > + currentViewState + ._timelineViewVerticalScrollController! + .position + .maxScrollExtent + ? currentViewState + ._timelineViewVerticalScrollController! + .position .maxScrollExtent - ? currentViewState - ._timelineViewVerticalScrollController!.position.maxScrollExtent - : scrollPosition; - currentViewState._timelineViewVerticalScrollController! - .jumpTo(scrollPosition); + : scrollPosition; + currentViewState._timelineViewVerticalScrollController!.jumpTo( + scrollPosition, + ); } return selectedDate!; @@ -4121,48 +4822,62 @@ class _CustomCalendarScrollViewState extends State currentViewState = _nextViewKey.currentState!; } - final double scrollPosition = - currentViewState._getScrollPositionForCurrentDate( - currentViewState._selectionPainter!.selectedDate!); + final double scrollPosition = currentViewState + ._getScrollPositionForCurrentDate( + currentViewState._selectionPainter!.selectedDate!, + ); if (scrollPosition == -1 || currentViewState._scrollController!.position.pixels == scrollPosition) { return; } currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.position.maxScrollExtent > - scrollPosition - ? scrollPosition - : currentViewState._scrollController!.position.maxScrollExtent); + currentViewState._scrollController!.position.maxScrollExtent > + scrollPosition + ? scrollPosition + : currentViewState._scrollController!.position.maxScrollExtent, + ); } DateTime? _updateSelectedDate( - RawKeyEvent event, - _CalendarViewState currentViewState, - _CalendarView currentView, - int resourceIndex, - DateTime? selectedAppointmentDate, - bool isAllDayAppointment) { - DateTime? selectedDate = currentViewState._selectionPainter!.selectedDate ?? + KeyEvent event, + _CalendarViewState currentViewState, + _CalendarView currentView, + int resourceIndex, + DateTime? selectedAppointmentDate, + bool isAllDayAppointment, + ) { + DateTime? selectedDate = + currentViewState._selectionPainter!.selectedDate ?? selectedAppointmentDate; if (event.logicalKey == LogicalKeyboardKey.arrowRight) { do { selectedDate = _updateSelectedDateForRightArrow( - currentView, currentViewState, selectedDate); + currentView, + currentViewState, + selectedDate, + ); } while (!_isSelectedDateEnabled(selectedDate, resourceIndex, true)); return selectedDate; } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { do { selectedDate = _updateSelectedDateForLeftArrow( - currentView, currentViewState, selectedDate); + currentView, + currentViewState, + selectedDate, + ); } while (!_isSelectedDateEnabled(selectedDate, resourceIndex, true)); return selectedDate; } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { do { - selectedDate = _updateSelectedDateForUpArrow(currentView, - currentViewState, selectedDate, selectedAppointmentDate); + selectedDate = _updateSelectedDateForUpArrow( + currentView, + currentViewState, + selectedDate, + selectedAppointmentDate, + ); if (resourceIndex != -1 && currentView.regions != null && currentView.regions!.isNotEmpty) { @@ -4171,14 +4886,18 @@ class _CustomCalendarScrollViewState extends State if (widget.controller.view != CalendarView.month && !CalendarViewHelper.isTimelineView(widget.calendar.view)) { - double yPosition = AppointmentHelper.timeToPosition(widget.calendar, - selectedDate!, currentViewState._timeIntervalHeight); + double yPosition = AppointmentHelper.timeToPosition( + widget.calendar, + selectedDate!, + currentViewState._timeIntervalHeight, + ); if (yPosition < 1 && !_isSelectedDateEnabled(selectedDate, resourceIndex, true)) { yPosition = AppointmentHelper.timeToPosition( - widget.calendar, - currentViewState._selectionPainter!.selectedDate!, - currentViewState._timeIntervalHeight); + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight, + ); currentViewState._scrollController!.jumpTo(yPosition); break; } @@ -4192,8 +4911,12 @@ class _CustomCalendarScrollViewState extends State } do { - selectedDate = _updateSelectedDateForDownArrow(currentView, - currentViewState, selectedDate, selectedAppointmentDate); + selectedDate = _updateSelectedDateForDownArrow( + currentView, + currentViewState, + selectedDate, + selectedAppointmentDate, + ); if (resourceIndex != -1 && currentView.regions != null && currentView.regions!.isNotEmpty) { @@ -4207,9 +4930,10 @@ class _CustomCalendarScrollViewState extends State .day != selectedDate.day) { final double yPosition = AppointmentHelper.timeToPosition( - widget.calendar, - currentViewState._selectionPainter!.selectedDate!, - currentViewState._timeIntervalHeight); + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight, + ); if (yPosition <= currentViewState._scrollController!.offset) { currentViewState._scrollController!.jumpTo(yPosition); } @@ -4226,21 +4950,30 @@ class _CustomCalendarScrollViewState extends State } /// Checks the selected date is enabled or not. - bool _isSelectedDateEnabled(DateTime date, int resourceIndex, - [bool isMinMaxDate = false]) { - final bool isMonthView = widget.view == CalendarView.month || + bool _isSelectedDateEnabled( + DateTime date, + int resourceIndex, [ + bool isMinMaxDate = false, + ]) { + final bool isMonthView = + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); if ((isMonthView && !isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, date)) || + widget.calendar.minDate, + widget.calendar.maxDate, + date, + )) || (!isMonthView && !CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - date, - timeInterval))) { + widget.calendar.minDate, + widget.calendar.maxDate, + date, + timeInterval, + ))) { return isMinMaxDate; } @@ -4255,7 +4988,9 @@ class _CustomCalendarScrollViewState extends State if (region.enablePointerInteraction || (region.actualStartTime.isAfter(date) && !CalendarViewHelper.isSameTimeSlot( - region.actualStartTime, date)) || + region.actualStartTime, + date, + )) || region.actualEndTime.isBefore(date) || CalendarViewHelper.isSameTimeSlot(region.actualEndTime, date)) { continue; @@ -4266,8 +5001,9 @@ class _CustomCalendarScrollViewState extends State if (resourceIndex != -1 && region.resourceIds != null && region.resourceIds!.isNotEmpty && - !region.resourceIds! - .contains(widget.resourceCollection![resourceIndex].id)) { + !region.resourceIds!.contains( + widget.resourceCollection![resourceIndex].id, + )) { continue; } @@ -4279,8 +5015,11 @@ class _CustomCalendarScrollViewState extends State } /// Method to handle the page up/down key for timeslot views in calendar. - KeyEventResult _updatePageUpAndDown(RawKeyEvent event, - _CalendarViewState currentViewState, bool isResourceEnabled) { + KeyEventResult _updatePageUpAndDown( + KeyEvent event, + _CalendarViewState currentViewState, + bool isResourceEnabled, + ) { if (widget.controller.view != CalendarView.day && widget.controller.view != CalendarView.week && widget.controller.view != CalendarView.workWeek && @@ -4289,17 +5028,20 @@ class _CustomCalendarScrollViewState extends State } final bool isDayView = CalendarViewHelper.isDayView( - widget.controller.view!, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final ScrollController scrollController = isResourceEnabled - ? widget.resourcePanelScrollController! - : currentViewState._scrollController!; + widget.controller.view!, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final ScrollController scrollController = + isResourceEnabled + ? widget.resourcePanelScrollController! + : currentViewState._scrollController!; final TargetPlatform platform = Theme.of(context).platform; double difference = 0; - final double scrollViewHeight = scrollController.position.maxScrollExtent + + final double scrollViewHeight = + scrollController.position.maxScrollExtent + scrollController.position.viewportDimension; double divideValue = 0.25; if (scrollController.position.pixels > scrollViewHeight / 2) { @@ -4318,25 +5060,30 @@ class _CustomCalendarScrollViewState extends State (platform == TargetPlatform.windows && event.logicalKey.keyId == 0x10700000022)) { double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.controller.view!); + widget.calendar.viewHeaderHeight, + widget.controller.view!, + ); double allDayHeight = 0; if (isDayView) { allDayHeight = _kAllDayLayoutHeight; viewHeaderHeight = 0; } else { - allDayHeight = allDayHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : allDayHeight; + allDayHeight = + allDayHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : allDayHeight; } final double timeRulerSize = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.controller.view!); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.controller.view!, + ); - final double viewPortHeight = isResourceEnabled - ? widget.height - viewHeaderHeight - timeRulerSize - : widget.height - allDayHeight - viewHeaderHeight; + final double viewPortHeight = + isResourceEnabled + ? widget.height - viewHeaderHeight - timeRulerSize + : widget.height - allDayHeight - viewHeaderHeight; final double viewPortEndPosition = scrollController.position.pixels + viewPortHeight; @@ -4358,45 +5105,49 @@ class _CustomCalendarScrollViewState extends State /// Updates the appointment selection based on keyboard navigation in calendar KeyEventResult _updateAppointmentSelection( - RawKeyEvent event, - _CalendarViewState currentVisibleViewState, - bool isResourceEnabled, - AppointmentView? currentSelectedAppointment, - AppointmentView? currentAllDayAppointment) { + KeyEvent event, + _CalendarViewState currentVisibleViewState, + bool isResourceEnabled, + AppointmentView? currentSelectedAppointment, + AppointmentView? currentAllDayAppointment, + ) { if (widget.controller.view == CalendarView.schedule) { return KeyEventResult.ignored; } AppointmentView? selectedAppointment; bool isAllDay = currentAllDayAppointment != null; - final List appointmentCollection = currentVisibleViewState - ._appointmentLayout - .getAppointmentViewCollection(); + final List appointmentCollection = + currentVisibleViewState._appointmentLayout + .getAppointmentViewCollection(); final List allDayAppointmentCollection = _updateCalendarStateDetails.allDayAppointmentViewCollection; final List tempAppColl = isAllDay ? allDayAppointmentCollection : appointmentCollection; - if (event.isShiftPressed) { + if (HardwareKeyboard.instance.isShiftPressed) { if (event.logicalKey == LogicalKeyboardKey.tab) { if (currentAllDayAppointment != null || currentSelectedAppointment != null) { - int index = tempAppColl.indexOf(isAllDay - ? currentAllDayAppointment - : currentSelectedAppointment!); + int index = tempAppColl.indexOf( + isAllDay ? currentAllDayAppointment : currentSelectedAppointment!, + ); index -= 1; if (tempAppColl.length > index && !index.isNegative) { - selectedAppointment = tempAppColl[index].appointment != null - ? tempAppColl[index] - : null; + selectedAppointment = + tempAppColl[index].appointment != null + ? tempAppColl[index] + : null; } } if (currentSelectedAppointment != null && selectedAppointment == null) { isAllDay = allDayAppointmentCollection.isNotEmpty; - selectedAppointment = isAllDay - ? allDayAppointmentCollection[ - allDayAppointmentCollection.length - 1] - : null; + selectedAppointment = + isAllDay + ? allDayAppointmentCollection[allDayAppointmentCollection + .length - + 1] + : null; } else if (currentSelectedAppointment == null && currentAllDayAppointment == null && selectedAppointment == null) { @@ -4404,9 +5155,9 @@ class _CustomCalendarScrollViewState extends State appointmentCollection.isNotEmpty) { for (int i = 0; i < appointmentCollection.length; i++) { if (AppointmentHelper.getDifference( - currentVisibleViewState._selectionPainter!.selectedDate!, - appointmentCollection[i].appointment!.actualStartTime) - .isNegative) { + currentVisibleViewState._selectionPainter!.selectedDate!, + appointmentCollection[i].appointment!.actualStartTime, + ).isNegative) { continue; } @@ -4416,29 +5167,33 @@ class _CustomCalendarScrollViewState extends State break; } } else { - selectedAppointment = appointmentCollection.isNotEmpty - ? appointmentCollection[appointmentCollection.length - 1] - : null; + selectedAppointment = + appointmentCollection.isNotEmpty + ? appointmentCollection[appointmentCollection.length - 1] + : null; } } return _updateAppointmentSelectionOnView( - selectedAppointment, - currentVisibleViewState, - isAllDay, - isResourceEnabled, - !event.isShiftPressed); + selectedAppointment, + currentVisibleViewState, + isAllDay, + isResourceEnabled, + !HardwareKeyboard.instance.isShiftPressed, + ); } } else if (event.logicalKey == LogicalKeyboardKey.tab) { if (currentAllDayAppointment != null || currentSelectedAppointment != null) { int index = tempAppColl.indexOf( - isAllDay ? currentAllDayAppointment : currentSelectedAppointment!); + isAllDay ? currentAllDayAppointment : currentSelectedAppointment!, + ); index += 1; if (tempAppColl.length > index) { - selectedAppointment = tempAppColl[index].appointment != null - ? tempAppColl[index] - : null; + selectedAppointment = + tempAppColl[index].appointment != null + ? tempAppColl[index] + : null; } } @@ -4451,9 +5206,9 @@ class _CustomCalendarScrollViewState extends State appointmentCollection.isNotEmpty) { for (int i = 0; i < appointmentCollection.length; i++) { if (AppointmentHelper.getDifference( - currentVisibleViewState._selectionPainter!.selectedDate!, - appointmentCollection[i].appointment!.actualStartTime) - .isNegative) { + currentVisibleViewState._selectionPainter!.selectedDate!, + appointmentCollection[i].appointment!.actualStartTime, + ).isNegative) { continue; } @@ -4462,20 +5217,22 @@ class _CustomCalendarScrollViewState extends State } } else { isAllDay = allDayAppointmentCollection.isNotEmpty; - selectedAppointment = isAllDay - ? allDayAppointmentCollection[0] - : appointmentCollection.isNotEmpty + selectedAppointment = + isAllDay + ? allDayAppointmentCollection[0] + : appointmentCollection.isNotEmpty ? appointmentCollection[0] : null; } } return _updateAppointmentSelectionOnView( - selectedAppointment, - currentVisibleViewState, - isAllDay, - isResourceEnabled, - !event.isShiftPressed); + selectedAppointment, + currentVisibleViewState, + isAllDay, + isResourceEnabled, + !HardwareKeyboard.instance.isShiftPressed, + ); } return KeyEventResult.ignored; @@ -4484,22 +5241,27 @@ class _CustomCalendarScrollViewState extends State /// Updates the selection for appointment view based on keyboard navigation /// in Calendar. KeyEventResult _updateAppointmentSelectionOnView( - AppointmentView? selectedAppointment, - _CalendarViewState currentVisibleViewState, - bool isAllDay, - bool isResourceEnabled, - bool isForward) { + AppointmentView? selectedAppointment, + _CalendarViewState currentVisibleViewState, + bool isAllDay, + bool isResourceEnabled, + bool isForward, + ) { final DateTime visibleStartDate = AppointmentHelper.convertToStartTime( - currentVisibleViewState.widget.visibleDates[0]); + currentVisibleViewState.widget.visibleDates[0], + ); final DateTime visibleEndDate = AppointmentHelper.convertToEndTime( - currentVisibleViewState.widget.visibleDates[ - currentVisibleViewState.widget.visibleDates.length - 1]); + currentVisibleViewState + .widget + .visibleDates[currentVisibleViewState.widget.visibleDates.length - 1], + ); final bool isDayView = CalendarViewHelper.isDayView( - widget.controller.view!, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.controller.view!, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); if (isAllDay && selectedAppointment != null) { currentVisibleViewState._updateAllDaySelection(selectedAppointment, null); @@ -4514,9 +5276,10 @@ class _CustomCalendarScrollViewState extends State if (selectedAppointment != null && AppointmentHelper.isAppointmentWithinVisibleDateRange( - selectedAppointment.appointment!, - visibleStartDate, - visibleEndDate)) { + selectedAppointment.appointment!, + visibleStartDate, + visibleEndDate, + )) { currentVisibleViewState._allDaySelectionNotifier.value = null; currentVisibleViewState._selectionPainter!.appointmentView = selectedAppointment; @@ -4527,16 +5290,24 @@ class _CustomCalendarScrollViewState extends State if (widget.controller.view != CalendarView.month) { late double offset; late double viewPortSize; - final double scrollViewHeight = currentVisibleViewState - ._scrollController!.position.maxScrollExtent + + final double scrollViewHeight = + currentVisibleViewState + ._scrollController! + .position + .maxScrollExtent + currentVisibleViewState - ._scrollController!.position.viewportDimension; + ._scrollController! + .position + .viewportDimension; final double resourceViewSize = isResourceEnabled ? widget.calendar.resourceViewSettings.size : 0; - final bool isTimeline = - CalendarViewHelper.isTimelineView(widget.controller.view!); + final bool isTimeline = CalendarViewHelper.isTimelineView( + widget.controller.view!, + ); double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.controller.view!); + widget.calendar.viewHeaderHeight, + widget.controller.view!, + ); if (isTimeline) { viewPortSize = widget.width - resourceViewSize; @@ -4548,33 +5319,37 @@ class _CustomCalendarScrollViewState extends State allDayHeight = _kAllDayLayoutHeight; viewHeaderHeight = 0; } else { - allDayHeight = allDayHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : allDayHeight; + allDayHeight = + allDayHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : allDayHeight; } viewPortSize = widget.height - allDayHeight - viewHeaderHeight; offset = selectedAppointment.appointmentRect!.top; } _updateScrollViewToAppointment( - offset, - currentVisibleViewState._scrollController!, - viewPortSize, - scrollViewHeight); + offset, + currentVisibleViewState._scrollController!, + viewPortSize, + scrollViewHeight, + ); if (isResourceEnabled) { - final double resourcePanelHeight = widget - .resourcePanelScrollController!.position.viewportDimension + + final double resourcePanelHeight = + widget.resourcePanelScrollController!.position.viewportDimension + widget.resourcePanelScrollController!.position.maxScrollExtent; final double timeRulerSize = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.controller.view!), + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.controller.view!, + ), viewPortSize = widget.height - viewHeaderHeight - timeRulerSize; _updateScrollViewToAppointment( - selectedAppointment.appointmentRect!.top, - widget.resourcePanelScrollController!, - viewPortSize, - resourcePanelHeight); + selectedAppointment.appointmentRect!.top, + widget.resourcePanelScrollController!, + viewPortSize, + resourcePanelHeight, + ); } } else if (widget.controller.view == CalendarView.month) { widget.agendaSelectedDate.value = null; @@ -4601,10 +5376,11 @@ class _CustomCalendarScrollViewState extends State /// Moves the scroll panel to the selected appointments position, if the /// selected appointment doesn't falls on the view port. void _updateScrollViewToAppointment( - double offset, - ScrollController scrollController, - double viewPortSize, - double panelHeight) { + double offset, + ScrollController scrollController, + double viewPortSize, + double panelHeight, + ) { if (offset < scrollController.position.pixels || offset > (scrollController.position.pixels + viewPortSize)) { if (offset + viewPortSize > panelHeight) { @@ -4614,31 +5390,34 @@ class _CustomCalendarScrollViewState extends State } } - KeyEventResult _onKeyDown(FocusNode node, RawKeyEvent event) { + KeyEventResult _onKeyDown(FocusNode node, KeyEvent event) { KeyEventResult result = KeyEventResult.ignored; - if (event.runtimeType != RawKeyDownEvent) { + if (event.runtimeType != KeyDownEvent) { return result; } widget.removePicker(); - if (event.isControlPressed && widget.view != CalendarView.schedule) { + if (HardwareKeyboard.instance.isControlPressed && + widget.view != CalendarView.schedule) { final bool canMoveToNextView = DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + ); final bool canMoveToPreviousView = DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL); + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + ); if (event.logicalKey == LogicalKeyboardKey.arrowRight && canMoveToNextView) { widget.isRTL @@ -4656,12 +5435,17 @@ class _CustomCalendarScrollViewState extends State } CalendarViewHelper.handleViewSwitchKeyBoardEvent( - event, widget.controller, widget.calendar.allowedViews); + event, + widget.controller, + widget.calendar.allowedViews, + ); _CalendarViewState currentVisibleViewState; _CalendarView currentVisibleView; final bool isResourcesEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + widget.calendar.dataSource, + widget.view, + ); if (_currentChildIndex == 0) { currentVisibleViewState = _previousViewKey.currentState!; currentVisibleView = _previousView; @@ -4674,7 +5458,10 @@ class _CustomCalendarScrollViewState extends State } result = _updatePageUpAndDown( - event, currentVisibleViewState, isResourcesEnabled); + event, + currentVisibleViewState, + isResourcesEnabled, + ); AppointmentView? currentSelectedAppointment = currentVisibleViewState._selectionPainter!.appointmentView; @@ -4682,11 +5469,12 @@ class _CustomCalendarScrollViewState extends State currentVisibleViewState._allDaySelectionNotifier.value?.appointmentView; result = _updateAppointmentSelection( - event, - currentVisibleViewState, - isResourcesEnabled, - currentSelectedAppointment, - currentAllDayAppointment); + event, + currentVisibleViewState, + isResourcesEnabled, + currentSelectedAppointment, + currentAllDayAppointment, + ); currentSelectedAppointment = currentVisibleViewState._selectionPainter!.appointmentView; @@ -4695,69 +5483,88 @@ class _CustomCalendarScrollViewState extends State if (event.logicalKey == LogicalKeyboardKey.enter && CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.calendar.onTap)) { - final AppointmentView? selectedAppointment = currentVisibleViewState - ._allDaySelectionNotifier.value?.appointmentView ?? + widget.calendar.onTap, + )) { + final AppointmentView? selectedAppointment = + currentVisibleViewState + ._allDaySelectionNotifier + .value + ?.appointmentView ?? currentVisibleViewState._selectionPainter!.appointmentView; final List? selectedAppointments = widget.controller.view == CalendarView.month && selectedAppointment == null ? AppointmentHelper.getSelectedDateAppointments( - _updateCalendarStateDetails.appointments, - widget.calendar.timeZone, - _updateCalendarStateDetails.selectedDate) + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + _updateCalendarStateDetails.selectedDate, + ) : selectedAppointment != null - ? [selectedAppointment.appointment!] - : null; + ? [selectedAppointment.appointment!] + : null; final CalendarElement tappedElement = _updateCalendarStateDetails.selectedDate != null ? CalendarElement.calendarCell : CalendarElement.appointment; CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, - tappedElement == CalendarElement.appointment - ? selectedAppointments![0].startTime - : _updateCalendarStateDetails.selectedDate, - CalendarViewHelper.getCustomAppointments( - selectedAppointments, widget.calendar.dataSource), - tappedElement, - isResourcesEnabled - ? widget.calendar.dataSource! - .resources![currentVisibleViewState._selectedResourceIndex] - : null); + widget.calendar, + tappedElement == CalendarElement.appointment + ? selectedAppointments![0].startTime + : _updateCalendarStateDetails.selectedDate, + CalendarViewHelper.getCustomAppointments( + selectedAppointments, + widget.calendar.dataSource, + ), + tappedElement, + isResourcesEnabled + ? widget.calendar.dataSource!.resources![currentVisibleViewState + ._selectedResourceIndex] + : null, + ); } - final int previousResourceIndex = isResourcesEnabled - ? currentVisibleViewState._selectedResourceIndex - : -1; + 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)) || + currentVisibleViewState.widget.visibleDates[0], + currentVisibleViewState + .widget + .visibleDates[currentVisibleViewState + .widget + .visibleDates + .length - + 1], + currentVisibleViewState._selectionPainter!.selectedDate, + )) || (currentSelectedAppointment != null || currentAllDayAppointment != null)) { - final int resourceIndex = isResourcesEnabled - ? currentVisibleViewState._selectedResourceIndex - : -1; + final int resourceIndex = + isResourcesEnabled + ? currentVisibleViewState._selectedResourceIndex + : -1; - final DateTime? selectedAppointmentDate = currentAllDayAppointment != null - ? AppointmentHelper.convertToStartTime( - currentAllDayAppointment.appointment!.actualStartTime) - : currentSelectedAppointment?.appointment!.actualStartTime; + final DateTime? selectedAppointmentDate = + currentAllDayAppointment != null + ? AppointmentHelper.convertToStartTime( + currentAllDayAppointment.appointment!.actualStartTime, + ) + : currentSelectedAppointment?.appointment!.actualStartTime; final bool isAllDayAppointment = currentAllDayAppointment != null; final DateTime? selectedDate = _updateSelectedDate( - event, - currentVisibleViewState, - currentVisibleView, - resourceIndex, - selectedAppointmentDate, - isAllDayAppointment); + event, + currentVisibleViewState, + currentVisibleView, + resourceIndex, + selectedAppointmentDate, + isAllDayAppointment, + ); if (selectedDate == null) { result = KeyEventResult.ignored; @@ -4776,19 +5583,22 @@ class _CustomCalendarScrollViewState extends State _updateCalendarStateDetails.selectedDate = selectedDate; if (widget.calendar.onSelectionChanged != null && (!CalendarViewHelper.isSameTimeSlot( - currentVisibleViewState._selectionPainter!.selectedDate, - selectedDate) || + currentVisibleViewState._selectionPainter!.selectedDate, + selectedDate, + ) || (isResourcesEnabled && currentVisibleViewState - ._selectionPainter!.selectedResourceIndex != + ._selectionPainter! + .selectedResourceIndex != currentVisibleViewState._selectedResourceIndex))) { CalendarViewHelper.raiseCalendarSelectionChangedCallback( - widget.calendar, - selectedDate, - isResourcesEnabled - ? widget.resourceCollection![ - currentVisibleViewState._selectedResourceIndex] - : null); + widget.calendar, + selectedDate, + isResourcesEnabled + ? widget.resourceCollection![currentVisibleViewState + ._selectedResourceIndex] + : null, + ); } currentVisibleViewState._selectionPainter!.selectedDate = selectedDate; currentVisibleViewState._updateAllDaySelection(null, null); @@ -4811,60 +5621,77 @@ class _CustomCalendarScrollViewState extends State final _CalendarViewState nextViewState = _nextViewKey.currentState!; if (widget.isRTL) { if (_currentChildIndex == 0) { - currentViewState._scrollController!.jumpTo(isScrolledToEnd - ? currentViewState._scrollController!.position.maxScrollExtent - : 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); + 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); + 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); + 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); + 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); + currentViewState._scrollController!.jumpTo( + isScrolledToEnd + ? currentViewState._scrollController!.position.maxScrollExtent + : 0, + ); previousViewState._scrollController!.jumpTo(0); } } } void _onHorizontalStart( - DragStartDetails dragStartDetails, - bool isResourceEnabled, - bool isTimelineView, - double viewHeaderHeight, - double timeLabelWidth, - bool isNeedDragAndDrop) { + DragStartDetails dragStartDetails, + bool isResourceEnabled, + bool isTimelineView, + double viewHeaderHeight, + double timeLabelWidth, + bool isNeedDragAndDrop, + ) { final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; if (currentState._hoveringAppointmentView != null && + currentState._hoveringAppointmentView!.appointment != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleAppointmentDragStart( - currentState._hoveringAppointmentView!.clone(), - isTimelineView, - Offset(dragStartDetails.localPosition.dx - widget.width, - dragStartDetails.localPosition.dy), - isResourceEnabled, - viewHeaderHeight, - timeLabelWidth); + currentState._hoveringAppointmentView!.clone(), + isTimelineView, + Offset( + dragStartDetails.localPosition.dx - widget.width, + dragStartDetails.localPosition.dy, + ), + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + ); return; } switch (widget.calendar.viewNavigationMode) { @@ -4886,28 +5713,33 @@ class _CustomCalendarScrollViewState extends State } } - void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails, - [bool isResourceEnabled = false, - bool isMonthView = false, - bool isTimelineView = false, - double viewHeaderHeight = 0, - double timeLabelWidth = 0, - double resourceItemHeight = 0, - double weekNumberPanelWidth = 0, - bool isNeedDragAndDrop = false]) { + void _onHorizontalUpdate( + DragUpdateDetails dragUpdateDetails, [ + bool isResourceEnabled = false, + bool isMonthView = false, + bool isTimelineView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double resourceItemHeight = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false, + ]) { if (_dragDetails.value.appointmentView != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleLongPressMove( - Offset(dragUpdateDetails.localPosition.dx - widget.width, - dragUpdateDetails.localPosition.dy), - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - resourceItemHeight, - weekNumberPanelWidth); + Offset( + dragUpdateDetails.localPosition.dx - widget.width, + dragUpdateDetails.localPosition.dy, + ), + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + ); return; } switch (widget.calendar.viewNavigationMode) { @@ -4922,24 +5754,26 @@ class _CustomCalendarScrollViewState extends State 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)) { + 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)) { + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL, + )) { _position = 0; return; } @@ -4953,25 +5787,28 @@ class _CustomCalendarScrollViewState extends State } } - void _onHorizontalEnd(DragEndDetails dragEndDetails, - [bool isResourceEnabled = false, - bool isTimelineView = false, - bool isMonthView = false, - double viewHeaderHeight = 0, - double timeLabelWidth = 0, - double weekNumberPanelWidth = 0, - bool isNeedDragAndDrop = false]) { + void _onHorizontalEnd( + DragEndDetails dragEndDetails, [ + bool isResourceEnabled = false, + bool isTimelineView = false, + bool isMonthView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false, + ]) { if (_dragDetails.value.appointmentView != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleLongPressEnd( - _dragDetails.value.position.value! - _dragDifferenceOffset!, - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - weekNumberPanelWidth); + _dragDetails.value.position.value! - _dragDifferenceOffset!, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + ); return; } switch (widget.calendar.viewNavigationMode) { @@ -4993,9 +5830,9 @@ class _CustomCalendarScrollViewState extends State _animationController.reset(); } - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _animationController.forward().then( + (dynamic value) => _updateNextView(), + ); /// updates the current view visible dates when the view swiped in /// right to left direction @@ -5004,13 +5841,14 @@ class _CustomCalendarScrollViewState extends State // 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)) { + 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 @@ -5030,7 +5868,9 @@ class _CustomCalendarScrollViewState extends State _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updateNextView()); /// updates the current view visible dates when fling the view in @@ -5048,9 +5888,9 @@ class _CustomCalendarScrollViewState extends State _animationController.reset(); } - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + _animationController.forward().then( + (dynamic value) => _updatePreviousView(), + ); /// updates the current view visible dates when the view swiped in /// left to right direction @@ -5059,13 +5899,14 @@ class _CustomCalendarScrollViewState extends State // 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)) { + 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 @@ -5085,7 +5926,9 @@ class _CustomCalendarScrollViewState extends State _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updatePreviousView()); /// updates the current view visible dates when fling the view in @@ -5110,24 +5953,29 @@ class _CustomCalendarScrollViewState extends State } void _onVerticalStart( - DragStartDetails dragStartDetails, - bool isResourceEnabled, - bool isTimelineView, - double viewHeaderHeight, - double timeLabelWidth, - bool isNeedDragAndDrop) { + DragStartDetails dragStartDetails, + bool isResourceEnabled, + bool isTimelineView, + double viewHeaderHeight, + double timeLabelWidth, + bool isNeedDragAndDrop, + ) { final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; if (currentState._hoveringAppointmentView != null && + currentState._hoveringAppointmentView!.appointment != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleAppointmentDragStart( - currentState._hoveringAppointmentView!.clone(), - isTimelineView, - Offset(dragStartDetails.localPosition.dx, - dragStartDetails.localPosition.dy - widget.height), - isResourceEnabled, - viewHeaderHeight, - timeLabelWidth); + currentState._hoveringAppointmentView!.clone(), + isTimelineView, + Offset( + dragStartDetails.localPosition.dx, + dragStartDetails.localPosition.dy - widget.height, + ), + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + ); return; } switch (widget.calendar.viewNavigationMode) { @@ -5143,28 +5991,33 @@ class _CustomCalendarScrollViewState extends State } } - void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails, - [bool isResourceEnabled = false, - bool isMonthView = false, - bool isTimelineView = false, - double viewHeaderHeight = 0, - double timeLabelWidth = 0, - double resourceItemHeight = 0, - double weekNumberPanelWidth = 0, - bool isNeedDragAndDrop = false]) { + void _onVerticalUpdate( + DragUpdateDetails dragUpdateDetails, [ + bool isResourceEnabled = false, + bool isMonthView = false, + bool isTimelineView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double resourceItemHeight = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false, + ]) { if (_dragDetails.value.appointmentView != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleLongPressMove( - Offset(dragUpdateDetails.localPosition.dx, - dragUpdateDetails.localPosition.dy - widget.height), - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - resourceItemHeight, - weekNumberPanelWidth); + Offset( + dragUpdateDetails.localPosition.dx, + dragUpdateDetails.localPosition.dy - widget.height, + ), + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + ); return; } switch (widget.calendar.viewNavigationMode) { @@ -5179,22 +6032,24 @@ class _CustomCalendarScrollViewState extends State 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)) { + 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)) { + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + )) { _position = 0; return; } @@ -5207,25 +6062,28 @@ class _CustomCalendarScrollViewState extends State } } - void _onVerticalEnd(DragEndDetails dragEndDetails, - [bool isResourceEnabled = false, - bool isTimelineView = false, - bool isMonthView = false, - double viewHeaderHeight = 0, - double timeLabelWidth = 0, - double weekNumberPanelWidth = 0, - bool isNeedDragAndDrop = false]) { + void _onVerticalEnd( + DragEndDetails dragEndDetails, [ + bool isResourceEnabled = false, + bool isTimelineView = false, + bool isMonthView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false, + ]) { if (_dragDetails.value.appointmentView != null && !widget.isMobilePlatform && isNeedDragAndDrop) { _handleLongPressEnd( - _dragDetails.value.position.value! - _dragDifferenceOffset!, - isTimelineView, - isResourceEnabled, - isMonthView, - viewHeaderHeight, - timeLabelWidth, - weekNumberPanelWidth); + _dragDetails.value.position.value! - _dragDifferenceOffset!, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + ); return; } switch (widget.calendar.viewNavigationMode) { @@ -5247,9 +6105,9 @@ class _CustomCalendarScrollViewState extends State _animationController.reset(); } - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _animationController.forward().then( + (dynamic value) => _updateNextView(), + ); /// updates the current view visible dates when the view swiped in /// bottom to top direction @@ -5259,12 +6117,13 @@ class _CustomCalendarScrollViewState extends State 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)) { + 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 @@ -5284,7 +6143,9 @@ class _CustomCalendarScrollViewState extends State _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updateNextView()); /// updates the current view visible dates when fling the view in @@ -5302,9 +6163,9 @@ class _CustomCalendarScrollViewState extends State _animationController.reset(); } - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + _animationController.forward().then( + (dynamic value) => _updatePreviousView(), + ); /// updates the current view visible dates when the view swiped in /// top to bottom direction @@ -5313,12 +6174,13 @@ class _CustomCalendarScrollViewState extends State // 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)) { + 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 @@ -5338,7 +6200,9 @@ class _CustomCalendarScrollViewState extends State _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updatePreviousView()); /// updates the current view visible dates when fling the view in @@ -5409,14 +6273,16 @@ class _CustomCalendarScrollViewState extends State if (view.visibleDates == _currentViewVisibleDates) { widget.getCalendarState(_updateCalendarStateDetails); if (!CalendarViewHelper.isCollectionEqual( - viewKey.currentState!._monthView.visibleAppointmentNotifier.value, - _updateCalendarStateDetails.visibleAppointments)) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value, + _updateCalendarStateDetails.visibleAppointments, + )) { viewKey.currentState!._monthView.visibleAppointmentNotifier.value = _updateCalendarStateDetails.visibleAppointments; } } else { - if (!CalendarViewHelper.isEmptyList(viewKey - .currentState!._monthView.visibleAppointmentNotifier.value)) { + if (!CalendarViewHelper.isEmptyList( + viewKey.currentState!._monthView.visibleAppointmentNotifier.value, + )) { viewKey.currentState!._monthView.visibleAppointmentNotifier.value = null; } @@ -5427,14 +6293,16 @@ class _CustomCalendarScrollViewState extends State if (view.visibleDates == _currentViewVisibleDates) { widget.getCalendarState(_updateCalendarStateDetails); if (!CalendarViewHelper.isCollectionEqual( - appointmentLayout.visibleAppointments.value, - _updateCalendarStateDetails.visibleAppointments)) { + appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails.visibleAppointments, + )) { appointmentLayout.visibleAppointments.value = _updateCalendarStateDetails.visibleAppointments; } } else { if (!CalendarViewHelper.isEmptyList( - appointmentLayout.visibleAppointments.value)) { + appointmentLayout.visibleAppointments.value, + )) { appointmentLayout.visibleAppointments.value = null; } } @@ -5446,33 +6314,34 @@ class _CustomCalendarScrollViewState extends State @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.timelineMonthWeekNumberNotifier, - this.dragDetails, - this.updateCalendarState, - this.getCalendarState, - {Key? key}) - : super(key: key); + this.calendar, + this.view, + this.visibleDates, + this.width, + this.height, + this.agendaSelectedDate, + this.locale, + this.calendarTheme, + this.themeData, + 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.timelineMonthWeekNumberNotifier, + this.dragDetails, + this.updateCalendarState, + this.getCalendarState, { + Key? key, + }) : super(key: key); final List visibleDates; final List? regions; @@ -5481,6 +6350,7 @@ class _CalendarView extends StatefulWidget { final CalendarView view; final double width; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final double height; final String locale; final ValueNotifier agendaSelectedDate, @@ -5530,11 +6400,12 @@ class _CalendarViewState extends State<_CalendarView> late double _timeIntervalHeight; final UpdateCalendarStateDetails _updateCalendarStateDetails = UpdateCalendarStateDetails(); - ValueNotifier _allDaySelectionNotifier = - ValueNotifier(null); + ValueNotifier _allDaySelectionNotifier = + ValueNotifier(null); late ValueNotifier _viewHeaderNotifier; - final ValueNotifier _calendarCellNotifier = - ValueNotifier(null), + final ValueNotifier _calendarCellNotifier = ValueNotifier( + null, + ), _allDayNotifier = ValueNotifier(null), _appointmentHoverNotifier = ValueNotifier(null); final ValueNotifier _selectionNotifier = ValueNotifier(false), @@ -5574,66 +6445,69 @@ class _CalendarViewState extends State<_CalendarView> @override void initState() { _resizingDetails = ValueNotifier<_ResizingPaintDetails>( - _ResizingPaintDetails(position: ValueNotifier(null))); + _ResizingPaintDetails(position: ValueNotifier(null)), + ); _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 + 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 + 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); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); if (widget.view != CalendarView.month) { _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); - _scrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_scrollListener); + widget.calendar.timeSlotViewSettings, + widget.view, + ); + _scrollController = ScrollController()..addListener(_scrollListener); if (CalendarViewHelper.isTimelineView(widget.view)) { _timelineRulerController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_timeRulerListener); - _timelineViewHeaderScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + ScrollController()..addListener(_timeRulerListener); + _timelineViewHeaderScrollController = ScrollController(); _timelineViewAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - animationBehavior: AnimationBehavior.normal); - _timelineViewAnimation = _timelineViewTween - .animate(_timelineViewAnimationController!) - ..addListener(_scrollAnimationListener); + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _timelineViewAnimation = _timelineViewTween.animate( + _timelineViewAnimationController!, + )..addListener(_scrollAnimationListener); _timelineViewVerticalScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_updateResourceScroll); - widget.resourcePanelScrollController - ?.addListener(_updateResourcePanelScroll); + ScrollController()..addListener(_updateResourceScroll); + widget.resourcePanelScrollController?.addListener( + _updateResourcePanelScroll, + ); } _scrollToPosition(); @@ -5641,7 +6515,8 @@ class _CalendarViewState extends State<_CalendarView> final DateTime today = DateTime.now(); _currentTimeNotifier = ValueNotifier( - (today.day * 24 * 60) + (today.hour * 60) + today.minute); + (today.day * 24 * 60) + (today.hour * 60) + today.minute, + ); _timer = _createTimer(); super.initState(); } @@ -5656,9 +6531,7 @@ class _CalendarViewState extends State<_CalendarView> _updateHorizontalLineCount(oldWidget); - _scrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_scrollListener); + _scrollController ??= ScrollController()..addListener(_scrollListener); if (isTimelineView) { _updateTimelineViews(oldWidget); @@ -5687,31 +6560,36 @@ class _CalendarViewState extends State<_CalendarView> _scrollToPosition(); } + widget.getCalendarState(_updateCalendarStateDetails); + /// Method called to update all day height, when the view changed from /// day to week views to avoid the blank space at the bottom of the view. final bool isCurrentView = _updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates; + widget.visibleDates; _updateAllDayHeight(isCurrentView); _timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + 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); + _allDaySelectionNotifier = ValueNotifier( + null, + ); final DateTime today = DateTime.now(); _currentTimeNotifier = ValueNotifier( - (today.day * 24 * 60) + (today.hour * 60) + today.minute); + (today.day * 24 * 60) + (today.hour * 60) + today.minute, + ); _timer?.cancel(); _timer = null; } @@ -5731,17 +6609,20 @@ class _CalendarViewState extends State<_CalendarView> /// 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) && + widget.calendar.dataSource, + widget.view, + ) && _selectedResourceIndex == -1) { _selectedResourceIndex = 0; } if (!CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { _selectedResourceIndex = -1; } @@ -5781,8 +6662,9 @@ class _CalendarViewState extends State<_CalendarView> } if (widget.resourcePanelScrollController != null) { - widget.resourcePanelScrollController! - .removeListener(_updateResourcePanelScroll); + widget.resourcePanelScrollController!.removeListener( + _updateResourcePanelScroll, + ); } if (CalendarViewHelper.isTimelineView(widget.view) && @@ -5826,25 +6708,28 @@ class _CalendarViewState extends State<_CalendarView> widget.view != CalendarView.month && widget.view != CalendarView.timelineMonth ? Timer.periodic(const 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; - } + 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; - }) + _currentTimeNotifier.value = + (today.day * 24 * 60) + (today.hour * 60) + today.minute; + }) : null; } @@ -5858,14 +6743,19 @@ class _CalendarViewState extends State<_CalendarView> if (widget.resourcePanelScrollController == null || !CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { return; } - if (widget.resourcePanelScrollController!.offset != - _timelineViewVerticalScrollController!.offset) { - _timelineViewVerticalScrollController! - .jumpTo(widget.resourcePanelScrollController!.offset); + if (_timelineViewVerticalScrollController != null && + _timelineViewVerticalScrollController!.hasClients && + widget.resourcePanelScrollController!.offset != + _timelineViewVerticalScrollController!.offset) { + _timelineViewVerticalScrollController!.jumpTo( + widget.resourcePanelScrollController!.offset, + ); } } @@ -5879,64 +6769,117 @@ class _CalendarViewState extends State<_CalendarView> if (widget.resourcePanelScrollController == null || !CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { return; } if (widget.resourcePanelScrollController!.offset != _timelineViewVerticalScrollController!.offset) { - widget.resourcePanelScrollController! - .jumpTo(_timelineViewVerticalScrollController!.offset); + widget.resourcePanelScrollController!.jumpTo( + _timelineViewVerticalScrollController!.offset, + ); } } Widget _getMonthView() { + final SystemMouseCursor currentCursor = + _mouseCursor == SystemMouseCursors.resizeUp || + _mouseCursor == SystemMouseCursors.resizeDown + ? SystemMouseCursors.resizeUpDown + : _mouseCursor == SystemMouseCursors.resizeRight || + _mouseCursor == SystemMouseCursors.resizeLeft + ? SystemMouseCursors.resizeLeftRight + : _mouseCursor; + return MouseRegion( - cursor: _mouseCursor, + cursor: currentCursor, onEnter: _pointerEnterEvent, onExit: _pointerExitEvent, onHover: _pointerHoverEvent, - child: Stack(children: [ - GestureDetector( - child: SizedBox( + child: Stack( + children: [ + GestureDetector( + onTapUp: _handleOnTapForMonth, + child: SizedBox( width: widget.width, height: widget.height, - child: _addMonthView(_isRTL, widget.locale)), - onTapUp: _handleOnTapForMonth, - onLongPressStart: null, - ), - _getResizeShadowView() - ]), + child: _addMonthView(_isRTL, widget.locale), + ), + ), + _getResizeShadowView(), + ], + ), ); } Widget _getDayView() { final bool isCurrentView = _updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates; + widget.visibleDates; + + // Check and update the time interval height while the all day panel + // appointments updated(all day height is default value) for current view. + if (isCurrentView && _updateCalendarStateDetails.allDayPanelHeight != 0) { + final bool isDayView = CalendarViewHelper.isDayView( + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); + // Default all day height is 0 on week and work week view + // Default all day height is view header height on day view. + final double defaultAllDayHeight = isDayView ? viewHeaderHeight : 0; + if (_allDayHeight == defaultAllDayHeight) { + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); + } + } + _updateAllDayHeight(isCurrentView); + final SystemMouseCursor currentCursor = + _mouseCursor == SystemMouseCursors.resizeUp || + _mouseCursor == SystemMouseCursors.resizeDown + ? SystemMouseCursors.resizeUpDown + : _mouseCursor == SystemMouseCursors.resizeRight || + _mouseCursor == SystemMouseCursors.resizeLeft + ? SystemMouseCursors.resizeLeftRight + : _mouseCursor; + return MouseRegion( - cursor: _mouseCursor, + cursor: currentCursor, onEnter: _pointerEnterEvent, onHover: _pointerHoverEvent, onExit: _pointerExitEvent, child: Stack( children: [ GestureDetector( - child: SizedBox( - height: widget.height, - width: widget.width, - child: _addDayView( - widget.width, - _timeIntervalHeight * _horizontalLinesCount!, - _isRTL, - widget.locale, - isCurrentView)), onTapUp: _handleOnTapForDay, - onLongPressStart: null, + child: SizedBox( + height: widget.height, + width: widget.width, + child: _addDayView( + widget.width, + _timeIntervalHeight * _horizontalLinesCount!, + _isRTL, + widget.locale, + isCurrentView, + ), + ), ), - _getResizeShadowView() + _getResizeShadowView(), ], ), ); @@ -5953,21 +6896,26 @@ class _CalendarViewState extends State<_CalendarView> _allDayHeight = 0; final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); if (isDayView) { final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); if (isCurrentView) { - _allDayHeight = _kAllDayLayoutHeight > viewHeaderHeight && - _updateCalendarStateDetails.allDayPanelHeight > viewHeaderHeight - ? _updateCalendarStateDetails.allDayPanelHeight > - _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : _updateCalendarStateDetails.allDayPanelHeight - : viewHeaderHeight; + _allDayHeight = + _kAllDayLayoutHeight > viewHeaderHeight && + _updateCalendarStateDetails.allDayPanelHeight > + viewHeaderHeight + ? _updateCalendarStateDetails.allDayPanelHeight > + _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight + : viewHeaderHeight; if (_allDayHeight < _updateCalendarStateDetails.allDayPanelHeight) { _allDayHeight += kAllDayAppointmentHeight; } @@ -5984,27 +6932,38 @@ class _CalendarViewState extends State<_CalendarView> } Widget _getTimelineView() { + final SystemMouseCursor currentCursor = + _mouseCursor == SystemMouseCursors.resizeUp || + _mouseCursor == SystemMouseCursors.resizeDown + ? SystemMouseCursors.resizeUpDown + : _mouseCursor == SystemMouseCursors.resizeRight || + _mouseCursor == SystemMouseCursors.resizeLeft + ? SystemMouseCursors.resizeLeftRight + : _mouseCursor; return MouseRegion( - cursor: _mouseCursor, - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: Stack(children: [ + cursor: currentCursor, + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: Stack( + children: [ GestureDetector( + onTapUp: _handleOnTapForTimeline, child: SizedBox( width: widget.width, height: widget.height, child: _addTimelineView( - _timeIntervalHeight * - (_horizontalLinesCount! * widget.visibleDates.length), - widget.height, - widget.locale), + _timeIntervalHeight * + (_horizontalLinesCount! * widget.visibleDates.length), + widget.height, + widget.locale, + ), ), - onTapUp: _handleOnTapForTimeline, - onLongPressStart: null, ), - _getResizeShadowView() - ])); + _getResizeShadowView(), + ], + ), + ); } void _timelineViewHoveringUpdate() { @@ -6021,37 +6980,47 @@ class _CalendarViewState extends State<_CalendarView> } void _scrollToPosition() { - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { if (widget.view == CalendarView.month) { return; } widget.getCalendarState(_updateCalendarStateDetails); final double scrollPosition = _getScrollPositionForCurrentDate( - _updateCalendarStateDetails.currentDate!); + _updateCalendarStateDetails.currentDate!, + ); if (scrollPosition == -1 || - _scrollController!.position.pixels == scrollPosition) { + (_scrollController != null && + _scrollController!.position.pixels == scrollPosition)) { return; } - - _scrollController!.jumpTo( + if (_scrollController != null) { + _scrollController!.jumpTo( _scrollController!.position.maxScrollExtent > scrollPosition ? scrollPosition - : _scrollController!.position.maxScrollExtent); + : _scrollController!.position.maxScrollExtent, + ); + } }); } double _getScrollPositionForCurrentDate(DateTime date) { final int visibleDatesCount = widget.visibleDates.length; - if (!isDateWithInDateRange(widget.visibleDates[0], - widget.visibleDates[visibleDatesCount - 1], date)) { + 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); + widget.calendar, + date, + _timeIntervalHeight, + ); } else { for (int i = 0; i < visibleDatesCount; i++) { if (!isSameDate(date, widget.visibleDates[i])) { @@ -6061,16 +7030,20 @@ class _CalendarViewState extends State<_CalendarView> if (widget.view == CalendarView.timelineMonth) { timeToPosition = _timeIntervalHeight * i; } else { - timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + timeToPosition = + (_getSingleViewWidthForTimeLineView(this) * i) + AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); + widget.calendar, + date, + _timeIntervalHeight, + ); } break; } } - if (_scrollController!.hasClients) { + if (_scrollController != null && _scrollController!.hasClients) { if (timeToPosition > _scrollController!.position.maxScrollExtent) { timeToPosition = _scrollController!.position.maxScrollExtent; } else if (timeToPosition < _scrollController!.position.minScrollExtent) { @@ -6093,8 +7066,9 @@ class _CalendarViewState extends State<_CalendarView> final double singleViewWidth = _getSingleViewWidthForTimeLineView(this); /// Calculate the scrolled position date. - scrolledDate = widget - .visibleDates[_scrollController!.position.pixels ~/ singleViewWidth]; + scrolledDate = + widget.visibleDates[_scrollController!.position.pixels ~/ + singleViewWidth]; /// Calculate the scrolled hour position without visible date position. scrolledPosition = _scrollController!.position.pixels % singleViewWidth; @@ -6107,18 +7081,25 @@ class _CalendarViewState extends State<_CalendarView> final double columnIndex = scrolledPosition / _timeIntervalHeight; /// Calculate the time based on calculated horizontal position. - final double time = ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / + 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); + scrolledDate.year, + scrolledDate.month, + scrolledDate.day, + hour, + minute, + ); /// Update the scrolled position after the widget generated. - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { _scrollController!.jumpTo(_getPositionFromDate(scrolledDate)); }); } @@ -6127,18 +7108,21 @@ class _CalendarViewState extends State<_CalendarView> double _getPositionFromDate(DateTime date) { final int visibleDatesCount = widget.visibleDates.length; _timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - visibleDatesCount, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + visibleDatesCount, + widget.isMobilePlatform, + ); double timeToPosition = 0; final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); if (!isTimelineView) { timeToPosition = AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); + widget.calendar, + date, + _timeIntervalHeight, + ); } else { for (int i = 0; i < visibleDatesCount; i++) { if (!isSameDate(date, widget.visibleDates[i])) { @@ -6148,9 +7132,13 @@ class _CalendarViewState extends State<_CalendarView> if (widget.view == CalendarView.timelineMonth) { timeToPosition = _timeIntervalHeight * i; } else { - timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + timeToPosition = + (_getSingleViewWidthForTimeLineView(this) * i) + AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); + widget.calendar, + date, + _timeIntervalHeight, + ); } break; @@ -6159,21 +7147,28 @@ class _CalendarViewState extends State<_CalendarView> double maxScrollPosition = 0; if (!isTimelineView) { - final double scrollViewHeight = widget.height - + final double scrollViewHeight = + widget.height - _allDayHeight - CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); final double scrollViewContentHeight = CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) * - _timeIntervalHeight; + widget.calendar.timeSlotViewSettings, + widget.view, + ) * + _timeIntervalHeight; maxScrollPosition = scrollViewContentHeight - scrollViewHeight; } else { final double scrollViewContentWidth = CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) * - _timeIntervalHeight * - visibleDatesCount; + widget.calendar.timeSlotViewSettings, + widget.view, + ) * + _timeIntervalHeight * + visibleDatesCount; maxScrollPosition = scrollViewContentWidth - widget.width; } @@ -6222,9 +7217,14 @@ class _CalendarViewState extends State<_CalendarView> if (widget.view == CalendarView.timelineMonth && widget.calendar.showWeekNumber) { final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - final DateTime? date = - _getDateFromPosition(_scrollController!.offset, 0, timeLabelWidth); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); + final DateTime? date = _getDateFromPosition( + _scrollController!.offset, + 0, + timeLabelWidth, + ); if (date != null) { widget.timelineMonthWeekNumberNotifier.value = date; } @@ -6236,32 +7236,37 @@ class _CalendarViewState extends State<_CalendarView> 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 + 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 + 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 (!CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView) && + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ) && _allDayHeight == 0) { if (_animationController!.status == AnimationStatus.completed) { _animationController!.reset(); @@ -6277,43 +7282,48 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.timeSlotViewSettings.endHour != oldWidget.calendar.timeSlotViewSettings.endHour || CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) != + widget.calendar.timeSlotViewSettings, + ) != CalendarViewHelper.getTimeInterval( - oldWidget.calendar.timeSlotViewSettings) || + 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); + widget.calendar.timeSlotViewSettings, + widget.view, + ); } else { - _horizontalLinesCount = _horizontalLinesCount ?? + _horizontalLinesCount = + _horizontalLinesCount ?? CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); + widget.calendar.timeSlotViewSettings, + widget.view, + ); } } void _updateTimelineViews(_CalendarView oldWidget) { _timelineRulerController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_timeRulerListener); + ScrollController()..addListener(_timeRulerListener); _timelineViewAnimationController ??= AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - animationBehavior: AnimationBehavior.normal); + duration: const Duration(milliseconds: 300), + vsync: this, + ); - _timelineViewAnimation ??= _timelineViewTween - .animate(_timelineViewAnimationController!) - ..addListener(_scrollAnimationListener); + _timelineViewAnimation ??= _timelineViewTween.animate( + _timelineViewAnimationController!, + )..addListener(_scrollAnimationListener); - _timelineViewHeaderScrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewVerticalScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewHeaderScrollController ??= ScrollController(); + _timelineViewVerticalScrollController = ScrollController(); _timelineViewVerticalScrollController!.addListener(_updateResourceScroll); - widget.resourcePanelScrollController - ?.addListener(_updateResourcePanelScroll); + widget.resourcePanelScrollController?.addListener( + _updateResourcePanelScroll, + ); } void _getPainterProperties(UpdateCalendarStateDetails details) { @@ -6328,24 +7338,31 @@ class _CalendarViewState extends State<_CalendarView> } Widget _addAllDayAppointmentPanel( - SfCalendarThemeData calendarTheme, bool isCurrentView) { + SfCalendarThemeData calendarTheme, + bool isCurrentView, + ) { final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); final Color borderColor = widget.calendar.cellBorderColor ?? calendarTheme.cellBorderColor!; final Widget shadowView = Divider( height: 1, thickness: 1, - color: borderColor.withOpacity(borderColor.opacity * 0.5), + color: borderColor.withValues(alpha: borderColor.a * 0.5), ); final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); double topPosition = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); if (isDayView) { topPosition = _allDayHeight; } @@ -6355,7 +7372,12 @@ class _CalendarViewState extends State<_CalendarView> widget.visibleDates != _updateCalendarStateDetails.currentViewVisibleDates)) { return Positioned( - left: 0, right: 0, top: topPosition, height: 1, child: shadowView); + left: 0, + right: 0, + top: topPosition, + height: 1, + child: shadowView, + ); } if (isDayView) { @@ -6364,9 +7386,10 @@ class _CalendarViewState extends State<_CalendarView> topPosition = 0; } - double panelHeight = isCurrentView - ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight - : 0; + double panelHeight = + isCurrentView + ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight + : 0; if (panelHeight < 0) { panelHeight = 0; } @@ -6376,7 +7399,8 @@ class _CalendarViewState extends State<_CalendarView> if (_allDaySelectionNotifier.value != null && _allDaySelectionNotifier.value!.appointmentView != null && (!_updateCalendarStateDetails.visibleAppointments.contains( - _allDaySelectionNotifier.value!.appointmentView!.appointment))) { + _allDaySelectionNotifier.value!.appointmentView!.appointment, + ))) { _allDaySelectionNotifier.value = null; } @@ -6398,74 +7422,88 @@ class _CalendarViewState extends State<_CalendarView> physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, children: [ - _getAllDayLayout(timeLabelWidth, panelHeight, - allDayExpanderHeight, isCurrentView) + _getAllDayLayout( + timeLabelWidth, + panelHeight, + allDayExpanderHeight, + isCurrentView, + ), ], ), ), Positioned( - left: 0, - top: allDayExpanderHeight - 1, - right: 0, - height: 1, - child: shadowView), + left: 0, + top: allDayExpanderHeight - 1, + right: 0, + height: 1, + child: shadowView, + ), ], ), ); } - Widget _getAllDayLayout(double timeLabelWidth, double panelHeight, - double allDayExpanderHeight, bool isCurrentView) { + Widget _getAllDayLayout( + double timeLabelWidth, + double panelHeight, + double allDayExpanderHeight, + bool isCurrentView, + ) { final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final Widget _allDayLayout = AllDayAppointmentLayout( - widget.calendar, - widget.view, - widget.visibleDates, - widget.visibleDates == - _updateCalendarStateDetails.currentViewVisibleDates - ? _updateCalendarStateDetails.visibleAppointments - : null, - timeLabelWidth, - allDayExpanderHeight, - panelHeight > 0 && (_heightAnimation!.value == 1 || isDayView), - _allDayExpanderAnimation!.value != 0.0 && - _allDayExpanderAnimation!.value != 1, - _isRTL, - widget.calendarTheme, - _allDaySelectionNotifier, - _allDayNotifier, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.width, - (isDayView && - _updateCalendarStateDetails.allDayPanelHeight < - _allDayHeight) || - !isCurrentView - ? _allDayHeight - : _updateCalendarStateDetails.allDayPanelHeight, - widget.localizations, - _getPainterProperties); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final Widget allDayLayout = AllDayAppointmentLayout( + widget.calendar, + widget.view, + widget.visibleDates, + widget.visibleDates == _updateCalendarStateDetails.currentViewVisibleDates + ? _updateCalendarStateDetails.visibleAppointments + : null, + timeLabelWidth, + allDayExpanderHeight, + panelHeight > 0 && (_heightAnimation!.value == 1 || isDayView), + _allDayExpanderAnimation!.value != 0.0 && + _allDayExpanderAnimation!.value != 1, + _isRTL, + widget.calendarTheme, + widget.themeData, + _allDaySelectionNotifier, + _allDayNotifier, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.width, + (isDayView && + _updateCalendarStateDetails.allDayPanelHeight < + _allDayHeight) || + !isCurrentView + ? _allDayHeight + : _updateCalendarStateDetails.allDayPanelHeight, + widget.localizations, + _getPainterProperties, + ); if ((_mouseCursor == SystemMouseCursors.basic || _mouseCursor == SystemMouseCursors.move) || !widget.calendar.allowAppointmentResize) { - return _allDayLayout; + return allDayLayout; } else { return GestureDetector( - child: _allDayLayout, onHorizontalDragStart: _onHorizontalStart, onHorizontalDragUpdate: _onHorizontalUpdate, onHorizontalDragEnd: _onHorizontalEnd, + child: allDayLayout, ); } } - Widget _addAppointmentPainter(double width, double height, - [double? resourceItemHeight]) { + Widget _addAppointmentPainter( + double width, + double height, [ + double? resourceItemHeight, + ]) { final List? visibleAppointments = widget.visibleDates == _updateCalendarStateDetails.currentViewVisibleDates @@ -6478,6 +7516,7 @@ class _CalendarViewState extends State<_CalendarView> ValueNotifier?>(visibleAppointments), _timeIntervalHeight, widget.calendarTheme, + widget.themeData, _isRTL, _appointmentHoverNotifier, widget.resourceCollection, @@ -6498,31 +7537,39 @@ class _CalendarViewState extends State<_CalendarView> final double xPosition = details.localPosition.dx; double yPosition = details.localPosition.dy; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); AppointmentView? appointmentView; const double padding = 10; final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeDown; final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeUp; final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); if (!CalendarViewHelper.isTimelineView(widget.view) && widget.view != CalendarView.month) { if (xPosition < timeLabelWidth) { return; } - final double allDayPanelHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + final double allDayPanelHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; - yPosition = yPosition - + yPosition = + yPosition - viewHeaderHeight - allDayPanelHeight + _scrollController!.offset; @@ -6532,14 +7579,17 @@ class _CalendarViewState extends State<_CalendarView> } else if (isForwardResize) { yPosition -= padding; } - appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + appointmentView = _appointmentLayout.getAppointmentViewOnPoint( + xPosition, + yPosition, + ); if (appointmentView == null) { return; } _resizingDetails.value.isAllDayPanel = false; - yPosition = details.localPosition.dy - + yPosition = + details.localPosition.dy - viewHeaderHeight - allDayPanelHeight + _scrollController!.offset; @@ -6552,27 +7602,47 @@ class _CalendarViewState extends State<_CalendarView> return; } - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - appointmentView, allDayPanelHeight, viewHeaderHeight); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + appointmentView, + allDayPanelHeight, + viewHeaderHeight, + ); _resizingDetails.value.position.value = Offset( - appointmentView.appointmentRect!.left, details.localPosition.dy); + appointmentView.appointmentRect!.left, + details.localPosition.dy, + ); } - _resizingDetails.value.resizingTime = isBackwardResize - ? _resizingDetails.value.appointmentView!.appointment!.actualStartTime - : _resizingDetails.value.appointmentView!.appointment!.actualEndTime; + _resizingDetails.value.resizingTime = + isBackwardResize + ? _resizingDetails + .value + .appointmentView! + .appointment! + .actualStartTime + : _resizingDetails + .value + .appointmentView! + .appointment! + .actualEndTime; _resizingDetails.value.scrollPosition = null; if (widget.calendar.appointmentBuilder == null) { _resizingDetails.value.appointmentColor = appointmentView!.appointment!.color; } if (CalendarViewHelper.shouldRaiseAppointmentResizeStartCallback( - widget.calendar.onAppointmentResizeStart)) { + widget.calendar.onAppointmentResizeStart, + )) { CalendarViewHelper.raiseAppointmentResizeStartCallback( + widget.calendar, + _getCalendarAppointmentToObject( + appointmentView!.appointment, widget.calendar, - _getCalendarAppointmentToObject( - appointmentView!.appointment, widget.calendar), - null); + ), + null, + ); } } @@ -6582,43 +7652,61 @@ class _CalendarViewState extends State<_CalendarView> } final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); double yPosition = details.localPosition.dy; final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeDown; final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeUp; - final double allDayPanelHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + final double allDayPanelHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; if (!CalendarViewHelper.isTimelineView(widget.view) && widget.view != CalendarView.month) { _updateMaximumResizingPosition( - isForwardResize, - isBackwardResize, - _resizingDetails.value.appointmentView!, - allDayPanelHeight, - viewHeaderHeight); + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + allDayPanelHeight, + viewHeaderHeight, + ); if ((isForwardResize && yPosition < _maximumResizingPosition!) || (isBackwardResize && yPosition > _maximumResizingPosition!)) { yPosition = _maximumResizingPosition!; } - _updateAutoScrollDay(details, viewHeaderHeight, allDayPanelHeight, - isForwardResize, isBackwardResize, yPosition); + _updateAutoScrollDay( + details, + viewHeaderHeight, + allDayPanelHeight, + isForwardResize, + isBackwardResize, + yPosition, + ); } _resizingDetails.value.scrollPosition = null; _resizingDetails.value.position.value = Offset( - _resizingDetails.value.appointmentView!.appointmentRect!.left, - yPosition); - _updateAppointmentResizingUpdateCallback(isForwardResize, isBackwardResize, - yPosition, viewHeaderHeight, allDayPanelHeight); + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition, + ); + _updateAppointmentResizingUpdateCallback( + isForwardResize, + isBackwardResize, + yPosition, + viewHeaderHeight, + allDayPanelHeight, + ); } void _onVerticalEnd(DragEndDetails details) { @@ -6633,24 +7721,30 @@ class _CalendarViewState extends State<_CalendarView> } final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); - final double allDayPanelHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + final double allDayPanelHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; final double currentYPosition = _resizingDetails.value.position.value!.dy > widget.height - 1 ? widget.height - 1 : _resizingDetails.value.position.value!.dy; - double yPosition = currentYPosition - + double yPosition = + currentYPosition - viewHeaderHeight - allDayPanelHeight + _scrollController!.offset; @@ -6658,35 +7752,41 @@ class _CalendarViewState extends State<_CalendarView> final CalendarAppointment appointment = _resizingDetails.value.appointmentView!.appointment!; final double timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); final double overAllHeight = _timeIntervalHeight * _horizontalLinesCount!; if (overAllHeight < widget.height && yPosition > overAllHeight) { yPosition = overAllHeight; } - final DateTime resizingTime = _timeFromPosition( - appointment.actualStartTime, - widget.calendar.timeSlotViewSettings, - yPosition, - null, - timeIntervalHeight, - false)!; + final DateTime resizingTime = + _timeFromPosition( + appointment.actualStartTime, + widget.calendar.timeSlotViewSettings, + yPosition, + null, + timeIntervalHeight, + false, + )!; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); DateTime updatedStartTime = appointment.actualStartTime, updatedEndTime = appointment.actualEndTime; if (AppointmentHelper.canAddSpanIcon( - widget.visibleDates, appointment, widget.view)) { + widget.visibleDates, + appointment, + widget.view, + )) { updatedStartTime = appointment.exactStartTime; updatedEndTime = appointment.exactEndTime; } @@ -6700,30 +7800,39 @@ class _CalendarViewState extends State<_CalendarView> final DateTime callbackStartDate = updatedStartTime; final DateTime callbackEndDate = updatedEndTime; updatedStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - updatedStartTime, widget.calendar.timeZone, appointment.startTimeZone); + updatedStartTime, + widget.calendar.timeZone, + appointment.startTimeZone, + ); updatedEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - updatedEndTime, widget.calendar.timeZone, appointment.endTimeZone); + updatedEndTime, + widget.calendar.timeZone, + appointment.endTimeZone, + ); if (CalendarViewHelper.isDraggingAppointmentHasDisabledCell( - widget.regions!, - widget.blackoutDates!, - updatedStartTime, - updatedEndTime, - false, - false, - widget.calendar.minDate, - widget.calendar.maxDate, - timeInterval, - -1, - widget.resourceCollection)) { + widget.regions!, + widget.blackoutDates!, + updatedStartTime, + updatedEndTime, + false, + false, + widget.calendar.minDate, + widget.calendar.maxDate, + timeInterval, + -1, + widget.resourceCollection, + )) { if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( - widget.calendar.onAppointmentResizeEnd)) { + widget.calendar.onAppointmentResizeEnd, + )) { CalendarViewHelper.raiseAppointmentResizeEndCallback( - widget.calendar, - appointment.data, - null, - appointment.exactStartTime, - appointment.exactEndTime); + widget.calendar, + appointment.data, + null, + appointment.exactStartTime, + appointment.exactEndTime, + ); } _resetResizingPainter(); @@ -6733,9 +7842,11 @@ class _CalendarViewState extends State<_CalendarView> CalendarAppointment? parentAppointment; if (appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty) { - for (int i = 0; - i < _updateCalendarStateDetails.appointments.length; - i++) { + for ( + int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++ + ) { final CalendarAppointment app = _updateCalendarStateDetails.appointments[i]; if (app.id == appointment.id) { @@ -6746,46 +7857,68 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.dataSource!.appointments!.remove(parentAppointment!.data); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [parentAppointment.data]); + CalendarDataSourceAction.remove, + [parentAppointment.data], + ); final DateTime exceptionDate = AppointmentHelper.convertTimeToAppointmentTimeZone( - appointment.exactStartTime, widget.calendar.timeZone, ''); + appointment.exactStartTime, + widget.calendar.timeZone, + '', + ); parentAppointment.recurrenceExceptionDates != null ? parentAppointment.recurrenceExceptionDates!.add(exceptionDate) : parentAppointment.recurrenceExceptionDates = [ - exceptionDate - ]; + exceptionDate, + ]; - final dynamic newParentAppointment = - _getCalendarAppointmentToObject(parentAppointment, widget.calendar); + final dynamic newParentAppointment = _getCalendarAppointmentToObject( + parentAppointment, + widget.calendar, + ); widget.calendar.dataSource!.appointments!.add(newParentAppointment); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.add, [newParentAppointment]); + CalendarDataSourceAction.add, + [newParentAppointment], + ); } else { widget.calendar.dataSource!.appointments!.remove(appointment.data); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [appointment.data]); + CalendarDataSourceAction.remove, + [appointment.data], + ); } appointment.startTime = updatedStartTime; appointment.endTime = updatedEndTime; - appointment.recurrenceId = parentAppointment != null - ? parentAppointment.id - : appointment.recurrenceId; + appointment.recurrenceId = + parentAppointment != null + ? parentAppointment.id + : appointment.recurrenceId; appointment.recurrenceRule = appointment.recurrenceId != null ? null : appointment.recurrenceRule; appointment.id = parentAppointment != null ? null : appointment.id; - final dynamic newAppointment = - _getCalendarAppointmentToObject(appointment, widget.calendar); + final dynamic newAppointment = _getCalendarAppointmentToObject( + appointment, + widget.calendar, + ); widget.calendar.dataSource!.appointments!.add(newAppointment); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.add, [newAppointment]); + CalendarDataSourceAction.add, + [newAppointment], + ); if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( - widget.calendar.onAppointmentResizeEnd)) { - CalendarViewHelper.raiseAppointmentResizeEndCallback(widget.calendar, - newAppointment, null, callbackStartDate, callbackEndDate); + widget.calendar.onAppointmentResizeEnd, + )) { + CalendarViewHelper.raiseAppointmentResizeEndCallback( + widget.calendar, + newAppointment, + null, + callbackStartDate, + callbackEndDate, + ); } _resetResizingPainter(); @@ -6793,19 +7926,25 @@ class _CalendarViewState extends State<_CalendarView> void _onHorizontalStart(DragStartDetails details) { final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); double xPosition = details.localPosition.dx; CalendarResource? resource; double yPosition = details.localPosition.dy; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); AppointmentView? appointmentView; const double padding = 10; @@ -6824,9 +7963,10 @@ class _CalendarViewState extends State<_CalendarView> } appointmentView = _getAllDayAppointmentOnPoint( - _updateCalendarStateDetails.allDayAppointmentViewCollection, - xPosition, - yPosition); + _updateCalendarStateDetails.allDayAppointmentViewCollection, + xPosition, + yPosition, + ); if (appointmentView == null) { return; } @@ -6834,16 +7974,23 @@ class _CalendarViewState extends State<_CalendarView> xPosition = details.localPosition.dx; yPosition = appointmentView.appointmentRect!.top + viewHeaderHeight; _resizingDetails.value.isAllDayPanel = true; - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - appointmentView, null, viewHeaderHeight); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + appointmentView, + null, + viewHeaderHeight, + ); } else if (isTimelineView) { yPosition -= viewHeaderHeight + timeLabelWidth; xPosition = _scrollController!.offset + details.localPosition.dx; if (_isRTL) { - xPosition = _scrollController!.offset + + xPosition = + _scrollController!.offset + (_scrollController!.position.viewportDimension - details.localPosition.dx); - xPosition = (_scrollController!.position.viewportDimension + + xPosition = + (_scrollController!.position.viewportDimension + _scrollController!.position.maxScrollExtent) - xPosition; } @@ -6855,32 +8002,45 @@ class _CalendarViewState extends State<_CalendarView> } final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + widget.calendar.dataSource, + widget.view, + ); if (isResourceEnabled) { yPosition += _timelineViewVerticalScrollController!.offset; } - appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + appointmentView = _appointmentLayout.getAppointmentViewOnPoint( + xPosition, + yPosition, + ); _resizingDetails.value.isAllDayPanel = false; if (appointmentView == null) { return; } if (isResourceEnabled) { - resource = widget.calendar.dataSource!.resources![ - _getSelectedResourceIndex(appointmentView.appointmentRect!.top, - viewHeaderHeight, timeLabelWidth)]; + resource = + widget.calendar.dataSource!.resources![_getSelectedResourceIndex( + appointmentView.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth, + )]; } - yPosition = appointmentView.appointmentRect!.top + + yPosition = + appointmentView.appointmentRect!.top + viewHeaderHeight + timeLabelWidth; if (isResourceEnabled) { yPosition -= _timelineViewVerticalScrollController!.offset; } - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - appointmentView, null, viewHeaderHeight); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + appointmentView, + null, + viewHeaderHeight, + ); } else if (widget.view == CalendarView.month) { _resizingDetails.value.monthRowCount = 0; yPosition -= viewHeaderHeight; @@ -6891,8 +8051,10 @@ class _CalendarViewState extends State<_CalendarView> xPosition -= padding; } - appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + appointmentView = _appointmentLayout.getAppointmentViewOnPoint( + xPosition, + yPosition, + ); _resizingDetails.value.isAllDayPanel = false; if (appointmentView == null) { return; @@ -6901,8 +8063,13 @@ class _CalendarViewState extends State<_CalendarView> xPosition = details.localPosition.dx; yPosition = appointmentView.appointmentRect!.top + viewHeaderHeight; - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - appointmentView, null, viewHeaderHeight); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + appointmentView, + null, + viewHeaderHeight, + ); } if (_mouseCursor != SystemMouseCursors.basic && @@ -6919,23 +8086,47 @@ class _CalendarViewState extends State<_CalendarView> appointmentView.appointment!.color; } if (isTimelineView && _isRTL) { - _resizingDetails.value.resizingTime = isForwardResize - ? _resizingDetails.value.appointmentView!.appointment!.actualStartTime - : _resizingDetails.value.appointmentView!.appointment!.actualEndTime; + _resizingDetails.value.resizingTime = + isForwardResize + ? _resizingDetails + .value + .appointmentView! + .appointment! + .actualStartTime + : _resizingDetails + .value + .appointmentView! + .appointment! + .actualEndTime; } else { - _resizingDetails.value.resizingTime = isBackwardResize - ? _resizingDetails.value.appointmentView!.appointment!.actualStartTime - : _resizingDetails.value.appointmentView!.appointment!.actualEndTime; + _resizingDetails.value.resizingTime = + isBackwardResize + ? _resizingDetails + .value + .appointmentView! + .appointment! + .actualStartTime + : _resizingDetails + .value + .appointmentView! + .appointment! + .actualEndTime; } - _resizingDetails.value.position.value = - Offset(details.localPosition.dx, yPosition); + _resizingDetails.value.position.value = Offset( + details.localPosition.dx, + yPosition, + ); if (CalendarViewHelper.shouldRaiseAppointmentResizeStartCallback( - widget.calendar.onAppointmentResizeStart)) { + widget.calendar.onAppointmentResizeStart, + )) { CalendarViewHelper.raiseAppointmentResizeStartCallback( + widget.calendar, + _getCalendarAppointmentToObject( + appointmentView.appointment, widget.calendar, - _getCalendarAppointmentToObject( - appointmentView.appointment, widget.calendar), - resource); + ), + resource, + ); } } @@ -6945,44 +8136,56 @@ class _CalendarViewState extends State<_CalendarView> } final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + widget.calendar.dataSource, + widget.view, + ); final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeRight; final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeLeft; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); double xPosition = details.localPosition.dx; double yPosition = _resizingDetails.value.position.value!.dy; late DateTime resizingTime; final double timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); if (isTimelineView) { - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - _resizingDetails.value.appointmentView!, null, null); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + null, + null, + ); if ((isForwardResize && xPosition < _maximumResizingPosition!) || (isBackwardResize && xPosition > _maximumResizingPosition!)) { xPosition = _maximumResizingPosition!; } _updateAutoScrollTimeline( - details, - timeIntervalHeight, - isForwardResize, - isBackwardResize, - xPosition, - yPosition, - timeLabelWidth, - isResourceEnabled); + details, + timeIntervalHeight, + isForwardResize, + isBackwardResize, + xPosition, + yPosition, + timeLabelWidth, + isResourceEnabled, + ); } else if (widget.view == CalendarView.month) { final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); double resizingPosition = details.localPosition.dy - viewHeaderHeight; if (resizingPosition < 0) { resizingPosition = 0; @@ -6990,7 +8193,8 @@ class _CalendarViewState extends State<_CalendarView> resizingPosition = widget.height - viewHeaderHeight - 1; } - final double cellHeight = (widget.height - viewHeaderHeight) / + final double cellHeight = + (widget.height - viewHeaderHeight) / widget.calendar.monthViewSettings.numberOfWeeksInView; final int appointmentRowIndex = (_resizingDetails.value.appointmentView!.appointmentRect!.top / @@ -6999,9 +8203,10 @@ class _CalendarViewState extends State<_CalendarView> int resizingRowIndex = (resizingPosition / cellHeight).truncate(); final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if (!_isRTL) { if (xPosition < weekNumberPanelWidth) { xPosition = weekNumberPanelWidth; @@ -7023,11 +8228,13 @@ class _CalendarViewState extends State<_CalendarView> final DateTime currentMonthDate = widget.visibleDates[widget.visibleDates.length ~/ 2]; final int startIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, - AppointmentHelper.getMonthStartDate(currentMonthDate)); + widget.visibleDates, + AppointmentHelper.getMonthStartDate(currentMonthDate), + ); final int endIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, - AppointmentHelper.getMonthEndDate(currentMonthDate)); + widget.visibleDates, + AppointmentHelper.getMonthEndDate(currentMonthDate), + ); final int startRowCount = startIndex ~/ DateTime.daysPerWeek; final int startColumnCount = startIndex % DateTime.daysPerWeek; final int endRowCount = endIndex ~/ DateTime.daysPerWeek; @@ -7114,11 +8321,12 @@ class _CalendarViewState extends State<_CalendarView> resizingTime = _getDateFromPosition(xPosition, resizingPosition, timeLabelWidth)!; - final int rowDifference = isBackwardResize - ? _isRTL - ? (appointmentRowIndex - resizingRowIndex).abs() - : appointmentRowIndex - resizingRowIndex - : _isRTL + final int rowDifference = + isBackwardResize + ? _isRTL + ? (appointmentRowIndex - resizingRowIndex).abs() + : appointmentRowIndex - resizingRowIndex + : _isRTL ? appointmentRowIndex - resizingRowIndex : (appointmentRowIndex - resizingRowIndex).abs(); if (((!_isRTL && @@ -7170,7 +8378,7 @@ class _CalendarViewState extends State<_CalendarView> _resizingDetails.value.monthCellHeight = cellHeight; yPosition = _resizingDetails.value.appointmentView!.appointmentRect!.top + - viewHeaderHeight; + viewHeaderHeight; } } else { if ((isForwardResize && xPosition < _maximumResizingPosition!) || @@ -7201,31 +8409,42 @@ class _CalendarViewState extends State<_CalendarView> if (_resizingDetails.value.isAllDayPanel || widget.view == CalendarView.month) { resizingTime = DateTime( - resizingTime.year, resizingTime.month, resizingTime.day, 0, 0, 0); + resizingTime.year, + resizingTime.month, + resizingTime.day, + ); } _resizingDetails.value.position.value = Offset(xPosition, yPosition); if (isTimelineView) { _updateAppointmentResizingUpdateCallback( - isForwardResize, isBackwardResize, yPosition, null, null, - xPosition: xPosition, - timeLabelWidth: timeLabelWidth, - isResourceEnabled: isResourceEnabled, - details: details); + isForwardResize, + isBackwardResize, + yPosition, + null, + null, + xPosition: xPosition, + timeLabelWidth: timeLabelWidth, + isResourceEnabled: isResourceEnabled, + details: details, + ); return; } if (CalendarViewHelper.shouldRaiseAppointmentResizeUpdateCallback( - widget.calendar.onAppointmentResizeUpdate)) { + widget.calendar.onAppointmentResizeUpdate, + )) { CalendarViewHelper.raiseAppointmentResizeUpdateCallback( + widget.calendar, + _getCalendarAppointmentToObject( + _resizingDetails.value.appointmentView!.appointment, widget.calendar, - _getCalendarAppointmentToObject( - _resizingDetails.value.appointmentView!.appointment, - widget.calendar), - null, - resizingTime, - _resizingDetails.value.position.value!); + ), + null, + resizingTime, + _resizingDetails.value.position.value!, + ); } } @@ -7241,14 +8460,18 @@ class _CalendarViewState extends State<_CalendarView> } final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); @@ -7256,21 +8479,25 @@ class _CalendarViewState extends State<_CalendarView> double yPosition = _resizingDetails.value.position.value!.dy; final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + widget.calendar.dataSource, + widget.view, + ); final CalendarAppointment appointment = _resizingDetails.value.appointmentView!.appointment!; final double timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); if (!isTimelineView && widget.view != CalendarView.month) { if (_isRTL) { if (xPosition > widget.width - timeLabelWidth - 1) { @@ -7290,9 +8517,10 @@ class _CalendarViewState extends State<_CalendarView> } else if (widget.view == CalendarView.month) { final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); _resizingDetails.value.monthRowCount = 0; if (!_isRTL) { if (xPosition < weekNumberPanelWidth) { @@ -7311,7 +8539,8 @@ class _CalendarViewState extends State<_CalendarView> xPosition = widget.width - 1; } - final double overAllWidth = _timeIntervalHeight * + final double overAllWidth = + _timeIntervalHeight * (_horizontalLinesCount! * widget.visibleDates.length); if (overAllWidth < widget.width && xPosition > overAllWidth) { @@ -7325,51 +8554,75 @@ class _CalendarViewState extends State<_CalendarView> widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth) { resizingTime = DateTime( - resizingTime.year, resizingTime.month, resizingTime.day, 0, 0, 0); + resizingTime.year, + resizingTime.month, + resizingTime.day, + ); } else if (isTimelineView) { - final DateTime time = _timeFromPosition( - resizingTime, - widget.calendar.timeSlotViewSettings, - xPosition, - this, - timeIntervalHeight, - isTimelineView)!; + final DateTime time = + _timeFromPosition( + resizingTime, + widget.calendar.timeSlotViewSettings, + xPosition, + this, + timeIntervalHeight, + isTimelineView, + )!; - resizingTime = DateTime(resizingTime.year, resizingTime.month, - resizingTime.day, time.hour, time.minute, time.second); + resizingTime = DateTime( + resizingTime.year, + resizingTime.month, + resizingTime.day, + time.hour, + time.minute, + time.second, + ); } CalendarResource? resource; int selectedResourceIndex = -1; if (isResourceEnabled) { selectedResourceIndex = _getSelectedResourceIndex( - _resizingDetails.value.appointmentView!.appointmentRect!.top, - viewHeaderHeight, - timeLabelWidth); + _resizingDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth, + ); resource = widget.calendar.dataSource!.resources![selectedResourceIndex]; } - final bool isMonthView = widget.view == CalendarView.timelineMonth || + final bool isMonthView = + widget.view == CalendarView.timelineMonth || widget.view == CalendarView.month; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); DateTime updatedStartTime = appointment.actualStartTime, updatedEndTime = appointment.actualEndTime; if ((_isRTL && _mouseCursor == SystemMouseCursors.resizeLeft) || (!_isRTL && _mouseCursor == SystemMouseCursors.resizeRight)) { if (isMonthView) { - updatedEndTime = DateTime(resizingTime.year, resizingTime.month, - resizingTime.day, updatedEndTime.hour, updatedEndTime.minute); + updatedEndTime = DateTime( + resizingTime.year, + resizingTime.month, + resizingTime.day, + updatedEndTime.hour, + updatedEndTime.minute, + ); } else { updatedEndTime = resizingTime; } } else if ((_isRTL && _mouseCursor == SystemMouseCursors.resizeRight) || (!_isRTL && _mouseCursor == SystemMouseCursors.resizeLeft)) { if (isMonthView) { - updatedStartTime = DateTime(resizingTime.year, resizingTime.month, - resizingTime.day, updatedStartTime.hour, updatedStartTime.minute); + updatedStartTime = DateTime( + resizingTime.year, + resizingTime.month, + resizingTime.day, + updatedStartTime.hour, + updatedStartTime.minute, + ); } else { updatedStartTime = resizingTime; } @@ -7378,29 +8631,38 @@ class _CalendarViewState extends State<_CalendarView> final DateTime callbackStartDate = updatedStartTime; final DateTime callbackEndDate = updatedEndTime; updatedStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - updatedStartTime, widget.calendar.timeZone, appointment.startTimeZone); + updatedStartTime, + widget.calendar.timeZone, + appointment.startTimeZone, + ); updatedEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - updatedEndTime, widget.calendar.timeZone, appointment.endTimeZone); + updatedEndTime, + widget.calendar.timeZone, + appointment.endTimeZone, + ); if (CalendarViewHelper.isDraggingAppointmentHasDisabledCell( - widget.regions!, - widget.blackoutDates!, - updatedStartTime, - updatedEndTime, - isTimelineView, - isMonthView, - widget.calendar.minDate, - widget.calendar.maxDate, - timeInterval, - selectedResourceIndex, - widget.resourceCollection)) { + widget.regions!, + widget.blackoutDates!, + updatedStartTime, + updatedEndTime, + isTimelineView, + isMonthView, + widget.calendar.minDate, + widget.calendar.maxDate, + timeInterval, + selectedResourceIndex, + widget.resourceCollection, + )) { if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( - widget.calendar.onAppointmentResizeEnd)) { + widget.calendar.onAppointmentResizeEnd, + )) { CalendarViewHelper.raiseAppointmentResizeEndCallback( - widget.calendar, - appointment.data, - resource, - appointment.exactStartTime, - appointment.exactEndTime); + widget.calendar, + appointment.data, + resource, + appointment.exactStartTime, + appointment.exactEndTime, + ); } _resetResizingPainter(); @@ -7412,9 +8674,11 @@ class _CalendarViewState extends State<_CalendarView> if ((appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty) || appointment.recurrenceId != null) { - for (int i = 0; - i < _updateCalendarStateDetails.appointments.length; - i++) { + for ( + int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++ + ) { final CalendarAppointment app = _updateCalendarStateDetails.appointments[i]; if (app.id == appointment.id || app.id == appointment.recurrenceId) { @@ -7425,49 +8689,61 @@ class _CalendarViewState extends State<_CalendarView> final List recurrenceDates = RecurrenceHelper.getRecurrenceDateTimeCollection( - parentAppointment!.recurrenceRule ?? '', + parentAppointment!.recurrenceRule ?? '', + parentAppointment.exactStartTime, + recurrenceDuration: AppointmentHelper.getDifference( parentAppointment.exactStartTime, - recurrenceDuration: AppointmentHelper.getDifference( - parentAppointment.exactStartTime, - parentAppointment.exactEndTime), - specificStartDate: widget.visibleDates[0], - specificEndDate: - widget.visibleDates[widget.visibleDates.length - 1]); - - for (int i = 0; - i < _updateCalendarStateDetails.appointments.length; - i++) { + parentAppointment.exactEndTime, + ), + specificStartDate: widget.visibleDates[0], + specificEndDate: + widget.visibleDates[widget.visibleDates.length - 1], + ); + + for ( + int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++ + ) { final CalendarAppointment calendarApp = _updateCalendarStateDetails.appointments[i]; if (calendarApp.recurrenceId != null && calendarApp.recurrenceId == parentAppointment.id) { recurrenceDates.add( - AppointmentHelper.convertTimeToAppointmentTimeZone( - calendarApp.startTime, - calendarApp.startTimeZone, - widget.calendar.timeZone)); + AppointmentHelper.convertTimeToAppointmentTimeZone( + calendarApp.startTime, + calendarApp.startTimeZone, + widget.calendar.timeZone, + ), + ); } } if (parentAppointment.recurrenceExceptionDates != null) { - for (int i = 0; - i < parentAppointment.recurrenceExceptionDates!.length; - i++) { + for ( + int i = 0; + i < parentAppointment.recurrenceExceptionDates!.length; + i++ + ) { recurrenceDates.remove( - AppointmentHelper.convertTimeToAppointmentTimeZone( - parentAppointment.recurrenceExceptionDates![i], - '', - widget.calendar.timeZone)); + AppointmentHelper.convertTimeToAppointmentTimeZone( + parentAppointment.recurrenceExceptionDates![i], + '', + widget.calendar.timeZone, + ), + ); } } recurrenceDates.sort(); - final int currentRecurrenceIndex = - recurrenceDates.indexOf(appointment.exactStartTime); + final int currentRecurrenceIndex = recurrenceDates.indexOf( + appointment.exactStartTime, + ); if (currentRecurrenceIndex != -1) { - final DateTime? previousRecurrence = currentRecurrenceIndex <= 0 - ? null - : recurrenceDates[currentRecurrenceIndex - 1]; + final DateTime? previousRecurrence = + currentRecurrenceIndex <= 0 + ? null + : recurrenceDates[currentRecurrenceIndex - 1]; final DateTime? nextRecurrence = currentRecurrenceIndex >= recurrenceDates.length - 1 ? null @@ -7485,13 +8761,15 @@ class _CalendarViewState extends State<_CalendarView> !isSameDate(nextRecurrence, resizingTime)) && !isSameDate(appointment.exactStartTime, resizingTime)) { if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( - widget.calendar.onAppointmentResizeEnd)) { + widget.calendar.onAppointmentResizeEnd, + )) { CalendarViewHelper.raiseAppointmentResizeEndCallback( - widget.calendar, - appointment.data, - resource, - appointment.exactStartTime, - appointment.exactEndTime); + widget.calendar, + appointment.data, + resource, + appointment.exactStartTime, + appointment.exactEndTime, + ); } _resetResizingPainter(); @@ -7504,77 +8782,103 @@ class _CalendarViewState extends State<_CalendarView> appointment.recurrenceRule!.isEmpty)) { widget.calendar.dataSource!.appointments!.remove(appointment.data); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [appointment.data]); + CalendarDataSourceAction.remove, + [appointment.data], + ); } else { - widget.calendar.dataSource!.appointments! - .remove(parentAppointment.data); + widget.calendar.dataSource!.appointments!.remove( + parentAppointment.data, + ); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [parentAppointment.data]); + CalendarDataSourceAction.remove, + [parentAppointment.data], + ); final DateTime exceptionDate = AppointmentHelper.convertTimeToAppointmentTimeZone( - appointment.exactStartTime, widget.calendar.timeZone, ''); + appointment.exactStartTime, + widget.calendar.timeZone, + '', + ); parentAppointment.recurrenceExceptionDates != null ? parentAppointment.recurrenceExceptionDates!.add(exceptionDate) : parentAppointment.recurrenceExceptionDates = [ - exceptionDate - ]; + exceptionDate, + ]; - final dynamic newParentAppointment = - _getCalendarAppointmentToObject(parentAppointment, widget.calendar); + final dynamic newParentAppointment = _getCalendarAppointmentToObject( + parentAppointment, + widget.calendar, + ); widget.calendar.dataSource!.appointments!.add(newParentAppointment); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.add, [newParentAppointment]); + CalendarDataSourceAction.add, + [newParentAppointment], + ); } } else { widget.calendar.dataSource!.appointments!.remove(appointment.data); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.remove, [appointment.data]); + CalendarDataSourceAction.remove, + [appointment.data], + ); } appointment.startTime = updatedStartTime; appointment.endTime = updatedEndTime; - appointment.recurrenceId = parentAppointment != null - ? parentAppointment.id - : appointment.recurrenceId; + appointment.recurrenceId = + parentAppointment != null + ? parentAppointment.id + : appointment.recurrenceId; appointment.recurrenceRule = appointment.recurrenceId != null ? null : appointment.recurrenceRule; appointment.id = parentAppointment != null ? null : appointment.id; - final dynamic newAppointment = - _getCalendarAppointmentToObject(appointment, widget.calendar); + final dynamic newAppointment = _getCalendarAppointmentToObject( + appointment, + widget.calendar, + ); widget.calendar.dataSource!.appointments!.add(newAppointment); widget.calendar.dataSource!.notifyListeners( - CalendarDataSourceAction.add, [newAppointment]); + CalendarDataSourceAction.add, + [newAppointment], + ); if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( - widget.calendar.onAppointmentResizeEnd)) { - CalendarViewHelper.raiseAppointmentResizeEndCallback(widget.calendar, - newAppointment, resource, callbackStartDate, callbackEndDate); + widget.calendar.onAppointmentResizeEnd, + )) { + CalendarViewHelper.raiseAppointmentResizeEndCallback( + widget.calendar, + newAppointment, + resource, + callbackStartDate, + callbackEndDate, + ); } _resetResizingPainter(); } Future _updateAutoScrollDay( - DragUpdateDetails details, - double viewHeaderHeight, - double allDayPanelHeight, - bool isForwardResize, - bool isBackwardResize, - double? yPosition) async { + DragUpdateDetails details, + double viewHeaderHeight, + double allDayPanelHeight, + bool isForwardResize, + bool isBackwardResize, + double? yPosition, + ) async { if (_resizingDetails.value.appointmentView == null) { return; } - final double timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + final double timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); if (yPosition! <= viewHeaderHeight + allDayPanelHeight && _scrollController!.position.pixels != 0) { @@ -7586,7 +8890,7 @@ class _CalendarViewState extends State<_CalendarView> if (yPosition != null && yPosition! <= viewHeaderHeight + allDayPanelHeight && _scrollController!.offset != 0) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = _scrollController!.position.pixels - timeIntervalHeight; if (scrollPosition < 0) { @@ -7596,8 +8900,9 @@ class _CalendarViewState extends State<_CalendarView> _resizingDetails.value.scrollPosition = scrollPosition; _resizingDetails.value.position.value = Offset( - _resizingDetails.value.appointmentView!.appointmentRect!.left, - yPosition! - 0.1); + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition! - 0.1, + ); await _scrollController!.position.animateTo( scrollPosition, @@ -7615,37 +8920,40 @@ class _CalendarViewState extends State<_CalendarView> yPosition = _resizingDetails.value.position.value?.dy; _updateMaximumResizingPosition( - isForwardResize, - isBackwardResize, - _resizingDetails.value.appointmentView!, - allDayPanelHeight, - viewHeaderHeight); + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + allDayPanelHeight, + viewHeaderHeight, + ); if ((isForwardResize && yPosition! < _maximumResizingPosition!) || (isBackwardResize && yPosition! > _maximumResizingPosition!)) { yPosition = _maximumResizingPosition; } _updateAppointmentResizingUpdateCallback( - isForwardResize, - isBackwardResize, - yPosition!, - viewHeaderHeight, - allDayPanelHeight); + isForwardResize, + isBackwardResize, + yPosition!, + viewHeaderHeight, + allDayPanelHeight, + ); _resizingDetails.value.position.value = Offset( - _resizingDetails.value.appointmentView!.appointmentRect!.left, - yPosition!); + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition!, + ); if (yPosition != null && yPosition! <= viewHeaderHeight + allDayPanelHeight && _scrollController!.offset != 0) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; @@ -7663,7 +8971,7 @@ class _CalendarViewState extends State<_CalendarView> yPosition! >= widget.height && _scrollController!.position.pixels != _scrollController!.position.maxScrollExtent) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = _scrollController!.position.pixels + timeIntervalHeight; if (scrollPosition > _scrollController!.position.maxScrollExtent) { @@ -7673,8 +8981,9 @@ class _CalendarViewState extends State<_CalendarView> _resizingDetails.value.scrollPosition = scrollPosition; _resizingDetails.value.position.value = Offset( - _resizingDetails.value.appointmentView!.appointmentRect!.left, - yPosition! - 0.1); + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition! - 0.1, + ); await _scrollController!.position.moveTo( scrollPosition, @@ -7693,38 +9002,41 @@ class _CalendarViewState extends State<_CalendarView> yPosition = _resizingDetails.value.position.value?.dy; _updateMaximumResizingPosition( - isForwardResize, - isBackwardResize, - _resizingDetails.value.appointmentView!, - allDayPanelHeight, - viewHeaderHeight); + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + allDayPanelHeight, + viewHeaderHeight, + ); if ((isForwardResize && yPosition! < _maximumResizingPosition!) || (isBackwardResize && yPosition! > _maximumResizingPosition!)) { yPosition = _maximumResizingPosition; } _updateAppointmentResizingUpdateCallback( - isForwardResize, - isBackwardResize, - yPosition!, - viewHeaderHeight, - allDayPanelHeight); + isForwardResize, + isBackwardResize, + yPosition!, + viewHeaderHeight, + allDayPanelHeight, + ); _resizingDetails.value.position.value = Offset( - _resizingDetails.value.appointmentView!.appointmentRect!.left, - yPosition!); + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition!, + ); if (yPosition != null && yPosition! >= widget.height && _scrollController!.position.pixels != _scrollController!.position.maxScrollExtent) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; @@ -7734,14 +9046,15 @@ class _CalendarViewState extends State<_CalendarView> } Future _updateAutoScrollTimeline( - DragUpdateDetails details, - double timeIntervalHeight, - bool isForwardResize, - bool isBackwardResize, - double? xPosition, - double yPosition, - double timeLabelWidth, - bool isResourceEnabled) async { + DragUpdateDetails details, + double timeIntervalHeight, + bool isForwardResize, + bool isBackwardResize, + double? xPosition, + double yPosition, + double timeLabelWidth, + bool isResourceEnabled, + ) async { if (_resizingDetails.value.appointmentView == null) { return; } @@ -7764,7 +9077,7 @@ class _CalendarViewState extends State<_CalendarView> _scrollController!.position.pixels != _scrollController!.position.maxScrollExtent) || (!_isRTL && _scrollController!.position.pixels != 0))) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = _scrollController!.position.pixels - timeIntervalHeight; if (_isRTL) { @@ -7781,7 +9094,9 @@ class _CalendarViewState extends State<_CalendarView> _resizingDetails.value.scrollPosition = scrollPosition; _resizingDetails.value.position.value = Offset( - xPosition! - 0.1, _resizingDetails.value.position.value!.dy); + xPosition! - 0.1, + _resizingDetails.value.position.value!.dy, + ); await _scrollController!.position.animateTo( scrollPosition, @@ -7798,22 +9113,34 @@ class _CalendarViewState extends State<_CalendarView> } xPosition = _resizingDetails.value.position.value?.dx; - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - _resizingDetails.value.appointmentView!, null, null); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + null, + null, + ); if ((isForwardResize && xPosition! < _maximumResizingPosition!) || (isBackwardResize && xPosition! > _maximumResizingPosition!)) { xPosition = _maximumResizingPosition; } _updateAppointmentResizingUpdateCallback( - isForwardResize, isBackwardResize, yPosition, null, null, - xPosition: xPosition, - timeLabelWidth: timeLabelWidth, - isResourceEnabled: isResourceEnabled, - details: details); + isForwardResize, + isBackwardResize, + yPosition, + null, + null, + xPosition: xPosition, + timeLabelWidth: timeLabelWidth, + isResourceEnabled: isResourceEnabled, + details: details, + ); - _resizingDetails.value.position.value = - Offset(xPosition!, _resizingDetails.value.position.value!.dy); + _resizingDetails.value.position.value = Offset( + xPosition!, + _resizingDetails.value.position.value!.dy, + ); if (xPosition != null && xPosition! <= 0 && @@ -7821,14 +9148,14 @@ class _CalendarViewState extends State<_CalendarView> _scrollController!.position.pixels != _scrollController!.position.maxScrollExtent) || (!_isRTL && _scrollController!.position.pixels != 0))) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; @@ -7850,7 +9177,7 @@ class _CalendarViewState extends State<_CalendarView> _scrollController!.position.pixels != _scrollController!.position.maxScrollExtent) || (_isRTL && _scrollController!.position.pixels != 0))) { - Future _updateScrollPosition() async { + Future updateScrollPosition() async { double scrollPosition = _scrollController!.position.pixels + timeIntervalHeight; if (_isRTL) { @@ -7867,7 +9194,9 @@ class _CalendarViewState extends State<_CalendarView> _resizingDetails.value.scrollPosition = scrollPosition; _resizingDetails.value.position.value = Offset( - xPosition! + 0.1, _resizingDetails.value.position.value!.dy); + xPosition! + 0.1, + _resizingDetails.value.position.value!.dy, + ); await _scrollController!.position.moveTo( scrollPosition, @@ -7884,22 +9213,34 @@ class _CalendarViewState extends State<_CalendarView> } xPosition = _resizingDetails.value.position.value?.dx; - _updateMaximumResizingPosition(isForwardResize, isBackwardResize, - _resizingDetails.value.appointmentView!, null, null); + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + null, + null, + ); if ((isForwardResize && xPosition! < _maximumResizingPosition!) || (isBackwardResize && xPosition! > _maximumResizingPosition!)) { xPosition = _maximumResizingPosition; } _updateAppointmentResizingUpdateCallback( - isForwardResize, isBackwardResize, yPosition, null, null, - xPosition: xPosition, - timeLabelWidth: timeLabelWidth, - isResourceEnabled: isResourceEnabled, - details: details); + isForwardResize, + isBackwardResize, + yPosition, + null, + null, + xPosition: xPosition, + timeLabelWidth: timeLabelWidth, + isResourceEnabled: isResourceEnabled, + details: details, + ); - _resizingDetails.value.position.value = - Offset(xPosition!, _resizingDetails.value.position.value!.dy); + _resizingDetails.value.position.value = Offset( + xPosition!, + _resizingDetails.value.position.value!.dy, + ); if (xPosition != null && xPosition! + padding >= widget.width && @@ -7907,14 +9248,14 @@ class _CalendarViewState extends State<_CalendarView> _scrollController!.position.pixels != _scrollController!.position.maxScrollExtent) || (_isRTL && _scrollController!.position.pixels != 0))) { - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; } } - _updateScrollPosition(); + updateScrollPosition(); } else if (_autoScrollTimer != null) { _autoScrollTimer!.cancel(); _autoScrollTimer = null; @@ -7924,11 +9265,12 @@ class _CalendarViewState extends State<_CalendarView> } void _updateMaximumResizingPosition( - bool isForwardResize, - bool isBackwardResize, - AppointmentView appointmentView, - double? allDayPanelHeight, - double? viewHeaderHeight) { + bool isForwardResize, + bool isBackwardResize, + AppointmentView appointmentView, + double? allDayPanelHeight, + double? viewHeaderHeight, + ) { switch (widget.view) { case CalendarView.schedule: break; @@ -7938,39 +9280,42 @@ class _CalendarViewState extends State<_CalendarView> { if (_resizingDetails.value.isAllDayPanel) { final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final double minimumCellWidth = ((widget.width - timeLabelWidth) / widget.visibleDates.length) / - 2; + 2; if (isForwardResize) { - _maximumResizingPosition = appointmentView.appointmentRect!.left + + _maximumResizingPosition = + appointmentView.appointmentRect!.left + (appointmentView.appointmentRect!.width > minimumCellWidth ? minimumCellWidth : appointmentView.appointmentRect!.width); } else if (isBackwardResize) { _maximumResizingPosition = appointmentView.appointmentRect!.right - - (appointmentView.appointmentRect!.width > minimumCellWidth - ? minimumCellWidth - : appointmentView.appointmentRect!.width); + (appointmentView.appointmentRect!.width > minimumCellWidth + ? minimumCellWidth + : appointmentView.appointmentRect!.width); } } else { final double timeIntervalSize = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); double minimumTimeIntervalSize = timeIntervalSize / 4; if (minimumTimeIntervalSize < 20) { minimumTimeIntervalSize = 20; } if (isForwardResize) { - _maximumResizingPosition = (appointmentView.appointmentRect!.top - + _maximumResizingPosition = + (appointmentView.appointmentRect!.top - _scrollController!.offset + allDayPanelHeight! + viewHeaderHeight!) + @@ -7981,13 +9326,13 @@ class _CalendarViewState extends State<_CalendarView> } else if (isBackwardResize) { _maximumResizingPosition = (appointmentView.appointmentRect!.bottom - - _scrollController!.offset + - allDayPanelHeight! + - viewHeaderHeight!) - - (appointmentView.appointmentRect!.height / 2 > - minimumTimeIntervalSize - ? minimumTimeIntervalSize - : appointmentView.appointmentRect!.height / 2); + _scrollController!.offset + + allDayPanelHeight! + + viewHeaderHeight!) - + (appointmentView.appointmentRect!.height / 2 > + minimumTimeIntervalSize + ? minimumTimeIntervalSize + : appointmentView.appointmentRect!.height / 2); } } } @@ -7998,40 +9343,47 @@ class _CalendarViewState extends State<_CalendarView> case CalendarView.timelineMonth: { final double timeIntervalSize = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); - double minimumTimeIntervalSize = timeIntervalSize / + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); + double minimumTimeIntervalSize = + timeIntervalSize / (widget.view == CalendarView.timelineMonth ? 2 : 4); if (minimumTimeIntervalSize < 20) { minimumTimeIntervalSize = 20; } if (isForwardResize) { - _maximumResizingPosition = appointmentView.appointmentRect!.left - + _maximumResizingPosition = + appointmentView.appointmentRect!.left - _scrollController!.offset; if (_isRTL) { - _maximumResizingPosition = _scrollController!.offset - + _maximumResizingPosition = + _scrollController!.offset - _scrollController!.position.maxScrollExtent + appointmentView.appointmentRect!.left; } - _maximumResizingPosition = _maximumResizingPosition! + + _maximumResizingPosition = + _maximumResizingPosition! + (appointmentView.appointmentRect!.width / 2 > minimumTimeIntervalSize ? minimumTimeIntervalSize : appointmentView.appointmentRect!.width / 2); } else if (isBackwardResize) { - _maximumResizingPosition = appointmentView.appointmentRect!.right - + _maximumResizingPosition = + appointmentView.appointmentRect!.right - _scrollController!.offset; if (_isRTL) { - _maximumResizingPosition = _scrollController!.offset - + _maximumResizingPosition = + _scrollController!.offset - _scrollController!.position.maxScrollExtent + appointmentView.appointmentRect!.right; } - _maximumResizingPosition = _maximumResizingPosition! - + _maximumResizingPosition = + _maximumResizingPosition! - (appointmentView.appointmentRect!.width / 2 > minimumTimeIntervalSize ? minimumTimeIntervalSize @@ -8043,19 +9395,22 @@ class _CalendarViewState extends State<_CalendarView> { final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); final double minimumCellWidth = ((widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek) / - 2; + 2; if (isForwardResize) { - _maximumResizingPosition = appointmentView.appointmentRect!.left + + _maximumResizingPosition = + appointmentView.appointmentRect!.left + (appointmentView.appointmentRect!.width / 2 > minimumCellWidth ? minimumCellWidth : appointmentView.appointmentRect!.width / 2); } else if (isBackwardResize) { - _maximumResizingPosition = appointmentView.appointmentRect!.right - + _maximumResizingPosition = + appointmentView.appointmentRect!.right - (appointmentView.appointmentRect!.width / 2 > minimumCellWidth ? minimumCellWidth : appointmentView.appointmentRect!.width / 2); @@ -8065,45 +9420,52 @@ class _CalendarViewState extends State<_CalendarView> } void _updateAppointmentResizingUpdateCallback( - bool isForwardResize, - bool isBackwardResize, - double yPosition, - double? viewHeaderHeight, - double? allDayPanelHeight, - {bool isResourceEnabled = false, - double? timeLabelWidth, - double? xPosition, - DragUpdateDetails? details}) { + bool isForwardResize, + bool isBackwardResize, + double yPosition, + double? viewHeaderHeight, + double? allDayPanelHeight, { + bool isResourceEnabled = false, + double? timeLabelWidth, + double? xPosition, + DragUpdateDetails? details, + }) { final double timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); late DateTime resizingTime; CalendarResource? resource; int selectedResourceIndex = -1; if (isResourceEnabled) { final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); selectedResourceIndex = _getSelectedResourceIndex( - _resizingDetails.value.appointmentView!.appointmentRect!.top, - viewHeaderHeight, - timeLabelWidth!); + _resizingDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth!, + ); resource = widget.calendar.dataSource!.resources![selectedResourceIndex]; } if (CalendarViewHelper.isTimelineView(widget.view)) { - final double overAllWidth = _timeIntervalHeight * + final double overAllWidth = + _timeIntervalHeight * (_horizontalLinesCount! * widget.visibleDates.length); double updatedXPosition = details!.localPosition.dx; if (updatedXPosition > widget.width - 1) { @@ -8115,24 +9477,39 @@ class _CalendarViewState extends State<_CalendarView> updatedXPosition = overAllWidth; } - resizingTime = _getDateFromPosition( - updatedXPosition, details.localPosition.dy, timeLabelWidth!)!; - final DateTime time = _timeFromPosition( - resizingTime, - widget.calendar.timeSlotViewSettings, - xPosition! > widget.width - 1 - ? widget.width - 1 - : (xPosition < 0 ? 0 : xPosition), - this, - timeIntervalHeight, - true)!; + resizingTime = + _getDateFromPosition( + updatedXPosition, + details.localPosition.dy, + timeLabelWidth!, + )!; + final DateTime time = + _timeFromPosition( + resizingTime, + widget.calendar.timeSlotViewSettings, + xPosition! > widget.width - 1 + ? widget.width - 1 + : (xPosition < 0 ? 0 : xPosition), + this, + timeIntervalHeight, + true, + )!; if (widget.view == CalendarView.timelineMonth) { resizingTime = DateTime( - resizingTime.year, resizingTime.month, resizingTime.day, 0, 0, 0); + resizingTime.year, + resizingTime.month, + resizingTime.day, + ); } else { - resizingTime = DateTime(resizingTime.year, resizingTime.month, - resizingTime.day, time.hour, time.minute, time.second); + resizingTime = DateTime( + resizingTime.year, + resizingTime.month, + resizingTime.day, + time.hour, + time.minute, + time.second, + ); } } else { final double overAllHeight = _timeIntervalHeight * _horizontalLinesCount!; @@ -8143,31 +9520,40 @@ class _CalendarViewState extends State<_CalendarView> } final double currentYPosition = updatedYPosition - viewHeaderHeight! - allDayPanelHeight!; - resizingTime = _timeFromPosition( - _resizingDetails.value.appointmentView!.appointment!.actualStartTime, - widget.calendar.timeSlotViewSettings, - currentYPosition > 0 ? currentYPosition : 0, - this, - timeIntervalHeight, - false)!; + resizingTime = + _timeFromPosition( + _resizingDetails + .value + .appointmentView! + .appointment! + .actualStartTime, + widget.calendar.timeSlotViewSettings, + currentYPosition > 0 ? currentYPosition : 0, + this, + timeIntervalHeight, + false, + )!; } _resizingDetails.value.resizingTime = resizingTime; if (CalendarViewHelper.shouldRaiseAppointmentResizeUpdateCallback( - widget.calendar.onAppointmentResizeUpdate)) { + widget.calendar.onAppointmentResizeUpdate, + )) { CalendarViewHelper.raiseAppointmentResizeUpdateCallback( + widget.calendar, + _getCalendarAppointmentToObject( + _resizingDetails.value.appointmentView!.appointment, widget.calendar, - _getCalendarAppointmentToObject( - _resizingDetails.value.appointmentView!.appointment, - widget.calendar), - resource, - resizingTime, - _resizingDetails.value.position.value!); + ), + resource, + resizingTime, + _resizingDetails.value.position.value!, + ); } } void _resetResizingPainter() { - SchedulerBinding.instance!.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { _resizingDetails.value.position.value = null; }); _resizingDetails.value.isAllDayPanel = false; @@ -8181,7 +9567,9 @@ class _CalendarViewState extends State<_CalendarView> // Returns the month view as a child for the calendar view. Widget _addMonthView(bool isRTL, String locale) { final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); final double height = widget.height - viewHeaderHeight; return Stack( children: [ @@ -8191,36 +9579,42 @@ class _CalendarViewState extends State<_CalendarView> right: 0, height: viewHeaderHeight, child: Container( - color: widget.calendar.viewHeaderStyle.backgroundColor ?? + color: + widget.calendar.viewHeaderStyle.backgroundColor ?? widget.calendarTheme.viewHeaderBackgroundColor, child: RepaintBoundary( child: CustomPaint( painter: _ViewHeaderViewPainter( - widget.visibleDates, + widget.visibleDates, + widget.view, + widget.calendar.viewHeaderStyle, + widget.calendar.timeSlotViewSettings, + CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, 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, - widget.calendar.showWeekNumber, - widget.isMobilePlatform, - widget.calendar.weekNumberStyle, - widget.localizations), + ), + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ), + widget.calendar.monthViewSettings, + isRTL, + widget.locale, + widget.calendarTheme, + widget.themeData, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + widget.calendar.todayTextStyle, + widget.calendar.cellBorderColor, + widget.calendar.minDate, + widget.calendar.maxDate, + _viewHeaderNotifier, + widget.textScaleFactor, + widget.calendar.showWeekNumber, + widget.isMobilePlatform, + widget.calendar.weekNumberStyle, + widget.localizations, + ), ), ), ), @@ -8231,15 +9625,18 @@ class _CalendarViewState extends State<_CalendarView> 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)), - ], - )), + child: _CalendarMultiChildContainer( + width: widget.width, + height: height, + builder: widget.calendar.monthCellBuilder, + children: [ + RepaintBoundary(child: _getMonthWidget(isRTL, height)), + RepaintBoundary( + child: _addAppointmentPainter(widget.width, height), + ), + ], + ), + ), ), Positioned( left: 0, @@ -8264,29 +9661,31 @@ class _CalendarViewState extends State<_CalendarView> ? _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, - widget.calendar.weekNumberStyle, - widget.isMobilePlatform, - ValueNotifier?>(visibleAppointments)); + 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, + widget.themeData, + _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, + widget.calendar.weekNumberStyle, + widget.isMobilePlatform, + ValueNotifier?>(visibleAppointments), + ); return _monthView; } @@ -8296,34 +9695,45 @@ class _CalendarViewState extends State<_CalendarView> } final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - final double allDayPanelHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; - final bool isVerticalResize = _mouseCursor == SystemMouseCursors.resizeUp || + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); + final double allDayPanelHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + final bool isVerticalResize = + _mouseCursor == SystemMouseCursors.resizeUp || _mouseCursor == SystemMouseCursors.resizeDown; final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - final bool isAllDayPanel = !isVerticalResize && + final bool isAllDayPanel = + !isVerticalResize && (!isTimelineView && widget.view != CalendarView.month); final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); - final double overAllWidth = isTimelineView - ? _timeIntervalHeight * - (_horizontalLinesCount! * widget.visibleDates.length) - : widget.width; + final double overAllWidth = + isTimelineView + ? _timeIntervalHeight * + (_horizontalLinesCount! * widget.visibleDates.length) + : widget.width; final double overAllHeight = isTimelineView || widget.view == CalendarView.month ? widget.height @@ -8332,67 +9742,87 @@ class _CalendarViewState extends State<_CalendarView> (_timeIntervalHeight * _horizontalLinesCount!); return Positioned( - left: 0, - width: overAllWidth, - height: overAllHeight, - top: 0, - child: GestureDetector( - child: IgnorePointer( - ignoring: _mouseCursor == SystemMouseCursors.basic || - _mouseCursor == SystemMouseCursors.move || - isAllDayPanel, - child: RepaintBoundary( - child: CustomPaint( - painter: _ResizingAppointmentPainter( - _resizingDetails, - _isRTL, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.calendar.appointmentTextStyle, - allDayPanelHeight, - viewHeaderHeight, - timeLabelWidth, - _timeIntervalHeight, - _scrollController, - widget.calendar.dragAndDropSettings, - widget.view, - _mouseCursor, - weekNumberPanelWidth, - widget.calendarTheme), - ))), - onVerticalDragStart: isVerticalResize ? _onVerticalStart : null, - onVerticalDragUpdate: isVerticalResize ? _onVerticalUpdate : null, - onVerticalDragEnd: isVerticalResize ? _onVerticalEnd : null, - onHorizontalDragStart: isVerticalResize ? null : _onHorizontalStart, - onHorizontalDragUpdate: isVerticalResize ? null : _onHorizontalUpdate, - onHorizontalDragEnd: isVerticalResize ? null : _onHorizontalEnd, - )); + left: 0, + width: overAllWidth, + height: overAllHeight, + top: 0, + child: GestureDetector( + onVerticalDragStart: isVerticalResize ? _onVerticalStart : null, + onVerticalDragUpdate: isVerticalResize ? _onVerticalUpdate : null, + onVerticalDragEnd: isVerticalResize ? _onVerticalEnd : null, + onHorizontalDragStart: isVerticalResize ? null : _onHorizontalStart, + onHorizontalDragUpdate: isVerticalResize ? null : _onHorizontalUpdate, + onHorizontalDragEnd: isVerticalResize ? null : _onHorizontalEnd, + child: IgnorePointer( + ignoring: + _mouseCursor == SystemMouseCursors.basic || + _mouseCursor == SystemMouseCursors.move || + isAllDayPanel, + child: RepaintBoundary( + child: CustomPaint( + painter: _ResizingAppointmentPainter( + _resizingDetails, + _isRTL, + widget.textScaleFactor, + widget.isMobilePlatform, + AppointmentHelper.getAppointmentTextStyle( + widget.calendar.appointmentTextStyle, + widget.view, + widget.themeData, + ), + allDayPanelHeight, + viewHeaderHeight, + timeLabelWidth, + _timeIntervalHeight, + _scrollController, + widget.calendar.dragAndDropSettings, + widget.view, + _mouseCursor, + weekNumberPanelWidth, + widget.calendarTheme, + ), + ), + ), + ), + ), + ); } // Returns the day view as a child for the calendar view. - Widget _addDayView(double width, double height, bool isRTL, String locale, - bool isCurrentView) { + Widget _addDayView( + double width, + double height, + bool isRTL, + String locale, + bool isCurrentView, + ) { double viewHeaderWidth = widget.width; final double actualViewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); double viewHeaderHeight = actualViewHeaderHeight; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); if (isDayView) { viewHeaderWidth = timeLabelWidth < 50 ? 50 : timeLabelWidth; viewHeaderHeight = _allDayHeight > viewHeaderHeight ? _allDayHeight : viewHeaderHeight; } - double panelHeight = isCurrentView - ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight - : 0; + double panelHeight = + isCurrentView + ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight + : 0; if (panelHeight < 0) { panelHeight = 0; } @@ -8408,122 +9838,142 @@ class _CalendarViewState extends State<_CalendarView> right: isRTL ? 0 : widget.width - viewHeaderWidth, height: actualViewHeaderHeight, child: Container( - color: widget.calendar.viewHeaderStyle.backgroundColor ?? + color: + widget.calendar.viewHeaderStyle.backgroundColor ?? widget.calendarTheme.viewHeaderBackgroundColor, child: RepaintBoundary( child: CustomPaint( painter: _ViewHeaderViewPainter( - widget.visibleDates, + widget.visibleDates, + widget.view, + widget.calendar.viewHeaderStyle, + widget.calendar.timeSlotViewSettings, + CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view, - widget.calendar.viewHeaderStyle, - widget.calendar.timeSlotViewSettings, - CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.view), - actualViewHeaderHeight, - 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, - widget.calendar.showWeekNumber, - widget.isMobilePlatform, - widget.calendar.weekNumberStyle, - widget.localizations), + ), + actualViewHeaderHeight, + widget.calendar.monthViewSettings, + isRTL, + widget.locale, + widget.calendarTheme, + widget.themeData, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + widget.calendar.todayTextStyle, + widget.calendar.cellBorderColor, + widget.calendar.minDate, + widget.calendar.maxDate, + _viewHeaderNotifier, + widget.textScaleFactor, + widget.calendar.showWeekNumber, + widget.isMobilePlatform, + widget.calendar.weekNumberStyle, + widget.localizations, + ), ), ), ), ), Positioned( - top: isDayView - ? viewHeaderHeight + allDayExpanderHeight - : viewHeaderHeight + _allDayHeight + allDayExpanderHeight, - left: 0, - right: 0, - bottom: 0, - child: Scrollbar( + top: + isDayView + ? viewHeaderHeight + allDayExpanderHeight + : viewHeaderHeight + _allDayHeight + allDayExpanderHeight, + left: 0, + right: 0, + bottom: 0, + child: Scrollbar( + controller: _scrollController, + thumbVisibility: !widget.isMobilePlatform, + child: ListView( + padding: EdgeInsets.zero, controller: _scrollController, - isAlwaysShown: !widget.isMobilePlatform, - child: ListView( - padding: EdgeInsets.zero, - controller: _scrollController, - scrollDirection: Axis.vertical, - physics: const ClampingScrollPhysics(), + physics: const ClampingScrollPhysics(), + children: [ + Stack( 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( + RepaintBoundary( + child: _CalendarMultiChildContainer( + width: width, + height: height, + builder: widget.calendar.monthCellBuilder, + children: [ + RepaintBoundary( + child: TimeSlotWidget( + widget.visibleDates, _horizontalLinesCount!, _timeIntervalHeight, - widget.calendar.timeSlotViewSettings, + timeLabelWidth, widget.calendar.cellBorderColor, - isRTL, - widget.locale, widget.calendarTheme, - CalendarViewHelper.isTimelineView(widget.view), - widget.visibleDates, - widget.textScaleFactor), - size: Size(timeLabelWidth, height), - ), + widget.themeData, + 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: _addSelectionView(), - size: Size(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), - ]) - ]), - )), + ), + _getCurrentTimeIndicator( + timeLabelWidth, + width, + height, + false, + ), + ], + ), + ], + ), + ), + ), ], ); } Widget _getCurrentTimeIndicator( - double timeLabelSize, double width, double height, bool isTimelineView) { + double timeLabelSize, + double width, + double height, + bool isTimelineView, + ) { if (!widget.calendar.showCurrentTimeIndicator || widget.view == CalendarView.timelineMonth) { - return const SizedBox( - width: 0, - height: 0, - ); + return const SizedBox(width: 0, height: 0); } return RepaintBoundary( @@ -8538,6 +9988,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendarTheme.todayHighlightColor, _isRTL, _currentTimeNotifier, + widget.calendar.timeZone ?? '', ), size: Size(width, height), ), @@ -8552,12 +10003,15 @@ class _CalendarViewState extends State<_CalendarView> _selectedResourceIndex == -1) { final bool isTimelineMonth = widget.view == CalendarView.timelineMonth; if ((isTimelineMonth && - (isSameDate(_updateCalendarStateDetails.selectedDate, - widget.calendar.initialSelectedDate))) || + (isSameDate( + _updateCalendarStateDetails.selectedDate, + widget.calendar.initialSelectedDate, + ))) || (!isTimelineMonth && (CalendarViewHelper.isSameTimeSlot( - _updateCalendarStateDetails.selectedDate, - widget.calendar.initialSelectedDate)))) { + _updateCalendarStateDetails.selectedDate, + widget.calendar.initialSelectedDate, + )))) { _selectedResourceIndex = 0; } } @@ -8566,36 +10020,49 @@ class _CalendarViewState extends State<_CalendarView> // 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); + widget.calendar.viewHeaderHeight, + widget.view, + ); final double timeLabelSize = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + 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); + 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), + 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( + Positioned( top: viewHeaderHeight, left: 0, right: 0, @@ -8604,11 +10071,14 @@ class _CalendarViewState extends State<_CalendarView> padding: EdgeInsets.zero, controller: _timelineRulerController, scrollDirection: Axis.horizontal, - physics: const _CustomNeverScrollableScrollPhysics(), + physics: + widget.isMobilePlatform + ? const _CustomNeverScrollableScrollPhysics() + : const ClampingScrollPhysics(), children: [ RepaintBoundary( - child: CustomPaint( - painter: _TimeRulerView( + child: CustomPaint( + painter: _TimeRulerView( _horizontalLinesCount!, _timeIntervalHeight, widget.calendar.timeSlotViewSettings, @@ -8618,90 +10088,117 @@ class _CalendarViewState extends State<_CalendarView> widget.calendarTheme, CalendarViewHelper.isTimelineView(widget.view), widget.visibleDates, - widget.textScaleFactor), - size: Size(width, timeLabelSize), - )), + widget.textScaleFactor, + ), + size: Size(width, timeLabelSize), + ), + ), ], - )), - Positioned( + ), + ), + Positioned( top: viewHeaderHeight + timeLabelSize, left: 0, right: 0, bottom: 0, child: Scrollbar( controller: _scrollController, - isAlwaysShown: !widget.isMobilePlatform, + thumbVisibility: !widget.isMobilePlatform, child: ListView( - padding: EdgeInsets.zero, - controller: _scrollController, - scrollDirection: Axis.horizontal, - physics: const _CustomNeverScrollableScrollPhysics(), - children: [ - SizedBox( - width: width, - child: Stack(children: [ - Scrollbar( - controller: _timelineViewVerticalScrollController, - isAlwaysShown: !widget.isMobilePlatform, - child: ListView( - padding: EdgeInsets.zero, - 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), + padding: EdgeInsets.zero, + controller: _scrollController, + scrollDirection: Axis.horizontal, + physics: + widget.isMobilePlatform + ? const _CustomNeverScrollableScrollPhysics() + : const ClampingScrollPhysics(), + children: [ + SizedBox( + width: width, + child: Stack( + children: [ + Scrollbar( + controller: _timelineViewVerticalScrollController, + thumbVisibility: !widget.isMobilePlatform, + child: ListView( + padding: EdgeInsets.zero, + controller: _timelineViewVerticalScrollController, + physics: + isResourceEnabled + ? const ClampingScrollPhysics() + : const NeverScrollableScrollPhysics(), + children: [ + Stack( + children: [ + RepaintBoundary( + child: _CalendarMultiChildContainer( + width: width, + height: height, + builder: widget.calendar.monthCellBuilder, + children: [ + RepaintBoundary( + child: TimelineWidget( + _horizontalLinesCount!, + widget.visibleDates, + widget.calendar.timeSlotViewSettings, + _timeIntervalHeight, + widget.calendar.cellBorderColor, + _isRTL, + widget.calendarTheme, + widget.themeData, + _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, ), - _getCurrentTimeIndicator( - timeLabelSize, width, height, true), - ]), - ])), - ])), - ]), - )), - ]); + size: Size(width, height), + ), + ), + _getCurrentTimeIndicator( + timeLabelSize, + width, + height, + true, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ); } //// Get the calendar details for all calendar views. @@ -8729,9 +10226,10 @@ class _CalendarViewState extends State<_CalendarView> double yPosition = position.dy; final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if ((!_isRTL && xPosition < weekNumberPanelWidth) || (_isRTL && xPosition > widget.width - weekNumberPanelWidth)) { /// Return null while the [getCalendarDetailsAtOffset] position placed on @@ -8740,16 +10238,19 @@ class _CalendarViewState extends State<_CalendarView> } final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); if (yPosition < viewHeaderHeight) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on view header in month view. return CalendarDetails( - null, - _getTappedViewHeaderDate(position, widget.width), - CalendarElement.viewHeader, - null); + null, + _getTappedViewHeaderDate(position, widget.width), + CalendarElement.viewHeader, + null, + ); } yPosition = yPosition - viewHeaderHeight; @@ -8757,9 +10258,12 @@ class _CalendarViewState extends State<_CalendarView> bool isMoreTapped = false; if (widget.calendar.monthViewSettings.appointmentDisplayMode == MonthAppointmentDisplayMode.appointment) { - appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); - isMoreTapped = appointmentView != null && + appointmentView = _appointmentLayout.getAppointmentViewOnPoint( + xPosition, + yPosition, + ); + isMoreTapped = + appointmentView != null && appointmentView.startIndex == -1 && appointmentView.endIndex == -1 && appointmentView.position == -1 && @@ -8775,10 +10279,11 @@ class _CalendarViewState extends State<_CalendarView> /// Check the position of 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, - getDate)) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + getDate, + )) { /// Return null while the [getCalendarDetailsAtOffset] position placed /// on not shown leading and trailing dates. return null; @@ -8789,19 +10294,26 @@ class _CalendarViewState extends State<_CalendarView> appointmentView == null || isMoreTapped ? _getSelectedAppointments(getDate) : [ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ]; - final CalendarElement selectedElement = appointmentView == null - ? CalendarElement.calendarCell - : isMoreTapped + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ]; + final CalendarElement selectedElement = + appointmentView == null + ? CalendarElement.calendarCell + : isMoreTapped ? CalendarElement.moreAppointmentRegion : CalendarElement.appointment; /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on month cells in month view. return CalendarDetails( - selectedAppointments, getDate, selectedElement, null); + selectedAppointments, + getDate, + selectedElement, + null, + ); } //// Handles the onTap callback for month cells, and view header of month @@ -8811,7 +10323,9 @@ class _CalendarViewState extends State<_CalendarView> /// Handles the tap and long press related functions for month view. AppointmentView? _handleTouchOnMonthView( - TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { + TapUpDetails? tapDetails, + LongPressStartDetails? longPressDetails, + ) { widget.removePicker(); final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; double xDetails = 0, yDetails = 0; @@ -8826,12 +10340,15 @@ class _CalendarViewState extends State<_CalendarView> } final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if ((!_isRTL && xDetails < weekNumberPanelWidth) || (_isRTL && xDetails > widget.width - weekNumberPanelWidth)) { return null; @@ -8853,8 +10370,11 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.monthViewSettings.appointmentDisplayMode == MonthAppointmentDisplayMode.appointment) { appointmentView = _appointmentLayout.getAppointmentViewOnPoint( - xDetails, yDetails - viewHeaderHeight); - isMoreTapped = appointmentView != null && + xDetails, + yDetails - viewHeaderHeight, + ); + isMoreTapped = + appointmentView != null && appointmentView.startIndex == -1 && appointmentView.endIndex == -1 && appointmentView.position == -1 && @@ -8875,10 +10395,15 @@ class _CalendarViewState extends State<_CalendarView> final DateTime selectedDate = _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0)!; if (appointmentView == null) { - if (!isDateWithInDateRange(widget.calendar.minDate, - widget.calendar.maxDate, selectedDate) || + if (!isDateWithInDateRange( + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + ) || CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, selectedDate)) { + widget.blackoutDates, + selectedDate, + )) { return null; } @@ -8888,10 +10413,11 @@ class _CalendarViewState extends State<_CalendarView> /// 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)) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate, + )) { return null; } @@ -8900,39 +10426,57 @@ class _CalendarViewState extends State<_CalendarView> final bool canRaiseTap = CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.calendar.onTap) && - isTapCallback; + widget.calendar.onTap, + ) && + isTapCallback; final bool canRaiseLongPress = CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress) && - !isTapCallback; + widget.calendar.onLongPress, + ) && + !isTapCallback; final bool canRaiseSelectionChanged = CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( - widget.calendar.onSelectionChanged); + widget.calendar.onSelectionChanged, + ); if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { - final List selectedAppointments = appointmentView == null || - isMoreTapped - ? _getSelectedAppointments(selectedDate) - : [ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ]; - final CalendarElement selectedElement = appointmentView == null - ? CalendarElement.calendarCell - : isMoreTapped + final List selectedAppointments = + appointmentView == null || isMoreTapped + ? _getSelectedAppointments(selectedDate) + : [ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ]; + final CalendarElement selectedElement = + appointmentView == null + ? CalendarElement.calendarCell + : isMoreTapped ? CalendarElement.moreAppointmentRegion : CalendarElement.appointment; if (canRaiseTap) { - CalendarViewHelper.raiseCalendarTapCallback(widget.calendar, - selectedDate, selectedAppointments, selectedElement, null); + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, + selectedDate, + selectedAppointments, + selectedElement, + null, + ); } else if (canRaiseLongPress) { - CalendarViewHelper.raiseCalendarLongPressCallback(widget.calendar, - selectedDate, selectedAppointments, selectedElement, null); + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, + selectedDate, + selectedAppointments, + selectedElement, + null, + ); } _updatedSelectionChangedCallback( - canRaiseSelectionChanged, previousSelectedDate); + canRaiseSelectionChanged, + previousSelectedDate, + ); } return appointmentView; } @@ -8941,24 +10485,36 @@ class _CalendarViewState extends State<_CalendarView> /// Raise selection changed callback based on the arguments passed. void _updatedSelectionChangedCallback( - bool canRaiseSelectionChanged, DateTime? previousSelectedDate, - [CalendarResource? selectedResource, - int? previousSelectedResourceIndex]) { - final bool isMonthView = widget.view == CalendarView.month || + bool canRaiseSelectionChanged, + DateTime? previousSelectedDate, [ + CalendarResource? selectedResource, + int? previousSelectedResourceIndex, + ]) { + final bool isMonthView = + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; if (canRaiseSelectionChanged && ((isMonthView && !isSameDate( - previousSelectedDate, _selectionPainter!.selectedDate)) || + previousSelectedDate, + _selectionPainter!.selectedDate, + )) || (!isMonthView && !CalendarViewHelper.isSameTimeSlot( - previousSelectedDate, _selectionPainter!.selectedDate)) || + previousSelectedDate, + _selectionPainter!.selectedDate, + )) || (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view) && + widget.calendar.dataSource, + widget.view, + ) && _selectionPainter!.selectedResourceIndex != previousSelectedResourceIndex))) { CalendarViewHelper.raiseCalendarSelectionChangedCallback( - widget.calendar, _selectionPainter!.selectedDate, selectedResource); + widget.calendar, + _selectionPainter!.selectedDate, + selectedResource, + ); } } @@ -8981,16 +10537,21 @@ class _CalendarViewState extends State<_CalendarView> /// 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; + 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); + widget.calendar.resourceViewSettings.size, + widget.height - viewHeaderHeight - timeLabelSize, + widget.calendar.resourceViewSettings, + resourceCount, + ); return (yPosition / resourceItemHeight).truncate(); } @@ -9000,23 +10561,28 @@ class _CalendarViewState extends State<_CalendarView> final double yDetails = position.dy; final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); if (yDetails < viewHeaderHeight) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on view header in timeline views. return CalendarDetails( - null, - _getTappedViewHeaderDate(position, widget.width), - CalendarElement.viewHeader, - null); + null, + _getTappedViewHeaderDate(position, widget.width), + CalendarElement.viewHeader, + null, + ); } double xPosition = _scrollController!.offset + xDetails; double yPosition = yDetails - viewHeaderHeight; final double timeLabelHeight = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); if (yPosition < timeLabelHeight) { /// Return null while the [getCalendarDetailsAtOffset] position placed on @@ -9029,24 +10595,31 @@ class _CalendarViewState extends State<_CalendarView> CalendarResource? calendarResource; if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { yPosition += _timelineViewVerticalScrollController!.offset; _selectedResourceIndex = _getSelectedResourceIndex( - yPosition, viewHeaderHeight, timeLabelHeight); + yPosition, + viewHeaderHeight, + timeLabelHeight, + ); calendarResource = widget.calendar.dataSource!.resources![_selectedResourceIndex]; } if (_isRTL) { - xPosition = _scrollController!.offset + + xPosition = + _scrollController!.offset + (_scrollController!.position.viewportDimension - xDetails); - xPosition = (_scrollController!.position.viewportDimension + + xPosition = + (_scrollController!.position.viewportDimension + _scrollController!.position.maxScrollExtent) - xPosition; } - final AppointmentView? appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + final AppointmentView? appointmentView = _appointmentLayout + .getAppointmentViewOnPoint(xPosition, yPosition); final DateTime getDate = _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0)!; @@ -9055,20 +10628,33 @@ class _CalendarViewState extends State<_CalendarView> /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on calendar cell in timeline views. return CalendarDetails( - null, getDate, CalendarElement.calendarCell, calendarResource); + null, + getDate, + CalendarElement.calendarCell, + calendarResource, + ); } else { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on appointment in timeline views. - return CalendarDetails([ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ], getDate, CalendarElement.appointment, calendarResource); + return CalendarDetails( + [ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ], + getDate, + CalendarElement.appointment, + calendarResource, + ); } } /// Handles the tap and long press related functions for timeline view. AppointmentView? _handleTouchOnTimeline( - TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { + TapUpDetails? tapDetails, + LongPressStartDetails? longPressDetails, + ) { widget.removePicker(); final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; double xDetails = 0, yDetails = 0; @@ -9083,7 +10669,9 @@ class _CalendarViewState extends State<_CalendarView> } final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); if (yDetails < viewHeaderHeight) { if (isTapCallback) { @@ -9102,7 +10690,9 @@ class _CalendarViewState extends State<_CalendarView> double xPosition = _scrollController!.offset + xDetails; double yPosition = yDetails - viewHeaderHeight; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); if (yPosition < timeLabelWidth) { return null; @@ -9113,10 +10703,15 @@ class _CalendarViewState extends State<_CalendarView> CalendarResource? selectedResource; if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { yPosition += _timelineViewVerticalScrollController!.offset; _selectedResourceIndex = _getSelectedResourceIndex( - yPosition, viewHeaderHeight, timeLabelWidth); + yPosition, + viewHeaderHeight, + timeLabelWidth, + ); selectedResource = widget.calendar.dataSource!.resources![_selectedResourceIndex]; } @@ -9126,15 +10721,17 @@ class _CalendarViewState extends State<_CalendarView> _selectionPainter!.selectedResourceIndex = _selectedResourceIndex; if (_isRTL) { - xPosition = _scrollController!.offset + + xPosition = + _scrollController!.offset + (_scrollController!.position.viewportDimension - xDetails); - xPosition = (_scrollController!.position.viewportDimension + + xPosition = + (_scrollController!.position.viewportDimension + _scrollController!.position.maxScrollExtent) - xPosition; } - final AppointmentView? appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + final AppointmentView? appointmentView = _appointmentLayout + .getAppointmentViewOnPoint(xPosition, yPosition); if (appointmentView == null) { _drawSelection(xDetails, yPosition, timeLabelWidth); selectedDate = _selectionPainter!.selectedDate; @@ -9152,87 +10749,117 @@ class _CalendarViewState extends State<_CalendarView> widget.updateCalendarState(_updateCalendarStateDetails); final bool canRaiseTap = CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.calendar.onTap) && - isTapCallback; + widget.calendar.onTap, + ) && + isTapCallback; final bool canRaiseLongPress = CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress) && - !isTapCallback; + widget.calendar.onLongPress, + ) && + !isTapCallback; final bool canRaiseSelectionChanged = CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( - widget.calendar.onSelectionChanged); + widget.calendar.onSelectionChanged, + ); if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { - final DateTime selectedDate = - _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0)!; + final DateTime? selectedDate = _getDateFromPosition( + xDetails, + yDetails - viewHeaderHeight, + 0, + ); + + /// Restrict the tap/long press callback while interact after + /// the timeslots. + if (selectedDate == null) { + return null; + } + final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); if (appointmentView == null) { if (!CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - selectedDate, - timeInterval) || + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + timeInterval, + ) || (widget.view == CalendarView.timelineMonth && CalendarViewHelper.isDateInDateCollection( - widget.calendar.blackoutDates, selectedDate))) { + widget.calendar.blackoutDates, + selectedDate, + ))) { return null; } /// Restrict the callback, while selected region as disabled /// [TimeRegion]. if (!_isEnabledRegion( - xDetails, selectedDate, _selectedResourceIndex)) { + xDetails, + selectedDate, + _selectedResourceIndex, + )) { return null; } if (canRaiseTap) { CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, - selectedDate, - null, - CalendarElement.calendarCell, - selectedResource); + widget.calendar, + selectedDate, + null, + CalendarElement.calendarCell, + selectedResource, + ); } else if (canRaiseLongPress) { CalendarViewHelper.raiseCalendarLongPressCallback( - widget.calendar, - selectedDate, - null, - CalendarElement.calendarCell, - selectedResource); + widget.calendar, + selectedDate, + null, + CalendarElement.calendarCell, + selectedResource, + ); } _updatedSelectionChangedCallback( - canRaiseSelectionChanged, - previousSelectedDate, - selectedResource, - previousSelectedResourceIndex); + canRaiseSelectionChanged, + previousSelectedDate, + selectedResource, + previousSelectedResourceIndex, + ); } else { if (canRaiseTap) { CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, - selectedDate, - [ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ], - CalendarElement.appointment, - selectedResource); + widget.calendar, + selectedDate, + [ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ], + CalendarElement.appointment, + selectedResource, + ); } else if (canRaiseLongPress) { CalendarViewHelper.raiseCalendarLongPressCallback( - widget.calendar, - selectedDate, - [ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) - ], - CalendarElement.appointment, - selectedResource); + widget.calendar, + selectedDate, + [ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, + widget.calendar.dataSource, + ), + ], + CalendarElement.appointment, + selectedResource, + ); } _updatedSelectionChangedCallback( - canRaiseSelectionChanged, - previousSelectedDate, - selectedResource, - previousSelectedResourceIndex); + canRaiseSelectionChanged, + previousSelectedDate, + selectedResource, + previousSelectedResourceIndex, + ); } } @@ -9248,7 +10875,7 @@ class _CalendarViewState extends State<_CalendarView> return; } - _allDaySelectionNotifier.value = SelectionDetails(view, date); + _allDaySelectionNotifier.value = AllDayPanelSelectionDetails(view, date); } //// Handles the onTap callback for day view cells, all day panel, and view @@ -9263,20 +10890,27 @@ class _CalendarViewState extends State<_CalendarView> final double yDetails = position.dy; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); - - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - final double allDayHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); + + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); + final double allDayHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; // time ruler position on time slot scroll view. if (!_isRTL && xDetails <= timeLabelWidth && @@ -9308,10 +10942,11 @@ class _CalendarViewState extends State<_CalendarView> /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on view header in week and work week view. return CalendarDetails( - null, - _getTappedViewHeaderDate(position, widget.width), - CalendarElement.viewHeader, - null); + null, + _getTappedViewHeaderDate(position, widget.width), + CalendarElement.viewHeader, + null, + ); } else if (yDetails < viewHeaderHeight + allDayHeight) { /// Check the position in view header when [CalendarView] is day /// If RTL, view header placed at right side, @@ -9321,14 +10956,17 @@ class _CalendarViewState extends State<_CalendarView> (_isRTL && widget.width - xDetails <= timeLabelWidth)) && yDetails < CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view)) { + widget.calendar.viewHeaderHeight, + widget.view, + )) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on view header in day view. return CalendarDetails( - null, - _getTappedViewHeaderDate(position, widget.width), - CalendarElement.viewHeader, - null); + null, + _getTappedViewHeaderDate(position, widget.width), + CalendarElement.viewHeader, + null, + ); } else if ((!_isRTL && timeLabelWidth >= xDetails) || (_isRTL && xDetails > widget.width - timeLabelWidth)) { /// Return null while the [getCalendarDetailsAtOffset] position placed @@ -9338,12 +10976,14 @@ class _CalendarViewState extends State<_CalendarView> final double yPosition = yDetails - viewHeaderHeight; final AppointmentView? appointmentView = _getAllDayAppointmentOnPoint( - _updateCalendarStateDetails.allDayAppointmentViewCollection, - xDetails, - yPosition); + _updateCalendarStateDetails.allDayAppointmentViewCollection, + xDetails, + yPosition, + ); /// Check the count position tapped or not - bool isTappedOnCount = appointmentView != null && + bool isTappedOnCount = + appointmentView != null && _updateCalendarStateDetails.allDayPanelHeight > allDayHeight && yPosition > allDayHeight - kAllDayAppointmentHeight; DateTime? selectedDate; @@ -9351,20 +10991,25 @@ class _CalendarViewState extends State<_CalendarView> selectedDate = _getTappedViewHeaderDate(position, widget.width); } - List? _moreRegionAppointments; + List? moreRegionAppointments; // Expand and collapsed the all day panel creates all the appointment // views including the hidden appointment. In that case, is tapped count // boolean property sets true. if (isTappedOnCount && selectedDate != null) { final int currentSelectedIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, selectedDate); + widget.visibleDates, + selectedDate, + ); if (currentSelectedIndex != -1) { - _moreRegionAppointments = []; - for (int i = 0; - i < - _updateCalendarStateDetails - .allDayAppointmentViewCollection.length; - i++) { + moreRegionAppointments = []; + for ( + int i = 0; + i < + _updateCalendarStateDetails + .allDayAppointmentViewCollection + .length; + i++ + ) { final AppointmentView currentView = _updateCalendarStateDetails.allDayAppointmentViewCollection[i]; if (currentView.appointment == null) { @@ -9373,7 +11018,7 @@ class _CalendarViewState extends State<_CalendarView> if (currentView.startIndex <= currentSelectedIndex && currentView.endIndex > currentSelectedIndex) { - _moreRegionAppointments.add(currentView.appointment!); + moreRegionAppointments.add(currentView.appointment!); } } } @@ -9394,16 +11039,21 @@ class _CalendarViewState extends State<_CalendarView> _updateCalendarStateDetails.allDayPanelHeight > allDayHeight && yPosition > allDayHeight - kAllDayAppointmentHeight) { final int currentSelectedIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, selectedDate); + widget.visibleDates, + selectedDate, + ); if (currentSelectedIndex != -1) { - _moreRegionAppointments = []; + moreRegionAppointments = []; final List selectedIndexAppointment = []; - for (int i = 0; - i < - _updateCalendarStateDetails - .allDayAppointmentViewCollection.length; - i++) { + for ( + int i = 0; + i < + _updateCalendarStateDetails + .allDayAppointmentViewCollection + .length; + i++ + ) { final AppointmentView currentView = _updateCalendarStateDetails.allDayAppointmentViewCollection[i]; if (currentView.appointment == null) { @@ -9413,19 +11063,24 @@ class _CalendarViewState extends State<_CalendarView> if (currentView.startIndex <= currentSelectedIndex && currentView.endIndex > currentSelectedIndex) { selectedIndexAppointment.add(currentView); - _moreRegionAppointments.add(currentView.appointment!); + moreRegionAppointments.add(currentView.appointment!); } } int maxPosition = 0; if (selectedIndexAppointment.isNotEmpty) { - maxPosition = selectedIndexAppointment - .reduce((AppointmentView currentAppView, - AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + maxPosition = + selectedIndexAppointment + .reduce( + ( + AppointmentView currentAppView, + AppointmentView nextAppView, + ) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; } final int endAppointmentPosition = allDayHeight ~/ kAllDayAppointmentHeight; @@ -9441,32 +11096,46 @@ class _CalendarViewState extends State<_CalendarView> appointmentView.position + 1 >= appointmentView.maxPositions)) { final List appointmentDetails = [ CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) + appointmentView.appointment!, + widget.calendar.dataSource, + ), ]; /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on appointments in day, week and workweek view. return CalendarDetails( - appointmentDetails, null, CalendarElement.appointment, null); + appointmentDetails, + null, + CalendarElement.appointment, + null, + ); } else if (isTappedOnCount) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on more appointment region in day, week and workweek /// view. return CalendarDetails( - widget.calendar.dataSource != null && - !AppointmentHelper.isCalendarAppointment( - widget.calendar.dataSource!) - ? CalendarViewHelper.getCustomAppointments( - _moreRegionAppointments, widget.calendar.dataSource) - : _moreRegionAppointments, - selectedDate, - CalendarElement.moreAppointmentRegion, - null); + widget.calendar.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.calendar.dataSource!, + ) + ? CalendarViewHelper.getCustomAppointments( + moreRegionAppointments, + widget.calendar.dataSource, + ) + : moreRegionAppointments, + selectedDate, + CalendarElement.moreAppointmentRegion, + null, + ); } else if (appointmentView == null) { /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on all day panel in day, week and work week view. return CalendarDetails( - null, selectedDate, CalendarElement.allDayPanel, null); + null, + selectedDate, + CalendarElement.allDayPanel, + null, + ); } return null; @@ -9474,50 +11143,65 @@ class _CalendarViewState extends State<_CalendarView> double yPosition = yDetails - viewHeaderHeight - allDayHeight + _scrollController!.offset; - final AppointmentView? appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xDetails, yPosition); + final AppointmentView? appointmentView = _appointmentLayout + .getAppointmentViewOnPoint(xDetails, yPosition); if (appointmentView == null) { /// Remove the scroll position for internally handles the scroll position /// in _getDateFromPosition method yPosition = yPosition - _scrollController!.offset; final DateTime? selectedDate = _getDateFromPosition( - !_isRTL ? xDetails - timeLabelWidth : xDetails, - yPosition, - timeLabelWidth); + !_isRTL ? xDetails - timeLabelWidth : xDetails, + yPosition, + timeLabelWidth, + ); /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on calendar cell in day, week and work week view. return CalendarDetails( - null, selectedDate, CalendarElement.calendarCell, null); + null, + selectedDate, + CalendarElement.calendarCell, + null, + ); } else { final List appointmentDetails = [ CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!, widget.calendar.dataSource) + appointmentView.appointment!, + widget.calendar.dataSource, + ), ]; /// Return calendar details while the [getCalendarDetailsAtOffset] /// position placed on appointments in day, week and work week view. return CalendarDetails( - appointmentDetails, null, CalendarElement.appointment, null); + appointmentDetails, + null, + CalendarElement.appointment, + null, + ); } } /// Handles the tap and long press related functions for day, week /// work week views. AppointmentView? _handleTouchOnDayView( - TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { + TapUpDetails? tapDetails, + LongPressStartDetails? longPressDetails, + ) { widget.removePicker(); final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); double xDetails = 0, yDetails = 0; bool isTappedCallback = false; final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); if (tapDetails != null) { isTappedCallback = true; xDetails = tapDetails.localPosition.dx; @@ -9537,15 +11221,21 @@ class _CalendarViewState extends State<_CalendarView> CalendarElement targetElement = CalendarElement.viewHeader; DateTime? selectedDate = _updateCalendarStateDetails.selectedDate; final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - - final double viewHeaderHeight = isDayView - ? 0 - : CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - final double allDayHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); + + final double viewHeaderHeight = + isDayView + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, + widget.view, + ); + final double allDayHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; if (!_isRTL && xDetails <= timeLabelWidth && yDetails > viewHeaderHeight + allDayHeight) { @@ -9583,7 +11273,9 @@ class _CalendarViewState extends State<_CalendarView> (_isRTL && widget.width - xDetails <= timeLabelWidth)) && yDetails < CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view)) { + widget.calendar.viewHeaderHeight, + widget.view, + )) { if (isTappedCallback) { _handleOnTapForViewHeader(tapDetails!, widget.width); } else if (!isTappedCallback) { @@ -9601,23 +11293,29 @@ class _CalendarViewState extends State<_CalendarView> final double yPosition = yDetails - viewHeaderHeight; final AppointmentView? appointmentView = _getAllDayAppointmentOnPoint( - _updateCalendarStateDetails.allDayAppointmentViewCollection, - xDetails, - yPosition); + _updateCalendarStateDetails.allDayAppointmentViewCollection, + xDetails, + yPosition, + ); if (appointmentView == null) { targetElement = CalendarElement.allDayPanel; if (isTappedCallback) { - selectedDate = - _getTappedViewHeaderDate(tapDetails!.localPosition, widget.width); + selectedDate = _getTappedViewHeaderDate( + tapDetails!.localPosition, + widget.width, + ); } else { selectedDate = _getTappedViewHeaderDate( - longPressDetails!.localPosition, widget.width); + longPressDetails!.localPosition, + widget.width, + ); } } /// Check the count position tapped or not - bool isTappedOnCount = appointmentView != null && + bool isTappedOnCount = + appointmentView != null && _updateCalendarStateDetails.allDayPanelHeight > allDayHeight && yPosition > allDayHeight - kAllDayAppointmentHeight; @@ -9636,15 +11334,20 @@ class _CalendarViewState extends State<_CalendarView> _updateCalendarStateDetails.allDayPanelHeight > allDayHeight && yPosition > allDayHeight - kAllDayAppointmentHeight) { final int currentSelectedIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, selectedDate); + widget.visibleDates, + selectedDate, + ); if (currentSelectedIndex != -1) { final List selectedIndexAppointment = []; - for (int i = 0; - i < - _updateCalendarStateDetails - .allDayAppointmentViewCollection.length; - i++) { + for ( + int i = 0; + i < + _updateCalendarStateDetails + .allDayAppointmentViewCollection + .length; + i++ + ) { final AppointmentView currentView = _updateCalendarStateDetails.allDayAppointmentViewCollection[i]; if (currentView.appointment == null) { @@ -9658,13 +11361,18 @@ class _CalendarViewState extends State<_CalendarView> int maxPosition = 0; if (selectedIndexAppointment.isNotEmpty) { - maxPosition = selectedIndexAppointment - .reduce((AppointmentView currentAppView, - AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; + maxPosition = + selectedIndexAppointment + .reduce( + ( + AppointmentView currentAppView, + AppointmentView nextAppView, + ) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView, + ) + .maxPositions; } final int endAppointmentPosition = allDayHeight ~/ kAllDayAppointmentHeight; @@ -9678,16 +11386,19 @@ class _CalendarViewState extends State<_CalendarView> (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)) { + 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, + )) && + !appointmentView.appointment!.isSpanned) { return null; } if (selectedDate != null) { @@ -9715,20 +11426,27 @@ class _CalendarViewState extends State<_CalendarView> selectedAppointmentView = appointmentView; } else { - final double yPosition = yDetails - + final double yPosition = + yDetails - viewHeaderHeight - allDayHeight + _scrollController!.offset; - final AppointmentView? appointmentView = - _appointmentLayout.getAppointmentViewOnPoint(xDetails, yPosition); + final AppointmentView? appointmentView = _appointmentLayout + .getAppointmentViewOnPoint(xDetails, yPosition); _allDaySelectionNotifier.value = null; if (appointmentView == null) { if (_isRTL) { - _drawSelection(xDetails, yDetails - viewHeaderHeight - allDayHeight, - timeLabelWidth); + _drawSelection( + xDetails, + yDetails - viewHeaderHeight - allDayHeight, + timeLabelWidth, + ); } else { - _drawSelection(xDetails - timeLabelWidth, - yDetails - viewHeaderHeight - allDayHeight, timeLabelWidth); + _drawSelection( + xDetails - timeLabelWidth, + yDetails - viewHeaderHeight - allDayHeight, + timeLabelWidth, + ); } targetElement = CalendarElement.calendarCell; } else { @@ -9747,16 +11465,20 @@ class _CalendarViewState extends State<_CalendarView> } widget.updateCalendarState(_updateCalendarStateDetails); - final bool canRaiseTap = CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.calendar.onTap) && + final bool canRaiseTap = + CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.calendar.onTap, + ) && isTappedCallback; final bool canRaiseLongPress = CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress) && - !isTappedCallback; + widget.calendar.onLongPress, + ) && + !isTappedCallback; final bool canRaiseSelectionChanged = CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( - widget.calendar.onSelectionChanged); + widget.calendar.onSelectionChanged, + ); if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { final double yPosition = yDetails - viewHeaderHeight - allDayHeight; if (_selectionPainter!.selectedDate != null && @@ -9766,15 +11488,23 @@ class _CalendarViewState extends State<_CalendarView> /// 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); + !_isRTL ? xDetails - timeLabelWidth : xDetails, + yPosition, + timeLabelWidth, + ); + + /// Restrict the tap/long press callback while interact after + /// the timeslots. + if (selectedDate == null) { + return null; + } if (!CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - selectedDate!, - timeInterval)) { + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + timeInterval, + )) { return null; } @@ -9782,68 +11512,94 @@ class _CalendarViewState extends State<_CalendarView> /// [TimeRegion]. if (targetElement == CalendarElement.calendarCell && !_isEnabledRegion( - yPosition, selectedDate, _selectedResourceIndex)) { + yPosition, + selectedDate, + _selectedResourceIndex, + )) { return null; } if (canRaiseTap) { CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, - _selectionPainter!.selectedDate, - selectedAppointments, - targetElement, - null); + widget.calendar, + _selectionPainter!.selectedDate, + selectedAppointments, + targetElement, + null, + ); } else if (canRaiseLongPress) { CalendarViewHelper.raiseCalendarLongPressCallback( - widget.calendar, - _selectionPainter!.selectedDate, - selectedAppointments, - targetElement, - null); + widget.calendar, + _selectionPainter!.selectedDate, + selectedAppointments, + targetElement, + null, + ); } _updatedSelectionChangedCallback( - canRaiseSelectionChanged, previousSelectedDate); + canRaiseSelectionChanged, + previousSelectedDate, + ); } else if (selectedAppointment != null) { selectedAppointments = [ CalendarViewHelper.getAppointmentDetail( - selectedAppointment, widget.calendar.dataSource) + selectedAppointment, + widget.calendar.dataSource, + ), ]; /// 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); + !_isRTL ? xDetails - timeLabelWidth : xDetails, + yPosition, + timeLabelWidth, + ); if (canRaiseTap) { CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, - selectedDate, - selectedAppointments, - CalendarElement.appointment, - null); + widget.calendar, + selectedDate, + selectedAppointments, + CalendarElement.appointment, + null, + ); } else if (canRaiseLongPress) { CalendarViewHelper.raiseCalendarLongPressCallback( - widget.calendar, - selectedDate, - selectedAppointments, - CalendarElement.appointment, - null); + widget.calendar, + selectedDate, + selectedAppointments, + CalendarElement.appointment, + null, + ); } _updatedSelectionChangedCallback( - canRaiseSelectionChanged, previousSelectedDate); + canRaiseSelectionChanged, + previousSelectedDate, + ); } else if (selectedDate != null && targetElement == CalendarElement.allDayPanel) { if (canRaiseTap) { CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, selectedDate, null, targetElement, null); + widget.calendar, + selectedDate, + null, + targetElement, + null, + ); } else if (canRaiseLongPress) { CalendarViewHelper.raiseCalendarLongPressCallback( - widget.calendar, selectedDate, null, targetElement, null); + widget.calendar, + selectedDate, + null, + targetElement, + null, + ); } _updatedSelectionChangedCallback( - canRaiseSelectionChanged, previousSelectedDate); + canRaiseSelectionChanged, + previousSelectedDate, + ); } } @@ -9861,28 +11617,33 @@ class _CalendarViewState extends State<_CalendarView> } final double timeIntervalSize = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + widget.isMobilePlatform, + ); - final double minuteHeight = timeIntervalSize / + final double minuteHeight = + timeIntervalSize / CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + 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()); + 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 + + minutes = + ((_scrollController!.offset + (_scrollController!.position.viewportDimension - y)) % viewWidth) ~/ minuteHeight; @@ -9893,11 +11654,18 @@ class _CalendarViewState extends State<_CalendarView> minutes = (_scrollController!.offset + y) ~/ minuteHeight; } - final DateTime date = DateTime(selectedDate.year, selectedDate.month, - selectedDate.day, 0, minutes + startDuration.inMinutes, 0); + final DateTime date = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + 0, + minutes + startDuration.inMinutes, + ); bool isValidRegion = true; final bool isResourcesEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + 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) || @@ -9911,8 +11679,9 @@ class _CalendarViewState extends State<_CalendarView> resourceIndex != -1 && region.resourceIds != null && region.resourceIds!.isNotEmpty && - !region.resourceIds! - .contains(widget.resourceCollection![resourceIndex].id)) { + !region.resourceIds!.contains( + widget.resourceCollection![resourceIndex].id, + )) { continue; } @@ -9931,8 +11700,12 @@ class _CalendarViewState extends State<_CalendarView> } /// Returns the default time interval width for timeline views. - double _getTimeIntervalWidth(double timeIntervalHeight, CalendarView view, - double width, bool isMobilePlatform) { + double _getTimeIntervalWidth( + double timeIntervalHeight, + CalendarView view, + double width, + bool isMobilePlatform, + ) { if (timeIntervalHeight >= 0) { return timeIntervalHeight; } @@ -9948,60 +11721,99 @@ class _CalendarViewState extends State<_CalendarView> /// 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) { + SfCalendar calendar, + CalendarView view, + double width, + double height, + int visibleDatesCount, + bool isMobilePlatform, + ) { final bool isTimelineView = CalendarViewHelper.isTimelineView(view); final bool isDayView = CalendarViewHelper.isDayView( - view, - calendar.timeSlotViewSettings.numberOfDaysInView, - calendar.timeSlotViewSettings.nonWorkingDays, - calendar.monthViewSettings.numberOfWeeksInView); - double timeIntervalHeight = isTimelineView - ? _getTimeIntervalWidth(calendar.timeSlotViewSettings.timeIntervalWidth, - view, width, isMobilePlatform) - : calendar.timeSlotViewSettings.timeIntervalHeight; + view, + calendar.timeSlotViewSettings.numberOfDaysInView, + calendar.timeSlotViewSettings.nonWorkingDays, + calendar.monthViewSettings.numberOfWeeksInView, + ); + double timeIntervalHeight = + isTimelineView + ? _getTimeIntervalWidth( + calendar.timeSlotViewSettings.timeIntervalWidth, + view, + width, + isMobilePlatform, + ) + : calendar.timeSlotViewSettings.timeIntervalHeight; if (!_isAutoTimeIntervalHeight(calendar, isTimelineView)) { return timeIntervalHeight; } - double viewHeaderHeight = - CalendarViewHelper.getViewHeaderHeight(calendar.viewHeaderHeight, view); + double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + calendar.viewHeaderHeight, + view, + ); + + double allDayViewHeight = 0; + final bool isCurrentView = + _updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates; if (isDayView) { - allDayHeight = _kAllDayLayoutHeight; + if (isCurrentView) { + allDayViewHeight = + _kAllDayLayoutHeight > viewHeaderHeight && + _updateCalendarStateDetails.allDayPanelHeight > + viewHeaderHeight + ? _updateCalendarStateDetails.allDayPanelHeight > + _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight + : viewHeaderHeight; + if (allDayViewHeight < _updateCalendarStateDetails.allDayPanelHeight) { + allDayViewHeight += kAllDayAppointmentHeight; + } + } else { + allDayViewHeight = viewHeaderHeight; + } + viewHeaderHeight = 0; - } else { - allDayHeight = allDayHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : allDayHeight; + } else if (isCurrentView) { + allDayViewHeight = + _updateCalendarStateDetails.allDayPanelHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight; } switch (view) { case CalendarView.day: case CalendarView.week: case CalendarView.workWeek: - timeIntervalHeight = (height - allDayHeight - viewHeaderHeight) / + timeIntervalHeight = + (height - allDayViewHeight - viewHeaderHeight) / CalendarViewHelper.getHorizontalLinesCount( - calendar.timeSlotViewSettings, view); + calendar.timeSlotViewSettings, + view, + ); break; case CalendarView.timelineDay: case CalendarView.timelineWeek: case CalendarView.timelineWorkWeek: case CalendarView.timelineMonth: { - final double _horizontalLinesCount = + final double horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( - calendar.timeSlotViewSettings, view); + calendar.timeSlotViewSettings, + view, + ); timeIntervalHeight = - width / (_horizontalLinesCount * visibleDatesCount); + width / (horizontalLinesCount * visibleDatesCount); if (!_isValidWidth( - width, calendar, visibleDatesCount, _horizontalLinesCount)) { + 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. @@ -10019,8 +11831,12 @@ class _CalendarViewState extends State<_CalendarView> /// 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) { + bool _isValidWidth( + double screenWidth, + SfCalendar calendar, + int visibleDatesCount, + double horizontalLinesCount, + ) { const int offSetValue = 10; final double tempWidth = visibleDatesCount * offSetValue * horizontalLinesCount; @@ -10038,27 +11854,41 @@ class _CalendarViewState extends State<_CalendarView> _getTappedViewHeaderDate(details.localPosition, width)!; _handleViewHeaderTapNavigation(tappedDate); if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.calendar.onTap)) { + widget.calendar.onTap, + )) { return; } CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, tappedDate, null, CalendarElement.viewHeader, null); + widget.calendar, + tappedDate, + null, + CalendarElement.viewHeader, + null, + ); } //// Handles the on long press callback for view header void _handleOnLongPressForViewHeader( - LongPressStartDetails details, double width) { + LongPressStartDetails details, + double width, + ) { final DateTime tappedDate = _getTappedViewHeaderDate(details.localPosition, width)!; _handleViewHeaderTapNavigation(tappedDate); if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress)) { + widget.calendar.onLongPress, + )) { return; } CalendarViewHelper.raiseCalendarLongPressCallback( - widget.calendar, tappedDate, null, CalendarElement.viewHeader, null); + widget.calendar, + tappedDate, + null, + CalendarElement.viewHeader, + null, + ); } void _handleViewHeaderTapNavigation(DateTime date) { @@ -10070,10 +11900,15 @@ class _CalendarViewState extends State<_CalendarView> } if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, date) || + widget.calendar.minDate, + widget.calendar.maxDate, + date, + ) || (widget.controller.view == CalendarView.timelineMonth && CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, date))) { + widget.blackoutDates, + date, + ))) { return; } @@ -10090,13 +11925,16 @@ class _CalendarViewState extends State<_CalendarView> DateTime? _getTappedViewHeaderDate(Offset localPosition, double width) { int index = 0; final double timeLabelViewWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); final int visibleDatesLength = widget.visibleDates.length; final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); if (!CalendarViewHelper.isTimelineView(widget.view)) { double cellWidth = 0; if (widget.view != CalendarView.month) { @@ -10107,9 +11945,10 @@ class _CalendarViewState extends State<_CalendarView> if (isDayView) { index = 0; } else { - index = ((localPosition.dx - (_isRTL ? 0 : timeLabelViewWidth)) / - cellWidth) - .truncate(); + index = + ((localPosition.dx - (_isRTL ? 0 : timeLabelViewWidth)) / + cellWidth) + .truncate(); } } else { cellWidth = width / DateTime.daysPerWeek; @@ -10130,13 +11969,14 @@ class _CalendarViewState extends State<_CalendarView> return widget.visibleDates[index]; } else { - index = ((_scrollController!.offset + - (_isRTL - ? _scrollController!.position.viewportDimension - - localPosition.dx - : localPosition.dx)) / - _getSingleViewWidthForTimeLineView(this)) - .truncate(); + index = + ((_scrollController!.offset + + (_isRTL + ? _scrollController!.position.viewportDimension - + localPosition.dx + : localPosition.dx)) / + _getSingleViewWidthForTimeLineView(this)) + .truncate(); if (index < 0 || index >= visibleDatesLength) { return null; @@ -10245,14 +12085,18 @@ class _CalendarViewState extends State<_CalendarView> } } - void _updateHoveringForViewHeader(Offset localPosition, double xPosition, - double yPosition, double viewHeaderHeight) { + 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; + widget.view != CalendarView.month && + widget.view != CalendarView.day && + widget.view != CalendarView.timelineDay; if (!isViewNavigationEnabled) { _removeAllWidgetHovering(); return; @@ -10294,12 +12138,14 @@ class _CalendarViewState extends State<_CalendarView> } final DateTime? hoverDate = _getTappedViewHeaderDate( - Offset( - CalendarViewHelper.isTimelineView(widget.view) - ? localPosition.dx - : xPosition, - yPosition), - widget.width); + Offset( + CalendarViewHelper.isTimelineView(widget.view) + ? localPosition.dx + : xPosition, + yPosition, + ), + widget.width, + ); // Remove the hovering when the position not in cell regions. if (hoverDate == null) { @@ -10309,7 +12155,10 @@ class _CalendarViewState extends State<_CalendarView> } if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) { + widget.calendar.minDate, + widget.calendar.maxDate, + hoverDate, + )) { _removeViewHeaderHovering(); return; @@ -10317,7 +12166,9 @@ class _CalendarViewState extends State<_CalendarView> if (widget.view == CalendarView.timelineMonth && CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, hoverDate)) { + widget.blackoutDates, + hoverDate, + )) { _removeViewHeaderHovering(); return; @@ -10376,9 +12227,13 @@ class _CalendarViewState extends State<_CalendarView> } } - void _updateMouseCursorForAppointment(AppointmentView? appointmentView, - double xPosition, double yPosition, bool isTimelineViews, - {bool isAllDayPanel = false}) { + void _updateMouseCursorForAppointment( + AppointmentView? appointmentView, + double xPosition, + double yPosition, + bool isTimelineViews, { + bool isAllDayPanel = false, + }) { _hoveringAppointmentView = appointmentView; if (!widget.calendar.allowAppointmentResize || (widget.view == CalendarView.month && @@ -10401,56 +12256,74 @@ class _CalendarViewState extends State<_CalendarView> if (isAllDayPanel || (widget.view == CalendarView.month || isTimelineViews)) { - final bool isMonthView = widget.view == CalendarView.month || + final bool isMonthView = + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; - final DateTime viewStartDate = - AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + final DateTime viewStartDate = AppointmentHelper.convertToStartTime( + widget.visibleDates[0], + ); final DateTime viewEndDate = AppointmentHelper.convertToEndTime( - widget.visibleDates[widget.visibleDates.length - 1]); + widget.visibleDates[widget.visibleDates.length - 1], + ); final DateTime appStartTime = appointmentView.appointment!.exactStartTime; final DateTime appEndTime = appointmentView.appointment!.exactEndTime; final bool canAddForwardSpanIcon = AppointmentHelper.canAddForwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate); + appStartTime, + appEndTime, + viewStartDate, + viewEndDate, + ); final bool canAddBackwardSpanIcon = AppointmentHelper.canAddBackwardSpanIcon( - appStartTime, appEndTime, viewStartDate, viewEndDate); + appStartTime, + appEndTime, + viewStartDate, + viewEndDate, + ); final DateTime appointmentStartTime = appointmentView.appointment!.isAllDay ? AppointmentHelper.convertToStartTime( - appointmentView.appointment!.actualStartTime) + appointmentView.appointment!.actualStartTime, + ) : appointmentView.appointment!.actualStartTime; - final DateTime appointmentEndTime = appointmentView.appointment!.isAllDay - ? AppointmentHelper.convertToEndTime( - appointmentView.appointment!.actualEndTime) - : appointmentView.appointment!.actualEndTime; + final DateTime appointmentEndTime = + appointmentView.appointment!.isAllDay + ? AppointmentHelper.convertToEndTime( + appointmentView.appointment!.actualEndTime, + ) + : appointmentView.appointment!.actualEndTime; final DateTime appointmentExactStartTime = appointmentView.appointment!.isAllDay ? AppointmentHelper.convertToStartTime( - appointmentView.appointment!.exactStartTime) + appointmentView.appointment!.exactStartTime, + ) : appointmentView.appointment!.exactStartTime; final DateTime appointmentExactEndTime = appointmentView.appointment!.isAllDay ? AppointmentHelper.convertToEndTime( - appointmentView.appointment!.exactEndTime) + appointmentView.appointment!.exactEndTime, + ) : appointmentView.appointment!.exactEndTime; if (xPosition >= appointmentView.appointmentRect!.left && xPosition <= appointmentView.appointmentRect!.left + padding && ((isMonthView && isSameDate( - _isRTL ? appointmentEndTime : appointmentStartTime, - _isRTL - ? appointmentExactEndTime - : appointmentExactStartTime)) || + _isRTL ? appointmentEndTime : appointmentStartTime, + _isRTL + ? appointmentExactEndTime + : appointmentExactStartTime, + )) || (!isMonthView && CalendarViewHelper.isSameTimeSlot( - _isRTL ? appointmentEndTime : appointmentStartTime, - _isRTL - ? appointmentExactEndTime - : appointmentExactStartTime))) && + _isRTL ? appointmentEndTime : appointmentStartTime, + _isRTL + ? appointmentExactEndTime + : appointmentExactStartTime, + ))) && ((_isRTL && !canAddForwardSpanIcon) || (!_isRTL && !canAddBackwardSpanIcon))) { setState(() { @@ -10460,16 +12333,18 @@ class _CalendarViewState extends State<_CalendarView> xPosition >= appointmentView.appointmentRect!.right - padding && ((isMonthView && isSameDate( - _isRTL ? appointmentStartTime : appointmentEndTime, - _isRTL - ? appointmentExactStartTime - : appointmentExactEndTime)) || + _isRTL ? appointmentStartTime : appointmentEndTime, + _isRTL + ? appointmentExactStartTime + : appointmentExactEndTime, + )) || (!isMonthView && CalendarViewHelper.isSameTimeSlot( - _isRTL ? appointmentStartTime : appointmentEndTime, - _isRTL - ? appointmentExactStartTime - : appointmentExactEndTime))) && + _isRTL ? appointmentStartTime : appointmentEndTime, + _isRTL + ? appointmentExactStartTime + : appointmentExactEndTime, + ))) && ((_isRTL && !canAddBackwardSpanIcon) || (!_isRTL && !canAddForwardSpanIcon))) { setState(() { @@ -10484,16 +12359,18 @@ class _CalendarViewState extends State<_CalendarView> if (yPosition >= appointmentView.appointmentRect!.top && yPosition <= appointmentView.appointmentRect!.top + padding && CalendarViewHelper.isSameTimeSlot( - appointmentView.appointment!.actualStartTime, - appointmentView.appointment!.exactStartTime)) { + appointmentView.appointment!.actualStartTime, + appointmentView.appointment!.exactStartTime, + )) { setState(() { _mouseCursor = SystemMouseCursors.resizeUp; }); } else if (yPosition <= appointmentView.appointmentRect!.bottom && yPosition >= appointmentView.appointmentRect!.bottom - padding && CalendarViewHelper.isSameTimeSlot( - appointmentView.appointment!.actualEndTime, - appointmentView.appointment!.exactEndTime)) { + appointmentView.appointment!.actualEndTime, + appointmentView.appointment!.exactEndTime, + )) { setState(() { _mouseCursor = SystemMouseCursors.resizeDown; }); @@ -10517,17 +12394,23 @@ class _CalendarViewState extends State<_CalendarView> final RenderBox box = context.findRenderObject()! as RenderBox; final Offset localPosition = box.globalToLocal(globalPosition); double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); + widget.calendar.viewHeaderHeight, + widget.view, + ); final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - double allDayHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view, + ); + double allDayHeight = + _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; final bool isDayView = CalendarViewHelper.isDayView( - widget.view, - widget.calendar.timeSlotViewSettings.numberOfDaysInView, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.calendar.monthViewSettings.numberOfWeeksInView); + widget.view, + widget.calendar.timeSlotViewSettings.numberOfDaysInView, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.calendar.monthViewSettings.numberOfWeeksInView, + ); /// All day panel and view header are arranged horizontally, /// so get the maximum value from all day height and view header height and @@ -10556,25 +12439,35 @@ class _CalendarViewState extends State<_CalendarView> _updateHoveringForAllDayPanel(localPosition.dx, localPosition.dy); final AppointmentView? appointment = _getAllDayAppointmentOnPoint( - _updateCalendarStateDetails.allDayAppointmentViewCollection, - localPosition.dx, - localPosition.dy); - _updateMouseCursorForAppointment(appointment, localPosition.dx, - localPosition.dy, isTimelineViews, - isAllDayPanel: true); + _updateCalendarStateDetails.allDayAppointmentViewCollection, + localPosition.dx, + localPosition.dy, + ); + _updateMouseCursorForAppointment( + appointment, + localPosition.dx, + localPosition.dy, + isTimelineViews, + isAllDayPanel: true, + ); return; } _updateHoveringForViewHeader( - localPosition, - _isRTL ? widget.width - localPosition.dx : localPosition.dx, - localPosition.dy, - viewHeaderHeight); + localPosition, + _isRTL ? widget.width - localPosition.dx : localPosition.dx, + localPosition.dy, + viewHeaderHeight, + ); return; } - _updateHoveringForViewHeader(localPosition, localPosition.dx, - localPosition.dy, viewHeaderHeight); + _updateHoveringForViewHeader( + localPosition, + localPosition.dx, + localPosition.dy, + viewHeaderHeight, + ); return; } @@ -10586,22 +12479,30 @@ class _CalendarViewState extends State<_CalendarView> final double allDayExpanderHeight = panelHeight * _allDayExpanderAnimation!.value; - final double allDayBottom = isDayView - ? viewHeaderHeight - : viewHeaderHeight + _allDayHeight + allDayExpanderHeight; + final double allDayBottom = + isDayView + ? 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); + localPosition.dx, + localPosition.dy - viewHeaderHeight, + ); final AppointmentView? appointment = _getAllDayAppointmentOnPoint( - _updateCalendarStateDetails.allDayAppointmentViewCollection, - localPosition.dx, - localPosition.dy - viewHeaderHeight); - _updateMouseCursorForAppointment(appointment, localPosition.dx, - localPosition.dy - viewHeaderHeight, isTimelineViews, - isAllDayPanel: true); + _updateCalendarStateDetails.allDayAppointmentViewCollection, + localPosition.dx, + localPosition.dy - viewHeaderHeight, + ); + _updateMouseCursorForAppointment( + appointment, + localPosition.dx, + localPosition.dy - viewHeaderHeight, + isTimelineViews, + isAllDayPanel: true, + ); } else { _removeAllWidgetHovering(); } @@ -10611,15 +12512,23 @@ class _CalendarViewState extends State<_CalendarView> yPosition = localPosition.dy - (viewHeaderHeight + allDayHeight); - final AppointmentView? appointment = - _appointmentLayout.getAppointmentViewOnPoint( - localPosition.dx, yPosition + _scrollController!.offset); + final AppointmentView? appointment = _appointmentLayout + .getAppointmentViewOnPoint( + localPosition.dx, + yPosition + _scrollController!.offset, + ); _hoveringAppointmentView = appointment; if (appointment != null) { _updateHoveringForAppointment( - localPosition.dx, yPosition + _scrollController!.offset); - _updateMouseCursorForAppointment(appointment, localPosition.dx, - yPosition + _scrollController!.offset, isTimelineViews); + localPosition.dx, + yPosition + _scrollController!.offset, + ); + _updateMouseCursorForAppointment( + appointment, + localPosition.dx, + yPosition + _scrollController!.offset, + isTimelineViews, + ); _hoveringDate = null; return; } @@ -10630,9 +12539,10 @@ class _CalendarViewState extends State<_CalendarView> if (widget.calendar.showWeekNumber && widget.view == CalendarView.month) { final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if ((!_isRTL && xPosition < weekNumberPanelWidth) || (_isRTL && xPosition > widget.width - weekNumberPanelWidth)) { _hoveringDate = null; @@ -10656,7 +12566,8 @@ class _CalendarViewState extends State<_CalendarView> /// on all the calendar views. if (isTimelineViews) { if (_isRTL) { - xPosition = (_getSingleViewWidthForTimeLineView(this) * + xPosition = + (_getSingleViewWidthForTimeLineView(this) * widget.visibleDates.length) - (_scrollController!.offset + (_scrollController!.position.viewportDimension - @@ -10668,23 +12579,33 @@ class _CalendarViewState extends State<_CalendarView> if (localPosition.dy < viewHeaderHeight) { _updateHoveringForViewHeader( - localPosition, xPosition, localPosition.dy, viewHeaderHeight); + localPosition, + xPosition, + localPosition.dy, + viewHeaderHeight, + ); return; } yPosition = localPosition.dy - viewHeaderHeight - timeLabelWidth; if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + widget.calendar.dataSource, + widget.view, + )) { yPosition += _timelineViewVerticalScrollController!.offset; } - final AppointmentView? appointment = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + final AppointmentView? appointment = _appointmentLayout + .getAppointmentViewOnPoint(xPosition, yPosition); _hoveringAppointmentView = appointment; if (appointment != null) { _updateHoveringForAppointment(xPosition, yPosition); _updateMouseCursorForAppointment( - appointment, xPosition, yPosition, isTimelineViews); + appointment, + xPosition, + yPosition, + isTimelineViews, + ); _hoveringDate = null; return; } @@ -10704,26 +12625,33 @@ class _CalendarViewState extends State<_CalendarView> } final DateTime? hoverDate = _getDateFromPosition( - isTimelineViews ? localPosition.dx : xPosition, - yPosition, - timeLabelWidth); + isTimelineViews ? localPosition.dx : xPosition, + yPosition, + timeLabelWidth, + ); /// Remove the hovering when the position not in cell regions or non active /// cell regions. - final bool isMonthView = widget.view == CalendarView.month || + final bool isMonthView = + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); if (hoverDate == null || (isMonthView && !isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) || + widget.calendar.minDate, + widget.calendar.maxDate, + hoverDate, + )) || (!isMonthView && !CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - hoverDate, - timeInterval))) { + widget.calendar.minDate, + widget.calendar.maxDate, + hoverDate, + timeInterval, + ))) { if (_hoveringDate != null) { _hoveringDate = null; } @@ -10738,7 +12666,9 @@ class _CalendarViewState extends State<_CalendarView> /// Check the hovering month cell date is blackout date. if (isMonthView && CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, hoverDate)) { + widget.blackoutDates, + hoverDate, + )) { if (_hoveringDate != null) { _hoveringDate = null; } @@ -10762,8 +12692,11 @@ class _CalendarViewState extends State<_CalendarView> return; } - final int hoveringResourceIndex = - _getSelectedResourceIndex(yPosition, viewHeaderHeight, timeLabelWidth); + final int hoveringResourceIndex = _getSelectedResourceIndex( + yPosition, + viewHeaderHeight, + timeLabelWidth, + ); /// Restrict the hovering, while selected region as disabled [TimeRegion]. if (((widget.view == CalendarView.day || @@ -10772,7 +12705,10 @@ class _CalendarViewState extends State<_CalendarView> !_isEnabledRegion(yPosition, hoverDate, hoveringResourceIndex)) || (isTimelineViews && !_isEnabledRegion( - localPosition.dx, hoverDate, hoveringResourceIndex))) { + localPosition.dx, + hoverDate, + hoveringResourceIndex, + ))) { if (_hoveringDate != null) { _hoveringDate = null; } @@ -10790,10 +12726,11 @@ class _CalendarViewState extends State<_CalendarView> /// [SfCalendar] month not shown leading and trailing dates. if (isMonthView && !CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - hoverDate)) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + hoverDate, + )) { if (_hoveringDate != null) { _hoveringDate = null; } @@ -10818,7 +12755,9 @@ class _CalendarViewState extends State<_CalendarView> } final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + 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 @@ -10842,7 +12781,9 @@ class _CalendarViewState extends State<_CalendarView> return; } else if (widget.view != CalendarView.month && CalendarViewHelper.isSameTimeSlot( - _selectionPainter!.selectedDate, _hoveringDate) && + _selectionPainter!.selectedDate, + _hoveringDate, + ) && hoveringResourceIndex == _selectedResourceIndex) { _calendarCellNotifier.value = null; return; @@ -10903,7 +12844,10 @@ class _CalendarViewState extends State<_CalendarView> } AppointmentView? _getAllDayAppointmentOnPoint( - List? appointmentCollection, double x, double y) { + List? appointmentCollection, + double x, + double y, + ) { if (appointmentCollection == null) { return null; } @@ -10928,26 +12872,35 @@ class _CalendarViewState extends State<_CalendarView> List _getSelectedAppointments(DateTime selectedDate) { return (widget.calendar.dataSource != null && !AppointmentHelper.isCalendarAppointment( - widget.calendar.dataSource!)) + widget.calendar.dataSource!, + )) ? CalendarViewHelper.getCustomAppointments( - AppointmentHelper.getSelectedDateAppointments( - _updateCalendarStateDetails.appointments, - widget.calendar.timeZone, - selectedDate), - widget.calendar.dataSource) - : (AppointmentHelper.getSelectedDateAppointments( + AppointmentHelper.getSelectedDateAppointments( _updateCalendarStateDetails.appointments, widget.calendar.timeZone, - selectedDate)); + selectedDate, + ), + widget.calendar.dataSource, + ) + : (AppointmentHelper.getSelectedDateAppointments( + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + selectedDate, + )); } DateTime? _getDateFromPositionForMonth( - double cellWidth, double cellHeight, double x, double y) { + 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) + + index = + (columnIndex * DateTime.daysPerWeek) + (DateTime.daysPerWeek - rowIndex) - 1; } else { @@ -10962,16 +12915,22 @@ class _CalendarViewState extends State<_CalendarView> } DateTime? _getDateFromPositionForWeek( - double cellWidth, double cellHeight, double x, double y) { + double cellWidth, + double cellHeight, + double x, + double y, + ) { final int columnIndex = ((_scrollController!.offset + y) / cellHeight).truncate(); - final double time = columnIndex == -1 - ? 0 - : ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / - 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; + final double time = + columnIndex == -1 + ? 0 + : ((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(); @@ -10989,20 +12948,26 @@ class _CalendarViewState extends State<_CalendarView> } DateTime? _getDateFromPositionForTimeline( - double cellWidth, double cellHeight, double x, double y) { + double cellWidth, + double cellHeight, + double x, + double y, + ) { int rowIndex, columnIndex; if (_isRTL) { - rowIndex = (((_scrollController!.offset % - _getSingleViewWidthForTimeLineView(this)) + - (_scrollController!.position.viewportDimension - x)) / - cellWidth) - .truncate(); + rowIndex = + (((_scrollController!.offset % + _getSingleViewWidthForTimeLineView(this)) + + (_scrollController!.position.viewportDimension - x)) / + cellWidth) + .truncate(); } else { - rowIndex = (((_scrollController!.offset % - _getSingleViewWidthForTimeLineView(this)) + - x) / - cellWidth) - .truncate(); + rowIndex = + (((_scrollController!.offset % + _getSingleViewWidthForTimeLineView(this)) + + x) / + cellWidth) + .truncate(); } columnIndex = (_scrollController!.offset / _getSingleViewWidthForTimeLineView(this)) @@ -11011,8 +12976,10 @@ class _CalendarViewState extends State<_CalendarView> columnIndex += rowIndex ~/ _horizontalLinesCount!; rowIndex = (rowIndex % _horizontalLinesCount!).toInt(); } - final double time = ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / + final double time = + ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings, + ) / 60) * rowIndex) + widget.calendar.timeSlotViewSettings.startHour; @@ -11045,9 +13012,10 @@ class _CalendarViewState extends State<_CalendarView> /// Remove the selection when the position is to week number panel. final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if (x > widget.width || (!_isRTL && x < weekNumberPanelWidth) || (_isRTL && x > widget.width - weekNumberPanelWidth)) { @@ -11062,9 +13030,12 @@ class _CalendarViewState extends State<_CalendarView> cellWidth = (widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek; - cellHeight = (widget.height - + cellHeight = + (widget.height - CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view)) / + widget.calendar.viewHeaderHeight, + widget.view, + )) / widget.calendar.monthViewSettings.numberOfWeeksInView; return _getDateFromPositionForMonth(cellWidth, cellHeight, x, y); } @@ -11086,7 +13057,8 @@ class _CalendarViewState extends State<_CalendarView> case CalendarView.timelineWorkWeek: case CalendarView.timelineMonth: { - final double viewWidth = _timeIntervalHeight * + final double viewWidth = + _timeIntervalHeight * (_horizontalLinesCount! * widget.visibleDates.length); if ((!_isRTL && x >= viewWidth) || (_isRTL && x < (widget.width - viewWidth))) { @@ -11101,20 +13073,26 @@ class _CalendarViewState extends State<_CalendarView> void _drawSelection(double x, double y, double timeLabelWidth) { final DateTime? selectedDate = _getDateFromPosition(x, y, timeLabelWidth); - final bool isMonthView = widget.view == CalendarView.month || + final bool isMonthView = + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); + widget.calendar.timeSlotViewSettings, + ); if (selectedDate == null || (isMonthView && - !isDateWithInDateRange(widget.calendar.minDate, - widget.calendar.maxDate, selectedDate)) || + !isDateWithInDateRange( + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + )) || (!isMonthView && !CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - selectedDate, - timeInterval))) { + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + timeInterval, + ))) { return; } @@ -11131,7 +13109,9 @@ class _CalendarViewState extends State<_CalendarView> if (isMonthView && CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, selectedDate)) { + widget.blackoutDates, + selectedDate, + )) { return; } @@ -11142,10 +13122,11 @@ class _CalendarViewState extends State<_CalendarView> /// 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)) { + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate, + )) { return; } @@ -11184,8 +13165,9 @@ class _CalendarViewState extends State<_CalendarView> ); if (appointmentView != null && - _updateCalendarStateDetails.visibleAppointments - .contains(appointmentView.appointment)) { + _updateCalendarStateDetails.visibleAppointments.contains( + appointmentView.appointment, + )) { _selectionPainter!.appointmentView = appointmentView; } @@ -11194,64 +13176,67 @@ class _CalendarViewState extends State<_CalendarView> 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); + 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.themeData, + widget.calendar.minDate, + widget.calendar.maxDate, + _viewHeaderNotifier, + widget.calendar.cellBorderColor, + widget.blackoutDates, + widget.calendar.blackoutDatesTextStyle, + widget.textScaleFactor, + ); return ListView( - padding: EdgeInsets.zero, - controller: _timelineViewHeaderScrollController, - scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - children: [ - CustomPaint( - painter: _timelineViewHeader, - size: Size(width, height), - ) - ]); + padding: EdgeInsets.zero, + controller: _timelineViewHeaderScrollController, + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + children: [ + CustomPaint(painter: _timelineViewHeader, size: Size(width, height)), + ], + ); } } 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, - this.showWeekNumber, - this.isMobilePlatform, - this.weekNumberStyle, - this.localizations) - : super(repaint: viewHeaderNotifier); + this.visibleDates, + this.view, + this.viewHeaderStyle, + this.timeSlotViewSettings, + this.timeLabelWidth, + this.viewHeaderHeight, + this.monthViewSettings, + this.isRTL, + this.locale, + this.calendarTheme, + this.themeData, + this.todayHighlightColor, + this.todayTextStyle, + this.cellBorderColor, + this.minDate, + this.maxDate, + this.viewHeaderNotifier, + this.textScaleFactor, + this.showWeekNumber, + this.isMobilePlatform, + this.weekNumberStyle, + this.localizations, + ) : super(repaint: viewHeaderNotifier); final CalendarView view; final ViewHeaderStyle viewHeaderStyle; @@ -11261,6 +13246,7 @@ class _ViewHeaderViewPainter extends CustomPainter { final double timeLabelWidth; final double viewHeaderHeight; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final bool isRTL; final String locale; final Color? todayHighlightColor; @@ -11283,67 +13269,96 @@ class _ViewHeaderViewPainter extends CustomPainter { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - showWeekNumber, size.width, isMobilePlatform); - double width = view == CalendarView.month - ? size.width - weekNumberPanelWidth - : size.width; + showWeekNumber, + size.width, + isMobilePlatform, + ); + double width = + view == CalendarView.month + ? size.width - weekNumberPanelWidth + : 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 viewHeaderDayStyle = calendarTheme.viewHeaderDayTextStyle!; final TextStyle viewHeaderDateStyle = - viewHeaderStyle.dateTextStyle ?? calendarTheme.viewHeaderDateTextStyle!; + calendarTheme.viewHeaderDateTextStyle!; final DateTime today = DateTime.now(); if (view != CalendarView.month) { _addViewHeaderForTimeSlotViews( - canvas, size, viewHeaderDayStyle, viewHeaderDateStyle, width, today); + canvas, + size, + viewHeaderDayStyle, + viewHeaderDateStyle, + width, + today, + ); } else { _addViewHeaderForMonthView( - canvas, size, viewHeaderDayStyle, width, today, weekNumberPanelWidth); + canvas, + size, + viewHeaderDayStyle, + width, + today, + weekNumberPanelWidth, + ); } } void _addViewHeaderForMonthView( - Canvas canvas, - Size size, - TextStyle viewHeaderDayStyle, - double width, - DateTime today, - double weekNumberPanelWidth) { + Canvas canvas, + Size size, + TextStyle viewHeaderDayStyle, + double width, + DateTime today, + double weekNumberPanelWidth, + ) { TextStyle dayTextStyle = viewHeaderDayStyle; - double xPosition = isRTL - ? size.width - width - weekNumberPanelWidth - : weekNumberPanelWidth; + double xPosition = + isRTL + ? size.width - width - weekNumberPanelWidth + : weekNumberPanelWidth; double yPosition = 0; final int visibleDatesLength = visibleDates.length; - bool hasToday = monthViewSettings.numberOfWeeksInView > 0 && + bool hasToday = + monthViewSettings.numberOfWeeksInView > 0 && monthViewSettings.numberOfWeeksInView < 6 || visibleDates[visibleDatesLength ~/ 2].month == today.month; if (hasToday) { hasToday = isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDatesLength - 1], today); + visibleDates[0], + visibleDates[visibleDatesLength - 1], + today, + ); } for (int i = 0; i < DateTime.daysPerWeek; i++) { final DateTime currentDate = visibleDates[i]; - String dayText = DateFormat(monthViewSettings.dayFormat, locale) - .format(currentDate) - .toUpperCase(); + String dayText = + DateFormat( + monthViewSettings.dayFormat, + locale, + ).format(currentDate).toUpperCase(); dayText = _updateViewHeaderFormat(monthViewSettings.dayFormat, dayText); if (hasToday && currentDate.weekday == today.weekday) { final Color? todayTextColor = CalendarViewHelper.getTodayHighlightTextColor( - todayHighlightColor, todayTextStyle, calendarTheme); + todayHighlightColor, + todayTextStyle, + calendarTheme, + ); - dayTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) - : viewHeaderDayStyle.copyWith(color: todayTextColor); + dayTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, + color: todayTextColor, + ) + : viewHeaderDayStyle.copyWith(color: todayTextColor); } else { dayTextStyle = viewHeaderDayStyle; } @@ -11359,9 +13374,9 @@ class _ViewHeaderViewPainter extends CustomPainter { } _dayTextPainter.paint( - canvas, - Offset( - xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition)); + canvas, + Offset(xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition), + ); if (isRTL) { xPosition -= width; @@ -11371,12 +13386,14 @@ class _ViewHeaderViewPainter extends CustomPainter { } if (weekNumberPanelWidth != 0 && showWeekNumber) { const double defaultFontSize = 14; - final TextStyle weekNumberTextStyle = - weekNumberStyle.textStyle ?? calendarTheme.weekNumberTextStyle!; + final TextStyle weekNumberTextStyle = calendarTheme.weekNumberTextStyle!; final double xPosition = isRTL ? (size.width - weekNumberPanelWidth) : 0; - _updateDayTextPainter(weekNumberTextStyle, weekNumberPanelWidth, - localizations.weeknumberLabel); + _updateDayTextPainter( + weekNumberTextStyle, + weekNumberPanelWidth, + localizations.weeknumberLabel, + ); /// Condition added to remove the ellipsis, when the width is too small /// the ellipsis alone displayed, hence to resolve this removed ecclipsis @@ -11386,31 +13403,34 @@ class _ViewHeaderViewPainter extends CustomPainter { (_dayTextPainter.width <= (weekNumberTextStyle.fontSize ?? defaultFontSize) * 1.5)) { _dayTextPainter.ellipsis = null; - _dayTextPainter.layout(minWidth: 0, maxWidth: weekNumberPanelWidth); + _dayTextPainter.layout(maxWidth: weekNumberPanelWidth); } _dayTextPainter.paint( - canvas, - Offset( - xPosition + - (weekNumberPanelWidth / 2 - _dayTextPainter.width / 2), - yPosition)); + canvas, + Offset( + xPosition + (weekNumberPanelWidth / 2 - _dayTextPainter.width / 2), + yPosition, + ), + ); } } void _addViewHeaderForTimeSlotViews( - Canvas canvas, - Size size, - TextStyle viewHeaderDayStyle, - TextStyle viewHeaderDateStyle, - double width, - DateTime today) { + Canvas canvas, + Size size, + TextStyle viewHeaderDayStyle, + TextStyle viewHeaderDateStyle, + double width, + DateTime today, + ) { double xPosition, yPosition; final bool isDayView = CalendarViewHelper.isDayView( - view, - timeSlotViewSettings.numberOfDaysInView, - timeSlotViewSettings.nonWorkingDays, - monthViewSettings.numberOfWeeksInView); + view, + timeSlotViewSettings.numberOfDaysInView, + timeSlotViewSettings.nonWorkingDays, + monthViewSettings.numberOfWeeksInView, + ); final double labelWidth = isDayView && timeLabelWidth < 50 ? 50 : timeLabelWidth; TextStyle dayTextStyle = viewHeaderDayStyle; @@ -11420,7 +13440,7 @@ class _ViewHeaderViewPainter extends CustomPainter { width = labelWidth; } - final Paint _linePainter = Paint(); + final Paint linePainter = Paint(); xPosition = isDayView ? 0 : timeLabelWidth; yPosition = 2; final int visibleDatesLength = visibleDates.length; @@ -11431,30 +13451,42 @@ class _ViewHeaderViewPainter extends CustomPainter { for (int i = 0; i < visibleDatesLength; i++) { final DateTime currentDate = visibleDates[i]; - String dayText = DateFormat(timeSlotViewSettings.dayFormat, locale) - .format(currentDate) - .toUpperCase(); + String dayText = + DateFormat( + timeSlotViewSettings.dayFormat, + locale, + ).format(currentDate).toUpperCase(); - dayText = - _updateViewHeaderFormat(timeSlotViewSettings.dayFormat, dayText); + dayText = _updateViewHeaderFormat( + timeSlotViewSettings.dayFormat, + dayText, + ); - final String dateText = - DateFormat(timeSlotViewSettings.dateFormat).format(currentDate); + final String dateText = DateFormat( + timeSlotViewSettings.dateFormat, + ).format(currentDate); final bool isToday = isSameDate(currentDate, today); if (isToday) { - final Color? todayTextStyleColor = todayTextStyle != null - ? todayTextStyle!.color - : calendarTheme.todayTextStyle!.color; + final Color? todayTextStyleColor = calendarTheme.todayTextStyle!.color; final Color? todayTextColor = CalendarViewHelper.getTodayHighlightTextColor( - todayHighlightColor, todayTextStyle, calendarTheme); - dayTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) - : viewHeaderDayStyle.copyWith(color: todayTextColor); - dateTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith(fontSize: viewHeaderDateStyle.fontSize) - : viewHeaderDateStyle.copyWith(color: todayTextStyleColor); + todayHighlightColor, + todayTextStyle, + calendarTheme, + ); + dayTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, + color: todayTextColor, + ) + : viewHeaderDayStyle.copyWith(color: todayTextColor); + dateTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: viewHeaderDateStyle.fontSize, + ) + : viewHeaderDateStyle.copyWith(color: todayTextStyleColor); } else { dayTextStyle = viewHeaderDayStyle; dateTextStyle = viewHeaderDateStyle; @@ -11462,17 +13494,21 @@ class _ViewHeaderViewPainter extends CustomPainter { if (!isDateWithInDateRange(minDate, maxDate, currentDate)) { dayTextStyle = dayTextStyle.copyWith( - color: dayTextStyle.color != null - ? dayTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light - ? Colors.black26 - : Colors.white38); + color: + dayTextStyle.color != null + ? dayTextStyle.color!.withValues(alpha: 0.38) + : themeData.brightness == Brightness.light + ? Colors.black26 + : Colors.white38, + ); dateTextStyle = dateTextStyle.copyWith( - color: dateTextStyle.color != null - ? dateTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light - ? Colors.black26 - : Colors.white38); + color: + dateTextStyle.color != null + ? dateTextStyle.color!.withValues(alpha: 0.38) + : themeData.brightness == Brightness.light + ? Colors.black26 + : Colors.white38, + ); } _updateDayTextPainter(dayTextStyle, width, dayText); @@ -11486,9 +13522,9 @@ class _ViewHeaderViewPainter extends CustomPainter { _dateTextPainter.textDirection = TextDirection.ltr; _dateTextPainter.textAlign = TextAlign.left; _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dateTextPainter.textScaleFactor = textScaleFactor; + _dateTextPainter.textScaler = TextScaler.linear(textScaleFactor); - _dateTextPainter.layout(minWidth: 0, maxWidth: width); + _dateTextPainter.layout(maxWidth: width); /// To calculate the day start position by width and day painter final double dayXPosition = (cellWidth - _dayTextPainter.width) / 2; @@ -11497,7 +13533,8 @@ class _ViewHeaderViewPainter extends CustomPainter { final double dateXPosition = (cellWidth - _dateTextPainter.width) / 2; const int inBetweenPadding = 2; - yPosition = size.height / 2 - + yPosition = + size.height / 2 - (_dayTextPainter.height + topPadding + _dateTextPainter.height + @@ -11505,40 +13542,51 @@ class _ViewHeaderViewPainter extends CustomPainter { 2; _dayTextPainter.paint( - canvas, Offset(xPosition + dayXPosition, yPosition)); + canvas, + Offset(xPosition + dayXPosition, yPosition), + ); if (isToday) { _drawTodayCircle( - canvas, - xPosition + dateXPosition, - yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, - _dateTextPainter); + canvas, + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, + _dateTextPainter, + ); } if (viewHeaderNotifier.value != null) { - _addMouseHoverForTimeSlotView(canvas, size, xPosition, yPosition, - dateXPosition, topPadding, isToday, inBetweenPadding); + _addMouseHoverForTimeSlotView( + canvas, + size, + xPosition, + yPosition, + dateXPosition, + topPadding, + isToday, + inBetweenPadding, + ); } _dateTextPainter.paint( - canvas, - Offset( - xPosition + dateXPosition, - yPosition + - topPadding + - _dayTextPainter.height + - inBetweenPadding)); + canvas, + Offset( + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, + ), + ); if (!isDayView && showWeekNumber && ((currentDate.weekday == DateTime.monday) || (view == CalendarView.workWeek && - timeSlotViewSettings.nonWorkingDays - .contains(DateTime.monday) && + timeSlotViewSettings.nonWorkingDays.contains( + DateTime.monday, + ) && i == visibleDatesLength ~/ 2))) { final String weekNumber = DateTimeHelper.getWeekNumberOfYear(currentDate).toString(); final TextStyle weekNumberTextStyle = - weekNumberStyle.textStyle ?? calendarTheme.weekNumberTextStyle!; + calendarTheme.weekNumberTextStyle!; final TextSpan dayTextSpan = TextSpan( text: weekNumber, style: weekNumberTextStyle, @@ -11547,13 +13595,15 @@ class _ViewHeaderViewPainter extends CustomPainter { _dateTextPainter.textDirection = TextDirection.ltr; _dateTextPainter.textAlign = TextAlign.left; _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dateTextPainter.textScaleFactor = textScaleFactor; - _dateTextPainter.layout(minWidth: 0, maxWidth: timeLabelWidth); - final double weekNumberPosition = isRTL - ? (size.width - timeLabelWidth) + - ((timeLabelWidth - _dateTextPainter.width) / 2) - : (timeLabelWidth - _dateTextPainter.width) / 2; - final double weekNumberYPosition = size.height / 2 - + _dateTextPainter.textScaler = TextScaler.linear(textScaleFactor); + _dateTextPainter.layout(maxWidth: timeLabelWidth); + final double weekNumberPosition = + isRTL + ? (size.width - timeLabelWidth) + + ((timeLabelWidth - _dateTextPainter.width) / 2) + : (timeLabelWidth - _dateTextPainter.width) / 2; + final double weekNumberYPosition = + size.height / 2 - (_dayTextPainter.height + topPadding + _dateTextPainter.height + @@ -11564,25 +13614,37 @@ class _ViewHeaderViewPainter extends CustomPainter { inBetweenPadding; const double padding = 10; final Rect rect = Rect.fromLTRB( - weekNumberPosition - padding, - weekNumberYPosition - (padding / 2), - weekNumberPosition + _dateTextPainter.width + padding, - weekNumberYPosition + _dateTextPainter.height + (padding / 2)); - _linePainter.style = PaintingStyle.fill; - _linePainter.color = weekNumberStyle.backgroundColor ?? + weekNumberPosition - padding, + weekNumberYPosition - (padding / 2), + weekNumberPosition + _dateTextPainter.width + padding, + weekNumberYPosition + _dateTextPainter.height + (padding / 2), + ); + linePainter.style = PaintingStyle.fill; + linePainter.color = + weekNumberStyle.backgroundColor ?? calendarTheme.weekNumberBackgroundColor!; - final RRect roundedRect = - RRect.fromRectAndRadius(rect, const Radius.circular(padding / 2)); - canvas.drawRRect(roundedRect, _linePainter); + final RRect roundedRect = RRect.fromRectAndRadius( + rect, + const Radius.circular(padding / 2), + ); + canvas.drawRRect(roundedRect, linePainter); _dateTextPainter.paint( - canvas, Offset(weekNumberPosition, weekNumberYPosition)); + canvas, + Offset(weekNumberPosition, weekNumberYPosition), + ); final double xPosition = isRTL ? (size.width - timeLabelWidth) : 0; _updateDayTextPainter( - weekNumberTextStyle, timeLabelWidth, localizations.weeknumberLabel); + weekNumberTextStyle, + timeLabelWidth, + localizations.weeknumberLabel, + ); _dayTextPainter.paint( - canvas, - Offset(xPosition + (timeLabelWidth / 2 - _dayTextPainter.width / 2), - yPosition)); + canvas, + Offset( + xPosition + (timeLabelWidth / 2 - _dayTextPainter.width / 2), + yPosition, + ), + ); } if (isRTL) { @@ -11593,8 +13655,13 @@ class _ViewHeaderViewPainter extends CustomPainter { } } - void _addMouseHoverForMonth(Canvas canvas, Size size, double xPosition, - double yPosition, double width) { + void _addMouseHoverForMonth( + Canvas canvas, + Size size, + double xPosition, + double yPosition, + double width, + ) { if (xPosition + (width / 2 - _dayTextPainter.width / 2) <= viewHeaderNotifier.value!.dx && xPosition + @@ -11604,41 +13671,45 @@ class _ViewHeaderViewPainter extends CustomPainter { 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)); + canvas, + xPosition + (width / 2 - _dayTextPainter.width / 2), + yPosition, + _dayTextPainter, + hoveringColor: (themeData.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withValues(alpha: 0.04), + ); } } void _addMouseHoverForTimeSlotView( - Canvas canvas, - Size size, - double xPosition, - double yPosition, - double dateXPosition, - double topPadding, - bool isToday, - int padding) { + 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); + final Color hoveringColor = + isToday + ? Colors.black.withValues(alpha: 0.12) + : (themeData.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withValues(alpha: 0.04); _drawTodayCircle( - canvas, - xPosition + dateXPosition, - yPosition + topPadding + _dayTextPainter.height + padding, - _dateTextPainter, - hoveringColor: hoveringColor); + canvas, + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + padding, + _dateTextPainter, + hoveringColor: hoveringColor, + ); } } @@ -11649,10 +13720,11 @@ class _ViewHeaderViewPainter extends CustomPainter { case CalendarView.workWeek: { if (!CalendarViewHelper.isDayView( - view, - timeSlotViewSettings.numberOfDaysInView, - timeSlotViewSettings.nonWorkingDays, - monthViewSettings.numberOfWeeksInView) && + view, + timeSlotViewSettings.numberOfDaysInView, + timeSlotViewSettings.nonWorkingDays, + monthViewSettings.numberOfWeeksInView, + ) && (dayFormat == 'EE' && (locale.contains('en')))) { return dayText[0]; } @@ -11677,21 +13749,21 @@ class _ViewHeaderViewPainter extends CustomPainter { } void _updateDayTextPainter( - TextStyle dayTextStyle, double width, String dayText) { - final TextSpan dayTextSpan = TextSpan( - text: dayText, - style: dayTextStyle, - ); + TextStyle dayTextStyle, + double width, + String dayText, + ) { + final TextSpan dayTextSpan = TextSpan(text: dayText, style: dayTextStyle); _dayTextPainter.text = dayTextSpan; _dayTextPainter.textDirection = TextDirection.ltr; _dayTextPainter.textAlign = TextAlign.left; _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dayTextPainter.textScaleFactor = textScaleFactor; + _dayTextPainter.textScaler = TextScaler.linear(textScaleFactor); _dayTextPainter.ellipsis = '...'; _dayTextPainter.maxLines = 1; - _dayTextPainter.layout(minWidth: 0, maxWidth: width); + _dayTextPainter.layout(maxWidth: width); } double _getViewHeaderWidth(double width) { @@ -11709,10 +13781,11 @@ class _ViewHeaderViewPainter extends CustomPainter { case CalendarView.workWeek: { if (CalendarViewHelper.isDayView( - view, - timeSlotViewSettings.numberOfDaysInView, - timeSlotViewSettings.nonWorkingDays, - monthViewSettings.numberOfWeeksInView)) { + view, + timeSlotViewSettings.numberOfDaysInView, + timeSlotViewSettings.nonWorkingDays, + monthViewSettings.numberOfWeeksInView, + )) { return timeLabelWidth; } return width - timeLabelWidth; @@ -11741,16 +13814,23 @@ class _ViewHeaderViewPainter extends CustomPainter { //// draw today highlight circle in view header. void _drawTodayCircle( - Canvas canvas, double x, double y, TextPainter dateTextPainter, - {Color? hoveringColor}) { + 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); + canvas.drawCircle( + Offset(x + painterWidth, y + painterHeight), + radius + circlePadding, + _circlePainter, + ); } /// overrides this property to build the semantics information which uses to @@ -11787,13 +13867,15 @@ class _ViewHeaderViewPainter extends CustomPainter { 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]).toUpperCase(), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, size.height), + properties: SemanticsProperties( + label: DateFormat('EEEEE').format(visibleDates[i]).toUpperCase(), + textDirection: TextDirection.ltr, + ), ), - )); + ); if (isRTL) { left -= cellWidth; } else { @@ -11810,17 +13892,20 @@ class _ViewHeaderViewPainter extends CustomPainter { const double top = 0; double left; final bool isDayView = CalendarViewHelper.isDayView( - view, - timeSlotViewSettings.numberOfDaysInView, - timeSlotViewSettings.nonWorkingDays, - monthViewSettings.numberOfWeeksInView); - final double cellWidth = isDayView - ? size.width - : (size.width - timeLabelWidth) / visibleDates.length; + view, + timeSlotViewSettings.numberOfDaysInView, + timeSlotViewSettings.nonWorkingDays, + monthViewSettings.numberOfWeeksInView, + ); + final double cellWidth = + isDayView + ? size.width + : (size.width - timeLabelWidth) / visibleDates.length; if (isRTL) { - left = isDayView - ? size.width - timeLabelWidth - : (size.width - timeLabelWidth) - cellWidth; + left = + isDayView + ? size.width - timeLabelWidth + : (size.width - timeLabelWidth) - cellWidth; } else { left = isDayView ? 0 : timeLabelWidth; } @@ -11829,25 +13914,35 @@ class _ViewHeaderViewPainter extends CustomPainter { if (showWeekNumber && ((visibleDate.weekday == DateTime.monday && !isDayView) || (view == CalendarView.workWeek && - timeSlotViewSettings.nonWorkingDays - .contains(DateTime.monday) && + timeSlotViewSettings.nonWorkingDays.contains( + DateTime.monday, + ) && i == visibleDates.length ~/ 2))) { final int weekNumber = DateTimeHelper.getWeekNumberOfYear(visibleDate); - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(isRTL ? (size.width - timeLabelWidth) : 0, 0, - isRTL ? size.width : timeLabelWidth, viewHeaderHeight), + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + isRTL ? (size.width - timeLabelWidth) : 0, + 0, + isRTL ? size.width : timeLabelWidth, + viewHeaderHeight, + ), properties: SemanticsProperties( label: 'week$weekNumber', textDirection: TextDirection.ltr, - ))); + ), + ), + ); } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: _getAccessibilityText(visibleDates[i]), - textDirection: TextDirection.ltr, + 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 { @@ -11878,21 +13973,21 @@ class _ViewHeaderViewPainter extends CustomPainter { 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.showWeekNumber, - this.isMobilePlatform, - this.getCalendarState) - : super(repaint: repaintNotifier); + this.calendar, + this.view, + this.visibleDates, + this.selectedDate, + this.selectionDecoration, + this.timeIntervalHeight, + this.calendarTheme, + this.repaintNotifier, + this.isRTL, + this.selectedResourceIndex, + this.resourceItemHeight, + this.showWeekNumber, + this.isMobilePlatform, + this.getCalendarState, + ) : super(repaint: repaintNotifier); final SfCalendar calendar; final CalendarView view; @@ -11921,42 +14016,50 @@ class _SelectionPainter extends CustomPainter { 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 isDayView = CalendarViewHelper.isDayView( - view, - calendar.timeSlotViewSettings.numberOfDaysInView, - calendar.timeSlotViewSettings.nonWorkingDays, - calendar.monthViewSettings.numberOfWeeksInView); + view, + calendar.timeSlotViewSettings.numberOfDaysInView, + calendar.timeSlotViewSettings.nonWorkingDays, + calendar.monthViewSettings.numberOfWeeksInView, + ); final bool isMonthView = view == CalendarView.month || view == CalendarView.timelineMonth; - final int timeInterval = - CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); + final int timeInterval = CalendarViewHelper.getTimeInterval( + calendar.timeSlotViewSettings, + ); if (selectedDate != null && ((isMonthView && !isDateWithInDateRange( - calendar.minDate, calendar.maxDate, selectedDate)) || + calendar.minDate, + calendar.maxDate, + selectedDate, + )) || (!isMonthView && !CalendarViewHelper.isDateTimeWithInDateTimeRange( - calendar.minDate, - calendar.maxDate, - selectedDate!, - timeInterval)))) { + 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); + calendar.timeSlotViewSettings.timeRulerSize, + view, + ); double width = size.width; final bool isTimeline = CalendarViewHelper.isTimelineView(view); if (view != CalendarView.month && !isTimeline) { width -= timeLabelWidth; } - final bool isResourceEnabled = isTimeline && + final bool isResourceEnabled = + isTimeline && CalendarViewHelper.isResourceEnabled(calendar.dataSource, view); if ((selectedDate == null && appointmentView == null) || visibleDates != _updateCalendarStateDetails.currentViewVisibleDates || @@ -12037,7 +14140,10 @@ class _SelectionPainter extends CustomPainter { void _drawMonthSelection(Canvas canvas, Size size, double width) { final int visibleDatesLength = visibleDates.length; if (!isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDatesLength - 1], selectedDate)) { + visibleDates[0], + visibleDates[visibleDatesLength - 1], + selectedDate, + )) { return; } @@ -12046,15 +14152,18 @@ class _SelectionPainter extends CustomPainter { /// Check the selected cell date as trailing or leading date when /// [SfCalendar] month not shown leading and trailing dates. if (!CalendarViewHelper.isCurrentMonthDate( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - selectedDate!)) { + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate!, + )) { return; } if (CalendarViewHelper.isDateInDateCollection( - calendar.blackoutDates, selectedDate!)) { + calendar.blackoutDates, + selectedDate!, + )) { return; } @@ -12062,7 +14171,10 @@ class _SelectionPainter extends CustomPainter { if (isSameDate(visibleDates[i], selectedDate)) { final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - showWeekNumber, width, isMobilePlatform); + showWeekNumber, + width, + isMobilePlatform, + ); _cellWidth = (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; final int columnIndex = (i / DateTime.daysPerWeek).truncate(); _yPosition = columnIndex * _cellHeight; @@ -12079,7 +14191,11 @@ class _SelectionPainter extends CustomPainter { } void _drawDaySelection( - Canvas canvas, Size size, double width, double timeLabelWidth) { + Canvas canvas, + Size size, + double width, + double timeLabelWidth, + ) { if (isSameDate(visibleDates[0], selectedDate)) { if (isRTL) { _xPosition = 0; @@ -12090,7 +14206,10 @@ class _SelectionPainter extends CustomPainter { selectedDate = _updateSelectedDate(); _yPosition = AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); + calendar, + selectedDate!, + timeIntervalHeight, + ); _drawSlotSelection(width + timeLabelWidth, size.height, canvas); } } @@ -12104,13 +14223,16 @@ class _SelectionPainter extends CustomPainter { /// 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 timeInterval = CalendarViewHelper.getTimeInterval( + calendar.timeSlotViewSettings, + ); final int startHour = calendar.timeSlotViewSettings.startHour.toInt(); - final double startMinute = (calendar.timeSlotViewSettings.startHour - + final double startMinute = + (calendar.timeSlotViewSettings.startHour - calendar.timeSlotViewSettings.startHour.toInt()) * 60; - final int selectedMinutes = ((selectedDate!.hour - startHour) * 60) + + final int selectedMinutes = + ((selectedDate!.hour - startHour) * 60) + (selectedDate!.minute - startMinute.toInt()); if (selectedMinutes % timeInterval != 0) { final int diff = selectedMinutes % timeInterval; @@ -12125,10 +14247,17 @@ class _SelectionPainter extends CustomPainter { } void _drawWeekSelection( - Canvas canvas, Size size, double timeLabelWidth, double width) { + Canvas canvas, + Size size, + double timeLabelWidth, + double width, + ) { final int visibleDatesLength = visibleDates.length; if (isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDatesLength - 1], selectedDate)) { + visibleDates[0], + visibleDates[visibleDatesLength - 1], + selectedDate, + )) { for (int i = 0; i < visibleDatesLength; i++) { if (isSameDate(selectedDate, visibleDates[i])) { final int rowIndex = i; @@ -12140,7 +14269,10 @@ class _SelectionPainter extends CustomPainter { selectedDate = _updateSelectedDate(); _yPosition = AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); + calendar, + selectedDate!, + timeIntervalHeight, + ); _drawSlotSelection(width + timeLabelWidth, size.height, canvas); break; } @@ -12160,12 +14292,17 @@ class _SelectionPainter extends CustomPainter { void _drawTimelineMonthSelection(Canvas canvas, Size size, double width) { if (!isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + visibleDates[0], + visibleDates[visibleDates.length - 1], + selectedDate, + )) { return; } if (CalendarViewHelper.isDateInDateCollection( - calendar.blackoutDates, selectedDate!)) { + calendar.blackoutDates, + selectedDate!, + )) { return; } @@ -12174,9 +14311,10 @@ class _SelectionPainter extends CustomPainter { _yPosition = _getTimelineYPosition(); _xPosition = isRTL ? size.width - ((i + 1) * _cellWidth) : i * _cellWidth; - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight!; + final double height = + selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; _drawSlotSelection(width, height, canvas); break; } @@ -12185,21 +14323,29 @@ class _SelectionPainter extends CustomPainter { void _drawTimelineWeekSelection(Canvas canvas, Size size, double width) { if (isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + 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) + + _xPosition = + (i * singleViewWidth) + AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); + calendar, + selectedDate!, + timeIntervalHeight, + ); if (isRTL) { _xPosition = size.width - _xPosition - _cellWidth; } _yPosition = _getTimelineYPosition(); - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight!; + final double height = + selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; _drawSlotSelection(width, height, canvas); break; } @@ -12210,10 +14356,14 @@ class _SelectionPainter extends CustomPainter { 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)); + _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 @@ -12226,19 +14376,24 @@ class _SelectionPainter extends CustomPainter { //// padding used to avoid first, last row and column selection clipping. const double padding = 0.5; final 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); + _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)); + _boxPainter = selectionDecoration!.createBoxPainter( + _updateSelectionDecorationPainter, + ); + _boxPainter.paint( + canvas, + Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size, textDirection: TextDirection.ltr), + ); } @override @@ -12255,16 +14410,17 @@ class _SelectionPainter extends CustomPainter { 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); + 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; @@ -12294,18 +14450,22 @@ class _TimeRulerView extends CustomPainter { 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); + canvas.drawLine( + Offset(lineXPosition, 0), + Offset(lineXPosition, size.height), + _linePainter, + ); } - _textPainter.textDirection = TextDirection.ltr; + _textPainter.textDirection = + CalendarViewHelper.getTextDirectionBasedOnLocale(locale); _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); - final TextStyle timeTextStyle = - timeSlotViewSettings.timeTextStyle ?? calendarTheme.timeTextStyle!; + final TextStyle timeTextStyle = calendarTheme.timeTextStyle!; - final double hour = (timeSlotViewSettings.startHour - + final double hour = + (timeSlotViewSettings.startHour - timeSlotViewSettings.startHour.toInt()) * 60; if (isTimelineView) { @@ -12315,7 +14475,14 @@ class _TimeRulerView extends CustomPainter { for (int i = 0; i < visibleDates.length; i++) { date = visibleDates[i]; _drawTimeLabels( - canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + canvas, + size, + date, + hour, + xPosition, + yPosition, + timeTextStyle, + ); if (isRTL) { xPosition -= timelineViewWidth; } else { @@ -12324,47 +14491,78 @@ class _TimeRulerView extends CustomPainter { } } else { _drawTimeLabels( - canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + 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) { + void _drawTimeLabels( + Canvas canvas, + Size size, + DateTime date, + double hour, + double xPosition, + double yPosition, + TextStyle timeTextStyle, + ) { const int padding = 5; - final int timeInterval = - CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final int timeInterval = CalendarViewHelper.getTimeInterval( + timeSlotViewSettings, + ); + + final List timeFormatStrings = CalendarViewHelper.getListFromString( + timeSlotViewSettings.timeFormat, + ); /// 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++) { + 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)); + Rect.fromLTWH(xPosition, 0, timeIntervalHeight, size.height), + ); canvas.restore(); canvas.drawLine( - Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); + Offset(xPosition, 0), + Offset(xPosition, size.height), + _linePainter, + ); } final double minute = (i * timeInterval) + hour; - date = DateTime(date.year, date.month, date.day, - timeSlotViewSettings.startHour.toInt(), minute.toInt()); - final String time = - DateFormat(timeSlotViewSettings.timeFormat, locale).format(date); - final TextSpan span = TextSpan( - text: time, - style: timeTextStyle, + date = DateTime( + date.year, + date.month, + date.day, + timeSlotViewSettings.startHour.toInt(), + minute.toInt(), + ); + final String time = CalendarViewHelper.getLocalizedString( + date, + timeFormatStrings, + locale, ); + final TextSpan span = TextSpan(text: time, style: timeTextStyle); + final double cellWidth = isTimelineView ? timeIntervalHeight : size.width; _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: cellWidth); + _textPainter.layout(maxWidth: cellWidth); if (isTimelineView && _textPainter.height > size.height) { return; } @@ -12389,10 +14587,14 @@ class _TimeRulerView extends CustomPainter { _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); + 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()) { @@ -12423,22 +14625,30 @@ class _TimeRulerView extends CustomPainter { } class _CalendarMultiChildContainer extends Stack { - _CalendarMultiChildContainer( - {this.painter, - List children = const [], - required this.width, - required this.height}) - : super(children: children); + const _CalendarMultiChildContainer({ + // ignore: unused_element_parameter + this.painter, + List children = const [], + required this.width, + required this.height, + required this.builder, + }) : super(children: children); final CustomPainter? painter; final double width; final double height; + final MonthCellBuilder? builder; @override RenderStack createRenderObject(BuildContext context) { final Directionality? widget = context.dependOnInheritedWidgetOfExactType(); - return _MultiChildContainerRenderObject(width, height, - painter: painter, direction: widget?.textDirection); + return _MultiChildContainerRenderObject( + width, + height, + builder, + painter: painter, + direction: widget?.textDirection, + ); } @override @@ -12457,10 +14667,16 @@ class _CalendarMultiChildContainer extends Stack { } class _MultiChildContainerRenderObject extends RenderStack { - _MultiChildContainerRenderObject(this._width, this._height, - {CustomPainter? painter, TextDirection? direction}) - : _painter = painter, - super(textDirection: direction); + _MultiChildContainerRenderObject( + this._width, + this._height, + this.builder, { + CustomPainter? painter, + TextDirection? direction, + }) : _painter = painter, + super(textDirection: direction); + + final MonthCellBuilder? builder; CustomPainter? get painter => _painter; CustomPainter? _painter; @@ -12532,6 +14748,31 @@ class _MultiChildContainerRenderObject extends RenderStack { } } + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + if (builder != null) { + RenderBox? child = lastChild; + bool isChildHit = false; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + if (isHit) { + isChildHit = true; + } + child = childParentData.previousSibling; + } + return isChildHit; + } + return super.hitTestChildren(result, position: position); + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -12547,8 +14788,10 @@ class _MultiChildContainerRenderObject extends RenderStack { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); for (dynamic child = firstChild; child != null; child = childAfter(child)) { child.layout(constraints); } @@ -12580,9 +14823,10 @@ class _MultiChildContainerRenderObject extends RenderStack { 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 SemanticsNode newChild = + _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); @@ -12626,10 +14870,12 @@ class _MultiChildContainerRenderObject extends RenderStack { semantics.addAll(painter!.semanticsBuilder!(size)); } // ignore: avoid_as - for (RenderRepaintBoundary? child = firstChild! as RenderRepaintBoundary; - child != null; - // ignore: avoid_as - child = childAfter(child) as RenderRepaintBoundary?) { + for ( + RenderRepaintBoundary? child = firstChild! as RenderRepaintBoundary; + child != null; + // ignore: avoid_as + child = childAfter(child) as RenderRepaintBoundary? + ) { if (child.child is! CustomCalendarRenderObject) { continue; } @@ -12649,7 +14895,7 @@ class _MultiChildContainerRenderObject extends RenderStack { class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { /// Creates scroll physics that does not let the user scroll. const _CustomNeverScrollableScrollPhysics({ScrollPhysics? parent}) - : super(parent: parent); + : super(parent: parent); @override _CustomNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) { @@ -12657,22 +14903,25 @@ class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { /// physics, because flutter framework set different parent physics /// based on platform(iOS, Android, etc.,) return _CustomNeverScrollableScrollPhysics( - parent: buildParent(const ClampingScrollPhysics( - parent: RangeMaintainingScrollPhysics()))); + parent: buildParent( + const 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); + this.timeIntervalSize, + this.timeRulerSize, + this.timeSlotViewSettings, + this.isTimelineView, + this.visibleDates, + this.todayHighlightColor, + this.isRTL, + ValueNotifier repaintNotifier, + this.timeZone, + ) : super(repaint: repaintNotifier); final double timeIntervalSize; final TimeSlotViewSettings timeSlotViewSettings; final bool isTimelineView; @@ -12680,6 +14929,7 @@ class _CurrentTimeIndicator extends CustomPainter { final double timeRulerSize; final Color? todayHighlightColor; final bool isRTL; + final String timeZone; @override void paint(Canvas canvas, Size size) { @@ -12689,6 +14939,11 @@ class _CurrentTimeIndicator extends CustomPainter { final int totalMinutes = (hours * 60) + minutes; final int viewStartMinutes = (timeSlotViewSettings.startHour * 60).toInt(); final int viewEndMinutes = (timeSlotViewSettings.endHour * 60).toInt(); + DateTime getLocationDateTime = DateTime.now(); + + if (!timeZoneLoaded) { + return; + } if (totalMinutes < viewStartMinutes || totalMinutes > viewEndMinutes) { return; } @@ -12706,17 +14961,30 @@ class _CurrentTimeIndicator extends CustomPainter { return; } - final double minuteHeight = timeIntervalSize / + final double minuteHeight = + timeIntervalSize / CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + + if (timeZoneLoaded && timeZone != '') { + getLocationDateTime = AppointmentHelper.convertTimezone(now, timeZone); + } else { + getLocationDateTime = DateTime.now(); + } + 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; + Duration( + hours: getLocationDateTime.hour, + minutes: getLocationDateTime.minute, + ), + 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; @@ -12724,8 +14992,11 @@ class _CurrentTimeIndicator extends CustomPainter { startXPosition = size.width - startXPosition; } canvas.drawCircle(Offset(startXPosition, 5), 5, painter); - canvas.drawLine(Offset(startXPosition, 0), - Offset(startXPosition, size.height), painter); + canvas.drawLine( + Offset(startXPosition, 0), + Offset(startXPosition, size.height), + painter, + ); } else { final double viewSize = (size.width - timeRulerSize) / visibleDates.length; @@ -12739,8 +15010,11 @@ class _CurrentTimeIndicator extends CustomPainter { startXPosition = size.width - startXPosition; } canvas.drawCircle(Offset(startXPosition, startYPosition), 5, painter); - canvas.drawLine(Offset(viewStartPosition, startYPosition), - Offset(viewEndPosition, startYPosition), painter); + canvas.drawLine( + Offset(viewStartPosition, startYPosition), + Offset(viewEndPosition, startYPosition), + painter, + ); } } @@ -12757,28 +15031,31 @@ class _CurrentTimeIndicator extends CustomPainter { /// Returns the date time value from the position. DateTime? _timeFromPosition( - DateTime date, - TimeSlotViewSettings timeSlotViewSettings, - double positionY, - _CalendarViewState? currentState, - double timeIntervalHeight, - bool isTimelineView) { + DateTime date, + TimeSlotViewSettings timeSlotViewSettings, + double positionY, + _CalendarViewState? currentState, + double timeIntervalHeight, + bool isTimelineView, +) { final double topPosition = currentState == null ? 0 : currentState._scrollController!.offset; final double singleIntervalHeightForAnHour = (60 / CalendarViewHelper.getTimeInterval(timeSlotViewSettings)) * - timeIntervalHeight; + timeIntervalHeight; final double startHour = timeSlotViewSettings.startHour; final double endHour = timeSlotViewSettings.endHour; if (isTimelineView) { if (currentState!._isRTL) { - positionY = (currentState._scrollController!.offset % + positionY = + (currentState._scrollController!.offset % _getSingleViewWidthForTimeLineView(currentState)) + (currentState._scrollController!.position.viewportDimension - positionY); } else { - positionY += currentState._scrollController!.offset % + positionY += + currentState._scrollController!.offset % _getSingleViewWidthForTimeLineView(currentState); } } else { @@ -12794,10 +15071,10 @@ DateTime? _timeFromPosition( hour = ((hour - endHour) + startHour).toInt(); } } - return DateTime(date.year, date.month, date.day, hour, minute, 0); + return DateTime(date.year, date.month, date.day, hour, minute); } - return DateTime(date.year, date.month, date.day, 0, 0, 0); + return DateTime(date.year, date.month, date.day); } /// Returns the single view width from the time line view for time line @@ -12808,15 +15085,23 @@ double _getSingleViewWidthForTimeLineView(_CalendarViewState viewState) { } class _ResizingPaintDetails { - _ResizingPaintDetails( - {this.appointmentView, - required this.position, - this.isAllDayPanel = false, - this.scrollPosition, - this.monthRowCount = 0, - this.monthCellHeight, - this.appointmentColor = Colors.transparent, - this.resizingTime}); + _ResizingPaintDetails({ + // ignore: unused_element_parameter + this.appointmentView, + required this.position, + // ignore: unused_element_parameter + this.isAllDayPanel = false, + // ignore: unused_element_parameter + this.scrollPosition, + // ignore: unused_element_parameter + this.monthRowCount = 0, + // ignore: unused_element_parameter + this.monthCellHeight, + // ignore: unused_element_parameter + this.appointmentColor = Colors.transparent, + // ignore: unused_element_parameter + this.resizingTime, + }); AppointmentView? appointmentView; final ValueNotifier position; @@ -12830,22 +15115,22 @@ class _ResizingPaintDetails { class _ResizingAppointmentPainter extends CustomPainter { _ResizingAppointmentPainter( - this.resizingDetails, - this.isRTL, - this.textScaleFactor, - this.isMobilePlatform, - this.appointmentTextStyle, - this.allDayHeight, - this.viewHeaderHeight, - this.timeLabelWidth, - this.timeIntervalHeight, - this.scrollController, - this.dragAndDropSettings, - this.view, - this.mouseCursor, - this.weekNumberPanelWidth, - this.calendarTheme) - : super(repaint: resizingDetails.value.position); + this.resizingDetails, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentTextStyle, + this.allDayHeight, + this.viewHeaderHeight, + this.timeLabelWidth, + this.timeIntervalHeight, + this.scrollController, + this.dragAndDropSettings, + this.view, + this.mouseCursor, + this.weekNumberPanelWidth, + this.calendarTheme, + ) : super(repaint: resizingDetails.value.position); final ValueNotifier<_ResizingPaintDetails> resizingDetails; @@ -12893,9 +15178,11 @@ class _ResizingAppointmentPainter extends CustomPainter { ? 0 : resizingDetails.value.scrollPosition ?? scrollController!.offset; - final bool isForwardResize = mouseCursor == SystemMouseCursors.resizeDown || + final bool isForwardResize = + mouseCursor == SystemMouseCursors.resizeDown || mouseCursor == SystemMouseCursors.resizeRight; - final bool isBackwardResize = mouseCursor == SystemMouseCursors.resizeUp || + final bool isBackwardResize = + mouseCursor == SystemMouseCursors.resizeUp || mouseCursor == SystemMouseCursors.resizeLeft; const int textStartPadding = 3; @@ -12905,7 +15192,8 @@ class _ResizingAppointmentPainter extends CustomPainter { _shadowPainter.color = resizingDetails.value.appointmentColor; final bool isTimelineView = CalendarViewHelper.isTimelineView(view); - final bool isHorizontalResize = resizingDetails.value.isAllDayPanel || + final bool isHorizontalResize = + resizingDetails.value.isAllDayPanel || isTimelineView || view == CalendarView.month; double left = resizingDetails.value.position.value!.dx, @@ -12978,19 +15266,28 @@ class _ResizingAppointmentPainter extends CustomPainter { right = isRTL ? size.width : size.width - weekNumberPanelWidth; } - bottom = top + + bottom = + top + resizingDetails.value.appointmentView!.appointmentRect!.height; rect = Rect.fromLTRB(left, top, right, bottom); canvas.drawRect(rect, _shadowPainter); - paintBorder(canvas, rect, - left: BorderSide( - color: calendarTheme.selectionBorderColor!, width: 2), - right: BorderSide( - color: calendarTheme.selectionBorderColor!, width: 2), - bottom: BorderSide( - color: calendarTheme.selectionBorderColor!, width: 2), - top: BorderSide( - color: calendarTheme.selectionBorderColor!, width: 2)); + paintBorder( + canvas, + rect, + left: BorderSide( + color: calendarTheme.selectionBorderColor!, + width: 2, + ), + right: BorderSide( + color: calendarTheme.selectionBorderColor!, + width: 2, + ), + bottom: BorderSide( + color: calendarTheme.selectionBorderColor!, + width: 2, + ), + top: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + ); } } else { if (isForwardResize) { @@ -13001,23 +15298,29 @@ class _ResizingAppointmentPainter extends CustomPainter { } else if (isTimelineView) { left = resizingDetails.value.appointmentView!.appointmentRect!.left - - scrollOffset; + scrollOffset; if (isRTL) { left = scrollOffset + scrollController!.position.viewportDimension; - left = left - + left = + left - ((scrollController!.position.viewportDimension + scrollController!.position.maxScrollExtent) - resizingDetails - .value.appointmentView!.appointmentRect!.left); + .value + .appointmentView! + .appointmentRect! + .left); } } right = resizingDetails.value.position.value!.dx; top = resizingDetails.value.position.value!.dy; - bottom = top + + bottom = + top + resizingDetails.value.appointmentView!.appointmentRect!.height; } else { - top = resizingDetails.value.appointmentView!.appointmentRect!.top - + top = + resizingDetails.value.appointmentView!.appointmentRect!.top - scrollOffset + allDayHeight + viewHeaderHeight; @@ -13039,29 +15342,34 @@ class _ResizingAppointmentPainter extends CustomPainter { } else if (isTimelineView) { right = resizingDetails.value.appointmentView!.appointmentRect!.right - - scrollOffset; + scrollOffset; if (isRTL) { right = scrollOffset + scrollController!.position.viewportDimension; - right = right - + right = + right - ((scrollController!.position.viewportDimension + scrollController!.position.maxScrollExtent) - resizingDetails - .value.appointmentView!.appointmentRect!.right); + .value + .appointmentView! + .appointmentRect! + .right); } } left = resizingDetails.value.position.value!.dx; top = resizingDetails.value.position.value!.dy; - bottom = top + + bottom = + top + resizingDetails.value.appointmentView!.appointmentRect!.height; } else { top = resizingDetails.value.position.value!.dy; bottom = resizingDetails.value.appointmentView!.appointmentRect!.bottom - - scrollOffset + - allDayHeight + - viewHeaderHeight; + scrollOffset + + allDayHeight + + viewHeaderHeight; if (top < viewHeaderHeight + allDayHeight) { top = viewHeaderHeight + allDayHeight; } @@ -13097,39 +15405,56 @@ class _ResizingAppointmentPainter extends CustomPainter { final bool isRecurrenceAppointment = resizingDetails.value.appointmentView!.appointment!.recurrenceRule != - null && - resizingDetails - .value.appointmentView!.appointment!.recurrenceRule!.isNotEmpty; + null && + resizingDetails + .value + .appointmentView! + .appointment! + .recurrenceRule! + .isNotEmpty; _updateTextPainter(span); if (view != CalendarView.month) { - _addSubjectTextForTimeslotViews(canvas, textStartPadding, xPosition, - yPosition, isRecurrenceAppointment, rect); + _addSubjectTextForTimeslotViews( + canvas, + textStartPadding, + xPosition, + yPosition, + isRecurrenceAppointment, + rect, + ); } else { _addSubjectTextForMonthView( - canvas, - resizingDetails.value.appointmentView!.appointmentRect!, - appointmentTextStyle, - span, - isRecurrenceAppointment, - xPosition, - rect, - yPosition); + canvas, + resizingDetails.value.appointmentView!.appointmentRect!, + appointmentTextStyle, + span, + isRecurrenceAppointment, + xPosition, + rect, + yPosition, + ); } - paintBorder(canvas, rect, - left: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), - right: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), - bottom: - BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), - top: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2)); + paintBorder( + canvas, + rect, + left: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + right: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + bottom: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + top: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + ); } /// Draw the time indicator when resizing the appointment on all calendar /// views except month and timelineMonth views. void _drawTimeIndicator( - Canvas canvas, bool isTimelineView, Size size, bool isBackwardResize) { + Canvas canvas, + bool isTimelineView, + Size size, + bool isBackwardResize, + ) { if (view == CalendarView.month || view == CalendarView.timelineMonth) { return; } @@ -13141,15 +15466,15 @@ class _ResizingAppointmentPainter extends CustomPainter { } final TextSpan span = TextSpan( - text: DateFormat(dragAndDropSettings.indicatorTimeFormat) - .format(resizingDetails.value.resizingTime!), - style: dragAndDropSettings.timeIndicatorStyle ?? - calendarTheme.timeIndicatorTextStyle, + text: DateFormat( + dragAndDropSettings.indicatorTimeFormat, + ).format(resizingDetails.value.resizingTime!), + style: calendarTheme.timeIndicatorTextStyle, ); _updateTextPainter(span); _textPainter.layout( - minWidth: 0, - maxWidth: isTimelineView ? timeIntervalHeight : timeLabelWidth); + maxWidth: isTimelineView ? timeIntervalHeight : timeLabelWidth, + ); double xPosition; double yPosition; if (isTimelineView) { @@ -13180,29 +15505,33 @@ class _ResizingAppointmentPainter extends CustomPainter { } void _addSubjectTextForTimeslotViews( - Canvas canvas, - int textStartPadding, - double xPosition, - double yPosition, - bool isRecurrenceAppointment, - Rect rect) { + Canvas canvas, + int textStartPadding, + double xPosition, + double yPosition, + bool isRecurrenceAppointment, + Rect rect, + ) { final double totalHeight = resizingDetails.value.appointmentView!.appointmentRect!.height - - textStartPadding; + textStartPadding; _updatePainterMaxLines(totalHeight); double maxTextWidth = resizingDetails.value.appointmentView!.appointmentRect!.width - - textStartPadding; + textStartPadding; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); if (isRTL) { xPosition -= textStartPadding + _textPainter.width; } _textPainter.paint( - canvas, - Offset(xPosition + (isRTL ? 0 : textStartPadding), - yPosition + textStartPadding)); + canvas, + Offset( + xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding, + ), + ); if (isRecurrenceAppointment || resizingDetails.value.appointmentView!.appointment!.recurrenceId != null) { @@ -13211,31 +15540,38 @@ class _ResizingAppointmentPainter extends CustomPainter { textSize = rect.width > rect.height ? rect.height : rect.width; } _addRecurrenceIcon( - rect, canvas, textStartPadding, isRecurrenceAppointment, textSize); + rect, + canvas, + textStartPadding, + isRecurrenceAppointment, + textSize, + ); } } void _addSubjectTextForMonthView( - Canvas canvas, - RRect appointmentRect, - TextStyle style, - TextSpan span, - bool isRecurrenceAppointment, - double xPosition, - Rect rect, - double yPosition) { + Canvas canvas, + RRect appointmentRect, + TextStyle style, + TextSpan span, + bool isRecurrenceAppointment, + double xPosition, + Rect rect, + double yPosition, + ) { double textSize = -1; if (textSize == -1) { //// left and right side padding value 2 subtracted in appointment width double maxTextWidth = appointmentRect.width - 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; for (double j = style.fontSize! - 1; j > 0; j--) { - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); if (_textPainter.height >= appointmentRect.height) { style = style.copyWith(fontSize: j); span = TextSpan( - text: resizingDetails.value.appointmentView!.appointment!.subject, - style: style); + text: resizingDetails.value.appointmentView!.appointment!.subject, + style: style, + ); _updateTextPainter(span); } else { textSize = j + 1; @@ -13244,13 +15580,14 @@ class _ResizingAppointmentPainter extends CustomPainter { } } else { span = TextSpan( - text: resizingDetails.value.appointmentView!.appointment!.subject, - style: style.copyWith(fontSize: textSize)); + text: resizingDetails.value.appointmentView!.appointment!.subject, + style: style.copyWith(fontSize: textSize), + ); _updateTextPainter(span); } final double textWidth = appointmentRect.width - (isRecurrenceAppointment ? textSize : 1); - _textPainter.layout(minWidth: 0, maxWidth: textWidth > 0 ? textWidth : 0); + _textPainter.layout(maxWidth: textWidth > 0 ? textWidth : 0); if (isRTL) { xPosition -= (isRTL ? 0 : 2) + _textPainter.width; } @@ -13271,57 +15608,76 @@ class _ResizingAppointmentPainter extends CustomPainter { _textPainter.textDirection = TextDirection.ltr; _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); } - void _addRecurrenceIcon(Rect rect, Canvas canvas, int? textPadding, - bool isRecurrenceAppointment, double textSize) { + void _addRecurrenceIcon( + Rect rect, + Canvas canvas, + int? textPadding, + bool isRecurrenceAppointment, + double textSize, + ) { const double xPadding = 2; const double bottomPadding = 2; final TextSpan icon = AppointmentHelper.getRecurrenceIcon( - appointmentTextStyle.color!, textSize, isRecurrenceAppointment); + appointmentTextStyle.color!, + textSize, + isRecurrenceAppointment, + ); _textPainter.text = icon; if (view == CalendarView.month) { - _textPainter.layout( - minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); + _textPainter.layout(maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); final double yPosition = rect.top + ((rect.height - _textPainter.height) / 2); const double rightPadding = 0; - final double recurrenceStartPosition = isRTL - ? rect.left + rightPadding - : rect.right - _textPainter.width - rightPadding; + final double recurrenceStartPosition = + isRTL + ? rect.left + rightPadding + : rect.right - _textPainter.width - rightPadding; canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(recurrenceStartPosition, yPosition, - recurrenceStartPosition + _textPainter.width, rect.bottom), - resizingDetails.value.appointmentView!.appointmentRect!.tlRadius), - _shadowPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + recurrenceStartPosition, + yPosition, + recurrenceStartPosition + _textPainter.width, + rect.bottom, + ), + resizingDetails.value.appointmentView!.appointmentRect!.tlRadius, + ), + _shadowPainter, + ); _textPainter.paint(canvas, Offset(recurrenceStartPosition, yPosition)); } else { double maxTextWidth = resizingDetails.value.appointmentView!.appointmentRect!.width - - textPadding! - - 2; + textPadding! - + 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - isRTL - ? rect.left + textSize + xPadding - : rect.right - textSize - xPadding, - rect.bottom - bottomPadding - textSize, - isRTL ? rect.left : rect.right, - rect.bottom), - resizingDetails.value.appointmentView!.appointmentRect!.tlRadius), - _shadowPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL + ? rect.left + textSize + xPadding + : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + isRTL ? rect.left : rect.right, + rect.bottom, + ), + resizingDetails.value.appointmentView!.appointmentRect!.tlRadius, + ), + _shadowPainter, + ); _textPainter.paint( - canvas, - Offset( - isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, - rect.bottom - bottomPadding - textSize)); + canvas, + Offset( + isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + ), + ); } } @@ -13339,7 +15695,9 @@ class _ResizingAppointmentPainter extends CustomPainter { } dynamic _getCalendarAppointmentToObject( - CalendarAppointment? calendarAppointment, SfCalendar calendar) { + CalendarAppointment? calendarAppointment, + SfCalendar calendar, +) { if (calendarAppointment == null) { return null; } @@ -13350,19 +15708,27 @@ dynamic _getCalendarAppointmentToObject( if (calendarAppointment.data is Appointment) { return appointment; } - final dynamic customObject = calendar.dataSource! - .convertAppointmentToObject(calendarAppointment.data, appointment); - assert(customObject != null, - 'Implement convertToCalendarAppointment method from CalendarDataSource'); + final dynamic customObject = calendar.dataSource!.convertAppointmentToObject( + calendarAppointment.data, + appointment, + ); + assert( + customObject != null, + 'Implement convertToCalendarAppointment method from CalendarDataSource', + ); return customObject; } class _DragPaintDetails { - _DragPaintDetails( - {this.appointmentView, - required this.position, - this.draggingTime, - this.timeIntervalHeight}); + _DragPaintDetails({ + // ignore: unused_element_parameter + this.appointmentView, + required this.position, + // ignore: unused_element_parameter + this.draggingTime, + // ignore: unused_element_parameter + this.timeIntervalHeight, + }); AppointmentView? appointmentView; final ValueNotifier position; @@ -13373,21 +15739,22 @@ class _DragPaintDetails { @immutable class _DraggingAppointmentWidget extends StatefulWidget { const _DraggingAppointmentWidget( - this.dragDetails, - this.isRTL, - this.textScaleFactor, - this.isMobilePlatform, - this.appointmentTextStyle, - this.dragAndDropSettings, - this.calendarView, - this.allDayPanelHeight, - this.viewHeaderHeight, - this.timeLabelWidth, - this.resourceItemHeight, - this.calendarTheme, - this.calendar, - this.width, - this.height); + this.dragDetails, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentTextStyle, + this.dragAndDropSettings, + this.calendarView, + this.allDayPanelHeight, + this.viewHeaderHeight, + this.timeLabelWidth, + this.resourceItemHeight, + this.calendarTheme, + this.calendar, + this.width, + this.height, + ); final ValueNotifier<_DragPaintDetails> dragDetails; @@ -13435,8 +15802,9 @@ class _DraggingAppointmentState extends State<_DraggingAppointmentWidget> { @override void dispose() { - widget.dragDetails.value.position - .removeListener(_updateDraggingAppointment); + widget.dragDetails.value.position.removeListener( + _updateDraggingAppointment, + ); super.dispose(); } @@ -13453,27 +15821,31 @@ class _DraggingAppointmentState extends State<_DraggingAppointmentWidget> { if (widget.dragDetails.value.appointmentView != null && widget.calendar.appointmentBuilder != null) { final DateTime date = DateTime( - _draggingAppointmentView!.appointment!.actualStartTime.year, - _draggingAppointmentView!.appointment!.actualStartTime.month, - _draggingAppointmentView!.appointment!.actualStartTime.day); + _draggingAppointmentView!.appointment!.actualStartTime.year, + _draggingAppointmentView!.appointment!.actualStartTime.month, + _draggingAppointmentView!.appointment!.actualStartTime.day, + ); child = widget.calendar.appointmentBuilder!( - context, - CalendarAppointmentDetails( - date, - List.unmodifiable([ - CalendarViewHelper.getAppointmentDetail( - _draggingAppointmentView!.appointment!, - widget.calendar.dataSource) - ]), - Rect.fromLTWH( - widget.dragDetails.value.position.value!.dx, - widget.dragDetails.value.position.value!.dy, - widget.isRTL - ? -_draggingAppointmentView!.appointmentRect!.width - : _draggingAppointmentView!.appointmentRect!.width, - _draggingAppointmentView!.appointmentRect!.height), - isMoreAppointmentRegion: false)); + context, + CalendarAppointmentDetails( + date, + List.unmodifiable([ + CalendarViewHelper.getAppointmentDetail( + _draggingAppointmentView!.appointment!, + widget.calendar.dataSource, + ), + ]), + Rect.fromLTWH( + widget.dragDetails.value.position.value!.dx, + widget.dragDetails.value.position.value!.dy, + widget.isRTL + ? -_draggingAppointmentView!.appointmentRect!.width + : _draggingAppointmentView!.appointmentRect!.width, + _draggingAppointmentView!.appointmentRect!.height, + ), + ), + ); } return _DraggingAppointmentRenderObjectWidget( @@ -13500,22 +15872,22 @@ class _DraggingAppointmentState extends State<_DraggingAppointmentWidget> { class _DraggingAppointmentRenderObjectWidget extends SingleChildRenderObjectWidget { const _DraggingAppointmentRenderObjectWidget( - this.dragDetails, - this.isRTL, - this.textScaleFactor, - this.isMobilePlatform, - this.appointmentTextStyle, - this.dragAndDropSettings, - this.calendarView, - this.allDayPanelHeight, - this.viewHeaderHeight, - this.timeLabelWidth, - this.resourceItemHeight, - this.calendarTheme, - this.width, - this.height, - {Widget? child}) - : super(child: child); + this.dragDetails, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentTextStyle, + this.dragAndDropSettings, + this.calendarView, + this.allDayPanelHeight, + this.viewHeaderHeight, + this.timeLabelWidth, + this.resourceItemHeight, + this.calendarTheme, + this.width, + this.height, { + Widget? child, + }) : super(child: child); final _DragPaintDetails dragDetails; final bool isRTL; @@ -13547,25 +15919,28 @@ class _DraggingAppointmentRenderObjectWidget @override RenderObject createRenderObject(BuildContext context) { return _DraggingAppointmentRenderObject( - dragDetails, - isRTL, - textScaleFactor, - isMobilePlatform, - appointmentTextStyle, - dragAndDropSettings, - calendarView, - allDayPanelHeight, - viewHeaderHeight, - timeLabelWidth, - resourceItemHeight, - calendarTheme, - width, - height); + dragDetails, + isRTL, + textScaleFactor, + isMobilePlatform, + appointmentTextStyle, + dragAndDropSettings, + calendarView, + allDayPanelHeight, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + calendarTheme, + width, + height, + ); } @override void updateRenderObject( - BuildContext context, _DraggingAppointmentRenderObject renderObject) { + BuildContext context, + _DraggingAppointmentRenderObject renderObject, + ) { renderObject ..dragDetails = dragDetails ..isRTL = isRTL @@ -13587,20 +15962,21 @@ class _DraggingAppointmentRenderObjectWidget class _DraggingAppointmentRenderObject extends RenderBox with RenderObjectWithChildMixin { _DraggingAppointmentRenderObject( - this._dragDetails, - this._isRTL, - this._textScaleFactor, - this._isMobilePlatform, - this._appointmentTextStyle, - this._dragAndDropSettings, - this._calendarView, - this._allDayPanelHeight, - this._viewHeaderHeight, - this._timeLabelWidth, - this._resourceItemHeight, - this._calendarTheme, - this._width, - this._height); + this._dragDetails, + this._isRTL, + this._textScaleFactor, + this._isMobilePlatform, + this._appointmentTextStyle, + this._dragAndDropSettings, + this._calendarView, + this._allDayPanelHeight, + this._viewHeaderHeight, + this._timeLabelWidth, + this._resourceItemHeight, + this._calendarTheme, + this._width, + this._height, + ); double _width; @@ -13857,14 +16233,19 @@ class _DraggingAppointmentRenderObject extends RenderBox @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); - child?.layout(constraints.copyWith( + child?.layout( + constraints.copyWith( minWidth: dragDetails.appointmentView!.appointmentRect!.width, minHeight: dragDetails.appointmentView!.appointmentRect!.height, maxWidth: dragDetails.appointmentView!.appointmentRect!.width, - maxHeight: dragDetails.appointmentView!.appointmentRect!.height)); + maxHeight: dragDetails.appointmentView!.appointmentRect!.height, + ), + ); } @override @@ -13874,13 +16255,15 @@ class _DraggingAppointmentRenderObject extends RenderBox _drawDefaultUI(context.canvas, isTimelineView); } else { context.paintChild( - child!, - Offset( - isRTL - ? dragDetails.position.value!.dx - - dragDetails.appointmentView!.appointmentRect!.width - : dragDetails.position.value!.dx, - dragDetails.position.value!.dy)); + child!, + Offset( + isRTL + ? dragDetails.position.value!.dx - + dragDetails.appointmentView!.appointmentRect!.width + : dragDetails.position.value!.dx, + dragDetails.position.value!.dy, + ), + ); if (dragAndDropSettings.showTimeIndicator && dragDetails.draggingTime != null) { _drawTimeIndicator(context.canvas, isTimelineView, size); @@ -13899,18 +16282,20 @@ class _DraggingAppointmentRenderObject extends RenderBox double yPosition; xPosition = dragDetails.position.value!.dx; yPosition = dragDetails.position.value!.dy; - _shadowPainter.color = - dragDetails.appointmentView!.appointment!.color.withOpacity(0.5); + _shadowPainter.color = dragDetails.appointmentView!.appointment!.color + .withValues(alpha: 0.5); final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH( - dragDetails.position.value!.dx, - dragDetails.position.value!.dy, - isRTL - ? -dragDetails.appointmentView!.appointmentRect!.width - : dragDetails.appointmentView!.appointmentRect!.width, - dragDetails.appointmentView!.appointmentRect!.height), - dragDetails.appointmentView!.appointmentRect!.tlRadius); + Rect.fromLTWH( + dragDetails.position.value!.dx, + dragDetails.position.value!.dy, + isRTL + ? -dragDetails.appointmentView!.appointmentRect!.width + : dragDetails.appointmentView!.appointmentRect!.width, + dragDetails.appointmentView!.appointmentRect!.height, + ), + dragDetails.appointmentView!.appointmentRect!.tlRadius, + ); final Path path = Path(); path.addRRect(rect); canvas.drawPath(path, _shadowPainter); @@ -13925,11 +16310,11 @@ class _DraggingAppointmentRenderObject extends RenderBox _textPainter.textDirection = TextDirection.ltr; _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); double maxTextWidth = dragDetails.appointmentView!.appointmentRect!.width - textStartPadding; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); if (isRTL) { xPosition -= textStartPadding + _textPainter.width; @@ -13942,15 +16327,20 @@ class _DraggingAppointmentRenderObject extends RenderBox maxTextWidth = dragDetails.appointmentView!.appointmentRect!.width - textStartPadding; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + _textPainter.layout(maxWidth: maxTextWidth); _textPainter.paint( - canvas, - isTimelineView - ? Offset(xPosition + (isRTL ? 0 : textStartPadding), - yPosition + textStartPadding) - : Offset(xPosition + (isRTL ? 0 : textStartPadding), - yPosition + textStartPadding)); + canvas, + isTimelineView + ? Offset( + xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding, + ) + : Offset( + xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding, + ), + ); if (dragAndDropSettings.showTimeIndicator && dragDetails.draggingTime != null) { _drawTimeIndicator(canvas, isTimelineView, size); @@ -13964,20 +16354,20 @@ class _DraggingAppointmentRenderObject extends RenderBox } final TextSpan span = TextSpan( - text: DateFormat(dragAndDropSettings.indicatorTimeFormat) - .format(dragDetails.draggingTime!), - style: dragAndDropSettings.timeIndicatorStyle ?? - calendarTheme.timeIndicatorTextStyle, + text: DateFormat( + dragAndDropSettings.indicatorTimeFormat, + ).format(dragDetails.draggingTime!), + style: calendarTheme.timeIndicatorTextStyle, ); _textPainter.text = span; _textPainter.maxLines = 1; _textPainter.textDirection = TextDirection.ltr; _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); final double timeLabelSize = isTimelineView ? dragDetails.timeIntervalHeight! : timeLabelWidth; - _textPainter.layout(minWidth: 0, maxWidth: timeLabelSize); + _textPainter.layout(maxWidth: timeLabelSize); double xPosition; double yPosition; if (isTimelineView) { 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 da4b55c9e..8faa3739b 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 @@ -15,22 +15,25 @@ class TimeSlotWidget extends StatefulWidget { /// Constructor to create the time slot widget to holds time slots view for /// day, week, workweek views. const TimeSlotWidget( - this.visibleDates, - this.horizontalLinesCount, - this.timeIntervalHeight, - this.timeLabelWidth, - this.cellBorderColor, - this.calendarTheme, - this.timeSlotViewSettings, - this.isRTL, - this.specialRegion, - this.calendarCellNotifier, - this.textScaleFactor, - this.timeRegionBuilder, - this.width, - this.height, - this.minDate, - this.maxDate); + this.visibleDates, + this.horizontalLinesCount, + this.timeIntervalHeight, + this.timeLabelWidth, + this.cellBorderColor, + this.calendarTheme, + this.themeData, + this.timeSlotViewSettings, + this.isRTL, + this.specialRegion, + this.calendarCellNotifier, + this.textScaleFactor, + this.timeRegionBuilder, + this.width, + this.height, + this.minDate, + this.maxDate, { + super.key, + }); /// Holds the visible dates collection for current time slot view. final List visibleDates; @@ -50,6 +53,9 @@ class TimeSlotWidget extends StatefulWidget { /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data value. + final ThemeData themeData; + /// Defines the time slot setting used to customize the time slots. final TimeSlotViewSettings timeSlotViewSettings; @@ -107,7 +113,9 @@ class _TimeSlotWidgetState extends State { widget.height != oldWidget.height || widget.timeRegionBuilder != oldWidget.timeRegionBuilder || !CalendarViewHelper.isCollectionEqual( - widget.specialRegion, oldWidget.specialRegion)) { + widget.specialRegion, + oldWidget.specialRegion, + )) { _updateSpecialRegionDetails(); _children.clear(); } @@ -124,9 +132,13 @@ class _TimeSlotWidgetState extends State { for (int i = 0; i < count; i++) { final TimeRegionView view = _specialRegionViews[i]; final Widget child = widget.timeRegionBuilder!( - context, - TimeRegionDetails(view.region.data, - widget.visibleDates[view.visibleIndex], view.bound)); + context, + TimeRegionDetails( + view.region.data, + widget.visibleDates[view.visibleIndex], + view.bound, + ), + ); _children.add(RepaintBoundary(child: child)); } @@ -139,6 +151,7 @@ class _TimeSlotWidgetState extends State { widget.timeLabelWidth, widget.cellBorderColor, widget.calendarTheme, + widget.themeData, widget.timeSlotViewSettings, widget.isRTL, widget.specialRegion, @@ -159,13 +172,16 @@ class _TimeSlotWidgetState extends State { return; } - final double minuteHeight = widget.timeIntervalHeight / + final double minuteHeight = + widget.timeIntervalHeight / CalendarViewHelper.getTimeInterval(widget.timeSlotViewSettings); - final DateTime startDate = - AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + final DateTime startDate = AppointmentHelper.convertToStartTime( + widget.visibleDates[0], + ); final int visibleDatesLength = widget.visibleDates.length; final DateTime endDate = AppointmentHelper.convertToEndTime( - widget.visibleDates[visibleDatesLength - 1]); + widget.visibleDates[visibleDatesLength - 1], + ); final double width = widget.width - widget.timeLabelWidth; final double cellWidth = width / visibleDatesLength; for (int i = 0; i < widget.specialRegion!.length; i++) { @@ -189,15 +205,19 @@ class _TimeSlotWidgetState extends State { } int startIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, regionStartTime); + widget.visibleDates, + regionStartTime, + ); int endIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, regionEndTime); + widget.visibleDates, + regionEndTime, + ); double startYPosition = CalendarViewHelper.getTimeToPosition( - Duration( - hours: regionStartTime.hour, minutes: regionStartTime.minute), - widget.timeSlotViewSettings, - minuteHeight); + Duration(hours: regionStartTime.hour, minutes: regionStartTime.minute), + widget.timeSlotViewSettings, + minuteHeight, + ); if (startIndex == -1) { if (startDate.isAfter(regionStartTime)) { // Set index as 0 when the region start date before the visible @@ -226,9 +246,10 @@ class _TimeSlotWidgetState extends State { } double endYPosition = CalendarViewHelper.getTimeToPosition( - Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), - widget.timeSlotViewSettings, - minuteHeight); + Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), + widget.timeSlotViewSettings, + minuteHeight, + ); if (endIndex == -1) { /// Find the previous index when the end date as non working date. if (endDate.isAfter(regionEndTime)) { @@ -273,8 +294,12 @@ class _TimeSlotWidgetState extends State { startXPosition = widget.width - (startXPosition + cellWidth); } - final Rect rect = Rect.fromLTRB(startXPosition, startPosition, - startXPosition + cellWidth, endPosition); + final Rect rect = Rect.fromLTRB( + startXPosition, + startPosition, + startXPosition + cellWidth, + endPosition, + ); _specialRegionViews.add(TimeRegionView(j, region, rect)); } } @@ -282,25 +307,26 @@ class _TimeSlotWidgetState extends State { } class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { - _TimeSlotRenderWidget( - this.visibleDates, - this.horizontalLinesCount, - this.timeIntervalHeight, - this.timeLabelWidth, - this.cellBorderColor, - this.calendarTheme, - this.timeSlotViewSettings, - this.isRTL, - this.specialRegion, - this.calendarCellNotifier, - this.textScaleFactor, - this.width, - this.height, - this.specialRegionBounds, - this.minDate, - this.maxDate, - {List widgets = const []}) - : super(children: widgets); + const _TimeSlotRenderWidget( + this.visibleDates, + this.horizontalLinesCount, + this.timeIntervalHeight, + this.timeLabelWidth, + this.cellBorderColor, + this.calendarTheme, + this.themeData, + this.timeSlotViewSettings, + this.isRTL, + this.specialRegion, + this.calendarCellNotifier, + this.textScaleFactor, + this.width, + this.height, + this.specialRegionBounds, + this.minDate, + this.maxDate, { + List widgets = const [], + }) : super(children: widgets); final List visibleDates; final double horizontalLinesCount; @@ -308,6 +334,7 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { final double timeLabelWidth; final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final TimeSlotViewSettings timeSlotViewSettings; final bool isRTL; final ValueNotifier calendarCellNotifier; @@ -322,27 +349,31 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { @override _TimeSlotRenderObject createRenderObject(BuildContext context) { return _TimeSlotRenderObject( - visibleDates, - horizontalLinesCount, - timeIntervalHeight, - timeLabelWidth, - cellBorderColor, - calendarTheme, - timeSlotViewSettings, - isRTL, - specialRegion, - calendarCellNotifier, - textScaleFactor, - width, - height, - specialRegionBounds, - minDate, - maxDate); + visibleDates, + horizontalLinesCount, + timeIntervalHeight, + timeLabelWidth, + cellBorderColor, + calendarTheme, + themeData, + timeSlotViewSettings, + isRTL, + specialRegion, + calendarCellNotifier, + textScaleFactor, + width, + height, + specialRegionBounds, + minDate, + maxDate, + ); } @override void updateRenderObject( - BuildContext context, _TimeSlotRenderObject renderObject) { + BuildContext context, + _TimeSlotRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..horizontalLinesCount = horizontalLinesCount @@ -350,6 +381,7 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { ..timeLabelWidth = timeLabelWidth ..cellBorderColor = cellBorderColor ..calendarTheme = calendarTheme + ..themeData = themeData ..timeSlotViewSettings = timeSlotViewSettings ..isRTL = isRTL ..specialRegion = specialRegion @@ -365,22 +397,24 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { class _TimeSlotRenderObject extends CustomCalendarRenderObject { _TimeSlotRenderObject( - this._visibleDates, - this._horizontalLinesCount, - this._timeIntervalHeight, - this._timeLabelWidth, - this._cellBorderColor, - this._calendarTheme, - this._timeSlotViewSettings, - this._isRTL, - this._specialRegion, - this._calendarCellNotifier, - this._textScaleFactor, - this._width, - this._height, - this.specialRegionBounds, - this._minDate, - this._maxDate); + this._visibleDates, + this._horizontalLinesCount, + this._timeIntervalHeight, + this._timeLabelWidth, + this._cellBorderColor, + this._calendarTheme, + this._themeData, + this._timeSlotViewSettings, + this._isRTL, + this._specialRegion, + this._calendarCellNotifier, + this._textScaleFactor, + this._width, + this._height, + this.specialRegionBounds, + this._minDate, + this._maxDate, + ); List _visibleDates; @@ -476,6 +510,18 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { markNeedsPaint(); } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + } + TimeSlotViewSettings _timeSlotViewSettings; TimeSlotViewSettings get timeSlotViewSettings => _timeSlotViewSettings; @@ -625,8 +671,10 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; if (specialRegion == null || specialRegion!.isEmpty) { return; @@ -639,11 +687,14 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { continue; } final Rect rect = view.bound; - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: rect.height, maxHeight: rect.height, minWidth: rect.width, - maxWidth: rect.width)); + maxWidth: rect.width, + ), + ); child = childAfter(child); } } @@ -677,12 +728,17 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { _drawTimeSlots(context.canvas, visibleDatesCount); } - void _minMaxExceeds(DateTime minDate, DateTime maxDate, Canvas canvas, - int visibleDatesCount) { + void _minMaxExceeds( + DateTime minDate, + DateTime maxDate, + Canvas canvas, + int visibleDatesCount, + ) { final DateTime visibleStartDate = visibleDates[0]; final DateTime visibleEndDate = visibleDates[visibleDatesCount - 1]; - final DateTime maxEndDate = - AppointmentHelper.convertToEndTime(visibleDates[visibleDatesCount - 1]); + final DateTime maxEndDate = AppointmentHelper.convertToEndTime( + visibleDates[visibleDatesCount - 1], + ); if (isDateWithInDateRange(visibleStartDate, visibleEndDate, minDate)) { _drawDisabledDate(visibleStartDate, minDate, canvas, visibleDatesCount); } @@ -691,26 +747,39 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { } } - void _drawDisabledDate(DateTime disabledStartDate, DateTime disabledEndDate, - Canvas canvas, int visibleDatesCount) { - final double minuteHeight = timeIntervalHeight / + void _drawDisabledDate( + DateTime disabledStartDate, + DateTime disabledEndDate, + Canvas canvas, + int visibleDatesCount, + ) { + final double minuteHeight = + timeIntervalHeight / CalendarViewHelper.getTimeInterval(timeSlotViewSettings); final double viewWidth = width - timeLabelWidth; final double cellWidth = viewWidth / visibleDatesCount; - final int startIndex = - DateTimeHelper.getVisibleDateIndex(visibleDates, disabledStartDate); - final int endIndex = - DateTimeHelper.getVisibleDateIndex(visibleDates, disabledEndDate); + 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); + Duration( + hours: disabledStartDate.hour, + minutes: disabledStartDate.minute, + ), + timeSlotViewSettings, + minuteHeight, + ); final double endYPosition = CalendarViewHelper.getTimeToPosition( - Duration(hours: disabledEndDate.hour, minutes: disabledEndDate.minute), - timeSlotViewSettings, - minuteHeight); + 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; @@ -729,9 +798,13 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { rightPosition = width - rightPosition; } rect = Rect.fromLTRB( - leftPosition, topPosition, rightPosition, bottomPosition); + leftPosition, + topPosition, + rightPosition, + bottomPosition, + ); _linePainter.style = PaintingStyle.fill; - _linePainter.color = Colors.grey.withOpacity(0.2); + _linePainter.color = Colors.grey.withValues(alpha: 0.2); canvas.drawRect(rect, _linePainter); } } @@ -748,7 +821,10 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { isRTL ? size.width - timeLabelWidth : size.width; for (int i = 1; i <= horizontalLinesCount; i++) { canvas.drawLine( - Offset(startXPosition, y), Offset(endXPosition, y), _linePainter); + Offset(startXPosition, y), + Offset(endXPosition, y), + _linePainter, + ); y += timeIntervalHeight; if (y == size.height) { @@ -771,11 +847,14 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { const double strokeWidth = 2; const double padding = strokeWidth / 2; double left = (calendarCellNotifier.value!.dx ~/ _cellWidth) * _cellWidth; - double top = (calendarCellNotifier.value!.dy ~/ timeIntervalHeight) * + double top = + (calendarCellNotifier.value!.dy ~/ timeIntervalHeight) * timeIntervalHeight; _linePainter.style = PaintingStyle.stroke; _linePainter.strokeWidth = strokeWidth; - _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); + _linePainter.color = calendarTheme.selectionBorderColor!.withValues( + alpha: 0.4, + ); left += isRTL ? 0 : timeLabelWidth; double height = timeIntervalHeight; if (top == 0) { @@ -784,12 +863,14 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { } canvas.drawRect( - Rect.fromLTWH( - left, - top, - left + _cellWidth == size.width ? _cellWidth - padding : _cellWidth, - top + height == size.height ? height - padding : height), - _linePainter); + Rect.fromLTWH( + left, + top, + left + _cellWidth == size.width ? _cellWidth - padding : _cellWidth, + top + height == size.height ? height - padding : height, + ), + _linePainter, + ); } void _addSpecialRegions(Canvas canvas) { @@ -798,24 +879,29 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { } final TextPainter painter = TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textAlign: isRTL ? TextAlign.right : TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); + textDirection: TextDirection.ltr, + maxLines: 1, + textAlign: isRTL ? TextAlign.right : TextAlign.left, + textScaler: TextScaler.linear(textScaleFactor), + textWidthBasis: TextWidthBasis.longestLine, + ); _linePainter.style = PaintingStyle.fill; final int count = specialRegionBounds.length; final TextStyle defaultTextStyle = TextStyle( - color: calendarTheme.brightness == Brightness.dark - ? Colors.white54 - : Colors.black45); + color: + themeData.brightness == Brightness.dark + ? Colors.white54 + : Colors.black45, + ); for (int i = 0; i < count; i++) { final TimeRegionView view = specialRegionBounds[i]; final CalendarTimeRegion region = view.region; - _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); + _linePainter.color = region.color ?? Colors.grey.withValues(alpha: 0.2); - final TextStyle textStyle = region.textStyle ?? defaultTextStyle; + final TextStyle textStyle = themeData.textTheme.bodyMedium! + .copyWith(fontSize: 14) + .merge(region.textStyle ?? defaultTextStyle); final Rect rect = view.bound; canvas.drawRect(rect, _linePainter); if ((region.text == null || region.text!.isEmpty) && @@ -828,11 +914,12 @@ 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); + painter.layout(maxWidth: rect.width - 4); painter.paint(canvas, Offset(rect.left + 3, rect.top + 3)); } } @@ -853,20 +940,23 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { final int startHour = timeSlotViewSettings.startHour.toInt(); final int hour = ((timeSlotViewSettings.startHour - startHour) * 60).toInt(); - final int timeInterval = - CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + 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 int minute = (i * timeInterval) + hour; date = DateTime(date.year, date.month, date.day, startHour, minute); - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, cellHeight), - properties: SemanticsProperties( - label: DateFormat('h a, dd MMMM yyyy').format(date), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, cellHeight), + properties: SemanticsProperties( + label: DateFormat('h a, dd MMMM yyyy').format(date), + textDirection: TextDirection.ltr, + ), ), - )); + ); top += cellHeight; } 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 1c0579193..8f9879f2f 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 @@ -17,28 +17,31 @@ class MonthViewWidget extends StatefulWidget { /// Constructor to create the month view widget to holds month cells for /// calendar month view. const MonthViewWidget( - this.visibleDates, - this.rowCount, - this.monthCellStyle, - this.isRTL, - this.todayHighlightColor, - this.todayTextStyle, - this.cellBorderColor, - this.calendarTheme, - this.calendarCellNotifier, - this.showTrailingAndLeadingDates, - this.minDate, - this.maxDate, - this.calendar, - this.blackoutDates, - this.blackoutDatesTextStyle, - this.textScaleFactor, - this.builder, - this.width, - this.height, - this.weekNumberStyle, - this.isMobilePlatform, - this.visibleAppointmentNotifier); + this.visibleDates, + this.rowCount, + this.monthCellStyle, + this.isRTL, + this.todayHighlightColor, + this.todayTextStyle, + this.cellBorderColor, + this.calendarTheme, + this.themeData, + this.calendarCellNotifier, + this.showTrailingAndLeadingDates, + this.minDate, + this.maxDate, + this.calendar, + this.blackoutDates, + this.blackoutDatesTextStyle, + this.textScaleFactor, + this.builder, + this.width, + this.height, + this.weekNumberStyle, + this.isMobilePlatform, + this.visibleAppointmentNotifier, { + super.key, + }); /// Defines the row count for the month view. final int rowCount; @@ -64,6 +67,9 @@ class MonthViewWidget extends StatefulWidget { /// Holds the theme data details for calendar. final SfCalendarThemeData calendarTheme; + /// Holds the theme data for calendar. + final ThemeData themeData; + /// Holds the current hovering point used to paint the hovering. final ValueNotifier calendarCellNotifier; @@ -141,9 +147,10 @@ class _MonthViewWidgetState extends State { final List children = []; final double weekNumberPanelWidth = CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if (widget.builder != null) { final int visibleDatesCount = widget.visibleDates.length; final double cellWidth = @@ -154,7 +161,9 @@ class _MonthViewWidgetState extends State { widget.visibleDates[visibleDatesCount ~/ 2].month; final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - widget.rowCount, widget.showTrailingAndLeadingDates); + widget.rowCount, + widget.showTrailingAndLeadingDates, + ); for (int i = 0; i < visibleDatesCount; i++) { final DateTime currentVisibleDate = widget.visibleDates[i]; if (!showTrailingLeadingDates && @@ -169,31 +178,35 @@ class _MonthViewWidgetState extends State { } final List appointments = - AppointmentHelper.getSelectedDateAppointments( - widget.visibleAppointmentNotifier.value, - widget.calendar.timeZone, - currentVisibleDate); + AppointmentHelper.getSpecificDateVisibleAppointment( + currentVisibleDate, + widget.visibleAppointmentNotifier.value, + ); List monthCellAppointment = appointments; if (widget.calendar.dataSource != null && !AppointmentHelper.isCalendarAppointment( - widget.calendar.dataSource!)) { + widget.calendar.dataSource!, + )) { monthCellAppointment = CalendarViewHelper.getCustomAppointments( - appointments, widget.calendar.dataSource); + appointments, + widget.calendar.dataSource, + ); } 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))); + 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; @@ -214,6 +227,7 @@ class _MonthViewWidgetState extends State { widget.todayTextStyle, widget.cellBorderColor, widget.calendarTheme, + widget.themeData, widget.calendarCellNotifier, widget.minDate, widget.maxDate, @@ -227,6 +241,7 @@ class _MonthViewWidgetState extends State { weekNumberPanelWidth, widget.isMobilePlatform, children: children, + widget.builder, ); } @@ -238,30 +253,32 @@ class _MonthViewWidgetState extends State { } class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { - _MonthViewRenderObjectWidget( - this.visibleDates, - this.visibleAppointments, - this.rowCount, - this.monthCellStyle, - this.isRTL, - this.todayHighlightColor, - this.todayTextStyle, - this.cellBorderColor, - this.calendarTheme, - this.calendarCellNotifier, - this.minDate, - this.maxDate, - this.blackoutDates, - this.blackoutDatesTextStyle, - this.showTrailingAndLeadingDates, - this.textScaleFactor, - this.width, - this.height, - this.weekNumberStyle, - this.weekNumberPanelWidth, - this.isMobilePlatform, - {List children = const []}) - : super(children: children); + const _MonthViewRenderObjectWidget( + this.visibleDates, + this.visibleAppointments, + this.rowCount, + this.monthCellStyle, + this.isRTL, + this.todayHighlightColor, + this.todayTextStyle, + this.cellBorderColor, + this.calendarTheme, + this.themeData, + this.calendarCellNotifier, + this.minDate, + this.maxDate, + this.blackoutDates, + this.blackoutDatesTextStyle, + this.showTrailingAndLeadingDates, + this.textScaleFactor, + this.width, + this.height, + this.weekNumberStyle, + this.weekNumberPanelWidth, + this.isMobilePlatform, + this.builder, { + List children = const [], + }) : super(children: children); final int rowCount; final MonthCellStyle monthCellStyle; @@ -272,6 +289,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { final TextStyle? todayTextStyle; final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final ValueNotifier calendarCellNotifier; final DateTime minDate; final DateTime maxDate; @@ -284,36 +302,42 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { final WeekNumberStyle weekNumberStyle; final double weekNumberPanelWidth; final bool isMobilePlatform; + final MonthCellBuilder? builder; @override _MonthViewRenderObject createRenderObject(BuildContext context) { return _MonthViewRenderObject( - visibleDates, - visibleAppointments, - rowCount, - monthCellStyle, - isRTL, - todayHighlightColor, - todayTextStyle, - cellBorderColor, - calendarTheme, - calendarCellNotifier, - minDate, - maxDate, - blackoutDates, - blackoutDatesTextStyle, - showTrailingAndLeadingDates, - textScaleFactor, - width, - height, - weekNumberStyle, - weekNumberPanelWidth, - isMobilePlatform); + visibleDates, + visibleAppointments, + rowCount, + monthCellStyle, + isRTL, + todayHighlightColor, + todayTextStyle, + cellBorderColor, + calendarTheme, + themeData, + calendarCellNotifier, + minDate, + maxDate, + blackoutDates, + blackoutDatesTextStyle, + showTrailingAndLeadingDates, + textScaleFactor, + width, + height, + weekNumberStyle, + weekNumberPanelWidth, + isMobilePlatform, + builder, + ); } @override void updateRenderObject( - BuildContext context, _MonthViewRenderObject renderObject) { + BuildContext context, + _MonthViewRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..visibleAppointments = visibleAppointments @@ -324,6 +348,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { ..todayTextStyle = todayTextStyle ..cellBorderColor = cellBorderColor ..calendarTheme = calendarTheme + ..themeData = themeData ..calendarCellNotifier = calendarCellNotifier ..minDate = minDate ..maxDate = maxDate @@ -335,33 +360,37 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { ..height = height ..weekNumberPanelWidth = weekNumberPanelWidth ..weekNumberStyle = weekNumberStyle - ..isMobilePlatform = isMobilePlatform; + ..isMobilePlatform = isMobilePlatform + ..builder = builder; } } class _MonthViewRenderObject extends CustomCalendarRenderObject { _MonthViewRenderObject( - this._visibleDates, - this._visibleAppointments, - this._rowCount, - this._monthCellStyle, - this._isRTL, - this._todayHighlightColor, - this._todayTextStyle, - this._cellBorderColor, - this._calendarTheme, - this._calendarCellNotifier, - this._minDate, - this._maxDate, - this._blackoutDates, - this._blackoutDatesTextStyle, - this._showTrailingAndLeadingDates, - this._textScaleFactor, - this._width, - this._height, - this._weekNumberStyle, - this._weekNumberPanelWidth, - this._isMobilePlatform); + this._visibleDates, + this._visibleAppointments, + this._rowCount, + this._monthCellStyle, + this._isRTL, + this._todayHighlightColor, + this._todayTextStyle, + this._cellBorderColor, + this._calendarTheme, + this._themeData, + this._calendarCellNotifier, + this._minDate, + this._maxDate, + this._blackoutDates, + this._blackoutDatesTextStyle, + this._showTrailingAndLeadingDates, + this._textScaleFactor, + this._width, + this._height, + this._weekNumberStyle, + this._weekNumberPanelWidth, + this._isMobilePlatform, + this.builder, + ); bool _isMobilePlatform; @@ -568,7 +597,22 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { if (childCount != 0) { return; } + markNeedsPaint(); + } + + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + _themeData = value; + if (childCount != 0) { + return; + } markNeedsPaint(); } @@ -687,6 +731,39 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { } } + MonthCellBuilder? builder; + + @override + bool hitTestSelf(Offset position) { + return builder != null; + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + if (builder == null) { + return false; + } + + RenderBox? child = firstChild; + + while (child != null) { + final CalendarParentData childParentData = + child.parentData! as CalendarParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + if (isHit) { + return true; + } + child = childParentData.nextSibling; + } + return false; + } + /// attach will called when the render object rendered in view. @override void attach(PipelineOwner owner) { @@ -704,28 +781,33 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); final double cellWidth = (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; for (dynamic child = firstChild; child != null; child = childAfter(child)) { - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minWidth: cellWidth, minHeight: cellHeight, maxWidth: cellWidth, - maxHeight: cellHeight)); + maxHeight: cellHeight, + ), + ); } } @override void paint(PaintingContext context, Offset offset) { - final bool _isNeedCustomPaint = childCount != 0; + final bool isNeedCustomPaint = childCount != 0; if (_blackoutDatesIndex.isEmpty) { _updateBlackoutDatesIndex(); } - if (!_isNeedCustomPaint) { + if (!isNeedCustomPaint) { _drawMonthCells(context.canvas, size); } else { final double cellWidth = @@ -737,7 +819,9 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final int currentMonth = visibleDates[visibleDatesCount ~/ 2].month; final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - rowCount, showTrailingAndLeadingDates); + rowCount, + showTrailingAndLeadingDates, + ); _drawWeekNumberPanel(context.canvas, cellHeight); for (int i = 0; i < visibleDatesCount; i++) { final DateTime currentVisibleDate = visibleDates[i]; @@ -751,9 +835,12 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { visibleDates[(i ~/ DateTime.daysPerWeek) * DateTime.daysPerWeek]; /// Calculate the row end date based on visible dates index. - final DateTime endDate = addDuration( - startDate, const Duration(days: DateTime.daysPerWeek - 1)) - as DateTime; + final DateTime endDate = + addDuration( + startDate, + const Duration(days: DateTime.daysPerWeek - 1), + ) + as DateTime; /// Used to check the start and end date is current month date or not. final bool isCurrentMonthWeek = @@ -762,8 +849,13 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { if (weekNumberPanelWidth != 0 && (showTrailingLeadingDates || (!showTrailingLeadingDates && isCurrentMonthWeek))) { - _drawWeekNumber(context.canvas, size, currentVisibleDate, - cellHeight, yPosition); + _drawWeekNumber( + context.canvas, + size, + currentVisibleDate, + cellHeight, + yPosition, + ); } } @@ -778,15 +870,31 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { } context.paintChild( - child!, - Offset(isRTL ? size.width - xPosition - cellWidth : xPosition, - yPosition)); + child!, + Offset( + isRTL ? size.width - xPosition - cellWidth : xPosition, + yPosition, + ), + ); + + if (child.parentData != null) { + (child.parentData! as CalendarParentData).offset = Offset( + isRTL ? size.width - xPosition - cellWidth : xPosition, + yPosition, + ); + } child = childAfter(child); if (calendarCellNotifier.value != null && !_blackoutDatesIndex.contains(i)) { - _addMouseHovering(context.canvas, size, cellWidth, cellHeight, - isRTL ? xPosition - weekNumberPanelWidth : xPosition, yPosition); + _addMouseHovering( + context.canvas, + size, + cellWidth, + cellHeight, + isRTL ? xPosition - weekNumberPanelWidth : xPosition, + yPosition, + ); } xPosition += cellWidth; @@ -808,8 +916,10 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final int count = blackoutDates == null ? 0 : blackoutDates!.length; for (int i = 0; i < count; i++) { final DateTime blackoutDate = blackoutDates![i]; - final int blackoutDateIndex = - DateTimeHelper.getVisibleDateIndex(visibleDates, blackoutDate); + final int blackoutDateIndex = DateTimeHelper.getVisibleDateIndex( + visibleDates, + blackoutDate, + ); if (blackoutDateIndex == -1) { continue; } @@ -818,23 +928,29 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { } } - void _drawWeekNumber(Canvas canvas, Size size, DateTime date, - double cellHeight, double yPosition) { + void _drawWeekNumber( + Canvas canvas, + Size size, + DateTime date, + double cellHeight, + double yPosition, + ) { final String weekNumber = DateTimeHelper.getWeekNumberOfYear(date).toString(); double xPosition = isRTL ? size.width - weekNumberPanelWidth : 0; - final TextStyle weekNumberTextStyle = - weekNumberStyle.textStyle ?? calendarTheme.weekNumberTextStyle!; - final TextSpan textSpan = - TextSpan(text: weekNumber, style: weekNumberTextStyle); + final TextStyle weekNumberTextStyle = calendarTheme.weekNumberTextStyle!; + final TextSpan textSpan = TextSpan( + text: weekNumber, + style: weekNumberTextStyle, + ); _textPainter.text = textSpan; _textPainter.textDirection = TextDirection.ltr; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); const double topPadding = 4; - _textPainter.layout(minWidth: 0, maxWidth: weekNumberPanelWidth); + _textPainter.layout(maxWidth: weekNumberPanelWidth); xPosition += (weekNumberPanelWidth - _textPainter.width) / 2; _textPainter.paint(canvas, Offset(xPosition, yPosition + topPadding)); } @@ -848,13 +964,20 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final double padding = isMobilePlatform ? 5 : 0; final double left = xPosition + padding; final double right = (xPosition + weekNumberPanelWidth) - padding; - final Rect rect = - Rect.fromLTRB(left, padding, right, size.height - padding); + final Rect rect = Rect.fromLTRB( + left, + padding, + right, + size.height - padding, + ); _linePainter.style = PaintingStyle.fill; - _linePainter.color = weekNumberStyle.backgroundColor ?? + _linePainter.color = + weekNumberStyle.backgroundColor ?? calendarTheme.weekNumberBackgroundColor!; - final RRect roundedRect = - RRect.fromRectAndRadius(rect, Radius.circular(padding)); + final RRect roundedRect = RRect.fromRectAndRadius( + rect, + Radius.circular(padding), + ); canvas.drawRRect(roundedRect, _linePainter); if (isMobilePlatform) { @@ -863,7 +986,10 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor!; for (int i = 0; i < rowCount - 1; i++) { canvas.drawLine( - Offset(left, yPosition), Offset(right, yPosition), _linePainter); + Offset(left, yPosition), + Offset(right, yPosition), + _linePainter, + ); yPosition += cellHeight; } } @@ -875,58 +1001,60 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final double cellWidth = (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; - double xPosition = isRTL - ? size.width - cellWidth - weekNumberPanelWidth - : weekNumberPanelWidth; + double xPosition = + isRTL + ? size.width - cellWidth - weekNumberPanelWidth + : weekNumberPanelWidth; double yPosition = viewPadding; _textPainter.textDirection = TextDirection.ltr; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); final int visibleDatesCount = visibleDates.length; final DateTime currentMonthDate = visibleDates[visibleDatesCount ~/ 2]; final int nextMonth = - DateTimeHelper.getDateTimeValue(getNextMonthDate(currentMonthDate)) - .month; + DateTimeHelper.getDateTimeValue( + getNextMonthDate(currentMonthDate), + ).month; final int previousMonth = - DateTimeHelper.getDateTimeValue(getPreviousMonthDate(currentMonthDate)) - .month; + DateTimeHelper.getDateTimeValue( + getPreviousMonthDate(currentMonthDate), + ).month; final DateTime today = DateTime.now(); bool isCurrentDate; _linePainter.isAntiAlias = true; - final TextStyle todayStyle = - todayTextStyle ?? calendarTheme.todayTextStyle!; - final TextStyle currentMonthTextStyle = - monthCellStyle.textStyle ?? calendarTheme.activeDatesTextStyle!; + final TextStyle todayStyle = calendarTheme.todayTextStyle!; + final TextStyle currentMonthTextStyle = calendarTheme.activeDatesTextStyle!; final TextStyle previousMonthTextStyle = - monthCellStyle.trailingDatesTextStyle ?? - calendarTheme.trailingDatesTextStyle!; - final TextStyle nextMonthTextStyle = monthCellStyle.leadingDatesTextStyle ?? - calendarTheme.leadingDatesTextStyle!; - final TextStyle? blackoutDatesStyle = - blackoutDatesTextStyle ?? calendarTheme.blackoutDatesTextStyle; - final TextStyle disabledTextStyle = TextStyle( - color: currentMonthTextStyle.color != null - ? currentMonthTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light - ? Colors.black26 - : Colors.white38, - fontSize: 13, - fontFamily: 'Roboto'); + calendarTheme.trailingDatesTextStyle!; + final TextStyle nextMonthTextStyle = calendarTheme.leadingDatesTextStyle!; + final TextStyle? blackoutDatesStyle = calendarTheme.blackoutDatesTextStyle; + final TextStyle disabledTextStyle = currentMonthTextStyle.copyWith( + color: + currentMonthTextStyle.color != null + ? currentMonthTextStyle.color!.withValues(alpha: 0.38) + : themeData.brightness == Brightness.light + ? Colors.black26 + : Colors.white38, + ); final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - rowCount, showTrailingAndLeadingDates); + rowCount, + showTrailingAndLeadingDates, + ); - final Color currentMonthBackgroundColor = monthCellStyle.backgroundColor ?? + final Color currentMonthBackgroundColor = + monthCellStyle.backgroundColor ?? calendarTheme.activeDatesBackgroundColor!; final Color nextMonthBackgroundColor = monthCellStyle.leadingDatesBackgroundColor ?? - calendarTheme.leadingDatesBackgroundColor!; + calendarTheme.leadingDatesBackgroundColor!; final Color previousMonthBackgroundColor = monthCellStyle.trailingDatesBackgroundColor ?? - calendarTheme.trailingDatesBackgroundColor!; - final Color todayBackgroundColor = monthCellStyle.todayBackgroundColor ?? + calendarTheme.trailingDatesBackgroundColor!; + final Color todayBackgroundColor = + monthCellStyle.todayBackgroundColor ?? calendarTheme.todayBackgroundColor!; TextStyle textStyle = currentMonthTextStyle; @@ -944,20 +1072,28 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { visibleDates[(i ~/ DateTime.daysPerWeek) * DateTime.daysPerWeek]; /// Calculate the row end date based on visible dates index. - final DateTime endDate = addDuration( - startDate, const Duration(days: DateTime.daysPerWeek - 1)) - as DateTime; + final DateTime endDate = + addDuration( + startDate, + const Duration(days: DateTime.daysPerWeek - 1), + ) + as DateTime; /// Used to check the start and end date is current month date or not. final bool isCurrentMonthWeek = startDate.month == currentMonthDate.month || - endDate.month == currentMonthDate.month; + endDate.month == currentMonthDate.month; if (weekNumberPanelWidth != 0 && (showTrailingLeadingDates || (!showTrailingLeadingDates && isCurrentMonthWeek))) { _drawWeekNumber( - canvas, size, currentVisibleDate, cellHeight, yPosition); + canvas, + size, + currentVisibleDate, + cellHeight, + yPosition, + ); } } @@ -1025,8 +1161,13 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final bool isBlackoutDate = _blackoutDatesIndex.contains(i); if (isBlackoutDate) { - textStyle = blackoutDatesStyle ?? - textStyle.copyWith(decoration: TextDecoration.lineThrough); + if (blackoutDatesStyle != null) { + textStyle = textStyle.merge(blackoutDatesStyle); + } else { + textStyle = textStyle.copyWith( + decoration: TextDecoration.lineThrough, + ); + } } final TextSpan span = TextSpan( @@ -1036,7 +1177,7 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: cellWidth); + _textPainter.layout(maxWidth: cellWidth); //// In web when the mouse hovering the cell, the painter style set as stroke, //// hence if background color set for an cell and mouse hovered for the @@ -1045,13 +1186,24 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { //// the style s fill. _linePainter.style = PaintingStyle.fill; canvas.drawRect( - Rect.fromLTWH( - xPosition, yPosition - viewPadding, cellWidth, cellHeight), - _linePainter); + Rect.fromLTWH( + xPosition, + yPosition - viewPadding, + cellWidth, + cellHeight, + ), + _linePainter, + ); if (calendarCellNotifier.value != null && !isBlackoutDate) { - _addMouseHovering(canvas, size, cellWidth, cellHeight, xPosition, - yPosition - viewPadding); + _addMouseHovering( + canvas, + size, + cellWidth, + cellHeight, + xPosition, + yPosition - viewPadding, + ); } if (isCurrentDate) { @@ -1061,16 +1213,22 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final double textHeight = _textPainter.height / 2; canvas.drawCircle( - Offset(xPosition + cellWidth / 2, - yPosition + circlePadding + textHeight), - textHeight + viewPadding, - _linePainter); + Offset( + xPosition + cellWidth / 2, + yPosition + circlePadding + textHeight, + ), + textHeight + viewPadding, + _linePainter, + ); } _textPainter.paint( - canvas, - Offset(xPosition + (cellWidth / 2 - _textPainter.width / 2), - yPosition + circlePadding)); + canvas, + Offset( + xPosition + (cellWidth / 2 - _textPainter.width / 2), + yPosition + circlePadding, + ), + ); if (isRTL) { if (xPosition - 1 < 0) { @@ -1089,79 +1247,117 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { } _drawVerticalAndHorizontalLines( - canvas, size, yPosition, xPosition, cellHeight, cellWidth); + canvas, + size, + yPosition, + xPosition, + cellHeight, + cellWidth, + ); } - void _addMouseHovering(Canvas canvas, Size size, double cellWidth, - double cellHeight, double xPosition, double yPosition) { + 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.style = PaintingStyle.stroke; _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); + _linePainter.color = calendarTheme.selectionBorderColor!.withValues( + alpha: 0.4, + ); canvas.drawRect( - Rect.fromLTWH( - xPosition == 0 ? xPosition + linePadding : xPosition, - yPosition, - (xPosition + cellWidth).round() >= size.width - ? cellWidth - linePadding - 1 - : cellWidth - 1, - (yPosition + cellHeight).round() >= size.height.round() - ? cellHeight - 1 - linePadding - : cellHeight - 1), - _linePainter); + Rect.fromLTWH( + xPosition == 0 ? xPosition + linePadding : xPosition, + yPosition, + (xPosition + cellWidth).round() >= size.width + ? cellWidth - linePadding - 1 + : cellWidth - 1, + (yPosition + cellHeight).round() >= size.height.round() + ? cellHeight - 1 - linePadding + : cellHeight - 1, + ), + _linePainter, + ); } } - void _drawVerticalAndHorizontalLines(Canvas canvas, Size size, - double yPosition, double xPosition, double cellHeight, double cellWidth) { + void _drawVerticalAndHorizontalLines( + Canvas canvas, + Size size, + double yPosition, + double xPosition, + double cellHeight, + double cellWidth, + ) { yPosition = cellHeight; _linePainter.strokeWidth = linePadding; _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor!; xPosition = isRTL ? 0 : weekNumberPanelWidth; final double finalXPosition = isRTL ? size.width - weekNumberPanelWidth : size.width; - canvas.drawLine(Offset(xPosition, linePadding), - Offset(finalXPosition, linePadding), _linePainter); + canvas.drawLine( + Offset(xPosition, linePadding), + Offset(finalXPosition, linePadding), + _linePainter, + ); for (int i = 0; i < rowCount - 1; i++) { canvas.drawLine( - Offset( - isMobilePlatform - ? isRTL - ? 0 - : weekNumberPanelWidth - : 0, - yPosition), - Offset( - isMobilePlatform - ? isRTL - ? size.width - weekNumberPanelWidth - : size.width - : size.width, - yPosition), - _linePainter); + Offset( + isMobilePlatform + ? isRTL + ? 0 + : weekNumberPanelWidth + : 0, + yPosition, + ), + Offset( + isMobilePlatform + ? isRTL + ? size.width - weekNumberPanelWidth + : size.width + : size.width, + yPosition, + ), + _linePainter, + ); yPosition += cellHeight; } - canvas.drawLine(Offset(0, size.height - linePadding), - Offset(size.width, size.height - linePadding), _linePainter); + canvas.drawLine( + Offset(0, size.height - linePadding), + Offset(size.width, size.height - linePadding), + _linePainter, + ); xPosition = weekNumberPanelWidth != 0 && !isRTL ? weekNumberPanelWidth : cellWidth; - canvas.drawLine(const Offset(linePadding, 0), - Offset(linePadding, size.height), _linePainter); + canvas.drawLine( + const Offset(linePadding, 0), + Offset(linePadding, size.height), + _linePainter, + ); final int count = weekNumberPanelWidth == 0 ? 6 : 7; for (int i = 0; i < count; i++) { canvas.drawLine( - Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); + Offset(xPosition, 0), + Offset(xPosition, size.height), + _linePainter, + ); xPosition += cellWidth; } } String _getAccessibilityText(DateTime date, int index) { - final String accessibilityText = - DateFormat('EEE, dd MMMM yyyy').format(date); + final String accessibilityText = DateFormat( + 'EEE, dd MMMM yyyy', + ).format(date); if (_blackoutDatesIndex.contains(index)) { return '$accessibilityText, Blackout date'; } @@ -1176,16 +1372,25 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; + + final RenderBox? child = firstChild; + if (child != null) { + return semanticsBuilder; + } + final double cellWidth = (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; - double left = isRTL - ? size.width - cellWidth - weekNumberPanelWidth - : weekNumberPanelWidth, + double left = + isRTL + ? size.width - cellWidth - weekNumberPanelWidth + : weekNumberPanelWidth, top = 0; final double cellHeight = size.height / rowCount; final bool showTrailingLeadingDates = CalendarViewHelper.isLeadingAndTrailingDatesVisible( - rowCount, showTrailingAndLeadingDates); + rowCount, + showTrailingAndLeadingDates, + ); final int currentMonth = visibleDates[visibleDates.length ~/ 2].month; for (int i = 0; i < visibleDates.length; i++) { final DateTime currentVisibleDate = visibleDates[i]; @@ -1199,9 +1404,12 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { visibleDates[(i ~/ DateTime.daysPerWeek) * DateTime.daysPerWeek]; /// Calculate the row end date based on visible dates index. - final DateTime endDate = addDuration( - startDate, const Duration(days: DateTime.daysPerWeek - 1)) - as DateTime; + final DateTime endDate = + addDuration( + startDate, + const Duration(days: DateTime.daysPerWeek - 1), + ) + as DateTime; /// Used to check the start and end date is current month date or not. final bool isCurrentMonthWeek = @@ -1210,27 +1418,41 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { if (weekNumberPanelWidth != 0 && (showTrailingLeadingDates || (!showTrailingLeadingDates && isCurrentMonthWeek))) { - final int weekNumber = - DateTimeHelper.getWeekNumberOfYear(currentVisibleDate); - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(isRTL ? (size.width - left - cellWidth) : 0, - top, weekNumberPanelWidth, cellHeight), + final int weekNumber = DateTimeHelper.getWeekNumberOfYear( + currentVisibleDate, + ); + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + isRTL ? (size.width - left - cellWidth) : 0, + top, + weekNumberPanelWidth, + cellHeight, + ), properties: SemanticsProperties( label: 'week$weekNumber', textDirection: TextDirection.ltr, - ))); + ), + ), + ); } } if (showTrailingLeadingDates || currentMonth == currentVisibleDate.month) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(isRTL ? size.width - left - cellWidth : left, top, - cellWidth, cellHeight), - properties: SemanticsProperties( - label: _getAccessibilityText(currentVisibleDate, i), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + isRTL ? size.width - left - cellWidth : left, + top, + cellWidth, + cellHeight, + ), + properties: SemanticsProperties( + label: _getAccessibilityText(currentVisibleDate, i), + textDirection: TextDirection.ltr, + ), ), - )); + ); } left += cellWidth; @@ -1246,4 +1468,16 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { @override List Function(Size size) get semanticsBuilder => _getSemanticsBuilder; + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderBox? child = firstChild; + if (child == null) { + return; + } + while (child != null) { + visitor(child); + child = childAfter(child); + } + } } 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 357db6c42..c6b6084aa 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 @@ -19,26 +19,29 @@ class TimelineWidget extends StatefulWidget { /// Constructor to create the timeline widget to holds time slots view for /// timeline views. const TimelineWidget( - this.horizontalLinesCountPerView, - this.visibleDates, - this.timeSlotViewSettings, - this.timeIntervalWidth, - this.cellBorderColor, - this.isRTL, - this.calendarTheme, - this.calendarCellNotifier, - this.scrollController, - this.specialRegion, - this.resourceItemHeight, - this.resourceCollection, - this.textScaleFactor, - this.isMobilePlatform, - this.timeRegionBuilder, - this.width, - this.height, - this.minDate, - this.maxDate, - this.blackoutDates); + this.horizontalLinesCountPerView, + this.visibleDates, + this.timeSlotViewSettings, + this.timeIntervalWidth, + this.cellBorderColor, + this.isRTL, + this.calendarTheme, + this.themeData, + this.calendarCellNotifier, + this.scrollController, + this.specialRegion, + this.resourceItemHeight, + this.resourceCollection, + this.textScaleFactor, + this.isMobilePlatform, + this.timeRegionBuilder, + this.width, + this.height, + this.minDate, + this.maxDate, + this.blackoutDates, { + super.key, + }); /// Defines the total number of time slots needed in the view. final double horizontalLinesCountPerView; @@ -58,6 +61,9 @@ class TimelineWidget extends StatefulWidget { /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data values. + final ThemeData themeData; + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; @@ -128,7 +134,9 @@ class _TimelineWidgetState extends State { widget.height != oldWidget.height || widget.timeRegionBuilder != oldWidget.timeRegionBuilder || !CalendarViewHelper.isCollectionEqual( - widget.specialRegion, oldWidget.specialRegion)) { + widget.specialRegion, + oldWidget.specialRegion, + )) { _updateSpecialRegionDetails(); _children.clear(); } @@ -145,9 +153,13 @@ class _TimelineWidgetState extends State { for (int i = 0; i < count; i++) { final TimeRegionView view = _specialRegionViews[i]; final Widget child = widget.timeRegionBuilder!( - context, - TimeRegionDetails(view.region.data, - widget.visibleDates[view.visibleIndex], view.bound)); + context, + TimeRegionDetails( + view.region.data, + widget.visibleDates[view.visibleIndex], + view.bound, + ), + ); _children.add(RepaintBoundary(child: child)); } @@ -161,6 +173,7 @@ class _TimelineWidgetState extends State { widget.cellBorderColor, widget.isRTL, widget.calendarTheme, + widget.themeData, widget.calendarCellNotifier, widget.scrollController, widget.specialRegion, @@ -187,14 +200,18 @@ class _TimelineWidgetState extends State { return; } - final double minuteHeight = widget.timeIntervalWidth / + final double minuteHeight = + widget.timeIntervalWidth / CalendarViewHelper.getTimeInterval(widget.timeSlotViewSettings); - final DateTime startDate = - AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + final DateTime startDate = AppointmentHelper.convertToStartTime( + widget.visibleDates[0], + ); final DateTime endDate = AppointmentHelper.convertToEndTime( - widget.visibleDates[visibleDatesCount - 1]); + widget.visibleDates[visibleDatesCount - 1], + ); final double viewWidth = widget.width / visibleDatesCount; - final bool isResourceEnabled = widget.resourceCollection != null && + final bool isResourceEnabled = + widget.resourceCollection != null && widget.resourceCollection!.isNotEmpty; for (int i = 0; i < widget.specialRegion!.length; i++) { final CalendarTimeRegion region = widget.specialRegion![i]; @@ -217,15 +234,19 @@ class _TimelineWidgetState extends State { } int startIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, regionStartTime); + widget.visibleDates, + regionStartTime, + ); int endIndex = DateTimeHelper.getVisibleDateIndex( - widget.visibleDates, regionEndTime); + widget.visibleDates, + regionEndTime, + ); double startXPosition = CalendarViewHelper.getTimeToPosition( - Duration( - hours: regionStartTime.hour, minutes: regionStartTime.minute), - widget.timeSlotViewSettings, - minuteHeight); + Duration(hours: regionStartTime.hour, minutes: regionStartTime.minute), + widget.timeSlotViewSettings, + minuteHeight, + ); if (startIndex == -1) { if (startDate.isAfter(regionStartTime)) { /// Set index as 0 when the region start date before the visible @@ -254,15 +275,16 @@ class _TimelineWidgetState extends State { } double endXPosition = CalendarViewHelper.getTimeToPosition( - Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), - widget.timeSlotViewSettings, - minuteHeight); + Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), + widget.timeSlotViewSettings, + minuteHeight, + ); if (endIndex == -1) { /// Find the previous index when the end date as non working date. if (endDate.isAfter(regionEndTime)) { for (int k = visibleDatesCount - 2; k >= 0; k--) { - final DateTime _currentDate = widget.visibleDates[k]; - if (_currentDate.isAfter(regionEndTime)) { + final DateTime currentDate = widget.visibleDates[k]; + if (currentDate.isAfter(regionEndTime)) { continue; } @@ -307,33 +329,56 @@ class _TimelineWidgetState extends State { region.resourceIds!.isNotEmpty) { for (int i = 0; i < region.resourceIds!.length; i++) { final int index = CalendarViewHelper.getResourceIndex( - widget.resourceCollection, region.resourceIds![i]); + widget.resourceCollection, + region.resourceIds![i], + ); topPosition = index * widget.resourceItemHeight; bottomPosition = topPosition + widget.resourceItemHeight; - _updateSpecialRegionRect(region, startPosition, endPosition, - topPosition, bottomPosition, startIndex); + _updateSpecialRegionRect( + region, + startPosition, + endPosition, + topPosition, + bottomPosition, + startIndex, + ); } } else { - _updateSpecialRegionRect(region, startPosition, endPosition, - topPosition, bottomPosition, startIndex); + _updateSpecialRegionRect( + region, + startPosition, + endPosition, + topPosition, + bottomPosition, + startIndex, + ); } } } void _updateSpecialRegionRect( - CalendarTimeRegion region, - double startPosition, - double endPosition, - double topPosition, - double bottomPosition, - int index) { + CalendarTimeRegion region, + double startPosition, + double endPosition, + double topPosition, + double bottomPosition, + int index, + ) { Rect rect; if (widget.isRTL) { rect = Rect.fromLTRB( - endPosition, topPosition, startPosition, bottomPosition); + endPosition, + topPosition, + startPosition, + bottomPosition, + ); } else { rect = Rect.fromLTRB( - startPosition, topPosition, endPosition, bottomPosition); + startPosition, + topPosition, + endPosition, + bottomPosition, + ); } _specialRegionViews.add(TimeRegionView(index, region, rect)); @@ -341,29 +386,30 @@ class _TimelineWidgetState extends State { } class _TimelineRenderWidget extends MultiChildRenderObjectWidget { - _TimelineRenderWidget( - this.horizontalLinesCountPerView, - this.visibleDates, - this.timeSlotViewSettings, - this.timeIntervalWidth, - this.cellBorderColor, - this.isRTL, - this.calendarTheme, - this.calendarCellNotifier, - this.scrollController, - this.specialRegion, - this.resourceItemHeight, - this.resourceCollection, - this.textScaleFactor, - this.isMobilePlatform, - this.width, - this.height, - this.specialRegionBounds, - this.minDate, - this.maxDate, - this.blackoutDates, - {List widgets = const []}) - : super(children: widgets); + const _TimelineRenderWidget( + this.horizontalLinesCountPerView, + this.visibleDates, + this.timeSlotViewSettings, + this.timeIntervalWidth, + this.cellBorderColor, + this.isRTL, + this.calendarTheme, + this.themeData, + this.calendarCellNotifier, + this.scrollController, + this.specialRegion, + this.resourceItemHeight, + this.resourceCollection, + this.textScaleFactor, + this.isMobilePlatform, + this.width, + this.height, + this.specialRegionBounds, + this.minDate, + this.maxDate, + this.blackoutDates, { + List widgets = const [], + }) : super(children: widgets); final double horizontalLinesCountPerView; final List visibleDates; @@ -371,6 +417,7 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { final double timeIntervalWidth; final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final bool isRTL; final ValueNotifier calendarCellNotifier; final ScrollController scrollController; @@ -389,31 +436,35 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { @override _TimelineRenderObject createRenderObject(BuildContext context) { return _TimelineRenderObject( - horizontalLinesCountPerView, - visibleDates, - timeSlotViewSettings, - timeIntervalWidth, - cellBorderColor, - isRTL, - calendarTheme, - calendarCellNotifier, - scrollController, - specialRegion, - resourceItemHeight, - resourceCollection, - textScaleFactor, - isMobilePlatform, - width, - height, - specialRegionBounds, - minDate, - maxDate, - blackoutDates); + horizontalLinesCountPerView, + visibleDates, + timeSlotViewSettings, + timeIntervalWidth, + cellBorderColor, + isRTL, + calendarTheme, + themeData, + calendarCellNotifier, + scrollController, + specialRegion, + resourceItemHeight, + resourceCollection, + textScaleFactor, + isMobilePlatform, + width, + height, + specialRegionBounds, + minDate, + maxDate, + blackoutDates, + ); } @override void updateRenderObject( - BuildContext context, _TimelineRenderObject renderObject) { + BuildContext context, + _TimelineRenderObject renderObject, + ) { renderObject ..horizontalLinesCountPerView = horizontalLinesCountPerView ..visibleDates = visibleDates @@ -422,6 +473,7 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { ..cellBorderColor = cellBorderColor ..isRTL = isRTL ..calendarTheme = calendarTheme + ..themeData = themeData ..calendarCellNotifier = calendarCellNotifier ..scrollController = scrollController ..specialRegion = specialRegion @@ -440,26 +492,28 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { class _TimelineRenderObject extends CustomCalendarRenderObject { _TimelineRenderObject( - this._horizontalLinesCountPerView, - this._visibleDates, - this._timeSlotViewSettings, - this._timeIntervalWidth, - this._cellBorderColor, - this._isRTL, - this._calendarTheme, - this._calendarCellNotifier, - this.scrollController, - this._specialRegion, - this._resourceItemHeight, - this.resourceCollection, - this._textScaleFactor, - this.isMobilePlatform, - this._width, - this._height, - this.specialRegionBounds, - this._minDate, - this._maxDate, - this._blackoutDates); + this._horizontalLinesCountPerView, + this._visibleDates, + this._timeSlotViewSettings, + this._timeIntervalWidth, + this._cellBorderColor, + this._isRTL, + this._calendarTheme, + this._themeData, + this._calendarCellNotifier, + this.scrollController, + this._specialRegion, + this._resourceItemHeight, + this.resourceCollection, + this._textScaleFactor, + this.isMobilePlatform, + this._width, + this._height, + this.specialRegionBounds, + this._minDate, + this._maxDate, + this._blackoutDates, + ); double _horizontalLinesCountPerView; @@ -555,6 +609,18 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { markNeedsPaint(); } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + } + double _resourceItemHeight; double get resourceItemHeight => _resourceItemHeight; @@ -722,8 +788,10 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; if (specialRegion == null || specialRegion!.isEmpty) { return; @@ -736,11 +804,14 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { continue; } final Rect rect = view.bound; - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: rect.height, maxHeight: rect.height, minWidth: rect.width, - maxWidth: rect.width)); + maxWidth: rect.width, + ), + ); child = childAfter(child); } } @@ -753,8 +824,14 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { resourceCollection != null && resourceCollection!.isNotEmpty; final int visibleDatesCount = visibleDates.length; final bool isTimelineMonth = visibleDatesCount > DateTime.daysPerWeek; - _minMaxExceeds(visibleDatesCount, isTimelineMonth, minDate, maxDate, - blackoutDates, context.canvas); + _minMaxExceeds( + visibleDatesCount, + isTimelineMonth, + minDate, + maxDate, + blackoutDates, + context.canvas, + ); if (isNeedDefaultPaint) { _addSpecialRegion(context.canvas, isResourceEnabled, isTimelineMonth); } else { @@ -778,24 +855,40 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { } void _minMaxExceeds( - int visibleDatesCount, - bool isTimelineMonth, - DateTime minDate, - DateTime maxDate, - List? blackoutDates, - Canvas canvas) { + int visibleDatesCount, + bool isTimelineMonth, + DateTime minDate, + DateTime maxDate, + List? blackoutDates, + Canvas canvas, + ) { final DateTime visibleStartDate = visibleDates[0]; final DateTime visibleEndDate = visibleDates[visibleDatesCount - 1]; - final int timeInterval = - CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final int timeInterval = CalendarViewHelper.getTimeInterval( + timeSlotViewSettings, + ); if (isDateWithInDateRange(visibleStartDate, visibleEndDate, minDate)) { - _drawDisabledDate(minDate, false, false, canvas, isTimelineMonth, - timeInterval, visibleDatesCount); + _drawDisabledDate( + minDate, + false, + false, + canvas, + isTimelineMonth, + timeInterval, + visibleDatesCount, + ); } if (isDateWithInDateRange(visibleStartDate, visibleEndDate, maxDate)) { - _drawDisabledDate(maxDate, true, false, canvas, isTimelineMonth, - timeInterval, visibleDatesCount); + _drawDisabledDate( + maxDate, + true, + false, + canvas, + isTimelineMonth, + timeInterval, + visibleDatesCount, + ); } if (blackoutDates == null || !isTimelineMonth) { @@ -806,34 +899,49 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { for (int i = 0; i < count; i++) { final DateTime blackoutDate = blackoutDates[i]; if (isDateWithInDateRange( - visibleStartDate, visibleEndDate, blackoutDate)) { - _drawDisabledDate(blackoutDate, false, true, canvas, isTimelineMonth, - timeInterval, visibleDatesCount); + visibleStartDate, + visibleEndDate, + blackoutDate, + )) { + _drawDisabledDate( + blackoutDate, + false, + true, + canvas, + isTimelineMonth, + timeInterval, + visibleDatesCount, + ); } } } void _drawDisabledDate( - DateTime disabledDate, - bool isMaxDate, - bool isBlackOutDate, - Canvas canvas, - bool isTimelineMonth, - int timeInterval, - int visibleDatesCount) { + DateTime disabledDate, + bool isMaxDate, + bool isBlackOutDate, + Canvas canvas, + bool isTimelineMonth, + int timeInterval, + int visibleDatesCount, + ) { final double minuteHeight = timeIntervalWidth / timeInterval; final double viewWidth = width / visibleDatesCount; - final int dateIndex = - DateTimeHelper.getVisibleDateIndex(visibleDates, disabledDate); + final int dateIndex = DateTimeHelper.getVisibleDateIndex( + visibleDates, + disabledDate, + ); double leftPosition = 0; const double topPosition = 0; - final double xPosition = isTimelineMonth - ? 0 - : CalendarViewHelper.getTimeToPosition( - Duration(hours: disabledDate.hour, minutes: disabledDate.minute), - timeSlotViewSettings, - minuteHeight); + final double xPosition = + isTimelineMonth + ? 0 + : CalendarViewHelper.getTimeToPosition( + Duration(hours: disabledDate.hour, minutes: disabledDate.minute), + timeSlotViewSettings, + minuteHeight, + ); double rightPosition = (dateIndex * viewWidth) + xPosition; if (isMaxDate == true) { leftPosition = @@ -850,15 +958,22 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { leftPosition = width - leftPosition; rightPosition = width - rightPosition; } - rect = - Rect.fromLTRB(leftPosition, topPosition, rightPosition, bottomPosition); + rect = Rect.fromLTRB( + leftPosition, + topPosition, + rightPosition, + bottomPosition, + ); _linePainter.style = PaintingStyle.fill; - _linePainter.color = Colors.grey.withOpacity(0.2); + _linePainter.color = Colors.grey.withValues(alpha: 0.2); canvas.drawRect(rect, _linePainter); } void _drawTimeline( - Canvas canvas, bool isResourceEnabled, int visibleDatesCount) { + Canvas canvas, + bool isResourceEnabled, + int visibleDatesCount, + ) { _linePainter.strokeWidth = 0.5; _linePainter.strokeCap = StrokeCap.round; _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor!; @@ -886,8 +1001,11 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { points.add(Offset(startXPosition, startYPosition)); points.add(Offset(endXPosition, endYPosition)); } else { - canvas.drawLine(Offset(startXPosition, startYPosition), - Offset(endXPosition, endYPosition), _linePainter); + canvas.drawLine( + Offset(startXPosition, startYPosition), + Offset(endXPosition, endYPosition), + _linePainter, + ); } if (isRTL) { @@ -909,8 +1027,11 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { endXPosition = size.width; startYPosition = resourceItemHeight; for (int i = 0; i < resourceCollection!.length; i++) { - canvas.drawLine(Offset(startXPosition, startYPosition), - Offset(endXPosition, startYPosition), _linePainter); + canvas.drawLine( + Offset(startXPosition, startYPosition), + Offset(endXPosition, startYPosition), + _linePainter, + ); startYPosition += resourceItemHeight; } } @@ -921,7 +1042,8 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { } void _addMouseHovering(Canvas canvas, Size size, bool isResourceEnabled) { - double left = (calendarCellNotifier.value!.dx ~/ timeIntervalWidth) * + double left = + (calendarCellNotifier.value!.dx ~/ timeIntervalWidth) * timeIntervalWidth; double top = 0; double height = size.height; @@ -933,11 +1055,12 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { } const double padding = 0.5; top = top == 0 ? padding : top; - height = height == size.height - ? top == padding - ? height - (padding * 2) - : height - padding - : height; + height = + height == size.height + ? top == padding + ? height - (padding * 2) + : height - padding + : height; double width = timeIntervalWidth; double difference = 0; if (isRTL && @@ -954,7 +1077,9 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { _linePainter.style = PaintingStyle.stroke; _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); + _linePainter.color = calendarTheme.selectionBorderColor!.withValues( + alpha: 0.4, + ); left = left == 0 ? left - difference + padding : left - difference; canvas.drawRect(Rect.fromLTWH(left, top, width, height), _linePainter); } @@ -962,7 +1087,10 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { /// Calculate the position for special regions and draw the special regions /// in the timeline views . void _addSpecialRegion( - Canvas canvas, bool isResourceEnabled, bool isTimelineMonth) { + Canvas canvas, + bool isResourceEnabled, + bool isTimelineMonth, + ) { /// 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. @@ -971,23 +1099,28 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { } final TextPainter painter = TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textScaleFactor: textScaleFactor, - textAlign: isRTL ? TextAlign.right : TextAlign.left, - textWidthBasis: TextWidthBasis.longestLine); + textDirection: TextDirection.ltr, + maxLines: 1, + textScaler: TextScaler.linear(textScaleFactor), + textAlign: isRTL ? TextAlign.right : TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + ); _linePainter.style = PaintingStyle.fill; final int count = specialRegionBounds.length; final TextStyle defaultTextStyle = TextStyle( - color: calendarTheme.brightness == Brightness.dark - ? Colors.white54 - : Colors.black45); + color: + themeData.brightness == Brightness.dark + ? Colors.white54 + : Colors.black45, + ); for (int i = 0; i < count; i++) { final TimeRegionView view = specialRegionBounds[i]; final CalendarTimeRegion region = view.region; - _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); - final TextStyle textStyle = region.textStyle ?? defaultTextStyle; + _linePainter.color = region.color ?? Colors.grey.withValues(alpha: 0.2); + final TextStyle textStyle = themeData.textTheme.bodyMedium! + .copyWith(fontSize: 14) + .merge(region.textStyle ?? defaultTextStyle); final Rect rect = view.bound; canvas.drawRect(rect, _linePainter); if ((region.text == null || region.text!.isEmpty) && @@ -1000,11 +1133,12 @@ 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); + painter.layout(maxWidth: rect.width - 4); painter.paint(canvas, Offset(rect.left + 3, rect.top + 3)); } } @@ -1022,39 +1156,55 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { if (isResourceEnabled) { 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 { - semanticsBuilder = - _getAccessibilityDates(size, top, height, semanticsBuilder); + semanticsBuilder = _getAccessibilityDates( + size, + top, + height, + semanticsBuilder, + ); } return semanticsBuilder; } /// Returns the custom painter semantics for visible dates collection. - List _getAccessibilityDates(Size size, double top, - double height, List semanticsBuilder, - [CalendarResource? resource]) { + List _getAccessibilityDates( + Size size, + double top, + double height, + List semanticsBuilder, [ + CalendarResource? resource, + ]) { double left = isRTL ? size.width - timeIntervalWidth : 0; final int startHour = timeSlotViewSettings.startHour.toInt(); final int startHourMinutes = ((timeSlotViewSettings.startHour - startHour) * 60).toInt(); - final int timeInterval = - CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final int timeInterval = CalendarViewHelper.getTimeInterval( + timeSlotViewSettings, + ); for (int j = 0; j < visibleDates.length; j++) { DateTime date = visibleDates[j]; for (int i = 0; i < horizontalLinesCountPerView; i++) { final int minute = (i * timeInterval) + startHourMinutes; date = DateTime(date.year, date.month, date.day, startHour, minute); - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, timeIntervalWidth, height), - properties: SemanticsProperties( - label: _getAccessibilityText(date, resource), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, timeIntervalWidth, height), + properties: SemanticsProperties( + label: _getAccessibilityText(date, resource), + textDirection: TextDirection.ltr, + ), ), - )); + ); if (isRTL) { left -= timeIntervalWidth; } else { @@ -1086,25 +1236,26 @@ 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, - this.viewHeaderStyle, - this.timeSlotViewSettings, - this.viewHeaderHeight, - this.isRTL, - this.todayHighlightColor, - this.todayTextStyle, - this.locale, - this.calendarTheme, - this.minDate, - this.maxDate, - this.viewHeaderNotifier, - this.cellBorderColor, - this.blackoutDates, - this.blackoutDatesTextStyle, - this.textScaleFactor) - : super(repaint: repaintNotifier); + this.visibleDates, + this.timelineViewHeaderScrollController, + this.repaintNotifier, + this.viewHeaderStyle, + this.timeSlotViewSettings, + this.viewHeaderHeight, + this.isRTL, + this.todayHighlightColor, + this.todayTextStyle, + this.locale, + this.calendarTheme, + this.themeData, + this.minDate, + this.maxDate, + this.viewHeaderNotifier, + this.cellBorderColor, + this.blackoutDates, + this.blackoutDatesTextStyle, + this.textScaleFactor, + ) : super(repaint: repaintNotifier); /// Holds the visible dates collection for current timeline view. final List visibleDates; @@ -1135,6 +1286,9 @@ class TimelineViewHeaderView extends CustomPainter { /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + /// Holds the framework theme data value. + final ThemeData themeData; + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; @@ -1176,50 +1330,58 @@ class TimelineViewHeaderView extends CustomPainter { final bool isTimelineMonth = visibleDatesLength > DateTime.daysPerWeek; final DateTime today = DateTime.now(); final double childWidth = size.width / visibleDatesLength; - final int index = isTimelineMonth - ? 0 - : timelineViewHeaderScrollController.offset ~/ childWidth; - _xPosition = !isTimelineMonth - ? timelineViewHeaderScrollController.offset - : isRTL + final int index = + isTimelineMonth + ? 0 + : timelineViewHeaderScrollController.offset ~/ childWidth; + _xPosition = + !isTimelineMonth + ? timelineViewHeaderScrollController.offset + : isRTL ? size.width - childWidth : 0; - TextStyle viewHeaderDayTextStyle = TextStyle( - color: calendarTheme.viewHeaderDayTextStyle!.color, - fontSize: 11, - fontWeight: FontWeight.w400, - fontFamily: 'Roboto'); - TextStyle viewHeaderDateTextStyle = TextStyle( - color: calendarTheme.viewHeaderDateTextStyle!.color, - fontSize: 15, - fontWeight: FontWeight.w400, - fontFamily: 'Roboto'); - - if (viewHeaderDateTextStyle == calendarTheme.viewHeaderDateTextStyle && + final TextStyle defaultThemeViewHeaderDayTextStyle = themeData + .textTheme + .bodySmall! + .copyWith( + color: themeData.colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 11, + ); + final TextStyle defaultThemeViewHeaderDateTextStyle = themeData + .textTheme + .bodyMedium! + .copyWith( + color: themeData.colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 15, + ); + + TextStyle viewHeaderDateStyle = calendarTheme.viewHeaderDateTextStyle!; + TextStyle viewHeaderDayStyle = calendarTheme.viewHeaderDayTextStyle!; + if (viewHeaderDateStyle == defaultThemeViewHeaderDateTextStyle && isTimelineMonth) { - viewHeaderDateTextStyle = viewHeaderDateTextStyle.copyWith( - fontSize: viewHeaderDayTextStyle.fontSize); + viewHeaderDateStyle = viewHeaderDateStyle.copyWith( + fontSize: viewHeaderDayStyle.fontSize, + ); } - final TextStyle viewHeaderDateStyle = - viewHeaderStyle.dateTextStyle ?? viewHeaderDateTextStyle; - if (viewHeaderDayTextStyle == calendarTheme.viewHeaderDayTextStyle && + + if (viewHeaderDayStyle == defaultThemeViewHeaderDayTextStyle && !isTimelineMonth) { - viewHeaderDayTextStyle = viewHeaderDayTextStyle.copyWith( - fontSize: viewHeaderDateStyle.fontSize); + viewHeaderDayStyle = viewHeaderDayStyle.copyWith( + fontSize: viewHeaderDateStyle.fontSize, + ); } - final TextStyle viewHeaderDayStyle = - viewHeaderStyle.dayTextStyle ?? viewHeaderDayTextStyle; - - final TextStyle? blackoutDatesStyle = - blackoutDatesTextStyle ?? calendarTheme.blackoutDatesTextStyle; + final TextStyle? blackoutDatesStyle = calendarTheme.blackoutDatesTextStyle; TextStyle dayTextStyle = viewHeaderDayStyle; TextStyle dateTextStyle = viewHeaderDateStyle; final Color? todayTextColor = CalendarViewHelper.getTodayHighlightTextColor( - todayHighlightColor, todayTextStyle, calendarTheme); + todayHighlightColor, + todayTextStyle, + calendarTheme, + ); if (isTimelineMonth) { _hoverPainter.strokeWidth = 0.5; @@ -1241,46 +1403,66 @@ class TimelineViewHeaderView extends CustomPainter { : timeSlotViewSettings.dayFormat; final String dayText = DateFormat(dayFormat, locale).format(currentDate); - final String dateText = - DateFormat(timeSlotViewSettings.dateFormat).format(currentDate); + final String dateText = DateFormat( + timeSlotViewSettings.dateFormat, + ).format(currentDate); - final bool isBlackoutDate = - CalendarViewHelper.isDateInDateCollection(blackoutDates, currentDate); + final bool isBlackoutDate = CalendarViewHelper.isDateInDateCollection( + blackoutDates, + currentDate, + ); if (isSameDate(currentDate, today)) { - dayTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) - : viewHeaderDayStyle.copyWith(color: todayTextColor); - dateTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: viewHeaderDateStyle.fontSize, color: todayTextColor) - : viewHeaderDateStyle.copyWith(color: todayTextColor); + dayTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, + color: todayTextColor, + ) + : viewHeaderDayStyle.copyWith(color: todayTextColor); + dateTextStyle = + todayTextStyle != null + ? calendarTheme.todayTextStyle!.copyWith( + fontSize: viewHeaderDateStyle.fontSize, + color: todayTextColor, + ) + : viewHeaderDateStyle.copyWith(color: todayTextColor); } else { dateTextStyle = viewHeaderDateStyle; dayTextStyle = viewHeaderDayStyle; } if (isTimelineMonth && isBlackoutDate) { - dateTextStyle = blackoutDatesStyle ?? - dateTextStyle.copyWith(decoration: TextDecoration.lineThrough); - dayTextStyle = blackoutDatesStyle ?? - dayTextStyle.copyWith(decoration: TextDecoration.lineThrough); + if (blackoutDatesStyle != null) { + dateTextStyle = dateTextStyle.merge(blackoutDatesStyle); + dayTextStyle = dayTextStyle.merge(blackoutDatesStyle); + } else { + dateTextStyle = dateTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + ); + dayTextStyle = dayTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + ); + } } if (!isDateWithInDateRange(minDate, maxDate, currentDate)) { dayTextStyle = dayTextStyle.copyWith( - color: dayTextStyle.color != null - ? dayTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light - ? Colors.black26 - : Colors.white38); + color: + dayTextStyle.color != null + ? dayTextStyle.color!.withValues(alpha: 0.38) + : themeData.brightness == Brightness.light + ? Colors.black26 + : Colors.white38, + ); dateTextStyle = dateTextStyle.copyWith( - color: dateTextStyle.color != null - ? dateTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light - ? Colors.black26 - : Colors.white38); + color: + dateTextStyle.color != null + ? dateTextStyle.color!.withValues(alpha: 0.38) + : themeData.brightness == Brightness.light + ? Colors.black26 + : Colors.white38, + ); } final TextSpan dayTextSpan = TextSpan(text: dayText, style: dayTextStyle); @@ -1289,19 +1471,21 @@ class TimelineViewHeaderView extends CustomPainter { _dayTextPainter.textDirection = TextDirection.ltr; _dayTextPainter.textAlign = TextAlign.left; _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dayTextPainter.textScaleFactor = textScaleFactor; + _dayTextPainter.textScaler = TextScaler.linear(textScaleFactor); - final TextSpan dateTextSpan = - TextSpan(text: dateText, style: dateTextStyle); + final TextSpan dateTextSpan = TextSpan( + text: dateText, + style: dateTextStyle, + ); _dateTextPainter.text = dateTextSpan; _dateTextPainter.textDirection = TextDirection.ltr; _dateTextPainter.textAlign = TextAlign.left; _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dateTextPainter.textScaleFactor = textScaleFactor; + _dateTextPainter.textScaler = TextScaler.linear(textScaleFactor); - _dayTextPainter.layout(minWidth: 0, maxWidth: childWidth); - _dateTextPainter.layout(minWidth: 0, maxWidth: childWidth); + _dayTextPainter.layout(maxWidth: childWidth); + _dateTextPainter.layout(maxWidth: childWidth); if (isTimelineMonth) { canvas.save(); _drawTimelineMonthViewHeader(canvas, childWidth, size, isBlackoutDate); @@ -1312,13 +1496,19 @@ class TimelineViewHeaderView extends CustomPainter { } void _drawTimelineTimeSlotsViewHeader( - Canvas canvas, Size size, double childWidth, int index, int i) { + Canvas canvas, + Size size, + double childWidth, + int index, + int i, + ) { if (_dateTextPainter.width + _xPosition + (_padding * 2) + _dayTextPainter.width > (i + 1) * childWidth) { - _xPosition = ((i + 1) * childWidth) - + _xPosition = + ((i + 1) * childWidth) - (_dateTextPainter.width + (_padding * 2) + _dayTextPainter.width); } @@ -1328,27 +1518,38 @@ class TimelineViewHeaderView extends CustomPainter { if (isRTL) { _dateTextPainter.paint( - canvas, - Offset( - size.width - - _xPosition - - (_padding * 2) - - _dayTextPainter.width - - _dateTextPainter.width, - viewHeaderHeight / 2 - _dateTextPainter.height / 2)); + canvas, + Offset( + size.width - _xPosition - _padding - _dateTextPainter.width, + viewHeaderHeight / 2 - _dateTextPainter.height / 2, + ), + ); _dayTextPainter.paint( - canvas, - Offset(size.width - _xPosition - _padding - _dayTextPainter.width, - viewHeaderHeight / 2 - _dayTextPainter.height / 2)); + canvas, + Offset( + size.width - + _xPosition - + (_padding * 2) - + _dayTextPainter.width - + _dateTextPainter.width, + viewHeaderHeight / 2 - _dayTextPainter.height / 2, + ), + ); } else { _dateTextPainter.paint( - canvas, - Offset(_padding + _xPosition, - viewHeaderHeight / 2 - _dateTextPainter.height / 2)); + canvas, + Offset( + _padding + _xPosition, + viewHeaderHeight / 2 - _dateTextPainter.height / 2, + ), + ); _dayTextPainter.paint( - canvas, - Offset(_dateTextPainter.width + _xPosition + (_padding * 2), - viewHeaderHeight / 2 - _dayTextPainter.height / 2)); + canvas, + Offset( + _dateTextPainter.width + _xPosition + (_padding * 2), + viewHeaderHeight / 2 - _dayTextPainter.height / 2, + ), + ); } if (index == i) { @@ -1359,16 +1560,22 @@ class TimelineViewHeaderView extends CustomPainter { } void _drawTimelineMonthViewHeader( - Canvas canvas, double childWidth, Size size, bool isBlackoutDate) { + Canvas canvas, + double childWidth, + Size size, + bool isBlackoutDate, + ) { canvas.clipRect(Rect.fromLTWH(_xPosition, 0, childWidth, size.height)); const double leftPadding = 2; - final double startXPosition = _xPosition + + final double startXPosition = + _xPosition + (childWidth - (_dateTextPainter.width + leftPadding + _dayTextPainter.width)) / 2; - final double startYPosition = (size.height - + final double startYPosition = + (size.height - (_dayTextPainter.height > _dateTextPainter.height ? _dayTextPainter.height : _dateTextPainter.height)) / @@ -1376,11 +1583,26 @@ class TimelineViewHeaderView extends CustomPainter { if (viewHeaderNotifier.value != null && !isBlackoutDate) { _addMouseHovering(canvas, size, childWidth); } - _dateTextPainter.paint(canvas, Offset(startXPosition, startYPosition)); - _dayTextPainter.paint( + if (!isRTL) { + _dateTextPainter.paint(canvas, Offset(startXPosition, startYPosition)); + _dayTextPainter.paint( + canvas, + Offset( + startXPosition + _dateTextPainter.width + leftPadding, + startYPosition, + ), + ); + } else { + _dayTextPainter.paint(canvas, Offset(startXPosition, startYPosition)); + _dateTextPainter.paint( canvas, - Offset(startXPosition + _dateTextPainter.width + leftPadding, - startYPosition)); + Offset( + startXPosition + _dayTextPainter.width + leftPadding, + startYPosition, + ), + ); + } + if (isRTL) { _xPosition -= childWidth; } else { @@ -1390,7 +1612,10 @@ class TimelineViewHeaderView extends CustomPainter { _hoverPainter.color = cellBorderColor ?? calendarTheme.cellBorderColor!; canvas.restore(); canvas.drawLine( - Offset(_xPosition, 0), Offset(_xPosition, size.height), _hoverPainter); + Offset(_xPosition, 0), + Offset(_xPosition, size.height), + _hoverPainter, + ); } void _addMouseHovering(Canvas canvas, Size size, [double? cellWidth]) { @@ -1400,19 +1625,21 @@ class TimelineViewHeaderView extends CustomPainter { timelineViewHeaderScrollController.position.viewportDimension) { difference = timelineViewHeaderScrollController.position.viewportDimension - - size.width; - } - final double leftPosition = isRTL && cellWidth == null - ? size.width - - _xPosition - - (_padding * 2) - - _dayTextPainter.width - - _dateTextPainter.width - - _padding - : _xPosition; - final double rightPosition = isRTL && cellWidth == null - ? size.width - _xPosition - : cellWidth != null + size.width; + } + final double leftPosition = + isRTL && cellWidth == null + ? size.width - + _xPosition - + (_padding * 2) - + _dayTextPainter.width - + _dateTextPainter.width - + _padding + : _xPosition; + final double rightPosition = + isRTL && cellWidth == null + ? size.width - _xPosition + : cellWidth != null ? _xPosition + cellWidth - _padding : _xPosition + _dayTextPainter.width + @@ -1421,13 +1648,14 @@ class TimelineViewHeaderView extends CustomPainter { if (leftPosition + difference <= viewHeaderNotifier.value!.dx && rightPosition + difference >= viewHeaderNotifier.value!.dx && (size.height) - _padding >= viewHeaderNotifier.value!.dy) { - _hoverPainter.color = (calendarTheme.brightness == Brightness.dark + _hoverPainter.color = (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) - .withOpacity(0.04); + .withValues(alpha: 0.04); canvas.drawRect( - Rect.fromLTRB(leftPosition, 0, rightPosition + _padding, size.height), - _hoverPainter); + Rect.fromLTRB(leftPosition, 0, rightPosition + _padding, size.height), + _hoverPainter, + ); } } @@ -1449,7 +1677,9 @@ class TimelineViewHeaderView extends CustomPainter { (isTimelineMonth && (oldWidget.blackoutDatesTextStyle != blackoutDatesTextStyle || !CalendarViewHelper.isDateCollectionEqual( - oldWidget.blackoutDates, blackoutDates))); + oldWidget.blackoutDates, + blackoutDates, + ))); } List _getSemanticsBuilder(Size size) { @@ -1460,13 +1690,15 @@ class TimelineViewHeaderView extends CustomPainter { double left = isRTL ? size.width - cellWidth : 0; const double top = 0; for (int i = 0; i < visibleDatesLength; i++) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: _getAccessibilityText(visibleDates[i]), - textDirection: TextDirection.ltr, + 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 { @@ -1478,7 +1710,8 @@ class TimelineViewHeaderView extends CustomPainter { } String _getAccessibilityText(DateTime date) { - final String textString = DateFormat('EEEEE').format(date) + + final String textString = + DateFormat('EEEEE').format(date) + DateFormat('dd/MMMM/yyyy').format(date); if (!isDateWithInDateRange(minDate, maxDate, date)) { return '$textString, Disabled date'; diff --git a/packages/syncfusion_flutter_calendar/pubspec.yaml b/packages/syncfusion_flutter_calendar/pubspec.yaml index 4e9a00018..4456e39ea 100644 --- a/packages/syncfusion_flutter_calendar/pubspec.yaml +++ b/packages/syncfusion_flutter_calendar/pubspec.yaml @@ -1,23 +1,40 @@ name: 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: 20.1.47 -homepage: https://github.com/syncfusion/flutter-examples +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_calendar + +screenshots: + - description: 'This screenshot shows the agenda view and recurrence view.' + path: screenshots/flutter-calendar-agenda-view-recurrence-view.png + - description: 'This screenshot shows the resource view and month cell customization.' + path: screenshots/flutter-calendar-resource-view-month-cell-customization.png + - description: 'This screenshot shows the load more feature in calendar.' + path: screenshots/flutter-calendar-load-more.gif environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.7.0 dependencies: flutter: sdk: flutter - timezone: 0.8.0 + timezone: ^0.10.0 syncfusion_flutter_core: - path: ../syncfusion_flutter_core - + path: ../syncfusion_flutter_core + syncfusion_flutter_datepicker: - path: ../syncfusion_flutter_datepicker + path: ../syncfusion_flutter_datepicker + + syncfusion_localizations: + path: ../syncfusion_localizations + + intl: '>=0.18.1 <0.21.0' + flutter_localizations: + sdk: flutter + - intl: ">=0.15.0 <0.20.0" flutter: assets: - - packages/timezone/data/2020a.tzf \ No newline at end of file + - packages/timezone/data/latest_all.tzf + - assets/fonts/Roboto-Medium.ttf + - assets/images/ \ No newline at end of file diff --git a/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-agenda-view-recurrence-view.png b/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-agenda-view-recurrence-view.png new file mode 100644 index 000000000..f64ed7466 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-agenda-view-recurrence-view.png differ diff --git a/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-load-more.gif b/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-load-more.gif new file mode 100644 index 000000000..59d74d429 Binary files /dev/null and b/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-load-more.gif differ diff --git a/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-resource-view-month-cell-customization.png b/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-resource-view-month-cell-customization.png new file mode 100644 index 000000000..d0abeaf2b Binary files /dev/null and b/packages/syncfusion_flutter_calendar/screenshots/flutter-calendar-resource-view-month-cell-customization.png differ diff --git a/packages/syncfusion_flutter_charts/CHANGELOG.md b/packages/syncfusion_flutter_charts/CHANGELOG.md index 6827f3b41..362fbd1a3 100644 --- a/packages/syncfusion_flutter_charts/CHANGELOG.md +++ b/packages/syncfusion_flutter_charts/CHANGELOG.md @@ -1,4 +1,704 @@ -## Unreleased +## [30.1.1] - 10/06/2025 + +**General** + +* The compatible version of our Flutter charts widget has been updated to Flutter SDK 3.32.0. + +### Features + +* Enhanced chart legend customization by introducing chart-specific legend item classes (CartesianLegendItem, CircularLegendItem, FunnelLegendItem, and PyramidLegendItem), enabling direct access to series, seriesIndex, and pointIndex. +* \#FR57680 - Added directional zooming to Cartesian charts for intuitive zoom control based on finger gestures. + +## [29.2.4] - 14/05/2025 + +**Bugs** + +* \#BD718806 - The legend now toggles properly even when the [offset](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Legend/offset.html) property is set and chart behaviors are enabled. + +## [29.1.40] - 29/04/2025 + +**Bugs** + +* \#GH2334 - Now, the [HistogramSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/HistogramSeries-class.html) will render properly when a single data point is set to the [dataSource](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartSeries/dataSource.html). +* \#GH2335 - The [onPointDoubleTap](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartSeries/onPointDoubleTap.html) callback is now correctly invoked on double-tap in the chart series. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.37+1] - 09/04/2025 + +**Bugs** + +* \#FB65781 - - Now, the [`labelsExtent`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/labelsExtent.html) properly specifies the space between the axis line and the axis title. + +## [29.1.35] - 01/04/2025 + +**Bugs** + +* \#BD702563 - Now, the [updateDataSource](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartSeriesController/updateDataSource.html) method properly clears data points when using removedDataIndexes in charts. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter charts widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter charts example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter charts example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + + +## [28.2.9] - 04/03/2025 + +**Bugs** + +* \#BD665639 - Now, the [onDataLabelTapped](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCircularChart/onDataLabelTapped.html) callback returns the point index properly for visible data labels. + +## [28.2.7] - 25/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +**Bugs** + +* \#GH1321 - Now the [trendLine](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Trendline-class.html) tooltip will update properly for all [activationMode](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/TooltipBehavior/activationMode.html). + +## [28.2.6] - 18/02/2025 + +**Bugs** + +* \#FR195944 - Now the mouse cursor icon customization is applicable to the entire chart area. +* \#BD688884 - Now for all activation modes, the crosshair will remains visible for a longer duration when the [shouldAlwaysShow](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CrosshairBehavior/shouldAlwaysShow.html) property is set to true. + +## [28.2.5] - 11/02/2025 + +**Bugs** + +* \#BD686856 - Now, the gap is applied between multiple [axes](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/axes.html) in the Cartesian axis. +* \#BD680484 - Now the tooltip appears instantly without any delay when interacting with the chart using [ActivationMode.singleTap](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ActivationMode.html). + +## [28.2.4] - 04/02/2025 + +**Bugs** + +* \#GH2260 - Type casting exception wasn’t thrown when setting double values to the start and end properties of the [plotBands](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/plotBands.html) in the [DatetimeCategoryAxis](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/DateTimeCategoryAxis-class.html). +* \#BD684087 - Now horizontal axis labels update properly when using [AxisLabelIntersectAction.multipleRows](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/AxisLabelIntersectAction.html) with [LabelRotation](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/labelRotation.html). + +## [28.1.40] - 21/01/2025 + +**Bugs** + +* \#FR195717 - The chart zoom in and out is now updated correctly when the chart key is dynamically changed. + +## [28.1.39] - 15/01/2025 + +**Bugs** + +* \#GH2212 - Now the axis labels will render properly when the [edgeLabelPlacement](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/edgeLabelPlacement.html) is set to 'hide'. +* \#GH2172 - Now the [CartesianSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CartesianSeries-class.html) will render properly with multiple series when an empty data source is set. + +## [28.1.38] - 07/01/2025 + +**Bugs** + +* \#GH2218 - Now [trackball](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/TrackballBehavior-class.html) update properly for [DateTimeCategoryAxis](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/DateTimeCategoryAxis-class.html) with multiple series in [CartesianCharts](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart-class.html). + +## [28.1.37] - 31/12/2024 + +**Bugs** + +* \#GH2171 - Now [LabelIntersectAction.none](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/labelIntersectAction.html) update properly for y axis in [CartesianCharts](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart-class.html). + +## [28.1.36] - 24/12/2024 + +**General** + +* The compatible version of our Flutter charts widget has been updated to Flutter SDK 3.27.0. + +**Bugs** + +* \#BD661538 - Resolved a failed assertion exception that occurred when using behaviors in the Chart and attempting to display the behavior before the chart finished loading. +* \#BD665634 - The data label tooltip now updates properly when long text is used in the data label in [CircularCharts](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CircularSeries-class.html). +* \#GH2208 - Resolved null exception when the [isVisibleLegend](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Trendline/isVisibleInLegend.html) is set to false for trendlines in [CartesianCharts](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart-class.html). + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +### Features + +* \#FB58652 - Added support to enable and disable the trackball behavior for specific series in cartesian charts. + +## [27.2.3] - 21/11/2024 + +**Bugs** + +* \#F194931 - Resolved [StackedAreaSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/StackedAreaSeries-class.html) is not rendering properly with negative data points when using multiple stacked series. + +## [27.1.54] - 22/10/2024 + +**Bugs** + +* \#BD640641 - Resolved failed assertion exception when the data source had value as zero in [SparkBarChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkBarChart-class.html). +* \#BD640555 - Now the [PlotBand](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SparkChartPlotBand-class.html) update properly for different start and end values. + +## [27.1.53] - 15/10/2024 + +**Bugs** + +* \#GH2084 - Now the [onDataLabelTapped](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/onDataLabelTapped.html) callback invoke properly when tap inside data label bounds. +* \#GH2091 - Resolved no element state exception when the data source had empty data in the [RadialBarSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/RadialBarSeries-class.html) with Legend. + +## [27.1.51] - 30/09/2024 + +**Bugs** + +* \#BD626485 - Resolved the range error exception when adding data dynamically in the circular series with [legendItemBuilder](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Legend/legendItemBuilder.html). +* \#BD624619 - The trackball builder's position now renders properly in the [groupAllPoints](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/TrackballDisplayMode.html) display mode on transposed charts. +* \#BD630726 - Resolved the range error exception when updating the data source with the [pointShaderMapper](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CircularSeries/pointShaderMapper.html) property in the circular series. + +## [27.1.48] - 18/09/2024 + +**General** + +* \#GH2008, \#GH2013, \#GH2012, \#BD620212, \#BD620214, \#F192976, \#BD621964, \#BD621021, \#BD620826, \#BD619659, \#BD619610, \#BD620964, \#GH2020, \#F194113, \#GH623599, \#GH1907, \#BD626072, \#BD626410, \#GH2041, \#BD626867, \#GH2045 - The compatible version of our Flutter charts widget has been updated to Flutter SDK 3.24.0. + +### Features + +* \#FB31997 - Added support for `borderRadius` in the candle series. +* Added support for customizing `width` and `spacing` of the candle series. +* Added support for individual plot offset customization for the chart axis. +* Added `additionalStart`, `additionalEnd`, `roundStart` and `roundEnd` enums to [`rangePadding`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/rangePadding.html) property to customize the axis range padding at the start or end of the axis. + +**Bugs** + +* The [`RadialBarSeries`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/RadialBarSeries-class.html) animation now updates properly when data points are reset dynamically. +* The range of the category axis now updates correctly when data points are dynamically replaced. +* The tooltip position of the trackball builder has been adjusted to inside within the plot area. + +## [26.2.9] - 13/08/2024 + +**Bugs** + +* Now, the trackball image marker is updated according to the marker visibility mode set to auto. +* Now, the [pointColorMapper](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartSeriesRenderer/pointColorMapper.html) colors are properly updated when the data source is dynamically changed with different lengths. + +## [26.1.35] - 11/06/2024 + +### Features + +Added two new indicators in Cartesian Chart: + +* \#FB15987 - Provided Rate of Change Indicator (ROC) support to technical indicators. +* \#FB15987 - Provided Weighted Moving Average Indicator (WMA) support to technical indicators. + +## [25.2.5] - 09/04/2024 + +### Improvements: + +ZoomPanBehavior: + +Added below methods for pointer and gesture events, along with a paint method, to allow for customization of the zooming and panning. + +* handleEvent +* handleDoubleTap +* handleScaleStart +* handleScaleUpdate +* handleScaleEnd +* handleLongPressStart +* handleLongPressMoveUpdate +* handleLongPressEnd +* onPaint + +## [25.1.35] - 15/03/2024 + +**General** + +* Provided th​e Material 3 themes support. + +### Features + +* Provided touch support for trackball builder. + +### Improvements: + +* Interaction performance has been improved by 2x for crosshair and trackball behavior. + +TrackballBehavior: + +* Improved the trackball label UI with the series name and y value instead of y value for XyDataSeries when setting the tooltip display mode as groupAllPoints. + +* Added below methods for pointer and gesture events, along with a paint method, to allow for customization of the trackball. + +* handleEvent +* handleLongPressStart +* handleLongPressMoveUpdate +* handleLongPressEnd +* handleTapDown +* handleTapUp +* handleDoubleTap +* handlePointerEnter +* handlePointerExit +* onPaint + +CrosshairBehavior: + +Added below methods for pointer and gesture events, along with a paint method, to allow for customization of the crosshair. + +* handleEvent +* handleLongPressStart +* handleLongPressMoveUpdate +* handleLongPressEnd +* handleTapDown +* handleTapUp +* handleDoubleTap +* handlePointerEnter +* handlePointerExit +* onPaint +* drawHorizontalAxisLine +* drawVerticalAxisLine +* drawHorizontalAxisTooltip +* drawVerticalAxisTooltip + +### Bug Fixes + +* Provided legend toggling animation support for [ColumnSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ColumnSeries-class.html) and [BarSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/BarSeries-class.html). +* Provided newly added datapoint animation support for [LineSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/LineSeries-class.html) +* Provided data label intersection support for multiple series. +* \#FB50894 - Now, the stacked series is rendering properly while having multiple series with different x values. + +## [24.1.46] - 17/01/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## [24.1.41] - 12/18/2023 + +### Features + +* Provided `onRendererCreated` callback support in the `ChartAxis`. +* Provided clipping support for annotation feature. +* Provided custom segment rendering support for Circular, Funnel, and Pyramid charts. +* Provided touch support for annotation content. + +## Improvements + +* Flutter charts rendering performance has been improved by 5x. +* Flutter charts memory usage has been reduced by 8x. +* Multi-level labels curly braces UI has been improved in ChartAxis. +* The `labelAlignment` property positioning has been improved in ChartAxis. +* Marker fill color is rendered based on themes. + +### Bug Fixes + +* \#FB48648 - Now, the line series will render correctly while having continuous empty points with drop mode. + +### Breaking changes + +Following breaking changes will occur in Charts. + +## CartesianChart: + +* The `series` property type has been changed to `List` from `List`. +* The `TechnicalIndicators` property has been renamed to `TechnicalIndicator`. + +## Axis: + +* The `visibleMinimum` property has been renamed to `initialVisibleMinimum`. +* The `visibleMaximum` property has been renamed to `initialVisibleMaximum`. +* The `zoomFactor` property has been renamed to `initialZoomFactor`. +* The `zoomPosition` property has been renamed to `initialZoomPosition`. +* The `minorTicksPerInterval` property is removed in both `CategoryAxis` and `DateTimeCategoryAxis`. +* The axis line now renders along with axis labels when the `placeLabelNearAxisLine` property is set to true. + +## Series: + +* The `isVisible` property has been renamed to `initialisVisible`. +* The `drawDataMarker` method arguments have been changed to `drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, Offset point, Size size, DataMarkerType type, [CartesianSeriesRenderer? seriesRenderer,])` in ChartSeries. +* The `calculateEmptyPointValue` method is removed from ChartSeries. +* Data label intersection does not work with multiple series. +* `ErrorBarSeries` has been changed to a single segmented series from multiple segments. + +## Selection: + +* The `initialSelectedIndex` will now select a single segment when the `enableMultiSelection` value is `false`. +* During the point selection, the single segment is now highlighted instead of the entire series in line-type series. + +## Legend: + +* The legend for `FunnelSeries` is now rendered according to the data source order. + +## ChartPointInfo: + +* The `seriesRendererDetails` and `seriesIndex` is removed in ChartPointInfo class. +* The `series` property type has been changed to `dynamic` from `CartesianSeries`. + +## CartesianChartPoint: + +The following properties has been removed from the `CartesianChartPoint` class. + +* yValue +* sortValue +* markerPoint +* markerPoint2 +* isEmpty +* isGap +* isDrop +* isVisible +* pointColorMapper +* dataLabelMapper +* region +* boxRectRegion +* outlierRegion +* outlierRegionPosition +* isIntermediateSum +* isTotalSum +* endValue +* originValue +* maxYValue +* labelRenderEvent +* isTooltipRenderEvent +* openPoint +* closePoint +* centerOpenPoint +* centerClosePoint +* lowPoint +* highPoint +* centerLowPoint +* centerHighPoint +* currentPoint +* startControl +* endControl +* highStartControl +* highEndControl +* lowStartControl +* lowEndControl +* minimumPoint +* maximumPoint +* lowerQuartilePoint +* upperQuartilePoint +* centerMinimumPoint +* centerMaximumPoint +* medianPoint +* centerMedianPoint +* centerMeanPoint +* originValueLeftPoint +* originValueRightPoint +* endValueLeftPoint +* endValueRightPoint +* horizontalPositiveErrorPoint +* horizontalNegativeErrorPoint +* verticalPositiveErrorPoint +* verticalNegativeErrorPoint +* errorBarValues +* outliersPoint +* controlPoint +* controlPointshigh +* controlPointslow +* regions +* cumulativeValue +* trackerRectRegion +* label +* label2 +* label3 +* label4 +* label5 +* outliersLabel +* labelFillRect +* labelFillRect2 +* labelFillRect3 +* labelFillRect4 +* labelFillRect5 +* outliersFillRect +* labelLocation +* labelLocation2 +* labelLocation3 +* labelLocation4 +* labelLocation5 +* outliersLocation +* dataLabelSaturationRegionInside +* dataLabelRegion +* dataLabelRegion2 +* dataLabelRegion3 +* dataLabelRegion4 +* dataLabelRegion5 +* outliersDataLabelRegion +* index +* overallDataPointIndex +* regionData +* visiblePointIndex + +## ChartPoint: + +The following properties has been removed from the `ChartPoint` class. Instead of this, you can get the values of these properties from the corresponding segment class. + +* degree +* startAngle +* endAngle +* midAngle +* center +* text +* fill +* strokeColor +* sortValue +* strokeWidth +* innerRadius +* outerRadius +* isExplode +* isShadow +* isEmpty +* isVisible +* isSelected +* dataLabelPosition +* renderPosition +* labelRect +* dataLabelSize +* saturationRegionOutside +* yRatio +* heightRatio +* radius +* pointColor +* trimmedText +* overflowTrimmedText +* isTooltipRenderEvent +* labelRenderEvent +* index +* shader + +## Behavior changes + +* Now, the axis line and labels are rendered above the series while using `crossesAt` feature in ChartAxis. +* The plot band `opacity` property is now applied to fill, stroke and text color. +* The series `opacity` property is now applied to the both series fill and stroke color. +* The tooltip `opacity` property is now applied to the fill, stroke, text and marker color. + +## Generic type changes + +The following classes are marked as a generic type. +* All chart series renderers +* All chart segments +* ChartSeriesController +* CircularSeriesController +* FunnelSeriesController +* PyramidSeriesController +* LegendItemBuilder +* ChartWidgetBuilder +* ChartDataLabelTemplateBuilder + +### Known issues + +* The bar type series legend toggling animation is not working. +* Newly added data point animation is not working. +* The axis size changes immediately when the axis range transitions from single digits to multiple digits, such as 2 (10) or 3 (100) digits. + +## [23.1.39] - 10/04/2023 + +**Bugs** + +* #FB46807 - The tooltip showByIndex public method has been enhanced to work based on a given seriesIndex, even when multiple series are overlapped. +* #FB46888 - The trackball hide method will work properly for the trackball tooltip builder. +* #FB46698 - Fixed the issue where the trackball tooltip markers overlapped the text when the tooltip text had different sizes. + + +## [22.2.9] - 08/15/2023 + +**Bugs** + +* #FB45898 - Resolved the axis range rendering issue while updating the multiple segments using the addedDataIndexes property dynamically. + +## [22.2.8] - 08/08/2023 + +**Bugs** + +* #FB45592 - Resolved the typecast exception when rendering the tooltip with trimmed text in the data label. + +## [22.2.7] - 08/02/2023 + +**Bugs** + +* #FB45592 - Resolved the typecast exception when rendering the tooltip with trimmed text in the data label. + +## [22.2.7] - 08/02/2023 + +**Bugs** + +* #FB45330 - Now, the trendline tooltip will work properly with the column series. +* #FB45141 - Now, the legend toggling will work properly when having the same name for multiple series. + +## [22.1.39] - 07/18/2023 + +**Bugs** + +* #FB44995 - Now, the selected index state gets maintained when switching applications in the chart. +* #FB45020 - Resolved the range error exception when zooming the chart with the [onMarkerRender](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/onMarkerRender.html) callback without enabling [markerSettings](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CartesianSeries/markerSettings.html) in the scatter series. + +## [22.1.38] - 07/11/2023 + +**Bugs** +* #FB44755 - Resolved the Null check exception when having a crosshair empty data source. + +## [22.1.37] - 07/04/2023 + +**Bugs** + +* #FB44618 - Now, the BorderDrawMode property will work properly in the StackedAreaSeries. +* #FB44657 - Resolved getting NaN exception when setting a maximum property as zero in the x and y-axis. + +## [22.1.36] - 06/28/2023 + +**Bugs** +* #FB44233 - Now, the leftmost points will come into view when performing panning to the left most of the chart. +* #FB44517 - Resolved the range error exception in the FastLineSeries with enabled trackball in the chart. + +## [21.2.10] - 06/13/2023 + +**Bugs** +* #FB44070 - Now, the doughnut series is rendering properly when having two data points with large differences. +* #FB44112 - Resolved range error exception when displaying trackball for the zoomed candle series. + +## [21.2.8] - 05/30/2023 + +**Bugs** +* #FB43703 - Resolved the selection not working issue when the selection disabled series placed above the selection enabled series. + +## [21.2.5] - 05/16/2023 + +**Bugs** +* #FB42814 - Now, the auto-scaling will work for negative intervals when the range padding is normal and auto. + +## [21.2.4] - 05/09/2023 + +**Bugs** +* #FB43916 - Now, the tooltip marker color will be displayed corresponding to the series color when using the tooltip shared mode. + +## [21.1.41] - 04/18/2023 + +**Bugs** +* #FB42529 - Now, the plotband will be updated along with the axis when updating the data using the updateDataSource method. + +## [21.1.38] - 04/04/2023 + +* #FB42369 - Resolved the null check exception when updating data using the updateDataSource method with an empty data source initially in the CategoryAxis. + +## [21.1.37] - 03/29/2023 + +**Bugs** +* #FB42269 - Resolved the null exception when having two data points only in the spline series with cardinal spline type. +* #FB41915 - Now, the null exception will no longer be thrown in the plotband, and the horizontalTextPadding property will work properly. + +## [21.1.35] - 03/23/2023 + +**Bugs** +* #FB40960 - Fixed an issue where the data label template was not updating properly when changing the visible range dynamically. +* #FB41822 - Fixed an issue of the candle series width decreasing when adding the non-rectangular series. + +## [20.4.54] - 03/15/2023 + +**Bugs** +* #FB41625 - Resolved null check exception that occurred when changing the data source of a circular series with a toggled legend. + +## [20.4.53] - 03/07/2023 + +**Bugs** +* #FB40694 - Resolved the range error exception in the FastLineSeries with the floatAllPoints trackball display mode. + +## [20.4.50] - 02/14/2023 + +**Bugs** +* #FB40202 - The builder data labels are not positioned when using multiple series in the SfCartesianChart has been resolved. + +## [20.4.43] - 01/10/2023 + +**Bug** +* #FB39500 - The issue with zooming and panning on the chart not working properly on the macOS trackpad has been resolved. + +## [20.3.61] - 12/13/2022 +**Bugs** + +* #FB37705 - Now, the circular data label builder will render properly with connector lines. + +## [20.3.60] - 12/06/2022 + +**Bugs** +* #FB39502 - Now, the series is rendered with both the [primaryXAxis](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/primaryXAxis.html) and [primaryYAxis](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/primaryYAxis.html) as [LogarithmicAxis](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/LogarithmicAxis-class.html). +* #FB39157 - Now, the [onPointTap](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CartesianSeries/onPointTap.html) event returns the respective data point index while having the nearest x values. + +## [20.3.59] - 11/29/2022 + +**Bugs** +* #FB37704 - Now, the fast line series will render the line when the data points are outside of the range controller's. + +## [20.3.57] - 11/15/2022 + +**Bugs** +* #FB38884 - The null exception will no longer be thrown in the chart while dynamically enabling the [isVisibleInLegend](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CartesianSeries/isVisibleInLegend.html) property for the series. + +## [20.3.56] - 11/08/2022 + +**Bugs** +* #FB37724 - Now, the Null check operator exception will no longer be thrown when refreshing the chart in the [onLegendTapped](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/onLegendTapped.html) callback. +* #FB38586 - Now, the plot band will render properly for the [LogarithmicAxis](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/LogarithmicAxis-class.html). + +## [20.3.52] - 10/26/2022 + +**Bugs** +* #FB38235 - The Null exception will no longer be thrown in trackball when using the RangeAreaSeries and AreaSeries with different data sources. + +## [20.3.50] - 10/18/2022 + +**Bugs** +* #FB37724 - Now, the series visibility gets toggled properly when setting the series visibility using the [onLegendTapped](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/onLegendTapped.html) callback. +* #FB37885 - Now, the candle series gets rendered properly when it starts updating data dynamically with a single data point. +* #FB38196 - Now, there is no exception that occurs while calling the trackball public method [show](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/TrackballBehavior/show.html) when there is no visible series in the chart. +* #FB38080 - Now, the trackball tooltip with builder will activate properly when using the [show](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/TrackballBehavior/show.html) public method if the trackball is already moved in the chart using user interaction. +* #FB38046 - Now, the doughnut series with stroke border renders properly with CornerStyle as both sided curve. +* #FB37274 - Now, Infinity or NaN toInt exception will no longer be thrown when rendering Bollinger band with mapping more number identical close point values. + +## [20.3.49] - 10/11/2022 + +**Bugs** +* #FB36732 - Now, while performing zooming and panning, the hidden series won't become visible. + +## [20.3.47] - 09/29/2022 + +**Bugs** +* #FB37559 - Now, the `NoSuchMethodError` exception will not be thrown when a tooltip is activated using the `showByIndex` method in circular charts. +* #FB37311 - The FastLineSeries renders when all the y-values are the same. +* #FB36534 – Now, the pie series will explode immediately. +* #FB37039 - When panning the X-axis with `zoomMode` as `ZoomMode.X`, the Y-axis range does not change. +* #I392604, G821 - Now, the candle series width will not decrease when having the MACD indicator. +* #I373783 - Now, the hollow candle will not throw an exception when setting the visible range. + +## [20.2.44] - 08/16/2022 + +**Bugs** +* Now, the 'size should not be infinite' assertion failed exception will not be thrown when having the SVG image as an annotation at the start and end point in the chart. + +## [20.2.43] - 08/08/2022 + +**Bugs** +* Now, the startup animation will work for circular, funnel, and pyramid charts when having the [legendItemBuilder](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Legend/legendItemBuilder.html) in legend. +* Now, the tooltip will display the correct value when using the logarithmic axis. + +## [20.2.36] - 07/01/2022 + +**Features** +* Provided the support to customize the date-time axis labels based on the interval type. + +* Now, the scrollbar can be displayed always or on-demand when the legend [overflowMode](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Legend/overflowMode.html) is set to `scroll`. + +* Provided the support to trim the intersecting axis labels when the [labelIntersectAction](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartAxis/labelIntersectAction.html) property is set to `AxisLabelIntersectAction.trim`. + +## [20.1.47] - 04/04/2022 + +**Bugs** +* The [onAxisLabelTapped](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/onAxisLabelTapped.html) callback works properly with the rotated axis labels. **Features** * Provided support to display the trackball tooltip smartly when there is no space horizontally. diff --git a/packages/syncfusion_flutter_charts/LICENSE b/packages/syncfusion_flutter_charts/LICENSE index 53cd02262..542076c7f 100644 --- a/packages/syncfusion_flutter_charts/LICENSE +++ b/packages/syncfusion_flutter_charts/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Chart 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. +Syncfusion® Flutter Chart 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_charts/README.md b/packages/syncfusion_flutter_charts/README.md index 5e1745d98..5612f58f0 100644 --- a/packages/syncfusion_flutter_charts/README.md +++ b/packages/syncfusion_flutter_charts/README.md @@ -19,7 +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?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) * [SfSparkWinLossChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkWinLossChart-class.html?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) -**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?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) 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?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) file. ## Table of contents - [Chart features](#chart-features) @@ -36,7 +36,7 @@ This [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_cha - [Bind spark charts data source](#bind-spark-charts-data-source) - [Add spark charts elements](#add-spark-charts-elements) - [Support and Feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Chart features @@ -84,22 +84,19 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about Syncfusion Flutter charts: +Take a look at the following to learn more about Syncfusion® Flutter charts: -* [Syncfusion Flutter Charts product page](https://www.syncfusion.com/flutter-widgets/flutter-charts?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) +* [Syncfusion® Flutter Charts product page](https://www.syncfusion.com/flutter-widgets/flutter-charts?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) * [Video tutorials](https://www.syncfusion.com/tutorial-videos/flutter/charts?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) * [Knowledge base](https://www.syncfusion.com/kb?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) * [Download Free Trial](https://www.syncfusion.com/downloads?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) @@ -320,14 +317,14 @@ Widget build(BuildContext context) { ## Support and Feedback -* For any other queries, reach our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) or post the queries through the [Community forums](https://www.syncfusion.com/forums?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) and submit a feature request or a bug through our [Feedback portal](https://www.syncfusion.com/feedback/flutter?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) or post the queries through the [Community forums](https://www.syncfusion.com/forums?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) and submit a feature request or a bug through our [Feedback portal](https://www.syncfusion.com/feedback/flutter?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev). * To renew the subscription, click [renew](https://www.syncfusion.com/sales/products?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) or contact our sales team at salessupport@syncfusion.com | Toll Free: 1-888-9 DOTNET. -## About Syncfusion Flutter Widgets -The Syncfusion's [Flutter library](https://www.syncfusion.com/flutter-widgets?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) contains an ever-growing set of UI widgets for creating cross-platform native mobile apps for Android, iOS, Web, macOS and Linux platforms. In addition to Charts, we provide popular Flutter Widgets such as [DataGrid](https://www.syncfusion.com/flutter-widgets/flutter-datagrid?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Calendar](https://www.syncfusion.com/flutter-widgets/flutter-calendar?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Radial Gauge](https://www.syncfusion.com/flutter-widgets/flutter-radial-gauge?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [PDF Viewer](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), and [PDF Library](https://www.syncfusion.com/flutter-widgets/pdf-library?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev). +## About Syncfusion® Flutter Widgets +The Syncfusion® [Flutter library](https://www.syncfusion.com/flutter-widgets?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) contains an ever-growing set of UI widgets for creating cross-platform native mobile apps for Android, iOS, Web, macOS and Linux platforms. In addition to Charts, we provide popular Flutter Widgets such as [DataGrid](https://www.syncfusion.com/flutter-widgets/flutter-datagrid?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Calendar](https://www.syncfusion.com/flutter-widgets/flutter-calendar?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Radial Gauge](https://www.syncfusion.com/flutter-widgets/flutter-radial-gauge?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [PDF Viewer](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), and [PDF Library](https://www.syncfusion.com/flutter-widgets/pdf-library?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev). -## About Syncfusion +## 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. +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?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [ASP.NET MVC](https://www.syncfusion.com/aspnet-mvc-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [ASP.NET WebForms](https://www.syncfusion.com/jquery/aspnet-web-forms-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [JavaScript](https://www.syncfusion.com/javascript-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Angular](https://www.syncfusion.com/angular-ui-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [React](https://www.syncfusion.com/react-ui-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Vue](https://www.syncfusion.com/vue-ui-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), and [Blazor](https://www.syncfusion.com/blazor-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Flutter](https://www.syncfusion.com/flutter-widgets?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [UWP](https://www.syncfusion.com/uwp-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), and [JavaScript](https://www.syncfusion.com/javascript-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev)), and desktop development ([WinForms](https://www.syncfusion.com/winforms-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WPF](https://www.syncfusion.com/wpf-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [UWP](https://www.syncfusion.com/uwp-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev) and [WinUI](https://www.syncfusion.com/winui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev)). 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?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [ASP.NET MVC](https://www.syncfusion.com/aspnet-mvc-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [ASP.NET WebForms](https://www.syncfusion.com/jquery/aspnet-web-forms-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [JavaScript](https://www.syncfusion.com/javascript-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Angular](https://www.syncfusion.com/angular-ui-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [React](https://www.syncfusion.com/react-ui-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Vue](https://www.syncfusion.com/vue-ui-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev)), mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Xamarin](https://www.syncfusion.com/xamarin-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Flutter](https://www.syncfusion.com/flutter-widgets?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [UWP](https://www.syncfusion.com/uwp-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), and [JavaScript](https://www.syncfusion.com/javascript-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev)), and desktop development ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WPF](https://www.syncfusion.com/wpf-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [UWP](https://www.syncfusion.com/uwp-ui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), and [WinUI](https://www.syncfusion.com/winui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev)). 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 diff --git a/packages/syncfusion_flutter_charts/analysis_options.yaml b/packages/syncfusion_flutter_charts/analysis_options.yaml index 75c5e05b8..35a1c2c48 100644 --- a/packages/syncfusion_flutter_charts/analysis_options.yaml +++ b/packages/syncfusion_flutter_charts/analysis_options.yaml @@ -1,17 +1,5 @@ include: package:syncfusion_flutter_core/analysis_options.yaml -analyzer: - errors: - include_file_not_found: ignore - lines_longer_than_80_chars: ignore - avoid_as: ignore - cast_nullable_to_non_nullable: ignore - implicit_dynamic_parameter: ignore - field_initializer_not_assignable: ignore - invalid_assignment: ignore - unnecessary_null_comparison: ignore - unnecessary_nullable_for_final_variable_declarations: ignore - must_be_immutable: ignore - avoid_equals_and_hash_code_on_mutable_classes: ignore - unnecessary_null_checks: ignore - noop_primitive_operations: ignore \ No newline at end of file +analyzer: + language: + strict-raw-types: false \ No newline at end of file diff --git a/packages/syncfusion_flutter_charts/assets/fonts/Roboto-Medium.ttf b/packages/syncfusion_flutter_charts/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 000000000..f714a514d Binary files /dev/null and b/packages/syncfusion_flutter_charts/assets/fonts/Roboto-Medium.ttf differ diff --git a/packages/syncfusion_flutter_charts/assets/fonts/Times-New-Roman.ttf b/packages/syncfusion_flutter_charts/assets/fonts/Times-New-Roman.ttf new file mode 100644 index 000000000..5cdac9ca5 Binary files /dev/null and b/packages/syncfusion_flutter_charts/assets/fonts/Times-New-Roman.ttf differ diff --git a/packages/syncfusion_flutter_charts/example/README.md b/packages/syncfusion_flutter_charts/example/README.md new file mode 100644 index 000000000..d6ac228dd --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/README.md @@ -0,0 +1,4 @@ +# charts_example + +How to render Syncfusion Flutter Charts widget? + diff --git a/packages/syncfusion_flutter_charts/example/android/.gitignore b/packages/syncfusion_flutter_charts/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_charts/example/android/.gitignore +++ b/packages/syncfusion_flutter_charts/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_charts/example/android/app/build.gradle b/packages/syncfusion_flutter_charts/example/android/app/build.gradle index 5fe3c929f..beb1a9fa2 100644 --- a/packages/syncfusion_flutter_charts/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_charts/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.charts_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.charts_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_charts/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_charts/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_charts/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_charts/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_charts/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_charts/example/android/app/src/main/AndroidManifest.xml index 3f41384db..3ec4510ae 100644 --- a/packages/syncfusion_flutter_charts/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_charts/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_charts/example/android/app/src/main/kotlin/com/example/charts_example/MainActivity.kt b/packages/syncfusion_flutter_charts/example/android/app/src/main/kotlin/com/example/charts_example/MainActivity.kt new file mode 100644 index 000000000..713a4bb67 --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/android/app/src/main/kotlin/com/example/charts_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.charts_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_charts/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_charts/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_charts/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_charts/example/android/build.gradle b/packages/syncfusion_flutter_charts/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_charts/example/android/build.gradle +++ b/packages/syncfusion_flutter_charts/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_charts/example/android/gradle.properties b/packages/syncfusion_flutter_charts/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_charts/example/android/gradle.properties +++ b/packages/syncfusion_flutter_charts/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_charts/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_charts/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_charts/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_charts/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_charts/example/android/settings.gradle b/packages/syncfusion_flutter_charts/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_charts/example/android/settings.gradle +++ b/packages/syncfusion_flutter_charts/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_charts/example/ios/.gitignore b/packages/syncfusion_flutter_charts/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_charts/example/ios/.gitignore +++ b/packages/syncfusion_flutter_charts/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_charts/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_charts/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_charts/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_charts/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_charts/example/ios/Flutter/Debug.xcconfig index e8efba114..592ceee85 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_charts/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_charts/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_charts/example/ios/Flutter/Release.xcconfig index 399e9340e..592ceee85 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_charts/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_charts/example/ios/Podfile b/packages/syncfusion_flutter_charts/example/ios/Podfile new file mode 100644 index 000000000..5a69b8902 --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/ios/Podfile @@ -0,0 +1,84 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.pbxproj index c40d8bb4d..d12aed4c1 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,19 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,14 +43,15 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; 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; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 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 = ""; }; @@ -51,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -74,7 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -82,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -89,30 +111,38 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -129,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -139,11 +172,17 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; }; }; }; @@ -156,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -182,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -196,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -211,18 +264,33 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -245,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -277,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -285,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -299,29 +368,74 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -351,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -365,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -375,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -407,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -415,10 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -429,20 +547,19 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample; 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; @@ -452,20 +569,18 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -473,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -494,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_charts/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_charts/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_charts/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_charts/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_charts/example/ios/Runner/Info.plist index 3eb77f364..e9e639483 100644 --- a/packages/syncfusion_flutter_charts/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_charts/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Charts Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_charts/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_charts/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_charts/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_charts/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_charts/example/lib/main.dart b/packages/syncfusion_flutter_charts/example/lib/main.dart index 9cec76cdd..5e903190b 100644 --- a/packages/syncfusion_flutter_charts/example/lib/main.dart +++ b/packages/syncfusion_flutter_charts/example/lib/main.dart @@ -10,7 +10,7 @@ class _ChartApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData(primarySwatch: Colors.blue), + theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: false), home: _MyHomePage(), ); } @@ -30,33 +30,34 @@ class _MyHomePageState extends State<_MyHomePage> { _SalesData('Feb', 28), _SalesData('Mar', 34), _SalesData('Apr', 32), - _SalesData('May', 40) + _SalesData('May', 40), ]; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Syncfusion Flutter chart'), - ), - body: Column(children: [ + appBar: AppBar(title: const Text('Syncfusion Flutter chart')), + body: Column( + children: [ //Initialize the chart widget SfCartesianChart( - primaryXAxis: CategoryAxis(), - // Chart title - title: ChartTitle(text: 'Half yearly sales analysis'), - // Enable legend - legend: Legend(isVisible: true), - // Enable tooltip - tooltipBehavior: TooltipBehavior(enable: true), - series: >[ - LineSeries<_SalesData, String>( - dataSource: data, - xValueMapper: (_SalesData sales, _) => sales.year, - yValueMapper: (_SalesData sales, _) => sales.sales, - name: 'Sales', - // Enable data label - dataLabelSettings: DataLabelSettings(isVisible: true)) - ]), + primaryXAxis: CategoryAxis(), + // Chart title + title: ChartTitle(text: 'Half yearly sales analysis'), + // Enable legend + legend: Legend(isVisible: true), + // Enable tooltip + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + LineSeries<_SalesData, String>( + dataSource: data, + xValueMapper: (_SalesData sales, _) => sales.year, + yValueMapper: (_SalesData sales, _) => sales.sales, + name: 'Sales', + // Enable data label + dataLabelSettings: DataLabelSettings(isVisible: true), + ), + ], + ), Expanded( child: Padding( padding: const EdgeInsets.all(8.0), @@ -64,10 +65,12 @@ class _MyHomePageState extends State<_MyHomePage> { child: SfSparkLineChart.custom( //Enable the trackball trackball: SparkChartTrackball( - activationMode: SparkChartActivationMode.tap), + activationMode: SparkChartActivationMode.tap, + ), //Enable marker marker: SparkChartMarker( - displayMode: SparkChartMarkerDisplayMode.all), + displayMode: SparkChartMarkerDisplayMode.all, + ), //Enable data label labelDisplayMode: SparkChartLabelDisplayMode.all, xValueMapper: (int index) => data[index].year, @@ -75,8 +78,10 @@ class _MyHomePageState extends State<_MyHomePage> { dataCount: 5, ), ), - ) - ])); + ), + ], + ), + ); } } diff --git a/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_charts/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_charts/example/macos/.gitignore b/packages/syncfusion_flutter_charts/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_charts/example/macos/.gitignore +++ b/packages/syncfusion_flutter_charts/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..47d630b3b 100644 --- a/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* charts_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "charts_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* charts_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* charts_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/charts_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/charts_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/charts_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/charts_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/charts_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/charts_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..7f7fea010 100644 --- a/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_charts/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_charts/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_charts/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_charts/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_charts/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_charts/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_charts/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_charts/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_charts/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..360ed869e 100644 --- a/packages/syncfusion_flutter_charts/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_charts/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = charts_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.chartsExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_charts/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_charts/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_charts/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_charts/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_charts/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_charts/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_charts/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_charts/example/pubspec.yaml b/packages/syncfusion_flutter_charts/example/pubspec.yaml index 3679c0b64..180874960 100644 --- a/packages/syncfusion_flutter_charts/example/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/example/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.0.0+1 publish_to: 'none' environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: @@ -13,6 +13,9 @@ dependencies: path: ../ cupertino_icons: ^1.0.2 -flutter: +dev_dependencies: + flutter_test: + sdk: flutter +flutter: uses-material-design: true diff --git a/packages/syncfusion_flutter_charts/example/web/index.html b/packages/syncfusion_flutter_charts/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_charts/example/web/index.html +++ b/packages/syncfusion_flutter_charts/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_charts/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_charts/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_charts/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_charts/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_charts/images/Document.png b/packages/syncfusion_flutter_charts/images/Document.png new file mode 100644 index 000000000..b4e89b92d Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/Document.png differ diff --git a/packages/syncfusion_flutter_charts/images/People_Circle12.png b/packages/syncfusion_flutter_charts/images/People_Circle12.png new file mode 100644 index 000000000..bafa37946 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/People_Circle12.png differ diff --git a/packages/syncfusion_flutter_charts/images/People_Circle14.png b/packages/syncfusion_flutter_charts/images/People_Circle14.png new file mode 100644 index 000000000..0ed7efe76 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/People_Circle14.png differ diff --git a/packages/syncfusion_flutter_charts/images/People_Circle16.png b/packages/syncfusion_flutter_charts/images/People_Circle16.png new file mode 100644 index 000000000..37d86ad47 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/People_Circle16.png differ diff --git a/packages/syncfusion_flutter_charts/images/People_Circle3.png b/packages/syncfusion_flutter_charts/images/People_Circle3.png new file mode 100644 index 000000000..34d3c6109 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/People_Circle3.png differ diff --git a/packages/syncfusion_flutter_charts/images/SB.png b/packages/syncfusion_flutter_charts/images/SB.png new file mode 100644 index 000000000..ac042e938 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/SB.png differ diff --git a/packages/syncfusion_flutter_charts/images/apple.png b/packages/syncfusion_flutter_charts/images/apple.png new file mode 100644 index 000000000..7c67b1985 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/apple.png differ diff --git a/packages/syncfusion_flutter_charts/images/axes.png b/packages/syncfusion_flutter_charts/images/axes.png new file mode 100644 index 000000000..2b98ca55f Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/axes.png differ diff --git a/packages/syncfusion_flutter_charts/images/axisfeatures.png b/packages/syncfusion_flutter_charts/images/axisfeatures.png new file mode 100644 index 000000000..98d877c67 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/axisfeatures.png differ diff --git a/packages/syncfusion_flutter_charts/images/bike.png b/packages/syncfusion_flutter_charts/images/bike.png new file mode 100644 index 000000000..2165945de Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/bike.png differ diff --git a/packages/syncfusion_flutter_charts/images/bike_legend.png b/packages/syncfusion_flutter_charts/images/bike_legend.png new file mode 100644 index 000000000..75befc73e Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/bike_legend.png differ diff --git a/packages/syncfusion_flutter_charts/images/book.png b/packages/syncfusion_flutter_charts/images/book.png new file mode 100644 index 000000000..a92ccc0a5 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/book.png differ diff --git a/packages/syncfusion_flutter_charts/images/bus.png b/packages/syncfusion_flutter_charts/images/bus.png new file mode 100644 index 000000000..a7796d619 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/bus.png differ diff --git a/packages/syncfusion_flutter_charts/images/car.png b/packages/syncfusion_flutter_charts/images/car.png new file mode 100644 index 000000000..f0f3fd420 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/car.png differ diff --git a/packages/syncfusion_flutter_charts/images/car_legend.png b/packages/syncfusion_flutter_charts/images/car_legend.png new file mode 100644 index 000000000..eab6cafa6 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/car_legend.png differ diff --git a/packages/syncfusion_flutter_charts/images/cartesian.png b/packages/syncfusion_flutter_charts/images/cartesian.png new file mode 100644 index 000000000..7de6f7627 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/cartesian.png differ diff --git a/packages/syncfusion_flutter_charts/images/cloudy.png b/packages/syncfusion_flutter_charts/images/cloudy.png new file mode 100644 index 000000000..5e033713b Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/cloudy.png differ diff --git a/packages/syncfusion_flutter_charts/images/code.png b/packages/syncfusion_flutter_charts/images/code.png new file mode 100644 index 000000000..b9a3169a8 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/code.png differ diff --git a/packages/syncfusion_flutter_charts/images/code1.png b/packages/syncfusion_flutter_charts/images/code1.png new file mode 100644 index 000000000..bb6185050 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/code1.png differ diff --git a/packages/syncfusion_flutter_charts/images/code2.png b/packages/syncfusion_flutter_charts/images/code2.png new file mode 100644 index 000000000..186d333b9 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/code2.png differ diff --git a/packages/syncfusion_flutter_charts/images/cycle.webp b/packages/syncfusion_flutter_charts/images/cycle.webp new file mode 100644 index 000000000..209b3f344 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/cycle.webp differ diff --git a/packages/syncfusion_flutter_charts/images/cycle_legend.png b/packages/syncfusion_flutter_charts/images/cycle_legend.png new file mode 100644 index 000000000..eccde60ca Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/cycle_legend.png differ diff --git a/packages/syncfusion_flutter_charts/images/dashline.png b/packages/syncfusion_flutter_charts/images/dashline.png new file mode 100644 index 000000000..b654fee33 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/dashline.png differ diff --git a/packages/syncfusion_flutter_charts/images/documentation.png b/packages/syncfusion_flutter_charts/images/documentation.png new file mode 100644 index 000000000..eedf97f1e Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/documentation.png differ diff --git a/packages/syncfusion_flutter_charts/images/flight.png b/packages/syncfusion_flutter_charts/images/flight.png new file mode 100644 index 000000000..e7b244710 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/flight.png differ diff --git a/packages/syncfusion_flutter_charts/images/funnel.png b/packages/syncfusion_flutter_charts/images/funnel.png new file mode 100644 index 000000000..c6d87f26a Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/funnel.png differ diff --git a/packages/syncfusion_flutter_charts/images/grouping_dark.png b/packages/syncfusion_flutter_charts/images/grouping_dark.png new file mode 100644 index 000000000..fbffec0e8 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/grouping_dark.png differ diff --git a/packages/syncfusion_flutter_charts/images/home.png b/packages/syncfusion_flutter_charts/images/home.png new file mode 100644 index 000000000..3aebd8a31 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/home.png differ diff --git a/packages/syncfusion_flutter_charts/images/information.png b/packages/syncfusion_flutter_charts/images/information.png new file mode 100644 index 000000000..b26c04679 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/information.png differ diff --git a/packages/syncfusion_flutter_charts/images/livechart.png b/packages/syncfusion_flutter_charts/images/livechart.png new file mode 100644 index 000000000..9ff4a01f3 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/livechart.png differ diff --git a/packages/syncfusion_flutter_charts/images/maps_facebook.png b/packages/syncfusion_flutter_charts/images/maps_facebook.png new file mode 100644 index 000000000..86d2e724b Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/maps_facebook.png differ diff --git a/packages/syncfusion_flutter_charts/images/maps_instagram.png b/packages/syncfusion_flutter_charts/images/maps_instagram.png new file mode 100644 index 000000000..af4bdf3d3 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/maps_instagram.png differ diff --git a/packages/syncfusion_flutter_charts/images/maps_snapchat.png b/packages/syncfusion_flutter_charts/images/maps_snapchat.png new file mode 100644 index 000000000..1c65cc662 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/maps_snapchat.png differ diff --git a/packages/syncfusion_flutter_charts/images/maps_twitter.png b/packages/syncfusion_flutter_charts/images/maps_twitter.png new file mode 100644 index 000000000..032a520af Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/maps_twitter.png differ diff --git a/packages/syncfusion_flutter_charts/images/orange.png b/packages/syncfusion_flutter_charts/images/orange.png new file mode 100644 index 000000000..624c5aafb Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/orange.png differ diff --git a/packages/syncfusion_flutter_charts/images/other_fruits.png b/packages/syncfusion_flutter_charts/images/other_fruits.png new file mode 100644 index 000000000..909f56516 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/other_fruits.png differ diff --git a/packages/syncfusion_flutter_charts/images/pears.png b/packages/syncfusion_flutter_charts/images/pears.png new file mode 100644 index 000000000..10ac13bb2 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/pears.png differ diff --git a/packages/syncfusion_flutter_charts/images/person.png b/packages/syncfusion_flutter_charts/images/person.png new file mode 100644 index 000000000..b4dc1fb70 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/person.png differ diff --git a/packages/syncfusion_flutter_charts/images/personal_loan.png b/packages/syncfusion_flutter_charts/images/personal_loan.png new file mode 100644 index 000000000..592e7ab47 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/personal_loan.png differ diff --git a/packages/syncfusion_flutter_charts/images/productpage.png b/packages/syncfusion_flutter_charts/images/productpage.png new file mode 100644 index 000000000..ee93ba9c9 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/productpage.png differ diff --git a/packages/syncfusion_flutter_charts/images/pyramid.png b/packages/syncfusion_flutter_charts/images/pyramid.png new file mode 100644 index 000000000..90c1be377 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/pyramid.png differ diff --git a/packages/syncfusion_flutter_charts/images/rainy.png b/packages/syncfusion_flutter_charts/images/rainy.png new file mode 100644 index 000000000..da1c79053 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/rainy.png differ diff --git a/packages/syncfusion_flutter_charts/images/seriesfeatures.png b/packages/syncfusion_flutter_charts/images/seriesfeatures.png new file mode 100644 index 000000000..61736c544 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/seriesfeatures.png differ diff --git a/packages/syncfusion_flutter_charts/images/setting.png b/packages/syncfusion_flutter_charts/images/setting.png new file mode 100644 index 000000000..0b5a7c3e9 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/setting.png differ diff --git a/packages/syncfusion_flutter_charts/images/sunny_image.png b/packages/syncfusion_flutter_charts/images/sunny_image.png new file mode 100644 index 000000000..05a83177d Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/sunny_image.png differ diff --git a/packages/syncfusion_flutter_charts/images/syncfusion.png b/packages/syncfusion_flutter_charts/images/syncfusion.png new file mode 100644 index 000000000..b832e00d2 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/syncfusion.png differ diff --git a/packages/syncfusion_flutter_charts/images/syncfusion1.png b/packages/syncfusion_flutter_charts/images/syncfusion1.png new file mode 100644 index 000000000..18ab9100c Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/syncfusion1.png differ diff --git a/packages/syncfusion_flutter_charts/images/train.png b/packages/syncfusion_flutter_charts/images/train.png new file mode 100644 index 000000000..c53585329 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/train.png differ diff --git a/packages/syncfusion_flutter_charts/images/truck_legend.png b/packages/syncfusion_flutter_charts/images/truck_legend.png new file mode 100644 index 000000000..f95a05446 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/truck_legend.png differ diff --git a/packages/syncfusion_flutter_charts/images/userinteraction.png b/packages/syncfusion_flutter_charts/images/userinteraction.png new file mode 100644 index 000000000..12a0aa32d Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/userinteraction.png differ diff --git a/packages/syncfusion_flutter_charts/images/youtube.png b/packages/syncfusion_flutter_charts/images/youtube.png new file mode 100644 index 000000000..a84d09e23 Binary files /dev/null and b/packages/syncfusion_flutter_charts/images/youtube.png differ diff --git a/packages/syncfusion_flutter_charts/lib/charts.dart b/packages/syncfusion_flutter_charts/lib/charts.dart index 741e3cbab..55de2dd31 100644 --- a/packages/syncfusion_flutter_charts/lib/charts.dart +++ b/packages/syncfusion_flutter_charts/lib/charts.dart @@ -1,5 +1,6 @@ -/// Syncfusion Flutter Charts is a data visualization library written natively in Dart for -/// creating beautiful and high-performance cartesian, circular, pyramid and funnel charts. +/// Syncfusion Flutter Charts is a data visualization library written natively +/// in Dart for creating beautiful and high-performance cartesian, circular, +/// pyramid and funnel charts. /// /// To use, import `package:syncfusion_flutter_charts/charts.dart`. /// @@ -13,18 +14,23 @@ library charts; -// export annotation -export './src/chart/annotation/annotation_settings.dart'; +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; -//export axis -export './src/chart/axis/axis.dart' - hide VisibleRange, AxisHelper, ChartAxisRendererDetails; -export './src/chart/axis/category_axis.dart' hide CategoryAxisDetails; -export './src/chart/axis/datetime_axis.dart' hide DateTimeAxisDetails; -export './src/chart/axis/datetime_category_axis.dart' - hide DateTimeCategoryAxisDetails; -export './src/chart/axis/logarithmic_axis.dart' hide LogarithmicAxisDetails; -export './src/chart/axis/multi_level_labels.dart' +export './src/charts/axis/axis.dart' + hide + AxisDependent, + RenderChartAxis, + AxisPlotBand, + DoubleRange, + DoubleRangeTween, + AxisRender; +export './src/charts/axis/category_axis.dart' hide RenderCategoryAxis; +export './src/charts/axis/datetime_axis.dart' hide RenderDateTimeAxis; +export './src/charts/axis/datetime_category_axis.dart' + hide RenderDateTimeCategoryAxis; +export './src/charts/axis/logarithmic_axis.dart' hide RenderLogarithmicAxis; +export './src/charts/axis/multi_level_labels.dart' show NumericMultiLevelLabel, CategoricalMultiLevelLabel, @@ -32,201 +38,110 @@ export './src/chart/axis/multi_level_labels.dart' DateTimeMultiLevelLabel, LogarithmicMultiLevelLabel, MultiLevelLabelStyle; -export './src/chart/axis/numeric_axis.dart' hide NumericAxisDetails; -export './src/chart/axis/plotband.dart' hide getPlotBandPainter; - -//export chart -export './src/chart/base/chart_base.dart' hide ContainerArea; - -//export chart behavior -export './src/chart/chart_behavior/chart_behavior.dart'; -export './src/chart/chart_behavior/selection_behavior.dart'; -export './src/chart/chart_behavior/zoom_behavior.dart'; - -//export chart segment -export './src/chart/chart_segment/area_segment.dart'; -export './src/chart/chart_segment/bar_segment.dart'; -export './src/chart/chart_segment/box_and_whisker_segment.dart'; -export './src/chart/chart_segment/bubble_segment.dart'; -export './src/chart/chart_segment/candle_segment.dart'; -export './src/chart/chart_segment/chart_segment.dart' hide SegmentHelper; -export './src/chart/chart_segment/column_segment.dart'; -export './src/chart/chart_segment/error_bar_segment.dart'; -export './src/chart/chart_segment/fastline_segment.dart'; -export './src/chart/chart_segment/hilo_segment.dart'; -export './src/chart/chart_segment/hiloopenclose_segment.dart'; -export './src/chart/chart_segment/histogram_segment.dart'; -export './src/chart/chart_segment/line_segment.dart'; -export './src/chart/chart_segment/range_area_segment.dart'; -export './src/chart/chart_segment/range_column_segment.dart'; -export './src/chart/chart_segment/scatter_segment.dart'; -export './src/chart/chart_segment/spline_area_segment.dart'; -export './src/chart/chart_segment/spline_range_area_segment.dart'; -export './src/chart/chart_segment/spline_segment.dart'; -export './src/chart/chart_segment/stacked_area_segment.dart'; -export './src/chart/chart_segment/stacked_bar_segment.dart'; -export './src/chart/chart_segment/stacked_column_segment.dart'; -export './src/chart/chart_segment/stacked_line_segment.dart'; -export './src/chart/chart_segment/stackedarea100_segment.dart'; -export './src/chart/chart_segment/stackedbar100_segment.dart'; -export './src/chart/chart_segment/stackedcolumn100_segment.dart'; -export './src/chart/chart_segment/stackedline100_segment.dart'; -export './src/chart/chart_segment/step_area_segment.dart'; -export './src/chart/chart_segment/stepline_segment.dart'; -export './src/chart/chart_segment/waterfall_segment.dart'; - -//export chart series - -export './src/chart/chart_series/area_series.dart'; -export './src/chart/chart_series/bar_series.dart'; -export './src/chart/chart_series/box_and_whisker_series.dart' +export './src/charts/axis/numeric_axis.dart' hide RenderNumericAxis; +export './src/charts/axis/plot_band.dart'; +export './src/charts/behaviors/crosshair.dart'; +export './src/charts/behaviors/trackball.dart' + show TrackballBehavior, TrackballMarkerSettings; +export './src/charts/behaviors/zooming.dart'; +export './src/charts/cartesian_chart.dart'; +export './src/charts/circular_chart.dart'; +export './src/charts/common/chart_point.dart' + hide CircularChartPoint, ChartPointInfo; +export './src/charts/common/connector_line.dart'; +export './src/charts/common/data_label.dart' show DataLabelSettings; +export './src/charts/common/empty_points.dart'; +export './src/charts/common/interactive_tooltip.dart'; +export './src/charts/common/legend.dart'; +export './src/charts/common/marker.dart' hide MarkerContainer, ChartMarker; +export './src/charts/funnel_chart.dart'; +export './src/charts/indicators/accumulation_distribution_indicator.dart' + hide ADIndicatorRenderer, ADIndicatorWidget; +export './src/charts/indicators/atr_indicator.dart' + hide AtrIndicatorRenderer, AtrIndicatorWidget; +export './src/charts/indicators/bollinger_bands_indicator.dart' + hide BollingerIndicatorRenderer, BollingerIndicatorWidget; +export './src/charts/indicators/ema_indicator.dart' + hide EmaIndicatorRenderer, EmaIndicatorWidget; +export './src/charts/indicators/macd_indicator.dart' + hide MacdIndicatorRenderer, MacdIndicatorWidget; +export './src/charts/indicators/momentum_indicator.dart' + hide MomentumIndicatorRenderer, MomentumIndicatorWidget; +export './src/charts/indicators/roc_indicator.dart' + hide RocIndicatorRenderer, RocIndicatorWidget; +export './src/charts/indicators/rsi_indicator.dart' + hide RsiIndicatorRenderer, RsiIndicatorWidget; +export './src/charts/indicators/sma_indicator.dart' + hide SmaIndicatorRenderer, SmaIndicatorWidget; +export './src/charts/indicators/stochastic_indicator.dart' + hide StochasticIndicatorRenderer, StochasticIndicatorWidget; +export './src/charts/indicators/technical_indicator.dart' + hide IndicatorRenderer, IndicatorWidget; +export './src/charts/indicators/tma_indicator.dart' + hide TmaIndicatorRenderer, TmaIndicatorWidget; +export './src/charts/indicators/wma_indicator.dart' + hide WmaIndicatorRenderer, WmaIndicatorWidget; +export './src/charts/pyramid_chart.dart'; +export './src/charts/series/area_series.dart'; +export './src/charts/series/bar_series.dart'; +export './src/charts/series/box_and_whisker_series.dart' hide BoxPlotQuartileValues; -export './src/chart/chart_series/bubble_series.dart'; -export './src/chart/chart_series/candle_series.dart'; -export './src/chart/chart_series/column_series.dart'; -export './src/chart/chart_series/error_bar_series.dart' - hide ChartErrorValues, ErrorBarMean; -export './src/chart/chart_series/fastline_series.dart'; -export './src/chart/chart_series/hilo_series.dart'; -export './src/chart/chart_series/hiloopenclose_series.dart'; -export './src/chart/chart_series/histogram_series.dart' hide HistogramValues; -export './src/chart/chart_series/line_series.dart'; -export './src/chart/chart_series/range_area_series.dart'; -export './src/chart/chart_series/range_column_series.dart'; -export './src/chart/chart_series/scatter_series.dart'; -export './src/chart/chart_series/series.dart' hide SeriesHelper; -export './src/chart/chart_series/spline_area_series.dart'; -export './src/chart/chart_series/spline_range_area_series.dart'; -export './src/chart/chart_series/spline_series.dart'; -export './src/chart/chart_series/stacked_area_series.dart'; -export './src/chart/chart_series/stacked_bar_series.dart'; -export './src/chart/chart_series/stacked_column_series.dart'; -export './src/chart/chart_series/stacked_line_series.dart'; -export './src/chart/chart_series/stackedarea100_series.dart'; -export './src/chart/chart_series/stackedbar100_series.dart'; -export './src/chart/chart_series/stackedcolumn100_series.dart'; -export './src/chart/chart_series/stackedline100_series.dart'; -export './src/chart/chart_series/step_area_series.dart'; -export './src/chart/chart_series/stepline_series.dart'; -export './src/chart/chart_series/waterfall_series.dart'; -export './src/chart/chart_series/xy_data_series.dart' - hide ChartLocation, CartesianPointHelper; - -//export common -export './src/chart/common/data_label.dart' hide DataLabelSettingsRenderer; -export './src/chart/common/interactive_tooltip.dart' hide ChartPointInfo; -export './src/chart/common/marker.dart' - hide MarkerSettingsRenderer, MarkerDetails; -export './src/chart/common/trackball_marker_settings.dart'; - -// export chart series renderer -export './src/chart/series_painter/area_painter.dart' hide AreaChartPainter; -export './src/chart/series_painter/bar_painter.dart' hide BarChartPainter; -export './src/chart/series_painter/box_and_whisker_painter.dart' - hide BoxAndWhiskerPainter; -export './src/chart/series_painter/bubble_painter.dart' hide BubbleChartPainter; -export './src/chart/series_painter/candle_painter.dart' hide CandlePainter; -export './src/chart/series_painter/column_painter.dart' hide ColumnChartPainter; -export './src/chart/series_painter/error_bar_painter.dart' - hide ErrorBarChartPainter; -export './src/chart/series_painter/fastline_painter.dart' - hide FastLineChartPainter; -export './src/chart/series_painter/hilo_painter.dart' hide HiloPainter; -export './src/chart/series_painter/hiloopenclose_painter.dart' - hide HiloOpenClosePainter; -export './src/chart/series_painter/histogram_painter.dart' - hide HistogramChartPainter; -export './src/chart/series_painter/line_painter.dart' hide LineChartPainter; -export './src/chart/series_painter/range_area_painter.dart' - hide RangeAreaChartPainter; -export './src/chart/series_painter/range_column_painter.dart' - hide RangeColumnChartPainter; -export './src/chart/series_painter/scatter_painter.dart' - hide ScatterChartPainter; -export './src/chart/series_painter/spline_area_painter.dart' - hide SplineAreaChartPainter; -export './src/chart/series_painter/spline_painter.dart' hide SplineChartPainter; -export './src/chart/series_painter/spline_range_area_painter.dart' - hide SplineRangeAreaChartPainter; -export './src/chart/series_painter/stacked_area_painter.dart' +export './src/charts/series/bubble_series.dart'; +export './src/charts/series/candle_series.dart'; +export './src/charts/series/chart_series.dart' hide - StackedAreaChartPainter, - StackedArea100ChartPainter, - stackedAreaPainter; -export './src/chart/series_painter/stacked_bar_painter.dart' - hide StackedBarChartPainter, StackedBar100ChartPainter; -export './src/chart/series_painter/stacked_column_painter.dart' - hide StackedColummnChartPainter, StackedColumn100ChartPainter; -export './src/chart/series_painter/stacked_line_painter.dart' - hide StackedLineChartPainter, StackedLine100ChartPainter; -export './src/chart/series_painter/step_area_painter.dart' - hide StepAreaChartPainter; -export './src/chart/series_painter/stepline_painter.dart' - hide StepLineChartPainter; -export './src/chart/series_painter/waterfall_painter.dart' - hide WaterfallChartPainter; - -// export technical indicators -export './src/chart/technical_indicators/accumulation_distribution_indicator.dart'; -export './src/chart/technical_indicators/atr_indicator.dart'; -export './src/chart/technical_indicators/bollinger_bands_indicator.dart'; -export './src/chart/technical_indicators/ema_indicator.dart'; -export './src/chart/technical_indicators/macd_indicator.dart'; -export './src/chart/technical_indicators/momentum_indicator.dart'; -export './src/chart/technical_indicators/rsi_indicator.dart'; -export './src/chart/technical_indicators/sma_indicator.dart'; -export './src/chart/technical_indicators/stochastic_indicator.dart'; -export './src/chart/technical_indicators/technical_indicator.dart' - hide TechnicalIndicatorsRenderer; -export './src/chart/technical_indicators/tma_indicator.dart'; - -// export trendlines -export './src/chart/trendlines/trendlines.dart' hide TrendlineRenderer; - -//export user interaction -export './src/chart/user_interaction/crosshair.dart' - hide CrosshairHelper, CrosshairRenderingDetails; -export './src/chart/user_interaction/trackball.dart' - hide TrackballRenderingDetails, TrackballHelper; -export './src/chart/user_interaction/trackball_marker_setting_renderer.dart' - hide TrackballMarkerSettingsRenderer; -export './src/chart/user_interaction/zooming_panning.dart' - show ZoomPanBehavior, ZoomPanBehaviorRenderer; - + SeriesSlot, + ContinuousSeriesMixin, + RealTimeUpdateMixin, + CartesianRealTimeUpdateMixin, + SbsSeriesMixin, + StackingSeriesMixin, + BarSeriesTrackerMixin, + SegmentAnimationMixin, + LineSeriesMixin, + ChartSeriesParentData; +export './src/charts/series/column_series.dart'; +export './src/charts/series/doughnut_series.dart'; +export './src/charts/series/error_bar_series.dart' + hide ErrorBarMean, ErrorValues, AxesRange; +export './src/charts/series/fast_line_series.dart'; +export './src/charts/series/funnel_series.dart'; +export './src/charts/series/hilo_open_close_series.dart'; +export './src/charts/series/hilo_series.dart'; +export './src/charts/series/histogram_series.dart'; +export './src/charts/series/line_series.dart'; +export './src/charts/series/pie_series.dart'; +export './src/charts/series/pyramid_series.dart'; +export './src/charts/series/radial_bar_series.dart'; +export './src/charts/series/range_area_series.dart'; +export './src/charts/series/range_column_series.dart'; +export './src/charts/series/scatter_series.dart'; +export './src/charts/series/spline_series.dart'; +export './src/charts/series/stacked_area100_series.dart'; +export './src/charts/series/stacked_area_series.dart'; +export './src/charts/series/stacked_bar100_series.dart'; +export './src/charts/series/stacked_bar_series.dart'; +export './src/charts/series/stacked_column100_series.dart'; +export './src/charts/series/stacked_column_series.dart'; +export './src/charts/series/stacked_line100_series.dart'; +export './src/charts/series/stacked_line_series.dart'; +export './src/charts/series/step_area_series.dart'; +export './src/charts/series/stepline_series.dart'; +export './src/charts/series/waterfall_series.dart'; //export utils -export './src/chart/utils/enum.dart'; - -// export circular library -export './src/circular_chart/base/circular_base.dart'; -export './src/circular_chart/renderer/chart_point.dart' - hide PointHelper, getCircularPoint; -export './src/circular_chart/renderer/circular_chart_annotation.dart'; -export './src/circular_chart/renderer/circular_series.dart' - hide getVisiblePointIndex; -export './src/circular_chart/renderer/circular_series_controller.dart'; -export './src/circular_chart/renderer/doughnut_series.dart'; -export './src/circular_chart/renderer/pie_series.dart'; -export './src/circular_chart/renderer/radial_bar_series.dart'; -export './src/circular_chart/renderer/renderer_base.dart'; -export './src/circular_chart/utils/enum.dart'; - -//export common -export './src/common/common.dart' - hide ChartContainer, MeasureWidgetContext, LegendRenderer; -export './src/common/event_args.dart' hide ErrorBarValues; -export './src/common/series/chart_series.dart'; -export './src/common/user_interaction/selection_behavior.dart' - hide SelectionDetails, SelectionHelper; -export './src/common/user_interaction/tooltip.dart' hide TooltipHelper; -export './src/common/utils/enum.dart'; -export './src/common/utils/typedef.dart'; - -// export funnel library -export './src/funnel_chart/base/funnel_base.dart'; -export './src/funnel_chart/renderer/funnel_series.dart'; - -// export pyramid library -export './src/pyramid_chart/base/pyramid_base.dart'; -export './src/pyramid_chart/renderer/pyramid_series.dart'; -export './src/pyramid_chart/renderer/series_controller.dart'; -export './src/pyramid_chart/utils/common.dart' hide PointInfoHelper; +export './src/charts/utils/enum.dart' hide ChartDataPointType; +export './src/charts/utils/typedef.dart' hide PointToPixelCallback; +export 'src/charts/common/annotation.dart'; +export 'src/charts/common/callbacks.dart' hide ErrorBarValues; +export 'src/charts/common/title.dart'; +//export user interaction +export 'src/charts/interactions/selection.dart'; +export 'src/charts/interactions/tooltip.dart' + hide ChartTooltipInfo, TrendlineTooltipInfo; +export 'src/charts/trendline/trendline.dart' + hide + TrendlineWidget, + TrendlineStack, + RenderTrendlineStack, + TrendlineParentData, + TrendlineContainer; diff --git a/packages/syncfusion_flutter_charts/lib/sparkcharts.dart b/packages/syncfusion_flutter_charts/lib/sparkcharts.dart index d5e05fcad..ed57cb75b 100644 --- a/packages/syncfusion_flutter_charts/lib/sparkcharts.dart +++ b/packages/syncfusion_flutter_charts/lib/sparkcharts.dart @@ -1,5 +1,6 @@ -/// 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. +/// 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`. /// 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 deleted file mode 100644 index 7befebd27..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart +++ /dev/null @@ -1,295 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../utils/enum.dart'; - -/// This class has the properties of cartesian chart annotation. -/// -/// Chart supports annotations that allow you to mark the specific area of interest in the chart area. You can add the custom widgets using this -/// annotation feature as depicted below. -/// -/// The x and y values can be specified with axis units or pixel units, and these can be identified by -/// using coordinateUnit property. When the logical pixel is specified, the annotation will be placed to pixel values whereas the point is specified, then -/// the annotation will be placed to series point values. -/// -/// Provides the options x, y, coordinateUnit, and widget to customize the cartesian chart annotation. -/// -@immutable -class CartesianChartAnnotation { - /// Creating an argument constructor of CartesianChartAnnotation class. - const CartesianChartAnnotation( - {this.widget, - 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}); - - /// Considers any widget as annotation. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// coordinateUnit: CoordinateUnit.point, - /// region: AnnotationRegion.chartArea, - /// x: 3, - /// y: 60 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final Widget? widget; - - /// Specifies the coordinate units for placing the annotation in either logicalPixel or point. - /// - /// Defaults to `CoordinateUnit.logicalPixel` - /// - /// Also refer [CoordinateUnit]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// coordinateUnit: CoordinateUnit.point, - /// x: 3, - /// y: 60 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final CoordinateUnit coordinateUnit; - - /// Annotations can be placed with respect to either plotArea or chart. - /// - /// Defaults to `AnnotationRegion.chart`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// region: AnnotationRegion.chartArea, - /// x: 3, - /// y: 60 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final AnnotationRegion region; - - /// Specifies the x-values as pixel, point or percentage values based on the coordinateUnit. - /// - /// Percentage value refers to the overall width of the chart. i.e. 100% is equal to the width of the chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// x: 3, - /// y: 60 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final dynamic x; - - /// Specifies the y-values as pixel, point or percentage values based on the coordinateUnit. - /// - /// Percentage value refers to the overall height of the chart. i.e. 100% is equal to the height of the chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// x: 3, - /// y: 60 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final dynamic y; - - /// Specifies the x-axis name to the annotation that should be bound. - /// - /// Defaults to `‘’`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// xAxisName: 'Gold' - /// ), - /// ], - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// x: 3, - /// y: 60, - /// xAxisName: 'Gold' - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String? xAxisName; - - /// Specifies the y-axis name to the annotation that should be bound. - /// - /// Defaults to `‘’`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// yAxisName: 'Gold' - /// ), - /// ], - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// x: 3, - /// y: 60, - /// yAxisName: 'Gold' - /// ), - /// ) - /// ); - ///} - ///``` - final String? yAxisName; - - /// Aligns the annotations horizontally. - /// - /// Alignment can be set to `ChartAlignment.near`, `ChartAlignment.far`, or `ChartAlignment.center`. - /// - /// Defaults to `ChartAlignment.center` - /// - /// Also refer [ChartAlignment]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// x: 3, - /// y: 60, - /// HorizontalAlignment: ChartAlignment.near - /// ) - /// ], - /// ) - /// ); - ///} - ///``` - final ChartAlignment horizontalAlignment; - - /// Aligns the annotations vertically. - /// - /// Alignment can be set to `ChartAlignment.near`, `ChartAlignment.far`, or `ChartAlignment.center`. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// Also refer [ChartAlignment]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// x: 3, - /// y: 60, - /// verticalAllignment: ChartAlignment.near - /// ) - /// ], - /// ) - /// ); - ///} - ///``` - final ChartAlignment verticalAlignment; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is CartesianChartAnnotation && - other.widget == widget && - other.coordinateUnit == coordinateUnit && - other.region == region && - other.horizontalAlignment == horizontalAlignment && - other.verticalAlignment == verticalAlignment && - other.x == x && - other.y == y && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName; - } - - @override - int get hashCode { - final List values = [ - widget, - coordinateUnit, - region, - horizontalAlignment, - verticalAlignment, - x, - y, - xAxisName, - yAxisName - ]; - return hashList(values); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart deleted file mode 100644 index 6f2bc662a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart +++ /dev/null @@ -1,3925 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../common/event_args.dart'; -import '../../common/rendering_details.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../../common/utils/typedef.dart' - show MultiLevelLabelFormatterCallback, ChartLabelFormatterCallback; -import '../axis/axis_renderer.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../axis/multi_level_labels.dart'; -import '../axis/numeric_axis.dart'; -import '../axis/plotband.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/interactive_tooltip.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// This class holds the properties of ChartAxis. -/// -/// Charts typically have two axes that are used to measure and categorize data: a vertical (Y) axis, and a horizontal (X) axis. -/// Vertical(Y) axis always uses a numerical scale. Horizontal(X) axis supports Category, Numeric, Date-time, Logarithmic. -/// -/// Provides the options for plotOffset, series visible, axis title, label padding, and alignment to customize the appearance. -/// -/// -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, - this.desiredIntervals, - 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, - this.rangeController, - this.maximumLabelWidth, - this.labelsExtent, - this.autoScrollingDelta, - AutoScrollingMode? autoScrollingMode, - double? borderWidth, - Color? borderColor, - AxisBorderType? axisBorderType, - MultiLevelLabelStyle? multiLevelLabelStyle, - this.multiLevelLabelFormatter, - this.multiLevelLabels, - this.axisLabelFormatter}) - : isVisible = isVisible ?? true, - anchorRangeToVisiblePoints = anchorRangeToVisiblePoints ?? true, - interactiveTooltip = interactiveTooltip ?? const InteractiveTooltip(), - isInversed = isInversed ?? false, - plotOffset = plotOffset ?? 0, - placeLabelsNearAxisLine = placeLabelsNearAxisLine ?? true, - opposedPosition = opposedPosition ?? false, - rangePadding = rangePadding ?? ChartRangePadding.auto, - labelRotation = labelRotation ?? 0, - labelPosition = labelPosition ?? ChartDataLabelPosition.outside, - labelAlignment = labelAlignment ?? LabelAlignment.center, - tickPosition = tickPosition ?? TickPosition.outside, - labelIntersectAction = - labelIntersectAction ?? AxisLabelIntersectAction.hide, - minorTicksPerInterval = minorTicksPerInterval ?? 0, - maximumLabels = maximumLabels ?? 3, - labelStyle = getTextStyle( - textStyle: labelStyle, - fontSize: 12.0, - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontFamily: 'Normal'), - title = title ?? AxisTitle(), - axisLine = axisLine ?? const AxisLine(), - majorTickLines = majorTickLines ?? const MajorTickLines(), - minorTickLines = minorTickLines ?? const MinorTickLines(), - majorGridLines = majorGridLines ?? const MajorGridLines(), - minorGridLines = minorGridLines ?? const MinorGridLines(), - edgeLabelPlacement = edgeLabelPlacement ?? EdgeLabelPlacement.none, - zoomFactor = zoomFactor ?? 1, - zoomPosition = zoomPosition ?? 0, - enableAutoIntervalOnZooming = enableAutoIntervalOnZooming ?? true, - plotBands = plotBands ?? [], - autoScrollingMode = autoScrollingMode ?? AutoScrollingMode.end, - axisBorderType = axisBorderType ?? AxisBorderType.rectangle, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - multiLevelLabelStyle = - multiLevelLabelStyle ?? const MultiLevelLabelStyle(); - - /// Toggles the visibility of the axis. - /// - /// Visibility of all the elements in the axis - /// such as title, labels, major tick lines, and major grid lines will be toggled together. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(isVisible: false), - /// ) - /// ); - ///} - ///``` - final bool isVisible; - - /// Determines the value axis range, based on the visible data points or based - /// on the overall data points available in chart. - /// - /// 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. - /// - /// 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 - /// visible points. - /// - /// _Note:_ This is applicable only to the value axis and not for other axis. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: NumericAxis(anchorRangeToVisiblePoints: false), - /// ) - /// ); - ///} - ///``` - final bool anchorRangeToVisiblePoints; - - /// Customizes the appearance of the axis line. The axis line is visible by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(axisLine: AxisLine(width: 10)), - /// ) - /// ); - ///} - ///``` - final AxisLine axisLine; - - /// Customizes the appearance of the major tick lines. - /// - /// Major ticks are small lines - /// used to indicate the intervals in an axis. Major tick lines are visible by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(majorTickLines: const MajorTickLines(width: 2)), - /// ) - /// ); - ///} - ///``` - final MajorTickLines majorTickLines; - - /// Customizes the appearance of the minor tick lines. - /// - /// Minor ticks are small lines - /// used to indicate the minor intervals between a major interval. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(minorTickLines: MinorTickLines(width: 2)), - /// ) - /// ); - ///} - ///``` - final MinorTickLines minorTickLines; - - /// Customizes the appearance of the major grid lines. - /// - /// Major grids are the lines - /// drawn on the plot area at all the major intervals in an axis. Major grid lines - /// are visible by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(majorGridLines: MajorGridLines(width: 2)), - /// ) - /// ); - ///} - ///``` - final MajorGridLines majorGridLines; - - /// Customizes the appearance of the minor grid lines. - /// - /// Minor grids are the lines drawn - /// on the plot area at all the minor intervals between the major intervals. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(minorGridLines: MinorGridLines(width: 0)), - /// ) - /// ); - ///} - ///``` - final MinorGridLines minorGridLines; - - /// Customizes the appearance of the axis labels. - /// - /// Labels are the axis values - /// placed at each interval. Axis labels are visible by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(labelStyle: TextStyle(color: Colors.black)), - /// ) - /// ); - ///} - ///``` - /// This property is used to show or hide the axis labels. - final TextStyle labelStyle; - - /// Customizes the appearance of the axis title. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(title: AxisTitle( text: 'Year')), - /// ) - /// ); - ///} - ///``` - final AxisTitle title; - - /// Padding for minimum and maximum values in an axis. - /// - /// Various types of range padding - /// such as round, none, normal, and additional can be applied. - /// - /// Defaults to `ChartRangePadding.auto`. - /// - /// Also refer [ChartRangePadding]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(rangePadding: ChartRangePadding.round), - /// ) - /// ); - ///} - ///``` - final ChartRangePadding rangePadding; - - /// The number of intervals in an axis. - /// - /// By default, the number of intervals is - /// calculated based on the minimum and maximum values and the axis width and height. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(desiredIntervals: 2), - /// ) - /// ); - ///} - ///``` - final int? desiredIntervals; - - /// The maximum number of labels to be displayed in an axis in 100 logical pixels. - /// - /// Defaults to `3`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(maximumLabels: 4), - /// ) - /// ); - ///} - ///``` - final int maximumLabels; - - /// Interval of the minor ticks. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(minorTicksPerInterval: 2), - /// ) - /// ); - ///} - ///``` - final int minorTicksPerInterval; - - /// Angle for axis labels. - /// The axis labels can be rotated to any angle. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(labelRotation: 90), - /// ) - /// ); - ///} - ///``` - final int labelRotation; - - /// Axis labels intersecting action. - /// - /// Various actions such as hide, trim, wrap, rotate - /// 90 degree, rotate 45 degree, and placing the labels in multiple rows can be - /// handled when the axis labels collide with each other. - /// - /// Defaults to `AxisLabelIntersectAction.hide`. - /// - /// Also refer [AxisLabelIntersectAction]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(labelIntersectAction: AxisLabelIntersectAction.multipleRows), - /// ) - /// ); - ///} - ///``` - final AxisLabelIntersectAction labelIntersectAction; - - /// Opposes the axis position. - /// - /// An axis can be placed at the opposite side of its default position. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(opposedPosition: true), - /// ) - /// ); - ///} - ///``` - final bool opposedPosition; - - /// Inverts the axis. - /// - /// Axis is rendered from the minimum value to maximum value by - /// default, and it can be inverted to render the axis from the maximum value - /// to minimum value. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(isInversed: true), - /// ) - /// ); - ///} - ///``` - final bool isInversed; - - /// Position of the labels. - /// - /// Axis labels can be placed either inside or - /// outside the plot area. - /// - /// Defaults to `ChartDataLabelPosition.outside`. - /// - /// Also refer [ChartDataLabelPosition]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(labelPosition: ChartDataLabelPosition.inside), - /// ) - /// ); - ///} - ///``` - final ChartDataLabelPosition labelPosition; - - /// Alignment of the labels. - /// - /// Axis labels can be placed either to the start, - /// end or center of the grid lines. - /// - /// Defaults to `LabelAlignment.start`. - /// - /// Also refer [LabelAlignment]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(labelAlignment: LabelAlignment.start), - /// ) - /// ); - ///} - ///``` - final LabelAlignment labelAlignment; - - /// Position of the tick lines. - /// - /// Tick lines can be placed either inside or - /// outside the plot area. - /// - /// Defaults to `TickPosition.outside`. - /// - /// Also refer [TickPosition]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(tickPosition: TickPosition.inside), - /// ) - /// ); - ///} - ///``` - final TickPosition tickPosition; - - /// Position of the edge labels. - /// - /// The edge labels in an axis can be hidden or shifted - /// inside the axis bounds. - /// - /// Defaults to `EdgeLabelPlacement.none`. - /// - /// Also refer [EdgeLabelPlacement]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(edgeLabelPlacement: EdgeLabelPlacement.hide), - /// ) - /// ); - ///} - ///``` - final EdgeLabelPlacement edgeLabelPlacement; - - /// Axis interval value. - /// - /// Using this, the axis labels can be displayed after - /// certain interval value. By default, interval will be - /// calculated based on the minimum and maximum values of the provided data. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(interval: 1), - /// ) - /// ); - ///} - ///``` - final double? interval; - - /// Padding for plot area. The axis is rendered in chart with padding. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(plotOffset: 60), - /// ) - /// ); - ///} - ///``` - final double plotOffset; - - /// Name of an axis. - /// - /// A unique name further used for linking the series to this - /// appropriate axis. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(name: 'primaryXAxis'), - /// ) - /// ); - ///} - ///``` - final String? name; - - /// Zoom factor of an axis. - /// - /// Scale the axis based on this value, and it ranges - /// from 0 to 1. - /// - /// Defaults to `1`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(zoomFactor: 0.5), - /// ) - /// ); - ///} - ///``` - final double zoomFactor; - - /// Position of the zoomed axis. The value ranges from 0 to 1. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(zoomPosition: 0.6), - /// ) - /// ); - ///} - ///``` - final double zoomPosition; - - /// Enables or disables the automatic interval while zooming. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(enableAutoIntervalOnZooming: true), - /// ) - /// ); - ///} - ///``` - final bool enableAutoIntervalOnZooming; - - /// Customizes the crosshair and selection zooming tooltip. Tooltip displays the current - /// axis value based on the crosshair position/selectionZoomRect position at an axis. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(interactiveTooltip: InteractiveTooltip(enable: true)), - /// ) - /// ); - ///} - ///``` - final InteractiveTooltip interactiveTooltip; - - /// Customization to place the axis crossing on another axis based on the value. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(crossesAt:10), - /// ) - /// ); - ///} - ///``` - final dynamic crossesAt; - - /// Axis line crossed on mentioned axis name, and applicable for plot band also. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// associatedAxisName: 'primaryXAxis', - /// plotBands: [ - /// PlotBand( - /// start: 2, - /// end: 5, - /// associatedAxisStart: 2, - /// ), - /// ], - /// ), - /// ) - /// ); - ///} - ///``` - final String? associatedAxisName; - - /// Consider to place the axis label respect to near axis line. - /// - /// Defaults to `true`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(placeLabelsNearAxisLine: false, crossesAt:10), - /// ) - /// ); - ///} - ///``` - final bool placeLabelsNearAxisLine; - - /// Render the plot band in axis. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands:[plotBands(start:20, end:30, color:Colors.red, text:'Flutter')] - /// ), - /// ) - /// ); - ///} - ///``` - final List plotBands; - - /// The `rangeController` property is used to set the maximum and minimum values for the chart in the viewport. - /// In the minimum and maximum properties of the axis, you can specify the minimum and maximum values with respect to the entire data source. - /// In the visibleMinimum and visibleMaximum properties, you can specify the values to be viewed in the viewed port i.e. range controller's start and end values respectively. - /// - /// Here you need to specify the `minimum`, `maximum`, `visibleMinimum`, and `visibleMaximum` properties to the axis and the axis values will be visible with respect to - /// visibleMinimum and visibleMaximum properties. - /// - ///```dart - ///Widget build(BuildContext context) { - /// RangeController rangeController = RangeController( - /// start: DateTime(2020, 2, 1), - /// end: DateTime(2020, 2, 30), - /// ); - /// SfCartesianChart sliderChart = SfCartesianChart( - /// margin: const EdgeInsets.all(0), - /// primaryXAxis: - /// DateTimeAxis(isVisible: false), - /// primaryYAxis: NumericAxis(isVisible: false), - /// plotAreaBorderWidth: 0, - /// series: >[ - /// SplineAreaSeries( - /// // Add required properties. - /// ) - /// ], - /// ); - /// return Scaffold( - /// body: Column( - /// children: [ - /// Expanded( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis( - /// maximum: DateTime(2020, 1, 1), - /// minimum: DateTime(2020, 3, 30), - /// // set maximum value from the range controller - /// visibleMaximum: rangeController.end, - /// // set minimum value from the range controller - /// visibleMinimum: rangeController.start, - /// rangeController: rangeController), - /// primaryYAxis: NumericAxis(), - /// series: >[ - /// SplineSeries( - /// dataSource: splineSeriesData, - /// xValueMapper: (ChartSampleData sales, _) => - /// sales.x as DateTime, - /// yValueMapper: (ChartSampleData sales, _) => sales.y, - /// // Add required properties. - /// ) - /// ], - /// ), - /// ), - /// Expanded( - /// child: SfRangeSelectorTheme( - /// data: SfRangeSelectorThemeData(), - /// child: SfRangeSelector( - /// min: min, - /// max: max, - /// controller: rangeController, - /// showTicks: true, - /// showLabels: true, - /// dragMode: SliderDragMode.both, - /// onChanged: (SfRangeValues value) { - /// // set the start value to rangeController from this callback - /// rangeController.start = value.start; - /// // set the end value to rangeController from this callback - /// rangeController.end = value.end; - /// setState(() {}); - /// }, - /// child: Container( - /// child: sliderChart, - /// ), - /// ), - /// )), - /// ], - /// ), - /// ); - ///} - ///``` - final RangeController? rangeController; - - /// Specifies maximum text width for axis labels. - /// - /// If an axis label exceeds the specified width, it will get trimmed and ellipse(...) will be - /// added at the end of the trimmed text. By default, the labels will not be trimmed. - /// - /// Complete label text will be shown in a tooltip when tapping/clicking over the trimmed axis labels. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis(maximumLabelWidth: 80), - /// series: [ - /// BarSeries( - /// dataSource: [ - /// ChartData(x: 'Goldin Finance 117', y: 597), - /// ChartData(x: 'Ping An Finance Center', y: 599), - /// ChartData(x: 'Makkah Clock Royal Tower', y: 601), - /// ChartData(x: 'Shanghai Tower', y: 632), - /// ChartData(x: 'Burj Khalifa', y: 828) - /// ], - /// xValueMapper: (ChartData sales, _) => sales.x, - /// yValueMapper: (ChartData sales, _) => sales.y - /// ) - /// ], - /// ) - /// ); - ///} - ///``` - final double? maximumLabelWidth; - - /// Specifies the fixed width for the axis labels. This width represents the space between axis line and - /// axis title. - /// - /// If an axis label exceeds the specified value, as like [maximumLabelWidth] feature, axis label - /// will get trimmed and ellipse(...) will be added at the end of the trimmed text. - /// - /// Additionally, if an axis label width is within the specified value, white space will be added - /// at the beginning for remaining width. This is done to maintain uniform bounds and to eliminate - /// axis label flickering on zooming/panning. - /// - /// Complete label text will be shown in a tooltip when tapping/clicking over the trimmed axis labels. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis(labelsExtent: 70), - /// series: [ - /// BarSeries( - /// dataSource: [ - /// ChartData(x: 'Goldin Finance 117', y: 597), - /// ChartData(x: 'Ping An Finance Center', y: 599), - /// ChartData(x: 'Makkah Clock Royal Tower', y: 601), - /// ChartData(x: 'Shanghai Tower', y: 632), - /// ChartData(x: 'Burj Khalifa', y: 828) - /// ], - /// xValueMapper: (ChartData sales, _) => sales.x, - /// yValueMapper: (ChartData sales, _) => sales.y - /// ) - /// ], - /// ) - /// ); - ///} - ///``` - 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; - - /// Border color of the axis label. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// borderColor: Colors.black, - /// ) - /// ) - /// ); - /// } - ///``` - final Color? borderColor; - - /// Border width of the axis label. - /// - /// Defaults to `0`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// borderWidth: 2, - /// ) - /// ) - /// ); - /// } - ///``` - final double borderWidth; - - /// Border type of the axis label. - /// - /// Defaults to `AxisBorderType.rectangle`. - /// - /// Also refer [AxisBorderType]. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// axisBorderType: AxisBorderType.withoutTopAndBottom, - /// ) - /// ) - /// ); - /// } - ///``` - final AxisBorderType axisBorderType; - - /// Customize the multi-level label’s border color, width, type, and - /// text style such as color, font size, etc. - /// - /// When the multi-level label’s width exceeds its respective segment, - /// then the label will get trimmed and on tapping / hovering over the - /// trimmed label, a tooltip will be shown. - /// - /// Also refer [multiLevelLabelFormatter]. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabelStyle: MultiLevelLabelStyle( - /// borderWidth: 1, - /// borderColor: Colors.black, - /// borderType: MultiLevelBorderType.rectangle, - /// textStyle: TextStyle( - /// fontSize: 10, - /// color: Colors.black, - /// ) - /// ) - /// ) - /// ) - /// ); - /// } - ///``` - final MultiLevelLabelStyle multiLevelLabelStyle; - - /// Provides the option to group the axis labels. You can customize the start, - /// end value of a multi-level label, text, and level of the multi-level labels. - /// - /// The `start` and `end` values for the category axis need to be string type, - /// in the case of date-time or date-time category axes need to be date-time - /// and in the case of numeric or logarithmic axes needs to be double. - /// - /// Defaults to `null`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 2, - /// text: 'First' - /// ), - /// NumericMultiLevelLabel( - /// start: 2, - /// end: 4, - /// text: 'Second' - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - // ignore: always_specify_types - final List? multiLevelLabels; - - /// Called while rendering each multi-level label. - /// - /// Provides label text, the actual level of the label, index, and - /// text styles such as color, font size, etc using `MultiLevelLabelRenderDetails` class. - /// - /// You can customize the text and text style using `ChartAxisLabel` class - /// and can return it. - /// - /// Defaults to `null`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabelFormatter: (MultiLevelLabelRenderDetails details) { - /// if (details.index == 1) { - /// return ChartAxisLabel('Text', details.textStyle); - /// } else { - /// return ChartAxisLabel(details.text, details.textStyle); - /// } - /// }, - /// multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 2, - /// text: 'First' - /// ), - /// NumericMultiLevelLabel( - /// start: 2, - /// end: 4, - /// text: 'Second' - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final MultiLevelLabelFormatterCallback? multiLevelLabelFormatter; - - /// 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. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primarXAxis: CategoryAxis( - /// axisLabelFormatter: (AxisLabelRenderDetails details) => axis(details), - /// ), - /// ) - /// ); - ///} - /// - ///ChartAxisLabel axis(AxisLabelRenderDetails details) { - /// return ChartAxisLabel('axis Label', details.textStyle); - ///} - ///``` - final ChartLabelFormatterCallback? axisLabelFormatter; -} - -/// Holds the axis label information. -/// -/// Axis Label used by the user-specified or by default to make the label for both x and y-axis. -/// -/// Provides options for label style, label size, text, and value to customize the appearance. -/// -class AxisLabel { - /// Creating an argument constructor of AxisLabel class. - AxisLabel(this.labelStyle, this.labelSize, this.text, this.value, - this.trimmedText, this.renderText); - - /// Specifies the label text style. - /// - /// The [TextStyle] is used to customize the chart title text style. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis( - /// labelStyle: TextStyle(color: Colors.black), - /// ) - /// ); - ///} - ///``` - TextStyle labelStyle; - - /// Hold the size of the label. - Size labelSize; - - /// Contains the text of the label. - String text; - - /// Contains the trimmed text of the label. - String? trimmedText; - - /// Contains the label text to be rendered. - String? renderText; - - /// Holds the value of the visible range of the axis. - num value; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AxisLabel && - other.labelStyle == labelStyle && - other.labelSize == labelSize && - other.text == text && - other.trimmedText == trimmedText && - other.renderText == renderText && - other.value == value; - } - - @override - int get hashCode { - final List values = [ - labelStyle, - labelSize, - text, - trimmedText, - renderText, - value - ]; - return hashList(values); - } - - List? _labelCollection; - - int _index = 1; - - /// Stores the location of an label. - Rect? _labelRegion; - - bool _needRender = true; -} - -/// This class Renders the major tick lines for axis. -/// -/// To render major grid lines, create an instance of [MajorTickLines], and assign it to the majorTickLines property of [ChartAxis]. -/// The Major tick lines can be drawn for each axis on the plot area. -/// -/// Provides options for [size], [width], and [color] to customize the appearance. -/// -class MajorTickLines { - /// Creating an argument constructor of MajorTickLines class. - const MajorTickLines({this.size = 5, this.width = 1, this.color}); - - /// Size of the major tick lines. - /// - /// Defaults to `8`. - /// - /// Size representation of the major ticks. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// majorTickLines: const MajorTickLines( - /// size: 6 - /// ) - /// ), - /// ) - /// ); - /// } - /// ``` - /// - final double size; - - /// Width of the major tick lines. - /// - /// Defaults to `1`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// majorTickLines: const MajorTickLines( - /// width: 2 - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final double width; - - /// Colors of the major tick lines. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// majorTickLines: const MajorTickLines( - /// color: Colors.black - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final Color? color; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MajorTickLines && - other.size == size && - other.width == width && - other.color == color; - } - - @override - int get hashCode { - final List values = [size, width, color]; - return hashList(values); - } -} - -/// This class has the properties of minor tick lines. -/// -/// To render minor grid lines, create an instance of [MinorTickLines], and assign it to the minorTickLines property of [ChartAxis]. -/// The Minor tick lines can be drawn for each axis on the plot area. -/// -/// Provides the color option to change the [color] of the tick line for the customization. -/// -@immutable -class MinorTickLines { - /// Creating an argument constructor of MinorTickLines class. - const MinorTickLines({this.size = 3, this.width = 0.7, this.color}); - - /// Height of the minor tick lines. - /// - /// Defaults to `3`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// minorTicksPerInterval: 5, - /// minorTickLines: MinorTickLines( - /// size: 5, - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final double size; - - /// Width of the minor tick lines. - /// - /// Defaults to `0.7`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// minorTicksPerInterval: 5, - /// minorTickLines: MinorTickLines( - /// width: 0.5, - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final double width; - - /// Color of the minor tick lines. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// minorTicksPerInterval: 5, - /// minorTickLines: MinorTickLines( - /// color:Colors.red, - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final Color? color; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MinorTickLines && - other.size == size && - other.width == width && - other.color == color; - } - - @override - int get hashCode { - final List values = [size, width, color]; - return hashList(values); - } -} - -/// Customizes the major grid lines. -/// -/// This class Renders the major grid lines for the axis. -/// -/// To render major grid lines, create an instance of [MajorGridLines], and assign it to the major gridLines property of [ChartAxis]. -/// Major grid lines can be drawn for each axis on the plot area. -/// -/// Provides options for [color], [width], and [dashArray] to customize the appearance. -/// -@immutable -class MajorGridLines { - /// Creating an argument constructor of MajorGridLines class. - const MajorGridLines({this.width = 0.7, this.color, this.dashArray}); - - /// Any number of values can be provided in the list. Odd value is considered as - /// rendering size and even value is considered as a gap. - /// - /// Defaults to `[0,0]`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// majorGridLines: MajorGridLines( - /// dashArray: [1,2] - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final List? dashArray; - - /// Width of the major grid lines. - /// - /// Defaults to `0.7`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// majorGridLines: MajorGridLines( - /// width:2 - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final double width; - - /// Color of the major grid lines. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// majorGridLines: MajorGridLines( - /// color:Colors.red - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final Color? color; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MajorGridLines && - other.dashArray == dashArray && - other.width == width && - other.color == color; - } - - @override - int get hashCode { - final List values = [dashArray, width, color]; - return hashList(values); - } -} - -/// Customizes the minor grid lines. -/// -/// To render minor grid lines, create an instance of [MinorGridLines], and assign it to the property of [ChartAxis]. -/// The Minor grid lines can be drawn for each axis on the plot area. -/// -/// Provides the options of [width], [color], and [dashArray] values to customize the appearance. -/// -class MinorGridLines { - /// Creating an argument constructor of MinorGridLines class. - const MinorGridLines({this.width = 0.5, this.color, this.dashArray}); - - /// Any number of values can be provided in the list. Odd value is considered - /// as rendering size and even value is considered as a gap. - /// - /// Defaults to `[0,0]`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// minorTicksPerInterval: 5, - /// minorGridLines: MinorGridLines( - /// dashArray: [10, 20], - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final List? dashArray; - - /// Width of the minor grid lines. - /// - /// Defaults to `0.5`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// minorTicksPerInterval: 5, - /// minorGridLines: MinorGridLines( - /// width: 0.7, - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final double width; - - /// Color of the minor grid lines. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// minorTicksPerInterval: 5, - /// minorGridLines: MinorGridLines( - /// color: Colors.red, - /// ), - /// ) - /// )); - ///} - ///``` - final Color? color; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MinorGridLines && - other.dashArray == dashArray && - other.width == width && - other.color == color; - } - - @override - int get hashCode { - final List values = [dashArray, width, color]; - return hashList(values); - } -} - -/// This class holds the property of the axis title. -/// -/// It has public properties to customize the text and font of the axis title. Axis does not display title by default. -/// Use of the property will customize the title. -/// -/// Provides text, text style, and text alignment options for customization of appearance. -/// -class AxisTitle { - /// Creating an argument constructor of AxisTitle class. - AxisTitle( - {this.text, TextStyle? textStyle, this.alignment = ChartAlignment.center}) - : textStyle = getTextStyle( - textStyle: textStyle, - fontFamily: 'Segoe UI', - fontSize: 15.0, - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal); - - /// Text to be displayed as axis title. - /// - /// Defaults to `‘’`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// title: AxisTitle( - /// text: 'Axis Title', - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final String? text; - - /// Customizes the appearance of text in axis title. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// title: AxisTitle( - /// text: 'Axis Title', - /// textStyle: TextStyle( - /// color: Colors.blue, - /// fontStyle: FontStyle.italic, - /// fontWeight: FontWeight.w600, - /// fontFamily: 'Roboto' - /// ) - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final TextStyle textStyle; - - /// Aligns the axis title. - /// - /// It is applicable for both vertical and horizontal axes. - /// - /// * `ChartAlignment.near`, moves the axis title to the beginning of the axis - /// - /// * `ChartAlignment.far`, moves the axis title to the end of the axis - /// - /// * `ChartAlignment.center`, moves the axis title to the center position of the axis. - /// - /// Defaults to `ChartAlignment.center`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// title: AxisTitle( - /// text: 'Axis Title', - /// alignment: ChartAlignment.far, - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final ChartAlignment alignment; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AxisTitle && - other.text == text && - other.textStyle == textStyle && - other.alignment == alignment; - } - - @override - int get hashCode { - final List values = [text, textStyle, alignment]; - return hashList(values); - } -} - -/// This class consists of axis line properties. -/// -/// It has the public properties to customize the axis line by increasing or decreasing the width of the axis line and -/// render the axis line with dashes by defining the [dashArray] value. -/// -/// Provides options for color, dash array, and width to customize the appearance of the axis line. -/// -class AxisLine { - /// Creating an argument constructor of AxisLine class. - const AxisLine({this.color, this.dashArray, this.width = 1}); - - /// Width of the axis line. - /// - /// Defaults to `1`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// axisLine:AxisLine( - /// width: 2 - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final double width; - - /// Color of the axis line. Color will be applied based on the brightness property of the app. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// axisLine:AxisLine( - /// color:Colors.blue, - /// ) - /// ), - /// )); - ///} - ///``` - 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. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// axisLine:AxisLine( - /// dashArray: [5,5], - /// ) - /// ), - /// ) - /// ); - ///} - ///``` - final List? dashArray; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AxisLine && - other.dashArray == dashArray && - other.width == width && - other.color == color; - } - - @override - int get hashCode { - final List values = [dashArray, width, color]; - return hashList(values); - } -} - -/// calculate visible range based on min, max values -class VisibleRange { - /// Creates an instance for visible range - VisibleRange(dynamic min, dynamic max) { - if ((min < max) == true) { - minimum = min; - maximum = max; - } else { - minimum = max; - maximum = min; - } - } - - /// specifies minimum range - dynamic minimum; - - /// Specifies maximum range - dynamic maximum; - - /// Specifies range interval - dynamic interval; - - /// Specifies delta value for min-max - late num delta; -} - -/// Creates an axis renderer for chart axis. -abstract class ChartAxisRenderer with CustomizeAxisElements { - /// Creating an argument constructor of ChartAxisRenderer class. - ChartAxisRenderer(); - - late ChartAxisRendererDetails _axisRendererDetails; - - @override - Color? getAxisLineColor(ChartAxis axis) => axis.axisLine.color; - @override - double getAxisLineWidth(ChartAxis axis) => axis.axisLine.width; - - @override - Color? getAxisMajorTickColor(ChartAxis axis, int majorTickIndex) => - axis.majorTickLines.color; - - @override - double getAxisMajorTickWidth(ChartAxis axis, int majorTickIndex) => - axis.majorTickLines.width; - - @override - Color? getAxisMinorTickColor( - ChartAxis axis, int majorTickIndex, int minorTickIndex) => - axis.minorTickLines.color; - - @override - double getAxisMinorTickWidth( - ChartAxis axis, int majorTickIndex, int minorTickIndex) => - axis.minorTickLines.width; - - @override - Color? getAxisMajorGridColor(ChartAxis axis, int majorGridIndex) => - axis.majorGridLines.color; - - @override - double getAxisMajorGridWidth(ChartAxis axis, int majorGridIndex) => - axis.majorGridLines.width; - - @override - Color? getAxisMinorGridColor( - ChartAxis axis, int majorGridIndex, int minorGridIndex) => - axis.minorGridLines.color; - @override - double getAxisMinorGridWidth( - ChartAxis axis, int majorGridIndex, int minorGridIndex) => - axis.minorGridLines.width; - - @override - String getAxisLabel(ChartAxis axis, String text, int labelIndex) => text; - - @override - TextStyle getAxisLabelStyle(ChartAxis axis, String text, int labelIndex) => - axis.labelStyle; - - /// It returns the axis label angle - @override - int getAxisLabelAngle( - ChartAxisRenderer axisRenderer, String text, int labelIndex) => - axisRenderer._axisRendererDetails - .getAxisLabelAngle(axisRenderer, text, labelIndex); - - /// To draw the horizontal axis line - @override - void drawHorizontalAxesLine( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - axisRenderer._axisRendererDetails - .drawHorizontalAxesLine(canvas, axisRenderer, chart); - } - - /// To draw the vertical axis line - @override - void drawVerticalAxesLine( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - axisRenderer._axisRendererDetails - .drawVerticalAxesLine(canvas, axisRenderer, chart); - } - - /// To draw the horizontal axes tick lines - @override - void drawHorizontalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - axisRenderer._axisRendererDetails.drawHorizontalAxesTickLines( - canvas, - axisRenderer, - chart, - renderType, - animationFactor, - oldAxisRenderer, - needAnimate); - } - - /// To draw the major grid lines of horizontal axes - @override - void drawHorizontalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart) { - axisRenderer._axisRendererDetails.drawHorizontalAxesMajorGridLines( - canvas, point, axisRenderer, grids, index, chart); - } - - /// To draw the minor grid lines of horizontal axes - @override - void drawHorizontalAxesMinorLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - double tempInterval, - Rect rect, - num nextValue, - int index, - SfCartesianChart chart, - [String? renderType]) { - axisRenderer._axisRendererDetails.drawHorizontalAxesMinorLines(canvas, - axisRenderer, tempInterval, rect, nextValue, index, chart, renderType); - } - - /// To draw tick lines of vertical axes - @override - void drawVerticalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - axisRenderer._axisRendererDetails._drawVerticalAxesTickLines( - canvas, - axisRenderer, - chart, - renderType, - animationFactor, - oldAxisRenderer, - needAnimate); - } - - /// To draw the major grid lines of vertical axes - @override - void drawVerticalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart) { - axisRenderer._axisRendererDetails._drawVerticalAxesMajorGridLines( - canvas, point, axisRenderer, grids, index, chart); - } - - /// To draw the minor grid lines of vertical axes - @override - void drawVerticalAxesMinorTickLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - num tempInterval, - Rect rect, - int index, - SfCartesianChart chart, - [String? renderType]) { - axisRenderer._axisRendererDetails._drawVerticalAxesMinorTickLines( - canvas, axisRenderer, tempInterval, rect, index, chart, renderType); - } - - /// To draw the axis labels of horizontal axes - @override - void drawHorizontalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - axisRenderer._axisRendererDetails._drawHorizontalAxesLabels( - canvas, - axisRenderer, - chart, - renderType, - animationFactor, - oldAxisRenderer, - needAnimate); - } - - /// To draw the axis labels of vertical axes - @override - void drawVerticalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - axisRenderer._axisRendererDetails._drawVerticalAxesLabels( - canvas, - axisRenderer, - chart, - renderType, - animationFactor, - oldAxisRenderer, - needAnimate); - } - - /// To draw the axis title of horizontal axes - @override - void drawHorizontalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - axisRenderer._axisRendererDetails - ._drawHorizontalAxesTitle(canvas, axisRenderer, chart); - } - - /// To draw the axis title of vertical axes - @override - void drawVerticalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - axisRenderer._axisRendererDetails - ._drawVerticalAxesTitle(canvas, axisRenderer, chart); - } - - /// returns the calculated interval for axis - num? calculateInterval(VisibleRange range, Size availableSize); - - /// to apply the range padding for the axis - void applyRangePadding(VisibleRange range, num interval); - - /// calculates the visible range of the axis - void calculateVisibleRange(Size availableSize); - - /// this method generates the visible labels for the specific axis - void generateVisibleLabels(); - - /// To calculate the range points - void calculateRange(ChartAxisRenderer _axisRenderer) { - _axisRenderer._axisRendererDetails._calculateRange(_axisRenderer); - } - - /// To dispose the objects. - void dispose() { - _axisRendererDetails.dispose(); - } -} - -/// Represents the class that holds the chart axis rendering details -class ChartAxisRendererDetails { - /// Creates an instance of chart axis renderer details - ChartAxisRendererDetails(this.axis, this.stateProperties, this.axisRenderer) { - visibleLabels = []; - visibleAxisMultiLevelLabels = []; - maximumLabelSize = Size.zero; - multiLevelLabelTotalSize = Size.zero; - seriesRenderers = []; - name = axis.name; - labelRotation = axis.labelRotation; - zoomFactor = axis.zoomFactor; - zoomPosition = axis.zoomPosition; - } - - /// Represents the chart axis renderer - late ChartAxisRenderer axisRenderer; - - /// Represents the chart axis value - late ChartAxis axis; - - /// Specifies the old axis value - ChartAxis? oldAxis; - - /// Specifies the value of state properties - final CartesianStateProperties stateProperties; - - /// Specifies the rendering details value - RenderingDetails get renderingDetails => stateProperties.renderingDetails; - - /// Specifies the cartesian chart - late SfCartesianChart chart; - - /// Specifies whether the series is stack100 - bool isStack100 = false; - - /// Specifies the value of range minimum and range maximum - dynamic rangeMinimum, rangeMaximum; - - /// Specifies the value of visible labels - late List visibleLabels; - - /// Specifies the value of visible labels - late List visibleAxisMultiLevelLabels; - - /// Stores each level's maximum size - List multiLevelsMaximumSize = []; - - /// Holds the size of larger label - Size maximumLabelSize = Size.zero; - - /// Holds the total size of the multilevel label - Size multiLevelLabelTotalSize = Size.zero; - - /// Holds the end point value of the axis border - double? axisBorderEnd; - - /// Holds the highest level value of multilevel labels - int? highestLevel; - - /// Specifies axis orientations such as vertical, horizontal. - AxisOrientation? orientation; - - /// Specifies the visible range based on min, max values. - VisibleRange? visibleRange; - - /// Specifies the actual range based on min, max values. - VisibleRange? actualRange; - - /// Specifies the minimum and maximum for multi level labels - num? minimumMultiLevelLabelValue, maximumMultiLevelLabelValue; - - /// To express the multi level label is enabled or not - bool isMultiLevelLabelEnabled = false; - - /// Holds the chart series - late List seriesRenderers; - - /// Specifies the value of bounds - // ignore: prefer_final_fields - Rect bounds = Rect.zero; - - /// Specifies whether the ticks are placed inside - bool? isInsideTickPosition; - - /// Specifies the total size value - late double totalSize; - - /// Specifies the p - double? previousZoomFactor, previousZoomPosition; - - /// Internal variables - String? name; - - /// Holds the value of label rotation - late int labelRotation; - - /// Holds the value of zoom factor and zoom position - late double zoomFactor, zoomPosition; - - /// Checking the axis label collision - bool isCollide = false; - - /// Holds the value of minimum, maximum, lowest and highest minimum and maximum value - num? min, max, lowMin, lowMax, highMin, highMax; - - /// Holds the value of axis size - late Size axisSize; - - /// Holds the value of cross axis renderer - late ChartAxisRenderer crossAxisRenderer; - - /// Specifies the cross value - num? crossValue; - - /// Specifies the cross range - VisibleRange? crossRange; - - /// Holds the label offset value - double? labelOffset; - - /// Holds the axis title offset value - double? titleOffset; - - /// Holds the axis title height - double? titleHeight; - - /// Specifies the x-axis start and x-axis end - Offset? xAxisStart, xAxisEnd; - - /// Specifies the value of visible minimum and visible maximum - num? visibleMinimum, visibleMaximum; - - /// Specifies the scrolling delta value - int? scrollingDelta; - - /// Returns the axis label angle - int getAxisLabelAngle( - ChartAxisRenderer axisRenderer, String text, int labelIndex) { - final int labelAngle = - (axis.labelIntersectAction == AxisLabelIntersectAction.rotate45 && - isCollide) - ? -45 - : (axis.labelIntersectAction == AxisLabelIntersectAction.rotate90 && - isCollide) - ? -90 - : labelRotation; - - return labelAngle; - } - - /// Method to draw the horizontal axes line - void drawHorizontalAxesLine( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final Rect rect = Rect.fromLTWH(bounds.left - axis.plotOffset, bounds.top, - bounds.width + 2 * axis.plotOffset, bounds.height); - - final CustomPaintStyle paintStyle = CustomPaintStyle( - axisRenderer.getAxisLineWidth(axis), - axisRenderer.getAxisLineColor(axis) ?? - renderingDetails.chartTheme.axisLineColor, - PaintingStyle.stroke); - drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), - Offset(rect.left + rect.width, rect.top), axis.axisLine.dashArray); - xAxisStart = Offset(rect.left, rect.top); - xAxisEnd = Offset(rect.left + rect.width, rect.top); - } - - /// Method to draw the vertical axes line - void drawVerticalAxesLine( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final Rect rect = Rect.fromLTWH(bounds.left, bounds.top - axis.plotOffset, - bounds.width, bounds.height + 2 * axis.plotOffset); - final CustomPaintStyle paintStyle = CustomPaintStyle( - axisRenderer.getAxisLineWidth(axis), - axisRenderer.getAxisLineColor(axis) ?? - renderingDetails.chartTheme.axisLineColor, - PaintingStyle.stroke); - drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), - Offset(rect.left, rect.top + rect.height), axis.axisLine.dashArray); - } - - /// Method to draw the horizontal axes tick lines - void drawHorizontalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - final dynamic chartAxis = axis; - double betweenTickPositionValue = 0.0; - late double tempInterval, pointX, pointY; - int length = visibleLabels.length; - if (length > 0) { - final MajorTickLines ticks = axis.majorTickLines; - final bool isBetweenTicks = - (axis is CategoryAxis || axis is DateTimeCategoryAxis) && - chartAxis.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 + visibleRange!.interval) - - tickBetweenLabel - : visibleLabels[i].value) - .toDouble(); - pointX = ((valueToCoefficient(tempInterval, this) * bounds.width) + - bounds.left) - .roundToDouble(); - pointY = bounds.top; - if (needAnimate!) { - final double? oldLocation = - _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval); - pointX = oldLocation != null - ? (oldLocation - (oldLocation - pointX) * animationFactor!) - : pointX; - } - if (bounds.left.roundToDouble() <= pointX && - bounds.right.roundToDouble() >= pointX) { - if ((axis.majorGridLines.width > 0) == true && - renderType == 'outside' && - ((axis.plotOffset > 0) == true || - (i != 0 && - (isBetweenTicks ? i != length - 1 : i != length)) || - (bounds.left <= pointX && - bounds.right >= pointX && - !stateProperties.requireInvertedAxis))) { - axisRenderer.drawHorizontalAxesMajorGridLines( - canvas, - Offset(pointX, pointY), - axisRenderer, - axis.majorGridLines, - i, - chart); - } - if ((axis.minorGridLines.width > 0) == true || - (axis.minorTickLines.width > 0) == true) { - num? nextValue = isBetweenTicks - ? (tempInterval + visibleRange!.interval) - : i == length - 1 - ? visibleRange!.maximum - : visibleLabels[i + 1].value; - if (nextValue != null) { - nextValue = - ((valueToCoefficient(nextValue, this) * bounds.width) + - bounds.left) - .roundToDouble(); - axisRenderer.drawHorizontalAxesMinorLines(canvas, axisRenderer, - pointX, bounds, nextValue, i, chart, renderType); - } - } - } - if (axis.majorTickLines.width > 0 == true && - (bounds.left <= pointX && bounds.right.roundToDouble() >= pointX) && - renderType == axis.tickPosition.toString().split('.')[1]) { - drawDashedPath( - canvas, - CustomPaintStyle( - axisRenderer.getAxisMajorTickWidth(axis, i), - axisRenderer.getAxisMajorTickColor(axis, i) ?? - renderingDetails.chartTheme.majorTickLineColor, - PaintingStyle.stroke), - Offset(pointX, pointY), - Offset( - pointX, - axis.opposedPosition == false - ? (isInsideTickPosition! - ? pointY - ticks.size - : pointY + ticks.size) - : (isInsideTickPosition! - ? pointY + ticks.size - : pointY - ticks.size))); - } - if ((axis.borderWidth > 0 || isMultiLevelLabelEnabled) && i == 0) { - setAxisBorderEndPoint(this, pointY); - } - if (axis.borderWidth > 0) { - if (i == 0 && !isBetweenTicks && visibleLabels.length > 1) { - final num nextPoint = - (valueToCoefficient(visibleLabels[i + 1].value, this) * - bounds.width) + - bounds.left; - betweenTickPositionValue = (nextPoint.toDouble() - pointX) / 2; - } - renderHorizontalAxisBorder(this, canvas, pointX, pointY, - isBetweenTicks, betweenTickPositionValue, i); - } - } - } - } - - /// To change chart based on range controller - void updateRangeControllerValues(ChartAxisRendererDetails _axisRenderer) { - stateProperties.zoomProgress = false; - stateProperties.isRedrawByZoomPan = false; - if (_axisRenderer is DateTimeAxisRenderer || - _axisRenderer is DateTimeCategoryAxisRenderer) { - _axisRenderer.rangeMinimum = - axis.rangeController!.start.millisecondsSinceEpoch; - _axisRenderer.rangeMaximum = - axis.rangeController!.end.millisecondsSinceEpoch; - } else { - _axisRenderer.rangeMinimum = axis.rangeController!.start; - _axisRenderer.rangeMaximum = axis.rangeController!.end; - } - } - - /// Auto scrolling feature - void updateAutoScrollingDelta( - int scrollingDelta, ChartAxisRenderer axisRenderer) { - this.scrollingDelta = scrollingDelta; - switch (axis.autoScrollingMode) { - case AutoScrollingMode.start: - final VisibleRange autoScrollRange = VisibleRange( - visibleRange!.minimum, visibleRange!.minimum + scrollingDelta); - autoScrollRange.delta = - autoScrollRange.maximum - autoScrollRange.minimum; - zoomFactor = autoScrollRange.delta / actualRange!.delta; - zoomPosition = 0; - break; - case AutoScrollingMode.end: - final VisibleRange autoScrollRange = VisibleRange( - visibleRange!.maximum - scrollingDelta, visibleRange!.maximum); - autoScrollRange.delta = - autoScrollRange.maximum - autoScrollRange.minimum; - zoomFactor = autoScrollRange.delta / actualRange!.delta; - zoomPosition = 1 - zoomFactor; - break; - } - } - - /// Method to draw the horizontal axes major grid lines - void drawHorizontalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart) { - final CustomPaintStyle paintStyle = CustomPaintStyle( - axisRenderer.getAxisMajorGridWidth(axis, index), - axisRenderer.getAxisMajorGridColor(axis, index) ?? - renderingDetails.chartTheme.majorGridLineColor, - PaintingStyle.stroke); - drawDashedPath( - canvas, - paintStyle, - Offset(point.dx, stateProperties.chartAxis.axisClipRect.top), - Offset( - point.dx, - stateProperties.chartAxis.axisClipRect.top + - stateProperties.chartAxis.axisClipRect.height), - grids.dashArray); - } - - /// Method to draw the horizontal axes minor lines - void drawHorizontalAxesMinorLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - double tempInterval, - Rect rect, - num nextValue, - int index, - SfCartesianChart chart, - [String? renderType]) { - double position = tempInterval; - final MinorTickLines ticks = axis.minorTickLines; - final num interval = - (tempInterval - nextValue).abs() / (axis.minorTicksPerInterval + 1); - for (int i = 0; i < axis.minorTicksPerInterval; i++) { - position = - axis.isInversed ? (position - interval) : (position + interval); - final double pointY = rect.top; - if (axis.minorGridLines.width > 0 && - renderType == 'outside' && - (bounds.left <= position && bounds.right >= position)) { - drawDashedPath( - canvas, - CustomPaintStyle( - axisRenderer.getAxisMinorGridWidth(axis, index, i), - axisRenderer.getAxisMinorGridColor(axis, index, i) ?? - renderingDetails.chartTheme.minorGridLineColor, - PaintingStyle.stroke), - Offset(position, stateProperties.chartAxis.axisClipRect.top), - Offset( - position, - stateProperties.chartAxis.axisClipRect.top + - stateProperties.chartAxis.axisClipRect.height), - axis.minorGridLines.dashArray); - } - - if (axis.minorTickLines.width > 0 && - bounds.left <= position && - bounds.right >= position && - renderType == axis.tickPosition.toString().split('.')[1]) { - drawDashedPath( - canvas, - CustomPaintStyle( - axisRenderer.getAxisMinorTickWidth(axis, index, i), - axisRenderer.getAxisMinorTickColor(axis, index, i) ?? - renderingDetails.chartTheme.minorTickLineColor, - PaintingStyle.stroke), - Offset(position, pointY), - Offset( - position, - !axis.opposedPosition - ? (isInsideTickPosition! - ? pointY - ticks.size - : pointY + ticks.size) - : (isInsideTickPosition! - ? pointY + ticks.size - : pointY - ticks.size)), - axis.minorGridLines.dashArray); - } - } - } - - /// To find the height of the current label - double _findMultiRows(int length, num currentX, AxisLabel currentLabel, - ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - AxisLabel label; - num pointX; - final List labelIndex = []; - bool isMultiRows; - for (int i = length - 1; i >= 0; i--) { - label = visibleLabels[i]; - pointX = (valueToCoefficient(label.value, this) * - stateProperties.chartAxis.axisClipRect.width) + - stateProperties.chartAxis.axisClipRect.left; - isMultiRows = !axis.isInversed - ? currentX < (pointX + label.labelSize.width / 2) - : currentX + currentLabel.labelSize.width > - (pointX - label.labelSize.width / 2); - if (isMultiRows) { - labelIndex.add(label._index); - currentLabel._index = (currentLabel._index > label._index) - ? currentLabel._index - : label._index + 1; - } else { - currentLabel._index = labelIndex.contains(label._index) - ? currentLabel._index - : label._index; - } - } - return currentLabel.labelSize.height * currentLabel._index; - } - - /// To get the label collection - List _gettingLabelCollection( - String currentLabel, num labelsExtent, ChartAxisRenderer axisRenderer) { - final List textCollection = currentLabel.split(RegExp(' ')); - final List labelCollection = []; - String text; - for (int i = 0; i < textCollection.length; i++) { - text = textCollection[i]; - (measureText(text, axis.labelStyle, labelRotation).width < labelsExtent) - ? labelCollection.add(text) - : labelCollection.add(getTrimmedText( - text, labelsExtent, axis.labelStyle, - axisRenderer: axisRenderer, - isRtl: axisRenderer._axisRendererDetails.stateProperties - .renderingDetails.isRtl)); - } - return labelCollection; - } - - /// Below method is for changing range while zooming - void calculateZoomRange(ChartAxisRenderer axisRenderer, Size axisSize) { - ChartAxisRenderer? oldAxisRenderer; - assert(axis.zoomFactor >= 0 && axis.zoomFactor <= 1, - 'The zoom factor of the axis should be between 0 and 1.'); - 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 - zoomFactor = zoomFactor > 1 - ? 1 - : zoomFactor < 0 - ? 0 - : zoomFactor; - zoomPosition = zoomPosition > 1 - ? 1 - : zoomPosition < 0 - ? 0 - : zoomPosition; - if (stateProperties.oldAxisRenderers.isNotEmpty) { - oldAxisRenderer = - getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); - } - if (oldAxisRenderer != null) { - zoomFactor = oldAxisRenderer._axisRendererDetails.axis.zoomFactor != - axis.zoomFactor - ? axis.zoomFactor - : zoomFactor; - zoomPosition = oldAxisRenderer._axisRendererDetails.axis.zoomPosition != - axis.zoomPosition - ? axis.zoomPosition - : zoomPosition; - if (axis.autoScrollingDelta == - oldAxisRenderer._axisRendererDetails.axis.autoScrollingDelta) { - scrollingDelta = oldAxisRenderer._axisRendererDetails.scrollingDelta; - } - } - - final VisibleRange baseRange = visibleRange!; - num start, end; - start = visibleRange!.minimum + zoomPosition * visibleRange!.delta; - end = start + zoomFactor * visibleRange!.delta; - - if (start < baseRange.minimum) { - end = end + (baseRange.minimum - start); - start = baseRange.minimum; - } - if (end > baseRange.maximum) { - start = start - (end - baseRange.maximum); - end = baseRange.maximum; - } - visibleRange!.minimum = start; - visibleRange!.maximum = end; - visibleRange!.delta = end - start; - } - - /// To set the zoom factor and position of axis through dynamic update or from - void setZoomFactorAndPosition(ChartAxisRenderer axisRenderer, - List axisRendererStates) { - 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 ChartAxisRenderer zoomedAxisRenderer - in stateProperties.zoomedAxisRendererStates) { - if (zoomedAxisRenderer._axisRendererDetails.name == name) { - if (didUpdateAxis) { - zoomedAxisRenderer._axisRendererDetails.zoomFactor = zoomFactor; - zoomedAxisRenderer._axisRendererDetails.zoomPosition = zoomPosition; - } else { - if (axis.autoScrollingDelta == null || - scrollingDelta != - zoomedAxisRenderer._axisRendererDetails.visibleRange!.delta) { - zoomFactor = zoomedAxisRenderer._axisRendererDetails.zoomFactor; - zoomPosition = zoomedAxisRenderer._axisRendererDetails.zoomPosition; - } - } - break; - } - } - } - - /// To provide chart changes to range controller - void setRangeControllerValues(ChartAxisRenderer _axisRenderer) { - if (_axisRenderer is DateTimeAxisRenderer || - _axisRenderer is DateTimeCategoryAxisRenderer) { - axis.rangeController!.start = DateTime.fromMillisecondsSinceEpoch( - _axisRenderer._axisRendererDetails.visibleRange!.minimum.toInt()); - axis.rangeController!.end = DateTime.fromMillisecondsSinceEpoch( - _axisRenderer._axisRendererDetails.visibleRange!.maximum.toInt()); - } else { - axis.rangeController!.start = - _axisRenderer._axisRendererDetails.visibleRange!.minimum; - axis.rangeController!.end = - _axisRenderer._axisRendererDetails.visibleRange!.maximum; - } - } - - /// Method to set zoom values from the range controller - void setZoomValuesFromRangeController() { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (!(stateProperties.isRedrawByZoomPan || - stateProperties.canSetRangeController)) { - if (stateProperties.rangeChangeBySlider && - !stateProperties.canSetRangeController && - rangeMinimum != null && - rangeMaximum != null) { - visibleRange!.delta = visibleRange!.maximum - visibleRange!.minimum; - if (this is! DateTimeCategoryAxisRenderer) { - visibleRange!.interval = axisRenderer is LogarithmicAxisRenderer - ? (axisDetails.axisRenderer as LogarithmicAxisRenderer) - .calculateLogNiceInterval(visibleRange!.delta) - : axisRenderer.calculateInterval(visibleRange!, axisSize); - } - visibleRange!.interval = - actualRange!.interval != null && actualRange!.interval % 1 != 0 - ? actualRange!.interval - : visibleRange!.interval; - zoomFactor = visibleRange!.delta / (actualRange!.delta); - zoomPosition = - (visibleRange!.minimum - actualRange!.minimum) / actualRange!.delta; - } - } - } - - /// Method to set the old range from range controller - void setOldRangeFromRangeController() { - if (!stateProperties.renderingDetails.initialRender! && - axis.rangeController != null && - !stateProperties.canSetRangeController) { - final ChartAxisRenderer? oldrenderer = - getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); - if (oldrenderer != null) { - final ChartAxisRendererDetails axisRendererDetails = - oldrenderer._axisRendererDetails; - visibleMinimum = rangeMinimum = - axisRendererDetails.rangeMinimum is DateTime - ? axisRendererDetails.rangeMinimum.millisecondsSinceEpoch - : axisRendererDetails.rangeMinimum; - visibleMaximum = rangeMaximum = - axisRendererDetails.rangeMaximum is DateTime - ? axisRendererDetails.rangeMaximum.millisecondsSinceEpoch - : axisRendererDetails.rangeMaximum; - } - } - } - - /// To get the previous location of an axis - double? _getPrevLocation(ChartAxisRenderer axisRenderer, - ChartAxisRenderer oldAxisRenderer, num value, - [Size? textSize, num? angle]) { - double? location; - final Rect bounds = axisRenderer._axisRendererDetails.bounds; - final ChartAxis axis = axisRenderer._axisRendererDetails.axis; - textSize ??= Size.zero; - if (oldAxisRenderer._axisRendererDetails.visibleRange!.minimum > value == - false) { - location = axisRenderer._axisRendererDetails.orientation == - AxisOrientation.vertical - ? (axis.isInversed - ? ((bounds.top + bounds.height) - - ((bounds.top - - (bounds.top - - (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * - bounds.height)) - .roundToDouble()) - .abs())) - : (bounds.bottom - - (valueToCoefficient( - value, oldAxisRenderer._axisRendererDetails) * - bounds.height)) - .roundToDouble()) - : (axis.isInversed - ? ((valueToCoefficient(value, axisRenderer._axisRendererDetails) * - bounds.width) + - bounds.right) - .roundToDouble() - : ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * - bounds.width) - - bounds.left) - .roundToDouble()); - } else if (oldAxisRenderer._axisRendererDetails.visibleRange!.maximum < - value == - false) { - location = axisRenderer._axisRendererDetails.orientation == - AxisOrientation.vertical - ? (axis.isInversed - ? (bounds.bottom - - (valueToCoefficient( - value, oldAxisRenderer._axisRendererDetails) * - bounds.height)) - .roundToDouble() - : ((bounds.top + bounds.height) - - ((bounds.top - - (bounds.top - - (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * - bounds.height)) - .roundToDouble()) - .abs()))) - : (axis.isInversed - ? ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * - bounds.width) - - bounds.left) - .roundToDouble() - : ((valueToCoefficient(value, axisRenderer._axisRendererDetails) * - bounds.width) + - bounds.right) - .roundToDouble()); - } else { - if (axisRenderer._axisRendererDetails.orientation == - AxisOrientation.vertical) { - location = - (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * - oldAxisRenderer._axisRendererDetails.bounds.height) + - oldAxisRenderer._axisRendererDetails.bounds.top; - location = ((oldAxisRenderer._axisRendererDetails.bounds.top + - oldAxisRenderer._axisRendererDetails.bounds.height) - - ((oldAxisRenderer._axisRendererDetails.bounds.top - location) - .abs())) - - textSize.height / 2; - } else { - location = - ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * - oldAxisRenderer._axisRendererDetails.bounds.width) + - oldAxisRenderer._axisRendererDetails.bounds.left) - .roundToDouble(); - if (angle != null) { - location -= angle == 0 ? textSize.width / 2 : 0; - } - } - } - return location; - } - - /// Return the x point - double _getPointX( - ChartAxisRenderer axisRenderer, Size textSize, Rect axisBounds) { - late double pointX; - const num innerPadding = 5; - final ChartAxisRendererDetails axisRendererDetails = - axisRenderer._axisRendererDetails; - final ChartAxis axis = axisRendererDetails.axis; - if (axis.borderWidth > 0 || axisRendererDetails.isMultiLevelLabelEnabled) { - pointX = getLabelOffsetX(axisRendererDetails, textSize); - } else { - if (axis.labelPosition == ChartDataLabelPosition.inside) { - pointX = (!axis.opposedPosition) - ? axisRendererDetails.labelOffset ?? - (axisBounds.left + - innerPadding + - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0)) - : axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - textSize.width - : (axisBounds.left - - axisRendererDetails.maximumLabelSize.width - - innerPadding - - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0)); - } else { - pointX = ((!axis.opposedPosition) - ? axisRendererDetails.labelOffset != null - ? axisRenderer._axisRendererDetails.labelOffset! - - textSize.width - : (axisBounds.left - - (axisRendererDetails.isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) - - textSize.width - - innerPadding) - : (axisRendererDetails.labelOffset ?? - (axisBounds.left + - (axisRendererDetails.isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding))) - .toDouble(); - } - } - return pointX; - } - - /// Return the y point - double _getPointY( - ChartAxisRenderer axisRenderer, AxisLabel label, Rect axisBounds) { - final ChartAxisRendererDetails axisRendererDetails = - axisRenderer._axisRendererDetails; - final ChartAxis axis = axisRendererDetails.axis; - double pointY; - const num innerPadding = 3; - if (axis.borderWidth > 0 || axisRendererDetails.isMultiLevelLabelEnabled) { - pointY = getLabelOffsetY(this, label._index); - } else { - if (axis.labelPosition == ChartDataLabelPosition.inside) { - pointY = axis.opposedPosition == false - ? axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - - axisRendererDetails.maximumLabelSize.height - : axisBounds.top - - innerPadding - - (label._index > 1 - ? axisRendererDetails.maximumLabelSize.height / 2 - : axisRendererDetails.maximumLabelSize.height) - - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0) - : axisRendererDetails.labelOffset ?? - axisBounds.top + - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0) + - (label._index > 1 - ? axisRendererDetails.maximumLabelSize.height / 2 - : 0); - } else { - pointY = (axis.opposedPosition == false - ? axisRendererDetails.labelOffset ?? - (axisBounds.top + - ((axisRendererDetails.isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding) + - (label._index > 1 - ? axisRendererDetails.maximumLabelSize.height / 2 - : 0)) - : axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - - axisRendererDetails.maximumLabelSize.height - : (axisBounds.top - - (((axisRendererDetails.isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding) - - (label._index > 1 - ? axisRendererDetails.maximumLabelSize.height / - 2 - : 0)) - - axisRendererDetails.maximumLabelSize.height)) - .toDouble(); - } - } - return pointY; - } - - /// To get shifted position for both axes - double _getShiftedPosition(ChartAxisRenderer axisRenderer, Rect axisBounds, - double pointX, double pointY, Size textSize, int i) { - final ChartAxis axis = axisRenderer._axisRendererDetails.axis; - if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 && - ((pointX < axisBounds.left && !axis.isInversed) || - (pointX + textSize.width > axisBounds.right && - axis.isInversed))) { - pointX = axis.isInversed - ? axisBounds.left + axisBounds.width - textSize.width - : axisBounds.left; - } - - if (i == axisRenderer._axisRendererDetails.visibleLabels.length - 1 && - ((((pointX + textSize.width) > axisBounds.right) && - !axis.isInversed) || - (pointX < axisBounds.left && axis.isInversed))) { - pointX = axis.isInversed - ? axisBounds.left - : axisBounds.left + axisBounds.width - textSize.width; - } - } else if ((axis.labelAlignment == LabelAlignment.end) && - (i == axisRenderer._axisRendererDetails.visibleLabels.length - 1 && - ((((pointX + textSize.width) > axisBounds.right) && - !axis.isInversed) || - (pointX < axisBounds.left && axis.isInversed)))) { - pointX = axis.isInversed - ? axisBounds.left - : axisBounds.left + - axisBounds.width - - textSize.width - - textSize.height / 2; - } else if ((axis.labelAlignment == LabelAlignment.start) && - (i == 0 && - ((pointX < axisBounds.left && !axis.isInversed) || - (pointX + textSize.width > axisBounds.right && - axis.isInversed)))) { - pointX = axis.isInversed - ? axisBounds.left + axisBounds.width - textSize.width - : axisBounds.left + textSize.height / 2; - } - } - - final Rect currentRegion = axis.labelRotation == 0 && - !(axis.labelIntersectAction == AxisLabelIntersectAction.rotate45 || - axis.labelIntersectAction == AxisLabelIntersectAction.rotate90) - ? Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height) - : Rect.fromLTWH(pointX - textSize.width / 2, - pointY - textSize.height / 2, textSize.width, textSize.height); - final bool isIntersect = i > 0 && - i < axisRenderer._axisRendererDetails.visibleLabels.length - 1 && - axis.labelIntersectAction == AxisLabelIntersectAction.hide && - axis.labelRotation % 180 == 0 && - axisRenderer._axisRendererDetails.visibleLabels[i - 1]._labelRegion != - null && - (axisRenderer._axisRendererDetails.axis.isInversed == false - ? currentRegion.left < - axisRenderer._axisRendererDetails.visibleLabels[i - 1] - ._labelRegion!.right - : currentRegion.right > - axisRenderer._axisRendererDetails.visibleLabels[i - 1] - ._labelRegion!.left); - axisRenderer._axisRendererDetails.visibleLabels[i]._labelRegion = - !isIntersect ? currentRegion : null; - return pointX; - } - - void _drawVerticalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - final dynamic axis = axisRenderer._axisRendererDetails.axis; - final Rect axisBounds = axisRenderer._axisRendererDetails.bounds; - double betweenTicksPointValue = 0.0; - final List visibleLabels = - axisRenderer._axisRendererDetails.visibleLabels; - double tempInterval, pointX, pointY; - int length = visibleLabels.length; - 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 - ._axisRendererDetails.visibleRange!.interval) - - tickBetweenLabel - : visibleLabels[i].value) - .toDouble(); - pointY = - (valueToCoefficient(tempInterval, axisRenderer._axisRendererDetails) * - axisBounds.height) + - axisBounds.top; - pointY = (axisBounds.top + axisBounds.height) - - (pointY - axisBounds.top).abs(); - pointX = axisBounds.left; - - if (needAnimate!) { - final double? oldLocation = - _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval); - pointY = oldLocation != null - ? (oldLocation - (oldLocation - pointY) * animationFactor!) - : pointY; - } - if (pointY >= axisBounds.top && pointY <= axisBounds.bottom) { - if (axis.majorGridLines.width > 0 == true && - renderType == 'outside' && - (axis.plotOffset > 0 == true || - ((i == 0 || i == length - 1) && - chart.plotAreaBorderWidth == 0) || - (((i == 0 && axis.opposedPosition == false) || - (i == length - 1 && axis.opposedPosition == true)) && - axis.axisLine.width == 0) || - (axisBounds.top < pointY - axis.majorGridLines.width && - axisBounds.bottom > pointY + axis.majorGridLines.width))) { - axisRenderer.drawVerticalAxesMajorGridLines( - canvas, - Offset(pointX, pointY), - axisRenderer, - axis.majorGridLines, - i, - chart); - } - if (axis.minorGridLines.width > 0 == true || - axis.minorTickLines.width > 0 == true) { - axisRenderer.drawVerticalAxesMinorTickLines(canvas, axisRenderer, - tempInterval, axisBounds, i, chart, renderType!); - } - if (axis.majorTickLines.width > 0 == true && - renderType == axis.tickPosition.toString().split('.')[1]) { - drawDashedPath( - canvas, - CustomPaintStyle( - axisRenderer.getAxisMajorTickWidth(axis, i), - axisRenderer.getAxisMajorTickColor(axis, i) ?? - renderingDetails.chartTheme.majorTickLineColor, - PaintingStyle.stroke), - Offset(pointX, pointY), - Offset( - axis.opposedPosition == false - ? (axisRenderer._axisRendererDetails.isInsideTickPosition! - ? pointX + axis.majorTickLines.size - : pointX - axis.majorTickLines.size) - : (axisRenderer._axisRendererDetails.isInsideTickPosition! - ? pointX - axis.majorTickLines.size - : pointX + axis.majorTickLines.size), - pointY)); - } - } - if ((axis.borderWidth > 0 == true || isMultiLevelLabelEnabled) && - i == 0) { - setAxisBorderEndPoint(this, pointX); - } - if (axis.borderWidth > 0 == true) { - if (i == 0 && !isBetweenTicks && visibleLabels.length > 1) { - num nextPoint = - (valueToCoefficient(visibleLabels[i + 1].value, this) * - bounds.height) + - bounds.top; - nextPoint = (axisBounds.top + axisBounds.height) - - (nextPoint - axisBounds.top).abs(); - betweenTicksPointValue = (nextPoint.toDouble() - pointY) / 2; - } - renderVerticalAxisBorder(this, canvas, pointX, pointY, isBetweenTicks, - betweenTicksPointValue, i); - } - } - } - - /// Method to draw the vertical axes major grid line - void _drawVerticalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart) { - final CustomPaintStyle paintStyle = CustomPaintStyle( - axisRenderer.getAxisMajorGridWidth( - axisRenderer._axisRendererDetails.axis, index), - axisRenderer.getAxisMajorGridColor( - axisRenderer._axisRendererDetails.axis, index) ?? - renderingDetails.chartTheme.majorGridLineColor, - PaintingStyle.stroke); - if (stateProperties.chartAxis.primaryXAxisRenderer!._axisRendererDetails - .xAxisStart != - Offset(stateProperties.chartAxis.axisClipRect.left, point.dy) && - stateProperties.chartAxis.primaryXAxisRenderer!._axisRendererDetails - .xAxisEnd != - Offset( - stateProperties.chartAxis.axisClipRect.left + - stateProperties.chartAxis.axisClipRect.width, - point.dy)) { - drawDashedPath( - canvas, - paintStyle, - Offset(stateProperties.chartAxis.axisClipRect.left, point.dy), - Offset( - stateProperties.chartAxis.axisClipRect.left + - stateProperties.chartAxis.axisClipRect.width, - point.dy), - grids.dashArray); - } - } - - void _drawVerticalAxesMinorTickLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - num tempInterval, - Rect rect, - int index, - SfCartesianChart chart, - [String? renderType]) { - num value = tempInterval; - double position = 0; - final VisibleRange range = visibleRange!; - final bool rendering = axis.minorTicksPerInterval > 0 && - (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0); - if (rendering) { - for (int i = 0; i < axis.minorTicksPerInterval; i++) { - value += range.interval / (axis.minorTicksPerInterval + 1); - if ((value < range.maximum) && (value > range.minimum)) { - position = - valueToCoefficient(value, axisRenderer._axisRendererDetails) * - rect.height; - position = (position + rect.top).floor().toDouble(); - if (axis.minorGridLines.width > 0 && - renderType == 'outside' && - rect.top <= position && - rect.bottom >= position) { - drawDashedPath( - canvas, - CustomPaintStyle( - axisRenderer.getAxisMinorGridWidth(axis, index, i), - axisRenderer.getAxisMinorGridColor(axis, index, i) ?? - renderingDetails.chartTheme.minorGridLineColor, - PaintingStyle.stroke), - Offset(stateProperties.chartAxis.axisClipRect.left, position), - Offset( - stateProperties.chartAxis.axisClipRect.left + - stateProperties.chartAxis.axisClipRect.width, - position), - axis.minorGridLines.dashArray); - } - if (axis.minorTickLines.width > 0 && - renderType == axis.tickPosition.toString().split('.')[1]) { - drawDashedPath( - canvas, - CustomPaintStyle( - axisRenderer.getAxisMinorTickWidth(axis, index, i), - axisRenderer.getAxisMinorTickColor(axis, index, i) ?? - renderingDetails.chartTheme.minorTickLineColor, - PaintingStyle.stroke), - Offset(rect.left, position), - Offset( - !axis.opposedPosition - ? (isInsideTickPosition! - ? rect.left + axis.minorTickLines.size - : rect.left - axis.minorTickLines.size) - : (isInsideTickPosition! - ? rect.left - axis.minorTickLines.size - : rect.left + axis.minorTickLines.size), - position)); - } - } - } - } - } - - /// To draw the axis labels of horizontal axes - void _drawHorizontalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - if (renderType == axis.labelPosition.toString().split('.')[1]) { - final Rect axisBounds = bounds; - int angle; - TextStyle textStyle; - final bool isRtl = axisRenderer - ._axisRendererDetails.stateProperties.renderingDetails.isRtl; - 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); - - textStyle = label.labelStyle; - textStyle = getTextStyle( - textStyle: textStyle, - fontColor: - textStyle.color ?? renderingDetails.chartTheme.axisLabelColor); - tempInterval = label.value.toDouble(); - angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); - - /// For negative angle calculations - if (angle.isNegative) { - angle = angle + 360; - } - labelRotation = angle; - final Size textSize = measureText(labelText, textStyle); - final Size rotatedTextSize = measureText(labelText, textStyle, angle); - pointX = ((valueToCoefficient( - tempInterval, axisRenderer._axisRendererDetails) * - axisBounds.width) + - axisBounds.left) - .roundToDouble(); - pointY = _getPointY(axisRenderer, label, axisBounds); - pointY -= angle == 0 ? textSize.height / 2 : 0; - pointY += rotatedTextSize.height / 2; - pointX -= angle == 0 ? textSize.width / 2 : 0; - - /// Edge label placement - shift for x-Axis - pointX = _getShiftedPosition( - axisRenderer, axisBounds, pointX, pointY, rotatedTextSize, i); - if (axis.labelAlignment == LabelAlignment.end) { - pointX = pointX + textSize.height / 2; - } else if (axis.labelAlignment == LabelAlignment.start) { - pointX = pointX - textSize.height / 2; - } - if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 || (i == visibleLabels.length - 1)) { - visibleLabels[i]._needRender = false; - continue; - } - } else if ((axis.labelAlignment == LabelAlignment.end) && - (i == visibleLabels.length - 1 || (i == 0 && axis.isInversed))) { - visibleLabels[i]._needRender = false; - continue; - } else if ((axis.labelAlignment == LabelAlignment.start) && - (i == 0 || (i == visibleLabels.length - 1 && axis.isInversed))) { - visibleLabels[i]._needRender = false; - continue; - } - } - - if (axis.labelIntersectAction == AxisLabelIntersectAction.hide && - axis.labelRotation % 180 == 0 && - i != 0 && - visibleLabels[i - 1]._needRender && - (!axis.isInversed - ? pointX <= previousLabelEnd - : (pointX + textSize.width) >= previousLabelEnd)) { - continue; - } - - previousLabelEnd = axis.isInversed ? pointX : pointX + textSize.width; - - if (needAnimate!) { - final double? oldLocation = _getPrevLocation( - axisRenderer, oldAxisRenderer!, tempInterval, textSize, angle); - pointX = oldLocation != null - ? (oldLocation - (oldLocation - pointX) * animationFactor!) - : pointX; - } - final Offset point = Offset(pointX, pointY); - if (axisBounds.left - textSize.width <= pointX && - axisBounds.right + textSize.width >= pointX) { - drawText(canvas, labelText, point, textStyle, angle, isRtl); - } - if (label._labelCollection != null && - label._labelCollection!.isNotEmpty && - axis.labelIntersectAction == AxisLabelIntersectAction.wrap) { - double? finalTextStartPoint; - for (int j = 1; j < label._labelCollection!.length; j++) { - final String wrapTxt = label._labelCollection![j]; - finalTextStartPoint = pointY + - (j * measureText(wrapTxt, axis.labelStyle, angle).height); - drawText(canvas, wrapTxt, Offset(pointX, finalTextStartPoint), - textStyle, angle, isRtl); - if (j == label._labelCollection!.length - 1) { - label._labelRegion = Rect.fromLTWH( - pointX, - pointY, - label._labelRegion!.width, - finalTextStartPoint + (label.labelSize.height) - pointY); - } - } - } - } - } - } - - /// To draw the axis labels of vertical axes - void _drawVerticalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - if (axis.labelPosition.toString().split('.')[1] == renderType) { - final Rect axisBounds = bounds; - TextStyle textStyle; - late double tempInterval, pointX, pointY, previousEnd; - for (int i = 0; i < visibleLabels.length; i++) { - final String labelText = - axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText!, i); - final int angle = - axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); - assert(angle - angle.floor() == 0, - 'The angle value of the vertical axis must be the whole number.'); - textStyle = visibleLabels[i].labelStyle; - textStyle = getTextStyle( - textStyle: textStyle, - fontColor: - textStyle.color ?? renderingDetails.chartTheme.axisLabelColor); - tempInterval = visibleLabels[i].value.toDouble(); - final Size textSize = measureText(labelText, textStyle, 0); - pointY = (valueToCoefficient( - tempInterval, axisRenderer._axisRendererDetails) * - axisBounds.height) + - axisBounds.top; - pointY = ((axisBounds.top + axisBounds.height) - - ((axisBounds.top - pointY).abs())) - - textSize.height / 2; - pointX = _getPointX(axisRenderer, textSize, axisBounds); - final ChartLocation location = getRotatedTextLocation( - pointX, pointY, labelText, textStyle, angle, axis); - if (axis.labelAlignment == LabelAlignment.center) { - pointX = location.x; - pointY = location.y; - } else if (axis.labelAlignment == LabelAlignment.end) { - pointX = location.x; - pointY = location.y - textSize.height / 2; - } else if (axis.labelAlignment == LabelAlignment.start) { - pointX = location.x; - pointY = location.y + textSize.height / 2; - } - if (axis.labelIntersectAction == AxisLabelIntersectAction.hide && - i != 0 && - (!axis.isInversed - ? pointY + (textSize.height / 2) > previousEnd - : pointY - (textSize.height / 2) < previousEnd)) { - continue; - } - previousEnd = !axis.isInversed - ? pointY - textSize.height / 2 - : pointY + textSize.height / 2; - - /// Edge label placement for y-Axis - if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) { - pointY = axisBounds.top + axisBounds.height - textSize.height; - } else if (i == visibleLabels.length - 1 && - axisBounds.top >= pointY + textSize.height / 2) { - pointY = axisBounds.top; - } - } else if (axis.labelAlignment == LabelAlignment.start) { - if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) { - pointY = axisBounds.top + axisBounds.height - textSize.height; - } - } else if (axis.labelAlignment == LabelAlignment.end) { - if (i == visibleLabels.length - 1 && - axisBounds.top >= pointY + textSize.height / 2) { - pointY = axisBounds.top + textSize.height / 2; - } - } - } else if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 || i == visibleLabels.length - 1) { - continue; - } - } else if ((axis.labelAlignment == LabelAlignment.end) && - (i == visibleLabels.length - 1 || (i == 0 && axis.isInversed))) { - continue; - } else if ((axis.labelAlignment == LabelAlignment.start) && - (i == 0 || (i == visibleLabels.length - 1 && axis.isInversed))) { - continue; - } - } - final Size rotatedTextSize = measureText(labelText, textStyle, angle); - visibleLabels[i]._labelRegion = axis.labelRotation == 0 - ? Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height) - : Rect.fromLTWH( - pointX - rotatedTextSize.width / 2, - pointY - rotatedTextSize.height / 2, - rotatedTextSize.width, - rotatedTextSize.height); - - if (needAnimate!) { - final double? oldLocation = _getPrevLocation( - axisRenderer, oldAxisRenderer!, tempInterval, textSize); - pointY = oldLocation != null - ? (oldLocation - (oldLocation - pointY) * animationFactor!) - : pointY; - } - - final Offset point = Offset(pointX, pointY); - if (axisBounds.top - textSize.height <= pointY && - axisBounds.bottom + textSize.height >= pointY) { - drawText( - canvas, - labelText, - point, - textStyle, - labelRotation, - axisRenderer - ._axisRendererDetails.stateProperties.renderingDetails.isRtl); - } - } - } - } - - /// To draw the axis title of horizontal axes - void _drawHorizontalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final Rect axisBounds = bounds; - Offset point; - final String title = axis.title.text ?? ''; - const int labelRotation = 0, innerPadding = 8; - TextStyle style = axis.title.textStyle; - style = getTextStyle( - textStyle: style, - fontColor: style.color ?? renderingDetails.chartTheme.axisTitleColor); - final Size textSize = measureText(title, style); - double top = 0.0; - final ChartAxisRendererDetails axisRendererDetails = - axisRenderer._axisRendererDetails; - if ((axis.borderWidth > 0 || isMultiLevelLabelEnabled) && - (visibleLabels.isNotEmpty)) { - top = getHorizontalAxisTitleOffset(this, textSize); - } else { - if (axis.labelPosition == ChartDataLabelPosition.inside) { - top = !axis.opposedPosition - ? axisRendererDetails.titleOffset ?? - axisBounds.top + - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + - (!kIsWeb - ? innerPadding - : innerPadding + textSize.height / 2) - : axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - textSize.height - : axisBounds.top - - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - - innerPadding - - textSize.height; - } else { - top = !axis.opposedPosition - ? axisRendererDetails.titleOffset ?? - axisBounds.top + - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + - innerPadding + - (!kIsWeb - ? maximumLabelSize.height - : maximumLabelSize.height + textSize.height / 2) - : axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - textSize.height - : axisBounds.top - - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - - innerPadding - - maximumLabelSize.height - - textSize.height; - } - } - axis.title.alignment == ChartAlignment.near - ? point = Offset(stateProperties.chartAxis.axisClipRect.left, top) - : axis.title.alignment == ChartAlignment.far - ? point = Offset( - stateProperties.chartAxis.axisClipRect.right - textSize.width, - top) - : point = Offset( - axisBounds.left + - ((axisBounds.width / 2) - (textSize.width / 2)), - top); - if (seriesRenderers.isNotEmpty || name == 'primaryXAxis') { - drawText(canvas, title, point, style, labelRotation); - } - } - - /// To draw the axis title of vertical axes - void _drawVerticalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final Rect axisBounds = bounds; - Offset point; - final String title = axis.title.text ?? ''; - final int labelRotation = axis.opposedPosition ? 90 : 270; - const int innerPadding = 10; - TextStyle style = axis.title.textStyle; - style = getTextStyle( - textStyle: style, - fontColor: style.color ?? renderingDetails.chartTheme.axisTitleColor); - final Size textSize = measureText(title, style); - double left = 0.0; - final ChartAxisRendererDetails axisRendererDetails = - axisRenderer._axisRendererDetails; - if (axis.borderWidth > 0 || isMultiLevelLabelEnabled) { - left = getVerticalAxisTitleOffset(this, textSize); - } else { - if (axis.labelPosition == ChartDataLabelPosition.inside) { - left = (!axis.opposedPosition) - ? axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - textSize.height / 2 - : axisBounds.left - - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - - innerPadding - - textSize.height - : axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! + textSize.height / 2 - : axisBounds.left + - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + - innerPadding * 2; - } else { - left = (!axis.opposedPosition) - ? axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - textSize.height - : (axisBounds.left - - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - - innerPadding - - maximumLabelSize.width - - textSize.height / 2) - : axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! + textSize.height / 2 - : axisBounds.left + - (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + - innerPadding + - maximumLabelSize.width + - textSize.height / 2; - } - } - axis.title.alignment == ChartAlignment.near - ? point = Offset(left, - stateProperties.chartAxis.axisClipRect.bottom - textSize.width / 2) - : axis.title.alignment == ChartAlignment.far - ? point = Offset(left, - stateProperties.chartAxis.axisClipRect.top + textSize.width / 2) - : point = Offset(left, axisBounds.top + (axisBounds.height / 2)); - if (seriesRenderers.isNotEmpty || name == 'primaryYAxis') { - drawText(canvas, title, point, style, labelRotation); - } - } - - /// Find the additional range - void _findAdditionalRange( - ChartAxisRenderer axisRenderer, num start, num end, num interval) { - num minimum; - num maximum; - minimum = ((start / interval).floor()) * interval; - maximum = ((end / interval).ceil()) * interval; - if (axis.rangePadding == ChartRangePadding.additional) { - minimum -= interval; - maximum += interval; - } - - /// Update the visible range to the axis. - _updateActualRange(axisRenderer, minimum, maximum, interval); - } - - /// Update visible range - void _updateActualRange( - ChartAxisRenderer axisRenderer, num minimum, num maximum, num interval) { - final dynamic chartAxis = axis; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - actualRange!.minimum = chartAxis.minimum == null - ? minimum - : (axisDetails is DateTimeCategoryAxisDetails - ? (axisDetails.getEffectiveRange(chartAxis.minimum, true)! - - (chartAxis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) - : chartAxis.minimum); - actualRange!.maximum = chartAxis.maximum == null - ? maximum - : (axisDetails is DateTimeCategoryAxisDetails - ? (axisDetails.getEffectiveRange(chartAxis.maximum, true)! + - (chartAxis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) - : chartAxis.maximum); - actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - actualRange!.interval = axis.interval ?? interval; - } - - /// Find the normal range - void _findNormalRange( - ChartAxisRenderer axisRenderer, num start, num end, num interval) { - final dynamic chartAxis = axis; - num remaining, minimum, maximum; - num startValue = start; - if ((axis is CategoryAxis || axis is DateTimeCategoryAxis) && - chartAxis.labelPlacement == LabelPlacement.onTicks) { - minimum = start - 0.5; - maximum = end + 0.5; - } else { - if (start < 0) { - startValue = 0; - minimum = start + (start / 20); - remaining = interval + getValueByPercentage(minimum, interval); - if ((0.365 * interval) >= remaining) { - minimum -= interval; - } - if (getValueByPercentage(minimum, interval) < 0) { - minimum = - (minimum - interval) - getValueByPercentage(minimum, interval); - } - } else { - minimum = start < ((5.0 / 6.0) * end) ? 0 : (start - (end - start) / 2); - if (minimum % interval > 0) { - minimum -= minimum % interval; - } - } - maximum = (end > 0) - ? (end + (end - startValue) / 20) - : (end - (end - startValue) / 20); - remaining = interval - (maximum % interval); - if ((0.365 * interval) >= remaining) { - maximum += interval; - } - if (maximum % interval > 0) { - maximum = (maximum + interval) - (maximum % interval); - } - } - if (minimum == 0) { - interval = (axisRenderer is NumericAxisRenderer) - ? calculateNumericNiceInterval( - axisRenderer, maximum - minimum, axisSize) - : axisRenderer.calculateInterval( - VisibleRange(minimum, maximum), axisSize)!; - maximum = (maximum / interval).ceil() * interval; - } - - /// Update the visible range to the axis. - _updateActualRange(axisRenderer, minimum, maximum, interval); - } - - /// To trigger the render label event - void triggerLabelRenderEvent(String labelText, num labelValue) { - TextStyle fontStyle = axis.labelStyle; - final String actualText = labelText; - String renderText = actualText; - String? eventActualText; - final AxisLabelRenderDetails axisLabelDetails = - AxisLabelRenderDetails(labelValue, actualText, fontStyle, axis); - if (axis.axisLabelFormatter != null) { - final ChartAxisLabel axisLabel = - axis.axisLabelFormatter!(axisLabelDetails); - fontStyle = axisLabel.textStyle; - renderText = axisLabel.text; - eventActualText = axisLabel.text; - } - Size textSize = measureText(renderText, axis.labelStyle, 0); - if (axis.maximumLabelWidth != null || axis.labelsExtent != null) { - if (axis.maximumLabelWidth != null) { - assert(axis.maximumLabelWidth! >= 0, - 'maximumLabelWidth must not be negative'); - } - if (axis.labelsExtent != null) { - assert(axis.labelsExtent! >= 0, 'labelsExtent must not be negative'); - } - if ((axis.maximumLabelWidth != null && - textSize.width > axis.maximumLabelWidth!) || - (axis.labelsExtent != null && textSize.width > axis.labelsExtent!)) { - labelText = getTrimmedText(renderText, - (axis.labelsExtent ?? axis.maximumLabelWidth)!, axis.labelStyle, - axisRenderer: axisRenderer, - isRtl: axisRenderer - ._axisRendererDetails.stateProperties.renderingDetails.isRtl); - } - textSize = measureText(labelText, axis.labelStyle, 0); - } - final String? trimmedText = - labelText.contains('...') || labelText.isEmpty ? labelText : null; - renderText = trimmedText ?? renderText; - final Size labelSize = - measureText(renderText, fontStyle, axis.labelRotation); - visibleLabels.add(AxisLabel(fontStyle, labelSize, - eventActualText ?? actualText, labelValue, trimmedText, renderText)); - } - - /// Calculate the maximum label's size. - void calculateMaximumLabelSize(ChartAxisRenderer axisRenderer, - CartesianStateProperties stateProperties) { - AxisLabelIntersectAction action; - AxisLabel label; - double maximumLabelHeight = 0.0, - maximumLabelWidth = 0.0, - labelMaximumWidth, - pointX; - action = axis.labelIntersectAction; - labelMaximumWidth = - stateProperties.chartAxis.axisClipRect.width / visibleLabels.length; - if (orientation == AxisOrientation.horizontal && - axis.labelIntersectAction != AxisLabelIntersectAction.none && - visibleLabels.length > 1) { - final Rect axisBounds = stateProperties.chartAxis.axisClipRect; - AxisLabel label1; - num pointX1; - for (int i = 0; i < visibleLabels.length - 1; i++) { - label = visibleLabels[i]; - pointX = (valueToCoefficient( - label.value, axisRenderer._axisRendererDetails) * - axisBounds.width) + - axisBounds.left; - pointX -= label.labelSize.width / 2; - pointX = (i == 0 && - axis.edgeLabelPlacement == EdgeLabelPlacement.shift && - ((pointX < axisBounds.left && !axis.isInversed) || - (pointX + label.labelSize.width > axisBounds.right && - axis.isInversed))) - ? (axis.isInversed - ? axisBounds.left + axisBounds.width - label.labelSize.width - : axisBounds.left) - : ((i == visibleLabels.length - 1 && - axis.edgeLabelPlacement == EdgeLabelPlacement.shift && - ((((pointX + label.labelSize.width) > axisBounds.right) && - !axis.isInversed) || - (pointX < axisBounds.left && axis.isInversed))) - ? (axis.isInversed - ? axisBounds.left - : axisBounds.left + - axisBounds.width - - label.labelSize.width) - : pointX); - - label1 = visibleLabels[i + 1]; - pointX1 = (valueToCoefficient( - label1.value, axisRenderer._axisRendererDetails) * - stateProperties.chartAxis.axisClipRect.width) + - stateProperties.chartAxis.axisClipRect.left; - pointX1 -= label1.labelSize.width / 2; - if ((((pointX + label.labelSize.width) > pointX1) && - !axis.isInversed) || - (((pointX - label.labelSize.width) < pointX1) && axis.isInversed)) { - isCollide = true; - break; - } - } - } - - for (int i = 0; i < visibleLabels.length; i++) { - label = visibleLabels[i]; - if (label.labelSize.width > maximumLabelWidth) { - maximumLabelWidth = label.labelSize.width; - } - if (label.labelSize.height > maximumLabelHeight) { - maximumLabelHeight = label.labelSize.height; - } - - if (orientation == AxisOrientation.horizontal) { - pointX = (valueToCoefficient( - label.value, axisRenderer._axisRendererDetails) * - stateProperties.chartAxis.axisClipRect.width) + - stateProperties.chartAxis.axisClipRect.left; - pointX -= label.labelSize.width / 2; - - /// Based on below options, perform label intersection - if (isCollide) { - final List _list = _performLabelIntersectAction( - label, - action, - maximumLabelWidth, - maximumLabelHeight, - labelMaximumWidth, - pointX, - i, - axisRenderer, - chart); - maximumLabelWidth = _list[0]; - maximumLabelHeight = _list[1]; - } - } - } - maximumLabelSize = Size(maximumLabelWidth, maximumLabelHeight); - } - - /// Return the height and width values of labelIntersectAction - List _performLabelIntersectAction( - AxisLabel label, - AxisLabelIntersectAction action, - double maximumLabelWidth, - double maximumLabelHeight, - double labelMaximumWidth, - num pointX, - int i, - ChartAxisRenderer axisRenderer, - SfCartesianChart chart) { - double height; - int angle = labelRotation; - Size currentLabelSize; - switch (action) { - case AxisLabelIntersectAction.multipleRows: - if (i > 0) { - height = _findMultiRows(i, pointX, label, axisRenderer, chart); - if (height > maximumLabelHeight) { - maximumLabelHeight = height; - } - } - break; - case AxisLabelIntersectAction.rotate45: - case AxisLabelIntersectAction.rotate90: - angle = action == AxisLabelIntersectAction.rotate45 ? -45 : -90; - labelRotation = angle; - currentLabelSize = - measureText(label.renderText!, axis.labelStyle, angle); - if (currentLabelSize.height > maximumLabelHeight) { - maximumLabelHeight = currentLabelSize.height; - } - if (currentLabelSize.width > maximumLabelWidth) { - maximumLabelWidth = currentLabelSize.width; - } - break; - case AxisLabelIntersectAction.wrap: - label._labelCollection = _gettingLabelCollection( - label.renderText!, labelMaximumWidth, axisRenderer); - if (label._labelCollection!.isNotEmpty) { - label.renderText = label._labelCollection![0]; - } - height = label.labelSize.height * label._labelCollection!.length; - if (height > maximumLabelHeight) { - maximumLabelHeight = height; - } - break; - default: - break; - } - return [maximumLabelWidth, maximumLabelHeight]; - } - - void _calculateRange(ChartAxisRenderer _axisRenderer) { - min = null; - max = null; - CartesianSeriesRenderer seriesRenderer; - double paddingInterval = 0; - ChartAxisRendererDetails _xAxisDetails, _yAxisDetails; - num? minimumX, maximumX, minimumY, maximumY; - String seriesType; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - for (int i = 0; i < seriesRenderers.length; i++) { - seriesRenderer = seriesRenderers[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - minimumX = seriesRendererDetails.minimumX; - maximumX = seriesRendererDetails.maximumX; - minimumY = seriesRendererDetails.minimumY; - maximumY = seriesRendererDetails.maximumY; - seriesType = seriesRendererDetails.seriesType; - if (seriesRendererDetails.visible! == true && - minimumX != null && - maximumX != null && - minimumY != null && - maximumY != null) { - paddingInterval = 0; - _xAxisDetails = seriesRendererDetails.xAxisDetails!; - _yAxisDetails = seriesRendererDetails.yAxisDetails!; - if (((_xAxisDetails is DateTimeAxisDetails || - _xAxisDetails is NumericAxisDetails) && - _xAxisDetails.axis.rangePadding == ChartRangePadding.auto) && - (seriesType.contains('column') || - (seriesType.contains('bar') && - seriesType.contains('errorbar') == false) || - seriesType == 'histogram')) { - seriesRendererDetails.minDelta = seriesRendererDetails.minDelta ?? - calculateMinPointsDelta( - _xAxisDetails.axisRenderer, seriesRenderers, stateProperties); - paddingInterval = seriesRendererDetails.minDelta! / 2; - } - if (((stateProperties.requireInvertedAxis - ? _yAxisDetails - : _xAxisDetails) == - axisDetails) && - orientation == AxisOrientation.horizontal) { - stateProperties.requireInvertedAxis - ? findMinMax(minimumY, maximumY) - : findMinMax( - minimumX - paddingInterval, maximumX + paddingInterval); - } - - if (((stateProperties.requireInvertedAxis - ? _xAxisDetails - : _yAxisDetails) == - axisDetails) && - orientation == AxisOrientation.vertical) { - stateProperties.requireInvertedAxis - ? findMinMax( - minimumX - paddingInterval, maximumX + paddingInterval) - : findMinMax(minimumY, maximumY); - } - } - } - } - - /// Find min and max values - void findMinMax(num minVal, num maxVal) { - if (min == null || min! > minVal) { - min = minVal; - } - if (max == null || max! < maxVal) { - max = maxVal; - } - } - - /// Calculate the interval based min and max values in axis - num calculateNumericNiceInterval( - ChartAxisRenderer axisRenderer, num delta, Size size) { - final List intervalDivisions = [10, 5, 2, 1]; - num niceInterval; - - /// Get the desired interval if desired interval not specified - final num actualDesiredIntervalCount = - calculateDesiredIntervalCount(size, axisRenderer); - niceInterval = delta / actualDesiredIntervalCount; - if (axisRenderer._axisRendererDetails.axis.desiredIntervals != null) { - return niceInterval; - } - - /// Get the minimum interval - final num minimumInterval = niceInterval == 0 - ? 0 - : math.pow(10, calculateLogBaseValue(niceInterval, 10).floor()); - for (int i = 0; i < intervalDivisions.length; i++) { - final num interval = intervalDivisions[i]; - final num currentInterval = minimumInterval * interval; - if (actualDesiredIntervalCount < (delta / currentInterval)) { - break; - } - niceInterval = currentInterval; - } - return niceInterval; - } - - /// Calculate the axis interval for numeric axis - num calculateDesiredIntervalCount( - Size availableSize, ChartAxisRenderer axisRenderer) { - final num size = axisRenderer._axisRendererDetails.orientation == - AxisOrientation.horizontal - ? availableSize.width - : availableSize.height; - if (axisRenderer._axisRendererDetails.axis.desiredIntervals == null) { - num desiredIntervalCount = - (axisRenderer._axisRendererDetails.orientation == - AxisOrientation.horizontal - ? 0.533 - : 1) * - axisRenderer._axisRendererDetails.axis.maximumLabels; - desiredIntervalCount = math.max(size * (desiredIntervalCount / 100), 1); - return desiredIntervalCount; - } else { - return axisRenderer._axisRendererDetails.axis.desiredIntervals!; - } - } - - /// Get the range padding of an axis - ChartRangePadding calculateRangePadding( - ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final ChartAxis axis = axisRenderer._axisRendererDetails.axis; - ChartRangePadding padding = ChartRangePadding.auto; - if (axis.rangePadding != ChartRangePadding.auto) { - padding = axis.rangePadding; - } else if (axis.rangePadding == ChartRangePadding.auto && - axisRenderer._axisRendererDetails.orientation != null) { - switch (axisRenderer._axisRendererDetails.orientation!) { - case AxisOrientation.horizontal: - padding = stateProperties.requireInvertedAxis - ? (isStack100 - ? ChartRangePadding.round - : ChartRangePadding.normal) - : ChartRangePadding.none; - break; - case AxisOrientation.vertical: - padding = !stateProperties.requireInvertedAxis - ? (isStack100 - ? ChartRangePadding.round - : ChartRangePadding.normal) - : ChartRangePadding.none; - break; - } - } - return padding; - } - - /// Applying range padding - void applyRangePaddings( - ChartAxisRenderer axisRenderer, - CartesianStateProperties stateProperties, - VisibleRange range, - num interval) { - final num start = range.minimum; - final num end = range.maximum; - final ChartRangePadding padding = - calculateRangePadding(axisRenderer, chart); - if (padding == ChartRangePadding.additional || - padding == ChartRangePadding.round) { - /// Get the additional range - _findAdditionalRange(axisRenderer, start, end, interval); - } else if (padding == ChartRangePadding.normal) { - /// Get the normal range - _findNormalRange(axisRenderer, start, end, interval); - } else { - _updateActualRange(axisRenderer, start, end, interval); - } - range.delta = range.maximum - range.minimum; - } - - /// Dispose the objects. - void dispose() { - visibleLabels.clear(); - seriesRenderers.clear(); - } -} - -// ignore: avoid_classes_with_only_static_members -/// Represents the helper class used to get the private fields from the chart axis renderer -class AxisHelper { - /// Methods to get the axis renderer details of axis renderer - static ChartAxisRendererDetails getAxisRendererDetails( - ChartAxisRenderer axisRenderer) { - return axisRenderer._axisRendererDetails; - } - - /// Method to get the axis label region value - static Rect? getLabelRegion(AxisLabel label) { - return label._labelRegion; - } - - /// Methods to set the axis renderer details of axis renderer - static void setAxisRendererDetails( - ChartAxisRenderer renderer, ChartAxisRendererDetails rendererDetails) { - renderer._axisRendererDetails = rendererDetails; - } -} 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 deleted file mode 100644 index 4baba0087..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart +++ /dev/null @@ -1,1064 +0,0 @@ -import 'dart:math' as math; -import 'dart:ui'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../axis/numeric_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Represents the chart axis panel -class ChartAxisPanel { - /// Creates an instance of chart axis panel - ChartAxisPanel(this.stateProperties) { - innerPadding = 5; - axisPadding = 5; - axisLineLabelPadding = 5; - axisLabelTitlePadding = 3; - axisClipRect = Rect.zero; - verticalAxisRenderers = []; - horizontalAxisRenderers = []; - needsRepaint = true; - } - - /// Specifies the cartesian state properties - final CartesianStateProperties stateProperties; - - /// 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 => stateProperties.chart; - - /// Specifies the value of primary XAxis renderer and primary YAxis renderer - ChartAxisRenderer? primaryXAxisRenderer, primaryYAxisRenderer; - - /// Specifies the value of primary XAxis details and primary YAxis details - late ChartAxisRendererDetails primaryXAxisDetails, primaryYAxisDetails; - - /// Specifies the value of left axis renderer - List leftAxisRenderers = []; - - /// Specifies the value of right axis renderer - List rightAxisRenderers = []; - - /// Specifies the value of top axis renderer - List topAxisRenderers = []; - - /// Specifies the value of bottom axis renderer - List bottomAxisRenderers = []; - - /// Specifies the left axes count value - late List leftAxesCount; - - /// Specifies the bottom axes count value - late List bottomAxesCount; - - /// Specifies the top axes count value - late List topAxesCount; - - /// Specifies the right axes count value - late List rightAxesCount; - - /// Specifies the bottom size value - double bottomSize = 0; - - /// Specifies the top size value - double topSize = 0; - - /// Specifies left size value - double leftSize = 0; - - /// Specifies the right size value - double rightSize = 0; - - /// Specifies the value of inner padding - double innerPadding = 0; - - /// Specifies the axis padding value - double axisPadding = 0; - - /// Specifies the padding between axis line and axis label - double? axisLineLabelPadding; - - /// Specifies the padding between axis label and axis title - double? axisLabelTitlePadding; - - /// Specifies the value of axis clip rect - late Rect axisClipRect; - - /// Specifies the list of vertical axis renderers - List verticalAxisRenderers = []; - - /// Specifies the list of horizontal axis renderers - List horizontalAxisRenderers = []; - - /// Specifies the list of axes renderers - List axisRenderersCollection = []; - - /// Whether to repaint axis or not - late bool needsRepaint; - - /// To get the crossAt values of a specific axis - void _getAxisCrossingValue(ChartAxisRenderer axisRenderer) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - if (axis.crossesAt != null) { - if (axis.associatedAxisName != null) { - for (int i = 0; - i < stateProperties.chartAxis.axisRenderersCollection.length; - i++) { - if (axis.associatedAxisName == - AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[i]) - .name) { - axisDetails.crossAxisRenderer = - stateProperties.chartAxis.axisRenderersCollection[i]; - _calculateCrossingValues( - axisRenderer, axisDetails.crossAxisRenderer); - } - } - } else { - axisDetails.crossAxisRenderer = stateProperties.requireInvertedAxis - ? (axisDetails.crossAxisRenderer = - axisDetails.orientation == AxisOrientation.horizontal - ? stateProperties.chartAxis.primaryXAxisRenderer! - : stateProperties.chartAxis.primaryYAxisRenderer!) - : (axisDetails.orientation == AxisOrientation.horizontal - ? stateProperties.chartAxis.primaryYAxisRenderer! - : stateProperties.chartAxis.primaryXAxisRenderer!); - - _calculateCrossingValues(axisRenderer, axisDetails.crossAxisRenderer); - } - } - } - - ///To get the axis crossing value - void _calculateCrossingValues(ChartAxisRenderer currentAxisRenderer, - ChartAxisRenderer targetAxisRenderer) { - final ChartAxisRendererDetails currentAxisDetails = - AxisHelper.getAxisRendererDetails(currentAxisRenderer); - final ChartAxisRendererDetails targetAxisDetails = - AxisHelper.getAxisRendererDetails(targetAxisRenderer); - dynamic value = currentAxisDetails.axis.crossesAt; - value = value is String && num.tryParse(value) != null - ? num.tryParse(value) - : value; - if (targetAxisDetails is DateTimeAxisDetails) { - value = value is DateTime ? value.millisecondsSinceEpoch : value; - targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); - } else if (targetAxisDetails is CategoryAxisDetails) { - value = value is num - ? value.floor() - : targetAxisDetails.labels.indexOf(value); - targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); - } else if (targetAxisDetails is DateTimeCategoryAxisDetails) { - value = value is num - ? value.floor() - : (value is DateTime - ? targetAxisDetails.labels - .indexOf('${value.microsecondsSinceEpoch}') - : null); - targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); - } else if (targetAxisDetails is LogarithmicAxisDetails) { - final LogarithmicAxis _axis = targetAxisDetails.axis as LogarithmicAxis; - value = calculateLogBaseValue(value, _axis.logBase); - targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); - } else if (targetAxisDetails is NumericAxisDetails) { - targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); - } - if (value.isNaN == false) { - currentAxisDetails.crossValue = - _updateCrossValue(value, targetAxisDetails.visibleRange!); - currentAxisDetails.crossRange = targetAxisDetails.visibleRange; - } - } - - ///To measure the bounds of each axis - void measureAxesBounds() { - bottomSize = 0; - topSize = 0; - leftSize = 0; - rightSize = 0; - leftAxesCount = []; - bottomAxesCount = []; - topAxesCount = []; - rightAxesCount = []; - bottomAxisRenderers = []; - rightAxisRenderers = []; - topAxisRenderers = []; - leftAxisRenderers = []; - - if (verticalAxisRenderers.isNotEmpty) { - for (int axisIndex = 0; - axisIndex < verticalAxisRenderers.length; - axisIndex++) { - final dynamic axisRenderer = verticalAxisRenderers[axisIndex]; - final dynamic axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - assert( - !(axisDetails.axis.interval != null) || - (axisDetails.axis.interval! > 0) == true, - 'The vertical axis interval value must be greater than 0.'); - axisDetails.calculateRangeAndInterval(stateProperties); - _getAxisCrossingValue(axisRenderer); - _measureAxesSize(axisRenderer); - } - _calculateSeriesClipRect(); - } - if (horizontalAxisRenderers.isNotEmpty) { - for (int axisIndex = 0; - axisIndex < horizontalAxisRenderers.length; - axisIndex++) { - final dynamic axisRenderer = horizontalAxisRenderers[axisIndex]; - final dynamic axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - _calculateLabelRotationAngle(axisDetails); - assert( - !(axisDetails.axis.interval != null) || - (axisDetails.axis.interval > 0) == true, - 'The horizontal axis interval value must be greater than 0.'); - axisDetails.calculateRangeAndInterval(stateProperties); - _getAxisCrossingValue(axisRenderer); - _measureAxesSize(axisRenderer); - } - } - _calculateAxesRect(); - } - - ///Calculate the axes total size - void _measureAxesSize(ChartAxisRenderer axisRenderer) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - ChartAxisRendererDetails crossAxisRendererDetails; - final ChartAxis axis = axisDetails.axis; - final bool isTitleEnabled = - axis.title.text != null && axis.title.text!.isNotEmpty; - double titleSize = 0; - num multiLevelLabelSize = 0; - axisDetails.totalSize = 0; - if (axis.multiLevelLabels != null && - axis.multiLevelLabels!.isNotEmpty && - axisDetails.visibleRange != null) { - if ((((axisDetails.visibleRange!.minimum! <= - axisDetails.minimumMultiLevelLabelValue! == - true) && - (axisDetails.minimumMultiLevelLabelValue! <= - axisDetails.visibleRange!.maximum!)) || - (axisDetails.visibleRange!.minimum! <= - axisDetails.maximumMultiLevelLabelValue! == - true)) && - !(axisDetails.visibleRange!.minimum! <= - axisDetails.minimumMultiLevelLabelValue! == - true && - axisDetails.visibleRange!.maximum! <= - axisDetails.minimumMultiLevelLabelValue! == - true)) { - axisDetails.isMultiLevelLabelEnabled = true; - } - } - if (axis.borderWidth > 0 || (axisDetails.isMultiLevelLabelEnabled)) { - const double axisLabelPadding = 10; - double maximumLabelWidth = axisDetails.maximumLabelSize.width; - double maximumLabelHeight = axisDetails.maximumLabelSize.height; - if (axisDetails.orientation == AxisOrientation.horizontal) { - maximumLabelHeight = maximumLabelHeight + axisLabelPadding; - } else { - maximumLabelWidth = maximumLabelWidth + axisLabelPadding; - } - axisDetails.maximumLabelSize = - Size(maximumLabelWidth, maximumLabelHeight); - } - if (axis.isVisible) { - if (isTitleEnabled) { - titleSize = measureText(axis.title.text!, axis.title.textStyle).height + - axisPadding; - axisDetails.titleHeight = titleSize; - } - final Rect rect = stateProperties.renderingDetails.chartContainerRect; - final int axisIndex = _getAxisIndex(axisRenderer); - final double tickSize = - (axisIndex == 0 && axis.tickPosition == TickPosition.inside) - ? 0 - : math.max( - axis.majorTickLines.size, - axis.minorTicksPerInterval > 0 - ? axis.minorTickLines.size - : 0) + - innerPadding; - final double labelSize = (axisIndex == 0 && - axis.labelPosition == ChartDataLabelPosition.inside) - ? 0 - : (axis.labelStyle.fontSize == 0 - ? 0 - : (axisDetails.orientation == AxisOrientation.horizontal) - ? axisDetails.maximumLabelSize.height - : (axis.labelsExtent != null && axis.labelsExtent! > 0) - ? axis.labelsExtent - : axisDetails.maximumLabelSize.width)! + - innerPadding; - if (axisDetails.isMultiLevelLabelEnabled && - axis.labelPosition == ChartDataLabelPosition.outside) { - multiLevelLabelSize = - axisDetails.orientation == AxisOrientation.horizontal - ? axisDetails.multiLevelLabelTotalSize.height - : axisDetails.multiLevelLabelTotalSize.width; - if (axisDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.squareBrace || - axisDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.curlyBrace) { - const double bracePadding = 5; - multiLevelLabelSize = multiLevelLabelSize + bracePadding; - } - } - axisDetails.totalSize = - titleSize + tickSize + labelSize + multiLevelLabelSize; - if (axisDetails.orientation == AxisOrientation.horizontal) { - if (!axis.opposedPosition) { - axisDetails.totalSize += bottomAxisRenderers.isNotEmpty && - axis.labelStyle.fontSize! > 0 && - (axis.labelPosition == ChartDataLabelPosition.outside || - ((axis.majorTickLines.width > 0 || - axis.minorTickLines.width > 0) && - axis.tickPosition == TickPosition.outside) || - (axis.title.text != null && axis.title.text != '')) - ? axisPadding.toDouble() - : 0; - if (axisDetails.crossValue != null && - axisDetails.crossRange != null) { - crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( - axisDetails.crossAxisRenderer); - final num crossPosition = valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.height; - axisDetails.totalSize = crossPosition - axisDetails.totalSize < 0 - ? (crossPosition - axisDetails.totalSize).abs() - : !axis.placeLabelsNearAxisLine - ? (isTitleEnabled ? labelSize + titleSize : labelSize) - : 0; - if (axisDetails.isMultiLevelLabelEnabled && - axis.labelPosition == ChartDataLabelPosition.outside) { - axisDetails.totalSize = !axis.placeLabelsNearAxisLine - ? axisDetails.totalSize + (multiLevelLabelSize.toDouble()) - : axisDetails.totalSize; - } - } - bottomSize += axisDetails.totalSize + - (bottomAxesCount.isNotEmpty && bottomAxesCount.length > 1 - ? axisPadding - : 0); - bottomAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); - } else { - axisDetails.totalSize += topAxisRenderers.isNotEmpty && - axis.labelStyle.fontSize! > 0 && - (axis.labelPosition == ChartDataLabelPosition.outside || - ((axis.majorTickLines.width > 0 || - axis.minorTickLines.width > 0) && - axis.tickPosition == TickPosition.outside) || - (axis.title.text != null && axis.title.text != '')) - ? axisPadding.toDouble() - : 0; - if (axisDetails.crossValue != null && - axisDetails.crossRange != null) { - crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( - axisDetails.crossAxisRenderer); - final num crossPosition = valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.height; - axisDetails.totalSize = crossPosition + axisDetails.totalSize > - rect.height - ? ((crossPosition + axisDetails.totalSize) - rect.height).abs() - : !axis.placeLabelsNearAxisLine - ? (isTitleEnabled ? labelSize + titleSize : labelSize) - : 0; - if (axisDetails.isMultiLevelLabelEnabled && - axis.labelPosition == ChartDataLabelPosition.outside) { - axisDetails.totalSize = !axis.placeLabelsNearAxisLine - ? axisDetails.totalSize + (multiLevelLabelSize.toDouble()) - : axisDetails.totalSize; - } - } - topSize += axisDetails.totalSize + - (topAxesCount.isNotEmpty && topAxesCount.length > 1 - ? axisPadding - : 0); - topAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); - } - } else if (axisDetails.orientation == AxisOrientation.vertical) { - if (!axis.opposedPosition) { - axisDetails.totalSize += leftAxisRenderers.isNotEmpty && - axis.labelStyle.fontSize! > 0 && - (axis.labelPosition == ChartDataLabelPosition.outside || - ((axis.majorTickLines.width > 0 || - axis.minorTickLines.width > 0) && - axis.tickPosition == TickPosition.outside) || - (axis.title.text != null && axis.title.text != '')) - ? axisPadding.toDouble() - : 0; - if (axisDetails.crossValue != null && - axisDetails.crossRange != null) { - crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( - axisDetails.crossAxisRenderer); - final num crossPosition = valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.width; - axisDetails.totalSize = crossPosition - axisDetails.totalSize < 0 - ? (crossPosition - axisDetails.totalSize).abs() - : !axis.placeLabelsNearAxisLine - ? (isTitleEnabled ? labelSize + titleSize : labelSize) - : 0; - if (axisDetails.isMultiLevelLabelEnabled && - axis.labelPosition == ChartDataLabelPosition.outside) { - axisDetails.totalSize = !axis.placeLabelsNearAxisLine - ? axisDetails.totalSize + (multiLevelLabelSize.toDouble()) - : axisDetails.totalSize; - } - } - leftSize += axisDetails.totalSize + - (leftAxesCount.isNotEmpty && leftAxesCount.length > 1 - ? axisPadding - : 0); - leftAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); - } else { - axisDetails.totalSize += rightAxisRenderers.isNotEmpty && - axis.labelStyle.fontSize! > 0 && - (axis.labelPosition == ChartDataLabelPosition.outside || - ((axis.majorTickLines.width > 0 || - axis.minorTickLines.width > 0) && - axis.tickPosition == TickPosition.outside) || - (axis.title.text != null && axis.title.text != '')) - ? axisPadding.toDouble() - : 0; - if (axisDetails.crossValue != null && - axisDetails.crossRange != null) { - crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( - axisDetails.crossAxisRenderer); - final num crossPosition = valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.width; - axisDetails.totalSize = crossPosition + axisDetails.totalSize > - rect.width - ? ((crossPosition + axisDetails.totalSize) - rect.width).abs() - : !axis.placeLabelsNearAxisLine - ? (isTitleEnabled ? labelSize + titleSize : labelSize) - : 0; - if (axisDetails.isMultiLevelLabelEnabled && - axis.labelPosition == ChartDataLabelPosition.outside) { - axisDetails.totalSize = !axis.placeLabelsNearAxisLine - ? axisDetails.totalSize + (multiLevelLabelSize.toDouble()) - : axisDetails.totalSize; - } - } - rightSize += axisDetails.totalSize + - (rightAxesCount.isNotEmpty && rightAxesCount.length > 1 - ? axisPadding - : 0); - rightAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); - } - } - } - } - - /// To get the axis index - int _getAxisIndex(ChartAxisRenderer axisRenderer) { - int index; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - if (axisDetails.orientation == AxisOrientation.horizontal) { - if (!axis.opposedPosition) { - bottomAxisRenderers.add(axisRenderer); - index = bottomAxisRenderers.length; - } else { - topAxisRenderers.add(axisRenderer); - index = topAxisRenderers.length; - } - } else if (axisDetails.orientation == AxisOrientation.vertical) { - if (!axis.opposedPosition) { - leftAxisRenderers.add(axisRenderer); - index = leftAxisRenderers.length; - } else { - rightAxisRenderers.add(axisRenderer); - index = rightAxisRenderers.length; - } - } else { - index = 0; - } - return index - 1; - } - - ///To find the axis label rotation angle - void _calculateLabelRotationAngle(ChartAxisRendererDetails axisDetails) { - int angle = axisDetails.labelRotation; - if (angle < -360 || angle > 360) { - angle %= 360; - } - if (angle.isNegative) { - angle = angle + 360; - } - axisDetails.labelRotation = angle; - } - - /// Calculate series clip rect size - void _calculateSeriesClipRect() { - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - final num padding = chartWidget.title.text.isNotEmpty ? 10 : 0; - stateProperties.chartAxis.axisClipRect = Rect.fromLTWH( - containerRect.left + leftSize, - containerRect.top + topSize + padding, - containerRect.width - leftSize - rightSize, - containerRect.height - topSize - bottomSize - padding); - } - - /// To return the crossAt value - num _updateCrossValue(num value, VisibleRange range) { - if (value < range.minimum) { - value = range.minimum; - } - if (value > range.maximum) { - value = range.maximum; - } - return value; - } - - /// Return the axis offset value for x and y axis - num? _getPrevAxisOffset( - List axesSize, Rect rect, int currentAxisIndex, String type) { - num? prevAxisOffsetValue; - if (currentAxisIndex > 0) { - for (int i = currentAxisIndex - 1; i >= 0; i--) { - final ChartAxisRenderer axisRenderer = axesSize[i].axisRenderer; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final Rect bounds = axisDetails.bounds; - final bool isLabelPositionInside = - axisDetails.axis.labelPosition == ChartDataLabelPosition.inside; - if (type == 'Left' && - ((axisDetails.labelOffset != null - ? axisDetails.labelOffset! - - axisDetails.maximumLabelSize.width - : bounds.left - bounds.width) < - rect.left)) { - prevAxisOffsetValue = axisDetails.labelOffset != null - ? axisDetails.labelOffset! - - (!isLabelPositionInside - ? axisDetails.maximumLabelSize.width - : 0) - - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.width - : 0) - - ((axisDetails.titleOffset != null) - ? axisDetails.titleHeight! - : 0) - : bounds.left - bounds.width; - break; - } else if (type == 'Bottom' && - ((axisDetails.labelOffset != null - ? axisDetails.labelOffset! + - axisDetails.maximumLabelSize.height - : bounds.top + bounds.height) > - rect.top + rect.height)) { - prevAxisOffsetValue = axisDetails.labelOffset != null - ? axisDetails.labelOffset! + - (!isLabelPositionInside - ? axisDetails.maximumLabelSize.height - : 0) + - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.height - : 0) + - ((axisDetails.titleOffset != null) - ? axisDetails.titleHeight! - : 0) - : bounds.top + bounds.height; - break; - } else if (type == 'Right' && - ((axisDetails.labelOffset != null - ? axisDetails.labelOffset! + - axisDetails.maximumLabelSize.width - : bounds.left + bounds.width) > - rect.left + rect.width)) { - prevAxisOffsetValue = axisDetails.labelOffset != null - ? axisDetails.labelOffset! + - (!isLabelPositionInside - ? axisDetails.maximumLabelSize.width - : 0) + - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.width - : 0) + - ((axisDetails.titleOffset != null) - ? axisDetails.titleHeight! - : 0) - : bounds.left + bounds.width; - break; - } else if (type == 'Top' && - ((axisDetails.labelOffset != null - ? axisDetails.labelOffset! - - axisDetails.maximumLabelSize.height - : bounds.top - bounds.height) < - rect.top)) { - prevAxisOffsetValue = axisDetails.labelOffset != null - ? axisDetails.labelOffset! - - (!isLabelPositionInside - ? axisDetails.maximumLabelSize.height - : 0) - - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.height - : 0) - - ((axisDetails.titleOffset != null) - ? axisDetails.titleHeight! - : 0) - : bounds.top - bounds.height; - break; - } - } - } - return prevAxisOffsetValue; - } - - /// Calculate axes bounds based on all axes - void _calculateAxesRect() { - _calculateSeriesClipRect(); - - /// Calculate the left axes rect - if (leftAxesCount.isNotEmpty) { - _calculateLeftAxesBounds(); - } - - /// Calculate the bottom axes rect - if (bottomAxesCount.isNotEmpty) { - _calculateBottomAxesBounds(); - } - - /// Calculate the right axes rect - if (rightAxesCount.isNotEmpty) { - _calculateRightAxesBounds(); - } - - /// Calculate the top axes rect - if (topAxesCount.isNotEmpty) { - _calculateTopAxesBounds(); - } - } - - /// Calculate the left axes bounds - void _calculateLeftAxesBounds() { - double axisSize, width; - final int axesLength = leftAxesCount.length; - final Rect rect = stateProperties.chartAxis.axisClipRect; - for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { - width = leftAxesCount[axisIndex].size; - final ChartAxisRenderer axisRenderer = - leftAxesCount[axisIndex].axisRenderer; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - ChartAxisRendererDetails crossAxisRendererDetails; - final ChartAxis axis = axisDetails.axis; - assert(axis.plotOffset >= 0, - 'The plot offset value of the axis must be greater than or equal to 0.'); - if (axisDetails.crossValue != null) { - crossAxisRendererDetails = - AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); - axisSize = (valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.width) + - rect.left; - if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisDetails.labelOffset = rect.left + - (axis.labelPosition == ChartDataLabelPosition.inside - ? axisLineLabelPadding! - : -axisLineLabelPadding!); - if (axis.title.text != null && axis.title.text!.isNotEmpty) { - axisDetails.titleOffset = - axis.labelPosition == ChartDataLabelPosition.inside - ? rect.left - axisLabelTitlePadding! - : rect.left - - axisLabelTitlePadding! - - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.width - : 0) - - axisDetails.maximumLabelSize.width; - } - } - } else { - final num? prevAxisOffsetValue = - _getPrevAxisOffset(leftAxesCount, rect, axisIndex, 'Left'); - axisSize = prevAxisOffsetValue == null - ? rect.left - : (prevAxisOffsetValue - - (axis.labelPosition == ChartDataLabelPosition.inside - ? (innerPadding + axisDetails.maximumLabelSize.width) - : 0)) - - (axis.tickPosition == TickPosition.inside - ? math.max( - axis.majorTickLines.size, - axis.minorTicksPerInterval > 0 - ? axis.minorTickLines.size - : 0) - : 0) - - axisPadding; - } - axisDetails.bounds = Rect.fromLTWH(axisSize, rect.top + axis.plotOffset, - width, rect.height - 2 * axis.plotOffset); - } - } - - /// Calculate the bottom axes bounds - void _calculateBottomAxesBounds() { - double axisSize, height; - final int axesLength = bottomAxesCount.length; - final Rect rect = stateProperties.chartAxis.axisClipRect; - for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { - height = bottomAxesCount[axisIndex].size; - final ChartAxisRenderer axisRenderer = - bottomAxesCount[axisIndex].axisRenderer; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - ChartAxisRendererDetails crossAxisRendererDetails; - final ChartAxis axis = axisDetails.axis; - assert(axis.plotOffset >= 0, - 'The plot offset value of the axis must be greater than or equal to 0.'); - if (axisDetails.crossValue != null) { - crossAxisRendererDetails = - AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); - axisSize = rect.top + - rect.height - - (valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.height); - if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisDetails.labelOffset = rect.top + - rect.height + - (axis.labelPosition == ChartDataLabelPosition.inside - ? -axisLineLabelPadding! - : axisLineLabelPadding!); - if (axis.title.text != null && axis.title.text!.isNotEmpty) { - axisDetails.titleOffset = - axis.labelPosition == ChartDataLabelPosition.inside - ? rect.top + rect.height + axisLabelTitlePadding! - : rect.top + - rect.height + - axisLabelTitlePadding! + - axisLineLabelPadding! + - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.height - : 0) + - axisDetails.maximumLabelSize.height; - } - } - } else { - final num? prevAxisOffsetValue = - _getPrevAxisOffset(bottomAxesCount, rect, axisIndex, 'Bottom'); - axisSize = (prevAxisOffsetValue == null) - ? rect.top + rect.height - : axisPadding + - prevAxisOffsetValue + - (axis.labelPosition == ChartDataLabelPosition.inside - ? (innerPadding + axisDetails.maximumLabelSize.height) - : 0) + - (axis.tickPosition == TickPosition.inside - ? math.max( - axis.majorTickLines.size, - axis.minorTicksPerInterval > 0 - ? axis.minorTickLines.size - : 0) - : 0); - } - axisDetails.bounds = Rect.fromLTWH(rect.left + axis.plotOffset, axisSize, - rect.width - 2 * axis.plotOffset, height); - } - } - - /// Calculate the right axes bounds - void _calculateRightAxesBounds() { - double axisSize, width; - final int axesLength = rightAxesCount.length; - final Rect rect = stateProperties.chartAxis.axisClipRect; - for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { - final ChartAxisRenderer axisRenderer = - rightAxesCount[axisIndex].axisRenderer; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - ChartAxisRendererDetails crossAxisRendererDetails; - final ChartAxis axis = axisDetails.axis; - assert(axis.plotOffset >= 0, - 'The plot offset value of the axis must be greater than or equal to 0.'); - width = rightAxesCount[axisIndex].size; - if (axisDetails.crossValue != null) { - crossAxisRendererDetails = - AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); - axisSize = rect.left + - (valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.width); - if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisDetails.labelOffset = rect.left + - rect.width + - (axis.labelPosition == ChartDataLabelPosition.inside - ? -axisLineLabelPadding! - : axisLineLabelPadding!); - if (axis.title.text != null && axis.title.text!.isNotEmpty) { - axisDetails.titleOffset = - axis.labelPosition == ChartDataLabelPosition.inside - ? rect.left + rect.width + axisLabelTitlePadding! - : rect.left + - rect.width + - axisLabelTitlePadding! + - axisLineLabelPadding! + - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.width - : 0) + - axisDetails.maximumLabelSize.width; - } - } - } else { - final num? prevAxisOffsetValue = - _getPrevAxisOffset(rightAxesCount, rect, axisIndex, 'Right'); - axisSize = (prevAxisOffsetValue == null) - ? rect.left + rect.width - : (prevAxisOffsetValue + - (axis.labelPosition == ChartDataLabelPosition.inside - ? axisDetails.maximumLabelSize.width + innerPadding - : 0)) + - (axis.tickPosition == TickPosition.inside - ? math.max( - axis.majorTickLines.size, - axis.minorTicksPerInterval > 0 - ? axis.minorTickLines.size - : 0) - : 0) + - axisPadding; - } - axisDetails.bounds = Rect.fromLTWH(axisSize, rect.top + axis.plotOffset, - width, rect.height - 2 * axis.plotOffset); - } - } - - /// Calculate the top axes bounds - void _calculateTopAxesBounds() { - double axisSize, height; - final int axesLength = topAxesCount.length; - final Rect rect = stateProperties.chartAxis.axisClipRect; - for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { - final ChartAxisRenderer axisRenderer = - topAxesCount[axisIndex].axisRenderer; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - ChartAxisRendererDetails crossAxisRendererDetails; - final ChartAxis axis = axisDetails.axis; - assert(axis.plotOffset >= 0, - 'The plot offset value of the axis must be greater than or equal to 0.'); - height = topAxesCount[axisIndex].size; - if (axisDetails.crossValue != null) { - crossAxisRendererDetails = - AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); - axisSize = rect.top + - rect.height - - (valueToCoefficient( - axisDetails.crossValue!, crossAxisRendererDetails) * - rect.height); - if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisDetails.labelOffset = rect.top + - (axis.labelPosition == ChartDataLabelPosition.inside - ? axisLineLabelPadding! - : -axisLineLabelPadding!); - if (axis.title.text != null && axis.title.text!.isNotEmpty) { - axisDetails.titleOffset = - axis.labelPosition == ChartDataLabelPosition.inside - ? rect.top - axisLabelTitlePadding! - : rect.top - - axisLabelTitlePadding! - - axisLineLabelPadding! - - (axisDetails.isMultiLevelLabelEnabled - ? axisDetails.multiLevelLabelTotalSize.height - : 0) - - axisDetails.maximumLabelSize.height; - } - } - } else { - final num? prevAxisOffsetValue = - _getPrevAxisOffset(topAxesCount, rect, axisIndex, 'Top'); - axisSize = (prevAxisOffsetValue == null) - ? rect.top - : prevAxisOffsetValue - - (axis.labelPosition == ChartDataLabelPosition.inside - ? (axisPadding + axisDetails.maximumLabelSize.height) - : 0) - - (axis.tickPosition == TickPosition.inside - ? math.max( - axis.majorTickLines.size, - axis.minorTicksPerInterval > 0 - ? axis.minorTickLines.size - : 0) - : 0) - - axisPadding; - } - axisDetails.bounds = Rect.fromLTWH(rect.left + axis.plotOffset, axisSize, - rect.width - 2 * axis.plotOffset, height); - } - } - - /// Calculate the visible axes - void calculateVisibleAxes() { - if (primaryXAxisRenderer != null) { - primaryXAxisRenderer!.dispose(); - } - - if (primaryYAxisRenderer != null) { - primaryYAxisRenderer!.dispose(); - } - - innerPadding = chartWidget.borderWidth; - axisPadding = 5; - axisClipRect = Rect.zero; - verticalAxisRenderers = []; - horizontalAxisRenderers = []; - axisRenderersCollection = []; - primaryXAxisRenderer = _getAxisRenderer(chartWidget.primaryXAxis); - primaryYAxisRenderer = _getAxisRenderer(chartWidget.primaryYAxis); - primaryXAxisDetails = - AxisHelper.getAxisRendererDetails(primaryXAxisRenderer!); - primaryYAxisDetails = - AxisHelper.getAxisRendererDetails(primaryYAxisRenderer!); - primaryXAxisDetails.name = (primaryXAxisDetails.name) ?? 'primaryXAxis'; - primaryYAxisDetails.name = primaryYAxisDetails.name ?? 'primaryYAxis'; - - final List _axesCollection = [ - chartWidget.primaryXAxis, - chartWidget.primaryYAxis - ]; - final List visibleSeriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers; - if (visibleSeriesRenderer.isNotEmpty) { - if (chartWidget.axes.isNotEmpty) { - _axesCollection.addAll(chartWidget.axes); - } - - for (int axisIndex = 0; axisIndex < _axesCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = axisIndex == 0 - ? primaryXAxisRenderer! - : (axisIndex == 1 - ? primaryYAxisRenderer! - : _getAxisRenderer(_axesCollection[axisIndex])); - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (axisDetails is CategoryAxisDetails) { - axisDetails.labels = []; - } else if (axisDetails is DateTimeCategoryAxisDetails) { - axisDetails.labels = []; - } - axisDetails.seriesRenderers = []; - for (int seriesIndex = 0; - seriesIndex < visibleSeriesRenderer.length; - seriesIndex++) { - final CartesianSeriesRenderer seriesRenderer = - visibleSeriesRenderer[seriesIndex]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - if ((axisDetails.name != null && - axisDetails.name == series.xAxisName) || - (series.xAxisName == null && - axisDetails.name == primaryXAxisDetails.name) || - (series.xAxisName != null && - axisDetails.name != series.xAxisName && - axisDetails.name == - stateProperties.chartAxis.primaryXAxisDetails.name)) { - axisDetails.orientation = stateProperties.requireInvertedAxis - ? AxisOrientation.vertical - : AxisOrientation.horizontal; - seriesRendererDetails.xAxisDetails = axisDetails; - axisDetails.seriesRenderers.add(seriesRenderer); - } else if ((axisDetails.name != null && - axisDetails.name == series.yAxisName) || - (series.yAxisName == null && - axisDetails.name == primaryYAxisDetails.name) || - (series.yAxisName != null && - axisDetails.name != series.yAxisName && - axisDetails.name == - stateProperties.chartAxis.primaryYAxisDetails.name)) { - axisDetails.orientation = stateProperties.requireInvertedAxis - ? AxisOrientation.horizontal - : AxisOrientation.vertical; - seriesRendererDetails.yAxisDetails = axisDetails; - axisDetails.seriesRenderers.add(seriesRenderer); - } - } - - ///Adding unmapped axes which were mapped with the indicators - if (axisDetails.orientation == null && - chartWidget.indicators.isNotEmpty) { - for (int i = 0; i < chartWidget.indicators.length; i++) { - if (chartWidget.indicators[i].isVisible) { - if (chartWidget.indicators[i].xAxisName == axisDetails.name) { - axisDetails.orientation = stateProperties.requireInvertedAxis - ? AxisOrientation.vertical - : AxisOrientation.horizontal; - } else if (chartWidget.indicators[i].yAxisName == - axisDetails.name) { - axisDetails.orientation = stateProperties.requireInvertedAxis - ? AxisOrientation.horizontal - : AxisOrientation.vertical; - } - } - } - } - if (axisDetails.orientation != null) { - axisDetails.orientation == AxisOrientation.vertical - ? verticalAxisRenderers.add(axisRenderer) - : horizontalAxisRenderers.add(axisRenderer); - } - final ChartAxisRenderer? oldAxisRenderer = - getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); - axisDetails.oldAxis = - stateProperties.renderingDetails.widgetNeedUpdate && - oldAxisRenderer != null - ? AxisHelper.getAxisRendererDetails(oldAxisRenderer).axis - : null; - axisRenderersCollection.add(axisRenderer); - } - } else { - stateProperties.chartAxis.primaryXAxisDetails.orientation = - stateProperties.requireInvertedAxis - ? AxisOrientation.vertical - : AxisOrientation.horizontal; - stateProperties.chartAxis.primaryYAxisDetails.orientation = - stateProperties.requireInvertedAxis - ? AxisOrientation.horizontal - : AxisOrientation.vertical; - horizontalAxisRenderers.add(primaryXAxisRenderer!); - verticalAxisRenderers.add(primaryYAxisRenderer!); - axisRenderersCollection.add(primaryXAxisRenderer!); - axisRenderersCollection.add(primaryYAxisRenderer!); - } - } - - ChartAxisRenderer _getAxisRenderer(ChartAxis axis) { - switch (axis.runtimeType) { - case NumericAxis: - return NumericAxisRenderer(axis as NumericAxis, stateProperties); - case LogarithmicAxis: - return LogarithmicAxisRenderer( - axis as LogarithmicAxis, stateProperties); - case CategoryAxis: - return CategoryAxisRenderer(axis as CategoryAxis, stateProperties); - case DateTimeAxis: - return DateTimeAxisRenderer(axis as DateTimeAxis, stateProperties); - case DateTimeCategoryAxis: - return DateTimeCategoryAxisRenderer( - axis as DateTimeCategoryAxis, stateProperties); - default: - return NumericAxisRenderer(axis as NumericAxis, stateProperties); - } - } -} 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 deleted file mode 100644 index 0eb502b35..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart +++ /dev/null @@ -1,476 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/rendering_details.dart'; -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/cartesian_state_properties.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'multi_level_labels.dart'; - -/// Represents the class for customize axis elements -abstract class CustomizeAxisElements { - /// To get axis line color - Color? getAxisLineColor(ChartAxis axis); - - ///To get axis line width - Color? getAxisMajorTickColor(ChartAxis axis, int majorTickIndex); - - /// To get major tick color - Color? getAxisMinorTickColor( - ChartAxis axis, int majorTickIndex, int minorTickIndex); - - /// To get major grid color - Color? getAxisMajorGridColor(ChartAxis axis, int majorGridIndex); - - /// To get minor grid color - Color? getAxisMinorGridColor( - ChartAxis axis, int majorGridIndex, int minorGridIndex); - - /// To get the axis line width - double getAxisLineWidth(ChartAxis axis); - - /// To get major tick width - double getAxisMajorTickWidth(ChartAxis axis, int majorTickIndex); - - /// To get minor tick width - double getAxisMinorTickWidth( - ChartAxis axis, int majorTickIndex, int minorTickIndex); - - /// To get major grid width - double getAxisMajorGridWidth(ChartAxis axis, int majorGridIndex); - - /// To get minor grid width - double getAxisMinorGridWidth( - ChartAxis axis, int majorGridIndex, int minorGridIndex); - - /// To get axis label text - String getAxisLabel(ChartAxis axis, String text, int labelIndex); - - /// To get axis label style - TextStyle getAxisLabelStyle(ChartAxis axis, String text, int labelIndex); - - /// To get angle of axis label - int getAxisLabelAngle( - ChartAxisRenderer axisRenderer, String text, int labelIndex); - - /// To draw horizontal axes lines - void drawHorizontalAxesLine( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw horizontal axes lines - void drawVerticalAxesLine( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw horizontal axes tick lines - void drawHorizontalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw vertical axes tick lines - void drawVerticalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw horizontal axes major grid lines - void drawHorizontalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart); - - /// To draw vertical axes major grid lines - void drawVerticalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart); - - /// To draw horizontal axes minor grid lines - void drawHorizontalAxesMinorLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - double tempInterval, - Rect rect, - num nextValue, - int index, - SfCartesianChart chart); - - /// To draw vertical axes minor grid lines - void drawVerticalAxesMinorTickLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - num tempInterval, - Rect rect, - int index, - SfCartesianChart chart); - - /// To draw horizontal axes labels - void drawHorizontalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw vertical axes labels - void drawVerticalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw horizontal axes title - void drawHorizontalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); - - /// To draw vertical axes title - void drawVerticalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); -} - -/// Represents the cartesian axis widgets -// ignore: must_be_immutable -class CartesianAxisWidget extends StatefulWidget { - /// Creates an instance for cartesian axis widget - // ignore: prefer_const_constructors_in_immutables - CartesianAxisWidget( - {required this.stateProperties, required this.renderType}); - - /// Specifies the cartesian state properties - final CartesianStateProperties stateProperties; - - /// Specifies the render types - String renderType; - - /// Specifies the cartesian axis widget state - // ignore: library_private_types_in_public_api - late _CartesianAxisWidgetState state; - - @override - State createState() => _CartesianAxisWidgetState(); -} - -class _CartesianAxisWidgetState extends State - with SingleTickerProviderStateMixin { - late List animationControllersList; - - /// Animation controller for axis - late AnimationController animationController; - - /// Repaint notifier for axis - late ValueNotifier axisRepaintNotifier; - - @override - void initState() { - axisRepaintNotifier = ValueNotifier(0); - animationController = AnimationController(vsync: this) - ..addListener(_repaintAxisElements); - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - animationController.duration = const Duration(milliseconds: 1000); - final Animation axisAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController, - curve: const Interval(0.1, 0.9, curve: Curves.decelerate), - )); - animationController.forward(from: 0.0); - // ignore: avoid_unnecessary_containers - return Container( - child: RepaintBoundary( - child: CustomPaint( - painter: _CartesianAxesPainter( - stateProperties: widget.stateProperties, - axisAnimation: axisAnimation, - renderType: widget.renderType, - isRepaint: widget.stateProperties.chartAxis.needsRepaint, - notifier: axisRepaintNotifier)))); - } - - void _animateAxis() { - final double animationFactor = animationController.value; - for (int axisIndex = 0; - axisIndex < - widget.stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - ///visibleMinimum and visibleMaximum not defined in the chart axis class, - /// so dynamic datatype used here - final ChartAxisRenderer axisRenderer = - widget.stateProperties.chartAxis.axisRenderersCollection[axisIndex]; - final dynamic axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - - ///visibleMinimum and visibleMaximum not defined in the chart axis class, - /// so dynamic datatype used here - dynamic oldAxisRenderer; - bool needAnimate = false; - if ((widget.stateProperties.requireInvertedAxis - ? axisDetails.orientation == AxisOrientation.vertical - : axisDetails.orientation == AxisOrientation.horizontal) && - // ignore: unnecessary_null_comparison - widget.stateProperties.oldAxisRenderers != null && - widget.stateProperties.oldAxisRenderers.isNotEmpty && - (axisDetails.axis.visibleMinimum != null || - axisDetails.axis.visibleMaximum != null)) { - oldAxisRenderer = getOldAxisRenderer( - axisRenderer, widget.stateProperties.oldAxisRenderers); - final dynamic oldAxisDetails = - AxisHelper.getAxisRendererDetails(oldAxisRenderer); - if (oldAxisRenderer != null && - (oldAxisDetails.axis.visibleMinimum != null && - oldAxisDetails.axis.visibleMaximum != null)) { - needAnimate = axisDetails.runtimeType == oldAxisDetails.runtimeType && - ((oldAxisDetails.axis.visibleMinimum != null && - oldAxisDetails.axis.visibleMinimum != - axisDetails.axis.visibleMinimum) || - (oldAxisDetails.axis.visibleMaximum != null && - oldAxisDetails.axis.visibleMaximum != - axisDetails.axis.visibleMaximum)) && - _checkSeriesAnimation(axisDetails.seriesRenderers); - if (needAnimate) { - if (axisRenderer is DateTimeAxisRenderer || - axisRenderer is DateTimeCategoryAxisRenderer) { - axisDetails.visibleMinimum = - (oldAxisDetails.axis.visibleMinimum.millisecondsSinceEpoch - - (oldAxisDetails.axis.visibleMinimum - .millisecondsSinceEpoch - - axisDetails.axis.visibleMinimum - .millisecondsSinceEpoch) * - animationFactor) - .toInt(); - axisDetails.visibleMaximum = - (oldAxisDetails.axis.visibleMaximum.millisecondsSinceEpoch - - (oldAxisDetails.axis.visibleMaximum - .millisecondsSinceEpoch - - axisDetails.axis.visibleMaximum - .millisecondsSinceEpoch) * - animationFactor) - .toInt(); - } else { - axisDetails.visibleMinimum = oldAxisDetails.axis.visibleMinimum - - (oldAxisDetails.axis.visibleMinimum - - axisDetails.axis.visibleMinimum) * - animationFactor; - axisDetails.visibleMaximum = oldAxisDetails.axis.visibleMaximum - - (oldAxisDetails.axis.visibleMaximum - - axisDetails.axis.visibleMaximum) * - animationFactor; - } - if (axisDetails is DateTimeCategoryAxisDetails) { - axisDetails.labels.clear(); - //ignore: prefer_foreach - for (final CartesianSeriesRenderer seriesRenderer - in axisDetails.seriesRenderers) { - widget.stateProperties.chartSeries.findSeriesMinMax( - SeriesHelper.getSeriesRendererDetails(seriesRenderer)); - } - } - axisDetails.calculateRangeAndInterval(widget.stateProperties); - for (final CartesianSeriesRenderer seriesRenderer - in axisDetails.seriesRenderers) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - seriesRendererDetails.calculateRegion = true; - seriesRendererDetails.repaintNotifier.value++; - if (seriesRendererDetails.series.dataLabelSettings.isVisible == - true && - widget.stateProperties.renderDataLabel?.state != null) { - widget.stateProperties.renderDataLabel?.state! - .dataLabelRepaintNotifier.value++; - } - } - } - } - } - } - } - - bool _checkSeriesAnimation(List seriesRenderers) { - for (int i = 0; i < seriesRenderers.length; i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderers[i]); - if (seriesRendererDetails.series.animationDuration <= 0) { - return false; - } - } - return true; - } - - @override - void dispose() { - disposeAnimationController(animationController, _repaintAxisElements); - super.dispose(); - } - - void _repaintAxisElements() { - _animateAxis(); - axisRepaintNotifier.value++; - } -} - -class _CartesianAxesPainter extends CustomPainter { - _CartesianAxesPainter( - {required this.stateProperties, - required this.isRepaint, - required ValueNotifier notifier, - required this.renderType, - required this.axisAnimation}) - : chart = stateProperties.chart, - super(repaint: notifier); - final CartesianStateProperties stateProperties; - final SfCartesianChart chart; - final bool isRepaint; - final String renderType; - final Animation axisAnimation; - - @override - void paint(Canvas canvas, Size size) { - _onAxisDraw(canvas); - } - - /// Draw method for axes - void _onAxisDraw(Canvas canvas) { - if (renderType == 'outside') { - _drawPlotAreaBorder(canvas); - if (chart.plotAreaBackgroundImage != null && - stateProperties.backgroundImage != null) { - paintImage( - canvas: canvas, - rect: stateProperties.chartAxis.axisClipRect, - image: stateProperties.backgroundImage!, - fit: BoxFit.fill); - } - } - _drawAxes(canvas); - } - - /// To draw a plot area border of a container - void _drawPlotAreaBorder(Canvas canvas) { - final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final Paint paint = Paint(); - paint.color = chart.plotAreaBorderColor ?? - renderingDetails.chartTheme.plotAreaBorderColor; - paint.style = PaintingStyle.stroke; - paint.strokeWidth = chart.plotAreaBorderWidth; - chart.plotAreaBorderWidth == 0 - ? paint.color = Colors.transparent - : paint.color = paint.color; - canvas.drawRect(axisClipRect, paint); - - canvas.drawRect( - axisClipRect, - Paint() - ..color = chart.plotAreaBackgroundColor ?? - renderingDetails.chartTheme.plotAreaBackgroundColor - ..style = PaintingStyle.fill); - } - - /// To draw horizontal axes - void _drawHorizontalAxes( - Canvas canvas, - ChartAxisRenderer axisRenderer, - double animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - if (axis.isVisible) { - if (axis.axisLine.width > 0 && renderType == 'outside') { - axisRenderer.drawHorizontalAxesLine(canvas, axisRenderer, chart); - } - if (axis.majorTickLines.width > 0 || axis.majorGridLines.width > 0) { - axisRenderer.drawHorizontalAxesTickLines(canvas, axisRenderer, chart, - renderType, animationFactor, oldAxisRenderer, needAnimate); - } - axisRenderer.drawHorizontalAxesLabels(canvas, axisRenderer, chart, - renderType, animationFactor, oldAxisRenderer, needAnimate); - if (axisDetails.isMultiLevelLabelEnabled) { - drawMultiLevelLabels(axisDetails, canvas); - } - if (renderType == 'outside') { - axisRenderer.drawHorizontalAxesTitle(canvas, axisRenderer, chart); - } - } - } - - /// To draw vertical axes - void _drawVerticalAxes( - Canvas canvas, - ChartAxisRenderer axisRenderer, - double animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - if (axis.isVisible) { - if (axis.axisLine.width > 0 && renderType == 'outside') { - axisRenderer.drawVerticalAxesLine(canvas, axisRenderer, chart); - } - if (axisDetails.visibleLabels.isNotEmpty && - (axis.majorTickLines.width > 0 || axis.majorGridLines.width > 0)) { - axisRenderer.drawVerticalAxesTickLines(canvas, axisRenderer, chart, - renderType, animationFactor, oldAxisRenderer, needAnimate); - } - axisRenderer.drawVerticalAxesLabels(canvas, axisRenderer, chart, - renderType, animationFactor, oldAxisRenderer, needAnimate); - if (axisDetails.isMultiLevelLabelEnabled) { - drawMultiLevelLabels(axisDetails, canvas); - } - if (renderType == 'outside') { - axisRenderer.drawVerticalAxesTitle(canvas, axisRenderer, chart); - } - } - } - - /// To draw chart axes - void _drawAxes(Canvas canvas) { - final double animationFactor = - // ignore: unnecessary_null_comparison - axisAnimation != null ? axisAnimation.value : 1; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - final ChartAxisRenderer axisRenderer = - stateProperties.chartAxis.axisRenderersCollection[axisIndex]; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - axisDetails.isInsideTickPosition = - axis.tickPosition == TickPosition.inside; - ChartAxisRenderer? oldAxisRenderer; - bool needAnimate = false; - // ignore: unnecessary_null_comparison - if (stateProperties.oldAxisRenderers != null && - stateProperties.oldAxisRenderers.isNotEmpty && - axisDetails.visibleRange != null) { - oldAxisRenderer = - getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); - if (oldAxisRenderer != null) { - final ChartAxisRendererDetails oldAxisDetails = - AxisHelper.getAxisRendererDetails(oldAxisRenderer); - if (oldAxisDetails.visibleRange != null) { - needAnimate = chart.enableAxisAnimation && - (oldAxisDetails.visibleRange!.minimum != - axisDetails.visibleRange!.minimum || - oldAxisDetails.visibleRange!.maximum != - axisDetails.visibleRange!.maximum); - } - } - } - axisDetails.orientation == AxisOrientation.horizontal - ? _drawHorizontalAxes(canvas, axisRenderer, animationFactor, - oldAxisRenderer, needAnimate) - : _drawVerticalAxes(canvas, axisRenderer, animationFactor, - oldAxisRenderer, needAnimate); - } - } - - @override - bool shouldRepaint(_CartesianAxesPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 166b7c006..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart +++ /dev/null @@ -1,639 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../common/event_args.dart'; -import '../../common/utils/typedef.dart' - show MultiLevelLabelFormatterCallback, ChartLabelFormatterCallback; -import '../axis/axis.dart'; -import '../axis/multi_level_labels.dart'; -import '../axis/plotband.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/interactive_tooltip.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// This class has the properties of the category axis. -/// -/// Category axis displays text labels instead of numbers. When the string values are bound to x values, then the x-axis -/// must be initialized with CategoryAxis. -/// -/// Provides the options for Label placement, arrange by index and interval used to customize the appearance. -@immutable -class CategoryAxis extends ChartAxis { - /// Creating an argument constructor of CategoryAxis class. - 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, - double? borderWidth, - Color? borderColor, - AxisBorderType? axisBorderType, - MultiLevelLabelFormatterCallback? multiLevelLabelFormatter, - MultiLevelLabelStyle? multiLevelLabelStyle, - List? multiLevelLabels, - AutoScrollingMode? autoScrollingMode, - ChartLabelFormatterCallback? axisLabelFormatter}) - : 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, - axisBorderType: axisBorderType, - borderColor: borderColor, - borderWidth: borderWidth, - multiLevelLabelStyle: multiLevelLabelStyle, - multiLevelLabels: multiLevelLabels, - multiLevelLabelFormatter: multiLevelLabelFormatter, - autoScrollingMode: autoScrollingMode, - axisLabelFormatter: axisLabelFormatter); - - /// Position of the 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: CategoryAxis(labelPlacement: LabelPlacement.onTicks), - /// ) - /// ); - ///} - ///``` - final LabelPlacement labelPlacement; - - /// Plots the data points based on the index value. - /// - /// By default, data points will be - /// grouped and plotted based on the x-value. They can also be grouped by the data - /// point index value. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis(arrangeByIndex: true), - /// ) - /// ); - ///} - ///``` - final bool arrangeByIndex; - - /// The minimum value of the axis. - /// - /// The axis will start from this value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: CategoryAxis(minimum: 0), - /// ) - /// ); - ///} - ///``` - final double? minimum; - - /// The maximum value of the axis. - /// - /// The axis will end at this value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: CategoryAxis(maximum: 10), - /// ) - /// ); - ///} - ///``` - final double? maximum; - - /// The minimum visible value of the axis. The axis is rendered from this value - /// initially. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: CategoryAxis(visibleMinimum: 0), - /// ) - /// ); - ///} - ///``` - final double? visibleMinimum; - - /// The maximum visible value of the axis. - /// - /// The axis is rendered to this value initially. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: CategoryAxis(visibleMaximum: 20), - /// )); - ///} - ///``` - final double? visibleMaximum; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is CategoryAxis && - other.name == name && - other.isVisible == isVisible && - other.title == title && - other.axisLine == axisLine && - other.arrangeByIndex == arrangeByIndex && - other.rangePadding == rangePadding && - other.labelPlacement == labelPlacement && - other.edgeLabelPlacement == edgeLabelPlacement && - other.labelPosition == labelPosition && - other.tickPosition == tickPosition && - other.labelRotation == labelRotation && - other.labelIntersectAction == labelIntersectAction && - other.labelAlignment == labelAlignment && - other.isInversed == isInversed && - other.opposedPosition == opposedPosition && - other.minorTicksPerInterval == minorTicksPerInterval && - other.maximumLabels == maximumLabels && - other.majorTickLines == majorTickLines && - other.minorTickLines == minorTickLines && - other.majorGridLines == majorGridLines && - other.minorGridLines == minorGridLines && - other.labelStyle == labelStyle && - other.plotOffset == plotOffset && - other.zoomFactor == zoomFactor && - other.zoomPosition == zoomPosition && - other.interactiveTooltip == interactiveTooltip && - other.minimum == minimum && - other.maximum == maximum && - other.interval == interval && - other.visibleMinimum == visibleMinimum && - other.visibleMaximum == visibleMaximum && - other.crossesAt == crossesAt && - other.associatedAxisName == associatedAxisName && - other.placeLabelsNearAxisLine == placeLabelsNearAxisLine && - other.plotBands == plotBands && - other.desiredIntervals == desiredIntervals && - other.rangeController == rangeController && - other.maximumLabelWidth == maximumLabelWidth && - other.labelsExtent == labelsExtent && - other.autoScrollingDelta == autoScrollingDelta && - other.axisBorderType == axisBorderType && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.multiLevelLabelStyle == multiLevelLabelStyle && - other.multiLevelLabels == multiLevelLabels && - other.multiLevelLabelFormatter == multiLevelLabelFormatter && - other.autoScrollingMode == autoScrollingMode && - other.axisLabelFormatter == axisLabelFormatter; - } - - @override - int get hashCode { - final List values = [ - name, - isVisible, - title, - axisLine, - arrangeByIndex, - rangePadding, - labelPlacement, - edgeLabelPlacement, - labelPosition, - tickPosition, - labelRotation, - labelIntersectAction, - labelAlignment, - isInversed, - opposedPosition, - minorTicksPerInterval, - maximumLabels, - majorTickLines, - minorTickLines, - majorGridLines, - minorGridLines, - labelStyle, - plotOffset, - zoomFactor, - zoomPosition, - interactiveTooltip, - minimum, - maximum, - interval, - visibleMinimum, - visibleMaximum, - crossesAt, - associatedAxisName, - placeLabelsNearAxisLine, - plotBands, - desiredIntervals, - rangeController, - maximumLabelWidth, - labelsExtent, - autoScrollingDelta, - axisBorderType, - borderColor, - borderWidth, - multiLevelLabelStyle, - multiLevelLabels, - multiLevelLabelFormatter, - autoScrollingMode, - axisLabelFormatter - ]; - return hashList(values); - } -} - -/// Creates an axis renderer for Category axis -class CategoryAxisRenderer extends ChartAxisRenderer { - /// Creating an argument constructor of CategoryAxisRenderer class. - CategoryAxisRenderer( - CategoryAxis _categoryAxis, CartesianStateProperties _stateProperties) { - _axisDetails = CategoryAxisDetails(_categoryAxis, this, _stateProperties); - AxisHelper.setAxisRendererDetails(this, _axisDetails); - } - - late CategoryAxisDetails _axisDetails; - - /// Calculates the visible range for an axis in chart. - @override - void calculateVisibleRange(Size availableSize) { - _axisDetails.setOldRangeFromRangeController(); - _axisDetails.visibleRange = - _axisDetails.stateProperties.rangeChangeBySlider && - _axisDetails.rangeMinimum != null && - _axisDetails.rangeMaximum != null - ? VisibleRange(_axisDetails.rangeMinimum, _axisDetails.rangeMaximum) - : VisibleRange(_axisDetails.actualRange!.minimum, - _axisDetails.actualRange!.maximum); - _axisDetails.visibleRange!.delta = _axisDetails.actualRange!.delta; - _axisDetails.visibleRange!.interval = _axisDetails.actualRange!.interval; - bool canAutoScroll = false; - if (_axisDetails._categoryAxis.autoScrollingDelta != null && - _axisDetails._categoryAxis.autoScrollingDelta! > 0 && - !_axisDetails.stateProperties.isRedrawByZoomPan) { - canAutoScroll = true; - _axisDetails.updateAutoScrollingDelta( - _axisDetails._categoryAxis.autoScrollingDelta!, this); - } - if ((!canAutoScroll || _axisDetails.stateProperties.zoomedState == true) && - !(_axisDetails.stateProperties.rangeChangeBySlider && - !_axisDetails.stateProperties.canSetRangeController)) { - _axisDetails.setZoomFactorAndPosition( - this, _axisDetails.stateProperties.zoomedAxisRendererStates); - } - if (_axisDetails.zoomFactor < 1 || - _axisDetails.zoomPosition > 0 || - (_axisDetails.axis.rangeController != null && - !_axisDetails.stateProperties.renderingDetails.initialRender!)) { - _axisDetails.stateProperties.zoomProgress = true; - _axisDetails.calculateZoomRange(this, availableSize); - if (_axisDetails.axis.rangeController != null && - _axisDetails.stateProperties.isRedrawByZoomPan && - _axisDetails.stateProperties.canSetRangeController && - _axisDetails.stateProperties.zoomProgress) { - _axisDetails.stateProperties.rangeChangedByChart = true; - _axisDetails.setRangeControllerValues(this); - } - } - _axisDetails.setZoomValuesFromRangeController(); - } - - /// Applies range padding - @override - void applyRangePadding(VisibleRange range, num? interval) { - ActualRangeChangedArgs rangeChangedArgs; - if (_axisDetails._categoryAxis.labelPlacement == - LabelPlacement.betweenTicks) { - range.minimum -= 0.5; - range.maximum += 0.5; - range.delta = range.maximum - range.minimum; - } - - if (!(_axisDetails._categoryAxis.minimum != null && - _axisDetails._categoryAxis.maximum != null)) { - ///Calculating range padding - _axisDetails.applyRangePaddings( - this, _axisDetails.stateProperties, range, interval!); - } - - calculateVisibleRange( - Size(_axisDetails.rect.width, _axisDetails.rect.height)); - - /// Setting range as visible zoomRange - if ((_axisDetails._categoryAxis.visibleMinimum != null || - _axisDetails._categoryAxis.visibleMaximum != null) && - (_axisDetails._categoryAxis.visibleMinimum != - _axisDetails._categoryAxis.visibleMaximum) && - (!_axisDetails.stateProperties.isRedrawByZoomPan)) { - _axisDetails.stateProperties.isRedrawByZoomPan = false; - _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum ?? - _axisDetails._categoryAxis.visibleMinimum ?? - _axisDetails.actualRange!.minimum; - _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum ?? - _axisDetails._categoryAxis.visibleMaximum ?? - _axisDetails.actualRange!.maximum; - if (_axisDetails._categoryAxis.labelPlacement == - LabelPlacement.betweenTicks) { - _axisDetails.visibleRange!.minimum = - _axisDetails._categoryAxis.visibleMinimum != null - ? (_axisDetails.visibleMinimum ?? - _axisDetails._categoryAxis.visibleMinimum!) - - 0.5 - : _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.maximum = - _axisDetails._categoryAxis.visibleMaximum != null - ? (_axisDetails.visibleMaximum ?? - _axisDetails._categoryAxis.visibleMaximum!) + - 0.5 - : _axisDetails.visibleRange!.maximum; - } - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = interval == null - ? calculateInterval(_axisDetails.visibleRange!, _axisDetails.axisSize) - : _axisDetails.visibleRange!.interval; - _axisDetails.zoomFactor = - _axisDetails.visibleRange!.delta / (range.delta); - _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - - _axisDetails.actualRange!.minimum) / - range.delta; - } - if (_axisDetails.chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs( - _axisDetails.name!, - _axisDetails._categoryAxis, - range.minimum, - range.maximum, - range.interval, - _axisDetails.orientation!); - rangeChangedArgs.visibleMin = _axisDetails.visibleRange!.minimum; - rangeChangedArgs.visibleMax = _axisDetails.visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _axisDetails.visibleRange!.interval; - _axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); - _axisDetails.visibleRange!.minimum = rangeChangedArgs.visibleMin; - _axisDetails.visibleRange!.maximum = rangeChangedArgs.visibleMax; - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; - _axisDetails.zoomFactor = - _axisDetails.visibleRange!.delta / (range.delta); - _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - - _axisDetails.actualRange!.minimum) / - range.delta; - } - } - - /// Generates the visible axis labels. - @override - void generateVisibleLabels() { - num tempInterval = _axisDetails.visibleRange!.minimum.ceil(); - int position; - String labelText; - _axisDetails.visibleLabels = []; - for (; - tempInterval <= _axisDetails.visibleRange!.maximum; - tempInterval += _axisDetails.visibleRange!.interval) { - if (withInRange(tempInterval, _axisDetails.visibleRange!)) { - position = tempInterval.round(); - if (position <= -1 || - (_axisDetails.labels.isNotEmpty && - position >= _axisDetails.labels.length)) { - continue; - } else if (_axisDetails.labels.isNotEmpty && - // ignore: unnecessary_null_comparison - _axisDetails.labels[position] != null) { - labelText = _axisDetails.labels[position]; - } else { - continue; - } - _axisDetails.triggerLabelRenderEvent(labelText, tempInterval); - } - } - - /// Get the maximum label of width and height in axis. - _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); - if (_axisDetails._categoryAxis.multiLevelLabels != null && - _axisDetails._categoryAxis.multiLevelLabels!.isNotEmpty) { - generateMultiLevelLabels(_axisDetails); - calculateMultiLevelLabelBounds(_axisDetails); - } - } - - /// Finds the interval of an axis. - @override - num calculateInterval(VisibleRange range, Size availableSize) => math - .max( - 1, - (_axisDetails.actualRange!.delta / - _axisDetails.calculateDesiredIntervalCount( - Size(_axisDetails.rect.width, _axisDetails.rect.height), - this)) - .floor()) - .toInt(); -} - -/// Represents the cartegory axis details class -class CategoryAxisDetails extends ChartAxisRendererDetails { - /// Creates an instance an in - CategoryAxisDetails(this._categoryAxis, ChartAxisRenderer axisRenderer, - CartesianStateProperties _stateProperties) - : super(_categoryAxis, _stateProperties, axisRenderer) { - labels = []; - } - - /// Specifies the list of labels - late List labels; - - /// Represents the rect value - late Rect rect; - final CategoryAxis _categoryAxis; - - /// Method to find the axis minimum and maximum value - void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - if (_categoryAxis.arrangeByIndex) { - // ignore: unnecessary_null_comparison - pointIndex < labels.length && labels[pointIndex] != null - ? labels[pointIndex] += ', ${point.x}' - : labels.add(point.x.toString()); - point.xValue = pointIndex; - } else { - if (!labels.contains(point.x.toString())) { - labels.add(point.x.toString()); - } - point.xValue = labels.indexOf(point.x.toString()); - } - point.yValue = point.y; - setCategoryMinMaxValues(axisRenderer, isXVisibleRange!, isYVisibleRange!, - point, pointIndex, dataLength, seriesRendererDetails); - } - - /// Listener for range controller - void _controlListener() { - stateProperties.canSetRangeController = false; - if (_categoryAxis.rangeController != null && - !stateProperties.rangeChangedByChart) { - updateRangeControllerValues(this); - stateProperties.rangeChangeBySlider = true; - stateProperties.redrawByRangeChange(); - } - } - - /// Calculate the range and interval - void calculateRangeAndInterval(CartesianStateProperties stateProperties, - [String? type]) { - chart = stateProperties.chart; - if (axis.rangeController != null) { - stateProperties.rangeChangeBySlider = true; - axis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - axisSize = Size(rect.width, rect.height); - axisRenderer.calculateRange(axisRenderer); - _calculateActualRange(); - if (actualRange != null) { - axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); - if (type == null && type != 'AxisCross' && _categoryAxis.isVisible) { - axisRenderer.generateVisibleLabels(); - } - } - } - - /// Calculate the required values of the actual range for the category axis - void _calculateActualRange() { - if (min == null && - max == null && - (stateProperties.zoomedState == true || stateProperties.zoomProgress)) { - min ??= 0; - max ??= 5; - } - if (min != null && max != null) { - actualRange = VisibleRange( - _categoryAxis.minimum ?? min, _categoryAxis.maximum ?? max); - CartesianSeriesRenderer seriesRenderer; - for (int i = 0; i < seriesRenderers.length; i++) { - seriesRenderer = seriesRenderers[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if ((actualRange!.maximum > - seriesRendererDetails.dataPoints.length - 1) == - true) { - for (int i = labels.length; i < actualRange!.maximum + 1; i++) { - labels.add(i.toString()); - } - } - } - actualRange = VisibleRange( - _categoryAxis.minimum ?? min, _categoryAxis.maximum ?? max); - - ///Below condition is for checking the min, max value is equal - if ((actualRange!.minimum == actualRange!.maximum) && - (_categoryAxis.labelPlacement == LabelPlacement.onTicks)) { - actualRange!.maximum += 1; - } - actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - actualRange!.interval = _categoryAxis.interval ?? - axisRenderer.calculateInterval( - actualRange!, Size(rect.width, rect.height)); - actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - } - } -} 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 deleted file mode 100644 index daabe11d1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart +++ /dev/null @@ -1,1114 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show DateFormat; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../common/event_args.dart'; -import '../../common/utils/typedef.dart' - show MultiLevelLabelFormatterCallback, ChartLabelFormatterCallback; -import '../axis/axis.dart'; -import '../axis/multi_level_labels.dart'; -import '../axis/plotband.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart' show updateErrorBarAxisRange; -import '../common/interactive_tooltip.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// This class holds the properties of the DateTime axis. -/// -/// The date-time axis uses a date-time scale and displays date-time values as axis labels in the specified format. -/// -/// The range of the Date time can be customized by [minimum] and [maximum] properties, also change data label format by the [dateFormat]. -/// -/// Provides the options for range padding, interval, date format for customizing the appearance. -/// -@immutable -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, - this.dateFormat, - this.intervalType = DateTimeIntervalType.auto, - InteractiveTooltip? interactiveTooltip, - this.labelFormat, - this.minimum, - this.maximum, - LabelAlignment? labelAlignment, - double? interval, - this.visibleMinimum, - this.visibleMaximum, - dynamic crossesAt, - String? associatedAxisName, - bool? placeLabelsNearAxisLine, - List? plotBands, - RangeController? rangeController, - int? desiredIntervals, - double? maximumLabelWidth, - double? labelsExtent, - this.autoScrollingDeltaType = DateTimeIntervalType.auto, - int? autoScrollingDelta, - double? borderWidth, - Color? borderColor, - AxisBorderType? axisBorderType, - MultiLevelLabelStyle? multiLevelLabelStyle, - MultiLevelLabelFormatterCallback? multiLevelLabelFormatter, - List? multiLevelLabels, - AutoScrollingMode? autoScrollingMode, - ChartLabelFormatterCallback? axisLabelFormatter}) - : 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, - axisBorderType: axisBorderType, - borderColor: borderColor, - borderWidth: borderWidth, - multiLevelLabelStyle: multiLevelLabelStyle, - multiLevelLabelFormatter: multiLevelLabelFormatter, - multiLevelLabels: multiLevelLabels, - autoScrollingMode: autoScrollingMode, - axisLabelFormatter: axisLabelFormatter); - - /// Formats the date-time axis labels. The default data-time axis label can be formatted - /// with various built-in date formats. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(dateFormat: DateFormat.y()), - /// ) - /// ); - ///} - ///``` - final DateFormat? dateFormat; - - /// Formats the date time-axis labels. The labels can be customized by adding desired - /// text to prefix or suffix. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(labelFormat: '{value}M'), - /// ) - /// ); - ///} - ///``` - 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, - /// 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: DateTimeAxis(intervalType: DateTimeIntervalType.years), - /// ) - /// ); - ///} - ///``` - final DateTimeIntervalType intervalType; - - /// Minimum value of the axis. The axis will start from this date. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(minimum: DateTime(2000)), - /// ) - /// ); - ///} - ///``` - final DateTime? minimum; - - /// Maximum value of the axis. The axis will end at this date. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(maximum: DateTime(2019)), - /// ) - /// ); - ///} - ///``` - final DateTime? maximum; - - /// The minimum visible value of the axis. The axis will be rendered from this date initially. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(visibleMinimum: DateTime(2000)), - /// ) - /// ); - ///} - ///``` - final DateTime? visibleMinimum; - - /// The maximum visible value of the axis. The axis will be rendered from this date initially. - /// - /// Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(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: DateTimeAxis(autoScrollingDeltaType: DateTimeIntervalType.months), - /// ) - /// ); - ///} - ///``` - final DateTimeIntervalType autoScrollingDeltaType; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is DateTimeAxis && - other.name == name && - other.isVisible == isVisible && - other.title == title && - other.axisLine == axisLine && - other.rangePadding == rangePadding && - other.edgeLabelPlacement == edgeLabelPlacement && - other.labelPosition == labelPosition && - other.tickPosition == tickPosition && - other.labelRotation == labelRotation && - other.labelIntersectAction == labelIntersectAction && - other.labelAlignment == labelAlignment && - other.isInversed == isInversed && - other.opposedPosition == opposedPosition && - other.minorTicksPerInterval == minorTicksPerInterval && - other.maximumLabels == maximumLabels && - other.majorTickLines == majorTickLines && - other.minorTickLines == minorTickLines && - other.majorGridLines == majorGridLines && - other.minorGridLines == minorGridLines && - other.labelStyle == labelStyle && - other.plotOffset == plotOffset && - other.zoomFactor == zoomFactor && - other.zoomPosition == zoomPosition && - other.interactiveTooltip == interactiveTooltip && - other.minimum == minimum && - other.maximum == maximum && - other.interval == interval && - other.visibleMinimum == visibleMinimum && - other.visibleMaximum == visibleMaximum && - other.crossesAt == crossesAt && - other.associatedAxisName == associatedAxisName && - other.placeLabelsNearAxisLine == placeLabelsNearAxisLine && - other.plotBands == plotBands && - other.desiredIntervals == desiredIntervals && - other.rangeController == rangeController && - other.maximumLabelWidth == maximumLabelWidth && - other.labelsExtent == labelsExtent && - other.dateFormat == dateFormat && - other.intervalType == intervalType && - other.autoScrollingDelta == autoScrollingDelta && - other.enableAutoIntervalOnZooming == enableAutoIntervalOnZooming && - other.axisBorderType == axisBorderType && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.multiLevelLabelStyle == multiLevelLabelStyle && - other.multiLevelLabels == multiLevelLabels && - other.multiLevelLabelFormatter == multiLevelLabelFormatter && - other.autoScrollingMode == autoScrollingMode && - other.axisLabelFormatter == axisLabelFormatter; - } - - @override - int get hashCode { - final List values = [ - name, - isVisible, - title, - axisLine, - rangePadding, - labelIntersectAction, - labelPosition, - tickPosition, - edgeLabelPlacement, - zoomFactor, - zoomPosition, - enableAutoIntervalOnZooming, - labelRotation, - isInversed, - opposedPosition, - minorTicksPerInterval, - maximumLabels, - plotOffset, - majorTickLines, - minorTickLines, - majorGridLines, - minorGridLines, - labelStyle, - dateFormat, - intervalType, - interactiveTooltip, - labelFormat, - minimum, - maximum, - labelAlignment, - interval, - visibleMinimum, - visibleMaximum, - crossesAt, - associatedAxisName, - placeLabelsNearAxisLine, - plotBands, - rangeController, - desiredIntervals, - maximumLabelWidth, - labelsExtent, - autoScrollingDeltaType, - autoScrollingDelta, - axisBorderType, - borderColor, - borderWidth, - multiLevelLabelStyle, - multiLevelLabels, - multiLevelLabelFormatter, - autoScrollingMode, - axisLabelFormatter - ]; - return hashList(values); - } -} - -/// Creates an axis renderer for Datetime axis -class DateTimeAxisRenderer extends ChartAxisRenderer { - /// Creating an argument constructor of DateTimeAxisRenderer class. - DateTimeAxisRenderer( - DateTimeAxis dateTimeAxis, CartesianStateProperties stateProperties) { - _axisDetails = DateTimeAxisDetails(dateTimeAxis, this, stateProperties); - AxisHelper.setAxisRendererDetails(this, _axisDetails); - } - - late DateTimeAxisDetails _axisDetails; - - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(VisibleRange range, num? interval) { - _axisDetails.min = range.minimum.toInt(); - _axisDetails.max = range.maximum.toInt(); - ActualRangeChangedArgs rangeChangedArgs; - if (_axisDetails.dateTimeAxis.minimum == null && - _axisDetails.dateTimeAxis.maximum == null) { - final ChartRangePadding rangePadding = - _axisDetails.calculateRangePadding(this, _axisDetails.chart); - final DateTime minimum = - DateTime.fromMillisecondsSinceEpoch(_axisDetails.min!.toInt()); - final DateTime maximum = - DateTime.fromMillisecondsSinceEpoch(_axisDetails.max!.toInt()); - if (rangePadding == ChartRangePadding.none) { - _axisDetails.min = minimum.millisecondsSinceEpoch; - _axisDetails.max = maximum.millisecondsSinceEpoch; - } else if (rangePadding == ChartRangePadding.additional || - rangePadding == ChartRangePadding.round) { - switch (_axisDetails.actualIntervalType) { - case DateTimeIntervalType.years: - _axisDetails._calculateYear( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.months: - _axisDetails._calculateMonth( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.days: - _axisDetails._calculateDay( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.hours: - _axisDetails._calculateHour( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.minutes: - _axisDetails._calculateMinute( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.seconds: - _axisDetails._calculateSecond( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.milliseconds: - _axisDetails._calculateMilliSecond( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.auto: - break; - } - } - } - range.minimum = _axisDetails.min; - range.maximum = _axisDetails.max; - range.delta = range.maximum - range.minimum; - - calculateVisibleRange(_axisDetails.axisSize); - - /// Setting range as visible zoomRange - if ((_axisDetails.dateTimeAxis.visibleMinimum != null || - _axisDetails.dateTimeAxis.visibleMaximum != null) && - (_axisDetails.dateTimeAxis.visibleMinimum != - _axisDetails.dateTimeAxis.visibleMaximum) && - (!_axisDetails.stateProperties.isRedrawByZoomPan)) { - _axisDetails.stateProperties.isRedrawByZoomPan = false; - _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum ?? - (_axisDetails.dateTimeAxis.visibleMinimum != null - ? _axisDetails.dateTimeAxis.visibleMinimum!.millisecondsSinceEpoch - : _axisDetails.actualRange!.minimum); - _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum ?? - (_axisDetails.dateTimeAxis.visibleMaximum != null - ? _axisDetails.dateTimeAxis.visibleMaximum!.millisecondsSinceEpoch - : _axisDetails.actualRange!.maximum); - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = - calculateInterval(_axisDetails.visibleRange!, _axisDetails.axisSize); - _axisDetails.visibleRange!.interval = - interval != null && interval % 1 != 0 - ? interval - : _axisDetails.visibleRange!.interval; - _axisDetails.zoomFactor = - _axisDetails.visibleRange!.delta / (range.delta); - _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - - _axisDetails.actualRange!.minimum) / - range.delta; - } - if (_axisDetails.chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs( - _axisDetails.name!, - _axisDetails.dateTimeAxis, - range.minimum, - range.maximum, - range.interval, - _axisDetails.orientation!); - rangeChangedArgs.visibleMin = _axisDetails.visibleRange!.minimum; - rangeChangedArgs.visibleMax = _axisDetails.visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _axisDetails.visibleRange!.interval; - _axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); - _axisDetails.visibleRange!.minimum = - rangeChangedArgs.visibleMin is DateTime - ? rangeChangedArgs.visibleMin.millisecondsSinceEpoch - : rangeChangedArgs.visibleMin; - _axisDetails.visibleRange!.maximum = - rangeChangedArgs.visibleMax is DateTime - ? rangeChangedArgs.visibleMax.millisecondsSinceEpoch - : rangeChangedArgs.visibleMax; - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; - _axisDetails.zoomFactor = - _axisDetails.visibleRange!.delta / (range.delta); - _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - - _axisDetails.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() { - _axisDetails.visibleLabels = []; - num prevInterval; - final List label = _axisDetails.visibleLabels; - int interval = _axisDetails.visibleRange!.minimum; - interval = _axisDetails._alignRangeStart( - this, interval, _axisDetails.visibleRange!.interval); - while (interval <= _axisDetails.visibleRange!.maximum) { - if (withInRange(interval, _axisDetails.visibleRange!)) { - prevInterval = (label.isNotEmpty) - ? _axisDetails - .visibleLabels[_axisDetails.visibleLabels.length - 1].value - : interval; - final DateFormat format = _axisDetails.dateTimeAxis.dateFormat ?? - getDateTimeLabelFormat(this, interval, prevInterval.toInt()); - - String labelText = - format.format(DateTime.fromMillisecondsSinceEpoch(interval)); - if (_axisDetails.dateTimeAxis.labelFormat != null && - _axisDetails.dateTimeAxis.labelFormat != '') { - labelText = _axisDetails.dateTimeAxis.labelFormat! - .replaceAll(RegExp('{value}'), labelText); - } - _axisDetails.triggerLabelRenderEvent(labelText, interval); - } - interval = _axisDetails - ._increaseDateTimeInterval( - this, interval, _axisDetails.visibleRange!.interval) - .millisecondsSinceEpoch; - } - - /// Get the maximum label of width and height in axis. - _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); - if (_axisDetails.dateTimeAxis.multiLevelLabels != null && - _axisDetails.dateTimeAxis.multiLevelLabels!.isNotEmpty) { - generateMultiLevelLabels(_axisDetails); - calculateMultiLevelLabelBounds(_axisDetails); - } - } - - /// Finds the interval of an axis. - @override - num calculateInterval(VisibleRange range, Size availableSize) => - calculateDateTimeNiceInterval(this, _axisDetails.axisSize, range).floor(); -} - -/// Represents the date time axis details -class DateTimeAxisDetails extends ChartAxisRendererDetails { - ///Argument constructor for DateTimeAxisDetails class - DateTimeAxisDetails(this.dateTimeAxis, ChartAxisRenderer axisRenderer, - CartesianStateProperties stateProperties) - : super(dateTimeAxis, stateProperties, axisRenderer); - - /// Holds the value of actual interval type - late DateTimeIntervalType actualIntervalType; - - /// Holds the value of date time interval - late int dateTimeInterval; - - /// Specifies the value of date time axis - final DateTimeAxis dateTimeAxis; - - /// To check the series has only one data point - bool isSingleDataPoint = false; - - /// Find the series min and max values of an series - void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - isSingleDataPoint = seriesRendererDetails.dataPoints.length == 1; - if (point.x != null) { - point.xValue = (point.x).millisecondsSinceEpoch; - } - final bool _anchorRangeToVisiblePoints = - seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; - final String seriesType = seriesRendererDetails.seriesType; - point.yValue = point.y; - if (isYVisibleRange!) { - seriesRendererDetails.minimumX ??= point.xValue; - seriesRendererDetails.maximumX ??= point.xValue; - } - if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && - !seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall') { - seriesRendererDetails.minimumY ??= point.yValue; - seriesRendererDetails.maximumY ??= point.yValue; - } - if (isYVisibleRange && point.xValue != null) { - seriesRendererDetails.minimumX = - math.min(seriesRendererDetails.minimumX!, point.xValue as num); - seriesRendererDetails.maximumX = - math.max(seriesRendererDetails.maximumX!, point.xValue as num); - } - if (isXVisibleRange || !_anchorRangeToVisiblePoints) { - if (point.yValue != null && - (!seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall')) { - seriesRendererDetails.minimumY = - math.min(seriesRendererDetails.minimumY!, point.yValue as num); - seriesRendererDetails.maximumY = - math.max(seriesRendererDetails.maximumY!, point.yValue as num); - } - if (point.high != null) { - highMin = math.min(highMin ?? point.high, point.high); - highMax = math.max(highMax ?? point.high, point.high); - } - if (point.low != null) { - lowMin = math.min(lowMin ?? point.low, point.low); - 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!); - } - 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; - seriesRendererDetails.minimumY = math.min( - (seriesRendererDetails.minimumY ?? point.yValue) as num, - point.yValue as num); - seriesRendererDetails.maximumY = math.max( - seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue); - } else if (seriesType == 'errorbar') { - updateErrorBarAxisRange(seriesRendererDetails, point); - } - } - if (pointIndex >= dataLength - 1) { - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') || - seriesType == 'boxandwhisker') { - lowMin ??= 0; - lowMax ??= 5; - highMin ??= 0; - highMax ??= 5; - seriesRendererDetails.minimumY = math.min(lowMin!, highMin!); - seriesRendererDetails.maximumY = math.max(lowMax!, highMax!); - } - seriesRendererDetails.minimumX ??= 2717008000; - seriesRendererDetails.maximumX ??= 13085008000; - } - } - - /// Listener for range controller - void _controlListener() { - stateProperties.canSetRangeController = false; - if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { - updateRangeControllerValues(this); - stateProperties.rangeChangeBySlider = true; - stateProperties.redrawByRangeChange(); - } - } - - /// Calculate axis range and interval - void calculateRangeAndInterval(CartesianStateProperties stateProperties, - [String? type]) { - chart = stateProperties.chart; - if (axis.rangeController != null) { - stateProperties.rangeChangeBySlider = true; - axis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - axisSize = Size(rect.width, rect.height); - axisRenderer.calculateRange(axisRenderer); - _calculateActualRange(); - if (actualRange != null) { - axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); - if (type == null && type != 'AxisCross' && dateTimeAxis.isVisible) { - axisRenderer.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 ??= 2717008000; - max ??= 13085008000; - //Default date-time value (January 1, 1970) converted into milliseconds - const int defaultTimeStamp = 2592000000; - actualRange = VisibleRange( - dateTimeAxis.minimum != null - ? dateTimeAxis.minimum!.millisecondsSinceEpoch - : min, - dateTimeAxis.maximum != null - ? dateTimeAxis.maximum!.millisecondsSinceEpoch - : max); - if (actualRange!.minimum == actualRange!.maximum) { - if (isSingleDataPoint && - dateTimeAxis.autoScrollingDelta != null && - dateTimeAxis.autoScrollingDelta! > 0) { - final num minMilliSeconds = const Duration(days: 1).inMilliseconds; - actualRange!.minimum = actualRange!.minimum - minMilliSeconds; - } else { - actualRange!.minimum = actualRange!.minimum - defaultTimeStamp; - actualRange!.maximum = actualRange!.maximum + defaultTimeStamp; - } - } - dateTimeInterval = - calculateDateTimeNiceInterval(axisRenderer, axisSize, actualRange!) - .floor(); - actualRange!.interval = dateTimeAxis.interval ?? dateTimeInterval; - actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - } - - /// Returns the range start values based on actual interval type - int _alignRangeStart( - DateTimeAxisRenderer axisRenderer, int startDate, num interval) { - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(startDate); - switch (axisRenderer._axisDetails.actualIntervalType) { - case DateTimeIntervalType.years: - final int year = - ((dateTime.year / interval).floor() * interval).floor(); - dateTime = DateTime(year, dateTime.month, dateTime.day, 0, 0, 0, 0); - break; - case DateTimeIntervalType.months: - final int month = ((dateTime.month / interval) * interval).floor(); - dateTime = DateTime(dateTime.year, month, dateTime.day, 0, 0, 0, 0); - break; - case DateTimeIntervalType.days: - final int day = ((dateTime.day / interval) * interval).floor(); - dateTime = DateTime(dateTime.year, dateTime.month, day, 0, 0, 0, 0); - break; - case DateTimeIntervalType.hours: - final int hour = - ((dateTime.hour / interval).floor() * interval).floor(); - dateTime = DateTime( - dateTime.year, dateTime.month, dateTime.day, hour, 0, 0, 0); - break; - case DateTimeIntervalType.minutes: - final int minute = - ((dateTime.minute / interval).floor() * interval).floor(); - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour, minute, 0, 0); - break; - case DateTimeIntervalType.seconds: - final int second = - ((dateTime.second / interval).floor() * interval).floor(); - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour, dateTime.minute, second, 0); - break; - case DateTimeIntervalType.milliseconds: - final int millisecond = - ((dateTime.millisecond / interval).floor() * interval).floor(); - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour, dateTime.minute, dateTime.second, millisecond, 0); - break; - case DateTimeIntervalType.auto: - break; - } - return dateTime.millisecondsSinceEpoch; - } - - /// Increase the range interval based on actual interval type - DateTime _increaseDateTimeInterval( - DateTimeAxisRenderer axisRenderer, int value, num dateInterval) { - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(value); - axisRenderer._axisDetails.visibleRange!.interval = dateInterval; - final bool isIntervalDecimal = dateInterval % 1 == 0; - final num interval = dateInterval; - if (isIntervalDecimal) { - final int interval = dateInterval.floor(); - switch (axisRenderer._axisDetails.actualIntervalType) { - case DateTimeIntervalType.years: - dateTime = DateTime(dateTime.year + interval, dateTime.month, - dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, 0); - break; - case DateTimeIntervalType.months: - dateTime = DateTime(dateTime.year, dateTime.month + interval, - dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, 0); - break; - case DateTimeIntervalType.days: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day + interval, - dateTime.hour, - dateTime.minute, - dateTime.second, - 0); - break; - case DateTimeIntervalType.hours: - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour + interval, dateTime.minute, dateTime.second, 0); - break; - case DateTimeIntervalType.minutes: - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour, dateTime.minute + interval, dateTime.second, 0); - break; - case DateTimeIntervalType.seconds: - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour, dateTime.minute, dateTime.second + interval, 0); - break; - case DateTimeIntervalType.milliseconds: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - dateTime.millisecond + interval); - break; - case DateTimeIntervalType.auto: - break; - } - } else { - switch (axisRenderer._axisDetails.actualIntervalType) { - case DateTimeIntervalType.years: - dateTime = DateTime( - dateTime.year, - dateTime.month + (interval * 12).floor(), - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - 0); - break; - case DateTimeIntervalType.months: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day + (interval * 30).floor(), - dateTime.hour, - dateTime.minute, - dateTime.second, - 0); - break; - case DateTimeIntervalType.days: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour + (interval * 24).floor(), - dateTime.minute, - dateTime.second, - 0); - break; - case DateTimeIntervalType.hours: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute + (interval * 60).floor(), - dateTime.second, - 0); - break; - case DateTimeIntervalType.minutes: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second + (interval * 60).floor(), - 0); - break; - case DateTimeIntervalType.seconds: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - (interval * 1000).floor()); - break; - case DateTimeIntervalType.milliseconds: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - dateTime.millisecond + interval.floor()); - break; - case DateTimeIntervalType.auto: - break; - } - } - return dateTime; - } - - /// Calculate year - void _calculateYear(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startYear = minimum.year; - final int endYear = maximum.year; - if (rangePadding == ChartRangePadding.additional) { - min = - DateTime(startYear - interval, 1, 1, 0, 0, 0).millisecondsSinceEpoch; - max = DateTime(endYear + interval, 1, 1, 0, 0, 0).millisecondsSinceEpoch; - } else { - min = DateTime(startYear, 0, 0, 0, 0, 0).millisecondsSinceEpoch; - max = DateTime(endYear, 11, 30, 23, 59, 59).millisecondsSinceEpoch; - } - } - - /// Calculate month - void _calculateMonth(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startMonth = minimum.month; - final int endMonth = maximum.month; - if (rangePadding == ChartRangePadding.round) { - min = - DateTime(minimum.year, startMonth, 0, 0, 0, 0).millisecondsSinceEpoch; - max = DateTime(maximum.year, endMonth, - DateTime(maximum.year, maximum.month, 0).day, 23, 59, 59) - .millisecondsSinceEpoch; - } else { - min = DateTime(minimum.year, startMonth + (-interval), 1, 0, 0, 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, endMonth + interval, endMonth == 2 ? 28 : 30, - 0, 0, 0) - .millisecondsSinceEpoch; - } - } - - /// Calculate day - void _calculateDay(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startDay = minimum.day; - final int endDay = maximum.day; - if (rangePadding == ChartRangePadding.round) { - min = DateTime(minimum.year, minimum.month, startDay, 0, 0, 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, endDay, 23, 59, 59) - .millisecondsSinceEpoch; - } else { - min = - DateTime(minimum.year, minimum.month, startDay + (-interval), 0, 0, 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, endDay + interval, 0, 0, 0) - .millisecondsSinceEpoch; - } - } - - /// Calculate hour - void _calculateHour(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startHour = ((minimum.hour / interval) * interval).toInt(); - final int endHour = maximum.hour + (minimum.hour - startHour).toInt(); - if (rangePadding == ChartRangePadding.round) { - min = DateTime(minimum.year, minimum.month, minimum.day, startHour, 0, 0) - .millisecondsSinceEpoch; - max = - DateTime(maximum.year, maximum.month, maximum.day, startHour, 59, 59) - .millisecondsSinceEpoch; - } else { - min = DateTime(minimum.year, minimum.month, minimum.day, - startHour + (-interval), 0, 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, - endHour + interval, 0, 0) - .millisecondsSinceEpoch; - } - } - - /// Calculate minute - void _calculateMinute(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startMinute = ((minimum.minute / interval) * interval).toInt(); - final int endMinute = - maximum.minute + (minimum.minute - startMinute).toInt(); - if (rangePadding == ChartRangePadding.round) { - min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, - startMinute, 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, - endMinute, 59) - .millisecondsSinceEpoch; - } else { - min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, - startMinute + (-interval), 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, - endMinute + interval, 0) - .millisecondsSinceEpoch; - } - } - - /// Calculate second - void _calculateSecond(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startSecond = ((minimum.second / interval) * interval).toInt(); - final int endSecond = - maximum.second + (minimum.second - startSecond).toInt(); - if (rangePadding == ChartRangePadding.round) { - min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, - minimum.minute, startSecond, 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, - maximum.minute, endSecond, 0) - .millisecondsSinceEpoch; - } else { - min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, - minimum.minute, startSecond + (-interval), 0) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, - maximum.minute, endSecond + interval, 0) - .millisecondsSinceEpoch; - } - } - - /// Calculate millisecond - void _calculateMilliSecond(DateTime minimum, DateTime maximum, - ChartRangePadding rangePadding, int interval) { - final int startMilliSecond = - ((minimum.millisecond / interval) * interval).toInt(); - final int endMilliSecond = - maximum.millisecond + (minimum.millisecond - startMilliSecond).toInt(); - if (rangePadding == ChartRangePadding.round) { - min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, - minimum.minute, minimum.second, startMilliSecond) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, - maximum.minute, maximum.second, endMilliSecond) - .millisecondsSinceEpoch; - } else { - min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, - minimum.minute, minimum.second, startMilliSecond + (-interval)) - .millisecondsSinceEpoch; - max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, - maximum.minute, maximum.second, endMilliSecond + interval) - .millisecondsSinceEpoch; - } - } - - ///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.milliseconds: - scrollingDelta = - Duration(milliseconds: dateTimeAxis.autoScrollingDelta!) - .inMilliseconds; - break; - case DateTimeIntervalType.auto: - scrollingDelta = dateTimeAxis.autoScrollingDelta!; - break; - } - super.updateAutoScrollingDelta(scrollingDelta, axisRenderer); - } - } -} 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 deleted file mode 100644 index 5a3497bfa..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart +++ /dev/null @@ -1,750 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show DateFormat; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../common/event_args.dart'; -import '../../common/utils/typedef.dart' - show MultiLevelLabelFormatterCallback, ChartLabelFormatterCallback; -import '../axis/axis.dart'; -import '../axis/multi_level_labels.dart'; -import '../axis/plotband.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/interactive_tooltip.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// 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. -@immutable -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, - double? borderWidth, - Color? borderColor, - AxisBorderType? axisBorderType, - MultiLevelLabelStyle? multiLevelLabelStyle, - MultiLevelLabelFormatterCallback? multiLevelLabelFormatter, - List? multiLevelLabels, - AutoScrollingMode? autoScrollingMode, - ChartLabelFormatterCallback? axisLabelFormatter}) - : 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, - axisBorderType: axisBorderType, - borderColor: borderColor, - borderWidth: borderWidth, - multiLevelLabelStyle: multiLevelLabelStyle, - multiLevelLabelFormatter: multiLevelLabelFormatter, - multiLevelLabels: multiLevelLabels, - autoScrollingMode: autoScrollingMode, - axisLabelFormatter: axisLabelFormatter); - - /// 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; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is DateTimeCategoryAxis && - other.name == name && - other.isVisible == isVisible && - other.title == title && - other.axisLine == axisLine && - other.rangePadding == rangePadding && - other.labelPlacement == labelPlacement && - other.edgeLabelPlacement == edgeLabelPlacement && - other.labelPosition == labelPosition && - other.tickPosition == tickPosition && - other.labelRotation == labelRotation && - other.labelIntersectAction == labelIntersectAction && - other.labelAlignment == labelAlignment && - other.isInversed == isInversed && - other.opposedPosition == opposedPosition && - other.minorTicksPerInterval == minorTicksPerInterval && - other.maximumLabels == maximumLabels && - other.majorTickLines == majorTickLines && - other.minorTickLines == minorTickLines && - other.majorGridLines == majorGridLines && - other.minorGridLines == minorGridLines && - other.labelStyle == labelStyle && - other.plotOffset == plotOffset && - other.zoomFactor == zoomFactor && - other.zoomPosition == zoomPosition && - other.interactiveTooltip == interactiveTooltip && - other.minimum == minimum && - other.maximum == maximum && - other.interval == interval && - other.visibleMinimum == visibleMinimum && - other.visibleMaximum == visibleMaximum && - other.crossesAt == crossesAt && - other.associatedAxisName == associatedAxisName && - other.placeLabelsNearAxisLine == placeLabelsNearAxisLine && - other.plotBands == plotBands && - other.desiredIntervals == desiredIntervals && - other.rangeController == rangeController && - other.maximumLabelWidth == maximumLabelWidth && - other.labelsExtent == labelsExtent && - other.autoScrollingDelta == autoScrollingDelta && - other.autoScrollingMode == autoScrollingMode && - other.intervalType == intervalType && - other.axisBorderType == axisBorderType && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.multiLevelLabelStyle == multiLevelLabelStyle && - other.multiLevelLabels == multiLevelLabels && - other.multiLevelLabelFormatter == multiLevelLabelFormatter && - other.dateFormat == dateFormat && - other.axisLabelFormatter == axisLabelFormatter; - } - - @override - int get hashCode { - final List values = [ - name, - isVisible, - title, - axisLine, - rangePadding, - labelPlacement, - edgeLabelPlacement, - labelPosition, - tickPosition, - labelRotation, - labelIntersectAction, - labelAlignment, - isInversed, - opposedPosition, - minorTicksPerInterval, - maximumLabels, - majorTickLines, - minorTickLines, - majorGridLines, - minorGridLines, - labelStyle, - plotOffset, - zoomFactor, - zoomPosition, - interactiveTooltip, - minimum, - maximum, - interval, - visibleMinimum, - visibleMaximum, - crossesAt, - associatedAxisName, - placeLabelsNearAxisLine, - plotBands, - desiredIntervals, - rangeController, - maximumLabelWidth, - labelsExtent, - autoScrollingDelta, - autoScrollingMode, - intervalType, - axisBorderType, - borderColor, - borderWidth, - multiLevelLabelStyle, - multiLevelLabels, - multiLevelLabelFormatter, - dateFormat, - axisLabelFormatter - ]; - return hashList(values); - } -} - -/// Creates an axis renderer for Category axis -class DateTimeCategoryAxisRenderer extends ChartAxisRenderer { - /// Creating an argument constructor of CategoryAxisRenderer class. - DateTimeCategoryAxisRenderer(DateTimeCategoryAxis dateTimeCategoryAxis, - CartesianStateProperties stateProperties) { - _axisDetails = DateTimeCategoryAxisDetails( - dateTimeCategoryAxis, stateProperties, this); - AxisHelper.setAxisRendererDetails(this, _axisDetails); - } - - late DateTimeCategoryAxisDetails _axisDetails; - - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(VisibleRange range, num? interval) { - ActualRangeChangedArgs rangeChangedArgs; - if (_axisDetails.dateTimeCategoryAxis.labelPlacement == - LabelPlacement.betweenTicks) { - range.minimum -= 0.5; - range.maximum += 0.5; - range.delta = range.maximum - range.minimum; - } - - if (_axisDetails.dateTimeCategoryAxis.isVisible && - !(_axisDetails.dateTimeCategoryAxis.minimum != null && - _axisDetails.dateTimeCategoryAxis.maximum != null)) { - ///Calculating range padding - _axisDetails.applyRangePaddings( - this, _axisDetails.stateProperties, range, interval!); - } - - calculateVisibleRange( - Size(_axisDetails.rect.width, _axisDetails.rect.height)); - - /// Setting range as visible zoomRange - if ((_axisDetails.dateTimeCategoryAxis.visibleMinimum != null || - _axisDetails.dateTimeCategoryAxis.visibleMaximum != null) && - (_axisDetails.dateTimeCategoryAxis.visibleMinimum != - _axisDetails.dateTimeCategoryAxis.visibleMaximum) && - _axisDetails.stateProperties.zoomedAxisRendererStates.isEmpty) { - _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum != null - ? _axisDetails.getEffectiveRange( - DateTime.fromMillisecondsSinceEpoch( - _axisDetails.visibleMinimum!.toInt()), - true) - : _axisDetails.getEffectiveRange( - _axisDetails.dateTimeCategoryAxis.visibleMinimum, true) ?? - _axisDetails.actualRange!.minimum; - _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum != null - ? _axisDetails.getEffectiveRange( - DateTime.fromMillisecondsSinceEpoch( - _axisDetails.visibleMaximum!.toInt()), - false) - : _axisDetails.getEffectiveRange( - _axisDetails.dateTimeCategoryAxis.visibleMaximum, false) ?? - _axisDetails.actualRange!.maximum; - if (_axisDetails.dateTimeCategoryAxis.labelPlacement == - LabelPlacement.betweenTicks) { - _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum != null - ? _axisDetails.getEffectiveRange( - DateTime.fromMillisecondsSinceEpoch( - _axisDetails.visibleMinimum!.toInt()), - true)! - - 0.5 - : (_axisDetails.dateTimeCategoryAxis.visibleMinimum != null - ? _axisDetails.getEffectiveRange( - _axisDetails.dateTimeCategoryAxis.visibleMinimum, - true)! - - 0.5 - : _axisDetails.visibleRange!.minimum); - _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum != null - ? _axisDetails.getEffectiveRange( - DateTime.fromMillisecondsSinceEpoch( - _axisDetails.visibleMaximum!.toInt()), - false)! + - 0.5 - : (_axisDetails.dateTimeCategoryAxis.visibleMaximum != null - ? _axisDetails.getEffectiveRange( - _axisDetails.dateTimeCategoryAxis.visibleMaximum, - false)! + - 0.5 - : _axisDetails.visibleRange!.maximum); - } - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = interval == null - ? calculateInterval(_axisDetails.visibleRange!, _axisDetails.axisSize) - : _axisDetails.visibleRange!.interval; - _axisDetails.zoomFactor = - _axisDetails.visibleRange!.delta / (range.delta); - _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - - _axisDetails.actualRange!.minimum) / - range.delta; - } - if (_axisDetails.chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs( - _axisDetails.name!, - _axisDetails.dateTimeCategoryAxis, - range.minimum, - range.maximum, - range.interval, - _axisDetails.orientation!); - rangeChangedArgs.visibleMin = _axisDetails.visibleRange!.minimum; - rangeChangedArgs.visibleMax = _axisDetails.visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _axisDetails.visibleRange!.interval; - _axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); - _axisDetails.visibleRange!.minimum = rangeChangedArgs.visibleMin - is DateTime - ? _axisDetails.getEffectiveRange(rangeChangedArgs.visibleMin, true) - : rangeChangedArgs.visibleMin; - _axisDetails.visibleRange!.maximum = rangeChangedArgs.visibleMax - is DateTime - ? _axisDetails.getEffectiveRange(rangeChangedArgs.visibleMax, false) - : rangeChangedArgs.visibleMax; - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; - _axisDetails.zoomFactor = - _axisDetails.visibleRange!.delta / (range.delta); - _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - - _axisDetails.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 = _axisDetails.visibleRange!.minimum.ceil(); - int position; - num prevInterval; - String labelText; - final List label = _axisDetails.visibleLabels; - _axisDetails.visibleLabels = []; - prevInterval = (label.isNotEmpty) - ? _axisDetails - .visibleLabels[_axisDetails.visibleLabels.length - 1].value - : tempInterval; - _axisDetails.dateTimeFormat = - _axisDetails.dateTimeCategoryAxis.dateFormat ?? - getDateTimeLabelFormat( - this, tempInterval.toInt(), prevInterval.toInt()); - for (; - tempInterval <= _axisDetails.visibleRange!.maximum; - tempInterval += _axisDetails.visibleRange!.interval) { - if (withInRange(tempInterval, _axisDetails.visibleRange!)) { - position = tempInterval.round(); - if (position <= -1 || - (_axisDetails.labels.isNotEmpty && - position >= _axisDetails.labels.length)) { - continue; - } else if (_axisDetails.labels.isNotEmpty && - // ignore: unnecessary_null_comparison - _axisDetails.labels[position] != null) { - labelText = _axisDetails.getFormattedLabel( - _axisDetails.labels[position], _axisDetails.dateFormat); - _axisDetails.labels[position] = labelText; - } else { - continue; - } - _axisDetails.triggerLabelRenderEvent(labelText, tempInterval); - } - } - - /// Get the maximum label of width and height in axis. - _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); - if (_axisDetails.dateTimeCategoryAxis.multiLevelLabels != null && - _axisDetails.dateTimeCategoryAxis.multiLevelLabels!.isNotEmpty) { - generateMultiLevelLabels(_axisDetails); - calculateMultiLevelLabelBounds(_axisDetails); - } - } - - /// Finds the interval of an axis. - @override - num calculateInterval(VisibleRange range, Size availableSize) => math - .max( - 1, - (_axisDetails.actualRange!.delta / - _axisDetails.calculateDesiredIntervalCount( - Size(_axisDetails.rect.width, _axisDetails.rect.height), - this)) - .floor()) - .toInt(); -} - -/// This class holds the details of DateTimeCategoryAxis -class DateTimeCategoryAxisDetails extends ChartAxisRendererDetails { - ///Argument constructor for DateTimeCategoryAxisDetails class - DateTimeCategoryAxisDetails(this.dateTimeCategoryAxis, - CartesianStateProperties stateProperties, ChartAxisRenderer axisRenderer) - : super(dateTimeCategoryAxis, stateProperties, axisRenderer) { - labels = []; - } - - /// Holds the list of labels - late List labels; - - /// Holds the value of rect - late Rect rect; - - /// Specifies the date time category axis - final DateTimeCategoryAxis dateTimeCategoryAxis; - - /// Specifies the actual interval type - late DateTimeIntervalType actualIntervalType; - - /// Specifies the date time format - DateFormat? dateTimeFormat; - - /// Gets the value of date time format - DateFormat get dateFormat => - dateTimeFormat ?? getDateTimeLabelFormat(axisRenderer); - - /// Find the series min and max values of an series - void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - final bool _anchorRangeToVisiblePoints = - seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; - final String seriesType = seriesRendererDetails.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!) { - seriesRendererDetails.minimumX ??= point.xValue; - seriesRendererDetails.maximumX ??= point.xValue; - } - if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && - !seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall') { - seriesRendererDetails.minimumY ??= point.yValue; - seriesRendererDetails.maximumY ??= point.yValue; - } - setCategoryMinMaxValues(axisRenderer, isXVisibleRange, isYVisibleRange, - point, pointIndex, dataLength, seriesRendererDetails); - } - - /// Listener for range controller - void _controlListener() { - stateProperties.canSetRangeController = false; - if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { - updateRangeControllerValues(this); - stateProperties.rangeChangeBySlider = true; - stateProperties.redrawByRangeChange(); - } - } - - /// Calculate axis range and interval - void calculateRangeAndInterval(CartesianStateProperties stateProperties, - [String? type]) { - chart = stateProperties.chart; - if (axis.rangeController != null) { - stateProperties.rangeChangeBySlider = true; - axis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - axisSize = Size(rect.width, rect.height); - axisRenderer.calculateRange(axisRenderer); - _calculateActualRange(); - if (actualRange != null) { - axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); - if (type == null && - type != 'AxisCross' && - dateTimeCategoryAxis.isVisible) { - axisRenderer.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( - dateTimeCategoryAxis.minimum != null - ? getEffectiveRange(dateTimeCategoryAxis.minimum, true) - : min, - 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(axisRenderer, axisSize, actualRange!, - startAndEnd[0], startAndEnd[1]) - .floor(); - } else { - actualIntervalType = DateTimeIntervalType.days; - } - actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - actualRange!.interval = dateTimeCategoryAxis.interval ?? - axisRenderer.calculateInterval( - actualRange!, Size(rect.width, rect.height)); - } - - /// Method to get the formatted labels - String getFormattedLabel(String label, DateFormat dateFormat) { - return dateFormat - .format(DateTime.fromMicrosecondsSinceEpoch(int.parse(label))); - } - - List _getStartAndEndDate(List labels) { - final List values = [...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)) - ]; - } - - /// Method to get the effective range - num? getEffectiveRange(DateTime? rangeDate, bool needMin) { - num index = 0; - if (rangeDate == null) { - return null; - } - for (final String label in labels) { - final int 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; - } -} 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 deleted file mode 100644 index b804c6906..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart +++ /dev/null @@ -1,673 +0,0 @@ -import 'dart:math' as math; -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show NumberFormat; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../common/event_args.dart'; -import '../../common/utils/typedef.dart' - show MultiLevelLabelFormatterCallback, ChartLabelFormatterCallback; -import '../axis/axis.dart'; -import '../axis/multi_level_labels.dart'; -import '../axis/plotband.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart' show updateErrorBarAxisRange; -import '../common/interactive_tooltip.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Logarithmic axis uses logarithmic scale and displays numbers as axis labels. -/// -/// Provides options to customize the range of log axis, use the [minimum], [maximum], and [interval] properties. -/// By default, the range will be calculated automatically based on the provided data. -/// -/// _Note:_ This is only applicable for [SfCartesianChart]. -@immutable -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, - this.minimum, - this.maximum, - 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, - int? autoScrollingDelta, - double? borderWidth, - Color? borderColor, - AxisBorderType? axisBorderType, - MultiLevelLabelStyle? multiLevelLabelStyle, - MultiLevelLabelFormatterCallback? multiLevelLabelFormatter, - List? multiLevelLabels, - AutoScrollingMode? autoScrollingMode, - ChartLabelFormatterCallback? axisLabelFormatter}) - : 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, - axisBorderType: axisBorderType, - borderColor: borderColor, - borderWidth: borderWidth, - multiLevelLabelStyle: multiLevelLabelStyle, - multiLevelLabelFormatter: multiLevelLabelFormatter, - multiLevelLabels: multiLevelLabels, - autoScrollingMode: autoScrollingMode, - axisLabelFormatter: axisLabelFormatter); - - /// Formats the numeric axis labels. - /// - /// The labels can be customized by adding desired text as prefix or suffix. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: LogaithmicAxis(labelFormat: '{value}M'), - /// ) - /// ); - ///} - ///``` - final String? labelFormat; - - /// Formats the logarithmic axis labels with globalized label formats. - /// - /// Provides the ability to format a number in a locale-specific way. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: LogarithmicAxis(numberFormat: NumberFormat.currencyCompact()), - /// ) - /// ); - ///} - ///``` - final NumberFormat? numberFormat; - - /// The minimum value of the axis. - /// - /// The axis will start from this value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: LogarithmicAxis(minimum: 0), - /// ) - /// ); - ///} - ///``` - final double? minimum; - - /// The maximum value of the axis. - /// The axis will end at this value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: LogarithmicAxis(maximum: 10), - /// ) - /// ); - ///} - ///``` - final double? maximum; - - /// The base value for logarithmic axis. - /// The axislabel will render this base value.i.e 10,100,1000 and so on. - /// - /// Defaults to `10`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: LogarithmicAxis(logBase: 10), - /// ) - /// ); - ///} - ///``` - final double logBase; - - /// The minimum visible value of the axis. - /// - /// The axis will be rendered from this value initially. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: LogarithmicAxis(visibleMinimum: 0), - /// ) - /// ); - ///} - ///``` - final double? visibleMinimum; - - /// The minimum visible value of the axis. - /// - /// The axis will be rendered from this value initially. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: LogarithmicAxis(visibleMaximum: 200), - /// ) - /// ); - ///} - ///``` - final double? visibleMaximum; - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is LogarithmicAxis && - other.name == name && - other.isVisible == isVisible && - other.title == title && - other.axisLine == axisLine && - other.numberFormat == numberFormat && - other.labelFormat == labelFormat && - other.rangePadding == rangePadding && - other.logBase == logBase && - other.edgeLabelPlacement == edgeLabelPlacement && - other.labelPosition == labelPosition && - other.tickPosition == tickPosition && - other.labelRotation == labelRotation && - other.labelIntersectAction == labelIntersectAction && - other.labelAlignment == labelAlignment && - other.isInversed == isInversed && - other.opposedPosition == opposedPosition && - other.minorTicksPerInterval == minorTicksPerInterval && - other.maximumLabels == maximumLabels && - other.majorTickLines == majorTickLines && - other.minorTickLines == minorTickLines && - other.majorGridLines == majorGridLines && - other.minorGridLines == minorGridLines && - other.labelStyle == labelStyle && - other.plotOffset == plotOffset && - other.zoomFactor == zoomFactor && - other.zoomPosition == zoomPosition && - other.interactiveTooltip == interactiveTooltip && - other.minimum == minimum && - other.maximum == maximum && - other.interval == interval && - other.visibleMinimum == visibleMinimum && - other.visibleMaximum == visibleMaximum && - other.crossesAt == crossesAt && - other.associatedAxisName == associatedAxisName && - other.placeLabelsNearAxisLine == placeLabelsNearAxisLine && - other.plotBands == plotBands && - other.desiredIntervals == desiredIntervals && - other.rangeController == rangeController && - other.maximumLabelWidth == maximumLabelWidth && - other.labelsExtent == labelsExtent && - other.autoScrollingDelta == autoScrollingDelta && - other.axisBorderType == axisBorderType && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.multiLevelLabelStyle == multiLevelLabelStyle && - other.multiLevelLabels == multiLevelLabels && - other.multiLevelLabelFormatter == multiLevelLabelFormatter && - other.autoScrollingMode == autoScrollingMode && - other.axisLabelFormatter == axisLabelFormatter; - } - - @override - int get hashCode { - final List values = [ - name, - isVisible, - title, - axisLine, - numberFormat, - labelFormat, - rangePadding, - logBase, - edgeLabelPlacement, - labelPosition, - tickPosition, - labelRotation, - labelIntersectAction, - labelAlignment, - isInversed, - opposedPosition, - minorTicksPerInterval, - maximumLabels, - majorTickLines, - minorTickLines, - majorGridLines, - minorGridLines, - labelStyle, - plotOffset, - zoomFactor, - zoomPosition, - interactiveTooltip, - minimum, - maximum, - interval, - visibleMinimum, - visibleMaximum, - crossesAt, - associatedAxisName, - placeLabelsNearAxisLine, - plotBands, - desiredIntervals, - rangeController, - maximumLabelWidth, - labelsExtent, - autoScrollingDelta, - axisBorderType, - borderColor, - borderWidth, - multiLevelLabelStyle, - multiLevelLabels, - multiLevelLabelFormatter, - autoScrollingMode, - axisLabelFormatter - ]; - return hashList(values); - } -} - -/// Creates an axis renderer for Logarithmic axis -class LogarithmicAxisRenderer extends ChartAxisRenderer { - /// Creating an argument constructor of LogarithmicAxisRenderer class. - LogarithmicAxisRenderer(LogarithmicAxis logarithmicAxis, - CartesianStateProperties stateProperties) { - _axisDetails = - LogarithmicAxisDetails(logarithmicAxis, stateProperties, this); - AxisHelper.setAxisRendererDetails(this, _axisDetails); - } - - late LogarithmicAxisDetails _axisDetails; - - /// Calculates the visible range for an axis in chart. - @override - void calculateVisibleRange(Size availableSize) { - _axisDetails.setOldRangeFromRangeController(); - _axisDetails.visibleRange = - _axisDetails.stateProperties.rangeChangeBySlider && - _axisDetails.rangeMinimum != null && - _axisDetails.rangeMaximum != null - ? VisibleRange(_axisDetails.rangeMinimum, _axisDetails.rangeMaximum) - : VisibleRange(_axisDetails.actualRange!.minimum, - _axisDetails.actualRange!.maximum); - _axisDetails.visibleRange!.delta = _axisDetails.actualRange!.delta; - _axisDetails.visibleRange!.interval = _axisDetails.actualRange!.interval; - bool canAutoScroll = false; - if (_axisDetails.logarithmicAxis.autoScrollingDelta != null && - _axisDetails.logarithmicAxis.autoScrollingDelta! > 0 && - !_axisDetails.stateProperties.isRedrawByZoomPan) { - canAutoScroll = true; - _axisDetails.updateAutoScrollingDelta( - _axisDetails.logarithmicAxis.autoScrollingDelta!, this); - } - if ((!canAutoScroll || _axisDetails.stateProperties.zoomedState == true) && - !(_axisDetails.stateProperties.rangeChangeBySlider && - !_axisDetails.stateProperties.canSetRangeController)) { - _axisDetails.setZoomFactorAndPosition( - this, _axisDetails.stateProperties.zoomedAxisRendererStates); - } - if (_axisDetails.zoomFactor < 1 || - _axisDetails.zoomPosition > 0 || - (_axisDetails.axis.rangeController != null && - !_axisDetails - .stateProperties.renderingDetails.initialRender!) && - !(_axisDetails.stateProperties.rangeChangeBySlider || - !_axisDetails.stateProperties.canSetRangeController)) { - _axisDetails.stateProperties.zoomProgress = true; - _axisDetails.calculateZoomRange(this, availableSize); - _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - - _axisDetails.visibleRange!.minimum; - _axisDetails.visibleRange!.interval = - _axisDetails.axis.enableAutoIntervalOnZooming && - _axisDetails.stateProperties.zoomProgress - ? (_axisDetails.axisRenderer as LogarithmicAxisRenderer) - .calculateLogNiceInterval(_axisDetails.visibleRange!.delta) - : _axisDetails.visibleRange!.interval; - _axisDetails.visibleRange!.interval = - _axisDetails.visibleRange!.interval.floor() == 0 - ? 1 - : _axisDetails.visibleRange!.interval.floor(); - if (_axisDetails.axis.rangeController != null && - _axisDetails.stateProperties.isRedrawByZoomPan && - _axisDetails.stateProperties.canSetRangeController && - _axisDetails.stateProperties.zoomProgress) { - _axisDetails.stateProperties.rangeChangedByChart = true; - _axisDetails.setRangeControllerValues(this); - } - } - _axisDetails.setZoomValuesFromRangeController(); - } - - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(VisibleRange range, num interval) {} - - /// Generates the visible axis labels. - @override - void generateVisibleLabels() { - num tempInterval = _axisDetails.visibleRange!.minimum; - String labelText; - _axisDetails.visibleLabels = []; - for (; - tempInterval <= _axisDetails.visibleRange!.maximum; - tempInterval += _axisDetails.visibleRange!.interval) { - labelText = - pow(_axisDetails.logarithmicAxis.logBase, tempInterval).toString(); - - labelText = double.parse(labelText) < 1 - ? labelText - : double.parse(labelText).floor().toString(); - - if (_axisDetails.logarithmicAxis.numberFormat != null) { - labelText = _axisDetails.logarithmicAxis.numberFormat! - .format(pow(_axisDetails.logarithmicAxis.logBase, tempInterval)); - } - - if (_axisDetails.logarithmicAxis.labelFormat != null && - _axisDetails.logarithmicAxis.labelFormat != '') { - labelText = _axisDetails.logarithmicAxis.labelFormat! - .replaceAll(RegExp('{value}'), labelText); - } - _axisDetails.triggerLabelRenderEvent(labelText, tempInterval); - } - - /// Get the maximum label of width and height in axis. - _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); - if (_axisDetails.logarithmicAxis.multiLevelLabels != null && - _axisDetails.logarithmicAxis.multiLevelLabels!.isNotEmpty) { - generateMultiLevelLabels(_axisDetails); - calculateMultiLevelLabelBounds(_axisDetails); - } - } - - /// Finds the interval of an axis. - @override - num? calculateInterval(VisibleRange range, Size availableSize) => null; - - /// To get the axis interval for logarithmic axis - num calculateLogNiceInterval(num delta) { - final List intervalDivisions = [10, 5, 2, 1]; - final num actualDesiredIntervalCount = - _axisDetails.calculateDesiredIntervalCount( - _axisDetails.axisSize, _axisDetails.axisRenderer); - num niceInterval = delta; - final num minInterval = - math.pow(10, calculateLogBaseValue(niceInterval, 10).floor()); - for (int i = 0; i < intervalDivisions.length; i++) { - final num interval = intervalDivisions[i]; - final num currentInterval = minInterval * interval; - if (actualDesiredIntervalCount < (delta / currentInterval)) { - break; - } - niceInterval = currentInterval; - } - return niceInterval; - } -} - -/// This class holds the details of LogarithmicAxis -class LogarithmicAxisDetails extends ChartAxisRendererDetails { - ///Argument constructor for LogarthmicAxisDetails class - LogarithmicAxisDetails(this.logarithmicAxis, - CartesianStateProperties stateProperties, ChartAxisRenderer axisRenderer) - : super(logarithmicAxis, stateProperties, axisRenderer); - - /// Specifies the value of logarithmic axis - final LogarithmicAxis logarithmicAxis; - - /// Find the series min and max values of an series - void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - final String seriesType = seriesRendererDetails.seriesType; - point.xValue = point.x; - point.yValue = point.y; - seriesRendererDetails.minimumX ??= point.xValue; - seriesRendererDetails.maximumX ??= point.xValue; - if (!seriesType.contains('range') && - (!seriesType.contains('hilo')) && - (!seriesType.contains('candle'))) { - seriesRendererDetails.minimumY ??= point.yValue; - seriesRendererDetails.maximumY ??= point.yValue; - } - lowMin ??= point.low; - lowMax ??= point.low; - highMin ??= point.high; - highMax ??= point.high; - if (point.xValue != null) { - seriesRendererDetails.minimumX = - math.min(seriesRendererDetails.minimumX!, point.xValue as num); - seriesRendererDetails.maximumX = - math.max(seriesRendererDetails.maximumX!, point.xValue as num); - } - if (point.yValue != null && - (!seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle'))) { - seriesRendererDetails.minimumY = - math.min(seriesRendererDetails.minimumY!, point.yValue as num); - seriesRendererDetails.maximumY = - math.max(seriesRendererDetails.maximumY!, point.yValue as num); - } - if (point.high != null) { - 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); - } - if (seriesType == 'errorbar') { - updateErrorBarAxisRange(seriesRendererDetails, point); - } - if (pointIndex >= dataLength - 1) { - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle')) { - lowMin ??= 0; - lowMax ??= 5; - highMin ??= 0; - highMax ??= 5; - seriesRendererDetails.minimumY = - math.min(lowMin!.toDouble(), highMin!.toDouble()); - seriesRendererDetails.maximumY = - math.max(lowMax!.toDouble(), highMax!.toDouble()); - } - seriesRendererDetails.minimumX ??= 0; - seriesRendererDetails.minimumY ??= 0; - seriesRendererDetails.maximumX ??= 5; - seriesRendererDetails.maximumY ??= 5; - } - } - - /// Listener for range controller - void _controlListener() { - stateProperties.canSetRangeController = false; - if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { - updateRangeControllerValues(this); - stateProperties.rangeChangeBySlider = true; - stateProperties.redrawByRangeChange(); - } - } - - /// Calculate the range and interval - void calculateRangeAndInterval(CartesianStateProperties stateProperties, - [String? type]) { - chart = stateProperties.chart; - if (logarithmicAxis.rangeController != null) { - stateProperties.rangeChangeBySlider = true; - logarithmicAxis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - axisSize = Size(rect.width, rect.height); - axisRenderer.calculateRange(axisRenderer); - _calculateActualRange(); - axisRenderer.calculateVisibleRange(axisSize); - - /// Setting range as visible zoomRange - if ((logarithmicAxis.visibleMinimum != null || - logarithmicAxis.visibleMaximum != null) && - (logarithmicAxis.visibleMinimum != logarithmicAxis.visibleMaximum) && - (!stateProperties.isRedrawByZoomPan)) { - stateProperties.isRedrawByZoomPan = false; - visibleRange!.minimum = logarithmicAxis.visibleMinimum != null - ? (math.log(logarithmicAxis.visibleMinimum!) / (math.log(10))).round() - : actualRange!.minimum; - visibleRange!.maximum = logarithmicAxis.visibleMaximum != null - ? (math.log(logarithmicAxis.visibleMaximum!) / (math.log(10))).round() - : actualRange!.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) / range.delta; - } - if (type == null && type != 'AxisCross' && logarithmicAxis.isVisible) { - axisRenderer.generateVisibleLabels(); - } - } - - /// Calculate the required values of the actual range for logarithmic axis - void _calculateActualRange() { - num logStart, logEnd; - this.min ??= 0; - this.max ??= 5; - this.min = logarithmicAxis.minimum ?? this.min; - this.max = logarithmicAxis.maximum ?? this.max; - actualRange = VisibleRange(this.min, this.max); - if (axis.anchorRangeToVisiblePoints && - needCalculateYrange(logarithmicAxis.minimum, logarithmicAxis.maximum, - stateProperties, orientation!)) { - final VisibleRange range = calculateYRangeOnZoomX(actualRange!, this); - this.min = range.minimum; - this.max = range.maximum; - } - this.min = this.min! < 0 ? 0 : this.min; - logStart = calculateLogBaseValue(this.min!, logarithmicAxis.logBase); - logStart = logStart.isFinite ? logStart : this.min!; - logEnd = calculateLogBaseValue(this.max!, logarithmicAxis.logBase); - logEnd = logEnd.isFinite ? logEnd : this.max!; - this.min = (logStart / 1).floor(); - this.max = (logEnd / 1).ceil(); - if (this.min == this.max) { - this.max = this.max! + 1; - } - actualRange = VisibleRange(this.min, this.max); - actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - actualRange!.interval = logarithmicAxis.interval ?? - (axisRenderer as LogarithmicAxisRenderer) - .calculateLogNiceInterval(actualRange!.delta); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/multi_level_labels.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/multi_level_labels.dart deleted file mode 100644 index 26e10fae3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/multi_level_labels.dart +++ /dev/null @@ -1,1582 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../../charts.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../utils/helper.dart'; - -/// Class which holds the properties of multi-level labels -class ChartMultiLevelLabel { - /// Constructor for ChartMultiLevelLabel class - const ChartMultiLevelLabel({this.start, this.end, int? level, this.text}) - : level = level ?? 0; - - /// Start value of the multi-level label. - /// The value from where the multi-level label border needs to start. - /// - /// The [start] value for the category axis needs to string type, in the case of - /// date-time or date-time category axes need to be date-time and in the - /// case of numeric or logarithmic axes need to double. - /// - /// Defaults to 'null' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: - /// NumericAxis(multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final T? start; - - /// End value of the multi-level label. - /// The value where the multi-level label border needs to end. - /// - /// The [end] value for the category axis need to string type, in the case of - /// date-time or date-time category axes need to be date-time and in the - /// case of numeric or logarithmic axes need to double. - /// - /// Defaults to 'null' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: - /// NumericAxis(multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final T? end; - - /// Text to be displayed in grouping label as the multi-level label. - /// - /// Defaults to 'null' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: - /// NumericAxis(multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final String? text; - - /// Level specifies in which row the multi-level label should be positioned. - /// - /// Defaults to '0' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: - /// NumericAxis(multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First Level', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final int? level; -} - -/// Provides options to customize the start, the end value of a multi-level label, -/// text, and level of the multi-level labels. -/// -/// The [start] and [end] values need to be double type. -/// -///```dart -/// Widget build(BuildContext context) { -/// return Container( -/// child: SfCartesianChart( -/// primaryXAxis: NumericAxis( -/// multiLevelLabels: const [ -/// NumericMultiLevelLabel( -/// start: 0, -/// end: 2, -/// text: 'First', -/// ), -/// NumericMultiLevelLabel( -/// start: 2, -/// end: 4, -/// text: 'Second', -/// ) -/// ] -/// ) -/// ) -/// ); -/// } -///``` -class NumericMultiLevelLabel extends ChartMultiLevelLabel { - /// Constructor for NumericMultiLevelLabel class - const NumericMultiLevelLabel({this.start, this.end, int? level, String? text}) - : super(start: start, end: end, text: text, level: level); - - @override - final double? start; - - @override - final double? end; -} - -/// Provides options to customize the start, the end value of a multi-level label, -/// text, and level of the multi-level labels. -/// -/// The [start] and [end] value need to be string type. -/// -///```dart -/// Widget build(BuildContext context) { -/// return Container( -/// child: SfCartesianChart( -/// primaryXAxis: CategoryAxis( -/// multiLevelLabels: const [ -/// CategoricalMultiLevelLabel(start: 'Jan', end: 'Feb', text: 'First') -/// ] -/// ) -/// ) -/// ); -/// } -///``` -class CategoricalMultiLevelLabel extends ChartMultiLevelLabel { - /// Constructor for CategoricalMultiLevelLabel class - const CategoricalMultiLevelLabel( - {this.start, this.end, int? level, String? text}) - : super(start: start, end: end, text: text, level: level); - - @override - final String? start; - - @override - final String? end; -} - -/// Provides options to customize the start, the end value of a multi-level label, -/// text, and level of the multi-level labels. -/// -/// The [start] and [end] value need to be date-time type. -/// -///```dart -/// Widget build(BuildContext context) { -/// return Container( -/// child: SfCartesianChart( -/// primaryXAxis: DateTimeAxis( -/// multiLevelLabels: [ -/// DateTimeMultiLevelLabel( -/// start: DateTime(2020, 2, 3), -/// end: DateTime(2020, 2, 5), -/// text: 'First' -/// ) -/// ] -/// ) -/// ) -/// ); -/// } -///``` -class DateTimeMultiLevelLabel extends ChartMultiLevelLabel { - /// Constructor for DateTimeMultiLevelLabel class - const DateTimeMultiLevelLabel( - {this.start, this.end, int? level, String? text}) - : super(start: start, end: end, text: text, level: level); - - @override - final DateTime? start; - - @override - final DateTime? end; -} - -/// Provides options to customize the start, the end value of a multi-level label, -/// text, and level of the multi-level labels. -/// -/// The [start] and [end] value need to be date-time type. -/// -///```dart -/// Widget build(BuildContext context) { -/// return Container( -/// child: SfCartesianChart( -/// primaryXAxis: DateTimeCategoryAxis( -/// multiLevelLabels: [ -/// DateTimeCategoricalMultiLevelLabel( -/// start: DateTime(2020, 2, 3), -/// end: DateTime(2020, 2, 5), -/// text: 'First' -/// ) -/// ] -/// ) -/// ) -/// ); -/// } -///``` -class DateTimeCategoricalMultiLevelLabel extends DateTimeMultiLevelLabel { - /// Constructor for DateTimeCategoryMultiLevelLabel class - const DateTimeCategoricalMultiLevelLabel( - {this.start, this.end, int? level, String? text}) - : super(start: start, end: end, text: text, level: level); - - @override - final DateTime? start; - - @override - final DateTime? end; -} - -/// Provides options to customize the start, the end value of a multi-level label, -/// text, and level of the multi-level labels. -/// -/// The [start] and [end] values need to be double type. -/// -///```dart -/// Widget build(BuildContext context) { -/// return Container( -/// child: SfCartesianChart( -/// primaryXAxis: -/// LogarithmicAxis(multiLevelLabels: const [ -/// LogarithmicMultiLevelLabel( -/// start: 0, -/// end: 4, -/// text: 'First', -/// ) -/// ] -/// ) -/// ) -/// ); -/// } -///``` -class LogarithmicMultiLevelLabel extends NumericMultiLevelLabel { - /// Constructor for LogarithmicMultiLevelLabel class - const LogarithmicMultiLevelLabel( - {this.start, this.end, int? level, String? text}) - : super(start: start, end: end, text: text, level: level); - - @override - final double? start; - - @override - final double? end; -} - -/// Customize the multi-level label’s border color, width, type, and -/// text style such as color, font size, etc. -/// -/// When the multi-level label’s width exceeds its respective segment, -/// then the label will get trimmed and on tapping / hovering over the trimmed label, -/// a tooltip will be shown. -/// -/// Also refer [multiLevelLabelFormatter]. -/// -class MultiLevelLabelStyle { - /// Creating an argument constructor of MultiLevelLabelStyle class. - const MultiLevelLabelStyle( - {this.textStyle = const TextStyle( - fontSize: 12.0, - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontFamily: 'Normal'), - this.borderWidth = 0, - this.borderColor, - this.borderType = MultiLevelBorderType.rectangle}); - - /// Specifies the text style of the multi-level labels. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabelStyle: MultiLevelLabelStyle( - /// textStyle: TextStyle( - /// fontSize: 10, - /// color: Colors.black, - /// ) - /// ), - /// multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final TextStyle? textStyle; - - /// Specifies the border width of multi-level labels. - /// - /// Defaults to '0' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabelStyle: MultiLevelLabelStyle( - /// borderWidth: 2.0 - /// ), - /// multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final double borderWidth; - - /// Specifies the border color of multi-level labels. - /// - /// Defaults to 'null' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabelStyle: MultiLevelLabelStyle( - /// borderColor: Colors.black - /// ), - /// multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final Color? borderColor; - - /// Specifies the border type of multi-level labels. - /// - /// Defaults to 'MultiLevelLabelBorderType.rectangle' - /// - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// multiLevelLabelStyle: MultiLevelLabelStyle( - /// borderType: MultiLevelBorderType.curlyBrace - /// ), - /// multiLevelLabels: const [ - /// NumericMultiLevelLabel( - /// start: 0, - /// end: 4, - /// text: 'First', - /// ) - /// ] - /// ) - /// ) - /// ); - /// } - ///``` - final MultiLevelBorderType borderType; -} - -/// Holds the multi-level axis label information -class AxisMultiLevelLabel { - /// Creating an argument constructor of MultiLevelLabel class - AxisMultiLevelLabel( - this.actualStart, this.actualEnd, this.index, this.level, this.text); - - /// Specifies the label text style - TextStyle? textStyle; - - /// Hold the size of the label - Size? labelSize; - - /// Contains the text of the label - String? text; - - /// Contains the trimmed text of the label - String? trimmedText; - - /// Contains the label text to be rendered - String? renderText; - - /// Stores the index value of multi-level label - int? index; - - /// Stores the re-ordered level - int? actualLevel; - - /// Stores the level given by the user - int? level; - - /// Stores the actual start value - num? actualStart; - - /// Stores the actual end value - num? actualEnd; - - /// To store the rect region for trimmed labels tooltip rendering - Rect? multiLabelRegion; -} - -/// To set the axis border end -void setAxisBorderEndPoint( - ChartAxisRendererDetails axisRendererDetails, double borderStartPoint) { - final ChartAxis axis = axisRendererDetails.axis; - double borderEndPoint = 0.0; - final bool isLabelPositionInside = - axis.labelPosition == ChartDataLabelPosition.inside; - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - final double labelHeight = axisRendererDetails.maximumLabelSize.height; - borderEndPoint = axis.opposedPosition == false - ? (isLabelPositionInside - ? borderStartPoint - labelHeight - : borderStartPoint + labelHeight) - : (isLabelPositionInside - ? borderStartPoint + labelHeight - : borderStartPoint - labelHeight); - } else { - final double labelWidth = axisRendererDetails.maximumLabelSize.width; - borderEndPoint = axis.opposedPosition == false - ? (isLabelPositionInside - ? borderStartPoint + labelWidth - : borderStartPoint - labelWidth) - : (isLabelPositionInside - ? borderStartPoint - labelWidth - : borderStartPoint + labelWidth); - } - if (axis.majorTickLines.width > 0 || axis.minorTickLines.width > 0) { - final double maximumTickHeight = - math.max(axis.majorTickLines.size, axis.minorTickLines.size); - if (axisRendererDetails.isInsideTickPosition! == isLabelPositionInside) { - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - borderEndPoint = axis.opposedPosition == false - ? (isLabelPositionInside - ? borderEndPoint - maximumTickHeight - : borderEndPoint + maximumTickHeight) - : (isLabelPositionInside - ? borderEndPoint + maximumTickHeight - : borderEndPoint - maximumTickHeight); - } else { - borderEndPoint = axis.opposedPosition == false - ? (isLabelPositionInside - ? borderEndPoint + maximumTickHeight - : borderEndPoint - maximumTickHeight) - : (isLabelPositionInside - ? borderEndPoint - maximumTickHeight - : borderEndPoint + maximumTickHeight); - } - } - } - if (axisRendererDetails.crossValue != null && - axisRendererDetails.crossRange != null && - !axis.placeLabelsNearAxisLine) { - borderEndPoint = _getAxisCrossingBorderEnd(axisRendererDetails); - } - axisRendererDetails.axisBorderEnd = borderEndPoint; -} - -/// To get the axis border end while axis-crossing -double _getAxisCrossingBorderEnd(ChartAxisRendererDetails axisRendererDetails) { - final ChartAxis axis = axisRendererDetails.axis; - double borderEndPoint = 0.0; - const double axisLabelLinePadding = 5.0; - final double labelOffset = axisRendererDetails.labelOffset!; - final Size labelSize = axisRendererDetails.maximumLabelSize; - final bool isOpposed = axis.opposedPosition; - final bool isHorizontalAxis = - axisRendererDetails.orientation == AxisOrientation.horizontal; - if (axis.labelPosition == ChartDataLabelPosition.outside) { - borderEndPoint = labelOffset + - (isHorizontalAxis - ? (isOpposed ? -labelSize.height : labelSize.height) + - (isOpposed ? axisLabelLinePadding : -axisLabelLinePadding) - : (isOpposed ? labelSize.width : -labelSize.width) + - (isOpposed ? -axisLabelLinePadding : axisLabelLinePadding)); - } else { - borderEndPoint = labelOffset + - (isHorizontalAxis - ? (isOpposed ? labelSize.height : -labelSize.height) + - (isOpposed ? -axisLabelLinePadding : axisLabelLinePadding) - : (isOpposed ? -labelSize.width : labelSize.width) + - (isOpposed ? axisLabelLinePadding : -axisLabelLinePadding)); - } - return borderEndPoint; -} - -/// To render horizontal axis border -void renderHorizontalAxisBorder( - ChartAxisRendererDetails axisRendererDetails, - Canvas canvas, - double pointX, - double pointY, - bool isBetweenTicks, - double betweenTickPositionValue, - int index) { - final Rect bounds = axisRendererDetails.bounds; - final ChartAxis axis = axisRendererDetails.axis; - final Paint borderPaint = Paint() - ..strokeWidth = axis.borderWidth - ..color = axis.borderColor != Colors.transparent - ? axis.borderColor! - : axisRendererDetails.renderingDetails.chartTheme.axisLineColor - ..style = PaintingStyle.stroke; - if (axisRendererDetails.crossValue != null && - axisRendererDetails.crossRange != null && - !axis.placeLabelsNearAxisLine) { - final double axisBorderEnd = axisRendererDetails.axisBorderEnd!; - final double maximumLabelHeight = - axisRendererDetails.maximumLabelSize.height; - pointY = axisBorderEnd + - ((axis.labelPosition == ChartDataLabelPosition.outside) - ? (axis.opposedPosition ? maximumLabelHeight : -maximumLabelHeight) - : (axis.opposedPosition - ? -maximumLabelHeight - : maximumLabelHeight)); - } - final double startOffsetX = pointX + betweenTickPositionValue; - final Offset borderStartOffset = Offset(startOffsetX, pointY); - final Offset borderEndOffset = - Offset(startOffsetX, axisRendererDetails.axisBorderEnd!); - if (isBetweenTicks || - (bounds.left.roundToDouble() < borderStartOffset.dx.roundToDouble() && - bounds.right.roundToDouble() > - borderStartOffset.dx.roundToDouble())) { - canvas.drawLine(borderStartOffset, borderEndOffset, borderPaint); - if (axis.axisBorderType == AxisBorderType.rectangle && index == 0) { - canvas.drawLine(Offset(bounds.left, pointY), Offset(bounds.right, pointY), - borderPaint); - canvas.drawLine(Offset(bounds.left, borderEndOffset.dy), - Offset(bounds.right, borderEndOffset.dy), borderPaint); - } - } -} - -/// To render vertical axis border -void renderVerticalAxisBorder( - ChartAxisRendererDetails axisRendererDetails, - Canvas canvas, - double pointX, - double pointY, - bool isBetweenTicks, - double betweenTickPositionValue, - int index) { - final ChartAxis axis = axisRendererDetails.axis; - final Paint borderPaint = Paint() - ..strokeWidth = axis.borderWidth - ..color = axis.borderColor != Colors.transparent - ? axis.borderColor! - : axisRendererDetails.renderingDetails.chartTheme.axisLineColor - ..style = PaintingStyle.stroke; - final Rect bounds = axisRendererDetails.bounds; - if (axisRendererDetails.crossValue != null && - axisRendererDetails.crossRange != null && - !axis.placeLabelsNearAxisLine) { - final double axisBorderEnd = axisRendererDetails.axisBorderEnd!; - final double maximumLabelWidth = axisRendererDetails.maximumLabelSize.width; - pointX = axisBorderEnd + - ((axis.labelPosition == ChartDataLabelPosition.outside) - ? (axis.opposedPosition ? -maximumLabelWidth : maximumLabelWidth) - : (axis.opposedPosition ? maximumLabelWidth : -maximumLabelWidth)); - } - final double startOffsetY = pointY + betweenTickPositionValue; - final Offset borderStartOffset = Offset(pointX, startOffsetY); - final Offset borderEndOffset = - Offset(axisRendererDetails.axisBorderEnd!, startOffsetY); - if (isBetweenTicks || - (bounds.bottom.roundToDouble() > borderEndOffset.dy.roundToDouble() && - bounds.top.roundToDouble() < borderEndOffset.dy.roundToDouble())) { - canvas.drawLine(borderStartOffset, borderEndOffset, borderPaint); - if (axis.axisBorderType == AxisBorderType.rectangle && index == 0) { - canvas.drawLine(Offset(borderStartOffset.dx, bounds.top), - Offset(borderStartOffset.dx, bounds.bottom), borderPaint); - canvas.drawLine(Offset(borderEndOffset.dx, bounds.top), - Offset(borderEndOffset.dx, bounds.bottom), borderPaint); - } - } -} - -/// To generate multi-level labels -void generateMultiLevelLabels(ChartAxisRendererDetails axisRendererDetails) { - final ChartAxis axis = axisRendererDetails.axis; - final List multiLevelLabelsList = - axis.multiLevelLabels!; - for (int index = 0; index < multiLevelLabelsList.length; index++) { - final String labelText = multiLevelLabelsList[index].text ?? ''; - final num actualStart = - _getActualValue(axisRendererDetails, multiLevelLabelsList[index].start); - final num actualEnd = - _getActualValue(axisRendererDetails, multiLevelLabelsList[index].end); - _setMinMaxMultiLevelLabelRange(actualStart, actualEnd, axisRendererDetails); - if ((axis is NumericAxis || axis is LogarithmicAxis) && - // ignore: unnecessary_null_comparison - (actualStart != null && actualEnd != null)) { - assert((actualStart <= actualEnd) == true, - 'The start value should be less than the end value'); - } - axisRendererDetails.visibleAxisMultiLevelLabels.add(AxisMultiLevelLabel( - actualStart, - actualEnd, - index, - multiLevelLabelsList[index].level, - labelText)); - } - _sortMultiLevelLabels(axisRendererDetails); - _triggerMultiLabelRenderCallback(axisRendererDetails); -} - -/// To trigger the multi-level axis label event -void _triggerMultiLabelRenderCallback( - ChartAxisRendererDetails axisRendererDetails) { - final ChartAxis axis = axisRendererDetails.axis; - TextStyle actualTextStyle = axis.multiLevelLabelStyle.textStyle!; - actualTextStyle = getTextStyle( - textStyle: actualTextStyle, - fontColor: - axisRendererDetails.renderingDetails.chartTheme.axisLabelColor); - final List visibleAxisMultiLevelLabels = - axisRendererDetails.visibleAxisMultiLevelLabels; - Rect axisBounds = axisRendererDetails.stateProperties.chartAxis.axisClipRect; - for (int i = 0; i < visibleAxisMultiLevelLabels.length; i++) { - String actualText = visibleAxisMultiLevelLabels[i].text ?? ''; - String? trimmedText; - TextStyle labelTextStyle = actualTextStyle; - if (axisRendererDetails.axis.multiLevelLabelFormatter != null) { - final MultiLevelLabelRenderDetails multiLevelLabelRenderDetails = - MultiLevelLabelRenderDetails( - visibleAxisMultiLevelLabels[i].actualLevel!, - actualText, - labelTextStyle, - visibleAxisMultiLevelLabels[i].index!, - axisRendererDetails.name); - final ChartAxisLabel chartAxisLabel = axisRendererDetails - .axis.multiLevelLabelFormatter!(multiLevelLabelRenderDetails); - labelTextStyle = chartAxisLabel.textStyle; - actualText = chartAxisLabel.text; - } - final Size textSize = measureText(actualText, labelTextStyle, 0); - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - if (axisRendererDetails.axis.plotOffset != 0) { - final double plotOffset = axisRendererDetails.axis.plotOffset; - axisBounds = Rect.fromLTRB(axisBounds.left + plotOffset, axisBounds.top, - axisBounds.right - (2 * plotOffset), axisBounds.bottom); - } - final bool isBetweenTicks = isLabelBetweenTicks(axisRendererDetails.axis); - final double betweenTicksInterval = isBetweenTicks ? 0.5 : 0; - final double labelStart = (valueToCoefficient( - visibleAxisMultiLevelLabels[i].actualStart! - - betweenTicksInterval, - axisRendererDetails) * - axisBounds.width) + - axisBounds.left; - final double labelEnd = (valueToCoefficient( - visibleAxisMultiLevelLabels[i].actualEnd! + - betweenTicksInterval, - axisRendererDetails) * - axisBounds.width) + - axisBounds.left; - final double labelRectWidth = (labelEnd - labelStart).abs(); - if ((labelRectWidth > 0.0) && labelRectWidth <= textSize.width) { - const double trimmedTextWidthPadding = 10.0; - final num maxWidth = labelRectWidth - trimmedTextWidthPadding; - trimmedText = getTrimmedText(actualText, maxWidth, labelTextStyle, - isRtl: axisRendererDetails.stateProperties.renderingDetails.isRtl); - } - } - final String renderText = trimmedText ?? actualText; - final Size labelSize = measureText(renderText, labelTextStyle, 0); - visibleAxisMultiLevelLabels[i].textStyle = labelTextStyle; - visibleAxisMultiLevelLabels[i].labelSize = labelSize; - visibleAxisMultiLevelLabels[i].text = actualText; - visibleAxisMultiLevelLabels[i].trimmedText = trimmedText; - visibleAxisMultiLevelLabels[i].renderText = renderText; - } -} - -/// To sort the levels of multi-level labels -void _sortMultiLevelLabels(ChartAxisRendererDetails axisRendererDetails) { - final List visibleAxisMultiLevelLabels = - axisRendererDetails.visibleAxisMultiLevelLabels; - axisRendererDetails.visibleAxisMultiLevelLabels.sort( - (AxisMultiLevelLabel a, AxisMultiLevelLabel b) => - a.level!.compareTo(b.level!)); - int currentLevel = 0; - int previousLevel = 0; - if (visibleAxisMultiLevelLabels.length > 1) { - for (int count = 0; count < visibleAxisMultiLevelLabels.length; count++) { - previousLevel = currentLevel; - if (count == 0 && visibleAxisMultiLevelLabels[count].level != 0) { - currentLevel = 0; - } else if (visibleAxisMultiLevelLabels[count].level! > - previousLevel + 1 && - visibleAxisMultiLevelLabels[count].level != - visibleAxisMultiLevelLabels[count - 1].level) { - currentLevel = previousLevel + 1; - } else if (visibleAxisMultiLevelLabels[count].level == - previousLevel + 1 && - visibleAxisMultiLevelLabels[count].level != - visibleAxisMultiLevelLabels[count - 1].level) { - currentLevel = visibleAxisMultiLevelLabels[count].level!; - } else { - currentLevel = previousLevel; - } - visibleAxisMultiLevelLabels[count].actualLevel = currentLevel; - axisRendererDetails.highestLevel = math.max(previousLevel, currentLevel); - } - } else { - visibleAxisMultiLevelLabels[currentLevel].actualLevel = currentLevel; - axisRendererDetails.highestLevel = currentLevel; - } -} - -/// To get the minimum and maximum start values for multi-level labels -void _setMinMaxMultiLevelLabelRange(num startValue, num endValue, - ChartAxisRendererDetails axisRendererDetails) { - if (axisRendererDetails.minimumMultiLevelLabelValue == null && - axisRendererDetails.maximumMultiLevelLabelValue == null) { - axisRendererDetails.minimumMultiLevelLabelValue = startValue; - axisRendererDetails.maximumMultiLevelLabelValue = endValue; - } - if (axisRendererDetails.minimumMultiLevelLabelValue! > startValue) { - axisRendererDetails.minimumMultiLevelLabelValue = startValue; - } - if (axisRendererDetails.maximumMultiLevelLabelValue! < endValue) { - axisRendererDetails.maximumMultiLevelLabelValue = endValue; - } -} - -/// Calculate the maximum bound for multi-level labels -void calculateMultiLevelLabelBounds( - ChartAxisRendererDetails axisRendererDetails) { - AxisMultiLevelLabel axisMultiLevelLabel; - final int highestLevel = axisRendererDetails.highestLevel!; - final Map maximumSizeMapping = {}; - double maximumWidth = 0.0, maximumHeight = 0.0; - int currentLevel; - for (int multiLevelLabelsCount = 0; - multiLevelLabelsCount < - axisRendererDetails.visibleAxisMultiLevelLabels.length; - multiLevelLabelsCount++) { - axisMultiLevelLabel = - axisRendererDetails.visibleAxisMultiLevelLabels[multiLevelLabelsCount]; - currentLevel = axisMultiLevelLabel.actualLevel!; - for (int levelCount = 0; levelCount <= highestLevel; levelCount++) { - if (currentLevel == levelCount) { - double maxWidth = maximumSizeMapping[levelCount]?.width ?? 0.0, - maxHeight = maximumSizeMapping[levelCount]?.height ?? 0.0; - maxWidth = axisMultiLevelLabel.labelSize!.width > maxWidth - ? axisMultiLevelLabel.labelSize!.width - : maxWidth; - maxHeight = axisMultiLevelLabel.labelSize!.height > maxHeight - ? axisMultiLevelLabel.labelSize!.height - : maxHeight; - maximumSizeMapping[levelCount] = Size(maxWidth, maxHeight); - } - } - } - final MultiLevelBorderType multiLevelLabelBorderType = - axisRendererDetails.axis.multiLevelLabelStyle.borderType; - double gapPadding = 6.0; - if (multiLevelLabelBorderType == MultiLevelBorderType.squareBrace) { - gapPadding = 3.0; - } - if (multiLevelLabelBorderType == MultiLevelBorderType.curlyBrace) { - gapPadding = 18.0; - } - for (int k = 0; k <= highestLevel; k++) { - final Size currentLevelMaximumSize = Size( - maximumSizeMapping[k]!.width + gapPadding, - maximumSizeMapping[k]!.height + gapPadding); - axisRendererDetails.multiLevelsMaximumSize.add(currentLevelMaximumSize); - maximumWidth += currentLevelMaximumSize.width; - maximumHeight += currentLevelMaximumSize.height; - } - axisRendererDetails.multiLevelLabelTotalSize = - Size(maximumWidth, maximumHeight); -} - -/// To get the multi-level label rectangle. -Rect _getMultiLevelLabelRect(ChartAxisRendererDetails axisRendererDetails, - num startValue, num endValue, int currentLevel) { - const double bracePadding = 5; - final Rect axisBounds = axisRendererDetails.bounds; - final bool isNotDefaultChart = (axisRendererDetails.axis.labelPosition == - ChartDataLabelPosition.inside) ^ - (axisRendererDetails.axis.opposedPosition); - final bool isBetweenTicks = isLabelBetweenTicks(axisRendererDetails.axis); - final double betweenTicksInterval = isBetweenTicks ? 0.5 : 0; - final bool isBraceType = - axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.squareBrace || - axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.curlyBrace; - double multiLevelLabelStart = axisRendererDetails.axisBorderEnd!; - double left = 0.0, right = 0.0, top = 0.0, bottom = 0.0; - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - multiLevelLabelStart = axisRendererDetails.axisBorderEnd! + - (isBraceType ? (isNotDefaultChart ? -bracePadding : bracePadding) : 0); - left = ((valueToCoefficient( - startValue - betweenTicksInterval, axisRendererDetails) * - axisBounds.width) + - axisBounds.left) - .roundToDouble(); - right = ((valueToCoefficient( - endValue + betweenTicksInterval, axisRendererDetails) * - axisBounds.width) + - axisBounds.left) - .roundToDouble(); - bottom = multiLevelLabelStart + - (isNotDefaultChart - ? (-axisRendererDetails.multiLevelsMaximumSize[currentLevel].height) - : axisRendererDetails.multiLevelsMaximumSize[currentLevel].height); - for (int i = 0; i < currentLevel; i++) { - bottom = bottom + - (isNotDefaultChart - ? (-axisRendererDetails.multiLevelsMaximumSize[i].height) - : axisRendererDetails.multiLevelsMaximumSize[i].height); - } - top = bottom - - (isNotDefaultChart - ? (-axisRendererDetails.multiLevelsMaximumSize[currentLevel].height) - : axisRendererDetails.multiLevelsMaximumSize[currentLevel].height); - } else { - multiLevelLabelStart = axisRendererDetails.axisBorderEnd! + - (isBraceType ? (isNotDefaultChart ? bracePadding : -bracePadding) : 0); - top = ((valueToCoefficient( - startValue - betweenTicksInterval, axisRendererDetails) * - axisBounds.height) + - axisBounds.top) - .roundToDouble(); - top = (axisBounds.top + axisBounds.height) - (top - axisBounds.top); - bottom = ((valueToCoefficient( - endValue + betweenTicksInterval, axisRendererDetails) * - axisBounds.height) + - axisBounds.top) - .roundToDouble(); - bottom = (axisBounds.top + axisBounds.height) - (bottom - axisBounds.top); - left = multiLevelLabelStart - - (isNotDefaultChart - ? (-axisRendererDetails.multiLevelsMaximumSize[currentLevel].width) - : axisRendererDetails.multiLevelsMaximumSize[currentLevel].width); - for (int i = 0; i < currentLevel; i++) { - left = left - - (isNotDefaultChart - ? (-axisRendererDetails.multiLevelsMaximumSize[i].width) - : axisRendererDetails.multiLevelsMaximumSize[i].width); - } - right = left + - (isNotDefaultChart - ? (-axisRendererDetails.multiLevelsMaximumSize[currentLevel].width) - : axisRendererDetails.multiLevelsMaximumSize[currentLevel].width); - } - return Rect.fromLTRB(left, top, right, bottom); -} - -/// To draw the path for the multi-level labels. -void _drawMultiLevelLabelBorder( - ChartAxisRendererDetails axisRendererDetails, - Canvas canvas, - Rect multiLevelLabelRect, - Paint pathPaint, - MultiLevelBorderType borderType, - AxisMultiLevelLabel multiLevelLabel) { - if (borderType == MultiLevelBorderType.rectangle) { - canvas.drawRect(multiLevelLabelRect, pathPaint); - } else if (borderType == MultiLevelBorderType.withoutTopAndBottom) { - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - canvas.drawLine(multiLevelLabelRect.topLeft, - multiLevelLabelRect.bottomLeft, pathPaint); - canvas.drawLine(multiLevelLabelRect.topRight, - multiLevelLabelRect.bottomRight, pathPaint); - } else { - canvas.drawLine( - multiLevelLabelRect.topLeft, multiLevelLabelRect.topRight, pathPaint); - canvas.drawLine(multiLevelLabelRect.bottomLeft, - multiLevelLabelRect.bottomRight, pathPaint); - } - } else if (borderType == MultiLevelBorderType.squareBrace) { - _drawSquareBracePath(axisRendererDetails, multiLevelLabelRect, - multiLevelLabel.labelSize!, canvas, pathPaint); - } else if (borderType == MultiLevelBorderType.curlyBrace) { - _drawCurlyBracePath(axisRendererDetails, multiLevelLabelRect, - multiLevelLabel.level!, multiLevelLabel.labelSize!, canvas, pathPaint); - } -} - -/// To draw the square brace path -void _drawSquareBracePath(ChartAxisRendererDetails axisRendererDetails, - Rect multiLevelLabelRect, Size labelSize, Canvas canvas, Paint pathPaint) { - const double textPadding = 2.0; - final bool isInversed = axisRendererDetails.axis.isInversed; - final bool isNotDefaultChart = (axisRendererDetails.axis.labelPosition == - ChartDataLabelPosition.inside) ^ - (axisRendererDetails.axis.opposedPosition); - final double braceTopPadding = isNotDefaultChart ? -3.0 : 3.0; - final Path pathBeforeText = Path(); - final Path pathAfterText = Path(); - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - pathBeforeText.moveTo(multiLevelLabelRect.left, multiLevelLabelRect.top); - pathBeforeText.lineTo( - multiLevelLabelRect.left, - multiLevelLabelRect.top + - multiLevelLabelRect.height / 2 + - braceTopPadding); - pathBeforeText.lineTo( - multiLevelLabelRect.left + - multiLevelLabelRect.width / 2 + - (isInversed - ? labelSize.width / 2 + textPadding - : -labelSize.width / 2 - textPadding), - multiLevelLabelRect.top + - multiLevelLabelRect.height / 2 + - braceTopPadding); - pathAfterText.moveTo(multiLevelLabelRect.right, multiLevelLabelRect.top); - pathAfterText.lineTo( - multiLevelLabelRect.right, - multiLevelLabelRect.top + - multiLevelLabelRect.height / 2 + - braceTopPadding); - pathAfterText.lineTo( - multiLevelLabelRect.right - - multiLevelLabelRect.width / 2 + - (isInversed - ? -labelSize.width / 2 - textPadding - : labelSize.width / 2 + textPadding), - multiLevelLabelRect.top + - multiLevelLabelRect.height / 2 + - braceTopPadding); - } else { - pathBeforeText.moveTo(multiLevelLabelRect.right, multiLevelLabelRect.top); - pathBeforeText.lineTo( - multiLevelLabelRect.right - - multiLevelLabelRect.width / 2 - - braceTopPadding, - multiLevelLabelRect.top); - pathBeforeText.lineTo( - multiLevelLabelRect.right - - multiLevelLabelRect.width / 2 - - braceTopPadding, - multiLevelLabelRect.top + - multiLevelLabelRect.height / 2 + - (isInversed - ? -labelSize.height / 2 - textPadding - : labelSize.height / 2 + textPadding)); - pathAfterText.moveTo(multiLevelLabelRect.right, multiLevelLabelRect.bottom); - pathAfterText.lineTo( - multiLevelLabelRect.right - - multiLevelLabelRect.width / 2 - - braceTopPadding, - multiLevelLabelRect.bottom); - pathAfterText.lineTo( - multiLevelLabelRect.right - - multiLevelLabelRect.width / 2 - - braceTopPadding, - multiLevelLabelRect.bottom - - multiLevelLabelRect.height / 2 + - (isInversed - ? labelSize.height / 2 + textPadding - : -labelSize.height / 2 - textPadding)); - } - canvas.drawPath(pathBeforeText, pathPaint); - canvas.drawPath(pathAfterText, pathPaint); -} - -/// To draw the curly brace path -void _drawCurlyBracePath( - ChartAxisRendererDetails axisRendererDetails, - Rect multiLevelLabelRect, - int index, - Size textSize, - Canvas canvas, - Paint paint) { - final bool isInversed = axisRendererDetails.axis.isInversed; - final bool isNotDefaultChart = (axisRendererDetails.axis.labelPosition == - ChartDataLabelPosition.inside) ^ - (axisRendererDetails.axis.opposedPosition); - final Path curlyBracePath = Path(); - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - final double curlyBracePadding = isInversed ? -7.0 : 7.0; - const double arrowWidth = 5.0; - const double curlyBraceTopPadding = 6.0; - Offset arrowPoint; - arrowPoint = Offset( - multiLevelLabelRect.left + multiLevelLabelRect.width / 2, - multiLevelLabelRect.top + - multiLevelLabelRect.height / 2 - - textSize.height / 2 + - curlyBraceTopPadding); - final double halfPathHeight = (arrowPoint.dy - multiLevelLabelRect.top) / 2; - final double curveEndPoint = multiLevelLabelRect.top + - halfPathHeight + - (isNotDefaultChart ? arrowWidth : -arrowWidth) / 2; - - curlyBracePath.moveTo(multiLevelLabelRect.left, multiLevelLabelRect.top); - curlyBracePath.quadraticBezierTo( - multiLevelLabelRect.left, - multiLevelLabelRect.top, - multiLevelLabelRect.left + curlyBracePadding / 2, - curveEndPoint); - curlyBracePath.quadraticBezierTo( - multiLevelLabelRect.left + curlyBracePadding / 2, - curveEndPoint, - multiLevelLabelRect.left + curlyBracePadding, - multiLevelLabelRect.top + halfPathHeight); - curlyBracePath.lineTo(multiLevelLabelRect.left + curlyBracePadding, - multiLevelLabelRect.top + halfPathHeight); - curlyBracePath.lineTo( - arrowPoint.dx + (isInversed ? arrowWidth : -arrowWidth), - multiLevelLabelRect.top + halfPathHeight); - curlyBracePath.lineTo(arrowPoint.dx, arrowPoint.dy); - curlyBracePath.lineTo( - arrowPoint.dx + (isInversed ? -arrowWidth : arrowWidth), - multiLevelLabelRect.top + halfPathHeight); - curlyBracePath.lineTo(multiLevelLabelRect.right - curlyBracePadding, - multiLevelLabelRect.top + halfPathHeight); - curlyBracePath.quadraticBezierTo( - multiLevelLabelRect.right - curlyBracePadding, - multiLevelLabelRect.top + halfPathHeight, - multiLevelLabelRect.right - curlyBracePadding / 2, - curveEndPoint); - curlyBracePath.quadraticBezierTo( - multiLevelLabelRect.right - curlyBracePadding / 2, - curveEndPoint, - multiLevelLabelRect.right, - multiLevelLabelRect.top); - curlyBracePath.lineTo(multiLevelLabelRect.right, multiLevelLabelRect.top); - } else { - final double curlyBracePadding = isInversed ? -12.0 : 12.0; - const double arrowWidth = 5.0; - const double curlyBraceWidthPadding = 15.0; - final double textRectWidth = - axisRendererDetails.multiLevelsMaximumSize[index].width - - curlyBraceWidthPadding; - final Offset arrowNosePoint = Offset( - multiLevelLabelRect.left + - (isNotDefaultChart ? -textRectWidth : textRectWidth), - multiLevelLabelRect.top + multiLevelLabelRect.height / 2); - final double halfPathWidth = - (multiLevelLabelRect.right - arrowNosePoint.dx) / 2; - final double curveEndPoint = multiLevelLabelRect.right - - halfPathWidth + - (isNotDefaultChart ? -arrowWidth : arrowWidth) / 2; - - curlyBracePath.moveTo(multiLevelLabelRect.right, multiLevelLabelRect.top); - curlyBracePath.quadraticBezierTo( - multiLevelLabelRect.right, - multiLevelLabelRect.top, - curveEndPoint, - multiLevelLabelRect.top - curlyBracePadding / 2); - curlyBracePath.quadraticBezierTo( - curveEndPoint, - multiLevelLabelRect.top - curlyBracePadding / 2, - multiLevelLabelRect.right - halfPathWidth, - multiLevelLabelRect.top - curlyBracePadding); - curlyBracePath.lineTo(multiLevelLabelRect.right - halfPathWidth, - multiLevelLabelRect.top - curlyBracePadding); - curlyBracePath.lineTo(multiLevelLabelRect.right - halfPathWidth, - arrowNosePoint.dy + (isInversed ? -arrowWidth : arrowWidth)); - curlyBracePath.lineTo(arrowNosePoint.dx, arrowNosePoint.dy); - curlyBracePath.lineTo(multiLevelLabelRect.right - halfPathWidth, - arrowNosePoint.dy + (isInversed ? arrowWidth : -arrowWidth)); - curlyBracePath.lineTo(multiLevelLabelRect.right - halfPathWidth, - multiLevelLabelRect.bottom + curlyBracePadding); - curlyBracePath.quadraticBezierTo( - multiLevelLabelRect.right - halfPathWidth, - multiLevelLabelRect.bottom + curlyBracePadding, - curveEndPoint, - multiLevelLabelRect.bottom + curlyBracePadding / 2); - curlyBracePath.quadraticBezierTo( - curveEndPoint, - multiLevelLabelRect.bottom + curlyBracePadding / 2, - multiLevelLabelRect.right, - multiLevelLabelRect.bottom); - curlyBracePath.lineTo( - multiLevelLabelRect.right, multiLevelLabelRect.bottom); - } - canvas.drawPath(curlyBracePath, paint); -} - -/// Render multi-level label text -void _drawMultiLevelLabelText( - ChartAxisRendererDetails axisRendererDetails, - Canvas canvas, - String renderText, - Rect multiLevelBorderRect, - AxisMultiLevelLabel multiLevelLabel) { - final Size labelSize = multiLevelLabel.labelSize!; - final bool isHorizontal = - axisRendererDetails.orientation == AxisOrientation.horizontal; - final bool isOpposed = axisRendererDetails.axis.opposedPosition == true; - final bool isLabelPositionInside = - axisRendererDetails.axis.labelPosition == ChartDataLabelPosition.inside; - final bool isNotDefaultChart = isLabelPositionInside ^ isOpposed; - Offset? textOffset; - double textGapPadding = 0.0; - final MultiLevelBorderType borderType = - axisRendererDetails.axis.multiLevelLabelStyle.borderType; - if (borderType == MultiLevelBorderType.rectangle || - borderType == MultiLevelBorderType.withoutTopAndBottom) { - textOffset = Offset( - multiLevelBorderRect.left + - multiLevelBorderRect.width / 2 - - labelSize.width / 2, - multiLevelBorderRect.top + - multiLevelBorderRect.height / 2 - - labelSize.height / 2); - } else if (borderType == MultiLevelBorderType.squareBrace) { - textGapPadding = isNotDefaultChart ? -3.0 : 3.0; - textOffset = Offset( - multiLevelBorderRect.left + - multiLevelBorderRect.width / 2 - - labelSize.width / 2 - - (isHorizontal ? 0 : textGapPadding), - multiLevelBorderRect.top + - multiLevelBorderRect.height / 2 - - labelSize.height / 2 + - (isHorizontal ? textGapPadding : 0)); - } else if (borderType == MultiLevelBorderType.curlyBrace) { - textGapPadding = isNotDefaultChart ? -9.0 : 9.0; - if (isHorizontal) { - textOffset = Offset( - multiLevelBorderRect.left + - multiLevelBorderRect.width / 2 - - labelSize.width / 2, - multiLevelBorderRect.top + - multiLevelBorderRect.height / 2 - - labelSize.height / 2 + - textGapPadding); - } else { - textGapPadding = isNotDefaultChart ? 18 : -18; - final double textRectWidth = axisRendererDetails - .multiLevelsMaximumSize[multiLevelLabel.actualLevel!].width + - textGapPadding; - - textOffset = Offset( - (isNotDefaultChart - ? multiLevelBorderRect.right - : multiLevelBorderRect.left) + - textRectWidth / 2 - - labelSize.width / 2, - multiLevelBorderRect.top + - multiLevelBorderRect.height / 2 - - labelSize.height / 2); - } - } - drawText(canvas, renderText, textOffset!, multiLevelLabel.textStyle!, 0); -} - -/// To draw multi-level labels -void drawMultiLevelLabels( - ChartAxisRendererDetails axisRendererDetails, Canvas canvas) { - final ChartAxis axis = axisRendererDetails.axis; - final Paint multiLevelBorderPaint = Paint() - ..color = axis.multiLevelLabelStyle.borderColor ?? - axisRendererDetails.renderingDetails.chartTheme.axisLineColor - ..style = PaintingStyle.stroke - ..strokeWidth = axis.multiLevelLabelStyle.borderWidth != 0 - ? axis.multiLevelLabelStyle.borderWidth - : axis.axisLine.width; - final bool isOpposed = axisRendererDetails.axis.opposedPosition; - final bool isLabelPositionInside = - axisRendererDetails.axis.labelPosition == ChartDataLabelPosition.inside; - final bool isNotDefaultChart = isLabelPositionInside ^ isOpposed; - const double bracePadding = 5; - final double clipRectBorderPadding = multiLevelBorderPaint.strokeWidth / 2; - final bool isBraceType = - axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.squareBrace || - axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.curlyBrace; - for (int k = 0; - k < axisRendererDetails.visibleAxisMultiLevelLabels.length; - k++) { - double multiLevelLabelStart = axisRendererDetails.axisBorderEnd!; - final AxisMultiLevelLabel multiLevelLabel = - axisRendererDetails.visibleAxisMultiLevelLabels[k]; - final Rect multiLevelBorderRect = _getMultiLevelLabelRect( - axisRendererDetails, - multiLevelLabel.actualStart!, - multiLevelLabel.actualEnd!, - multiLevelLabel.actualLevel!); - multiLevelLabel.multiLabelRegion = multiLevelBorderRect; - final String renderText = multiLevelLabel.renderText!; - canvas.save(); - if (axisRendererDetails.orientation == AxisOrientation.horizontal) { - multiLevelLabelStart = multiLevelLabelStart + - (isBraceType - ? (isNotDefaultChart ? -bracePadding : bracePadding) - : 0); - canvas.clipRect(Rect.fromLTRB( - axisRendererDetails.bounds.left - clipRectBorderPadding, - multiLevelLabelStart + - (isNotDefaultChart - ? clipRectBorderPadding - : -clipRectBorderPadding), - axisRendererDetails.bounds.right + clipRectBorderPadding, - multiLevelLabelStart + - (isNotDefaultChart - ? -axisRendererDetails.multiLevelLabelTotalSize.height - - clipRectBorderPadding - : axisRendererDetails.multiLevelLabelTotalSize.height + - clipRectBorderPadding), - )); - } else { - multiLevelLabelStart = multiLevelLabelStart + - (isBraceType - ? (isNotDefaultChart ? bracePadding : -bracePadding) - : 0); - canvas.clipRect(Rect.fromLTRB( - multiLevelLabelStart + - (isNotDefaultChart - ? axisRendererDetails.multiLevelLabelTotalSize.width + - clipRectBorderPadding - : -axisRendererDetails.multiLevelLabelTotalSize.width - - clipRectBorderPadding), - axisRendererDetails.bounds.top - clipRectBorderPadding, - multiLevelLabelStart + - (isNotDefaultChart - ? -clipRectBorderPadding - : clipRectBorderPadding), - axisRendererDetails.bounds.bottom + clipRectBorderPadding)); - } - _drawMultiLevelLabelBorder( - axisRendererDetails, - canvas, - multiLevelBorderRect, - multiLevelBorderPaint, - axis.multiLevelLabelStyle.borderType, - multiLevelLabel); - _drawMultiLevelLabelText(axisRendererDetails, canvas, renderText, - multiLevelBorderRect, multiLevelLabel); - canvas.restore(); - } -} - -/// To get the label offset for vertical axis -double getLabelOffsetX( - ChartAxisRendererDetails axisRendererDetails, Size textSize) { - final ChartAxis axis = axisRendererDetails.axis; - final bool isBetweenTicks = isLabelBetweenTicks(axis); - final Rect axisBounds = axisRendererDetails.bounds; - double pointX = 0.0; - const double innerPadding = 5.0; - if (axis.labelPosition == ChartDataLabelPosition.inside) { - pointX = (!axis.opposedPosition) - ? axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - : (axisBounds.left + - (axisRendererDetails.isInsideTickPosition! || isBetweenTicks - ? axis.majorTickLines.size - : 0)) + - innerPadding - : axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - textSize.width - : (axisBounds.left - - axisRendererDetails.maximumLabelSize.width - - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0.0)) + - innerPadding; - } else { - pointX = ((!axis.opposedPosition) - ? axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - textSize.width - : (axisBounds.left - - (axisRendererDetails.isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) - - textSize.width - - innerPadding) - : (axisRendererDetails.labelOffset ?? - (axisBounds.left + - (axisRendererDetails.isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding))) - .toDouble(); - } - return pointX; -} - -/// To get the label offset for horizontal axis -double getLabelOffsetY( - ChartAxisRendererDetails axisRendererDetails, int index) { - final ChartAxis axis = axisRendererDetails.axis; - final Rect axisBounds = axisRendererDetails.bounds; - final double halfTextHeight = axisRendererDetails.maximumLabelSize.height / 2; - double pointY = 0.0; - const num innerPadding = 5; - if (axis.labelPosition == ChartDataLabelPosition.inside) { - pointY = axis.opposedPosition == false - ? axisRendererDetails.labelOffset != null - ? axisRendererDetails.labelOffset! - - (2 * halfTextHeight) + - (2 * innerPadding) - : axisBounds.top + - innerPadding - - (index > 1 ? halfTextHeight : halfTextHeight * 2) - - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0) - : axisRendererDetails.labelOffset ?? - axisBounds.top + - innerPadding + - (axisRendererDetails.isInsideTickPosition! - ? axis.majorTickLines.size - : 0) + - (index > 1 ? halfTextHeight : 0); - } else { - if (axisRendererDetails.labelOffset != null) { - pointY = axis.opposedPosition == false - ? axisRendererDetails.labelOffset!.toDouble() + innerPadding - 5 - : axisRendererDetails.labelOffset!.toDouble() - - (2 * halfTextHeight) + - (2 * innerPadding); - } else { - pointY = (axis.opposedPosition == false - ? (axisBounds.top + - (((axisRendererDetails.isInsideTickPosition!) - ? 0 - : axis.majorTickLines.size) + - innerPadding) + - (index > 1 ? halfTextHeight : 0)) - : (axisBounds.top - - ((((axisRendererDetails.isInsideTickPosition!) - ? 0 - : axis.majorTickLines.size) + - innerPadding) - - (index > 1 ? halfTextHeight : 0)) - - (2 * (halfTextHeight - innerPadding)))) - .toDouble(); - } - } - return pointY; -} - -/// To get the offset for horizontal axis title -double getHorizontalAxisTitleOffset( - ChartAxisRendererDetails axisRendererDetails, Size textSize) { - const double titlePadding = 8; - final ChartAxis axis = axisRendererDetails.axis; - final double maximumTickHeight = - math.max(axis.majorTickLines.size, axis.minorTickLines.size); - double rectStartPoint = axisRendererDetails.axisBorderEnd!; - if (axisRendererDetails.isMultiLevelLabelEnabled) { - const double bracePadding = 5; - rectStartPoint = rectStartPoint + - ((axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.squareBrace || - axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.curlyBrace) - ? ((axisRendererDetails.axis.labelPosition == - ChartDataLabelPosition.inside) ^ - (axisRendererDetails.axis.opposedPosition) - ? -bracePadding - : bracePadding) - : 0); - } - double top = 0.0; - if (axis.labelPosition == ChartDataLabelPosition.outside) { - if (axis.opposedPosition) { - top = axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - textSize.height - : rectStartPoint - - (axisRendererDetails.isMultiLevelLabelEnabled - ? axisRendererDetails.multiLevelLabelTotalSize.height - : 0) - - textSize.height - - titlePadding; - } else { - top = axisRendererDetails.titleOffset ?? - rectStartPoint + - (axisRendererDetails.isMultiLevelLabelEnabled - ? axisRendererDetails.multiLevelLabelTotalSize.height - : 0) + - titlePadding; - } - } else { - if (axis.opposedPosition) { - top = axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - textSize.height - : rectStartPoint - - axisRendererDetails.maximumLabelSize.height - - maximumTickHeight - - textSize.height - - titlePadding; - } else { - top = axisRendererDetails.titleOffset ?? - rectStartPoint + - axisRendererDetails.maximumLabelSize.height + - maximumTickHeight + - titlePadding; - } - } - return top; -} - -/// To get the offset for vertical axis title -double getVerticalAxisTitleOffset( - ChartAxisRendererDetails axisRendererDetails, Size textSize) { - double left = 0.0; - const int innerPadding = 5; - const int axisCrossTitlePadding = 2; - final ChartAxis axis = axisRendererDetails.axis; - final double maximumTickHeight = - math.max(axis.majorTickLines.size, axis.minorTickLines.size); - double rectStartPoint = axisRendererDetails.axisBorderEnd!; - if (axisRendererDetails.isMultiLevelLabelEnabled) { - const double bracePadding = 5; - rectStartPoint = axisRendererDetails.axisBorderEnd! + - ((axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.squareBrace || - axisRendererDetails.axis.multiLevelLabelStyle.borderType == - MultiLevelBorderType.curlyBrace) - ? ((axisRendererDetails.axis.labelPosition == - ChartDataLabelPosition.inside) ^ - (axisRendererDetails.axis.opposedPosition) - ? bracePadding - : -bracePadding) - : 0); - } - if (axis.labelPosition == ChartDataLabelPosition.outside) { - if (!axis.opposedPosition) { - left = axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - - textSize.height / 2 - - axisCrossTitlePadding - : rectStartPoint - - ((axis.multiLevelLabels != null) - ? axisRendererDetails.multiLevelLabelTotalSize.width - : 0.0) - - textSize.height / 2 - - innerPadding; - } else { - left = axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! + - textSize.height / 2 + - axisCrossTitlePadding - : rectStartPoint + - (axis.multiLevelLabels != null - ? axisRendererDetails.multiLevelLabelTotalSize.width - : 0.0) + - innerPadding + - textSize.height / 2; - } - } else { - if (!axis.opposedPosition) { - left = axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! - - textSize.height / 2 - - axisCrossTitlePadding - : rectStartPoint - - maximumTickHeight - - axisRendererDetails.maximumLabelSize.width - - textSize.height / 2 - - innerPadding; - } else { - left = axisRendererDetails.titleOffset != null - ? axisRendererDetails.titleOffset! + - textSize.height / 2 + - axisCrossTitlePadding - : rectStartPoint + - maximumTickHeight + - axisRendererDetails.maximumLabelSize.width + - textSize.height / 2 + - innerPadding; - } - } - return left; -} - -/// Returns the actual start or end value that is -/// for numeric and logarithmic axis, returns numeric value, -/// for date time axis returns milliseconds, -/// for category and date time category returns index value -num _getActualValue(ChartAxisRendererDetails axisRendererDetails, - dynamic multiLevelLabelInputValue) { - final ChartAxis axis = axisRendererDetails.axis; - if (axis is NumericAxis) { - multiLevelLabelInputValue = multiLevelLabelInputValue.toDouble(); - } - if (axis is CategoryAxis) { - final CategoryAxisDetails axisDetails = - axisRendererDetails as CategoryAxisDetails; - multiLevelLabelInputValue = - axisDetails.labels.indexOf(multiLevelLabelInputValue); - } - if (axis is DateTimeAxis) { - multiLevelLabelInputValue = - multiLevelLabelInputValue.millisecondsSinceEpoch; - } - if (axis is DateTimeCategoryAxis) { - final DateTimeCategoryAxisDetails axisDetails = - axisRendererDetails as DateTimeCategoryAxisDetails; - multiLevelLabelInputValue = axisDetails.labels.indexOf( - axisRendererDetails.dateFormat.format(multiLevelLabelInputValue)); - } - if (axis is LogarithmicAxis) { - final LogarithmicAxisDetails axisDetails = - axisRendererDetails as LogarithmicAxisDetails; - multiLevelLabelInputValue = calculateLogBaseValue( - multiLevelLabelInputValue, axisDetails.logarithmicAxis.logBase); - } - return multiLevelLabelInputValue as num; -} - -/// To find the label is in between ticks -bool isLabelBetweenTicks(ChartAxis axis) { - bool isBetweenTicks = false; - if (axis is CategoryAxis || axis is DateTimeCategoryAxis) { - if (axis is CategoryAxis) { - final CategoryAxis chartAxis = axis; - isBetweenTicks = chartAxis.labelPlacement == LabelPlacement.betweenTicks; - } else if (axis is DateTimeCategoryAxis) { - final DateTimeCategoryAxis chartAxis = axis; - isBetweenTicks = chartAxis.labelPlacement == LabelPlacement.betweenTicks; - } - } - return isBetweenTicks; -} 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 deleted file mode 100644 index 0608b6109..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart +++ /dev/null @@ -1,762 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show NumberFormat; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../common/event_args.dart'; -import '../../common/utils/typedef.dart' - show MultiLevelLabelFormatterCallback, ChartLabelFormatterCallback; -import '../axis/axis.dart'; -import '../axis/multi_level_labels.dart'; -import '../axis/plotband.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart' show updateErrorBarAxisRange; -import '../common/interactive_tooltip.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// This class has the properties of the numeric axis. -/// -/// Numeric axis uses a numerical scale and displays numbers as labels. By default, [NumericAxis] is set to both -/// horizontal axis and vertical axis. -/// -/// Provides the options of [name], axis line, label rotation, label format, alignment and label position are -/// used to customize the appearance. -/// -@immutable -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, - 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, - bool? enableAutoIntervalOnZooming, - InteractiveTooltip? interactiveTooltip, - this.minimum, - this.maximum, - double? interval, - this.visibleMinimum, - this.visibleMaximum, - dynamic crossesAt, - String? associatedAxisName, - bool? placeLabelsNearAxisLine, - List? plotBands, - this.decimalPlaces = 3, - int? desiredIntervals, - RangeController? rangeController, - double? maximumLabelWidth, - double? labelsExtent, - int? autoScrollingDelta, - AutoScrollingMode? autoScrollingMode, - double? borderWidth, - Color? borderColor, - AxisBorderType? axisBorderType, - List? multiLevelLabels, - MultiLevelLabelFormatterCallback? multiLevelLabelFormatter, - MultiLevelLabelStyle? multiLevelLabelStyle, - ChartLabelFormatterCallback? axisLabelFormatter}) - : 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, - axisBorderType: axisBorderType, - borderColor: borderColor, - borderWidth: borderWidth, - multiLevelLabelFormatter: multiLevelLabelFormatter, - multiLevelLabels: multiLevelLabels, - multiLevelLabelStyle: multiLevelLabelStyle, - autoScrollingMode: autoScrollingMode, - axisLabelFormatter: axisLabelFormatter); - - /// Formats the numeric axis labels. - /// - /// The labels can be customized by adding desired text as prefix or suffix. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(labelFormat: '{value}M'), - /// ) - /// ); - ///} - ///``` - final String? labelFormat; - - /// Formats the numeric axis labels with globalized label formats. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(numberFormat: NumberFormat.currencyCompact()), - /// ) - /// ); - ///} - ///``` - final NumberFormat? numberFormat; - - /// The minimum value of the axis. - /// - /// The axis will start from this value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(minimum: 0), - /// ) - /// ); - ///} - ///``` - final double? minimum; - - /// The maximum value of the axis. - /// - /// The axis will end at this value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(maximum: 200), - /// ) - /// ); - ///} - ///``` - final double? maximum; - - /// The minimum visible value of the axis. - /// - /// The axis will be rendered from this value initially. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(visibleMinimum: 0), - /// ) - /// ); - ///} - ///``` - final double? visibleMinimum; - - /// The maximum visible value of the axis. - /// - /// The axis will be rendered till this value initially. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(visibleMaximum: 200), - /// ) - /// ); - ///} - ///``` - final double? visibleMaximum; - - /// The rounding decimal value of the label. - /// - /// Defaults to `3`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(decimalPlaces: 3), - /// ) - /// ); - ///} - ///``` - final int decimalPlaces; - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is NumericAxis && - other.name == name && - other.isVisible == isVisible && - other.title == title && - other.axisLine == axisLine && - other.numberFormat == numberFormat && - other.labelFormat == labelFormat && - other.rangePadding == rangePadding && - other.decimalPlaces == decimalPlaces && - other.edgeLabelPlacement == edgeLabelPlacement && - other.labelPosition == labelPosition && - other.tickPosition == tickPosition && - other.labelRotation == labelRotation && - other.labelIntersectAction == labelIntersectAction && - other.labelAlignment == labelAlignment && - other.isInversed == isInversed && - other.opposedPosition == opposedPosition && - other.minorTicksPerInterval == minorTicksPerInterval && - other.maximumLabels == maximumLabels && - other.majorTickLines == majorTickLines && - other.minorTickLines == minorTickLines && - other.majorGridLines == majorGridLines && - other.minorGridLines == minorGridLines && - other.labelStyle == labelStyle && - other.plotOffset == plotOffset && - other.zoomFactor == zoomFactor && - other.zoomPosition == zoomPosition && - other.interactiveTooltip == interactiveTooltip && - other.minimum == minimum && - other.maximum == maximum && - other.interval == interval && - other.visibleMinimum == visibleMinimum && - other.visibleMaximum == visibleMaximum && - other.crossesAt == crossesAt && - other.associatedAxisName == associatedAxisName && - other.placeLabelsNearAxisLine == placeLabelsNearAxisLine && - other.plotBands == plotBands && - other.desiredIntervals == desiredIntervals && - other.rangeController == rangeController && - other.maximumLabelWidth == maximumLabelWidth && - other.labelsExtent == labelsExtent && - other.autoScrollingDelta == autoScrollingDelta && - other.axisBorderType == axisBorderType && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.multiLevelLabelStyle == multiLevelLabelStyle && - other.multiLevelLabels == multiLevelLabels && - other.multiLevelLabelFormatter == multiLevelLabelFormatter && - other.autoScrollingMode == autoScrollingMode && - other.axisLabelFormatter == axisLabelFormatter; - } - - @override - int get hashCode { - final List values = [ - name, - isVisible, - title, - axisLine, - numberFormat, - labelFormat, - rangePadding, - decimalPlaces, - edgeLabelPlacement, - labelPosition, - tickPosition, - labelRotation, - labelIntersectAction, - labelAlignment, - isInversed, - opposedPosition, - minorTicksPerInterval, - maximumLabels, - majorTickLines, - minorTickLines, - majorGridLines, - minorGridLines, - labelStyle, - plotOffset, - zoomFactor, - zoomPosition, - interactiveTooltip, - minimum, - maximum, - interval, - visibleMinimum, - visibleMaximum, - crossesAt, - associatedAxisName, - placeLabelsNearAxisLine, - plotBands, - desiredIntervals, - rangeController, - maximumLabelWidth, - labelsExtent, - autoScrollingDelta, - axisBorderType, - borderColor, - borderWidth, - multiLevelLabelStyle, - multiLevelLabels, - multiLevelLabelFormatter, - autoScrollingMode, - axisLabelFormatter - ]; - return hashList(values); - } -} - -/// Creates an axis renderer for Numeric axis. -class NumericAxisRenderer extends ChartAxisRenderer { - /// Creating an argument constructor of NumericAxisRenderer class. - NumericAxisRenderer( - NumericAxis numericAxis, CartesianStateProperties stateProperties) { - _axisDetails = NumericAxisDetails(numericAxis, this, stateProperties); - AxisHelper.setAxisRendererDetails(this, _axisDetails); - } - - late NumericAxisDetails _axisDetails; - - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(VisibleRange range, num? interval) { - ActualRangeChangedArgs rangeChangedArgs; - final NumericAxisDetails axisDetails = - AxisHelper.getAxisRendererDetails(this) as NumericAxisDetails; - if (!(axisDetails.numericAxis.minimum != null && - axisDetails.numericAxis.maximum != null)) { - ///Calculating range padding - axisDetails.applyRangePaddings( - this, axisDetails.stateProperties, range, interval!); - } - - calculateVisibleRange(axisDetails.axisSize); - - /// Setting range as visible zoomRange - if ((axisDetails.numericAxis.visibleMinimum != null || - axisDetails.numericAxis.visibleMaximum != null) && - (axisDetails.numericAxis.visibleMinimum != - axisDetails.numericAxis.visibleMaximum) && - (!axisDetails.stateProperties.isRedrawByZoomPan)) { - axisDetails.stateProperties.isRedrawByZoomPan = false; - axisDetails.visibleRange!.minimum = axisDetails.visibleMinimum ?? - axisDetails.numericAxis.visibleMinimum ?? - axisDetails.actualRange!.minimum; - axisDetails.visibleRange!.maximum = axisDetails.visibleMaximum ?? - axisDetails.numericAxis.visibleMaximum ?? - axisDetails.actualRange!.maximum; - axisDetails.visibleRange!.delta = - axisDetails.visibleRange!.maximum - axisDetails.visibleRange!.minimum; - axisDetails.visibleRange!.interval = interval == null - ? axisDetails.calculateNumericNiceInterval( - this, - axisDetails.visibleRange!.maximum - - axisDetails.visibleRange!.minimum, - axisDetails.axisSize) - : axisDetails.visibleRange!.interval; - axisDetails.zoomFactor = axisDetails.visibleRange!.delta / range.delta; - axisDetails.zoomPosition = (axisDetails.visibleRange!.minimum - - axisDetails.actualRange!.minimum) / - range.delta; - } - if (axisDetails.chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs( - axisDetails.name!, - axisDetails.numericAxis, - range.minimum, - range.maximum, - range.interval, - axisDetails.orientation!); - rangeChangedArgs.visibleMin = axisDetails.visibleRange!.minimum; - rangeChangedArgs.visibleMax = axisDetails.visibleRange!.maximum; - rangeChangedArgs.visibleInterval = axisDetails.visibleRange!.interval; - axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); - axisDetails.visibleRange!.minimum = rangeChangedArgs.visibleMin; - axisDetails.visibleRange!.maximum = rangeChangedArgs.visibleMax; - axisDetails.visibleRange!.delta = - axisDetails.visibleRange!.maximum - axisDetails.visibleRange!.minimum; - axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; - axisDetails.zoomFactor = axisDetails.visibleRange!.delta / range.delta; - axisDetails.zoomPosition = (axisDetails.visibleRange!.minimum - - axisDetails.actualRange!.minimum) / - range.delta; - } - } - - /// Generates the visible axis labels. - @override - void generateVisibleLabels() { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(this); - final VisibleRange? visibleRange = axisDetails.visibleRange; - final NumericAxis numericAxis = axisDetails.axis as NumericAxis; - num tempInterval = visibleRange!.minimum; - String text; - 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' - ? interval.floor() - : interval - : interval; - axisDetails.visibleLabels = []; - for (; tempInterval <= maximumVisibleRange; tempInterval += interval) { - num minimumVisibleRange = tempInterval; - if (minimumVisibleRange <= maximumVisibleRange && - minimumVisibleRange >= visibleRange.minimum) { - final int fractionDigits = - (minimumVisibleRange.toString().split('.').length >= 2) - ? minimumVisibleRange.toString().split('.')[1].toString().length - : 0; - final int fractionDigitValue = - fractionDigits > 20 ? 20 : fractionDigits; - minimumVisibleRange = minimumVisibleRange.toString().contains('e') - ? minimumVisibleRange - : num.tryParse( - minimumVisibleRange.toStringAsFixed(fractionDigitValue))!; - if (minimumVisibleRange.toString().split('.').length > 1) { - final String str = minimumVisibleRange.toString(); - final List? list = str.split('.'); - minimumVisibleRange = double.parse( - minimumVisibleRange.toStringAsFixed(numericAxis.decimalPlaces)); - if (list != null && - list.length > 1 && - (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - minimumVisibleRange % 1 == 0)) { - minimumVisibleRange = minimumVisibleRange.round(); - } - } - text = minimumVisibleRange.toString(); - if (numericAxis.numberFormat != null) { - text = numericAxis.numberFormat!.format(minimumVisibleRange); - } - if (numericAxis.labelFormat != null && numericAxis.labelFormat != '') { - text = numericAxis.labelFormat!.replaceAll(RegExp('{value}'), text); - } - text = axisDetails.stateProperties.chartAxis.primaryYAxisDetails - .isStack100 == - true && - axisDetails.name == - axisDetails - .stateProperties.chartAxis.primaryYAxisDetails.name - ? '$text%' - : text; - axisDetails.triggerLabelRenderEvent(text, tempInterval); - } - } - - /// Get the maximum label of width and height in axis. - axisDetails.calculateMaximumLabelSize(this, axisDetails.stateProperties); - if (numericAxis.multiLevelLabels != null && - numericAxis.multiLevelLabels!.isNotEmpty) { - generateMultiLevelLabels(_axisDetails); - calculateMultiLevelLabelBounds(axisDetails); - } - } - - /// Calculates the visible range for an axis in chart. - @override - void calculateVisibleRange(Size availableSize) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(this); - final CartesianStateProperties stateProperties = - axisDetails.stateProperties; - axisDetails.setOldRangeFromRangeController(); - axisDetails.visibleRange = stateProperties.rangeChangeBySlider && - axisDetails.rangeMinimum != null && - axisDetails.rangeMaximum != null - ? VisibleRange(axisDetails.rangeMinimum, axisDetails.rangeMaximum) - : VisibleRange( - axisDetails.actualRange!.minimum, axisDetails.actualRange!.maximum); - axisDetails.visibleRange!.delta = axisDetails.actualRange!.delta; - axisDetails.visibleRange!.interval = axisDetails.actualRange!.interval; - bool canAutoScroll = false; - if (axisDetails.axis.autoScrollingDelta != null && - axisDetails.axis.autoScrollingDelta! > 0 && - !stateProperties.isRedrawByZoomPan) { - canAutoScroll = true; - axisDetails.updateAutoScrollingDelta( - axisDetails.axis.autoScrollingDelta!, this); - } - if ((!canAutoScroll || stateProperties.zoomedState == true) && - !(stateProperties.rangeChangeBySlider && - !stateProperties.canSetRangeController)) { - axisDetails.setZoomFactorAndPosition( - this, stateProperties.zoomedAxisRendererStates); - } - if (axisDetails.zoomFactor < 1 || - axisDetails.zoomPosition > 0 || - (axisDetails.axis.rangeController != null && - !stateProperties.renderingDetails.initialRender!) && - !(stateProperties.rangeChangeBySlider || - !stateProperties.canSetRangeController)) { - stateProperties.zoomProgress = true; - axisDetails.calculateZoomRange(this, availableSize); - axisDetails.visibleRange!.interval = !canAutoScroll && - axisDetails.axis.enableAutoIntervalOnZooming && - stateProperties.zoomProgress - ? calculateInterval(axisDetails.visibleRange!, axisDetails.axisSize) - : axisDetails.visibleRange!.interval; - if (axisDetails.axis.rangeController != null && - stateProperties.isRedrawByZoomPan && - stateProperties.canSetRangeController && - stateProperties.zoomProgress) { - stateProperties.rangeChangedByChart = true; - axisDetails.setRangeControllerValues(this); - } - } - axisDetails.setZoomValuesFromRangeController(); - } - - /// Finds the interval of an axis. - @override - num calculateInterval(VisibleRange range, Size availableSize) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(this); - return axisDetails.calculateNumericNiceInterval( - this, range.maximum - range.minimum, axisDetails.axisSize); - } -} - -/// This class holds the details of NumericAxis -class NumericAxisDetails extends ChartAxisRendererDetails { - ///Argument constructor for NumericAxisDetails class - NumericAxisDetails(this.numericAxis, ChartAxisRenderer axisRenderer, - CartesianStateProperties stateProperties) - : super(numericAxis, stateProperties, axisRenderer); - - /// Holds the value of axis padding - // ignore:unused_field - final int axisPadding = 5; - - /// Holds the value of inner padding - // ignore:unused_field - final int innerPadding = 5; - - @override - late Size axisSize; - - /// Represents the value of numeric axis - final NumericAxis numericAxis; - - /// Find the series min and max values of an series - void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - final bool _anchorRangeToVisiblePoints = - seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; - final String seriesType = seriesRendererDetails.seriesType; - point.xValue = point.x; - point.yValue = point.y; - if (isYVisibleRange!) { - seriesRendererDetails.minimumX ??= point.xValue; - seriesRendererDetails.maximumX ??= point.xValue; - } - if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && - !seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall') { - seriesRendererDetails.minimumY ??= point.yValue; - seriesRendererDetails.maximumY ??= point.yValue; - } - - if (isYVisibleRange && point.xValue != null) { - seriesRendererDetails.minimumX = - math.min(seriesRendererDetails.minimumX!, point.xValue as num); - seriesRendererDetails.maximumX = - math.max(seriesRendererDetails.maximumX!, point.xValue as num); - } - if (isXVisibleRange || !_anchorRangeToVisiblePoints) { - if (point.yValue != null && - (!seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall')) { - seriesRendererDetails.minimumY = - math.min(seriesRendererDetails.minimumY!, point.yValue as num); - seriesRendererDetails.maximumY = - math.max(seriesRendererDetails.maximumY!, point.yValue as num); - } - 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.maximum!, 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; - seriesRendererDetails.minimumY = findMinValue( - seriesRendererDetails.minimumY ?? point.yValue, point.yValue); - seriesRendererDetails.maximumY = findMaxValue( - seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue); - } - if (seriesType == 'errorbar') { - updateErrorBarAxisRange(seriesRendererDetails, point); - } - } - if (pointIndex >= dataLength - 1) { - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') || - seriesType == 'boxandwhisker') { - lowMin ??= 0; - lowMax ??= 5; - highMin ??= 0; - highMax ??= 5; - seriesRendererDetails.minimumY = math.min(lowMin!, highMin!); - seriesRendererDetails.maximumY = math.max(lowMax!, highMax!); - } - - seriesRendererDetails.minimumX ??= 0; - seriesRendererDetails.minimumY ??= 0; - seriesRendererDetails.maximumX ??= 5; - seriesRendererDetails.maximumY ??= 5; - } - } - - /// Listener for range controller - void _controlListener() { - stateProperties.canSetRangeController = false; - if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { - updateRangeControllerValues(this); - stateProperties.rangeChangeBySlider = true; - stateProperties.redrawByRangeChange(); - } - } - - /// Calculate the range and interval - void calculateRangeAndInterval(CartesianStateProperties stateProperties, - [String? type]) { - chart = stateProperties.chart; - if (axis.rangeController != null) { - stateProperties.rangeChangeBySlider = true; - axis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - axisSize = Size(rect.width, rect.height); - axisRenderer.calculateRange(axisRenderer); - _calculateActualRange(); - if (actualRange != null) { - axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); - if (type == null && type != 'AxisCross' && numericAxis.isVisible) { - axisRenderer.generateVisibleLabels(); - } - } - } - - /// Calculate the required values of the actual range for numeric axis - void _calculateActualRange() { - min ??= 0; - max ??= 5; - - /// Below condition is for checking whether the min and max are equal and - /// also whether they are positive or negative in order - /// to set the min and max as zero accordingly. - if (min == max && min! < 0 && max! < 0) { - max = 0; - } - if (min == max && min! > 0 && max! > 0) { - min = 0; - } - - actualRange = - VisibleRange(numericAxis.minimum ?? min, numericAxis.maximum ?? max); - if (axis.anchorRangeToVisiblePoints && - needCalculateYrange(numericAxis.minimum, numericAxis.maximum, - stateProperties, orientation!)) { - actualRange = calculateYRangeOnZoomX(actualRange!, this); - } - - ///Below condition is for checking the min, max value is equal - 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) == true) { - 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!.interval = numericAxis.interval ?? - calculateNumericNiceInterval( - axisRenderer, actualRange!.delta, axisSize); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart deleted file mode 100644 index 42aca985c..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart +++ /dev/null @@ -1,1147 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../axis/numeric_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Render plot band. -/// -/// Plot bands are also known as strip lines, which are 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, you have to add plot bands using the plotBands property of the respective axis. You can also add -/// multiple plot bands to an axis. -/// -/// Provides the property of visible, opacity, start, end, color, border color, and border width to -/// customize the appearance. -/// -@immutable -class PlotBand { - /// Creating an argument constructor of PlotBand class. - // ignore: prefer_const_constructors_in_immutables - PlotBand( - {this.isVisible = true, - this.start, - this.end, - this.color = Colors.grey, - this.opacity = 1.0, - this.borderColor = Colors.transparent, - this.borderWidth = 0, - this.text, - TextStyle? textStyle, - this.isRepeatable = false, - this.repeatEvery = 1, - this.verticalTextPadding, - this.horizontalTextPadding, - this.repeatUntil, - this.textAngle, - this.shouldRenderAboveSeries = false, - this.sizeType = DateTimeIntervalType.auto, - this.dashArray = const [0, 0], - this.size, - this.associatedAxisStart, - this.associatedAxisEnd, - this.verticalTextAlignment = TextAnchor.middle, - this.horizontalTextAlignment = TextAnchor.middle, - this.gradient}) - : textStyle = textStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12); - - /// Toggles the visibility of the plot band. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final bool isVisible; - - /// Specifies the start value of plot band. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// start: 1, - /// end: 5 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic start; - - /// Specifies the end value of plot band. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// start: 1, - /// end: 5 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic end; - - /// Text to be displayed in the plot band segment. - /// - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// text:'Winter' - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final String? text; - - /// Customizes the text style of plot band. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// textStyle: const TextStyle(color:Colors.red) - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final TextStyle textStyle; - - /// Color of the plot band. - /// - /// Defaults to `Colors.grey`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// color: Colors.red - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final Color color; - - /// Color of the plot band border. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// borderColor: Colors.red - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final Color borderColor; - - /// Width of the plot band border. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// borderWidth: 2 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final double borderWidth; - - /// Opacity of the plot band. The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// opacity: 0.5 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final double opacity; - - /// Specifies the plot band need to be repeated in specified interval. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// isRepeatable: true - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final bool isRepeatable; - - /// Interval of the plot band need to be repeated. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// repeatEvery: 200 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic repeatEvery; - - /// End of the plot band need to be repeated. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// repeatUntil: 600 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic repeatUntil; - - /// Angle of the plot band text. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// textAngle: 90 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final double? textAngle; - - /// Specifies whether the plot band needs to be rendered above the series. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// shouldRenderAboveSeries: true - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final bool shouldRenderAboveSeries; - - /// Date time interval type of the plot band. - /// - /// Defaults to `DateTimeIntervalType.auto`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// sizeType: DateTimeIntervalType.years - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final DateTimeIntervalType sizeType; - - /// Dashes of the series. Any number of values can be provided in the list. Odd value - /// is considered as rendering size and even value is considered as gap. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// dashArray: [10, 10] - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final List dashArray; - - /// Size of the plot band. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// size: 20 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic size; - - /// Perpendicular axis start value. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// associatedAxisStart: 2 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic associatedAxisStart; - - /// Perpendicular axis end value. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// associatedAxisStart: 2 - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final dynamic associatedAxisEnd; - - /// Vertical text alignment of the plot band text. - /// - /// Defaults to `TextAnchor.middle`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// verticalTextAlignment: TextAnchor.start - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final TextAnchor verticalTextAlignment; - - /// Horizontal text alignment of the plot band text. - /// - /// Defaults to `TextAnchor.middle`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// horizontalTextAlignment: TextAnchor.end - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final TextAnchor horizontalTextAlignment; - - /// Fills the plot band with gradient color. - /// - ///```dart - ///final List color = []; - /// color.add(Colors.pink[50]); - /// color.add(Colors.pink[200]); - /// color.add(Colors.pink); - /// - ///final List stops = []; - /// stops.add(0.0); - /// stops.add(0.5); - /// stops.add(1.0); - /// - ///final LinearGradient gradients = LinearGradient(colors: color, stops: stops); - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// isVisible:true, - /// gradient: gradients - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - ///``` - final LinearGradient? gradient; - - /// To move the plot band text vertically. - /// - /// Takes pixel or percentage value. For pixel, input should be like `10px` and for percentage - /// input should be like `10%`. If no suffix is specified (`10`), it will be considered as pixel value. - /// Percentage value referes to the overall height of the chart. i.e. 100% is equal to the height - /// of the chart. - /// - /// This is applicable for both vertical and horizontal axis. Positive value for this property - /// moves the text upwards and negative value moves downwards. - /// - /// If [verticalTextAlignment] or [horizontalTextAlignment] is specified, text padding will be calculated - /// from that modified position. - /// - /// Defaults to `null`. - /// - /// - ///```dart - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// verticalTextPadding:'30%', - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - final String? verticalTextPadding; - - /// To move the plot band text horizontally. - /// - /// Takes pixel or percentage value. For pixel, input should be like `10px` and for percentage - /// input should be like `10%`. If no suffix is specified (`10`), it will be considered as pixel value. - /// Percentage value referes to the overall width of the chart. i.e. 100% is equal to the width - /// of the chart. - /// - /// This is applicable for both vertical and horizontal axis. Positive value for this property - /// moves the text to right and negative value moves to left. - /// - /// If [verticalTextAlignment] or [horizontalTextAlignment] is specified, text padding will be calculated - /// from that modified position. - /// - /// Defaults to `null`. - ///```dart - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// plotBands: [ - /// PlotBand( - /// horizontalTextPadding:'30%', - /// ) - /// ] - /// ) - /// ) - /// ); - ///} - final String? horizontalTextPadding; - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is PlotBand && - other.isVisible == isVisible && - other.start == start && - other.end == end && - other.color == color && - other.opacity == opacity && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.text == text && - other.textStyle == textStyle && - other.isRepeatable == isRepeatable && - other.repeatEvery == repeatEvery && - other.verticalTextPadding == verticalTextPadding && - other.horizontalTextPadding == horizontalTextPadding && - other.repeatUntil == repeatUntil && - other.textAngle == textAngle && - other.shouldRenderAboveSeries == shouldRenderAboveSeries && - other.sizeType == sizeType && - other.dashArray == dashArray && - other.size == size && - other.associatedAxisStart == associatedAxisStart && - other.associatedAxisEnd == associatedAxisEnd && - other.verticalTextAlignment == verticalTextAlignment && - other.horizontalTextAlignment == horizontalTextAlignment && - other.gradient == gradient; - } - - @override - int get hashCode { - final List values = [ - isVisible, - start, - end, - color, - opacity, - borderColor, - borderWidth, - text, - textStyle, - isRepeatable, - repeatEvery, - verticalTextPadding, - horizontalTextPadding, - repeatUntil, - textAngle, - shouldRenderAboveSeries, - sizeType, - dashArray, - size, - associatedAxisStart, - associatedAxisEnd, - verticalTextAlignment, - horizontalTextAlignment, - gradient - ]; - return hashList(values); - } -} - -/// Method to get the plot band painter -CustomPainter getPlotBandPainter( - {required CartesianStateProperties stateProperties, - required bool shouldRenderAboveSeries}) { - return _PlotBandPainter( - stateProperties: stateProperties, - shouldRenderAboveSeries: shouldRenderAboveSeries); -} - -class _PlotBandPainter extends CustomPainter { - _PlotBandPainter( - {required this.stateProperties, required this.shouldRenderAboveSeries}) - : chart = stateProperties.chart; - - final CartesianStateProperties stateProperties; - - final SfCartesianChart chart; - - final bool shouldRenderAboveSeries; - - /// To paint plotbands - @override - void paint(Canvas canvas, Size size) { - Rect clipRect; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - final ChartAxisRenderer axisRenderer = - stateProperties.chartAxis.axisRenderersCollection[axisIndex]; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - for (int j = 0; j < axis.plotBands.length; j++) { - final PlotBand plotBand = axis.plotBands[j]; - if (plotBand.isVisible && - shouldRenderAboveSeries != plotBand.shouldRenderAboveSeries) { - clipRect = Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left, - stateProperties.chartAxis.axisClipRect.top, - stateProperties.chartAxis.axisClipRect.right, - stateProperties.chartAxis.axisClipRect.bottom); - canvas.clipRect(clipRect); - _renderPlotBand(canvas, axisRenderer, plotBand); - } - } - } - } - - /// To find the start and end location for plotband - ChartLocation _getStartAndEndValues(ChartAxisRenderer axisRenderer, - dynamic start, dynamic end, PlotBand plotBand, bool isNeedRepeat) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - dynamic startValue = start is String && num.tryParse(start) != null - ? num.tryParse(start) - : start; - - dynamic endValue = - end is String && num.tryParse(end) != null ? num.tryParse(end) : end; - if (axisRenderer is DateTimeAxisRenderer) { - startValue = startValue is DateTime - ? startValue.millisecondsSinceEpoch - : startValue; - endValue = isNeedRepeat - ? plotBand.repeatUntil is DateTime - ? plotBand.repeatUntil.millisecondsSinceEpoch - : plotBand.repeatUntil - : endValue is DateTime - ? endValue.millisecondsSinceEpoch - : endValue; - } else if (axisDetails is CategoryAxisDetails) { - startValue = startValue is num - ? startValue - : axisDetails.labels.indexOf(startValue); - endValue = isNeedRepeat - ? plotBand.repeatUntil is num - ? plotBand.repeatUntil.floor() - : axisDetails.labels.indexOf(plotBand.repeatUntil) - : endValue is num - ? endValue - : axisDetails.labels.indexOf(endValue); - } - if (axisDetails is DateTimeCategoryAxisDetails) { - startValue = startValue is num - ? startValue - : (startValue is DateTime - ? axisDetails.labels.indexOf(axisDetails.axis.isVisible - ? axisDetails.dateFormat.format(startValue) - : startValue.microsecondsSinceEpoch.toString()) - : axisDetails.labels.indexOf(startValue)); - endValue = isNeedRepeat - ? plotBand.repeatUntil is num - ? plotBand.repeatUntil.floor() - : axisDetails.labels.indexOf(plotBand.repeatUntil) - : endValue is num - ? endValue - : endValue is DateTime - ? axisDetails.labels.indexOf(axisDetails.axis.isVisible - ? axisDetails.dateFormat.format(endValue) - : endValue.microsecondsSinceEpoch.toString()) - : axisDetails.labels.indexOf(endValue); - } else if (axisRenderer is LogarithmicAxisRenderer || - axisRenderer is NumericAxisRenderer) { - endValue = isNeedRepeat ? plotBand.repeatUntil : endValue; - } - return ChartLocation(startValue.toDouble(), endValue.toDouble()); - } - - /// Render a method for plotband - void _renderPlotBand( - Canvas canvas, ChartAxisRenderer axisRenderer, PlotBand plotBand) { - num startValue, endValue; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final bool isNeedRepeat = plotBand.isRepeatable && - plotBand.repeatUntil != null && - plotBand.repeatEvery != null; - - final ChartLocation startAndEndValues = _getStartAndEndValues( - axisRenderer, - plotBand.start ?? axisDetails.visibleRange!.minimum, - plotBand.end ?? axisDetails.visibleRange!.maximum, - plotBand, - isNeedRepeat); - startValue = startAndEndValues.x; - endValue = startAndEndValues.y; - if (isNeedRepeat) { - num repeatStart = startValue, repeatEnd; - while (repeatStart < endValue) { - repeatEnd = _getPlotBandValue(axisRenderer, plotBand, repeatStart, - plotBand.size ?? plotBand.repeatEvery); - repeatEnd = repeatEnd > endValue ? endValue : repeatEnd; - _renderPlotBandElement( - axisRenderer, repeatStart, repeatEnd, plotBand, canvas); - repeatStart = plotBand.size != null - ? _getPlotBandValue( - axisRenderer, plotBand, repeatStart, plotBand.repeatEvery) - : repeatEnd; - } - } else { - _renderPlotBandElement( - axisRenderer, startValue, endValue, plotBand, canvas); - } - } - - /// To get and return value for date time axis - num _getPlotBandValue(ChartAxisRenderer axisRenderer, PlotBand plotBand, - num value, num increment) { - final int addValue = increment.toInt(); - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - DateTimeIntervalType intervalType; - if (axisDetails is DateTimeAxisDetails) { - intervalType = (plotBand.sizeType == DateTimeIntervalType.auto) - ? axisDetails.actualIntervalType - : plotBand.sizeType; - DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); - switch (intervalType) { - case DateTimeIntervalType.years: - date = DateTime(date.year + addValue, date.month, date.day, date.hour, - date.minute, date.second, date.millisecond); - break; - case DateTimeIntervalType.months: - date = DateTime(date.year, date.month + addValue, date.day, date.hour, - date.minute, date.second, date.millisecond); - break; - case DateTimeIntervalType.days: - date = DateTime(date.year, date.month, date.day + addValue, date.hour, - date.minute, date.second, date.millisecond); - break; - case DateTimeIntervalType.hours: - date = DateTime(date.year, date.month, date.day, date.hour + addValue, - date.minute, date.second, date.millisecond); - break; - case DateTimeIntervalType.minutes: - date = DateTime(date.year, date.month, date.day, date.hour, - date.minute + addValue, date.second, date.millisecond); - break; - case DateTimeIntervalType.seconds: - date = DateTime(date.year, date.month, date.day, date.hour, - date.minute, date.second + addValue, date.millisecond); - break; - case DateTimeIntervalType.milliseconds: - date = DateTime(date.year, date.month, date.day, date.hour, - date.minute, date.second, date.millisecond + addValue); - break; - case DateTimeIntervalType.auto: - break; - } - value = date.millisecondsSinceEpoch; - } else { - value += addValue; - } - return value; - } - - /// Render plotband element - void _renderPlotBandElement(ChartAxisRenderer axisRenderer, num startValue, - num endValue, PlotBand plotBand, Canvas canvas) { - ChartLocation startPoint, endPoint, segmentStartPoint, segmentEndPoint; - Rect plotBandRect; - int textAngle; - double? left, top, bottom, right; - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisDetails.axis; - final Rect axisRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - axisDetails.orientation == AxisOrientation.horizontal - ? axis.plotOffset - : 0, - axisDetails.orientation == AxisOrientation.vertical - ? axis.plotOffset - : 0)); - - startValue = axis is LogarithmicAxis - ? calculateLogBaseValue(startValue, axis.logBase) - : startValue; - endValue = axis is LogarithmicAxis - ? calculateLogBaseValue(endValue, axis.logBase) - : endValue; - - endValue < 0 - ? endValue <= axisDetails.visibleRange!.minimum - ? endValue = axisDetails.visibleRange!.minimum - : endValue = endValue - : endValue >= axisDetails.visibleRange!.maximum - ? endValue = axisDetails.visibleRange!.maximum - : endValue = endValue; - - startValue < 0 - ? startValue <= axisDetails.visibleRange!.minimum - ? startValue = axisDetails.visibleRange!.minimum - : startValue = startValue - : startValue >= axisDetails.visibleRange!.maximum - ? startValue = axisDetails.visibleRange!.maximum - : startValue = startValue; - - startPoint = calculatePoint(startValue, startValue, axisDetails, - axisDetails, stateProperties.requireInvertedAxis, null, axisRect); - endPoint = calculatePoint(endValue, endValue, axisDetails, axisDetails, - stateProperties.requireInvertedAxis, null, axisRect); - - ChartAxisRenderer? segmentAxisRenderer; - if (plotBand.associatedAxisStart != null || - plotBand.associatedAxisEnd != null) { - if (axis.associatedAxisName == null) { - segmentAxisRenderer = - (axisDetails.orientation == AxisOrientation.horizontal) - ? stateProperties.chartAxis.primaryYAxisRenderer - : stateProperties.chartAxis.primaryXAxisRenderer; - } else { - for (int axisIndex = 0; - axisIndex < - stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - final ChartAxisRenderer targetAxisRenderer = - stateProperties.chartAxis.axisRenderersCollection[axisIndex]; - final ChartAxisRendererDetails targetAxisDetails = - AxisHelper.getAxisRendererDetails(targetAxisRenderer); - if (axis.associatedAxisName == targetAxisDetails.name) { - segmentAxisRenderer = axisRenderer; - } - } - } - final ChartLocation startAndEndValues = _getStartAndEndValues( - segmentAxisRenderer!, - plotBand.associatedAxisStart ?? startValue, - plotBand.associatedAxisEnd ?? endValue, - plotBand, - false); - final ChartAxisRendererDetails segmentAxisDetails = - AxisHelper.getAxisRendererDetails(segmentAxisRenderer); - if (segmentAxisDetails.orientation == AxisOrientation.horizontal) { - segmentStartPoint = calculatePoint( - startAndEndValues.x, - startValue, - segmentAxisDetails, - axisDetails, - stateProperties.requireInvertedAxis, - null, - axisRect); - segmentEndPoint = calculatePoint( - startAndEndValues.y, - endValue, - segmentAxisDetails, - axisDetails, - stateProperties.requireInvertedAxis, - null, - axisRect); - left = plotBand.associatedAxisStart != null - ? segmentStartPoint.x - : axisRect.left; - right = plotBand.associatedAxisEnd != null - ? segmentEndPoint.x - : axisRect.right; - } else { - segmentStartPoint = calculatePoint( - startValue, - startAndEndValues.x, - axisDetails, - segmentAxisDetails, - stateProperties.requireInvertedAxis, - null, - axisRect); - segmentEndPoint = calculatePoint( - endValue, - startAndEndValues.y, - axisDetails, - segmentAxisDetails, - stateProperties.requireInvertedAxis, - null, - axisRect); - top = plotBand.associatedAxisStart != null - ? segmentStartPoint.y - : axisRect.bottom; - bottom = plotBand.associatedAxisEnd != null - ? segmentEndPoint.y - : axisRect.top; - } - } - - if (axisDetails.orientation == AxisOrientation.horizontal) { - 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; - plotBandRect = Rect.fromLTRB(left ?? axisRect.left, top ?? endPoint.y, - right ?? axisRect.right, bottom ?? startPoint.y); - } - _drawPlotBand(plotBand, plotBandRect, textAngle, canvas, axis); - } - - /// To draw the plotband - void _drawPlotBand(PlotBand plotBand, Rect plotBandRect, int textAngle, - Canvas canvas, ChartAxis axis) { - final List dashArray = plotBand.dashArray; - bool needDashLine = true; - // ignore: unnecessary_null_comparison - if (plotBandRect != null && plotBand.color != null) { - Path? path; - for (int i = 1; i < dashArray.length; i = i + 2) { - if (dashArray[i] == 0) { - needDashLine = false; - } - } - - path = Path() - ..moveTo(plotBandRect.left, plotBandRect.top) - ..lineTo(plotBandRect.left + plotBandRect.width, plotBandRect.top) - ..lineTo(plotBandRect.left + plotBandRect.width, - plotBandRect.top + plotBandRect.height) - ..lineTo(plotBandRect.left, plotBandRect.top + plotBandRect.height) - ..close(); - - final Paint paint = Paint(); - Path? _dashPath; - if (needDashLine) { - paint.isAntiAlias = false; - _dashPath = - dashPath(path, dashArray: CircularIntervalList(dashArray)); - } else { - _dashPath = path; - } - // ignore: unnecessary_null_comparison - if (path != null) { - Paint fillPaint; - if (plotBand.gradient != null) { - fillPaint = Paint() - ..shader = plotBand.gradient!.createShader(plotBandRect) - ..style = PaintingStyle.fill; - } else { - fillPaint = Paint() - ..color = plotBand.color.withOpacity(plotBand.opacity) - ..style = PaintingStyle.fill; - } - canvas.drawPath(path, fillPaint); - if (plotBand.borderWidth > 0 && - // ignore: unnecessary_null_comparison - plotBand.borderColor != null && - // ignore: unnecessary_null_comparison - _dashPath != null) { - canvas.drawPath( - _dashPath, - paint - ..color = plotBand.borderColor.withOpacity(plotBand.opacity) - ..style = PaintingStyle.stroke - ..strokeWidth = plotBand.borderWidth); - } - } - } - if (plotBand.text != null && plotBand.text!.isNotEmpty) { - final Size textSize = - measureText(plotBand.text!, plotBand.textStyle, textAngle); - num? x = 0; - num? y = 0; - if (plotBand.horizontalTextPadding != null && - plotBand.horizontalTextPadding != '') { - if (plotBand.horizontalTextPadding!.contains('%')) { - x = percentageToValue( - plotBand.horizontalTextPadding!, - chart.isTransposed - ? stateProperties.chartAxis.axisClipRect.bottom - : stateProperties.chartAxis.axisClipRect.right); - } else if (plotBand.verticalTextPadding!.contains('px')) { - x = double.parse(plotBand.horizontalTextPadding! - .substring(0, plotBand.horizontalTextPadding!.length - 2)); - } else { - x = double.parse(plotBand.horizontalTextPadding!); - } - } - if (plotBand.verticalTextPadding != null && - plotBand.verticalTextPadding != '') { - if (plotBand.verticalTextPadding!.contains('%')) { - y = percentageToValue( - plotBand.verticalTextPadding!, - chart.isTransposed - ? stateProperties.chartAxis.axisClipRect.right - : stateProperties.chartAxis.axisClipRect.bottom); - } else if (plotBand.verticalTextPadding!.contains('px')) { - y = double.parse(plotBand.verticalTextPadding! - .substring(0, plotBand.verticalTextPadding!.length - 2)); - } else { - y = double.parse(plotBand.verticalTextPadding!); - } - } - - drawText( - canvas, - plotBand.text!, - Offset( - plotBand.horizontalTextAlignment == TextAnchor.middle - ? (plotBandRect.left + - plotBandRect.width / 2 - - ((plotBandRect.left == - stateProperties.chartAxis.axisClipRect.right) - ? 0 - : (plotBandRect.right == - stateProperties - .chartAxis.axisClipRect.left) - ? textSize.width - : textSize.width / 2) + - x!) + - (textAngle != 0 ? textSize.width / 2 : 0) - : plotBand.horizontalTextAlignment == TextAnchor.start - ? plotBandRect.left + x! - : plotBandRect.right - textSize.width + x!, - plotBand.verticalTextAlignment == TextAnchor.middle - ? (plotBandRect.top + - plotBandRect.height / 2 - - ((plotBandRect.bottom == - stateProperties.chartAxis.axisClipRect.top) - ? textSize.height - : (plotBandRect.top == - stateProperties - .chartAxis.axisClipRect.bottom) - ? 0 + y! - : textSize.height + y!)) + - (textAngle != 0 ? textSize.height / 2 : 0) - : plotBand.verticalTextAlignment == TextAnchor.start - ? (plotBandRect.top - y!) - : plotBandRect.bottom - textSize.height - y!), - plotBand.textStyle, - textAngle); - } - } - - @override - bool shouldRepaint(_PlotBandPainter oldDelegate) => true; -} 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 deleted file mode 100644 index 83cb0a97c..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart +++ /dev/null @@ -1,4418 +0,0 @@ -import 'dart:ui'; -import 'dart:ui' as 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:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/localizations.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; - -import '../../common/common.dart'; -import '../../common/event_args.dart'; -import '../../common/legend/legend.dart'; -import '../../common/legend/renderer.dart'; -import '../../common/rendering_details.dart'; -import '../../common/series/chart_series.dart'; -import '../../common/template/rendering.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../../common/utils/typedef.dart'; -import '../annotation/annotation_settings.dart'; -import '../axis/axis.dart'; -import '../axis/axis_panel.dart'; -import '../axis/axis_renderer.dart'; -import '../axis/numeric_axis.dart'; -import '../axis/plotband.dart'; -import '../base/series_base.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_segment/column_segment.dart'; -import '../chart_series/error_bar_series.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/data_label_renderer.dart'; -import '../common/marker.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../series_painter/area_painter.dart'; -import '../series_painter/bar_painter.dart'; -import '../series_painter/box_and_whisker_painter.dart'; -import '../series_painter/bubble_painter.dart'; -import '../series_painter/candle_painter.dart'; -import '../series_painter/column_painter.dart'; -import '../series_painter/error_bar_painter.dart'; -import '../series_painter/fastline_painter.dart'; -import '../series_painter/hilo_painter.dart'; -import '../series_painter/hiloopenclose_painter.dart'; -import '../series_painter/histogram_painter.dart'; -import '../series_painter/line_painter.dart'; -import '../series_painter/range_area_painter.dart'; -import '../series_painter/range_column_painter.dart'; -import '../series_painter/scatter_painter.dart'; -import '../series_painter/spline_area_painter.dart'; -import '../series_painter/spline_painter.dart'; -import '../series_painter/spline_range_area_painter.dart'; -import '../series_painter/stacked_area_painter.dart'; -import '../series_painter/stacked_bar_painter.dart'; -import '../series_painter/stacked_column_painter.dart'; -import '../series_painter/stacked_line_painter.dart'; -import '../series_painter/step_area_painter.dart'; -import '../series_painter/stepline_painter.dart'; -import '../series_painter/waterfall_painter.dart'; -import '../technical_indicators/technical_indicator.dart'; -import '../trendlines/trendlines.dart'; -import '../trendlines/trendlines_painter.dart'; -import '../user_interaction/crosshair.dart'; -import '../user_interaction/crosshair_painter.dart'; -import '../user_interaction/selection_renderer.dart'; -import '../user_interaction/trackball.dart'; -import '../user_interaction/trackball_marker_setting_renderer.dart'; -import '../user_interaction/trackball_painter.dart'; -import '../user_interaction/trackball_template.dart'; -import '../user_interaction/zooming_painter.dart'; -import '../user_interaction/zooming_panning.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Renders the Cartesian type charts. -/// -/// Cartesian charts are generally charts with horizontal and vertical axes.[SfCartesianChart] provides options to customize -/// chart types using the `series` property. -/// -///```dart -///TooltipBehavior _tooltipBehavior; -/// -///@override -///void initState() { -/// _tooltipBehavior = TooltipBehavior( enable: true); -/// super.initState(); -/// } -/// -///Widget build(BuildContext context) { -/// return Center( -/// child:SfCartesianChart( -/// title: ChartTitle(text: 'Flutter Chart'), -/// legend: Legend(isVisible: true), -/// series: getDefaultData(), -/// tooltipBehavior: _tooltipBehavior, -/// ) -/// ); -///} -///static List> getDefaultData() { -/// final List chartData = [ -/// SalesData(DateTime(2005, 0, 1), 'India', 1.5, 21, 28, 680, 760), -/// SalesData(DateTime(2006, 0, 1), 'China', 2.2, 24, 44, 550, 880), -/// SalesData(DateTime(2007, 0, 1), 'USA', 3.32, 36, 48, 440, 788), -/// SalesData(DateTime(2008, 0, 1), 'Japan', 4.56, 38, 50, 350, 560), -/// SalesData(DateTime(2009, 0, 1), 'Russia', 5.87, 54, 66, 444, 566), -/// SalesData(DateTime(2010, 0, 1), 'France', 6.8, 57, 78, 780, 650), -/// SalesData(DateTime(2011, 0, 1), 'Germany', 8.5, 70, 84, 450, 800) -/// ]; -/// return >[ -/// LineSeries( -/// enableToolTip: isTooltipVisible, -/// dataSource: chartData, -/// xValueMapper: (SalesData sales, _) => sales.numeric, -/// yValueMapper: (SalesData sales, _) => sales.sales1, -/// width: lineWidth ?? 2, -/// enableAnimation: false, -/// markerSettings: MarkerSettings( -/// isVisible: isMarkerVisible, -/// height: markerWidth ?? 4, -/// width: markerHeight ?? 4, -/// shape: DataMarkerType.Circle, -/// borderWidth: 3, -/// borderColor: Colors.red), -/// dataLabelSettings: DataLabelSettings( -/// visible: isDataLabelVisible, position: ChartDataLabelAlignment.Auto)), -/// LineSeries( -/// enableToolTip: isTooltipVisible, -/// dataSource: chartData, -/// enableAnimation: false, -/// width: lineWidth ?? 2, -/// xValueMapper: (SalesData sales, _) => sales.numeric, -/// yValueMapper: (SalesData sales, _) => sales.sales2, -/// markerSettings: MarkerSettings( -/// isVisible: isMarkerVisible, -/// height: markerWidth ?? 4, -/// width: markerHeight ?? 4, -/// shape: DataMarkerType.Circle, -/// borderWidth: 3, -/// borderColor: Colors.black), -/// dataLabelSettings: DataLabelSettings( -/// isVisible: isDataLabelVisible, position: ChartDataLabelAlignment.Auto)) -/// ]; -/// } -/// ``` -/// -// ignore: must_be_immutable -class SfCartesianChart extends StatefulWidget { - /// Creating an argument constructor of SfCartesianChart class. - SfCartesianChart( - {Key? key, - this.backgroundColor, - this.enableSideBySideSeriesPlacement = true, - this.borderColor = Colors.transparent, - this.borderWidth = 0, - this.plotAreaBackgroundColor, - this.plotAreaBorderColor, - this.plotAreaBorderWidth = 0.7, - this.plotAreaBackgroundImage, - this.onTooltipRender, - this.onActualRangeChanged, - this.onDataLabelRender, - this.onLegendItemRender, - this.onTrackballPositionChanging, - this.onCrosshairPositionChanging, - this.onZooming, - this.onZoomStart, - this.onZoomEnd, - this.onZoomReset, - this.onAxisLabelTapped, - this.onDataLabelTapped, - this.onLegendTapped, - this.onSelectionChanged, - this.onChartTouchInteractionUp, - this.onChartTouchInteractionDown, - this.onChartTouchInteractionMove, - this.onMarkerRender, - 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), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - 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}) - : primaryXAxis = primaryXAxis ?? NumericAxis(), - primaryYAxis = primaryYAxis ?? NumericAxis(), - title = title ?? ChartTitle(), - axes = axes ?? [], - series = series ?? >[], - margin = margin ?? const EdgeInsets.all(10), - zoomPanBehavior = zoomPanBehavior ?? ZoomPanBehavior(), - tooltipBehavior = tooltipBehavior ?? TooltipBehavior(), - crosshairBehavior = crosshairBehavior ?? CrosshairBehavior(), - trackballBehavior = trackballBehavior ?? TrackballBehavior(), - legend = legend ?? Legend(), - selectionType = selectionType ?? SelectionType.point, - selectionGesture = selectionGesture ?? ActivationMode.singleTap, - enableMultiSelection = enableMultiSelection ?? false, - indicators = indicators ?? >[], - super(key: key); - - /// Customizes the chart title. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// title: ChartTitle( - /// text: 'Area with animation', - /// alignment: ChartAlignment.center, - /// backgroundColor: Colors.white, - /// borderColor: Colors.transparent, - /// borderWidth: 0) - /// ) - /// ); - ///} - ///``` - final ChartTitle title; - - /// Customizes the legend in the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// legend: Legend(isVisible: true), - /// ) - /// ); - ///} - ///``` - final Legend legend; - - /// Background color of the chart. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// backgroundColor: Colors.blue - /// ) - /// ); - ///} - ///``` - final Color? backgroundColor; - - /// Color of the chart border. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// borderColor: Colors.red - /// ) - /// ); - ///} - ///``` - final Color borderColor; - - /// Width of the chart border. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// borderWidth: 2 - /// ) - /// ); - ///} - ///``` - final double borderWidth; - - /// Background color of the plot area. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// plotAreaBackgroundColor: Colors.red, - /// ) - /// ); - ///} - ///``` - final Color? plotAreaBackgroundColor; - - /// Border color of the plot area. - /// - /// Defaults to `Colors.grey`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// plotAreaBorderColor: Colors.red, - /// ) - /// ); - ///} - ///``` - final Color? plotAreaBorderColor; - - /// Border width of the plot area. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// plotAreaBorderColor: Colors.red, - /// plotAreaBorderWidth: 2 - /// ) - /// ); - ///} - ///``` - final double plotAreaBorderWidth; - - /// Customizes the primary x-axis in chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: DateTimeAxis(interval: 1) - /// ) - /// ); - ///} - ///``` - final ChartAxis primaryXAxis; - - /// Customizes the primary y-axis in chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryYAxis: NumericAxis(isinversed: false) - /// ) - /// ); - ///} - ///``` - final ChartAxis primaryYAxis; - - /// Margin for chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// margin: const EdgeInsets.all(2), - /// borderColor: Colors.blue - /// ) - /// ); - ///} - ///``` - final EdgeInsets margin; - - /// Customizes the additional axes in the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// axes: [ - /// NumericAxis( - /// majorGridLines: MajorGridLines( - /// color: Colors.transparent) - /// ) - /// ] - /// ) - /// ); - ///} - ///``` - final List axes; - - /// Enables or disables the placing of series side by side. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// enableSideBySideSeriesPlacement: false - /// ) - /// ); - ///} - ///``` - final bool enableSideBySideSeriesPlacement; - - /// 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. - ///```dart - ///TooltipBehavior _tooltipBehavior; - /// - ///@override - ///void initState() { - /// _tooltipBehavior = TooltipBehavior( enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// tooltipBehavior: _tooltipBehavior, - /// onTooltipRender: (TooltipArgs args) => tool(args) - /// ) - /// ); - ///} - ///void tool(TooltipArgs args) { - /// args.locationX = 30; - ///} - ///``` - 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. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onActualRangeChanged: (ActualRangeChangedArgs args) => range(args) - /// ) - /// ); - ///} - ///void range(ActualRangeChangedArgs args) { - /// print(args.visibleMin); - ///} - ///``` - final ChartActualRangeChangedCallback? onActualRangeChanged; - - /// Occurs when tapping the axis label. Here, you can get the appropriate axis that is - /// tapped and the axis label text. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onDataLabelRender: (DataLabelRenderArgs args) => dataLabel(args), - /// ) - /// ); - ///} - ///void dataLabel(DataLabelRenderArgs args) { - /// args.text = 'data Label'; - ///} - ///``` - 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. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// legend: Legend(isVisible: true), - /// onLegendItemRender: (LegendRenderArgs args) => legend(args) - /// ) - /// ); - ///} - ///void legend(LegendRenderArgs args) { - /// args.seriesIndex = 2; - ///} - ///``` - final ChartLegendRenderCallback? onLegendItemRender; - - /// Occurs while the trackball position is changed. Here, you can customize the text of - /// the trackball. - ///```dart - ///TrackballBehavior _trackballBehavior; - /// - ///@override - ///void initState() { - /// _trackballBehavior = TrackballBehavior( enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: _trackballBehavior, - /// onTrackballPositionChanging: (TrackballArgs args) => trackball(args) - /// ) - /// ); - ///} - ///void trackball(TrackballArgs args) { - /// args.chartPointInfo = ChartPointInfo(); - ///} - ///``` - final ChartTrackballCallback? onTrackballPositionChanging; - - /// Occurs when tapping the axis label. Here, you can get the appropriate axis that is - /// tapped and the axis label text. - ///```dart - ///CrosshairBehavior _crosshairBehavior; - /// - ///@override - ///void initState() { - /// _crosshairBehavior = CrosshairBehavior(enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// crosshairBehavior: crosshairBehavior, - /// onCrosshairPositionChanging: (CrosshairRenderArgs args) => crosshair(args) - /// ) - /// ); - ///} - ///void crosshair(CrosshairRenderArgs args) { - /// args.text = 'crosshair'; - ///} - ///``` - 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 - /// zoom position, previous zoom factor, and previous zoom position. - ///```dart - ///ZoomPanBehavior _zoomPanBehavior; - /// - ///@override - ///void initState() { - /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// zoomPanBehavior: _zoomPanBehavior, - /// onZoomStart: (ZoomPanArgs args) => zoom(args), - /// ) - /// ); - ///} - ///void zoom(ZoomPanArgs args) { - /// args.currentZoomFactor = 0.2; - ///} - ///``` - 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. - ///```dart - ///ZoomPanBehavior _zoomPanBehavior; - /// - ///@override - ///void initState() { - /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// zoomPanBehavior: _zoomPanBehavior, - /// onZoomEnd: (ZoomPanArgs args) => zoom(args), - /// ) - /// ); - ///} - ///void zoom(ZoomPanArgs args) { - /// print(args.currentZoomPosition); - ///} - ///``` - 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. - ///```dart - ///ZoomPanBehavior _zoomPanBehavior; - /// - ///@override - ///void initState() { - /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// zoomPanBehavior: _zoomPanBehavior, - /// onZoomReset: (ZoomPanArgs args) => zoom(args), - /// ) - /// ); - ///} - ///void zoom(ZoomPanArgs args) { - /// print(args.currentZoomPosition); - ///} - ///``` - final ChartZoomingCallback? onZoomReset; - - /// Occurs when Zooming event is performed. Here, you can get the axis, current zoom factor, - /// current zoom position, previous zoom factor, and previous zoom position. - ///```dart - ///ZoomPanBehavior _zoomPanBehavior; - /// - ///@override - ///void initState() { - /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// zoomPanBehavior: _zoomPanBehavior, - /// onZooming: (ZoomPanArgs args) => zoom(args), - /// ) - /// ); - ///} - ///void zoom(ZoomPanArgs args) { - /// print(args.currentZoomPosition); - ///} - ///``` - final ChartZoomingCallback? onZooming; - - /// Called when the data label is tapped. - /// - /// 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. - /// - ///_Note:_ This callback will not be called, when the builder is specified for data label - /// (data label template). For this case, custom widget specified in the `DataLabelSettings.builder` property - /// can be wrapped using the `GestureDetector` and this functionality can be achieved in the application level. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onDataLabelTapped: (DataLabelTapDetails args) { - /// print(arg.seriesIndex); - /// } - /// ) - /// ); - ///} - /// - ///``` - - final DataLabelTapCallback? onDataLabelTapped; - - /// Occurs when tapping the axis label. Here, you can get the appropriate axis that is - /// tapped and the axis label text. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onAxisLabelTapped: (AxisLabelTapArgs args) => axis(args), - /// ) - /// ); - ///} - ///void axis(AxisLabelTapArgs args) { - /// print(args.text); - ///} - ///``` - 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. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onLegendTapped: (LegendTapArgs args) => legend(args), - /// ) - /// ); - ///} - ///void legend(LegendTapArgs args) { - /// print(args.pointIndex); - ///} - ///``` - 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 - /// width, unselected border width, series index, and point index. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onSelectionChanged: (SelectionArgs args) => print(args.selectedColor), - /// ) - /// ); - ///} - final ChartSelectionCallback? onSelectionChanged; - - /// Occurs when tapped on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final ChartTouchInteractionCallback? onChartTouchInteractionUp; - - /// Occurs when touched on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final ChartTouchInteractionCallback? onChartTouchInteractionDown; - - /// Occurs when touched and moved on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final ChartTouchInteractionCallback? onChartTouchInteractionMove; - - /// Occurs when the marker is rendered. Here, you can get the marker pointIndex - /// shape, height and width of data markers. - ///```dart - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onMarkerRender: (MarkerRenderArgs markerargs) - /// { - /// if(markerargs.pointIndex==2) - /// { - /// markerargs.markerHeight=20.0; - /// markerargs.markerWidth=20.0; - /// markerargs.shape=DataMarkerType.triangle; - /// } - /// }, - /// ) - /// ); - ///} - ///``` - /// - final ChartMarkerRenderCallback? onMarkerRender; - - /// Customizes the tooltip in chart. - /// - ///```dart - ///TooltipBehavior _tooltipBehavior; - /// - ///@override - ///void initState() { - /// _tooltipBehavior = TooltipBehavior(enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// tooltipBehavior: _tooltipBehavior - /// ) - /// ); - ///} - ///``` - final TooltipBehavior tooltipBehavior; - - /// Customizes the crosshair in chart. - /// - ///```dart - ///CrosshairBehavior _crosshairBehavior; - /// - ///@override - ///void initState() { - /// _crosshairBehavior = CrosshairBehavior(enable: true); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior, - /// ) - /// ); - ///} - ///``` - final CrosshairBehavior crosshairBehavior; - - /// Customizes the trackball in chart. - /// - ///```dart - ///TrackballBehavior _trackballBehavior; - /// - ///@override - ///void initState() { - /// _trackballBehavior = TrackballBehavior(enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: _trackballBehavior, - /// ) - /// ); - ///} - ///``` - final TrackballBehavior trackballBehavior; - - /// Customizes the zooming and panning settings. - /// - ///```dart - ///ZoomPanBehavior _zoomPanBehavior; - /// - ///@override - ///void initState() { - /// _zoomPanBehavior = ZoomPanBehavior( enablePanning: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// zoomPanBehavior: _zoomPanBehavior - /// ) - /// ); - ///} - ///``` - final ZoomPanBehavior zoomPanBehavior; - - /// Mode of selecting the data points or series. - /// - /// Defaults to `SelectionType.point`. - /// - /// Also refer [SelectionType]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// selectionType: SelectionType.series, - /// ) - /// ); - ///} - ///``` - final SelectionType selectionType; - - /// Customizes the annotations. Annotations are used to mark the specific area of interest - /// in the plot area with texts, shapes, or images. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// annotations: [ - /// CartesianChartAnnotation( - /// widget: Container( - /// child: const Text('Annotation')), - /// coordinateUnit: CoordinateUnit.point, - /// region: AnnotationRegion.chartArea, - /// x: 3, - /// y: 60 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final List? annotations; - - /// Enables or disables the multiple data points or series selection. - /// - /// Defaults to `false`. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///@override - ///void initState() { - /// _selectionBehavior = SelectionBehavior( enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// enableMultiSelection: true, - /// series: >[ - /// BarSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final bool enableMultiSelection; - - /// Gesture for activating the selection. Selection can be activated in tap, - /// double tap, and long press. - /// - /// Defaults to `ActivationMode.tap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///@override - ///void initState() { - /// _selectionBehavior = SelectionBehavior( enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// selectionGesture: ActivationMode.doubleTap, - /// series: >[ - /// BarSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ActivationMode selectionGesture; - - /// Background image for chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// plotAreaBackgroundImage: const AssetImage('images/bike.png'), - /// ) - /// ); - ///} - ///``` - 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. - /// - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///@override - ///void initState() { - /// _selectionBehavior = SelectionBehavior( enable: true); - /// super.initState(); - /// } - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// BarSeries( - /// initialSelectedDataIndexes: [2, 0], - /// selectionBehavior: _selectionBehavior - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - - /// By setting this, the orientation of x-axis is set to vertical and orientation of - /// y-axis is set to horizontal. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// isTransposed: true, - /// ) - /// ); - ///} - ///``` - final bool isTransposed; - - /// Axis elements animation on visible range change. - /// - /// Axis elements like grid lines, tick lines and labels will be animated when the axis range is changed dynamically. - /// Axis visible range will be changed while zooming, panning or while updating the data points. - /// - /// The elements will be animated on setting `true` to this property and this is applicable for all primary and secondary axis in the chart. - /// - /// Defaults to `false`. - /// - /// See also [ChartSeries.animationDuration] for changing the series animation duration. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// enableAxisAnimation: true, - /// ) - /// ); - ///} - ///``` - final bool enableAxisAnimation; - - /// Customizes the series in chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// AreaSeries( - /// dataSource: chartData, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final List> series; - - /// Color palette for chart series. If the series color is not specified, then the series - /// will be rendered with appropriate palette color. Ten colors are available by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// palette: [Colors.red, Colors.green] - /// ) - /// ); - ///} - ///``` - final List palette; - - /// 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(); -} - -/// Represents the state class of [SfCartesianChart] widget -/// -class SfCartesianChartState extends State - with TickerProviderStateMixin { - late CartesianStateProperties _stateProperties; - - /// To initialize default values - void _initializeDefaultValues() { - _stateProperties = CartesianStateProperties( - renderingDetails: RenderingDetails(), chartState: this); - _stateProperties.chartAxis = ChartAxisPanel(_stateProperties); - _stateProperties.chartSeries = ChartSeriesPanel(_stateProperties); - _stateProperties.renderingDetails.chartLegend = - ChartLegend(_stateProperties); - _stateProperties.seriesRenderers = []; - _stateProperties.controllerList = {}; - _stateProperties.repaintNotifiers = >{ - 'zoom': ValueNotifier(0), - 'trendline': ValueNotifier(0), - 'trackball': ValueNotifier(0), - 'crosshair': ValueNotifier(0), - 'indicator': ValueNotifier(0), - }; - _stateProperties.renderingDetails.legendWidgetContext = - []; - _stateProperties.renderingDetails.didSizeChange = false; - _stateProperties.renderingDetails.templates = []; - _stateProperties.oldAxisRenderers = []; - _stateProperties.zoomedAxisRendererStates = []; - _stateProperties.zoomAxes = []; - _stateProperties.renderingDetails.chartContainerRect = Rect.zero; - _stateProperties.zoomProgress = false; - _stateProperties.renderingDetails.legendToggleStates = - []; - _stateProperties.selectedSegments = []; - _stateProperties.unselectedSegments = []; - _stateProperties.renderingDetails.legendToggleTemplateStates = - []; - _stateProperties.renderDatalabelRegions = []; - _stateProperties.renderingDetails.dataLabelTemplateRegions = []; - _stateProperties.annotationRegions = []; - _stateProperties.renderingDetails.animateCompleted = false; - _stateProperties.renderingDetails.widgetNeedUpdate = false; - _stateProperties.oldSeriesRenderers = []; - _stateProperties.oldSeriesKeys = ?>[]; - _stateProperties.renderingDetails.isLegendToggled = false; - _stateProperties.oldSeriesVisible = []; - _stateProperties.touchStartPositions = []; - _stateProperties.touchMovePositions = []; - _stateProperties.enableDoubleTap = false; - _stateProperties.legendToggling = false; - _stateProperties.painterKeys = []; - _stateProperties.isNeedUpdate = true; - _stateProperties.isRedrawByZoomPan = false; - _stateProperties.isLoadMoreIndicator = false; - _stateProperties.technicalIndicatorRenderer = - []; - _stateProperties.zoomPanBehaviorRenderer = - ZoomPanBehaviorRenderer(_stateProperties); - _stateProperties.trackballBehaviorRenderer = - TrackballBehaviorRenderer(_stateProperties); - _stateProperties.crosshairBehaviorRenderer = - CrosshairBehaviorRenderer(_stateProperties); - _stateProperties.renderingDetails.tooltipBehaviorRenderer = - TooltipBehaviorRenderer(_stateProperties); - _stateProperties.renderingDetails.legendRenderer = - LegendRenderer(widget.legend); - _stateProperties.trackballMarkerSettingsRenderer = - TrackballMarkerSettingsRenderer( - widget.trackballBehavior.markerSettings); - final TargetPlatform platform = defaultTargetPlatform; - _stateProperties.enableMouseHover = kIsWeb || - platform == TargetPlatform.windows || - platform == TargetPlatform.macOS || - platform == TargetPlatform.linux; - } - - /// 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(); - // Create the series renderer while initial rendering // - _createAndUpdateSeriesRenderer(); - 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() { - _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); - _stateProperties.renderingDetails.isRtl = - Directionality.of(context) == TextDirection.rtl; - 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) { - _stateProperties.isRedrawByZoomPan = false; - _stateProperties.isLoadMoreIndicator = false; - _stateProperties.zoomProgress = false; - final List oldWidgetSeriesRenderers = - //ignore: prefer_spread_collections - []..addAll(_stateProperties.seriesRenderers); - final List oldWidgetOldSeriesRenderers = - [] - //ignore: prefer_spread_collections - ..addAll(_stateProperties.oldSeriesRenderers); - - //Update and maintain the series state, when we update the series in the series collection // - - _createAndUpdateSeriesRenderer( - oldWidget, oldWidgetSeriesRenderers, oldWidgetOldSeriesRenderers); - needsRepaintChart( - _stateProperties, - _stateProperties.chartAxis.axisRenderersCollection, - oldWidgetSeriesRenderers); - _stateProperties.renderingDetails.isLegendToggled = false; - // ignore: unnecessary_null_comparison - if (_stateProperties.renderingDetails.legendWidgetContext != null && - _stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { - _stateProperties.renderingDetails.legendWidgetContext.clear(); - } - final SeriesRendererDetails? seriesRendererDetails = - _stateProperties.seriesRenderers.isNotEmpty - ? SeriesHelper.getSeriesRendererDetails( - _stateProperties.seriesRenderers[0]) - : null; - if (seriesRendererDetails?.selectionBehaviorRenderer != null) { - final SelectionDetails? selectionDetails = - SelectionHelper.getRenderingDetails( - seriesRendererDetails!.selectionBehaviorRenderer!); - if (_stateProperties.seriesRenderers.isNotEmpty && - selectionDetails?.selectionRenderer != null) { - selectionDetails?.selectionRenderer?.isInteraction = false; - } - } - if (_stateProperties.isNeedUpdate) { - _stateProperties.renderingDetails.widgetNeedUpdate = true; - _stateProperties.oldSeriesRenderers = oldWidgetSeriesRenderers; - _getOldSeriesKeys(_stateProperties.oldSeriesRenderers); - _stateProperties.oldAxisRenderers = [] - //ignore: prefer_spread_collections - ..addAll(_stateProperties.chartAxis.axisRenderersCollection); - } - 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) { - _stateProperties.renderingDetails.oldDeviceOrientation = - _stateProperties.renderingDetails.oldDeviceOrientation == null - ? MediaQuery.of(context).orientation - : _stateProperties.renderingDetails.deviceOrientation; - _stateProperties.renderingDetails.deviceOrientation = - MediaQuery.of(context).orientation; - _stateProperties.renderingDetails.initialRender = - _stateProperties.renderingDetails.initialRender == null; - _stateProperties.requireInvertedAxis = false; - _stateProperties.triggerLoaded = false; - _stateProperties.isSeriesLoaded = _stateProperties.isSeriesLoaded ?? true; - _findVisibleSeries(context); - _stateProperties.isSeriesLoaded = false; - - return RepaintBoundary( - child: ChartContainer( - child: Container( - decoration: BoxDecoration( - color: widget.backgroundColor ?? - _stateProperties.renderingDetails.chartTheme.backgroundColor, - border: - Border.all(color: widget.borderColor, width: widget.borderWidth)), - child: Container( - margin: EdgeInsets.fromLTRB(widget.margin.left, widget.margin.top, - widget.margin.right, widget.margin.bottom), - child: Column( - children: [_renderTitle(), _renderChartElements(context)], - )), - ))); - } - - /// 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 life cycle is terminal: there is no way to remount a [State] object that has been disposed. - /// - /// Sub classes 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() { - _stateProperties.controllerList.forEach(disposeAnimationController); - super.dispose(); - } - - /// Method to convert the [SfCartesianChart] 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` - /// - /// ```dart - /// final GlobalKey _key = GlobalKey(); - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Column( - /// children: [SfCartesianChart( - /// key: _key - /// series: >[ - /// AreaSeries( - /// dataSource: chartData, - /// ), - /// ], - /// ), - /// RaisedButton( - /// child: Text( - /// 'To Image', - /// ), - /// onPressed: _renderImage, - /// shape: RoundedRectangleBorder( - /// borderRadius: BorderRadius.circular(20)), - /// ) - /// ], - /// ), - /// ); - /// } - - /// Future _renderImage() async { - /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); - /// final bytes = await data.toByteData(format: dart_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.white, - /// child: Image.memory(bytes.buffer.asUint8List()), - /// ), - /// ), - /// ); - /// }, - /// ), - /// ); - /// } - ///} - ///``` - - Future toImage({double pixelRatio = 1.0}) async { - 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 - - return image; - } - - ///Storing old series key values - void _getOldSeriesKeys(List oldSeriesRenderers) { - _stateProperties.oldSeriesKeys = ?>[]; - for (int i = 0; i < oldSeriesRenderers.length; i++) { - _stateProperties.oldSeriesKeys.add( - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[i]) - .series - .key); - } - } - - /// In this method, create and update the series renderer for each series // - void _createAndUpdateSeriesRenderer( - [SfCartesianChart? oldWidget, - List? oldWidgetSeriesRenderers, - List? oldWidgetOldSeriesRenderers]) { - // ignore: unnecessary_null_comparison - if (widget.series != null && widget.series.isNotEmpty) { - if (oldWidget != null) { - _stateProperties.oldSeriesRenderers = []; - _stateProperties.oldSeriesRenderers.addAll(oldWidgetSeriesRenderers!); - } - _stateProperties.seriesRenderers = []; - final int seriesLength = widget.series.length; - dynamic series; - int? index, oldSeriesIndex; - for (int i = 0; i < seriesLength; i++) { - series = widget.series[i]; - index = null; - oldSeriesIndex = null; - if (oldWidget != null) { - if (oldWidgetOldSeriesRenderers!.isNotEmpty) { - // Check the current series is already exist in old widget // - index = i < oldWidgetOldSeriesRenderers.length && - isSameSeries( - SeriesHelper.getSeriesRendererDetails( - oldWidgetOldSeriesRenderers[i]) - .series, - series) - ? i - : _getExistingSeriesIndex(series, oldWidgetOldSeriesRenderers); - } - if (oldWidgetSeriesRenderers!.isNotEmpty) { - oldSeriesIndex = i < oldWidgetSeriesRenderers.length && - isSameSeries( - SeriesHelper.getSeriesRendererDetails( - oldWidgetSeriesRenderers[i]) - .series, - series) - ? i - : _getExistingSeriesIndex(series, oldWidgetSeriesRenderers); - } - } - // Create and update the series list here - CartesianSeriesRenderer seriesRenderer; - - if (index != null && - index < oldWidgetOldSeriesRenderers!.length && - // ignore: unnecessary_null_comparison - oldWidgetOldSeriesRenderers[index] != null) { - seriesRenderer = oldWidgetOldSeriesRenderers[index]; - } else { - seriesRenderer = series.createRenderer(series); - SeriesHelper.setSeriesRendererDetails( - seriesRenderer, SeriesRendererDetails(seriesRenderer)); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - seriesRendererDetails.seriesIndex = i; - seriesRendererDetails.seriesType = getSeriesType(seriesRenderer); - seriesRendererDetails.stateProperties = _stateProperties; - seriesRendererDetails.repaintNotifier = ValueNotifier(0); - if (seriesRenderer is XyDataSeriesRenderer) { - seriesRendererDetails.animationController = - AnimationController(vsync: this) - ..addListener(seriesRendererDetails.repaintSeriesElement); - _stateProperties - .controllerList[seriesRendererDetails.animationController] = - seriesRendererDetails.repaintSeriesElement; - seriesRendererDetails.animationController.addStatusListener( - seriesRendererDetails.animationStatusListener); - } - seriesRendererDetails.controller ??= - ChartSeriesController(seriesRenderer as XyDataSeriesRenderer); - } - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (series.onRendererCreated != null) { - series.onRendererCreated(seriesRendererDetails.controller); - } - seriesRendererDetails.series = series; - seriesRendererDetails.isSelectionEnable = - series.selectionBehavior.enable == true; - seriesRendererDetails.visible = null; - seriesRendererDetails.chart = widget; - seriesRendererDetails.hasDataLabelTemplate = false; - - if (oldWidgetSeriesRenderers != null && - oldSeriesIndex != null && - oldWidgetSeriesRenderers.length > oldSeriesIndex) { - final SeriesRendererDetails oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - oldWidgetSeriesRenderers[oldSeriesIndex]); - seriesRendererDetails.oldSeries = oldSeriesRendererDetails.series; - if (seriesRenderer is FastLineSeriesRenderer && - oldWidgetSeriesRenderers[oldSeriesIndex] - is FastLineSeriesRenderer) { - final FastLineSeriesRenderer fastlineSeriesRenderer = - oldWidgetSeriesRenderers[oldSeriesIndex] - as FastLineSeriesRenderer; - seriesRendererDetails - .oldDataPoints = >[] - //ignore: prefer_spread_collections - ..addAll( - SeriesHelper.getSeriesRendererDetails(fastlineSeriesRenderer) - .overallDataPoints); - } else { - seriesRendererDetails.oldDataPoints = - >[] - //ignore: prefer_spread_collections - ..addAll(oldSeriesRendererDetails.dataPoints); - } - seriesRendererDetails.oldSelectedIndexes = - oldSeriesRendererDetails.oldSelectedIndexes; - seriesRendererDetails.repaintNotifier = - oldSeriesRendererDetails.repaintNotifier; - seriesRendererDetails.animationController = - oldSeriesRendererDetails.animationController; - } else { - seriesRendererDetails.oldSeries = null; - seriesRendererDetails.oldDataPoints = - >[]; - } - - _stateProperties.seriesRenderers.add(seriesRenderer); - _stateProperties.chartSeries.visibleSeriesRenderers.add(seriesRenderer); - } - } else { - _stateProperties.seriesRenderers.clear(); - _stateProperties.chartSeries.visibleSeriesRenderers.clear(); - } - } - - /// Check current series index is exist in another index - int? _getExistingSeriesIndex(CartesianSeries currentSeries, - List oldSeriesRenderers) { - if (currentSeries.key != null) { - for (int index = 0; index < oldSeriesRenderers.length; index++) { - final CartesianSeries series = - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[index]) - .series; - if (isSameSeries(series, currentSeries)) { - return index; - } - } - } - return null; - } - - /// Refresh method for axis - void _refresh() { - if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { - for (int i = 0; - i < _stateProperties.renderingDetails.legendWidgetContext.length; - i++) { - final MeasureWidgetContext templateContext = - _stateProperties.renderingDetails.legendWidgetContext[i]; - final RenderBox renderBox = - templateContext.context!.findRenderObject() as RenderBox; - templateContext.size = renderBox.size; - } - _stateProperties.legendRefresh = true; - setState(() { - /// The chart will be rebuilding again, Once legend template sizes will be calculated. - }); - } - } - - Widget _renderTitle() { - Widget titleWidget; - // ignore: unnecessary_null_comparison - if (_stateProperties.chart.title.text != null && - _stateProperties.chart.title.text.isNotEmpty) { - final Paint titleBackground = Paint() - ..color = _stateProperties.chart.title.borderColor - ..style = PaintingStyle.stroke - ..strokeWidth = _stateProperties.chart.title.borderWidth; - final TextStyle titleStyle = getTextStyle( - textStyle: _stateProperties.chart.title.textStyle, - background: titleBackground, - fontColor: _stateProperties.chart.title.textStyle.color ?? - _stateProperties.renderingDetails.chartTheme.titleTextColor); - final TextStyle textStyle = TextStyle( - color: titleStyle.color, - fontSize: titleStyle.fontSize, - fontFamily: titleStyle.fontFamily, - fontStyle: titleStyle.fontStyle, - fontWeight: titleStyle.fontWeight); - titleWidget = Container( - child: Container( - child: Text(_stateProperties.chart.title.text, - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - textScaleFactor: 1.2, - style: textStyle), - decoration: BoxDecoration( - color: _stateProperties.chart.title.backgroundColor ?? - _stateProperties - .renderingDetails.chartTheme.titleBackgroundColor, - border: Border.all( - color: _stateProperties.chart.title.borderWidth == 0 - ? Colors.transparent - : _stateProperties.chart.title - .borderColor, // ?? _chartTheme.titleTextColor, - width: _stateProperties.chart.title.borderWidth)), - ), - alignment: - (_stateProperties.chart.title.alignment == ChartAlignment.near) - ? Alignment.topLeft - : (_stateProperties.chart.title.alignment == ChartAlignment.far) - ? Alignment.topRight - : (_stateProperties.chart.title.alignment == - ChartAlignment.center) - ? Alignment.topCenter - : Alignment.topCenter, - ); - } else { - titleWidget = Container(); - } - return titleWidget; - } - - /// To arrange the chart area and legend area based on the legend position - Widget _renderChartElements(BuildContext context) { - if (widget.plotAreaBackgroundImage != null || widget.legend.image != null) { - calculateImage(_stateProperties); - } - _stateProperties.renderingDetails.deviceOrientation = - MediaQuery.of(context).orientation; - return Expanded( - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - Widget? element; - _stateProperties.renderingDetails.prevSize = - _stateProperties.renderingDetails.prevSize ?? constraints.biggest; - _stateProperties.renderingDetails.didSizeChange = - _stateProperties.renderingDetails.prevSize != constraints.biggest; - _stateProperties.renderingDetails.prevSize = constraints.biggest; - _stateProperties.isTooltipOrientationChanged = false; - final CartesianChartPoint _crosshairPoint = - _getCrosshairChartPoint(_stateProperties); - SchedulerBinding.instance!.addPostFrameCallback((_) { - _validateStateMaintenance(_stateProperties, _crosshairPoint); - }); - final List legendTemplates = - _bindCartesianLegendTemplateWidgets(); - if (legendTemplates.isNotEmpty && - _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { - // ignore: avoid_unnecessary_containers - element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance?.addPostFrameCallback((_) => _refresh()); - } else { - _initialize(constraints); - _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( - _stateProperties.renderingDetails.chartLegend.chartSize); - _stateProperties.containerArea = ContainerArea(_stateProperties); - element = getElements( - _stateProperties, _stateProperties.containerArea, constraints); - } - return element!; - }), - ); - } - - /// To return the template widget - List _bindCartesianLegendTemplateWidgets() { - Widget? legendWidget; - final List templates = []; - if (widget.legend.isVisible! && widget.legend.legendItemBuilder != null) { - for (int i = 0; i < _stateProperties.seriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _stateProperties.seriesRenderers[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.series.isVisibleInLegend == true) { - legendWidget = widget.legend.legendItemBuilder!( - seriesRendererDetails.seriesName!, - seriesRendererDetails.series, - null, - i); - templates.add(MeasureWidgetSize( - stateProperties: _stateProperties, - seriesIndex: i, - pointIndex: null, - type: 'Legend', - currentKey: GlobalKey(), - currentWidget: legendWidget, - opacityValue: 0.0)); - } - } - } - return templates; - } - - /// To initialize chart legend - void _initialize(BoxConstraints constraints) { - final double width = constraints.maxWidth; - final double height = constraints.maxHeight; - final bool isMobilePlatform = - defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS; - _stateProperties.renderingDetails.legendRenderer.legendPosition = - (widget.legend.position == LegendPosition.auto) - ? (height > width - ? isMobilePlatform - ? LegendPosition.top - : LegendPosition.bottom - : LegendPosition.right) - : widget.legend.position; - final LegendPosition position = - _stateProperties.renderingDetails.legendRenderer.legendPosition; - final double widthPadding = - position == LegendPosition.left || position == LegendPosition.right - ? 5 - : 0; - final double heightPadding = - position == LegendPosition.top || position == LegendPosition.bottom - ? 5 - : 0; - _stateProperties.renderingDetails.chartLegend.chartSize = - Size(width - widthPadding, height - heightPadding); - } - - /// To find the visible series - void _findVisibleSeries(BuildContext context) { - final List _seriesRenderers = - _stateProperties.seriesRenderers; - bool legendCheck = false; - _stateProperties.chartSeries.visibleSeriesRenderers = - []; - final List visibleSeriesRenderers = - _stateProperties.chartSeries.visibleSeriesRenderers; - for (int i = 0; i < _seriesRenderers.length; i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(_seriesRenderers[i]); - seriesRendererDetails.seriesName = seriesRendererDetails.series.name ?? - '${SfLocalizations.of(context).series} $i'; - final CartesianSeries cartesianSeries = - seriesRendererDetails.series; - seriesRendererDetails.markerSettingsRenderer = - MarkerSettingsRenderer(seriesRendererDetails.series.markerSettings); - Trendline trendline; - TrendlineRenderer trendlineRenderer; - if (cartesianSeries.trendlines != null) { - seriesRendererDetails.trendlineRenderer = []; - final List trendlineTypes = [0, 0, 0, 0, 0, 0]; - for (final Trendline trendline in cartesianSeries.trendlines!) { - trendlineRenderer = TrendlineRenderer(trendline); - seriesRendererDetails.trendlineRenderer.add(trendlineRenderer); - trendlineRenderer.name ??= - (trendline.type == TrendlineType.movingAverage - ? 'Moving average' - : trendline.type.toString().substring(14)) + - (' ${trendlineTypes[trendline.type.index]++}'); - } - for (final TrendlineRenderer trendlineRenderer - in seriesRendererDetails.trendlineRenderer) { - trendline = trendlineRenderer.trendline; - trendlineRenderer.name = trendlineRenderer.name![0].toUpperCase() + - trendlineRenderer.name!.substring(1); - if (trendlineTypes[trendline.type.index] == 1 && - trendlineRenderer.name![trendlineRenderer.name!.length - 1] == - '0') { - trendlineRenderer.name = trendlineRenderer.name! - .substring(0, trendlineRenderer.name!.length - 2); - } - } - } - if (_stateProperties.renderingDetails.initialRender! || - (_stateProperties.renderingDetails.widgetNeedUpdate && - !_stateProperties.legendToggling && - (_stateProperties.renderingDetails.oldDeviceOrientation == - MediaQuery.of(context).orientation))) { - if (seriesRendererDetails.oldSeries != null && - (_stateProperties - .renderingDetails.legendRenderer.legend!.isVisible! || - _stateProperties - .renderingDetails.legendToggleStates.isNotEmpty) && - (seriesRendererDetails.oldSeries!.isVisible == - seriesRendererDetails.series.isVisible)) { - legendCheck = true; - } else { - if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - _stateProperties.renderingDetails.legendToggleStates.clear(); - } - - seriesRendererDetails.visible = - _stateProperties.renderingDetails.initialRender! - ? seriesRendererDetails.series.isVisible - : seriesRendererDetails.visible ?? - seriesRendererDetails.series.isVisible; - } - } else { - legendCheck = true; - } - - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails(_seriesRenderers[0]); - final bool isFirstBarSeries = - seriesDetails.series.toString().contains('Bar') && - !seriesDetails.series.toString().contains('ErrorBar'); - final bool isMultipleBarSeries = - seriesRendererDetails.series.toString().contains('Bar') && - !seriesRendererDetails.series.toString().contains('ErrorBar'); - if (i == 0 || - (!isFirstBarSeries && !isMultipleBarSeries) || - (isFirstBarSeries && isMultipleBarSeries)) { - visibleSeriesRenderers.add(_seriesRenderers[i]); - if (!_stateProperties.renderingDetails.initialRender! && - _stateProperties.oldSeriesVisible.isNotEmpty && - i < visibleSeriesRenderers.length) { - if (i < visibleSeriesRenderers.length && - i < _stateProperties.oldSeriesVisible.length) { - _stateProperties.oldSeriesVisible[i] = - SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderers[i]) - .visible; - } - } - if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - final SeriesRendererDetails visibleSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - visibleSeriesRenderers[visibleSeriesRenderers.length - 1]); - for (final LegendRenderContext toggledLegendContext - in _stateProperties.renderingDetails.legendToggleStates) { - if (toggledLegendContext.seriesIndex == - visibleSeriesRendererDetails.seriesIndex && - toggledLegendContext.series == - visibleSeriesRendererDetails.oldSeries) { - toggledLegendContext.series = visibleSeriesRendererDetails.series; - toggledLegendContext.seriesRenderer = - visibleSeriesRendererDetails; - if (toggledLegendContext.isTrendline! && - visibleSeriesRendererDetails.series.trendlines != null && - visibleSeriesRendererDetails.oldSeries?.trendlines != null && - toggledLegendContext.trendline == - visibleSeriesRendererDetails.oldSeries - ?.trendlines![toggledLegendContext.trendlineIndex!]) { - toggledLegendContext.trendline = visibleSeriesRendererDetails - .series.trendlines![toggledLegendContext.trendlineIndex!]; - } - } - } - } - if (legendCheck) { - final int index = visibleSeriesRenderers.length - 1; - final SeriesRendererDetails visibleSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - visibleSeriesRenderers[index]); - final String? legendItemText = - visibleSeriesDetails.series.legendItemText; - final String? legendText = - _stateProperties.chart.legend.legendItemBuilder != null - ? visibleSeriesDetails.seriesName - : visibleSeriesDetails.series.isVisibleInLegend == true && - _stateProperties.renderingDetails.chartLegend - .legendCollections != - null && - _stateProperties.renderingDetails.chartLegend - .legendCollections!.isNotEmpty - ? _getLegendItemCollection(index)!.text - : null; - - final String? seriesName = visibleSeriesDetails.series.name; - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails(_stateProperties.chartSeries - .visibleSeriesRenderers[visibleSeriesRenderers.length - 1]); - seriesDetails.visible = _checkWithLegendToggleState( - visibleSeriesRenderers.length - 1, - SeriesHelper.getSeriesRendererDetails( - visibleSeriesRenderers[visibleSeriesRenderers.length - 1]) - .series, - legendText ?? legendItemText ?? seriesName ?? 'Series $index'); - } - // ignore: unnecessary_type_check - final CartesianSeriesRenderer? cSeriesRenderer = _stateProperties - .chartSeries - .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] - is CartesianSeriesRenderer - ? _stateProperties.chartSeries - .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] - : null; - final SeriesRendererDetails cSeriesDetails = - SeriesHelper.getSeriesRendererDetails(cSeriesRenderer!); - if (cSeriesDetails.series != null && - cSeriesDetails.series.trendlines != null) { - Trendline? trendline; - TrendlineRenderer trendlineRenderer; - for (int j = 0; j < cSeriesDetails.series.trendlines!.length; j++) { - trendline = cSeriesDetails.series.trendlines![j]; - trendlineRenderer = cSeriesDetails.trendlineRenderer[j]; - trendlineRenderer.visible = _checkWithTrendlineLegendToggleState( - visibleSeriesRenderers.length - 1, - cSeriesDetails.series, - j, - trendline, - trendlineRenderer.name!) && - cSeriesDetails.visible! == true; - } - _stateProperties.isTrendlineToggled = false; - } - } - legendCheck = false; - } - _stateProperties.chartSeries.visibleSeriesRenderers = - visibleSeriesRenderers; - - /// setting indicators visibility - if (_stateProperties.chart.indicators.isNotEmpty) { - TechnicalIndicatorsRenderer technicalIndicatorRenderer; - _stateProperties.technicalIndicatorRenderer.clear(); - for (int i = 0; i < _stateProperties.chart.indicators.length; i++) { - technicalIndicatorRenderer = TechnicalIndicatorsRenderer( - _stateProperties.chart.indicators[i], _stateProperties); - _stateProperties.technicalIndicatorRenderer - .add(technicalIndicatorRenderer); - technicalIndicatorRenderer.visible = - _stateProperties.renderingDetails.initialRender! - ? _stateProperties.chart.indicators[i].isVisible - : _checkIndicatorLegendToggleState( - visibleSeriesRenderers.length + i, - technicalIndicatorRenderer.visible ?? - _stateProperties.chart.indicators[i].isVisible); - } - } - _stateProperties.seriesRenderers = _seriesRenderers; - } - - /// This will return crosshair chart points - CartesianChartPoint _getCrosshairChartPoint( - CartesianStateProperties stateProperties) { - CartesianChartPoint crosshairChartPoint = - CartesianChartPoint(null, null); - final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - stateProperties.crosshairBehaviorRenderer; - final CrosshairRenderingDetails crosshairRenderingDetails = - CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); - if (stateProperties.renderingDetails.oldDeviceOrientation != - stateProperties.renderingDetails.deviceOrientation || - stateProperties.renderingDetails.didSizeChange) { - if (crosshairRenderingDetails.position != null && - stateProperties.chart.crosshairBehavior.enable) { - crosshairChartPoint = calculatePixelToPoint( - crosshairRenderingDetails.position!, - stateProperties.seriesRenderers.first); - } - } - return crosshairChartPoint; - } - - /// Here for orientation change/browser resize, the logic in this method will get executed - void _validateStateMaintenance(CartesianStateProperties stateProperties, - CartesianChartPoint point) { - final TrackballBehaviorRenderer trackballBehaviorRenderer = - stateProperties.trackballBehaviorRenderer; - final TrackballRenderingDetails trackballRenderingDetails = - TrackballHelper.getRenderingDetails(trackballBehaviorRenderer); - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - stateProperties.crosshairBehaviorRenderer; - final CrosshairRenderingDetails crosshairRenderingDetails = - CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); - late Offset crosshairOffset; - if (stateProperties.renderingDetails.oldDeviceOrientation != - stateProperties.renderingDetails.deviceOrientation || - stateProperties.renderingDetails.didSizeChange) { - if (trackballRenderingDetails.chartPointInfo.isNotEmpty && - stateProperties.chart.trackballBehavior.enable) { - stateProperties.isTrackballOrientationChanged = true; - trackballRenderingDetails.internalShowByIndex( - trackballRenderingDetails.chartPointInfo[0].dataPointIndex!); - } - if (crosshairRenderingDetails.position != null && - stateProperties.chart.crosshairBehavior.enable) { - stateProperties.isCrosshairOrientationChanged = true; - crosshairOffset = - calculatePointToPixel(point, stateProperties.seriesRenderers.first); - crosshairRenderingDetails.internalShow( - crosshairOffset.dx, crosshairOffset.dy, 'pixel'); - } - if (tooltipRenderingDetails.showLocation != null && - stateProperties.chart.tooltipBehavior.enable && - !stateProperties.isTooltipHidden && - !stateProperties.requireAxisTooltip && - tooltipRenderingDetails.currentSeriesDetails.seriesIndex != null && - tooltipRenderingDetails.pointIndex != null) { - stateProperties.isTooltipOrientationChanged = true; - tooltipRenderingDetails.internalShowByIndex( - tooltipRenderingDetails.currentSeriesDetails.seriesIndex, - tooltipRenderingDetails.pointIndex!); - } - } - } - - /// This method returns the legend render context of a particular series - /// since there is no necessity that the series index will match with the legend index - /// especially when the previous series is made invisible in legend - LegendRenderContext? _getLegendItemCollection(int index) { - for (final LegendRenderContext legendContext - in _stateProperties.renderingDetails.chartLegend.legendCollections!) { - if (legendContext.seriesIndex == index) { - return legendContext; - } - } - return null; - } - - /// To check the legend toggle state - bool _checkIndicatorLegendToggleState(int seriesIndex, bool seriesVisible) { - bool? seriesRender; - if (widget.legend.legendItemBuilder != null) { - final List legendToggles = - _stateProperties.renderingDetails.legendToggleTemplateStates; - if (legendToggles.isNotEmpty) { - for (int j = 0; j < legendToggles.length; j++) { - final MeasureWidgetContext item = legendToggles[j]; - if (seriesIndex == item.seriesIndex) { - seriesRender = false; - break; - } - } - } - } else { - if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; - j < _stateProperties.renderingDetails.legendToggleStates.length; - j++) { - final LegendRenderContext legendRenderContext = - _stateProperties.renderingDetails.legendToggleStates[j]; - if (seriesIndex == legendRenderContext.seriesIndex) { - seriesRender = false; - break; - } - } - } - } - return seriesRender ?? true; - } - - /// Check whether trendline enable with legend toggled state - bool _checkWithTrendlineLegendToggleState( - int seriesIndex, - CartesianSeries series, - int trendlineIndex, - Trendline trendline, - String text) { - bool? seriesRender; - if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; - j < _stateProperties.renderingDetails.legendToggleStates.length; - j++) { - final LegendRenderContext legendRenderContext = - _stateProperties.renderingDetails.legendToggleStates[j]; - if ((legendRenderContext.trendline == trendline && - legendRenderContext.seriesIndex == seriesIndex && - legendRenderContext.trendlineIndex == trendlineIndex) || - _stateProperties.isTrendlineToggled) { - seriesRender = false; - break; - } - } - } - return seriesRender ?? true; - } - - /// To toggle series visibility based on legend toggle states - bool _checkWithLegendToggleState( - int seriesIndex, ChartSeries series, String text) { - bool? seriesRender; - if (_stateProperties.chart.legend.legendItemBuilder != null) { - final List legendToggles = - _stateProperties.renderingDetails.legendToggleTemplateStates; - if (legendToggles.isNotEmpty) { - for (int j = 0; j < legendToggles.length; j++) { - final MeasureWidgetContext item = legendToggles[j]; - if (seriesIndex == item.seriesIndex) { - seriesRender = false; - break; - } - } - } - } else { - if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; - j < _stateProperties.renderingDetails.legendToggleStates.length; - j++) { - final LegendRenderContext legendRenderContext = - _stateProperties.renderingDetails.legendToggleStates[j]; - if (seriesIndex == legendRenderContext.seriesIndex && - !legendRenderContext.isTrendline! && - legendRenderContext.series == series) { - if (series is CartesianSeries) { - final CartesianSeries cSeries = series; - if (cSeries.trendlines != null) { - _stateProperties.isTrendlineToggled = true; - } - } - seriesRender = false; - break; - } - } - } - } - return seriesRender ?? true; - } -} - -/// Represents the container area -// ignore: must_be_immutable -class ContainerArea extends StatelessWidget { - /// Creates an instance for container area - // ignore: prefer_const_constructors_in_immutables - ContainerArea(this._stateProperties); - final CartesianStateProperties _stateProperties; - - /// Gets the chart from state properties - SfCartesianChart get chart => _stateProperties.chart; - RenderingDetails get _renderingDetails => _stateProperties.renderingDetails; - - /// Specifies the render box - late RenderBox renderBox; - Offset? _touchPosition; - Offset? _tapDownDetails; - Offset? _mousePointerDetails; - late CartesianSeries _series; - late XyDataSeriesRenderer _seriesRenderer; - Offset? _zoomStartPosition; - bool get _enableMouseHover => _stateProperties.enableMouseHover; - - /// Get trackball rendering Details - TrackballRenderingDetails get trackballRenderingDetails => - TrackballHelper.getRenderingDetails( - _stateProperties.trackballBehaviorRenderer); - - @override - Widget build(BuildContext context) { - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - - //this boolean prohibits both x and y scrolls for the parent widget - final bool isXYPanMode = (chart.crosshairBehavior.enable && - chart.crosshairBehavior.activationMode == - ActivationMode.singleTap) || - ((chart.zoomPanBehavior.enablePinching || - chart.zoomPanBehavior.enablePanning) && - chart.zoomPanBehavior.zoomMode == ZoomMode.xy); - - final bool requireInvertedAxis = _stateProperties.seriesRenderers.isNotEmpty - ? (chart.isTransposed ^ - getSeriesType(_stateProperties.seriesRenderers[0]) - .toLowerCase() - .contains('bar')) - : chart.isTransposed; - - //this boolean prohibits x scrolls for the parent widget - final bool isXPan = (chart.trackballBehavior.enable && - !requireInvertedAxis && - chart.trackballBehavior.activationMode == - ActivationMode.singleTap) || - chart.onPlotAreaSwipe != null || - (!requireInvertedAxis && chart.loadMoreIndicatorBuilder != null) || - (chart.zoomPanBehavior.enablePanning && - chart.zoomPanBehavior.zoomMode == ZoomMode.x); - - //this boolean prohibits y scrolls for the parent widget - final bool isYPan = (chart.trackballBehavior.enable && - requireInvertedAxis && - chart.trackballBehavior.activationMode == - ActivationMode.singleTap) || - (requireInvertedAxis && chart.loadMoreIndicatorBuilder != null) || - (chart.zoomPanBehavior.enablePanning && - chart.zoomPanBehavior.zoomMode == ZoomMode.y); - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Container( - decoration: const BoxDecoration(color: Colors.transparent), - - /// To get the mouse region of the chart - child: MouseRegion( - // 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) { - if (_stateProperties.chartState.mounted) { - _stateProperties.pointerDeviceKind = event.kind; - _performPointerDown(event); - ChartTouchInteractionArgs touchArgs; - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = - renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - }, - onPointerMove: (PointerMoveEvent event) { - if (_stateProperties.chartState.mounted) { - _performPointerMove(event); - ChartTouchInteractionArgs touchArgs; - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = - renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionMove!(touchArgs); - } - } - }, - onPointerUp: (PointerUpEvent event) { - if (_stateProperties.chartState.mounted) { - _stateProperties.isTouchUp = true; - _performPointerUp(event); - _stateProperties.isTouchUp = false; - ChartTouchInteractionArgs touchArgs; - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = - renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - } - }, - onPointerSignal: (PointerSignalEvent event) { - if (_stateProperties.chartState.mounted && - event is PointerScrollEvent) { - _performPointerSignal(event); - } - }, - child: GestureDetector( - onTapDown: (TapDownDetails details) { - if (_stateProperties.chartState.mounted) { - final Offset position = - renderBox.globalToLocal(details.globalPosition); - _touchPosition = position; - } - }, - onTap: () { - if (_stateProperties.chartState.mounted && - _stateProperties.chartSeries.visibleSeriesRenderers - .isNotEmpty && - _touchPosition != null && - chart.selectionGesture == - ActivationMode.singleTap && - zoomingBehaviorDetails.isPinching != true) { - final Offset position = _touchPosition!; - final CartesianSeriesRenderer - selectionseriesRenderer = _findSeries(position)!; - final SeriesRendererDetails selectionSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - selectionseriesRenderer); - if (selectionSeriesDetails.visible == true) { - final SelectionBehaviorRenderer? - selectionBehaviorRenderer = - selectionSeriesDetails - .selectionBehaviorRenderer; - final SelectionDetails? selectionDetails = - SelectionHelper.getRenderingDetails( - selectionBehaviorRenderer!); - if (selectionSeriesDetails.isSelectionEnable == - true && - selectionDetails?.selectionRenderer != null && - selectionSeriesDetails.isOuterRegion == false) { - selectionDetails?.selectionRenderer - ?.seriesRendererDetails = - selectionSeriesDetails; - selectionBehaviorRenderer.onTouchDown( - position.dx, position.dy); - } - } - } - }, - onTapUp: (TapUpDetails details) { - if (_stateProperties.chartState.mounted) { - final Offset position = - renderBox.globalToLocal(details.globalPosition); - final List - visibleSeriesRenderer = _stateProperties - .chartSeries.visibleSeriesRenderers; - final CartesianSeriesRenderer? - cartesianSeriesRenderer = _findSeries(position); - if (cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails( - cartesianSeriesRenderer) - .series - .onPointTap != - null) { - calculatePointSeriesIndex(chart, _stateProperties, - position, null, ActivationMode.singleTap); - } - if (chart.onAxisLabelTapped != null) { - _triggerAxisLabelEvent(position); - } - if (chart.onDataLabelTapped != null) { - triggerDataLabelEvent( - chart, visibleSeriesRenderer, position); - } - } - }, - onDoubleTap: () { - if (_stateProperties.chartState.mounted) { - _performDoubleTap(); - } - }, - onLongPressMoveUpdate: - (LongPressMoveUpdateDetails details) { - if (_stateProperties.chartState.mounted) { - _performLongPressMoveUpdate(details); - } - }, - onLongPress: () { - if (_stateProperties.chartState.mounted) { - _performLongPress(); - } - }, - onLongPressEnd: (LongPressEndDetails details) { - if (_stateProperties.chartState.mounted) { - _performLongPressEnd(); - } - }, - onPanDown: (DragDownDetails details) { - if (_stateProperties.chartState.mounted) { - _performPanDown(details); - } - }, - onVerticalDragUpdate: isXYPanMode || isYPan - ? (DragUpdateDetails details) { - _performPanUpdate(details); - } - : null, - onVerticalDragEnd: isXYPanMode || isYPan - ? (DragEndDetails details) { - _performPanEnd(details); - } - : null, - onHorizontalDragUpdate: isXYPanMode || isXPan - ? (DragUpdateDetails details) { - _performPanUpdate(details); - } - : null, - onHorizontalDragEnd: isXYPanMode || isXPan - ? (DragEndDetails details) { - _performPanEnd(details); - } - : null, - child: Container( - child: _initializeChart(constraints, context), - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: const BoxDecoration( - color: Colors.transparent)))))); - }); - } - - /// To initialize chart - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - // chart._stateProperties.tooltipBehaviorRenderer = TooltipBehaviorRenderer(chart.tooltipBehavior); - - _calculateContainerSize(constraints); - _calculateBounds(); - return Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context)); - } - - /// To get the size of a container - void _calculateContainerSize(BoxConstraints constraints) { - final double width = constraints.maxWidth; - final double height = constraints.maxHeight; - _stateProperties.renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width, height); - } - - /// Calculate container bounds - void _calculateBounds() { - _stateProperties.chartSeries.processData(); - _stateProperties.chartAxis.measureAxesBounds(); - _stateProperties.rangeChangeBySlider = false; - _stateProperties.rangeChangedByChart = false; - } - - /// To calculate the trendline region - void _calculateTrendlineRegion(CartesianStateProperties _stateProperties, - XyDataSeriesRenderer seriesRenderer) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.series.trendlines != null) { - TrendlineRenderer trendlineRenderer; - for (int i = 0; - i < seriesRendererDetails.series.trendlines!.length; - i++) { - trendlineRenderer = seriesRendererDetails.trendlineRenderer[i]; - if (trendlineRenderer.isNeedRender) { - trendlineRenderer.calculateTrendlinePoints( - seriesRendererDetails, _stateProperties); - } - } - } - } - - /// To render chart widgets - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _stateProperties.renderingDetails.chartWidgets = []; - _stateProperties.renderDatalabelRegions = []; - _bindAxisWidgets('outside'); - _bindPlotBandWidgets(true); - _bindSeriesWidgets(); - _bindPlotBandWidgets(false); - _bindDataLabelWidgets(); - _bindTrendlineWidget(); - _bindAxisWidgets('inside'); - _renderTemplates(); - _bindInteractionWidgets(constraints, context); - _bindLoadMoreIndicatorWidget(); - renderBox = context.findRenderObject()! as RenderBox; - _stateProperties.containerArea = this; - _stateProperties.legendRefresh = false; - // ignore: avoid_unnecessary_containers - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: _stateProperties.renderingDetails.chartWidgets!)); - } - - void _bindLoadMoreIndicatorWidget() { - _stateProperties.renderingDetails.chartWidgets!.add(StatefulBuilder( - builder: (BuildContext context, StateSetter stateSetter) { - Widget renderWidget; - _stateProperties.loadMoreViewStateSetter = stateSetter; - renderWidget = _stateProperties.isLoadMoreIndicator - ? Center( - child: _stateProperties.chart.loadMoreIndicatorBuilder!( - context, _stateProperties.swipeDirection)) - : renderWidget = Container(); - return renderWidget; - })); - } - - void _bindPlotBandWidgets(bool shouldRenderAboveSeries) { - _stateProperties.renderingDetails.chartWidgets!.add(RepaintBoundary( - child: CustomPaint( - painter: getPlotBandPainter( - stateProperties: _stateProperties, - shouldRenderAboveSeries: shouldRenderAboveSeries)))); - } - - void _bindTrendlineWidget() { - bool isTrendline = false; - final Map> trendlineAnimations = - >{}; - for (int i = 0; - i < _stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - _seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i] - as XyDataSeriesRenderer; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(_seriesRenderer); - _series = seriesRendererDetails.series; - // ignore: unnecessary_null_comparison - if (_seriesRenderer != null && - seriesRendererDetails.visible! == true && - _series.trendlines != null) { - isTrendline = true; - for (int j = 0; - j < seriesRendererDetails.trendlineRenderer.length; - j++) { - final TrendlineRenderer trendlineRenderer = - seriesRendererDetails.trendlineRenderer[j]; - final Trendline trendline = _series.trendlines![j]; - if (trendline.animationDuration > 0 && - (_renderingDetails.oldDeviceOrientation == null || - _stateProperties.renderingDetails.oldDeviceOrientation == - _stateProperties.renderingDetails.deviceOrientation) && - seriesRendererDetails.needsAnimation == true && - seriesRendererDetails.oldSeries == null) { - final int totalAnimationDuration = - trendline.animationDuration.toInt() + - trendline.animationDelay!.toInt(); - trendlineRenderer.animationController.duration = - Duration(milliseconds: totalAnimationDuration); - const double maxSeriesInterval = 0.8; - double minSeriesInterval = 0.1; - minSeriesInterval = trendline.animationDelay!.toInt() / - totalAnimationDuration * - (maxSeriesInterval - minSeriesInterval) + - minSeriesInterval; - trendlineAnimations['$i-$j'] = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: trendlineRenderer.animationController, - curve: Interval(minSeriesInterval, maxSeriesInterval, - curve: Curves.decelerate), - )); - trendlineRenderer.animationController.forward(from: 0.0); - } - } - } - } - if (isTrendline) { - _stateProperties.renderingDetails.chartWidgets!.add(RepaintBoundary( - child: CustomPaint( - painter: TrendlinePainter( - stateProperties: _stateProperties, - trendlineAnimations: trendlineAnimations, - notifier: _stateProperties.repaintNotifiers['trendline']!)), - )); - } - } - - /// To bind the widget for data label - void _bindDataLabelWidgets() { - _stateProperties.renderDataLabel = DataLabelRenderer( - stateProperties: _stateProperties, - show: _stateProperties.renderingDetails.animateCompleted); - _stateProperties.renderingDetails.chartWidgets! - .add(_stateProperties.renderDataLabel!); - } - - /// To render a template - void _renderTemplates() { - _stateProperties.annotationRegions = []; - _stateProperties.renderingDetails.templates = []; - _renderDataLabelTemplates(); - 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, _stateProperties); - final ChartTemplateInfo chartTemplateInfo = ChartTemplateInfo( - key: GlobalKey(), - animationDuration: 200, - widget: annotation.widget!, - templateType: 'Annotation', - needMeasure: true, - pointIndex: i, - verticalAlignment: annotation.verticalAlignment, - horizontalAlignment: annotation.horizontalAlignment, - clipRect: annotation.region == AnnotationRegion.chart - ? _stateProperties.renderingDetails.chartContainerRect - : _stateProperties.chartAxis.axisClipRect, - location: Offset(location.x.toDouble(), location.y.toDouble())); - _stateProperties.renderingDetails.templates.add(chartTemplateInfo); - } - } - - if (_renderingDetails.templates.isNotEmpty) { - final int templateLength = - _stateProperties.renderingDetails.templates.length; - for (int i = 0; - i < _stateProperties.renderingDetails.templates.length; - i++) { - final ChartTemplateInfo templateInfo = - _stateProperties.renderingDetails.templates[i]; - _stateProperties.renderingDetails.chartWidgets!.add(RenderTemplate( - template: templateInfo, - templateIndex: i, - templateLength: templateLength, - stateProperties: _stateProperties)); - } - } - } - - /// To render data label template - void _renderDataLabelTemplates() { - Widget? labelWidget; - CartesianChartPoint point; - _stateProperties.renderingDetails.dataLabelTemplateRegions = []; - for (int i = 0; - i < _stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final CartesianSeriesRenderer seriesRenderer = - _stateProperties.chartSeries.visibleSeriesRenderers[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - num padding; - if (series.dataLabelSettings.isVisible && - seriesRendererDetails.visible! == true) { - for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { - point = seriesRendererDetails.dataPoints[j]; - if (point.isVisible && - !point.isGap && - withInRange(seriesRendererDetails.dataPoints[j].xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!)) { - labelWidget = (series.dataLabelSettings.builder != null) - ? series.dataLabelSettings.builder!( - series.dataSource[point.overallDataPointIndex!], - point, - series, - point.overallDataPointIndex!, - i) - : null; - if (labelWidget != null) { - final String seriesType = seriesRendererDetails.seriesType; - final List dataLabelTemplateYValues = - (seriesType.contains('range') || - (seriesType.contains('hilo') && - !seriesType.contains('hiloopenclose'))) - ? [point.low as num, point.high as num] - : (seriesType.contains('candle') || - seriesType.contains('hiloopenclose')) - ? [ - point.low as num, - point.high as num, - point.open as num, - point.close as num - ] - : seriesType.contains('box') - ? [point.minimum!] - : [point.y as num]; - for (int k = 0; k < dataLabelTemplateYValues.length; k++) { - padding = (k == 0 && - dataLabelTemplateYValues.length > 1 && - !_stateProperties.requireInvertedAxis) - ? 20 - : 0; - final ChartLocation location = calculatePoint( - point.xValue, - dataLabelTemplateYValues[k], - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - _stateProperties.requireInvertedAxis, - series, - _stateProperties.chartAxis.axisClipRect); - final ChartTemplateInfo templateInfo = ChartTemplateInfo( - key: GlobalKey(), - templateType: 'DataLabel', - pointIndex: j, - seriesIndex: i, - needMeasure: true, - clipRect: _stateProperties.chartAxis.axisClipRect, - animationDuration: - (series.animationDuration + 1000.0).floor(), - widget: labelWidget, - location: Offset(location.x, location.y + padding)); - _stateProperties.renderingDetails.templates.add(templateInfo); - } - } - } - } - } - } - } - - /// To bind a series of widgets for all series - void _bindSeriesWidgets() { - _stateProperties.painterKeys = []; - _stateProperties.animationCompleteCount = 0; - _stateProperties.renderingDetails.animateCompleted = false; - for (int i = 0; - i < _stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - _seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i] - as XyDataSeriesRenderer; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(_seriesRenderer); - seriesRendererDetails.animationCompleted = false; - _series = seriesRendererDetails.series; - final String _seriesType = seriesRendererDetails.seriesType; - if (seriesRendererDetails.isIndicator == true) { - seriesRendererDetails.repaintNotifier = ValueNotifier(0); - // ignore: unnecessary_type_check - if (_seriesRenderer is XyDataSeriesRenderer) { - seriesRendererDetails.animationController = - AnimationController(vsync: _stateProperties.chartState) - ..addListener(seriesRendererDetails.repaintSeriesElement); - _stateProperties - .controllerList[seriesRendererDetails.animationController] = - seriesRendererDetails.repaintSeriesElement; - seriesRendererDetails.animationController - .addStatusListener(seriesRendererDetails.animationStatusListener); - } - } - // ignore: unnecessary_null_comparison - if (_seriesRenderer != null && seriesRendererDetails.visible! == true) { - _calculateTrendlineRegion(_stateProperties, _seriesRenderer); - seriesRendererDetails.selectionBehavior = _series.selectionBehavior; - final dynamic selectionBehavior = - seriesRendererDetails.selectionBehavior; - seriesRendererDetails.selectionBehaviorRenderer = - SelectionBehaviorRenderer( - selectionBehavior, chart, _stateProperties); - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.setSelectionBehaviorRenderer( - _series.selectionBehavior, selectionBehaviorRenderer!); - // ignore: unnecessary_null_comparison - if (selectionBehaviorRenderer != null) { - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - selectionDetails.selectionRenderer ??= SelectionRenderer(); - selectionDetails.selectionRenderer!.chart = chart; - selectionDetails.selectionRenderer!.stateProperties = - _stateProperties; - selectionDetails.selectionRenderer!.seriesRendererDetails = - seriesRendererDetails; - _series = seriesRendererDetails.series; - if (selectionBehavior.selectionController != null) { - selectionDetails.selectRange(); - } - selectionDetails.selectionRenderer!.selectedSegments = - _stateProperties.selectedSegments; - selectionDetails.selectionRenderer!.unselectedSegments = - _stateProperties.unselectedSegments; - //To determine whether initialSelectedDataIndexes collection is updated dynamically - bool isSelecetedIndexesUpdated = false; - if (_series.initialSelectedDataIndexes != null && - _series.initialSelectedDataIndexes!.isNotEmpty && - seriesRendererDetails.oldSelectedIndexes != null && - seriesRendererDetails.oldSelectedIndexes!.isNotEmpty == true && - seriesRendererDetails.oldSelectedIndexes!.length == - _series.initialSelectedDataIndexes!.length) { - for (final int index in _series.initialSelectedDataIndexes!) { - isSelecetedIndexesUpdated = - seriesRendererDetails.oldSelectedIndexes!.contains(index) == - false; - if (isSelecetedIndexesUpdated) { - break; - } - } - } else { - isSelecetedIndexesUpdated = - _series.initialSelectedDataIndexes!.isNotEmpty; - } - int totalSelectedSegment = 0; - int? selectedSeriesIndex; - if (selectionBehavior.enable == true && - _stateProperties.selectedSegments.isNotEmpty && - _stateProperties.unselectedSegments.isNotEmpty) { - for (int j = 0; j < _stateProperties.selectedSegments.length; j++) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties( - _stateProperties.selectedSegments[j]); - if (segmentProperties.seriesIndex == i) { - totalSelectedSegment += 1; - selectedSeriesIndex = segmentProperties.seriesIndex; - } - } - for (int k = 0; - k < _stateProperties.unselectedSegments.length; - k++) { - if (SegmentHelper.getSegmentProperties( - _stateProperties.unselectedSegments[k]) - .seriesIndex == - i) { - totalSelectedSegment += 1; - } - } - } - if (_stateProperties.isRangeSelectionSlider == false && - selectionBehavior.enable == true && - (isSelecetedIndexesUpdated || - (!_renderingDetails.initialRender! && - (totalSelectedSegment != 0 && - (totalSelectedSegment < - SeriesHelper.getSeriesRendererDetails( - _stateProperties.seriesRenderers[i]) - .dataPoints - .length))))) { - int segmentLength = seriesRendererDetails.dataPoints.length; - - if (isLineTypeSeries(seriesRendererDetails.seriesType) || - seriesRendererDetails.seriesType.contains('boxandwhisker') == - true) { - segmentLength = seriesRendererDetails.dataPoints.length - 1; - } - - for (int j = 0; j < segmentLength; j++) { - final ChartSegment segment = ColumnSegment(); - SegmentHelper.setSegmentProperties( - segment, SegmentProperties(_stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segment.currentSegmentIndex = j; - segmentProperties.seriesIndex = i; - segmentProperties.currentPoint = - seriesRendererDetails.dataPoints[j]; - ((_series.initialSelectedDataIndexes! - .contains(segment.currentSegmentIndex) && - isSelecetedIndexesUpdated) || - chart.selectionType == SelectionType.series && - selectedSeriesIndex == i) - ? SelectionHelper.getRenderingDetails( - selectionBehaviorRenderer) - .selectionRenderer! - .selectedSegments - .add(segment) - : SelectionHelper.getRenderingDetails( - selectionBehaviorRenderer) - .selectionRenderer! - .unselectedSegments! - .add(segment); - } - seriesRendererDetails.oldSelectedIndexes = [] - //ignore: prefer_spread_collections - ..addAll(_series.initialSelectedDataIndexes!); - } - } - // ignore: unnecessary_null_comparison - if (seriesRendererDetails.animationController != null && - _series.animationDuration > 0 && - !_renderingDetails.didSizeChange && - (_renderingDetails.oldDeviceOrientation == null || - _stateProperties.legendRefresh || - _stateProperties.renderingDetails.oldDeviceOrientation == - _stateProperties.renderingDetails.deviceOrientation) && - (_renderingDetails.initialRender! || - _stateProperties.legendRefresh || - ((_seriesType == 'column' || _seriesType == 'bar') && - _stateProperties.legendToggling) || - (!_stateProperties.legendToggling && - seriesRendererDetails.needsAnimation == true && - _stateProperties.renderingDetails.widgetNeedUpdate))) { - if ((_seriesType == 'column' || _seriesType == 'bar') && - _stateProperties.legendToggling) { - seriesRendererDetails.needAnimateSeriesElements = true; - } - _stateProperties.forwardAnimation(seriesRendererDetails); - } else { - seriesRendererDetails.animationController.duration = Duration.zero; - seriesRendererDetails.seriesAnimation = - Tween(begin: 1, end: 1.0).animate(CurvedAnimation( - parent: seriesRendererDetails.animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), - )); - seriesRendererDetails.seriesElementAnimation = - Tween(begin: 1, end: 1.0).animate(CurvedAnimation( - parent: seriesRendererDetails.animationController, - curve: const Interval(1.0, 1.0, curve: Curves.decelerate), - )); - _stateProperties.animationCompleteCount++; - seriesRendererDetails.animationCompleted = true; - setAnimationStatus(_stateProperties); - } - } - // ignore: avoid_unnecessary_containers - _stateProperties.renderingDetails.chartWidgets!.add(Container( - child: RepaintBoundary( - child: CustomPaint( - painter: _getSeriesPainter( - i, seriesRendererDetails.animationController, _seriesRenderer), - )))); - } - _stateProperties.renderingDetails.chartWidgets!.add(Container( - color: Colors.red, - child: RepaintBoundary( - child: CustomPaint( - painter: ZoomRectPainter( - isRepaint: true, - stateProperties: _stateProperties, - notifier: _stateProperties.repaintNotifiers['zoom']))))); - _stateProperties.legendToggling = false; - } - - /// Bind the axis widgets - void _bindAxisWidgets(String renderType) { - // ignore: unnecessary_null_comparison - if (_stateProperties.chartAxis.axisRenderersCollection != null && - _stateProperties.chartAxis.axisRenderersCollection.isNotEmpty && - _stateProperties.chartAxis.axisRenderersCollection.length > 1) { - final CartesianAxisWidget axisWidget = CartesianAxisWidget( - stateProperties: _stateProperties, renderType: renderType); - renderType == 'outside' - ? _stateProperties.renderOutsideAxis = axisWidget - : _stateProperties.renderInsideAxis = axisWidget; - _stateProperties.renderingDetails.chartWidgets!.add(axisWidget); - } - } - - /// To find a series on selection event - CartesianSeriesRenderer? _findSeries(Offset position) { - CartesianSeriesRenderer? seriesRenderer; - SelectionBehaviorRenderer? selectionBehaviorRenderer; - outerLoop: - for (int i = _stateProperties.chartSeries.visibleSeriesRenderers.length - 1; - i >= 0; - i--) { - seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final String _seriesType = seriesRendererDetails.seriesType; - if (_seriesType == 'column' || - _seriesType == 'bar' || - _seriesType == 'scatter' || - _seriesType == 'bubble' || - _seriesType == 'fastline' || - _seriesType.contains('area') || - _seriesType.contains('stackedcolumn') || - _seriesType.contains('stackedbar') || - _seriesType.contains('range') || - _seriesType == 'histogram' || - _seriesType == 'waterfall' || - _seriesType == 'errorbar') { - for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { - if (seriesRendererDetails.dataPoints[j].region != null && - seriesRendererDetails.dataPoints[j].region!.contains(position) == - true) { - seriesRendererDetails.isOuterRegion = false; - break outerLoop; - } else { - seriesRendererDetails.isOuterRegion = true; - } - } - } else { - selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - bool isSelect = false; - seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i]; - for (int k = - _stateProperties.chartSeries.visibleSeriesRenderers.length - 1; - k >= 0; - k--) { - isSelect = seriesRendererDetails.isSelectionEnable == true && - seriesRendererDetails.visible != null && - seriesRendererDetails.visible! == true && - seriesRendererDetails.visibleDataPoints != null && - seriesRendererDetails.visibleDataPoints!.isNotEmpty && - (SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer! - .isSeriesContainsPoint( - SeriesHelper.getSeriesRendererDetails(_stateProperties - .chartSeries.visibleSeriesRenderers[i]), - position) == - true); - if (isSelect) { - return _stateProperties.chartSeries.visibleSeriesRenderers[i]; - } else if (seriesRendererDetails.visible != null && - seriesRendererDetails.visible! == true && - seriesRendererDetails.visibleDataPoints != null && - seriesRendererDetails.visibleDataPoints!.isNotEmpty && - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer! - .isSeriesContainsPoint( - SeriesHelper.getSeriesRendererDetails(_stateProperties - .chartSeries.visibleSeriesRenderers[i]), - position) == - true) { - return _stateProperties.chartSeries.visibleSeriesRenderers[i]; - } - } - } - } - return seriesRenderer; - } - - /// To perform the pointer down event - void _performPointerDown(PointerDownEvent event) { - _stateProperties.canSetRangeController = true; - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - _tapDownDetails = event.position; - if (chart.zoomPanBehavior.enablePinching == true) { - ZoomPanArgs? zoomStartArgs; - if (_stateProperties.touchStartPositions.length < 2) { - _stateProperties.touchStartPositions.add(event); - } - if (_stateProperties.touchStartPositions.length == 2) { - for (int axisIndex = 0; - axisIndex < - _stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(_stateProperties - .chartAxis.axisRenderersCollection[axisIndex]); - - if (chart.onZoomStart != null) { - zoomStartArgs = - bindZoomEvent(chart, axisDetails, chart.onZoomStart!); - axisDetails.zoomFactor = zoomStartArgs.currentZoomFactor; - axisDetails.zoomPosition = zoomStartArgs.currentZoomPosition; - } - _stateProperties.zoomPanBehaviorRenderer.onPinchStart( - axisDetails.axis, - _stateProperties.touchStartPositions[0].position.dx, - _stateProperties.touchStartPositions[0].position.dy, - _stateProperties.touchStartPositions[1].position.dx, - _stateProperties.touchStartPositions[1].position.dy, - axisDetails.zoomFactor); - } - } - } - final Offset position = renderBox.globalToLocal(event.position); - _touchPosition = position; - - final CartesianSeriesRenderer? cartesianSeriesRenderer = - _findSeries(position); - // ignore: unnecessary_null_comparison - if (chart.trackballBehavior != null && - chart.trackballBehavior.enable && - chart.trackballBehavior.activationMode == ActivationMode.singleTap && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series - is! ErrorBarSeries) { - if (chart.trackballBehavior.builder != null) { - trackballRenderingDetails.isMoving = true; - trackballRenderingDetails.showTemplateTrackball(position); - } else { - _stateProperties.trackballBehaviorRenderer - .onTouchDown(position.dx, position.dy); - } - } - // ignore: unnecessary_null_comparison - if (chart.crosshairBehavior != null && - chart.crosshairBehavior.enable && - chart.crosshairBehavior.activationMode == ActivationMode.singleTap && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series - is! ErrorBarSeries) { - _stateProperties.crosshairBehaviorRenderer - .onTouchDown(position.dx, position.dy); - } - } - - /// To perform the pointer move event - void _performPointerMove(PointerMoveEvent event) { - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (chart.zoomPanBehavior.enablePinching == true && - _stateProperties.touchStartPositions.length == 2) { - zoomingBehaviorDetails.isPinching = true; - final int pointerID = event.pointer; - bool addPointer = true; - for (int i = 0; i < _stateProperties.touchMovePositions.length; i++) { - if (_stateProperties.touchMovePositions[i].pointer == pointerID) { - addPointer = false; - } - } - if (_stateProperties.touchMovePositions.length < 2 && addPointer) { - _stateProperties.touchMovePositions.add(event); - } - - if (_stateProperties.touchMovePositions.length == 2 && - _stateProperties.touchStartPositions.length == 2) { - if (_stateProperties.touchMovePositions[0].pointer == event.pointer) { - _stateProperties.touchMovePositions[0] = event; - } - if (_stateProperties.touchMovePositions[1].pointer == event.pointer) { - _stateProperties.touchMovePositions[1] = event; - } - zoomingBehaviorDetails.performPinchZooming( - _stateProperties.touchStartPositions, - _stateProperties.touchMovePositions); - } - } - } - - /// To perform the pointer up event - void _performPointerUp(PointerUpEvent event) { - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (_stateProperties.touchStartPositions.length == 2 && - _stateProperties.touchMovePositions.length == 2 && - zoomingBehaviorDetails.isPinching == true) { - _calculatePinchZoomingArgs(); - } - - _stateProperties.touchStartPositions = []; - _stateProperties.touchMovePositions = []; - zoomingBehaviorDetails.isPinching = false; - zoomingBehaviorDetails.delayRedraw = false; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isHovering = false; - final Offset position = renderBox.globalToLocal(event.position); - final CartesianSeriesRenderer? cartesianSeriesRenderer = - _findSeries(position); - // ignore: unnecessary_null_comparison - if ((chart.trackballBehavior != null && - chart.trackballBehavior.enable && - !chart.trackballBehavior.shouldAlwaysShow && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) - .series is! ErrorBarSeries && - chart.trackballBehavior.activationMode != - ActivationMode.doubleTap && - zoomingBehaviorDetails.isPinching != true) || - // ignore: unnecessary_null_comparison - (chart.zoomPanBehavior != null && - ((chart.zoomPanBehavior.enableDoubleTapZooming || - chart.zoomPanBehavior.enablePanning || - chart.zoomPanBehavior.enablePinching || - chart.zoomPanBehavior.enableSelectionZooming) && - !chart.trackballBehavior.shouldAlwaysShow))) { - _stateProperties.trackballBehaviorRenderer - .onTouchUp(position.dx, position.dy); - - trackballRenderingDetails.isLongPressActivated = false; - } - // ignore: unnecessary_null_comparison - if ((chart.crosshairBehavior != null && - chart.crosshairBehavior.enable && - !chart.crosshairBehavior.shouldAlwaysShow && - chart.crosshairBehavior.activationMode != - ActivationMode.doubleTap && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) - .series is! ErrorBarSeries && - zoomingBehaviorDetails.isPinching != true) || - // ignore: unnecessary_null_comparison - (chart.zoomPanBehavior != null && - ((chart.zoomPanBehavior.enableDoubleTapZooming || - chart.zoomPanBehavior.enablePanning || - chart.zoomPanBehavior.enablePinching || - chart.zoomPanBehavior.enableSelectionZooming) && - !chart.crosshairBehavior.shouldAlwaysShow))) { - _stateProperties.crosshairBehaviorRenderer - .onTouchUp(position.dx, position.dy); - CrosshairHelper.getRenderingDetails( - _stateProperties.crosshairBehaviorRenderer) - .isLongPressActivated = false; - } - if (chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap || - shouldShowAxisTooltip(_stateProperties)) { - tooltipRenderingDetails.isInteraction = true; - chart.tooltipBehavior.builder != null - ? tooltipRenderingDetails.showTemplateTooltip(position) - : _stateProperties.renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx, position.dy); - } - } - - /// To perform the pointer signal event - void _performPointerSignal(PointerScrollEvent event) { - _mousePointerDetails = event.position; - if (_mousePointerDetails != null) { - final Offset position = renderBox.globalToLocal(event.position); - if (chart.zoomPanBehavior.enableMouseWheelZooming && - _stateProperties.chartAxis.axisClipRect.contains(position)) { - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer) - .performMouseWheelZooming(event, position.dx, position.dy); - } - } - } - - /// To calculate the arguments of pinch zooming event - void _calculatePinchZoomingArgs() { - ZoomPanArgs? zoomEndArgs; - bool resetFlag = false; - int axisIndex; - for (axisIndex = 0; - axisIndex < _stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - if (chart.onZoomEnd != null) { - zoomEndArgs = bindZoomEvent(chart, axisDetails, chart.onZoomEnd!); - axisDetails.zoomFactor = zoomEndArgs.currentZoomFactor; - axisDetails.zoomPosition = zoomEndArgs.currentZoomPosition; - } - if (axisDetails.zoomFactor.toInt() == 1 && - axisDetails.zoomPosition.toInt() == 0 && - chart.onZoomReset != null) { - resetFlag = true; - } - _stateProperties.zoomAxes = []; - _stateProperties.zoomPanBehaviorRenderer.onPinchEnd( - axisDetails.axis, - _stateProperties.touchMovePositions[0].position.dx, - _stateProperties.touchMovePositions[0].position.dy, - _stateProperties.touchMovePositions[1].position.dx, - _stateProperties.touchMovePositions[1].position.dy, - axisDetails.zoomFactor); - } - if (resetFlag) { - for (int index = 0; - index < _stateProperties.chartAxis.axisRenderersCollection.length; - index++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[index]); - bindZoomEvent(chart, axisDetails, chart.onZoomReset!); - } - } - } - - /// To perform long press move update - void _performLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - final Offset? position = renderBox.globalToLocal(details.globalPosition); - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (zoomingBehaviorDetails.isPinching != true) { - if (chart.zoomPanBehavior.enableSelectionZooming && - position != null && - _zoomStartPosition != null) { - zoomingBehaviorDetails.canPerformSelection = true; - _stateProperties.zoomPanBehaviorRenderer.onDrawSelectionZoomRect( - position.dx, - position.dy, - _zoomStartPosition!.dx, - _zoomStartPosition!.dy); - } - } - // ignore: unnecessary_null_comparison - if (chart.trackballBehavior != null && - chart.trackballBehavior.enable && - // ignore: unnecessary_null_comparison - _renderingDetails != null && - chart.trackballBehavior.activationMode != ActivationMode.doubleTap && - position != null && - _findSeries(position) != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series - is! ErrorBarSeries) { - if (chart.trackballBehavior.activationMode == ActivationMode.singleTap) { - chart.trackballBehavior.builder != null - ? trackballRenderingDetails.showTemplateTrackball(position) - : _stateProperties.trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } - if (chart.trackballBehavior.activationMode == ActivationMode.longPress && - trackballRenderingDetails.isLongPressActivated == true) { - chart.trackballBehavior.builder != null - ? trackballRenderingDetails.showTemplateTrackball(position) - : _stateProperties.trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } - } - // ignore: unnecessary_null_comparison - if (chart.crosshairBehavior != null && - chart.crosshairBehavior.enable && - chart.crosshairBehavior.activationMode != ActivationMode.doubleTap && - position != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series - is! ErrorBarSeries) { - if (chart.crosshairBehavior.activationMode == ActivationMode.singleTap) { - _stateProperties.crosshairBehaviorRenderer - .onTouchMove(position.dx, position.dy); - // ignore: unnecessary_null_comparison - } else if ((chart.crosshairBehavior != null && - chart.crosshairBehavior.activationMode == - ActivationMode.longPress && - CrosshairHelper.getRenderingDetails( - _stateProperties.crosshairBehaviorRenderer) - .isLongPressActivated == - true) && - !chart.zoomPanBehavior.enableSelectionZooming) { - _stateProperties.crosshairBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } - } - } - - /// To perform long press end - void _performLongPressEnd() { - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (zoomingBehaviorDetails.isPinching != true) { - zoomingBehaviorDetails.canPerformSelection = false; - if (chart.zoomPanBehavior.enableSelectionZooming && - zoomingBehaviorDetails.zoomingRect.width != 0) { - zoomingBehaviorDetails - .doSelectionZooming(zoomingBehaviorDetails.zoomingRect); - if (zoomingBehaviorDetails.canPerformSelection != true) { - zoomingBehaviorDetails.zoomingRect = Rect.zero; - } - } - } - } - - /// To perform pan down - void _performPanDown(DragDownDetails details) { - _stateProperties.startOffset = - renderBox.globalToLocal(details.globalPosition); - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (zoomingBehaviorDetails.isPinching != true) { - _zoomStartPosition = renderBox.globalToLocal(details.globalPosition); - if (chart.zoomPanBehavior.enablePanning == true) { - zoomingBehaviorDetails.isPanning = true; - zoomingBehaviorDetails.previousMovedPosition = null; - } - } - } - - /// To perform long press on chart - void _performLongPress() { - Offset? position; - if (_tapDownDetails != null) { - position = renderBox.globalToLocal(_tapDownDetails!); - final CartesianSeriesRenderer? cartesianSeriesRenderer = - _findSeries(position); - if (cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) - .series - .onPointLongPress != - null) { - calculatePointSeriesIndex( - chart, _stateProperties, position, null, ActivationMode.longPress); - } - if (chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == - ActivationMode.longPress || - shouldShowAxisTooltip(_stateProperties)) { - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isInteraction = true; - chart.tooltipBehavior.builder != null - ? tooltipRenderingDetails.showTemplateTooltip(position) - : _stateProperties.renderingDetails.tooltipBehaviorRenderer - .onLongPress(position.dx, position.dy); - } - } - // ignore: unnecessary_null_comparison - if (_stateProperties.chartSeries.visibleSeriesRenderers != null && - position != null && - chart.selectionGesture == ActivationMode.longPress) { - final CartesianSeriesRenderer selectionSeriesRenderer = - _findSeries(position)!; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(selectionSeriesRenderer); - final SelectionBehaviorRenderer selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer!; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer) - .selectionRenderer! - .seriesRendererDetails = seriesRendererDetails; - selectionBehaviorRenderer.onLongPress(position.dx, position.dy); - } - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - // ignore: unnecessary_null_comparison - if ((chart.trackballBehavior != null && - chart.trackballBehavior.enable == true && - chart.trackballBehavior.activationMode == - ActivationMode.longPress) && - SeriesHelper.getSeriesRendererDetails(_findSeries(position!)!).series - is! ErrorBarSeries && - // ignore: unnecessary_null_comparison - position != null && - zoomingBehaviorDetails.isPinching != true) { - trackballRenderingDetails.isLongPressActivated = true; - chart.trackballBehavior.builder != null - ? trackballRenderingDetails.showTemplateTrackball(position) - : _stateProperties.trackballBehaviorRenderer - .onTouchDown(position.dx, position.dy); - } - // ignore: unnecessary_null_comparison - if ((chart.crosshairBehavior != null && - chart.crosshairBehavior.enable == true && - chart.crosshairBehavior.activationMode == - ActivationMode.longPress) && - SeriesHelper.getSeriesRendererDetails(_findSeries(position!)!).series - is! ErrorBarSeries && - !chart.zoomPanBehavior.enableSelectionZooming && - zoomingBehaviorDetails.isPinching != true && - // ignore: unnecessary_null_comparison - position != null) { - CrosshairHelper.getRenderingDetails( - _stateProperties.crosshairBehaviorRenderer) - .isLongPressActivated = true; - _stateProperties.crosshairBehaviorRenderer - .onTouchDown(position.dx, position.dy); - } - } - - /// Method for double tap - void _performDoubleTap() { - if (_tapDownDetails != null) { - final Offset position = renderBox.globalToLocal(_tapDownDetails!); - final CartesianSeriesRenderer? cartesianSeriesRenderer = - _findSeries(position); - if (cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) - .series - .onPointDoubleTap != - null) { - calculatePointSeriesIndex( - chart, _stateProperties, position, null, ActivationMode.doubleTap); - } - // ignore: unnecessary_null_comparison - if (chart.trackballBehavior != null && - chart.trackballBehavior.enable && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series - is! ErrorBarSeries && - chart.trackballBehavior.activationMode == ActivationMode.doubleTap) { - chart.trackballBehavior.builder != null - ? trackballRenderingDetails.showTemplateTrackball(position) - : _stateProperties.trackballBehaviorRenderer - .onDoubleTap(position.dx, position.dy); - _stateProperties.enableDoubleTap = true; - _stateProperties.isTouchUp = true; - _stateProperties.trackballBehaviorRenderer - .onTouchUp(position.dx, position.dy); - _stateProperties.isTouchUp = false; - _stateProperties.enableDoubleTap = false; - } - // ignore: unnecessary_null_comparison - if (chart.crosshairBehavior != null && - chart.crosshairBehavior.enable && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series - is! ErrorBarSeries && - chart.crosshairBehavior.activationMode == ActivationMode.doubleTap) { - _stateProperties.crosshairBehaviorRenderer - .onDoubleTap(position.dx, position.dy); - _stateProperties.enableDoubleTap = true; - _stateProperties.isTouchUp = true; - _stateProperties.crosshairBehaviorRenderer - .onTouchUp(position.dx, position.dy); - _stateProperties.isTouchUp = false; - _stateProperties.enableDoubleTap = false; - } - - if (chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == - ActivationMode.doubleTap || - shouldShowAxisTooltip(_stateProperties)) { - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isInteraction = true; - chart.tooltipBehavior.builder != null - ? tooltipRenderingDetails.showTemplateTooltip(position) - : _stateProperties.renderingDetails.tooltipBehaviorRenderer - .onDoubleTap(position.dx, position.dy); - } - // ignore: unnecessary_null_comparison - if (_stateProperties.chartSeries.visibleSeriesRenderers != null && - chart.selectionGesture == ActivationMode.doubleTap) { - final CartesianSeriesRenderer selectionSeriesRenderer = - _findSeries(position)!; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(selectionSeriesRenderer); - final SelectionBehaviorRenderer selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer!; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer) - .selectionRenderer! - .seriesRendererDetails = seriesRendererDetails; - selectionBehaviorRenderer.onDoubleTap(position.dx, position.dy); - } - } - - if (chart.zoomPanBehavior.enableDoubleTapZooming == true) { - final Offset? doubleTapPosition = _touchPosition; - final Offset? position = doubleTapPosition; - if (position != null) { - _stateProperties.zoomPanBehaviorRenderer.onDoubleTap( - position.dx, - position.dy, - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer) - .zoomFactor); - } - } - } - - /// Update the details for pan - void _performPanUpdate(DragUpdateDetails details) { - if (_stateProperties.chartState.mounted) { - Offset? position; - _stateProperties.currentPosition = - renderBox.globalToLocal(details.globalPosition); - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (zoomingBehaviorDetails.isPinching != true) { - position = renderBox.globalToLocal(details.globalPosition); - if (zoomingBehaviorDetails.isPanning == true && - chart.zoomPanBehavior.enablePanning && - zoomingBehaviorDetails.previousMovedPosition != null && - !_stateProperties.isLoadMoreIndicator) { - _stateProperties.zoomPanBehaviorRenderer - .onPan(position.dx, position.dy); - } - zoomingBehaviorDetails.previousMovedPosition = position; - } - final bool panInProgress = chart.zoomPanBehavior.enablePanning && - zoomingBehaviorDetails.previousMovedPosition != null; - // ignore: unnecessary_null_comparison - if (chart.trackballBehavior != null && - chart.trackballBehavior.enable && - position != null && - _findSeries(position) != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series - is! ErrorBarSeries && - !panInProgress && - chart.trackballBehavior.activationMode != ActivationMode.doubleTap) { - if (chart.trackballBehavior.activationMode == - ActivationMode.singleTap) { - if (chart.trackballBehavior.builder != null) { - trackballRenderingDetails.isMoving = true; - trackballRenderingDetails.showTemplateTrackball(position); - } else { - _stateProperties.trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } - // ignore: unnecessary_null_comparison - } else if (chart.trackballBehavior != null && - chart.trackballBehavior.activationMode == - ActivationMode.longPress && - // ignore: unnecessary_null_comparison - position != null && - _findSeries(position) != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series - is! ErrorBarSeries && - trackballRenderingDetails.isLongPressActivated == true) { - chart.trackballBehavior.builder != null - ? trackballRenderingDetails.showTemplateTrackball(position) - : _stateProperties.trackballBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } - } - // ignore: unnecessary_null_comparison - if (chart.crosshairBehavior != null && - chart.crosshairBehavior.enable && - chart.crosshairBehavior.activationMode != ActivationMode.doubleTap && - position != null && - _findSeries(position) != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series - is! ErrorBarSeries && - !panInProgress) { - if (chart.crosshairBehavior.activationMode == - ActivationMode.singleTap) { - _stateProperties.crosshairBehaviorRenderer - .onTouchMove(position.dx, position.dy); - // ignore: unnecessary_null_comparison - } else if (chart.crosshairBehavior != null && - chart.crosshairBehavior.activationMode == - ActivationMode.longPress && - CrosshairHelper.getRenderingDetails( - _stateProperties.crosshairBehaviorRenderer) - .isLongPressActivated == - true) { - _stateProperties.crosshairBehaviorRenderer - .onTouchMove(position.dx, position.dy); - } - } - } - } - - /// Method for the pan end event - void _performPanEnd(DragEndDetails details) { - if (_stateProperties.chartState.mounted) { - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - if (zoomingBehaviorDetails.isPinching != true) { - zoomingBehaviorDetails.isPanning = false; - zoomingBehaviorDetails.previousMovedPosition = null; - } - if (chart.trackballBehavior.enable && - !chart.trackballBehavior.shouldAlwaysShow && - chart.trackballBehavior.activationMode != ActivationMode.doubleTap && - _touchPosition != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(_touchPosition!)!) - .series is! ErrorBarSeries) { - _stateProperties.isTouchUp = true; - _stateProperties.trackballBehaviorRenderer - .onTouchUp(_touchPosition!.dx, _touchPosition!.dy); - _stateProperties.isTouchUp = false; - trackballRenderingDetails.isLongPressActivated = false; - } - if (chart.crosshairBehavior.enable && - !chart.crosshairBehavior.shouldAlwaysShow && - _touchPosition != null && - SeriesHelper.getSeriesRendererDetails(_findSeries(_touchPosition!)!) - .series is! ErrorBarSeries && - chart.crosshairBehavior.activationMode != ActivationMode.doubleTap) { - _stateProperties.isTouchUp = true; - _stateProperties.crosshairBehaviorRenderer - .onTouchUp(_touchPosition!.dx, _touchPosition!.dy); - _stateProperties.isTouchUp = false; - CrosshairHelper.getRenderingDetails( - _stateProperties.crosshairBehaviorRenderer) - .isLongPressActivated = false; - } - - /// Pagination/Swiping feature - if (chart.onPlotAreaSwipe != null && - _stateProperties.zoomedState != true && - _stateProperties.startOffset != null && - _stateProperties.currentPosition != null && - _stateProperties.chartAxis.axisClipRect - .contains(_stateProperties.startOffset!) && - _stateProperties.chartAxis.axisClipRect - .contains(_stateProperties.currentPosition!)) { - //swipe configuration options - const double swipeMaxDistanceThreshold = 50.0; - final double swipeMinDisplacement = - (_stateProperties.requireInvertedAxis - ? _stateProperties.chartAxis.axisClipRect.height - : _stateProperties.chartAxis.axisClipRect.width) * - 0.1; - final double swipeMinVelocity = - _stateProperties.pointerDeviceKind == PointerDeviceKind.mouse - ? 0.0 - : 240; - ChartSwipeDirection swipeDirection; - - final double dx = (_stateProperties.currentPosition!.dx - - _stateProperties.startOffset!.dx) - .abs(); - final double dy = (_stateProperties.currentPosition!.dy - - _stateProperties.startOffset!.dy) - .abs(); - final double velocity = details.primaryVelocity!; - if (_stateProperties.requireInvertedAxis && - dx <= swipeMaxDistanceThreshold && - dy >= swipeMinDisplacement && - velocity.abs() >= swipeMinVelocity) { - ///vertical - swipeDirection = - _stateProperties.pointerDeviceKind == PointerDeviceKind.mouse - ? (_stateProperties.currentPosition!.dy > - _stateProperties.startOffset!.dy - ? ChartSwipeDirection.end - : ChartSwipeDirection.start) - : (velocity < 0 - ? ChartSwipeDirection.start - : ChartSwipeDirection.end); - chart.onPlotAreaSwipe!(swipeDirection); - } else if (!_stateProperties.requireInvertedAxis && - dx >= swipeMinDisplacement && - dy <= swipeMaxDistanceThreshold && - velocity.abs() >= swipeMinVelocity) { - ///horizontal - swipeDirection = - _stateProperties.pointerDeviceKind == PointerDeviceKind.mouse - ? (_stateProperties.currentPosition!.dx > - _stateProperties.startOffset!.dx - ? ChartSwipeDirection.start - : ChartSwipeDirection.end) - : (velocity > 0 - ? ChartSwipeDirection.start - : ChartSwipeDirection.end); - chart.onPlotAreaSwipe!(swipeDirection); - } - } - - ///Load More feature - if (chart.loadMoreIndicatorBuilder != null && - _stateProperties.startOffset != null && - _stateProperties.currentPosition != null) { - final bool verticallyDragging = (_stateProperties.currentPosition!.dy - - _stateProperties.startOffset!.dy) - .abs() > - (_stateProperties.currentPosition!.dx - - _stateProperties.startOffset!.dx) - .abs(); - if ((!verticallyDragging && !_stateProperties.requireInvertedAxis) || - (verticallyDragging && _stateProperties.requireInvertedAxis)) { - bool loadMore = false; - final ChartAxisRendererDetails primaryXAxisDetails = - _stateProperties.chartAxis.primaryXAxisDetails; - // Here, direction is set accordingly based on the axis transposed value - // and primary x-axis inversed value. - final ChartSwipeDirection direction = - _stateProperties.requireInvertedAxis - ? (_stateProperties.currentPosition!.dy > - _stateProperties.startOffset!.dy - ? primaryXAxisDetails.axis.isInversed - ? ChartSwipeDirection.start - : ChartSwipeDirection.end - : primaryXAxisDetails.axis.isInversed - ? ChartSwipeDirection.end - : ChartSwipeDirection.start) - : (_stateProperties.currentPosition!.dx > - _stateProperties.startOffset!.dx - ? primaryXAxisDetails.axis.isInversed - ? ChartSwipeDirection.end - : ChartSwipeDirection.start - : primaryXAxisDetails.axis.isInversed - ? ChartSwipeDirection.start - : ChartSwipeDirection.end); - for (int axisIndex = 0; - axisIndex < - _stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(_stateProperties - .chartAxis.axisRenderersCollection[axisIndex]); - if (((!verticallyDragging && - axisDetails.orientation == - AxisOrientation.horizontal) || - (verticallyDragging && - axisDetails.orientation == AxisOrientation.vertical)) && - axisDetails.actualRange != null && - ((axisDetails.actualRange!.minimum.round() == - axisDetails.visibleRange!.minimum.round() && - direction == ChartSwipeDirection.start) || - (axisDetails.actualRange!.maximum.round() == - axisDetails.visibleRange!.maximum.round() && - direction == ChartSwipeDirection.end))) { - loadMore = true; - break; - } - } - - if (loadMore && !_stateProperties.isLoadMoreIndicator) { - _stateProperties.isLoadMoreIndicator = true; - _stateProperties.loadMoreViewStateSetter(() { - _stateProperties.swipeDirection = direction; - }); - } else { - _stateProperties.isLoadMoreIndicator = false; - } - } - } - _stateProperties.startOffset = null; - _stateProperties.currentPosition = null; - } - } - - /// To perform mouse hover event - void _performMouseHover(PointerEvent event) { - if (_stateProperties.chartState.mounted) { - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isHovering = true; - tooltipRenderingDetails.isInteraction = true; - final Offset position = renderBox.globalToLocal(event.position); - final CartesianSeriesRenderer? cartesianSeriesRenderer = - _findSeries(position); - if ((chart.tooltipBehavior.enable && - chart.tooltipBehavior.activationMode == - ActivationMode.singleTap) || - shouldShowAxisTooltip(_stateProperties)) { - chart.tooltipBehavior.builder != null - ? tooltipRenderingDetails.showTemplateTooltip(position) - : _stateProperties.renderingDetails.tooltipBehaviorRenderer - .onEnter(position.dx, position.dy); - } - if (chart.trackballBehavior.enable && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series - is! ErrorBarSeries && - chart.trackballBehavior.activationMode == ActivationMode.singleTap) { - chart.trackballBehavior.builder != null - ? trackballRenderingDetails.showTemplateTrackball(position) - : _stateProperties.trackballBehaviorRenderer - .onEnter(position.dx, position.dy); - } - if (chart.crosshairBehavior.enable && - cartesianSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series - is! ErrorBarSeries && - chart.crosshairBehavior.activationMode == ActivationMode.singleTap) { - _stateProperties.crosshairBehaviorRenderer - .onEnter(position.dx, position.dy); - } - } - } - - /// To perform the mouse exit event - void _performMouseExit(PointerEvent event) { - if (_stateProperties.chartState.mounted) { - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.tooltipBehavior.enable || - shouldShowAxisTooltip(_stateProperties)) { - _stateProperties.renderingDetails.tooltipBehaviorRenderer - .onExit(position.dx, position.dy); - } - if (chart.crosshairBehavior.enable) { - _stateProperties.crosshairBehaviorRenderer - .onExit(position.dx, position.dy); - } - if (chart.trackballBehavior.enable) { - _stateProperties.trackballBehaviorRenderer - .onExit(position.dx, position.dy); - } - } - } - - /// To bind the interaction widgets - void _bindInteractionWidgets( - BoxConstraints constraints, BuildContext context) { - TrackballPainter trackballPainter; - CrosshairPainter crosshairPainter; - - final List userInteractionWidgets = []; - final ZoomRectPainter zoomRectPainter = - ZoomRectPainter(stateProperties: _stateProperties); - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - zoomingBehaviorDetails.painter = zoomRectPainter; - CrosshairHelper.setStateProperties( - chart.crosshairBehavior, _stateProperties); - TooltipHelper.setStateProperties(chart.tooltipBehavior, _stateProperties); - TrackballHelper.setStateProperties( - chart.trackballBehavior, _stateProperties); - ZoomPanBehaviorHelper.setStateProperties( - chart.zoomPanBehavior, _stateProperties); - // ignore: unnecessary_null_comparison - if (chart.trackballBehavior != null && chart.trackballBehavior.enable) { - if (chart.trackballBehavior.builder != null) { - trackballRenderingDetails.trackballTemplate = TrackballTemplate( - key: GlobalKey>(), - trackballBehavior: chart.trackballBehavior, - stateProperties: _stateProperties); - userInteractionWidgets - .add(trackballRenderingDetails.trackballTemplate!); - } else { - trackballPainter = TrackballPainter( - stateProperties: _stateProperties, - valueNotifier: _stateProperties.repaintNotifiers['trackball']!); - trackballRenderingDetails.trackballPainter = trackballPainter; - userInteractionWidgets.add(Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: const BoxDecoration(color: Colors.transparent), - child: CustomPaint(painter: trackballPainter))); - } - } - // ignore: unnecessary_null_comparison - if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable) { - crosshairPainter = CrosshairPainter( - stateProperties: _stateProperties, - valueNotifier: _stateProperties.repaintNotifiers['crosshair']!); - CrosshairHelper.getRenderingDetails( - _stateProperties.crosshairBehaviorRenderer) - .crosshairPainter = crosshairPainter; - userInteractionWidgets.add(Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: const BoxDecoration(color: Colors.transparent), - child: CustomPaint(painter: crosshairPainter))); - } - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _renderingDetails.tooltipBehaviorRenderer); - if (chart.tooltipBehavior.enable || - shouldShowAxisTooltip(_stateProperties)) { - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue = null; - tooltipRenderingDetails.chartTooltip = SfTooltip( - color: tooltip.color ?? _renderingDetails.chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.builder == null ? tooltip.borderWidth : 0, - duration: tooltip.duration.toInt(), - shouldAlwaysShow: tooltip.shouldAlwaysShow, - elevation: tooltip.elevation, - canShowMarker: tooltip.canShowMarker, - textAlignment: tooltip.textAlignment, - decimalPlaces: tooltip.decimalPlaces, - labelColor: tooltip.textStyle.color ?? - _renderingDetails.chartTheme.tooltipLabelColor, - header: tooltip.header, - format: tooltip.format, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? tooltipRenderingDetails.tooltipRenderingEvent - : null); - _renderingDetails.chartWidgets! - .add(tooltipRenderingDetails.chartTooltip!); - } - final Widget uiWidget = IgnorePointer( - ignoring: chart.annotations != null, - child: Stack(children: userInteractionWidgets)); - _stateProperties.renderingDetails.chartWidgets!.add(uiWidget); - } - - /// Triggering onAxisLabelTapped event - void _triggerAxisLabelEvent(Offset position) { - for (int i = 0; - i < _stateProperties.chartAxis.axisRenderersCollection.length; - i++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[i]); - final List labels = axisDetails.visibleLabels; - for (int k = 0; k < labels.length; k++) { - if (axisDetails.axis.isVisible && - AxisHelper.getLabelRegion(labels[k]) != null && - AxisHelper.getLabelRegion(labels[k])!.contains(position)) { - AxisLabelTapArgs labelArgs; - labelArgs = AxisLabelTapArgs(axisDetails.axis, axisDetails.name!); - labelArgs.text = labels[k].text; - labelArgs.value = labels[k].value; - chart.onAxisLabelTapped!(labelArgs); - } - } - } - } - - /// Gets method of the series painter - CustomPainter _getSeriesPainter(int value, AnimationController controller, - CartesianSeriesRenderer seriesRenderer) { - CustomPainter? customPainter; - final PainterKey painterKey = PainterKey( - index: value, name: 'series $value', isRenderCompleted: false); - _stateProperties.painterKeys.add(painterKey); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - switch (seriesRendererDetails.seriesType) { - case 'line': - customPainter = LineChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as LineSeriesRenderer, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'spline': - customPainter = SplineChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as SplineSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'column': - customPainter = ColumnChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as ColumnSeriesRenderer, - isRepaint: !(_stateProperties.zoomedState != null) || - _stateProperties.zoomedAxisRendererStates.isNotEmpty, - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - - case 'scatter': - customPainter = ScatterChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as ScatterSeriesRenderer, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stepline': - customPainter = StepLineChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StepLineSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'area': - customPainter = AreaChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as AreaSeriesRenderer, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'bubble': - customPainter = BubbleChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as BubbleSeriesRenderer, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'bar': - customPainter = BarChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as BarSeriesRenderer, - isRepaint: ((_stateProperties.zoomedState != null) == false) || - _stateProperties.zoomedAxisRendererStates.isNotEmpty, - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'fastline': - customPainter = FastLineChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as FastLineSeriesRenderer, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - painterKey: painterKey, - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'rangecolumn': - customPainter = RangeColumnChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as RangeColumnSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'rangearea': - customPainter = RangeAreaChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as RangeAreaSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'steparea': - customPainter = StepAreaChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StepAreaSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'splinearea': - customPainter = SplineAreaChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as SplineAreaSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'splinerangearea': - customPainter = SplineRangeAreaChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as SplineRangeAreaSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedarea': - customPainter = StackedAreaChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedAreaSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedbar': - customPainter = StackedBarChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedBarSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedcolumn': - customPainter = StackedColummnChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedColumnSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedline': - customPainter = StackedLineChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedLineSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedarea100': - customPainter = StackedArea100ChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedArea100SeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedbar100': - customPainter = StackedBar100ChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedBar100SeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedcolumn100': - customPainter = StackedColumn100ChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedColumn100SeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'stackedline100': - customPainter = StackedLine100ChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as StackedLine100SeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'hilo': - customPainter = HiloPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as HiloSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - - case 'hiloopenclose': - customPainter = HiloOpenClosePainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as HiloOpenCloseSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'candle': - customPainter = CandlePainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as CandleSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'histogram': - customPainter = HistogramChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as HistogramSeriesRenderer, - chartSeries: _stateProperties.chartSeries, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'boxandwhisker': - customPainter = BoxAndWhiskerPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as BoxAndWhiskerSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'waterfall': - customPainter = WaterfallChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as WaterfallSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - case 'errorbar': - customPainter = ErrorBarChartPainter( - stateProperties: _stateProperties, - seriesRenderer: seriesRenderer as ErrorBarSeriesRenderer, - painterKey: painterKey, - isRepaint: _stateProperties.zoomedState != null - ? _stateProperties.zoomedAxisRendererStates.isNotEmpty - : (_stateProperties.legendToggling || - seriesRendererDetails.needsRepaint == true), - animationController: controller, - notifier: seriesRendererDetails.repaintNotifier); - break; - } - 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 deleted file mode 100644 index be4dc7e15..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart +++ /dev/null @@ -1,1102 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; - -import '../../common/utils/typedef.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../axis/numeric_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/histogram_series.dart'; -import '../chart_series/series.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../series_painter/bubble_painter.dart'; -import '../series_painter/fastline_painter.dart'; -import '../series_painter/histogram_painter.dart'; -import '../series_painter/spline_painter.dart'; -import '../series_painter/waterfall_painter.dart'; -import '../technical_indicators/accumulation_distribution_indicator.dart'; -import '../technical_indicators/atr_indicator.dart'; -import '../technical_indicators/bollinger_bands_indicator.dart'; -import '../technical_indicators/ema_indicator.dart'; -import '../technical_indicators/macd_indicator.dart'; -import '../technical_indicators/momentum_indicator.dart'; -import '../technical_indicators/rsi_indicator.dart'; -import '../technical_indicators/sma_indicator.dart'; -import '../technical_indicators/stochastic_indicator.dart'; -import '../technical_indicators/technical_indicator.dart'; -import '../technical_indicators/tma_indicator.dart'; -import '../trendlines/trendlines.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Represents the chart series panel class -class ChartSeriesPanel { - /// Creates an instance of chart series panel - ChartSeriesPanel(this.stateProperties); - - /// Specifies the value of state properties - final CartesianStateProperties stateProperties; - - /// Here, we are using get keyword in order 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 => stateProperties.chart; - - /// Specifies whether the series is stacked 100 - bool isStacked100 = false; - - /// Specifies the palette index value - int paletteIndex = 0; - - /// Holds the sum of Y-values - num sumOfYvalues = 0; - - /// Holds the list of YValues - List yValues = []; - - /// Contains the visible series for chart - List visibleSeriesRenderers = - []; - - /// Holds the list of cluster stacked item info - List clusterStackedItemInfo = - []; - - /// Specifies whether axis range animation is required - bool needAxisRangeAnimation = false; - - /// To get data and process data for rendering chart - void processData() { - final List seriesRendererList = - visibleSeriesRenderers; - isStacked100 = false; - paletteIndex = 0; - _findAreaType(seriesRendererList); - if (chart.indicators.isNotEmpty) { - _populateDataPoints(seriesRendererList); - _calculateIndicators(); - stateProperties.chartAxis.calculateVisibleAxes(); - _findMinMax(seriesRendererList); - _renderTrendline(); - } else { - stateProperties.chartAxis.calculateVisibleAxes(); - _populateDataPoints(seriesRendererList); - } - calculateStackedValues(findSeriesCollection(stateProperties)); - _renderTrendline(); - } - - ///check whether axis animation applicable or not - bool _needAxisAnimation(CartesianSeriesRenderer seriesRenderer, - CartesianSeriesRenderer oldSeriesRenderer) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final SeriesRendererDetails oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); - final dynamic oldAxis = oldSeriesRendererDetails.xAxisDetails!.axis; - final dynamic axis = seriesRendererDetails.xAxisDetails!.axis; - final bool needAnimation = - seriesRendererDetails.series.animationDuration > 0 == true && - seriesRendererDetails.yAxisDetails!.runtimeType == - oldSeriesRendererDetails.yAxisDetails!.runtimeType && - seriesRendererDetails.xAxisDetails!.runtimeType == - oldSeriesRendererDetails.xAxisDetails!.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) { - stateProperties.totalAnimatingSeries = 0; - bool isSelectionRangeChangeByEvent = false; - for (final CartesianSeriesRenderer seriesRenderer in seriesRendererList) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final CartesianSeries series = - seriesRendererDetails.series; - final ChartIndexedValueMapper? _bubbleSize = series.sizeValueMapper; - seriesRendererDetails.minimumX = seriesRendererDetails.minimumY = - seriesRendererDetails.minDelta = seriesRendererDetails.maximumX = - seriesRendererDetails.maximumY = null; - if (seriesRenderer is BubbleSeriesRenderer) { - seriesRendererDetails.maxSize = seriesRendererDetails.minSize = null; - } - seriesRendererDetails.needAnimateSeriesElements = false; - seriesRendererDetails.needsAnimation = false; - seriesRendererDetails.reAnimate = false; - CartesianChartPoint? currentPoint; - yValues = []; - sumOfYvalues = 0; - seriesRendererDetails.dataPoints = >[]; - if (seriesRenderer is FastLineSeriesRenderer) { - seriesRendererDetails.overallDataPoints = - >[]; - } - seriesRendererDetails.xValues = []; - if (!isStacked100 && - seriesRendererDetails.seriesType.contains('100') == true) { - isStacked100 = true; - } - if (seriesRendererDetails.visible! == true) { - stateProperties.totalAnimatingSeries++; - } - if (seriesRenderer is HistogramSeriesRenderer) { - final HistogramSeries series = - seriesRendererDetails.series as HistogramSeries; - for (int pointIndex = 0; - pointIndex < series.dataSource.length; - pointIndex++) { - yValues.add(series.yValueMapper!(pointIndex) ?? 0); - sumOfYvalues += yValues[pointIndex]; - } - seriesRendererDetails.processData(series, yValues, sumOfYvalues); - seriesRendererDetails.histogramValues.minValue = - seriesRendererDetails.histogramValues.yValues!.reduce(math.min); - seriesRendererDetails.histogramValues.binWidth = series.binInterval ?? - (3.5 * seriesRendererDetails.histogramValues.sDValue!) / - math.pow(seriesRendererDetails.histogramValues.yValues!.length, - 1 / 3); - } - final String seriesType = seriesRendererDetails.seriesType; - final bool needSorting = series.sortingOrder != SortingOrder.none && - series.sortFieldValueMapper != null; - // ignore: unnecessary_null_comparison - if (series.dataSource != null) { - dynamic xVal; - dynamic yVal; - num? low, high; - num maxYValue = 0; - seriesRendererDetails.overAllDataPoints = - ?>[]; - for (int pointIndex = 0; pointIndex < series.dataSource.length;) { - currentPoint = getChartPoint( - seriesRenderer, series.dataSource[pointIndex], pointIndex); - xVal = currentPoint?.x; - yVal = currentPoint?.y; - high = currentPoint?.high; - low = currentPoint?.low; - currentPoint?.overallDataPointIndex = pointIndex; - - seriesRendererDetails.overAllDataPoints.add(currentPoint); - if (seriesRenderer is WaterfallSeriesRenderer) { - yVal ??= 0; - maxYValue += yVal; - currentPoint!.maxYValue = maxYValue; - } - - if (xVal != null) { - num bubbleSize; - final dynamic xAxis = seriesRendererDetails.xAxisDetails?.axis; - final dynamic yAxis = seriesRendererDetails.yAxisDetails?.axis; - dynamic xMin = xAxis?.visibleMinimum; - dynamic xMax = xAxis?.visibleMaximum; - final dynamic yMin = yAxis?.visibleMinimum; - final dynamic yMax = yAxis?.visibleMaximum; - dynamic xPointValue = xVal; - bool _isXVisibleRange = true; - bool _isYVisibleRange = true; - if (xAxis is DateTimeAxis) { - xMin = xMin != null ? xMin.millisecondsSinceEpoch : xMin; - xMax = xMax != null ? xMax.millisecondsSinceEpoch : xMax; - 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; - } - if (yMin != null || yMax != null) { - _isYVisibleRange = false; - } - - if ((xMin != null || - xMax != null || - yMin != null || - yMax != null) && - // ignore: unnecessary_null_comparison - stateProperties.oldSeriesRenderers != null && - stateProperties.oldSeriesRenderers.isNotEmpty) { - final int seriesIndex = stateProperties - .chartSeries.visibleSeriesRenderers - .indexOf(seriesRenderer); - final CartesianSeriesRenderer? _oldSeriesRenderer = - stateProperties.oldSeriesRenderers.length - 1 >= seriesIndex - ? stateProperties.oldSeriesRenderers[seriesIndex] - : null; - if (_oldSeriesRenderer != null && - (stateProperties.chart.onSelectionChanged != null || - _needAxisAnimation(seriesRenderer, _oldSeriesRenderer))) { - final SeriesRendererDetails oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(_oldSeriesRenderer); - isSelectionRangeChangeByEvent = - oldSeriesRendererDetails.minimumX != xMin || - oldSeriesRendererDetails.maximumX != xMax || - oldSeriesRendererDetails.minimumY != yMin || - oldSeriesRendererDetails.maximumY != yMax; - } - } - - if (!(!(isSelectionRangeChangeByEvent || - stateProperties.rangeChangeBySlider || - stateProperties.zoomedState == true || - stateProperties.renderingDetails.didSizeChange || - stateProperties.zoomProgress) && - (xMin != null || - xMax != null || - yMin != null || - yMax != null) && - (yVal != null || (low != null && high != null))) || - ((xMin != null && xMax != null) - ? (xPointValue >= xMin) == true && - (xPointValue <= xMax) == true - : xMin != null - ? xPointValue >= xMin - : xMax != null - ? xPointValue <= xMax - : false) == - true || - ((yMin != null && yMax != null) - ? ((yVal ?? low) >= yMin) == true && - ((yVal ?? high) <= yMax) == true - : yMin != null - ? (yVal ?? low) >= yMin - : yMax != null - ? (yVal ?? high) <= yMax - : false) == - true) { - _isXVisibleRange = true; - _isYVisibleRange = true; - seriesRendererDetails.dataPoints.add(currentPoint!); - seriesRendererDetails.xValues!.add(xVal); - if (seriesRenderer is BubbleSeriesRenderer) { - bubbleSize = series.sizeValueMapper == null - ? 4 - : _bubbleSize!(pointIndex) ?? 4; - currentPoint.bubbleSize = bubbleSize.toDouble(); - seriesRendererDetails.maxSize ??= - currentPoint.bubbleSize!.toDouble(); - seriesRendererDetails.minSize ??= - currentPoint.bubbleSize!.toDouble(); - seriesRendererDetails.maxSize = math.max( - seriesRendererDetails.maxSize!, - currentPoint.bubbleSize!.toDouble()); - seriesRendererDetails.minSize = math.min( - seriesRendererDetails.minSize!, - currentPoint.bubbleSize!.toDouble()); - } - - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') || - seriesType == 'boxandwhisker' - ? seriesType == 'hiloopenclose' || - seriesType.contains('candle') - ? (currentPoint.low == null || - currentPoint.high == null || - currentPoint.open == null || - currentPoint.close == null) - : seriesType == 'boxandwhisker' - ? (currentPoint.minimum == null || - currentPoint.maximum == null || - currentPoint.lowerQuartile == null || - currentPoint.upperQuartile == null) - : (currentPoint.low == null || - currentPoint.high == null) - : currentPoint.y == null) { - if (seriesRenderer is XyDataSeriesRenderer && - seriesType != 'waterfall') { - seriesRenderer.calculateEmptyPointValue( - pointIndex, currentPoint, seriesRenderer); - } - } - - // Below lines for changing high, low values based on input - if ((seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') || - seriesType == 'boxandwhisker') && - currentPoint.isVisible) { - if (seriesType == 'boxandwhisker') { - final num max = currentPoint.maximum!; - final num min = currentPoint.minimum!; - currentPoint.maximum = math.max(max, min); - currentPoint.minimum = math.min(max, min); - } else { - final num high = currentPoint.high; - final num low = currentPoint.low; - currentPoint.high = math.max(high, low); - currentPoint.low = math.min(high, low); - } - } - //determines whether the data source has been changed in-order to perform dynamic animation - if (seriesRendererDetails.needsAnimation == false && - needAxisRangeAnimation != true) { - if (seriesRendererDetails.oldSeries == null || - seriesRendererDetails.oldDataPoints!.length < - seriesRendererDetails.dataPoints.length == - true) { - seriesRendererDetails.needAnimateSeriesElements = true; - seriesRendererDetails.needsAnimation = - seriesRendererDetails.visible!; - } else { - seriesRendererDetails.needsAnimation = (seriesRendererDetails - .dataPoints.length <= - seriesRendererDetails.oldDataPoints!.length) == - true - ? seriesRendererDetails.visible! == true && - findChangesInPoint( - currentPoint, - seriesRendererDetails.oldDataPoints![ - seriesRendererDetails.dataPoints.length - 1], - seriesRendererDetails, - ) - : seriesRendererDetails.visible!; - } - } - } - if (seriesRendererDetails.xAxisDetails != null && - seriesRendererDetails.yAxisDetails != null && - !needSorting && - chart.indicators.isEmpty) { - _findMinMaxValue( - seriesRendererDetails.xAxisDetails!.axisRenderer, - seriesRenderer, - currentPoint!, - pointIndex, - series.dataSource.length, - _isXVisibleRange, - _isYVisibleRange); - } - if (seriesRenderer is SplineSeriesRenderer && !needSorting) { - if (pointIndex == 0) { - seriesRendererDetails.xValueList.clear(); - seriesRendererDetails.yValueList.clear(); - } - if (!currentPoint!.isDrop) { - seriesRendererDetails.xValueList.add(currentPoint.xValue); - seriesRendererDetails.yValueList.add(currentPoint.yValue); - } - } - } - pointIndex = seriesRendererDetails.seriesType != 'histogram' - ? pointIndex + 1 - : pointIndex + yVal as int; - } - if (seriesRendererDetails.xAxisDetails - is DateTimeCategoryAxisRenderer) { - _sortDateTimeCategoryDetails(seriesRendererDetails); - } - if (needSorting) { - _sortDataSource(seriesRendererDetails); - if (chart.indicators.isEmpty) { - findSeriesMinMax(seriesRendererDetails); - } - } - } - if (seriesRenderer is FastLineSeriesRenderer) { - seriesRendererDetails.overallDataPoints - .addAll(seriesRendererDetails.dataPoints); - } - } - } - - /// To find the minimum and maximum values for axis - void _findMinMaxValue( - ChartAxisRenderer axisRenderer, - CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint currentPoint, - int pointIndex, - int dataLength, - [bool? isXVisibleRange, - bool? isYVisibleRange]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.visible! == true) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (axisDetails is NumericAxisDetails) { - axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, - pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisDetails is CategoryAxisDetails) { - axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, - pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisDetails is DateTimeAxisDetails) { - axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, - pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisDetails is LogarithmicAxisDetails) { - axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, - pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisDetails is DateTimeCategoryAxisDetails) { - axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, - pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } - } - } - - /// To find minimum and maximum series values - void findSeriesMinMax(SeriesRendererDetails seriesRendererDetails) { - final ChartAxisRenderer axisRenderer = - seriesRendererDetails.xAxisDetails!.axisRenderer; - if (seriesRendererDetails.visible! == true) { - if (seriesRendererDetails.renderer is SplineSeriesRenderer) { - seriesRendererDetails.xValueList.clear(); - seriesRendererDetails.yValueList.clear(); - } - for (int pointIndex = 0; - pointIndex < seriesRendererDetails.dataPoints.length; - pointIndex++) { - _findMinMaxValue( - axisRenderer, - seriesRendererDetails.renderer, - seriesRendererDetails.dataPoints[pointIndex], - pointIndex, - seriesRendererDetails.dataPoints.length, - true, - true); - if (seriesRendererDetails.renderer is SplineSeriesRenderer) { - if (seriesRendererDetails.dataPoints[pointIndex].isDrop == false) { - seriesRendererDetails.xValueList - .add(seriesRendererDetails.dataPoints[pointIndex].xValue); - seriesRendererDetails.yValueList - .add(seriesRendererDetails.dataPoints[pointIndex].yValue); - } - } - } - } - } - - /// To find minimum and maximum in series collection - void _findMinMax(List seriesCollection) { - SeriesRendererDetails seriesRendererDetails; - for (int seriesIndex = 0; - seriesIndex < seriesCollection.length; - seriesIndex++) { - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesCollection[seriesIndex]); - findSeriesMinMax(seriesRendererDetails); - } - } - - /// To render a trendline - void _renderTrendline() { - for (final CartesianSeriesRenderer seriesRenderer - in visibleSeriesRenderers) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.series.trendlines != null) { - TrendlineRenderer trendlineRenderer; - Trendline trendline; - for (int i = 0; - i < seriesRendererDetails.series.trendlines!.length; - i++) { - trendline = seriesRendererDetails.series.trendlines![i]; - trendlineRenderer = seriesRendererDetails.trendlineRenderer[i]; - trendlineRenderer.isNeedRender = trendlineRenderer.visible == true && - seriesRendererDetails.visible! == true && - (trendline.type == TrendlineType.polynomial - ? (trendline.polynomialOrder >= 2 && - trendline.polynomialOrder <= 6) - : !(trendline.type == TrendlineType.movingAverage) || - (trendline.period >= 2 && - trendline.period <= - seriesRendererDetails.series.dataSource.length - - 1)); - trendlineRenderer.animationController = - AnimationController(vsync: stateProperties.chartState) - ..addListener(stateProperties.repaintTrendlines); - stateProperties - .controllerList[trendlineRenderer.animationController] = - stateProperties.repaintTrendlines; - if (trendlineRenderer.isNeedRender) { - trendlineRenderer.setDataSource(seriesRendererDetails, chart); - } - } - } - } - } - - /// Sort the dataSource - void _sortDataSource(SeriesRendererDetails seriesRendererDetails) { - seriesRendererDetails.dataPoints.sort( - // ignore: missing_return - (CartesianChartPoint firstPoint, - CartesianChartPoint secondPoint) { - if (seriesRendererDetails.series.sortingOrder == SortingOrder.ascending) { - return ((firstPoint.sortValue == null) - ? -1 - : (secondPoint.sortValue == null - ? 1 - : (firstPoint.sortValue is String - ? firstPoint.sortValue - .toLowerCase() - .compareTo(secondPoint.sortValue.toLowerCase()) - : firstPoint.sortValue - .compareTo(secondPoint.sortValue)))) as int; - } else if (seriesRendererDetails.series.sortingOrder == - SortingOrder.descending) { - return ((firstPoint.sortValue == null) - ? 1 - : (secondPoint.sortValue == null - ? -1 - : (firstPoint.sortValue is String - ? secondPoint.sortValue - .toLowerCase() - .compareTo(firstPoint.sortValue.toLowerCase()) - : secondPoint.sortValue - .compareTo(firstPoint.sortValue)))) as int; - } else { - return 0; - } - }); - } - - /// To calculate stacked values of a stacked series - void calculateStackedValues( - List seriesRendererCollection) { - StackedItemInfo stackedItemInfo; - ClusterStackedItemInfo clusterStackedItemInfo; - String groupName = ''; - List? positiveValues; - List? negativeValues; - CartesianSeriesRenderer seriesRenderer; - if (isStacked100) { - _calculateStackingPercentage(seriesRendererCollection); - } - - stateProperties.chartSeries.clusterStackedItemInfo = - []; - for (int i = 0; i < seriesRendererCollection.length; i++) { - seriesRenderer = seriesRendererCollection[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRenderer is StackedSeriesRenderer && - seriesRendererDetails.series is StackedSeriesBase) { - final StackedSeriesBase stackedSeriesBase = - seriesRendererDetails.series as StackedSeriesBase; - if (seriesRendererDetails.dataPoints.isNotEmpty == true) { - groupName = - (seriesRendererDetails.seriesType.contains('stackedarea') == true) - ? 'stackedareagroup' - // ignore: unnecessary_null_comparison - : stackedSeriesBase.groupName; - // : (stackedSeriesBase.groupName ?? ('series ' + i.toString())); - stackedItemInfo = StackedItemInfo(i, seriesRenderer); - if (stateProperties.chartSeries.clusterStackedItemInfo.isNotEmpty) { - for (int k = 0; - k < stateProperties.chartSeries.clusterStackedItemInfo.length; - k++) { - clusterStackedItemInfo = - stateProperties.chartSeries.clusterStackedItemInfo[k]; - if (clusterStackedItemInfo.stackName == groupName) { - clusterStackedItemInfo.stackedItemInfo.add(stackedItemInfo); - break; - } else if (k == - stateProperties.chartSeries.clusterStackedItemInfo.length - - 1) { - stateProperties.chartSeries.clusterStackedItemInfo.add( - ClusterStackedItemInfo( - groupName, [stackedItemInfo])); - break; - } - } - } else { - stateProperties.chartSeries.clusterStackedItemInfo.add( - ClusterStackedItemInfo( - groupName, [stackedItemInfo])); - } - - seriesRendererDetails.stackingValues = []; - StackingInfo? currentPositiveStackInfo; - - if (positiveValues == null || negativeValues == null) { - positiveValues = []; - currentPositiveStackInfo = StackingInfo(groupName, []); - positiveValues.add(currentPositiveStackInfo); - negativeValues = []; - negativeValues.add(StackingInfo(groupName, [])); - } - _addStackingValues( - seriesRendererDetails, - isStacked100, - positiveValues, - negativeValues, - currentPositiveStackInfo, - groupName); - } - } - } - } - - /// To add the values of stacked series - void _addStackingValues( - SeriesRendererDetails seriesRendererDetails, - bool isStacked100, - List positiveValues, - List negativeValues, - StackingInfo? currentPositiveStackInfo, - String groupName) { - num lastValue, value; - CartesianChartPoint point; - StackingInfo? currentNegativeStackInfo; - final List startValues = []; - final List endValues = []; - for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { - point = seriesRendererDetails.dataPoints[j]; - value = point.y; - if (positiveValues.isNotEmpty) { - for (int k = 0; k < positiveValues.length; k++) { - if (groupName == positiveValues[k].groupName) { - currentPositiveStackInfo = positiveValues[k]; - break; - } else if (k == positiveValues.length - 1) { - currentPositiveStackInfo = StackingInfo(groupName, []); - positiveValues.add(currentPositiveStackInfo); - } - } - } - if (negativeValues.isNotEmpty) { - for (int k = 0; k < negativeValues.length; k++) { - if (groupName == negativeValues[k].groupName) { - currentNegativeStackInfo = negativeValues[k]; - break; - } else if (k == negativeValues.length - 1) { - currentNegativeStackInfo = StackingInfo(groupName, []); - negativeValues.add(currentNegativeStackInfo); - } - } - } - if (currentPositiveStackInfo?.stackingValues != null) { - final int length = currentPositiveStackInfo!.stackingValues!.length; - if (length == 0 || j > length - 1) { - currentPositiveStackInfo.stackingValues!.add(0); - } - } - if (currentNegativeStackInfo?.stackingValues != null) { - final int length = currentNegativeStackInfo!.stackingValues!.length; - if (length == 0 || j > length - 1) { - currentNegativeStackInfo.stackingValues!.add(0); - } - } - if (isStacked100 && - seriesRendererDetails.renderer is StackedSeriesRenderer) { - value = value / seriesRendererDetails.percentageValues[j] * 100; - value = value.isNaN ? 0 : value; - } - if (seriesRendererDetails.seriesType.contains('stackedarea') == true || - value >= 0) { - lastValue = currentPositiveStackInfo!.stackingValues![j]; - currentPositiveStackInfo.stackingValues![j] = - (lastValue + value).toDouble(); - } else { - lastValue = currentNegativeStackInfo!.stackingValues![j]; - currentNegativeStackInfo.stackingValues![j] = - (lastValue + value).toDouble(); - } - startValues.add(lastValue.toDouble()); - endValues.add((value + lastValue).toDouble()); - if (isStacked100 && endValues[j] > 100) { - endValues[j] = 100; - } - point.cumulativeValue = - seriesRendererDetails.seriesType.contains('100') == false - ? endValues[j] - : endValues[j].truncateToDouble(); - } - if (seriesRendererDetails.renderer is StackedSeriesRenderer) { - seriesRendererDetails.stackingValues - .add(StackedValues(startValues, endValues)); - } - seriesRendererDetails.minimumY = startValues.reduce(math.min); - seriesRendererDetails.maximumY = endValues.reduce(math.max); - - if (seriesRendererDetails.minimumY! > endValues.reduce(math.min) == true) { - seriesRendererDetails.minimumY = - isStacked100 ? -100 : endValues.reduce(math.min); - } - if (seriesRendererDetails.maximumY! < startValues.reduce(math.max) == - true) { - seriesRendererDetails.maximumY = 0; - } - } - - /// To find the percentage of stacked series - void _calculateStackingPercentage( - List seriesRendererCollection) { - List? percentageValues; - CartesianSeriesRenderer seriesRenderer; - SeriesRendererDetails seriesRendererDetails; - String groupName; - StackingInfo? stackingInfo; - int length; - num lastValue, value; - CartesianChartPoint point; - for (int i = 0; i < seriesRendererCollection.length; i++) { - seriesRenderer = seriesRendererCollection[i]; - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - seriesRendererDetails.yAxisDetails!.isStack100 = true; - if (seriesRenderer is StackedSeriesRenderer && - seriesRendererDetails.series is StackedSeriesBase) { - final StackedSeriesBase stackedSeriesBase = - seriesRendererDetails.series as StackedSeriesBase; - if (seriesRendererDetails.dataPoints.isNotEmpty == true) { - groupName = (seriesRendererDetails.seriesType == 'stackedarea100') - ? 'stackedareagroup' - : stackedSeriesBase.groupName; - - if (percentageValues == null) { - percentageValues = []; - stackingInfo = StackingInfo(groupName, []); - } - for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { - point = seriesRendererDetails.dataPoints[j]; - value = point.y; - if (percentageValues.isNotEmpty) { - for (int k = 0; k < percentageValues.length; k++) { - if (groupName == percentageValues[k].groupName) { - stackingInfo = percentageValues[k]; - break; - } else if (k == percentageValues.length - 1) { - stackingInfo = StackingInfo(groupName, []); - percentageValues.add(stackingInfo); - } - } - } - if (stackingInfo?.stackingValues != null) { - length = stackingInfo!.stackingValues!.length; - if (length == 0 || j > length - 1) { - stackingInfo.stackingValues!.add(0); - } - } - if (seriesRendererDetails.seriesType.contains('stackedarea') == - true || - value >= 0) { - lastValue = stackingInfo!.stackingValues![j]; - stackingInfo.stackingValues![j] = (lastValue + value).toDouble(); - } else { - lastValue = stackingInfo!.stackingValues![j]; - stackingInfo.stackingValues![j] = (lastValue - value).toDouble(); - } - if (j == seriesRendererDetails.dataPoints.length - 1) { - percentageValues.add(stackingInfo); - } - } - } - if (percentageValues != null) { - for (int i = 0; i < percentageValues.length; i++) { - if (seriesRendererDetails.seriesType == 'stackedarea100') { - seriesRendererDetails.percentageValues = - percentageValues[i].stackingValues!; - } else { - if (stackedSeriesBase.groupName == - percentageValues[i].groupName) { - seriesRendererDetails.percentageValues = - percentageValues[i].stackingValues!; - } - } - } - } - } - } - } - - /// Calculate area type - void _findAreaType(List seriesRendererList) { - if (visibleSeriesRenderers.isNotEmpty) { - int index = -1; - for (final CartesianSeriesRenderer series in seriesRendererList) { - _setSeriesType(series, index += 1); - } - } - } - - /// To find and set the series type - void _setSeriesType(CartesianSeriesRenderer seriesRenderer, int index) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.series.color == null) { - seriesRendererDetails.seriesColor = - chart.palette[paletteIndex % chart.palette.length]; - paletteIndex++; - } else { - seriesRendererDetails.seriesColor = seriesRendererDetails.series.color; - } - - seriesRendererDetails.seriesType = getSeriesType(seriesRenderer); - - if (index == 0) { - final String seriesType = seriesRendererDetails.seriesType; - stateProperties.requireInvertedAxis = chart.isTransposed ^ - ((seriesType.toLowerCase().contains('bar')) && - (!seriesType.toLowerCase().contains('errorbar'))); - } - } - - ///below method is for indicator rendering - void _calculateIndicators() { - // ignore: unnecessary_null_comparison - if (chart.indicators != null && chart.indicators.isNotEmpty) { - dynamic indicator; - bool existField; - Map _map = {}; - TechnicalIndicatorsRenderer technicalIndicatorsRenderer; - if (!chart.legend.isVisible!) { - final List textCollection = []; - for (int i = 0; i < chart.indicators.length; i++) { - final TechnicalIndicators indicator = - chart.indicators[i]; - technicalIndicatorsRenderer = - stateProperties.technicalIndicatorRenderer[i]; - setIndicatorType(indicator, technicalIndicatorsRenderer); - textCollection.add(technicalIndicatorsRenderer.indicatorType); - } - //ignore: prefer_collection_literals - _map = Map(); - //ignore: avoid_function_literals_in_foreach_calls - textCollection.forEach((String str) => - _map[str] = !_map.containsKey(str) ? (1) : (_map[str]! + 1)); - } - - final List indicatorTextCollection = []; - for (int i = 0; i < chart.indicators.length; i++) { - indicator = chart.indicators[i]; - technicalIndicatorsRenderer = - stateProperties.technicalIndicatorRenderer[i]; - technicalIndicatorsRenderer.dataPoints = - >[]; - technicalIndicatorsRenderer.index = i; - if (!chart.legend.isVisible!) { - final int count = indicatorTextCollection - .contains(technicalIndicatorsRenderer.indicatorType) - ? getIndicatorId(indicatorTextCollection, - technicalIndicatorsRenderer.indicatorType) - : 0; - indicatorTextCollection - .add(technicalIndicatorsRenderer.indicatorType); - technicalIndicatorsRenderer.name = indicator.name ?? - (technicalIndicatorsRenderer.indicatorType + - (_map[technicalIndicatorsRenderer.indicatorType] == 1 - ? '' - : ' $count')); - } - if (indicator != null && - indicator.isVisible == true && - (indicator.dataSource != null || indicator.seriesName != null)) { - if (indicator.dataSource != null && - indicator.dataSource.isNotEmpty == true) { - existField = technicalIndicatorsRenderer.indicatorType == 'SMA' || - technicalIndicatorsRenderer.indicatorType == 'TMA' || - technicalIndicatorsRenderer.indicatorType == 'EMA'; - final String valueField = - existField ? _getFieldType(indicator).toLowerCase() : ''; - CartesianChartPoint currentPoint; - for (int pointIndex = 0; - pointIndex < indicator.dataSource.length; - pointIndex++) { - if (indicator.xValueMapper != null) { - final dynamic xVal = indicator.xValueMapper(pointIndex); - num? highValue, lowValue, openValue, closeValue, volumeValue; - technicalIndicatorsRenderer.dataPoints! - .add(CartesianChartPoint(xVal, null)); - currentPoint = technicalIndicatorsRenderer.dataPoints![ - technicalIndicatorsRenderer.dataPoints!.length - 1]; - if (indicator.highValueMapper != null) { - highValue = indicator.highValueMapper(pointIndex); - technicalIndicatorsRenderer - .dataPoints![ - technicalIndicatorsRenderer.dataPoints!.length - 1] - .high = highValue; - } - if (indicator.lowValueMapper != null) { - lowValue = indicator.lowValueMapper(pointIndex); - technicalIndicatorsRenderer - .dataPoints![ - technicalIndicatorsRenderer.dataPoints!.length - 1] - .low = lowValue; - } - - // changing high,low value - if (currentPoint.high != null && currentPoint.low != null) { - final num high = currentPoint.high; - final num low = currentPoint.low; - currentPoint.high = math.max(high, low); - currentPoint.low = math.min(high, low); - } - if (indicator.openValueMapper != null) { - openValue = indicator.openValueMapper(pointIndex); - technicalIndicatorsRenderer - .dataPoints![ - technicalIndicatorsRenderer.dataPoints!.length - 1] - .open = openValue; - } - if (indicator.closeValueMapper != null) { - closeValue = indicator.closeValueMapper(pointIndex); - technicalIndicatorsRenderer - .dataPoints![ - technicalIndicatorsRenderer.dataPoints!.length - 1] - .close = closeValue; - } - if (indicator is AccumulationDistributionIndicator && - indicator.volumeValueMapper != null) { - volumeValue = indicator.volumeValueMapper!(pointIndex); - technicalIndicatorsRenderer - .dataPoints![ - technicalIndicatorsRenderer.dataPoints!.length - 1] - .volume = volumeValue; - } - - if ((closeValue == null && - (!existField || valueField == 'close')) || - (highValue == null && - (valueField == 'high' || - technicalIndicatorsRenderer.indicatorType == 'AD' || - technicalIndicatorsRenderer.indicatorType == - 'ATR' || - technicalIndicatorsRenderer.indicatorType == - 'RSI' || - technicalIndicatorsRenderer.indicatorType == - 'Stochastic')) || - (lowValue == null && - (valueField == 'low' || - technicalIndicatorsRenderer.indicatorType == 'AD' || - technicalIndicatorsRenderer.indicatorType == - 'ATR' || - technicalIndicatorsRenderer.indicatorType == - 'RSI' || - technicalIndicatorsRenderer.indicatorType == - 'Stochastic')) || - (openValue == null && valueField == 'open') || - (volumeValue == null && - technicalIndicatorsRenderer.indicatorType == 'AD')) { - technicalIndicatorsRenderer.dataPoints!.removeAt( - technicalIndicatorsRenderer.dataPoints!.length - 1); - } - } - } - } else if (indicator.seriesName != null) { - CartesianSeriesRenderer? seriesRenderer; - - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - if (indicator.seriesName == - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]) - .series - .name) { - seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[i]; - break; - } - } - final SeriesRendererDetails? seriesRendererDetails = - seriesRenderer != null - ? SeriesHelper.getSeriesRendererDetails(seriesRenderer) - : null; - technicalIndicatorsRenderer.dataPoints = (seriesRendererDetails != - null && - (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType == 'boxandwhisker')) - ? seriesRendererDetails.dataPoints - : null; - } - if (technicalIndicatorsRenderer.dataPoints != null && - technicalIndicatorsRenderer.dataPoints!.isNotEmpty) { - technicalIndicatorsRenderer.initDataSource( - indicator, technicalIndicatorsRenderer, chart); - if (technicalIndicatorsRenderer.renderPoints.isNotEmpty) { - stateProperties.chartSeries.visibleSeriesRenderers - .addAll(technicalIndicatorsRenderer.targetSeriesRenderers); - } - } - } - } - } - } - - /// To get the field type of an indicator - String _getFieldType(TechnicalIndicators indicator) { - String valueField = ''; - if (indicator is EmaIndicator) { - valueField = indicator.valueField; - } else if (indicator is TmaIndicator) { - valueField = indicator.valueField; - } else if (indicator is SmaIndicator) { - valueField = indicator.valueField; - } - return valueField; - } - - /// To return the indicator id - int getIndicatorId(List list, String str) { - int count = 0; - for (int i = 0; i < list.length; i++) { - if (list[i] == str) { - count++; - } - } - return count; - } - - /// Setting indicator type - void setIndicatorType(TechnicalIndicators indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - if (indicator is AtrIndicator) { - technicalIndicatorsRenderer.indicatorType = 'ATR'; - } else if (indicator is AccumulationDistributionIndicator) { - technicalIndicatorsRenderer.indicatorType = 'AD'; - } else if (indicator is BollingerBandIndicator) { - technicalIndicatorsRenderer.indicatorType = 'Bollinger'; - } else if (indicator is EmaIndicator) { - technicalIndicatorsRenderer.indicatorType = 'EMA'; - } else if (indicator is MacdIndicator) { - technicalIndicatorsRenderer.indicatorType = 'MACD'; - } else if (indicator is MomentumIndicator) { - technicalIndicatorsRenderer.indicatorType = 'Momentum'; - } else if (indicator is RsiIndicator) { - technicalIndicatorsRenderer.indicatorType = 'RSI'; - } else if (indicator is SmaIndicator) { - technicalIndicatorsRenderer.indicatorType = 'SMA'; - } else if (indicator is StochasticIndicator) { - technicalIndicatorsRenderer.indicatorType = 'Stochastic'; - } else if (indicator is TmaIndicator) { - technicalIndicatorsRenderer.indicatorType = 'TMA'; - } - } - - /// This function sorts the details available based on the x which is of date time type - void _sortDateTimeCategoryDetails( - SeriesRendererDetails seriesRendererDetails) { - final DateTimeCategoryAxisDetails axisDetails = - seriesRendererDetails.xAxisDetails as DateTimeCategoryAxisDetails; - seriesRendererDetails.dataPoints.sort((CartesianChartPoint point1, - CartesianChartPoint point2) { - return point2.x.isAfter(point1.x) == true ? -1 : 1; - }); - axisDetails.labels.sort((String first, String second) { - return int.parse(first) < int.parse(second) ? -1 : 1; - }); - seriesRendererDetails.xValues?.sort(); - for (final CartesianChartPoint point - in seriesRendererDetails.dataPoints) { - point.xValue = - axisDetails.labels.indexOf(point.x.microsecondsSinceEpoch.toString()); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart deleted file mode 100644 index 9bff15790..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; - -/// Holds the gestures for chart. -/// -/// The class ChartBehavior has the public methods to customize the chart behavior, pass the coordinates to detect -/// the specific point or area in the Rendered chart. -/// -abstract class ChartBehavior { - /// Hits while tapping on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onTouchDown(double xPos, double yPos); - - /// Hits while release tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onTouchUp(double xPos, double yPos); - - /// Hits while double tapping on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onDoubleTap(double xPos, double yPos); - - /// Hits while tap and moving on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onTouchMove(double xPos, double yPos); - - /// Hits while a long tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onLongPress(double xPos, double yPos); - - /// Hits while enter tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onEnter(double xPos, double yPos); - - /// Hits while exit tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onExit(double xPos, double yPos); - - /// Hit while try to render the chart. - /// - /// * canvas - Canvas used to draw the chart. - /// - void onPaint(Canvas canvas); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart deleted file mode 100644 index 8de92bd16..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../chart_segment/chart_segment.dart'; - -/// Customizes the selection in chart. -/// -///ChartSelectionBehavior is used to customize the behavior of the chart series segments on selection. It is based on the -/// current point coordinate and touch position. It has the methods to customize the selection behavior. -/// -abstract class ChartSelectionBehavior { - /// Hits while tapping on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onTouchDown(double xPos, double yPos); - - /// Hits while double tapping on the chart.. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onDoubleTap(double xPos, double yPos); - - /// Hits while a long tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - /// - void onLongPress(double xPos, double yPos); - - /// It gets the fill property of the selected item in the series - /// - /// * paint - Paint value of the selected item. - /// * seriesIndex - Series index of the selected point - /// * pointIndex - Point index of the selected point in the series. - /// * selectedSegment - Value of Selected segment in the series. - /// - Paint getSelectedItemFill(Paint paint, int seriesIndex, int pointIndex, - List selectedSegments); - - /// It gets the fill property of the unselected item in the series - /// - /// * paint - Paint value of the unselected item. - /// * seriesIndex - Series index of the unselected point - /// * pointIndex - Point index of the unselected point in the series. - /// * unselectedSegment - Value of unselected segment in the series. - /// - Paint getUnselectedItemFill(Paint paint, int seriesIndex, int pointIndex, - List unselectedSegments); - - /// It gets the border property of the selected item in the series - /// - /// * paint - Paint value of the selected item. - /// * seriesIndex - Series index of the selected point - /// * pointIndex - Point index of the selected point in the series. - /// * selectedSegment - Value of Selected segment in the series. - /// - Paint getSelectedItemBorder(Paint paint, int seriesIndex, int pointIndex, - List selectedSegments); - - /// It gets the border property of the selected item in the series - /// - /// * paint - Paint value of the unselected item. - /// * seriesIndex - Series index of the unselected point - /// * pointIndex - Point index of the unselected point in the series. - /// * unselectedSegment - Value of unSelected segment in the series. - /// - Paint getUnselectedItemBorder(Paint paint, int seriesIndex, int pointIndex, - List unselectedSegments); - - /// It gets the fill property of the selected item in the series - /// - /// * color - Color value of the selected item. - /// * seriesIndex - Series index of the selected point - /// * pointIndex - Point index of the selected point in the series. - /// * selected region - Value of Selected region in the series. - /// - Color getCircularSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List selectedRegions); - - /// It gets the circular fill property of the unselected item in the series - /// - /// * color - Color value of the unselected item. - /// * seriesIndex - Series index of the unselected point - /// * pointIndex - Point index of the unselected point in the series. - /// * unselectedRegion - Value of unSelected region in the series. - /// - Color getCircularUnSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List unselectedRegions); - - /// It gets the circular border property of the seleted item in the series - /// - /// * color - Color value of the selected item. - /// * seriesIndex - Series index of the selected point - /// * pointIndex - Point index of the selected point in the series. - /// * selectedRegion - Value of Selected region of the series. - /// - Color getCircularSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List selectedRegions); - - /// It gets the circular border property of the unselected item in the series - /// - /// * color - Color value of the unselected item. - /// * seriesIndex - Series index of the unselected point - /// * pointIndex - Point index of the unselected point in the series. - /// * unselectedRegion - Value of UnSelected region in the series. - /// - Color getCircularUnSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List unselectedRegions); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart deleted file mode 100644 index 093a43036..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import '../axis/axis.dart'; - -/// Holds the zooming gestures. -/// -/// You can zoom in and zoom out using Zoom behavior. It can be used to customize the -/// DoubleTap zooming, selection zooming and zoomPinching. -abstract class ZoomBehavior { - ///Hits while double tapping on the chart. - ///* xPos - X value of the pan position. - ///* yPos - Y value of the pan position. - void onPan(double xPos, double yPos); - - ///Hits while double tapping on the chart. - ///* xPos - X value of the double tap position. - ///* yPos - Y value of the double tap position. - ///* zoomFactor - zoom in and zoom out position - void onDoubleTap(double xPos, double yPos, double zoomFactor); - - /// To paint in the chart plot area. - /// - /// * canvas - Canvas used to draw the chart. - void onPaint(Canvas canvas); - - /// Chart can be zoomed by a rectangular selecting region on the plot area. - /// - /// * startX - start position of selection zooming in X axis. - /// * startY - start position of selection zooming in Y axis. - /// * currentX - end position of the selection zooming in X axis. - /// * currentY - end position of the selection zooming in Y axis. - void onDrawSelectionZoomRect( - double currentX, double currentY, double startX, double startY); - - ///Chart can be pinched to zoom in / zoom out for starting position. - /// - /// * firstX - first position of pinching in X axis. - /// * firstY - first position of pinching in Y axis. - /// * secondX - last position of pinching in X axis. - /// * secondY - last position of pinching in X axis. - /// scaleFacator - scale factor is a number which scales some quantity in charts. - void onPinchStart(ChartAxis axis, double firstX, double firstY, - double secondX, double secondY, double scaleFactor); - - ///Chart can be pinched to zoom in / zoom out for ending position. - /// - /// * firstX - first position of pinching in X axis. - /// * firstY - first position of pinching in Y axis. - /// * secondX - last position of pinching in X axis. - /// * secondY - last position of pinching in X axis. - /// scaleFacator - scale factor is a number which scales some quantity in charts. - void onPinchEnd(ChartAxis axis, double firstX, double firstY, double secondX, - double secondY, double scaleFactor); - - ///Pinching can be performed by moving two fingers over the chart. - /// - ///* position - which position have to zoom in the axis. - /// scaleFacator - scale factor is a number which scales some quantity in charts. - void onPinch(ChartAxisRendererDetails axisDetails, double position, - double scaleFactor); -} 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 deleted file mode 100644 index 7c436ba43..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../chart_series/series.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for area series. -/// -/// 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. -/// -class AreaSegment extends ChartSegment { - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - fillPaint = Paint(); - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.color != null) { - fillPaint!.color = _segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - fillPaint = (_segmentProperties.pathRect != null) - ? getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.pathRect!, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis) - : fillPaint; - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the area series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the area series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final Paint _strokePaint = Paint(); - _strokePaint - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.series.borderWidth; - if (_segmentProperties.series.borderGradient != null) { - _strokePaint.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.strokePath!.getBounds()); - } else if (_segmentProperties.strokeColor != null) { - _strokePaint.color = _segmentProperties.series.borderColor; - } - _segmentProperties.series.borderWidth == 0 - ? _strokePaint.color = Colors.transparent - : _strokePaint.color; - _strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = _strokePaint; - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - _segmentProperties.pathRect = _segmentProperties.path.getBounds(); - canvas.drawPath( - _segmentProperties.path, - (_segmentProperties.series.gradient == null) - ? fillPaint! - : getFillPaint()); - if (strokePaint!.color != Colors.transparent && - _segmentProperties.strokePath != null) { - drawDashedLine(canvas, _segmentProperties.series.dashArray, strokePaint!, - _segmentProperties.strokePath!); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index b10cb624a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../chart_series/series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for bar series. -/// -/// 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. -/// -class BarSegment extends ChartSegment { - /// The rectangle of the segment. This could be used to render a segment while overriding this segment. - late RRect segmentRect; - - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - if (_segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.color - : (_segmentProperties.currentPoint!.pointColorMapper ?? - _segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.currentPoint!.region!, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis); - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the the bar series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the the bar series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.strokeWidth!; - (_segmentProperties.series.borderGradient != null) - ? strokePaint!.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!) - : strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.strokeColor!; - _segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - _segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final BarSeries barSeries = - _segmentProperties.series as BarSeries; - if (_segmentProperties.trackerFillPaint != null && - barSeries.isTrackVisible) { - _drawSegmentRect(canvas, _segmentProperties.trackBarRect, - _segmentProperties.trackerFillPaint!); - } - - if (_segmentProperties.trackerStrokePaint != null && - barSeries.isTrackVisible) { - _drawSegmentRect(canvas, _segmentProperties.trackBarRect, - _segmentProperties.trackerStrokePaint!); - } - - if (fillPaint != null) { - _drawSegmentRect(canvas, segmentRect, fillPaint!); - } - if (strokePaint != null) { - (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0) - ? drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _segmentProperties.path) - : _drawSegmentRect(canvas, segmentRect, strokePaint!); - } - } - - /// To draw Segment rect of bar segment. - void _drawSegmentRect(Canvas canvas, RRect segmentRect, Paint paint) { - (_segmentProperties.series.animationDuration > 0 == true) - ? animateRectSeries( - canvas, - _segmentProperties.seriesRenderer, - paint, - segmentRect, - _segmentProperties.currentPoint!.yValue, - animationFactor, - _segmentProperties.oldPoint?.region ?? _segmentProperties.oldRegion, - _segmentProperties.oldPoint?.yValue, - _segmentProperties.oldSeriesVisible) - : canvas.drawRRect(segmentRect, paint); - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index b831b1163..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart +++ /dev/null @@ -1,510 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; - -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for box and whisker series. -/// -/// 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. -class BoxAndWhiskerSegment extends ChartSegment { - late double _maxY, - _lowerY, - _upperY, - _centerMax, - _centerMin, - _minY, - _centersY, - _topLineY, - _bottomLineY, - _medianX, - _medianY; - - late Paint _meanPaint; - - late bool _isTransposed; - - late ChartLocation _centerMinPoint, _centerMaxPoint; - - late BoxAndWhiskerSeries _boxAndWhiskerSeries; - - late SegmentProperties _segmentProperties; - - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - - /// Get and set the paint options for box and whisker series. - if (_segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.color - : (_segmentProperties.currentPoint!.pointColorMapper ?? - _segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.currentPoint!.region!, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis); - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the box plot series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the box plot series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.strokeWidth!; - _meanPaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.strokeWidth!; - if (_segmentProperties.series.borderGradient != null) { - strokePaint!.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!); - _meanPaint.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!); - } else { - strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.strokeColor!; - _meanPaint.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.strokeColor!; - } - _segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - _segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - _setSegmentProperties(); - _boxAndWhiskerSeries = - _segmentProperties.series as BoxAndWhiskerSeries; - _segmentProperties.x = _segmentProperties.max = double.nan; - _isTransposed = - SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis; - _segmentProperties.minPoint = - _segmentProperties.currentPoint!.minimumPoint!; - _segmentProperties.maxPoint = - _segmentProperties.currentPoint!.maximumPoint!; - _centerMinPoint = _segmentProperties.currentPoint!.centerMinimumPoint!; - _centerMaxPoint = _segmentProperties.currentPoint!.centerMaximumPoint!; - _segmentProperties.x = _segmentProperties.minPoint.x; - _segmentProperties.min = _segmentProperties.minPoint.y; - _segmentProperties.max = _segmentProperties.maxPoint.y; - _centerMax = _centerMaxPoint.x; - _maxY = _centerMaxPoint.y; - _centerMin = _centerMinPoint.x; - _minY = _centerMinPoint.y; - _segmentProperties.lowerX = - _segmentProperties.currentPoint!.lowerQuartilePoint!.x; - _lowerY = _segmentProperties.currentPoint!.lowerQuartilePoint!.y; - _segmentProperties.upperX = - _segmentProperties.currentPoint!.upperQuartilePoint!.x; - _upperY = _segmentProperties.currentPoint!.upperQuartilePoint!.y; - _medianX = _segmentProperties.currentPoint!.medianPoint!.x; - _medianY = _segmentProperties.currentPoint!.medianPoint!.y; - - _centersY = (_lowerY > _upperY) - ? (_upperY + ((_upperY - _lowerY).abs() / 2)) - : (_lowerY + ((_lowerY - _upperY).abs() / 2)); - - _segmentProperties.topRectY = _centersY - ((_centersY - _upperY).abs() * 1); - _segmentProperties.bottomRectY = - _centersY + ((_centersY - _lowerY).abs() * 1); - } - - /// To draw rect path of box and whisker segments. - void _drawRectPath() { - _segmentProperties.path.moveTo( - !_isTransposed - ? _segmentProperties.lowerX - : _segmentProperties.topRectY, - !_isTransposed ? _segmentProperties.topRectY : _upperY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.upperX - : _segmentProperties.topRectY, - !_isTransposed ? _segmentProperties.topRectY : _lowerY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.upperX - : _segmentProperties.bottomRectY, - !_isTransposed ? _segmentProperties.bottomRectY : _lowerY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.lowerX - : _segmentProperties.bottomRectY, - !_isTransposed ? _segmentProperties.bottomRectY : _upperY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.lowerX - : _segmentProperties.topRectY, - !_isTransposed ? _segmentProperties.topRectY : _upperY); - _segmentProperties.path.close(); - } - - /// To draw line path of box and whisker segments. - void _drawLine(Canvas canvas) { - canvas.drawLine(Offset(_segmentProperties.lowerX, _topLineY), - Offset(_segmentProperties.upperX, _topLineY), strokePaint!); - canvas.drawLine(Offset(_centerMax, _segmentProperties.topRectY), - Offset(_centerMax, _topLineY), strokePaint!); - canvas.drawLine(Offset(_segmentProperties.lowerX, _medianY), - Offset(_segmentProperties.upperX, _medianY), strokePaint!); - canvas.drawLine(Offset(_centerMax, _segmentProperties.bottomRectY), - Offset(_centerMax, _bottomLineY), strokePaint!); - canvas.drawLine(Offset(_segmentProperties.lowerX, _bottomLineY), - Offset(_segmentProperties.upperX, _bottomLineY), strokePaint!); - } - - /// To draw mean line path of box and whisker segments. - void _drawMeanLine( - Canvas canvas, Offset position, Size size, bool isTransposed) { - final double x = !isTransposed ? position.dx : position.dy; - final double y = !isTransposed ? position.dy : position.dx; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - if (_segmentProperties.series.animationDuration <= 0 == true || - animationFactor >= - seriesRendererDetails.stateProperties.seriesDurationFactor) { - /// `0.15` is animation duration of mean point value, as like marker. - final double opacity = (animationFactor - - seriesRendererDetails.stateProperties.seriesDurationFactor) * - (1 / 0.15); - _meanPaint.color = Color.fromRGBO(_meanPaint.color.red, - _meanPaint.color.green, _meanPaint.color.blue, opacity); - canvas.drawLine(Offset(x + size.width / 2, y - size.height / 2), - Offset(x - size.width / 2, y + size.height / 2), _meanPaint); - canvas.drawLine(Offset(x + size.width / 2, y + size.height / 2), - Offset(x - size.width / 2, y - size.height / 2), _meanPaint); - } - } - - /// To draw line path of box and whisker segments. - void _drawFillLine(Canvas canvas) { - final bool isOpen = _segmentProperties.currentPoint!.lowerQuartile! > - _segmentProperties.currentPoint!.upperQuartile!; - canvas.drawLine( - Offset(_segmentProperties.topRectY, _maxY), - Offset( - _segmentProperties.topRectY + - ((isOpen - ? (_segmentProperties.lowerX - _centerMax) - : (_segmentProperties.upperX - _centerMax)) - .abs() * - animationFactor), - _maxY), - strokePaint!); - canvas.drawLine( - Offset(_segmentProperties.bottomRectY, _maxY), - Offset( - _segmentProperties.bottomRectY - - ((isOpen - ? (_segmentProperties.upperX - _centerMin) - : (_segmentProperties.lowerX - _centerMin)) - .abs() * - animationFactor), - _maxY), - strokePaint!); - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - if (fillPaint != null && seriesRendererDetails.reAnimate == true || - (!(seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - _segmentProperties.oldSeriesRenderer != null && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false))) { - _segmentProperties.path = Path(); - if (!_isTransposed && - _segmentProperties.currentPoint!.lowerQuartile! > - _segmentProperties.currentPoint!.upperQuartile! == - true) { - final double temp = _upperY; - _upperY = _lowerY; - _lowerY = temp; - } - - if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - true) { - animationFactor = 1; - } - if (_lowerY > _upperY) { - _centersY = _upperY + ((_upperY - _lowerY).abs() / 2); - _segmentProperties.topRectY = - _centersY - ((_centersY - _upperY).abs() * animationFactor); - _segmentProperties.bottomRectY = - _centersY + ((_centersY - _lowerY).abs() * animationFactor); - } else { - _centersY = _lowerY + ((_lowerY - _upperY).abs() / 2); - _segmentProperties.topRectY = - _centersY - ((_centersY - _upperY).abs() * animationFactor); - _segmentProperties.bottomRectY = - _centersY + ((_centersY - _lowerY).abs() * animationFactor); - } - _topLineY = _segmentProperties.topRectY - - ((_segmentProperties.topRectY - _maxY).abs() * animationFactor); - _bottomLineY = _segmentProperties.bottomRectY + - ((_segmentProperties.bottomRectY - _minY).abs() * animationFactor); - - _bottomLineY = _minY < _lowerY - ? _segmentProperties.bottomRectY + - ((_upperY - _maxY).abs() * animationFactor) - : _bottomLineY; - - _topLineY = _maxY > _upperY - ? _segmentProperties.topRectY - - ((_lowerY - _minY).abs() * animationFactor) - : _topLineY; - - if (_isTransposed) { - if (_segmentProperties.lowerX > _segmentProperties.upperX == true) { - _centersY = _segmentProperties.upperX + - ((_segmentProperties.lowerX - _segmentProperties.upperX).abs() / - 2); - _segmentProperties.topRectY = _centersY + - ((_centersY - _segmentProperties.lowerX).abs() * animationFactor); - _segmentProperties.bottomRectY = _centersY - - ((_centersY - _segmentProperties.upperX).abs() * animationFactor); - } else { - _centersY = _segmentProperties.lowerX + - (_segmentProperties.upperX - _segmentProperties.lowerX).abs() / 2; - _segmentProperties.topRectY = _centersY + - ((_centersY - _segmentProperties.upperX).abs() * animationFactor); - _segmentProperties.bottomRectY = _centersY - - ((_centersY - _segmentProperties.lowerX).abs() * animationFactor); - } - _segmentProperties.path.moveTo(_centerMax, _upperY); - _segmentProperties.path.lineTo(_centerMax, _lowerY); - if (_centerMax < _segmentProperties.upperX) { - _segmentProperties.path.moveTo(_segmentProperties.bottomRectY, _maxY); - _segmentProperties.path.lineTo( - _segmentProperties.topRectY - - ((_segmentProperties.lowerX - _centerMax).abs() * - animationFactor), - _maxY); - } else { - _segmentProperties.path.moveTo(_segmentProperties.topRectY, _maxY); - _segmentProperties.path.lineTo( - _segmentProperties.topRectY + - ((_segmentProperties.upperX - _centerMax).abs() * - animationFactor), - _maxY); - } - _segmentProperties.path.moveTo(_medianX, _upperY); - _segmentProperties.path.lineTo(_medianX, _lowerY); - if (_centerMin > _segmentProperties.lowerX) { - _segmentProperties.path.moveTo(_segmentProperties.topRectY, _maxY); - _segmentProperties.path.lineTo( - _segmentProperties.bottomRectY + - ((_segmentProperties.upperX - _centerMin).abs() * - animationFactor), - _maxY); - } else { - _segmentProperties.path.moveTo(_segmentProperties.bottomRectY, _maxY); - _segmentProperties.path.lineTo( - _segmentProperties.bottomRectY - - ((_segmentProperties.lowerX - _centerMin).abs() * - animationFactor), - _maxY); - } - _segmentProperties.path.moveTo(_centerMin, _upperY); - _segmentProperties.path.lineTo(_centerMin, _lowerY); - if (_boxAndWhiskerSeries.showMean) { - _drawMeanLine( - canvas, - Offset(_segmentProperties.currentPoint!.centerMeanPoint!.y, - _segmentProperties.currentPoint!.centerMeanPoint!.x), - Size(_segmentProperties.series.markerSettings.width, - _segmentProperties.series.markerSettings.height), - _isTransposed); - } - - _segmentProperties.lowerX == _segmentProperties.upperX - ? canvas.drawLine(Offset(_segmentProperties.lowerX, _lowerY), - Offset(_segmentProperties.upperX, _upperY), fillPaint!) - : _drawRectPath(); - } else { - if (_segmentProperties.currentPoint!.lowerQuartile! > - _segmentProperties.currentPoint!.upperQuartile! == - true) { - final double temp = _upperY; - _upperY = _lowerY; - _lowerY = temp; - } - _drawLine(canvas); - if (_boxAndWhiskerSeries.showMean) { - _drawMeanLine( - canvas, - Offset(_segmentProperties.currentPoint!.centerMeanPoint!.x, - _segmentProperties.currentPoint!.centerMeanPoint!.y), - Size(_segmentProperties.series.markerSettings.width, - _segmentProperties.series.markerSettings.height), - _isTransposed); - } - _lowerY == _upperY - ? canvas.drawLine(Offset(_segmentProperties.lowerX, _lowerY), - Offset(_segmentProperties.upperX, _upperY), fillPaint!) - : _drawRectPath(); - } - - if (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0 && - _segmentProperties.series.animationDuration <= 0 == true) { - canvas.drawPath(_segmentProperties.path, fillPaint!); - drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _segmentProperties.path); - } else { - canvas.drawPath(_segmentProperties.path, fillPaint!); - canvas.drawPath(_segmentProperties.path, strokePaint!); - } - if (fillPaint!.style == PaintingStyle.fill) { - if (_isTransposed) { - if (_segmentProperties.currentPoint!.lowerQuartile! > - _segmentProperties.currentPoint!.upperQuartile! == - true) { - _drawFillLine(canvas); - } - if (_boxAndWhiskerSeries.showMean) { - _drawMeanLine( - canvas, - Offset(_segmentProperties.currentPoint!.centerMeanPoint!.y, - _segmentProperties.currentPoint!.centerMeanPoint!.x), - Size(_segmentProperties.series.markerSettings.width, - _segmentProperties.series.markerSettings.height), - _isTransposed); - } - } else { - _drawLine(canvas); - if (_boxAndWhiskerSeries.showMean) { - _drawMeanLine( - canvas, - Offset(_segmentProperties.currentPoint!.centerMeanPoint!.x, - _segmentProperties.currentPoint!.centerMeanPoint!.y), - Size(_segmentProperties.series.markerSettings.width, - _segmentProperties.series.markerSettings.height), - _isTransposed); - } - } - } - } else if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false) { - final BoxAndWhiskerSegment currentSegment = seriesRendererDetails - .segments[currentSegmentIndex!] as BoxAndWhiskerSegment; - final SegmentProperties chartSegmentPropeties = - SegmentHelper.getSegmentProperties(currentSegment); - final SeriesRendererDetails oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - chartSegmentPropeties.oldSeriesRenderer!); - final BoxAndWhiskerSegment? oldSegment = - (oldSeriesRendererDetails.segments.isNotEmpty == true && - oldSeriesRendererDetails.segments[0] - is BoxAndWhiskerSegment && - oldSeriesRendererDetails.segments.length - 1 >= - currentSegmentIndex! == - true) - ? oldSeriesRendererDetails.segments[currentSegmentIndex!] - as BoxAndWhiskerSegment? - : null; - SegmentProperties? oldSegmentProperties; - if (oldSegment != null) { - oldSegmentProperties = SegmentHelper.getSegmentProperties(oldSegment); - } - - animateBoxSeries( - _boxAndWhiskerSeries.showMean, - Offset(_segmentProperties.currentPoint!.centerMeanPoint!.x, - _segmentProperties.currentPoint!.centerMeanPoint!.y), - Offset(_segmentProperties.currentPoint!.centerMeanPoint!.y, - _segmentProperties.currentPoint!.centerMeanPoint!.x), - Size(_segmentProperties.series.markerSettings.width, - _segmentProperties.series.markerSettings.height), - _segmentProperties.max, - _isTransposed, - _segmentProperties.currentPoint!.lowerQuartile!, - _segmentProperties.currentPoint!.upperQuartile!, - _minY, - _maxY, - oldSegment?._minY, - oldSegment?._maxY, - _segmentProperties.lowerX, - _lowerY, - _segmentProperties.upperX, - _upperY, - _centerMin, - _centerMax, - oldSegmentProperties?.lowerX, - oldSegment?._lowerY, - oldSegmentProperties?.upperX, - oldSegment?._upperY, - oldSegment?._centerMin, - oldSegment?._centerMax, - _medianX, - _medianY, - animationFactor, - fillPaint!, - strokePaint!, - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer)); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 7d6046695..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for bubble series. -/// -/// 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. -class BubbleSegment extends ChartSegment { - /// Center position of the bubble and size. - late double _centerX, _centerY, _radius, _size; - - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - final SegmentProperties bubbleSegmentProperties = _segmentProperties; - final bool hasPointColor = - bubbleSegmentProperties.series.pointColorMapper != null; - if (bubbleSegmentProperties.series.gradient == null) { - if (bubbleSegmentProperties.color != null) { - fillPaint = Paint() - ..color = bubbleSegmentProperties.currentPoint!.isEmpty == true - ? bubbleSegmentProperties.series.emptyPointSettings.color - : ((hasPointColor && - bubbleSegmentProperties.currentPoint!.pointColorMapper != - null) - ? bubbleSegmentProperties.currentPoint!.pointColorMapper! - : bubbleSegmentProperties.color!) - ..style = PaintingStyle.fill; - } - } else { - fillPaint = getLinearGradientPaint( - bubbleSegmentProperties.series.gradient!, - bubbleSegmentProperties.currentPoint!.region!, - SeriesHelper.getSeriesRendererDetails( - bubbleSegmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis); - } - assert(bubbleSegmentProperties.series.opacity >= 0, - 'The opacity value of the bubble series should be greater than or equal to 0.'); - assert(bubbleSegmentProperties.series.opacity <= 1, - 'The opacity value of the bubble series should be less than or equal to 1.'); - if (fillPaint?.color != null) { - fillPaint!.color = (bubbleSegmentProperties.series.opacity < 1 && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(bubbleSegmentProperties.series.opacity) - : fillPaint!.color; - } - bubbleSegmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final SegmentProperties bubbleSegmentProperties = _segmentProperties; - final Paint _strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = bubbleSegmentProperties.currentPoint!.isEmpty == true - ? bubbleSegmentProperties.series.emptyPointSettings.borderWidth - : bubbleSegmentProperties.strokeWidth!; - bubbleSegmentProperties.series.borderGradient != null - ? _strokePaint.shader = bubbleSegmentProperties.series.borderGradient! - .createShader(bubbleSegmentProperties.currentPoint!.region!) - : _strokePaint.color = - bubbleSegmentProperties.currentPoint!.isEmpty == true - ? bubbleSegmentProperties.series.emptyPointSettings.borderColor - : bubbleSegmentProperties.strokeColor!; - bubbleSegmentProperties.series.borderWidth == 0 - ? _strokePaint.color = Colors.transparent - : _strokePaint.color; - bubbleSegmentProperties.defaultStrokeColor = _strokePaint; - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - _setSegmentProperties(); - - final SegmentProperties bubbleSegmentProperties = _segmentProperties; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - bubbleSegmentProperties.seriesRenderer); - _centerX = _centerY = double.nan; - final Rect rect = calculatePlotOffset( - seriesRendererDetails.stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final ChartLocation location = calculatePoint( - bubbleSegmentProperties.currentPoint!.xValue, - bubbleSegmentProperties.currentPoint!.yValue, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - bubbleSegmentProperties.series, - rect); - _centerX = location.x; - _centerY = location.y; - if (bubbleSegmentProperties.seriesRenderer is BubbleSeriesRenderer) - _radius = calculateBubbleRadius( - seriesRendererDetails, - bubbleSegmentProperties.series, - bubbleSegmentProperties.currentPoint!); - bubbleSegmentProperties.currentPoint!.region = Rect.fromLTRB( - location.x - 2 * _radius, - location.y - 2 * _radius, - location.x + 2 * _radius, - location.y + 2 * _radius); - _size = _radius = bubbleSegmentProperties.currentPoint!.region!.width / 2; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - - final SegmentProperties bubbleSegmentProperties = _segmentProperties; - bubbleSegmentProperties.segmentRect = RRect.fromRectAndRadius( - bubbleSegmentProperties.currentPoint!.region!, Radius.zero); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - bubbleSegmentProperties.seriesRenderer); - if (seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == true && - seriesRendererDetails.reAnimate == false && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - seriesRendererDetails.stateProperties.oldSeriesRenderers.isNotEmpty == - true && - bubbleSegmentProperties.oldSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails( - bubbleSegmentProperties.oldSeriesRenderer!) - .segments - .isNotEmpty == - true && - SeriesHelper.getSeriesRendererDetails( - bubbleSegmentProperties.oldSeriesRenderer!) - .segments[0] is BubbleSegment && - bubbleSegmentProperties.series.animationDuration > 0 && - bubbleSegmentProperties.oldPoint != null) { - final BubbleSegment currentSegment = - seriesRendererDetails.segments[currentSegmentIndex!] as BubbleSegment; - final SegmentProperties seriesSegmentProperties = - SegmentHelper.getSegmentProperties(currentSegment); - final SeriesRendererDetails oldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - seriesSegmentProperties.oldSeriesRenderer!); - final BubbleSegment? oldSegment = - (oldSeriesDetails.segments.length - 1 >= currentSegmentIndex! == true) - ? oldSeriesDetails.segments[currentSegmentIndex!] - as BubbleSegment? - : null; - animateBubbleSeries( - canvas, - _centerX, - _centerY, - oldSegment?._centerX, - oldSegment?._centerY, - oldSegment?._size, - animationFactor, - _radius, - strokePaint!, - fillPaint!, - bubbleSegmentProperties.seriesRenderer); - } else { - canvas.drawCircle( - Offset(_centerX, _centerY), _radius * animationFactor, fillPaint!); - canvas.drawCircle( - Offset(_centerX, _centerY), _radius * animationFactor, strokePaint!); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 61ddff7ea..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart +++ /dev/null @@ -1,437 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/financial_series_base.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for candle series. -/// -/// 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. -/// -class CandleSegment extends ChartSegment { - // ignore: unused_field - late double _highY, - _centerHigh, - _centerLow, - _lowY, - _centersY, - _topLineY, - _bottomLineY; - // ignore: unused_field - late Path _linePath; - late bool _isTransposed, _showSameValue; - late ChartLocation _centerLowPoint, _centerHighPoint; - - late CandleSegment _currentSegment; - CandleSegment? _oldSegment; - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty != null && - _segmentProperties.currentPoint!.isEmpty! == true - ? _segmentProperties.series.emptyPointSettings.color - : (_segmentProperties.currentPoint!.pointColorMapper ?? - _segmentProperties.color!); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the candle series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the candle series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - fillPaint!.strokeWidth = _segmentProperties.strokeWidth!; - fillPaint!.style = _segmentProperties.isSolid == true - ? PaintingStyle.fill - : PaintingStyle.stroke; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final SegmentProperties candleSegmentProperties = _segmentProperties; - final Paint _strokePaint = Paint(); - if (candleSegmentProperties.strokeColor != null) { - _strokePaint.color = _segmentProperties.pointColorMapper ?? - candleSegmentProperties.strokeColor!; - _strokePaint.color = (candleSegmentProperties.series.opacity < 1 && - _strokePaint.color != Colors.transparent) - ? _strokePaint.color - .withOpacity(candleSegmentProperties.series.opacity) - : _strokePaint.color; - } - _strokePaint.strokeWidth = candleSegmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.round; - candleSegmentProperties.defaultStrokeColor = _strokePaint; - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - _setSegmentProperties(); - _segmentProperties.isBull = _segmentProperties.currentPoint!.open < - _segmentProperties.currentPoint!.close; - _segmentProperties.x = - _segmentProperties.high = _segmentProperties.low = double.nan; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - _isTransposed = seriesRendererDetails.stateProperties.requireInvertedAxis; - _segmentProperties.lowPoint = _segmentProperties.currentPoint!.lowPoint!; - _segmentProperties.highPoint = _segmentProperties.currentPoint!.highPoint!; - _centerLowPoint = _segmentProperties.currentPoint!.centerLowPoint!; - _centerHighPoint = _segmentProperties.currentPoint!.centerHighPoint!; - _segmentProperties.x = _segmentProperties.lowPoint.x; - _segmentProperties.low = _segmentProperties.lowPoint.y; - _segmentProperties.high = _segmentProperties.highPoint.y; - _centerHigh = _centerHighPoint.x; - _highY = _centerHighPoint.y; - _centerLow = _centerLowPoint.x; - _lowY = _centerLowPoint.y; - _segmentProperties.openX = _segmentProperties.currentPoint!.openPoint!.x; - _segmentProperties.openY = _segmentProperties.currentPoint!.openPoint!.y; - _segmentProperties.closeX = _segmentProperties.currentPoint!.closePoint!.x; - _segmentProperties.closeY = _segmentProperties.currentPoint!.closePoint!.y; - - _showSameValue = - (_segmentProperties.series as FinancialSeriesBase) - .showIndicationForSameValues == - true && - (seriesRendererDetails.stateProperties.requireInvertedAxis == false - ? _centerHighPoint.y == _centerLowPoint.y - : _centerHighPoint.x == _centerLowPoint.x); - - _segmentProperties.x = _segmentProperties.lowPoint.x = - (_showSameValue && _isTransposed) - ? _segmentProperties.lowPoint.x - 2 - : _segmentProperties.lowPoint.x; - _segmentProperties.highPoint.x = (_showSameValue && _isTransposed) - ? _segmentProperties.highPoint.x + 2 - : _segmentProperties.highPoint.x; - _segmentProperties.low = _segmentProperties.lowPoint.y = - (_showSameValue && !_isTransposed) - ? _segmentProperties.lowPoint.y - 2 - : _segmentProperties.lowPoint.y; - _segmentProperties.high = _segmentProperties.highPoint.y = - (_showSameValue && !_isTransposed) - ? _segmentProperties.highPoint.y + 2 - : _segmentProperties.highPoint.y; - _centerHigh = _centerHighPoint.x = (_showSameValue && _isTransposed) - ? _centerHighPoint.x + 2 - : _centerHighPoint.x; - _highY = _centerHighPoint.y = (_showSameValue && !_isTransposed) - ? _centerHighPoint.y + 2 - : _centerHighPoint.y; - _centerLow = _centerLowPoint.x = (_showSameValue && _isTransposed) - ? _centerLowPoint.x - 2 - : _centerLowPoint.x; - _lowY = _centerLowPoint.y = (_showSameValue && !_isTransposed) - ? _centerLowPoint.y - 2 - : _centerLowPoint.y; - _centersY = _segmentProperties.closeY + - ((_segmentProperties.closeY - _segmentProperties.openY).abs() / 2); - _segmentProperties.topRectY = - _centersY - ((_centersY - _segmentProperties.closeY).abs() * 1); - _segmentProperties.bottomRectY = - _centersY + ((_centersY - _segmentProperties.openY).abs() * 1); - } - - /// To draw rect path of candle segments. - void _drawRectPath() { - _segmentProperties.path.moveTo( - !_isTransposed ? _segmentProperties.openX : _segmentProperties.topRectY, - !_isTransposed - ? _segmentProperties.topRectY - : _segmentProperties.closeY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.closeX - : _segmentProperties.topRectY, - !_isTransposed - ? _segmentProperties.topRectY - : _segmentProperties.openY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.closeX - : _segmentProperties.bottomRectY, - !_isTransposed - ? _segmentProperties.bottomRectY - : _segmentProperties.openY); - _segmentProperties.path.lineTo( - !_isTransposed - ? _segmentProperties.openX - : _segmentProperties.bottomRectY, - !_isTransposed - ? _segmentProperties.bottomRectY - : _segmentProperties.closeY); - _segmentProperties.path.lineTo( - !_isTransposed ? _segmentProperties.openX : _segmentProperties.topRectY, - !_isTransposed - ? _segmentProperties.topRectY - : _segmentProperties.closeY); - _segmentProperties.path.close(); - } - - void _drawLine(Canvas canvas) { - canvas.drawLine(Offset(_centerHigh, _segmentProperties.topRectY), - Offset(_centerHigh, _topLineY), fillPaint!); - canvas.drawLine(Offset(_centerHigh, _segmentProperties.bottomRectY), - Offset(_centerHigh, _bottomLineY), fillPaint!); - } - - void _drawFillLine(Canvas canvas) { - final bool isOpen = _segmentProperties.currentPoint!.open > - _segmentProperties.currentPoint!.close; - canvas.drawLine( - Offset(_segmentProperties.topRectY, _highY), - Offset( - _segmentProperties.topRectY + - ((isOpen - ? (_segmentProperties.openX - _centerHigh) - : (_segmentProperties.closeX - _centerHigh)) - .abs() * - animationFactor), - _highY), - fillPaint!); - canvas.drawLine( - Offset(_segmentProperties.bottomRectY, _highY), - Offset( - _segmentProperties.bottomRectY - - ((isOpen - ? (_segmentProperties.closeX - _centerLow) - : (_segmentProperties.openX - _centerLow)) - .abs() * - animationFactor), - _highY), - fillPaint!); - } - - void _calculateCandlePositions(num openX, num closeX) { - _centersY = closeX + ((openX - closeX).abs() / 2); - _segmentProperties.topRectY = - _centersY + ((_centersY - openX).abs() * animationFactor); - _segmentProperties.bottomRectY = - _centersY - ((_centersY - closeX).abs() * animationFactor); - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - if (fillPaint != null && - (seriesRendererDetails.reAnimate == true || - !(seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false))) { - _segmentProperties.path = Path(); - _linePath = Path(); - - if (!_isTransposed && - _segmentProperties.currentPoint!.open > - _segmentProperties.currentPoint!.close == - true) { - final double temp = _segmentProperties.closeY; - _segmentProperties.closeY = _segmentProperties.openY; - _segmentProperties.openY = temp; - } - - if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - true) { - animationFactor = 1; - } - _centersY = _segmentProperties.closeY + - ((_segmentProperties.closeY - _segmentProperties.openY).abs() / 2); - _segmentProperties.topRectY = _centersY - - ((_centersY - _segmentProperties.closeY).abs() * animationFactor); - _topLineY = _segmentProperties.topRectY - - ((_segmentProperties.topRectY - _highY).abs() * animationFactor); - _segmentProperties.bottomRectY = _centersY + - ((_centersY - _segmentProperties.openY).abs() * animationFactor); - _bottomLineY = _segmentProperties.bottomRectY + - ((_segmentProperties.bottomRectY - _lowY).abs() * animationFactor); - - _bottomLineY = _lowY < _segmentProperties.openY - ? _segmentProperties.bottomRectY - - ((_segmentProperties.openY - _lowY).abs() * animationFactor) - : _bottomLineY; - - _topLineY = _highY > _segmentProperties.closeY - ? _segmentProperties.topRectY + - ((_segmentProperties.closeY - _highY).abs() * animationFactor) - : _topLineY; - - if (_isTransposed) { - _segmentProperties.currentPoint!.open > - _segmentProperties.currentPoint!.close == - true - ? _calculateCandlePositions( - _segmentProperties.openX, _segmentProperties.closeX) - : _calculateCandlePositions( - _segmentProperties.closeX, _segmentProperties.openX); - - if (_showSameValue) { - canvas.drawLine(Offset(_centerHighPoint.x, _centerHighPoint.y), - Offset(_centerLowPoint.x, _centerHighPoint.y), fillPaint!); - } else { - _segmentProperties.path.moveTo(_segmentProperties.topRectY, _highY); - _centerHigh < _segmentProperties.closeX - ? _segmentProperties.path.lineTo( - _segmentProperties.topRectY - - ((_segmentProperties.closeX - _centerHigh).abs() * - animationFactor), - _highY) - : _segmentProperties.path.lineTo( - _segmentProperties.topRectY + - ((_segmentProperties.closeX - _centerHigh).abs() * - animationFactor), - _highY); - _segmentProperties.path - .moveTo(_segmentProperties.bottomRectY, _highY); - _centerLow > _segmentProperties.openX - ? _segmentProperties.path.lineTo( - _segmentProperties.bottomRectY + - ((_segmentProperties.openX - _centerLow).abs() * - animationFactor), - _highY) - : _segmentProperties.path.lineTo( - _segmentProperties.bottomRectY - - ((_segmentProperties.openX - _centerLow).abs() * - animationFactor), - _highY); - _linePath = _segmentProperties.path; - } - _segmentProperties.openX == _segmentProperties.closeX - ? canvas.drawLine( - Offset(_segmentProperties.openX, _segmentProperties.openY), - Offset(_segmentProperties.closeX, _segmentProperties.closeY), - fillPaint!) - : _drawRectPath(); - } else { - _showSameValue - ? canvas.drawLine( - Offset(_centerHighPoint.x, _segmentProperties.highPoint.y), - Offset(_centerHighPoint.x, _segmentProperties.lowPoint.y), - fillPaint!) - : _drawLine(canvas); - - _segmentProperties.openY == _segmentProperties.closeY - ? canvas.drawLine( - Offset(_segmentProperties.openX, _segmentProperties.openY), - Offset(_segmentProperties.closeX, _segmentProperties.closeY), - fillPaint!) - : _drawRectPath(); - } - - if (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0 && - fillPaint!.style != PaintingStyle.fill && - _segmentProperties.series.animationDuration <= 0 == true) { - drawDashedLine(canvas, _segmentProperties.series.dashArray, fillPaint!, - _segmentProperties.path); - } else { - canvas.drawPath(_segmentProperties.path, fillPaint!); - if (fillPaint!.style == PaintingStyle.fill) { - _isTransposed - ? _showSameValue - ? canvas.drawLine( - Offset(_centerHighPoint.x, _centerHighPoint.y), - Offset(_centerLowPoint.x, _centerHighPoint.y), - fillPaint!) - : _drawFillLine(canvas) - : _showSameValue - ? canvas.drawLine( - Offset( - _centerHighPoint.x, _segmentProperties.highPoint.y), - Offset(_centerHighPoint.x, _segmentProperties.lowPoint.y), - fillPaint!) - : _drawLine(canvas); - } - } - } else if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false) { - _currentSegment = - seriesRendererDetails.segments[currentSegmentIndex!] as CandleSegment; - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(_currentSegment); - SegmentProperties? oldSegmentProperties; - final SeriesRendererDetails? oldRendererDetails = - currentSegmentProperties.oldSeriesRenderer != null - ? SeriesHelper.getSeriesRendererDetails( - currentSegmentProperties.oldSeriesRenderer!) - : null; - _oldSegment = seriesRendererDetails.reAnimate == false && - (oldRendererDetails != null && - oldRendererDetails.segments.isNotEmpty == true && - oldRendererDetails.segments[0] is CandleSegment && - oldRendererDetails.segments.length - 1 >= - currentSegmentIndex! == - true) - ? oldRendererDetails.segments[currentSegmentIndex!] as CandleSegment? - : null; - if (_oldSegment != null) { - oldSegmentProperties = SegmentHelper.getSegmentProperties(_oldSegment!); - } - animateCandleSeries( - _showSameValue, - _segmentProperties.high, - _isTransposed, - _segmentProperties.currentPoint!.open!.toDouble(), - _segmentProperties.currentPoint!.close!.toDouble(), - _lowY, - _highY, - _oldSegment?._lowY, - _oldSegment?._highY, - _segmentProperties.openX, - _segmentProperties.openY, - _segmentProperties.closeX, - _segmentProperties.closeY, - _centerLow, - _centerHigh, - oldSegmentProperties?.openX, - oldSegmentProperties?.openY, - oldSegmentProperties?.closeX, - oldSegmentProperties?.closeY, - _oldSegment?._centerLow, - _oldSegment?._centerHigh, - animationFactor, - fillPaint!, - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer)); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 34012f3e0..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; - -/// Creates the segments for chart series. -/// -/// It has the public method and properties to customize the segment in the chart series, User can customize -/// the calculation of the segment points by using the method [calculateSegmentPoints]. It has the property to -/// store the old value of the series to support dynamic animation. -/// -/// Provides the public properties color, stroke color, fill paint, stroke paint, series and old series to customize and dynamically -/// change each segment in the chart. -/// -abstract class ChartSegment { - /// Gets the color of the series. - Paint getFillPaint(); - - /// Gets the border color of the series. - Paint getStrokePaint(); - - /// Calculates the rendering bounds of a segment. - void calculateSegmentPoints(); - - /// Draws segment in series bounds. - void onPaint(Canvas canvas); - - /// Fill paint of the segment. - Paint? fillPaint; - - /// Stroke paint of the segment. - Paint? strokePaint; - - /// Animation factor value. - late double animationFactor; - - /// Current point offset value. - List points = []; - - /// Current index value. - int? currentSegmentIndex; - - /// Represents the segment properties. - SegmentProperties? _segmentProperties; - - /// To dispose the objects. - void dispose() { - _segmentProperties = null; - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the private fields of chart segment -class SegmentHelper { - /// Method to get the segment properties of corresponding chart segment. - static SegmentProperties getSegmentProperties(ChartSegment chartSegment) => - chartSegment._segmentProperties!; - - /// Method to set the segment properties of corresponding chart segment. - static void setSegmentProperties( - ChartSegment segment, SegmentProperties segmentProperties) => - segment._segmentProperties = segmentProperties; -} 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 deleted file mode 100644 index 5b2039da0..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../chart_series/series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for column series. -/// -/// 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. -/// -class ColumnSegment extends ChartSegment { - /// Rectangle of the segment. This could be used to render the segment while overriding this segment. - late RRect segmentRect; - - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - - /// Get and set the paint options for column series. - if (_segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.color - : (_segmentProperties.currentPoint!.pointColorMapper ?? - _segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.currentPoint!.region!, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis); - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the column series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the column series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.strokeWidth!; - _segmentProperties.series.borderGradient != null - ? strokePaint!.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!) - : strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.strokeColor!; - _segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - _segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final ColumnSeries columnSeries = - _segmentProperties.series as ColumnSeries; - - if (_segmentProperties.trackerFillPaint != null && - columnSeries.isTrackVisible) { - _drawSegmentRect(canvas, _segmentProperties.trackRect, - _segmentProperties.trackerFillPaint!); - } - - if (_segmentProperties.trackerStrokePaint != null && - columnSeries.isTrackVisible) { - _drawSegmentRect(canvas, _segmentProperties.trackRect, - _segmentProperties.trackerStrokePaint!); - } - - if (fillPaint != null) { - _drawSegmentRect(canvas, segmentRect, fillPaint!); - } - if (strokePaint != null) { - (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0) - ? drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _segmentProperties.path) - : _drawSegmentRect(canvas, segmentRect, strokePaint!); - } - } - - /// To draw segment rect for column segment. - void _drawSegmentRect(Canvas canvas, RRect segmentRect, Paint paint) { - (_segmentProperties.series.animationDuration > 0 == true) - ? animateRectSeries( - canvas, - _segmentProperties.seriesRenderer, - paint, - segmentRect, - _segmentProperties.currentPoint!.yValue, - animationFactor, - _segmentProperties.oldPoint != null - ? _segmentProperties.oldPoint!.region - : _segmentProperties.oldRegion, - _segmentProperties.oldPoint?.yValue, - _segmentProperties.oldSeriesVisible) - : canvas.drawRRect(segmentRect, paint); - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart deleted file mode 100644 index 6aca689de..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart +++ /dev/null @@ -1,255 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../../../charts.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import 'chart_segment.dart'; - -/// Segment class for error bar. -class ErrorBarSegment extends ChartSegment { - final double _effectiveAnimationFactor = 0.05; - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - late double _errorBarMidPointX; - late double _errorBarMidPointY; - late double _capPointValue; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - strokePaint = Paint() - ..color = _segmentProperties.color! - .withOpacity(_segmentProperties.series.opacity) - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.series.width!; - _segmentProperties.defaultStrokeColor = strokePaint; - setShader(_segmentProperties, strokePaint!); - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final ErrorBarSeries errorBarSeries = - _segmentProperties.series as ErrorBarSeries; - _animateErrorBar( - canvas, - strokePaint!, - _segmentProperties.currentPoint!, - animationFactor, - _segmentProperties.seriesRenderer, - errorBarSeries.capLength!, - errorBarSeries.dashArray); - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } - - /// To animate error bar. - void _animateErrorBar( - Canvas canvas, - Paint errorBarPaint, - CartesianChartPoint point, - double animationFactor, - CartesianSeriesRenderer seriesRenderer, - double capLength, - List dashArray) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (animationFactor != 0) { - animationFactor = (!seriesRendererDetails.reAnimate && - !seriesRendererDetails - .stateProperties.renderingDetails.initialRender! && - seriesRendererDetails.series.key != null && - seriesRendererDetails.stateProperties.oldSeriesKeys - .contains(seriesRendererDetails.series.key)) - ? 1 - : animationFactor; - - final bool isTransposedChart = seriesRendererDetails.chart.isTransposed; - _errorBarMidPointX = point.currentPoint!.x; - _errorBarMidPointY = point.currentPoint!.y; - - double animatingPoint; - if (point.verticalPositiveErrorPoint != null) { - animatingPoint = isTransposedChart - ? _errorBarMidPointX + - ((point.verticalPositiveErrorPoint!.x - _errorBarMidPointX) * - _effectiveAnimationFactor) - : _errorBarMidPointY - - ((_errorBarMidPointY - point.verticalPositiveErrorPoint!.y) * - _effectiveAnimationFactor); - - _capPointValue = animatingPoint - - ((animatingPoint - - (isTransposedChart - ? point.verticalPositiveErrorPoint!.x - : point.verticalPositiveErrorPoint!.y)) * - animationFactor); - - _verticalErrorBarRendering(canvas, errorBarPaint, _capPointValue, - animationFactor, capLength, dashArray, isTransposedChart); - } - if (point.verticalNegativeErrorPoint != null) { - animatingPoint = isTransposedChart - ? _errorBarMidPointX + - ((point.verticalNegativeErrorPoint!.x - _errorBarMidPointX) * - _effectiveAnimationFactor) - : _errorBarMidPointY + - ((point.verticalNegativeErrorPoint!.y - _errorBarMidPointY) * - _effectiveAnimationFactor); - - _capPointValue = animatingPoint + - (((isTransposedChart - ? point.verticalNegativeErrorPoint!.x - : point.verticalNegativeErrorPoint!.y) - - animatingPoint) * - animationFactor); - - _verticalErrorBarRendering(canvas, errorBarPaint, _capPointValue, - animationFactor, capLength, dashArray, isTransposedChart); - } - if (point.horizontalPositiveErrorPoint != null) { - animatingPoint = isTransposedChart - ? _errorBarMidPointY - - ((_errorBarMidPointY - point.horizontalPositiveErrorPoint!.y) * - _effectiveAnimationFactor) - : _errorBarMidPointX + - ((point.horizontalPositiveErrorPoint!.x - _errorBarMidPointX) * - _effectiveAnimationFactor); - - _capPointValue = animatingPoint + - (((isTransposedChart - ? point.horizontalPositiveErrorPoint!.y - : point.horizontalPositiveErrorPoint!.x) - - animatingPoint) * - animationFactor); - - _horizontalErrorBarRendering(canvas, errorBarPaint, _capPointValue, - animationFactor, capLength, dashArray, isTransposedChart); - } - if (point.horizontalNegativeErrorPoint != null) { - animatingPoint = isTransposedChart - ? _errorBarMidPointY + - ((point.horizontalNegativeErrorPoint!.y - _errorBarMidPointY) * - _effectiveAnimationFactor) - : _errorBarMidPointX - - ((_errorBarMidPointX - point.horizontalNegativeErrorPoint!.x) * - _effectiveAnimationFactor); - - _capPointValue = animatingPoint - - ((animatingPoint - - (isTransposedChart - ? point.horizontalNegativeErrorPoint!.y - : point.horizontalNegativeErrorPoint!.x)) * - animationFactor); - - _horizontalErrorBarRendering(canvas, errorBarPaint, _capPointValue, - animationFactor, capLength, dashArray, isTransposedChart); - } - } - } - - /// Animating vertical error bar. - void _verticalErrorBarRendering( - Canvas canvas, - Paint errorBarPaint, - double capPointValue, - double animationFactor, - double capLength, - List dashArray, - bool isTransposedChart) { - final Path verticalPath = Path(); - final Path capPath = Path(); - if (isTransposedChart) { - verticalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); - verticalPath.lineTo(capPointValue, _errorBarMidPointY); - capPath.moveTo(capPointValue, _errorBarMidPointY - (capLength / 2)); - capPath.lineTo(capPointValue, _errorBarMidPointY + (capLength / 2)); - } else { - verticalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); - verticalPath.lineTo(_errorBarMidPointX, capPointValue); - capPath.moveTo(_errorBarMidPointX - (capLength / 2), capPointValue); - capPath.lineTo(_errorBarMidPointX + (capLength / 2), capPointValue); - } - verticalPath.close(); - capPath.close(); - - // ignore: unnecessary_null_comparison - if (dashArray != null) { - // Draws vertical line of the error bar. - drawDashedLine(canvas, dashArray, errorBarPaint, verticalPath); - // Draws cap of the error bar. - drawDashedLine(canvas, dashArray, errorBarPaint, capPath); - } else { - canvas.drawPath(verticalPath, errorBarPaint); - canvas.drawPath(capPath, errorBarPaint); - } - } - - /// Animating horizontal error bar. - void _horizontalErrorBarRendering( - Canvas canvas, - Paint errorBarPaint, - double capPointValue, - double animationFactor, - double capLength, - List dashArray, - bool isTransposedChart) { - final Path horizontalPath = Path(); - final Path horizontalCapPath = Path(); - if (isTransposedChart) { - horizontalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); - horizontalPath.lineTo(_errorBarMidPointX, capPointValue); - horizontalCapPath.moveTo( - _errorBarMidPointX - (capLength / 2), capPointValue); - horizontalCapPath.lineTo( - _errorBarMidPointX + (capLength / 2), capPointValue); - } else { - horizontalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); - horizontalPath.lineTo(capPointValue, _errorBarMidPointY); - horizontalCapPath.moveTo( - capPointValue, - _errorBarMidPointY - (capLength / 2), - ); - horizontalCapPath.lineTo( - capPointValue, - _errorBarMidPointY + (capLength / 2), - ); - } - horizontalPath.close(); - horizontalCapPath.close(); - - // ignore: unnecessary_null_comparison - if (dashArray != null) { - // Draws horizontal line of the error bar. - drawDashedLine(canvas, dashArray, errorBarPaint, horizontalPath); - // Draws cap of the error bar. - drawDashedLine(canvas, dashArray, errorBarPaint, horizontalCapPath); - } else { - canvas.drawPath(horizontalPath, errorBarPaint); - canvas.drawPath(horizontalCapPath, errorBarPaint); - } - } -} 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 deleted file mode 100644 index 8f113d4d6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../chart_series/series.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for fast line series. -/// -/// 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`. -class FastLineSegment extends ChartSegment { - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - - final Paint _fillPaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the fast line series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the fast line series should be less than or equal to 1.'); - if (_segmentProperties.color != null) { - _fillPaint.color = _segmentProperties.color! - .withOpacity(_segmentProperties.series.opacity); - } - _fillPaint.style = PaintingStyle.fill; - _segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - - final Paint _strokePaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the fast line series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the fast line series should be less than or equal to 1.'); - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.strokeColor != null) { - _strokePaint.color = (_segmentProperties.series.opacity < 1 == true && - _segmentProperties.strokeColor != Colors.transparent) - ? _segmentProperties.strokeColor! - .withOpacity(_segmentProperties.series.opacity) - : _segmentProperties.strokeColor!; - } - } else { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - _strokePaint.shader = _segmentProperties.series.gradient! - .createShader(seriesRendererDetails.segmentPath!.getBounds()); - } - _strokePaint.strokeWidth = _segmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = _strokePaint; - setShader(_segmentProperties, _strokePaint); - return _strokePaint; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - // ignore: unnecessary_null_comparison - _segmentProperties.series.dashArray != null - ? drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, seriesRendererDetails.segmentPath!) - : canvas.drawPath(seriesRendererDetails.segmentPath!, strokePaint!); - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index a6a031009..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart +++ /dev/null @@ -1,200 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import '../chart_series/series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for hilo series. -/// -/// 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`. -class HiloSegment extends ChartSegment { - late double _centerX, _highX, _lowX, _centerY, _highY, _lowY; - - late bool _showSameValue, _isTransposed; - late HiloSeries _hiloSeries; - late HiloSegment _currentSegment; - HiloSegment? _oldSegment; - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - - final Paint _fillPaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the Hilo series will not accept negative numbers.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the Hilo series must be less than or equal to 1.'); - if (_segmentProperties.color != null) { - _fillPaint.color = _segmentProperties.pointColorMapper ?? - _segmentProperties.color! - .withOpacity(_segmentProperties.series.opacity); - } - _fillPaint.strokeWidth = _segmentProperties.strokeWidth!; - _fillPaint.style = PaintingStyle.fill; - _segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final Paint _strokePaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the Hilo series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the Hilo series should be less than or equal to 1.'); - if (_segmentProperties.strokeColor != null) { - _strokePaint.color = _segmentProperties.currentPoint!.isEmpty != null && - _segmentProperties.currentPoint!.isEmpty! == true - ? _segmentProperties.series.emptyPointSettings.color - : _segmentProperties.pointColorMapper ?? - _segmentProperties.strokeColor!; - _strokePaint.color = (_segmentProperties.series.opacity < 1 == true && - _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_segmentProperties.series.opacity) - : _strokePaint.color; - } - _strokePaint.strokeWidth = _segmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = _strokePaint; - setShader(_segmentProperties, _strokePaint); - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - _setSegmentProperties(); - _hiloSeries = _segmentProperties.series as HiloSeries; - _segmentProperties.x = - _segmentProperties.high = _segmentProperties.low = double.nan; - _segmentProperties.lowPoint = _segmentProperties.currentPoint!.lowPoint!; - _segmentProperties.highPoint = _segmentProperties.currentPoint!.highPoint!; - - _isTransposed = - SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis; - - _segmentProperties.x = _segmentProperties.lowPoint.x; - _segmentProperties.low = _segmentProperties.lowPoint.y; - _segmentProperties.high = _segmentProperties.highPoint.y; - - _showSameValue = _hiloSeries.showIndicationForSameValues && - (!_isTransposed - ? _segmentProperties.low == _segmentProperties.high - : _segmentProperties.lowPoint.x == _segmentProperties.highPoint.x); - - if (_showSameValue) { - if (_isTransposed) { - _segmentProperties.x = - _segmentProperties.lowPoint.x = _segmentProperties.lowPoint.x - 2; - _segmentProperties.highPoint.x = _segmentProperties.highPoint.x + 2; - } else { - _segmentProperties.low = - _segmentProperties.lowPoint.y = _segmentProperties.lowPoint.y - 2; - _segmentProperties.high = - _segmentProperties.highPoint.y = _segmentProperties.highPoint.y + 2; - } - } - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - if (_segmentProperties.series.animationDuration > 0 == true && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false) { - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - false || - seriesRendererDetails.reAnimate == true) { - if (_isTransposed) { - _lowX = _segmentProperties.lowPoint.x; - _highX = _segmentProperties.highPoint.x; - _centerX = _highX + ((_lowX - _highX) / 2); - _highX = _centerX + ((_centerX - _highX).abs() * animationFactor); - _lowX = _centerX - ((_lowX - _centerX).abs() * animationFactor); - canvas.drawLine(Offset(_lowX, _segmentProperties.lowPoint.y), - Offset(_highX, _segmentProperties.highPoint.y), strokePaint!); - } else { - _centerY = _segmentProperties.high + - ((_segmentProperties.low - _segmentProperties.high) / 2); - _highY = _centerY - - ((_centerY - _segmentProperties.high) * animationFactor); - _lowY = _centerY + - ((_segmentProperties.low - _centerY) * animationFactor); - canvas.drawLine(Offset(_segmentProperties.lowPoint.x, _highY), - Offset(_segmentProperties.highPoint.x, _lowY), strokePaint!); - } - } else { - _currentSegment = - seriesRendererDetails.segments[currentSegmentIndex!] as HiloSegment; - final SeriesRendererDetails? oldSeriesDetails = - _currentSegment._segmentProperties.oldSeriesRenderer != null - ? SeriesHelper.getSeriesRendererDetails( - _currentSegment._segmentProperties.oldSeriesRenderer!) - : null; - _oldSegment = seriesRendererDetails.reAnimate == false && - (oldSeriesDetails != null && - oldSeriesDetails.segments.isNotEmpty == true && - oldSeriesDetails.segments[0] is HiloSegment && - oldSeriesDetails.segments.length - 1 >= - currentSegmentIndex! == - true) - ? oldSeriesDetails.segments[currentSegmentIndex!] as HiloSegment? - : null; - animateHiloSeres( - _isTransposed, - _segmentProperties.lowPoint, - _segmentProperties.highPoint, - _oldSegment?._segmentProperties.lowPoint, - _oldSegment?._segmentProperties.highPoint, - animationFactor, - strokePaint!, - canvas, - _segmentProperties.seriesRenderer); - } - } else { - if (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0) { - _segmentProperties.path = Path(); - _segmentProperties.path - .moveTo(_segmentProperties.lowPoint.x, _segmentProperties.high); - _segmentProperties.path - .lineTo(_segmentProperties.highPoint.x, _segmentProperties.low); - drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _segmentProperties.path); - } else { - canvas.drawLine( - Offset(_segmentProperties.lowPoint.x, _segmentProperties.high), - Offset(_segmentProperties.highPoint.x, _segmentProperties.low), - strokePaint!); - } - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 4b94f0765..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart +++ /dev/null @@ -1,305 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for hilo open close series. -/// -/// Generates the hilo open close series points and has the [calculateSegmentPoints] method overrided to customize -/// the hilo open close segment point calculation. -/// -/// Gets the path and color from the `series`. -class HiloOpenCloseSegment extends ChartSegment { - late double _centerY, - _highY, - _centerX, - _lowX, - _highX, - _centerHigh, - _centerLow, - _lowY; - - late ChartLocation _centerLowPoint, _centerHighPoint; - late bool _showSameValue, _isTransposed; - late HiloOpenCloseSegment _currentSegment; - HiloOpenCloseSegment? _oldSegment; - late HiloOpenCloseSeries _hiloOpenCloseSeries; - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - final Paint fillPaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the Hilo open-close series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the Hilo open-close series should be less than or equal to 1.'); - if (_segmentProperties.color != null) { - fillPaint.color = _segmentProperties.pointColorMapper ?? - _segmentProperties.color! - .withOpacity(_segmentProperties.series.opacity); - } - fillPaint.strokeWidth = _segmentProperties.strokeWidth!; - fillPaint.style = PaintingStyle.fill; - _segmentProperties.defaultFillColor = fillPaint; - return fillPaint; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final Paint strokePaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the Hilo open-close series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the Hilo open-close series should be less than or equal to 1.'); - if (_segmentProperties.strokeColor != null) { - strokePaint.color = _segmentProperties.currentPoint!.isEmpty != null && - _segmentProperties.currentPoint!.isEmpty! == true - ? _segmentProperties.series.emptyPointSettings.color - : _segmentProperties.pointColorMapper ?? - _segmentProperties.strokeColor!; - strokePaint.color = (_segmentProperties.series.opacity < 1 == true && - strokePaint.color != Colors.transparent) - ? strokePaint.color.withOpacity(_segmentProperties.series.opacity) - : strokePaint.color; - } - strokePaint.strokeWidth = _segmentProperties.strokeWidth!; - strokePaint.style = PaintingStyle.stroke; - strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = strokePaint; - setShader(_segmentProperties, strokePaint); - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - _setSegmentProperties(); - _hiloOpenCloseSeries = - _segmentProperties.series as HiloOpenCloseSeries; - _isTransposed = - SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) - .stateProperties - .requireInvertedAxis; - _segmentProperties.isBull = _segmentProperties.currentPoint!.open < - _segmentProperties.currentPoint!.close; - _segmentProperties.lowPoint = _segmentProperties.currentPoint!.lowPoint!; - _segmentProperties.highPoint = _segmentProperties.currentPoint!.highPoint!; - _centerLowPoint = _segmentProperties.currentPoint!.centerLowPoint!; - _centerHighPoint = _segmentProperties.currentPoint!.centerHighPoint!; - _segmentProperties.x = _lowX = _segmentProperties.lowPoint.x; - _segmentProperties.low = _segmentProperties.lowPoint.y; - _segmentProperties.high = _segmentProperties.highPoint.y; - _highX = _segmentProperties.highPoint.x; - _centerHigh = _centerHighPoint.x; - _highY = _centerHighPoint.y; - _centerLow = _centerLowPoint.x; - _lowY = _centerLowPoint.y; - _segmentProperties.openX = _segmentProperties.currentPoint!.openPoint!.x; - _segmentProperties.openY = _segmentProperties.currentPoint!.openPoint!.y; - _segmentProperties.closeX = _segmentProperties.currentPoint!.closePoint!.x; - _segmentProperties.closeY = _segmentProperties.currentPoint!.closePoint!.y; - - _showSameValue = _hiloOpenCloseSeries.showIndicationForSameValues && - (!_isTransposed - ? _centerHighPoint.y == _centerLowPoint.y - : _centerHighPoint.x == _centerLowPoint.x); - - if (_showSameValue) { - if (_isTransposed) { - _segmentProperties.x = - _segmentProperties.lowPoint.x = _segmentProperties.lowPoint.x - 2; - _segmentProperties.highPoint.x = _segmentProperties.highPoint.x + 2; - _centerHigh = _centerHighPoint.x = _centerHighPoint.x + 2; - _centerLow = _centerLowPoint.x = _centerLowPoint.x - 2; - } else { - _segmentProperties.low = - _segmentProperties.lowPoint.y = _segmentProperties.lowPoint.y - 2; - _segmentProperties.high = - _segmentProperties.highPoint.y = _segmentProperties.highPoint.y + 2; - _highY = _centerHighPoint.y = _centerHighPoint.y + 2; - _lowY = _centerLowPoint.y = _centerLowPoint.y - 2; - } - } - } - - /// Draws the _path between open and close values. - void drawHiloOpenClosePath(Canvas canvas) { - canvas.drawLine( - Offset(_centerHigh, _highY), Offset(_centerLow, _lowY), strokePaint!); - canvas.drawLine( - Offset(_segmentProperties.openX, _segmentProperties.openY), - Offset(_isTransposed ? _segmentProperties.openX : _centerHigh, - _isTransposed ? _highY : _segmentProperties.openY), - strokePaint!); - canvas.drawLine( - Offset(_segmentProperties.closeX, _segmentProperties.closeY), - Offset(_isTransposed ? _segmentProperties.closeX : _centerLow, - _isTransposed ? _highY : _segmentProperties.closeY), - strokePaint!); - } - - /// To draw dashed hilo open close path - Path _drawDashedHiloOpenClosePath(Canvas canvas) { - _segmentProperties.path.moveTo(_centerHigh, _highY); - _segmentProperties.path.lineTo(_centerLow, _lowY); - _segmentProperties.path - .moveTo(_segmentProperties.openX, _segmentProperties.openY); - _segmentProperties.path.lineTo( - _isTransposed ? _segmentProperties.openX : _centerHigh, - _isTransposed ? _highY : _segmentProperties.openY); - _segmentProperties.path.moveTo( - _isTransposed ? _segmentProperties.closeX : _centerLow, - _isTransposed ? _highY : _segmentProperties.closeY); - _segmentProperties.path - .lineTo(_segmentProperties.closeX, _segmentProperties.closeY); - return _segmentProperties.path; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - if (strokePaint != null) { - _segmentProperties.path = Path(); - if (_segmentProperties.series.animationDuration > 0 == true && - _segmentProperties.stateProperties.renderingDetails.isLegendToggled == - false) { - if (_segmentProperties - .stateProperties.renderingDetails.widgetNeedUpdate == - false || - seriesRendererDetails.reAnimate == true) { - if (_isTransposed) { - _centerX = _highX + ((_lowX - _highX) / 2); - _segmentProperties.openX = _centerX - - ((_centerX - _segmentProperties.currentPoint!.openPoint!.x) * - animationFactor); - _segmentProperties.closeX = _centerX + - ((_segmentProperties.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!); - canvas.drawLine( - Offset(_segmentProperties.openX, _segmentProperties.openY), - Offset(_segmentProperties.openX, _highY), - strokePaint!); - canvas.drawLine( - Offset(_segmentProperties.closeX, _lowY), - Offset(_segmentProperties.closeX, _segmentProperties.closeY), - strokePaint!); - } else { - _centerY = _segmentProperties.high + - ((_segmentProperties.low - _segmentProperties.high) / 2); - _segmentProperties.openY = _centerY - - ((_centerY - _segmentProperties.currentPoint!.openPoint!.y) * - animationFactor); - _segmentProperties.closeY = _centerY + - ((_segmentProperties.currentPoint!.closePoint!.y - _centerY) * - animationFactor); - _highY = _centerY - - ((_centerY - _segmentProperties.high) * animationFactor); - _lowY = _centerY + - ((_segmentProperties.low - _centerY) * animationFactor); - canvas.drawLine(Offset(_centerHigh, _highY), - Offset(_centerLow, _lowY), strokePaint!); - canvas.drawLine( - Offset(_segmentProperties.openX, _segmentProperties.openY), - Offset(_centerHigh, _segmentProperties.openY), - strokePaint!); - canvas.drawLine( - Offset(_centerLow, _segmentProperties.closeY), - Offset(_segmentProperties.closeX, _segmentProperties.closeY), - strokePaint!); - } - } else { - _currentSegment = seriesRendererDetails.segments[currentSegmentIndex!] - as HiloOpenCloseSegment; - final SeriesRendererDetails? oldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - _currentSegment._segmentProperties.oldSeriesRenderer!); - _oldSegment = seriesRendererDetails.reAnimate == false && - (_currentSegment._segmentProperties.oldSeriesRenderer != - null && - oldSeriesDetails!.segments.isNotEmpty == true && - oldSeriesDetails.segments[0] is HiloOpenCloseSegment && - oldSeriesDetails.segments.length - 1 >= - currentSegmentIndex! == - true) - ? oldSeriesDetails.segments[currentSegmentIndex!] - as HiloOpenCloseSegment? - : null; - animateHiloOpenCloseSeries( - _isTransposed, - _isTransposed - ? _segmentProperties.lowPoint.x - : _segmentProperties.low, - _isTransposed - ? _segmentProperties.highPoint.x - : _segmentProperties.high, - _isTransposed - ? (_oldSegment != null - ? _oldSegment!._segmentProperties.lowPoint.x - : null) - : _oldSegment?._segmentProperties.low, - _isTransposed - ? (_oldSegment != null - ? _oldSegment!._segmentProperties.highPoint.x - : null) - : _oldSegment?._segmentProperties.high, - _segmentProperties.openX, - _segmentProperties.openY, - _segmentProperties.closeX, - _segmentProperties.closeY, - _isTransposed ? _centerLowPoint.y : _centerLow, - _isTransposed ? _centerHighPoint.y : _centerHigh, - _oldSegment?._segmentProperties.openX, - _oldSegment?._segmentProperties.openY, - _oldSegment?._segmentProperties.closeX, - _oldSegment?._segmentProperties.closeY, - _isTransposed - ? (_oldSegment != null - ? _oldSegment!._centerLowPoint.y - : null) - : _oldSegment?._centerLow, - _isTransposed - ? (_oldSegment != null - ? _oldSegment!._centerHighPoint.y - : null) - : _oldSegment?._centerHigh, - animationFactor, - strokePaint!, - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer)); - } - } else { - (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0) - ? drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _drawDashedHiloOpenClosePath(canvas)) - : drawHiloOpenClosePath(canvas); - } - } - } - - /// Method to set segment properties - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index f62113413..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for column series. -/// -/// 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. -/// -class HistogramSegment extends ChartSegment { - // We are using `segmentRect` to draw the histogram segment in the series. - // we can override this class and customize the histogram segment by getting `segmentRect`. - /// Rectangle of the segment - late RRect segmentRect; - - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - // final bool hasPointColor = series.pointColorMapper != null ? true : false; - - /// Get and set the paint options for column series. - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.color != null) { - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.color - : ((_segmentProperties.currentPoint!.pointColorMapper != null) - ? _segmentProperties.currentPoint!.pointColorMapper! - : _segmentProperties.color!) - ..style = PaintingStyle.fill; - } - } else { - fillPaint = getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.currentPoint!.region!, - _segmentProperties.stateProperties.requireInvertedAxis); - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the histogram series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the histogram series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.strokeWidth!; - if (_segmentProperties.series.borderGradient != null) { - strokePaint!.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!); - } else if (_segmentProperties.strokeColor != null) { - strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.strokeColor!; - } - _segmentProperties.defaultStrokeColor = strokePaint; - _segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final HistogramSeries histogramSeries = - _segmentProperties.series as HistogramSeries; - - if (_segmentProperties.trackerFillPaint != null && - histogramSeries.isTrackVisible) { - _drawSegmentRect(_segmentProperties.trackerFillPaint!, canvas, - _segmentProperties.trackRect); - } - - if (_segmentProperties.trackerStrokePaint != null && - histogramSeries.isTrackVisible) { - _drawSegmentRect(_segmentProperties.trackerStrokePaint!, canvas, - _segmentProperties.trackRect); - } - - if (fillPaint != null) { - _drawSegmentRect(fillPaint!, canvas, segmentRect); - } - if (strokePaint != null) { - (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0) - ? drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _segmentProperties.path) - : _drawSegmentRect(strokePaint!, canvas, segmentRect); - } - } - - /// To draw the rect of a given segment. - void _drawSegmentRect(Paint getPaint, Canvas canvas, RRect getRect) { - ((_segmentProperties.stateProperties.renderingDetails.initialRender! == - true || - _segmentProperties - .stateProperties.renderingDetails.isLegendToggled == - true || - (_segmentProperties.series.key != null && - _segmentProperties.stateProperties.oldSeriesKeys - .contains(_segmentProperties.series.key) == - true)) && - _segmentProperties.series.animationDuration > 0 == true) - ? animateRectSeries( - canvas, - _segmentProperties.seriesRenderer, - getPaint, - getRect, - _segmentProperties.currentPoint!.yValue, - animationFactor, - _segmentProperties.oldPoint != null - ? _segmentProperties.oldPoint!.region - : _segmentProperties.oldRegion, - _segmentProperties.oldPoint?.yValue, - _segmentProperties.oldSeriesVisible) - : canvas.drawRRect(getRect, getPaint); - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 76e349800..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart +++ /dev/null @@ -1,293 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import '../axis/axis.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for line series. -/// -/// Line segment is a part of a line series that is bounded by two distinct end point. -/// 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`. -/// -/// _Note:_ This is only applicable for [SfCartesianChart]. -class LineSegment extends ChartSegment { - /// Old segment points. - double? _oldX1, _oldY1, _oldX2, _oldY2; - - late bool _needAnimate, _newlyAddedSegment = false; - - late Rect _axisClipRect; - - late ChartLocation _first, _second, _currentPointLocation, _nextPointLocation; - - late ChartAxisRendererDetails _xAxisRenderer, _yAxisRenderer; - ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; - - late LineSegment _currentSegment; - LineSegment? _oldSegment; - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - - final Paint _fillPaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the line series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the line series should be less than or equal to 1.'); - if (_segmentProperties.color != null) { - _fillPaint.color = _segmentProperties.pointColorMapper ?? - _segmentProperties.color! - .withOpacity(_segmentProperties.series.opacity); - } - _fillPaint.strokeWidth = _segmentProperties.strokeWidth!; - _fillPaint.style = PaintingStyle.fill; - _segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - - final Paint strokePaint = Paint(); - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the line series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the line series should be less than or equal to 1.'); - if (_segmentProperties.strokeColor != null) { - strokePaint.color = _segmentProperties.pointColorMapper ?? - _segmentProperties.strokeColor!; - strokePaint.color = (_segmentProperties.series.opacity < 1 == true && - strokePaint.color != Colors.transparent) - ? strokePaint.color.withOpacity(_segmentProperties.series.opacity) - : strokePaint.color; - } - strokePaint.strokeWidth = _segmentProperties.strokeWidth!; - strokePaint.style = PaintingStyle.stroke; - strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = strokePaint; - setShader(_segmentProperties, strokePaint); - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - _setSegmentProperties(); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - _xAxisRenderer = seriesRendererDetails.xAxisDetails!; - _yAxisRenderer = seriesRendererDetails.yAxisDetails!; - _axisClipRect = calculatePlotOffset( - _segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - _currentPointLocation = calculatePoint( - _segmentProperties.currentPoint!.xValue, - _segmentProperties.currentPoint!.yValue, - _xAxisRenderer, - _yAxisRenderer, - _segmentProperties.stateProperties.requireInvertedAxis, - _segmentProperties.series, - _axisClipRect); - _segmentProperties.x1 = _currentPointLocation.x; - _segmentProperties.y1 = _currentPointLocation.y; - _nextPointLocation = calculatePoint( - _segmentProperties.nextPoint!.xValue, - _segmentProperties.nextPoint!.yValue, - _xAxisRenderer, - _yAxisRenderer, - _segmentProperties.stateProperties.requireInvertedAxis, - _segmentProperties.series, - _axisClipRect); - _segmentProperties.x2 = _nextPointLocation.x; - _segmentProperties.y2 = _nextPointLocation.y; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - double? prevX, prevY; - LineSegment? prevSegment; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer); - - final Rect rect = calculatePlotOffset( - _segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - _segmentProperties.path = Path(); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[currentSegmentIndex!]); - if (_segmentProperties.series.animationDuration > 0 == true && - seriesRendererDetails.reAnimate == false && - _segmentProperties.oldSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.oldSeriesRenderer!) - .segments - .isNotEmpty == - true && - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.oldSeriesRenderer!) - .segments[0] is LineSegment && - _segmentProperties.stateProperties.oldSeriesRenderers.length - 1 >= - segmentProperties.seriesIndex == - true && - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!) - .segments - .isNotEmpty == - true) { - _currentSegment = - seriesRendererDetails.segments[currentSegmentIndex!] as LineSegment; - final SeriesRendererDetails oldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - SegmentHelper.getSegmentProperties(_currentSegment) - .oldSeriesRenderer!); - _oldSegment = - (oldSeriesDetails.segments.length - 1 >= currentSegmentIndex! == true) - ? oldSeriesDetails.segments[currentSegmentIndex!] as LineSegment? - : null; - if (currentSegmentIndex! > 0) { - prevSegment = - (oldSeriesDetails.segments.length - 1 >= currentSegmentIndex! - 1 == - true) - ? oldSeriesDetails.segments[currentSegmentIndex! - 1] - as LineSegment? - : null; - } - _oldX1 = _oldSegment?._segmentProperties.x1; - _oldY1 = _oldSegment?._segmentProperties.y1; - _oldX2 = _oldSegment?._segmentProperties.x2; - _oldY2 = _oldSegment?._segmentProperties.y2; - if (_oldSegment == null && - _segmentProperties - .stateProperties.renderingDetails.widgetNeedUpdate == - true) { - _newlyAddedSegment = true; - prevX = prevSegment?._segmentProperties.x2; - prevY = prevSegment?._segmentProperties.y2; - } else { - _newlyAddedSegment = false; - } - if (_oldSegment != null && - (_oldX1!.isNaN || _oldX2!.isNaN) && - _segmentProperties.stateProperties.oldAxisRenderers.isNotEmpty == - true) { - _oldXAxisRenderer = getOldAxisRenderer( - seriesRendererDetails.xAxisDetails!.axisRenderer, - _segmentProperties.stateProperties.oldAxisRenderers); - _oldYAxisRenderer = getOldAxisRenderer( - seriesRendererDetails.yAxisDetails!.axisRenderer, - _segmentProperties.stateProperties.oldAxisRenderers); - if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - final ChartAxisRendererDetails _oldXAxisDetails = - AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); - final ChartAxisRendererDetails _oldYAxisDetails = - AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); - _needAnimate = _oldYAxisDetails.visibleRange!.minimum != - seriesRendererDetails.yAxisDetails!.visibleRange!.minimum || - _oldYAxisDetails.visibleRange!.maximum != - seriesRendererDetails.yAxisDetails!.visibleRange!.maximum || - _oldXAxisDetails.visibleRange!.minimum != - seriesRendererDetails.xAxisDetails!.visibleRange!.minimum || - _oldXAxisDetails.visibleRange!.maximum != - seriesRendererDetails.xAxisDetails!.visibleRange!.maximum; - } - if (_needAnimate) { - final ChartAxisRendererDetails _oldXAxisDetails = - AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); - final ChartAxisRendererDetails _oldYAxisDetails = - AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); - _first = calculatePoint( - _segmentProperties.currentPoint!.xValue, - _segmentProperties.currentPoint!.yValue, - _oldXAxisDetails, - _oldYAxisDetails, - _segmentProperties.stateProperties.requireInvertedAxis, - _segmentProperties.series, - rect); - _second = calculatePoint( - _segmentProperties.nextPoint!.xValue, - _segmentProperties.nextPoint!.yValue, - _oldXAxisDetails, - _oldYAxisDetails, - _segmentProperties.stateProperties.requireInvertedAxis, - _segmentProperties.series, - rect); - _oldX1 = _first.x; - _oldX2 = _second.x; - _oldY1 = _first.y; - _oldY2 = _second.y; - } - } - _newlyAddedSegment - ? animateToPoint( - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer), - strokePaint!, - animationFactor, - _currentSegment._segmentProperties.x1, - _currentSegment._segmentProperties.y1, - _currentSegment._segmentProperties.x2, - _currentSegment._segmentProperties.y2, - prevX, - prevY) - : animateLineTypeSeries( - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer), - strokePaint!, - animationFactor, - _currentSegment._segmentProperties.x1, - _currentSegment._segmentProperties.y1, - _currentSegment._segmentProperties.x2, - _currentSegment._segmentProperties.y2, - _oldX1, - _oldY1, - _oldX2, - _oldY2, - ); - } else { - if (_segmentProperties.series.dashArray[0] != 0 && - _segmentProperties.series.dashArray[1] != 0) { - _segmentProperties.path - .moveTo(_segmentProperties.x1, _segmentProperties.y1); - _segmentProperties.path - .lineTo(_segmentProperties.x2, _segmentProperties.y2); - drawDashedLine(canvas, _segmentProperties.series.dashArray, - strokePaint!, _segmentProperties.path); - } else { - canvas.drawLine(Offset(_segmentProperties.x1, _segmentProperties.y1), - Offset(_segmentProperties.x2, _segmentProperties.y2), strokePaint!); - } - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 21616ecf6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for range area series. -/// -/// Generates the range area series points and has the [calculateSegmentPoints] method overrides to customize -/// the range area segment point calculation. -/// -/// Gets the path and color from the `series`. -class RangeAreaSegment extends ChartSegment { - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - fillPaint = Paint(); - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.color != null) { - fillPaint!.color = _segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - fillPaint = (_segmentProperties.pathRect != null) - ? getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.pathRect!, - _segmentProperties.stateProperties.requireInvertedAxis) - : fillPaint; - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the range area series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the range area series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final Paint _strokePaint = Paint(); - _strokePaint - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.series.borderWidth; - if (_segmentProperties.series.borderGradient != null && - _segmentProperties.borderPath != null) { - _strokePaint.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.borderPath!.getBounds()); - } else if (_segmentProperties.strokeColor != null) { - _strokePaint.color = _segmentProperties.series.borderColor; - } - _segmentProperties.series.borderWidth == 0 - ? _strokePaint.color = Colors.transparent - : _strokePaint.color; - _strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = _strokePaint; - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final RangeAreaSeries _series = - _segmentProperties.series as RangeAreaSeries; - _segmentProperties.pathRect = _segmentProperties.path.getBounds(); - canvas.drawPath(_segmentProperties.path, - (_series.gradient == null) ? fillPaint! : getFillPaint()); - strokePaint = getStrokePaint(); - if (strokePaint!.color != Colors.transparent) { - drawDashedLine( - canvas, - _series.dashArray, - strokePaint!, - _series.borderDrawMode == RangeAreaBorderMode.all - ? _segmentProperties.path - : _segmentProperties.borderPath!); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 5d49dcbdf..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../chart_series/series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for range column series. -/// -/// Generates the range column series points and has the [calculateSegmentPoints] method overrides to customize -/// the range column segment point calculation. -/// -/// Gets the path and color from the `series`. -class RangeColumnSegment extends ChartSegment { - // we can override this class and customize the range column segment by getting `segmentRect`. - /// Rectangle of the segment. - late RRect segmentRect; - - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - final bool hasPointColor = - _segmentProperties.series.pointColorMapper != null; - - /// Get and set the paint options for range column series. - if (_segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.color - : ((hasPointColor && - _segmentProperties.currentPoint!.pointColorMapper != null) - ? _segmentProperties.currentPoint!.pointColorMapper - : _segmentProperties.color)! - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.currentPoint!.region!, - _segmentProperties.stateProperties.requireInvertedAxis); - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the range column series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the range column series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint!; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.strokeWidth!; - _segmentProperties.defaultStrokeColor = strokePaint; - _segmentProperties.series.borderGradient != null - ? strokePaint!.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!) - : strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.strokeColor!; - _segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final RangeColumnSeries _series = - _segmentProperties.series as RangeColumnSeries; - - if (_segmentProperties.trackerFillPaint != null && _series.isTrackVisible) { - canvas.drawRRect( - _segmentProperties.trackRect, _segmentProperties.trackerFillPaint!); - } - - if (_segmentProperties.trackerStrokePaint != null && - _series.isTrackVisible) { - canvas.drawRRect( - _segmentProperties.trackRect, _segmentProperties.trackerStrokePaint!); - } - - if (fillPaint != null) { - (_series.animationDuration > 0 && - _segmentProperties - .stateProperties.renderingDetails.isLegendToggled == - false) - ? animateRangeColumn( - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer), - fillPaint!, - segmentRect, - _segmentProperties.oldPoint != null - ? _segmentProperties.oldPoint!.region - : _segmentProperties.oldRegion, - animationFactor) - : canvas.drawRRect(segmentRect, fillPaint!); - } - if (strokePaint != null) { - (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? drawDashedLine( - canvas, _series.dashArray, strokePaint!, _segmentProperties.path) - : (_series.animationDuration > 0 && - _segmentProperties - .stateProperties.renderingDetails.isLegendToggled == - false) - ? animateRangeColumn( - canvas, - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer), - strokePaint!, - segmentRect, - _segmentProperties.oldPoint != null - ? _segmentProperties.oldPoint!.region - : _segmentProperties.oldRegion, - animationFactor) - : canvas.drawRRect(segmentRect, strokePaint!); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index da4d9c394..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; -import '../common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for scatter series. -/// -/// Generates the scatter series points and has the [calculateSegmentPoints] method overrides to customize -/// the scatter segment point calculation. -/// -/// Gets the path and color from the `series`. -class ScatterSegment extends ChartSegment { - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - final bool hasPointColor = - _segmentProperties.series.pointColorMapper != null; - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.color != null) { - fillPaint = Paint() - ..color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.color - : ((hasPointColor && - _segmentProperties.currentPoint!.pointColorMapper != null) - ? _segmentProperties.currentPoint!.pointColorMapper - : _segmentProperties.series.markerSettings.isVisible == true - ? _segmentProperties.series.markerSettings.color ?? - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer) - .seriesColor! - : _segmentProperties.color)! - ..style = PaintingStyle.fill; - } - } else { - fillPaint = getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.currentPoint!.region!, - _segmentProperties.stateProperties.requireInvertedAxis); - } - _segmentProperties.defaultFillColor = fillPaint; - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the the scatter series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the the scatter series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final ScatterSeriesRenderer _scatterRenderer = - _segmentProperties.seriesRenderer as ScatterSeriesRenderer; - final Paint strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderWidth - : _segmentProperties.series.markerSettings.isVisible == true - ? _segmentProperties.series.markerSettings.borderWidth - : _segmentProperties.strokeWidth!; - if (_segmentProperties.series.borderGradient != null) { - strokePaint.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.currentPoint!.region!); - } else { - strokePaint.color = _segmentProperties.currentPoint!.isEmpty == true - ? _segmentProperties.series.emptyPointSettings.borderColor - : _segmentProperties.series.markerSettings.isVisible == true - ? _segmentProperties.series.markerSettings.borderColor ?? - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer) - .seriesColor! - : _segmentProperties.strokeColor!; - } - (strokePaint.strokeWidth == 0 && - SeriesHelper.getSeriesRendererDetails(_scatterRenderer) - .isLineType == - false) - ? strokePaint.color = Colors.transparent - : strokePaint.color; - _segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - if (fillPaint != null) { - const double defaultAnimationFactor = 1; - animateScatterSeries( - SeriesHelper.getSeriesRendererDetails( - _segmentProperties.seriesRenderer), - _segmentProperties.point!, - _segmentProperties.oldPoint, - _segmentProperties.series.animationDuration > 0 == true && - _segmentProperties - .stateProperties.renderingDetails.isLegendToggled == - false - ? animationFactor - : defaultAnimationFactor, - canvas, - fillPaint!, - strokePaint!, - currentSegmentIndex!, - this); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 0b596103b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for spline area series. -/// -/// 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`. -class SplineAreaSegment extends ChartSegment { - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - fillPaint = Paint(); - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.color != null) { - fillPaint!.color = _segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - fillPaint = (_segmentProperties.pathRect != null) - ? getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.pathRect!, - _segmentProperties.stateProperties.requireInvertedAxis) - : fillPaint; - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the spline area series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the spline area series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - final Paint strokePaint = Paint(); - strokePaint - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.series.borderWidth; - if (_segmentProperties.series.borderGradient != null) { - strokePaint.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.strokePath!.getBounds()); - } else if (_segmentProperties.strokeColor != null) { - strokePaint.color = _segmentProperties.series.borderColor; - } - _segmentProperties.series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - _segmentProperties.pathRect = _segmentProperties.path.getBounds(); - canvas.drawPath( - _segmentProperties.path, - (_segmentProperties.series.gradient == null) - ? fillPaint! - : getFillPaint()); - drawDashedLine(canvas, _segmentProperties.series.dashArray, strokePaint!, - _segmentProperties.strokePath!); - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index 637a0a9f1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../chart_series/series.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for spline area series. -/// -/// Generates the spline area series points and has the [calculateSegmentPoints] method overrides to customize -/// the spline area segment point calculation. -/// -/// Gets the path and color from the `series`. -class SplineRangeAreaSegment extends ChartSegment { - late SegmentProperties _segmentProperties; - bool _isInitialize = false; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - _setSegmentProperties(); - fillPaint = Paint(); - if (_segmentProperties.series.gradient == null) { - if (_segmentProperties.color != null) { - fillPaint!.color = _segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - fillPaint = (_segmentProperties.pathRect != null) - ? getLinearGradientPaint( - _segmentProperties.series.gradient!, - _segmentProperties.pathRect!, - _segmentProperties.stateProperties.requireInvertedAxis) - : fillPaint; - } - assert(_segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the spline range area series should be greater than or equal to 0.'); - assert(_segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the spline range area series should be less than or equal to 1.'); - fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) - : fillPaint!.color; - _segmentProperties.defaultFillColor = fillPaint; - setShader(_segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - _setSegmentProperties(); - - final Paint strokePaint = Paint(); - strokePaint - ..style = PaintingStyle.stroke - ..strokeWidth = _segmentProperties.series.borderWidth; - if (_segmentProperties.series.borderGradient != null) { - strokePaint.shader = _segmentProperties.series.borderGradient! - .createShader(_segmentProperties.strokePath!.getBounds()); - } else if (_segmentProperties.strokeColor != null) { - strokePaint.color = _segmentProperties.series.borderColor; - } - _segmentProperties.series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - strokePaint.strokeCap = StrokeCap.round; - _segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - _setSegmentProperties(); - final SplineRangeAreaSeries splineRangeAreaSeries = - SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) - .series as SplineRangeAreaSeries; - _segmentProperties.pathRect = _segmentProperties.path.getBounds(); - canvas.drawPath( - _segmentProperties.path, - (_segmentProperties.series.gradient == null) - ? fillPaint! - : getFillPaint()); - if (strokePaint!.color != Colors.transparent) { - drawDashedLine( - canvas, - _segmentProperties.series.dashArray, - strokePaint!, - splineRangeAreaSeries.borderDrawMode == RangeAreaBorderMode.all - ? _segmentProperties.path - : _segmentProperties.strokePath!); - } - } - - /// Method to set segment properties. - void _setSegmentProperties() { - if (!_isInitialize) { - _segmentProperties = SegmentHelper.getSegmentProperties(this); - _isInitialize = true; - } - } -} 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 deleted file mode 100644 index f7ee5deb1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart +++ /dev/null @@ -1,332 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for spline series. -/// -/// Generates the spline series points and has the [calculateSegmentPoints] method overrides to customize -/// the spline segment point calculation. -/// -/// Gets the path and color from the `series`. -class SplineSegment extends ChartSegment { - double? _oldX1, _oldY1, _oldX2, _oldY2, _oldX3, _oldY3, _oldX4, _oldY4; - - /// Start point X value. - double? startControlX; - - /// Start point Y value. - double? startControlY; - - /// End point X value. - double? endControlX; - - /// End point Y value. - double? endControlY; - - ChartLocation? _currentPointLocation, _nextPointLocation; - late ChartAxisRendererDetails _xAxisRenderer, _yAxisRenderer; - ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; - late Rect _axisClipRect; - SplineSegment? _currentSegment; - SplineSegment? _oldSegment; - late bool _needAnimate; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _fillPaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the spline series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the spline series should be less than or equal to 1.'); - if (segmentProperties.strokeColor != null) { - _fillPaint.color = segmentProperties.strokeColor! - .withOpacity(segmentProperties.series.opacity); - } - _fillPaint.strokeWidth = segmentProperties.strokeWidth!; - _fillPaint.style = PaintingStyle.stroke; - segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _strokePaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the spline series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the spline series should be less than or equal to 1.'); - if (segmentProperties.strokeColor != null) { - _strokePaint.color = segmentProperties.pointColorMapper ?? - segmentProperties.strokeColor! - .withOpacity(segmentProperties.series.opacity); - _strokePaint.color = (segmentProperties.series.opacity < 1 == true && - _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) - : _strokePaint.color; - } - _strokePaint.strokeWidth = segmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.round; - segmentProperties.defaultStrokeColor = _strokePaint; - setShader(segmentProperties, _strokePaint); - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final SeriesRendererDetails segmentSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - _xAxisRenderer = segmentSeriesRendererDetails.xAxisDetails!; - _yAxisRenderer = segmentSeriesRendererDetails.yAxisDetails!; - _axisClipRect = calculatePlotOffset( - segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, - segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); - _currentPointLocation = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentPoint!.yValue, - _xAxisRenderer, - _yAxisRenderer, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - _axisClipRect); - segmentProperties.x1 = _currentPointLocation!.x; - segmentProperties.y1 = _currentPointLocation!.y; - _nextPointLocation = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextPoint!.yValue, - _xAxisRenderer, - _yAxisRenderer, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - _axisClipRect); - segmentProperties.x2 = _nextPointLocation!.x; - segmentProperties.y2 = _nextPointLocation!.y; - - startControlX = segmentProperties.currentPoint!.startControl!.x; - startControlY = segmentProperties.currentPoint!.startControl!.y; - endControlX = segmentProperties.currentPoint!.endControl!.x; - endControlY = segmentProperties.currentPoint!.endControl!.y; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final SeriesRendererDetails segmentSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (segmentSeriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segmentSeriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - segmentSeriesRendererDetails.segments[currentSegmentIndex!], - segmentSeriesRendererDetails.chart); - } - late Rect rect; - if (segmentSeriesRendererDetails.xAxisDetails != null && - segmentSeriesRendererDetails.yAxisDetails != null) { - rect = calculatePlotOffset( - segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, - segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); - } - - // Draw spline series. - if (segmentProperties.series.animationDuration > 0 == true && - segmentSeriesRendererDetails.reAnimate == false && - segmentProperties.stateProperties.renderingDetails.widgetNeedUpdate == - true && - segmentProperties.stateProperties.renderingDetails.isLegendToggled == - false && - segmentProperties.stateProperties.oldSeriesRenderers.isNotEmpty == - true && - segmentProperties.oldSeries != null && - SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) - .segments - .isNotEmpty == - true && - SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) - .segments[0] is SplineSegment && - segmentProperties.stateProperties.oldSeriesRenderers.length - 1 >= - SegmentHelper.getSegmentProperties(segmentSeriesRendererDetails - .segments[currentSegmentIndex!]) - .seriesIndex == - true && - SeriesHelper.getSeriesRendererDetails(SegmentHelper.getSegmentProperties( - segmentSeriesRendererDetails - .segments[currentSegmentIndex!]) - .oldSeriesRenderer!) - .segments - .isNotEmpty == - true && - segmentProperties.currentPoint!.isGap != true && - segmentProperties.nextPoint!.isGap != true) { - _currentSegment = segmentSeriesRendererDetails - .segments[currentSegmentIndex!] as SplineSegment; - final SegmentProperties _currentSegmentProperties = - SegmentHelper.getSegmentProperties(_currentSegment!); - SegmentProperties? _oldSegmentProperties; - final SeriesRendererDetails _currentOldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _currentSegmentProperties.oldSeriesRenderer!); - _oldSegment = (_currentOldSeriesRendererDetails.segments.length - 1 >= - currentSegmentIndex! == - true) - ? _currentOldSeriesRendererDetails.segments[currentSegmentIndex!] - as SplineSegment? - : null; - if (_oldSegment != null) { - _oldSegmentProperties = - SegmentHelper.getSegmentProperties(_oldSegment!); - } - _oldX1 = _oldSegmentProperties?.x1; - _oldY1 = _oldSegmentProperties?.y1; - _oldX2 = _oldSegmentProperties?.x2; - _oldY2 = _oldSegmentProperties?.y2; - _oldX3 = _oldSegment?.startControlX; - _oldY3 = _oldSegment?.startControlY; - _oldX4 = _oldSegment?.endControlX; - _oldY4 = _oldSegment?.endControlY; - - if (_oldSegment != null && - (_oldX1!.isNaN || _oldX2!.isNaN) && - segmentProperties.stateProperties.oldAxisRenderers.isNotEmpty == - true) { - ChartLocation _oldPoint; - _oldXAxisRenderer = getOldAxisRenderer( - segmentSeriesRendererDetails.xAxisDetails!.axisRenderer, - segmentProperties.stateProperties.oldAxisRenderers); - _oldYAxisRenderer = getOldAxisRenderer( - segmentSeriesRendererDetails.yAxisDetails!.axisRenderer, - segmentProperties.stateProperties.oldAxisRenderers); - if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - final ChartAxisRendererDetails _oldXAxisDetails = - AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); - final ChartAxisRendererDetails _oldYAxisDetails = - AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); - _needAnimate = _oldYAxisDetails.visibleRange!.minimum != - segmentSeriesRendererDetails - .yAxisDetails!.visibleRange!.minimum || - _oldYAxisDetails.visibleRange!.maximum != - segmentSeriesRendererDetails - .yAxisDetails!.visibleRange!.maximum || - _oldXAxisDetails.visibleRange!.minimum != - segmentSeriesRendererDetails - .xAxisDetails!.visibleRange!.minimum || - _oldXAxisDetails.visibleRange!.maximum != - segmentSeriesRendererDetails - .xAxisDetails!.visibleRange!.maximum; - } - if (_needAnimate) { - final ChartAxisRendererDetails _oldXAxisDetails = - AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); - final ChartAxisRendererDetails _oldYAxisDetails = - AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); - _oldPoint = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentPoint!.yValue, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - _oldX1 = _oldPoint.x; - _oldY1 = _oldPoint.y; - _oldPoint = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextPoint!.xValue, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - _oldX2 = _oldPoint.x; - _oldY2 = _oldPoint.y; - _oldPoint = calculatePoint( - startControlX!, - startControlY!, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - _oldX3 = _oldPoint.x; - _oldY3 = _oldPoint.y; - _oldPoint = calculatePoint( - endControlX!, - endControlY!, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - _oldX4 = _oldPoint.x; - _oldY4 = _oldPoint.y; - } - } - animateLineTypeSeries( - canvas, - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer), - strokePaint!, - animationFactor, - _currentSegmentProperties.x1, - _currentSegmentProperties.y1, - _currentSegmentProperties.x2, - _currentSegmentProperties.y2, - _oldX1, - _oldY1, - _oldX2, - _oldY2, - _currentSegment!.startControlX, - _currentSegment!.startControlY, - _oldX3, - _oldY3, - _currentSegment!.endControlX, - _currentSegment!.endControlY, - _oldX4, - _oldY4, - ); - } else { - final Path path = Path(); - path.moveTo(segmentProperties.x1, segmentProperties.y1); - if (segmentProperties.currentPoint!.isGap != true && - segmentProperties.nextPoint!.isGap != true) { - path.cubicTo(startControlX!, startControlY!, endControlX!, endControlY!, - segmentProperties.x2, segmentProperties.y2); - drawDashedLine( - canvas, segmentProperties.series.dashArray, strokePaint!, path); - } - } - } - - @override - void dispose() { - _currentPointLocation = null; - _nextPointLocation = null; - _currentSegment = null; - _oldSegment = null; - super.dispose(); - } -} 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 deleted file mode 100644 index 63045b757..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for stacked area series. -/// -/// Generates the stacked area series points and has the [calculateSegmentPoints] method overrides to customize -/// the stacked area segment point calculation. -/// -/// Gets the path and color from the `series`. -class StackedAreaSegment extends ChartSegment { - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - fillPaint = Paint(); - if (segmentProperties.series.gradient == null) { - if (segmentProperties.color != null) { - fillPaint!.color = segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - fillPaint = (segmentProperties.pathRect != null) - ? getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.pathRect!, - segmentProperties.stateProperties.requireInvertedAxis) - : fillPaint; - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked area series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked area series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - strokePaint = Paint(); - strokePaint! - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.series.borderWidth; - if (segmentProperties.series.borderGradient != null && - segmentProperties.strokePath != null) { - strokePaint!.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.strokePath!.getBounds()); - } else if (segmentProperties.strokeColor != null) { - strokePaint!.color = segmentProperties.series.borderColor; - } - segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - strokePaint!.strokeCap = StrokeCap.round; - segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - drawStackedAreaPath(segmentProperties.path, segmentProperties.strokePath!, - segmentProperties.seriesRenderer, canvas, fillPaint!, strokePaint!); - } -} 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 deleted file mode 100644 index 0f682cd8e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for stacked bar series. -/// -/// Generates the stacked bar series points and has the [calculateSegmentPoints] method overrides to customize -/// the stacked bar segment point calculation. -/// -/// Gets the path and color from the `series`. -class StackedBarSegment extends ChartSegment { - /// Stacked values. - late double stackValues; - - late StackedBarSeries _stackedBarSeries; - - /// Rectangle of the segment this could be used to render the segment while overriding this segment. - late RRect segmentRect; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - - // Get and set the paint options for column series. - if (segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.color - : (segmentProperties.currentPoint!.pointColorMapper ?? - segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.currentPoint!.region!, - segmentProperties.stateProperties.requireInvertedAxis); - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked bar series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked bar series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderWidth - : segmentProperties.strokeWidth!; - if (segmentProperties.series.borderGradient != null) { - strokePaint!.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.currentPoint!.region!); - } else if (segmentProperties.strokeColor != null) { - strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderColor - : segmentProperties.strokeColor!; - } - segmentProperties.defaultStrokeColor = strokePaint; - segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - _stackedBarSeries = - segmentProperties.series as StackedBarSeries; - if (segmentProperties.trackerFillPaint != null && - _stackedBarSeries.isTrackVisible) { - canvas.drawRRect( - segmentProperties.trackRect, segmentProperties.trackerFillPaint!); - } - - if (segmentProperties.trackerStrokePaint != null && - _stackedBarSeries.isTrackVisible) { - canvas.drawRRect( - segmentProperties.trackRect, segmentProperties.trackerStrokePaint!); - } - - renderStackingRectSeries( - fillPaint, - strokePaint, - segmentProperties.path, - animationFactor, - segmentProperties.seriesRenderer, - canvas, - segmentRect, - segmentProperties.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 deleted file mode 100644 index c7ee0b834..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for stacked column series. -/// -/// Generates the stacked column series points and has the [calculateSegmentPoints] method overrides to customize -/// the stacked column segment point calculation. -/// -/// Gets the path and color from the `series`. -class StackedColumnSegment extends ChartSegment { - /// Stack values. - late double stackValues; - - /// Represents the stacked column series. - 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 - late RRect segmentRect; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - - // Get and set the paint options for column _series. - if (segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.color - : (segmentProperties.currentPoint!.pointColorMapper ?? - segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.currentPoint!.region!, - segmentProperties.stateProperties.requireInvertedAxis); - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked column series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked column series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderWidth - : segmentProperties.strokeWidth!; - if (segmentProperties.series.borderGradient != null) { - strokePaint!.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.currentPoint!.region!); - } else if (segmentProperties.strokeColor != null) { - strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderColor - : segmentProperties.strokeColor!; - } - segmentProperties.defaultStrokeColor = strokePaint; - segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - _stackedColumnSeries = - segmentProperties.series as StackedColumnSeries; - if (segmentProperties.trackerFillPaint != null && - _stackedColumnSeries.isTrackVisible) { - canvas.drawRRect( - segmentProperties.trackRect, segmentProperties.trackerFillPaint!); - } - if (segmentProperties.trackerStrokePaint != null && - _stackedColumnSeries.isTrackVisible) { - canvas.drawRRect( - segmentProperties.trackRect, segmentProperties.trackerStrokePaint!); - } - renderStackingRectSeries( - fillPaint, - strokePaint, - segmentProperties.path, - animationFactor, - segmentProperties.seriesRenderer, - canvas, - segmentRect, - segmentProperties.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 deleted file mode 100644 index 1b51b226a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart +++ /dev/null @@ -1,135 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../chart_series/series.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for 100% stacked line series. -/// -/// 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`. -class StackedLineSegment extends ChartSegment { - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _fillPaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked line series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked line series should be less than or equal to 1.'); - if (segmentProperties.color != null) { - _fillPaint.color = segmentProperties.pointColorMapper ?? - segmentProperties.color! - .withOpacity(segmentProperties.series.opacity); - } - _fillPaint.strokeWidth = segmentProperties.strokeWidth!; - _fillPaint.style = PaintingStyle.fill; - segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _strokePaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked line series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked line series should be less than or equal to 1.'); - if (segmentProperties.strokeColor != null) { - _strokePaint.color = - segmentProperties.pointColorMapper ?? segmentProperties.strokeColor!; - _strokePaint.color = (segmentProperties.series.opacity < 1 == true && - _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) - : _strokePaint.color; - } - _strokePaint.strokeWidth = segmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.round; - segmentProperties.defaultStrokeColor = _strokePaint; - setShader(segmentProperties, _strokePaint); - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final SeriesRendererDetails segmentSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - late Rect rect; - rect = calculatePlotOffset( - segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, - segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); - - final ChartLocation currentChartPoint = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - final ChartLocation _nextLocation = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - - final ChartLocation currentCummulativePoint = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - - final ChartLocation nextCummulativePoint = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - - segmentProperties.x1 = currentChartPoint.x; - segmentProperties.y1 = currentChartPoint.y; - segmentProperties.x2 = _nextLocation.x; - segmentProperties.y2 = _nextLocation.y; - segmentProperties.currentCummulativeValue = currentCummulativePoint.y; - segmentProperties.nextCummulativeValue = nextCummulativePoint.y; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - renderStackedLineSeries( - segmentProperties.series as StackedSeriesBase, - canvas, - strokePaint!, - segmentProperties.x1, - segmentProperties.y1, - segmentProperties.x2, - segmentProperties.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 deleted file mode 100644 index 0136890c4..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for 100% stacked area series. -/// -/// Generates the stacked area100 series points and has the [calculateSegmentPoints] method overrides to customize -/// the stacked area100 segment point calculation. -/// -/// Gets the path and color from the `series`. -class StackedArea100Segment extends ChartSegment { - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - fillPaint = Paint(); - if (segmentProperties.series.gradient == null) { - if (segmentProperties.color != null) { - fillPaint!.color = segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - fillPaint = (segmentProperties.pathRect != null) - ? getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.pathRect!, - segmentProperties.stateProperties.requireInvertedAxis) - : fillPaint; - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked area 100 series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked area 100 series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint strokePaint = Paint(); - strokePaint - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.series.borderWidth; - if (segmentProperties.series.borderGradient != null && - segmentProperties.strokePath != null) { - strokePaint.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.strokePath!.getBounds()); - } else if (segmentProperties.strokeColor != null) { - strokePaint.color = segmentProperties.series.borderColor; - } - segmentProperties.series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - strokePaint.strokeCap = StrokeCap.round; - segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - drawStackedAreaPath(segmentProperties.path, segmentProperties.strokePath!, - segmentProperties.seriesRenderer, canvas, fillPaint!, strokePaint!); - } -} 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 deleted file mode 100644 index c35d724a2..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for 100% stacked bar series. -/// -/// Generates the stacked bar100 series points and has the [calculateSegmentPoints] method overrides to customize -/// the stacked bar100 segment point calculation. -/// -/// Gets the path and color from the `series`. -class StackedBar100Segment extends ChartSegment { - /// Stacked value of the segments. - late double stackValues; - - /// Rectangle of the segment - late RRect segmentRect; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - - /// Get and set the paint options for column series. - if (segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.color - : (segmentProperties.currentPoint!.pointColorMapper ?? - segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.currentPoint!.region!, - segmentProperties.stateProperties.requireInvertedAxis); - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked bar 100 series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked bar 100 series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderWidth - : segmentProperties.strokeWidth!; - if (segmentProperties.series.borderGradient != null) { - strokePaint!.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.currentPoint!.region!); - } else if (segmentProperties.strokeColor != null) { - strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderColor - : segmentProperties.strokeColor!; - } - segmentProperties.defaultStrokeColor = strokePaint; - segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - renderStackingRectSeries( - fillPaint, - strokePaint, - segmentProperties.path, - animationFactor, - segmentProperties.seriesRenderer, - canvas, - segmentRect, - segmentProperties.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 deleted file mode 100644 index c15aab82b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; - -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for 100% stacked column series. -/// -/// Generates the stacked column100 series points and has the [calculateSegmentPoints] method overrides to customize -/// the stacked column100 segment point calculation. -/// -/// Gets the path and color from the `series`. -class StackedColumn100Segment extends ChartSegment { - /// Stacked value. - late double stackValues; - - // The rectangle of the segment. This could be used to render a segment while overriding this segment. - /// Rectangle of the segment. - late RRect segmentRect; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - - /// Get and set the paint options for stackedcolumn100 series. - if (segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.color - : (segmentProperties.currentPoint!.pointColorMapper ?? - segmentProperties.color!) - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.currentPoint!.region!, - segmentProperties.stateProperties.requireInvertedAxis); - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked column100 series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked column100 series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderWidth - : segmentProperties.strokeWidth!; - if (segmentProperties.series.borderGradient != null) { - strokePaint!.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.currentPoint!.region!); - } else if (segmentProperties.strokeColor != null) { - strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && - segmentProperties.currentPoint!.isEmpty! == true - ? segmentProperties.series.emptyPointSettings.borderColor - : segmentProperties.strokeColor!; - } - segmentProperties.defaultStrokeColor = strokePaint; - segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - renderStackingRectSeries( - fillPaint, - strokePaint, - segmentProperties.path, - animationFactor, - segmentProperties.seriesRenderer, - canvas, - segmentRect, - segmentProperties.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 deleted file mode 100644 index 0dd72e5af..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for 100% stacked line series. -/// -/// 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`. -class StackedLine100Segment extends ChartSegment { - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _fillPaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked line100 series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked line100 series should be less than or equal to 1.'); - if (segmentProperties.color != null) { - _fillPaint.color = segmentProperties.pointColorMapper ?? - segmentProperties.color! - .withOpacity(segmentProperties.series.opacity); - } - _fillPaint.strokeWidth = segmentProperties.strokeWidth!; - _fillPaint.style = PaintingStyle.fill; - segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _strokePaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the stacked line100 series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the stacked line100 series should be less than or equal to 1.'); - if (segmentProperties.strokeColor != null) { - _strokePaint.color = - segmentProperties.pointColorMapper ?? segmentProperties.strokeColor!; - _strokePaint.color = (segmentProperties.series.opacity < 1 == true && - _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) - : _strokePaint.color; - } - _strokePaint.strokeWidth = segmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.round; - segmentProperties.defaultStrokeColor = _strokePaint; - setShader(segmentProperties, _strokePaint); - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final SeriesRendererDetails segmentSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - final Rect rect = calculatePlotOffset( - segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, - segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final ChartLocation currentChartPoint = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - final ChartLocation _nextLocation = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - - final ChartLocation currentCummulativePoint = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - - final ChartLocation nextCummulativePoint = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextCummulativePos, - segmentSeriesRendererDetails.xAxisDetails!, - segmentSeriesRendererDetails.yAxisDetails!, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - rect); - - segmentProperties.x1 = currentChartPoint.x; - segmentProperties.y1 = currentChartPoint.y; - segmentProperties.x2 = _nextLocation.x; - segmentProperties.y2 = _nextLocation.y; - segmentProperties.currentCummulativeValue = currentCummulativePoint.y; - segmentProperties.nextCummulativeValue = nextCummulativePoint.y; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - renderStackedLineSeries( - segmentProperties.series as StackedSeriesBase, - canvas, - strokePaint!, - segmentProperties.x1, - segmentProperties.y1, - segmentProperties.x2, - segmentProperties.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 deleted file mode 100644 index 37270b971..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; - -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for step area series. -/// -/// Generates the step area series points and has the [calculateSegmentPoints] method overrides to customize -/// the step area segment point calculation. -/// -/// Gets the path and color from the `series`. -class StepAreaSegment extends ChartSegment { - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - fillPaint = Paint(); - if (segmentProperties.series.gradient == null) { - if (segmentProperties.color != null) { - fillPaint!.color = segmentProperties.color!; - fillPaint!.style = PaintingStyle.fill; - } - } else { - // ignore: unnecessary_null_comparison - fillPaint = (segmentProperties.pathRect != null) - ? getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.pathRect!, - segmentProperties.stateProperties.requireInvertedAxis) - : fillPaint; - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the the step area series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the the step area series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint strokePaint = Paint(); - if (segmentProperties.strokeColor != null) { - strokePaint - ..color = segmentProperties.series.borderColor - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.series.borderWidth; - segmentProperties.series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - } - strokePaint.strokeCap = StrokeCap.round; - segmentProperties.defaultStrokeColor = strokePaint; - return strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - segmentProperties.pathRect = segmentProperties.path.getBounds(); - canvas.drawPath( - segmentProperties.path, - (segmentProperties.series.gradient == null) - ? fillPaint! - : getFillPaint()); - - if (strokePaint!.color != Colors.transparent) { - drawDashedLine(canvas, segmentProperties.series.dashArray, strokePaint!, - segmentProperties.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 deleted file mode 100644 index 17b557942..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart +++ /dev/null @@ -1,325 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; -import '../axis/axis.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for step line series. -/// -/// Generates the step line series points and has the [calculateSegmentPoints] method overrides to customize -/// the step line segment point calculation. -/// -/// Gets the path and color from the `series`. -class StepLineSegment extends ChartSegment { - late double _x1Pos, _y1Pos, _x2Pos, _y2Pos; - - double? _oldX1, _oldY1, _oldX2, _oldY2, _oldX3, _oldY3; - 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 - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _fillPaint = Paint(); - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the step line series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the step line series should be less than or equal to 1.'); - if (segmentProperties.color != null) { - _fillPaint.color = segmentProperties.color! - .withOpacity(segmentProperties.series.opacity); - } - _fillPaint.strokeWidth = segmentProperties.strokeWidth!; - _fillPaint.style = PaintingStyle.stroke; - segmentProperties.defaultFillColor = _fillPaint; - return _fillPaint; - } - - /// Gets the stroke color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Paint _strokePaint = Paint(); - if (segmentProperties.strokeColor != null) { - _strokePaint.color = - segmentProperties.pointColorMapper ?? segmentProperties.strokeColor!; - _strokePaint.color = (segmentProperties.series.opacity < 1 == true && - _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) - : _strokePaint.color; - } - _strokePaint.strokeWidth = segmentProperties.strokeWidth!; - _strokePaint.style = PaintingStyle.stroke; - _strokePaint.strokeCap = StrokeCap.square; - segmentProperties.defaultStrokeColor = _strokePaint; - setShader(segmentProperties, _strokePaint); - return _strokePaint; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final ChartAxisRendererDetails _xAxisRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .xAxisDetails!; - final ChartAxisRendererDetails _yAxisRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .yAxisDetails!; - final Rect _axisClipRect = calculatePlotOffset( - segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset( - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .xAxisDetails! - .axis - .plotOffset, - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .yAxisDetails! - .axis - .plotOffset)); - _currentLocation = calculatePoint( - segmentProperties.currentPoint!.xValue, - segmentProperties.currentPoint!.yValue, - _xAxisRendererDetails, - _yAxisRendererDetails, - segmentProperties.stateProperties.requireInvertedAxis, - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .series, - _axisClipRect); - _nextLocation = calculatePoint( - segmentProperties.nextPoint!.xValue, - segmentProperties.nextPoint!.yValue, - _xAxisRendererDetails, - _yAxisRendererDetails, - segmentProperties.stateProperties.requireInvertedAxis, - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .series, - _axisClipRect); - _midLocation = calculatePoint( - segmentProperties.midX, - segmentProperties.midY, - _xAxisRendererDetails, - _yAxisRendererDetails, - segmentProperties.stateProperties.requireInvertedAxis, - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .series, - _axisClipRect); - segmentProperties.x1 = _currentLocation.x; - segmentProperties.y1 = _currentLocation.y; - segmentProperties.x2 = _nextLocation.x; - segmentProperties.y2 = _nextLocation.y; - segmentProperties.x3 = _midLocation.x; - segmentProperties.y3 = _midLocation.y; - } - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final Rect _rect = calculatePlotOffset( - segmentProperties.stateProperties.chartAxis.axisClipRect, - Offset( - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .xAxisDetails! - .axis - .plotOffset, - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .yAxisDetails! - .axis - .plotOffset)); - segmentProperties.path = Path(); - if (segmentProperties.series.animationDuration > 0 == true && - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).reAnimate == - false && - segmentProperties.stateProperties.renderingDetails.widgetNeedUpdate == - true && - segmentProperties.stateProperties.renderingDetails.isLegendToggled == - false && - segmentProperties.stateProperties.oldSeriesRenderers.isNotEmpty == - true && - segmentProperties.oldSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) - .segments - .isNotEmpty == - true && - SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) - .segments[0] is StepLineSegment && - segmentProperties.stateProperties.oldSeriesRenderers.length - 1 >= - SegmentHelper.getSegmentProperties(SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).segments[currentSegmentIndex!]) - .seriesIndex == - true && - SeriesHelper.getSeriesRendererDetails(SegmentHelper.getSegmentProperties( - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .segments[currentSegmentIndex!]) - .oldSeriesRenderer!) - .segments - .isNotEmpty == - true) { - _currentSegment = SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .segments[currentSegmentIndex!] as StepLineSegment; - final SegmentProperties _currentSegmentProperties = - SegmentHelper.getSegmentProperties(_currentSegment); - SegmentProperties? _oldSegmentProperties; - _oldSegment = (SeriesHelper.getSeriesRendererDetails( - _currentSegmentProperties.oldSeriesRenderer!) - .segments - .length - - 1 >= - currentSegmentIndex! == - true) - ? SeriesHelper.getSeriesRendererDetails( - _currentSegmentProperties.oldSeriesRenderer!) - .segments[currentSegmentIndex!] as StepLineSegment? - : null; - if (_oldSegment != null) { - _oldSegmentProperties = - SegmentHelper.getSegmentProperties(_oldSegment!); - } - _oldX1 = _oldSegmentProperties?.x1; - _oldY1 = _oldSegmentProperties?.y1; - _oldX2 = _oldSegmentProperties?.x2; - _oldY2 = _oldSegmentProperties?.y2; - _oldX3 = _oldSegmentProperties?.x3; - _oldY3 = _oldSegmentProperties?.y3; - - if (_oldSegment != null && - (_oldX1!.isNaN || _oldX2!.isNaN) && - segmentProperties.stateProperties.oldAxisRenderers.isNotEmpty == - false) { - _oldXAxisRenderer = getOldAxisRenderer( - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .xAxisDetails! - .axisRenderer, - segmentProperties.stateProperties.oldAxisRenderers); - _oldYAxisRenderer = getOldAxisRenderer( - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .yAxisDetails! - .axisRenderer, - segmentProperties.stateProperties.oldAxisRenderers); - final ChartAxisRendererDetails _oldXAxisDetails = - AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); - final ChartAxisRendererDetails _oldYAxisDetails = - AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); - if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisDetails.visibleRange!.minimum != - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .yAxisDetails! - .visibleRange! - .minimum || - _oldYAxisDetails.visibleRange!.maximum != - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .yAxisDetails! - .visibleRange! - .maximum || - _oldXAxisDetails.visibleRange!.minimum != - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .xAxisDetails! - .visibleRange! - .minimum || - _oldXAxisDetails.visibleRange!.maximum != - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .xAxisDetails! - .visibleRange! - .maximum; - } - if (_needAnimate) { - final ChartAxisRendererDetails _oldXAxisDetails = - AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); - final ChartAxisRendererDetails _oldYAxisDetails = - AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); - _oldLocation = calculatePoint( - _x1Pos, - _y1Pos, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - _rect); - _oldX1 = _oldLocation!.x; - _oldY1 = _oldLocation!.y; - - _oldLocation = calculatePoint( - _x2Pos, - _y2Pos, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - _rect); - _oldX2 = _oldLocation!.x; - _oldY2 = _oldLocation!.y; - _oldLocation = calculatePoint( - segmentProperties.midX, - segmentProperties.midY, - _oldXAxisDetails, - _oldYAxisDetails, - segmentProperties.stateProperties.requireInvertedAxis, - segmentProperties.series, - _rect); - _oldX3 = _oldLocation!.x; - _oldY3 = _oldLocation!.y; - } - } - animateLineTypeSeries( - canvas, - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer), - strokePaint!, - animationFactor, - _currentSegmentProperties.x1, - _currentSegmentProperties.y1, - _currentSegmentProperties.x2, - _currentSegmentProperties.y2, - _oldX1, - _oldY1, - _oldX2, - _oldY2, - _currentSegmentProperties.x3, - _currentSegmentProperties.y3, - _oldX3, - _oldY3, - ); - } else { - if (segmentProperties.series.dashArray[0] != 0 && - segmentProperties.series.dashArray[1] != 0) { - segmentProperties.path - .moveTo(segmentProperties.x1, segmentProperties.y1); - segmentProperties.path - .lineTo(segmentProperties.x3, segmentProperties.y3); - segmentProperties.path - .lineTo(segmentProperties.x2, segmentProperties.y2); - drawDashedLine(canvas, segmentProperties.series.dashArray, strokePaint!, - segmentProperties.path); - } else { - canvas.drawLine(Offset(segmentProperties.x1, segmentProperties.y1), - Offset(segmentProperties.x3, segmentProperties.y3), strokePaint!); - canvas.drawLine(Offset(segmentProperties.x3, segmentProperties.y3), - Offset(segmentProperties.x2, segmentProperties.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 deleted file mode 100644 index 4f54eaf18..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; -import '../chart_series/series.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../utils/helper.dart'; -import 'chart_segment.dart'; - -/// Creates the segments for waterfall series. -/// -/// 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`. -class WaterfallSegment extends ChartSegment { - /// To find the x and y values of connector lines between each data point. - late double _x1, _y1, _x2, _y2; - - /// Get the connector line paint. - Paint? connectorLineStrokePaint; - - /// 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`. - late RRect segmentRect; - - /// Gets the color of the series. - @override - Paint getFillPaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final CartesianChartPoint? _currentPoint = - segmentProperties.currentPoint; - final bool hasPointColor = - segmentProperties.series.pointColorMapper != null; - - /// Get and set the paint options for waterfall series. - if (segmentProperties.series.gradient == null) { - fillPaint = Paint() - ..color = ((hasPointColor && _currentPoint!.pointColorMapper != null) - ? _currentPoint.pointColorMapper - : _currentPoint!.isIntermediateSum! - ? segmentProperties.intermediateSumColor ?? - segmentProperties.color! - : _currentPoint.isTotalSum! - ? segmentProperties.totalSumColor ?? - segmentProperties.color! - : _currentPoint.yValue < 0 == true - ? segmentProperties.negativePointsColor ?? - segmentProperties.color! - : segmentProperties.color!)! - ..style = PaintingStyle.fill; - } else { - fillPaint = getLinearGradientPaint( - segmentProperties.series.gradient!, - segmentProperties.currentPoint!.region!, - segmentProperties.stateProperties.requireInvertedAxis); - } - assert(segmentProperties.series.opacity >= 0 == true, - 'The opacity value of the waterfall series should be greater than or equal to 0.'); - assert(segmentProperties.series.opacity <= 1 == true, - 'The opacity value of the waterfall series should be less than or equal to 1.'); - fillPaint!.color = (segmentProperties.series.opacity < 1 == true && - fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) - : fillPaint!.color; - segmentProperties.defaultFillColor = fillPaint; - setShader(segmentProperties, fillPaint!); - return fillPaint!; - } - - /// Gets the border color of the series. - @override - Paint getStrokePaint() { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - strokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = segmentProperties.strokeWidth!; - segmentProperties.defaultStrokeColor = strokePaint; - segmentProperties.series.borderGradient != null - ? strokePaint!.shader = segmentProperties.series.borderGradient! - .createShader(segmentProperties.currentPoint!.region!) - : strokePaint!.color = segmentProperties.strokeColor!; - segmentProperties.series.borderWidth == 0 - ? strokePaint!.color = Colors.transparent - : strokePaint!.color; - return strokePaint!; - } - - /// Calculates the rendering bounds of a segment. - @override - void calculateSegmentPoints() {} - - /// Draws segment in series bounds. - @override - void onPaint(Canvas canvas) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(this); - final WaterfallSeries _series = - segmentProperties.series as WaterfallSeries; - CartesianChartPoint oldPaint; - final Path linePath = Path(); - - if (fillPaint != null) { - (_series.animationDuration > 0 && - segmentProperties - .stateProperties.renderingDetails.isLegendToggled == - false) - ? animateRangeColumn( - canvas, - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer), - fillPaint!, - segmentRect, - segmentProperties.oldPoint != null - ? segmentProperties.oldPoint!.region - : segmentProperties.oldRegion, - animationFactor) - : canvas.drawRRect(segmentRect, fillPaint!); - } - if (strokePaint != null) { - (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? drawDashedLine( - canvas, _series.dashArray, strokePaint!, segmentProperties.path) - : (_series.animationDuration > 0 && - segmentProperties - .stateProperties.renderingDetails.isLegendToggled == - false) - ? animateRangeColumn( - canvas, - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer), - strokePaint!, - segmentRect, - segmentProperties.oldPoint != null - ? segmentProperties.oldPoint!.region - : segmentProperties.oldRegion, - animationFactor) - : canvas.drawRRect(segmentRect, strokePaint!); - } - if (connectorLineStrokePaint != null && - segmentProperties.currentPoint!.overallDataPointIndex! > 0 == true) { - oldPaint = SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .dataPoints[ - segmentProperties.currentPoint!.overallDataPointIndex! - 1]; - _x1 = oldPaint.endValueRightPoint!.x; - _y1 = oldPaint.endValueRightPoint!.y; - if (segmentProperties.currentPoint!.isTotalSum! == true || - segmentProperties.currentPoint!.isIntermediateSum! == true) { - _x2 = segmentProperties.currentPoint!.endValueLeftPoint!.x; - _y2 = segmentProperties.currentPoint!.endValueLeftPoint!.y; - } else { - _x2 = segmentProperties.currentPoint!.originValueLeftPoint!.x; - _y2 = segmentProperties.currentPoint!.originValueLeftPoint!.y; - } - if (_series.animationDuration <= 0 || - animationFactor >= - segmentProperties.stateProperties.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); - } else { - canvas.drawLine( - 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 deleted file mode 100644 index e3769b932..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// This class renders the area series. -/// -/// To render an area chart, create an instance of [AreaSeries], and add it to the series collection property of [SfCartesianChart]. -/// The area chart shows the filled area to represent the data, but when there are more than a series, this may hide the other series. -/// To get rid of this, increase or decrease the transparency of the series. -/// -/// It provides options for color, opacity, border color, and border width to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=E_odUnOsBtQ} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - this.borderDrawMode = BorderDrawMode.top, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onRendererCreated: onRendererCreated, - onCreateRenderer: onCreateRenderer, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - trendlines: trendlines, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of area series. - /// - /// It has three types of [BorderDrawMode], - /// - /// * [BorderDrawMode.all] renders border for all the sides of area. - /// - /// * [BorderDrawMode.top] renders border only for top side. - /// - /// * [BorderDrawMode.excludeBottom] renders border except bottom side. - /// - /// Defaults to `BorderDrawMode.top`. - /// - /// Also refer [BorderDrawMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// AreaSeries( - /// borderWidth: 3, - /// borderColor: Colors.red, - /// borderDrawMode: BorderDrawMode.all, - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderDrawMode borderDrawMode; - - /// Create the area series renderer. - AreaSeriesRenderer createRenderer(ChartSeries series) { - AreaSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as AreaSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return AreaSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AreaSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.borderDrawMode == borderDrawMode && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - borderDrawMode, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index f96b7b0ea..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart +++ /dev/null @@ -1,357 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// This class has the properties of the bar series. -/// -/// To render a bar chart, create an instance of [BarSeries], and add it to the series collection property of [SfCartesianChart]. -/// The bar series are rectangular bars with heights or lengths proportional to their represented values.It has the property of spacing to provide -/// spacing between bars. -/// -/// Provides options for color, opacity, border color and border width to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=MHQzCN_jD1Q} -@immutable -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, - this.isTrackVisible = false, - 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 = BorderRadius.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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - List? dashArray, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - trendlines: trendlines, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - emptyPointSettings: emptyPointSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - initialSelectedDataIndexes: initialSelectedDataIndexes, - dashArray: dashArray); - - /// Color of the track. - /// - /// Defaults to `Colors.grey`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isTrackVisible: true, - /// trackColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackColor; - - /// Color of the track border. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackBorderColor; - - /// Width of the track border. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red, - /// trackBorderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackBorderWidth; - - /// Padding of the track. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isTrackVisible: true, - /// trackPadding: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackPadding; - - /// Spacing between the bars. The value ranges from 0 to 1. - /// - /// 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing also affects the height of the bar. For example, setting 20% spacing - /// and 100% height renders the bar with 80% of total height. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// spacing: 0.5 - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Customizes the corners of the bar. - /// - /// Each corner can be customized with desired - /// value or with a single value. - /// - /// Defaults to `Radius.zero`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// borderRadius: BorderRadius.all(Radius.circular(5)) - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderRadius borderRadius; - - /// Renders bars with track. - /// - /// Track is a rectangular bar rendered from the start to the - /// end of the axis. - /// - /// Bar series will be rendered above the track. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isTrackVisible: true - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isTrackVisible; - - /// Create the bar series renderer. - BarSeriesRenderer createRenderer(ChartSeries series) { - BarSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as BarSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return BarSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is BarSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 0d0a45ecb..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart +++ /dev/null @@ -1,317 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// 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]. -/// 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 -/// to customize the appearance. -/// -@immutable -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, - this.spacing = 0, - MarkerSettings? markerSettings, - DataLabelSettings? dataLabelSettings, - bool? isVisible, - bool? enableTooltip, - double? animationDuration, - Color? borderColor, - double? borderWidth, - LinearGradient? gradient, - LinearGradient? borderGradient, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? trendlines, - this.boxPlotMode = BoxPlotMode.normal, - this.showMean = true}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - dashArray: dashArray, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor ?? Colors.black, - borderWidth: borderWidth ?? 1, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - trendlines: trendlines); - - /// To change the box plot rendering mode. - /// - /// The box plot series rendering mode can be changed by - /// using [BoxPlotMode] property. The below values are applicable for this, - /// - /// * `BoxPlotMode.normal` - The quartile values are calculated by splitting the list and - /// by getting the median values. - /// * `BoxPlotMode.exclusive` - The quartile values are calculated by using the formula - /// (N+1) * P (N count, P percentile), and their index value starts - /// from 1 in the list. - /// * `BoxPlotMode.inclusive` - The quartile values are calculated by using the formula - /// (N−1) * P (N count, P percentile), and their index value starts - /// from 0 in the list. - /// - /// Also refer [BoxPlotMode]. - /// - /// Defaults to `BoxPlotMode.normal`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BoxAndWhiskerSeries( - /// boxPlotMode: BoxPlotMode.normal - /// ), - /// ], - /// ); - /// } - /// ``` - final BoxPlotMode boxPlotMode; - - /// Indication for mean value in box plot. - /// - /// If [showMean] property value is set to true, a cross symbol will be - /// displayed at the mean value, for each data point in box plot. Else, - /// it will not be displayed. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BoxAndWhiskerSeries( - /// showMean: false - /// ), - /// ], - /// ); - /// } - /// ``` - final bool showMean; - - /// Spacing between the box plots. - /// - /// The value ranges from 0 to 1, where 1 represents 100% and 0 represents 0% - /// of the available space. - /// - /// Spacing affects the width of the box plots. For example, setting 20% - /// spacing and 100% width renders the box plots with 80% of total width. - /// - /// Also refer [CartesianSeries.width]. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BoxAndWhiskerSeries( - /// spacing: 0.5, - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Create the Box and Whisker series renderer. - BoxAndWhiskerSeriesRenderer createRenderer(ChartSeries series) { - BoxAndWhiskerSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as BoxAndWhiskerSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return BoxAndWhiskerSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is BoxAndWhiskerSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.boxPlotMode == boxPlotMode && - other.showMean == showMean && - other.spacing == spacing && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - width, - markerSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - boxPlotMode, - showMean, - spacing, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} - -/// Represents the class for box plot quartile values. -class BoxPlotQuartileValues { - /// Creates an instance of box plot quartile values. - BoxPlotQuartileValues( - {this.minimum, - this.maximum, - //ignore: unused_element, avoid_unused_constructor_parameters - List? outliers, - this.upperQuartile, - this.lowerQuartile, - this.average, - this.median, - this.mean}); - - /// Specifies the value of minimum. - num? minimum; - - /// Specifies the value of maximum. - num? maximum; - - /// Specifies the list of outliers. - List? outliers = []; - - /// Specifies the value of the upper quartiles. - double? upperQuartile; - - /// Specifies the value of lower quartiles. - double? lowerQuartile; - - /// Specifies the value of average. - num? average; - - /// Specifies the value of median. - num? median; - - /// Specifies the mean value. - num? mean; -} 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 deleted file mode 100644 index 1882485bc..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// This class holds the properties of the bubble series. -/// -/// To render a bubble chart, create an instance of [BubbleSeries], and add it to the series collection property of [SfCartesianChart]. -/// A bubble chart requires three fields (X, Y, and Size) to plot a point. Here, [sizeValueMapper] is used to map the size of each bubble segment from the data source. -/// -/// Provide the options for color, opacity, border color, and border width to customize the appearance. -/// -@immutable -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, - 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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - SortingOrder? sortingOrder, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - sizeValueMapper: sizeValueMapper, - dataSource: dataSource, - trendlines: trendlines, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - markerSettings: markerSettings, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Maximum radius value of the bubble in the series. - /// - /// Defaults to `10`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// maximumRadius: 9 - /// ), - /// ], - /// ); - /// } - /// ``` - final num maximumRadius; - - /// Minimum radius value of the bubble in the series. - /// - /// Defaults to `3`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// minimumRadius: 9 - /// ), - /// ], - /// ); - /// } - /// ``` - final num minimumRadius; - - /// Create the bubble series renderer. - BubbleSeriesRenderer createRenderer(ChartSeries series) { - BubbleSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as BubbleSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return BubbleSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is BubbleSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.maximumRadius == maximumRadius && - other.minimumRadius == minimumRadius && - other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - maximumRadius, - minimumRadius, - initialSelectedDataIndexes, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index c88efc861..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart +++ /dev/null @@ -1,215 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'financial_series_base.dart'; - -/// 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]. -/// 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. -/// -/// Provides options for color, opacity, border color, and border width -/// to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=g5cniDExpRw} -@immutable -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, - double? borderWidth, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes, - bool? showIndicationForSameValues, - List? trendlines, - }) : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - dashArray: dashArray, - xValueMapper: xValueMapper, - lowValueMapper: lowValueMapper, - highValueMapper: highValueMapper, - // ignore: unnecessary_null_comparison - openValueMapper: openValueMapper != null - ? (int index) => openValueMapper(dataSource[index], index) - : null, - // ignore: unnecessary_null_comparison - closeValueMapper: closeValueMapper != null - ? (int index) => closeValueMapper(dataSource[index], index) - : null, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderWidth: borderWidth ?? 2, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - bearColor: bearColor ?? Colors.red, - bullColor: bullColor ?? Colors.green, - enableSolidCandles: enableSolidCandles ?? false, - initialSelectedDataIndexes: initialSelectedDataIndexes, - showIndicationForSameValues: showIndicationForSameValues ?? false, - trendlines: trendlines); - - /// Create the candle series renderer. - CandleSeriesRenderer createRenderer(ChartSeries series) { - CandleSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as CandleSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return CandleSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is CandleSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.lowValueMapper == lowValueMapper && - other.highValueMapper == highValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.bearColor == bearColor && - other.bullColor == bullColor && - other.enableSolidCandles == enableSolidCandles && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderWidth == borderWidth && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.showIndicationForSameValues == showIndicationForSameValues && - other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - lowValueMapper, - highValueMapper, - openValueMapper, - closeValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - bearColor, - bullColor, - enableSolidCandles, - emptyPointSettings, - dataLabelSettings, - isVisible, - enableTooltip, - animationDuration, - borderWidth, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - dashArray, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - showIndicationForSameValues, - trendlines, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 3eb750f93..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart +++ /dev/null @@ -1,352 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// This class has the properties of the column series. -/// -/// To render a column chart, create an instance of [ColumnSeries], and add it to the series collection property of [SfCartesianChart]. -/// The column series is a rectangular column with heights or lengths proportional to the values that they represent. It has the spacing -/// property to separate the column. -/// -/// Provide the options of color, opacity, border color, and border width to customize the appearance. -/// -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, - this.isTrackVisible = false, - 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 = BorderRadius.zero, - bool? enableTooltip, - double? animationDuration, - this.trackColor = Colors.grey, - this.trackBorderColor = Colors.transparent, - this.trackBorderWidth = 1, - this.trackPadding = 0, - Color? borderColor, - double? borderWidth, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - List? dashArray, - SeriesRendererCreatedCallback? onRendererCreated, - List? initialSelectedDataIndexes, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - }) : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - trendlines: trendlines, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - dashArray: dashArray, - onRendererCreated: onRendererCreated, - initialSelectedDataIndexes: initialSelectedDataIndexes, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress); - - /// Color of the track. - /// - /// Defaults to `Colors.grey`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// isTrackVisible: true, - /// trackColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackColor; - - /// Color of the track border. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackBorderColor; - - /// Width of the track border. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red, - /// trackBorderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackBorderWidth; - - /// Padding of the track. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// isTrackVisible: true, - /// trackPadding: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackPadding; - - /// Spacing between the columns. The value ranges from 0 to 1. - /// 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing also affects the width of the column. For example, setting 20% spacing - /// and 100% width renders the column with 80% of total width. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// isTrackVisible: true, - /// spacing: 0.5 - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Renders column with track. - /// - /// Track is a rectangular bar rendered from the start to the end of the axis. - /// Column series will be rendered above the track. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// isTrackVisible: true - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isTrackVisible; - - /// Customizes the corners of the column. - /// - /// Each corner can be customized with a desired value or with a single value. - /// - /// Defaults to `Radius.zero`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// borderRadius: BorderRadius.all(Radius.circular(5)) - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderRadius borderRadius; - - /// Create the column series renderer. - ColumnSeriesRenderer createRenderer(ChartSeries series) { - ColumnSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as ColumnSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return ColumnSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ColumnSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.initialSelectedDataIndexes == initialSelectedDataIndexes && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - initialSelectedDataIndexes, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart deleted file mode 100644 index 484757557..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart +++ /dev/null @@ -1,424 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../../charts.dart'; - -/// This class has the properties of the error bar series. -/// -/// To render a error bar chart, create an instance of [ErrorBarSeries], -/// and add it to the series collection property of [SfCartesianChart]. -/// -class ErrorBarSeries extends XyDataSeries { - /// Creating an argument constructor of ErrorBarSeries class. - ErrorBarSeries( - {ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - required List dataSource, - required ChartValueMapper xValueMapper, - required ChartValueMapper yValueMapper, - ChartValueMapper? sortFieldValueMapper, - ChartValueMapper? pointColorMapper, - SortingOrder? sortingOrder, - String? xAxisName, - String? yAxisName, - String? name, - Color? color, - double? width, - EmptyPointSettings? emptyPointSettings, - bool? isVisible, - double? animationDuration, - double? opacity, - double animationDelay = 1500, - List? dashArray, - SeriesRendererCreatedCallback? onRendererCreated, - String? legendItemText, - bool isVisibleInLegend = false, - LegendIconType legendIconType = LegendIconType.verticalLine, - this.type = ErrorBarType.fixed, - this.direction = Direction.both, - this.mode = RenderingMode.vertical, - this.verticalErrorValue = 3, - this.horizontalErrorValue = 1, - this.verticalPositiveErrorValue = 3, - this.horizontalPositiveErrorValue = 1, - this.verticalNegativeErrorValue = 3, - this.horizontalNegativeErrorValue = 1, - this.capLength = 10, - this.onRenderDetailsUpdate, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 2, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - animationDuration: animationDuration, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - dashArray: dashArray, - onRendererCreated: onRendererCreated, - onCreateShader: onCreateShader, - ); - - /// Type of the error bar. - /// - /// Defaults to `ErrorBarType.fixed`. - /// - /// Other values are `ErrorBarType.percentage`, `ErrorBarType.standardDeviation`, - /// `ErrorBarType.custom`, `ErrorBarType.standardError`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// type:ErrorBarType.fixed, - /// ), - /// ], - /// ); - /// } - /// ``` - final ErrorBarType? type; - - /// Direction of error bar. - /// - /// Defaults to `Direction.both`. - /// - /// Also refer [Direction]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// direction: Direction.plus, - /// ), - /// ], - /// ); - /// } - /// ``` - final Direction? direction; - - /// Mode of error bar. - /// - /// Defaults to `RenderingMode.vertical`. - /// - /// Other values are `RenderingMode.horizontal` and `RenderingMode.both`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// mode: RenderingMode.both, - /// ), - /// ], - /// ); - /// } - /// ``` - final RenderingMode? mode; - - /// Vertical error value in Y direction. - /// - /// Defaults to `3`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// verticalErrorValue: 2, - /// mode: RenderingMode.vertical, - /// ), - /// ], - /// ); - /// } - /// ``` - final double? verticalErrorValue; - - /// Horizontal error value in X direction.. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// horizontalErrorValue:2, - /// mode: RenderingMode.horizontal, - /// ), - /// ], - /// ); - /// } - /// ``` - final double? horizontalErrorValue; - - /// Vertical error value in positive Y direction. - /// It's only applicable for 'ErrorBarType.custom'. - /// - /// Defaults to `3`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// type:ErrorBarType.custom, - /// verticalPositiveErrorValue:2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? verticalPositiveErrorValue; - - /// Horizontal error value in positive X direction. - /// It's only applicable for 'ErrorBarType.custom'. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// type:ErrorBarType.custom, - /// horizontalPositiveErrorValue:2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? horizontalPositiveErrorValue; - - /// Vertical error value in negative Y direction. - /// It's only applicable for 'ErrorBarType.custom'. - /// - /// Defaults to `3`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// type:ErrorBarType.custom, - /// verticalNegativeErrorValue:2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? verticalNegativeErrorValue; - - /// Horizontal error value in negative X direction. - /// It's only applicable for 'ErrorBarType.custom'. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// type:ErrorBarType.custom, - /// horizontalNegativeErrorValue:2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? horizontalNegativeErrorValue; - - /// Length of the error bar's cap. - /// - /// Defaults to `10`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// capLength:20.0, - /// ), - /// ], - /// ); - /// } - /// ``` - final double? capLength; - - /// Callback which gets called on error bar render. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ErrorBarSeries( - /// onRenderDetailsUpdate: (ErrorBarRenderDetails errorBarRenderDetails){ - /// print(args.pointIndex); - /// print(args.viewPortPointIndex); - /// print(args.calculatedErrorBarValues!.horizontalPositiveErrorValue); - /// print(args.calculatedErrorBarValues!.horizontalNegativeErrorValue); - /// print(args.calculatedErrorBarValues!.verticalPositiveErrorValue); - /// print(args.calculatedErrorBarValues!.verticalNegativeErrorValue); - /// } - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartErrorBarRenderCallback? onRenderDetailsUpdate; - - /// Create the error bar series renderer. - ErrorBarSeriesRenderer createRenderer(ChartSeries series) { - ErrorBarSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as ErrorBarSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return ErrorBarSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ErrorBarSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.width == width && - other.emptyPointSettings == emptyPointSettings && - other.isVisible == isVisible && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.type == type && - other.direction == direction && - other.mode == mode && - other.verticalErrorValue == verticalErrorValue && - other.horizontalErrorValue == horizontalErrorValue && - other.verticalPositiveErrorValue == verticalPositiveErrorValue && - other.horizontalPositiveErrorValue == horizontalPositiveErrorValue && - other.verticalNegativeErrorValue == verticalNegativeErrorValue && - other.horizontalNegativeErrorValue == horizontalNegativeErrorValue && - other.capLength == capLength && - other.onCreateShader == onCreateShader && - other.onRenderDetailsUpdate == onRenderDetailsUpdate; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - width, - emptyPointSettings, - isVisible, - dashArray, - animationDuration, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - type, - direction, - mode, - verticalErrorValue, - horizontalErrorValue, - verticalPositiveErrorValue, - verticalNegativeErrorValue, - horizontalPositiveErrorValue, - horizontalNegativeErrorValue, - capLength, - onRenderDetailsUpdate - ]; - return hashList(values); - } -} - -/// Represents the error values of error bar. -class ChartErrorValues { - /// Creates an instance of chart error values. - ChartErrorValues( - {this.errorX, this.errorY, this.customNegativeX, this.customNegativeY}); - - /// Specifies the value of x. - num? errorX; - - /// Specifies the value of y. - num? errorY; - - /// Specifies the value of x in custom type error bar. - num? customNegativeX; - - /// Specifies the value of y in custom type error bar. - num? customNegativeY; -} - -/// Holds the values related to standard error and standard deviation error bars. -class ErrorBarMean { - /// Creates an instance of ErrorBarMean. - ErrorBarMean( - {this.verticalSquareRoot, - this.horizontalSquareRoot, - this.verticalMean, - this.horizontalMean}); - - /// Mean's required square root value of all data points in y direction. - final num? verticalSquareRoot; - - /// Mean's required square root value of all data points in x direction. - final num? horizontalSquareRoot; - - /// Required mean value of all data points in y direction. - final num? verticalMean; - - /// Required mean value of all data points in x direction. - final num? horizontalMean; -} 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 deleted file mode 100644 index 925447f16..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the [FastLineSeries]. -/// -/// [FastLineSeries] is a line chart, but it loads faster than [LineSeries]. -/// -/// You can use this when there are large number of points to be loaded in a chart. To render a fast line chart, -/// create an instance of [FastLineSeries], and add it to the series collection property of [SfCartesianChart]. -/// -/// The following properties are used to customize the appearance of fast line segment: -/// -/// * color - Changes the color of the line. -/// * opacity - Controls the transparency of the chart series. -/// * width - Changes the stroke width of the line. -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - trendlines: trendlines, - color: color, - width: width ?? 2, - gradient: gradient, - dashArray: dashArray, - markerSettings: markerSettings, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Create the fastline series renderer. - FastLineSeriesRenderer createRenderer(ChartSeries series) { - FastLineSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as FastLineSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return FastLineSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is FastLineSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.gradient == gradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - gradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 641b6e1f6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../common/data_label.dart'; -import '../common/marker.dart'; -import '../trendlines/trendlines.dart'; -import 'xy_data_series.dart'; - -/// Represents the financial series base. -abstract class FinancialSeriesBase extends XyDataSeries { - /// Creates an instance of financial series base. - FinancialSeriesBase( - {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, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - this.enableSolidCandles, - this.bearColor, - this.bullColor, - double? opacity, - double? animationDelay, - this.showIndicationForSameValues = false, - List? trendlines, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : dashArray = dashArray ?? [0, 0], - spacing = spacing ?? 0, - super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - lowValueMapper: lowValueMapper, - highValueMapper: highValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - initialSelectedDataIndexes: initialSelectedDataIndexes, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - trendlines: trendlines); - - /// Specifies the volume value mapper. - final ChartIndexedValueMapper? volumeValueMapper; - - /// Specifies the open value mapper. - final ChartIndexedValueMapper? openValueMapper; - - /// Specifies the close value mapper. - final ChartIndexedValueMapper? closeValueMapper; - - /// Specifies the bear color. - final Color? bearColor; - - /// Specifies the bull color. - final Color? bullColor; - - /// Specifies whether the solid candles. - final bool? enableSolidCandles; - - /// Specifies the dash array. - @override - final List dashArray; - - /// Spacing between the columns. The value ranges from 0 to 1. - /// 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing also affects the width of the column. For example, setting 20% spacing - /// and 100% width renders the column with 80% of total width. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HiloSeries( - /// spacing: 0.5, - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// If it is set to true, a small vertical line will be rendered. Else nothing will - /// be rendered for that specific data point and left as a blank area. - /// - /// This is applicable for the following series types: - /// * HiLo (High low) - /// * OHLC (Open high low close) - /// * Candle - /// - /// Defaults to `false`. - final bool showIndicationForSameValues; -} 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 deleted file mode 100644 index cff4b9f56..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart +++ /dev/null @@ -1,196 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'financial_series_base.dart'; - -/// Renders the hilo series. -/// -/// [HiloSeries] illustrates the price movements in stock using the high and low values. -/// -/// To render a hilo chart, create an instance of [HiloSeries], and add it to the series collection property of [SfCartesianChart]. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - double? spacing, - List? initialSelectedDataIndexes, - bool? showIndicationForSameValues, - List? trendlines, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - }) : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - dashArray: dashArray, - spacing: spacing, - xValueMapper: xValueMapper, - lowValueMapper: lowValueMapper, - highValueMapper: highValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - color: color, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderWidth: borderWidth ?? 2, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - showIndicationForSameValues: showIndicationForSameValues ?? false, - initialSelectedDataIndexes: initialSelectedDataIndexes, - trendlines: trendlines); - - /// Create the hilo series renderer. - HiloSeriesRenderer createRenderer(ChartSeries series) { - HiloSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as HiloSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return HiloSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is HiloSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.lowValueMapper == lowValueMapper && - other.highValueMapper == highValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.bearColor == bearColor && - other.bullColor == bullColor && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderWidth == borderWidth && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.spacing == spacing && - other.showIndicationForSameValues == showIndicationForSameValues && - other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - lowValueMapper, - highValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - bearColor, - bullColor, - emptyPointSettings, - dataLabelSettings, - isVisible, - enableTooltip, - animationDuration, - borderWidth, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - dashArray, - opacity, - animationDelay, - spacing, - onRendererCreated, - initialSelectedDataIndexes, - showIndicationForSameValues, - trendlines, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 81959315d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart +++ /dev/null @@ -1,220 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'financial_series_base.dart'; - -/// Renders the hilo open close series. -/// -/// Hilo open close series is used to represent the low, high, open and closing values over time. -/// -/// To render a hilo open close chart, create an instance of [HiloOpenCloseSeries], -/// and add it to the series collection property of [SfCartesianChart]. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=g5cniDExpRw} -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - double? spacing, - List? initialSelectedDataIndexes, - bool? showIndicationForSameValues, - List? trendlines, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - dashArray: dashArray, - spacing: spacing, - xValueMapper: xValueMapper, - lowValueMapper: lowValueMapper, - highValueMapper: highValueMapper, - // ignore: unnecessary_null_comparison - openValueMapper: openValueMapper != null - ? (int index) => openValueMapper(dataSource[index], index) - : null, - // ignore: unnecessary_null_comparison - closeValueMapper: closeValueMapper != null - ? (int index) => closeValueMapper(dataSource[index], index) - : null, - // ignore: unnecessary_null_comparison - volumeValueMapper: volumeValueMapper != null - ? (int index) => volumeValueMapper(dataSource[index], index) - : null, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderWidth: borderWidth ?? 2, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - bearColor: bearColor ?? Colors.red, - bullColor: bullColor ?? Colors.green, - initialSelectedDataIndexes: initialSelectedDataIndexes, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - showIndicationForSameValues: showIndicationForSameValues ?? false, - trendlines: trendlines); - - /// Create the hilo open close series renderer. - HiloOpenCloseSeriesRenderer createRenderer(ChartSeries series) { - HiloOpenCloseSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as HiloOpenCloseSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return HiloOpenCloseSeriesRenderer(); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is HiloOpenCloseSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.lowValueMapper == lowValueMapper && - other.highValueMapper == highValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.volumeValueMapper == volumeValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.bearColor == bearColor && - other.bullColor == bullColor && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderWidth == borderWidth && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.spacing == spacing && - other.showIndicationForSameValues == showIndicationForSameValues && - other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - lowValueMapper, - highValueMapper, - openValueMapper, - closeValueMapper, - volumeValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - bearColor, - bullColor, - emptyPointSettings, - dataLabelSettings, - isVisible, - enableTooltip, - animationDuration, - borderWidth, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - dashArray, - opacity, - animationDelay, - spacing, - onRendererCreated, - initialSelectedDataIndexes, - showIndicationForSameValues, - trendlines, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index fb7ad47e6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart +++ /dev/null @@ -1,480 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// This class has the properties of the histogram series. -/// -/// To render a histogram chart, create an instance of [HistogramSeries], and add it to the series collection property of [SfCartesianChart]. -/// The histogram series is a rectangular histogram with heights or lengths proportional to the values that they represent. It has the spacing -/// property to separate the histogram. -/// -/// Provide the options of color, opacity, border color, and border width to customize the appearance. -/// -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, - this.isTrackVisible = false, - 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 = BorderRadius.zero, - bool? enableTooltip, - double? animationDuration, - this.trackColor = Colors.grey, - this.trackBorderColor = Colors.transparent, - this.trackBorderWidth = 1, - this.trackPadding = 0, - Color? borderColor, - double? borderWidth, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - List? dashArray, - this.binInterval, - this.showNormalDistributionCurve = false, - this.curveColor = Colors.blue, - this.curveWidth = 2, - this.curveDashArray, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - trendlines: trendlines, - color: color, - width: width ?? 0.95, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - dashArray: dashArray); - - /// Interval value by which the data points are grouped and rendered as bars, in histogram series. - /// - /// For example, if the [binInterval] is set to 20, the x-axis will split with 20 as the interval. - /// The first bar in the histogram represents the count of values lying between 0 to 20 - /// in the provided data and the second bar will represent 20 to 40. - /// - /// If no value is specified for this property, then the interval will be calculated - /// automatically based on the data points count and value. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// binInterval: 4 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? 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. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// showNormalDistributionCurve: true - /// ), - /// ], - /// ); - /// } - /// ``` - final bool showNormalDistributionCurve; - - /// Color of the normal distribution spline curve. - /// - /// Defaults to `Colors.blue`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// curveColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color curveColor; - - /// Width of the normal distribution spline curve. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// curveWidth: 4 - /// ), - /// ], - /// ); - /// } - /// ``` - final double curveWidth; - - /// Dash array of the normal distribution spline curve. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// curveDashArray: [2, 3] - /// ), - /// ], - /// ); - /// } - /// ``` - final List? curveDashArray; - - /// Color of the track. - /// - /// Defaults to `Colors.grey`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// isTrackVisible: true, - /// trackColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackColor; - - /// Color of the track border. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackBorderColor; - - /// Width of the track border. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red, - /// trackBorderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackBorderWidth; - - /// Padding of the track. - /// - /// By default, track will be rendered based on the bar’s available width and spacing. - /// If you wish to change the track width, you can use this property. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// isTrackVisible: true, - /// trackPadding: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackPadding; - - /// Spacing between the bars in histogram series. - /// - /// The value ranges from 0 to 1. 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing also affects the width of the bar. For example, setting 20% spacing - /// and 100% width renders the bar with 80% of total width. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// spacing: 0.5, - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Renders the bar in histogram series with track. - /// - /// Track is a rectangular bar rendered from the start to the end of the axis. - /// Bars in the histogram will be rendered above the track. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// isTrackVisible: true, - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isTrackVisible; - - /// Customizes the corners of the bars in histogram series. - /// - /// Each corner can be customized individually or can be customized together, by specifying a single value. - /// - /// Defaults to `Radius.zero`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// HistogramSeries( - /// borderRadius: BorderRadius.circular(5), - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderRadius borderRadius; - - /// Create the histogram series renderer. - HistogramSeriesRenderer createRenderer(ChartSeries series) { - HistogramSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as HistogramSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return HistogramSeriesRenderer(); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is HistogramSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.binInterval == binInterval && - other.showNormalDistributionCurve == showNormalDistributionCurve && - other.curveColor == curveColor && - other.curveWidth == curveWidth && - other.curveDashArray == curveDashArray && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - binInterval, - showNormalDistributionCurve, - curveColor, - curveWidth, - curveDashArray, - isTrackVisible, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} - -/// Represents the histogram values. -class HistogramValues { - /// Creates an instance of histogram values. - HistogramValues( - {this.sDValue, this.mean, this.binWidth, this.yValues, this.minValue}); - - /// Specifies the value of SD. - num? sDValue; - - /// Specifies the value of mean. - num? mean; - - /// Specifies the value of bin width. - num? binWidth; - - /// Specifies the minimum value. - num? minValue; - - /// Specifies the list of y values. - List? yValues = []; -} 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 deleted file mode 100644 index 07497cdad..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the line series. -/// -/// This class holds the properties of line series. To render a line chart, -/// create an instance of [LineSeries], and add it to the series collection -/// property of [SfCartesianChart]. A line chart requires two fields (X and Y) -/// to plot a point. -/// -/// Provide the options for color, opacity, border color, and border width to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=zhcxdh4-Jt8} -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - SortingOrder? sortingOrder, - String? legendItemText, - double? opacity, - double? animationDelay, - List? initialSelectedDataIndexes, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onRendererCreated: onRendererCreated, - onCreateRenderer: onCreateRenderer, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 2, - markerSettings: markerSettings, - emptyPointSettings: emptyPointSettings, - trendlines: trendlines, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Create the line series renderer. - LineSeriesRenderer createRenderer(ChartSeries series) { - LineSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as LineSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return LineSeriesRenderer(); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is LineSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.gradient == gradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - gradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 4425de556..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the range area series. -/// -/// To render a range area chart, create an instance of [RangeAreaSeries] and add to the series collection property of [SfCartesianChart]. -/// [RangeAreaSeries] is similar to [Areaseries] requires two Y values for a point, data should contain high and low values. -/// -/// High and low value specify the maximum and minimum range of the point. -/// -/// [highValueMapper] - Field in the data source, which is considered as high value for the data points. -/// [lowValueMapper] - Field in the data source, which is considered as low value for the data points. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - this.borderDrawMode = RangeAreaBorderMode.all, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - trendlines: trendlines, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of area series. - /// - /// Defaults to `BorderDrawMode.top`. - /// - /// Also refer [BorderDrawMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeAreaSeries( - /// borderDrawMode: BorderDrawMode.all, - /// ), - /// ], - /// ); - /// } - /// ``` - final RangeAreaBorderMode borderDrawMode; - - /// Create the range area series renderer. - RangeAreaSeriesRenderer createRenderer(ChartSeries series) { - RangeAreaSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as RangeAreaSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return RangeAreaSeriesRenderer(); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is RangeAreaSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.borderDrawMode == borderDrawMode && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - borderDrawMode, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 0669e8b49..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart +++ /dev/null @@ -1,361 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the range column series. -/// -/// To render a range column chart, create an instance of [RangeColumnSeries] and add to the series collection property of [SfCartesianChart]. -/// -/// [RangeColumnSeries] is similar to column series but requires two Y values for a point, -/// your data should contain high and low values. -/// High and low value specify the maximum and minimum range of the point. -/// -/// * [highValueMapper] - Field in the data source, which is considered as high value for the data points. -/// * [lowValueMapper] - Field in the data source, which is considered as low value for the data points. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} -@immutable -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, - this.isTrackVisible = false, - 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 = BorderRadius.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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - List? dashArray, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - trendlines: trendlines, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - dashArray: dashArray, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Color of the track. - /// - /// Defaults to `Colors.grey`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// isTrackVisible: true, - /// trackColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackColor; - - /// Color of the track border. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackBorderColor; - - /// Width of the track border. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red, - /// trackBorderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackBorderWidth; - - /// Padding of the track. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// isTrackVisible: true, - /// trackPadding: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackPadding; - - /// Spacing between the columns. - /// - /// The value ranges from 0 to 1. 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing also affects the width of the range column. For example, setting 20% spacing - /// and 100% width renders the column with 80% of total width. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// spacing: 0.5, - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Renders range column with track. - /// - /// Track is a rectangular bar rendered from the start to the end of the axis. Range Column Series will be rendered - /// above the track. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// isTrackVisible: true, - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isTrackVisible; - - /// Customizes the corners of the range column. - /// - /// Each corner can be customized with a desired value or with a single value. - /// - /// Defaults to `Radius.zero`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// borderRadius: BorderRadius.circular(5), - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderRadius borderRadius; - - /// Create the range column series renderer. - RangeColumnSeriesRenderer createRenderer(ChartSeries series) { - RangeColumnSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as RangeColumnSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return RangeColumnSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is RangeColumnSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index a7587a0e0..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the scatter series. -/// -/// To render a scatter chart, create an instance of [ScatterSeries], and add it to the series collection property of [SfCartesianChart]. -/// -/// The following properties, such as [color], [opacity], [borderWidth], [borderColor] can be used to customize the appearance of the scatter segment. -/// -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - SortingOrder? sortingOrder, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - isVisible: isVisible, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - trendlines: trendlines, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Create the scatter series renderer. - ScatterSeriesRenderer createRenderer(ChartSeries series) { - ScatterSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as ScatterSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return ScatterSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ScatterSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index efb9dc93f..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart +++ /dev/null @@ -1,1756 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; - -import '../../common/rendering_details.dart'; -import '../../common/template/rendering.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/interactive_tooltip.dart'; -import '../trendlines/trendlines.dart'; -import '../user_interaction/trackball.dart'; -import '../user_interaction/trackball_painter.dart'; -import '../user_interaction/trackball_template.dart'; -import '../utils/helper.dart'; -import 'series_renderer_properties.dart'; - -/// This class has the properties of the cartesian series. -/// -/// The cartesian series provides a variety of options, such as animation, dynamic animation, transpose, color palette, -/// color mapping to customize the Cartesian chart. The chart’s data source can be sorted using the sorting order and -/// [sortFieldValueMapper] properties of series. -/// -/// Provides the options for animation, color palette, sorting, and empty point mode to customize the charts. -/// -abstract class CartesianSeries extends ChartSeries { - /// Creating an argument constructor of [CartesianSeries] class. - CartesianSeries( - {this.key, - this.xValueMapper, - this.yValueMapper, - this.dataLabelMapper, - this.name, - required this.dataSource, - this.xAxisName, - this.yAxisName, - this.sizeValueMapper, - this.pointColorMapper, - this.color, - this.legendItemText, - this.sortFieldValueMapper, - this.gradient, - this.borderGradient, - this.width, - this.highValueMapper, - this.lowValueMapper, - this.intermediateSumPredicate, - this.totalSumPredicate, - this.trendlines, - this.onRendererCreated, - this.onCreateRenderer, - this.onPointTap, - this.onPointDoubleTap, - this.onPointLongPress, - this.onCreateShader, - MarkerSettings? markerSettings, - bool? isVisible, - bool? enableTooltip, - EmptyPointSettings? emptyPointSettings, - DataLabelSettings? dataLabelSettings, - double? animationDuration, - List? dashArray, - List? initialSelectedDataIndexes, - Color? borderColor, - double? borderWidth, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - double? opacity, - double? animationDelay, - SortingOrder? sortingOrder}) - : isVisible = isVisible ?? true, - markerSettings = markerSettings ?? const MarkerSettings(), - dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), - enableTooltip = enableTooltip ?? true, - emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), - dashArray = dashArray ?? [0, 0], - initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - animationDuration = animationDuration ?? 1500, - borderColor = borderColor ?? Colors.transparent, - selectionBehavior = selectionBehavior ?? SelectionBehavior(), - legendIconType = legendIconType ?? LegendIconType.seriesType, - isVisibleInLegend = isVisibleInLegend ?? true, - borderWidth = borderWidth ?? 0, - opacity = opacity ?? 1, - animationDelay = animationDelay ?? 0, - sortingOrder = sortingOrder ?? SortingOrder.none, - super( - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - animationDelay: animationDelay, - opacity: opacity); - - /// Key to identify a series in a collection. - /// - /// On specifying [ValueKey] as the series [key], existing series index can be changed in the series collection without losing its state. - /// - /// When a new series is added dynamically to the collection, existing series index will be changed. On that case, - /// the existing series and its state will be linked based on its chart type and this key value. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// key: const ValueKey('line_series_key'), - /// ), - /// ], - /// ); - /// } - /// ``` - final ValueKey? key; - - /// Used to create the renderer for custom series. - /// - /// This is applicable only when the custom series is defined in the sample - /// and for built-in series types, it is not applicable. - /// - /// Renderer created in this will hold the series state and - /// this should be created for each series. [onCreateRenderer] callback - /// function should return the renderer class and should not return null. - /// - /// Series state will be created only once per series and will not be created - /// again when we update the series. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onCreateRenderer:(ChartSeries series){ - /// return CustomLinerSeriesRenderer(); - /// } - /// ), - /// ], - /// ); - /// } - /// class CustomLinerSeriesRenderer extends LineSeriesRenderer { - /// CustomLinerSeriesRenderer(this.series); - /// final ColumnSeries series; - /// - /// @override - /// int get currentSegmentIndex => super.currentSegmentIndex!; - /// - /// @override - /// Paint getFillPaint() { - /// final Paint customerFillPaint = Paint(); - /// customerFillPaint.color = series.dataSource[currentSegmentIndex].y > 30 - /// ? Colors.red - /// : Colors.green; - /// customerFillPaint.style = PaintingStyle.fill; - /// return customerFillPaint; - /// } - /// - /// @override - /// void onPaint(Canvas canvas) { - /// super.onPaint(canvas); - /// } - /// } - /// ``` - final ChartSeriesRendererFactory? onCreateRenderer; - - /// Triggers when the series renderer is created. - /// - /// Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// ChartSeriesController? _chartSeriesController; - /// return Column( - /// children: [ - /// SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ), - /// ] - /// ), - /// TextButton( - /// child: Text("Animate series"), - /// onPressed: () { - /// _chartSeriesController?.animate(); - /// } - /// ) - /// ] - /// ); - /// } - /// ``` - final SeriesRendererCreatedCallback? onRendererCreated; - - /// Called when tapped on the chart data point. - /// - /// The user can fetch the series index, point index, viewport point index and - /// data of the tapped data point. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartPointInteractionCallback? onPointTap; - - /// Called when double tapped on the chart data point. - /// - /// The user can fetch the series index, point index, viewport point index and - /// data of the double-tapped data point. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointDoubleTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartPointInteractionCallback? onPointDoubleTap; - - /// Called when long pressed on the chart data point. - /// - /// The user can fetch the series index, point index, viewport point index and - /// data of the long-pressed data point. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointLongPress: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartPointInteractionCallback? onPointLongPress; - - /// Data required for rendering the series. - /// - /// If no data source is specified, empty chart will be rendered without series. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - @override - final List dataSource; - - /// Field in the data source, which is considered as x-value. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// ), - /// ], - /// ); - /// } - /// - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - @override - final ChartIndexedValueMapper? xValueMapper; - - /// Field in the data source, which is considered as y-value. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// ), - /// ], - /// ); - /// } - /// - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - @override - final ChartIndexedValueMapper? yValueMapper; - - /// Field in the data source, which is considered as fill color for the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (ColumnColors sales, _) => sales.x, - /// yValueMapper: (ColumnColors sales, _) => sales.y, - /// pointColorMapper: (ColumnColors sales, _) => sales.pointColorMapper, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ColumnColors(1991, 7.8, const Color.fromRGBO(0, 0, 255, 1)), - /// ColumnColors(1992, 6.5, const Color.fromRGBO(255, 0, 0, 1)), - /// ColumnColors(1993, 6.0, const Color.fromRGBO(255, 100, 102, 1)), - /// ]; - /// class ColumnColors { - /// ColumnColors(this.x, this.y,this.pointColorMapper); - /// final num x; - /// final num y; - /// final Color pointColorMapper; - /// } - /// ``` - @override - final ChartIndexedValueMapper? pointColorMapper; - - /// Field in the data source, which is considered as text for the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// dataSource: [ - /// SalesData(DateTime(2005, 0, 1), 'India', 16), - /// SalesData(DateTime(2006, 0, 1), 'China', 12), - /// SalesData(DateTime(2007, 0, 1), 'USA',18), - /// ], - /// dataLabelSettings: DataLabelSettings(isVisible:true), - /// dataLabelMapper: (SalesData data, _) => data.category, - /// ), - /// ], - /// ); - /// } - /// class SalesData { - /// SalesData(this.year, this.category, this.sales1); - /// final DateTime year; - /// final String category; - /// final int sales1; - /// } - /// ``` - @override - final ChartIndexedValueMapper? dataLabelMapper; - - /// Field in the data source, which is considered as size of the bubble for - /// all the data points. - /// - /// _Note:_ This is applicable only for bubble series. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// dataSource: chartData, - /// sizeValueMapper: (BubbleColors sales, _) => sales.bubbleSize, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// BubbleColors(92.2, 7.8, 1.347), - /// BubbleColors(74, 6.5, 1.241), - /// BubbleColors(90.4, 6.0, 0.238), - /// BubbleColors(99.4, 2.2, 0.197), - /// ]; - /// class BubbleColors { - /// BubbleColors(this.year, this.growth,[this.bubbleSize]); - /// final num year; - /// final num growth; - /// final num bubbleSize; - /// } - /// ``` - final ChartIndexedValueMapper? sizeValueMapper; - - /// Field in the data source, which is considered as high value for the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// dataSource: [ - /// SalesData(2005, 24, 16), - /// SalesData(2006, 22, 12), - /// SalesData(2007, 31, 18), - /// ], - /// xValueMapper: (SalesData data, _) => data.year, - /// lowValueMapper: (SalesData data, _) => data.low, - /// highValueMapper: (SalesData data, _) => data.high, - /// ), - /// ], - /// ); - /// } - /// class SalesData { - /// SalesData(this.year, this.high, this.low); - /// final num year; - /// final num high; - /// final num low; - /// } - /// ``` - final ChartIndexedValueMapper? highValueMapper; - - /// Field in the data source, which is considered as low value for the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// RangeColumnSeries( - /// dataSource: [ - /// SalesData(2005, 24, 16), - /// SalesData(2006, 22, 12), - /// SalesData(2007, 31, 18), - /// ], - /// xValueMapper: (SalesData data, _) => data.year, - /// lowValueMapper: (SalesData data, _) => data.low, - /// highValueMapper: (SalesData data, _) => data.high, - /// ), - /// ], - /// ); - /// } - /// class SalesData { - /// SalesData(this.year, this.high, this.low); - /// final num year; - /// final num high; - /// final num low; - /// } - /// ``` - final ChartIndexedValueMapper? lowValueMapper; - - /// A boolean value, based on which the data point will be considered as intermediate sum or not. - /// - /// If this has true value, then that point will be considered as an intermediate sum. Else if - /// it has false, then it will be considered as a normal data point in chart. - /// - /// This callback will be called for all the data points to check if the data is intermediate sum. - /// - /// _Note:_ This is applicable only for waterfall chart. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// dataSource: [ - /// SalesData(2, 24, true), - /// SalesData(3, 22, false), - /// SalesData(4, 31, true), - /// ], - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// intermediateSumPredicate: (SalesData data, _) => data.isIntermediate, - /// ), - /// ], - /// ); - /// } - /// class SalesData { - /// SalesData(this.x, this.y, this.isIntermediate); - /// final num x; - /// final num y; - /// final bool isIntermediate; - /// } - /// ``` - final ChartIndexedValueMapper? intermediateSumPredicate; - - /// A boolean value, based on which the data point will be considered as total sum or not. - /// - /// If this has true value, then that point will be considered as a total sum. Else if - /// it has false, then it will be considered as a normal data point in chart. - /// - /// This callback will be called for all the data points to check if the data is total sum. - /// - /// _Note:_ This is applicable only for waterfall chart. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// dataSource: [ - /// SalesData(2, 24, true), - /// SalesData(3, 22, true), - /// SalesData(4, 31, false), - /// ], - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// totalSumPredicate: (SalesData data, _) => data.isTotalSum, - /// ), - /// ], - /// ); - /// } - /// class SalesData { - /// SalesData(this.x, this.y, this.isTotalSum); - /// final num x; - /// final num y; - /// final bool isTotalSum; - /// } - /// ``` - final ChartIndexedValueMapper? totalSumPredicate; - - /// Name of the x-axis to bind the series. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// axes: [ - /// NumericAxis( - /// name: 'xAxis1' - /// ) - /// ], - /// series: >[ - /// BubbleSeries( - /// xAxisName: 'xAxis1', - /// ), - /// ], - /// ); - /// } - /// ``` - final String? xAxisName; - - /// Name of the y-axis to bind the series. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// axes: [ - /// NumericAxis( - /// name: 'yAxis1' - /// ) - /// ], - /// series: >[ - /// BubbleSeries( - /// yAxisName: 'yAxis1', - /// ), - /// ], - /// ); - /// } - /// ``` - final String? yAxisName; - - /// Color of the series. - /// - /// If no color is specified, then the series will be rendered - /// with the default palette color. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// color: const Color.fromRGBO(255, 0, 102, 1), - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? color; - - /// Width of the series. - /// - /// In line, spline, step line, and fast line series, width - /// of the line will be changed. In column series, width of the column rectangle will - /// be changed. In bar series, the height of the bar rectangle will be changed. - /// - /// _Note:_ This is not applicable for area, scatter, and bubble series. - /// - /// Default to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// width: 3, - /// ), - /// ], - /// ); - /// } - /// ``` - final double? width; - - /// Indication of data points. - /// - /// Marks the data point location with symbols for better - /// indication. The shape, color, border, and size of the marker can be customized. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// markerSettings: MarkerSettings(isVisible: true) - /// ), - /// ], - /// ); - /// } - /// ``` - final MarkerSettings markerSettings; - - /// Customizes the empty points, i.e. null data points in a series. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// emptyPointSettings: EmptyPointSettings(color: Colors.black) - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final EmptyPointSettings emptyPointSettings; - - /// Customizes the data labels in a series. Data label is a text, which displays - /// the details about the data point. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final DataLabelSettings dataLabelSettings; - - /// Customizes the trendlines. - /// - /// Trendline are used to mark the specific area of interest - /// in the plot area with texts, shapes, or images. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline( - /// type: TrendlineType.linear - /// ) - /// ] - /// ) - /// ], - /// ); - /// } - /// ``` - final List? trendlines; - - /// Fills the chart series with gradient color. - /// - /// Default to `null`. - /// - /// ```dart - /// final List color = [Colors.red, Colors.blue, Colors.pink]; - /// final List stops = [0.0, 0.5, 1.0]; - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// gradient: LinearGradient(colors: color, stops: stops) - /// ), - /// ], - /// ); - /// } - /// ``` - final LinearGradient? gradient; - - /// Fills the border of the chart series with gradient color. - /// - /// Default to `null`. - /// - /// ```dart - /// final List color = [Colors.red, Colors.blue, Colors.pink]; - /// final List stops = [0.0, 0.5, 1.0]; - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// borderGradient: LinearGradient(colors: color, stops: stops), - /// borderWidth: 3 - /// ), - /// ], - /// ); - /// } - /// ``` - final LinearGradient? borderGradient; - - /// 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 - /// text prefix will be considered as series name. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// name: 'Bubble Series', - /// ), - /// ], - /// ); - /// } - /// ``` - @override - 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. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: TooltipBehavior(enable: true), - /// series: >[ - /// BubbleSeries( - /// enableTooltip: true, - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final bool enableTooltip; - - /// Dashes of the series. - /// - /// Any number of values can be provided in the list. Odd value - /// is considered as rendering size and even value is considered as gap. - /// - /// _Note:_ This is applicable for line, spline, step line, and fast line series only. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// dashArray: [10, 10], - /// ), - /// ], - /// ); - /// } - /// ``` - final List dashArray; - - /// Duration of the series animation. It takes millisecond value as input. - /// - /// Series will be animated while rendering. Animation is enabled by default, - /// you can also control the duration of the animation using `animationDuration` property. - /// You can disable the animation by setting 0 value to that property. - /// - /// Defaults to `1500`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// animationDuration: 1000, - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final double animationDuration; - - /// Border color of the series. - /// - /// _Note:_ This is not applicable for line, spline, step line, stacked line, stacked line 100 - /// and fast line series types. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final Color borderColor; - - /// Border width of the series. - /// - /// _Note:_ This is not applicable for line, spline, step line, stacked line, stacked line 100 - /// and fast line series types. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final double borderWidth; - - /// 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. - /// - /// Defaults to `LegendIconType.seriesType`. - /// - /// Also refer [LegendIconType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend(isVisible:true), - /// series: >[ - /// LineSeries( - /// legendIconType: LegendIconType.diamond, - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final LegendIconType legendIconType; - - /// Toggles the visibility of the legend item of this specific series in the legend. - /// - /// If it is set to false, the legend item for this series will not be visible in the legend. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend(isVisible:true), - /// series: >[ - /// LineSeries( - /// isVisibleInLegend: false - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isVisibleInLegend; - - /// 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. - /// - /// Defaults to `‘’`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend(isVisible:true), - /// series: >[ - /// LineSeries( - /// legendItemText: 'Legend' - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final String? legendItemText; - - /// Customizes the data points or series on selection. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: SelectionBehavior( - /// enable:true - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final SelectionBehavior selectionBehavior; - - /// Opacity of the series. - /// - /// The value ranges from 0 to 1. It used to control the transparency of the legend icon shape. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// opacity: 0.8 - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final double opacity; - - /// Field in the data source, which is considered for sorting the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// sortFieldValueMapper: (SalesData sales, _) => sales.x, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - @override - 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. - /// - /// Default to `SortingOrder.none`. - /// - /// Also refer [SortingOrder]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// sortFieldValueMapper: (SalesData sales, _) => sales.x, - /// sortingOrder: SortingOrder.descending - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - @override - final SortingOrder sortingOrder; - - /// Toggles the visibility of the series. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isVisible: false - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final bool isVisible; - - /// Delay duration of the series animation.It takes a millisecond value as input. - /// By default, the series will get animated for the specified duration. - /// If animationDelay is specified, then the series will begin to animate - /// after the specified duration. - /// - /// Defaults to 0 for all the series except ErrorBarSeries. - /// The default value for the ErrorBarSeries is 1500. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// animationDelay: 300 - /// ), - /// ], - /// ); - /// } - /// ``` - @override - final double? animationDelay; - - /// List of data indexes to initially be selected. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: SelectionBehavior( - /// enable:true - /// ), - /// initialSelectedDataIndexes: [0] - /// ), - /// ], - /// ); - /// } - /// ``` - final List? initialSelectedDataIndexes; - - /// 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 SfCartesianChart( - /// series: >[ - /// BarSeries( - /// // To use the gradient for shader rendering, - /// // `import 'dart:ui' as ui;` this file should be imported. - /// onCreateShader: (ShaderDetails details) { - /// return ui.Gradient.linear( - /// details.rect.topRight, - /// details.rect.bottomLeft, - /// [Colors.yellow, Colors.lightBlue, Colors.brown], - /// [0.2,0.6,1] - /// ); - /// }, - /// ), - /// ] - /// ); - /// } - /// ``` - final CartesianShaderCallback? onCreateShader; -} - -/// Creates a series renderer for chart series. -abstract class ChartSeriesRenderer {} - -/// 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. -class ChartSeriesController { - /// Creating an argument constructor of ChartSeriesController class. - ChartSeriesController(this.seriesRenderer); - - /// Used to access the series properties. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// print(controller.seriesRenderer is LineSeriesRenderer); - /// }, - /// ), - /// ], - /// ); - /// } - /// ``` - final XyDataSeriesRenderer seriesRenderer; - - bool _needXRecalculation = false, _needYRecalculation = false; - - /// Used to process only the newly added, updated and removed data points in a series, - /// instead of processing all the data points. - /// - /// To re-render the chart with modified data points, setState() will be called. - /// This will render the process and render the chart from scratch. - /// Thus, the app’s performance will be degraded on continuous update. - /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. - /// Chart will process only that point and skip various steps like bounds calculation, - /// old data points processing, etc. Thus, this will improve the app’s performance. - /// - /// The following are the arguments of this method. - /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. - /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. - /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. - /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. - /// * removedDataIndex – `int` type – Index of removed data point in the existing series. - /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. - /// - /// Returns `void`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// ChartSeriesController? _chartSeriesController; - /// return Column( - /// children: [ - /// SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ), - /// ] - /// ), - /// TextButton( - /// child: Text("Update data source"), - /// onPressed: () { - /// chartData.removeAt(0); - /// chartData.add(ChartData(3,23)); - /// _chartSeriesController?.updateDataSource( - /// addedDataIndexes: [chartData.length -1], - /// removedDataIndexes: [0], - /// ); - /// } - /// ) - /// ] - /// ); - /// } - /// ``` - void updateDataSource( - {List? addedDataIndexes, - List? removedDataIndexes, - List? updatedDataIndexes, - int? addedDataIndex, - int? removedDataIndex, - int? updatedDataIndex}) { - bool _needUpdate = false; - if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { - _removeDataPointsList(removedDataIndexes); - } else if (removedDataIndex != null) { - _removeDataPoint(removedDataIndex); - } - if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(addedDataIndexes, false); - } else if (addedDataIndex != null) { - _addOrUpdateDataPoint(addedDataIndex, false); - } - if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { - _needUpdate = true; - _addOrUpdateDataPoints(updatedDataIndexes, true); - } else if (updatedDataIndex != null) { - _needUpdate = true; - _addOrUpdateDataPoint(updatedDataIndex, true); - } - _updateCartesianSeries( - _needXRecalculation, _needYRecalculation, _needUpdate); - } - - /// Add or update the data points on dynamic series update. - void _addOrUpdateDataPoints(List indexes, bool needUpdate) { - for (int i = 0; i < indexes.length; i++) { - final int dataIndex = indexes[i]; - _addOrUpdateDataPoint(dataIndex, needUpdate); - } - } - - /// Add or update a data point in the given index. - void _addOrUpdateDataPoint(int index, bool needUpdate) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final CartesianSeries series = - seriesRendererDetails.series; - if (index >= 0 && - series.dataSource.length > index && - series.dataSource[index] != null) { - final VisibleRange xRange = - seriesRendererDetails.xAxisDetails!.visibleRange!; - final VisibleRange yRange = - seriesRendererDetails.yAxisDetails!.visibleRange!; - final CartesianChartPoint currentPoint = - getChartPoint(seriesRenderer, series.dataSource[index], index)!; - final String seriesType = seriesRendererDetails.seriesType; - dynamic x = currentPoint.x; - if (seriesRendererDetails.xAxisDetails is DateTimeAxisDetails || - seriesRendererDetails.xAxisDetails is DateTimeCategoryAxisDetails) { - x = x.millisecondsSinceEpoch; - } else if (seriesRendererDetails.xAxisDetails is LogarithmicAxisDetails) { - final LogarithmicAxis axis = - seriesRendererDetails.xAxisDetails!.axis as LogarithmicAxis; - currentPoint.xValue = currentPoint.x; - x = calculateLogBaseValue((x > 1) == true ? x : 1, axis.logBase); - } else if (seriesRendererDetails.xAxisDetails is CategoryAxisDetails) { - x = index; - } - currentPoint.xValue ??= x; - currentPoint.yValue = currentPoint.y; - currentPoint.overallDataPointIndex = index; - if (!_needXRecalculation && - ((xRange.minimum >= x) == true || (xRange.maximum <= x) == true)) { - _needXRecalculation = true; - } - num? minYVal = currentPoint.y ?? currentPoint.low; - num? maxYVal = currentPoint.y ?? currentPoint.high; - if (seriesRendererDetails.yAxisDetails is LogarithmicAxisRenderer && - minYVal != null && - maxYVal != null) { - final LogarithmicAxis axis = - seriesRendererDetails.yAxisDetails!.axis as LogarithmicAxis; - minYVal = - calculateLogBaseValue(minYVal > 1 ? minYVal : 1, axis.logBase); - maxYVal = - calculateLogBaseValue(maxYVal > 1 ? maxYVal : 1, axis.logBase); - } - if (!_needYRecalculation && - minYVal != null && - maxYVal != null && - ((yRange.minimum >= minYVal) == true || - (yRange.maximum <= maxYVal) == true)) { - _needYRecalculation = true; - } - - if (needUpdate) { - if (seriesRendererDetails.dataPoints.length > index == true) { - seriesRendererDetails.dataPoints[index] = currentPoint; - } - } else { - if (seriesRendererDetails.dataPoints.length == index) { - seriesRendererDetails.dataPoints.add(currentPoint); - } else if (seriesRendererDetails.dataPoints.length > index == true && - index >= 0) { - seriesRendererDetails.dataPoints.insert(index, currentPoint); - } - } - - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') - ? seriesType == 'hiloopenclose' || seriesType.contains('candle') - ? (currentPoint.low == null || - currentPoint.high == null || - currentPoint.open == null || - currentPoint.close == null) - : (currentPoint.low == null || currentPoint.high == null) - : currentPoint.y == null) { - // ignore: unnecessary_type_check - if (seriesRenderer is XyDataSeriesRenderer) { - seriesRenderer.calculateEmptyPointValue( - index, currentPoint, seriesRenderer); - } - } - - /// Below lines for changing high, low values based on input. - if ((seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle')) && - currentPoint.isVisible) { - final num high = currentPoint.high; - final num low = currentPoint.low; - currentPoint.high = math.max(high, low); - currentPoint.low = math.min(high, low); - } - } - } - - /// Converts logical pixel value to the data point value. - /// - /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. - /// - /// Since this method is in the series controller, x and y-axis associated with this particular series will be - /// considering for conversion value. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// ChartSeriesController? _chartSeriesController; - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ), - /// ], - /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args) { - /// final Offset value = Offset(args.position.dx, args.position.dy); - /// final CartesianChartPoint? chartpoint = _chartSeriesController?.pixelToPoint(value); - /// print('X point: ${chartpoint?.x}'); - /// print('Y point: ${chartpoint?.y}'); - /// }, - /// ); - /// } - /// ``` - CartesianChartPoint pixelToPoint(Offset position) { - return calculatePixelToPoint(position, seriesRenderer); - } - - /// Converts chart data point value to logical pixel value. - /// - /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. - /// - /// Since this method is in the series controller, x and y-axis associated with this particular series will be - /// considering for conversion value. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// ChartSeriesController? _chartSeriesController; - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// onPointTap: (ChartPointDetails args) { - /// final CartesianChartPoint chartPoint = CartesianChartPoint( - /// chartData[args.pointIndex!].x, - /// chartData[args.pointIndex!].y); - /// final Offset? pointLocation = _chartSeriesController?.pointToPixel(chartPoint); - /// print('X location: ${pointLocation!.dx}'); - /// print('Y location: ${pointLocation.dy}'); - /// }, - /// ), - /// ], - /// ); - /// } - /// ``` - Offset pointToPixel(CartesianChartPoint point) { - return calculatePointToPixel(point, seriesRenderer); - } - - /// 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` - /// property's value in the series. If the value is 0, then the animation will not be performed. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// ChartSeriesController? _chartSeriesController; - /// return Column( - /// children: [ - /// SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ), - /// ] - /// ), - /// TextButton( - /// child: Text("Animate series"), - /// onPressed: () { - /// _chartSeriesController?.animate(); - /// } - /// ) - /// ] - /// ); - /// } - /// ``` - void animate() { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.visible! == true && - seriesRendererDetails.series.animationDuration > 0 == true) { - final CartesianStateProperties stateProperties = - seriesRendererDetails.stateProperties; - final SfCartesianChart chart = - seriesRendererDetails.stateProperties.chart; - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TrackballBehavior trackball = chart.trackballBehavior; - final TrackballRenderingDetails trackballRenderingDetails = - TrackballHelper.getRenderingDetails( - stateProperties.trackballBehaviorRenderer); - final TrackballPainter? trackballPainter = - trackballRenderingDetails.trackballPainter; - final RenderingDetails renderingDetails = - stateProperties.renderingDetails; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - renderingDetails.tooltipBehaviorRenderer); - //This hides the tooltip if rendered for this current series renderer - if (tooltip.enable && - (tooltip.builder != null - ? tooltipRenderingDetails.seriesIndex == - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]) - .seriesIndex - : tooltipRenderingDetails.currentSeriesDetails?.renderer == - seriesRenderer)) { - tooltip.hide(); - } - // This hides the trackball if rendered for this current series renderer. - if (trackball.enable) { - for (final ChartPointInfo point - in trackballRenderingDetails.chartPointInfo) { - if (point.seriesRendererDetails?.renderer == seriesRenderer) { - if (trackballPainter != null) { - stateProperties.repaintNotifiers['trackball']!.value++; - trackballPainter.canResetPath = true; - break; - } else { - final GlobalKey key = - trackballRenderingDetails.trackballTemplate!.key as GlobalKey; - final TrackballTemplateState trackballTemplateState = - key.currentState! as TrackballTemplateState; - trackballTemplateState.hideTrackballTemplate(); - break; - } - } - } - } - seriesRendererDetails.reAnimate = seriesRendererDetails.needsAnimation = - seriesRendererDetails.needAnimateSeriesElements = true; - renderingDetails.initialRender = false; - // This repaints the datalabels for the series if renderered. - stateProperties.renderDataLabel?.state?.repaintDataLabelElements(); - - // This animates the datalabel templates of the animating series. - if (seriesRendererDetails.series.dataLabelSettings.builder != null) { - for (final ChartTemplateInfo template in renderingDetails.templates) { - if (template.templateType == 'DataLabel' && - template.animationDuration > 0 && - template.seriesIndex == - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]) - .seriesIndex) { - template.animationController.forward(from: 0.0); - } - } - } - stateProperties.totalAnimatingSeries = 1; - stateProperties.animationCompleteCount = 0; - stateProperties.forwardAnimation(seriesRendererDetails); - // This animates the trendlines of the animating series. - if (seriesRendererDetails.trendlineRenderer.isNotEmpty == true) { - for (final TrendlineRenderer trendlineRenderer - in seriesRendererDetails.trendlineRenderer) { - if (trendlineRenderer.visible) { - final Trendline trendline = trendlineRenderer.trendline; - trendlineRenderer.animationController.duration = - Duration(milliseconds: trendline.animationDuration.toInt()); - trendlineRenderer.animationController.forward(from: 0.0); - } - } - } - } - } - - /// Remove list of points. - void _removeDataPointsList(List removedDataIndexes) { - /// Remove the redudant index from the list. - final List indexList = removedDataIndexes.toSet().toList(); - indexList.sort((int b, int a) => a.compareTo(b)); - for (int i = 0; i < indexList.length; i++) { - final int _dataIndex = indexList[i]; - _removeDataPoint(_dataIndex); - } - } - - /// Remove a data point in the given index. - void _removeDataPoint(int index) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.dataPoints.isNotEmpty == true && - index >= 0 && - index < seriesRendererDetails.dataPoints.length) { - final CartesianChartPoint currentPoint = - seriesRendererDetails.dataPoints[index]; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - if (xAxisDetails is DateTimeCategoryAxisRenderer || - xAxisDetails is CategoryAxisRenderer) { - _needXRecalculation = true; - } - seriesRendererDetails.dataPoints.removeAt(index); - // ignore: unnecessary_null_comparison - if (currentPoint != null) { - if (!_needXRecalculation && - (seriesRendererDetails.minimumX == currentPoint.x || - seriesRendererDetails.maximumX == currentPoint.x)) { - _needXRecalculation = true; - } - final String seriesType = seriesRendererDetails.seriesType; - - /// Below lines for changing high, low values based on input. - if ((seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle')) && - currentPoint.isVisible) { - final num high = currentPoint.high; - final num low = currentPoint.low; - currentPoint.high = math.max(high, low); - currentPoint.low = math.min(high, low); - } - final num? minYVal = currentPoint.y ?? currentPoint.low; - final num? maxYVal = currentPoint.y ?? currentPoint.high; - if (!_needYRecalculation && - minYVal != null && - maxYVal != null && - (seriesRendererDetails.minimumY == minYVal || - seriesRendererDetails.maximumY == maxYVal)) { - _needYRecalculation = true; - } - } - } - } - - /// After add/remove/update data points, recalculate the x, y range and interval. - void _updateCartesianSeries( - bool needXRecalculation, bool needYRecalculation, bool needUpdate) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final CartesianStateProperties stateProperties = - seriesRendererDetails.stateProperties; - stateProperties.isRedrawByZoomPan = false; - if (needXRecalculation || needYRecalculation || needUpdate) { - if (needXRecalculation) { - seriesRendererDetails.minimumX = seriesRendererDetails.maximumX = null; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - if (xAxisDetails is DateTimeCategoryAxisDetails) { - xAxisDetails.labels.clear(); - } - } - if (needYRecalculation) { - seriesRendererDetails.minimumY = seriesRendererDetails.maximumY = null; - } - stateProperties.chartSeries.findSeriesMinMax(seriesRendererDetails); - if (seriesRendererDetails.seriesType.contains('stacked') == true) { - stateProperties.chartSeries - .calculateStackedValues(findSeriesCollection(stateProperties)); - } - } - if (needXRecalculation) { - final dynamic axisRenderer = seriesRendererDetails.xAxisDetails!; - axisRenderer.calculateRangeAndInterval(stateProperties); - } - if (needYRecalculation) { - final dynamic axisRenderer = seriesRendererDetails.yAxisDetails!; - axisRenderer.calculateRangeAndInterval(stateProperties); - } - if (needXRecalculation || needYRecalculation) { - stateProperties.renderOutsideAxis.state.axisRepaintNotifier.value++; - stateProperties.renderInsideAxis.state.axisRepaintNotifier.value++; - for (final CartesianSeriesRenderer seriesRenderer - in stateProperties.chartSeries.visibleSeriesRenderers) { - _repaintSeries(stateProperties, - SeriesHelper.getSeriesRendererDetails(seriesRenderer)); - } - } else { - _repaintSeries(stateProperties, seriesRendererDetails); - } - stateProperties.isLoadMoreIndicator = false; - //This makes the update data source method work with dynamic animation(scheduled for release) - // seriesRenderer.seriesRendererDetails.needsAnimation = seriesRenderer.seriesRendererDetails.needAnimateSeriesElements = - // chartState.widgetNeedUpdate = true; - // chartState.initialRender = false; - // seriesRenderer.seriesRendererDetails.stateProperties.totalAnimatingSeries = 1; - // seriesRenderer.seriesRendererDetails.stateProperties.animationCompleteCount = 0; - // seriesRenderer.seriesRendererDetails.stateProperties.forwardAnimation( - // seriesRenderer, seriesRenderer.seriesRendererDetails.series.animationDuration); - } - - // This method repaints the series and its elements for the given series renderer. - void _repaintSeries(CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails) { - seriesRendererDetails.calculateRegion = true; - seriesRendererDetails.repaintNotifier.value++; - if (seriesRendererDetails.series.dataLabelSettings.isVisible == true) { - stateProperties.renderDataLabel?.state!.dataLabelRepaintNotifier.value++; - } - } -} - -/// Creates a series renderer for Cartesian series. -abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { - /// Holds the properties required to render the series. - late SeriesRendererDetails _seriesRendererDetails; - - /// To create segment for series. - ChartSegment createSegment(); - - /// To customize each segments. - // ignore: unused_element - void customizeSegment(ChartSegment segment); - - /// To customize each data markers. - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]); - - /// To customize each data labels. - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style); - - /// To calculate the value of empty points. - void calculateEmptyPointValue( - int pointIndex, CartesianChartPoint currentPoint, - [CartesianSeriesRenderer seriesRenderer]); - - /// To dispose the CartesianSeriesRenderer class objects. - void dispose() { - _seriesRendererDetails.dispose(); - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the private fields of chart series renderer. -class SeriesHelper { - /// Method to get the series renderer details of corresponding series renderer. - static SeriesRendererDetails getSeriesRendererDetails( - CartesianSeriesRenderer renderer) => - renderer._seriesRendererDetails; - - /// Method to set the series renderer details of corresponding series renderer. - static void setSeriesRendererDetails(CartesianSeriesRenderer renderer, - SeriesRendererDetails rendererDetails) => - renderer._seriesRendererDetails = rendererDetails; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart deleted file mode 100644 index 8c8bf2618..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart +++ /dev/null @@ -1,666 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show DateFormat; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/data_label.dart'; -import '../common/marker.dart'; -import '../common/renderer.dart'; -import '../trendlines/trendlines.dart'; -import '../utils/helper.dart'; -import 'financial_series_base.dart'; -import 'histogram_series.dart'; - -/// Represents the series renderer details. -class SeriesRendererDetails { - /// Argument constructor for SeriesRendererDetails class. - SeriesRendererDetails(this.renderer); - - /// Specifies the value of Cartesian series renderer. - final CartesianSeriesRenderer renderer; - - /// Specifies the value of series name. - String? seriesName; - - /// Holds whether the series is visible. - bool? visible; - - /// Specifies whether to repaint the series. - bool needsRepaint = true; - - /// Holds the value of Cartesian chart. - late SfCartesianChart chart; - - /// Stores the series type. - late String seriesType; - - /// Whether to check the series is rect series or not. - // ignore: prefer_final_fields - bool isRectSeries = false; - - /// Specifies whether the current point types series - bool isPointSeries = false; - - /// Specifies whether the series has side by side info - bool hasSideBySideInfo = false; - - /// Specifies whether the series has tooltip behavior - bool hasTooltip = false; - - /// Specifies whether to calculate region for the waterfall/ stacked bar/ stacked column - bool needsToCalculateRegion = false; - - /// Specifies the histogram values - late HistogramValues histogramValues; - - /// Specifies the list of draw control points. - final List> drawControlPoints = >[]; - - /// Specifies the list of low control points. - final List> drawLowControlPoints = >[]; - - /// Specifies the list of high control points. - final List> drawHighControlPoints = >[]; - - /// Specifies the segment path value. - Path? segmentPath; - - /// Gets the Segments collection variable declarations. - // ignore: prefer_final_fields - List segments = []; - - /// Maintain the old series state. - CartesianSeries? oldSeries; - - /// Store the current series state - late CartesianSeries series; - - /// Holds the collection of Cartesian data points. - // ignore: prefer_final_fields - List> dataPoints = - >[]; - - /// Holds the collection of Cartesian visible data points. - List>? visibleDataPoints; - - /// Holds the collection of old data points. - List>? oldDataPoints; - - /// Holds the old series initial selected data indexes. - List? oldSelectedIndexes; - - /// Holds the information for x Axis. - ChartAxisRendererDetails? xAxisDetails; - - /// Holds the information for y Axis. - ChartAxisRendererDetails? yAxisDetails; - - /// Minimum x value for Series. - num? minimumX; - - /// Maximum x value for Series. - num? maximumX; - - /// Minimum y value for Series. - num? minimumY; - - /// Maximum y value for Series. - num? maximumY; - - /// Hold the data about point regions. - Map? regionalData; - - /// Color for the series based on color palette. - Color? seriesColor; - - /// Specifies the list of x-values. - List? xValues; - - /// Holds the Cartesian state properties. - late CartesianStateProperties stateProperties; - - /// Contains the collection of path for markers. - late List markerShapes; - - /// Specifies the value of marker shapes. - late List markerShapes2; - - /// Specifies whether the region is outer. - // ignore: prefer_final_fields - bool isOuterRegion = false; - - /// Used to differentiate indicator from series. - // ignore: prefer_final_fields - bool isIndicator = false; - - /// Storing mindelta for rect series. - num? minDelta; - - /// Repaint notifier for series. - late ValueNotifier repaintNotifier; - - /// Specifies whether the series elements need to animate. - // ignore: prefer_final_fields - bool needAnimateSeriesElements = false; - - /// Specifies whether needs to animate. - bool needsAnimation = false; - - /// Specifies whether to reanimate the elements. - bool reAnimate = false; - - /// Specifies whether to calculate the region. - bool calculateRegion = false; - - /// Specifies the series animation. - Animation? seriesAnimation; - - /// Specifies the series element animation. - Animation? seriesElementAnimation; - - /// Controls the animation of the corresponding series. - 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; - - /// Specifies the trendline renderer - List trendlineRenderer = []; - - /// Specifies the value of data label setting renderer - late DataLabelSettingsRenderer dataLabelSettingsRenderer; - - /// Specifies the marker setting renderer - MarkerSettingsRenderer? markerSettingsRenderer; - - /// Specifies whether the selection is enabled - // ignore: prefer_final_fields - bool isSelectionEnable = false; - - /// Specifies the value of selection behavior renderer - SelectionBehaviorRenderer? selectionBehaviorRenderer; - - /// Specifies the selection behavior - dynamic selectionBehavior; - - /// Specifies whether the marker is rendered - // ignore: prefer_final_fields - bool isMarkerRenderEvent = false; - - /// bool for animation status - late bool animationCompleted; - - /// Specifies whether the series has data label templates - // ignore: prefer_final_fields - bool hasDataLabelTemplate = false; - - // ignore: prefer_final_fields - /// It specifies the side by side information of the visible range. - VisibleRange? sideBySideInfo; - - /// Store the rect position - late num rectPosition; - - /// Store the rect count - late num rectCount; - - /// Represents the old series renderer - List? oldSeriesRenderers; - - /// Represents the maximum size - double? maxSize; - - /// Represents the minimum size - double? minSize; - - /// Represents the index value of the series corresponding to this renderer - int? seriesIndex; - - /// Represents the candle series renderer - late CandleSeries candleSeries; - - /// Specifies the over all data points - List> overallDataPoints = - >[]; - - /// Specifies the hilo open close series - late HiloOpenCloseSeries hiloOpenCloseSeries; - - /// Store the stacking values - List stackingValues = []; - - /// Store the percentage values - List percentageValues = []; - - /// Specifies the list of xValue - final List xValueList = []; - - /// Specifies the list of yValue - final List yValueList = []; - - /// Specifies whether the series is line type - final bool isLineType = false; - - /// Holds the collection of Cartesian overall data points - // ignore: prefer_final_fields - List?> overAllDataPoints = - ?>[]; - - /// To draw area segments - //ignore: unused_element - void drawSegment(Canvas canvas, ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - if (SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .isSelectionEnable == - true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - segments[segment.currentSegmentIndex!], stateProperties.chart); - } - segment.onPaint(canvas); - } - - /// To render a series of elements for all series - void renderSeriesElements(SfCartesianChart chart, Canvas canvas, - Animation? animationController) { - markerShapes = []; - markerShapes2 = []; - assert( - // ignore: unnecessary_null_comparison - !(series.markerSettings.height != null) || - series.markerSettings.height >= 0, - 'The height of the marker should be greater than or equal to 0.'); - assert( - // ignore: unnecessary_null_comparison - !(series.markerSettings.width != null) || - series.markerSettings.width >= 0, - 'The width of the marker must be greater than or equal to 0.'); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - final CartesianChartPoint point = dataPoints[pointIndex]; - if ((series.markerSettings.isVisible && - renderer is! BoxAndWhiskerSeriesRenderer) || - renderer is ScatterSeriesRenderer) { - markerSettingsRenderer?.renderMarker( - this, point, animationController, canvas, pointIndex); - } - } - } - - /// Method to repaint the series element - void repaintSeriesElement() { - repaintNotifier.value++; - } - - /// Method to listen the animation status - void animationStatusListener(AnimationStatus status) { - // ignore: unnecessary_null_comparison - if (stateProperties != null && status == AnimationStatus.completed) { - reAnimate = false; - animationCompleted = true; - stateProperties.animationCompleteCount++; - setAnimationStatus(stateProperties); - // ignore: unnecessary_null_comparison - } else if (stateProperties != null && status == AnimationStatus.forward) { - stateProperties.renderingDetails.animateCompleted = false; - animationCompleted = false; - } - } - - /// To store the series properties - void storeSeriesProperties( - CartesianStateProperties stateProperties, int index) { - this.stateProperties = stateProperties; - chart = stateProperties.chart; - isRectSeries = seriesType.contains('column') || - (seriesType.contains('bar') && !seriesType.contains('errorbar')) || - seriesType == 'histogram'; - regionalData = {}; - segmentPath = Path(); - segments = []; - seriesColor = series.color ?? chart.palette[index % chart.palette.length]; - - // calculates the tooltip region for trenlines in this series - final List? trendlines = series.trendlines; - if (trendlines != null && - // ignore: unnecessary_null_comparison - 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++) { - final CartesianChartPoint trendlinePoint = - trendlineRenderer[j].pointsData![k]; - calculateTooltipRegion(trendlinePoint, index, this, - stateProperties, trendlines[j], trendlineRenderer[j], j); - } - } - } - } - } - } - - /// 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; - 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!); - } - sDValue = math.sqrt(sumValue / (histogramValues.yValues!.length - 1)); - histogramValues.sDValue = sDValue; - } - - /// method to set the bool variables based on the series type - void setSeriesProperties(SeriesRendererDetails seriesRendererDetails) { - if (seriesType.contains('column') || - seriesType.contains('stackedbar') || - seriesType == 'bar' || - seriesType == 'histogram' || - seriesType == 'waterfall') { - isRectSeries = true; - needsToCalculateRegion = seriesType.contains('waterfall') || - seriesType.contains('stackedcolumn') || - seriesType.contains('stackedbar'); - } else if (seriesType == 'scatter' || seriesType == 'bubble') { - isPointSeries = true; - } - - hasSideBySideInfo = isRectSeries || - (seriesType.contains('candle') || - seriesType.contains('hilo') || - seriesType.contains('histogram') || - seriesType.contains('box')); - - hasTooltip = chart.tooltipBehavior != null && - seriesType != 'errorbar' && - (chart.tooltipBehavior.enable || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null) && - seriesType != 'boxandwhisker'; - } - - /// To find the region data of a series - void calculateRegionData( - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - int seriesIndex, - CartesianChartPoint point, - int pointIndex, - [VisibleRange? sideBySideInfo, - CartesianChartPoint? _nextPoint, - num? midX, - num? midY]) { - if (withInRange(seriesRendererDetails.dataPoints[pointIndex].xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!)) { - seriesRendererDetails.visibleDataPoints! - .add(seriesRendererDetails.dataPoints[pointIndex]); - seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex = - seriesRendererDetails.visibleDataPoints!.length - 1; - } - chart = stateProperties.chart; - final Rect rect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails!.axis.plotOffset, yAxisDetails!.axis.plotOffset)); - - CartesianChartPoint point; - if (visible!) { - point = dataPoints[pointIndex]; - if (point.region == null || - seriesRendererDetails.calculateRegion == true || - needsToCalculateRegion) { - if (seriesRendererDetails.calculateRegion == true && - dataPoints.length == pointIndex - 1) { - seriesRendererDetails.calculateRegion = false; - } - - /// side by side range calculated - seriesRendererDetails.sideBySideInfo = hasSideBySideInfo - ? calculateSideBySideInfo( - seriesRendererDetails.renderer, stateProperties) - : seriesRendererDetails.sideBySideInfo; - if (isRectSeries) { - calculateRectSeriesRegion( - point, pointIndex, seriesRendererDetails, stateProperties); - } else if (isPointSeries) { - calculatePointSeriesRegion( - point, pointIndex, seriesRendererDetails, stateProperties, rect); - } else if (seriesType == 'errorbar') { - calculateErrorBarSeriesRegion( - point, pointIndex, seriesRendererDetails, stateProperties, rect); - } else { - calculatePathSeriesRegion( - point, - pointIndex, - this, - stateProperties, - rect, - series.markerSettings.height, - series.markerSettings.width, - sideBySideInfo, - _nextPoint, - midX, - midY); - } - } - // ignore: unnecessary_null_comparison - if (hasTooltip) { - calculateTooltipRegion( - point, seriesIndex, seriesRendererDetails, stateProperties); - } - } - } - - /// To find the region data of chart tooltip - void calculateTooltipRegionUsingIndex(SfCartesianChart chart, int seriesIndex, - CartesianChartPoint point, int pointIndex) { - /// For tooltip implementation - // ignore: unnecessary_null_comparison - if (series.enableTooltip != null && - series.enableTooltip && - // ignore: unnecessary_null_comparison - point != null && - !point.isGap && - !point.isDrop) { - final List regionData = []; - String? date; - final List regionRect = []; - final dynamic primaryAxisDetails = xAxisDetails; - if (primaryAxisDetails is DateTimeAxisDetails) { - final DateTimeAxis _axis = primaryAxisDetails.axis as DateTimeAxis; - final num interval = primaryAxisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (primaryAxisDetails.visibleLabels.isNotEmpty) - ? primaryAxisDetails - .visibleLabels[primaryAxisDetails.visibleLabels.length - 1] - .value - : interval; - final DateFormat dateFormat = _axis.dateFormat ?? - getDateTimeLabelFormat(xAxisDetails!.axisRenderer, interval.toInt(), - prevInterval.toInt()); - date = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue)); - } else if (primaryAxisDetails is DateTimeCategoryAxisDetails) { - date = primaryAxisDetails.dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); - } - xAxisDetails is CategoryAxisDetails - ? regionData.add(point.x.toString()) - : xAxisDetails is DateTimeAxisDetails || - xAxisDetails is DateTimeCategoryAxisDetails - ? regionData.add(date.toString()) - : regionData.add(getLabelValue(point.xValue, xAxisDetails!.axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - if (seriesType.contains('range')) { - regionData.add(getLabelValue(point.high, yAxisDetails!.axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - regionData.add(getLabelValue(point.low, yAxisDetails!.axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - } else { - regionData.add(getLabelValue(point.yValue, yAxisDetails!.axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - } - regionData.add(series.name ?? 'series $seriesIndex'); - regionRect.add(point.region); - regionRect.add(isRectSeries - ? seriesType == 'column' || seriesType.contains('stackedcolumn') - ? (point.yValue > 0) == true - ? 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)); - regionRect.add(point.pointColorMapper); - regionRect.add(point.bubbleSize); - if (seriesType.contains('stacked')) { - regionData.add((point.cumulativeValue).toString()); - } - regionalData![regionRect] = regionData; - } - } - - /// To calculate the empty point average mode value - void calculateAverageModeValue( - int pointIndex, - int pointLength, - CartesianChartPoint currentPoint, - CartesianChartPoint prevPoint) { - final List dataSource = series.dataSource; - final CartesianChartPoint _nextPoint = getPointFromData( - this, - pointLength < dataSource.length - 1 - ? dataSource.indexOf(dataSource[pointLength + 1]) - : dataSource.indexOf(dataSource[pointLength])); - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle')) { - final CartesianSeries _cartesianSeries = series; - if (_cartesianSeries is FinancialSeriesBase && - _cartesianSeries.showIndicationForSameValues) { - if (currentPoint.low != null || currentPoint.high != null) { - currentPoint.low = currentPoint.low ?? currentPoint.high; - currentPoint.high = currentPoint.high ?? currentPoint.low; - } else { - currentPoint.low = 0; - currentPoint.high = 0; - currentPoint.open = 0; - currentPoint.close = 0; - currentPoint.isGap = true; - } - if (seriesType == 'hiloopenclose' || seriesType == 'candle') { - if (currentPoint.open != null || currentPoint.close != null) { - currentPoint.open = currentPoint.open ?? currentPoint.close; - currentPoint.close = currentPoint.close ?? currentPoint.open; - } else { - currentPoint.low = 0; - currentPoint.high = 0; - currentPoint.open = 0; - currentPoint.close = 0; - currentPoint.isGap = true; - } - } - } else { - if (pointIndex == 0) { - if (currentPoint.low == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.low = 0 - : currentPoint.low = ((_nextPoint.low) ?? 0) / 2; - } - if (currentPoint.high == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.high = 0 - : currentPoint.high = ((_nextPoint.high) ?? 0) / 2; - } - if (seriesType == 'hiloopenclose' || seriesType == 'candle') { - if (currentPoint.open == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.open = 0 - : currentPoint.open = ((_nextPoint.open) ?? 0) / 2; - } - if (currentPoint.close == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.close = 0 - : currentPoint.close = ((_nextPoint.close) ?? 0) / 2; - } - } - } else if (pointIndex == dataSource.length - 1) { - currentPoint.low = currentPoint.low ?? ((prevPoint.low) ?? 0) / 2; - currentPoint.high = currentPoint.high ?? ((prevPoint.high) ?? 0) / 2; - - if (seriesType == 'hiloopenclose' || seriesType == 'candle') { - currentPoint.open = - currentPoint.open ?? ((prevPoint.open) ?? 0) / 2; - currentPoint.close = - currentPoint.close ?? ((prevPoint.close) ?? 0) / 2; - } - } else { - currentPoint.low = currentPoint.low ?? - (((prevPoint.low) ?? 0) + ((_nextPoint.low) ?? 0)) / 2; - currentPoint.high = currentPoint.high ?? - (((prevPoint.high) ?? 0) + ((_nextPoint.high) ?? 0)) / 2; - - if (seriesType == 'hiloopenclose' || seriesType == 'candle') { - currentPoint.open = currentPoint.open ?? - (((prevPoint.open) ?? 0) + ((_nextPoint.open) ?? 0)) / 2; - currentPoint.close = currentPoint.close ?? - (((prevPoint.close) ?? 0) + ((_nextPoint.close) ?? 0)) / 2; - } - } - } - } else { - if (pointIndex == 0) { - ///Check the first point is null - pointIndex == dataSource.length - 1 - ? - - ///Check the series contains single point with null value - currentPoint.y = 0 - : currentPoint.y = ((_nextPoint.y) ?? 0) / 2; - } else if (pointIndex == dataSource.length - 1) { - ///Check the last point is null - currentPoint.y = ((prevPoint.y) ?? 0) / 2; - } else { - currentPoint.y = (((prevPoint.y) ?? 0) + ((_nextPoint.y) ?? 0)) / 2; - } - } - } - - /// To dispose the objects. - void dispose() { - for (final ChartSegment segment in segments) { - segment.dispose(); - } - - segments.clear(); - } -} 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 deleted file mode 100644 index 0b40d1746..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart +++ /dev/null @@ -1,253 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the spline series. -/// -/// To render a spline area chart, create an instance of [SplineAreaSeries], and add it to the series collection property of [SfCartesianChart]. -/// Properties such as [color], [opacity], [width] are used to customize the appearance of spline area chart. -@immutable -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, - this.splineType, - 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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - this.borderDrawMode = BorderDrawMode.top}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - trendlines: trendlines, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of area series. - /// - /// Defaults to `BorderDrawMode.top`. - /// - /// Also refer [BorderDrawMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineAreaSeries( - /// borderColor: Colors.red, - /// borderWidth: 3, - /// borderDrawMode: BorderDrawMode.all, - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderDrawMode borderDrawMode; - - /// Type of the spline curve. Various type of curves such as clamped, cardinal, - /// monotonic, and natural can be rendered between the data points. - /// - /// Defaults to `SplineType.natural`. - /// - /// Also refer [SplineType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineAreaSeries( - /// splineType: SplineType.monotonic, - /// ), - /// ], - /// ); - /// } - /// ``` - final SplineType? splineType; - - /// Line tension of the cardinal spline. The value ranges from 0 to 1. - /// - /// Defaults to `0.5`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineAreaSeries( - /// cardinalSplineTension: 0.4, - /// ), - /// ], - /// ); - /// } - /// ``` - final double cardinalSplineTension; - - /// Create the spline area series renderer. - SplineAreaSeriesRenderer createRenderer(ChartSeries series) { - SplineAreaSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as SplineAreaSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return SplineAreaSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is SplineAreaSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.borderDrawMode == borderDrawMode && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.cardinalSplineTension == cardinalSplineTension && - other.splineType == splineType; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - borderDrawMode, - onRendererCreated, - cardinalSplineTension, - splineType, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 9d1eb3e13..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart +++ /dev/null @@ -1,271 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the spline range area series. -/// -/// To render a spline range area chart, create an instance of [SplineRangeAreaSeries], and add it to the series collection property of [SfCartesianChart]. -/// Properties such as [color], [opacity], [width] are used to customize the appearance of spline area chart. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} -@immutable -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, - this.splineType, - 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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - this.borderDrawMode = RangeAreaBorderMode.all}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - lowValueMapper: lowValueMapper, - highValueMapper: highValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - trendlines: trendlines, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of the spline range area series. - /// - /// It takes the following two values: - /// - /// * [RangeAreaBorderMode.all] renders border for all the sides of the series. - /// * [RangeAreaBorderMode.excludeSides] renders border at the top and bottom of the series, - /// and excludes both sides. - /// - /// Defaults to `RangeAreaBorderMode.all`. - /// - /// Also refer [RangeAreaBorderMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineRangeAreaSeries( - /// borderColor: Colors.red, - /// borderWidth: 3, - /// borderDrawMode: RangeAreaBorderMode.excludeSides, - /// ), - /// ], - /// ); - /// } - /// ``` - final RangeAreaBorderMode borderDrawMode; - - /// Type of the spline curve in spline range area series. - /// - /// Various type of curves such as `SplineType.clamped`, `SplineType.cardinal`, `SplineType.monotonic` - /// and `SplineType.natural` can be rendered between the data points. - /// - /// Defaults to `SplineType.natural`. - /// - /// Also refer [SplineType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineRangeAreaSeries( - /// splineType: SplineType.monotonic - /// ), - /// ], - /// ); - /// } - /// ``` - final SplineType? splineType; - - /// Line tension of the cardinal spline curve. - /// - /// This is applicable only when `SplineType.cardinal` is set to [splineType] property. - /// - /// Defaults to `0.5`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineRangeAreaSeries( - /// splineType: SplineType.cardinal, - /// cardinalSplineTension: 0.2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double cardinalSplineTension; - - /// Create the spline area series renderer. - SplineRangeAreaSeriesRenderer createRenderer(ChartSeries series) { - SplineRangeAreaSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = - onCreateRenderer!(series) as SplineRangeAreaSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return SplineRangeAreaSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is SplineRangeAreaSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.lowValueMapper == lowValueMapper && - other.highValueMapper == highValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.borderDrawMode == borderDrawMode && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.cardinalSplineTension == cardinalSplineTension && - other.splineType == splineType; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - lowValueMapper, - highValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - borderDrawMode, - onRendererCreated, - cardinalSplineTension, - splineType, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 6928a52f5..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart +++ /dev/null @@ -1,227 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the spline series. -/// -/// The spline chart draws a curved line between the points in a data series.To render a spline chart, create an instance of -/// [SplineSeries], and add it to the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize the color, opacity and width of the spline series segments. -@immutable -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, - this.splineType, - this.cardinalSplineTension = 0.5, - EmptyPointSettings? emptyPointSettings, - DataLabelSettings? dataLabelSettings, - bool? isVisible, - List? trendlines, - bool? enableTooltip, - List? dashArray, - double? animationDuration, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - SortingOrder? sortingOrder, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - width: width ?? 2, - trendlines: trendlines, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - isVisible: isVisible, - dashArray: dashArray, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Type of the spline curve. Various type of curves such as clamped, cardinal, - /// monotonic, and natural can be rendered between the data points. - /// - /// Defaults to `SplineType.natural`. - /// - /// Also refer [SplineType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// splineType: SplineType.monotonic, - /// ), - /// ], - /// ); - /// } - /// ``` - final SplineType? splineType; - - /// Line tension of the cardinal spline. The value ranges from 0 to 1. - /// - /// Defaults to `0.5`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// cardinalSplineTension: 0.4, - /// ), - /// ], - /// ); - /// } - /// ``` - final double cardinalSplineTension; - - /// Create the spline area series renderer. - SplineSeriesRenderer createRenderer(ChartSeries series) { - SplineSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as SplineSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return SplineSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is SplineSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.gradient == gradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes && - other.cardinalSplineTension == cardinalSplineTension && - other.splineType == splineType; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - gradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - cardinalSplineTension, - splineType, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index edd1b0ba2..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the stacked area series. -/// -/// A stacked area chart is the extension of a basic area chart to display -/// the evolution of the value of several groups on the same graphic. -/// -/// The values of each group are displayed on top of each other. -/// -/// To render a stacked area chart, create an instance of [StackedAreaSeries], and add it to the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize the [color], [opacity], [borderWidth], [borderColor] of the stacked area segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - this.borderDrawMode = BorderDrawMode.top}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - trendlines: trendlines, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - groupName: groupName, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of stacked area series. - /// - /// Defaults to `BorderDrawMode.top`. - /// - /// Also refer [BorderDrawMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedAreaSeries( - /// borderDrawMode: BorderDrawMode.all, - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderDrawMode borderDrawMode; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedAreaSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.groupName == groupName && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.gradient == gradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.borderGradient == borderGradient && - other.borderDrawMode == borderDrawMode; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - groupName, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - gradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - borderColor, - borderWidth, - borderGradient, - borderDrawMode, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the stacked area series renderer. - StackedAreaSeriesRenderer createRenderer(ChartSeries series) { - StackedAreaSeriesRenderer stackedAreaSeriesRenderer; - if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = - onCreateRenderer!(series) as StackedAreaSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedAreaSeriesRenderer(); - } -} 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 deleted file mode 100644 index e5d7526fb..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart +++ /dev/null @@ -1,238 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the stacked bar series. -/// -/// Stacked bar chart consists of multiple bar series stacked horizontally one after another. -/// The length of each series is determined by the value in each data point. -/// -/// To render a stacked bar chart, create an instance of [StackedBarSeries], and add it to -/// the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize properties such as [color], [opacity], -/// [borderWidth], [borderColor], [borderRadius] of the Stackedbar segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} - -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - dashArray: dashArray, - groupName: groupName, - spacing: spacing, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - trendlines: trendlines, - xAxisName: xAxisName, - isTrackVisible: isTrackVisible, - trackColor: trackColor, - trackBorderColor: trackBorderColor, - trackBorderWidth: trackBorderWidth, - trackPadding: trackPadding, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - borderRadius: borderRadius, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedBarSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.groupName == groupName && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - groupName, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the stacked bar series renderer. - StackedBarSeriesRenderer createRenderer(ChartSeries series) { - StackedBarSeriesRenderer stackedAreaSeriesRenderer; - if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = - onCreateRenderer!(series) as StackedBarSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedBarSeriesRenderer(); - } -} 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 deleted file mode 100644 index d5cca5554..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart +++ /dev/null @@ -1,236 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the stacked column series. -/// -/// In a stacked column chart, data series are stacked one on top of the other in vertical columns. -/// -/// To render a stacked column chart, create an instance of [StackedColumnSeries], -/// and add it to the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize properties such as [color], [opacity], -/// [borderWidth], [borderColor], [borderRadius] of the stacked column segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - dashArray: dashArray, - groupName: groupName, - spacing: spacing, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - trendlines: trendlines, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - isTrackVisible: isTrackVisible, - trackColor: trackColor, - trackBorderColor: trackBorderColor, - trackBorderWidth: trackBorderWidth, - trackPadding: trackPadding, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - borderRadius: borderRadius, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedColumnSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.groupName == groupName && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - groupName, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the stacked area series renderer. - StackedColumnSeriesRenderer createRenderer(ChartSeries series) { - StackedColumnSeriesRenderer stackedAreaSeriesRenderer; - if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = - onCreateRenderer!(series) as StackedColumnSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedColumnSeriesRenderer(); - } -} 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 deleted file mode 100644 index 5cf1c1712..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the stacked line series. -/// -/// A stacked line chart is a line chart in which lines do not overlap because they are cumulative at each point. -/// -/// A stacked line chart displays series as a set of points connected by a line. -/// -/// To render a stacked line chart, create an instance of [StackedLineSeries], and add it to the series collection property of [SfCartesianChart]. -/// Provides options to customize [color], [opacity], [width] of the stacked line segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - SortingOrder? sortingOrder, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - trendlines: trendlines, - yAxisName: yAxisName, - color: color, - width: width ?? 2, - markerSettings: markerSettings, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - groupName: groupName, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedLineSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.groupName == groupName && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - groupName, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// To create a stacked line series renderer. - StackedLineSeriesRenderer createRenderer(ChartSeries series) { - StackedLineSeriesRenderer stackedLineSeriesRenderer; - if (onCreateRenderer != null) { - stackedLineSeriesRenderer = - onCreateRenderer!(series) as StackedLineSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedLineSeriesRenderer(); - } -} 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 deleted file mode 100644 index e09e05048..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart +++ /dev/null @@ -1,249 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../common/data_label.dart'; -import '../common/marker.dart'; -import '../trendlines/trendlines.dart'; -import 'xy_data_series.dart'; - -/// Represents the stacked series base class. -abstract class StackedSeriesBase extends XyDataSeries { - /// Creates an instance of stacked series renderer. - 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, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - double? animationDelay, - double? opacity}) - : borderRadius = borderRadius ?? BorderRadius.zero, - trackColor = trackColor ?? Colors.grey, - trackBorderColor = trackBorderColor ?? Colors.transparent, - trackBorderWidth = trackBorderWidth ?? 1, - trackPadding = trackPadding ?? 0, - groupName = groupName ?? '', - isTrackVisible = isTrackVisible ?? false, - dashArray = dashArray ?? [0, 0], - spacing = spacing ?? 0, - super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - trendlines: trendlines, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - initialSelectedDataIndexes: initialSelectedDataIndexes, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Customizes the corners of the column. Each corner can be customized with a desired - /// value or with a single value. - /// - /// Defaults to `BorderRadius.zero`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// borderRadius: BorderRadius.circular(5), - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderRadius borderRadius; - - /// Spacing between the columns. The value ranges from 0 to 1. - /// 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing also affects the width of the column. For example, setting 20% spacing - /// and 100% width renders the column with 80% of total width. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// spacing: 0.5, - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Renders column with track. Track is a rectangular bar rendered from the start - /// to the end of the axis. Column series will be rendered above the track. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// isTrackVisible: true, - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isTrackVisible; - - /// Color of the track. - /// - /// Defaults to `Colors.grey`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// isTrackVisible: true, - /// trackColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackColor; - - /// Color of the track border. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color trackBorderColor; - - /// Width of the track border. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// isTrackVisible: true, - /// trackBorderColor: Colors.red , - /// trackBorderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackBorderWidth; - - /// Padding of the track. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// isTrackVisible: true, - /// trackPadding: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final double trackPadding; - - /// Specifies the dash array. - @override - final List dashArray; - - /// Specifies the group name. - final String groupName; -} - -/// Represents the stacked series renderer class. -abstract class StackedSeriesRenderer extends XyDataSeriesRenderer {} 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 deleted file mode 100644 index 019018951..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart +++ /dev/null @@ -1,227 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the stacked area series. -/// -/// A stacked area chart is the extension of a basic area chart to display the -/// evolution of the value of several groups on the same graphic. -/// -/// The values of each group are displayed on top of each other. -/// -/// Stacked area 100 series show the percentage-of-the-whole of each group -/// and are plotted by the percentage of each value to the total amount in each group. -/// -/// Provides options to customize the [color], [opacity], [borderWidth], [borderColor], -/// [borderDrawMode] of the stacked area 100 series segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - this.borderDrawMode = BorderDrawMode.top}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - color: color, - trendlines: trendlines, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - groupName: groupName, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of stacked area 100 series. - /// - /// Defaults to `BorderDrawMode.top`. - /// - /// Also refer [BorderDrawMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedAreaSeries( - /// borderDrawMode: BorderDrawMode.all, - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderDrawMode borderDrawMode; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedArea100Series && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.groupName == groupName && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.gradient == gradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.borderGradient == borderGradient && - other.borderDrawMode == borderDrawMode; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - groupName, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - gradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - borderColor, - borderWidth, - borderGradient, - borderDrawMode, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the stacked area series renderer. - StackedArea100SeriesRenderer createRenderer(ChartSeries series) { - StackedArea100SeriesRenderer stackedAreaSeriesRenderer; - if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = - onCreateRenderer!(series) as StackedArea100SeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedArea100SeriesRenderer(); - } -} 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 deleted file mode 100644 index 6dfd2c38b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart +++ /dev/null @@ -1,227 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the 100% stacked bar series. -/// -/// A [StackedBar100Series] is a chart series type designed to show the relative percentage of multiple data series in stacked bars, -/// where the total (cumulative) of each stacked bar always equals 100. -/// -/// To render a 100% stacked bar chart, create an instance of [StackedBar100Series], and add it to -/// the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize properties such as [color], [opacity], -/// [borderWidth], [borderColor], [borderRadius] of the stacked bar 100 segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - dashArray: dashArray, - groupName: groupName, - spacing: spacing, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - trendlines: trendlines, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - borderRadius: borderRadius, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedBar100Series && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.groupName == groupName && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - groupName, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the stacked area series renderer. - StackedBar100SeriesRenderer createRenderer(ChartSeries series) { - StackedBar100SeriesRenderer stackedBarSeriesRenderer; - if (onCreateRenderer != null) { - stackedBarSeriesRenderer = - onCreateRenderer!(series) as StackedBar100SeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedBar100SeriesRenderer(); - } -} 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 deleted file mode 100644 index 43f18c2b4..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart +++ /dev/null @@ -1,227 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the 100% stacked column series. -/// -/// A stacked column 100 is an chart series type meant to show the relative -/// percentage of multiple data series in stacked columns, where the total (cumulative) of stacked columns always equals 100%. -/// -/// To render a 100% stacked column chart, create an instance of [StackedColumn100Series], -/// and add it to the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize properties such as [color], [opacity], -/// [borderWidth], [borderColor], [borderRadius] of the Stacked column 100 segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - List? dashArray, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - dashArray: dashArray, - groupName: groupName, - spacing: spacing, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - trendlines: trendlines, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - borderRadius: borderRadius, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedColumn100Series && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.groupName == groupName && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.trackColor == trackColor && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackPadding == trackPadding && - other.spacing == spacing && - other.borderRadius == borderRadius && - other.isTrackVisible == isTrackVisible && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - groupName, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - trackColor, - trackBorderColor, - trackBorderWidth, - trackPadding, - spacing, - borderRadius, - isTrackVisible, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the stacked area series renderer. - StackedColumn100SeriesRenderer createRenderer(ChartSeries series) { - StackedColumn100SeriesRenderer stackedAreaSeriesRenderer; - if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = - onCreateRenderer!(series) as StackedColumn100SeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedColumn100SeriesRenderer(); - } -} 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 deleted file mode 100644 index ac7463ac6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import 'stacked_series_base.dart'; - -/// Renders the 100% stacked line series. -/// -/// A stacked 100 line chart is a line chart in which lines do not overlap because they are cumulative at each point. -/// In the stacked 100 line chart, the lines reach a total of 100% of the axis range at each point. -/// -/// To render a 100% stacked line chart, create an instance of [StackedLine100Series], and add it to the series collection property of [SfCartesianChart]. -/// Provides options to customize color,opacity and width of the StackedLine100 segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - SortingOrder? sortingOrder, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 2, - trendlines: trendlines, - markerSettings: markerSettings, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - groupName: groupName, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StackedLine100Series && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.groupName == groupName && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - groupName, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// to create a stacked line 100 series renderer. - StackedLine100SeriesRenderer createRenderer(ChartSeries series) { - StackedLine100SeriesRenderer stackedLine100SeriesRenderer; - if (onCreateRenderer != null) { - stackedLine100SeriesRenderer = - onCreateRenderer!(series) as StackedLine100SeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StackedLine100SeriesRenderer(); - } -} 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 deleted file mode 100644 index d7a8b29a1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the step area series. -/// -/// A step area chart is a form of area chart which is useful for displaying changing numeric values over a period of time. -/// -/// The x-axis in a step area chart represents a period of time, -/// and values are plotted on the y-axis at constant or irregular time intervals. -/// -/// Step area charts are similar to step line charts, except in a step area chart -/// the area occupied by the data series is filled in with color. -/// -/// To render a spline area chart, create an instance of [StepAreaSeries], and add it to the series collection property of [SfCartesianChart]. -/// -/// Provides options to customize the [color], [opacity], [width] of the step area segments. -@immutable -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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - this.borderDrawMode = BorderDrawMode.top}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - name: name, - trendlines: trendlines, - color: color, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - gradient: gradient, - borderGradient: borderGradient, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Border type of step area series. - /// - /// Defaults to `BorderDrawMode.top`. - /// - /// Also refer [BorderDrawMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StepAreaSeries( - /// borderDrawMode: BorderDrawMode.all, - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderDrawMode borderDrawMode; - - /// To create a step area series renderer. - StepAreaSeriesRenderer createRenderer(ChartSeries series) { - StepAreaSeriesRenderer stepAreaRenderer; - if (onCreateRenderer != null) { - stepAreaRenderer = onCreateRenderer!(series) as StepAreaSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StepAreaSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StepAreaSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.pointColorMapper == pointColorMapper && - other.dataLabelMapper == dataLabelMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.borderDrawMode == borderDrawMode && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - pointColorMapper, - dataLabelMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - borderColor, - borderWidth, - gradient, - borderGradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - borderDrawMode, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 652d83217..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the step line series. -/// -/// A step line chart is a line chart in which points are connected -/// by horizontal and vertical line segments, looking like steps of a staircase. -/// -/// To render a step line chart, create an instance of [StepLineSeries], and add it to the series collection property of [SfCartesianChart]. -/// Provides option to customize the [color], [opacity], [width] of the step line segments. -@immutable -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, - SelectionBehavior? selectionBehavior, - SortingOrder? sortingOrder, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - double? animationDelay, - double? opacity}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - xAxisName: xAxisName, - yAxisName: yAxisName, - trendlines: trendlines, - name: name, - color: color, - width: width ?? 2, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - emptyPointSettings: emptyPointSettings, - enableTooltip: enableTooltip, - dashArray: dashArray, - isVisible: isVisible, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader); - - /// Create the stacked area series renderer. - StepLineSeriesRenderer createRenderer(ChartSeries series) { - StepLineSeriesRenderer stepLineSeriesRenderer; - if (onCreateRenderer != null) { - stepLineSeriesRenderer = - onCreateRenderer!(series) as StepLineSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return StepLineSeriesRenderer(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StepLineSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.gradient == gradient && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - xAxisName, - yAxisName, - name, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - isVisible, - enableTooltip, - dashArray, - animationDuration, - gradient, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 38dd32fa4..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart +++ /dev/null @@ -1,387 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -/// Renders the waterfall series. -/// -/// To render a waterfall chart, create an instance of [WaterfallSeries] and add to the series collection property of [SfCartesianChart]. -/// -/// [WaterfallSeries] is similar to range column series, -/// in range column high and low value should be there, but in waterfall -/// we have find the endValue and originValue of each data point. -@immutable -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, - 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 = BorderRadius.zero, - bool? enableTooltip, - double? animationDuration, - Color? borderColor, - List? trendlines, - double? borderWidth, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - double? opacity, - double? animationDelay, - List? dashArray, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - name: name, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - intermediateSumPredicate: intermediateSumPredicate, - totalSumPredicate: totalSumPredicate, - sortFieldValueMapper: sortFieldValueMapper, - pointColorMapper: pointColorMapper, - dataLabelMapper: dataLabelMapper, - dataSource: dataSource, - trendlines: trendlines, - xAxisName: xAxisName, - yAxisName: yAxisName, - color: color, - width: width ?? 0.7, - markerSettings: markerSettings, - dataLabelSettings: dataLabelSettings, - isVisible: isVisible, - gradient: gradient, - borderGradient: borderGradient, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - borderColor: borderColor, - borderWidth: borderWidth, - selectionBehavior: selectionBehavior, - legendItemText: legendItemText, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - dashArray: dashArray, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Color of the negative data points in the series. - /// - /// If no color is specified, then the negative data points will be rendered with the - /// series default color. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// negativePointsColor: Colors.red, - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? negativePointsColor; - - /// Color of the intermediate sum points in the series. - /// - /// If no color is specified, then the intermediate sum points will be rendered with the - /// series default color. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// intermediateSumColor: Colors.red, - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? intermediateSumColor; - - /// Color of the total sum points in the series. - /// - /// If no color is specified, then the total sum points will be rendered with the - /// series default color. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// totalSumColor: Colors.red, - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? totalSumColor; - - /// Options to customize the waterfall chart connector line. - /// - /// Data points in waterfall chart are connected using the connector line. Provides the options to - /// change the width, color and dash array of the connector line to customize the appearance. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// connectorLineSettings: WaterfallConnectorLineSettings( - /// width: 2, - /// color: Colors.black, - /// dashArray: [2,3] - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final WaterfallConnectorLineSettings connectorLineSettings; - - /// Spacing between the data points in waterfall chart. - /// - /// The value ranges from 0 to 1, where 1 represents 100% and 0 represents 0% of the available space. - /// - /// Spacing affects the width of the bars in waterfall. For example, setting 20% spacing and 100% width - /// renders the bars with 80% of total width. - /// - /// Also refer [CartesianSeries.width]. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// spacing: 0.5, - /// ), - /// ], - /// ); - /// } - /// ``` - final double spacing; - - /// Customizes the corners of the waterfall. - /// - /// Each corner can be customized with a desired value or with a single value. - /// - /// Defaults to `Radius.zero`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// borderRadius: BorderRadius.circular(5), - /// ), - /// ], - /// ); - /// } - /// ``` - final BorderRadius borderRadius; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is WaterfallSeries && - other.key == key && - other.onCreateRenderer == onCreateRenderer && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.intermediateSumPredicate == intermediateSumPredicate && - other.totalSumPredicate == totalSumPredicate && - other.negativePointsColor == negativePointsColor && - other.intermediateSumColor == intermediateSumColor && - other.totalSumColor == totalSumColor && - other.sortFieldValueMapper == sortFieldValueMapper && - other.dataLabelMapper == dataLabelMapper && - other.pointColorMapper == pointColorMapper && - other.sortingOrder == sortingOrder && - other.connectorLineSettings == connectorLineSettings && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.name == name && - other.spacing == spacing && - other.color == color && - other.width == width && - other.markerSettings == markerSettings && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.trendlines == trendlines && - other.gradient == gradient && - other.borderGradient == borderGradient && - other.borderRadius == borderRadius && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.isVisible == isVisible && - other.enableTooltip == enableTooltip && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.selectionBehavior == selectionBehavior && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.opacity == opacity && - other.animationDelay == animationDelay && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.onCreateShader == onCreateShader && - other.initialSelectedDataIndexes == initialSelectedDataIndexes; - } - - @override - int get hashCode { - final List values = [ - key, - onCreateRenderer, - dataSource, - xValueMapper, - yValueMapper, - intermediateSumPredicate, - totalSumPredicate, - negativePointsColor, - intermediateSumColor, - totalSumColor, - sortFieldValueMapper, - dataLabelMapper, - pointColorMapper, - sortingOrder, - connectorLineSettings, - xAxisName, - yAxisName, - name, - spacing, - color, - width, - markerSettings, - emptyPointSettings, - dataLabelSettings, - trendlines, - gradient, - borderGradient, - borderWidth, - borderRadius, - borderColor, - isVisible, - enableTooltip, - dashArray, - animationDuration, - selectionBehavior, - isVisibleInLegend, - legendIconType, - legendItemText, - opacity, - animationDelay, - onRendererCreated, - initialSelectedDataIndexes, - onPointTap, - onPointDoubleTap, - onPointLongPress - ]; - return hashList(values); - } - - /// Create the waterfall series renderer. - WaterfallSeriesRenderer createRenderer(ChartSeries series) { - WaterfallSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as WaterfallSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return WaterfallSeriesRenderer(); - } -} - -/// Options to customize the waterfall chart connector line. -/// -/// Data points in waterfall chart are connected using the connector line and this class hold the -/// properties to customize it. -/// -/// It provides the options to change the width, color and dash array of the connector line to -/// customize the appearance. -/// -class WaterfallConnectorLineSettings extends ConnectorLineSettings { - /// Creating an argument constructor of WaterfallConnectorLineSettings class. - const WaterfallConnectorLineSettings( - {double? width, Color? color, this.dashArray = const [0, 0]}) - : super(color: color, width: width ?? 1); - - /// Dashes of the waterfall chart connector line. - /// - /// Any number of values can be provided in the list. Odd values are considered as rendering - /// size and even values are considered as gap. - /// - /// Defaults to `null.` - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// WaterfallSeries( - /// connectorLineSettings: WaterfallConnectorLineSettings( - /// dashArray: [2,3] - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - 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 deleted file mode 100644 index 4e9a08ae9..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart +++ /dev/null @@ -1,646 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/event_args.dart' show ErrorBarValues; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/data_label.dart'; -import '../common/marker.dart'; -import '../trendlines/trendlines.dart'; - -/// Renders the xy series. -/// -/// Cartesian charts uses two axis namely x and y, to render. -abstract class XyDataSeries extends CartesianSeries { - /// Creating an argument constructor of XyDataSeries class. - XyDataSeries( - {ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - SeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - CartesianShaderCallback? onCreateShader, - 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, - SelectionBehavior? selectionBehavior, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - double? opacity, - double? animationDelay, - Color? color, - List? initialSelectedDataIndexes, - SortingOrder? sortingOrder}) - : super( - key: key, - onRendererCreated: onRendererCreated, - onCreateRenderer: onCreateRenderer, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - isVisible: isVisible, - legendItemText: legendItemText, - xAxisName: xAxisName, - dashArray: dashArray, - isVisibleInLegend: isVisibleInLegend, - borderColor: borderColor, - trendlines: trendlines, - borderWidth: borderWidth, - yAxisName: yAxisName, - color: color, - name: name, - width: width, - 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, - pointColorMapper: pointColorMapper != null - ? (int index) => pointColorMapper(dataSource[index], index) - : null, - dataLabelMapper: dataLabelMapper != null - ? (int index) => dataLabelMapper(dataSource[index], index) - : null, - sizeValueMapper: sizeValueMapper != null - ? (int index) => sizeValueMapper(dataSource[index], index) - : null, - highValueMapper: highValueMapper != null - ? (int index) => highValueMapper(dataSource[index], index) - : null, - lowValueMapper: lowValueMapper != null - ? (int index) => lowValueMapper(dataSource[index], index) - : null, - intermediateSumPredicate: intermediateSumPredicate != null - ? (int index) => - intermediateSumPredicate(dataSource[index], index) - : null, - totalSumPredicate: totalSumPredicate != null - ? (int index) => totalSumPredicate(dataSource[index], index) - : null, - dataSource: dataSource, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - enableTooltip: enableTooltip, - animationDuration: animationDuration, - selectionBehavior: selectionBehavior, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - opacity: opacity, - animationDelay: animationDelay, - onCreateShader: onCreateShader, - gradient: gradient, - borderGradient: borderGradient, - markerSettings: markerSettings, - initialSelectedDataIndexes: initialSelectedDataIndexes); -} - -/// This class has the properties of [CartesianChartPoint]. -/// -/// Chart point is a class that is used to store the current x and y values from the datasource. -/// Contains x and y coordinates which are converted from the x and y values. -/// -class CartesianChartPoint { - /// Creating an argument constructor of CartesianChartPoint class. - CartesianChartPoint( - [this.x, - this.y, - this.dataLabelMapper, - this.pointColorMapper, - this.bubbleSize, - this.high, - this.low, - this.open, - this.close, - this.volume, - this.sortValue, - this.minimum, - this.maximum, - this.isIntermediateSum, - this.isTotalSum, - this.maxYValue = 0, - this.outliers, - this.upperQuartile, - this.lowerQuartile, - this.mean, - this.median, - this.originValue, - this.endValue]) { - x = x; - y = y; - sortValue = sortValue; - markerPoint = markerPoint; - isEmpty = isEmpty; - isGap = isGap; - isVisible = isVisible; - bubbleSize = bubbleSize; - pointColorMapper = pointColorMapper; - dataLabelMapper = dataLabelMapper; - high = high; - low = low; - open = open; - close = close; - markerPoint2 = markerPoint2; - volume = volume; - minimum = minimum; - maximum = maximum; - outliers = outliers; - upperQuartile = upperQuartile; - lowerQuartile = lowerQuartile; - mean = mean; - median = median; - isIntermediateSum = isIntermediateSum; - isTotalSum = isTotalSum; - originValue = originValue; - endValue = endValue; - maxYValue = maxYValue; - } - - /// X value of the point. - D? x; - - /// Y value of the point. - D? y; - - /// Stores the xValues of the point. - D? xValue; - - /// Stores the yValues of the point. - D? yValue; - - /// Sort value of the point. - D? sortValue; - - /// High value of the point. - D? high; - - /// Low value of the point. - D? low; - - /// Open value of the point. - D? open; - - /// Close value of the point. - D? close; - - /// Volume value of the point. - num? volume; - - /// Marker point location. - ChartLocation? markerPoint; - - /// Second marker point location. - ChartLocation? markerPoint2; - - /// Size of the bubble. - num? bubbleSize; - - /// To set empty value. - bool? isEmpty; - - /// To set gap value. - bool isGap = false; - - /// To set the drop value. - bool isDrop = false; - - /// To check marker event is triggered. - bool _isMarkerEventTriggered = false; - - /// To store the marker color when callback is triggered. - MarkerDetails? _markerDetails; - - /// Sets the visibility of the series. - bool isVisible = true; - - /// Used to map the color value from data points. - Color? pointColorMapper; - - /// Maps the data label value from data points. - String? dataLabelMapper; - - /// Stores the region rect. - Rect? region; - - /// Stores the region of box series rect. - Rect? boxRectRegion; - - /// Store the outliers region. - List? outlierRegion; - - /// Store the outliers region. - List? outlierRegionPosition; - - /// Minimum value of box plot series. - num? minimum; - - /// Maximum value of box plot series. - num? maximum; - - /// Outlier values of box plot series. - List? outliers = []; - - /// Upper quartile value of box plot series. - double? upperQuartile; - - /// Lower quartile value of box plot series. - double? lowerQuartile; - - /// Average value of the given data source in box plot series. - num? mean; - - /// Median value of the given data source in box plot series. - num? median; - - /// The intermediate sum value of the waterfall series. - bool? isIntermediateSum; - - /// The total sum value of the waterfall series. - bool? isTotalSum; - - /// The end value of each data point in the waterfall series. - num? endValue; - - /// The Origin value of each data point in waterfall series. - num? originValue; - - /// To find the maximum Y value in the waterfall series. - num maxYValue; - - /// To execute OnDataLabelRender event or not. - // ignore: prefer_final_fields - bool labelRenderEvent = false; - - /// To execute onTooltipRender event or not. - // ignore: prefer_final_fields - bool isTooltipRenderEvent = false; - - /// Stores the chart location. - ChartLocation? openPoint, - closePoint, - centerOpenPoint, - centerClosePoint, - lowPoint, - highPoint, - centerLowPoint, - centerHighPoint, - currentPoint, - startControl, - endControl, - highStartControl, - highEndControl, - lowStartControl, - lowEndControl, - minimumPoint, - maximumPoint, - lowerQuartilePoint, - upperQuartilePoint, - centerMinimumPoint, - centerMaximumPoint, - medianPoint, - centerMedianPoint, - centerMeanPoint, - originValueLeftPoint, - originValueRightPoint, - endValueLeftPoint, - endValueRightPoint, - horizontalPositiveErrorPoint, - horizontalNegativeErrorPoint, - verticalPositiveErrorPoint, - verticalNegativeErrorPoint; - - /// Stores the error values in all directions - ErrorBarValues? errorBarValues; - - /// Stores the outliers location. - List outliersPoint = []; - - /// Control points for spline series. - List? controlPoint; - - /// Control points for spline range area series. - List? controlPointshigh; - - /// Control points for spline range area series. - List? controlPointslow; - - /// Stores the list of regions. - List? regions; - - /// Stores the cumulative value. - double? cumulativeValue; - - /// Stores the tracker rect region. - Rect? trackerRectRegion; - - /// Stores the y-value/high value data label text. - String? label; - - /// Stores the data label text of low value. - String? label2; - - /// Stores the data label text of close value. - String? label3; - - /// Stores the data label text of open value. - String? label4; - - /// Stores the median data label text. - String? label5; - - /// Stores the outliers data label text. - List outliersLabel = []; - - /// Stores the yValue/high value data label Rect. - RRect? labelFillRect; - - /// Stores the data label text of low value Rect. - RRect? labelFillRect2; - - /// Stores the data label text of close value Rect. - RRect? labelFillRect3; - - /// Stores the data label text of open value Rect. - RRect? labelFillRect4; - - /// Stores the median data label Rect. - RRect? labelFillRect5; - - /// Stores the outliers data label Rect. - List outliersFillRect = []; - - /// Stores the data label location. - ChartLocation? labelLocation; - - /// Stores the second data label location. - ChartLocation? labelLocation2; - - /// Stores the third data label location. - ChartLocation? labelLocation3; - - /// Stores the fourth data label location. - ChartLocation? labelLocation4; - - /// Stores the median data label location. - ChartLocation? labelLocation5; - - /// Stores the outliers data label location. - List outliersLocation = []; - - /// Data label region saturation. - bool dataLabelSaturationRegionInside = false; - - /// Stores the data label region. - Rect? dataLabelRegion; - - /// Stores the second data label region. - Rect? dataLabelRegion2; - - /// Stores the third data label region. - Rect? dataLabelRegion3; - - /// Stores the forth data label region. - Rect? dataLabelRegion4; - - /// Stores the median data label region. - Rect? dataLabelRegion5; - - /// Stores the outliers data label region. - List outliersDataLabelRegion = []; - - /// Stores the data point index. - int? index; - - /// Stores the data index. - int? overallDataPointIndex; - - /// Stores the region data of the data point. - List? regionData; - - /// Stores the visible point index. - int? visiblePointIndex; - - TextStyle? _dataLabelTextStyle; - - Color? _dataLabelColor; -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class for Cartesian chart point. -class CartesianPointHelper { - /// Returns the datalabel text style for a given point. - static TextStyle? getDataLabelTextStyle(CartesianChartPoint point) { - return point._dataLabelTextStyle; - } - - /// Sets the datalabel text style in a given point. - static void setDataLabelTextStyle( - CartesianChartPoint point, TextStyle? style) { - point._dataLabelTextStyle = style; - } - - /// Returns the datalabel color for a given point. - static Color? getDataLabelColor(CartesianChartPoint point) { - return point._dataLabelColor; - } - - /// Sets the datalabel color in a given point. - static void setDataLabelColor( - CartesianChartPoint point, Color? color) { - point._dataLabelColor = color; - } - - /// Returns the marker event triggered flag for a given point. - static bool getIsMarkerEventTriggered(CartesianChartPoint point) { - return point._isMarkerEventTriggered; - } - - /// Sets the marker event triggered flag for a given point. - static void setIsMarkerEventTriggered( - CartesianChartPoint point, bool isMarkerEventTriggered) { - point._isMarkerEventTriggered = isMarkerEventTriggered; - } - - /// Returns the MarkerDetails for a given point. - static MarkerDetails? getMarkerDetails(CartesianChartPoint point) { - return point._markerDetails; - } - - /// Sets the MarkerDetails for a given point. - static void setMarkerDetails( - CartesianChartPoint point, MarkerDetails? details) { - point._markerDetails = details; - } -} - -/// Represents the chart location. -class ChartLocation { - /// Creates an instance of chart location. - ChartLocation(this.x, this.y); - - /// Specifies the value of x. - double x; - - /// Specifies the value of y. - double y; -} - -/// Creates series renderer for xy data series. -abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { - /// To calculate empty point value for the specific mode. - @override - void calculateEmptyPointValue( - int pointIndex, CartesianChartPoint currentPoint, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - final int pointLength = seriesRendererDetails.dataPoints.length - 1; - final String _seriesType = seriesRendererDetails.seriesType; - final CartesianChartPoint prevPoint = - seriesRendererDetails.dataPoints[ - seriesRendererDetails.dataPoints.length >= 2 == true - ? pointLength - 1 - : pointLength]; - if (_seriesType.contains('range') || - _seriesType.contains('hilo') || - _seriesType == 'candle' - ? _seriesType == 'hiloopenclose' || _seriesType == 'candle' - ? (currentPoint.low == null || - currentPoint.high == null || - currentPoint.open == null || - currentPoint.close == null) - : (currentPoint.low == null || currentPoint.high == null) - : currentPoint.y == null) { - switch (seriesRendererDetails.series.emptyPointSettings.mode) { - case EmptyPointMode.zero: - currentPoint.isEmpty = true; - if (_seriesType.contains('range') || - _seriesType.contains('hilo') || - _seriesType.contains('candle')) { - currentPoint.high = 0; - currentPoint.low = 0; - if (_seriesType == 'hiloopenclose' || _seriesType == 'candle') { - currentPoint.open = 0; - currentPoint.close = 0; - } - } else { - currentPoint.y = 0; - } - break; - - case EmptyPointMode.average: - if (seriesRenderer is XyDataSeriesRenderer) { - seriesRendererDetails.calculateAverageModeValue( - pointIndex, pointLength, currentPoint, prevPoint); - } - currentPoint.isEmpty = true; - break; - - case EmptyPointMode.gap: - if (_seriesType == 'scatter' || - _seriesType == 'column' || - _seriesType == 'bar' || - _seriesType == 'bubble' || - _seriesType == 'splinearea' || - _seriesType == 'rangecolumn' || - _seriesType.contains('hilo') || - _seriesType.contains('candle') || - _seriesType == 'rangearea' || - _seriesType.contains('stacked')) { - currentPoint.y = pointIndex != 0 && - (!_seriesType.contains('stackedcolumn') && - !_seriesType.contains('stackedbar')) - ? prevPoint.y ?? 0 - : 0; - currentPoint.open = 0; - currentPoint.close = 0; - currentPoint.isVisible = false; - } else if (_seriesType.contains('line') || - _seriesType == 'area' || - _seriesType == 'steparea') { - if (_seriesType == 'splinerangearea') { - // ignore: prefer_if_null_operators - currentPoint.low = currentPoint.low == null - ? pointIndex != 0 - ? prevPoint.low ?? 0 - : 0 - : currentPoint.low; - // ignore: prefer_if_null_operators - currentPoint.high = currentPoint.high == null - ? pointIndex != 0 - ? prevPoint.high ?? 0 - : 0 - : currentPoint.high; - } else { - currentPoint.y = pointIndex != 0 ? prevPoint.y ?? 0 : 0; - } - } - currentPoint.isVisible = false; - currentPoint.isGap = true; - break; - case EmptyPointMode.drop: - if (_seriesType == 'splinerangearea') { - // ignore: prefer_if_null_operators - currentPoint.low = currentPoint.low == null - ? pointIndex != 0 - ? prevPoint.low ?? 0 - : 0 - : currentPoint.low; - // ignore: prefer_if_null_operators - currentPoint.high = currentPoint.high == null - ? pointIndex != 0 - ? prevPoint.high ?? 0 - : 0 - : currentPoint.high; - } - currentPoint.y = pointIndex != 0 && - (_seriesType != 'area' && - _seriesType != 'splinearea' && - _seriesType != 'splinerangearea' && - _seriesType != 'steparea' && - !_seriesType.contains('stackedcolumn') && - !_seriesType.contains('stackedbar')) - ? prevPoint.y ?? 0 - : 0; - currentPoint.isDrop = true; - currentPoint.isVisible = false; - break; - default: - currentPoint.y = 0; - break; - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart deleted file mode 100644 index 91ada8d0f..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart +++ /dev/null @@ -1,383 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; -import 'dart:ui' as dart_ui; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; - -import '../../common/rendering_details.dart'; -import '../../common/state_properties.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../axis/axis.dart'; -import '../axis/axis_panel.dart'; -import '../axis/axis_renderer.dart'; -import '../base/chart_base.dart'; -import '../base/series_base.dart'; -import '../chart_series/series.dart'; -import '../common/common.dart'; -import '../common/interactive_tooltip.dart'; -import '../common/renderer.dart'; - -import '../technical_indicators/technical_indicator.dart'; -import '../user_interaction/trackball.dart'; -import '../user_interaction/trackball_marker_setting_renderer.dart'; -import '../user_interaction/zooming_panning.dart'; - -/// Represents the Cartesian state properties class -class CartesianStateProperties extends StateProperties { - /// Creates an instance of Cartesian state properties - CartesianStateProperties( - {required this.renderingDetails, required this.chartState}) - : super(renderingDetails, chartState); - - /// Holds the Cartesian chart state - @override - final SfCartesianChartState chartState; - - /// Specifies the chart rendering details - @override - late RenderingDetails renderingDetails; - - /// Holds the animation controller along with their listener for all series and trendlines - late Map controllerList; - - /// Holds the value of repaint notifiers - late Map> repaintNotifiers; - - /// Holds the value of zoom axis renderer state - late List zoomedAxisRendererStates; - - /// Holds the value of old axis renderer - late List oldAxisRenderers; - - /// Specifies whether the zoom is in progress - late bool zoomProgress; - - /// Specifies the list of zoom axes - late List zoomAxes; - - /// Specifies the value of selected segments - late List selectedSegments; - - /// Specifies the list of unselected segments - late List unselectedSegments; - - /// Holds the list of data label region - late List renderDatalabelRegions; - - /// Holds the list of annotation region - late List annotationRegions; - - /// Specifies whether the legend is refreshed - bool legendRefresh = false; - - /// Holds the data label renderer - DataLabelRenderer? renderDataLabel; - - /// Holds the value axis renderer outside - late CartesianAxisWidget renderOutsideAxis; - - /// Holds the value axis renderer inside - late CartesianAxisWidget renderInsideAxis; - - ///Holds the list of old series renderers - late List oldSeriesRenderers; - - /// Holds the list of old series keys - late List?> oldSeriesKeys; - - /// Holds the list of segments - late List segments; - - /// Holds the list of old visible series - late List oldSeriesVisible; - - /// Specifies the zoom state - bool? zoomedState; - - /// Holds the touch state position - late List touchStartPositions; - - /// Holds the touch move position - late List touchMovePositions; - - ///Specifies whether the double tap or mouse hover is enabled - late bool enableDoubleTap, enableMouseHover; - - /// Specifies whether legend is toggled - bool legendToggling = false; - - /// Specifies the value of background image - dart_ui.Image? backgroundImage; - - /// Specifies the value of legend icon image - dart_ui.Image? legendIconImage; - - /// Specifies whether the trendline is toggled - bool isTrendlineToggled = false; - - /// Specifies the list of painter keys - late List painterKeys; - - /// Specifies whether the trigger is loaded - late bool triggerLoaded; - - /// Specifies the range change by slider value - //ignore: preferfinalfields - bool rangeChangeBySlider = false; - - /// Specifies the value of range changed by chart - //ignore: preferfinalfields - bool rangeChangedByChart = false; - - /// Specifies whether the range is selected by slider - //ignore: preferfinalfields - bool isRangeSelectionSlider = false; - - /// Specifies whether the series is loaded - bool? isSeriesLoaded; - - /// Specifies whether update is needed - late bool isNeedUpdate; - - /// Specifies the list of series renderer - late List seriesRenderers; - - /// Holds the information of AxisBase class - late ChartAxisPanel chartAxis; - - /// Holds the information of SeriesBase class - late ChartSeriesPanel chartSeries; - - /// Holds the information of ContainerArea class - /// ignore: unusedfield - late ContainerArea containerArea; - - /// Whether to check chart axis is inverted or not - late bool requireInvertedAxis; - - /// To check if axis trimmed text is tapped - //ignore: preferfinalfields - bool requireAxisTooltip = false; - - /// To check the trackball orientation changes. - bool isTrackballOrientationChanged = false; - - /// To check the crosshair orientation changes. - bool isCrosshairOrientationChanged = false; - - /// Trackball timer. - Timer? trackballTimer; - - /// Tooltip timer. - Timer? tooltipTimer; - - /// To check the tooltip orientation changes. - bool isTooltipOrientationChanged = false; - - /// To check if tooltip has been hidden or not. - bool isTooltipHidden = false; - - /// Specifies the list of chart point info - //ignore: preferfinalfields - List chartPointInfo = []; - - /// Holds the zoom pan behavior renderer - late ZoomPanBehaviorRenderer zoomPanBehaviorRenderer; - - /// Holds the trackball behavior renderer - late TrackballBehaviorRenderer trackballBehaviorRenderer; - - /// Holds the cross hair behavior renderer - late CrosshairBehaviorRenderer crosshairBehaviorRenderer; - - /// Holds the technical indicator renderer - late List technicalIndicatorRenderer; - - /// Holds the trackball marker setting renderer - late TrackballMarkerSettingsRenderer trackballMarkerSettingsRenderer; - - //Here, we are using get keyword in order 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. - @override - SfCartesianChart get chart => chartState.widget; - - /// Setting series animation duration factor - double seriesDurationFactor = 0.85; - - /// Setting trendline animation duration factor - double trendlineDurationFactor = 0.85; - - /// holds the count for total no of series that should be animated - late int totalAnimatingSeries; - - /// holds the no of animation completed series - late int animationCompleteCount; - - /// Specifies the value of selection args - SelectionArgs? selectionArgs; - - /// Specifies whether the touch is up - bool isTouchUp = false; - - /// Holds the value of load more state setter - late StateSetter loadMoreViewStateSetter; - - /// Represents the swipe direction - late ChartSwipeDirection swipeDirection; - - /// Specifies the value of offset - Offset? startOffset, currentPosition; - - /// Specifies whether redrawn is due to zoom and pan - late bool isRedrawByZoomPan; - - /// Holds the value of pointer device kind - late PointerDeviceKind pointerDeviceKind; - - ///To check the load more widget is in progress or not - late bool isLoadMoreIndicator; - - /// Specifies whether to set the range controller - bool canSetRangeController = false; - - /// Specifies the shader for series - Shader? shader; - - /// Method to set the painter key - void setPainterKey(int index, String name, bool renderComplete) { - int value = 0; - for (int i = 0; i < painterKeys.length; i++) { - final PainterKey painterKey = painterKeys[i]; - if (painterKey.isRenderCompleted) { - value++; - } else if (painterKey.index == index && - painterKey.name == name && - !painterKey.isRenderCompleted) { - painterKey.isRenderCompleted = renderComplete; - value++; - } - if (value >= painterKeys.length && !triggerLoaded) { - triggerLoaded = true; - } - } - } - - /// Method to check whether the animation is completed - // ignore: unused_element - bool get animationCompleted { - SeriesRendererDetails seriesRendererDetails; - for (final CartesianSeriesRenderer seriesRenderer in seriesRenderers) { - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.animationController.status == - AnimationStatus.forward) { - return false; - } - } - return true; - } - - /// Method to repaint the trendlines - void repaintTrendlines() { - repaintNotifiers['trendline']!.value++; - } - - /// Method to forward the animation - void forwardAnimation(SeriesRendererDetails seriesRendererDetails) { - final int totalAnimationDuration = - seriesRendererDetails.series.animationDuration.toInt() + - seriesRendererDetails.series.animationDelay!.toInt(); - seriesRendererDetails.animationController.duration = - Duration(milliseconds: totalAnimationDuration); - const double maxSeriesInterval = 0.8; - double minSeriesInterval = 0.1; - minSeriesInterval = seriesRendererDetails.series.animationDelay!.toInt() / - totalAnimationDuration * - (maxSeriesInterval - minSeriesInterval) + - minSeriesInterval; - seriesRendererDetails.seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: seriesRendererDetails.animationController, - curve: Interval(minSeriesInterval, maxSeriesInterval, - curve: Curves.decelerate), - )); - seriesRendererDetails.seriesElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: seriesRendererDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - seriesRendererDetails.animationController.forward(from: 0.0); - } - - /// Method to redraw by change - void redrawByRangeChange() { - oldAxisRenderers = chartAxis.axisRenderersCollection; - if (chartState.mounted) { - // ignore: invalid_use_of_protected_member - chartState.setState(() { - /// check the "mounted" property of this object and to ensure the object is still in the tree. - /// When we do the range change by using the slider or other way, chart will be rebuilding again. - }); - } - } - - /// Redraw method for chart axis - void redraw() { - oldAxisRenderers = chartAxis.axisRenderersCollection; - TooltipHelper.getRenderingDetails(renderingDetails.tooltipBehaviorRenderer) - .timer - ?.cancel(); - if (TrackballHelper.getRenderingDetails(trackballBehaviorRenderer) - .trackballPainter - ?.timer != - null) { - TrackballHelper.getRenderingDetails(trackballBehaviorRenderer) - .trackballPainter - ?.timer! - .cancel(); - } - if (renderingDetails.isLegendToggled) { - segments = []; - oldSeriesVisible = - List.filled(chartSeries.visibleSeriesRenderers.length, null); - for (int i = 0; i < chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - chartSeries.visibleSeriesRenderers[i]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRenderer is ColumnSeriesRenderer || - seriesRenderer is BarSeriesRenderer) { - for (int j = 0; j < seriesRendererDetails.segments.length; j++) { - segments.add(seriesRendererDetails.segments[j]); - } - } - } - } - // ignore: unnecessary_null_comparison - if (zoomedAxisRendererStates != null && - zoomedAxisRendererStates.isNotEmpty) { - zoomedState = false; - for (final ChartAxisRenderer axisRenderer in zoomedAxisRendererStates) { - zoomedState = - AxisHelper.getAxisRendererDetails(axisRenderer).zoomFactor != 1; - if (zoomedState!) { - break; - } - } - } - - renderingDetails.widgetNeedUpdate = false; - - if (chartState.mounted) { - isRedrawByZoomPan = true; - // ignore: invalid_use_of_protected_member - chartState.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. - }); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart deleted file mode 100644 index 108cb536a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart +++ /dev/null @@ -1,2088 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; -import 'dart:ui' as dart_ui; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/axis/datetime_axis.dart' - show DateTimeAxisDetails; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/marker.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -import '../../common/event_args.dart'; -import '../../common/rendering_details.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../../common/utils/typedef.dart'; -import '../axis/axis.dart'; -import '../base/chart_base.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_segment/scatter_segment.dart'; -import '../chart_series/error_bar_series.dart'; -import '../chart_series/series.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/waterfall_series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/data_label.dart'; -import '../common/renderer.dart'; -import '../series_painter/box_and_whisker_painter.dart'; -import '../series_painter/stacked_line_painter.dart'; -import '../user_interaction/trackball.dart'; -import '../utils/enum.dart' - show ErrorBarType, RenderingMode, Direction, DateTimeIntervalType; -import '../utils/helper.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Represents the custom paint style class. -class CustomPaintStyle { - /// Creates an instance of custom paint class. - CustomPaintStyle(this.strokeWidth, this.color, this.paintStyle); - - /// Specifies the color value. - Color color; - - /// Specifies the value of stroke width. - double strokeWidth; - - /// Specifies the value of painting style. - PaintingStyle paintStyle; -} - -/// Represents the axis size class. -class AxisSize { - /// Creates an instance of axis size class. - AxisSize(this.axisRenderer, this.size); - - /// Holds the value of size. - double size; - - /// Holds the value of axis renderer. - ChartAxisRenderer axisRenderer; -} - -/// Represents the painter key. -class PainterKey { - /// Creates an instance for painter key. - PainterKey( - {required this.index, - required this.name, - required this.isRenderCompleted}); - - /// Represents the value of index. - final int index; - - /// Specifies the key name. - final String name; - - /// Specifies whether the rendering is completed. - bool isRenderCompleted; -} - -/// Represents the class of stacked values. -class StackedValues { - /// Creates the instance of stacked values. - StackedValues(this.startValues, this.endValues); - - /// Represents the start value. - List startValues; - - /// Represents the end values. - List endValues; -} - -/// Represents the cluster stacked item info class. -class ClusterStackedItemInfo { - /// Creates an instance of cluster stacked info. - ClusterStackedItemInfo(this.stackName, this.stackedItemInfo); - - /// Holds the stack name value. - String stackName; - - /// Holds the list of stacked item info. - List stackedItemInfo; -} - -/// Represents the stacked item info class. -class StackedItemInfo { - /// Creates an instance of stacked item info. - StackedItemInfo(this.seriesIndex, this.seriesRenderer); - - /// Specifies the value of series index. - int seriesIndex; - - /// Specifies the value of stacked series renderer. - StackedSeriesRenderer seriesRenderer; -} - -/// To get Cartesian type data label saturation color. -Color getDataLabelSaturationColor( - CartesianChartPoint currentPoint, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - DataLabelSettingsRenderer dataLabelSettingsRenderer) { - final SfCartesianChart chart = stateProperties.chart; - Color color; - final DataLabelSettings dataLabel = - seriesRendererDetails.series.dataLabelSettings; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final ChartDataLabelAlignment labelPosition = - (seriesRendererDetails.seriesType == 'rangecolumn' && - (dataLabel.labelAlignment == ChartDataLabelAlignment.bottom || - dataLabel.labelAlignment == ChartDataLabelAlignment.middle)) - ? ChartDataLabelAlignment.auto - : dataLabel.labelAlignment; - final ChartAlignment alignment = dataLabel.alignment; - final String seriesType = seriesRendererDetails.seriesType == 'line' || - seriesRendererDetails.seriesType == 'stackedline' || - seriesRendererDetails.seriesType == 'stackedline100' || - seriesRendererDetails.seriesType == 'spline' || - seriesRendererDetails.seriesType == 'stepline' - ? 'Line' - : seriesRendererDetails.isRectSeries == true - ? 'Column' - : seriesRendererDetails.seriesType == 'bubble' || - seriesRendererDetails.seriesType == 'scatter' - ? 'Circle' - : seriesRendererDetails.seriesType.contains('area') == true - ? 'area' - : 'Default'; - if (dataLabel.useSeriesColor || dataLabelSettingsRenderer.color != null) { - color = dataLabelSettingsRenderer.color ?? - (currentPoint.pointColorMapper ?? seriesRendererDetails.seriesColor!); - } else { - switch (seriesType) { - case 'Line': - color = _getOuterDataLabelColor(dataLabelSettingsRenderer, - chart.plotAreaBackgroundColor, renderingDetails.chartTheme); - break; - case 'Column': - color = (!currentPoint.dataLabelSaturationRegionInside && - (labelPosition == ChartDataLabelAlignment.outer || - (labelPosition == ChartDataLabelAlignment.top && - alignment == ChartAlignment.far) || - seriesRendererDetails.seriesType == 'rangecolumn' && - (labelPosition == ChartDataLabelAlignment.top && - alignment == ChartAlignment.near) || - (labelPosition == ChartDataLabelAlignment.auto && - (seriesRendererDetails.seriesType.contains('100') == - false && - seriesRendererDetails.seriesType != 'stackedbar' && - seriesRendererDetails.seriesType != - 'stackedcolumn')))) - ? _getOuterDataLabelColor(dataLabelSettingsRenderer, - chart.plotAreaBackgroundColor, renderingDetails.chartTheme) - : _getInnerDataLabelColor(currentPoint, seriesRendererDetails, - renderingDetails.chartTheme); - break; - case 'Circle': - color = (labelPosition == ChartDataLabelAlignment.middle && - alignment == ChartAlignment.center || - labelPosition == ChartDataLabelAlignment.bottom && - alignment == ChartAlignment.far || - labelPosition == ChartDataLabelAlignment.top && - alignment == ChartAlignment.near || - labelPosition == ChartDataLabelAlignment.outer && - alignment == ChartAlignment.near) - ? _getInnerDataLabelColor(currentPoint, seriesRendererDetails, - renderingDetails.chartTheme) - : _getOuterDataLabelColor(dataLabelSettingsRenderer, - chart.plotAreaBackgroundColor, renderingDetails.chartTheme); - break; - - case 'area': - color = (!currentPoint.dataLabelSaturationRegionInside && - currentPoint.labelLocation!.y < currentPoint.markerPoint!.y) - ? _getOuterDataLabelColor(dataLabelSettingsRenderer, - chart.plotAreaBackgroundColor, renderingDetails.chartTheme) - : _getInnerDataLabelColor(currentPoint, seriesRendererDetails, - renderingDetails.chartTheme); - break; - - default: - color = Colors.white; - } - } - return getSaturationColor(color); -} - -/// Get the data label color of open-close series -Color getOpenCloseDataLabelColor(CartesianChartPoint currentPoint, - SeriesRendererDetails seriesRendererDetails, SfCartesianChart chart) { - final int index = seriesRendererDetails.seriesType.contains('candle') - ? seriesRendererDetails.visibleDataPoints!.indexOf(currentPoint) - : seriesRendererDetails.dataPoints.indexOf(currentPoint); - final Color color = seriesRendererDetails.segments[index].fillPaint!.style == - PaintingStyle.fill - ? SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[index]) - .color! - : const Color.fromRGBO(255, 255, 255, 1); - return getSaturationColor(color); -} - -/// To get outer data label color -Color _getOuterDataLabelColor( - DataLabelSettingsRenderer dataLabelSettingsRenderer, - Color? backgroundColor, - SfChartThemeData theme) => - dataLabelSettingsRenderer.color ?? - backgroundColor ?? - (theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black); - -///To get inner data label -Color _getInnerDataLabelColor(CartesianChartPoint currentPoint, - SeriesRendererDetails seriesRendererDetails, SfChartThemeData theme) { - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRendererDetails.dataLabelSettingsRenderer; - Color innerColor; - Color? seriesColor = seriesRendererDetails.series.color; - if (seriesRendererDetails.seriesType == 'waterfall') { - seriesColor = getWaterfallSeriesColor( - seriesRendererDetails.series as WaterfallSeries, - currentPoint, - seriesColor); - } - // ignore: prefer_if_null_operators - innerColor = dataLabelSettingsRenderer.color != null - ? dataLabelSettingsRenderer.color! - // ignore: prefer_if_null_operators - : currentPoint.pointColorMapper != null - // ignore: prefer_if_null_operators - ? currentPoint.pointColorMapper! - // ignore: prefer_if_null_operators - : seriesColor != null - ? seriesColor - // ignore: prefer_if_null_operators - : seriesRendererDetails.seriesColor != null - ? seriesRendererDetails.seriesColor! - : theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; - return innerColor; -} - -/// To animate column and bar series -void animateRectSeries( - Canvas canvas, - CartesianSeriesRenderer seriesRenderer, - Paint fillPaint, - RRect segmentRect, - num yPoint, - double animationFactor, - Rect? oldSegmentRect, - num? oldYValue, - bool? oldSeriesVisible) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final bool comparePrev = - seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == - true && - oldYValue != null && - seriesRendererDetails.reAnimate == false && - oldSegmentRect != null; - final bool isLargePrev = oldYValue != null && oldYValue > yPoint; - final bool isSingleSeries = - seriesRendererDetails.stateProperties.renderingDetails.isLegendToggled == - true && - _checkSingleSeries(seriesRendererDetails); - ((seriesRendererDetails.seriesType == 'column' && - seriesRendererDetails.chart.isTransposed == true) || - (seriesRendererDetails.seriesType == 'bar' && - seriesRendererDetails.chart.isTransposed == false) || - (seriesRendererDetails.seriesType == 'histogram' && - seriesRendererDetails.chart.isTransposed == true)) - ? _animateTransposedRectSeries( - canvas, - seriesRendererDetails, - fillPaint, - segmentRect, - yPoint, - animationFactor, - oldSegmentRect, - oldSeriesVisible, - comparePrev, - isLargePrev, - isSingleSeries, - oldYValue) - : _animateNormalRectSeries( - canvas, - seriesRendererDetails, - fillPaint, - segmentRect, - yPoint, - animationFactor, - oldSegmentRect, - oldSeriesVisible, - comparePrev, - isLargePrev, - isSingleSeries, - oldYValue); -} - -/// To animate transposed bar and column series -void _animateTransposedRectSeries( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - Paint fillPaint, - RRect segmentRect, - num yPoint, - double animationFactor, - Rect? oldSegmentRect, - bool? oldSeriesVisible, - bool comparePrev, - bool isLargePrev, - 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; - const num defaultCrossesAtValue = 0; - final num crossesAt = getCrossesAtValue(seriesRendererDetails.renderer, - seriesRendererDetails.stateProperties) ?? - defaultCrossesAtValue; - seriesRendererDetails.needAnimateSeriesElements = - seriesRendererDetails.needAnimateSeriesElements == true || - segmentRect.outerRect != oldSegmentRect; - if (seriesRendererDetails.stateProperties.renderingDetails.isLegendToggled == - false || - seriesRendererDetails.reAnimate == true) { - width = segmentRect.width * - ((!comparePrev && - seriesRendererDetails.reAnimate == false && - seriesRendererDetails - .stateProperties.renderingDetails.initialRender! == - false && - oldSegmentRect == null && - seriesRendererDetails.series.key != null && - seriesRendererDetails.stateProperties.oldSeriesKeys - .contains(seriesRendererDetails.series.key) == - true) - ? 1 - : animationFactor); - if (seriesRendererDetails.yAxisDetails!.axis.isInversed == false) { - if (comparePrev) { - if (yPoint < crossesAt) { - left = isLargePrev - ? oldSegmentRect!.left - - (animationFactor * (oldSegmentRect.left - segmentRect.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 + - (animationFactor * (segmentRect.right - oldSegmentRect.right)) - : oldSegmentRect!.right - - (animationFactor * - (oldSegmentRect.right - segmentRect.right)); - width = right - segmentRect.left; - } - } else { - right = yPoint < crossesAt - ? segmentRect.right - : (segmentRect.right - segmentRect.width) + width; - } - } else { - if (comparePrev) { - if (yPoint < crossesAt) { - right = isLargePrev - ? oldSegmentRect!.right + - (animationFactor * (segmentRect.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 - - (animationFactor * (oldSegmentRect.left - segmentRect.left)) - : oldSegmentRect!.left + - (animationFactor * (segmentRect.left - oldSegmentRect.left)); - right = segmentRect.right; - width = right - left; - } - } else { - right = yPoint < crossesAt - ? (segmentRect.right - segmentRect.width) + width - : segmentRect.right; - } - } - rect = Rect.fromLTWH(right - width, top, width, height); - } else if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - true && - oldSegmentRect != null) { - rect = _performTransposedLegendToggleAnimation( - seriesRendererDetails, - segmentRect, - oldSegmentRect, - oldSeriesVisible, - isSingleSeries, - animationFactor); - } - - canvas.drawRRect( - RRect.fromRectAndCorners(rect ?? segmentRect.middleRect, - bottomLeft: segmentRect.blRadius, - bottomRight: segmentRect.brRadius, - topLeft: segmentRect.tlRadius, - topRight: segmentRect.trRadius), - fillPaint); -} - -/// To perform legend toggled animation on transposed chart -Rect _performTransposedLegendToggleAnimation( - SeriesRendererDetails seriesRendererDetails, - RRect segmentRect, - Rect? oldSegmentRect, - bool? oldSeriesVisible, - bool isSingleSeries, - double animationFactor) { - final CartesianSeries series = seriesRendererDetails.series; - num bottom; - double height = segmentRect.height; - double top = segmentRect.top; - double right = segmentRect.right; - final double width = segmentRect.width; - if (oldSeriesVisible != null && oldSeriesVisible) { - bottom = oldSegmentRect!.bottom > segmentRect.bottom - ? oldSegmentRect.bottom + - (animationFactor * (segmentRect.bottom - oldSegmentRect.bottom)) - : oldSegmentRect.bottom - - (animationFactor * (oldSegmentRect.bottom - segmentRect.bottom)); - top = oldSegmentRect.top > segmentRect.top - ? oldSegmentRect.top - - (animationFactor * (oldSegmentRect.top - segmentRect.top)) - : oldSegmentRect.top + - (animationFactor * (segmentRect.top - oldSegmentRect.top)); - height = bottom - top; - } else { - if (series == seriesRendererDetails.chart.series[0] && !isSingleSeries) { - if (seriesRendererDetails.xAxisDetails!.axis.isInversed) { - top = segmentRect.top; - height = segmentRect.height * animationFactor; - } else { - bottom = segmentRect.bottom; - top = bottom - (segmentRect.height * animationFactor); - height = bottom - top; - } - } else if (series == - seriesRendererDetails - .chart.series[seriesRendererDetails.chart.series.length - 1] && - !isSingleSeries) { - if (seriesRendererDetails.xAxisDetails!.axis.isInversed) { - bottom = segmentRect.bottom; - top = bottom - (segmentRect.height * animationFactor); - height = bottom - top; - } else { - top = segmentRect.top; - height = segmentRect.height * animationFactor; - } - } else { - height = segmentRect.height * animationFactor; - top = segmentRect.center.dy - height / 2; - } - } - right = segmentRect.right; - return Rect.fromLTWH(right - width, top, width, height); -} - -/// Rect animation for bar and column series -void _animateNormalRectSeries( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - Paint fillPaint, - RRect segmentRect, - num yPoint, - double animationFactor, - Rect? oldSegmentRect, - bool? oldSeriesVisible, - bool comparePrev, - bool isLargePrev, - 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; - const num defaultCrossesAtValue = 0; - final num crossesAt = getCrossesAtValue(seriesRendererDetails.renderer, - seriesRendererDetails.stateProperties) ?? - defaultCrossesAtValue; - seriesRendererDetails.needAnimateSeriesElements = - seriesRendererDetails.needAnimateSeriesElements == true || - segmentRect.outerRect != oldSegmentRect; - if (seriesRendererDetails.stateProperties.renderingDetails.isLegendToggled == - false || - seriesRendererDetails.reAnimate == true) { - height = segmentRect.height * - ((!comparePrev && - seriesRendererDetails.reAnimate == false && - seriesRendererDetails - .stateProperties.renderingDetails.initialRender! == - false && - oldSegmentRect == null && - seriesRendererDetails.series.key != null && - seriesRendererDetails.stateProperties.oldSeriesKeys - .contains(seriesRendererDetails.series.key) == - true) - ? 1 - : animationFactor); - if (seriesRendererDetails.yAxisDetails!.axis.isInversed == false) { - if (comparePrev) { - if (yPoint < crossesAt) { - bottom = isLargePrev - ? oldSegmentRect!.bottom - - (animationFactor * - (oldSegmentRect.bottom - segmentRect.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 + - (animationFactor * (segmentRect.top - oldSegmentRect.top)) - : oldSegmentRect!.top - - (animationFactor * (oldSegmentRect.top - segmentRect.top)); - height = segmentRect.bottom - top; - } - } else { - top = yPoint < crossesAt - ? segmentRect.top - : (segmentRect.top + segmentRect.height) - height; - } - } else { - if (comparePrev) { - if (yPoint < crossesAt) { - top = isLargePrev - ? oldSegmentRect!.top + - (animationFactor * (segmentRect.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 - - (animationFactor * - (oldSegmentRect.bottom - segmentRect.bottom)) - : oldSegmentRect!.bottom + - (animationFactor * - (segmentRect.bottom - oldSegmentRect.bottom)); - height = bottom - top; - } - } else { - top = yPoint < crossesAt - ? (segmentRect.top + segmentRect.height) - height - : segmentRect.top; - } - } - rect = Rect.fromLTWH(left, top, width, height); - } else if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - true && - oldSegmentRect != null && - oldSeriesVisible != null) { - rect = _performLegendToggleAnimation(seriesRendererDetails, segmentRect, - oldSegmentRect, oldSeriesVisible, isSingleSeries, animationFactor); - } - canvas.drawRRect( - RRect.fromRectAndCorners(rect ?? segmentRect.middleRect, - bottomLeft: segmentRect.blRadius, - bottomRight: segmentRect.brRadius, - topLeft: segmentRect.tlRadius, - topRight: segmentRect.trRadius), - fillPaint); -} - -/// Perform legend toggle animation -Rect _performLegendToggleAnimation( - SeriesRendererDetails seriesRendererDetails, - RRect segmentRect, - Rect oldSegmentRect, - bool oldSeriesVisible, - bool isSingleSeries, - double animationFactor) { - num right; - final CartesianSeries series = seriesRendererDetails.series; - final double height = segmentRect.height; - double width = segmentRect.width; - double left = segmentRect.left; - final double top = segmentRect.top; - if (oldSeriesVisible) { - right = oldSegmentRect.right > segmentRect.right - ? oldSegmentRect.right + - (animationFactor * (segmentRect.right - oldSegmentRect.right)) - : oldSegmentRect.right - - (animationFactor * (oldSegmentRect.right - segmentRect.right)); - left = oldSegmentRect.left > segmentRect.left - ? oldSegmentRect.left - - (animationFactor * (oldSegmentRect.left - segmentRect.left)) - : oldSegmentRect.left + - (animationFactor * (segmentRect.left - oldSegmentRect.left)); - width = right - left; - } else { - if (series == seriesRendererDetails.chart.series[0] && !isSingleSeries) { - if (seriesRendererDetails.xAxisDetails!.axis.isInversed) { - right = segmentRect.right; - left = right - (segmentRect.width * animationFactor); - width = right - left; - } else { - left = segmentRect.left; - width = segmentRect.width * animationFactor; - } - } else if (series == - seriesRendererDetails - .chart.series[seriesRendererDetails.chart.series.length - 1] && - !isSingleSeries) { - if (seriesRendererDetails.xAxisDetails!.axis.isInversed) { - left = segmentRect.left; - width = segmentRect.width * animationFactor; - } else { - right = segmentRect.right; - left = right - (segmentRect.width * animationFactor); - width = right - left; - } - } else { - width = segmentRect.width * animationFactor; - left = segmentRect.center.dx - width / 2; - } - } - return Rect.fromLTWH(left, top, width, height); -} - -/// To animation rect for stacked column series -void animateStackedRectSeries( - Canvas canvas, - RRect segmentRect, - Paint fillPaint, - SeriesRendererDetails seriesRendererDetails, - double animationFactor, - CartesianChartPoint currentPoint, - CartesianStateProperties stateProperties) { - int index, seriesIndex; - List seriesCollection; - Rect? prevRegion; - final StackedSeriesBase series = - seriesRendererDetails.series as StackedSeriesBase; - index = seriesRendererDetails.dataPoints.indexOf(currentPoint); - seriesCollection = findSeriesCollection(stateProperties); - seriesIndex = seriesCollection.indexOf(seriesRendererDetails.renderer); - bool isStackLine = false; - if (seriesIndex != 0) { - if (seriesRendererDetails.stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex - 1] - is StackedLineSeriesRenderer || - seriesRendererDetails.stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex - 1] - is StackedLine100SeriesRenderer) { - isStackLine = true; - } - if (!isStackLine) { - for (int j = 0; - j < stateProperties.chartSeries.clusterStackedItemInfo.length; - j++) { - final ClusterStackedItemInfo clusterStackedItemInfo = - stateProperties.chartSeries.clusterStackedItemInfo[j]; - if (clusterStackedItemInfo.stackName == series.groupName) { - if (clusterStackedItemInfo.stackedItemInfo.length >= 2) { - for (int k = 0; - k < clusterStackedItemInfo.stackedItemInfo.length; - k++) { - final StackedItemInfo stackedItemInfo = - clusterStackedItemInfo.stackedItemInfo[k]; - if (stackedItemInfo.seriesIndex == seriesIndex && - k != 0 && - index < - SeriesHelper.getSeriesRendererDetails( - clusterStackedItemInfo - .stackedItemInfo[k - 1].seriesRenderer) - .dataPoints - .length) { - prevRegion = _getPrevRegion( - currentPoint.yValue, clusterStackedItemInfo, index, k); - } - } - } - } - } - } - } - _drawAnimatedStackedRect(canvas, segmentRect, fillPaint, - seriesRendererDetails, animationFactor, currentPoint, index, prevRegion); -} - -/// To draw the animated rect for stacked series -void _drawAnimatedStackedRect( - Canvas canvas, - RRect segmentRect, - Paint fillPaint, - SeriesRendererDetails seriesRendererDetails, - double animationFactor, - CartesianChartPoint currentPoint, - int index, - Rect? prevRegion) { - double top = segmentRect.top, height = segmentRect.height; - double right = segmentRect.right, width = segmentRect.width; - final double height1 = segmentRect.height, top1 = segmentRect.top; - const num defaultCrossesAtValue = 0; - final num crossesAt = getCrossesAtValue(seriesRendererDetails.renderer, - seriesRendererDetails.stateProperties) ?? - defaultCrossesAtValue; - height = segmentRect.height * animationFactor; - width = segmentRect.width * animationFactor; - if ((seriesRendererDetails.seriesType.contains('stackedcolumn') == true) && - seriesRendererDetails.chart.isTransposed != true || - (seriesRendererDetails.seriesType.contains('stackedbar') == true) && - seriesRendererDetails.chart.isTransposed == true) { - seriesRendererDetails.yAxisDetails!.axis.isInversed != true - ? (seriesRendererDetails.dataPoints[index].y > crossesAt) == true - ? prevRegion == null - ? top = (segmentRect.top + segmentRect.height) - height - : top = prevRegion.top - height - : prevRegion == null - ? top = segmentRect.top - : top = prevRegion.bottom - : (seriesRendererDetails.dataPoints[index].y > crossesAt) == true - ? prevRegion == null - ? top = segmentRect.top - : top = prevRegion.bottom - : prevRegion == null - ? top = (segmentRect.top + segmentRect.height) - height - : top = prevRegion.top - height; - - segmentRect = RRect.fromRectAndCorners( - Rect.fromLTWH(segmentRect.left, top, segmentRect.width, height), - bottomLeft: segmentRect.blRadius, - bottomRight: segmentRect.brRadius, - topLeft: segmentRect.tlRadius, - topRight: segmentRect.trRadius); - currentPoint.region = - Rect.fromLTWH(segmentRect.left, top, segmentRect.width, height); - canvas.drawRRect(segmentRect, fillPaint); - } else if ((seriesRendererDetails.seriesType.contains('stackedcolumn') == - true) && - seriesRendererDetails.chart.isTransposed == true || - (seriesRendererDetails.seriesType.contains('stackedbar') == true) && - seriesRendererDetails.chart.isTransposed != true) { - seriesRendererDetails.yAxisDetails!.axis.isInversed != true - ? (seriesRendererDetails.dataPoints[index].y > crossesAt) == true - ? prevRegion == null - ? right = (segmentRect.right - segmentRect.width) + width - : right = prevRegion.right + width - : prevRegion == null - ? right = segmentRect.right - : right = prevRegion.left - : (seriesRendererDetails.dataPoints[index].y > crossesAt) == true - ? prevRegion == null - ? right = segmentRect.right - : right = prevRegion.left - : prevRegion == null - ? right = (segmentRect.right - segmentRect.width) + width - : right = prevRegion.right + width; - - segmentRect = RRect.fromRectAndCorners( - Rect.fromLTWH(right - width, top1, width, height1), - bottomLeft: segmentRect.blRadius, - bottomRight: segmentRect.brRadius, - topLeft: segmentRect.tlRadius, - topRight: segmentRect.trRadius); - currentPoint.region = - Rect.fromLTWH(segmentRect.left, top, right - segmentRect.left, height); - canvas.drawRRect(segmentRect, fillPaint); - } -} - -/// This recursive function returns the previous region of the cluster stacked info for animation purposes -Rect? _getPrevRegion(num yValue, ClusterStackedItemInfo clusterStackedItemInfo, - int index, int k) { - Rect? prevRegion; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - clusterStackedItemInfo.stackedItemInfo[k - 1].seriesRenderer); - if (((yValue > 0) == true && - (seriesRendererDetails.dataPoints[index].yValue > 0) == true) || - ((yValue < 0 == true) && - (seriesRendererDetails.dataPoints[index].yValue < 0 == true))) { - prevRegion = seriesRendererDetails.dataPoints[index].region; - } else { - if (k > 1) { - prevRegion = _getPrevRegion(yValue, clusterStackedItemInfo, index, k - 1); - } - } - return prevRegion; -} - -/// To check the series count -bool _checkSingleSeries(SeriesRendererDetails seriesRendererDetails) { - int count = 0; - SeriesRendererDetails seriesDetails; - for (final CartesianSeriesRenderer seriesRenderer in seriesRendererDetails - .stateProperties.chartSeries.visibleSeriesRenderers) { - seriesDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesDetails.visible! == true && - (seriesDetails.seriesType == 'column' || - seriesDetails.seriesType == 'bar')) { - count++; - } - } - return count == 1; -} - -/// to animate dynamic update in line, spline, stepLine series -void animateLineTypeSeries( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - Paint strokePaint, - double animationFactor, - 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, seriesRendererDetails); - y2 = - getAnimateValue(animationFactor, y2, oldY2, newY2, seriesRendererDetails); - y3 = y3 != null - ? getAnimateValue( - animationFactor, y3, oldY3, newY3, seriesRendererDetails) - : y3; - y4 = y4 != null - ? getAnimateValue( - animationFactor, y4, oldY4, newY4, seriesRendererDetails) - : y4; - x1 = - getAnimateValue(animationFactor, x1, oldX1, newX1, seriesRendererDetails); - x2 = - getAnimateValue(animationFactor, x2, oldX2, newX2, seriesRendererDetails); - x3 = x3 != null - ? getAnimateValue( - animationFactor, x3, oldX3, newX3, seriesRendererDetails) - : x3; - x4 = x4 != null - ? getAnimateValue( - animationFactor, x4, oldX4, newX4, seriesRendererDetails) - : x4; - - final Path path = Path(); - path.moveTo(x1, y1); - if (seriesRendererDetails.seriesType == 'stepline') { - path.lineTo(x3!, y3!); - } - seriesRendererDetails.seriesType == 'spline' - ? path.cubicTo(x3!, y3!, x4!, y4!, x2, y2) - : path.lineTo(x2, y2); - drawDashedLine( - canvas, seriesRendererDetails.series.dashArray, strokePaint, path); -} - -/// Method to animate to point -void animateToPoint( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - Paint strokePaint, - double animationFactor, - double x1, - double y1, - double x2, - double y2, - double? prevX, - double? prevY) { - final Path path = Path(); - prevX ??= x1; - prevY ??= y1; - 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); - drawDashedLine( - canvas, seriesRendererDetails.series.dashArray, strokePaint, path); -} - -/// To get value of animation based on animation factor -double getAnimateValue( - double animationFactor, double? value, double? oldvalue, double? newValue, - [SeriesRendererDetails? seriesRendererDetails]) { - if (seriesRendererDetails != null) { - seriesRendererDetails.needAnimateSeriesElements = - seriesRendererDetails.needAnimateSeriesElements == true || - oldvalue != newValue; - } - return ((oldvalue != null && newValue != null && !oldvalue.isNaN) - ? ((oldvalue > newValue) - ? oldvalue - ((oldvalue - newValue) * animationFactor) - : oldvalue + ((newValue - oldvalue) * animationFactor)) - : value)!; -} - -/// To animate scatter series -void animateScatterSeries( - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - CartesianChartPoint? _oldPoint, - double animationFactor, - Canvas canvas, - Paint fillPaint, - Paint strokePaint, - int index, - ScatterSegment segment) { - final CartesianSeries series = seriesRendererDetails.series; - final MarkerDetails? pointMarkerDetails = - CartesianPointHelper.getMarkerDetails(point); - double width = pointMarkerDetails != null - ? pointMarkerDetails.size!.width - : series.markerSettings.width, - height = pointMarkerDetails != null - ? pointMarkerDetails.size!.height - : series.markerSettings.height; - DataMarkerType markerType = pointMarkerDetails != null - ? pointMarkerDetails.markerType! - : series.markerSettings.shape; - if (pointMarkerDetails != null) { - fillPaint.color = pointMarkerDetails.color!; - } - - double x = point.markerPoint!.x; - double y = point.markerPoint!.y; - if (seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == - true && - _oldPoint != null && - seriesRendererDetails.reAnimate == false && - _oldPoint.markerPoint != null) { - x = getAnimateValue( - animationFactor, x, _oldPoint.markerPoint!.x, x, seriesRendererDetails); - y = getAnimateValue( - animationFactor, y, _oldPoint.markerPoint!.y, y, seriesRendererDetails); - segment.animationFactor = 1; - } - final bool isMarkerEventTriggered = - CartesianPointHelper.getIsMarkerEventTriggered(point); - if (seriesRendererDetails.chart.onMarkerRender != null && - ((seriesRendererDetails.animationController.value == 0.0 && - !isMarkerEventTriggered && - seriesRendererDetails.animationController.status == - AnimationStatus.forward) || - (seriesRendererDetails.animationController.duration!.inMilliseconds == - 0 && - !isMarkerEventTriggered))) { - final MarkerRenderArgs markerRenderArgs = triggerMarkerRenderEvent( - seriesRendererDetails, - Size(width, height), - markerType, - index, - seriesRendererDetails.seriesAnimation, - segment)!; - CartesianPointHelper.setIsMarkerEventTriggered(point, true); - width = markerRenderArgs.markerWidth; - height = markerRenderArgs.markerHeight; - markerType = markerRenderArgs.shape; - - final Size size = - Size(markerRenderArgs.markerHeight, markerRenderArgs.markerWidth); - CartesianPointHelper.setMarkerDetails( - point, - MarkerDetails( - markerType: markerType, - borderColor: markerRenderArgs.borderColor, - color: markerRenderArgs.color, - borderWidth: markerRenderArgs.borderWidth, - size: size)); - } - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - - segmentProperties.seriesRenderer.drawDataMarker(index, canvas, fillPaint, - strokePaint, x, y, segmentProperties.seriesRenderer); -} - -/// To animate bubble series -void animateBubbleSeries( - Canvas canvas, - double newX, - double newY, - double? oldX, - double? oldY, - double? oldSize, - double animationFactor, - double radius, - Paint strokePaint, - Paint fillPaint, - CartesianSeriesRenderer seriesRenderer) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - double x = newX; - double y = newY; - double size = radius; - x = getAnimateValue(animationFactor, x, oldX, newX, seriesRendererDetails); - y = getAnimateValue(animationFactor, y, oldY, newY, seriesRendererDetails); - size = getAnimateValue( - animationFactor, size, oldSize, radius, seriesRendererDetails); - canvas.drawCircle(Offset(x, y), size, fillPaint); - canvas.drawCircle(Offset(x, y), size, strokePaint); -} - -/// To animates range column series -void animateRangeColumn( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - Paint fillPaint, - RRect segmentRect, - Rect? oldSegmentRect, - double animationFactor) { - double height = segmentRect.height; - double width = segmentRect.width; - double left = segmentRect.left; - double top = segmentRect.top; - if (oldSegmentRect == null || seriesRendererDetails.reAnimate == true) { - if (seriesRendererDetails.chart.isTransposed == false) { - height = segmentRect.height * animationFactor; - top = segmentRect.center.dy - height / 2; - } else { - width = segmentRect.width * animationFactor; - left = segmentRect.center.dx - width / 2; - } - } else { - if (seriesRendererDetails.chart.isTransposed == false) { - height = getAnimateValue(animationFactor, height, oldSegmentRect.height, - segmentRect.height, seriesRendererDetails); - top = getAnimateValue(animationFactor, top, oldSegmentRect.top, - segmentRect.top, seriesRendererDetails); - } else { - width = getAnimateValue(animationFactor, width, oldSegmentRect.width, - segmentRect.width, seriesRendererDetails); - left = getAnimateValue(animationFactor, left, oldSegmentRect.left, - segmentRect.left, seriesRendererDetails); - } - } - canvas.drawRRect( - RRect.fromRectAndCorners(Rect.fromLTWH(left, top, width, height), - bottomLeft: segmentRect.blRadius, - bottomRight: segmentRect.brRadius, - topLeft: segmentRect.tlRadius, - topRight: segmentRect.trRadius), - fillPaint); -} - -/// To animate linear type animation -void performLinearAnimation(CartesianStateProperties stateProperties, - ChartAxis axis, Canvas canvas, double animationFactor) { - stateProperties.chart.isTransposed - ? canvas.clipRect(Rect.fromLTRB( - 0, - axis.isInversed - ? 0 - : (1 - animationFactor) * - stateProperties.chartAxis.axisClipRect.bottom, - stateProperties.chartAxis.axisClipRect.left + - stateProperties.chartAxis.axisClipRect.width, - axis.isInversed - ? animationFactor * - (stateProperties.chartAxis.axisClipRect.top + - stateProperties.chartAxis.axisClipRect.height) - : stateProperties.chartAxis.axisClipRect.top + - stateProperties.chartAxis.axisClipRect.height)) - : canvas.clipRect(Rect.fromLTRB( - axis.isInversed - ? (1 - animationFactor) * - (stateProperties.chartAxis.axisClipRect.right) - : 0, - 0, - axis.isInversed - ? stateProperties.chartAxis.axisClipRect.left + - stateProperties.chartAxis.axisClipRect.width - : animationFactor * - (stateProperties.chartAxis.axisClipRect.left + - stateProperties.chartAxis.axisClipRect.width), - stateProperties.chartAxis.axisClipRect.top + - stateProperties.chartAxis.axisClipRect.height)); -} - -/// To animate Hilo series -void animateHiloSeres( - bool transposed, - ChartLocation newLow, - ChartLocation newHigh, - ChartLocation? oldLow, - ChartLocation? oldHigh, - double animationFactor, - Paint paint, - Canvas canvas, - CartesianSeriesRenderer seriesRenderer) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (transposed) { - double lowX = newLow.x; - double highX = newHigh.x; - double y = newLow.y; - y = getAnimateValue( - animationFactor, y, oldLow?.y, newLow.y, seriesRendererDetails); - lowX = getAnimateValue( - animationFactor, lowX, oldLow?.x, newLow.x, seriesRendererDetails); - highX = getAnimateValue( - animationFactor, highX, oldHigh?.x, newHigh.x, seriesRendererDetails); - canvas.drawLine(Offset(lowX, y), Offset(highX, y), paint); - } else { - double low = newLow.y; - double high = newHigh.y; - double x = newLow.x; - x = getAnimateValue( - animationFactor, x, oldLow?.x, newLow.x, seriesRendererDetails); - low = getAnimateValue( - animationFactor, low, oldLow?.y, newLow.y, seriesRendererDetails); - high = getAnimateValue( - animationFactor, high, oldHigh?.y, newHigh.y, seriesRendererDetails); - canvas.drawLine(Offset(x, low), Offset(x, high), paint); - } -} - -/// To animate the Hilo open-close series -void animateHiloOpenCloseSeries( - bool transposed, - 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, - SeriesRendererDetails seriesRendererDetails) { - 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, seriesRendererDetails); - high = getAnimateValue( - animationFactor, high, oldHigh, newHigh, seriesRendererDetails); - openX = getAnimateValue( - animationFactor, openX, oldOpenX, newOpenX, seriesRendererDetails); - openY = getAnimateValue( - animationFactor, openY, oldOpenY, newOpenY, seriesRendererDetails); - closeX = getAnimateValue( - animationFactor, closeX, oldCloseX, newCloseX, seriesRendererDetails); - closeY = getAnimateValue( - animationFactor, closeY, oldCloseY, newCloseY, seriesRendererDetails); - centerHigh = getAnimateValue(animationFactor, centerHigh, oldCenterHigh, - newCenterHigh, seriesRendererDetails); - centerLow = getAnimateValue(animationFactor, centerLow, oldCenterLow, - newCenterLow, seriesRendererDetails); - if (transposed) { - canvas.drawLine(Offset(low, centerLow), Offset(high, centerHigh), paint); - canvas.drawLine(Offset(openX, openY), Offset(openX, centerHigh), paint); - canvas.drawLine(Offset(closeX, centerLow), Offset(closeX, closeY), paint); - } else { - canvas.drawLine(Offset(centerHigh, low), Offset(centerLow, high), paint); - canvas.drawLine(Offset(openX, openY), Offset(centerHigh, openY), paint); - canvas.drawLine(Offset(centerLow, closeY), Offset(closeX, closeY), paint); - } -} - -/// To animate the box and whisker series -void animateBoxSeries( - bool showMean, - Offset meanPosition, - Offset transposedMeanPosition, - Size size, - double max, - bool transposed, - 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, - SeriesRendererDetails seriesRendererDetails) { - 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, seriesRendererDetails); - maxY = getAnimateValue( - animationFactor, maxY, oldMax, newMax, seriesRendererDetails); - lowerQuartileX = getAnimateValue(animationFactor, lowerQuartileX, - oldLowerQuartileX, newLowerQuartileX, seriesRendererDetails); - lowerQuartileY = getAnimateValue(animationFactor, lowerQuartileY, - oldLowerQuartileY, newLowerQuartileY, seriesRendererDetails); - upperQuartileX = getAnimateValue(animationFactor, upperQuartileX, - oldUpperQuartileX, newUpperQuartileX, seriesRendererDetails); - upperQuartileY = getAnimateValue(animationFactor, upperQuartileY, - oldUpperQuartileY, newUpperQuartileY, seriesRendererDetails); - centerMax = getAnimateValue(animationFactor, centerMax, oldCenterMax, - newCenterMax, seriesRendererDetails); - centerMin = getAnimateValue(animationFactor, centerMin, oldCenterMin, - newCenterMin, seriesRendererDetails); - final Path path = Path(); - if (!transposed && lowerQuartile > upperQuartile) { - final double temp = upperQuartileY; - upperQuartileY = lowerQuartileY; - lowerQuartileY = temp; - } - if (transposed) { - path.moveTo(centerMax, upperQuartileY); - path.lineTo(centerMax, lowerQuartileY); - path.moveTo(centerMax, maxY); - (centerMax < upperQuartileX) - ? path.lineTo(upperQuartileX, maxY) - : path.lineTo(upperQuartileX, maxY); - path.moveTo(medianX, upperQuartileY); - path.lineTo(medianX, lowerQuartileY); - path.moveTo(centerMin, maxY); - (centerMin > lowerQuartileX) - ? path.lineTo(lowerQuartileX, maxY) - : path.lineTo(lowerQuartileX, maxY); - path.moveTo(centerMin, upperQuartileY); - path.lineTo(centerMin, lowerQuartileY); - if (showMean) { - _drawMeanValuePath( - seriesRendererDetails, - animationFactor, - transposedMeanPosition.dy, - transposedMeanPosition.dx, - size, - strokePaint, - canvas); - } - if (lowerQuartileX == upperQuartileX) { - canvas.drawLine(Offset(lowerQuartileX, lowerQuartileY), - Offset(upperQuartileX, upperQuartileY), fillPaint); - } else { - path.moveTo(upperQuartileX, upperQuartileY); - path.lineTo(upperQuartileX, lowerQuartileY); - path.lineTo(lowerQuartileX, lowerQuartileY); - path.lineTo(lowerQuartileX, upperQuartileY); - path.lineTo(upperQuartileX, upperQuartileY); - path.close(); - } - } else { - if (lowerQuartile > upperQuartile) { - final double temp = upperQuartileY; - upperQuartileY = lowerQuartileY; - lowerQuartileY = temp; - } - canvas.drawLine(Offset(lowerQuartileX, maxY), Offset(upperQuartileX, maxY), - strokePaint); - canvas.drawLine(Offset(centerMax, upperQuartileY), Offset(centerMax, maxY), - strokePaint); - canvas.drawLine(Offset(lowerQuartileX, medianY), - Offset(upperQuartileX, medianY), strokePaint); - canvas.drawLine(Offset(centerMin, lowerQuartileY), Offset(centerMin, minY), - strokePaint); - canvas.drawLine(Offset(lowerQuartileX, minY), Offset(upperQuartileX, minY), - strokePaint); - if (showMean) { - _drawMeanValuePath(seriesRendererDetails, animationFactor, - meanPosition.dx, meanPosition.dy, size, strokePaint, canvas); - } - if (lowerQuartileY == upperQuartileY) { - canvas.drawLine(Offset(lowerQuartileX, lowerQuartileY), - Offset(upperQuartileX, upperQuartileY), fillPaint); - } else { - path.moveTo(lowerQuartileX, lowerQuartileY); - path.lineTo(upperQuartileX, lowerQuartileY); - path.lineTo(upperQuartileX, upperQuartileY); - path.lineTo(lowerQuartileX, upperQuartileY); - path.lineTo(lowerQuartileX, lowerQuartileY); - path.close(); - } - } - if (seriesRendererDetails.series.dashArray[0] != 0 && - seriesRendererDetails.series.dashArray[1] != 0 && - seriesRendererDetails.series.animationDuration <= 0) { - canvas.drawPath(path, fillPaint); - drawDashedLine( - canvas, seriesRendererDetails.series.dashArray, strokePaint, path); - } else { - canvas.drawPath(path, fillPaint); - canvas.drawPath(path, strokePaint); - } - if (fillPaint.style == PaintingStyle.fill) { - if (transposed) { - canvas.drawLine(Offset(centerMax, upperQuartileY), - Offset(centerMax, lowerQuartileY), strokePaint); - canvas.drawLine( - Offset(centerMax, maxY), Offset(upperQuartileX, maxY), strokePaint); - canvas.drawLine(Offset(medianX, upperQuartileY), - Offset(medianX, lowerQuartileY), strokePaint); - canvas.drawLine( - Offset(centerMin, maxY), Offset(lowerQuartileX, maxY), strokePaint); - canvas.drawLine(Offset(centerMin, upperQuartileY), - Offset(centerMin, lowerQuartileY), strokePaint); - if (showMean) { - _drawMeanValuePath( - seriesRendererDetails, - animationFactor, - transposedMeanPosition.dy, - transposedMeanPosition.dx, - size, - strokePaint, - canvas); - } - } else { - canvas.drawLine(Offset(lowerQuartileX, maxY), - Offset(upperQuartileX, maxY), strokePaint); - canvas.drawLine(Offset(centerMax, upperQuartileY), - Offset(centerMax, maxY), strokePaint); - canvas.drawLine(Offset(lowerQuartileX, medianY), - Offset(upperQuartileX, medianY), strokePaint); - canvas.drawLine(Offset(centerMin, lowerQuartileY), - Offset(centerMin, minY), strokePaint); - canvas.drawLine(Offset(lowerQuartileX, minY), - Offset(upperQuartileX, minY), strokePaint); - if (showMean) { - _drawMeanValuePath(seriesRendererDetails, animationFactor, - meanPosition.dx, meanPosition.dy, size, strokePaint, canvas); - } - } - } -} - -/// To draw the path of mean value in box plot series. -void _drawMeanValuePath( - SeriesRendererDetails seriesRendererDetails, - double animationFactor, - num x, - num y, - Size size, - Paint strokePaint, - Canvas canvas) { - if (seriesRendererDetails.series.animationDuration <= 0 || - animationFactor >= - seriesRendererDetails.stateProperties.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), - Offset(x - size.width / 2, y - size.height / 2), strokePaint); - } -} - -/// To animate the candle series -void animateCandleSeries( - bool showSameValue, - double high, - bool transposed, - 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, - SeriesRendererDetails seriesRendererDetails) { - 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, seriesRendererDetails); - highY = getAnimateValue( - animationFactor, highY, oldHigh, newHigh, seriesRendererDetails); - openX = getAnimateValue( - animationFactor, openX, oldOpenX, newOpenX, seriesRendererDetails); - openY = getAnimateValue( - animationFactor, openY, oldOpenY, newOpenY, seriesRendererDetails); - closeX = getAnimateValue( - animationFactor, closeX, oldCloseX, newCloseX, seriesRendererDetails); - closeY = getAnimateValue( - animationFactor, closeY, oldCloseY, newCloseY, seriesRendererDetails); - centerHigh = getAnimateValue(animationFactor, centerHigh, oldCenterHigh, - newCenterHigh, seriesRendererDetails); - centerLow = getAnimateValue(animationFactor, centerLow, oldCenterLow, - newCenterLow, seriesRendererDetails); - final Path path = Path(); - if (!transposed && open > close) { - final double temp = closeY; - closeY = openY; - openY = temp; - } - - if (transposed) { - if (showSameValue) { - canvas.drawLine( - Offset(centerHigh, highY), Offset(centerLow, highY), paint); - } else { - path.moveTo(centerHigh, highY); - (centerHigh < closeX) - ? path.lineTo(closeX, highY) - : path.lineTo(closeX, highY); - path.moveTo(centerLow, highY); - (centerLow > openX) - ? path.lineTo(openX, highY) - : path.lineTo(openX, highY); - } - if (openX == closeX) { - canvas.drawLine(Offset(openX, openY), Offset(closeX, closeY), paint); - } else { - path.moveTo(closeX, closeY); - path.lineTo(closeX, openY); - path.lineTo(openX, openY); - path.lineTo(openX, closeY); - path.lineTo(closeX, closeY); - path.close(); - } - } else { - if (open > close) { - final double temp = closeY; - closeY = openY; - openY = temp; - } - if (showSameValue) { - canvas.drawLine( - Offset(centerHigh, high), Offset(centerHigh, lowY), paint); - } else { - canvas.drawLine( - Offset(centerHigh, closeY), Offset(centerHigh, highY), paint); - canvas.drawLine(Offset(centerLow, openY), Offset(centerLow, lowY), paint); - } - if (openY == closeY) { - canvas.drawLine(Offset(openX, openY), Offset(closeX, closeY), paint); - } else { - path.moveTo(openX, openY); - path.lineTo(closeX, openY); - path.lineTo(closeX, closeY); - path.lineTo(openX, closeY); - path.lineTo(openX, openY); - path.close(); - } - } - - (seriesRendererDetails.series.dashArray[0] != 0 && - seriesRendererDetails.series.dashArray[1] != 0 && - paint.style != PaintingStyle.fill && - seriesRendererDetails.series.animationDuration <= 0) - ? drawDashedLine( - canvas, seriesRendererDetails.series.dashArray, paint, path) - : canvas.drawPath(path, paint); - if (paint.style == PaintingStyle.fill) { - if (transposed) { - 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); - } - } else { - if (showSameValue) { - canvas.drawLine( - Offset(centerHigh, high), Offset(centerHigh, lowY), paint); - } else { - canvas.drawLine( - Offset(centerHigh, closeY), Offset(centerHigh, highY), paint); - canvas.drawLine( - Offset(centerLow, openY), Offset(centerLow, lowY), paint); - } - } - } -} - -/// To get nearest chart points from the touch point -List>? getNearestChartPoints( - double pointX, - double pointY, - ChartAxisRenderer actualXAxisRenderer, - ChartAxisRenderer actualYAxisRenderer, - SeriesRendererDetails seriesRendererDetails, - [List>? firstNearestDataPoints]) { - final ChartAxisRendererDetails actualXAxisDetails = - AxisHelper.getAxisRendererDetails(actualXAxisRenderer); - final ChartAxisRendererDetails actualYAxisDetails = - AxisHelper.getAxisRendererDetails(actualYAxisRenderer); - final List> dataPointList = - >[]; - List> dataList = - >[]; - final List xValues = []; - final List yValues = []; - - firstNearestDataPoints != null - ? dataList = firstNearestDataPoints - : dataList = seriesRendererDetails.visibleDataPoints!; - - for (int i = 0; i < dataList.length; i++) { - xValues.add(dataList[i].xValue); - (seriesRendererDetails.renderer is BoxAndWhiskerSeriesRenderer) - ? yValues.add((dataList[i].maximum! + dataList[i].minimum!) / 2) - : yValues.add( - dataList[i].yValue ?? (dataList[i].high + dataList[i].low) / 2); - } - num nearPointX = dataList[0].xValue; - num nearPointY = actualYAxisDetails.visibleRange!.minimum; - - final Rect rect = calculatePlotOffset( - seriesRendererDetails.stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - - final num touchXValue = pointToXValue( - seriesRendererDetails.stateProperties.requireInvertedAxis, - actualXAxisRenderer, - actualXAxisDetails.axis.isVisible - ? actualXAxisDetails.bounds - : seriesRendererDetails.stateProperties.chartAxis.axisClipRect, - pointX - rect.left, - pointY - rect.top); - final num touchYValue = pointToYValue( - seriesRendererDetails.stateProperties.requireInvertedAxis, - actualYAxisRenderer, - actualYAxisDetails.axis.isVisible - ? actualYAxisDetails.bounds - : seriesRendererDetails.stateProperties.chartAxis.axisClipRect, - pointX - rect.left, - pointY - rect.top); - double delta = 0; - for (int i = 0; i < dataList.length; i++) { - final double currX = xValues[i].toDouble(); - final double currY = yValues[i].toDouble(); - if (delta == touchXValue - currX) { - final CartesianChartPoint dataPoint = dataList[i]; - if (dataPoint.isDrop != true && dataPoint.isGap != true) { - if ((touchYValue - currY).abs() > (touchYValue - nearPointY).abs()) { - dataPointList.clear(); - } - dataPointList.add(dataPoint); - } - } else if ((touchXValue - currX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = currX; - nearPointY = currY; - delta = touchXValue - currX; - final CartesianChartPoint dataPoint = dataList[i]; - dataPointList.clear(); - if (dataPoint.isDrop != true && dataPoint.isGap != true) { - dataPointList.add(dataPoint); - } - } - } - return dataPointList; -} - -/// Return the arguments for zoom event -ZoomPanArgs bindZoomEvent(SfCartesianChart chart, - ChartAxisRendererDetails axisDetails, ChartZoomingCallback zoomEventType) { - final ZoomPanArgs zoomPanArgs = ZoomPanArgs(axisDetails.axis, - axisDetails.previousZoomPosition, axisDetails.previousZoomFactor); - zoomPanArgs.currentZoomFactor = axisDetails.zoomFactor; - zoomPanArgs.currentZoomPosition = axisDetails.zoomPosition; - zoomEventType == chart.onZoomStart - ? chart.onZoomStart!(zoomPanArgs) - : zoomEventType == chart.onZoomEnd - ? chart.onZoomEnd!(zoomPanArgs) - : zoomEventType == chart.onZooming - ? chart.onZooming!(zoomPanArgs) - : chart.onZoomReset!(zoomPanArgs); - - return zoomPanArgs; -} - -/// Method to returning path for dashed border in rect type series -Path findingRectSeriesDashedBorder( - CartesianChartPoint currentPoint, double borderWidth) { - 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; - width = borderWidth / 2; - path = Path(); - path.moveTo(topLeft.dx + width, topLeft.dy + width); - path.lineTo(topRight.dx - width, topRight.dy + width); - path.lineTo(bottomRight.dx - width, bottomRight.dy - width); - path.lineTo(bottomLeft.dx + width, bottomLeft.dy - width); - path.lineTo(topLeft.dx + width, topLeft.dy + width); - path.close(); - return path; -} - -/// below method is for getting image from the image provider -Future _getImageInfo(ImageProvider imageProvider) async { - final Completer completer = Completer(); - imageProvider - .resolve(ImageConfiguration.empty) - .addListener(ImageStreamListener((ImageInfo info, bool _) { - completer.complete(info); - // return completer.future; - })); - final ImageInfo imageInfo = await completer.future; - return imageInfo.image; -} - -/// Method to calculate the image value -//ignore: avoid_void_async -void calculateImage(CartesianStateProperties stateProperties, - [SeriesRendererDetails? seriesRendererDetails, - TrackballBehavior? trackballBehavior]) async { - final SfCartesianChart chart = stateProperties.chart; - // ignore: unnecessary_null_comparison - if (chart != null && seriesRendererDetails == null) { - if (chart.plotAreaBackgroundImage != null) { - stateProperties.backgroundImage = - await _getImageInfo(chart.plotAreaBackgroundImage!); - stateProperties.renderOutsideAxis.state.axisRepaintNotifier.value++; - } - } else if (trackballBehavior != null && - trackballBehavior.markerSettings!.image != null) { - stateProperties.trackballMarkerSettingsRenderer.image = - await _getImageInfo(trackballBehavior.markerSettings!.image!); - stateProperties.repaintNotifiers['trackball']!.value++; - } else { - final CartesianSeries series = - seriesRendererDetails!.series; - if (series.markerSettings.image != null) { - seriesRendererDetails.markerSettingsRenderer!.image = - await _getImageInfo(series.markerSettings.image!); - if (!seriesRendererDetails.markerSettingsRenderer!.isImageDrawn) { - seriesRendererDetails.repaintNotifier.value++; - seriesRendererDetails.markerSettingsRenderer!.isImageDrawn = true; - } - } - } -} - -/// Set the shader for series types. -void setShader(SegmentProperties segmentProperties, Paint paint) => - paint.shader = segmentProperties.stateProperties.shader ?? paint.shader; - -/// Used to calculate the error values of standard error type error bar. -ChartErrorValues _getStandardErrorValues( - SeriesRendererDetails seriesRendererDetails, - ErrorBarSeries series, - ChartErrorValues chartErrorValues, - ErrorBarMean mean, - int dataPointsLength) { - final num errorX = (chartErrorValues.errorX! * mean.horizontalSquareRoot!) / - math.sqrt(dataPointsLength); - final num errorY = (chartErrorValues.errorY! * mean.verticalSquareRoot!) / - math.sqrt(dataPointsLength); - return ChartErrorValues(errorX: errorX, errorY: errorY); -} - -/// Used to calculate the error values of standard deviation type error bar. -ChartErrorValues _getStandardDeviationValues( - SeriesRendererDetails seriesRendererDetails, - ErrorBarSeries series, - ChartErrorValues chartErrorValues, - ErrorBarMean mean, - int dataPointsLength) { - num errorY = chartErrorValues.errorY!, errorX = chartErrorValues.errorX!; - errorY = (series.mode == RenderingMode.horizontal) - ? errorY - : errorY * (mean.verticalSquareRoot! + mean.verticalMean!); - errorX = (series.mode == RenderingMode.vertical) - ? errorX - : errorX * (mean.horizontalSquareRoot! + mean.horizontalMean!); - return ChartErrorValues(errorX: errorX, errorY: errorY); -} - -/// Returns the error values of error bar. -ChartErrorValues _getErrorValues(SeriesRendererDetails seriesRendererDetails, - ErrorBarSeries series, num actualXValue, num actualYValue, - {ErrorBarMean? mean, int? dataPointsLength}) { - final RenderingMode? mode = series.mode; - num errorX = 0.0, - errorY = 0.0, - customNegativeErrorX = 0.0, - customNegativeErrorY = 0.0; - ChartErrorValues? chartErrorValues; - if (series.type != ErrorBarType.custom) { - errorY = (mode == RenderingMode.horizontal) - ? errorY - : series.verticalErrorValue!; - errorX = (mode == RenderingMode.vertical) - ? errorX - : series.horizontalErrorValue!; - chartErrorValues = ChartErrorValues(errorX: errorX, errorY: errorY); - if (series.type == ErrorBarType.standardError) { - chartErrorValues = _getStandardErrorValues(seriesRendererDetails, series, - chartErrorValues, mean!, dataPointsLength!); - } - // Updating the error values for standard deviation type - if (series.type == ErrorBarType.standardDeviation) { - chartErrorValues = _getStandardDeviationValues(seriesRendererDetails, - series, chartErrorValues, mean!, dataPointsLength!); - } - // Updating the error values for percentage type - if (series.type == ErrorBarType.percentage) { - errorY = (mode == RenderingMode.horizontal) - ? errorY - : (errorY / 100) * actualYValue; - errorX = (mode == RenderingMode.vertical) - ? errorX - : (errorX / 100) * actualXValue; - chartErrorValues = ChartErrorValues(errorX: errorX, errorY: errorY); - } - } - // Updating the error values for custom type - else if (series.type == ErrorBarType.custom) { - errorY = (mode == RenderingMode.horizontal) - ? errorY - : series.verticalPositiveErrorValue!; - customNegativeErrorY = (mode == RenderingMode.horizontal) - ? customNegativeErrorY - : series.verticalNegativeErrorValue!; - errorX = (mode == RenderingMode.vertical) - ? errorX - : series.horizontalPositiveErrorValue!; - customNegativeErrorX = (mode == RenderingMode.vertical) - ? customNegativeErrorX - : series.horizontalNegativeErrorValue!; - chartErrorValues = ChartErrorValues( - errorX: errorX, - errorY: errorY, - customNegativeX: customNegativeErrorX, - customNegativeY: customNegativeErrorY); - } - return chartErrorValues!; -} - -/// Returns the calculated error bar values. -ErrorBarValues _getCalculatedErrorValues( - SeriesRendererDetails seriesRendererDetails, - ErrorBarSeries series, - ChartErrorValues chartErrorValues, - num actualXValue, - num actualYValue) { - final bool isDirectionPlus = series.direction == Direction.plus; - final bool isBothDirection = series.direction == Direction.both; - final bool isDirectionMinus = series.direction == Direction.minus; - final bool isCustomFixedType = - series.type == ErrorBarType.fixed || series.type == ErrorBarType.custom; - double? verticalPositiveErrorValue, - horizontalPositiveErrorValue, - verticalNegativeErrorValue, - horizontalNegativeErrorValue; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - if (series.mode == RenderingMode.horizontal || - series.mode == RenderingMode.both) { - if (isDirectionPlus || isBothDirection) { - if (xAxisDetails is DateTimeAxisDetails && isCustomFixedType) { - horizontalPositiveErrorValue = _updateDateTimeHorizontalErrorValue( - seriesRendererDetails, actualXValue, chartErrorValues.errorX!); - } else { - horizontalPositiveErrorValue = - actualXValue + chartErrorValues.errorX! as double; - } - } - if (isDirectionMinus || isBothDirection) { - if (xAxisDetails is DateTimeAxisDetails && isCustomFixedType) { - horizontalNegativeErrorValue = _updateDateTimeHorizontalErrorValue( - seriesRendererDetails, - actualXValue, - (series.type == ErrorBarType.custom) - ? -chartErrorValues.customNegativeX! - : -chartErrorValues.errorX!); - } else { - horizontalNegativeErrorValue = actualXValue - - ((series.type == ErrorBarType.custom) - ? chartErrorValues.customNegativeX! - : chartErrorValues.errorX!) as double; - } - } - } - - if (series.mode == RenderingMode.vertical || - series.mode == RenderingMode.both) { - if (isDirectionPlus || isBothDirection) { - verticalPositiveErrorValue = - actualYValue + chartErrorValues.errorY! as double; - } - if (isDirectionMinus || isBothDirection) { - verticalNegativeErrorValue = actualYValue - - ((series.type == ErrorBarType.custom) - ? chartErrorValues.customNegativeY! - : chartErrorValues.errorY!) as double; - } - } - return ErrorBarValues( - horizontalPositiveErrorValue, - horizontalNegativeErrorValue, - verticalPositiveErrorValue, - verticalNegativeErrorValue); -} - -/// Set maximum and minimum values of axis in error bar series. -void updateErrorBarAxisRange(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point) { - if (!point.isGap) { - final ErrorBarSeries series = - seriesRendererDetails.series as ErrorBarSeries; - // For standard error and standard deviation type error bars, all data - // points are needed to calculate the error values. So this iteration is - // executed. - if (series.type == ErrorBarType.standardDeviation || - series.type == ErrorBarType.standardError) { - final List yValuesList = []; - final List xValuesList = []; - num sumOfX = 0, sumOfY = 0; - final int dataPointsLength = series.dataSource.length; - num? verticalSquareRoot, - horizontalSquareRoot, - verticalMean, - horizontalMean; - for (int pointIndex = 0; - pointIndex < series.dataSource.length; - pointIndex++) { - yValuesList.add(series.yValueMapper!(pointIndex) ?? 0); - sumOfY += yValuesList[pointIndex]; - final dynamic xValue = series.xValueMapper!(pointIndex); - if (xValue is DateTime) { - xValuesList.add(xValue.millisecondsSinceEpoch); - } else if (xValue is String) { - xValuesList.add(pointIndex); - } else { - xValuesList.add(xValue); - } - sumOfX += xValuesList[pointIndex]; - } - verticalMean = (series.mode == RenderingMode.horizontal) - ? verticalMean - : sumOfY / dataPointsLength; - horizontalMean = (series.mode == RenderingMode.vertical) - ? horizontalMean - : sumOfX / dataPointsLength; - for (int pointIndex = 0; - pointIndex < series.dataSource.length; - pointIndex++) { - sumOfY = (series.mode == RenderingMode.horizontal) - ? sumOfY - : sumOfY + math.pow(yValuesList[pointIndex] - verticalMean!, 2); - sumOfX = (series.mode == RenderingMode.vertical) - ? sumOfX - : sumOfX + math.pow(xValuesList[pointIndex] - horizontalMean!, 2); - } - verticalSquareRoot = math.sqrt(sumOfY / (dataPointsLength - 1)); - horizontalSquareRoot = math.sqrt(sumOfX / (dataPointsLength - 1)); - final ErrorBarMean mean = ErrorBarMean( - verticalSquareRoot: verticalSquareRoot, - horizontalSquareRoot: horizontalSquareRoot, - verticalMean: verticalMean, - horizontalMean: horizontalMean); - final ChartErrorValues chartErrorValues = _getErrorValues( - seriesRendererDetails, series, point.xValue, point.yValue, - mean: mean, dataPointsLength: dataPointsLength); - point.errorBarValues = _getCalculatedErrorValues(seriesRendererDetails, - series, chartErrorValues, point.xValue, point.yValue); - } else { - final ChartErrorValues chartErrorValues = _getErrorValues( - seriesRendererDetails, series, point.xValue, point.yValue); - point.errorBarValues = _getCalculatedErrorValues(seriesRendererDetails, - series, chartErrorValues, point.xValue, point.yValue); - } - } - if (point.errorBarValues?.verticalNegativeErrorValue != null) { - seriesRendererDetails.minimumY = findMinValue( - seriesRendererDetails.minimumY ?? point.yValue, - point.errorBarValues!.verticalNegativeErrorValue!); - } - if (point.errorBarValues?.verticalPositiveErrorValue != null) { - seriesRendererDetails.maximumY = findMaxValue( - seriesRendererDetails.maximumY ?? point.yValue, - point.errorBarValues!.verticalPositiveErrorValue!); - } - if (point.errorBarValues?.horizontalPositiveErrorValue != null) { - seriesRendererDetails.maximumX = findMaxValue( - seriesRendererDetails.maximumX ?? point.xValue, - point.errorBarValues!.horizontalPositiveErrorValue!); - } - if (point.errorBarValues?.horizontalNegativeErrorValue != null) { - seriesRendererDetails.minimumX = findMinValue( - seriesRendererDetails.minimumX ?? point.xValue, - point.errorBarValues!.horizontalNegativeErrorValue!); - } -} - -/// To calculate the error values for DateTime axis -double _updateDateTimeHorizontalErrorValue( - SeriesRendererDetails seriesRendererDetails, - num actualXValue, - num errorValue) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - DateTime errorXValue = - DateTime.fromMillisecondsSinceEpoch(actualXValue.toInt()); - final int errorX = errorValue.toInt(); - final DateTimeAxisDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(xAxisDetails.axisRenderer) - as DateTimeAxisDetails; - final DateTimeIntervalType type = - axisRendererDetails.dateTimeAxis.intervalType; - switch (type) { - case DateTimeIntervalType.years: - errorXValue = DateTime( - errorXValue.year + errorX, errorXValue.month, errorXValue.day); - break; - case DateTimeIntervalType.months: - errorXValue = DateTime( - errorXValue.year, errorXValue.month + errorX, errorXValue.day); - break; - case DateTimeIntervalType.days: - errorXValue = DateTime( - errorXValue.year, errorXValue.month, errorXValue.day + errorX); - break; - case DateTimeIntervalType.hours: - errorXValue = errorXValue.add(Duration(hours: errorX)); - break; - case DateTimeIntervalType.minutes: - errorXValue = errorXValue.add(Duration(minutes: errorX)); - break; - case DateTimeIntervalType.seconds: - errorXValue = errorXValue.add(Duration(seconds: errorX)); - break; - case DateTimeIntervalType.milliseconds: - errorXValue = errorXValue.add(Duration(milliseconds: errorX)); - break; - case DateTimeIntervalType.auto: - errorXValue = errorXValue.add(Duration(milliseconds: errorX)); - break; - } - return errorXValue.millisecondsSinceEpoch.toDouble(); -} 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 deleted file mode 100644 index 31afa8157..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart +++ /dev/null @@ -1,618 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../circular_chart/renderer/circular_chart_annotation.dart'; -import '../../circular_chart/utils/enum.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/data_label_renderer.dart'; -import '../utils/enum.dart'; - -/// Customizes the data label. -/// -/// Data labels can be added to a chart series by enabling the [isVisible] option in the dataLabelSettings. It has -/// options to customize the appearance of the data label. -/// -/// Provide options like color, border width, border color, alignment and data label text style for customization. -/// -@immutable -class DataLabelSettings { - /// Creating an argument constructor of DataLabelSettings class. - const DataLabelSettings( - {this.alignment = ChartAlignment.center, - this.color, - 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, - this.useSeriesColor = false, - this.offset, - this.showCumulativeValues = false, - this.showZeroValue = true, - this.borderColor = Colors.transparent, - this.borderWidth = 0, - this.overflowMode = OverflowMode.none, - this.labelIntersectAction = LabelIntersectAction.shift, - this.connectorLineSettings = const ConnectorLineSettings(), - this.labelPosition = ChartDataLabelPosition.inside}); - - /// Alignment of the data label. - /// - /// The data label can be aligned far, near, or center of the data point position. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// Also refer [ChartAlignment]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// alignment: ChartAlignment.center - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartAlignment alignment; - - /// Color of the data label. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// color: Colors.red - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? color; - - /// Customizes the data label font. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// textStyle: TextStyle( - /// fontSize: 12 - /// ) - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final TextStyle textStyle; - - /// Margin between the data label text and its shape. - /// - /// Defaults to `EdgeInsets.fromLTRB(5, 5, 5, 5)`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// margin: const EdgeInsets.all(2), - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final EdgeInsets margin; - - /// Opacity of the data label. - /// - /// The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// opacity: 0.8 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final double opacity; - - /// Uses the series color for filling the data label shape. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// useSeriesColor: true - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final bool useSeriesColor; - - /// Position of the data label. - /// - /// _Note:_ This is applicable for Cartesian chart. - /// - /// Defaults to `ChartDataLabelAlignment.auto`. - /// - /// Also refer [ChartDataLabelAlignment]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// labelAlignment: ChartDataLabelAlignment.top - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartDataLabelAlignment labelAlignment; - - /// Customizes the data label border radius. - /// - /// Defaults to `5`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// borderRadius: 3, - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final double borderRadius; - - /// Toggles the visibility of the data label in the series. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isVisible; - - /// Rotation angle of the data label. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// angle:40 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final int angle; - - /// Border color of the data label. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final Color borderColor; - - /// Border width of the data label. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final double borderWidth; - - /// Position of the data label. - /// - /// _Note:_ This is applicable for pie and doughnut series types alone. - /// - /// Defaults to `ChartDataLabelPosition.inside`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// labelPosition: ChartDataLabelPosition.outside - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartDataLabelPosition labelPosition; - - /// Action on data labels when it’s overflowing from its region area. - /// - /// The overflowing data label rendering behavior can be changed based - /// on this. If `overflowMode` property is set to `OverflowMode.none` - /// then the `labelIntersectAction` takes the priority, else - /// `overflowMode` takes the priority. - /// - /// _Note:_ This is applicable for pie, doughnut, pyramid, and funnel series - /// types alone. - /// - /// Defaults to `OverflowMode.none`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// overflowMode: OverflowMode.shift - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final OverflowMode overflowMode; - - /// Customizes the connector lines. Connector line is rendered when the data label is - /// placed outside the chart. - /// - /// _Note:_ This is applicable for pie and doughnut series types alone. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// connectorLineSettings: ConnectorLineSettings( - /// width: 6, - /// type:ConnectorType.curve - /// ) - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final ConnectorLineSettings connectorLineSettings; - - /// Action on data labels intersection. - /// - /// The intersecting data labels can be hidden. - /// - /// _Note:_ This is applicable for pie, doughnut, funnel and pyramid series types alone. - /// - /// Defaults to `LabelIntersectAction.shift`. - /// - /// Also refer [LabelIntersectAction]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// labelIntersectAction: LabelIntersectAction.shift - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final LabelIntersectAction labelIntersectAction; - - /// Builder for data label. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// builder: (dynamic data, dynamic point, dynamic series, int pointIndex, int seriesIndex) { - /// return Container( - /// height: 30, - /// width: 30, - /// child: Image.asset('images/horse.jpg') - /// ); - /// } - /// ) - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartWidgetBuilder? builder; - - /// To show the cumulative values in stacked type series charts. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// StackedColumnSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true - /// ), - /// ), - /// StackedColumnSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// showCumulativeValues: true - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final bool showCumulativeValues; - - /// Hides the data label and its connector line, if the data point value is 0 (Zero). - /// - /// If the data label is enabled, it will be visible for all the data points in the series. - /// By using this property, we can hide the data label and its connector line, for the data - /// points if its value is 0 (Zero). - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// showZeroValue: false - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final bool showZeroValue; - - /// Moves the data label vertically or horizontally from its position. - /// - /// If you wish to reposition the data label, you can achieve using this property. - /// You can move the data label in both vertical and horizontal direction from - /// its position. It takes the logical pixel value for x and y values as input. - /// - /// Positive value for x, moves the data label to right and negative value - /// moves to left. - /// Positive value for y, moves the data label upwards and negative value - /// moves downwards. - /// - /// These are applied to the data label's final position. i.e. after considering - /// the position and alignment values. - /// - /// Also refer [labelAlignment]. - /// - /// _Note:_ This property is only applicable for Cartesian charts and not for - /// Circular, Pyramid and Funnel charts. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// dataLabelSettings: DataLabelSettings( - /// isVisible: true, - /// offset: Offset(200,200) - /// ) - /// ), - /// ], - /// ); - /// } - /// ``` - final Offset? offset; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is DataLabelSettings && - other.alignment == alignment && - other.color == color && - other.textStyle == textStyle && - other.margin == margin && - other.opacity == opacity && - other.labelAlignment == labelAlignment && - other.borderRadius == borderRadius && - other.isVisible == isVisible && - other.angle == angle && - other.builder == builder && - other.useSeriesColor == useSeriesColor && - other.offset == offset && - other.showCumulativeValues == showCumulativeValues && - other.showZeroValue == showZeroValue && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.overflowMode == overflowMode && - other.labelIntersectAction == labelIntersectAction && - other.connectorLineSettings == connectorLineSettings && - other.labelPosition == labelPosition; - } - - @override - int get hashCode { - final List values = [ - alignment, - color, - textStyle, - margin, - opacity, - labelAlignment, - borderRadius, - isVisible, - angle, - builder, - useSeriesColor, - offset, - showCumulativeValues, - showZeroValue, - borderColor, - borderWidth, - overflowMode, - labelIntersectAction, - connectorLineSettings, - labelPosition - ]; - return hashList(values); - } -} - -/// Data label renderer class for mutable fields and methods. -class DataLabelSettingsRenderer { - /// Creates an argument constructor for DataLabelSettings renderer class. - DataLabelSettingsRenderer(this.dataLabelSettings) { - angle = dataLabelSettings.angle; - offset = dataLabelSettings.offset; - color = dataLabelSettings.color; - } - - /// Represents the data label settings. - final DataLabelSettings dataLabelSettings; - - /// Holds the color value. - Color? color; - - /// Holds the text style value. - TextStyle? textStyle; - - /// Holds the value of original style. - TextStyle? originalStyle; - - /// Holds the value of angle. - late int angle; - - /// Specifies the value of offset. - Offset? offset; - - /// To render charts with data labels. - void renderDataLabel( - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - Animation animationController, - Canvas canvas, - int labelIndex, - DataLabelSettingsRenderer dataLabelSettingsRenderer) { - calculateDataLabelPosition(seriesRendererDetails, point, labelIndex, - stateProperties, dataLabelSettingsRenderer, animationController); - drawDataLabel( - canvas, - seriesRendererDetails, - stateProperties, - dataLabelSettings, - point, - labelIndex, - animationController, - dataLabelSettingsRenderer); - } -} 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 deleted file mode 100644 index 0736c34dd..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart +++ /dev/null @@ -1,2092 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../common/event_args.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../axis/logarithmic_axis.dart'; -import '../axis/numeric_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/waterfall_series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/data_label.dart'; -import '../utils/helper.dart'; - -/// Calculating the label location based on alignment value. -List _getAlignedLabelLocations( - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - DataLabelSettings dataLabel, - ChartLocation chartLocation, - ChartLocation? chartLocation2, - Size textSize) { - final SfCartesianChart chart = stateProperties.chart; - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final bool transposed = stateProperties.requireInvertedAxis; - final bool isRangeSeries = - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - final double alignmentValue = textSize.height + - (series.markerSettings.isVisible - ? ((series.markerSettings.borderWidth * 2) + - series.markerSettings.height) - : 0); - if ((seriesRendererDetails.seriesType.contains('bar') == true && - !chart.isTransposed) || - (seriesRendererDetails.seriesType.contains('column') == true && - chart.isTransposed) || - (seriesRendererDetails.seriesType.contains('waterfall') == true && - chart.isTransposed) || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - isBoxSeries) { - chartLocation.x = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation.x - : _calculateAlignment( - alignmentValue, - chartLocation.x, - dataLabel.alignment, - (isRangeSeries - ? point.high - : isBoxSeries - ? point.maximum - : point.yValue) < - 0, - transposed); - if (isRangeSeries || isBoxSeries) { - chartLocation2!.x = - (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation2.x - : _calculateAlignment( - alignmentValue, - chartLocation2.x, - dataLabel.alignment, - (isRangeSeries - ? point.low - : isBoxSeries - ? point.minimum - : point.yValue) < - 0, - transposed); - } - } else { - chartLocation.y = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation.y - : _calculateAlignment( - alignmentValue, - chartLocation.y, - dataLabel.alignment, - (isRangeSeries - ? point.high - : isBoxSeries - ? point.maximum - : point.yValue) < - 0, - transposed); - if (isRangeSeries || isBoxSeries) { - chartLocation2!.y = - (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation2.y - : _calculateAlignment( - alignmentValue, - chartLocation2.y, - dataLabel.alignment, - (isRangeSeries - ? point.low - : isBoxSeries - ? point.minimum - : point.yValue) < - 0, - transposed); - } - } - return [chartLocation, chartLocation2]; -} - -/// Calculating the label location based on dataLabel position value -/// (for range and rect series only). -List _getLabelLocations( - int index, - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - DataLabelSettings dataLabel, - ChartLocation? chartLocation, - ChartLocation? chartLocation2, - Size textSize, - Size? textSize2) { - final bool transposed = stateProperties.requireInvertedAxis; - final EdgeInsets margin = dataLabel.margin; - final bool isRangeSeries = - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - final bool inversed = seriesRendererDetails.yAxisDetails!.axis.isInversed; - final num value = isRangeSeries - ? point.high - : isBoxSeries - ? point.maximum - : point.yValue; - final bool minus = (value < 0 && !inversed) || (!(value < 0) && inversed); - if (!stateProperties.requireInvertedAxis) { - chartLocation!.y = !isBoxSeries - ? _calculateRectPosition( - chartLocation.y, - point.region!, - minus, - isRangeSeries - ? ((dataLabel.labelAlignment == ChartDataLabelAlignment.outer || - dataLabel.labelAlignment == ChartDataLabelAlignment.top) - ? dataLabel.labelAlignment - : ChartDataLabelAlignment.auto) - : dataLabel.labelAlignment, - seriesRendererDetails, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - stateProperties, - transposed, - margin) - : chartLocation.y; - } else { - chartLocation!.x = !isBoxSeries - ? _calculateRectPosition( - chartLocation.x, - point.region!, - minus, - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == - true || - isBoxSeries - ? ChartDataLabelAlignment.auto - : isRangeSeries - ? ((dataLabel.labelAlignment == - ChartDataLabelAlignment.outer || - dataLabel.labelAlignment == - ChartDataLabelAlignment.top) - ? dataLabel.labelAlignment - : ChartDataLabelAlignment.auto) - : dataLabel.labelAlignment, - seriesRendererDetails, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - stateProperties, - transposed, - margin) - : chartLocation.x; - } - chartLocation2 = isRangeSeries - ? _getSecondLabelLocation(index, stateProperties, seriesRendererDetails, - point, dataLabel, chartLocation, chartLocation2!, textSize) - : chartLocation2; - return [chartLocation, chartLocation2]; -} - -/// Finding range series second label location. -ChartLocation _getSecondLabelLocation( - int index, - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - DataLabelSettings dataLabel, - ChartLocation chartLocation, - ChartLocation chartLocation2, - Size textSize) { - final bool inversed = seriesRendererDetails.yAxisDetails!.axis.isInversed; - final bool transposed = stateProperties.requireInvertedAxis; - final EdgeInsets margin = dataLabel.margin; - bool minus; - - minus = (seriesRendererDetails.seriesType == 'boxandwhisker') - ? (point.minimum! < 0 && !inversed) || (!(point.minimum! < 0) && inversed) - : ((point.low < 0) == true && !inversed) || - ((point.low < 0) == false && inversed); - - if (!stateProperties.requireInvertedAxis) { - chartLocation2.y = _calculateRectPosition( - chartLocation2.y, - point.region!, - minus, - dataLabel.labelAlignment == ChartDataLabelAlignment.top - ? ChartDataLabelAlignment.auto - : ChartDataLabelAlignment.top, - seriesRendererDetails, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - stateProperties, - transposed, - margin); - } else { - chartLocation2.x = _calculateRectPosition( - chartLocation2.x, - point.region!, - minus, - dataLabel.labelAlignment == ChartDataLabelAlignment.top - ? ChartDataLabelAlignment.auto - : ChartDataLabelAlignment.top, - seriesRendererDetails, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - stateProperties, - transposed, - margin); - } - return chartLocation2; -} - -/// Setting data label region. -void _calculateDataLabelRegion( - CartesianChartPoint point, - DataLabelSettings dataLabel, - CartesianStateProperties stateProperties, - ChartLocation chartLocation, - ChartLocation? chartLocation2, - bool isRangeSeries, - Rect clipRect, - Size textSize, - Size? textSize2, - ChartLocation? chartLocation3, - ChartLocation? chartLocation4, - ChartLocation? chartLocation5, - Size? textSize3, - Size? textSize4, - Size? textSize5, - SeriesRendererDetails seriesRendererDetails, - int index) { - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRendererDetails.dataLabelSettingsRenderer; - Rect? rect, rect2, rect3, rect4, rect5; - final EdgeInsets margin = dataLabel.margin; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - rect = _calculateLabelRect(chartLocation, textSize, margin, - dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); - // if angle is given label will - rect = - ((index == 0 || index == seriesRendererDetails.dataPoints.length - 1) && - (dataLabelSettingsRenderer.angle / 90) % 2 == 1 && - !stateProperties.requireInvertedAxis) - ? rect - : (dataLabelSettingsRenderer.angle / 90) % 2 == 1 - ? rect - : _validateRect(rect, clipRect); - if (isRangeSeries || isBoxSeries) { - rect2 = _calculateLabelRect(chartLocation2!, textSize2!, margin, - dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); - rect2 = _validateRect(rect2, clipRect); - } - if ((seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - isBoxSeries) && - (chartLocation3 != null || - chartLocation4 != null || - chartLocation5 != null)) { - rect3 = _calculateLabelRect(chartLocation3!, textSize3!, margin, - dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); - rect3 = _validateRect(rect3, clipRect); - - rect4 = _calculateLabelRect(chartLocation4!, textSize4!, margin, - dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); - rect4 = _validateRect(rect4, clipRect); - - if (isBoxSeries) { - rect5 = _calculateLabelRect(chartLocation5!, textSize5!, margin, - dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); - rect5 = _validateRect(rect5, clipRect); - } - } - if (dataLabelSettingsRenderer.color != null || - dataLabel.useSeriesColor || - // ignore: unnecessary_null_comparison - (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { - final RRect fillRect = - _calculatePaddedFillRect(rect, dataLabel.borderRadius, margin); - 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); - if (margin == EdgeInsets.zero) { - point.labelFillRect = fillRect; - } else { - final Rect rect = fillRect.middleRect; - if (seriesRendererDetails.seriesType == 'candle' && - stateProperties.requireInvertedAxis && - (point.close > point.high) == true) { - point.labelLocation = ChartLocation( - rect.left - rect.width - textSize.width, - rect.top + rect.height / 2 - textSize.height / 2); - } else if (isBoxSeries && - stateProperties.requireInvertedAxis && - point.upperQuartile! > point.maximum!) { - point.labelLocation = ChartLocation( - rect.left - rect.width - textSize.width, - rect.top + rect.height / 2 - textSize.height / 2); - } else if (seriesRendererDetails.seriesType == 'candle' && - !stateProperties.requireInvertedAxis && - (point.close > point.high) == true) { - point.labelLocation = ChartLocation( - rect.left + rect.width / 2 - textSize.width / 2, - rect.top + rect.height + textSize.height); - } else if (isBoxSeries && - !stateProperties.requireInvertedAxis && - point.upperQuartile! > point.maximum!) { - point.labelLocation = ChartLocation( - rect.left + rect.width / 2 - textSize.width / 2, - rect.top + rect.height + textSize.height); - } else { - point.labelLocation = ChartLocation( - 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.labelFillRect = _rectToRrect(rect, dataLabel.borderRadius); - } - if (isRangeSeries || isBoxSeries) { - final RRect fillRect2 = - _calculatePaddedFillRect(rect2!, dataLabel.borderRadius, margin); - point.labelLocation2 = ChartLocation( - 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); - if (margin == EdgeInsets.zero) { - point.labelFillRect2 = fillRect2; - } else { - final Rect rect2 = fillRect2.middleRect; - 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.labelFillRect2 = _rectToRrect(rect2, dataLabel.borderRadius); - } - } - if (seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - isBoxSeries && (rect3 != null || rect4 != null || rect5 != null)) { - final RRect fillRect3 = - _calculatePaddedFillRect(rect3!, dataLabel.borderRadius, margin); - point.labelLocation3 = ChartLocation( - 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); - if (margin == EdgeInsets.zero) { - point.labelFillRect3 = fillRect3; - } else { - final Rect rect3 = fillRect3.middleRect; - 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.labelFillRect3 = _rectToRrect(rect3, dataLabel.borderRadius); - } - final RRect fillRect4 = - _calculatePaddedFillRect(rect4!, dataLabel.borderRadius, margin); - point.labelLocation4 = ChartLocation( - 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); - if (margin == EdgeInsets.zero) { - point.labelFillRect4 = fillRect4; - } else { - final Rect rect4 = fillRect4.middleRect; - 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.labelFillRect4 = _rectToRrect(rect4, dataLabel.borderRadius); - } - if (isBoxSeries) { - final RRect fillRect5 = - _calculatePaddedFillRect(rect5!, dataLabel.borderRadius, margin); - point.labelLocation5 = ChartLocation( - 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); - if (margin == EdgeInsets.zero) { - point.labelFillRect5 = fillRect5; - } else { - final Rect rect5 = fillRect5.middleRect; - 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.labelFillRect5 = _rectToRrect(rect5, dataLabel.borderRadius); - } - } - } - } else { - if (seriesRendererDetails.seriesType == 'candle' && - stateProperties.requireInvertedAxis && - (point.close > point.high) == true) { - point.labelLocation = ChartLocation( - rect.left - rect.width - textSize.width - 2, - rect.top + rect.height / 2 - textSize.height / 2); - } else if (isBoxSeries && - stateProperties.requireInvertedAxis && - point.upperQuartile! > point.maximum!) { - point.labelLocation = ChartLocation( - rect.left - rect.width - textSize.width - 2, - rect.top + rect.height / 2 - textSize.height / 2); - } else if (seriesRendererDetails.seriesType == 'candle' && - !stateProperties.requireInvertedAxis && - (point.close > point.high) == true) { - point.labelLocation = ChartLocation( - rect.left + rect.width / 2 - textSize.width / 2, - rect.top + rect.height + textSize.height / 2); - } else if (isBoxSeries && - !stateProperties.requireInvertedAxis && - point.upperQuartile! > point.maximum!) { - point.labelLocation = ChartLocation( - rect.left + rect.width / 2 - textSize.width / 2, - rect.top + rect.height + textSize.height / 2); - } else { - point.labelLocation = ChartLocation( - 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); - if (isRangeSeries || isBoxSeries) { - if (seriesRendererDetails.seriesType == 'candle' && - stateProperties.requireInvertedAxis && - (point.close > point.high) == true) { - point.labelLocation2 = ChartLocation( - rect2!.left + rect2.width + textSize2!.width + 2, - rect2.top + rect2.height / 2 - textSize2.height / 2); - } else if (isBoxSeries && - stateProperties.requireInvertedAxis && - point.upperQuartile! > point.maximum!) { - point.labelLocation2 = ChartLocation( - rect2!.left + rect2.width + textSize2!.width + 2, - rect2.top + rect2.height / 2 - textSize2.height / 2); - } else if (seriesRendererDetails.seriesType == 'candle' && - !stateProperties.requireInvertedAxis && - (point.close > point.high) == true) { - point.labelLocation2 = ChartLocation( - rect2!.left + rect2.width / 2 - textSize2!.width / 2, - rect2.top - rect2.height - textSize2.height); - } else if (isBoxSeries && - !stateProperties.requireInvertedAxis && - point.upperQuartile! > point.maximum!) { - point.labelLocation2 = ChartLocation( - 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.top + rect2.height / 2 - textSize2.height / 2); - } - point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, - point.labelLocation2!.y, textSize2.width, textSize2.height); - } - if ((seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - isBoxSeries) && - (rect3 != null || rect4 != null)) { - 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.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); - if (rect5 != null) { - 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); - } - } - } -} - -/// To find the position of a series to render. -double _calculatePathPosition( - double labelLocation, - ChartDataLabelAlignment position, - Size size, - double borderWidth, - SeriesRendererDetails seriesRendererDetails, - int index, - bool inverted, - ChartLocation point, - CartesianStateProperties stateProperties, - CartesianChartPoint currentPoint, - Size markerSize) { - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - const double padding = 5; - final bool needFill = series.dataLabelSettings.color != null || - series.dataLabelSettings.color != Colors.transparent || - series.dataLabelSettings.useSeriesColor; - final num fillSpace = needFill ? padding : 0; - if (seriesRendererDetails.seriesType.contains('area') == true && - seriesRendererDetails.seriesType.contains('rangearea') == false && - seriesRendererDetails.yAxisDetails!.axis.isInversed == true) { - position = position == ChartDataLabelAlignment.top - ? ChartDataLabelAlignment.bottom - : (position == ChartDataLabelAlignment.bottom - ? ChartDataLabelAlignment.top - : position); - } - position = (stateProperties.chartSeries.visibleSeriesRenderers.length == 1 && - (seriesRendererDetails.seriesType == 'stackedarea100' || - seriesRendererDetails.seriesType == 'stackedline100') && - position == ChartDataLabelAlignment.auto) - ? ChartDataLabelAlignment.bottom - : position; - switch (position) { - case ChartDataLabelAlignment.top: - case ChartDataLabelAlignment.outer: - labelLocation = labelLocation - - markerSize.height - - borderWidth - - (size.height / 2) - - padding - - fillSpace; - break; - case ChartDataLabelAlignment.bottom: - labelLocation = labelLocation + - markerSize.height + - borderWidth + - (size.height / 2) + - padding + - fillSpace; - break; - case ChartDataLabelAlignment.auto: - labelLocation = _calculatePathActualPosition( - seriesRendererDetails, - size, - index, - inverted, - borderWidth, - point, - stateProperties, - currentPoint, - seriesRendererDetails.yAxisDetails!.axis.isInversed); - break; - case ChartDataLabelAlignment.middle: - break; - } - return labelLocation; -} - -/// Below method is for dataLabel alignment calculation. -double _calculateAlignment(double value, double labelLocation, - ChartAlignment alignment, bool isMinus, bool inverted) { - switch (alignment) { - case ChartAlignment.far: - labelLocation = !inverted - ? (isMinus ? labelLocation + value : labelLocation - value) - : (isMinus ? labelLocation - value : labelLocation + value); - break; - case ChartAlignment.near: - labelLocation = !inverted - ? (isMinus ? labelLocation - value : labelLocation + value) - : (isMinus ? labelLocation + value : labelLocation - value); - break; - case ChartAlignment.center: - labelLocation = labelLocation; - break; - } - return labelLocation; -} - -/// Calculate label position for non rect series. -double _calculatePathActualPosition( - SeriesRendererDetails seriesRendererDetails, - Size size, - int index, - bool inverted, - double borderWidth, - ChartLocation point, - CartesianStateProperties stateProperties, - CartesianChartPoint currentPoint, - bool inversed) { - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - late double yLocation; - bool isBottom, isOverLap = true; - Rect labelRect; - int positionIndex; - final ChartDataLabelAlignment position = - _getActualPathDataLabelAlignment(seriesRendererDetails, index, inversed); - isBottom = position == ChartDataLabelAlignment.bottom; - final List dataLabelPosition = List.filled(5, null); - dataLabelPosition[0] = 'DataLabelPosition.Outer'; - dataLabelPosition[1] = 'DataLabelPosition.Top'; - dataLabelPosition[2] = 'DataLabelPosition.Bottom'; - dataLabelPosition[3] = 'DataLabelPosition.Middle'; - dataLabelPosition[4] = 'DataLabelPosition.Auto'; - positionIndex = dataLabelPosition.indexOf(position.toString()).toInt(); - while (isOverLap && positionIndex < 4) { - yLocation = _calculatePathPosition( - point.y.toDouble(), - position, - size, - borderWidth, - seriesRendererDetails, - index, - inverted, - point, - stateProperties, - currentPoint, - Size( - series.markerSettings.width / 2, series.markerSettings.height / 2)); - labelRect = _calculateLabelRect( - ChartLocation(point.x, yLocation), - size, - series.dataLabelSettings.margin, - series.dataLabelSettings.color != null || - series.dataLabelSettings.useSeriesColor); - isOverLap = labelRect.top < 0 || - ((labelRect.top + labelRect.height) > - stateProperties.chartAxis.axisClipRect.height) || - findingCollision(labelRect, stateProperties.renderDatalabelRegions); - positionIndex = isBottom ? positionIndex - 1 : positionIndex + 1; - isBottom = false; - } - return yLocation; -} - -/// Finding the label position for non rect series. -ChartDataLabelAlignment _getActualPathDataLabelAlignment( - SeriesRendererDetails seriesRendererDetails, int index, bool inversed) { - final List> points = - seriesRendererDetails.dataPoints; - final num yValue = points[index].yValue; - final CartesianChartPoint? _nextPoint = - points.length - 1 > index ? points[index + 1] : null; - final CartesianChartPoint? previousPoint = - index > 0 ? points[index - 1] : null; - ChartDataLabelAlignment position; - if (seriesRendererDetails.seriesType == 'bubble' || - index == points.length - 1) { - position = ChartDataLabelAlignment.top; - } else { - if (index == 0) { - position = (!_nextPoint!.isVisible || - yValue > _nextPoint.yValue || - (yValue < _nextPoint.yValue && inversed)) - ? ChartDataLabelAlignment.top - : ChartDataLabelAlignment.bottom; - } else if (index == points.length - 1) { - position = (!previousPoint!.isVisible || - yValue > previousPoint.yValue || - (yValue < previousPoint.yValue && inversed)) - ? ChartDataLabelAlignment.top - : ChartDataLabelAlignment.bottom; - } else { - if (!_nextPoint!.isVisible && !previousPoint!.isVisible) { - position = ChartDataLabelAlignment.top; - } else if (!_nextPoint.isVisible) { - position = ((_nextPoint.yValue > yValue) == true || - (previousPoint!.yValue > yValue) == true) - ? ChartDataLabelAlignment.bottom - : ChartDataLabelAlignment.top; - } else { - final num slope = (_nextPoint.yValue - previousPoint!.yValue) / 2; - final num intersectY = - (slope * index) + (_nextPoint.yValue - (slope * (index + 1))); - position = !inversed - ? intersectY < yValue - ? ChartDataLabelAlignment.top - : ChartDataLabelAlignment.bottom - : intersectY < yValue - ? ChartDataLabelAlignment.bottom - : ChartDataLabelAlignment.top; - } - } - } - return position; -} - -/// To get the data label position. -ChartDataLabelAlignment _getPosition(int position) { - late ChartDataLabelAlignment dataLabelPosition; - switch (position) { - case 0: - dataLabelPosition = ChartDataLabelAlignment.outer; - break; - case 1: - dataLabelPosition = ChartDataLabelAlignment.top; - break; - case 2: - dataLabelPosition = ChartDataLabelAlignment.bottom; - break; - case 3: - dataLabelPosition = ChartDataLabelAlignment.middle; - break; - case 4: - dataLabelPosition = ChartDataLabelAlignment.auto; - break; - } - return dataLabelPosition; -} - -/// Getting label rect. -Rect _calculateLabelRect( - ChartLocation location, Size textSize, EdgeInsets margin, bool needRect) { - return needRect - ? Rect.fromLTWH( - location.x - (textSize.width / 2) - margin.left, - location.y - (textSize.height / 2) - margin.top, - textSize.width + margin.left + margin.right, - textSize.height + margin.top + margin.bottom) - : Rect.fromLTWH(location.x - (textSize.width / 2), - location.y - (textSize.height / 2), textSize.width, textSize.height); -} - -/// Below method is for rendering data label. -void drawDataLabel( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - DataLabelSettings dataLabel, - CartesianChartPoint point, - int index, - Animation dataLabelAnimation, - DataLabelSettingsRenderer dataLabelSettingsRenderer) { - double x = 0; - double y = 0; - if (dataLabelSettingsRenderer.offset != null) { - x = dataLabelSettingsRenderer.offset!.dx; - y = dataLabelSettingsRenderer.offset!.dy; - } - final double opacity = - seriesRendererDetails.needAnimateSeriesElements == true && - // ignore: unnecessary_null_comparison - dataLabelAnimation != null - ? dataLabelAnimation.value - : 1; - TextStyle? dataLabelStyle; - final String? label = point.label; - dataLabelStyle = dataLabelSettingsRenderer.textStyle; - if (label != null && - // ignore: unnecessary_null_comparison - point != null && - point.isVisible && - point.isGap != true && - isLabelWithinRange(seriesRendererDetails, point)) { - final TextStyle font = (dataLabelStyle == null) - ? const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12) - : dataLabelStyle; - final Color fontColor = font.color ?? - getDataLabelSaturationColor(point, seriesRendererDetails, - stateProperties, 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); - final bool isDatalabelCollide = (stateProperties.requireInvertedAxis || - (dataLabelSettingsRenderer.angle / 90) % 2 != 1) && - findingCollision(labelRect, stateProperties.renderDatalabelRegions); - if (!(label.isNotEmpty && isDatalabelCollide) || - // ignore: unnecessary_null_comparison - dataLabel.labelIntersectAction == null) { - final TextStyle _textStyle = TextStyle( - color: fontColor.withOpacity(opacity), - fontSize: font.fontSize, - fontFamily: font.fontFamily, - fontStyle: font.fontStyle, - fontWeight: font.fontWeight, - inherit: font.inherit, - backgroundColor: font.backgroundColor, - letterSpacing: font.letterSpacing, - wordSpacing: font.wordSpacing, - textBaseline: font.textBaseline, - height: font.height, - locale: font.locale, - foreground: font.foreground, - background: font.background, - shadows: font.shadows, - fontFeatures: font.fontFeatures, - decoration: font.decoration, - decorationColor: font.decorationColor, - decorationStyle: font.decorationStyle, - decorationThickness: font.decorationThickness, - debugLabel: font.debugLabel, - fontFamilyFallback: font.fontFamilyFallback); - _drawDataLabelRectAndText( - canvas, - seriesRendererDetails, - index, - dataLabel, - point, - _textStyle, - opacity, - label, - x, - y, - stateProperties, - stateProperties.chart); - stateProperties.renderDatalabelRegions.add(labelRect); - } - } -} - -/// Method to trigger the data label event. -void triggerDataLabelEvent(SfCartesianChart chart, - List visibleSeriesRenderer, Offset position) { - SeriesRendererDetails seriesRendererDetails; - for (int seriesIndex = 0; - seriesIndex < visibleSeriesRenderer.length; - seriesIndex++) { - final CartesianSeriesRenderer seriesRenderer = - visibleSeriesRenderer[seriesIndex]; - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final List>? dataPoints = - seriesRendererDetails.visibleDataPoints; - for (int pointIndex = 0; pointIndex < dataPoints!.length; pointIndex++) { - if (seriesRendererDetails.series.dataLabelSettings.isVisible == true && - dataPoints[pointIndex].dataLabelRegion != null && - dataPoints[pointIndex].dataLabelRegion!.contains(position)) { - final CartesianChartPoint point = dataPoints[pointIndex]; - final Offset position = - Offset(point.labelLocation!.x, point.labelLocation!.y); - dataLabelTapEvent(chart, seriesRendererDetails.series.dataLabelSettings, - pointIndex, point, position, seriesIndex); - break; - } - } - } -} - -/// Draw the data label text and data label rect. -void _drawDataLabelRectAndText( - Canvas canvas, - SeriesRendererDetails seriesRendererDetails, - int index, - DataLabelSettings dataLabel, - CartesianChartPoint point, - TextStyle _textStyle, - double opacity, - String label, - double x, - double y, - CartesianStateProperties stateProperties, - [SfCartesianChart? chart]) { - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRendererDetails.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 bool isRangeSeries = - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - double padding = 0.0; - // ignore: unnecessary_null_comparison - if (dataLabelSettingsRenderer.angle != null && - dataLabelSettingsRenderer.angle > 0) { - final Rect rect = rotatedTextSize( - Size(point.dataLabelRegion!.width, point.dataLabelRegion!.height), - dataLabelSettingsRenderer.angle); - if (stateProperties.chartAxis.axisClipRect.top > - point.dataLabelRegion!.center.dy + rect.top) { - padding = (point.dataLabelRegion!.center.dy + rect.top) - - stateProperties.chartAxis.axisClipRect.top; - } else if (stateProperties.chartAxis.axisClipRect.bottom < - point.dataLabelRegion!.center.dy + rect.bottom) { - padding = (point.dataLabelRegion!.center.dy + rect.bottom) - - stateProperties.chartAxis.axisClipRect.bottom; - } - } - if (dataLabelSettingsRenderer.color != null || - dataLabel.useSeriesColor || - // ignore: unnecessary_null_comparison - (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { - final RRect fillRect = point.labelFillRect!; - final Path path = Path(); - path.addRRect(fillRect); - final RRect? fillRect2 = point.labelFillRect2; - final Path path2 = Path(); - if (isRangeSeries || isBoxSeries) { - path2.addRRect(fillRect2!); - } - final RRect? fillRect3 = point.labelFillRect3; - final Path path3 = Path(); - final RRect? fillRect4 = point.labelFillRect4; - final Path path4 = Path(); - final RRect? fillRect5 = point.labelFillRect5; - final Path path5 = Path(); - if (seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - isBoxSeries) { - path3.addRRect(fillRect3!); - path4.addRRect(fillRect4!); - if (isBoxSeries) { - path5.addRRect(fillRect5!); - } - } - // ignore: unnecessary_null_comparison - if (dataLabel.borderColor != null && dataLabel.borderWidth > 0) { - final Paint strokePaint = Paint() - ..color = dataLabel.borderColor.withOpacity( - (opacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : opacity - (1 - dataLabel.opacity)) - ..strokeWidth = dataLabel.borderWidth - ..style = PaintingStyle.stroke; - dataLabel.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color = strokePaint.color; - canvas.save(); - canvas.translate(point.dataLabelRegion!.center.dx + x, - point.dataLabelRegion!.center.dy - padding); - // ignore: unnecessary_null_comparison - 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); - if (point.label!.isNotEmpty) { - canvas.drawPath(path, strokePaint); - } - canvas.restore(); - if (isRangeSeries || isBoxSeries) { - if (point.label2!.isNotEmpty) { - canvas.drawPath(path2, strokePaint); - } - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') == true || - isBoxSeries) { - if (point.label3!.isNotEmpty) { - canvas.drawPath(path3, strokePaint); - } - if (point.label4!.isNotEmpty) { - canvas.drawPath(path4, strokePaint); - } - } - if (isBoxSeries) { - if (point.label5!.isNotEmpty) { - canvas.drawPath(path5, strokePaint); - } - } - } - } - if (dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor) { - Color? seriesColor = seriesRendererDetails.seriesColor!; - if (seriesRendererDetails.seriesType == 'waterfall') { - seriesColor = getWaterfallSeriesColor( - seriesRendererDetails.series as WaterfallSeries, - point, - seriesColor); - } - final Paint paint = Paint() - ..color = dataLabelSettingsRenderer.color != Colors.transparent - ? ((dataLabelSettingsRenderer.color ?? - (point.pointColorMapper ?? seriesColor!)) - .withOpacity((opacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : opacity - (1 - dataLabel.opacity))) - : Colors.transparent - ..style = PaintingStyle.fill; - canvas.save(); - canvas.translate(point.dataLabelRegion!.center.dx + x, - point.dataLabelRegion!.center.dy - padding); - // ignore: unnecessary_null_comparison - 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); - if (point.label!.isNotEmpty) { - canvas.drawPath(path, paint); - } - canvas.restore(); - if (isRangeSeries || isBoxSeries) { - if (point.label2!.isNotEmpty) { - canvas.drawPath(path2, paint); - } - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') == true || - isBoxSeries) { - if (point.label3!.isNotEmpty) { - canvas.drawPath(path3, paint); - } - if (point.label4!.isNotEmpty) { - canvas.drawPath(path4, paint); - } - } - if (isBoxSeries) { - if (point.label5!.isNotEmpty) { - canvas.drawPath(path5, paint); - } - } - } - } - } - - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - label, - dataLabelSettingsRenderer.angle != 0 - ? point.dataLabelRegion!.center.dx + x - : point.labelLocation!.x + x, - dataLabelSettingsRenderer.angle != 0 - ? point.dataLabelRegion!.center.dy - y - padding - : point.labelLocation!.y - y, - dataLabelSettingsRenderer.angle, - _textStyle); - - if (isRangeSeries || isBoxSeries) { - if (withInRange(isBoxSeries ? point.minimum : point.low, - seriesRendererDetails.yAxisDetails!.visibleRange!)) { - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - label2!, - point.labelLocation2!.x + x, - point.labelLocation2!.y - y, - dataLabelSettingsRenderer.angle, - _textStyle); - } - if (seriesRendererDetails.seriesType == 'hiloopenclose' && - (label3 != null && - label4 != null && - (point.labelLocation3!.y - point.labelLocation4!.y).round() >= - 8 || - (point.labelLocation4!.x - point.labelLocation3!.x).round() >= - 15)) { - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - label3!, - point.labelLocation3!.x + x, - point.labelLocation3!.y + y, - dataLabelSettingsRenderer.angle, - _textStyle); - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - 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)) { - final Color fontColor = - getOpenCloseDataLabelColor(point, seriesRendererDetails, chart!); - final TextStyle _textStyleOpenClose = TextStyle( - color: fontColor.withOpacity(opacity), - fontSize: _textStyle.fontSize, - fontFamily: _textStyle.fontFamily, - fontStyle: _textStyle.fontStyle, - fontWeight: _textStyle.fontWeight, - 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); - if ((point.labelLocation2!.y - point.labelLocation3!.y).abs() >= 8 || - (point.labelLocation2!.x - point.labelLocation3!.x).abs() >= 8) { - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - label3, - 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) { - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - label4, - point.labelLocation4!.x + x, - point.labelLocation4!.y + y, - dataLabelSettingsRenderer.angle, - _textStyleOpenClose); - } - if (label5 != null && point.labelLocation5 != null) { - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - label5, - point.labelLocation5!.x + x, - point.labelLocation5!.y + y, - dataLabelSettingsRenderer.angle, - _textStyleOpenClose); - } - - if (isBoxSeries) { - if (point.outliers!.isNotEmpty) { - final List outliersLocation = []; - final List outliersTextSize = []; - final List outliersRect = []; - const int outlierPadding = 12; - for (int outlierIndex = 0; - outlierIndex < point.outliers!.length; - outlierIndex++) { - point.outliersLabel.add(point.dataLabelMapper ?? - _getLabelText( - point.outliers![outlierIndex], seriesRendererDetails)); - outliersTextSize.add(measureText( - point.outliersLabel[outlierIndex], - dataLabelSettingsRenderer.textStyle == null - ? const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12) - : dataLabelSettingsRenderer.originalStyle!)); - outliersLocation.add(ChartLocation( - point.outliersPoint[outlierIndex].x, - point.outliersPoint[outlierIndex].y + outlierPadding)); - // ignore: unnecessary_null_comparison - if (outliersLocation[outlierIndex] != null) { - outliersRect.add(_calculateLabelRect( - outliersLocation[outlierIndex], - outliersTextSize[outlierIndex], - dataLabel.margin, - dataLabelSettingsRenderer.color != null || - dataLabel.useSeriesColor)); - outliersRect[outlierIndex] = _validateRect( - outliersRect[outlierIndex], - calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails - .yAxisDetails!.axis.plotOffset))); - } - if (dataLabelSettingsRenderer.color != null || - dataLabel.useSeriesColor || - // ignore: unnecessary_null_comparison - (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { - // ignore: unnecessary_null_comparison - if (outliersRect[outlierIndex] != null) { - final RRect outliersFillRect = _calculatePaddedFillRect( - outliersRect[outlierIndex], - dataLabel.borderRadius, - dataLabel.margin); - if (dataLabel.margin == EdgeInsets.zero) { - point.outliersFillRect.add(outliersFillRect); - } else { - final Rect outliersRect = outliersFillRect.middleRect; - point.outliersLocation.add(ChartLocation( - outliersRect.left + - outliersRect.width / 2 - - outliersTextSize[outlierIndex].width / 2, - outliersRect.top + - outliersRect.height / 2 - - outliersTextSize[outlierIndex].height / 2)); - point.outliersDataLabelRegion.add(Rect.fromLTWH( - point.outliersLocation[outlierIndex].x, - point.outliersLocation[outlierIndex].y, - outliersTextSize[outlierIndex].width, - outliersTextSize[outlierIndex].height)); - point.outliersFillRect - .add(_rectToRrect(outliersRect, dataLabel.borderRadius)); - } - final RRect fillOutlierRect = - point.outliersFillRect[outlierIndex]; - final Path outlierPath = Path(); - outlierPath.addRRect(fillOutlierRect); - final Paint paint = Paint() - ..color = (dataLabelSettingsRenderer.color ?? - (point.pointColorMapper ?? - seriesRendererDetails.seriesColor!)) - .withOpacity((opacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : opacity - (1 - dataLabel.opacity)) - ..style = PaintingStyle.fill; - canvas.drawPath(outlierPath, paint); - final Paint strokePaint = Paint() - ..color = dataLabel.borderColor.withOpacity( - (opacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : opacity - (1 - dataLabel.opacity)) - ..strokeWidth = dataLabel.borderWidth - ..style = PaintingStyle.stroke; - dataLabel.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color = strokePaint.color; - canvas.drawPath(outlierPath, strokePaint); - } - } else { - // ignore: unnecessary_null_comparison - if (outliersRect[outlierIndex] != null) { - point.outliersLocation.add(ChartLocation( - outliersRect[outlierIndex].left + - outliersRect[outlierIndex].width / 2 - - outliersTextSize[outlierIndex].width / 2, - outliersRect[outlierIndex].top + - outliersRect[outlierIndex].height / 2 - - outliersTextSize[outlierIndex].height / 2)); - point.outliersDataLabelRegion.add(Rect.fromLTWH( - point.outliersLocation[outlierIndex].x, - point.outliersLocation[outlierIndex].y, - outliersTextSize[outlierIndex].width, - outliersTextSize[outlierIndex].height)); - } - } - final String outlierLabel = - point.dataLabelMapper ?? point.outliersLabel[outlierIndex]; - seriesRendererDetails.renderer.drawDataLabel( - index, - canvas, - outlierLabel, - point.outliersLocation[outlierIndex].x + x, - point.outliersLocation[outlierIndex].y + y, - dataLabelSettingsRenderer.angle, - _textStyle); - } - } - } - } - } -} - -/// Following method returns the data label text. -String _getLabelText( - dynamic labelValue, SeriesRendererDetails seriesRendererDetails) { - if (labelValue.toString().split('.').length > 1) { - final String str = labelValue.toString(); - final List list = str.split('.'); - labelValue = double.parse(labelValue.toStringAsFixed(6)); - if (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000') { - labelValue = labelValue.round(); - } - } - final dynamic yAxis = seriesRendererDetails.yAxisDetails!.axis; - if (yAxis is NumericAxis || yAxis is LogarithmicAxis) { - final dynamic value = yAxis?.numberFormat != null - ? yAxis.numberFormat.format(labelValue) - : labelValue; - return ((yAxis.labelFormat != null && yAxis.labelFormat != '') - ? yAxis.labelFormat.replaceAll(RegExp('{value}'), value.toString()) - : value.toString()) as String; - } else { - return labelValue.toString(); - } -} - -/// Calculating rect position for dataLabel. -double _calculateRectPosition( - double labelLocation, - Rect rect, - bool isMinus, - ChartDataLabelAlignment position, - SeriesRendererDetails seriesRendererDetails, - Size textSize, - double borderWidth, - int index, - ChartLocation point, - CartesianStateProperties stateProperties, - bool inverted, - EdgeInsets margin) { - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - double padding; - padding = seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('rangecolumn') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true - ? 2 - : 5; - final bool needFill = series.dataLabelSettings.color != null || - series.dataLabelSettings.color != Colors.transparent || - series.dataLabelSettings.useSeriesColor; - final double textLength = !inverted ? textSize.height : textSize.width; - final double extraSpace = - borderWidth + textLength / 2 + padding + (needFill ? padding : 0); - if (seriesRendererDetails.seriesType.contains('stack') == true) { - position = position == ChartDataLabelAlignment.outer - ? ChartDataLabelAlignment.top - : position; - } - - /// Locating the data label based on position. - switch (position) { - case ChartDataLabelAlignment.bottom: - labelLocation = !inverted - ? (isMinus - ? (labelLocation - rect.height + extraSpace) - : (labelLocation + rect.height - extraSpace)) - : (isMinus - ? (labelLocation + rect.width - extraSpace) - : (labelLocation - rect.width + extraSpace)); - break; - case ChartDataLabelAlignment.middle: - labelLocation = !inverted - ? (isMinus - ? labelLocation - (rect.height / 2) - : labelLocation + (rect.height / 2)) - : (isMinus - ? labelLocation + (rect.width / 2) - : labelLocation - (rect.width / 2)); - break; - case ChartDataLabelAlignment.auto: - labelLocation = _calculateRectActualPosition( - labelLocation, - rect, - isMinus, - seriesRendererDetails, - textSize, - index, - point, - inverted, - borderWidth, - stateProperties, - margin); - break; - case ChartDataLabelAlignment.top: - case ChartDataLabelAlignment.outer: - labelLocation = _calculateTopAndOuterPosition( - textSize, - labelLocation, - rect, - position, - seriesRendererDetails, - index, - extraSpace, - isMinus, - point, - inverted, - borderWidth); - break; - } - return labelLocation; -} - -/// Calculating the label location if position is given as auto. -double _calculateRectActualPosition( - double labelLocation, - Rect rect, - bool minus, - SeriesRendererDetails seriesRendererDetails, - Size textSize, - int index, - ChartLocation point, - bool inverted, - double borderWidth, - CartesianStateProperties stateProperties, - EdgeInsets margin) { - late double location; - Rect labelRect; - bool isOverLap = true; - int position = 0; - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final int finalPosition = - seriesRendererDetails.seriesType.contains('range') == true ? 2 : 4; - while (isOverLap && position < finalPosition) { - location = _calculateRectPosition( - labelLocation, - rect, - minus, - _getPosition(position), - seriesRendererDetails, - textSize, - borderWidth, - index, - point, - stateProperties, - inverted, - margin); - if (!inverted) { - labelRect = _calculateLabelRect( - ChartLocation(point.x, location), - textSize, - margin, - series.dataLabelSettings.color != null || - series.dataLabelSettings.useSeriesColor); - isOverLap = labelRect.top < 0 || - labelRect.top > stateProperties.chartAxis.axisClipRect.height || - ((series.dataLabelSettings.angle / 90) % 2 != 1 && - findingCollision( - labelRect, stateProperties.renderDatalabelRegions)); - } else { - labelRect = _calculateLabelRect( - ChartLocation(location, point.y), - textSize, - margin, - series.dataLabelSettings.color != null || - series.dataLabelSettings.useSeriesColor); - isOverLap = labelRect.left < 0 || - labelRect.left + labelRect.width > - stateProperties.chartAxis.axisClipRect.right || - (series.dataLabelSettings.angle % 180 != 0 && - findingCollision( - labelRect, stateProperties.renderDatalabelRegions)); - } - seriesRendererDetails.dataPoints[index].dataLabelSaturationRegionInside = - isOverLap || - seriesRendererDetails - .dataPoints[index].dataLabelSaturationRegionInside == - true; - position++; - } - return location; -} - -/// Calculation for top and outer position of data label for rect series. -double _calculateTopAndOuterPosition( - Size textSize, - double location, - Rect rect, - ChartDataLabelAlignment position, - SeriesRendererDetails seriesRendererDetails, - int index, - double extraSpace, - bool isMinus, - ChartLocation point, - bool inverted, - double borderWidth) { - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final num markerHeight = - series.markerSettings.isVisible ? series.markerSettings.height / 2 : 0; - if (((isMinus && - seriesRendererDetails.seriesType.contains('range') == false) && - position == ChartDataLabelAlignment.top) || - ((!isMinus || - seriesRendererDetails.seriesType.contains('range') == true) && - position == ChartDataLabelAlignment.outer)) { - location = !inverted - ? location - extraSpace - markerHeight - : location + extraSpace + markerHeight; - } else { - location = !inverted - ? location + extraSpace + markerHeight - : location - extraSpace - markerHeight; - } - return location; -} - -/// Add padding for fill rect (if data label fill color is given). -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); - - return _rectToRrect(rect, radius); -} - -/// Converting rect into rounded 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), - bottomRight: Radius.elliptical(radius, radius)); - -/// 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 - double left, top; - left = rect.left < clipRect.left ? clipRect.left : rect.left; - top = double.parse(rect.top.toStringAsFixed(2)) < clipRect.top - ? clipRect.top - : rect.top; - left -= ((double.parse(left.toStringAsFixed(2)) + rect.width) > - clipRect.right) - ? (double.parse(left.toStringAsFixed(2)) + rect.width) - clipRect.right - : 0; - top -= (double.parse(top.toStringAsFixed(2)) + rect.height) > clipRect.bottom - ? (double.parse(top.toStringAsFixed(2)) + rect.height) - clipRect.bottom - : 0; - left = left < clipRect.left ? clipRect.left : left; - rect = Rect.fromLTWH(left, top, rect.width, rect.height); - return rect; -} - -/// It returns a boolean value that labels within range or not. -bool isLabelWithinRange(SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point) { - bool isWithInRange = true; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - if (seriesRendererDetails.yAxisDetails is! LogarithmicAxisDetails) { - isWithInRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!) && - (seriesRendererDetails.seriesType.contains('range') || - seriesRendererDetails.seriesType == 'hilo' - ? (isBoxSeries && point.minimum != null && point.maximum != null) || - (!isBoxSeries && point.low != null && point.high != null) && - (withInRange( - isBoxSeries ? point.minimum : point.low, - seriesRendererDetails - .yAxisDetails!.visibleRange!) || - withInRange(isBoxSeries ? point.maximum : point.high, - seriesRendererDetails.yAxisDetails!.visibleRange!)) - : seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') || - isBoxSeries - ? (withInRange(isBoxSeries ? point.minimum : point.low, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange(isBoxSeries ? point.maximum : point.high, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange(isBoxSeries ? point.lowerQuartile : point.open, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange(isBoxSeries ? point.upperQuartile : point.close, - seriesRendererDetails.yAxisDetails!.visibleRange!)) - : withInRange( - seriesRendererDetails.seriesType.contains('100') - ? point.cumulativeValue - : seriesRendererDetails.seriesType == 'waterfall' - ? point.endValue ?? 0 - : point.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!)); - } - return isWithInRange; -} - -/// Calculating data label position and updating the label region for current data point. -void calculateDataLabelPosition( - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - int index, - CartesianStateProperties stateProperties, - DataLabelSettingsRenderer dataLabelSettingsRenderer, - Animation dataLabelAnimation, - [Size? templateSize, - Offset? templateLocation]) { - final SfCartesianChart chart = stateProperties.chart; - final CartesianSeries series = seriesRendererDetails.series; - if (dataLabelSettingsRenderer.angle.isNegative) { - final int angle = dataLabelSettingsRenderer.angle + 360; - dataLabelSettingsRenderer.angle = angle; - } - final DataLabelSettings dataLabel = series.dataLabelSettings; - Size? textSize, textSize2, textSize3, textSize4, textSize5; - double? value1, value2; - const int boxPlotPadding = 8; - final Rect rect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - if (seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true) { - value1 = ((point.open != null && - point.close != null && - (point.close < point.open) == true) - ? point.close - : point.open) - ?.toDouble(); - value2 = ((point.open != null && - point.close != null && - (point.close > point.open) == true) - ? point.close - : point.open) - ?.toDouble(); - } - final bool transposed = stateProperties.requireInvertedAxis; - final bool inversed = seriesRendererDetails.yAxisDetails!.axis.isInversed; - final Rect clipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final bool isRangeSeries = - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - if (isBoxSeries) { - value1 = (point.upperQuartile != null && - point.lowerQuartile != null && - point.upperQuartile! < point.lowerQuartile!) - ? point.upperQuartile! - : point.lowerQuartile!; - value2 = (point.upperQuartile != null && - point.lowerQuartile != null && - point.upperQuartile! > point.lowerQuartile!) - ? point.upperQuartile! - : point.lowerQuartile!; - } - // ignore: prefer_final_locals - List labelList = []; - // ignore: prefer_final_locals - String label = point.dataLabelMapper ?? - point.label ?? - _getLabelText( - isRangeSeries - ? (!inversed ? point.high : point.low) - : isBoxSeries - ? (!inversed ? point.maximum : point.minimum) - : ((dataLabel.showCumulativeValues && - point.cumulativeValue != null) - ? point.cumulativeValue - : point.yValue), - seriesRendererDetails); - if (isRangeSeries) { - point.label2 = point.dataLabelMapper ?? - point.label2 ?? - _getLabelText( - !inversed ? point.low : point.high, seriesRendererDetails); - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') == true) { - point.label3 = point.dataLabelMapper ?? - point.label3 ?? - _getLabelText( - (point.open > point.close) == true - ? !inversed - ? point.close - : point.open - : !inversed - ? point.open - : point.close, - seriesRendererDetails); - point.label4 = point.dataLabelMapper ?? - point.label4 ?? - _getLabelText( - (point.open > point.close) == true - ? !inversed - ? point.open - : point.close - : !inversed - ? point.close - : point.open, - seriesRendererDetails); - } - } else if (isBoxSeries) { - point.label2 = point.dataLabelMapper ?? - point.label2 ?? - _getLabelText( - !inversed ? point.minimum : point.maximum, seriesRendererDetails); - point.label3 = point.dataLabelMapper ?? - point.label3 ?? - _getLabelText( - point.lowerQuartile! > point.upperQuartile! - ? !inversed - ? point.upperQuartile - : point.lowerQuartile - : !inversed - ? point.lowerQuartile - : point.upperQuartile, - seriesRendererDetails); - point.label4 = point.dataLabelMapper ?? - point.label4 ?? - _getLabelText( - point.lowerQuartile! > point.upperQuartile! - ? !inversed - ? point.lowerQuartile - : point.upperQuartile - : !inversed - ? point.upperQuartile - : point.lowerQuartile, - seriesRendererDetails); - point.label5 = point.dataLabelMapper ?? - point.label5 ?? - _getLabelText(point.median, seriesRendererDetails); - } - DataLabelRenderArgs dataLabelArgs; - TextStyle? dataLabelStyle = dataLabelSettingsRenderer.textStyle; - //ignore: prefer_conditional_assignment - if (dataLabelSettingsRenderer.originalStyle == null) { - dataLabelSettingsRenderer.originalStyle = dataLabel.textStyle; - } - dataLabelStyle = dataLabelSettingsRenderer.originalStyle; - if (chart.onDataLabelRender != null && - seriesRendererDetails.visibleDataPoints![index].labelRenderEvent == - false) { - labelList.add(label); - if (isRangeSeries) { - labelList.add(point.label2!); - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') == true) { - labelList.add(point.label3!); - labelList.add(point.label4!); - } - } else if (isBoxSeries) { - labelList.add(point.label2!); - labelList.add(point.label3!); - labelList.add(point.label4!); - labelList.add(point.label5!); - } - seriesRendererDetails.visibleDataPoints![index].labelRenderEvent = true; - for (int i = 0; i < labelList.length; i++) { - dataLabelArgs = DataLabelRenderArgs( - seriesRendererDetails.series, - seriesRendererDetails.dataPoints, - index, - seriesRendererDetails - .visibleDataPoints![index].overallDataPointIndex); - dataLabelArgs.text = labelList[i]; - dataLabelArgs.textStyle = dataLabelStyle!; - dataLabelArgs.color = - seriesRendererDetails.series.dataLabelSettings.color; - chart.onDataLabelRender!(dataLabelArgs); - labelList[i] = dataLabelArgs.text; - index = dataLabelArgs.viewportPointIndex!; - CartesianPointHelper.setDataLabelTextStyle( - point, dataLabelArgs.textStyle); - CartesianPointHelper.setDataLabelColor(point, dataLabelArgs.color); - dataLabelSettingsRenderer.offset = dataLabelArgs.offset; - } - } - dataLabelSettingsRenderer.textStyle = dataLabelStyle; - if (chart.onDataLabelRender != null) { - dataLabelSettingsRenderer.color = - CartesianPointHelper.getDataLabelColor(point); - dataLabelSettingsRenderer.textStyle = - CartesianPointHelper.getDataLabelTextStyle(point); - dataLabelStyle = dataLabelSettingsRenderer.textStyle; - } - // ignore: unnecessary_null_comparison - if (point != null && - point.isVisible && - point.isGap != true && - (point.y != 0 || dataLabel.showZeroValue)) { - final double markerPointX = dataLabel.builder == null - ? seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - isBoxSeries - ? seriesRendererDetails.stateProperties.requireInvertedAxis == true - ? point.region!.centerRight.dx - : point.region!.topCenter.dx - : point.markerPoint!.x - : templateLocation!.dx; - final double markerPointY = dataLabel.builder == null - ? seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - isBoxSeries - ? seriesRendererDetails.stateProperties.requireInvertedAxis == true - ? point.region!.centerRight.dy - : point.region!.topCenter.dy - : point.markerPoint!.y - : templateLocation!.dy; - final ChartLocation markerPoint2 = calculatePoint( - point.xValue, - seriesRendererDetails.yAxisDetails!.axis.isInversed == true - ? value2 - : value1, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - series, - rect); - final ChartLocation markerPoint3 = calculatePoint( - point.xValue, - seriesRendererDetails.yAxisDetails!.axis.isInversed == true - ? value1 - : value2, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - series, - rect); - final TextStyle font = (dataLabelSettingsRenderer.textStyle == null) - ? const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12) - : dataLabelStyle!; - point.label = labelList.isNotEmpty ? labelList[0] : label; - if (point.label != null) { - ChartLocation? chartLocation, - chartLocation2, - chartLocation3, - chartLocation4, - chartLocation5; - textSize = dataLabel.builder == null - ? measureText(point.label!, font) - : templateSize!; - chartLocation = ChartLocation(markerPointX, markerPointY); - if (isRangeSeries || isBoxSeries) { - point.label2 = labelList.isNotEmpty ? labelList[1] : point.label2; - textSize2 = dataLabel.builder == null - ? measureText(point.label2!, font) - : templateSize!; - chartLocation2 = ChartLocation( - dataLabel.builder == null - ? seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - isBoxSeries - ? seriesRendererDetails - .stateProperties.requireInvertedAxis == - true - ? point.region!.centerLeft.dx - : point.region!.bottomCenter.dx - : point.markerPoint2!.x - : templateLocation!.dx, - dataLabel.builder == null - ? seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - isBoxSeries - ? seriesRendererDetails - .stateProperties.requireInvertedAxis == - true - ? point.region!.centerLeft.dy - : point.region!.bottomCenter.dy - : point.markerPoint2!.y - : templateLocation!.dy); - if (isBoxSeries) { - if (seriesRendererDetails.stateProperties.requireInvertedAxis == - false) { - chartLocation.y = chartLocation.y - boxPlotPadding; - chartLocation2.y = chartLocation2.y + boxPlotPadding; - } else { - chartLocation.x = chartLocation.x + boxPlotPadding; - chartLocation2.x = chartLocation2.x - boxPlotPadding; - } - } - } - final List alignedLabelLocations = - _getAlignedLabelLocations(stateProperties, seriesRendererDetails, - point, dataLabel, chartLocation, chartLocation2, textSize); - chartLocation = alignedLabelLocations[0]; - chartLocation2 = alignedLabelLocations[1]; - if (seriesRendererDetails.seriesType.contains('column') == false && - seriesRendererDetails.seriesType.contains('waterfall') == false && - seriesRendererDetails.seriesType.contains('bar') == false && - seriesRendererDetails.seriesType.contains('histogram') == false && - seriesRendererDetails.seriesType.contains('rangearea') == false && - seriesRendererDetails.seriesType.contains('hilo') == false && - seriesRendererDetails.seriesType.contains('candle') == false && - !isBoxSeries) { - chartLocation!.y = _calculatePathPosition( - chartLocation.y, - dataLabel.labelAlignment, - textSize, - dataLabel.borderWidth, - seriesRendererDetails, - index, - transposed, - chartLocation, - stateProperties, - point, - Size( - series.markerSettings.isVisible - ? series.markerSettings.width / 2 - : 0, - series.markerSettings.isVisible - ? series.markerSettings.height / 2 - : 0)); - } else { - final List _locations = _getLabelLocations( - index, - stateProperties, - seriesRendererDetails, - point, - dataLabel, - chartLocation, - chartLocation2, - textSize, - textSize2); - chartLocation = _locations[0]; - chartLocation2 = _locations[1]; - } - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') == true || - isBoxSeries) { - if (!isBoxSeries) { - point.label3 = labelList.isNotEmpty ? labelList[2] : point.label3; - point.label4 = labelList.isNotEmpty ? labelList[3] : point.label4; - // point.label3 = point.dataLabelMapper ?? - // _getLabelText( - // (point.open > point.close) == true - // ? !inversed - // ? point.close - // : point.open - // : !inversed - // ? point.open - // : point.close, - // seriesRenderer); - // point.label4 = point.dataLabelMapper ?? - // _getLabelText( - // (point.open > point.close) == true - // ? !inversed - // ? point.open - // : point.close - // : !inversed - // ? point.close - // : point.open, - // seriesRenderer); - } else { - point.label3 = labelList.isNotEmpty ? labelList[2] : point.label3; - point.label4 = labelList.isNotEmpty ? labelList[3] : point.label4; - point.label5 = labelList.isNotEmpty ? labelList[4] : point.label5; - // point.label3 = point.dataLabelMapper ?? - // _getLabelText( - // point.lowerQuartile! > point.upperQuartile! - // ? !inversed - // ? point.upperQuartile - // : point.lowerQuartile - // : !inversed - // ? point.lowerQuartile - // : point.upperQuartile, - // seriesRenderer); - // point.label4 = point.dataLabelMapper ?? - // _getLabelText( - // point.lowerQuartile! > point.upperQuartile! - // ? !inversed - // ? point.lowerQuartile - // : point.upperQuartile - // : !inversed - // ? point.upperQuartile - // : point.lowerQuartile, - // seriesRenderer); - // point.label5 = point.dataLabelMapper ?? - // _getLabelText(point.median, seriesRenderer); - } - textSize3 = dataLabel.builder == null - ? measureText(point.label3!, font) - : templateSize; - if (seriesRendererDetails.seriesType.contains('hilo') == true) { - chartLocation3 = (point.open > point.close) == true - ? ChartLocation(point.centerClosePoint!.x + textSize3!.width, - point.closePoint!.y) - : ChartLocation(point.centerOpenPoint!.x - textSize3!.width, - point.openPoint!.y); - } else if (seriesRendererDetails.seriesType == 'candle' && - seriesRendererDetails.stateProperties.requireInvertedAxis == true) { - chartLocation3 = (point.open > point.close) == true - ? ChartLocation(point.closePoint!.x, markerPoint2.y + 1) - : ChartLocation(point.openPoint!.x, markerPoint2.y + 1); - } else if (isBoxSeries) { - chartLocation3 = (seriesRendererDetails - .stateProperties.requireInvertedAxis == - true) - ? ChartLocation(point.lowerQuartilePoint!.x + boxPlotPadding, - markerPoint2.y + 1) - : ChartLocation( - point.region!.topCenter.dx, markerPoint2.y - boxPlotPadding); - } else { - chartLocation3 = - ChartLocation(point.region!.topCenter.dx, markerPoint2.y); - } - textSize4 = dataLabel.builder == null - ? measureText(point.label4!, font) - : templateSize; - if (seriesRendererDetails.seriesType.contains('hilo') == true) { - chartLocation4 = (point.open > point.close) == true - ? ChartLocation(point.centerOpenPoint!.x - textSize4!.width, - point.openPoint!.y) - : ChartLocation(point.centerClosePoint!.x + textSize4!.width, - point.closePoint!.y); - } else if (seriesRendererDetails.seriesType == 'candle' && - seriesRendererDetails.stateProperties.requireInvertedAxis == true) { - chartLocation4 = (point.open > point.close) == true - ? ChartLocation(point.openPoint!.x, markerPoint3.y + 1) - : ChartLocation(point.closePoint!.x, markerPoint3.y + 1); - } else if (isBoxSeries) { - chartLocation4 = - (seriesRendererDetails.stateProperties.requireInvertedAxis == - true) - ? ChartLocation(point.upperQuartilePoint!.x - boxPlotPadding, - markerPoint3.y + 1) - : ChartLocation(point.region!.bottomCenter.dx, - markerPoint3.y + boxPlotPadding); - } else { - chartLocation4 = - ChartLocation(point.region!.bottomCenter.dx, markerPoint3.y + 1); - } - if (isBoxSeries) { - textSize5 = measureText(point.label5!, font); - chartLocation5 = - (seriesRendererDetails.stateProperties.requireInvertedAxis == - false) - ? ChartLocation( - point.centerMedianPoint!.x, point.centerMedianPoint!.y) - : ChartLocation( - point.centerMedianPoint!.x, point.centerMedianPoint!.y); - } - final List alignedLabelLocations2 = - _getAlignedLabelLocations(stateProperties, seriesRendererDetails, - point, dataLabel, chartLocation3, chartLocation4, textSize3!); - chartLocation3 = alignedLabelLocations2[0]; - chartLocation4 = alignedLabelLocations2[1]; - final List _locations = _getLabelLocations( - index, - stateProperties, - seriesRendererDetails, - point, - dataLabel, - chartLocation3, - chartLocation4, - textSize3, - textSize4!); - chartLocation3 = _locations[0]; - chartLocation4 = _locations[1]; - } - _calculateDataLabelRegion( - point, - dataLabel, - stateProperties, - chartLocation!, - chartLocation2, - isRangeSeries, - clipRect, - textSize, - textSize2, - chartLocation3, - chartLocation4, - chartLocation5, - textSize3, - textSize4, - textSize5, - seriesRendererDetails, - index); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart deleted file mode 100644 index f52101e4e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart +++ /dev/null @@ -1,546 +0,0 @@ -import 'package:flutter/material.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Customizes the interactive tooltip. -/// -/// Shows the information about the segments. To enable the interactive tooltip, set that [enable] property to true. -/// -/// By using this,to customize the [color], [borderWidth], [borderRadius], -/// [format] and so on. -/// -/// _Note:_ Intereactive tooltip applicable for axis types and trackball. - -@immutable -class InteractiveTooltip { - /// Creating an argument constructor of InteractiveTooltip class. - const InteractiveTooltip( - {this.enable = true, - this.color, - this.borderColor, - this.borderWidth = 0, - this.borderRadius = 5, - this.arrowLength = 7, - this.arrowWidth = 5, - this.format, - this.connectorLineColor, - this.connectorLineWidth = 1.5, - this.connectorLineDashArray, - this.decimalPlaces = 3, - 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. - /// - /// This tooltip will be displayed at the axis for crosshair and - /// will be displayed near to the trackline for trackball. - /// - /// Defaults to `true`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip(enable: false) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final bool enable; - - /// Color of the interactive tooltip. - /// - /// Used to change the [color] of the tooltip text. - /// - /// Defaults to `null`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// color: Colors.grey - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final Color? color; - - /// Border color of the interactive tooltip. - /// - /// Used to change the stroke color of the axis tooltip. - /// - /// Defaults to `Colors.black`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final Color? borderColor; - - /// Border width of the interactive tooltip. - /// - /// Used to change the stroke width of the axis tooltip. - /// - /// Defaults to `0`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final double borderWidth; - - /// Customizes the text in the interactive tooltip. - /// - /// Used to change the text color, size, font family, font style, etc. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// textStyle: TextStyle( - /// fontSize: 14 - /// ) - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final TextStyle textStyle; - - /// Customizes the corners of the interactive tooltip. - /// - /// Each corner can be customized with a desired value or a single value. - /// - /// Defaults to `Radius.zero`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// borderColor: Colors.red, - /// borderWidth: 3, - /// borderRadius: 2 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final double borderRadius; - - /// It Specifies the length of the tooltip. - /// - /// Defaults to `7`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// arrowLength: 5 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior:trackballBehavior - /// ); - /// } - ///``` - final double arrowLength; - - /// It specifies the width of the tooltip arrow. - /// - /// Defaults to `5`. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// arrowWidth: 4 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final double arrowWidth; - - /// Text format of the interactive tooltip. - /// - /// By default, axis value will be displayed in the tooltip, and it can be customized by - /// adding desired text as prefix or suffix. - /// - ///```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// tooltipSettings: InteractiveTooltip( - /// format: 'point.y %' - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final String? format; - - /// Width of the selection zooming tooltip connector line. - /// - /// Defaults to `1.5`. - /// - ///```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior: ZoomPanBehavior( - /// enableSelectionZooming: true, - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior, - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip( - /// connectorLineWidth: 2 - /// ) - /// ), - /// ); - /// } - ///``` - final double connectorLineWidth; - - /// Color of the selection zooming tooltip connector line. - /// - ///```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior: ZoomPanBehavior( - /// enableSelectionZooming: true, - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior, - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip( - /// connectorLineColor: Colors.red - /// ) - /// ), - /// ); - /// } - ///``` - final Color? connectorLineColor; - - /// Giving dash array to the selection zooming tooltip connector line. - /// - ///```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior: ZoomPanBehavior( - /// enableSelectionZooming: true, - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior, - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip( - /// connectorLineDashArray: [10,10] - /// ) - /// ), - /// ); - /// } - ///``` - final List? connectorLineDashArray; - - /// Rounding decimal places of the selection zooming tooltip or trackball tooltip label. - /// - /// Defaults to `3`. - /// - ///```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior: ZoomPanBehavior( - /// enableSelectionZooming: true, - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior, - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip( - /// decimalPlaces: 4 - /// ) - /// ), - /// ); - /// } - ///``` - 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 - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior: ZoomPanBehavior( - /// enableSelectionZooming: true, - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior, - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip( - /// canShowMarker: false - /// ) - /// ), - /// ); - /// } - ///``` - final bool canShowMarker; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is InteractiveTooltip && - other.enable == enable && - other.color == color && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.borderRadius == borderRadius && - other.arrowLength == arrowLength && - other.arrowWidth == arrowWidth && - other.format == format && - other.connectorLineColor == connectorLineColor && - other.connectorLineWidth == connectorLineWidth && - other.connectorLineDashArray == connectorLineDashArray && - other.decimalPlaces == decimalPlaces && - other.canShowMarker == canShowMarker && - other.textStyle == textStyle; - } - - @override - int get hashCode { - final List values = [ - enable, - color, - borderColor, - borderWidth, - borderRadius, - arrowLength, - arrowWidth, - format, - connectorLineColor, - connectorLineWidth, - connectorLineDashArray, - decimalPlaces, - canShowMarker, - textStyle - ]; - return hashList(values); - } -} - -/// Represents the class of chart point info -class ChartPointInfo { - /// Marker x position - double? markerXPos; - - /// Marker y position - double? markerYPos; - - /// label for trackball and cross hair - String? label; - - /// Data point index - int? dataPointIndex; - - /// Instance of chart series - CartesianSeries? series; - - /// Instance of SeriesRendererDetails - SeriesRendererDetails? seriesRendererDetails; - - /// Chart data point - CartesianChartPoint? chartDataPoint; - - /// X position of the label - double? xPosition; - - /// Y position of the label - double? yPosition; - - /// Color of the segment - Color? color; - - /// header text - String? header; - - /// Low Y position of financial series - double? lowYPosition; - - /// High X position of financial series - double? highXPosition; - - /// High Y position of financial series - double? highYPosition; - - /// Open y position of financial series - double? openYPosition; - - /// close y position of financial series - double? closeYPosition; - - /// open x position of financial series - double? openXPosition; - - /// close x position of financial series - double? closeXPosition; - - /// Minimum Y position of box plot series - double? minYPosition; - - /// Maximum Y position of box plot series - double? maxYPosition; - - /// Lower y position of box plot series - double? lowerXPosition; - - /// Upper y position of box plot series - double? upperXPosition; - - /// Lower x position of box plot series - double? lowerYPosition; - - /// Upper x position of box plot series - double? upperYPosition; - - /// Maximum x position for box plot series - double? maxXPosition; - - /// series index value - late int seriesIndex; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart deleted file mode 100644 index d8edd0fdc..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart +++ /dev/null @@ -1,594 +0,0 @@ -import 'dart:ui' as dart_ui; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import './../../common/event_args.dart' show MarkerRenderArgs; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/waterfall_series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/data_label_renderer.dart'; -import '../utils/helper.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// 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 [isVisible] property of [MarkerSettings]. -/// -/// Provides the options of [color], border width, border color and [shape] of the marker to customize the appearance. -/// -@immutable -class MarkerSettings { - /// Creating an argument constructor of MarkerSettings class. - const MarkerSettings( - {bool? isVisible, - double? height = 8, - double? width = 8, - this.color, - DataMarkerType? shape, - double? borderWidth, - this.borderColor, - this.image}) - : isVisible = isVisible ?? false, - height = height ?? 8, - width = width ?? 8, - shape = shape ?? DataMarkerType.circle, - borderWidth = borderWidth ?? 2; - - /// Toggles the visibility of the marker. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings(isVisible: true), - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isVisible; - - /// Height of the marker shape. - /// - /// Defaults to `4`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// height: 6 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final double height; - - /// Width of the marker shape. - /// - /// Defaults to `4`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// width: 10 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final double width; - - /// Color of the marker shape. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// color: Colors.red - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? color; - - /// Shape of the marker. - /// - /// Defaults to `DataMarkerType.circle`. - /// - /// Also refer [DataMarkerType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// shape: DataMarkerType.rectangle - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final DataMarkerType shape; - - /// Border color of the marker. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? borderColor; - - /// Border width of the marker. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final double borderWidth; - - /// Image to be used as marker. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings( - /// isVisible: true, - /// shape: DataMarkerType.rectangle, - /// image: const AssetImage('images/bike.png') - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final ImageProvider? image; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MarkerSettings && - other.isVisible == isVisible && - other.height == height && - other.width == width && - other.color == color && - other.shape == shape && - other.borderWidth == borderWidth && - other.borderColor == borderColor && - other.image == image; - } - - @override - int get hashCode { - final List values = [ - isVisible, - height, - width, - color, - shape, - borderWidth, - borderColor, - image - ]; - return hashList(values); - } -} - -/// To hold the individual point's marker details for the onMarkerRender event. -class MarkerDetails { - /// Creates an argument constructor for MarkerDetails class. - const MarkerDetails( - {this.markerType, - this.color, - this.borderColor, - this.borderWidth, - this.size}); - - /// Shape of the marker which is obtained from callback. - final DataMarkerType? markerType; - - /// Color of the marker which is obtained from callback. - final Color? color; - - /// Border color of the marker which is obtained from callback. - final Color? borderColor; - - /// Border width of the marker which is obtained from callback. - final double? borderWidth; - - /// Size of the marker which is obtained from callback. - final Size? size; -} - -/// Marker settings renderer class for mutable fields and methods. -class MarkerSettingsRenderer { - /// Creates an argument constructor for MarkerSettings renderer class. - MarkerSettingsRenderer(this.markerSettings) { - color = markerSettings.color; - - borderColor = markerSettings.borderColor; - - borderWidth = markerSettings.borderWidth; - } - - /// Holds the marker settings value. - final MarkerSettings markerSettings; - - /// Holds the color value. - // ignore: prefer_final_fields - Color? color; - - /// Holds the value of border color - Color? borderColor; - - /// Holds the value of border width. - late double borderWidth; - - /// Holds the value of image. - dart_ui.Image? image; - - /// Specifies the image drawn in the marker or not. - bool isImageDrawn = false; - - /// To paint the marker here. - void renderMarker( - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - Animation? animationController, - Canvas canvas, - int markerIndex, - [int? outlierIndex]) { - final bool isDataPointVisible = isLabelWithinRange( - seriesRendererDetails, seriesRendererDetails.dataPoints[markerIndex]); - Paint strokePaint, fillPaint; - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - Size size = Size(series.markerSettings.width, series.markerSettings.height); - CartesianChartPoint point; - DataMarkerType markerType = series.markerSettings.shape; - Color? seriesColor = seriesRendererDetails.seriesColor; - point = seriesRendererDetails.dataPoints[markerIndex]; - if (seriesRendererDetails.seriesType == 'waterfall') { - seriesColor = getWaterfallSeriesColor( - seriesRendererDetails.series as WaterfallSeries, - point, - seriesColor); - } - MarkerRenderArgs? event; - borderColor = series.markerSettings.borderColor ?? seriesColor; - color = series.markerSettings.color; - borderWidth = series.markerSettings.borderWidth; - final bool isMarkerEventTriggered = - CartesianPointHelper.getIsMarkerEventTriggered(point); - if (isDataPointVisible && - seriesRendererDetails.chart.onMarkerRender != null && - seriesRendererDetails.isMarkerRenderEvent == false) { - if (animationController == null || - ((animationController.value == 0.0 && - !isMarkerEventTriggered && - animationController.status == AnimationStatus.forward) || - (seriesRendererDetails - .animationController.duration!.inMilliseconds == - 0 && - !isMarkerEventTriggered))) { - CartesianPointHelper.setIsMarkerEventTriggered(point, true); - event = triggerMarkerRenderEvent( - seriesRendererDetails, - size, - markerType, - seriesRendererDetails.dataPoints[markerIndex].visiblePointIndex!, - animationController)!; - markerType = event.shape; - borderColor = event.borderColor; - color = event.color; - borderWidth = event.borderWidth; - size = Size(event.markerHeight, event.markerWidth); - CartesianPointHelper.setMarkerDetails( - point, - MarkerDetails( - markerType: markerType, - borderColor: borderColor, - color: color, - borderWidth: borderWidth, - size: size)); - } - } - final bool hasPointColor = series.pointColorMapper != null; - final bool isBoxSeries = - seriesRendererDetails.seriesType.contains('boxandwhisker'); - final double opacity = (animationController != null && - (seriesRendererDetails - .stateProperties.renderingDetails.initialRender! == - true || - seriesRendererDetails.needAnimateSeriesElements == true)) - ? animationController.value - : 1; - final MarkerDetails? pointMarkerDetails = - CartesianPointHelper.getMarkerDetails(point); - !isBoxSeries - ? seriesRendererDetails.markerShapes.add(isDataPointVisible - ? getMarkerShapesPath( - pointMarkerDetails?.markerType ?? markerType, - Offset(point.markerPoint!.x, point.markerPoint!.y), - pointMarkerDetails?.size ?? size, - seriesRendererDetails, - markerIndex, - null, - animationController) - : null) - : seriesRendererDetails.markerShapes.add(isDataPointVisible - ? getMarkerShapesPath( - pointMarkerDetails?.markerType ?? markerType, - Offset(point.outliersPoint[outlierIndex!].x, - point.outliersPoint[outlierIndex].y), - pointMarkerDetails?.size ?? size, - seriesRendererDetails, - markerIndex, - null, - animationController) - : null); - if (seriesRendererDetails.seriesType.contains('range') == true) { - seriesRendererDetails.markerShapes2.add(isDataPointVisible - ? getMarkerShapesPath( - pointMarkerDetails?.markerType ?? markerType, - Offset(point.markerPoint2!.x, point.markerPoint2!.y), - pointMarkerDetails?.size ?? size, - seriesRendererDetails, - markerIndex, - null, - animationController) - : null); - } - strokePaint = getStrokePaint( - point, - series, - pointMarkerDetails, - opacity, - hasPointColor, - seriesColor, - markerType, - seriesRendererDetails, - animationController, - size); - - fillPaint = getFillPaint( - point, series, seriesRendererDetails, pointMarkerDetails, opacity); - final bool isScatter = seriesRendererDetails.seriesType == 'scatter'; - final Rect axisClipRect = - seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - - // Render marker points. - if ((series.markerSettings.isVisible || isScatter || isBoxSeries) && - point.isVisible && - withInRect(seriesRendererDetails, point.markerPoint, axisClipRect) && - (point.markerPoint != null || - // ignore: unnecessary_null_comparison - point.outliersPoint[outlierIndex!] != null) && - point.isGap != true && - (!isScatter || series.markerSettings.shape == DataMarkerType.image) && - seriesRendererDetails - .markerShapes[isBoxSeries ? outlierIndex! : markerIndex] != - null) { - seriesRendererDetails.renderer.drawDataMarker( - isBoxSeries ? outlierIndex! : markerIndex, - canvas, - fillPaint, - strokePaint, - isBoxSeries - ? point.outliersPoint[outlierIndex!].x - : point.markerPoint!.x, - isBoxSeries - ? point.outliersPoint[outlierIndex!].y - : point.markerPoint!.y, - seriesRendererDetails.renderer); - if (series.markerSettings.shape == DataMarkerType.image) { - drawImageMarker( - seriesRendererDetails, - canvas, - isBoxSeries - ? point.outliersPoint[outlierIndex!].x - : point.markerPoint!.x, - isBoxSeries - ? point.outliersPoint[outlierIndex!].y - : point.markerPoint!.y); - if (seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType == 'hilo') { - drawImageMarker(seriesRendererDetails, canvas, point.markerPoint2!.x, - point.markerPoint2!.y); - } - } - } - setMarkerEventTrigged(point, seriesRendererDetails, animationController); - } - - /// Method to set whether the marker is triggered - void setMarkerEventTrigged( - CartesianChartPoint point, - SeriesRendererDetails seriesRendererDetails, - Animation? animationController, - ) { - if (seriesRendererDetails.chart.onMarkerRender != null && - seriesRendererDetails.isMarkerRenderEvent == false) { - if (animationController == null || - ((animationController.value == 1.0 && - animationController.status == AnimationStatus.completed) || - (seriesRendererDetails - .animationController.duration!.inMilliseconds == - 0))) { - CartesianPointHelper.setIsMarkerEventTriggered(point, false); - } - } - } - - /// To get the marker fill paint - Paint getFillPaint( - CartesianChartPoint point, - XyDataSeries series, - SeriesRendererDetails seriesRendererDetails, - MarkerDetails? pointMarkerDetails, - double opacity) { - return Paint() - ..color = point.isEmpty == true - ? series.emptyPointSettings.color - : color != Colors.transparent - ? (pointMarkerDetails?.color ?? - color ?? - (seriesRendererDetails.stateProperties.renderingDetails - .chartTheme.brightness == - Brightness.light - ? Colors.white - : Colors.black)) - .withOpacity(opacity) - : color! - ..style = PaintingStyle.fill; - } - - /// To get the marker stroke paint - Paint getStrokePaint( - CartesianChartPoint point, - XyDataSeries series, - MarkerDetails? pointMarkerDetails, - double opacity, - bool hasPointColor, - Color? seriesColor, - DataMarkerType markerType, - SeriesRendererDetails seriesRendererDetails, - Animation? animationController, - Size size) { - Paint strokePaint = Paint() - ..color = point.isEmpty == true - ? (series.emptyPointSettings.borderWidth == 0 - ? Colors.transparent - : series.emptyPointSettings.borderColor.withOpacity(opacity)) - : (series.markerSettings.borderWidth == 0 - ? Colors.transparent - : (pointMarkerDetails?.borderColor != null - ? pointMarkerDetails!.borderColor!.withOpacity(opacity) - : (hasPointColor && point.pointColorMapper != null) - ? point.pointColorMapper!.withOpacity(opacity) - : (borderColor != null - ? borderColor!.withOpacity(opacity) - : seriesColor!.withOpacity(opacity)))) - ..style = PaintingStyle.stroke - ..strokeWidth = point.isEmpty == true - ? series.emptyPointSettings.borderWidth - : pointMarkerDetails?.borderWidth ?? borderWidth; - - if (series.gradient != null && - series.markerSettings.borderColor == null && - ((pointMarkerDetails == null) || - (pointMarkerDetails != null && - pointMarkerDetails.borderColor == null))) { - strokePaint = getLinearGradientPaint( - series.gradient!, - getMarkerShapesPath( - pointMarkerDetails?.markerType ?? markerType, - Offset(point.markerPoint!.x, point.markerPoint!.y), - pointMarkerDetails?.size ?? size, - seriesRendererDetails, - null, - null, - animationController) - .getBounds(), - seriesRendererDetails.stateProperties.requireInvertedAxis); - strokePaint.style = PaintingStyle.stroke; - strokePaint.strokeWidth = point.isEmpty == true - ? series.emptyPointSettings.borderWidth - : pointMarkerDetails?.borderWidth ?? - series.markerSettings.borderWidth; - } - - return strokePaint; - } - - /// To determine if the marker is within axis clip rect. - bool withInRect(SeriesRendererDetails seriesRendererDetails, - ChartLocation? markerPoint, Rect axisClipRect) { - bool withInRect = false; - - withInRect = markerPoint != null && - markerPoint.x >= axisClipRect.left && - markerPoint.x <= axisClipRect.right && - markerPoint.y <= axisClipRect.bottom && - markerPoint.y >= axisClipRect.top; - - return withInRect; - } - - /// Paint the image marker. - void drawImageMarker(SeriesRendererDetails seriesRendererDetails, - Canvas canvas, double pointX, double pointY) { - if (seriesRendererDetails.markerSettingsRenderer!.image != null) { - final double imageWidth = - 2 * seriesRendererDetails.series.markerSettings.width; - final double imageHeight = - 2 * seriesRendererDetails.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); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart deleted file mode 100644 index 3450786d2..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart +++ /dev/null @@ -1,1100 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart' show DateFormat; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/error_bar_series.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; - -import '../../common/event_args.dart' show ErrorBarValues; -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/data_label.dart'; -import '../common/marker.dart'; -import '../series_painter/histogram_painter.dart'; -import '../trendlines/trendlines.dart'; -import '../utils/helper.dart'; -import 'cartesian_state_properties.dart'; -import 'trackball_marker_settings.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Represents the data label renderer class. -// ignore: must_be_immutable -class DataLabelRenderer extends StatefulWidget { - /// Creates an instance of data label renderer. - // ignore: prefer_const_constructors_in_immutables - DataLabelRenderer({required this.stateProperties, required this.show}); - - /// Holds the value of state properties. - final CartesianStateProperties stateProperties; - - /// Specifies whether to show the data labels. - bool show; - - /// Specifies the data label renderer state. - // ignore: library_private_types_in_public_api - _DataLabelRendererState? state; - - @override - // ignore: no_logic_in_create_state - State createState() { - state = _DataLabelRendererState(); - return state!; - } -} - -class _DataLabelRendererState extends State - with SingleTickerProviderStateMixin { - // List animationControllersList; - - /// Animation controller for series. - late AnimationController animationController; - - /// Repaint notifier for crosshair container. - late ValueNotifier dataLabelRepaintNotifier; - - @override - void initState() { - dataLabelRepaintNotifier = ValueNotifier(0); - animationController = AnimationController(vsync: this) - ..addListener(repaintDataLabelElements); - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - animationController.duration = Duration( - milliseconds: - widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); - final Animation dataLabelAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), - )); - - animationController.forward(from: 0.0); - // ignore: avoid_unnecessary_containers - return Container( - child: CustomPaint( - painter: _DataLabelPainter( - stateProperties: widget.stateProperties, - animation: dataLabelAnimation, - state: this, - animationController: animationController, - notifier: dataLabelRepaintNotifier))); - } - - @override - void dispose() { - // if (animationController != null) { - animationController.removeListener(repaintDataLabelElements); - animationController.dispose(); - // animationController = null; - // } - super.dispose(); - } - - /// To repaint data label elements. - void repaintDataLabelElements() { - dataLabelRepaintNotifier.value++; - } - - void render() { - setState(() { - widget.show = true; - }); - } -} - -class _DataLabelPainter extends CustomPainter { - _DataLabelPainter( - {required this.stateProperties, - required this.state, - required this.animationController, - required this.animation, - required ValueNotifier notifier}) - : super(repaint: notifier); - - final CartesianStateProperties stateProperties; - - final _DataLabelRendererState state; - - final AnimationController animationController; - - final Animation animation; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(stateProperties.chartAxis.axisClipRect); - stateProperties.renderDatalabelRegions = []; - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - SeriesRendererDetails seriesRendererDetails; - for (int i = 0; i < visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[i]; - DataLabelSettingsRenderer dataLabelSettingsRenderer; - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRendererDetails.series.dataLabelSettings.isVisible == true && - (seriesRendererDetails.animationCompleted == true || - seriesRendererDetails.series.animationDuration == 0 || - !stateProperties.renderingDetails.initialRender!) && - (seriesRendererDetails.needAnimateSeriesElements == false || - (stateProperties.seriesDurationFactor < - seriesRendererDetails.animationController.value || - seriesRendererDetails.series.animationDuration == 0) || - (seriesRendererDetails.animationController.status == - AnimationStatus.dismissed)) && - seriesRendererDetails.series.dataLabelSettings.builder == null) { - seriesRendererDetails.dataLabelSettingsRenderer = - DataLabelSettingsRenderer( - seriesRendererDetails.series.dataLabelSettings); - if (seriesRendererDetails.visibleDataPoints != null && - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - for (int j = 0; - j < seriesRendererDetails.visibleDataPoints!.length; - j++) { - if (seriesRendererDetails.visible! == true && - // ignore: unnecessary_null_comparison - seriesRendererDetails.series.dataLabelSettings != null) { - dataLabelSettingsRenderer = - seriesRendererDetails.dataLabelSettingsRenderer; - dataLabelSettingsRenderer.renderDataLabel( - stateProperties, - seriesRendererDetails, - seriesRendererDetails.visibleDataPoints![j], - animation, - canvas, - j, - dataLabelSettingsRenderer); - } - } - } - if (animation.value >= 1) { - seriesRendererDetails.needAnimateSeriesElements = false; - } - } - } - } - - @override - bool shouldRepaint(_DataLabelPainter oldDelegate) => true; -} - -/// Find rect type series region. -void calculateRectSeriesRegion( - CartesianChartPoint point, - int pointIndex, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final num? crossesAt = - getCrossesAtValue(seriesRendererDetails.renderer, stateProperties); - final num sideBySideMinimumVal = - seriesRendererDetails.sideBySideInfo!.minimum; - - final num sideBySideMaximumVal = - seriesRendererDetails.sideBySideInfo!.maximum; - - final num origin = - crossesAt ?? math.max(yAxisDetails.visibleRange!.minimum, 0); - - /// Get the rectangle based on points. - final Rect rect = - (seriesRendererDetails.seriesType.contains('stackedcolumn') == true || - seriesRendererDetails.seriesType.contains('stackedbar') == - true) && - seriesRendererDetails.renderer is StackedSeriesRenderer - ? calculateRectangle( - point.xValue + sideBySideMinimumVal, - seriesRendererDetails.stackingValues[0].endValues[pointIndex], - point.xValue + sideBySideMaximumVal, - crossesAt ?? - seriesRendererDetails - .stackingValues[0].startValues[pointIndex], - seriesRendererDetails.renderer, - stateProperties) - : calculateRectangle( - point.xValue + sideBySideMinimumVal, - seriesRendererDetails.seriesType == 'rangecolumn' - ? point.high - : seriesRendererDetails.seriesType == 'boxandwhisker' - ? point.maximum - : seriesRendererDetails.seriesType == 'waterfall' - ? point.endValue - : point.yValue, - point.xValue + sideBySideMaximumVal, - seriesRendererDetails.seriesType == 'rangecolumn' - ? point.low - : seriesRendererDetails.seriesType == 'boxandwhisker' - ? point.minimum - : seriesRendererDetails.seriesType == 'waterfall' - ? point.originValue - : origin, - seriesRendererDetails.renderer, - stateProperties); - - point.region = rect; - final dynamic series = seriesRendererDetails.series; - - ///Get shadow rect region. - if (seriesRendererDetails.seriesType != 'stackedcolumn100' && - seriesRendererDetails.seriesType != 'stackedbar100' && - seriesRendererDetails.seriesType != 'waterfall' && - series.isTrackVisible == true) { - final Rect shadowPointRect = calculateShadowRectangle( - point.xValue + sideBySideMinimumVal, - seriesRendererDetails.seriesType == 'rangecolumn' - ? point.high - : point.yValue, - point.xValue + sideBySideMaximumVal, - seriesRendererDetails.seriesType == 'rangecolumn' ? point.high : origin, - seriesRendererDetails.renderer, - stateProperties, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - - point.trackerRectRegion = shadowPointRect; - } - if (seriesRendererDetails.seriesType == 'rangecolumn' || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { - point.markerPoint = stateProperties.requireInvertedAxis != true - ? ChartLocation(rect.topCenter.dx, rect.topCenter.dy) - : ChartLocation(rect.centerRight.dx, rect.centerRight.dy); - point.markerPoint2 = stateProperties.requireInvertedAxis != true - ? ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy) - : ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy); - } else { - point.markerPoint = stateProperties.requireInvertedAxis != true - ? (yAxisDetails.axis.isInversed - ? (point.yValue.isNegative == true - ? ChartLocation(rect.topCenter.dx, rect.topCenter.dy) - : ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy)) - : (point.yValue.isNegative == true - ? ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy) - : ChartLocation(rect.topCenter.dx, rect.topCenter.dy))) - : (yAxisDetails.axis.isInversed - ? (point.yValue.isNegative == true - ? ChartLocation(rect.centerRight.dx, rect.centerRight.dy) - : ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy)) - : (point.yValue.isNegative == true - ? ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy) - : ChartLocation(rect.centerRight.dx, rect.centerRight.dy))); - } - if (seriesRendererDetails.seriesType == 'waterfall') { - /// The below values are used to find the chart location of the connector lines of each data point. - point.originValueLeftPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.originValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); - point.originValueRightPoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.originValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); - point.endValueLeftPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.endValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); - point.endValueRightPoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.endValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); - } -} - -/// Calculate scatter, bubble series data points region. -void calculatePointSeriesRegion( - CartesianChartPoint point, - int pointIndex, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - Rect rect) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final CartesianSeries series = seriesRendererDetails.series; - final ChartLocation currentPoint = calculatePoint( - point.xValue, - point.yValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.markerPoint = currentPoint; - if (seriesRendererDetails.seriesType == 'scatter') { - point.region = Rect.fromLTRB( - currentPoint.x - series.markerSettings.width, - currentPoint.y - series.markerSettings.width, - currentPoint.x + series.markerSettings.width, - currentPoint.y + series.markerSettings.width); - } else { - final num bubbleRadius = - calculateBubbleRadius(seriesRendererDetails, series, point); - point.region = Rect.fromLTRB( - currentPoint.x - 2 * bubbleRadius, - currentPoint.y - 2 * bubbleRadius, - currentPoint.x + 2 * bubbleRadius, - currentPoint.y + 2 * bubbleRadius); - } -} - -/// Calculate errorBar series data points region. -void calculateErrorBarSeriesRegion( - CartesianChartPoint point, - int pointIndex, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - Rect rect) { - if (!point.isGap) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final ErrorBarSeries series = - seriesRendererDetails.series as ErrorBarSeries; - final num actualXValue = point.xValue; - final num actualYValue = point.yValue; - final ChartLocation currentPoint = calculatePoint( - actualXValue, - actualYValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.currentPoint = currentPoint; - final ErrorBarValues errorBarValues = point.errorBarValues!; - if (errorBarValues.horizontalPositiveErrorValue != null) { - point.horizontalPositiveErrorPoint = calculatePoint( - errorBarValues.horizontalPositiveErrorValue!, - actualYValue, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - rect); - } - if (errorBarValues.horizontalNegativeErrorValue != null) { - point.horizontalNegativeErrorPoint = calculatePoint( - errorBarValues.horizontalNegativeErrorValue!, - actualYValue, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - rect); - } - if (errorBarValues.verticalPositiveErrorValue != null) { - point.verticalPositiveErrorPoint = calculatePoint( - actualXValue, - errorBarValues.verticalPositiveErrorValue!, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - rect); - } - if (errorBarValues.verticalNegativeErrorValue != null) { - point.verticalNegativeErrorPoint = calculatePoint( - actualXValue, - errorBarValues.verticalNegativeErrorValue, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - rect); - } - } -} - -/// Calculate data point region for path series like line, area, etc., -void calculatePathSeriesRegion( - CartesianChartPoint point, - int pointIndex, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - Rect rect, - double markerHeight, - double markerWidth, - [VisibleRange? sideBySideInfo, - CartesianChartPoint? _nextPoint, - num? midX, - num? midY]) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final num? sideBySideMinimumVal = - seriesRendererDetails.sideBySideInfo?.minimum; - - final num? sideBySideMaximumVal = - seriesRendererDetails.sideBySideInfo?.maximum; - if (seriesRendererDetails.seriesType != 'rangearea' && - seriesRendererDetails.seriesType != 'splinerangearea' && - (seriesRendererDetails.seriesType.contains('hilo') == false) && - (seriesRendererDetails.seriesType.contains('candle') == false) && - seriesRendererDetails.seriesType.contains('boxandwhisker') == false) { - if (seriesRendererDetails.seriesType == 'spline' && - pointIndex <= seriesRendererDetails.dataPoints.length - 2) { - point.controlPoint = seriesRendererDetails.drawControlPoints[pointIndex]; - point.startControl = calculatePoint( - point.controlPoint![0].dx, - point.controlPoint![0].dy, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.endControl = calculatePoint( - point.controlPoint![1].dx, - point.controlPoint![1].dy, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - if (seriesRendererDetails.seriesType == 'splinearea' && - pointIndex != 0 && - pointIndex <= seriesRendererDetails.dataPoints.length - 1) { - point.controlPoint = - seriesRendererDetails.drawControlPoints[pointIndex - 1]; - point.startControl = calculatePoint( - point.controlPoint![0].dx, - point.controlPoint![0].dy, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.endControl = calculatePoint( - point.controlPoint![1].dx, - point.controlPoint![1].dy, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - - if (seriesRendererDetails.seriesType == 'stepline') { - point.currentPoint = calculatePoint( - point.xValue, - point.yValue, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - final ChartLocation currentPoint = - (seriesRendererDetails.seriesType == 'stackedarea' || - seriesRendererDetails.seriesType == 'stackedarea100' || - seriesRendererDetails.seriesType == 'stackedline' || - seriesRendererDetails.seriesType == 'stackedline100') && - seriesRendererDetails.renderer is StackedSeriesRenderer - ? calculatePoint( - point.xValue, - seriesRendererDetails.stackingValues[0].endValues[pointIndex], - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect) - : calculatePoint( - point.xValue, - point.yValue, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.region = Rect.fromLTWH(currentPoint.x - markerWidth, - currentPoint.y - markerHeight, 2 * markerWidth, 2 * markerHeight); - point.markerPoint = currentPoint; - } else { - num? value1, value2; - value1 = (point.low != null && - point.high != null && - (point.low < point.high) == true) - ? point.high - : point.low; - value2 = (point.low != null && - point.high != null && - (point.low > point.high) == true) - ? point.high - : point.low; - if (seriesRendererDetails.seriesType == 'boxandwhisker') { - value1 = (point.minimum != null && - point.maximum != null && - point.minimum! < point.maximum!) - ? point.maximum - : point.minimum; - value2 = (point.minimum != null && - point.maximum != null && - point.minimum! > point.maximum!) - ? point.maximum - : point.minimum; - } - point.markerPoint = calculatePoint( - point.xValue, - yAxisDetails.axis.isInversed ? value2 : value1, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.markerPoint2 = calculatePoint( - point.xValue, - yAxisDetails.axis.isInversed ? value1 : value2, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - if (seriesRendererDetails.seriesType == 'splinerangearea' && - pointIndex != 0 && - pointIndex <= seriesRendererDetails.dataPoints.length - 1) { - point.controlPointshigh = seriesRendererDetails.drawHighControlPoints[ - seriesRendererDetails.dataPoints.indexOf(point) - 1]; - - point.highStartControl = calculatePoint( - point.controlPointshigh![0].dx, - point.controlPointshigh![0].dy, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.highEndControl = calculatePoint( - point.controlPointshigh![1].dx, - point.controlPointshigh![1].dy, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - if (seriesRendererDetails.seriesType == 'splinerangearea' && - pointIndex >= 0 && - pointIndex <= seriesRendererDetails.dataPoints.length - 2) { - point.controlPointslow = seriesRendererDetails.drawLowControlPoints[ - seriesRendererDetails.dataPoints.indexOf(point)]; - - point.lowStartControl = calculatePoint( - point.controlPointslow![0].dx, - point.controlPointslow![0].dy, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.lowEndControl = calculatePoint( - point.controlPointslow![1].dx, - point.controlPointslow![1].dy, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - - if (seriesRendererDetails.seriesType == 'hilo' && - point.low != null && - point.high != null) { - point.lowPoint = calculatePoint( - point.xValue, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.highPoint = calculatePoint( - point.xValue, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } else if ((seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType == 'candle') && - point.open != null && - point.close != null && - point.low != null && - point.high != null) { - final num center = (point.xValue + sideBySideMinimumVal) + - (((point.xValue + sideBySideMaximumVal) - - (point.xValue + sideBySideMinimumVal)) / - 2); - point.openPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.open, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.closePoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.close, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - if (seriesRendererDetails.series.dataLabelSettings.isVisible == true) { - point.centerOpenPoint = calculatePoint( - point.xValue, - point.open, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.centerClosePoint = calculatePoint( - point.xValue, - point.close, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - - point.centerHighPoint = calculatePoint( - center, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.centerLowPoint = calculatePoint( - center, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.lowPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - point.highPoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - - if (seriesRendererDetails.seriesType.contains('boxandwhisker') == true && - point.minimum != null && - point.maximum != null && - point.upperQuartile != null && - point.lowerQuartile != null && - point.median != null) { - final num center = (point.xValue + sideBySideMinimumVal) + - (((point.xValue + sideBySideMaximumVal) - - (point.xValue + sideBySideMinimumVal)) / - 2); - - point.centerMeanPoint = calculatePoint( - center, - point.mean, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.minimumPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.minimum, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.maximumPoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.maximum, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.centerMinimumPoint = calculatePoint( - center, - point.minimum, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.centerMaximumPoint = calculatePoint( - center, - point.maximum, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.lowerQuartilePoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.lowerQuartile, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.upperQuartilePoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.upperQuartile, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.medianPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.median, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - - point.centerMedianPoint = calculatePoint( - center, - point.median, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - } - point.region = seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true - ? !stateProperties.requireInvertedAxis - ? Rect.fromLTWH( - point.markerPoint!.x, - point.markerPoint!.y, - seriesRendererDetails.series.borderWidth, - point.markerPoint2!.y - point.markerPoint!.y) - : Rect.fromLTWH( - point.markerPoint2!.x, - point.markerPoint2!.y, - (point.markerPoint!.x - point.markerPoint2!.x).abs(), - seriesRendererDetails.series.borderWidth) - : Rect.fromLTRB( - point.markerPoint!.x - markerWidth, - point.markerPoint!.y - markerHeight, - point.markerPoint!.x + markerWidth, - point.markerPoint2!.y); - if (seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { - point.boxRectRegion = calculateRectangle( - point.xValue + sideBySideMinimumVal, - point.upperQuartile!, - point.xValue + sideBySideMaximumVal, - point.lowerQuartile!, - seriesRendererDetails.renderer, - stateProperties); - } - } -} - -/// Finding outliers region. -void calculateOutlierRegion(CartesianChartPoint point, - ChartLocation outlierPosition, num outlierWidth) { - point.outlierRegion!.add(Rect.fromLTRB( - outlierPosition.x - outlierWidth, - outlierPosition.y - outlierWidth, - outlierPosition.x + outlierWidth, - outlierPosition.y + outlierWidth)); -} - -/// Finding tooltip region. -void calculateTooltipRegion( - CartesianChartPoint point, - int seriesIndex, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - [Trendline? trendline, - TrendlineRenderer? trendlineRenderer, - int? trendlineIndex]) { - final SfCartesianChart chart = stateProperties.chart; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final CartesianSeries series = seriesRendererDetails.series; - final num? crossesAt = - getCrossesAtValue(seriesRendererDetails.renderer, stateProperties); - // ignore: unnecessary_null_comparison - if ((series.enableTooltip != null || - // ignore: unnecessary_null_comparison - seriesRendererDetails.chart.trackballBehavior != null || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null) && - (series.enableTooltip || - seriesRendererDetails.chart.trackballBehavior.enable == true || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null) && - // ignore: unnecessary_null_comparison - point != null && - !point.isGap && - !point.isDrop && - seriesRendererDetails.regionalData != null) { - bool isTrendline = false; - if (trendline != null) { - isTrendline = true; - } - final List regionData = []; - num binWidth = 0; - String? date; - final List regionRect = []; - if (seriesRendererDetails.renderer is HistogramSeriesRenderer) { - binWidth = seriesRendererDetails.histogramValues.binWidth!; - } - if (xAxisDetails is DateTimeAxisDetails) { - final DateTimeAxis xAxis = xAxisDetails.axis as DateTimeAxis; - final num interval = xAxisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (xAxisDetails.visibleLabels.isNotEmpty) - ? xAxisDetails - .visibleLabels[xAxisDetails.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = xAxis.dateFormat ?? - getDateTimeLabelFormat(xAxisDetails.axisRenderer, interval.toInt(), - prevInterval.toInt()); - date = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); - } else if (xAxisDetails is DateTimeCategoryAxisDetails) { - date = point.x is DateTime - ? xAxisDetails.dateFormat.format(point.x) - : point.x.toString(); - } - xAxisDetails is CategoryAxisDetails - ? regionData.add(point.x.toString()) - : (xAxisDetails is DateTimeAxisDetails || - xAxisDetails is DateTimeCategoryAxisDetails) - ? regionData.add(date!.toString()) - : seriesRendererDetails.seriesType != 'histogram' - ? regionData.add(getLabelValue(point.xValue, xAxisDetails.axis, - chart.tooltipBehavior.decimalPlaces) - .toString()) - : regionData.add( - '${getLabelValue(point.xValue - binWidth / 2, xAxisDetails.axis, chart.tooltipBehavior.decimalPlaces)} - ${getLabelValue(point.xValue + binWidth / 2, xAxisDetails.axis, chart.tooltipBehavior.decimalPlaces)}'); - - if (seriesRendererDetails.seriesType.contains('range') == true && - !isTrendline || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { - if (seriesRendererDetails.seriesType != 'hiloopenclose' && - seriesRendererDetails.seriesType != 'candle' && - seriesRendererDetails.seriesType != 'boxandwhisker') { - regionData.add(point.high.toString()); - regionData.add(point.low.toString()); - } else if (seriesRendererDetails.seriesType != 'boxandwhisker') { - regionData.add(point.high.toString()); - regionData.add(point.low.toString()); - regionData.add(point.open.toString()); - regionData.add(point.close.toString()); - } else { - regionData.add(point.minimum.toString()); - regionData.add(point.maximum.toString()); - regionData.add(point.lowerQuartile.toString()); - regionData.add(point.upperQuartile.toString()); - regionData.add(point.median.toString()); - regionData.add(point.mean.toString()); - } - } else { - regionData.add(point.yValue.toString()); - } - regionData.add(isTrendline - ? trendlineRenderer!.name ?? '' - : series.name ?? 'series $seriesIndex'); - regionRect.add( - seriesRendererDetails.seriesType.contains('boxandwhisker') == true - ? point.boxRectRegion - : point.region); - regionRect.add((seriesRendererDetails.isRectSeries == true) || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true - ? seriesRendererDetails.seriesType == 'column' || - seriesRendererDetails.seriesType.contains('stackedcolumn') == - true || - seriesRendererDetails.seriesType == 'histogram' - ? (point.yValue > (crossesAt ?? 0)) == true - ? point.region!.topCenter - : point.region!.bottomCenter - : seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == - true || - seriesRendererDetails.seriesType - .contains('boxandwhisker') == - true - ? point.region!.topCenter - : point.region!.topCenter - : (seriesRendererDetails.seriesType.contains('rangearea') == true - ? (isTrendline - ? Offset(point.markerPoint!.x, point.markerPoint!.y) - : Offset(point.markerPoint!.x, point.markerPoint!.y)) - : point.region!.center)); - regionRect.add( - isTrendline ? trendlineRenderer!.fillColor : point.pointColorMapper); - regionRect.add(point.bubbleSize); - regionRect.add(point); - regionRect.add(point.outlierRegion); - regionRect.add(point.outlierRegionPosition); - if (seriesRendererDetails.seriesType.contains('stacked') == true) { - regionData.add((point.cumulativeValue).toString()); - } - regionData.add('$isTrendline'); - if (isTrendline) { - regionRect.add(trendline); - } - seriesRendererDetails.regionalData![regionRect] = regionData; - point.regionData = regionData; - } -} - -/// Paint the image marker. -void drawImageMarker(SeriesRendererDetails? seriesRendererDetails, - Canvas canvas, double pointX, double pointY, - [TrackballMarkerSettings? trackballMarkerSettings, - CartesianStateProperties? stateProperties]) { - final MarkerSettingsRenderer? markerSettingsRenderer = - seriesRendererDetails?.markerSettingsRenderer; - - if (seriesRendererDetails != null && markerSettingsRenderer!.image != null) { - final double imageWidth = - 2.0 * seriesRendererDetails.series.markerSettings.width; - final double imageHeight = - 2.0 * seriesRendererDetails.series.markerSettings.height; - final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, - pointY - imageHeight / 2, imageWidth, imageHeight); - paintImage( - canvas: canvas, - rect: positionRect, - image: markerSettingsRenderer.image!, - fit: BoxFit.fill); - } - - if (stateProperties?.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: stateProperties!.trackballMarkerSettingsRenderer.image!, - fit: BoxFit.fill); - } -} - -/// This method is for to calculate and rendering the length and Offsets of the dashed lines. -void drawDashedLine( - Canvas canvas, List dashArray, Paint paint, Path path) { - bool even = false; - for (int i = 1; i < dashArray.length; i = i + 2) { - if (dashArray[i] == 0) { - even = true; - } - } - if (even == false) { - paint.isAntiAlias = false; - canvas.drawPath( - dashPath( - path, - dashArray: CircularIntervalList(dashArray), - )!, - paint); - } else { - canvas.drawPath(path, paint); - } -} - -/// To dispose the old segments. -void disposeOldSegments( - SfCartesianChart chart, SeriesRendererDetails seriesRendererDetails) { - if (!chart.legend.isVisible! && - !seriesRendererDetails.isSelectionEnable && - !chart.zoomPanBehavior.enableDoubleTapZooming && - !chart.zoomPanBehavior.enableMouseWheelZooming && - !chart.zoomPanBehavior.enablePanning && - !chart.zoomPanBehavior.enablePinching && - !chart.zoomPanBehavior.enableSelectionZooming) { - seriesRendererDetails.dispose(); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart deleted file mode 100644 index 30e8d55c9..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'package:flutter/material.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_segment/waterfall_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/waterfall_series.dart'; -import '../chart_series/xy_data_series.dart'; -import 'cartesian_state_properties.dart'; - -/// Represents the segment properties class -class SegmentProperties { - /// Argument constructor for SegmentProperties class - SegmentProperties(this.stateProperties, this.segment); - - ///Color of the segment - Color? color, strokeColor; - - ///Border width of the segment - double? strokeWidth; - - /// Represents the value of chart series - late XyDataSeries series; - - /// Represents the value of old series - XyDataSeries? oldSeries; - - ///Chart series renderer - late CartesianSeriesRenderer seriesRenderer; - - /// Represents the value of old series renderer - CartesianSeriesRenderer? oldSeriesRenderer; - - /// Rectangle of the segment - RRect? segmentRect; - - /// Default fill color & stroke color - Paint? defaultFillColor, defaultStrokeColor; - - /// Represents the old segment index - int? oldSegmentIndex; - - /// Represents the series index - late int seriesIndex; - - /// Represents the current, old and next point - CartesianChartPoint? currentPoint, point, oldPoint, nextPoint; - - /// Old series visibility property. - bool? oldSeriesVisible; - - /// Old rect region. - Rect? oldRegion; - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the segment - final ChartSegment segment; - - /// Represents the value of path - late Path path; - - /// Represents the value of stroke path - Path? strokePath; - - /// Represents the value of path rect - Rect? pathRect; - - /// Represents the track bar rect - late RRect trackBarRect; - - /// Represents the tracker fill paint - Paint? trackerFillPaint; - - /// Represents the tracker stroke paint - Paint? trackerStrokePaint; - - /// Represents the value of x, high and low - late double x, low, high; - - /// Represents the value of openX and closeX value - late double openX, closeX; - - /// Represents the value of top rect y and bottom rect y value - late double topRectY, bottomRectY; - - /// Represents the low, high, min and max point value - late ChartLocation lowPoint, highPoint, minPoint, maxPoint; - - /// Represents the point color mapper value - Color? pointColorMapper; - - /// Specifies the value of isSolid and isBull - late bool isSolid = false, isBull = false; - - /// Specifies the value of track rect - late RRect trackRect; - - /// Colors of the negative point, intermediate point and total point. - Color? negativePointsColor, intermediateSumColor, totalSumColor; - - /// Represents the value a1, y1, x2, y2, x3 and y3 - late double x1, y1, x2, y2, x3, y3; - - /// Represents the value of midX, midY - late num midX, midY; - - /// Represents the value of cumulative position - late double currentCummulativePos, nextCummulativePos; - - /// Represents the cumulative value - late double currentCummulativeValue, nextCummulativeValue; - - /// Represents the value of border path - Path? borderPath; - - /// Represents the openY, closeY - late double openY, closeY; - - /// Represents the value of min and max - late double min, max; - - /// Represents the value of lowerX and upperX - late double lowerX, upperX; - - /// Method to get series tracker fill. - Paint getTrackerFillPaint() { - final dynamic seriesWithTrack = series; - trackerFillPaint = Paint() - ..color = seriesWithTrack.trackColor - ..style = PaintingStyle.fill; - return trackerFillPaint!; - } - - /// Method to get series tracker stroke color. - Paint getTrackerStrokePaint() { - final dynamic seriesWithTrack = series; - trackerStrokePaint = Paint() - ..color = seriesWithTrack.trackBorderColor - ..strokeWidth = seriesWithTrack.trackBorderWidth - ..style = PaintingStyle.stroke; - seriesWithTrack.trackBorderWidth == 0 - ? trackerStrokePaint!.color = Colors.transparent - : trackerStrokePaint!.color; - return trackerStrokePaint!; - } - - /// Get the color of connector lines. - Paint? getConnectorLineStrokePaint() { - if (series is WaterfallSeries) { - final WaterfallSeries waterfallSeries = - series as WaterfallSeries; - - (segment as WaterfallSegment).connectorLineStrokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = waterfallSeries.connectorLineSettings.width - ..color = waterfallSeries.connectorLineSettings.color ?? - stateProperties - .renderingDetails.chartTheme.waterfallConnectorLineColor; - return (segment as WaterfallSegment).connectorLineStrokePaint!; - } - - return null; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart deleted file mode 100644 index e72918e9c..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart +++ /dev/null @@ -1,162 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/marker.dart'; -import '../user_interaction/trackball_marker_setting_renderer.dart'; -import '../utils/enum.dart'; - -/// 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 in [TrackballMarkerSettings]. -/// Provides the options like color, border width, border color and shape of the -/// marker to customize the appearance. -class TrackballMarkerSettings extends MarkerSettings { - /// Creating an argument constructor of TrackballMarkerSettings class. - const TrackballMarkerSettings( - {this.markerVisibility = TrackballVisibilityMode.auto, - double? height, - double? width, - Color? color, - DataMarkerType? shape, - double? borderWidth, - Color? borderColor, - ImageProvider? image}) - : super( - height: height, - width: width, - color: color, - shape: shape, - borderWidth: borderWidth, - borderColor: borderColor, - image: image); - - /// Whether marker should be visible or not when trackball is enabled. - /// - /// The below values are applicable for this: - /// * TrackballVisibilityMode.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. - /// * TrackballVisibilityMode.visible - Makes the trackball marker visible for all the series, - /// irrespective of considering the [isVisible] property's value in the `markerSettings`. - /// * TrackballVisibilityMode.hidden - Hides the trackball marker for all the series. - /// - /// Defaults to `TrackballVisibilityMode.auto`. - /// - /// Also refer [TrackballVisibilityMode]. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// markerSettings: TrackballMarkerSettings( - /// markerVisibility: TrackballVisibilityMode.visible, - /// width: 10 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final TrackballVisibilityMode markerVisibility; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TrackballMarkerSettings && - other.markerVisibility == markerVisibility && - other.height == height && - other.width == width && - other.color == color && - other.shape == shape && - other.borderWidth == borderWidth && - other.borderColor == borderColor && - other.image == image; - } - - @override - int get hashCode { - final List values = [ - markerVisibility, - height, - width, - color, - shape, - borderWidth, - borderColor, - image - ]; - return hashList(values); - } -} - -/// Options to show the details of the trackball template. -@immutable -class TrackballDetails { - /// Constructor of TrackballDetails class. - const TrackballDetails( - [this.point, - this.series, - this.pointIndex, - this.seriesIndex, - this.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; - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TrackballDetails && - other.point == point && - other.series == series && - other.pointIndex == pointIndex && - other.seriesIndex == seriesIndex && - other.groupingModeInfo == groupingModeInfo; - } - - @override - int get hashCode { - final List values = [ - point, - series, - pointIndex, - seriesIndex, - groupingModeInfo - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 8603a4979..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart +++ /dev/null @@ -1,372 +0,0 @@ -import 'dart:math' as math_lib; - -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for area series. -class AreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of AreaSeriesRenderer class. - AreaSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// Creates a segment for a data point in the series. - ChartSegment _createSegments( - Path path, Path strokePath, int seriesIndex, double animateFactor, - [List? _points]) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final AreaSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final List oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - segment.currentSegmentIndex = 0; - if (_points != null) { - segment.points = _points; - } - segmentProperties.seriesRenderer = this; - segmentProperties.seriesIndex = seriesIndex; - segment.animationFactor = animateFactor; - segmentProperties.path = path; - segmentProperties.strokePath = strokePath; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = 0; - } - customizeSegment(segment); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// To draw area segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - segment.onPaint(canvas); - } - - /// To create area series segments. - @override - AreaSegment createSegment() => AreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final AreaSegment areaSegment = segment as AreaSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(areaSegment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - areaSegment.strokePaint = areaSegment.getStrokePaint(); - areaSegment.fillPaint = areaSegment.getFillPaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the area chart painter -class AreaChartPainter extends CustomPainter { - /// Creates an instance of area chart painter - AreaChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the value of chart - final SfCartesianChart chart; - - /// Specifies whether to repaint the series - final bool isRepaint; - - /// Specifies the value of animation controller - final Animation animationController; - - /// Specifies the area series renderer - final AreaSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for area series - @override - void paint(Canvas canvas, Size size) { - final int seriesIndex = painterKey.index; - Rect clipRect; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final AreaSeries series = - seriesRendererDetails.series as AreaSeries; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - double animationFactor; - CartesianChartPoint? prevPoint, point, _point; - ChartLocation? currentPoint, originPoint, _oldPoint; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - CartesianSeriesRenderer? oldSeriesRenderer; - SeriesRendererDetails? oldSeriesRendererDetails; - final Path _path = Path(); - final Path _strokePath = Path(); - final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); - final num origin = crossesAt ?? 0; - final List _points = []; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the area series must be greater than or equal to 0.'); - final List oldSeriesRenderers = - stateProperties.oldSeriesRenderers; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final bool widgetNeedUpdate = renderingDetails.widgetNeedUpdate; - final bool isLegendToggled = renderingDetails.isLegendToggled; - final bool isTransposed = - seriesRendererDetails.stateProperties.requireInvertedAxis; - canvas.save(); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - - oldSeriesRenderer = getOldSeriesRenderer(stateProperties, - seriesRendererDetails, seriesIndex, oldSeriesRenderers); - - if (oldSeriesRenderer != null) { - oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); - } - - if (seriesRendererDetails.reAnimate == true || - ((!(widgetNeedUpdate || isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isDrop) { - _point = getOldChartPoint( - stateProperties, - seriesRendererDetails, - AreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - _oldPoint = _point != null - ? calculatePoint( - _point.xValue, - _point.yValue, - oldSeriesRendererDetails!.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - isTransposed, - oldSeriesRendererDetails.series, - axisClipRect) - : null; - currentPoint = calculatePoint(point.xValue, point.yValue, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - originPoint = calculatePoint( - point.xValue, - math_lib.max(yAxisDetails.visibleRange!.minimum, origin), - xAxisDetails, - yAxisDetails, - isTransposed, - series, - axisClipRect); - double x = currentPoint.x; - double y = currentPoint.y; - _points.add(Offset(x, y)); - final bool closed = - series.emptyPointSettings.mode == EmptyPointMode.drop && - _getSeriesVisibility(dataPoints, pointIndex); - if (_oldPoint != null) { - isTransposed - ? x = getAnimateValue(animationFactor, x, _oldPoint.x, - currentPoint.x, seriesRendererDetails) - : y = getAnimateValue(animationFactor, y, _oldPoint.y, - currentPoint.y, seriesRendererDetails); - } - if (prevPoint == null || - dataPoints[pointIndex - 1].isGap == true || - (dataPoints[pointIndex].isGap == true) || - (dataPoints[pointIndex - 1].isVisible == false && - series.emptyPointSettings.mode == EmptyPointMode.gap)) { - _path.moveTo(originPoint.x, originPoint.y); - if (series.borderDrawMode == BorderDrawMode.excludeBottom || - series.borderDrawMode == BorderDrawMode.all) { - if (dataPoints[pointIndex].isGap != true) { - _strokePath.moveTo(originPoint.x, originPoint.y); - _strokePath.lineTo(x, y); - } - } else if (series.borderDrawMode == BorderDrawMode.top) { - _strokePath.moveTo(x, y); - } - _path.lineTo(x, y); - } else if (pointIndex == dataPoints.length - 1 || - dataPoints[pointIndex + 1].isGap == true) { - _strokePath.lineTo(x, y); - if (series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(originPoint.x, originPoint.y); - } else if (series.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(originPoint.x, originPoint.y); - _strokePath.close(); - } - _path.lineTo(x, y); - _path.lineTo(originPoint.x, originPoint.y); - } else { - _strokePath.lineTo(x, y); - _path.lineTo(x, y); - - if (closed) { - _path.lineTo(originPoint.x, originPoint.y); - if (series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(originPoint.x, originPoint.y); - } else if (series.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(originPoint.x, originPoint.y); - _strokePath.close(); - } - } - } - prevPoint = point; - } - } - // ignore: unnecessary_null_comparison - if (_path != null) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(_path, _strokePath, painterKey.index, - animationFactor, _points)); - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The area series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - /// It returns the visibility of area series. - bool _getSeriesVisibility( - List> points, int index) { - for (int i = index; i < points.length - 1; i++) { - if (!points[i + 1].isDrop) { - return false; - } - } - return true; - } - - @override - bool shouldRepaint(AreaChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 330b1da35..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart +++ /dev/null @@ -1,308 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for bar series. -class BarSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of BarSeriesRenderer class. - BarSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// To add bar segments to chart segments. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final BarSeries _barSeries = - _currentSeriesDetails.series as BarSeries; - final BarSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final List oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - segmentProperties.series = _barSeries; - segmentProperties.seriesRenderer = this; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.animationFactor = animateFactor; - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - final SeriesRendererDetails segmentOldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties - .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && - segmentOldSeriesDetails.segments[0] is BarSegment && - (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == - true) - ? segmentOldSeriesDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - if ((_currentSeriesDetails.stateProperties.selectedSegments.length - 1 >= - pointIndex) == - true && - SegmentHelper.getSegmentProperties(_currentSeriesDetails - .stateProperties.selectedSegments[pointIndex]) - .oldSegmentIndex == - null) { - final ChartSegment selectedSegment = - _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegment); - selectedSegmentProperties.oldSeriesRenderer = - oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; - selectedSegmentProperties.seriesRenderer = this; - selectedSegmentProperties.oldSegmentIndex = - getOldSegmentIndex(selectedSegment); - } - } else if (_currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - true && - // ignore: unnecessary_null_comparison - _currentSeriesDetails.stateProperties.segments != null && - _currentSeriesDetails.stateProperties.segments.isNotEmpty == true) { - segmentProperties.oldSeriesVisible = _currentSeriesDetails - .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; - for (int i = 0; - i < _currentSeriesDetails.stateProperties.segments.length; - i++) { - final BarSegment oldSegment = - _currentSeriesDetails.stateProperties.segments[i] as BarSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == - segmentProperties.seriesIndex) { - segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segmentProperties.path = - findingRectSeriesDashedBorder(currentPoint, _barSeries.borderWidth); - segment.segmentRect = - getRRectFromRect(currentPoint.region!, _barSeries.borderRadius); - //Tracker rect - if (_barSeries.isTrackVisible) { - segmentProperties.trackBarRect = getRRectFromRect( - currentPoint.trackerRectRegion!, _barSeries.borderRadius); - } - segmentProperties.segmentRect = segment.segmentRect; - customizeSegment(segment); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// To draw bar segment. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[segment.currentSegmentIndex!], - _currentSeriesDetails.stateProperties.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - BarSegment createSegment() => BarSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final BarSegment barSegment = segment as BarSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(barSegment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - barSegment.strokePaint = barSegment.getStrokePaint(); - barSegment.fillPaint = barSegment.getFillPaint(); - segmentProperties.trackerFillPaint = - segmentProperties.getTrackerFillPaint(); - segmentProperties.trackerStrokePaint = - segmentProperties.getTrackerStrokePaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the bar chart painter -class BarChartPainter extends CustomPainter { - /// Creates an instance of bar chart painter - BarChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Specifies the Cartesian state chart properties - final CartesianStateProperties stateProperties; - - /// Specifies the Cartesian chart - final SfCartesianChart chart; - - /// Specifies whether to repaint the segment - final bool isRepaint; - - /// Specifies the value of animation controller - final Animation animationController; - - /// Specifies the value of bar series renderer - final BarSeriesRenderer seriesRenderer; - - /// Represents the value of painter key - final PainterKey painterKey; - - /// Painter method for bar series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final BarSeries series = - seriesRendererDetails.series as BarSeries; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the bar series must be greater than or equal to 0.'); - Rect axisClipRect, clipRect; - double animationFactor; - CartesianChartPoint point; - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = point != null && - point.yValue != null && - withInRange(point.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The bar series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(BarChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index b092e0b1b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart +++ /dev/null @@ -1,419 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../../charts.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/box_and_whisker_series.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/marker.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for box and whisker series. -class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of BoxAndWhiskerSeriesRenderer class. - BoxAndWhiskerSeriesRenderer(); - - late BoxAndWhiskerSegment _segment; - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - late SeriesRendererDetails _oldSeriesDetails; - - /// Range box plot _segment is created here. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _segment = createSegment(); - SegmentHelper.setSegmentProperties(_segment, - SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_segment); - _currentSeriesDetails.oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - _currentSeriesDetails.isRectSeries = false; - segmentProperties.seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - _segment.animationFactor = animateFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - _currentSeriesDetails.oldSeriesRenderers != null && - _currentSeriesDetails.oldSeriesRenderers!.isNotEmpty == true && - (_currentSeriesDetails.oldSeriesRenderers!.length - 1 >= - segmentProperties.seriesIndex) == - true) { - _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( - _currentSeriesDetails - .oldSeriesRenderers![segmentProperties.seriesIndex]); - if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = _currentSeriesDetails - .oldSeriesRenderers![segmentProperties.seriesIndex]; - segmentProperties.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, segmentProperties.maxPoint.y)) - ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.minPoint.y)) - ..add(Offset(segmentProperties.lowerX, segmentProperties.topRectY)) - ..add(Offset(segmentProperties.upperX, segmentProperties.topRectY)) - ..add(Offset(segmentProperties.upperX, segmentProperties.bottomRectY)) - ..add(Offset(segmentProperties.lowerX, segmentProperties.bottomRectY)); - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _currentSeriesDetails.segments.add(_segment); - return _segment; - } - - 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 int yCount = yValues.length; - _boxPlotQuartileValues = BoxPlotQuartileValues(); - _boxPlotQuartileValues.average = - (yValues.fold(0, (num x, num? y) => (x.toDouble()) + y!)) / yCount; - if (mode == BoxPlotMode.exclusive) { - _boxPlotQuartileValues.lowerQuartile = - _getExclusiveQuartileValue(yValues, yCount, 0.25); - _boxPlotQuartileValues.upperQuartile = - _getExclusiveQuartileValue(yValues, yCount, 0.75); - _boxPlotQuartileValues.median = - _getExclusiveQuartileValue(yValues, yCount, 0.5); - } else if (mode == BoxPlotMode.inclusive) { - _boxPlotQuartileValues.lowerQuartile = - _getInclusiveQuartileValue(yValues, yCount, 0.25); - _boxPlotQuartileValues.upperQuartile = - _getInclusiveQuartileValue(yValues, yCount, 0.75); - _boxPlotQuartileValues.median = - _getInclusiveQuartileValue(yValues, yCount, 0.5); - } else { - _boxPlotQuartileValues.median = _getMedian(yValues); - _getQuartileValues(yValues, yCount, _boxPlotQuartileValues); - } - _getMinMaxOutlier(yValues, yCount, _boxPlotQuartileValues); - point.minimum = _boxPlotQuartileValues.minimum; - point.maximum = _boxPlotQuartileValues.maximum; - point.lowerQuartile = _boxPlotQuartileValues.lowerQuartile; - point.upperQuartile = _boxPlotQuartileValues.upperQuartile; - point.median = _boxPlotQuartileValues.median; - point.outliers = _boxPlotQuartileValues.outliers; - point.mean = _boxPlotQuartileValues.average; - } - - /// To find exclusive quartile values. - double _getExclusiveQuartileValue( - List yValues, int count, num percentile) { - if (count == 0) { - return 0; - } else if (count == 1) { - return yValues[0]!.toDouble(); - } - num value = 0; - final num rank = percentile * (count + 1); - final int integerRank = (rank.abs()).floor(); - final num fractionRank = rank - integerRank; - if (integerRank == 0) { - value = yValues[0]!; - } else if (integerRank > count - 1) { - value = yValues[count - 1]!; - } else { - value = - fractionRank * (yValues[integerRank]! - yValues[integerRank - 1]!) + - yValues[integerRank - 1]!; - } - return value.toDouble(); - } - - /// To find inclusive quartile values. - double _getInclusiveQuartileValue( - List yValues, int count, num percentile) { - if (count == 0) { - return 0; - } else if (count == 1) { - return yValues[0]!.toDouble(); - } - num value = 0; - final num rank = percentile * (count - 1); - final int integerRank = (rank.abs()).floor(); - final num fractionRank = rank - integerRank; - value = fractionRank * (yValues[integerRank + 1]! - yValues[integerRank]!) + - yValues[integerRank]!; - return value.toDouble(); - } - - /// To find a median value of each box plot point. - 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. - void _getQuartileValues(dynamic yValues, num count, - BoxPlotQuartileValues _boxPlotQuartileValues) { - if (count == 1) { - _boxPlotQuartileValues.lowerQuartile = yValues[0]; - _boxPlotQuartileValues.upperQuartile = yValues[0]; - } - final bool isEvenList = count % 2 == 0; - final num halfLength = count ~/ 2; - 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, int count, - BoxPlotQuartileValues _boxPlotQuartileValues) { - 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]!); - } 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]!); - } else { - _boxPlotQuartileValues.maximum = yValues[i]; - break; - } - } - } - - @override - BoxAndWhiskerSegment createSegment() => BoxAndWhiskerSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - final BoxAndWhiskerSegment boxSegment = _segment as BoxAndWhiskerSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(boxSegment); - segmentProperties.color = - segmentProperties.currentPoint!.pointColorMapper ?? - _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - boxSegment.strokePaint = boxSegment.getStrokePaint(); - boxSegment.fillPaint = boxSegment.getFillPaint(); - } - - /// Draws outlier with different shape and color of the appropriate - /// data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the box and whisker painter -class BoxAndWhiskerPainter extends CustomPainter { - /// Creates an instance of box and whisker painter - BoxAndWhiskerPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian chart state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart value - final SfCartesianChart chart; - - /// Specifies whether to repaint the segment - final bool isRepaint; - - /// Represents the animation controller value - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the box and whisker series renderer - BoxAndWhiskerSeriesRenderer seriesRenderer; - - /// Specifies the value of painter key - final PainterKey painterKey; - - /// Painter method for box and whisker series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List> dataPoints = - seriesRendererDetails.dataPoints; - assert(dataPoints.isNotEmpty, - 'The data points should be available to render the box and whisker series.'); - Rect clipRect; - double animationFactor; - final BoxAndWhiskerSeries series = - seriesRendererDetails.series as BoxAndWhiskerSeries; - CartesianChartPoint? point; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the bar series must be greater than or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - assert(point.y != null, - 'The yValues of the box and whisker series should not be null.'); - (point.y).remove(null); - (point.y).sort(); - seriesRenderer._findBoxPlotValues(point.y, point, series.boxPlotMode); - seriesRendererDetails.calculateRegionData( - seriesRendererDetails.stateProperties, - seriesRendererDetails, - painterKey.index, - point, - pointIndex, - seriesRendererDetails.sideBySideInfo); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - point, segmentIndex += 1, painterKey.index, animationFactor)); - } - if (point.outliers!.isNotEmpty) { - final MarkerSettingsRenderer markerSettingsRenderer = - MarkerSettingsRenderer(series.markerSettings); - seriesRendererDetails.markerShapes = []; - point.outlierRegion = []; - point.outlierRegionPosition = []; - for (int outlierIndex = 0; - outlierIndex < point.outliers!.length; - outlierIndex++) { - point.outliersPoint.add(calculatePoint( - point.xValue, - point.outliers![outlierIndex], - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect)); - calculateOutlierRegion(point, point.outliersPoint[outlierIndex], - series.markerSettings.width); - point.outlierRegionPosition!.add(Offset( - point.outliersPoint[outlierIndex].x, - point.outliersPoint[outlierIndex].y)); - markerSettingsRenderer.renderMarker( - seriesRendererDetails, - point, - seriesRendererDetails.seriesElementAnimation, - canvas, - pointIndex, - outlierIndex); - } - } - // ignore: unnecessary_null_comparison - if (chart.tooltipBehavior != null && chart.tooltipBehavior.enable) { - calculateTooltipRegion( - point, seriesIndex, seriesRendererDetails, stateProperties); - } - } - clipRect = calculatePlotOffset( - Rect.fromLTWH( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - - canvas.restore(); - if ((series.animationDuration <= 0 || - animationFactor >= stateProperties.seriesDurationFactor) && - series.dataLabelSettings.isVisible) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The box and whisker series should be available to render a data label on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (seriesRendererDetails.visible! == true && animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(BoxAndWhiskerPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 9f648de33..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart +++ /dev/null @@ -1,268 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for bubble series. -class BubbleSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of BubbleSeriesRenderer class. - BubbleSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// To add bubble segments to segment list. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final BubbleSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final List oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - _currentSeriesDetails.isRectSeries = false; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segmentProperties.seriesIndex = seriesIndex; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - segment.animationFactor = animateFactor; - segmentProperties.currentPoint = currentPoint; - segmentProperties.seriesRenderer = this; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - final SeriesRendererDetails segmentOldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties.oldPoint = - (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == true - ? segmentOldSeriesDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - if ((_currentSeriesDetails.stateProperties.selectedSegments.length - 1 >= - pointIndex) == - true && - SegmentHelper.getSegmentProperties(_currentSeriesDetails - .stateProperties.selectedSegments[pointIndex]) - .oldSegmentIndex == - null) { - final ChartSegment selectedSegment = - _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegment); - selectedSegmentProperties.oldSeriesRenderer = - oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; - selectedSegmentProperties.seriesRenderer = this; - selectedSegmentProperties.oldSegmentIndex = - getOldSegmentIndex(selectedSegment); - } - } - segment.calculateSegmentPoints(); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// To draw bubble segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - BubbleSegment createSegment() => BubbleSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final BubbleSegment bubbleSegment = segment as BubbleSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(bubbleSegment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; // ?? - // bubbleSegment.segmentProperties.seriesRenderer.seriesRendererDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - bubbleSegment.strokePaint = bubbleSegment.getStrokePaint(); - bubbleSegment.fillPaint = bubbleSegment.getFillPaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - // ignore: unnecessary_null_comparison - if (seriesRenderer != null) { - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the bubble chart painter -class BubbleChartPainter extends CustomPainter { - /// Creates an instance of bubble chart painter - BubbleChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian chart state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart - final SfCartesianChart chart; - - /// Specifies whether to repaint the segment - final bool isRepaint; - - /// Specifies the value of animation controller - final Animation animationController; - - /// Specifies the bubble series renderer - final BubbleSeriesRenderer seriesRenderer; - - /// Represents the painter key - final PainterKey painterKey; - - /// Painter method for bubble series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - double animationFactor; - final BubbleSeries series = - seriesRendererDetails.series as BubbleSeries; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the bar series must be greater than or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - final CartesianChartPoint currentPoint = - dataPoints[pointIndex]; - final bool withInXRange = withInRange(currentPoint.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = currentPoint != null && - currentPoint.yValue != null && - withInRange(currentPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData( - stateProperties, - seriesRendererDetails, - painterKey.index, - currentPoint, - pointIndex); - if (currentPoint.isVisible && !currentPoint.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(currentPoint, segmentIndex += 1, - seriesIndex, animationFactor)); - } - } - } - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The bubble series should be available to render a marker on it.'); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(BubbleChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 57c22f91d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart +++ /dev/null @@ -1,446 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for candle series. -class CandleSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of CandleSeriesRenderer class. - CandleSeriesRenderer(); - - late CandleSegment _candleSegment, _segment; - - late CandleSeriesRenderer _candelSeriesRenderer; - - List? _oldSeriesRenderers; - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - late SeriesRendererDetails _oldSeriesDetails; - - /// Range column _segment is created here. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _segment = createSegment(); - SegmentHelper.setSegmentProperties(_segment, - SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_segment); - _oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - _currentSeriesDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (_segment != null) { - segmentProperties.seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - _segment.animationFactor = animateFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer); - if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex) { - _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers![segmentProperties.seriesIndex]); - if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers![segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); - } - } - _segment.calculateSegmentPoints(); - //stores the points for rendering candle - high, low and rect points - _segment.points - ..add( - Offset(currentPoint.markerPoint!.x, segmentProperties.highPoint.y)) - ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.lowPoint.y)) - ..add(Offset(segmentProperties.openX, segmentProperties.topRectY)) - ..add(Offset(segmentProperties.closeX, segmentProperties.topRectY)) - ..add(Offset(segmentProperties.closeX, segmentProperties.bottomRectY)) - ..add(Offset(segmentProperties.openX, segmentProperties.bottomRectY)); - _candleSegment = _segment; - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _currentSeriesDetails.segments.add(_segment); - } - return _segment; - } - - @override - CandleSegment createSegment() => CandleSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - _currentSeriesDetails.candleSeries = - _currentSeriesDetails.series as CandleSeries; - _candelSeriesRenderer = SegmentHelper.getSegmentProperties(_segment) - .seriesRenderer as CandleSeriesRenderer; - _candleSegment = _candelSeriesRenderer._candleSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_candleSegment); - if (_currentSeriesDetails.candleSeries.enableSolidCandles! == true) { - segmentProperties.isSolid = true; - segmentProperties.color = - _getCandleColor(_candleSegment, segmentProperties); - } else { - segmentProperties.isSolid = segmentProperties.isBull == false; - segmentProperties.color = - _getCandleColor(_candleSegment, segmentProperties); - } - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - Color? _getCandleColor( - CandleSegment candleSegment, SegmentProperties segmentProperties) { - final SeriesRendererDetails candleSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails.candleSeries.enableSolidCandles! && - segmentProperties.isSolid) { - return (candleSeriesDetails - .dataPoints[segmentProperties - .currentPoint!.overallDataPointIndex!] - .open < - candleSeriesDetails - .dataPoints[segmentProperties - .currentPoint!.overallDataPointIndex!] - .close) == - true - ? _currentSeriesDetails.candleSeries.bullColor - : _currentSeriesDetails.candleSeries.bearColor; - } - final Color? color = - segmentProperties.currentPoint!.overallDataPointIndex! - 1 >= 0 && - (candleSeriesDetails - .dataPoints[segmentProperties - .currentPoint!.overallDataPointIndex! - - 1] - .close > - candleSeriesDetails - .dataPoints[segmentProperties - .currentPoint!.overallDataPointIndex!] - .close) == - true - ? _currentSeriesDetails.candleSeries.bearColor - : _currentSeriesDetails.candleSeries.bullColor; - return color; - } - - /// To draw candle series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[_segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - _segment.onPaint(canvas); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) {} - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the candle series painter -class CandlePainter extends CustomPainter { - /// Calling the default constructor of CandlePainter class. - CandlePainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties. - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the candle series renderer - CandleSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for candle series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List> dataPoints = - seriesRendererDetails.dataPoints; - double animationFactor; - final CandleSeries series = - seriesRendererDetails.series as CandleSeries; - CartesianChartPoint point; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the bar series must be greater than or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - final bool hasTooltip = chart.tooltipBehavior != null && - (chart.tooltipBehavior.enable || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null); - final bool hasSeriesElements = seriesRendererDetails.visible! && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible || - (chart.tooltipBehavior != null && - chart.tooltipBehavior.enable && - (chart.tooltipBehavior != null && - chart.tooltipBehavior.enable && - series.enableTooltip))); - seriesRendererDetails.sideBySideInfo = calculateSideBySideInfo( - seriesRendererDetails.renderer, stateProperties); - final num? sideBySideMinimumVal = - seriesRendererDetails.sideBySideInfo?.minimum; - - final num? sideBySideMaximumVal = - seriesRendererDetails.sideBySideInfo?.maximum; - - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - bool withInHighLowRange = false, withInOpenCloseRange = false; - if (point != null && - point.high != null && - point.low != null && - point.open != null && - point.close != null) { - withInHighLowRange = withInRange(point.high, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange( - point.low, seriesRendererDetails.yAxisDetails!.visibleRange!); - withInOpenCloseRange = withInRange(point.open, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange(point.close, - seriesRendererDetails.yAxisDetails!.visibleRange!); - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - if (withInXRange || (withInHighLowRange && withInOpenCloseRange)) { - if (withInXRange) { - seriesRendererDetails.visibleDataPoints! - .add(seriesRendererDetails.dataPoints[pointIndex]); - seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex = - seriesRendererDetails.visibleDataPoints!.length - 1; - } - point.openPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.open, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - point.closePoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.close, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - point.lowPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - point.highPoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - final num center = (point.xValue + sideBySideMinimumVal) + - (((point.xValue + sideBySideMaximumVal) - - (point.xValue + sideBySideMinimumVal)) / - 2); - - point.centerHighPoint = calculatePoint( - center, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - point.centerLowPoint = calculatePoint( - center, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - final num value1 = - (point.low < point.high) == true ? point.high : point.low; - final num value2 = - (point.low > point.high) == true ? point.high : point.low; - point.markerPoint = calculatePoint( - point.xValue, - yAxisDetails.axis.isInversed ? value2 : value1, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - if (hasSeriesElements) { - if (point.region == null || - seriesRendererDetails.calculateRegion == true) { - if (seriesRendererDetails.calculateRegion == true && - dataPoints.length == pointIndex - 1) { - seriesRendererDetails.calculateRegion = false; - } - - point.markerPoint2 = calculatePoint( - point.xValue, - yAxisDetails.axis.isInversed ? value1 : value2, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - if (seriesRendererDetails.series.dataLabelSettings.isVisible == - true) { - point.centerOpenPoint = calculatePoint( - point.xValue, - point.open, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - point.centerClosePoint = calculatePoint( - point.xValue, - point.close, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - } - - point.region = !stateProperties.requireInvertedAxis - ? Rect.fromLTWH( - point.markerPoint!.x, - point.markerPoint!.y, - seriesRendererDetails.series.borderWidth, - point.markerPoint2!.y - point.markerPoint!.y) - : Rect.fromLTWH( - point.markerPoint2!.x, - point.markerPoint2!.y, - (point.markerPoint!.x - point.markerPoint2!.x).abs(), - seriesRendererDetails.series.borderWidth); - } - if (hasTooltip) { - calculateTooltipRegion( - point, seriesIndex, seriesRendererDetails, stateProperties); - } - } - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - } - } - } - canvas.restore(); - if (seriesRendererDetails.visible! == true && animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(CandlePainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index cba6e1f4f..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart +++ /dev/null @@ -1,319 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for column series. -class ColumnSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of ColumnSeriesRenderer class. - ColumnSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// To add column segments in segments list. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final ColumnSegment segment = createSegment() as ColumnSegment; - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final CartesianStateProperties _stateProperties = - _currentSeriesDetails.stateProperties; - final List oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - final ColumnSeries _columnSeries = - _currentSeriesDetails.series as ColumnSeries; - segmentProperties.seriesRenderer = this; - segmentProperties.series = _columnSeries; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.animationFactor = animateFactor; - segmentProperties.currentPoint = currentPoint; - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - _stateProperties.zoomPanBehaviorRenderer); - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_stateProperties.renderingDetails.widgetNeedUpdate && - zoomingBehaviorDetails.isPinching != true && - !_stateProperties.renderingDetails.isLegendToggled && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - final SeriesRendererDetails segmentOldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties - .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && - segmentOldSeriesDetails.segments[0] is ColumnSegment && - (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == - true) - ? segmentOldSeriesDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - if ((_stateProperties.selectedSegments.length - 1 >= pointIndex) && - SegmentHelper.getSegmentProperties(_currentSeriesDetails - .stateProperties.selectedSegments[pointIndex]) - .oldSegmentIndex == - null) { - final ChartSegment selectedSegment = - _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegment); - selectedSegmentProperties.oldSeriesRenderer = - oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; - selectedSegmentProperties.seriesRenderer = this; - selectedSegmentProperties.oldSegmentIndex = - getOldSegmentIndex(selectedSegment); - } - } else if (_stateProperties.renderingDetails.isLegendToggled && - // ignore: unnecessary_null_comparison - _stateProperties.segments != null && - _stateProperties.segments.isNotEmpty) { - segmentProperties.oldSeriesVisible = - _stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; - - ColumnSegment oldSegment; - for (int i = 0; i < _stateProperties.segments.length; i++) { - oldSegment = _stateProperties.segments[i] as ColumnSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == - segmentProperties.seriesIndex) { - segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - - segmentProperties.path = - findingRectSeriesDashedBorder(currentPoint, _columnSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (_columnSeries.borderRadius != null) { - segment.segmentRect = - getRRectFromRect(currentPoint.region!, _columnSeries.borderRadius); - - //Tracker rect - if (_columnSeries.isTrackVisible) { - segmentProperties.trackRect = getRRectFromRect( - currentPoint.trackerRectRegion!, _columnSeries.borderRadius); - } - } - segmentProperties.segmentRect = segment.segmentRect; - customizeSegment(segment); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// To draw column series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ChartSegment createSegment() => ColumnSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final ColumnSegment columnSegment = segment as ColumnSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(columnSegment); - segmentProperties.color = - segmentProperties.currentPoint!.pointColorMapper ?? - _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - columnSegment.strokePaint = columnSegment.getStrokePaint(); - columnSegment.fillPaint = columnSegment.getFillPaint(); - segmentProperties.trackerFillPaint = - segmentProperties.getTrackerFillPaint(); - segmentProperties.trackerStrokePaint = - segmentProperties.getTrackerStrokePaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the column Chart painter -class ColumnChartPainter extends CustomPainter { - /// Calling the default constructor of ColumnChartPainter class. - ColumnChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the column series renderer - ColumnSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for column series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final ColumnSeries series = - seriesRendererDetails.series as ColumnSeries; - if (seriesRendererDetails.visible! == true) { - Rect axisClipRect, clipRect; - double animationFactor; - CartesianChartPoint point; - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the bar series must be greater than or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = point != null && - point.yValue != null && - withInRange(point.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The column series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(ColumnChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart deleted file mode 100644 index bd38342cd..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import './../axis/axis.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for error bar series. -class ErrorBarSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of ErrorBarSeriesRenderer class. - ErrorBarSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// To add error bar segments in segments list. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final ErrorBarSegment segment = createSegment() as ErrorBarSegment; - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final ErrorBarSeries _errorBarSeries = - _currentSeriesDetails.series as ErrorBarSeries; - segmentProperties.seriesRenderer = this; - segmentProperties.series = _errorBarSeries; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.animationFactor = animateFactor; - if (_errorBarSeries.onRenderDetailsUpdate != null && - _currentSeriesDetails.animationController.status == - AnimationStatus.completed) { - final ErrorBarRenderDetails errorBarRenderDetails = ErrorBarRenderDetails( - currentPoint.visiblePointIndex, - currentPoint.overallDataPointIndex, - currentPoint.errorBarValues); - _errorBarSeries.onRenderDetailsUpdate!(errorBarRenderDetails); - } - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - customizeSegment(segment); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// To draw error bar series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ChartSegment createSegment() => ErrorBarSegment(); - - /// Changes the series color. - @override - void customizeSegment(ChartSegment segment) { - final ErrorBarSegment errorBarSegment = segment as ErrorBarSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(errorBarSegment); - segmentProperties.color = - segmentProperties.currentPoint!.pointColorMapper ?? - _segmentSeriesDetails.seriesColor; - errorBarSegment.strokePaint = errorBarSegment.getStrokePaint(); - } - - /// Data marker is not applicable for error bar series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) {} - - /// Data label is not applicable for error bar series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) {} -} - -/// Represents the error bar chart painter. -class ErrorBarChartPainter extends CustomPainter { - /// Calling the default constructor of ErrorBarChartPainter class. - ErrorBarChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the error bar series renderer - ErrorBarSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for error bar series. - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final ErrorBarSeries series = - seriesRendererDetails.series as ErrorBarSeries; - if (seriesRendererDetails.visible! == true) { - Rect axisClipRect; - double animationFactor; - CartesianChartPoint point; - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the error bar series must be greater than or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = point != null && - point.yValue != null && - withInRange(point.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - } - } - canvas.restore(); - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(ErrorBarChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 8a780965a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart +++ /dev/null @@ -1,379 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for fastline series. -class FastLineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of FastLineSeriesRenderer class. - FastLineSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// Adds the segment to the segments list. - ChartSegment _createSegments(int seriesIndex, int pointIndex, - SfCartesianChart chart, double animateFactor, - [List? _points]) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final FastLineSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesRenderer = this; - segment.animationFactor = animateFactor; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_points != null) { - segment.points = _points; - } - segmentProperties.oldSegmentIndex = 0; - customizeSegment(segment); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// Renders the segment. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[0], _currentSeriesDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - FastLineSegment createSegment() => FastLineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final FastLineSegment fastLineSegment = segment as FastLineSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(fastLineSegment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - fastLineSegment.strokePaint = fastLineSegment.getStrokePaint(); - fastLineSegment.fillPaint = fastLineSegment.getFillPaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the fastline Chart painter -class FastLineChartPainter extends CustomPainter { - /// Calling the default constructor of FastLineChartPainter class. - FastLineChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the fastline series renderer - final FastLineSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for fast line series - @override - void paint(Canvas canvas, Size size) { - Rect clipRect; - double animationFactor; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final FastLineSeries series = - seriesRendererDetails.series as FastLineSeries; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List _points = []; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - if (seriesRendererDetails.reAnimate == true || - (series.animationDuration > 0 && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false)) { - seriesRendererDetails.needAnimateSeriesElements = - seriesRendererDetails.needsAnimation; - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - int segmentIndex = -1; - CartesianChartPoint? prevPoint, point; - ChartLocation currentLocation; - final VisibleRange xVisibleRange = xAxisDetails.visibleRange!; - final VisibleRange yVisibleRange = yAxisDetails.visibleRange!; - final List> seriesPoints = - seriesRendererDetails.dataPoints; - assert(seriesPoints.isNotEmpty, - 'The data points should be available to render fast line series.'); - final Rect areaBounds = - seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - final num xTolerance = (xVisibleRange.delta / areaBounds.width).abs(); - final num yTolerance = (yVisibleRange.delta / areaBounds.height).abs(); - num prevXValue = (seriesPoints.isNotEmpty && - // ignore: unnecessary_null_comparison - seriesPoints[0] != null && - (seriesPoints[0].xValue > xTolerance) == true) - ? 0 - : xTolerance; - num prevYValue = (seriesPoints.isNotEmpty && - // ignore: unnecessary_null_comparison - seriesPoints[0] != null && - (seriesPoints[0].yValue > yTolerance) == true) - ? 0 - : yTolerance; - num xVal = 0; - num yVal = 0; - - final List> dataPoints = - >[]; - - ///Eliminating nearest points - CartesianChartPoint currentPoint; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - // ignore: unnecessary_null_comparison - final bool hasTooltipBehavior = chart.tooltipBehavior != null; - final bool hasSeriesElements = seriesRendererDetails.visible! && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible || - (hasTooltipBehavior && - chart.tooltipBehavior.enable && - (hasTooltipBehavior && - chart.tooltipBehavior.enable && - series.enableTooltip))); - final bool hasTooltip = hasTooltipBehavior && - (chart.tooltipBehavior.enable || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null); - for (int pointIndex = 0; - pointIndex < seriesRendererDetails.dataPoints.length; - pointIndex++) { - currentPoint = seriesRendererDetails.dataPoints[pointIndex]; - xVal = currentPoint.xValue ?? xVisibleRange.minimum; - yVal = currentPoint.yValue ?? yVisibleRange.minimum; - if ((prevXValue - xVal).abs() >= xTolerance || - (prevYValue - yVal).abs() >= yTolerance) { - point = currentPoint; - dataPoints.add(currentPoint); - bool withInXRange = withInRange(currentPoint.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - bool withInYRange = currentPoint != null && - currentPoint.yValue != null && - withInRange(currentPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - bool inRange = withInXRange || withInYRange; - if (!inRange && - (pointIndex + 1 < seriesRendererDetails.dataPoints.length)) { - final CartesianChartPoint? nextPoint = - seriesRendererDetails.dataPoints[pointIndex + 1]; - withInXRange = withInRange(nextPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = nextPoint != null && - nextPoint.yValue != null && - withInRange(nextPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - inRange = withInXRange || withInYRange; - if (!inRange && pointIndex - 1 >= 0) { - final CartesianChartPoint? prevPoint = - seriesRendererDetails.dataPoints[pointIndex - 1]; - withInXRange = withInRange(prevPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = prevPoint != null && - prevPoint.yValue != null && - withInRange(prevPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - } - } - if (withInXRange || withInYRange) { - currentLocation = calculatePoint( - xVal, - yVal, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - areaBounds); - if (withInXRange) { - seriesRendererDetails.visibleDataPoints!.add(currentPoint); - seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex = - seriesRendererDetails.visibleDataPoints!.length - 1; - } - - if (hasSeriesElements) { - // ignore: unnecessary_null_comparison - if (series.markerSettings != null) { - final double markerHeight = series.markerSettings.height, - markerWidth = series.markerSettings.width; - point.region = Rect.fromLTWH( - currentLocation.x - markerWidth, - currentLocation.y - markerHeight, - 2 * markerWidth, - 2 * markerHeight); - point.markerPoint = currentLocation; - } - if (point.region == null) { - if (seriesRendererDetails.calculateRegion == true && - dataPoints.length == pointIndex - 1) { - seriesRendererDetails.calculateRegion = false; - } - } - - if (hasTooltip) { - calculateTooltipRegion( - point, seriesIndex, seriesRendererDetails, stateProperties); - } - } - - if (point.isVisible) { - _points.add(Offset(currentLocation.x, currentLocation.y)); - if (prevPoint == null) { - seriesRendererDetails.segmentPath! - .moveTo(currentLocation.x, currentLocation.y); - } else if (seriesRendererDetails - .dataPoints[pointIndex - 1].isVisible == - false && - series.emptyPointSettings.mode == EmptyPointMode.gap) { - seriesRendererDetails.segmentPath! - .moveTo(currentLocation.x, currentLocation.y); - } else if (point.isGap != true && - seriesRendererDetails.dataPoints[pointIndex - 1].isGap != - true && - seriesRendererDetails.dataPoints[pointIndex].isVisible == - true) { - seriesRendererDetails.segmentPath! - .lineTo(currentLocation.x, currentLocation.y); - } else { - seriesRendererDetails.segmentPath! - .moveTo(currentLocation.x, currentLocation.y); - } - prevPoint = point; - } - prevXValue = xVal; - prevYValue = yVal; - } - } - } - - if (seriesRendererDetails.segmentPath != null) { - seriesRendererDetails.dataPoints = dataPoints; - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(painterKey.index, segmentIndex += 1, - chart, animationFactor, _points)); - } - - canvas.restore(); - - if ((series.animationDuration <= 0 || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The fast line series should be available to render a marker on it.'); - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(FastLineChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 4b8a22e9f..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart +++ /dev/null @@ -1,507 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/marker.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for hilo series. -class HiloSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of HiloSeriesRenderer class. - HiloSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - late SeriesRendererDetails _oldSeriesDetails; - - /// Hilo segment is created here - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _currentSeriesDetails.isRectSeries = false; - final HiloSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final List oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.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)); - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - segmentProperties.seriesRenderer = this; - segment.animationFactor = animateFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer); - if (_currentSeriesDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex) { - _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]); - if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - } - } - segment.calculateSegmentPoints(); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _currentSeriesDetails.segments.add(segment); - } - return segment; - } - - /// To render hilo series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - if (!((segmentProperties.currentPoint?.low == - segmentProperties.currentPoint?.high) && - //ignore: always_specify_types - !(_currentSeriesDetails.series as HiloSeries) - .showIndicationForSameValues)) { - segment.onPaint(canvas); - } - } - - @override - HiloSegment createSegment() => HiloSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the Hilo series painter -class HiloPainter extends CustomPainter { - /// Calling the default constructor of HiloPainter class. - HiloPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the hilo series renderer - HiloSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for Hilo series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List> dataPoints = - seriesRendererDetails.dataPoints; - Rect markerClipRect; - double animationFactor; - final HiloSeries series = - seriesRendererDetails.series as HiloSeries; - CartesianChartPoint point; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - markerClipRect = calculatePlotOffset( - Rect.fromLTWH( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - final bool hasTooltip = chart.tooltipBehavior != null && - (chart.tooltipBehavior.enable || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null); - final bool hasSeriesElements = seriesRendererDetails.visible! && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible || - (chart.tooltipBehavior != null && - chart.tooltipBehavior.enable && - (chart.tooltipBehavior != null && - chart.tooltipBehavior.enable && - series.enableTooltip))); - seriesRendererDetails.sideBySideInfo = calculateSideBySideInfo( - seriesRendererDetails.renderer, stateProperties); - - final bool showMarker = (series.animationDuration <= 0 || - animationFactor >= stateProperties.seriesDurationFactor) && - series.markerSettings.isVisible && - seriesRendererDetails.markerSettingsRenderer != null; - late Size size; - late DataMarkerType markerType; - late bool hasPointColor; - Color? seriesColor; - canvas.clipRect(axisClipRect); - if (showMarker) { - seriesRendererDetails.markerShapes = []; - seriesRendererDetails.markerShapes2 = []; - assert( - // ignore: unnecessary_null_comparison - !(seriesRendererDetails.series.markerSettings.height != null) || - seriesRendererDetails.series.markerSettings.height >= 0, - 'The height of the marker should be greater than or equal to 0.'); - assert( - // ignore: unnecessary_null_comparison - !(seriesRendererDetails.series.markerSettings.width != null) || - seriesRendererDetails.series.markerSettings.width >= 0, - 'The width of the marker must be greater than or equal to 0.'); - - hasPointColor = series.pointColorMapper != null; - } - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInHighLowRange = point != null && - point.high != null && - point.low != null && - (withInRange(point.high, - seriesRendererDetails.yAxisDetails!.visibleRange!) || - withInRange(point.low, - seriesRendererDetails.yAxisDetails!.visibleRange!)); - if (withInXRange || withInHighLowRange) { - if (withInXRange) { - seriesRendererDetails.visibleDataPoints! - .add(seriesRendererDetails.dataPoints[pointIndex]); - seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex = - seriesRendererDetails.visibleDataPoints!.length - 1; - } - if (point.high != null && point.low != null) { - point.lowPoint = calculatePoint( - point.xValue, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - point.highPoint = calculatePoint( - point.xValue, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - ChartLocation? value1, value2; - value1 = (point.low < point.high) == true - ? point.highPoint - : point.lowPoint; - value2 = (point.low > point.high) == true - ? point.highPoint - : point.lowPoint; - point.markerPoint = yAxisDetails.axis.isInversed ? value2 : value1; - point.markerPoint2 = yAxisDetails.axis.isInversed ? value1 : value2; - - if (hasSeriesElements) { - if (point.region == null || - seriesRendererDetails.calculateRegion == true) { - if (seriesRendererDetails.calculateRegion == true && - dataPoints.length == pointIndex - 1) { - seriesRendererDetails.calculateRegion = false; - } - - point.region = !stateProperties.requireInvertedAxis - ? Rect.fromLTWH( - point.markerPoint!.x, - point.markerPoint!.y, - seriesRendererDetails.series.borderWidth, - point.markerPoint2!.y - point.markerPoint!.y) - : Rect.fromLTWH( - point.markerPoint2!.x, - point.markerPoint2!.y, - (point.markerPoint!.x - point.markerPoint2!.x).abs(), - seriesRendererDetails.series.borderWidth); - } - if (hasTooltip) { - calculateTooltipRegion( - point, seriesIndex, seriesRendererDetails, stateProperties); - } - } - - if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - if (showMarker) { - MarkerRenderArgs? event; - size = Size( - series.markerSettings.width, series.markerSettings.height); - markerType = series.markerSettings.shape; - seriesColor = seriesRendererDetails.seriesColor; - seriesRendererDetails.markerSettingsRenderer!.borderColor = - series.markerSettings.borderColor ?? seriesColor; - seriesRendererDetails.markerSettingsRenderer!.color = - series.markerSettings.color; - seriesRendererDetails.markerSettingsRenderer!.borderWidth = - series.markerSettings.borderWidth; - final bool isMarkerEventTriggered = - CartesianPointHelper.getIsMarkerEventTriggered(point); - if (withInXRange && - withInHighLowRange && - seriesRendererDetails.chart.onMarkerRender != null && - seriesRendererDetails.isMarkerRenderEvent == false) { - if (seriesRendererDetails.seriesElementAnimation == null || - ((seriesRendererDetails.seriesElementAnimation!.value == - 0.0 && - !isMarkerEventTriggered && - seriesRendererDetails - .seriesElementAnimation!.status == - AnimationStatus.forward) || - (seriesRendererDetails.animationController.duration! - .inMilliseconds == - 0 && - !isMarkerEventTriggered))) { - CartesianPointHelper.setIsMarkerEventTriggered(point, true); - event = triggerMarkerRenderEvent( - seriesRendererDetails, - size, - markerType, - seriesRendererDetails - .dataPoints[pointIndex].visiblePointIndex!, - seriesRendererDetails.seriesElementAnimation)!; - markerType = event.shape; - seriesRendererDetails.markerSettingsRenderer?.borderColor = - event.borderColor; - seriesRendererDetails.markerSettingsRenderer?.color = - event.color; - seriesRendererDetails.markerSettingsRenderer?.borderWidth = - event.borderWidth; - size = Size(event.markerHeight, event.markerWidth); - CartesianPointHelper.setMarkerDetails( - point, - MarkerDetails( - markerType: markerType, - borderColor: seriesRendererDetails - .markerSettingsRenderer?.borderColor, - color: seriesRendererDetails - .markerSettingsRenderer?.color, - borderWidth: seriesRendererDetails - .markerSettingsRenderer?.borderWidth, - size: size)); - } - } - - final double opacity = - (seriesRendererDetails.seriesElementAnimation != null && - (seriesRendererDetails.stateProperties - .renderingDetails.initialRender! == - true || - seriesRendererDetails.needAnimateSeriesElements == - true)) - ? seriesRendererDetails.seriesElementAnimation!.value - : 1; - final MarkerDetails? pointMarkerDetails = - CartesianPointHelper.getMarkerDetails(point); - seriesRendererDetails.markerShapes.add(getMarkerShapesPath( - pointMarkerDetails?.markerType ?? markerType, - Offset(point.markerPoint!.x, point.markerPoint!.y), - pointMarkerDetails?.size ?? size, - seriesRendererDetails, - pointIndex, - null, - seriesRendererDetails.seriesElementAnimation)); - seriesRendererDetails.markerShapes2.add(getMarkerShapesPath( - pointMarkerDetails?.markerType ?? markerType, - Offset(point.markerPoint2!.x, point.markerPoint2!.y), - pointMarkerDetails?.size ?? size, - seriesRendererDetails, - pointIndex, - null, - seriesRendererDetails.seriesElementAnimation)); - - final Paint strokePaint = - seriesRendererDetails.markerSettingsRenderer!.getStrokePaint( - point, - series, - pointMarkerDetails, - opacity, - hasPointColor, - seriesColor, - markerType, - seriesRendererDetails, - seriesRendererDetails.seriesElementAnimation, - size); - - final Paint fillPaint = - seriesRendererDetails.markerSettingsRenderer!.getFillPaint( - point, - series, - seriesRendererDetails, - pointMarkerDetails, - opacity); - - // Render marker points. - if (seriesRendererDetails.markerSettingsRenderer!.withInRect( - seriesRendererDetails, point.markerPoint, axisClipRect) && - point.markerPoint != null && - point.isGap != true && - seriesRendererDetails.markerShapes[pointIndex] != null) { - final bool needToClip = showMarker && - (point.lowPoint!.x == axisClipRect.left || - point.lowPoint!.x == axisClipRect.right || - point.lowPoint!.y == axisClipRect.bottom || - point.highPoint!.y == axisClipRect.top); - if (needToClip) { - canvas.restore(); - canvas.clipRect(markerClipRect); - canvas.save(); - } - seriesRendererDetails.renderer.drawDataMarker( - pointIndex, - canvas, - fillPaint, - strokePaint, - point.markerPoint!.x, - point.markerPoint!.y, - seriesRendererDetails.renderer); - if (series.markerSettings.shape == DataMarkerType.image) { - drawImageMarker(seriesRendererDetails, canvas, - point.markerPoint!.x, point.markerPoint!.y); - drawImageMarker(seriesRendererDetails, canvas, - point.markerPoint2!.x, point.markerPoint2!.y); - } - if (needToClip) { - canvas.clipRect(axisClipRect); - } - } - seriesRendererDetails.markerSettingsRenderer - ?.setMarkerEventTrigged(point, seriesRendererDetails, - seriesRendererDetails.seriesElementAnimation); - } - } - } else if (showMarker && !(withInXRange || withInHighLowRange)) { - seriesRendererDetails.markerShapes.add(null); - seriesRendererDetails.markerShapes2.add(null); - } - } - - canvas.restore(); - - if (seriesRendererDetails.visible! == true && animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(HiloPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 51e49521b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart +++ /dev/null @@ -1,400 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for hilo open close series. -class HiloOpenCloseSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of HiloOpenCloseSeriesRenderer class. - HiloOpenCloseSeriesRenderer(); - - late HiloOpenCloseSegment _segment; - - List? _oldSeriesRenderers; - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - late SeriesRendererDetails _oldSeriesDetails; - - /// Hilo open close _segment is created here. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _segment = createSegment(); - SegmentHelper.setSegmentProperties(_segment, - SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_segment); - _oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - _currentSeriesDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (_segment != null) { - segmentProperties.seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - _segment.animationFactor = animateFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segmentProperties.currentPoint = currentPoint; - if (_oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length > segmentProperties.seriesIndex) { - _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers![segmentProperties.seriesIndex]); - } - - _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer); - if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex && - _oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers![segmentProperties.seriesIndex]; - segmentProperties.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, segmentProperties.highPoint.y)) - ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.lowPoint.y)) - ..add(Offset(segmentProperties.openX, segmentProperties.openY)) - ..add(Offset(segmentProperties.closeX, segmentProperties.closeY)); - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _currentSeriesDetails.segments.add(_segment); - } - return _segment; - } - - /// To render hilo open close series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[_segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - _segment.onPaint(canvas); - } - - @override - HiloOpenCloseSegment createSegment() => HiloOpenCloseSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - _currentSeriesDetails.hiloOpenCloseSeries = - _currentSeriesDetails.series as HiloOpenCloseSeries; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_segment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = - _segment is HiloOpenCloseSegment && segmentProperties.isBull == true - ? _currentSeriesDetails.hiloOpenCloseSeries.bullColor - : _currentSeriesDetails.hiloOpenCloseSeries.bearColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) {} - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the HiloOpenClose series painter -class HiloOpenClosePainter extends CustomPainter { - /// Calling the default constructor of HiloOpenClosePainter class. - HiloOpenClosePainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the Hilo open close series renderer - HiloOpenCloseSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for Hilo open-close series - @override - void paint(Canvas canvas, Size size) { - double animationFactor; - CartesianChartPoint point; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final HiloOpenCloseSeries series = - seriesRendererDetails.series as HiloOpenCloseSeries; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - final bool hasTooltip = chart.tooltipBehavior != null && - (chart.tooltipBehavior.enable || - seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null); - final bool hasSeriesElements = seriesRendererDetails.visible! && - (series.dataLabelSettings.isVisible || - (chart.tooltipBehavior != null && - chart.tooltipBehavior.enable && - (chart.tooltipBehavior != null && - chart.tooltipBehavior.enable && - series.enableTooltip))); - seriesRendererDetails.sideBySideInfo = calculateSideBySideInfo( - seriesRendererDetails.renderer, stateProperties); - final num? sideBySideMinimumVal = - seriesRendererDetails.sideBySideInfo?.minimum; - - final num? sideBySideMaximumVal = - seriesRendererDetails.sideBySideInfo?.maximum; - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - bool withInHighLowRange = false, withInOpenCloseRange = false; - if (point != null && - point.high != null && - point.low != null && - point.open != null && - point.close != null) { - withInHighLowRange = withInRange(point.high, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange( - point.low, seriesRendererDetails.yAxisDetails!.visibleRange!); - withInOpenCloseRange = withInRange(point.open, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - withInRange(point.close, - seriesRendererDetails.yAxisDetails!.visibleRange!); - - if (withInXRange || (withInHighLowRange && withInOpenCloseRange)) { - if (withInXRange) { - seriesRendererDetails.visibleDataPoints! - .add(seriesRendererDetails.dataPoints[pointIndex]); - seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex = - seriesRendererDetails.visibleDataPoints!.length - 1; - } - point.openPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.open, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - point.closePoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.close, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - point.lowPoint = calculatePoint( - point.xValue + sideBySideMinimumVal, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - point.highPoint = calculatePoint( - point.xValue + sideBySideMaximumVal, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - final num center = (point.xValue + sideBySideMinimumVal) + - (((point.xValue + sideBySideMaximumVal) - - (point.xValue + sideBySideMinimumVal)) / - 2); - - point.centerHighPoint = calculatePoint( - center, - point.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - point.centerLowPoint = calculatePoint( - center, - point.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - final num value1 = - (point.low < point.high) == true ? point.high : point.low; - final num value2 = - (point.low > point.high) == true ? point.high : point.low; - point.markerPoint = calculatePoint( - point.xValue, - yAxisDetails.axis.isInversed ? value2 : value1, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - if (hasSeriesElements) { - if (point.region == null || - seriesRendererDetails.calculateRegion == true) { - if (seriesRendererDetails.calculateRegion == true && - dataPoints.length == pointIndex - 1) { - seriesRendererDetails.calculateRegion = false; - } - - point.markerPoint2 = calculatePoint( - point.xValue, - yAxisDetails.axis.isInversed ? value1 : value2, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - if (seriesRendererDetails.series.dataLabelSettings.isVisible == - true) { - point.centerOpenPoint = calculatePoint( - point.xValue, - point.open, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - - point.centerClosePoint = calculatePoint( - point.xValue, - point.close, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect); - } - - point.region = !stateProperties.requireInvertedAxis - ? Rect.fromLTWH( - point.markerPoint!.x, - point.markerPoint!.y, - seriesRendererDetails.series.borderWidth, - point.markerPoint2!.y - point.markerPoint!.y) - : Rect.fromLTWH( - point.markerPoint2!.x, - point.markerPoint2!.y, - (point.markerPoint!.x - point.markerPoint2!.x).abs(), - seriesRendererDetails.series.borderWidth); - } - if (hasTooltip) { - calculateTooltipRegion( - point, seriesIndex, seriesRendererDetails, stateProperties); - } - } - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - } - } - } - - canvas.restore(); - if (seriesRendererDetails.visible! == true && animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(HiloOpenClosePainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 52c0e6462..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart +++ /dev/null @@ -1,389 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../axis/axis.dart'; -import '../base/series_base.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for histogram series. -class HistogramSeriesRenderer extends XyDataSeriesRenderer { - late HistogramSegment _segment; - late HistogramSeries _histogramSeries; - BorderRadius? _borderRadius; - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - late SeriesRendererDetails _oldSeriesDetails; - - /// Find the path for distribution line in the histogram. - Path _findNormalDistributionPath( - HistogramSeries series, SfCartesianChart chart) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final num min = seriesRendererDetails.xAxisDetails!.visibleRange!.minimum; - final num max = seriesRendererDetails.xAxisDetails!.visibleRange!.maximum; - num xValue, yValue; - final Path path = Path(); - ChartLocation pointLocation; - const num pointsCount = 500; - final num del = (max - min) / (pointsCount - 1); - for (int i = 0; i < pointsCount; i++) { - xValue = min + i * del; - yValue = math.exp( - -(xValue - seriesRendererDetails.histogramValues.mean!) * - (xValue - seriesRendererDetails.histogramValues.mean!) / - (2 * - seriesRendererDetails.histogramValues.sDValue! * - seriesRendererDetails.histogramValues.sDValue!)) / - (seriesRendererDetails.histogramValues.sDValue! * - math.sqrt(2 * math.pi)); - pointLocation = calculatePoint( - xValue, - yValue * - seriesRendererDetails.histogramValues.binWidth! * - seriesRendererDetails.histogramValues.yValues!.length, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - seriesRendererDetails.stateProperties.chartAxis.axisClipRect); - i == 0 - ? path.moveTo(pointLocation.x, pointLocation.y) - : path.lineTo(pointLocation.x, pointLocation.y); - } - return path; - } - - /// To add histogram segments to segments list. - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - int pointIndex, - VisibleRange sideBySideInfo, - int seriesIndex, - double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _segment = createSegment(); - SegmentHelper.setSegmentProperties(_segment, - SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_segment); - _currentSeriesDetails.oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - _histogramSeries = - _currentSeriesDetails.series as HistogramSeries; - _borderRadius = _histogramSeries.borderRadius; - segmentProperties.seriesRenderer = this; - segmentProperties.series = _histogramSeries; - segmentProperties.seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - _segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - _segment.animationFactor = animateFactor; - final num origin = - math.max(_currentSeriesDetails.yAxisDetails!.visibleRange!.minimum, 0); - currentPoint.region = calculateRectangle( - currentPoint.xValue + sideBySideInfo.minimum, - currentPoint.yValue, - currentPoint.xValue + sideBySideInfo.maximum, - math.max(_currentSeriesDetails.yAxisDetails!.visibleRange!.minimum, 0), - this, - _currentSeriesDetails.stateProperties); - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - _currentSeriesDetails.oldSeriesRenderers != null && - _currentSeriesDetails.oldSeriesRenderers!.isNotEmpty == true && - (_currentSeriesDetails.oldSeriesRenderers!.length - 1 >= - segmentProperties.seriesIndex) == - true) { - _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( - _currentSeriesDetails - .oldSeriesRenderers![segmentProperties.seriesIndex]); - if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = _currentSeriesDetails - .oldSeriesRenderers![segmentProperties.seriesIndex]; - final SeriesRendererDetails segmentOldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties - .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && - segmentOldSeriesDetails.segments[0] is HistogramSegment && - (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == - true) - ? segmentOldSeriesDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); - } - } else if (_currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - true && - // ignore: unnecessary_null_comparison - _currentSeriesDetails.stateProperties.segments != null && - _currentSeriesDetails.stateProperties.segments.isNotEmpty == true) { - segmentProperties.oldSeriesVisible = _currentSeriesDetails - .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; - for (int i = 0; - i < _currentSeriesDetails.stateProperties.segments.length; - i++) { - final HistogramSegment oldSegment = _currentSeriesDetails - .stateProperties.segments[i] as HistogramSegment; - if (oldSegment.currentSegmentIndex == _segment.currentSegmentIndex && - SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == - segmentProperties.seriesIndex) { - segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _histogramSeries.borderWidth); - if (_borderRadius != null) { - _segment.segmentRect = - getRRectFromRect(currentPoint.region!, _borderRadius!); - } - //Tracker rect - if (_histogramSeries.isTrackVisible) { - currentPoint.trackerRectRegion = calculateShadowRectangle( - currentPoint.xValue + sideBySideInfo.minimum, - currentPoint.yValue, - currentPoint.xValue + sideBySideInfo.maximum, - origin, - this, - _currentSeriesDetails.stateProperties, - Offset(_segmentSeriesDetails.xAxisDetails!.axis.plotOffset, - _segmentSeriesDetails.yAxisDetails!.axis.plotOffset)); - if (_borderRadius != null) { - segmentProperties.trackRect = - getRRectFromRect(currentPoint.trackerRectRegion!, _borderRadius!); - } - } - segmentProperties.segmentRect = _segment.segmentRect; - customizeSegment(_segment); - _currentSeriesDetails.segments.add(_segment); - return _segment; - } - - /// Creates a segment for a data point in the series. - @override - HistogramSegment createSegment() => HistogramSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final HistogramSegment histogramSegment = segment as HistogramSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(histogramSegment); - segmentProperties.color = - segmentProperties.currentPoint!.pointColorMapper ?? - _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - histogramSegment.strokePaint = histogramSegment.getStrokePaint(); - histogramSegment.fillPaint = histogramSegment.getFillPaint(); - segmentProperties.trackerFillPaint = - segmentProperties.getTrackerFillPaint(); - segmentProperties.trackerStrokePaint = - segmentProperties.getTrackerStrokePaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the Histogram Chart painter -class HistogramChartPainter extends CustomPainter { - /// Calling the default constructor of HistogramChartPainter class. - HistogramChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.chartSeries, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the histogram series renderer - HistogramSeriesRenderer seriesRenderer; - - /// Holds the information of seriesBase class - ChartSeriesPanel chartSeries; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for histogram series - @override - void paint(Canvas canvas, Size size) { - Rect axisClipRect, clipRect; - double animationFactor; - CartesianChartPoint point; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - - /// Clip rect added - if (seriesRendererDetails.visible! == true) { - final HistogramSeries series = - seriesRendererDetails.series as HistogramSeries; - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - - /// side by side range calculated - - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = point != null && - point.yValue != null && - withInRange(point.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData( - stateProperties, - seriesRendererDetails, - painterKey.index, - point, - pointIndex, - seriesRendererDetails.sideBySideInfo); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - point, - segmentIndex += 1, - seriesRendererDetails.sideBySideInfo!, - painterKey.index, - animationFactor)); - } - } - } - if (series.showNormalDistributionCurve) { - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - final Path _path = - seriesRenderer._findNormalDistributionPath(series, chart); - final Paint _paint = Paint() - ..strokeWidth = series.curveWidth - ..color = series.curveColor - ..style = PaintingStyle.stroke; - series.curveDashArray == null - ? canvas.drawPath(_path, _paint) - : drawDashedLine(canvas, series.curveDashArray!, _paint, _path); - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The histogram series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(HistogramChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 5cd375880..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart +++ /dev/null @@ -1,313 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/renderer.dart'; -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for line series. -class LineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of LineSeriesRenderer class. - LineSeriesRenderer(); - - late LineSegment _lineSegment, _segment; - - List? _oldSeriesRenderers; - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// To add line segments to segments list. - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _segment = createSegment(); - SegmentHelper.setSegmentProperties(_segment, - SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_segment); - SegmentHelper.setSegmentProperties(_segment, segmentProperties); - _oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - segmentProperties.seriesRenderer = this; - segmentProperties.seriesIndex = seriesIndex; - segmentProperties.currentPoint = currentPoint; - _segment.currentSegmentIndex = pointIndex; - segmentProperties.nextPoint = _nextPoint; - _segment.animationFactor = animateFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers![segmentProperties.seriesIndex]) - .seriesName == - _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers![segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); - } - _segment.calculateSegmentPoints(); - _segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1)); - _segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); - customizeSegment(_segment); - _currentSeriesDetails.segments.add(_segment); - return _segment; - } - - /// To render line series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[_segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - _segment.onPaint(canvas); - } - - /// Creates a _segment for a data point in the series. - @override - LineSegment createSegment() => LineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - _lineSegment = _segment as LineSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(_lineSegment); - segmentProperties.color = segmentProperties.pointColorMapper ?? - segmentProperties.series.color ?? - _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.pointColorMapper ?? - segmentProperties.series.color ?? - _segmentSeriesDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - _lineSegment.strokePaint = _lineSegment.getStrokePaint(); - _lineSegment.fillPaint = _lineSegment.getFillPaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the Line Chart painter -class LineChartPainter extends CustomPainter { - /// Calling the default constructor of LineChartPainter class. - LineChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the line series renderer - final LineSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for line series - @override - void paint(Canvas canvas, Size size) { - double animationFactor; - Rect clipRect; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final LineSeries series = - seriesRendererDetails.series as LineSeries; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - int segmentIndex = -1; - CartesianChartPoint? currentPoint, - _nextPoint, - startPoint, - endPoint; - - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - currentPoint = dataPoints[pointIndex]; - bool withInXRange = withInRange(currentPoint.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - bool withInYRange = currentPoint != null && - currentPoint.yValue != null && - withInRange(currentPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - - bool inRange = withInXRange || withInYRange; - if (!inRange && pointIndex + 1 < dataPoints.length) { - final CartesianChartPoint? nextPoint = - dataPoints[pointIndex + 1]; - withInXRange = withInRange(nextPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = nextPoint != null && - nextPoint.yValue != null && - withInRange(nextPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - inRange = withInXRange || withInYRange; - if (!inRange && pointIndex - 1 >= 0) { - final CartesianChartPoint? prevPoint = - dataPoints[pointIndex - 1]; - withInXRange = withInRange(prevPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = prevPoint != null && - prevPoint.yValue != null && - withInRange(prevPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - } - } - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, seriesIndex, currentPoint, pointIndex); - if ((currentPoint.isVisible && !currentPoint.isGap) && - startPoint == null) { - startPoint = currentPoint; - } - 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) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(startPoint, endPoint, - segmentIndex += 1, seriesIndex, animationFactor)); - endPoint = startPoint = null; - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The line series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(seriesIndex, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(LineChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 4ec45d59e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart +++ /dev/null @@ -1,421 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for range area series. -class RangeAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of RangeAreaSeriesRenderer class. - RangeAreaSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// Range area segment is created here. - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - final RangeAreaSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - _currentSeriesDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.animationFactor = animateFactor; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - _currentSeriesDetails.series as XyDataSeries; - _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer); - if (_points != null) { - segment.points = _points; - } - segment.currentSegmentIndex = 0; - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - segmentProperties.oldSegmentIndex = 0; - _currentSeriesDetails.segments.add(segment); - } - return segment; - } - - /// To render range area series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[0], _currentSeriesDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - RangeAreaSegment createSegment() => RangeAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the RangeArea Chart painter -class RangeAreaChartPainter extends CustomPainter { - /// Calling the default constructor of RangeAreaChartPainter class. - RangeAreaChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the range area series renderer - final RangeAreaSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for range area series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final RangeAreaSeries series = - seriesRendererDetails.series as RangeAreaSeries; - Rect clipRect; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - CartesianSeriesRenderer? oldSeriesRenderer; - SeriesRendererDetails? oldSeriesRendererDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - CartesianChartPoint? point, prevPoint, oldPoint; - final Path _path = Path(); - ChartLocation? currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; - double currentLowX, currentLowY, currentHighX, currentHighY; - double animationFactor; - final Path _borderPath = Path(); - RangeAreaSegment rangeAreaSegment; - final List _points = []; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final List oldSeriesRenderers = - stateProperties.oldSeriesRenderers; - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final bool isTransposed = stateProperties.requireInvertedAxis; - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - - oldSeriesRenderer = getOldSeriesRenderer(stateProperties, - seriesRendererDetails, seriesIndex, oldSeriesRenderers); - - if (oldSeriesRenderer != null) { - oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); - } - - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isDrop) { - oldPoint = getOldChartPoint( - stateProperties, - seriesRendererDetails, - RangeAreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - if (oldPoint != null) { - oldPointLow = calculatePoint( - oldPoint.xValue, - oldPoint.low, - oldSeriesRendererDetails!.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - isTransposed, - oldSeriesRendererDetails.series, - axisClipRect); - oldPointHigh = calculatePoint( - oldPoint.xValue, - oldPoint.high, - oldSeriesRendererDetails.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - isTransposed, - oldSeriesRendererDetails.series, - axisClipRect); - } else { - oldPointLow = oldPointHigh = null; - } - currentPointLow = calculatePoint(point.xValue, point.low, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - currentPointHigh = calculatePoint(point.xValue, point.high, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - _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; - if (oldPointLow != null) { - if (chart.isTransposed) { - currentLowX = getAnimateValue(animationFactor, currentLowX, - oldPointLow.x, currentPointLow.x, seriesRendererDetails); - } else { - currentLowY = getAnimateValue(animationFactor, currentLowY, - oldPointLow.y, currentPointLow.y, seriesRendererDetails); - } - } - if (oldPointHigh != null) { - if (chart.isTransposed) { - currentHighX = getAnimateValue(animationFactor, currentHighX, - oldPointHigh.x, currentPointHigh.x, seriesRendererDetails); - } else { - currentHighY = getAnimateValue(animationFactor, currentHighY, - oldPointHigh.y, currentPointHigh.y, seriesRendererDetails); - } - } - if (prevPoint == null || - dataPoints[pointIndex - 1].isGap == true || - (dataPoints[pointIndex].isGap == true) || - (dataPoints[pointIndex - 1].isVisible == false && - series.emptyPointSettings.mode == EmptyPointMode.gap)) { - _path.moveTo(currentLowX, currentLowY); - _path.lineTo(currentHighX, currentHighY); - _borderPath.moveTo(currentHighX, currentHighY); - } else if (pointIndex == dataPoints.length - 1 || - dataPoints[pointIndex + 1].isGap == true) { - _path.lineTo(currentHighX, currentHighY); - _path.lineTo(currentLowX, currentLowY); - _borderPath.lineTo(currentHighX, currentHighY); - _borderPath.moveTo(currentLowX, currentLowY); - } else { - _borderPath.lineTo(currentHighX, currentHighY); - _path.lineTo(currentHighX, currentHighY); - } - prevPoint = point; - } - if (pointIndex >= dataPoints.length - 1) { - seriesRenderer._createSegments( - painterKey.index, chart, animationFactor, _points); - } - } - for (int pointIndex = dataPoints.length - 2; - pointIndex >= 0; - pointIndex--) { - point = dataPoints[pointIndex]; - if (point.isVisible && !point.isDrop) { - oldPoint = getOldChartPoint( - stateProperties, - seriesRendererDetails, - RangeAreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - if (oldPoint != null) { - oldPointLow = calculatePoint( - oldPoint.xValue, - oldPoint.low, - oldSeriesRendererDetails!.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - isTransposed, - oldSeriesRendererDetails.series, - axisClipRect); - oldPointHigh = calculatePoint( - oldPoint.xValue, - oldPoint.high, - oldSeriesRendererDetails.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - isTransposed, - oldSeriesRendererDetails.series, - axisClipRect); - } else { - oldPointLow = oldPointHigh = null; - } - currentPointLow = calculatePoint(point.xValue, point.low, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - currentPointHigh = calculatePoint(point.xValue, point.high, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - - currentLowX = currentPointLow.x; - currentLowY = currentPointLow.y; - currentHighX = currentPointHigh.x; - currentHighY = currentPointHigh.y; - - if (oldPointLow != null) { - if (chart.isTransposed) { - currentLowX = getAnimateValue(animationFactor, currentLowX, - oldPointLow.x, currentPointLow.x, seriesRendererDetails); - } else { - currentLowY = getAnimateValue(animationFactor, currentLowY, - oldPointLow.y, currentPointLow.y, seriesRendererDetails); - } - } - if (oldPointHigh != null) { - if (chart.isTransposed) { - currentHighX = getAnimateValue(animationFactor, currentHighX, - oldPointHigh.x, currentPointHigh.x, seriesRendererDetails); - } else { - currentHighY = getAnimateValue(animationFactor, currentHighY, - oldPointHigh.y, currentPointHigh.y, seriesRendererDetails); - } - } - if (dataPoints[pointIndex + 1].isGap == true) { - _borderPath.moveTo(currentLowX, currentLowY); - _path.moveTo(currentLowX, currentLowY); - } else if (dataPoints[pointIndex].isGap != true) { - if (pointIndex + 1 == dataPoints.length - 1 && - dataPoints[pointIndex + 1].isDrop) { - _borderPath.moveTo(currentLowX, currentLowY); - } else { - _borderPath.lineTo(currentLowX, currentLowY); - } - _path.lineTo(currentLowX, currentLowY); - } - - prevPoint = point; - } - } - // ignore: unnecessary_null_comparison - if (_path != null && - // ignore: unnecessary_null_comparison - seriesRendererDetails.segments != null && - seriesRendererDetails.segments.isNotEmpty == true) { - rangeAreaSegment = - seriesRendererDetails.segments[0] as RangeAreaSegment; - SegmentHelper.getSegmentProperties(rangeAreaSegment) - ..path = _path - ..borderPath = _borderPath; - seriesRendererDetails.drawSegment(canvas, rangeAreaSegment); - } - - clipRect = calculatePlotOffset( - Rect.fromLTWH( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The range area series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(RangeAreaChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 39d14990d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart +++ /dev/null @@ -1,330 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for range column series -class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of RangeColumnSeriesRenderer class. - RangeColumnSeriesRenderer(); - - late SeriesRendererDetails _currentSeriesDetails; - late SeriesRendererDetails _segmentSeriesDetails; - - /// To add range column segments in segments list. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); - _currentSeriesDetails.isRectSeries = true; - final RangeColumnSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(_currentSeriesDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final List? oldSeriesRenderers = - _currentSeriesDetails.stateProperties.oldSeriesRenderers; - final RangeColumnSeries _rangeColumnSeries = - _currentSeriesDetails.series as RangeColumnSeries; - final BorderRadius borderRadius = _rangeColumnSeries.borderRadius; - segmentProperties.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)); - segmentProperties.seriesRenderer = this; - segmentProperties.series = _rangeColumnSeries; - segment.animationFactor = animateFactor; - segmentProperties.currentPoint = currentPoint; - _segmentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == true && - ZoomPanBehaviorHelper.getRenderingDetails(_currentSeriesDetails - .stateProperties.zoomPanBehaviorRenderer) - .isPinching != - true && - _currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - _segmentSeriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - final SeriesRendererDetails segmentOldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties - .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && - segmentOldSeriesDetails.segments[0] is RangeColumnSegment && - (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == - true) - ? segmentOldSeriesDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - if ((_currentSeriesDetails.stateProperties.selectedSegments.length - 1 >= - pointIndex) == - true && - SegmentHelper.getSegmentProperties(_currentSeriesDetails - .stateProperties.selectedSegments[pointIndex]) - .oldSegmentIndex == - null) { - final ChartSegment selectedSegment = - _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegment); - selectedSegmentProperties.oldSeriesRenderer = - oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; - selectedSegmentProperties.seriesRenderer = this; - selectedSegmentProperties.oldSegmentIndex = - getOldSegmentIndex(selectedSegment); - } - } else if (_currentSeriesDetails - .stateProperties.renderingDetails.isLegendToggled == - true && - // ignore: unnecessary_null_comparison - _currentSeriesDetails.stateProperties.segments != null && - _currentSeriesDetails.stateProperties.segments.isNotEmpty == true) { - segmentProperties.oldSeriesVisible = _currentSeriesDetails - .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; - RangeColumnSegment oldSegment; - for (int i = 0; - i < _currentSeriesDetails.stateProperties.segments.length; - i++) { - oldSegment = _currentSeriesDetails.stateProperties.segments[i] - as RangeColumnSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == - segmentProperties.seriesIndex) { - segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _rangeColumnSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (borderRadius != null) { - segment.segmentRect = - getRRectFromRect(currentPoint.region!, borderRadius); - - //Tracker rect - if (_rangeColumnSeries.isTrackVisible) { - segmentProperties.trackRect = - getRRectFromRect(currentPoint.trackerRectRegion!, borderRadius); - } - } - segmentProperties.segmentRect = segment.segmentRect; - customizeSegment(segment); - _currentSeriesDetails.segments.add(segment); - return segment; - } - - /// To render range column series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segmentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segmentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - _currentSeriesDetails.segments[segment.currentSegmentIndex!], - _currentSeriesDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - RangeColumnSegment createSegment() => RangeColumnSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final RangeColumnSegment rangeColumnSegment = segment as RangeColumnSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(rangeColumnSegment); - segmentProperties.color = _segmentSeriesDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - rangeColumnSegment.strokePaint = rangeColumnSegment.getStrokePaint(); - rangeColumnSegment.fillPaint = rangeColumnSegment.getFillPaint(); - segmentProperties.trackerFillPaint = - segmentProperties.getTrackerFillPaint(); - segmentProperties.trackerStrokePaint = - segmentProperties.getTrackerStrokePaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the RangeColumn Chart painter -class RangeColumnChartPainter extends CustomPainter { - /// Calling the default constructor of RangeColumnChartPainter class. - RangeColumnChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the range column series renderer - RangeColumnSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for range column series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final RangeColumnSeries series = - seriesRendererDetails.series as RangeColumnSeries; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - Rect axisClipRect, clipRect; - double animationFactor; - CartesianChartPoint point; - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInHighLowRange = point != null && - point.high != null && - withInRange(point.high, - seriesRendererDetails.yAxisDetails!.visibleRange!) && - point.low != null && - withInRange( - point.low, seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInHighLowRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(point, segmentIndex += 1, - painterKey.index, animationFactor)); - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The range column series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(RangeColumnChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 93f10fc7b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -export 'package:syncfusion_flutter_core/core.dart' show DataMarkerType; - -/// Creates series renderer for scatter series -class ScatterSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of ScatterSeriesRenderer class. - ScatterSeriesRenderer(); - - // ignore:unused_field - CartesianChartPoint? _point; - - ScatterSegment? _segment; - - /// Adds the points to the segments . - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final ScatterSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(seriesRendererDetails.stateProperties, segment)); - - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - - final List oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segment.animationFactor = animateFactor; - segmentProperties.point = currentPoint; - segmentProperties.currentPoint = currentPoint; - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - final SeriesRendererDetails oldSeriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties.oldPoint = (oldSeriesDetails.segments.isNotEmpty == - true && - oldSeriesDetails.segments[0] is ScatterSegment && - (oldSeriesDetails.dataPoints.length - 1 >= pointIndex) == true) - ? oldSeriesDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - if ((seriesRendererDetails.stateProperties.selectedSegments.length - - 1 >= - pointIndex) == - true && - SegmentHelper.getSegmentProperties(seriesRendererDetails - .stateProperties.selectedSegments[pointIndex]) - .oldSegmentIndex == - null) { - final ChartSegment selectedSegment = seriesRendererDetails - .stateProperties.selectedSegments[pointIndex]; - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegment); - selectedSegmentProperties.oldSeriesRenderer = - oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; - selectedSegmentProperties.seriesRenderer = this; - selectedSegmentProperties.oldSegmentIndex = - getOldSegmentIndex(selectedSegment); - } - } - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final ChartLocation location = calculatePoint( - currentPoint.xValue, - currentPoint.yValue, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - seriesRendererDetails.stateProperties.chartAxis.axisClipRect); - segment.points.add(Offset(location.x, location.y)); - segmentProperties.segmentRect = - RRect.fromRectAndRadius(currentPoint.region!, Radius.zero); - - customizeSegment(segment); - seriesRendererDetails.segments.add(segment); - _segment = segment; - } - return segment; - } - - /// To render scatter series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails currentSeriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (currentSeriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - currentSeriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ScatterSegment createSegment() => ScatterSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final ScatterSegment scatterSegment = segment as ScatterSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(scatterSegment); - segmentProperties.color = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = - ((segmentProperties.series.markerSettings.shape == - DataMarkerType.verticalLine || - segmentProperties.series.markerSettings.shape == - DataMarkerType.horizontalLine) && - segmentProperties.series.borderWidth == 0) - ? segmentProperties.series.markerSettings.borderWidth - : segmentProperties.series.borderWidth; - scatterSegment.strokePaint = scatterSegment.getStrokePaint(); - scatterSegment.fillPaint = scatterSegment.getFillPaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - final Size size = Size(seriesRendererDetails.series.markerSettings.width, - seriesRendererDetails.series.markerSettings.height); - final Path markerPath = getMarkerShapesPath( - seriesRendererDetails.series.markerSettings.shape, - Offset(pointX, pointY), - size, - seriesRendererDetails, - index, - null, - seriesRendererDetails.seriesElementAnimation, - _segment); - canvas.drawPath(markerPath, strokePaint); - canvas.drawPath(markerPath, fillPaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the scatter Chart painter -class ScatterChartPainter extends CustomPainter { - /// Calling the default constructor of ScatterChartPainter class. - ScatterChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the scatter series renderer - final ScatterSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for scatter series - @override - void paint(Canvas canvas, Size size) { - canvas.save(); - double animationFactor; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ScatterSeries series = - seriesRendererDetails.series as ScatterSeries; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final List> dataPoints = - seriesRendererDetails.dataPoints; - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - final CartesianChartPoint currentPoint = - dataPoints[pointIndex]; - final bool withInXRange = withInRange(currentPoint.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = currentPoint != null && - currentPoint.yValue != null && - withInRange(currentPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData( - stateProperties, - seriesRendererDetails, - painterKey.index, - currentPoint, - pointIndex); - if (currentPoint.isVisible && !currentPoint.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(currentPoint, segmentIndex += 1, - seriesIndex, animationFactor)); - } - } - } - if (series.animationDuration <= 0 || - animationFactor >= stateProperties.seriesDurationFactor) { - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - canvas.restore(); - } - - @override - bool shouldRepaint(ScatterChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index d93d5e659..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart +++ /dev/null @@ -1,413 +0,0 @@ -import 'dart:math' as math_lib; - -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for spline area series. -class SplineAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of SplineAreaSeriesRenderer class. - SplineAreaSeriesRenderer(); - - /// Spline area segment is created here. - ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, - double animateFactor, Path path, Path strokePath, - [List? _points]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SplineAreaSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = 0; - segment.animationFactor = animateFactor; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segmentProperties.seriesRenderer = this; - if (_points != null) { - segment.points = _points; - } - segmentProperties.path = path; - segmentProperties.strokePath = strokePath; - SegmentHelper.setSegmentProperties(segment, segmentProperties); - customizeSegment(segment); - segmentProperties.oldSegmentIndex = 0; - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render spline area series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails currentDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (currentDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - currentDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[0], seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - SplineAreaSegment createSegment() => SplineAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = seriesRendererDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the SplineArea Chart painter -class SplineAreaChartPainter extends CustomPainter { - /// Calling the default constructor of SplineAreaChartPainter class. - SplineAreaChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the spline area series renderer - final SplineAreaSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for spline area series - @override - void paint(Canvas canvas, Size size) { - double animationFactor; - CartesianChartPoint? prevPoint, point, oldChartPoint; - CartesianSeriesRenderer? oldSeriesRenderer; - SeriesRendererDetails? oldSeriesRendererDetails; - ChartLocation? currentPoint, originPoint, oldPointLocation; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - Rect clipRect; - final SplineAreaSeries series = - seriesRendererDetails.series as SplineAreaSeries; - if (seriesRendererDetails.hasDataLabelTemplate == false) { - seriesRendererDetails.drawControlPoints.clear(); - } - final Path _path = Path(); - final Path _strokePath = Path(); - final List _points = []; - final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); - final num origin = crossesAt ?? 0; - double? startControlX, startControlY, endControlX, endControlY; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final List oldSeriesRenderers = - stateProperties.oldSeriesRenderers; - final List> dataPoints = - seriesRendererDetails.dataPoints; - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final bool isTransposed = - seriesRendererDetails.stateProperties.requireInvertedAxis; - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - oldSeriesRenderer = getOldSeriesRenderer(stateProperties, - seriesRendererDetails, seriesIndex, oldSeriesRenderers); - - oldSeriesRendererDetails = oldSeriesRenderer == null - ? null - : SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); - - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - if (seriesRendererDetails.hasDataLabelTemplate == false) { - calculateSplineAreaControlPoints(seriesRenderer); - } - - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isDrop) { - //Stores the old data point details of the corresponding point index - oldChartPoint = getOldChartPoint( - stateProperties, - seriesRendererDetails, - SplineAreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - oldPointLocation = oldChartPoint != null - ? calculatePoint( - oldChartPoint.xValue, - oldChartPoint.yValue, - oldSeriesRendererDetails!.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - isTransposed, - oldSeriesRendererDetails.series, - axisClipRect) - : null; - currentPoint = calculatePoint(point.xValue, point.yValue, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - originPoint = calculatePoint( - point.xValue, - math_lib.max(yAxisDetails.visibleRange!.minimum, origin), - xAxisDetails, - yAxisDetails, - isTransposed, - series, - axisClipRect); - double x = currentPoint.x; - double y = currentPoint.y; - startControlX = startControlY = endControlX = endControlY = null; - _points.add(Offset(currentPoint.x, currentPoint.y)); - final bool closed = - series.emptyPointSettings.mode == EmptyPointMode.drop && - _getSeriesVisibility(dataPoints, pointIndex); - - //calculates animation values for control points and data points - if (oldPointLocation != null) { - isTransposed - ? x = getAnimateValue(animationFactor, x, oldPointLocation.x, - currentPoint.x, seriesRendererDetails) - : y = getAnimateValue(animationFactor, y, oldPointLocation.y, - currentPoint.y, seriesRendererDetails); - if (point.startControl != null) { - startControlY = getAnimateValue( - animationFactor, - startControlY, - oldChartPoint!.startControl!.y, - point.startControl!.y, - seriesRendererDetails); - startControlX = getAnimateValue( - animationFactor, - startControlX, - oldChartPoint.startControl!.x, - point.startControl!.x, - seriesRendererDetails); - } - if (point.endControl != null) { - endControlX = getAnimateValue( - animationFactor, - endControlX, - oldChartPoint!.endControl!.x, - point.endControl!.x, - seriesRendererDetails); - endControlY = getAnimateValue( - animationFactor, - endControlY, - oldChartPoint.endControl!.y, - point.endControl!.y, - seriesRendererDetails); - } - } else { - if (point.startControl != null) { - startControlX = point.startControl!.x; - startControlY = point.startControl!.y; - } - if (point.endControl != null) { - endControlX = point.endControl!.x; - endControlY = point.endControl!.y; - } - } - - if (prevPoint == null || - dataPoints[pointIndex - 1].isGap == true || - (dataPoints[pointIndex].isGap == true) || - (dataPoints[pointIndex - 1].isVisible == false && - series.emptyPointSettings.mode == EmptyPointMode.gap)) { - _path.moveTo(originPoint.x, originPoint.y); - if (series.borderDrawMode == BorderDrawMode.excludeBottom || - series.borderDrawMode == BorderDrawMode.all) { - if (dataPoints[pointIndex].isGap != true) { - _strokePath.moveTo(originPoint.x, originPoint.y); - _strokePath.lineTo(x, y); - } - } else if (series.borderDrawMode == BorderDrawMode.top) { - _strokePath.moveTo(x, y); - } - _path.lineTo(x, y); - } else if (pointIndex == dataPoints.length - 1 || - dataPoints[pointIndex + 1].isGap == true) { - _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) { - _strokePath.lineTo(originPoint.x, originPoint.y); - _strokePath.close(); - } - _path.cubicTo( - startControlX, startControlY, endControlX, endControlY, x, y); - _path.lineTo(originPoint.x, originPoint.y); - } else { - _strokePath.cubicTo(startControlX!, startControlY!, endControlX!, - endControlY!, x, y); - _path.cubicTo( - startControlX, startControlY, endControlX, endControlY, x, y); - - if (closed) { - _path.cubicTo(startControlX, startControlY, endControlX, - endControlY, originPoint.x, originPoint.y); - if (series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(originPoint.x, originPoint.y); - } else if (series.borderDrawMode == BorderDrawMode.all) { - _strokePath.cubicTo(startControlX, startControlY, endControlX, - endControlY, originPoint.x, originPoint.y); - _strokePath.close(); - } - } - } - prevPoint = point; - } - } - // ignore: unnecessary_null_comparison - if (_path != null) { - seriesRenderer._drawSegment( - canvas, - seriesRenderer._createSegments( - painterKey.index, chart, animationFactor, _path, _strokePath)); - } - - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The spline area series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(SplineAreaChartPainter oldDelegate) => isRepaint; - - /// It returns the visibility of area series - bool _getSeriesVisibility( - List> points, int index) { - for (int i = index; i < points.length - 1; i++) { - if (!points[i + 1].isDrop) { - return false; - } - } - return true; - } -} 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 deleted file mode 100644 index a09166294..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart +++ /dev/null @@ -1,306 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for spline series. -class SplineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of SplineSeriesRenderer class. - SplineSeriesRenderer(); - - /// Spline segment is created here. - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint nextPoint, - int pointIndex, - int seriesIndex, - double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SplineSegment segment = createSegment() as SplineSegment; - final List _oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - segment.animationFactor = animateFactor; - segmentProperties.currentPoint = currentPoint; - segmentProperties.nextPoint = nextPoint; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesIndex = seriesIndex; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segmentProperties.seriesRenderer = this; - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer); - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - seriesDetails.seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSeries = SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!) - .series as XyDataSeries; - } - SegmentHelper.setSegmentProperties(segment, segmentProperties); - segment.calculateSegmentPoints(); - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); - - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render spline series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (seriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ChartSegment createSegment() => SplineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - segmentProperties.color = seriesDetails.seriesColor; - segmentProperties.strokeColor = seriesDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the spline Chart painter -class SplineChartPainter extends CustomPainter { - /// Calling the default constructor of SplineChartPainter class. - SplineChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the spline series renderer - final SplineSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for spline series - @override - void paint(Canvas canvas, Size size) { - Rect clipRect; - double animationFactor; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final SplineSeries series = - seriesRendererDetails.series as SplineSeries; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - if (seriesRendererDetails.hasDataLabelTemplate == false) { - seriesRendererDetails.drawControlPoints.clear(); - } - - /// Clip rect will be added for series. - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - if (seriesRendererDetails.hasDataLabelTemplate == false) { - calculateSplineAreaControlPoints(seriesRenderer); - } - - int segmentIndex = -1; - - CartesianChartPoint? point, _nextPoint, startPoint, endPoint; - - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - //Draw spline for spline series - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - if (withInRange(seriesRendererDetails.dataPoints[pointIndex].xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!) || - (pointIndex < dataPoints.length - 1 && - withInRange( - seriesRendererDetails.dataPoints[pointIndex + 1].xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!))) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, 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) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(startPoint, endPoint, - segmentIndex += 1, seriesIndex, animationFactor)); - endPoint = startPoint = null; - } - } - } - - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The spline series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - - ///Draw marker and other elements for spline series - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(SplineChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 0342d6eb3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart +++ /dev/null @@ -1,516 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for spline range area series. -class SplineRangeAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of SplineRangeAreaSeriesRenderer class. - SplineRangeAreaSeriesRenderer(); - - /// Spline range area segment is created here. - ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, - double animateFactor, Path path, Path strokePath, - [List? _points]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SplineRangeAreaSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(seriesRendererDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = 0; - segment.animationFactor = animateFactor; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segmentProperties.seriesRenderer = this; - if (_points != null) { - segment.points = _points; - } - segmentProperties.path = path; - segmentProperties.strokePath = strokePath; - segmentProperties.oldSegmentIndex = 0; - - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render spline range area series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - if (seriesDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[0], seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - @override - SplineRangeAreaSegment createSegment() => SplineRangeAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final SeriesRendererDetails seriesDetails = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); - segmentProperties.color = seriesDetails.seriesColor; - segmentProperties.strokeColor = seriesDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the SplineRangeArea Chart painter -class SplineRangeAreaChartPainter extends CustomPainter { - /// Calling the default constructor of SplineRangeAreaChartPainter class. - SplineRangeAreaChartPainter( - {required this.stateProperties, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey, - required this.seriesRenderer}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the Spline range area series renderer - final SplineRangeAreaSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for spline range area series - @override - void paint(Canvas canvas, Size size) { - Rect clipRect; - double animationFactor; - ChartLocation? currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final int pointsLength = seriesRendererDetails.dataPoints.length; - CartesianChartPoint? prevPoint, point, oldChartPoint; - final Path _path = Path(); - final Path _strokePath = Path(); - final List> dataPoints = - seriesRendererDetails.dataPoints; - CartesianSeriesRenderer? oldSeriesRenderer; - - final List _points = []; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - double? currentPointLowX, - currentPointLowY, - currentPointHighX, - currentPointHighY, - startControlX, - startControlY, - endControlX, - endControlY; - seriesRendererDetails.drawHighControlPoints.clear(); - seriesRendererDetails.drawLowControlPoints.clear(); - - // Clip rect will be added for series. - if (seriesRendererDetails.visible! == true) { - canvas.save(); - final int seriesIndex = painterKey.index; - - oldSeriesRenderer = getOldSeriesRenderer(stateProperties, - seriesRendererDetails, seriesIndex, oldSeriesRenderers); - - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - final bool isTransposed = stateProperties.requireInvertedAxis; - final SplineRangeAreaSeries series = - seriesRendererDetails.series - as SplineRangeAreaSeries; - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - SplineRangeAreaSegment splineRangeAreaSegment; - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - if (seriesRendererDetails.hasDataLabelTemplate == false) { - calculateSplineAreaControlPoints(seriesRenderer); - } - - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < pointsLength; pointIndex++) { - point = seriesRendererDetails.dataPoints[pointIndex]; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isDrop) { - oldChartPoint = getOldChartPoint( - stateProperties, - seriesRendererDetails, - SplineRangeAreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - - oldPointHigh = (oldChartPoint != null) - ? calculatePoint( - oldChartPoint.xValue, - oldChartPoint.high, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - isTransposed, - series, - axisClipRect) - : null; - currentPointLow = calculatePoint(point.xValue, point.low, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - currentPointHigh = calculatePoint(point.xValue, point.high, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - _points.add(Offset(currentPointLow.x, currentPointLow.y)); - _points.add(Offset(currentPointHigh.x, currentPointHigh.y)); - - currentPointLowX = currentPointLow.x; - currentPointLowY = currentPointLow.y; - - if (oldPointHigh != null) { - if (isTransposed) { - currentPointHighX = getAnimateValue( - animationFactor, - currentPointHighX, - oldPointHigh.x, - currentPointHigh.x, - seriesRendererDetails); - currentPointHighY = currentPointHigh.y; - } else { - currentPointHighX = currentPointHigh.x; - currentPointHighY = getAnimateValue( - animationFactor, - currentPointHighY, - oldPointHigh.y, - currentPointHigh.y, - seriesRendererDetails); - } - if (point.highStartControl != null) { - startControlX = getAnimateValue( - animationFactor, - startControlX, - oldChartPoint!.highStartControl!.x, - point.highStartControl!.x, - seriesRendererDetails); - startControlY = getAnimateValue( - animationFactor, - startControlY, - oldChartPoint.highStartControl!.y, - point.highStartControl!.y, - seriesRendererDetails); - } else { - startControlX = startControlY = null; - } - if (point.highEndControl != null) { - endControlX = getAnimateValue( - animationFactor, - endControlX, - oldChartPoint!.highEndControl!.x, - point.highEndControl!.x, - seriesRendererDetails); - endControlY = getAnimateValue( - animationFactor, - endControlY, - oldChartPoint.highEndControl!.y, - point.highEndControl!.y, - seriesRendererDetails); - } else { - endControlX = endControlY = null; - } - } else { - currentPointHighX = currentPointHigh.x; - currentPointHighY = currentPointHigh.y; - startControlX = point.highStartControl?.x; - startControlY = point.highStartControl?.y; - endControlX = point.highEndControl?.x; - endControlY = point.highEndControl?.y; - } - // if (pointIndex == 3) print('$startControlX -- $startControlY,,,,$endControlX -- $endControlY'); - if (prevPoint == null || - dataPoints[pointIndex - 1].isGap == true || - (dataPoints[pointIndex].isGap == true) || - (dataPoints[pointIndex - 1].isVisible == false && - series.emptyPointSettings.mode == EmptyPointMode.gap)) { - _path.moveTo(currentPointLowX, currentPointLowY); - _path.lineTo(currentPointHighX, currentPointHighY); - _strokePath.moveTo(currentPointHighX, currentPointHighY); - } else if (pointIndex == dataPoints.length - 1 || - dataPoints[pointIndex + 1].isGap == true) { - _path.cubicTo(startControlX!, startControlY!, endControlX!, - endControlY!, currentPointHighX, currentPointHighY); - - _strokePath.cubicTo(startControlX, startControlY, endControlX, - endControlY, currentPointHighX, currentPointHighY); - - _path.lineTo(currentPointLowX, currentPointLowY); - - _strokePath.lineTo(currentPointHighX, currentPointHighY); - _strokePath.moveTo(currentPointLowX, currentPointLowY); - } else { - _path.cubicTo(startControlX!, startControlY!, endControlX!, - endControlY!, currentPointHighX, currentPointHighY); - - _strokePath.cubicTo(startControlX, startControlY, endControlX, - endControlY, currentPointHighX, currentPointHighY); - } - - prevPoint = point; - } - if (pointIndex >= dataPoints.length - 1) { - seriesRenderer._createSegments(painterKey.index, chart, - animationFactor, _path, _strokePath, _points); - } - } - - for (int pointIndex = dataPoints.length - 2; - pointIndex >= 0; - pointIndex--) { - point = dataPoints[pointIndex]; - if (point.isVisible && !point.isDrop) { - oldChartPoint = getOldChartPoint( - stateProperties, - seriesRendererDetails, - SplineRangeAreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - if (oldChartPoint != null) { - oldPointLow = calculatePoint( - oldChartPoint.xValue, - oldChartPoint.low, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - isTransposed, - series, - axisClipRect); - } else { - oldPointLow = null; - } - currentPointLow = calculatePoint(point.xValue, point.low, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - currentPointHigh = calculatePoint(point.xValue, point.high, - xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); - - if (oldPointLow != null) { - if (isTransposed) { - currentPointLowX = getAnimateValue( - animationFactor, - currentPointLowX, - oldPointLow.x, - currentPointLow.x, - seriesRendererDetails); - currentPointLowY = currentPointLow.y; - } else { - currentPointLowX = currentPointLow.x; - currentPointLowY = getAnimateValue( - animationFactor, - currentPointLowY, - oldPointLow.y, - currentPointLow.y, - seriesRendererDetails); - } - if (point.lowStartControl != null) { - startControlX = getAnimateValue( - animationFactor, - startControlX, - oldChartPoint!.lowStartControl!.x, - point.lowStartControl!.x, - seriesRendererDetails); - startControlY = getAnimateValue( - animationFactor, - startControlY, - oldChartPoint.lowStartControl!.y, - point.lowStartControl!.y, - seriesRendererDetails); - } else { - startControlX = startControlY = null; - } - if (point.lowEndControl != null) { - endControlX = getAnimateValue( - animationFactor, - endControlX, - oldChartPoint!.lowEndControl!.x, - point.lowEndControl!.x, - seriesRendererDetails); - endControlY = getAnimateValue( - animationFactor, - endControlY, - oldChartPoint.lowEndControl!.y, - point.lowEndControl!.y, - seriesRendererDetails); - } else { - endControlX = endControlY = null; - } - } else { - currentPointLowX = currentPointLow.x; - currentPointLowY = currentPointLow.y; - startControlX = point.lowStartControl?.x; - startControlY = point.lowStartControl?.y; - endControlX = point.lowEndControl?.x; - endControlY = point.lowEndControl?.y; - } - - if (dataPoints[pointIndex + 1].isGap == true) { - _strokePath.moveTo(currentPointLowX, currentPointLowY); - _path.moveTo(currentPointLowX, currentPointLowY); - } else if (dataPoints[pointIndex].isGap != true) { - (pointIndex + 1 == dataPoints.length - 1 && - dataPoints[pointIndex + 1].isDrop) - ? _strokePath.moveTo(currentPointLowX, currentPointLowY) - : _strokePath.cubicTo( - endControlX!, - endControlY!, - startControlX!, - startControlY!, - currentPointLowX, - currentPointLowY); - - _path.cubicTo(endControlX!, endControlY!, startControlX!, - startControlY!, currentPointLowX, currentPointLowY); - } - } - } - - /// Draw the spline range area series - // ignore: unnecessary_null_comparison - if (_path != null && - // ignore: unnecessary_null_comparison - seriesRendererDetails.segments.isNotEmpty) { - splineRangeAreaSegment = - seriesRendererDetails.segments[0] as SplineRangeAreaSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(splineRangeAreaSegment); - segmentProperties.path = _path; - segmentProperties.strokePath = _strokePath; - seriesRendererDetails.drawSegment(canvas, splineRangeAreaSegment); - } - - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - (!renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The spline range area series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - } - - @override - bool shouldRepaint(SplineRangeAreaChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 49d8673a7..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart +++ /dev/null @@ -1,536 +0,0 @@ -import 'dart:math' as math_lib; - -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for stacked area series. -class StackedAreaSeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedAreaSeriesRenderer class. - StackedAreaSeriesRenderer(); - - /// Stacked area segment is created here. - // ignore: unused_element - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedAreaSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final List _oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segment.currentSegmentIndex = 0; - segmentProperties.seriesIndex = seriesIndex; - if (_points != null) { - segment.points = _points; - } - segment.animationFactor = animateFactor; - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && - seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = 0; - } - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked area series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[0], seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedAreaSegment createSegment() => StackedAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Creates series renderer for Stacked area 100 series -class StackedArea100SeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedArea100SeriesRenderer class. - StackedArea100SeriesRenderer(); - - /// Stacked Area segment is created here. - // ignore: unused_element - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - // Clearing the old chart segments objects with a dispose call. - disposeOldSegments(chart, seriesRendererDetails); - final StackedArea100Segment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final List _oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segment.currentSegmentIndex = 0; - segmentProperties.seriesIndex = seriesIndex; - if (_points != null) { - segment.points = _points; - } - segment.animationFactor = animateFactor; - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - false && - seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && - seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSeries = SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!) - .series as XyDataSeries; - segmentProperties.oldSegmentIndex = 0; - } - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked area 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[0], seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedArea100Segment createSegment() => StackedArea100Segment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the StackedArea Chart painter -class StackedAreaChartPainter extends CustomPainter { - /// Calling the default constructor of StackedAreaChartPainter class. - StackedAreaChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the Stacked area series renderer - final StackedAreaSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked area series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - stackedAreaPainter( - canvas, - seriesRenderer, - seriesRendererDetails, - stateProperties, - seriesRendererDetails.seriesAnimation, - seriesRendererDetails.seriesElementAnimation!, - painterKey); - } - - @override - bool shouldRepaint(StackedAreaChartPainter oldDelegate) => isRepaint; -} - -/// Represents the StackedArea100 Chart painter -class StackedArea100ChartPainter extends CustomPainter { - /// Calling the default constructor of StackedArea100ChartPainter class. - StackedArea100ChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the StackedArea100 series renderer - final StackedArea100SeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked area 100 series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - stackedAreaPainter( - canvas, - seriesRenderer, - seriesRendererDetails, - stateProperties, - seriesRendererDetails.seriesAnimation, - seriesRendererDetails.seriesElementAnimation!, - painterKey); - } - - @override - bool shouldRepaint(StackedArea100ChartPainter oldDelegate) => isRepaint; -} - -/// Painter method for stacked area series -void stackedAreaPainter( - Canvas canvas, - dynamic seriesRenderer, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - Animation? seriesAnimation, - Animation chartElementAnimation, - PainterKey painterKey) { - Rect clipRect, axisClipRect; - final int seriesIndex = painterKey.index; - final SfCartesianChart chart = stateProperties.chart; - final RenderingDetails _renderingDetails = stateProperties.renderingDetails; - - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - double animationFactor; - final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); - final List> dataPoints = - seriesRendererDetails.dataPoints; - - // Clip rect will be added for series. - if (seriesRendererDetails.visible! == true) { - final dynamic series = seriesRendererDetails.series; - canvas.save(); - axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - if (seriesRendererDetails.reAnimate == true || - ((!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0 == true)) { - performLinearAnimation(stateProperties, - seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); - } - - final Path _path = Path(), _strokePath = Path(); - final Rect rect = stateProperties.chartAxis.axisClipRect; - ChartLocation point1, point2; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!, - yAxisDetails = seriesRendererDetails.yAxisDetails!; - CartesianChartPoint? point; - final dynamic _series = seriesRendererDetails.series; - final List _points = []; - if (dataPoints.isNotEmpty) { - int startPoint = 0; - final StackedValues stackedValues = - seriesRendererDetails.stackingValues[0]; - List seriesRendererCollection; - CartesianSeriesRenderer previousSeriesRenderer; - SeriesRendererDetails previousSeriesRendererDetails; - seriesRendererCollection = findSeriesCollection(stateProperties); - point1 = calculatePoint( - dataPoints[0].xValue, - math_lib.max(yAxisDetails.visibleRange!.minimum, - crossesAt ?? stackedValues.startValues[0]), - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - _series, - rect); - _path.moveTo(point1.x, point1.y); - _strokePath.moveTo(point1.x, point1.y); - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, seriesIndex, point, pointIndex); - if (point.isVisible) { - point1 = calculatePoint( - dataPoints[pointIndex].xValue, - stackedValues.endValues[pointIndex], - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - _series, - rect); - _points.add(Offset(point1.x, point1.y)); - _path.lineTo(point1.x, point1.y); - _strokePath.lineTo(point1.x, point1.y); - } else { - if (_series.emptyPointSettings.mode != EmptyPointMode.drop) { - for (int j = pointIndex - 1; j >= startPoint; j--) { - point2 = calculatePoint( - dataPoints[j].xValue, - crossesAt ?? stackedValues.startValues[j], - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - _series, - rect); - _path.lineTo(point2.x, point2.y); - if (_series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(point1.x, point2.y); - } else if (_series.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(point2.x, point2.y); - } - } - if (dataPoints.length > pointIndex + 1 && - // ignore: unnecessary_null_comparison - dataPoints[pointIndex + 1] != null && - dataPoints[pointIndex + 1].isVisible) { - point1 = calculatePoint( - dataPoints[pointIndex + 1].xValue, - crossesAt ?? stackedValues.startValues[pointIndex + 1], - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - _series, - rect); - _path.moveTo(point1.x, point1.y); - _strokePath.moveTo(point1.x, point1.y); - } - startPoint = pointIndex + 1; - } - } - if (pointIndex >= dataPoints.length - 1) { - seriesRenderer._createSegments( - painterKey.index, chart, animationFactor, _points); - } - } - for (int j = dataPoints.length - 1; j >= startPoint; j--) { - previousSeriesRenderer = - getPreviousSeriesRenderer(seriesRendererCollection, seriesIndex); - previousSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(previousSeriesRenderer); - if (previousSeriesRendererDetails.series.emptyPointSettings.mode != - EmptyPointMode.drop || - previousSeriesRendererDetails.dataPoints[j].isVisible == true) { - point2 = calculatePoint( - dataPoints[j].xValue, - crossesAt ?? stackedValues.startValues[j], - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - _series, - rect); - _path.lineTo(point2.x, point2.y); - if (_series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(point1.x, point2.y); - } else if (_series.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(point2.x, point2.y); - } - } - } - } - // ignore: unnecessary_null_comparison - if (_path != null && - // ignore: unnecessary_null_comparison - seriesRendererDetails.segments != null && - seriesRendererDetails.segments.isNotEmpty == true) { - final ChartSegment areaSegment = seriesRendererDetails.segments[0]; - SegmentHelper.getSegmentProperties(areaSegment) - ..path = _path - ..strokePath = _strokePath; - seriesRenderer._drawSegment(canvas, areaSegment); - } - - clipRect = calculatePlotOffset( - Rect.fromLTRB( - rect.left - _series.markerSettings.width, - rect.top - _series.markerSettings.height, - rect.right + _series.markerSettings.width, - rect.bottom + _series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if ((_series.animationDuration <= 0 == true || - !_renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (_series.markerSettings.isVisible == true || - _series.dataLabelSettings.isVisible == true)) { - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } -} 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 deleted file mode 100644 index 22e13c854..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart +++ /dev/null @@ -1,416 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for stacked bar series. -class StackedBarSeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedBarSeriesRenderer class. - StackedBarSeriesRenderer(); - - /// Stacked bar segment is created here. - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedBarSegment segment = createSegment(); - SegmentHelper.setSegmentProperties(segment, - SegmentProperties(seriesRendererDetails.stateProperties, segment)); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final StackedBarSeries _stackedBarSeries = - seriesRendererDetails.series as StackedBarSeries; - seriesRendererDetails.isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segmentProperties.seriesRenderer = this; - segmentProperties.series = _stackedBarSeries; - segmentProperties.currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _stackedBarSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (_stackedBarSeries.borderRadius != null) { - segment.segmentRect = getRRectFromRect( - currentPoint.region!, _stackedBarSeries.borderRadius); - - //Tracker rect - if (_stackedBarSeries.isTrackVisible) { - segmentProperties.trackRect = getRRectFromRect( - currentPoint.trackerRectRegion!, _stackedBarSeries.borderRadius); - segmentProperties.trackerFillPaint = - segmentProperties.getTrackerFillPaint(); - segmentProperties.trackerStrokePaint = - segmentProperties.getTrackerStrokePaint(); - } - } - segmentProperties.segmentRect = segment.segmentRect; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked bar series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - @override - StackedBarSegment createSegment() => StackedBarSegment(); - - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } -} - -/// Represents the StackedBar Chart painter -class StackedBarChartPainter extends CustomPainter { - /// Calling the default constructor of StackedBarChartPainter class. - StackedBarChartPainter({ - required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier, - }) : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies the Cartesian chart point. - CartesianChartPoint? point; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the StackedBar series renderer - final StackedBarSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - // ignore: unused_field - // static double animation; - - /// Painter method for stacked bar series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - _stackedBarPainter(canvas, seriesRenderer, seriesRendererDetails, - stateProperties, painterKey); - } - - @override - bool shouldRepaint(StackedBarChartPainter oldDelegate) => isRepaint; -} - -/// Creates series renderer for Stacked bar 100 series -class StackedBar100SeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedBar100SeriesRenderer class. - StackedBar100SeriesRenderer(); - - /// Stacked Bar segment is created here - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedBar100Segment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - - final StackedBar100Series _stackedBar100Series = - seriesRendererDetails.series as StackedBar100Series; - seriesRendererDetails.isRectSeries = true; - - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segmentProperties.seriesRenderer = this; - segmentProperties.series = _stackedBar100Series; - segmentProperties.currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _stackedBar100Series.borderWidth); - segment.segmentRect = getRRectFromRect( - currentPoint.region!, _stackedBar100Series.borderRadius); - segmentProperties.segmentRect = segment.segmentRect; - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(segment); - currentSegmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked bar 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - @override - StackedBar100Segment createSegment() => StackedBar100Segment(); - - @override - void customizeSegment(ChartSegment segment) { - final SegmentProperties bar100SegmentProperties = - SegmentHelper.getSegmentProperties(segment); - bar100SegmentProperties.color = - bar100SegmentProperties.currentPoint!.pointColorMapper ?? - SeriesHelper.getSeriesRendererDetails( - bar100SegmentProperties.seriesRenderer) - .seriesColor; - bar100SegmentProperties.strokeColor = - bar100SegmentProperties.series.borderColor; - bar100SegmentProperties.strokeWidth = - bar100SegmentProperties.series.borderWidth; - } - - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } -} - -/// Represents the StackedBar100 Chart painter -class StackedBar100ChartPainter extends CustomPainter { - /// Calling the default constructor of StackedBar100ChartPainter class. - StackedBar100ChartPainter({ - required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier, - }) : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies the Cartesian chart point. - CartesianChartPoint? point; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the StackedBar100 series renderer - final StackedBar100SeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked bar 100 series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - _stackedBarPainter(canvas, seriesRenderer, seriesRendererDetails, - stateProperties, painterKey); - } - - @override - bool shouldRepaint(StackedBar100ChartPainter oldDelegate) => isRepaint; -} - -/// Rect painter for stacked series -void _stackedBarPainter( - Canvas canvas, - dynamic seriesRenderer, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - PainterKey painterKey) { - if (seriesRendererDetails.visible! == true) { - canvas.save(); - Rect clipRect, axisClipRect; - CartesianChartPoint point; - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final RenderingDetails _renderingDetails = stateProperties.renderingDetails; - final int seriesIndex = painterKey.index; - final Animation seriesAnimation = - seriesRendererDetails.seriesAnimation!; - final Animation chartElementAnimation = - seriesRendererDetails.seriesElementAnimation!; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - double animationFactor; - // ignore: unnecessary_null_comparison - animationFactor = seriesAnimation != null && - (seriesRendererDetails.reAnimate == true || - (!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled))) - ? seriesAnimation.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - - /// Clip rect will be added for series. - axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.clipRect(axisClipRect); - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; - pointIndex < seriesRendererDetails.dataPoints.length; - pointIndex++) { - point = seriesRendererDetails.dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = point != null && - point.yValue != null && - withInRange( - point.yValue, seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - point, segmentIndex += 1, painterKey.index, animationFactor)); - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !_renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - stateProperties.chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } -} 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 deleted file mode 100644 index d40c38649..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart +++ /dev/null @@ -1,411 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for stacked column series. -class StackedColumnSeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedColumnSeriesRenderer class. - StackedColumnSeriesRenderer(); - - /// Stacked column segment is created here. - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedColumnSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final StackedColumnSeries _stackedColumnSeries = - seriesRendererDetails.series as StackedColumnSeries; - seriesRendererDetails.isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segmentProperties.seriesRenderer = this; - segmentProperties.series = _stackedColumnSeries; - segmentProperties.currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _stackedColumnSeries.borderWidth); - segment.segmentRect = getRRectFromRect( - currentPoint.region!, _stackedColumnSeries.borderRadius); - - // Tracker rect. - if (_stackedColumnSeries.isTrackVisible) { - segmentProperties.trackRect = getRRectFromRect( - currentPoint.trackerRectRegion!, _stackedColumnSeries.borderRadius); - segmentProperties.trackerFillPaint = - segmentProperties.getTrackerFillPaint(); - segmentProperties.trackerStrokePaint = - segmentProperties.getTrackerStrokePaint(); - } - segmentProperties.segmentRect = segment.segmentRect; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked column series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - @override - StackedColumnSegment createSegment() => StackedColumnSegment(); - - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } -} - -/// Represents the StackedColumn Chart painter -class StackedColummnChartPainter extends CustomPainter { - /// Calling the default constructor of StackedColummnChartPainter class. - StackedColummnChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies the Cartesian chart point. - CartesianChartPoint? point; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the StackedColumn series renderer - final StackedColumnSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked column series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - _stackedRectPainter(canvas, seriesRenderer, seriesRendererDetails, - stateProperties, painterKey); - } - - @override - bool shouldRepaint(StackedColummnChartPainter oldDelegate) => isRepaint; -} - -/// Creates series renderer for Stacked column 100 series -class StackedColumn100SeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedColumn100SeriesRenderer class. - StackedColumn100SeriesRenderer(); - - /// Stacked Column 100 segment is created here - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedColumn100Segment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final StackedColumn100Series _stackedColumn100Series = - seriesRendererDetails.series - as StackedColumn100Series; - seriesRendererDetails.isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segmentProperties.seriesRenderer = this; - segmentProperties.series = _stackedColumn100Series; - segmentProperties.currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _stackedColumn100Series.borderWidth); - segment.segmentRect = getRRectFromRect( - currentPoint.region!, _stackedColumn100Series.borderRadius); - segmentProperties.segmentRect = segment.segmentRect; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked column 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedColumn100Segment createSegment() => StackedColumn100Segment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final StackedColumn100Segment column100Segment = - segment as StackedColumn100Segment; - final SegmentProperties column100SegmentProperties = - SegmentHelper.getSegmentProperties(column100Segment); - column100SegmentProperties.color = - column100SegmentProperties.currentPoint!.pointColorMapper ?? - SeriesHelper.getSeriesRendererDetails( - column100SegmentProperties.seriesRenderer) - .seriesColor; - column100SegmentProperties.strokeColor = - column100SegmentProperties.series.borderColor; - column100SegmentProperties.strokeWidth = - column100SegmentProperties.series.borderWidth; - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } -} - -/// Represents the StacedColumn100 Chart painter -class StackedColumn100ChartPainter extends CustomPainter { - /// Calling the default constructor of StackedColumn100ChartPainter class. - StackedColumn100ChartPainter({ - required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier, - }) : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies the Cartesian chart point. - CartesianChartPoint? point; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the StackedColumn100 series renderer - final StackedColumn100SeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked column 100 series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - _stackedRectPainter(canvas, seriesRenderer, seriesRendererDetails, - stateProperties, painterKey); - } - - @override - bool shouldRepaint(StackedColumn100ChartPainter oldDelegate) => isRepaint; -} - -/// Rect painter for stacked series -void _stackedRectPainter( - Canvas canvas, - dynamic seriesRenderer, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties, - PainterKey painterKey) { - if (seriesRendererDetails.visible! == true) { - canvas.save(); - Rect clipRect, axisClipRect; - CartesianChartPoint point; - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final RenderingDetails _renderingDetails = stateProperties.renderingDetails; - final int seriesIndex = painterKey.index; - final Animation seriesAnimation = - seriesRendererDetails.seriesAnimation!; - final Animation chartElementAnimation = - seriesRendererDetails.seriesElementAnimation!; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - double animationFactor; - // ignore: unnecessary_null_comparison - animationFactor = seriesAnimation != null && - (seriesRendererDetails.reAnimate == true || - (!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled))) - ? seriesAnimation.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - // Clip rect will be added for series. - axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.clipRect(axisClipRect); - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; - pointIndex < seriesRendererDetails.dataPoints.length; - pointIndex++) { - point = seriesRendererDetails.dataPoints[pointIndex]; - final bool withInXRange = withInRange( - point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!); - final bool withInYRange = point != null && - point.yValue != null && - withInRange( - point.yValue, seriesRendererDetails.yAxisDetails!.visibleRange!); - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - point, segmentIndex += 1, painterKey.index, animationFactor)); - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !_renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - stateProperties.chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } -} 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 deleted file mode 100644 index 42898d634..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart +++ /dev/null @@ -1,513 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for stacked line series. -class StackedLineSeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedLineSeriesRenderer class. - StackedLineSeriesRenderer(); - - /// Stacked line segment is created here. - // ignore: unused_element - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animationFactor, - double currentCummulativePos, - double nextCummulativePos) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedLineSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final List? _oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segmentProperties.seriesIndex = seriesIndex; - segmentProperties.currentPoint = currentPoint; - segment.currentSegmentIndex = pointIndex; - segmentProperties.nextPoint = _nextPoint; - segment.animationFactor = animationFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segmentProperties.currentCummulativePos = currentCummulativePos; - segmentProperties.nextCummulativePos = nextCummulativePos; - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && - seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers[segmentProperties.seriesIndex]; - SegmentHelper.setSegmentProperties(segment, segmentProperties); - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(segment); - segment.points.add( - Offset(currentSegmentProperties.x1, currentSegmentProperties.y1)); - segment.points.add( - Offset(currentSegmentProperties.x2, currentSegmentProperties.y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked line series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedLineSegment createSegment() => StackedLineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = seriesRendererDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the stacked line chart painter -class StackedLineChartPainter extends CustomPainter { - /// Calling the default constructor of StackedLineChartPainter class. - StackedLineChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the StacledLine series renderer - final StackedLineSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked line series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Clearing the old chart segments objects with a dispose call. - disposeOldSegments(chart, seriesRendererDetails); - _stackedLinePainter( - canvas, - seriesRenderer, - seriesRendererDetails, - seriesRendererDetails.seriesAnimation, - stateProperties, - seriesRendererDetails.seriesElementAnimation!, - painterKey); - } - - @override - bool shouldRepaint(StackedLineChartPainter oldDelegate) => isRepaint; -} - -/// Creates series renderer for Stacked line 100 series -class StackedLine100SeriesRenderer extends StackedSeriesRenderer { - /// Calling the default constructor of StackedLine100SeriesRenderer class. - StackedLine100SeriesRenderer(); - - ///Stacked line segment is created here - // ignore: unused_element - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animationFactor, - double currentCummulativePos, - double nextCummulativePos) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StackedLine100Segment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final List _oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segmentProperties.seriesIndex = seriesIndex; - segmentProperties.currentPoint = currentPoint; - segment.currentSegmentIndex = pointIndex; - segmentProperties.nextPoint = _nextPoint; - segment.animationFactor = animationFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - segmentProperties.currentCummulativePos = currentCummulativePos; - segmentProperties.nextCummulativePos = nextCummulativePos; - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && - seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1)); - segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - } - return segment; - } - - /// To render stacked line 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedLine100Segment createSegment() => StackedLine100Segment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = seriesRendererDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the StackedLine100 Chart painter -class StackedLine100ChartPainter extends CustomPainter { - /// Calling the default constructor of StackedLine100ChartPainter class. - StackedLine100ChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the StackedLine100 series renderer - final StackedLine100SeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for stacked line 100 series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - _stackedLinePainter( - canvas, - seriesRenderer, - seriesRendererDetails, - seriesRendererDetails.seriesAnimation, - stateProperties, - seriesRendererDetails.seriesElementAnimation!, - painterKey); - } - - @override - bool shouldRepaint(StackedLine100ChartPainter oldDelegate) => isRepaint; -} - -/// Painter for stacked line series -void _stackedLinePainter( - Canvas canvas, - dynamic seriesRenderer, - SeriesRendererDetails seriesRendererDetails, - Animation? seriesAnimation, - CartesianStateProperties stateProperties, - Animation chartElementAnimation, - PainterKey painterKey) { - Rect clipRect; - double animationFactor; - final RenderingDetails _renderingDetails = stateProperties.renderingDetails; - if (seriesRendererDetails.visible! == true) { - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - canvas.save(); - animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final int seriesIndex = painterKey.index; - StackedValues? stackedValues; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - if ((seriesRenderer is StackedLine100SeriesRenderer || - seriesRenderer is StackedLineSeriesRenderer) && - seriesRendererDetails.stackingValues.isNotEmpty == true) { - stackedValues = seriesRendererDetails.stackingValues[0]; - } - final Rect rect = - seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - - // Clip rect will be added for series. - final Rect axisClipRect = calculatePlotOffset( - rect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.clipRect(axisClipRect); - if (seriesRendererDetails.reAnimate == true || - ((!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation(stateProperties, - seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); - } - - int segmentIndex = -1; - double? currentCummulativePos, nextCummulativePos; - CartesianChartPoint? startPoint, - endPoint, - currentPoint, - _nextPoint; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; - pointIndex < seriesRendererDetails.dataPoints.length; - pointIndex++) { - currentPoint = seriesRendererDetails.dataPoints[pointIndex]; - bool withInXRange = withInRange(currentPoint.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - bool withInYRange = currentPoint != null && - currentPoint.yValue != null && - withInRange(currentPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - - bool inRange = withInXRange || withInYRange; - if (!inRange && - pointIndex + 1 < seriesRendererDetails.dataPoints.length) { - final CartesianChartPoint? nextPoint = - seriesRendererDetails.dataPoints[pointIndex + 1]; - withInXRange = withInRange(nextPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = nextPoint != null && - nextPoint.yValue != null && - withInRange(nextPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - inRange = withInXRange || withInYRange; - if (!inRange && pointIndex - 1 >= 0) { - final CartesianChartPoint? prevPoint = - seriesRendererDetails.dataPoints[pointIndex - 1]; - withInXRange = withInRange(prevPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = prevPoint != null && - prevPoint.yValue != null && - withInRange(prevPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - } - } - if (withInXRange || withInYRange) { - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, seriesIndex, currentPoint, pointIndex); - if ((currentPoint.isVisible && !currentPoint.isGap) && - startPoint == null && - stackedValues != null) { - startPoint = currentPoint; - currentCummulativePos = stackedValues.endValues[pointIndex]; - } - if (pointIndex + 1 < seriesRendererDetails.dataPoints.length) { - _nextPoint = seriesRendererDetails.dataPoints[pointIndex + 1]; - if (startPoint != null && _nextPoint.isGap) { - startPoint = null; - } else if (_nextPoint.isVisible && - !_nextPoint.isGap && - stackedValues != null) { - endPoint = _nextPoint; - nextCummulativePos = stackedValues.endValues[pointIndex + 1]; - } - } - - if (startPoint != null && endPoint != null) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - startPoint, - endPoint, - segmentIndex += 1, - seriesIndex, - animationFactor, - currentCummulativePos!, - nextCummulativePos!)); - endPoint = startPoint = null; - } - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - rect.left - series.markerSettings.width, - rect.top - series.markerSettings.height, - rect.right + series.markerSettings.width, - rect.bottom + series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !_renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - stateProperties.chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(seriesIndex, painterKey.name, true); - } - } -} 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 deleted file mode 100644 index e2602290d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart +++ /dev/null @@ -1,429 +0,0 @@ -import 'dart:math' as math_lib; - -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for step area series. -class StepAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of StepAreaSeriesRenderer class. - StepAreaSeriesRenderer(); - - /// Step area segment is created here. - ChartSegment _createSegments( - Path path, Path strokePath, int seriesIndex, double animateFactor, - [List? _points]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StepAreaSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - seriesRendererDetails.isRectSeries = false; - segmentProperties.path = path; - segmentProperties.strokePath = strokePath; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = 0; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - if (_points != null) { - segment.points = _points; - } - segment.animationFactor = animateFactor; - segment.calculateSegmentPoints(); - segmentProperties.oldSegmentIndex = 0; - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - return segment; - } - - /// To render step area series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[0], seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StepAreaSegment createSegment() => StepAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the Step area chart painter -class StepAreaChartPainter extends CustomPainter { - /// Calling the default constructor of StepAreaChartPainter class. - StepAreaChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the Step area series renderer - final StepAreaSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for step area series - @override - void paint(Canvas canvas, Size size) { - double animationFactor; - CartesianChartPoint? prevPoint, point, oldChartPoint; - ChartLocation? currentPoint, - originPoint, - previousPoint, - oldPoint, - prevOldPoint; - CartesianSeriesRenderer? oldSeriesRenderer; - late SeriesRendererDetails oldSeriesRendererDetails; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final List oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final StepAreaSeries _series = - seriesRendererDetails.series as StepAreaSeries; - final Path _path = Path(), _strokePath = Path(); - final List _points = []; - final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); - final num origin = crossesAt ?? 0; - - /// Clip rect will be added for series. - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(_series.animationDuration != null) || - _series.animationDuration >= 0, - 'The animation duration of the step area series must be greater or equal to 0.'); - canvas.save(); - final List> dataPoints = - seriesRendererDetails.dataPoints; - final int seriesIndex = painterKey.index; - final StepAreaSeries series = - seriesRendererDetails.series as StepAreaSeries; - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.clipRect(axisClipRect); - - oldSeriesRenderer = getOldSeriesRenderer(stateProperties, - seriesRendererDetails, seriesIndex, oldSeriesRenderers); - - if (oldSeriesRenderer != null) { - oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); - } - - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys - .contains(seriesRendererDetails.series.key)) && - (seriesRendererDetails.series.animationDuration > 0) == true)) { - performLinearAnimation(stateProperties, - seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); - } - - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (point.isVisible) { - oldChartPoint = getOldChartPoint( - stateProperties, - seriesRendererDetails, - StepAreaSegment, - seriesIndex, - pointIndex, - oldSeriesRenderer, - oldSeriesRenderers); - oldPoint = oldChartPoint != null - ? calculatePoint( - oldChartPoint.xValue, - oldChartPoint.yValue, - oldSeriesRendererDetails.xAxisDetails!, - oldSeriesRendererDetails.yAxisDetails!, - chart.isTransposed, - oldSeriesRendererDetails.series, - axisClipRect) - : null; - currentPoint = calculatePoint( - point.xValue, - point.yValue, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - axisClipRect); - previousPoint = prevPoint != null - ? calculatePoint( - prevPoint.xValue, - prevPoint.yValue, - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - axisClipRect) - : null; - originPoint = calculatePoint( - point.xValue, - math_lib.max(yAxisDetails.visibleRange!.minimum, origin), - xAxisDetails, - yAxisDetails, - seriesRendererDetails.stateProperties.requireInvertedAxis, - series, - axisClipRect); - _points.add(Offset(currentPoint.x, currentPoint.y)); - _drawStepAreaPath( - _path, - _strokePath, - prevPoint, - currentPoint, - originPoint, - previousPoint, - oldPoint, - prevOldPoint, - pointIndex, - animationFactor, - _series, - seriesRendererDetails); - prevPoint = point; - prevOldPoint = oldPoint; - } - } - // ignore: unnecessary_null_comparison - if (_path != null && _strokePath != null) { - seriesRenderer._drawSegment( - canvas, - seriesRenderer._createSegments(_path, _strokePath, painterKey.index, - animationFactor, _points)); - } - _drawSeries(canvas, animationFactor, seriesRendererDetails); - } - } - - ///Draw series elements and add cliprect - void _drawSeries(Canvas canvas, double animationFactor, - SeriesRendererDetails seriesRendererDetails) { - final StepAreaSeries series = - seriesRendererDetails.series as StepAreaSeries; - final Rect clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !stateProperties.renderingDetails.initialRender! || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The step area series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - - @override - bool shouldRepaint(StepAreaChartPainter oldDelegate) => isRepaint; - - /// To draw the step area path - void _drawStepAreaPath( - Path _path, - Path _strokePath, - CartesianChartPoint? prevPoint, - ChartLocation currentPoint, - ChartLocation originPoint, - ChartLocation? previousPoint, - ChartLocation? oldPoint, - ChartLocation? prevOldPoint, - int pointIndex, - double animationFactor, - StepAreaSeries stepAreaSeries, - SeriesRendererDetails seriesRendererDetails) { - double x = currentPoint.x; - double y = currentPoint.y; - double? previousPointY = previousPoint?.y; - final bool closed = - stepAreaSeries.emptyPointSettings.mode == EmptyPointMode.drop && - _getSeriesVisibility(seriesRendererDetails.dataPoints, pointIndex); - if (oldPoint != null) { - if (stateProperties.chart.isTransposed) { - x = getAnimateValue(animationFactor, x, oldPoint.x, currentPoint.x, - seriesRendererDetails); - } else { - y = getAnimateValue(animationFactor, y, oldPoint.y, currentPoint.y, - seriesRendererDetails); - previousPointY = previousPointY != null - ? getAnimateValue(animationFactor, previousPointY, prevOldPoint?.y, - previousPoint?.y, seriesRendererDetails) - : previousPointY; - } - } - if (prevPoint == null || - seriesRendererDetails.dataPoints[pointIndex - 1].isGap == true || - (seriesRendererDetails.dataPoints[pointIndex].isGap == true) || - (seriesRendererDetails.dataPoints[pointIndex - 1].isVisible == false && - stepAreaSeries.emptyPointSettings.mode == EmptyPointMode.gap)) { - _path.moveTo(originPoint.x, originPoint.y); - if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { - if (seriesRendererDetails.dataPoints[pointIndex].isGap != true) { - _strokePath.moveTo(originPoint.x, originPoint.y); - _strokePath.lineTo(x, y); - } - } else if (stepAreaSeries.borderDrawMode == BorderDrawMode.all) { - if (seriesRendererDetails.dataPoints[pointIndex].isGap != true) { - _strokePath.moveTo(originPoint.x, originPoint.y); - _strokePath.lineTo(x, y); - } - } else if (stepAreaSeries.borderDrawMode == BorderDrawMode.top) { - _strokePath.moveTo(x, y); - } - _path.lineTo(x, y); - } else if (pointIndex == seriesRendererDetails.dataPoints.length - 1 || - seriesRendererDetails.dataPoints[pointIndex + 1].isGap == true) { - _strokePath.lineTo(x, previousPointY!); - _strokePath.lineTo(x, y); - if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(originPoint.x, originPoint.y); - } else if (stepAreaSeries.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(originPoint.x, originPoint.y); - _strokePath.close(); - } - _path.lineTo(x, previousPointY); - _path.lineTo(x, y); - _path.lineTo(originPoint.x, originPoint.y); - } else { - _path.lineTo(x, previousPointY!); - _strokePath.lineTo(x, previousPointY); - _strokePath.lineTo(x, y); - _path.lineTo(x, y); - if (closed) { - _path.lineTo(originPoint.x, originPoint.y); - if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(originPoint.x, originPoint.y); - } else if (stepAreaSeries.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(originPoint.x, originPoint.y); - _strokePath.close(); - } - } - } - } - - /// To find the visibility of a series - bool _getSeriesVisibility( - List> points, int index) { - for (int i = index; i < points.length - 1; i++) { - if (!points[i + 1].isDrop) { - return false; - } - } - return true; - } -} 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 deleted file mode 100644 index 9c2a4c970..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart +++ /dev/null @@ -1,350 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for step line series. -class StepLineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of StepLineSeriesRenderer class. - StepLineSeriesRenderer(); - - /// Step line segment is created here. - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - num midX, - num midY, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final StepLineSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - final List _oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - seriesRendererDetails.isRectSeries = false; - segment.currentSegmentIndex = pointIndex; - segmentProperties.seriesIndex = seriesIndex; - segmentProperties.seriesRenderer = this; - segmentProperties.series = - seriesRendererDetails.series as XyDataSeries; - segmentProperties.currentPoint = currentPoint; - segmentProperties.midX = midX; - segmentProperties.midY = midY; - segmentProperties.nextPoint = _nextPoint; - segment.animationFactor = animateFactor; - segmentProperties.pointColorMapper = currentPoint.pointColorMapper; - if (seriesRendererDetails - .stateProperties.renderingDetails.widgetNeedUpdate == - true && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - _oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - _oldSeriesRenderers[segmentProperties.seriesIndex]; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1)); - segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - seriesRendererDetails.segments.add(segment); - return segment; - } - - /// To render step line series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StepLineSegment createSegment() => StepLineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = seriesRendererDetails.seriesColor; - segmentProperties.strokeColor = seriesRendererDetails.seriesColor; - segmentProperties.strokeWidth = segmentProperties.series.width; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the Step line chart painter -class StepLineChartPainter extends CustomPainter { - /// Calling the default constructor of StepLineChartPainter class. - StepLineChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final Animation animationController; - - /// Specifies the step line series renderer - final StepLineSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for step line series - @override - void paint(Canvas canvas, Size size) { - double animationFactor; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - final StepLineSeries series = - seriesRendererDetails.series as StepLineSeries; - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - if (seriesRendererDetails.visible! == true) { - canvas.save(); - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - if (seriesRendererDetails.reAnimate == true || - ((!(renderingDetails.widgetNeedUpdate || - renderingDetails.isLegendToggled) || - !stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - stateProperties, xAxisDetails.axis, canvas, animationFactor); - } - int segmentIndex = -1; - CartesianChartPoint? startPoint, - endPoint, - currentPoint, - _nextPoint; - num? midX, midY; - - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - currentPoint = dataPoints[pointIndex]; - bool withInXRange = withInRange(currentPoint.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - bool withInYRange = currentPoint != null && - currentPoint.yValue != null && - withInRange(currentPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - bool inRange = withInXRange || withInYRange; - if (!inRange && pointIndex + 1 < dataPoints.length) { - final CartesianChartPoint? nextPoint = - dataPoints[pointIndex + 1]; - withInXRange = withInRange(nextPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = nextPoint != null && - nextPoint.yValue != null && - withInRange(nextPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - - inRange = withInXRange || withInYRange; - if (!inRange && pointIndex - 1 >= 0) { - final CartesianChartPoint? prevPoint = - dataPoints[pointIndex - 1]; - withInXRange = withInRange(prevPoint!.xValue, - seriesRendererDetails.xAxisDetails!.visibleRange!); - withInYRange = prevPoint != null && - prevPoint.yValue != null && - withInRange(prevPoint.yValue, - seriesRendererDetails.yAxisDetails!.visibleRange!); - } - } - if (withInXRange || withInYRange) { - if ((currentPoint.isVisible && !currentPoint.isGap) && - startPoint == null) { - startPoint = currentPoint; - } - 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; - midX = _nextPoint.xValue; - midY = currentPoint.yValue; - } else if (_nextPoint.isDrop) { - _nextPoint = _getDropValue(dataPoints, pointIndex); - midX = _nextPoint?.xValue; - midY = currentPoint.yValue; - } - } - - seriesRendererDetails.calculateRegionData( - stateProperties, - seriesRendererDetails, - seriesIndex, - currentPoint, - pointIndex, - null, - _nextPoint, - midX, - midY); - if (startPoint != null && - endPoint != null && - midX != null && - midY != null) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments(startPoint, midX, midY, endPoint, - segmentIndex += 1, seriesIndex, animationFactor)); - endPoint = startPoint = midX = midY = null; - } - } - } - _drawSeries(canvas, animationFactor, seriesRendererDetails); - } - } - - ///Draw series elements and add cliprect - void _drawSeries(Canvas canvas, double animationFactor, - SeriesRendererDetails seriesRendererDetails) { - final StepLineSeries series = - seriesRendererDetails.series as StepLineSeries; - final Rect clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - - canvas.restore(); - if ((series.animationDuration <= 0 || - (!stateProperties.renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The step line series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (seriesRendererDetails.visible! == true && animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - - /// To get point value in the drop mode - CartesianChartPoint? _getDropValue( - List> points, int pointIndex) { - CartesianChartPoint? value; - for (int i = pointIndex; i < points.length - 1; i++) { - if (!points[i + 1].isDrop) { - value = points[i + 1]; - break; - } - } - return value; - } - - @override - bool shouldRepaint(StepLineChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 57b0b9673..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart +++ /dev/null @@ -1,335 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; - -import '../../../charts.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../common/segment_properties.dart'; -import '../utils/helper.dart'; - -/// Creates series renderer for waterfall series. -class WaterfallSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of WaterfallSeriesRenderer class. - WaterfallSeriesRenderer(); - - late WaterfallSeries _waterfallSeries; - - /// To add waterfall segments in segments list. - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - final WaterfallSegment segment = createSegment(); - final SegmentProperties segmentProperties = - SegmentProperties(seriesRendererDetails.stateProperties, segment); - SegmentHelper.setSegmentProperties(segment, segmentProperties); - - final List? oldSeriesRenderers = - seriesRendererDetails.stateProperties.oldSeriesRenderers; - _waterfallSeries = - seriesRendererDetails.series as WaterfallSeries; - final BorderRadius borderRadius = _waterfallSeries.borderRadius; - segmentProperties.seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segmentProperties.seriesRenderer = this; - segmentProperties.series = _waterfallSeries; - segment.animationFactor = animateFactor; - segmentProperties.currentPoint = currentPoint; - if (seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == true && - ZoomPanBehaviorHelper.getRenderingDetails(seriesRendererDetails - .stateProperties.zoomPanBehaviorRenderer) - .isPinching != - true && - seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - false && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && - SeriesHelper.getSeriesRendererDetails( - oldSeriesRenderers[segmentProperties.seriesIndex]) - .seriesName == - SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .seriesName) { - segmentProperties.oldSeriesRenderer = - oldSeriesRenderers[segmentProperties.seriesIndex]; - final SeriesRendererDetails oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - segmentProperties - .oldPoint = (oldSeriesRendererDetails.segments.isNotEmpty == true && - oldSeriesRendererDetails.segments[0] is WaterfallSegment && - (oldSeriesRendererDetails.dataPoints.length - 1 >= pointIndex) == - true) - ? oldSeriesRendererDetails.dataPoints[pointIndex] - : null; - segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); - } else if (seriesRendererDetails - .stateProperties.renderingDetails.isLegendToggled == - true && - // ignore: unnecessary_null_comparison - seriesRendererDetails.stateProperties.segments != null && - seriesRendererDetails.stateProperties.segments.isNotEmpty == true) { - segmentProperties.oldSeriesVisible = seriesRendererDetails - .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; - WaterfallSegment oldSegment; - for (int i = 0; - i < seriesRendererDetails.stateProperties.segments.length; - i++) { - oldSegment = seriesRendererDetails.stateProperties.segments[i] - as WaterfallSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == - segmentProperties.seriesIndex) { - segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segmentProperties.path = findingRectSeriesDashedBorder( - currentPoint, _waterfallSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (borderRadius != null) { - segment.segmentRect = - getRRectFromRect(currentPoint.region!, borderRadius); - } - segmentProperties.segmentRect = segment.segmentRect; - customizeSegment(segment); - seriesRendererDetails.segments.add(segment); - return segment; - } - - /// To render waterfall series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(this); - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[segment.currentSegmentIndex!], - seriesRendererDetails.chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - WaterfallSegment createSegment() => WaterfallSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final WaterfallSegment waterfallSegment = segment as WaterfallSegment; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - segmentProperties.color = - SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) - .seriesColor; - segmentProperties.negativePointsColor = - _waterfallSeries.negativePointsColor; - segmentProperties.intermediateSumColor = - _waterfallSeries.intermediateSumColor; - segmentProperties.totalSumColor = _waterfallSeries.totalSumColor; - segmentProperties.strokeColor = segmentProperties.series.borderColor; - segmentProperties.strokeWidth = segmentProperties.series.borderWidth; - waterfallSegment.strokePaint = waterfallSegment.getStrokePaint(); - waterfallSegment.fillPaint = waterfallSegment.getFillPaint(); - waterfallSegment.connectorLineStrokePaint = - segmentProperties.getConnectorLineStrokePaint(); - } - - /// Draws the marker with different shapes and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer!); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - -/// Represents the waterfall Chart painter -class WaterfallChartPainter extends CustomPainter { - /// Calling the default constructor of WaterfallChartPainter class. - WaterfallChartPainter( - {required this.stateProperties, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Represents the Cartesian state properties - final CartesianStateProperties stateProperties; - - /// Represents the Cartesian chart. - final SfCartesianChart chart; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController animationController; - - /// Specifies the list of current chart location - List currentChartLocations = []; - - /// Specifies the Waterfall series renderer - WaterfallSeriesRenderer seriesRenderer; - - /// Specifies the painter key value - final PainterKey painterKey; - - /// Painter method for waterfall series - @override - void paint(Canvas canvas, Size size) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // Disposing the old chart segments. - disposeOldSegments(chart, seriesRendererDetails); - - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final List> dataPoints = - seriesRendererDetails.dataPoints; - final WaterfallSeries series = - seriesRendererDetails.series as WaterfallSeries; - final num origin = math.max(yAxisDetails.visibleRange!.minimum, 0); - num currentEndValue = 0, intermediateOrigin = 0, prevEndValue = 0; - num originValue = 0; - if (seriesRendererDetails.visible! == true) { - assert( - // ignore: unnecessary_null_comparison - !(series.animationDuration != null) || series.animationDuration >= 0, - 'The animation duration of the fast line series must be greater or equal to 0.'); - Rect axisClipRect; - double animationFactor; - CartesianChartPoint? point; - canvas.save(); - final int seriesIndex = painterKey.index; - seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); - axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesRendererDetails.seriesAnimation != null - ? seriesRendererDetails.seriesAnimation!.value - : 1; - stateProperties.shader = null; - if (series.onCreateShader != null) { - stateProperties.shader = series.onCreateShader!( - ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); - } - int segmentIndex = -1; - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - currentEndValue += - (point.isIntermediateSum! || point.isTotalSum!) ? 0 : point.yValue; - point.yValue = - point.y = point.isTotalSum! ? currentEndValue : point.yValue; - originValue = point.isIntermediateSum == true - ? intermediateOrigin - // ignore: unnecessary_null_comparison - : ((prevEndValue != null) ? prevEndValue : origin); - originValue = point.isTotalSum! ? 0 : originValue; - point.yValue = point.y = point.isIntermediateSum! - ? currentEndValue - originValue - : point.yValue; - point.endValue = currentEndValue; - point.originValue = originValue; - seriesRendererDetails.calculateRegionData(stateProperties, - seriesRendererDetails, painterKey.index, point, pointIndex); - if (renderingDetails.templates.isNotEmpty && - pointIndex < renderingDetails.templates.length) { - renderingDetails.templates[pointIndex].location = - Offset(point.markerPoint!.x, point.markerPoint!.y); - } - if (point.isVisible && !point.isGap) { - seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - point, segmentIndex += 1, painterKey.index, animationFactor)); - } - if (point.isIntermediateSum!) { - intermediateOrigin = currentEndValue; - } - prevEndValue = currentEndValue; - } - _drawSeries(canvas, animationFactor, seriesRendererDetails); - } - } - - ///Draw series elements and add cliprect - void _drawSeries(Canvas canvas, double animationFactor, - SeriesRendererDetails seriesRendererDetails) { - final WaterfallSeries series = - seriesRendererDetails.series as WaterfallSeries; - final Rect clipRect = calculatePlotOffset( - Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - (!stateProperties.renderingDetails.initialRender! && - seriesRendererDetails.needAnimateSeriesElements == false) || - animationFactor >= stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - // ignore: unnecessary_null_comparison - assert(seriesRenderer != null, - 'The waterfall series should be available to render a marker on it.'); - canvas.clipRect(clipRect); - seriesRendererDetails.renderSeriesElements( - chart, canvas, seriesRendererDetails.seriesElementAnimation); - } - if (animationFactor >= 1) { - stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } - - @override - bool shouldRepaint(WaterfallChartPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index 4ad7eb167..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// This class holds the properties of the accumulation distribution indicator. -/// -/// Accumulation distribution indicator is a volume-based indicator designed to measure the accumulative flow of money into and out of a security. -/// It requires [volumeValueMapper] property additionally with the data source to calculate the signal line. -/// -/// It provides options for series visible, axis name, series name, animation duration, legend visibility, -/// signal line width, and color. -@immutable -class AccumulationDistributionIndicator - extends TechnicalIndicators { - /// Creating an argument constructor of AccumulationDistributionIndicator class. - AccumulationDistributionIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - List? dataSource, - ChartValueMapper? xValueMapper, - ChartValueMapper? highValueMapper, - ChartValueMapper? lowValueMapper, - ChartValueMapper? closeValueMapper, - ChartValueMapper? volumeValueMapper, - String? name, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - Color? signalLineColor, - double? signalLineWidth, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : volumeValueMapper = (volumeValueMapper != null) - ? ((int index) => volumeValueMapper(dataSource![index], index)) - : null, - super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Volume of series. - /// - /// This value is mapped to the series. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// AccumulationDistributionIndicator( - /// seriesName: 'Series1', - /// volumeValueMapper: (dynamic data, _) => data.y, - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// volumeValueMapper: (Sample sales, _) => sales.volume, - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final ChartIndexedValueMapper? volumeValueMapper; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AccumulationDistributionIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.closeValueMapper == closeValueMapper && - other.volumeValueMapper == volumeValueMapper && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - closeValueMapper, - volumeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index a0201b787..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// This class holds the properties of the average true range indicator. -/// -/// ATR indicator is a technical analysis volatility indicator. This indicator does not indicate the price trend, -/// simply the degree of price volatility. The average true range is an N-day smoothed moving average (SMA) of the true range values. -/// -/// Provides options for series visible, axis name, series name, animation duration, legend visibility, -/// signal line width, and color to customize the appearance of indicator. -@immutable -class AtrIndicator extends TechnicalIndicators { - /// Creating an argument constructor of AtrIndicator class. - AtrIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - List? dataSource, - ChartValueMapper? xValueMapper, - ChartValueMapper? highValueMapper, - ChartValueMapper? lowValueMapper, - ChartValueMapper? closeValueMapper, - String? name, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - String? legendItemText, - Color? signalLineColor, - double? signalLineWidth, - int? period, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AtrIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - period - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 17a35c15b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart +++ /dev/null @@ -1,238 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// This class has the property of bollinger band indicator. -/// -/// This indicator also has [upperLineColor], [lowerLineColor] property for defining the brushes for the indicator lines. -/// Also, we can specify standard deviation values for the [BollingerBandIndicator] using [standardDeviation] property. -/// -/// Provides options for series visible, axis name, series name, animation duration, legend visibility, -/// band color to customize the appearance. -@immutable -class BollingerBandIndicator extends TechnicalIndicators { - /// Creating an argument constructor of BollingerBandIndicator class. - BollingerBandIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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), - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Standard deviation value of the bollinger band. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// BollingerBandIndicator( - /// period: 2, - /// standardDeviation: 3 - /// ), - /// ], - /// ); - /// } - /// ``` - final int standardDeviation; - - /// Upper line color of the bollinger band. - /// - /// Defaults to `Colors.red`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// BollingerBandIndicator( - /// period: 2, - /// standardDeviation: 3, - /// upperLineColor: Colors.yellow - /// ), - /// ], - /// ); - /// } - /// ``` - final Color upperLineColor; - - /// Upper line width value of the bollinger band. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// BollingerBandIndicator( - /// period: 2, - /// standardDeviation: 3, - /// upperLineWidth: 3 - /// ), - /// ], - /// ); - /// } - /// ``` - final double upperLineWidth; - - /// Lower line color value of the bollinger band. - /// - /// Defaults to `Colors.green`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// BollingerBandIndicator( - /// period: 2, - /// standardDeviation: 3, - /// lowerLineColor: Colors.red - /// ), - /// ], - /// ); - /// } - /// ``` - final Color lowerLineColor; - - /// Lower line width value of the bollinger band. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// BollingerBandIndicator( - /// period: 2, - /// standardDeviation: 3, - /// lowerLineWidth: 4 - /// ), - /// ], - /// ); - /// } - /// ``` - final double lowerLineWidth; - - /// Band color of the bollinger band. - /// - /// Default is `Color(0x409e9e9e)`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// BollingerBandIndicator( - /// period: 2, - /// standardDeviation: 3, - /// bandColor: Colors.blue - /// ), - /// ], - /// ); - /// } - /// ``` - final Color bandColor; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is BollingerBandIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.standardDeviation == standardDeviation && - other.upperLineColor == upperLineColor && - other.upperLineWidth == upperLineWidth && - other.lowerLineColor == lowerLineColor && - other.lowerLineWidth == lowerLineWidth && - other.bandColor == bandColor; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - period, - standardDeviation, - upperLineColor, - upperLineWidth, - lowerLineColor, - lowerLineWidth, - bandColor - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index d955c69d9..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// Renders EMA indicator -/// -/// An EMA indicator is a simple, arithmetic moving average that is calculated by adding the -/// closing price for the number of time periods and dividing the total value by the number of periods. -/// -/// It also has the [valueField] property. Based on this property indicator will be rendered. -@immutable -class EmaIndicator extends TechnicalIndicators { - /// Creating an argument constructor of EmaIndicator class. - EmaIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : valueField = (valueField ?? 'close').toLowerCase(), - super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - openValueMapper: openValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Value field property for EMA indicator. - /// - /// Based on the value field property, the moving average is calculated and the indicator is rendered. - /// - /// Defaults to `close`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// EmaIndicator( - /// seriesName: 'Series1' - /// period: 4, - /// valueField: 'low' - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final String valueField; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is EmaIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.valueField == valueField; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - openValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - valueField, - period - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index df0bcf862..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart +++ /dev/null @@ -1,294 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../utils/enum.dart'; -import 'technical_indicator.dart'; - -/// This class holds the properties of the Macd Indicator. -/// -/// The macd indicator has [shortPeriod] and [longPeriod] for defining the motion of the indicator. -/// Also, you can draw line, histogram macd or both using the [macdType] property. -/// -/// The [macdLineColor] property is used to define the color for the -/// macd line and the [histogramNegativeColor] and [histogramPositiveColor] property is used to define the color for the macd histogram. -/// -/// Provides the options of macd type, name, short Period, long period and macd line color is used to customize the appearance. -@immutable -class MacdIndicator extends TechnicalIndicators { - /// Creating an argument constructor of MacdIndicator class. - MacdIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Short period value of the macd indicator. - /// - /// Defaults to `12`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// shortPeriod: 2 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final int shortPeriod; - - /// Long period value of the macd indicator. - /// - /// Defaults to `26`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// longPeriod: 31 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final int longPeriod; - - /// Macd line color of the macd indicator. - /// - /// Defaults to `Colors.orange`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// macdLineColor: Colors.red - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color macdLineColor; - - /// Macd line width of the macd indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// macdLineWidth: 3 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double macdLineWidth; - - /// Macd type line of the macd indicator. - /// - /// Defaults to `MacdType.both`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// macdType: MacdType.histogram - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final MacdType macdType; - - /// Histogram positive color of the macd indicator. - /// - /// Defaults to `Colors.green`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// macdType: MacdType.histogram, - /// histogramPositiveColor: Colors.red - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color histogramPositiveColor; - - /// Histogram negative color of the macd indicator. - /// - /// Defaults to `Colors.red`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MacdIndicator( - /// seriesName: 'Series1', - /// macdType: MacdType.histogram, - /// histogramNegativeColor: Colors.green - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color histogramNegativeColor; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MacdIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.shortPeriod == shortPeriod && - other.longPeriod == longPeriod && - other.macdLineColor == macdLineColor && - other.macdLineWidth == macdLineWidth && - other.macdType == macdType && - other.histogramPositiveColor == histogramPositiveColor && - other.histogramNegativeColor == histogramNegativeColor; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - period, - shortPeriod, - longPeriod, - macdLineColor, - macdLineWidth, - macdType, - histogramPositiveColor, - histogramNegativeColor - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 4316c5d22..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// Renders the momentum indicator. -/// -/// This class renders the momentum indicator, it also has a center line. The [centerLineColor] and [centerLineWidth] -/// property is used to define center line. -/// -/// Provides the options for visibility, center line color, center line width, and period values to customize the appearance. -/// -@immutable -class MomentumIndicator extends TechnicalIndicators { - /// Creating an argument constructor of MomentumIndicator class. - MomentumIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - openValueMapper: openValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Center line color of the momentum indicator. - /// - /// Defaults to `Colors.red`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MomentumIndicator( - /// seriesName: 'Series1' - /// centerLineColor : Colors.green - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color centerLineColor; - - /// Center line width of the momentum indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// MomentumIndicator( - /// seriesName: 'Series1' - /// centerLineWidth: 3 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double centerLineWidth; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MomentumIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.centerLineColor == centerLineColor && - other.centerLineWidth == centerLineWidth; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - openValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - period, - centerLineColor, - centerLineWidth - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 45f8af60a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart +++ /dev/null @@ -1,299 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// Renders relative strength index (RSI) indicator. -/// -/// The relative strength index (RSI) is a momentum indicator that measures the magnitude of recent price -/// changes to evaluate [overbought] or [oversold] conditions. -/// -/// The RSI indicator has additional two lines other than the signal line.They indicate the [overbought] and [oversold] region. -/// -/// The [upperLineColor] property is used to define the color for the line that indicates [overbought] region, and -/// the [lowerLineColor] property is used to define the color for the line that indicates [oversold] region. -@immutable -class RsiIndicator extends TechnicalIndicators { - /// Creating an argument constructor of RsiIndicator class. - RsiIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Show zones boolean value for RSI indicator. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// showZones : false - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final bool showZones; - - /// Overbought value for RSI indicator. - /// - /// Defaults to `80`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// overbought : 50 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double overbought; - - /// Oversold value for RSI indicator. - /// - /// Defaults to `20`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// oversold : 30 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double oversold; - - /// Color of the upper line for RSI indicator. - /// - /// Defaults to `Colors.red`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// upperLineColor : Colors.greenAccent - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color upperLineColor; - - /// Width of the upper line for RSI indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// upperLineWidth : 4.0 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double upperLineWidth; - - /// Color of the lower line for RSI indicator. - /// - /// Defaults to `Colors.green`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// lowerLineColor : Colors.blue - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color lowerLineColor; - - /// Width of the lower line for RSI indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// RsiIndicator( - /// seriesName: 'Series1' - /// lowerLineWidth : 4.0 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double lowerLineWidth; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is RsiIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.showZones == showZones && - other.overbought == overbought && - other.oversold == oversold && - other.upperLineColor == upperLineColor && - other.upperLineWidth == upperLineWidth && - other.lowerLineColor == lowerLineColor && - other.lowerLineWidth == lowerLineWidth; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - period, - showZones, - overbought, - oversold, - upperLineColor, - upperLineWidth, - lowerLineColor, - lowerLineWidth - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 1f7216b65..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// Renders simple moving average (SMA) indicator. -/// -/// A simple moving average (SMA) is an arithmetic moving average calculated by adding recent closing prices and -/// then dividing the total by the number of time periods in the calculation average. -/// -/// It also has a [valueField] property. Based on this property, the indicator will be rendered. -@immutable -class SmaIndicator extends TechnicalIndicators { - /// Creating an argument constructor of SmaIndicator class. - SmaIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : valueField = (valueField ?? 'close').toLowerCase(), - super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - openValueMapper: openValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Value field value for SMA indicator. - /// - /// Value field determines the field for the rendering of SMA indicator. - /// - /// Defaults to `close`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// SmaIndicator( - /// seriesName: 'Series1' - /// period: 4, - /// valueField: 'low' - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final String valueField; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is SmaIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.valueField == valueField; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - openValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - valueField, - period - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index a608e2f15..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart +++ /dev/null @@ -1,405 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// Renders stochastic indicator. -/// -/// The stochastic indicator is used to measure the range and momentum of price movements. It contains kPeriod and dPeriod properties defining -/// the ‘k’ percentage and ‘d’ percentage respectively. -/// -/// In this indicator [upperLineColor], [lowerLineColor] and [periodLineColor] property are used to define the color for -/// the stochastic indicator lines. -@immutable -class StochasticIndicator extends TechnicalIndicators { - /// Creating an argument constructor of StochasticIndicator class. - StochasticIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - openValueMapper: openValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// Show zones boolean value for stochastic indicator. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// showZones : false - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final bool showZones; - - /// Overbought value for stochastic indicator - /// - /// Defaults to `80`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// overbought: 50 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double overbought; - - /// Oversold value for stochastic Indicator. - /// - /// Defaults to `20`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// oversold: 30 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double oversold; - - /// Color of the upper line for stochastic Indicator. - /// - /// Defaults to `Colors.red`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// upperLineColor: Colors.greenAccent - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color upperLineColor; - - /// Width of the upper line for stochastic Indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// upperLineWidth: 4.0 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double upperLineWidth; - - /// Color of the lower line for stochastic Indicator. - /// - /// Defaults to `Colors.green`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// lowerLineColor: Colors.blue - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color lowerLineColor; - - /// Width of lower line for stochastic Indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// lowerLineWidth: 4.0 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double lowerLineWidth; - - /// Color of the period line for stochastic Indicator. - /// - /// Defaults to `Colors.yellow`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// periodLineColor: Colors.orange - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final Color periodLineColor; - - /// Width of the period line for stochastic Indicator. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// periodLineWidth: 5.0 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final double periodLineWidth; - - /// Value of kPeriod in stochastic Indicator. - /// - /// Defaults to `3`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// kPeriod: 4 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final num kPeriod; - - /// Value of dPeriod in stochastic Indicator. - /// - /// Defaults to `5`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// dPeriod: 4 - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final num dPeriod; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is StochasticIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.showZones == showZones && - other.overbought == overbought && - other.oversold == oversold && - other.upperLineColor == upperLineColor && - other.upperLineWidth == upperLineWidth && - other.lowerLineColor == lowerLineColor && - other.lowerLineWidth == lowerLineWidth && - other.periodLineColor == periodLineColor && - other.periodLineWidth == periodLineWidth && - other.kPeriod == kPeriod && - other.dPeriod == dPeriod; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - openValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - period, - showZones, - overbought, - oversold, - upperLineColor, - upperLineWidth, - lowerLineColor, - lowerLineWidth, - periodLineColor, - periodLineWidth, - kPeriod, - dPeriod - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 4e9a1f3f8..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart +++ /dev/null @@ -1,1862 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import '../../common/event_args.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../base/chart_base.dart'; -import '../chart_series/column_series.dart'; -import '../chart_series/line_series.dart'; -import '../chart_series/range_area_series.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../series_painter/column_painter.dart'; -import '../series_painter/line_painter.dart'; -import '../series_painter/range_area_painter.dart'; -import '../utils/enum.dart'; -import 'accumulation_distribution_indicator.dart'; -import 'atr_indicator.dart'; -import 'bollinger_bands_indicator.dart'; -import 'ema_indicator.dart'; -import 'macd_indicator.dart'; -import 'momentum_indicator.dart'; -import 'rsi_indicator.dart'; -import 'sma_indicator.dart'; -import 'stochastic_indicator.dart'; -import 'tma_indicator.dart'; - -/// Customize the technical indicators. -/// -/// The technical indicator is a mathematical calculation based on historical price, volume or (in the case of futures contracts) open interest information, -/// which is intended to predict the direction of the financial market. -/// -/// Indicators generally overlay the chart data to show the data flow over a period of time. -/// -/// _Note:_ This property is applicable only for financial chart series types. -class TechnicalIndicators { - /// Creating an argument constructor of TechnicalIndicators class. - TechnicalIndicators( - {bool? isVisible, - this.xAxisName, - this.yAxisName, - this.seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - this.dataSource, - ChartValueMapper? xValueMapper, - ChartValueMapper? lowValueMapper, - ChartValueMapper? highValueMapper, - ChartValueMapper? openValueMapper, - ChartValueMapper? closeValueMapper, - this.name, - this.onRenderDetailsUpdate, - bool? isVisibleInLegend, - LegendIconType? legendIconType, - this.legendItemText, - Color? signalLineColor, - double? signalLineWidth, - int? period}) - : isVisible = isVisible ?? true, - dashArray = dashArray ?? [0, 0], - animationDuration = animationDuration ?? 1500, - animationDelay = animationDelay ?? 0, - isVisibleInLegend = isVisibleInLegend ?? true, - legendIconType = legendIconType ?? LegendIconType.seriesType, - signalLineColor = signalLineColor ?? Colors.blue, - signalLineWidth = signalLineWidth ?? 2, - period = period ?? 14, - xValueMapper = (xValueMapper != null) - ? ((int index) => xValueMapper(dataSource![index], index)) - : null, - lowValueMapper = (lowValueMapper != null) - ? ((int index) => lowValueMapper(dataSource![index], index)) - : null, - highValueMapper = (highValueMapper != null) - ? ((int index) => highValueMapper(dataSource![index], index)) - : null, - openValueMapper = (openValueMapper != null) - ? ((int index) => openValueMapper(dataSource![index], index)) - : null, - closeValueMapper = (closeValueMapper != null) - ? ((int index) => closeValueMapper(dataSource![index], index)) - : null; - - /// Boolean property to change the visibility of the technical indicators. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// isVisible: false - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isVisible; - - /// Property to map the technical indicators with the axes. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// primaryXAxis: NumericAxis(name: 'xAxis') - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// xAxisName: 'xAxis', - /// showZones : false - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final String? xAxisName; - - /// Property to map the technical indicators with the axes. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// primaryYAxis: NumericAxis(name: 'yAxis') - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1', - /// xAxisName: 'yAxis', - /// showZones : false - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final String? yAxisName; - - /// Property to link indicators to a series based on names. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// seriesName: 'Series1' - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final String? seriesName; - - /// Property to provide dash array for the technical indicators. - /// - /// Defaults to `[0, 0]`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dashArray: [2, 3] - /// ), - /// ], - /// ); - /// } - /// ``` - final List dashArray; - - /// Animation duration for the technical indicators. - /// - /// Defaults to `1500`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// animationDuration: 1000 - /// ), - /// ], - /// ); - /// } - /// ``` - final double animationDuration; - - /// Delay duration of the technical indicator's animation. - /// It takes a millisecond value as input. - /// By default, the technical indicator will get animated for the specified duration. - /// If animationDelay is specified, then the technical indicator will begin to animate - /// after the specified duration. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// animationDelay: 500 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? animationDelay; - - /// Property to provide data for the technical indicators without any series. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dataSource: chartData, - /// xValueMapper: (chartData data, _) => data.x, - /// lowValueMapper: (chartData data, _) => data.low, - /// highValueMapper: (chartData data, _) => data.high, - /// openValueMapper: (chartData data, _) => data.open, - /// closeValueMapper: (chartData data, _) => data.close, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ChartData(1, 23, 50, 28, 38), - /// ChartData(2, 35, 80, 58, 78), - /// ChartData(3, 19, 90, 38, 58) - /// ]; - /// - /// class ChartData { - /// ChartData(this.x, this.low, this.high, this.open, this.close); - /// final double x; - /// final double low; - /// final double high; - /// final double open; - /// final double close; - /// } - /// ``` - final List? dataSource; - - /// Value mapper to map the x values with technical indicators. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dataSource: chartData, - /// xValueMapper: (chartData data, _) => data.x, - /// lowValueMapper: (chartData data, _) => data.low, - /// highValueMapper: (chartData data, _) => data.high, - /// openValueMapper: (chartData data, _) => data.open, - /// closeValueMapper: (chartData data, _) => data.close, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ChartData(1, 23, 50, 28, 38), - /// ChartData(2, 35, 80, 58, 78), - /// ChartData(3, 19, 90, 38, 58) - /// ]; - /// - /// class ChartData { - /// ChartData(this.x, this.low, this.high, this.open, this.close); - /// final double x; - /// final double low; - /// final double high; - /// final double open; - /// final double close; - /// } - /// ``` - final ChartIndexedValueMapper? xValueMapper; - - /// Value mapper to map the low values with technical indicators. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dataSource: chartData, - /// xValueMapper: (chartData data, _) => data.x, - /// lowValueMapper: (chartData data, _) => data.low, - /// highValueMapper: (chartData data, _) => data.high, - /// openValueMapper: (chartData data, _) => data.open, - /// closeValueMapper: (chartData data, _) => data.close, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ChartData(1, 23, 50, 28, 38), - /// ChartData(2, 35, 80, 58, 78), - /// ChartData(3, 19, 90, 38, 58) - /// ]; - /// - /// class ChartData { - /// ChartData(this.x, this.low, this.high, this.open, this.close); - /// final double x; - /// final double low; - /// final double high; - /// final double open; - /// final double close; - /// } - /// ``` - final ChartIndexedValueMapper? lowValueMapper; - - /// Value mapper to map the high values with technical indicators. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dataSource: chartData, - /// xValueMapper: (chartData data, _) => data.x, - /// lowValueMapper: (chartData data, _) => data.low, - /// highValueMapper: (chartData data, _) => data.high, - /// openValueMapper: (chartData data, _) => data.open, - /// closeValueMapper: (chartData data, _) => data.close, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ChartData(1, 23, 50, 28, 38), - /// ChartData(2, 35, 80, 58, 78), - /// ChartData(3, 19, 90, 38, 58) - /// ]; - /// - /// class ChartData { - /// ChartData(this.x, this.low, this.high, this.open, this.close); - /// final double x; - /// final double low; - /// final double high; - /// final double open; - /// final double close; - /// } - /// ``` - final ChartIndexedValueMapper? highValueMapper; - - /// Value mapper to map the open values with technical indicators. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dataSource: chartData, - /// xValueMapper: (chartData data, _) => data.x, - /// lowValueMapper: (chartData data, _) => data.low, - /// highValueMapper: (chartData data, _) => data.high, - /// openValueMapper: (chartData data, _) => data.open, - /// closeValueMapper: (chartData data, _) => data.close, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ChartData(1, 23, 50, 28, 38), - /// ChartData(2, 35, 80, 58, 78), - /// ChartData(3, 19, 90, 38, 58) - /// ]; - /// - /// class ChartData { - /// ChartData(this.x, this.low, this.high, this.open, this.close); - /// final double x; - /// final double low; - /// final double high; - /// final double open; - /// final double close; - /// } - /// ``` - final ChartIndexedValueMapper? openValueMapper; - - /// Value mapper to map the close values with technical indicators. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// dataSource: chartData, - /// xValueMapper: (chartData data, _) => data.x, - /// lowValueMapper: (chartData data, _) => data.low, - /// highValueMapper: (chartData data, _) => data.high, - /// openValueMapper: (chartData data, _) => data.open, - /// closeValueMapper: (chartData data, _) => data.close, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ChartData(1, 23, 50, 28, 38), - /// ChartData(2, 35, 80, 58, 78), - /// ChartData(3, 19, 90, 38, 58) - /// ]; - /// - /// class ChartData { - /// ChartData(this.x, this.low, this.high, this.open, this.close); - /// final double x; - /// final double low; - /// final double high; - /// final double open; - /// final double close; - /// } - /// ``` - final ChartIndexedValueMapper? closeValueMapper; - - /// Property to provide name for the technical indicators. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// name: 'indicators' - /// ), - /// ], - /// ); - /// } - /// ``` - final String? name; - - /// Boolean property to determine the rendering of legends for the technical indicators. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// isVisibleInLegend : false - /// ), - /// ], - /// ); - /// } - /// ``` - final bool isVisibleInLegend; - - /// Property to provide icon type for the technical indicators legend. - /// - /// Defaults to `LegendIconType.seriesType`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// legendIconType: LegendIconType.diamond - /// ), - /// ], - /// ); - /// } - /// ``` - final LegendIconType legendIconType; - - /// Property to provide the text for the technical indicators legend. - /// - /// Defaults to 'null'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// legendItemText : 'SMA', - /// ), - /// ], - /// ); - /// } - /// ``` - final String? legendItemText; - - /// Property to provide the color of the signal line in the technical indicators. - /// - /// Defaults to `Colors.blue`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// signalLineColor : Colors.red, - /// ), - /// ], - /// ); - /// } - /// ``` - final Color signalLineColor; - - /// Property to provide the width of the signal line in the technical indicators. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// signalLineWidth : 4.0 - /// ), - /// ], - /// ); - /// } - /// ``` - final double signalLineWidth; - - /// Period determines the start point for the rendering of technical indicators. - /// - /// Defaults to `14`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// period : 4 - /// ), - /// ], - /// ); - /// } - /// ``` - final int period; - - /// Callback which gets called while rendering the indicators. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// StochasticIndicator( - /// onRenderDetailsUpdate: (IndicatorRenderParams params) { - /// return TechnicalIndicatorRenderDetails(Colors.cyan, 3.0, [5,5]); - /// }, - /// ), - /// ], - /// ); - /// } - /// ``` - final ChartIndicatorRenderCallback? onRenderDetailsUpdate; -} - -/// Technical indicator renderer class for mutable fields and methods. -class TechnicalIndicatorsRenderer { - /// Creates an argument constructor for TechnicalIndicator renderer class. - TechnicalIndicatorsRenderer( - this.technicalIndicatorRenderer, this.stateProperties); - - /// Specifies the technical indicator renderer. - final TechnicalIndicators technicalIndicatorRenderer; - - /// Holds the state properties. - final CartesianStateProperties stateProperties; - - /// Specifies the name value. - late String name; - - /// Specifies whether the indicator is visible. - bool? visible; - - /// Specifies whether it is indicator. - bool isIndicator = true; - - /// Represents the series type. - final String seriesType = 'indicator'; - - /// Represents the indicator type. - late String indicatorType; - - /// Specifies the value of index. - late int index; - - /// Specifies the list of target series renderer. - List targetSeriesRenderers = - []; - - /// Specifies the list of data points. - List>? dataPoints = - >[]; - - /// Used for test case. - List> renderPoints = - >[]; - - /// Used for events. - List>? bollingerUpper = - >[]; - - /// Used for events. - List>? bollingerLower = - >[]; - - /// Used for events. - List>? macdLine = - >[]; - - /// Used for events. - List>? macdHistogram = - >[]; - - /// Used for events. - List>? stochasticperiod = - >[]; - - /// Used for events. - double? momentumCenterLineValue; - - /// To get and return CartesianChartPoint. - CartesianChartPoint getDataPoint( - dynamic x, num y, CartesianChartPoint sourcePoint, int index, - [TechnicalIndicators? indicator]) { - final CartesianChartPoint point = - CartesianChartPoint(x, y); - point.xValue = sourcePoint.xValue; - point.index = index; - point.yValue = y; - point.isVisible = true; - if (indicator is MacdIndicator && - (indicator.macdType == MacdType.histogram || - indicator.macdType == MacdType.both)) { - final MacdIndicator _indicator = indicator; - point.pointColorMapper = point.yValue >= 0 == true - ? _indicator.histogramPositiveColor - : _indicator.histogramNegativeColor; - } - return point; - } - - /// To get chart point of range type series. - CartesianChartPoint getRangePoint(dynamic x, num high, num low, - CartesianChartPoint sourcePoint, int index, - //ignore: unused_element - [TechnicalIndicators? indicator]) { - final CartesianChartPoint point = - CartesianChartPoint(x, null); - point.high = high; - point.low = low; - point.xValue = sourcePoint.xValue; - point.index = index; - point.isVisible = true; - return point; - } - - /// To set properties of technical indicators. - void setSeriesProperties(TechnicalIndicators indicator, - String name, Color color, double width, SfCartesianChart chart, - [bool isLine = false, - bool isRangeArea = false, - bool isHistogram = false]) { - List? _dashArray; - if (indicator.onRenderDetailsUpdate != null && - isRangeArea == false && - isHistogram == false && - isLine == false) { - TechnicalIndicatorRenderDetails indicators; - if (indicator is BollingerBandIndicator) { - final BollingerBandIndicatorRenderParams indicatorRenderParams = - BollingerBandIndicatorRenderParams(bollingerUpper, bollingerLower, - renderPoints, name, width, color, indicator.dashArray); - indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); - } else if (indicator is MomentumIndicator) { - final MomentumIndicatorRenderParams indicatorRenderParams = - MomentumIndicatorRenderParams(momentumCenterLineValue, renderPoints, - name, width, color, indicator.dashArray); - indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); - } else if (indicator is StochasticIndicator) { - final StochasticIndicatorRenderParams indicatorRenderParams = - StochasticIndicatorRenderParams(stochasticperiod, renderPoints, - name, width, color, indicator.dashArray); - indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); - } else if (indicator is MacdIndicator) { - final MacdIndicatorRenderParams indicatorRenderParams = - MacdIndicatorRenderParams(macdLine, macdHistogram, renderPoints, - name, width, color, indicator.dashArray); - indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); - } else { - final IndicatorRenderParams indicatorRenderParams = - IndicatorRenderParams( - renderPoints, name, width, color, indicator.dashArray); - indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); - } - - color = indicators.signalLineColor ?? color; - width = indicators.signalLineWidth ?? width; - _dashArray = indicators.signalLineDashArray ?? indicator.dashArray; - } - final CartesianSeries series = isRangeArea == true - ? RangeAreaSeries( - name: name, - color: color, - dashArray: indicator.dashArray, - borderWidth: width, - xAxisName: indicator.xAxisName, - animationDuration: indicator.animationDuration, - animationDelay: indicator.animationDelay, - yAxisName: indicator.yAxisName, - enableTooltip: false, - //ignore: always_specify_types - xValueMapper: (dynamic, _) => null, - //ignore: always_specify_types - highValueMapper: (dynamic, _) => null, - //ignore: always_specify_types - lowValueMapper: (dynamic, _) => null, - dataSource: []) - : (isHistogram == true - ? ColumnSeries( - name: name, - color: color, - width: width, - xAxisName: indicator.xAxisName, - animationDuration: indicator.animationDuration, - animationDelay: indicator.animationDelay, - yAxisName: indicator.yAxisName, - //ignore: always_specify_types - xValueMapper: (dynamic, _) => null, - //ignore: always_specify_types - yValueMapper: (dynamic, _) => null, - dataSource: []) - : LineSeries( - name: name, - color: color, - dashArray: _dashArray ?? indicator.dashArray, - width: width, - xAxisName: indicator.xAxisName, - animationDuration: indicator.animationDuration, - animationDelay: indicator.animationDelay, - yAxisName: indicator.yAxisName, - //ignore: always_specify_types - xValueMapper: (dynamic, _) => null, - //ignore: always_specify_types - yValueMapper: (dynamic, _) => null, - dataSource: [])); - final CartesianSeriesRenderer seriesRenderer = isRangeArea == true - ? RangeAreaSeriesRenderer() - : (isHistogram == true ? ColumnSeriesRenderer() : LineSeriesRenderer()); - final SeriesRendererDetails seriesRendererDetails = - SeriesRendererDetails(seriesRenderer); - SeriesHelper.setSeriesRendererDetails( - seriesRenderer, seriesRendererDetails); - seriesRendererDetails.stateProperties = stateProperties; - seriesRendererDetails.series = series; - seriesRendererDetails.visible = visible; - seriesRendererDetails.chart = chart; - seriesRendererDetails.seriesType = - isRangeArea ? 'rangearea' : (isHistogram ? 'column' : 'line'); - seriesRendererDetails.isIndicator = true; - seriesRendererDetails.seriesName = name; - targetSeriesRenderers.add(seriesRenderer); - } - - /// Set series range of technical indicators. - void setSeriesRange(List> points, - TechnicalIndicators indicator, List xValues, - [CartesianSeriesRenderer? seriesRenderer]) { - if (seriesRenderer == null) { - SeriesHelper.getSeriesRendererDetails(targetSeriesRenderers[0]) - .dataPoints = points; - SeriesHelper.getSeriesRendererDetails(targetSeriesRenderers[0]).xValues = - xValues; - } else { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - seriesRendererDetails.dataPoints = points; - seriesRendererDetails.xValues = xValues; - } - } - - /// To get the value field value of technical indicators. - num getFieldValue(List?> dataPoints, int index, - String valueField) { - num? val; - if (valueField == 'low') { - val = dataPoints[index]?.low; - } else if (valueField == 'high') { - val = dataPoints[index]?.high; - } else if (valueField == 'open') { - val = dataPoints[index]?.open; - } else if (valueField == 'y') { - val = dataPoints[index]?.y; - } else { - val = dataPoints[index]?.close; - } - - ///ignore: unnecessary_statements - val = val ?? 0; - return val; - } - - /// To initialize data source of technical indicators. - void initDataSource( - TechnicalIndicators indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - technicalIndicatorsRenderer.targetSeriesRenderers = - []; - final List> validData = - technicalIndicatorsRenderer.dataPoints!; - if (validData.isNotEmpty && - indicator is AccumulationDistributionIndicator) { - _calculateADPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } else if (validData.isNotEmpty && - validData.length > indicator.period && - indicator is AtrIndicator && - indicator.period > 0) { - _calculateATRPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } else if (indicator is BollingerBandIndicator && indicator.period > 0) { - _calculateBollingerBandPoints( - indicator, technicalIndicatorsRenderer, chart); - } else if (validData.isNotEmpty && - validData.length > indicator.period && - indicator is EmaIndicator && - indicator.period > 0) { - _calculateEMAPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } else if (indicator is MacdIndicator && indicator.period > 0) { - _calculateMacdPoints(indicator, technicalIndicatorsRenderer, chart); - } else if (indicator is MomentumIndicator && indicator.period > 0) { - _calculateMomentumIndicatorPoints( - indicator, technicalIndicatorsRenderer, chart); - } else if (indicator is RsiIndicator && indicator.period > 0) { - _calculateRSIPoints(indicator, technicalIndicatorsRenderer, chart); - } else if (indicator is SmaIndicator && indicator.period > 0) { - _calculateSMAPoints(indicator, technicalIndicatorsRenderer, chart); - } else if (indicator is StochasticIndicator && indicator.period > 0) { - _calculateStochasticIndicatorPoints( - indicator, technicalIndicatorsRenderer, chart); - } else if (validData.isNotEmpty && - validData.length > indicator.period && - indicator is TmaIndicator && - indicator.period > 0) { - _calculateTMAPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } - } - - /// To calculate the rendering points of the accumulation distribution indicator. - void _calculateADPoints( - AccumulationDistributionIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - num sum = 0, value = 0, high = 0, low = 0, close = 0; - 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!; - point = technicalIndicatorsRenderer.getDataPoint( - validData[i].x, sum, validData[i], points.length); - points.add(point); - xValues.add(point.x); - } - technicalIndicatorsRenderer.renderPoints = points; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'AD', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); - } - - /// To calculate the rendering points of the ATR indicator. - void _calculateATRPoints( - AtrIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - num average = 0; - num highLow = 0, highClose = 0, lowClose = 0, trueRange = 0, tempRange = 0; - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - final List<_TempData> temp = <_TempData>[]; - final num period = indicator.period; - num sum = 0; - for (int i = 0; i < validData.length; i++) { - if (!validData[i].isDrop && !validData[i].isGap) { - highLow = validData[i].high - validData[i].low; - if (i > 0) { - highClose = (validData[i].high - (validData[i - 1].close ?? 0)).abs(); - lowClose = (validData[i].low - (validData[i - 1].close ?? 0)).abs(); - } - tempRange = math.max(highLow, highClose); - trueRange = math.max(tempRange, lowClose); - sum = sum + trueRange; - if (i >= period && period > 0) { - average = - (temp[temp.length - 1].y * (period - 1) + trueRange) / period; - point = technicalIndicatorsRenderer.getDataPoint( - validData[i].x, average, validData[i], points.length); - points.add(point); - xValues.add(point.x); - } else { - average = sum / period; - if (i == period - 1) { - point = technicalIndicatorsRenderer.getDataPoint( - validData[i].x, average, validData[i], points.length); - points.add(point); - xValues.add(point.x); - } - } - temp.add(_TempData(validData[i].x, average)); - } - } - technicalIndicatorsRenderer.renderPoints = points; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'ATR', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); - } - - void _calculateBollingerBandPoints( - BollingerBandIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final bool enableBand = indicator.bandColor != Colors.transparent && - // ignore: unnecessary_null_comparison - indicator.bandColor != null; - final int start = enableBand ? 1 : 0; - final List> signalCollection = - >[], - upperCollection = >[], - lowerCollection = >[], - bandCollection = >[]; - final List xValues = []; - - //prepare data - final List> validData = - technicalIndicatorsRenderer.dataPoints!; - if (validData.isNotEmpty && - validData.length >= indicator.period && - indicator.period > 0) { - num sum = 0, deviationSum = 0; - final num multiplier = indicator.standardDeviation; - final int limit = validData.length, length = indicator.period.round(); - // This has been null before - final List smaPoints = List.filled(limit, -1), - 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; - } - num sma = sum / indicator.period; - for (int i = 0; i < limit; i++) { - final num y = validData[i].close ?? 0; - if (i >= length - 1 && i < limit) { - if (i - indicator.period >= 0) { - final num diff = y - validData[i - length].close; - sum = sum + diff; - sma = sum / (indicator.period); - smaPoints[i] = sma; - deviations[i] = math.pow(y - sma, 2); - deviationSum += deviations[i] - deviations[i - length]; - } else { - smaPoints[i] = sma; - deviations[i] = math.pow(y - sma, 2); - deviationSum += deviations[i]; - } - final num range = math.sqrt(deviationSum / (indicator.period)); - final num lowerBand = smaPoints[i] - (multiplier * range); - final num upperBand = smaPoints[i] + (multiplier * range); - if (i + 1 == length) { - for (int j = 0; j < length - 1; j++) { - bollingerPoints[j] = _BollingerData( - x: validData[j].xValue, - midBand: smaPoints[i], - lowBand: lowerBand, - upBand: upperBand, - visible: true); - } - } - bollingerPoints[i] = _BollingerData( - x: validData[i].xValue, - midBand: smaPoints[i], - lowBand: lowerBand, - upBand: upperBand, - visible: true); - } else { - if (i < indicator.period - 1) { - smaPoints[i] = sma; - deviations[i] = math.pow(y - sma, 2); - deviationSum += deviations[i]; - } - } - } - int i = -1, j = -1; - for (int k = 0; k < limit; k++) { - if (k >= (length - 1)) { - xValues.add(validData[k].x); - upperCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[k].x, - bollingerPoints[k].upBand, - validData[k], - upperCollection.length)); - lowerCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[k].x, - bollingerPoints[k].lowBand, - validData[k], - lowerCollection.length)); - signalCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[k].x, - bollingerPoints[k].midBand, - validData[k], - signalCollection.length)); - if (enableBand) { - bandCollection.add(technicalIndicatorsRenderer.getRangePoint( - validData[k].x, - upperCollection[++i].y, - lowerCollection[++j].y, - validData[k], - bandCollection.length)); - } - } - } - } - technicalIndicatorsRenderer.renderPoints = signalCollection; - technicalIndicatorsRenderer.bollingerUpper = upperCollection; - technicalIndicatorsRenderer.bollingerLower = lowerCollection; - // Decides the type of renderer class to be used - bool isLine, isRangeArea; - if (indicator.bandColor != Colors.transparent && - // ignore: unnecessary_null_comparison - indicator.bandColor != null) { - isLine = false; - isRangeArea = true; - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'rangearea', - indicator.bandColor, 0, chart, isLine, isRangeArea); - } - isLine = true; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'BollingerBand', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); - if (enableBand) { - technicalIndicatorsRenderer.setSeriesRange(bandCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[0]); - } - technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[start]); - technicalIndicatorsRenderer.setSeriesRange(upperCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[start + 1]); - technicalIndicatorsRenderer.setSeriesRange(lowerCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[start + 2]); - } - - /// To calculate the rendering points of the EMA indicator. - void _calculateEMAPoints( - EmaIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final int period = indicator.period; - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - if (validData.length >= period && period > 0) { - num sum = 0, average = 0; - final num k = 2 / (period + 1); - for (int i = 0; i < period; i++) { - sum += technicalIndicatorsRenderer.getFieldValue( - validData, i, indicator.valueField); - } - average = sum / period; - point = technicalIndicatorsRenderer.getDataPoint(validData[period - 1].x, - average, validData[period - 1], points.length); - points.add(point); - xValues.add(point.x); - int index = period; - while (index < validData.length) { - if (validData[index].isVisible || - validData[index].isGap == true || - validData[index].isDrop == true) { - final num prevAverage = points[index - period].y; - final num yValue = (technicalIndicatorsRenderer.getFieldValue( - validData, index, indicator.valueField) - - prevAverage) * - k + - prevAverage; - point = technicalIndicatorsRenderer.getDataPoint( - validData[index].x, yValue, validData[index], points.length); - points.add(point); - xValues.add(point.x); - } - index++; - } - } - technicalIndicatorsRenderer.renderPoints = points; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'EMA', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); - } - - void _calculateMacdPoints( - MacdIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - List> signalCollection = - >[]; - final num fastPeriod = indicator.longPeriod; - final num slowPeriod = indicator.shortPeriod; - final num trigger = indicator.period; - final num length = fastPeriod + trigger; - List> macdCollection = - >[], - histogramCollection = >[]; - final List> validData = - technicalIndicatorsRenderer.dataPoints!; - List signalX = [], - histogramX = [], - macdX = [], - collection; - CartesianSeriesRenderer? histogramSeriesRenderer, macdLineSeriesRenderer; - - if (validData.isNotEmpty && - length < validData.length && - slowPeriod <= fastPeriod && - slowPeriod > 0 && - indicator.period > 0 && - (length - 2) >= 0) { - final List shortEMA = _calculateEMAValues( - slowPeriod, validData, 'close', technicalIndicatorsRenderer); - final List longEMA = _calculateEMAValues( - fastPeriod, validData, 'close', technicalIndicatorsRenderer); - final List macdValues = _getMACDVales(indicator, shortEMA, longEMA); - collection = _getMACDPoints( - indicator, macdValues, validData, technicalIndicatorsRenderer); - macdCollection = collection[0]; - macdX = collection[1]; - final List signalEMA = _calculateEMAValues( - trigger, macdCollection, 'y', technicalIndicatorsRenderer); - collection = _getSignalPoints( - indicator, signalEMA, validData, technicalIndicatorsRenderer); - signalCollection = collection[0]; - signalX = collection[1]; - if (indicator.macdType == MacdType.histogram || - indicator.macdType == MacdType.both) { - collection = _getHistogramPoints(indicator, macdValues, signalEMA, - validData, technicalIndicatorsRenderer); - histogramCollection = collection[0]; - histogramX = collection[1]; - } - } - technicalIndicatorsRenderer.renderPoints = signalCollection; - technicalIndicatorsRenderer.macdHistogram = histogramCollection; - technicalIndicatorsRenderer.macdLine = macdCollection; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'MACD', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - // To describe the type of series renderer to be assigned. - bool isLine, isRangeArea, isHistogram; - 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, 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, - isLine, - isRangeArea, - isHistogram); - } - if (indicator.macdType == MacdType.histogram) { - histogramSeriesRenderer = - technicalIndicatorsRenderer.targetSeriesRenderers[1]; - } else { - macdLineSeriesRenderer = - technicalIndicatorsRenderer.targetSeriesRenderers[1]; - if (indicator.macdType == MacdType.both) { - histogramSeriesRenderer = - technicalIndicatorsRenderer.targetSeriesRenderers[2]; - } - } - technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, - signalX, technicalIndicatorsRenderer.targetSeriesRenderers[0]); - if (histogramSeriesRenderer != null) { - technicalIndicatorsRenderer.setSeriesRange( - histogramCollection, indicator, histogramX, histogramSeriesRenderer); - } - if (macdLineSeriesRenderer != null) { - technicalIndicatorsRenderer.setSeriesRange( - macdCollection, indicator, macdX, macdLineSeriesRenderer); - } - } - - /// Calculates the EMA values for the given period. - List _calculateEMAValues( - num period, - List> validData, - String valueField, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - num sum = 0, initialEMA = 0; - final List emaValues = []; - final num emaPercent = 2 / (period + 1); - for (int i = 0; i < period; i++) { - sum += - technicalIndicatorsRenderer.getFieldValue(validData, i, valueField); - } - initialEMA = sum / period; - emaValues.add(initialEMA); - num emaAvg = initialEMA; - for (int j = period.toInt(); j < validData.length; j++) { - emaAvg = - (technicalIndicatorsRenderer.getFieldValue(validData, j, valueField) - - emaAvg) * - emaPercent + - emaAvg; - emaValues.add(emaAvg); - } - return emaValues; - } - - /// Defines the MACD Points. - List _getMACDPoints( - MacdIndicator indicator, - List macdPoints, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - final List> macdCollection = - >[]; - final List xValues = []; - int dataMACDIndex = indicator.longPeriod - 1, macdIndex = 0; - while (dataMACDIndex < validData.length) { - macdCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[dataMACDIndex].x, - macdPoints[macdIndex], - validData[dataMACDIndex], - macdCollection.length)); - xValues.add(validData[dataMACDIndex].x); - dataMACDIndex++; - macdIndex++; - } - return [macdCollection, xValues]; - } - - /// Calculates the signal points. - List _getSignalPoints( - MacdIndicator indicator, - List signalEma, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - int dataSignalIndex = indicator.longPeriod + indicator.period - 2; - int signalIndex = 0; - final List xValues = []; - final List> signalCollection = - >[]; - while (dataSignalIndex < validData.length) { - signalCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[dataSignalIndex].x, - signalEma[signalIndex], - validData[dataSignalIndex], - signalCollection.length)); - xValues.add(validData[dataSignalIndex].x); - dataSignalIndex++; - signalIndex++; - } - return [signalCollection, xValues]; - } - - /// Calculates the MACD values. - List _getMACDVales(MacdIndicator indicator, - List shortEma, List longEma) { - final List macdPoints = []; - final int diff = indicator.longPeriod - indicator.shortPeriod; - for (int i = 0; i < longEma.length; i++) { - macdPoints.add(shortEma[i + diff] - longEma[i]); - } - return macdPoints; - } - - /// Calculates the Histogram Points. - List _getHistogramPoints( - MacdIndicator indicator, - List macdPoints, - List signalEma, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - int dataHistogramIndex = indicator.longPeriod + indicator.period - 2; - int histogramIndex = 0; - final List> histogramCollection = - >[]; - final List xValues = []; - while (dataHistogramIndex < validData.length) { - histogramCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[dataHistogramIndex].x, - macdPoints[histogramIndex + (indicator.period - 1)] - - signalEma[histogramIndex], - validData[dataHistogramIndex], - histogramCollection.length, - indicator)); - xValues.add(validData[dataHistogramIndex].x); - dataHistogramIndex++; - histogramIndex++; - } - return [histogramCollection, xValues]; - } - - void _calculateMomentumIndicatorPoints( - MomentumIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> signalCollection = - >[], - centerLineCollection = >[], - validData = technicalIndicatorsRenderer.dataPoints!; - final List centerXValues = [], xValues = []; - num value; - - if (validData.isNotEmpty) { - 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( - validData[i].x, 100, validData[i], centerLineCollection.length)); - centerXValues.add(validData[i].x); - if (!(i < length)) { - value = (validData[i].close ?? 0) / - (validData[i - length].close ?? 1) * - 100; - signalCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[i].x, value, validData[i], signalCollection.length)); - xValues.add(validData[i].x); - } - } - } - technicalIndicatorsRenderer.renderPoints = signalCollection; - technicalIndicatorsRenderer.momentumCenterLineValue = - centerLineCollection.first.y.toDouble(); - // Decides the type of renderer class to be used - const bool isLine = true; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'Momentum', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'CenterLine', - indicator.centerLineColor, indicator.centerLineWidth, chart, isLine); - technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[0]); - technicalIndicatorsRenderer.setSeriesRange( - centerLineCollection, - indicator, - centerXValues, - technicalIndicatorsRenderer.targetSeriesRenderers[1]); - } - } - - void _calculateRSIPoints( - RsiIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> signalCollection = - >[], - lowerCollection = >[], - upperCollection = >[], - validData = technicalIndicatorsRenderer.dataPoints!; - - final List xValues = [], signalXValues = []; - - if (validData.isNotEmpty && - validData.length >= indicator.period && - indicator.period > 0) { - if (indicator.showZones) { - for (int i = 0; i < validData.length; i++) { - upperCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[i].x, - indicator.overbought, - validData[i], - upperCollection.length)); - lowerCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[i].x, - indicator.oversold, - validData[i], - lowerCollection.length)); - xValues.add(validData[i].x); - } - } - num prevClose = validData[0].close ?? 0, gain = 0, loss = 0; - for (int i = 1; i <= indicator.period; i++) { - final num close = validData[i].close ?? 0.0; - if (close > prevClose) { - gain += close - prevClose; - } else { - loss += prevClose - close; - } - prevClose = close; - } - gain = gain / indicator.period; - loss = loss / indicator.period; - - signalCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[indicator.period].x, - 100 - (100 / (1 + (gain / loss))), - validData[indicator.period], - signalCollection.length)); - signalXValues.add(validData[indicator.period].x); - - for (int j = indicator.period + 1; j < validData.length; j++) { - if (!validData[j].isGap && !validData[j].isDrop) { - final num close = validData[j].close; - if (close > prevClose) { - gain = (gain * (indicator.period - 1) + (close - prevClose)) / - indicator.period; - loss = (loss * (indicator.period - 1)) / indicator.period; - } else if (close < prevClose) { - loss = (loss * (indicator.period - 1) + (prevClose - close)) / - indicator.period; - gain = (gain * (indicator.period - 1)) / indicator.period; - } - prevClose = close; - signalCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[j].x, - 100 - (100 / (1 + (gain / loss))), - validData[j], - signalCollection.length)); - signalXValues.add(validData[j].x); - } - } - } - technicalIndicatorsRenderer.renderPoints = signalCollection; - // Decides the type of renderer class to be used. - const bool isLine = true; - // final CartesianSeriesRenderer signalSeriesRenderer = - // technicalIndicatorsRenderer.targetSeriesRenderers[0]; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'RSI', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - if (indicator.showZones == true) { - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); - } - - technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, - signalXValues, technicalIndicatorsRenderer.targetSeriesRenderers[0]); - if (indicator.showZones) { - technicalIndicatorsRenderer.setSeriesRange(upperCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[1]); - technicalIndicatorsRenderer.setSeriesRange(lowerCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[2]); - } - } - - void _calculateSMAPoints( - SmaIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> smaPoints = - >[]; - final List> points = - technicalIndicatorsRenderer.dataPoints!; - final List xValues = []; - CartesianChartPoint point; - if (points.isNotEmpty) { - final List> validData = points; - - if (validData.length >= indicator.period && indicator.period > 0) { - num average = 0, sum = 0; - - for (int i = 0; i < indicator.period; i++) { - sum += technicalIndicatorsRenderer.getFieldValue( - validData, i, indicator.valueField); - } - - average = sum / indicator.period; - point = technicalIndicatorsRenderer.getDataPoint( - validData[indicator.period - 1].x, - average, - validData[indicator.period - 1], - smaPoints.length); - smaPoints.add(point); - xValues.add(point.x); - - int index = indicator.period; - while (index < validData.length) { - sum -= technicalIndicatorsRenderer.getFieldValue( - validData, index - indicator.period, indicator.valueField); - sum += technicalIndicatorsRenderer.getFieldValue( - validData, index, indicator.valueField); - average = sum / indicator.period; - point = technicalIndicatorsRenderer.getDataPoint( - validData[index].x, average, validData[index], smaPoints.length); - smaPoints.add(point); - xValues.add(point.x); - index++; - } - } - technicalIndicatorsRenderer.renderPoints = smaPoints; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'SMA', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - // final CartesianSeriesRenderer signalSeriesRenderer = - // technicalIndicatorsRenderer.targetSeriesRenderers[0]; - technicalIndicatorsRenderer.setSeriesRange(smaPoints, indicator, xValues); - } - } - - /// To calculate the stochastic indicator points. - void _calculateStochasticIndicatorPoints( - StochasticIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - List> signalCollection = - >[], - source = >[], - periodCollection = >[]; - final List> lowerCollection = - >[], - upperCollection = >[]; - final List> validData = - technicalIndicatorsRenderer.dataPoints!; - final List xValues = []; - late List collection, signalX, periodX; - if (validData.isNotEmpty && - validData.length >= indicator.period && - indicator.period > 0) { - if (indicator.showZones) { - for (int i = 0; i < validData.length; i++) { - upperCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[i].x, - indicator.overbought, - validData[i], - upperCollection.length)); - lowerCollection.add(technicalIndicatorsRenderer.getDataPoint( - validData[i].x, - indicator.oversold, - validData[i], - lowerCollection.length)); - xValues.add(validData[i].x); - } - } - source = calculatePeriod(indicator.period, indicator.kPeriod.toInt(), - validData, technicalIndicatorsRenderer); - collection = _stochasticCalculation(indicator.period, - indicator.kPeriod.toInt(), source, technicalIndicatorsRenderer); - periodCollection = collection[0]; - periodX = collection[1]; - collection = _stochasticCalculation( - (indicator.period + indicator.kPeriod - 1).toInt(), - indicator.dPeriod.toInt(), - source, - technicalIndicatorsRenderer); - signalCollection = collection[0]; - signalX = collection[1]; - } - technicalIndicatorsRenderer.renderPoints = signalCollection; - technicalIndicatorsRenderer.stochasticperiod = periodCollection; - // Decides the type of renderer class to be used. - const bool isLine = true; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'Stocastic', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'PeriodLine', - indicator.periodLineColor, indicator.periodLineWidth, chart, isLine); - if (indicator.showZones) { - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); - technicalIndicatorsRenderer.setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); - } - technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, - signalX, technicalIndicatorsRenderer.targetSeriesRenderers[0]); - technicalIndicatorsRenderer.setSeriesRange(periodCollection, indicator, - periodX, technicalIndicatorsRenderer.targetSeriesRenderers[1]); - if (indicator.showZones) { - technicalIndicatorsRenderer.setSeriesRange(upperCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[2]); - technicalIndicatorsRenderer.setSeriesRange(lowerCollection, indicator, - xValues, technicalIndicatorsRenderer.targetSeriesRenderers[3]); - } - } - - /// To calculate the values of the stochastic indicator. - List _stochasticCalculation( - int period, - int kPeriod, - List> data, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - final List> pointCollection = - >[]; - final List xValues = []; - if (data.length >= period + kPeriod && kPeriod > 0) { - final int count = period + (kPeriod - 1); - final List temp = [], values = []; - for (int i = 0; i < data.length; i++) { - final num value = data[i].y; - temp.add(value); - } - num length = temp.length; - while (length >= count) { - num sum = 0; - for (int i = period - 1; i < (period + kPeriod - 1); i++) { - sum = sum + temp[i]; - } - sum = sum / kPeriod; - final String _sum = sum.toStringAsFixed(2); - values.add(double.parse(_sum)); - temp.removeRange(0, 1); - length = temp.length; - } - final int len = count - 1; - for (int i = 0; i < data.length; i++) { - if (!(i < len)) { - pointCollection.add(technicalIndicatorsRenderer.getDataPoint( - data[i].x, values[i - len], data[i], pointCollection.length)); - xValues.add(data[i].x); - data[i].y = values[i - len]; - } - } - } - - return [pointCollection, xValues]; - } - - /// To return list of stochastic indicator points. - List> calculatePeriod( - int period, - int kPeriod, - List> data, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - // 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 = - >[]; - - for (int j = 0; j < data.length; j++) { - lowValue[j] = data[j].low ?? 0; - highValue[j] = data[j].high ?? 0; - closeValue[j] = data[j].close ?? 0; - } - if (data.length > period) { - final List mins = [], maxs = []; - for (int i = 0; i < period - 1; ++i) { - maxs.add(0); - mins.add(0); - modifiedSource.add(technicalIndicatorsRenderer.getDataPoint( - data[i].x, data[i].close, data[i], modifiedSource.length)); - } - num? min, max; - for (int i = period - 1; i < data.length; ++i) { - for (int j = 0; j < period; ++j) { - min ??= lowValue[i - j]; - max ??= highValue[i - j]; - min = math.min(min, lowValue[i - j]); - max = math.max(max, highValue[i - j]); - } - maxs.add(max!); - mins.add(min!); - min = null; - max = null; - } - - for (int i = period - 1; i < data.length; ++i) { - num top = 0, bottom = 0; - top += closeValue[i] - mins[i]; - bottom += maxs[i] - mins[i]; - modifiedSource.add(technicalIndicatorsRenderer.getDataPoint( - data[i].x, (top / bottom) * 100, data[i], modifiedSource.length)); - } - } - return modifiedSource; - } - - /// To calculate the values of the TMA indicator. - void _calculateTMAPoints( - TmaIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final num period = indicator.period; - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - if (validData.isNotEmpty && - validData.length >= indicator.period && - period > 0) { - //prepare data - if (validData.isNotEmpty && validData.length >= period) { - num sum = 0; - int index = 0; - List smaValues = []; - int length = validData.length; - - while (length >= period) { - sum = 0; - index = validData.length - length; - for (int j = index; j < index + period; j++) { - sum += technicalIndicatorsRenderer.getFieldValue( - validData, j, indicator.valueField); - } - sum = sum / period; - smaValues.add(sum); - length--; - } - //initial values - for (int k = 0; k < period - 1; k++) { - sum = 0; - for (int j = 0; j < k + 1; j++) { - sum += technicalIndicatorsRenderer.getFieldValue( - validData, j, indicator.valueField); - } - sum = sum / (k + 1); - smaValues = _splice(smaValues, k, sum); - } - - index = indicator.period; - while (index <= smaValues.length) { - sum = 0; - for (int j = index - indicator.period; j < index; j++) { - sum = sum + smaValues[j]; - } - sum = sum / indicator.period; - point = technicalIndicatorsRenderer.getDataPoint( - validData[index - 1].x, sum, validData[index - 1], points.length); - points.add(point); - xValues.add(point.x); - index++; - } - } - } - technicalIndicatorsRenderer.renderPoints = points; - technicalIndicatorsRenderer.setSeriesProperties( - indicator, - indicator.name ?? 'TMA', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - // final CartesianSeriesRenderer signalSeriesRenderer = - // technicalIndicatorsRenderer.targetSeriesRenderers[0]; - technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); - } - - /// To return list of spliced values - List _splice(List list, int index, num? elements) { - if (elements != null) { - list.insertAll(index, [elements]); - } - return list; - } -} - -class _TempData { - _TempData(this.x, this.y); - final dynamic x; - final num y; -} - -class _BollingerData { - _BollingerData( - {this.x, - required this.midBand, - required this.lowBand, - required this.upBand, - required this.visible}); - num? x; - num midBand; - num lowBand; - num upBand; - bool visible; -} 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 deleted file mode 100644 index 89e5451d8..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'technical_indicator.dart'; - -/// Renders Triangular Moving Average (TMA) indicator. -/// -/// The Triangular Moving Average (TMA) is a technical indicator similar to other moving averages. -/// The TMA shows the average (or average) price of an asset over a specified number of data points over a period of time. -/// -/// The technical indicator is rendered on the basis of the [valueField] property. -@immutable -class TmaIndicator extends TechnicalIndicators { - /// Creating an argument constructor of TmaIndicator class. - TmaIndicator( - {bool? isVisible, - String? xAxisName, - String? yAxisName, - String? seriesName, - List? dashArray, - double? animationDuration, - double? animationDelay, - 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, - ChartIndicatorRenderCallback? onRenderDetailsUpdate}) - : valueField = (valueField ?? 'close').toLowerCase(), - super( - isVisible: isVisible, - xAxisName: xAxisName, - yAxisName: yAxisName, - seriesName: seriesName, - dashArray: dashArray, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: xValueMapper, - highValueMapper: highValueMapper, - lowValueMapper: lowValueMapper, - openValueMapper: openValueMapper, - closeValueMapper: closeValueMapper, - name: name, - isVisibleInLegend: isVisibleInLegend, - legendIconType: legendIconType, - legendItemText: legendItemText, - signalLineColor: signalLineColor, - signalLineWidth: signalLineWidth, - period: period, - onRenderDetailsUpdate: onRenderDetailsUpdate); - - /// ValueField value for tma indicator. - /// - /// Value field determines the field for rendering the indicators. - /// - /// Defaults to `close`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// indicators: >[ - /// TmaIndicator( - /// seriesName: 'Series1' - /// period: 4, - /// valueField: 'low' - /// ), - /// ], - /// series: >[ - /// HiloOpenCloseSeries( - /// name: 'Series1' - /// ) - /// ] - /// ); - /// } - /// ``` - final String valueField; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TmaIndicator && - other.isVisible == isVisible && - other.xAxisName == xAxisName && - other.yAxisName == yAxisName && - other.seriesName == seriesName && - other.dashArray == dashArray && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.highValueMapper == highValueMapper && - other.lowValueMapper == lowValueMapper && - other.openValueMapper == openValueMapper && - other.closeValueMapper == closeValueMapper && - other.period == period && - other.name == name && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.legendItemText == legendItemText && - other.signalLineColor == signalLineColor && - other.signalLineWidth == signalLineWidth && - other.valueField == valueField; - } - - @override - int get hashCode { - final List values = [ - isVisible, - xAxisName, - yAxisName, - seriesName, - dashArray, - animationDuration, - animationDelay, - dataSource, - xValueMapper, - highValueMapper, - lowValueMapper, - openValueMapper, - closeValueMapper, - name, - isVisibleInLegend, - legendIconType, - legendItemText, - signalLineColor, - signalLineWidth, - valueField, - period - ]; - return hashList(values); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart deleted file mode 100644 index 6044d03f2..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart +++ /dev/null @@ -1,1577 +0,0 @@ -import 'dart:math'; -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_series/candle_series.dart'; -import '../chart_series/hilo_series.dart'; -import '../chart_series/hiloopenclose_series.dart'; -import '../chart_series/range_area_series.dart'; -import '../chart_series/range_column_series.dart'; -import '../chart_series/stacked_series_base.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/marker.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Renders the chart trendline. -/// -/// A trendline is a straight line that connects two or more price points -/// and then extends into the future to act as a line of support. -/// Trendlines provide support for forward and backward forecastings. -/// -/// Provides option to customize the trendline types, [width], [forwardForecast] and [backwardForecast]. -class Trendline { - /// Creating an argument constructor of Trendline class. - Trendline( - {this.enableTooltip = true, - this.intercept, - this.name, - this.dashArray, - 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.animationDelay = 0, - this.valueField = 'high', - this.isVisibleInLegend = true, - this.legendIconType = LegendIconType.horizontalLine, - this.markerSettings = const MarkerSettings(), - this.polynomialOrder = 2, - this.period = 2, - this.onRenderDetailsUpdate}); - - /// Determines the animation time of trendline. - /// - /// Defaults to `1500 `. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(animationDuration: 150) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double animationDuration; - - /// Delay duration of the trendline animation. - /// It takes a millisecond value as input. - /// By default,the trendline will get animated for the specified duration. - /// If animationDelay is specified, then the trendline will begin to animate - /// after the specified duration. - /// - /// Defaults to '0'. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(animationDelay: 500) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double? animationDelay; - - /// Specifies the backward forecasting of trendlines. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(backwardForecast: 3) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double backwardForecast; - - /// Specifies the forward forecasting of trendlines. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(forwardForecast: 3) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double forwardForecast; - - /// Width of trendlines. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(width: 4) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double width; - - /// Opacity of the trendline. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(opacity: 0.85) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double opacity; - - /// Pattern of dashes and gaps used to stroke the trendline. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(dashArray: [2,3]) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final List? dashArray; - - /// Enables the tooltip for trendlines. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(enableTooltip: false) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final bool enableTooltip; - - /// Color of the trendline. - /// - /// Defaults to `Colors.blue`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(color: Colors.greenAccent) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final Color color; - - /// Provides the name for trendline. - /// - /// Defaults to `type` of the trendline chosen. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(name: 'Trendline1') - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final String? name; - - /// Specifies the intercept value of the trendlines. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(intercept: 20) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final double? intercept; - - /// Determines the visibility of the trendline. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(isVisible: false) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final bool isVisible; - - /// Specifies the type of legend icon for trendline. - /// - /// Defaults to `LegendIconType.HorizontalLine`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(legendIconType: LegendIconType.circle) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final LegendIconType legendIconType; - - /// Specifies the intercept value of the trendlines. - /// - /// Defaults to `TrendlineType.linear`. - /// - /// Also refer [TrendlineType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(type: TrendlineType.power) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final TrendlineType type; - - /// To choose the valueField(low or high) to render the trendline. - /// - /// Defaults to `high`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(valueField: 'low') - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final String valueField; - - /// Settings to configure the marker of trendline. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline( - /// markerSettings: MarkerSettings(isVisible: true) - /// ) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final MarkerSettings markerSettings; - - /// Show/hides the legend for trendline. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(isVisibleInLegend: false) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final bool isVisibleInLegend; - - /// Specifies the order of the polynomial for polynomial trendline. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline( - /// type: TrendlineType.polynomial, - /// polynomialOrder: 4 - /// ) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final int polynomialOrder; - - /// Specifies the period for moving average trendline. - /// - /// Defaults to `2`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// Trendline(period: 3) - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final int period; - - /// Callback which gets called while rendering the trendline. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// trendlines: [ - /// onRenderDetailsUpdate: (TrendlineRenderParams args) { - /// print('Slope value: ' + args.slope[0]); - /// print('r-squared value: ' + args.rSquaredValue); - /// print('Intercept value (x): ' + args.intercept); - /// } - /// ] - /// ) - /// ] - /// ); - /// } - /// ``` - final ChartTrendlineRenderCallback? onRenderDetailsUpdate; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is Trendline && - other.enableTooltip == enableTooltip && - other.intercept == intercept && - other.name == name && - other.dashArray == dashArray && - other.color == color && - other.type == type && - other.backwardForecast == backwardForecast && - other.forwardForecast == forwardForecast && - other.opacity == opacity && - other.isVisible == isVisible && - other.width == width && - other.animationDuration == animationDuration && - other.valueField == valueField && - other.isVisibleInLegend == isVisibleInLegend && - other.legendIconType == legendIconType && - other.markerSettings == markerSettings && - other.polynomialOrder == polynomialOrder && - other.period == period; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - enableTooltip, - intercept, - name, - dashArray, - color, - type, - backwardForecast, - forwardForecast, - opacity, - isVisible, - width, - animationDuration, - valueField, - isVisibleInLegend, - legendIconType, - markerSettings, - polynomialOrder, - period - ]; - return hashList(values); - } -} - -/// Trendline renderer class for mutable fields and methods. -class TrendlineRenderer { - /// Creates an argument constructor for Trendline renderer class. - TrendlineRenderer(this.trendline) { - opacity = trendline.opacity; - dashArray = trendline.dashArray; - fillColor = trendline.color; - visible = trendline.isVisible; - name = trendline.name; - } - - /// Holds the value of trendline. - final Trendline trendline; - - /// Holds the collection of Cartesian data points. - List>? pointsData; - - /// Holds the slope intercept. - // ignore: library_private_types_in_public_api - _SlopeIntercept slopeIntercept = _SlopeIntercept(); - - /// Holds the slope intercept value for equation. - // ignore: library_private_types_in_public_api - _SlopeIntercept slopeInterceptData = _SlopeIntercept(); - - /// Holds the polynomial slopes. - List? polynomialSlopes; - - /// Holds the polynomial slopes for equation. - List? polynomialSlopesData; - - /// Holds the list of marker shapes. - late List markerShapes; - - /// Holds the list of point value. - late List points; - - /// Holds the opacity value. - late double opacity; - - /// Holds the list of dash array. - List? dashArray; - - /// Holds the fill color value. - late Color fillColor; - - /// Specifies whether the trendline is visible. - late bool visible; - - /// Specifies the value of name. - String? name; - - /// Specifies whether the renderer is needed. - late bool isNeedRender; - - /// Holds the value of animation controller. - late AnimationController animationController; - - /// Checks whether the trendline rendered event is specified. - bool isTrendlineRenderEvent = false; - - late SeriesRendererDetails _seriesRendererDetails; - - // In excel, the date is considered from Jan 1, 1900. To achieve the same scenario, we have considered the year from 1900. - // Reference link: https://support.microsoft.com/en-us/office/datevalue-function-df8b07d4-7761-4a93-bc33-b7471bbff252 - /// Holds the excel starting date value. - final DateTime excelDate = DateTime(1900, 01, 01); - - /// Defines the data point of trendline. - CartesianChartPoint getDataPoint( - dynamic x, - num y, - CartesianChartPoint sourcePoint, - SeriesRendererDetails seriesRendererDetails, - int index) { - final CartesianChartPoint trendPoint = - CartesianChartPoint(x, y); - trendPoint.x = (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) - ? DateTime.fromMillisecondsSinceEpoch(x.floor()) - : x; - trendPoint.y = y; - trendPoint.xValue = x; - trendPoint.pointColorMapper = _seriesRendererDetails.series.color; - trendPoint.index = index; - trendPoint.yValue = y; - trendPoint.isVisible = true; - seriesRendererDetails.minimumX = - math.min(seriesRendererDetails.minimumX!, trendPoint.xValue); - seriesRendererDetails.minimumY = - math.min(seriesRendererDetails.minimumY!, trendPoint.yValue); - seriesRendererDetails.maximumX = - math.max(seriesRendererDetails.maximumX!, trendPoint.xValue); - seriesRendererDetails.maximumY = - math.max(seriesRendererDetails.maximumY!, trendPoint.yValue); - return trendPoint; - } - - /// Defines the linear points. - List> getLinearPoints( - List> points, - dynamic xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails, - // ignore: library_private_types_in_public_api - _SlopeIntercept slopeInterceptLinear) { - num x1Linear, x2Linear; - final List> pts = - >[]; - xValues.sort(); - if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { - x1Linear = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[0], - -trendline.backwardForecast); - x2Linear = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[xValues.length - 1], - trendline.forwardForecast); - } else { - x1Linear = xValues[0] - trendline.backwardForecast; - x2Linear = xValues[xValues.length - 1] + trendline.forwardForecast; - } - final num y1Linear = slopeInterceptLinear.slope! * x1Linear + - slopeInterceptLinear.intercept!; - final num y2Linear = slopeInterceptLinear.slope! * x2Linear + - slopeInterceptLinear.intercept!; - pts.add(getDataPoint( - x1Linear, y1Linear, points[0], seriesRendererDetails, pts.length)); - pts.add(getDataPoint(x2Linear, y2Linear, points[points.length - 1], - seriesRendererDetails, pts.length)); - return pts; - } - - /// Setting the linear range for trendline series. - void _setLinearRange(List> points, - SeriesRendererDetails seriesRendererDetails) { - final List xValues = []; - final List slopeInterceptXValues = []; - final List yValues = []; - int index = 0; - const int startXValue = 1; - while (index < points.length) { - final CartesianChartPoint point = points[index]; - xValues.add(point.xValue ?? point.x); - slopeInterceptXValues.add((seriesRendererDetails - .xAxisDetails?.axisRenderer is DateTimeAxisRenderer) - ? point.x.difference(excelDate).inDays - : startXValue + index); - (!(seriesRendererDetails.series is RangeAreaSeries || - seriesRendererDetails.series is RangeColumnSeries || - seriesRendererDetails.series is HiloSeries || - seriesRendererDetails.series is HiloOpenCloseSeries || - seriesRendererDetails.series is CandleSeries)) - ? yValues.add(point.yValue ?? point.y) - : yValues.add(trendline.valueField.toLowerCase() == 'low' - ? point.low - : point.high); - - index++; - } - slopeIntercept = _findSlopeIntercept(xValues, yValues, points); - if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) - pointsData = getLinearPoints( - points, xValues, yValues, seriesRendererDetails, slopeIntercept); - slopeInterceptData = - _findSlopeIntercept(slopeInterceptXValues, yValues, points); - } - - /// Defines Exponential Points. - List> getExponentialPoints( - List> points, - dynamic xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails, - // ignore: library_private_types_in_public_api - _SlopeIntercept slopeInterceptExpo) { - num x1, x2, x3; - final int midPoint = (points.length / 2).round(); - final List> ptsExpo = - >[]; - if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { - x1 = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[0], - -trendline.backwardForecast); - x2 = xValues[midPoint - 1]; - x3 = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[xValues.length - 1], - trendline.forwardForecast); - } else { - x1 = xValues[0] - trendline.backwardForecast; - x2 = xValues[midPoint - 1]; - x3 = xValues[xValues.length - 1] + trendline.forwardForecast; - } - final num y1 = slopeInterceptExpo.intercept! * - math.exp(slopeInterceptExpo.slope! * x1); - - final num y2 = slopeInterceptExpo.intercept! * - math.exp(slopeInterceptExpo.slope! * x2); - - final num y3 = slopeInterceptExpo.intercept! * - math.exp(slopeInterceptExpo.slope! * x3); - ptsExpo.add(getDataPoint(x1, y1.isNaN ? 0 : y1, points[0], - seriesRendererDetails, ptsExpo.length)); - ptsExpo.add(getDataPoint(x2, y2.isNaN ? 0 : y2, points[midPoint - 1], - seriesRendererDetails, ptsExpo.length)); - ptsExpo.add(getDataPoint(x2, y2.isNaN ? 0 : y2, points[midPoint - 1], - seriesRendererDetails, ptsExpo.length)); - ptsExpo.add(getDataPoint(x3, y3.isNaN ? 0 : y3, points[points.length - 1], - seriesRendererDetails, ptsExpo.length)); - // avoid rendering trendline when values are NaN - if (y1.isNaN || y2.isNaN || y3.isNaN) { - for (int i = 0; i < ptsExpo.length; i++) { - ptsExpo[i].x = 0; - ptsExpo[i].y = 0; - } - } - return ptsExpo; - } - - /// Setting the exponential range for trendline series. - void _setExponentialRange(List> points, - SeriesRendererDetails seriesRendererDetails) { - final List xValues = []; - final List slopeInterceptXValues = []; - final List yValues = []; - int index = 0; - const int startXValue = 1; - while (index < points.length) { - final CartesianChartPoint point = points[index]; - xValues.add(point.xValue ?? point.x); - slopeInterceptXValues.add(seriesRendererDetails.xAxisDetails?.axisRenderer - is DateTimeAxisRenderer - ? seriesRendererDetails.dataPoints[index].x - .difference(excelDate) - .inDays - : startXValue + index); - (!(seriesRendererDetails.series is RangeAreaSeries || - seriesRendererDetails.series is RangeColumnSeries || - seriesRendererDetails.series is HiloSeries || - seriesRendererDetails.series is HiloOpenCloseSeries || - seriesRendererDetails.series is CandleSeries)) - ? yValues.add(math.log(point.yValue ?? point.y)) - : yValues.add(trendline.valueField.toLowerCase() == 'low' - ? math.log(point.low) - : math.log(point.high)); - - index++; - } - xValues.sort(); - slopeIntercept = _findSlopeIntercept(xValues, yValues, points); - if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) - pointsData = getExponentialPoints( - points, xValues, yValues, seriesRendererDetails, slopeIntercept); - slopeInterceptData = - _findSlopeIntercept(slopeInterceptXValues, yValues, points); - } - - /// Defines Power Points. - List> getPowerPoints( - List> points, - dynamic xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails, - // ignore: library_private_types_in_public_api - _SlopeIntercept slopeInterceptPow) { - num x1, x2, x3; - final int midPoint = (points.length / 2).round(); - final List> ptsPow = - >[]; - if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { - x1 = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[0], - -trendline.backwardForecast); - x2 = xValues[midPoint - 1]; - x3 = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[xValues.length - 1], - trendline.forwardForecast); - } else { - x1 = xValues[0] - trendline.backwardForecast; - x1 = x1 > -1 ? x1 : 0; - x2 = xValues[midPoint - 1]; - x3 = xValues[xValues.length - 1] + trendline.forwardForecast; - } - final num y1 = x1 == 0 - ? 0 - : slopeInterceptPow.intercept! * math.pow(x1, slopeInterceptPow.slope!); - final num y2 = - slopeInterceptPow.intercept! * math.pow(x2, slopeInterceptPow.slope!); - final num y3 = - slopeInterceptPow.intercept! * math.pow(x3, slopeInterceptPow.slope!); - ptsPow.add(getDataPoint(x1, y1.isNaN ? 0 : y1, points[0], - seriesRendererDetails, ptsPow.length)); - ptsPow.add(getDataPoint(x2, y2.isNaN ? 0 : y2, points[midPoint - 1], - seriesRendererDetails, ptsPow.length)); - ptsPow.add(getDataPoint(x3, y3.isNaN ? 0 : y3, points[points.length - 1], - seriesRendererDetails, ptsPow.length)); - // avoid rendering trendline when values are NaN - if (y1.isNaN || y2.isNaN || y3.isNaN) { - for (int i = 0; i < ptsPow.length; i++) { - ptsPow[i].x = 0; - ptsPow[i].y = 0; - } - } - return ptsPow; - } - - /// Setting the power range values for trendline series. - void _setPowerRange(List> points, - SeriesRendererDetails seriesRendererDetails) { - final List xValues = []; - final List slopeInterceptXValues = []; - final List yValues = []; - final List powerPoints = []; - int index = 0; - const int startXValue = 1; - 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) - : (seriesRendererDetails.xAxisDetails?.axisRenderer - is CategoryAxisRenderer || - seriesRendererDetails.xAxisDetails?.axisRenderer - is DateTimeCategoryAxisRenderer) - ? point.xValue - : point.x; - xValues.add(xVal); - slopeInterceptXValues.add(math.log(seriesRendererDetails - .xAxisDetails?.axisRenderer is DateTimeAxisRenderer - ? seriesRendererDetails.dataPoints[index].x - .difference(excelDate) - .inDays - : startXValue + index)); - (!(seriesRendererDetails.series is RangeAreaSeries || - seriesRendererDetails.series is RangeColumnSeries || - seriesRendererDetails.series is HiloSeries || - seriesRendererDetails.series is HiloOpenCloseSeries || - seriesRendererDetails.series is CandleSeries)) - ? yValues.add(math.log(point.yValue ?? point.y)) - : yValues.add(trendline.valueField.toLowerCase() == 'low' - ? math.log(point.low) - : math.log(point.high)); - index++; - } - powerPoints.sort(); - slopeIntercept = _findSlopeIntercept(xValues, yValues, points); - if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) - pointsData = getPowerPoints( - points, powerPoints, yValues, seriesRendererDetails, slopeIntercept); - slopeInterceptData = - _findSlopeIntercept(slopeInterceptXValues, yValues, points); - } - - /// Defines Logarithmic Points. - List> getLogarithmicPoints( - List> points, - dynamic xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails, - // ignore: library_private_types_in_public_api - _SlopeIntercept slopeInterceptLog) { - num x1, x2, x3; - final int midPoint = (points.length / 2).round(); - final List> ptsLog = - >[]; - if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { - x1 = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[0], - -trendline.backwardForecast); - x2 = xValues[midPoint - 1]; - x3 = _increaseDateTimeForecast( - seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, - xValues[xValues.length - 1], - trendline.forwardForecast); - } else { - x1 = xValues[0] - trendline.backwardForecast; - x2 = xValues[midPoint - 1]; - x3 = xValues[xValues.length - 1] + trendline.forwardForecast; - } - final num y1 = slopeInterceptLog.intercept! + - (slopeInterceptLog.slope! * - ((math.log(x1)).isFinite ? math.log(x1) : x1)); - final num y2 = slopeInterceptLog.intercept! + - (slopeInterceptLog.slope! * - ((math.log(x2)).isFinite ? math.log(x2) : x2)); - final num y3 = slopeInterceptLog.intercept! + - (slopeInterceptLog.slope! * - ((math.log(x3)).isFinite ? math.log(x3) : x3)); - ptsLog.add( - getDataPoint(x1, y1, points[0], seriesRendererDetails, ptsLog.length)); - ptsLog.add(getDataPoint( - x2, y2, points[midPoint - 1], seriesRendererDetails, ptsLog.length)); - ptsLog.add(getDataPoint(x3, y3, points[points.length - 1], - seriesRendererDetails, ptsLog.length)); - return ptsLog; - } - - /// Setting the logarithmic range for trendline series. - void _setLogarithmicRange(List> points, - SeriesRendererDetails seriesRendererDetails) { - final List xLogValue = []; - final List slopeInterceptXLogValue = []; - final List yLogValue = []; - final List xPointsLgr = []; - int index = 0; - const int startXValue = 1; - 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) - : (seriesRendererDetails.xAxisDetails?.axisRenderer - is CategoryAxisRenderer || - seriesRendererDetails.xAxisDetails?.axisRenderer - is DateTimeCategoryAxisRenderer) - ? point.xValue - : point.x; - xLogValue.add(xVal); - slopeInterceptXLogValue.add(math.log(seriesRendererDetails - .xAxisDetails?.axisRenderer is DateTimeAxisRenderer - ? points[index].x.difference(excelDate).inDays - : startXValue + index)); - (!(seriesRendererDetails.series is RangeAreaSeries || - seriesRendererDetails.series is RangeColumnSeries || - seriesRendererDetails.series is HiloSeries || - seriesRendererDetails.series is HiloOpenCloseSeries || - seriesRendererDetails.series is CandleSeries)) - ? yLogValue.add(point.yValue ?? point.y) - : yLogValue.add(trendline.valueField.toLowerCase() == 'low' - ? point.low - : point.high); - index++; - } - xPointsLgr.sort(); - slopeIntercept = _findSlopeIntercept(xLogValue, yLogValue, points); - if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) - pointsData = getLogarithmicPoints( - points, xPointsLgr, yLogValue, seriesRendererDetails, slopeIntercept); - slopeInterceptData = - _findSlopeIntercept(slopeInterceptXLogValue, yLogValue, points); - } - - /// Defines Polynomial points. - List> _getPolynomialPoints( - List> points, - dynamic xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails) { - //ignore: unused_local_variable - final int midPoint = (points.length / 2).round(); - const int startXValue = 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; - } - } - - final List matrix = _getMatrix(trendline, xValues); - if (!_gaussJordanElimination(matrix, polynomialSlopes!)) { - // The trendline will not be generated if there is just one data point or if the x and y values are the same, - // for example (1,1), (1,1). So, the line was commented. And now marker alone will be rendered in this case. - // _polynomialSlopes = null; - } - pts = _getPoints(points, xValues, yValues, seriesRendererDetails); - if (trendline.onRenderDetailsUpdate != null) { - polynomialSlopesData = - List.filled(trendline.polynomialOrder + 1, 0); - - for (int i = 0; i < xValues.length; i++) { - final num yVal = yValues[i]; - for (int j = 0; j <= trendline.polynomialOrder; j++) { - polynomialSlopesData![j] += pow( - seriesRendererDetails.xAxisDetails?.axisRenderer - is DateTimeAxisRenderer - ? points[i].x.difference(excelDate).inDays - : startXValue + i.toDouble(), - j) * - yVal; - } - } - final List xData = []; - for (int i = 0; i < xValues.length; i++) { - xData.add(seriesRendererDetails.xAxisDetails?.axisRenderer - is DateTimeAxisRenderer - ? points[i].x.difference(excelDate).inDays - : startXValue + i.toDouble()); - } - final List matrix = _getMatrix(trendline, xData); - // To find the prompt polynomial slopes for the trendline equation, gaussJordanElimination method is used here. - if (!_gaussJordanElimination(matrix, polynomialSlopesData!)) {} - } - return pts; - } - - /// Get matrix values for polynomial type. - List _getMatrix(Trendline trendline, dynamic xValues) { - final List numArray = - 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.filled(trendline.polynomialOrder + 1, null); - } - - num num1 = 0; - for (int nIndex = 0; nIndex < xValues.length; nIndex++) { - final num d = xValues[nIndex]; - num num2 = 1.0; - for (int j = 0; j < numArray.length; j++, num1++) { - numArray[j] ??= 0; - numArray[j] += num2; - num2 *= d; - } - } - - for (int i = 0; i <= trendline.polynomialOrder; i++) { - for (int j = 0; j <= trendline.polynomialOrder; j++) { - matrix[i][j] = numArray[i + j]; - } - } - return matrix; - } - - /// Setting the polynomial range for trendline series. - void _setPolynomialRange(List> points, - SeriesRendererDetails seriesRendererDetails) { - final List xPolyValues = []; - final List yPolyValues = []; - int index = 0; - while (index < points.length) { - final CartesianChartPoint point = points[index]; - xPolyValues.add(point.xValue ?? point.x); - (!(seriesRendererDetails.series is RangeAreaSeries || - seriesRendererDetails.series is RangeColumnSeries || - seriesRendererDetails.series is HiloSeries || - seriesRendererDetails.series is HiloOpenCloseSeries || - seriesRendererDetails.series is CandleSeries)) - ? yPolyValues.add(point.yValue ?? point.y) - : yPolyValues.add(trendline.valueField.toLowerCase() == 'low' - ? point.low - : point.high); - index++; - } - xPolyValues.sort(); - pointsData = _getPolynomialPoints( - points, xPolyValues, yPolyValues, seriesRendererDetails); - } - - /// To return points list. - List> _getPoints( - List> points, - dynamic xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails) { - //ignore: unused_local_variable - final int midPoint = (points.length / 2).round(); - final List _polynomialSlopesList = polynomialSlopes!; - final List> pts = - >[]; - - num x1 = 1; - dynamic xVal; - num yVal; - final num _backwardForecast = - seriesRendererDetails.xAxisDetails is DateTimeAxisDetails - ? _getForecastDate(seriesRendererDetails.xAxisDetails!, false) - : trendline.backwardForecast; - final num _forwardForecast = - seriesRendererDetails.xAxisDetails is DateTimeAxisDetails - ? _getForecastDate(seriesRendererDetails.xAxisDetails!, true) - : trendline.forwardForecast; - - for (int index = 1; index <= _polynomialSlopesList.length; index++) { - if (index == 1) { - xVal = xValues[0] - _backwardForecast.toDouble(); - yVal = _getPolynomialYValue(_polynomialSlopesList, xVal); - pts.add(getDataPoint( - xVal, yVal, points[0], seriesRendererDetails, pts.length)); - } else if (index == _polynomialSlopesList.length) { - xVal = xValues[points.length - 1] + _forwardForecast.toDouble(); - yVal = _getPolynomialYValue(_polynomialSlopesList, xVal); - pts.add(getDataPoint(xVal, yVal, points[points.length - 1], - seriesRendererDetails, pts.length)); - } else { - x1 += (points.length + trendline.forwardForecast) / - _polynomialSlopesList.length; - xVal = xValues[x1.floor() - 1] * 1.0; - yVal = _getPolynomialYValue(_polynomialSlopesList, xVal); - pts.add(getDataPoint(xVal, yVal, points[x1.floor() - 1], - seriesRendererDetails, pts.length)); - } - } - return pts; - } - - /// To get polynomial Y value of trendline - double _getPolynomialYValue(List slopes, dynamic x) { - double sum = 0; - for (int i = 0; i < slopes.length; i++) { - sum += slopes[i] * pow(x, i); - } - return sum; - } - - /// Defines moving average points. - List> getMovingAveragePoints( - List> points, - List xValues, - List yValues, - SeriesRendererDetails seriesRendererDetails) { - final List> pts = - >[]; - int periods = trendline.period >= points.length - ? points.length - 1 - : trendline.period; - periods = max(2, periods); - double? y; - dynamic x; - int count, nullCount; - for (int index = 0; index < points.length - 1; index++) { - y = 0.0; - count = nullCount = 0; - for (int j = index; count < periods; j++) { - count++; - if (j >= yValues.length || yValues[j] == null) { - nullCount++; - } - y = y! + (j >= yValues.length ? 0 : yValues[j]!); - } - 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(x, y, points[periods - 1 + index], - seriesRendererDetails, pts.length)); - } - } - return pts; - } - - /// Setting the moving average range for trendline series. - void _setMovingAverageRange(List> points, - SeriesRendererDetails seriesRendererDetails) { - final List xValues = [], xAvgValues = []; - final List yValues = []; - - for (int index = 0; index < points.length; index++) { - final dynamic point = points[index]; - xAvgValues.add(point.xValue ?? point.x); - xValues.add(index + 1); - (!(seriesRendererDetails.series is RangeAreaSeries || - seriesRendererDetails.series is RangeColumnSeries || - seriesRendererDetails.series is HiloSeries || - seriesRendererDetails.series is HiloOpenCloseSeries || - seriesRendererDetails.series is CandleSeries)) - ? yValues.add(point.yValue ?? point.y) - : yValues.add(trendline.valueField.toLowerCase() == 'low' - ? point.low - : point.high); - } - xAvgValues.sort(); - pointsData = getMovingAveragePoints( - points, xAvgValues, yValues, seriesRendererDetails); - } - - /// Setting the slope intercept for trendline series. - _SlopeIntercept _findSlopeIntercept(dynamic xValues, dynamic yValues, - List> points) { - double xAvg = 0.0, yAvg = 0.0, xyAvg = 0.0, xxAvg = 0.0; - int index = 0; - double slope = 0.0, intercept = 0.0; - while (index < points.length) { - if ((yValues[index]).isNaN == true) { - yValues[index] = (yValues[index - 1] + yValues[index + 1]) / 2; - } - xAvg += xValues[index]; - yAvg += yValues[index]; - xyAvg += xValues[index].toDouble() * yValues[index].toDouble(); - xxAvg += xValues[index].toDouble() * xValues[index].toDouble(); - index++; - } - if (trendline.intercept != null && - trendline.intercept != 0 && - (trendline.type == TrendlineType.linear || - trendline.type == TrendlineType.exponential)) { - intercept = trendline.intercept!.toDouble(); - switch (trendline.type) { - case TrendlineType.linear: - slope = (xyAvg - (intercept * xAvg)) / xxAvg; - break; - case TrendlineType.exponential: - slope = (xyAvg - (math.log(intercept.abs()) * xAvg)) / xxAvg; - break; - default: - break; - } - } else { - slope = ((points.length * xyAvg) - (xAvg * yAvg)) / - ((points.length * xxAvg) - (xAvg * xAvg)); - - intercept = (trendline.type == TrendlineType.exponential || - trendline.type == TrendlineType.power) - ? math.exp((yAvg - (slope * xAvg)) / points.length) - : (yAvg - (slope * xAvg)) / points.length; - } - slopeIntercept.slope = slope; - slopeIntercept.intercept = intercept; - return slopeIntercept; - } - - /// To set initial data source for trendlines. - void _initDataSource( - SfCartesianChart chart, SeriesRendererDetails seriesRendererDetails) { - if (pointsData!.isNotEmpty) { - switch (trendline.type) { - case TrendlineType.linear: - _setLinearRange(pointsData!, seriesRendererDetails); - break; - case TrendlineType.exponential: - _setExponentialRange(pointsData!, seriesRendererDetails); - break; - case TrendlineType.power: - _setPowerRange(pointsData!, seriesRendererDetails); - break; - case TrendlineType.logarithmic: - _setLogarithmicRange(pointsData!, seriesRendererDetails); - break; - case TrendlineType.polynomial: - _setPolynomialRange(pointsData!, seriesRendererDetails); - break; - case TrendlineType.movingAverage: - _setMovingAverageRange(pointsData!, seriesRendererDetails); - break; - default: - break; - } - } - } - - /// To find the actual points of trend line series. - void calculateTrendlinePoints(SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties) { - final Rect rect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - points = []; - if (seriesRendererDetails.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 = - calculatePoint( - (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) - ? pointsData![i].xValue - : pointsData![i].x, - pointsData![i].y, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - points.add(Offset(currentChartPoint.x, currentChartPoint.y)); - pointsData![i].region = Rect.fromLTRB( - points[i].dx, points[i].dy, points[i].dx, points[i].dy); - } - } - _calculateMarkerShapesPoint(seriesRendererDetails); - } - } - - /// Calculate marker shapes for trendlines. - void _calculateMarkerShapesPoint( - SeriesRendererDetails seriesRendererDetails) { - markerShapes = []; - 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), - size, - seriesRendererDetails)); - } - } - - /// To set data source for trendlines. - void setDataSource( - SeriesRendererDetails? seriesRendererDetails, SfCartesianChart chart) { - if (seriesRendererDetails?.series != null) { - _seriesRendererDetails = seriesRendererDetails!; - pointsData = seriesRendererDetails.dataPoints; - if (seriesRendererDetails.renderer is StackedSeriesRenderer) { - for (int i = 0; i < pointsData!.length; i++) { - pointsData![i].y = - seriesRendererDetails.stackingValues[0].endValues[i]; - pointsData![i].yValue = - seriesRendererDetails.stackingValues[0].endValues[i]; - } - } - _initDataSource(chart, _seriesRendererDetails); - } - } - - /// To obtain control points for type curve trendlines. - List getControlPoints(List _dataPoints, int index) { - List yCoef = []; - final List controlPoints = []; - final List xValues = [], yValues = []; - for (int i = 0; i < _dataPoints.length; i++) { - xValues.add(_dataPoints[i].dx); - yValues.add(_dataPoints[i].dy); - } - yCoef = naturalSpline( - xValues, yValues, yCoef, xValues.length, SplineType.natural); - 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 = - AxisHelper.getAxisRendererDetails(axisRenderer).axis as DateTimeAxis; - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(value); - switch (axis.intervalType) { - case DateTimeIntervalType.years: - dateTime = DateTime(dateTime.year + interval.floor(), dateTime.month, - dateTime.day, 0, 0, 0, 0); - break; - case DateTimeIntervalType.months: - dateTime = DateTime(dateTime.year, dateTime.month + interval.floor(), - dateTime.day, 0, 0, 0, 0); - break; - case DateTimeIntervalType.days: - dateTime = DateTime(dateTime.year, dateTime.month, - dateTime.day + interval.floor(), 0, 0, 0, 0); - break; - case DateTimeIntervalType.hours: - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour + interval.floor(), 0, 0, 0); - break; - case DateTimeIntervalType.minutes: - dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, - dateTime.hour, dateTime.minute + interval.floor(), 0, 0); - break; - case DateTimeIntervalType.seconds: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second + interval.floor(), - 0); - break; - case DateTimeIntervalType.milliseconds: - dateTime = DateTime( - dateTime.year, - dateTime.month, - dateTime.day, - dateTime.hour, - dateTime.minute, - dateTime.second, - dateTime.millisecond + interval.floor()); - break; - case DateTimeIntervalType.auto: - break; - } - return dateTime.millisecondsSinceEpoch; - } - - /// Boolean for gaussJordanElimination in polynomial type trendlines. - bool _gaussJordanElimination( - List matrix, List _polynomialSlopesList) { - final int length = matrix.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; - } - int index1 = 0; - while (index1 < length) { - num num1 = 0; - int index2 = 0, index3 = 0, index4 = 0; - while (index4 < length) { - if (numArray3[index4] != 1) { - int index5 = 0; - while (index5 < length) { - if (numArray3[index5] == 0 && - (matrix[index4][index5]).abs() >= num1 == true) { - num1 = (matrix[index4][index5]).abs(); - index2 = index4; - index3 = index5; - } - ++index5; - } - } - ++index4; - } - ++numArray3[index3]; - if (index2 != index3) { - int index4_1 = 0; - while (index4_1 < length) { - final num num2 = matrix[index2][index4_1]; - matrix[index2][index4_1] = matrix[index3][index4_1]; - matrix[index3][index4_1] = num2; - ++index4_1; - } - final num num3 = _polynomialSlopesList[index2]; - _polynomialSlopesList[index2] = _polynomialSlopesList[index3]; - _polynomialSlopesList[index3] = num3; - } - numArray2[index1] = index2; - numArray1[index1] = index3; - if (matrix[index3][index3] == 0.0) { - return false; - } - final num num4 = 1.0 / matrix[index3][index3]; - matrix[index3][index3] = 1.0; - int iindex4 = 0; - while (iindex4 < length) { - matrix[index3][iindex4] *= num4; - ++iindex4; - } - _polynomialSlopesList[index3] *= num4; - int iandex4 = 0; - while (iandex4 < length) { - if (iandex4 != index3) { - final num num2 = matrix[iandex4][index3]; - matrix[iandex4][index3] = 0.0; - int index5 = 0; - while (index5 < length) { - matrix[iandex4][index5] -= matrix[index3][index5] * num2; - ++index5; - } - _polynomialSlopesList[iandex4] -= - _polynomialSlopesList[index3] * num2; - } - ++iandex4; - } - ++index1; - } - for (int iindex1 = length - 1; iindex1 >= 0; iindex1--) { - if (numArray2[iindex1] != numArray1[iindex1]) { - for (int iindex2 = 0; iindex2 < length; iindex2++) { - final num number = matrix[iindex2][numArray2[iindex1]]; - matrix[iindex2][numArray2[iindex1]] = - matrix[iindex2][numArray1[iindex1]]; - matrix[iindex2][numArray1[iindex1]] = number; - } - } - } - return true; - } - - /// It returns the polynomial points. - List getPolynomialCurve( - List> points, - SeriesRendererDetails seriesRendererDetails, - CartesianStateProperties stateProperties) { - final List polyPoints = []; - final dynamic start = - seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer - ? points[0].xValue - : points[0].x; - final dynamic end = - seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer - ? points[points.length - 1].xValue - : points[points.length - 1].xValue; - for (dynamic x = start; - polyPoints.length <= 100; - x += (end - start) / 100) { - final double y = _getPolynomialYValue(polynomialSlopes!, x); - final ChartLocation position = calculatePoint( - x, - y, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - stateProperties.chartAxis.axisClipRect); - polyPoints.add(Offset(position.x, position.y)); - } - return polyPoints; - } - - /// To return predicted forecast values. - int _getForecastDate( - ChartAxisRendererDetails axisRendererDetails, bool _isForward) { - Duration duration = Duration.zero; - final DateTimeAxis axis = axisRendererDetails.axis as DateTimeAxis; - switch (axis.intervalType) { - case DateTimeIntervalType.auto: - duration = Duration.zero; - break; - case DateTimeIntervalType.years: - duration = Duration( - days: (365.25 * - (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast)) - .round()); - break; - case DateTimeIntervalType.months: - duration = Duration( - days: 31 * - (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast) - .round()); - break; - case DateTimeIntervalType.days: - duration = Duration( - days: (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast) - .round()); - break; - case DateTimeIntervalType.hours: - duration = Duration( - hours: (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast) - .round()); - break; - case DateTimeIntervalType.minutes: - duration = Duration( - minutes: (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast) - .round()); - break; - case DateTimeIntervalType.seconds: - duration = Duration( - seconds: (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast) - .round()); - break; - case DateTimeIntervalType.milliseconds: - duration = Duration( - milliseconds: (_isForward - ? trendline.forwardForecast - : trendline.backwardForecast) - .round()); - } - return duration.inMilliseconds; - } -} - -class _SlopeIntercept { - num? slope; - 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 deleted file mode 100644 index 5a1eb511d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart +++ /dev/null @@ -1,258 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/event_args.dart'; -import '../../common/rendering_details.dart'; -import '../base/chart_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/renderer.dart'; -import '../trendlines/trendlines.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Represents the trend line painter. -class TrendlinePainter extends CustomPainter { - /// Creates an instance for trend line painter. - TrendlinePainter( - {required this.stateProperties, - required this.trendlineAnimations, - required ValueNotifier notifier}) - : super(repaint: notifier); - - /// Holds the cartesian state properties value. - final CartesianStateProperties stateProperties; - - /// Holds the list of trend line animation. - final Map> trendlineAnimations; - - @override - void paint(Canvas canvas, Size size) { - double animationFactor; - Animation? trendlineAnimation; - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - final RenderingDetails renderingDetails = - stateProperties.renderingDetails; - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - TrendlineRenderer trendlineRenderer; - Trendline trendline; - List controlPoints; - const int minimumDataLength = 2; - if (series.trendlines != null) { - for (int j = 0; j < series.trendlines!.length; j++) { - trendline = series.trendlines![j]; - trendlineRenderer = seriesRendererDetails.trendlineRenderer[j]; - assert(trendline.width >= 0, - 'The width of the trendlines must be greater or equal to 0.'); - 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 && - trendlineRenderer.pointsData != null && - trendlineRenderer.pointsData!.isNotEmpty) { - canvas.save(); - animationFactor = (!renderingDetails.isLegendToggled && - (seriesRendererDetails.oldSeries == null)) && - trendlineAnimation != null - ? trendlineAnimation.value - : 1; - final Rect axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.clipRect(axisClipRect); - final Path path = Path(); - final Paint paint = Paint(); - paint.strokeWidth = trendline.width; - if (seriesRendererDetails.reAnimate == true || - (trendline.animationDuration > 0 && - seriesRendererDetails.oldSeries == null)) { - performLinearAnimation( - stateProperties, - seriesRendererDetails.xAxisDetails!.axis, - canvas, - animationFactor); - } - renderTrendlineEvent( - stateProperties.chart, - trendline, - series.trendlines!.indexOf(trendline), - stateProperties.chartSeries.visibleSeriesRenderers - .indexOf(seriesRendererDetails.renderer), - seriesRendererDetails.seriesName!); - paint.color = trendlineRenderer.fillColor - .withOpacity(trendlineRenderer.opacity); - paint.style = PaintingStyle.stroke; - // The first two points are always accessed to generate the linear trend line, - // so a single data point throws an exception, thus ensured the data length is more than or equal to 2. - if (trendline.type == TrendlineType.linear && - trendlineRenderer.points.length >= minimumDataLength) { - path.moveTo(trendlineRenderer.points[0].dx, - trendlineRenderer.points[0].dy); - path.lineTo(trendlineRenderer.points[1].dx, - trendlineRenderer.points[1].dy); - } else if (trendline.type == TrendlineType.exponential || - trendline.type == TrendlineType.power || - trendline.type == TrendlineType.logarithmic) { - path.moveTo(trendlineRenderer.points[0].dx, - trendlineRenderer.points[0].dy); - for (int i = 0; i < trendlineRenderer.points.length - 1; i++) { - controlPoints = trendlineRenderer.getControlPoints( - trendlineRenderer.points, i); - path.cubicTo( - 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) { - path.moveTo(trendlineRenderer.points[0].dx, - trendlineRenderer.points[0].dy); - for (int i = 0; i < trendlineRenderer.points.length - 1; i++) { - controlPoints = trendlineRenderer.getControlPoints( - trendlineRenderer.points, i); - path.cubicTo( - 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 && - trendlineRenderer.pointsData != null && - trendlineRenderer.pointsData!.isNotEmpty) { - final List polynomialPoints = - trendlineRenderer.getPolynomialCurve( - trendlineRenderer.pointsData!, - seriesRendererDetails, - stateProperties); - path.moveTo(polynomialPoints[0].dx, polynomialPoints[0].dy); - for (int i = 1; i < polynomialPoints.length; i++) { - path.lineTo(polynomialPoints[i].dx, polynomialPoints[i].dy); - } - } else if (trendline.type == TrendlineType.movingAverage) { - path.moveTo(trendlineRenderer.points[0].dx, - trendlineRenderer.points[0].dy); - for (int i = 1; i < trendlineRenderer.points.length; i++) { - path.lineTo(trendlineRenderer.points[i].dx, - trendlineRenderer.points[i].dy); - } - } - _drawTrendlineMarker(trendlineRenderer, trendline, - seriesRendererDetails, animationFactor, canvas, path, paint); - } - } - } - } - } - - /// To draw the marker on trendline. - void _drawTrendlineMarker( - TrendlineRenderer trendlineRenderer, - Trendline trendline, - SeriesRendererDetails seriesRendererDetails, - double animationFactor, - Canvas canvas, - Path path, - Paint paint, - ) { - Rect clipRect; - final Rect _axisClipRect = stateProperties.chartAxis.axisClipRect; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - (trendlineRenderer.dashArray != null) - ? drawDashedLine(canvas, trendlineRenderer.dashArray!, paint, path) - : canvas.drawPath(path, paint); - clipRect = calculatePlotOffset( - Rect.fromLTRB( - _axisClipRect.left - trendline.markerSettings.width, - _axisClipRect.top - trendline.markerSettings.height, - _axisClipRect.right + trendline.markerSettings.width, - _axisClipRect.bottom + trendline.markerSettings.height), - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - canvas.restore(); - if (trendlineRenderer.visible && - (animationFactor > stateProperties.trendlineDurationFactor)) { - canvas.clipRect(clipRect); - - if (trendline.markerSettings.isVisible) { - for (final CartesianChartPoint point - in trendlineRenderer.pointsData!) { - if (point.isVisible && point.isGap != true) { - if (trendline.markerSettings.shape == DataMarkerType.image) { - drawImageMarker(seriesRendererDetails, canvas, - point.markerPoint!.x, point.markerPoint!.y); - } - final Paint strokePaint = Paint() - ..color = trendline.markerSettings.borderWidth == 0 - ? Colors.transparent - : trendline.markerSettings.borderColor ?? - trendlineRenderer.fillColor - ..strokeWidth = trendline.markerSettings.borderWidth - ..style = PaintingStyle.stroke; - - final Paint fillPaint = Paint() - ..color = trendline.markerSettings.color ?? - (renderingDetails.chartTheme.brightness == Brightness.light - ? Colors.white - : Colors.black) - ..style = PaintingStyle.fill; - final int index = trendlineRenderer.pointsData!.indexOf(point); - canvas.drawPath(trendlineRenderer.markerShapes[index], strokePaint); - canvas.drawPath(trendlineRenderer.markerShapes[index], fillPaint); - } - } - } - } - } - - /// Setting the values of render trend line event. - void renderTrendlineEvent(SfCartesianChart chart, Trendline trendline, - int trendlineIndex, int seriesIndex, String seriesName) { - TrendlineRenderParams args; - final TrendlineRenderer trendlineRenderer = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]) - .trendlineRenderer[trendlineIndex]; - - final List? slope = trendline.type == TrendlineType.polynomial - ? trendlineRenderer.polynomialSlopesData - : trendline.type == TrendlineType.movingAverage - ? null - : [trendlineRenderer.slopeInterceptData.slope!.toDouble()]; - if (trendline.onRenderDetailsUpdate != null && - !trendlineRenderer.isTrendlineRenderEvent) { - trendlineRenderer.isTrendlineRenderEvent = true; - args = TrendlineRenderParams( - trendlineRenderer.slopeIntercept.intercept?.toDouble(), - seriesIndex, - trendlineRenderer.name!, - seriesName, - trendlineRenderer.points, - slope, - getRSquaredValue( - stateProperties.seriesRenderers[seriesIndex], - trendline, - slope, - trendlineRenderer.slopeIntercept.intercept?.toDouble())); - trendline.onRenderDetailsUpdate!(args); - } - } - - @override - bool shouldRepaint(TrendlinePainter oldDelegate) => true; -} 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 deleted file mode 100644 index 082766376..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart +++ /dev/null @@ -1,506 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../base/chart_base.dart'; -import '../chart_behavior/chart_behavior.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'crosshair_painter.dart'; - -/// This class has the properties of the crosshair behavior. -/// -/// Crosshair behavior has the activation mode and line type property to set the behavior of the crosshair. -/// It also has the property to customize the appearance. -/// -/// Provide options for activation mode, line type, line color, line width, hide delay for customizing the -/// behavior of the crosshair. -class CrosshairBehavior { - /// Creating an argument constructor of CrosshairBehavior class. - CrosshairBehavior({ - this.activationMode = ActivationMode.longPress, - this.lineType = CrosshairLineType.both, - this.lineDashArray, - this.enable = false, - this.lineColor, - this.lineWidth = 1, - this.shouldAlwaysShow = false, - this.hideDelay = 0, - }); - - /// Toggles the visibility of the crosshair. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior(enable: true); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final bool enable; - - /// Width of the crosshair line. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// lineWidth: 5 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// } - /// ``` - final double lineWidth; - - /// Color of the crosshair line. - /// - /// Color will be applied based on the brightness - /// property of the app. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior(enable: true); - /// enable: true, - /// lineColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final Color? lineColor; - - /// Dashes of the crosshair 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. - /// - /// Defaults to `[0,0]`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// lineDashArray: [10,10] - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final List? lineDashArray; - - /// Gesture for activating the crosshair. - /// - /// Crosshair can be activated in tap, double tap - /// and long press. - /// - /// Defaults to `ActivationMode.longPress`. - /// - /// Also refer [ActivationMode]. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// activationMode: ActivationMode.doubleTap - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final ActivationMode activationMode; - - /// Type of crosshair line. - /// - /// By default, both vertical and horizontal lines will be - /// displayed. You can change this by specifying values to this property. - /// - /// Defaults to `CrosshairLineType.both`. - /// - /// Also refer [CrosshairLineType]. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// lineType: CrosshairLineType.horizontal - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final CrosshairLineType lineType; - - /// Enables or disables the crosshair. - /// - /// By default, the crosshair will be hidden on touch. - /// To avoid this, set this property to true. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// shouldAlwaysShow: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final bool shouldAlwaysShow; - - /// Time delay for hiding the crosshair. - /// - /// Defaults to `0`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// hideDelay: 3000 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final double hideDelay; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is CrosshairBehavior && - other.activationMode == activationMode && - other.lineType == lineType && - other.lineDashArray == lineDashArray && - other.enable == enable && - other.lineColor == lineColor && - other.lineWidth == lineWidth && - other.shouldAlwaysShow == shouldAlwaysShow && - other.hideDelay == hideDelay; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - activationMode, - lineType, - lineDashArray, - enable, - lineColor, - lineWidth, - shouldAlwaysShow, - hideDelay - ]; - return hashList(values); - } - - /// Represents the cartesian state properties. - late CartesianStateProperties _stateProperties; - - /// Displays the crosshair at the specified x and y-positions. - /// - /// x & y - x and y values/pixel where the crosshair needs to be shown. - /// - /// coordinateUnit - specify the type of x and y values given. `pixel` or `point` for logical pixel and chart data point respectively. - /// - /// Defaults to `point`. - void show(dynamic x, double y, [String coordinateUnit = 'point']) { - // ignore: unnecessary_null_comparison - if (_stateProperties != null) { - final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - _stateProperties.crosshairBehaviorRenderer; - final CrosshairRenderingDetails renderingDetails = - CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); - renderingDetails.internalShow(x, y, coordinateUnit); - } - } - - /// Displays the crosshair at the specified point index. - /// - /// pointIndex - index of point at which the crosshair needs to be shown. - void showByIndex(int pointIndex) { - // ignore: unnecessary_null_comparison - if (_stateProperties != null) { - final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - _stateProperties.crosshairBehaviorRenderer; - final CrosshairRenderingDetails renderingDetails = - CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); - if (validIndex(pointIndex, 0, renderingDetails.crosshairPainter!.chart)) { - if (renderingDetails.crosshairPainter != null) { - final List visibleSeriesRenderer = - renderingDetails.crosshairPainter!.stateProperties.chartSeries - .visibleSeriesRenderers; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderer[0]); - if (pointIndex != null && - pointIndex.abs() < seriesRendererDetails.dataPoints.length) { - renderingDetails.crosshairPainter!.generateAllPoints(Offset( - seriesRendererDetails.dataPoints[pointIndex].markerPoint!.x, - seriesRendererDetails.dataPoints[pointIndex].markerPoint!.y)); - } - renderingDetails.crosshairPainter!.canResetPath = false; - renderingDetails.crosshairPainter!.stateProperties - .repaintNotifiers['crosshair']!.value++; - } - } - } - } - - /// Hides the crosshair if it is displayed. - void hide() { - // ignore: unnecessary_null_comparison - if (_stateProperties != null) { - final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - _stateProperties.crosshairBehaviorRenderer; - final CrosshairRenderingDetails renderingDetails = - CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); - if (renderingDetails.crosshairPainter != null) { - renderingDetails.crosshairPainter!.canResetPath = false; - ValueNotifier(renderingDetails.crosshairPainter!.stateProperties - .repaintNotifiers['crosshair']!.value++); - renderingDetails.crosshairPainter!.timer?.cancel(); - if (!_stateProperties.isTouchUp) { - renderingDetails.crosshairPainter!.stateProperties - .repaintNotifiers['crosshair']!.value++; - renderingDetails.crosshairPainter!.canResetPath = true; - renderingDetails.position = null; - } else { - if (!shouldAlwaysShow) { - final double duration = (hideDelay == 0 && - renderingDetails.crosshairPainter!.stateProperties - .enableDoubleTap == - true) - ? 200 - : hideDelay; - renderingDetails.crosshairPainter!.timer = - Timer(Duration(milliseconds: duration.toInt()), () { - renderingDetails.crosshairPainter!.stateProperties - .repaintNotifiers['crosshair']!.value++; - renderingDetails.crosshairPainter!.canResetPath = true; - renderingDetails.position = null; - }); - } - } - } - } - } -} - -/// Crosshair renderer class for mutable fields and methods. -class CrosshairBehaviorRenderer with ChartBehavior { - /// Creates an argument constructor for Crosshair renderer class. - CrosshairBehaviorRenderer(this._stateProperties) { - _crosshairRenderingDetails = CrosshairRenderingDetails(_stateProperties); - } - - final CartesianStateProperties _stateProperties; - - /// Specifies the value of crosshair rendering details. - late CrosshairRenderingDetails _crosshairRenderingDetails; - - /// Enables the crosshair on double tap. - @override - void onDoubleTap(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); - - /// Enables the crosshair on long press. - @override - void onLongPress(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); - - /// Enables the crosshair on touch down. - @override - void onTouchDown(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); - - /// Enables the crosshair on touch move. - @override - void onTouchMove(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); - - /// Enables the crosshair on touch up. - @override - void onTouchUp(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.hide(); - - /// Enables the crosshair on mouse hover. - @override - void onEnter(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); - - /// Disables the crosshair on mouse exit. - @override - void onExit(double xPos, double yPos) => - _crosshairRenderingDetails._crosshairBehavior.hide(); - - /// Draws the crosshair. - @override - void onPaint(Canvas canvas) { - if (_crosshairRenderingDetails.crosshairPainter != null) { - _crosshairRenderingDetails.crosshairPainter!.drawCrosshair(canvas); - } - } -} - -/// Represents the class that holds the rendering details of cross hair. -class CrosshairRenderingDetails { - /// Creates an instance of cross hair rendering details. - CrosshairRenderingDetails(this._stateProperties); - - final CartesianStateProperties _stateProperties; - - SfCartesianChart get _chart => _stateProperties.chart; - - CrosshairBehavior get _crosshairBehavior => _chart.crosshairBehavior; - - /// Touch position. - Offset? position; - - /// Holds the instance of CrosshairPainter. - CrosshairPainter? crosshairPainter; - - /// Check whether long press activated or not. - bool isLongPressActivated = false; - - /// To draw cross hair line. - 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 && paint != null) { - crosshairPainter!.drawCrosshairLine(canvas, paint, seriesIndex); - } - } - - /// To get the paint value for the crosshair. - Paint? linePainter(Paint paint) => crosshairPainter?.getLinePainter(paint); - - /// To show the crosshair with provided coordinates. - void internalShow(dynamic x, double y, [String coordinateUnit = 'point']) { - final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - _stateProperties.crosshairBehaviorRenderer; - if (coordinateUnit != 'pixel') { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _stateProperties.chartSeries.visibleSeriesRenderers[0]); - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartLocation location = calculatePoint( - (x is DateTime && xAxisDetails is! DateTimeCategoryAxisDetails) - ? x.millisecondsSinceEpoch - : ((x is DateTime && xAxisDetails is DateTimeCategoryAxisDetails) - ? xAxisDetails.labels - .indexOf(xAxisDetails.dateFormat.format(x)) - : ((x is String && xAxisDetails is CategoryAxisDetails) - ? xAxisDetails.labels.indexOf(x) - : x)), - y, - xAxisDetails, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - seriesRendererDetails.stateProperties.chartAxis.axisClipRect); - x = location.x; - y = location.y.truncateToDouble(); - } - - final CrosshairRenderingDetails renderingDetails = - CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); - if (renderingDetails.crosshairPainter != null && x != null) { - renderingDetails.crosshairPainter! - .generateAllPoints(Offset(x.toDouble(), y)); - renderingDetails.crosshairPainter!.canResetPath = false; - renderingDetails.crosshairPainter!.stateProperties - .repaintNotifiers['crosshair']!.value++; - } - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the crosshair rendering details instance from its renderer. -class CrosshairHelper { - /// Returns the crosshair rendering details instance from its renderer. - static CrosshairRenderingDetails getRenderingDetails( - CrosshairBehaviorRenderer renderer) { - return renderer._crosshairRenderingDetails; - } - - /// Method to set the cartesian state properties. - static void setStateProperties(CrosshairBehavior crosshairBehavior, - CartesianStateProperties stateProperties) { - crosshairBehavior._stateProperties = stateProperties; - } -} 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 deleted file mode 100644 index 506f48b84..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart +++ /dev/null @@ -1,583 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../common/event_args.dart'; -import '../../common/rendering_details.dart'; -import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../base/chart_base.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/renderer.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'crosshair.dart'; - -/// Represents the crosshair painter. -class CrosshairPainter extends CustomPainter { - /// Calling the default constructor of CrosshairPainter class. - CrosshairPainter({required this.stateProperties, required this.valueNotifier}) - : chart = stateProperties.chart, - super(repaint: valueNotifier); - - /// Represents the cartesian state properties. - final CartesianStateProperties stateProperties; - - /// Represents the cartesian chart. - final SfCartesianChart chart; - - RenderingDetails get _renderingDetails => stateProperties.renderingDetails; - - /// Represents the value of timer. - Timer? timer; - - /// Repaint notifier for crosshair. - ValueNotifier valueNotifier; - - // double pointerLength; - // double pointerWidth; - - /// Represents the nose point y value. - double nosePointY = 0; - - /// Represents the nose point x value. - double nosePointX = 0; - - /// Represents the total width value. - double totalWidth = 0; - - // double x; - // double y; - // double xPos; - // double yPos; - - /// Represents the value of isTop. - bool isTop = false; - - // double cornerRadius; - - /// Represents the background path value for crosshair. - Path backgroundPath = Path(); - - /// Represents the canResetPath value of crosshair. - bool canResetPath = true; - - /// Represents the value of isleft. - bool isLeft = false; - - /// Represents the value of isRight. - bool isRight = false; - - // bool enable; - - /// Represents the padding value for crosshair. - double padding = 0; - - /// Specifies the list of string value for the crosshair tooltip. - List stringValue = []; - - /// Represents the boundary rect for crosshair. - Rect boundaryRect = Rect.zero; - - /// Represents the left padding for crosshair. - double leftPadding = 0; - - /// Represents the top padding for crosshair. - double topPadding = 0; - - /// Specifies whether the orientation is horizontal or not. - bool isHorizontalOrientation = false; - - // TextStyle labelStyle; - - @override - void paint(Canvas canvas, Size size) { - if (!canResetPath) { - stateProperties.crosshairBehaviorRenderer.onPaint(canvas); - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => true; - - /// Calculate trackball points. - void generateAllPoints(Offset position) { - final Rect _axisClipRect = stateProperties.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; - CrosshairHelper.getRenderingDetails( - stateProperties.crosshairBehaviorRenderer) - .position = Offset(dx, dy); - } - - /// Gets the line painter. - Paint getLinePainter(Paint crosshairLinePaint) => crosshairLinePaint; - - /// Draw the path of the crosshair line. - void drawCrosshairLine(Canvas canvas, Paint paint, int? index) { - final CrosshairRenderingDetails renderingDetails = - CrosshairHelper.getRenderingDetails( - stateProperties.crosshairBehaviorRenderer); - if (renderingDetails.position != null) { - final Path dashArrayPath = Path(); - if ((chart.crosshairBehavior.lineType == CrosshairLineType.horizontal || - chart.crosshairBehavior.lineType == CrosshairLineType.both) && - chart.crosshairBehavior.lineWidth != 0) { - dashArrayPath.moveTo(stateProperties.chartAxis.axisClipRect.left, - renderingDetails.position!.dy); - dashArrayPath.lineTo(stateProperties.chartAxis.axisClipRect.right, - renderingDetails.position!.dy); - chart.crosshairBehavior.lineDashArray != null - ? 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(renderingDetails.position!.dx, - stateProperties.chartAxis.axisClipRect.top); - dashArrayPath.lineTo(renderingDetails.position!.dx, - stateProperties.chartAxis.axisClipRect.bottom); - chart.crosshairBehavior.lineDashArray != null - ? drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, - paint, dashArrayPath) - : canvas.drawPath(dashArrayPath, paint); - } - } - } - - /// To draw crosshair. - void drawCrosshair(Canvas canvas) { - final Paint fillPaint = Paint() - ..color = _renderingDetails.chartTheme.crosshairBackgroundColor - ..strokeCap = StrokeCap.butt - ..isAntiAlias = false - ..style = PaintingStyle.fill; - - final Paint strokePaint = Paint() - ..color = _renderingDetails.chartTheme.crosshairBackgroundColor - ..strokeCap = StrokeCap.butt - ..isAntiAlias = false - ..style = PaintingStyle.stroke; - chart.crosshairBehavior.lineWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color = strokePaint.color; - - final Paint crosshairLinePaint = Paint(); - final CrosshairRenderingDetails renderingDetails = - CrosshairHelper.getRenderingDetails( - stateProperties.crosshairBehaviorRenderer); - if (renderingDetails.position != null) { - final Offset position = renderingDetails.position!; - - crosshairLinePaint.color = chart.crosshairBehavior.lineColor ?? - _renderingDetails.chartTheme.crosshairLineColor; - crosshairLinePaint.strokeWidth = chart.crosshairBehavior.lineWidth; - crosshairLinePaint.style = PaintingStyle.stroke; - CrosshairRenderArgs crosshairEventArgs; - if (chart.onCrosshairPositionChanging != null) { - crosshairEventArgs = CrosshairRenderArgs(); - crosshairEventArgs.text = ''; - crosshairEventArgs.lineColor = crosshairLinePaint.color; - chart.onCrosshairPositionChanging!(crosshairEventArgs); - crosshairLinePaint.color = crosshairEventArgs.lineColor; - } - renderingDetails.drawLine( - canvas, renderingDetails.linePainter(crosshairLinePaint), null); - - _drawBottomAxesTooltip(canvas, position, strokePaint, fillPaint); - _drawTopAxesTooltip(canvas, position, strokePaint, fillPaint); - _drawLeftAxesTooltip(canvas, position, strokePaint, fillPaint); - _drawRightAxesTooltip(canvas, position, strokePaint, fillPaint); - } - } - - /// Draw bottom axes tooltip. - void _drawBottomAxesTooltip( - Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { - ChartAxisRendererDetails axisDetails; - String value; - Size labelSize; - Rect labelRect, validatedRect; - RRect tooltipRect; - //ignore: unused_local_variable - Color? color; - const double padding = 10; - CrosshairRenderArgs crosshairEventArgs; - for (int index = 0; - index < stateProperties.chartAxis.bottomAxesCount.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.bottomAxesCount[index].axisRenderer); - final ChartAxis axis = axisDetails.axis; - if (_needToAddTooltip(axisDetails)) { - fillPaint.color = axis.interactiveTooltip.color ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getXValue(axisDetails.axisRenderer, position); - if (chart.onCrosshairPositionChanging != null) { - crosshairEventArgs = CrosshairRenderArgs( - axis, value, axisDetails.name, axisDetails.orientation); - crosshairEventArgs.text = value; - crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? - _renderingDetails.chartTheme.crosshairLineColor; - chart.onCrosshairPositionChanging!(crosshairEventArgs); - value = crosshairEventArgs.text; - color = crosshairEventArgs.lineColor; - } - labelSize = measureText(value, axis.interactiveTooltip.textStyle); - labelRect = Rect.fromLTWH( - position.dx - (labelSize.width / 2 + padding / 2), - axisDetails.bounds.top + axis.interactiveTooltip.arrowLength, - labelSize.width + padding, - labelSize.height + padding); - labelRect = - validateRectBounds(labelRect, _renderingDetails.chartContainerRect); - validatedRect = validateRectXPosition(labelRect, stateProperties); - backgroundPath.reset(); - tooltipRect = getRoundedCornerRect( - validatedRect, axis.interactiveTooltip.borderRadius); - backgroundPath.addRRect(tooltipRect); - drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - position.dx, - tooltipRect.top - axis.interactiveTooltip.arrowLength, - (tooltipRect.right - tooltipRect.width / 2) + - axis.interactiveTooltip.arrowWidth, - tooltipRect.top, - (tooltipRect.left + tooltipRect.width / 2) - - axis.interactiveTooltip.arrowWidth, - tooltipRect.top, - position.dx, - tooltipRect.top - axis.interactiveTooltip.arrowLength); - _drawTooltipText(canvas, value, axis.interactiveTooltip.textStyle, - tooltipRect, labelSize); - } - } - } - - /// Draw top axes tooltip. - void _drawTopAxesTooltip( - Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { - ChartAxis axis; - ChartAxisRendererDetails axisDetails; - String value; - Size labelSize; - Rect labelRect, validatedRect; - RRect tooltipRect; - const double padding = 10; - //ignore: unused_local_variable - Color? color; - CrosshairRenderArgs crosshairEventArgs; - for (int index = 0; - index < stateProperties.chartAxis.topAxesCount.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.topAxesCount[index].axisRenderer); - axis = axisDetails.axis; - if (_needToAddTooltip(axisDetails)) { - fillPaint.color = axis.interactiveTooltip.color ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getXValue(axisDetails.axisRenderer, position); - if (chart.onCrosshairPositionChanging != null) { - crosshairEventArgs = CrosshairRenderArgs(axisDetails.axis, value, - axisDetails.name, axisDetails.orientation); - crosshairEventArgs.text = value; - crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? - _renderingDetails.chartTheme.crosshairLineColor; - chart.onCrosshairPositionChanging!(crosshairEventArgs); - value = crosshairEventArgs.text; - color = crosshairEventArgs.lineColor; - } - labelSize = measureText(value, axis.interactiveTooltip.textStyle); - labelRect = Rect.fromLTWH( - position.dx - (labelSize.width / 2 + padding / 2), - axisDetails.bounds.top - - (labelSize.height + padding) - - axis.interactiveTooltip.arrowLength, - labelSize.width + padding, - labelSize.height + padding); - labelRect = - validateRectBounds(labelRect, _renderingDetails.chartContainerRect); - validatedRect = validateRectXPosition(labelRect, stateProperties); - backgroundPath.reset(); - tooltipRect = getRoundedCornerRect( - validatedRect, axis.interactiveTooltip.borderRadius); - backgroundPath.addRRect(tooltipRect); - drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - position.dx, - tooltipRect.bottom + axis.interactiveTooltip.arrowLength, - (tooltipRect.right - tooltipRect.width / 2) + - axis.interactiveTooltip.arrowWidth, - tooltipRect.bottom, - (tooltipRect.left + tooltipRect.width / 2) - - axis.interactiveTooltip.arrowWidth, - tooltipRect.bottom, - position.dx, - tooltipRect.bottom + axis.interactiveTooltip.arrowLength); - _drawTooltipText(canvas, value, axis.interactiveTooltip.textStyle, - tooltipRect, labelSize); - } - } - } - - /// Draw left axes tooltip. - void _drawLeftAxesTooltip( - Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { - ChartAxis axis; - ChartAxisRendererDetails axisDetails; - String value; - Size labelSize; - Rect labelRect, validatedRect; - RRect tooltipRect; - const double padding = 10; - //ignore: unused_local_variable - Color? color; - CrosshairRenderArgs crosshairEventArgs; - for (int index = 0; - index < stateProperties.chartAxis.leftAxesCount.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.leftAxesCount[index].axisRenderer); - axis = axisDetails.axis; - if (_needToAddTooltip(axisDetails)) { - fillPaint.color = axis.interactiveTooltip.color ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getYValue(axisDetails.axisRenderer, position); - if (chart.onCrosshairPositionChanging != null) { - crosshairEventArgs = CrosshairRenderArgs( - axis, value, axisDetails.name, axisDetails.orientation); - crosshairEventArgs.text = value; - crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? - _renderingDetails.chartTheme.crosshairLineColor; - chart.onCrosshairPositionChanging!(crosshairEventArgs); - value = crosshairEventArgs.text; - color = crosshairEventArgs.lineColor; - } - labelSize = measureText(value, axis.interactiveTooltip.textStyle); - labelRect = Rect.fromLTWH( - axisDetails.bounds.left - - (labelSize.width + padding) - - axis.interactiveTooltip.arrowLength, - position.dy - (labelSize.height + padding) / 2, - labelSize.width + padding, - labelSize.height + padding); - labelRect = - validateRectBounds(labelRect, _renderingDetails.chartContainerRect); - validatedRect = validateRectYPosition(labelRect, stateProperties); - backgroundPath.reset(); - tooltipRect = getRoundedCornerRect( - validatedRect, axis.interactiveTooltip.borderRadius); - - backgroundPath.addRRect(tooltipRect); - drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - tooltipRect.right, - tooltipRect.top + - tooltipRect.height / 2 - - axis.interactiveTooltip.arrowWidth, - tooltipRect.right, - tooltipRect.bottom - - tooltipRect.height / 2 + - axis.interactiveTooltip.arrowWidth, - tooltipRect.right + axis.interactiveTooltip.arrowLength, - position.dy, - tooltipRect.right + axis.interactiveTooltip.arrowLength, - position.dy); - - _drawTooltipText(canvas, value, axis.interactiveTooltip.textStyle, - tooltipRect, labelSize); - } - } - } - - /// Draw right axes tooltip. - void _drawRightAxesTooltip( - Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { - ChartAxis axis; - ChartAxisRendererDetails axisDetails; - String value; - Size labelSize; - Rect labelRect, validatedRect; - CrosshairRenderArgs crosshairEventArgs; - RRect tooltipRect; - //ignore: unused_local_variable - Color? color; - const double padding = 10; - for (int index = 0; - index < stateProperties.chartAxis.rightAxesCount.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.rightAxesCount[index].axisRenderer); - axis = axisDetails.axis; - if (_needToAddTooltip(axisDetails)) { - fillPaint.color = axis.interactiveTooltip.color ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor ?? - _renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getYValue(axisDetails.axisRenderer, position); - if (chart.onCrosshairPositionChanging != null) { - crosshairEventArgs = CrosshairRenderArgs( - axis, value, axisDetails.name, axisDetails.orientation); - crosshairEventArgs.text = value; - crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? - _renderingDetails.chartTheme.crosshairLineColor; - chart.onCrosshairPositionChanging!(crosshairEventArgs); - value = crosshairEventArgs.text; - color = crosshairEventArgs.lineColor; - } - labelSize = measureText(value, axis.interactiveTooltip.textStyle); - labelRect = Rect.fromLTWH( - axisDetails.bounds.left + axis.interactiveTooltip.arrowLength, - position.dy - (labelSize.height / 2 + padding / 2), - labelSize.width + padding, - labelSize.height + padding); - labelRect = - validateRectBounds(labelRect, _renderingDetails.chartContainerRect); - validatedRect = validateRectYPosition(labelRect, stateProperties); - backgroundPath.reset(); - tooltipRect = getRoundedCornerRect( - validatedRect, axis.interactiveTooltip.borderRadius); - backgroundPath.addRRect(tooltipRect); - drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - tooltipRect.left, - tooltipRect.top + - tooltipRect.height / 2 - - axis.interactiveTooltip.arrowWidth, - tooltipRect.left, - tooltipRect.bottom - - tooltipRect.height / 2 + - axis.interactiveTooltip.arrowWidth, - tooltipRect.left - axis.interactiveTooltip.arrowLength, - position.dy, - tooltipRect.left - axis.interactiveTooltip.arrowLength, - position.dy); - _drawTooltipText(canvas, value, axis.interactiveTooltip.textStyle, - tooltipRect, labelSize); - } - } - } - - void _drawTooltipText(Canvas canvas, String text, TextStyle textStyle, - RRect tooltipRect, Size labelSize) { - drawText( - canvas, - text, - Offset((tooltipRect.left + tooltipRect.width / 2) - labelSize.width / 2, - (tooltipRect.top + tooltipRect.height / 2) - labelSize.height / 2), - TextStyle( - color: textStyle.color ?? - _renderingDetails.chartTheme.tooltipLabelColor, - fontSize: textStyle.fontSize, - fontWeight: 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), - 0); - } - - /// To find the x value of crosshair. - String _getXValue(ChartAxisRenderer axisRenderer, Offset position) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final num value = pointToXVal( - chart, - axisDetails.axisRenderer, - axisDetails.bounds, - position.dx - - (stateProperties.chartAxis.axisClipRect.left + - axisDetails.axis.plotOffset), - position.dy - - (stateProperties.chartAxis.axisClipRect.top + - axisDetails.axis.plotOffset)); - String resultantString = - getInteractiveTooltipLabel(value, axisDetails.axisRenderer).toString(); - if (axisDetails.axis.interactiveTooltip.format != null) { - final String stringValue = axisDetails.axis.interactiveTooltip.format! - .replaceAll('{value}', resultantString); - resultantString = stringValue; - } - return resultantString; - } - - /// To find the y value of crosshair. - String _getYValue(ChartAxisRenderer axisRenderer, Offset position) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final num value = pointToYVal( - chart, - axisDetails.axisRenderer, - axisDetails.bounds, - position.dx - - (stateProperties.chartAxis.axisClipRect.left + - axisDetails.axis.plotOffset), - position.dy - - (stateProperties.chartAxis.axisClipRect.top + - axisDetails.axis.plotOffset)); - String resultantString = - getInteractiveTooltipLabel(value, axisDetails.axisRenderer).toString(); - if (axisDetails.axis.interactiveTooltip.format != null) { - final String stringValue = axisDetails.axis.interactiveTooltip.format! - .replaceAll('{value}', resultantString); - resultantString = stringValue; - } - return resultantString; - } - - /// To add the tooltip for crosshair. - bool _needToAddTooltip(ChartAxisRendererDetails axisDetails) { - return axisDetails.axis.interactiveTooltip.enable && - ((axisDetails is! CategoryAxisDetails && - axisDetails.visibleLabels.isNotEmpty) || - (axisDetails is CategoryAxisDetails && - axisDetails.labels.isNotEmpty)); - } -} 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 deleted file mode 100644 index ddd41a47e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart +++ /dev/null @@ -1,1891 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; - -import '../../circular_chart/base/circular_base.dart'; -import '../../common/event_args.dart'; - -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/helper.dart'; -import '../../common/utils/typedef.dart'; -import '../../funnel_chart/base/funnel_base.dart'; - -import '../base/chart_base.dart'; - -import '../chart_segment/box_and_whisker_segment.dart'; -import '../chart_segment/candle_segment.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_segment/error_bar_segment.dart'; -import '../chart_segment/fastline_segment.dart'; -import '../chart_segment/hilo_segment.dart'; -import '../chart_segment/hiloopenclose_segment.dart'; -import '../chart_segment/line_segment.dart'; -import '../chart_segment/spline_segment.dart'; -import '../chart_segment/stacked_line_segment.dart'; -import '../chart_segment/stackedline100_segment.dart'; -import '../chart_segment/stepline_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; - -import '../common/common.dart'; - -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Creates a selection renderer for selection behavior. -class SelectionRenderer { - /// Calling the default constructor of SelectionRenderer class. - SelectionRenderer(); - - /// Represents the point index of the data points. - int? pointIndex; - - /// Represents the series index of the cartesian series. - int? seriesIndex; - - /// Represens the value of cartesian series index. - late int cartesianSeriesIndex; - - /// Represents the value of cartesian point index. - int? cartesianPointIndex; - - /// Specifies whether the selection is done or not. - late bool isSelection; - - /// Specifies the value of selected and current segment. - SegmentProperties? selectedSegmentProperties, currentSegmentProperties; - - /// Holds the list of default selected segments. - final List defaultselectedSegments = []; - - /// Holds the list of default unselected segments. - final List defaultunselectedSegments = []; - - /// Specifies whether the segmens is selected or not. - bool isSelected = false; - - /// Represents the value of chart. - late dynamic chart; - - /// Represents the chart state properties. - late dynamic stateProperties; - - /// Represents the chart series renderer. - dynamic seriesRendererDetails; - - /// Represents the fill and stroke color for selection painter. - late Color fillColor, strokeColor; - - /// Represents the value of fill and stroke opacity. - late double fillOpacity, strokeOpacity; - - /// Represents the value of stroke width. - late double strokeWidth; - - /// Specifies the value of selection args. - SelectionArgs? selectionArgs; - - /// Specifies the value of selected segments. - late List selectedSegments; - - /// Specifies the value of unselected segments. - List? unselectedSegments; - - /// Specifies the data point selection type. - SelectionType? selectionType; - - /// Represents the value of viewport point index. - int? viewportIndex; - - /// Specifies whether the point is selected or not. - bool selected = false; - - /// Specifies the user interaction is performed or not for selection. - bool isInteraction = false; - - /// Selects or deselects the specified data point in the series. - /// - /// The following are the arguments to be passed. - /// * `pointIndex` - index of the data point that needs to be selected. - /// * `seriesIndex` - index of the series in which the data point is selected. - /// - /// Where the `pointIndex` is a required argument and `seriesIndex` is an optional argument. By default, `0` will - /// be considered as the series index. Thus it will take effect on the first series if no value is specified. - /// - /// For circular, pyramid and funnel charts, series index should always be `0`, as it has only one series. - /// - /// If the specified data point is already selected, it will be deselected, else it will be selected. - /// Selection type and multi-selection functionality is also applicable for this, but it is based on - /// the API values specified in [ChartSelectionBehavior]. - /// - /// _Note:_ Even though, the [enable] property in [ChartSelectionBehavior] is set to false, this method - /// will work. - - void selectDataPoints(int? pointIndex, [int? seriesIndex]) { - if (chart is SfCartesianChart) { - if (validIndex(pointIndex, seriesIndex, chart)) { - bool select = false; - final List seriesRenderList = - stateProperties.chartSeries.visibleSeriesRenderers; - final CartesianSeriesRenderer seriesRender = - seriesRenderList[seriesIndex!]; - selected = pointIndex != null; - viewportIndex = getVisibleDataPointIndex( - pointIndex, SeriesHelper.getSeriesRendererDetails(seriesRender)); - final String seriesType = seriesRendererDetails.seriesType; - final SelectionBehaviorRenderer selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - selectionDetails.selectionRenderer!.isInteraction = true; - if (isLineTypeSeries(seriesType) || - seriesType.contains('hilo') || - seriesType == 'candle' || - seriesType.contains('boxandwhisker')) { - if (seriesRendererDetails.isSelectionEnable = true) { - selectionDetails.selectionRenderer!.cartesianPointIndex = - pointIndex; - selectionDetails.selectionRenderer!.cartesianSeriesIndex = - seriesIndex; - select = selectionDetails.selectionRenderer!.isCartesianSelection( - chart, seriesRender, pointIndex, seriesIndex); - } - } else { - stateProperties.renderDatalabelRegions = []; - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - if (seriesType.contains('area') || seriesType == 'fastline') { - selectionDetails.selectionRenderer!.seriesIndex = seriesIndex; - } else { - selectionDetails.selectionRenderer!.seriesIndex = seriesIndex; - selectionDetails.selectionRenderer!.pointIndex = pointIndex; - } - select = selectionDetails.selectionRenderer!.isCartesianSelection( - chart, seriesRender, pointIndex, seriesIndex); - } - if (select) { - for (final CartesianSeriesRenderer _seriesRenderer - in stateProperties.chartSeries.visibleSeriesRenderers) { - ValueNotifier( - SeriesHelper.getSeriesRendererDetails(_seriesRenderer) - .repaintNotifier - .value++); - } - } - selectionType = null; - } - } else if (chart is SfCircularChart) { - if (validIndex(pointIndex!, seriesIndex!, chart)) { - stateProperties.chartSeries.seriesPointSelection( - null, chart.selectionGesture, pointIndex, seriesIndex); - } - } else if (chart is SfFunnelChart) { - if (pointIndex! < chart.series.dataSource.length) { - stateProperties.chartSeries - .seriesPointSelection(pointIndex, chart.selectionGesture); - } - } else { - if (pointIndex! < chart.series.dataSource.length) { - seriesRendererDetails.stateProperties.chartSeries - .seriesPointSelection(pointIndex, chart.selectionGesture); - } - } - } - - /// Selection for selected dataPoint index. - void selectedDataPointIndex( - CartesianSeriesRenderer seriesRenderer, List selectedData) { - for (int data = 0; data < selectedData.length; data++) { - final int selectedItem = selectedData[data]; - if (chart.onSelectionChanged != null) {} - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - for (int j = 0; j < seriesRendererDetails.segments.length; j++) { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[j]); - currentSegmentProperties!.segment.currentSegmentIndex == selectedItem - ? selectedSegments.add(seriesRendererDetails.segments[j]) - : unselectedSegments!.add(seriesRendererDetails.segments[j]); - } - } - selectedSegmentsColors(selectedSegments); - unselectedSegmentsColors(unselectedSegments!); - } - - /// Paint method for default fill color settings. - Paint getDefaultFillColor(List>? points, - int? point, ChartSegment segment) { - final String seriesType = seriesRendererDetails.seriesType; - final Paint selectedFillPaint = Paint(); - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - if (seriesRendererDetails.series is CartesianSeries) { - seriesRendererDetails.seriesType == 'line' || - seriesType == 'spline' || - seriesType == 'stepline' || - seriesType == 'fastline' || - seriesType == 'stackedline' || - seriesType == 'stackedline100' || - seriesType.contains('hilo') - ? selectedFillPaint.color = - segmentProperties.defaultStrokeColor!.color - : selectedFillPaint.color = segmentProperties.defaultFillColor!.color; - if (segmentProperties.defaultFillColor?.shader != null) { - selectedFillPaint.shader = segmentProperties.defaultFillColor!.shader; - } - } - - if (seriesRendererDetails.seriesType == 'candle') { - if (segment is CandleSegment && segmentProperties.isSolid == true) { - selectedFillPaint.style = PaintingStyle.fill; - selectedFillPaint.strokeWidth = - seriesRendererDetails.series.borderWidth; - } else { - selectedFillPaint.style = PaintingStyle.stroke; - selectedFillPaint.strokeWidth = - seriesRendererDetails.series.borderWidth; - } - } else { - selectedFillPaint.style = PaintingStyle.fill; - } - return selectedFillPaint; - } - - /// Paint method for default stroke color settings. - Paint getDefaultStrokeColor(List>? points, - int? point, ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final Paint selectedStrokePaint = Paint(); - if (seriesRendererDetails.series is CartesianSeries) { - selectedStrokePaint.color = segmentProperties.defaultStrokeColor!.color; - selectedStrokePaint.strokeWidth = - segmentProperties.defaultStrokeColor!.strokeWidth; - } - selectedStrokePaint.style = PaintingStyle.stroke; - return selectedStrokePaint; - } - - /// Paint method with selected fill color values. - Paint getFillColor(bool isSelection, ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final dynamic selectionBehavior = seriesRendererDetails.selectionBehavior; - final CartesianSeries series = - seriesRendererDetails.series; - assert( - ((selectionBehavior.selectedOpacity != null) == false) || - selectionBehavior.selectedOpacity >= 0 == true && - selectionBehavior.selectedOpacity <= 1 == true, - 'The selected opacity of selection settings should between 0 and 1.'); - assert( - ((selectionBehavior.unselectedOpacity != null) == false) || - selectionBehavior.unselectedOpacity >= 0 == true && - selectionBehavior.unselectedOpacity <= 1 == true, - 'The unselected opacity of selection settings should between 0 and 1.'); - final ChartSelectionCallback? chartEventSelection = - chart.onSelectionChanged; - if (isSelection) { - // ignore: unnecessary_type_check - if (series is CartesianSeries) { - fillColor = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.selectedColor != null - ? selectionArgs!.selectedColor - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.selectedColor != null - ? stateProperties.selectionArgs!.selectedColor - : selectionBehavior.selectedColor ?? - segmentProperties.defaultFillColor!.color; - } - fillOpacity = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.selectedColor != null - ? selectionArgs!.selectedColor!.opacity - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.selectedColor != null - ? stateProperties.selectionArgs!.selectedColor.opacity - : selectionBehavior.selectedOpacity ?? series.opacity; - } else { - // ignore: unnecessary_type_check - if (series is CartesianSeries) { - fillColor = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.unselectedColor != null - ? selectionArgs!.unselectedColor - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.unselectedColor != null - ? stateProperties.selectionArgs!.unselectedColor - : selectionBehavior.unselectedColor ?? - segmentProperties.defaultFillColor!.color; - } - fillOpacity = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.unselectedColor != null - ? selectionArgs!.unselectedColor!.opacity - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.unselectedColor != null - ? stateProperties.selectionArgs!.unselectedColor.opacity - : selectionBehavior.unselectedOpacity ?? series.opacity; - } - final Paint selectedFillPaint = Paint(); - selectedFillPaint.color = fillColor.withOpacity(fillOpacity); - if (seriesRendererDetails.seriesType == 'candle') { - if (segment is CandleSegment && segmentProperties.isSolid == true) { - selectedFillPaint.style = PaintingStyle.fill; - selectedFillPaint.strokeWidth = series.borderWidth; - } else { - selectedFillPaint.style = PaintingStyle.stroke; - selectedFillPaint.strokeWidth = series.borderWidth; - } - } else { - selectedFillPaint.style = PaintingStyle.fill; - } - return selectedFillPaint; - } - - /// Paint method with selected stroke color values. - Paint getStrokeColor(bool isSelection, ChartSegment segment, - [CartesianChartPoint? point]) { - final CartesianSeries series = - seriesRendererDetails.series; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - final String seriesType = seriesRendererDetails.seriesType; - final dynamic selectionBehavior = seriesRendererDetails.selectionBehavior; - final ChartSelectionCallback? chartEventSelection = - chart.onSelectionChanged; - if (isSelection) { - // ignore: unnecessary_type_check - if (series is CartesianSeries) { - seriesType == 'line' || - seriesType == 'spline' || - seriesType == 'stepline' || - seriesType == 'fastline' || - seriesType == 'stackedline' || - seriesType == 'stackedline100' || - seriesType.contains('hilo') || - seriesType == 'candle' || - seriesType == 'boxandwhisker' - ? strokeColor = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.selectedColor != null - ? selectionArgs!.selectedColor - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.selectedColor != null - ? stateProperties.selectionArgs!.selectedColor - : selectionBehavior.selectedColor ?? - segmentProperties.defaultFillColor!.color - : strokeColor = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.selectedBorderColor != null - ? selectionArgs!.selectedBorderColor - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.selectedBorderColor != - null - ? stateProperties.selectionArgs!.selectedBorderColor - : selectionBehavior.selectedBorderColor ?? - series.borderColor; - } - - strokeOpacity = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.selectedBorderColor != null - ? selectionArgs!.selectedBorderColor!.opacity - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.selectedBorderColor != null - ? stateProperties.selectionArgs!.selectedBorderColor.opacity - : selectionBehavior.selectedOpacity ?? series.opacity; - - strokeWidth = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.selectedBorderWidth != null - ? selectionArgs!.selectedBorderWidth - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.selectedBorderWidth != null - ? stateProperties.selectionArgs!.selectedBorderWidth - : selectionBehavior.selectedBorderWidth ?? series.borderWidth; - } else { - // ignore: unnecessary_type_check - if (series is CartesianSeries) { - segment is LineSegment || - segment is SplineSegment || - segment is StepLineSegment || - segment is FastLineSegment || - segment is StackedLineSegment || - segment is HiloSegment || - segment is HiloOpenCloseSegment || - segment is CandleSegment || - segment is BoxAndWhiskerSegment - ? strokeColor = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.unselectedColor != null - ? selectionArgs!.unselectedColor - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.unselectedColor != null - ? stateProperties.selectionArgs!.unselectedColor - : selectionBehavior.unselectedColor ?? - segmentProperties.defaultFillColor!.color - : strokeColor = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.unselectedBorderColor != null - ? selectionArgs!.unselectedBorderColor - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.unselectedBorderColor != - null - ? stateProperties.selectionArgs!.unselectedBorderColor - : selectionBehavior.unselectedBorderColor ?? - series.borderColor; - } - strokeOpacity = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.unselectedColor != null - ? selectionArgs!.unselectedColor!.opacity - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.unselectedColor != null - ? stateProperties.selectionArgs!.unselectedColor.opacity - : selectionBehavior.unselectedOpacity ?? series.opacity; - strokeWidth = chartEventSelection != null && - selectionArgs != null && - selectionArgs!.unselectedBorderWidth != null - ? selectionArgs!.unselectedBorderWidth - : stateProperties.selectionArgs != null && - stateProperties.selectionArgs!.unselectedBorderWidth != null - ? stateProperties.selectionArgs!.unselectedBorderWidth - : selectionBehavior.unselectedBorderWidth ?? series.borderWidth; - } - final Paint selectedStrokePaint = Paint(); - selectedStrokePaint.color = strokeColor; - selectedStrokePaint.strokeWidth = strokeWidth; - selectedStrokePaint.color.withOpacity(series.opacity); - selectedStrokePaint.style = PaintingStyle.stroke; - return selectedStrokePaint; - } - - /// Give selected color for selected segments. - void selectedSegmentsColors(List selectedSegments) { - for (int i = 0; i < selectedSegments.length; i++) { - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegments[i]); - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries - .visibleSeriesRenderers[selectedSegmentProperties.seriesIndex]); - if (seriesRendererDetails.seriesType.contains('area') == false && - seriesRendererDetails.seriesType != 'fastline') { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[selectedSegments[i].currentSegmentIndex]); - final Paint fillPaint = - getFillColor(true, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = - seriesRendererDetails.selectionBehaviorRenderer.getSelectedItemFill( - fillPaint, - selectedSegmentProperties.seriesIndex, - selectedSegments[i].currentSegmentIndex, - selectedSegments); - final Paint strokePaint = - getStrokeColor(true, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = seriesRendererDetails - .selectionBehaviorRenderer - .getSelectedItemBorder( - strokePaint, - selectedSegmentProperties.seriesIndex, - selectedSegments[i].currentSegmentIndex, - selectedSegments); - } else { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - final Paint fillPaint = - getFillColor(true, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = - seriesRendererDetails.selectionBehaviorRenderer.getSelectedItemFill( - fillPaint, - selectedSegmentProperties.seriesIndex, - selectedSegments[i].currentSegmentIndex, - selectedSegments); - final Paint strokePaint = - getStrokeColor(true, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = seriesRendererDetails - .selectionBehaviorRenderer - .getSelectedItemBorder( - strokePaint, - selectedSegmentProperties.seriesIndex, - selectedSegments[i].currentSegmentIndex, - selectedSegments); - } - } - } - - /// Give unselected color for unselected segments. - void unselectedSegmentsColors(List unselectedSegments) { - for (int i = 0; i < unselectedSegments.length; i++) { - if (unselectedSegments[i] is! ErrorBarSegment) { - final SegmentProperties unselectedSegmentProperties = - SegmentHelper.getSegmentProperties(unselectedSegments[i]); - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[ - unselectedSegmentProperties.seriesIndex]); - if (seriesRendererDetails.seriesType.contains('area') == false && - seriesRendererDetails.seriesType != 'fastline') { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[unselectedSegments[i].currentSegmentIndex]); - final Paint fillPaint = - getFillColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = seriesRendererDetails - .selectionBehaviorRenderer - .getUnselectedItemFill( - fillPaint, - unselectedSegmentProperties.seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - final Paint strokePaint = - getStrokeColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = seriesRendererDetails - .selectionBehaviorRenderer - .getUnselectedItemBorder( - strokePaint, - unselectedSegmentProperties.seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - } else { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - final Paint fillPaint = - getFillColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = seriesRendererDetails - .selectionBehaviorRenderer - .getUnselectedItemFill( - fillPaint, - unselectedSegmentProperties.seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - final Paint strokePaint = - getStrokeColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = seriesRendererDetails - .selectionBehaviorRenderer - .getUnselectedItemBorder( - strokePaint, - unselectedSegmentProperties.seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - } - } - } - } - - /// Change color and removing unselected segments from list. - void changeColorAndPopUnselectedSegments( - List unselectedSegments) { - int k = unselectedSegments.length - 1; - while (unselectedSegments.isNotEmpty) { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[ - SegmentHelper.getSegmentProperties(unselectedSegments[k]) - .seriesIndex]); - if (seriesRendererDetails.seriesType.contains('area') == false && - seriesRendererDetails.seriesType != 'fastline') { - if (unselectedSegments[k].currentSegmentIndex! < - seriesRendererDetails.segments.length && - unselectedSegments[k] is! ErrorBarSegment) { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[unselectedSegments[k].currentSegmentIndex]); - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - final Paint strokePaint = getDefaultStrokeColor( - null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = strokePaint; - } - unselectedSegments.remove(unselectedSegments[k]); - k--; - } else { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = strokePaint; - unselectedSegments.remove(unselectedSegments[0]); - k--; - } - } - } - - /// Change color and remove selected segments from list. - bool changeColorAndPopSelectedSegments( - List selectedSegments, bool isSamePointSelect) { - int j = selectedSegments.length - 1; - while (selectedSegments.isNotEmpty) { - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegments[j]); - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries - .visibleSeriesRenderers[selectedSegmentProperties.seriesIndex]); - if (seriesRendererDetails.seriesType.contains('area') == false && - seriesRendererDetails.seriesType != 'fastline') { - if (selectedSegments[j].currentSegmentIndex! < - seriesRendererDetails.segments.length) { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[selectedSegments[j].currentSegmentIndex]); - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - final Paint strokePaint = getDefaultStrokeColor( - null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = strokePaint; - if (seriesRendererDetails.seriesType == 'line' || - seriesRendererDetails.seriesType == 'spline' || - seriesRendererDetails.seriesType == 'stepline' || - seriesRendererDetails.seriesType == 'stackedline' || - seriesRendererDetails.seriesType == 'stackedline100' || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType.contains('boxandwhisker') == - true) { - if (selectedSegmentProperties.currentPoint!.overallDataPointIndex == - cartesianPointIndex && - selectedSegmentProperties.seriesIndex == cartesianSeriesIndex) { - isSamePointSelect = true; - } - } else { - if ((currentSegmentProperties.currentPoint!.overallDataPointIndex == - pointIndex || - selectedSegmentProperties - .currentPoint!.overallDataPointIndex == - pointIndex) && - currentSegmentProperties.oldSegmentIndex == - selectedSegmentProperties.oldSegmentIndex && - selectedSegmentProperties.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - } - } - selectedSegments.remove(selectedSegments[j]); - j--; - } else { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = strokePaint; - if (SegmentHelper.getSegmentProperties(selectedSegments[0]) - .seriesIndex == - seriesIndex) { - isSamePointSelect = true; - } - selectedSegments.remove(selectedSegments[0]); - j--; - } - } - return isSamePointSelect; - } - - bool _isSamePointSelected(List selectedSegments) { - bool isSamePointSelected = false; - for (int j = 0; - j < selectedSegments.length && selectedSegments.isNotEmpty; - j++) { - final SegmentProperties selectedSegmentProperties = - SegmentHelper.getSegmentProperties(selectedSegments[j]); - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries - .visibleSeriesRenderers[selectedSegmentProperties.seriesIndex]); - if (seriesRendererDetails.seriesType.contains('area') == false && - seriesRendererDetails.seriesType != 'fastline') { - if (selectedSegments[j].currentSegmentIndex! < - seriesRendererDetails.segments.length) { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[selectedSegments[j].currentSegmentIndex]); - if (((seriesRendererDetails.seriesType.indexOf('line') >= 0) == - true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType.contains('boxandwhisker') == - true) && - selectedSegmentProperties.currentPoint!.overallDataPointIndex == - cartesianPointIndex && - selectedSegmentProperties.seriesIndex == cartesianSeriesIndex) { - isSamePointSelected = true; - } else { - if ((currentSegmentProperties.currentPoint!.overallDataPointIndex == - pointIndex || - selectedSegmentProperties - .currentPoint!.overallDataPointIndex == - pointIndex) && - currentSegmentProperties.oldSegmentIndex == - selectedSegmentProperties.oldSegmentIndex && - selectedSegmentProperties.seriesIndex == seriesIndex) { - isSamePointSelected = true; - } - } - } - } else { - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = strokePaint; - if (SegmentHelper.getSegmentProperties(selectedSegments[0]) - .seriesIndex == - seriesIndex) { - isSamePointSelected = true; - } - } - } - return isSamePointSelected; - } - - /// To get the selected segment on tap. - ChartSegment? getTappedSegment() { - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - for (int k = 0; k < seriesRendererDetails.segments.length; k++) { - if (seriesRendererDetails.seriesType.contains('area') == false && - seriesRendererDetails.seriesType != 'fastline') { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - if (seriesRendererDetails.seriesType == 'line' || - seriesRendererDetails.seriesType == 'spline' || - seriesRendererDetails.seriesType == 'stepline' || - seriesRendererDetails.seriesType == 'stackedline' || - seriesRendererDetails.seriesType == 'stackedline100' || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType.contains('boxandwhisker') == - true) { - if (currentSegmentProperties!.segment.currentSegmentIndex == - cartesianPointIndex && - currentSegmentProperties!.seriesIndex == cartesianSeriesIndex) { - selectedSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - } - } else { - if (currentSegmentProperties!.currentPoint!.overallDataPointIndex == - pointIndex && - currentSegmentProperties!.seriesIndex == seriesIndex) { - selectedSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - } - } - } else { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - if (currentSegmentProperties!.seriesIndex == - SelectionHelper.getRenderingDetails( - seriesRendererDetails.selectionBehaviorRenderer!) - .selectionRenderer! - .seriesIndex) { - selectedSegmentProperties = currentSegmentProperties; - break; - } - } - } - } - return selectedSegmentProperties?.segment; - } - - /// To check position of the selection segment. - bool checkPosition() { - outerLoop: - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - for (int k = 0; k < seriesRendererDetails.segments.length; k++) { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - if ((currentSegmentProperties!.currentPoint!.overallDataPointIndex == - pointIndex) && - currentSegmentProperties!.seriesIndex == seriesIndex) { - isSelected = true; - break outerLoop; - } else { - isSelected = false; - } - } - } - return isSelected; - } - - /// To ensure selection for cartesian chart type. - bool isCartesianSelection(SfCartesianChart chartAssign, - CartesianSeriesRenderer seriesAssign, int? pointIndex, int? seriesIndex) { - chart = chartAssign; - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesAssign); - - if (chart.onSelectionChanged != null && - selected && - (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && - _isSamePointSelected(selectedSegments)))) { - chart.onSelectionChanged(getSelectionEventArgs( - seriesRendererDetails.series, - seriesIndex!, - viewportIndex!, - seriesRendererDetails)); - selected = false; - } - - /// Maintained the event arguments on zooming, device orientation change. - if (selectionArgs != null) { - stateProperties.selectionArgs = selectionArgs; - } - - /// For point mode. - if ((selectionType ?? chart.selectionType) == SelectionType.point) { - if (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && - _isSamePointSelected(selectedSegments))) { - bool isSamePointSelect = false; - - /// UnSelecting the last selected segment. - if (selectedSegments.length == 1) { - changeColorAndPopUnselectedSegments(unselectedSegments!); - } - - /// Executes when multiSelection is enabled. - bool multiSelect = false; - if (chart.enableMultiSelection == true) { - if (selectedSegments.isNotEmpty) { - for (int i = - stateProperties.chartSeries.visibleSeriesRenderers.length - - 1; - i >= 0; - i--) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - - /// To identify the tapped segment. - for (int k = 0; k < seriesRendererDetails.segments.length; k++) { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - if ((currentSegmentProperties! - .currentPoint!.overallDataPointIndex == - pointIndex) && - currentSegmentProperties!.seriesIndex == seriesIndex) { - selectedSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - break; - } - } - } - - /// To identify that tapped segment in any one of the selected segment. - if (selectedSegmentProperties != null) { - for (int k = 0; k < selectedSegments.length; k++) { - if ((selectedSegmentProperties!.segment.currentSegmentIndex == - selectedSegments[k].currentSegmentIndex || - selectedSegmentProperties! - .currentPoint!.overallDataPointIndex == - SegmentHelper.getSegmentProperties( - selectedSegments[k]) - .currentPoint! - .overallDataPointIndex) && - selectedSegmentProperties!.seriesIndex == - SegmentHelper.getSegmentProperties(selectedSegments[k]) - .seriesIndex) { - multiSelect = true; - break; - } - } - } - - /// Executes when tapped again in one of the selected segments. - if (multiSelect) { - for (int j = selectedSegments.length - 1; j >= 0; j--) { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[ - selectedSegmentProperties!.seriesIndex]); - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[selectedSegments[j].currentSegmentIndex]); - - /// Applying default settings when last selected segment becomes unselected. - if (((selectedSegmentProperties!.segment.currentSegmentIndex == - selectedSegments[j].currentSegmentIndex || - selectedSegmentProperties! - .currentPoint!.overallDataPointIndex == - SegmentHelper.getSegmentProperties( - selectedSegments[j]) - .currentPoint! - .overallDataPointIndex) && - selectedSegmentProperties!.seriesIndex == - SegmentHelper.getSegmentProperties( - selectedSegments[j]) - .seriesIndex) && - (selectedSegments.length == 1)) { - final Paint fillPaint = getDefaultFillColor( - null, null, currentSegmentProperties.segment); - final Paint strokePaint = getDefaultStrokeColor( - null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - - if ((currentSegmentProperties - .currentPoint!.overallDataPointIndex == - pointIndex || - SegmentHelper.getSegmentProperties( - selectedSegments[j]) - .currentPoint! - .overallDataPointIndex == - pointIndex) && - selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - selectedSegments.remove(selectedSegments[j]); - } - - /// Applying unselected color for unselected segments in multiSelect option. - else if ((selectedSegmentProperties! - .currentPoint!.overallDataPointIndex == - SegmentHelper.getSegmentProperties(selectedSegments[j]) - .currentPoint! - .overallDataPointIndex) && - selectedSegmentProperties!.seriesIndex == - selectedSegmentProperties!.seriesIndex) { - final Paint fillPaint = - getFillColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - final Paint strokePaint = - getStrokeColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.strokePaint = strokePaint; - - if ((currentSegmentProperties - .currentPoint!.overallDataPointIndex == - pointIndex || - SegmentHelper.getSegmentProperties( - selectedSegments[j]) - .currentPoint! - .overallDataPointIndex == - pointIndex) && - selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - unselectedSegments!.add(selectedSegments[j]); - selectedSegments.remove(selectedSegments[j]); - } - } - } - } - } else { - unselectedSegments?.clear(); - isSamePointSelect = changeColorAndPopSelectedSegments( - selectedSegments, isSamePointSelect); - } - - /// To check that the selection setting is enable or not. - if (seriesRendererDetails.isSelectionEnable == true) { - if (!isSamePointSelect) { - seriesRendererDetails.seriesType == 'column' || - seriesRendererDetails.seriesType == 'bar' || - seriesRendererDetails.seriesType == 'scatter' || - seriesRendererDetails.seriesType == 'bubble' || - seriesRendererDetails.seriesType - .contains('stackedcolumn') == - true || - seriesRendererDetails.seriesType.contains('stackedbar') == - true || - seriesRendererDetails.seriesType == 'rangecolumn' || - seriesRendererDetails.seriesType == 'waterfall' - ? isSelected = checkPosition() - : isSelected = true; - unselectedSegments?.clear(); - for (int i = - stateProperties.chartSeries.visibleSeriesRenderers.length - - 1; - i >= 0; - i--) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (isSelected) { - for (int j = 0; - j < seriesRendererDetails.segments.length; - j++) { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[j]); - if (currentSegmentProperties!.segment.currentSegmentIndex == - null || - pointIndex == null) { - break; - } - (seriesRendererDetails.seriesType.contains('area') == true - ? currentSegmentProperties! - .segment.currentSegmentIndex == - pointIndex - : currentSegmentProperties! - .currentPoint!.overallDataPointIndex == - pointIndex) && - currentSegmentProperties!.seriesIndex == seriesIndex - ? selectedSegments.add(seriesRendererDetails.segments[j]) - : unselectedSegments! - .add(seriesRendererDetails.segments[j]); - } - - /// Giving color to unselected segments. - unselectedSegmentsColors(unselectedSegments!); - - /// Giving Color to selected segments. - selectedSegmentsColors(selectedSegments); - } - } - } else { - isSelected = true; - } - } - } - } - - /// For series mode. - else if ((selectionType ?? chart.selectionType) == SelectionType.series) { - if (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && - _isSamePointSelected(selectedSegments))) { - bool isSamePointSelect = false; - - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - for (int k = 0; k < seriesRendererDetails.segments.length; k++) { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - final ChartSegment compareSegment = - seriesRendererDetails.segments[k]; - if (currentSegmentProperties!.segment.currentSegmentIndex != - compareSegment.currentSegmentIndex && - currentSegmentProperties!.seriesIndex != - SegmentHelper.getSegmentProperties(compareSegment) - .seriesIndex) { - isSelected = false; - } - } - } - - /// Executes only when final selected segment became unselected. - if (selectedSegments.length == seriesRendererDetails.segments.length) { - changeColorAndPopUnselectedSegments(unselectedSegments!); - } - - /// Executes when multiSelect option is enabled. - bool multiSelect = false; - if (chart.enableMultiSelection == true) { - if (selectedSegments.isNotEmpty) { - selectedSegmentProperties = - SegmentHelper.getSegmentProperties(getTappedSegment()!); - - /// To identify that tapped again in any one of the selected segments. - if (selectedSegmentProperties != null) { - for (int k = 0; k < selectedSegments.length; k++) { - if (seriesIndex == - SegmentHelper.getSegmentProperties(selectedSegments[k]) - .seriesIndex) { - multiSelect = true; - break; - } - } - } - - /// Executes when tapped again in one of the selected segments. - if (multiSelect) { - SegmentProperties currentSegmentProperties; - for (int j = selectedSegments.length - 1; j >= 0; j--) { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[ - selectedSegmentProperties!.seriesIndex]); - - currentSegmentProperties = (seriesRendererDetails.seriesType - .contains('area') == - false && - seriesRendererDetails.seriesType != 'fastline') - ? SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[selectedSegments[j].currentSegmentIndex]) - : SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - - /// Applying series fill when all last selected segment becomes unselected. - if (seriesRendererDetails.seriesType.contains('area') == - false && - seriesRendererDetails.seriesType != 'fastline') { - if ((selectedSegmentProperties!.seriesIndex == - selectedSegmentProperties!.seriesIndex) && - (selectedSegments.length <= - seriesRendererDetails.segments.length)) { - final Paint fillPaint = getDefaultFillColor( - null, null, currentSegmentProperties.segment); - final Paint strokePaint = getDefaultStrokeColor( - null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - if (SegmentHelper.getSegmentProperties(selectedSegments[j]) - .currentPoint! - .overallDataPointIndex == - pointIndex && - selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - selectedSegments.remove(selectedSegments[j]); - } - - /// Applying unselected color for unselected segments in multiSelect option. - else if (selectedSegmentProperties!.seriesIndex == - selectedSegmentProperties!.seriesIndex) { - final Paint fillPaint = - getFillColor(false, currentSegmentProperties.segment); - final Paint strokePaint = - getStrokeColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - if (SegmentHelper.getSegmentProperties(selectedSegments[j]) - .currentPoint! - .overallDataPointIndex == - pointIndex && - selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - unselectedSegments!.add(selectedSegments[j]); - selectedSegments.remove(selectedSegments[j]); - } - } else { - if ((selectedSegmentProperties!.seriesIndex == - selectedSegmentProperties!.seriesIndex) && - (selectedSegments.length <= - seriesRendererDetails.segments.length)) { - final Paint fillPaint = getDefaultFillColor( - null, null, currentSegmentProperties.segment); - final Paint strokePaint = getDefaultStrokeColor( - null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - if (selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - selectedSegments.remove(selectedSegments[j]); - } - - /// Applying unselected color for unselected segments in multiSelect option. - else if (selectedSegmentProperties!.seriesIndex == - selectedSegmentProperties!.seriesIndex) { - final Paint fillPaint = - getFillColor(false, currentSegmentProperties.segment); - final Paint strokePaint = - getStrokeColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - if (selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - unselectedSegments!.add(selectedSegments[j]); - selectedSegments.remove(selectedSegments[j]); - } - } - } - } - } - } else { - /// Executes when multiSelect is not enable. - unselectedSegments?.clear(); - isSamePointSelect = changeColorAndPopSelectedSegments( - selectedSegments, isSamePointSelect); - } - - /// To identify the tapped segment. - if (seriesRendererDetails.isSelectionEnable == true) { - if (!isSamePointSelect) { - seriesRendererDetails.seriesType == 'column' || - seriesRendererDetails.seriesType == 'bar' || - seriesRendererDetails.seriesType == 'scatter' || - seriesRendererDetails.seriesType == 'bubble' || - seriesRendererDetails.seriesType - .contains('stackedcolumn') == - true || - seriesRendererDetails.seriesType.contains('stackedbar') == - true || - seriesRendererDetails.seriesType == 'rangecolumn' || - seriesRendererDetails.seriesType == 'waterfall' - ? isSelected = checkPosition() - : isSelected = true; - selectedSegmentProperties = - SegmentHelper.getSegmentProperties(getTappedSegment()!); - if (isSelected) { - /// To push the selected and unselected segment. - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (seriesRendererDetails.seriesType.contains('area') == - false && - seriesRendererDetails.seriesType != 'fastline') { - if (seriesIndex != null) { - for (int k = 0; - k < seriesRendererDetails.segments.length; - k++) { - currentSegmentProperties = - SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - currentSegmentProperties!.seriesIndex == seriesIndex - ? selectedSegments - .add(seriesRendererDetails.segments[k]) - : unselectedSegments! - .add(seriesRendererDetails.segments[k]); - } - } - } else { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[0]); - currentSegmentProperties!.seriesIndex == seriesIndex - ? selectedSegments.add(seriesRendererDetails.segments[0]) - : unselectedSegments! - .add(seriesRendererDetails.segments[0]); - } - - /// Give color to the unselected segment. - unselectedSegmentsColors(unselectedSegments!); - - /// Give color to the selected segment. - selectedSegmentsColors(selectedSegments); - } - } - } else { - isSelected = true; - } - } - } - } - - /// For cluster mode. - else if ((selectionType ?? chart.selectionType) == SelectionType.cluster) { - if (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && - _isSamePointSelected(selectedSegments))) { - bool isSamePointSelect = false; - - /// Executes only when last selected segment became unselected. - if (selectedSegments.length == - stateProperties.chartSeries.visibleSeriesRenderers.length) { - changeColorAndPopUnselectedSegments(unselectedSegments!); - } - - /// Executes when multiSelect option is enabled. - bool multiSelect = false; - if (chart.enableMultiSelection == true) { - if (selectedSegments.isNotEmpty) { - selectedSegmentProperties = - SegmentHelper.getSegmentProperties(getTappedSegment()!); - - /// To identify that tapped again in any one of the selected segment. - if (selectedSegmentProperties != null) { - for (int k = 0; k < selectedSegments.length; k++) { - if (selectedSegmentProperties!.segment.currentSegmentIndex == - selectedSegments[k].currentSegmentIndex) { - multiSelect = true; - break; - } - } - } - - /// Executes when tapped again in one of the selected segment. - if (multiSelect) { - for (int j = selectedSegments.length - 1; j >= 0; j--) { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[ - selectedSegmentProperties!.seriesIndex]); - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[selectedSegments[j].currentSegmentIndex]); - - /// Applying default settings when last selected segment becomes unselected. - if ((selectedSegmentProperties!.segment.currentSegmentIndex == - selectedSegments[j].currentSegmentIndex) && - (selectedSegments.length <= - stateProperties - .chartSeries.visibleSeriesRenderers.length)) { - final Paint fillPaint = getDefaultFillColor( - null, null, currentSegmentProperties.segment); - final Paint strokePaint = getDefaultStrokeColor( - null, null, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - - if (selectedSegments[j].currentSegmentIndex == pointIndex && - selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - // if(isSamePointSelect == false && ) - selectedSegments.remove(selectedSegments[j]); - } - - /// Applying unselected color for unselected segment in multiSelect option. - else if (selectedSegmentProperties! - .segment.currentSegmentIndex == - selectedSegments[j].currentSegmentIndex) { - final Paint fillPaint = - getFillColor(false, currentSegmentProperties.segment); - final Paint strokePaint = - getStrokeColor(false, currentSegmentProperties.segment); - currentSegmentProperties.segment.fillPaint = fillPaint; - currentSegmentProperties.segment.strokePaint = strokePaint; - - if (selectedSegments[j].currentSegmentIndex == pointIndex && - selectedSegmentProperties!.seriesIndex == seriesIndex) { - isSamePointSelect = true; - } - - unselectedSegments!.add(selectedSegments[j]); - selectedSegments.remove(selectedSegments[j]); - } - } - } - } - } else { - unselectedSegments?.clear(); - - /// Executes when multiSelect is not enable. - isSamePointSelect = changeColorAndPopSelectedSegments( - selectedSegments, isSamePointSelect); - } - - /// To identify the tapped segment - if (seriesRendererDetails.isSelectionEnable == true) { - if (!isSamePointSelect) { - final bool isSegmentSeries = seriesRendererDetails.seriesType == - 'column' || - seriesRendererDetails.seriesType == 'bar' || - seriesRendererDetails.seriesType == 'scatter' || - seriesRendererDetails.seriesType == 'bubble' || - seriesRendererDetails.seriesType.contains('stackedcolumn') == - true || - seriesRendererDetails.seriesType.contains('stackedbar') == - true || - seriesRendererDetails.seriesType == 'rangecolumn' || - seriesRendererDetails.seriesType == 'waterfall'; - selectedSegmentProperties = - SegmentHelper.getSegmentProperties(getTappedSegment()!); - isSegmentSeries ? isSelected = checkPosition() : isSelected = true; - if (isSelected) { - /// To Push the Selected and Unselected segments. - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (currentSegmentProperties!.segment.currentSegmentIndex == - null || - pointIndex == null) { - break; - } - for (int k = 0; - k < seriesRendererDetails.segments.length; - k++) { - currentSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - - if (isSegmentSeries) { - currentSegmentProperties!.currentPoint!.xValue == - selectedSegmentProperties!.currentPoint!.xValue - ? selectedSegments - .add(seriesRendererDetails.segments[k]) - : unselectedSegments! - .add(seriesRendererDetails.segments[k]); - } else { - currentSegmentProperties!.segment.currentSegmentIndex == - selectedSegmentProperties! - .segment.currentSegmentIndex - ? selectedSegments - .add(seriesRendererDetails.segments[k]) - : unselectedSegments! - .add(seriesRendererDetails.segments[k]); - } - } - } - - /// Giving color to unselected segments. - unselectedSegmentsColors(unselectedSegments!); - - /// Giving color to selected segments. - selectedSegmentsColors(selectedSegments); - } - } else { - isSelected = true; - } - } - } - } - return isSelected; - } - - /// To get point index and series index. - void getPointAndSeriesIndex(SfCartesianChart chart, Offset position, - SeriesRendererDetails seriesRendererDetails) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - if (selectionBehaviorRenderer == null) { - return; - } - SegmentProperties currentSegmentProperties; - SegmentProperties? selectedSegmentProperties; - for (int k = 0; k < seriesRendererDetails.segments.length; k++) { - currentSegmentProperties = - SegmentHelper.getSegmentProperties(seriesRendererDetails.segments[k]); - if (currentSegmentProperties.segmentRect!.contains(position) == true) { - selected = true; - selectedSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[k]); - viewportIndex = - selectedSegmentProperties.currentPoint?.visiblePointIndex; - } - } - - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - if (selectedSegmentProperties == null) { - selectionDetails.selectionRenderer!.pointIndex = null; - selectionDetails.selectionRenderer!.seriesIndex = null; - } else { - selectionDetails.selectionRenderer!.pointIndex = - selectedSegmentProperties.currentPoint?.overallDataPointIndex; - selectionDetails.selectionRenderer!.seriesIndex = - selectedSegmentProperties.seriesIndex; - } - } - - /// To check that touch point is lies in segment. - bool isLineIntersect( - CartesianChartPoint segmentStartPoint, - CartesianChartPoint segmentEndPoint, - CartesianChartPoint touchStartPoint, - CartesianChartPoint touchEndPoint) { - final int topPos = - getPointDirection(segmentStartPoint, segmentEndPoint, touchStartPoint); - final int botPos = - getPointDirection(segmentStartPoint, segmentEndPoint, touchEndPoint); - final int leftPos = - getPointDirection(touchStartPoint, touchEndPoint, segmentStartPoint); - final int rightPos = - getPointDirection(touchStartPoint, touchEndPoint, segmentEndPoint); - - return topPos != botPos && leftPos != rightPos; - } - - /// To get the segment points direction. - static int getPointDirection( - CartesianChartPoint point1, - CartesianChartPoint point2, - CartesianChartPoint point3) { - final int value = (((point2.y - point1.y) * (point3.x - point2.x)) - - ((point2.x - point1.x) * (point3.y - point2.y))) - .toInt(); - - if (value == 0) { - return 0; - } - - return (value > 0) ? 1 : 2; - } - - /// To identify that series contains a given point. - bool isSeriesContainsPoint( - SeriesRendererDetails seriesRendererDetails, Offset position) { - int? dataPointIndex; - SegmentProperties? startSegmentProperties; - SegmentProperties? endSegmentProperties; - final List>? nearestDataPoints = - getNearestChartPoints( - position.dx, - position.dy, - seriesRendererDetails.xAxisDetails!.axisRenderer, - seriesRendererDetails.yAxisDetails!.axisRenderer, - seriesRendererDetails); - if (nearestDataPoints == null) { - return false; - } - for (final CartesianChartPoint dataPoint in nearestDataPoints) { - dataPointIndex = seriesRendererDetails - .dataPoints[seriesRendererDetails.dataPoints.indexOf(dataPoint)] - .visiblePointIndex; - } - - if (dataPointIndex != null && - seriesRendererDetails.segments.isNotEmpty == true) { - if (seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { - startSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[dataPointIndex]); - } else { - if (dataPointIndex == 0 && - dataPointIndex < seriesRendererDetails.segments.length) { - startSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[dataPointIndex]); - } else if (dataPointIndex == - seriesRendererDetails.dataPoints.length - 1 && - dataPointIndex - 1 < seriesRendererDetails.segments.length) { - startSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[dataPointIndex - 1]); - } else { - if (dataPointIndex - 1 < seriesRendererDetails.segments.length) { - startSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[dataPointIndex - 1]); - } - - if (dataPointIndex < seriesRendererDetails.segments.length) { - endSegmentProperties = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[dataPointIndex]); - } - } - } - // ignore: unnecessary_null_comparison - if (startSegmentProperties != null) { - cartesianSeriesIndex = startSegmentProperties.seriesIndex; - cartesianPointIndex = - startSegmentProperties.segment.currentSegmentIndex; - if (_isSegmentIntersect( - startSegmentProperties.segment, position.dx, position.dy)) { - return true; - } - } else if (endSegmentProperties != null) { - cartesianSeriesIndex = endSegmentProperties.seriesIndex; - cartesianPointIndex = endSegmentProperties.segment.currentSegmentIndex; - return _isSegmentIntersect( - endSegmentProperties.segment, position.dx, position.dy); - } - } - return false; - } - - /// To identify the cartesian point index. - int? getCartesianPointIndex(Offset position) { - final List> firstNearestDataPoints = - >[]; - int previousIndex, nextIndex; - int? dataPointIndex, - previousDataPointIndex, - nextDataPointIndex, - nearestDataPointIndex; - final List>? nearestDataPoints = - getNearestChartPoints( - position.dx, - position.dy, - seriesRendererDetails.xAxisDetails!.axisRenderer, - seriesRendererDetails.yAxisDetails!.axisRenderer, - seriesRendererDetails); - - for (final CartesianChartPoint dataPoint in nearestDataPoints!) { - dataPointIndex = dataPoint.overallDataPointIndex; - viewportIndex = dataPoint.visiblePointIndex; - previousIndex = seriesRendererDetails.dataPoints.indexOf(dataPoint) - 1; - previousIndex < 0 - ? previousDataPointIndex = dataPointIndex - : previousDataPointIndex = previousIndex; - nextIndex = seriesRendererDetails.dataPoints.indexOf(dataPoint) + 1; - nextIndex > seriesRendererDetails.dataPoints.length - 1 - ? nextDataPointIndex = dataPointIndex - : nextDataPointIndex = nextIndex; - } - - firstNearestDataPoints - .add(seriesRendererDetails.dataPoints[previousDataPointIndex]); - firstNearestDataPoints - .add(seriesRendererDetails.dataPoints[nextDataPointIndex]); - final List>? firstNearestPoints = - getNearestChartPoints( - position.dx, - position.dy, - seriesRendererDetails.xAxisDetails!.axisRenderer, - seriesRendererDetails.yAxisDetails!.axisRenderer, - seriesRendererDetails, - firstNearestDataPoints); - - for (final CartesianChartPoint dataPoint in firstNearestPoints!) { - if (seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { - nearestDataPointIndex = dataPointIndex; - } else { - if (dataPointIndex! < dataPoint.overallDataPointIndex!) { - nearestDataPointIndex = dataPointIndex; - } else if (dataPointIndex == dataPoint.overallDataPointIndex) { - dataPoint.overallDataPointIndex! - 1 < 0 - ? nearestDataPointIndex = dataPoint.overallDataPointIndex - : nearestDataPointIndex = dataPoint.overallDataPointIndex! - 1; - } else { - nearestDataPointIndex = dataPoint.overallDataPointIndex; - } - } - } - - SelectionHelper.getRenderingDetails( - seriesRendererDetails.selectionBehaviorRenderer) - .selectionRenderer! - .cartesianPointIndex = nearestDataPointIndex; - return nearestDataPointIndex; - } - - /// To know the segment is intersect with touch point. - bool _isSegmentIntersect( - ChartSegment segment, double touchX1, double touchY1) { - dynamic currentSegment; - num x1, x2, y1, y2; - if (segment is LineSegment || - segment is SplineSegment || - segment is StepLineSegment || - segment is StackedLineSegment || - segment is HiloSegment || - segment is HiloOpenCloseSegment || - segment is CandleSegment || - segment is BoxAndWhiskerSegment || - segment is StackedLine100Segment) { - currentSegment = segment; - currentSegmentProperties = - SegmentHelper.getSegmentProperties(currentSegment); - } - x1 = currentSegment is HiloSegment || - currentSegment is HiloOpenCloseSegment || - currentSegment is CandleSegment || - currentSegment is BoxAndWhiskerSegment - ? currentSegmentProperties!.x - : currentSegmentProperties!.x1; - if (currentSegment is HiloSegment || - currentSegment is HiloOpenCloseSegment || - currentSegment is CandleSegment || - currentSegment is BoxAndWhiskerSegment) { - x2 = currentSegmentProperties!.x; - if (currentSegment is BoxAndWhiskerSegment) { - y1 = currentSegmentProperties!.min; - y2 = currentSegmentProperties!.max; - } else { - y1 = currentSegmentProperties!.low; - y2 = currentSegmentProperties!.high; - } - } else { - y1 = currentSegment is StackedLineSegment || - currentSegment is StackedLine100Segment - ? currentSegmentProperties!.currentCummulativeValue - : currentSegmentProperties!.y1; - x2 = currentSegmentProperties!.x2; - y2 = currentSegment is StackedLineSegment || - currentSegment is StackedLine100Segment - ? currentSegmentProperties!.nextCummulativeValue - : currentSegmentProperties!.y2; - } - - final CartesianChartPoint leftPoint = - CartesianChartPoint(touchX1 - 20, touchY1 - 20); - final CartesianChartPoint rightPoint = - CartesianChartPoint(touchX1 + 20, touchY1 + 20); - final CartesianChartPoint topPoint = - CartesianChartPoint(touchX1 + 20, touchY1 - 20); - final CartesianChartPoint bottomPoint = - CartesianChartPoint(touchX1 - 20, touchY1 + 20); - - final CartesianChartPoint startSegment = - CartesianChartPoint(x1, y1); - final CartesianChartPoint endSegment = - CartesianChartPoint(x2, y2); - - if (isLineIntersect(startSegment, endSegment, leftPoint, rightPoint) || - isLineIntersect(startSegment, endSegment, topPoint, bottomPoint)) { - return true; - } - - if (seriesRendererDetails.seriesType == 'stepline') { - final num x3 = currentSegmentProperties!.x3; - final num y3 = currentSegmentProperties!.y3; - final num x2 = currentSegmentProperties!.x2; - final num y2 = currentSegmentProperties!.y2; - final CartesianChartPoint endSegment = - CartesianChartPoint(x2, y2); - final CartesianChartPoint midSegment = - CartesianChartPoint(x3, y3); - if (isLineIntersect(endSegment, midSegment, leftPoint, rightPoint) || - isLineIntersect(endSegment, midSegment, topPoint, bottomPoint)) { - return true; - } - } - return false; - } - - /// To get the index of the selected segment. - void getSelectedSeriesIndex(SfCartesianChart chart, Offset position, - SeriesRendererDetails seriesRendererDetails) { - Rect? currentSegment; - int? seriesIndex; - SelectionBehaviorRenderer? selectionBehaviorRenderer; - CartesianChartPoint point; - outerLoop: - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer!; - for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { - point = seriesRendererDetails.dataPoints[j]; - currentSegment = point.region; - if (currentSegment != null && currentSegment.contains(position)) { - seriesIndex = i; - break outerLoop; - } - } - } - if (selectionBehaviorRenderer == null) { - return; - } - - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer) - .selectionRenderer! - .seriesIndex = seriesIndex; - } - - /// To do selection for cartesian type chart. - void performSelection(Offset position) { - bool select = false; - bool isSelect = false; - isInteraction = true; - int? cartesianPointIndex; - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails( - seriesRendererDetails.selectionBehaviorRenderer); - if (seriesRendererDetails.seriesType == 'line' || - seriesRendererDetails.seriesType == 'spline' || - seriesRendererDetails.seriesType == 'stepline' || - seriesRendererDetails.seriesType == 'stackedline' || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true || - seriesRendererDetails.seriesType == 'stackedline100') { - isSelect = seriesRendererDetails.isSelectionEnable == true && - isSeriesContainsPoint(seriesRendererDetails, position); - if (isSelect) { - cartesianPointIndex = getCartesianPointIndex(position); - selected = cartesianPointIndex != null; - select = selectionDetails.selectionRenderer!.isCartesianSelection( - chart, - seriesRendererDetails.renderer, - cartesianPointIndex, - cartesianSeriesIndex); - } - } else { - stateProperties.renderDatalabelRegions = []; - (seriesRendererDetails.seriesType.contains('area') == true || - seriesRendererDetails.seriesType == 'fastline') - ? getSelectedSeriesIndex(chart, position, seriesRendererDetails) - : getPointAndSeriesIndex(chart, position, seriesRendererDetails); - - select = selectionDetails.selectionRenderer!.isCartesianSelection( - chart, seriesRendererDetails.renderer, pointIndex, seriesIndex); - } - - if (select) { - for (final CartesianSeriesRenderer _seriesRenderer - in stateProperties.chartSeries.visibleSeriesRenderers) { - ValueNotifier( - SeriesHelper.getSeriesRendererDetails(_seriesRenderer) - .repaintNotifier - .value++); - } - } - } - - /// To check whether the segments are selected or not. - // ignore: unused_element - void checkWithSelectionState( - ChartSegment currentSegment, SfCartesianChart chart) { - bool isSelected = false; - final SegmentProperties currentSegmentProperties = - SegmentHelper.getSegmentProperties(currentSegment); - if (selectedSegments.isNotEmpty) { - for (int i = 0; i < selectedSegments.length; i++) { - if (SegmentHelper.getSegmentProperties(selectedSegments[i]) - .seriesIndex == - currentSegmentProperties.seriesIndex && - (isInteraction || currentSegmentProperties.oldSegmentIndex != -1) && - (seriesRendererDetails.seriesType.contains('area') == true - ? selectedSegments[i].currentSegmentIndex == - currentSegment.currentSegmentIndex - : SegmentHelper.getSegmentProperties(selectedSegments[i]) - .currentPoint! - .overallDataPointIndex == - (isInteraction - ? currentSegmentProperties - .currentPoint!.overallDataPointIndex - : (currentSegmentProperties.oldSegmentIndex ?? - currentSegmentProperties - .currentPoint!.overallDataPointIndex)))) { - selectedSegmentsColors([currentSegment]); - isSelected = true; - break; - } - } - } - - if (!isSelected && unselectedSegments!.isNotEmpty) { - for (int i = 0; i < unselectedSegments!.length; i++) { - if (SegmentHelper.getSegmentProperties(unselectedSegments![i]) - .seriesIndex == - currentSegmentProperties.seriesIndex && - (currentSegmentProperties.oldSegmentIndex == -1 || - currentSegmentProperties.oldSegmentIndex != - currentSegment.currentSegmentIndex || - seriesRendererDetails.seriesType.contains('area') == true - ? unselectedSegments![i].currentSegmentIndex == - currentSegment.currentSegmentIndex - : SegmentHelper.getSegmentProperties(unselectedSegments![i]) - .currentPoint - ?.overallDataPointIndex == - currentSegmentProperties - .currentPoint?.overallDataPointIndex)) { - unselectedSegmentsColors([currentSegment]); - isSelected = true; - break; - } - } - } - } - - /// To get the selection event argument values. - SelectionArgs getSelectionEventArgs( - CartesianSeries series, - int seriesIndex, - int pointIndex, - SeriesRendererDetails seriesRenderDetails) { - // ignore: unnecessary_null_comparison - if (series != null) { - selectionArgs = SelectionArgs( - seriesRenderer: seriesRendererDetails.renderer, - seriesIndex: seriesIndex, - viewportPointIndex: pointIndex, - pointIndex: seriesRendererDetails - .visibleDataPoints![pointIndex].overallDataPointIndex!); - } - return selectionArgs!; - } -} 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 deleted file mode 100644 index d37bdb852..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart +++ /dev/null @@ -1,1875 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; - -import '../../common/rendering_details.dart'; -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../axis/axis_panel.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/financial_series_base.dart'; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../common/interactive_tooltip.dart'; -import '../common/renderer.dart'; -import '../utils/helper.dart'; -import 'trackball_painter.dart'; -import 'trackball_template.dart'; - -/// 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 using enable property of [TrackballBehavior]. -/// -/// Provides options to customize the [activationMode], [tooltipDisplayMode], [lineType] and [tooltipSettings]. -@immutable -// ignore: must_be_immutable -class TrackballBehavior { - /// Creating an argument constructor of TrackballBehavior class. - TrackballBehavior({ - 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, - this.lineColor, - this.lineWidth = 1, - this.shouldAlwaysShow = false, - this.builder, - this.hideDelay = 0, - }); - - /// Toggles the visibility of the trackball. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior(enable: true); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final bool enable; - - /// Width of the track line. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineWidth: 5 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final double lineWidth; - - /// Color of the track line. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final Color? lineColor; - - /// Dashes of the track line. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineDashArray: [10,10] - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final List? lineDashArray; - - /// Gesture for activating the trackball. - /// - /// Trackball can be activated in tap, double tap and long press. - /// - /// Defaults to `ActivationMode.longPress`. - /// - /// Also refer [ActivationMode]. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// activationMode: ActivationMode.doubleTap - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final ActivationMode activationMode; - - /// Alignment of the trackball tooltip. - /// - /// The trackball tooltip can be aligned at the top, bottom, and center position of the chart. - /// - /// _Note:_ This is applicable only when the `tooltipDisplayMode` property is set to `TrackballDisplayMode.groupAllPoints`. - /// - /// Defaults to `ChartAlignment.center` - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// tooltipDisplayMode: TrackballDisplayMode.groupAllPoints, - /// tooltipAlignment: ChartAlignment.far - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final ChartAlignment tooltipAlignment; - - /// Type of trackball line. By default, vertical line will be displayed. - /// - /// You can change this by specifying values to this property. - /// - /// Defaults to `TrackballLineType.vertical`. - /// - /// Also refer [TrackballLineType] - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineType: TrackballLineType.horizontal - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final TrackballLineType lineType; - - /// Display mode of tooltip. - /// - /// By default, tooltip of all the series under the current point index value will be shown. - /// - /// Defaults to `TrackballDisplayMode.floatAllPoints`. - /// - /// Also refer [TrackballDisplayMode]. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// tooltipDisplayMode: TrackballDisplayMode.groupAllPoints - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final TrackballDisplayMode tooltipDisplayMode; - - /// Shows or hides the trackball. - /// - /// By default, the trackball will be hidden on touch. To avoid this, set this property to true. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// shouldAlwaysShow: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final bool shouldAlwaysShow; - - /// Customizes the trackball tooltip. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// canShowMarker: false - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - InteractiveTooltip tooltipSettings; - - /// Giving disappear delay for trackball. - /// - /// Defaults to `0`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// hideDelay: 2000 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior, - /// ); - /// } - /// ``` - final double hideDelay; - - /// Builder of the trackball tooltip. - /// - /// Add any custom widget as the trackball template. - /// - /// If the trackball display mode is `groupAllPoints` or `nearestPoint` it will called once and if it is - /// `floatAllPoints`, it will be called for each point. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// builder: (BuildContext context, TrackballDetails trackballDetails) { - /// return Container( - /// width: 70, - /// decoration: - /// const BoxDecoration(color: Color.fromRGBO(66, 244, 164, 1)), - /// child: Text('${trackballDetails.point?.cumulativeValue}') - /// ); - /// } - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final ChartTrackballBuilder? builder; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TrackballBehavior && - other.activationMode == activationMode && - other.lineType == lineType && - other.tooltipDisplayMode == tooltipDisplayMode && - other.tooltipAlignment == tooltipAlignment && - other.tooltipSettings == tooltipSettings && - other.lineDashArray == lineDashArray && - other.markerSettings == markerSettings && - other.enable == enable && - other.lineColor == lineColor && - other.lineWidth == lineWidth && - other.shouldAlwaysShow == shouldAlwaysShow && - other.builder == builder && - other.hideDelay == hideDelay; - } - - @override - int get hashCode { - final List values = [ - activationMode, - lineType, - tooltipDisplayMode, - tooltipAlignment, - tooltipSettings, - markerSettings, - lineDashArray, - enable, - lineColor, - lineWidth, - shouldAlwaysShow, - builder, - hideDelay - ]; - return hashList(values); - } - - /// Holds the value of cartesian state properties - late CartesianStateProperties _stateProperties; - - ///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 - /// in [TrackballMarkerSettings]. - /// - ///Provides the options like color, border width, border color and shape of the - /// marker to customize the appearance. - final TrackballMarkerSettings? markerSettings; - - /// Displays the trackball at the specified x and y-positions. - /// - /// *x and y - x & y pixels/values at which the trackball needs to be shown. - /// - /// coordinateUnit - specify the type of x and y values given. - /// - /// `pixel` or `point` for logical pixel and chart data point respectively. - /// - /// Defaults to `point`. - void show(dynamic x, double y, [String coordinateUnit = 'point']) { - final CartesianStateProperties stateProperties = _stateProperties; - final TrackballRenderingDetails _trackballRenderingDetails = - TrackballHelper.getRenderingDetails( - _stateProperties.trackballBehaviorRenderer); - final List visibleSeriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderer[0]); - if (_trackballRenderingDetails.trackballPainter != null || - builder != null) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - if (coordinateUnit != 'pixel') { - final ChartLocation location = calculatePoint( - (x is DateTime && (xAxisDetails is! DateTimeCategoryAxisDetails)) - ? x.millisecondsSinceEpoch - : ((x is DateTime && - xAxisDetails is DateTimeCategoryAxisDetails) - ? xAxisDetails.labels - .indexOf(xAxisDetails.dateFormat.format(x)) - : ((x is String && xAxisDetails is CategoryAxisDetails) - ? xAxisDetails.labels.indexOf(x) - : x)), - y, - xAxisDetails, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - seriesRendererDetails.stateProperties.chartAxis.axisClipRect); - x = location.x; - y = location.y; - } - if (seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { - if (_trackballRenderingDetails.trackballPainter != null) { - _trackballRenderingDetails.isTrackballTemplate = false; - _trackballRenderingDetails.generateAllPoints(Offset(x.toDouble(), y)); - } else if (builder != null && (!_trackballRenderingDetails.isMoving)) { - _trackballRenderingDetails - .showTemplateTrackball(Offset(x.toDouble(), y)); - } - } - } - - if (_trackballRenderingDetails.trackballPainter != null) { - _trackballRenderingDetails.trackballPainter!.canResetPath = false; - stateProperties.repaintNotifiers['trackball']!.value++; - } - } - - /// Displays the trackball at the specified point index. - /// - /// * pointIndex - index of the point for which the trackball must be shown - void showByIndex(int pointIndex) { - final TrackballRenderingDetails _trackballRenderingDetails = - TrackballHelper.getRenderingDetails( - _stateProperties.trackballBehaviorRenderer); - _trackballRenderingDetails.internalShowByIndex( - pointIndex, - ); - } - - /// Hides the trackball if it is displayed. - void hide() { - final CartesianStateProperties stateProperties = _stateProperties; - final TrackballRenderingDetails _trackballRenderingDetails = - TrackballHelper.getRenderingDetails( - _stateProperties.trackballBehaviorRenderer); - if (_trackballRenderingDetails.trackballPainter != null && - !_trackballRenderingDetails.isTrackballTemplate) { - if (stateProperties.chart.trackballBehavior.activationMode == - ActivationMode.doubleTap) { - _trackballRenderingDetails.trackballPainter!.canResetPath = false; - ValueNotifier(_trackballRenderingDetails.trackballPainter! - .stateProperties.repaintNotifiers['trackball']!.value++); - if (_trackballRenderingDetails.trackballPainter!.timer != null) { - _trackballRenderingDetails.trackballPainter!.timer?.cancel(); - } - } - if (!stateProperties.isTouchUp) { - _trackballRenderingDetails.trackballPainter!.stateProperties - .repaintNotifiers['trackball']!.value++; - _trackballRenderingDetails.chartPointInfo.clear(); - _trackballRenderingDetails.trackballPainter!.canResetPath = true; - } else { - final double duration = - (hideDelay == 0 && stateProperties.enableDoubleTap) - ? 200 - : hideDelay; - if (!shouldAlwaysShow) { - if (_trackballRenderingDetails.trackballPainter!.timer != null) { - _trackballRenderingDetails.trackballPainter!.timer!.cancel(); - } - _trackballRenderingDetails.trackballPainter!.timer = - Timer(Duration(milliseconds: duration.toInt()), () { - _trackballRenderingDetails.trackballPainter!.stateProperties - .repaintNotifiers['trackball']!.value++; - _trackballRenderingDetails.trackballPainter!.canResetPath = true; - _trackballRenderingDetails.chartPointInfo.clear(); - }); - } - } - } else if (_trackballRenderingDetails.trackballTemplate != null) { - GlobalKey key = - _trackballRenderingDetails.trackballTemplate!.key as GlobalKey; - TrackballTemplateState? trackballTemplateState = - key.currentState as TrackballTemplateState; - final double duration = shouldAlwaysShow || - (hideDelay == 0 && stateProperties.enableDoubleTap) - ? 200 - : hideDelay; - if (!stateProperties.isTrackballOrientationChanged) { - stateProperties.trackballTimer = - Timer(Duration(milliseconds: duration.toInt()), () { - if (stateProperties.isTrackballOrientationChanged) { - key = - _trackballRenderingDetails.trackballTemplate!.key as GlobalKey; - trackballTemplateState = key.currentState as TrackballTemplateState; - } - trackballTemplateState!.hideTrackballTemplate(); - stateProperties.isTrackballOrientationChanged = false; - _trackballRenderingDetails.chartPointInfo.clear(); - }); - } - } - } -} - -///Trackball behavior renderer class for mutable fields and methods -class TrackballBehaviorRenderer with ChartBehavior { - /// Creates an argument constructor for trackball renderer class - TrackballBehaviorRenderer(this._stateProperties) { - _trackballRenderingDetails = TrackballRenderingDetails(_stateProperties); - } - final CartesianStateProperties _stateProperties; - - /// Specifies the value of trackball rendering details - late TrackballRenderingDetails _trackballRenderingDetails; - - /// Performs the double-tap action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the long press action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the touch-down action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the touch-move action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the touch-up action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.hide(); - - /// Performs the mouse-hover action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the mouse-exit action. - /// - /// 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) => - _trackballRenderingDetails.trackballBehavior.hide(); - - /// Draws trackball - /// - /// * canvas - Canvas used to draw the track line on the chart. - @override - void onPaint(Canvas canvas) { - if (_trackballRenderingDetails.trackballPainter != null && - !_trackballRenderingDetails.trackballPainter!.canResetPath) { - _trackballRenderingDetails.trackballPainter!.drawTrackball(canvas); - } - } -} - -/// Represents the class that holds the rendering details of trackball -class TrackballRenderingDetails { - /// Creates an instance of trackball rendering details - TrackballRenderingDetails(this._stateProperties); - final CartesianStateProperties _stateProperties; - SfCartesianChart get _chart => _stateProperties.chart; - - /// Specifies the trackball behavior - TrackballBehavior get trackballBehavior => _chart.trackballBehavior; - - /// Check whether long press activated or not. - // ignore: prefer_final_fields - bool isLongPressActivated = false; - - /// Check whether onPointerMove or not. - // ignore: prefer_final_fields - bool isMoving = false; - - /// Touch position - late Offset tapPosition; - - /// Holds the instance of trackball painter. - TrackballPainter? trackballPainter; - - /// Specifies the trackball template - TrackballTemplate? trackballTemplate; - List _visibleLocation = []; - - /// Specifies the marker shape - final List markerShapes = []; - late Rect _axisClipRect; - - //ignore: unused_field - late double _xPos; - //ignore: unused_field - late double _yPos; - List> _points = >[]; - List _currentPointIndices = []; - List _visibleSeriesIndices = []; - List> _visibleSeriesList = - >[]; - late TrackballGroupingModeInfo _groupingModeInfo; - - /// Specifies the chart point info - List chartPointInfo = []; - - /// Specifies the tooltip top - List tooltipTop = []; - - /// Specifies the tooltip bottom - List tooltipBottom = []; - - /// Specifies the xAxesInfo - final List xAxesInfo = []; - - /// Specifies the yAxesInfo - final List yAxesInfo = []; - - /// Specifies the visible points - List visiblePoints = []; - - /// Specifies the tooltip position - TooltipPositions? tooltipPosition; - - late num _tooltipPadding; - - /// Specifies whether it is range series - bool isRangeSeries = false; - - /// Specifies whether it is box series - bool isBoxSeries = false; - - /// Specifies whether the trackball has template - bool isTrackballTemplate = false; - - /// To render the trackball marker for both tooltip and template - void trackballMarker(int index) { - if (trackballBehavior.markerSettings != null && - (trackballBehavior.markerSettings!.markerVisibility == - TrackballVisibilityMode.auto - ? (chartPointInfo[index] - .seriesRendererDetails! - .series - .markerSettings - .isVisible == - true) - : trackballBehavior.markerSettings!.markerVisibility == - TrackballVisibilityMode.visible)) { - final MarkerSettings markerSettings = trackballBehavior.markerSettings!; - final DataMarkerType markerType = markerSettings.shape; - final Size size = Size(markerSettings.width, markerSettings.height); - final String seriesType = - chartPointInfo[index].seriesRendererDetails!.seriesType; - markerShapes.add(getMarkerShapesPath( - markerType, - Offset( - chartPointInfo[index].xPosition!, - seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType == 'candle' - ? chartPointInfo[index].highYPosition! - : seriesType == 'boxandwhisker' - ? chartPointInfo[index].maxYPosition! - : chartPointInfo[index].yPosition!), - size, - chartPointInfo[index].seriesRendererDetails!, - null, - trackballBehavior)); - } - } - - /// To show trackball based on point index - void showTrackball(List visibleSeriesRenderers, - int pointIndex, TrackballBehaviorRenderer trackballBehaviorRenderer) { - ChartLocation position; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderers[0]); - final Rect rect = - seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - final List> dataPoints = - >[]; - for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { - if (seriesRendererDetails.dataPoints[i].isGap != true) { - dataPoints.add(seriesRendererDetails.dataPoints[i]); - } - } - // ignore: unnecessary_null_comparison - assert(pointIndex != null, 'Point index must not be null'); -// ignore: unnecessary_null_comparison - if (pointIndex != null && - pointIndex.abs() < seriesRendererDetails.dataPoints.length) { - final int index = pointIndex; - final num xValue = seriesRendererDetails.dataPoints[index].xValue; - final num yValue = seriesRendererDetails.series - is FinancialSeriesBase || - seriesRendererDetails.seriesType.contains('range') == true - ? seriesRendererDetails.dataPoints[index].high - : seriesRendererDetails.dataPoints[index].yValue; - position = calculatePoint( - xValue, - yValue, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - seriesRendererDetails.stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - rect); - if (trackballPainter != null) { - generateAllPoints(Offset(position.x, position.y)); - } else if (trackballBehavior.builder != null) { - showTemplateTrackball(Offset(position.x, position.y)); - } - } - } - - /// Method to show the trackball with template - void showTemplateTrackball(Offset position) { - 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; - isTrackballTemplate = true; - generateAllPoints(position); - CartesianChartPoint dataPoint; - for (int index = 0; index < chartPointInfo.length; index++) { - dataPoint = chartPointInfo[index] - .seriesRendererDetails! - .dataPoints[chartPointInfo[index].dataPointIndex!]; - if (trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { - _points.add(dataPoint); - _currentPointIndices.add(chartPointInfo[index].dataPointIndex!); - _visibleSeriesIndices.add(_stateProperties - .chartSeries.visibleSeriesRenderers - .indexOf(chartPointInfo[index].seriesRendererDetails!.renderer)); - _visibleSeriesList - .add(chartPointInfo[index].seriesRendererDetails!.series); - } - trackballMarker(index); - } - _groupingModeInfo = TrackballGroupingModeInfo(_points, _currentPointIndices, - _visibleSeriesIndices, _visibleSeriesList); - assert(trackballTemplateState.mounted, - 'Template state which must be mounted before accessing to avoid rebuilding'); - if (trackballTemplateState.mounted && - trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { - trackballTemplateState.chartPointInfo = chartPointInfo; - trackballTemplateState.groupingModeInfo = _groupingModeInfo; - trackballTemplateState.markerShapes = markerShapes; - trackballTemplateState.refresh(); - } else if (trackballTemplateState.mounted) { - trackballTemplateState.chartPointInfo = chartPointInfo; - trackballTemplateState.markerShapes = markerShapes; - trackballTemplateState.refresh(); - } - _points = >[]; - _currentPointIndices = []; - _visibleSeriesIndices = []; - _visibleSeriesList = >[]; - tooltipTop.clear(); - tooltipBottom.clear(); - } - - /// Calculate trackball points - void generateAllPoints(Offset position) { - _axisClipRect = _stateProperties.chartAxis.axisClipRect; - _tooltipPadding = _stateProperties.requireInvertedAxis ? 8 : 5; - chartPointInfo = []; - visiblePoints = []; - markerShapes.clear(); - tooltipTop = tooltipBottom = _visibleLocation = []; - trackballPainter?.tooltipTop = []; - trackballPainter?.tooltipBottom = []; - final Rect seriesBounds = _axisClipRect; - 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; - late SeriesRendererDetails cartesianSeriesRendererDetails; - ChartAxisRendererDetails chartAxisDetails, xAxisDetails, yAxisDetails; - CartesianChartPoint chartDataPoint; - ChartAxisPanel chartAxis; - String seriesType, labelValue, seriesName; - bool invertedAxis = _stateProperties.requireInvertedAxis; - CartesianSeries series; - num? xValue, - yValue, - minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - meanValue, - highValue, - lowValue, - openValue, - closeValue, - bubbleSize, - cumulativeValue; - Rect axisClipRect; - final TrackballDisplayMode tooltipDisplayMode = - _stateProperties.chart.trackballBehavior.tooltipDisplayMode; - ChartLocation highLocation, maxLocation; - chartAxisDetails = SeriesHelper.getSeriesRendererDetails( - _stateProperties.seriesRenderers[0]) - .xAxisDetails!; - for (final CartesianSeriesRenderer axisSeriesRenderer - in chartAxisDetails.seriesRenderers) { - cartesianSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(axisSeriesRenderer); - seriesType = cartesianSeriesRendererDetails.seriesType; - isRangeSeries = seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType == 'candle'; - isBoxSeries = seriesType == 'boxandwhisker'; - if (cartesianSeriesRendererDetails.visible == false || - (cartesianSeriesRendererDetails.dataPoints.isEmpty == true && - cartesianSeriesRendererDetails.isRectSeries == false)) { - continue; - } - if (cartesianSeriesRendererDetails.dataPoints.isNotEmpty == true) { - final List>? nearestDataPoints = - getNearestChartPoints( - position.dx, - position.dy, - cartesianSeriesRendererDetails.xAxisDetails!.axisRenderer, - cartesianSeriesRendererDetails.yAxisDetails!.axisRenderer, - cartesianSeriesRendererDetails); - for (final CartesianChartPoint dataPoint - in nearestDataPoints!) { - index = cartesianSeriesRendererDetails.dataPoints.indexOf(dataPoint); - chartDataPoint = cartesianSeriesRendererDetails.dataPoints[index]; - xAxisDetails = cartesianSeriesRendererDetails.xAxisDetails!; - yAxisDetails = cartesianSeriesRendererDetails.yAxisDetails!; - chartAxis = cartesianSeriesRendererDetails.stateProperties.chartAxis; - invertedAxis = _stateProperties.requireInvertedAxis; - series = cartesianSeriesRendererDetails.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 = cartesianSeriesRendererDetails.series.name ?? - 'Series $seriesIndex'; - bubbleSize = chartDataPoint.bubbleSize; - cumulativeValue = chartDataPoint.cumulativeValue; - axisClipRect = calculatePlotOffset( - chartAxis.axisClipRect, - Offset( - xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - cummulativePos = calculatePoint( - xValue!, - cumulativeValue, - xAxisDetails, - yAxisDetails, - invertedAxis, - series, - axisClipRect) - .y; - xPos = calculatePoint( - xValue, - seriesType.contains('stacked') ? cumulativeValue : yValue, - xAxisDetails, - yAxisDetails, - invertedAxis, - series, - axisClipRect) - .x; - if (!xPos.toDouble().isNaN) { - if (seriesIndex == 0 || - ((leastX! - position.dx).abs() > (xPos - position.dx).abs())) { - leastX = xPos; - } - labelValue = _getTrackballLabelText( - cartesianSeriesRendererDetails, - xValue, - yValue, - lowValue, - highValue, - openValue, - closeValue, - minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - meanValue, - seriesName, - bubbleSize, - cumulativeValue, - dataPoint); - yPos = seriesType.contains('stacked') - ? cummulativePos - : calculatePoint(xValue, yValue, xAxisDetails, yAxisDetails, - invertedAxis, series, axisClipRect) - .y; - if (isRangeSeries) { - lowYPos = calculatePoint(xValue, lowValue, xAxisDetails, - yAxisDetails, invertedAxis, series, axisClipRect) - .y; - highLocation = calculatePoint(xValue, highValue, xAxisDetails, - yAxisDetails, 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, xAxisDetails, - yAxisDetails, invertedAxis, series, axisClipRect) - .y; - maxLocation = calculatePoint(xValue, maximumValue, xAxisDetails, - yAxisDetails, 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, - isRangeSeries - ? highYPos! - : isBoxSeries - ? maxYPos! - : yPos)) || - seriesBounds.overlaps(rect)) { - visiblePoints.add(ClosestPoints( - closestPointX: !isRangeSeries - ? xPos - : isBoxSeries - ? maxXPos! - : highXPos!, - closestPointY: isRangeSeries - ? highYPos! - : isBoxSeries - ? maxYPos! - : yPos)); - _addChartPointInfo( - cartesianSeriesRendererDetails, - 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!, cartesianSeriesRendererDetails, position.dx, position.dy); - _validatePointsWithSeries(leastX); - } - 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 - ? chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => - a.xPosition!.compareTo(b.xPosition!)) - : tooltipDisplayMode == TrackballDisplayMode.floatAllPoints - ? chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => - a.yPosition!.compareTo(b.yPosition!)) - : chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => - b.yPosition!.compareTo(a.yPosition!)); - } - if (tooltipDisplayMode == TrackballDisplayMode.nearestPoint || - (cartesianSeriesRendererDetails.isRectSeries == true && - tooltipDisplayMode != TrackballDisplayMode.groupAllPoints)) { - _validateNearestPointForAllSeries( - leastX!, chartPointInfo, position.dx, position.dy); - } - } - _triggerTrackballRenderCallback(); - } - - /// Event for trackball render - void _triggerTrackballRenderCallback() { - if (_chart.onTrackballPositionChanging != null) { - _stateProperties.chartPointInfo = chartPointInfo; - int index; - for (index = _stateProperties.chartPointInfo.length - 1; - index >= 0; - index--) { - TrackballArgs chartPoint; - chartPoint = TrackballArgs(); - chartPoint.chartPointInfo = _stateProperties.chartPointInfo[index]; - _chart.onTrackballPositionChanging!(chartPoint); - _stateProperties.chartPointInfo[index].label = - chartPoint.chartPointInfo.label; - _stateProperties.chartPointInfo[index].header = - chartPoint.chartPointInfo.header; - if (!isTrackballTemplate && - _stateProperties.chartPointInfo[index].label == null || - _stateProperties.chartPointInfo[index].label == '') { - _stateProperties.chartPointInfo.removeAt(index); - visiblePoints.removeAt(index); - } - } - } - } - - /// To validate the nearest point in all series for trackball - void _validateNearestPointForAllSeries(double leastX, - List trackballInfo, double touchXPos, double touchYPos) { - double xPos = 0, yPos; - final List tempTrackballInfo = - List.from(trackballInfo); - ChartPointInfo pointInfo; - num? yValue; - num xValue; - Rect axisClipRect; - CartesianChartPoint dataPoint; - ChartAxisRendererDetails xAxisDetails, yAxisDetails; - int i; - for (i = 0; i < tempTrackballInfo.length; i++) { - pointInfo = tempTrackballInfo[i]; - dataPoint = pointInfo - .seriesRendererDetails!.dataPoints[pointInfo.dataPointIndex!]; - xAxisDetails = pointInfo.seriesRendererDetails!.xAxisDetails!; - yAxisDetails = pointInfo.seriesRendererDetails!.yAxisDetails!; - xValue = dataPoint.xValue; - if (pointInfo.seriesRendererDetails!.seriesType != 'boxandwhisker') { - yValue = dataPoint.yValue; - } - axisClipRect = calculatePlotOffset( - pointInfo - .seriesRendererDetails!.stateProperties.chartAxis.axisClipRect, - Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); - final ChartLocation chartPointOffset = calculatePoint( - xValue, - yValue, - xAxisDetails, - yAxisDetails, - _stateProperties.requireInvertedAxis, - pointInfo.seriesRendererDetails!.series, - axisClipRect); - - xPos = chartPointOffset.x; - yPos = chartPointOffset.y; - if (_stateProperties.chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.floatAllPoints) { - final bool isTransposed = pointInfo - .seriesRendererDetails!.stateProperties.requireInvertedAxis; - if (leastX != xPos && !isTransposed) { - trackballInfo.remove(pointInfo); - } - yPos = touchYPos; - xPos = touchXPos; - if (_stateProperties.chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.floatAllPoints) { - ChartPointInfo point = trackballInfo[0]; - for (i = 1; i < trackballInfo.length; i++) { - final bool isXYPositioned = !isTransposed - ? (((point.yPosition! - yPos).abs() > - (trackballInfo[i].yPosition! - yPos).abs()) && - point.xPosition == trackballInfo[i].xPosition) - : (((point.xPosition! - xPos).abs() > - (trackballInfo[i].xPosition! - xPos).abs()) && - point.yPosition == trackballInfo[i].yPosition); - if (isXYPositioned) { - point = trackballInfo[i]; - } - } - trackballInfo - ..clear() - ..add(point); - } - } - } - } - - /// To find the nearest x value to render a trackball - void _validateNearestXValue( - double leastX, - SeriesRendererDetails seriesRendererDetails, - double touchXPos, - double touchYPos) { - final List leastPointInfo = []; - final Rect axisClipRect = calculatePlotOffset( - seriesRendererDetails.stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final bool invertedAxis = - seriesRendererDetails.stateProperties.requireInvertedAxis; - double nearPointX = invertedAxis ? axisClipRect.top : axisClipRect.left; - final double touchXValue = invertedAxis ? touchYPos : touchXPos; - double delta = 0, currX; - num xValue; - num? yValue; - CartesianChartPoint dataPoint; - CartesianSeries series; - ChartAxisRendererDetails xAxisDetails, yAxisDetails; - ChartLocation currXLocation; - for (final ChartPointInfo pointInfo in chartPointInfo) { - if (pointInfo.dataPointIndex! < seriesRendererDetails.dataPoints.length) { - dataPoint = seriesRendererDetails.dataPoints[pointInfo.dataPointIndex!]; - xAxisDetails = pointInfo.seriesRendererDetails!.xAxisDetails!; - yAxisDetails = pointInfo.seriesRendererDetails!.yAxisDetails!; - xValue = dataPoint.xValue; - if (seriesRendererDetails.seriesType != 'boxandwhisker') { - yValue = dataPoint.yValue; - } - series = pointInfo.seriesRendererDetails!.series; - currXLocation = calculatePoint(xValue, yValue, xAxisDetails, - yAxisDetails, invertedAxis, series, axisClipRect); - currX = invertedAxis ? currXLocation.y : currXLocation.x; - - if (delta == touchXValue - currX) { - leastPointInfo.add(pointInfo); - } else if ((touchXValue - currX).toDouble().abs() <= - (touchXValue - nearPointX).toDouble().abs()) { - nearPointX = currX; - delta = touchXValue - currX; - leastPointInfo.clear(); - leastPointInfo.add(pointInfo); - } - } - if (chartPointInfo.isNotEmpty) { - if (chartPointInfo[0].dataPointIndex! < - seriesRendererDetails.dataPoints.length) { - leastX = _getLeastX( - chartPointInfo[0], seriesRendererDetails, axisClipRect); - } - } - - if (pointInfo.seriesRendererDetails!.seriesType.contains('bar') == true - ? invertedAxis - : invertedAxis) { - _yPos = leastX; - } else { - _xPos = leastX; - } - } - } - - void _validatePointsWithSeries(double leastX) { - final List xValueList = []; - for (final ChartPointInfo pointInfo in chartPointInfo) { - xValueList.add(pointInfo.chartDataPoint?.xValue); - } - if (xValueList.isNotEmpty) { - for (int count = 0; count < xValueList.length; count++) { - if (xValueList[0] != xValueList[count]) { - final List leastPointInfo = []; - for (final ChartPointInfo pointInfo in chartPointInfo) { - if (pointInfo.xPosition == leastX) { - leastPointInfo.add(pointInfo); - visiblePoints.clear(); - visiblePoints.add(ClosestPoints( - closestPointX: !isRangeSeries - ? pointInfo.xPosition! - : isBoxSeries - ? pointInfo.maxXPosition! - : pointInfo.highXPosition!, - closestPointY: isRangeSeries - ? pointInfo.highYPosition! - : isBoxSeries - ? pointInfo.maxYPosition! - : pointInfo.yPosition!)); - } - } - chartPointInfo.clear(); - chartPointInfo = leastPointInfo; - } - } - } - } - - /// To get the lowest x value to render trackball - double _getLeastX(ChartPointInfo pointInfo, - SeriesRendererDetails seriesRendererDetails, Rect axisClipRect) { - return calculatePoint( - seriesRendererDetails.dataPoints[pointInfo.dataPointIndex!].xValue, - 0, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - _stateProperties.requireInvertedAxis, - seriesRendererDetails.series, - axisClipRect) - .x; - } - - /// To render the trackball marker - void renderTrackballMarker(SeriesRendererDetails seriesRendererDetails, - Canvas canvas, TrackballBehavior trackballBehavior, int index) { - final CartesianChartPoint point = trackballBehavior - ._stateProperties - .trackballBehaviorRenderer - ._trackballRenderingDetails - .chartPointInfo[index] - .chartDataPoint!; - final TrackballMarkerSettings markerSettings = - trackballBehavior.markerSettings!; - final RenderingDetails renderingDetails = - seriesRendererDetails.stateProperties.renderingDetails; - if (markerSettings.shape == DataMarkerType.image) { - drawImageMarker(null, canvas, chartPointInfo[index].markerXPos!, - chartPointInfo[index].markerYPos!, markerSettings, _stateProperties); - } - final Paint strokePaint = Paint() - ..color = trackballBehavior.markerSettings!.borderWidth == 0 - ? Colors.transparent - : ((point.pointColorMapper != null) - ? point.pointColorMapper! - : markerSettings.borderColor ?? - seriesRendererDetails.seriesColor!) - ..strokeWidth = markerSettings.borderWidth - ..style = PaintingStyle.stroke; - - final Paint fillPaint = Paint() - ..color = markerSettings.color ?? - (renderingDetails.chartTheme.brightness == Brightness.light - ? Colors.white - : Colors.black) - ..style = PaintingStyle.fill; - canvas.drawPath(markerShapes[index], strokePaint); - canvas.drawPath(markerShapes[index], fillPaint); - } - - /// To add chart point info - void _addChartPointInfo( - SeriesRendererDetails seriesRendererDetails, - double xPos, - 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.seriesRendererDetails = seriesRendererDetails; - pointInfo.series = seriesRendererDetails.series; - pointInfo.markerXPos = xPos; - pointInfo.markerYPos = yPos; - pointInfo.xPosition = xPos; - pointInfo.yPosition = yPos; - pointInfo.seriesIndex = seriesIndex; - - if (seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType == 'candle') { - pointInfo.lowYPosition = lowYPos!; - pointInfo.highXPosition = highXPos!; - pointInfo.highYPosition = highYPos!; - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType == 'candle') { - pointInfo.openXPosition = openXPos!; - pointInfo.openYPosition = openYPos!; - pointInfo.closeXPosition = closeXPos!; - pointInfo.closeYPosition = closeYPos!; - } - } else if (seriesRendererDetails.seriesType.contains('boxandwhisker') == - true) { - pointInfo.minYPosition = minYPos!; - pointInfo.maxYPosition = maxYPos!; - pointInfo.maxXPosition = maxXPos!; - pointInfo.lowerXPosition = lowerXPos!; - pointInfo.lowerYPosition = lowerYPos!; - pointInfo.upperXPosition = upperXPos!; - pointInfo.upperYPosition = upperYPos!; - } - - if (seriesRendererDetails.segments.length > dataPointIndex) { - pointInfo.color = SegmentHelper.getSegmentProperties( - seriesRendererDetails.segments[dataPointIndex]) - .color!; - } else if (seriesRendererDetails.segments.length > 1) { - pointInfo.color = SegmentHelper.getSegmentProperties(seriesRendererDetails - .segments[seriesRendererDetails.segments.length - 1]) - .color!; - } - pointInfo.chartDataPoint = seriesRendererDetails.dataPoints[dataPointIndex]; - pointInfo.dataPointIndex = dataPointIndex; - if (!isTrackballTemplate) { - pointInfo.label = label!; - pointInfo.header = _getHeaderText( - seriesRendererDetails.dataPoints[dataPointIndex], - seriesRendererDetails); - } - chartPointInfo.add(pointInfo); - } - - /// Method to place the collided tooltips properly - TooltipPositions smartTooltipPositions( - List tooltipTop, - List tooltipBottom, - List _xAxesInfo, - List _yAxesInfo, - List chartPointInfo, - bool requireInvertedAxis, - [bool? isPainterTooltip]) { - _tooltipPadding = _stateProperties.requireInvertedAxis ? 8 : 5; - num tooltipWidth = 0; - TooltipPositions tooltipPosition; - for (int i = 0; i < chartPointInfo.length; i++) { - requireInvertedAxis - ? _visibleLocation.add(chartPointInfo[i].xPosition!) - : _visibleLocation.add((chartPointInfo[i] - .seriesRendererDetails! - .seriesType - .contains('range') == - true || - chartPointInfo[i] - .seriesRendererDetails! - .seriesType - .contains('hilo') == - true || - chartPointInfo[i].seriesRendererDetails!.seriesType == - 'candle') - ? chartPointInfo[i].highYPosition! - : chartPointInfo[i].seriesRendererDetails!.seriesType == - 'boxandwhisker' - ? chartPointInfo[i].maxYPosition! - : chartPointInfo[i].yPosition!); - - tooltipWidth += tooltipBottom[i] - tooltipTop[i] + _tooltipPadding; - } - tooltipPosition = _continuousOverlappingPoints( - tooltipTop, tooltipBottom, _visibleLocation); - - if (!requireInvertedAxis - ? tooltipWidth < (_axisClipRect.bottom - _axisClipRect.top) - : tooltipWidth < (_axisClipRect.right - _axisClipRect.left)) { - tooltipPosition = - _verticalArrangements(tooltipPosition, _xAxesInfo, _yAxesInfo); - } - return tooltipPosition; - } - - TooltipPositions _verticalArrangements(TooltipPositions tooltipPPosition, - List _xAxesInfo, List _yAxesInfo) { - final TooltipPositions tooltipPosition = tooltipPPosition; - num? startPos, chartHeight; - final bool isTransposed = _stateProperties.requireInvertedAxis; - num secWidth, width; - final int length = tooltipPosition.tooltipTop.length; - ChartAxisRenderer yAxisRenderer; - final int axesLength = - _stateProperties.chartAxis.axisRenderersCollection.length; - for (int i = length - 1; i >= 0; i--) { - yAxisRenderer = _yAxesInfo[i]; - for (int k = 0; k < axesLength; k++) { - if (yAxisRenderer == - _stateProperties.chartAxis.axisRenderersCollection[k]) { - if (isTransposed) { - chartHeight = _axisClipRect.right; - startPos = _axisClipRect.left; - } else { - chartHeight = _axisClipRect.bottom - _axisClipRect.top; - startPos = _axisClipRect.top; - } - } - } - width = tooltipPosition.tooltipBottom[i] - tooltipPosition.tooltipTop[i]; - if (chartHeight != null && - chartHeight < tooltipPosition.tooltipBottom[i]) { - tooltipPosition.tooltipBottom[i] = chartHeight - 2; - tooltipPosition.tooltipTop[i] = - tooltipPosition.tooltipBottom[i] - width; - for (int j = i - 1; j >= 0; j--) { - secWidth = - tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; - if (tooltipPosition.tooltipBottom[j] > - tooltipPosition.tooltipTop[j + 1] && - (tooltipPosition.tooltipTop[j + 1] > startPos! && - tooltipPosition.tooltipBottom[j + 1] < chartHeight)) { - tooltipPosition.tooltipBottom[j] = - tooltipPosition.tooltipTop[j + 1] - _tooltipPadding; - tooltipPosition.tooltipTop[j] = - tooltipPosition.tooltipBottom[j] - secWidth; - } - } - } - } - for (int i = 0; i < length; i++) { - yAxisRenderer = _yAxesInfo[i]; - for (int k = 0; k < axesLength; k++) { - if (yAxisRenderer == - _stateProperties.chartAxis.axisRenderersCollection[k]) { - if (isTransposed) { - chartHeight = _axisClipRect.right; - startPos = _axisClipRect.left; - } else { - chartHeight = _axisClipRect.bottom - _axisClipRect.top; - startPos = _axisClipRect.top; - } - } - } - width = tooltipPosition.tooltipBottom[i] - tooltipPosition.tooltipTop[i]; - if (startPos != null && tooltipPosition.tooltipTop[i] < startPos) { - tooltipPosition.tooltipTop[i] = startPos + 1; - tooltipPosition.tooltipBottom[i] = - tooltipPosition.tooltipTop[i] + width; - for (int j = i + 1; j <= (length - 1); j++) { - secWidth = - tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; - if (tooltipPosition.tooltipTop[j] < - tooltipPosition.tooltipBottom[j - 1] && - (tooltipPosition.tooltipTop[j - 1] > startPos && - tooltipPosition.tooltipBottom[j - 1] < chartHeight!)) { - tooltipPosition.tooltipTop[j] = - tooltipPosition.tooltipBottom[j - 1] + _tooltipPadding; - tooltipPosition.tooltipBottom[j] = - tooltipPosition.tooltipTop[j] + secWidth; - } - } - } - } - return tooltipPosition; - } - - // Method to identify the colliding trackball tooltips and return the new tooltip positions - TooltipPositions _continuousOverlappingPoints(List tooltipTop, - List tooltipBottom, List visibleLocation) { - num temp, - count = 0, - start = 0, - halfHeight, - midPos, - tempTooltipHeight, - temp1TooltipHeight; - int startPoint = 0, i, j, k; - final num endPoint = tooltipBottom.length - 1; - num tooltipHeight = (tooltipBottom[0] - tooltipTop[0]) + _tooltipPadding; - temp = tooltipTop[0] + tooltipHeight; - start = tooltipTop[0]; - for (i = 0; i < endPoint; i++) { - // To identify that tooltip collides or not - if (temp >= tooltipTop[i + 1]) { - tooltipHeight = - tooltipBottom[i + 1] - tooltipTop[i + 1] + _tooltipPadding; - temp += tooltipHeight; - count++; - // This condition executes when the tooltip count is half of the total number of tooltips - if (count - 1 == endPoint - 1 || i == endPoint - 1) { - halfHeight = (temp - start) / 2; - midPos = (visibleLocation[startPoint] + visibleLocation[i + 1]) / 2; - tempTooltipHeight = - tooltipBottom[startPoint] - tooltipTop[startPoint]; - tooltipTop[startPoint] = midPos - halfHeight; - tooltipBottom[startPoint] = - tooltipTop[startPoint] + tempTooltipHeight; - for (k = startPoint; k > 0; k--) { - if (tooltipTop[k] <= tooltipBottom[k - 1] + _tooltipPadding) { - temp1TooltipHeight = tooltipBottom[k - 1] - tooltipTop[k - 1]; - tooltipTop[k - 1] = - tooltipTop[k] - temp1TooltipHeight - _tooltipPadding; - tooltipBottom[k - 1] = tooltipTop[k - 1] + temp1TooltipHeight; - } else { - break; - } - } - // To set tool tip positions based on the half height and other tooltip height - for (j = startPoint + 1; j <= startPoint + count; j++) { - tempTooltipHeight = tooltipBottom[j] - tooltipTop[j]; - tooltipTop[j] = tooltipBottom[j - 1] + _tooltipPadding; - tooltipBottom[j] = tooltipTop[j] + tempTooltipHeight; - } - } - } else { - count = i > 0 ? count : 0; - // This exectutes when any of the middle tooltip collides - if (count > 0) { - halfHeight = (temp - start) / 2; - midPos = (visibleLocation[startPoint] + visibleLocation[i]) / 2; - tempTooltipHeight = - tooltipBottom[startPoint] - tooltipTop[startPoint]; - tooltipTop[startPoint] = midPos - halfHeight; - tooltipBottom[startPoint] = - tooltipTop[startPoint] + tempTooltipHeight; - for (k = startPoint; k > 0; k--) { - if (tooltipTop[k] <= tooltipBottom[k - 1] + _tooltipPadding) { - temp1TooltipHeight = tooltipBottom[k - 1] - tooltipTop[k - 1]; - tooltipTop[k - 1] = - tooltipTop[k] - temp1TooltipHeight - _tooltipPadding; - tooltipBottom[k - 1] = tooltipTop[k - 1] + temp1TooltipHeight; - } else { - break; - } - } - - // To set tool tip positions based on the half height and other tooltip height - for (j = startPoint + 1; j <= startPoint + count; j++) { - tempTooltipHeight = tooltipBottom[j] - tooltipTop[j]; - tooltipTop[j] = tooltipBottom[j - 1] + _tooltipPadding; - tooltipBottom[j] = tooltipTop[j] + tempTooltipHeight; - } - count = 0; - } - tooltipHeight = - (tooltipBottom[i + 1] - tooltipTop[i + 1]) + _tooltipPadding; - temp = tooltipTop[i + 1] + tooltipHeight; - start = tooltipTop[i + 1]; - startPoint = i + 1; - } - } - return TooltipPositions(tooltipTop, tooltipBottom); - } - - /// To get and return label text of the trackball - String _getTrackballLabelText( - SeriesRendererDetails seriesRendererDetails, - 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, - CartesianChartPoint dataPoint) { - String labelValue; - final int digits = trackballBehavior.tooltipSettings.decimalPlaces; - final ChartAxis yAxis = seriesRendererDetails.yAxisDetails!.axis; - if (trackballBehavior.tooltipSettings.format != null) { - dynamic x; - final ChartAxisRendererDetails axisDetails = - seriesRendererDetails.xAxisDetails!; - if (axisDetails is DateTimeAxisDetails) { - final num interval = axisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (axisDetails.visibleLabels.isNotEmpty) - ? axisDetails - .visibleLabels[axisDetails.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = - (axisDetails.axis as DateTimeAxis).dateFormat ?? - getDateTimeLabelFormat(axisDetails.axisRenderer, - interval.toInt(), prevInterval.toInt()); - x = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(xValue! as int)); - } else if (axisDetails is CategoryAxisDetails) { - x = dataPoint.x; - } else if (axisDetails is DateTimeCategoryAxisDetails) { - final num interval = axisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (axisDetails.visibleLabels.isNotEmpty) - ? axisDetails - .visibleLabels[axisDetails.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = - (axisDetails.axis as DateTimeCategoryAxis).dateFormat ?? - getDateTimeLabelFormat(axisDetails.axisRenderer, - interval.toInt(), prevInterval.toInt()); - x = dateFormat.format(DateTime.fromMillisecondsSinceEpoch( - (dataPoint.x).millisecondsSinceEpoch)); - } - labelValue = seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == true - ? seriesRendererDetails.seriesType.contains('boxandwhisker') == true - ? (trackballBehavior.tooltipSettings.format! - .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll('point.minimum', minValue.toString()) - .replaceAll('point.maximum', maxValue.toString()) - .replaceAll( - 'point.lowerQuartile', lowerQuartileValue.toString()) - .replaceAll( - 'point.upperQuartile', upperQuartileValue.toString()) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', seriesName)) - : seriesRendererDetails.seriesType == 'hilo' || - seriesRendererDetails.seriesType.contains('range') == true - ? (trackballBehavior.tooltipSettings.format! - .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll('point.high', highValue.toString()) - .replaceAll('point.low', lowValue.toString()) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', seriesName)) - : (trackballBehavior.tooltipSettings.format! - .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll('point.high', highValue.toString()) - .replaceAll('point.low', lowValue.toString()) - .replaceAll('point.open', openValue.toString()) - .replaceAll('point.close', closeValue.toString()) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', seriesName)) - : seriesRendererDetails.seriesType == 'bubble' - ? (trackballBehavior.tooltipSettings.format! - .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll( - 'point.y', - getLabelValue(yValue, - seriesRendererDetails.yAxisDetails!.axis, digits)) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', seriesName) - .replaceAll('point.size', bubbleSize.toString())) - : seriesRendererDetails.seriesType.contains('stacked') == true - ? (trackballBehavior.tooltipSettings.format! - .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll('point.y', getLabelValue(yValue, seriesRendererDetails.yAxisDetails!.axis, digits)) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', seriesName) - .replaceAll('point.cumulativeValue', cumulativeValue.toString())) - : (trackballBehavior.tooltipSettings.format!.replaceAll('point.x', (x ?? xValue).toString()).replaceAll('point.y', getLabelValue(yValue, seriesRendererDetails.yAxisDetails!.axis, digits)).replaceAll('{', '').replaceAll('}', '').replaceAll('series.name', seriesName)); - } else { - labelValue = seriesRendererDetails.seriesType.contains('range') == - false && - seriesRendererDetails.seriesType.contains('candle') == false && - seriesRendererDetails.seriesType.contains('hilo') == false && - seriesRendererDetails.seriesType.contains('boxandwhisker') == - false - ? getLabelValue(yValue, yAxis, digits) - : seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == - true - ? seriesRendererDetails.seriesType.contains('boxandwhisker') == - true - ? 'Maximum : ${getLabelValue(maxValue, yAxis)}\nMinimum : ${getLabelValue(minValue, yAxis)}\nLowerQuartile : ${getLabelValue(lowerQuartileValue, yAxis)}\nUpperQuartile : ${getLabelValue(upperQuartileValue, yAxis)}' - : 'High : ${getLabelValue(highValue, yAxis)}\nLow : ${getLabelValue(lowValue, yAxis)}\nOpen : ${getLabelValue(openValue, yAxis)}\nClose : ${getLabelValue(closeValue, yAxis)}' - : 'High : ${getLabelValue(highValue, yAxis)}\nLow : ${getLabelValue(lowValue, yAxis)}'; - } - return labelValue; - } - - /// To get header text of trackball - String _getHeaderText(CartesianChartPoint point, - SeriesRendererDetails seriesRendererDetails) { - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - String headerText; - String? date; - if (xAxisDetails is DateTimeAxisDetails) { - final DateTimeAxis _xAxis = xAxisDetails.axis as DateTimeAxis; - final num interval = xAxisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (xAxisDetails.visibleLabels.isNotEmpty) - ? xAxisDetails - .visibleLabels[xAxisDetails.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = _xAxis.dateFormat ?? - getDateTimeLabelFormat(xAxisDetails.axisRenderer, interval.toInt(), - prevInterval.toInt()); - date = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); - } - headerText = xAxisDetails is CategoryAxisDetails - ? point.x.toString() - : xAxisDetails is DateTimeAxisDetails - ? date!.toString() - : (xAxisDetails is DateTimeCategoryAxisDetails - ? xAxisDetails.getFormattedLabel( - '${point.x.microsecondsSinceEpoch}', - xAxisDetails.dateFormat) - : getLabelValue(point.xValue, xAxisDetails.axis, - _chart.tooltipBehavior.decimalPlaces)); - return headerText; - } - - /// To draw trackball line - 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 && paint != null) { - trackballPainter!.drawTrackBallLine(canvas, paint, seriesIndex); - } - } - - /// Returns the track line painter - Paint? linePainter(Paint paint) => trackballPainter?.getLinePainter(paint); - - /// Trackball show by index - void internalShowByIndex( - int pointIndex, - ) { - final CartesianStateProperties stateProperties = _stateProperties; - final TrackballRenderingDetails _trackballRenderingDetails = - TrackballHelper.getRenderingDetails( - _stateProperties.trackballBehaviorRenderer); - if (_trackballRenderingDetails.trackballPainter != null || - _chart.trackballBehavior.builder != null) { - if (validIndex(pointIndex, 0, stateProperties.chart)) { - _trackballRenderingDetails.showTrackball( - stateProperties.chartSeries.visibleSeriesRenderers, - pointIndex, - _stateProperties.trackballBehaviorRenderer); - } - if (_trackballRenderingDetails.trackballPainter != null) { - _trackballRenderingDetails.trackballPainter!.canResetPath = false; - _trackballRenderingDetails.trackballPainter!.stateProperties - .repaintNotifiers['trackball']!.value++; - } - } - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the trackball rendering details instance from its renderer -class TrackballHelper { - /// Returns the trackball rendering details instance from its renderer - static TrackballRenderingDetails getRenderingDetails( - TrackballBehaviorRenderer renderer) { - return renderer._trackballRenderingDetails; - } - - /// Returns the cartesian state properties from its instance - static CartesianStateProperties getStateProperties( - TrackballBehavior trackballBehavior) { - return trackballBehavior._stateProperties; - } - - /// Method to set the cartesian state properties - static void setStateProperties(TrackballBehavior trackballBehavior, - CartesianStateProperties stateProperties) { - trackballBehavior._stateProperties = stateProperties; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart deleted file mode 100644 index e35697afd..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:ui' as dart_ui; -import '../chart_series/series.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/trackball_marker_settings.dart'; - -/// Creates a class for TrackballMarkerSettingsRenderer. -class TrackballMarkerSettingsRenderer { - /// Creates an argument constructor for TrackballMarkerSettings class. - TrackballMarkerSettingsRenderer(this._trackballMarkerSettings); - - ///ignore: unused_field - final TrackballMarkerSettings? _trackballMarkerSettings; - - /// Specifies the marker image. - dart_ui.Image? image; -} - -/// Class to store the group mode details of trackball template. -class TrackballGroupingModeInfo { - /// Creates an argument constructor for TrackballGroupingModeInfo class. - TrackballGroupingModeInfo(this.points, this.currentPointIndices, - this.visibleSeriesIndices, this.visibleSeriesList); - - /// Specifies the cartesian chart points. - final List> points; - - /// Specifies the current point indices. - final List currentPointIndices; - - /// Specifies the visible series indices. - final List visibleSeriesIndices; - - /// 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 deleted file mode 100644 index befbe4af3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart +++ /dev/null @@ -1,1776 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../common/rendering_details.dart'; -import '../axis/axis.dart'; -import '../chart_segment/chart_segment.dart'; -import '../chart_series/series.dart'; -import '../chart_series/series_renderer_properties.dart'; -import '../chart_series/xy_data_series.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/interactive_tooltip.dart'; -import '../common/renderer.dart'; -import '../utils/helper.dart'; -import 'trackball.dart'; -import 'trackball_template.dart'; - -/// Represents the Trackline painter. -class TracklinePainter extends CustomPainter { - /// Creates constructor of TracklinePainter class. - TracklinePainter(this.trackballBehavior, this.stateProperties, - this.chartPointInfo, this.markerShapes); - - /// Represents the value trackball behavior. - TrackballBehavior trackballBehavior; - - /// Represents the cartesian state properties. - CartesianStateProperties stateProperties; - - /// Specifies the list of chart point information of data points. - List? chartPointInfo; - - /// Specifies the list of maker shape paths. - List? markerShapes; - - /// Specifies whether the trackline is drawn or not. - bool isTrackLineDrawn = false; - - @override - void paint(Canvas canvas, Size size) { - final Path dashArrayPath = Path(); - final Paint trackballLinePaint = Paint(); - trackballLinePaint.color = trackballBehavior.lineColor ?? - stateProperties.renderingDetails.chartTheme.crosshairLineColor; - trackballLinePaint.strokeWidth = trackballBehavior.lineWidth; - trackballLinePaint.style = PaintingStyle.stroke; - trackballBehavior.lineWidth == 0 - ? trackballLinePaint.color = Colors.transparent - : trackballLinePaint.color = trackballLinePaint.color; - final Rect boundaryRect = stateProperties.chartAxis.axisClipRect; - - if (chartPointInfo != null && chartPointInfo!.isNotEmpty) { - for (int index = 0; index < chartPointInfo!.length; index++) { - if (index == 0) { - if (chartPointInfo![index] - .seriesRendererDetails! - .seriesType - .contains('bar') == - true - ? stateProperties.requireInvertedAxis - : stateProperties.requireInvertedAxis) { - dashArrayPath.moveTo( - boundaryRect.left, chartPointInfo![index].yPosition!); - dashArrayPath.lineTo( - boundaryRect.right, chartPointInfo![index].yPosition!); - } else { - dashArrayPath.moveTo( - chartPointInfo![index].xPosition!, boundaryRect.top); - dashArrayPath.lineTo( - chartPointInfo![index].xPosition!, boundaryRect.bottom); - } - trackballBehavior.lineDashArray != null - ? drawDashedLine(canvas, trackballBehavior.lineDashArray!, - trackballLinePaint, dashArrayPath) - : canvas.drawPath(dashArrayPath, trackballLinePaint); - } - if (markerShapes != null && - markerShapes!.isNotEmpty && - markerShapes!.length > index) { - TrackballHelper.getRenderingDetails( - stateProperties.trackballBehaviorRenderer) - .renderTrackballMarker( - chartPointInfo![index].seriesRendererDetails!, - canvas, - trackballBehavior, - index); - } - } - } - } - - @override - bool shouldRepaint(TracklinePainter oldDelegate) => true; -} - -/// Represents the trackball painter. -class TrackballPainter extends CustomPainter { - /// Calling the default constructor of TrackballPainter class. - TrackballPainter({required this.stateProperties, required this.valueNotifier}) - : chart = stateProperties.chart, - super(repaint: valueNotifier); - - /// Represents the cartesian chart properties. - final CartesianStateProperties stateProperties; - - /// Represents the value of cartesian chart. - final SfCartesianChart chart; - - /// Specifies the value of timer. - Timer? timer; - - /// Repaint notifier for trackball. - ValueNotifier valueNotifier; - - /// Represents the value of pointer length. - late double pointerLength; - - /// Represents the value of pointer width. - late double pointerWidth; - - /// Specifies the value of nose point y value. - double nosePointY = 0; - - /// Specifies the value of nose point x value. - double nosePointX = 0; - - /// Specifies the value of total width. - double totalWidth = 0; - - /// Represents the value of x value. - double? x; - - /// Represents the value of y value. - double? y; - - /// Represents the value of x position. - double? xPos; - - /// Represents the value of y position. - double? yPos; - - /// Represents the value of isTop. - bool isTop = false; - - /// Represents the value of border radius. - late double borderRadius; - - /// Represents the value of background path. - Path backgroundPath = Path(); - - /// Represents the value for canResetPath for trackball. - bool canResetPath = true; - - /// Represents the value of isleft. - bool isLeft = false; - - /// Represents the value of isright. - bool isRight = false; - - /// Specifies the padding value for group all dispaly mode. - double groupAllPadding = 10; - - /// Specifies the list of string values for the trackball. - List stringValue = []; - - /// Represents the boundary rect for trackball. - Rect boundaryRect = Rect.zero; - - /// Represents the value of left padding. - double leftPadding = 0; - - /// Represents the value of top padding. - double topPadding = 0; - - /// Specifies whether the orientation is horizontal or not. - bool isHorizontalOrientation = false; - - /// Specifies whether the series is rect type or not. - bool isRectSeries = false; - - /// Specifies the text style for label. - late TextStyle labelStyle; - - /// Specifies whether the divider is needed or not. - bool divider = true; - - /// Specifies the list of marker shaper paths. - List? _markerShapes; - - /// Specifies the list of tooltip top values. - List tooltipTop = []; - - /// Specifies the list of tooltip bottom values. - List tooltipBottom = []; - - final List _xAxesInfo = []; - - final List _yAxesInfo = []; - - /// Specifies the list of chart point infos - late List chartPointInfo; - - late List _visiblePoints; - - TooltipPositions? _tooltipPosition; - - num _padding = 5; - - late num _tooltipPadding; - - ///Specifies whether the series is range type or not. - bool isRangeSeries = false; - - ///Specifies whether the series is box and whishers series or not. - bool isBoxSeries = false; - - /// Represents the rect value of label. - late Rect labelRect; - - /// Represents the value of marker size and padding. - late num markerSize, markerPadding; - - /// Specifies whether the group mode is enabled or not. - bool isGroupMode = false; - - /// Represents the value of last marker result height. - late double lastMarkerResultHeight; - - ChartLocation? _minLocation, _maxLocation; - - /// Trackball rendering details - TrackballRenderingDetails get trackballRenderingDetails => - TrackballHelper.getRenderingDetails( - stateProperties.trackballBehaviorRenderer); - - @override - void paint(Canvas canvas, Size size) { - stateProperties.trackballBehaviorRenderer.onPaint(canvas); - } - - /// To get the paint for trackball line painter. - Paint getLinePainter(Paint trackballLinePaint) => trackballLinePaint; - - /// To draw the trackball for all series. - void drawTrackball(Canvas canvas) { - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - if (!_isSeriesAnimating()) { - chartPointInfo = trackballRenderingDetails.chartPointInfo; - _markerShapes = trackballRenderingDetails.markerShapes; - _visiblePoints = trackballRenderingDetails.visiblePoints; - isRangeSeries = trackballRenderingDetails.isRangeSeries; - isBoxSeries = trackballRenderingDetails.isBoxSeries; - _tooltipPadding = stateProperties.requireInvertedAxis ? 8 : 5; - borderRadius = chart.trackballBehavior.tooltipSettings.borderRadius; - pointerLength = chart.trackballBehavior.tooltipSettings.arrowLength; - pointerWidth = chart.trackballBehavior.tooltipSettings.arrowWidth; - isGroupMode = chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints; - - isLeft = false; - isRight = false; - double height = 0, width = 0; - boundaryRect = stateProperties.chartAxis.axisClipRect; - totalWidth = boundaryRect.left + boundaryRect.width; - labelStyle = TextStyle( - color: chart.trackballBehavior.tooltipSettings.textStyle.color ?? - renderingDetails.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++) { - if (!canResetPath && - trackLinePoint != null && - chart.trackballBehavior.lineType != TrackballLineType.none) { - final Paint trackballLinePaint = Paint(); - trackballLinePaint.color = chart.trackballBehavior.lineColor ?? - renderingDetails.chartTheme.crosshairLineColor; - trackballLinePaint.strokeWidth = chart.trackballBehavior.lineWidth; - trackballLinePaint.style = PaintingStyle.stroke; - chart.trackballBehavior.lineWidth == 0 - ? trackballLinePaint.color = Colors.transparent - : trackballLinePaint.color = trackballLinePaint.color; - trackballRenderingDetails.drawLine( - canvas, - trackballRenderingDetails.linePainter(trackballLinePaint), - chartPointInfo.indexOf(trackLinePoint)); - } - final ChartPointInfo next = chartPointInfo[index]; - final ChartPointInfo pres = trackLinePoint!; - final Offset pos = trackballRenderingDetails.tapPosition; - if (stateProperties.requireInvertedAxis - ? ((pos.dy - pres.yPosition!).abs() >= - (pos.dy - next.yPosition!).abs()) - : ((pos.dx - pres.xPosition!).abs() >= - (pos.dx - next.xPosition!).abs())) { - trackLinePoint = chartPointInfo[index]; - } - final bool isRectTypeSeries = chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('column') == - true || - chartPointInfo[index].seriesRendererDetails!.seriesType == - 'candle' || - chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('boxandwhisker') == - true || - chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('hilo') == - true; - if ((isRectTypeSeries && !stateProperties.requireInvertedAxis) || - (chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('bar') == - true && - stateProperties.requireInvertedAxis)) { - isHorizontalOrientation = true; - } - isRectSeries = false; - if ((isRectTypeSeries && stateProperties.requireInvertedAxis) || - (chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('bar') == - true && - !stateProperties.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] - .seriesRendererDetails! - .series - .markerSettings - .isVisible == - true) - : 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 != ''))) { - for (int markerIndex = 0; - markerIndex < chartPointInfo.length; - markerIndex++) { - _renderEachMarkers(canvas, markerIndex); - } - _calculateTrackballRect( - canvas, width, height, index, chartPointInfo); - } else { - if (!canResetPath && - chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - tooltipTop.add(stateProperties.requireInvertedAxis - ? _visiblePoints[index].closestPointX - - _tooltipPadding - - (width / 2) - : _visiblePoints[index].closestPointY - - _tooltipPadding - - height / 2); - tooltipBottom.add(stateProperties.requireInvertedAxis - ? (_visiblePoints[index].closestPointX + - _tooltipPadding + - (width / 2)) + - (chart.trackballBehavior.tooltipSettings.canShowMarker - ? 20 - : 0) - : _visiblePoints[index].closestPointY + - _tooltipPadding + - height / 2); - _xAxesInfo.add(chartPointInfo[index] - .seriesRendererDetails! - .xAxisDetails! - .axisRenderer); - _yAxesInfo.add(chartPointInfo[index] - .seriesRendererDetails! - .yAxisDetails! - .axisRenderer); - } - } - } - - if (isGroupMode) { - break; - } - } - -// ignore: unnecessary_null_comparison - if (tooltipTop != null && tooltipTop.isNotEmpty) { - _tooltipPosition = trackballRenderingDetails.smartTooltipPositions( - tooltipTop, - tooltipBottom, - _xAxesInfo, - _yAxesInfo, - chartPointInfo, - stateProperties.requireInvertedAxis, - true); - } - - for (int index = 0; index < chartPointInfo.length; index++) { - if (!isGroupMode) { - _renderEachMarkers(canvas, 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] - .seriesRendererDetails! - .series - .markerSettings - .isVisible == - true) - : 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(); - } - } - } - } - } - - void _renderEachMarkers(Canvas canvas, int markerIndex) { - trackballRenderingDetails.trackballMarker(markerIndex); - if (_markerShapes != null && - _markerShapes!.isNotEmpty && - _markerShapes!.length > markerIndex) { - trackballRenderingDetails.renderTrackballMarker( - chartPointInfo[markerIndex].seriesRendererDetails!, - canvas, - chart.trackballBehavior, - markerIndex); - } - } - - bool _isSeriesAnimating() { - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (!(seriesRendererDetails.animationCompleted == true || - seriesRendererDetails.series.animationDuration == 0 || - !stateProperties.renderingDetails.initialRender!) && - seriesRendererDetails.series.isVisible == true) { - return true; - } - } - return false; - } - - /// Specifies whether the trackball header text is to be rendered or not. - bool headerText = false; - - /// Specifies the value for formatting x value. - bool xFormat = false; - - /// Specifies whether the labelFormat contains colon or not. - bool isColon = true; - - /// To get tooltip size. - Size _getTooltipSize(double height, double width, int index) { - final Offset position = Offset( - chartPointInfo[index].xPosition!, chartPointInfo[index].yPosition!); - Offset pos; - ChartAxisRendererDetails xAxisDetails, yAxisDetails; - SeriesRendererDetails? seriesRendererDetails; - num? _minX, _maxX; - stringValue = []; - final String? format = chartPointInfo[index] - .seriesRendererDetails! - .chart - .trackballBehavior - .tooltipSettings - .format; - if (format != null && - format.contains('point.x') && - !format.contains('point.y')) { - xFormat = true; - } - if (format != null && - format.contains('point.x') && - format.contains('point.y') && - !format.contains(':')) { - isColon = false; - } - if (chartPointInfo[index].header != null && - chartPointInfo[index].header != '') { - stringValue.add(TrackballElement(chartPointInfo[index].header!, null)); - } - if (isGroupMode) { - String str1 = ''; - for (int i = 0; i < chartPointInfo.length; i++) { - pos = trackballRenderingDetails.tapPosition; - xAxisDetails = chartPointInfo[i].seriesRendererDetails!.xAxisDetails!; - seriesRendererDetails = chartPointInfo[i].seriesRendererDetails; - yAxisDetails = chartPointInfo[i].seriesRendererDetails!.yAxisDetails!; - _minX = seriesRendererDetails!.minimumX; - _maxX = seriesRendererDetails.maximumX; - _minLocation = calculatePoint( - _minX!, - seriesRendererDetails.minimumY!, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - chartPointInfo[index].series, - stateProperties.chartAxis.axisClipRect); - _maxLocation = calculatePoint( - _maxX!, - seriesRendererDetails.maximumY!, - xAxisDetails, - yAxisDetails, - stateProperties.requireInvertedAxis, - chartPointInfo[index].series, - stateProperties.chartAxis.axisClipRect); - if (chartPointInfo[i].header != null && - 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].seriesRendererDetails!.isIndicator == true) { - isHeader = chartPointInfo[0].header != null && - chartPointInfo[0].header != ''; - isLabel = - chartPointInfo[0].label != null && chartPointInfo[0].label != ''; - } - divider = isHeader && isLabel; - final String seriesType = - chartPointInfo[i].seriesRendererDetails!.seriesType; - if (chartPointInfo[i].seriesRendererDetails!.isIndicator == true && - chartPointInfo[i] - .seriesRendererDetails! - .series - .name! - .contains('rangearea') == - true) { - 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] - .seriesRendererDetails! - .chart - .trackballBehavior - .tooltipSettings - .format == - null && - isLabel) { - stringValue.add(TrackballElement( - '${(chartPointInfo[index].header == null || chartPointInfo[index].header == '') ? '' : i == 0 ? '\n' : ''}${chartPointInfo[i].seriesRendererDetails!.seriesName}\n${chartPointInfo[i].label}', - chartPointInfo[i].seriesRendererDetails!.renderer)); - } else if (chartPointInfo[i].seriesRendererDetails!.series.name != - null) { - if (chartPointInfo[i] - .seriesRendererDetails! - .chart - .trackballBehavior - .tooltipSettings - .format != - null) { - if (isHeader && isLabel && i == 0) { - stringValue.add(TrackballElement('', null)); - } - if (isLabel) { - stringValue.add(TrackballElement(chartPointInfo[i].label!, - chartPointInfo[i].seriesRendererDetails!.renderer)); - } - } else if (isLabel && - chartPointInfo[i].label!.contains(':') && - (chartPointInfo[i].header == null || - chartPointInfo[i].header == '')) { - stringValue.add(TrackballElement(chartPointInfo[i].label!, - chartPointInfo[i].seriesRendererDetails!.renderer)); - divider = false; - } else { - if (isHeader && isLabel && i == 0) { - stringValue.add(TrackballElement('', null)); - } - if (isLabel) { - final CartesianStateProperties stateProperties = - chartPointInfo[i].seriesRendererDetails!.stateProperties; - //ignore: avoid_bool_literals_in_conditional_expressions - if (chartPointInfo[i].seriesRendererDetails!.isIndicator == - true && - stateProperties - .renderingDetails.legendToggleStates.isEmpty - ? pos.dx >= _minLocation!.x && pos.dx <= _maxLocation!.x - : true) { - stringValue.add(TrackballElement( - '$str1${chartPointInfo[i].seriesRendererDetails!.series.name!}: ${chartPointInfo[i].label!}', - chartPointInfo[i].seriesRendererDetails!.renderer)); - } - } - divider = (chartPointInfo[0].header != null && - chartPointInfo[0].header != '') && - isLabel; - } - if (str1 != '') { - str1 = ''; - } - } else { - if (isLabel) { - if (isHeader && i == 0) { - stringValue.add(TrackballElement('', null)); - } - stringValue.add(TrackballElement(chartPointInfo[i].label!, - chartPointInfo[i].seriesRendererDetails!.renderer)); - } - } - } - for (int i = 0; i < stringValue.length; 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; - } - height += measureText(measureString, labelStyle).height; - } - if (stateProperties.requireInvertedAxis) { - y = position.dy; - x = (chart.trackballBehavior.tooltipAlignment == ChartAlignment.center) - ? boundaryRect.center.dx - : (chart.trackballBehavior.tooltipAlignment == ChartAlignment.near) - ? boundaryRect.top - : boundaryRect.bottom; - } else { - x = position.dx; - y = (chart.trackballBehavior.tooltipAlignment == ChartAlignment.center) - ? boundaryRect.center.dy - : (chart.trackballBehavior.tooltipAlignment == ChartAlignment.near) - ? boundaryRect.top - : boundaryRect.bottom; - } - } else { - stringValue = []; - if (chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - stringValue.add(TrackballElement(chartPointInfo[index].label!, - chartPointInfo[index].seriesRendererDetails!.renderer)); - } - - 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); - width = size.width; - height = size.height; - - if (chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('column') == - true || - chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('bar') == - true || - chartPointInfo[index].seriesRendererDetails!.seriesType == 'candle' || - chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('boxandwhisker') == - true || - chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('hilo') == - true) { - x = position.dx; - y = position.dy; - } else if (chartPointInfo[index].seriesRendererDetails!.seriesType == - 'rangearea') { - x = chartPointInfo[index].chartDataPoint!.markerPoint!.x; - y = (chartPointInfo[index].chartDataPoint!.markerPoint!.y + - chartPointInfo[index].chartDataPoint!.markerPoint2!.y) / - 2; - } else { - x = position.dx; - y = position.dy; - } - } - return Size(width, height); - } - - /// To find the rect location of the trackball. - void _calculateTrackballRect( - Canvas canvas, double width, double height, int index, - [List? chartPointInfo, - TooltipPositions? tooltipPosition]) { - final String seriesType = - chartPointInfo![index].seriesRendererDetails!.seriesType; - const double widthPadding = 17; - markerSize = 10; - if (!chart.trackballBehavior.tooltipSettings.canShowMarker) { - labelRect = Rect.fromLTWH(x!, y!, width + 15, height + 10); - } else { - labelRect = Rect.fromLTWH( - x!, y!, width + (2 * markerSize) + widthPadding, height + 10); - } - - if (y! > pointerLength + labelRect.height) { - _calculateTooltipSize(labelRect, chartPointInfo, tooltipPosition, index); - } else { - isTop = false; - if (seriesType.contains('bar') - ? stateProperties.requireInvertedAxis - : stateProperties.requireInvertedAxis) { - 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 - ? boundaryRect.left - : tooltipRightEnd > totalWidth - ? totalWidth - labelRect.width - : xPos; - } else { - if (isGroupMode) { - xPos = x! - labelRect.width / 2; - yPos = y; - } else { - xPos = x!; - yPos = (y! + pointerLength / 2) + _padding; - } - nosePointX = labelRect.left; - nosePointY = labelRect.top; - if (!stateProperties.renderingDetails.isRtl) { - if ((isGroupMode - ? (xPos! + (labelRect.width / 2) + groupAllPadding) - : xPos! + labelRect.width + _padding + pointerLength) > - boundaryRect.right) { - xPos = isGroupMode - ? (xPos! - (labelRect.width / 2) - groupAllPadding) - : xPos! - labelRect.width - _padding - pointerLength; - isLeft = true; - } else { - xPos = isGroupMode - ? x! + groupAllPadding - : x! + _padding + pointerLength; - } - } else { - if (x! - labelRect.width - _padding - pointerLength > - boundaryRect.left) { - xPos = isGroupMode - ? (xPos! - (labelRect.width / 2) - groupAllPadding) - : xPos! - labelRect.width - _padding - pointerLength; - isLeft = true; - } else { - xPos = isGroupMode - ? x! + groupAllPadding - : x! + _padding + pointerLength; - isRight = true; - } - } - - if (isGroupMode && (yPos! + labelRect.height) >= boundaryRect.bottom) { - yPos = boundaryRect.bottom - labelRect.height; - } - - if (isGroupMode && yPos! <= boundaryRect.top) { - yPos = boundaryRect.top; - } - } - } - - labelRect = isGroupMode || - chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.nearestPoint - ? Rect.fromLTWH(xPos!, yPos!, labelRect.width, labelRect.height) - : Rect.fromLTWH( - stateProperties.requireInvertedAxis - ? tooltipPosition!.tooltipTop[index].toDouble() - : xPos!, - !stateProperties.requireInvertedAxis - ? tooltipPosition!.tooltipTop[index].toDouble() - : yPos!, - labelRect.width, - labelRect.height); - if (isGroupMode) { - _drawTooltipBackground( - canvas, - labelRect, - nosePointX, - nosePointY, - borderRadius, - isTop, - backgroundPath, - isLeft, - isRight, - index, - null, - null); - } else { - if (stateProperties.requireInvertedAxis - ? tooltipPosition!.tooltipTop[index] >= boundaryRect.left && - tooltipPosition.tooltipBottom[index] <= boundaryRect.right - : tooltipPosition!.tooltipTop[index] >= boundaryRect.top && - tooltipPosition.tooltipBottom[index] <= boundaryRect.bottom) { - _drawTooltipBackground( - canvas, - labelRect, - nosePointX, - nosePointY, - borderRadius, - isTop, - backgroundPath, - isLeft, - isRight, - index, - seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType == 'candle' - ? chartPointInfo[index].highXPosition - : seriesType.contains('box') - ? chartPointInfo[index].maxXPosition - : chartPointInfo[index].xPosition, - seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType == 'candle' - ? chartPointInfo[index].highYPosition - : seriesType.contains('box') - ? chartPointInfo[index].maxYPosition - : chartPointInfo[index].yPosition); - } - } - } - - /// To find the trackball tooltip size. - void _calculateTooltipSize( - Rect labelRect, - List? chartPointInfo, - TooltipPositions? tooltipPositions, - int index) { - isTop = true; - isRight = false; - if (chartPointInfo![index] - .seriesRendererDetails! - .seriesType - .contains('bar') == - true - ? stateProperties.requireInvertedAxis - : stateProperties.requireInvertedAxis) { - 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 - ? boundaryRect.left - : tooltipRightEnd > totalWidth - ? totalWidth - labelRect.width - : xPos; - yPos = yPos! - pointerLength; - if (yPos! + labelRect.height >= boundaryRect.bottom) { - yPos = boundaryRect.bottom - labelRect.height; - } - } else { - xPos = x!; - yPos = y! - labelRect.height / 2; - nosePointY = yPos!; - nosePointX = labelRect.left; - if (!stateProperties.renderingDetails.isRtl) { - if (xPos! + labelRect.width + _padding + pointerLength > - boundaryRect.right) { - xPos = isGroupMode - ? xPos! - labelRect.width - groupAllPadding - : xPos! - labelRect.width - _padding - pointerLength; - isLeft = true; - } else { - xPos = isGroupMode - ? x! + groupAllPadding - : x! + _padding + pointerLength; - isLeft = false; - isRight = true; - } - } else { - xPos = isGroupMode - ? xPos! - labelRect.width - groupAllPadding - : xPos! - labelRect.width - _padding - pointerLength; - if (xPos! < boundaryRect.left) { - xPos = isGroupMode - ? x! + groupAllPadding - : x! + _padding + pointerLength; - isRight = true; - } else { - isLeft = true; - } - } - if (yPos! + labelRect.height >= boundaryRect.bottom) { - yPos = boundaryRect.bottom - labelRect.height; - } - } - } - - /// To draw the line for the trackball. - void drawTrackBallLine(Canvas canvas, Paint paint, int index) { - final Path dashArrayPath = Path(); - if (chartPointInfo[index] - .seriesRendererDetails! - .seriesType - .contains('bar') == - true - ? stateProperties.requireInvertedAxis - : stateProperties.requireInvertedAxis) { - dashArrayPath.moveTo(boundaryRect.left, chartPointInfo[index].yPosition!); - dashArrayPath.lineTo( - boundaryRect.right, chartPointInfo[index].yPosition!); - } else { - dashArrayPath.moveTo(chartPointInfo[index].xPosition!, boundaryRect.top); - dashArrayPath.lineTo( - chartPointInfo[index].xPosition!, boundaryRect.bottom); - } - chart.trackballBehavior.lineDashArray != null - ? drawDashedLine(canvas, chart.trackballBehavior.lineDashArray!, paint, - dashArrayPath) - : canvas.drawPath(dashArrayPath, paint); - } - - /// To draw background of trackball tooltip. - void _drawTooltipBackground( - Canvas canvas, - Rect labelRect, - double xPos, - double yPos, - double borderRadius, - bool isTop, - Path backgroundPath, - bool isLeft, - bool isRight, - int index, - double? xPosition, - double? yPosition) { - final double startArrow = pointerLength; - final double endArrow = pointerLength; - if (isTop) { - _drawTooltip( - canvas, - labelRect, - xPos, - yPos, - xPos - startArrow, - yPos - startArrow, - xPos + endArrow, - yPos - endArrow, - borderRadius, - backgroundPath, - isLeft, - isRight, - index, - xPosition, - yPosition); - } else { - _drawTooltip( - canvas, - labelRect, - xPos, - yPos, - xPos - startArrow, - yPos + startArrow, - xPos + endArrow, - yPos + endArrow, - borderRadius, - backgroundPath, - isLeft, - isRight, - index, - xPosition, - yPosition); - } - } - - /// To draw the tooltip on the trackball. - void _drawTooltip( - Canvas canvas, - Rect tooltipRect, - double? xPos, - double? yPos, - double startX, - double startY, - double endX, - double endY, - double borderRadius, - Path backgroundPath, - bool isLeft, - bool isRight, - int index, - double? xPosition, - double? yPosition) { - backgroundPath.reset(); - final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; - if (!canResetPath && - chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.none) { - if (!isGroupMode && - !(xPosition == null || yPosition == null) && - (tooltipRect.left > axisClipRect.left && - tooltipRect.right < axisClipRect.right && - tooltipRect.top > axisClipRect.top && - tooltipRect.bottom < axisClipRect.bottom)) { - if (stateProperties.requireInvertedAxis) { - if (isLeft) { - startX = tooltipRect.left + borderRadius; - endX = startX + pointerWidth; - } else if (isRight) { - endX = tooltipRect.right - borderRadius; - startX = endX - pointerWidth; - } - backgroundPath.moveTo( - (tooltipRect.left + tooltipRect.width / 2) - pointerWidth, - startY); - backgroundPath.lineTo(xPosition, yPosition); - backgroundPath.lineTo( - (tooltipRect.right - tooltipRect.width / 2) + pointerWidth, endY); - } else { - if (isLeft) { - backgroundPath.moveTo(tooltipRect.right, - tooltipRect.top + tooltipRect.height / 2 - pointerWidth); - backgroundPath.lineTo(tooltipRect.right, - tooltipRect.bottom - tooltipRect.height / 2 + pointerWidth); - backgroundPath.lineTo(tooltipRect.right + pointerLength, yPosition); - backgroundPath.lineTo(tooltipRect.right + pointerLength, yPosition); - backgroundPath.lineTo(tooltipRect.right, - tooltipRect.top + tooltipRect.height / 2 - pointerWidth); - } else { - backgroundPath.moveTo(tooltipRect.left, - tooltipRect.top + tooltipRect.height / 2 - pointerWidth); - backgroundPath.lineTo(tooltipRect.left, - tooltipRect.bottom - tooltipRect.height / 2 + pointerWidth); - backgroundPath.lineTo(tooltipRect.left - pointerLength, yPosition); - backgroundPath.lineTo(tooltipRect.left, - tooltipRect.top + tooltipRect.height / 2 - pointerWidth); - } - } - } - - double rectLeft = tooltipRect.left; - double rectRight = tooltipRect.right; - - if (tooltipRect.width < axisClipRect.width && - tooltipRect.height < axisClipRect.height) { - if (tooltipRect.left < axisClipRect.left) { - rectLeft = rectLeft + (axisClipRect.left - tooltipRect.left); - rectRight = rectRight + (axisClipRect.left - tooltipRect.left); - } else if (tooltipRect.right > axisClipRect.right) { - rectLeft = rectLeft - (tooltipRect.right - axisClipRect.right); - rectRight = rectRight - (tooltipRect.right - axisClipRect.right); - } - tooltipRect = Rect.fromLTRB( - rectLeft, tooltipRect.top, rectRight, tooltipRect.bottom); - if ((tooltipRect.left < axisClipRect.left) || - tooltipRect.right > axisClipRect.right) { - tooltipRect = Rect.zero; - } - - if (tooltipRect != Rect.zero) { - _drawRectandText(canvas, backgroundPath, tooltipRect, index); - } - } else { - tooltipRect = Rect.zero; - } - - xPos = null; - yPos = null; - } - } - - TextStyle _getLabelStyle(FontWeight fontWeight, TextStyle labelStyle) { - return TextStyle( - fontWeight: fontWeight, - color: labelStyle.color, - fontSize: labelStyle.fontSize, - fontFamily: labelStyle.fontFamily, - fontStyle: labelStyle.fontStyle, - inherit: labelStyle.inherit, - backgroundColor: labelStyle.backgroundColor, - letterSpacing: labelStyle.letterSpacing, - wordSpacing: labelStyle.wordSpacing, - textBaseline: labelStyle.textBaseline, - height: labelStyle.height, - locale: labelStyle.locale, - foreground: labelStyle.foreground, - background: labelStyle.background, - shadows: labelStyle.shadows, - fontFeatures: labelStyle.fontFeatures, - decoration: labelStyle.decoration, - decorationColor: labelStyle.decorationColor, - decorationStyle: labelStyle.decorationStyle, - decorationThickness: labelStyle.decorationThickness, - debugLabel: labelStyle.debugLabel, - fontFamilyFallback: labelStyle.fontFamilyFallback); - } - - double _getMultiLineTextOffset(RRect tooltipRect, String text, int index, - double totalLabelWidth, TextStyle style, - [double? previousWidth]) { - final double textOffsetX = - tooltipRect.left + tooltipRect.width / 2 + totalLabelWidth / 2; - final Size currentTextSize = measureText(text, style); - return textOffsetX - - currentTextSize.width - - (index == 0 ? 0 : previousWidth!); - } - - /// Draw trackball tooltip rect and text. - void _drawRectandText( - Canvas canvas, Path backgroundPath, Rect rect, int index) { - const double textOffsetPadding = 4; - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - final RRect tooltipRect = RRect.fromRectAndCorners( - rect, - bottomLeft: Radius.circular(borderRadius), - bottomRight: Radius.circular(borderRadius), - topLeft: Radius.circular(borderRadius), - topRight: Radius.circular(borderRadius), - ); - const double padding = 10; - backgroundPath.addRRect(tooltipRect); - - final Paint fillPaint = Paint() - ..color = chart.trackballBehavior.tooltipSettings.color ?? - renderingDetails.chartTheme.crosshairBackgroundColor - ..isAntiAlias = false - ..style = PaintingStyle.fill; - - final Paint strokePaint = Paint() - ..color = chart.trackballBehavior.tooltipSettings.borderColor ?? - renderingDetails.chartTheme.crosshairBackgroundColor - ..strokeWidth = chart.trackballBehavior.tooltipSettings.borderWidth - ..strokeCap = StrokeCap.butt - ..isAntiAlias = false - ..style = PaintingStyle.stroke; - - canvas.drawPath(backgroundPath, strokePaint); - canvas.drawPath(backgroundPath, fillPaint); - final Paint dividerPaint = Paint(); - dividerPaint.color = renderingDetails.chartTheme.tooltipSeparatorColor; - dividerPaint.strokeWidth = 1; - dividerPaint.style = PaintingStyle.stroke; - if (isGroupMode && divider) { - final Size headerResult = measureText(stringValue[0].label, labelStyle); - canvas.drawLine( - Offset(tooltipRect.left + padding, - tooltipRect.top + headerResult.height + padding), - Offset(tooltipRect.right - padding, - tooltipRect.top + headerResult.height + padding), - dividerPaint); - } - double eachTextHeight = 0; - Size labelSize; - double totalHeight = 0; - final bool isRtl = stateProperties.renderingDetails.isRtl; - - for (int i = 0; i < stringValue.length; i++) { - labelSize = measureText(stringValue[i].label, labelStyle); - totalHeight += labelSize.height; - } - - eachTextHeight = - (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; - } - } - const double animationFactor = 1; - labelStyle = _getLabelStyle(FontWeight.normal, labelStyle); - labelSize = measureText(stringValue[i].label, labelStyle); - eachTextHeight += labelSize.height; - if (!stringValue[i].label.contains(':') && - !stringValue[i].label.contains('') && - !stringValue[i].label.contains('')) { - labelStyle = _getLabelStyle(FontWeight.bold, labelStyle); - _drawTooltipMarker( - stringValue[i].label, - canvas, - tooltipRect, - animationFactor, - labelSize, - chartPointInfo[index].seriesRendererDetails!, - i, - null, - null, - eachTextHeight, - index); - drawText( - canvas, - stringValue[i].label, - Offset( - (tooltipRect.left + tooltipRect.width / 2) - - labelSize.width / 2 + - (isRtl ? -markerPadding : markerPadding), - eachTextHeight - labelSize.height), - labelStyle, - 0); - } else { - // ignore: unnecessary_null_comparison - 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++) { - final List str1 = str[j].split(':'); - if (str1.length > 1) { - for (int k = 0; k < str1.length; k++) { - double width = 0.0; - if (isRtl) { - str1[k] = k == 0 ? ': ${str1[k]}'.trim() : '${str1[k]} '; - width = measureText(str1[0], labelStyle).width; - } else { - width = - k > 0 ? measureText(str1[k - 1], labelStyle).width : 0; - str1[k] = k == 1 ? ':${str1[k]}' : str1[k]; - } - labelStyle = _getLabelStyle( - k > 0 ? FontWeight.bold : FontWeight.normal, labelStyle); - - if (k == 0) { - _drawTooltipMarker( - str1[k], - canvas, - tooltipRect, - animationFactor, - labelSize, - chartPointInfo[index].seriesRendererDetails!, - i, - null, - width, - eachTextHeight, - index); - } - drawText( - canvas, - str1[k], - isRtl - ? Offset( - _getMultiLineTextOffset( - tooltipRect, - str1[k], - k, - labelSize.width, - labelStyle, - width, - ) - - ((!isGroupMode && - chart.trackballBehavior - .tooltipSettings.canShowMarker) - ? markerPadding - : textOffsetPadding), - (eachTextHeight - labelSize.height) + padding) - : Offset( - (((!isGroupMode && - chart - .trackballBehavior - .tooltipSettings - .canShowMarker) - ? (tooltipRect.left + - tooltipRect.width / 2 - - labelSize.width / 2) - : (tooltipRect.left + - textOffsetPadding)) + - markerPadding) + - width, - (eachTextHeight - labelSize.height) + padding), - labelStyle, - 0); - - padding = k > 0 - ? padding + - (labelStyle.fontSize! + (labelStyle.fontSize! * 0.15)) - : padding; - } - } else { - labelStyle = _getLabelStyle(FontWeight.bold, labelStyle); - - _drawTooltipMarker( - str1[str1.length - 1], - canvas, - tooltipRect, - animationFactor, - labelSize, - chartPointInfo[index].seriesRendererDetails!, - 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], - isRtl - ? Offset( - tooltipRect.right - - measureText(str1[str1.length - 1], labelStyle) - .width - - markerPadding - - textOffsetPadding, - eachTextHeight - labelSize.height + padding) - : Offset( - markerPadding + - tooltipRect.left + - textOffsetPadding, - eachTextHeight - labelSize.height + padding), - labelStyle, - 0); - padding = padding + - (labelStyle.fontSize! + (labelStyle.fontSize! * 0.15)); - } - } - } else { - final bool hasFormat = - chart.trackballBehavior.tooltipSettings.format != null; - List str1 = str[str.length - 1].split(':'); - final List boldString = []; - if (str[str.length - 1].contains('')) { - str1 = []; - final List boldSplit = str[str.length - 1].split(''); - for (int i = 0; i < boldSplit.length; i++) { - if (boldSplit[i] != '') { - boldString.add(boldSplit[i].substring( - boldSplit[i].indexOf('') + 3, boldSplit[i].length)); - final List str2 = boldSplit[i].split(''); - for (int s = 0; s < str2.length; s++) { - str1.add(str2[s]); - } - } - } - } else if (str1.length > 2 || xFormat || !isColon || headerText) { - str1 = []; - str1.add(str[str.length - 1]); - } - double previousWidth = 0.0; - for (int j = 0; j < str1.length; j++) { - bool isBold = false; - for (int i = 0; i < boldString.length; i++) { - if (str1[j] == boldString[i]) { - isBold = true; - break; - } - } - final double width = - j > 0 ? measureText(str1[j - 1], labelStyle).width : 0; - previousWidth += width; - String colon = boldString.isNotEmpty - ? '' - : j > 0 - ? ' :' - : ''; - if (isRtl & !hasFormat) { - colon = boldString.isNotEmpty - ? '' - : j > 0 - ? ' : ' - : ''; - } - labelStyle = _getLabelStyle( - ((headerText && boldString.isEmpty) || xFormat || isBold) - ? FontWeight.bold - : j > 0 - ? boldString.isNotEmpty - ? FontWeight.normal - : FontWeight.bold - : FontWeight.normal, - labelStyle); - - if (j == 0) { - _drawTooltipMarker( - str1[j], - canvas, - tooltipRect, - animationFactor, - labelSize, - chartPointInfo[index].seriesRendererDetails!, - i, - previousWidth, - width, - eachTextHeight, - index); - } - markerPadding = - chart.trackballBehavior.tooltipSettings.canShowMarker - ? markerPadding + - (j == 0 && !isGroupMode - ? 13 - : j == 0 && isGroupMode - ? 7 - : 0) - : 0; - final Offset textEndPoint = Offset( - isRtl - ? tooltipRect.right - textOffsetPadding - markerPadding - : markerPadding + - (tooltipRect.left + textOffsetPadding) + - (previousWidth > width ? previousWidth : width), - eachTextHeight - labelSize.height); - - if (!isRtl || (isRtl && !hasFormat)) { - drawText( - canvas, - isRtl ? str1[j] + colon : colon + str1[j], - isRtl - ? Offset( - textEndPoint.dx - - (previousWidth > width - ? previousWidth - : width) - - measureText(str1[j] + colon, labelStyle).width, - textEndPoint.dy) - : textEndPoint, - labelStyle, - 0); - } else { - _drawTextWithFormat(canvas, str1, labelStyle, textEndPoint); - break; - } - headerText = false; - } - } - } - } - } - } - - void _drawTextWithFormat(Canvas canvas, List textCollection, - TextStyle labelStyle, Offset textEndPoint) { - final String arrangedText = _getRtlStringWithColon(textCollection); - final List strings = arrangedText.split(':'); - double previousWidth = 0.0; - for (int textCount = 0; textCount < strings.length; textCount++) { - final bool containsBackSpace = strings[textCount].endsWith(' '); - final bool containsFrontSpace = strings[textCount].startsWith(' '); - strings[textCount] = strings[textCount].trim(); - if (containsFrontSpace) { - strings[textCount] = '${strings[textCount]} '; - } - if (containsBackSpace) { - strings[textCount] = ' ${strings[textCount]}'; - } - final String currentText = - textCount > 0 ? '${strings[textCount]}:' : (strings[textCount]); - final Size currentTextSize = measureText(currentText, labelStyle); - previousWidth += currentTextSize.width; - drawText( - canvas, - currentText, - Offset(textEndPoint.dx - previousWidth, textEndPoint.dy), - labelStyle, - 0); - } - } - - String _getRtlStringWithColon(List textCollection) { - String string = ''; - for (int i = textCollection.length - 1; i >= 0; i--) { - final bool containsBackSpace = textCollection[i].endsWith(' '); - final bool containsFrontSpace = textCollection[i].startsWith(' '); - textCollection[i] = textCollection[i].trim(); - if (containsFrontSpace) { - textCollection[i] = '${textCollection[i]} '; - } - if (containsBackSpace) { - textCollection[i] = ' ${textCollection[i]}'; - } - string = string + - (i == textCollection.length - 1 - ? textCollection[i] - : ':${textCollection[i]}'); - } - return string; - } - - /// Draw marker inside the trackball tooltip. - void _drawTooltipMarker( - String labelValue, - Canvas canvas, - RRect tooltipRect, - double animationFactor, - Size tooltipMarkerResult, - SeriesRendererDetails? seriesRendererDetails, - int i, - double? previousWidth, - double? width, - double eachTextHeight, - int index, - [Size? headerSize]) { - final Size tooltipStringResult = tooltipMarkerResult; - markerSize = 5; - Offset markerPoint; - final bool isRtl = stateProperties.renderingDetails.isRtl; - if (chart.trackballBehavior.tooltipSettings.canShowMarker) { - if (!isGroupMode) { - if (seriesRendererDetails!.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('boxandwhisker') == - true) { - final double markerStartPoint = isRtl - ? tooltipStringResult.width / 2 + markerSize - : -tooltipMarkerResult.width / 2 - markerSize; - markerPoint = Offset( - tooltipRect.left + tooltipRect.width / 2 + markerStartPoint, - (eachTextHeight - tooltipStringResult.height / 2) + 0.0); - _renderMarker(markerPoint, seriesRendererDetails, animationFactor, - canvas, index); - } else { - final double markerStartPoint = isRtl - ? tooltipStringResult.width / 2 + markerSize - : -tooltipMarkerResult.width / 2 - markerSize; - markerPoint = Offset( - tooltipRect.left + tooltipRect.width / 2 + markerStartPoint, - ((tooltipRect.top + tooltipRect.height) - - tooltipStringResult.height / 2) - - markerSize); - } - _renderMarker( - markerPoint, seriesRendererDetails, animationFactor, canvas, index); - } else { - if (i > 0 && labelValue != '') { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stringValue[i].seriesRenderer!); - // ignore: unnecessary_null_comparison - if (seriesRendererDetails != null && - seriesRendererDetails.series.name != null && - seriesRendererDetails - .chart.trackballBehavior.tooltipSettings.format == - null) { - if (previousWidth != null && width != null) { - final double markerStartPoint = - isRtl ? (tooltipRect.right - 10) : (tooltipRect.left + 10); - markerPoint = Offset( - markerStartPoint + - (previousWidth > width ? previousWidth : width), - eachTextHeight - tooltipMarkerResult.height / 2); - _renderMarker(markerPoint, seriesRendererDetails, animationFactor, - canvas, index); - } else if (stringValue[i].needRender) { - markerPoint = Offset( - isRtl ? tooltipRect.right - 10 : 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, seriesRendererDetails, animationFactor, - canvas, index); - } - } else { - final double markerStartPoint = isRtl - ? tooltipStringResult.width / 2 + markerSize - : -tooltipMarkerResult.width / 2 - markerSize; - markerPoint = Offset( - (tooltipRect.left + tooltipRect.width / 2) + markerStartPoint, - eachTextHeight - tooltipMarkerResult.height / 2); - _renderMarker(markerPoint, seriesRendererDetails, animationFactor, - canvas, index); - } - } - } - } - } - - // To render marker for the chart tooltip. - void _renderMarker( - Offset markerPoint, - SeriesRendererDetails _seriesRendererDetails, - double animationFactor, - Canvas canvas, - int index) { - final MarkerSettings markerSettings = - chart.trackballBehavior.markerSettings == null - ? _seriesRendererDetails.series.markerSettings - : chart.trackballBehavior.markerSettings!; - final Path markerPath = getMarkerShapesPath( - markerSettings.shape, - markerPoint, - Size((2 * markerSize) * animationFactor, - (2 * markerSize) * animationFactor), - _seriesRendererDetails, - index, - chart.trackballBehavior); - - if (markerSettings.shape == DataMarkerType.image) { - drawImageMarker(null, canvas, markerPoint.dx, markerPoint.dy, - chart.trackballBehavior.markerSettings, stateProperties); - } - - Color? _seriesColor; - if (_seriesRendererDetails.seriesType.contains('candle') == true) { - _seriesColor = SegmentHelper.getSegmentProperties(_seriesRendererDetails - .segments[chartPointInfo[index].dataPointIndex!]) - .color; - } else if (_seriesRendererDetails.seriesType.contains('hiloopenclose') == - true) { - _seriesColor = SegmentHelper.getSegmentProperties(_seriesRendererDetails - .segments[chartPointInfo[index].dataPointIndex!]) - .isBull == - true - ? _seriesRendererDetails.hiloOpenCloseSeries.bullColor - : _seriesRendererDetails.hiloOpenCloseSeries.bearColor; - } else { - _seriesColor = (chartPointInfo[index].dataPointIndex! < - _seriesRendererDetails.dataPoints.length - ? _seriesRendererDetails - .dataPoints[chartPointInfo[index].dataPointIndex!] - .pointColorMapper - : null) ?? - _seriesRendererDetails.seriesColor; - } - - Paint markerPaint = Paint(); - markerPaint.color = markerSettings.color ?? _seriesColor ?? Colors.white; - if (_seriesRendererDetails.series.gradient != null) { - markerPaint = getLinearGradientPaint( - _seriesRendererDetails.series.gradient!, - getMarkerShapesPath( - markerSettings.shape, - Offset(markerPoint.dx, markerPoint.dy), - Size((2 * markerSize) * animationFactor, - (2 * markerSize) * animationFactor), - _seriesRendererDetails) - .getBounds(), - _seriesRendererDetails.stateProperties.requireInvertedAxis); - } - canvas.drawPath(markerPath, markerPaint); - Paint markerBorderPaint = Paint(); - markerBorderPaint.color = markerSettings.borderColor ?? - _seriesColor ?? - _seriesRendererDetails - .stateProperties.renderingDetails.chartTheme.tooltipLabelColor; - markerBorderPaint.strokeWidth = 1; - markerBorderPaint.style = PaintingStyle.stroke; - - if (_seriesRendererDetails.series.gradient != null) { - markerBorderPaint = getLinearGradientPaint( - _seriesRendererDetails.series.gradient!, - getMarkerShapesPath( - markerSettings.shape, - Offset(markerPoint.dx, markerPoint.dy), - Size((2 * markerSize) * animationFactor, - (2 * markerSize) * animationFactor), - _seriesRendererDetails) - .getBounds(), - _seriesRendererDetails.stateProperties.requireInvertedAxis); - } - canvas.drawPath(markerPath, markerBorderPaint); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => true; - - /// Return value as string. - String getFormattedValue(num value) => value.toString(); -} 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 deleted file mode 100644 index 4184c893e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart +++ /dev/null @@ -1,692 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; - -import '../../common/rendering_details.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/interactive_tooltip.dart'; -import 'trackball.dart'; -import 'trackball_painter.dart'; - -/// Widget class which is used to display the trackball template. -class TrackballTemplate extends StatefulWidget { - /// Creates an instance of trackball template. - const TrackballTemplate( - {required Key key, - required this.stateProperties, - required this.trackballBehavior}) - : super(key: key); - - /// Specifies the value of cartesian state properties. - final CartesianStateProperties stateProperties; - - /// Holds the value of trackball behavior. - final TrackballBehavior trackballBehavior; - - @override - State createState() { - return TrackballTemplateState(); - } -} - -/// Represents the trackball template state. -class TrackballTemplateState extends State { - bool _isRender = false; - - //ignore: unused_field - late TrackballTemplateState _state; - - /// Holds the chart point info. - List? chartPointInfo; - - /// Holds the list of marker shapes. - List? markerShapes; - - /// Holds the value of trackball grouping mode info. - late TrackballGroupingModeInfo groupingModeInfo; - late Widget _template; - - /// Specifies the trackball duration value. - //ignore: unused_field - late double duration; - - /// Specifies whether to show the trackball always. - bool? alwaysShow; - - bool _isRangeSeries = false, _isBoxSeries = false; - - @override - void initState() { - _state = this; - super.initState(); - } - - @override - void didUpdateWidget(TrackballTemplate oldWidget) { - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - Widget trackballWidget; - final List trackballWidgets = []; - _state = this; - String seriesType; - if (_isRender && - widget.stateProperties.animationCompleted && - chartPointInfo != null && - chartPointInfo!.isNotEmpty) { - for (int index = 0; index < chartPointInfo!.length; index++) { - seriesType = chartPointInfo![index].seriesRendererDetails!.seriesType; - _isRangeSeries = seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType == 'candle'; - _isBoxSeries = seriesType == 'boxandwhisker'; - if (widget.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { - _template = widget.trackballBehavior.builder!(context, - TrackballDetails(null, null, null, null, groupingModeInfo)); - trackballWidget = _TrackballRenderObject( - child: _template, - template: _template, - stateProperties: widget.stateProperties, - xPos: chartPointInfo![index].xPosition!, - yPos: (_isRangeSeries - ? chartPointInfo![index].highYPosition - : _isBoxSeries - ? chartPointInfo![index].maxYPosition - : chartPointInfo![index].yPosition)!, - trackballBehavior: widget.trackballBehavior); - - trackballWidgets.add(trackballWidget); - - break; - } else if (widget.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.none) { - _template = widget.trackballBehavior.builder!( - context, - TrackballDetails( - chartPointInfo![index] - .seriesRendererDetails! - .dataPoints[chartPointInfo![index].dataPointIndex!], - chartPointInfo![index].seriesRendererDetails!.series, - chartPointInfo![index].dataPointIndex, - chartPointInfo![index].seriesIndex, - null)); - - trackballWidget = _TrackballRenderObject( - child: _template, - template: _template, - stateProperties: widget.stateProperties, - xPos: chartPointInfo![index].xPosition!, - yPos: (_isRangeSeries - ? chartPointInfo![index].highYPosition - : _isBoxSeries - ? chartPointInfo![index].maxYPosition - : chartPointInfo![index].yPosition)!, - trackballBehavior: widget.trackballBehavior, - chartPointInfo: chartPointInfo!, - index: index); - - trackballWidgets.add(trackballWidget); - } - } - return Stack(children: [ - CustomPaint( - painter: TracklinePainter(widget.trackballBehavior, - widget.stateProperties, chartPointInfo, markerShapes)), - Stack(children: trackballWidgets), - ]); - } else { - trackballWidget = Container(); - return trackballWidget; - } - } - - /// Notify the object changes to framework. - void refresh() { - setState(() { - _isRender = true; - }); - } - - /// To hide tooltip templates. - void hideTrackballTemplate() { - if (mounted && alwaysShow != null && !alwaysShow!) { - setState(() { - _isRender = false; - }); - } - } -} - -@immutable -class _TrackballRenderObject extends SingleChildRenderObjectWidget { - const _TrackballRenderObject( - {Key? key, - required Widget child, - required this.template, - required this.stateProperties, - 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 CartesianStateProperties stateProperties; - final List? chartPointInfo; - final double xPos; - final double yPos; - final TrackballBehavior trackballBehavior; - - @override - RenderObject createRenderObject(BuildContext context) { - return TrackballTemplateRenderBox( - template, stateProperties, xPos, yPos, chartPointInfo, index); - } - - @override - void updateRenderObject( - BuildContext context, covariant TrackballTemplateRenderBox renderBox) { - renderBox.template = template; - renderBox.index = index; - renderBox.xPos = xPos; - renderBox.yPos = yPos; - renderBox.chartPointInfo = chartPointInfo; - } -} - -/// Render the annotation widget in the respective position. -class TrackballTemplateRenderBox extends RenderShiftedBox { - /// Creates an instance of trackball template render box. - TrackballTemplateRenderBox( - this._template, this.stateProperties, this.xPos, this.yPos, - [this.chartPointInfo, this.index, RenderBox? child]) - : super(child); - - Widget _template; - - /// Holds the value of cartesian state properties. - final CartesianStateProperties stateProperties; - - /// Holds the value of x and y position. - double xPos, yPos; - - /// Specifies the list of chart point info. - List? chartPointInfo; - - /// Holds the value of index. - int? index; - - /// Holds the value of pointer length and pointer width respectively. - late double pointerLength, pointerWidth; - - /// Holds the value of trackball template rect. - Rect? trackballTemplateRect; - - /// Holds the value of boundary rect. - late Rect boundaryRect; - - /// Specifies the value of padding. - num padding = 10; - - /// Specifies the value of trackball behavior. - late TrackballBehavior trackballBehavior; - - /// Specifies whether to group all the points. - bool isGroupAllPoints = false; - - /// Specifies whether it is the nearest point. - bool isNearestPoint = false; - - /// Gets the template widget. - Widget get template => _template; - - /// Specifies whether tooltip is present at right. - bool isRight = false; - - /// Specifies whether tooltip is present at bottom. - bool isBottom = false; - - /// Specifies whether the template is present inside the bounds. - bool isTemplateInBounds = true; - // Offset arrowOffset; - - /// Holds the tooltip position. - TooltipPositions? tooltipPosition; - - /// Holds the value of box parent data. - late BoxParentData childParentData; - - /// Gets the trackball rendering details. - TrackballRenderingDetails get trackballRenderingDetails => - TrackballHelper.getRenderingDetails( - stateProperties.trackballBehaviorRenderer); - - /// Sets the template value. - set template(Widget value) { - if (_template != value) { - _template = value; - markNeedsLayout(); - } - } - - @override - void performLayout() { - trackballBehavior = stateProperties.chart.trackballBehavior; - isGroupAllPoints = trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints; - isNearestPoint = trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.nearestPoint; - final List? tooltipTop = []; - final List tooltipBottom = []; - final List visiblePoints = - trackballRenderingDetails.visiblePoints; - final List xAxesInfo = - trackballRenderingDetails.xAxesInfo; - final List yAxesInfo = - trackballRenderingDetails.yAxesInfo; - boundaryRect = stateProperties.chartAxis.axisClipRect; - final num totalWidth = boundaryRect.left + boundaryRect.width; - tooltipPosition = trackballRenderingDetails.tooltipPosition; - final bool isTrackballMarkerEnabled = - trackballBehavior.markerSettings != null; - final BoxConstraints constraints = this.constraints; - pointerLength = trackballBehavior.tooltipSettings.arrowLength; - pointerWidth = trackballBehavior.tooltipSettings.arrowWidth; - double left, top; - 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 as BoxParentData; - - if (isGroupAllPoints) { - if (trackballBehavior.tooltipAlignment == ChartAlignment.center) { - yPos = boundaryRect.center.dy - size.height / 2; - } else if (trackballBehavior.tooltipAlignment == - ChartAlignment.near) { - yPos = boundaryRect.top; - } else { - yPos = boundaryRect.bottom; - } - - if (yPos + size.height > boundaryRect.bottom && - trackballBehavior.tooltipAlignment == ChartAlignment.far) { - yPos = boundaryRect.bottom - size.height; - } - } - if (chartPointInfo != null) { - for (int index = 0; index < chartPointInfo!.length; index++) { - tooltipTop!.add(stateProperties.requireInvertedAxis - ? visiblePoints[index].closestPointX - (size.width / 2) - : visiblePoints[index].closestPointY - size.height / 2); - tooltipBottom.add(stateProperties.requireInvertedAxis - ? visiblePoints[index].closestPointX + (size.width / 2) - : visiblePoints[index].closestPointY + size.height / 2); - xAxesInfo.add(chartPointInfo![index] - .seriesRendererDetails! - .xAxisDetails! - .axisRenderer); - yAxesInfo.add(chartPointInfo![index] - .seriesRendererDetails! - .yAxisDetails! - .axisRenderer); - } - } - if (tooltipTop != null && tooltipTop.isNotEmpty) { - tooltipPosition = trackballRenderingDetails.smartTooltipPositions( - tooltipTop, - tooltipBottom, - xAxesInfo, - yAxesInfo, - chartPointInfo!, - stateProperties.requireInvertedAxis); - } - - if (!isGroupAllPoints) { - left = (stateProperties.requireInvertedAxis - ? tooltipPosition!.tooltipTop[index!] - : xPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0)) - .toDouble(); - top = (stateProperties.requireInvertedAxis - ? yPos + - pointerLength + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0) - : tooltipPosition!.tooltipTop[index!]) - .toDouble(); - - if (isNearestPoint) { - left = stateProperties.requireInvertedAxis - ? xPos + size.width / 2 - : xPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0); - top = stateProperties.requireInvertedAxis - ? yPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0) - : yPos - size.height / 2; - } - - if (!stateProperties.requireInvertedAxis) { - if (left + size.width > totalWidth) { - isRight = true; - left = xPos - - size.width - - pointerLength - - (isTrackballMarkerEnabled - ? (trackballBehavior.markerSettings!.width / 2) - : 0); - } else { - isRight = false; - } - } else { - if (top + size.height > boundaryRect.bottom) { - isBottom = true; - top = yPos - - pointerLength - - size.height - - (isTrackballMarkerEnabled - ? (trackballBehavior.markerSettings!.height) - : 0); - } else { - isBottom = false; - } - } - trackballTemplateRect = - Rect.fromLTWH(left, top, size.width, size.height); - double xPlotOffset = visiblePoints.first.closestPointX - - trackballTemplateRect!.width / 2; - final double rightTemplateEnd = - xPlotOffset + trackballTemplateRect!.width; - final double leftTemplateEnd = xPlotOffset; - - if (_isTemplateWithinBounds( - boundaryRect, trackballTemplateRect!, offset)) { - isTemplateInBounds = true; - childParentData.offset = Offset(left, top); - } else if (boundaryRect.width > trackballTemplateRect!.width && - boundaryRect.height > trackballTemplateRect!.height) { - isTemplateInBounds = true; - if (rightTemplateEnd > boundaryRect.right) { - xPlotOffset = - xPlotOffset - (rightTemplateEnd - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (totalWidth + - trackballTemplateRect!.width - - boundaryRect.right); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset > boundaryRect.right) { - isTemplateInBounds = false; - } - } - } else if (leftTemplateEnd < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - leftTemplateEnd); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (totalWidth + - trackballTemplateRect!.width - - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - isTemplateInBounds = false; - } - } - } - childParentData.offset = Offset(xPlotOffset, yPos); - } else { - child!.layout(constraints.copyWith(maxWidth: 0), - parentUsesSize: true); - isTemplateInBounds = false; - } - } else { - if (xPos + size.width > totalWidth) { - xPos = xPos - - size.width - - 2 * padding - - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0); - } - - trackballTemplateRect = - Rect.fromLTWH(xPos, yPos, size.width, size.height); - double xPlotOffset = visiblePoints.first.closestPointX - - trackballTemplateRect!.width / 2; - final double rightTemplateEnd = - xPlotOffset + trackballTemplateRect!.width; - final double leftTemplateEnd = xPlotOffset; - - if (_isTemplateWithinBounds( - boundaryRect, trackballTemplateRect!, offset) && - (boundaryRect.right > trackballTemplateRect!.right && - boundaryRect.left < trackballTemplateRect!.left)) { - isTemplateInBounds = true; - childParentData.offset = Offset( - xPos + - (trackballTemplateRect!.right + padding > boundaryRect.right - ? trackballTemplateRect!.right + - padding - - boundaryRect.right - : padding) + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0), - yPos); - } else if (boundaryRect.width > trackballTemplateRect!.width && - boundaryRect.height > trackballTemplateRect!.height) { - isTemplateInBounds = true; - if (rightTemplateEnd > boundaryRect.right) { - xPlotOffset = - xPlotOffset - (rightTemplateEnd - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (totalWidth + - trackballTemplateRect!.width - - boundaryRect.right); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset > boundaryRect.right) { - isTemplateInBounds = false; - } - } - } else if (leftTemplateEnd < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - leftTemplateEnd); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (xPlotOffset + - trackballTemplateRect!.width - - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset > boundaryRect.right) { - isTemplateInBounds = false; - } - } - } - childParentData.offset = Offset(xPlotOffset, yPos); - } else { - child!.layout(constraints.copyWith(maxWidth: 0), - parentUsesSize: true); - isTemplateInBounds = false; - } - } - } - } else { - size = Size.zero; - } - if (!isGroupAllPoints && index == chartPointInfo!.length - 1) { - tooltipTop?.clear(); - tooltipBottom.clear(); - yAxesInfo.clear(); - xAxesInfo.clear(); - } - } - - /// To check template is within bounds. - bool _isTemplateWithinBounds(Rect bounds, Rect templateRect, Offset? offset) { - final Rect rect = Rect.fromLTWH( - padding + templateRect.left, - (3 * padding) + templateRect.top, - templateRect.width, - templateRect.height); - final Rect axisBounds = Rect.fromLTWH(padding + bounds.left, - (3 * padding) + bounds.top, bounds.width, bounds.height); - return rect.left >= axisBounds.left && - rect.left + rect.width <= axisBounds.left + axisBounds.width && - rect.top >= axisBounds.top && - rect.bottom <= axisBounds.top + axisBounds.height; - } - - @override - void paint(PaintingContext context, Offset offset) { - final bool isTemplateWithInBoundsInTransposedChart = - _isTemplateWithinBounds(boundaryRect, trackballTemplateRect!, offset); - if ((!stateProperties.requireInvertedAxis && isTemplateInBounds) || - (stateProperties.requireInvertedAxis && - isTemplateWithInBoundsInTransposedChart)) { - super.paint(context, offset); - } - - final RenderingDetails renderingDetails = stateProperties.renderingDetails; - if (!isGroupAllPoints) { - final Rect templateRect = Rect.fromLTWH( - offset.dx + trackballTemplateRect!.left, - offset.dy + trackballTemplateRect!.top, - trackballTemplateRect!.width, - trackballTemplateRect!.height); - final Paint fillPaint = Paint() - ..color = trackballBehavior.tooltipSettings.color ?? - (chartPointInfo![index!].seriesRendererDetails!.series.color ?? - renderingDetails.chartTheme.crosshairBackgroundColor) - ..isAntiAlias = false - ..style = PaintingStyle.fill; - final Paint strokePaint = Paint() - ..color = trackballBehavior.tooltipSettings.borderColor ?? - (chartPointInfo![index!].seriesRendererDetails!.series.color ?? - renderingDetails.chartTheme.crosshairBackgroundColor) - ..strokeWidth = trackballBehavior.tooltipSettings.borderWidth - ..strokeCap = StrokeCap.butt - ..isAntiAlias = false - ..style = PaintingStyle.stroke; - final Path path = Path(); - if (trackballTemplateRect!.left > boundaryRect.left && - trackballTemplateRect!.right < boundaryRect.right) { - if (!stateProperties.requireInvertedAxis) { - if (!isRight) { - path.moveTo(templateRect.left, - templateRect.top + templateRect.height / 2 - pointerWidth); - path.lineTo(templateRect.left, - templateRect.bottom - templateRect.height / 2 + pointerWidth); - path.lineTo(templateRect.left - pointerLength, yPos + offset.dy); - path.lineTo(templateRect.left, - templateRect.top + templateRect.height / 2 - pointerWidth); - } else { - path.moveTo(templateRect.right, - templateRect.top + templateRect.height / 2 - pointerWidth); - path.lineTo(templateRect.right, - templateRect.bottom - templateRect.height / 2 + pointerWidth); - path.lineTo(templateRect.right + pointerLength, yPos + offset.dy); - path.lineTo(templateRect.right, - templateRect.top + templateRect.height / 2 - pointerWidth); - } - } else if (isTemplateInBounds && - isTemplateWithInBoundsInTransposedChart) { - if (!isBottom) { - path.moveTo( - templateRect.left + templateRect.width / 2 + pointerWidth, - templateRect.top); - path.lineTo( - templateRect.right - templateRect.width / 2 - pointerWidth, - templateRect.top); - path.lineTo(xPos + offset.dx, yPos + offset.dy); - } else { - path.moveTo( - templateRect.left + templateRect.width / 2 + pointerWidth, - templateRect.bottom); - path.lineTo( - templateRect.right - templateRect.width / 2 - pointerWidth, - templateRect.bottom); - path.lineTo(xPos + offset.dx, yPos + offset.dy); - } - } - - if (isTemplateInBounds) { - context.canvas.drawPath(path, fillPaint); - context.canvas.drawPath(path, strokePaint); - } - } - } - } -} - -/// Class to store about the details of the closest points. -class ClosestPoints { - /// Creates the parameterized constructor for class ClosestPoints. - const ClosestPoints( - {required this.closestPointX, required this.closestPointY}); - - /// Holds the closest x point value. - final double closestPointX; - - /// Holds the closest y point value. - final double closestPointY; -} - -/// Class to store trackball tooltip start and end positions. -class TooltipPositions { - /// Creates the parameterized constructor for the class TooltipPositions. - const TooltipPositions(this.tooltipTop, this.tooltipBottom); - - /// Specifies the tooltip top value. - final List tooltipTop; - - /// Specifies the tooltip bottom value. - 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); - - /// Specifies the trackball label value. - final String label; - - /// Specifies the value of cartesian series renderer. - final CartesianSeriesRenderer? seriesRenderer; - - /// Specifies whether to render the trackball element. - bool needRender = true; -} 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 deleted file mode 100644 index 890a14e00..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart +++ /dev/null @@ -1,410 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../common/rendering_details.dart'; -import '../../common/utils/helper.dart'; -import '../axis/axis.dart'; -import '../base/chart_base.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/interactive_tooltip.dart'; -import '../utils/helper.dart'; - -/// Class for drawing zooming rectangle. -class ZoomRectPainter extends CustomPainter { - /// Creates an instance for zoom rect painter. - ZoomRectPainter( - {this.isRepaint = true, - required this.stateProperties, - ValueNotifier? notifier}) - : chart = stateProperties.chart, - super(repaint: notifier); - - /// Specifies whether to repaint the zoom rect. - final bool isRepaint; - - /// Holds the value of chart. - final SfCartesianChart chart; - - /// Specifies the cartesian state properties. - CartesianStateProperties stateProperties; - - /// Specifies the value of stroke paint and fill paint. - late Paint strokePaint, fillPaint; - - /// Gets the value of rendering details. - RenderingDetails get renderingDetails => stateProperties.renderingDetails; - - @override - void paint(Canvas canvas, Size size) => - stateProperties.zoomPanBehaviorRenderer.onPaint(canvas); - - /// To draw zooming rectangle. - void drawRect(Canvas canvas) { - final Color? fillColor = chart.zoomPanBehavior.selectionRectColor; - strokePaint = Paint() - ..color = chart.zoomPanBehavior.selectionRectBorderColor ?? - renderingDetails.chartTheme.selectionRectBorderColor - ..strokeWidth = chart.zoomPanBehavior.selectionRectBorderWidth - ..style = PaintingStyle.stroke; - chart.zoomPanBehavior.selectionRectBorderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color = strokePaint.color; - fillPaint = Paint() - ..color = fillColor != null - ? Color.fromRGBO(fillColor.red, fillColor.green, fillColor.blue, 0.3) - : renderingDetails.chartTheme.selectionRectColor - ..style = PaintingStyle.fill; - strokePaint.isAntiAlias = false; - final ZoomingBehaviorDetails zoomingBehaviorDetails = - ZoomPanBehaviorHelper.getRenderingDetails( - stateProperties.zoomPanBehaviorRenderer); - if (zoomingBehaviorDetails.rectPath != null) { - canvas.drawPath( - dashPath( - zoomingBehaviorDetails.rectPath!, - dashArray: CircularIntervalList([5, 5]), - )!, - strokePaint); - canvas.drawRect(zoomingBehaviorDetails.zoomingRect, fillPaint); - final Rect zoomRect = zoomingBehaviorDetails.zoomingRect; - - /// To show the interactive tooltip on selection zooming - if (zoomRect.width != 0) { - _drawConnectorLine( - canvas, - Offset(zoomRect.bottomRight.dx, zoomRect.bottomRight.dy), - Offset(zoomRect.topLeft.dx, zoomRect.topLeft.dy)); - } - } - } - - /// To draw connector line. - void _drawConnectorLine(Canvas canvas, Offset start, Offset end) { - _drawAxisTooltip(stateProperties.chartAxis.bottomAxisRenderers, canvas, - start, end, 'bottom'); - _drawAxisTooltip( - stateProperties.chartAxis.topAxisRenderers, canvas, start, end, 'top'); - _drawAxisTooltip(stateProperties.chartAxis.leftAxisRenderers, canvas, start, - end, 'left'); - _drawAxisTooltip(stateProperties.chartAxis.rightAxisRenderers, canvas, - start, end, 'right'); - } - - /// Draw axis tootip connector line. - void _drawAxisTooltip(List axisRenderers, Canvas canvas, - Offset startPosition, Offset endPosition, String axisPosition) { - for (int index = 0; index < axisRenderers.length; index++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderers[index]); - if (axisDetails.axis.interactiveTooltip.enable && - axisDetails.visibleLabels.isNotEmpty) { - _drawTooltipConnector( - axisDetails, startPosition, endPosition, canvas, axisPosition); - } - } - } - - /// Returns the tooltip label on zooming. - String _getValue(Offset position, - ChartAxisRendererDetails axisRendererDetails, String axisPosition) { - final ChartAxis axis = axisRendererDetails.axis; - final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; - final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; - final num value = isHorizontal - ? pointToXVal( - chart, - axisRendererDetails.axisRenderer, - axisRendererDetails.bounds, - position.dx - (axisClipRect.left + axis.plotOffset), - position.dy - (axisClipRect.top + axis.plotOffset)) - : pointToYVal( - chart, - axisRendererDetails.axisRenderer, - axisRendererDetails.bounds, - position.dx - (axisClipRect.left + axis.plotOffset), - position.dy - (axisClipRect.top + axis.plotOffset)); - - dynamic result = - getInteractiveTooltipLabel(value, axisRendererDetails.axisRenderer); - if (axis.interactiveTooltip.format != null) { - final String stringValue = - axis.interactiveTooltip.format!.replaceAll('{value}', result); - result = stringValue; - } - return result.toString(); - } - - /// Validate the rect by comparing small and large rect. - Rect _validateRect(Rect largeRect, Rect smallRect, String axisPosition) => - Rect.fromLTRB( - axisPosition == 'left' - ? (smallRect.left - (largeRect.width - smallRect.width)) - : smallRect.left, - smallRect.top, - axisPosition == 'right' - ? (smallRect.right + (largeRect.width - smallRect.width)) - : smallRect.right, - smallRect.bottom); - - /// Calculate the rect, based on the zoomed axis position. - Rect _calculateRect(ChartAxisRendererDetails axisRendererDetails, - Offset position, Size labelSize, String axisPosition) { - Rect rect; - const double paddingForRect = 10; - final double arrowLength = - axisRendererDetails.axis.interactiveTooltip.arrowLength; - if (axisPosition == 'bottom') { - rect = Rect.fromLTWH( - position.dx - (labelSize.width / 2 + paddingForRect / 2), - axisRendererDetails.bounds.top + arrowLength, - labelSize.width + paddingForRect, - labelSize.height + paddingForRect); - } else if (axisPosition == 'top') { - rect = Rect.fromLTWH( - position.dx - (labelSize.width / 2 + paddingForRect / 2), - axisRendererDetails.bounds.top - - (labelSize.height + paddingForRect) - - arrowLength, - labelSize.width + paddingForRect, - labelSize.height + paddingForRect); - } else if (axisPosition == 'left') { - rect = Rect.fromLTWH( - axisRendererDetails.bounds.left - - (labelSize.width + paddingForRect) - - arrowLength, - position.dy - (labelSize.height + paddingForRect) / 2, - labelSize.width + paddingForRect, - labelSize.height + paddingForRect); - } else { - rect = Rect.fromLTWH( - axisRendererDetails.bounds.left + arrowLength, - position.dy - (labelSize.height / 2 + paddingForRect / 2), - labelSize.width + paddingForRect, - labelSize.height + paddingForRect); - } - return rect; - } - - /// To draw tooltip connector. - void _drawTooltipConnector( - ChartAxisRendererDetails axisRendererDetails, - Offset startPosition, - Offset endPosition, - Canvas canvas, - String axisPosition) { - RRect? startTooltipRect, endTooltipRect; - String startValue, endValue; - Size startLabelSize, endLabelSize; - Rect startLabelRect, endLabelRect; - final ChartAxis axis = axisRendererDetails.axis; - final Paint labelFillPaint = Paint() - ..color = renderingDetails.chartTheme.crosshairBackgroundColor - ..strokeCap = StrokeCap.butt - ..isAntiAlias = false - ..style = PaintingStyle.fill; - - final Paint labelStrokePaint = Paint() - ..color = renderingDetails.chartTheme.crosshairBackgroundColor - ..strokeCap = StrokeCap.butt - ..isAntiAlias = false - ..style = PaintingStyle.stroke; - - final Paint connectorLinePaint = Paint() - ..color = axis.interactiveTooltip.connectorLineColor ?? - renderingDetails.chartTheme.selectionTooltipConnectorLineColor - ..strokeWidth = axis.interactiveTooltip.connectorLineWidth - ..style = PaintingStyle.stroke; - - chart.crosshairBehavior.lineWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color = strokePaint.color; - - final Path startLabelPath = Path(); - final Path endLabelPath = Path(); - final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; - startValue = _getValue(startPosition, axisRendererDetails, axisPosition); - endValue = _getValue(endPosition, axisRendererDetails, axisPosition); - startLabelSize = measureText(startValue, axis.interactiveTooltip.textStyle); - endLabelSize = measureText(endValue, axis.interactiveTooltip.textStyle); - startLabelRect = _calculateRect( - axisRendererDetails, startPosition, startLabelSize, axisPosition); - endLabelRect = _calculateRect( - axisRendererDetails, endPosition, endLabelSize, axisPosition); - if (!isHorizontal && startLabelRect.width != endLabelRect.width) { - (startLabelRect.width > endLabelRect.width) - ? endLabelRect = - _validateRect(startLabelRect, endLabelRect, axisPosition) - : startLabelRect = - _validateRect(endLabelRect, startLabelRect, axisPosition); - } - startTooltipRect = _drawTooltip( - canvas, - labelFillPaint, - labelStrokePaint, - startLabelPath, - startPosition, - startLabelRect, - startTooltipRect, - startValue, - startLabelSize, - axis.interactiveTooltip, - axisPosition); - endTooltipRect = _drawTooltip( - canvas, - labelFillPaint, - labelStrokePaint, - endLabelPath, - endPosition, - endLabelRect, - endTooltipRect, - endValue, - endLabelSize, - axis.interactiveTooltip, - axisPosition); - _drawConnector(canvas, connectorLinePaint, startTooltipRect, endTooltipRect, - startPosition, endPosition, axis.interactiveTooltip, axisPosition); - } - - /// To draw connectors. - void _drawConnector( - Canvas canvas, - Paint connectorLinePaint, - RRect startTooltipRect, - RRect endTooltipRect, - Offset startPosition, - Offset endPosition, - InteractiveTooltip tooltip, - String axisPosition) { - final Path connectorPath = Path(); - if (axisPosition == 'bottom') { - connectorPath.moveTo( - startPosition.dx, startTooltipRect.top - tooltip.arrowLength); - connectorPath.lineTo( - endPosition.dx, endTooltipRect.top - tooltip.arrowLength); - } else if (axisPosition == 'top') { - connectorPath.moveTo( - startPosition.dx, startTooltipRect.bottom + tooltip.arrowLength); - connectorPath.lineTo( - endPosition.dx, endTooltipRect.bottom + tooltip.arrowLength); - } else if (axisPosition == 'left') { - connectorPath.moveTo( - startTooltipRect.right + tooltip.arrowLength, startPosition.dy); - connectorPath.lineTo( - endTooltipRect.right + tooltip.arrowLength, endPosition.dy); - } else { - connectorPath.moveTo( - startTooltipRect.left - tooltip.arrowLength, startPosition.dy); - connectorPath.lineTo( - endTooltipRect.left - tooltip.arrowLength, endPosition.dy); - } - tooltip.connectorLineDashArray != null - ? canvas.drawPath( - dashPath(connectorPath, - dashArray: CircularIntervalList( - tooltip.connectorLineDashArray!))!, - connectorLinePaint) - : canvas.drawPath(connectorPath, connectorLinePaint); - } - - /// To draw tooltip. - RRect _drawTooltip( - Canvas canvas, - Paint fillPaint, - Paint strokePaint, - Path path, - Offset position, - Rect labelRect, - RRect? rect, - String value, - Size labelSize, - InteractiveTooltip tooltip, - String axisPosition) { - fillPaint.color = - tooltip.color ?? renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.color = tooltip.borderColor ?? - renderingDetails.chartTheme.crosshairBackgroundColor; - strokePaint.strokeWidth = tooltip.borderWidth; - - final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; - labelRect = - validateRectBounds(labelRect, renderingDetails.chartContainerRect); - labelRect = isHorizontal - ? validateRectXPosition(labelRect, stateProperties) - : validateRectYPosition(labelRect, stateProperties); - path.reset(); - rect = getRoundedCornerRect(labelRect, tooltip.borderRadius); - path.addRRect(rect); - _calculateNeckPositions(canvas, fillPaint, strokePaint, path, axisPosition, - position, rect, tooltip); - drawText( - canvas, - value, - Offset((rect.left + rect.width / 2) - labelSize.width / 2, - (rect.top + rect.height / 2) - labelSize.height / 2), - TextStyle( - color: tooltip.textStyle.color ?? - renderingDetails.chartTheme.tooltipLabelColor, - fontSize: tooltip.textStyle.fontSize, - fontWeight: tooltip.textStyle.fontWeight, - fontFamily: tooltip.textStyle.fontFamily, - fontStyle: tooltip.textStyle.fontStyle), - 0); - return rect; - } - - /// To calculate tootip neck positions. - void _calculateNeckPositions( - Canvas canvas, - Paint fillPaint, - Paint strokePaint, - Path path, - String axisPosition, - Offset position, - RRect rect, - InteractiveTooltip tooltip) { - double x1, x2, x3, x4, y1, y2, y3, y4; - if (axisPosition == 'bottom') { - x1 = position.dx; - y1 = rect.top - tooltip.arrowLength; - x2 = (rect.right - rect.width / 2) + tooltip.arrowWidth; - y2 = rect.top; - x3 = (rect.left + rect.width / 2) - tooltip.arrowWidth; - y3 = rect.top; - x4 = position.dx; - y4 = rect.top - tooltip.arrowLength; - } else if (axisPosition == 'top') { - x1 = position.dx; - y1 = rect.bottom + tooltip.arrowLength; - x2 = (rect.right - rect.width / 2) + tooltip.arrowWidth; - y2 = rect.bottom; - x3 = (rect.left + rect.width / 2) - tooltip.arrowWidth; - y3 = rect.bottom; - x4 = position.dx; - y4 = rect.bottom + tooltip.arrowLength; - } else if (axisPosition == 'left') { - x1 = rect.right; - y1 = rect.top + rect.height / 2 - tooltip.arrowWidth; - x2 = rect.right; - y2 = rect.bottom - rect.height / 2 + tooltip.arrowWidth; - x3 = rect.right + tooltip.arrowLength; - y3 = position.dy; - x4 = rect.right + tooltip.arrowLength; - y4 = position.dy; - } else { - x1 = rect.left; - y1 = rect.top + rect.height / 2 - tooltip.arrowWidth; - x2 = rect.left; - y2 = rect.bottom - rect.height / 2 + tooltip.arrowWidth; - x3 = rect.left - tooltip.arrowLength; - y3 = position.dy; - x4 = rect.left - tooltip.arrowLength; - y4 = position.dy; - } - drawTooltipArrowhead( - canvas, path, fillPaint, strokePaint, x1, y1, x2, y2, x3, y3, x4, y4); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index f9374b45d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart +++ /dev/null @@ -1,1144 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import '../axis/axis.dart'; -import '../base/chart_base.dart'; -import '../chart_behavior/zoom_behavior.dart'; -import '../common/cartesian_state_properties.dart'; -import '../common/common.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'zooming_painter.dart'; - -/// Customizes the zooming options. -/// -/// Customize the various zooming actions such as tap zooming, selection zooming, -/// zoom pinch. In selection zooming, you can long-press and drag to select a -/// range on the chart to be zoomed in and also you can customize the selection -/// zooming rectangle using `selectionRectBorderWidth`, -/// `selectionRectBorderColor` and `selectionRectColor` properties. -/// -/// Pinch zooming can be performed by moving two fingers over the chart. -/// Default mode is [ZoomMode.xy]. Zooming will be stopped after reaching -/// `maximumZoomLevel`. -/// -/// _Note:_ This is only applicable for `SfCartesianChart`. -class ZoomPanBehavior { - /// Creating an argument constructor of ZoomPanBehavior class. - ZoomPanBehavior( - {this.enablePinching = false, - this.enableDoubleTapZooming = false, - this.enablePanning = false, - this.enableSelectionZooming = false, - this.enableMouseWheelZooming = false, - this.zoomMode = ZoomMode.xy, - this.maximumZoomLevel = 0.01, - this.selectionRectBorderWidth = 1, - this.selectionRectBorderColor, - this.selectionRectColor}); - - /// Enables or disables the pinch zooming. - /// - /// Pinching can be performed by moving two fingers over the - /// chart. You can zoom the chart through pinch gesture in touch enabled devices. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enablePinching: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enablePinching; - - /// Enables or disables the double tap zooming. - /// - /// Zooming will enable when you tap double time in plotarea. - /// After reaching the maximum zoom level, zooming will be stopped. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableDoubleTapZooming: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enableDoubleTapZooming; - - /// Enables or disables the panning. - /// - /// Panning can be performed on a zoomed axis. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enablePanning: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enablePanning; - - /// Enables or disables the selection zooming. - /// - /// Selection zooming can be performed by long-press and then dragging. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enableSelectionZooming; - - /// Enables or disables the mouseWheelZooming. - /// - /// Mouse wheel zooming can be performed by rolling the mouse wheel up or - /// down. The place where the cursor is hovering gets zoomed in or out - /// according to the mouse wheel rolling up or down. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableMouseWheelZooming: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enableMouseWheelZooming; - - /// By default, both the x and y-axes in the chart can be zoomed. - /// - /// It can be changed by setting value to this property. - /// - /// Defaults to `ZoomMode.xy`. - /// - /// Also refer [ZoomMode]. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// zoomMode: ZoomMode.x - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final ZoomMode zoomMode; - - /// Maximum zoom level. - /// - /// Zooming will be stopped after reached this value and ranges from 0 to 1. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// maximumZoomLevel: 0.8 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final double maximumZoomLevel; - - /// Border width of the selection zooming rectangle. - /// - /// Used to change the stroke width of the selection rectangle. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true, - /// selectionRectBorderWidth: 2 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final double selectionRectBorderWidth; - - /// Border color of the selection zooming rectangle. - /// - /// It used to change the stroke color of the selection rectangle. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true, - /// selectionRectBorderColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final Color? selectionRectBorderColor; - - /// Color of the selection zooming rectangle. - /// - /// It used to change the background color of the selection rectangle. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true, - /// selectionRectColor: Colors.yellow - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final Color? selectionRectColor; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ZoomPanBehavior && - other.enablePinching == enablePinching && - other.enableDoubleTapZooming == enableDoubleTapZooming && - other.enablePanning == enablePanning && - other.enableSelectionZooming == enableSelectionZooming && - other.enableMouseWheelZooming == enableMouseWheelZooming && - other.zoomMode == zoomMode && - other.maximumZoomLevel == maximumZoomLevel && - other.selectionRectBorderWidth == selectionRectBorderWidth && - other.selectionRectBorderColor == selectionRectBorderColor && - other.selectionRectColor == selectionRectColor; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - enablePinching, - enableDoubleTapZooming, - enablePanning, - enableSelectionZooming, - enableMouseWheelZooming, - zoomMode, - maximumZoomLevel, - selectionRectBorderWidth, - selectionRectBorderColor, - selectionRectColor - ]; - return hashList(values); - } - - /// Holds the value of cartesian state properties. - late CartesianStateProperties _stateProperties; - - /// Increases the magnification of the plot area. - void zoomIn() { - _stateProperties.canSetRangeController = true; - final SfCartesianChart chart = _stateProperties.chart; - final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = - _stateProperties.zoomPanBehaviorRenderer; - zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomIn = true; - zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomOut = false; - final double? zoomFactor = - zoomPanBehaviorRenderer._zoomingBehaviorDetails.zoomFactor; - _stateProperties.zoomProgress = true; - ChartAxisRendererDetails axisDetails; - bool? needZoom; - for (int index = 0; - index < _stateProperties.chartAxis.axisRenderersCollection.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[index]); - if ((axisDetails.orientation == AxisOrientation.vertical && - chart.zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisDetails.orientation == AxisOrientation.horizontal && - chart.zoomPanBehavior.zoomMode != ZoomMode.y)) { - if (axisDetails.zoomFactor <= 1.0 && axisDetails.zoomFactor > 0.0) { - if (axisDetails.zoomFactor - 0.1 < 0) { - needZoom = false; - break; - } else { - zoomPanBehaviorRenderer._zoomingBehaviorDetails - .setZoomFactorAndZoomPosition( - _stateProperties.chartState, axisDetails, zoomFactor); - needZoom = true; - } - } - if (chart.onZooming != null) { - bindZoomEvent(chart, axisDetails, chart.onZooming!); - } - } - } - if (needZoom == true) { - zoomPanBehaviorRenderer._zoomingBehaviorDetails.createZoomState(); - } - } - - /// Decreases the magnification of the plot area. - void zoomOut() { - _stateProperties.canSetRangeController = true; - final SfCartesianChart chart = _stateProperties.chart; - final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = - _stateProperties.zoomPanBehaviorRenderer; - zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomOut = true; - zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomIn = false; - final double? zoomFactor = - zoomPanBehaviorRenderer._zoomingBehaviorDetails.zoomFactor; - ChartAxisRendererDetails axisDetails; - for (int index = 0; - index < _stateProperties.chartAxis.axisRenderersCollection.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[index]); - if ((axisDetails.orientation == AxisOrientation.vertical && - chart.zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisDetails.orientation == AxisOrientation.horizontal && - chart.zoomPanBehavior.zoomMode != ZoomMode.y)) { - if (axisDetails.zoomFactor < 1.0 && axisDetails.zoomFactor > 0.0) { - zoomPanBehaviorRenderer._zoomingBehaviorDetails - .setZoomFactorAndZoomPosition( - _stateProperties.chartState, axisDetails, zoomFactor); - axisDetails.zoomFactor = axisDetails.zoomFactor > 1.0 - ? 1.0 - : (axisDetails.zoomFactor < 0.0 ? 0.0 : axisDetails.zoomFactor); - } - if (chart.onZooming != null) { - bindZoomEvent(chart, axisDetails, chart.onZooming!); - } - } - } - zoomPanBehaviorRenderer._zoomingBehaviorDetails.createZoomState(); - } - - /// Changes the zoom level using zoom factor. - /// - /// 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) { - _stateProperties.canSetRangeController = true; - final SfCartesianChart chart = _stateProperties.chart; - ChartAxisRendererDetails axisDetails; - for (int index = 0; - index < _stateProperties.chartAxis.axisRenderersCollection.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[index]); - axisDetails.zoomFactor = zoomFactor; - if (chart.onZooming != null) { - bindZoomEvent(chart, axisDetails, chart.onZooming!); - } - _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails - .createZoomState(); - } - } - - /// Zooms the chart for a given rectangle value. - /// - /// 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) { - _stateProperties.canSetRangeController = true; - _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails - .doSelectionZooming(rect); - } - - /// Change the zoom level of an appropriate axis. - /// - /// 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) { - _stateProperties.canSetRangeController = true; - final ChartAxisRenderer? axisRenderer = findExistingAxisRenderer( - axis, _stateProperties.chartAxis.axisRenderersCollection); - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer!); - // ignore: unnecessary_null_comparison - if (axisRenderer != null) { - axisDetails.zoomFactor = zoomFactor; - axisDetails.zoomPosition = zoomPosition; - _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails - .createZoomState(); - } - } - - /// Pans the plot area for given left, right, top, and bottom directions. - /// - /// To perform this action, the plot area needs to be in zoomed state. - void panToDirection(String direction) { - _stateProperties.canSetRangeController = true; - final SfCartesianChart chart = _stateProperties.chart; - ChartAxisRendererDetails axisDetails; - direction = direction.toLowerCase(); - for (int axisIndex = 0; - axisIndex < _stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - axisDetails = AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - if (axisDetails.orientation == AxisOrientation.horizontal) { - if (direction == 'left') { - axisDetails.zoomPosition = - (axisDetails.zoomPosition > 0 && axisDetails.zoomPosition <= 0.9) - ? axisDetails.zoomPosition - 0.1 - : axisDetails.zoomPosition; - axisDetails.zoomPosition = - axisDetails.zoomPosition < 0.0 ? 0.0 : axisDetails.zoomPosition; - } - if (direction == 'right') { - axisDetails.zoomPosition = - (axisDetails.zoomPosition >= 0 && axisDetails.zoomPosition < 1) - ? axisDetails.zoomPosition + 0.1 - : axisDetails.zoomPosition; - axisDetails.zoomPosition = - axisDetails.zoomPosition > 1.0 ? 1.0 : axisDetails.zoomPosition; - } - } else { - if (direction == 'bottom') { - axisDetails.zoomPosition = - (axisDetails.zoomPosition > 0 && axisDetails.zoomPosition <= 0.9) - ? axisDetails.zoomPosition - 0.1 - : axisDetails.zoomPosition; - axisDetails.zoomPosition = - axisDetails.zoomPosition < 0.0 ? 0.0 : axisDetails.zoomPosition; - } - if (direction == 'top') { - axisDetails.zoomPosition = - (axisDetails.zoomPosition >= 0 && axisDetails.zoomPosition < 1) - ? axisDetails.zoomPosition + 0.1 - : axisDetails.zoomPosition; - axisDetails.zoomPosition = - axisDetails.zoomPosition > 1.0 ? 1.0 : axisDetails.zoomPosition; - } - } - if (chart.onZooming != null) { - bindZoomEvent(chart, axisDetails, chart.onZooming!); - } - } - _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails - .createZoomState(); - } - - /// Returns the plot area back to its original position after zooming. - void reset() { - _stateProperties.canSetRangeController = true; - final SfCartesianChart chart = _stateProperties.chart; - ChartAxisRendererDetails axisDetails; - for (int index = 0; - index < _stateProperties.chartAxis.axisRenderersCollection.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[index]); - axisDetails.zoomFactor = 1.0; - axisDetails.zoomPosition = 0.0; - if (chart.onZoomReset != null) { - bindZoomEvent(chart, axisDetails, chart.onZoomReset!); - } - } - _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails - .createZoomState(); - } -} - -/// Creates a renderer class for [ZoomPanBehavior] class -class ZoomPanBehaviorRenderer with ZoomBehavior { - /// Creates an argument constructor for ZoomPanBehavior renderer class. - ZoomPanBehaviorRenderer(this._stateProperties) { - _zoomingBehaviorDetails = ZoomingBehaviorDetails(_stateProperties); - } - - /// Holds the value of zoom behavior details. - late ZoomingBehaviorDetails _zoomingBehaviorDetails; - final CartesianStateProperties _stateProperties; - - /// Performs panning action. - @override - void onPan(double xPos, double yPos) => - _zoomingBehaviorDetails.doPan(xPos, yPos); - - /// Performs the double-tap action. - @override - void onDoubleTap(double xPos, double yPos, double? zoomFactor) => - _zoomingBehaviorDetails.doubleTapZooming(xPos, yPos, zoomFactor); - - /// Draws selection zoomRect. - @override - void onPaint(Canvas canvas) => - _zoomingBehaviorDetails.painter.drawRect(canvas); - - /// Performs selection zooming. - @override - void onDrawSelectionZoomRect( - double currentX, double currentY, double startX, double startY) => - _zoomingBehaviorDetails.drawSelectionZoomRect( - currentX, currentY, startX, startY); - - /// Performs pinch start action. - @override - void onPinchStart(ChartAxis axis, double firstX, double firstY, - double secondX, double secondY, double scaleFactor) {} - - /// Performs pinch end action. - @override - void onPinchEnd(ChartAxis axis, double firstX, double firstY, double secondX, - double secondY, double scaleFactor) {} - - /// Performs pinch zooming. - @override - void onPinch(ChartAxisRendererDetails axisDetails, double position, - double scaleFactor) { - axisDetails.zoomFactor = scaleFactor; - axisDetails.zoomPosition = position; - } -} - -/// Represents the zoom axis range class. -class ZoomAxisRange { - /// Creates an instance of zoom axis range class. - ZoomAxisRange({this.actualMin, this.actualDelta, this.min, this.delta}); - - /// Holds the value of actual minimum, actual delta, minimum and delta value. - double? actualMin, actualDelta, min, delta; -} - -/// Represents the zooming behavior details class. -class ZoomingBehaviorDetails { - /// Creates an instance for zooming behavior details. - ZoomingBehaviorDetails(this.stateProperties); - - ZoomPanBehavior get _zoomPanBehavior => _chart.zoomPanBehavior; - SfCartesianChart get _chart => stateProperties.chart; - - /// Creates an instance of cartesian state properties. - final CartesianStateProperties stateProperties; - - /// Holds the value of zoom rect painter. - late ZoomRectPainter painter; - - /// Holds the previously moved position. - Offset? previousMovedPosition; - - /// Specifies whether the panning or pinching is done. - bool? isPanning, isPinching; - - /// Specifies whether to do perform selection. - bool canPerformSelection = false; - - /// Holds the value of zooming rect. - Rect zoomingRect = Rect.zero; - - /// Specifies whether to draw with delay. - bool delayRedraw = false; - - /// Specifies the value of zoom factor and zoom position. - double? zoomFactor, _zoomPosition; - - /// Specifies whether the zoom in or zoom out is performed. - late bool isZoomIn, isZoomOut; - - /// Specifies the value of rect path. - Path? rectPath; - - /// Below method for double tap zooming. - void doubleTapZooming(double xPos, double yPos, double? zoomFactor) { - stateProperties.canSetRangeController = true; - stateProperties.zoomProgress = true; - ChartAxisRendererDetails axisDetails; - double cumulative, origin, maxZoomFactor; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - if (_chart.onZoomStart != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomStart!); - } - axisDetails.previousZoomFactor = axisDetails.zoomFactor; - axisDetails.previousZoomPosition = axisDetails.zoomPosition; - if ((axisDetails.orientation == AxisOrientation.vertical && - _zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisDetails.orientation == AxisOrientation.horizontal && - _zoomPanBehavior.zoomMode != ZoomMode.y)) { - cumulative = math.max( - math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1) + (0.25), 1); - if (cumulative >= 1) { - origin = axisDetails.orientation == AxisOrientation.horizontal - ? xPos / stateProperties.chartAxis.axisClipRect.width - : 1 - (yPos / stateProperties.chartAxis.axisClipRect.height); - origin = origin > 1 - ? 1 - : origin < 0 - ? 0 - : origin; - zoomFactor = - cumulative == 1 ? 1 : _minMax(1 / cumulative, 0, 1).toDouble(); - _zoomPosition = (cumulative == 1) - ? 0 - : axisDetails.zoomPosition + - ((axisDetails.zoomFactor - zoomFactor) * origin); - if (axisDetails.zoomPosition != _zoomPosition || - axisDetails.zoomFactor != zoomFactor) { - zoomFactor = (_zoomPosition! + zoomFactor) > 1 - ? (1 - _zoomPosition!) - : zoomFactor; - } - - axisDetails.zoomPosition = _zoomPosition!; - axisDetails.zoomFactor = zoomFactor; - axisDetails.bounds = Rect.zero; - axisDetails.visibleLabels = []; - } - maxZoomFactor = _zoomPanBehavior.maximumZoomLevel; - if (zoomFactor! < maxZoomFactor) { - axisDetails.zoomFactor = maxZoomFactor; - axisDetails.zoomPosition = 0.0; - zoomFactor = maxZoomFactor; - } - } - - if (_chart.onZoomEnd != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomEnd!); - } - } - createZoomState(); - } - - /// Below method is for panning the zoomed chart. - void doPan(double xPos, double yPos) { - stateProperties.canSetRangeController = true; - num currentScale, value; - ChartAxisRendererDetails axisDetails; - double currentZoomPosition; - bool isNeedPan = false; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - axisDetails.previousZoomFactor = axisDetails.zoomFactor; - axisDetails.previousZoomPosition = axisDetails.zoomPosition; - if ((axisDetails.orientation == AxisOrientation.vertical && - _zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisDetails.orientation == AxisOrientation.horizontal && - _zoomPanBehavior.zoomMode != ZoomMode.y)) { - currentZoomPosition = axisDetails.zoomPosition; - currentScale = math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1); - if (axisDetails.orientation == AxisOrientation.horizontal) { - value = (previousMovedPosition!.dx - xPos) / - stateProperties.chartAxis.axisClipRect.width / - currentScale; - currentZoomPosition = _minMax( - axisDetails.axis.isInversed - ? axisDetails.zoomPosition - value - : axisDetails.zoomPosition + value, - 0, - 1 - axisDetails.zoomFactor) - .toDouble(); - axisDetails.zoomPosition = currentZoomPosition; - if (currentZoomPosition > 0.0 && !isNeedPan) { - isNeedPan = true; - } - } else { - value = (previousMovedPosition!.dy - yPos) / - stateProperties.chartAxis.axisClipRect.height / - currentScale; - currentZoomPosition = _minMax( - axisDetails.axis.isInversed - ? axisDetails.zoomPosition + value - : axisDetails.zoomPosition - value, - 0, - 1 - axisDetails.zoomFactor) - .toDouble(); - axisDetails.zoomPosition = currentZoomPosition; - if (currentZoomPosition > 0.0 && !isNeedPan) { - isNeedPan = true; - } - } - } - if (_chart.onZooming != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZooming!); - } - } - if (isNeedPan) { - createZoomState(); - } - } - - /// Below method for drawing selection rectangle. - void drawSelectionZoomRect( - double currentX, double currentY, double startX, double startY) { - stateProperties.canSetRangeController = true; - final Rect clipRect = stateProperties.chartAxis.axisClipRect; - final Offset startPosition = Offset( - (startX < clipRect.left) ? clipRect.left : startX, - (startY < clipRect.top) ? clipRect.top : startY); - final Offset currentMousePosition = Offset( - (currentX > clipRect.right) - ? clipRect.right - : ((currentX < clipRect.left) ? clipRect.left : currentX), - (currentY > clipRect.bottom) - ? clipRect.bottom - : ((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(); - } 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(); - } 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(); - } - zoomingRect = rectPath!.getBounds(); - stateProperties.repaintNotifiers['zoom']!.value++; - } - - /// Below method for zooming selected portion. - void doSelectionZooming(Rect zoomRect) { - stateProperties.canSetRangeController = true; - final Rect rect = stateProperties.chartAxis.axisClipRect; - ChartAxisRendererDetails axisDetails; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - if (_chart.onZoomStart != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomStart!); - } - axisDetails.previousZoomFactor = axisDetails.zoomFactor; - axisDetails.previousZoomPosition = axisDetails.zoomPosition; - if (axisDetails.orientation == AxisOrientation.horizontal) { - if (_zoomPanBehavior.zoomMode != ZoomMode.y) { - axisDetails.zoomPosition += - ((zoomRect.left - rect.left) / (rect.width)).abs() * - axisDetails.zoomFactor; - axisDetails.zoomFactor *= zoomRect.width / rect.width; - axisDetails.zoomFactor = - axisDetails.zoomFactor >= _zoomPanBehavior.maximumZoomLevel - ? axisDetails.zoomFactor - : _zoomPanBehavior.maximumZoomLevel; - } - } else { - if (_zoomPanBehavior.zoomMode != ZoomMode.x) { - axisDetails.zoomPosition += (1 - - ((zoomRect.height + (zoomRect.top - rect.top)) / - (rect.height)) - .abs()) * - axisDetails.zoomFactor; - axisDetails.zoomFactor *= zoomRect.height / rect.height; - - axisDetails.zoomFactor = - axisDetails.zoomFactor >= _zoomPanBehavior.maximumZoomLevel - ? axisDetails.zoomFactor - : _zoomPanBehavior.maximumZoomLevel; - } - } - if (_chart.onZoomEnd != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomEnd!); - } - } - - zoomRect = Rect.zero; - rectPath = Path(); - createZoomState(); - } - - /// Below method is for pinch zooming. - void performPinchZooming( - List touchStartList, List touchMoveList) { - stateProperties.canSetRangeController = true; - num touch0StartX, - touch0StartY, - touch1StartX, - touch1StartY, - touch0EndX, - touch0EndY, - touch1EndX, - touch1EndY; - if (!(zoomingRect.width > 0 && zoomingRect.height > 0)) { - _calculateZoomAxesRange(_chart); - delayRedraw = true; - final Rect clipRect = stateProperties.chartAxis.axisClipRect; - final Rect containerRect = - stateProperties.renderingDetails.chartContainerRect; - touch0StartX = touchStartList[0].position.dx - containerRect.left; - touch0StartY = touchStartList[0].position.dy - containerRect.top; - touch0EndX = touchMoveList[0].position.dx - containerRect.left; - touch0EndY = touchMoveList[0].position.dy - containerRect.top; - touch1StartX = touchStartList[1].position.dx - containerRect.left; - touch1StartY = touchStartList[1].position.dy - containerRect.top; - touch1EndX = touchMoveList[1].position.dx - containerRect.left; - touch1EndY = touchMoveList[1].position.dy - containerRect.top; - double scaleX, scaleY, clipX, clipY; - Rect pinchRect; - scaleX = - (touch0EndX - touch1EndX).abs() / (touch0StartX - touch1StartX).abs(); - scaleY = - (touch0EndY - touch1EndY).abs() / (touch0StartY - touch1StartY).abs(); - clipX = ((clipRect.left - touch0EndX) / scaleX) + - math.min(touch0StartX, touch1StartX); - clipY = ((clipRect.top - touch0EndY) / scaleY) + - math.min(touch0StartY, touch1StartY); - pinchRect = Rect.fromLTWH( - clipX, clipY, clipRect.width / scaleX, clipRect.height / scaleY); - _calculatePinchZoomFactor(_chart, pinchRect); - stateProperties.zoomProgress = true; - createZoomState(); - } - } - - /// To create zoomed states. - void createZoomState() { - stateProperties.rangeChangeBySlider = false; - stateProperties.isRedrawByZoomPan = true; - stateProperties.zoomedAxisRendererStates = []; - stateProperties.zoomedAxisRendererStates - .addAll(stateProperties.chartAxis.axisRenderersCollection); - stateProperties.renderingDetails.isLegendToggled = false; - stateProperties.redraw(); - } - - /// Below method is for pinch zooming. - void _calculatePinchZoomFactor(SfCartesianChart chart, Rect pinchRect) { - stateProperties.canSetRangeController = true; - final ZoomMode mode = _zoomPanBehavior.zoomMode; - num selectionMin, selectionMax, rangeMin, rangeMax, value, axisTrans; - double currentZoomPosition, currentZoomFactor; - double currentFactor, currentPosition, maxZoomFactor, minZoomFactor = 1.0; - final Rect clipRect = stateProperties.chartAxis.axisClipRect; - final List _zoomAxes = stateProperties.zoomAxes; - ChartAxisRendererDetails axisDetails; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - axisDetails.previousZoomFactor = axisDetails.zoomFactor; - axisDetails.previousZoomPosition = axisDetails.zoomPosition; - if ((axisDetails.orientation == AxisOrientation.horizontal && - mode != ZoomMode.y) || - (axisDetails.orientation == AxisOrientation.vertical && - mode != ZoomMode.x)) { - if (axisDetails.orientation == AxisOrientation.horizontal) { - value = pinchRect.left - clipRect.left; - axisTrans = clipRect.width / _zoomAxes[axisIndex].delta!; - rangeMin = value / axisTrans + _zoomAxes[axisIndex].min!; - value = pinchRect.left + pinchRect.width - clipRect.left; - rangeMax = value / axisTrans + _zoomAxes[axisIndex].min!; - } else { - value = pinchRect.top - clipRect.top; - axisTrans = clipRect.height / _zoomAxes[axisIndex].delta!; - rangeMin = (value * -1 + clipRect.height) / axisTrans + - _zoomAxes[axisIndex].min!; - value = pinchRect.top + pinchRect.height - clipRect.top; - rangeMax = (value * -1 + clipRect.height) / axisTrans + - _zoomAxes[axisIndex].min!; - } - selectionMin = math.min(rangeMin, rangeMax); - selectionMax = math.max(rangeMin, rangeMax); - currentPosition = (selectionMin - _zoomAxes[axisIndex].actualMin!) / - _zoomAxes[axisIndex].actualDelta!; - currentFactor = - (selectionMax - selectionMin) / _zoomAxes[axisIndex].actualDelta!; - currentZoomPosition = currentPosition < 0 ? 0 : currentPosition; - currentZoomFactor = currentFactor > 1 ? 1 : currentFactor; - maxZoomFactor = _zoomPanBehavior.maximumZoomLevel; - if (axisDetails.axis.autoScrollingDelta != null && - axisDetails.scrollingDelta != null) { - // To find zoom factor for corresponding auto scroll delta. - minZoomFactor = - axisDetails.scrollingDelta! / _zoomAxes[axisIndex].actualDelta!; - } - if (currentZoomFactor < maxZoomFactor) { - axisDetails.zoomFactor = maxZoomFactor; - currentZoomFactor = maxZoomFactor; - } - if (currentZoomFactor > minZoomFactor) { - // To restrict zoom factor to corresponding auto scroll delta. - axisDetails.zoomFactor = minZoomFactor; - currentZoomFactor = minZoomFactor; - } - stateProperties.zoomPanBehaviorRenderer - .onPinch(axisDetails, currentZoomPosition, currentZoomFactor); - } - if (chart.onZooming != null) { - bindZoomEvent(chart, axisDetails, chart.onZooming!); - } - } - } - - num _minMax(num value, num min, num max) => - value > max ? max : (value < min ? min : value); - - /// Below method is for storing calculated zoom range. - void _calculateZoomAxesRange(SfCartesianChart chart) { - ChartAxisRendererDetails axisDetails; - ZoomAxisRange range; - VisibleRange? axisRange; - for (int index = 0; - index < stateProperties.chartAxis.axisRenderersCollection.length; - index++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[index]); - range = ZoomAxisRange(); - if (axisDetails.visibleRange != null) { - axisRange = axisDetails.visibleRange!; - } - if (stateProperties.zoomAxes.isNotEmpty && - index <= stateProperties.zoomAxes.length - 1) { - if (!delayRedraw) { - stateProperties.zoomAxes[index].min = axisRange!.minimum.toDouble(); - stateProperties.zoomAxes[index].delta = axisRange.delta.toDouble(); - } - } else { - if (axisDetails.actualRange != null) { - range.actualMin = axisDetails.actualRange!.minimum.toDouble(); - range.actualDelta = axisDetails.actualRange!.delta.toDouble(); - } - range.min = axisRange!.minimum.toDouble(); - range.delta = axisRange.delta.toDouble(); - stateProperties.zoomAxes.add(range); - } - } - } - - /// Below method is for mouse wheel Zooming. - void performMouseWheelZooming( - PointerScrollEvent event, double mouseX, double mouseY) { - stateProperties.canSetRangeController = true; - final double direction = (event.scrollDelta.dy / 120) > 0 ? -1 : 1; - double origin = 0.5; - double cumulative, zoomFactor, zoomPosition, maxZoomFactor; - stateProperties.zoomProgress = true; - _calculateZoomAxesRange(_chart); - isPanning = _chart.zoomPanBehavior.enablePanning; - ChartAxisRendererDetails axisDetails; - for (int axisIndex = 0; - axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; - axisIndex++) { - axisDetails = AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[axisIndex]); - if (_chart.onZoomStart != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomStart!); - } - axisDetails.previousZoomFactor = axisDetails.zoomFactor; - axisDetails.previousZoomPosition = axisDetails.zoomPosition; - if ((axisDetails.orientation == AxisOrientation.vertical && - _zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisDetails.orientation == AxisOrientation.horizontal && - _zoomPanBehavior.zoomMode != ZoomMode.y)) { - cumulative = math.max( - math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1) + - (0.25 * direction), - 1); - if (cumulative >= 1) { - origin = axisDetails.orientation == AxisOrientation.horizontal - ? mouseX / stateProperties.chartAxis.axisClipRect.width - : 1 - (mouseY / stateProperties.chartAxis.axisClipRect.height); - origin = origin > 1 - ? 1 - : origin < 0 - ? 0 - : origin; - zoomFactor = ((cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1)) - .toDouble(); - zoomPosition = (cumulative == 1) - ? 0 - : axisDetails.zoomPosition + - ((axisDetails.zoomFactor - zoomFactor) * origin); - if (axisDetails.zoomPosition != zoomPosition || - axisDetails.zoomFactor != zoomFactor) { - zoomFactor = (zoomPosition + zoomFactor) > 1 - ? (1 - zoomPosition) - : zoomFactor; - } - axisDetails.zoomPosition = zoomPosition; - axisDetails.zoomFactor = zoomFactor; - axisDetails.bounds = Rect.zero; - axisDetails.visibleLabels = []; - maxZoomFactor = _zoomPanBehavior.maximumZoomLevel; - if (zoomFactor < maxZoomFactor) { - axisDetails.zoomFactor = maxZoomFactor; - zoomFactor = maxZoomFactor; - } - if (_chart.onZoomEnd != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomEnd!); - } - if (axisDetails.zoomFactor.toInt() == 1 && - axisDetails.zoomPosition.toInt() == 0 && - _chart.onZoomReset != null) { - bindZoomEvent(_chart, axisDetails, _chart.onZoomReset!); - } - } - } - } - createZoomState(); - } - - /// Below method is for zoomIn and zoomOut public methods. - void setZoomFactorAndZoomPosition(SfCartesianChartState chartState, - ChartAxisRendererDetails axisDetails, double? zoomFactor) { - final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; - final num direction = isZoomIn - ? 1 - : isZoomOut - ? -1 - : 1; - final num cumulative = math.max( - math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1) + - (0.1 * direction), - 1); - if (cumulative >= 1) { - num origin = axisDetails.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 - : axisDetails.zoomPosition + - ((axisDetails.zoomFactor - zoomFactor) * origin); - if (axisDetails.zoomPosition != _zoomPosition || - axisDetails.zoomFactor != zoomFactor) { - zoomFactor = (_zoomPosition! + zoomFactor) > 1 - ? (1 - _zoomPosition!) - : zoomFactor; - } - - axisDetails.zoomPosition = _zoomPosition!; - axisDetails.zoomFactor = zoomFactor; - } - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the zooming behavior rendering details instance from its renderer. -class ZoomPanBehaviorHelper { - /// Gets the zooming behavior details. - static ZoomingBehaviorDetails getRenderingDetails( - ZoomPanBehaviorRenderer renderer) { - return renderer._zoomingBehaviorDetails; - } - - /// Method to set the cartesian state properties. - static void setStateProperties(ZoomPanBehavior zoomPanBehavior, - CartesianStateProperties stateProperties) { - zoomPanBehavior._stateProperties = stateProperties; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart deleted file mode 100644 index ef8c82340..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart +++ /dev/null @@ -1,504 +0,0 @@ -/// Orientation of an axis. -/// -/// The axis orientation can be changed either to vertical or horizontal. -enum AxisOrientation { - /// - AxisOrientation.vertical, renders the axis vertically. - vertical, - - /// - AxisOrientation.horizontal, renders the axis horizontally. - horizontal -} - -/// Padding for axis ranges. -enum ChartRangePadding { - /// - ChartRangePadding.auto, will apply `none` as padding for horizontal numeric axis, while the - /// vertical numeric axis takes `normal` as padding calculation. - auto, - - /// - ChartRangePadding.none, will not add any padding to the minimum and maximum values. - none, - - /// - ChartRangePadding.normal, will apply padding to the axis based on the default range calculation. - normal, - - /// - ChartRangePadding.additional, will add an interval to the minimum and maximum of the axis. - additional, - - /// - ChartRangePadding.round, will round the minimum and maximum values to the nearest possible value. - round -} - -/// Placement of category axis labels. -enum LabelPlacement { - /// - LabelPlacement.betweenTicks, places the axis label between the ticks. - betweenTicks, - - /// - LabelPlacement.onTicks, places the axis label on the ticks. - onTicks -} - -/// Action while the axis label intersects. Axis label placements can be determined when the axis labels get intersected. -enum AxisLabelIntersectAction { - /// - AxisLabelIntersectAction.none, will not perform any action on intersecting labels. - none, - - /// - AxisLabelIntersectAction.hide, hides the intersecting labels. - hide, - - /// - AxisLabelIntersectAction.wrap, wraps and places the intersecting labels in the next line. - wrap, - - /// - AxisLabelIntersectAction.multipleRows, places the axis labels in multiple rows. - multipleRows, - - /// - AxisLabelIntersectAction.rotate45, rotates all the axis labels to 45°. - rotate45, - - /// - AxisLabelIntersectAction.rotate90, rotates all the axis labels to 90°. - rotate90 -} - -/// Interval type of the DateTime and DateTimeCategory axis. -enum DateTimeIntervalType { - /// - DateTimeIntervalType.auto, will calculate interval based on the data points. - auto, - - /// - DateTimeIntervalType.years, will consider interval as years. - years, - - /// - DateTimeIntervalType.months, will consider interval as months. - months, - - /// - DateTimeIntervalType.days, will consider interval as days. - days, - - /// - DateTimeIntervalType.hours, will consider interval as hours. - hours, - - /// - DateTimeIntervalType.minutes, will consider interval as minutes. - minutes, - - /// - DateTimeIntervalType.seconds, will consider interval as seconds. - seconds, - - /// - DateTimeIntervalType.milliseconds, will consider interval as milliseconds(ms). - milliseconds, -} - -/// Position of the axis labels. -enum ChartDataLabelPosition { - /// - ChartDataLabelPosition.inside places the axis label inside the plot area - inside, - - /// - ChartDataLabelPosition.outside places the axis label outside the plot area. - outside -} - -/// Renders a variety of splines -/// -/// Spline supports the following types. -/// If SplineType.cardinal type is specified, then specify the tension using cardinalSplineTension property. -enum SplineType { - ///- SplineType.natural, will rendering normal spline. - natural, - - ///- SplineType.monotonic, will rendering monotonic type spline. - monotonic, - - ///- SplineType.cardinal, will rendering cardinal type spline. - cardinal, - - ///- SplineType.clamped, will rendering clamped type spline. - clamped -} - -/// Placement of edge labels in the axis. -enum EdgeLabelPlacement { - /// - EdgeLabelPlacement.none, places the edge labels in its own position. - none, - - /// - EdgeLabelPlacement.hides, hide the edge labels. - hide, - - /// - EdgeLabelPlacement.shift, shift the edge labels inside the plot area bounds. - shift -} - -/// Mode of empty data points. -/// -/// When the data points are given a null value, EmptyPointMode will be triggered. -enum EmptyPointMode { - /// - EmptyPointMode.gap, will consider as gap for the empty points. - gap, - - /// - EmptyPointMode.zero, will consider as 0 value for the empty points. - zero, - - /// - EmptyPointMode.drop, will drops to a minimum of the axis. - drop, - - /// - EmptyPointMode.average, will consider the average value of its previous and next data points. - average -} - -/// Sorting order of data points. -/// -/// Specifies the data points based on the specified sorting property. -enum SortingOrder { - /// - SortingOrder.ascending, arranges the points in ascending order. - ascending, - - /// - SortingOrder.descending, arranges the points in descending order. - descending, - - /// - SortingOrder.none renders the points without sorting. - none -} - -/// Position of the ticks in the axis. -enum TickPosition { - /// - TickPosition.inside, places the ticks inside the plot area. - inside, - - /// - TickPosition.outside, places the ticks outside the plot area. - outside -} - -/// Trendline type -/// -/// On the basis of the trendline type, the trendline is rendered -enum TrendlineType { - /// - TrendlineType.linear, displays linear trendline type. - linear, - - /// - TrendlineType.exponential, displays exponential trendline type. - exponential, - - /// - TrendlineType.power, displays power trendline type. - power, - - /// - TrendlineType.logarithmic, displays logarithmic trendline type. - logarithmic, - - /// - TrendlineType.polynomial, displays polynomial trendline type. - polynomial, - - /// - TrendlineType.movingAverage, displays movingAverage trendline type. - movingAverage -} - -/// Mode to activate a specific interactive user feature. -/// -/// Based on the activation mode, the user interaction will be triggered. -enum ActivationMode { - /// - ActivationMode.singleTap, activates the appropriate feature on single tap. - singleTap, - - /// - ActivationMode.doubleTap, activates on double tap. - doubleTap, - - /// - ActivationMode.longPress, activates on longPress. - longPress, - - /// - ActivationMode.none, does not activate any feature. - none -} - -/// Trackball tooltip's display mode. -/// -/// Based on TrackballDisplayMode, the trackball tooltip will be displayed. -enum TrackballDisplayMode { - /// - TrackballDisplayMode.none, will not show the tooltip. - none, - - /// - TrackballDisplayMode.floatAllPoints, displays separate tooltip for the points of different series. - floatAllPoints, - - /// - TrackballDisplayMode.groupAllPoints, groups the tooltip text of the points of different series. - groupAllPoints, - - /// - TrackballDisplayMode.nearestPoint, displays the tooltip of nearest point. - nearestPoint -} - -/// Crosshair line type. -enum CrosshairLineType { - /// - CrosshairLineType.both, displays both horizontal and vertical lines. - both, - - /// - CrosshairLineType.horizontal, displays horizontal line. - horizontal, - - /// - CrosshairLineType.vertical, displays vertical line. - vertical, - - /// - CrosshairLineType.none, will not display crosshair line. - none -} - -/// Trackball line type. -enum TrackballLineType { - /// - TrackballLineType.horizontal, displays horizontal trackball line. - vertical, - - /// - TrackballLineType.vertical, displays vertical trackball line. - horizontal, - - /// - TrackballLineType.none, will not display trackball line. - none -} - -/// Zooming mode in [SfCartesianChart] -enum ZoomMode { - /// - `ZoomMode.x`, zooms in horizontal direction. - x, - - /// - `ZoomMode.y`, zooms in vertical direction. - y, - - /// - `ZoomMode.xy`, zooms in both horizontal and vertical direction. - xy -} - -/// Data point selection type. -enum SelectionType { - /// - SelectionType.point, selects the individual point. - point, - - /// - SelectionType.series, selects the entire series. - series, - - /// - SelectionType.cluster, selects the cluster of data points. - cluster -} - -/// Coordinate unit for placing annotations. -enum CoordinateUnit { - /// - CoordinateUnit.point, places the annotation concerning to the points. - point, - - /// - CoordinateUnit.logicalPixel, places the annotation concerning to the pixel value. - logicalPixel, - - /// - CoordinateUnit.percentage, places the annotation concerning to the percentage value. - percentage -} - -/// Annotation is a note by way of explanation or comment added to the chart. -/// -/// Region of annotation for positioning it. -enum AnnotationRegion { - /// - AnnotationRegion.chart, places the annotation anywhere in the chart area. - chart, - - /// - AnnotationRegion.plotArea, places the annotation anywhere in the plot area. - plotArea -} - -/// Border mode of area type series. -/// -/// Borders rendered for area type series can be customized. -enum BorderDrawMode { - /// - BorderDrawMode.all, renders border for all the sides of area. - all, - - /// - BorderDrawMode.top, renders border only for top side. - top, - - /// - BorderDrawMode.excludeBottom, renders border except bottom side. - excludeBottom -} - -/// Border mode of range area series. -enum RangeAreaBorderMode { - /// - RangeAreaBorderMode.all, renders border for all the sides of area. - all, - - /// - RangeAreaBorderMode.excludeSides, renders border except left and right side. - excludeSides -} - -/// Types of text rendering positions. -enum TextAnchor { - /// - TextAnchor.start, anchors the text at the start position. - start, - - /// - TextAnchor.middle, anchors the text at the middle position. - middle, - - /// - TextAnchor.end, anchors the text at the end position. - end -} - -/// Tooltip positioning. -enum TooltipPosition { - /// - TooltipPosition.auto, position of tooltip gets tp the default position. - auto, - - /// - TooltipPosition.pointer, position of the tooltip will be at the pointer position. - pointer -} - -/// Macd indicator type. -enum MacdType { - /// - MacdType.both, indicator will have both line and histogram. - both, - - /// - MacdType.line, the indicator will have a line only. - line, - - /// - MacdType.histogram, the indicator will have a histogram line only. - histogram -} - -/// Box plot series rendering mode. -enum BoxPlotMode { - /// - BoxPlotMode.normal, box plot mode set as normal. - /// - /// The quartile values are calculated by splitting the list and getting the median values. - normal, - - /// - BoxPlotMode.inclusive, box plot mode set as inclusive. - /// - /// The quartile values are calculated using the formula (N−1) * P (N count, P percentile), and their index value starts from 0 in the list. - inclusive, - - /// - BoxPlotMode.exclusive, box plot mode set as exclusive. - /// - /// The quartile values are calculated using the formula (N+1) * P (N count, P percentile), and their index value starts from 1 in the list. - exclusive -} - -/// Used to align the Cartesian data label positions. -/// -/// Aligns the data label text to near, center and far. -enum LabelAlignment { - /// `LabelAlignment.end`, datalabel alignment is greater distance to series line. - end, - - /// `LabelAlignment.start`, datalabel alignment is closer to series line. - start, - - /// `LabelAlignment.center`, datalabel alignment is center of the series line. - center -} - -/// Whether marker should be visible or not when trackball is enabled. -enum TrackballVisibilityMode { - /// * TrackballVisibilityMode.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, - - /// * TrackballVisibilityMode.visible - Makes the trackball marker visible for all the series, - /// irrespective of considering the `isVisible` property's value in the `markerSettings`. - visible, - - /// * TrackballVisibilityMode.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 -} - -/// Determines the type of the Error Bar. -enum ErrorBarType { - /// `ErrorBarType.fixed` - The horizontal and vertical error values should be fixed. - fixed, - - /// `ErrorBarType.percentage` - The horizontal and vertical error values changes into error percentages. - percentage, - - /// `ErrorBarType.standardDeviation` - The horizontal and vertical error values changes depends on the deviation values. - standardDeviation, - - /// `ErrorBarType.standardError` - The horizontal and vertical error values changes depends on approximate - /// error values of all points. - standardError, - - /// `ErrorBarType.custom` - It determines the positive and negative error values in both horizontal - /// and vertical direction. - custom -} - -/// Determines the error bar direction. -enum Direction { - /// `Direction.plus` - Determines the error bar direction in positive side. - plus, - - /// `Direction.minus` - Determines the error bar direction in negative side. - minus, - - /// `Direction.both` - Determines the error bar direction in both positive and negative sides. - both -} - -/// Determines mode of the error bar. -enum RenderingMode { - /// `RenderingMode.vertical` - Determines the vertical side of the error bar. - vertical, - - /// `RenderingMode.horizontal` - Determines the horizontal side of the error bar. - horizontal, - - /// `RenderingMode.both` - Determines both the vertical and horizontal sides of the error bar. - both -} - -/// Border type of the chart axis label. -/// -/// Defaults to `AxisBorderType.rectangle`. -enum AxisBorderType { - /// `AxisBorderType.rectangle` renders border for axis label with rectangle shape. - rectangle, - - /// `AxisBorderType.withoutTopAndBottom` renders border for axis label without top - /// and bottom in the rectangle. - withoutTopAndBottom, -} - -/// Border type of the chart axis multi-level label. -/// -/// Defaults to `MultiLevelBorderType.rectangle`. -enum MultiLevelBorderType { - /// `MultiLevelBorderType.rectangle` renders border for multi-level axis label - /// in rectangle shape. - rectangle, - - /// `MultiLevelBorderType.withoutTopAndBottom` renders border for multi-level - /// axis label without top and bottom in a rectangle shape. - withoutTopAndBottom, - - /// `MultiLevelBorderType.squareBrace` renders square brace as the border - /// for the multi-level label. - squareBrace, - - /// `MultiLevelBorderType.curlyBrace` renders curly brace as the border - /// for the multi-level label. - curlyBrace, -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart deleted file mode 100644 index b973f1db7..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart +++ /dev/null @@ -1,4339 +0,0 @@ -import 'dart:math' as math; -import 'dart:math' as math_lib; -import 'dart:math'; -import 'dart:ui' as dart_ui; - -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import './../../circular_chart/utils/helper.dart'; -import './../../common/rendering_details.dart'; -import './../../common/user_interaction/selection_behavior.dart'; -import './../../common/utils/helper.dart'; -import './../axis/axis.dart'; -import './../axis/category_axis.dart'; -import './../axis/datetime_axis.dart'; -import './../axis/datetime_category_axis.dart'; -import './../axis/logarithmic_axis.dart'; -import './../chart_segment/chart_segment.dart'; -import './../chart_series/financial_series_base.dart'; -import './../chart_series/series.dart'; -import './../chart_series/series_renderer_properties.dart'; -import './../chart_series/stacked_series_base.dart'; -import './../chart_series/xy_data_series.dart'; -import './../common/cartesian_state_properties.dart'; -import './../common/common.dart'; -import './../common/marker.dart'; -import './../common/renderer.dart'; -import './../common/segment_properties.dart'; -import '../../../charts.dart'; - -/// Return percentage to value. -num? percentageToValue(String? value, num size) { - if (value != null) { - return value.contains('%') - ? (size / 100) * num.tryParse(value.replaceAll(RegExp('%'), ''))! - : num.tryParse(value); - } - return null; -} - -/// Draw the text. -void drawText(Canvas canvas, String text, Offset point, TextStyle style, - [int? angle, bool? isRtl]) { - final int maxLines = getMaxLinesContent(text); - final TextSpan span = TextSpan(text: text, style: style); - final TextPainter tp = TextPainter( - text: span, - textDirection: - isRtl == true ? dart_ui.TextDirection.rtl : dart_ui.TextDirection.ltr, - textAlign: TextAlign.center, - maxLines: maxLines); - tp.layout(); - canvas.save(); - canvas.translate(point.dx, point.dy); - Offset labelOffset = Offset.zero; - if (angle != null && angle > 0) { - canvas.rotate(degreeToRadian(angle)); - labelOffset = Offset(-tp.width / 2, -tp.height / 2); - } - tp.paint(canvas, labelOffset); - canvas.restore(); -} - -/// Draw the path. -void drawDashedPath(Canvas canvas, CustomPaintStyle style, Offset moveToPoint, - Offset lineToPoint, - [List? dashArray]) { - bool even = false; - final Path path = Path(); - path.moveTo(moveToPoint.dx, moveToPoint.dy); - path.lineTo(lineToPoint.dx, lineToPoint.dy); - final Paint paint = Paint(); - paint.strokeWidth = style.strokeWidth; - paint.color = style.color; - paint.style = style.paintStyle; - - if (dashArray != null) { - for (int i = 1; i < dashArray.length; i = i + 2) { - if (dashArray[i] == 0) { - even = true; - } - } - if (even == false) { - canvas.drawPath( - dashPath( - path, - dashArray: CircularIntervalList(dashArray), - )!, - paint); - } - } else { - canvas.drawPath(path, paint); - } -} - -/// Find the position of point. -num valueToCoefficient( - num? value, ChartAxisRendererDetails axisRendererDetails) { - num result = 0; - if (axisRendererDetails.visibleRange != null && value != null) { - final VisibleRange range = axisRendererDetails.visibleRange!; - // ignore: unnecessary_null_comparison - if (range != null) { - result = (value - range.minimum) / (range.delta); - result = axisRendererDetails.axis.isInversed ? (1 - result) : result; - } - } - return result; -} - -/// Find logarithmic values. -num calculateLogBaseValue(num value, num base) => - math.log(value) / math.log(base); - -/// To check if value is within range. -bool withInRange(num value, VisibleRange range) => -// ignore: unnecessary_null_comparison - value != null && (value <= range.maximum) && (value >= range.minimum); - -/// 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! - ? series.intermediateSumColor ?? seriesColor - : point.isTotalSum! - ? series.totalSumColor ?? seriesColor - : point.yValue < 0 == true - ? series.negativePointsColor ?? seriesColor - : seriesColor; - -/// Get the location of point. -ChartLocation calculatePoint( - num x, - num? y, - ChartAxisRendererDetails xAxisRendererDetails, - ChartAxisRendererDetails yAxisRendererDetails, - bool isInverted, - CartesianSeries? series, - Rect rect) { - final ChartAxis xAxis = xAxisRendererDetails.axis; - final ChartAxis yAxis = yAxisRendererDetails.axis; - x = xAxis is LogarithmicAxis - ? calculateLogBaseValue(x > 0 ? x : 0, xAxis.logBase) - : x; - y = yAxis is LogarithmicAxis - ? y != null - ? calculateLogBaseValue(y > 0 ? y : 0, yAxis.logBase) - : 0 - : y; - x = valueToCoefficient(x.isInfinite ? 0 : x, xAxisRendererDetails); - y = valueToCoefficient( - y != null - ? y.isInfinite - ? 0 - : y < 0 && - yAxis is LogarithmicAxis && - (series is SplineSeries || - series is SplineAreaSeries || - series is SplineRangeAreaSeries) - ? 0 - : y - : y, - yAxisRendererDetails); - final num xLength = isInverted ? rect.height : rect.width; - final num yLength = isInverted ? rect.width : rect.height; - final double locationX = - rect.left + (isInverted ? (y * yLength) : (x * xLength)); - final double locationY = - rect.top + (isInverted ? (1 - x) * xLength : (1 - y) * yLength); - return ChartLocation(locationX, locationY); -} - -/// Calculate the minimum points delta. -num calculateMinPointsDelta( - ChartAxisRenderer axisRenderer, - List seriesRenderers, - CartesianStateProperties stateProperties) { - num minDelta = 1.7976931348623157e+308, minVal; - num? seriesMin; - dynamic xValues; - for (final CartesianSeriesRenderer seriesRenderer in seriesRenderers) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final CartesianSeries series = - seriesRendererDetails.series; - num value; - xValues = []; - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (seriesRendererDetails.visible! == true && - ((axisRendererDetails.name == series.xAxisName) || - (axisRendererDetails.name == - (stateProperties.chart.primaryXAxis.name ?? - 'primaryXAxis') && - series.xAxisName == null) || - (axisRendererDetails.name == - AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.primaryXAxisRenderer!) - .name && - series.xAxisName != null))) { - xValues = axisRenderer is DateTimeAxisRenderer - ? seriesRendererDetails.overAllDataPoints - .map((CartesianChartPoint? point) { - return point!.xValue; - }).toList() - : seriesRendererDetails.dataPoints - .map((CartesianChartPoint point) { - return point.xValue; - }).toList(); - xValues.sort(); - if (xValues.length == 1) { - DateTime? minDate; - num? minimumInSeconds; - if (axisRenderer is DateTimeAxisRenderer) { - minDate = DateTime.fromMillisecondsSinceEpoch( - seriesRendererDetails.minimumX! as int); - minDate = minDate.subtract(const Duration(days: 1)); - minimumInSeconds = minDate.millisecondsSinceEpoch; - } - seriesMin = (axisRenderer is DateTimeAxisRenderer && - seriesRendererDetails.minimumX == - seriesRendererDetails.maximumX) - ? minimumInSeconds - : seriesRendererDetails.minimumX; - minVal = xValues[0] - - (seriesMin ?? axisRendererDetails.visibleRange!.minimum); - if (minVal != 0) { - minDelta = math.min(minDelta, minVal); - } - } else { - for (int i = 0; i < xValues.length; i++) { - value = xValues[i]; - // ignore: unnecessary_null_comparison - if (i > 0 && value != null) { - minVal = value - xValues[i - 1]; - if (minVal != 0) { - minDelta = math.min(minDelta, minVal); - } - } - } - } - } - } - if (minDelta == 1.7976931348623157e+308) { - minDelta = 1; - } - - return minDelta; -} - -/// Draw legend series type icon. -PaintingStyle calculateLegendShapes(Path path, Rect rect, String seriesType) { - PaintingStyle style = PaintingStyle.fill; - switch (seriesType) { - case 'line': - case 'fastline': - case 'stackedline': - case 'stackedline100': - style = PaintingStyle.stroke; - break; - case 'spline': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.splineSeries); - style = PaintingStyle.stroke; - break; - case 'splinearea': - case 'splinerangearea': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.splineAreaSeries); - break; - case 'bar': - case 'stackedbar': - case 'stackedbar100': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.barSeries); - break; - case 'column': - case 'stackedcolumn': - case 'stackedcolumn100': - case 'rangecolumn': - case 'histogram': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.columnSeries); - break; - case 'area': - case 'stackedarea': - case 'rangearea': - case 'stackedarea100': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.areaSeries); - break; - case 'stepline': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.stepLineSeries); - style = PaintingStyle.stroke; - break; - case 'bubble': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.bubbleSeries); - break; - case 'hilo': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.hiloSeries); - style = PaintingStyle.stroke; - break; - case 'hiloopenclose': - case 'candle': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.candleSeries); - style = PaintingStyle.stroke; - break; - case 'waterfall': - case 'boxandwhisker': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.waterfallSeries); - break; - case 'pie': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.pieSeries); - break; - case 'doughnut': - case 'radialbar': - break; - case 'steparea': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.stepAreaSeries); - break; - case 'pyramid': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.pyramidSeries); - break; - case 'funnel': - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.funnelSeries); - break; - default: - path.addArc( - Rect.fromLTRB(rect.left - rect.width / 2, rect.top - rect.height / 2, - rect.left + rect.width / 2, rect.top + rect.height / 2), - 0.0, - 2 * math.pi); - break; - } - return style; -} - -/// Calculate bar legend icon path. -void calculateBarTypeIconPath( - Path path, double x, double y, double width, double height) { - const num padding = 10; - path.moveTo(x - (width / 2) - padding / 4, y - 3 * (height / 5)); - path.lineTo(x + 3 * (width / 10), y - 3 * (height / 5)); - path.lineTo(x + 3 * (width / 10), y - 3 * (height / 10)); - path.lineTo(x - (width / 2) - padding / 4, y - 3 * (height / 10)); - path.close(); - path.moveTo( - x - (width / 2) - (padding / 4), y - (height / 5) + (padding / 20)); - path.lineTo( - x + (width / 2) + (padding / 4), y - (height / 5) + (padding / 20)); - path.lineTo( - x + (width / 2) + (padding / 4), y + (height / 10) + (padding / 20)); - path.lineTo( - x - (width / 2) - (padding / 4), y + (height / 10) + (padding / 20)); - path.close(); - path.moveTo( - x - (width / 2) - (padding / 4), y + (height / 5) + (padding / 10)); - path.lineTo(x - width / 4, y + (height / 5) + (padding / 10)); - path.lineTo(x - width / 4, y + (height / 2) + (padding / 10)); - path.lineTo( - x - (width / 2) - (padding / 4), y + (height / 2) + (padding / 10)); - path.close(); -} - -/// Calculate column legend icon path. -void calculateColumnTypeIconPath( - Path path, double x, double y, double width, double height) { - const num padding = 10; - path.moveTo(x - 3 * (width / 5), y - (height / 5)); - path.lineTo(x + 3 * (-width / 10), y - (height / 5)); - path.lineTo(x + 3 * (-width / 10), y + (height / 2)); - path.lineTo(x - 3 * (width / 5), y + (height / 2)); - path.close(); - path.moveTo( - x - (width / 10) - (width / 20), y - (height / 4) - (padding / 2)); - path.lineTo( - x + (width / 10) + (width / 20), y - (height / 4) - (padding / 2)); - path.lineTo(x + (width / 10) + (width / 20), y + (height / 2)); - path.lineTo(x - (width / 10) - (width / 20), y + (height / 2)); - path.close(); - path.moveTo(x + 3 * (width / 10), y); - path.lineTo(x + 3 * (width / 5), y); - path.lineTo(x + 3 * (width / 5), y + (height / 2)); - path.lineTo(x + 3 * (width / 10), y + (height / 2)); - path.close(); -} - -/// Calculate area type legend icon path. -void calculateAreaTypeIconPath( - Path path, double x, double y, double width, double height) { - const num padding = 10; - path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); - path.lineTo(x - (width / 4) - (padding / 8), y - (height / 2)); - path.lineTo(x, y + (height / 4)); - path.lineTo(x + (width / 4) + (padding / 8), y - (height / 2) + (height / 4)); - path.lineTo(x + (height / 2) + (padding / 4), y + (height / 2)); - path.close(); -} - -/// Calculate stepline legend icon path. -void calculateSteplineIconPath( - Path path, double x, double y, double width, double height) { - const num padding = 10; - path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); - path.lineTo(x - (width / 2) + (width / 10), y + (height / 2)); - path.lineTo(x - (width / 2) + (width / 10), y); - path.lineTo(x - (width / 10), y); - path.lineTo(x - (width / 10), y + (height / 2)); - path.lineTo(x + (width / 5), y + (height / 2)); - path.lineTo(x + (width / 5), y - (height / 2)); - path.lineTo(x + (width / 2), y - (height / 2)); - path.lineTo(x + (width / 2), y + (height / 2)); - path.lineTo(x + (width / 2) + (padding / 4), y + (height / 2)); -} - -/// Calculate steparea legend icon path. -void calculateStepAreaIconPath( - Path path, double x, double y, double width, double height) { - const num padding = 10; - path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); - path.lineTo(x - (width / 2) - (padding / 4), y - (height / 4)); - path.lineTo(x - (width / 2) + (width / 10), y - (height / 4)); - path.lineTo(x - (width / 2) + (width / 10), y - (height / 2)); - path.lineTo(x - (width / 10), y - (height / 2)); - path.lineTo(x - (width / 10), y); - path.lineTo(x + (width / 5), y); - path.lineTo(x + (width / 5), y - (height / 3)); - path.lineTo(x + (width / 2), y - (height / 3)); - path.lineTo(x + (width / 2), y + (height / 2)); - path.close(); -} - -/// Calculate pie legend icon path. -void calculatePieIconPath( - Path path, double x, double y, double width, double height) { - 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).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).toDouble(), degreesToRadians(-80).toDouble(), false); - path.close(); -} - -/// Calculate pyramid legend icon path. -void calculatePyramidIconPath( - 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(); -} - -/// Calculate funnel legend icon path. -void calculateFunnelIconPath( - 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(); -} - -/// Calculate the rect bounds for column series and bar series. -Rect calculateRectangle( - num x1, - num? y1, - num x2, - num? y2, - CartesianSeriesRenderer seriesRenderer, - CartesianStateProperties stateProperties) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final Rect rect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final bool isInverted = stateProperties.requireInvertedAxis; - final ChartLocation point1 = calculatePoint( - x1, - y1, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - isInverted, - seriesRendererDetails.series, - rect); - final ChartLocation point2 = calculatePoint( - x2, - y2, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - isInverted, - seriesRendererDetails.series, - rect); - return Rect.fromLTWH( - math.min(point1.x, point2.x), - math.min(point1.y, point2.y), - (point2.x - point1.x).abs(), - (point2.y - point1.y).abs()); -} - -/// Calculate the tracker rect bounds for column series and bar series. -Rect calculateShadowRectangle( - num x1, - num y1, - num x2, - num y2, - CartesianSeriesRenderer seriesRenderer, - CartesianStateProperties stateProperties, - Offset plotOffset) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final Rect rect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, - seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final bool isInverted = stateProperties.requireInvertedAxis; - final ChartLocation point1 = calculatePoint( - x1, - y1, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - isInverted, - seriesRendererDetails.series, - rect); - final ChartLocation point2 = calculatePoint( - x2, - y2, - seriesRendererDetails.xAxisDetails!, - seriesRendererDetails.yAxisDetails!, - isInverted, - seriesRendererDetails.series, - rect); - final bool isColumn = seriesRendererDetails.seriesType == 'column'; - final bool isHistogram = seriesRendererDetails.seriesType == 'histogram'; - final bool isStackedColumn = - seriesRendererDetails.seriesType == 'stackedcolumn'; - final bool isStackedBar = seriesRendererDetails.seriesType == 'stackedbar'; - final bool isRangeColumn = seriesRendererDetails.seriesType == 'rangecolumn'; - ColumnSeries? columnSeries; - BarSeries? barSeries; - StackedColumnSeries? stackedColumnSeries; - StackedBarSeries? stackedBarSeries; - RangeColumnSeries? rangeColumnSeries; - HistogramSeries? histogramSeries; - if (seriesRendererDetails.seriesType == 'column') { - columnSeries = - seriesRendererDetails.series as ColumnSeries; - } else if (seriesRendererDetails.seriesType == 'bar') { - barSeries = seriesRendererDetails.series as BarSeries; - } else if (seriesRendererDetails.seriesType == 'stackedcolumn') { - stackedColumnSeries = - seriesRendererDetails.series as StackedColumnSeries; - } else if (seriesRendererDetails.seriesType == 'stackedbar') { - stackedBarSeries = - seriesRendererDetails.series as StackedBarSeries; - } else if (seriesRendererDetails.seriesType == 'rangecolumn') { - rangeColumnSeries = - seriesRendererDetails.series as RangeColumnSeries; - } else if (seriesRendererDetails.seriesType == 'histogram') { - histogramSeries = - seriesRendererDetails.series as HistogramSeries; - } - return !stateProperties.chart.isTransposed - ? _getNormalShadowRect( - rect, - isColumn, - isHistogram, - isRangeColumn, - isStackedColumn, - isStackedBar, - stateProperties, - plotOffset, - point1, - point2, - columnSeries, - barSeries, - stackedColumnSeries, - stackedBarSeries, - rangeColumnSeries, - histogramSeries) - : _getTransposedShadowRect( - rect, - isColumn, - isHistogram, - isRangeColumn, - isStackedColumn, - isStackedBar, - stateProperties, - plotOffset, - point1, - point2, - columnSeries, - barSeries, - stackedColumnSeries, - stackedBarSeries, - rangeColumnSeries, - histogramSeries); -} - -/// Calculate shadow rectangle for normal rect series. -Rect _getNormalShadowRect( - Rect rect, - bool isColumn, - bool isHistogram, - bool isRangeColumn, - bool isStackedColumn, - bool isStackedBar, - CartesianStateProperties stateProperties, - Offset plotOffset, - ChartLocation point1, - ChartLocation point2, - 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) - : isHistogram - ? math.min(point1.x, point2.x) + - (-histogramSeries!.trackBorderWidth - - histogramSeries.trackPadding) - : isRangeColumn - ? math.min(point1.x, point2.x) + - (-rangeColumnSeries!.trackBorderWidth - - rangeColumnSeries.trackPadding) - : isStackedColumn - ? math.min(point1.x, point2.x) + - (-stackedColumnSeries!.trackBorderWidth - - stackedColumnSeries.trackPadding) - : isStackedBar - ? stateProperties.chartAxis.axisClipRect.left - : stateProperties.chartAxis.axisClipRect.left, - isColumn || isHistogram || isRangeColumn - ? rect.top - : isStackedColumn - ? rect.top - : isStackedBar - ? (math.min(point1.y, point2.y) - - stackedBarSeries!.trackBorderWidth - - stackedBarSeries.trackPadding) - : (math.min(point1.y, point2.y) - - barSeries!.trackBorderWidth - - barSeries.trackPadding), - isColumn - ? (point2.x - point1.x).abs() + - (columnSeries!.trackBorderWidth * 2) + - columnSeries.trackPadding * 2 - : isHistogram - ? (point2.x - point1.x).abs() + - (histogramSeries!.trackBorderWidth * 2) + - histogramSeries.trackPadding * 2 - : isRangeColumn - ? (point2.x - point1.x).abs() + - (rangeColumnSeries!.trackBorderWidth * 2) + - rangeColumnSeries.trackPadding * 2 - : isStackedColumn - ? (point2.x - point1.x).abs() + - (stackedColumnSeries!.trackBorderWidth * 2) + - stackedColumnSeries.trackPadding * 2 - : isStackedBar - ? stateProperties.chartAxis.axisClipRect.width - : stateProperties.chartAxis.axisClipRect.width, - isColumn || isHistogram || isRangeColumn - ? (stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy) - : isStackedColumn - ? (stateProperties.chartAxis.axisClipRect.height - - 2 * plotOffset.dy) - : isStackedBar - ? (point2.y - point1.y).abs() + - stackedBarSeries!.trackBorderWidth * 2 + - stackedBarSeries.trackPadding * 2 - : (point2.y - point1.y).abs() + - barSeries!.trackBorderWidth * 2 + - barSeries.trackPadding * 2); -} - -/// Calculate shadow rectangle for transposed rect series. -Rect _getTransposedShadowRect( - Rect rect, - bool isColumn, - bool isHistogram, - bool isRangeColumn, - bool isStackedColumn, - bool isStackedBar, - CartesianStateProperties stateProperties, - Offset plotOffset, - ChartLocation point1, - ChartLocation point2, - ColumnSeries? columnSeries, - BarSeries? barSeries, - StackedColumnSeries? stackedColumnSeries, - StackedBarSeries? stackedBarSeries, - RangeColumnSeries? rangeColumnSeries, - HistogramSeries? histogramSeries) { - return Rect.fromLTWH( - isColumn || isRangeColumn || isHistogram - ? stateProperties.chartAxis.axisClipRect.left - : isStackedColumn - ? stateProperties.chartAxis.axisClipRect.left - : isStackedBar - ? math.min(point1.x, point2.x) + - (-stackedBarSeries!.trackBorderWidth - - stackedBarSeries.trackPadding) - : math.min(point1.x, point2.x) + - (-barSeries!.trackBorderWidth - barSeries.trackPadding), - isColumn - ? (math.min(point1.y, point2.y) - - columnSeries!.trackBorderWidth - - columnSeries.trackPadding) - : isHistogram - ? (math.min(point1.y, point2.y) - - histogramSeries!.trackBorderWidth - - histogramSeries.trackPadding) - : isRangeColumn - ? (math.min(point1.y, point2.y) - - rangeColumnSeries!.trackBorderWidth - - rangeColumnSeries.trackPadding) - : isStackedColumn - ? (math.min(point1.y, point2.y) - - stackedColumnSeries!.trackBorderWidth - - stackedColumnSeries.trackPadding) - : isStackedBar - ? rect.top - : rect.top, - isColumn || isRangeColumn || isHistogram - ? stateProperties.chartAxis.axisClipRect.width - : isStackedColumn - ? stateProperties.chartAxis.axisClipRect.width - : isStackedBar - ? (point2.x - point1.x).abs() + - (stackedBarSeries!.trackBorderWidth * 2) + - stackedBarSeries.trackPadding * 2 - : (point2.x - point1.x).abs() + - (barSeries!.trackBorderWidth * 2) + - barSeries.trackPadding * 2, - isColumn - ? ((point2.y - point1.y).abs() + - columnSeries!.trackBorderWidth * 2 + - columnSeries.trackPadding * 2) - : isHistogram - ? ((point2.y - point1.y).abs() + - histogramSeries!.trackBorderWidth * 2 + - histogramSeries.trackPadding * 2) - : isRangeColumn - ? (point2.y - point1.y).abs() + - rangeColumnSeries!.trackBorderWidth * 2 + - rangeColumnSeries.trackPadding * 2 - : isStackedColumn - ? (point2.y - point1.y).abs() + - stackedColumnSeries!.trackBorderWidth * 2 + - stackedColumnSeries.trackPadding * 2 - : isStackedBar - ? (stateProperties.chartAxis.axisClipRect.height - - 2 * plotOffset.dy) - : (stateProperties.chartAxis.axisClipRect.height - - 2 * plotOffset.dy)); -} - -/// Calculate the side by side range for column and bar series. -VisibleRange calculateSideBySideInfo(CartesianSeriesRenderer seriesRenderer, - CartesianStateProperties stateProperties) { - num? rectPosition; - num? count; - num seriesSpacing; - num? pointSpacing; - final SfCartesianChart chart = stateProperties.chart; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final CartesianSeries series = seriesRendererDetails.series; - if (seriesRendererDetails.seriesType == 'column' && - chart.enableSideBySideSeriesPlacement) { - final ColumnSeriesRenderer columnSeriesRenderer = - seriesRenderer as ColumnSeriesRenderer; - _calculateSideBySidePositions(columnSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'histogram' && - chart.enableSideBySideSeriesPlacement) { - final HistogramSeriesRenderer histogramSeriesRenderer = - seriesRenderer as HistogramSeriesRenderer; - _calculateSideBySidePositions(histogramSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'bar' && - chart.enableSideBySideSeriesPlacement) { - final BarSeriesRenderer barSeriesRenderer = - seriesRenderer as BarSeriesRenderer; - _calculateSideBySidePositions(barSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if ((seriesRendererDetails.seriesType.contains('stackedcolumn') == - true || - seriesRendererDetails.seriesType.contains('stackedbar') == true) && - chart.enableSideBySideSeriesPlacement) { - final StackedSeriesRenderer stackedRectSeriesRenderer = - seriesRenderer as StackedSeriesRenderer; - _calculateSideBySidePositions(stackedRectSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'rangecolumn' && - chart.enableSideBySideSeriesPlacement) { - final RangeColumnSeriesRenderer rangeColumnSeriesRenderer = - seriesRenderer as RangeColumnSeriesRenderer; - _calculateSideBySidePositions(rangeColumnSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'hilo' && - chart.enableSideBySideSeriesPlacement) { - final HiloSeriesRenderer hiloSeriesRenderer = - seriesRenderer as HiloSeriesRenderer; - _calculateSideBySidePositions(hiloSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'hiloopenclose' && - chart.enableSideBySideSeriesPlacement) { - final HiloOpenCloseSeriesRenderer hiloOpenCloseSeriesRenderer = - seriesRenderer as HiloOpenCloseSeriesRenderer; - _calculateSideBySidePositions(hiloOpenCloseSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'candle' && - chart.enableSideBySideSeriesPlacement) { - final CandleSeriesRenderer candleSeriesRenderer = - seriesRenderer as CandleSeriesRenderer; - _calculateSideBySidePositions(candleSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'boxandwhisker' && - chart.enableSideBySideSeriesPlacement) { - final BoxAndWhiskerSeriesRenderer boxAndWhiskerSeriesRenderer = - seriesRenderer as BoxAndWhiskerSeriesRenderer; - _calculateSideBySidePositions(boxAndWhiskerSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } else if (seriesRendererDetails.seriesType == 'waterfall' && - chart.enableSideBySideSeriesPlacement) { - final WaterfallSeriesRenderer waterfallSeriesRenderer = - seriesRenderer as WaterfallSeriesRenderer; - _calculateSideBySidePositions(waterfallSeriesRenderer, stateProperties); - rectPosition = seriesRendererDetails.rectPosition; - count = seriesRendererDetails.rectCount; - } - - if (seriesRendererDetails.seriesType == 'column') { - final ColumnSeries columnSeries = - series as ColumnSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? columnSeries.spacing : 0; - assert(columnSeries.width == null || columnSeries.width! <= 1, - 'The width of the column series must be less than or equal 1.'); - pointSpacing = columnSeries.width!; - } else if (seriesRendererDetails.seriesType == 'histogram') { - final HistogramSeries histogramSeries = - series as HistogramSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? histogramSeries.spacing : 0; - assert(histogramSeries.width! <= 1, - 'The width of the histogram series must be less than or equal 1.'); - pointSpacing = histogramSeries.width!; - } else if (seriesRendererDetails.seriesType == 'stackedcolumn' || - seriesRendererDetails.seriesType == 'stackedcolumn100' || - seriesRendererDetails.seriesType == 'stackedbar' || - seriesRendererDetails.seriesType == 'stackedbar100') { - final StackedSeriesBase stackedRectSeries = - series as StackedSeriesBase; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? stackedRectSeries.spacing : 0; - pointSpacing = stackedRectSeries.width!; - } else if (seriesRendererDetails.seriesType == 'rangecolumn') { - final RangeColumnSeries rangeColumnSeries = - series as RangeColumnSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? rangeColumnSeries.spacing : 0; - 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 (seriesRendererDetails.seriesType == 'hilo') { - final HiloSeries hiloSeries = - series as HiloSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? hiloSeries.spacing : 0; - pointSpacing = hiloSeries.width!; - } else if (seriesRendererDetails.seriesType == 'hiloopenclose') { - final HiloOpenCloseSeries hiloOpenCloseSeries = - series as HiloOpenCloseSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? hiloOpenCloseSeries.spacing : 0; - pointSpacing = hiloOpenCloseSeries.width!; - } else if (seriesRendererDetails.seriesType == 'candle') { - final CandleSeries candleSeries = - series as CandleSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? candleSeries.spacing : 0; - pointSpacing = candleSeries.width!; - } else if (seriesRendererDetails.seriesType == 'boxandwhisker') { - final BoxAndWhiskerSeries boxAndWhiskerSeries = - series as BoxAndWhiskerSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? boxAndWhiskerSeries.spacing : 0; - assert(boxAndWhiskerSeries.width! <= 1, - 'The width of the box plot series must be less than or equal to 1.'); - pointSpacing = boxAndWhiskerSeries.width!; - } else if (seriesRendererDetails.seriesType == 'waterfall') { - final WaterfallSeries waterfallSeries = - series as WaterfallSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? waterfallSeries.spacing : 0; - assert(waterfallSeries.width! <= 1, - 'The width of the waterfall series must be less than or equal to 1.'); - pointSpacing = waterfallSeries.width!; - } else { - final BarSeries barSeries = - series as BarSeries; - seriesSpacing = - chart.enableSideBySideSeriesPlacement ? barSeries.spacing : 0; - // 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!; - - /// Gets the minimum point delta in series. - final num minPointsDelta = seriesRendererDetails.minDelta ?? - calculateMinPointsDelta(seriesRendererDetails.xAxisDetails!.axisRenderer, - stateProperties.seriesRenderers, stateProperties); - final num width = minPointsDelta * pointSpacing!; - final num location = position / rectCount - 0.5; - VisibleRange doubleRange = VisibleRange(location, location + (1 / rectCount)); - - /// Side by side range will be calculated based on calculated width. - if ((doubleRange.minimum is num) && (doubleRange.maximum is num)) { - doubleRange = - VisibleRange(doubleRange.minimum * width, doubleRange.maximum * width); - doubleRange.delta = doubleRange.maximum - doubleRange.minimum; - final num radius = seriesSpacing * doubleRange.delta; - doubleRange = VisibleRange( - doubleRange.minimum + radius / 2, doubleRange.maximum - radius / 2); - doubleRange.delta = doubleRange.maximum - doubleRange.minimum; - } - return doubleRange; -} - -/// The method returns rotated text location for the given angle. -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); - - /// label rotation for 0 to 90. - pointX += ((rotateTextSize.width - textSize.width).abs() / 2) + - (((angle > 90 ? 90 : angle) / 90) * textSize.height); - - /// label rotation for 90 to 180. - pointX += (angle > 90) ? (rotateTextSize.width - textSize.height).abs() : 0; - pointY += (angle > 90) - ? (angle / 180) * textSize.height - - (((180 - angle) / 180) * textSize.height) - : 0; - - /// label rotation 180 to 270. - pointX -= (angle > 180) ? (angle / 270) * textSize.height : 0; - pointY += (angle > 180) - ? (rotateTextSize.height - textSize.height).abs() - - (angle / 270) * textSize.height - : 0; - - /// Label rotation 270 to 360. - pointX -= - (angle > 270) ? (rotateTextSize.width - textSize.height).abs() : 0; - pointY -= (angle > 270) - ? (((angle - 270) / 90) * textSize.height) + - (textSize.height * ((angle - 270) / 90)) / 2 - : 0; -// ignore: unnecessary_null_comparison - if (axis != null && axis.labelRotation.isNegative) { - final num rotation = axis.labelRotation.abs(); - if (rotation > 15 && rotation < 90) { - pointX -= (rotateTextSize.width - textSize.height).abs() / 2; - pointY -= ((90 - rotation) / 90) / 2 * textSize.height; - } else if (rotation > 90 && rotation < 180) { - pointX += rotation > 164 - ? 0 - : (rotateTextSize.width - textSize.height).abs() / 2 + - ((rotation - 90) / 90) / 2 * textSize.height; - pointY += (rotation / 180) / 2 * textSize.height; - } else if (rotation > 195 && rotation < 270) { - pointX -= (rotateTextSize.width - textSize.height).abs() / 2; - } else if (rotation > 270 && rotation < 360) { - pointX += (rotateTextSize.width - textSize.height).abs() / 2; - } - } - } - return ChartLocation(pointX, pointY); -} - -/// Checking whether new series and old series are similar. -bool isSameSeries(dynamic oldSeries, dynamic newSeries) { - return oldSeries.runtimeType == newSeries.runtimeType && - oldSeries.key == newSeries.key; -} - -/// Calculate the side by side position for rect series. -void _calculateSideBySidePositions(CartesianSeriesRenderer seriesRenderer, - CartesianStateProperties stateProperties) { - final List seriesCollection = - _findRectSeriesCollection(stateProperties); - int rectCount = 0; - num? position; - final num seriesLength = seriesCollection.length; - List<_StackingGroup>? stackingGroupPos; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - for (final dynamic seriesRenderer in seriesCollection) { - if (seriesRenderer is ColumnSeriesRenderer || - seriesRenderer is BarSeriesRenderer || - seriesRenderer is RangeColumnSeriesRenderer || - seriesRenderer is HiloSeriesRenderer || - seriesRenderer is HiloOpenCloseSeriesRenderer || - seriesRenderer is HistogramSeriesRenderer || - seriesRenderer is CandleSeriesRenderer || - seriesRenderer is BoxAndWhiskerSeriesRenderer || - seriesRenderer is WaterfallSeriesRenderer) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - seriesRendererDetails.rectPosition = rectCount++; - seriesRendererDetails.rectCount = seriesLength; - } - } - if (seriesRenderer is StackedSeriesRenderer) { - for (int i = 0; i < seriesCollection.length; i++) { - StackedSeriesBase? series; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesCollection[i]); - if (seriesCollection[i] is StackedSeriesRenderer) { - seriesRenderer = seriesCollection[i]; - series = - seriesRendererDetails.series as StackedSeriesBase; - } - // ignore: unnecessary_null_comparison - if (seriesRenderer != null && seriesRenderer is StackedSeriesRenderer) { - final String groupName = series!.groupName; - // ignore: unnecessary_null_comparison - if (groupName != null) { - stackingGroupPos ??= <_StackingGroup>[]; - if (stackingGroupPos.isEmpty) { - seriesRendererDetails.rectPosition = rectCount; - stackingGroupPos.add(_StackingGroup(groupName, rectCount++)); - } else if (stackingGroupPos.isNotEmpty) { - for (int j = 0; j < stackingGroupPos.length; j++) { - if (groupName == stackingGroupPos[j].groupName) { - seriesRendererDetails.rectPosition = - stackingGroupPos[j].stackCount; - break; - } else if (groupName != stackingGroupPos[j].groupName && - j == stackingGroupPos.length - 1) { - seriesRendererDetails.rectPosition = rectCount; - stackingGroupPos.add(_StackingGroup(groupName, rectCount++)); - break; - } - } - } - } else { - if (position == null) { - seriesRendererDetails.rectPosition = rectCount; - position = rectCount++; - } else { - seriesRendererDetails.rectPosition = position; - } - } - } - } - } - if (seriesRendererDetails.seriesType.contains('stackedcolumn') == true || - seriesRendererDetails.seriesType.contains('stackedbar') == true) { - for (int i = 0; i < seriesCollection.length; i++) { - StackedSeriesRenderer? seriesRenderer; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesCollection[i]); - if (seriesCollection[i] is StackedSeriesRenderer) { - seriesRenderer = seriesCollection[i] as StackedSeriesRenderer; - } - if (seriesRenderer != null) { - seriesRendererDetails.rectCount = rectCount; - } - } - } -} - -/// Find the column and bar series collection in axes. -List findSeriesCollection( - CartesianStateProperties stateProperties, - //ignore: unused_element - [bool? isRect]) { - final List seriesRendererCollection = - []; - for (int xAxisIndex = 0; - xAxisIndex < stateProperties.chartAxis.horizontalAxisRenderers.length; - xAxisIndex++) { - final ChartAxisRenderer xAxisRenderer = - stateProperties.chartAxis.horizontalAxisRenderers[xAxisIndex]; - final ChartAxisRendererDetails xAxisRendererDetails = - AxisHelper.getAxisRendererDetails(xAxisRenderer); - for (int xSeriesIndex = 0; - xSeriesIndex < xAxisRendererDetails.seriesRenderers.length; - xSeriesIndex++) { - final CartesianSeriesRenderer xAxisSeriesRenderer = - xAxisRendererDetails.seriesRenderers[xSeriesIndex]; - for (int yAxisIndex = 0; - yAxisIndex < stateProperties.chartAxis.verticalAxisRenderers.length; - yAxisIndex++) { - final ChartAxisRenderer yAxisRenderer = - stateProperties.chartAxis.verticalAxisRenderers[yAxisIndex]; - final ChartAxisRendererDetails yAxisRendererDetails = - AxisHelper.getAxisRendererDetails(yAxisRenderer); - for (int ySeriesIndex = 0; - ySeriesIndex < yAxisRendererDetails.seriesRenderers.length; - ySeriesIndex++) { - final CartesianSeriesRenderer yAxisSeriesRenderer = - yAxisRendererDetails.seriesRenderers[ySeriesIndex]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(xAxisSeriesRenderer); - if (xAxisSeriesRenderer == yAxisSeriesRenderer && - (seriesRendererDetails.seriesType.contains('column') == true || - seriesRendererDetails.seriesType.contains('bar') == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('stackedarea') == - true || - seriesRendererDetails.seriesType.contains('stackedline') == - true || - seriesRendererDetails.seriesType == 'histogram' || - seriesRendererDetails.seriesType == 'boxandwhisker') && - seriesRendererDetails.visible! == true) { - if (!seriesRendererCollection.contains(yAxisSeriesRenderer)) { - seriesRendererCollection.add(yAxisSeriesRenderer); - } - } - } - } - } - } - return seriesRendererCollection; -} - -/// Convert normal rect to rounded rect by using border radius. -RRect getRRectFromRect(Rect rect, BorderRadius borderRadius) { - return RRect.fromRectAndCorners( - rect, - bottomLeft: borderRadius.bottomLeft, - bottomRight: borderRadius.bottomRight, - topLeft: borderRadius.topLeft, - topRight: borderRadius.topRight, - ); -} - -/// Find the rect series collection in axes. -List _findRectSeriesCollection( - CartesianStateProperties stateProperties) { - final List seriesRenderCollection = - []; - for (int xAxisIndex = 0; - xAxisIndex < stateProperties.chartAxis.horizontalAxisRenderers.length; - xAxisIndex++) { - final ChartAxisRenderer xAxisRenderer = - stateProperties.chartAxis.horizontalAxisRenderers[xAxisIndex]; - final ChartAxisRendererDetails xAxisRendererDetails = - AxisHelper.getAxisRendererDetails(xAxisRenderer); - for (int xSeriesIndex = 0; - xSeriesIndex < xAxisRendererDetails.seriesRenderers.length; - xSeriesIndex++) { - final CartesianSeriesRenderer xAxisSeriesRenderer = - xAxisRendererDetails.seriesRenderers[xSeriesIndex]; - for (int yAxisIndex = 0; - yAxisIndex < stateProperties.chartAxis.verticalAxisRenderers.length; - yAxisIndex++) { - final ChartAxisRenderer yAxisRenderer = - stateProperties.chartAxis.verticalAxisRenderers[yAxisIndex]; - final ChartAxisRendererDetails yAxisRendererDetails = - AxisHelper.getAxisRendererDetails(yAxisRenderer); - for (int ySeriesIndex = 0; - ySeriesIndex < yAxisRendererDetails.seriesRenderers.length; - ySeriesIndex++) { - final CartesianSeriesRenderer yAxisSeriesRenderer = - yAxisRendererDetails.seriesRenderers[ySeriesIndex]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(xAxisSeriesRenderer); - if (xAxisSeriesRenderer == yAxisSeriesRenderer && - (seriesRendererDetails.seriesType.contains('column') == true || - seriesRendererDetails.seriesType.contains('waterfall') == - true || - (seriesRendererDetails.seriesType.contains('bar') == true && - !seriesRendererDetails.seriesType.contains('errorbar')) || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType == 'histogram' || - seriesRendererDetails.seriesType == 'boxandwhisker') && - seriesRendererDetails.visible! == true) { - if (!seriesRenderCollection.contains(yAxisSeriesRenderer)) { - seriesRenderCollection.add(yAxisSeriesRenderer); - } - } - } - } - } - } - return seriesRenderCollection; -} - -/// To calculate plot offset. -Rect calculatePlotOffset(Rect axisClipRect, Offset plotOffset) => Rect.fromLTWH( - axisClipRect.left + plotOffset.dx, - axisClipRect.top + plotOffset.dy, - axisClipRect.width - 2 * plotOffset.dx, - axisClipRect.height - 2 * plotOffset.dy); - -/// Get gradient fill colors. -Paint getLinearGradientPaint( - LinearGradient gradientFill, Rect region, bool isInvertedAxis) { - Paint gradientPaint; - gradientPaint = Paint() - ..shader = gradientFill.createShader(region) - ..style = PaintingStyle.fill; - return gradientPaint; -} - -/// Method to get the shader paint. -Paint getShaderPaint(Shader shader) { - Paint shaderPaint; - shaderPaint = Paint() - ..shader = shader - ..style = PaintingStyle.fill; - return shaderPaint; -} - -/// Gets the the actual label value for tooltip and data label etc. -String getLabelValue(dynamic value, dynamic axis, [int? showDigits]) { - if (value.toString().split('.').length > 1) { - final String str = value.toString(); - final List list = str.split('.'); - value = axis is LogarithmicAxis ? math.pow(10, value) : value; - value = double.parse(value.toStringAsFixed(showDigits ?? 3)); - value = (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000' || - list[1] == '0000000') - ? value.round() - : value; - } - final dynamic text = axis is NumericAxis && axis.numberFormat != null - ? axis.numberFormat!.format(value) - : value; - return ((axis.labelFormat != null && axis.labelFormat != '') - ? axis.labelFormat.replaceAll(RegExp('{value}'), text.toString()) - : text.toString()) as String; -} - -/// Calculate the X value from the current screen point. -double pointToXValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, - Rect rect, double x, double y) { - // ignore: unnecessary_null_comparison - if (axisRenderer != null) { - if (!_requireInvertedAxis) { - return _coefficientToValue(x / rect.width, axisRenderer); - } - return _coefficientToValue(1 - (y / rect.height), axisRenderer); - } - return double.nan; -} - -/// Calculate the Y value from the current screen point. -// ignore: unused_element -double pointToYValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, - Rect rect, double x, double y) { - // ignore: unnecessary_null_comparison - if (axisRenderer != null) { - if (!_requireInvertedAxis) { - return _coefficientToValue(1 - (y / rect.height), axisRenderer); - } - return _coefficientToValue(x / rect.width, axisRenderer); - } - return double.nan; -} - -/// Returns coefficient-based value. -double _coefficientToValue(double coefficient, ChartAxisRenderer axisRenderer) { - double result; - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - coefficient = - axisRendererDetails.axis.isInversed ? 1 - coefficient : coefficient; - result = axisRendererDetails.visibleRange!.minimum + - (axisRendererDetails.visibleRange!.delta * coefficient); - return result; -} - -/// To repaint chart and axes. -void needsRepaintChart( - CartesianStateProperties stateProperties, - List oldChartAxisRenderers, - List oldChartSeriesRenderers) { - if (stateProperties.chartSeries.visibleSeriesRenderers.length == - oldChartSeriesRenderers.length) { - for (int seriesIndex = 0; - seriesIndex < stateProperties.seriesRenderers.length; - seriesIndex++) { - _canRepaintChartSeries( - stateProperties, oldChartSeriesRenderers, seriesIndex); - } - } else { - // ignore: avoid_function_literals_in_foreach_calls - stateProperties.seriesRenderers.forEach( - (CartesianSeriesRenderer seriesRenderer) => - SeriesHelper.getSeriesRendererDetails(seriesRenderer).needsRepaint = - true); - } - if (stateProperties.chartAxis.axisRenderersCollection.length == - oldChartAxisRenderers.length) { - for (int axisIndex = 0; - axisIndex < oldChartAxisRenderers.length; - axisIndex++) { - _canRepaintAxis(stateProperties, oldChartAxisRenderers, axisIndex); - if (stateProperties.chartAxis.needsRepaint) { - break; - } - } - } else { - stateProperties.chartAxis.needsRepaint = true; - } -} - -/// To check series repaint. -void _canRepaintChartSeries(CartesianStateProperties stateProperties, - List oldChartSeriesRenderers, int seriesIndex) { - final CartesianSeriesRenderer seriesRenderer = - stateProperties.seriesRenderers[seriesIndex]; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final CartesianSeries series = seriesRendererDetails.series; - final CartesianSeriesRenderer oldWidgetSeriesRenderer = - oldChartSeriesRenderers[seriesIndex]; - final SeriesRendererDetails oldSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(oldWidgetSeriesRenderer); - final CartesianSeries oldWidgetSeries = - oldSeriesRendererDetails.series; - if (series.animationDuration != oldWidgetSeries.animationDuration || - oldSeriesRendererDetails.stateProperties.chartSeries != - stateProperties.chartSeries || - series.color?.value != oldWidgetSeries.color?.value || - series.width != oldWidgetSeries.width || - series.isVisible != oldWidgetSeries.isVisible || - series.enableTooltip != oldWidgetSeries.enableTooltip || - series.name != oldWidgetSeries.name || - series.gradient != oldWidgetSeries.gradient || - seriesRendererDetails.xAxisDetails!.visibleRange?.delta != - oldSeriesRendererDetails.xAxisDetails!.visibleRange?.delta || - seriesRendererDetails.xAxisDetails!.visibleRange?.maximum != - oldSeriesRendererDetails.xAxisDetails!.visibleRange?.maximum || - seriesRendererDetails.xAxisDetails!.visibleRange?.minimum != - oldSeriesRendererDetails.xAxisDetails!.visibleRange?.minimum || - seriesRendererDetails.xAxisDetails!.visibleRange?.interval != - oldSeriesRendererDetails.xAxisDetails!.visibleRange?.interval || - seriesRendererDetails.xAxisDetails!.axis.isVisible != - oldSeriesRendererDetails.xAxisDetails!.axis.isVisible || - seriesRendererDetails.xAxisDetails!.bounds != - oldSeriesRendererDetails.xAxisDetails!.bounds || - seriesRendererDetails.xAxisDetails!.axis.isInversed != - oldSeriesRendererDetails.xAxisDetails!.axis.isInversed || - seriesRendererDetails.xAxisDetails!.axis.desiredIntervals != - oldSeriesRendererDetails.xAxisDetails!.axis.desiredIntervals || - seriesRendererDetails.xAxisDetails!.axis.enableAutoIntervalOnZooming != - oldSeriesRendererDetails - .xAxisDetails!.axis.enableAutoIntervalOnZooming || - seriesRendererDetails.xAxisDetails!.axis.opposedPosition != - oldSeriesRendererDetails.xAxisDetails!.axis.opposedPosition || - seriesRendererDetails.xAxisDetails!.orientation != - oldSeriesRendererDetails.xAxisDetails!.orientation || - seriesRendererDetails.xAxisDetails!.axis.plotOffset != - oldSeriesRendererDetails.xAxisDetails!.axis.plotOffset || - seriesRendererDetails.xAxisDetails!.axis.rangePadding != - oldSeriesRendererDetails.xAxisDetails!.axis.rangePadding || - seriesRendererDetails.dataPoints.length != - oldSeriesRendererDetails.dataPoints.length || - seriesRendererDetails.yAxisDetails!.visibleRange?.delta != - oldSeriesRendererDetails.yAxisDetails!.visibleRange?.delta || - seriesRendererDetails.yAxisDetails!.visibleRange?.maximum != - oldSeriesRendererDetails.yAxisDetails!.visibleRange?.maximum || - seriesRendererDetails.yAxisDetails!.visibleRange?.minimum != - oldSeriesRendererDetails.yAxisDetails!.visibleRange?.minimum || - seriesRendererDetails.yAxisDetails!.visibleRange?.interval != - oldSeriesRendererDetails.yAxisDetails!.visibleRange?.interval || - seriesRendererDetails.yAxisDetails!.axis.isVisible != - oldSeriesRendererDetails.yAxisDetails!.axis.isVisible || - seriesRendererDetails.yAxisDetails!.bounds != - oldSeriesRendererDetails.yAxisDetails!.bounds || - seriesRendererDetails.yAxisDetails!.axis.isInversed != - oldSeriesRendererDetails.yAxisDetails!.axis.isInversed || - seriesRendererDetails.yAxisDetails!.axis.desiredIntervals != - oldSeriesRendererDetails.yAxisDetails!.axis.desiredIntervals || - seriesRendererDetails.yAxisDetails!.axis.enableAutoIntervalOnZooming != - oldSeriesRendererDetails - .yAxisDetails!.axis.enableAutoIntervalOnZooming || - seriesRendererDetails.yAxisDetails!.axis.opposedPosition != - oldSeriesRendererDetails.yAxisDetails!.axis.opposedPosition || - seriesRendererDetails.yAxisDetails!.orientation != - oldSeriesRendererDetails.yAxisDetails!.orientation || - seriesRendererDetails.yAxisDetails!.axis.plotOffset != - oldSeriesRendererDetails.yAxisDetails!.axis.plotOffset || - seriesRendererDetails.yAxisDetails!.axis.rangePadding != - oldSeriesRendererDetails.yAxisDetails!.axis.rangePadding || - series.animationDuration != oldWidgetSeries.animationDuration || - series.borderColor != oldWidgetSeries.borderColor || - series.borderWidth != oldWidgetSeries.borderWidth || - series.sizeValueMapper != oldWidgetSeries.sizeValueMapper || - series.emptyPointSettings.borderWidth != - oldWidgetSeries.emptyPointSettings.borderWidth || - series.emptyPointSettings.borderColor != - oldWidgetSeries.emptyPointSettings.borderColor || - series.emptyPointSettings.color != - oldWidgetSeries.emptyPointSettings.color || - series.emptyPointSettings.mode != - oldWidgetSeries.emptyPointSettings.mode || - seriesRendererDetails.maximumX != oldSeriesRendererDetails.maximumX || - seriesRendererDetails.maximumY != oldSeriesRendererDetails.maximumY || - seriesRendererDetails.minimumX != oldSeriesRendererDetails.minimumX || - seriesRendererDetails.minimumY != oldSeriesRendererDetails.minimumY || - 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 || - series.markerSettings.borderColor?.value != - oldWidgetSeries.markerSettings.borderColor?.value || - series.markerSettings.isVisible != - oldWidgetSeries.markerSettings.isVisible || - series.markerSettings.borderWidth != - oldWidgetSeries.markerSettings.borderWidth || - series.markerSettings.height != oldWidgetSeries.markerSettings.height || - series.markerSettings.shape != oldWidgetSeries.markerSettings.shape || - series.dataLabelSettings.color?.value != - oldWidgetSeries.dataLabelSettings.color?.value || - series.dataLabelSettings.isVisible != - oldWidgetSeries.dataLabelSettings.isVisible || - series.dataLabelSettings.labelAlignment != - oldWidgetSeries.dataLabelSettings.labelAlignment || - series.dataLabelSettings.opacity != - oldWidgetSeries.dataLabelSettings.opacity || - series.dataLabelSettings.alignment != - 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.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.borderRadius != - oldWidgetSeries.dataLabelSettings.borderRadius) { - seriesRendererDetails.needsRepaint = true; - } else { - seriesRendererDetails.needsRepaint = false; - } -} - -/// To check axis repaint. -void _canRepaintAxis(CartesianStateProperties stateProperties, - List oldChartAxisRenderers, int axisIndex) { - // ignore: unnecessary_null_comparison - if (stateProperties.chartAxis.axisRenderersCollection != null && - stateProperties.chartAxis.axisRenderersCollection.isNotEmpty) { - final ChartAxisRenderer axisRenderer = - stateProperties.chartAxis.axisRenderersCollection[axisIndex]; - final ChartAxisRenderer oldWidgetAxisRenderer = - oldChartAxisRenderers[axisIndex]; - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxisRendererDetails oldAxisRendererDetails = - AxisHelper.getAxisRendererDetails(oldWidgetAxisRenderer); - final ChartAxis axis = axisRendererDetails.axis; - final ChartAxis oldWidgetAxis = oldAxisRendererDetails.axis; - if (axis.rangePadding.index != oldWidgetAxis.rangePadding.index || - axis.plotOffset != oldWidgetAxis.plotOffset || - axisRendererDetails.orientation != oldAxisRendererDetails.orientation || - axis.opposedPosition != oldWidgetAxis.opposedPosition || - axis.minorTicksPerInterval != oldWidgetAxis.minorTicksPerInterval || - axis.desiredIntervals != oldWidgetAxis.desiredIntervals || - axis.isInversed != oldWidgetAxis.isInversed || - axisRendererDetails.bounds != oldAxisRendererDetails.bounds || - axis.majorGridLines.dashArray?.length != - oldWidgetAxis.majorGridLines.dashArray?.length || - axis.majorGridLines.width != oldWidgetAxis.majorGridLines.width || - axis.majorGridLines.color != oldWidgetAxis.majorGridLines.color || - axis.title != oldWidgetAxis.title || - axisRendererDetails.visibleRange?.interval != - oldAxisRendererDetails.visibleRange?.interval || - axisRendererDetails.visibleRange?.minimum != - oldAxisRendererDetails.visibleRange?.minimum || - axisRendererDetails.visibleRange?.maximum != - oldAxisRendererDetails.visibleRange?.maximum || - axisRendererDetails.visibleRange?.delta != - oldAxisRendererDetails.visibleRange?.delta || - axisRendererDetails.isInsideTickPosition != - oldAxisRendererDetails.isInsideTickPosition || - axis.maximumLabels != oldWidgetAxis.maximumLabels || - axis.minorGridLines.dashArray?.length != - oldWidgetAxis.minorGridLines.dashArray?.length || - axis.minorGridLines.width != oldWidgetAxis.minorGridLines.width || - axis.minorGridLines.color != oldWidgetAxis.minorGridLines.color || - axis.tickPosition.index != oldWidgetAxis.tickPosition.index) { - stateProperties.chartAxis.needsRepaint = true; - } else { - stateProperties.chartAxis.needsRepaint = false; - } - } -} - -/// To get interactive tooltip label. -dynamic getInteractiveTooltipLabel( - dynamic value, ChartAxisRenderer axisRenderer) { - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final ChartAxis axis = axisRendererDetails.axis; - if (axisRenderer is CategoryAxisRenderer) { - final CategoryAxisDetails categoryAxisRendererDetails = - axisRendererDetails as CategoryAxisDetails; - value = value < 0 == true ? 0 : value; - value = categoryAxisRendererDetails.labels[ - (value.round() >= categoryAxisRendererDetails.labels.length == true - ? (value.round() > categoryAxisRendererDetails.labels.length == - true - ? categoryAxisRendererDetails.labels.length - 1 - : value - 1) - : value) - .round()]; - } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - final DateTimeCategoryAxisDetails dateTimeAxisDetails = - axisRendererDetails as DateTimeCategoryAxisDetails; - value = value < 0 == true ? 0 : value; - value = dateTimeAxisDetails.labels[ - (value.round() >= dateTimeAxisDetails.labels.length == true - ? (value.round() > dateTimeAxisDetails.labels.length == true - ? dateTimeAxisDetails.labels.length - 1 - : value - 1) - : value) - .round()]; - } else if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _dateTimeAxis = axisRendererDetails.axis as DateTimeAxis; - final num interval = axisRendererDetails.visibleRange!.minimum.ceil(); - final num previousInterval = (axisRendererDetails.visibleLabels.isNotEmpty) - ? axisRendererDetails - .visibleLabels[axisRendererDetails.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = _dateTimeAxis.dateFormat ?? - getDateTimeLabelFormat( - axisRenderer, interval.toInt(), previousInterval.toInt()); - value = - dateFormat.format(DateTime.fromMillisecondsSinceEpoch(value.toInt())); - } else { - value = getLabelValue(value, axis, axis.interactiveTooltip.decimalPlaces); - } - return value; -} - -/// Returns the path of marker shapes. -Path getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, - [SeriesRendererDetails? seriesRendererDetails, - int? index, - TrackballBehavior? trackballBehavior, - Animation? animationController, - ChartSegment? segment]) { - final Path path = Path(); - final Rect rect = segment != null && segment is ScatterSegment - ? Rect.fromLTWH( - position.dx - ((segment.animationFactor * size.width) / 2), - position.dy - ((segment.animationFactor * size.height) / 2), - segment.animationFactor * size.width, - segment.animationFactor * size.height) - : Rect.fromLTWH(position.dx - size.width / 2, - position.dy - size.height / 2, size.width, size.height); - switch (markerType) { - case DataMarkerType.circle: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.circle); - } - break; - case DataMarkerType.rectangle: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.rectangle); - } - break; - case DataMarkerType.image: - { - // ignore: unnecessary_null_comparison - if (seriesRendererDetails!.series != null) { - _loadMarkerImage(seriesRendererDetails.renderer, trackballBehavior); - } - } - break; - case DataMarkerType.pentagon: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.pentagon); - } - break; - case DataMarkerType.verticalLine: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.verticalLine); - } - break; - case DataMarkerType.invertedTriangle: - { - getShapesPath( - path: path, - rect: rect, - shapeType: ShapeMarkerType.invertedTriangle); - } - break; - case DataMarkerType.horizontalLine: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.horizontalLine); - } - break; - case DataMarkerType.diamond: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.diamond); - } - break; - case DataMarkerType.triangle: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.triangle); - } - break; - case DataMarkerType.none: - break; - } - return path; -} - -/// Represents the stacking info class. -class StackingInfo { - /// Creates an instance of stacking info class. - StackingInfo(this.groupName, this.stackingValues); - - /// Holds the group name. - String groupName; - - /// Holds the list of stacking values. - // ignore: prefer_final_fields - List? stackingValues; -} - -class _StackingGroup { - _StackingGroup(this.groupName, this.stackCount); - - String groupName; - int stackCount; -} - -/// To load marker image. -// ignore: avoid_void_async -void _loadMarkerImage(CartesianSeriesRenderer seriesRenderer, - TrackballBehavior? trackballBehavior) async { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - if ((trackballBehavior != null && - trackballBehavior.markerSettings != null && - trackballBehavior.markerSettings!.shape == DataMarkerType.image && - trackballBehavior.markerSettings!.image != null) || - // ignore: unnecessary_null_comparison - (series.markerSettings != null && - (series.markerSettings.isVisible || - seriesRendererDetails.seriesType == 'scatter') && - series.markerSettings.shape == DataMarkerType.image && - series.markerSettings.image != null)) { - calculateImage(seriesRendererDetails.stateProperties, seriesRendererDetails, - trackballBehavior); - } -} - -/// Gets the chart location of the annotation. -ChartLocation getAnnotationLocation(CartesianChartAnnotation annotation, - CartesianStateProperties stateProperties) { - final String? xAxisName = annotation.xAxisName; - final String? yAxisName = annotation.yAxisName; - ChartAxisRenderer? xAxisRenderer, yAxisRenderer; - num? xValue; - Rect axisClipRect; - ChartLocation? location; - if (annotation.coordinateUnit == CoordinateUnit.logicalPixel) { - location = annotation.region == AnnotationRegion.chart - ? ChartLocation(annotation.x.toDouble(), annotation.y.toDouble()) - : ChartLocation( - stateProperties.chartAxis.axisClipRect.left + annotation.x, - stateProperties.chartAxis.axisClipRect.top + annotation.y); - } else if (annotation.coordinateUnit == CoordinateUnit.point) { - for (int i = 0; - i < stateProperties.chartAxis.axisRenderersCollection.length; - i++) { - final ChartAxisRenderer axisRenderer = - stateProperties.chartAxis.axisRenderersCollection[i]; - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (xAxisName == axisRendererDetails.name || - (xAxisName == null && axisRendererDetails.name == 'primaryXAxis')) { - xAxisRenderer = axisRenderer; - if (xAxisRenderer is CategoryAxisRenderer) { - final CategoryAxisDetails categoryAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) - as CategoryAxisDetails; - if (annotation.x != null && - num.tryParse(annotation.x.toString()) != null) { - xValue = num.tryParse(annotation.x.toString())!; - } else if (annotation.x is String) { - xValue = categoryAxisDetails.labels.indexOf(annotation.x); - } - } else if (xAxisRenderer is DateTimeAxisRenderer) { - xValue = annotation.x is DateTime - ? (annotation.x).millisecondsSinceEpoch - : annotation.x; - } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { - final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = - AxisHelper.getAxisRendererDetails(xAxisRenderer) - as DateTimeCategoryAxisDetails; - 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 - ? dateTimeCategoryAxisDetails.labels.indexOf( - dateTimeCategoryAxisDetails.dateFormat - .format(annotation.x)) - : dateTimeCategoryAxisDetails.labels.indexOf(annotation.x)); - } - } else { - xValue = annotation.x is DateTime - ? (annotation.x).millisecondsSinceEpoch - : annotation.x; - } - } else if (yAxisName == axisRendererDetails.name || - (yAxisName == null && axisRendererDetails.name == 'primaryYAxis')) { - yAxisRenderer = axisRenderer; - } - } - - if (xAxisRenderer != null && yAxisRenderer != null) { - axisClipRect = calculatePlotOffset( - stateProperties.chartAxis.axisClipRect, - Offset( - AxisHelper.getAxisRendererDetails(xAxisRenderer).axis.plotOffset, - AxisHelper.getAxisRendererDetails(yAxisRenderer) - .axis - .plotOffset)); - location = calculatePoint( - xValue!, - annotation.y, - AxisHelper.getAxisRendererDetails(xAxisRenderer), - AxisHelper.getAxisRendererDetails(yAxisRenderer), - stateProperties.requireInvertedAxis, - null, - axisClipRect); - } - } else { - if (annotation.region == AnnotationRegion.chart) { - location = ChartLocation( - stateProperties.renderingDetails.chartContainerRect.left + - percentageToValue( - annotation.x.contains('%') == true - ? annotation.x - : annotation.x + '%', - stateProperties - .renderingDetails.chartContainerRect.right)! - .toDouble(), - stateProperties.renderingDetails.chartContainerRect.top + - percentageToValue( - annotation.y.contains('%') == true - ? annotation.y - : annotation.y + '%', - stateProperties - .renderingDetails.chartContainerRect.bottom)! - .toDouble()); - } else { - location = ChartLocation( - stateProperties.chartAxis.axisClipRect.left + - percentageToValue( - annotation.x.contains('%') == true - ? annotation.x - : annotation.x + '%', - stateProperties.chartAxis.axisClipRect.right - - stateProperties.chartAxis.axisClipRect.left)! - .toDouble(), - stateProperties.chartAxis.axisClipRect.top + - percentageToValue( - annotation.y.contains('%') == true - ? annotation.y - : annotation.y + '%', - stateProperties.chartAxis.axisClipRect.bottom)! - .toDouble()); - } - } - return location!; -} - -/// Draw tooltip arrow head. -void drawTooltipArrowhead( - Canvas canvas, - Path backgroundPath, - Paint fillPaint, - Paint strokePaint, - double x1, - double y1, - double x2, - double y2, - double x3, - double y3, - double x4, - double y4) { - backgroundPath.moveTo(x1, y1); - backgroundPath.lineTo(x2, y2); - backgroundPath.lineTo(x3, y3); - backgroundPath.lineTo(x4, y4); - backgroundPath.lineTo(x1, y1); - fillPaint.isAntiAlias = true; - canvas.drawPath(backgroundPath, strokePaint); - canvas.drawPath(backgroundPath, fillPaint); -} - -/// Calculate rounded rect from rect and corner radius. -RRect getRoundedCornerRect(Rect rect, double cornerRadius) => - RRect.fromRectAndCorners( - rect, - bottomLeft: Radius.circular(cornerRadius), - bottomRight: Radius.circular(cornerRadius), - topLeft: Radius.circular(cornerRadius), - topRight: Radius.circular(cornerRadius), - ); - -/// Calculate the X value from the current screen point. -double pointToXVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, - Rect rect, double x, double y) { - // ignore: unnecessary_null_comparison - if (axisRenderer != null) { - return _coefficientToValue(x / rect.width, axisRenderer); - } - return double.nan; -} - -/// Calculate the Y value from the current screen point. -double pointToYVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, - Rect rect, double x, double y) { - // ignore: unnecessary_null_comparison - if (axisRenderer != null) { - return _coefficientToValue(1 - (y / rect.height), axisRenderer); - } - return double.nan; -} - -/// Gets the x position of validated rect. -Rect validateRectXPosition( - Rect labelRect, CartesianStateProperties stateProperties) { - Rect validatedRect = labelRect; - if (labelRect.right >= stateProperties.chartAxis.axisClipRect.right) { - validatedRect = Rect.fromLTRB( - labelRect.left - - (labelRect.right - stateProperties.chartAxis.axisClipRect.right), - labelRect.top, - stateProperties.chartAxis.axisClipRect.right, - labelRect.bottom); - } else if (labelRect.left <= stateProperties.chartAxis.axisClipRect.left) { - validatedRect = Rect.fromLTRB( - stateProperties.chartAxis.axisClipRect.left, - labelRect.top, - labelRect.right + - (stateProperties.chartAxis.axisClipRect.left - labelRect.left), - labelRect.bottom); - } - return validatedRect; -} - -/// Gets the y position of validated rect. -Rect validateRectYPosition( - Rect labelRect, CartesianStateProperties stateProperties) { - Rect validatedRect = labelRect; - if (labelRect.bottom >= stateProperties.chartAxis.axisClipRect.bottom) { - validatedRect = Rect.fromLTRB( - labelRect.left, - labelRect.top - - (labelRect.bottom - stateProperties.chartAxis.axisClipRect.bottom), - labelRect.right, - stateProperties.chartAxis.axisClipRect.bottom); - } else if (labelRect.top <= stateProperties.chartAxis.axisClipRect.top) { - validatedRect = Rect.fromLTRB( - labelRect.left, - stateProperties.chartAxis.axisClipRect.top, - labelRect.right, - labelRect.bottom + - (stateProperties.chartAxis.axisClipRect.top - labelRect.top)); - } - return validatedRect; -} - -/// This method will validate whether the tooltip exceeds the screen or not. -Rect validateRectBounds(Rect tooltipRect, Rect boundary) { - Rect validatedRect = tooltipRect; - double difference = 0; - - /// Padding between the corners. - const double padding = 0.5; - - if (tooltipRect.left < boundary.left) { - difference = (boundary.left - tooltipRect.left) + padding; - validatedRect = Rect.fromLTRB( - validatedRect.left + difference, - validatedRect.top, - validatedRect.right + difference, - validatedRect.bottom); - } - if (tooltipRect.right > boundary.right) { - difference = (tooltipRect.right - boundary.right) + padding; - validatedRect = Rect.fromLTRB( - validatedRect.left - difference, - validatedRect.top, - validatedRect.right - difference, - validatedRect.bottom); - } - if (tooltipRect.top < boundary.top) { - difference = (boundary.top - tooltipRect.top) + padding; - validatedRect = Rect.fromLTRB( - validatedRect.left, - validatedRect.top + difference, - validatedRect.right, - validatedRect.bottom + difference); - } - - if (tooltipRect.bottom > boundary.bottom) { - difference = (tooltipRect.bottom - boundary.bottom) + padding; - validatedRect = Rect.fromLTRB( - validatedRect.left, - validatedRect.top - difference, - validatedRect.right, - validatedRect.bottom - difference); - } - - return validatedRect; -} - -/// To render a rect for stacked series. -void renderStackingRectSeries( - Paint? fillPaint, - Paint? strokePaint, - Path path, - double animationFactor, - CartesianSeriesRenderer seriesRenderer, - Canvas canvas, - RRect segmentRect, - CartesianChartPoint _currentPoint, - int currentSegmentIndex) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final StackedSeriesBase series = - seriesRendererDetails.series as StackedSeriesBase; - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[currentSegmentIndex], - seriesRendererDetails.chart); - } - if (fillPaint != null) { - series.animationDuration > 0 - ? animateStackedRectSeries( - canvas, - segmentRect, - fillPaint, - seriesRendererDetails, - animationFactor, - _currentPoint, - seriesRendererDetails.stateProperties) - : canvas.drawRRect(segmentRect, fillPaint); - } - if (strokePaint != null) { - if (series.dashArray[0] != 0 && series.dashArray[1] != 0) { - final XyDataSeries _series = - seriesRendererDetails.series as XyDataSeries; - drawDashedLine(canvas, _series.dashArray, strokePaint, path); - } else { - series.animationDuration > 0 - ? animateStackedRectSeries( - canvas, - segmentRect, - strokePaint, - seriesRendererDetails, - animationFactor, - _currentPoint, - seriesRendererDetails.stateProperties) - : canvas.drawRRect(segmentRect, strokePaint); - } - } -} - -/// Draw stacked area path. -void drawStackedAreaPath( - Path _path, - Path _strokePath, - CartesianSeriesRenderer seriesRenderer, - Canvas canvas, - Paint fillPaint, - Paint strokePaint) { - Rect _pathRect; - dynamic stackedAreaSegment; - _pathRect = _path.getBounds(); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final XyDataSeries _series = - seriesRendererDetails.series as XyDataSeries; - stackedAreaSegment = seriesRendererDetails.segments[0]; - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(stackedAreaSegment); - segmentProperties.pathRect = _pathRect; - if (seriesRendererDetails.isSelectionEnable == true) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRendererDetails.selectionBehaviorRenderer; - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) - .selectionRenderer - ?.checkWithSelectionState( - seriesRendererDetails.segments[0], seriesRendererDetails.chart); - } - canvas.drawPath( - _path, - (_series.gradient == null) - ? fillPaint - : seriesRendererDetails.segments[0].getFillPaint()); - strokePaint = seriesRendererDetails.segments[0].getStrokePaint(); - - if (strokePaint.color != Colors.transparent) { - drawDashedLine(canvas, _series.dashArray, strokePaint, _strokePath); - } -} - -/// Render stacked line series. -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); - drawDashedLine(canvas, series.dashArray, strokePaint, path); -} - -/// Painter method for stacked area series. -void stackedAreaPainter( - Canvas canvas, - dynamic seriesRenderer, - CartesianStateProperties _stateProperties, - Animation? seriesAnimation, - Animation? chartElementAnimation, - PainterKey painterKey) { - Rect clipRect, axisClipRect; - final int seriesIndex = painterKey.index; - final SfCartesianChart chart = _stateProperties.chart; - final RenderingDetails _renderingDetails = _stateProperties.renderingDetails; - seriesRenderer.seriesRendererDetails - .storeSeriesProperties(_stateProperties.chartState, seriesIndex); - double animationFactor; - final num? crossesAt = getCrossesAtValue(seriesRenderer, _stateProperties); - final List> dataPoints = - seriesRenderer.seriesRendererDetails.dataPoints; - - /// Clip rect will be added for series. - if (seriesRenderer.seriesRendererDetails.visible! == true) { - final dynamic series = seriesRenderer.seriesRendererDetails.series; - canvas.save(); - axisClipRect = calculatePlotOffset( - _stateProperties.chartAxis.axisClipRect, - Offset( - seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, - seriesRenderer - .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); - canvas.clipRect(axisClipRect); - animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; - if (seriesRenderer.seriesRendererDetails.reAnimate == true || - ((!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled) || - !_stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0 == true)) { - performLinearAnimation( - _stateProperties, - seriesRenderer.seriesRendererDetails.xAxisDetails!.axis, - canvas, - animationFactor); - } - - final Path _path = Path(), _strokePath = Path(); - final Rect rect = _stateProperties.chartAxis.axisClipRect; - ChartLocation point1, point2; - final ChartAxisRendererDetails xAxisDetails = - seriesRenderer.seriesRendererDetails.xAxisDetails!, - yAxisDetails = seriesRenderer.seriesRendererDetails.yAxisDetails!; - CartesianChartPoint? point; - final dynamic _series = seriesRenderer.seriesRendererDetails.series; - final List _points = []; - if (dataPoints.isNotEmpty) { - int startPoint = 0; - final StackedValues stackedValues = - seriesRenderer.seriesRendererDetails.stackingValues[0]; - List seriesRendererCollection; - CartesianSeriesRenderer previousSeriesRenderer; - seriesRendererCollection = findSeriesCollection(_stateProperties); - point1 = calculatePoint( - dataPoints[0].xValue, - math_lib.max(yAxisDetails.visibleRange!.minimum, - crossesAt ?? stackedValues.startValues[0]), - xAxisDetails, - yAxisDetails, - _stateProperties.requireInvertedAxis, - _series, - rect); - _path.moveTo(point1.x, point1.y); - _strokePath.moveTo(point1.x, point1.y); - if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null || - seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == - true) { - seriesRenderer.seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRenderer.seriesRendererDetails - .setSeriesProperties(seriesRenderer.seriesRendererDetails); - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { - point = dataPoints[pointIndex]; - seriesRenderer.seriesRendererDetails.calculateRegionData( - _stateProperties, seriesRenderer, seriesIndex, point, pointIndex); - if (point.isVisible) { - point1 = calculatePoint( - dataPoints[pointIndex].xValue, - stackedValues.endValues[pointIndex], - xAxisDetails, - yAxisDetails, - _stateProperties.requireInvertedAxis, - _series, - rect); - _points.add(Offset(point1.x, point1.y)); - _path.lineTo(point1.x, point1.y); - _strokePath.lineTo(point1.x, point1.y); - } else { - if (_series.emptyPointSettings.mode != EmptyPointMode.drop) { - for (int j = pointIndex - 1; j >= startPoint; j--) { - point2 = calculatePoint( - dataPoints[j].xValue, - crossesAt ?? stackedValues.startValues[j], - xAxisDetails, - yAxisDetails, - _stateProperties.requireInvertedAxis, - _series, - rect); - _path.lineTo(point2.x, point2.y); - if (_series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(point1.x, point2.y); - } else if (_series.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(point2.x, point2.y); - } - } - if (dataPoints.length > pointIndex + 1 && - // ignore: unnecessary_null_comparison - dataPoints[pointIndex + 1] != null && - dataPoints[pointIndex + 1].isVisible) { - point1 = calculatePoint( - dataPoints[pointIndex + 1].xValue, - crossesAt ?? stackedValues.startValues[pointIndex + 1], - xAxisDetails, - yAxisDetails, - _stateProperties.requireInvertedAxis, - _series, - rect); - _path.moveTo(point1.x, point1.y); - _strokePath.moveTo(point1.x, point1.y); - } - startPoint = pointIndex + 1; - } - } - if (pointIndex >= dataPoints.length - 1) { - seriesRenderer._createSegments( - painterKey.index, chart, animationFactor, _points); - } - } - for (int j = dataPoints.length - 1; j >= startPoint; j--) { - previousSeriesRenderer = - getPreviousSeriesRenderer(seriesRendererCollection, seriesIndex); - final SeriesRendererDetails previousSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(previousSeriesRenderer); - if (previousSeriesRendererDetails.series.emptyPointSettings.mode != - EmptyPointMode.drop || - previousSeriesRendererDetails.dataPoints[j].isVisible == true) { - point2 = calculatePoint( - dataPoints[j].xValue, - crossesAt ?? stackedValues.startValues[j], - xAxisDetails, - yAxisDetails, - _stateProperties.requireInvertedAxis, - _series, - rect); - _path.lineTo(point2.x, point2.y); - if (_series.borderDrawMode == BorderDrawMode.excludeBottom) { - _strokePath.lineTo(point1.x, point2.y); - } else if (_series.borderDrawMode == BorderDrawMode.all) { - _strokePath.lineTo(point2.x, point2.y); - } - } - } - } - // ignore: unnecessary_null_comparison - if (_path != null && - seriesRenderer.seriesRendererDetails.segments != null && - seriesRenderer.seriesRendererDetails.segments.isNotEmpty == true) { - final dynamic areaSegment = - seriesRenderer.seriesRendererDetails.segments[0]; - seriesRenderer.seriesRendererDetails.drawSegment( - canvas, - areaSegment - .._path = _path - .._strokePath = _strokePath); - } - - clipRect = calculatePlotOffset( - Rect.fromLTRB( - rect.left - _series.markerSettings.width, - rect.top - _series.markerSettings.height, - rect.right + _series.markerSettings.width, - rect.bottom + _series.markerSettings.height), - Offset( - seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, - seriesRenderer - .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); - canvas.restore(); - if ((_series.animationDuration <= 0 == true || - !_renderingDetails.initialRender! || - animationFactor >= _stateProperties.seriesDurationFactor) && - (_series.markerSettings.isVisible == true || - _series.dataLabelSettings.isVisible == true || - _series.errorBarSettings.isVisible! == true)) { - canvas.clipRect(clipRect); - seriesRenderer.seriesRendererDetails - .renderSeriesElements(chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - _stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } -} - -/// To get previous series renderer. -CartesianSeriesRenderer getPreviousSeriesRenderer( - List seriesRendererCollection, num seriesIndex) { - for (int i = 0; i < seriesRendererCollection.length; i++) { - if (seriesIndex == - seriesRendererCollection.indexOf(seriesRendererCollection[i]) && - i != 0) { - return seriesRendererCollection[i - 1]; - } - } - return seriesRendererCollection[0]; -} - -/// Rect painter for stacked series. -void stackedRectPainter(Canvas canvas, dynamic seriesRenderer, - CartesianStateProperties _stateProperties, PainterKey painterKey) { - if (seriesRenderer.seriesRendererDetails.visible! == true) { - canvas.save(); - Rect clipRect, axisClipRect; - CartesianChartPoint point; - final XyDataSeries series = - seriesRenderer.seriesRendererDetails.series; - final RenderingDetails _renderingDetails = - _stateProperties.renderingDetails; - final int seriesIndex = painterKey.index; - final Animation seriesAnimation = - seriesRenderer.seriesRendererDetails.seriesAnimation; - final Animation chartElementAnimation = - seriesRenderer.seriesRendererDetails.seriesElementAnimation; - seriesRenderer.seriesRendererDetails - .storeSeriesProperties(_stateProperties.chartState, seriesIndex); - double animationFactor; - // ignore: unnecessary_null_comparison - animationFactor = seriesAnimation != null && - (seriesRenderer.seriesRendererDetails.reAnimate == true || - (!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled))) - ? seriesAnimation.value - : 1; - - /// Clip rect will be added for series. - axisClipRect = calculatePlotOffset( - _stateProperties.chartAxis.axisClipRect, - Offset( - seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, - seriesRenderer - .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); - canvas.clipRect(axisClipRect); - int segmentIndex = -1; - if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null || - seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == - true) { - seriesRenderer.seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRenderer.seriesRendererDetails - .setSeriesProperties(seriesRenderer.seriesRendererDetails); - for (int pointIndex = 0; - pointIndex < seriesRenderer.seriesRendererDetails.dataPoints.length; - pointIndex++) { - point = seriesRenderer.seriesRendererDetails.dataPoints[pointIndex]; - seriesRenderer.seriesRendererDetails.calculateRegionData(_stateProperties, - seriesRenderer, painterKey.index, point, pointIndex); - if (point.isVisible && !point.isGap) { - seriesRenderer.seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - point, segmentIndex += 1, painterKey.index, animationFactor)); - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - _stateProperties.chartAxis.axisClipRect.left - - series.markerSettings.width, - _stateProperties.chartAxis.axisClipRect.top - - series.markerSettings.height, - _stateProperties.chartAxis.axisClipRect.right + - series.markerSettings.width, - _stateProperties.chartAxis.axisClipRect.bottom + - series.markerSettings.height), - Offset( - seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, - seriesRenderer - .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !_renderingDetails.initialRender! || - animationFactor >= _stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - canvas.clipRect(clipRect); - seriesRenderer.seriesRendererDetails.renderSeriesElements( - _stateProperties.chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - _stateProperties.setPainterKey(painterKey.index, painterKey.name, true); - } - } -} - -/// Painter for stacked line series. -void stackedLinePainter( - Canvas canvas, - dynamic seriesRenderer, - Animation? seriesAnimation, - CartesianStateProperties _stateProperties, - Animation? chartElementAnimation, - PainterKey painterKey) { - Rect clipRect; - double animationFactor; - final RenderingDetails _renderingDetails = _stateProperties.renderingDetails; - if (seriesRenderer.seriesRendererDetails.visible! == true) { - final XyDataSeries series = - seriesRenderer.seriesRendererDetails.series; - canvas.save(); - animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; - final int seriesIndex = painterKey.index; - StackedValues? stackedValues; - seriesRenderer.seriesRendererDetails - .storeSeriesProperties(_stateProperties.chartState, seriesIndex); - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - if (seriesRenderer is StackedSeriesRenderer && - seriesRendererDetails.stackingValues.isNotEmpty == true) { - stackedValues = seriesRendererDetails.stackingValues[0]; - } - final Rect rect = seriesRenderer - .seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - - /// Clip rect will be added for series. - final Rect axisClipRect = calculatePlotOffset( - rect, - Offset( - seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, - seriesRenderer - .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); - canvas.clipRect(axisClipRect); - if (seriesRenderer.seriesRendererDetails.reAnimate == true || - ((!(_renderingDetails.widgetNeedUpdate || - _renderingDetails.isLegendToggled) || - !_stateProperties.oldSeriesKeys.contains(series.key)) && - series.animationDuration > 0)) { - performLinearAnimation( - _stateProperties, - seriesRenderer.seriesRendererDetails.xAxisDetails!.axis, - canvas, - animationFactor); - } - - int segmentIndex = -1; - double? currentCummulativePos, nextCummulativePos; - CartesianChartPoint? startPoint, - endPoint, - currentPoint, - _nextPoint; - if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null || - seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == - true) { - seriesRenderer.seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRenderer.seriesRendererDetails - .setSeriesProperties(seriesRenderer.seriesRendererDetails); - for (int pointIndex = 0; - pointIndex < seriesRenderer.seriesRendererDetails.dataPoints.length; - pointIndex++) { - currentPoint = - seriesRenderer.seriesRendererDetails.dataPoints[pointIndex]; - seriesRenderer.seriesRendererDetails.calculateRegionData(_stateProperties, - seriesRenderer, seriesIndex, currentPoint, pointIndex); - if ((currentPoint!.isVisible && !currentPoint.isGap) && - startPoint == null && - stackedValues != null) { - startPoint = currentPoint; - currentCummulativePos = stackedValues.endValues[pointIndex]; - } - if (pointIndex + 1 < - seriesRenderer.seriesRendererDetails.dataPoints.length) { - _nextPoint = - seriesRenderer.seriesRendererDetails.dataPoints[pointIndex + 1]; - if (startPoint != null && _nextPoint!.isGap) { - startPoint = null; - } else if (_nextPoint!.isVisible && - !_nextPoint.isGap && - stackedValues != null) { - endPoint = _nextPoint; - nextCummulativePos = stackedValues.endValues[pointIndex + 1]; - } - } - - if (startPoint != null && endPoint != null) { - seriesRenderer.seriesRendererDetails.drawSegment( - canvas, - seriesRenderer._createSegments( - startPoint, - endPoint, - segmentIndex += 1, - seriesIndex, - animationFactor, - currentCummulativePos!, - nextCummulativePos!)); - endPoint = startPoint = null; - } - } - clipRect = calculatePlotOffset( - Rect.fromLTRB( - rect.left - series.markerSettings.width, - rect.top - series.markerSettings.height, - rect.right + series.markerSettings.width, - rect.bottom + series.markerSettings.height), - Offset( - seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, - seriesRenderer - .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); - canvas.restore(); - if ((series.animationDuration <= 0 || - !_renderingDetails.initialRender! || - animationFactor >= _stateProperties.seriesDurationFactor) && - (series.markerSettings.isVisible || - series.dataLabelSettings.isVisible)) { - canvas.clipRect(clipRect); - seriesRenderer.seriesRendererDetails.renderSeriesElements( - _stateProperties.chart, canvas, chartElementAnimation); - } - if (animationFactor >= 1) { - _stateProperties.setPainterKey(seriesIndex, painterKey.name, true); - } - } -} - -/// To find MonotonicSpline. -List? _getMonotonicSpline(List xValues, List yValues, - List yCoef, int dataCount, List dx) { - final int count = dataCount; - int index = -1; - - 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] = 0; - } - } - // Add the first and last coefficient value as Slope[0] and Slope[n-1] - if (slope.isEmpty) { - return null; - } - - slope[0] == double.nan - ? coefficient[++index] = 0 - : coefficient[++index] = slope[0]; - - for (int i = 0; i < dx.length - 1; i++) { - if (slope.length > 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 interPoint = firstPoint + _nextPoint; - coefficient[++index] = 3 * - interPoint / - (((interPoint + _nextPoint) / m) + - ((interPoint + firstPoint) / next)); - } - } - } - } - slope[slope.length - 1] == double.nan - ? coefficient[++index] = 0 - : coefficient[++index] = slope[slope.length - 1]; - - yCoef.addAll(coefficient); - - return yCoef; -} - -/// To find CardinalSpline. -List _getCardinalSpline(List xValues, List yValues, - List yCoef, int dataCount, double tension) { - if (tension < 0.1) { - tension = 0; - } else if (tension > 1) { - tension = 1; - } - - final int count = dataCount; - - final List tangentsX = List.filled(count, null); - - for (int i = 0; i < count; i++) { - if (i == 0 && xValues.length > 2) { - tangentsX[i] = tension * (xValues[i + 2] - xValues[i]); - } else if (i == count - 1 && count - 3 >= 0) { - tangentsX[i] = tension * (xValues[count - 1] - xValues[count - 3]); - } else if (i - 1 >= 0 && xValues.length > i + 1) { - tangentsX[i] = tension * (xValues[i + 1] - xValues[i - 1]); - } - - if (tangentsX[i] == double.nan) { - tangentsX[i] = 0; - } - } - - yCoef.addAll(tangentsX); - return yCoef; -} - -/// To find NaturalSpline. -List naturalSpline(List xValues, List yValues, - List yCoeff, int dataCount, SplineType? splineType) { - const double a = 6; - final int count = dataCount; - int i, k; - final List yCoef = List.filled(count, null); - double d1, d2, d3, dy1, dy2; - num p; - - 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]); - final num dn = (xValues[count - 1] - xValues[count - 2]) / - (yValues[count - 1] - yValues[count - 2]); - yCoef[0] = - (3 * (yValues[1] - yValues[0]) / (xValues[1] - xValues[0])) - (3 * d0); - yCoef[count - 1] = (3 * dn) - - ((3 * (yValues[count - 1] - yValues[count - 2])) / - (xValues[count - 1] - xValues[count - 2])); - - if (yCoef[0] == double.infinity || yCoef[0] == double.nan) { - yCoef[0] = 0; - } - - if (yCoef[count - 1] == double.infinity || yCoef[count - 1] == double.nan) { - yCoef[count - 1] = 0; - } - } else { - yCoef[0] = u[0] = 0; - yCoef[count - 1] = 0; - } - - for (i = 1; i < count - 1; i++) { - yCoef[i] = 0; - if ((yValues[i + 1] != double.nan) && - (yValues[i - 1] != double.nan) && - (yValues[i] != double.nan) && - // ignore: unnecessary_null_comparison - yValues[i + 1] != null && - // ignore: unnecessary_null_comparison - xValues[i + 1] != null && - // ignore: unnecessary_null_comparison - yValues[i - 1] != null && - // ignore: unnecessary_null_comparison - xValues[i - 1] != null && - // ignore: unnecessary_null_comparison - xValues[i] != null && - // ignore: unnecessary_null_comparison - yValues[i] != null) { - 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 { - p = 1 / ((d1 * yCoef[i - 1]!) + (2 * d2)); - yCoef[i] = -p * d3; - // ignore: unnecessary_null_comparison - if (d1 != null && u[i - 1] != null) { - u[i] = p * ((a * ((dy1 / d3) - (dy2 / d1))) - (d1 * u[i - 1]!)); - } - } - } - } - - 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]!; - } - } - - yCoeff.addAll(yCoef); - return yCoeff; -} - -/// To find Monotonic ControlPoints. -List _calculateMonotonicControlPoints( - double pointX, - double pointY, - double pointX1, - double pointY1, - double coefficientY, - double coefficientY1, - double dx, - List controlPoints) { - final num value = dx / 3; - 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); - 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, - CartesianSeriesRenderer seriesRenderer, - List controlPoints) { - double yCoefficient = coefficientY; - double y1Coefficient = coefficientY1; - if (SeriesHelper.getSeriesRendererDetails(seriesRenderer).xAxisDetails - is DateTimeAxisDetails) { - yCoefficient = coefficientY / dateTimeInterval(seriesRenderer); - y1Coefficient = coefficientY1 / dateTimeInterval(seriesRenderer); - } - final List values = List.filled(4, null); - values[0] = pointX + (coefficientY / 3); - values[1] = pointY + (yCoefficient / 3); - values[2] = pointX1 - (coefficientY1 / 3); - 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 = - SeriesHelper.getSeriesRendererDetails(seriesRenderer).xAxisDetails!.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; -} - -/// Triggers marker event. -MarkerRenderArgs? triggerMarkerRenderEvent( - SeriesRendererDetails seriesRendererDetails, - Size size, - DataMarkerType markerType, - int pointIndex, - Animation? animationController, - [ChartSegment? segment]) { - MarkerRenderArgs? markerargs; - final int seriesIndex = seriesRendererDetails - .stateProperties.chartSeries.visibleSeriesRenderers - .indexOf(seriesRendererDetails.renderer); - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final MarkerSettingsRenderer markerSettingsRenderer = - seriesRendererDetails.markerSettingsRenderer!; - markerSettingsRenderer.color = series.markerSettings.color; - final bool isScatter = segment != null && segment is ScatterSegment; - if (seriesRendererDetails.chart.onMarkerRender != null) { - markerargs = MarkerRenderArgs( - pointIndex, - seriesIndex, - seriesRendererDetails - .visibleDataPoints![pointIndex].overallDataPointIndex!); - markerargs.markerHeight = size.height; - markerargs.markerWidth = size.width; - markerargs.shape = markerType; - markerargs.color = - isScatter ? segment.fillPaint!.color : markerSettingsRenderer.color; - markerargs.borderColor = isScatter - ? segment.strokePaint!.color - : markerSettingsRenderer.borderColor; - markerargs.borderWidth = isScatter - ? segment.strokePaint!.strokeWidth - : markerSettingsRenderer.borderWidth; - seriesRendererDetails.chart.onMarkerRender!(markerargs); - size = Size(markerargs.markerWidth, markerargs.markerHeight); - markerType = markerargs.shape; - markerSettingsRenderer.color = markerargs.color; - markerSettingsRenderer.borderColor = markerargs.borderColor; - markerSettingsRenderer.borderWidth = markerargs.borderWidth; - - if (isScatter) { - segment.fillPaint!.color = (markerargs.color != null && - segment.fillPaint!.color != markerargs.color - ? markerargs.color - : segment.fillPaint!.color)!; - segment.strokePaint!.color = (markerargs.borderColor != null && - segment.strokePaint!.color != markerargs.borderColor - ? markerargs.borderColor - : segment.strokePaint!.color)!; - // ignore: unnecessary_null_comparison - segment.strokePaint!.strokeWidth = markerargs.borderWidth != null && - segment.strokePaint!.strokeWidth != markerargs.borderWidth - ? markerargs.borderWidth - : segment.strokePaint!.strokeWidth; - } - } - return markerargs; -} - -/// To find Natural ControlPoints. -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 nextX = xValues[i + 1].toDouble(); - final double nextY = yValues[i + 1]!.toDouble(); - const double oneThird = 1 / 3.0; - double deltaX2 = nextX - x; - deltaX2 = deltaX2 * deltaX2; - final double dx1 = (2 * x) + nextX; - final double dx2 = x + (2 * nextX); - final double dy1 = (2 * y) + nextY; - final double dy2 = y + (2 * nextY); - final double y1 = - oneThird * (dy1 - (oneThird * deltaX2 * (yCoef + (0.5 * nextyCoef)))); - final double y2 = - 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 SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final dynamic series = seriesRendererDetails.series; - List? yCoef = []; - List? lowCoef = []; - List? highCoef = []; - final List xValues = []; - final List yValues = []; - final List highValues = []; - final List lowValues = []; - final SplineType? splineType = series.splineType; - int i; - for (i = 0; i < seriesRendererDetails.dataPoints.length; i++) { - xValues.add(seriesRendererDetails.dataPoints[i].xValue); - if (seriesRenderer is SplineAreaSeriesRenderer || - seriesRenderer is SplineSeriesRenderer) { - yValues.add(seriesRendererDetails.dataPoints[i].yValue); - } else if (seriesRenderer is SplineRangeAreaSeriesRenderer) { - highValues.add(seriesRendererDetails.dataPoints[i].high); - lowValues.add(seriesRendererDetails.dataPoints[i].low); - } - } - - if (xValues.isNotEmpty) { - final List dx = List.filled(xValues.length - 1, null); - - /// Check the type of spline. - if (splineType == SplineType.monotonic) { - if (seriesRenderer is SplineAreaSeriesRenderer || - seriesRenderer is SplineSeriesRenderer) { - yCoef = - _getMonotonicSpline(xValues, yValues, yCoef, xValues.length, dx); - } else { - lowCoef = _getMonotonicSpline( - xValues, lowValues, lowCoef, xValues.length, dx); - highCoef = _getMonotonicSpline( - xValues, highValues, highCoef, xValues.length, dx); - } - } else if (splineType == SplineType.cardinal) { - if (series is SplineAreaSeries || series is SplineSeries) { - yCoef = _getCardinalSpline(xValues, yValues, yCoef, xValues.length, - series.cardinalSplineTension); - } else { - lowCoef = _getCardinalSpline(xValues, lowValues, lowCoef, - xValues.length, series.cardinalSplineTension); - highCoef = _getCardinalSpline(xValues, highValues, highCoef, - xValues.length, series.cardinalSplineTension); - } - } else { - if (series is SplineAreaSeries || - seriesRenderer is SplineSeriesRenderer) { - yCoef = - naturalSpline(xValues, yValues, yCoef, xValues.length, splineType); - } else { - lowCoef = naturalSpline( - xValues, lowValues, lowCoef, xValues.length, splineType); - highCoef = naturalSpline( - xValues, highValues, highCoef, xValues.length, splineType); - } - } - (seriesRenderer is SplineAreaSeriesRenderer || - seriesRenderer is SplineSeriesRenderer) - ? _updateSplineAreaControlPoints( - seriesRenderer, splineType, xValues, yValues, yCoef, dx) - : _findSplineRangeAreaControlPoint( - seriesRenderer as SplineRangeAreaSeriesRenderer, - splineType, - xValues, - lowValues, - highValues, - dx, - lowCoef, - highCoef); - } -} - -/// To update the dynamic points of the spline area. -void _updateSplineAreaControlPoints( - dynamic seriesRenderer, - SplineType? splineType, - List xValues, - List yValues, - List? yCoef, - List? dx) { - double x, y, nextX, nextY; - List controlPoints; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - for (int pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { - controlPoints = []; - // ignore: unnecessary_null_comparison - if (xValues[pointIndex] != null && - // ignore: unnecessary_null_comparison - yValues[pointIndex] != null && - // ignore: unnecessary_null_comparison - xValues[pointIndex + 1] != null && - // ignore: unnecessary_null_comparison - yValues[pointIndex + 1] != null) { - 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(), - controlPoints); - seriesRendererDetails.drawControlPoints.add(controlPoints); - } else if (splineType == SplineType.cardinal) { - controlPoints = _calculateCardinalControlPoints( - x, - y, - nextX, - nextY, - yCoef![pointIndex]!.toDouble(), - yCoef[pointIndex + 1]!.toDouble(), - seriesRenderer, - controlPoints); - seriesRendererDetails.drawControlPoints.add(controlPoints); - } else { - controlPoints = calculateControlPoints( - xValues, - yValues, - yCoef![pointIndex]!.toDouble(), - yCoef[pointIndex + 1]!.toDouble(), - pointIndex, - controlPoints); - seriesRendererDetails.drawControlPoints.add(controlPoints); - } - } - } -} - -/// Calculate spline range area control point. -void _findSplineRangeAreaControlPoint( - SplineRangeAreaSeriesRenderer seriesRenderer, - SplineType? splineType, - List xValues, - List lowValues, - List highValues, - 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 = []; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - // ignore: unnecessary_null_comparison - if (xValues[pointIndex] != null && - seriesRendererDetails.dataPoints[pointIndex].low != null && - seriesRendererDetails.dataPoints[pointIndex].high != null && - // ignore: unnecessary_null_comparison - xValues[pointIndex + 1] != null && - seriesRendererDetails.dataPoints[pointIndex + 1].low != null && - seriesRendererDetails.dataPoints[pointIndex + 1].high != null) { - x = xValues[pointIndex].toDouble(); - low = seriesRendererDetails.dataPoints[pointIndex].low.toDouble(); - high = seriesRendererDetails.dataPoints[pointIndex].high.toDouble(); - nextX = xValues[pointIndex + 1].toDouble(); - nextlow = seriesRendererDetails.dataPoints[pointIndex + 1].low.toDouble(); - nexthigh = - seriesRendererDetails.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(), - controlPointslow); - seriesRendererDetails.drawLowControlPoints.add(controlPointslow); - controlPointshigh = _calculateMonotonicControlPoints( - x, - high, - nextX, - nexthigh, - highCoef![pointIndex]!.toDouble(), - highCoef[pointIndex + 1]!.toDouble(), - dx[pointIndex]!.toDouble(), - controlPointshigh); - seriesRendererDetails.drawHighControlPoints.add(controlPointshigh); - } else if (splineType == SplineType.cardinal) { - controlPointslow = _calculateCardinalControlPoints( - x, - low, - nextX, - nextlow, - lowCoef![pointIndex]!.toDouble(), - lowCoef[pointIndex + 1]!.toDouble(), - seriesRenderer, - controlPointslow); - seriesRendererDetails.drawLowControlPoints.add(controlPointslow); - controlPointshigh = _calculateCardinalControlPoints( - x, - high, - nextX, - nexthigh, - highCoef![pointIndex]!.toDouble(), - highCoef[pointIndex + 1]!.toDouble(), - seriesRenderer, - controlPointshigh); - seriesRendererDetails.drawHighControlPoints.add(controlPointshigh); - } else { - controlPointslow = calculateControlPoints( - xValues, - lowValues, - lowCoef![pointIndex]!.toDouble(), - lowCoef[pointIndex + 1]!.toDouble(), - pointIndex, - controlPointslow); - seriesRendererDetails.drawLowControlPoints.add(controlPointslow); - controlPointshigh = calculateControlPoints( - xValues, - highValues, - highCoef![pointIndex]!.toDouble(), - highCoef[pointIndex + 1]!.toDouble(), - pointIndex, - controlPointshigh); - seriesRendererDetails.drawHighControlPoints.add(controlPointshigh); - } - } - } -} - -/// Get the old axis (for stock chart animation). -ChartAxisRenderer? getOldAxisRenderer(ChartAxisRenderer axisRenderer, - List oldAxisRendererList) { - for (int i = 0; i < oldAxisRendererList.length; i++) { - if (AxisHelper.getAxisRendererDetails(oldAxisRendererList[i]).name == - AxisHelper.getAxisRendererDetails(axisRenderer).name) { - return oldAxisRendererList[i]; - } - } - return null; -} - -/// To get chart point. -CartesianChartPoint? getChartPoint( - CartesianSeriesRenderer seriesRenderer, dynamic data, int pointIndex) { - dynamic xVal, - yVal, - highVal, - lowVal, - openVal, - closeVal, - volumeVal, - sortVal, - sizeVal, - colorVal, - textVal, - minVal, - minimumVal, - maximumVal; - bool? isIntermediateSum, isTotalSum; - CartesianChartPoint? currentPoint; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - final dynamic series = seriesRendererDetails.series; - 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 = - series.sortFieldValueMapper; - final ChartIndexedValueMapper? _pointColorMap = - series.pointColorMapper; - final dynamic _sizeMap = series.sizeValueMapper; - final ChartIndexedValueMapper? _pointTextMap = series.dataLabelMapper; - - if (seriesRenderer is HistogramSeriesRenderer) { - minVal = seriesRendererDetails.histogramValues.minValue; - yVal = seriesRendererDetails.histogramValues.yValues! - .where((dynamic x) => - x >= minVal == true && - x < (minVal + seriesRendererDetails.histogramValues.binWidth) == - true) - .length; - xVal = minVal + seriesRendererDetails.histogramValues.binWidth! / 2; - minVal += seriesRendererDetails.histogramValues.binWidth; - seriesRendererDetails.histogramValues.minValue = minVal; - } else { - if (_xMap != null) { - xVal = _xMap(pointIndex); - } - - if (_yMap != null) { - yVal = (series is RangeColumnSeries || - series is RangeAreaSeries || - series is HiloSeries || - series is HiloOpenCloseSeries || - series is SplineRangeAreaSeries || - series is CandleSeries) - ? null - : _yMap(pointIndex); - } - } - - if (xVal != null) { - if (yVal != null) { - assert( - 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.'); - } - if (yVal != null && series is BoxAndWhiskerSeries) { - final List yValues = yVal; - //ignore: always_specify_types - yValues.removeWhere((value) => value == null); - maximumVal = yValues.cast().reduce(max); - minimumVal = yValues.cast().reduce(min); - } - if (_highMap != null) { - highVal = _highMap(pointIndex); - } - - if (_lowMap != null) { - lowVal = _lowMap(pointIndex); - } - - if (series is FinancialSeriesBase) { - final FinancialSeriesBase financialSeries = - seriesRendererDetails.series as FinancialSeriesBase; - final ChartIndexedValueMapper? _openMap = - financialSeries.openValueMapper; - final ChartIndexedValueMapper? _closeMap = - financialSeries.closeValueMapper; - final ChartIndexedValueMapper? _volumeMap = - financialSeries.volumeValueMapper; - - if (_openMap != null) { - openVal = _openMap(pointIndex); - } - - if (_closeMap != null) { - closeVal = _closeMap(pointIndex); - } - - if (_volumeMap != null && financialSeries is HiloOpenCloseSeries) { - volumeVal = _volumeMap(pointIndex); - } - } - - if (_sortFieldMap != null) { - sortVal = _sortFieldMap(pointIndex); - } - - if (_sizeMap != null) { - sizeVal = _sizeMap(pointIndex); - } - - if (_pointColorMap != null) { - colorVal = _pointColorMap(pointIndex); - } - - if (_pointTextMap != null) { - textVal = _pointTextMap(pointIndex); - } - - if (_isIntermediateSumMap != null) { - isIntermediateSum = _isIntermediateSumMap(pointIndex); - isIntermediateSum ??= false; - } else { - isIntermediateSum = false; - } - - if (_isTotalSumMap != null) { - isTotalSum = _isTotalSumMap(pointIndex); - isTotalSum ??= false; - } else { - isTotalSum = false; - } - currentPoint = CartesianChartPoint( - xVal, - yVal, - textVal, - colorVal, - sizeVal, - highVal, - lowVal, - openVal, - closeVal, - volumeVal, - sortVal, - minimumVal, - maximumVal, - isIntermediateSum, - isTotalSum); - } - return currentPoint; -} - -/// Gets the minimum value. -num findMinValue(num axisValue, num pointValue) => - math.min(axisValue, pointValue); - -/// Gets the maximum value. -num findMaxValue(num axisValue, num pointValue) => - math.max(axisValue, pointValue); - -/// This method finds whether the given point has been updated/changed and returns a boolean value. -bool findChangesInPoint( - CartesianChartPoint point, - CartesianChartPoint oldPoint, - SeriesRendererDetails seriesRendererDetails) { - if (seriesRendererDetails.series.sortingOrder == - seriesRendererDetails.oldSeries?.sortingOrder) { - if (seriesRendererDetails.renderer is CandleSeriesRenderer || - seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType.contains('hilo') == true) { - return point.x != oldPoint.x || - point.high != oldPoint.high || - point.low != oldPoint.low || - point.open != oldPoint.open || - point.close != oldPoint.close || - point.volume != oldPoint.volume || - point.sortValue != oldPoint.sortValue; - } else if (seriesRendererDetails.seriesType == 'waterfall') { - return point.x != oldPoint.x || - (point.y != null && (point.y != oldPoint.y)) || - point.sortValue != oldPoint.sortValue || - point.isIntermediateSum != oldPoint.isIntermediateSum || - point.isTotalSum != oldPoint.isTotalSum; - } else if (seriesRendererDetails.seriesType == 'boxandwhisker') { - if (point.y.length != oldPoint.y.length || - point.x != oldPoint.x || - point.sortValue != oldPoint.sortValue) { - return true; - } else { - point.y.sort(); - for (int i = 0; i < point.y.length; i++) { - if (point.y[i] != oldPoint.y[i]) { - return true; - } - } - return false; - } - } else { - return point.x != oldPoint.x || - point.y != oldPoint.y || - point.bubbleSize != oldPoint.bubbleSize || - point.sortValue != oldPoint.sortValue; - } - } else { - return true; - } -} - -/// To calculate range Y on zoom mode X. -VisibleRange calculateYRangeOnZoomX( - VisibleRange _actualRange, dynamic axisRenderer) { - num? _mini, _maxi; - final dynamic axis = axisRenderer.axis; - final List _seriesRenderers = - axisRenderer.seriesRenderers; - final num? minimum = axis.minimum, maximum = axis.maximum; - for (int i = 0; - i < _seriesRenderers.length && _seriesRenderers.isNotEmpty; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(_seriesRenderers[i]); - final dynamic xAxisRenderer = seriesRendererDetails.xAxisDetails; - xAxisRenderer.calculateRangeAndInterval( - axisRenderer.stateProperties, 'AnchoringRange'); - final VisibleRange xRange = xAxisRenderer.visibleRange; - if (seriesRendererDetails.yAxisDetails == axisRenderer && - // ignore: unnecessary_null_comparison - xRange != null && - seriesRendererDetails.visible! == true) { - for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { - final CartesianChartPoint point = - seriesRendererDetails.dataPoints[j]; - if (point.xValue >= xRange.minimum == true && - point.xValue <= xRange.maximum == true) { - if (point.yValue != null) { - _mini = min(_mini ?? point.yValue, point.yValue); - _maxi = max(_maxi ?? point.yValue, point.yValue); - } else if (point.high != null && point.low != null) { - _mini = min(_mini ?? point.low, point.low); - _maxi = max(_maxi ?? point.high, point.high); - } - } - } - } - } - return VisibleRange(minimum ?? (_mini ?? _actualRange.minimum), - maximum ?? (_maxi ?? _actualRange.maximum)); -} - -/// Bool to calculate for Y range. -bool needCalculateYrange(num? minimum, num? maximum, - CartesianStateProperties stateProperties, AxisOrientation _orientation) { - final SfCartesianChart chart = stateProperties.chart; - return !(minimum != null && maximum != null) && - (stateProperties.rangeChangeBySlider || - ((stateProperties.zoomedState == true || - stateProperties.zoomProgress) && - (!stateProperties.requireInvertedAxis - ? (_orientation == AxisOrientation.vertical && - chart.zoomPanBehavior.zoomMode == ZoomMode.x) - : (_orientation == AxisOrientation.horizontal && - chart.zoomPanBehavior.zoomMode == ZoomMode.y)))); -} - -/// This method returns the axisRenderer for the given axis from given collection, if not found returns null. -ChartAxisRenderer? findExistingAxisRenderer( - ChartAxis axis, List axisRenderers) { - for (final ChartAxisRenderer axisRenderer in axisRenderers) { - if (identical(axis, AxisHelper.getAxisRendererDetails(axisRenderer).axis)) { - return axisRenderer; - } - } - return null; -} - -/// Method to get the old segment index value. -int getOldSegmentIndex(ChartSegment segment) { - final SegmentProperties segmentProperties = - SegmentHelper.getSegmentProperties(segment); - if (segmentProperties.oldSeriesRenderer != null) { - for (final ChartSegment oldSegment in SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!) - .segments) { - final SegmentProperties oldSegmentProperties = - SegmentHelper.getSegmentProperties(oldSegment); - if (segment.runtimeType == oldSegment.runtimeType && - (SeriesHelper.getSeriesRendererDetails( - segmentProperties.seriesRenderer) - .xAxisDetails is CategoryAxisRenderer - ? oldSegmentProperties.currentPoint!.x == - segmentProperties.currentPoint!.x - : oldSegmentProperties.currentPoint!.xValue == - segmentProperties.currentPoint!.xValue)) { - final SeriesRendererDetails rendererDetails = - SeriesHelper.getSeriesRendererDetails( - segmentProperties.oldSeriesRenderer!); - return rendererDetails.segments.indexOf(oldSegment); - } - } - } - return -1; -} - -/// This method determines whether all the series animations have been completed and renders the datalabel. -void setAnimationStatus(CartesianStateProperties stateProperties) { - if (stateProperties.totalAnimatingSeries == - stateProperties.animationCompleteCount) { - stateProperties.renderingDetails.animateCompleted = true; - stateProperties.animationCompleteCount = 0; - } else { - stateProperties.renderingDetails.animateCompleted = false; - } - if (stateProperties.renderDataLabel != null) { - if (stateProperties.renderDataLabel!.state!.mounted == true) { - stateProperties.renderDataLabel!.state?.render(); - } - } -} - -/// Calculate date time nice interval. -int calculateDateTimeNiceInterval( - ChartAxisRenderer axisRenderer, Size size, VisibleRange range, - [DateTime? startDate, DateTime? endDate]) { - final ChartAxis axis = AxisHelper.getAxisRendererDetails(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; - const num hours = 24, minutes = 60, seconds = 60, milliseconds = 1000; - final num totalDays = - ((startDate.millisecondsSinceEpoch - endDate.millisecondsSinceEpoch) / - perDay) - .abs(); - dynamic axisRendererDetails; - if (axis is DateTimeAxis) { - axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails; - actualIntervalType = axis.intervalType; - } else if (axis is DateTimeCategoryAxis) { - axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer) - as DateTimeCategoryAxisDetails; - actualIntervalType = axis.intervalType; - } - switch (actualIntervalType) { - case DateTimeIntervalType.years: - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays / 365, size); - break; - case DateTimeIntervalType.months: - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays / 30, size); - break; - case DateTimeIntervalType.days: - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays, size); - break; - case DateTimeIntervalType.hours: - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * hours, size); - break; - case DateTimeIntervalType.minutes: - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * hours * minutes, size); - break; - case DateTimeIntervalType.seconds: - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * hours * minutes * seconds, size); - break; - case DateTimeIntervalType.milliseconds: - interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, - totalDays * hours * minutes * seconds * milliseconds, size); - break; - case DateTimeIntervalType.auto: - - /// For years. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays / 365, size); - if (interval! >= 1) { - _setActualIntervalType(axisRenderer, DateTimeIntervalType.years); - return interval.floor(); - } - - /// For months. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays / 30, size); - if (interval! >= 1) { - _setActualIntervalType( - axisRenderer, - notDoubleInterval - ? DateTimeIntervalType.months - : DateTimeIntervalType.years); - return interval.floor(); - } - - /// For days. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays, size); - if (interval! >= 1) { - _setActualIntervalType( - axisRenderer, - notDoubleInterval - ? DateTimeIntervalType.days - : DateTimeIntervalType.months); - return interval.floor(); - } - - /// For hours. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * 24, size); - if (interval! >= 1) { - _setActualIntervalType( - axisRenderer, - notDoubleInterval - ? DateTimeIntervalType.hours - : DateTimeIntervalType.days); - return interval.floor(); - } - - /// For minutes. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60, size); - if (interval! >= 1) { - _setActualIntervalType( - axisRenderer, - notDoubleInterval - ? DateTimeIntervalType.minutes - : DateTimeIntervalType.hours); - return interval.floor(); - } - - /// For seconds. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60 * 60, size); - if (interval! >= 1) { - _setActualIntervalType( - axisRenderer, - notDoubleInterval - ? DateTimeIntervalType.seconds - : DateTimeIntervalType.minutes); - return interval.floor(); - } - - /// For milliseconds. - interval = axisRendererDetails.calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60 * 60 * 1000, size); - _setActualIntervalType( - axisRenderer, - notDoubleInterval - ? DateTimeIntervalType.milliseconds - : DateTimeIntervalType.seconds); - return interval! < 1 ? interval.ceil() : interval.floor(); - default: - break; - } - _setActualIntervalType(axisRenderer, actualIntervalType!); - return interval! < 1 ? interval.ceil() : interval.floor(); -} - -void _setActualIntervalType( - ChartAxisRenderer axisRenderer, DateTimeIntervalType intervalType) { - if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxisDetails dateTimeAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails; - dateTimeAxisDetails.actualIntervalType = intervalType; - } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) - as DateTimeCategoryAxisDetails; - dateTimeCategoryAxisDetails.actualIntervalType = intervalType; - } -} - -/// To get the label format of the date-time axis. -DateFormat getDateTimeLabelFormat(ChartAxisRenderer axisRenderer, - [int? interval, int? prevInterval]) { - DateFormat? format; - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final bool notDoubleInterval = (axisRendererDetails.axis.interval != null && - axisRendererDetails.axis.interval! % 1 == 0) || - axisRendererDetails.axis.interval == null; - DateTimeIntervalType? actualIntervalType; - VisibleRange? visibleRange; - num? minimum; - if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxisDetails dateTimeAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails; - actualIntervalType = dateTimeAxisDetails.actualIntervalType; - visibleRange = dateTimeAxisDetails.visibleRange; - minimum = dateTimeAxisDetails.min; - } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) - as DateTimeCategoryAxisDetails; - visibleRange = dateTimeCategoryAxisDetails.visibleRange; - minimum = dateTimeCategoryAxisDetails.min; - actualIntervalType = dateTimeCategoryAxisDetails.actualIntervalType; - } - switch (actualIntervalType) { - case DateTimeIntervalType.years: - format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); - break; - case DateTimeIntervalType.months: - format = (minimum == interval || interval == prevInterval) - ? _getFirstLabelFormat(actualIntervalType) - : _getDateTimeFormat( - actualIntervalType, visibleRange, interval, prevInterval); - - break; - case DateTimeIntervalType.days: - format = (minimum == interval || interval == prevInterval) - ? _getFirstLabelFormat(actualIntervalType) - : _getDateTimeFormat( - actualIntervalType, visibleRange, interval, prevInterval); - break; - case DateTimeIntervalType.hours: - format = DateFormat.j(); - break; - case DateTimeIntervalType.minutes: - format = DateFormat.Hm(); - break; - case DateTimeIntervalType.seconds: - format = DateFormat.ms(); - break; - case DateTimeIntervalType.milliseconds: - final DateFormat? _format = DateFormat('ss.SSS'); - format = _format; - break; - case DateTimeIntervalType.auto: - break; - default: - break; - } - return format!; -} - -/// Calculate the dateTime format. -DateFormat? _getDateTimeFormat(DateTimeIntervalType? actualIntervalType, - VisibleRange? visibleRange, int? interval, int? prevInterval) { - final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); - final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); - DateFormat? format; - final bool isIntervalDecimal = visibleRange!.interval % 1 == 0; - if (actualIntervalType == DateTimeIntervalType.months) { - format = minimum.year == maximum.year - ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) - : DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = minimum.month != maximum.month - ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) - : DateFormat.d(); - } - - return format; -} - -/// Returns the first label format for date time values. -DateFormat? _getFirstLabelFormat(DateTimeIntervalType? actualIntervalType) { - DateFormat? format; - - if (actualIntervalType == DateTimeIntervalType.months) { - format = DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = DateFormat.MMMd(); - } else if (actualIntervalType == DateTimeIntervalType.minutes) { - format = DateFormat.Hm(); - } - - return format; -} - -/// Method to set the minimum and maximum value of category axis. -void setCategoryMinMaxValues( - ChartAxisRenderer axisRenderer, - bool isXVisibleRange, - bool isYVisibleRange, - CartesianChartPoint point, - int pointIndex, - int dataLength, - SeriesRendererDetails seriesRendererDetails) { - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final String seriesType = seriesRendererDetails.seriesType; - final bool anchorRangeToVisiblePoints = - seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; - if (isYVisibleRange) { - seriesRendererDetails.minimumX ??= point.xValue; - seriesRendererDetails.maximumX ??= point.xValue; - } - if ((isXVisibleRange || !anchorRangeToVisiblePoints) && - !seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall') { - seriesRendererDetails.minimumY ??= point.yValue; - seriesRendererDetails.maximumY ??= point.yValue; - } - if (isYVisibleRange && point.xValue != null) { - seriesRendererDetails.minimumX = - math.min(seriesRendererDetails.minimumX!, point.xValue); - seriesRendererDetails.maximumX = - math.max(seriesRendererDetails.maximumX!, point.xValue); - } - if (isXVisibleRange || !anchorRangeToVisiblePoints) { - if (point.yValue != null && - (!seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall')) { - seriesRendererDetails.minimumY = - math.min(seriesRendererDetails.minimumY!, point.yValue); - seriesRendererDetails.maximumY = - math.max(seriesRendererDetails.maximumY!, point.yValue); - } - - if (point.high != null) { - axisRendererDetails.highMin = - findMinValue(axisRendererDetails.highMin ?? point.high, point.high); - axisRendererDetails.highMax = - findMaxValue(axisRendererDetails.highMax ?? point.high, point.high); - } - if (point.low != null) { - axisRendererDetails.lowMin = - findMinValue(axisRendererDetails.lowMin ?? point.low, point.low); - axisRendererDetails.lowMax = - findMaxValue(axisRendererDetails.lowMax ?? point.low, point.low); - } - if (point.maximum != null) { - axisRendererDetails.highMin = findMinValue( - axisRendererDetails.highMin ?? point.maximum!, point.maximum!); - axisRendererDetails.highMax = findMaxValue( - axisRendererDetails.highMax ?? point.minimum!, point.maximum!); - } - if (point.minimum != null) { - axisRendererDetails.lowMin = findMinValue( - axisRendererDetails.lowMin ?? point.minimum!, point.minimum!); - axisRendererDetails.lowMax = findMaxValue( - axisRendererDetails.lowMax ?? point.minimum!, point.minimum!); - } - if (seriesType == 'waterfall') { - /// Empty point is not applicable for Waterfall series. - point.yValue ??= 0; - seriesRendererDetails.minimumY = findMinValue( - seriesRendererDetails.minimumY ?? point.yValue, point.yValue); - seriesRendererDetails.maximumY = findMaxValue( - seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue); - } else if (seriesType == 'errorbar') { - updateErrorBarAxisRange(seriesRendererDetails, point); - } - } - - if (pointIndex >= dataLength - 1) { - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') || - seriesType == 'boxandwhisker') { - axisRendererDetails.lowMin ??= 0; - axisRendererDetails.lowMax ??= 5; - axisRendererDetails.highMin ??= 0; - axisRendererDetails.highMax ??= 5; - seriesRendererDetails.minimumY = - math.min(axisRendererDetails.lowMin!, axisRendererDetails.highMin!); - seriesRendererDetails.maximumY = - math.max(axisRendererDetails.lowMax!, axisRendererDetails.highMax!); - } - seriesRendererDetails.minimumY ??= 0; - seriesRendererDetails.maximumY ??= 5; - } -} - -/// Method to calculate the date time visible range. -void calculateDateTimeVisibleRange( - Size availableSize, ChartAxisRenderer axisRenderer) { - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - final VisibleRange actualRange = axisRendererDetails.actualRange!; - final CartesianStateProperties stateProperties = - axisRendererDetails.stateProperties; - axisRendererDetails.setOldRangeFromRangeController(); - axisRendererDetails.visibleRange = stateProperties.rangeChangeBySlider && - axisRendererDetails.rangeMinimum != null && - axisRendererDetails.rangeMaximum != null - ? VisibleRange( - axisRendererDetails.rangeMinimum, axisRendererDetails.rangeMaximum) - : VisibleRange(actualRange.minimum, actualRange.maximum); - final VisibleRange visibleRange = axisRendererDetails.visibleRange!; - visibleRange.delta = actualRange.delta; - visibleRange.interval = actualRange.interval; - bool canAutoScroll = false; - if (axisRendererDetails.axis.autoScrollingDelta != null && - axisRendererDetails.axis.autoScrollingDelta! > 0 && - !stateProperties.isRedrawByZoomPan) { - canAutoScroll = true; - if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxisDetails dateTimeAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) - as DateTimeAxisDetails; - dateTimeAxisDetails.updateScrollingDelta(); - } else { - final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer) - as DateTimeCategoryAxisDetails; - dateTimeCategoryAxisDetails.updateAutoScrollingDelta( - dateTimeCategoryAxisDetails.axis.autoScrollingDelta!, axisRenderer); - } - } - if ((!canAutoScroll || stateProperties.zoomedState == true) && - !(stateProperties.rangeChangeBySlider && - !stateProperties.canSetRangeController)) { - axisRendererDetails.setZoomFactorAndPosition( - axisRenderer, stateProperties.zoomedAxisRendererStates); - } - if (axisRendererDetails.zoomFactor < 1 || - axisRendererDetails.zoomPosition > 0 || - (axisRendererDetails.axis.rangeController != null && - !stateProperties.renderingDetails.initialRender!)) { - stateProperties.zoomProgress = true; - axisRendererDetails.calculateZoomRange(axisRenderer, availableSize); - visibleRange.interval = - axisRendererDetails.axis.enableAutoIntervalOnZooming && - stateProperties.zoomProgress && - !canAutoScroll && - axisRenderer is DateTimeAxisRenderer - ? axisRenderer.calculateInterval(visibleRange, availableSize) - : visibleRange.interval; - if (axisRenderer is DateTimeAxisRenderer) { - visibleRange.minimum = (visibleRange.minimum).floor(); - visibleRange.maximum = (visibleRange.maximum).floor(); - } - if (axisRendererDetails.axis.rangeController != null && - stateProperties.isRedrawByZoomPan && - stateProperties.canSetRangeController && - stateProperties.zoomProgress) { - stateProperties.rangeChangedByChart = true; - axisRendererDetails.setRangeControllerValues(axisRenderer); - } - } - axisRendererDetails.setZoomValuesFromRangeController(); -} - -/// This method used to return the cross value of the axis. -num? getCrossesAtValue(CartesianSeriesRenderer seriesRenderer, - CartesianStateProperties stateProperties) { - num? crossesAt; - final int seriesIndex = stateProperties.chartSeries.visibleSeriesRenderers - .indexOf(seriesRenderer); - final List axisCollection = - stateProperties.requireInvertedAxis - ? stateProperties.chartAxis.verticalAxisRenderers - : stateProperties.chartAxis.horizontalAxisRenderers; - for (int i = 0; i < axisCollection.length; i++) { - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails(axisCollection[i]); - if (SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]) - .xAxisDetails! - .name == - axisRendererDetails.name) { - crossesAt = axisRendererDetails.crossValue; - break; - } - } - return crossesAt; -} - -/// Method to get the tooltip padding data. -List getTooltipPaddingData(SeriesRendererDetails seriesRendererDetails, - bool isTrendLine, Rect region, Rect paddedRegion, Offset? tooltipPosition) { - Offset? padding, position; - if (seriesRendererDetails.seriesType == 'bubble' && !isTrendLine) { - padding = Offset(region.center.dx - region.centerLeft.dx, - 2 * (region.center.dy - region.topCenter.dy)); - position = Offset(tooltipPosition!.dx, paddedRegion.top); - if (region.top < 0) { - padding = Offset(padding.dx, padding.dy + region.top); - } - } else if (seriesRendererDetails.seriesType == 'scatter') { - padding = Offset(seriesRendererDetails.series.markerSettings.width, - seriesRendererDetails.series.markerSettings.height / 2); - position = Offset(tooltipPosition!.dx, tooltipPosition.dy); - } else if (seriesRendererDetails.seriesType.contains('rangearea') == true) { - padding = Offset(seriesRendererDetails.series.markerSettings.width, - seriesRendererDetails.series.markerSettings.height / 2); - position = Offset(tooltipPosition!.dx, tooltipPosition.dy); - } else { - padding = (seriesRendererDetails.series.markerSettings.isVisible == true) - ? Offset( - seriesRendererDetails.series.markerSettings.width / 2, - seriesRendererDetails.series.markerSettings.height / 2 + - seriesRendererDetails.series.markerSettings.borderWidth / 2) - : const Offset(2, 2); - } - return [padding, position ?? tooltipPosition]; -} - -/// Returns the old series renderer instance for the given series renderer. -CartesianSeriesRenderer? getOldSeriesRenderer( - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - int seriesIndex, - List oldSeriesRenderers) { - if (stateProperties.renderingDetails.widgetNeedUpdate && - seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && - seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= seriesIndex && - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[seriesIndex]) - .seriesName == - seriesRendererDetails.seriesName) { - return oldSeriesRenderers[seriesIndex]; - } else { - return null; - } -} - -/// Returns the old chart point for the given point and series index if present. -CartesianChartPoint? getOldChartPoint( - CartesianStateProperties stateProperties, - SeriesRendererDetails seriesRendererDetails, - Type segmentType, - int seriesIndex, - int pointIndex, - CartesianSeriesRenderer? oldSeriesRenderer, - List oldSeriesRenderers) { - final RenderingDetails _renderingDetails = stateProperties.renderingDetails; - - return seriesRendererDetails.reAnimate == false && - (seriesRendererDetails.series.animationDuration > 0 && - _renderingDetails.widgetNeedUpdate && - !_renderingDetails.isLegendToggled && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderer != null && - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) - .segments - .isNotEmpty == - true && - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) - .segments[0] - .runtimeType == - segmentType && - oldSeriesRenderers.length - 1 >= seriesIndex && - SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) - .dataPoints - .length - - 1 >= - pointIndex) - ? SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) - .dataPoints[pointIndex] - : null; -} - -/// Boolean to check whether it is necessary to render the axis tooltip. -bool shouldShowAxisTooltip(CartesianStateProperties stateProperties) { - bool requireAxisTooltip = false; - for (int i = 0; - i < stateProperties.chartAxis.axisRenderersCollection.length; - i++) { - final ChartAxisRendererDetails axisRendererDetails = - AxisHelper.getAxisRendererDetails( - stateProperties.chartAxis.axisRenderersCollection[i]); - requireAxisTooltip = axisRendererDetails.axis.maximumLabelWidth != null || - axisRendererDetails.axis.labelsExtent != null; - if (axisRendererDetails.axis.multiLevelLabels != null) { - requireAxisTooltip = - axisRendererDetails.visibleAxisMultiLevelLabels.isNotEmpty; - } - if (requireAxisTooltip) { - break; - } - } - return requireAxisTooltip; -} - -/// Method to get the visible data point index. -int? getVisibleDataPointIndex( - int? pointIndex, SeriesRendererDetails seriesRendererDetails) { - int? index; - if (pointIndex != null) { - if (pointIndex < - seriesRendererDetails.dataPoints[0].overallDataPointIndex! || - pointIndex > - seriesRendererDetails - .dataPoints[seriesRendererDetails.dataPoints.length - 1] - .overallDataPointIndex!) { - index = null; - } else if (pointIndex > seriesRendererDetails.dataPoints.length - 1) { - for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { - if (pointIndex == - seriesRendererDetails.dataPoints[i].overallDataPointIndex) { - index = seriesRendererDetails.dataPoints[i].visiblePointIndex; - } - } - } else { - index = seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex; - } - } - return index; -} - -/// Method to check whether the series is line series type. -bool isLineTypeSeries(String seriesType) { - return seriesType == 'line' || - seriesType == 'spline' || - seriesType == 'stepline' || - seriesType == 'stackedline' || - seriesType == 'stackedline100'; -} - -/// A circular array for dash offsets and lengths. -class CircularIntervalList { - /// Creates an instance of circular interval list. - CircularIntervalList(this._values); - final List _values; - int _index = 0; - - /// Returns the next value. - T get next { - if (_index >= _values.length) { - _index = 0; - } - return _values[_index++]; - } -} - -/// Gets the R-squared value. -double? getRSquaredValue(CartesianSeriesRenderer series, Trendline trendline, - List? slope, double? intercept) { - double rSquare = 0.0; - const int power = 2; - final List xValue = []; - final List yValue = []; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(series); - double yMean = 0; - const int startXValue = 1; - final DateTime excelDate = DateTime(1900, 01, 01); - for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { - xValue.add( - seriesRendererDetails.xAxisDetails?.axisRenderer is DateTimeAxisRenderer - ? seriesRendererDetails.dataPoints[i].x.difference(excelDate).inDays - : i + startXValue); - yValue.add(seriesRendererDetails.dataPoints[i].y.toDouble()); - yMean += seriesRendererDetails.dataPoints[i].y.toDouble(); - } - yMean = yMean / yValue.length; - // Total sum of square (ssTot) - double ssTot = 0.0; - for (int j = 0; j < yValue.length; j++) { - ssTot += math.pow(yValue[j] - yMean, power); - } - // Sum of squares due to regression (ssReg) - double ssReg = 0.0; - if (trendline.type == TrendlineType.linear) { - for (int k = 0; k < yValue.length; k++) { - ssReg += math.pow(((slope![0] * xValue[k]) + intercept!) - yMean, power); - } - } - if (trendline.type == TrendlineType.exponential) { - for (int k = 0; k < yValue.length; k++) { - ssReg += math.pow( - (intercept! * math.exp(slope![0] * xValue[k])) - yMean, power); - } - } - if (trendline.type == TrendlineType.logarithmic) { - for (int k = 0; k < yValue.length; k++) { - ssReg += math.pow( - ((slope![0] * math.log(xValue[k])) + intercept!) - yMean, power); - } - } - if (trendline.type == TrendlineType.power) { - for (int k = 0; k < yValue.length; k++) { - ssReg += math.pow( - (intercept! * math.pow(xValue[k], slope![0])) - yMean, power); - } - } - if (trendline.type == TrendlineType.polynomial) { - for (int k = 0; k < yValue.length; k++) { - double yCap = 0.0; - for (int i = 0; i < slope!.length; i++) { - yCap += slope[i] * math.pow(xValue[k], i); - } - ssReg += math.pow(yCap - yMean, power); - } - } - rSquare = ssReg / ssTot; - return rSquare.isNaN ? 0 : rSquare; -} - -/// To calculate and return the bubble size. -double calculateBubbleRadius( - SeriesRendererDetails seriesRendererDetails, - CartesianSeries series, - CartesianChartPoint currentPoint) { - final BubbleSeries bubbleSeries = - series as BubbleSeries; - num bubbleRadius, sizeRange, radiusRange, maxSize, minSize; - const num defaultBubbleSize = 4; - maxSize = seriesRendererDetails.maxSize!; - minSize = seriesRendererDetails.minSize!; - sizeRange = maxSize - minSize; - final num minRadius = bubbleSeries.minimumRadius, - maxRadius = bubbleSeries.maximumRadius; - final double bubbleSize = - ((currentPoint.bubbleSize) ?? defaultBubbleSize).toDouble(); - assert(minRadius >= 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) { - // ignore: unnecessary_null_comparison - minRadius != null ? bubbleRadius = minRadius : bubbleRadius = maxRadius; - } else { - if (sizeRange == 0) { - bubbleRadius = bubbleSize == 0 ? minRadius : maxRadius; - } else { - radiusRange = maxRadius - minRadius; - bubbleRadius = - minRadius + radiusRange * ((bubbleSize.abs() / maxSize).abs()); - } - } - return bubbleRadius.toDouble(); -} - -/// Cartesian point to pixel. -Offset calculatePointToPixel( - CartesianChartPoint point, dynamic seriesRenderer) { - final num x = point.x; - final num y = point.y; - final SeriesRendererDetails seriesRendererDetails; - if (seriesRenderer is SeriesRendererDetails == false) { - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - } else { - seriesRendererDetails = seriesRenderer; - } - final ChartAxisRendererDetails xAxisDetails = - seriesRendererDetails.xAxisDetails!; - final ChartAxisRendererDetails yAxisDetails = - seriesRendererDetails.yAxisDetails!; - - final bool isInverted = - seriesRendererDetails.stateProperties.requireInvertedAxis; - - final CartesianSeries series = seriesRendererDetails.series; - final ChartLocation location = calculatePoint( - x, - y, - xAxisDetails, - yAxisDetails, - isInverted, - series, - seriesRendererDetails.stateProperties.chartAxis.axisClipRect); - - return Offset(location.x, location.y); -} - -/// Cartesian pixel to point. -CartesianChartPoint calculatePixelToPoint( - Offset position, dynamic seriesRenderer) { - SeriesRendererDetails seriesRendererDetails; - if (seriesRenderer is SeriesRendererDetails) { - seriesRendererDetails = seriesRenderer; - } else { - seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(seriesRenderer); - } - if (seriesRendererDetails.xAxisDetails == null) { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - seriesRendererDetails.stateProperties.oldSeriesRenderers[0]); - } - ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!; - ChartAxisRendererDetails yAxisDetails = seriesRendererDetails.yAxisDetails!; - - final ChartAxis xAxis = xAxisDetails.axis; - final ChartAxis yAxis = yAxisDetails.axis; - - final CartesianSeries series = seriesRendererDetails.series; - - final Rect rect = - seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - - if (series.xAxisName != null || series.yAxisName != null) { - for (final ChartAxisRenderer axisRenderer in seriesRendererDetails - .stateProperties.chartAxis.axisRenderersCollection) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (axisDetails.name == series.xAxisName) { - xAxisDetails = axisDetails; - } else if (axisDetails.name == series.yAxisName) { - yAxisDetails = axisDetails; - } - } - } else { - xAxisDetails = xAxisDetails; - yAxisDetails = yAxisDetails; - } - - num xValue = pointToXValue( - seriesRendererDetails.stateProperties.requireInvertedAxis, - xAxisDetails.axisRenderer, - rect, - position.dx - (rect.left + xAxis.plotOffset), - position.dy - (rect.top + yAxis.plotOffset)); - num yValue = pointToYValue( - seriesRendererDetails.stateProperties.requireInvertedAxis, - yAxisDetails.axisRenderer, - rect, - position.dx - (rect.left + xAxis.plotOffset), - position.dy - (rect.top + yAxis.plotOffset)); - - if (xAxisDetails is LogarithmicAxisDetails) { - final LogarithmicAxis axis = xAxis as LogarithmicAxis; - xValue = math.pow(xValue, calculateLogBaseValue(xValue, axis.logBase)); - } else { - xValue = xValue; - } - if (yAxisDetails is LogarithmicAxisDetails) { - final LogarithmicAxis axis = yAxis as LogarithmicAxis; - yValue = math.pow(yValue, calculateLogBaseValue(yValue, axis.logBase)); - } else { - yValue = yValue; - } - return CartesianChartPoint(xValue, yValue); -} - -/// To get seriesType of the cartesian series renderer. -String getSeriesType(CartesianSeriesRenderer seriesRenderer) { - String seriesType = ''; - - if (seriesRenderer is AreaSeriesRenderer) { - seriesType = 'area'; - } else if (seriesRenderer is BarSeriesRenderer) { - seriesType = 'bar'; - } else if (seriesRenderer is BubbleSeriesRenderer) { - seriesType = 'bubble'; - } else if (seriesRenderer is ColumnSeriesRenderer) { - seriesType = 'column'; - } else if (seriesRenderer is FastLineSeriesRenderer) { - seriesType = 'fastline'; - } else if (seriesRenderer is LineSeriesRenderer) { - seriesType = 'line'; - } else if (seriesRenderer is ScatterSeriesRenderer) { - seriesType = 'scatter'; - } else if (seriesRenderer is SplineSeriesRenderer) { - seriesType = 'spline'; - } else if (seriesRenderer is StepLineSeriesRenderer) { - seriesType = 'stepline'; - } else if (seriesRenderer is StackedColumnSeriesRenderer) { - seriesType = 'stackedcolumn'; - } else if (seriesRenderer is StackedBarSeriesRenderer) { - seriesType = 'stackedbar'; - } else if (seriesRenderer is StackedAreaSeriesRenderer) { - seriesType = 'stackedarea'; - } else if (seriesRenderer is StackedArea100SeriesRenderer) { - seriesType = 'stackedarea100'; - } else if (seriesRenderer is StackedLineSeriesRenderer) { - seriesType = 'stackedline'; - } else if (seriesRenderer is StackedLine100SeriesRenderer) { - seriesType = 'stackedline100'; - } else if (seriesRenderer is RangeColumnSeriesRenderer) { - seriesType = 'rangecolumn'; - } else if (seriesRenderer is RangeAreaSeriesRenderer) { - seriesType = 'rangearea'; - } else if (seriesRenderer is StackedColumn100SeriesRenderer) { - seriesType = 'stackedcolumn100'; - } else if (seriesRenderer is StackedBar100SeriesRenderer) { - seriesType = 'stackedbar100'; - } else if (seriesRenderer is SplineAreaSeriesRenderer) { - seriesType = 'splinearea'; - } else if (seriesRenderer is StepAreaSeriesRenderer) { - seriesType = 'steparea'; - } else if (seriesRenderer is HiloSeriesRenderer) { - seriesType = 'hilo'; - } else if (seriesRenderer is HiloOpenCloseSeriesRenderer) { - seriesType = 'hiloopenclose'; - } else if (seriesRenderer is CandleSeriesRenderer) { - seriesType = 'candle'; - } else if (seriesRenderer is HistogramSeriesRenderer) { - seriesType = 'histogram'; - } else if (seriesRenderer is SplineRangeAreaSeriesRenderer) { - seriesType = 'splinerangearea'; - } else if (seriesRenderer is BoxAndWhiskerSeriesRenderer) { - seriesType = 'boxandwhisker'; - } else if (seriesRenderer is WaterfallSeriesRenderer) { - seriesType = 'waterfall'; - } else if (seriesRenderer is ErrorBarSeriesRenderer) { - seriesType = 'errorbar'; - } - - return seriesType; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart new file mode 100644 index 000000000..8773f4e50 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart @@ -0,0 +1,7097 @@ +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 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../base.dart'; +import '../common/callbacks.dart'; +import '../common/core_tooltip.dart'; +import '../common/interactive_tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'category_axis.dart'; +import 'datetime_category_axis.dart'; +import 'logarithmic_axis.dart'; +import 'multi_level_labels.dart'; +import 'plot_band.dart'; + +typedef _AlignLabel = double Function(double position, AxisLabel label); + +/// This class holds the properties of [ChartAxis]. +/// +/// Charts typically have two axes that are used to measure and +/// categorize data: a vertical (Y) axis, and a horizontal (X) axis. +/// Vertical(Y) axis always uses a numerical scale. Horizontal(X) axis +/// supports Category, Numeric, Date-time, Logarithmic. +/// +/// Provides the options for plotOffset, series visible, axis title, +/// label padding, and alignment to customize the appearance. +abstract class ChartAxis extends LeafRenderObjectWidget { + /// Creating an argument constructor of [ChartAxis] class. + const ChartAxis({ + Key? key, + this.name, + this.plotOffset, + this.plotOffsetStart, + this.plotOffsetEnd, + this.isVisible = true, + this.anchorRangeToVisiblePoints = true, + this.title = const AxisTitle(), + this.axisLine = const AxisLine(), + this.rangePadding = ChartRangePadding.auto, + this.labelRotation = 0, + this.labelPosition = ChartDataLabelPosition.outside, + this.labelAlignment = LabelAlignment.center, + this.tickPosition = TickPosition.outside, + this.majorTickLines = const MajorTickLines(), + this.minorTickLines = const MinorTickLines(), + this.labelStyle, + this.labelIntersectAction = AxisLabelIntersectAction.hide, + this.desiredIntervals, + this.majorGridLines = const MajorGridLines(), + this.minorGridLines = const MinorGridLines(), + this.maximumLabels = 3, + this.minorTicksPerInterval = 0, + this.isInversed = false, + this.opposedPosition = false, + this.edgeLabelPlacement = EdgeLabelPlacement.none, + this.enableAutoIntervalOnZooming = true, + this.initialZoomFactor = 1, + this.initialZoomPosition = 0, + this.interactiveTooltip = const InteractiveTooltip(), + this.interval, + this.crossesAt, + this.associatedAxisName, + this.placeLabelsNearAxisLine = true, + this.plotBands = const [], + this.rangeController, + this.maximumLabelWidth, + this.labelsExtent, + this.autoScrollingDelta, + this.autoScrollingMode = AutoScrollingMode.end, + this.borderWidth = 0.0, + this.borderColor, + this.axisBorderType = AxisBorderType.rectangle, + this.multiLevelLabelStyle = const MultiLevelLabelStyle(), + this.multiLevelLabelFormatter, + this.axisLabelFormatter, + }) : super(key: key); + + /// Toggles the visibility of the axis. + /// + /// Visibility of all the elements in the axis + /// such as title, labels, major tick lines, and major grid lines + /// will be toggled together. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(isVisible: false), + /// ) + /// ); + /// } + /// ``` + final bool isVisible; + + /// Determines the value axis range, based on the visible data points or based + /// on the overall data points available in chart. + /// + /// 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. + /// + /// 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 visible points. + /// + /// _Note:_ This is applicable only to the value axis and not for other axis. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: NumericAxis(anchorRangeToVisiblePoints: false), + /// ) + /// ); + /// } + /// ``` + final bool anchorRangeToVisiblePoints; + + /// Customizes the appearance of the axis line. The axis line is + /// visible by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(axisLine: AxisLine(width: 10)), + /// ) + /// ); + /// } + /// ``` + final AxisLine axisLine; + + /// Customizes the appearance of the major tick lines. + /// + /// Major ticks are small lines used to indicate the intervals in an axis. + /// Major tick lines are visible by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(majorTickLines: const MajorTickLines(width: 2)), + /// ) + /// ); + /// } + /// ``` + final MajorTickLines majorTickLines; + + /// Customizes the appearance of the minor tick lines. + /// + /// Minor ticks are small lines + /// used to indicate the minor intervals between a major interval. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(minorTickLines: MinorTickLines(width: 2)), + /// ) + /// ); + /// } + /// ``` + final MinorTickLines minorTickLines; + + /// Customizes the appearance of the major grid lines. + /// + /// Major grids are the lines + /// drawn on the plot area at all the major intervals in an axis. + /// Major grid lines are visible by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(majorGridLines: MajorGridLines(width: 2)), + /// ) + /// ); + /// } + /// ``` + final MajorGridLines majorGridLines; + + /// Customizes the appearance of the minor grid lines. + /// + /// Minor grids are the lines drawn + /// on the plot area at all the minor intervals between the major intervals. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(minorGridLines: MinorGridLines(width: 2)), + /// ) + /// ); + /// } + /// ``` + final MinorGridLines minorGridLines; + + /// Customizes the appearance of the axis labels. + /// + /// Labels are the axis values + /// placed at each interval. Axis labels are visible by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(labelStyle: TextStyle(color: Colors.black)), + /// ) + /// ); + /// } + /// ``` + final TextStyle? labelStyle; + + /// Customizes the appearance of the axis title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(title: AxisTitle( text: 'Year')), + /// ) + /// ); + /// } + /// ``` + final AxisTitle title; + + /// Padding for minimum and maximum values in an axis. + /// + /// Various types of range padding + /// such as round, none, normal, and additional can be applied. + /// + /// Defaults to `ChartRangePadding.auto`. + /// + /// Also refer [ChartRangePadding]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(rangePadding: ChartRangePadding.round), + /// ) + /// ); + /// } + /// ``` + final ChartRangePadding rangePadding; + + /// The number of intervals in an axis. + /// + /// By default, the number of intervals is calculated based on the minimum + /// and maximum values and the axis width and height. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(desiredIntervals: 2), + /// ) + /// ); + /// } + /// ``` + final int? desiredIntervals; + + /// The maximum number of labels to be displayed in an axis in + /// 100 logical pixels. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(maximumLabels: 4), + /// ) + /// ); + /// } + /// ``` + final int maximumLabels; + + /// Interval of the minor ticks. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(minorTicksPerInterval: 2), + /// ) + /// ); + /// } + /// ``` + final int minorTicksPerInterval; + + /// Angle for axis labels. + /// The axis labels can be rotated to any angle. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(labelRotation: 90), + /// ) + /// ); + /// } + /// ``` + final int labelRotation; + + /// Axis labels intersecting action. + /// + /// Various actions such as hide, trim, wrap, rotate + /// 90 degree, rotate 45 degree, and placing the labels in multiple rows can + /// be handled when the axis labels collide with each other. + /// + /// Defaults to `AxisLabelIntersectAction.hide`. + /// + /// Also refer [AxisLabelIntersectAction]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(labelIntersectAction: AxisLabelIntersectAction.multipleRows), + /// ) + /// ); + /// } + /// ``` + final AxisLabelIntersectAction labelIntersectAction; + + /// Opposes the axis position. + /// + /// An axis can be placed at the opposite side of its default position. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(opposedPosition: true), + /// ) + /// ); + /// } + /// ``` + final bool opposedPosition; + + /// Inverts the axis. + /// + /// Axis is rendered from the minimum value to maximum value by + /// default, and it can be inverted to render the axis from the maximum value + /// to minimum value. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(isInversed: true), + /// ) + /// ); + /// } + /// ``` + final bool isInversed; + + /// Position of the labels. + /// + /// Axis labels can be placed either inside or + /// outside the plot area. + /// + /// Defaults to `ChartDataLabelPosition.outside`. + /// + /// Also refer [ChartDataLabelPosition]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(labelPosition: ChartDataLabelPosition.inside), + /// ) + /// ); + /// } + /// ``` + final ChartDataLabelPosition labelPosition; + + /// Alignment of the labels. + /// + /// Axis labels can be placed either to the start, + /// end or center of the grid lines. + /// + /// Defaults to `LabelAlignment.start`. + /// + /// Also refer [LabelAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(labelAlignment: LabelAlignment.start), + /// ) + /// ); + /// } + /// ``` + final LabelAlignment labelAlignment; + + /// Position of the tick lines. + /// + /// Tick lines can be placed either inside or + /// outside the plot area. + /// + /// Defaults to `TickPosition.outside`. + /// + /// Also refer [TickPosition]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(tickPosition: TickPosition.inside), + /// ) + /// ); + /// } + /// ``` + final TickPosition tickPosition; + + /// Position of the edge labels. + /// + /// The edge labels in an axis can be hidden or shifted + /// inside the axis bounds. + /// + /// Defaults to `EdgeLabelPlacement.none`. + /// + /// Also refer [EdgeLabelPlacement]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(edgeLabelPlacement: EdgeLabelPlacement.hide), + /// ) + /// ); + /// } + /// ``` + final EdgeLabelPlacement edgeLabelPlacement; + + /// Axis interval value. + /// + /// Using this, the axis labels can be displayed after + /// certain interval value. By default, interval will be + /// calculated based on the minimum and maximum values of the provided data. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(interval: 1), + /// ) + /// ); + /// } + /// ``` + final double? interval; + + /// The plotOffset property is used to offset the rendering of the axis at + /// start and end position. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// primaryXAxis: NumericAxis(plotOffset: 60), + /// ); + /// } + /// ``` + final double? plotOffset; + + /// The plotOffsetStart property is used to offset the rendering of the axis + /// at start position. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// primaryXAxis: NumericAxis(plotOffsetStart: 60), + /// ); + /// } + /// ``` + final double? plotOffsetStart; + + /// The plotOffsetEnd property is used to offset the rendering of the axis + /// at end position. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// primaryXAxis: NumericAxis(plotOffsetEnd: 60), + /// ); + /// } + /// ``` + final double? plotOffsetEnd; + + /// Name of an axis. + /// + /// A unique name further used for linking the series to this + /// appropriate axis. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(name: 'primaryXAxis'), + /// ) + /// ); + /// } + /// ``` + final String? name; + + /// Defines the percentage of the visible range from the total range of axis values. + /// It applies only during load time and the value will not be updated when zooming or panning. + /// + /// Scale the axis based on this value, and it ranges + /// from 0 to 1. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(initialZoomFactor: 0.5), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the onRendererCreated callback, as shown in the code below, to update the zoom + /// factor value dynamically. + /// + /// ```dart + /// NumericAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// initialZoomFactor: 0.5, + /// onRendererCreated: (NumericAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (_SalesData sales, _) => sales.year, + /// yValueMapper: (_SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.zoomFactor = 0.3; + /// } + /// }, + /// child: const Text('Update ZoomFactor'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double initialZoomFactor; + + /// Defines the zoom position for the actual range of the axis. + /// It applies only during load time and the value will not be updated when zooming or panning. + /// + /// The value ranges from 0 to 1. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(initialZoomPosition: 0.6), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the onRendererCreated callback, as shown in the code below, to update the zoom + /// position value dynamically. + /// + /// ```dart + /// NumericAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// initialZoomPosition: 0.6, + /// onRendererCreated: (NumericAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (_SalesData sales, _) => sales.year, + /// yValueMapper: (_SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.zoomPosition = 0.2; + /// } + /// }, + /// child: const Text('Update ZoomPosition'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double initialZoomPosition; + + /// Enables or disables the automatic interval while zooming. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(enableAutoIntervalOnZooming: true), + /// ) + /// ); + /// } + /// ``` + final bool enableAutoIntervalOnZooming; + + /// Customizes the crosshair and selection zooming tooltip. Tooltip displays + /// the current axis value based on the crosshair position/selectionZoomRect + /// position at an axis. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(interactiveTooltip: InteractiveTooltip(enable: true)), + /// ) + /// ); + /// } + /// ``` + final InteractiveTooltip interactiveTooltip; + + /// Customization to place the axis crossing on another axis based + /// on the value. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(crossesAt:10), + /// ) + /// ); + /// } + /// ``` + final dynamic crossesAt; + + /// Axis line crossed on mentioned axis name, + /// and applicable for plot band also. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// associatedAxisName: 'primaryXAxis', + /// plotBands: [ + /// PlotBand( + /// start: 2, + /// end: 5, + /// associatedAxisStart: 2, + /// ), + /// ], + /// ), + /// ) + /// ); + /// } + /// ``` + final String? associatedAxisName; + + /// Consider to place the axis label respect to near axis line. + /// + /// Defaults to `true`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(placeLabelsNearAxisLine: false, crossesAt:10), + /// ) + /// ); + /// } + /// ``` + final bool placeLabelsNearAxisLine; + + /// Render the plot band in axis. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: + /// [PlotBand(start:20, end:30, color:Colors.red, text:'Flutter')] + /// ), + /// ) + /// ); + /// } + /// ``` + final List plotBands; + + /// The `rangeController` property is used to set the maximum and minimum + /// values for the chart in the viewport. In the minimum and maximum + /// properties of the axis, you can specify the minimum and maximum values + /// with respect to the entire data source. In the visibleMinimum and + /// visibleMaximum properties, you can specify the values to be viewed in the + /// viewed port i.e. range controller's start and end values respectively. + /// + /// Here you need to specify the `minimum`, `maximum`, `visibleMinimum`, + /// and `visibleMaximum` properties to the axis and the axis values will be + /// visible with respect to visibleMinimum and visibleMaximum properties. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// RangeController rangeController = RangeController( + /// start: DateTime(2020, 2, 1), + /// end: DateTime(2020, 2, 30), + /// ); + /// SfCartesianChart sliderChart = SfCartesianChart( + /// margin: const EdgeInsets.all(0), + /// primaryXAxis: + /// DateTimeAxis(isVisible: false), + /// primaryYAxis: NumericAxis(isVisible: false), + /// plotAreaBorderWidth: 0, + /// series: >[ + /// SplineAreaSeries( + /// // Add required properties. + /// ) + /// ], + /// ); + /// return Scaffold( + /// body: Column( + /// children: [ + /// Expanded( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis( + /// maximum: DateTime(2020, 1, 1), + /// minimum: DateTime(2020, 3, 30), + /// // set maximum value from the range controller + /// visibleMaximum: rangeController.end, + /// // set minimum value from the range controller + /// visibleMinimum: rangeController.start, + /// rangeController: rangeController), + /// primaryYAxis: NumericAxis(), + /// series: >[ + /// SplineSeries( + /// dataSource: splineSeriesData, + /// xValueMapper: (ChartSampleData sales, _) => + /// sales.x as DateTime, + /// yValueMapper: (ChartSampleData sales, _) => sales.y, + /// // Add required properties. + /// ) + /// ], + /// ), + /// ), + /// Expanded( + /// child: SfRangeSelectorTheme( + /// data: SfRangeSelectorThemeData(), + /// child: SfRangeSelector( + /// min: min, + /// max: max, + /// controller: rangeController, + /// showTicks: true, + /// showLabels: true, + /// dragMode: SliderDragMode.both, + /// onChanged: (SfRangeValues value) { + /// // set the start value to rangeController from this callback + /// rangeController.start = value.start; + /// // set the end value to rangeController from this callback + /// rangeController.end = value.end; + /// setState(() {}); + /// }, + /// child: Container( + /// child: sliderChart, + /// ), + /// ), + /// )), + /// ], + /// ), + /// ); + /// } + /// ``` + final RangeController? rangeController; + + /// Specifies maximum text width for axis labels. + /// + /// If an axis label exceeds the specified width, it will get trimmed and + /// ellipse(...) will be added at the end of the trimmed text. By default, + // the labels will not be trimmed. + /// + /// Complete label text will be shown in a tooltip when tapping/clicking over + /// the trimmed axis labels. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: CategoryAxis(maximumLabelWidth: 80), + /// series: [ + /// BarSeries( + /// dataSource: [ + /// ChartData(x: 'Goldin Finance 117', y: 597), + /// ChartData(x: 'Ping An Finance Center', y: 599), + /// ChartData(x: 'Makkah Clock Royal Tower', y: 601), + /// ChartData(x: 'Shanghai Tower', y: 632), + /// ChartData(x: 'Burj Khalifa', y: 828) + /// ], + /// xValueMapper: (ChartData sales, _) => sales.x, + /// yValueMapper: (ChartData sales, _) => sales.y + /// ) + /// ], + /// ) + /// ); + /// } + /// ``` + final double? maximumLabelWidth; + + /// Specifies the fixed width for the axis labels. This width represents the + /// space between axis line and axis title, if value exceeds the maximum size + /// of the axis label. + /// + /// If the `labelsExtent` value is greater than the axis label width, and no + /// [maximumLabelWidth] is specified, the additional space defined by + /// `labelsExtent` will be applied between the axis line and the axis title. + /// If [maximumLabelWidth] is specified, extra space is applied only if + /// `labelsExtent` exceeds the [maximumLabelWidth]. + /// + /// When multi-level labels are enabled, [labelsExtent] does not account for + /// their size; it only considers the axis label width. However, if the + /// `labelsExtent` value exceeds the axis label width, the extra space will + /// be applied between the axis title and the multi-level label. + /// + /// If the [labelPosition] is set to 'ChartDataLabelPosition.inside', and if + /// [labelsExtent] value exceeds the axis label width or [maximumLabelWidth], + /// additional space will be added between the axis line and the axis title. + /// + /// The [labelsExtent] property is especially useful when arranging the + /// multiple charts in a column layout, as it ensures that the Y-axis bounds + /// appear evenly aligned across all charts for better visual consistency. + /// + /// Additionally, if an axis label width or [maximumLabelWidth] is within the + /// specified value, white space will be added at the beginning for remaining + /// width.This is done to maintain uniform bounds and to eliminate axis label + /// flickering on zooming/panning. + /// + /// Complete label text will be shown in a tooltip when tapping/clicking over + /// the trimmed axis labels. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: CategoryAxis(labelsExtent: 70), + /// series: [ + /// BarSeries( + /// dataSource: [ + /// ChartData(x: 'Goldin Finance 117', y: 597), + /// ChartData(x: 'Ping An Finance Center', y: 599), + /// ChartData(x: 'Makkah Clock Royal Tower', y: 601), + /// ChartData(x: 'Shanghai Tower', y: 632), + /// ChartData(x: 'Burj Khalifa', y: 828) + /// ], + /// xValueMapper: (ChartData sales, _) => sales.x, + /// yValueMapper: (ChartData sales, _) => sales.y + /// ) + /// ], + /// ) + /// ); + /// } + /// ``` + 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. + /// + /// If the axis contains both initialVisibleMinimum or initialVisibleMaximum + /// and autoScrollingDelta, the autoScrollingDelta will be restricted. + /// Because both properties tends to define visible the number of data points + /// in the chart and so initialVisibleMinimum and initialVisibleMaximum + /// takes priority over autoScrollingDelta. + /// + /// 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; + + /// Border color of the axis label. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// borderColor: Colors.black, + /// ) + /// ) + /// ); + /// } + /// ``` + final Color? borderColor; + + /// Border width of the axis label. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// borderWidth: 2, + /// ) + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Border type of the axis label. + /// + /// Defaults to `AxisBorderType.rectangle`. + /// + /// Also refer [AxisBorderType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// axisBorderType: AxisBorderType.withoutTopAndBottom, + /// ) + /// ) + /// ); + /// } + /// ``` + final AxisBorderType axisBorderType; + + /// Customize the multi-level label’s border color, width, type, and + /// text style such as color, font size, etc. + /// + /// When the multi-level label’s width exceeds its respective segment, + /// then the label will get trimmed and on tapping / hovering over the + /// trimmed label, a tooltip will be shown. + /// + /// Also refer [multiLevelLabelFormatter]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabelStyle: MultiLevelLabelStyle( + /// borderWidth: 1, + /// borderColor: Colors.black, + /// borderType: MultiLevelBorderType.rectangle, + /// textStyle: TextStyle( + /// fontSize: 10, + /// color: Colors.black, + /// ) + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + final MultiLevelLabelStyle multiLevelLabelStyle; + + /// Called while rendering each multi-level label. + /// + /// Provides label text, the actual level of the label, index, and + /// text styles such as color, font size, etc using + /// `MultiLevelLabelRenderDetails` class. + /// + /// You can customize the text and text style using `ChartAxisLabel` class + /// and can return it. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabelFormatter: (MultiLevelLabelRenderDetails details) { + /// if (details.index == 1) { + /// return ChartAxisLabel('Text', details.textStyle); + /// } else { + /// return ChartAxisLabel(details.text, details.textStyle); + /// } + /// }, + /// multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 2, + /// text: 'First' + /// ), + /// NumericMultiLevelLabel( + /// start: 2, + /// end: 4, + /// text: 'Second' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final MultiLevelLabelFormatterCallback? multiLevelLabelFormatter; + + /// 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. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: CategoryAxis( + /// axisLabelFormatter: + /// (AxisLabelRenderDetails details) => axis(details), + /// ), + /// ) + /// ); + /// } + /// + /// ChartAxisLabel axis(AxisLabelRenderDetails details) { + /// return ChartAxisLabel('axis Label', details.textStyle); + /// } + /// ``` + final ChartLabelFormatterCallback? axisLabelFormatter; + + @protected + @factory + RenderChartAxis createRenderer(); + + @mustCallSuper + @override + RenderChartAxis createRenderObject(BuildContext context) { + return createRenderer() + ..widget = this + ..isVisible = isVisible + ..anchorRangeToVisiblePoints = anchorRangeToVisiblePoints + ..axisLine = axisLine + ..majorTickLines = majorTickLines + ..minorTickLines = minorTickLines + ..majorGridLines = majorGridLines + ..minorGridLines = minorGridLines + ..labelStyle = labelStyle + ..title = title + ..rangePadding = rangePadding + ..desiredIntervals = desiredIntervals + ..maximumLabels = maximumLabels + ..minorTicksPerInterval = minorTicksPerInterval + ..labelRotation = labelRotation + ..labelIntersectAction = labelIntersectAction + ..opposedPosition = opposedPosition + ..isInversed = isInversed + ..labelPosition = labelPosition + ..labelAlignment = labelAlignment + ..tickPosition = tickPosition + ..edgeLabelPlacement = edgeLabelPlacement + ..interval = interval + ..plotOffset = plotOffset + ..plotOffsetStart = plotOffsetStart + ..plotOffsetEnd = plotOffsetEnd + ..name = name + ..initialZoomFactor = initialZoomFactor + ..initialZoomPosition = initialZoomPosition + ..enableAutoIntervalOnZooming = enableAutoIntervalOnZooming + ..interactiveTooltip = interactiveTooltip + ..crossesAt = crossesAt + ..associatedAxisName = associatedAxisName + ..placeLabelsNearAxisLine = placeLabelsNearAxisLine + ..plotBands = plotBands + ..rangeController = rangeController + ..maximumLabelWidth = maximumLabelWidth + ..labelsExtent = labelsExtent + ..autoScrollingDelta = autoScrollingDelta + ..autoScrollingMode = autoScrollingMode + ..borderColor = borderColor + ..borderWidth = borderWidth + ..axisBorderType = axisBorderType + ..multiLevelLabelStyle = multiLevelLabelStyle + ..multiLevelLabelFormatter = multiLevelLabelFormatter + ..axisLabelFormatter = axisLabelFormatter + ..textDirection = Directionality.of(context); + } + + @mustCallSuper + @override + void updateRenderObject(BuildContext context, RenderChartAxis renderObject) { + renderObject + ..widget = this + ..isVisible = isVisible + ..anchorRangeToVisiblePoints = anchorRangeToVisiblePoints + ..axisLine = axisLine + ..majorTickLines = majorTickLines + ..minorTickLines = minorTickLines + ..majorGridLines = majorGridLines + ..minorGridLines = minorGridLines + ..labelStyle = labelStyle + ..title = title + ..rangePadding = rangePadding + ..desiredIntervals = desiredIntervals + ..maximumLabels = maximumLabels + ..minorTicksPerInterval = minorTicksPerInterval + ..labelRotation = labelRotation + ..labelIntersectAction = labelIntersectAction + ..opposedPosition = opposedPosition + ..isInversed = isInversed + ..labelPosition = labelPosition + ..labelAlignment = labelAlignment + ..tickPosition = tickPosition + ..edgeLabelPlacement = edgeLabelPlacement + ..interval = interval + ..plotOffset = plotOffset + ..plotOffsetStart = plotOffsetStart + ..plotOffsetEnd = plotOffsetEnd + ..name = name + ..enableAutoIntervalOnZooming = enableAutoIntervalOnZooming + ..interactiveTooltip = interactiveTooltip + ..crossesAt = crossesAt + ..associatedAxisName = associatedAxisName + ..placeLabelsNearAxisLine = placeLabelsNearAxisLine + ..plotBands = plotBands + ..rangeController = rangeController + ..maximumLabelWidth = maximumLabelWidth + ..labelsExtent = labelsExtent + ..autoScrollingDelta = autoScrollingDelta + ..autoScrollingMode = autoScrollingMode + ..borderColor = borderColor + ..borderWidth = borderWidth + ..axisBorderType = axisBorderType + ..multiLevelLabelStyle = multiLevelLabelStyle + ..multiLevelLabelFormatter = multiLevelLabelFormatter + ..axisLabelFormatter = axisLabelFormatter + ..textDirection = Directionality.of(context); + } +} + +enum AxisRender { gridLines, underPlotBand, normal, overPlotBand } + +abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { + final List dependents = []; + final List majorTickPositions = []; + final List minorTickPositions = []; + final List borderPositions = []; + + late ChartAxis widget; + // [plotOffset] is excluded from [size]. + late Size _renderSize; + AxisRender renderType = AxisRender.normal; + late _AxisRenderer? _renderer = _VerticalAxisRenderer(this); + List? visiblePlotBands; + AnimationController? _animationController; + CurvedAnimation? _animation; + AxisLabelIntersectAction effectiveLabelIntersectAction = + AxisLabelIntersectAction.hide; + + int sbsSeriesCount = 0; + // Added this property to ensure whether the zooming is progress + // for update the auto scrolling. + bool zoomingInProgress = false; + bool _needsRangeUpdate = true; + bool _isLayoutPhase = false; + bool _hasCollidingLabels = false; + bool _dependentIsStacked100 = false; + + num get visibleInterval => _visibleInterval; + num _visibleInterval = 1; + num get actualInterval => _actualInterval; + num _actualInterval = 1; + + List get visibleLabels => _visibleLabels; + final List _visibleLabels = []; + + List get visibleMultilevelLabels => + _visibleMultilevelLabels; + final List _visibleMultilevelLabels = + []; + + ChartAxisController get controller; + + @override + RenderCartesianAxes? get parent => super.parent as RenderCartesianAxes?; + + bool get canAnimate => parent?.enableAxisAnimation ?? false; + + @protected + bool hasTrimmedAxisLabel = false; + + DoubleRange? get actualRange => _actualRange; + DoubleRange? _actualRange; + + DoubleRange? get visibleRange => _visibleRange; + DoubleRange? _visibleRange; + + DoubleRange? get effectiveVisibleRange => + _effectiveVisibleRange ?? _visibleRange; + DoubleRange? _effectiveVisibleRange; + DoubleRangeTween? _visibleRangeTween; + + RenderChartAxis? get associatedAxis => _associatedAxis; + RenderChartAxis? _associatedAxis; + set associatedAxis(RenderChartAxis? value) { + if (_associatedAxis != value) { + _associatedAxis = value; + } + } + + SfChartThemeData? get chartThemeData => _chartThemeData; + SfChartThemeData? _chartThemeData; + set chartThemeData(SfChartThemeData? value) { + if (_chartThemeData != value) { + _chartThemeData = value; + } + } + + String? get name => _name; + String? _name; + set name(String? value) { + if (_name != value) { + _name = value; + } + } + + bool get isXAxis => _isXAxis; + bool _isXAxis = false; + set isXAxis(bool value) { + _isXAxis = value; + _invalidateOrientation(); + } + + bool get isVertical => _isVertical; + bool _isVertical = true; + set isVertical(bool value) { + _isVertical = value; + if (value) { + effectiveLabelIntersectAction = + labelIntersectAction == AxisLabelIntersectAction.none + ? AxisLabelIntersectAction.none + : AxisLabelIntersectAction.hide; + if (_renderer is! _VerticalAxisRenderer) { + _renderer = _VerticalAxisRenderer(this); + } + } else { + effectiveLabelIntersectAction = labelIntersectAction; + if (_renderer is! _HorizontalAxisRenderer) { + _renderer = _HorizontalAxisRenderer(this); + } + } + + markNeedsLayout(); + } + + bool get isTransposed => _isTransposed; + bool _isTransposed = false; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + _invalidateOrientation(); + markNeedsLayout(); + } + } + + bool get isVisible => _isVisible; + bool _isVisible = true; + set isVisible(bool value) { + _isVisible = value; + markNeedsLayout(); + } + + bool get anchorRangeToVisiblePoints => _anchorRangeToVisiblePoints; + bool _anchorRangeToVisiblePoints = true; + set anchorRangeToVisiblePoints(bool value) { + if (_anchorRangeToVisiblePoints != value) { + _anchorRangeToVisiblePoints = value; + markNeedsRangeUpdate(); + } + } + + bool get enableAutoIntervalOnZooming => _enableAutoIntervalOnZooming; + bool _enableAutoIntervalOnZooming = true; + set enableAutoIntervalOnZooming(bool value) { + if (_enableAutoIntervalOnZooming != value) { + _enableAutoIntervalOnZooming = value; + markNeedsLayout(); + } + } + + AxisLine get axisLine => _axisLine; + AxisLine _axisLine = const AxisLine(); + set axisLine(AxisLine value) { + if (_axisLine != value) { + _axisLine = value; + markNeedsLayout(); + } + } + + MajorTickLines get majorTickLines => _majorTickLines; + MajorTickLines _majorTickLines = const MajorTickLines(); + set majorTickLines(MajorTickLines value) { + if (_majorTickLines != value) { + _majorTickLines = value; + markNeedsLayout(); + } + } + + MinorTickLines get minorTickLines => _minorTickLines; + MinorTickLines _minorTickLines = const MinorTickLines(); + set minorTickLines(MinorTickLines value) { + if (_minorTickLines != value) { + _minorTickLines = value; + markNeedsLayout(); + } + } + + MajorGridLines get majorGridLines => _majorGridLines; + MajorGridLines _majorGridLines = const MajorGridLines(); + set majorGridLines(MajorGridLines value) { + if (_majorGridLines != value) { + _majorGridLines = value; + markNeedsLayout(); + } + } + + MinorGridLines get minorGridLines => _minorGridLines; + MinorGridLines _minorGridLines = const MinorGridLines(); + set minorGridLines(MinorGridLines value) { + if (_minorGridLines != value) { + _minorGridLines = value; + markNeedsLayout(); + } + } + + TextStyle? get labelStyle => _labelStyle; + TextStyle? _labelStyle; + set labelStyle(TextStyle? value) { + if (_labelStyle != value) { + _labelStyle = value; + markNeedsLayout(); + } + } + + AxisTitle get title => _title; + AxisTitle _title = const AxisTitle(); + set title(AxisTitle value) { + if (_title != value) { + _title = value; + markNeedsLayout(); + } + } + + ChartRangePadding get rangePadding => _rangePadding; + ChartRangePadding _rangePadding = ChartRangePadding.auto; + set rangePadding(ChartRangePadding value) { + if (_rangePadding != value) { + _rangePadding = value; + markNeedsLayout(); + } + } + + int? get desiredIntervals => _desiredIntervals; + int? _desiredIntervals; + set desiredIntervals(int? value) { + if (_desiredIntervals != value) { + _desiredIntervals = value; + markNeedsLayout(); + } + } + + int get maximumLabels => _maximumLabels; + int _maximumLabels = 3; + set maximumLabels(int value) { + if (_maximumLabels != value) { + _maximumLabels = value; + markNeedsLayout(); + } + } + + int get minorTicksPerInterval => _minorTicksPerInterval; + int _minorTicksPerInterval = 0; + set minorTicksPerInterval(int value) { + if (_minorTicksPerInterval != value) { + _minorTicksPerInterval = value; + markNeedsLayout(); + } + } + + int get labelRotation => _labelRotation; + int _labelRotation = 0; + set labelRotation(int value) { + if (_labelRotation != value) { + _labelRotation = value % 360; + markNeedsLayout(); + } + } + + AxisLabelIntersectAction get labelIntersectAction => _labelIntersectAction; + AxisLabelIntersectAction _labelIntersectAction = + AxisLabelIntersectAction.hide; + set labelIntersectAction(AxisLabelIntersectAction value) { + if (_labelIntersectAction != value) { + _labelIntersectAction = value; + if (isVertical) { + effectiveLabelIntersectAction = + labelIntersectAction == AxisLabelIntersectAction.none + ? AxisLabelIntersectAction.none + : AxisLabelIntersectAction.hide; + } else { + effectiveLabelIntersectAction = labelIntersectAction; + } + markNeedsLayout(); + } + } + + bool get opposedPosition => _opposedPosition; + bool _opposedPosition = false; + set opposedPosition(bool value) { + if (_opposedPosition != value) { + _opposedPosition = value; + _invertElementsOrder = value; + markNeedsLayout(); + } + } + + bool get invertElementsOrder => _invertElementsOrder; + bool _invertElementsOrder = false; + set invertElementsOrder(bool value) { + if (_invertElementsOrder != value) { + _invertElementsOrder = value; + generateMultiLevelLabels(); + updateMultiLevelLabels(); + // Called [preferredSize] to reupdate the label and tick positioning. + _renderer!._preferredSize(size); + } + } + + bool get isInversed => _isInversed; + bool _isInversed = false; + set isInversed(bool value) { + if (_isInversed != value) { + _isInversed = value; + markNeedsLayout(); + } + } + + ChartDataLabelPosition get labelPosition => _labelPosition; + ChartDataLabelPosition _labelPosition = ChartDataLabelPosition.outside; + set labelPosition(ChartDataLabelPosition value) { + if (_labelPosition != value) { + _labelPosition = value; + markNeedsLayout(); + } + } + + LabelAlignment get labelAlignment => _labelAlignment; + LabelAlignment _labelAlignment = LabelAlignment.center; + set labelAlignment(LabelAlignment value) { + if (_labelAlignment != value) { + _labelAlignment = value; + markNeedsLayout(); + } + } + + TickPosition get tickPosition => _tickPosition; + TickPosition _tickPosition = TickPosition.outside; + set tickPosition(TickPosition value) { + if (_tickPosition != value) { + _tickPosition = value; + markNeedsLayout(); + } + } + + EdgeLabelPlacement get edgeLabelPlacement => _edgeLabelPlacement; + EdgeLabelPlacement _edgeLabelPlacement = EdgeLabelPlacement.none; + set edgeLabelPlacement(EdgeLabelPlacement value) { + if (_edgeLabelPlacement != value) { + _edgeLabelPlacement = value; + markNeedsLayout(); + } + } + + double? get interval => _interval; + double? _interval; + set interval(double? value) { + if (_interval != value) { + assert(value == null || value > 0, 'Interval must be greater than 0'); + _interval = value; + markNeedsRangeUpdate(); + } + } + + double? get plotOffset => _plotOffset; + double? _plotOffset; + set plotOffset(double? value) { + if (_plotOffset != value) { + assert( + value == null || value >= 0, + 'PlotOffset must be greater than or equal to 0', + ); + _plotOffset = value; + _updateEffectivePlotOffset(); + } + } + + double? get plotOffsetStart => _plotOffsetStart; + double? _plotOffsetStart; + set plotOffsetStart(double? value) { + if (_plotOffsetStart != value) { + assert( + value == null || value >= 0, + 'PlotOffsetStart must be greater than or equal to 0', + ); + _plotOffsetStart = value; + _updateEffectivePlotOffset(); + } + } + + double? get plotOffsetEnd => _plotOffsetEnd; + double? _plotOffsetEnd; + set plotOffsetEnd(double? value) { + if (_plotOffsetEnd != value) { + assert( + value == null || value >= 0, + 'PlotOffsetEnd must be greater than or equal to 0', + ); + _plotOffsetEnd = value; + _updateEffectivePlotOffset(); + } + } + + double get initialZoomFactor => _initialZoomFactor; + double _initialZoomFactor = 1; + set initialZoomFactor(double value) { + if (_initialZoomFactor != value) { + _initialZoomFactor = value; + assert( + initialZoomFactor >= 0 && initialZoomFactor <= 1, + 'The initialZoomFactor of the axis should be between 0 and 1', + ); + controller.zoomFactor = clampDouble(value, 0.0, 1.0); + markNeedsRangeUpdate(); + } + } + + double get initialZoomPosition => _initialZoomPosition; + double _initialZoomPosition = 0.0; + set initialZoomPosition(double value) { + if (_initialZoomPosition != value) { + _initialZoomPosition = value; + _needsRangeUpdate = true; + assert( + initialZoomPosition >= 0 && initialZoomPosition <= 1, + 'The initialZoomPosition of the axis should be between 0 and 1', + ); + controller.zoomPosition = clampDouble(value, 0.0, 1.0); + markNeedsRangeUpdate(); + } + } + + InteractiveTooltip get interactiveTooltip => _interactiveTooltip; + InteractiveTooltip _interactiveTooltip = const InteractiveTooltip(); + set interactiveTooltip(InteractiveTooltip value) { + if (_interactiveTooltip != value) { + _interactiveTooltip = value; + markNeedsLayout(); + } + } + + dynamic get crossesAt => _crossesAt; + dynamic _crossesAt; + set crossesAt(dynamic value) { + if (_crossesAt != value) { + _crossesAt = value; + markNeedsLayout(); + } + } + + String? get associatedAxisName => _associatedAxisName; + String? _associatedAxisName; + set associatedAxisName(String? value) { + if (_associatedAxisName != value) { + _associatedAxisName = value; + markNeedsLayout(); + } + } + + bool get placeLabelsNearAxisLine => _placeLabelsNearAxisLine; + bool _placeLabelsNearAxisLine = false; + set placeLabelsNearAxisLine(bool value) { + if (_placeLabelsNearAxisLine != value) { + _placeLabelsNearAxisLine = value; + markNeedsLayout(); + } + } + + List get plotBands => _plotBands; + List _plotBands = []; + set plotBands(List value) { + if (_plotBands != value) { + _plotBands = value; + markNeedsLayout(); + } + } + + LabelPlacement get labelPlacement => _labelPlacement; + LabelPlacement _labelPlacement = LabelPlacement.onTicks; + set labelPlacement(LabelPlacement value) { + if (_labelPlacement != value) { + _labelPlacement = value; + markNeedsRangeUpdate(); + } + } + + RangeController? get rangeController => _rangeController; + RangeController? _rangeController; + set rangeController(RangeController? value) { + if (_rangeController != value) { + _rangeController = value; + markNeedsRangeUpdate(); + } + } + + double? get maximumLabelWidth => _maximumLabelWidth; + double? _maximumLabelWidth; + set maximumLabelWidth(double? value) { + if (_maximumLabelWidth != value) { + _maximumLabelWidth = value; + assert( + maximumLabelWidth == null || maximumLabelWidth! >= 0, + 'maximumLabelWidth must not be negative', + ); + markNeedsLayout(); + } + } + + double? get labelsExtent => _labelsExtent; + double? _labelsExtent; + set labelsExtent(double? value) { + if (_labelsExtent != value) { + _labelsExtent = value; + assert( + labelsExtent == null || labelsExtent! >= 0, + 'labelsExtent must not be negative', + ); + markNeedsLayout(); + } + } + + int? get autoScrollingDelta => _autoScrollingDelta; + int? _autoScrollingDelta; + set autoScrollingDelta(int? value) { + if (_autoScrollingDelta != value) { + _autoScrollingDelta = value; + markNeedsLayout(); + } + } + + AutoScrollingMode get autoScrollingMode => _autoScrollingMode; + AutoScrollingMode _autoScrollingMode = AutoScrollingMode.end; + set autoScrollingMode(AutoScrollingMode value) { + if (_autoScrollingMode != value) { + _autoScrollingMode = value; + markNeedsLayout(); + } + } + + Color? get borderColor => _borderColor; + Color? _borderColor; + set borderColor(Color? value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + double get borderWidth => _borderWidth; + double _borderWidth = 0.0; + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsPaint(); + } + } + + AxisBorderType get axisBorderType => _axisBorderType; + AxisBorderType _axisBorderType = AxisBorderType.rectangle; + set axisBorderType(AxisBorderType value) { + if (_axisBorderType != value) { + _axisBorderType = value; + markNeedsLayout(); + } + } + + MultiLevelLabelStyle get multiLevelLabelStyle => _multiLevelLabelStyle; + MultiLevelLabelStyle _multiLevelLabelStyle = const MultiLevelLabelStyle(); + set multiLevelLabelStyle(MultiLevelLabelStyle value) { + if (_multiLevelLabelStyle != value) { + _multiLevelLabelStyle = value; + markNeedsLayout(); + } + } + + MultiLevelLabelFormatterCallback? get multiLevelLabelFormatter => + _multiLevelLabelFormatter; + MultiLevelLabelFormatterCallback? _multiLevelLabelFormatter; + set multiLevelLabelFormatter(MultiLevelLabelFormatterCallback? value) { + if (_multiLevelLabelFormatter != value) { + _multiLevelLabelFormatter = value; + markNeedsLayout(); + } + } + + ChartLabelFormatterCallback? get axisLabelFormatter => _axisLabelFormatter; + ChartLabelFormatterCallback? _axisLabelFormatter; + set axisLabelFormatter(ChartLabelFormatterCallback? value) { + if (_axisLabelFormatter != value) { + _axisLabelFormatter = value; + markNeedsLayout(); + } + } + + TextDirection get textDirection => _textDirection; + late TextDirection _textDirection; + set textDirection(TextDirection value) { + _textDirection = value; + markNeedsLayout(); + } + + double get innerSize => _renderer?.innerSize ?? 0.0; + + double get outerSize => _renderer?.outerSize ?? 0.0; + + double _effectivePlotOffset = 0.0; + double _effectivePlotOffsetStart = 0.0; + double _effectivePlotOffsetEnd = 0.0; + + void _updateEffectivePlotOffset() { + if (plotOffset != null) { + _effectivePlotOffsetStart = plotOffset!; + _effectivePlotOffsetEnd = plotOffset!; + } else { + _effectivePlotOffsetStart = plotOffsetStart ?? 0.0; + _effectivePlotOffsetEnd = plotOffsetEnd ?? 0.0; + } + + _effectivePlotOffset = _effectivePlotOffsetStart + _effectivePlotOffsetEnd; + if (plotOffset != null || + plotOffsetStart != null || + plotOffsetEnd != null) { + markNeedsRangeUpdate(); + } + } + + void markNeedsRangeUpdate() { + if (hasSize) { + _needsRangeUpdate = true; + if (!_isLayoutPhase) { + markNeedsUpdate(); + } + } + } + + void addDependent(AxisDependent dependent, {bool isXAxis = true}) { + if (!dependents.contains(dependent)) { + dependents.add(dependent); + _needsRangeUpdate = true; + } + + if (dependent is Stacking100SeriesMixin) { + _dependentIsStacked100 = true; + } + + if (this.isXAxis != isXAxis) { + _needsRangeUpdate = true; + } + + this.isXAxis = isXAxis; + } + + void removeDependent(AxisDependent dependent) { + if (dependents.contains(dependent)) { + dependents.remove(dependent); + _needsRangeUpdate = true; + } + } + + void _invalidateOrientation() { + if (isXAxis) { + isVertical = isTransposed; + } else { + isVertical = !isTransposed; + } + } + + @override + void performUpdate() { + markNeedsLayout(); + } + + @override + void attach(PipelineOwner owner) { + _animationController = AnimationController( + vsync: parent!.vsync, + duration: const Duration(milliseconds: 1000), + value: 1.0, + ); + _animation = CurvedAnimation( + parent: _animationController!, + curve: const Interval(0.1, 0.9, curve: Curves.decelerate), + ); + _animationController!.addStatusListener(_onAnimationStatusChanged); + _animation!.addListener(markNeedsRangeUpdate); + controller._addZoomFactorAndPositionListener( + _onZoomFactorAndPositionChanged, + ); + super.attach(owner); + } + + void _onZoomFactorAndPositionChanged() { + associatedAxis?._needsRangeUpdate = true; + markNeedsRangeUpdate(); + } + + @override + void detach() { + _animationController?.removeStatusListener(_onAnimationStatusChanged); + _animation?.removeListener(markNeedsRangeUpdate); + controller._removeZoomFactorAndPositionListener( + _onZoomFactorAndPositionChanged, + ); + super.detach(); + } + + void _onAnimationStatusChanged(AnimationStatus status) { + if (status == AnimationStatus.completed) { + if (_visibleRangeTween != null) { + _visibleRange = _visibleRangeTween!.end; + _visibleRangeTween = null; + } + _effectiveVisibleRange = null; + controller._handleAnimationCompleted(); + } + } + + @override + void performLayout() { + _isLayoutPhase = true; + final Size availableSize = constraints.biggest; + final bool isResized = (parentData! as CartesianAxesParentData).isResized; + if (_needsRangeUpdate || isResized) { + _calculateRangeAndInterval(availableSize); + } + + visibleLabels.clear(); + visibleMultilevelLabels.clear(); + majorTickPositions.clear(); + minorTickPositions.clear(); + + _renderSize = availableSize; + + if (visibleRange != null) { + generateVisibleLabels(); + generateMultiLevelLabels(); + } + + Size newSize = Size.zero; + if (isVisible) { + assert(_renderer != null); + newSize = _renderer!._preferredSize(availableSize); + } else { + newSize = + isVertical + ? Size(0, availableSize.height) + : Size(availableSize.width, 0); + } + final Size constraintsSize = constraints.biggest; + size = Size( + min(newSize.width, constraintsSize.width), + min(newSize.height, constraintsSize.height), + ); + + if (isVertical) { + _renderSize = Size(size.width, size.height - _effectivePlotOffset); + } else { + _renderSize = Size(size.width - _effectivePlotOffset, size.height); + } + + if (visibleRange != null) { + if (_effectivePlotOffset > 0) { + _calculateLabelPositions(); + _calculateBorderPositions(); + } + updateMultiLevelLabels(); + calculateTickPositions( + labelPlacement, + source: majorTickPositions, + canCalculateMinorTick: minorTicksPerInterval > 0, + ); + } + _needsRangeUpdate = false; + zoomingInProgress = false; + } + + @override + void performPostLayout() { + super.performPostLayout(); + _isLayoutPhase = false; + if (visibleRange != null) { + visiblePlotBands?.clear(); + generatePlotBands(); + } + } + + void calculateVisibleRangeAndInvokeAnimation() { + if (parent == null || + !parent!.enableAxisAnimation || + !hasSize || + _animation == null) { + return; + } + + Size availableSize = size; + if (_effectivePlotOffset > 0) { + if (isVertical) { + availableSize = Size( + availableSize.width, + availableSize.height - _effectivePlotOffset, + ); + } else { + availableSize = Size( + availableSize.width - _effectivePlotOffset, + availableSize.height, + ); + } + } + DoubleRange newActualRange = calculateActualRange(); + final num newActualInterval = calculateActualInterval( + newActualRange, + availableSize, + ); + newActualRange = applyRangePadding( + newActualRange, + newActualInterval, + availableSize, + ); + late DoubleRange newVisibleRange; + // TODO(VijayakumarM): Need to handle anchorRangeToVisiblePoints in series? + if (!anchorRangeToVisiblePoints && + isXAxis && + parent != null && + parent!.behaviorArea != null && + parent!.behaviorArea!.effectiveZoomMode == ZoomMode.x) { + newVisibleRange = newActualRange; + } else { + newVisibleRange = calculateVisibleRange(newActualRange); + if (autoScrollingDelta != null && + autoScrollingDelta! > 0 && + !zoomingInProgress) { + final DoubleRange autoScrollRange = updateAutoScrollingDelta( + autoScrollingDelta!, + newActualRange, + newVisibleRange, + ); + if ((autoScrollingMode == AutoScrollingMode.end && + newActualRange.minimum < autoScrollRange.minimum) || + (autoScrollingMode == AutoScrollingMode.start && + newActualRange.maximum > autoScrollRange.maximum)) { + newVisibleRange = autoScrollRange; + } + } + } + + if (newVisibleRange != _visibleRange) { + _effectiveVisibleRange = newVisibleRange; + _visibleRangeTween = DoubleRangeTween( + begin: _visibleRange, + end: newVisibleRange, + ); + _animationController?.forward(from: 0.0); + } + } + + void _calculateRangeAndInterval(Size availableSize) { + if (_effectivePlotOffset > 0) { + if (isVertical) { + availableSize = Size( + availableSize.width, + availableSize.height - _effectivePlotOffset, + ); + } else { + availableSize = Size( + availableSize.width - _effectivePlotOffset, + availableSize.height, + ); + } + } + + DoubleRange newActualRange = calculateActualRange(); + final num newActualInterval = calculateActualInterval( + newActualRange, + availableSize, + ); + _actualInterval = newActualInterval; + newActualRange = applyRangePadding( + newActualRange, + newActualInterval, + availableSize, + ); + controller._updateActualRange(newActualRange); + late DoubleRange newVisibleRange; + num newVisibleInterval = _actualInterval; + if (_visibleRangeTween == null) { + final bool canKeepActualRange = !anchorRangeToVisiblePoints; + if (canKeepActualRange && + !isXAxis && + parent != null && + parent!.behaviorArea != null && + parent!.behaviorArea!.effectiveZoomMode == ZoomMode.x) { + newVisibleRange = newActualRange.copyWith(); + } else { + newVisibleRange = calculateVisibleRange(newActualRange.copyWith()); + if (autoScrollingDelta != null && + autoScrollingDelta! > 0 && + !zoomingInProgress) { + final DoubleRange autoScrollRange = updateAutoScrollingDelta( + autoScrollingDelta!, + newActualRange, + newVisibleRange, + ); + if ((autoScrollingMode == AutoScrollingMode.end && + newActualRange.minimum < autoScrollRange.minimum) || + (autoScrollingMode == AutoScrollingMode.start && + newActualRange.maximum > autoScrollRange.maximum)) { + newVisibleRange = autoScrollRange; + } + } + newVisibleInterval = calculateVisibleInterval( + newVisibleRange, + availableSize, + ); + } + } else { + final bool canKeepActualRange = !anchorRangeToVisiblePoints; + if (canKeepActualRange && + !isXAxis && + parent != null && + parent!.behaviorArea != null && + parent!.behaviorArea!.effectiveZoomMode == ZoomMode.x) { + newVisibleRange = newActualRange.copyWith(); + } else { + newVisibleRange = _visibleRangeTween!.evaluate(_animation!)!; + newVisibleInterval = calculateVisibleInterval( + newVisibleRange, + availableSize, + ); + } + } + + // Handled range controller when performing panning. + if (rangeController != null) { + updateRangeControllerValues(newVisibleRange); + } + + if (parent != null && + parent!.onActualRangeChanged != null && + (newActualRange != actualRange || newVisibleRange != visibleRange)) { + final ActualRangeChangedArgs args = ActualRangeChangedArgs( + name, + widget, + newActualRange.minimum, + newActualRange.maximum, + newActualInterval, + isVertical ? AxisOrientation.vertical : AxisOrientation.horizontal, + ); + args + ..visibleMin = newVisibleRange.minimum + ..visibleMax = newVisibleRange.maximum + ..visibleInterval = newVisibleInterval; + parent!.onActualRangeChanged!(args); + if (newVisibleRange.minimum != args.visibleMin || + newVisibleRange.maximum != args.visibleMax) { + newVisibleRange.minimum = args.visibleMin; + newVisibleRange.maximum = args.visibleMax; + final num actualRangeDelta = newActualRange.delta; + controller.zoomFactor = newVisibleRange.delta / actualRangeDelta; + controller.zoomPosition = + (newVisibleRange.minimum - newActualRange.minimum) / + actualRangeDelta; + } + newVisibleInterval = args.visibleInterval; + } + _actualRange = newActualRange; + controller._actualRange = newActualRange; + _actualInterval = newActualInterval; + _visibleRange = newVisibleRange; + _visibleInterval = newVisibleInterval; + + if (attached && _needsRangeUpdate) { + invokeLayoutCallback((Object? constraints) { + for (final AxisDependent dependent in dependents) { + dependent.didRangeChange(this); + } + }); + } + } + + @protected + DoubleRange updateAutoScrollingDelta( + int scrollingDelta, + DoubleRange actualRange, + DoubleRange visibleRange, + ) { + switch (autoScrollingMode) { + case AutoScrollingMode.start: + final DoubleRange autoScrollRange = DoubleRange( + actualRange.minimum, + actualRange.minimum + scrollingDelta, + ); + controller.zoomFactor = autoScrollRange.delta / actualRange.delta; + controller.zoomPosition = 0; + return autoScrollRange; + + case AutoScrollingMode.end: + final DoubleRange autoScrollRange = DoubleRange( + actualRange.maximum - scrollingDelta, + actualRange.maximum, + ); + controller.zoomFactor = autoScrollRange.delta / actualRange.delta; + controller.zoomPosition = 1 - controller.zoomFactor; + return autoScrollRange; + } + } + + void updateRangeControllerValues(DoubleRange newVisibleRange) { + if (rangeController!.start != newVisibleRange.minimum) { + rangeController!.start = newVisibleRange.minimum; + } + if (rangeController!.end != newVisibleRange.maximum) { + rangeController!.end = newVisibleRange.maximum; + } + } + + @protected + DoubleRange calculateActualRange() { + if (dependents.isEmpty) { + return defaultRange(); + } + + num minimum = double.infinity; + num maximum = double.negativeInfinity; + + for (final AxisDependent dependent in dependents) { + if (dependent.includeRange) { + final DoubleRange current = dependent.range(this); + if (current.minimum.isFinite) { + minimum = min(minimum, current.minimum); + } + if (current.maximum.isFinite) { + maximum = max(maximum, current.maximum); + } + } + } + + if (minimum == maximum && minimum < 0 && maximum < 0) { + maximum = 0.0; + } + + if (minimum == maximum && minimum > 0 && maximum > 0) { + minimum = 0.0; + } + + if (minimum.isInfinite && maximum.isInfinite) { + final DoubleRange range = defaultRange(); + minimum = range.minimum; + maximum = range.maximum; + } + + return DoubleRange(minimum, maximum); + } + + DoubleRange defaultRange(); + + @protected + num calculateActualInterval(DoubleRange range, Size availableSize) { + return interval ?? calculateNiceInterval(range.delta, availableSize); + } + + @protected + DoubleRange applyRangePadding( + DoubleRange range, + num interval, + Size availableSize, + ) { + final ChartRangePadding padding = effectiveRangePadding(); + if (padding == ChartRangePadding.additional || + padding == ChartRangePadding.additionalStart || + padding == ChartRangePadding.additionalEnd) { + _addAdditionalRange(range, interval); + } else if (padding == ChartRangePadding.round || + padding == ChartRangePadding.roundStart || + padding == ChartRangePadding.roundEnd) { + _roundRange(range, interval); + } else if (padding == ChartRangePadding.normal) { + addNormalRange(range, interval, availableSize); + } + + return range; + } + + @protected + ChartRangePadding effectiveRangePadding() { + ChartRangePadding padding = ChartRangePadding.auto; + if (rangePadding == ChartRangePadding.auto) { + if (isVertical) { + padding = + !isTransposed + ? (_dependentIsStacked100 + ? ChartRangePadding.round + : ChartRangePadding.normal) + : ChartRangePadding.none; + } else { + padding = + isTransposed + ? (_dependentIsStacked100 + ? ChartRangePadding.round + : ChartRangePadding.normal) + : ChartRangePadding.none; + } + } else { + padding = rangePadding; + } + return padding; + } + + void _roundRange(DoubleRange range, num interval) { + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = ((range.minimum / interval).floor()) * interval; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = ((range.maximum / interval).ceil()) * interval; + } + } + + void _addAdditionalRange(DoubleRange range, num interval) { + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalStart) { + final num minimum = ((range.minimum / interval).floor()) * interval; + range.minimum = minimum - interval; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + final num maximum = ((range.maximum / interval).ceil()) * interval; + range.maximum = maximum + interval; + } + } + + @protected + void addNormalRange(DoubleRange range, num interval, Size availableSize) { + num minimum; + num remaining; + num start = range.minimum; + if (range.minimum < 0) { + final num end = range.maximum; + if (start.isNegative && end.isNegative) { + minimum = start > ((5.0 / 6.0) * end) ? 0 : start - (end - start) / 2; + } else { + start = 0.0; + minimum = range.minimum + (range.minimum / 20); + } + + remaining = interval + _remainder(minimum, interval); + if ((0.365 * interval) >= remaining) { + minimum -= interval; + } + + if (_remainder(minimum, interval) < 0) { + minimum = (minimum - interval) - _remainder(minimum, interval); + } + } else { + minimum = + range.minimum < ((5.0 / 6.0) * range.maximum) + ? 0 + : (range.minimum - (range.maximum - range.minimum) / 2); + if (minimum % interval > 0) { + minimum -= minimum % interval; + } + } + num maximum = + (range.maximum > 0) + ? (range.maximum + (range.maximum - start) / 20) + : (range.maximum - (range.maximum - start) / 20); + remaining = interval - (maximum % interval); + if ((0.365 * interval) >= remaining) { + maximum += interval; + } + if (maximum % interval > 0) { + maximum = + range.maximum > 0 + ? (maximum + interval) - (maximum % interval) + : (maximum + interval) + (maximum % interval); + } + range.minimum = minimum; + range.maximum = maximum; + if (minimum == 0) { + updateNormalRangePadding(range, availableSize); + } + } + + num _remainder(num minimum, num interval) { + if (minimum.isNegative) { + final num min = + num.tryParse(minimum.toString().replaceAll(RegExp('-'), ''))!; + return num.tryParse('-${min % interval}')!; + } else { + return minimum % interval; + } + } + + void updateNormalRangePadding(DoubleRange range, Size size) { + _actualInterval = calculateActualInterval(range, size); + range.maximum = (range.maximum / _actualInterval).ceil() * _actualInterval; + } + + DoubleRange calculateVisibleRange(DoubleRange range) { + if (controller.zoomFactor < 1) { + final DoubleRange baseRange = range; + num start = + baseRange.minimum + (controller.zoomPosition * baseRange.delta); + num end = start + (controller.zoomFactor * baseRange.delta); + + if (start < baseRange.minimum) { + end = end + (baseRange.minimum - start); + start = baseRange.minimum; + } + + if (end > baseRange.maximum) { + start = start - (end - baseRange.maximum); + end = baseRange.maximum; + } + + return DoubleRange(start, end); + } + + return range; + } + + @protected + num calculateVisibleInterval(DoubleRange visibleRange, Size availableSize) { + if (controller.zoomFactor < 1 || controller.zoomPosition > 0) { + return enableAutoIntervalOnZooming + ? calculateNiceInterval(visibleRange.delta, availableSize) + : _actualInterval; + } + + return _actualInterval; + } + + @protected + num calculateNiceInterval(num delta, Size availableSize) { + final num intervalsCount = desiredIntervalsCount(availableSize); + num niceInterval = desiredNiceInterval(delta, intervalsCount); + assert(niceInterval != double.infinity); + if (desiredIntervals != null) { + return niceInterval; + } + + final List divisions = [10, 5, 2, 1]; + final num minimumInterval = + niceInterval == 0 ? 0 : pow(10, (log(niceInterval) / log(10)).floor()); + for (int i = 0; i < divisions.length; i++) { + final num interval = divisions[i]; + final num currentInterval = minimumInterval * interval; + if (intervalsCount < (delta / currentInterval)) { + break; + } + niceInterval = currentInterval; + } + + return niceInterval; + } + + @protected + num desiredNiceInterval(num delta, num intervalsCount) { + return delta / intervalsCount; + } + + @nonVirtual + num desiredIntervalsCount(Size availableSize) { + if (desiredIntervals == null) { + final num size = isVertical ? availableSize.height : availableSize.width; + num intervalsCount = (isVertical ? 1 : 0.533) * maximumLabels; + intervalsCount = max(size * (intervalsCount / 100), 1); + return intervalsCount; + } + return desiredIntervals!; + } + + @protected + @mustCallSuper + void generateVisibleLabels() { + _calculateLabelPositions(); + _calculateBorderPositions(); + } + + void _calculateBorderPositions() { + borderPositions.clear(); + + final Color labelBorderColor = + (borderColor ?? chartThemeData!.axisLineColor)!; + if (borderWidth > 0 && labelBorderColor != Colors.transparent) { + switch (labelPlacement) { + case LabelPlacement.onTicks: + calculateTickPositions( + LabelPlacement.betweenTicks, + source: borderPositions, + canCalculateMajorTick: false, + ); + break; + + case LabelPlacement.betweenTicks: + calculateTickPositions( + LabelPlacement.onTicks, + source: borderPositions, + canCalculateMajorTick: false, + ); + break; + } + } + } + + @protected + void generateMultiLevelLabels() {} + + @protected + void updateMultiLevelLabels() {} + + void _calculateLabelPositions() { + if (visibleLabels.isEmpty) { + return; + } + + final int length = visibleLabels.length; + _AlignLabel betweenLabelsAlign; + switch (labelAlignment) { + case LabelAlignment.start: + betweenLabelsAlign = !isInversed ? _startAlignment : _endAlignment; + break; + + case LabelAlignment.end: + betweenLabelsAlign = !isInversed ? _endAlignment : _startAlignment; + break; + + case LabelAlignment.center: + betweenLabelsAlign = _centerAlignment; + break; + } + + _AlignLabel startLabelAlign = betweenLabelsAlign; + _AlignLabel endLabelAlign = betweenLabelsAlign; + if (edgeLabelPlacement == EdgeLabelPlacement.shift) { + if (isVertical) { + if (isInversed) { + startLabelAlign = _edgeLabelStartAlignment; + endLabelAlign = _edgeLabelEndAlignment; + } else { + startLabelAlign = _edgeLabelEndAlignment; + endLabelAlign = _edgeLabelStartAlignment; + } + } else { + if (isInversed) { + startLabelAlign = _edgeLabelEndAlignment; + endLabelAlign = _edgeLabelStartAlignment; + } else { + startLabelAlign = _edgeLabelStartAlignment; + endLabelAlign = _edgeLabelEndAlignment; + } + } + } + + if (effectiveLabelIntersectAction != AxisLabelIntersectAction.none) { + _hasCollidingLabels = _isCollidingLabels( + length, + startLabelAlign, + betweenLabelsAlign, + endLabelAlign, + ); + if (_hasCollidingLabels) { + _arrangeLabels( + length, + startLabelAlign, + betweenLabelsAlign, + endLabelAlign, + ); + } + } else { + _hasCollidingLabels = false; + _arrangeLabels( + length, + startLabelAlign, + betweenLabelsAlign, + endLabelAlign, + ); + } + } + + bool _isCollidingLabels( + int labelsLength, + _AlignLabel startLabelAlign, + _AlignLabel betweenLabelsAlign, + _AlignLabel endLabelAlign, + ) { + if (labelsLength == 1) { + final AxisLabel current = visibleLabels[0]; + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + return false; + } + + if (labelsLength < 2) { + return false; + } + + late int startIndex; + late int endIndex; + AxisLabel source; + AxisLabel current = visibleLabels[0]; + + if (edgeLabelPlacement == EdgeLabelPlacement.hide) { + current.isVisible = false; + // Start is [2] because the [0] is hidden and source is [1]. + startIndex = 2; + // End is [labelsLength - 2]. + // Because the last label [labelsLength - 1] is hidden. + endIndex = labelsLength - 2; + // Taken [1] label as source because edge label [0] is hidden. + source = visibleLabels[1]; + source.position = betweenLabelsAlign(pointToPixel(source.value), source); + } else { + // Start is [1] because the [0] is source. + startIndex = 1; + endIndex = labelsLength - 1; + source = current; + source.position = startLabelAlign(pointToPixel(source.value), source); + } + + bool hasCollidingLabels = false; + for (int i = startIndex; i < endIndex; i++) { + current = visibleLabels[i]; + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + hasCollidingLabels = _isIntersect(current, source); + if (hasCollidingLabels) { + return true; + } + + source = current; + } + + // If edgeLabelPlacement is [hide], endIndex is [length - 2]. + // If edgeLabelPlacement is [none] or [shift], endIndex is [length - 1]. + current = visibleLabels[endIndex]; + if (edgeLabelPlacement == EdgeLabelPlacement.hide) { + // Now need to hide the [length - 1] last label. + visibleLabels[labelsLength - 1].isVisible = false; + // Need to find the endIndex [length - 2] position. Because, the for loop + // only run till the previous index of endIndex. + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + } else { + // Need to find the endIndex [length - 1] position. Because, the for loop + // only run till the previous index of endIndex. + current.position = endLabelAlign(pointToPixel(current.value), current); + } + return _isIntersect(current, source); + } + + void _arrangeLabels( + int length, + _AlignLabel startLabelAlign, + _AlignLabel betweenLabelsAlign, + _AlignLabel endLabelAlign, + ) { + if (length == 1) { + final AxisLabel current = visibleLabels[0]; + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + return; + } else if (length <= 2) { + // Handles 2 visible labels when set [EdgeLabelPlacement] as hide. + AxisLabel current = visibleLabels[0]; + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + current = visibleLabels[1]; + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + return; + } + late int startIndex; + late int endIndex; + final double extent = _renderSize.width / length; + final double edgeLabelsExtent = extent / 2; + // The previous label which is not intersecting with the current label. + AxisLabel source; + AxisLabel current = visibleLabels[0]; + final AxisLabel Function(AxisLabel, AxisLabel, double, _AlignLabel, {int i}) + applyLabelIntersectAction = _applyLabelIntersectAction(); + + if (edgeLabelPlacement == EdgeLabelPlacement.hide) { + // Start edge label [0] visibility is collapsed. + current.isVisible = false; + // Start is [2] because the [0] is hidden and source is [1]. + startIndex = 2; + // End is [length - 2] because the last label [length - 1] is hidden. + endIndex = length - 2; + // Taken [1] label as current because edge label [0] is hidden. + current = visibleLabels[1]; + // [1] index will overlaps with the [2] label. So taken the [2] as source. + source = visibleLabels[startIndex]; + source.position = betweenLabelsAlign(pointToPixel(source.value), source); + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + if (effectiveLabelIntersectAction != AxisLabelIntersectAction.hide && + effectiveLabelIntersectAction != + AxisLabelIntersectAction.multipleRows) { + source = applyLabelIntersectAction( + current, + source, + extent, + betweenLabelsAlign, + ); + } else { + source = current; + } + } else { + // Start is [1] because the [0] is source. + startIndex = 1; + endIndex = length - 1; + // [0] index will overlaps with the [1] label. So taken the [1] as source. + source = visibleLabels[startIndex]; + current.position = startLabelAlign(pointToPixel(current.value), current); + source.position = betweenLabelsAlign(pointToPixel(source.value), source); + if (effectiveLabelIntersectAction != AxisLabelIntersectAction.hide && + effectiveLabelIntersectAction != + AxisLabelIntersectAction.multipleRows) { + source = applyLabelIntersectAction( + current, + source, + edgeLabelsExtent, + startLabelAlign, + ); + } else { + source = current; + } + } + + for (int i = startIndex; i < endIndex; i++) { + current = visibleLabels[i]; + current.position = betweenLabelsAlign( + pointToPixel(current.value), + current, + ); + source = applyLabelIntersectAction( + current, + source, + extent, + betweenLabelsAlign, + i: i, + ); + } + + if (edgeLabelPlacement == EdgeLabelPlacement.hide) { + current = visibleLabels[endIndex]; + current.position = endLabelAlign(pointToPixel(current.value), current); + applyLabelIntersectAction( + current, + source, + edgeLabelsExtent, + endLabelAlign, + i: endIndex, + ); + visibleLabels[length - 1].isVisible = false; + } else { + current = visibleLabels[endIndex]; + current.position = endLabelAlign(pointToPixel(current.value), current); + applyLabelIntersectAction( + current, + source, + edgeLabelsExtent, + endLabelAlign, + i: endIndex, + ); + } + } + + AxisLabel Function( + AxisLabel source, + AxisLabel target, + double extent, + _AlignLabel align, { + int i, + }) + _applyLabelIntersectAction() { + switch (effectiveLabelIntersectAction) { + case AxisLabelIntersectAction.none: + return _applyNoneIntersectAction; + + case AxisLabelIntersectAction.hide: + return _applyHideIntersectAction; + + case AxisLabelIntersectAction.wrap: + return _applyWrapIntersectAction; + + case AxisLabelIntersectAction.trim: + return _applyTrimIntersectAction; + + case AxisLabelIntersectAction.multipleRows: + return _applyMultipleRowsIntersectAction; + + case AxisLabelIntersectAction.rotate45: + return _applyRotate45IntersectAction; + + case AxisLabelIntersectAction.rotate90: + return _applyRotate90IntersectAction; + } + } + + AxisLabel _applyNoneIntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + return current; + } + + AxisLabel _applyHideIntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + current.isVisible = !_isIntersect(current, source); + return current.isVisible ? current : source; + } + + AxisLabel _applyWrapIntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + final List words = current.renderText.split(RegExp(r'\s+')); + final int wrapLength = words.length; + final TextStyle textStyle = current.labelStyle; + final List wrapLabels = []; + double textWidth = 0.0; + double textHeight = 0.0; + bool isTrimmed = false; + for (int i = 0; i < wrapLength; i++) { + final String text = words[i]; + Size textSize = measureText(text, textStyle, labelRotation); + String trimText = text; + if (textSize.width > extent) { + trimText = trimmedText(text, textStyle, extent, labelRotation); + } + if (text != trimText) { + isTrimmed = true; + hasTrimmedAxisLabel = true; + } + textSize = measureText(trimText, textStyle, labelRotation); + textWidth = max(textWidth, textSize.width); + textHeight += textSize.height; + wrapLabels.add(trimText); + } + + final String finalText = wrapLabels.join('\n'); + current + ..renderText = finalText + ..trimmedText = isTrimmed ? finalText : current.trimmedText + ..labelSize = Size(textWidth, textHeight) + ..position = align(pointToPixel(current.value), current); + words.clear(); + return current; + } + + AxisLabel _applyTrimIntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + final TextStyle textStyle = current.labelStyle; + if (current.labelSize.width > extent) { + current.renderText = trimmedText( + current.renderText, + textStyle, + extent, + labelRotation, + ); + if (current.renderText != current.text) { + current.trimmedText = current.renderText; + hasTrimmedAxisLabel = true; + } + } + current + ..labelSize = measureText(current.renderText, textStyle, labelRotation) + ..position = align(pointToPixel(current.value), current); + return current; + } + + AxisLabel _applyMultipleRowsIntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + if (_isIntersect(current, source)) { + current + ..labelSize = measureText( + current.renderText, + current.labelStyle, + labelRotation, + ) + ..position = align(pointToPixel(current.value), current); + _computeRowIndexForMultiRows(i, current); + return source; + } + return current; + } + + /// Checks if the current label intersects with previous labels. + /// Adjusts the current label's row index to avoid overlap. + void _computeRowIndexForMultiRows(int length, AxisLabel current) { + final List labelIndex = []; + for (int i = length - 1; i >= 0; i--) { + final AxisLabel source = visibleLabels[i]; + if (_isIntersect(current, source)) { + labelIndex.add(source._rowIndex); + current._rowIndex = + current._rowIndex > source._rowIndex + ? current._rowIndex + : source._rowIndex + 1; + } else { + current._rowIndex = + labelIndex.contains(source._rowIndex) + ? current._rowIndex + : source._rowIndex; + } + } + } + + AxisLabel _applyRotate90IntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + current + ..labelSize = measureText( + current.renderText, + current.labelStyle, + angle90Degree, + ) + ..position = align(pointToPixel(current.value), current); + return current; + } + + AxisLabel _applyRotate45IntersectAction( + AxisLabel current, + AxisLabel source, + double extent, + _AlignLabel align, { + int i = 0, + }) { + current + ..labelSize = measureText( + current.renderText, + current.labelStyle, + angle45Degree, + ) + ..position = align(pointToPixel(current.value), current); + return current; + } + + bool _isIntersect(AxisLabel current, AxisLabel source) { + return isVertical + ? _isVerticalLabelIntersect(current, source) + : _isHorizontalLabelIntersect(current, source); + } + + bool _isHorizontalLabelIntersect(AxisLabel current, AxisLabel source) { + if (current.position != null && source.position != null) { + return !isInversed + ? current.position! < source.position! + source.labelSize.width + : current.position! + current.labelSize.width > source.position!; + } + return false; + } + + bool _isVerticalLabelIntersect(AxisLabel current, AxisLabel source) { + if (current.position != null && source.position != null) { + return !isInversed + ? current.position! + current.labelSize.height > source.position! + : current.position! < source.position! + source.labelSize.height; + } + return false; + } + + double _edgeLabelStartAlignment(double position, AxisLabel label) { + switch (edgeLabelPlacement) { + case EdgeLabelPlacement.none: + case EdgeLabelPlacement.hide: + return position; + + case EdgeLabelPlacement.shift: + position = _centerAlignment(position, label); + return position < 0 ? 0.0 : position; + } + } + + double _edgeLabelEndAlignment(double position, AxisLabel label) { + switch (edgeLabelPlacement) { + case EdgeLabelPlacement.none: + case EdgeLabelPlacement.hide: + return isVertical + ? position - label.labelSize.height + : position - label.labelSize.width; + + case EdgeLabelPlacement.shift: + position = _centerAlignment(position, label); + final double actualWidth = _renderSize.width + _effectivePlotOffset; + final double actualHeight = _renderSize.height + _effectivePlotOffset; + if (isVertical) { + final double labelSize = label.labelSize.height; + if (position + labelSize > actualHeight) { + return actualHeight - labelSize; + } + } else { + final double labelSize = label.labelSize.width; + if (position + labelSize > actualWidth) { + return actualWidth - labelSize; + } + } + } + return position; + } + + double _startAlignment(double position, AxisLabel label) { + switch (edgeLabelPlacement) { + case EdgeLabelPlacement.none: + case EdgeLabelPlacement.hide: + return position; + + case EdgeLabelPlacement.shift: + final double shiftSize = + isVertical ? label.labelSize.height / 2 : label.labelSize.width / 2; + return position - shiftSize < 0 ? 0.0 : position; + } + } + + double _endAlignment(double position, AxisLabel label) { + switch (edgeLabelPlacement) { + case EdgeLabelPlacement.none: + case EdgeLabelPlacement.hide: + return isVertical + ? position - label.labelSize.height + : position - label.labelSize.width; + + case EdgeLabelPlacement.shift: + if (isVertical) { + final double shiftSize = label.labelSize.height; + return position + shiftSize > _renderSize.height + ? _renderSize.height - shiftSize + : position - shiftSize; + } else { + final double shiftSize = label.labelSize.width; + return position + shiftSize > _renderSize.width + ? _renderSize.width - shiftSize + : position - shiftSize; + } + } + } + + double _centerAlignment(double position, AxisLabel label) { + return isVertical + ? position - label.labelSize.height / 2 + : position - label.labelSize.width / 2; + } + + @protected + void calculateTickPositions( + LabelPlacement placement, { + List? source, + bool canCalculateMinorTick = false, + bool canCalculateMajorTick = true, + }) { + int length = visibleLabels.length; + if (length == 0 || effectiveVisibleRange == null) { + return; + } + + final bool isBetweenTicks = placement == LabelPlacement.betweenTicks; + final num tickBetweenLabel = isBetweenTicks ? visibleInterval / 2 : 0; + length += isBetweenTicks ? 1 : 0; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + final double? position = visibleLabels[i].position; + if (position == null || position.isNaN) { + continue; + } + num current; + if (isBetweenTicks) { + if (i < lastIndex) { + current = visibleLabels[i].value - tickBetweenLabel; + } else { + current = + (visibleLabels[i - 1].value + visibleInterval) - tickBetweenLabel; + } + } else { + current = visibleLabels[i].value; + } + + source!.add(pointToPixel(current)); + + if (canCalculateMinorTick) { + final num start = current; + final num end = start + visibleInterval; + final double minorTickInterval = + visibleInterval / (minorTicksPerInterval + 1); + for (int j = 1; j <= minorTicksPerInterval; j++) { + final double tickValue = start + minorTickInterval * j; + if (tickValue < end && tickValue < visibleRange!.maximum) { + minorTickPositions.add(pointToPixel(tickValue)); + } + } + } + } + } + + @protected + void generatePlotBands() { + if (plotBands.isNotEmpty && + associatedAxis != null && + associatedAxis!.visibleRange != null) { + if (visiblePlotBands != null) { + visiblePlotBands!.clear(); + } else { + visiblePlotBands = []; + } + final int length = plotBands.length; + final Rect Function(PlotBand plotBand, num start, num end) bounds = + isVertical ? verticalPlotBandBounds : horizontalPlotBandBounds; + for (int i = 0; i < length; i++) { + final PlotBand plotBand = plotBands[i]; + if (plotBand.isVisible) { + final num min = + plotBand.start != null + ? actualValue(plotBand.start) + : visibleRange!.minimum; + num max = + plotBand.end != null + ? actualValue(plotBand.end) + : visibleRange!.maximum; + + num extent; + if (plotBand.isRepeatable) { + extent = plotBand.repeatEvery; + if (plotBand.repeatUntil != null) { + max = actualValue(plotBand.repeatUntil); + if (max > actualRange!.maximum) { + max = actualRange!.maximum; + } + } else { + max = actualRange!.maximum; + } + } else { + extent = max - min; + } + + num current = min; + if (plotBand.isRepeatable) { + while (current < max) { + current = formPlotBandFrame( + plotBand, + current, + extent, + max, + bounds, + ); + } + } else { + formPlotBandFrame(plotBand, current, extent, max, bounds); + } + } + } + } + } + + num formPlotBandFrame( + PlotBand plotBand, + num current, + num extent, + num max, + Rect Function(PlotBand plotBand, num start, num end) bounds, + ) { + num end = plotBandExtent( + plotBand, + current, + plotBand.isRepeatable ? plotBand.size ?? extent : extent, + ); + if (end > max) { + end = max; + } + if (visibleRange!.lies(current, end)) { + final Rect frame = bounds(plotBand, current, end); + addPlotBand(frame, plotBand); + } + current = + plotBand.size != null + ? plotBandExtent( + plotBand, + current, + plotBand.isRepeatable ? plotBand.repeatEvery : end, + ) + : end; + return current; + } + + @nonVirtual + void addPlotBand(Rect frame, PlotBand plotBand) { + final TextStyle textStyle = chartThemeData!.plotBandLabelTextStyle!.merge( + plotBand.textStyle, + ); + final Rect bounds = parent!.paintBounds; + final AxisPlotBand plotBandDetails = AxisPlotBand( + bounds: frame, + opacity: plotBand.opacity, + color: plotBand.color, + gradient: plotBand.gradient, + borderColor: plotBand.borderColor, + borderWidth: plotBand.borderWidth, + dashArray: plotBand.dashArray, + text: plotBand.text ?? '', + textAngle: plotBand.textAngle ?? (isVertical ? 0 : 270), + textStyle: textStyle, + xAlign: plotBand.horizontalTextAlignment, + yAlign: plotBand.verticalTextAlignment, + translateX: paddingFromSize(plotBand.horizontalTextPadding, bounds.width), + translateY: paddingFromSize(plotBand.verticalTextPadding, bounds.height), + shouldRenderAboveSeries: plotBand.shouldRenderAboveSeries, + ); + visiblePlotBands!.add(plotBandDetails); + } + + @protected + num plotBandExtent(PlotBand plotBand, num current, num size) { + return current + size; + } + + Rect horizontalPlotBandBounds(PlotBand plotBand, num start, num end) { + final double left = pointToPixel(start); + late double top; + final double right = pointToPixel(end); + late double bottom; + + if (plotBand.associatedAxisStart != null) { + if (associatedAxis is RenderDateTimeCategoryAxis) { + final dynamic associateStart = plotBand.associatedAxisStart; + top = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + associateStart is num + ? (associatedAxis! as RenderDateTimeCategoryAxis) + .indexToDateTime(associateStart as int) + : associateStart, + ), + ); + } else if (associatedAxis is RenderCategoryAxis) { + final dynamic associateStart = plotBand.associatedAxisStart; + top = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + associateStart is String + ? (associatedAxis! as RenderCategoryAxis).labels.indexOf( + associateStart, + ) + : associateStart, + ), + ); + } else { + top = associatedAxis!.pointToPixel( + associatedAxis!.actualValue(plotBand.associatedAxisStart!), + ); + } + } else { + if (associatedAxis is RenderLogarithmicAxis) { + top = associatedAxis!.pointToPixel( + (associatedAxis! as RenderLogarithmicAxis).toPow( + associatedAxis!.visibleRange!.minimum, + ), + ); + } else { + top = associatedAxis!.pointToPixel( + associatedAxis!.visibleRange!.minimum, + ); + } + } + + if (plotBand.associatedAxisEnd != null) { + if (associatedAxis is RenderDateTimeCategoryAxis) { + final dynamic associateEnd = plotBand.associatedAxisEnd; + bottom = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + associateEnd is num + ? (associatedAxis! as RenderDateTimeCategoryAxis) + .indexToDateTime(associateEnd as int) + : associateEnd, + ), + ); + } else if (associatedAxis is RenderCategoryAxis) { + final dynamic associateEnd = plotBand.associatedAxisEnd; + final num index = + associateEnd is String + ? (associatedAxis! as RenderCategoryAxis).labels.indexOf( + associateEnd, + ) + : associateEnd; + bottom = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + index != -1 ? index : associatedAxis!.actualRange!.maximum, + ), + ); + } else { + bottom = associatedAxis!.pointToPixel( + associatedAxis!.actualValue(plotBand.associatedAxisEnd!), + ); + } + } else { + if (associatedAxis is RenderLogarithmicAxis) { + bottom = associatedAxis!.pointToPixel( + (associatedAxis! as RenderLogarithmicAxis).toPow( + associatedAxis!.visibleRange!.maximum, + ), + ); + } else { + bottom = associatedAxis!.pointToPixel( + associatedAxis!.visibleRange!.maximum, + ); + } + } + + return Rect.fromLTRB(left, top, right, bottom); + } + + Rect verticalPlotBandBounds(PlotBand plotBand, num start, num end) { + late double left; + final double top = pointToPixel(start); + late double right; + final double bottom = pointToPixel(end); + + if (plotBand.associatedAxisStart != null) { + if (associatedAxis is RenderDateTimeCategoryAxis) { + final dynamic associateStart = plotBand.associatedAxisStart; + left = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + associateStart is num + ? (associatedAxis! as RenderDateTimeCategoryAxis) + .indexToDateTime(associateStart as int) + : associateStart, + ), + ); + } else if (associatedAxis is RenderCategoryAxis) { + final dynamic associateStart = plotBand.associatedAxisStart; + left = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + associateStart is String + ? (associatedAxis! as RenderCategoryAxis).labels.indexOf( + associateStart, + ) + : associateStart, + ), + ); + } else { + left = associatedAxis!.pointToPixel( + associatedAxis!.actualValue(plotBand.associatedAxisStart!), + ); + } + } else { + if (associatedAxis is RenderLogarithmicAxis) { + left = associatedAxis!.pointToPixel( + (associatedAxis! as RenderLogarithmicAxis).toPow( + associatedAxis!.visibleRange!.minimum, + ), + ); + } else { + left = associatedAxis!.pointToPixel( + associatedAxis!.visibleRange!.minimum, + ); + } + } + + if (plotBand.associatedAxisEnd != null) { + if (associatedAxis is RenderDateTimeCategoryAxis) { + final dynamic associateEnd = plotBand.associatedAxisEnd; + right = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + associateEnd is num + ? (associatedAxis! as RenderDateTimeCategoryAxis) + .indexToDateTime(associateEnd as int) + : associateEnd, + ), + ); + } else if (associatedAxis is RenderCategoryAxis) { + final dynamic associateEnd = plotBand.associatedAxisEnd; + final num index = + associateEnd is String + ? (associatedAxis! as RenderCategoryAxis).labels.indexOf( + associateEnd, + ) + : associateEnd; + right = associatedAxis!.pointToPixel( + associatedAxis!.actualValue( + index != -1 ? index : associatedAxis!.actualRange!.maximum, + ), + ); + } else { + right = associatedAxis!.pointToPixel( + associatedAxis!.actualValue(plotBand.associatedAxisEnd!), + ); + } + } else { + if (associatedAxis is RenderLogarithmicAxis) { + right = associatedAxis!.pointToPixel( + (associatedAxis! as RenderLogarithmicAxis).toPow( + associatedAxis!.visibleRange!.maximum, + ), + ); + } else { + right = associatedAxis!.pointToPixel( + associatedAxis!.visibleRange!.maximum, + ); + } + } + + return Rect.fromLTRB(left, top, right, bottom); + } + + num actualValue(Object value) { + final num cross = value as num; + final num minimum = actualRange!.minimum; + final num maximum = actualRange!.maximum; + if (cross < minimum) { + return minimum; + } + if (cross > maximum) { + return maximum; + } + return cross; + } + + double pointToPixel(num dataPoint, {DoubleRange? range}) { + if (dataPoint.isNaN) { + return double.nan; + } + + final num coefficient = _valueToCoefficient(dataPoint, range: range); + if (isVertical) { + return (isInversed + ? _effectivePlotOffsetStart + : _effectivePlotOffsetEnd) + + _renderSize.height * (1 - coefficient); + } else { + return (isInversed + ? _effectivePlotOffsetEnd + : _effectivePlotOffsetStart) + + _renderSize.width * coefficient; + } + } + + double pixelToPoint(Rect rect, double x, double y) { + rect = Rect.fromLTWH( + rect.left + (!isVertical ? _effectivePlotOffsetStart : 0), + rect.top + (isVertical ? _effectivePlotOffsetEnd : 0), + rect.width - (!isVertical ? _effectivePlotOffset : 0), + rect.height - (isVertical ? _effectivePlotOffset : 0), + ); + + if (visibleRange != null) { + return isVertical + ? _coefficientToValue(1 - ((y - rect.top) / rect.height)) + : _coefficientToValue((x - rect.left) / rect.width); + } + return double.nan; + } + + double _coefficientToValue(double coefficient) { + return visibleRange!.minimum + + (visibleRange!.delta * (isInversed ? 1 - coefficient : coefficient)); + } + + num _valueToCoefficient(num value, {DoubleRange? range}) { + range ??= visibleRange; + if (range != null) { + final double result = (value - range.minimum) / range.delta; + return isInversed ? 1 - result : result; + } + + return 0; + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + return hasTrimmedAxisLabel || parent!.onAxisLabelTapped != null; + } + + void handleTapUp(TapUpDetails details) { + if (parent != null && + parent!.parentData != null && + parent!.behaviorArea != null && + (parent!.onAxisLabelTapped != null || hasTrimmedAxisLabel)) { + final Offset localPosition = globalToLocal(details.globalPosition); + for (final AxisLabel label in visibleLabels) { + if (label.region != null && label.region!.contains(localPosition)) { + if (parent!.onAxisLabelTapped != null) { + final AxisLabelTapArgs args = + AxisLabelTapArgs(widget, name) + ..text = label.text + ..value = label.value; + parent!.onAxisLabelTapped!(args); + } + if (hasTrimmedAxisLabel && label.trimmedText != null) { + final BoxParentData parentData = + parent!.parentData! as BoxParentData; + final Rect parentBounds = parentData.offset & parent!.size; + parent!.behaviorArea!.showTooltip( + TooltipInfo( + primaryPosition: localToGlobal(label.region!.topCenter), + secondaryPosition: localToGlobal(label.region!.bottomCenter), + text: label.text, + surfaceBounds: Rect.fromPoints( + parent!.localToGlobal(parentBounds.topLeft), + parent!.localToGlobal(parentBounds.bottomRight), + ), + ), + ); + } + break; + } + } + } + } + + void handlePointerHover(PointerHoverEvent details) { + if (hasTrimmedAxisLabel && + parent != null && + parent!.parentData != null && + parent!.behaviorArea != null) { + final Offset localPosition = globalToLocal(details.position); + for (final AxisLabel label in visibleLabels) { + if (label.trimmedText != null && + label.region != null && + label.region!.contains(localPosition)) { + final BoxParentData parentData = parent!.parentData! as BoxParentData; + final Rect parentBounds = parentData.offset & parent!.size; + parent!.behaviorArea!.showTooltip( + TooltipInfo( + primaryPosition: localToGlobal(label.region!.topCenter), + secondaryPosition: localToGlobal(label.region!.bottomCenter), + text: label.text, + surfaceBounds: Rect.fromPoints( + parent!.localToGlobal(parentBounds.topLeft), + parent!.localToGlobal(parentBounds.bottomRight), + ), + ), + ); + break; + } + } + } + } + + @override + @nonVirtual + void paint(PaintingContext context, Offset offset) { + if (!_isVisible) { + renderType = AxisRender.normal; + return; + } + + assert(_renderer != null); + if (_renderer == null) { + renderType = AxisRender.normal; + return; + } + + switch (renderType) { + case AxisRender.gridLines: + _renderer!._paintGridLines(context, offset); + break; + case AxisRender.underPlotBand: + _renderer!._paintUnderPlotBands(context, offset); + break; + case AxisRender.normal: + onPaint(context, offset); + break; + case AxisRender.overPlotBand: + _renderer!._paintOverPlotBands(context, offset); + break; + } + renderType = AxisRender.normal; + } + + @protected + void onPaint(PaintingContext context, Offset offset) { + assert(_renderer != null); + _renderer?._onPaint(context, offset); + } + + @override + void dispose() { + dependents.clear(); + visibleLabels.clear(); + majorTickPositions.clear(); + minorTickPositions.clear(); + borderPositions.clear(); + visibleMultilevelLabels.clear(); + + _animationController?.removeStatusListener(_onAnimationStatusChanged); + if (_animation != null) { + _animation!.removeListener(markNeedsRangeUpdate); + _animation!.dispose(); + } + _animationController?.dispose(); + super.dispose(); + } +} + +class DoubleRangeTween extends Tween { + DoubleRangeTween({super.begin, super.end}); + + @override + DoubleRange? transform(double t) { + return DoubleRange.lerp(begin, end, t); + } +} + +class DoubleRange { + DoubleRange(num min, num max) { + if (max > min) { + minimum = min; + maximum = max; + } else { + minimum = max; + maximum = min; + } + } + + DoubleRange.zero() { + minimum = 0; + maximum = 0; + } + + num delta = 1; + + num get minimum => _minimum; + num _minimum = 0; + set minimum(num value) { + if (_minimum != value) { + _minimum = value; + if (value > _maximum) { + delta = value - _maximum; + } else { + delta = _maximum - value; + } + } + } + + num get maximum => _maximum; + num _maximum = 1; + set maximum(num value) { + if (_maximum != value) { + _maximum = value; + if (value > _minimum) { + delta = value - _minimum; + } else { + delta = _minimum - value; + } + } + } + + bool get isEmpty => _minimum == 0.0 && _maximum == 0.0; + + DoubleRange operator +(DoubleRange other) { + return DoubleRange( + min(_minimum, other.minimum), + max(_maximum, other.maximum), + ); + } + + DoubleRange copyWith({num? minimum, num? maximum}) { + return DoubleRange(minimum ?? _minimum, maximum ?? _maximum); + } + + bool lies(num start, num end) { + return end >= minimum && start <= maximum; + } + + bool contains(num value) { + return value >= minimum && value <= maximum; + } + + static DoubleRange? lerp(DoubleRange? a, DoubleRange? b, double t) { + if (a == null && b == null) { + return null; + } + if (a == null) { + return DoubleRange(b!.minimum, b.maximum); + } + if (b == null) { + return DoubleRange(a.minimum, a.maximum); + } + + return DoubleRange( + lerpDouble(a.minimum, b.minimum, t)!, + lerpDouble(a.maximum, b.maximum, t)!, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is DoubleRange && + other.minimum == minimum && + other.maximum == maximum; + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hash(minimum, maximum); + + @override + String toString() { + return '${objectRuntimeType(this, 'DoubleRange')}($minimum, $maximum)'; + } +} + +mixin AxisDependent { + final DoubleRange xRange = DoubleRange(0, 1); + final DoubleRange yRange = DoubleRange(0, 1); + + bool includeRange = true; + + num get xMin => xRange.minimum; + set xMin(num value) { + if (xRange.minimum != value) { + xRange.minimum = value; + xAxis?._needsRangeUpdate = true; + } + } + + num get xMax => xRange.maximum; + set xMax(num value) { + if (xRange.maximum != value) { + xRange.maximum = value; + xAxis?._needsRangeUpdate = true; + } + } + + num get yMin => yRange.minimum; + set yMin(num value) { + if (yRange.minimum != value) { + yRange.minimum = value; + yAxis?._needsRangeUpdate = true; + } + } + + num get yMax => yRange.maximum; + set yMax(num value) { + if (yRange.maximum != value) { + yRange.maximum = value; + yAxis?._needsRangeUpdate = true; + } + } + + String? get xAxisName => _xAxisName; + String? _xAxisName; + set xAxisName(String? value) { + if (_xAxisName != value) { + _xAxisName = value; + } + } + + String? get yAxisName => _yAxisName; + String? _yAxisName; + set yAxisName(String? value) { + if (_yAxisName != value) { + _yAxisName = value; + } + } + + RenderChartAxis? get xAxis => _xAxis; + RenderChartAxis? _xAxis; + set xAxis(RenderChartAxis? value) { + assert(value != null); + if (_xAxis != value) { + _xAxis?.removeDependent(this); + _xAxis = value; + _xAxis?.addDependent(this); + } + } + + RenderChartAxis? get yAxis => _yAxis; + RenderChartAxis? _yAxis; + set yAxis(RenderChartAxis? value) { + assert(value != null); + if (_yAxis != value) { + _yAxis?.removeDependent(this); + _yAxis = value; + _yAxis?.addDependent(this, isXAxis: false); + } + } + + bool get isTransposed => _isTransposed; + bool _isTransposed = false; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + } + } + + DoubleRange range(RenderChartAxis axis) { + if (axis == xAxis) { + return xRange; + } else { + return yRange; + } + } + + void didRangeChange(RenderChartAxis axis); +} + +abstract class _GridLineRenderer { + _GridLineRenderer(this.axis); + + final RenderChartAxis axis; + + @nonVirtual + void onPaint(PaintingContext context, Offset offset) { + final Offset plotAreaGlobalOffset = axis.parent!.localToGlobal( + axis.parent!.plotAreaOffset, + ); + final Offset plotAreaOffset = axis.globalToLocal(plotAreaGlobalOffset); + context.canvas.save(); + context.canvas.clipRect(axis.parent!.plotAreaBounds); + _drawMajorGridLines(context, plotAreaOffset + offset); + _drawMinorGridLines(context, plotAreaOffset + offset); + context.canvas.restore(); + } + + @protected + void _drawMajorGridLines(PaintingContext context, Offset offset); + + @protected + void _drawMinorGridLines(PaintingContext context, Offset offset); +} + +class _HorizontalGridLineRenderer extends _GridLineRenderer { + _HorizontalGridLineRenderer(RenderChartAxis axis) : super(axis); + + @override + void _drawMajorGridLines(PaintingContext context, Offset offset) { + final MajorGridLines majorGridLines = axis.majorGridLines; + final Color color = + (majorGridLines.color ?? axis.chartThemeData!.majorGridLineColor)!; + _drawGridLines( + context, + offset, + axis.majorTickPositions, + color, + majorGridLines.width, + majorGridLines.dashArray, + ); + } + + @override + void _drawMinorGridLines(PaintingContext context, Offset offset) { + final MinorGridLines minorGridLines = axis.minorGridLines; + final Color color = + (minorGridLines.color ?? axis.chartThemeData!.minorGridLineColor)!; + _drawGridLines( + context, + offset, + axis.minorTickPositions, + color, + minorGridLines.width, + minorGridLines.dashArray, + ); + } + + void _drawGridLines( + PaintingContext context, + Offset offset, + List positions, + Color color, + double width, + List? dashArray, + ) { + if (axis.associatedAxis != null && + color != Colors.transparent && + width > 0) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = color + ..strokeWidth = width + ..style = PaintingStyle.stroke; + + final RenderChartAxis associatedAxis = axis.associatedAxis!; + num minimum = associatedAxis.visibleRange!.minimum; + num maximum = associatedAxis.visibleRange!.maximum; + + if (associatedAxis is RenderLogarithmicAxis) { + minimum = associatedAxis.toPow(minimum); + maximum = associatedAxis.toPow(maximum); + } + + final double plotOffsetStart = associatedAxis._effectivePlotOffsetStart; + final double plotOffsetEnd = associatedAxis._effectivePlotOffsetEnd; + double y1 = associatedAxis.pointToPixel(minimum); + double y2 = associatedAxis.pointToPixel(maximum); + if (associatedAxis.isInversed) { + y1 = y1 - plotOffsetStart; + y2 = y2 + plotOffsetEnd; + } else { + y1 = y1 + plotOffsetStart; + y2 = y2 - plotOffsetEnd; + } + for (final double position in positions) { + final Offset start = offset.translate(position, y1); + final Offset end = Offset(start.dx, offset.dy + y2); + drawDashes(context.canvas, dashArray, paint, start: start, end: end); + } + } + } +} + +class _VerticalGridLineRenderer extends _GridLineRenderer { + _VerticalGridLineRenderer(RenderChartAxis axis) : super(axis); + + @override + void _drawMajorGridLines(PaintingContext context, Offset offset) { + final MajorGridLines majorGridLines = axis.majorGridLines; + final Color color = + (majorGridLines.color ?? axis.chartThemeData!.majorGridLineColor)!; + _drawGridLines( + context, + offset, + axis.majorTickPositions, + color, + majorGridLines.width, + majorGridLines.dashArray, + ); + } + + @override + void _drawMinorGridLines(PaintingContext context, Offset offset) { + final MinorGridLines minorGridLines = axis.minorGridLines; + final Color color = + (minorGridLines.color ?? axis.chartThemeData!.minorGridLineColor)!; + _drawGridLines( + context, + offset, + axis.minorTickPositions, + color, + minorGridLines.width, + minorGridLines.dashArray, + ); + } + + void _drawGridLines( + PaintingContext context, + Offset offset, + List positions, + Color color, + double width, + List? dashArray, + ) { + if (axis.associatedAxis != null && + color != Colors.transparent && + width > 0) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = color + ..strokeWidth = width + ..style = PaintingStyle.stroke; + + final RenderChartAxis associatedAxis = axis.associatedAxis!; + num minimum = associatedAxis.visibleRange!.minimum; + num maximum = associatedAxis.visibleRange!.maximum; + + if (associatedAxis is RenderLogarithmicAxis) { + minimum = associatedAxis.toPow(minimum); + maximum = associatedAxis.toPow(maximum); + } + + final double plotOffsetStart = associatedAxis._effectivePlotOffsetStart; + final double plotOffsetEnd = associatedAxis._effectivePlotOffsetEnd; + double x1 = associatedAxis.pointToPixel(minimum); + double x2 = associatedAxis.pointToPixel(maximum); + if (associatedAxis.isInversed) { + x1 = x1 + plotOffsetStart; + x2 = x2 - plotOffsetEnd; + } else { + x1 = x1 - plotOffsetStart; + x2 = x2 + plotOffsetEnd; + } + for (final double position in positions) { + final Offset start = offset.translate(x1, position); + final Offset end = Offset(offset.dx + x2, start.dy); + drawDashes(context.canvas, dashArray, paint, start: start, end: end); + } + } + } +} + +class AxisPlotBand { + AxisPlotBand({ + required this.bounds, + required this.opacity, + required this.color, + required this.gradient, + required this.borderColor, + required this.borderWidth, + required this.dashArray, + required this.text, + required this.textAngle, + required this.textStyle, + required this.xAlign, + required this.yAlign, + required this.translateX, + required this.translateY, + required this.shouldRenderAboveSeries, + }); + + final Rect bounds; + final double opacity; + final Color color; + final Gradient? gradient; + final Color borderColor; + final double borderWidth; + final List? dashArray; + final String text; + final int textAngle; + final TextStyle textStyle; + final TextAnchor xAlign; + final TextAnchor yAlign; + final double translateX; + final double translateY; + final bool shouldRenderAboveSeries; +} + +abstract class _PlotBandRenderer { + _PlotBandRenderer(this.axis); + + final RenderChartAxis axis; + final TextPainter _textPainter = TextPainter(); + + @nonVirtual + void _paintPlotBandsOnSeries(PaintingContext context, Offset offset) { + _drawPlotBand(context, offset, shouldRenderAboveSeries: true); + } + + @nonVirtual + void _paintPlotBandsUnderSeries(PaintingContext context, Offset offset) { + _drawPlotBand(context, offset); + } + + void _drawPlotBand( + PaintingContext context, + Offset offset, { + bool shouldRenderAboveSeries = false, + }) { + if (axis.visiblePlotBands == null || axis.visiblePlotBands!.isEmpty) { + return; + } + + final Offset plotAreaGlobalOffset = axis.parent!.localToGlobal( + axis.parent!.plotAreaOffset, + ); + final Offset plotAreaOffset = axis.globalToLocal(plotAreaGlobalOffset); + offset += plotAreaOffset; + context.canvas.save(); + context.canvas.clipRect(axis.parent!.plotAreaBounds); + for (final AxisPlotBand plotBand in axis.visiblePlotBands!) { + if (shouldRenderAboveSeries == plotBand.shouldRenderAboveSeries) { + final Rect bounds = plotBand.bounds.translate(offset.dx, offset.dy); + final Paint paint = Paint(); + if (plotBand.gradient != null) { + paint.shader = plotBand.gradient!.createShader(bounds); + } else { + if (plotBand.opacity < 1.0) { + paint.color = plotBand.color.withValues(alpha: plotBand.opacity); + } else { + paint.color = plotBand.color; + } + } + + if (paint.color != Colors.transparent && bounds.width != 0.0) { + context.canvas.drawRect(bounds, paint); + } + + if (plotBand.borderWidth > 0 && + plotBand.borderColor != Colors.transparent) { + paint + ..color = + plotBand.opacity < 1.0 + ? plotBand.borderColor.withValues(alpha: plotBand.opacity) + : plotBand.borderColor + ..strokeWidth = plotBand.borderWidth + ..style = PaintingStyle.stroke; + final Path path = + Path() + ..moveTo(bounds.left, bounds.top) + ..lineTo(bounds.left + bounds.width, bounds.top) + ..lineTo(bounds.left + bounds.width, bounds.top + bounds.height) + ..lineTo(bounds.left, bounds.top + bounds.height) + ..close(); + drawDashes(context.canvas, plotBand.dashArray, paint, path: path); + } + _drawText(context, bounds, plotBand); + } + } + context.canvas.restore(); + } + + @protected + void _drawText(PaintingContext context, Rect bounds, AxisPlotBand plotBand); + + Offset _textPosition(AxisPlotBand plotBand, Rect bounds, Size textSize) { + late double x, y; + switch (plotBand.xAlign) { + case TextAnchor.start: + x = bounds.left + plotBand.translateX; + break; + case TextAnchor.middle: + x = bounds.center.dx - textSize.width / 2 + plotBand.translateX; + break; + case TextAnchor.end: + x = bounds.right - textSize.width + plotBand.translateX; + break; + } + + if (bounds.top > bounds.bottom) { + // Top value is always greater than the bottom value. So vertical + // alignment is working oppositely, hence swapped the value. + bounds = Rect.fromLTRB( + bounds.left, + bounds.bottom, + bounds.right, + bounds.top, + ); + } + + switch (plotBand.yAlign) { + case TextAnchor.start: + y = bounds.top - plotBand.translateY; + break; + case TextAnchor.middle: + y = bounds.center.dy - textSize.height / 2 - plotBand.translateY; + break; + case TextAnchor.end: + y = bounds.bottom - textSize.height - plotBand.translateY; + break; + } + + return Offset(x, y); + } +} + +class _HorizontalPlotBandRenderer extends _PlotBandRenderer { + _HorizontalPlotBandRenderer(RenderChartAxis axis) : super(axis); + + @override + void _drawText(PaintingContext context, Rect bounds, AxisPlotBand plotBand) { + if (plotBand.text.isNotEmpty) { + TextStyle style = plotBand.textStyle; + if (plotBand.opacity < 1.0) { + style = style.copyWith( + color: style.color?.withValues(alpha: plotBand.opacity), + ); + } + final TextSpan span = TextSpan(text: plotBand.text, style: style); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr + ..layout(); + final Offset position = _textPosition( + plotBand, + bounds, + _textPainter.size, + ); + if (plotBand.textAngle == 0) { + _textPainter.paint(context.canvas, position); + } else { + final double halfWidth = _textPainter.width / 2; + final double halfHeight = _textPainter.height / 2; + context.canvas + ..save() + ..translate(position.dx + halfWidth, position.dy + halfHeight) + ..rotate(degreeToRadian(plotBand.textAngle)); + _textPainter.paint(context.canvas, Offset(-halfWidth, -halfHeight)); + context.canvas.restore(); + } + } + } +} + +class _VerticalPlotBandRenderer extends _PlotBandRenderer { + _VerticalPlotBandRenderer(RenderChartAxis axis) : super(axis); + + @override + void _drawText(PaintingContext context, Rect bounds, AxisPlotBand plotBand) { + if (plotBand.text.isNotEmpty) { + TextStyle style = plotBand.textStyle; + if (plotBand.opacity < 1.0) { + style = style.copyWith( + color: style.color?.withValues(alpha: plotBand.opacity), + ); + } + final TextSpan span = TextSpan(text: plotBand.text, style: style); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr + ..layout(); + final Offset position = _textPosition( + plotBand, + bounds, + _textPainter.size, + ); + if (plotBand.textAngle == 0) { + _textPainter.paint(context.canvas, position); + } else { + final double halfWidth = _textPainter.width / 2; + final double halfHeight = _textPainter.height / 2; + context.canvas + ..save() + ..translate(position.dx + halfWidth, position.dy + halfHeight) + ..rotate(degreeToRadian(plotBand.textAngle)); + _textPainter.paint(context.canvas, Offset(-halfWidth, -halfHeight)); + context.canvas.restore(); + } + } + } +} + +abstract class _AxisRenderer { + _AxisRenderer({required this.axis}); + + final RenderChartAxis axis; + late _GridLineRenderer _gridLineRenderer; + _PlotBandRenderer? _plotBandRenderer; + + final TextPainter _textPainter = TextPainter(); + final Map _multilevelLabelSizes = {}; + + late Offset _axisLineOffset; + late Offset _majorTickOffset; + late Offset _minorTickOffset; + late Offset _labelOffset; + late Offset _borderOffset; + late Offset _multilevelLabelOffset; + late Offset _titleOffset; + late _MultilevelLabelBorderShape _multilevelLabelBorderShape; + + double _maxLabelSize = 0.0; + double _axisBorderSize = 0.0; + + double get innerSize => _innerSize; + double _innerSize = 0.0; + + double get outerSize => _outerSize; + double _outerSize = 0.0; + + Size _preferredSize(Size availableSize) { + _axisBorderSize = 0.0; + _maxLabelSize = 0.0; + _innerSize = 0.0; + _outerSize = 0.0; + + double majorTickSize = 0.0; + double minorTickSize = 0.0; + double minorTickAdj = 0.0; + double majorTickAdj = 0.0; + double maxTickSize = 0.0; + double maxLabelSize = 0.0; + double multilevelLabelSize = 0.0; + double effectiveMultilevelBorderSize = 0.0; + double borderSize = 0.0; + double effectiveBorderSize = 0.0; + double titleSize = 0.0; + + final MajorTickLines majorTickLines = axis.majorTickLines; + if (majorTickLines.width > 0 && majorTickLines.size > 0) { + majorTickSize = majorTickLines.size; + } + + final MinorTickLines minorTickLines = axis.minorTickLines; + if (minorTickLines.width > 0 && minorTickLines.size > 0) { + minorTickSize = minorTickLines.size; + if (majorTickSize >= minorTickSize) { + minorTickAdj = majorTickSize - minorTickSize; + } else { + majorTickAdj = minorTickSize - majorTickSize; + } + } + + maxTickSize = max(majorTickSize, minorTickSize); + maxLabelSize = _labelsMaxSize(); + _maxLabelSize = maxLabelSize; + multilevelLabelSize = _multilevelLabelsTotalSize(); + borderSize = axis.borderWidth / 2; + titleSize = 0.0; + if (axis.title.text != null && axis.title.text!.isNotEmpty) { + final TextStyle textStyle = axis.chartThemeData!.axisTitleTextStyle! + .merge(axis.title.textStyle); + titleSize = measureText(axis.title.text!, textStyle).height; + } + + double innerTickSize = 0.0; + double innerMajorTickSize = 0.0; + double innerMinorTickSize = 0.0; + double outerTickSize = 0.0; + double innerLabelSize = 0.0; + double outerLabelSize = 0.0; + double innerMultilevelLabelSize = 0.0; + double outerMultilevelLabelSize = 0.0; + + if (axis.tickPosition == TickPosition.outside) { + outerTickSize = maxTickSize; + } else { + innerTickSize = maxTickSize; + innerMajorTickSize = majorTickSize; + innerMinorTickSize = minorTickSize; + } + + if (axis.labelPosition == ChartDataLabelPosition.outside) { + outerLabelSize = maxLabelSize; + outerMultilevelLabelSize = multilevelLabelSize; + } else { + innerLabelSize = maxLabelSize; + innerMultilevelLabelSize = multilevelLabelSize; + } + + double outerLabelGap = axis.isVertical ? 5.0 : 3.0; + double innerLabelGap = outerLabelGap; + double outerBorderGap = 3.0; + double innerBorderGap = outerBorderGap; + double outerMultilevelLabelGap = 3.0; + double innerMultilevelLabelGap = 3.0; + double titleGap = 5.0; + + if (maxLabelSize <= 0) { + outerLabelGap = 0.0; + outerLabelGap = 0.0; + } + + final MultiLevelBorderType borderType = + axis.multiLevelLabelStyle.borderType; + if (multilevelLabelSize > 0) { + if (borderType == MultiLevelBorderType.rectangle || + borderType == MultiLevelBorderType.withoutTopAndBottom) { + effectiveMultilevelBorderSize = + axis.multiLevelLabelStyle.borderWidth / 2; + if (borderSize > 0) { + outerMultilevelLabelGap = 0.0; + innerMultilevelLabelGap = 0.0; + } + } + } else { + outerMultilevelLabelGap = 0.0; + innerMultilevelLabelGap = 0.0; + } + + if (borderSize <= 0) { + outerBorderGap = 0.0; + innerBorderGap = 0.0; + } else { + if (effectiveMultilevelBorderSize == 0.0) { + effectiveBorderSize = borderSize; + } + } + + if (axis.labelPosition == ChartDataLabelPosition.outside) { + innerLabelGap = 3.0; + innerBorderGap = 0.0; + innerMultilevelLabelGap = 0.0; + } else { + outerLabelGap = 0.0; + outerBorderGap = 0.0; + outerMultilevelLabelGap = 0.0; + } + if (titleSize <= 0) { + titleGap = 0.0; + } + + double axisLinePadding = 0.0; + double majorTickPadding = 0.0; + double minorTickPadding = 0.0; + double labelPadding = 0.0; + double borderPadding = 0.0; + double multilevelLabelPadding = 0.0; + double titlePadding = 0.0; + + // The `labelsExtent` defines the space between the axis line and the title, + // if its value exceeds the maximum size of the label. + final double labelsExtent = axis.labelsExtent ?? 0.0; + double labelsExtentPadding = 0.0; + if (maxLabelSize < labelsExtent) { + labelsExtentPadding = labelsExtent - maxLabelSize; + } + + double outerSize = 0.0; + + // Normal order + // Axis line -> Tick -> Label -> Border -> Multilevel label -> Title + // ------------------------------------------------------------------ + // Inverted order + // Title -> Multilevel label -> Border -> Label -> Tick -> Axis line + final bool normalOrder = !axis.invertElementsOrder; + if (normalOrder) { + axisLinePadding = 0.0; + majorTickPadding = 0.0; + minorTickPadding = 0.0; + labelPadding = outerTickSize + outerLabelGap; + multilevelLabelPadding = + labelPadding + + outerLabelSize + + outerBorderGap + + effectiveBorderSize + + outerMultilevelLabelGap + + effectiveMultilevelBorderSize; + titlePadding = + multilevelLabelPadding + + outerMultilevelLabelSize + + effectiveMultilevelBorderSize + + labelsExtentPadding + + titleGap; + _axisBorderSize = multilevelLabelPadding - outerMultilevelLabelGap; + outerSize = titlePadding + titleSize; + } else { + titlePadding = 0.0; + multilevelLabelPadding = + titleSize + + titleGap + + labelsExtentPadding + + effectiveMultilevelBorderSize; + borderPadding = + multilevelLabelPadding + + outerMultilevelLabelSize + + outerMultilevelLabelGap + + effectiveBorderSize; + labelPadding = borderPadding + outerBorderGap; + minorTickPadding = + labelPadding + outerLabelSize + outerLabelGap + minorTickAdj; + majorTickPadding = + labelPadding + outerLabelSize + outerLabelGap + majorTickAdj; + axisLinePadding = + labelPadding + outerLabelSize + outerLabelGap + outerTickSize; + _axisBorderSize = + effectiveBorderSize + + effectiveMultilevelBorderSize + + outerBorderGap + + outerLabelSize + + outerLabelGap + + outerTickSize; + outerSize = axisLinePadding; + } + + if (axis.tickPosition == TickPosition.inside) { + if (normalOrder) { + majorTickPadding = -innerMajorTickSize; + minorTickPadding = -innerMinorTickSize; + } else { + majorTickPadding = outerSize; + minorTickPadding = outerSize; + } + } + + if (axis.labelPosition == ChartDataLabelPosition.inside) { + if (normalOrder) { + labelPadding = innerTickSize + innerLabelGap + innerLabelSize; + borderPadding = labelPadding + innerBorderGap; + multilevelLabelPadding = + borderPadding + innerMultilevelLabelGap + innerMultilevelLabelSize; + _axisBorderSize = + innerTickSize + innerLabelGap + innerLabelSize + innerBorderGap; + labelPadding *= -1; + borderPadding *= -1; + multilevelLabelPadding *= -1; + } else { + labelPadding = outerSize + innerTickSize + innerLabelGap; + borderPadding = outerSize; + multilevelLabelPadding = + labelPadding + + innerLabelSize + + innerBorderGap + + innerMultilevelLabelGap; + _axisBorderSize = + innerTickSize + innerLabelGap + innerLabelSize + innerBorderGap; + } + } + + if (maxLabelSize <= 0) { + _axisBorderSize = 0.0; + } + + if (axis.isVertical) { + _axisLineOffset = Offset(axisLinePadding, 0.0); + _majorTickOffset = Offset(majorTickPadding, 0.0); + _minorTickOffset = Offset(minorTickPadding, 0.0); + _labelOffset = Offset(labelPadding, 0.0); + _borderOffset = Offset(borderPadding, 0.0); + _multilevelLabelOffset = Offset(multilevelLabelPadding, 0.0); + _titleOffset = Offset(titlePadding, 0.0); + return Size(outerSize, availableSize.height); + } else { + _axisLineOffset = Offset(0.0, axisLinePadding); + _majorTickOffset = Offset(0.0, majorTickPadding); + _minorTickOffset = Offset(0.0, minorTickPadding); + _labelOffset = Offset(0.0, labelPadding); + _borderOffset = Offset(0.0, borderPadding); + _multilevelLabelOffset = Offset(0.0, multilevelLabelPadding); + _titleOffset = Offset(0.0, titlePadding); + return Size(availableSize.width, outerSize); + } + } + + double _labelsMaxSize(); + + double _multilevelLabelsTotalSize(); + + @nonVirtual + void _paintGridLines(PaintingContext context, Offset offset) { + _gridLineRenderer.onPaint(context, offset); + } + + @nonVirtual + void _paintOverPlotBands(PaintingContext context, Offset offset) { + _plotBandRenderer?._paintPlotBandsOnSeries(context, offset); + } + + @nonVirtual + void _paintUnderPlotBands(PaintingContext context, Offset offset) { + _plotBandRenderer?._paintPlotBandsUnderSeries(context, offset); + } + + @nonVirtual + void _onPaint(PaintingContext context, Offset offset) { + _drawAxisLine(context, offset + _axisLineOffset); + _drawTicks(context, offset); + _drawLabels(context, offset, _labelOffset); + _drawBorders(context, offset + _borderOffset); + _drawMultilevelLabels(context, offset + _multilevelLabelOffset); + _drawTitle(context, offset + _titleOffset); + } + + @protected + void _drawAxisLine(PaintingContext context, Offset offset); + + @protected + @nonVirtual + void _drawTicks(PaintingContext context, Offset offset) { + if (axis.parentData == null) { + return; + } + final Rect clipRect = + axis.tickPosition == TickPosition.inside + ? axis.parent!.plotAreaBounds + : (axis.parentData! as CartesianAxesParentData).offset & axis.size; + final Rect extendedClipRect = clipRect.inflate( + max(axis.majorTickLines.size, axis.minorTickLines.size) / 2, + ); + // HACK: If the ouput renders in html web renderer, y axis labels, ticks + // and axis line hide. To avoid this y axis hide, we called canvas save + // two times. + context.canvas.save(); + context.canvas.save(); + context.canvas.clipRect(extendedClipRect); + _drawMajorTicks(context, offset + _majorTickOffset); + _drawMinorTicks(context, offset + _minorTickOffset); + // HACK: If the ouput renders in html web renderer, y axis labels, ticks + // and axis line hide. To avoid this y axis hide, we called canvas restore + // two times. + context.canvas.restore(); + context.canvas.restore(); + } + + @protected + void _drawMajorTicks(PaintingContext context, Offset offset); + + @protected + void _drawMinorTicks(PaintingContext context, Offset offset); + + @protected + void _drawLabels(PaintingContext context, Offset offset, Offset labelOffset); + + @protected + void _drawBorders(PaintingContext context, Offset offset); + + @protected + void _drawMultilevelLabels(PaintingContext context, Offset offset); + + @protected + void _drawTitle(PaintingContext context, Offset offset); +} + +class _HorizontalAxisRenderer extends _AxisRenderer { + _HorizontalAxisRenderer(RenderChartAxis axis) : super(axis: axis) { + _gridLineRenderer = _HorizontalGridLineRenderer(axis); + _plotBandRenderer = _HorizontalPlotBandRenderer(axis); + _multilevelLabelBorderShape = _HorizontalMultilevelLabelBorderShape(); + } + + @override + double _labelsMaxSize() { + if (axis.effectiveLabelIntersectAction == + AxisLabelIntersectAction.multipleRows) { + return _multipleRowsLabelsMaxSize(); + } + + double maxHeight = 0.0; + final int length = axis.visibleLabels.length; + for (int i = 0; i < length; i++) { + final AxisLabel label = axis.visibleLabels[i]; + maxHeight = max(maxHeight, label.labelSize.height); + } + + return maxHeight; + } + + double _multipleRowsLabelsMaxSize() { + final List visibleLabels = axis.visibleLabels; + // Calculates the max height for each row based on the _rowIndex. + final Map rowMaxHeights = {}; + for (final AxisLabel current in visibleLabels) { + final double labelHeight = current.labelSize.height; + final int rowIndex = current._rowIndex; + if (rowMaxHeights.containsKey(rowIndex)) { + rowMaxHeights[rowIndex] = max(rowMaxHeights[rowIndex]!, labelHeight); + } else { + rowMaxHeights[rowIndex] = labelHeight; + } + } + + // Add 3 spacing between rows when rendering labels. + if (rowMaxHeights.length > 1) { + for (final int rowIndex in rowMaxHeights.keys) { + rowMaxHeights[rowIndex] = rowMaxHeights[rowIndex]! + 3; + } + } + + // Set current label _rowSize using cumulative height of previous rows. + for (final AxisLabel current in visibleLabels) { + final int rowIndex = current._rowIndex; + if (rowIndex > 0) { + double cumulativeHeight = 0.0; + for (int i = 0; i < rowIndex; i++) { + if (rowMaxHeights.containsKey(i)) { + cumulativeHeight += rowMaxHeights[i]!; + } + } + current._prevRowSize = Size(current.labelSize.width, cumulativeHeight); + } + } + + // Calculate the overall max height by summing the max heights of all rows. + return rowMaxHeights.values.fold(0.0, (sum, height) => sum + height); + } + + @override + double _multilevelLabelsTotalSize() { + _multilevelLabelSizes.clear(); + double maxHeight = double.negativeInfinity; + for (final AxisMultilevelLabel label in axis.visibleMultilevelLabels) { + double height = label.actualTextSize.height; + if (axis.multiLevelLabelStyle.borderType == + MultiLevelBorderType.curlyBrace) { + height += 2 * textPaddingOfCurlyBrace; + } else { + height += 2 * textPadding; + } + + _multilevelLabelSizes.update( + label.level, + (double value) => maxHeight = max(maxHeight, height), + ifAbsent: () { + maxHeight = double.negativeInfinity; + return maxHeight = max(maxHeight, height); + }, + ); + } + + return _multilevelLabelSizes.isEmpty + ? 0 + : _multilevelLabelSizes.values.reduce((double a, double b) => a + b); + } + + @override + void _drawAxisLine(PaintingContext context, Offset offset) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = (axis.axisLine.color ?? axis.chartThemeData!.axisLineColor)! + ..strokeWidth = axis.axisLine.width + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes( + context.canvas, + axis.axisLine.dashArray, + paint, + start: offset, + end: Offset(offset.dx + axis.size.width, offset.dy), + ); + } + } + + @override + void _drawMajorTicks(PaintingContext context, Offset offset) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = + (axis.majorTickLines.color ?? + axis.chartThemeData!.majorTickLineColor)! + ..strokeWidth = axis.majorTickLines.width + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + for (final double position in axis.majorTickPositions) { + final Offset start = offset.translate(position, 0.0); + final Offset end = start.translate(0.0, axis.majorTickLines.size); + context.canvas.drawLine(start, end, paint); + } + } + } + + @override + void _drawMinorTicks(PaintingContext context, Offset offset) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = + (axis.minorTickLines.color ?? + axis.chartThemeData!.minorTickLineColor)! + ..strokeWidth = axis.minorTickLines.width + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + for (final double position in axis.minorTickPositions) { + final Offset start = offset.translate(position, 0.0); + final Offset end = start.translate(0.0, axis.minorTickLines.size); + context.canvas.drawLine(start, end, paint); + } + } + } + + @override + void _drawLabels(PaintingContext context, Offset offset, Offset labelOffset) { + if (axis.visibleLabels.isEmpty) { + return; + } + + final AxisLabelIntersectAction action = axis.effectiveLabelIntersectAction; + int rotationAngle = axis.labelRotation; + if (axis._hasCollidingLabels) { + if (action == AxisLabelIntersectAction.rotate90) { + rotationAngle = angle90Degree; + } else if (action == AxisLabelIntersectAction.rotate45) { + rotationAngle = angle45Degree; + } + } + + final bool isMultipleRows = action == AxisLabelIntersectAction.multipleRows; + final bool isOutSide = axis.labelPosition == ChartDataLabelPosition.outside; + final bool normalOrder = + (!axis.invertElementsOrder && isOutSide) || + (axis.invertElementsOrder && !isOutSide); + final List axisLabels = axis.visibleLabels; + for (final AxisLabel label in axisLabels) { + if (!label.isVisible || label.position == null || label.position!.isNaN) { + continue; + } + + double dy = labelOffset.dy; + if (!normalOrder) { + dy += _maxLabelSize - label.labelSize.height; + if (isMultipleRows) { + dy -= label._prevRowSize.height; + } + } else { + if (isMultipleRows) { + dy += label._prevRowSize.height; + } + } + + Offset position = Offset(labelOffset.dx + label.position!, dy); + label.region = Rect.fromLTWH( + position.dx, + position.dy, + label.labelSize.width, + label.labelSize.height, + ); + position += offset; + _drawLabel(context, position, label, rotationAngle); + } + } + + void _drawLabel( + PaintingContext context, + Offset position, + AxisLabel label, + int rotationAngle, + ) { + final TextSpan span = TextSpan( + text: label.renderText, + style: label.labelStyle, + ); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr; + _textPainter.layout(); + if (rotationAngle == 0) { + _textPainter.paint(context.canvas, position); + } else { + context.canvas + ..save() + ..translate( + position.dx + label.labelSize.width / 2, + position.dy + label.labelSize.height / 2, + ) + ..rotate(degreeToRadian(rotationAngle)); + _textPainter.paint( + context.canvas, + Offset(-_textPainter.size.width / 2, -_textPainter.size.height / 2), + ); + context.canvas.restore(); + } + } + + @override + void _drawBorders(PaintingContext context, Offset offset) { + if (_axisBorderSize <= 0) { + return; + } + + final Color effectiveBorderColor = + (axis.borderColor ?? axis.chartThemeData!.axisLineColor)!; + if (effectiveBorderColor != Colors.transparent && + axis.borderWidth > 0 && + axis.borderPositions.isNotEmpty) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = effectiveBorderColor + ..strokeWidth = axis.borderWidth + ..style = PaintingStyle.stroke; + final double axisLeft = axis.paintBounds.left; + final double axisRight = axis.paintBounds.right; + final List positions = axis.borderPositions; + for (final double position in positions) { + if (axisLeft <= position && axisRight >= position) { + final Offset start = offset.translate(position, 0.0); + final Offset end = start.translate(0.0, _axisBorderSize); + context.canvas.drawLine(start, end, paint); + } + } + + if (axis.axisBorderType == AxisBorderType.rectangle) { + Offset start = offset; + Offset end = start.translate(axis.size.width, 0.0); + context.canvas.drawLine(start, end, paint); + + start = start.translate(0.0, _axisBorderSize); + end = end.translate(0.0, _axisBorderSize); + context.canvas.drawLine(start, end, paint); + } + } + } + + @override + void _drawMultilevelLabels(PaintingContext context, Offset offset) { + final List labels = axis.visibleMultilevelLabels; + final int length = labels.length; + if (length > 0) { + final MultiLevelBorderType borderType = + axis.multiLevelLabelStyle.borderType; + final Paint paint = + Paint() + ..color = + (axis.multiLevelLabelStyle.borderColor ?? + axis.chartThemeData!.axisLineColor)! + ..strokeWidth = + axis.multiLevelLabelStyle.borderWidth != 0 + ? axis.multiLevelLabelStyle.borderWidth + : axis.axisLine.width + ..style = PaintingStyle.stroke; + + double top = offset.dy; + int level = _multilevelLabelSizes.keys.first; + double height = _multilevelLabelSizes[level]!; + for (final AxisMultilevelLabel label in labels) { + if (level != label.level) { + top += height; + level = label.level; + height = _multilevelLabelSizes[level]!; + } + + final TextSpan span = TextSpan( + text: label.trimmedText, + style: label.style.copyWith( + color: label.style.color ?? axis.chartThemeData!.axisLabelColor, + ), + ); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr + ..layout(); + final Rect bounds = Rect.fromLTWH( + offset.dx + label.region.left, + top + label.region.top, + label.region.width, + height, + ); + _multilevelLabelBorderShape.onPaint( + context, + borderType, + bounds, + _textPainter, + paint, + axis, + ); + } + } + } + + @override + void _drawTitle(PaintingContext context, Offset offset) { + final TextStyle textStyle = axis.chartThemeData!.axisTitleTextStyle!.merge( + axis.title.textStyle, + ); + final TextSpan span = TextSpan(text: axis.title.text, style: textStyle); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr; + _textPainter.layout(); + + late double x; + switch (axis.title.alignment) { + case ChartAlignment.near: + x = offset.dx; + break; + case ChartAlignment.center: + x = offset.dx + axis.size.width / 2 - _textPainter.size.width / 2; + break; + case ChartAlignment.far: + x = offset.dx + axis.size.width - _textPainter.size.width; + break; + } + + _textPainter.paint(context.canvas, Offset(x, offset.dy)); + } +} + +class _VerticalAxisRenderer extends _AxisRenderer { + _VerticalAxisRenderer(RenderChartAxis axis) : super(axis: axis) { + _gridLineRenderer = _VerticalGridLineRenderer(axis); + _plotBandRenderer = _VerticalPlotBandRenderer(axis); + _multilevelLabelBorderShape = _VerticalMultilevelLabelBorderShape(); + } + + @override + double _labelsMaxSize() { + double maxTextWidth = 0.0; + final int length = axis.visibleLabels.length; + for (int i = 0; i < length; i++) { + final AxisLabel label = axis.visibleLabels[i]; + if (label.renderText.isEmpty) { + continue; + } + + maxTextWidth = max(maxTextWidth, label.labelSize.width); + } + + return maxTextWidth; + } + + @override + double _multilevelLabelsTotalSize() { + _multilevelLabelSizes.clear(); + double maxSize = double.negativeInfinity; + for (final AxisMultilevelLabel label in axis.visibleMultilevelLabels) { + double width = label.actualTextSize.width; + if (axis.multiLevelLabelStyle.borderType == + MultiLevelBorderType.curlyBrace) { + width += 2 * textPaddingOfCurlyBrace; + } else { + width += 2 * textPadding; + } + + _multilevelLabelSizes.update( + label.level, + (double value) => maxSize = max(maxSize, width), + ifAbsent: () { + maxSize = double.negativeInfinity; + return maxSize = max(maxSize, width); + }, + ); + } + + return _multilevelLabelSizes.isEmpty + ? 0 + : _multilevelLabelSizes.values.reduce((double a, double b) => a + b); + } + + @override + void _drawAxisLine(PaintingContext context, Offset offset) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = (axis.axisLine.color ?? axis.chartThemeData!.axisLineColor)! + ..strokeWidth = axis.axisLine.width + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes( + context.canvas, + axis.axisLine.dashArray, + paint, + start: offset, + end: Offset(offset.dx, offset.dy + axis.size.height), + ); + } + } + + @override + void _drawMajorTicks(PaintingContext context, Offset offset) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = + (axis.majorTickLines.color ?? + axis.chartThemeData!.majorTickLineColor)! + ..strokeWidth = axis.majorTickLines.width + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + for (final double position in axis.majorTickPositions) { + final Offset start = offset.translate(0.0, position); + final Offset end = start.translate(axis.majorTickLines.size, 0.0); + context.canvas.drawLine(start, end, paint); + } + } + } + + @override + void _drawMinorTicks(PaintingContext context, Offset offset) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = + (axis.minorTickLines.color ?? + axis.chartThemeData!.minorTickLineColor)! + ..strokeWidth = axis.minorTickLines.width + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + for (final double position in axis.minorTickPositions) { + final Offset start = offset.translate(0.0, position); + final Offset end = start.translate(axis.minorTickLines.size, 0.0); + context.canvas.drawLine(start, end, paint); + } + } + } + + @override + void _drawLabels(PaintingContext context, Offset offset, Offset labelOffset) { + if (axis.visibleLabels.isEmpty) { + return; + } + + final AxisLabelIntersectAction action = axis.effectiveLabelIntersectAction; + int rotationAngle = axis.labelRotation; + if (axis._hasCollidingLabels) { + if (action == AxisLabelIntersectAction.rotate90) { + rotationAngle = angle90Degree; + } else if (action == AxisLabelIntersectAction.rotate45) { + rotationAngle = angle45Degree; + } + } + + final bool isOutSide = axis.labelPosition == ChartDataLabelPosition.outside; + final List axisLabels = axis.visibleLabels; + for (final AxisLabel label in axisLabels) { + if (!label.isVisible || label.position == null || label.position!.isNaN) { + continue; + } + + double dx = labelOffset.dx; + if ((axis.invertElementsOrder && isOutSide) || + (!axis.invertElementsOrder && !isOutSide)) { + dx += _maxLabelSize - label.labelSize.width; + } + + Offset position = Offset(dx, labelOffset.dy + label.position!); + label.region = Rect.fromLTWH( + position.dx, + position.dy, + label.labelSize.width, + label.labelSize.height, + ); + position += offset; + _drawLabel(context, position, label, rotationAngle); + } + } + + void _drawLabel( + PaintingContext context, + Offset position, + AxisLabel label, + int rotationAngle, + ) { + final TextSpan span = TextSpan( + text: label.renderText, + style: label.labelStyle, + ); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr + ..layout(); + if (rotationAngle == 0) { + _textPainter.paint(context.canvas, position); + } else { + context.canvas + ..save() + ..translate( + position.dx + label.labelSize.width / 2, + position.dy + label.labelSize.height / 2, + ) + ..rotate(degreeToRadian(rotationAngle)); + _textPainter.paint( + context.canvas, + Offset(-_textPainter.size.width / 2, -_textPainter.size.height / 2), + ); + context.canvas.restore(); + } + } + + @override + void _drawBorders(PaintingContext context, Offset offset) { + if (_axisBorderSize <= 0) { + return; + } + + final Color effectiveBorderColor = + (axis.borderColor ?? axis.chartThemeData!.axisLineColor)!; + if (effectiveBorderColor != Colors.transparent && + axis.borderWidth > 0 && + axis.borderPositions.isNotEmpty) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = effectiveBorderColor + ..strokeWidth = axis.borderWidth + ..style = PaintingStyle.stroke; + final double axisTop = axis.paintBounds.top; + final double axisBottom = axis.paintBounds.bottom; + final List positions = axis.borderPositions; + for (final double position in positions) { + if (axisTop <= position && axisBottom >= position) { + final Offset start = offset.translate(0.0, position); + final Offset end = start.translate(_axisBorderSize, 0.0); + context.canvas.drawLine(start, end, paint); + } + } + + if (axis.axisBorderType == AxisBorderType.rectangle) { + Offset start = offset; + Offset end = start.translate(0.0, axis.size.height); + context.canvas.drawLine(start, end, paint); + + start = start.translate(_axisBorderSize, 0.0); + end = start.translate(0.0, axis.size.height); + context.canvas.drawLine(start, end, paint); + } + } + } + + @override + void _drawMultilevelLabels(PaintingContext context, Offset offset) { + final List labels = axis.visibleMultilevelLabels; + final int length = labels.length; + if (length > 0) { + final MultiLevelBorderType borderType = + axis.multiLevelLabelStyle.borderType; + final Paint paint = + Paint() + ..color = + (axis.multiLevelLabelStyle.borderColor ?? + axis.chartThemeData!.axisLineColor)! + ..strokeWidth = + axis.multiLevelLabelStyle.borderWidth != 0 + ? axis.multiLevelLabelStyle.borderWidth + : axis.axisLine.width + ..style = PaintingStyle.stroke; + + double left = offset.dx; + int level = _multilevelLabelSizes.keys.first; + double width = _multilevelLabelSizes[level]!; + for (final AxisMultilevelLabel label in labels) { + if (level != label.level) { + left += width; + level = label.level; + width = _multilevelLabelSizes[level]!; + } + + final TextSpan span = TextSpan( + text: label.trimmedText, + style: label.style.copyWith( + color: label.style.color ?? axis.chartThemeData!.axisLabelColor, + ), + ); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr + ..layout(); + final Rect bounds = Rect.fromLTWH( + left + label.region.left, + offset.dy + label.region.top, + width, + label.region.height, + ); + _multilevelLabelBorderShape.onPaint( + context, + borderType, + bounds, + _textPainter, + paint, + axis, + ); + } + } + } + + @override + void _drawTitle(PaintingContext context, Offset offset) { + final int rotationAngle = axis.invertElementsOrder ? 270 : 90; + final TextStyle textStyle = axis.chartThemeData!.axisTitleTextStyle!.merge( + axis.title.textStyle, + ); + final TextSpan span = TextSpan(text: axis.title.text, style: textStyle); + _textPainter + ..text = span + ..textAlign = TextAlign.center + ..textDirection = TextDirection.ltr; + _textPainter.layout(); + + double x = offset.dx; + if (!axis.invertElementsOrder) { + x += _textPainter.height; + } + + late double y; + switch (axis.title.alignment) { + case ChartAlignment.near: + y = offset.dy + axis.size.height; + if (!axis.invertElementsOrder) { + y -= _textPainter.width; + } + break; + case ChartAlignment.center: + y = + offset.dy + + axis.size.height / 2 + + (_textPainter.width / 2 * (axis.invertElementsOrder ? 1 : -1)); + break; + case ChartAlignment.far: + y = offset.dy; + if (axis.invertElementsOrder) { + y += _textPainter.width; + } + break; + } + + context.canvas + ..save() + ..translate(x, y) + ..rotate(degreeToRadian(rotationAngle)); + _textPainter.paint(context.canvas, Offset.zero); + context.canvas.restore(); + } +} + +abstract class _MultilevelLabelBorderShape { + final double _curveBraceCurveSize = 10.0; + + double preferredSize(MultiLevelBorderType type) { + switch (type) { + case MultiLevelBorderType.rectangle: + case MultiLevelBorderType.withoutTopAndBottom: + case MultiLevelBorderType.squareBrace: + return 0.0; + + case MultiLevelBorderType.curlyBrace: + return 2 * _curveBraceCurveSize; + } + } + + void onPaint( + PaintingContext context, + MultiLevelBorderType type, + Rect bounds, + TextPainter labelPainter, + Paint borderPaint, + RenderChartAxis axis, + ) { + if (axis.parentData == null) { + return; + } + final Rect clipRect = + axis.labelPosition == ChartDataLabelPosition.inside + ? axis.parent!.plotAreaBounds + : (axis.parentData! as CartesianAxesParentData).offset & axis.size; + final Rect extendedClipRect = clipRect.inflate(borderPaint.strokeWidth / 2); + context.canvas.save(); + context.canvas.clipRect(extendedClipRect); + final bool invertElementsOrder = + axis.labelPosition == ChartDataLabelPosition.inside + ? !axis.invertElementsOrder + : axis.invertElementsOrder; + switch (type) { + case MultiLevelBorderType.rectangle: + _drawRectangleBorder(context, bounds, labelPainter, borderPaint); + break; + case MultiLevelBorderType.withoutTopAndBottom: + _drawBorderWithoutTopAndBottom( + context, + bounds, + labelPainter, + borderPaint, + ); + break; + case MultiLevelBorderType.squareBrace: + _drawSquareBraceBorder( + context, + bounds, + labelPainter, + borderPaint, + invertElementsOrder, + ); + break; + case MultiLevelBorderType.curlyBrace: + _drawCurlyBraceBorder( + context, + bounds, + labelPainter, + borderPaint, + invertElementsOrder, + ); + break; + } + context.canvas.restore(); + } + + void _drawRectangleBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + ) { + context.canvas.drawRect(bounds, paint); + _drawLabelAtRectangleBorderCenter(context, labelPainter, bounds); + } + + void _drawLabelAtRectangleBorderCenter( + PaintingContext context, + TextPainter labelPainter, + Rect bounds, + ) { + labelPainter.paint( + context.canvas, + bounds.center.translate( + -labelPainter.size.width / 2, + -labelPainter.size.height / 2, + ), + ); + } + + @protected + void _drawBorderWithoutTopAndBottom( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + ); + + @protected + void _drawSquareBraceBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + bool isOpposed, + ); + + @protected + void _drawCurlyBraceBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + bool isOpposed, + ); +} + +class _HorizontalMultilevelLabelBorderShape + extends _MultilevelLabelBorderShape { + @override + void _drawBorderWithoutTopAndBottom( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + ) { + context.canvas + ..drawLine(bounds.topLeft, bounds.bottomLeft, paint) + ..drawLine(bounds.topRight, bounds.bottomRight, paint); + _drawLabelAtRectangleBorderCenter(context, labelPainter, bounds); + } + + @override + void _drawSquareBraceBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + bool isOpposed, + ) { + final Offset labelOffset = bounds.center.translate( + -labelPainter.size.width / 2, + -labelPainter.size.height / 2, + ); + Offset labelStart = labelOffset.translate( + 0.0, + labelPainter.size.height / 2, + ); + Offset labelEnd = labelStart.translate(labelPainter.size.width, 0.0); + + Offset topLeft = bounds.topLeft; + Offset topRight = bounds.topRight; + // Handling axis inversed. + if (topLeft.dx > topRight.dx) { + final Offset temp = labelEnd; + labelEnd = labelStart; + labelStart = temp; + } + + if (isOpposed) { + topLeft = bounds.bottomLeft; + topRight = bounds.bottomRight; + } + + final Offset centerLeft = bounds.centerLeft; + final Offset centerRight = bounds.centerRight; + context.canvas + ..drawLine(topLeft, centerLeft, paint) + ..drawLine(centerLeft, labelStart, paint) + ..drawLine(labelEnd, centerRight, paint) + ..drawLine(centerRight, topRight, paint); + _drawLabelAtRectangleBorderCenter(context, labelPainter, bounds); + } + + @override + void _drawCurlyBraceBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + bool isOpposed, + ) { + if (isOpposed) { + _drawOpposedCurlyBraceBorder(bounds, context, labelPainter, paint); + } else { + _drawNormalCurlyBraceBorder(bounds, context, labelPainter, paint); + } + } + + void _drawNormalCurlyBraceBorder( + Rect bounds, + PaintingContext context, + TextPainter labelPainter, + Paint paint, + ) { + final Path path = Path(); + double left = bounds.left; + double right = bounds.right; + if (left > right) { + final double temp = left; + left = right; + right = temp; + } + + final Rect startCurveRegion = Rect.fromLTWH( + left, + bounds.top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect centerStartCurveRegion = Rect.fromLTWH( + bounds.center.dx - _curveBraceCurveSize, + startCurveRegion.bottom, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..moveTo(startCurveRegion.left, startCurveRegion.top) + ..quadraticBezierTo( + startCurveRegion.bottomLeft.dx, + startCurveRegion.bottomLeft.dy, + startCurveRegion.bottomRight.dx, + startCurveRegion.bottomRight.dy, + ) + ..lineTo(centerStartCurveRegion.left, centerStartCurveRegion.top) + ..quadraticBezierTo( + centerStartCurveRegion.topRight.dx, + centerStartCurveRegion.topRight.dy, + centerStartCurveRegion.bottomRight.dx, + centerStartCurveRegion.bottomRight.dy, + ); + + final Rect centerEndCurveRegion = Rect.fromLTWH( + bounds.center.dx, + centerStartCurveRegion.top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect endCurveRegion = Rect.fromLTWH( + right - _curveBraceCurveSize, + startCurveRegion.top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..quadraticBezierTo( + centerEndCurveRegion.topLeft.dx, + centerEndCurveRegion.topLeft.dy, + centerEndCurveRegion.topRight.dx, + centerEndCurveRegion.topRight.dy, + ) + ..lineTo(endCurveRegion.left, endCurveRegion.bottom) + ..quadraticBezierTo( + endCurveRegion.bottomRight.dx, + endCurveRegion.bottomRight.dy, + endCurveRegion.topRight.dx, + endCurveRegion.topRight.dy, + ); + context.canvas.drawPath(path, paint); + // TODO(VijayakumarM): Add label padding. + labelPainter.paint( + context.canvas, + Offset( + bounds.center.dx - labelPainter.size.width / 2, + bounds.top + 2 * _curveBraceCurveSize, + ), + ); + } + + void _drawOpposedCurlyBraceBorder( + Rect bounds, + PaintingContext context, + TextPainter labelPainter, + Paint paint, + ) { + final Path path = Path(); + double left = bounds.left; + double right = bounds.right; + if (left > right) { + final double temp = left; + left = right; + right = temp; + } + + final Rect startCurveRegion = Rect.fromLTWH( + left, + bounds.bottom - _curveBraceCurveSize, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect centerStartCurveRegion = Rect.fromLTWH( + bounds.center.dx - _curveBraceCurveSize, + startCurveRegion.top - _curveBraceCurveSize, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..moveTo(startCurveRegion.left, startCurveRegion.bottom) + ..quadraticBezierTo( + startCurveRegion.topLeft.dx, + startCurveRegion.topLeft.dy, + startCurveRegion.topRight.dx, + startCurveRegion.topRight.dy, + ) + ..lineTo(centerStartCurveRegion.left, centerStartCurveRegion.bottom) + ..quadraticBezierTo( + centerStartCurveRegion.bottomRight.dx, + centerStartCurveRegion.bottomRight.dy, + centerStartCurveRegion.topRight.dx, + centerStartCurveRegion.topRight.dy, + ); + + final Rect centerEndCurveRegion = Rect.fromLTWH( + bounds.center.dx, + centerStartCurveRegion.top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect endCurveRegion = Rect.fromLTWH( + right - _curveBraceCurveSize, + startCurveRegion.top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..quadraticBezierTo( + centerEndCurveRegion.bottomLeft.dx, + centerEndCurveRegion.bottomLeft.dy, + centerEndCurveRegion.bottomRight.dx, + centerEndCurveRegion.bottomRight.dy, + ) + ..lineTo(endCurveRegion.left, endCurveRegion.top) + ..quadraticBezierTo( + endCurveRegion.topRight.dx, + endCurveRegion.topRight.dy, + endCurveRegion.bottomRight.dx, + endCurveRegion.bottomRight.dy, + ); + context.canvas.drawPath(path, paint); + // TODO(VijayakumarM): Add label padding. + labelPainter.paint( + context.canvas, + Offset(bounds.center.dx - labelPainter.size.width / 2, bounds.top), + ); + } +} + +class _VerticalMultilevelLabelBorderShape extends _MultilevelLabelBorderShape { + @override + void _drawBorderWithoutTopAndBottom( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + ) { + context.canvas + ..drawLine(bounds.topLeft, bounds.topRight, paint) + ..drawLine(bounds.bottomLeft, bounds.bottomRight, paint); + _drawLabelAtRectangleBorderCenter(context, labelPainter, bounds); + } + + @override + void _drawSquareBraceBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + bool isOpposed, + ) { + Offset topLeft = bounds.topLeft; + Offset bottomLeft = bounds.bottomLeft; + + final Offset labelOffset = bounds.center.translate( + -labelPainter.size.width / 2, + -labelPainter.size.height / 2, + ); + final double halfLabelWidth = labelPainter.size.width / 2; + Offset labelStart = labelOffset.translate( + halfLabelWidth, + labelPainter.size.height, + ); + Offset labelEnd = labelOffset.translate(halfLabelWidth, 0.0); + + // Handling axis inversed. + if (topLeft.dy < bottomLeft.dy) { + final Offset temp = labelEnd; + labelEnd = labelStart; + labelStart = temp; + } + + if (isOpposed) { + topLeft = bounds.topRight; + bottomLeft = bounds.bottomRight; + } + + final Offset topCenter = bounds.topCenter; + final Offset bottomCenter = bounds.bottomCenter; + context.canvas + ..drawLine(topLeft, topCenter, paint) + ..drawLine(topCenter, labelStart, paint) + ..drawLine(labelEnd, bottomCenter, paint) + ..drawLine(bottomCenter, bottomLeft, paint); + _drawLabelAtRectangleBorderCenter(context, labelPainter, bounds); + } + + @override + void _drawCurlyBraceBorder( + PaintingContext context, + Rect bounds, + TextPainter labelPainter, + Paint paint, + bool isOpposed, + ) { + if (isOpposed) { + _drawOpposedCurlyBraceBorder(bounds, context, labelPainter, paint); + } else { + _drawNormalCurlyBraceBorder(bounds, context, labelPainter, paint); + } + } + + void _drawNormalCurlyBraceBorder( + Rect bounds, + PaintingContext context, + TextPainter labelPainter, + Paint paint, + ) { + final Path path = Path(); + double top = bounds.top; + double bottom = bounds.bottom; + // Handling axis inversed. + if (top > bottom) { + final double temp = top; + top = bottom; + bottom = temp; + } + + final Rect startCurveRegion = Rect.fromLTWH( + bounds.left, + top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect centerStartCurveRegion = Rect.fromLTWH( + startCurveRegion.left + _curveBraceCurveSize, + bounds.center.dy - _curveBraceCurveSize, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..moveTo(startCurveRegion.left, startCurveRegion.top) + ..quadraticBezierTo( + startCurveRegion.topRight.dx, + startCurveRegion.topRight.dy, + startCurveRegion.bottomRight.dx, + startCurveRegion.bottomRight.dy, + ) + ..lineTo(centerStartCurveRegion.left, centerStartCurveRegion.top) + ..quadraticBezierTo( + centerStartCurveRegion.bottomLeft.dx, + centerStartCurveRegion.bottomLeft.dy, + centerStartCurveRegion.bottomRight.dx, + centerStartCurveRegion.bottomRight.dy, + ); + + final Rect centerEndCurveRegion = Rect.fromLTWH( + centerStartCurveRegion.left, + bounds.center.dy, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect endCurveRegion = Rect.fromLTWH( + startCurveRegion.left, + bottom - _curveBraceCurveSize, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..quadraticBezierTo( + centerEndCurveRegion.topLeft.dx, + centerEndCurveRegion.topLeft.dy, + centerEndCurveRegion.bottomLeft.dx, + centerEndCurveRegion.bottomLeft.dy, + ) + ..lineTo(endCurveRegion.right, endCurveRegion.top) + ..quadraticBezierTo( + endCurveRegion.bottomRight.dx, + endCurveRegion.bottomRight.dy, + endCurveRegion.bottomLeft.dx, + endCurveRegion.bottomLeft.dy, + ); + context.canvas.drawPath(path, paint); + // TODO(VijayakumarM): Add label padding. + labelPainter.paint( + context.canvas, + Offset( + bounds.left + 2 * _curveBraceCurveSize, + bounds.center.dy - labelPainter.size.height / 2, + ), + ); + } + + void _drawOpposedCurlyBraceBorder( + Rect bounds, + PaintingContext context, + TextPainter labelPainter, + Paint paint, + ) { + final Path path = Path(); + double top = bounds.top; + double bottom = bounds.bottom; + // Handling axis inversed. + if (top > bottom) { + final double temp = top; + top = bottom; + bottom = temp; + } + + final Rect startCurveRegion = Rect.fromLTWH( + bounds.right - _curveBraceCurveSize, + top, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect centerStartCurveRegion = Rect.fromLTWH( + startCurveRegion.left - _curveBraceCurveSize, + bounds.center.dy - _curveBraceCurveSize, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..moveTo(startCurveRegion.right, startCurveRegion.top) + ..quadraticBezierTo( + startCurveRegion.topLeft.dx, + startCurveRegion.topLeft.dy, + startCurveRegion.bottomLeft.dx, + startCurveRegion.bottomLeft.dy, + ) + ..lineTo(centerStartCurveRegion.right, centerStartCurveRegion.top) + ..quadraticBezierTo( + centerStartCurveRegion.bottomRight.dx, + centerStartCurveRegion.bottomRight.dy, + centerStartCurveRegion.bottomLeft.dx, + centerStartCurveRegion.bottomLeft.dy, + ); + + final Rect centerEndCurveRegion = Rect.fromLTWH( + centerStartCurveRegion.left, + bounds.center.dy, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + final Rect endCurveRegion = Rect.fromLTWH( + startCurveRegion.left, + bottom - _curveBraceCurveSize, + _curveBraceCurveSize, + _curveBraceCurveSize, + ); + + path + ..quadraticBezierTo( + centerEndCurveRegion.topRight.dx, + centerEndCurveRegion.topRight.dy, + centerEndCurveRegion.bottomRight.dx, + centerEndCurveRegion.bottomRight.dy, + ) + ..lineTo(endCurveRegion.left, endCurveRegion.top) + ..quadraticBezierTo( + endCurveRegion.bottomLeft.dx, + endCurveRegion.bottomLeft.dy, + endCurveRegion.bottomRight.dx, + endCurveRegion.bottomRight.dy, + ); + context.canvas.drawPath(path, paint); + // TODO(VijayakumarM): Add label padding. + labelPainter.paint( + context.canvas, + Offset(bounds.left, bounds.center.dy - labelPainter.size.height / 2), + ); + } +} + +/// Holds the axis label information. +/// +/// Axis Label used by the user-specified or +/// by default to make the label for both x and y-axis. +/// +/// Provides options for label style, label size, text, +/// and value to customize the appearance. +class AxisLabel { + /// Creating an argument constructor of [AxisLabel] class. + AxisLabel( + this.labelStyle, + this.labelSize, + this.text, + this.value, + this.trimmedText, + this.renderText, + ); + + /// Specifies the label text style. + /// + /// The [TextStyle] is used to customize the chart title text style. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: CategoryAxis( + /// labelStyle: TextStyle(color: Colors.black), + /// ), + /// ), + /// ); + /// } + /// ``` + TextStyle labelStyle; + + /// Hold the size of the label. + Size labelSize; + + /// Contains the text of the label. + String text; + + /// Contains the trimmed text of the label. + String? trimmedText; + + /// Contains the label text to be rendered. + String renderText = ''; + + /// Holds the value of the visible range of the axis. + late num value; + + /// Specifies the label position. + double? position; + + /// Specifies the label visibility. + bool isVisible = true; + + /// Specifies the label region. + Rect? region; + + /// Specifies the row index of the label when multiple rows are set. + /// + /// The `_rowIndex` determines the row position of the label. + /// For example: + /// - `_rowIndex = 0` for the first row. + /// - `_rowIndex = 1` for the second row. + /// - `_rowIndex = 2` for the third row, and so on. + /// + /// This is only applicable when multiple rows are enabled for the axis labels. + int _rowIndex = 0; + + /// Specifies the size of the row for the label when multiple rows are set. + /// + /// The `_prevRowSize` is calculated based on the cumulative height of all + /// previous rows. + /// For example: + /// - For the first row (`_rowIndex = 0`), `_prevRowSize` is zero. + /// - For the second row (`_rowIndex = 1`), `_prevRowSize` is the height of + /// the label size of the first row. + /// - For the third row (`_rowIndex = 2`), `_prevRowSize` is the sum of the + /// heights of the first and second rows. + /// + /// This is only applicable when multiple rows are enabled for the axis labels. + Size _prevRowSize = Size.zero; +} + +/// This class Renders the major tick lines for axis. +/// +/// To render major grid lines, create an instance of [MajorTickLines], +/// and assign it to the majorTickLines property of [ChartAxis]. +/// The Major tick lines can be drawn for each axis on the plot area. +/// +/// Provides options for [size], [width], and [color] to customize +/// the appearance. +class MajorTickLines { + /// Creating an argument constructor of [MajorTickLines] class. + const MajorTickLines({this.size = 5, this.width = 1, this.color}); + + /// Size of the major tick lines. + /// + /// Defaults to `8`. + /// + /// Size representation of the major ticks. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// majorTickLines: const MajorTickLines( + /// size: 6 + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final double size; + + /// Width of the major tick lines. + /// + /// Defaults to `1`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// majorTickLines: const MajorTickLines( + /// width: 2 + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final double width; + + /// Colors of the major tick lines. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// majorTickLines: const MajorTickLines( + /// color: Colors.black + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final Color? color; +} + +/// This class has the properties of minor tick lines. +/// +/// To render minor grid lines, create an instance of [MinorTickLines], +/// and assign it to the minorTickLines property of [ChartAxis]. +/// The Minor tick lines can be drawn for each axis on the plot area. +/// +/// Provides the color option to change the [color] of the +/// tick line for the customization. +@immutable +class MinorTickLines { + /// Creating an argument constructor of MinorTickLines class. + const MinorTickLines({this.size = 3, this.width = 0.7, this.color}); + + /// Height of the minor tick lines. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// minorTicksPerInterval: 5, + /// minorTickLines: MinorTickLines( + /// size: 5, + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + final double size; + + /// Width of the minor tick lines. + /// + /// Defaults to `0.7`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// minorTicksPerInterval: 5, + /// minorTickLines: MinorTickLines( + /// width: 0.5, + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + final double width; + + /// Color of the minor tick lines. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// minorTicksPerInterval: 5, + /// minorTickLines: MinorTickLines( + /// color:Colors.red, + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + final Color? color; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MinorTickLines && + other.size == size && + other.width == width && + other.color == color; + } + + @override + int get hashCode { + final List values = [size, width, color]; + return Object.hashAll(values); + } +} + +/// Customizes the major grid lines. +/// +/// This class Renders the major grid lines for the axis. +/// +/// To render major grid lines, create an instance of [MajorGridLines], +/// and assign it to the major gridLines property of [ChartAxis]. +/// Major grid lines can be drawn for each axis on the plot area. +/// +/// Provides options for [color], [width], and [dashArray] +/// to customize the appearance. +@immutable +class MajorGridLines { + /// Creating an argument constructor of MajorGridLines class. + const MajorGridLines({this.width = 0.7, this.color, this.dashArray}); + + /// Any number of values can be provided in the list. Odd value is considered + /// as rendering size and even value is considered as a gap. + /// + /// Defaults to `[0,0]`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// majorGridLines: MajorGridLines( + /// dashArray: [1,2] + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final List? dashArray; + + /// Width of the major grid lines. + /// + /// Defaults to `0.7`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// majorGridLines: MajorGridLines( + /// width:2 + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final double width; + + /// Color of the major grid lines. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// majorGridLines: MajorGridLines( + /// color:Colors.red + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final Color? color; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MajorGridLines && + other.dashArray == dashArray && + other.width == width && + other.color == color; + } + + @override + int get hashCode { + final List values = [dashArray, width, color]; + return Object.hashAll(values); + } +} + +/// Customizes the minor grid lines. +/// +/// To render minor grid lines, create an instance of [MinorGridLines], +/// and assign it to the property of [ChartAxis]. +/// The Minor grid lines can be drawn for each axis on the plot area. +/// +/// Provides the options of [width], [color], and [dashArray] values +/// to customize the appearance. +class MinorGridLines { + /// Creating an argument constructor of MinorGridLines class. + const MinorGridLines({this.width = 0.5, this.color, this.dashArray}); + + /// Any number of values can be provided in the list. Odd value is considered + /// as rendering size and even value is considered as a gap. + /// + /// Defaults to `[0,0]`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// minorTicksPerInterval: 5, + /// minorGridLines: MinorGridLines( + /// dashArray: [10, 20], + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + final List? dashArray; + + /// Width of the minor grid lines. + /// + /// Defaults to `0.5`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// minorTicksPerInterval: 5, + /// minorGridLines: MinorGridLines( + /// width: 0.7, + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + final double width; + + /// Color of the minor grid lines. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// minorTicksPerInterval: 5, + /// minorGridLines: MinorGridLines( + /// color: Colors.red, + /// ), + /// ) + /// )); + /// } + /// ``` + final Color? color; +} + +/// This class consists of axis line properties. +/// +/// It has the public properties to customize the axis line by increasing or +/// decreasing the width of the axis line and render the axis line with dashes +/// by defining the [dashArray] value. +/// +/// Provides options for color, dash array, and width to customize the +/// appearance of the axis line. +class AxisLine { + /// Creating an argument constructor of AxisLine class. + const AxisLine({this.color, this.dashArray, this.width = 1}); + + /// Width of the axis line. + /// + /// Defaults to `1`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// axisLine:AxisLine( + /// width: 2 + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final double width; + + /// Color of the axis line. Color will be applied based on the brightness + /// property of the app. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// axisLine:AxisLine( + /// color:Colors.blue, + /// ) + /// ), + /// )); + /// } + /// ``` + 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. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// axisLine:AxisLine( + /// dashArray: [5,5], + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final List? dashArray; +} + +/// This class holds the property of the axis title. +/// +/// It has public properties to customize the text and font of the axis title. +/// Axis does not display title by default. +/// Use of the property will customize the title. +/// +/// Provides text, text style, and text alignment options for +/// customization of appearance. +class AxisTitle { + /// Creating an argument constructor of AxisTitle class. + const AxisTitle({ + this.text, + this.textStyle, + this.alignment = ChartAlignment.center, + }); + + /// Text to be displayed as axis title. + /// + /// Defaults to `‘’`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// title: AxisTitle( + /// text: 'Axis Title', + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final String? text; + + /// Customizes the appearance of text in axis title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// title: AxisTitle( + /// text: 'Axis Title', + /// textStyle: TextStyle( + /// color: Colors.blue, + /// fontStyle: FontStyle.italic, + /// fontWeight: FontWeight.w600, + /// fontFamily: 'Roboto' + /// ) + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Aligns the axis title. + /// + /// It is applicable for both vertical and horizontal axes. + /// + /// * `ChartAlignment.near`, moves the axis title to the beginning of the axis + /// + /// * `ChartAlignment.far`, moves the axis title to the end of the axis + /// + /// * `ChartAlignment.center`, moves the axis title to the center + /// position of the axis. + /// + /// Defaults to `ChartAlignment.center`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// title: AxisTitle( + /// text: 'Axis Title', + /// alignment: ChartAlignment.far, + /// ) + /// ), + /// ) + /// ); + /// } + /// ``` + final ChartAlignment alignment; +} + +abstract class ChartAxisController { + ChartAxisController(this.axis); + + final RenderChartAxis axis; + final List _zoomListeners = []; + + Tween? _zoomFactorTween; + Tween? _zoomPositionTween; + DoubleRange? _actualRange; + bool _isVisibleMinChanged = false; + bool _isVisibleMaxChanged = false; + + VoidCallback? get _onUpdateInitialZoomFactorAndPosition; + + double get previousZoomFactor => _previousZoomFactor; + double _previousZoomFactor = 1.0; + + double get previousZoomPosition => _previousZoomPosition; + double _previousZoomPosition = 0.0; + + double get zoomFactor => + _zoomFactorTween != null && axis._animation != null + ? _zoomFactorTween!.evaluate(axis._animation!) + : _zoomFactor; + double _zoomFactor = 1.0; + set zoomFactor(double value) { + _zoomFactor = value; + if (_onUpdateInitialZoomFactorAndPosition != null || + (!_isVisibleMinChanged && !_isVisibleMaxChanged)) { + _updateZoomFactorTween(_zoomFactor, _zoomFactor); + } + _notifyZoomFactorAndPositionListener(); + } + + double get zoomPosition => + _zoomPositionTween != null && axis._animation != null + ? _zoomPositionTween!.evaluate(axis._animation!) + : _zoomPosition; + double _zoomPosition = 0.0; + set zoomPosition(double value) { + _zoomPosition = value; + if (_onUpdateInitialZoomFactorAndPosition != null || + (!_isVisibleMinChanged && !_isVisibleMaxChanged)) { + _updateZoomPositionTween(_zoomPosition, _zoomPosition); + } + _notifyZoomFactorAndPositionListener(); + } + + void _updateActualRange(DoubleRange range) { + _actualRange = range; + _onUpdateInitialZoomFactorAndPosition?.call(); + _isVisibleMinChanged = false; + _isVisibleMaxChanged = false; + } + + void _updateZoomFactorAndPosition(num? min, num? max) { + if (_actualRange == null) { + return; + } + + final num actualMin = _actualRange!.minimum; + final num actualMax = _actualRange!.maximum; + final num visibleMin = min ?? actualMin; + final num visibleMax = max ?? actualMax; + zoomFactor = (visibleMax - visibleMin) / _actualRange!.delta; + zoomPosition = (visibleMin - actualMin) / _actualRange!.delta; + + if (_onUpdateInitialZoomFactorAndPosition != null) { + return; + } + + final DoubleRange? visibleRange = axis.visibleRange; + final num currentVisibleMin = visibleRange?.minimum ?? actualMin; + final num currentVisibleMax = visibleRange?.maximum ?? actualMax; + _previousZoomFactor = + (currentVisibleMax - currentVisibleMin) / _actualRange!.delta; + _previousZoomPosition = + (currentVisibleMin - actualMin) / _actualRange!.delta; + + _updateZoomFactorTween(_previousZoomFactor, _zoomFactor); + _updateZoomPositionTween(_previousZoomPosition, _zoomPosition); + + if (axis.rangeController != null) { + // We have set false for this properties in startAnimation method, + // but rangeController returned here before calling startAnimation method. + // So, handled here. + _isVisibleMaxChanged = false; + _isVisibleMinChanged = false; + return; + } + + _startAnimation(); + } + + void _updateZoomFactorTween(double? start, double? end) { + if (_zoomFactorTween != null) { + _zoomFactorTween!.begin = start; + _zoomFactorTween!.end = end; + } else { + _zoomFactorTween = Tween(begin: start, end: end); + } + } + + void _updateZoomPositionTween(double? start, double? end) { + if (_zoomPositionTween != null) { + _zoomPositionTween!.begin = start; + _zoomPositionTween!.end = end; + } else { + _zoomPositionTween = Tween(begin: start, end: end); + } + } + + void _startAnimation() { + if (axis.zoomingInProgress) { + return; + } + + if (_isVisibleMinChanged) { + axis._animationController?.forward(from: 0.0); + _isVisibleMinChanged = false; + } + + if (_isVisibleMaxChanged) { + axis._animationController?.forward(from: 0.0); + _isVisibleMaxChanged = false; + } + } + + void _handleAnimationCompleted() { + if (_zoomFactorTween != null && _zoomFactorTween!.end != null) { + _zoomFactor = _zoomFactorTween!.end!; + } + if (_zoomPositionTween != null && _zoomPositionTween!.end != null) { + _zoomPosition = _zoomPositionTween!.end!; + } + } + + void _addZoomFactorAndPositionListener(VoidCallback listener) { + _zoomListeners.add(listener); + } + + void _removeZoomFactorAndPositionListener(VoidCallback listener) { + _zoomListeners.remove(listener); + } + + @protected + void _notifyZoomFactorAndPositionListener() { + for (final VoidCallback listener in _zoomListeners) { + listener(); + } + } + + void dispose() { + _zoomListeners.clear(); + } +} + +class CategoryAxisController extends ChartAxisController { + CategoryAxisController(super.axis); + + @override + VoidCallback? get _onUpdateInitialZoomFactorAndPosition => + _updateInitialZoomFactorAndPosition; + late VoidCallback? _updateInitialZoomFactorAndPosition = + _applyInitialZoomFactorAndPosition; + + double? get visibleMinimum => _visibleMin(); + double? _visibleMinimum; + set visibleMinimum(double? value) { + if (visibleMinimum != value) { + _visibleMinimum = value; + _updateMinMaxIfNeeded(); + _isVisibleMinChanged = true; + _updateZoomFactorAndPosition(_visibleMinimum, _visibleMaximum); + } + } + + double? get visibleMaximum => _visibleMax(); + double? _visibleMaximum; + set visibleMaximum(double? value) { + if (visibleMaximum != value) { + _visibleMaximum = value; + _updateMinMaxIfNeeded(); + _isVisibleMaxChanged = true; + _updateZoomFactorAndPosition(_visibleMinimum, _visibleMaximum); + } + } + + void _updateMinMaxIfNeeded() { + if (_visibleMinimum != null && + _visibleMaximum != null && + _visibleMinimum == _visibleMaximum) { + if (axis.labelPlacement == LabelPlacement.onTicks) { + _visibleMaximum = _visibleMaximum! + 1; + } else { + _visibleMinimum = _visibleMinimum! - 0.5; + _visibleMaximum = _visibleMaximum! + 0.5; + } + } + } + + void _applyInitialZoomFactorAndPosition() { + if (zoomFactor == 1.0 && zoomPosition == 0.0) { + _updateZoomFactorAndPosition(_visibleMinimum, _visibleMaximum); + } + _visibleMinimum = null; + _visibleMaximum = null; + _updateInitialZoomFactorAndPosition = null; + } + + double? _visibleMin() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMinimum; + } + return axis.visibleRange!.minimum.toDouble(); + } + + double? _visibleMax() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMaximum; + } + return axis.visibleRange!.maximum.toDouble(); + } +} + +class DateTimeAxisController extends ChartAxisController { + DateTimeAxisController(super.axis); + + @override + VoidCallback? get _onUpdateInitialZoomFactorAndPosition => + _updateInitialZoomFactorAndPosition; + late VoidCallback? _updateInitialZoomFactorAndPosition = + _applyInitialZoomFactorAndPosition; + + double? _visibleMinimumInMs; + double? _visibleMaximumInMs; + + DateTime? get visibleMinimum => _visibleMin(); + DateTime? _visibleMinimum; + set visibleMinimum(DateTime? value) { + if (visibleMinimum != value) { + _visibleMinimum = value; + _updateMinMaxIfNeeded(); + _isVisibleMinChanged = true; + _updateZoomFactorAndPosition(_visibleMinimumInMs, _visibleMaximumInMs); + } + } + + DateTime? get visibleMaximum => _visibleMax(); + DateTime? _visibleMaximum; + set visibleMaximum(DateTime? value) { + if (visibleMaximum != value) { + _visibleMaximum = value; + _updateMinMaxIfNeeded(); + _isVisibleMaxChanged = true; + _updateZoomFactorAndPosition(_visibleMinimumInMs, _visibleMaximumInMs); + } + } + + void _updateMinMaxIfNeeded() { + _visibleMinimumInMs = null; + if (_visibleMinimum != null) { + _visibleMinimumInMs = _visibleMinimum!.millisecondsSinceEpoch.toDouble(); + } + + _visibleMaximumInMs = null; + if (_visibleMaximum != null) { + _visibleMaximumInMs = _visibleMaximum!.millisecondsSinceEpoch.toDouble(); + } + + if (_visibleMinimumInMs == _visibleMaximumInMs) { + _visibleMinimumInMs = _visibleMinimumInMs! - defaultTimeStamp; + _visibleMaximumInMs = _visibleMaximumInMs! + defaultTimeStamp; + } + } + + void _applyInitialZoomFactorAndPosition() { + if (zoomFactor == 1.0 && zoomPosition == 0.0) { + _updateZoomFactorAndPosition(_visibleMinimumInMs, _visibleMaximumInMs); + } + _visibleMinimum = null; + _visibleMaximum = null; + _updateInitialZoomFactorAndPosition = null; + } + + DateTime? _visibleMin() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMinimum; + } + return DateTime.fromMillisecondsSinceEpoch( + axis.visibleRange!.minimum.toInt(), + ); + } + + DateTime? _visibleMax() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMaximum; + } + return DateTime.fromMillisecondsSinceEpoch( + axis.visibleRange!.maximum.toInt(), + ); + } +} + +class DateTimeCategoryAxisController extends ChartAxisController { + DateTimeCategoryAxisController(this._dateAxis) : super(_dateAxis); + + final RenderDateTimeCategoryAxis _dateAxis; + + num? _visibleMinimumIndex; + num? _visibleMaximumIndex; + + @override + VoidCallback? get _onUpdateInitialZoomFactorAndPosition => + _updateInitialZoomFactorAndPosition; + late VoidCallback? _updateInitialZoomFactorAndPosition = + _applyInitialZoomFactorAndPosition; + + DateTime? get visibleMinimum => _visibleMin(); + DateTime? _visibleMinimum; + set visibleMinimum(DateTime? value) { + if (visibleMinimum != value) { + _visibleMinimum = value; + if (_dateAxis.labels.isNotEmpty) { + _updateMinMaxIfNeeded(); + _isVisibleMinChanged = true; + _updateZoomFactorAndPosition( + _visibleMinimumIndex, + _visibleMaximumIndex, + ); + } + } + } + + DateTime? get visibleMaximum => _visibleMax(); + DateTime? _visibleMaximum; + set visibleMaximum(DateTime? value) { + if (visibleMaximum != value) { + _visibleMaximum = value; + if (_dateAxis.labels.isNotEmpty) { + _updateMinMaxIfNeeded(); + _isVisibleMaxChanged = true; + _updateZoomFactorAndPosition( + _visibleMinimumIndex, + _visibleMaximumIndex, + ); + } + } + } + + @override + void _updateActualRange(DoubleRange range) { + if (_actualRange != null) { + return; + } + _actualRange = range; + if (_visibleMinimumIndex == null && + _visibleMaximumIndex == null && + _dateAxis.labels.isNotEmpty) { + _visibleMinimumIndex = _dateAxis.effectiveValue(_visibleMinimum); + _visibleMaximumIndex = _dateAxis.effectiveValue( + _visibleMaximum, + needMin: false, + ); + if (axis.labelPlacement == LabelPlacement.betweenTicks) { + if (_visibleMinimumIndex != null) { + _visibleMinimumIndex = _visibleMinimumIndex! - 0.5; + } + if (_visibleMaximumIndex != null) { + _visibleMaximumIndex = _visibleMaximumIndex! - 0.5; + } + } + _updateMinMaxIfNeeded(); + if (_visibleMaximumIndex != null && _visibleMinimumIndex != null) { + _updateZoomFactorAndPosition( + _visibleMinimumIndex, + _visibleMaximumIndex, + ); + } + } + _onUpdateInitialZoomFactorAndPosition?.call(); + } + + void _updateMinMaxIfNeeded() { + _visibleMinimumIndex = null; + if (_visibleMinimum != null) { + _visibleMinimumIndex = _dateAxis.effectiveValue(_visibleMinimum); + if (axis.labelPlacement == LabelPlacement.betweenTicks) { + _visibleMinimumIndex = _visibleMinimumIndex! - 0.5; + } + } + + _visibleMaximumIndex = null; + if (_visibleMaximum != null) { + _visibleMaximumIndex = _dateAxis.effectiveValue( + _visibleMaximum, + needMin: false, + ); + if (axis.labelPlacement == LabelPlacement.betweenTicks) { + _visibleMaximumIndex = _visibleMaximumIndex! + 0.5; + } + } + + if (_visibleMinimumIndex != null && + _visibleMaximumIndex != null && + _visibleMinimumIndex == _visibleMaximumIndex) { + if (axis.labelPlacement == LabelPlacement.onTicks) { + _visibleMaximumIndex = _visibleMaximumIndex! + 1; + } else { + _visibleMinimumIndex = _visibleMinimumIndex! - 0.5; + _visibleMaximumIndex = _visibleMaximumIndex! + 0.5; + } + } + } + + void _applyInitialZoomFactorAndPosition() { + if (zoomFactor == 1.0 && zoomPosition == 0.0) { + _updateZoomFactorAndPosition(_visibleMinimumIndex, _visibleMaximumIndex); + } + _visibleMinimum = null; + _visibleMaximum = null; + _updateInitialZoomFactorAndPosition = null; + } + + DateTime? _visibleMin() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMinimum; + } + return DateTime.fromMillisecondsSinceEpoch( + axis.visibleRange!.minimum.toInt(), + ); + } + + DateTime? _visibleMax() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMaximum; + } + return DateTime.fromMillisecondsSinceEpoch( + axis.visibleRange!.maximum.toInt(), + ); + } +} + +class LogarithmicAxisController extends ChartAxisController { + LogarithmicAxisController(this._logAxis) : super(_logAxis); + + final RenderLogarithmicAxis _logAxis; + + @override + VoidCallback? get _onUpdateInitialZoomFactorAndPosition => + _updateInitialZoomFactorAndPosition; + late VoidCallback? _updateInitialZoomFactorAndPosition = + _applyInitialZoomFactorAndPosition; + + num? _logVisibleMin; + num? _logVisibleMax; + + double? get visibleMinimum => _visibleMin(); + double? _visibleMinimum; + set visibleMinimum(double? value) { + if (visibleMinimum != value) { + _visibleMinimum = value; + _updateMinMaxIfNeeded(); + _isVisibleMinChanged = true; + _updateZoomFactorAndPosition(_logVisibleMin, _logVisibleMax); + } + } + + double? get visibleMaximum => _visibleMax(); + double? _visibleMaximum; + set visibleMaximum(double? value) { + if (visibleMaximum != value) { + _visibleMaximum = value; + _updateMinMaxIfNeeded(); + _isVisibleMaxChanged = true; + _updateZoomFactorAndPosition(_logVisibleMin, _logVisibleMax); + } + } + + void _updateMinMaxIfNeeded() { + if (_visibleMinimum != null) { + _logVisibleMin = _logAxis.toLog(_visibleMinimum!).floor(); + } + if (_visibleMaximum != null) { + _logVisibleMax = _logAxis.toLog(_visibleMaximum!).ceil(); + } + if (_logVisibleMax != null && _logVisibleMin == _logVisibleMax) { + _logVisibleMax = _logVisibleMax! + 1; + } + } + + void _applyInitialZoomFactorAndPosition() { + if (zoomFactor == 1.0 && zoomPosition == 0.0) { + _updateZoomFactorAndPosition(_logVisibleMin, _logVisibleMax); + } + _visibleMinimum = null; + _visibleMaximum = null; + _updateInitialZoomFactorAndPosition = null; + } + + double? _visibleMin() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMinimum; + } + return _logAxis.toPow(axis.visibleRange!.minimum).toDouble(); + } + + double? _visibleMax() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMaximum; + } + return _logAxis.toPow(axis.visibleRange!.maximum).toDouble(); + } +} + +class NumericAxisController extends ChartAxisController { + NumericAxisController(super.axis); + + @override + VoidCallback? get _onUpdateInitialZoomFactorAndPosition => + _updateInitialZoomFactorAndPosition; + late VoidCallback? _updateInitialZoomFactorAndPosition = + _applyInitialZoomFactorAndPosition; + + double? get visibleMinimum => _visibleMin(); + double? _visibleMinimum; + set visibleMinimum(double? value) { + if (visibleMinimum != value) { + _visibleMinimum = value; + _updateMinMaxIfNeeded(); + _isVisibleMinChanged = true; + _updateZoomFactorAndPosition(_visibleMinimum, _visibleMaximum); + } + } + + double? get visibleMaximum => _visibleMax(); + double? _visibleMaximum; + set visibleMaximum(double? value) { + if (visibleMaximum != value) { + _visibleMaximum = value; + _updateMinMaxIfNeeded(); + _isVisibleMaxChanged = true; + _updateZoomFactorAndPosition(_visibleMinimum, _visibleMaximum); + } + } + + void _updateMinMaxIfNeeded() { + if (_visibleMaximum != null && _visibleMinimum == _visibleMaximum) { + _visibleMaximum = _visibleMaximum! + 1; + } + } + + void _applyInitialZoomFactorAndPosition() { + if (zoomFactor == 1.0 && zoomPosition == 0.0) { + _updateZoomFactorAndPosition(_visibleMinimum, _visibleMaximum); + } + _visibleMinimum = null; + _visibleMaximum = null; + _updateInitialZoomFactorAndPosition = null; + } + + double? _visibleMin() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMinimum; + } + return axis.visibleRange?.minimum.toDouble(); + } + + double? _visibleMax() { + if (_actualRange == null || axis.visibleRange == null) { + return _visibleMaximum; + } + return axis.visibleRange?.maximum.toDouble(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart new file mode 100644 index 000000000..7b099177b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart @@ -0,0 +1,987 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'axis.dart'; +import 'multi_level_labels.dart'; +import 'plot_band.dart'; + +/// This class has the properties of the category axis. +/// +/// Category axis displays text labels instead of numbers. When the string +/// values are bound to x values, then the x-axis must be initialized +/// with [CategoryAxis]. +/// +/// Provides the options for Label placement, arrange by index and interval used +/// to customize the appearance. +class CategoryAxis extends ChartAxis { + /// Creating an argument constructor of [CategoryAxis] class. + const CategoryAxis({ + super.key, + super.name, + super.isVisible, + super.title, + super.axisLine, + this.arrangeByIndex = false, + super.rangePadding, + this.labelPlacement = LabelPlacement.betweenTicks, + super.edgeLabelPlacement, + super.labelPosition, + super.tickPosition, + super.labelRotation, + super.labelIntersectAction, + super.labelAlignment, + super.isInversed, + super.opposedPosition, + super.maximumLabels, + super.majorTickLines, + super.majorGridLines, + super.labelStyle, + super.plotOffset, + super.plotOffsetStart, + super.plotOffsetEnd, + super.initialZoomFactor, + super.initialZoomPosition, + super.interactiveTooltip, + this.minimum, + this.maximum, + super.interval, + this.initialVisibleMinimum, + this.initialVisibleMaximum, + super.crossesAt, + super.associatedAxisName, + super.placeLabelsNearAxisLine, + super.plotBands, + super.desiredIntervals, + super.rangeController, + super.maximumLabelWidth, + super.labelsExtent, + super.autoScrollingDelta, + super.borderWidth, + super.borderColor, + super.axisBorderType, + super.multiLevelLabelFormatter, + super.multiLevelLabelStyle, + this.multiLevelLabels, + super.autoScrollingMode, + super.axisLabelFormatter, + this.onRendererCreated, + }) : assert( + (initialVisibleMaximum == null && initialVisibleMinimum == null) || + autoScrollingDelta == null, + 'Both properties have the same behavior to display the visible data points, use any one of the properties', + ); + + /// Position of the 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: CategoryAxis(labelPlacement: LabelPlacement.onTicks), + /// ) + /// ); + /// } + /// ``` + final LabelPlacement labelPlacement; + + /// Plots the data points based on the index value. + /// + /// By default, data points will be + /// grouped and plotted based on the x-value. They can also be grouped by the + /// data point index value. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: CategoryAxis(arrangeByIndex: true), + /// ) + /// ); + /// } + /// ``` + final bool arrangeByIndex; + + /// The minimum value of the axis. + /// + /// The axis will start from this value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: CategoryAxis(minimum: 0), + /// ) + /// ); + /// } + /// ``` + final double? minimum; + + /// The maximum value of the axis. + /// + /// The axis will end at this value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: CategoryAxis(maximum: 10), + /// ) + /// ); + /// } + /// ``` + final double? maximum; + + /// The minimum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: CategoryAxis(initialVisibleMinimum: 0), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// minimum value dynamically. + /// + /// ```dart + /// CategoryAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: CategoryAxis( + /// initialVisibleMinimum: 0, + /// onRendererCreated: (CategoryAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = 30; + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double? initialVisibleMinimum; + + /// The maximum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: CategoryAxis(initialVisibleMaximum: 20), + /// )); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// maximum value dynamically + /// + /// ```dart + /// CategoryAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: CategoryAxis( + /// initialVisibleMaximum: 20, + /// onRendererCreated: (CategoryAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMaximum = 70; + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double? initialVisibleMaximum; + + /// Provides the option to group the axis labels. You can customize the start, + /// end value of a multi-level label, text, and level of + /// the multi-level labels. + /// + /// The `start` and `end` values for the category axis need to be string type, + /// in the case of date-time or date-time category axes need to be date-time + /// and in the case of numeric or logarithmic axes needs to be double. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: CategoryAxis( + /// multiLevelLabels: [ + /// CategoricalMultiLevelLabel( + /// start: '2', + /// end: '4', + /// text: 'First' + /// ), + /// CategoricalMultiLevelLabel( + /// start: '5', + /// end: '7', + /// text: 'Second' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final List? multiLevelLabels; + + final Function(CategoryAxisController)? onRendererCreated; + + @override + RenderCategoryAxis createRenderer() { + return RenderCategoryAxis(); + } + + @override + RenderCategoryAxis createRenderObject(BuildContext context) { + final RenderCategoryAxis renderer = + super.createRenderObject(context) as RenderCategoryAxis; + renderer + ..labelPlacement = labelPlacement + ..arrangeByIndex = arrangeByIndex + ..minimum = minimum + ..maximum = maximum + ..initialVisibleMinimum = initialVisibleMinimum + ..initialVisibleMaximum = initialVisibleMaximum + ..multiLevelLabels = multiLevelLabels + ..onRendererCreated = onRendererCreated; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCategoryAxis renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..labelPlacement = labelPlacement + ..arrangeByIndex = arrangeByIndex + ..minimum = minimum + ..maximum = maximum + ..multiLevelLabels = multiLevelLabels; + } +} + +class RenderCategoryAxis extends RenderChartAxis { + final List labels = []; + final List _multilevelLabels = []; + + @override + CategoryAxisController get controller => _controller; + late final CategoryAxisController _controller = CategoryAxisController(this); + + @override + @nonVirtual + bool get canAnimate => + super.canAnimate || + (initialVisibleMinimum != null && initialVisibleMaximum != null); + + bool get arrangeByIndex => _arrangeByIndex; + bool _arrangeByIndex = false; + set arrangeByIndex(bool value) { + if (_arrangeByIndex != value) { + _arrangeByIndex = value; + markNeedsUpdate(); + } + } + + double? get minimum => _minimum; + double? _minimum; + set minimum(double? value) { + if (_minimum != value) { + _minimum = value; + markNeedsRangeUpdate(); + } + } + + double? get maximum => _maximum; + double? _maximum; + set maximum(double? value) { + if (_maximum != value) { + _maximum = value; + markNeedsRangeUpdate(); + } + } + + double? get initialVisibleMinimum => _initialVisibleMinimum; + double? _initialVisibleMinimum; + set initialVisibleMinimum(double? value) { + if (_initialVisibleMinimum == value) { + return; + } + _initialVisibleMinimum = value; + if (rangeController != null && rangeController!.start != null) { + _updateVisibleMinMax(min: (rangeController!.start as num).toDouble()); + } else { + _updateVisibleMinMax(min: initialVisibleMinimum); + } + } + + double? get initialVisibleMaximum => _initialVisibleMaximum; + double? _initialVisibleMaximum; + set initialVisibleMaximum(double? value) { + if (_initialVisibleMaximum == value) { + return; + } + _initialVisibleMaximum = value; + if (rangeController != null && rangeController!.end != null) { + _updateVisibleMinMax(max: (rangeController!.end as num).toDouble()); + } else { + _updateVisibleMinMax(max: initialVisibleMaximum); + } + } + + List? get multiLevelLabels => _multiLevelLabels; + List? _multiLevelLabels; + set multiLevelLabels(List? value) { + if (_multiLevelLabels != value) { + _multiLevelLabels = value; + markNeedsLayout(); + } + } + + @override + set rangeController(RangeController? value) { + super.rangeController = value; + if (value == null) { + return; + } + _updateVisibleMinMax( + min: (value.start as num?)?.toDouble(), + max: (value.end as num?)?.toDouble(), + ); + } + + Function(CategoryAxisController)? get onRendererCreated => _onRendererCreated; + Function(CategoryAxisController)? _onRendererCreated; + set onRendererCreated(Function(CategoryAxisController)? value) { + if (_onRendererCreated != value) { + _onRendererCreated = value; + } + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(controller); + rangeController?.addListener(_handleRangeControllerChange); + super.attach(owner); + } + + @override + void detach() { + rangeController?.removeListener(_handleRangeControllerChange); + super.detach(); + } + + void _handleRangeControllerChange() { + _updateVisibleMinMax( + min: (rangeController!.start as num?)?.toDouble(), + max: (rangeController!.end as num?)?.toDouble(), + ); + } + + void _updateVisibleMinMax({double? min, double? max}) { + if (min != null) { + controller.visibleMinimum = min; + } + if (max != null) { + controller.visibleMaximum = max; + } + } + + @override + num actualValue(Object value) { + if (value is String) { + return super.actualValue( + _toIndex(value, defaultIndex: visibleRange!.minimum), + ); + } + + return super.actualValue(value); + } + + @override + DoubleRange calculateActualRange() { + if (minimum != null && maximum != null) { + return DoubleRange(minimum!, maximum!); + } + + final DoubleRange range = super.calculateActualRange(); + if (minimum != null) { + range.minimum = minimum!; + } else if (maximum != null) { + range.maximum = maximum!; + } + + if (range.minimum == range.maximum && + labelPlacement == LabelPlacement.onTicks) { + range.maximum += 1; + } + + return range.copyWith(); + } + + @override + DoubleRange updateAutoScrollingDelta( + int scrollingDelta, + DoubleRange actualRange, + DoubleRange visibleRange, + ) { + if (initialVisibleMaximum != null || initialVisibleMinimum != null) { + return visibleRange; + } + return super.updateAutoScrollingDelta( + scrollingDelta, + actualRange, + visibleRange, + ); + } + + @override + DoubleRange defaultRange() => DoubleRange.zero(); + + @override + num calculateNiceInterval(num delta, Size availableSize) { + final num intervalsCount = desiredIntervalsCount(availableSize); + final num niceInterval = desiredNiceInterval(delta, intervalsCount); + return max(1, niceInterval.floor()); + } + + @override + DoubleRange applyRangePadding( + DoubleRange range, + num interval, + Size availableSize, + ) { + if (labelPlacement == LabelPlacement.betweenTicks) { + range.minimum -= 0.5; + range.maximum += 0.5; + } + + if (minimum == null || maximum == null) { + range = super.applyRangePadding(range, interval, availableSize); + + if (minimum != null) { + range.minimum = minimum!; + } + if (maximum != null) { + range.maximum = maximum!; + } + } + + if (range.minimum == range.maximum) { + return _handleEqualRange(range); + } + + return range.copyWith(); + } + + DoubleRange _handleEqualRange(DoubleRange range) { + if (labelPlacement == LabelPlacement.onTicks) { + return DoubleRange(range.minimum, range.maximum + 1); + } else if (labelPlacement == LabelPlacement.betweenTicks) { + return DoubleRange(range.minimum - 0.5, range.maximum + 0.5); + } + + return range; + } + + @override + void addNormalRange(DoubleRange range, num interval, Size availableSize) { + if (labelPlacement == LabelPlacement.onTicks) { + range.minimum -= 0.5; + range.maximum += 0.5; + if (range.minimum == 0) { + updateNormalRangePadding(range, availableSize); + } + } else { + super.addNormalRange(range, interval, availableSize); + } + } + + @override + void generateVisibleLabels() { + hasTrimmedAxisLabel = false; + if (visibleRange == null || visibleInterval == 0) { + return; + } + + final double extent = maximumLabelWidth ?? double.maxFinite; + labels.clear(); + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer) { + if (dependent.controller.isVisible) { + final List actualXValues = dependent.xRawValues; + final int actualXValuesLength = actualXValues.length; + for (int i = 0; i < actualXValuesLength; i++) { + final String rawX = actualXValues[i].toString(); + if (arrangeByIndex) { + final int length = labels.length; + if (i < length && labels[i] != null) { + labels[i] = '${labels[i]}, $rawX'; + } else { + labels.add(rawX); + } + } else { + if (!labels.contains(rawX)) { + labels.add(rawX); + } + } + } + } + } + } + + final num visibleMinimum = visibleRange!.minimum; + final num visibleMaximum = visibleRange!.maximum; + int length = labels.length; + // Calculate the labels until the maximum visible range. + if (length > 0 && visibleMaximum > length) { + for (int i = length; i <= visibleMaximum; i++) { + labels.add(i.toString()); + } + } + + length = labels.length; + if (length == 0) { + return; + } + + final bool isRtl = textDirection == TextDirection.rtl; + num current = visibleRange!.minimum.ceil(); + while (current <= visibleMaximum) { + if (current < visibleMinimum || + !effectiveVisibleRange!.contains(current)) { + current += visibleInterval; + continue; + } + + final int currentValue = current.round(); + String text = ''; + if (currentValue <= -1 || currentValue >= length) { + current += visibleInterval; + continue; + } else { + text = labels[currentValue]!; + } + + String callbackText = text; + TextStyle callbackTextStyle = chartThemeData!.axisLabelTextStyle!.merge( + labelStyle, + ); + if (axisLabelFormatter != null) { + final AxisLabelRenderDetails details = AxisLabelRenderDetails( + current, + callbackText, + callbackTextStyle, + this, + null, + null, + ); + final ChartAxisLabel label = axisLabelFormatter!(details); + callbackText = label.text; + callbackTextStyle = callbackTextStyle.merge(label.textStyle); + } + + Size textSize = measureText( + callbackText, + callbackTextStyle, + labelRotation, + ); + String textAfterTrimming = callbackText; + if (extent.isFinite && textSize.width > extent) { + textAfterTrimming = trimmedText( + callbackText, + callbackTextStyle, + extent, + labelRotation, + isRtl: isRtl, + ); + } + + textSize = measureText( + textAfterTrimming, + callbackTextStyle, + labelRotation, + ); + final bool isTextTrimmed = callbackText != textAfterTrimming; + final AxisLabel label = AxisLabel( + callbackTextStyle, + textSize, + callbackText, + current, + isTextTrimmed ? textAfterTrimming : null, + textAfterTrimming, + ); + visibleLabels.add(label); + if (isTextTrimmed) { + hasTrimmedAxisLabel = true; + } + current += visibleInterval; + } + + super.generateVisibleLabels(); + } + + @override + void calculateTickPositions( + LabelPlacement placement, { + List? source, + bool canCalculateMinorTick = false, + bool canCalculateMajorTick = true, + }) { + int length = visibleLabels.length; + if (length == 0 || effectiveVisibleRange == null) { + return; + } + + bool isBetweenTicks = placement == LabelPlacement.betweenTicks; + if (!isBetweenTicks && !canCalculateMajorTick) { + isBetweenTicks = !isBetweenTicks; + } + + final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; + length += isBetweenTicks ? 1 : 0; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + num current; + if (isBetweenTicks) { + if (i < lastIndex) { + current = visibleLabels[i].value - tickBetweenLabel; + } else { + current = + (visibleLabels[i - 1].value + visibleInterval) - tickBetweenLabel; + } + } else { + current = visibleLabels[i].value; + } + + source!.add(pointToPixel(current)); + } + } + + @override + void generatePlotBands() { + if (plotBands.isNotEmpty && + associatedAxis != null && + associatedAxis!.visibleRange != null) { + visiblePlotBands ??= []; + final int length = plotBands.length; + final Rect Function(PlotBand plotBand, num start, num end) bounds = + isVertical ? verticalPlotBandBounds : horizontalPlotBandBounds; + + for (int i = 0; i < length; i++) { + final PlotBand plotBand = plotBands[i]; + if (plotBand.isVisible) { + final dynamic actualStart = plotBand.start; + final dynamic actualEnd = plotBand.end; + final num min = + actualStart != null + ? actualValue( + _toIndex(actualStart, defaultIndex: visibleRange!.minimum), + ) + : visibleRange!.minimum; + num max = + actualEnd != null + ? actualValue( + _toIndex(actualEnd, defaultIndex: visibleRange!.maximum), + ) + : visibleRange!.maximum; + + num extent; + if (plotBand.isRepeatable) { + extent = plotBand.repeatEvery; + final dynamic actualRepeatUntil = plotBand.repeatUntil; + if (actualRepeatUntil != null) { + max = actualValue( + _toIndex( + actualRepeatUntil, + defaultIndex: visibleRange!.maximum, + ), + ); + if (max > actualRange!.maximum) { + max = actualRange!.maximum; + } + } else { + max = actualRange!.maximum; + } + } else { + extent = max - min; + } + + num current = min; + if (plotBand.isRepeatable) { + while (current < max) { + current = formPlotBandFrame( + plotBand, + current, + extent, + max, + bounds, + ); + } + } else { + formPlotBandFrame(plotBand, current, extent, max, bounds); + } + } + } + } + } + + // Find index, if the plot band [start,end,repeatUntil] value has string. + // Find index, if crossesAt have a string value. + num _toIndex(dynamic value, {num? defaultIndex}) { + if (value is String) { + final num index = labels.indexOf(value); + return index != -1 ? index : defaultIndex!; + } + return value; + } + + @override + void generateMultiLevelLabels() { + _multilevelLabels.clear(); + visibleMultilevelLabels.clear(); + + final int length = multiLevelLabels?.length ?? 0; + if (length == 0) { + return; + } + + for (int i = 0; i < length; i++) { + final CategoricalMultiLevelLabel label = multiLevelLabels![i]; + _multilevelLabels.add( + AxisMultilevelLabel( + label.text, + label.level, + labels.indexOf(label.start), + labels.indexOf(label.end), + ), + ); + } + + _multilevelLabels.sort( + (AxisMultilevelLabel a, AxisMultilevelLabel b) => + a.level.compareTo(b.level), + ); + + final void Function(AxisMultilevelLabel label) add = + invertElementsOrder + ? (AxisMultilevelLabel label) => + visibleMultilevelLabels.insert(0, label) + : (AxisMultilevelLabel label) => visibleMultilevelLabels.add(label); + + final int labelsLength = _multilevelLabels.length; + final TextStyle textStyle = chartThemeData!.axisMultiLevelLabelTextStyle! + .merge(multiLevelLabelStyle.textStyle); + for (int i = 0; i < labelsLength; i++) { + final AxisMultilevelLabel current = _multilevelLabels[i]; + if (isLies(current.start, current.end, visibleRange!)) { + String desiredText = current.text; + TextStyle desiredTextStyle = textStyle; + if (multiLevelLabelFormatter != null) { + final MultiLevelLabelRenderDetails details = + MultiLevelLabelRenderDetails( + current.level, + desiredText, + desiredTextStyle, + i, + name, + ); + final ChartAxisLabel label = multiLevelLabelFormatter!(details); + desiredText = label.text; + desiredTextStyle = textStyle.merge(label.textStyle); + } + current + ..actualTextSize = measureText(desiredText, desiredTextStyle, 0) + ..renderText = desiredText + ..style = desiredTextStyle; + add(current); + } + } + } + + @override + void updateMultiLevelLabels() { + late Rect Function(double start, double end, Size size) labelBounds; + labelBounds = isVertical ? _verticalLabelBounds : _horizontalLabelBounds; + + final bool isRtl = textDirection == TextDirection.rtl; + final bool isBetweenTicks = labelPlacement == LabelPlacement.betweenTicks; + final num betweenTicksInterval = isBetweenTicks ? 0.5 : 0; + + for (final AxisMultilevelLabel label in visibleMultilevelLabels) { + final num startValue = label.start - betweenTicksInterval; + final num endValue = label.end + betweenTicksInterval; + final double start = pointToPixel(startValue); + final double end = pointToPixel(endValue); + final double extent = (end - start - textPadding).abs(); + + String renderText = label.renderText; + final TextStyle style = label.style; + if (label.actualTextSize.width > extent) { + renderText = trimmedText(renderText, style, extent, 0, isRtl: isRtl); + } + + label.trimmedText = renderText; + label + ..transformStart = start + ..transformEnd = end + ..region = labelBounds(start, end, measureText(renderText, style, 0)); + } + } + + Rect _horizontalLabelBounds(double start, double end, Size labelSize) { + double height = labelSize.height; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + height += 2 * textPaddingOfCurlyBrace; + } else { + height += 2 * textPadding; + } + + return Rect.fromLTRB(start, 0, end, height); + } + + Rect _verticalLabelBounds(double start, double end, Size labelSize) { + double width = labelSize.width; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + width += 2 * textPaddingOfCurlyBrace; + } else { + width += 2 * textPadding; + } + + return Rect.fromLTRB(0, start, width, end); + } + + @override + void performUpdate() { + updateXValuesWithArrangeByIndex(); + super.performUpdate(); + } + + void updateXValuesWithArrangeByIndex() { + if (!arrangeByIndex) { + final List groupedRawValues = []; + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer && + dependent.controller.isVisible) { + final List xRawValues = dependent.xRawValues; + final int length = xRawValues.length; + if (length > 0) { + num minValue = 0; + num maxValue = 0; + for (int i = 0; i < length; i++) { + final String rawX = xRawValues[i].toString(); + if (!groupedRawValues.contains(rawX)) { + groupedRawValues.add(rawX); + } + + final int index = groupedRawValues.indexOf(rawX); + dependent.xValues[i] = index; + minValue = min(minValue, index); + maxValue = max(maxValue, index); + } + dependent.xMin = minValue; + dependent.xMax = maxValue; + } + } + } + } else { + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer && + dependent.controller.isVisible) { + final int length = dependent.dataCount; + if (length > 0) { + dependent.xValues.clear(); + for (int i = 0; i < length; i++) { + dependent.xValues.add(i); + } + dependent.xMin = 0; + dependent.xMax = length - 1; + } + } + } + } + } + + @override + void dispose() { + _multilevelLabels.clear(); + labels.clear(); + controller.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart new file mode 100644 index 000000000..e013012de --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart @@ -0,0 +1,1747 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' hide TextDirection; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'axis.dart'; +import 'multi_level_labels.dart'; +import 'plot_band.dart'; + +// This class holds the properties of the DateTime axis. +/// +/// The date-time axis uses a date-time scale and displays date-time values as +/// axis labels in the specified format. +/// +/// The range of the Date time can be customized by [minimum] and [maximum] +/// properties, also change data label format by the [dateFormat]. +/// +/// Provides the options for range padding, interval, date format for +/// customizing the appearance. +class DateTimeAxis extends ChartAxis { + /// Creating an argument constructor of [DateTimeAxis] class. + const DateTimeAxis({ + super.key, + super.name, + super.isVisible = true, + super.title, + super.axisLine, + super.rangePadding, + super.labelIntersectAction, + super.labelPosition, + super.tickPosition, + super.edgeLabelPlacement, + super.initialZoomFactor, + super.initialZoomPosition, + super.enableAutoIntervalOnZooming, + super.labelRotation, + super.isInversed, + super.opposedPosition, + super.minorTicksPerInterval, + super.maximumLabels, + super.plotOffset, + super.plotOffsetStart, + super.plotOffsetEnd, + super.majorTickLines, + super.minorTickLines, + super.majorGridLines, + super.minorGridLines, + super.labelStyle, + this.dateFormat, + this.intervalType = DateTimeIntervalType.auto, + super.interactiveTooltip, + this.labelFormat, + this.minimum, + this.maximum, + super.labelAlignment, + super.interval, + this.initialVisibleMinimum, + this.initialVisibleMaximum, + super.crossesAt, + super.associatedAxisName, + super.placeLabelsNearAxisLine, + super.plotBands, + super.rangeController, + super.desiredIntervals, + super.maximumLabelWidth, + super.labelsExtent, + this.autoScrollingDeltaType = DateTimeIntervalType.auto, + super.autoScrollingDelta, + super.borderWidth, + super.borderColor, + super.axisBorderType, + super.multiLevelLabelStyle, + super.multiLevelLabelFormatter, + this.multiLevelLabels, + super.autoScrollingMode, + super.axisLabelFormatter, + this.onRendererCreated, + }) : assert( + (initialVisibleMaximum == null && initialVisibleMinimum == null) || + autoScrollingDelta == null, + 'Both properties have the same behavior to display the visible data points, use any one of the properties', + ); + + /// Formats the date-time axis labels. The default data-time axis label can be + /// formatted with various built-in date formats. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis(dateFormat: DateFormat.y()), + /// ) + /// ); + /// } + /// ``` + final DateFormat? dateFormat; + + /// Formats the date time-axis labels. The labels can be customized by adding + /// desired text to prefix or suffix. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis(labelFormat: '{value} AM'), + /// ) + /// ); + /// } + /// ``` + 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, 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: + /// DateTimeAxis(intervalType: DateTimeIntervalType.years), + /// ) + /// ); + /// } + /// ``` + final DateTimeIntervalType intervalType; + + /// Minimum value of the axis. The axis will start from this date. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis(minimum: DateTime(2000)), + /// ) + /// ); + /// } + /// ``` + final DateTime? minimum; + + /// Maximum value of the axis. The axis will end at this date. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis(maximum: DateTime(2019)), + /// ) + /// ); + /// } + /// ``` + final DateTime? maximum; + + /// The minimum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: DateTimeAxis(initialVisibleMinimum: DateTime(2019)), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// minimum value dynamically. + /// + /// ```dart + /// DateTimeAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: DateTimeAxis( + /// initialVisibleMinimum: DateTime(2019), + /// onRendererCreated: (DateTimeAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = DateTime(2017); + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final DateTime? initialVisibleMinimum; + + /// The maximum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: DateTimeAxis(initialVisibleMaximum: DateTime(2020)), + /// )); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// maximum value dynamically + /// + /// ```dart + /// DateTimeAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: DateTimeAxis( + /// initialVisibleMaximum: DateTime(2020), + /// onRendererCreated: (DateTimeAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMaximum = DateTime(2024); + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final DateTime? initialVisibleMaximum; + + /// 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; + + /// Provides the option to group the axis labels. You can customize the start, + /// end value of a multi-level label, text, and level of + /// the multi-level labels. + /// + /// The `start` and `end` values for the category axis need to be string type, + /// in the case of date-time or date-time category axes need to be date-time + /// and in the case of numeric or logarithmic axes needs to be double. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis( + /// multiLevelLabels: [ + /// DateTimeMultiLevelLabel( + /// start: DateTime(2010, 1, 1), + /// end: DateTime(2010, 2, 1), + /// text: 'First' + /// ), + /// DateTimeMultiLevelLabel( + /// start: DateTime(2011, 1, 1), + /// end: DateTime(2011, 2, 1), + /// text: 'Second' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final List? multiLevelLabels; + + final Function(DateTimeAxisController)? onRendererCreated; + + @override + RenderDateTimeAxis createRenderer() { + return RenderDateTimeAxis(); + } + + @override + RenderDateTimeAxis createRenderObject(BuildContext context) { + final RenderDateTimeAxis renderer = + super.createRenderObject(context) as RenderDateTimeAxis; + renderer + ..dateFormat = dateFormat + ..labelFormat = labelFormat + ..intervalType = intervalType + ..minimum = minimum + ..maximum = maximum + ..initialVisibleMinimum = initialVisibleMinimum + ..initialVisibleMaximum = initialVisibleMaximum + ..autoScrollingDeltaType = autoScrollingDeltaType + ..multiLevelLabels = multiLevelLabels + ..onRendererCreated = onRendererCreated; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RenderDateTimeAxis renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..dateFormat = dateFormat + ..labelFormat = labelFormat + ..intervalType = intervalType + ..minimum = minimum + ..maximum = maximum + ..autoScrollingDeltaType = autoScrollingDeltaType + ..multiLevelLabels = multiLevelLabels; + } +} + +class RenderDateTimeAxis extends RenderChartAxis { + final List _multilevelLabels = []; + + DateTimeIntervalType get visibleIntervalType => _visibleIntervalType; + DateTimeIntervalType _visibleIntervalType = DateTimeIntervalType.auto; + + @override + DateTimeAxisController get controller => _controller; + late final DateTimeAxisController _controller = DateTimeAxisController(this); + + @override + @nonVirtual + bool get canAnimate => + super.canAnimate || + (initialVisibleMinimum != null && initialVisibleMaximum != null); + + DateFormat? get dateFormat => _dateFormat; + DateFormat? _dateFormat; + set dateFormat(DateFormat? value) { + if (_dateFormat != value) { + _dateFormat = value; + markNeedsLayout(); + } + } + + String? get labelFormat => _labelFormat; + String? _labelFormat; + set labelFormat(String? value) { + if (_labelFormat != value) { + _labelFormat = value; + markNeedsLayout(); + } + } + + DateTimeIntervalType get intervalType => _intervalType; + DateTimeIntervalType _intervalType = DateTimeIntervalType.auto; + set intervalType(DateTimeIntervalType value) { + if (_intervalType != value) { + _intervalType = value; + markNeedsLayout(); + } + } + + DateTime? get minimum => _minimum; + DateTime? _minimum; + set minimum(DateTime? value) { + if (_minimum != value) { + _minimum = value; + markNeedsRangeUpdate(); + } + } + + DateTime? get maximum => _maximum; + DateTime? _maximum; + set maximum(DateTime? value) { + if (_maximum != value) { + _maximum = value; + markNeedsRangeUpdate(); + } + } + + DateTime? get initialVisibleMinimum => _initialVisibleMinimum; + DateTime? _initialVisibleMinimum; + set initialVisibleMinimum(DateTime? value) { + if (_initialVisibleMinimum == value) { + return; + } + _initialVisibleMinimum = value; + if (rangeController != null && rangeController!.start != null) { + _updateVisibleMinMax(min: rangeController!.start); + } else { + _updateVisibleMinMax(min: initialVisibleMinimum); + } + } + + DateTime? get initialVisibleMaximum => _initialVisibleMaximum; + DateTime? _initialVisibleMaximum; + set initialVisibleMaximum(DateTime? value) { + if (_initialVisibleMaximum == value) { + return; + } + _initialVisibleMaximum = value; + if (rangeController != null && rangeController!.end != null) { + _updateVisibleMinMax(max: rangeController!.end); + } else { + _updateVisibleMinMax(max: initialVisibleMaximum); + } + } + + DateTimeIntervalType get autoScrollingDeltaType => _autoScrollingDeltaType; + DateTimeIntervalType _autoScrollingDeltaType = DateTimeIntervalType.auto; + set autoScrollingDeltaType(DateTimeIntervalType value) { + if (_autoScrollingDeltaType != value) { + _autoScrollingDeltaType = value; + } + } + + List? get multiLevelLabels => _multiLevelLabels; + List? _multiLevelLabels; + set multiLevelLabels(List? value) { + if (_multiLevelLabels != value) { + _multiLevelLabels = value; + } + } + + @override + set rangeController(RangeController? value) { + super.rangeController = value; + if (value == null) { + return; + } + assert(rangeController!.start is DateTime); + assert(rangeController!.end is DateTime); + _updateVisibleMinMax(min: value.start, max: value.end); + } + + Function(DateTimeAxisController)? get onRendererCreated => _onRendererCreated; + Function(DateTimeAxisController)? _onRendererCreated; + set onRendererCreated(Function(DateTimeAxisController)? value) { + if (_onRendererCreated != value) { + _onRendererCreated = value; + } + } + + @override + @nonVirtual + num actualValue(Object value) { + assert(value.runtimeType == DateTime); + final DateTime date = value as DateTime; + return super.actualValue(date.millisecondsSinceEpoch); + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(controller); + rangeController?.addListener(_handleRangeControllerChange); + super.attach(owner); + } + + @override + void detach() { + rangeController?.removeListener(_handleRangeControllerChange); + super.detach(); + } + + void _handleRangeControllerChange() { + dynamic start = rangeController!.start; + dynamic end = rangeController!.end; + if (rangeController!.start is! DateTime) { + start = DateTime.fromMillisecondsSinceEpoch( + (rangeController!.start as num).toInt(), + ); + } + if (rangeController!.end is! DateTime) { + end = DateTime.fromMillisecondsSinceEpoch( + (rangeController!.end as num).toInt(), + ); + } + _updateVisibleMinMax(min: start, max: end); + } + + void _updateVisibleMinMax({DateTime? min, DateTime? max}) { + if (min != null) { + controller.visibleMinimum = min; + } + if (max != null) { + controller.visibleMaximum = max; + } + } + + @override + void updateRangeControllerValues(DoubleRange newVisibleRange) { + final DateTime start = DateTime.fromMillisecondsSinceEpoch( + newVisibleRange.minimum.toInt(), + ); + final DateTime end = DateTime.fromMillisecondsSinceEpoch( + newVisibleRange.maximum.toInt(), + ); + if (rangeController!.start != start) { + rangeController!.start = start; + } + if (rangeController!.end != end) { + rangeController!.end = end; + } + } + + @override + DoubleRange calculateActualRange() { + if (minimum != null && maximum != null) { + if (minimum == maximum) { + return DoubleRange( + minimum!.millisecondsSinceEpoch - defaultTimeStamp, + maximum!.millisecondsSinceEpoch + defaultTimeStamp, + ); + } + return DoubleRange( + minimum!.millisecondsSinceEpoch, + maximum!.millisecondsSinceEpoch, + ); + } + + final DoubleRange range = super.calculateActualRange(); + if (minimum != null) { + range.minimum = minimum!.millisecondsSinceEpoch; + } else if (maximum != null) { + range.maximum = maximum!.millisecondsSinceEpoch; + } + + if (range.minimum == range.maximum) { + range.minimum -= defaultTimeStamp; + range.maximum += defaultTimeStamp; + } + + return range.copyWith(); + } + + @override + DoubleRange defaultRange() => DoubleRange( + DateTime(1970, 2).millisecondsSinceEpoch, + DateTime(1970, 6).millisecondsSinceEpoch, + ); + + @override + num calculateActualInterval(DoubleRange range, Size availableSize) { + return calculateNiceInterval(range.delta, availableSize); + } + + @override + num calculateNiceInterval(num delta, Size availableSize) { + if (intervalType == DateTimeIntervalType.auto) { + final num typeBasedInterval = _calculateIntervalAndType( + delta, + availableSize, + ); + return interval ?? typeBasedInterval; + } + + _visibleIntervalType = intervalType; + return interval ?? + super + .calculateNiceInterval(_calculateTypeInterval(delta), availableSize) + .ceil(); + } + + num _calculateTypeInterval(num delta) { + const int perDay = 24 * 60 * 60 * 1000; + const num hours = 24, minutes = 60, seconds = 60, milliseconds = 1000; + final num totalDays = (delta / perDay).abs(); + switch (visibleIntervalType) { + case DateTimeIntervalType.years: + return totalDays / 365; + + case DateTimeIntervalType.months: + return totalDays / 30; + + case DateTimeIntervalType.days: + return totalDays; + + case DateTimeIntervalType.hours: + return totalDays * hours; + + case DateTimeIntervalType.minutes: + return totalDays * hours * minutes; + + case DateTimeIntervalType.seconds: + return totalDays * hours * minutes * seconds; + + case DateTimeIntervalType.milliseconds: + return totalDays * hours * minutes * seconds * milliseconds; + + case DateTimeIntervalType.auto: + return 1; + } + } + + num _calculateIntervalAndType(num delta, Size availableSize) { + const int perDay = 24 * 60 * 60 * 1000; + const num hours = 24, minutes = 60, seconds = 60, milliseconds = 1000; + final num totalDays = (delta / perDay).abs(); + + // For years. + num niceInterval = super.calculateNiceInterval( + totalDays / 365, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.years; + return niceInterval.floor(); + } + + // For months. + niceInterval = super.calculateNiceInterval(totalDays / 30, availableSize); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.months; + return niceInterval.floor(); + } + + // For days. + niceInterval = super.calculateNiceInterval(totalDays, availableSize); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.days; + return niceInterval.floor(); + } + + // For hours. + niceInterval = super.calculateNiceInterval( + totalDays * hours, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.hours; + return niceInterval.floor(); + } + + // For minutes. + niceInterval = super.calculateNiceInterval( + totalDays * hours * minutes, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.minutes; + return niceInterval.floor(); + } + + // For seconds. + niceInterval = super.calculateNiceInterval( + totalDays * hours * minutes * seconds, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.seconds; + return niceInterval.floor(); + } + + // For milliseconds. + niceInterval = super.calculateNiceInterval( + totalDays * hours * minutes * seconds * milliseconds, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.milliseconds; + return niceInterval.floor(); + } + + return niceInterval.ceil(); + } + + @override + DoubleRange updateAutoScrollingDelta( + int scrollingDelta, + DoubleRange actualRange, + DoubleRange visibleRange, + ) { + if (initialVisibleMaximum != null || initialVisibleMinimum != null) { + return visibleRange; + } + final DateTimeIntervalType intervalType = + autoScrollingDeltaType == DateTimeIntervalType.auto + ? _visibleIntervalType + : autoScrollingDeltaType; + DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( + visibleRange.maximum.toInt(), + ); + switch (intervalType) { + case DateTimeIntervalType.years: + dateTime = DateTime( + dateTime.year - autoScrollingDelta!, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + dateTime.millisecond, + dateTime.microsecond, + ); + scrollingDelta = + visibleRange.maximum.toInt() - dateTime.millisecondsSinceEpoch; + break; + case DateTimeIntervalType.months: + dateTime = DateTime( + dateTime.year, + dateTime.month - autoScrollingDelta!, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + dateTime.millisecond, + dateTime.microsecond, + ); + scrollingDelta = + visibleRange.maximum.toInt() - dateTime.millisecondsSinceEpoch; + break; + case DateTimeIntervalType.days: + scrollingDelta = Duration(days: autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.hours: + scrollingDelta = Duration(hours: autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.minutes: + scrollingDelta = Duration(minutes: autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.seconds: + scrollingDelta = Duration(seconds: autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.milliseconds: + scrollingDelta = + Duration(milliseconds: autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.auto: + scrollingDelta = autoScrollingDelta!; + break; + } + return super.updateAutoScrollingDelta( + scrollingDelta, + actualRange, + visibleRange, + ); + } + + @override + DoubleRange applyRangePadding( + DoubleRange range, + num interval, + Size availableSize, + ) { + if (minimum == null && maximum == null) { + final ChartRangePadding padding = effectiveRangePadding(); + if (padding == ChartRangePadding.additional || + padding == ChartRangePadding.additionalStart || + padding == ChartRangePadding.additionalEnd) { + _addAdditionalRange(range, interval.toInt()); + } else if (padding == ChartRangePadding.round || + padding == ChartRangePadding.roundStart || + padding == ChartRangePadding.roundEnd) { + _roundRange(range, interval.toInt()); + } + } + + return range; + } + + void _addAdditionalRange(DoubleRange range, int interval) { + switch (visibleIntervalType) { + case DateTimeIntervalType.years: + _addAdditionalYear(range, interval); + break; + + case DateTimeIntervalType.months: + _addAdditionalMonth(range, interval); + break; + + case DateTimeIntervalType.days: + _addAdditionalDays(range, interval); + break; + + case DateTimeIntervalType.hours: + _addAdditionalHours(range, interval); + break; + + case DateTimeIntervalType.minutes: + _addAdditionalMinutes(range, interval); + break; + + case DateTimeIntervalType.seconds: + _addAdditionalSeconds(range, interval); + break; + + case DateTimeIntervalType.milliseconds: + _addAdditionalMilliseconds(range, interval); + break; + + case DateTimeIntervalType.auto: + break; + } + } + + void _addAdditionalYear(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startYear = startDate.year; + final int endYear = endDate.year; + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalStart) { + range.minimum = DateTime(startYear - interval).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = DateTime(endYear + interval).millisecondsSinceEpoch; + } + } + + void _addAdditionalMonth(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startMonth = startDate.month; + final int endMonth = endDate.month; + + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalStart) { + range.minimum = + DateTime( + startDate.year, + startMonth - interval, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = + // TODO(Natrayan): Revisit the end month calculation. + DateTime( + endDate.year, + endMonth + interval, + endMonth == 2 ? 28 : 30, + ).millisecondsSinceEpoch; + } + } + + void _addAdditionalDays(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startDay = startDate.day; + final int endDay = endDate.day; + if (rangePadding == ChartRangePadding.additionalStart || + rangePadding == ChartRangePadding.additional) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDay - interval, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDay + interval, + ).millisecondsSinceEpoch; + } + } + + void _addAdditionalHours(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startHour = ((startDate.hour / interval) * interval).toInt(); + final int endHour = endDate.hour + (startDate.hour - startHour); + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startHour - interval, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + endHour + interval, + ).millisecondsSinceEpoch; + } + } + + void _addAdditionalMinutes(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startMinute = ((startDate.minute / interval) * interval).toInt(); + final int endMinute = endDate.minute + (startDate.minute - startMinute); + if (rangePadding == ChartRangePadding.additionalStart || + rangePadding == ChartRangePadding.additional) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startMinute - interval, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + endDate.hour, + endMinute + interval, + ).millisecondsSinceEpoch; + } + } + + void _addAdditionalSeconds(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startSecond = ((startDate.second / interval) * interval).toInt(); + final int endSecond = endDate.second + (startDate.second - startSecond); + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + startSecond - interval, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + endDate.hour, + endDate.minute, + endSecond + interval, + ).millisecondsSinceEpoch; + } + } + + void _addAdditionalMilliseconds(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startMilliSecond = + ((startDate.millisecond / interval) * interval).toInt(); + final int endMilliSecond = + endDate.millisecond + (startDate.millisecond - startMilliSecond); + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + startDate.second, + startMilliSecond - interval, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.additionalEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + endDate.hour, + endDate.minute, + endDate.second, + endMilliSecond + interval, + ).millisecondsSinceEpoch; + } + } + + void _roundRange(DoubleRange range, int interval) { + switch (visibleIntervalType) { + case DateTimeIntervalType.years: + _roundYears(range, interval); + break; + + case DateTimeIntervalType.months: + _roundMonths(range, interval); + break; + + case DateTimeIntervalType.days: + _roundDays(range, interval); + break; + + case DateTimeIntervalType.hours: + _roundHours(range, interval); + break; + + case DateTimeIntervalType.minutes: + _roundMinutes(range, interval); + break; + + case DateTimeIntervalType.seconds: + _roundSeconds(range, interval); + break; + + case DateTimeIntervalType.milliseconds: + _roundMilliseconds(range, interval); + break; + + case DateTimeIntervalType.auto: + break; + } + } + + void _roundYears(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startYear = startDate.year; + final int endYear = endDate.year; + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = DateTime(startYear, 0, 0).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime(endYear, 11, 30, 23, 59, 59).millisecondsSinceEpoch; + } + } + + void _roundMonths(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startMonth = startDate.month; + final int endMonth = endDate.month; + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = + DateTime(startDate.year, startMonth, 0).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime( + endDate.year, + endMonth, + DateTime(endDate.year, endDate.month, 0).day, + 23, + 59, + 59, + ).millisecondsSinceEpoch; + } + } + + void _roundDays(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startDay = startDate.day; + final int endDay = endDate.day; + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDay, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDay, + 23, + 59, + 59, + ).millisecondsSinceEpoch; + } + } + + void _roundHours(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startHour = ((startDate.hour / interval) * interval).toInt(); + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startHour, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + startHour, + 59, + 59, + ).millisecondsSinceEpoch; + } + } + + void _roundMinutes(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startMinute = ((startDate.minute / interval) * interval).toInt(); + final int endMinute = endDate.minute + (startDate.minute - startMinute); + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startMinute, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + endDate.hour, + endMinute, + 59, + ).millisecondsSinceEpoch; + } + } + + void _roundSeconds(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startSecond = ((startDate.second / interval) * interval).toInt(); + final int endSecond = endDate.second + (startDate.second - startSecond); + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + startSecond, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + endSecond, + ).millisecondsSinceEpoch; + } + } + + void _roundMilliseconds(DoubleRange range, int interval) { + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + range.minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + range.maximum.toInt(), + ); + final int startMilliSecond = + ((startDate.millisecond / interval) * interval).toInt(); + final int endMilliSecond = + endDate.millisecond + (startDate.millisecond - startMilliSecond); + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundStart) { + range.minimum = + DateTime( + startDate.year, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + startDate.second, + startMilliSecond, + ).millisecondsSinceEpoch; + } + if (rangePadding == ChartRangePadding.round || + rangePadding == ChartRangePadding.roundEnd) { + range.maximum = + DateTime( + endDate.year, + endDate.month, + endDate.day, + endDate.hour, + endDate.minute, + endDate.second, + endMilliSecond, + ).millisecondsSinceEpoch; + } + } + + @override + void generateVisibleLabels() { + hasTrimmedAxisLabel = false; + if (visibleRange == null || visibleInterval == 0) { + return; + } + + final double extent = maximumLabelWidth ?? double.maxFinite; + final bool isRtl = textDirection == TextDirection.rtl; + num current = visibleRange!.minimum; + current = _niceStart(current); + num previous = current; + final num visibleMinimum = visibleRange!.minimum; + final num visibleMaximum = visibleRange!.maximum; + while (current <= visibleMaximum) { + if (current < visibleMinimum || + !effectiveVisibleRange!.contains(current)) { + current = + _nextDate( + current, + visibleInterval, + visibleIntervalType, + ).millisecondsSinceEpoch; + continue; + } + + final DateFormat niceDateFormat = + dateFormat ?? + dateTimeAxisLabelFormat(this, current, previous.toInt()); + String text = niceDateFormat.format( + DateTime.fromMillisecondsSinceEpoch(current.toInt()), + ); + if (labelFormat != null && labelFormat != '') { + text = labelFormat!.replaceAll(RegExp('{value}'), text); + } + + String callbackText = text; + TextStyle callbackTextStyle = chartThemeData!.axisLabelTextStyle!.merge( + labelStyle, + ); + if (axisLabelFormatter != null) { + final AxisLabelRenderDetails details = AxisLabelRenderDetails( + current, + callbackText, + callbackTextStyle, + this, + visibleIntervalType, + text, + ); + final ChartAxisLabel label = axisLabelFormatter!(details); + callbackText = label.text; + callbackTextStyle = callbackTextStyle.merge(label.textStyle); + } + + Size textSize = measureText(callbackText, callbackTextStyle, 0); + String textAfterTrimming = callbackText; + if (extent.isFinite && textSize.width > extent) { + textAfterTrimming = trimmedText( + callbackText, + callbackTextStyle, + extent, + labelRotation, + isRtl: isRtl, + ); + } + + textSize = measureText( + textAfterTrimming, + callbackTextStyle, + labelRotation, + ); + final bool isTextTrimmed = callbackText != textAfterTrimming; + final AxisLabel label = AxisLabel( + callbackTextStyle, + textSize, + callbackText, + current, + isTextTrimmed ? textAfterTrimming : null, + textAfterTrimming, + ); + visibleLabels.add(label); + previous = current; + if (isTextTrimmed) { + hasTrimmedAxisLabel = true; + } + current = + _nextDate( + current, + visibleInterval, + visibleIntervalType, + ).millisecondsSinceEpoch; + if (previous == current) { + return; + } + } + + super.generateVisibleLabels(); + } + + int _niceStart(num startDate) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(startDate.toInt()); + switch (visibleIntervalType) { + case DateTimeIntervalType.years: + final int year = + ((date.year / visibleInterval).floor() * visibleInterval).floor(); + date = DateTime(year, date.month, date.day); + break; + + case DateTimeIntervalType.months: + final int month = + ((date.month / visibleInterval) * visibleInterval).floor(); + date = DateTime(date.year, month, date.day); + break; + + case DateTimeIntervalType.days: + final int day = + ((date.day / visibleInterval) * visibleInterval).floor(); + date = DateTime(date.year, date.month, day); + break; + + case DateTimeIntervalType.hours: + final int hour = + ((date.hour / visibleInterval).floor() * visibleInterval).floor(); + date = DateTime(date.year, date.month, date.day, hour); + break; + + case DateTimeIntervalType.minutes: + final int minute = + ((date.minute / visibleInterval).floor() * visibleInterval).floor(); + date = DateTime(date.year, date.month, date.day, date.hour, minute); + break; + + case DateTimeIntervalType.seconds: + final int second = + ((date.second / visibleInterval).floor() * visibleInterval).floor(); + date = DateTime( + date.year, + date.month, + date.day, + date.hour, + date.minute, + second, + ); + break; + + case DateTimeIntervalType.milliseconds: + final int millisecond = + ((date.millisecond / visibleInterval).floor() * visibleInterval) + .floor(); + date = DateTime( + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second, + millisecond, + ); + break; + + case DateTimeIntervalType.auto: + break; + } + + return date.millisecondsSinceEpoch; + } + + DateTime _nextDate( + num current, + num interval, + DateTimeIntervalType intervalType, + ) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(current.toInt()); + final bool hasDecimalInterval = interval % 1 == 0; + if (hasDecimalInterval) { + final int effectiveInterval = interval.floor(); + switch (intervalType) { + case DateTimeIntervalType.years: + return date = DateTime( + date.year + effectiveInterval, + date.month, + date.day, + date.hour, + date.minute, + date.second, + ); + + case DateTimeIntervalType.months: + return date = DateTime( + date.year, + date.month + effectiveInterval, + date.day, + date.hour, + date.minute, + date.second, + ); + + case DateTimeIntervalType.days: + return date.add(Duration(days: effectiveInterval)); + + case DateTimeIntervalType.hours: + return date.add(Duration(hours: effectiveInterval)); + + case DateTimeIntervalType.minutes: + return date.add(Duration(minutes: effectiveInterval)); + + case DateTimeIntervalType.seconds: + return date.add(Duration(seconds: effectiveInterval)); + + case DateTimeIntervalType.milliseconds: + return date.add(Duration(milliseconds: effectiveInterval)); + + case DateTimeIntervalType.auto: + break; + } + } else { + switch (intervalType) { + case DateTimeIntervalType.years: + return date = DateTime( + date.year, + date.month + (interval * 12).floor(), + date.day, + date.hour, + date.minute, + date.second, + ); + + case DateTimeIntervalType.months: + return date.add(Duration(days: (interval * 30).floor())); + + case DateTimeIntervalType.days: + return date.add(Duration(hours: (interval * 24).floor())); + + case DateTimeIntervalType.hours: + return date.add(Duration(minutes: (interval * 60).floor())); + + case DateTimeIntervalType.minutes: + return date.add(Duration(seconds: (interval * 60).floor())); + + case DateTimeIntervalType.seconds: + return date.add(Duration(seconds: (interval * 1000).floor())); + + case DateTimeIntervalType.milliseconds: + return date.add(Duration(milliseconds: interval.floor())); + + case DateTimeIntervalType.auto: + break; + } + } + return date; + } + + @override + void calculateTickPositions( + LabelPlacement placement, { + List? source, + bool canCalculateMinorTick = false, + bool canCalculateMajorTick = true, + }) { + final int length = visibleLabels.length; + if (length == 0) { + return; + } + + final bool isBetweenTicks = placement == LabelPlacement.betweenTicks; + for (int i = 0; i < length; i++) { + final bool hasNextLabel = length - 1 > i; + num current = visibleLabels[i].value; + if (isBetweenTicks) { + final num nextValue = + hasNextLabel ? visibleLabels[i + 1].value : visibleRange!.maximum; + current = (current + nextValue) / 2; + } + + source!.add(pointToPixel(current)); + + if (canCalculateMinorTick) { + final num start = current; + final num end = + hasNextLabel ? visibleLabels[i + 1].value : visibleRange!.maximum; + final double minorTickInterval = + (end - start) / (minorTicksPerInterval + 1); + for (int j = 1; j <= minorTicksPerInterval; j++) { + final double tickValue = start + minorTickInterval * j; + if (tickValue < end && tickValue < visibleRange!.maximum) { + minorTickPositions.add(pointToPixel(tickValue)); + } + } + } + } + } + + @override + num plotBandExtent(PlotBand plotBand, num current, num size) { + if (plotBand.isRepeatable) { + DateTimeIntervalType sizeType = plotBand.sizeType; + if (plotBand.sizeType == DateTimeIntervalType.auto) { + sizeType = visibleIntervalType; + } + return _nextDate(current, size, sizeType).millisecondsSinceEpoch; + } else { + return super.plotBandExtent(plotBand, current, size); + } + } + + @override + void generateMultiLevelLabels() { + _multilevelLabels.clear(); + visibleMultilevelLabels.clear(); + + // TODO(Natrayansf): Move actualMultilevelLabels to property setter. + final int length = multiLevelLabels?.length ?? 0; + if (length == 0) { + return; + } + + for (int i = 0; i < length; i++) { + final DateTimeMultiLevelLabel label = multiLevelLabels![i]; + assert( + label.start.millisecondsSinceEpoch <= label.end.millisecondsSinceEpoch, + ); + _multilevelLabels.add( + AxisMultilevelLabel( + label.text, + label.level, + label.start.millisecondsSinceEpoch, + label.end.millisecondsSinceEpoch, + ), + ); + } + + _multilevelLabels.sort( + (AxisMultilevelLabel a, AxisMultilevelLabel b) => + a.level.compareTo(b.level), + ); + + final void Function(AxisMultilevelLabel label) add = + invertElementsOrder + ? (AxisMultilevelLabel label) => + visibleMultilevelLabels.insert(0, label) + : (AxisMultilevelLabel label) => visibleMultilevelLabels.add(label); + + final int labelsLength = _multilevelLabels.length; + final TextStyle textStyle = chartThemeData!.axisMultiLevelLabelTextStyle! + .merge(multiLevelLabelStyle.textStyle); + for (int i = 0; i < labelsLength; i++) { + final AxisMultilevelLabel current = _multilevelLabels[i]; + if (isLies(current.start, current.end, visibleRange!)) { + String desiredText = current.text; + TextStyle desiredTextStyle = textStyle; + if (multiLevelLabelFormatter != null) { + final MultiLevelLabelRenderDetails details = + MultiLevelLabelRenderDetails( + current.level, + desiredText, + desiredTextStyle, + i, + name, + ); + final ChartAxisLabel label = multiLevelLabelFormatter!(details); + desiredText = label.text; + desiredTextStyle = textStyle.merge(label.textStyle); + } + + current + ..actualTextSize = measureText(desiredText, desiredTextStyle, 0) + ..renderText = desiredText + ..style = desiredTextStyle; + add(current); + } + } + } + + @override + void updateMultiLevelLabels() { + late Rect Function(double start, double end, Size size) labelBounds; + labelBounds = isVertical ? _verticalLabelBounds : _horizontalLabelBounds; + + final bool isRtl = textDirection == TextDirection.rtl; + for (final AxisMultilevelLabel label in visibleMultilevelLabels) { + final double start = pointToPixel(label.start); + final double end = pointToPixel(label.end); + final double extent = (end - start - textPadding).abs(); + + String renderText = label.renderText; + final TextStyle style = label.style; + if (label.actualTextSize.width > extent) { + renderText = trimmedText(renderText, style, extent, 0, isRtl: isRtl); + } + + // TODO(Natrayansf): Set only when trimming is done. + label.trimmedText = renderText; + label + ..transformStart = start + ..transformEnd = end + ..region = labelBounds(start, end, measureText(renderText, style, 0)); + } + } + + Rect _horizontalLabelBounds(double start, double end, Size labelSize) { + double height = labelSize.height; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + height += 2 * textPaddingOfCurlyBrace; + } else { + height += 2 * textPadding; + } + + return Rect.fromLTRB(start, 0, end, height); + } + + Rect _verticalLabelBounds(double start, double end, Size labelSize) { + double width = labelSize.width; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + width += 2 * textPaddingOfCurlyBrace; + } else { + width += 2 * textPadding; + } + + return Rect.fromLTRB(0, start, width, end); + } + + @override + void dispose() { + _multilevelLabels.clear(); + controller.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart new file mode 100644 index 000000000..ec00cdda2 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart @@ -0,0 +1,1181 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' hide TextDirection; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'axis.dart'; +import 'multi_level_labels.dart'; +import 'plot_band.dart'; + +/// 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. + const DateTimeCategoryAxis({ + super.key, + super.name, + super.isVisible = true, + super.title, + super.axisLine, + super.rangePadding, + super.edgeLabelPlacement, + super.labelPosition, + super.tickPosition, + super.labelRotation, + super.labelIntersectAction, + super.labelAlignment, + super.isInversed, + super.opposedPosition, + super.maximumLabels, + super.majorTickLines, + super.majorGridLines, + super.labelStyle, + super.plotOffset, + super.plotOffsetStart, + super.plotOffsetEnd, + super.initialZoomFactor, + super.initialZoomPosition, + super.interactiveTooltip, + this.minimum, + this.maximum, + super.interval, + this.initialVisibleMinimum, + this.initialVisibleMaximum, + super.crossesAt, + super.associatedAxisName, + super.placeLabelsNearAxisLine, + super.plotBands, + super.desiredIntervals, + super.rangeController, + super.maximumLabelWidth, + super.labelsExtent, + this.labelPlacement = LabelPlacement.betweenTicks, + this.dateFormat, + this.intervalType = DateTimeIntervalType.auto, + this.autoScrollingDeltaType = DateTimeIntervalType.auto, + super.autoScrollingDelta, + super.borderWidth, + super.borderColor, + super.axisBorderType, + super.multiLevelLabelStyle, + super.multiLevelLabelFormatter, + this.multiLevelLabels, + super.autoScrollingMode, + super.axisLabelFormatter, + this.onRendererCreated, + }) : assert( + (initialVisibleMaximum == null && initialVisibleMinimum == null) || + autoScrollingDelta == null, + 'Both properties have the same behavior to display the visible data points, use any one of the properties', + ); + + /// 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 is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: DateTimeCategoryAxis(initialVisibleMinimum: DateTime(2019)), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// minimum value dynamically. + /// + /// ```dart + /// DateTimeCategoryAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis( + /// initialVisibleMinimum: DateTime(2019), + /// onRendererCreated: (DateTimeCategoryAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = DateTime(2017); + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final DateTime? initialVisibleMinimum; + + /// The maximum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: DateTimeCategoryAxis(initialVisibleMaximum: DateTime(2020)), + /// )); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// maximum value dynamically + /// + /// ```dart + /// DateTimeCategoryAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: DateTimeAxis( + /// initialVisibleMaximum: DateTime(2020), + /// onRendererCreated: (DateTimeCategoryAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMaximum = DateTime(2024); + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final DateTime? initialVisibleMaximum; + + /// 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; + + /// Provides the option to group the axis labels. You can customize the start, + /// end value of a multi-level label, text, and level of + /// the multi-level labels. + /// + /// The `start` and `end` values for the category axis need to be string type, + /// in the case of date-time or date-time category axes need to be date-time + /// and in the case of numeric or logarithmic axes needs to be double. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis( + /// multiLevelLabels: [ + /// DateTimeCategoricalMultiLevelLabel( + /// start: DateTime(2010, 1, 1), + /// end: DateTime(2010, 2, 1), + /// text: 'First' + /// ), + /// DateTimeCategoricalMultiLevelLabel( + /// start: DateTime(2011, 1, 1), + /// end: DateTime(2011, 2, 1), + /// text: 'Second' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final List? multiLevelLabels; + + final Function(DateTimeCategoryAxisController)? onRendererCreated; + + @override + RenderDateTimeCategoryAxis createRenderer() { + return RenderDateTimeCategoryAxis(); + } + + @override + RenderDateTimeCategoryAxis createRenderObject(BuildContext context) { + final RenderDateTimeCategoryAxis renderer = + super.createRenderObject(context) as RenderDateTimeCategoryAxis; + renderer + ..labelPlacement = labelPlacement + ..dateFormat = dateFormat + ..intervalType = intervalType + ..minimum = minimum + ..maximum = maximum + ..initialVisibleMinimum = initialVisibleMinimum + ..initialVisibleMaximum = initialVisibleMaximum + ..autoScrollingDeltaType = autoScrollingDeltaType + ..multiLevelLabels = multiLevelLabels + ..onRendererCreated = onRendererCreated; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RenderDateTimeCategoryAxis renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..labelPlacement = labelPlacement + ..dateFormat = dateFormat + ..intervalType = intervalType + ..minimum = minimum + ..maximum = maximum + ..autoScrollingDeltaType = autoScrollingDeltaType + ..multiLevelLabels = multiLevelLabels; + } +} + +class RenderDateTimeCategoryAxis extends RenderChartAxis { + final List labels = []; + final List _multilevelLabels = []; + + DateTimeIntervalType get visibleIntervalType => _visibleIntervalType; + DateTimeIntervalType _visibleIntervalType = DateTimeIntervalType.auto; + + @override + DateTimeCategoryAxisController get controller => _controller; + late final DateTimeCategoryAxisController _controller = + DateTimeCategoryAxisController(this); + + @override + @nonVirtual + bool get canAnimate => + super.canAnimate || + (initialVisibleMinimum != null && initialVisibleMaximum != null); + + DateFormat? get dateFormat => _dateFormat; + DateFormat? _dateFormat; + set dateFormat(DateFormat? value) { + if (_dateFormat != value) { + _dateFormat = value; + markNeedsLayout(); + } + } + + DateTimeIntervalType get intervalType => _intervalType; + DateTimeIntervalType _intervalType = DateTimeIntervalType.auto; + set intervalType(DateTimeIntervalType value) { + if (_intervalType != value) { + _intervalType = value; + markNeedsLayout(); + } + } + + DateTime? get minimum => _minimum; + DateTime? _minimum; + set minimum(DateTime? value) { + if (_minimum != value) { + _minimum = value; + markNeedsRangeUpdate(); + } + } + + DateTime? get maximum => _maximum; + DateTime? _maximum; + set maximum(DateTime? value) { + if (_maximum != value) { + _maximum = value; + markNeedsRangeUpdate(); + } + } + + DateTime? get initialVisibleMinimum => _initialVisibleMinimum; + DateTime? _initialVisibleMinimum; + set initialVisibleMinimum(DateTime? value) { + if (_initialVisibleMinimum == value) { + return; + } + _initialVisibleMinimum = value; + if (rangeController != null && rangeController!.start != null) { + _updateVisibleMinMax(min: rangeController!.start); + } else { + _updateVisibleMinMax(min: initialVisibleMinimum); + } + } + + DateTime? get initialVisibleMaximum => _initialVisibleMaximum; + DateTime? _initialVisibleMaximum; + set initialVisibleMaximum(DateTime? value) { + if (_initialVisibleMaximum == value) { + return; + } + _initialVisibleMaximum = value; + if (rangeController != null && rangeController!.end != null) { + _updateVisibleMinMax(max: rangeController!.end); + } else { + _updateVisibleMinMax(max: initialVisibleMaximum); + } + } + + DateTimeIntervalType get autoScrollingDeltaType => _autoScrollingDeltaType; + DateTimeIntervalType _autoScrollingDeltaType = DateTimeIntervalType.auto; + set autoScrollingDeltaType(DateTimeIntervalType value) { + if (_autoScrollingDeltaType != value) { + _autoScrollingDeltaType = value; + markNeedsLayout(); + } + } + + List? get multiLevelLabels => + _multiLevelLabels; + List? _multiLevelLabels; + set multiLevelLabels(List? value) { + if (_multiLevelLabels != value) { + _multiLevelLabels = value; + markNeedsLayout(); + } + } + + @override + set rangeController(RangeController? value) { + super.rangeController = value; + if (value == null) { + return; + } + assert(rangeController!.start is DateTime); + assert(rangeController!.end is DateTime); + _updateVisibleMinMax(min: value.start, max: value.end); + } + + Function(DateTimeCategoryAxisController)? get onRendererCreated => + _onRendererCreated; + Function(DateTimeCategoryAxisController)? _onRendererCreated; + set onRendererCreated(Function(DateTimeCategoryAxisController)? value) { + if (_onRendererCreated != value) { + _onRendererCreated = value; + } + } + + @override + num actualValue(Object value) { + if (value is num) { + return super.actualValue(value); + } + + assert(value.runtimeType == DateTime); + return super.actualValue(effectiveValue(value as DateTime)!); + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(controller); + rangeController?.addListener(_handleRangeControllerChange); + super.attach(owner); + } + + @override + void detach() { + rangeController?.removeListener(_handleRangeControllerChange); + super.detach(); + } + + void _handleRangeControllerChange() { + assert(rangeController != null); + dynamic start = rangeController!.start; + dynamic end = rangeController!.end; + if (rangeController!.start is! DateTime) { + start = DateTime.fromMillisecondsSinceEpoch( + labels[(rangeController!.start as num).toInt()], + ); + } + if (rangeController!.end is! DateTime) { + end = DateTime.fromMillisecondsSinceEpoch( + labels[(rangeController!.end as num).toInt()], + ); + } + _updateVisibleMinMax(min: start, max: end); + } + + void _updateVisibleMinMax({DateTime? min, DateTime? max}) { + if (min != null) { + controller.visibleMinimum = min; + } + if (max != null) { + controller.visibleMaximum = max; + } + } + + @override + DoubleRange calculateActualRange() { + if (minimum != null && maximum != null) { + return DoubleRange( + effectiveValue(minimum)!, + effectiveValue(maximum, needMin: false)!, + ); + } + + final DoubleRange range = super.calculateActualRange(); + if (minimum != null) { + range.minimum = effectiveValue(minimum)!; + } else if (maximum != null) { + range.maximum = effectiveValue(maximum, needMin: false)!; + } + + if (range.minimum == range.maximum && + labelPlacement == LabelPlacement.onTicks) { + range.maximum += 1; + } + + return range.copyWith(); + } + + @override + DoubleRange updateAutoScrollingDelta( + int scrollingDelta, + DoubleRange actualRange, + DoubleRange visibleRange, + ) { + if (initialVisibleMaximum != null || initialVisibleMinimum != null) { + return visibleRange; + } + return super.updateAutoScrollingDelta( + scrollingDelta, + actualRange, + visibleRange, + ); + } + + @override + DoubleRange defaultRange() => DoubleRange.zero(); + + @override + num calculateActualInterval(DoubleRange range, Size availableSize) { + return calculateNiceInterval(range.delta, availableSize); + } + + @override + num calculateNiceInterval(num delta, Size availableSize) { + if (intervalType == DateTimeIntervalType.auto) { + if (labels.isNotEmpty) { + _calculateIntervalAndType(labels.first, labels.last, availableSize); + } else { + _visibleIntervalType = DateTimeIntervalType.days; + } + + return interval ?? + max(1, super.calculateNiceInterval(delta, availableSize)); + } + + _visibleIntervalType = intervalType; + return interval ?? + max(1, super.calculateNiceInterval(delta, availableSize)); + } + + num _calculateIntervalAndType(num minimum, num maximum, Size availableSize) { + const int perDay = 24 * 60 * 60 * 1000; + const num hours = 24, minutes = 60, seconds = 60, milliseconds = 1000; + final DateTime startDate = DateTime.fromMillisecondsSinceEpoch( + minimum.toInt(), + ); + final DateTime endDate = DateTime.fromMillisecondsSinceEpoch( + maximum.toInt(), + ); + final num totalDays = + ((endDate.millisecondsSinceEpoch - startDate.millisecondsSinceEpoch) / + perDay) + .abs(); + + // For years. + num niceInterval = super.calculateNiceInterval( + totalDays / 365, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.years; + return niceInterval.floor(); + } + + // For months. + niceInterval = super.calculateNiceInterval(totalDays / 30, availableSize); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.months; + return niceInterval.floor(); + } + + // For days. + niceInterval = super.calculateNiceInterval(totalDays, availableSize); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.days; + return niceInterval.floor(); + } + + // For hours. + niceInterval = super.calculateNiceInterval( + totalDays * hours, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.hours; + return niceInterval.floor(); + } + + // For minutes. + niceInterval = super.calculateNiceInterval( + totalDays * hours * minutes, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.minutes; + return niceInterval.floor(); + } + + // For seconds. + niceInterval = super.calculateNiceInterval( + totalDays * hours * minutes * seconds, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.seconds; + return niceInterval.floor(); + } + + // For milliseconds. + niceInterval = super.calculateNiceInterval( + totalDays * hours * minutes * seconds * milliseconds, + availableSize, + ); + if (niceInterval >= 1) { + _visibleIntervalType = DateTimeIntervalType.milliseconds; + return niceInterval.floor(); + } + + return niceInterval.ceil(); + } + + @override + DoubleRange applyRangePadding( + DoubleRange range, + num interval, + Size availableSize, + ) { + if (labelPlacement == LabelPlacement.betweenTicks) { + range.minimum -= 0.5; + range.maximum += 0.5; + } + + if (range.minimum == range.maximum) { + return _handleEqualRange(range); + } + + if (minimum == null || maximum == null) { + range = super.applyRangePadding(range, interval, availableSize); + + if (minimum != null) { + range.minimum = effectiveValue(minimum)!; + if (labelPlacement == LabelPlacement.onTicks) { + range.minimum -= 0.5; + } + } + if (maximum != null) { + range.maximum = effectiveValue(maximum, needMin: false)!; + if (labelPlacement == LabelPlacement.onTicks) { + range.maximum += 0.5; + } + } + } + + if (range.minimum == range.maximum) { + return _handleEqualRange(range); + } + + return range.copyWith(); + } + + DoubleRange _handleEqualRange(DoubleRange range) { + if (labelPlacement == LabelPlacement.onTicks) { + return DoubleRange(range.minimum, range.maximum + 1); + } else if (labelPlacement == LabelPlacement.betweenTicks) { + return DoubleRange(range.minimum - 0.5, range.maximum + 0.5); + } + + return range; + } + + @override + void addNormalRange(DoubleRange range, num interval, Size availableSize) { + if (labelPlacement == LabelPlacement.onTicks) { + range.minimum -= 0.5; + range.maximum += 0.5; + if (range.minimum == 0) { + updateNormalRangePadding(range, availableSize); + } + } else { + super.addNormalRange(range, interval, availableSize); + } + } + + @override + void generateVisibleLabels() { + hasTrimmedAxisLabel = false; + if (visibleRange == null || visibleInterval == 0) { + return; + } + + final double extent = maximumLabelWidth ?? double.maxFinite; + final bool isRtl = textDirection == TextDirection.rtl; + num niceInterval = visibleInterval; + final List split = niceInterval.toString().split('.'); + niceInterval = + split.length >= 2 + ? split[1].length == 1 && split[1] == '0' + ? niceInterval.floor() + : niceInterval.ceil() + : niceInterval; + final num visibleMinimum = visibleRange!.minimum; + final num visibleMaximum = visibleRange!.maximum; + num current = visibleMinimum.ceil(); + num previous = current; + final DateFormat niceDateTimeFormat = + dateFormat ?? + dateTimeCategoryAxisLabelFormat(this, current, previous.toInt()); + while (current <= visibleMaximum) { + if (current < visibleMinimum || + !effectiveVisibleRange!.contains(current)) { + current += niceInterval; + continue; + } + + String text = ''; + final int currentValue = current.round(); + if (currentValue <= -1 || + labels.isNotEmpty && currentValue >= labels.length) { + current += niceInterval; + continue; + } else if (labels.isNotEmpty) { + text = niceDateTimeFormat.format( + DateTime.fromMillisecondsSinceEpoch(labels[currentValue]), + ); + } else { + current += niceInterval; + continue; + } + + String callbackText = text; + TextStyle callbackTextStyle = chartThemeData!.axisLabelTextStyle!.merge( + labelStyle, + ); + if (axisLabelFormatter != null) { + final AxisLabelRenderDetails details = AxisLabelRenderDetails( + current, + callbackText, + callbackTextStyle, + this, + visibleIntervalType, + text, + ); + final ChartAxisLabel label = axisLabelFormatter!(details); + callbackText = label.text; + callbackTextStyle = callbackTextStyle.merge(label.textStyle); + } + + String textAfterTrimming = callbackText; + Size textSize = measureText( + callbackText, + callbackTextStyle, + labelRotation, + ); + if (extent.isFinite && textSize.width > extent) { + textAfterTrimming = trimmedText( + callbackText, + callbackTextStyle, + extent, + labelRotation, + isRtl: isRtl, + ); + } + + textSize = measureText( + textAfterTrimming, + callbackTextStyle, + labelRotation, + ); + final bool isTextTrimmed = callbackText != textAfterTrimming; + final AxisLabel label = AxisLabel( + callbackTextStyle, + textSize, + callbackText, + current, + isTextTrimmed ? textAfterTrimming : null, + textAfterTrimming, + ); + visibleLabels.add(label); + previous = current; + if (isTextTrimmed) { + hasTrimmedAxisLabel = true; + } + current += niceInterval; + } + + super.generateVisibleLabels(); + } + + @override + void calculateTickPositions( + LabelPlacement placement, { + List? source, + bool canCalculateMinorTick = false, + bool canCalculateMajorTick = true, + }) { + int length = visibleLabels.length; + if (length == 0) { + return; + } + + bool isBetweenTicks = placement == LabelPlacement.betweenTicks; + if (!isBetweenTicks && !canCalculateMajorTick) { + isBetweenTicks = !isBetweenTicks; + } + + final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; + length += isBetweenTicks ? 1 : 0; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + num current; + if (isBetweenTicks) { + if (i < lastIndex) { + current = visibleLabels[i].value - tickBetweenLabel; + } else { + final num gap = interval != null ? interval! : 1; + current = visibleLabels[i - 1].value + gap - tickBetweenLabel; + } + } else { + current = visibleLabels[i].value; + } + + source!.add(pointToPixel(current)); + } + } + + @override + void generatePlotBands() { + if (plotBands.isNotEmpty && + associatedAxis != null && + associatedAxis!.visibleRange != null) { + visiblePlotBands ??= []; + final int length = plotBands.length; + final Rect Function(PlotBand plotBand, num start, num end) bounds = + isVertical ? verticalPlotBandBounds : horizontalPlotBandBounds; + + for (int i = 0; i < length; i++) { + final PlotBand plotBand = plotBands[i]; + if (plotBand.isVisible) { + final dynamic actualStart = plotBand.start; + final dynamic actualEnd = plotBand.end; + final num min = + actualStart != null + ? _handleValueType(actualStart) + : visibleRange!.minimum; + num max = + actualEnd != null + ? _handleValueType(actualEnd) + : visibleRange!.maximum; + + num extent; + if (plotBand.isRepeatable) { + extent = plotBand.repeatEvery; + final dynamic actualRepeatUntil = plotBand.repeatUntil; + if (actualRepeatUntil != null) { + max = _handleValueType(actualRepeatUntil); + if (max > actualRange!.maximum) { + max = actualRange!.maximum; + } + } else { + max = actualRange!.maximum; + } + } else { + extent = max - min; + } + + num current = min; + if (plotBand.isRepeatable) { + while (current < max) { + current = formPlotBandFrame( + plotBand, + current, + extent, + max, + bounds, + ); + } + } else { + formPlotBandFrame(plotBand, current, extent, max, bounds); + } + } + } + } + } + + num _handleValueType(dynamic value) { + if (value is double) { + return actualValue(value); + } else if (value is num) { + return actualValue(indexToDateTime(value as int)); + } + return actualValue(value); + } + + // Convert index to DateTime, if the plot band start/end value is index. + @nonVirtual + DateTime indexToDateTime(int index) { + if (labels.isNotEmpty) { + final int length = labels.length; + if (index >= 0 && index <= length) { + return DateTime.fromMillisecondsSinceEpoch(labels[index]); + } else if (index > length) { + return DateTime.fromMillisecondsSinceEpoch(labels.last); + } else { + return DateTime.fromMillisecondsSinceEpoch(labels.first); + } + } + return DateTime.fromMillisecondsSinceEpoch(index); + } + + @override + void generateMultiLevelLabels() { + _multilevelLabels.clear(); + visibleMultilevelLabels.clear(); + + final int length = multiLevelLabels?.length ?? 0; + if (length == 0) { + return; + } + + for (int index = 0; index < length; index++) { + final DateTimeCategoricalMultiLevelLabel label = multiLevelLabels![index]; + _multilevelLabels.add( + AxisMultilevelLabel( + label.text, + label.level, + effectiveValue(label.start)!, + effectiveValue(label.end, needMin: false)!, + ), + ); + } + + _multilevelLabels.sort( + (AxisMultilevelLabel a, AxisMultilevelLabel b) => + a.level.compareTo(b.level), + ); + + final void Function(AxisMultilevelLabel label) add = + invertElementsOrder + ? (AxisMultilevelLabel label) => + visibleMultilevelLabels.insert(0, label) + : (AxisMultilevelLabel label) => visibleMultilevelLabels.add(label); + + final int labelsLength = _multilevelLabels.length; + final TextStyle textStyle = chartThemeData!.axisMultiLevelLabelTextStyle! + .merge(multiLevelLabelStyle.textStyle); + for (int i = 0; i < labelsLength; i++) { + final AxisMultilevelLabel current = _multilevelLabels[i]; + if (isLies(current.start, current.end, visibleRange!)) { + String desiredText = current.text; + TextStyle desiredTextStyle = textStyle; + if (multiLevelLabelFormatter != null) { + final MultiLevelLabelRenderDetails details = + MultiLevelLabelRenderDetails( + current.level, + desiredText, + desiredTextStyle, + i, + name, + ); + final ChartAxisLabel label = multiLevelLabelFormatter!(details); + desiredText = label.text; + desiredTextStyle = textStyle.merge(label.textStyle); + } + current + ..actualTextSize = measureText(desiredText, desiredTextStyle, 0) + ..renderText = desiredText + ..style = desiredTextStyle; + add(current); + } + } + } + + @override + void updateMultiLevelLabels() { + late Rect Function(double start, double end, Size size) labelBounds; + labelBounds = isVertical ? _verticalLabelBounds : _horizontalLabelBounds; + + final bool isRtl = textDirection == TextDirection.rtl; + final bool isBetweenTicks = labelPlacement == LabelPlacement.betweenTicks; + final num betweenTicksInterval = isBetweenTicks ? 0.5 : 0; + + for (final AxisMultilevelLabel label in visibleMultilevelLabels) { + final num startValue = label.start - betweenTicksInterval; + final num endValue = label.end + betweenTicksInterval; + final double start = pointToPixel(startValue); + final double end = pointToPixel(endValue); + final double extent = (end - start - textPadding).abs(); + + String renderText = label.renderText; + final TextStyle style = label.style; + if (label.actualTextSize.width > extent) { + renderText = trimmedText(renderText, style, extent, 0, isRtl: isRtl); + } + + label.trimmedText = renderText; + label + ..transformStart = start + ..transformEnd = end + ..region = labelBounds(start, end, measureText(renderText, style, 0)); + } + } + + Rect _horizontalLabelBounds(double start, double end, Size labelSize) { + double height = labelSize.height; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + height += 2 * textPaddingOfCurlyBrace; + } else { + height += 2 * textPadding; + } + + return Rect.fromLTRB(start, 0, end, height); + } + + Rect _verticalLabelBounds(double start, double end, Size labelSize) { + double width = labelSize.width; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + width += 2 * textPaddingOfCurlyBrace; + } else { + width += 2 * textPadding; + } + + return Rect.fromLTRB(0, start, width, end); + } + + int? effectiveValue(DateTime? rangeDate, {bool needMin = true}) { + if (rangeDate == null) { + return null; + } + + int index = 0; + final int rangeDateMs = rangeDate.millisecondsSinceEpoch; + for (final int label in labels) { + if (needMin) { + if (label > rangeDateMs) { + if (!(labels.first == label)) { + index++; + } + break; + } else if (label < rangeDateMs) { + index = labels.indexOf(label); + } else { + index = labels.indexOf(label); + break; + } + } else { + if (label <= rangeDateMs) { + index = labels.indexOf(label); + } + if (label >= rangeDateMs) { + break; + } + } + } + return index; + } + + // During sorting, always keep xValues as linear data. + void updateXValues() { + labels.clear(); + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer && + dependent.controller.isVisible) { + final List xRawValues = dependent.xRawValues; + final int length = xRawValues.length; + if (length > 0) { + const int minValue = 0; + int maxValue = 0; + for (int i = 0; i < length; i++) { + final int rawX = (xRawValues[i] as DateTime).millisecondsSinceEpoch; + if (!labels.contains(rawX)) { + labels.add(rawX); + } + + final int index = labels.indexOf(rawX); + dependent.xValues[i] = index; + maxValue = max(maxValue, index); + } + dependent.xMin = minValue; + dependent.xMax = maxValue; + } + } + } + } + + @override + void dispose() { + _multilevelLabels.clear(); + controller.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart new file mode 100644 index 000000000..6fe19ec21 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart @@ -0,0 +1,992 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show NumberFormat; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'axis.dart'; +import 'multi_level_labels.dart'; +import 'plot_band.dart'; + +/// Logarithmic axis uses logarithmic scale and displays numbers as axis labels. +/// +/// Provides options to customize the range of log axis, use the [minimum], +/// [maximum], and [interval] properties. By default, the range will be +/// calculated automatically based on the provided data. +/// +/// _Note:_ This is only applicable for [SfCartesianChart]. +class LogarithmicAxis extends ChartAxis { + /// Creating an argument constructor of [LogarithmicAxis] class. + const LogarithmicAxis({ + super.key, + super.name, + super.isVisible = true, + super.anchorRangeToVisiblePoints = true, + super.title, + super.axisLine, + super.labelIntersectAction, + super.labelRotation, + super.labelPosition, + super.tickPosition, + super.isInversed, + super.opposedPosition, + super.minorTicksPerInterval, + super.maximumLabels, + super.majorTickLines, + super.minorTickLines, + super.majorGridLines, + super.minorGridLines, + super.edgeLabelPlacement, + super.labelStyle, + super.plotOffset, + super.plotOffsetStart, + super.plotOffsetEnd, + super.initialZoomFactor, + super.initialZoomPosition, + super.enableAutoIntervalOnZooming, + super.interactiveTooltip, + this.minimum, + this.maximum, + super.interval, + this.logBase = 10, + this.labelFormat, + this.numberFormat, + this.initialVisibleMinimum, + this.initialVisibleMaximum, + super.labelAlignment, + super.crossesAt, + super.associatedAxisName, + super.placeLabelsNearAxisLine, + super.plotBands, + super.desiredIntervals, + super.rangeController, + super.maximumLabelWidth, + super.labelsExtent, + super.autoScrollingDelta, + super.borderWidth, + super.borderColor, + super.axisBorderType, + super.multiLevelLabelStyle, + super.multiLevelLabelFormatter, + this.multiLevelLabels, + super.autoScrollingMode, + super.axisLabelFormatter, + this.onRendererCreated, + }) : assert( + (initialVisibleMaximum == null && initialVisibleMinimum == null) || + autoScrollingDelta == null, + 'Both properties have the same behavior to display the visible data points, use any one of the properties', + ); + + /// Formats the numeric axis labels. + /// + /// The labels can be customized by adding desired text as prefix or suffix. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: LogarithmicAxis(labelFormat: '{value}M'), + /// ) + /// ); + /// } + /// ``` + final String? labelFormat; + + /// Formats the logarithmic axis labels with globalized label formats. + /// + /// Provides the ability to format a number in a locale-specific way. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// LogarithmicAxis(numberFormat: NumberFormat.compactCurrency()), + /// ) + /// ); + /// } + /// ``` + final NumberFormat? numberFormat; + + /// The minimum value of the axis. + /// + /// The axis will start from this value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: LogarithmicAxis(minimum: 0), + /// ) + /// ); + /// } + /// ``` + final double? minimum; + + /// The maximum value of the axis. + /// The axis will end at this value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: LogarithmicAxis(maximum: 10), + /// ) + /// ); + /// } + /// ``` + final double? maximum; + + /// The base value for logarithmic axis. + /// The axis label will render this base value.i.e 10,100,1000 and so on. + /// + /// Defaults to `10`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: LogarithmicAxis(logBase: 10), + /// ) + /// ); + /// } + /// ``` + final double logBase; + + /// The minimum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(initialVisibleMinimum: 0), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// minimum value dynamically. + /// + /// ```dart + /// LogarithmicAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: LogarithmicAxis( + /// initialVisibleMinimum: 0, + /// initialVisibleMaximum: 100, + /// onRendererCreated: (LogarithmicAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = 30; + /// axisController!.visibleMaximum = 70; + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double? initialVisibleMinimum; + + /// The maximum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: LogarithmicAxis(initialVisibleMaximum: 200), + /// ) + /// ); + ///} + ///``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// maximum value dynamically. + /// + /// ```dart + /// LogarithmicAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: LogarithmicAxis( + /// initialVisibleMinimum: 0, + /// initialVisibleMaximum: 200, + /// onRendererCreated: (LogarithmicAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = 10; + /// axisController!.visibleMaximum = 70; + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double? initialVisibleMaximum; + + /// Provides the option to group the axis labels. You can customize the start, + /// end value of a multi-level label, text, and level of + /// the multi-level labels. + /// + /// The `start` and `end` values for the category axis need to be string type, + /// in the case of date-time or date-time category axes need to be date-time + /// and in the case of numeric or logarithmic axes needs to be double. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: LogarithmicAxis( + /// multiLevelLabels: const [ + /// LogarithmicMultiLevelLabel( + /// start: 0, + /// end: 2, + /// text: 'First' + /// ), + /// LogarithmicMultiLevelLabel( + /// start: 2, + /// end: 4, + /// text: 'Second' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final List? multiLevelLabels; + + final Function(LogarithmicAxisController)? onRendererCreated; + + @override + RenderLogarithmicAxis createRenderer() { + return RenderLogarithmicAxis(); + } + + @override + RenderLogarithmicAxis createRenderObject(BuildContext context) { + final RenderLogarithmicAxis renderer = + super.createRenderObject(context) as RenderLogarithmicAxis; + renderer + ..labelFormat = labelFormat + ..numberFormat = numberFormat + ..logBase = logBase + ..minimum = minimum + ..maximum = maximum + ..initialVisibleMinimum = initialVisibleMinimum + ..initialVisibleMaximum = initialVisibleMaximum + ..multiLevelLabels = multiLevelLabels + ..onRendererCreated = onRendererCreated; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RenderLogarithmicAxis renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..labelFormat = labelFormat + ..numberFormat = numberFormat + ..logBase = logBase + ..minimum = minimum + ..maximum = maximum + ..multiLevelLabels = multiLevelLabels; + } +} + +class RenderLogarithmicAxis extends RenderChartAxis { + final List _multilevelLabels = []; + bool _dependentIsStacked = false; + + @override + LogarithmicAxisController get controller => _controller; + late final LogarithmicAxisController _controller = LogarithmicAxisController( + this, + ); + + @override + @nonVirtual + bool get canAnimate => + super.canAnimate || + (initialVisibleMinimum != null && initialVisibleMaximum != null); + + String? get labelFormat => _labelFormat; + String? _labelFormat; + set labelFormat(String? value) { + if (_labelFormat != value) { + _labelFormat = value; + markNeedsLayout(); + } + } + + NumberFormat? get numberFormat => _numberFormat; + NumberFormat? _numberFormat; + set numberFormat(NumberFormat? value) { + if (_numberFormat != value) { + _numberFormat = value; + markNeedsLayout(); + } + } + + double? get minimum => _minimum; + double? _minimum; + set minimum(double? value) { + if (_minimum != value) { + _minimum = value; + markNeedsLayout(); + } + } + + double? get maximum => _maximum; + double? _maximum; + set maximum(double? value) { + if (_maximum != value) { + _maximum = value; + markNeedsRangeUpdate(); + } + } + + double get logBase => _logBase; + double _logBase = 10; + set logBase(double value) { + if (_logBase != value) { + _logBase = value <= 0 ? 2 : value; + markNeedsUpdate(); + } + } + + double? get initialVisibleMinimum => _initialVisibleMinimum; + double? _initialVisibleMinimum; + set initialVisibleMinimum(double? value) { + if (_initialVisibleMinimum == value) { + return; + } + _initialVisibleMinimum = value; + if (rangeController != null && rangeController!.start != null) { + _updateVisibleMinMax(min: (rangeController!.start as num).toDouble()); + } else { + _updateVisibleMinMax(min: initialVisibleMinimum); + } + } + + double? get initialVisibleMaximum => _initialVisibleMaximum; + double? _initialVisibleMaximum; + set initialVisibleMaximum(double? value) { + if (_initialVisibleMaximum == value) { + return; + } + _initialVisibleMaximum = value; + if (rangeController != null && rangeController!.end != null) { + _updateVisibleMinMax(max: (rangeController!.end as num).toDouble()); + } else { + _updateVisibleMinMax(max: initialVisibleMaximum); + } + } + + List? get multiLevelLabels => _multiLevelLabels; + List? _multiLevelLabels; + set multiLevelLabels(List? value) { + if (_multiLevelLabels != value) { + _multiLevelLabels = value; + markNeedsLayout(); + } + } + + @override + set rangeController(RangeController? value) { + super.rangeController = value; + if (value == null) { + return; + } + _updateVisibleMinMax( + min: (value.start as num?)?.toDouble(), + max: (value.end as num?)?.toDouble(), + ); + } + + Function(LogarithmicAxisController)? get onRendererCreated => + _onRendererCreated; + Function(LogarithmicAxisController)? _onRendererCreated; + set onRendererCreated(Function(LogarithmicAxisController)? value) { + if (_onRendererCreated != value) { + _onRendererCreated = value; + } + } + + @override + void addDependent(AxisDependent dependent, {bool isXAxis = true}) { + super.addDependent(dependent, isXAxis: isXAxis); + if (isVertical && dependent is Stacking100SeriesMixin) { + _dependentIsStacked = true; + } + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(controller); + rangeController?.addListener(_handleRangeControllerChange); + super.attach(owner); + } + + @override + void detach() { + rangeController?.removeListener(_handleRangeControllerChange); + super.detach(); + } + + void _handleRangeControllerChange() { + assert(rangeController != null); + _updateVisibleMinMax( + min: (rangeController!.start as num?)?.toDouble(), + max: (rangeController!.end as num?)?.toDouble(), + ); + } + + void _updateVisibleMinMax({double? min, double? max}) { + if (min != null) { + controller.visibleMinimum = min; + } + if (max != null) { + controller.visibleMaximum = max; + } + } + + @override + DoubleRange applyRangePadding( + DoubleRange range, + num interval, + Size availableSize, + ) { + return range; + } + + @override + num desiredNiceInterval(num delta, num intervalsCount) { + return delta; + } + + @override + num actualValue(Object value) { + final num cross = value as num; + final num minimum = toPow(actualRange!.minimum); + final num maximum = toPow(actualRange!.maximum); + if (cross < minimum) { + return minimum; + } + if (cross > maximum) { + return maximum; + } + return cross; + } + + @override + DoubleRange calculateActualRange() { + final DoubleRange range = super.calculateActualRange(); + if (minimum != null) { + range.minimum = minimum!; + } + if (maximum != null) { + range.maximum = maximum!; + } + + return _calculateRange(range); + } + + DoubleRange _calculateRange(DoubleRange range) { + final DoubleRange rangeCopy = range.copyWith(); + num max = rangeCopy.maximum; + num min = rangeCopy.minimum; + if (min < 0) { + min = 0; + } + num start = toLog(min); + start = start.isFinite ? start : min; + num end = toLog(max); + end = end.isFinite ? end : max; + min = (start / 1).floor(); + max = (end / 1).ceil(); + if (min == max) { + max = max + 1; + } + + return rangeCopy + ..minimum = min + ..maximum = max; + } + + @override + DoubleRange updateAutoScrollingDelta( + int scrollingDelta, + DoubleRange actualRange, + DoubleRange visibleRange, + ) { + if (initialVisibleMaximum != null || initialVisibleMinimum != null) { + return visibleRange; + } + return super.updateAutoScrollingDelta( + scrollingDelta, + actualRange, + visibleRange, + ); + } + + @override + DoubleRange defaultRange() => DoubleRange(1, 10); + + @override + num calculateNiceInterval(num delta, Size availableSize) { + num niceInterval; + + final num intervalsCount = desiredIntervalsCount(availableSize); + niceInterval = desiredNiceInterval(delta, intervalsCount); + + final List divisions = [10, 5, 2, 1]; + final num minimumInterval = + niceInterval == 0 ? 0 : pow(10, (log(niceInterval) / log(10)).floor()); + for (int i = 0; i < divisions.length; i++) { + final num interval = divisions[i]; + final num currentInterval = minimumInterval * interval; + if (intervalsCount < (delta / currentInterval)) { + break; + } + niceInterval = currentInterval; + } + + return niceInterval; + } + + @override + void generateVisibleLabels() { + hasTrimmedAxisLabel = false; + if (visibleRange == null || visibleInterval == 0) { + return; + } + + final double extent = maximumLabelWidth ?? double.maxFinite; + final bool isRtl = textDirection == TextDirection.rtl; + num current = visibleRange!.minimum; + final num visibleMinimum = visibleRange!.minimum; + final num visibleMaximum = visibleRange!.maximum; + while (current <= visibleMaximum) { + if (current < visibleMinimum || + !effectiveVisibleRange!.contains(current)) { + current += visibleInterval; + continue; + } + + final num currentValue = toPow(current); + String text = + currentValue < 1 + ? currentValue.toString() + : currentValue.floor().toString(); + if (numberFormat != null) { + text = numberFormat!.format(toPow(current)); + } + + if (labelFormat != null && labelFormat != '') { + text = labelFormat!.replaceAll(RegExp('{value}'), text); + } + + if (_dependentIsStacked) { + text = '$text%'; + } + String callbackText = text; + TextStyle callbackTextStyle = chartThemeData!.axisLabelTextStyle!.merge( + labelStyle, + ); + if (axisLabelFormatter != null) { + final AxisLabelRenderDetails details = AxisLabelRenderDetails( + current, + callbackText, + callbackTextStyle, + this, + null, + null, + ); + final ChartAxisLabel label = axisLabelFormatter!(details); + callbackText = label.text; + callbackTextStyle = callbackTextStyle.merge(label.textStyle); + } + + Size textSize = measureText(callbackText, callbackTextStyle, 0); + String textAfterTrimming = callbackText; + if (extent.isFinite && textSize.width > extent) { + textAfterTrimming = trimmedText( + callbackText, + callbackTextStyle, + extent, + labelRotation, + isRtl: isRtl, + ); + } + + textSize = measureText( + textAfterTrimming, + callbackTextStyle, + labelRotation, + ); + final bool isTextTrimmed = callbackText != textAfterTrimming; + final AxisLabel label = AxisLabel( + callbackTextStyle, + textSize, + callbackText, + currentValue, + isTextTrimmed ? textAfterTrimming : null, + textAfterTrimming, + ); + visibleLabels.add(label); + if (isTextTrimmed) { + hasTrimmedAxisLabel = true; + } + current += visibleInterval; + } + + super.generateVisibleLabels(); + } + + @override + void calculateTickPositions( + LabelPlacement placement, { + List? source, + bool canCalculateMinorTick = false, + bool canCalculateMajorTick = true, + }) { + int length = visibleLabels.length; + if (length == 0) { + return; + } + + final bool isBetweenTicks = placement == LabelPlacement.betweenTicks; + final num tickBetweenLabel = isBetweenTicks ? visibleInterval / 2 : 0; + length += isBetweenTicks ? 1 : 0; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + num current; + if (isBetweenTicks) { + if (i < lastIndex) { + current = toLog(visibleLabels[i].value) - tickBetweenLabel; + } else { + current = + (toLog(visibleLabels[i - 1].value) + visibleInterval) - + tickBetweenLabel; + } + } else { + current = toLog(visibleLabels[i].value); + } + + source!.add(pointToPixel(toPow(current))); + + if (canCalculateMinorTick) { + final num start = current; + final num end = start + visibleInterval; + final double minorTickInterval = + visibleInterval / (minorTicksPerInterval + 1); + for (int j = 1; j <= minorTicksPerInterval; j++) { + final double tickValue = start + minorTickInterval * j; + if (tickValue < end && tickValue < visibleRange!.maximum) { + minorTickPositions.add(pointToPixel(toPow(tickValue))); + } + } + } + } + } + + @override + void generatePlotBands() { + if (plotBands.isNotEmpty && + associatedAxis != null && + associatedAxis!.visibleRange != null) { + visiblePlotBands ??= []; + final int length = plotBands.length; + final Rect Function(PlotBand plotBand, num start, num end) bounds = + isVertical ? verticalPlotBandBounds : horizontalPlotBandBounds; + + for (int i = 0; i < length; i++) { + final PlotBand plotBand = plotBands[i]; + if (plotBand.isVisible) { + final num min = + plotBand.start != null + ? actualValue(plotBand.start) + : toPow(visibleRange!.minimum); + num max = + plotBand.end != null + ? actualValue(plotBand.end) + : toPow(visibleRange!.maximum); + + num extent; + if (plotBand.isRepeatable) { + extent = plotBand.repeatEvery; + if (plotBand.repeatUntil != null) { + max = actualValue(plotBand.repeatUntil); + final num actualMax = toPow(actualRange!.maximum); + if (max > actualMax) { + max = actualMax; + } + } else { + max = toPow(actualRange!.maximum); + } + } else { + extent = max - min; + } + + num current = min; + if (plotBand.isRepeatable) { + while (current < max) { + current = formPlotBandFrame( + plotBand, + current, + extent, + max, + bounds, + ); + } + } else { + formPlotBandFrame(plotBand, current, extent, max, bounds); + } + } + } + } + } + + @override + num formPlotBandFrame( + PlotBand plotBand, + num current, + num extent, + num max, + Rect Function(PlotBand plotBand, num start, num end) bounds, + ) { + num end = plotBandExtent( + plotBand, + current, + plotBand.isRepeatable ? plotBand.size : extent, + ); + if (end > max) { + end = max; + } + final DoubleRange logRange = visibleRange!.copyWith(); + final DoubleRange powRange = DoubleRange( + toPow(logRange.minimum), + toPow(logRange.maximum), + ); + if (powRange.lies(current, end)) { + final Rect frame = bounds(plotBand, current, end); + addPlotBand(frame, plotBand); + current = + plotBand.size != null + ? plotBandExtent( + plotBand, + current, + plotBand.isRepeatable ? plotBand.repeatEvery : end, + ) + : end; + } + return current; + } + + @override + void generateMultiLevelLabels() { + _multilevelLabels.clear(); + visibleMultilevelLabels.clear(); + + final int length = multiLevelLabels?.length ?? 0; + if (length == 0) { + return; + } + + for (int i = 0; i < length; i++) { + final LogarithmicMultiLevelLabel label = multiLevelLabels![i]; + assert(label.start <= label.end); + _multilevelLabels.add( + AxisMultilevelLabel( + label.text, + label.level, + toLog(label.start), + toLog(label.end), + ), + ); + } + + _multilevelLabels.sort( + (AxisMultilevelLabel a, AxisMultilevelLabel b) => + a.level.compareTo(b.level), + ); + + final void Function(AxisMultilevelLabel label) add = + invertElementsOrder + ? (AxisMultilevelLabel label) => + visibleMultilevelLabels.insert(0, label) + : (AxisMultilevelLabel label) => visibleMultilevelLabels.add(label); + + final int labelsLength = _multilevelLabels.length; + final TextStyle textStyle = chartThemeData!.axisMultiLevelLabelTextStyle! + .merge(multiLevelLabelStyle.textStyle); + for (int i = 0; i < labelsLength; i++) { + final AxisMultilevelLabel current = _multilevelLabels[i]; + if (isLies(current.start, current.end, visibleRange!)) { + String desiredText = current.text; + TextStyle desiredTextStyle = textStyle; + if (multiLevelLabelFormatter != null) { + final MultiLevelLabelRenderDetails details = + MultiLevelLabelRenderDetails( + current.level, + desiredText, + desiredTextStyle, + i, + name, + ); + final ChartAxisLabel label = multiLevelLabelFormatter!(details); + desiredText = label.text; + desiredTextStyle = textStyle.merge(label.textStyle); + } + + current + ..actualTextSize = measureText(desiredText, desiredTextStyle, 0) + ..renderText = desiredText + ..style = desiredTextStyle; + add(current); + } + } + } + + @override + void updateMultiLevelLabels() { + late Rect Function(double start, double end, Size size) labelBounds; + labelBounds = isVertical ? _verticalLabelBounds : _horizontalLabelBounds; + + const double padding = 12.0; + final bool isRtl = textDirection == TextDirection.rtl; + for (final AxisMultilevelLabel label in visibleMultilevelLabels) { + final double start = pointToPixel(toPow(label.start)); + final double end = pointToPixel(toPow(label.end)); + final double extent = (end - start - padding).abs(); + + String renderText = label.renderText; + final TextStyle style = label.style; + if (label.actualTextSize.width > extent) { + renderText = trimmedText(renderText, style, extent, 0, isRtl: isRtl); + } + + label.trimmedText = renderText; + label + ..transformStart = start + ..transformEnd = end + ..region = labelBounds(start, end, measureText(renderText, style, 0)); + } + } + + Rect _horizontalLabelBounds(double start, double end, Size labelSize) { + double height = labelSize.height; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + height += 2 * textPaddingOfCurlyBrace; + } else { + height += 2 * textPadding; + } + + return Rect.fromLTRB(start, 0, end, height); + } + + Rect _verticalLabelBounds(double start, double end, Size labelSize) { + double width = labelSize.width; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + width += 2 * textPaddingOfCurlyBrace; + } else { + width += 2 * textPadding; + } + + return Rect.fromLTRB(0, start, width, end); + } + + @override + double pointToPixel(num dataPoint, {DoubleRange? range}) { + return super.pointToPixel(toLog(dataPoint)); + } + + num toPow(num value) => pow(_logBase, value); + + num toLog(num value) { + if (value <= 0) { + return 0; + } + + final num logValue = _log(value); + if (logValue.isInfinite || logValue.isNaN) { + return 0; + } + + return logValue; + } + + num _log(num value) => log(value) / log(_logBase); + + @override + void dispose() { + _multilevelLabels.clear(); + controller.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/multi_level_labels.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/multi_level_labels.dart new file mode 100644 index 000000000..7df9d3be9 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/multi_level_labels.dart @@ -0,0 +1,440 @@ +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; + +/// Class which holds the properties of multi-level labels +class ChartMultiLevelLabel { + /// Constructor for ChartMultiLevelLabel class + const ChartMultiLevelLabel({ + required this.start, + required this.end, + this.level = 0, + this.text = '', + }); + + /// Start value of the multi-level label. + /// The value from where the multi-level label border needs to start. + /// + /// The [start] value for the category axis needs to string type, in the case + /// of date-time or date-time category axes need to be date-time and in the + /// case of numeric or logarithmic axes need to double. + /// + /// Defaults to 'null' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final T start; + + /// End value of the multi-level label. + /// The value where the multi-level label border needs to end. + /// + /// The [end] value for the category axis need to string type, in the case of + /// date-time or date-time category axes need to be date-time and in the + /// case of numeric or logarithmic axes need to double. + /// + /// Defaults to 'null' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final T end; + + /// Text to be displayed in grouping label as the multi-level label. + /// + /// Defaults to 'null' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final String text; + + /// Level specifies in which row the multi-level label should be positioned. + /// + /// Defaults to '0' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: + /// NumericAxis(multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// level: 1, + /// text: 'First Level', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final int level; +} + +/// Provides options to customize the start, the end value of a multi-level +/// label, text, and level of the multi-level labels. +/// +/// The [start] and [end] values need to be double type. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfCartesianChart( +/// primaryXAxis: NumericAxis( +/// multiLevelLabels: const [ +/// NumericMultiLevelLabel( +/// start: 0, +/// end: 2, +/// text: 'First', +/// ), +/// NumericMultiLevelLabel( +/// start: 2, +/// end: 4, +/// text: 'Second', +/// ) +/// ] +/// ) +/// ) +/// ); +/// } +/// ``` +class NumericMultiLevelLabel extends ChartMultiLevelLabel { + /// Constructor for [NumericMultiLevelLabel] class. + const NumericMultiLevelLabel({ + required super.start, + required super.end, + super.level, + super.text, + }); +} + +/// Provides options to customize the start, the end value of a multi-level +/// label, text, and level of the multi-level labels. +/// +/// The [start] and [end] value need to be string type. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfCartesianChart( +/// primaryXAxis: CategoryAxis( +/// multiLevelLabels: const [ +/// CategoricalMultiLevelLabel(start: 'Jan', end: 'Feb', text: 'First') +/// ] +/// ) +/// ) +/// ); +/// } +/// ``` +class CategoricalMultiLevelLabel extends ChartMultiLevelLabel { + /// Constructor for [CategoricalMultiLevelLabel] class. + const CategoricalMultiLevelLabel({ + required super.start, + required super.end, + super.level, + super.text, + }); +} + +/// Provides options to customize the start, the end value of a multi-level +/// label, text, and level of the multi-level labels. +/// +/// The [start] and [end] value need to be date-time type. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfCartesianChart( +/// primaryXAxis: DateTimeAxis( +/// multiLevelLabels: [ +/// DateTimeMultiLevelLabel( +/// start: DateTime(2020, 2, 3), +/// end: DateTime(2020, 2, 5), +/// text: 'First' +/// ) +/// ] +/// ) +/// ) +/// ); +/// } +/// ``` +class DateTimeMultiLevelLabel extends ChartMultiLevelLabel { + /// Constructor for [DateTimeMultiLevelLabel] class. + const DateTimeMultiLevelLabel({ + required super.start, + required super.end, + super.level, + super.text, + }); +} + +/// Provides options to customize the start, the end value of a multi-level +/// label, text, and level of the multi-level labels. +/// +/// The [start] and [end] value need to be date-time type. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfCartesianChart( +/// primaryXAxis: DateTimeCategoryAxis( +/// multiLevelLabels: [ +/// DateTimeCategoricalMultiLevelLabel( +/// start: DateTime(2020, 2, 3), +/// end: DateTime(2020, 2, 5), +/// text: 'First' +/// ) +/// ] +/// ) +/// ) +/// ); +/// } +/// ``` +class DateTimeCategoricalMultiLevelLabel extends DateTimeMultiLevelLabel { + /// Constructor for [DateTimeCategoryMultiLevelLabel] class + const DateTimeCategoricalMultiLevelLabel({ + required super.start, + required super.end, + super.level, + super.text, + }); +} + +/// Provides options to customize the start, the end value of a multi-level +/// label, text, and level of the multi-level labels. +/// +/// The [start] and [end] values need to be double type. +/// +///```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfCartesianChart( +/// primaryXAxis: +/// LogarithmicAxis(multiLevelLabels: const [ +/// LogarithmicMultiLevelLabel( +/// start: 0, +/// end: 4, +/// text: 'First', +/// ) +/// ] +/// ) +/// ) +/// ); +/// } +///``` +class LogarithmicMultiLevelLabel extends NumericMultiLevelLabel { + /// Constructor for LogarithmicMultiLevelLabel class. + const LogarithmicMultiLevelLabel({ + required super.start, + required super.end, + super.level, + super.text, + }); +} + +/// Customize the multi-level label’s border color, width, type, and +/// text style such as color, font size, etc. +/// +/// When the multi-level label’s width exceeds its respective segment, +/// then the label will get trimmed and on tapping / hovering over the trimmed +/// label, a tooltip will be shown. +/// +/// Also refer [multiLevelLabelFormatter]. +class MultiLevelLabelStyle { + /// Creating an argument constructor of [MultiLevelLabelStyle] class. + const MultiLevelLabelStyle({ + this.textStyle, + this.borderWidth = 0, + this.borderColor, + this.borderType = MultiLevelBorderType.rectangle, + }); + + /// Specifies the text style of the multi-level labels. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabelStyle: MultiLevelLabelStyle( + /// textStyle: TextStyle( + /// fontSize: 10, + /// color: Colors.black, + /// ) + /// ), + /// multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Specifies the border width of multi-level labels. + /// + /// Defaults to '0' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabelStyle: MultiLevelLabelStyle( + /// borderWidth: 2.0 + /// ), + /// multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Specifies the border color of multi-level labels. + /// + /// Defaults to 'null' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabelStyle: MultiLevelLabelStyle( + /// borderColor: Colors.black + /// ), + /// multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final Color? borderColor; + + /// Specifies the border type of multi-level labels. + /// + /// Defaults to 'MultiLevelLabelBorderType.rectangle' + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabelStyle: MultiLevelLabelStyle( + /// borderType: MultiLevelBorderType.curlyBrace + /// ), + /// multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 4, + /// text: 'First', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final MultiLevelBorderType borderType; +} + +/// Holds the multi-level axis label information. +class AxisMultilevelLabel { + /// Argument constructor of [AxisMultilevelLabel] class. + AxisMultilevelLabel(this.text, this.level, this.start, this.end); + + /// Contains the text of the label. + String text; + + /// Stores the level given by the user. + int level; + + /// Stores the actual start value. + num start; + + /// Stores the actual end value. + num end; + + /// Contains the label text to be rendered. + late Size actualTextSize; + + /// Contains the label text to be rendered. + late String renderText; + + /// Contains the trimmed label text to be rendered. + late String trimmedText; + + /// Specifies the label text style. + late TextStyle style; + + /// Stores the transform start value. + late num transformStart; + + /// Stores the transform end value. + late num transformEnd; + + /// To store the rect region for trimmed labels tooltip rendering. + late Rect region; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart new file mode 100644 index 000000000..5dc7087f5 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart @@ -0,0 +1,772 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' hide TextDirection; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'axis.dart'; +import 'multi_level_labels.dart'; + +/// This class has the properties of the numeric axis. +/// +/// Numeric axis uses a numerical scale and displays numbers as labels. +/// By default, [NumericAxis] is set to both horizontal axis and vertical axis. +/// +/// Provides the options of [name], axis line, label rotation, label format, +/// alignment and label position are used to customize the appearance. +class NumericAxis extends ChartAxis { + /// Creating an argument constructor of [NumericAxis] class. + const NumericAxis({ + super.key, + super.name, + super.isVisible = true, + super.anchorRangeToVisiblePoints = true, + super.title, + super.axisLine, + super.rangePadding, + super.labelIntersectAction, + super.labelRotation, + this.labelFormat, + this.numberFormat, + super.labelAlignment, + super.labelPosition, + super.tickPosition, + super.isInversed, + super.opposedPosition, + super.minorTicksPerInterval, + super.maximumLabels, + super.majorTickLines, + super.minorTickLines, + super.majorGridLines, + super.minorGridLines, + super.edgeLabelPlacement, + super.labelStyle, + super.plotOffset, + super.plotOffsetStart, + super.plotOffsetEnd, + super.initialZoomFactor, + super.initialZoomPosition, + super.enableAutoIntervalOnZooming, + super.interactiveTooltip, + this.minimum, + this.maximum, + super.interval, + this.initialVisibleMinimum, + this.initialVisibleMaximum, + super.crossesAt, + super.associatedAxisName, + super.placeLabelsNearAxisLine, + super.plotBands, + this.decimalPlaces = 3, + super.desiredIntervals, + super.rangeController, + super.maximumLabelWidth, + super.labelsExtent, + super.autoScrollingDelta, + super.autoScrollingMode, + super.borderWidth, + super.borderColor, + super.axisBorderType, + this.multiLevelLabels, + super.multiLevelLabelFormatter, + super.multiLevelLabelStyle, + super.axisLabelFormatter, + this.onRendererCreated, + }) : assert( + (initialVisibleMaximum == null && initialVisibleMinimum == null) || + autoScrollingDelta == null, + 'Both properties have the same behavior to display the visible data points, use any one of the properties', + ); + + /// Formats the numeric axis labels. + /// + /// The labels can be customized by adding desired text as prefix or suffix. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(labelFormat: '{value}M'), + /// ) + /// ); + /// } + /// ``` + final String? labelFormat; + + /// Formats the numeric axis labels with globalized label formats. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(numberFormat: + /// NumberFormat.compactCurrency()), + /// ) + /// ); + /// } + /// ``` + final NumberFormat? numberFormat; + + /// The minimum value of the axis. + /// + /// The axis will start from this value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(minimum: 0), + /// ) + /// ); + /// } + /// ``` + final double? minimum; + + /// The maximum value of the axis. + /// + /// The axis will end at this value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(maximum: 200), + /// ) + /// ); + /// } + /// ``` + final double? maximum; + + /// The minimum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(initialVisibleMinimum: 0), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// minimum value dynamically. + /// + /// ```dart + /// NumericAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// initialVisibleMinimum: 0, + /// initialVisibleMaximum: 100, + /// onRendererCreated: (NumericAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = 30; + /// axisController!.visibleMaximum = 70; + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double? initialVisibleMinimum; + + /// The maximum visible value of the axis. The axis is rendered from this value initially, and + /// it applies only during load time. The value will not be updated when zooming or panning. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(initialVisibleMaximum: 200), + /// ) + /// ); + /// } + /// ``` + /// + /// Use the [onRendererCreated] callback, as shown in the code below, to update the visible + /// maximum value dynamically. + /// + /// ```dart + /// NumericAxisController? axisController; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// initialVisibleMinimum: 0, + /// initialVisibleMaximum: 200, + /// onRendererCreated: (NumericAxisController controller) { + /// axisController = controller; + /// }, + /// ), + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (axisController != null) { + /// axisController!.visibleMinimum = 10; + /// axisController!.visibleMaximum = 70; + /// } + /// }, + /// child: const Text('Update Axis Range'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final double? initialVisibleMaximum; + + /// The rounding decimal value of the label. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(decimalPlaces: 3), + /// ) + /// ); + /// } + /// ``` + final int decimalPlaces; + + /// Provides the option to group the axis labels. You can customize the start, + /// end value of a multi-level label, text, and level of + /// the multi-level labels. + /// + /// The `start` and `end` values for the category axis need to be string type, + /// in the case of date-time or date-time category axes need to be date-time + /// and in the case of numeric or logarithmic axes needs to be double. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// multiLevelLabels: const [ + /// NumericMultiLevelLabel( + /// start: 0, + /// end: 2, + /// text: 'First' + /// ), + /// NumericMultiLevelLabel( + /// start: 2, + /// end: 4, + /// text: 'Second' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final List? multiLevelLabels; + + final Function(NumericAxisController)? onRendererCreated; + + @override + RenderChartAxis createRenderer() { + return RenderNumericAxis(); + } + + @override + RenderNumericAxis createRenderObject(BuildContext context) { + final RenderNumericAxis renderer = + super.createRenderObject(context) as RenderNumericAxis; + renderer + ..labelFormat = labelFormat + ..numberFormat = numberFormat + ..minimum = minimum + ..maximum = maximum + ..initialVisibleMinimum = initialVisibleMinimum + ..initialVisibleMaximum = initialVisibleMaximum + ..decimalPlaces = decimalPlaces + ..multiLevelLabels = multiLevelLabels + ..onRendererCreated = onRendererCreated; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RenderNumericAxis renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..labelFormat = labelFormat + ..numberFormat = numberFormat + ..minimum = minimum + ..maximum = maximum + ..decimalPlaces = decimalPlaces + ..multiLevelLabels = multiLevelLabels; + } +} + +class RenderNumericAxis extends RenderChartAxis { + final List _multilevelLabels = []; + bool _dependentIsStacked = false; + + @override + NumericAxisController get controller => _controller; + late final NumericAxisController _controller = NumericAxisController(this); + + @override + @nonVirtual + bool get canAnimate => + super.canAnimate || + (initialVisibleMinimum != null && initialVisibleMaximum != null); + + String? get labelFormat => _labelFormat; + String? _labelFormat; + set labelFormat(String? value) { + if (_labelFormat != value) { + _labelFormat = value; + markNeedsLayout(); + } + } + + NumberFormat? get numberFormat => _numberFormat; + NumberFormat? _numberFormat; + set numberFormat(NumberFormat? value) { + if (_numberFormat != value) { + _numberFormat = value; + markNeedsLayout(); + } + } + + double? get minimum => _minimum; + double? _minimum; + set minimum(double? value) { + if (_minimum != value) { + _minimum = value; + markNeedsRangeUpdate(); + } + } + + double? get maximum => _maximum; + double? _maximum; + set maximum(double? value) { + if (_maximum != value) { + _maximum = value; + markNeedsRangeUpdate(); + } + } + + int get decimalPlaces => _decimalPlaces; + int _decimalPlaces = 3; + set decimalPlaces(int value) { + if (_decimalPlaces != value) { + _decimalPlaces = value; + markNeedsLayout(); + } + } + + double? get initialVisibleMinimum => _initialVisibleMinimum; + double? _initialVisibleMinimum; + set initialVisibleMinimum(double? value) { + if (_initialVisibleMinimum == value) { + return; + } + _initialVisibleMinimum = value; + if (rangeController != null && rangeController!.start != null) { + _updateVisibleMinMax(min: (rangeController!.start as num).toDouble()); + } else { + _updateVisibleMinMax(min: initialVisibleMinimum); + } + } + + double? get initialVisibleMaximum => _initialVisibleMaximum; + double? _initialVisibleMaximum; + set initialVisibleMaximum(double? value) { + if (_initialVisibleMaximum == value) { + return; + } + _initialVisibleMaximum = value; + if (rangeController != null && rangeController!.end != null) { + _updateVisibleMinMax(max: (rangeController!.end as num).toDouble()); + } else { + _updateVisibleMinMax(max: initialVisibleMaximum); + } + } + + List? get multiLevelLabels => _multiLevelLabels; + List? _multiLevelLabels; + set multiLevelLabels(List? value) { + if (_multiLevelLabels != value) { + _multiLevelLabels = value; + markNeedsLayout(); + } + } + + @override + set rangeController(RangeController? value) { + super.rangeController = value; + if (value == null) { + return; + } + _updateVisibleMinMax( + min: (value.start as num?)?.toDouble(), + max: (value.end as num?)?.toDouble(), + ); + } + + Function(NumericAxisController)? get onRendererCreated => _onRendererCreated; + Function(NumericAxisController)? _onRendererCreated; + set onRendererCreated(Function(NumericAxisController)? value) { + if (_onRendererCreated != value) { + _onRendererCreated = value; + } + } + + @override + void addDependent(AxisDependent dependent, {bool isXAxis = true}) { + super.addDependent(dependent, isXAxis: isXAxis); + if (isVertical && dependent is Stacking100SeriesMixin) { + _dependentIsStacked = true; + } + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(controller); + rangeController?.addListener(_handleRangeControllerChange); + super.attach(owner); + } + + @override + void detach() { + rangeController?.removeListener(_handleRangeControllerChange); + super.detach(); + } + + void _handleRangeControllerChange() { + _updateVisibleMinMax( + min: (rangeController!.start as num?)?.toDouble(), + max: (rangeController!.end as num?)?.toDouble(), + ); + } + + void _updateVisibleMinMax({double? min, double? max}) { + if (min != null) { + controller.visibleMinimum = min; + } + if (max != null) { + controller.visibleMaximum = max; + } + } + + @override + DoubleRange updateAutoScrollingDelta( + int scrollingDelta, + DoubleRange actualRange, + DoubleRange visibleRange, + ) { + if (initialVisibleMaximum != null || initialVisibleMinimum != null) { + return visibleRange; + } + return super.updateAutoScrollingDelta( + scrollingDelta, + actualRange, + visibleRange, + ); + } + + @override + DoubleRange defaultRange() => DoubleRange(0, 5); + + @override + DoubleRange calculateActualRange() { + if (minimum != null && maximum != null) { + if (minimum == maximum) { + return DoubleRange(minimum!, maximum! + 1); + } + return DoubleRange(minimum!, maximum!); + } + + final DoubleRange range = super.calculateActualRange(); + if (minimum != null) { + range.minimum = minimum!; + } else if (maximum != null) { + range.maximum = maximum!; + } + + if (range.minimum == range.maximum) { + range.maximum += 1; + } + + return range.copyWith(); + } + + @override + DoubleRange applyRangePadding( + DoubleRange range, + num interval, + Size availableSize, + ) { + if (minimum != null && maximum != null) { + return range; + } + + range = super.applyRangePadding(range, interval, availableSize); + if (minimum != null) { + range.minimum = minimum!; + } else if (maximum != null) { + range.maximum = maximum!; + } + + return range.copyWith(); + } + + @override + void generateVisibleLabels() { + hasTrimmedAxisLabel = false; + if (visibleRange == null || visibleInterval == 0) { + return; + } + final double extent = maximumLabelWidth ?? double.maxFinite; + final bool isRtl = textDirection == TextDirection.rtl; + num current = visibleRange!.minimum; + final num visibleMinimum = visibleRange!.minimum; + final num visibleMaximum = visibleRange!.maximum; + while (current <= visibleMaximum) { + if (current < visibleMinimum || + !effectiveVisibleRange!.contains(current)) { + current += visibleInterval; + continue; + } + + num currentValue = current; + final String currentText = currentValue.toString(); + final List pieces = currentText.split('.'); + final int piecesLength = pieces.length; + int digits = piecesLength >= 2 ? pieces[1].length : 0; + digits = digits > 20 ? 20 : digits; + currentValue = + currentText.contains('e') + ? currentValue + : num.tryParse(currentValue.toStringAsFixed(digits))!; + + String text = numericAxisLabel(this, currentValue, decimalPlaces); + if (_dependentIsStacked) { + text = '$text%'; + } + String callbackText = text; + TextStyle callbackTextStyle = chartThemeData!.axisLabelTextStyle!.merge( + labelStyle, + ); + if (axisLabelFormatter != null) { + final AxisLabelRenderDetails details = AxisLabelRenderDetails( + current, + callbackText, + callbackTextStyle, + this, + null, + null, + ); + final ChartAxisLabel label = axisLabelFormatter!(details); + callbackText = label.text; + callbackTextStyle = callbackTextStyle.merge(label.textStyle); + } + + Size textSize = measureText(callbackText, callbackTextStyle, 0); + String textAfterTrimming = callbackText; + if (extent.isFinite && textSize.width > extent) { + textAfterTrimming = trimmedText( + callbackText, + callbackTextStyle, + extent, + labelRotation, + isRtl: isRtl, + ); + } + + textSize = measureText( + textAfterTrimming, + callbackTextStyle, + labelRotation, + ); + final bool isTextTrimmed = callbackText != textAfterTrimming; + final AxisLabel label = AxisLabel( + callbackTextStyle, + textSize, + callbackText, + current, + isTextTrimmed ? textAfterTrimming : null, + textAfterTrimming, + ); + visibleLabels.add(label); + if (isTextTrimmed) { + hasTrimmedAxisLabel = true; + } + current += visibleInterval; + } + + super.generateVisibleLabels(); + } + + @override + void generateMultiLevelLabels() { + _multilevelLabels.clear(); + visibleMultilevelLabels.clear(); + + // TODO(VijayakumarM): Move multilevelLabels to property setter. + final int length = multiLevelLabels?.length ?? 0; + if (length == 0) { + return; + } + + for (int i = 0; i < length; i++) { + final NumericMultiLevelLabel label = multiLevelLabels![i]; + assert(label.start <= label.end); + _multilevelLabels.add( + AxisMultilevelLabel(label.text, label.level, label.start, label.end), + ); + } + + _multilevelLabels.sort( + (AxisMultilevelLabel a, AxisMultilevelLabel b) => + a.level.compareTo(b.level), + ); + final void Function(AxisMultilevelLabel label) add = + invertElementsOrder + ? (AxisMultilevelLabel label) => + visibleMultilevelLabels.insert(0, label) + : (AxisMultilevelLabel label) => visibleMultilevelLabels.add(label); + + final int labelsLength = _multilevelLabels.length; + final TextStyle textStyle = chartThemeData!.axisMultiLevelLabelTextStyle! + .merge(multiLevelLabelStyle.textStyle); + for (int i = 0; i < labelsLength; i++) { + final AxisMultilevelLabel current = _multilevelLabels[i]; + if (isLies(current.start, current.end, visibleRange!)) { + String desiredText = current.text; + TextStyle desiredTextStyle = textStyle; + if (multiLevelLabelFormatter != null) { + final MultiLevelLabelRenderDetails details = + MultiLevelLabelRenderDetails( + current.level, + desiredText, + desiredTextStyle, + i, + name, + ); + final ChartAxisLabel label = multiLevelLabelFormatter!(details); + desiredText = label.text; + desiredTextStyle = textStyle.merge(label.textStyle); + } + + current + ..actualTextSize = measureText(desiredText, desiredTextStyle, 0) + ..renderText = desiredText + ..style = desiredTextStyle; + add(current); + } + } + } + + @override + void updateMultiLevelLabels() { + late Rect Function(double start, double end, Size size) labelBounds; + labelBounds = isVertical ? _verticalLabelBounds : _horizontalLabelBounds; + + final bool isRtl = textDirection == TextDirection.rtl; + for (final AxisMultilevelLabel label in visibleMultilevelLabels) { + final double start = pointToPixel(label.start); + final double end = pointToPixel(label.end); + final double extent = (end - start - textPadding).abs(); + + String renderText = label.renderText; + final TextStyle style = label.style; + if (label.actualTextSize.width > extent) { + renderText = trimmedText(renderText, style, extent, 0, isRtl: isRtl); + } + + label.trimmedText = renderText; + label + ..transformStart = start + ..transformEnd = end + ..region = labelBounds(start, end, measureText(renderText, style, 0)); + } + } + + Rect _horizontalLabelBounds(double start, double end, Size labelSize) { + double height = labelSize.height; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + height += 2 * textPaddingOfCurlyBrace; + } else { + height += 2 * textPadding; + } + + return Rect.fromLTRB(start, 0, end, height); + } + + Rect _verticalLabelBounds(double start, double end, Size labelSize) { + double width = labelSize.width; + if (multiLevelLabelStyle.borderType == MultiLevelBorderType.curlyBrace) { + width += 2 * textPaddingOfCurlyBrace; + } else { + width += 2 * textPadding; + } + + return Rect.fromLTRB(0, start, width, end); + } + + @override + void dispose() { + _multilevelLabels.clear(); + controller.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/plot_band.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/plot_band.dart new file mode 100644 index 000000000..bcb60468e --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/plot_band.dart @@ -0,0 +1,661 @@ +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; + +/// Render plot band. +/// +/// Plot bands are also known as strip lines, which are 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, you have to add plot bands using the plotBands property of the +/// respective axis. You can also add multiple plot bands to an axis. +/// +/// Provides the property of visible, opacity, start, end, color, border color, +/// and border width to customize the appearance. +@immutable +class PlotBand { + /// Creating an argument constructor of PlotBand class. + const PlotBand({ + this.isVisible = true, + this.start, + this.end, + this.associatedAxisStart, + this.associatedAxisEnd, + this.color = Colors.grey, + this.gradient, + this.opacity = 1.0, + this.borderColor = Colors.transparent, + this.borderWidth = 2, + this.dashArray = const [0, 0], + this.text, + this.textStyle, + this.textAngle, + this.verticalTextPadding, + this.horizontalTextPadding, + this.verticalTextAlignment = TextAnchor.middle, + this.horizontalTextAlignment = TextAnchor.middle, + this.isRepeatable = false, + this.repeatEvery = 1, + this.repeatUntil, + this.size, + this.sizeType = DateTimeIntervalType.auto, + this.shouldRenderAboveSeries = false, + }); + + /// Toggles the visibility of the plot band. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final bool isVisible; + + /// Specifies the start value of plot band. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// start: 1, + /// end: 5 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic start; + + /// Specifies the end value of plot band. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// start: 1, + /// end: 5 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic end; + + /// Text to be displayed in the plot band segment. + /// + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// text:'Winter' + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final String? text; + + /// Customizes the text style of plot band. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// textStyle: const TextStyle(color:Colors.red) + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Color of the plot band. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// color: Colors.red + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final Color color; + + /// Color of the plot band border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// borderColor: Colors.red + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final Color borderColor; + + /// Width of the plot band border. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// borderWidth: 2 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Opacity of the plot band. The value ranges from 0 to 1. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// opacity: 0.5 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final double opacity; + + /// Specifies the plot band need to be repeated in specified interval. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// isRepeatable: true + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final bool isRepeatable; + + /// Interval of the plot band need to be repeated. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// repeatEvery: 200 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic repeatEvery; + + /// End of the plot band need to be repeated. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// repeatUntil: 600 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic repeatUntil; + + /// Angle of the plot band text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// textAngle: 90 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final int? textAngle; + + /// Specifies whether the plot band needs to be rendered above the series. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// shouldRenderAboveSeries: true + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final bool shouldRenderAboveSeries; + + /// Date time interval type of the plot band. + /// + /// Defaults to `DateTimeIntervalType.auto`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// sizeType: DateTimeIntervalType.years + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final DateTimeIntervalType sizeType; + + /// Dashes of the series. Any number of values can be provided in the list. + /// Odd value is considered as rendering size and even value is considered + /// as gap. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// dashArray: [10, 10] + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final List dashArray; + + /// Size of the plot band. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// size: 20 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic size; + + /// Perpendicular axis start value. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// associatedAxisStart: 2 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic associatedAxisStart; + + /// Perpendicular axis end value. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// associatedAxisEnd: 2 + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final dynamic associatedAxisEnd; + + /// Vertical text alignment of the plot band text. + /// + /// Defaults to `TextAnchor.middle`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// verticalTextAlignment: TextAnchor.start + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final TextAnchor verticalTextAlignment; + + /// Horizontal text alignment of the plot band text. + /// + /// Defaults to `TextAnchor.middle`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// horizontalTextAlignment: TextAnchor.end + /// ) + /// ] + /// ) + /// ) + /// ); + ///} + ///``` + final TextAnchor horizontalTextAlignment; + + /// Fills the plot band with gradient color. + /// + /// ```dart + /// final List color = []; + /// final List stops = []; + /// + /// Widget build(BuildContext context) { + /// color.add(Colors.pink[50]!); + /// color.add(Colors.pink[200]!); + /// color.add(Colors.pink); + /// stops.add(0.0); + /// stops.add(0.5); + /// stops.add(1.0); + /// + /// final LinearGradient gradients = + /// LinearGradient(colors: color, stops: stops); + /// + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// isVisible:true, + /// gradient: gradients + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final LinearGradient? gradient; + + /// To move the plot band text vertically. + /// + /// Takes pixel or percentage value. For pixel, input should be like `10px` + /// and for percentage input should be like `10%`. If no suffix is specified + // (`10`), it will be considered as pixel value. Percentage value refers to + // the overall height of the chart. i.e. 100% is equal to the height + /// of the chart. + /// + /// This is applicable for both vertical and horizontal axis. Positive value + /// for this property moves the text upwards and negative + // value moves downwards. + /// + /// If [verticalTextAlignment] or [horizontalTextAlignment] is specified, + /// text padding will be calculated from that modified position. + /// + /// Defaults to `null`. + /// + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// verticalTextPadding:'30%', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final String? verticalTextPadding; + + /// To move the plot band text horizontally. + /// + /// Takes pixel or percentage value. For pixel, input should be like `10px` + /// and for percentage input should be like `10%`. If no suffix is specified + /// (`10`), it will be considered as pixel value. Percentage value refers to + /// the overall width of the chart. i.e. 100% is equal to the width + /// of the chart. + /// + /// This is applicable for both vertical and horizontal axis. Positive value + /// for this property moves the text to right and negative value + /// moves to left. + /// + /// If [verticalTextAlignment] or [horizontalTextAlignment] is specified, + /// text padding will be calculated from that modified position. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// plotBands: [ + /// PlotBand( + /// horizontalTextPadding:'30%', + /// ) + /// ] + /// ) + /// ) + /// ); + /// } + /// ``` + final String? horizontalTextPadding; + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is PlotBand && + other.isVisible == isVisible && + other.start == start && + other.end == end && + other.color == color && + other.opacity == opacity && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.text == text && + other.textStyle == textStyle && + other.isRepeatable == isRepeatable && + other.repeatEvery == repeatEvery && + other.verticalTextPadding == verticalTextPadding && + other.horizontalTextPadding == horizontalTextPadding && + other.repeatUntil == repeatUntil && + other.textAngle == textAngle && + other.shouldRenderAboveSeries == shouldRenderAboveSeries && + other.sizeType == sizeType && + other.dashArray == dashArray && + other.size == size && + other.associatedAxisStart == associatedAxisStart && + other.associatedAxisEnd == associatedAxisEnd && + other.verticalTextAlignment == verticalTextAlignment && + other.horizontalTextAlignment == horizontalTextAlignment && + other.gradient == gradient; + } + + @override + int get hashCode { + final List values = [ + isVisible, + start, + end, + color, + opacity, + borderColor, + borderWidth, + text, + textStyle, + isRepeatable, + repeatEvery, + verticalTextPadding, + horizontalTextPadding, + repeatUntil, + textAngle, + shouldRenderAboveSeries, + sizeType, + dashArray, + size, + associatedAxisStart, + associatedAxisEnd, + verticalTextAlignment, + horizontalTextAlignment, + gradient, + ]; + return Object.hashAll(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/base.dart b/packages/syncfusion_flutter_charts/lib/src/charts/base.dart new file mode 100644 index 000000000..ba75180e6 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/base.dart @@ -0,0 +1,3912 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide Image; +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 'axis/axis.dart'; +import 'axis/category_axis.dart'; +import 'behaviors/crosshair.dart'; +import 'behaviors/trackball.dart'; +import 'behaviors/zooming.dart'; +import 'common/annotation.dart'; +import 'common/callbacks.dart'; +import 'common/core_legend.dart'; +import 'common/element_widget.dart'; +import 'common/legend.dart'; +import 'indicators/accumulation_distribution_indicator.dart'; +import 'indicators/atr_indicator.dart'; +import 'indicators/bollinger_bands_indicator.dart'; +import 'indicators/ema_indicator.dart'; +import 'indicators/macd_indicator.dart'; +import 'indicators/momentum_indicator.dart'; +import 'indicators/roc_indicator.dart'; +import 'indicators/rsi_indicator.dart'; +import 'indicators/sma_indicator.dart'; +import 'indicators/stochastic_indicator.dart'; +import 'indicators/technical_indicator.dart'; +import 'indicators/tma_indicator.dart'; +import 'indicators/wma_indicator.dart'; +import 'interactions/behavior.dart'; +import 'interactions/selection.dart'; +import 'interactions/tooltip.dart'; +import 'series/chart_series.dart'; +import 'utils/constants.dart'; +import 'utils/enum.dart'; +import 'utils/helper.dart'; +import 'utils/typedef.dart'; + +class ChartAreaParentData + extends ContainerBoxParentData {} + +class ChartArea extends MultiChildRenderObjectWidget { + const ChartArea({ + super.key, + required this.legendKey, + required this.legendItems, + this.onChartTouchInteractionDown, + this.onChartTouchInteractionMove, + this.onChartTouchInteractionUp, + required super.children, + }); + + final GlobalKey? legendKey; + final List legendItems; + final ChartTouchInteractionCallback? onChartTouchInteractionDown; + final ChartTouchInteractionCallback? onChartTouchInteractionMove; + final ChartTouchInteractionCallback? onChartTouchInteractionUp; + + @override + ChartAreaRenderObjectElement createElement() { + return ChartAreaRenderObjectElement(this); + } + + @override + RenderChartArea createRenderObject(BuildContext context) { + return RenderChartArea() + ..gestureSettings = MediaQuery.of(context).gestureSettings + ..legendKey = legendKey + ..legendItems = legendItems + ..onChartTouchInteractionDown = onChartTouchInteractionDown + ..onChartTouchInteractionMove = onChartTouchInteractionMove + ..onChartTouchInteractionUp = onChartTouchInteractionUp; + } + + @override + void updateRenderObject(BuildContext context, RenderChartArea renderObject) { + super.updateRenderObject(context, renderObject); + renderObject + ..legendKey = legendKey + ..legendItems = legendItems + ..onChartTouchInteractionDown = onChartTouchInteractionDown + ..onChartTouchInteractionMove = onChartTouchInteractionMove + ..onChartTouchInteractionUp = onChartTouchInteractionUp; + } +} + +class ChartAreaRenderObjectElement extends MultiChildRenderObjectElement { + ChartAreaRenderObjectElement(super.widget); + + late RenderChartArea chartArea; + bool _hasUpdateScheduled = false; + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); // Chart build phase ends here. + chartArea = renderObject as RenderChartArea; + chartArea._scheduleUpdate = _scheduleUpdate; + chartArea._initializeDragGestureRecognizers(); + chartArea._update(); + _hasUpdateScheduled = false; + } + + @override + void update(MultiChildRenderObjectWidget newWidget) { + super.update(newWidget); + chartArea._initializeDragGestureRecognizers(); + chartArea._update(); + _hasUpdateScheduled = false; + } + + @override + void rebuild({bool force = false}) { + super.rebuild(force: force); + if (_hasUpdateScheduled) { + chartArea._update(); + } + _hasUpdateScheduled = false; + } + + void _scheduleUpdate() { + if (!_hasUpdateScheduled && !dirty) { + markNeedsBuild(); + _hasUpdateScheduled = true; + } + } +} + +mixin ChartAreaUpdateMixin on RenderBox { + VoidCallback? _onScheduleUpdate; + VoidCallback? _onScheduleLegendUpdate; + + void markNeedsUpdate() { + _onScheduleUpdate?.call(); + } + + void markNeedsLegendUpdate() { + _onScheduleLegendUpdate?.call(); + } + + @override + void adoptChild(RenderObject child) { + if (child is ChartAreaUpdateMixin) { + child._onScheduleUpdate = _onScheduleUpdate; + child._onScheduleLegendUpdate = _onScheduleLegendUpdate; + } + super.adoptChild(child); + } + + @override + void dropChild(RenderObject child) { + if (child is ChartAreaUpdateMixin) { + child._onScheduleUpdate = null; + child._onScheduleLegendUpdate = null; + } + super.dropChild(child); + } + + void update() { + performUpdate(); + } + + @protected + void performUpdate() { + visitChildren((RenderObject child) { + if (child is ChartAreaUpdateMixin) { + child.update(); + } + }); + } + + @override + void performLayout() { + super.performLayout(); + performPostLayout(); + } + + @protected + void performPostLayout() { + visitChildren((RenderObject child) { + if (child is ChartAreaUpdateMixin) { + child.performPostLayout(); + } + }); + } +} + +class RenderChartArea extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin< + ChartAreaUpdateMixin, + ChartAreaParentData + > + implements MouseTrackerAnnotation { + RenderChartArea() { + final GestureArenaTeam team = GestureArenaTeam(); + + _tapGestureRecognizer = + TapGestureRecognizer() + ..team = team + ..onTapDown = _handleTapDown + ..onTapUp = _handleTapUp + ..gestureSettings = gestureSettings; + + _doubleTapGestureRecognizer = + DoubleTapGestureRecognizer() + ..onDoubleTapDown = _handleDoubleTapDown + ..onDoubleTap = _handleDoubleTap + ..onDoubleTapCancel = _handleDoubleTapCancel + ..gestureSettings = gestureSettings; + + _longPressGestureRecognizer = + LongPressGestureRecognizer() + ..team = team + ..onLongPressStart = _handleLongPressStart + ..onLongPressMoveUpdate = _handleLongPressMoveUpdate + ..onLongPressEnd = _handleLongPressEnd + ..gestureSettings = gestureSettings; + + _scaleGestureRecognizer = + ScaleGestureRecognizer() + ..onStart = _handleScaleStart + ..onUpdate = _handleScaleUpdate + ..onEnd = _handleScaleEnd + ..gestureSettings = gestureSettings; + } + + void _initializeDragGestureRecognizers() { + // This boolean prohibits both x and y scrolls for the parent widget. + bool canHandleXYDrag = false; + // This boolean prohibits x scrolls for the parent widget. + bool canHandleXDrag = false; + // This boolean prohibits y scrolls for the parent widget. + bool canHandleYDrag = false; + + if (!canHandleXYDrag && + crosshairBehavior != null && + crosshairBehavior!.enable && + crosshairBehavior!.activationMode == ActivationMode.singleTap) { + canHandleXYDrag = true; + } + + if (isTransposed != null) { + if ((hasLoadingIndicator != null && hasLoadingIndicator!) || + (trackballBehavior != null && + trackballBehavior!.enable && + trackballBehavior!.activationMode == ActivationMode.singleTap)) { + canHandleXDrag = !isTransposed!; + canHandleYDrag = isTransposed!; + } + } + + if (!canHandleXYDrag || !canHandleXDrag || !canHandleYDrag) { + if (zoomPanBehavior != null && + _behaviorArea != null && + (zoomPanBehavior!.enablePinching || zoomPanBehavior!.enablePanning)) { + final ZoomMode zoomMode = _behaviorArea!.effectiveZoomMode; + canHandleXYDrag = zoomMode == ZoomMode.xy; + canHandleXDrag = zoomMode == ZoomMode.x; + canHandleYDrag = zoomMode == ZoomMode.y; + } + } + + final GestureArenaTeam team = GestureArenaTeam(); + final bool canHandleHorizontalDrag = canHandleXYDrag || canHandleXDrag; + final bool canHandleVerticalDrag = canHandleXYDrag || canHandleYDrag; + _horizontalDragGestureRecognizer = + HorizontalDragGestureRecognizer() + ..team = team + ..onStart = + canHandleHorizontalDrag ? _handleHorizontalDragStart : null + ..onUpdate = + canHandleHorizontalDrag ? _handleHorizontalDragUpdate : null + ..onEnd = canHandleHorizontalDrag ? _handleHorizontalDragEnd : null + ..gestureSettings = gestureSettings; + + _verticalDragGestureRecognizer = + VerticalDragGestureRecognizer() + ..team = team + ..onStart = canHandleVerticalDrag ? _handleVerticalDragStart : null + ..onUpdate = canHandleVerticalDrag ? _handleVerticalDragUpdate : null + ..onEnd = canHandleVerticalDrag ? _handleVerticalDragEnd : null + ..gestureSettings = gestureSettings; + } + + GlobalKey? legendKey; + List? legendItems; + RenderChartPlotArea? _plotArea; + RenderIndicatorArea? _indicatorArea; + RenderBehaviorArea? _behaviorArea; + RenderCartesianAxes? _cartesianAxes; + DeviceGestureSettings? gestureSettings; + + bool _needsLegendUpdate = true; + bool _validForMouseTracker = false; + bool _isScaled = false; + bool _isPanned = false; + late VoidCallback? _scheduleUpdate; + + TapGestureRecognizer? _tapGestureRecognizer; + DoubleTapGestureRecognizer? _doubleTapGestureRecognizer; + LongPressGestureRecognizer? _longPressGestureRecognizer; + ScaleGestureRecognizer? _scaleGestureRecognizer; + HorizontalDragGestureRecognizer? _horizontalDragGestureRecognizer; + VerticalDragGestureRecognizer? _verticalDragGestureRecognizer; + + ChartTouchInteractionCallback? onChartTouchInteractionDown; + ChartTouchInteractionCallback? onChartTouchInteractionMove; + ChartTouchInteractionCallback? onChartTouchInteractionUp; + CrosshairBehavior? crosshairBehavior; + ZoomPanBehavior? zoomPanBehavior; + TrackballBehavior? trackballBehavior; + bool? hasLoadingIndicator; + bool? isTransposed; + + Offset? _doubleTapPosition; + int _pointerCount = 0; + + @override + bool get isRepaintBoundary => true; + + @override + bool get validForMouseTracker => _validForMouseTracker; + + @override + MouseCursor get cursor => MouseCursor.defer; + + @override + PointerEnterEventListener? get onEnter => _handlePointerEnter; + + @override + PointerExitEventListener? get onExit => _handlePointerExit; + + void scheduleUpdateFrame() { + _scheduleUpdate!(); + } + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! ChartAreaParentData) { + child.parentData = ChartAreaParentData(); + } + } + + @override + void insert(ChartAreaUpdateMixin child, {ChartAreaUpdateMixin? after}) { + child._onScheduleUpdate = scheduleUpdateFrame; + child._onScheduleLegendUpdate = markNeedsLegendUpdate; + super.insert(child, after: after); + } + + @override + void remove(ChartAreaUpdateMixin child) { + child._onScheduleUpdate = null; + child._onScheduleLegendUpdate = null; + super.remove(child); + } + + @override + void attach(PipelineOwner owner) { + _validForMouseTracker = true; + super.attach(owner); + } + + @override + void detach() { + _validForMouseTracker = false; + super.detach(); + } + + void markNeedsLegendUpdate() { + _needsLegendUpdate = true; + _collectAndUpdateLegend(); + } + + void _update() { + visitChildren((RenderObject child) { + if (child is ChartAreaUpdateMixin) { + child.update(); + if (child is RenderChartPlotArea) { + _plotArea = child; + } + if (child is RenderBehaviorArea && _plotArea != null) { + _plotArea!.behaviorArea = child; + child.plotArea = _plotArea; + } + } + }); + + _collectAndUpdateLegend(); + } + + void _collectAndUpdateLegend() { + if (_needsLegendUpdate && _plotArea != null) { + legendItems!.clear(); + List? items = _plotArea?._buildLegendItems(); + if (items != null) { + legendItems!.addAll(items); + } + items = _indicatorArea?._buildLegendItems(); + if (items != null) { + legendItems!.addAll(items); + } + (legendKey?.currentState as LegendLayoutState?)?.update(legendItems!); + _needsLegendUpdate = false; + } + } + + @override + void performLayout() { + RenderBox? child = firstChild; + while (child != null) { + final ChartAreaParentData childParentData = + child.parentData! as ChartAreaParentData; + child.layout(constraints); + child = childParentData.nextSibling; + } + + size = constraints.biggest; + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + bool isHit = false; + if (size.contains(position)) { + RenderBox? child = lastChild; + while (child != null) { + final ChartAreaParentData childParentData = + child.parentData! as ChartAreaParentData; + final bool isChildHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + isHit = isHit || isChildHit; + child = childParentData.previousSibling; + } + + isHit = + isHit || + onChartTouchInteractionDown != null || + onChartTouchInteractionMove != null || + onChartTouchInteractionUp != null; + + if (isHit) { + result.add(BoxHitTestEntry(this, position)); + } + } + + return isHit; + } + + bool _isDoubleTapGesture() { + if (_plotArea != null) { + final TooltipBehavior? tooltipBehavior = _plotArea!.tooltipBehavior; + final bool hasSelection = + _plotArea!.selectionGesture == ActivationMode.doubleTap; + final bool hasTooltip = + tooltipBehavior != null && + tooltipBehavior.enable && + tooltipBehavior.activationMode == ActivationMode.doubleTap; + final bool hasTrackball = + trackballBehavior != null && + trackballBehavior!.enable && + trackballBehavior!.activationMode == ActivationMode.doubleTap; + final bool hasCrosshair = + crosshairBehavior != null && + crosshairBehavior!.enable && + crosshairBehavior!.activationMode == ActivationMode.doubleTap; + final bool hasZooming = + zoomPanBehavior != null && zoomPanBehavior!.enableDoubleTapZooming; + + bool hasPointDoubleTap = false; + _plotArea!.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer && + child.isVisible() && + child.onPointDoubleTap != null) { + hasPointDoubleTap = true; + return; + } + }); + return hasSelection || + hasTooltip || + hasTrackball || + hasCrosshair || + hasZooming || + hasPointDoubleTap; + } + return false; + } + + @override + @nonVirtual + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + if (event.kind == PointerDeviceKind.mouse) { + isHover = true; + } + + if (event is PointerDownEvent) { + _pointerCount++; + _tapGestureRecognizer?.addPointer(event); + if (_isDoubleTapGesture()) { + _doubleTapGestureRecognizer?.addPointer(event); + } + _longPressGestureRecognizer?.addPointer(event); + _horizontalDragGestureRecognizer?.addPointer(event); + _verticalDragGestureRecognizer?.addPointer(event); + _scaleGestureRecognizer?.addPointer(event); + _handlePointerDown(event); + } else if (event is PointerMoveEvent) { + if (_pointerCount != 1) { + _horizontalDragGestureRecognizer?.rejectGesture(event.pointer); + _verticalDragGestureRecognizer?.rejectGesture(event.pointer); + _scaleGestureRecognizer?.acceptGesture(event.pointer); + } + _handlePointerMove(event); + } else if (event is PointerHoverEvent) { + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.android) { + _handlePointerHover(event); + } + } else if (event is PointerUpEvent) { + _pointerCount = 0; + _handlePointerUp(event); + } + + if (_isBehaviorAreaHit(event.position)) { + _behaviorArea?.handleEvent(event, entry); + } + } + + bool _isCartesianAxesHit(Offset globalPosition) { + if (_cartesianAxes != null && attached) { + return true; + } + return false; + } + + bool _isPlotAreaHit(Offset globalPosition) { + if (_plotArea != null && attached) { + final Offset localPosition = _plotArea!.globalToLocal(globalPosition); + return _plotArea!.size.contains(localPosition); + } + return false; + } + + bool _isBehaviorAreaHit(Offset globalPosition) { + if (_behaviorArea != null && attached) { + return true; + } + return false; + } + + @protected + void _handlePointerEnter(PointerEnterEvent details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.position)) { + _behaviorArea!.handlePointerEnter(details); + } + } + + @protected + void _handlePointerDown(PointerDownEvent details) { + if (!attached) { + return; + } + onChartTouchInteractionDown?.call( + ChartTouchInteractionArgs()..position = globalToLocal(details.position), + ); + if (_isPlotAreaHit(details.position)) { + _plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child.handlePointerDown(details); + } + }); + } + } + + @protected + void _handlePointerMove(PointerMoveEvent details) { + if (!attached) { + return; + } + onChartTouchInteractionMove?.call( + ChartTouchInteractionArgs()..position = globalToLocal(details.position), + ); + } + + @protected + void _handlePointerHover(PointerHoverEvent details) { + if (!attached) { + return; + } + if (_isCartesianAxesHit(details.position)) { + _cartesianAxes?.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + child.handlePointerHover(details); + } + }); + } + if (_isPlotAreaHit(details.position)) { + _plotArea!.isTooltipActivated = false; + RenderBox? child = _plotArea?.lastChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + if (child is ChartSeriesRenderer && child.isVisible()) { + child.handlePointerHover(details); + } + child = childParentData.previousSibling; + } + } + } + + @protected + void _handlePointerUp(PointerUpEvent details) { + if (!attached) { + return; + } + onChartTouchInteractionUp?.call( + ChartTouchInteractionArgs()..position = globalToLocal(details.position), + ); + if (_isPlotAreaHit(details.position)) { + _plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child.handlePointerUp(details); + } + }); + } + } + + @protected + void _handlePointerExit(PointerExitEvent details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.position)) { + _behaviorArea?.handlePointerExit(details); + } + } + + @protected + void _handleLongPressStart(LongPressStartDetails details) { + if (!attached) { + return; + } + if (_isPlotAreaHit(details.globalPosition)) { + _plotArea!.isTooltipActivated = false; + RenderBox? child = _plotArea?.lastChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + if (child is ChartSeriesRenderer && child.isVisible()) { + child.handleLongPressStart(details); + } + child = childParentData.previousSibling; + } + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _behaviorArea?.handleLongPressStart(details); + } + } + + @protected + void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _behaviorArea?.handleLongPressMoveUpdate(details); + } + } + + @protected + void _handleLongPressEnd(LongPressEndDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _behaviorArea?.handleLongPressEnd(details); + } + } + + @protected + void _handleTapDown(TapDownDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _behaviorArea?.handleTapDown(details); + } + } + + @protected + void _handleTapUp(TapUpDetails details) { + if (!attached) { + return; + } + if (_isCartesianAxesHit(details.globalPosition)) { + _cartesianAxes?.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + child.handleTapUp(details); + } + }); + } + if (_isPlotAreaHit(details.globalPosition)) { + _plotArea!.isTooltipActivated = false; + RenderBox? child = _plotArea?.lastChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + if (child is ChartSeriesRenderer && child.isVisible()) { + child.handleTapUp(details); + } + child = childParentData.previousSibling; + } + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _behaviorArea?.handleTapUp(details); + } + } + + void _handleDoubleTapDown(TapDownDetails details) { + if (!attached) { + return; + } + _doubleTapPosition = details.globalPosition; + } + + @protected + void _handleDoubleTap() { + if (_doubleTapPosition == null || !attached) { + return; + } + if (_isPlotAreaHit(_doubleTapPosition!)) { + _plotArea!.isTooltipActivated = false; + RenderBox? child = _plotArea?.lastChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + if (child is ChartSeriesRenderer && child.isVisible()) { + child.handleDoubleTap(_doubleTapPosition!); + } + child = childParentData.previousSibling; + } + } + if (_isBehaviorAreaHit(_doubleTapPosition!)) { + _behaviorArea?.handleDoubleTap(_doubleTapPosition!); + } + _doubleTapPosition = null; + } + + void _handleDoubleTapCancel() { + if (!attached) { + return; + } + _doubleTapPosition = null; + } + + @protected + void _handleScaleStart(ScaleStartDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.focalPoint)) { + _isScaled = true; + _behaviorArea?.handleScaleStart(details); + } + } + + @protected + void _handleScaleUpdate(ScaleUpdateDetails details) { + if (!attached) { + return; + } + if (_isPlotAreaHit(details.focalPoint)) { + _plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child.handleScaleUpdate(details); + } + }); + } + if (_isBehaviorAreaHit(details.focalPoint)) { + _isScaled = true; + _behaviorArea?.handleScaleUpdate(details); + } + } + + @protected + void _handleScaleEnd(ScaleEndDetails details) { + if (!attached) { + return; + } + if (_isScaled) { + _isScaled = false; + _behaviorArea?.handleScaleEnd(details); + } + } + + @protected + void _handleHorizontalDragStart(DragStartDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _isPanned = true; + _behaviorArea!.handleHorizontalDragStart(details); + } + } + + @protected + void _handleHorizontalDragUpdate(DragUpdateDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _isPanned = true; + _behaviorArea!.handleHorizontalDragUpdate(details); + } + } + + @protected + void _handleHorizontalDragEnd(DragEndDetails details) { + if (!attached) { + return; + } + if (_isPanned) { + _isPanned = false; + _behaviorArea!.handleHorizontalDragEnd(details); + } + } + + @protected + void _handleVerticalDragStart(DragStartDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _isPanned = true; + _behaviorArea!.handleVerticalDragStart(details); + } + } + + @protected + void _handleVerticalDragUpdate(DragUpdateDetails details) { + if (!attached) { + return; + } + if (_isBehaviorAreaHit(details.globalPosition)) { + _isPanned = true; + _behaviorArea!.handleVerticalDragUpdate(details); + } + } + + @protected + void _handleVerticalDragEnd(DragEndDetails details) { + if (!attached) { + return; + } + if (_isPanned) { + _isPanned = false; + _behaviorArea!.handleVerticalDragEnd(details); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + defaultPaint(context, offset); + } + + @override + void dispose() { + legendItems?.clear(); + + _tapGestureRecognizer?.dispose(); + _doubleTapGestureRecognizer?.dispose(); + _longPressGestureRecognizer?.dispose(); + _scaleGestureRecognizer?.dispose(); + super.dispose(); + } +} + +class CartesianChartArea extends ChartArea { + const CartesianChartArea({ + super.key, + required super.legendKey, + required super.legendItems, + required this.isTransposed, + this.hasLoadingIndicator = false, + this.plotAreaBackgroundImage, + this.plotAreaBackgroundColor, + this.crosshairBehavior, + this.trackballBehavior, + this.zoomPanBehavior, + super.onChartTouchInteractionDown, + super.onChartTouchInteractionMove, + super.onChartTouchInteractionUp, + required super.children, + }); + + final ImageProvider? plotAreaBackgroundImage; + final Color? plotAreaBackgroundColor; + final bool isTransposed; + final bool hasLoadingIndicator; + final CrosshairBehavior? crosshairBehavior; + final TrackballBehavior? trackballBehavior; + final ZoomPanBehavior? zoomPanBehavior; + + @override + RenderCartesianChartArea createRenderObject(BuildContext context) { + return RenderCartesianChartArea() + ..gestureSettings = MediaQuery.of(context).gestureSettings + ..legendKey = legendKey + ..legendItems = legendItems + ..isTransposed = isTransposed + ..hasLoadingIndicator = hasLoadingIndicator + ..plotAreaBackgroundImage = plotAreaBackgroundImage + ..plotAreaBackgroundColor = plotAreaBackgroundColor + ..onChartTouchInteractionDown = onChartTouchInteractionDown + ..onChartTouchInteractionMove = onChartTouchInteractionMove + ..onChartTouchInteractionUp = onChartTouchInteractionUp + ..crosshairBehavior = crosshairBehavior + ..trackballBehavior = trackballBehavior + ..zoomPanBehavior = zoomPanBehavior; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCartesianChartArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..isTransposed = isTransposed + ..hasLoadingIndicator = hasLoadingIndicator + ..plotAreaBackgroundImage = plotAreaBackgroundImage + ..plotAreaBackgroundColor = plotAreaBackgroundColor + ..crosshairBehavior = crosshairBehavior + ..trackballBehavior = trackballBehavior + ..zoomPanBehavior = zoomPanBehavior; + } +} + +class RenderCartesianChartArea extends RenderChartArea { + RenderCartesianChartArea(); + + RenderAnnotationArea? _annotationArea; + Image? _plotAreaImage; + + RenderCartesianAxes? get cartesianAxes => _cartesianAxes; + + ImageProvider? get plotAreaBackgroundImage => _plotAreaBackgroundImage; + ImageProvider? _plotAreaBackgroundImage; + set plotAreaBackgroundImage(ImageProvider? value) { + if (_plotAreaBackgroundImage != value) { + _plotAreaBackgroundImage = value; + _fetchImage(); + } + } + + Color? get plotAreaBackgroundColor => _plotAreaBackgroundColor; + Color? _plotAreaBackgroundColor; + set plotAreaBackgroundColor(Color? value) { + if (plotAreaBackgroundColor != value) { + _plotAreaBackgroundColor = value; + markNeedsPaint(); + } + } + + @override + void _update() { + if (_cartesianAxes != null) { + _cartesianAxes!.update(); + } + + if (_plotArea != null) { + _plotArea!.update(); + if (_behaviorArea != null) { + _behaviorArea!.update(); + _plotArea!.behaviorArea = _behaviorArea; + } + } + + if (_indicatorArea != null) { + _indicatorArea!.update(); + } + + if (_annotationArea != null) { + _annotationArea!.update(); + } + + markNeedsLegendUpdate(); + } + + @override + void insert(ChartAreaUpdateMixin child, {ChartAreaUpdateMixin? after}) { + if (child is RenderCartesianChartPlotArea) { + _plotArea = child; + } + + if (child is RenderCartesianAxes) { + _cartesianAxes = child; + (_plotArea as RenderCartesianChartPlotArea?)?._cartesianAxes = child; + } + + if (child is RenderBehaviorArea) { + _behaviorArea = child; + _behaviorArea!.cartesianAxes = _cartesianAxes; + _behaviorArea!.indicatorArea = _indicatorArea; + _behaviorArea!.plotArea = _plotArea; + _cartesianAxes?.behaviorArea = _behaviorArea; + } + + if (child is RenderIndicatorArea) { + _indicatorArea = child; + } + + if (child is RenderAnnotationArea) { + _annotationArea = child; + } + + super.insert(child, after: after); + } + + @override + void remove(ChartAreaUpdateMixin child) { + if (child is RenderCartesianAxes) { + _cartesianAxes = null; + } + + if (child is RenderCartesianChartPlotArea) { + _plotArea = null; + child._cartesianAxes = null; + } + + if (child is RenderBehaviorArea) { + _behaviorArea = null; + child.cartesianAxes = null; + child.plotArea = null; + _cartesianAxes?.behaviorArea = null; + } + + if (child is RenderIndicatorArea) { + _indicatorArea = null; + } + + if (child is RenderAnnotationArea) { + _annotationArea = null; + } + + super.remove(child); + } + + @override + void performLayout() { + assert(_cartesianAxes != null); + if (_cartesianAxes != null) { + _cartesianAxes?.layout(constraints, parentUsesSize: true); + } + + assert(_cartesianAxes!._plotAreaConstraints != null); + assert(_plotArea != null); + if (_plotArea != null) { + final Offset plotAreaOffset = _cartesianAxes!.plotAreaOffset; + final BoxConstraints plotAreaConstraints = + _cartesianAxes!._plotAreaConstraints!; + + ChartAreaParentData childParentData = + _plotArea!.parentData! as ChartAreaParentData; + childParentData.offset = plotAreaOffset; + _plotArea!.layout(plotAreaConstraints, parentUsesSize: true); + + if (_behaviorArea != null && _behaviorArea!.parentData != null) { + childParentData = _behaviorArea!.parentData! as ChartAreaParentData; + childParentData.offset = plotAreaOffset; + _behaviorArea!.layout(plotAreaConstraints); + } + + if (_indicatorArea != null) { + final ChartAreaParentData indicatorParentData = + _indicatorArea!.parentData! as ChartAreaParentData; + indicatorParentData.offset = _cartesianAxes!.plotAreaOffset; + _indicatorArea!.layout(_cartesianAxes!._plotAreaConstraints!); + } + + if (_annotationArea != null) { + _annotationArea!._plotAreaOffset = plotAreaOffset; + _annotationArea!._plotAreaBounds = plotAreaOffset & _plotArea!.size; + _annotationArea!.layout(constraints); + } + } + + size = constraints.biggest; + } + + void _fetchImage() { + if (plotAreaBackgroundImage != null) { + fetchImage(plotAreaBackgroundImage).then((Image? value) { + _plotAreaImage = value; + markNeedsPaint(); + }); + } else { + _plotAreaImage = null; + } + } + + @override + void paint(PaintingContext context, Offset offset) { + // The plot area image and background should be drawn in the chart area, + // as it is meant to be positioned below the axis grid lines. + if (_plotArea != null) { + final Offset paintOffset = + (_plotArea!.parentData! as BoxParentData).offset; + final Rect rect = (offset + paintOffset) & _plotArea!.size; + if (_plotAreaImage != null) { + paintImage( + canvas: context.canvas, + rect: rect, + image: _plotAreaImage!, + fit: BoxFit.fill, + ); + } + + if (plotAreaBackgroundColor != null) { + context.canvas.drawRect( + rect, + Paint() + ..isAntiAlias = true + ..color = plotAreaBackgroundColor!, + ); + } + } + + if (_cartesianAxes != null) { + _cartesianAxes!._isGridLinePaint = true; + context.paintChild( + _cartesianAxes!, + (_cartesianAxes!.parentData! as BoxParentData).offset + offset, + ); + } + + defaultPaint(context, offset); + } + + @override + void dispose() { + _plotAreaImage?.dispose(); + super.dispose(); + } +} + +class ChartPlotArea extends MultiChildRenderObjectWidget { + const ChartPlotArea({ + super.key, + required this.vsync, + required this.localizations, + required this.legendKey, + required this.backgroundColor, + this.borderColor, + required this.borderWidth, + this.enableAxisAnimation = false, + required this.legend, + required this.onLegendItemRender, + required this.onLegendTapped, + this.onDataLabelRender, + this.onMarkerRender, + this.onTooltipRender, + this.onDataLabelTapped, + required this.palette, + required this.selectionMode, + required this.selectionGesture, + required this.enableMultiSelection, + this.tooltipBehavior, + this.onSelectionChanged, + required this.chartThemeData, + required this.themeData, + required super.children, + }); + + final TickerProvider vsync; + final SfLocalizations? localizations; + final GlobalKey? legendKey; + final Color? backgroundColor; + final Color? borderColor; + final double borderWidth; + final bool enableAxisAnimation; + final Legend legend; + final ChartLegendRenderCallback? onLegendItemRender; + final ChartLegendTapCallback? onLegendTapped; + final ChartDataLabelRenderCallback? onDataLabelRender; + final ChartMarkerRenderCallback? onMarkerRender; + final ChartTooltipCallback? onTooltipRender; + final DataLabelTapCallback? onDataLabelTapped; + final List palette; + final SelectionType selectionMode; + final ActivationMode selectionGesture; + final bool enableMultiSelection; + final TooltipBehavior? tooltipBehavior; + final ChartSelectionCallback? onSelectionChanged; + final SfChartThemeData chartThemeData; + final ThemeData themeData; + + @override + RenderChartPlotArea createRenderObject(BuildContext context) { + final RenderChartPlotArea plotArea = RenderChartPlotArea(); + plotArea + ..vsync = vsync + ..backgroundColor = backgroundColor + ..borderColor = borderColor + ..borderWidth = borderWidth + ..enableAxisAnimation = enableAxisAnimation + ..legend = legend + ..onLegendItemRender = onLegendItemRender + ..onLegendTapped = onLegendTapped + ..onDataLabelRender = onDataLabelRender + ..onMarkerRender = onMarkerRender + ..onTooltipRender = onTooltipRender + ..onDataLabelTapped = onDataLabelTapped + ..palette = palette + ..selectionMode = selectionMode + ..selectionGesture = selectionGesture + ..enableMultiSelection = enableMultiSelection + ..tooltipBehavior = tooltipBehavior + ..onSelectionChanged = onSelectionChanged + ..chartThemeData = chartThemeData + ..themeData = themeData; + return plotArea; + } + + @override + void updateRenderObject( + BuildContext context, + RenderChartPlotArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..backgroundColor = backgroundColor + ..borderColor = borderColor + ..borderWidth = borderWidth + ..enableAxisAnimation = enableAxisAnimation + ..legend = legend + ..onLegendItemRender = onLegendItemRender + ..onLegendTapped = onLegendTapped + ..onDataLabelRender = onDataLabelRender + ..onDataLabelTapped = onDataLabelTapped + ..onMarkerRender = onMarkerRender + ..onTooltipRender = onTooltipRender + ..palette = palette + ..selectionMode = selectionMode + ..selectionGesture = selectionGesture + ..enableMultiSelection = enableMultiSelection + ..tooltipBehavior = tooltipBehavior + ..onSelectionChanged = onSelectionChanged + ..chartThemeData = chartThemeData + ..themeData = themeData; + } +} + +class RenderChartPlotArea extends RenderStack with ChartAreaUpdateMixin { + RenderChartPlotArea({super.textDirection = TextDirection.ltr}); + + GlobalKey? legendKey; + Legend? legend; + + ChartLegendRenderCallback? onLegendItemRender; + ChartLegendTapCallback? onLegendTapped; + ChartDataLabelRenderCallback? onDataLabelRender; + ChartTooltipCallback? onTooltipRender; + ChartMarkerRenderCallback? onMarkerRender; + SeriesRender render = SeriesRender.normal; + + DataLabelTapCallback? onDataLabelTapped; + ChartSelectionCallback? onSelectionChanged; + + late final SelectionController selectionController = SelectionController( + this, + ); + RenderBehaviorArea? behaviorArea; + SfLocalizations? localizations; + + Function(Offset)? onTouchDown; + Function(Offset)? onTouchMove; + Function(Offset)? onTouchUp; + + /// The [TickerProvider] for the [AnimationController] that + /// runs the animation. + TickerProvider? get vsync => _vsync; + TickerProvider? _vsync; + set vsync(TickerProvider? value) { + if (_vsync != value) { + _vsync = value; + } + } + + Color? get backgroundColor => _backgroundColor; + Color? _backgroundColor; + set backgroundColor(Color? value) { + if (_backgroundColor != value) { + _backgroundColor = value; + markNeedsPaint(); + } + } + + Color? get borderColor => _borderColor; + Color? _borderColor; + set borderColor(Color? value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + double get borderWidth => _borderWidth; + double _borderWidth = 1; + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsPaint(); + } + } + + bool get enableAxisAnimation => _enableAxisAnimation; + bool _enableAxisAnimation = false; + set enableAxisAnimation(bool value) { + if (_enableAxisAnimation != value) { + _enableAxisAnimation = value; + } + } + + List? get palette => _palette; + List? _palette; + set palette(List? value) { + if (_palette != value) { + _palette = value; + } + } + + SelectionType get selectionMode => _selectionMode; + SelectionType _selectionMode = SelectionType.point; + set selectionMode(SelectionType value) { + if (_selectionMode != value) { + selectionController.resetSelection(); + _selectionMode = value; + } + } + + ActivationMode get selectionGesture => _selectionGesture; + ActivationMode _selectionGesture = ActivationMode.singleTap; + set selectionGesture(ActivationMode value) { + if (_selectionGesture != value) { + _selectionGesture = value; + } + } + + bool get enableMultiSelection => _enableMultipleSelection; + bool _enableMultipleSelection = false; + set enableMultiSelection(bool value) { + if (_enableMultipleSelection != value) { + _enableMultipleSelection = value; + selectionController.enableMultiSelection = value; + } + } + + TooltipBehavior? get tooltipBehavior => _tooltipBehavior; + TooltipBehavior? _tooltipBehavior; + set tooltipBehavior(TooltipBehavior? value) { + if (_tooltipBehavior != value) { + _tooltipBehavior = value; + } + } + + TrackballBehavior? get trackballBehavior => _trackballBehavior; + TrackballBehavior? _trackballBehavior; + set trackballBehavior(TrackballBehavior? value) { + if (_trackballBehavior != value) { + _trackballBehavior = value; + } + } + + SfChartThemeData? get chartThemeData => _chartThemeData; + SfChartThemeData? _chartThemeData; + set chartThemeData(SfChartThemeData? value) { + if (_chartThemeData != value) { + _chartThemeData = value; + } + } + + ThemeData? get themeData => _themeData; + ThemeData? _themeData; + set themeData(ThemeData? value) { + if (_themeData != value) { + _themeData = value; + } + } + + // Imagine three series, each with the same data point. Example: [1, 20]. + // First click on the data point, should shown 3rd series tooltip. + // On the second click on the same data point, check 3rd series tooltip is + // already in visible or not. If the 3rd series tooltip is visible, keep it + // displayed and avoid 1st and 2nd series tooltip method calling. + // And, it specifies for hover, singleTap, doubleTap and longPress methods. + bool isTooltipActivated = false; + + List? _buildLegendItems() { + int index = 0; + final List legendItems = []; + visitChildren((RenderObject child) { + final LegendItemProviderMixin provider = child as LegendItemProviderMixin; + final List? items = provider.buildLegendItems(index); + if (items != null) { + legendItems.addAll(items); + } + index++; + }); + + return legendItems; + } + + @override + void performUpdate() { + int index = 0; + visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child + ..index = index++ + ..chartThemeData = _chartThemeData + ..palette = _palette!; + } + }); + + super.performUpdate(); + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + bool isHit = false; + RenderBox? child = lastChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + final bool isChildHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + isHit = isHit || isChildHit; + child = childParentData.previousSibling; + } + + return isHit; + } + + @override + void paint(PaintingContext context, Offset offset) { + final Rect bounds = Rect.fromLTWH( + offset.dx, + offset.dy, + size.width, + size.height, + ); + if (backgroundColor != null) { + context.canvas.drawRect( + bounds, + Paint() + ..isAntiAlias = true + ..color = backgroundColor!, + ); + } + + if (_borderColor != null && + _borderColor != Colors.transparent && + _borderWidth > 0) { + context.canvas.drawRect( + bounds, + Paint() + ..isAntiAlias = true + ..color = _borderColor! + ..strokeWidth = _borderWidth + ..style = PaintingStyle.stroke, + ); + } + super.paint(context, offset); + } +} + +class CartesianChartPlotArea extends ChartPlotArea { + const CartesianChartPlotArea({ + super.key, + required super.vsync, + required super.localizations, + required super.legendKey, + required this.isTransposed, + required super.backgroundColor, + required super.borderColor, + required super.borderWidth, + required super.enableAxisAnimation, + required this.enableSideBySideSeriesPlacement, + required super.legend, + required super.onLegendItemRender, + required super.onLegendTapped, + required super.onDataLabelRender, + required super.onMarkerRender, + required super.onTooltipRender, + required super.onDataLabelTapped, + required super.palette, + required super.selectionMode, + required super.selectionGesture, + required super.enableMultiSelection, + super.tooltipBehavior, + this.crosshairBehavior, + this.trackballBehavior, + this.zoomPanBehavior, + super.onSelectionChanged, + required super.chartThemeData, + required super.themeData, + required super.children, + }); + + final bool isTransposed; + final bool enableSideBySideSeriesPlacement; + final CrosshairBehavior? crosshairBehavior; + final TrackballBehavior? trackballBehavior; + final ZoomPanBehavior? zoomPanBehavior; + + @override + RenderCartesianChartPlotArea createRenderObject(BuildContext context) { + return RenderCartesianChartPlotArea() + ..vsync = vsync + ..legendKey = legendKey + ..backgroundColor = backgroundColor + ..borderColor = borderColor + ..borderWidth = borderWidth + ..legend = legend + ..onLegendItemRender = onLegendItemRender + ..onLegendTapped = onLegendTapped + ..onDataLabelRender = onDataLabelRender + ..onDataLabelTapped = onDataLabelTapped + ..onTooltipRender = onTooltipRender + ..onMarkerRender = onMarkerRender + ..palette = palette + ..selectionMode = selectionMode + ..selectionGesture = selectionGesture + ..enableMultiSelection = enableMultiSelection + ..tooltipBehavior = tooltipBehavior + ..chartThemeData = chartThemeData + ..themeData = themeData + ..isTransposed = isTransposed + ..enableSideBySideSeriesPlacement = enableSideBySideSeriesPlacement + ..crosshairBehavior = crosshairBehavior + ..trackballBehavior = trackballBehavior + ..zoomPanBehavior = zoomPanBehavior + ..onSelectionChanged = onSelectionChanged; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCartesianChartPlotArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..isTransposed = isTransposed + ..enableSideBySideSeriesPlacement = enableSideBySideSeriesPlacement + ..crosshairBehavior = crosshairBehavior + ..trackballBehavior = trackballBehavior + ..zoomPanBehavior = zoomPanBehavior; + } +} + +class RenderCartesianChartPlotArea extends RenderChartPlotArea { + late num _primaryAxisAdjacentDataPointsMinDiff; + RenderCartesianAxes? _cartesianAxes; + Map>? sbsDetails; + bool isLegendToggled = false; + + bool get isTransposed => _isTransposed; + bool _isTransposed = false; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + visitChildren((RenderObject child) { + if (child is AxisDependent) { + final AxisDependent axisDependent = child as AxisDependent; + axisDependent.isTransposed = isTransposed; + } + }); + markNeedsLayout(); + } + } + + bool get enableSideBySideSeriesPlacement => _enableSideBySideSeriesPlacement; + bool _enableSideBySideSeriesPlacement = true; + set enableSideBySideSeriesPlacement(bool value) { + if (_enableSideBySideSeriesPlacement != value) { + _enableSideBySideSeriesPlacement = value; + markNeedsUpdate(); + } + } + + CrosshairBehavior? get crosshairBehavior => _crosshairBehavior; + CrosshairBehavior? _crosshairBehavior; + set crosshairBehavior(CrosshairBehavior? value) { + if (_crosshairBehavior != value) { + _crosshairBehavior = value; + } + } + + ZoomPanBehavior? get zoomPanBehavior => _zoomPanBehavior; + ZoomPanBehavior? _zoomPanBehavior; + set zoomPanBehavior(ZoomPanBehavior? value) { + if (_zoomPanBehavior != value) { + _zoomPanBehavior = value; + } + } + + SelectionBehavior? get selectionBehavior => _selectionBehavior; + SelectionBehavior? _selectionBehavior; + set selectionBehavior(SelectionBehavior? value) { + if (_selectionBehavior != value) { + _selectionBehavior = value; + } + } + + @override + void remove(RenderBox child) { + if (child is CartesianSeriesRenderer) { + if (child.xAxis != null && child.xAxis!.dependents.contains(child)) { + child.xAxis!.dependents.remove(child); + } + if (child.yAxis != null && child.yAxis!.dependents.contains(child)) { + child.yAxis!.dependents.remove(child); + } + } + super.remove(child); + } + + void _resetSbsInfo() { + if (sbsDetails != null) { + sbsDetails!.clear(); + } else { + sbsDetails = >{}; + } + + visitChildren((RenderObject child) { + if (child is ClusterSeriesMixin) { + final ClusterSeriesMixin cluster = child as ClusterSeriesMixin; + cluster._clusterIndex = -1; + if (child is SbsSeriesMixin) { + child.sbsInfo = DoubleRange.zero(); + } + } + }); + } + + void _groupClusterSeries() { + int index = 0; + _resetSbsInfo(); + visitChildren((RenderObject child) { + if (child is! ClusterSeriesMixin) { + return; + } + + final AxisDependent? series = child as AxisDependent?; + if (series == null || + series.xAxis == null || + !(series as CartesianSeriesRenderer).controller.isVisible) { + return; + } + + final Map groupingKeys = {}; + for (final AxisDependent xDependent in series.xAxis!.dependents) { + final bool isSeries = xDependent is CartesianSeriesRenderer; + if ((isSeries && !xDependent.controller.isVisible) || + xDependent is ClusterSeriesMixin && + (xDependent as ClusterSeriesMixin)._clusterIndex != -1) { + continue; + } + + if (xDependent is StackingSeriesMixin) { + if (xDependent.yAxis == null) { + continue; + } + + for (final AxisDependent yDependent in xDependent.yAxis!.dependents) { + if (yDependent is! CartesianSeriesRenderer || + !yDependent.controller.isVisible) { + continue; + } + if (xDependent.xAxis!.dependents.contains(yDependent)) { + if (yDependent is StackingSeriesMixin) { + final StackingSeriesMixin stacked = + yDependent as StackingSeriesMixin; + final String groupName = stacked.groupName; + final int size = + sbsDetails!.isNotEmpty && + groupingKeys.isNotEmpty && + groupingKeys.containsKey(groupName) + ? sbsDetails![groupingKeys[groupName]]!.length + : 0; + bool isSameType = false; + StackingSeriesMixin? previous; + if (size > 0) { + previous = + sbsDetails![groupingKeys[groupName]]![size - 1] + as StackingSeriesMixin?; + isSameType = + previous != null && + previous.yAxis!.dependents.contains(yDependent) && + previous.runtimeType == yDependent.runtimeType; + } + + if (groupingKeys.containsKey(groupName) && isSameType) { + sbsDetails![groupingKeys[groupName]]!.add(yDependent); + if (stacked is ClusterSeriesMixin) { + (stacked as ClusterSeriesMixin)._clusterIndex = + groupingKeys[groupName]!; + } + } else { + groupingKeys.update( + groupName, + (int value) => index, + ifAbsent: () => index, + ); + + sbsDetails!.update( + index, + (List values) { + values.add(yDependent); + return values; + }, + ifAbsent: () { + return [yDependent]; + }, + ); + + series.xAxis!.sbsSeriesCount = index + 1; + if (yDependent is ClusterSeriesMixin) { + (yDependent as ClusterSeriesMixin)._clusterIndex = index; + } + index++; + } + } + } + } + } else if (xDependent is SbsSeriesMixin) { + sbsDetails!.update( + index, + (List values) { + values.add(xDependent); + return values; + }, + ifAbsent: () { + return [xDependent]; + }, + ); + + if (xDependent is ClusterSeriesMixin) { + (xDependent as ClusterSeriesMixin)._clusterIndex = index; + } + series.xAxis!.sbsSeriesCount = index + 1; + index++; + } + } + + _computeSbsInfo(); + index = 0; + }); + } + + double _calculateSbsTotalWidth() { + double totalWidth = 0; + num minDiff = double.infinity; + for (final List rendererGroup in sbsDetails!.values) { + double maxWidth = 0; + for (final AxisDependent renderer in rendererGroup) { + SbsSeriesMixin? sbs; + if (renderer is SbsSeriesMixin) { + sbs = renderer; + } + + if (sbs != null && sbs.dataCount > 0) { + maxWidth = maxWidth > sbs.width ? maxWidth : sbs.width; + minDiff = min(sbs.primaryAxisAdjacentDataPointsMinDiff, minDiff); + } + } + + totalWidth += maxWidth; + } + + _primaryAxisAdjacentDataPointsMinDiff = minDiff.isInfinite ? 1 : minDiff; + return totalWidth; + } + + double _calculateSbsMaxWidth(List rendererGroup) { + double maxWidth = 0; + for (final AxisDependent renderer in rendererGroup) { + SbsSeriesMixin? sbs; + if (renderer is SbsSeriesMixin) { + sbs = renderer; + } + + if (sbs != null) { + maxWidth = maxWidth > sbs.width ? maxWidth : sbs.width; + } + } + + return maxWidth; + } + + void _computeSbsInfo() { + if (sbsDetails != null && sbsDetails!.isNotEmpty) { + final double totalWidth = _calculateSbsTotalWidth() / sbsDetails!.length; + double startPosition = 0; + double end = 0; + for (final List rendererGroup in sbsDetails!.values) { + final double sbsMaxWidth = _calculateSbsMaxWidth(rendererGroup); + for (final AxisDependent renderer in rendererGroup) { + SbsSeriesMixin? sbs; + if (renderer is SbsSeriesMixin) { + sbs = renderer; + } + + if (sbs == null || !sbs.controller.isVisible) { + continue; + } + + final double width = sbs.width; + if (!enableSideBySideSeriesPlacement) { + final double range = + (width * _primaryAxisAdjacentDataPointsMinDiff) / 2; + sbs.sbsInfo = DoubleRange(-range, range); + continue; + } + + if (sbs.xAxis == null) { + sbs.sbsInfo = DoubleRange(0, 1); + } + + final int seriesCount = + sbs.xAxis != null ? sbs.xAxis!.sbsSeriesCount : 0; + if ((sbs as ClusterSeriesMixin)._clusterIndex == 0) { + startPosition = + -_primaryAxisAdjacentDataPointsMinDiff * (totalWidth / 2); + } + + final double space = (sbsMaxWidth - width) / seriesCount; + double start = + startPosition + + ((space * _primaryAxisAdjacentDataPointsMinDiff) / 2); + + end = + start + + ((width / seriesCount) * _primaryAxisAdjacentDataPointsMinDiff); + final double delta = end - start; + final double spacing = sbs.spacing * delta; + + start += spacing / 2; + end -= spacing / 2; + + sbs.sbsInfo = DoubleRange(start, end); + end += spacing / 2; + } + + startPosition = end; + } + } + } + + @override + void performUpdate() { + visitChildren((RenderObject child) { + if (child is ChartAreaUpdateMixin) { + child.update(); + } + }); + + visitChildren((RenderObject child) { + final AxisDependent? axisDependent = child as AxisDependent?; + if (axisDependent != null) { + final RenderChartAxis? axis = axisDependent.xAxis; + if (axis is RenderCategoryAxis && !axis.arrangeByIndex) { + axis.update(); + } + } + }); + + _groupClusterSeries(); + _computeSbsInfo(); + int index = -1; + visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer) { + child + ..index = index++ + ..chartThemeData = _chartThemeData + ..paletteColor = _palette![index % _palette!.length] + ..isTransposed = isTransposed; + } + }); + super.performUpdate(); + } + + @override + void performLayout() { + super.performLayout(); + if (_cartesianAxes != null && parentData != null) { + _cartesianAxes!.plotAreaBounds = + (parentData! as BoxParentData).offset & size; + } + } + + bool _hasDataLabel() { + RenderBox? child = firstChild; + while (child != null) { + if (child is CartesianSeriesRenderer) { + if (child.dataLabelSettings.isVisible) { + return true; + } + } + child = childAfter(child); + } + return false; + } + + bool _hasTrendline() { + RenderBox? child = firstChild; + while (child != null) { + if (child is CartesianSeriesRenderer) { + if (child.trendlines != null && child.trendlines!.isNotEmpty) { + return true; + } + } + child = childAfter(child); + } + return false; + } + + @override + void paint(PaintingContext context, Offset offset) { + render = SeriesRender.normal; + super.paint(context, offset); + if (_hasDataLabel()) { + render = SeriesRender.dataLabel; + RenderBox? child = firstChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + context.paintChild(child, childParentData.offset + offset); + child = childParentData.nextSibling; + } + } + + if (_hasTrendline()) { + render = SeriesRender.trendline; + RenderBox? child = firstChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + context.paintChild(child, childParentData.offset + offset); + child = childParentData.nextSibling; + } + } + render = SeriesRender.normal; + } +} + +enum SeriesRender { normal, dataLabel, trendline } + +/// Represents the side by side and stacking series i.e. the series which +/// are grouped together. +mixin ClusterSeriesMixin { + int _clusterIndex = -1; +} + +class CartesianAxes extends MultiChildRenderObjectWidget { + const CartesianAxes({ + super.key, + required this.vsync, + required this.enableAxisAnimation, + required this.isTransposed, + required this.onAxisLabelTapped, + required this.onActualRangeChanged, + required this.indicators, + required this.chartThemeData, + super.children, + }); + + final TickerProvider vsync; + final bool enableAxisAnimation; + final bool isTransposed; + final ChartAxisLabelTapCallback? onAxisLabelTapped; + final ChartActualRangeChangedCallback? onActualRangeChanged; + final List indicators; + final SfChartThemeData chartThemeData; + + @override + RenderCartesianAxes createRenderObject(BuildContext context) { + return RenderCartesianAxes( + vsync: vsync, + enableAxisAnimation: enableAxisAnimation, + isTransposed: isTransposed, + chartThemeData: chartThemeData, + ) + ..onAxisLabelTapped = onAxisLabelTapped + ..indicators = indicators + ..onActualRangeChanged = onActualRangeChanged; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCartesianAxes renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..vsync = vsync + ..enableAxisAnimation = enableAxisAnimation + ..isTransposed = isTransposed + ..onAxisLabelTapped = onAxisLabelTapped + ..onActualRangeChanged = onActualRangeChanged + ..indicators = indicators + ..chartThemeData = chartThemeData; + } +} + +class RenderCartesianAxes extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin< + RenderChartAxis, + CartesianAxesParentData + >, + ChartAreaUpdateMixin { + RenderCartesianAxes({ + required TickerProvider vsync, + required bool enableAxisAnimation, + required bool isTransposed, + required SfChartThemeData chartThemeData, + }) : _vsync = vsync, + _enableAxisAnimation = enableAxisAnimation, + _isTransposed = isTransposed, + _chartThemeData = chartThemeData; + RenderBehaviorArea? behaviorArea; + BoxConstraints? _plotAreaConstraints; + Offset plotAreaOffset = Offset.zero; + Rect plotAreaBounds = Rect.zero; + bool _isGridLinePaint = false; + + final Map axes = {}; + final List _xAxes = []; + final List _yAxes = []; + + ChartAxisLabelTapCallback? onAxisLabelTapped; + ChartActualRangeChangedCallback? onActualRangeChanged; + + bool get enableAxisAnimation => _enableAxisAnimation; + bool _enableAxisAnimation = false; + set enableAxisAnimation(bool value) { + if (_enableAxisAnimation != value) { + _enableAxisAnimation = value; + } + } + + /// The [TickerProvider] for the [AnimationController] that + /// runs the animation. + TickerProvider get vsync => _vsync; + TickerProvider _vsync; + set vsync(TickerProvider value) { + if (_vsync != value) { + _vsync = value; + } + } + + bool get isTransposed => _isTransposed; + bool _isTransposed; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + markNeedsUpdate(); + } + } + + List get indicators => _indicators; + List _indicators = []; + set indicators(List value) { + if (_indicators != value) { + _indicators = value; + markNeedsUpdate(); + } + } + + SfChartThemeData get chartThemeData => _chartThemeData; + SfChartThemeData _chartThemeData; + set chartThemeData(SfChartThemeData value) { + if (_chartThemeData != value) { + _chartThemeData = value; + markNeedsUpdate(); + } + } + + @override + void remove(RenderChartAxis child) { + super.remove(child); + + if (_xAxes.contains(child)) { + _xAxes.remove(child); + } + + if (_yAxes.contains(child)) { + _yAxes.remove(child); + } + + if (axes.containsValue(child)) { + axes.removeWhere((String? key, RenderChartAxis value) => value == child); + } + } + + @override + void performUpdate() { + if (firstChild == null) { + return; + } + + final CartesianAxesParentData firstChildParentData = + firstChild!.parentData! as CartesianAxesParentData; + final String primaryXAxisName = firstChild!.name ?? primaryXAxisDefaultName; + firstChild!.name = primaryXAxisName; + // Sets isXAxis value defaults to true for primaryXAxis here because its + // value changed while adding multiple axes to the axes collection and maps + // series with different x axis instead of primary axis. + firstChild!.isXAxis = true; + + assert(firstChildParentData.nextSibling != null); + final RenderChartAxis primaryYAxis = firstChildParentData.nextSibling!; + final String primaryYAxisName = + primaryYAxis.name ?? primaryYAxisDefaultName; + primaryYAxis.name = primaryYAxisName; + + visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + assert(axes[child.name] == null || axes[child.name] == child); + axes[child.name] = child; + } + }); + + final RenderCartesianChartArea? chartArea = + parent as RenderCartesianChartArea?; + assert(chartArea != null); + if (chartArea != null) { + assert(chartArea._plotArea != null); + if (chartArea._plotArea!.childCount > 0) { + chartArea._plotArea!.visitChildren((RenderObject child) { + final AxisDependent? axisDependent = child as AxisDependent?; + if (axisDependent != null) { + RenderChartAxis? axis = + axes[axisDependent.xAxisName ?? primaryXAxisName]; + axisDependent.xAxis = axis; + + axis = axes[axisDependent.yAxisName ?? primaryYAxisName]; + axisDependent.yAxis = axis; + } + }); + } else { + firstChild!.isXAxis = true; + firstChildParentData.nextSibling!.isXAxis = false; + } + + if (chartArea._indicatorArea != null) { + chartArea._indicatorArea!.visitChildren((RenderObject child) { + if (child is IndicatorRenderer) { + child.xAxis = axes[child.xAxisName ?? primaryXAxisName]; + child.yAxis = axes[child.yAxisName ?? primaryYAxisName]; + } + }); + } + } + + visitChildren((RenderObject child) { + final RenderChartAxis axis = child as RenderChartAxis; + axis.isTransposed = isTransposed; + axis.chartThemeData = chartThemeData; + if (axis.isXAxis) { + if (!_xAxes.contains(axis)) { + _xAxes.add(axis); + } + if (_yAxes.contains(axis)) { + _yAxes.remove(axis); + } + } else { + if (!_yAxes.contains(axis)) { + _yAxes.add(axis); + } + if (_xAxes.contains(axis)) { + _xAxes.remove(axis); + } + } + }); + + visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + child.associatedAxis = _associatedAxis(child); + } + }); + + for (final RenderChartAxis axis in _xAxes) { + axis.update(); + } + + for (final RenderChartAxis axis in _yAxes) { + axis.update(); + } + } + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! CartesianAxesParentData) { + child.parentData = CartesianAxesParentData(); + } + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + bool isHit = false; + RenderBox? child = lastChild; + while (child != null) { + final CartesianAxesParentData childParentData = + child.parentData! as CartesianAxesParentData; + final bool isChildHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + isHit = isHit || isChildHit; + child = childParentData.previousSibling; + } + + return isHit; + } + + @override + void performLayout() { + double topAxesHeight = 0; + double bottomAxesHeight = 0; + double horizontalAxesHeight = 0; + + double leftAxesWidth = 0; + double rightAxesWidth = 0; + double verticalAxesWidth = 0; + + List horizontalAxes = _xAxes; + List verticalAxes = _yAxes; + if (isTransposed) { + horizontalAxes = _yAxes; + verticalAxes = _xAxes; + } + + void measureHorizontalAxes(BoxConstraints horizontalAxesConstraints) { + topAxesHeight = 0; + bottomAxesHeight = 0; + for (final RenderChartAxis axis in horizontalAxes) { + final CartesianAxesParentData childParentData = + axis.parentData! as CartesianAxesParentData; + childParentData.isResized = hasSize && size != constraints.biggest; + axis.layout(horizontalAxesConstraints, parentUsesSize: true); + if (axis.crossesAt == null || !axis.placeLabelsNearAxisLine) { + final double axisHeight = axis.size.height; + if (axis.opposedPosition) { + topAxesHeight += axisHeight; + // Apply the padding(gap) between the multiple axes. + if (axis.isVisible && topAxesHeight > axisHeight) { + topAxesHeight += spaceBetweenMultipleAxes; + } + } else { + bottomAxesHeight += axisHeight; + // Apply the padding(gap) between the multiple axes. + if (axis.isVisible && bottomAxesHeight > axisHeight) { + bottomAxesHeight += spaceBetweenMultipleAxes; + } + } + } + } + horizontalAxesHeight = topAxesHeight + bottomAxesHeight; + } + + void measureVerticalAxes(BoxConstraints verticalAxesConstraints) { + leftAxesWidth = 0; + rightAxesWidth = 0; + for (final RenderChartAxis axis in verticalAxes) { + final CartesianAxesParentData childParentData = + axis.parentData! as CartesianAxesParentData; + childParentData.isResized = hasSize && size != constraints.biggest; + axis.layout(verticalAxesConstraints, parentUsesSize: true); + if (axis.crossesAt == null || !axis.placeLabelsNearAxisLine) { + final double axisWidth = axis.size.width; + if (axis.opposedPosition) { + rightAxesWidth += axisWidth; + // Apply the padding(gap) between the multiple axes. + if (axis.isVisible && rightAxesWidth > axisWidth) { + rightAxesWidth += spaceBetweenMultipleAxes; + } + } else { + leftAxesWidth += axisWidth; + // Apply the padding(gap) between the multiple axes. + if (axis.isVisible && leftAxesWidth > axisWidth) { + leftAxesWidth += spaceBetweenMultipleAxes; + } + } + } + } + verticalAxesWidth = leftAxesWidth + rightAxesWidth; + } + + BoxConstraints verticalAxisConstraints = BoxConstraints( + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + ); + measureVerticalAxes(verticalAxisConstraints); + + final BoxConstraints horizontalAxisConstraints = BoxConstraints( + maxWidth: constraints.maxWidth - verticalAxesWidth, + maxHeight: constraints.maxHeight, + ); + measureHorizontalAxes(horizontalAxisConstraints); + + if (verticalAxisConstraints.maxHeight > 0 && horizontalAxesHeight == 0) { + // Hack: If there is no horizontal axes is visible, + // then set a very small height to relayout the vertical axes + // to avoid mutated renderbox exception. + horizontalAxesHeight = 0.00000001; + } + + verticalAxisConstraints = BoxConstraints( + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight - horizontalAxesHeight, + ); + measureVerticalAxes(verticalAxisConstraints); + + final Rect newPlotAreaBounds = Rect.fromLTWH( + leftAxesWidth, + topAxesHeight, + horizontalAxisConstraints.maxWidth, + verticalAxisConstraints.maxHeight, + ); + plotAreaOffset = newPlotAreaBounds.topLeft; + _plotAreaConstraints = BoxConstraints( + maxWidth: newPlotAreaBounds.width, + maxHeight: newPlotAreaBounds.height, + ); + + if (!plotAreaBounds.isEmpty && newPlotAreaBounds != plotAreaBounds) { + plotAreaBounds = newPlotAreaBounds; + } + + _arrangeVerticalAxes(newPlotAreaBounds, verticalAxes); + _arrangeHorizontalAxes(newPlotAreaBounds, horizontalAxes); + size = constraints.biggest; + performPostLayout(); + } + + void _arrangeVerticalAxes( + Rect plotAreaBounds, + List verticalAxes, + ) { + Offset leftAxisPosition = plotAreaBounds.topLeft; + Offset rightAxisPosition = plotAreaBounds.topRight; + for (final RenderChartAxis axis in verticalAxes) { + final double? crossing = _crossValue(axis); + final CartesianAxesParentData childParentData = + axis.parentData! as CartesianAxesParentData; + if (axis.opposedPosition) { + axis.invertElementsOrder = false; + if (crossing != null) { + if (crossing + axis.size.width > plotAreaBounds.right) { + axis.invertElementsOrder = true; + childParentData.offset = Offset( + crossing - axis.size.width, + plotAreaBounds.top, + ); + } else { + childParentData.offset = Offset(crossing, plotAreaBounds.top); + } + } else { + // Move the axis with padding value for multiple axes. + if (axis.isVisible && rightAxisPosition != plotAreaBounds.topRight) { + rightAxisPosition = rightAxisPosition.translate( + spaceBetweenMultipleAxes, + 0, + ); + } + childParentData.offset = rightAxisPosition; + rightAxisPosition = rightAxisPosition.translate(axis.size.width, 0); + } + } else { + axis.invertElementsOrder = true; + if (crossing != null) { + final double y = crossing - axis.size.width; + if (y < plotAreaBounds.left) { + axis.invertElementsOrder = false; + childParentData.offset = Offset(crossing, plotAreaBounds.top); + } else { + childParentData.offset = Offset(y, plotAreaBounds.top); + } + } else { + // Move the axis with padding value for multiple axes. + if (axis.isVisible && leftAxisPosition != plotAreaBounds.topLeft) { + leftAxisPosition = leftAxisPosition.translate( + -spaceBetweenMultipleAxes, + 0, + ); + } + childParentData.offset = leftAxisPosition.translate( + -axis.size.width, + 0, + ); + leftAxisPosition = childParentData.offset; + } + } + } + } + + void _arrangeHorizontalAxes( + Rect plotAreaBounds, + List horizontalAxes, + ) { + Offset topAxisPosition = plotAreaBounds.topLeft; + Offset bottomAxisPosition = plotAreaBounds.bottomLeft; + for (final RenderChartAxis axis in horizontalAxes) { + final double? crossing = _crossValue(axis); + final CartesianAxesParentData childParentData = + axis.parentData! as CartesianAxesParentData; + if (axis.opposedPosition) { + axis.invertElementsOrder = true; + if (crossing != null) { + final double y = crossing - axis.size.height; + if (y < plotAreaBounds.top) { + axis.invertElementsOrder = false; + childParentData.offset = Offset(plotAreaBounds.left, crossing); + } else { + childParentData.offset = Offset(plotAreaBounds.left, y); + } + } else { + // Move the axis with padding value for multiple axes. + if (axis.isVisible && topAxisPosition != plotAreaBounds.topLeft) { + topAxisPosition = topAxisPosition.translate( + 0, + -spaceBetweenMultipleAxes, + ); + } + childParentData.offset = topAxisPosition.translate( + 0, + -axis.size.height, + ); + topAxisPosition = childParentData.offset; + } + } else { + axis.invertElementsOrder = false; + if (crossing != null) { + if (crossing + axis.size.height > plotAreaBounds.bottom) { + axis.invertElementsOrder = true; + childParentData.offset = Offset( + plotAreaBounds.left, + crossing - axis.size.height, + ); + } else { + childParentData.offset = Offset(plotAreaBounds.left, crossing); + } + } else { + // Move the axis with padding value for multiple axes. + if (axis.isVisible && + bottomAxisPosition != plotAreaBounds.bottomLeft) { + bottomAxisPosition = bottomAxisPosition.translate( + 0, + spaceBetweenMultipleAxes, + ); + } + childParentData.offset = bottomAxisPosition; + bottomAxisPosition = bottomAxisPosition.translate( + 0, + axis.size.height, + ); + } + } + } + } + + double? _crossValue(RenderChartAxis axis) { + if (axis.placeLabelsNearAxisLine && + axis.crossesAt != null && + axis.associatedAxis != null) { + return axis.associatedAxis!.pointToPixel( + axis.associatedAxis!.actualValue(axis.crossesAt), + ); + } + + return null; + } + + RenderChartAxis? _associatedAxis(RenderChartAxis axis) { + if (axis.associatedAxisName != null) { + final RenderChartAxis? associatedAxis = axes[axis.associatedAxisName]; + if (associatedAxis != null && + ((associatedAxis.isVertical && axis.isVertical) || + (!associatedAxis.isVertical && !axis.isVertical))) { + return axis.isXAxis ? _yAxes.first : _xAxes.first; + } else { + return associatedAxis; + } + } + + return axis.isXAxis ? _yAxes.first : _xAxes.first; + } + + void _paintUnderSeries(PaintingContext context, Offset offset) { + RenderChartAxis? child = firstChild; + while (child != null) { + child.renderType = AxisRender.gridLines; + context.paintChild( + child, + (child.parentData! as BoxParentData).offset + offset, + ); + child = childAfter(child); + } + + _paintPlotBands(context, offset); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_isGridLinePaint) { + _paintUnderSeries(context, offset); + } else { + defaultPaint(context, offset); + _paintPlotBands(context, offset, onSeries: true); + } + _isGridLinePaint = false; + } + + void _paintPlotBands( + PaintingContext context, + Offset offset, { + bool onSeries = false, + }) { + RenderChartAxis? child = firstChild; + while (child != null) { + child.renderType = + onSeries ? AxisRender.overPlotBand : AxisRender.underPlotBand; + context.paintChild( + child, + (child.parentData! as BoxParentData).offset + offset, + ); + child = childAfter(child); + } + } +} + +class CartesianAxesParentData extends ContainerBoxParentData { + bool isResized = false; +} + +class CircularChartPlotArea extends ChartPlotArea { + const CircularChartPlotArea({ + super.key, + required super.vsync, + required super.localizations, + required super.legendKey, + required super.backgroundColor, + super.borderColor, + required super.borderWidth, + required super.legend, + required super.onLegendItemRender, + required super.onLegendTapped, + required super.onDataLabelRender, + required super.onTooltipRender, + required super.onDataLabelTapped, + required super.palette, + required super.selectionMode, + required super.selectionGesture, + required super.enableMultiSelection, + required super.tooltipBehavior, + required super.chartThemeData, + required super.themeData, + required this.centerX, + required this.centerY, + this.onCreateShader, + super.onSelectionChanged, + required super.children, + }); + + final String centerX; + final String centerY; + final CircularShaderCallback? onCreateShader; + + @override + RenderCircularChartPlotArea createRenderObject(BuildContext context) { + final RenderCircularChartPlotArea plotArea = RenderCircularChartPlotArea(); + plotArea + ..vsync = vsync + ..legendKey = legendKey + ..backgroundColor = backgroundColor + ..borderColor = borderColor + ..borderWidth = borderWidth + ..legend = legend + ..onLegendItemRender = onLegendItemRender + ..onLegendTapped = onLegendTapped + ..onDataLabelRender = onDataLabelRender + ..onDataLabelTapped = onDataLabelTapped + ..onTooltipRender = onTooltipRender + ..palette = palette + ..selectionMode = selectionMode + ..selectionGesture = selectionGesture + ..enableMultiSelection = enableMultiSelection + ..tooltipBehavior = tooltipBehavior + ..chartThemeData = chartThemeData + ..themeData = themeData + ..centerX = centerX + ..centerY = centerY + ..onCreateShader = onCreateShader + ..onSelectionChanged = onSelectionChanged; + return plotArea; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCircularChartPlotArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..centerX = centerX + ..centerY = centerY + ..onCreateShader = onCreateShader; + } +} + +class RenderCircularChartPlotArea extends RenderChartPlotArea { + String get centerX => _centerX; + String _centerX = '50%'; + set centerX(String value) { + if (_centerX != value) { + _centerX = value; + markNeedsLayout(); + } + } + + String get centerY => _centerY; + String _centerY = '50%'; + set centerY(String value) { + if (_centerY != value) { + _centerY = value; + markNeedsLayout(); + } + } + + CircularShaderCallback? onCreateShader; + + @override + void performUpdate() { + int index = 0; + visitChildren((RenderObject child) { + if (child is CircularSeriesRenderer) { + child + ..index = index++ + ..chartThemeData = _chartThemeData + ..palette = _palette! + ..centerX = centerX + ..centerY = centerY + ..onCreateShader = onCreateShader; + } + }); + super.performUpdate(); + } +} + +class IndicatorStack extends StatefulWidget { + const IndicatorStack({ + super.key, + required this.vsync, + required this.indicators, + required this.isTransposed, + required this.onLegendTapped, + required this.onLegendItemRender, + this.trackballBehavior, + this.textDirection, + }); + + final TickerProvider vsync; + final List indicators; + final bool isTransposed; + final ChartLegendTapCallback? onLegendTapped; + final ChartLegendRenderCallback? onLegendItemRender; + final TrackballBehavior? trackballBehavior; + final TextDirection? textDirection; + + @override + State createState() => _IndicatorStackState(); +} + +class _IndicatorStackState extends State { + @override + Widget build(BuildContext context) { + final List children = []; + final int length = widget.indicators.length; + for (int i = 0; i < length; i++) { + late Widget current; + final TechnicalIndicator indicator = widget.indicators[i]; + switch (indicator.toString()) { + case 'AD': + current = ADIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'ATR': + current = AtrIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'Bollinger': + current = BollingerIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'EMA': + current = EmaIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'MACD': + current = MacdIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'Momentum': + current = MomentumIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'RSI': + current = RsiIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'SMA': + current = SmaIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'Stochastic': + current = StochasticIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'TMA': + current = TmaIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'ROC': + current = RocIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + + case 'WMA': + current = WmaIndicatorWidget( + vsync: widget.vsync, + indicator: indicator, + index: i, + isTransposed: widget.isTransposed, + onLegendTapped: widget.onLegendTapped, + onLegendItemRender: widget.onLegendItemRender, + ); + break; + } + children.add(current); + } + + return IndicatorArea( + indicators: widget.indicators, + trackballBehavior: widget.trackballBehavior, + textDirection: widget.textDirection, + children: children, + ); + } +} + +class IndicatorArea extends MultiChildRenderObjectWidget { + const IndicatorArea({ + super.key, + required this.indicators, + this.trackballBehavior, + this.textDirection, + super.children, + }); + + final List indicators; + final TrackballBehavior? trackballBehavior; + final TextDirection? textDirection; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderIndicatorArea() + ..indicators = indicators + ..trackballBehavior = trackballBehavior + ..textDirection = textDirection; + } + + @override + void updateRenderObject( + BuildContext context, + RenderIndicatorArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..indicators = indicators + ..trackballBehavior = trackballBehavior + ..textDirection = textDirection; + } +} + +class RenderIndicatorArea extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin, + ChartAreaUpdateMixin { + RenderCartesianChartArea? chartArea; + TrackballBehavior? trackballBehavior; + late TextDirection? textDirection; + late List indicators; + final Map series = {}; + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! ChartAreaParentData) { + child.parentData = ChartAreaParentData(); + } + } + + @override + void performUpdate() { + if (indicators.isNotEmpty) { + final RenderCartesianChartArea? chartArea = + parent as RenderCartesianChartArea?; + if (chartArea != null) { + chartArea._plotArea!.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer) { + series[child.name] = child; + } + }); + } + } + super.performUpdate(); + } + + List? _buildLegendItems() { + int index = 0; + final List legendItems = []; + visitChildren((RenderObject child) { + final LegendItemProviderMixin provider = child as LegendItemProviderMixin; + final List? items = provider.buildLegendItems(index); + if (items != null) { + legendItems.addAll(items); + } + index++; + }); + + return legendItems; + } + + @override + void performLayout() { + RenderBox? child = firstChild; + while (child != null) { + child.layout(constraints); + child = childAfter(child); + } + size = constraints.biggest; + } + + @override + void paint(PaintingContext context, Offset offset) { + defaultPaint(context, offset); + } +} + +class AnnotationParentData extends StackParentData { + bool isVisible = true; +} + +class AnnotationArea extends MultiChildRenderObjectWidget { + const AnnotationArea({ + super.key, + this.annotations, + this.isTransposed = false, + super.children, + }); + + final List? annotations; + final bool isTransposed; + + @override + RenderAnnotationArea createRenderObject(BuildContext context) { + return RenderAnnotationArea() + ..annotations = annotations + ..isTransposed = isTransposed; + } + + @override + void updateRenderObject( + BuildContext context, + RenderAnnotationArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..annotations = annotations + ..isTransposed = isTransposed; + } +} + +class RenderAnnotationArea extends RenderStack with ChartAreaUpdateMixin { + // Below mentioned things must be considered while rendering the annotations: + // - Renders on the position based on coordinate Unit. + // - Coordinate unit has logical pixels, points and percentage. + // - Can align annotations horizontally and vertically. + // - Can render with in the chart area and plot area using annotation region. + // => With chart area, need to consider zooming, panning, visible indexes. + // => With plot area, annotation must be hidden if it exceeds plot area. + // - While hiding the annotation, need to consider the case that annotation + // should hide only when it exceeds the plot area fully. + // - While hiding the annotation with in plot area, need to check whether the + // annotation exceeds the plot area bounds or not. + // - Multiple annotations can be given. + // - Five types of axis must be considered to access x and y values. + // - Need to access axis for transposed case and to transform values. + // - Need to layout with chart area constraints and need to position + // based on annotation region. + // - Both string and numeric values can be given as percentage values. + // - Consider visible index while rendering the annotations. + + Offset _plotAreaOffset = Offset.zero; + Rect _plotAreaBounds = Rect.zero; + + List? get annotations => _annotations; + List? _annotations; + set annotations(List? value) { + if (_annotations != value) { + _annotations = value; + markNeedsLayout(); + } + } + + bool get isTransposed => _isTransposed; + bool _isTransposed = false; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + markNeedsLayout(); + } + } + + @override + void setupParentData(covariant RenderObject child) { + if (child is! AnnotationParentData) { + child.parentData = AnnotationParentData(); + } + } + + @override + void update() { + super.update(); + markNeedsLayout(); + } + + @override + void performLayout() { + size = constraints.biggest; + + RenderBox? child = firstChild; + int index = 0; + while (child != null) { + final AnnotationParentData? childParentData = + child.parentData as AnnotationParentData?; + final CartesianChartAnnotation annotation = _annotations![index]; + late Offset offset; + switch (annotation.coordinateUnit) { + case CoordinateUnit.point: + offset = _offsetFromRawPoints(annotation); + break; + + case CoordinateUnit.logicalPixel: + offset = _offsetFromLogicalPixels(annotation); + break; + + case CoordinateUnit.percentage: + offset = _offsetFromPercentage(annotation); + break; + } + + child.layout(constraints, parentUsesSize: true); + final Size childSize = child.size; + final double x = _horizontalAlignment( + annotation.horizontalAlignment, + offset.dx, + childSize, + ); + final double y = _verticalAlignment( + annotation.verticalAlignment, + offset.dy, + childSize, + ); + if (childParentData != null) { + childParentData.offset = Offset(x, y); + childParentData.isVisible = _isVisible( + annotation, + _isBeyondRegion(annotation, x, y, childSize), + ); + child = childParentData.nextSibling; + } + + index++; + } + } + + Offset _offsetFromRawPoints(CartesianChartAnnotation annotation) { + final RenderChartAxis? xAxis = _xAxis(annotation); + final RenderChartAxis? yAxis = _yAxis(annotation); + if (xAxis != null && yAxis != null) { + final Offset position = rawValueToPixelPoint( + annotation.x, + annotation.y, + xAxis, + yAxis, + isTransposed, + ); + return Offset( + position.dx + _plotAreaOffset.dx, + position.dy + _plotAreaOffset.dy, + ); + } + return Offset.zero; + } + + Offset _offsetFromLogicalPixels(CartesianChartAnnotation annotation) { + final num x = annotation.x; + final num y = annotation.y; + return Offset(x.toDouble(), y.toDouble()); + } + + Offset _offsetFromPercentage(CartesianChartAnnotation annotation) { + assert(annotation.x is String); + assert(annotation.y is String); + final double xFactor = factorFromValue(annotation.x); + final double yFactor = factorFromValue(annotation.y); + double width = 0.0; + double height = 0.0; + if (annotation.region == AnnotationRegion.plotArea) { + final RenderChartAxis? xAxis = _xAxis(annotation); + if (xAxis != null) { + width = _plotAreaBounds.size.width; + } + + final RenderChartAxis? yAxis = _yAxis(annotation); + if (yAxis != null) { + height = _plotAreaBounds.size.height; + } + } else { + width = size.width; + height = size.height; + } + + return Offset(width * xFactor, height * yFactor) + + (annotation.region == AnnotationRegion.plotArea + ? _plotAreaOffset + : Offset.zero); + } + + RenderChartAxis? _xAxis(CartesianChartAnnotation annotation) { + RenderChartAxis? xAxis; + final RenderCartesianChartArea? chartArea = + parent as RenderCartesianChartArea?; + if (chartArea != null) { + final Map? axes = + chartArea._cartesianAxes?.axes; + final List? xAxes = chartArea._cartesianAxes?._xAxes; + if (axes != null && + axes.isNotEmpty && + xAxes != null && + xAxes.isNotEmpty) { + if (annotation.xAxisName != null) { + xAxis = axes[annotation.xAxisName]; + } + xAxis ??= xAxes.first; + } + } + + return xAxis; + } + + RenderChartAxis? _yAxis(CartesianChartAnnotation annotation) { + RenderChartAxis? yAxis; + final RenderCartesianChartArea? chartArea = + parent as RenderCartesianChartArea?; + if (chartArea != null) { + final Map? axes = + chartArea._cartesianAxes?.axes; + final List? yAxes = chartArea._cartesianAxes?._yAxes; + if (axes != null && + axes.isNotEmpty && + yAxes != null && + yAxes.isNotEmpty) { + if (annotation.yAxisName != null) { + yAxis = axes[annotation.yAxisName]; + } + yAxis ??= yAxes.first; + } + } + + return yAxis; + } + + double _horizontalAlignment( + ChartAlignment horizontalAlignment, + double xPosition, + Size childSize, + ) { + final double size = childSize.width; + switch (horizontalAlignment) { + case ChartAlignment.near: + return xPosition; + case ChartAlignment.center: + return xPosition - size / 2; + case ChartAlignment.far: + return xPosition - size; + } + } + + double _verticalAlignment( + ChartAlignment verticalAlignment, + double yPosition, + Size childSize, + ) { + final double size = childSize.height; + switch (verticalAlignment) { + case ChartAlignment.near: + return yPosition; + case ChartAlignment.center: + return yPosition - size / 2; + case ChartAlignment.far: + return yPosition - size; + } + } + + bool _isBeyondRegion( + CartesianChartAnnotation annotation, + double x, + double y, + Size size, + ) { + final Rect childBounds = Rect.fromLTWH(x, y, size.width, size.height); + switch (annotation.region) { + case AnnotationRegion.chart: + return _isBeyond(childBounds, paintBounds); + + case AnnotationRegion.plotArea: + return _isBeyond(childBounds, _plotAreaBounds); + } + } + + bool _isBeyond(Rect current, Rect source) { + return current.left < source.left || + current.right > source.right || + current.top < source.top || + current.bottom > source.bottom; + } + + bool _isVisible(CartesianChartAnnotation annotation, bool isBeyondRegion) { + if (annotation.clip == ChartClipBehavior.hide) { + return !isBeyondRegion; + } + return true; + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox? child = firstChild; + int index = 0; + while (child != null) { + context.canvas.save(); + final CartesianChartAnnotation annotation = _annotations![index]; + if (annotation.clip == ChartClipBehavior.clip) { + context.canvas.clipRect( + annotation.region == AnnotationRegion.chart + ? paintBounds + : _plotAreaBounds, + ); + } + + final AnnotationParentData childParentData = + child.parentData! as AnnotationParentData; + if (childParentData.isVisible) { + context.paintChild(child, childParentData.offset + offset); + } + child = childParentData.nextSibling; + context.canvas.restore(); + + index++; + } + } +} + +class CircularAnnotationArea extends MultiChildRenderObjectWidget { + const CircularAnnotationArea({super.key, this.annotations, super.children}); + + final List? annotations; + + @override + RenderCircularAnnotationArea createRenderObject(BuildContext context) { + return RenderCircularAnnotationArea()..annotations = annotations; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCircularAnnotationArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.annotations = annotations; + } +} + +class RenderCircularAnnotationArea extends RenderStack + with ChartAreaUpdateMixin { + List? get annotations => _annotations; + List? _annotations; + set annotations(List? value) { + if (_annotations != value) { + _annotations = value; + markNeedsLayout(); + } + } + + @override + void setupParentData(covariant RenderObject child) { + if (child is! AnnotationParentData) { + child.parentData = AnnotationParentData(); + } + } + + @override + void performLayout() { + size = constraints.biggest; + final double minSize = (min(size.width, size.height)) / 2; + RenderBox? child = firstChild; + int index = 0; + while (child != null) { + final AnnotationParentData? childParentData = + child.parentData as AnnotationParentData?; + final CircularChartAnnotation annotation = _annotations![index]; + + final double radius = percentToValue(annotation.radius, minSize)!; + final Offset angle = calculateOffset( + annotation.angle.toDouble(), + radius, + Offset(size.width / 2, size.height / 2), + ); + final Offset offset = Offset(angle.dx, angle.dy); + final double width = percentToValue(annotations![index].width, minSize)!; + final double height = + percentToValue(annotations![index].height, minSize)!; + + if (height > 0 && width > 0) { + final BoxConstraints childConstraints = BoxConstraints( + minHeight: height, + minWidth: width, + maxHeight: height, + maxWidth: width, + ); + child.layout(childConstraints, parentUsesSize: true); + } else { + child.layout(constraints, parentUsesSize: true); + } + + final Size childSize = child.size; + final double x = _horizontalAlignment( + annotation.horizontalAlignment, + offset.dx, + childSize, + ); + final double y = _verticalAlignment( + annotation.verticalAlignment, + offset.dy, + childSize, + ); + if (childParentData != null) { + childParentData.offset = Offset(x, y); + child = childParentData.nextSibling; + } + index++; + } + } + + double _horizontalAlignment( + ChartAlignment horizontalAlignment, + double xPosition, + Size childSize, + ) { + final double size = childSize.width; + switch (horizontalAlignment) { + case ChartAlignment.near: + return xPosition; + case ChartAlignment.center: + return xPosition - size / 2; + case ChartAlignment.far: + return xPosition - size; + } + } + + double _verticalAlignment( + ChartAlignment verticalAlignment, + double yPosition, + Size childSize, + ) { + final double size = childSize.height; + switch (verticalAlignment) { + case ChartAlignment.near: + return yPosition; + case ChartAlignment.center: + return yPosition - size / 2; + case ChartAlignment.far: + return yPosition - size; + } + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox? child = firstChild; + while (child != null) { + final AnnotationParentData childParentData = + child.parentData! as AnnotationParentData; + context.paintChild(child, childParentData.offset + offset); + child = childParentData.nextSibling; + } + } +} + +class LoadingIndicator extends CustomConstrainedLayoutBuilder { + const LoadingIndicator({ + super.key, + required this.isTransposed, + required this.isInversed, + required this.onSwipe, + required super.builder, + }); + + final bool isTransposed; + final bool isInversed; + final ChartPlotAreaSwipeCallback onSwipe; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderLoadingIndicator() + ..isTransposed = isTransposed + ..isInversed = isInversed + ..onSwipe = onSwipe; + } + + @override + void updateRenderObject( + BuildContext context, + RenderLoadingIndicator renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..isTransposed = isTransposed + ..isInversed = isInversed + ..onSwipe = onSwipe; + } +} + +class RenderLoadingIndicator extends RenderProxyBox + with CustomRenderConstrainedLayoutBuilder { + bool _isDesktop = false; + Offset _startPosition = Offset.zero; + Offset _endPosition = Offset.zero; + + late ChartPlotAreaSwipeCallback onSwipe; + + bool get isTransposed => _isTransposed; + bool _isTransposed = false; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + markNeedsLayout(); + } + } + + bool get isInversed => _isInversed; + bool _isInversed = false; + set isInversed(bool value) { + if (_isInversed != value) { + _isInversed = value; + markNeedsLayout(); + } + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + return true; + } + + void handlePointerDown(PointerDownEvent details) { + _isDesktop = details.kind == PointerDeviceKind.mouse; + } + + void handleScaleStart(ScaleStartDetails details) { + if (!attached) { + return; + } + _startPosition = globalToLocal(details.focalPoint); + } + + void handleScaleUpdate(ScaleUpdateDetails details) { + if (!attached) { + return; + } + _endPosition = globalToLocal(details.focalPoint); + } + + void handleScaleEnd(ScaleEndDetails details) { + _handlePlotAreaSwipe(details.velocity); + } + + void handleDragStart(DragStartDetails details) { + if (!attached) { + return; + } + _startPosition = globalToLocal(details.globalPosition); + } + + void handleDragUpdate(DragUpdateDetails details) { + if (!attached) { + return; + } + _endPosition = globalToLocal(details.globalPosition); + } + + void handleDragEnd(DragEndDetails details) { + _handlePlotAreaSwipe(details.velocity); + } + + void _handlePlotAreaSwipe(Velocity swipeVelocity) { + final double minsSwipeVelocity = _isDesktop ? 0.0 : 240.0; + final double velocity = + isTransposed + ? swipeVelocity.pixelsPerSecond.dy + : swipeVelocity.pixelsPerSecond.dx; + if (velocity.abs() < minsSwipeVelocity) { + _startPosition = Offset.zero; + _endPosition = Offset.zero; + return; + } + + ChartSwipeDirection direction; + if (isTransposed) { + direction = + _isDesktop + ? _endPosition.dy > _startPosition.dy + ? ChartSwipeDirection.end + : ChartSwipeDirection.start + : velocity > 0 + ? ChartSwipeDirection.start + : ChartSwipeDirection.end; + } else { + direction = + _isDesktop + ? _endPosition.dx > _startPosition.dx + ? ChartSwipeDirection.start + : ChartSwipeDirection.end + : velocity > 0 + ? ChartSwipeDirection.start + : ChartSwipeDirection.end; + } + + if (isInversed) { + direction = + direction == ChartSwipeDirection.start + ? ChartSwipeDirection.end + : ChartSwipeDirection.start; + } + + final bool verticallyDragging = + (_endPosition.dy - _startPosition.dy).abs() > + (_endPosition.dx - _startPosition.dx).abs(); + + if ((verticallyDragging && !isTransposed) || + (!verticallyDragging && isTransposed)) { + return; + } + + onSwipe(direction); + + _startPosition = Offset.zero; + _endPosition = Offset.zero; + + bool buildLoadMoreIndicator = false; + final List axes = + (parent! as RenderBehaviorArea).cartesianAxes!.axes.values.toList(); + + for (int axisIndex = 0; axisIndex < axes.length; axisIndex++) { + final RenderChartAxis axis = axes[axisIndex]; + final DoubleRange visibleRange = axis.visibleRange!; + final DoubleRange actualRange = axis.actualRange!; + + if (((!verticallyDragging && axis.isXAxis) || + (verticallyDragging && !axis.isXAxis)) && + ((actualRange.minimum.round() == visibleRange.minimum.round() && + direction == ChartSwipeDirection.start) || + (actualRange.maximum.round() == visibleRange.maximum.round() && + direction == ChartSwipeDirection.end))) { + buildLoadMoreIndicator = true; + break; + } + } + if (buildLoadMoreIndicator) { + markNeedsBuild(); + } + } + + @override + void performLayout() { + rebuildIfNecessary(); + child?.layout(constraints); + size = constraints.biggest; + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return child?.hitTest(result, position: position) ?? false; + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null) { + context.paintChild(child!, offset); + } + } +} + +class ChartBehavior { + RenderBox? get parentBox => _parentBox; + RenderBox? _parentBox; + set parentBox(RenderBox? value) { + if (_parentBox != value) { + _parentBox = value; + } + } + + /// To customize the necessary pointer events in behaviors. + /// (e.g., CrosshairBehavior, TrackballBehavior, ZoomingBehavior). + @protected + void handleEvent(PointerEvent event, BoxHitTestEntry entry) {} + + /// Called when a long press gesture by a primary button has been + /// recognized in behavior. + @protected + void handleLongPressStart(LongPressStartDetails details) {} + + /// Called when moving after the long press gesture by a primary button + /// is recognized in behavior. + @protected + void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {} + + /// Called when the pointer stops contacting the screen after a long-press by + /// a primary button in behavior. + @protected + void handleLongPressEnd(LongPressEndDetails details) {} + + /// Called when the pointer tap has contacted the screen in behavior. + @protected + void handleTapDown(TapDownDetails details) {} + + /// Called when pointer has stopped contacting screen in behavior. + @protected + void handleTapUp(TapUpDetails details) {} + + /// Called when pointer tap has contacted the screen double time. + @protected + void handleDoubleTap(Offset position) {} + + /// Called when the pointers in contact with the screen + /// and initial scale of 1.0. + @protected + void handleScaleStart(ScaleStartDetails details) {} + + /// Called when the pointers in contact with the screen have indicated + /// a new scale. + @protected + void handleScaleUpdate(ScaleUpdateDetails details) {} + + /// Called when the pointers are no longer in contact with the screen. + @protected + void handleScaleEnd(ScaleEndDetails details) {} + + /// Called when a pointer or mouse enter on the screen. + @protected + void handlePointerEnter(PointerEnterEvent details) {} + + /// Called when a pointer or mouse exit on the screen. + @protected + void handlePointerExit(PointerExitEvent details) {} + + /// Called to customize each behaviors with given context at the given offset. + @protected + void onPaint( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) {} +} + +typedef SelectionCallback = void Function(int seriesIndex, int pointIndex); + +class SelectionController { + SelectionController(this.parentBox); + + final RenderChartPlotArea parentBox; + final List _selectionListeners = []; + final List _deselectionListeners = []; + + final Map> selectedDataPoints = >{}; + + bool get hasSelection => _hasSelectedIndexes; + bool _hasSelectedIndexes = false; + + bool get enableMultiSelection => _enableMultipleSelection; + bool _enableMultipleSelection = false; + set enableMultiSelection(bool value) { + if (_enableMultipleSelection != value) { + _enableMultipleSelection = value; + resetSelection(); + } + } + + void updateSelectionParent(SelectionBehavior selectionBehavior) { + selectionBehavior._parentBox = parentBox; + } + + void resetSelection() { + _hasSelectedIndexes = false; + selectedDataPoints.forEach(( + int previousSelectedSeriesIndex, + List values, + ) { + final int length = values.length; + for (int i = 0; i < length; i++) { + _notifyDeselectionListeners(previousSelectedSeriesIndex, values[i]); + } + values.clear(); + }); + selectedDataPoints.clear(); + } + + void updateSelection( + ChartSeriesRenderer series, + int seriesIndex, + int segmentPointIndex, + bool togglingEnabled, { + SelectionType? selectionType, + bool forceSelection = false, + bool forceDeselection = false, + }) { + if (series is ContinuousSeriesMixin && + selectionType == SelectionType.point) { + return; + } + + int deselectedSeriesIndex = -1; + int deselectedPointIndex = -1; + int selectedSeriesIndex = -1; + int selectedPointIndex = -1; + + if (selectionType == SelectionType.cluster) { + seriesIndex = 0; + } + if (selectionType == SelectionType.series) { + segmentPointIndex = 0; + } + + if (enableMultiSelection) { + if (forceSelection) { + final bool existingSelected = + selectedDataPoints[seriesIndex]?.contains(segmentPointIndex) ?? + false; + if (existingSelected) { + return; + } + } + if (forceDeselection) { + _notifyDeselectionListeners(seriesIndex, segmentPointIndex); + } + + if (selectedDataPoints.containsKey(seriesIndex)) { + final List points = selectedDataPoints[seriesIndex]!; + if (points.contains(segmentPointIndex)) { + if (togglingEnabled) { + points.remove(segmentPointIndex); + deselectedSeriesIndex = seriesIndex; + deselectedPointIndex = segmentPointIndex; + } + } else { + points.add(segmentPointIndex); + selectedSeriesIndex = seriesIndex; + selectedPointIndex = segmentPointIndex; + } + } else { + selectedDataPoints[seriesIndex] = [segmentPointIndex]; + selectedSeriesIndex = seriesIndex; + selectedPointIndex = segmentPointIndex; + } + } else { + if (selectedDataPoints.containsKey(seriesIndex)) { + final List points = selectedDataPoints[seriesIndex]!; + if (points.contains(segmentPointIndex)) { + if (togglingEnabled) { + points.remove(segmentPointIndex); + deselectedSeriesIndex = seriesIndex; + deselectedPointIndex = segmentPointIndex; + } + } else { + deselectedSeriesIndex = seriesIndex; + deselectedPointIndex = points[0]; + points.clear(); + points.add(segmentPointIndex); + selectedSeriesIndex = seriesIndex; + selectedPointIndex = segmentPointIndex; + } + } else { + selectedDataPoints.forEach(( + int previousSelectedSeriesIndex, + List values, + ) { + if (values.isNotEmpty) { + deselectedSeriesIndex = previousSelectedSeriesIndex; + deselectedPointIndex = values[0]; + values.clear(); + } + }); + selectedDataPoints.clear(); + + selectedDataPoints[seriesIndex] = [segmentPointIndex]; + selectedSeriesIndex = seriesIndex; + selectedPointIndex = segmentPointIndex; + } + } + + _removeEmptyKeys(); + _hasSelectedIndexes = selectedDataPoints.isNotEmpty; + if (deselectedSeriesIndex != -1 && deselectedPointIndex != -1) { + _notifyDeselectionListeners(deselectedSeriesIndex, deselectedPointIndex); + } + + if (selectedSeriesIndex != -1 && selectedPointIndex != -1) { + _notifySelectionListeners(selectionType); + } + } + + void _removeEmptyKeys() { + final List keysToRemove = []; + selectedDataPoints.forEach((int key, List values) { + if (values.isEmpty) { + keysToRemove.add(key); + } + }); + + for (int i = 0; i < keysToRemove.length; i++) { + final int key = keysToRemove[i]; + selectedDataPoints.remove(key); + } + } + + void addSelectionListener(SelectionCallback listener) { + _selectionListeners.add(listener); + } + + void removeSelectionListener(SelectionCallback listener) { + _selectionListeners.remove(listener); + } + + void addDeselectionListener(SelectionCallback listener) { + _deselectionListeners.add(listener); + } + + void removeDeselectionListener(SelectionCallback listener) { + _deselectionListeners.remove(listener); + } + + void _notifySelectionListeners([SelectionType? selectionType]) { + if (selectionType == SelectionType.cluster) { + if (selectedDataPoints.isNotEmpty) { + selectedDataPoints.forEach((int seriesIndex, List pointIndexes) { + for (final int pointIndex in pointIndexes) { + for (final SelectionCallback listener in _selectionListeners) { + listener(seriesIndex, pointIndex); + } + } + }); + } + } else { + selectedDataPoints.forEach((int seriesIndex, List pointIndexes) { + if (pointIndexes.isNotEmpty) { + for (final int pointIndex in pointIndexes) { + for (final SelectionCallback listener in _selectionListeners) { + listener(seriesIndex, pointIndex); + } + } + } + }); + } + } + + void _notifyDeselectionListeners(int seriesIndex, int segmentPointIndex) { + for (final SelectionCallback listener in _deselectionListeners) { + listener(seriesIndex, segmentPointIndex); + } + } + + void dispose() { + _selectionListeners.clear(); + } + + void reset() { + selectedDataPoints.clear(); + _notifySelectionListeners(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/crosshair.dart b/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/crosshair.dart new file mode 100644 index 000000000..4135e1a68 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/crosshair.dart @@ -0,0 +1,1291 @@ +import 'dart:async'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart' hide TextDirection; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; +import '../base.dart'; +import '../common/callbacks.dart'; +import '../common/interactive_tooltip.dart'; +import '../interactions/behavior.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// This class has the properties of the crosshair behavior. +/// +/// Crosshair behavior has the activation mode and line type property to set +/// the behavior of the crosshair. It also has the property to customize +/// the appearance. +/// +/// Provide options for activation mode, line type, line color, line width, +/// hide delay for customizing the behavior of the crosshair. +@immutable +// ignore: must_be_immutable +class CrosshairBehavior extends ChartBehavior { + /// Creating an argument constructor of [CrosshairBehavior] class. + CrosshairBehavior({ + this.activationMode = ActivationMode.longPress, + this.lineType = CrosshairLineType.both, + this.lineDashArray, + this.enable = false, + this.lineColor, + this.lineWidth = 1, + this.shouldAlwaysShow = false, + this.hideDelay = 0, + }); + + /// Toggles the visibility of the crosshair. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final bool enable; + + /// Width of the crosshair line. + /// + /// Defaults to `1`. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true, + /// lineWidth: 5 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final double lineWidth; + + /// Color of the crosshair line. + /// + /// Color will be applied based on the brightness + /// property of the app. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true,lineColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final Color? lineColor; + + /// Dashes of the crosshair 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. + /// + /// Defaults to `[0,0]`. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true, + /// lineDashArray: [10,10] + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final List? lineDashArray; + + /// Gesture for activating the crosshair. + /// + /// Crosshair can be activated in tap, double tap + /// and long press. + /// + /// Defaults to `ActivationMode.longPress`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true, + /// activationMode: ActivationMode.doubleTap + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final ActivationMode activationMode; + + /// Type of crosshair line. + /// + /// By default, both vertical and horizontal lines will be + /// displayed. You can change this by specifying values to this property. + /// + /// Defaults to `CrosshairLineType.both`. + /// + /// Also refer [CrosshairLineType]. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true, + /// lineType: CrosshairLineType.horizontal + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final CrosshairLineType lineType; + + /// Enables or disables the crosshair. + /// + /// By default, the crosshair will be hidden on touch. + /// To avoid this, set this property to true. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true, + /// shouldAlwaysShow: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final bool shouldAlwaysShow; + + /// Time delay for hiding the crosshair. + /// + /// Defaults to `0`. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior( + /// enable: true, + /// hideDelay: 3000 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior + /// ); + /// } + /// ``` + final double hideDelay; + + /// Hold crosshair target position. + Offset? _position; + Timer? _crosshairHideTimer; + + final List _verticalPaths = []; + final List _horizontalPaths = []; + final List _verticalLabels = []; + final List _horizontalLabels = []; + final List _verticalLabelPositions = []; + final List _horizontalLabelPositions = []; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is CrosshairBehavior && + other.activationMode == activationMode && + other.lineType == lineType && + other.lineDashArray == lineDashArray && + other.enable == enable && + other.lineColor == lineColor && + other.lineWidth == lineWidth && + other.shouldAlwaysShow == shouldAlwaysShow && + other.hideDelay == hideDelay; + } + + @override + int get hashCode { + final List values = [ + activationMode, + lineType, + lineDashArray, + enable, + lineColor, + lineWidth, + shouldAlwaysShow, + hideDelay, + ]; + return Object.hashAll(values); + } + + /// Displays the crosshair at the specified x and y-positions. + /// + /// x & y - x and y values/pixel where the crosshair needs to be shown. + /// + /// coordinateUnit - specify the type of x and y values given. `pixel` or + /// `point` for logical pixel and chart data point respectively. + /// + /// Defaults to `point`. + void show(dynamic x, double y, [String coordinateUnit = 'point']) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + assert(x != null); + assert(!y.isNaN); + if (coordinateUnit == 'point') { + _position = rawValueToPixelPoint( + x, + y, + parent.xAxis, + parent.yAxis, + parent.isTransposed, + ); + } else if (coordinateUnit == 'pixel') { + if (x is num) { + _position = Offset(x.toDouble(), y); + } else { + _position = Offset( + rawValueToPixelPoint( + x, + y, + parent.xAxis, + parent.yAxis, + parent.isTransposed, + ).dx, + y, + ); + } + } + + _show(parent); + } + + /// Displays the crosshair at the specified point index. + /// + /// pointIndex - index of point at which the crosshair needs to be shown. + void showByIndex(int pointIndex) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent != null && + parent.plotArea != null && + parent.plotArea!.firstChild != null) { + final CartesianSeriesRenderer seriesRenderer = + parent.plotArea!.firstChild! as CartesianSeriesRenderer; + final List visibleIndexes = seriesRenderer.visibleIndexes; + if (visibleIndexes.isNotEmpty && + visibleIndexes.first <= pointIndex && + pointIndex <= visibleIndexes.last) { + final num y = seriesRenderer.trackballYValue(pointIndex); + if (seriesRenderer.xRawValues.isNotEmpty && !y.isNaN) { + show(seriesRenderer.xRawValues[pointIndex], y.toDouble()); + } + } + } + } + + /// Hides the crosshair if it is displayed. + void hide() { + _position = null; + _resetCrosshairHolders(); + (parentBox as RenderBehaviorArea?)?.invalidate(); + } + + /// To customize the necessary pointer events in behaviors. + /// (e.g., CrosshairBehavior, TrackballBehavior, ZoomingBehavior). + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + if (parentBox == null) { + return; + } + + if (event is PointerDownEvent) { + _handlePointerDown(event); + } else if (event is PointerMoveEvent) { + _handlePointerMove(event); + } else if (event is PointerHoverEvent) { + _handlePointerHover(event); + } else if (event is PointerCancelEvent) { + _hideCrosshair(immediately: true); + } else if (event is PointerUpEvent) { + _hideCrosshair(); + } + } + + void _handlePointerDown(PointerDownEvent details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showCrosshair(parentBox!.globalToLocal(details.position)); + } + } + + void _handlePointerMove(PointerMoveEvent details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showCrosshair(parentBox!.globalToLocal(details.position)); + } + } + + void _handlePointerHover(PointerHoverEvent details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showCrosshair(parentBox!.globalToLocal(details.position)); + } + } + + /// Called when a pointer or mouse enter on the screen. + @override + void handlePointerEnter(PointerEnterEvent details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showCrosshair(parentBox!.globalToLocal(details.position)); + } + } + + /// Called when a pointer or mouse exit on the screen. + @override + void handlePointerExit(PointerExitEvent details) { + _hideCrosshair(immediately: true); + } + + /// Called when a long press gesture by a primary button has been + /// recognized in behavior. + @override + void handleLongPressStart(LongPressStartDetails details) { + if (parentBox != null && activationMode == ActivationMode.longPress) { + _showCrosshair(parentBox!.globalToLocal(details.globalPosition)); + } + } + + /// Called when moving after the long press gesture by a primary button is + /// recognized in behavior. + @override + void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + if (parentBox != null && activationMode == ActivationMode.longPress) { + _showCrosshair(parentBox!.globalToLocal(details.globalPosition)); + } + } + + /// Called when the pointer stops contacting the screen after a long-press + /// by a primary button in behavior. + @override + void handleLongPressEnd(LongPressEndDetails details) { + _hideCrosshair(); + } + + /// Called when the pointer tap has contacted the screen in behavior. + @override + void handleTapDown(TapDownDetails details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showCrosshair(parentBox!.globalToLocal(details.globalPosition)); + } + } + + /// Called when pointer has stopped contacting screen in behavior. + @override + void handleTapUp(TapUpDetails details) { + _hideCrosshair(); + } + + /// Called when pointer tap has contacted the screen double time in behavior. + @override + void handleDoubleTap(Offset position) { + if (parentBox != null && activationMode == ActivationMode.doubleTap) { + _showCrosshair(parentBox!.globalToLocal(position)); + _hideCrosshair(doubleTapHideDelay: 200); + } + } + + void _showCrosshair(Offset localPosition) { + if (enable) { + show(localPosition.dx, localPosition.dy, 'pixel'); + } + } + + void _hideCrosshair({int doubleTapHideDelay = 0, bool immediately = false}) { + if (immediately) { + hide(); + } else if (!shouldAlwaysShow) { + final int hideDelayDuration = + hideDelay > 0 ? hideDelay.toInt() : doubleTapHideDelay; + _crosshairHideTimer?.cancel(); + _crosshairHideTimer = Timer( + Duration(milliseconds: hideDelayDuration), + () { + _crosshairHideTimer = null; + hide(); + }, + ); + } + } + + void _resetCrosshairHolders() { + _verticalPaths.clear(); + _horizontalPaths.clear(); + _verticalLabels.clear(); + _horizontalLabels.clear(); + _verticalLabelPositions.clear(); + _horizontalLabelPositions.clear(); + } + + void _show(RenderBehaviorArea parent) { + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (_position == null || cartesianAxes == null) { + return; + } + + _calculateTooltipLabelAndPositions(parent, cartesianAxes); + parent.invalidate(); + } + + void _calculateTooltipLabelAndPositions( + RenderBehaviorArea parent, + RenderCartesianAxes cartesianAxes, + ) { + final Rect plotAreaBounds = parent.paintBounds; + if (plotAreaBounds.contains(_position!)) { + _resetCrosshairHolders(); + + final Offset plotAreaOffset = + (parent.parentData! as BoxParentData).offset; + RenderChartAxis? child = cartesianAxes.firstChild; + while (child != null) { + final InteractiveTooltip interactiveTooltip = child.interactiveTooltip; + if (child.isVisible && + interactiveTooltip.enable && + child.visibleLabels.isNotEmpty) { + final TextStyle textStyle = child.chartThemeData!.crosshairTextStyle! + .merge(interactiveTooltip.textStyle); + + final Offset parentDataOffset = + (child.parentData! as BoxParentData).offset; + final Offset axisOffset = parentDataOffset.translate( + -plotAreaOffset.dx, + -plotAreaOffset.dy, + ); + final Rect axisBounds = axisOffset & child.size; + + if (child.isVertical) { + _computeVerticalAxisTooltips( + child, + _position!, + textStyle, + axisBounds, + plotAreaBounds, + interactiveTooltip.arrowLength, + interactiveTooltip.arrowWidth, + interactiveTooltip.borderRadius, + ); + } else { + _computeHorizontalAxisTooltips( + child, + _position!, + textStyle, + axisBounds, + plotAreaBounds, + interactiveTooltip.arrowLength, + interactiveTooltip.arrowWidth, + interactiveTooltip.borderRadius, + ); + } + } + + final CartesianAxesParentData childParentData = + child.parentData! as CartesianAxesParentData; + child = childParentData.nextSibling; + } + } + } + + void _computeHorizontalAxisTooltips( + RenderChartAxis axis, + Offset position, + TextStyle textStyle, + Rect axisBounds, + Rect plotAreaBounds, + double arrowLength, + double arrowWidth, + double borderRadius, + ) { + final num actualXValue = _actualXValue(axis, position, plotAreaBounds); + String label = _resultantString(axis, actualXValue); + label = _triggerCrosshairCallback(axis, label, actualXValue); + final Size labelSize = measureText(label, textStyle); + + final String tooltipPosition = axis.opposedPosition ? 'Top' : 'Bottom'; + final Rect tooltipRect = _calculateTooltipRect( + tooltipPosition, + position, + labelSize, + axisBounds, + arrowLength, + ); + + final Rect validatedRect = _validateRectBounds(tooltipRect, axisBounds); + _validateRectXPosition(validatedRect, plotAreaBounds); + + final RRect tooltipRRect = RRect.fromRectAndRadius( + validatedRect, + Radius.circular(borderRadius), + ); + + final Path tooltipAndArrowPath = + Path() + ..addRRect(tooltipRRect) + ..addPath( + _tooltipArrowHeadPath( + tooltipPosition, + tooltipRRect, + position, + arrowLength, + arrowWidth, + ), + Offset.zero, + ); + + _horizontalPaths.add(tooltipAndArrowPath); + _horizontalLabels.add(label); + _horizontalLabelPositions.add(_textPosition(tooltipRRect, labelSize)); + } + + void _computeVerticalAxisTooltips( + RenderChartAxis axis, + Offset position, + TextStyle textStyle, + Rect axisBounds, + Rect plotAreaBounds, + double arrowLength, + double arrowWidth, + double borderRadius, + ) { + final num actualYValue = _actualYValue(axis, position); + String label = _resultantString(axis, actualYValue); + label = _triggerCrosshairCallback(axis, label, actualYValue); + final Size labelSize = measureText(label, textStyle); + + final String tooltipPosition = axis.opposedPosition ? 'Right' : 'Left'; + final Rect tooltipRect = _calculateTooltipRect( + tooltipPosition, + position, + labelSize, + axisBounds, + arrowLength, + ); + + final Rect validatedRect = _validateRectBounds(tooltipRect, axisBounds); + _validateRectYPosition(validatedRect, plotAreaBounds); + + final RRect tooltipRRect = RRect.fromRectAndRadius( + validatedRect, + Radius.circular(borderRadius), + ); + + final Path tooltipAndArrowPath = + Path() + ..addRRect(tooltipRRect) + ..addPath( + _tooltipArrowHeadPath( + tooltipPosition, + tooltipRRect, + position, + arrowLength, + arrowWidth, + ), + Offset.zero, + ); + + _verticalPaths.add(tooltipAndArrowPath); + _verticalLabels.add(label); + _verticalLabelPositions.add(_textPosition(tooltipRRect, labelSize)); + } + + num _actualXValue( + RenderChartAxis axis, + Offset position, + Rect plotAreaBounds, + ) { + return axis.pixelToPoint( + axis.paintBounds, + position.dx - plotAreaBounds.left, + position.dy - plotAreaBounds.top, + ); + } + + num _actualYValue(RenderChartAxis axis, Offset position) { + return axis.pixelToPoint(axis.paintBounds, position.dx, position.dy); + } + + String _triggerCrosshairCallback( + RenderChartAxis axis, + String label, + num value, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent != null && + parent.onCrosshairPositionChanging != null && + parent.chartThemeData != null) { + final CrosshairRenderArgs crosshairEventArgs = CrosshairRenderArgs( + axis.widget, + _rawValue(axis, value), + axis.name, + axis.isVertical ? AxisOrientation.vertical : AxisOrientation.horizontal, + ); + crosshairEventArgs.text = label; + parent.onCrosshairPositionChanging!(crosshairEventArgs); + return crosshairEventArgs.text; + } + return label; + } + + Rect _validateRectBounds(Rect tooltipRect, Rect axisBounds) { + const double padding = 0.5; // Padding between the corners. + Rect validatedRect = tooltipRect; + double difference = 0; + + if (tooltipRect.left < axisBounds.left) { + difference = (axisBounds.left - tooltipRect.left) + padding; + validatedRect = validatedRect.translate(difference, 0); + } + if (tooltipRect.right > axisBounds.right) { + difference = (tooltipRect.right - axisBounds.right) + padding; + validatedRect = validatedRect.translate(-difference, 0); + } + if (tooltipRect.top < axisBounds.top) { + difference = (axisBounds.top - tooltipRect.top) + padding; + validatedRect = validatedRect.translate(0, difference); + } + + if (tooltipRect.bottom > axisBounds.bottom) { + difference = (tooltipRect.bottom - axisBounds.bottom) + padding; + validatedRect = validatedRect.translate(0, -difference); + } + return validatedRect; + } + + Rect _validateRectXPosition(Rect labelRect, Rect axisClipRect) { + if (labelRect.right >= axisClipRect.right) { + return Rect.fromLTRB( + labelRect.left - (labelRect.right - axisClipRect.right), + labelRect.top, + axisClipRect.right, + labelRect.bottom, + ); + } else if (labelRect.left <= axisClipRect.left) { + return Rect.fromLTRB( + axisClipRect.left, + labelRect.top, + labelRect.right + (axisClipRect.left - labelRect.left), + labelRect.bottom, + ); + } + return labelRect; + } + + Rect _validateRectYPosition(Rect labelRect, Rect axisClipRect) { + if (labelRect.bottom >= axisClipRect.bottom) { + return Rect.fromLTRB( + labelRect.left, + labelRect.top - (labelRect.bottom - axisClipRect.bottom), + labelRect.right, + axisClipRect.bottom, + ); + } else if (labelRect.top <= axisClipRect.top) { + return Rect.fromLTRB( + labelRect.left, + axisClipRect.top, + labelRect.right, + labelRect.bottom + (axisClipRect.top - labelRect.top), + ); + } + return labelRect; + } + + Rect _calculateTooltipRect( + String axis, + Offset position, + Size labelSize, + Rect axisBounds, + double arrowLength, + ) { + final double labelWidthWithPadding = labelSize.width + crosshairPadding; + final double labelHeightWithPadding = labelSize.height + crosshairPadding; + switch (axis) { + case 'Left': + return Rect.fromLTWH( + axisBounds.right - labelWidthWithPadding - arrowLength, + position.dy - (labelHeightWithPadding / 2), + labelWidthWithPadding, + labelHeightWithPadding, + ); + + case 'Right': + return Rect.fromLTWH( + axisBounds.left + arrowLength, + position.dy - (labelHeightWithPadding / 2), + labelSize.width + crosshairPadding, + labelHeightWithPadding, + ); + + case 'Top': + return Rect.fromLTWH( + position.dx - (labelWidthWithPadding / 2), + axisBounds.bottom - labelHeightWithPadding - arrowLength, + labelWidthWithPadding, + labelHeightWithPadding, + ); + + case 'Bottom': + return Rect.fromLTWH( + position.dx - (labelWidthWithPadding / 2), + axisBounds.top + arrowLength, + labelWidthWithPadding, + labelSize.height + crosshairPadding, + ); + } + return Rect.zero; + } + + Path _tooltipArrowHeadPath( + String axis, + RRect tooltipRect, + Offset position, + double arrowLength, + double arrowWidth, + ) { + final Path arrowPath = Path(); + final double tooltipLeft = tooltipRect.left; + final double tooltipRight = tooltipRect.right; + final double tooltipTop = tooltipRect.top; + final double tooltipBottom = tooltipRect.bottom; + final double rectHalfWidth = tooltipRect.width / 2; + final double rectHalfHeight = tooltipRect.height / 2; + switch (axis) { + case 'Left': + arrowPath.moveTo( + tooltipRight, + tooltipTop + rectHalfHeight - arrowWidth, + ); + arrowPath.lineTo( + tooltipRight, + tooltipBottom - rectHalfHeight + arrowWidth, + ); + arrowPath.lineTo(tooltipRight + arrowLength, position.dy); + arrowPath.close(); + return arrowPath; + + case 'Right': + arrowPath.moveTo(tooltipLeft, tooltipTop + rectHalfHeight - arrowWidth); + arrowPath.lineTo( + tooltipLeft, + tooltipBottom - rectHalfHeight + arrowWidth, + ); + arrowPath.lineTo(tooltipLeft - arrowLength, position.dy); + arrowPath.close(); + return arrowPath; + + case 'Top': + arrowPath.moveTo(position.dx, tooltipBottom + arrowLength); + arrowPath.lineTo( + (tooltipRight - rectHalfWidth) + arrowWidth, + tooltipBottom, + ); + arrowPath.lineTo( + (tooltipLeft + rectHalfWidth) - arrowWidth, + tooltipBottom, + ); + arrowPath.close(); + return arrowPath; + + case 'Bottom': + arrowPath.moveTo(position.dx, tooltipTop - arrowLength); + arrowPath.lineTo( + (tooltipRight - rectHalfWidth) + arrowWidth, + tooltipTop, + ); + arrowPath.lineTo( + (tooltipLeft + rectHalfWidth) - arrowWidth, + tooltipTop, + ); + arrowPath.close(); + return arrowPath; + } + return arrowPath; + } + + Offset _textPosition(RRect tooltipRect, Size labelSize) { + return Offset( + (tooltipRect.left + tooltipRect.width / 2) - labelSize.width / 2, + (tooltipRect.top + tooltipRect.height / 2) - labelSize.height / 2, + ); + } + + String _resultantString(RenderChartAxis axis, num actualValue) { + final String resultantString = _interactiveTooltipLabel(actualValue, axis); + if (axis.interactiveTooltip.format != null) { + return axis.interactiveTooltip.format!.replaceAll( + '{value}', + resultantString, + ); + } else { + return resultantString; + } + } + + /// To get interactive tooltip label. + String _interactiveTooltipLabel(num value, RenderChartAxis axis) { + if (axis is RenderCategoryAxis) { + final num index = value < 0 ? 0 : value; + final List labels = axis.labels; + final int labelsLength = labels.length; + return labels[(index.round() >= labelsLength + ? (index.round() > labelsLength + ? labelsLength - 1 + : index - 1) + : index) + .round()] + .toString(); + } else if (axis is RenderDateTimeCategoryAxis) { + final num index = value < 0 ? 0 : value; + final List labels = axis.labels; + final int labelsLength = labels.length; + final int milliseconds = + labels[(index.round() >= labelsLength + ? (index.round() > labelsLength + ? labelsLength - 1 + : index - 1) + : index) + .round()]; + final num interval = axis.visibleRange!.minimum.ceil(); + final num previousInterval = + labels.isNotEmpty ? labels[labelsLength - 1] : interval; + final DateFormat dateFormat = + axis.dateFormat ?? + dateTimeCategoryAxisLabelFormat( + axis, + interval.toInt(), + previousInterval.toInt(), + ); + return dateFormat.format( + DateTime.fromMillisecondsSinceEpoch(milliseconds), + ); + } else if (axis is RenderDateTimeAxis) { + final num interval = axis.visibleRange!.minimum.ceil(); + final List visibleLabels = axis.visibleLabels; + final num previousInterval = + visibleLabels.isNotEmpty + ? visibleLabels[visibleLabels.length - 1].value + : interval; + final DateFormat dateFormat = + axis.dateFormat ?? + dateTimeAxisLabelFormat( + axis, + interval.toInt(), + previousInterval.toInt(), + ); + return dateFormat.format( + DateTime.fromMillisecondsSinceEpoch(value.toInt()), + ); + } else if (axis is RenderLogarithmicAxis) { + return logAxisLabel( + axis, + axis.toPow(value), + axis.interactiveTooltip.decimalPlaces, + ); + } else if (axis is RenderNumericAxis) { + return numericAxisLabel( + axis, + value, + axis.interactiveTooltip.decimalPlaces, + ); + } else { + return ''; + } + } + + // It specifies for callback value field. + dynamic _rawValue(RenderChartAxis axis, num value) { + if (axis is RenderCategoryAxis) { + final num index = value < 0 ? 0 : value; + final int labelsLength = axis.labels.length; + final String? label = + axis.labels[(index.round() >= labelsLength + ? (index.round() > labelsLength + ? labelsLength - 1 + : index - 1) + : index) + .round()]; + return axis.labels.indexOf(label); + } else if (axis is RenderDateTimeCategoryAxis) { + final num index = value < 0 ? 0 : value; + final int labelsLength = axis.labels.length; + return axis.labels[(index.round() >= labelsLength + ? (index.round() > labelsLength ? labelsLength - 1 : index - 1) + : index) + .round()]; + } else { + return value; + } + } + + /// Override this method to customize the crosshair tooltips & line rendering. + @override + void onPaint( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (_position == null || parent == null) { + return; + } + + if (parent.paintBounds.contains(_position!)) { + _drawCrosshairLines(context, _position!, parent, chartThemeData); + _drawCrosshairTooltip(context, parent); + } + } + + void _drawCrosshairLines( + PaintingContext context, + Offset offset, + RenderBehaviorArea parent, + SfChartThemeData chartThemeData, + ) { + Color crosshairLineColor = + (lineColor ?? chartThemeData.crosshairLineColor)!; + if (parent.onCrosshairPositionChanging != null && + parent.chartThemeData != null) { + final CrosshairRenderArgs crosshairEventArgs = CrosshairRenderArgs(); + crosshairEventArgs.text = ''; + crosshairEventArgs.lineColor = crosshairLineColor; + parent.onCrosshairPositionChanging!(crosshairEventArgs); + crosshairLineColor = crosshairEventArgs.lineColor; + } + + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = crosshairLineColor + ..strokeWidth = lineWidth + ..style = PaintingStyle.stroke; + + switch (lineType) { + case CrosshairLineType.both: + drawHorizontalAxisLine(context, offset, lineDashArray, paint); + drawVerticalAxisLine(context, offset, lineDashArray, paint); + break; + + case CrosshairLineType.horizontal: + drawHorizontalAxisLine(context, offset, lineDashArray, paint); + break; + + case CrosshairLineType.vertical: + drawVerticalAxisLine(context, offset, lineDashArray, paint); + break; + + case CrosshairLineType.none: + break; + } + } + + /// Override this method to customize the horizontal line drawing and styling. + @protected + void drawHorizontalAxisLine( + PaintingContext context, + Offset offset, + List? dashArray, + Paint paint, + ) { + if (parentBox == null) { + return; + } + + final Rect plotAreaBounds = parentBox!.paintBounds; + final Offset start = Offset(plotAreaBounds.left, offset.dy); + final Offset end = Offset(plotAreaBounds.right, offset.dy); + drawDashes(context.canvas, dashArray, paint, start: start, end: end); + } + + /// Override this method to customize the vertical line drawing and styling. + @protected + void drawVerticalAxisLine( + PaintingContext context, + Offset offset, + List? dashArray, + Paint paint, + ) { + if (parentBox == null) { + return; + } + + final Rect plotAreaBounds = parentBox!.paintBounds; + final Offset start = Offset(offset.dx, plotAreaBounds.top); + final Offset end = Offset(offset.dx, plotAreaBounds.bottom); + drawDashes(context.canvas, dashArray, paint, start: start, end: end); + } + + void _drawCrosshairTooltip( + PaintingContext context, + RenderBehaviorArea parent, + ) { + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (cartesianAxes == null) { + return; + } + + final Color themeBackgroundColor = + parent.chartThemeData!.crosshairBackgroundColor!; + _drawHorizontalAxisTooltip(context, cartesianAxes, themeBackgroundColor); + _drawVerticalAxisTooltip(context, cartesianAxes, themeBackgroundColor); + } + + void _drawHorizontalAxisTooltip( + PaintingContext context, + RenderCartesianAxes cartesianAxes, + Color themeBackgroundColor, + ) { + if (_horizontalPaths.isNotEmpty && + _horizontalLabels.isNotEmpty && + _horizontalLabelPositions.isNotEmpty) { + final Paint fillPaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + RenderChartAxis? child = cartesianAxes.firstChild; + int index = 0; + while (child != null) { + final InteractiveTooltip interactiveTooltip = child.interactiveTooltip; + if (!child.isVertical && + child.isVisible && + interactiveTooltip.enable && + child.visibleLabels.isNotEmpty) { + final TextStyle textStyle = child.chartThemeData!.crosshairTextStyle! + .merge(interactiveTooltip.textStyle); + fillPaint.color = interactiveTooltip.color ?? themeBackgroundColor; + strokePaint + ..color = interactiveTooltip.borderColor ?? themeBackgroundColor + ..strokeWidth = interactiveTooltip.borderWidth; + + drawHorizontalAxisTooltip( + context, + _horizontalLabelPositions[index], + _horizontalLabels[index], + textStyle, + _horizontalPaths[index], + fillPaint, + strokePaint, + ); + + index++; + } + + final CartesianAxesParentData childParentData = + child.parentData! as CartesianAxesParentData; + child = childParentData.nextSibling; + } + } + } + + void _drawVerticalAxisTooltip( + PaintingContext context, + RenderCartesianAxes cartesianAxes, + Color themeBackgroundColor, + ) { + if (_verticalPaths.isNotEmpty && + _verticalLabels.isNotEmpty && + _verticalLabelPositions.isNotEmpty) { + final Paint fillPaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + RenderChartAxis? child = cartesianAxes.firstChild; + int index = 0; + while (child != null) { + final InteractiveTooltip interactiveTooltip = child.interactiveTooltip; + if (child.isVertical && + child.isVisible && + interactiveTooltip.enable && + child.visibleLabels.isNotEmpty) { + final TextStyle textStyle = child.chartThemeData!.crosshairTextStyle! + .merge(interactiveTooltip.textStyle); + fillPaint.color = interactiveTooltip.color ?? themeBackgroundColor; + strokePaint + ..color = interactiveTooltip.borderColor ?? themeBackgroundColor + ..strokeWidth = interactiveTooltip.borderWidth; + + drawVerticalAxisTooltip( + context, + _verticalLabelPositions[index], + _verticalLabels[index], + textStyle, + _verticalPaths[index], + fillPaint, + strokePaint, + ); + + index++; + } + + final CartesianAxesParentData childParentData = + child.parentData! as CartesianAxesParentData; + child = childParentData.nextSibling; + } + } + } + + /// Override this method to customize the horizontal axis tooltip with styling + /// and it's position. + @protected + void drawHorizontalAxisTooltip( + PaintingContext context, + Offset position, + String text, + TextStyle style, [ + Path? path, + Paint? fillPaint, + Paint? strokePaint, + ]) { + _drawTooltipAndLabel( + context, + position, + text, + style, + path, + fillPaint, + strokePaint, + ); + } + + /// Override this method to customize the vertical axis tooltip with styling + /// and it's position. + @protected + void drawVerticalAxisTooltip( + PaintingContext context, + Offset position, + String text, + TextStyle style, [ + Path? path, + Paint? fillPaint, + Paint? strokePaint, + ]) { + _drawTooltipAndLabel( + context, + position, + text, + style, + path, + fillPaint, + strokePaint, + ); + } + + void _drawTooltipAndLabel( + PaintingContext context, + Offset position, + String text, + TextStyle style, [ + Path? path, + Paint? fillPaint, + Paint? strokePaint, + ]) { + if (text.isEmpty) { + return; + } + + // Draw tooltip rectangle. + if (path != null && fillPaint != null && strokePaint != null) { + context.canvas.drawPath(path, strokePaint); + context.canvas.drawPath(path, fillPaint); + } + + // Draw label. + final TextPainter textPainter = TextPainter( + text: TextSpan(text: text, style: style), + textAlign: TextAlign.center, + maxLines: getMaxLinesContent(text), + textDirection: TextDirection.ltr, + ); + textPainter + ..layout() + ..paint(context.canvas, position); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/trackball.dart b/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/trackball.dart new file mode 100644 index 000000000..398e6bb36 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/trackball.dart @@ -0,0 +1,3628 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../base.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/interactive_tooltip.dart'; +import '../common/marker.dart'; +import '../indicators/technical_indicator.dart'; +import '../interactions/behavior.dart'; +import '../series/bar_series.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; + +/// 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 using enable property of [TrackballBehavior]. +/// +/// Provides options to customize the [activationMode], [tooltipDisplayMode], +/// [lineType] and [tooltipSettings]. +@immutable +// ignore: must_be_immutable +class TrackballBehavior extends ChartBehavior { + /// Creating an argument constructor of TrackballBehavior class. + TrackballBehavior({ + 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, + this.lineColor, + this.lineWidth = 1, + this.shouldAlwaysShow = false, + this.builder, + this.hideDelay = 0, + }) { + _fetchImage(); + } + + /// Toggles the visibility of the trackball. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final bool enable; + + /// Width of the track line. + /// + /// Defaults to `1`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// lineWidth: 5 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final double lineWidth; + + /// Color of the track line. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// lineColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final Color? lineColor; + + /// Dashes of the track line. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// lineDashArray: [10,10] + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final List? lineDashArray; + + /// Gesture for activating the trackball. + /// + /// Trackball can be activated in tap, double tap and long press. + /// + /// Defaults to `ActivationMode.longPress`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// activationMode: ActivationMode.doubleTap + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final ActivationMode activationMode; + + /// Alignment of the trackball tooltip. + /// + /// The trackball tooltip can be aligned at the top, bottom, and center + /// position of the chart. + /// + /// _Note:_ This is applicable only when the `tooltipDisplayMode` property + /// is set to `TrackballDisplayMode.groupAllPoints`. + /// + /// Defaults to `ChartAlignment.center` + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipDisplayMode: TrackballDisplayMode.groupAllPoints, + /// tooltipAlignment: ChartAlignment.far + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final ChartAlignment tooltipAlignment; + + /// Type of trackball line. By default, vertical line will be displayed. + /// + /// You can change this by specifying values to this property. + /// + /// Defaults to `TrackballLineType.vertical`. + /// + /// Also refer [TrackballLineType] + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// lineType: TrackballLineType.vertical + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final TrackballLineType lineType; + + /// Display mode of tooltip. + /// + /// By default, tooltip of all the series under the current point index value + /// will be shown. + /// + /// Defaults to `TrackballDisplayMode.floatAllPoints`. + /// + /// Also refer [TrackballDisplayMode]. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipDisplayMode: TrackballDisplayMode.groupAllPoints + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final TrackballDisplayMode tooltipDisplayMode; + + /// Shows or hides the trackball. + /// + /// By default, the trackball will be hidden on touch. To avoid this, + /// set this property to true. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// shouldAlwaysShow: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final bool shouldAlwaysShow; + + /// Customizes the trackball tooltip. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// canShowMarker: false + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final InteractiveTooltip tooltipSettings; + + /// Giving disappear delay for trackball. + /// + /// Defaults to `0`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// hideDelay: 2000 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior, + /// ); + /// } + /// ``` + final double hideDelay; + + /// Builder of the trackball tooltip. + /// + /// Add any custom widget as the trackball template. + /// + /// If the trackball display mode is `groupAllPoints` or `nearestPoint` + /// it will called once and if it is + /// `floatAllPoints`, it will be called for each point. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// builder: (BuildContext context, TrackballDetails trackballDetails) { + /// return Container( + /// width: 70, + /// decoration: + /// const BoxDecoration(color: Color.fromRGBO(66, 244, 164, 1)), + /// child: Text('${trackballDetails.point?.cumulative}') + /// ); + /// } + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final ChartTrackballBuilder? builder; + + /// Hold trackball target position. + Offset? _position; + Offset? _dividerStartOffset; + Offset? _dividerEndOffset; + Timer? _trackballHideTimer; + SfChartThemeData? _chartThemeData; + ThemeData? _themeData; + Rect _plotAreaBounds = Rect.zero; + Image? _trackballImage; + bool _isTransposed = false; + bool _isLeft = false; + bool _isRight = false; + bool _isTop = false; + + List chartPointInfo = []; + final List _visiblePoints = []; + final List<_TooltipLabels> _tooltipLabels = <_TooltipLabels>[]; + final List _lineMarkers = []; + final List _tooltipMarkers = []; + final List _tooltipPaths = []; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is TrackballBehavior && + other.activationMode == activationMode && + other.lineType == lineType && + other.tooltipDisplayMode == tooltipDisplayMode && + other.tooltipAlignment == tooltipAlignment && + other.tooltipSettings == tooltipSettings && + other.lineDashArray == lineDashArray && + other.markerSettings == markerSettings && + other.enable == enable && + other.lineColor == lineColor && + other.lineWidth == lineWidth && + other.shouldAlwaysShow == shouldAlwaysShow && + other.builder == builder && + other.hideDelay == hideDelay; + } + + @override + int get hashCode { + final List values = [ + activationMode, + lineType, + tooltipDisplayMode, + tooltipAlignment, + tooltipSettings, + markerSettings, + lineDashArray, + enable, + lineColor, + lineWidth, + shouldAlwaysShow, + builder, + hideDelay, + ]; + return Object.hashAll(values); + } + + /// 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 in [TrackballMarkerSettings]. + /// + /// Provides the options like color, border width, border color and shape of + /// the marker to customize the appearance. + final TrackballMarkerSettings? markerSettings; + + /// Displays the trackball at the specified x and y-positions. + /// + /// *x and y - x & y pixels/values at which the trackball needs to be shown. + /// + /// coordinateUnit - specify the type of x and y values given. + /// + /// `pixel` or `point` for logical pixel and chart data point respectively. + /// + /// Defaults to `point`. + void show(dynamic x, double y, [String coordinateUnit = 'point']) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + assert(x != null); + assert(!y.isNaN); + if (coordinateUnit == 'point') { + _position = rawValueToPixelPoint( + x, + y, + parent.xAxis, + parent.yAxis, + parent.isTransposed, + ); + } else if (coordinateUnit == 'pixel') { + if (x is num) { + _position = Offset(x.toDouble(), y); + } else { + _position = Offset( + rawValueToPixelPoint( + x, + y, + parent.xAxis, + parent.yAxis, + parent.isTransposed, + ).dx, + y, + ); + } + } + + _show(parent); + } + + /// Displays the trackball at the specified point index. + /// + /// * pointIndex - index of the point for which the trackball must be shown + void showByIndex(int pointIndex) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent != null && + parent.plotArea != null && + parent.plotArea!.firstChild != null) { + final CartesianSeriesRenderer renderer = + parent.plotArea!.firstChild! as CartesianSeriesRenderer; + final List visibleIndexes = renderer.visibleIndexes; + if (visibleIndexes.isNotEmpty && + visibleIndexes.first <= pointIndex && + pointIndex <= visibleIndexes.last) { + show(renderer.xRawValues[pointIndex], 0); + } + } + } + + /// Hides the trackball if it is displayed. + void hide() { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + _position = null; + if (builder != null) { + parent?.trackballBuilder!([]); + } + _resetDataHolders(); + parent?.invalidate(); + } + + /// To customize the necessary pointer events in behaviors. + /// (e.g., CrosshairBehavior, TrackballBehavior, ZoomingBehavior). + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + if (parentBox == null) { + return; + } + + if (event is PointerDownEvent) { + _handlePointerDown(event); + } else if (event is PointerMoveEvent) { + _handlePointerMove(event); + } else if (event is PointerHoverEvent) { + _handlePointerHover(event); + } else if (event is PointerCancelEvent) { + _hideTrackball(immediately: true); + } else if (event is PointerUpEvent) { + _hideTrackball(); + } + } + + void _handlePointerDown(PointerDownEvent details) { + if (activationMode == ActivationMode.singleTap) { + _showTrackball(parentBox!.globalToLocal(details.position)); + } + } + + void _handlePointerMove(PointerMoveEvent details) { + if (activationMode == ActivationMode.singleTap) { + _showTrackball(parentBox!.globalToLocal(details.position)); + } + } + + void _handlePointerHover(PointerHoverEvent details) { + if (activationMode == ActivationMode.singleTap) { + _showTrackball(parentBox!.globalToLocal(details.position)); + } + } + + /// Called when a pointer or mouse enter on the screen. + @override + void handlePointerEnter(PointerEnterEvent details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showTrackball(parentBox!.globalToLocal(details.position)); + } + } + + /// Called when a pointer or mouse exit on the screen. + @override + void handlePointerExit(PointerExitEvent details) { + _hideTrackball(immediately: true); + } + + /// Called when a long press gesture by a primary button has been + /// recognized in behavior. + @override + void handleLongPressStart(LongPressStartDetails details) { + if (parentBox != null && activationMode == ActivationMode.longPress) { + _showTrackball(parentBox!.globalToLocal(details.globalPosition)); + } + } + + /// Called when moving after the long press gesture by a primary button is + /// recognized in behavior. + @override + void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + if (parentBox != null && activationMode == ActivationMode.longPress) { + _showTrackball(parentBox!.globalToLocal(details.globalPosition)); + } + } + + /// Called when the pointer stops contacting the screen after a long-press + /// by a primary button in behavior. + @override + void handleLongPressEnd(LongPressEndDetails details) { + _hideTrackball(); + } + + /// Called when the pointer tap has contacted the screen in behavior. + @override + void handleTapDown(TapDownDetails details) { + if (parentBox != null && activationMode == ActivationMode.singleTap) { + _showTrackball(parentBox!.globalToLocal(details.globalPosition)); + } + } + + /// Called when pointer has stopped contacting screen in behavior. + @override + void handleTapUp(TapUpDetails details) { + _hideTrackball(); + } + + /// Called when pointer tap has contacted the screen double time in behavior. + @override + void handleDoubleTap(Offset position) { + if (parentBox != null && activationMode == ActivationMode.doubleTap) { + _showTrackball(parentBox!.globalToLocal(position)); + _hideTrackball(doubleTapHideDelay: 200); + } + } + + void _showTrackball(Offset localPosition) { + if (enable) { + show(localPosition.dx, localPosition.dy, 'pixel'); + } + } + + void _hideTrackball({int doubleTapHideDelay = 0, bool immediately = false}) { + if (immediately) { + hide(); + } else if (!shouldAlwaysShow) { + final int hideDelayDuration = + hideDelay > 0 ? hideDelay.toInt() : doubleTapHideDelay; + _trackballHideTimer?.cancel(); + _trackballHideTimer = Timer( + Duration(milliseconds: hideDelayDuration), + () { + _trackballHideTimer = null; + hide(); + }, + ); + } + } + + void _fetchImage() { + if (markerSettings != null && + markerSettings!.shape == DataMarkerType.image && + markerSettings!.image != null) { + fetchImage(markerSettings!.image).then((Image? value) { + _trackballImage = value; + (parentBox as RenderBehaviorArea?)?.invalidate(); + }); + } else { + _trackballImage = null; + } + } + + void _show(RenderBehaviorArea parent) { + if (_position == null) { + return; + } + + _generateAllPoints(parent, _position!); + parent.invalidate(); + if (builder != null) { + final List details = []; + final List chartPoints = []; + final List currentPointIndices = []; + final List visibleSeriesIndices = []; + final List visibleSeriesList = []; + final int length = chartPointInfo.length; + if (tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { + for (int i = 0; i < length; i++) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + final bool isIndicator = pointInfo.series is IndicatorRenderer; + final bool isCartesianSeries = + pointInfo.series is CartesianSeriesRenderer; + if (isIndicator || + (isCartesianSeries && pointInfo.series.enableTrackball)) { + chartPoints.add(pointInfo.chartPoint!); + currentPointIndices.add(pointInfo.dataPointIndex!); + visibleSeriesIndices.add(pointInfo.seriesIndex!); + visibleSeriesList.add(pointInfo.series); + } + } + + final TrackballGroupingModeInfo groupingModeInfo = + TrackballGroupingModeInfo( + chartPoints, + currentPointIndices, + visibleSeriesIndices, + visibleSeriesList, + ); + details.add(TrackballDetails(null, null, null, null, groupingModeInfo)); + } else { + for (int i = 0; i < length; i++) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + final bool isIndicator = pointInfo.series is IndicatorRenderer; + final bool isCartesianSeries = + pointInfo.series is CartesianSeriesRenderer; + if (isIndicator || + (isCartesianSeries && pointInfo.series.enableTrackball)) { + details.add( + TrackballDetails( + pointInfo.chartPoint, + pointInfo.series, + pointInfo.dataPointIndex, + pointInfo.seriesIndex, + ), + ); + } + } + } + parent.trackballBuilder!(details); + chartPoints.clear(); + currentPointIndices.clear(); + visibleSeriesIndices.clear(); + visibleSeriesList.clear(); + } + } + + void _resetDataHolders() { + chartPointInfo.clear(); + _visiblePoints.clear(); + _tooltipLabels.clear(); + _lineMarkers.clear(); + _tooltipMarkers.clear(); + _tooltipPaths.clear(); + _dividerStartOffset = null; + _dividerEndOffset = null; + _isTransposed = false; + _isLeft = false; + _isRight = false; + _isTop = false; + } + + void _generateAllPoints(RenderBehaviorArea parent, Offset position) { + final RenderChartPlotArea? chartPlotArea = parent.plotArea; + if (chartPlotArea == null) { + return; + } + + _resetDataHolders(); + double leastX = 0.0; + final bool isRtl = parent.textDirection == TextDirection.rtl; + final bool isGroupMode = + tooltipDisplayMode == TrackballDisplayMode.groupAllPoints; + _chartThemeData = parent.chartThemeData; + _themeData = parent.themeData; + _plotAreaBounds = parent.paintBounds; + _isTransposed = parent.isTransposed; + + chartPlotArea.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.enableTrackball && + child.dataSource != null && + child.dataSource!.isNotEmpty && + child.animationController != null && + !child.animationController!.isAnimating) { + final List nearestPointIndexes = _findNearestChartPointIndexes( + child, + position, + ); + for (final int nearestPointIndex in nearestPointIndexes) { + final ChartSegment segment = child.segmentAt(nearestPointIndex); + final TrackballInfo? trackballInfo = segment.trackballInfo( + position, + nearestPointIndex, + ); + + if (trackballInfo != null) { + final ChartTrackballInfo trackInfo = + trackballInfo as ChartTrackballInfo; + if (trackInfo.pointIndex >= 0) { + final Offset trackPosition = trackInfo.position!; + double xPos = trackPosition.dx; + double yPos = trackPosition.dy; + final double touchXPos = position.dx; + if (trackInfo.seriesIndex == 0 || + ((leastX - touchXPos).abs() > (xPos - touchXPos).abs())) { + leastX = xPos; + } + + final Rect rect = _plotAreaBounds.intersect( + Rect.fromLTWH(xPos - 1, yPos - 1, 2, 2), + ); + if (_plotAreaBounds.contains(trackPosition) || + _plotAreaBounds.overlaps(rect)) { + _visiblePoints.add(Offset(xPos, yPos)); + _addChartPointInfo(trackInfo, xPos, yPos); + if (isGroupMode && leastX >= _plotAreaBounds.left) { + if (_isTransposed) { + yPos = leastX; + } else { + xPos = leastX; + } + } + } + + _updateLeastX(leastX, child.dataCount); + if (child is BarSeriesRenderer ? _isTransposed : _isTransposed) { + yPos = leastX; + } else { + xPos = leastX; + } + } + } + } + } + }); + + if (parent.indicatorArea != null) { + parent.indicatorArea!.visitChildren((RenderObject child) { + if (child is IndicatorRenderer && child.effectiveIsVisible) { + final List? trackballInfo = child.trackballInfo( + position, + ); + if (trackballInfo != null && + trackballInfo.isNotEmpty && + child.animationFactor == 1) { + for (final TrackballInfo trackInfo in trackballInfo) { + final ChartTrackballInfo info = trackInfo as ChartTrackballInfo; + final CartesianChartPoint chartPoint = info.point; + final bool pointIsNaN = + (chartPoint.xValue != null && chartPoint.xValue!.isNaN) || + (chartPoint.y != null && chartPoint.y!.isNaN); + if (trackInfo.pointIndex >= 0 && !pointIsNaN) { + final Offset indicatorPosition = info.position!; + double xPos = indicatorPosition.dx; + double yPos = indicatorPosition.dy; + final double touchXPos = position.dx; + if ((leastX - touchXPos).abs() > (xPos - touchXPos).abs()) { + leastX = xPos; + } + + if (_isTransposed && + (leastX - touchXPos).abs() > (yPos - touchXPos).abs()) { + leastX = yPos; + } + + final Rect rect = _plotAreaBounds.intersect( + Rect.fromLTWH(xPos - 1, yPos - 1, 2, 2), + ); + if (_plotAreaBounds.contains(indicatorPosition) || + _plotAreaBounds.overlaps(rect)) { + _visiblePoints.add(Offset(xPos, yPos)); + _addChartPointInfo(info, xPos, yPos); + if (isGroupMode && leastX >= _plotAreaBounds.left) { + if (_isTransposed) { + yPos = leastX; + } else { + xPos = leastX; + } + } + } + } + } + } + _updateLeastX(leastX, child.dataCount); + } + }); + } + + _validateLeastPointInfoWithLeastX(leastX); + _sortTrackballPoints(_isTransposed); + _triggerTrackballRenderCallback(parent); + _applyTooltipDisplayMode( + _chartThemeData!, + _themeData!, + leastX, + position, + isRtl, + ); + } + + void _sortTrackballPoints(bool isTranposed) { + if (_visiblePoints.isNotEmpty) { + isTranposed + ? _visiblePoints.sort((Offset a, Offset b) => a.dx.compareTo(b.dx)) + : _visiblePoints.sort((Offset a, Offset b) => a.dy.compareTo(b.dy)); + } + if (chartPointInfo.isNotEmpty) { + if (tooltipDisplayMode != TrackballDisplayMode.groupAllPoints) { + isTranposed + ? chartPointInfo.sort( + (ChartPointInfo a, ChartPointInfo b) => + a.xPosition!.compareTo(b.xPosition!), + ) + : tooltipDisplayMode == TrackballDisplayMode.floatAllPoints + ? chartPointInfo.sort( + (ChartPointInfo a, ChartPointInfo b) => + a.yPosition!.compareTo(b.yPosition!), + ) + : chartPointInfo.sort( + (ChartPointInfo a, ChartPointInfo b) => + b.yPosition!.compareTo(a.yPosition!), + ); + } + } + } + + void _triggerTrackballRenderCallback(RenderBehaviorArea parent) { + if (parent.onTrackballPositionChanging != null) { + final int length = chartPointInfo.length - 1; + for (int i = length; i >= 0; i--) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + final TrackballArgs trackballArgs = TrackballArgs(); + trackballArgs.chartPointInfo = pointInfo; + parent.onTrackballPositionChanging!(trackballArgs); + chartPointInfo[i].label = trackballArgs.chartPointInfo.label; + chartPointInfo[i].header = trackballArgs.chartPointInfo.header; + if (builder == null && pointInfo.label == null || + pointInfo.label == '') { + chartPointInfo.removeAt(i); + _visiblePoints.removeAt(i); + } + } + } + } + + List _findNearestChartPointIndexes( + CartesianSeriesRenderer series, + Offset position, + ) { + final List indexes = []; + final int dataCount = series.dataCount; + final RenderChartAxis xAxis = series.xAxis!; + final RenderChartAxis yAxis = series.yAxis!; + final Rect bounds = series.paintBounds; + num xValue = xAxis.pixelToPoint(bounds, position.dx, position.dy); + num yValue = yAxis.pixelToPoint(bounds, position.dx, position.dy); + + if (series.canFindLinearVisibleIndexes && + xAxis is RenderCategoryAxis && + xAxis.arrangeByIndex) { + final DoubleRange range = xAxis.visibleRange!; + final int index = xValue.round(); + if (index <= range.maximum && + index >= range.minimum && + index < dataCount && + index >= 0) { + indexes.add(index); + } + return indexes; + } else { + if (xAxis is RenderLogarithmicAxis) { + xValue = xAxis.toPow(xValue); + } + if (yAxis is RenderLogarithmicAxis) { + yValue = yAxis.toPow(yValue); + } + + if (series.canFindLinearVisibleIndexes) { + final int binaryIndex = binarySearch( + series.xValues, + xValue.toDouble(), + 0, + dataCount - 1, + ); + if (binaryIndex >= 0) { + indexes.add(binaryIndex); + } + } else { + double delta = 0; + num nearPointX = series.xValues[0]; + num nearPointY = series.yAxis!.visibleRange!.minimum; + for (int i = 0; i < dataCount; i++) { + final num touchXValue = xValue; + final num touchYValue = yValue; + final double curX = series.xValues[i].toDouble(); + final double curY = series.trackballYValue(i).toDouble(); + if (delta == touchXValue - curX) { + if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { + indexes.clear(); + } + indexes.add(i); + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + indexes.clear(); + indexes.add(i); + } + } + } + return indexes; + } + } + + void _addChartPointInfo( + ChartTrackballInfo trackballInfo, + double xPos, + double yPos, + ) { + final ChartPointInfo pointInfo = ChartPointInfo( + label: trackballInfo.text, + header: trackballInfo.header, + color: trackballInfo.color, + series: trackballInfo.series, + seriesName: trackballInfo.name, + seriesIndex: trackballInfo.seriesIndex, + chartPoint: trackballInfo.point, + dataPointIndex: trackballInfo.pointIndex, + xPosition: xPos, + yPosition: yPos, + markerXPos: xPos, + markerYPos: yPos, + lowYPosition: trackballInfo.lowYPos, + highXPosition: trackballInfo.highXPos, + highYPosition: trackballInfo.highYPos, + openXPosition: trackballInfo.openXPos, + openYPosition: trackballInfo.openYPos, + closeXPosition: trackballInfo.closeXPos, + closeYPosition: trackballInfo.closeYPos, + minYPosition: trackballInfo.minYPos, + maxYPosition: trackballInfo.maxYPos, + maxXPosition: trackballInfo.maxXPos, + lowerXPosition: trackballInfo.lowerXPos, + lowerYPosition: trackballInfo.lowerYPos, + upperXPosition: trackballInfo.upperXPos, + upperYPosition: trackballInfo.upperYPos, + ); + chartPointInfo.add(pointInfo); + } + + void _updateLeastX(double leastX, int dataCount) { + if (chartPointInfo.isNotEmpty && + chartPointInfo[0].dataPointIndex! < dataCount) { + leastX = chartPointInfo[0].xPosition!; + } + } + + void _validateLeastPointInfoWithLeastX(double leastX) { + final int length = chartPointInfo.length; + if (length > 1) { + final ChartPointInfo firstPoint = chartPointInfo[0]; + final bool isFloatAllPoints = + tooltipDisplayMode == TrackballDisplayMode.floatAllPoints; + for (int i = 0; i < length; i++) { + final ChartPointInfo currentPoint = chartPointInfo[i]; + if (firstPoint.chartPoint!.xValue! != + currentPoint.chartPoint!.xValue!) { + final List leastPointInfo = []; + for (final ChartPointInfo pointInfo in chartPointInfo) { + final double xPos = pointInfo.xPosition!; + if (xPos == leastX) { + leastPointInfo.add(pointInfo); + final int leastLength = leastPointInfo.length; + if (!(isFloatAllPoints && + leastLength > 1 && + (pointInfo.series is IndicatorRenderer || + pointInfo.seriesIndex != + leastPointInfo[leastLength - 2].seriesIndex))) { + _visiblePoints.clear(); + } + + _visiblePoints.add(Offset(xPos, pointInfo.yPosition!)); + } + } + chartPointInfo.clear(); + chartPointInfo = leastPointInfo; + break; + } + } + } + } + + void _validateNearestChartPointInfo(double leastX, Offset position) { + final double touchXPos = position.dx; + final double touchYPos = position.dy; + int length = chartPointInfo.length; + if (length > 1) { + ChartPointInfo? pointInfo; + final ChartPointInfo firstPoint = chartPointInfo[0]; + final double firstX = firstPoint.xPosition!; + if (leastX != firstX && !_isTransposed) { + chartPointInfo.remove(firstPoint); + } + pointInfo = firstPoint; + length = chartPointInfo.length; + for (int i = 1; i < length; i++) { + final ChartPointInfo nextPoint = chartPointInfo[i]; + final double nextX = nextPoint.xPosition!; + final double nextY = nextPoint.yPosition!; + final bool isXYPositioned = + !_isTransposed + ? (((pointInfo!.yPosition! - touchYPos).abs() > + (nextY - touchYPos).abs()) && + pointInfo.xPosition! == nextX) + : (((pointInfo!.xPosition! - touchXPos).abs() > + (nextX - touchXPos).abs()) && + pointInfo.yPosition! == nextY); + if (isXYPositioned) { + pointInfo = chartPointInfo[i]; + } + } + + if (pointInfo != null) { + chartPointInfo + ..clear() + ..add(pointInfo); + } + } + } + + void _applyTooltipDisplayMode( + SfChartThemeData chartThemeData, + ThemeData themeData, + double leastX, + Offset position, + bool isRtl, + ) { + if (chartPointInfo.isEmpty) { + return; + } + + // It applicable for template tooltip. + if (tooltipDisplayMode == TrackballDisplayMode.nearestPoint) { + _validateNearestChartPointInfo(leastX, position); + } + + if (builder == null) { + final bool markerIsVisible = + markerSettings != null && + markerSettings!.markerVisibility == TrackballVisibilityMode.visible; + final bool markerAutoVisibility = + markerSettings != null && + markerSettings!.markerVisibility == TrackballVisibilityMode.auto; + + if (tooltipSettings.enable) { + final TextStyle labelStyle = _createLabelStyle( + FontWeight.normal, + _chartThemeData!.trackballTextStyle!, + ); + switch (tooltipDisplayMode) { + case TrackballDisplayMode.nearestPoint: + _applyNearestPointDisplayMode( + labelStyle, + markerIsVisible, + markerAutoVisibility, + isRtl, + ); + break; + + case TrackballDisplayMode.floatAllPoints: + _applyFloatAllPointsDisplayMode( + labelStyle, + markerIsVisible, + markerAutoVisibility, + isRtl, + ); + break; + + case TrackballDisplayMode.groupAllPoints: + _applyGroupAllPointDisplayMode( + labelStyle, + markerIsVisible, + markerAutoVisibility, + isRtl, + ); + break; + + case TrackballDisplayMode.none: + break; + } + } + + if (markerSettings != null && + markerSettings!.markerVisibility != TrackballVisibilityMode.hidden) { + _computeLineMarkers( + themeData, + _lineMarkers, + markerIsVisible, + markerAutoVisibility, + ); + } + } + } + + void _applyNearestPointDisplayMode( + TextStyle labelStyle, + bool markerIsVisible, + bool markerAutoVisibility, + bool isRtl, + ) { + final double arrowLength = tooltipSettings.arrowLength; + final double arrowWidth = tooltipSettings.arrowWidth; + double borderRadius = tooltipSettings.borderRadius; + for (final ChartPointInfo pointInfo in chartPointInfo) { + final Size labelSize = _labelSize(pointInfo.label!, labelStyle); + final dynamic series = pointInfo.series; + double width = labelSize.width; + if (width < defaultTooltipWidth) { + width = defaultTooltipWidth; + borderRadius = borderRadius > 5 ? 5 : borderRadius; + } + borderRadius = borderRadius > 15 ? 15 : borderRadius; + final double padding = + (markerAutoVisibility + ? series is IndicatorRenderer || + (series != null && series.markerSettings.isVisible) + : markerIsVisible) + ? (markerSettings!.width / 2) + defaultTrackballPadding + : defaultTrackballPadding; + + _computeNearestTooltip( + pointInfo, + labelStyle, + width, + labelSize.height, + padding, + arrowWidth, + arrowLength, + borderRadius, + isRtl, + ); + } + } + + void _computeNearestTooltip( + ChartPointInfo pointInfo, + TextStyle labelStyle, + double width, + double height, + double padding, + double arrowWidth, + double arrowLength, + double borderRadius, + bool isRtl, + ) { + final double xPosition = pointInfo.xPosition!; + final double yPosition = pointInfo.yPosition!; + final Rect tooltipRect = _tooltipRect(xPosition, yPosition, width, height); + final double labelRectWidth = tooltipRect.width; + final double labelRectHeight = tooltipRect.height; + final Offset alignPosition = _alignPosition( + xPosition, + yPosition, + labelRectWidth, + labelRectHeight, + arrowLength, + arrowWidth, + padding, + isRtl, + ); + final RRect tooltipRRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + alignPosition.dx, + alignPosition.dy, + labelRectWidth, + labelRectHeight, + ), + Radius.circular(borderRadius), + ); + + final Path nosePath = _nosePath( + _tooltipDirection(), + tooltipRRect, + Offset(xPosition, yPosition), + arrowLength, + arrowWidth, + ); + final Path nearestTooltipPath = + Path() + ..addRRect(tooltipRRect) + ..addPath(nosePath, Offset.zero); + _tooltipPaths.add(nearestTooltipPath); + + if (tooltipSettings.canShowMarker) { + final Offset markerPosition = _markerPosition( + tooltipRRect, + width, + height, + defaultTrackballPadding, + isRtl, + ); + _computeTooltipMarkers(pointInfo, markerPosition); + } + + if (pointInfo.label != null) { + _computeTooltipLabels( + pointInfo.label!, + width, + height, + labelStyle, + tooltipRRect, + isRtl, + ); + } + } + + void _applyFloatAllPointsDisplayMode( + TextStyle labelStyle, + bool markerIsVisible, + bool markerAutoVisibility, + bool isRtl, + ) { + final double arrowLength = tooltipSettings.arrowLength; + final double arrowWidth = tooltipSettings.arrowWidth; + double borderRadius = tooltipSettings.borderRadius; + final _TooltipPositions floatTooltipPosition = + _computeTooltipPositionForFloatAllPoints(labelStyle, borderRadius); + + final int length = chartPointInfo.length; + for (int i = 0; i < length; i++) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + final dynamic series = pointInfo.series; + final Size labelSize = _labelSize(pointInfo.label!, labelStyle); + double width = labelSize.width; + if (width < defaultTooltipWidth) { + width = defaultTooltipWidth; + borderRadius = borderRadius > 5 ? 5 : borderRadius; + } + borderRadius = borderRadius > 15 ? 15 : borderRadius; + final double padding = + (markerAutoVisibility + ? series is IndicatorRenderer || + (series != null && series.markerSettings.isVisible) + : markerIsVisible) + ? (markerSettings!.width / 2) + defaultTrackballPadding + : defaultTrackballPadding; + + final num tooltipTop = floatTooltipPosition.tooltipTop[i]; + final num tooltipBottom = floatTooltipPosition.tooltipBottom[i]; + if (_isTransposed + ? tooltipTop >= _plotAreaBounds.left && + tooltipBottom <= _plotAreaBounds.right + : tooltipTop >= _plotAreaBounds.top && + tooltipBottom <= _plotAreaBounds.bottom) { + _computeFloatAllPointTooltip( + i, + pointInfo, + width, + labelSize.height, + padding, + arrowWidth, + arrowLength, + borderRadius, + labelStyle, + floatTooltipPosition, + isRtl, + ); + } + } + } + + void _computeFloatAllPointTooltip( + int index, + ChartPointInfo pointInfo, + double width, + double height, + double padding, + double arrowWidth, + double arrowLength, + double borderRadius, + TextStyle labelStyle, + _TooltipPositions tooltipPosition, + bool isRtl, + ) { + final double xPosition = pointInfo.xPosition!; + final double yPosition = pointInfo.yPosition!; + final Rect tooltipRect = _tooltipRect(xPosition, yPosition, width, height); + final double labelRectWidth = tooltipRect.width; + final double labelRectHeight = tooltipRect.height; + final Offset alignPosition = _alignPosition( + xPosition, + yPosition, + labelRectWidth, + labelRectHeight, + arrowLength, + arrowWidth, + padding, + isRtl, + ); + + final double topValue = tooltipPosition.tooltipTop[index].toDouble(); + final RRect tooltipRRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + _isTransposed ? topValue : alignPosition.dx, + _isTransposed ? alignPosition.dy : topValue, + labelRectWidth, + labelRectHeight, + ), + Radius.circular(borderRadius), + ); + + final Path nosePath = _nosePath( + _tooltipDirection(), + tooltipRRect, + Offset(xPosition, yPosition), + arrowLength, + arrowWidth, + ); + final Path nearestTooltipPath = + Path() + ..addRRect(tooltipRRect) + ..addPath(nosePath, Offset.zero); + _tooltipPaths.add(nearestTooltipPath); + + if (tooltipSettings.canShowMarker) { + final Offset markerPosition = _markerPosition( + tooltipRRect, + width, + height, + defaultTrackballPadding, + isRtl, + ); + _computeTooltipMarkers(pointInfo, markerPosition); + } + + if (pointInfo.label != null) { + _computeTooltipLabels( + pointInfo.label!, + width, + height, + labelStyle, + tooltipRRect, + isRtl, + ); + } + } + + _TooltipPositions _computeTooltipPositionForFloatAllPoints( + TextStyle labelStyle, + double borderRadius, + ) { + final List tooltipTop = []; + final List tooltipBottom = []; + final List xAxesInfo = []; + final List yAxesInfo = []; + final num tooltipPaddingForFloatPoint = _isTransposed ? 8 : 5; + final int length = chartPointInfo.length; + for (int i = 0; i < length; i++) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + final dynamic series = pointInfo.series; + final String label = pointInfo.label!; + final Size labelSize = _labelSize(label, labelStyle); + final double height = labelSize.height; + double width = labelSize.width; + if (width < defaultTooltipWidth) { + width = defaultTooltipWidth; + } + + if (label != '' && _visiblePoints.isNotEmpty) { + final Offset visiblePoint = _visiblePoints[i]; + final double closeX = visiblePoint.dx; + final double closeY = visiblePoint.dy; + tooltipTop.add( + _isTransposed + ? closeX - tooltipPaddingForFloatPoint - (width / 2) + : closeY - tooltipPaddingForFloatPoint - height / 2, + ); + tooltipBottom.add( + _isTransposed + ? (closeX + tooltipPaddingForFloatPoint + (width / 2)) + + (tooltipSettings.canShowMarker + ? trackballTooltipMarkerSize + : 0) + : closeY + tooltipPaddingForFloatPoint + height / 2, + ); + if (series != null && series.xAxis != null) { + xAxesInfo.add(series.xAxis!); + } + if (series != null && series.yAxis != null) { + yAxesInfo.add(series.yAxis!); + } + } + } + + if (tooltipTop.isNotEmpty && tooltipBottom.isNotEmpty) { + return _smartTooltipPositions( + tooltipTop, + tooltipBottom, + xAxesInfo, + yAxesInfo, + chartPointInfo, + tooltipPaddingForFloatPoint, + ); + } + return _TooltipPositions(tooltipTop, tooltipBottom); + } + + /// Method to place the collided tooltips properly + _TooltipPositions _smartTooltipPositions( + List tooltipTop, + List tooltipBottom, + List xAxesInfo, + List yAxesInfo, + List chartPointInfo, [ + num tooltipPaddingForFloatPoint = 0, + ]) { + final List visibleLocation = []; + num totalHeight = 0; + final int length = chartPointInfo.length; + for (int i = 0; i < length; i++) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + _isTransposed + ? visibleLocation.add(pointInfo.xPosition!) + : visibleLocation.add(pointInfo.yPosition!); + totalHeight += + tooltipBottom[i] - tooltipTop[i] + tooltipPaddingForFloatPoint; + } + + _TooltipPositions smartTooltipPosition = _continuousOverlappingPoints( + tooltipTop, + tooltipBottom, + visibleLocation, + tooltipPaddingForFloatPoint, + ); + if (!_isTransposed + ? totalHeight < (_plotAreaBounds.bottom - _plotAreaBounds.top) + : totalHeight < (_plotAreaBounds.right - _plotAreaBounds.left)) { + smartTooltipPosition = _verticalArrangements( + smartTooltipPosition, + xAxesInfo, + yAxesInfo, + tooltipPaddingForFloatPoint, + ); + } + return smartTooltipPosition; + } + + _TooltipPositions _verticalArrangements( + _TooltipPositions tooltipPosition, + List xAxesInfo, + List yAxesInfo, + num tooltipPaddingForFloatPoint, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return tooltipPosition; + } + num? startPos; + num? chartHeight; + num secWidth; + num width; + final int tooltipTopLength = tooltipPosition.tooltipTop.length; + RenderChartAxis yAxis; + for (int i = tooltipTopLength - 1; i >= 0; i--) { + yAxis = yAxesInfo[i]; + RenderChartAxis? child = parent.cartesianAxes!.firstChild; + while (child != null) { + if (yAxis == child) { + if (_isTransposed) { + chartHeight = _plotAreaBounds.right; + startPos = _plotAreaBounds.left; + } else { + chartHeight = _plotAreaBounds.bottom - _plotAreaBounds.top; + startPos = _plotAreaBounds.top; + } + } + + final CartesianAxesParentData childParentData = + child.parentData! as CartesianAxesParentData; + child = childParentData.nextSibling; + } + + width = tooltipPosition.tooltipBottom[i] - tooltipPosition.tooltipTop[i]; + if (chartHeight != null && + chartHeight < tooltipPosition.tooltipBottom[i]) { + tooltipPosition.tooltipBottom[i] = chartHeight - 2; + tooltipPosition.tooltipTop[i] = + tooltipPosition.tooltipBottom[i] - width; + for (int j = i - 1; j >= 0; j--) { + secWidth = + tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; + if (tooltipPosition.tooltipBottom[j] > + tooltipPosition.tooltipTop[j + 1] && + (tooltipPosition.tooltipTop[j + 1] > startPos! && + tooltipPosition.tooltipBottom[j + 1] < chartHeight)) { + tooltipPosition.tooltipBottom[j] = + tooltipPosition.tooltipTop[j + 1] - tooltipPaddingForFloatPoint; + tooltipPosition.tooltipTop[j] = + tooltipPosition.tooltipBottom[j] - secWidth; + } + } + } + } + + for (int i = 0; i < tooltipTopLength; i++) { + yAxis = yAxesInfo[i]; + RenderChartAxis? child = parent.cartesianAxes!.firstChild; + while (child != null) { + if (yAxis == child) { + if (_isTransposed) { + chartHeight = _plotAreaBounds.right; + startPos = _plotAreaBounds.left; + } else { + chartHeight = _plotAreaBounds.bottom - _plotAreaBounds.top; + startPos = _plotAreaBounds.top; + } + } + + final CartesianAxesParentData childParentData = + child.parentData! as CartesianAxesParentData; + child = childParentData.nextSibling; + } + + width = tooltipPosition.tooltipBottom[i] - tooltipPosition.tooltipTop[i]; + if (startPos != null && tooltipPosition.tooltipTop[i] < startPos) { + tooltipPosition.tooltipTop[i] = startPos + 1; + tooltipPosition.tooltipBottom[i] = + tooltipPosition.tooltipTop[i] + width; + for (int j = i + 1; j <= (tooltipTopLength - 1); j++) { + secWidth = + tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; + if (tooltipPosition.tooltipTop[j] < + tooltipPosition.tooltipBottom[j - 1] && + (tooltipPosition.tooltipTop[j - 1] > startPos && + tooltipPosition.tooltipBottom[j - 1] < chartHeight!)) { + tooltipPosition.tooltipTop[j] = + tooltipPosition.tooltipBottom[j - 1] + + tooltipPaddingForFloatPoint; + tooltipPosition.tooltipBottom[j] = + tooltipPosition.tooltipTop[j] + secWidth; + } + } + } + } + return tooltipPosition; + } + + // Method to identify the colliding trackball tooltips and + // return the new tooltip positions + _TooltipPositions _continuousOverlappingPoints( + List tooltipTop, + List tooltipBottom, + List visibleLocation, + num tooltipPadding, + ) { + num temp; + num count = 0; + num start = 0; + num halfHeight; + num midPos; + num tempTooltipHeight; + num temp1TooltipHeight; + int startPoint = 0, i, j, k; + final num endPoint = tooltipBottom.length - 1; + final num firstTop = tooltipTop[0]; + num tooltipHeight = (tooltipBottom[0] - firstTop) + tooltipPadding; + temp = firstTop + tooltipHeight; + start = firstTop; + for (i = 0; i < endPoint; i++) { + // To identify that tooltip collides or not. + if (temp >= tooltipTop[i + 1]) { + tooltipHeight = + tooltipBottom[i + 1] - tooltipTop[i + 1] + tooltipPadding; + temp += tooltipHeight; + count++; + // This condition executes when the tooltip count is half of the total + // number of tooltips. + if (count - 1 == endPoint - 1 || i == endPoint - 1) { + halfHeight = (temp - start) / 2; + midPos = (visibleLocation[startPoint] + visibleLocation[i + 1]) / 2; + tempTooltipHeight = + tooltipBottom[startPoint] - tooltipTop[startPoint]; + tooltipTop[startPoint] = midPos - halfHeight; + tooltipBottom[startPoint] = + tooltipTop[startPoint] + tempTooltipHeight; + for (k = startPoint; k > 0; k--) { + if (tooltipTop[k] <= tooltipBottom[k - 1] + tooltipPadding) { + temp1TooltipHeight = tooltipBottom[k - 1] - tooltipTop[k - 1]; + tooltipTop[k - 1] = + tooltipTop[k] - temp1TooltipHeight - tooltipPadding; + tooltipBottom[k - 1] = tooltipTop[k - 1] + temp1TooltipHeight; + } else { + break; + } + } + // To set tool tip positions based on the half height and + // other tooltip height. + for (j = startPoint + 1; j <= startPoint + count; j++) { + tempTooltipHeight = tooltipBottom[j] - tooltipTop[j]; + tooltipTop[j] = tooltipBottom[j - 1] + tooltipPadding; + tooltipBottom[j] = tooltipTop[j] + tempTooltipHeight; + } + } + } else { + count = i > 0 ? count : 0; + // This executes when any of the middle tooltip collides. + if (count > 0) { + halfHeight = (temp - start) / 2; + midPos = (visibleLocation[startPoint] + visibleLocation[i]) / 2; + tempTooltipHeight = + tooltipBottom[startPoint] - tooltipTop[startPoint]; + tooltipTop[startPoint] = midPos - halfHeight; + tooltipBottom[startPoint] = + tooltipTop[startPoint] + tempTooltipHeight; + for (k = startPoint; k > 0; k--) { + if (tooltipTop[k] <= tooltipBottom[k - 1] + tooltipPadding) { + temp1TooltipHeight = tooltipBottom[k - 1] - tooltipTop[k - 1]; + tooltipTop[k - 1] = + tooltipTop[k] - temp1TooltipHeight - tooltipPadding; + tooltipBottom[k - 1] = tooltipTop[k - 1] + temp1TooltipHeight; + } else { + break; + } + } + + // To set tool tip positions based on the half height and + // other tooltip height. + for (j = startPoint + 1; j <= startPoint + count; j++) { + tempTooltipHeight = tooltipBottom[j] - tooltipTop[j]; + tooltipTop[j] = tooltipBottom[j - 1] + tooltipPadding; + tooltipBottom[j] = tooltipTop[j] + tempTooltipHeight; + } + count = 0; + } + tooltipHeight = + (tooltipBottom[i + 1] - tooltipTop[i + 1]) + tooltipPadding; + temp = tooltipTop[i + 1] + tooltipHeight; + start = tooltipTop[i + 1]; + startPoint = i + 1; + } + } + return _TooltipPositions(tooltipTop, tooltipBottom); + } + + void _applyGroupAllPointDisplayMode( + TextStyle labelStyle, + bool markerIsVisible, + bool markerAutoVisibility, + bool isRtl, + ) { + double borderRadius = tooltipSettings.borderRadius; + final ChartPointInfo pointInfo = chartPointInfo[0]; + final double xPosition = pointInfo.xPosition!; + final double yPosition = pointInfo.yPosition!; + final dynamic series = pointInfo.series; + final Size totalLabelSize = _labelSizeForGroupAllPoints(labelStyle); + final double height = totalLabelSize.height; + double width = totalLabelSize.width; + if (width < defaultTooltipWidth) { + width = defaultTooltipWidth; + borderRadius = borderRadius > 5 ? 5 : borderRadius; + } + borderRadius = borderRadius > 15 ? 15 : borderRadius; + final double padding = + (markerAutoVisibility + ? series is IndicatorRenderer || + (series != null && series.markerSettings.isVisible) + : markerIsVisible) + ? (markerSettings!.width / 2) + defaultTrackballPadding + : defaultTrackballPadding; + + final Rect tooltipRect = _tooltipRect(xPosition, yPosition, width, height); + final double labelRectWidth = tooltipRect.width; + final double labelRectHeight = tooltipRect.height; + final Offset defaultPosition = _defaultGroupPosition(xPosition, yPosition); + final Offset alignPosition = _alignPosition( + defaultPosition.dx, + defaultPosition.dy, + labelRectWidth, + labelRectHeight, + tooltipSettings.arrowLength, + tooltipSettings.arrowWidth, + padding, + isRtl, + true, + ); + + final RRect tooltipRRect = _validateRect( + Rect.fromLTWH( + alignPosition.dx, + alignPosition.dy, + labelRectWidth, + labelRectHeight, + ), + _plotAreaBounds, + borderRadius, + ); + + if (tooltipRRect != RRect.zero) { + _tooltipPaths.add(Path()..addRRect(tooltipRRect)); + _computeGroupTooltipLabels( + alignPosition, + tooltipRRect, + totalLabelSize, + labelStyle, + isRtl, + ); + } + } + + void _computeGroupTooltipLabels( + Offset alignPosition, + RRect tooltipRRect, + Size totalLabelSize, + TextStyle textStyle, + bool isRtl, + ) { + bool hasIndicator = false; + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent != null && parent.indicatorArea != null) { + hasIndicator = true; + } + + final bool canShowMarker = tooltipSettings.canShowMarker; + final double markerSize = canShowMarker ? trackballTooltipMarkerSize : 0; + const double halfMarkerSize = trackballTooltipMarkerSize / 2; + // It specifies for marker position calculation. + double totalLabelHeight = tooltipRRect.top + defaultTrackballPadding; + // It specifies for label position calculation with label style. + double eachTextHeight = 0; + + final String? header = chartPointInfo[0].header; + if (header != null && header != '') { + const double headerPadding = defaultTooltipWidth; + final TextStyle boldStyle = _createLabelStyle(FontWeight.bold, textStyle); + final Size headerSize = measureText(header, boldStyle); + final double headerHeight = headerSize.height; + totalLabelHeight += headerHeight; + eachTextHeight += headerHeight; + + _tooltipLabels.add( + _TooltipLabels( + header, + boldStyle, + Offset( + tooltipRRect.left + tooltipRRect.width / 2, + tooltipRRect.top + headerHeight / 2 + headerPadding / 2, + ).translate(-headerSize.width / 2, -headerSize.height / 2), + ), + ); + + // Divider offset calculation. + _dividerStartOffset = Offset( + tooltipRRect.left + headerPadding, + tooltipRRect.top + headerHeight + headerPadding, + ); + _dividerEndOffset = Offset( + tooltipRRect.right - headerPadding, + tooltipRRect.top + headerHeight + headerPadding, + ); + } + + // Empty text size consideration between the header and series text. + final Size emptyTextSize = measureText('', textStyle); + final double emptyTextHeight = emptyTextSize.height; + totalLabelHeight += emptyTextHeight; + eachTextHeight += emptyTextHeight; + + final bool hasFormat = tooltipSettings.format != null; + double padding = defaultTrackballPadding; + if (isRtl && !canShowMarker) { + if (hasFormat) { + padding += halfMarkerSize; + } else { + padding = halfMarkerSize; + } + } else if (!isRtl && !canShowMarker && !hasFormat) { + padding += padding; + } + + final double rectLeftWithPadding = tooltipRRect.left + padding; + final double rectRightWithPadding = tooltipRRect.right - padding; + final double markerX = + isRtl + ? rectRightWithPadding - halfMarkerSize + : rectLeftWithPadding + halfMarkerSize; + final double x = + isRtl + ? rectRightWithPadding - markerSize + : rectLeftWithPadding + markerSize; + final double y = tooltipRRect.top + defaultTrackballPadding; + final int length = chartPointInfo.length; + for (int i = 0; i < length; i++) { + final ChartPointInfo pointInfo = chartPointInfo[i]; + final String text = pointInfo.label!; + final Size actualLabelSize = measureText(text, textStyle); + final double actualLabelHeight = actualLabelSize.height; + totalLabelHeight += actualLabelHeight; + // Apply gap between xYDataSeries and SBS series types. + if (!hasFormat && text.contains('\n')) { + totalLabelHeight += defaultTrackballPadding; + } + + // Marker position calculation. + if (canShowMarker) { + Offset markerPosition; + if (text.contains('\n') && hasIndicator) { + markerPosition = Offset( + markerX, + totalLabelHeight - actualLabelHeight + defaultTrackballPadding, + ); + } else { + markerPosition = Offset( + markerX, + totalLabelHeight - actualLabelHeight / 2, + ); + } + + _computeTooltipMarkers(pointInfo, markerPosition); + } + + // Label style and position calculation. + final double dy = y + eachTextHeight; + if (hasFormat) { + final double dx = canShowMarker ? x : x + defaultTrackballPadding; + _computeFormatTooltipLabels(dx, dy, text, textStyle, isRtl); + } else { + _computeDefaultTooltipLabels(x, dy, text, textStyle, isRtl); + // Apply gap between xYDataSeries and SBS series types. + if (text.contains('\n')) { + eachTextHeight += defaultTrackballPadding; + } + } + eachTextHeight += actualLabelHeight; + } + } + + Size _labelSize(String text, TextStyle textStyle) { + if (text != '') { + if (text.contains('') && text.contains('')) { + text = text.replaceAll('', '').replaceAll('', ''); + return measureText(text, _createLabelStyle(FontWeight.bold, textStyle)); + } + } + return measureText(text, textStyle); + } + + Size _labelSizeForGroupAllPoints(TextStyle textStyle) { + double width = 0; + double height = 0; + final bool hasFormat = tooltipSettings.format != null; + final TextStyle boldStyle = _createLabelStyle(FontWeight.bold, textStyle); + TextStyle labelStyle = boldStyle; + if (hasFormat) { + final String format = tooltipSettings.format!; + if ((!format.contains('') || !format.contains('')) && + (format.contains(':') && format.split(':').length != 2)) { + labelStyle = textStyle; + } + } + + // Header text size. + final String? header = chartPointInfo[0].header; + if (header != null) { + final Size headerSize = _labelSize(header, boldStyle); + if (headerSize.width > width) { + width = headerSize.width; + } + height += headerSize.height; + } + + // Empty text size consideration. + final Size emptyTextSize = measureText('', labelStyle); + if (emptyTextSize.width > width) { + width = emptyTextSize.width; + } + height += emptyTextSize.height; + + final int length = chartPointInfo.length; + for (int i = 0; i < length; i++) { + final String? label = chartPointInfo[i].label; + if (label != null) { + final Size labelSize = _labelSize(label, labelStyle); + if (labelSize.width > width) { + width = labelSize.width; + } + height += labelSize.height; + // Apply gap between xYDataSeries and SBS series types. + if (!hasFormat && label.contains('\n')) { + height += defaultTrackballPadding; + } + } + } + return Size(width, height); + } + + Offset _defaultGroupPosition(double xPosition, double yPosition) { + double xPos = xPosition; + double yPos = yPosition; + if (_isTransposed) { + switch (tooltipAlignment) { + case ChartAlignment.near: + xPos = _plotAreaBounds.left; + break; + + case ChartAlignment.center: + xPos = _plotAreaBounds.center.dx; + break; + + case ChartAlignment.far: + xPos = _plotAreaBounds.right; + break; + } + } else { + switch (tooltipAlignment) { + case ChartAlignment.near: + yPos = _plotAreaBounds.top; + break; + + case ChartAlignment.center: + yPos = _plotAreaBounds.center.dy; + break; + + case ChartAlignment.far: + yPos = _plotAreaBounds.bottom; + break; + } + } + return Offset(xPos, yPos); + } + + Offset _markerPosition( + RRect tooltipRRect, + double labelWidth, + double labelHeight, + double markerPadding, + bool isRtl, + ) { + final double padding = labelWidth / 2 + markerPadding; + return Offset( + (tooltipRRect.left + tooltipRRect.width / 2) + + (isRtl ? padding : -padding), + tooltipRRect.top + tooltipRRect.height / 2, + ); + } + + Rect _tooltipRect(double x, double y, double width, double height) { + if (tooltipSettings.canShowMarker) { + return Rect.fromLTWH( + x, + y, + width + (trackballTooltipMarkerSize + trackballTooltipPadding), + height + defaultTooltipWidth, + ); + } else { + return Rect.fromLTWH( + x, + y, + width + trackballTooltipMarkerSize, + height + defaultTooltipWidth, + ); + } + } + + Offset _alignPosition( + double xPosition, + double yPosition, + double rectWidth, + double rectHeight, + double arrowLength, + double arrowWidth, + double padding, + bool isRtl, [ + bool isGroupMode = false, + ]) { + double xPos = xPosition; + double yPos = yPosition; + if (yPosition > arrowLength + rectHeight) { + _isTop = true; + _isRight = false; + if (_isTransposed) { + final double totalWidth = _plotAreaBounds.left + _plotAreaBounds.width; + final double halfRectWidth = rectWidth / 2; + xPos = xPosition - halfRectWidth; + if (xPos < _plotAreaBounds.left) { + xPos = _plotAreaBounds.left; + } else if ((xPosition + halfRectWidth) > totalWidth) { + xPos = totalWidth - rectWidth; + } + + yPos = (yPosition - rectHeight) - padding; + yPos = yPos - arrowLength; + if (yPos + rectHeight >= _plotAreaBounds.bottom) { + yPos = _plotAreaBounds.bottom - rectHeight; + } + } else { + yPos = yPosition - rectHeight / 2; + if (!isRtl) { + if (xPos + rectWidth + padding + arrowLength > + _plotAreaBounds.right) { + xPos = + isGroupMode + ? xPos - rectWidth - groupAllPadding + : xPos - rectWidth - padding - arrowLength; + _isLeft = true; + } else { + xPos = + isGroupMode + ? xPosition + groupAllPadding + : xPosition + padding + arrowLength; + _isLeft = false; + _isRight = true; + } + } else { + xPos = + isGroupMode + ? xPos - rectWidth - groupAllPadding + : xPos - rectWidth - padding - arrowLength; + if (xPos < _plotAreaBounds.left) { + xPos = + isGroupMode + ? xPosition + groupAllPadding + : xPosition + padding + arrowLength; + _isRight = true; + } else { + _isLeft = true; + } + } + if (yPos + rectHeight >= _plotAreaBounds.bottom) { + yPos = _plotAreaBounds.bottom - rectHeight; + } + } + } else { + _isTop = false; + if (_isTransposed) { + final double totalWidth = _plotAreaBounds.left + _plotAreaBounds.width; + final double halfRectWidth = rectWidth / 2; + xPos = xPosition - halfRectWidth; + if (xPos < _plotAreaBounds.left) { + xPos = _plotAreaBounds.left; + } else if ((xPosition + halfRectWidth) > totalWidth) { + xPos = totalWidth - rectWidth; + } + + yPos = (yPosition + arrowLength) + padding; + } else { + if (isGroupMode) { + xPos = xPosition - rectWidth / 2; + yPos = yPosition - rectHeight / 2; + } else { + yPos = (yPosition + arrowLength / 2) + padding; + } + + if (!isRtl) { + if ((isGroupMode + ? (xPos + (rectWidth / 2) + groupAllPadding) + : xPos + rectWidth + padding + arrowLength) > + _plotAreaBounds.right) { + xPos = + isGroupMode + ? (xPos - (rectWidth / 2) - groupAllPadding) + : xPos - rectWidth - padding - arrowLength; + _isLeft = true; + } else { + xPos = + isGroupMode + ? xPosition + groupAllPadding + : xPosition + padding + arrowLength; + _isRight = true; + } + } else { + if (xPosition - rectWidth - padding - arrowLength > + _plotAreaBounds.left) { + xPos = + isGroupMode + ? (xPos - (rectWidth / 2) - groupAllPadding) + : xPos - rectWidth - padding - arrowLength; + _isLeft = true; + } else { + xPos = + isGroupMode + ? xPosition + groupAllPadding + : xPosition + padding + arrowLength; + _isRight = true; + } + } + + if (isGroupMode) { + if ((yPos + rectHeight) >= _plotAreaBounds.bottom) { + yPos = _plotAreaBounds.bottom / 2 - rectHeight / 2; + } + + if (yPos <= _plotAreaBounds.top) { + yPos = _plotAreaBounds.top; + } + } + } + } + + return Offset(xPos, yPos); + } + + RRect _validateRect( + Rect tooltipRRect, + Rect plotAreaBounds, + double borderRadius, + ) { + if (tooltipRRect == Rect.zero || + tooltipRRect.width >= plotAreaBounds.width || + tooltipRRect.height >= plotAreaBounds.height) { + return RRect.zero; + } + + double rectLeft = tooltipRRect.left; + double rectRight = tooltipRRect.right; + if (tooltipRRect.left < plotAreaBounds.left) { + final double left = plotAreaBounds.left - tooltipRRect.left; + rectLeft += left; + rectRight += left; + } else if (tooltipRRect.right > plotAreaBounds.right) { + final double right = tooltipRRect.right - plotAreaBounds.right; + rectLeft -= right; + rectRight -= right; + } + + RRect alignedRect = RRect.fromRectAndRadius( + Rect.fromLTRB(rectLeft, tooltipRRect.top, rectRight, tooltipRRect.bottom), + Radius.circular(borderRadius), + ); + + if (alignedRect.left < plotAreaBounds.left || + alignedRect.right > plotAreaBounds.right) { + alignedRect = RRect.zero; + } + return alignedRect; + } + + Path _nosePath( + String tooltipPosition, + RRect tooltipRect, + Offset position, + double arrowLength, + double arrowWidth, + ) { + final Path nosePath = Path(); + final double tooltipLeft = tooltipRect.left; + final double tooltipRight = tooltipRect.right; + final double tooltipTop = tooltipRect.top; + final double tooltipBottom = tooltipRect.bottom; + final double rectHalfWidth = tooltipRect.width / 2; + final double rectHalfHeight = tooltipRect.height / 2; + switch (tooltipPosition) { + case 'Left': + nosePath.moveTo(tooltipRight, tooltipTop + rectHalfHeight - arrowWidth); + nosePath.lineTo( + tooltipRight, + tooltipBottom - rectHalfHeight + arrowWidth, + ); + nosePath.lineTo(tooltipRight + arrowLength, position.dy); + nosePath.close(); + return nosePath; + + case 'Right': + nosePath.moveTo(tooltipLeft, tooltipTop + rectHalfHeight - arrowWidth); + nosePath.lineTo( + tooltipLeft, + tooltipBottom - rectHalfHeight + arrowWidth, + ); + nosePath.lineTo(tooltipLeft - arrowLength, position.dy); + nosePath.close(); + return nosePath; + + case 'Top': + nosePath.moveTo(position.dx, tooltipBottom + arrowLength); + nosePath.lineTo( + (tooltipRight - rectHalfWidth) + arrowWidth, + tooltipBottom, + ); + nosePath.lineTo( + (tooltipLeft + rectHalfWidth) - arrowWidth, + tooltipBottom, + ); + nosePath.close(); + return nosePath; + + case 'Bottom': + nosePath.moveTo(position.dx, tooltipTop - arrowLength); + nosePath.lineTo( + (tooltipRight - rectHalfWidth) + arrowWidth, + tooltipTop, + ); + nosePath.lineTo((tooltipLeft + rectHalfWidth) - arrowWidth, tooltipTop); + nosePath.close(); + return nosePath; + } + return nosePath; + } + + String _tooltipDirection() { + if (_isRight) { + return 'Right'; + } else if (_isLeft) { + return 'Left'; + } else if (_isTop) { + return 'Top'; + } else { + return 'Bottom'; + } + } + + TextStyle _createLabelStyle(FontWeight fontWeight, TextStyle labelStyle) { + return TextStyle( + fontWeight: fontWeight, + color: labelStyle.color, + fontSize: labelStyle.fontSize, + fontFamily: labelStyle.fontFamily, + fontStyle: labelStyle.fontStyle, + inherit: labelStyle.inherit, + backgroundColor: labelStyle.backgroundColor, + letterSpacing: labelStyle.letterSpacing, + wordSpacing: labelStyle.wordSpacing, + textBaseline: labelStyle.textBaseline, + height: labelStyle.height, + locale: labelStyle.locale, + foreground: labelStyle.foreground, + background: labelStyle.background, + shadows: labelStyle.shadows, + fontFeatures: labelStyle.fontFeatures, + decoration: labelStyle.decoration, + decorationColor: labelStyle.decorationColor, + decorationStyle: labelStyle.decorationStyle, + decorationThickness: labelStyle.decorationThickness, + debugLabel: labelStyle.debugLabel, + fontFamilyFallback: labelStyle.fontFamilyFallback, + ); + } + + void _computeTooltipMarkers(ChartPointInfo pointInfo, Offset markerPosition) { + final Color color = pointInfo.color!; + final ChartMarker marker = + ChartMarker() + ..x = markerPosition.dx + ..y = markerPosition.dy + ..index = pointInfo.dataPointIndex! + ..height = tooltipMarkerSize + ..width = tooltipMarkerSize + ..borderColor = color + ..borderWidth = 1 + ..color = color; + if (markerSettings != null) { + marker.merge( + borderColor: markerSettings!.borderColor ?? color, + color: markerSettings!.color ?? color, + image: markerSettings!.image, + type: markerSettings!.shape, + ); + } + marker.position = Offset( + marker.x - marker.width / 2, + marker.y - marker.height / 2, + ); + marker.shader = _markerShader( + pointInfo, + marker.position & Size(marker.height, marker.width), + ); + _tooltipMarkers.add(marker); + } + + Shader? _markerShader(ChartPointInfo pointInfo, Rect bounds) { + final dynamic series = pointInfo.series; + if (series is CartesianSeriesRenderer) { + if (series.onCreateShader != null) { + final ShaderDetails details = ShaderDetails(bounds, 'marker'); + return series.onCreateShader!(details); + } else if (series.gradient != null) { + return series.gradient!.createShader(bounds); + } + } + return null; + } + + void _computeLineMarkers( + ThemeData themeData, + List source, + bool markerIsVisible, + bool markerAutoVisibility, + ) { + final Color themeFillColor = themeData.colorScheme.surface; + for (final ChartPointInfo pointInfo in chartPointInfo) { + final dynamic series = pointInfo.series; + final bool isVisible = + markerAutoVisibility + ? series is IndicatorRenderer || + (series != null && series.markerSettings.isVisible) + : markerIsVisible; + if (isVisible) { + final Color color = pointInfo.color!; + final ChartMarker marker = + ChartMarker() + ..x = pointInfo.markerXPos! + ..y = pointInfo.markerYPos! + ..index = pointInfo.dataPointIndex! + ..borderColor = color + ..color = themeFillColor; + if (markerSettings != null) { + marker.merge( + borderColor: markerSettings!.borderColor ?? color, + borderWidth: markerSettings!.borderWidth, + color: markerSettings!.color ?? themeFillColor, + height: markerSettings!.height, + width: markerSettings!.width, + image: markerSettings!.image, + type: markerSettings!.shape, + ); + } + marker.position = Offset( + marker.x - marker.width / 2, + marker.y - marker.height / 2, + ); + source.add(marker); + } + } + } + + void _computeTooltipLabels( + String text, + double width, + double height, + TextStyle textStyle, + RRect tooltipRRect, + bool isRtl, + ) { + final bool canShowMarker = tooltipSettings.canShowMarker; + final double markerSize = canShowMarker ? trackballTooltipMarkerSize : 0; + final double padding = canShowMarker ? 0 : defaultTrackballPadding; + final double markerSizeWithPadding = defaultTrackballPadding + markerSize; + final double x = + isRtl + ? tooltipRRect.right - markerSizeWithPadding - padding + : tooltipRRect.left + markerSizeWithPadding + padding; + final double y = tooltipRRect.top + defaultTrackballPadding; + if (tooltipSettings.format != null) { + _computeFormatTooltipLabels(x, y, text, textStyle, isRtl); + } else { + // It represents for SBS type series. + if (text.contains('\n') || text.contains(':')) { + _computeDefaultTooltipLabels(x, y, text, textStyle, isRtl); + } else { + // It represents for xYDataSeries. + final TextStyle boldStyle = _createLabelStyle( + FontWeight.bold, + textStyle, + ); + final Offset offset = Offset( + isRtl ? x - measureText(text, boldStyle).width : x, + y, + ); + _tooltipLabels.add(_TooltipLabels(text, boldStyle, offset)); + } + } + } + + void _computeDefaultTooltipLabels( + double x, + double y, + String text, + TextStyle textStyle, + bool isRtl, + ) { + final TextStyle boldStyle = _createLabelStyle(FontWeight.bold, textStyle); + double eachTextHeight = 0; + final List labels = text.split('\n'); + final int labelsLength = labels.length; + for (int i = 0; i < labelsLength; i++) { + final String label = labels[i]; + double dx = x; + final double dy = y + eachTextHeight; + if (label.contains(':')) { + final List parts = label.split(':'); + if (parts.length == 2) { + final String leftText = '${parts[0]}:'; + final Size leftSize = measureText(leftText, textStyle); + final String rightText = isRtl ? ' ${parts[1]}' : parts[1]; + final Size rightSize = measureText(rightText, textStyle); + eachTextHeight += isRtl ? rightSize.height : leftSize.height; + if (isRtl) { + dx -= rightSize.width; + _tooltipLabels.add( + _TooltipLabels(rightText, textStyle, Offset(dx, dy)), + ); + dx -= leftSize.width; + _tooltipLabels.add( + _TooltipLabels(leftText, boldStyle, Offset(dx, dy)), + ); + } else { + _tooltipLabels.add( + _TooltipLabels(leftText, textStyle, Offset(dx, dy)), + ); + dx += leftSize.width; + _tooltipLabels.add( + _TooltipLabels(rightText, boldStyle, Offset(dx, dy)), + ); + } + } + } else { + final Size labelSize = measureText(label, boldStyle); + if (isRtl) { + dx -= labelSize.width; + } + _tooltipLabels.add(_TooltipLabels(label, boldStyle, Offset(dx, dy))); + eachTextHeight += labelSize.height; + } + } + } + + void _computeFormatTooltipLabels( + double x, + double y, + String text, + TextStyle textStyle, + bool isRtl, + ) { + if (text.contains('\n')) { + _multiLineLabelFormat(x, y, text, textStyle, isRtl); + } else { + // If the text contains a single colon and is not already formatted with + // bold tags, apply the default labelStyle. Otherwise, use the single line + // label format. + if (text.split(':').length == 2 && + (!(text.contains('') && text.contains('')))) { + _computeDefaultTooltipLabels(x, y, text, textStyle, isRtl); + } else { + _singleLineLabelFormat(x, y, text, textStyle, isRtl); + } + } + } + + void _multiLineLabelFormat( + double x, + double y, + String label, + TextStyle textStyle, + bool isRtl, + ) { + double dy = y; + final List multiLines = label.split('\n'); + for (final String text in multiLines) { + // If the text contains a single colon and is not already formatted with + // bold tags, apply the default labelStyle. Otherwise, use the single line + // label format. + if (text.split(':').length == 2 && + (!(text.contains('') && text.contains('')))) { + _computeDefaultTooltipLabels(x, dy, text, textStyle, isRtl); + } else { + _singleLineLabelFormat(x, dy, text, textStyle, isRtl); + } + final Size textSize = measureText(text, textStyle); + dy += textSize.height; + } + } + + void _singleLineLabelFormat( + double x, + double y, + String label, + TextStyle textStyle, + bool isRtl, + ) { + final TextStyle boldStyle = _createLabelStyle(FontWeight.bold, textStyle); + double dx = x; + if (label.contains('') && label.contains('')) { + if (isRtl) { + final List boldParts = label.split(''); + final int length = boldParts.length; + final int boldPartsStart = length - 1; + for (int i = boldPartsStart; i >= 0; --i) { + final String text = boldParts[i]; + if (text == '') { + continue; + } + + if (text.contains('') || text.contains('')) { + final List parts = text.split(''); + final int length = parts.length; + final int partsStart = length - 1; + for (int j = partsStart; j >= 0; --j) { + final String part = parts[j]; + if (part == '') { + continue; + } + + final TextStyle currentStyle = + j == partsStart ? boldStyle : textStyle; + final Size textSize = measureText(part, currentStyle); + dx -= textSize.width; + _tooltipLabels.add( + _TooltipLabels(part, currentStyle, Offset(dx, y)), + ); + } + } else { + dx -= measureText(text, textStyle).width; + _tooltipLabels.add(_TooltipLabels(text, textStyle, Offset(dx, y))); + } + } + } else { + final List boldParts = label.split(''); + final int length = boldParts.length; + for (int i = 0; i < length; i++) { + final String text = boldParts[i]; + if (text == '') { + continue; + } + + if (text.contains('') || text.contains('')) { + final List parts = text.split(''); + final int length = parts.length; + for (int j = 0; j < length; j++) { + final String part = parts[j]; + if (part == '') { + continue; + } + + final TextStyle currentStyle = j == 0 ? boldStyle : textStyle; + final Size textSize = measureText(part, currentStyle); + _tooltipLabels.add( + _TooltipLabels(part, currentStyle, Offset(dx, y)), + ); + dx += textSize.width; + } + } else { + _tooltipLabels.add(_TooltipLabels(text, textStyle, Offset(dx, y))); + dx += measureText(text, textStyle).width; + } + } + } + } else { + final TextStyle style = !label.contains(':') ? boldStyle : textStyle; + final Size labelSize = measureText(label, style); + dx = isRtl ? x - labelSize.width : x; + _tooltipLabels.add(_TooltipLabels(label, style, Offset(dx, y))); + } + } + + /// Override this method to customize the trackball tooltip labels + /// and it's positions and line rendering. + @override + void onPaint( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) { + _drawTrackballLine(context, offset, chartThemeData, themeData); + // Draw line marker. + _drawMarkers(context, chartThemeData, _lineMarkers); + _drawLabel(context, offset, chartThemeData, themeData); + } + + void _drawTrackballLine( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) { + if (chartPointInfo.isNotEmpty && lineType != TrackballLineType.none) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = (lineColor ?? chartThemeData.crosshairLineColor)! + ..strokeWidth = lineWidth + ..style = PaintingStyle.stroke; + + _drawLine( + context, + offset, + chartThemeData, + themeData, + lineDashArray, + paint, + ); + } + } + + void _drawLine( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + List? dashArray, + Paint paint, + ) { + if (parentBox == null) { + return; + } + + final Rect plotAreaBounds = parentBox!.paintBounds; + final Path path = Path(); + if (_isTransposed) { + final double y = chartPointInfo[0].yPosition!; + path + ..moveTo(plotAreaBounds.left, y) + ..lineTo(plotAreaBounds.right, y); + } else { + final double x = chartPointInfo[0].xPosition!; + path + ..moveTo(x, plotAreaBounds.top) + ..lineTo(x, plotAreaBounds.bottom); + } + drawDashes(context.canvas, dashArray, paint, path: path); + } + + void _drawLabel( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + final bool isRtl = parent.textDirection == TextDirection.rtl; + if (tooltipDisplayMode != TrackballDisplayMode.none) { + // Draw tooltip rectangle. + if (_tooltipPaths.isNotEmpty) { + final Color themeBackgroundColor = + chartThemeData.crosshairBackgroundColor!; + final Paint fillPaint = + Paint() + ..color = tooltipSettings.color ?? themeBackgroundColor + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..color = tooltipSettings.borderColor ?? themeBackgroundColor + ..strokeWidth = tooltipSettings.borderWidth + ..strokeCap = StrokeCap.butt + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + final int length = _tooltipPaths.length; + for (int i = 0; i < length; i++) { + final Path path = _tooltipPaths[i]; + context.canvas.drawPath(path, strokePaint); + context.canvas.drawPath(path, fillPaint); + } + } + + // Draw tooltip marker. + _drawMarkers(context, chartThemeData, _tooltipMarkers); + + // Draw divider. + if (tooltipDisplayMode == TrackballDisplayMode.groupAllPoints && + _dividerStartOffset != null && + _dividerEndOffset != null) { + context.canvas.drawLine( + _dividerStartOffset!, + _dividerEndOffset!, + Paint() + ..color = chartThemeData.tooltipSeparatorColor! + ..strokeWidth = 1 + ..style = PaintingStyle.stroke + ..isAntiAlias = true, + ); + } + + // Draw tooltip labels. + if (_tooltipLabels.isNotEmpty) { + final int length = _tooltipLabels.length; + for (int i = 0; i < length; i++) { + final _TooltipLabels label = _tooltipLabels[i]; + _drawText( + context.canvas, + label.text, + label.position, + label.style, + isRtl, + ); + } + } + } + } + + void _drawMarkers( + PaintingContext context, + SfChartThemeData chartThemeData, + List markers, + ) { + if (markers.isNotEmpty) { + final Paint fillPaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + for (final ChartMarker marker in markers) { + fillPaint + ..color = marker.color! + ..shader = marker.shader; + strokePaint + ..color = marker.borderColor! + ..strokeWidth = marker.borderWidth; + _drawMarker( + context.canvas, + marker.position, + Size(marker.width, marker.height), + marker.type, + fillPaint, + strokePaint, + ); + } + } + } + + void _drawMarker( + Canvas canvas, + Offset position, + Size size, + DataMarkerType type, + Paint fillPaint, + Paint strokePaint, + ) { + if (position.isNaN) { + return; + } + + if (type == DataMarkerType.image) { + if (_trackballImage != null) { + paintImage( + canvas: canvas, + rect: position & size, + image: _trackballImage!, + ); + } + } else if (type != DataMarkerType.none) { + paint( + canvas: canvas, + rect: position & size, + shapeType: toShapeMarkerType(type), + paint: fillPaint, + borderPaint: strokePaint, + ); + } + } + + void _drawText( + Canvas canvas, + String text, + Offset position, + TextStyle style, + bool isRtl, + ) { + final TextPainter textPainter = TextPainter( + text: TextSpan(text: text, style: style), + textAlign: isRtl ? TextAlign.right : TextAlign.left, + maxLines: getMaxLinesContent(text), + textDirection: TextDirection.ltr, + ); + textPainter + ..layout() + ..paint(canvas, position); + } +} + +class TrackballBuilderOpacityWidget extends Opacity { + const TrackballBuilderOpacityWidget({ + super.key, + super.child, + required super.opacity, + }); + + @override + RenderOpacity createRenderObject(BuildContext context) { + return TrackballOpacityRenderBox( + opacity: opacity, + alwaysIncludeSemantics: alwaysIncludeSemantics, + ); + } +} + +class TrackballOpacityRenderBox extends RenderOpacity { + TrackballOpacityRenderBox({ + super.opacity = 1.0, + super.alwaysIncludeSemantics = false, + super.child, + }); +} + +class TrackballBuilderRenderObjectWidget extends SingleChildRenderObjectWidget { + const TrackballBuilderRenderObjectWidget({ + Key? key, + this.index, + required this.xPos, + required this.yPos, + required this.builder, + required this.chartPointInfo, + required this.trackballBehavior, + required Widget child, + }) : super(key: key, child: child); + + final int? index; + final double xPos; + final double yPos; + final Widget builder; + final List? chartPointInfo; + final TrackballBehavior trackballBehavior; + + @override + RenderObject createRenderObject(BuildContext context) { + return TrackballBuilderRenderBox( + index, + xPos, + yPos, + builder, + chartPointInfo, + trackballBehavior, + ); + } + + @override + void updateRenderObject( + BuildContext context, + covariant TrackballBuilderRenderBox renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..index = index + ..xPos = xPos + ..yPos = yPos + ..builder = builder + ..chartPointInfo = chartPointInfo + ..trackballBehavior = trackballBehavior; + } +} + +/// Render the annotation widget in the respective position. +class TrackballBuilderRenderBox extends RenderShiftedBox { + /// Creates an instance of trackball template render box. + TrackballBuilderRenderBox( + this.index, + this.xPos, + this.yPos, + this._builder, + this.chartPointInfo, + this.trackballBehavior, [ + RenderBox? child, + ]) : super(child); + + /// Holds the value of x and y position. + double xPos, yPos; + + /// Specifies the list of chart point info. + List? chartPointInfo; + + /// Holds the value of index. + int? index; + + /// Holds the value of pointer length and pointer width respectively. + late double pointerLength, pointerWidth; + + /// Holds the value of trackball template rect. + Rect? trackballTemplateRect; + + /// Holds the value of boundary rect. + late Rect plotAreaBounds; + + /// Specifies the value of padding. + num padding = 10; + + /// Specifies the value of trackball behavior. + TrackballBehavior trackballBehavior; + + /// Specifies whether to group all the points. + bool isGroupAllPoints = false; + + /// Specifies whether it is the nearest point. + bool isNearestPoint = false; + + /// Specifies whether tooltip is present at right. + bool isRight = false; + + /// Specifies whether tooltip is present at bottom. + bool isBottom = false; + + /// Specifies whether the template is present inside the bounds. + bool isTemplateInBounds = true; + // Offset arrowOffset; + + /// Holds the tooltip position. + late _TooltipPositions? _tooltipPosition; + + /// Holds the value of box parent data. + late BoxParentData childParentData; + + /// Gets and sets the builder widget. + Widget get builder => _builder; + Widget _builder; + set builder(Widget value) { + if (_builder != value) { + _builder = value; + markNeedsLayout(); + } + } + + bool isTransposed = false; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + if (child != null && child!.parentData != null) { + final BoxParentData childParentData = child!.parentData! as BoxParentData; + return result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + } + return false; + } + + @override + void performLayout() { + size = constraints.biggest; + isTransposed = + chartPointInfo != null && + chartPointInfo!.isNotEmpty && + chartPointInfo![0].series!.isTransposed; + final TrackballDisplayMode tooltipDisplayMode = + trackballBehavior.tooltipDisplayMode; + isGroupAllPoints = + tooltipDisplayMode == TrackballDisplayMode.groupAllPoints; + isNearestPoint = tooltipDisplayMode == TrackballDisplayMode.nearestPoint; + + final List tooltipTop = []; + final List tooltipBottom = []; + final List xAxesInfo = []; + final List yAxesInfo = []; + final TrackballMarkerSettings? markerSettings = + trackballBehavior.markerSettings; + final bool isTrackballMarkerEnabled = markerSettings != null; + + final List visiblePoints = trackballBehavior._visiblePoints; + pointerLength = trackballBehavior.tooltipSettings.arrowLength; + pointerWidth = trackballBehavior.tooltipSettings.arrowWidth; + plotAreaBounds = trackballBehavior._plotAreaBounds; + final double boundaryLeft = plotAreaBounds.left; + final double boundaryRight = plotAreaBounds.right; + final num totalWidth = boundaryLeft + plotAreaBounds.width; + double left; + double top; + if (child != null) { + child!.layout(constraints, parentUsesSize: true); + if (child!.parentData is BoxParentData) { + childParentData = child!.parentData! as BoxParentData; + final double markerHalfWidth = + isTrackballMarkerEnabled ? markerSettings.width / 2 : 0; + final double markerHalfHeight = + isTrackballMarkerEnabled ? markerSettings.height / 2 : 0; + final double templateFullWidth = child!.size.width; + final double templateFullHeight = child!.size.height; + final double templateHalfWidth = templateFullWidth / 2; + final double templateHalfHeight = templateFullHeight / 2; + + if (isGroupAllPoints) { + final ChartAlignment tooltipAlignment = + trackballBehavior.tooltipAlignment; + final Offset tooltipPosition = trackballBehavior + ._defaultGroupPosition(xPos, yPos); + xPos = tooltipPosition.dx; + yPos = tooltipPosition.dy; + if (isTransposed) { + if (tooltipAlignment == ChartAlignment.far) { + xPos = tooltipPosition.dx - templateFullWidth; + } else if (tooltipAlignment == ChartAlignment.center) { + xPos = tooltipPosition.dx - templateHalfWidth; + } + } else { + if (tooltipAlignment == ChartAlignment.far) { + yPos = tooltipPosition.dy - templateFullHeight; + } else if (tooltipAlignment == ChartAlignment.center) { + yPos = tooltipPosition.dy - templateHalfHeight; + } + } + } + + if (chartPointInfo != null && + chartPointInfo!.isNotEmpty && + !isGroupAllPoints) { + final int length = chartPointInfo!.length; + for (int i = 0; i < length; i++) { + final dynamic series = chartPointInfo![i].series; + final Offset visiblePoint = visiblePoints[i]; + final double closestPointX = visiblePoint.dx; + final double closestPointY = visiblePoint.dy; + tooltipTop.add( + isTransposed + ? closestPointX - templateHalfWidth + : closestPointY - templateHalfHeight, + ); + tooltipBottom.add( + isTransposed + ? closestPointX + templateHalfWidth + : closestPointY + templateHalfHeight, + ); + xAxesInfo.add(series.xAxis!); + yAxesInfo.add(series.yAxis!); + } + + if (tooltipTop.isNotEmpty) { + _tooltipPosition = trackballBehavior._smartTooltipPositions( + tooltipTop, + tooltipBottom, + xAxesInfo, + yAxesInfo, + chartPointInfo!, + isTransposed ? 8 : 5, + ); + } + + if (isNearestPoint) { + left = + isTransposed + ? xPos + templateHalfWidth + : xPos + padding + markerHalfWidth; + top = + isTransposed + ? yPos + padding + markerHalfWidth + : yPos - templateHalfHeight; + } else { + left = + (isTransposed + ? _tooltipPosition!.tooltipTop[index!] + : xPos + padding + markerHalfWidth) + .toDouble(); + top = + (isTransposed + ? yPos + pointerLength + markerHalfWidth + : _tooltipPosition!.tooltipTop[index!]) + .toDouble(); + } + + if (!isTransposed) { + if (left + templateFullWidth > totalWidth) { + isRight = true; + left = xPos - templateFullWidth - pointerLength - markerHalfWidth; + } else { + isRight = false; + } + } else { + if (top + templateFullHeight > plotAreaBounds.bottom) { + isBottom = true; + top = yPos - templateFullHeight - pointerLength - markerHalfWidth; + } else { + isBottom = false; + } + } + + trackballTemplateRect = Rect.fromLTWH( + left, + top, + templateFullWidth, + templateFullHeight, + ); + double xPlotOffset = + visiblePoints.first.dx - trackballTemplateRect!.width / 2; + final double rightTemplateEnd = + xPlotOffset + trackballTemplateRect!.width; + final double leftTemplateEnd = xPlotOffset; + + if (_isTemplateWithinBounds(plotAreaBounds, trackballTemplateRect!)) { + isTemplateInBounds = true; + childParentData.offset = Offset(left, top); + } else if (plotAreaBounds.width > trackballTemplateRect!.width && + plotAreaBounds.height > trackballTemplateRect!.height) { + isTemplateInBounds = true; + if (rightTemplateEnd > boundaryRight) { + xPlotOffset = xPlotOffset - (rightTemplateEnd - boundaryRight); + if (xPlotOffset < boundaryLeft) { + xPlotOffset = xPlotOffset + (boundaryLeft - xPlotOffset); + if (xPlotOffset + trackballTemplateRect!.width > + boundaryRight) { + xPlotOffset = + xPlotOffset - + (totalWidth + + trackballTemplateRect!.width - + boundaryRight); + } + if (xPlotOffset < boundaryLeft || xPlotOffset > boundaryRight) { + isTemplateInBounds = false; + } + } + } else if (leftTemplateEnd < boundaryLeft) { + xPlotOffset = xPlotOffset + (boundaryLeft - leftTemplateEnd); + if (xPlotOffset + trackballTemplateRect!.width > boundaryRight) { + xPlotOffset = + xPlotOffset - + (totalWidth + trackballTemplateRect!.width - boundaryRight); + if (xPlotOffset < boundaryLeft) { + xPlotOffset = xPlotOffset + (boundaryLeft - xPlotOffset); + } + if (xPlotOffset < boundaryLeft || + xPlotOffset + trackballTemplateRect!.width > + boundaryRight) { + isTemplateInBounds = false; + } + } + } + + /// Adjusts the yPos of the trackball tooltip to ensure it stays + /// within the plot area's boundaries. + final double templateHeightWithPadding = + trackballTemplateRect!.height + padding; + final double templateRectTop = yPos - templateHeightWithPadding; + final double templateRectBottom = yPos + templateHeightWithPadding; + double yPlotOffset = yPos; + if (templateRectTop >= plotAreaBounds.top) { + yPlotOffset = yPlotOffset - templateHeightWithPadding; + } else if (templateRectBottom <= plotAreaBounds.bottom) { + yPlotOffset = yPlotOffset + padding; + } + + if (templateRectTop < plotAreaBounds.top && + templateRectBottom > plotAreaBounds.bottom) { + isTemplateInBounds = false; + } + childParentData.offset = Offset(xPlotOffset, yPlotOffset); + } else { + child!.layout( + constraints.copyWith(maxWidth: 0), + parentUsesSize: true, + ); + isTemplateInBounds = false; + } + } else { + if (visiblePoints.isNotEmpty) { + // Adjusts the yPos and xPos of the trackball tooltip to ensure + // it stays within the plot area bounds. + if (isTransposed) { + final double templateYPosition = + yPos - templateFullHeight - padding - markerHalfHeight; + // Move the template inside the plot area bounds, when + // template top lesser than plot area bounds top. + if (templateYPosition < plotAreaBounds.top) { + yPos = yPos + padding + markerHalfHeight; + } else { + yPos = templateYPosition; + } + } else { + final double templateXPosition = xPos + padding + markerHalfWidth; + // Move the template inside the plot area bounds, when + // template right greater than plot area bounds right. + if (templateXPosition + templateFullWidth > + plotAreaBounds.right) { + xPos = xPos - templateFullWidth - padding - markerHalfWidth; + } else { + xPos = templateXPosition; + } + } + + trackballTemplateRect = Rect.fromLTWH( + xPos, + yPos, + templateFullWidth, + templateFullHeight, + ); + double xPlotOffset = + visiblePoints.first.dx - trackballTemplateRect!.width / 2; + final double rightTemplateEnd = + xPlotOffset + trackballTemplateRect!.width; + final double leftTemplateEnd = xPlotOffset; + + if (_isTemplateWithinBounds( + plotAreaBounds, + trackballTemplateRect!, + )) { + isTemplateInBounds = true; + childParentData.offset = Offset(xPos, yPos); + } else if (plotAreaBounds.width > trackballTemplateRect!.width && + plotAreaBounds.height > trackballTemplateRect!.height) { + isTemplateInBounds = true; + if (rightTemplateEnd > boundaryRight) { + xPlotOffset = xPlotOffset - (rightTemplateEnd - boundaryRight); + if (xPlotOffset < boundaryLeft) { + xPlotOffset = xPlotOffset + (boundaryLeft - xPlotOffset); + if (xPlotOffset + trackballTemplateRect!.width > + boundaryRight) { + xPlotOffset = + xPlotOffset - + (totalWidth + + trackballTemplateRect!.width - + boundaryRight); + } + if (xPlotOffset < boundaryLeft || + xPlotOffset > boundaryRight) { + isTemplateInBounds = false; + } + } + } else if (leftTemplateEnd < boundaryLeft) { + xPlotOffset = xPlotOffset + (boundaryLeft - leftTemplateEnd); + if (xPlotOffset + trackballTemplateRect!.width > + boundaryRight) { + xPlotOffset = + xPlotOffset - + (xPlotOffset + + trackballTemplateRect!.width - + boundaryRight); + if (xPlotOffset < boundaryLeft) { + xPlotOffset = xPlotOffset + (boundaryLeft - xPlotOffset); + } + if (xPlotOffset < boundaryLeft || + xPlotOffset > boundaryRight) { + isTemplateInBounds = false; + } + } + } + + childParentData.offset = Offset(xPlotOffset, yPos); + } else { + child!.layout( + constraints.copyWith(maxWidth: 0), + parentUsesSize: true, + ); + isTemplateInBounds = false; + } + } + } + } + } + if (!isGroupAllPoints && index == chartPointInfo!.length - 1) { + tooltipTop.clear(); + tooltipBottom.clear(); + yAxesInfo.clear(); + xAxesInfo.clear(); + } + } + + /// To check template is within bounds. + bool _isTemplateWithinBounds(Rect plotAreaBounds, Rect templateRect) { + final double triplePadding = (3 * padding).toDouble(); + final Rect rect = Rect.fromLTWH( + padding + templateRect.left, + triplePadding + templateRect.top, + templateRect.width, + templateRect.height, + ); + final Rect axisBounds = Rect.fromLTWH( + padding + plotAreaBounds.left, + triplePadding + plotAreaBounds.top, + plotAreaBounds.width, + plotAreaBounds.height, + ); + return rect.left >= axisBounds.left && + rect.left + rect.width <= axisBounds.left + axisBounds.width && + rect.top >= axisBounds.top && + rect.bottom <= axisBounds.top + axisBounds.height; + } + + void _calculateMarkerPositions( + PaintingContext context, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) { + final TrackballMarkerSettings? markerSettings = + trackballBehavior.markerSettings; + if ((chartPointInfo != null && chartPointInfo!.isEmpty) || + markerSettings == null || + markerSettings.markerVisibility == TrackballVisibilityMode.hidden) { + return; + } + + final List markers = []; + final TrackballVisibilityMode markerVisibility = + markerSettings.markerVisibility; + trackballBehavior._computeLineMarkers( + themeData, + markers, + markerVisibility == TrackballVisibilityMode.visible, + markerVisibility == TrackballVisibilityMode.auto, + ); + trackballBehavior._drawMarkers(context, chartThemeData, markers); + } + + @override + void paint(PaintingContext context, Offset offset) { + final SfChartThemeData chartThemeData = trackballBehavior._chartThemeData!; + final ThemeData themeData = trackballBehavior._themeData!; + _calculateMarkerPositions(context, chartThemeData, themeData); + + final bool isTemplateWithInBoundsInTransposedChart = + _isTemplateWithinBounds(plotAreaBounds, trackballTemplateRect!); + if ((!isTransposed && isTemplateInBounds) || + (isTransposed && isTemplateWithInBoundsInTransposedChart)) { + super.paint(context, offset); + } + + if (!isGroupAllPoints) { + final Color chartThemeBackgroundColor = + chartThemeData.crosshairBackgroundColor!; + final ChartPointInfo pointInfo = chartPointInfo![index!]; + final Color color = + pointInfo.series is IndicatorRenderer + ? pointInfo.color + : (pointInfo.series!.color) ?? chartThemeBackgroundColor; + final InteractiveTooltip tooltipSettings = + trackballBehavior.tooltipSettings; + final Paint fillPaint = + Paint() + ..color = tooltipSettings.color ?? color + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..color = tooltipSettings.borderColor ?? color + ..strokeWidth = tooltipSettings.borderWidth + ..strokeCap = StrokeCap.butt + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + if (trackballTemplateRect!.left > plotAreaBounds.left && + trackballTemplateRect!.right < plotAreaBounds.right) { + final RRect templateRRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + offset.dx + trackballTemplateRect!.left, + offset.dy + trackballTemplateRect!.top, + trackballTemplateRect!.width, + trackballTemplateRect!.height, + ), + Radius.zero, + ); + + String nosePosition = ''; + if (!isTransposed) { + if (!isRight) { + nosePosition = 'Right'; + } else { + nosePosition = 'Left'; + } + } else if (isTemplateInBounds && + isTemplateWithInBoundsInTransposedChart) { + if (!isBottom) { + nosePosition = 'Bottom'; + } else { + nosePosition = 'Top'; + } + } + + final Path nosePath = trackballBehavior._nosePath( + nosePosition, + templateRRect, + Offset(xPos, yPos), + pointerLength, + pointerWidth, + ); + + if (isTemplateInBounds) { + context.canvas.drawPath(nosePath, fillPaint); + context.canvas.drawPath(nosePath, strokePaint); + } + } + } + } +} + +/// 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 in [TrackballMarkerSettings]. +/// Provides the options like color, border width, border color and shape of the +/// marker to customize the appearance. +class TrackballMarkerSettings extends MarkerSettings { + /// Creating an argument constructor of TrackballMarkerSettings class. + const TrackballMarkerSettings({ + this.markerVisibility = TrackballVisibilityMode.auto, + super.height, + super.width, + super.color, + super.shape, + super.borderWidth, + super.borderColor, + super.image, + }); + + /// Whether marker should be visible or not when trackball is enabled. + /// + /// The below values are applicable for this: + /// * TrackballVisibilityMode.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. + /// * TrackballVisibilityMode.visible - Makes the trackball marker visible + /// for all the series, + /// irrespective of considering the [isVisible] property's value in the + /// `markerSettings`. + /// * TrackballVisibilityMode.hidden - Hides the trackball marker for all + /// the series. + /// + /// Defaults to `TrackballVisibilityMode.auto`. + /// + /// Also refer [TrackballVisibilityMode]. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// markerSettings: TrackballMarkerSettings( + /// markerVisibility: TrackballVisibilityMode.visible, + /// width: 10 + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + ///``` + final TrackballVisibilityMode markerVisibility; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is TrackballMarkerSettings && + other.markerVisibility == markerVisibility && + other.height == height && + other.width == width && + other.color == color && + other.shape == shape && + other.borderWidth == borderWidth && + other.borderColor == borderColor && + other.image == image; + } + + @override + int get hashCode { + final List values = [ + markerVisibility, + height, + width, + color, + shape, + borderWidth, + borderColor, + image, + ]; + return Object.hashAll(values); + } +} + +class TrackballInfo { + TrackballInfo({required this.position, this.name, this.color}); + + /// Local position of the tooltip. + final Offset? position; + + /// Specifies the series name. + final String? name; + + /// Specifies the series color. + final Color? color; +} + +class ChartTrackballInfo extends TrackballInfo { + ChartTrackballInfo({ + required super.position, + required this.point, + required this.series, + required this.seriesIndex, + required this.segmentIndex, + required this.pointIndex, + this.header, + this.text, + this.lowYPos, + this.highXPos, + this.highYPos, + this.openXPos, + this.openYPos, + this.closeXPos, + this.closeYPos, + this.minYPos, + this.maxXPos, + this.maxYPos, + this.lowerXPos, + this.lowerYPos, + this.upperXPos, + this.upperYPos, + super.name, + super.color, + }); + + final CartesianChartPoint point; + final dynamic series; + final int seriesIndex; + final int segmentIndex; + final int pointIndex; + final String? header; + final String? text; + + 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; +} + +/// Class to store trackball tooltip start and end positions. +class _TooltipPositions { + /// Creates the parameterized constructor for the class TooltipPositions. + const _TooltipPositions(this.tooltipTop, this.tooltipBottom); + + /// Specifies the tooltip top value. + final List tooltipTop; + + /// Specifies the tooltip bottom value. + final List tooltipBottom; +} + +/// Class to store trackball tooltip label, label style and positions. +class _TooltipLabels { + /// Creates the parameterized constructor for the class _TooltipLabels. + _TooltipLabels(this.text, this.style, this.position); + + /// Specifies the tooltip label value. + final String text; + + /// Specifies the tooltip label style value. + final TextStyle style; + + /// Specifies the tooltip label position value. + final Offset position; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/zooming.dart b/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/zooming.dart new file mode 100644 index 000000000..9221d906e --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/behaviors/zooming.dart @@ -0,0 +1,1326 @@ +import 'dart: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/theme.dart'; + +import '../axis/axis.dart'; + +import '../base.dart'; +import '../common/callbacks.dart'; +import '../interactions/behavior.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import '../utils/zooming_helper.dart'; + +/// Customizes the zooming options. +/// +/// Customize the various zooming actions such as tap zooming, selection +/// zooming, zoom pinch. In selection zooming, you can long-press and drag to +/// select a range on the chart to be zoomed in and also you can customize the +/// selection zooming rectangle using `selectionRectBorderWidth`, +/// `selectionRectBorderColor` and `selectionRectColor` properties. +/// +/// Pinch zooming can be performed by moving two fingers over the chart. +/// Default mode is [ZoomMode.xy]. Zooming will be stopped after reaching +/// `maximumZoomLevel`. +/// +/// _Note:_ This is only applicable for `SfCartesianChart`. + +class ZoomPanBehavior extends ChartBehavior { + /// Creating an argument constructor of ZoomPanBehavior class. + ZoomPanBehavior({ + this.enablePinching = false, + this.enableDoubleTapZooming = false, + this.enablePanning = false, + this.enableSelectionZooming = false, + this.enableMouseWheelZooming = false, + this.enableDirectionalZooming = false, + this.zoomMode = ZoomMode.xy, + this.maximumZoomLevel = 0.01, + this.selectionRectBorderWidth = 1, + this.selectionRectBorderColor, + this.selectionRectColor, + }); + + /// Enables or disables the pinch zooming. + /// + /// Pinching can be performed by moving two fingers over the chart. + /// You can zoom the chart through pinch gesture in touch enabled devices. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enablePinching: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final bool enablePinching; + + /// Enables or disables the double tap zooming. + /// + /// Zooming will enable when you tap double time in plot area. + /// After reaching the maximum zoom level, zooming will be stopped. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableDoubleTapZooming: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final bool enableDoubleTapZooming; + + /// Enables or disables the panning. + /// + /// Panning can be performed on a zoomed axis. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enablePanning: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final bool enablePanning; + + /// Enables or disables the selection zooming. + /// + /// Selection zooming can be performed by long-press and then dragging. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final bool enableSelectionZooming; + + /// Enables or disables the mouseWheelZooming. + /// + /// Mouse wheel zooming can be performed by rolling the mouse wheel up or + /// down. The place where the cursor is hovering gets zoomed in or out + /// according to the mouse wheel rolling up or down. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableMouseWheelZooming: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final bool enableMouseWheelZooming; + + /// Enables or disables directional zooming + /// + /// Enables directional zooming behavior in the chart. When it set to true, + /// users can zoom in a specific direction (horizontal, vertical, or diagonal) based on + /// the angle of their pinch or drag gesture. This provides precise control over zooming, + /// especially in scenarios with dense or complex data. + /// + /// Directional zooming is effective only when both [enableDirectionalZooming] and + /// [enablePinching] are set to true. + /// + /// Directional zooming is designed specifically for finger interactions (touch gestures). + /// It is not effective for mouse or trackpad-based interactions. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableDirectionalZooming: true, + /// zoomMode: ZoomMode.xy + /// enablePinching: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior, + /// ); + /// } + /// ``` + final bool enableDirectionalZooming; + + /// By default, both the x and y-axes in the chart can be zoomed. + /// + /// It can be changed by setting value to this property. + /// + /// Defaults to `ZoomMode.xy`. + /// + /// Also refer [ZoomMode]. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// zoomMode: ZoomMode.x + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final ZoomMode zoomMode; + + /// Maximum zoom level. + /// + /// Zooming will be stopped after reached this value and ranges from 0 to 1. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// maximumZoomLevel: 0.8 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final double maximumZoomLevel; + + /// Border width of the selection zooming rectangle. + /// + /// Used to change the stroke width of the selection rectangle. + /// + /// Defaults to `1`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// selectionRectBorderWidth: 2 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final double selectionRectBorderWidth; + + /// Border color of the selection zooming rectangle. + /// + /// It used to change the stroke color of the selection rectangle. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// selectionRectBorderColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final Color? selectionRectBorderColor; + + /// Color of the selection zooming rectangle. + /// + /// It used to change the background color of the selection rectangle. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// selectionRectColor: Colors.yellow + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior + /// ); + /// } + /// ``` + final Color? selectionRectColor; + + // Holds the previous moved position of panning. + Offset? _previousMovedPosition; + + // Holds the overall zoom level. + double _currentZoomLevel = 1.0; + + // Holds the zooming rect start position. + Offset? _zoomRectStartPosition; + + // Holds the current scale value of pinching. + double? _previousScale; + + // Holds the value of zooming rect. + Rect _zoomingRect = Rect.zero; + + // Holds the path of the zooming rect. + Path? _rectPath; + + // Determines zoom direction (horizontal, vertical or both) based on scale changes and angle. + ZoomMode _computeDirectionalZoomMode(ScaleUpdateDetails details) { + final double scale = details.scale; + final double horizontalScale = details.horizontalScale; + final double verticalScale = details.verticalScale; + final double dx = (scale - horizontalScale).abs(); + final double dy = (scale - verticalScale).abs(); + final double angle = ((180 / pi) * atan2(dx, dy)).abs(); + + final bool isXDirection = + (angle >= 340 && angle <= 360) || + (angle >= 0 && angle <= 20) || + (angle >= 160 && angle <= 200); + final bool isYDirection = + (angle >= 70 && angle <= 110) || (angle >= 250 && angle <= 290); + final bool isBothDirection = + (angle > 20 && angle < 70) || + (angle > 110 && angle < 160) || + (angle > 200 && angle < 250) || + (angle > 290 && angle < 340); + + if (isBothDirection) { + return ZoomMode.xy; + } else if (isXDirection) { + return ZoomMode.x; + } else if (isYDirection) { + return ZoomMode.y; + } + return ZoomMode.xy; + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is ZoomPanBehavior && + other.enablePinching == enablePinching && + other.enableDoubleTapZooming == enableDoubleTapZooming && + other.enablePanning == enablePanning && + other.enableSelectionZooming == enableSelectionZooming && + other.enableMouseWheelZooming == enableMouseWheelZooming && + other.enableDirectionalZooming == enableDirectionalZooming && + other.zoomMode == zoomMode && + other.maximumZoomLevel == maximumZoomLevel && + other.selectionRectBorderWidth == selectionRectBorderWidth && + other.selectionRectBorderColor == selectionRectBorderColor && + other.selectionRectColor == selectionRectColor; + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode { + final List values = [ + enablePinching, + enableDoubleTapZooming, + enablePanning, + enableSelectionZooming, + enableMouseWheelZooming, + enableDirectionalZooming, + zoomMode, + maximumZoomLevel, + selectionRectBorderWidth, + selectionRectBorderColor, + selectionRectColor, + ]; + return Object.hashAll(values); + } + + /// To customize the necessary pointer events in behaviors. + /// (e.g., CrosshairBehavior, TrackballBehavior, ZoomingBehavior). + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + if (event is PointerScrollEvent || event is PointerPanZoomUpdateEvent) { + _performMouseWheelZooming(event); + } + } + + /// Called when pointer tap has contacted the screen double time in behavior. + @override + void handleDoubleTap(Offset position) { + if (enableDoubleTapZooming) { + _zoomInAndOut(0.25, origin: position); + } + } + + /// Called when the pointers in contact with the screen + /// and initial scale of 1.0. + @override + void handleScaleStart(ScaleStartDetails details) { + if (!enablePinching) { + return; + } + _previousScale = null; + _updateZoomStartDetails(); + } + + /// Called when the pointers in contact with the screen have indicated + /// a new scale. + @override + void handleScaleUpdate(ScaleUpdateDetails details) { + if (details.pointerCount == 1 || !enablePinching) { + return; + } + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + final RenderCartesianAxes? axes = parent.cartesianAxes; + + if (axes == null) { + return; + } + + // Directional zooming works only if both [enableDirectionalZooming] and + // [enablePinching] are true. + parent.directionalZoomMode = + enableDirectionalZooming && zoomMode == ZoomMode.xy + ? _computeDirectionalZoomMode(details) + : zoomMode; + + if (details.pointerCount == 2) { + parent.hideInteractiveTooltip(); + _pinchZoom(axes, parent, details, details.localFocalPoint); + } + } + + /// Called when the pointers are no longer in contact with the screen. + @override + void handleScaleEnd(ScaleEndDetails details) { + if (!enablePinching) { + return; + } + _previousScale = null; + _updateZoomEndDetails(); + } + + void handleHorizontalDragStart(DragStartDetails details) { + if (!enablePanning) { + return; + } + _previousMovedPosition = null; + _updateZoomStartDetails(); + } + + void handleHorizontalDragUpdate(DragUpdateDetails details) { + if (!enablePanning) { + return; + } + _dragUpdate(details.localPosition); + } + + void handleHorizontalDragEnd(DragEndDetails details) { + if (!enablePanning) { + return; + } + _previousMovedPosition = null; + _updateZoomEndDetails(); + } + + void handleVerticalDragStart(DragStartDetails details) { + if (!enablePanning) { + return; + } + _previousMovedPosition = null; + _updateZoomStartDetails(); + } + + void handleVerticalDragUpdate(DragUpdateDetails details) { + if (!enablePanning) { + return; + } + _dragUpdate(details.localPosition); + } + + void handleVerticalDragEnd(DragEndDetails details) { + if (!enablePanning) { + return; + } + _previousMovedPosition = null; + _updateZoomEndDetails(); + } + + /// Called when a long press gesture by a primary button has been + /// recognized in behavior. + @override + void handleLongPressStart(LongPressStartDetails details) { + if (parentBox == null || !enableSelectionZooming) { + return; + } + final Offset position = parentBox!.globalToLocal(details.globalPosition); + if (_zoomRectStartPosition != position) { + _zoomRectStartPosition = position; + } + } + + /// Called when moving after the long press gesture by a primary button is + /// recognized in behavior. + @override + void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + if (parentBox == null || !enableSelectionZooming) { + return; + } + final Offset position = parentBox!.globalToLocal(details.globalPosition); + _doSelectionZooming(details, position.dx, position.dy); + parentBox!.markNeedsPaint(); + } + + /// Called when the pointer stops contacting the screen after a long-press + /// by a primary button in behavior. + @override + void handleLongPressEnd(LongPressEndDetails details) { + if (parentBox == null || !enableSelectionZooming) { + return; + } + if (_zoomRectStartPosition != null && _zoomingRect.width != 0) { + _drawSelectionZoomRect(_zoomingRect); + } + _zoomRectStartPosition = null; + _zoomingRect = Rect.zero; + parentBox!.markNeedsPaint(); + } + + // Method to perform pan zooming. + void _pan( + RenderCartesianAxes axes, + RenderBehaviorArea parent, + Offset position, + ) { + final ZoomMode effectiveZoomMode = parent.effectiveZoomMode; + double currentZoomPosition; + double calcZoomPosition; + if (_previousMovedPosition != null) { + final Offset translatePosition = _previousMovedPosition! - position; + axes.visitChildren((RenderObject child) { + if (child is RenderChartAxis && child.controller.zoomFactor != 1) { + if (_canZoom(child, effectiveZoomMode)) { + child.zoomingInProgress = true; + _previousScale ??= _toScaleValue(child.controller.zoomFactor); + currentZoomPosition = child.controller.zoomPosition; + calcZoomPosition = _toPanValue( + child.paintBounds, + translatePosition, + child.controller.zoomPosition, + _previousScale!, + child.isVertical, + child.isInversed, + ); + currentZoomPosition = _minMax( + calcZoomPosition, + 0, + 1 - child.controller.zoomFactor, + ); + if (currentZoomPosition != child.controller.zoomPosition) { + child.controller.zoomPosition = currentZoomPosition; + } + if (parent.onZooming != null) { + _bindZoomEvent(child, parent.onZooming!); + } + } + } + }); + } + _previousMovedPosition = position; + } + + // Method to perform pinch zooming + void _pinchZoom( + RenderCartesianAxes axes, + RenderBehaviorArea parent, + ScaleUpdateDetails details, + Offset location, + ) { + final ZoomMode effectiveZoomMode = parent.effectiveZoomMode; + axes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + if (_canZoom(child, effectiveZoomMode)) { + child.zoomingInProgress = true; + final double maxZoomLevel = _toScaleValue(maximumZoomLevel); + final double origin = _calculateOrigin( + child, + child.paintBounds, + location, + ); + final double currentScale = + effectiveZoomMode == ZoomMode.xy + ? details.scale + : child.isVertical + ? details.verticalScale + : details.horizontalScale; + _previousScale ??= _toScaleValue(child.controller.zoomFactor); + _currentZoomLevel = _previousScale! * currentScale; + if (_currentZoomLevel > maxZoomLevel) { + _currentZoomLevel = maxZoomLevel; + } + _zoom(parent, child, origin, _currentZoomLevel); + if (parent.onZooming != null) { + _bindZoomEvent(child, parent.onZooming!); + } + } + } + }); + } + + // Method to perform mouse wheel zooming. + void _performMouseWheelZooming(PointerEvent event) { + if (enableMouseWheelZooming) { + int direction = 0; + if (event is PointerScrollEvent) { + direction = event.scrollDelta.dy > 0 ? -1 : 1; + } else if (event is PointerPanZoomUpdateEvent) { + direction = event.panDelta.dy > 0 ? 1 : -1; + } + + _zoomInAndOut(0.25 * direction, origin: event.position); + } + } + + // Method to find the scale value for current zoom factor. + double _toScaleValue(double zoomFactor) { + return max(1 / _minMax(zoomFactor, 0, 1), 1); + } + + double _toPanValue( + Rect bounds, + Offset position, + double zoomPosition, + double scale, + bool isVertical, + bool isInversed, + ) { + double value = + (isVertical + ? position.dy / bounds.height + : position.dx / bounds.width) / + scale; + if (isVertical && isInversed || !isVertical && !isInversed) { + value = zoomPosition + value; + } else { + value = zoomPosition - value; + } + return value; + } + + // Method to find the chart needs zooming or not. + bool _canZoom(RenderChartAxis axis, ZoomMode effectiveZoomMode) { + final bool canDirectionalZoom = effectiveZoomMode == ZoomMode.xy; + + if ((axis.isVertical && effectiveZoomMode == ZoomMode.y) || + (!axis.isVertical && effectiveZoomMode == ZoomMode.x) || + canDirectionalZoom) { + return true; + } + + return false; + } + + // Method to find the origin value based on touch position. + double _calculateOrigin( + RenderChartAxis axis, + Rect bounds, + Offset? manipulation, + ) { + if (manipulation == null) { + return 0.5; + } + double origin; + double plotOffsetStart = 0.0; + double plotOffsetEnd = 0.0; + if (axis.plotOffset != null) { + plotOffsetStart = axis.plotOffset!; + plotOffsetEnd = axis.plotOffset!; + } else { + plotOffsetStart = axis.plotOffsetStart ?? 0.0; + plotOffsetEnd = axis.plotOffsetEnd ?? 0.0; + } + + if (axis.isVertical) { + origin = + axis.isInversed + ? ((manipulation.dy - plotOffsetEnd) / bounds.height) + : 1 - ((manipulation.dy - plotOffsetStart) / bounds.height); + } else { + origin = + axis.isInversed + ? 1.0 - ((manipulation.dx - plotOffsetStart) / bounds.width) + : (manipulation.dx - plotOffsetEnd) / bounds.width; + } + + return origin; + } + + // Method to update the zoom values. + void _zoom( + RenderBehaviorArea parent, + RenderChartAxis axis, + double originPoint, + double cumulativeZoomLevel, + ) { + double currentZoomPosition; + double currentZoomFactor; + if (cumulativeZoomLevel == 1) { + currentZoomFactor = 1; + currentZoomPosition = 0; + } else { + currentZoomFactor = _minMax(1 / cumulativeZoomLevel, 0, 1); + currentZoomPosition = + axis.controller.zoomPosition + + ((axis.controller.zoomFactor - currentZoomFactor) * originPoint); + } + + if (axis.controller.zoomFactor != currentZoomFactor) { + axis.controller.zoomFactor = currentZoomFactor; + } + if (axis.controller.zoomPosition != currentZoomPosition) { + axis.controller.zoomPosition = _minMax( + currentZoomPosition, + 0, + 1 - axis.controller.zoomFactor, + ); + } + } + + double _minMax(double value, double min, double max) { + return value > max ? max : (value < min ? min : value); + } + + void _dragUpdate(Offset position) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + parent.hideInteractiveTooltip(); + final RenderCartesianAxes? axes = parent.cartesianAxes; + + if (axes == null) { + return; + } + _pan(axes, parent, position); + } + + void _updateZoomStartDetails() { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + if (enablePanning || enablePinching) { + parent.hideInteractiveTooltip(); + } + final RenderCartesianAxes? axes = parent.cartesianAxes; + if (axes == null) { + return; + } + if (parent.onZoomStart != null) { + axes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + _bindZoomEvent(child, parent.onZoomStart!); + } + }); + } + } + + void _updateZoomEndDetails() { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + final RenderCartesianAxes? axes = parent.cartesianAxes; + if (axes == null) { + return; + } + if (parent.onZoomEnd != null) { + axes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + _bindZoomEvent(child, parent.onZoomEnd!); + } + }); + } + } + + void _zoomInAndOut(double zoomLevel, {Offset? origin}) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + parent.hideInteractiveTooltip(); + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (cartesianAxes == null) { + return; + } + + final Rect clipRect = parent.paintBounds; + final ZoomMode effectiveZoomMode = parent.effectiveZoomMode; + cartesianAxes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + if (_canZoom(child, effectiveZoomMode)) { + final double originPoint = _calculateOrigin(child, clipRect, origin); + child.zoomingInProgress = true; + if (parent.onZoomStart != null) { + _bindZoomEvent(child, parent.onZoomStart!); + } + + final double maxZoomLevel = _toScaleValue(maximumZoomLevel); + _currentZoomLevel = _toScaleValue(child.controller.zoomFactor); + _currentZoomLevel = _currentZoomLevel + zoomLevel; + if (_currentZoomLevel > maxZoomLevel) { + _currentZoomLevel = maxZoomLevel; + } + + _zoom(parent, child, originPoint, _currentZoomLevel); + + if (parent.onZoomEnd != null) { + _bindZoomEvent(child, parent.onZoomEnd!); + } + } + } + }); + } + + /// Increases the magnification of the plot area. + void zoomIn() { + _zoomInAndOut(0.1); + } + + /// Decreases the magnification of the plot area. + void zoomOut() { + _zoomInAndOut(-0.1); + } + + /// Changes the zoom level using zoom factor. + /// + /// 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 RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + parent.hideInteractiveTooltip(); + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (cartesianAxes == null) { + return; + } + final ZoomMode effectiveZoomMode = parent.effectiveZoomMode; + cartesianAxes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + if (_canZoom(child, effectiveZoomMode)) { + child.controller.zoomFactor = max(zoomFactor, maximumZoomLevel); + if (parent.onZooming != null) { + _bindZoomEvent(child, parent.onZooming!); + } + } + } + }); + } + + /// Zooms the chart for a given rectangle value. + /// + /// 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) { + _drawSelectionZoomRect(rect); + } + + /// Change the zoom level of an appropriate axis. + /// + /// 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 RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + parent.hideInteractiveTooltip(); + final RenderChartAxis? axisDetails = + axis.name != null + ? parent.axisFromName(axis.name) + : parent.axisFromObject(axis); + + if (axisDetails != null) { + axisDetails.controller.zoomFactor = max(zoomFactor, maximumZoomLevel); + axisDetails.controller.zoomPosition = zoomPosition; + } + parent.invalidate(); + } + + /// Pans the plot area for given left, right, top, and bottom directions. + /// + /// To perform this action, the plot area needs to be in zoomed state. + void panToDirection(String direction) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + parent.hideInteractiveTooltip(); + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (cartesianAxes == null) { + return; + } + direction = direction.toLowerCase(); + cartesianAxes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + final double currentZoomFactor = child.controller.zoomFactor; + final double increaseZoomPosition = + child.controller.zoomPosition + (child.isInversed ? -0.1 : 0.1); + final double decreaseZoomPosition = + child.controller.zoomPosition + (child.isInversed ? 0.1 : -0.1); + if ((child.isVertical && direction == 'bottom') || + (!child.isVertical && direction == 'left')) { + child.controller.zoomPosition = _minMax( + decreaseZoomPosition, + 0, + 1 - currentZoomFactor, + ); + } else if ((child.isVertical && direction == 'top') || + (!child.isVertical && direction == 'right')) { + child.controller.zoomPosition = _minMax( + increaseZoomPosition, + 0, + 1 - currentZoomFactor, + ); + } + if (parent.onZooming != null) { + _bindZoomEvent(child, parent.onZooming!); + } + } + }); + parent.invalidate(); + } + + /// Returns the plot area back to its original position after zooming. + void reset() { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + parent.hideInteractiveTooltip(); + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (cartesianAxes == null) { + return; + } + + cartesianAxes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + child.controller.zoomFactor = 1.0; + child.controller.zoomPosition = 0.0; + if (parent.onZoomReset != null) { + _bindZoomEvent(child, parent.onZoomReset!); + } + } + }); + } + + ZoomPanArgs _bindZoomEvent( + RenderChartAxis axis, + ChartZoomingCallback zoomEventType, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + final ZoomPanArgs zoomPanArgs = ZoomPanArgs( + axis, + axis.controller.previousZoomPosition, + axis.controller.previousZoomFactor, + ); + zoomPanArgs.currentZoomFactor = axis.controller.zoomFactor; + zoomPanArgs.currentZoomPosition = axis.controller.zoomPosition; + if (parent == null) { + return zoomPanArgs; + } + zoomEventType == parent.onZoomStart + ? parent.onZoomStart!(zoomPanArgs) + : zoomEventType == parent.onZoomEnd + ? parent.onZoomEnd!(zoomPanArgs) + : zoomEventType == parent.onZooming + ? parent.onZooming!(zoomPanArgs) + : parent.onZoomReset!(zoomPanArgs); + return zoomPanArgs; + } + + void _doSelectionZooming( + LongPressMoveUpdateDetails details, + double currentX, + double currentY, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + if (_zoomRectStartPosition != null) { + final Rect clipRect = parent.paintBounds; + final Offset startPosition = Offset( + max(_zoomRectStartPosition!.dx, clipRect.left), + max(_zoomRectStartPosition!.dy, clipRect.top), + ); + Offset currentMousePosition = + startPosition + details.localOffsetFromOrigin; + final double currentX = _minMax( + currentMousePosition.dx, + clipRect.left, + clipRect.right, + ); + final double currentY = _minMax( + currentMousePosition.dy, + clipRect.top, + clipRect.bottom, + ); + currentMousePosition = Offset(currentX, currentY); + _rectPath = Path(); + final ZoomMode effectiveZoomMode = parent.effectiveZoomMode; + if (effectiveZoomMode == 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(); + } else if (effectiveZoomMode == 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(); + } 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(); + } + _zoomingRect = _rectPath!.getBounds(); + } + } + + void _drawSelectionZoomRect(Rect zoomRect) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + parent.hideInteractiveTooltip(); + final RenderCartesianAxes? axes = parent.cartesianAxes; + if (axes == null) { + return; + } + axes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + child.zoomingInProgress = true; + if (parent.onZoomStart != null) { + _bindZoomEvent(child, parent.onZoomStart!); + } + final Rect clipRect = child.paintBounds; + final ZoomMode effectiveZoomMode = parent.effectiveZoomMode; + if (child.isVertical) { + if (effectiveZoomMode != ZoomMode.x) { + final double zoomRectHeightFromTop = + zoomRect.height + (zoomRect.top - clipRect.top); + child.controller.zoomPosition += + (1 - (zoomRectHeightFromTop / clipRect.height).abs()) * + child.controller.zoomFactor; + child.controller.zoomFactor *= zoomRect.height / clipRect.height; + child.controller.zoomFactor = max( + child.controller.zoomFactor, + maximumZoomLevel, + ); + } + } else { + if (effectiveZoomMode != ZoomMode.y) { + final double zoomRectWidthFromLeft = zoomRect.left - clipRect.left; + child.controller.zoomPosition += + (zoomRectWidthFromLeft / clipRect.width).abs() * + child.controller.zoomFactor; + child.controller.zoomFactor *= + zoomRect.width / child.paintBounds.width; + child.controller.zoomFactor = max( + child.controller.zoomFactor, + maximumZoomLevel, + ); + } + } + if (parent.onZoomEnd != null) { + _bindZoomEvent(child, parent.onZoomEnd!); + } + } + }); + zoomRect = Rect.zero; + _rectPath = Path(); + } + + void _drawTooltipConnector( + RenderCartesianAxes axes, + RenderBehaviorArea? parent, + Offset startPosition, + Offset endPosition, + Canvas canvas, + Rect plotAreaBounds, + Offset plotAreaOffset, + ) { + RRect? startTooltipRect, endTooltipRect; + String startValue, endValue; + Size startLabelSize, endLabelSize; + Rect startLabelRect, endLabelRect; + TextStyle textStyle = + parent!.chartThemeData!.selectionZoomingTooltipTextStyle!; + final Paint labelFillPaint = + Paint() + ..color = axes.chartThemeData.crosshairBackgroundColor! + ..isAntiAlias = true; + + final Paint labelStrokePaint = + Paint() + ..color = axes.chartThemeData.crosshairBackgroundColor! + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + axes.visitChildren((RenderObject child) { + if (child is RenderChartAxis) { + if (child.interactiveTooltip.enable) { + textStyle = textStyle.merge(child.interactiveTooltip.textStyle); + labelFillPaint.color = + (child.interactiveTooltip.color ?? + axes.chartThemeData.crosshairBackgroundColor)!; + labelStrokePaint.color = + (child.interactiveTooltip.borderColor ?? + axes.chartThemeData.crosshairBackgroundColor)!; + labelStrokePaint.strokeWidth = child.interactiveTooltip.borderWidth; + final Paint connectorLinePaint = + Paint() + ..color = + (child.interactiveTooltip.connectorLineColor ?? + axes.chartThemeData.selectionTooltipConnectorLineColor)! + ..strokeWidth = child.interactiveTooltip.connectorLineWidth + ..style = PaintingStyle.stroke; + + final Path startLabelPath = Path(); + final Path endLabelPath = Path(); + startValue = tooltipValue(startPosition, child, plotAreaBounds); + endValue = tooltipValue(endPosition, child, plotAreaBounds); + + if (startValue.isNotEmpty && endValue.isNotEmpty) { + startLabelSize = measureText(startValue, textStyle); + endLabelSize = measureText(endValue, textStyle); + startLabelRect = calculateRect( + child, + startPosition, + startLabelSize, + ); + endLabelRect = calculateRect(child, endPosition, endLabelSize); + if (child.isVertical && + startLabelRect.width != endLabelRect.width) { + final String axisPosition = + child.opposedPosition ? 'right' : 'left'; + (startLabelRect.width > endLabelRect.width) + ? endLabelRect = validateRect( + startLabelRect, + endLabelRect, + axisPosition, + ) + : startLabelRect = validateRect( + endLabelRect, + startLabelRect, + axisPosition, + ); + } + startTooltipRect = calculateTooltipRect( + canvas, + labelFillPaint, + labelStrokePaint, + startLabelPath, + startPosition, + startLabelRect, + startTooltipRect, + startValue, + startLabelSize, + plotAreaBounds, + textStyle, + child, + plotAreaOffset, + ); + endTooltipRect = calculateTooltipRect( + canvas, + labelFillPaint, + labelStrokePaint, + endLabelPath, + endPosition, + endLabelRect, + endTooltipRect, + endValue, + endLabelSize, + plotAreaBounds, + textStyle, + child, + plotAreaOffset, + ); + drawConnector( + canvas, + connectorLinePaint, + startTooltipRect!, + endTooltipRect!, + startPosition, + endPosition, + child, + ); + } + } + } + }); + } + + @override + void onPaint( + PaintingContext context, + Offset offset, + SfChartThemeData chartThemeData, + ThemeData themeData, + ) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; + if (cartesianAxes == null) { + return; + } + if (_zoomingRect != Rect.zero && _rectPath != null) { + Color? fillColor = selectionRectColor; + if (fillColor != null && + fillColor != Colors.transparent && + fillColor.a == 1) { + fillColor = fillColor.withValues(alpha: 0.3); + } + final Paint fillPaint = + Paint() + ..color = + (fillColor ?? cartesianAxes.chartThemeData.selectionRectColor)! + ..style = PaintingStyle.fill; + context.canvas.drawRect(_zoomingRect, fillPaint); + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..color = + (selectionRectBorderColor ?? + cartesianAxes.chartThemeData.selectionRectBorderColor)! + ..strokeWidth = selectionRectBorderWidth + ..style = PaintingStyle.stroke; + + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + final List dashArray = [5, 5]; + drawDashes(context.canvas, dashArray, strokePaint, path: _rectPath); + } + + final Offset plotAreaOffset = + (parent.parentData! as BoxParentData).offset; + // Selection zooming tooltip rendering + _drawTooltipConnector( + cartesianAxes, + parent, + _zoomingRect.topLeft, + _zoomingRect.bottomRight, + context.canvas, + parent.paintBounds, + plotAreaOffset, + ); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart new file mode 100644 index 000000000..07a5062ad --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart @@ -0,0 +1,1764 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'axis/axis.dart'; +import 'axis/numeric_axis.dart'; +import 'base.dart'; +import 'behaviors/crosshair.dart'; +import 'behaviors/trackball.dart'; +import 'behaviors/zooming.dart'; +import 'common/annotation.dart'; +import 'common/callbacks.dart'; +import 'common/chart_point.dart'; +import 'common/core_legend.dart' as core; +import 'common/core_tooltip.dart'; +import 'common/element_widget.dart'; +import 'common/legend.dart'; +import 'common/title.dart'; +import 'indicators/technical_indicator.dart'; +import 'interactions/behavior.dart'; +import 'interactions/tooltip.dart'; +import 'series/chart_series.dart'; +import 'theme.dart'; +import 'utils/constants.dart'; +import 'utils/enum.dart'; +import 'utils/helper.dart'; +import 'utils/typedef.dart'; + +/// Renders the Cartesian type charts. +/// +/// Cartesian charts are generally charts with horizontal and vertical axes. +/// [SfCartesianChart] provides options to customize +/// chart types using the `series` property. +/// +/// ```dart +/// late TooltipBehavior _tooltipBehavior; +/// +/// @override +/// void initState() { +/// _tooltipBehavior = TooltipBehavior(enable: true); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Center( +/// child: SfCartesianChart( +/// title: ChartTitle(text: 'Flutter Chart'), +/// legend: Legend(isVisible: true), +/// series: getDefaultData(), +/// tooltipBehavior: _tooltipBehavior, +/// )); +/// } +/// +/// static List> getDefaultData() { +/// final bool isDataLabelVisible = true, +/// isMarkerVisible = true, +/// isTooltipVisible = true; +/// double? lineWidth, markerWidth, markerHeight; +/// final List chartData = [ +/// SalesData(DateTime(2005, 0, 1), 'India', 1.5, 21, 28, 680, 760), +/// SalesData(DateTime(2006, 0, 1), 'China', 2.2, 24, 44, 550, 880), +/// SalesData(DateTime(2007, 0, 1), 'USA', 3.32, 36, 48, 440, 788), +/// SalesData(DateTime(2008, 0, 1), 'Japan', 4.56, 38, 50, 350, 560), +/// SalesData(DateTime(2009, 0, 1), 'Russia', 5.87, 54, 66, 444, 566), +/// SalesData(DateTime(2010, 0, 1), 'France', 6.8, 57, 78, 780, 650), +/// SalesData(DateTime(2011, 0, 1), 'Germany', 8.5, 70, 84, 450, 800) +/// ]; +/// return >[ +/// LineSeries( +/// enableTooltip: true, +/// dataSource: chartData, +/// xValueMapper: (SalesData sales, _) => sales.y, +/// yValueMapper: (SalesData sales, _) => sales.y4, +/// width: lineWidth ?? 2, +/// markerSettings: MarkerSettings( +/// isVisible: isMarkerVisible, +/// height: markerWidth ?? 4, +/// width: markerHeight ?? 4, +/// shape: DataMarkerType.circle, +/// borderWidth: 3, +/// borderColor: Colors.red), +/// dataLabelSettings: DataLabelSettings( +/// isVisible: isDataLabelVisible, +/// labelAlignment: ChartDataLabelAlignment.auto)), +/// LineSeries( +/// enableTooltip: isTooltipVisible, +/// dataSource: chartData, +/// width: lineWidth ?? 2, +/// xValueMapper: (SalesData sales, _) => sales.y, +/// yValueMapper: (SalesData sales, _) => sales.y3, +/// markerSettings: MarkerSettings( +/// isVisible: isMarkerVisible, +/// height: markerWidth ?? 4, +/// width: markerHeight ?? 4, +/// shape: DataMarkerType.circle, +/// borderWidth: 3, +/// borderColor: Colors.black), +/// dataLabelSettings: DataLabelSettings( +/// isVisible: isDataLabelVisible, +/// labelAlignment: ChartDataLabelAlignment.auto)) +/// ]; +/// } +/// ``` +class SfCartesianChart extends StatefulWidget { + /// Creating an argument constructor of [SfCartesianChart] class. + const SfCartesianChart({ + Key? key, + this.backgroundColor, + this.enableSideBySideSeriesPlacement = true, + this.borderColor = Colors.transparent, + this.borderWidth = 0.0, + this.plotAreaBackgroundColor, + this.plotAreaBorderColor, + this.plotAreaBorderWidth = 0.7, + this.plotAreaBackgroundImage, + this.onTooltipRender, + this.onActualRangeChanged, + this.onDataLabelRender, + this.onLegendItemRender, + this.onTrackballPositionChanging, + this.onCrosshairPositionChanging, + this.onZooming, + this.onZoomStart, + this.onZoomEnd, + this.onZoomReset, + this.onAxisLabelTapped, + this.onDataLabelTapped, + this.onLegendTapped, + this.onSelectionChanged, + this.onChartTouchInteractionUp, + this.onChartTouchInteractionDown, + this.onChartTouchInteractionMove, + this.onMarkerRender, + this.isTransposed = false, + this.enableAxisAnimation = false, + this.annotations, + this.loadMoreIndicatorBuilder, + this.onPlotAreaSwipe, + this.palette, + this.primaryXAxis = const NumericAxis(), + this.primaryYAxis = const NumericAxis(), + this.margin = const EdgeInsets.all(10), + this.tooltipBehavior, + this.zoomPanBehavior, + this.legend = const Legend(), + this.selectionType = SelectionType.point, + this.selectionGesture = ActivationMode.singleTap, + this.enableMultiSelection = false, + this.crosshairBehavior, + this.trackballBehavior, + this.series = const [], + this.title = const ChartTitle(), + this.axes = const [], + this.indicators = const [], + }) : super(key: key); + + /// Customizes the chart title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// title: ChartTitle( + /// text: 'Area with animation', + /// alignment: ChartAlignment.center, + /// backgroundColor: Colors.white, + /// borderColor: Colors.transparent, + /// borderWidth: 0) + /// ) + /// ); + /// } + /// ``` + final ChartTitle title; + + /// Customizes the legend in the chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// legend: Legend(isVisible: true), + /// ) + /// ); + /// } + /// ``` + final Legend legend; + + /// Background color of the chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// backgroundColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + final Color? backgroundColor; + + /// Color of the chart border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// borderColor: Colors.red + /// ) + /// ); + /// } + /// ``` + final Color borderColor; + + /// Width of the chart border. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// borderWidth: 2 + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Background color of the plot area. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// plotAreaBackgroundColor: Colors.red, + /// ) + /// ); + /// } + /// ``` + final Color? plotAreaBackgroundColor; + + /// Border color of the plot area. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// plotAreaBorderColor: Colors.red, + /// ) + /// ); + /// } + /// ``` + final Color? plotAreaBorderColor; + + /// Border width of the plot area. + /// + /// Defaults to `0.7`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// plotAreaBorderColor: Colors.red, + /// plotAreaBorderWidth: 2 + /// ) + /// ); + /// } + /// ``` + final double plotAreaBorderWidth; + + /// Customizes the primary x-axis in chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis(interval: 1) + /// ) + /// ); + /// } + /// ``` + final ChartAxis primaryXAxis; + + /// Customizes the primary y-axis in chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryYAxis: NumericAxis(isInversed: false) + /// ) + /// ); + /// } + /// ``` + final ChartAxis primaryYAxis; + + /// Margin for chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// margin: const EdgeInsets.all(2), + /// borderColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + final EdgeInsets margin; + + /// Customizes the additional axes in the chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart(axes: [ + /// NumericAxis(majorGridLines: MajorGridLines(color: Colors.transparent)) + /// ])); + /// } + /// ``` + final List axes; + + /// Enables or disables the placing of series side by side. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// enableSideBySideSeriesPlacement: false + /// ) + /// ); + /// } + /// ``` + final bool enableSideBySideSeriesPlacement; + + /// 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. + /// + /// ```dart + /// TooltipBehavior _tooltipBehavior; + /// + /// @override + /// void initState() { + /// _tooltipBehavior = TooltipBehavior( enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// tooltipBehavior: _tooltipBehavior, + /// onTooltipRender: (TooltipArgs args) => tool(args) + /// ) + /// ); + /// } + /// + /// void tool(TooltipArgs args) { + /// args.locationX = 30; + /// } + /// ``` + 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. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onActualRangeChanged: (ActualRangeChangedArgs args) => range(args) + /// ) + /// ); + /// } + /// + /// void range(ActualRangeChangedArgs args) { + /// print(args.visibleMin); + /// } + /// ``` + final ChartActualRangeChangedCallback? onActualRangeChanged; + + /// Occurs when tapping the axis label. Here, you can get the appropriate axis + /// that is tapped and the axis label text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onDataLabelRender: (DataLabelRenderArgs args) => dataLabel(args), + /// series: >[ + /// ColumnSeries( + /// dataLabelSettings: DataLabelSettings(isVisible: true), + /// ), + /// ], + /// ) + /// ); + /// } + /// + /// void dataLabel(DataLabelRenderArgs args) { + /// args.text = 'data Label'; + /// } + /// ``` + 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. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// legend: Legend(isVisible: true), + /// onLegendItemRender: (LegendRenderArgs args) => legend(args) + /// ) + /// ); + /// } + /// void legend(LegendRenderArgs args) { + /// args.seriesIndex = 2; + /// } + /// ``` + final ChartLegendRenderCallback? onLegendItemRender; + + /// Occurs while the trackball position is changed. Here, you can customize + /// the text of the trackball. + /// + /// ```dart + /// late TrackballBehavior _trackballBehavior; + /// + /// @override + /// void initState() { + /// _trackballBehavior = TrackballBehavior( enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: _trackballBehavior, + /// onTrackballPositionChanging: (TrackballArgs args) => trackball(args) + /// ) + /// ); + /// } + /// void trackball(TrackballArgs args) { + /// args.chartPointInfo = ChartPointInfo(); + /// } + /// ``` + final ChartTrackballCallback? onTrackballPositionChanging; + + /// Occurs when tapping the axis label. Here, you can get the appropriate axis + /// that is tapped and the axis label text. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// @override + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior, + /// onCrosshairPositionChanging: (CrosshairRenderArgs args) => crosshair(args) + /// ) + /// ); + /// } + /// void crosshair(CrosshairRenderArgs args) { + /// args.text = 'crosshair'; + /// } + /// ``` + 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 zoom position, previous zoom factor, and previous zoom position. + /// + /// ```dart + /// late ZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// zoomPanBehavior: _zoomPanBehavior, + /// onZoomStart: (ZoomPanArgs args) => zoom(args), + /// ) + /// ); + /// } + /// void zoom(ZoomPanArgs args) { + /// args.currentZoomFactor = 0.2; + /// } + /// ``` + 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. + /// + /// ```dart + /// late ZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// zoomPanBehavior: _zoomPanBehavior, + /// onZoomEnd: (ZoomPanArgs args) => zoom(args), + /// ) + /// ); + /// } + /// void zoom(ZoomPanArgs args) { + /// print(args.currentZoomPosition); + /// } + /// ``` + 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. + /// + /// ```dart + /// late ZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// zoomPanBehavior: _zoomPanBehavior, + /// onZoomReset: (ZoomPanArgs args) => zoom(args), + /// ) + /// ); + /// } + /// void zoom(ZoomPanArgs args) { + /// print(args.currentZoomPosition); + /// } + /// ``` + final ChartZoomingCallback? onZoomReset; + + /// Occurs when Zooming event is performed. Here, you can get the axis, + /// current zoom factor, current zoom position, previous zoom factor, + /// and previous zoom position. + /// + /// ```dart + /// late ZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = ZoomPanBehavior(enableSelectionZooming: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// zoomPanBehavior: _zoomPanBehavior, + /// onZooming: (ZoomPanArgs args) => zoom(args), + /// ) + /// ); + /// } + /// void zoom(ZoomPanArgs args) { + /// print(args.currentZoomPosition); + /// } + /// ``` + final ChartZoomingCallback? onZooming; + + /// Called when the data label is tapped. + /// + /// 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. + /// + /// _Note:_ This callback will not be called, when the builder is specified + /// for data label (data label template). For this case, custom widget + /// specified in the `DataLabelSettings.builder` property can be wrapped + /// using the `GestureDetector` and this functionality can be achieved + /// in the application level. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onDataLabelTapped: (DataLabelTapDetails args) { + /// print(arg.seriesIndex); + /// } + /// ) + /// ); + /// } + /// ``` + final DataLabelTapCallback? onDataLabelTapped; + + /// Occurs when tapping the axis label. Here, you can get the appropriate + /// axis that is tapped and the axis label text. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onAxisLabelTapped: (AxisLabelTapArgs args) => axis(args), + /// ) + /// ); + /// } + /// + /// void axis(AxisLabelTapArgs args) { + /// print(args.text); + /// } + /// ``` + 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. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onLegendTapped: (LegendTapArgs args) => legend(args), + /// ) + /// ); + /// } + /// + /// void legend(LegendTapArgs args) { + /// print(args.pointIndex); + /// } + /// ``` + 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 width, unselected border width, + /// series index, and point index. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onSelectionChanged: (SelectionArgs args) => print(args.selectedColor), + /// ) + /// ); + /// } + final ChartSelectionCallback? onSelectionChanged; + + /// Occurs when tapped on the chart area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final ChartTouchInteractionCallback? onChartTouchInteractionUp; + + /// Occurs when touched on the chart area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final ChartTouchInteractionCallback? onChartTouchInteractionDown; + + /// Occurs when touched and moved on the chart area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final ChartTouchInteractionCallback? onChartTouchInteractionMove; + + /// Occurs when the marker is rendered. Here, you can get the marker + /// pointIndex shape, height and width of data markers. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container(child: SfCartesianChart( + /// onMarkerRender: (MarkerRenderArgs args) { + /// if (args.pointIndex == 2) { + /// args.markerHeight = 20.0; + /// args.markerWidth = 20.0; + /// args.shape = DataMarkerType.triangle; + /// } + /// }, + /// )); + /// } + /// ``` + final ChartMarkerRenderCallback? onMarkerRender; + + /// Customizes the tooltip in chart. + /// + /// ```dart + /// late TooltipBehavior _tooltipBehavior; + /// + /// @override + /// void initState() { + /// _tooltipBehavior = TooltipBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// tooltipBehavior: _tooltipBehavior + /// ) + /// ); + /// } + /// ``` + final TooltipBehavior? tooltipBehavior; + + /// Customizes the crosshair in chart. + /// + /// ```dart + /// late CrosshairBehavior _crosshairBehavior; + /// + /// @override + /// void initState() { + /// _crosshairBehavior = CrosshairBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// crosshairBehavior: _crosshairBehavior, + /// ) + /// ); + /// } + /// ``` + final CrosshairBehavior? crosshairBehavior; + + /// Customizes the trackball in chart. + /// + /// ```dart + /// late TrackballBehavior _trackballBehavior; + /// + /// @override + /// void initState() { + /// _trackballBehavior = TrackballBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: _trackballBehavior, + /// ) + /// ); + /// } + /// ``` + final TrackballBehavior? trackballBehavior; + + /// Customizes the zooming and panning settings. + /// + /// ```dart + /// late ZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = ZoomPanBehavior( enablePanning: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// zoomPanBehavior: _zoomPanBehavior + /// ) + /// ); + /// } + /// ``` + final ZoomPanBehavior? zoomPanBehavior; + + /// Mode of selecting the data points or series. + /// + /// Defaults to `SelectionType.point`. + /// + /// Also refer [SelectionType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// selectionType: SelectionType.series, + /// ) + /// ); + /// } + /// ``` + final SelectionType selectionType; + + /// Customizes the annotations. Annotations are used to mark the specific area + /// of interest in the plot area with texts, shapes, or images. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// coordinateUnit: CoordinateUnit.point, + /// region: AnnotationRegion.chart, + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final List? annotations; + + /// Enables or disables the multiple data points or series selection. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// @override + /// void initState() { + /// _selectionBehavior = SelectionBehavior( enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// enableMultiSelection: true, + /// series: >[ + /// BarSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool enableMultiSelection; + + /// Gesture for activating the selection. Selection can be activated in tap, + /// double tap, and long press. + /// + /// Defaults to `ActivationMode.tap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// @override + /// void initState() { + /// _selectionBehavior = SelectionBehavior( enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// selectionGesture: ActivationMode.doubleTap, + /// series: >[ + /// BarSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final ActivationMode selectionGesture; + + /// Background image for chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// plotAreaBackgroundImage: const AssetImage('images/bike.png'), + /// ) + /// ); + /// } + /// ``` + final ImageProvider? plotAreaBackgroundImage; + + /// By setting this, the orientation of x-axis is set to vertical and + /// orientation of y-axis is set to horizontal. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// isTransposed: true, + /// ) + /// ); + /// } + /// ``` + final bool isTransposed; + + /// Axis elements animation on visible range change. + /// + /// Axis elements like grid lines, tick lines and labels will be animated when + /// the axis range is changed dynamically. Axis visible range will be changed + /// while zooming, panning or while updating the data points. + /// + /// The elements will be animated on setting `true` to this property and this + /// is applicable for all primary and secondary axis in the chart. + /// + /// Defaults to `false`. + /// + /// See also [ChartSeries.animationDuration] for changing the series + /// animation duration. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// enableAxisAnimation: true, + /// ) + /// ); + /// } + /// ``` + final bool enableAxisAnimation; + + /// Customizes the series in chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// AreaSeries( + /// dataSource: chartData, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final List series; + + /// Color palette for chart series. If the series color is not specified, + /// then the series will be rendered with appropriate palette color. + /// Ten colors are available by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// palette: [Colors.red, Colors.green] + /// ) + /// ); + /// } + /// ``` + final List? palette; + + /// 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 + /// ChartSeriesController? seriesController; + /// + /// 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(); +} + +class SfCartesianChartState extends State + with TickerProviderStateMixin { + final List _legendItems = []; + + late GlobalKey? _legendKey; + late GlobalKey? _tooltipKey; + late GlobalKey _trackballBuilderKey; + late SfChartThemeData _chartThemeData; + late ThemeData _themeData; + List? _annotations; + ChartSwipeDirection? _swipeDirection; + SfLocalizations? _localizations; + Widget? _trackballBuilder; + + SfChartThemeData _updateThemeData( + BuildContext context, + ChartThemeData effectiveChartThemeData, + ) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + axisLineColor: + chartThemeData.axisLineColor ?? effectiveChartThemeData.axisLineColor, + axisLabelColor: + chartThemeData.axisLabelColor ?? + effectiveChartThemeData.axisLabelColor, + axisTitleColor: + chartThemeData.axisTitleColor ?? + effectiveChartThemeData.axisTitleColor, + titleTextColor: + chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor, + crosshairBackgroundColor: + chartThemeData.crosshairBackgroundColor ?? + effectiveChartThemeData.crosshairBackgroundColor, + crosshairLabelColor: + chartThemeData.crosshairLabelColor ?? + effectiveChartThemeData.crosshairLabelColor, + legendTextColor: + chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor, + legendTitleColor: + chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor, + majorGridLineColor: + chartThemeData.majorGridLineColor ?? + effectiveChartThemeData.majorGridLineColor, + minorGridLineColor: + chartThemeData.minorGridLineColor ?? + effectiveChartThemeData.minorGridLineColor, + majorTickLineColor: + chartThemeData.majorTickLineColor ?? + effectiveChartThemeData.majorTickLineColor, + minorTickLineColor: + chartThemeData.minorTickLineColor ?? + effectiveChartThemeData.minorTickLineColor, + selectionRectColor: + chartThemeData.selectionRectColor ?? + effectiveChartThemeData.selectionRectColor, + selectionRectBorderColor: + chartThemeData.selectionRectBorderColor ?? + effectiveChartThemeData.selectionRectBorderColor, + selectionTooltipConnectorLineColor: + chartThemeData.selectionTooltipConnectorLineColor ?? + effectiveChartThemeData.selectionTooltipConnectorLineColor, + waterfallConnectorLineColor: + chartThemeData.waterfallConnectorLineColor ?? + effectiveChartThemeData.waterfallConnectorLineColor, + tooltipLabelColor: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + tooltipSeparatorColor: + chartThemeData.tooltipSeparatorColor ?? + effectiveChartThemeData.tooltipSeparatorColor, + backgroundColor: + widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: + widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, + plotAreaBackgroundColor: + widget.plotAreaBackgroundColor ?? + chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + plotAreaBorderColor: + widget.plotAreaBorderColor ?? + chartThemeData.plotAreaBorderColor ?? + effectiveChartThemeData.plotAreaBorderColor, + legendBackgroundColor: + widget.legend.backgroundColor ?? + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + crosshairLineColor: + widget.crosshairBehavior?.lineColor ?? + chartThemeData.crosshairLineColor ?? + effectiveChartThemeData.crosshairLineColor, + tooltipColor: + widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: + chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor, + ) + .merge(chartThemeData.titleTextStyle) + .merge(widget.title.textStyle), + axisTitleTextStyle: effectiveChartThemeData.axisTitleTextStyle! + .copyWith( + color: + chartThemeData.axisTitleColor ?? + effectiveChartThemeData.axisTitleColor, + ) + .merge(chartThemeData.axisTitleTextStyle), + axisLabelTextStyle: effectiveChartThemeData.axisLabelTextStyle! + .copyWith( + color: + chartThemeData.axisLabelColor ?? + effectiveChartThemeData.axisLabelColor, + ) + .merge(chartThemeData.axisLabelTextStyle), + axisMultiLevelLabelTextStyle: effectiveChartThemeData + .axisMultiLevelLabelTextStyle! + .copyWith( + color: + chartThemeData.axisLabelColor ?? + effectiveChartThemeData.axisLabelColor, + ) + .merge(chartThemeData.axisMultiLevelLabelTextStyle), + plotBandLabelTextStyle: effectiveChartThemeData.plotBandLabelTextStyle! + .merge(chartThemeData.plotBandLabelTextStyle), + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: + chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor, + ) + .merge(chartThemeData.legendTitleTextStyle) + .merge(widget.legend.title?.textStyle), + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: + chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor, + ) + .merge(chartThemeData.legendTextStyle) + .merge(widget.legend.textStyle), + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.tooltipTextStyle) + .merge(widget.tooltipBehavior?.textStyle), + trackballTextStyle: effectiveChartThemeData.trackballTextStyle! + .copyWith( + color: + chartThemeData.crosshairLabelColor ?? + effectiveChartThemeData.crosshairLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackballBehavior?.tooltipSettings.textStyle), + crosshairTextStyle: effectiveChartThemeData.crosshairTextStyle! + .copyWith( + color: + chartThemeData.crosshairLabelColor ?? + effectiveChartThemeData.crosshairLabelColor, + ) + .merge(chartThemeData.crosshairTextStyle), + selectionZoomingTooltipTextStyle: effectiveChartThemeData + .selectionZoomingTooltipTextStyle! + .copyWith( + color: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.selectionZoomingTooltipTextStyle), + ); + return chartThemeData; + } + + Widget _buildLegendItem(BuildContext context, int index) { + return buildLegendItem(context, _legendItems[index], widget.legend); + } + + void _collectAnnotationWidgets() { + if (widget.annotations != null) { + if (_annotations == null) { + _annotations = []; + } else { + _annotations!.clear(); + } + + for (final CartesianChartAnnotation annotation in widget.annotations!) { + _annotations!.add(annotation.widget); + } + } + } + + Widget? _buildTooltipWidget( + BuildContext context, + TooltipInfo? info, + Size maxSize, + ) { + return buildTooltipWidget( + context, + info, + maxSize, + widget.tooltipBehavior, + _chartThemeData, + _themeData, + ); + } + + void _buildTrackballWidget(List details) { + final TrackballBehavior trackballBehavior = widget.trackballBehavior!; + final List chartPointInfo = + trackballBehavior.chartPointInfo; + if (details.isEmpty || trackballBehavior.builder == null) { + _trackballBuilder = const SizedBox(width: 0, height: 0); + } else if (details.isNotEmpty && + trackballBehavior.builder != null && + chartPointInfo.isNotEmpty) { + _trackballBuilder = Stack( + children: + List.generate(details.length, (int index) { + final ChartPointInfo info = chartPointInfo[index]; + final Widget builder = trackballBehavior.builder!.call( + context, + details[index], + ); + return TrackballBuilderRenderObjectWidget( + index: index, + xPos: info.xPosition!, + yPos: info.yPosition!, + builder: builder, + chartPointInfo: chartPointInfo, + trackballBehavior: trackballBehavior, + child: builder, + ); + }).toList(), + ); + } + final RenderObjectElement? trackballBuilderElement = + _trackballBuilderKey.currentContext as RenderObjectElement?; + if (trackballBuilderElement != null && + trackballBuilderElement.mounted && + trackballBuilderElement.renderObject.attached) { + final RenderObject? renderObject = + trackballBuilderElement.findRenderObject(); + if (renderObject != null && + renderObject.attached && + renderObject is CustomRenderConstrainedLayoutBuilder) { + renderObject.markNeedsBuild(); + } + } + } + + void _handleSwipe(ChartSwipeDirection direction) { + _swipeDirection = direction; + widget.onPlotAreaSwipe?.call(direction); + } + + Widget _buildLoadingIndicator( + BuildContext context, + BoxConstraints constraints, + ) { + if (widget.loadMoreIndicatorBuilder != null && _swipeDirection != null) { + return widget.loadMoreIndicatorBuilder!(context, _swipeDirection!); + } + return const SizedBox(width: 0, height: 0); + } + + @override + void initState() { + _legendKey = GlobalKey(); + _tooltipKey = GlobalKey(); + _trackballBuilderKey = GlobalKey(); + _collectAnnotationWidgets(); + super.initState(); + } + + @override + void didUpdateWidget(SfCartesianChart oldWidget) { + if (oldWidget.annotations != widget.annotations) { + _collectAnnotationWidgets(); + } + super.didUpdateWidget(oldWidget); + } + + @override + void didChangeDependencies() { + _localizations = SfLocalizations.of(context); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + _themeData = Theme.of(context); + final ChartThemeData effectiveChartThemeData = ChartThemeData(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); + bool isTransposed = widget.isTransposed; + if (widget.series.isNotEmpty && widget.series[0].transposed()) { + isTransposed = !isTransposed; + } + + final core.LegendPosition legendPosition = effectiveLegendPosition( + widget.legend, + ); + final Axis orientation = effectiveLegendOrientation( + legendPosition, + widget.legend, + ); + Widget current = core.LegendLayout( + key: _legendKey, + padding: EdgeInsets.zero, + showLegend: widget.legend.isVisible, + legendPosition: legendPosition, + legendAlignment: effectiveLegendAlignment(widget.legend.alignment), + legendTitleAlignment: effectiveLegendAlignment( + widget.legend.title?.alignment, + ), + itemIconBorderColor: widget.legend.iconBorderColor, + itemIconBorderWidth: widget.legend.iconBorderWidth, + legendBorderColor: widget.legend.borderColor, + legendBackgroundColor: _chartThemeData.legendBackgroundColor, + legendBorderWidth: widget.legend.borderWidth, + itemOpacity: widget.legend.opacity, + legendWidthFactor: percentageToWidthFactor( + widget.legend.width, + legendPosition, + ), + legendHeightFactor: percentageToHeightFactor( + widget.legend.height, + legendPosition, + ), + itemInnerSpacing: widget.legend.padding, + itemSpacing: 0.0, + itemPadding: widget.legend.itemPadding, + itemRunSpacing: 0.0, + itemIconHeight: widget.legend.iconHeight, + itemIconWidth: widget.legend.iconWidth, + scrollbarVisibility: effectiveScrollbarVisibility(widget.legend), + enableToggling: widget.legend.toggleSeriesVisibility, + itemTextStyle: _chartThemeData.legendTextStyle!, + isResponsive: widget.legend.isResponsive, + legendWrapDirection: orientation, + legendTitle: buildLegendTitle(_chartThemeData, widget.legend), + legendOverflowMode: effectiveOverflowMode(widget.legend), + legendItemBuilder: + widget.legend.legendItemBuilder != null ? _buildLegendItem : null, + legendFloatingOffset: widget.legend.offset, + toggledTextOpacity: 0.2, + child: CartesianChartArea( + legendKey: _legendKey, + legendItems: _legendItems, + isTransposed: isTransposed, + crosshairBehavior: widget.crosshairBehavior, + trackballBehavior: widget.trackballBehavior, + zoomPanBehavior: widget.zoomPanBehavior, + hasLoadingIndicator: + widget.loadMoreIndicatorBuilder != null || + widget.onPlotAreaSwipe != null, + onChartTouchInteractionDown: widget.onChartTouchInteractionDown, + onChartTouchInteractionMove: widget.onChartTouchInteractionMove, + onChartTouchInteractionUp: widget.onChartTouchInteractionUp, + plotAreaBackgroundImage: widget.plotAreaBackgroundImage, + plotAreaBackgroundColor: _chartThemeData.plotAreaBackgroundColor, + children: [ + CartesianChartPlotArea( + vsync: this, + localizations: _localizations, + legendKey: _legendKey, + isTransposed: isTransposed, + backgroundColor: null, + borderColor: _chartThemeData.plotAreaBorderColor, + borderWidth: widget.plotAreaBorderWidth, + enableAxisAnimation: widget.enableAxisAnimation, + enableSideBySideSeriesPlacement: + widget.enableSideBySideSeriesPlacement, + legend: widget.legend, + onLegendItemRender: widget.onLegendItemRender, + onLegendTapped: widget.onLegendTapped, + onDataLabelRender: widget.onDataLabelRender, + onDataLabelTapped: widget.onDataLabelTapped, + onMarkerRender: widget.onMarkerRender, + onTooltipRender: widget.onTooltipRender, + palette: widget.palette ?? effectiveChartThemeData.palette, + selectionMode: widget.selectionType, + selectionGesture: widget.selectionGesture, + enableMultiSelection: widget.enableMultiSelection, + tooltipBehavior: widget.tooltipBehavior, + crosshairBehavior: widget.crosshairBehavior, + trackballBehavior: widget.trackballBehavior, + zoomPanBehavior: widget.zoomPanBehavior, + onSelectionChanged: widget.onSelectionChanged, + chartThemeData: _chartThemeData, + themeData: _themeData, + children: widget.series, + ), + CartesianAxes( + vsync: this, + enableAxisAnimation: widget.enableAxisAnimation, + isTransposed: isTransposed, + onAxisLabelTapped: widget.onAxisLabelTapped, + onActualRangeChanged: widget.onActualRangeChanged, + indicators: widget.indicators, + chartThemeData: _chartThemeData, + children: [ + widget.primaryXAxis, + widget.primaryYAxis, + ...widget.axes, + ], + ), + if (widget.indicators.isNotEmpty) + IndicatorStack( + vsync: this, + isTransposed: isTransposed, + indicators: widget.indicators, + onLegendItemRender: widget.onLegendItemRender, + onLegendTapped: widget.onLegendTapped, + trackballBehavior: widget.trackballBehavior, + textDirection: Directionality.of(context), + ), + if (widget.annotations != null && + widget.annotations!.isNotEmpty && + _annotations != null && + _annotations!.isNotEmpty) + AnnotationArea( + isTransposed: isTransposed, + annotations: widget.annotations, + children: _annotations!, + ), + BehaviorArea( + tooltipKey: _tooltipKey, + primaryXAxisName: + widget.primaryXAxis.name ?? primaryXAxisDefaultName, + primaryYAxisName: + widget.primaryYAxis.name ?? primaryYAxisDefaultName, + isTransposed: isTransposed, + tooltipBehavior: widget.tooltipBehavior, + crosshairBehavior: widget.crosshairBehavior, + zoomPanBehavior: widget.zoomPanBehavior, + onZooming: widget.onZooming, + onZoomStart: widget.onZoomStart, + onZoomEnd: widget.onZoomEnd, + onZoomReset: widget.onZoomReset, + textDirection: Directionality.of(context), + trackballBehavior: widget.trackballBehavior, + chartThemeData: _chartThemeData, + themeData: _themeData, + trackballBuilder: _buildTrackballWidget, + onTrackballPositionChanging: widget.onTrackballPositionChanging, + onCrosshairPositionChanging: widget.onCrosshairPositionChanging, + onTooltipRender: widget.onTooltipRender, + children: [ + if (widget.trackballBehavior != null && + widget.trackballBehavior!.enable && + widget.trackballBehavior!.builder != null) + TrackballBuilderOpacityWidget( + opacity: 1.0, + child: CustomLayoutBuilder( + key: _trackballBuilderKey, + builder: ( + BuildContext context, + BoxConstraints constraints, + ) { + return _trackballBuilder ?? const SizedBox(height: 0); + }, + ), + ), + if (widget.loadMoreIndicatorBuilder != null || + widget.onPlotAreaSwipe != null) + LoadingIndicator( + isTransposed: widget.isTransposed, + isInversed: widget.primaryXAxis.isInversed, + onSwipe: _handleSwipe, + builder: _buildLoadingIndicator, + ), + if (widget.tooltipBehavior != null && + widget.tooltipBehavior!.enable) + CoreTooltip( + key: _tooltipKey, + builder: _buildTooltipWidget, + opacity: widget.tooltipBehavior!.opacity, + borderColor: widget.tooltipBehavior!.borderColor, + borderWidth: widget.tooltipBehavior!.borderWidth, + color: + (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, + showDuration: widget.tooltipBehavior!.duration.toInt(), + shadowColor: widget.tooltipBehavior!.shadowColor, + elevation: widget.tooltipBehavior!.elevation, + animationDuration: widget.tooltipBehavior!.animationDuration, + shouldAlwaysShow: widget.tooltipBehavior!.shouldAlwaysShow, + ), + ], + ), + ], + ), + ); + + current = buildChartWithTitle(current, widget.title, _chartThemeData); + + return RepaintBoundary( + child: Container( + decoration: BoxDecoration( + color: _chartThemeData.backgroundColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: Padding( + padding: widget.margin.resolve(Directionality.maybeOf(context)), + child: current, + ), + ), + ); + } + + /// Method to convert the [SfCartesianChart] 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` + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [SfCartesianChart( + /// key: _key + /// series: >[ + /// AreaSeries( + /// dataSource: chartData, + /// ), + /// ], + /// ), + /// RaisedButton( + /// child: Text( + /// 'To Image', + /// ), + /// onPressed: _renderImage, + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20)), + /// ) + /// ], + /// ), + /// ); + /// } + + /// Future _renderImage() async { + /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); + /// final bytes = await data.toByteData(format: dart_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.white, + /// child: Image.memory(bytes.buffer.asUint8List()), + /// ), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// } + /// ``` + Future toImage({double pixelRatio = 1.0}) async { + final RenderRepaintBoundary? boundary = + context.findRenderObject() as RenderRepaintBoundary?; + if (boundary != null) { + final Image image = await boundary.toImage(pixelRatio: pixelRatio); + return image; + } + + return null; + } + + @override + void dispose() { + _annotations?.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart new file mode 100644 index 000000000..0eaac161d --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart @@ -0,0 +1,1000 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'base.dart'; +import 'common/annotation.dart'; +import 'common/callbacks.dart'; +import 'common/core_legend.dart' as core; +import 'common/core_tooltip.dart'; +import 'common/legend.dart'; +import 'common/title.dart'; +import 'interactions/behavior.dart'; +import 'interactions/tooltip.dart'; +import 'series/chart_series.dart'; +import 'theme.dart'; +import 'utils/enum.dart'; +import 'utils/helper.dart'; +import 'utils/typedef.dart'; + +/// Renders the circular chart. +/// +/// The SfCircularChart widget supports pie, doughnut, and radial bar series +/// that can be customized within the circular chart's class. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Center( +/// child:SfCircularChart( +/// title: ChartTitle(text: 'Sales by sales person'), +/// legend: Legend(isVisible: true), +/// series: >[ +/// PieSeries<_PieData, String>( +/// explode: true, +/// explodeIndex: 0, +/// dataSource: pieData, +/// xValueMapper: (_PieData data, _) => data.xData, +/// yValueMapper: (_PieData data, _) => data.yData, +/// dataLabelMapper: (_PieData data, _) => data.text, +/// dataLabelSettings: DataLabelSettings(isVisible: true)), +/// ] +/// ) +/// ); +/// } +/// +/// class _PieData { +/// _PieData(this.xData, this.yData, [this.text]); +/// final String xData; +/// final num yData; +/// String? text; +/// } +/// ``` +//ignore:must_be_immutable +class SfCircularChart extends StatefulWidget { + /// Creating an argument constructor of SfCircularChart class. + const SfCircularChart({ + Key? key, + this.backgroundColor, + this.backgroundImage, + this.annotations, + this.borderColor = Colors.transparent, + this.borderWidth = 0.0, + this.onLegendItemRender, + this.onTooltipRender, + this.onDataLabelRender, + this.onDataLabelTapped, + this.onLegendTapped, + this.onSelectionChanged, + this.onChartTouchInteractionUp, + this.onChartTouchInteractionDown, + this.onChartTouchInteractionMove, + this.onCreateShader, + this.palette, + this.margin = const EdgeInsets.fromLTRB(10, 10, 10, 10), + this.series = const [], + this.title = const ChartTitle(), + this.legend = const Legend(), + this.centerX = '50%', + this.centerY = '50%', + this.tooltipBehavior, + this.selectionGesture = ActivationMode.singleTap, + this.enableMultiSelection = false, + }) : super(key: key); + + /// Customizes the chart title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// title: ChartTitle(text: 'Default rendering') + /// ) + /// ); + /// } + /// ``` + final ChartTitle title; + + /// Customizes the chart series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataSource: chartData, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final List series; + + /// Specifies the margin for circular chart. + /// + /// Defaults to `const EdgeInsets.fromLTRB(10, 10, 10, 10)`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// margin: const EdgeInsets.all(2) + /// ) + /// ); + /// } + /// ``` + final EdgeInsets margin; + + /// Customizes the legend in the chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// legend: Legend(isVisible: true) + /// ) + /// ); + /// } + /// ``` + final Legend legend; + + /// Customizes the tooltip in chart. + /// + /// ```dart + /// late TooltipBehavior _tooltipBehavior; + /// + /// @override + /// void initState() { + /// _tooltipBehavior = TooltipBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// tooltipBehavior: _tooltipBehavior + /// ) + /// ); + /// } + /// ``` + final TooltipBehavior? tooltipBehavior; + + /// Background color of the chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// backgroundColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + 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. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// angle: 200, + /// radius: '80%', + /// widget: const Text('Circular Chart') + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final List? annotations; + + /// Border color of the chart. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// borderColor: Colors.red + /// ) + /// ); + /// } + /// ``` + final Color borderColor; + + /// Border width of the chart. + /// + /// Defaults to `0.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// borderWidth: 2 + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Background image for chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// backgroundImage: const AssetImage('image.png'), + /// ) + /// ); + /// } + /// ``` + final ImageProvider? backgroundImage; + + /// X value for placing the chart. The value ranges from 0% to 100% and also + /// if values are mentioned in pixel then the chart will moved accordingly. + /// + /// For example, if set `50%` means the x value place center of the chart area + /// or we set `50` means the x value placed 50 pixel. + /// + /// Defaults to `50%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// centerX: '50%' + /// ) + /// ); + /// } + /// ``` + final String centerX; + + /// Y value for placing the chart. The value ranges from 0% to 100% and also + /// if values are mentioned in pixel then the chart will moved accordingly. + /// + /// For example, if set `50%` means the y value place center of the chart + /// area or we set `50` means the y value placed 50 pixel. + /// + /// Defaults to `50%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// centerY: '50%' + /// ) + /// ); + /// } + /// ``` + final String centerY; + + /// Occurs while legend is rendered. Here, you can get the legend's text, + /// shape, series index, and point index case of circular series. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// legend: Legend(isVisible: true), + /// onLegendItemRender: (LegendRendererArgs args) => legend(args), + /// ) + /// ); + /// } + /// void legend(LegendRendererArgs args) { + /// args.legendIconType = LegendIconType.diamond; + /// } + /// ``` + 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. + /// ```dart + /// late TooltipBehavior _tooltipBehavior; + /// + /// @override + /// void initState() { + /// _tooltipBehavior = TooltipBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// tooltipBehavior: _tooltipBehavior, + /// onTooltipRender: (TooltipArgs args) => tool(args) + /// ) + /// ); + /// } + /// void tool(TooltipArgs args) { + /// args.locationX = 30; + /// } + /// ``` + 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 index, text, and text style. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onDataLabelRender: (DataLabelRenderArgs args) => dataLabel(args), + /// series: >[ + /// PieSeries( + /// dataLabelSettings: DataLabelSettings(isVisible: true), + /// ), + /// ], + /// ), + /// ); + /// } + /// void dataLabel(DataLabelRenderArgs args) { + /// args.text = 'dataLabel'; + /// } + /// ``` + final CircularDataLabelRenderCallback? onDataLabelRender; + + /// 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 + /// final List colors = [ + /// const Color.fromRGBO(75, 135, 185, 1), + /// const Color.fromRGBO(192, 108, 132, 1), + /// const Color.fromRGBO(246, 114, 128, 1), + /// const Color.fromRGBO(248, 177, 149, 1), + /// const Color.fromRGBO(116, 180, 155, 1) + /// ]; + /// final List stops = [ + /// 0.2, + /// 0.4, + /// 0.6, + /// 0.8, + /// 1, + /// ]; + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onCreateShader: (ChartShaderDetails chartShaderDetails) { + /// return ui.Gradient.linear( + /// chartShaderDetails.outerRect.topRight, + /// chartShaderDetails.outerRect.centerLeft, + /// colors, + /// stops); + /// }, + /// ) + /// ); + /// } + /// ``` + final CircularShaderCallback? onCreateShader; + + /// Called when the data label is tapped. + /// + /// 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. + /// + /// _Note:_ This callback will not be called, when the builder is specified + /// for data label (data label template). For this case, custom widget + /// specified in the `DataLabelSettings.builder` property can be wrapped using + /// the `GestureDetector` and this functionality can be achieved + /// in the application level. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onDataLabelTapped: (DataLabelTapDetails args) { + /// print(arg.seriesIndex); + /// } + /// ) + /// ); + /// } + /// ``` + + final DataLabelTapCallback? onDataLabelTapped; + + /// Occurs when the legend is tapped. Here, you can get the series, + /// series index, and point index. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onLegendTapped: (LegendTapArgs args) => legend(args), + /// legend: Legend(isVisible: true), + /// ) + /// ); + /// } + /// void legend(LegendTapArgs args) { + /// print(args.pointIndex); + /// } + /// ``` + 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 width, unselected border width, series index, and + /// point index. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onSelectionChanged: (SelectionArgs args) => select(args), + /// ) + /// ); + /// } + /// void select(SelectionArgs args) { + /// print(args.selectedBorderColor); + /// } + /// ``` + final CircularSelectionCallback? onSelectionChanged; + + /// Occurs when tapped on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final CircularTouchInteractionCallback? onChartTouchInteractionUp; + + /// Occurs when touched on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + /// + final CircularTouchInteractionCallback? onChartTouchInteractionDown; + + /// Occurs when touched and moved on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + 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. + /// Ten colors are available by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// palette: [Colors.red, Colors.green] + /// ) + /// ); + /// } + /// ``` + final List? palette; + + /// Gesture for activating the selection. + /// + /// Selection can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, + /// `ActivationMode.doubleTap`, and + /// `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// selectionGesture: ActivationMode.singleTap, + /// series: >[ + /// PieSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final ActivationMode selectionGesture; + + /// Enables or disables the multiple data points or series selection. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// enableMultiSelection: true, + /// series: >[ + /// PieSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool enableMultiSelection; + + @override + State createState() => SfCircularChartState(); +} + +/// Represents the state class of [SfCircularChart] widget. +/// +class SfCircularChartState extends State + with TickerProviderStateMixin { + final List _legendItems = []; + + late GlobalKey _legendKey; + late GlobalKey _tooltipKey; + late SfChartThemeData _chartThemeData; + late ThemeData _themeData; + SfLocalizations? _localizations; + List? _annotations; + + SfChartThemeData _updateThemeData( + BuildContext context, + ChartThemeData effectiveChartThemeData, + ) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + backgroundColor: + widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: + widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, + legendBackgroundColor: + widget.legend.backgroundColor ?? + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + tooltipColor: + widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + plotAreaBackgroundColor: + chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: + chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor, + ) + .merge(chartThemeData.titleTextStyle) + .merge(widget.title.textStyle), + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: + chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor, + ) + .merge(chartThemeData.legendTitleTextStyle) + .merge(widget.legend.title?.textStyle), + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: + chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor, + ) + .merge(chartThemeData.legendTextStyle) + .merge(widget.legend.textStyle), + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.tooltipTextStyle) + .merge(widget.tooltipBehavior?.textStyle), + ); + return chartThemeData; + } + + Widget _buildLegendItem(BuildContext context, int index) { + return buildLegendItem(context, _legendItems[index], widget.legend); + } + + Widget? _buildTooltipWidget( + BuildContext context, + TooltipInfo? info, + Size maxSize, + ) { + return buildTooltipWidget( + context, + info, + maxSize, + widget.tooltipBehavior, + _chartThemeData, + _themeData, + ); + } + + // ignore: unused_element + void _handleTouchDown(Offset localPosition) { + widget.onChartTouchInteractionDown?.call( + ChartTouchInteractionArgs()..position = localPosition, + ); + } + + // ignore: unused_element + void _handleTouchMove(Offset localPosition) { + widget.onChartTouchInteractionMove?.call( + ChartTouchInteractionArgs()..position = localPosition, + ); + } + + // ignore: unused_element + void _handleTouchUp(Offset localPosition) { + widget.onChartTouchInteractionUp?.call( + ChartTouchInteractionArgs()..position = localPosition, + ); + } + + /// 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() { + _legendKey = GlobalKey(); + _tooltipKey = GlobalKey(); + 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() { + _localizations = SfLocalizations.of(context); + if (widget.annotations != null) { + _collectAnnotationWidgets(); + } + super.didChangeDependencies(); + } + + void _collectAnnotationWidgets() { + if (widget.annotations != null) { + if (_annotations == null) { + _annotations = []; + } else { + _annotations!.clear(); + } + + for (final CircularChartAnnotation annotation in widget.annotations!) { + _annotations!.add(annotation.widget); + } + } + } + + /// 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) { + if (oldWidget.annotations != widget.annotations) { + _collectAnnotationWidgets(); + } + 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) { + _themeData = Theme.of(context); + final ChartThemeData effectiveChartThemeData = ChartThemeData(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); + final core.LegendPosition legendPosition = effectiveLegendPosition( + widget.legend, + ); + final Axis orientation = effectiveLegendOrientation( + legendPosition, + widget.legend, + ); + Widget current = core.LegendLayout( + key: _legendKey, + padding: EdgeInsets.zero, + showLegend: widget.legend.isVisible, + legendPosition: legendPosition, + legendAlignment: effectiveLegendAlignment(widget.legend.alignment), + legendTitleAlignment: effectiveLegendAlignment( + widget.legend.title?.alignment, + ), + itemIconBorderColor: widget.legend.iconBorderColor, + itemIconBorderWidth: widget.legend.iconBorderWidth, + legendBorderColor: widget.legend.borderColor, + legendBackgroundColor: _chartThemeData.legendBackgroundColor, + legendBorderWidth: widget.legend.borderWidth, + itemOpacity: widget.legend.opacity, + legendWidthFactor: percentageToWidthFactor( + widget.legend.width, + legendPosition, + ), + legendHeightFactor: percentageToHeightFactor( + widget.legend.height, + legendPosition, + ), + itemInnerSpacing: widget.legend.padding, + itemSpacing: 0.0, + itemPadding: widget.legend.itemPadding, + itemRunSpacing: 0.0, + itemIconHeight: widget.legend.iconHeight, + itemIconWidth: widget.legend.iconWidth, + scrollbarVisibility: effectiveScrollbarVisibility(widget.legend), + enableToggling: widget.legend.toggleSeriesVisibility, + itemTextStyle: _chartThemeData.legendTextStyle!, + isResponsive: widget.legend.isResponsive, + legendWrapDirection: orientation, + legendTitle: buildLegendTitle(_chartThemeData, widget.legend), + legendOverflowMode: effectiveOverflowMode(widget.legend), + legendItemBuilder: + widget.legend.legendItemBuilder != null ? _buildLegendItem : null, + legendFloatingOffset: widget.legend.offset, + child: ChartArea( + legendKey: _legendKey, + legendItems: _legendItems, + onChartTouchInteractionDown: widget.onChartTouchInteractionDown, + onChartTouchInteractionMove: widget.onChartTouchInteractionMove, + onChartTouchInteractionUp: widget.onChartTouchInteractionUp, + children: [ + CircularChartPlotArea( + vsync: this, + localizations: _localizations, + legendKey: _legendKey, + palette: widget.palette ?? effectiveChartThemeData.palette, + chartThemeData: _chartThemeData, + themeData: _themeData, + backgroundColor: null, + borderWidth: widget.borderWidth, + legend: widget.legend, + onLegendItemRender: widget.onLegendItemRender, + onDataLabelRender: widget.onDataLabelRender, + onLegendTapped: widget.onLegendTapped, + onTooltipRender: widget.onTooltipRender, + onDataLabelTapped: widget.onDataLabelTapped, + selectionMode: SelectionType.point, + selectionGesture: widget.selectionGesture, + enableMultiSelection: widget.enableMultiSelection, + tooltipBehavior: widget.tooltipBehavior, + centerX: widget.centerX, + centerY: widget.centerY, + onCreateShader: widget.onCreateShader, + onSelectionChanged: widget.onSelectionChanged, + children: widget.series, + ), + if (widget.annotations != null && + widget.annotations!.isNotEmpty && + _annotations != null && + _annotations!.isNotEmpty) + CircularAnnotationArea( + annotations: widget.annotations, + children: _annotations!, + ), + if (widget.tooltipBehavior != null) + BehaviorArea( + tooltipKey: _tooltipKey, + chartThemeData: _chartThemeData, + themeData: _themeData, + tooltipBehavior: widget.tooltipBehavior, + onTooltipRender: widget.onTooltipRender, + children: [ + if (widget.tooltipBehavior != null && + widget.tooltipBehavior!.enable) + CoreTooltip( + key: _tooltipKey, + builder: _buildTooltipWidget, + opacity: widget.tooltipBehavior!.opacity, + borderColor: widget.tooltipBehavior!.borderColor, + borderWidth: widget.tooltipBehavior!.borderWidth, + color: + (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, + showDuration: widget.tooltipBehavior!.duration.toInt(), + shadowColor: widget.tooltipBehavior!.shadowColor, + elevation: widget.tooltipBehavior!.elevation, + animationDuration: + widget.tooltipBehavior!.animationDuration, + shouldAlwaysShow: widget.tooltipBehavior!.shouldAlwaysShow, + ), + ], + ), + ], + ), + ); + + current = buildChartWithTitle(current, widget.title, _chartThemeData); + + return RepaintBoundary( + child: Container( + decoration: BoxDecoration( + image: + widget.backgroundImage != null + ? DecorationImage( + image: widget.backgroundImage!, + fit: BoxFit.fill, + ) + : null, + color: _chartThemeData.backgroundColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: Padding( + padding: widget.margin.resolve(Directionality.maybeOf(context)), + child: current, + ), + ), + ); + } + + /// 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`. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [SfCircularChart( + /// key: _key + /// series: >[ + /// PieSeries( + /// enableTooltip: true, + /// ), + /// ], + /// ), + /// RaisedButton( + /// child: Text( + /// 'To Image', + /// ), + /// onPressed: _renderImage, + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20)), + /// ) + /// ], + /// ), + /// ); + /// } + /// + /// Future _renderImage() async { + /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); + /// final bytes = await data.toByteData(format: dart_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.white, + /// child: Image.memory(bytes.buffer.asUint8List()), + /// ), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// } + /// ``` + Future toImage({double pixelRatio = 1.0}) async { + final RenderRepaintBoundary? boundary = + context.findRenderObject() as RenderRepaintBoundary?; + if (boundary != null) { + final Image image = await boundary.toImage(pixelRatio: pixelRatio); + return image; + } + + return null; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/annotation.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/annotation.dart new file mode 100644 index 000000000..f04d5511d --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/annotation.dart @@ -0,0 +1,551 @@ +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; + +/// This class has the properties of cartesian chart annotation. +/// +/// Chart supports annotations that allow you to mark the specific area of +/// interest in the chart area. You can add the custom widgets using this +/// annotation feature as depicted below. +/// +/// The x and y values can be specified with axis units or pixel units, and +/// these can be identified by using coordinateUnit property. When the logical +/// pixel is specified, the annotation will be placed to pixel values whereas +/// the point is specified, then the annotation will be placed to series +/// point values. +/// +/// Provides the options x, y, coordinateUnit, and widget to customize the +/// cartesian chart annotation. +@immutable +class CartesianChartAnnotation { + /// Creating an argument constructor of [CartesianChartAnnotation] class. + const CartesianChartAnnotation({ + required this.widget, + required this.x, + required this.y, + this.coordinateUnit = CoordinateUnit.logicalPixel, + this.xAxisName, + this.yAxisName, + this.region = AnnotationRegion.chart, + this.clip = ChartClipBehavior.clip, + this.horizontalAlignment = ChartAlignment.center, + this.verticalAlignment = ChartAlignment.center, + }); + + /// Considers any widget as annotation. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// coordinateUnit: CoordinateUnit.point, + /// region: AnnotationRegion.chart, + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final Widget widget; + + /// Specifies the coordinate units for placing the annotation in either + /// logicalPixel or point. + /// + /// Defaults to `CoordinateUnit.logicalPixel` + /// + /// Also refer [CoordinateUnit]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// coordinateUnit: CoordinateUnit.point, + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final CoordinateUnit coordinateUnit; + + /// Annotations can be placed with respect to either plotArea or chart. + /// + /// Defaults to `AnnotationRegion.chart`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// region: AnnotationRegion.chart, + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final AnnotationRegion region; + + /// Clip or hide the annotation when it exceeds the chart area or plot area + /// based on [region] provided. + /// + /// Defaults to `ChartClipBehavior.clip`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// clip: ChartClipBehavior.hide, + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final ChartClipBehavior clip; + + /// Specifies the x-values as pixel, point or percentage values based on the + /// coordinateUnit. + /// + /// Percentage value refers to the overall width of the chart. i.e. 100% is + /// equal to the width of the chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final dynamic x; + + /// Specifies the y-values as pixel, point or percentage values based on the + /// coordinateUnit. + /// + /// Percentage value refers to the overall height of the chart. i.e. 100% is + /// equal to the height of the chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// x: 3, + /// y: 60 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final dynamic y; + + /// Specifies the x-axis name to the annotation that should be bound. + /// + /// Defaults to `‘’`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// xAxisName: 'Gold' + /// ), + /// ], + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// x: 3, + /// y: 60, + /// xAxisName: 'Gold' + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String? xAxisName; + + /// Specifies the y-axis name to the annotation that should be bound. + /// + /// Defaults to `‘’`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// yAxisName: 'Gold' + /// ), + /// ], + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// x: 3, + /// y: 60, + /// yAxisName: 'Gold' + /// ), + /// ) + /// ); + /// } + /// ``` + final String? yAxisName; + + /// Aligns the annotations horizontally. + /// + /// Alignment can be set to `ChartAlignment.near`, `ChartAlignment.far`, or + /// `ChartAlignment.center`. + /// + /// Defaults to `ChartAlignment.center` + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// x: 3, + /// y: 60, + /// horizontalAlignment: ChartAlignment.near + /// ) + /// ], + /// ) + /// ); + /// } + /// ``` + final ChartAlignment horizontalAlignment; + + /// Aligns the annotations vertically. + /// + /// Alignment can be set to `ChartAlignment.near`, `ChartAlignment.far`, or + /// `ChartAlignment.center`. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// annotations: [ + /// CartesianChartAnnotation( + /// widget: Container( + /// child: const Text('Annotation')), + /// x: 3, + /// y: 60, + /// verticalAlignment: ChartAlignment.near + /// ) + /// ], + /// ) + /// ); + /// } + /// ``` + final ChartAlignment verticalAlignment; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is CartesianChartAnnotation && + other.widget == widget && + other.coordinateUnit == coordinateUnit && + other.region == region && + other.clip == clip && + other.horizontalAlignment == horizontalAlignment && + other.verticalAlignment == verticalAlignment && + other.x == x && + other.y == y && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName; + } + + @override + int get hashCode { + final List values = [ + widget, + coordinateUnit, + region, + clip, + horizontalAlignment, + verticalAlignment, + x, + y, + xAxisName, + yAxisName, + ]; + return Object.hashAll(values); + } +} + +/// Customizes the annotation of the circular chart. +/// +/// Circular chart allows you to mark the specific area of interest in the +/// chart area. You can add the custom widgets using this annotation feature, +/// It has the properties for customizing the appearance. +/// +/// The angle, orientation, height, and width of the inserted annotation can all +/// be customized. +/// +/// It provides options for angle, height, width, vertical and horizontal +/// alignment to customize the appearance. +@immutable +class CircularChartAnnotation { + /// Creating an argument constructor of CircularChartAnnotation class. + const CircularChartAnnotation({ + this.angle = 0, + this.radius = '0%', + required this.widget, + this.height = '0%', + this.width = '0%', + this.horizontalAlignment = ChartAlignment.center, + this.verticalAlignment = ChartAlignment.center, + }); + + /// Angle to rotate the annotation. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// angle: 40, + /// widget: const Text('Circular'), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final int angle; + + /// Radius for placing the annotation. The value ranges from 0% to 100% and + /// also if values are + /// mentioned in pixel then the annotation will moved accordingly. + /// + /// For example, if set `50%` means the annotation starting text will be + /// placed from the `50%` + /// chart area or we set `50` means the annotation starting text will be + /// placed from 50 pixels. + /// + /// Defaults to `0%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// radius: '10%' + /// widget: const Text('Circular'), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String radius; + + /// Considers any widget as annotation. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// widget: const Text('Circular'), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final Widget widget; + + /// Height of the annotation. + /// + /// Defaults to `0%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// height: '10%', + /// widget: const Text('Circular'), + /// ), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String height; + + /// Width of the annotation. + /// + /// Defaults to `0%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// width: '10%', + /// widget: const Text('Circular'), + /// ), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String width; + + /// Aligns the annotation horizontally. + /// + /// Alignment can be set to `ChartAlignment.center`, `ChartAlignment.far`, or + /// `ChartAlignment.near`. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// horizontalAlignment: ChartAlignment.near + /// widget: const Text('Circular'), + /// ), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final ChartAlignment horizontalAlignment; + + /// Aligns the annotation vertically. + /// + /// Alignment can be set to `ChartAlignment.center`, `ChartAlignment.far`, or + /// `ChartAlignment.near`. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// verticalAlignment: ChartAlignment.near + /// widget: const Text('Circular'), + /// ), + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final ChartAlignment verticalAlignment; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is CircularChartAnnotation && + other.widget == widget && + other.radius == radius && + other.angle == angle && + other.horizontalAlignment == horizontalAlignment && + other.verticalAlignment == verticalAlignment && + other.height == height && + other.width == width; + } + + @override + int get hashCode { + final List values = [ + widget, + radius, + angle, + horizontalAlignment, + verticalAlignment, + height, + width, + ]; + return Object.hashAll(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/callbacks.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/callbacks.dart new file mode 100644 index 000000000..e8d879604 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/callbacks.dart @@ -0,0 +1,975 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart' show DataMarkerType; + +import '../axis/axis.dart'; +import '../common/data_label.dart'; +import '../indicators/technical_indicator.dart'; +import '../utils/enum.dart'; +import 'chart_point.dart'; + +/// Holds the arguments for the event onTooltipRender. +/// +/// Event is triggered when the tooltip is rendered, which allows you to +/// customize tooltip arguments. +class TooltipArgs { + /// Creating an argument constructor of TooltipArgs class. + TooltipArgs([ + this.seriesIndex, + this.dataPoints, + this.viewportPointIndex, + this.pointIndex, + ]); + + /// Get and set the tooltip text. + String? text; + + /// Get and set the header text of the tooltip. + String? header; + + /// Get and set the x location of the tooltip. + double? locationX; + + /// Get and set the y location of the tooltip. + double? locationY; + + /// Get the index of the current series. + final dynamic seriesIndex; + + /// Get the list of data points in the series. + final List? dataPoints; + + /// Get the overall index value of the tooltip. + final num? pointIndex; + + /// Get the view port index value of the tooltip. + final num? viewportPointIndex; +} + +/// Holds the `onActualRangeChanged` event arguments. +/// +/// 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 { + /// Creating an argument constructor of ActualRangeChangedArgs class. + ActualRangeChangedArgs([ + this.axisName, + this.axis, + this.actualMin, + this.actualMax, + this.actualInterval, + this.orientation, + ]); + + /// Get the name of the axis. + final String? axisName; + + /// Get the axis type. + final ChartAxis? axis; + + /// Get the actual minimum range of an axis. + final dynamic actualMin; + + /// Get the actual maximum range of an axis. + final dynamic actualMax; + + /// Get the actual interval of an axis. + final dynamic actualInterval; + + /// Get and set the minimum visible range for an axis. + dynamic visibleMin; + + /// Get and set the maximum visible range for an axis. + dynamic visibleMax; + + /// Get and set the interval for the visible range for an axis. + dynamic visibleInterval; + + /// Get the orientation of an axis. + final AxisOrientation? orientation; +} + +/// 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 event. +class AxisLabelRenderDetails { + /// Creating an argument constructor of AxisLabelRenderDetails class. + AxisLabelRenderDetails( + this.value, + this.text, + this.textStyle, + this.axis, + this.currentDateTimeIntervalType, + this.currentDateFormat, + ); + + /// Actual text value of the axis label. + final String text; + + /// Get the value of the axis label. + final num value; + + /// Get the chart axis type and its properties. + final RenderChartAxis axis; + + /// Get the text style of an axis label. + final TextStyle textStyle; + + /// Specifies the date time interval type calculated internally for + /// the date-time values that are displayed on the axis. + /// + /// _Note:_ This is applicable for DateTimeAxis and DateTimeCategoryAxis. + final DateTimeIntervalType? currentDateTimeIntervalType; + + /// Specifies the date format calculated internally for the current date-time + /// values that are displayed on the axis. + /// + /// _Note:_ This is applicable for DateTimeAxis and DateTimeCategoryAxis. + final String? currentDateFormat; +} + +/// Holds multi-level label text, name of the axis, index, actual level of the +/// multi-level label, text style such as color, font size, etc arguments for +/// multi-level label formatter callback. +/// +/// The value in the `index` will be obtained as per the order of the labels +/// specified in the `multiLevelLabels` property irrespective of the value +/// specified in the `level` property. +/// +/// The level obtained in the `actualLevel`property is the re-ordered level +/// irrespective of the value specified in the `level` property. +class MultiLevelLabelRenderDetails { + /// Creating an argument constructor of MultiLevelLabelRenderDetails class. + MultiLevelLabelRenderDetails( + this.actualLevel, + this.text, + this.textStyle, + this.index, + this.axisName, + ); + + /// Get the multi-level label text. + final String text; + + /// Get the text style of the multi-level label. + final TextStyle textStyle; + + /// Get the index value of the multi-level label. + final int index; + + /// Get the actual level of the multi-level label. + final int actualLevel; + + /// Get the axis name. + final String? axisName; +} + +/// 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 +/// triggered and provides options to customize the data label text, data label +/// text style, the background color. +/// +/// It has the public properties of data label text, series, data points, and +/// point index. +class DataLabelRenderArgs { + /// Creating an argument constructor of DataLabelRenderArgs class. + DataLabelRenderArgs({ + required this.seriesRenderer, + required this.dataPoints, + required this.viewportPointIndex, + required this.pointIndex, + }); + + /// Get and set the text value of a data label. + String? text; + + /// Get and set the style property of the data label text. + TextStyle textStyle = const TextStyle( + fontFamily: 'Roboto', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 12, + ); + + /// Get the current series. + /// + /// ```dart + /// SfCartesianChart( + /// onDataLabelRender: (DataLabelRenderArgs args) { + /// CartesianSeriesRenderer series = args.seriesRenderer; + /// //Changed the background color of the data label based on the series type + /// if (series.name == 'Product A') { + /// args.color = Colors.blue; + /// } else if(series.name == 'Product B'){ + /// args.color = Colors.red; + /// } + /// }, + /// ) + /// ``` + /// + /// _Note:_ Series type may vary based on the chart type. + /// + /// * Cartesian chart: CartesianSeries series; + /// * Circular chart: CircularSeries series; + /// * Funnel Chart: FunnelSeries series; + /// * Pyramid Chart: PyramidSeries series; + final dynamic seriesRenderer; + + /// Get the data points of the series. + final dynamic dataPoints; + + /// Get the overall index value of a data label. + final int pointIndex; + + /// Get and set the background color of a data label. + Color color = Colors.transparent; + + /// 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.zero; + + /// Get the view port index value of a data label. + final int viewportPointIndex; +} + +/// Holds the onLegendItemRender event arguments. +/// +/// The onLegendItemRender event triggers when the legend item is rendering and +/// can customize the [text], [legendIconType], and [color]. +/// +/// _Note:_ [pointIndex] and [color] is applicable for [SfCircularChart], +/// [SfPyramidChart] and [SfFunnelChart]. +class LegendRenderArgs { + /// Creating an argument constructor of LegendRenderArgs class. + LegendRenderArgs([this.seriesIndex, this.pointIndex]); + + /// Get and set the legend text. + String? text; + + /// Get and set the shape of a legend. + LegendIconType? legendIconType; + + /// Get the current series index. + final int? seriesIndex; + + /// Get the current point index. + final int? pointIndex; + + /// Get and set the color of the legend icon. + Color? color; +} + +/// Holds the onRenderDetailsUpdate callback arguments of trendline. +class TrendlineRenderParams { + /// Creating an argument constructor of TrendlineRenderParams class. + TrendlineRenderParams([ + this.intercept, + this.seriesIndex, + this.trendlineName, + this.seriesName, + this.calculatedDataPoints, + this.slope, + this.rSquaredValue, + ]); + + /// Get the intercept value. + final double? intercept; + + /// Get the index of the series. + final int? seriesIndex; + + /// Gets the name of the trendline. + /// + /// If the user specifies a value for the `name` property in the series, + /// that value can be fetched here. If it is null, then the name generated + /// internally for the trendline can be fetched here. + final String? trendlineName; + + /// Gets the name of the series. + /// + /// If the user specifies a value for the `name` property in the series, + /// that value can be fetched here. If it is null, then the name generated + /// internally for the series can be fetched here. + final String? seriesName; + + /// Get the data points of the trendline. + final List? calculatedDataPoints; + + /// Gets the r-squared value. + final double? rSquaredValue; + + /// Gets the slope value. + final List? slope; +} + +/// Holds arguments for onTrackballPositionChanging event. +/// +/// The event is triggered when the trackball is rendered and provides options +/// to customize the label text. +class TrackballArgs { + /// Get and set the trackball tooltip text. + ChartPointInfo chartPointInfo = ChartPointInfo(); +} + +/// Holds the onCrosshairPositionChanging event arguments. +/// +/// CrosshairRenderArgs is the type of Argument to the +/// onCrosshairPositionChanging event, whenever the crosshair position is +/// changed, the onCrosshairPositionChanging event is +/// triggered and provides options to customize the text, line color. +/// +/// It has the public properties of text, line color, axis, axis name, value, +/// and orientation. +class CrosshairRenderArgs { + /// Creating an argument constructor of CrosshairRenderArgs class. + CrosshairRenderArgs([this.axis, this.value, this.axisName, this.orientation]); + + /// Get the type of chart axis and its properties. + final ChartAxis? axis; + + /// Get and set the crosshair text. + late String text; + + /// Get and set the color of the crosshair line. + late Color lineColor; + + /// Get the visible range value. + final dynamic value; + + /// Get the name of the axis. + final String? axisName; + + /// Get the axis orientation. + final AxisOrientation? orientation; +} + +/// Holds the chart TouchUp event arguments. +/// +/// ChartTouchInteractionArgs is used to store the touch point coordinates when +/// the touch event is triggered. +/// Detects the points or areas in the chart as the offset values of x and y. +class ChartTouchInteractionArgs { + /// Get the position of the touch interaction. + late Offset position; +} + +/// Holds the zooming event arguments. +/// +/// The zooming events are onZooming, onZoomStart, onZoomEnd and onZoomReset. +/// It contains [axis], [currentZoomPosition], [currentZoomFactor], +/// [previousZoomPosition] and [previousZoomFactor] arguments. +/// +/// _Note:_ This is only applicable for [SfCartesianChart]. +class ZoomPanArgs { + /// Creating an argument constructor of ZoomPanArgs class. + ZoomPanArgs([this.axis, this.previousZoomPosition, this.previousZoomFactor]); + + /// Get the chart axis types and properties. + final RenderChartAxis? axis; + + /// Get and set the current zoom position. + late double currentZoomPosition; + + /// Get and set the current zoom factor. + late double currentZoomFactor; + + /// Get the previous zoom position. + final double? previousZoomPosition; + + /// Get the previous zoom factor. + final double? previousZoomFactor; +} + +/// Holds the arguments of `onPointTap`, `onPointDoubleTap` and +/// `onPointLongPress` callbacks. +/// +/// The user can fetch the series index, point index, view port point index and +/// data of the current point. +class ChartPointDetails { + /// Creating an argument constructor of ChartPointDetails class. + ChartPointDetails([ + this.seriesIndex, + this.viewportPointIndex, + this.dataPoints, + this.pointIndex, + ]); + + /// Get the series index. + final int? seriesIndex; + + /// Get the overall index value. + final int? pointIndex; + + /// Get the list of data points. + final List? dataPoints; + + /// Get the view port index value. + final num? viewportPointIndex; +} + +/// Holds the onAxisLabelTapped event arguments. +/// +/// This is the argument type of the onAxisLabelTapped event. Whenever the axis +/// label is tapped, the onAxisLabelTapped event is triggered and provides +/// options to get the axis type, label text, and axis name. +class AxisLabelTapArgs { + /// Creating an argument constructor of AxisLabelTapArgs class. + AxisLabelTapArgs([this.axis, this.axisName]); + + /// Get the type of chart axis and its properties. + final ChartAxis? axis; + + /// Get the text of the axis label at the tapped position. + late String text; + + /// Get the value holds the properties of the visible label. + late num value; + + /// Get the axis name. + 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]. +class LegendTapArgs { + /// Creating an argument constructor of LegendTapArgs class. + LegendTapArgs([this.series, this.seriesIndex, this.pointIndex]); + + /// Get the current series. + /// + /// ```dart + /// SfCartesianChart( + /// onLegendTapped: (LegendTapArgs args) { + /// CartesianSeries series = args.series; + /// print(series.name); + /// }, + /// ) + /// ``` + /// + /// _Note_: Series type may vary based on the chart type. + /// + /// * Cartesian chart: CartesianSeries series; + /// * Circular chart: CircularSeries series; + /// * Funnel Chart: FunnelSeries series; + /// * Pyramid Chart: PyramidSeries series; + final dynamic series; + + /// Get the current series index. + final int? seriesIndex; + + /// Get the current point index. + final int? pointIndex; +} + +/// Holds the onSelectionChanged event arguments. +/// +/// Here [selectedColor], [unselectedColor], [selectedBorderColor], +/// [selectedBorderWidth], [unselectedBorderColor] and [unselectedBorderWidth] +/// can be customized. +class SelectionArgs { + /// Creating an argument constructor of SelectionArgs class. + SelectionArgs({ + 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; + + /// Get and set the color of unselected series or data points. + Color? unselectedColor; + + /// Get and set the border color of the selected series or data points. + Color? selectedBorderColor; + + /// Get and set the border width of the selected series or data points. + double? selectedBorderWidth; + + /// Get and set the border color of the unselected series or data points. + Color? unselectedBorderColor; + + /// Get and set the border width of the unselected series or data points. + double? unselectedBorderWidth; + + /// Get the series index. + final int seriesIndex; + + /// Get the overall index value of the selected data points. + final int pointIndex; + + /// Get the view port index value of the selected data points. + final int viewportPointIndex; +} + +@Deprecated('Use IndicatorRenderParams instead.') +/// Holds the onRenderDetailsUpdate event arguments. +/// +/// Triggers when indicator is rendering. You can customize the +/// [signalLineColor], [signalLineWidth], and [signalLineDashArray]. +/// +/// _Note:_ This is only applicable for [SfCartesianChart]. +class IndicatorRenderArgs { + /// Creating an argument constructor of IndicatorRenderArgs class. + @Deprecated('Use IndicatorRenderParams instead.') + IndicatorRenderArgs([ + this.indicator, + this.index, + this.seriesName, + this.dataPoints, + ]); + + /// Get the technical indicator information. + final TechnicalIndicator? indicator; + + /// Get the indicator name. + late String indicatorName; + + /// Get the current index of the technical indicator. + final int? index; + + /// Get and set the color of the signal line. + late Color signalLineColor; + + /// Get and set the width of the signal line. + late double signalLineWidth; + + /// Get and set the dash array size. + late List lineDashArray; + + /// Get the series name. + final String? seriesName; + + /// Get the current data points. + final List? dataPoints; +} + +/// Holds the onMarkerRender event arguments. +/// +/// MarkerRenderArgs is the argument type of onMarkerRender event. Whenever the +/// onMarkerRender is triggered, the shape of the marker, color, marker width, +/// height, border color, and border width can be customized. +/// +/// Has the public properties of point index, series index, shape, marker width, +/// and height. +class MarkerRenderArgs { + /// Creating an argument constructor of MarkerRenderArgs class. + MarkerRenderArgs([ + this.viewportPointIndex, + this.seriesIndex, + this.pointIndex, + ]); + + /// Get the overall index value of the marker. + final int? pointIndex; + + /// Get the series index of the marker. + final int? seriesIndex; + + /// Get and set the shape of the marker. + late DataMarkerType shape; + + /// Get and set the width of the marker. + late double markerWidth; + + /// Get and set the height of the marker. + late double markerHeight; + + /// Get and set the color of the marker. + Color? color; + + /// Get and set the border color of the marker. + Color? borderColor; + + /// Get and set the border width of marker. + late double borderWidth; + + /// Get the view port index value of the marker. + 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, +/// series index, point index and its text. +class DataLabelTapDetails { + /// Creating an argument constructor of DataLabelTapDetails class. + DataLabelTapDetails( + this.seriesIndex, + this.viewportPointIndex, + this.text, + this.dataLabelSettings, + this.pointIndex, + ); + + /// Get the position of the tapped data label in logical pixels. + late Offset position; + + /// Get the series index of the tapped data label. + final int seriesIndex; + + /// Get the overall index value of the tapped data label. + final int pointIndex; + + /// Get the text of the tapped data label. + final String text; + + /// Get the data label customization options specified in that particular + /// series. + final DataLabelSettings dataLabelSettings; + + /// Get the view port 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; +} + +/// Holds the onCreateShader callback arguments. +class ShaderDetails { + /// Creating an argument constructor of ShaderDetails class. + ShaderDetails(this.rect, this.renderType); + + /// Holds the chart area rect. + final Rect rect; + + ///Conveys whether the current rendering element is 'series' or 'legend'. + final String renderType; +} + +/// Holds the onRenderDetailsUpdate callback arguments. +class IndicatorRenderParams { + /// Creating an argument constructor of IndicatorRenderParams class. + IndicatorRenderParams( + this.calculatedDataPoints, + this.name, + this.signalLineWidth, + this.signalLineColor, + this.signalLineDashArray, + ); + + /// Gets the calculated indicator data points details. + final List? calculatedDataPoints; + + /// Gets the width of the signal line. + late double signalLineWidth; + + /// Gets the color of the signal line. + late Color signalLineColor; + + /// Gets the name of the indicator. + /// + /// If the user specifies a value for the `name` property in the + /// `TechnicalIndicators` class, that value can be fetched here. If it is + /// null, then the name generated internally for the + /// indicator can be fetched here. + final String name; + + /// Gets the dash array of the signal line. + late List signalLineDashArray; +} + +/// Holds the onRenderDetailsUpdate callback arguments. +class BollingerBandIndicatorRenderParams extends IndicatorRenderParams { + /// Creating an argument constructor of BollingerBandIndicatorRenderParams + /// class. + BollingerBandIndicatorRenderParams( + this.upperLineValues, + this.lowerLineValues, + super.calculatedDataPoints, + super.name, + super.signalLineWidth, + super.signalLineColor, + super.signalLineDashArray, + ); + + /// Gets the calculated upper line values of the Bollinger band indicator. + final List? upperLineValues; + + /// Gets the calculated lower line values of the Bollinger band indicator. + final List? lowerLineValues; +} + +/// Holds the onRenderDetailsUpdate callback arguments. +class MomentumIndicatorRenderParams extends IndicatorRenderParams { + /// Creating an argument constructor of MomentumIndicatorRenderParams class. + MomentumIndicatorRenderParams( + this.centerLineValue, + List? calculatedDataPoints, + String name, + double signalLineWidth, + Color signalLineColor, + List signalLineDashArray, + ) : super( + calculatedDataPoints, + name, + signalLineWidth, + signalLineColor, + signalLineDashArray, + ); + + /// Gets the calculated center line value of the Momentum indicator. + final double? centerLineValue; +} + +/// Holds the onRenderDetailsUpdate callback arguments. +class RocIndicatorRenderParams extends IndicatorRenderParams { + /// Creating an argument constructor of RocIndicatorRenderParams class. + RocIndicatorRenderParams( + this.centerLineValue, + List? calculatedDataPoints, + String name, + double signalLineWidth, + Color signalLineColor, + List signalLineDashArray, + ) : super( + calculatedDataPoints, + name, + signalLineWidth, + signalLineColor, + signalLineDashArray, + ); + + /// Gets the calculated center line value of the Roc indicator. + final double? centerLineValue; +} + +/// Holds the onRenderDetailsUpdate callback arguments. +class StochasticIndicatorRenderParams extends IndicatorRenderParams { + /// Creating an argument constructor of StochasticIndicatorRenderParams class. + StochasticIndicatorRenderParams( + this.periodLineValues, + List? calculatedDataPoints, + String name, + double signalLineWidth, + Color signalLineColor, + List signalLineDashArray, + ) : super( + calculatedDataPoints, + name, + signalLineWidth, + signalLineColor, + signalLineDashArray, + ); + + /// Gets the calculated period line values of the stochastic indicator. + final List? periodLineValues; +} + +/// Holds the onRenderDetailsUpdate callback arguments. +class MacdIndicatorRenderParams extends IndicatorRenderParams { + /// Creating an argument constructor of MacdIndicatorRenderParams class. + MacdIndicatorRenderParams( + this.macdLineValues, + this.macdHistogramValues, + List? calculatedDataPoints, + String name, + double signalLineWidth, + Color signalLineColor, + List signalLineDashArray, + ) : super( + calculatedDataPoints, + name, + signalLineWidth, + signalLineColor, + signalLineDashArray, + ); + + /// Gets the calculated Macd line values of the Macd indicator. + final List? macdLineValues; + + /// Gets the calculated histogram values of the Macd indicator. + final List? macdHistogramValues; +} + +/// Holds the TechnicalIndicatorRenderDetails values +class TechnicalIndicatorRenderDetails { + /// Creating an argument constructor of TechnicalIndicatorRenderDetails class. + TechnicalIndicatorRenderDetails( + this.signalLineColor, + this.signalLineWidth, + this.signalLineDashArray, + ); + + /// Color of the signal line. + final Color signalLineColor; + + /// Width of the signal line. + final double signalLineWidth; + + /// Dash array of the signal line + final List? signalLineDashArray; +} + +/// Holds the ErrorBarRenderDetails values. +class ErrorBarRenderDetails { + /// Creating an argument constructor of ErrorBarRenderDetails class. + ErrorBarRenderDetails( + this.pointIndex, + this.viewPortPointIndex, + this.calculatedErrorBarValues, + ); + + /// Specifies the overall point index. + final int? pointIndex; + + /// Specifies the current point index. + final int? viewPortPointIndex; + + /// Specifies the current data point's error values. + final ErrorBarValues? calculatedErrorBarValues; +} + +/// Holds the error values of data point. +class ErrorBarValues { + /// Creating an argument constructor of ErrorBarValues class. + ErrorBarValues( + this.horizontalPositiveErrorValue, + this.horizontalNegativeErrorValue, + this.verticalPositiveErrorValue, + this.verticalNegativeErrorValue, + ); + + /// Holds the positive error value in horizontal point. + final double? horizontalPositiveErrorValue; + + /// Holds the negative error value in horizontal point. + final double? horizontalNegativeErrorValue; + + /// Holds the positive error value in vertical point. + final double? verticalPositiveErrorValue; + + /// Holds the negative error value in vertical point. + final double? verticalNegativeErrorValue; +} + +/// Options to show the details of the trackball template. +@immutable +class TrackballDetails { + /// Constructor of TrackballDetails class. + const TrackballDetails([ + this.point, + this.series, + this.pointIndex, + this.seriesIndex, + this.groupingModeInfo, + ]); + + /// It specifies the Cartesian chart point. + final CartesianChartPoint? point; + + /// It specifies the Cartesian series. + final dynamic 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; + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is TrackballDetails && + other.point == point && + other.series == series && + other.pointIndex == pointIndex && + other.seriesIndex == seriesIndex && + other.groupingModeInfo == groupingModeInfo; + } + + @override + int get hashCode { + final List values = [ + point, + series, + pointIndex, + seriesIndex, + groupingModeInfo, + ]; + return Object.hashAll(values); + } +} + +/// Class to store the group mode details of trackball template. +class TrackballGroupingModeInfo { + /// Creates an argument constructor for TrackballGroupingModeInfo class. + TrackballGroupingModeInfo( + this.points, + this.currentPointIndices, + this.visibleSeriesIndices, + this.visibleSeriesList, + ); + + /// Specifies the cartesian chart points. + final List points; + + /// Specifies the current point indices. + final List currentPointIndices; + + /// Specifies the visible series indices. + final List visibleSeriesIndices; + + /// Specifies the cartesian visible series list. + final List visibleSeriesList; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart new file mode 100644 index 000000000..f0e03487c --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart @@ -0,0 +1,476 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; + +/// This class has the properties of [CartesianChartPoint]. +/// +/// Chart point is a class that is used to store the current x and y values +/// from the data source. Contains x and y coordinates which are converted +/// from the x and y values. +class CartesianChartPoint extends ChartPoint { + /// Creating an argument constructor of [CartesianChartPoint] class. + CartesianChartPoint({ + super.x, + super.y, + this.xValue, + this.high, + this.low, + this.open, + this.close, + this.volume, + this.maximum, + this.minimum, + this.upperQuartile, + this.lowerQuartile, + this.median, + this.mean, + this.outliers, + this.bubbleSize, + this.cumulative, + }); + + /// X value of chart point. + num? xValue; + + /// High value of the point. + num? high; + + /// Low value of the point. + num? low; + + /// Open value of the point. + num? open; + + /// Close value of the point. + num? close; + + /// Volume value of the point. + num? volume; + + /// Minimum value of the point. + num? minimum; + + /// Maximum value of the point. + num? maximum; + + /// Lower quartile value of the point. + num? lowerQuartile; + + /// Upper quartile value of the point. + num? upperQuartile; + + /// Median value of the point. + num? median; + + /// Mean value of the point. + num? mean; + + /// Outliers value of the point. + List? outliers; + + /// Bubble size value of the point. + num? bubbleSize; + + /// Cumulative value of the point. + num? cumulative; + + @override + dynamic operator [](ChartDataPointType pointType) { + switch (pointType) { + case ChartDataPointType.y: + return y!; + case ChartDataPointType.high: + return high!; + case ChartDataPointType.low: + return low!; + case ChartDataPointType.open: + return open!; + case ChartDataPointType.close: + return close!; + case ChartDataPointType.volume: + return volume!; + case ChartDataPointType.median: + return median!; + case ChartDataPointType.mean: + return mean!; + case ChartDataPointType.outliers: + return outliers!; + case ChartDataPointType.bubbleSize: + return bubbleSize!; + case ChartDataPointType.cumulative: + return cumulative!; + } + } + + @override + void operator []=(ChartDataPointType pointType, dynamic value) { + switch (pointType) { + case ChartDataPointType.y: + y = value; + break; + case ChartDataPointType.high: + high = value; + break; + case ChartDataPointType.low: + low = value; + break; + case ChartDataPointType.open: + open = value; + break; + case ChartDataPointType.close: + close = value; + break; + case ChartDataPointType.volume: + volume = value; + break; + case ChartDataPointType.median: + median = value; + break; + case ChartDataPointType.mean: + mean = value; + break; + case ChartDataPointType.outliers: + outliers = value; + break; + case ChartDataPointType.bubbleSize: + bubbleSize = value; + break; + case ChartDataPointType.cumulative: + cumulative = value; + break; + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('x', x)); + properties.add(DiagnosticsProperty('xValue', xValue)); + properties.add(DiagnosticsProperty('y', y)); + properties.add(DiagnosticsProperty('high', high)); + properties.add(DiagnosticsProperty('low', low)); + properties.add(DiagnosticsProperty('open', open)); + properties.add(DiagnosticsProperty('close', close)); + properties.add(DiagnosticsProperty('volume', volume)); + properties.add(DiagnosticsProperty('minimum', minimum)); + properties.add(DiagnosticsProperty('maximum', maximum)); + properties.add(DiagnosticsProperty('lowerQuartile', lowerQuartile)); + properties.add(DiagnosticsProperty('upperQuartile', upperQuartile)); + properties.add(DiagnosticsProperty('median', median)); + properties.add(DiagnosticsProperty('mean', mean)); + properties.add(DiagnosticsProperty>('outliers', outliers)); + properties.add(DiagnosticsProperty('bubbleSize', bubbleSize)); + properties.add(DiagnosticsProperty('cumulative', cumulative)); + } +} + +/// It is the data type for the circular chart and it has the properties is used +/// to assign at the value declaration of the circular chart. +/// +/// It provides the options for color, stroke color, fill color, radius, angle +/// to customize the circular chart. +class ChartPoint with Diagnosticable { + /// Creating an argument constructor of [ChartPoint] class. + ChartPoint({this.x, this.y}); + + /// Raw x value of the point. + D? x; + + /// Y value of the point. + num? y; + + /// Color of the point. + Color? color; + + /// To get the visibility of chart point. + bool isVisible = true; + + dynamic operator [](ChartDataPointType pointType) { + return y!; + } + + void operator []=(ChartDataPointType pointType, dynamic value) { + if (pointType == ChartDataPointType.y) { + y = value; + } + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('x', x)); + properties.add(DiagnosticsProperty('y', y)); + } +} + +class CircularChartPoint extends ChartPoint { + /// Creating an argument constructor of [CircularChartPoint] class. + CircularChartPoint({super.x, super.y}); + + /// Degree of chart point. + num? degree; + + /// Start angle of chart point. + double? startAngle; + + /// End angle of chart point. + double? endAngle; + + /// Middle angle of chart point. + double? midAngle; + + /// Center position of chart point. + Offset? center; + + /// Inner radius of chart point. + num? innerRadius; + + /// Outer radius of chart point. + num? outerRadius; + + /// To set the explode value of chart point. + bool isExplode = false; + + /// To set the explode offset value of chart point. + String explodeOffset = '10%'; + + /// Text value of chart point. + String? text; + + /// Fill color of the chart point. + late Color fill; + + /// Data label position of chart point. + late Position dataLabelPosition; + + /// Render position of chart point. + ChartDataLabelPosition? renderPosition; + + /// Label rect of chart point. + late Rect labelRect; + + /// Size of the Data label of chart point. + Size dataLabelSize = Size.zero; + + /// Saturation region value of chart point. + bool saturationRegionOutside = false; + + /// To store the trimmed text, if the label renders outside + /// the container area. + String? trimmedText; + + /// Stores the segment overflow data label trimmed text. + String? overflowTrimmedText; + + /// Holds the value either 0 or 1 to denote whether the current label + /// is positioned smartly. + num? isLabelUpdated; + + /// Holds the shifted angle of chart point. + num? newAngle; + + Path? connectorPath; + + Offset labelLocation = Offset.zero; +} + +/// This is similar to the point of the Cartesian chart. +class PointInfo { + /// Creating an argument constructor of [PointInfo] class. + PointInfo(this.x, this.y); + + /// X value of point info. + dynamic x; + + /// Y value of point info. + num? y; + + /// Text value of point info. + String? text; + + /// Fill color of point info. + late Color fill; + + /// Stores the point info color. + late Color color; + + /// Stores the border color of point info. + late Color borderColor; + + /// Stores the sort value. + D? sortValue; + + /// Stores the border width value. + late num borderWidth; + + /// To set the property of explode. + bool isExplode = false; + + /// To set the property of shadow. + bool isShadow = false; + + /// To set the property of empty. + bool isEmpty = false; + + /// To set the property of visible. + bool isVisible = true; + + /// To set the property of selection. + bool isSelected = false; + + /// Stores the value of label position. + Position? dataLabelPosition; + + /// Stores the value of chart data label position. + ChartDataLabelPosition? renderPosition; + + /// Stores the value of label rect. + Rect? labelRect; + + // TODO(VijayakumarM): Necessary? + /// To check if labels collide. + // bool _isLabelCollide = false; + + /// Stores the value data label size. + Size dataLabelSize = Size.zero; + + /// To set the saturation region. + bool saturationRegionOutside = false; + + /// Stores the value of Y ratio. + late num yRatio; + + /// Stores the value of height ratio. + late num heightRatio; + + /// Stores the list value of path region. + late List pathRegion; + + /// Stores the value of region. + Rect? region; + + /// Stores the offset value of symbol location. + late Offset symbolLocation; + + /// Stores the value of explode Distance. + num? explodeDistance; + + /// To execute onTooltipRender event or not. + // ignore: prefer_final_fields + bool isTooltipRenderEvent = false; + + /// To execute OnDataLabelRender event or not. + // ignore: prefer_final_fields + bool labelRenderEvent = false; +} + +/// Represents the class of chart point info +class ChartPointInfo { + ChartPointInfo({ + this.chartPoint, + this.dataPointIndex, + this.label, + this.series, + this.seriesName, + this.seriesIndex, + this.color, + this.header, + this.xPosition, + this.yPosition, + this.highXPosition, + this.highYPosition, + this.openXPosition, + this.openYPosition, + this.lowYPosition, + this.closeXPosition, + this.closeYPosition, + this.minYPosition, + this.maxXPosition, + this.maxYPosition, + this.lowerXPosition, + this.lowerYPosition, + this.upperXPosition, + this.upperYPosition, + this.markerXPos, + this.markerYPos, + }); + + /// Marker x position. + double? markerXPos; + + /// Marker y position. + double? markerYPos; + + /// label for trackball and cross hair. + String? label; + + /// Data point index. + int? dataPointIndex; + + /// Instance of chart series. + dynamic series; + + /// Cartesian chart point. + CartesianChartPoint? chartPoint; + + /// X position of the label. + double? xPosition; + + /// Y position of the label. + double? yPosition; + + /// Color of the segment. + Color? color; + + /// header text. + String? header; + + /// Low Y position of financial series. + double? lowYPosition; + + /// High X position of financial series. + double? highXPosition; + + /// High Y position of financial series. + double? highYPosition; + + /// Open y position of financial series. + double? openYPosition; + + /// close y position of financial series. + double? closeYPosition; + + /// open x position of financial series. + double? openXPosition; + + /// close x position of financial series. + double? closeXPosition; + + /// Minimum Y position of box plot series. + double? minYPosition; + + /// Maximum Y position of box plot series. + double? maxYPosition; + + /// Lower y position of box plot series. + double? lowerXPosition; + + /// Upper y position of box plot series. + double? upperXPosition; + + /// Lower x position of box plot series. + double? lowerYPosition; + + /// Upper x position of box plot series. + double? upperYPosition; + + /// Maximum x position for box plot series. + double? maxXPosition; + + /// series index value. + int? seriesIndex; + + /// series name value. + String? seriesName; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart new file mode 100644 index 000000000..bac3876e5 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart @@ -0,0 +1,693 @@ +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'callbacks.dart'; +import 'chart_point.dart'; +import 'circular_data_label_helper.dart'; +import 'core_tooltip.dart'; +import 'data_label.dart'; +import 'element_widget.dart'; + +// ignore: must_be_immutable +base class CircularChartDataLabelPositioned + extends ParentDataWidget + with LinkedListEntry { + CircularChartDataLabelPositioned({ + super.key, + required this.x, + required this.y, + required this.dataPointIndex, + required this.position, + required this.point, + required super.child, + }); + + final num x; + final num y; + final int dataPointIndex; + final ChartDataPointType position; + + Offset offset = Offset.zero; + Size size = Size.zero; + CircularChartPoint? point; + + @override + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is CircularDataLabelBoxParentData); + final CircularDataLabelBoxParentData parentData = + renderObject.parentData! as CircularDataLabelBoxParentData; + bool needsLayout = false; + + if (parentData.x != x) { + parentData.x = x; + needsLayout = true; + } + + if (parentData.y != y) { + parentData.y = y; + needsLayout = true; + } + + if (parentData.dataPointIndex != dataPointIndex) { + parentData.dataPointIndex = dataPointIndex; + needsLayout = true; + } + + if (parentData.position != position) { + parentData.position = position; + needsLayout = true; + } + + if (parentData.point != point) { + parentData.point = point; + needsLayout = true; + } + + if (needsLayout) { + final RenderObject? targetParent = renderObject.parent; + if (targetParent is RenderObject) { + targetParent.markNeedsLayout(); + } + } + } + + @override + Type get debugTypicalAncestorWidgetClass => CircularDataLabelStack; +} + +class CircularDataLabelContainer extends StatefulWidget { + const CircularDataLabelContainer({ + super.key, + required this.series, + required this.dataSource, + this.mapper, + this.builder, + required this.settings, + }); + + final ChartWidgetBuilder? builder; + final List dataSource; + final ChartValueMapper? mapper; + final ChartSeries series; + final DataLabelSettings settings; + + @override + State> createState() => + _CircularDataLabelContainerState(); +} + +typedef _ChartDataLabelWidgetBuilder = + Widget Function( + T data, + ChartPoint point, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ); + +class _CircularDataLabelContainerState + extends State> + with ChartElementParentDataMixin { + List? _builderChildren; + LinkedList? _textChildren; + + @override + CircularSeriesRenderer? get renderer => + super.renderer as CircularSeriesRenderer?; + + Widget _dataLabelFromBuilder( + T data, + ChartPoint point, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + return widget.builder!(data, point, series, pointIndex, seriesIndex); + } + + Widget _dataLabelFromMapper( + T data, + ChartPoint point, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + String text = widget.mapper!(data, pointIndex) ?? ''; + + if (renderer!.groupTo != null) { + text = renderer!.groupingDataLabelValues[pointIndex].toString(); + } + + return _buildDataLabelText(text, pointIndex); + } + + Widget _defaultDataLabel( + T data, + ChartPoint point, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + // TODO(VijayakumarM): What happen when y is nan? + final num value = point[position]; + final String formattedText = decimalLabelValue(value); + return _buildDataLabelText(formattedText, pointIndex, isYText: true); + } + + Color _dataPointColor(int dataPointIndex) { + final DataLabelSettings settings = widget.settings; + if (settings.color != null) { + return settings.color!.withValues(alpha: settings.opacity); + } else if (settings.useSeriesColor) { + return renderer!.segments[dataPointIndex].fillPaint.color.withValues( + alpha: settings.opacity, + ); + } + return Colors.transparent; + } + + DataLabelText _buildDataLabelText( + String text, + int pointIndex, { + bool isYText = false, + }) { + final RenderChartPlotArea parent = renderer!.parent!; + final TextStyle dataLabelTextStyle = parent.themeData!.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(parent.chartThemeData!.dataLabelTextStyle) + .merge(widget.settings.textStyle); + + if (xRawValues != null && + xRawValues!.isNotEmpty && + xRawValues!.length - 1 == pointIndex) { + if (renderer!.groupTo != null && + text != 'Others' && + xRawValues![pointIndex].toString() == 'Others') { + text = isYText ? 'Others : $text' : 'Others'; + } + } + + return DataLabelText( + text: text, + textStyle: dataLabelTextStyle, + color: _dataPointColor(pointIndex), + ); + } + + void _addToList(CircularChartDataLabelPositioned child) { + _builderChildren!.add(child); + } + + void _addToLinkedList(CircularChartDataLabelPositioned child) { + _textChildren!.add(child); + } + + void _buildDataLabels( + _ChartDataLabelWidgetBuilder callback, + Function(CircularChartDataLabelPositioned) add, + ) { + const List positions = ChartDataPointType.values; + final int yLength = yLists?.length ?? 0; + final int posAdj = _positionIndex(yLength); + List? actualXValues; + if (xRawValues != null && xRawValues!.isNotEmpty) { + actualXValues = xRawValues; + } else { + actualXValues = xValues; + } + + if (actualXValues == null || renderer!.segments.isEmpty) { + return; + } + + final bool hasSortedIndexes = + renderer!.sortingOrder != SortingOrder.none && + sortedIndexes != null && + sortedIndexes!.isNotEmpty; + + for (int i = 0; i < renderer!.dataCount; i++) { + _obtainLabel( + i, + actualXValues, + yLength, + positions, + posAdj, + callback, + add, + hasSortedIndexes, + ); + } + } + + int _positionIndex(int yListsLength) { + // TODO(VijayakumarM): Add comment. + return yListsLength == 1 ? 0 : 1; + } + + void _obtainLabel( + int index, + List rawXValues, + int yLength, + List positions, + int posAdj, + _ChartDataLabelWidgetBuilder callback, + Function(CircularChartDataLabelPositioned) add, + bool hasSortedIndexes, + ) { + final int pointIndex = hasSortedIndexes ? sortedIndexes![index] : index; + final num x = xValues![index]; + final CircularChartPoint point = CircularChartPoint( + x: rawXValues[index] as D?, + ); + point.color = _dataPointColor(index); + for (int j = 0; j < yLength; j++) { + final List yValues = yLists![j]; + point.y = yValues[index]; + + // TODO(Lavanya): Recheck here. + // final ChartDataPointType position = positions[j + posAdj]; + // point[position] = yValues[index]; + } + + for (int k = 0; k < yLength; k++) { + final ChartDataPointType position = positions[k + posAdj]; + final CircularChartDataLabelPositioned child = + CircularChartDataLabelPositioned( + x: x, + y: point[position], + dataPointIndex: index, + position: position, + point: point, + child: callback( + widget.dataSource[pointIndex], + point, + widget.series, + pointIndex, + renderer!.index, + position, + ), + ); + + add(child); + } + } + + @override + Widget build(BuildContext context) { + return ChartElementLayoutBuilder( + state: this, + builder: (BuildContext context, BoxConstraints constraints) { + _ChartDataLabelWidgetBuilder callback; + _builderChildren?.clear(); + _textChildren?.clear(); + if (renderer != null && + renderer!.initialIsVisible && + yLists != null && + yLists!.isNotEmpty) { + if (widget.builder != null) { + callback = _dataLabelFromBuilder; + } else { + callback = + widget.mapper != null + ? _dataLabelFromMapper + : _defaultDataLabel; + } + void Function(CircularChartDataLabelPositioned child) add; + if (widget.builder != null) { + _builderChildren = []; + add = _addToList; + } else { + _textChildren = LinkedList(); + add = _addToLinkedList; + } + + if (xValues != null && xValues!.isNotEmpty) { + _buildDataLabels(callback, add); + } + } + + return ChartFadeTransition( + opacity: animation!, + child: CircularDataLabelStack( + series: renderer, + settings: widget.settings, + labels: _textChildren, + children: _builderChildren ?? [], + ), + ); + }, + ); + } +} + +class CircularDataLabelBoxParentData extends ChartElementParentData { + CircularChartPoint? point; +} + +class CircularDataLabelStack extends ChartElementStack { + const CircularDataLabelStack({ + super.key, + required this.series, + required this.labels, + required this.settings, + super.children, + }); + + final CircularSeriesRenderer? series; + final LinkedList? labels; + final DataLabelSettings settings; + + @override + RenderCircularDataLabelStack createRenderObject(BuildContext context) { + return RenderCircularDataLabelStack() + ..series = series + ..labels = labels + ..settings = settings; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCircularDataLabelStack renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..series = series + ..labels = labels + ..settings = settings; + } +} + +class RenderCircularDataLabelStack extends RenderChartElementStack { + late CircularSeriesRenderer? series; + late LinkedList? labels; + late DataLabelSettings settings; + bool hasTrimmedDataLabel = false; + + List widgets = + []; + + @override + bool get sizedByParent => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return false; + } + + @override + bool hitTestSelf(Offset position) { + return (series?.parent?.onDataLabelTapped != null || hasTrimmedDataLabel) && + _findSelectedDataLabelIndex(position) != -1; + } + + int _findSelectedDataLabelIndex(Offset localPosition) { + if (series?.parent?.onDataLabelTapped == null && !hasTrimmedDataLabel) { + return -1; + } + + if (childCount > 0) { + RenderBox? child = lastChild; + while (child != null) { + final CircularDataLabelBoxParentData childParentData = + child.parentData! as CircularDataLabelBoxParentData; + if (childParentData.point!.isVisible && + (childParentData.offset & child.size).contains(localPosition)) { + return childParentData.dataPointIndex; + } + child = childParentData.previousSibling; + } + } else if (labels != null) { + final int labelsLength = labels!.length - 1; + for (int i = labelsLength; i > -1; i--) { + final CircularChartDataLabelPositioned label = labels!.elementAt(i); + if (!label.point!.isVisible) { + continue; + } + final Rect rect = Rect.fromLTWH( + label.offset.dx, + label.offset.dy, + label.size.width + settings.margin.horizontal, + label.size.height + settings.margin.vertical, + ); + if (rect.contains(localPosition)) { + return label.dataPointIndex; + } + } + } + return -1; + } + + @override + void handleTapUp(Offset localPosition) { + if (series?.parent?.onDataLabelTapped != null) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + + final String text = + childCount > 0 + ? '' + : (labels!.elementAt(selectedIndex).child as DataLabelText).text; + series!.parent!.onDataLabelTapped!( + DataLabelTapDetails( + series!.index, + series!.viewportIndex(selectedIndex), + text, + settings, + selectedIndex, + ), + ); + } else if (hasTrimmedDataLabel) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + final CircularChartPoint point = labels!.elementAt(selectedIndex).point!; + if (point.isVisible && + point.trimmedText != null && + point.text != point.trimmedText) { + _showTooltipForTrimmedDataLabel(point, selectedIndex); + } + } + } + + @override + void handlePointerHover(Offset localPosition) { + if (hasTrimmedDataLabel) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + final CircularChartPoint point = labels!.elementAt(selectedIndex).point!; + if (point.isVisible && + point.trimmedText != null && + point.text != point.trimmedText) { + _showTooltipForTrimmedDataLabel(point, selectedIndex); + } + } + } + + void _showTooltipForTrimmedDataLabel( + CircularChartPoint point, + int pointIndex, + ) { + final RenderCircularChartPlotArea plotArea = + series!.parent! as RenderCircularChartPlotArea; + + plotArea.behaviorArea?.showTooltip( + TooltipInfo( + primaryPosition: localToGlobal(point.labelRect.topCenter), + secondaryPosition: localToGlobal(point.labelRect.topCenter), + text: point.text, + surfaceBounds: Rect.fromPoints( + plotArea.localToGlobal(paintBounds.topLeft), + plotArea.localToGlobal(paintBounds.bottomRight), + ), + ), + ); + } + + @override + void setupParentData(RenderObject child) { + if (child is! CircularDataLabelBoxParentData) { + child.parentData = CircularDataLabelBoxParentData(); + } + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + @override + void performLayout() { + renderDataLabelRegions.clear(); + + if (series == null) { + return; + } + + if (childCount > 0) { + RenderBox? child = firstChild; + widgets.clear(); + while (child != null) { + final CircularDataLabelBoxParentData currentChildData = + child.parentData! as CircularDataLabelBoxParentData; + widgets.add(currentChildData); + final RenderBox? nextSibling = currentChildData.nextSibling; + + child.layout(constraints, parentUsesSize: true); + currentChildData.offset = series!.dataLabelPosition( + currentChildData, + child.size, + ); + // TODO(Lavanya): Need to handle the offset value for the + // shift data label. + final Offset offset = _invokeDataLabelRender( + currentChildData.dataPointIndex, + ); + currentChildData.offset = Offset( + currentChildData.offset.dx + offset.dx, + currentChildData.offset.dy - offset.dy, + ); + child = nextSibling; + } + + if (series!.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + shiftCircularDataLabelTemplate(series!, widgets); + } + } else if (labels != null) { + for (final CircularChartDataLabelPositioned currentLabel in labels!) { + final CircularDataLabelBoxParentData currentLabelData = + CircularDataLabelBoxParentData() + ..x = currentLabel.x + ..y = currentLabel.y + ..dataPointIndex = currentLabel.dataPointIndex + ..position = currentLabel.position + ..point = currentLabel.point; + + final DataLabelText details = currentLabel.child as DataLabelText; + final Offset offset = _invokeDataLabelRender( + currentLabel.dataPointIndex, + details, + ); + currentLabel.offset = Offset( + currentLabel.offset.dx + offset.dx, + currentLabel.offset.dy - offset.dy, + ); + currentLabel.point!.text = details.text; + currentLabel.size = measureText(details.text, details.textStyle); + currentLabel.offset += series!.dataLabelPosition( + currentLabelData, + currentLabel.size, + ); + hasTrimmedDataLabel = currentLabel.point!.trimmedText != null; + + if (currentLabel.point!.text != details.text) { + details.text = currentLabel.point!.text!; + currentLabel.size = measureText(details.text, details.textStyle); + } + } + if (series!.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + shiftCircularDataLabels(series!, labels!); + hasTrimmedDataLabel = labels!.any( + (element) => element.point!.trimmedText != null, + ); + } + } + } + + Offset _invokeDataLabelRender(int pointIndex, [DataLabelText? details]) { + if (series!.parent?.onDataLabelRender != null) { + final DataLabelRenderArgs dataLabelArgs = DataLabelRenderArgs( + seriesRenderer: series, + dataPoints: series!.chartPoints, + viewportPointIndex: pointIndex, + pointIndex: pointIndex, + )..offset = settings.offset; + if (details != null) { + dataLabelArgs + ..text = details.text + ..textStyle = details.textStyle + ..color = details.color; + } + + series!.parent!.onDataLabelRender!(dataLabelArgs); + if (details != null) { + details + ..text = dataLabelArgs.text ?? '' + ..textStyle = details.textStyle.merge(dataLabelArgs.textStyle) + ..color = dataLabelArgs.color; + } + + return dataLabelArgs.offset; + } + + return settings.offset; + } + + @override + void paint(PaintingContext context, Offset offset) { + context.canvas + ..save() + ..clipRect(paintBounds); + if (childCount > 0) { + RenderBox? child = firstChild; + while (child != null) { + final CircularDataLabelBoxParentData childParentData = + child.parentData! as CircularDataLabelBoxParentData; + final CircularChartPoint point = childParentData.point!; + if (point.isVisible) { + if (point.connectorPath != null) { + series!.drawConnectorLine( + point.connectorPath!, + context.canvas, + childParentData.dataPointIndex, + ); + } + context.paintChild(child, childParentData.offset + offset); + } + child = childParentData.nextSibling; + } + } else if (labels != null) { + final Paint fillPaint = Paint(); + final Paint strokePaint = + Paint() + ..color = settings.borderColor + ..strokeWidth = settings.borderWidth + ..style = PaintingStyle.stroke; + for (final CircularChartDataLabelPositioned label in labels!) { + final DataLabelText details = label.child as DataLabelText; + fillPaint.color = details.color; + series!.drawDataLabelWithBackground( + label, + label.dataPointIndex, + context.canvas, + details.text, + label.offset, + settings.angle, + details.textStyle, + fillPaint, + strokePaint, + ); + } + } + context.canvas.restore(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart new file mode 100644 index 000000000..b2c3c4180 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart @@ -0,0 +1,1200 @@ +import 'dart:collection'; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../series/chart_series.dart'; +import '../series/radial_bar_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'chart_point.dart'; +import 'circular_data_label.dart'; +import 'connector_line.dart'; +import 'data_label.dart'; + +/// Decides to increase the angle or not. +bool isIncreaseAngle = false; + +/// To store the points which render at left and positioned outside. +List leftPoints = []; + +/// To store the points which render at right and positioned outside. +List rightPoints = []; + +List renderDataLabelRegions = []; + +/// For checking whether elements collide. +bool findingCollision(Rect rect, List regions, [Rect? pathRect]) { + bool isCollide = false; + if (pathRect != null && + (pathRect.left < rect.left && + pathRect.width > rect.width && + pathRect.top < rect.top && + pathRect.height > rect.height)) { + isCollide = false; + } else if (pathRect != null) { + isCollide = true; + } + for (int i = 0; i < regions.length; i++) { + final Rect regionRect = regions[i]; + if ((rect.left < regionRect.left + regionRect.width && + rect.left + rect.width > regionRect.left) && + (rect.top < regionRect.top + regionRect.height && + rect.top + rect.height > regionRect.top)) { + isCollide = true; + break; + } + } + return isCollide; +} + +/// Method to get a text when the text overlap with another segment/slice. +String segmentOverflowTrimmedText( + CircularSeriesRenderer seriesRenderer, + String text, + Size size, + CircularChartPoint point, + Rect labelRect, + Offset centerLocation, + Offset labelLocation, + OverflowMode action, + TextStyle dataLabelStyle, +) { + bool isTextWithinRegion; + const String ellipse = '...'; + const int minCharacterLength = 3; + // To reduce the additional padding around label rect. + // ignore: prefer_const_declarations + final double labelPadding = kIsWeb ? 4 : 2; + + final bool labelLeftEnd = _isInsideSegment( + point.labelRect.centerLeft - Offset(labelPadding, 0), + centerLocation, + point.outerRadius!, + point.startAngle!, + point.endAngle!, + ); + + final bool labelRightEnd = _isInsideSegment( + point.labelRect.centerRight - Offset(labelPadding, 0), + centerLocation, + point.outerRadius!, + point.startAngle!, + point.endAngle!, + ); + + if (labelLeftEnd && labelRightEnd) { + return text; + } else { + isTextWithinRegion = false; + while (!isTextWithinRegion) { + if (action == OverflowMode.trim) { + if (text == '') { + break; + } + if (text.length > minCharacterLength) { + text = addEllipse(text, text.length, ellipse, isRtl: false); + } else { + text = ''; + break; + } + if (text == ellipse) { + text = ''; + break; + } + const num labelPadding = 0; + final Size trimSize = measureText(text, dataLabelStyle); + Offset trimmedLabelLocation = calculateOffset( + point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, + point.center!, + ); + trimmedLabelLocation = Offset( + (trimmedLabelLocation.dx - trimSize.width - 5) + + (seriesRenderer.dataLabelSettings.angle == 0 + ? 0 + : trimSize.width / 2), + (trimmedLabelLocation.dy - trimSize.height / 2) + + (seriesRenderer.dataLabelSettings.angle == 0 + ? 0 + : trimSize.height / 2), + ); + final Rect trimmedLabelRect = Rect.fromLTWH( + trimmedLabelLocation.dx - labelPadding, + trimmedLabelLocation.dy - labelPadding, + trimSize.width + (2 * labelPadding), + trimSize.height + (2 * labelPadding), + ); + + final bool trimmedLeftEnd = _isInsideSegment( + trimmedLabelRect.centerLeft, + centerLocation, + point.outerRadius!, + point.startAngle!, + point.endAngle!, + ); + + final bool trimmedRightEnd = _isInsideSegment( + trimmedLabelRect.centerRight, + centerLocation, + point.outerRadius!, + point.startAngle!, + point.endAngle!, + ); + if (trimmedLeftEnd && trimmedRightEnd) { + isTextWithinRegion = true; + point.labelRect = trimmedLabelRect; + } + } else { + text = ''; + isTextWithinRegion = true; + break; + } + } + } + return text; +} + +/// Add the ellipse with trimmed text. +String addEllipse(String text, int maxLength, String ellipse, {bool? isRtl}) { + if (isRtl ?? false) { + if (text.contains(ellipse)) { + text = text.replaceAll(ellipse, ''); + text = text.substring(1, text.length); + } else { + text = text.substring(ellipse.length, text.length); + } + return ellipse + text; + } else { + maxLength--; + final int length = maxLength - ellipse.length; + final String trimText = text.substring(0, length); + return trimText + ellipse; + } +} + +/// Method to check if a label is inside the point region based +/// on the angle for pie and doughnut series. +bool _isInsideSegment( + Offset point, + Offset center, + num radius, + num start, + num end, +) { + final Offset labelOffset = point - center; + final double labelRadius = labelOffset.distance; + + if (labelRadius < radius) { + final num originAngle = atan2(-labelOffset.dy, labelOffset.dx) * 180 / pi; + num labelAngle = 360 - originAngle; + + if (labelAngle > 270) { + labelAngle -= 360; + } + labelAngle = labelAngle.round(); + + return labelAngle >= start && labelAngle <= end; + } else { + return false; + } +} + +/// Method for setting color to data label. +Color findThemeColor( + CircularSeriesRenderer seriesRenderer, + CircularChartPoint point, + DataLabelSettings dataLabelSettings, +) { + // TODO(Lavanya): Recheck here. + final Color dataLabelBackgroundColor = + seriesRenderer.parent!.themeData!.colorScheme.surface; + if (dataLabelSettings.color != null) { + return dataLabelSettings.color!; + } else { + return (dataLabelSettings.useSeriesColor + ? point.fill + : (seriesRenderer.parent!.backgroundColor ?? dataLabelBackgroundColor)); + } +} + +/// To render outside positioned data labels. +void renderOutsideDataLabel( + CircularChartPoint point, + Size textSize, + int pointIndex, + CircularSeriesRenderer seriesRenderer, + int seriesIndex, + TextStyle textStyle, + List renderDataLabelRegions, +) { + Path connectorPath; + Rect? rect; + Offset labelLocation; + final DataLabelSettings settings = seriesRenderer.dataLabelSettings; + const String defaultConnectorLineLength = '10%'; + final EdgeInsets margin = settings.margin; + final ConnectorLineSettings connector = settings.connectorLineSettings; + connectorPath = Path(); + final num connectorLength = + percentToValue( + connector.length ?? defaultConnectorLineLength, + point.outerRadius!, + )!; + final Offset startPoint = calculateOffset( + point.midAngle!, + point.outerRadius!.toDouble(), + point.center!, + ); + final Offset endPoint = calculateOffset( + point.midAngle!, + (point.outerRadius! + connectorLength).toDouble(), + 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, + // To avoid the extra padding added to the exact template size. + settings.builder != null && + settings.labelIntersectAction == LabelIntersectAction.shift + ? seriesRenderer.dataLabelSettings + : null, + ); + + point.connectorPath = connectorPath; + point.labelRect = rect!; + labelLocation = Offset( + rect.left + margin.left, + rect.top + rect.height / 2 - textSize.height / 2, + ); + point.labelLocation = labelLocation; + + final Rect containerRect = seriesRenderer.paintBounds; + + if (seriesRenderer.dataLabelSettings.builder == null) { + if (seriesRenderer.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide) { + if (!findingCollision(rect, renderDataLabelRegions) && + (rect.left > containerRect.left && + rect.left + rect.width < + containerRect.left + containerRect.width) && + rect.top > containerRect.top && + rect.top + rect.height < containerRect.top + containerRect.height) { + // TODO(Lavanya): drawLabel method add renderDataLabelRegions. + if (seriesRenderer.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) { + renderDataLabelRegions.add(rect); + } + } else { + point.isVisible = false; + } + } else if (seriesRenderer.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + renderDataLabelRegions.add(rect); + } else { + // TODO(Lavanya): drawLabel method add renderDataLabelRegions. + if (seriesRenderer.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) { + renderDataLabelRegions.add(rect); + } + } + } else { + if (seriesRenderer.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) { + if ((!findingCollision(rect, renderDataLabelRegions) && + (rect.left > containerRect.left && + rect.left + rect.width < + containerRect.left + containerRect.width) && + rect.top > containerRect.top && + rect.top + rect.height < + containerRect.top + containerRect.height) || + settings.labelIntersectAction == LabelIntersectAction.none) { + point.connectorPath = connectorPath; + point.labelRect = rect; + point.labelLocation = labelLocation; + } else { + point.isVisible = false; + } + } + } +} + +/// To return data label rect calculation method based on position. +Rect? getDataLabelRect( + Position position, + ConnectorType connectorType, + EdgeInsets margin, + Path connectorPath, + Offset endPoint, + Size textSize, [ + DataLabelSettings? dataLabelSettings, +]) { + Rect? rect; + const int lineLength = 10; + switch (position) { + case Position.right: + connectorType == ConnectorType.line + ? connectorPath.lineTo(endPoint.dx + lineLength, endPoint.dy) + : connectorPath.quadraticBezierTo( + endPoint.dx, + endPoint.dy, + endPoint.dx + lineLength, + endPoint.dy, + ); + rect = + dataLabelSettings != null && dataLabelSettings.builder != null + ? Rect.fromLTWH( + endPoint.dx, + endPoint.dy, + textSize.width, + textSize.height, + ) + : Rect.fromLTWH( + endPoint.dx + lineLength, + endPoint.dy - (textSize.height / 2) - margin.top, + textSize.width + margin.left + margin.right, + textSize.height + margin.top + margin.bottom, + ); + break; + case Position.left: + connectorType == ConnectorType.line + ? connectorPath.lineTo(endPoint.dx - lineLength, endPoint.dy) + : connectorPath.quadraticBezierTo( + endPoint.dx, + endPoint.dy, + endPoint.dx - lineLength, + endPoint.dy, + ); + rect = + dataLabelSettings != null && dataLabelSettings.builder != null + ? Rect.fromLTWH( + endPoint.dx, + endPoint.dy, + textSize.width, + textSize.height, + ) + : Rect.fromLTWH( + endPoint.dx - + lineLength - + margin.right - + textSize.width - + margin.left, + endPoint.dy - ((textSize.height / 2) + margin.top), + textSize.width + margin.left + margin.right, + textSize.height + margin.top + margin.bottom, + ); + break; + } + return rect; +} + +void shiftCircularDataLabels( + CircularSeriesRenderer seriesRenderer, + LinkedList labels, +) { + final List points = []; + + if (seriesRenderer is RadialBarSeriesRenderer) { + return; + } + + if (seriesRenderer.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + const int labelPadding = 2; + final DataLabelSettings dataLabelSettings = + seriesRenderer.dataLabelSettings; + if (dataLabelSettings.builder == null) { + leftPoints = []; + rightPoints = []; + + for (int i = 0; i < labels.length; i++) { + final CircularChartPoint point = labels.elementAt(i).point!; + points.add(point); + if (point.isVisible) { + point.newAngle = point.midAngle; + if (point.dataLabelPosition == Position.left && + point.renderPosition == ChartDataLabelPosition.outside) { + leftPoints.add(point); + } else if (point.dataLabelPosition == Position.right && + point.renderPosition == ChartDataLabelPosition.outside) { + rightPoints.add(point); + } + } + } + leftPoints.sort( + (CircularChartPoint a, CircularChartPoint b) => + a.newAngle!.compareTo(b.newAngle!), + ); + if (leftPoints.isNotEmpty) { + _arrangeLeftSidePoints(seriesRenderer); + } + isIncreaseAngle = false; + if (rightPoints.isNotEmpty) { + _arrangeRightSidePoints(seriesRenderer); + } + } + + for (int pointIndex = 0; pointIndex < labels.length; pointIndex++) { + final CircularChartDataLabelPositioned dataLabelPositioned = labels + .elementAt(pointIndex); + final CircularChartPoint point = dataLabelPositioned.point!; + if (point.isVisible) { + final EdgeInsets margin = seriesRenderer.dataLabelSettings.margin; + Rect rect = point.labelRect; + Offset labelLocation; + final Size textSize = dataLabelPositioned.size; + + labelLocation = Offset( + rect.left + + (point.renderPosition == ChartDataLabelPosition.inside + ? labelPadding + : margin.left), + rect.top + rect.height / 2 - textSize.height / 2, + ); + const String defaultConnectorLineLength = '10%'; + point.trimmedText = point.text; + Path shiftedConnectorPath = Path(); + final num connectorLength = + percentToValue( + seriesRenderer.dataLabelSettings.connectorLineSettings.length ?? + defaultConnectorLineLength, + point.outerRadius!, + )!; + final Offset startPoint = calculateOffset( + (point.startAngle! + point.endAngle!) / 2, + point.outerRadius!.toDouble(), + point.center!, + ); + final Offset endPoint = calculateOffset( + point.newAngle!.toDouble(), + (point.outerRadius! + connectorLength).toDouble(), + point.center!, + ); + shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + if (seriesRenderer.dataLabelSettings.connectorLineSettings.type == + ConnectorType.line) { + shiftedConnectorPath.lineTo(endPoint.dx, endPoint.dy); + } + getDataLabelRect( + point.dataLabelPosition, + seriesRenderer.dataLabelSettings.connectorLineSettings.type, + margin, + shiftedConnectorPath, + endPoint, + textSize, + ); + final Offset midAngle = getPerpendicularDistance( + Offset(startPoint.dx, startPoint.dy), + point, + ); + if (seriesRenderer.dataLabelSettings.connectorLineSettings.type == + ConnectorType.curve && + (point.isLabelUpdated) == 1) { + const int spacing = 10; + shiftedConnectorPath = Path(); + shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + shiftedConnectorPath.quadraticBezierTo( + midAngle.dx, + midAngle.dy, + endPoint.dx - + (point.dataLabelPosition == Position.left ? spacing : -spacing), + endPoint.dy, + ); + } + + // TODO(Lavanya): Recheck connector line here. + point.connectorPath = + point.renderPosition == ChartDataLabelPosition.outside + ? shiftedConnectorPath + : null; + + // TODO(Lavanya): Recheck here. + final Rect containerRect = seriesRenderer.paintBounds; + if (containerRect.left > rect.left) { + labelLocation = Offset( + containerRect.left, + rect.top + rect.height / 2 - textSize.height / 2, + ); + } + + final DataLabelText details = + labels.elementAt(pointIndex).child as DataLabelText; + if (point.labelRect.left < containerRect.left && + point.renderPosition == ChartDataLabelPosition.outside) { + point.trimmedText = getTrimmedText( + point.trimmedText!, + point.labelRect.right - containerRect.left, + details.textStyle, + isRtl: false, + ); // TODO(Lavanya): Recheck here. + } + if (point.labelRect.right > containerRect.right && + point.renderPosition == ChartDataLabelPosition.outside) { + point.trimmedText = getTrimmedText( + point.trimmedText!, + containerRect.right - point.labelRect.left, + details.textStyle, + isRtl: false, + ); + } + + if (point.text != point.trimmedText) { + details.text = point.trimmedText!; + point.dataLabelSize = measureText(details.text, details.textStyle); + dataLabelPositioned.size = point.dataLabelSize; + rect = + getDataLabelRect( + point.dataLabelPosition, + seriesRenderer.dataLabelSettings.connectorLineSettings.type, + margin, + shiftedConnectorPath, + endPoint, + point.dataLabelSize, + )!; + } else { + point.trimmedText = null; + } + point.labelLocation = labelLocation; + dataLabelPositioned.offset = labelLocation; + + // TODO(Lavanya): Recheck here. + if (point.trimmedText != '' && + !isOverlapWithPrevious(point, points, pointIndex) && + rect != Rect.zero) { + point.isVisible = true; + point.labelRect = rect; + } else { + point.isVisible = false; + } + } + } + } +} + +/// Left side points alignment calculation. +void _arrangeLeftSidePoints(CircularSeriesRenderer seriesRenderer) { + CircularChartPoint previousPoint; + CircularChartPoint currentPoint; + bool angleChanged = false; + bool startFresh = false; + for (int i = 1; i < leftPoints.length; i++) { + currentPoint = leftPoints[i]; + previousPoint = leftPoints[i - 1]; + if (isOverlapWithPrevious(currentPoint, leftPoints, i) && + currentPoint.isVisible || + !(currentPoint.newAngle! < 270)) { + angleChanged = true; + if (startFresh) { + isIncreaseAngle = false; + } + if (!isIncreaseAngle) { + for (int k = i; k > 0; k--) { + _decreaseAngle( + leftPoints[k], + leftPoints[k - 1], + seriesRenderer, + false, + ); + for (int index = 1; index < leftPoints.length; index++) { + if ((leftPoints[index].isLabelUpdated) != null && + leftPoints[index].newAngle! - 10 < 100) { + isIncreaseAngle = true; + } + } + } + } else { + for (int k = i; k < leftPoints.length; k++) { + _increaseAngle( + leftPoints[k - 1], + leftPoints[k], + seriesRenderer, + false, + ); + } + } + } else { + if (angleChanged && + // ignore: unnecessary_null_comparison + previousPoint != null && + (previousPoint.isLabelUpdated) == 1) { + startFresh = true; + } + } + } +} + +/// Right side points alignments calculation. +void _arrangeRightSidePoints(CircularSeriesRenderer seriesRenderer) { + bool startFresh = false; + bool angleChanged = false; + num checkAngle; + CircularChartPoint currentPoint; + final CircularChartPoint? lastPoint = + rightPoints.length > 1 ? rightPoints[rightPoints.length - 1] : null; + CircularChartPoint nextPoint; + if (lastPoint != null) { + if (lastPoint.newAngle! > 360) { + lastPoint.newAngle = lastPoint.newAngle! - 360; + // TODO(Lavanya): Recheck here. + // PointHelper.setNewAngle( + // lastPoint, PointHelper.getNewAngle(lastPoint)! - 360); + } + if (lastPoint.newAngle! > 90 && lastPoint.newAngle! < 270) { + isIncreaseAngle = true; + _changeLabelAngle(lastPoint, 89, seriesRenderer); + } + } + for (int i = rightPoints.length - 2; i >= 0; i--) { + currentPoint = rightPoints[i]; + nextPoint = rightPoints[i + 1]; + if (isOverlapWithNext(currentPoint, rightPoints, i) && + currentPoint.isVisible || + !(currentPoint.newAngle! <= 90 || currentPoint.newAngle! >= 270)) { + checkAngle = lastPoint!.newAngle! + 1; + angleChanged = true; + // If last's point change angle in beyond the limit, + //stop the increasing angle and do decrease the angle. + if (startFresh) { + isIncreaseAngle = false; + } else if (checkAngle > 90 && + checkAngle < 270 && + (nextPoint.isLabelUpdated) == 1) { + isIncreaseAngle = true; + } + if (!isIncreaseAngle) { + for (int k = i + 1; k < rightPoints.length; k++) { + _increaseAngle( + rightPoints[k - 1], + rightPoints[k], + seriesRenderer, + true, + ); + } + } else { + for (int k = i + 1; k > 0; k--) { + _decreaseAngle( + rightPoints[k], + rightPoints[k - 1], + seriesRenderer, + true, + ); + } + } + } else { + //If a point did not overlapped with previous points, + //increase the angle always for right side points. + if (angleChanged && + // ignore: unnecessary_null_comparison + nextPoint != null && + (nextPoint.isLabelUpdated) == 1) { + startFresh = true; + } + } + } +} + +/// Decrease the angle of the label if it intersects with labels. +void _decreaseAngle( + CircularChartPoint currentPoint, + CircularChartPoint previousPoint, + CircularSeriesRenderer seriesRenderer, + bool isRightSide, +) { + int count = 1; + if (isRightSide) { + while (isOverlap(currentPoint.labelRect, previousPoint.labelRect) || + (seriesRenderer.pointRadii.isNotEmpty && + (!((previousPoint.labelRect.height + previousPoint.labelRect.top) < + currentPoint.labelRect.top)))) { + int newAngle = previousPoint.newAngle!.toInt() - count; + if (newAngle < 0) { + newAngle = 360 + newAngle; + } + if (newAngle <= 270 && newAngle >= 90) { + newAngle = 270; + isIncreaseAngle = true; + break; + } + _changeLabelAngle(previousPoint, newAngle, seriesRenderer); + count++; + } + } else { + if (currentPoint.newAngle! > 270) { + _changeLabelAngle(currentPoint, 270, seriesRenderer); + previousPoint.newAngle = 270; + //PointHelper.setNewAngle(previousPoint, 270); + } + while (isOverlap(currentPoint.labelRect, previousPoint.labelRect) || + (seriesRenderer.pointRadii.isNotEmpty && + ((currentPoint.labelRect.top + currentPoint.labelRect.height) > + previousPoint.labelRect.bottom))) { + int newAngle = previousPoint.newAngle!.toInt() - count; + if (!(newAngle <= 270 && newAngle >= 90)) { + newAngle = 270; + isIncreaseAngle = true; + break; + } + _changeLabelAngle(previousPoint, newAngle, seriesRenderer); + if (isOverlap(currentPoint.labelRect, previousPoint.labelRect) && + // ignore: unnecessary_null_comparison + leftPoints.indexOf(previousPoint) == null && + (newAngle - 1 < 90 && newAngle - 1 > 270)) { + _changeLabelAngle( + currentPoint, + currentPoint.newAngle! + 1, + seriesRenderer, + ); + _arrangeLeftSidePoints(seriesRenderer); + break; + } + count++; + } + } +} + +/// Increase the angle of the label if it intersects labels. +void _increaseAngle( + CircularChartPoint currentPoint, + CircularChartPoint nextPoint, + CircularSeriesRenderer seriesRenderer, + bool isRightSide, +) { + int count = 1; + if (isRightSide) { + while (isOverlap(currentPoint.labelRect, nextPoint.labelRect) || + (seriesRenderer.pointRadii.isNotEmpty && + (!((currentPoint.labelRect.top + currentPoint.labelRect.height) < + nextPoint.labelRect.top)))) { + int newAngle = nextPoint.newAngle!.toInt() + count; + if (newAngle < 270 && newAngle > 90) { + newAngle = 90; + isIncreaseAngle = true; + break; + } + _changeLabelAngle(nextPoint, newAngle, seriesRenderer); + if (isOverlap(currentPoint.labelRect, nextPoint.labelRect) && + (newAngle + 1 > 90 && newAngle + 1 < 270) && + rightPoints.indexOf(nextPoint) == rightPoints.length - 1) { + _changeLabelAngle( + currentPoint, + currentPoint.newAngle! - 1, + seriesRenderer, + ); + _arrangeRightSidePoints(seriesRenderer); + break; + } + count++; + } + } else { + while (isOverlap(currentPoint.labelRect, nextPoint.labelRect) || + (seriesRenderer.pointRadii.isNotEmpty && + (currentPoint.labelRect.top < + (nextPoint.labelRect.top + nextPoint.labelRect.height)))) { + int newAngle = nextPoint.newAngle!.toInt() + count; + if (!(newAngle < 270 && newAngle > 90)) { + newAngle = 270; + isIncreaseAngle = false; + break; + } + _changeLabelAngle(nextPoint, newAngle, seriesRenderer); + count++; + } + } +} + +/// Change the label angle based on the given new angle. +void _changeLabelAngle( + CircularChartPoint currentPoint, + num newAngle, + CircularSeriesRenderer seriesRenderer, +) { + // TODO(Lavanya): Code cleanup for seriesRenderer field. + + const String defaultConnectorLineLength = '10%'; + final DataLabelSettings dataLabelSettings = seriesRenderer.dataLabelSettings; + final RenderChartPlotArea parent = seriesRenderer.parent!; + final TextStyle dataLabelStyle = + parent.themeData!.textTheme.bodySmall! + ..merge(parent.chartThemeData!.dataLabelTextStyle) + ..merge(dataLabelSettings.textStyle); + // Builder check for change the angle based on the template size. + final Size textSize = + dataLabelSettings.builder != null + ? currentPoint.dataLabelSize + : measureText(currentPoint.text!, dataLabelStyle); + final Path angleChangedConnectorPath = Path(); + final num connectorLength = + percentToValue( + dataLabelSettings.connectorLineSettings.length ?? + defaultConnectorLineLength, + currentPoint.outerRadius!, + )!; + final Offset startPoint = calculateOffset( + newAngle.toDouble(), + currentPoint.outerRadius!.toDouble(), + currentPoint.center!, + ); + final Offset endPoint = calculateOffset( + newAngle.toDouble(), + currentPoint.outerRadius!.toDouble() + connectorLength, + currentPoint.center!, + ); + angleChangedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + if (dataLabelSettings.connectorLineSettings.type == ConnectorType.line) { + angleChangedConnectorPath.lineTo(endPoint.dx, endPoint.dy); + } + + // TODO(Lavanya): Recheck label rect position here. + currentPoint.labelRect = + getDataLabelRect( + currentPoint.dataLabelPosition, + seriesRenderer.dataLabelSettings.connectorLineSettings.type, + dataLabelSettings.margin, + angleChangedConnectorPath, + endPoint, + textSize, + )!; + + // TODO(Lavanya): Recheck connector line here. + currentPoint.connectorPath = angleChangedConnectorPath; + currentPoint.isLabelUpdated = 1; + currentPoint.newAngle = newAngle; +} + +/// To find the labels are intersect. +bool isOverlap(Rect currentRect, Rect rect) { + return currentRect.left < rect.left + rect.width && + currentRect.left + currentRect.width > rect.left && + currentRect.top < (rect.top + rect.height) && + (currentRect.height + currentRect.top) > rect.top; +} + +/// To find the current point overlapped with previous points. +bool isOverlapWithPrevious( + CircularChartPoint currentPoint, + List points, + int currentPointIndex, +) { + for (int i = 0; i < currentPointIndex; i++) { + if (i != points.indexOf(currentPoint) && + points[i].isVisible && + isOverlap(currentPoint.labelRect, points[i].labelRect)) { + return true; + } + } + return false; +} + +/// To find the current point overlapped with next points. +bool isOverlapWithNext( + CircularChartPoint point, + List points, + int pointIndex, +) { + for (int i = pointIndex; i < points.length; i++) { + if (i != points.indexOf(point) && + points[i].isVisible && + // ignore: unnecessary_null_comparison + (points[i].labelRect != null && point.labelRect != null) && + isOverlap(point.labelRect, points[i].labelRect)) { + return true; + } + } + return false; +} + +/// Calculate the connected line path for shifted data label. +Offset getPerpendicularDistance(Offset startPoint, CircularChartPoint point) { + Offset increasedLocation; + const num add = 10; + final num height = add + 10 * sin(point.midAngle! * pi / 360); + if (point.midAngle! > 270 && point.midAngle! < 360) { + increasedLocation = Offset( + startPoint.dx + height * (cos((360 - point.midAngle!) * pi / 180)), + startPoint.dy - height * (sin((360 - point.midAngle!) * pi / 180)), + ); + } else if (point.midAngle! > 0 && point.midAngle! < 90) { + increasedLocation = Offset( + startPoint.dx + height * (cos(point.midAngle! * pi / 180)), + startPoint.dy + height * (sin(point.midAngle! * pi / 180)), + ); + } else if (point.midAngle! > 0 && point.midAngle! < 90) { + increasedLocation = Offset( + startPoint.dx - height * (cos((point.midAngle! - 90) * pi / 180)), + startPoint.dy + height * (sin((point.midAngle! - 90) * pi / 180)), + ); + } else { + increasedLocation = Offset( + startPoint.dx - height * (cos((point.midAngle! - 180) * pi / 180)), + startPoint.dy - height * (sin((point.midAngle! - 180) * pi / 180)), + ); + } + return increasedLocation; +} + +/// To trim the text by given width. +String getTrimmedText( + String text, + num labelsExtent, + TextStyle labelStyle, { + bool? isRtl, +}) { + String label = text; + + num size = measureText(label, labelStyle).width; + if (size > labelsExtent) { + final int textLength = text.length; + if (isRtl ?? false) { + for (int i = 0; i < textLength - 1; i++) { + label = '...${text.substring(i + 1, textLength)}'; + size = measureText(label, labelStyle).width; + if (size <= labelsExtent) { + return label == '...' ? '' : label; + } + } + } else { + for (int i = textLength - 1; i >= 0; --i) { + label = '${text.substring(0, i)}...'; + size = measureText(label, labelStyle).width; + if (size <= labelsExtent) { + return label == '...' ? '' : label; + } + } + } + } + return label == '...' ? '' : label; +} + +/// To shift the data label template in the circular chart. +void shiftCircularDataLabelTemplate( + CircularSeriesRenderer seriesRenderer, + List widgets, +) { + if (seriesRenderer is RadialBarSeriesRenderer) { + return; + } + + leftPoints = []; + rightPoints = []; + final List points = []; + final List renderDataLabelRegions = []; + const int labelPadding = 2; + final List templates = widgets; + + for (int i = 0; i < templates.length; i++) { + final CircularChartPoint point = templates[i].point!; + + if (point.newAngle == null && point.isVisible) { + // For the data label position is inside. + if (seriesRenderer.dataLabelSettings.labelPosition == + ChartDataLabelPosition.inside) { + Offset labelLocation = calculateOffset( + point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, + point.center!, + ); + // TODO(Lavanya): Recheck here. + // labelLocation = Offset(labelLocation.dx - (rectSize[i].width / 2), + // labelLocation.dy - (rectSize[i].height / 2)); + labelLocation = Offset( + labelLocation.dx - (point.labelRect.width / 2), + labelLocation.dy - (point.labelRect.height / 2), + ); + final Rect rect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + point.labelRect.width + (2 * labelPadding), + point.labelRect.height + (2 * labelPadding), + ); + // If collide with label when the position is inside calculate the outside rect value of that perticular label. + if (findingCollision(rect, renderDataLabelRegions)) { + _renderOutsideDataLabelTemplate( + point, + seriesRenderer, + point.labelRect.size, + renderDataLabelRegions, + ); + } else { + point.renderPosition = ChartDataLabelPosition.inside; + point.labelRect = rect; + // Stored the region of template rect to compare with next label. + renderDataLabelRegions.add(rect); + } + } else if (seriesRenderer.dataLabelSettings.labelPosition == + ChartDataLabelPosition.outside) { + _renderOutsideDataLabelTemplate( + point, + seriesRenderer, + point.labelRect.size, + renderDataLabelRegions, + ); + } + } + } + + for (int i = 0; i < templates.length; i++) { + final CircularChartPoint point = templates[i].point!; + points.add(point); + if (point.isVisible) { + point.newAngle = point.midAngle; + if (point.dataLabelPosition == Position.left && + point.renderPosition == ChartDataLabelPosition.outside) { + leftPoints.add(point); + } else if (point.dataLabelPosition == Position.right && + point.renderPosition == ChartDataLabelPosition.outside) { + rightPoints.add(point); + } + } + } + leftPoints.sort( + (CircularChartPoint a, CircularChartPoint b) => + a.newAngle!.compareTo(b.newAngle!), + ); + if (leftPoints.isNotEmpty) { + _arrangeLeftSidePoints(seriesRenderer); + } + isIncreaseAngle = false; + if (rightPoints.isNotEmpty) { + _arrangeRightSidePoints(seriesRenderer); + } + int pointIndex = 0; + + // Iterate the template for avoid the hidden data points and get the visible points. + while (pointIndex < templates.length) { + final CircularDataLabelBoxParentData child = templates[pointIndex]; + + final CircularChartPoint point = child.point!; + if (point.isVisible) { + final EdgeInsets margin = seriesRenderer.dataLabelSettings.margin; + final Rect rect = point.labelRect; + Offset labelLocation = point.labelLocation; + final Size templateSize = point.labelRect.size; + labelLocation = Offset( + rect.left + + (point.renderPosition == ChartDataLabelPosition.inside + ? labelPadding + : margin.left), + rect.top + margin.top, + ); + const String defaultConnectorLineLength = '10%'; + final Path shiftedConnectorPath = Path(); + final num connectorLength = + percentToValue( + seriesRenderer.dataLabelSettings.connectorLineSettings.length ?? + defaultConnectorLineLength, + point.outerRadius!, + )!; + final Offset startPoint = calculateOffset( + (point.startAngle! + point.endAngle!) / 2, + point.outerRadius!.toDouble(), + point.center!, + ); + final Offset endPoint = calculateOffset( + point.newAngle!.toDouble(), + (point.outerRadius! + connectorLength).toDouble(), + point.center!, + ); + shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + if (seriesRenderer.dataLabelSettings.connectorLineSettings.type == + ConnectorType.line) { + shiftedConnectorPath.lineTo(endPoint.dx, endPoint.dy); + } + + getDataLabelRect( + point.dataLabelPosition, + seriesRenderer.dataLabelSettings.connectorLineSettings.type, + margin, + shiftedConnectorPath, + endPoint, + templateSize, + )!; + + point.connectorPath = + point.renderPosition == ChartDataLabelPosition.outside + ? shiftedConnectorPath + : null; + + final Rect containerRect = seriesRenderer.paintBounds; + + if (isTemplateWithinBounds(containerRect, rect) && + !isOverlapWithPrevious(point, points, pointIndex) && + rect != Rect.zero) { + point.isVisible = true; + point.labelRect = rect; + point.labelLocation = labelLocation; + child.offset = labelLocation; + } else { + point.isVisible = false; + } + } + pointIndex++; + } +} + +/// To check template is within bounds. +bool isTemplateWithinBounds(Rect bounds, Rect templateRect) => + templateRect.left >= bounds.left && + templateRect.left + templateRect.width <= bounds.left + bounds.width && + templateRect.top >= bounds.top && + templateRect.top + templateRect.height <= bounds.top + bounds.height; + +// Calculate the data label rectangle value when the data label template +// position is outside and it consider the outer radius. +void _renderOutsideDataLabelTemplate( + CircularChartPoint point, + CircularSeriesRenderer seriesRenderer, + Size templateSize, + List renderDataLabelRegion, +) { + Path connectorPath; + const String defaultConnectorLineLength = '10%'; + final EdgeInsets margin = seriesRenderer.dataLabelSettings.margin; + final ConnectorLineSettings connector = + seriesRenderer.dataLabelSettings.connectorLineSettings; + connectorPath = Path(); + final num connectorLength = + percentToValue( + connector.length ?? defaultConnectorLineLength, + point.outerRadius!, + )!; + final Offset startPoint = calculateOffset( + point.midAngle!, + point.outerRadius!.toDouble(), + point.center!, + ); + final Offset endPoint = calculateOffset( + point.midAngle!, + (point.outerRadius! + connectorLength).toDouble(), + point.center!, + ); + connectorPath.moveTo(startPoint.dx, startPoint.dy); + if (connector.type == ConnectorType.line) { + connectorPath.lineTo(endPoint.dx, endPoint.dy); + } + point.dataLabelSize = templateSize; + final Rect rect = + getDataLabelRect( + point.dataLabelPosition, + connector.type, + margin, + connectorPath, + endPoint, + templateSize, + )!; + point.connectorPath = connectorPath; + point.labelRect = rect; + point.renderPosition = ChartDataLabelPosition.outside; + renderDataLabelRegions.add(rect); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/connector_line.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/connector_line.dart new file mode 100644 index 000000000..16499c009 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/connector_line.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; + +import '../common/data_label.dart'; +import '../utils/enum.dart'; + +/// This class holds the properties of the connector line. +/// +/// ConnectorLineSetting is the Argument type of [DataLabelSettings], It is used to customize the data label connected lines while the data label +/// position is outside the chart. It is enabled by setting the data label visibility. +/// +/// It provides the options for length, width, color, and enum type [ConnectorType] to customize the appearance. +/// +class ConnectorLineSettings { + /// Creating an argument constructor of ConnectorLineSettings class. + const ConnectorLineSettings({ + this.length, + double? width, + ConnectorType? type, + this.color, + }) : width = width ?? 1.0, + type = type ?? ConnectorType.line; + + /// Length of the connector line. The value range from 0% to 100%. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// length: '8%' + /// ) + /// ) + /// ) + /// ] + /// ) + /// ); + /// } + /// ``` + final String? length; + + /// Width of the connector line. + /// + /// Defaults to `1.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// width: 2 + /// ) + /// ) + /// ) + /// ] + /// ) + /// ); + /// } + /// ``` + final double width; + + /// Color of the connector line. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// color: Colors.red, + /// ) + /// ) + /// ) + /// ] + /// ) + /// ); + /// } + /// ``` + final Color? color; + + /// Type of the connector line. + /// + /// Defaults to `ConnectorType.line`. + /// + /// Also refer [ConnectorType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// type: ConnectorType.curve + /// ) + /// ) + /// ) + /// ] + /// ) + /// ); + /// } + /// ``` + final ConnectorType type; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart new file mode 100644 index 000000000..4999969fe --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart @@ -0,0 +1,2065 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui' as ui; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart' as shape_helper; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../series/chart_series.dart'; +import '../utils/helper.dart'; +import 'element_widget.dart'; + +/// Callback which is used to generate a widget legend item. +typedef LegendItemBuilder = Widget Function(BuildContext context, int index); + +/// Callback which is used to pass the toggled details to target. +typedef LegendItemTapCallback = void Function(LegendItem, bool); + +/// Callback which returns toggled indices and teh current toggled index. +typedef ToggledIndicesChangedCallback = + void Function(List indices, int currentIndex); + +/// Called with the details of single legend item. +typedef ItemRenderCallback = void Function(ItemRendererDetails); + +/// Signature to return a [Widget] for the given value. +typedef LegendPointerBuilder = + Widget Function(BuildContext context, dynamic value); + +/// Positions the legend in the different directions. +enum LegendPosition { + /// Places the legend at left. + left, + + /// Places the legend at right. + right, + + /// Places the legend at top. + top, + + /// Places the legend at bottom. + bottom, +} + +/// Behavior of the legend items when it overflows. +enum LegendOverflowMode { + /// 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, + + /// It will wrap and place the remaining legend items to next + /// line with scrolling. + wrapScroll, + + /// Exceeding items will be clipped. + none, +} + +/// Option to place the labels either between the bars or +/// on the bar in bar legend. +enum LegendLabelsPlacement { + /// [LegendLabelsPlacement.Item] places labels in the center + /// of the bar. + onItem, + + /// [LegendLabelsPlacement.betweenItems] places labels + /// in-between two bars. + betweenItems, +} + +/// Placement of edge labels in the bar legend. +enum LegendEdgeLabelsPlacement { + /// 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, +} + +/// Behavior of the labels when it overflowed from the shape. +enum LegendLabelOverflow { + /// 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, +} + +/// Applies gradient or solid color for the bar segments. +enum LegendPaintingStyle { + /// Applies solid color for bar segments. + solid, + + /// Applies gradient color for bar segments. + gradient, +} + +/// Specifies the alignment of legend. +enum LegendAlignment { + /// Denotes near. + near, + + /// Denotes center. + center, + + /// Denotes far. + far, +} + +/// Specifies the visibility of the legend. +enum LegendScrollbarVisibility { + /// Denotes always visible. + visible, + + /// Denotes always hidden. + hidden, + + /// Denotes auto. + auto, +} + +/// The legend provider. +mixin LegendItemProviderMixin { + /// Builds the legend item. + List? buildLegendItems(int index); + + /// Returns the legend icon type. + ShapeMarkerType effectiveLegendIconType(); +} + +/// Details of single legend item. +class ItemRendererDetails { + /// Creates [ItemRendererDetails]. + ItemRendererDetails({ + required this.item, + required this.index, + required this.text, + required this.color, + required this.iconType, + required this.iconBorderColor, + required this.iconBorderWidth, + }); + + /// Hold complete details about the item. + final LegendItem item; + + /// Index of the legend item. + final int index; + + /// Particular legend item text. + String text; + + /// Particular legend icon color. + Color? color; + + /// Particular legend icon type. + ShapeMarkerType iconType; + + /// Border color of the icon. + Color? iconBorderColor; + + /// Border width of the icon. + double? iconBorderWidth; +} + +/// Represents the class of items in legends. +abstract class LegendItem { + /// Creates a [LegendItem]. + LegendItem({ + required this.text, + required this.iconType, + required this.iconColor, + required this.iconBorderWidth, + required this.seriesIndex, + this.iconBorderColor, + this.shader, + this.imageProvider, + this.onTap, + this.onRender, + this.isToggled = false, + this.overlayMarkerType, + this.degree, + this.endAngle, + this.startAngle, + this.series, + this.pointIndex = -1, + }); + + /// Specifies the text of the legend. + final String text; + + /// Specifies the color of the icon. + final Color iconColor; + + /// Specifies the border of the icon. + final Color? iconBorderColor; + + /// Specifies the border width of icon. + final double? iconBorderWidth; + + /// Specifies the shader of the icon. + final Shader? shader; + + /// Identifies an image. + final ImageProvider? imageProvider; + + /// Specifies the type of the icon. + final ShapeMarkerType iconType; + + /// Specifies the overlay marker for cartesian line type icon. + final ShapeMarkerType? overlayMarkerType; + + /// Specifies the start angle for radial bar icon. + final double? startAngle; + + /// Specifies the degree for radial bar icon. + final double? degree; + + /// Specifies the end angle for radial bar icon. + final double? endAngle; + + /// Invoked when tapping the legend item. + final LegendItemTapCallback? onTap; + + /// Specifies whether the current item is toggled. + final bool isToggled; + + /// Invoked when the item is created. + final ItemRenderCallback? onRender; + + /// Specifies the legend item is tapped. + VoidCallback? onToggled; + + /// Specifies the series associated with the legend item. + final ChartSeriesRenderer? series; + + /// Specifies the index of the series associated with the legend item. + final int seriesIndex; + + /// Specifies the index of the data point in the series. + final int pointIndex; +} + +/// The base layout for the legend dependents. +class LegendLayout extends StatefulWidget { + /// Constructor for the [LegendLayout]. + const LegendLayout({ + Key? key, + this.showLegend = false, + this.padding = const EdgeInsets.all(10), + this.backgroundImage, + this.backgroundColor, + this.borderColor, + this.borderWidth = 0.7, + this.legendItems, + this.legendItemBuilder, + this.legendWidthFactor = 0.3, + this.legendHeightFactor = 0.3, + this.legendPosition = LegendPosition.top, + this.legendWrapDirection = Axis.horizontal, + this.legendOverflowMode = LegendOverflowMode.wrap, + this.legendFloatingOffset, + this.legendAlignment = LegendAlignment.center, + this.legendTitleAlignment = LegendAlignment.center, + this.legendBorderColor, + this.legendBackgroundColor, + this.legendBorderWidth = 1.0, + this.legendTitle, + this.itemsScrollDirection = Axis.vertical, + this.itemInnerSpacing = 10.0, + this.itemSpacing = 10.0, + this.itemPadding = 0.0, + this.itemRunSpacing = 6.0, + this.itemIconHeight = 8.0, + this.itemIconWidth = 8.0, + this.itemOpacity = 1.0, + this.itemIconBorderColor, + this.itemIconBorderWidth, + required this.itemTextStyle, + this.scrollbarVisibility = LegendScrollbarVisibility.auto, + this.enableToggling = false, + this.toggledIconColor, + this.toggledTextOpacity = 0.5, + this.toggledItemColor, + this.isResponsive = false, + this.onTouchDown, + this.onTouchMove, + this.onTouchUp, + required this.child, + }) : super(key: key); + + /// Specifies whether to shows or hides the legend. + final bool showLegend; + + /// Specifies the legend items. + final List? legendItems; + + /// Widget builder for legend items. + final LegendItemBuilder? legendItemBuilder; + + /// Specifies the width of legend. + final double legendWidthFactor; + + /// Specifies the height of legend. + final double legendHeightFactor; + + /// Places the legend in custom position. + final Offset? legendFloatingOffset; + + /// Positions the legend in the different directions. + final LegendPosition legendPosition; + + /// Empty space inside the decoration. + final EdgeInsets padding; + + /// Empty padding around the legend item. + final double itemPadding; + + /// A border to draw surround the legend layout. + final Color? borderColor; + + /// A border to draw surround the legend layout. + final double borderWidth; + + /// Specifies the child. + final Widget child; + + /// Specifies the alignment of legend. + final LegendAlignment legendAlignment; + + /// The background image to the legend layout. + final ImageProvider? backgroundImage; + + /// The background color to the legend. + final Color? backgroundColor; + + /// A border color to the item icon. + final Color? itemIconBorderColor; + + /// A border width to the item icon. + final double? itemIconBorderWidth; + + /// A border to draw surround the legend. + final Color? legendBorderColor; + + /// A legend background. + final Color? legendBackgroundColor; + + /// A border to draw surround the legend. + final double legendBorderWidth; + + /// Opacity of the item. + final double itemOpacity; + + /// Specifies the space between the legend text and the icon. + final double itemInnerSpacing; + + /// Specifies the cross axis run spacing for the wrapped elements. + final double itemRunSpacing; + + /// Specifies the size of the legend icon. + final double itemIconHeight; + + /// Specifies the size of the legend icon. + final double itemIconWidth; + + /// Toggles the scrollbar visibility. + final LegendScrollbarVisibility scrollbarVisibility; + + /// Customizes the legend item's text style. + final TextStyle itemTextStyle; + + /// Avoid the legend rendering if its size is greater than its child. + final bool isResponsive; + + /// Arranges the legend items in either horizontal or vertical direction. + final Axis legendWrapDirection; + + /// Scroll the legend items in either horizontal or vertical direction. + final Axis? itemsScrollDirection; + + /// The title which provide a small note about the legends. + final Widget? legendTitle; + + /// Specifies the alignment of legend title. + final LegendAlignment legendTitleAlignment; + + /// Wraps or scrolls the legend items when it overflows. + final LegendOverflowMode legendOverflowMode; + + /// Specifies the space between the each legend items. + final double itemSpacing; + + /// Specifies whether to enable toggling. + final bool enableToggling; + + /// Specifies the toggle item color. + final Color? toggledIconColor; + + /// Specifies the toggle item's color. Applicable for vector builder. + final Color? toggledItemColor; + + /// Specifies the toggle item's text color opacity. + final double toggledTextOpacity; + + /// Called when touch down. + final Function(Offset)? onTouchDown; + + /// Called when touch move. + final Function(Offset)? onTouchMove; + + /// Called when touch up. + final Function(Offset)? onTouchUp; + + @override + State createState() => LegendLayoutState(); +} + +/// State of [LegendLayout]. +class LegendLayoutState extends State { + late GlobalKey _legendKey; + late GlobalKey _plotAreaKey; + + List? _items; + + LegendPosition _effectiveLegendPosition( + LegendPosition position, + TextDirection direction, + ) { + if (position == LegendPosition.top || position == LegendPosition.bottom) { + return position; + } + + if (direction == TextDirection.rtl) { + if (position == LegendPosition.left) { + return LegendPosition.right; + } + if (position == LegendPosition.right) { + return LegendPosition.left; + } + } + + return position; + } + + /// Updates the new items to legend. + void update(List items) { + _items = items; + final RenderObjectElement? legendElement = + _legendKey.currentContext as RenderObjectElement?; + if (legendElement != null && + legendElement.mounted && + legendElement.renderObject.attached) { + final RenderObject? renderObject = legendElement.findRenderObject(); + if (renderObject != null && + renderObject.attached && + renderObject is CustomRenderConstrainedLayoutBuilder) { + renderObject.markNeedsBuild(); + } + } + } + + @override + void initState() { + _legendKey = GlobalKey(); + _plotAreaKey = GlobalKey(); + _items = widget.legendItems; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final Widget? legend = + widget.showLegend + ? CustomLayoutBuilder( + key: _legendKey, + builder: (BuildContext context, BoxConstraints constraints) { + return _buildLegend(); + }, + ) + : null; + + return _LegendLayoutHandler( + onTouchDown: widget.onTouchDown, + onTouchMove: widget.onTouchMove, + onTouchUp: widget.onTouchUp, + backgroundImage: widget.backgroundImage, + legendBackgroundColor: widget.legendBackgroundColor, + legendBorderColor: widget.legendBorderColor, + legendBorderWidth: widget.legendBorderWidth, + legendWidthFactor: widget.legendWidthFactor, + legendHeightFactor: widget.legendHeightFactor, + legendFloatingOffset: widget.legendFloatingOffset, + legendPosition: _effectiveLegendPosition( + widget.legendPosition, + Directionality.of(context), + ), + legendAlignment: widget.legendAlignment, + padding: widget.padding, + backgroundColor: widget.backgroundColor, + borderColor: widget.borderColor, + borderWidth: widget.borderWidth, + isResponsive: widget.isResponsive, + legendTitleAlignment: widget.legendTitleAlignment, + legendTitle: widget.legendTitle, + legend: legend, + // TODO(VijayakumarM): Testing needed. + plotArea: KeyedSubtree(key: _plotAreaKey, child: widget.child), + ); + } + + Widget _buildLegend() { + return Padding( + padding: EdgeInsets.symmetric(horizontal: widget.itemPadding / 2), + child: _VectorLegend( + title: widget.legendTitle, + items: _items, + direction: widget.legendWrapDirection, + itemSpacing: widget.itemSpacing, + itemRunSpacing: widget.itemRunSpacing, + overflowMode: widget.legendOverflowMode, + scrollbarVisibility: widget.scrollbarVisibility, + iconBorderColor: widget.itemIconBorderColor, + iconBorderWidth: widget.itemIconBorderWidth, + iconSize: Size(widget.itemIconWidth, widget.itemIconHeight), + itemBuilder: widget.legendItemBuilder, + spacing: widget.itemInnerSpacing, + itemPadding: widget.itemPadding, + textStyle: widget.itemTextStyle, + enableToggling: widget.enableToggling, + toggledIconColor: widget.toggledIconColor, + toggledItemColor: widget.toggledItemColor, + toggledTextOpacity: widget.toggledTextOpacity, + itemIconOpacity: widget.itemOpacity, + ), + ); + } +} + +enum _LegendSlot { legendTitle, legend, plotArea } + +class _LegendLayoutHandler + extends SlottedMultiChildRenderObjectWidget<_LegendSlot, RenderBox> { + const _LegendLayoutHandler({ + Key? key, + this.onTouchDown, + this.onTouchMove, + this.onTouchUp, + this.backgroundImage, + this.legendBackgroundColor, + this.legendBorderColor, + this.legendBorderWidth = 1.0, + required this.legendWidthFactor, + required this.legendHeightFactor, + required this.legendFloatingOffset, + required this.legendPosition, + required this.legendAlignment, + required this.legendTitleAlignment, + required this.padding, + required this.borderColor, + required this.backgroundColor, + required this.borderWidth, + required this.isResponsive, + required this.legendTitle, + required this.legend, + required this.plotArea, + }) : super(key: key); + + final Function(Offset)? onTouchDown; + final Function(Offset)? onTouchMove; + final Function(Offset)? onTouchUp; + final ImageProvider? backgroundImage; + final Color? legendBackgroundColor; + final Color? legendBorderColor; + final double legendBorderWidth; + final double legendWidthFactor; + final double legendHeightFactor; + final Offset? legendFloatingOffset; + final LegendPosition legendPosition; + final LegendAlignment legendAlignment; + final LegendAlignment legendTitleAlignment; + final EdgeInsets padding; + final Color? backgroundColor; + final Color? borderColor; + final double borderWidth; + final bool isResponsive; + final Widget? legendTitle; + final Widget? legend; + final Widget? plotArea; + + @override + _RenderLegendLayoutHandler createRenderObject(BuildContext context) { + return _RenderLegendLayoutHandler( + backgroundImage: backgroundImage, + legendBackgroundColor: legendBackgroundColor, + legendBorderColor: legendBorderColor, + legendBorderWidth: legendBorderWidth, + legendWidthFactor: legendWidthFactor, + legendHeightFactor: legendHeightFactor, + legendFloatingOffset: legendFloatingOffset, + legendPosition: legendPosition, + legendAlignment: legendAlignment, + legendTitleAlignment: legendTitleAlignment, + padding: padding, + backgroundColor: backgroundColor, + borderColor: borderColor, + borderWidth: borderWidth, + isResponsive: isResponsive, + ) + ..onTouchDown = onTouchDown + ..onTouchMove = onTouchMove + ..onTouchUp = onTouchUp; + } + + @override + void updateRenderObject( + BuildContext context, + _RenderLegendLayoutHandler renderObject, + ) { + renderObject + ..backgroundImage = backgroundImage + ..legendBackgroundColor = legendBackgroundColor + ..legendBorderColor = legendBorderColor + ..legendBorderWidth = legendBorderWidth + ..legendWidthFactor = legendWidthFactor + ..legendHeightFactor = legendHeightFactor + ..legendFloatingOffset = legendFloatingOffset + ..legendPosition = legendPosition + ..legendAlignment = legendAlignment + ..legendTitleAlignment = legendTitleAlignment + ..padding = padding + ..backgroundColor = backgroundColor + ..borderColor = borderColor + ..borderWidth = borderWidth + ..isResponsive = isResponsive + ..onTouchDown = onTouchDown + ..onTouchMove = onTouchMove + ..onTouchUp = onTouchUp; + } + + @override + Iterable<_LegendSlot> get slots => _LegendSlot.values; + + @override + Widget? childForSlot(_LegendSlot slot) { + switch (slot) { + case _LegendSlot.legendTitle: + return legendTitle; + case _LegendSlot.legend: + return legend; + case _LegendSlot.plotArea: + return plotArea; + } + } +} + +class _LegendLayoutParentData extends ContainerBoxParentData {} + +class _RenderLegendLayoutHandler extends RenderBox + with SlottedContainerRenderObjectMixin<_LegendSlot, RenderBox> { + _RenderLegendLayoutHandler({ + ImageProvider? backgroundImage, + required Color? legendBackgroundColor, + required Color? legendBorderColor, + required double legendBorderWidth, + required double legendWidthFactor, + required double legendHeightFactor, + required Offset? legendFloatingOffset, + required LegendPosition legendPosition, + required LegendAlignment legendAlignment, + required LegendAlignment legendTitleAlignment, + required EdgeInsets padding, + required Color? backgroundColor, + required Color? borderColor, + required double borderWidth, + required bool isResponsive, + }) : _backgroundImage = backgroundImage, + _legendBackgroundColor = legendBackgroundColor, + _legendBorderColor = legendBorderColor, + _legendBorderWidth = legendBorderWidth, + _legendWidthFactor = legendWidthFactor, + _legendHeightFactor = legendHeightFactor, + _legendFloatingOffset = legendFloatingOffset, + _isLegendFloating = legendFloatingOffset != null, + _legendPosition = legendPosition, + _legendAlignment = legendAlignment, + _legendTitleAlignment = legendTitleAlignment, + _padding = padding, + _backgroundColor = backgroundColor, + _borderColor = borderColor, + _borderWidth = borderWidth, + _isResponsive = isResponsive { + _fetchImage(); + } + + final double _spaceBetweenLegendAndPlotArea = 5.0; + bool _legendNeedsPaint = false; + bool _isLegendFloating = false; + Image? _image; + + RenderBox? get legendTitle => childForSlot(_LegendSlot.legendTitle); + RenderBox? get legend => childForSlot(_LegendSlot.legend); + RenderBox? get plotArea => childForSlot(_LegendSlot.plotArea); + + Function(Offset)? onTouchDown; + Function(Offset)? onTouchMove; + Function(Offset)? onTouchUp; + + @override + Iterable get children { + return [ + if (plotArea != null) plotArea!, + if (legendTitle != null) legendTitle!, + if (legend != null) legend!, + ]; + } + + Color? get legendBorderColor => _legendBorderColor; + Color? _legendBorderColor; + set legendBorderColor(Color? value) { + if (_legendBorderColor != value) { + _legendBorderColor = value; + markNeedsPaint(); + } + } + + ImageProvider? get backgroundImage => _backgroundImage; + ImageProvider? _backgroundImage; + set backgroundImage(ImageProvider? value) { + if (_backgroundImage != value) { + _backgroundImage = value; + _fetchImage(); + } + } + + Color? get legendBackgroundColor => _legendBackgroundColor; + Color? _legendBackgroundColor; + set legendBackgroundColor(Color? value) { + if (_legendBackgroundColor != value) { + _legendBackgroundColor = value; + markNeedsPaint(); + } + } + + double get legendBorderWidth => _legendBorderWidth; + double _legendBorderWidth = 1.0; + set legendBorderWidth(double value) { + if (_legendBorderWidth != value) { + _legendBorderWidth = value; + markNeedsPaint(); + } + } + + double get legendWidthFactor => _legendWidthFactor; + double _legendWidthFactor = 0.3; + set legendWidthFactor(double value) { + if (_legendWidthFactor != value) { + _legendWidthFactor = value; + markNeedsLayout(); + } + } + + double get legendHeightFactor => _legendHeightFactor; + double _legendHeightFactor = 0.3; + set legendHeightFactor(double value) { + if (_legendHeightFactor != value) { + _legendHeightFactor = value; + markNeedsLayout(); + } + } + + Offset? get legendFloatingOffset => _legendFloatingOffset; + Offset? _legendFloatingOffset; + set legendFloatingOffset(Offset? value) { + if (_legendFloatingOffset != value) { + _legendFloatingOffset = value; + _isLegendFloating = value != null; + markNeedsLayout(); + } + } + + LegendPosition get legendPosition => _legendPosition; + LegendPosition _legendPosition; + set legendPosition(LegendPosition value) { + if (_legendPosition != value) { + _legendPosition = value; + markNeedsLayout(); + } + } + + LegendAlignment get legendAlignment => _legendAlignment; + LegendAlignment _legendAlignment; + set legendAlignment(LegendAlignment value) { + if (_legendAlignment != value) { + _legendAlignment = value; + markNeedsLayout(); + } + } + + LegendAlignment get legendTitleAlignment => _legendTitleAlignment; + LegendAlignment _legendTitleAlignment; + set legendTitleAlignment(LegendAlignment value) { + if (_legendTitleAlignment != value) { + _legendTitleAlignment = value; + markNeedsLayout(); + } + } + + EdgeInsets get padding => _padding; + EdgeInsets _padding; + set padding(EdgeInsets value) { + if (_padding != value) { + _padding = value; + markNeedsLayout(); + } + } + + Color? get backgroundColor => _backgroundColor; + Color? _backgroundColor; + set backgroundColor(Color? value) { + if (_backgroundColor != value) { + _backgroundColor = value; + markNeedsPaint(); + } + } + + Color? get borderColor => _borderColor; + Color? _borderColor; + set borderColor(Color? value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + double get borderWidth => _borderWidth; + double _borderWidth = 0.7; + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsPaint(); + } + } + + bool get isResponsive => _isResponsive; + bool _isResponsive = false; + set isResponsive(bool value) { + if (_isResponsive != value) { + _isResponsive = value; + } + } + + void _fetchImage() { + if (backgroundImage != null) { + fetchImage(backgroundImage).then((Image? value) { + _image = value; + markNeedsPaint(); + }); + } else { + _image = null; + } + } + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! _LegendLayoutParentData) { + child.parentData = _LegendLayoutParentData(); + } + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + final Iterable<_LegendSlot> slots = + _isLegendFloating ? _LegendSlot.values : _LegendSlot.values.reversed; + for (final _LegendSlot slot in slots) { + final RenderBox? child = childForSlot(slot); + if (child != null) { + final BoxParentData childParentData = + child.parentData! as BoxParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - childParentData.offset); + return child.hitTest(result, position: transformed); + }, + ); + if (isHit) { + return true; + } + } + } + + return false; + } + + @override + void performLayout() { + if (plotArea == null) { + size = constraints.biggest; + return; + } + + final double desiredWidth = + constraints.maxWidth.isInfinite ? 300 : constraints.maxWidth; + final double desiredHeight = + constraints.maxHeight.isInfinite ? 300 : constraints.maxHeight; + final double availableWidth = desiredWidth - padding.horizontal; + final double availableHeight = desiredHeight - padding.vertical; + + Size legendSize = Size.zero; + double widthFactor = legendWidthFactor; + double heightFactor = legendHeightFactor; + + if (widthFactor.isNaN) { + widthFactor = 1.0; + } + + if (heightFactor.isNaN) { + heightFactor = 1.0; + } + + Size legendTitleSize = Size.zero; + _legendNeedsPaint = legend != null; + if (_legendNeedsPaint) { + BoxConstraints legendConstraints = BoxConstraints( + maxWidth: availableWidth * widthFactor, + maxHeight: availableHeight * heightFactor, + ); + + if (legendTitle != null) { + legendTitle!.layout(legendConstraints, parentUsesSize: true); + legendTitleSize = legendTitle!.size; + } + + legendConstraints = legendConstraints.copyWith( + maxWidth: availableWidth, + maxHeight: max(0, legendConstraints.maxHeight - legendTitleSize.height), + ); + legend!.layout(legendConstraints, parentUsesSize: true); + if (legend!.size.isEmpty && legendTitleSize.isEmpty) { + legendSize = Size.zero; + _legendNeedsPaint = false; + } else { + legendSize = legend!.size; + _legendNeedsPaint = true; + } + + legendSize = Size( + max(legendSize.width, legendTitleSize.width), + legendSize.height + legendTitleSize.height, + ); + } + + final double gap = + _isLegendFloating || legendSize.isEmpty + ? 0 + : _spaceBetweenLegendAndPlotArea; + late BoxConstraints plotAreaConstraints; + if (_isLegendFloating) { + plotAreaConstraints = BoxConstraints( + maxWidth: availableWidth, + maxHeight: availableHeight, + ); + } else { + switch (legendPosition) { + case LegendPosition.left: + case LegendPosition.right: + final double plotAreaWidth = availableWidth - legendSize.width - gap; + plotAreaConstraints = BoxConstraints( + maxWidth: plotAreaWidth < 0 ? 0 : plotAreaWidth, + maxHeight: availableHeight, + ); + break; + + case LegendPosition.top: + case LegendPosition.bottom: + final double plotAreaHeight = + availableHeight - legendSize.height - gap; + plotAreaConstraints = BoxConstraints( + maxWidth: availableWidth, + maxHeight: plotAreaHeight < 0 ? 0 : plotAreaHeight, + ); + break; + } + if (isResponsive) { + if (plotAreaConstraints.maxWidth < legendSize.width || + (plotAreaConstraints.maxHeight < legendSize.height || + legend!.size.height == 0)) { + _legendNeedsPaint = false; + plotAreaConstraints = BoxConstraints( + maxWidth: availableWidth, + maxHeight: availableHeight, + ); + } + } + } + + plotArea!.layout(plotAreaConstraints, parentUsesSize: true); + _alignChildren( + legendSize, + legendTitleSize, + availableWidth, + availableHeight, + ); + + size = Size(desiredWidth, desiredHeight); + } + + void _alignChildren( + Size legendSize, + Size legendTitleSize, + double availableWidth, + double availableHeight, + ) { + final double gap = + _isLegendFloating || legendSize.isEmpty + ? 0 + : _spaceBetweenLegendAndPlotArea; + final BoxParentData plotAreaParentData = + plotArea!.parentData! as BoxParentData; + if (_legendNeedsPaint) { + final BoxParentData legendParentData = + legend!.parentData! as BoxParentData; + final BoxParentData? legendTitleParentData = + legendTitle?.parentData! as BoxParentData?; + + Size legendAreaSize; + switch (legendPosition) { + case LegendPosition.left: + case LegendPosition.right: + legendAreaSize = Size(legendSize.width, availableHeight); + break; + + case LegendPosition.top: + case LegendPosition.bottom: + legendAreaSize = Size(availableWidth, legendSize.height); + break; + } + + if (_isLegendFloating) { + plotAreaParentData.offset = padding.topLeft; + + final Alignment alignmentForLegend = _effectiveLegendAlignment(); + legendParentData.offset = alignmentForLegend.alongOffset( + legendAreaSize - legendSize as Offset, + ); + switch (legendPosition) { + case LegendPosition.left: + legendParentData.offset += padding.topLeft; + break; + + case LegendPosition.top: + legendParentData.offset += padding.topLeft; + break; + + case LegendPosition.right: + legendParentData.offset = Offset( + plotArea!.size.width - legendSize.width - padding.left - gap, + legendParentData.offset.dy + padding.top, + ); + break; + + case LegendPosition.bottom: + legendParentData.offset = Offset( + legendParentData.offset.dx + padding.left, + plotArea!.size.height - legendSize.height - padding.top, + ); + break; + } + + legendTitleParentData?.offset = legendParentData.offset; + legendParentData.offset = legendParentData.offset.translate( + legendFloatingOffset!.dx, + legendFloatingOffset!.dy + legendTitleSize.height, + ); + + //Edge detection for legend. + Offset legendOffset = legendParentData.offset; + Offset legendTitleOffset = + legendTitleParentData != null + ? legendTitleParentData.offset + : Offset.zero; + final Offset parentOffset = plotAreaParentData.offset; + if (legendOffset.dx < parentOffset.dx) { + legendOffset = Offset(parentOffset.dx, legendOffset.dy); + legendTitleOffset = Offset(legendOffset.dx, legendTitleOffset.dy); + } else if ((legendOffset.dx + (legendSize.width + padding.left + gap)) > + plotArea!.size.width) { + legendOffset = Offset( + plotArea!.size.width - legendSize.width, + legendOffset.dy, + ); + legendTitleOffset = Offset(legendOffset.dx, legendTitleOffset.dy); + } + + if (legendOffset.dy < parentOffset.dy) { + legendOffset = Offset(legendOffset.dx, parentOffset.dy); + legendTitleOffset = Offset(legendTitleOffset.dx, legendOffset.dy); + } else if ((legendOffset.dy + (legendSize.height + padding.top + gap)) > + plotArea!.size.height) { + legendOffset = Offset( + legendOffset.dx, + plotArea!.size.height - legendSize.height + padding.top, + ); + legendTitleOffset = Offset(legendTitleOffset.dx, legendOffset.dy); + } + legendParentData.offset = legendOffset; + legendTitleParentData?.offset = + legendTitleOffset + + _legendTitleAlignmentOffset( + legendTitleAlignment, + legendTitleSize, + legendSize, + ).translate( + legendPosition == LegendPosition.right + ? 0 + : legendFloatingOffset!.dx, + legendPosition == LegendPosition.bottom + ? 0 + : legendFloatingOffset!.dy, + ); + } else { + final Alignment alignmentForLegend = _effectiveLegendAlignment(); + legendParentData.offset = alignmentForLegend.alongOffset( + legendAreaSize - legendSize as Offset, + ); + final Size legendPortionFromAvailableSize = + _isLegendFloating ? Size.zero : legendSize; + switch (legendPosition) { + case LegendPosition.left: + legendParentData.offset += padding.topLeft; + plotAreaParentData.offset = Offset( + padding.left + legendPortionFromAvailableSize.width + gap, + padding.top, + ); + break; + + case LegendPosition.top: + legendParentData.offset += padding.topLeft; + plotAreaParentData.offset = Offset( + padding.left, + padding.top + legendPortionFromAvailableSize.height + gap, + ); + break; + + case LegendPosition.right: + legendParentData.offset += Offset( + padding.left + plotArea!.size.width + gap, + padding.top, + ); + plotAreaParentData.offset = Offset(padding.left, padding.top); + break; + + case LegendPosition.bottom: + legendParentData.offset += Offset( + padding.left, + padding.top + plotArea!.size.height + gap, + ); + plotAreaParentData.offset = Offset(padding.left, padding.top); + break; + } + + legendTitleParentData?.offset = + legendParentData.offset + + _legendTitleAlignmentOffset( + legendTitleAlignment, + legendTitleSize, + legendSize, + ); + legendParentData.offset = legendParentData.offset.translate( + 0.0, + legendTitleSize.height, + ); + } + } else { + plotAreaParentData.offset = padding.topLeft; + } + } + + Offset _legendTitleAlignmentOffset( + LegendAlignment alignment, + Size titleSize, + Size areaSize, + ) { + switch (alignment) { + case LegendAlignment.near: + return Offset.zero; + case LegendAlignment.center: + return Offset(max(0.0, areaSize.width / 2 - titleSize.width / 2), 0.0); + case LegendAlignment.far: + return Offset(max(0.0, areaSize.width - titleSize.width), 0.0); + } + } + + Alignment _effectiveLegendAlignment() { + switch (legendPosition) { + case LegendPosition.left: + case LegendPosition.right: + switch (legendAlignment) { + case LegendAlignment.near: + return Alignment.topLeft; + case LegendAlignment.center: + return Alignment.centerLeft; + case LegendAlignment.far: + return Alignment.bottomLeft; + } + + case LegendPosition.bottom: + case LegendPosition.top: + switch (legendAlignment) { + case LegendAlignment.near: + return Alignment.topLeft; + case LegendAlignment.center: + return Alignment.topCenter; + case LegendAlignment.far: + return Alignment.topRight; + } + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (plotArea == null) { + return; + } + + if (backgroundColor != null && backgroundColor != Colors.transparent) { + context.canvas.drawRect( + paintBounds, + Paint() + ..isAntiAlias = true + ..color = backgroundColor!, + ); + } + + if (_image != null) { + paintImage( + canvas: context.canvas, + rect: paintBounds, + image: _image!, + fit: BoxFit.fill, + ); + } + + if (borderColor != null && + borderColor != Colors.transparent && + borderWidth > 0) { + context.canvas.drawRect( + paintBounds.deflate(borderWidth / 2), + Paint() + ..isAntiAlias = true + ..color = borderColor! + ..strokeWidth = borderWidth + ..style = PaintingStyle.stroke, + ); + } + + if (!_isLegendFloating && _legendNeedsPaint) { + _drawLegendBackgroundAndBorder(context, offset); + _paintLegend(context, offset); + } + + final BoxParentData plotAreaParentData = + plotArea!.parentData! as BoxParentData; + context.paintChild(plotArea!, offset + plotAreaParentData.offset); + + if (_isLegendFloating && _legendNeedsPaint) { + _drawLegendBackgroundAndBorder(context, offset); + _paintLegend(context, offset); + } + } + + void _drawLegendBackgroundAndBorder(PaintingContext context, Offset offset) { + if (legend != null) { + final bool canDrawLegendBackground = + legendBackgroundColor != null && + legendBackgroundColor != Colors.transparent; + final bool canDrawLegendBorder = + legendBorderColor != null && + legendBorderColor != Colors.transparent && + legendBorderWidth > 0; + if (canDrawLegendBackground || canDrawLegendBorder) { + Size legendSize = legend!.size; + final BoxParentData legendParentData = + legend!.parentData! as BoxParentData; + final Rect legendBounds = legendParentData.offset & legend!.size; + Offset legendTitleOffset = legendParentData.offset; + if (legendTitle != null) { + legendTitleOffset = + (legendTitle!.parentData! as BoxParentData).offset; + legendSize = Size( + legendSize.width, + legendTitle!.size.height + legendSize.height, + ); + } + + final Rect bounds = Rect.fromLTWH( + legendBounds.left + offset.dx, + legendTitleOffset.dy + offset.dy, + legendSize.width, + legendSize.height, + ); + if (canDrawLegendBackground) { + context.canvas.drawRect( + bounds, + Paint()..color = legendBackgroundColor!, + ); + } + + if (canDrawLegendBorder) { + context.canvas.drawRect( + bounds.deflate(legendBorderWidth / 2), + Paint() + ..color = legendBorderColor! + ..strokeWidth = legendBorderWidth + ..style = PaintingStyle.stroke, + ); + } + } + } + } + + void _paintLegend(PaintingContext context, Offset offset) { + if (legendTitle != null) { + final BoxParentData legendTitleParentData = + legendTitle!.parentData! as BoxParentData; + context.paintChild(legendTitle!, offset + legendTitleParentData.offset); + } + + final BoxParentData legendParentData = legend!.parentData! as BoxParentData; + context.paintChild(legend!, offset + legendParentData.offset); + } +} + +class _VectorLegend extends StatefulWidget { + const _VectorLegend({ + required this.title, + required this.items, + required this.direction, + required this.iconBorderColor, + required this.iconBorderWidth, + required this.iconSize, + required this.itemBuilder, + required this.spacing, + required this.itemSpacing, + required this.itemPadding, + required this.itemRunSpacing, + required this.overflowMode, + required this.scrollbarVisibility, + required this.textStyle, + required this.enableToggling, + required this.toggledIconColor, + required this.toggledItemColor, + required this.toggledTextOpacity, + required this.itemIconOpacity, + }); + + /// Specifies the legend title. + final Widget? title; + + /// Specifies the legend items. + final List? items; + + /// Specifies the size of the legend icon. + final Size iconSize; + + /// Specifies border color of the icon. + final Color? iconBorderColor; + + /// Specifies border width of the icon. + final double? iconBorderWidth; + + /// Customizes the legend item's text style. + final TextStyle textStyle; + + /// Specifies the space between the legend text and the icon. + final double spacing; + + /// Specifies the toggle item's text color opacity. + final double itemIconOpacity; + + /// Specifies the toggle item's text color opacity. + final double toggledTextOpacity; + + /// Wraps or scrolls the legend items when it overflows. + final LegendOverflowMode overflowMode; + + /// Toggles the scrollbar visibility. + final LegendScrollbarVisibility scrollbarVisibility; + + /// Widget builder for legend items. + final IndexedWidgetBuilder? itemBuilder; + + /// Specifies whether to enable toggling. + final bool enableToggling; + + /// Specifies the toggle item color. + final Color? toggledIconColor; + + /// Specifies the toggle item's color. Applicable for vector builder. + final Color? toggledItemColor; + + /// Specifies the space between the each legend items. + final double itemSpacing; + + /// Specifies the cross axis run spacing for the wrapped elements. + final double itemRunSpacing; + + /// Surrounding padding. + final double itemPadding; + + /// Arranges the legend items in either horizontal or vertical direction. + final Axis direction; + + @override + State<_VectorLegend> createState() => _VectorLegendState(); +} + +class _VectorLegendState extends State<_VectorLegend> { + ScrollController? _controller; + + List _buildLegendItems(BuildContext context) { + final List legendItems = []; + if (widget.items != null) { + final int length = widget.items!.length; + for (int i = 0; i < length; i++) { + final LegendItem item = widget.items![i]; + legendItems.add( + _IconText( + details: item, + index: i, + itemBuilder: widget.itemBuilder, + padding: EdgeInsets.all(widget.itemPadding / 2), + textStyle: widget.textStyle, + iconSize: widget.iconSize, + iconBorderColor: widget.iconBorderColor, + iconBorderWidth: widget.iconBorderWidth, + spacing: widget.spacing, + iconOpacity: widget.itemIconOpacity, + toggleEnabled: widget.enableToggling, + isToggled: item.isToggled, + toggledColor: _effectiveToggledColor(context), + toggledTextOpacity: widget.toggledTextOpacity, + onTap: item.onTap, + overlayMarkerType: item.overlayMarkerType, + degree: item.degree, + startAngle: item.startAngle, + endAngle: item.endAngle, + ), + ); + } + } + + return legendItems; + } + + Color? _effectiveToggledColor(BuildContext context) { + Color? toggledColor; + if (widget.enableToggling) { + toggledColor = widget.toggledIconColor ?? widget.toggledItemColor; + if (toggledColor == null || toggledColor == Colors.transparent) { + toggledColor = + Theme.of(context).brightness == Brightness.light + ? const Color.fromRGBO(230, 230, 230, 1) + : const Color.fromRGBO(66, 66, 66, 1); + } + } + + return toggledColor; + } + + Widget _wrapWithWrap(List items) { + final double horizontalPadding = widget.itemPadding / 2; + return Padding( + padding: EdgeInsets.only( + left: horizontalPadding, + right: horizontalPadding, + ), + child: Wrap( + direction: widget.direction, + spacing: widget.itemSpacing, + runSpacing: widget.itemRunSpacing, + runAlignment: WrapAlignment.center, + children: items, + ), + ); + } + + Widget _wrapWithScrollable(Widget current, BuildContext context) { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _controller, + scrollDirection: _scrollDirection(), + child: current, + ), + ); + } + + Axis _scrollDirection() { + switch (widget.overflowMode) { + case LegendOverflowMode.scroll: + return widget.direction; + + case LegendOverflowMode.wrapScroll: + switch (widget.direction) { + case Axis.horizontal: + return Axis.vertical; + + case Axis.vertical: + return Axis.horizontal; + } + + case LegendOverflowMode.wrap: + case LegendOverflowMode.none: + return Axis.vertical; + } + } + + Widget _wrapWithDirectional(List items) { + switch (widget.direction) { + case Axis.horizontal: + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: items, + ); + + case Axis.vertical: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: items, + ); + } + } + + @override + void initState() { + if (widget.scrollbarVisibility != LegendScrollbarVisibility.hidden) { + _controller = ScrollController(); + } + super.initState(); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final List items = _buildLegendItems(context); + Widget current; + switch (widget.overflowMode) { + case LegendOverflowMode.wrap: + case LegendOverflowMode.wrapScroll: + current = _wrapWithWrap(items); + break; + + case LegendOverflowMode.scroll: + current = _wrapWithDirectional(items); + break; + + case LegendOverflowMode.none: + current = SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: widget.direction, + child: _wrapWithDirectional(items), + ); + break; + } + + switch (widget.overflowMode) { + case LegendOverflowMode.scroll: + case LegendOverflowMode.wrapScroll: + current = _wrapWithScrollable(current, context); + break; + + case LegendOverflowMode.wrap: + case LegendOverflowMode.none: + break; + } + + switch (widget.scrollbarVisibility) { + case LegendScrollbarVisibility.visible: + case LegendScrollbarVisibility.auto: + final double scrollbarThickness = + defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android + ? 4.0 + : 5.0; + current = Scrollbar( + controller: _controller, + thickness: scrollbarThickness, + thumbVisibility: + widget.scrollbarVisibility == LegendScrollbarVisibility.visible, + child: current, + ); + break; + + case LegendScrollbarVisibility.hidden: + break; + } + + return current; + } +} + +/// Represents the class for generating legend item. +class _IconText extends StatefulWidget { + /// Creates a [_IconText]. + const _IconText({ + required this.details, + required this.index, + this.itemBuilder, + required this.padding, + required this.textStyle, + required this.iconSize, + required this.iconBorderColor, + required this.iconBorderWidth, + required this.spacing, + required this.toggleEnabled, + this.toggledColor, + required this.toggledTextOpacity, + required this.isToggled, + required this.iconOpacity, + this.onTap, + this.overlayMarkerType, + this.degree, + this.startAngle, + this.endAngle, + }); + + /// Hold item details. + final LegendItem details; + + /// Specifies the item index. + final int index; + + /// Widget builder for legend item. + final IndexedWidgetBuilder? itemBuilder; + + /// Specifies the padding for the legend item. + final EdgeInsets padding; + + /// Specifies the style of the text. + final TextStyle textStyle; + + /// Specifies the size of the legend icon. + final Size iconSize; + + /// Specifies the border color of the icon. + final Color? iconBorderColor; + + /// Specifies the border width of the icon. + final double? iconBorderWidth; + + /// Specifies the space between the legend text and the icon. + final double spacing; + + /// Specifies whether to handle toggling. + final bool toggleEnabled; + + /// Specifies whether the current item is toggled. + final bool isToggled; + + /// Specifies the toggled item color. + final Color? toggledColor; + + /// Specifies the toggle item's text color opacity. + final double toggledTextOpacity; + + /// Specifies the toggle item's text color opacity. + final double iconOpacity; + + /// Invokes while tapping the legend item. + final LegendItemTapCallback? onTap; + + /// Specifies the overlay marker for cartesian line type icon. + final ShapeMarkerType? overlayMarkerType; + + /// Specifies the start angle for radial bar icon. + final double? startAngle; + + /// Specifies the degree for radial bar icon. + final double? degree; + + /// Specifies the end angle for radial bar icon. + final double? endAngle; + + @override + _IconTextState createState() => _IconTextState(); +} + +class _IconTextState extends State<_IconText> + with SingleTickerProviderStateMixin { + late AnimationController _toggleAnimationController; + late Animation _toggleAnimation; + late ColorTween _iconColorTween; + late ColorTween _iconBorderColorTween; + late ColorTween _shaderMaskColorTween; + late Tween _opacityTween; + + ImageInfo? _imageInfo; + ImageStream? _imageStream; + late Completer? _completer; + Future? _obtainImage; + bool _isToggled = false; + + Widget _buildCustomPaint( + ItemRendererDetails details, + AsyncSnapshot snapshot, + ) { + Widget current = CustomPaint( + size: widget.iconSize, + painter: _LegendIconShape( + color: details.color!.withValues(alpha: widget.iconOpacity), + iconType: details.iconType, + iconBorderColor: details.iconBorderColor, + iconBorderWidth: details.iconBorderWidth, + image: snapshot.data, + shader: widget.details.shader, + overlayMarkerType: widget.overlayMarkerType, + degree: widget.degree, + startAngle: widget.startAngle, + endAngle: widget.endAngle, + ), + ); + + if (widget.details.shader != null && + details.color != null && + !_toggleAnimationController.isDismissed) { + current = _buildShaderMask(details.color!, current); + } + + return current; + } + + Widget _buildShaderMask(Color color, Widget current) { + return ShaderMask( + blendMode: BlendMode.srcATop, + shaderCallback: (Rect bounds) { + return LinearGradient( + colors: [color, color], + ).createShader(bounds); + }, + child: current, + ); + } + + Future? _retrieveImageFromProvider() async { + if (widget.details.iconType != ShapeMarkerType.image || + widget.details.imageProvider == null) { + return null; + } + + _completer = Completer(); + _imageStream?.removeListener(imageStreamListener(_completer!)); + _imageStream = widget.details.imageProvider!.resolve( + ImageConfiguration.empty, + ); + _imageStream!.addListener(imageStreamListener(_completer!)); + _imageInfo?.dispose(); + _imageInfo = await _completer!.future; + return _imageInfo!.image; + } + + ImageStreamListener imageStreamListener(Completer completer) { + return ImageStreamListener((ImageInfo image, bool synchronousCall) { + completer.complete(image); + }); + } + + void rebuild() { + setState(() { + // Rebuilding the widget to update the UI while toggling. + }); + } + + void _handleTapUp(TapUpDetails details) { + widget.onTap?.call(widget.details, !_isToggled); + } + + void _onToggled() { + _isToggled = !_isToggled; + if (_isToggled) { + _toggleAnimationController.forward(); + } else { + _toggleAnimationController.reverse(); + } + } + + @override + void initState() { + _toggleAnimationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 250), + ); + _toggleAnimationController.addListener(rebuild); + _toggleAnimation = CurvedAnimation( + parent: _toggleAnimationController, + curve: Curves.easeInOut, + ); + + final Color begin = + widget.details.shader == null && widget.details.imageProvider == null + ? widget.details.iconColor + : Colors.transparent; + final Color? borderColorBegin = + widget.details.shader == null && widget.details.imageProvider == null + ? widget.details.iconBorderColor ?? widget.iconBorderColor + : Colors.transparent; + _iconColorTween = ColorTween(begin: begin, end: widget.toggledColor); + _iconBorderColorTween = ColorTween( + begin: borderColorBegin, + end: widget.toggledColor, + ); + _shaderMaskColorTween = ColorTween(end: widget.toggledColor); + _opacityTween = Tween(begin: 1.0, end: widget.toggledTextOpacity); + + _isToggled = widget.isToggled; + _toggleAnimationController.value = _isToggled ? 1.0 : 0.0; + + _obtainImage = _retrieveImageFromProvider(); + widget.details.onToggled = _onToggled; + super.initState(); + } + + @override + void didUpdateWidget(_IconText oldWidget) { + if (widget.details.iconColor != oldWidget.details.iconColor || + widget.details.shader != oldWidget.details.shader) { + _iconColorTween.begin = + widget.details.shader == null && widget.details.imageProvider == null + ? widget.details.iconColor + : Colors.transparent; + } + + if (widget.details.iconBorderColor != oldWidget.details.iconBorderColor || + widget.details.shader != oldWidget.details.shader) { + _iconBorderColorTween.begin = + widget.details.shader == null && widget.details.imageProvider == null + ? widget.details.iconBorderColor ?? + widget.iconBorderColor ?? + Colors.transparent + : Colors.transparent; + } + + if (widget.toggledColor != null && + widget.toggledColor != oldWidget.toggledColor) { + _iconColorTween.end = widget.toggledColor; + _iconBorderColorTween.end = widget.toggledColor; + _shaderMaskColorTween.end = widget.toggledColor; + } + + if (widget.toggledTextOpacity != oldWidget.toggledTextOpacity) { + _opacityTween = Tween(begin: 1.0, end: widget.toggledTextOpacity); + } + + if (widget.details.imageProvider != oldWidget.details.imageProvider) { + _obtainImage = _retrieveImageFromProvider(); + } + + widget.details.onToggled = _onToggled; + if (widget.isToggled != oldWidget.isToggled) { + _isToggled = widget.isToggled; + _toggleAnimationController.value = _isToggled ? 1.0 : 0.0; + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _toggleAnimation.removeListener(rebuild); + _toggleAnimationController.dispose(); + _imageStream?.removeListener(imageStreamListener(_completer!)); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // TODO(VijayakumarM): Avoid always wrapping into [FutureBuilder]. + return FutureBuilder( + future: _obtainImage, + builder: (BuildContext context, AsyncSnapshot snapshot) { + Widget current; + if (widget.itemBuilder != null) { + current = widget.itemBuilder!.call(context, widget.index); + final Color? color = _shaderMaskColorTween.evaluate(_toggleAnimation); + if (color != null) { + current = _buildShaderMask(color, current); + } + } else { + final Color? effectiveIconColor = _iconColorTween.evaluate( + _toggleAnimation, + ); + final Color? effectiveBorderIconColor = _iconBorderColorTween + .evaluate(_toggleAnimation); + final ItemRendererDetails details = ItemRendererDetails( + item: widget.details, + index: widget.index, + text: widget.details.text, + color: effectiveIconColor, + iconType: widget.details.iconType, + iconBorderColor: effectiveBorderIconColor, + iconBorderWidth: + widget.iconBorderWidth ?? widget.details.iconBorderWidth, + ); + widget.details.onRender?.call(details); + if (effectiveIconColor != null && + effectiveIconColor != details.color) { + _iconColorTween.begin = details.color; + details.color = _iconColorTween.evaluate(_toggleAnimation); + } + if (effectiveBorderIconColor != null && + effectiveBorderIconColor != details.iconBorderColor) { + _iconBorderColorTween.begin = details.iconBorderColor; + details.iconBorderColor = _iconBorderColorTween.evaluate( + _toggleAnimation, + ); + } + current = SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildCustomPaint(details, snapshot), + SizedBox(width: widget.spacing), + Text( + details.text, + style: widget.textStyle.copyWith( + color: + widget.textStyle.foreground == null + ? widget.textStyle.color!.withValues( + alpha: _opacityTween.evaluate(_toggleAnimation), + ) + : widget.textStyle.foreground!.color, + ), + ), + ], + ), + ); + } + + current = Padding(padding: widget.padding, child: current); + + if (widget.toggleEnabled) { + current = MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTapUp: _handleTapUp, + behavior: HitTestBehavior.opaque, + child: current, + ), + ); + } + + return current; + }, + ); + } +} + +/// Represents the class for rendering icon shape. +class _LegendIconShape extends CustomPainter { + /// Represents [_LegendIconShape] + _LegendIconShape({ + this.color, + this.iconType = ShapeMarkerType.circle, + this.iconBorderColor, + this.iconBorderWidth, + this.image, + this.shader, + this.overlayMarkerType, + this.degree, + this.startAngle, + this.endAngle, + }); + + /// Specifies the color of the icon. + final Color? color; + + /// Specifies the icon type. + final ShapeMarkerType iconType; + + /// Specifies the border color of the icon. + final Color? iconBorderColor; + + /// Specifies the border width of the icon. + final double? iconBorderWidth; + + /// Identifies an image. + final ui.Image? image; + + /// Specifies the shader of the icon. + final Shader? shader; + + /// Specifies the overlay marker for cartesian line icon type icon. + final ShapeMarkerType? overlayMarkerType; + + /// Specifies the start angle for radial bar icon. + final double? startAngle; + + /// Specifies the degree for radial bar icon. + final double? degree; + + /// Specifies the end angle for radial bar icon. + final double? endAngle; + + Paint _getFillPaint() { + final Paint paint = Paint()..strokeWidth = iconBorderWidth ?? 1; + if (shader != null) { + paint.shader = shader; + } else if (color != null) { + paint.color = color!; + } + + return paint; + } + + Paint? _getStrokePaint() { + return Paint() + ..color = iconBorderColor ?? Colors.transparent + ..strokeWidth = iconBorderWidth ?? 1 + ..style = PaintingStyle.stroke; + } + + @override + void paint(Canvas canvas, Size size) { + if (iconType == ShapeMarkerType.image && image != null) { + paintImage(canvas: canvas, rect: Offset.zero & size, image: image!); + } else { + shape_helper.paint( + canvas: canvas, + rect: Offset.zero & size, + shapeType: iconType, + paint: _getFillPaint(), + borderPaint: _getStrokePaint(), + overlayMarkerType: overlayMarkerType, + degree: degree, + startAngle: startAngle, + endAngle: endAngle, + ); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart new file mode 100644 index 000000000..62fe6e746 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart @@ -0,0 +1,950 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'element_widget.dart'; + +typedef TooltipWidgetBuilder = + Widget? Function(BuildContext, TooltipInfo?, Size); + +/// Holds details of a tooltip is shown. +@immutable +class TooltipInfo { + const TooltipInfo({ + required this.primaryPosition, + required this.secondaryPosition, + this.text, + this.surfaceBounds, + }); + + /// Global position of the tooltip. Used when tooltip renders on top. + final Offset primaryPosition; + + /// Global position of the tooltip. Used when tooltip renders on bottom. + final Offset secondaryPosition; + + /// Text to be displayed in the tooltip. + final String? text; + + /// [RenderBox] of the tooltip. + final Rect? surfaceBounds; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is TooltipInfo && + other.text == text && + other.surfaceBounds == surfaceBounds; + } + + @override + int get hashCode { + final List values = [ + primaryPosition, + secondaryPosition, + text, + surfaceBounds, + ]; + return Object.hashAll(values); + } +} + +class TooltipOpacity extends Opacity { + const TooltipOpacity({ + super.key, + required super.opacity, + super.alwaysIncludeSemantics = false, + super.child, + }); + + @override + RenderOpacity createRenderObject(BuildContext context) { + return TooltipOpacityRenderBox( + opacity: opacity, + alwaysIncludeSemantics: alwaysIncludeSemantics, + ); + } +} + +class TooltipOpacityRenderBox extends RenderOpacity { + TooltipOpacityRenderBox({ + super.opacity = 1.0, + super.alwaysIncludeSemantics = false, + super.child, + }); +} + +class CoreTooltip extends StatefulWidget { + const CoreTooltip({ + super.key, + required this.builder, + this.animationDuration = 500, + this.showDuration = 3000, + this.innerPadding = 5.0, + this.color = Colors.black, + this.borderColor = Colors.black, + this.borderWidth = 0.0, + this.shouldAlwaysShow = false, + this.elevation = 0.0, + this.shadowColor, + this.triangleHeight = 7.0, + this.preferTooltipOnTop, + this.opacity = 1.0, + }); + + final TooltipWidgetBuilder? builder; + final int animationDuration; + final int showDuration; + final double innerPadding; + final Color color; + final Color borderColor; + final double borderWidth; + final Color? shadowColor; + final double elevation; + final bool shouldAlwaysShow; + final double triangleHeight; + final bool? preferTooltipOnTop; + final double opacity; + + @override + State createState() => CoreTooltipState(); +} + +class CoreTooltipState extends State + with SingleTickerProviderStateMixin { + final GlobalKey _tooltipKey = GlobalKey(); + late AnimationController _controller; + late CurvedAnimation _animation; + + bool _isDesktop = false; + TooltipInfo? _info; + PointerDeviceKind _pointerDeviceKind = PointerDeviceKind.touch; + + /// In desktop mode, the tooltip will be displayed upon hover interaction, + /// which means that the show method may be triggered for every hover + /// movement. To prevent this, we have delayed show method for 100ms. + Timer? _desktopShowDelayTimer; + Timer? _showTimer; + + void show( + TooltipInfo info, + PointerDeviceKind kind, { + bool immediately = false, + }) { + if (_isDesktop && kind == PointerDeviceKind.mouse) { + _desktopShowDelayTimer?.cancel(); + if (immediately) { + _show(info, kind); + } else { + _desktopShowDelayTimer = Timer(const Duration(milliseconds: 50), () { + _show(info, kind); + _desktopShowDelayTimer = null; + }); + } + } else { + _show(info, kind); + } + } + + void _show(TooltipInfo info, PointerDeviceKind kind) { + final bool startFromZero = _info != info; + _info = info; + _pointerDeviceKind = kind; + _controller.forward(from: startFromZero ? 0 : null); + _startShowTimer(); + final RenderObjectElement? tooltipElement = + _tooltipKey.currentContext as RenderObjectElement?; + if (tooltipElement != null && + tooltipElement.mounted && + tooltipElement.renderObject.attached) { + final RenderObject? renderObject = tooltipElement.findRenderObject(); + if (renderObject != null && + renderObject.attached && + renderObject is CustomRenderConstrainedLayoutBuilder) { + renderObject.markNeedsBuild(); + } + } + } + + void hide({bool immediately = false}) { + immediately ? _controller.reset() : _controller.reverse(); + // (_tooltipKey.currentContext?.findRenderObject() + // as RenderConstrainedLayoutBuilder?) + // ?.markNeedsBuild(); + } + + void _startShowTimer() { + // if (_isDesktop && _pointerDeviceKind == PointerDeviceKind.mouse) { + // } else { + _showTimer?.cancel(); + if (widget.showDuration.isInfinite || widget.shouldAlwaysShow) { + return; + } + + _showTimer = Timer( + // When the [animationDuration] is 3000 and the [showDuration] is 3000, + // the tooltip will start hiding after it completes the scale animation, + // without staying in the visual for 3 seconds. + // So, [widget.animationDuration] has been considered in [_showTimer]. + Duration(milliseconds: widget.animationDuration + widget.showDuration), + () { + if (mounted) { + hide(); + _showTimer = null; + } + }, + ); + // } + } + + @override + void initState() { + _controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: widget.animationDuration), + ); + _animation = CurvedAnimation( + parent: _controller, + curve: Curves.easeOutBack, + ); + + super.initState(); + } + + @override + void dispose() { + _info = null; + _isDesktop = false; + _controller.dispose(); + _animation.dispose(); + + _desktopShowDelayTimer?.cancel(); + _showTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData chartThemeData = Theme.of(context); + _isDesktop = + kIsWeb || + chartThemeData.platform == TargetPlatform.macOS || + chartThemeData.platform == TargetPlatform.windows || + chartThemeData.platform == TargetPlatform.linux; + return TooltipOpacity( + opacity: widget.opacity, + child: CustomLayoutBuilder( + key: _tooltipKey, + builder: (BuildContext context, BoxConstraints constraints) { + return _CoreTooltipRenderObjectWidget( + primaryPosition: _info?.primaryPosition, + secondaryPosition: _info?.secondaryPosition, + edgeBounds: _info?.surfaceBounds, + innerPadding: widget.innerPadding, + color: widget.color, + borderColor: widget.borderColor, + borderWidth: widget.borderWidth, + shadowColor: widget.shadowColor, + elevation: widget.elevation, + shouldAlwaysShow: widget.shouldAlwaysShow, + triangleHeight: widget.triangleHeight, + preferTooltipOnTop: widget.preferTooltipOnTop, + isDesktop: _isDesktop, + deviceKind: _pointerDeviceKind, + state: this, + child: widget.builder?.call(context, _info, constraints.biggest), + ); + }, + ), + ); + } +} + +class _CoreTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { + const _CoreTooltipRenderObjectWidget({ + required this.primaryPosition, + required this.secondaryPosition, + required this.edgeBounds, + required this.innerPadding, + required this.color, + required this.borderColor, + required this.borderWidth, + required this.shadowColor, + required this.elevation, + required this.shouldAlwaysShow, + required this.triangleHeight, + required this.preferTooltipOnTop, + required this.isDesktop, + required this.deviceKind, + required this.state, + super.child, + }); + + final Offset? primaryPosition; + final Offset? secondaryPosition; + final Rect? edgeBounds; + final double innerPadding; + final Color color; + final Color borderColor; + final double borderWidth; + final Color? shadowColor; + final double elevation; + final bool shouldAlwaysShow; + final double triangleHeight; + final bool? preferTooltipOnTop; + final bool isDesktop; + final PointerDeviceKind deviceKind; + final CoreTooltipState state; + + @override + RenderObject createRenderObject(BuildContext context) { + return _CoreTooltipRenderBox() + ..primaryPosition = primaryPosition + ..secondaryPosition = secondaryPosition + ..edgeBounds = edgeBounds + ..innerPadding = innerPadding + ..color = color + ..borderColor = borderColor + ..borderWidth = borderWidth + ..shadowColor = shadowColor + ..elevation = elevation + ..shouldAlwaysShow = shouldAlwaysShow + ..triangleHeight = triangleHeight + ..preferTooltipOnTop = preferTooltipOnTop + .._state = state + ..textDirection = Directionality.of(context); + } + + @override + void updateRenderObject( + BuildContext context, + _CoreTooltipRenderBox renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..primaryPosition = primaryPosition + ..secondaryPosition = secondaryPosition + ..edgeBounds = edgeBounds + ..innerPadding = innerPadding + ..color = color + ..borderColor = borderColor + ..borderWidth = borderWidth + ..shadowColor = shadowColor + ..elevation = elevation + ..shouldAlwaysShow = shouldAlwaysShow + ..triangleHeight = triangleHeight + ..preferTooltipOnTop = preferTooltipOnTop + .._state = state + ..textDirection = Directionality.of(context); + } +} + +class _CoreTooltipRenderBox extends RenderProxyBox { + final double _noseGap = 2.0; + late CoreTooltipState _state; + Path _path = Path(); + final _RectangularShape _tooltipShape = const _RectangularShape(); + + final Paint _fillPaint = Paint()..isAntiAlias = true; + final Paint _strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + Offset? _localPrimaryPosition; + Offset? _localSecondaryPosition; + Offset? _nosePosition; + + Offset? get primaryPosition => _primaryPosition; + Offset? _primaryPosition; + set primaryPosition(Offset? value) { + if (_primaryPosition == value) { + return; + } + _primaryPosition = value; + _localPrimaryPosition = value != null ? globalToLocal(value) : null; + markNeedsPaint(); + } + + Offset? get secondaryPosition => _secondaryPosition; + Offset? _secondaryPosition; + set secondaryPosition(Offset? value) { + if (_secondaryPosition == value) { + return; + } + _secondaryPosition = value; + _localSecondaryPosition = value != null ? globalToLocal(value) : null; + markNeedsPaint(); + } + + Rect? _localEdgeBounds; + + Rect? get edgeBounds => _edgeBounds; + Rect? _edgeBounds; + set edgeBounds(Rect? value) { + _edgeBounds = value; + if (value != null) { + _localEdgeBounds = Rect.fromPoints( + globalToLocal(edgeBounds!.topLeft), + globalToLocal(edgeBounds!.bottomRight), + ); + } else { + _localEdgeBounds = null; + } + markNeedsLayout(); + } + + double get innerPadding => _innerPadding; + double _innerPadding = 5.0; + set innerPadding(double value) { + _innerPadding = value; + markNeedsLayout(); + } + + Color get color => _color; + Color _color = Colors.black; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + _fillPaint.color = _color; + markNeedsPaint(); + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.black; + set borderColor(Color value) { + if (_borderColor == value) { + return; + } + _borderColor = value; + _strokePaint.color = _borderColor; + markNeedsPaint(); + } + + double get borderWidth => _borderWidth; + double _borderWidth = 0.0; + set borderWidth(double value) { + if (_borderWidth == value) { + return; + } + _borderWidth = value; + _strokePaint.strokeWidth = _borderWidth; + markNeedsPaint(); + } + + Color? get shadowColor => _shadowColor; + Color? _shadowColor; + set shadowColor(Color? value) { + if (_shadowColor == value) { + return; + } + _shadowColor = value; + markNeedsPaint(); + } + + double get elevation => _elevation; + double _elevation = 0.0; + set elevation(double value) { + if (_elevation == value) { + return; + } + _elevation = value; + markNeedsPaint(); + } + + bool get shouldAlwaysShow => _shouldAlwaysShow; + bool _shouldAlwaysShow = false; + set shouldAlwaysShow(bool value) { + if (_shouldAlwaysShow == value) { + return; + } + _shouldAlwaysShow = value; + markNeedsPaint(); + } + + double get triangleHeight => _triangleHeight; + double _triangleHeight = 7.0; + set triangleHeight(double value) { + _triangleHeight = value; + markNeedsLayout(); + } + + bool? _effectivePreferTooltipOnTop; + + bool? get preferTooltipOnTop { + assert(false, 'Use _effectivePreferTooltipOnTop instead.'); + return _preferTooltipOnTop; + } + + bool? _preferTooltipOnTop; + set preferTooltipOnTop(bool? value) { + _preferTooltipOnTop = value; + _effectivePreferTooltipOnTop = value; + markNeedsLayout(); + } + + BorderRadius _effectiveBorderRadius = BorderRadius.circular(5); + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.circular(5); + set borderRadius(BorderRadius value) { + if (_borderRadius == value) { + return; + } + _borderRadius = value; + _effectiveBorderRadius = _borderRadius.resolve(textDirection); + markNeedsPaint(); + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection = TextDirection.ltr; + set textDirection(TextDirection value) { + if (_textDirection == value) { + return; + } + _textDirection = value; + _effectiveBorderRadius = _borderRadius.resolve(value); + markNeedsPaint(); + } + + void _onAnimationUpdate() { + markNeedsLayout(); + } + + @override + void setupParentData(covariant RenderObject child) { + if (child.parentData is! BoxParentData) { + child.parentData = BoxParentData(); + } + } + + @override + void attach(PipelineOwner owner) { + _state._animation.addListener(_onAnimationUpdate); + super.attach(owner); + } + + @override + void detach() { + _state._animation.removeListener(_onAnimationUpdate); + super.detach(); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + if (_state._animation.value == 1.0 && + child != null && + child!.parentData != null) { + final BoxParentData childParentData = child!.parentData! as BoxParentData; + return result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + } + return false; + } + + @override + void performLayout() { + // Hide the tooltip while resizing. + if (!hasSize || size != constraints.biggest) { + _nosePosition = null; + _primaryPosition = null; + _secondaryPosition = null; + size = constraints.biggest; + return; + } + + size = constraints.biggest; + if (child == null) { + return; + } + + final Rect surfaceBounds = _localEdgeBounds ?? paintBounds; + child?.layout(constraints, parentUsesSize: true); + _effectivePreferTooltipOnTop ??= _canPreferTooltipOnTop(surfaceBounds); + _nosePosition = + _effectivePreferTooltipOnTop! + ? _localPrimaryPosition?.translate(0.0, -_noseGap) + : _localSecondaryPosition?.translate(0.0, _noseGap); + _validateNosePosition(); + + _path = _tooltipShape.outerPath( + position: _nosePosition, + preferTooltipOnTop: _effectivePreferTooltipOnTop!, + child: child!, + surfaceBounds: surfaceBounds, + animation: _state._animation, + fillPaint: _fillPaint, + strokePaint: _strokePaint, + borderRadius: _effectiveBorderRadius, + triangleHeight: _triangleHeight, + innerPadding: _innerPadding, + ); + + final int multiplier = (_effectivePreferTooltipOnTop! ? 1 : -1); + final double halfTooltipHeight = child!.size.height / 2; + final Offset pathShift = Offset( + _nosePosition!.dx, + _nosePosition!.dy - + (_triangleHeight * multiplier + halfTooltipHeight * multiplier), + ); + _path = _path.shift(pathShift); + + final Rect pathBounds = _path.getBounds(); + final Offset pathCenter = pathBounds.center; + final double triangleHeight = _triangleHeight * multiplier; + final Offset childPosition = Offset( + pathCenter.dx - child!.size.width / 2, + pathCenter.dy - (child!.size.height + triangleHeight) / 2, + ); + final BoxParentData childParentData = child!.parentData! as BoxParentData; + childParentData.offset = + childPosition + _childShiftOffset(pathBounds, surfaceBounds); + } + + bool _canPreferTooltipOnTop(Rect surfaceBounds) { + if (_localPrimaryPosition == null) { + return true; + } + + final Size childSize = child?.size ?? Size.zero; + final Offset tooltipStart = Offset( + _localPrimaryPosition!.dx - childSize.width / 2, + _localPrimaryPosition!.dy - _triangleHeight - childSize.height, + ); + final Rect tooltipBounds = Rect.fromLTWH( + tooltipStart.dx, + tooltipStart.dy, + childSize.width, + childSize.height + _triangleHeight, + ); + + if (tooltipBounds.top < surfaceBounds.top) { + return false; + } else if (tooltipBounds.bottom > surfaceBounds.bottom) { + return true; + } + + return true; + } + + void _validateNosePosition() { + if (_localEdgeBounds == null) { + if (_nosePosition!.dx < 0) { + _nosePosition = Offset(0, _nosePosition!.dy); + } + if (_nosePosition!.dx > size.width) { + _nosePosition = Offset(size.width, _nosePosition!.dy); + } + if (_nosePosition!.dy < _noseGap) { + _nosePosition = Offset(_nosePosition!.dx, _noseGap); + } + if (_nosePosition!.dy > size.height) { + _nosePosition = Offset(_nosePosition!.dx, size.height - _noseGap); + } + } + } + + Offset _childShiftOffset(Rect pathBounds, Rect surfaceBounds) { + if (_localPrimaryPosition != null) { + final double start = _localPrimaryPosition!.dx; + final Size childSize = child!.size; + final double halfChildWidth = childSize.width / 2; + + final double pathWidth = pathBounds.width; + final double tooltipRectWidth = child!.size.width; + // Tooltip nose width which exceeds the tooltip rect width. + final double excessWidth = pathWidth - tooltipRectWidth; + // Shifting the position to left side. + if (start + halfChildWidth > surfaceBounds.right) { + return Offset(-excessWidth / 2, 0.0); + } + // Shifting the position to right side. + else if (start - halfChildWidth < surfaceBounds.left) { + return Offset(excessWidth / 2, 0.0); + } + } + + return Offset.zero; + } + + @override + void paint(PaintingContext context, Offset offset) { + final double animationValue = _state._animation.value; + if (child == null || + _nosePosition == null || + _effectivePreferTooltipOnTop == null) { + return; + } + + context.canvas.save(); + context.canvas.translate(_nosePosition!.dx, _nosePosition!.dy); + context.canvas.scale(animationValue); + context.canvas.translate(-_nosePosition!.dx, -_nosePosition!.dy); + // In web HTML rendering, fill color clipped half of its tooltip's size. + // To avoid this issue we are drawing stroke before fill. + // Due to this, half of the stroke width only + // visible to us so that we are twice the stroke width. + // TODO(VijayakumarM): Check this comment. + if (elevation > 0) { + context.canvas.drawShadow(_path, shadowColor ?? color, elevation, true); + } + // Drawing stroke. + context.canvas.drawPath(_path, _strokePaint); + // Drawing fill color. + context.canvas.drawPath(_path, _fillPaint); + // Clipping corners and to ignore excess portions. + context.canvas.clipPath(_path); + // Drawing tooltip's builder/child. + final BoxParentData childParentData = child!.parentData! as BoxParentData; + // Used [pushTransform] because scrollable widgets are not scaled with + // [context.paintChild]. + context.pushTransform( + true, + Offset(_nosePosition!.dx, _nosePosition!.dy), + Matrix4.diagonal3Values(animationValue, animationValue, 1), + (PaintingContext context, Offset translateOffset) { + context.paintChild(child!, childParentData.offset + offset); + }, + ); + + context.canvas.restore(); + } +} + +class _RectangularShape { + const _RectangularShape(); + Path outerPath({ + required Offset? position, + required bool preferTooltipOnTop, + required RenderBox child, + required Rect surfaceBounds, + required Animation animation, + required Paint fillPaint, + required Paint strokePaint, + required BorderRadius borderRadius, + required double triangleHeight, + required double innerPadding, + }) { + if (position == null) { + return Path(); + } + + const double tooltipTriangleWidth = 12.0; + const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; + const double elevation = 0.0; + + final double tooltipWidth = child.size.width; + double tooltipHeight = child.size.height; + final double halfTooltipWidth = tooltipWidth / 2; + double halfTooltipHeight = tooltipHeight / 2; + + final double tooltipStartPoint = triangleHeight + tooltipHeight / 2; + double tooltipTriangleOffsetY = tooltipStartPoint - triangleHeight; + + final double endGlobal = surfaceBounds.right - innerPadding; + double rightLineWidth = + position.dx + halfTooltipWidth > endGlobal + ? endGlobal - position.dx + : halfTooltipWidth; + final double leftLineWidth = + position.dx - halfTooltipWidth < surfaceBounds.left + innerPadding + ? position.dx - surfaceBounds.left - innerPadding + : tooltipWidth - rightLineWidth; + rightLineWidth = + leftLineWidth < halfTooltipWidth + ? halfTooltipWidth - leftLineWidth + rightLineWidth + : rightLineWidth; + + double moveNosePoint = + leftLineWidth < tooltipWidth * 0.1 + ? tooltipWidth * 0.1 - leftLineWidth + : 0.0; + moveNosePoint = + rightLineWidth < tooltipWidth * 0.1 + ? -(tooltipWidth * 0.1 - 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, + ), + ); + } + + return _tooltipPath( + triangleHeight, + halfTooltipHeight, + halfTooltipTriangleWidth, + tooltipTriangleOffsetY, + moveNosePoint, + rightLineWidth, + leftLineWidth, + borderRadius, + tooltipHeight, + ); + } + + Path _tooltipPath( + double tooltipTriangleHeight, + double halfTooltipHeight, + double halfTooltipTriangleWidth, + double tooltipTriangleOffsetY, + double moveNosePoint, + double rightLineWidth, + double leftLineWidth, + BorderRadius borderRadius, + double tooltipHeight, + ) { + final Path path = Path(); + 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; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart new file mode 100644 index 000000000..8a2039591 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart @@ -0,0 +1,1568 @@ +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../axis/axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../base.dart'; +import '../series/chart_series.dart'; +import '../series/histogram_series.dart'; +import '../series/waterfall_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'callbacks.dart'; +import 'chart_point.dart'; +import 'connector_line.dart'; +import 'element_widget.dart'; + +/// Customizes the data label. +/// +/// Data labels can be added to a chart series by enabling the [isVisible] +/// option in the dataLabelSettings. It has options to customize the appearance +/// of the data label. +/// +/// Provide options like color, border width, border color, alignment and +/// data label text style for customization. +@immutable +class DataLabelSettings { + /// Creating an argument constructor of DataLabelSettings class. + const DataLabelSettings({ + this.isVisible = false, + this.alignment = ChartAlignment.center, + this.color, + this.textStyle, + this.margin = const EdgeInsets.all(5.0), + this.opacity = 1.0, + this.labelAlignment = ChartDataLabelAlignment.auto, + this.borderRadius = 5.0, + this.angle = 0, + this.builder, + this.useSeriesColor = false, + this.offset = Offset.zero, + this.showCumulativeValues = false, + this.showZeroValue = true, + this.borderColor = Colors.transparent, + this.borderWidth = 1.0, + this.overflowMode = OverflowMode.none, + this.labelIntersectAction = LabelIntersectAction.shift, + this.connectorLineSettings = const ConnectorLineSettings(), + this.labelPosition = ChartDataLabelPosition.inside, + }); + + /// Alignment of the data label. + /// + /// The data label can be aligned far, near, or center of the + /// data point position. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// alignment: ChartAlignment.center + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartAlignment alignment; + + /// Rotation angle of the data label. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// angle:40 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final int angle; + + /// Border color of the data label. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// borderColor: Colors.red, + /// borderWidth: 2 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final Color borderColor; + + /// Customizes the data label border radius. + /// + /// Defaults to `5`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// borderRadius: 3, + /// borderColor: Colors.red, + /// borderWidth: 2 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final double borderRadius; + + /// Border width of the data label. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// borderColor: Colors.red, + /// borderWidth: 2 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final double borderWidth; + + /// Builder for data label. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// builder: (dynamic data, dynamic point, + /// dynamic series, int pointIndex, int seriesIndex) { + /// return Container( + /// height: 30, + /// width: 30, + /// child: Image.asset('images/horse.jpg') + /// ); + /// } + /// ) + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartWidgetBuilder? builder; + + /// Color of the data label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// color: Colors.red + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? color; + + /// Customizes the connector lines. Connector line is rendered when the + /// data label is placed outside the chart. + /// + /// _Note:_ This is applicable for pie and doughnut series types alone. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// connectorLineSettings: ConnectorLineSettings( + /// width: 6, + /// type:ConnectorType.curve + /// ) + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final ConnectorLineSettings connectorLineSettings; + + /// Toggles the visibility of the data label in the series. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isVisible; + + /// Position of the data label. + /// + /// _Note:_ This is applicable for Cartesian chart. + /// + /// Defaults to `ChartDataLabelAlignment.auto`. + /// + /// Also refer [ChartDataLabelAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// labelAlignment: ChartDataLabelAlignment.top + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartDataLabelAlignment labelAlignment; + + /// Action on data labels intersection. + /// + /// The intersecting data labels can be hidden. + /// + /// _Note:_ This is applicable for pie, doughnut, funnel and + /// pyramid series types alone. + /// + /// Defaults to `LabelIntersectAction.shift`. + /// + /// Also refer [LabelIntersectAction]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// labelIntersectAction: LabelIntersectAction.shift + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final LabelIntersectAction labelIntersectAction; + + /// Position of the data label. + /// + /// _Note:_ This is applicable for pie and doughnut series types alone. + /// + /// Defaults to `ChartDataLabelPosition.inside`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// labelPosition: ChartDataLabelPosition.outside + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartDataLabelPosition labelPosition; + + /// Margin between the data label text and its shape. + /// + /// Defaults to `EdgeInsets.fromLTRB(5, 5, 5, 5)`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// margin: const EdgeInsets.all(2), + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final EdgeInsets margin; + + /// Moves the data label vertically or horizontally from its position. + /// + /// If you wish to reposition the data label, you can achieve using this + /// property. You can move the data label in both vertical and horizontal + /// direction from its position. It takes the logical pixel value for + /// x and y values as input. + /// + /// Positive value for x, moves the data label to right and negative value + /// moves to left. + /// Positive value for y, moves the data label upwards and negative value + /// moves downwards. + /// + /// These are applied to the data label's final position. i.e. after + /// considering the position and alignment values. + /// + /// Also refer [labelAlignment]. + /// + /// _Note:_ This property is only applicable for Cartesian charts and not for + /// Circular, Pyramid and Funnel charts. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// offset: Offset(200,200) + /// ) + /// ), + /// ], + /// ); + /// } + /// ``` + final Offset offset; + + /// Opacity of the data label. + /// + /// The value ranges from 0 to 1. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// opacity: 0.8 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final double opacity; + + /// Action on data labels when it’s overflowing from its region area. + /// + /// The overflowing data label rendering behavior can be changed based + /// on this. If `overflowMode` property is set to `OverflowMode.none` + /// then the `labelIntersectAction` takes the priority, else + /// `overflowMode` takes the priority. + /// + /// _Note:_ This is applicable for pie, doughnut, pyramid, and funnel series + /// types alone. + /// + /// Defaults to `OverflowMode.none`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// overflowMode: OverflowMode.shift + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final OverflowMode overflowMode; + + /// To show the cumulative values in stacked type series charts. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true + /// ), + /// ), + /// StackedColumnSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// showCumulativeValues: true + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final bool showCumulativeValues; + + /// Hides the data label and its connector line, if the + /// data point value is 0 (Zero). + /// + /// If the data label is enabled, it will be visible for all the data points + /// in the series. By using this property, we can hide the data label and its + /// connector line, for the data points if its value is 0 (Zero). + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// showZeroValue: false + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final bool showZeroValue; + + /// Customizes the data label font. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// textStyle: TextStyle( + /// fontSize: 12 + /// ) + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Uses the series color for filling the data label shape. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// dataLabelSettings: DataLabelSettings( + /// isVisible: true, + /// useSeriesColor: true + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final bool useSeriesColor; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is DataLabelSettings && + other.alignment == alignment && + other.color == color && + other.textStyle == textStyle && + other.margin == margin && + other.opacity == opacity && + other.labelAlignment == labelAlignment && + other.borderRadius == borderRadius && + other.isVisible == isVisible && + other.angle == angle && + other.builder == builder && + other.useSeriesColor == useSeriesColor && + other.offset == offset && + other.showCumulativeValues == showCumulativeValues && + other.showZeroValue == showZeroValue && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.overflowMode == overflowMode && + other.labelIntersectAction == labelIntersectAction && + other.connectorLineSettings == connectorLineSettings && + other.labelPosition == labelPosition; + } + + @override + int get hashCode { + final List values = [ + alignment, + color, + textStyle, + margin, + opacity, + labelAlignment, + borderRadius, + isVisible, + angle, + builder, + useSeriesColor, + offset, + showCumulativeValues, + showZeroValue, + borderColor, + borderWidth, + overflowMode, + labelIntersectAction, + connectorLineSettings, + labelPosition, + ]; + return Object.hashAll(values); + } +} + +typedef _ChartDataLabelWidgetBuilder = + Widget Function( + T data, + int yIndex, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ); + +// ignore: must_be_immutable +base class CartesianChartDataLabelPositioned + extends ParentDataWidget + with LinkedListEntry { + CartesianChartDataLabelPositioned({ + super.key, + required this.x, + required this.y, + required this.dataPointIndex, + required this.position, + required super.child, + }); + + final num x; + final num y; + final int dataPointIndex; + final ChartDataPointType position; + + Offset offset = Offset.zero; + Size size = Size.zero; + Rect bounds = Rect.zero; + Rect rotatedBounds = Rect.zero; + bool isVisible = true; + ChartDataLabelAlignment labelAlignment = ChartDataLabelAlignment.auto; + + @override + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is ChartElementParentData); + final ChartElementParentData parentData = + renderObject.parentData! as ChartElementParentData; + bool needsLayout = false; + + if (parentData.x != x) { + parentData.x = x; + needsLayout = true; + } + + if (parentData.y != y) { + parentData.y = y; + needsLayout = true; + } + + if (parentData.dataPointIndex != dataPointIndex) { + parentData.dataPointIndex = dataPointIndex; + needsLayout = true; + } + + if (parentData.position != position) { + parentData.position = position; + needsLayout = true; + } + + if (needsLayout) { + final RenderObject? targetParent = renderObject.parent; + if (targetParent is RenderObject) { + targetParent.markNeedsLayout(); + } + } + } + + @override + Type get debugTypicalAncestorWidgetClass => CartesianDataLabelStack; +} + +// ignore: must_be_immutable +class DataLabelText extends Widget { + DataLabelText({ + super.key, + required this.text, + required this.textStyle, + this.color = Colors.transparent, + }); + + String text; + TextStyle textStyle; + Color color; + + @override + Element createElement() { + throw UnimplementedError(); + } + + @override + String toStringShort() { + return text; + } +} + +class CartesianDataLabelContainer extends StatefulWidget { + const CartesianDataLabelContainer({ + super.key, + required this.series, + required this.dataSource, + this.mapper, + this.builder, + required this.settings, + required this.positions, + }); + + final ChartWidgetBuilder? builder; + final List dataSource; + final ChartValueMapper? mapper; + final ChartSeries series; + final DataLabelSettings settings; + final List positions; + + @override + State> createState() => + _CartesianDataLabelContainerState(); +} + +class _CartesianDataLabelContainerState + extends State> + with ChartElementParentDataMixin { + List? _builderChildren; + LinkedList? _textChildren; + + @override + CartesianSeriesRenderer? get renderer => + super.renderer as CartesianSeriesRenderer?; + + Widget _dataLabelFromBuilder( + T data, + int yIndex, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final ChartPoint point = renderer!.chartPoints[pointIndex]; + return widget.builder!(data, point, series, pointIndex, seriesIndex); + } + + Widget _dataLabelFromMapper( + T data, + int yIndex, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final String text = widget.mapper!(data, pointIndex) ?? ''; + return _buildDataLabelText(text, pointIndex); + } + + Widget _defaultDataLabel( + T data, + int yIndex, + ChartSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final DataLabelSettings settings = widget.settings; + final num value = + stackedYValues != null && !settings.showCumulativeValues + ? stackedYValues![pointIndex] + : yLists![yIndex][pointIndex]; + final String formattedText = formatNumericValue(value, renderer!.yAxis); + return _buildDataLabelText(formattedText, pointIndex); + } + + Color _dataPointColor(int dataPointIndex) { + final DataLabelSettings settings = widget.settings; + if (settings.color != null) { + return settings.color!.withValues(alpha: settings.opacity); + } else if (settings.useSeriesColor) { + final Color? pointColor = + renderer!.pointColors.isNotEmpty + ? renderer!.pointColors[dataPointIndex] + : null; + return (pointColor ?? renderer!.color ?? renderer!.paletteColor) + .withValues(alpha: settings.opacity); + } else { + return Colors.transparent; + } + } + + DataLabelText _buildDataLabelText(String text, int pointIndex) { + final RenderChartPlotArea parent = renderer!.parent!; + final TextStyle dataLabelTextStyle = parent.themeData!.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(parent.chartThemeData!.dataLabelTextStyle) + .merge(widget.settings.textStyle); + return DataLabelText( + text: text, + textStyle: dataLabelTextStyle, + color: _dataPointColor(pointIndex), + ); + } + + void _addToList(CartesianChartDataLabelPositioned child) { + _builderChildren!.add(child); + } + + void _addToLinkedList(CartesianChartDataLabelPositioned child) { + _textChildren!.add(child); + } + + void _buildLinearDataLabels( + _ChartDataLabelWidgetBuilder callback, + Function(CartesianChartDataLabelPositioned) add, + ) { + final int yLength = yLists?.length ?? 0; + List? actualXValues; + if (xRawValues != null && xRawValues!.isNotEmpty) { + actualXValues = xRawValues; + } else { + actualXValues = xValues; + } + + if (actualXValues == null || renderer!.visibleIndexes.isEmpty) { + return; + } + + final bool isEmptyMode = + renderer! is! WaterfallSeriesRenderer && + (renderer!.emptyPointSettings.mode == EmptyPointMode.drop || + renderer!.emptyPointSettings.mode == EmptyPointMode.gap); + + final bool hasSortedIndexes = + renderer!.sortingOrder != SortingOrder.none && + sortedIndexes != null && + sortedIndexes!.isNotEmpty; + + final int start = renderer!.visibleIndexes[0]; + final int end = renderer!.visibleIndexes[1]; + final int xLength = actualXValues.length; + for (int i = start; i <= end && i < xLength; i++) { + _obtainLabel( + i, + actualXValues, + yLength, + callback, + add, + isEmptyMode, + hasSortedIndexes, + ); + } + } + + void _buildNonLinearDataLabels( + _ChartDataLabelWidgetBuilder callback, + Function(CartesianChartDataLabelPositioned) add, + ) { + final int yLength = yLists?.length ?? 0; + List? actualXValues; + if (xRawValues != null && xRawValues!.isNotEmpty) { + actualXValues = xRawValues; + } else { + actualXValues = xValues; + } + + if (actualXValues == null || renderer!.visibleIndexes.isEmpty) { + return; + } + + final bool isEmptyMode = + renderer! is! WaterfallSeriesRenderer && + (renderer!.emptyPointSettings.mode == EmptyPointMode.drop || + renderer!.emptyPointSettings.mode == EmptyPointMode.gap); + + final bool hasSortedIndexes = + renderer!.sortingOrder != SortingOrder.none && + sortedIndexes != null && + sortedIndexes!.isNotEmpty; + + final int xLength = actualXValues.length; + for (final int index in renderer!.visibleIndexes) { + if (index < xLength) { + _obtainLabel( + index, + actualXValues, + yLength, + callback, + add, + isEmptyMode, + hasSortedIndexes, + ); + } + } + } + + void _obtainLabel( + int index, + List rawXValues, + int yLength, + _ChartDataLabelWidgetBuilder callback, + Function(CartesianChartDataLabelPositioned) add, + bool isEmptyMode, + bool hasSortedIndexes, + ) { + if (isEmptyMode && renderer!.emptyPointIndexes.contains(index)) { + return; + } + + int pointIndex = hasSortedIndexes ? sortedIndexes![index] : index; + final bool isHisto = renderer is HistogramSeriesRenderer; + final int dataSourceLength = widget.dataSource.length; + final num x = xValues![index]; + for (int k = 0; k < yLength; k++) { + final List yValues = yLists![k]; + final ChartDataPointType position = widget.positions[k]; + if (isHisto && pointIndex >= dataSourceLength) { + pointIndex = dataSourceLength - 1; + } + final CartesianChartDataLabelPositioned child = + CartesianChartDataLabelPositioned( + x: x, + y: yValues[index], + dataPointIndex: index, + position: position, + child: callback( + widget.dataSource[pointIndex], + k, + widget.series, + index, + renderer!.index, + position, + ), + ); + add(child); + } + } + + @override + void dispose() { + _builderChildren?.clear(); + _textChildren?.clear(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ChartElementLayoutBuilder( + state: this, + builder: (BuildContext context, BoxConstraints constraints) { + _builderChildren?.clear(); + _textChildren?.clear(); + if (renderer != null && + renderer!.controller.isVisible && + yLists != null && + yLists!.isNotEmpty) { + _ChartDataLabelWidgetBuilder callback; + if (widget.builder != null) { + callback = _dataLabelFromBuilder; + } else { + callback = + widget.mapper != null + ? _dataLabelFromMapper + : _defaultDataLabel; + } + void Function(CartesianChartDataLabelPositioned child) add; + if (widget.builder != null) { + _builderChildren = []; + add = _addToList; + } else { + _textChildren = LinkedList(); + add = _addToLinkedList; + } + + if (xValues != null && xValues!.isNotEmpty) { + if (renderer!.canFindLinearVisibleIndexes) { + _buildLinearDataLabels(callback, add); + } else { + _buildNonLinearDataLabels(callback, add); + } + } + } + + return ChartFadeTransition( + opacity: animation!, + child: CartesianDataLabelStack( + series: renderer, + settings: widget.settings, + labels: _textChildren, + children: _builderChildren ?? [], + ), + ); + }, + ); + } +} + +class CartesianDataLabelStack extends ChartElementStack { + const CartesianDataLabelStack({ + super.key, + required this.series, + required this.labels, + required this.settings, + super.children, + }); + + final CartesianSeriesRenderer? series; + final LinkedList? labels; + final DataLabelSettings settings; + + @override + RenderCartesianDataLabelStack createRenderObject(BuildContext context) { + return RenderCartesianDataLabelStack() + ..series = series + ..labels = labels + ..settings = settings; + } + + @override + void updateRenderObject( + BuildContext context, + RenderCartesianDataLabelStack renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..series = series + ..labels = labels + ..settings = settings; + } +} + +class RenderCartesianDataLabelStack extends RenderChartElementStack { + late CartesianSeriesRenderer? series; + late LinkedList? labels; + late DataLabelSettings settings; + + @override + bool get sizedByParent => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return false; + } + + @override + bool hitTestSelf(Offset position) { + return series?.parent?.onDataLabelTapped != null; + } + + int _findSelectedDataLabelIndex(Offset localPosition) { + if (series?.parent?.onDataLabelTapped == null) { + return -1; + } + + if (childCount > 0) { + RenderBox? child = lastChild; + while (child != null) { + final ChartElementParentData childParentData = + child.parentData! as ChartElementParentData; + if (childParentData.bounds.contains(localPosition)) { + return childParentData.dataPointIndex; + } + child = childParentData.previousSibling; + } + } else if (labels != null) { + for (int i = labels!.length - 1; i > -1; i--) { + final CartesianChartDataLabelPositioned label = labels!.elementAt(i); + if (label.bounds.contains(localPosition)) { + return label.dataPointIndex; + } + } + } + return -1; + } + + @override + void handleTapUp(Offset localPosition) { + if (series?.parent?.onDataLabelTapped != null) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + + final String text = + childCount > 0 + ? '' + : (labels!.elementAt(selectedIndex).child as DataLabelText).text; + series!.parent!.onDataLabelTapped!( + DataLabelTapDetails( + series!.index, + series!.viewportIndex(selectedIndex), + text, + settings, + selectedIndex, + ), + ); + } + } + + @override + void setupParentData(RenderObject child) { + if (child is! ChartElementParentData) { + child.parentData = ChartElementParentData(); + } + } + + @override + void performResize() { + size = constraints.biggest; + } + + @override + void performLayout() { + if (series == null || series!.xAxis == null || series!.yAxis == null) { + return; + } + + if (childCount > 0) { + ChartElementParentData? previousChildData; + RenderBox? child = firstChild; + while (child != null) { + final ChartElementParentData currentChildData = + child.parentData! as ChartElementParentData; + currentChildData.isVisible = true; + final RenderBox? nextSibling = currentChildData.nextSibling; + ChartElementParentData? nextChildData; + if (nextSibling != null) { + nextChildData = nextSibling.parentData! as ChartElementParentData; + } + child.layout(constraints, parentUsesSize: true); + currentChildData.labelAlignment = series!.effectiveDataLabelAlignment( + settings.labelAlignment, + currentChildData.position, + previousChildData, + currentChildData, + nextChildData, + ); + currentChildData.offset = _calculateAlignedPosition( + currentChildData.labelAlignment, + previousChildData, + currentChildData, + nextChildData, + child.size, + ); + final Offset offset = _invokeDataLabelRender( + currentChildData.dataPointIndex, + ); + currentChildData.offset = Offset( + currentChildData.offset.dx + offset.dx, + currentChildData.offset.dy - offset.dy, + ); + currentChildData.bounds = _calculateBounds( + child.size, + currentChildData.offset, + ); + currentChildData.rotatedBounds = calculateRotatedBounds( + currentChildData.bounds, + settings.angle, + ); + child = nextSibling; + previousChildData = currentChildData; + } + } else if (labels != null) { + ChartElementParentData? previousLabelData; + ChartElementParentData? nextLabelData; + for (final CartesianChartDataLabelPositioned currentLabel in labels!) { + currentLabel.isVisible = true; + final ChartElementParentData currentLabelData = + nextLabelData ?? ChartElementParentData() + ..x = currentLabel.x + ..y = currentLabel.y + ..dataPointIndex = currentLabel.dataPointIndex + ..position = currentLabel.position; + final CartesianChartDataLabelPositioned? nextLabel = currentLabel.next; + if (nextLabel != null) { + nextLabelData = + ChartElementParentData() + ..x = nextLabel.x + ..y = nextLabel.y + ..dataPointIndex = nextLabel.dataPointIndex + ..position = nextLabel.position; + } + final DataLabelText details = currentLabel.child as DataLabelText; + final Offset offset = _invokeDataLabelRender( + currentLabel.dataPointIndex, + details, + ); + currentLabel.offset = Offset( + currentLabel.offset.dx + offset.dx, + currentLabel.offset.dy - offset.dy, + ); + currentLabel.size = measureText(details.text, details.textStyle); + currentLabel.labelAlignment = series!.effectiveDataLabelAlignment( + settings.labelAlignment, + currentLabel.position, + previousLabelData, + currentLabelData, + nextLabelData, + ); + currentLabel.offset += _calculateAlignedPosition( + currentLabel.labelAlignment, + previousLabelData, + currentLabelData, + nextLabelData, + currentLabel.size, + ); + currentLabel.bounds = _calculateBounds( + currentLabel.size, + currentLabel.offset, + ); + currentLabel.rotatedBounds = calculateRotatedBounds( + currentLabel.bounds, + settings.angle, + ); + previousLabelData = currentLabelData; + } + } + + _handleLabelIntersectAction(); + } + + Offset _invokeDataLabelRender(int pointIndex, [DataLabelText? details]) { + if (series!.parent?.onDataLabelRender != null) { + final DataLabelRenderArgs dataLabelArgs = DataLabelRenderArgs( + seriesRenderer: series, + dataPoints: series!.chartPoints, + viewportPointIndex: series!.viewportIndex(pointIndex), + pointIndex: pointIndex, + )..offset = settings.offset; + if (details != null) { + dataLabelArgs + ..text = details.text + ..textStyle = details.textStyle + ..color = details.color; + } + + series!.parent!.onDataLabelRender!(dataLabelArgs); + if (details != null) { + details + ..text = dataLabelArgs.text ?? '' + ..textStyle = details.textStyle.merge(dataLabelArgs.textStyle) + ..color = dataLabelArgs.color; + } + + return dataLabelArgs.offset; + } + + return settings.offset; + } + + Offset _calculateAlignedPosition( + ChartDataLabelAlignment alignment, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + Size size, + ) { + final Offset position = series!.dataLabelPosition(current, alignment, size); + + ChartAlignment? xAlignment; + ChartAlignment? yAlignment; + if (series!.isTransposed) { + yAlignment = ChartAlignment.center; + if (settings.alignment != ChartAlignment.center) { + xAlignment = settings.alignment; + } + } else { + xAlignment = ChartAlignment.center; + if (settings.alignment != ChartAlignment.center) { + yAlignment = settings.alignment; + } + } + final double x = _addPlacementAlignment( + xAlignment, + position.dx, + size.width, + ); + final double y = _addPlacementAlignment( + yAlignment, + position.dy, + size.height, + ); + return _alignWithInRange(current.x!, current.y!, x, y, paintBounds, size); + } + + double _addPlacementAlignment( + ChartAlignment? alignment, + double position, + double size, + ) { + if (alignment == null) { + return position; + } + + switch (alignment) { + case ChartAlignment.near: + return position + size; + case ChartAlignment.far: + return position - size; + case ChartAlignment.center: + return position - size / 2; + } + } + + Offset _alignWithInRange( + num xPoint, + num yPoint, + double labelX, + double labelY, + Rect source, + Size size, + ) { + final DoubleRange xRange = series!.xAxis!.effectiveVisibleRange!; + final DoubleRange yRange = series!.yAxis!.effectiveVisibleRange!; + num xValue = xPoint; + if (series!.xAxis! is RenderLogarithmicAxis) { + xValue = (series!.xAxis! as RenderLogarithmicAxis).toLog(xValue); + } + + num yValue = yPoint; + if (series!.yAxis! is RenderLogarithmicAxis) { + yValue = (series!.yAxis! as RenderLogarithmicAxis).toLog(yValue); + } + + if (!xRange.contains(xValue) || !yRange.contains(yValue)) { + return const Offset(double.nan, double.nan); + } + + if (labelX < source.left) { + labelX = source.left; + } else if (labelX + size.width > source.right) { + labelX = source.right - size.width - settings.margin.horizontal; + } + + if (labelY < source.top) { + labelY = source.top; + } else if (labelY + size.height > source.bottom) { + labelY = source.bottom - size.height - settings.margin.vertical; + } + + return Offset(labelX, labelY); + } + + Rect _calculateBounds(Size childSize, Offset offset) { + return Rect.fromLTWH( + offset.dx, + offset.dy, + childSize.width + settings.margin.horizontal, + childSize.height + settings.margin.vertical, + ); + } + + void _handleLabelIntersectAction() { + if (series!.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + if (childCount > 0) { + _handleLabelIntersectActionForWidgets(); + } else if (labels != null) { + _handleLabelIntersectActionForLabels(); + } + } + } + + void _handleLabelIntersectActionForWidgets() { + RenderBox? child = firstChild; + while (child != null) { + final ChartElementParentData currentChildData = + child.parentData! as ChartElementParentData; + if (!currentChildData.isVisible) { + child = currentChildData.nextSibling; + continue; + } + + RenderBox? nextSibling = currentChildData.nextSibling; + ChartElementParentData? nextChildData; + currentChildData.isVisible = true; + while (nextSibling != null) { + nextChildData = nextSibling.parentData! as ChartElementParentData; + if (!currentChildData.rotatedBounds.topLeft.isNaN && + !currentChildData.rotatedBounds.bottomRight.isNaN && + currentChildData.rotatedBounds.overlaps( + nextChildData.rotatedBounds, + )) { + nextChildData.isVisible = false; + } + nextSibling = nextChildData.nextSibling; + } + child = currentChildData.nextSibling; + } + } + + void _handleLabelIntersectActionForLabels() { + for (final CartesianChartDataLabelPositioned label in labels!) { + if (!label.isVisible) { + continue; + } + CartesianChartDataLabelPositioned? nextLabel = label.next; + while (nextLabel != null) { + if (!label.rotatedBounds.topLeft.isNaN && + !label.rotatedBounds.bottomRight.isNaN && + label.rotatedBounds.overlaps(nextLabel.rotatedBounds)) { + nextLabel.isVisible = false; + } + nextLabel = nextLabel.next; + } + } + } + + // To handle multiple series data collision, we need to check the data label + // collision for all the series after data label layout. + @override + void handleDataLabelCollision(CartesianSeriesRenderer series) { + series.parent?.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.dataLabelSettings.isVisible && + child.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none && + child.dataLabelContainer != null) { + child.dataLabelContainer!.handleMultiSeriesDataLabelCollisions(); + } + }); + } + + @override + void handleMultiSeriesDataLabelCollisions() { + if (childCount > 0) { + _handleMultiSeriesDataLabelCollisionsForWidgets(); + } else if (labels != null) { + _handleMultiSeriesDataLabelCollisionsForLabels(); + } + } + + void _handleMultiSeriesDataLabelCollisionsForWidgets() { + series?.parent?.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.index != series!.index && + child.index > series!.index && + child.dataLabelSettings.isVisible && + child.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + final RenderBox? nextSeriesDataLabelRenderBox = + child.dataLabelContainer?.child; + + RenderBox? currentChild = firstChild; + while (currentChild != null) { + final ChartElementParentData currentChildData = + currentChild.parentData! as ChartElementParentData; + if (!currentChildData.isVisible) { + currentChild = currentChildData.nextSibling; + continue; + } + + nextSeriesDataLabelRenderBox?.visitChildren(( + RenderObject nextSeriesDataLabel, + ) { + final RenderCartesianDataLabelStack nextSeriesDataLabelStack = + nextSeriesDataLabel as RenderCartesianDataLabelStack; + if (nextSeriesDataLabelStack.childCount > 0) { + RenderBox? nextChild = nextSeriesDataLabelStack.firstChild; + while (nextChild != null) { + final ChartElementParentData nextChildData = + nextChild.parentData! as ChartElementParentData; + if (!nextChildData.isVisible) { + nextChild = nextChildData.nextSibling; + continue; + } + + if (!currentChildData.rotatedBounds.topLeft.isNaN && + !currentChildData.rotatedBounds.bottomRight.isNaN && + currentChildData.rotatedBounds.overlaps( + nextChildData.rotatedBounds, + )) { + nextChildData.isVisible = false; + } + nextChild = nextChildData.nextSibling; + } + } + }); + currentChild = currentChildData.nextSibling; + } + } + }); + } + + void _handleMultiSeriesDataLabelCollisionsForLabels() { + series?.parent?.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.index != series!.index && + child.index > series!.index && + child.dataLabelSettings.isVisible && + child.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + final RenderBox? nextSeriesDataLabelRenderBox = + child.dataLabelContainer?.child; + for (final CartesianChartDataLabelPositioned currentLabel in labels!) { + if (!currentLabel.isVisible) { + continue; + } + + nextSeriesDataLabelRenderBox?.visitChildren(( + RenderObject nextSeriesDataLabel, + ) { + final RenderCartesianDataLabelStack nextSeriesDataLabelStack = + nextSeriesDataLabel as RenderCartesianDataLabelStack; + final LinkedList? nextLabels = + nextSeriesDataLabelStack.labels; + if (nextLabels != null && nextLabels.isNotEmpty) { + for (final CartesianChartDataLabelPositioned nextLabel + in nextLabels) { + if (!nextLabel.isVisible) { + continue; + } + + if (!currentLabel.rotatedBounds.topLeft.isNaN && + !currentLabel.rotatedBounds.bottomRight.isNaN && + currentLabel.rotatedBounds.overlaps( + nextLabel.rotatedBounds, + )) { + nextLabel.isVisible = false; + } + } + } + }); + } + } + }); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (series == null || series!.xAxis == null || series!.yAxis == null) { + return; + } + + context.canvas + ..save() + ..clipRect(paintBounds); + if (childCount > 0) { + final EdgeInsets margin = settings.margin; + final Offset marginOffset = + offset + Offset(margin.horizontal / 2, margin.vertical); + RenderBox? child = firstChild; + while (child != null) { + final ChartElementParentData childParentData = + child.parentData! as ChartElementParentData; + if (!childParentData.offset.isNaN && childParentData.isVisible) { + context.paintChild(child, childParentData.offset + marginOffset); + } + child = childParentData.nextSibling; + } + } else if (labels != null) { + final Paint fillPaint = Paint()..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..color = settings.borderColor + ..strokeWidth = settings.borderWidth + ..style = PaintingStyle.stroke; + for (final CartesianChartDataLabelPositioned label in labels!) { + if (label.offset.isNaN || !label.isVisible) { + continue; + } + + Color surfaceColor = series!.dataLabelSurfaceColor(label); + final DataLabelText details = label.child as DataLabelText; + surfaceColor = + details.color == Colors.transparent ? surfaceColor : details.color; + final TextStyle effectiveTextStyle = saturatedTextStyle( + surfaceColor, + details.textStyle, + ); + fillPaint + ..color = details.color + ..shader = series!.markerShader(label.offset & label.size); + series!.drawDataLabelWithBackground( + label.dataPointIndex, + context.canvas, + details.text, + label.offset, + settings.angle, + effectiveTextStyle, + fillPaint, + strokePaint, + ); + } + } + context.canvas.restore(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart new file mode 100644 index 000000000..8f5ee1429 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart @@ -0,0 +1,511 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../axis/axis.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import 'marker.dart'; + +mixin ChartElementParentDataMixin { + ChartSeriesRenderer? renderer; + + List? xRawValues; + + List? xValues; + + List>? yLists; + + DoubleRange? sbsInfo; + + List? stackedYValues; + + List? sortedIndexes; + + CurvedAnimation? animation; +} + +class ChartFadeTransition extends FadeTransition { + const ChartFadeTransition({super.key, required super.opacity, super.child}); + + @override + RenderChartFadeTransition createRenderObject(BuildContext context) { + return RenderChartFadeTransition( + opacity: opacity, + alwaysIncludeSemantics: alwaysIncludeSemantics, + ); + } +} + +class RenderChartFadeTransition extends RenderAnimatedOpacity { + RenderChartFadeTransition({ + required super.opacity, + super.alwaysIncludeSemantics, + }); + + ChartMarker markerAt(int pointIndex) { + if (child != null && child is RenderChartElementStack) { + return (child! as RenderChartElementStack).markerAt(pointIndex); + } + return ChartMarker(); + } + + void refresh() { + markNeedsLayout(); + (child as RenderChartElementStack?)?.refresh(); + } + + void handlePointerHover(Offset localPosition) { + (child as RenderChartElementStack?)?.handlePointerHover(localPosition); + } + + void handleTapUp(Offset localPosition) { + (child as RenderChartElementStack?)?.handleTapUp(localPosition); + } + + void handleMultiSeriesDataLabelCollisions() { + (child as RenderChartElementStack?)?.handleMultiSeriesDataLabelCollisions(); + } + + void handleDataLabelCollision(CartesianSeriesRenderer series) { + (child as RenderChartElementStack?)?.handleDataLabelCollision(series); + } +} + +class ChartElementLayoutBuilder + extends CustomConstrainedLayoutBuilder { + const ChartElementLayoutBuilder({ + super.key, + required this.state, + required super.builder, + }); + + final ChartElementParentDataMixin state; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderChartElementLayoutBuilder()..state = state; + } +} + +class RenderChartElementLayoutBuilder extends RenderBox + with + RenderObjectWithChildMixin, + CustomRenderConstrainedLayoutBuilder, + ChartElementParentDataMixin { + late ChartElementParentDataMixin state; + + @override + bool get sizedByParent => true; + + ChartMarker markerAt(int pointIndex) { + if (child != null && child is RenderChartFadeTransition) { + return (child! as RenderChartFadeTransition).markerAt(pointIndex); + } + return ChartMarker(); + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + void refresh() { + markNeedsBuild(); + (child as RenderChartFadeTransition?)?.refresh(); + } + + @override + void performLayout() { + state + ..renderer = renderer + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = yLists + ..stackedYValues = stackedYValues + ..sortedIndexes = sortedIndexes + ..sbsInfo = sbsInfo + ..animation = animation; + rebuildIfNecessary(); + child?.layout(constraints); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return child?.hitTest(result, position: position) ?? false; + } + + void handlePointerHover(Offset localPosition) { + (child as RenderChartFadeTransition?)?.handlePointerHover(localPosition); + } + + void handleTapUp(Offset localPosition) { + (child as RenderChartFadeTransition?)?.handleTapUp(localPosition); + } + + void handleMultiSeriesDataLabelCollisions() { + (child as RenderChartFadeTransition?) + ?.handleMultiSeriesDataLabelCollisions(); + } + + void handleDataLabelCollision(CartesianSeriesRenderer series) { + (child as RenderChartFadeTransition?)?.handleDataLabelCollision(series); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null) { + context.paintChild(child!, offset); + } + } +} + +class ChartElementParentData extends ContainerBoxParentData { + num? x; + num? y; + int dataPointIndex = -1; + ChartDataPointType position = ChartDataPointType.y; + ChartDataLabelAlignment labelAlignment = ChartDataLabelAlignment.auto; + Rect bounds = Rect.zero; + Rect rotatedBounds = Rect.zero; + bool isVisible = true; +} + +class ChartElementStack extends MultiChildRenderObjectWidget { + const ChartElementStack({super.key, super.children}); + + @override + RenderChartElementStack createRenderObject(BuildContext context) { + return RenderChartElementStack(); + } + + @override + void updateRenderObject( + BuildContext context, + RenderChartElementStack renderObject, + ) { + super.updateRenderObject(context, renderObject); + } +} + +class RenderChartElementStack extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + ChartMarker markerAt(int pointIndex) => ChartMarker(); + + void refresh() { + markNeedsLayout(); + } + + void handlePointerHover(Offset localPosition) {} + + void handleTapUp(Offset localPosition) {} + + // Once all cartesian series layouts are completed, use this method to + // handle the collisions of data labels across multiple series. + void handleMultiSeriesDataLabelCollisions() {} + + // To handle multiple series data collision, we need to check the data label + // collision for all the series after data label layout. + void handleDataLabelCollision(CartesianSeriesRenderer series) {} +} + +abstract class CustomConstrainedLayoutBuilder< + ConstraintType extends Constraints +> + extends RenderObjectWidget { + const CustomConstrainedLayoutBuilder({super.key, required this.builder}); + + final Widget Function(BuildContext context, ConstraintType constraints) + builder; + + @override + RenderObjectElement createElement() => + CustomLayoutBuilderElement(this); + + @protected + bool updateShouldRebuild( + covariant CustomConstrainedLayoutBuilder oldWidget, + ) => true; +} + +class CustomLayoutBuilderElement + extends RenderObjectElement { + CustomLayoutBuilderElement( + CustomConstrainedLayoutBuilder super.widget, + ); + + @override + CustomRenderConstrainedLayoutBuilder + get renderObject => + super.renderObject + as CustomRenderConstrainedLayoutBuilder; + + Element? _child; + + @override + void visitChildren(ElementVisitor visitor) { + if (_child != null) { + visitor(_child!); + } + } + + @override + void forgetChild(Element child) { + assert(child == _child); + _child = null; + super.forgetChild(child); + } + + @override + void mount(Element? parent, Object? newSlot) { + super.mount(parent, newSlot); + renderObject.updateCallback(_layout); + } + + @override + void update(CustomConstrainedLayoutBuilder newWidget) { + assert(widget != newWidget); + final CustomConstrainedLayoutBuilder oldWidget = + widget as CustomConstrainedLayoutBuilder; + super.update(newWidget); + assert(widget == newWidget); + + renderObject.updateCallback(_layout); + if (newWidget.updateShouldRebuild(oldWidget)) { + renderObject.markNeedsBuild(); + } + } + + @override + void performRebuild() { + renderObject.markNeedsBuild(); + super.performRebuild(); + } + + @override + void unmount() { + renderObject.updateCallback(null); + super.unmount(); + } + + void _layout(ConstraintType constraints) { + @pragma('vm:notify-debugger-on-exception') + void layoutCallback() { + Widget built; + try { + built = (widget as CustomConstrainedLayoutBuilder) + .builder(this, constraints); + debugWidgetBuilderValue(widget, built); + } catch (e, stack) { + built = ErrorWidget.builder( + _reportException( + ErrorDescription('building $widget'), + e, + stack, + informationCollector: + () => [ + if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)), + ], + ), + ); + } + try { + _child = updateChild(_child, built, null); + assert(_child != null); + } catch (e, stack) { + built = ErrorWidget.builder( + _reportException( + ErrorDescription('building $widget'), + e, + stack, + informationCollector: + () => [ + if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)), + ], + ), + ); + _child = updateChild(null, built, slot); + } + } + + owner!.buildScope(this, layoutCallback); + } + + @override + void insertRenderObjectChild(RenderObject child, Object? slot) { + final RenderObjectWithChildMixin renderObject = + this.renderObject; + assert(slot == null); + assert(renderObject.debugValidateChild(child)); + renderObject.child = child; + assert(renderObject == this.renderObject); + } + + @override + void moveRenderObjectChild( + RenderObject child, + Object? oldSlot, + Object? newSlot, + ) { + assert(false); + } + + @override + void removeRenderObjectChild(RenderObject child, Object? slot) { + final CustomRenderConstrainedLayoutBuilder + renderObject = this.renderObject; + assert(renderObject.child == child); + renderObject.child = null; + assert(renderObject == this.renderObject); + } +} + +mixin CustomRenderConstrainedLayoutBuilder< + ConstraintType extends Constraints, + ChildType extends RenderObject +> + on RenderObjectWithChildMixin { + LayoutCallback? _callback; + + void updateCallback(LayoutCallback? value) { + if (value == _callback) { + return; + } + _callback = value; + markNeedsLayout(); + } + + bool _needsBuild = true; + + void markNeedsBuild() { + _needsBuild = true; + markNeedsLayout(); + } + + Constraints? _previousConstraints; + + void rebuildIfNecessary() { + assert(_callback != null); + if (_needsBuild || constraints != _previousConstraints) { + _previousConstraints = constraints; + _needsBuild = false; + invokeLayoutCallback(_callback!); + } + } +} + +class CustomLayoutBuilder + extends CustomConstrainedLayoutBuilder { + const CustomLayoutBuilder({super.key, required super.builder}); + + @override + RenderObject createRenderObject(BuildContext context) => + CustomRenderLayoutBuilder(); +} + +class CustomRenderLayoutBuilder extends RenderBox + with + RenderObjectWithChildMixin, + CustomRenderConstrainedLayoutBuilder { + @override + double computeMinIntrinsicWidth(double height) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMaxIntrinsicWidth(double height) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMinIntrinsicHeight(double width) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMaxIntrinsicHeight(double width) { + assert(_debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + assert( + debugCannotComputeDryLayout( + reason: + 'Calculating the dry layout would require running the layout callback ' + 'speculatively, which might mutate the live render object tree.', + ), + ); + return Size.zero; + } + + @override + void performLayout() { + final BoxConstraints constraints = this.constraints; + rebuildIfNecessary(); + if (child != null) { + child!.layout(constraints, parentUsesSize: true); + size = constraints.constrain(child!.size); + } else { + size = constraints.biggest; + } + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + if (child != null) { + return child!.getDistanceToActualBaseline(baseline); + } + return super.computeDistanceToActualBaseline(baseline); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return child?.hitTest(result, position: position) ?? false; + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null) { + context.paintChild(child!, offset); + } + } + + bool _debugThrowIfNotCheckingIntrinsics() { + assert(() { + if (!RenderObject.debugCheckingIntrinsics) { + throw FlutterError( + 'LayoutBuilder does not support returning intrinsic dimensions.\n' + 'Calculating the intrinsic dimensions would require running the layout ' + 'callback speculatively, which might mutate the live render object tree.', + ); + } + return true; + }()); + + return true; + } +} + +FlutterErrorDetails _reportException( + DiagnosticsNode context, + Object exception, + StackTrace stack, { + InformationCollector? informationCollector, +}) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widgets library', + context: context, + informationCollector: informationCollector, + ); + FlutterError.reportError(details); + return details; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/empty_points.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/empty_points.dart new file mode 100644 index 000000000..d0661accd --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/empty_points.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; + +/// Handling empty points in charts +/// +/// Data points with a null value are considered empty points. Empty data points +/// are ignored and are not plotted in the chart. +/// By using the emptyPointSettings property in series, you can decide on the +/// action taken for empty points. +/// +/// Defaults to `EmptyPointMode.gap`. +/// +/// _Note:_ This is common for Cartesian, circular, pyramid and funnel charts. +class EmptyPointSettings { + /// Creating an argument constructor of EmptyPointSettings class. + const EmptyPointSettings({ + this.color = Colors.grey, + this.mode = EmptyPointMode.gap, + this.borderColor = Colors.transparent, + this.borderWidth = 2.0, + }); + + /// Color of the empty data point. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// emptyPointSettings: EmptyPointSettings( + /// color: Colors.black, + /// mode: EmptyPointMode.average + /// ) + /// ), + /// ], + /// ); + /// } + /// ``` + final Color color; + + /// Border color of the empty data point. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// emptyPointSettings: EmptyPointSettings( + /// borderColor: Colors.black, + /// borderWidth: 2, + /// mode:EmptyPointMode.average + /// ) + /// ), + /// ], + /// ); + /// } + /// ``` + final Color borderColor; + + /// Border width of the empty data point. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// emptyPointSettings: EmptyPointSettings( + /// borderColor: Colors.black, + /// borderWidth: 2, + /// mode:EmptyPointMode.average + /// ) + /// ), + /// ], + /// ); + /// } + /// ``` + final double borderWidth; + + /// By default, gap will be generated for empty points, i.e. data points + /// with null value. + /// + /// The empty points display the values that can be considered as zero, + /// average, or gap. + /// + /// Defaults to `EmptyPointMode.gap`. + /// + /// Also refer [EmptyPointMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// emptyPointSettings: EmptyPointSettings( + /// mode:EmptyPointMode.average + /// ) + /// ), + /// ], + /// ); + /// } + /// ``` + final EmptyPointMode mode; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/funnel_data_label.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/funnel_data_label.dart new file mode 100644 index 000000000..379133bec --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/funnel_data_label.dart @@ -0,0 +1,553 @@ +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../series/funnel_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'callbacks.dart'; +import 'chart_point.dart'; +import 'data_label.dart'; +import 'element_widget.dart'; + +// ignore: must_be_immutable +base class FunnelChartDataLabelPositioned + extends ParentDataWidget + with LinkedListEntry { + FunnelChartDataLabelPositioned({ + super.key, + required this.x, + required this.y, + required this.dataPointIndex, + required this.position, + required super.child, + }); + + final num x; + final num y; + final int dataPointIndex; + final ChartDataPointType position; + + Path connectorPath = Path(); + Offset offset = Offset.zero; + Size size = Size.zero; + + @override + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is ChartElementParentData); + final ChartElementParentData parentData = + renderObject.parentData! as ChartElementParentData; + bool needsLayout = false; + + if (parentData.x != x) { + parentData.x = x; + needsLayout = true; + } + + if (parentData.y != y) { + parentData.y = y; + needsLayout = true; + } + + if (parentData.dataPointIndex != dataPointIndex) { + parentData.dataPointIndex = dataPointIndex; + needsLayout = true; + } + + if (parentData.position != position) { + parentData.position = position; + needsLayout = true; + } + + if (needsLayout) { + final RenderObject? targetParent = renderObject.parent; + if (targetParent is RenderObject) { + targetParent.markNeedsLayout(); + } + } + } + + @override + Type get debugTypicalAncestorWidgetClass => FunnelDataLabelStack; +} + +class FunnelDataLabelContainer extends StatefulWidget { + const FunnelDataLabelContainer({ + super.key, + required this.series, + required this.dataSource, + this.mapper, + this.builder, + required this.settings, + }); + + final ChartWidgetBuilder? builder; + final List dataSource; + final ChartValueMapper? mapper; + final FunnelSeries series; + final DataLabelSettings settings; + + @override + State> createState() => + _FunnelDataLabelContainerState(); +} + +typedef _ChartDataLabelWidgetBuilder = + Widget Function( + T data, + ChartPoint point, + FunnelSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ); + +class _FunnelDataLabelContainerState + extends State> + with ChartElementParentDataMixin { + List? _builderChildren; + LinkedList? _textChildren; + + Widget _dataLabelFromBuilder( + T data, + ChartPoint point, + FunnelSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + return widget.builder!(data, point, series, pointIndex, seriesIndex); + } + + Widget _dataLabelFromMapper( + T data, + ChartPoint point, + FunnelSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final String text = widget.mapper!(data, pointIndex) ?? ''; + return _buildDataLabelText(text, pointIndex); + } + + Widget _defaultDataLabel( + T data, + ChartPoint point, + FunnelSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final num value = point[position]; + final String formattedText = decimalLabelValue(value); + return _buildDataLabelText(formattedText, pointIndex); + } + + Color _dataPointColor(int dataPointIndex) { + final DataLabelSettings settings = widget.settings; + if (settings.color != null) { + return settings.color!.withValues(alpha: settings.opacity); + } else if (settings.useSeriesColor) { + final int segmentsLastIndex = renderer!.segments.length - 1; + return renderer! + .segments[segmentsLastIndex - dataPointIndex] + .fillPaint + .color + .withValues(alpha: settings.opacity); + } + return Colors.transparent; + } + + DataLabelText _buildDataLabelText(String text, int pointIndex) { + final RenderChartPlotArea parent = renderer!.parent!; + final TextStyle dataLabelTextStyle = parent.themeData!.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(parent.chartThemeData!.dataLabelTextStyle) + .merge(widget.settings.textStyle); + return DataLabelText( + text: text, + textStyle: dataLabelTextStyle, + color: _dataPointColor(pointIndex), + ); + } + + void _addToList(FunnelChartDataLabelPositioned child) { + _builderChildren!.add(child); + } + + void _addToLinkedList(FunnelChartDataLabelPositioned child) { + _textChildren!.add(child); + } + + void _buildDataLabels( + _ChartDataLabelWidgetBuilder callback, + Function(FunnelChartDataLabelPositioned) add, + ) { + const List positions = ChartDataPointType.values; + final int yLength = yLists?.length ?? 0; + final int posAdj = _positionIndex(yLength); + List? actualXValues; + if (xRawValues != null && xRawValues!.isNotEmpty) { + actualXValues = xRawValues; + } else { + actualXValues = xValues; + } + + if (actualXValues == null || renderer!.segments.isEmpty) { + return; + } + + for (int i = 0; i < renderer!.dataCount; i++) { + _obtainLabel(i, actualXValues, yLength, positions, posAdj, callback, add); + } + } + + int _positionIndex(int yListsLength) { + return yListsLength == 1 ? 0 : 1; + } + + void _obtainLabel( + int index, + List rawXValues, + int yLength, + List positions, + int posAdj, + _ChartDataLabelWidgetBuilder callback, + Function(FunnelChartDataLabelPositioned) add, + ) { + final num x = xValues![index]; + final ChartPoint point = ChartPoint(x: rawXValues[index] as D?); + + for (int k = 0; k < yLength; k++) { + final List yValues = yLists![k]; + point.y = yValues[index]; + final ChartDataPointType position = positions[k + posAdj]; + final FunnelChartDataLabelPositioned child = + FunnelChartDataLabelPositioned( + x: x, + y: yValues[index], + dataPointIndex: index, + position: position, + child: callback( + widget.dataSource[index], + point, + widget.series, + index, + renderer!.index, + position, + ), + ); + add(child); + } + } + + @override + Widget build(BuildContext context) { + return ChartElementLayoutBuilder( + state: this, + builder: (BuildContext context, BoxConstraints constraints) { + _ChartDataLabelWidgetBuilder callback; + _builderChildren?.clear(); + _textChildren?.clear(); + if (renderer != null && + renderer!.initialIsVisible && + yLists != null && + yLists!.isNotEmpty) { + if (widget.builder != null) { + callback = _dataLabelFromBuilder; + } else { + callback = + widget.mapper != null + ? _dataLabelFromMapper + : _defaultDataLabel; + } + void Function(FunnelChartDataLabelPositioned child) add; + if (widget.builder != null) { + _builderChildren = []; + add = _addToList; + } else { + _textChildren = LinkedList(); + add = _addToLinkedList; + } + + if (xValues != null && xValues!.isNotEmpty) { + _buildDataLabels(callback, add); + } + } + + return ChartFadeTransition( + opacity: animation!, + child: FunnelDataLabelStack( + series: renderer as FunnelSeriesRenderer?, + settings: widget.settings, + labels: _textChildren, + children: _builderChildren ?? [], + ), + ); + }, + ); + } +} + +class FunnelDataLabelStack extends ChartElementStack { + const FunnelDataLabelStack({ + super.key, + required this.series, + required this.labels, + required this.settings, + super.children, + }); + + final FunnelSeriesRenderer? series; + final LinkedList? labels; + final DataLabelSettings settings; + + @override + RenderFunnelDataLabelStack createRenderObject(BuildContext context) { + return RenderFunnelDataLabelStack() + ..series = series + ..labels = labels + ..settings = settings; + } + + @override + void updateRenderObject( + BuildContext context, + RenderFunnelDataLabelStack renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..series = series + ..labels = labels + ..settings = settings; + } +} + +class RenderFunnelDataLabelStack extends RenderChartElementStack { + late FunnelSeriesRenderer? series; + late LinkedList? labels; + late DataLabelSettings settings; + + @override + bool get sizedByParent => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return false; + } + + @override + bool hitTestSelf(Offset position) { + return series?.parent?.onDataLabelTapped != null && + _findSelectedDataLabelIndex(position) != -1; + } + + int _findSelectedDataLabelIndex(Offset localPosition) { + if (series?.parent?.onDataLabelTapped == null) { + return -1; + } + + if (childCount > 0) { + RenderBox? child = lastChild; + while (child != null) { + final ChartElementParentData childParentData = + child.parentData! as ChartElementParentData; + if ((childParentData.offset & child.size).contains(localPosition)) { + return childParentData.dataPointIndex; + } + child = childParentData.previousSibling; + } + } else if (labels != null) { + for (int i = labels!.length - 1; i > -1; i--) { + final FunnelChartDataLabelPositioned label = labels!.elementAt(i); + final Rect rect = Rect.fromLTWH( + label.offset.dx, + label.offset.dy, + label.size.width + settings.margin.horizontal, + label.size.height + settings.margin.vertical, + ); + if (rect.contains(localPosition)) { + return label.dataPointIndex; + } + } + } + return -1; + } + + @override + void handleTapUp(Offset localPosition) { + if (series?.parent?.onDataLabelTapped != null) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + + final String text = + childCount > 0 + ? '' + : (labels!.elementAt(selectedIndex).child as DataLabelText).text; + series!.parent!.onDataLabelTapped!( + DataLabelTapDetails( + series!.index, + series!.viewportIndex(selectedIndex), + text, + settings, + selectedIndex, + ), + ); + } + } + + @override + void setupParentData(RenderObject child) { + if (child is! ChartElementParentData) { + child.parentData = ChartElementParentData(); + } + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + @override + void performLayout() { + if (series == null) { + return; + } + + if (childCount > 0) { + RenderBox? child = firstChild; + while (child != null) { + final ChartElementParentData currentChildData = + child.parentData! as ChartElementParentData; + final RenderBox? nextSibling = currentChildData.nextSibling; + child.layout(constraints, parentUsesSize: true); + currentChildData.offset = series!.dataLabelPosition( + currentChildData, + child.size, + ); + final Offset offset = _invokeDataLabelRender( + currentChildData.dataPointIndex, + ); + currentChildData.offset = Offset( + currentChildData.offset.dx + offset.dx, + currentChildData.offset.dy - offset.dy, + ); + // TODO(Praveen): Builder works only for inner and outer position, + // Need to handle for intersection. + child = nextSibling; + } + } else if (labels != null) { + for (final FunnelChartDataLabelPositioned currentLabel in labels!) { + final ChartElementParentData currentLabelData = + ChartElementParentData() + ..x = currentLabel.x + ..y = currentLabel.y + ..dataPointIndex = currentLabel.dataPointIndex + ..position = currentLabel.position; + final DataLabelText details = currentLabel.child as DataLabelText; + final Offset offset = _invokeDataLabelRender( + currentLabel.dataPointIndex, + details, + ); + currentLabel.offset = Offset( + currentLabel.offset.dx + offset.dx, + currentLabel.offset.dy - offset.dy, + ); + currentLabel.size = measureText(details.text, details.textStyle); + currentLabel.offset += series!.dataLabelPosition( + currentLabelData, + currentLabel.size, + ); + currentLabel.connectorPath = _calculateConnectorPath( + currentLabel.dataPointIndex, + currentLabel.offset, + currentLabel.size, + ); + } + } + } + + Path _calculateConnectorPath(int index, Offset offset, Size size) { + final int segmentsLastIndex = series!.segments.length - 1; + final List points = + series!.segments[segmentsLastIndex - index].points; + final double startPoint = (points[1].dx + points[2].dx) / 2; + final double endPoint = offset.dx; + final double y = offset.dy + size.height / 2; + return Path() + ..moveTo(startPoint, y) + ..lineTo(endPoint, y); + } + + Offset _invokeDataLabelRender(int pointIndex, [DataLabelText? details]) { + if (series!.parent?.onDataLabelRender != null) { + final DataLabelRenderArgs dataLabelArgs = DataLabelRenderArgs( + seriesRenderer: series, + dataPoints: series!.chartPoints, + viewportPointIndex: pointIndex, + pointIndex: pointIndex, + )..offset = settings.offset; + if (details != null) { + dataLabelArgs + ..text = details.text + ..textStyle = details.textStyle + ..color = details.color; + } + + series!.parent!.onDataLabelRender!(dataLabelArgs); + if (details != null) { + details + ..text = dataLabelArgs.text ?? '' + ..textStyle = details.textStyle.merge(dataLabelArgs.textStyle) + ..color = dataLabelArgs.color; + } + + return dataLabelArgs.offset; + } + + return settings.offset; + } + + @override + void paint(PaintingContext context, Offset offset) { + context.canvas + ..save() + ..clipRect(paintBounds); + if (childCount > 0) { + defaultPaint(context, offset); + } else if (labels != null) { + final List previousRect = []; + final Paint fillPaint = Paint(); + final Paint strokePaint = + Paint() + ..color = settings.borderColor + ..strokeWidth = settings.borderWidth + ..style = PaintingStyle.stroke; + for (final FunnelChartDataLabelPositioned label in labels!) { + final DataLabelText details = label.child as DataLabelText; + fillPaint.color = details.color; + series!.drawDataLabelWithBackground( + label.dataPointIndex, + context.canvas, + details.text, + label.size, + label.offset, + settings.angle, + details.textStyle, + fillPaint, + strokePaint, + label.connectorPath, + previousRect, + ); + } + } + context.canvas.restore(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/interactive_tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/interactive_tooltip.dart new file mode 100644 index 000000000..c5cdfd990 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/interactive_tooltip.dart @@ -0,0 +1,459 @@ +import 'package:flutter/material.dart'; + +/// Customizes the interactive tooltip. +/// +/// Shows the information about the segments. To enable the interactive tooltip, +/// set that [enable] property to true. +/// +/// By using this,to customize the [color], [borderWidth], [borderRadius], +/// [format] and so on. +/// +/// _Note:_ Interactive tooltip applicable for axis types and trackball. +@immutable +class InteractiveTooltip { + /// Creating an argument constructor of InteractiveTooltip class. + const InteractiveTooltip({ + this.enable = true, + this.color, + this.borderColor, + this.borderWidth = 0, + this.borderRadius = 5, + this.arrowLength = 7, + this.arrowWidth = 5, + this.format, + this.connectorLineColor, + this.connectorLineWidth = 1.5, + this.connectorLineDashArray, + this.decimalPlaces = 3, + this.canShowMarker = true, + this.textStyle, + }); + + /// Toggles the visibility of the interactive tooltip in an axis. + /// + /// This tooltip will be displayed at the axis for crosshair and + /// will be displayed near to the trackline for trackball. + /// + /// Defaults to `true`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip(enable: false) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final bool enable; + + /// Color of the interactive tooltip. + /// + /// Used to change the [color] of the tooltip text. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// color: Colors.grey + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final Color? color; + + /// Border color of the interactive tooltip. + /// + /// Used to change the stroke color of the axis tooltip. + /// + /// Defaults to `Colors.black`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final Color? borderColor; + + /// Border width of the interactive tooltip. + /// + /// Used to change the stroke width of the axis tooltip. + /// + /// Defaults to `0`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final double borderWidth; + + /// Customizes the text in the interactive tooltip. + /// + /// Used to change the text color, size, font family, font style, etc. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// textStyle: TextStyle( + /// fontSize: 14 + /// ) + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Customizes the corners of the interactive tooltip. + /// + /// Each corner can be customized with a desired value or a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// borderColor: Colors.red, + /// borderWidth: 3, + /// borderRadius: 2 + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final double borderRadius; + + /// It Specifies the length of the tooltip. + /// + /// Defaults to `7`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// arrowLength: 5 + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior:trackballBehavior + /// ); + /// } + /// ``` + final double arrowLength; + + /// It specifies the width of the tooltip arrow. + /// + /// Defaults to `5`. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// arrowWidth: 4 + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final double arrowWidth; + + /// Text format of the interactive tooltip. + /// + /// By default, axis value will be displayed in the tooltip, and it can be + /// customized by adding desired text as prefix or suffix. + /// + /// ```dart + /// late TrackballBehavior trackballBehavior; + /// + /// void initState() { + /// trackballBehavior = TrackballBehavior( + /// enable: true, + /// tooltipSettings: InteractiveTooltip( + /// format: 'point.y %' + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: trackballBehavior + /// ); + /// } + /// ``` + final String? format; + + /// Width of the selection zooming tooltip connector line. + /// + /// Defaults to `1.5`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior, + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip( + /// connectorLineWidth: 2 + /// ) + /// ), + /// ); + /// } + /// ``` + final double connectorLineWidth; + + /// Color of the selection zooming tooltip connector line. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior, + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip( + /// connectorLineColor: Colors.red + /// ) + /// ), + /// ); + /// } + /// ``` + final Color? connectorLineColor; + + /// Giving dash array to the selection zooming tooltip connector line. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior = zoomPanBehavior, + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip( + /// connectorLineDashArray: [10,10] + /// ) + /// ), + /// ); + /// } + /// ``` + final List? connectorLineDashArray; + + /// Rounding decimal places of the selection zooming tooltip or trackball + /// tooltip label. + /// + /// Defaults to `3`. + /// + /// ```dart + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior, + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip( + /// decimalPlaces: 4 + /// ) + /// ), + /// ); + /// } + /// ``` + 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 + /// late ZoomPanBehavior zoomPanBehavior; + /// + /// void initState() { + /// zoomPanBehavior = ZoomPanBehavior( + /// enableSelectionZooming: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// zoomPanBehavior: zoomPanBehavior, + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip( + /// canShowMarker: false + /// ) + /// ), + /// ); + /// } + /// ``` + final bool canShowMarker; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is InteractiveTooltip && + other.enable == enable && + other.color == color && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.borderRadius == borderRadius && + other.arrowLength == arrowLength && + other.arrowWidth == arrowWidth && + other.format == format && + other.connectorLineColor == connectorLineColor && + other.connectorLineWidth == connectorLineWidth && + other.connectorLineDashArray == connectorLineDashArray && + other.decimalPlaces == decimalPlaces && + other.canShowMarker == canShowMarker && + other.textStyle == textStyle; + } + + @override + int get hashCode { + final List values = [ + enable, + color, + borderColor, + borderWidth, + borderRadius, + arrowLength, + arrowWidth, + format, + connectorLineColor, + connectorLineWidth, + connectorLineDashArray, + decimalPlaces, + canShowMarker, + textStyle, + ]; + return Object.hashAll(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/layout_handler.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/layout_handler.dart new file mode 100644 index 000000000..b02a8bfab --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/layout_handler.dart @@ -0,0 +1,23 @@ +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +class ChartLayoutHandler extends SingleChildRenderObjectWidget { + const ChartLayoutHandler({super.key, required super.child}); + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderChartLayoutHandler(); + } +} + +class RenderChartLayoutHandler extends RenderProxyBox { + @override + void performLayout() { + final double desiredWidth = + constraints.maxWidth.isInfinite ? 300 : constraints.maxWidth; + final double desiredHeight = + constraints.maxHeight.isInfinite ? 300 : constraints.maxHeight; + child?.layout(BoxConstraints.tight(Size(desiredWidth, desiredHeight))); + size = Size(desiredWidth, desiredHeight); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/legend.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/legend.dart new file mode 100644 index 000000000..73c8f4fc0 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/legend.dart @@ -0,0 +1,781 @@ +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; +import '../utils/typedef.dart'; +import 'core_legend.dart' as core; + +/// Identify the series in chart. +/// +/// Legend contains list of chart series/data points in chart. It helps to +/// identify the corresponding data series in chart. The name property of +/// [SfCartesianChart] is used to define the label for the corresponding series +/// legend item and for [SfCircularChart] type chart by default values mapped +/// with xValueMapper will be displayed. +/// +/// Provides options such as isVisible, borderWidth, alignment, opacity, +/// borderColor, padding and so on to customize the appearance of the legend. +@immutable +class Legend { + /// Creating an argument constructor of Legend class. + const Legend({ + this.isVisible = false, + this.position = LegendPosition.auto, + this.alignment = ChartAlignment.center, + this.backgroundColor, + this.borderColor, + this.borderWidth = 1.0, + this.opacity = 1.0, + this.height, + this.width, + this.padding = 10.0, + this.iconHeight = 12.0, + this.iconWidth = 12.0, + this.shouldAlwaysShowScrollbar = false, + this.toggleSeriesVisibility = true, + this.textStyle, + this.isResponsive = false, + this.orientation = LegendItemOrientation.auto, + this.title, + this.overflowMode = LegendItemOverflowMode.scroll, + this.legendItemBuilder, + this.iconBorderColor, + this.iconBorderWidth, + this.itemPadding = 15.0, + this.offset, + this.image, + }); + + /// Toggles the visibility of the legend. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true + /// ) + /// ); + /// } + /// ``` + final bool isVisible; + + /// Position of the legend. + /// + /// If the chart width is greater than chart height, then the + /// legend will be placed at the right, else it will be placed + /// at the bottom of the chart.The available options are auto, + /// bottom, left, right, and top. + /// + /// Defaults to `LegendPosition.auto`. + /// + /// Also refer [LegendPosition]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// position: LegendPosition.bottom + /// ) + /// ); + /// } + /// ``` + final LegendPosition position; + + /// Alignment of the legend. + /// + /// Alignment will work if the legend width is greater than + /// the total legend item's width. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// alignment: ChartAlignment.near + /// ) + /// ); + /// } + /// ``` + final ChartAlignment alignment; + + /// Background color of the legend. + /// + /// Used to change the background color of legend shape. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// backgroundColor: Colors.grey + /// ) + /// ); + /// } + /// ``` + final Color? backgroundColor; + + /// Toggles the scrollbar visibility. + /// + /// When set to false, the scrollbar appears only when scrolling else the + /// scrollbar fades out. When true, the scrollbar will never fade out and will + /// always be visible when the items are overflown. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend:Legend( + /// isVisible: true, + /// shouldAlwaysShowScrollbar: true + /// ) + /// ); + /// } + /// ``` + final bool shouldAlwaysShowScrollbar; + + /// Border color of the legend. + /// + /// Used to change the stroke color of the legend shape. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ) + /// ); + /// } + /// ``` + final Color? borderColor; + + /// Border width of the legend. + /// + /// Used to change the stroke width of the legend shape. + /// + /// Defaults to `0.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Border color of the icon in the legend items. + /// + /// Used to change the stroke color of the legend icon shape. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// iconBorderColor: Colors.yellow, + /// iconBorderWidth: 4 + /// ) + /// ); + /// } + /// ``` + final Color? iconBorderColor; + + /// Border width of the icon in the legend items. + /// + /// Used to change the stroke width of the legend icon shape. + /// + /// Defaults to null. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// iconBorderColor: Colors.yellow, + /// iconBorderWidth: 4 + /// ) + /// ); + /// } + /// ``` + final double? iconBorderWidth; + + /// Opacity of the legend. + /// + /// Used to control the transparency of the legend icon shape. + /// The value ranges from 0 to 1. + /// + /// Defaults to `1.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// opacity: 0.5 + /// ) + /// ); + /// } + /// ``` + final double opacity; + + /// The height of the legend. + /// + /// It takes percentage value from the overall chart height. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// height: '30%' + /// ) + /// ); + /// } + /// ``` + final String? height; + + /// The width of the legend. + /// + /// It takes percentage value from the overall chart width. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// width: '30%' + /// ) + /// ); + /// } + /// ``` + final String? width; + + /// Padding between the legend items. + /// + /// Used to add padding between the icon shape and the text. + /// + /// Defaults to `5.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// padding: 4.0 + /// ) + /// ); + /// } + /// ``` + final double padding; + + /// Height of the icon in legend item. + /// + /// Used to change the height of the icon shape. + /// + /// Defaults to `12`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// iconHeight: 14 + /// ) + /// ); + /// } + /// ``` + final double iconHeight; + + /// Width of the icon in legend item. + /// + /// Used to change the width of the icon shape. + /// + /// Defaults to `12`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// iconWidth: 14 + /// ) + /// ); + /// } + /// ``` + final double iconWidth; + + /// Toggles the series visibility. + /// + /// If it is set to false, then on tapping the legend item, + /// series visibility will not be toggled. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// toggleSeriesVisibility: false + /// ) + /// ); + /// } + /// ``` + final bool toggleSeriesVisibility; + + /// Customizes the legend item text. + /// + /// Used to change the text color, size, font family, font style, etc. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// textStyle: TextStyle(color: Colors.red) + /// ) + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Toggles the visibility of the legend. + /// + /// If the width or height of the legend is greater than the plot area bounds. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// isResponsive: true + /// ) + /// ); + /// } + /// ``` + final bool isResponsive; + + /// Orientation of the legend. + /// + /// The legend items will be placed either in horizontal or + /// in vertical orientation. By default, it is set to auto, i.e. if the + /// legend position is top or bottom, orientation is set + /// to horizontal, else it is set to vertical. + /// + /// Defaults to `LegendItemOrientation.auto`. + /// + /// Also refer [LegendItemOrientation]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// orientation: LegendItemOrientation.vertical + /// ) + /// ); + /// } + /// ``` + final LegendItemOrientation orientation; + + /// Customizes the legend title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// title: LegendTitle( + /// text: 'Countries' + /// ) + /// ) + /// ); + /// } + /// ``` + final LegendTitle? title; + + /// Widget builder for legend items. + /// + /// Customize the appearance of legend items in + /// template by using `legendItemBuilder` property of legend. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// legendItemBuilder: + /// (String name, dynamic series, dynamic point, int index) { + /// return Container( + /// height: 30, + /// width: 80, + /// child: Row( + /// children: [ + /// Container(child: Image.asset('images/bike.png')), + /// Container(child: Text(index.toString())), + /// ] + /// ) + /// ); + /// } + /// ) + /// ); + /// } + /// ``` + final LegendItemBuilder? legendItemBuilder; + + /// Overflow legend items. + /// + /// The legend items can be placed in multiple rows or scroll can be enabled + /// using the overflowMode property if size of the total legend items exceeds + /// the available size. It can be scrolled, wrapped, or left. + /// + /// Defaults to `LegendItemOverflowMode.scroll`. + /// + /// Also refer [LegendItemOverflowMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// overflowMode: LegendItemOverflowMode.wrap + /// ) + /// ); + /// } + /// ``` + final LegendItemOverflowMode overflowMode; + + /// Padding of the legend items. + /// + /// Used to add padding between the first legend text and the second + /// legend icon shape. + /// + /// Defaults to `10.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// itemPadding: 5 + /// ) + /// ); + /// } + /// ``` + final double itemPadding; + + /// Places the legend in custom position. + /// + /// If the [offset] has been set, the legend is moved from its actual + /// position. For example, if the [position] is `top`, then the legend + /// will be placed in the top but in the position added to the + /// actual top position. + /// + /// Also, the legend will not take a dedicated position for it and will be + /// drawn on the top of the chart's plot area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// offset: Offset(20,40) + /// ) + /// ); + /// } + /// ``` + final Offset? offset; + + /// Used to add image to the legend icon. + /// + /// Default image size is `10.0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// image: const AssetImage('images/bike.png') + /// ) + /// ); + /// } + /// ``` + final ImageProvider? image; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is Legend && + other.isVisible == isVisible && + other.position == position && + other.alignment == alignment && + other.backgroundColor == backgroundColor && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.opacity == opacity && + other.height == height && + other.width == width && + other.padding == padding && + other.iconHeight == iconHeight && + other.iconWidth == iconWidth && + other.toggleSeriesVisibility == toggleSeriesVisibility && + other.textStyle == textStyle && + other.isResponsive == isResponsive && + other.orientation == orientation && + other.title == title && + other.overflowMode == overflowMode && + other.legendItemBuilder == legendItemBuilder && + other.iconBorderColor == iconBorderColor && + other.iconBorderWidth == iconBorderWidth && + other.itemPadding == itemPadding && + other.image == image; + } + + @override + int get hashCode { + final List values = [ + isVisible, + position, + alignment, + backgroundColor, + borderColor, + borderWidth, + opacity, + height, + width, + padding, + iconHeight, + iconWidth, + toggleSeriesVisibility, + textStyle, + isResponsive, + orientation, + title, + overflowMode, + legendItemBuilder, + iconBorderColor, + iconBorderWidth, + itemPadding, + image, + ]; + return Object.hashAll(values); + } +} + +/// Customizes the legend title. +/// +/// Takes a string and display the legend title.By default,the legend title +/// horizontally center aligned to the chart's width and it will placed at +/// the bottom of the chart. +/// +/// Provides Options to customize the [text], [textStyle] and [alignment] +/// properties. +@immutable +class LegendTitle { + /// Creating an argument constructor of LegendTitle class. + const LegendTitle({ + this.text, + this.textStyle, + this.alignment = ChartAlignment.center, + }); + + /// Legend title text. + /// + /// Used to change the text of the title. + /// + /// Defaults to `‘’`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// title: ChartTitle( + /// text: 'Legend title' + /// ) + /// ) + /// ); + /// } + /// ``` + final String? text; + + /// Customize the legend title text. + /// + /// Used to change the text color, size, font family, fontStyle, and + /// font weight for the legend title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// title: ChartTitle( + /// text: 'Legend title', + /// textStyle: TextStyle(color: Colors.red) + /// ) + /// ) + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Alignment of the legend title. + /// + /// Used to change the alignment of the title text. + /// + /// It can be `ChartAlignment.near`,`ChartAlignment.center`, or + /// `ChartAlignment.far`. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment] + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend( + /// isVisible: true, + /// title: ChartTitle( + /// text: 'Legend title', + /// alignment: ChartAlignment.near + /// ) + /// ) + /// ); + /// } + /// ``` + final ChartAlignment alignment; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is LegendTitle && + other.textStyle == textStyle && + other.alignment == alignment && + other.text == text; + } + + @override + int get hashCode { + final List values = [text, textStyle, alignment]; + return Object.hashAll(values); + } +} + +/// Represents a cartesian legend item in legends. +class CartesianLegendItem extends core.LegendItem { + CartesianLegendItem({ + required super.text, + required super.iconType, + required super.iconColor, + required super.iconBorderWidth, + required super.seriesIndex, + super.iconBorderColor, + super.shader, + super.imageProvider, + super.overlayMarkerType, + super.isToggled = false, + super.onTap, + super.onRender, + super.series, + super.pointIndex = -1, + }); +} + +/// Represents a circular legend item in legends. +class CircularLegendItem extends core.LegendItem { + CircularLegendItem({ + required super.text, + required super.iconType, + required super.iconColor, + required super.iconBorderWidth, + required super.seriesIndex, + super.iconBorderColor, + super.shader, + super.imageProvider, + super.degree, + super.endAngle, + super.startAngle, + super.isToggled = false, + super.onTap, + super.onRender, + super.series, + super.pointIndex = -1, + }); +} + +/// Represents a funnel legend item in legends. +class FunnelLegendItem extends core.LegendItem { + FunnelLegendItem({ + required super.text, + required super.iconType, + required super.iconColor, + required super.iconBorderWidth, + required super.seriesIndex, + super.iconBorderColor, + super.shader, + super.imageProvider, + super.isToggled = false, + super.onTap, + super.onRender, + super.series, + super.pointIndex = -1, + }); +} + +/// Represents a pyramid legend item in legends. +class PyramidLegendItem extends core.LegendItem { + PyramidLegendItem({ + required super.text, + required super.iconType, + required super.iconColor, + required super.iconBorderWidth, + required super.seriesIndex, + super.iconBorderColor, + super.shader, + super.imageProvider, + super.isToggled = false, + super.onTap, + super.onRender, + super.series, + super.pointIndex = -1, + }); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart new file mode 100644 index 000000000..b7c285205 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart @@ -0,0 +1,621 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart' show DataMarkerType; + +import '../axis/axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../series/chart_series.dart'; +import '../utils/helper.dart'; +import 'callbacks.dart'; +import 'element_widget.dart'; + +/// 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 [isVisible] +/// property of [MarkerSettings]. +/// +/// Provides the options of [color], border width, border color and [shape] of +/// the marker to customize the appearance. +@immutable +class MarkerSettings { + /// Creating an argument constructor of MarkerSettings class. + const MarkerSettings({ + this.isVisible = false, + this.color, + this.shape = DataMarkerType.circle, + this.height = 8.0, + this.width = 8.0, + this.borderColor, + this.borderWidth = 2.0, + this.image, + }); + + /// Toggles the visibility of the marker. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings(isVisible: true), + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isVisible; + + /// Height of the marker shape. + /// + /// Defaults to `4`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// height: 6 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final double height; + + /// Width of the marker shape. + /// + /// Defaults to `4`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// width: 10 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final double width; + + /// Color of the marker shape. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// color: Colors.red + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? color; + + /// Shape of the marker. + /// + /// Defaults to `DataMarkerType.circle`. + /// + /// Also refer [DataMarkerType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// shape: DataMarkerType.rectangle + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final DataMarkerType shape; + + /// Border color of the marker. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? borderColor; + + /// Border width of the marker. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final double borderWidth; + + /// Image to be used as marker. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings( + /// isVisible: true, + /// shape: DataMarkerType.rectangle, + /// image: const AssetImage('images/bike.png') + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final ImageProvider? image; + + MarkerSettings copyWith({ + bool? isVisible, + Color? color, + DataMarkerType? shape, + double? height, + double? width, + Color? borderColor, + double? borderWidth, + ImageProvider? image, + }) { + return MarkerSettings( + isVisible: isVisible ?? this.isVisible, + color: color ?? this.color, + shape: shape ?? this.shape, + height: height ?? this.height, + width: width ?? this.width, + borderColor: borderColor ?? this.borderColor, + borderWidth: borderWidth ?? this.borderWidth, + image: image ?? this.image, + ); + } + + ChartMarker _create() { + return ChartMarker() + ..type = shape + ..height = height + ..width = width + ..color = color + ..borderColor = borderColor + ..borderWidth = borderWidth + ..image = image; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MarkerSettings && + other.isVisible == isVisible && + other.height == height && + other.width == width && + other.color == color && + other.shape == shape && + other.borderWidth == borderWidth && + other.borderColor == borderColor && + other.image == image; + } + + @override + int get hashCode { + final List values = [ + isVisible, + height, + width, + color, + shape, + borderWidth, + borderColor, + image, + ]; + return Object.hashAll(values); + } +} + +class MarkerContainer extends StatefulWidget { + const MarkerContainer({ + super.key, + required this.series, + required this.dataSource, + required this.settings, + }); + + final List dataSource; + final ChartSeries series; + final MarkerSettings settings; + + @override + State> createState() => _MarkerContainerState(); +} + +class ChartMarker { + late num x; + late num y; + + DataMarkerType type = DataMarkerType.circle; + double height = 8.0; + double width = 8.0; + Color? color; + Color? borderColor; + double borderWidth = 2.0; + ImageProvider? image; + int index = -1; + Shader? shader; + + Offset position = const Offset(-1, -1); + + ChartMarker copyWith({ + DataMarkerType? type, + double? height, + double? width, + Color? color, + Color? borderColor, + double? borderWidth, + ImageProvider? image, + Shader? shader, + }) { + return ChartMarker() + ..type = type ?? this.type + ..height = height ?? this.height + ..width = width ?? this.width + ..color = color ?? this.color + ..borderColor = borderColor ?? this.borderColor + ..borderWidth = borderWidth ?? this.borderWidth + ..image = image ?? this.image + ..shader = shader ?? this.shader; + } + + void merge({ + DataMarkerType? type, + double? height, + double? width, + Color? color, + Color? borderColor, + double? borderWidth, + ImageProvider? image, + Shader? shader, + }) { + this.type = type ?? this.type; + this.height = height ?? this.height; + this.width = width ?? this.width; + this.color = color ?? this.color; + this.borderColor = borderColor ?? this.borderColor; + this.borderWidth = borderWidth ?? this.borderWidth; + this.image = image ?? this.image; + this.shader = shader ?? this.shader; + } +} + +class _MarkerContainerState extends State> + with ChartElementParentDataMixin { + List markers = []; + + @override + CartesianSeriesRenderer? get renderer => + super.renderer as CartesianSeriesRenderer?; + + void _buildLinearMarkers(BuildContext context) { + if (renderer!.visibleIndexes.isEmpty) { + return; + } + + final Color themeFillColor = Theme.of(context).colorScheme.surface; + final ChartMarker base = widget.settings._create(); + final double sbsValue = + sbsInfo != null ? (sbsInfo!.maximum + sbsInfo!.minimum) / 2 : 0.0; + final int start = renderer!.visibleIndexes[0]; + final int end = renderer!.visibleIndexes[1]; + final int yLength = yLists?.length ?? 0; + for (int i = start; i <= end; i++) { + _updateDetails(i, base, sbsValue, yLength, themeFillColor); + } + } + + void _buildNonLinearMarkers(BuildContext context) { + if (renderer!.visibleIndexes.isEmpty) { + return; + } + + final Color themeFillColor = Theme.of(context).colorScheme.surface; + final ChartMarker base = widget.settings._create(); + final double sbsValue = + sbsInfo != null ? (sbsInfo!.maximum + sbsInfo!.minimum) / 2 : 0.0; + final int yLength = yLists?.length ?? 0; + for (final int index in renderer!.visibleIndexes) { + _updateDetails(index, base, sbsValue, yLength, themeFillColor); + } + } + + void _updateDetails( + int index, + ChartMarker base, + double sbsValue, + int yLength, + Color themeFillColor, + ) { + final num x = xValues![index] + sbsValue; + for (int j = 0; j < yLength; j++) { + final List yValues = yLists![j]; + final num y = yValues[index]; + + final ChartMarker marker = base.copyWith( + color: renderer!.markerSettings.color ?? themeFillColor, + borderColor: _dataPointBorderColor(index), + ); + marker + ..x = x + ..y = y + ..index = index; + if (renderer!.parent != null && + renderer!.parent!.onMarkerRender != null) { + final MarkerRenderArgs args = + MarkerRenderArgs( + renderer!.viewportIndex(index), + renderer!.index, + index, + ) + ..shape = marker.type + ..color = marker.color + ..borderColor = marker.borderColor + ..borderWidth = marker.borderWidth + ..markerHeight = marker.height + ..markerWidth = marker.width; + renderer!.parent!.onMarkerRender!(args); + marker.merge( + type: args.shape, + color: args.color, + borderColor: args.borderColor, + borderWidth: args.borderWidth, + height: args.markerHeight, + width: args.markerWidth, + ); + } + + markers.add(marker); + } + } + + Color? _dataPointBorderColor(int dataPointIndex) { + if (renderer!.isEmpty(dataPointIndex)) { + return renderer!.emptyPointSettings.color; + } else if (renderer!.markerSettings.borderColor != null) { + return renderer!.markerSettings.borderColor!; + } else if (renderer!.pointColors.isNotEmpty && + renderer!.pointColors[dataPointIndex] != null) { + return renderer!.pointColors[dataPointIndex]; + } else { + return renderer!.color ?? renderer!.paletteColor; + } + } + + @override + void dispose() { + markers.clear(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ChartElementLayoutBuilder( + state: this, + builder: (BuildContext context, BoxConstraints constraints) { + markers.clear(); + if (renderer != null && + xValues != null && + xValues!.isNotEmpty && + renderer!.controller.isVisible) { + if (renderer!.canFindLinearVisibleIndexes) { + _buildLinearMarkers(context); + } else { + _buildNonLinearMarkers(context); + } + } + + return ChartFadeTransition( + opacity: animation!, + child: _MarkerStack( + series: renderer, + markers: markers, + settings: widget.settings, + ), + ); + }, + ); + } +} + +class _MarkerStack extends ChartElementStack { + const _MarkerStack({ + super.key, + required this.series, + required this.markers, + required this.settings, + }); + + final CartesianSeriesRenderer? series; + final List markers; + final MarkerSettings settings; + + @override + _RenderMarkerStack createRenderObject(BuildContext context) { + return _RenderMarkerStack() + ..series = series + ..markers = markers + ..settings = settings; + } + + @override + void updateRenderObject( + BuildContext context, + _RenderMarkerStack renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..series = series + ..markers = markers + ..settings = settings; + } +} + +class _RenderMarkerStack extends RenderChartElementStack { + late CartesianSeriesRenderer? series; + late List markers; + late MarkerSettings settings; + + @override + bool get sizedByParent => true; + + @override + ChartMarker markerAt(int pointIndex) { + for (final ChartMarker marker in markers) { + if (marker.index == pointIndex) { + return marker; + } + } + return ChartMarker(); + } + + @override + void performResize() { + size = constraints.biggest; + } + + @override + void performLayout() { + if (series == null || series!.xAxis == null || series!.yAxis == null) { + return; + } + + final bool hasShader = + series!.onCreateShader != null || series!.gradient != null; + + final DoubleRange xRange = series!.xAxis!.effectiveVisibleRange!; + final DoubleRange yRange = series!.yAxis!.effectiveVisibleRange!; + for (final ChartMarker marker in markers) { + num xValue = marker.x; + if (series!.xAxis! is RenderLogarithmicAxis) { + xValue = (series!.xAxis! as RenderLogarithmicAxis).toLog(xValue); + } + + num yValue = marker.y; + if (series!.yAxis! is RenderLogarithmicAxis) { + yValue = (series!.yAxis! as RenderLogarithmicAxis).toLog(yValue); + } + + if (!xRange.contains(xValue) || !yRange.contains(yValue)) { + marker.position = const Offset(double.nan, double.nan); + continue; + } + + final Size markerSize = Size(marker.width, marker.height); + final double positionX = + series!.pointToPixelX(marker.x, marker.y) - markerSize.width / 2; + final double positionY = + series!.pointToPixelY(marker.x, marker.y) - markerSize.height / 2; + marker.position = Offset(positionX, positionY); + + if (hasShader) { + marker.shader = series!.markerShader(marker.position & markerSize); + } + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (series == null || series!.xAxis == null || series!.yAxis == null) { + return; + } + + final Paint fillPaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + for (final ChartMarker marker in markers) { + if (marker.position.isNaN) { + continue; + } + + fillPaint.color = marker.color ?? Colors.transparent; + strokePaint.color = marker.borderColor ?? Colors.transparent; + strokePaint.strokeWidth = marker.borderWidth; + if (settings.borderColor != null) { + strokePaint.shader = null; + } else { + strokePaint.shader = marker.shader; + } + + series!.drawDataMarker( + marker.index, + context.canvas, + fillPaint, + strokePaint, + marker.position, + Size(marker.width, marker.height), + marker.type, + series, + ); + } + } + + @override + void dispose() { + for (final ChartMarker marker in markers) { + marker.shader?.dispose(); + } + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/pyramid_data_label.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/pyramid_data_label.dart new file mode 100644 index 000000000..b7ed7ee6a --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/pyramid_data_label.dart @@ -0,0 +1,548 @@ +import 'dart:collection'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../series/pyramid_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'callbacks.dart'; +import 'chart_point.dart'; +import 'data_label.dart'; +import 'element_widget.dart'; + +// ignore: must_be_immutable +base class PyramidChartDataLabelPositioned + extends ParentDataWidget + with LinkedListEntry { + PyramidChartDataLabelPositioned({ + super.key, + required this.x, + required this.y, + required this.dataPointIndex, + required this.position, + required super.child, + }); + + final num x; + final num y; + final int dataPointIndex; + final ChartDataPointType position; + + Path connectorPath = Path(); + Offset offset = Offset.zero; + Size size = Size.zero; + + @override + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is ChartElementParentData); + final ChartElementParentData parentData = + renderObject.parentData! as ChartElementParentData; + bool needsLayout = false; + + if (parentData.x != x) { + parentData.x = x; + needsLayout = true; + } + + if (parentData.y != y) { + parentData.y = y; + needsLayout = true; + } + + if (parentData.dataPointIndex != dataPointIndex) { + parentData.dataPointIndex = dataPointIndex; + needsLayout = true; + } + + if (parentData.position != position) { + parentData.position = position; + needsLayout = true; + } + + if (needsLayout) { + final RenderObject? targetParent = renderObject.parent; + if (targetParent is RenderObject) { + targetParent.markNeedsLayout(); + } + } + } + + @override + Type get debugTypicalAncestorWidgetClass => PyramidDataLabelStack; +} + +class PyramidDataLabelContainer extends StatefulWidget { + const PyramidDataLabelContainer({ + super.key, + required this.series, + required this.dataSource, + this.mapper, + this.builder, + required this.settings, + }); + + final ChartWidgetBuilder? builder; + final List dataSource; + final ChartValueMapper? mapper; + final PyramidSeries series; + final DataLabelSettings settings; + + @override + State> createState() => + _PyramidDataLabelContainerState(); +} + +typedef _ChartDataLabelWidgetBuilder = + Widget Function( + T data, + ChartPoint point, + PyramidSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ); + +class _PyramidDataLabelContainerState + extends State> + with ChartElementParentDataMixin { + List? _builderChildren; + LinkedList? _textChildren; + + Widget _dataLabelFromBuilder( + T data, + ChartPoint point, + PyramidSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + return widget.builder!(data, point, series, pointIndex, seriesIndex); + } + + Widget _dataLabelFromMapper( + T data, + ChartPoint point, + PyramidSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final String text = widget.mapper!(data, pointIndex) ?? ''; + return _buildDataLabelText(text, pointIndex); + } + + Widget _defaultDataLabel( + T data, + ChartPoint point, + PyramidSeries series, + int pointIndex, + int seriesIndex, + ChartDataPointType position, + ) { + final num value = point[position]; + final String formattedText = decimalLabelValue(value); + return _buildDataLabelText(formattedText, pointIndex); + } + + Color _dataPointColor(int dataPointIndex) { + final DataLabelSettings settings = widget.settings; + if (settings.color != null) { + return settings.color!.withValues(alpha: settings.opacity); + } else if (settings.useSeriesColor) { + return renderer!.segments[dataPointIndex].fillPaint.color.withValues( + alpha: settings.opacity, + ); + } + return Colors.transparent; + } + + DataLabelText _buildDataLabelText(String text, int pointIndex) { + final RenderChartPlotArea parent = renderer!.parent!; + final TextStyle dataLabelTextStyle = parent.themeData!.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(parent.chartThemeData!.dataLabelTextStyle) + .merge(widget.settings.textStyle); + return DataLabelText( + text: text, + textStyle: dataLabelTextStyle, + color: _dataPointColor(pointIndex), + ); + } + + void _addToList(PyramidChartDataLabelPositioned child) { + _builderChildren!.add(child); + } + + void _addToLinkedList(PyramidChartDataLabelPositioned child) { + _textChildren!.add(child); + } + + void _buildDataLabels( + _ChartDataLabelWidgetBuilder callback, + Function(PyramidChartDataLabelPositioned) add, + ) { + const List positions = ChartDataPointType.values; + final int yLength = yLists?.length ?? 0; + final int posAdj = _positionIndex(yLength); + List? actualXValues; + if (xRawValues != null && xRawValues!.isNotEmpty) { + actualXValues = xRawValues; + } else { + actualXValues = xValues; + } + + if (actualXValues == null || renderer!.segments.isEmpty) { + return; + } + + for (int i = 0; i < renderer!.dataCount; i++) { + _obtainLabel(i, actualXValues, yLength, positions, posAdj, callback, add); + } + } + + int _positionIndex(int yListsLength) { + return yListsLength == 1 ? 0 : 1; + } + + void _obtainLabel( + int index, + List rawXValues, + int yLength, + List positions, + int posAdj, + _ChartDataLabelWidgetBuilder callback, + Function(PyramidChartDataLabelPositioned) add, + ) { + final num x = xValues![index]; + final ChartPoint point = ChartPoint(x: rawXValues[index] as D?); + + for (int k = 0; k < yLength; k++) { + final List yValues = yLists![k]; + point.y = yValues[index]; + final ChartDataPointType position = positions[k + posAdj]; + final PyramidChartDataLabelPositioned child = + PyramidChartDataLabelPositioned( + x: x, + y: yValues[index], + dataPointIndex: index, + position: position, + child: callback( + widget.dataSource[index], + point, + widget.series, + index, + renderer!.index, + position, + ), + ); + add(child); + } + } + + @override + Widget build(BuildContext context) { + return ChartElementLayoutBuilder( + state: this, + builder: (BuildContext context, BoxConstraints constraints) { + _ChartDataLabelWidgetBuilder callback; + _builderChildren?.clear(); + _textChildren?.clear(); + if (renderer != null && + renderer!.initialIsVisible && + yLists != null && + yLists!.isNotEmpty) { + if (widget.builder != null) { + callback = _dataLabelFromBuilder; + } else { + callback = + widget.mapper != null + ? _dataLabelFromMapper + : _defaultDataLabel; + } + void Function(PyramidChartDataLabelPositioned child) add; + if (widget.builder != null) { + _builderChildren = []; + add = _addToList; + } else { + _textChildren = LinkedList(); + add = _addToLinkedList; + } + + if (xValues != null && xValues!.isNotEmpty) { + _buildDataLabels(callback, add); + } + } + + return ChartFadeTransition( + opacity: animation!, + child: PyramidDataLabelStack( + series: renderer as PyramidSeriesRenderer?, + settings: widget.settings, + labels: _textChildren, + children: _builderChildren ?? [], + ), + ); + }, + ); + } +} + +class PyramidDataLabelStack extends ChartElementStack { + const PyramidDataLabelStack({ + super.key, + required this.series, + required this.labels, + required this.settings, + super.children, + }); + + final PyramidSeriesRenderer? series; + final LinkedList? labels; + final DataLabelSettings settings; + + @override + RenderPyramidDataLabelStack createRenderObject(BuildContext context) { + return RenderPyramidDataLabelStack() + ..series = series + ..labels = labels + ..settings = settings; + } + + @override + void updateRenderObject( + BuildContext context, + RenderPyramidDataLabelStack renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..series = series + ..labels = labels + ..settings = settings; + } +} + +class RenderPyramidDataLabelStack extends RenderChartElementStack { + late PyramidSeriesRenderer? series; + late LinkedList? labels; + late DataLabelSettings settings; + + @override + bool get sizedByParent => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return false; + } + + @override + bool hitTestSelf(Offset position) { + return series?.parent?.onDataLabelTapped != null && + _findSelectedDataLabelIndex(position) != -1; + } + + int _findSelectedDataLabelIndex(Offset localPosition) { + if (series?.parent?.onDataLabelTapped == null) { + return -1; + } + + if (childCount > 0) { + RenderBox? child = lastChild; + while (child != null) { + final ChartElementParentData childParentData = + child.parentData! as ChartElementParentData; + if ((childParentData.offset & child.size).contains(localPosition)) { + return childParentData.dataPointIndex; + } + child = childParentData.previousSibling; + } + } else if (labels != null) { + for (int i = labels!.length - 1; i > -1; i--) { + final PyramidChartDataLabelPositioned label = labels!.elementAt(i); + final Rect rect = Rect.fromLTWH( + label.offset.dx, + label.offset.dy, + label.size.width + settings.margin.horizontal, + label.size.height + settings.margin.vertical, + ); + if (rect.contains(localPosition)) { + return label.dataPointIndex; + } + } + } + return -1; + } + + @override + void handleTapUp(Offset localPosition) { + if (series?.parent?.onDataLabelTapped != null) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + + final String text = + childCount > 0 + ? '' + : (labels!.elementAt(selectedIndex).child as DataLabelText).text; + series!.parent!.onDataLabelTapped!( + DataLabelTapDetails( + series!.index, + series!.viewportIndex(selectedIndex), + text, + settings, + selectedIndex, + ), + ); + } + } + + @override + void setupParentData(RenderObject child) { + if (child is! ChartElementParentData) { + child.parentData = ChartElementParentData(); + } + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + @override + void performLayout() { + if (series == null) { + return; + } + + if (childCount > 0) { + RenderBox? child = firstChild; + while (child != null) { + final ChartElementParentData currentChildData = + child.parentData! as ChartElementParentData; + final RenderBox? nextSibling = currentChildData.nextSibling; + child.layout(constraints, parentUsesSize: true); + currentChildData.offset = series!.dataLabelPosition( + currentChildData, + child.size, + ); + final Offset offset = _invokeDataLabelRender( + currentChildData.dataPointIndex, + ); + currentChildData.offset = Offset( + currentChildData.offset.dx + offset.dx, + currentChildData.offset.dy - offset.dy, + ); + // TODO(Praveen): Builder works only for inner and outer position, + // Need to handle for intersection. + child = nextSibling; + } + } else if (labels != null) { + for (final PyramidChartDataLabelPositioned currentLabel in labels!) { + final ChartElementParentData currentLabelData = + ChartElementParentData() + ..x = currentLabel.x + ..y = currentLabel.y + ..dataPointIndex = currentLabel.dataPointIndex + ..position = currentLabel.position; + final DataLabelText details = currentLabel.child as DataLabelText; + final Offset offset = _invokeDataLabelRender( + currentLabel.dataPointIndex, + details, + ); + currentLabel.offset = Offset( + currentLabel.offset.dx + offset.dx, + currentLabel.offset.dy - offset.dy, + ); + currentLabel.size = measureText(details.text, details.textStyle); + currentLabel.offset += series!.dataLabelPosition( + currentLabelData, + currentLabel.size, + ); + currentLabel.connectorPath = _drawConnectorPath( + currentLabel.dataPointIndex, + currentLabel.offset, + currentLabel.size, + ); + } + } + } + + Path _drawConnectorPath(int index, Offset offset, Size size) { + final List points = series!.segments[index].points; + final double startPoint = (points[1].dx + points[2].dx) / 2; + final double endPoint = offset.dx; + final double y = offset.dy + size.height / 2; + return Path() + ..moveTo(startPoint, y) + ..lineTo(endPoint, y); + } + + Offset _invokeDataLabelRender(int pointIndex, [DataLabelText? details]) { + if (series!.parent?.onDataLabelRender != null) { + final DataLabelRenderArgs dataLabelArgs = DataLabelRenderArgs( + seriesRenderer: series, + dataPoints: series!.chartPoints, + viewportPointIndex: pointIndex, + pointIndex: pointIndex, + )..offset = settings.offset; + if (details != null) { + dataLabelArgs + ..text = details.text + ..textStyle = details.textStyle + ..color = details.color; + } + + series!.parent!.onDataLabelRender!(dataLabelArgs); + if (details != null) { + details + ..text = dataLabelArgs.text ?? '' + ..textStyle = details.textStyle.merge(dataLabelArgs.textStyle) + ..color = dataLabelArgs.color; + } + + return dataLabelArgs.offset; + } + + return settings.offset; + } + + @override + void paint(PaintingContext context, Offset offset) { + context.canvas + ..save() + ..clipRect(paintBounds); + if (childCount > 0) { + defaultPaint(context, offset); + } else if (labels != null) { + final List previousRect = []; + final Paint fillPaint = Paint(); + final Paint strokePaint = + Paint() + ..color = settings.borderColor + ..strokeWidth = settings.borderWidth + ..style = PaintingStyle.stroke; + for (final PyramidChartDataLabelPositioned label in labels!) { + final DataLabelText details = label.child as DataLabelText; + fillPaint.color = details.color; + series!.drawDataLabelWithBackground( + label.dataPointIndex, + context.canvas, + details.text, + label.size, + label.offset, + settings.angle, + details.textStyle, + fillPaint, + strokePaint, + label.connectorPath, + previousRect, + ); + } + } + context.canvas.restore(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/title.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/title.dart new file mode 100644 index 000000000..71a25335d --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/title.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; + +import '../utils/enum.dart'; + +/// It has the properties of the chart title. +/// +/// ChartTitle can define and customize the Chart title using title property +/// of SfCartesianChart. The text property of ChartTitle is used to set the +/// text for the title. +/// +/// It provides an option of text, text style, alignment border color and width +/// to customize the appearance. +@immutable +class ChartTitle { + /// Creating an argument constructor of ChartTitle class. + const ChartTitle({ + this.text = '', + this.textStyle, + this.alignment = ChartAlignment.center, + this.borderColor = Colors.transparent, + this.borderWidth = 0, + this.backgroundColor, + }); + + /// Text to be displayed as chart title. Any desired text can be set as chart + /// title. If the width of the chart title exceeds the width of the chart, + /// then the title will be wrapped to multiple rows. + /// + /// Defaults to `''`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// title: ChartTitle( + /// text: 'Chart Title' + /// ) + /// ); + /// } + /// ``` + final String text; + + /// Customizes the appearance of the chart title text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// title: ChartTitle( + /// text: 'Chart Title', + /// textStyle: TextStyle( + /// color: Colors.red, + /// fontSize: 12, + /// fontStyle: FontStyle.normal, + /// fontWeight: FontWeight.w400, + /// fontFamily: 'Roboto' + /// ) + /// ) + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Aligns the chart title. + /// + /// The alignment change is applicable only when the width of the + /// chart title is less than the width of the chart. + /// + /// * `ChartAlignment.near` places the chart title at the beginning + /// of the chart + /// + /// * `ChartAlignment.far` moves the chart title to end of the chart + /// + /// * `ChartAlignment.center` places the title at the center position + /// of the chart’s width. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// Also refer [ChartAlignment]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// title: ChartTitle( + /// text: 'Chart Title', + /// alignment: ChartAlignment.near + /// ) + /// ); + /// } + /// ``` + final ChartAlignment alignment; + + /// Background color of the chart title. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// title: ChartTitle( + /// text: 'Chart Title', + /// backgroundColor: Colors.white + /// ) + /// ); + /// } + /// ``` + final Color? backgroundColor; + + /// Border color of the chart title. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// title: ChartTitle( + /// text: 'Chart Title', + /// borderColor: Colors.red, + /// borderWidth: 4 + /// ) + /// ); + /// } + /// ``` + final Color borderColor; + + /// Border width of the chart title. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// title: ChartTitle( + /// text: 'Chart Title', + /// borderColor: Colors.red, + /// borderWidth: 4 + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is ChartTitle && + other.text == text && + other.textStyle == textStyle && + other.alignment == alignment && + other.backgroundColor == backgroundColor && + other.borderColor == borderColor && + other.borderWidth == borderWidth; + } + + @override + int get hashCode { + final List values = [ + text, + textStyle, + alignment, + backgroundColor, + borderColor, + borderWidth, + ]; + return Object.hashAll(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart new file mode 100644 index 000000000..ca47ace23 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart @@ -0,0 +1,745 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'base.dart'; +import 'common/core_legend.dart' as core; +import 'common/core_tooltip.dart'; +import 'common/legend.dart'; +import 'common/title.dart'; +import 'interactions/behavior.dart'; +import 'interactions/tooltip.dart'; +import 'series/funnel_series.dart'; +import 'theme.dart'; +import 'utils/enum.dart'; +import 'utils/helper.dart'; +import 'utils/typedef.dart'; + +/// Renders the funnel chart. +/// +/// A funnel chart is a specialized chart type that demonstrates +/// the flow of users through a business or sales process. +/// The chart begins with a broad head and ends in a narrow neck. +/// +/// The number of users at each stage of the process are indicated +/// from the funnel's width as it narrows. +/// +/// To render a funnel chart, create an instance of FunnelSeries, +/// and add it to the series property of [SfFunnelChart]. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} +//ignore:must_be_immutable +class SfFunnelChart extends StatefulWidget { + /// Creating an argument constructor of SfFunnelChart class. + const SfFunnelChart({ + Key? key, + this.backgroundColor, + this.backgroundImage, + this.borderColor = Colors.transparent, + this.borderWidth = 0.0, + this.onLegendItemRender, + this.onTooltipRender, + this.onDataLabelRender, + this.onLegendTapped, + this.onDataLabelTapped, + this.onSelectionChanged, + this.onChartTouchInteractionUp, + this.onChartTouchInteractionDown, + this.onChartTouchInteractionMove, + this.palette, + this.margin = const EdgeInsets.fromLTRB(10, 10, 10, 10), + this.series = const FunnelSeries(), + this.title = const ChartTitle(), + this.legend = const Legend(), + this.tooltipBehavior, + this.selectionGesture = ActivationMode.singleTap, + this.enableMultiSelection = false, + }) : super(key: key); + + /// Customizes the chart title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// title: ChartTitle(text: 'Funnel Chart') + /// ) + /// ); + /// } + /// ``` + final ChartTitle title; + + /// Background color of the chart. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// backgroundColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + final Color? backgroundColor; + + /// Border color of the chart. + /// + /// Defaults to `Colors.transparent`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// borderColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + final Color borderColor; + + /// Border width of the chart. + /// + /// Defaults to `0.0`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// borderWidth: 2 + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Customizes the chart series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries<_FunnelData, String>( + /// dataSource: data, + /// xValueMapper: (_FunnelData data, _) => data.xData, + /// yValueMapper: (_FunnelData data, _) => data.yData, + /// ), + /// ), + /// ); + /// } + /// ``` + final FunnelSeries series; + + /// Margin for chart. + /// + /// Defaults to `const EdgeInsets.all(10)`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// margin: const EdgeInsets.all(2), + /// ) + /// ); + /// } + /// ``` + final EdgeInsets margin; + + /// Customizes the legend in the chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// legend: Legend(isVisible: true) + /// ) + /// ); + /// } + /// ``` + final Legend legend; + + /// 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. + /// + /// Ten colors are available by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// palette: [Colors.red, Colors.green] + /// ) + /// ); + /// } + /// ``` + final List? palette; + + /// Customizes the tooltip in chart. + /// + /// ```dart + /// late TooltipBehavior _tooltipBehavior; + /// + /// @override + /// void initState() { + /// _tooltipBehavior = TooltipBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// tooltipBehavior: _tooltipBehavior + /// ) + /// ); + /// } + /// ``` + final TooltipBehavior? tooltipBehavior; + + /// Occurs while legend is rendered. + /// + /// Here, you can get the legend's text, shape, series index, and + /// the point index case of funnel series. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// legend: Legend(isVisible: true), + /// onLegendItemRender: (LegendRendererArgs args) => legend(args), + /// ) + /// ); + /// } + /// void legend(LegendRendererArgs args) { + /// args.legendIconType = LegendIconType.diamond; + /// } + /// ``` + final FunnelLegendRenderCallback? onLegendItemRender; + + /// Occurs while tooltip is rendered. + /// + /// Here, you can get the Tooltip render arguments and customize them. + final FunnelTooltipCallback? onTooltipRender; + + /// Occurs when the data label is rendered. + /// + /// Here we can get get the data label render arguments and + /// customize the data label parameters. + final FunnelDataLabelRenderCallback? onDataLabelRender; + + /// Occurs when the legend is tapped, + /// using this event the legend tap arguments can be customized. + final ChartLegendTapCallback? onLegendTapped; + + /// Gesture for activating the selection. + /// + /// Selection can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, + /// `ActivationMode.doubleTap`, and + /// `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// selectionGesture: ActivationMode.singleTap, + /// series: FunnelSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ) + /// ); + /// } + /// ``` + final ActivationMode selectionGesture; + + /// Enables or disables the multiple data points selection. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// enableMultiSelection: true, + /// series: FunnelSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ) + /// ); + /// } + /// ``` + final bool enableMultiSelection; + + /// Background image for chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// backgroundImage: const AssetImage('image.png'), + /// ) + /// ); + /// } + /// ``` + final ImageProvider? backgroundImage; + + /// Occurs while selection changes. Here, you can get the series, + /// selected color, unselected color, selected border color, + /// unselected border color, selected border width, unselected border width, + /// series index, and point index. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// onSelectionChanged: (SelectionArgs args) => select(args), + /// ) + /// ); + /// } + /// void select(SelectionArgs args) { + /// print(args.selectedBorderColor); + /// } + /// ``` + final FunnelSelectionCallback? onSelectionChanged; + + /// Occurs when tapped on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final FunnelTouchInteractionCallback? onChartTouchInteractionUp; + + /// Occurs when touched on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final FunnelTouchInteractionCallback? onChartTouchInteractionDown; + + /// Occurs when touched and moved on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final FunnelTouchInteractionCallback? onChartTouchInteractionMove; + + /// Called when the data label is tapped. + /// + /// 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. + /// + /// _Note:_ This callback will not be called, when the builder is specified + /// for data label (data label template). For this case, custom widget + /// specified in the `DataLabelSettings.builder` property can be wrapped + /// using the `GestureDetector` and this functionality can be achieved in the + /// application level. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// onDataLabelTapped: (DataLabelTapDetails args) { + /// print(args.seriesIndex); + /// } + /// ) + /// ); + /// } + /// ``` + + final DataLabelTapCallback? onDataLabelTapped; + + @override + State createState() => SfFunnelChartState(); +} + +/// Represents the state class of [SfFunnelChart] widget. +class SfFunnelChartState extends State + with TickerProviderStateMixin { + final List _legendItems = []; + + late GlobalKey _legendKey; + late GlobalKey _tooltipKey; + late SfChartThemeData _chartThemeData; + late ThemeData _themeData; + SfLocalizations? _localizations; + + SfChartThemeData _updateThemeData( + BuildContext context, + ChartThemeData effectiveChartThemeData, + ) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + backgroundColor: + widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: + widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, + legendBackgroundColor: + widget.legend.backgroundColor ?? + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + tooltipColor: + widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + plotAreaBackgroundColor: + chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: + chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor, + ) + .merge(chartThemeData.titleTextStyle) + .merge(widget.title.textStyle), + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: + chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor, + ) + .merge(chartThemeData.legendTitleTextStyle) + .merge(widget.legend.title?.textStyle), + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: + chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor, + ) + .merge(chartThemeData.legendTextStyle) + .merge(widget.legend.textStyle), + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.tooltipTextStyle) + .merge(widget.tooltipBehavior?.textStyle), + ); + return chartThemeData; + } + + Widget _buildLegendItem(BuildContext context, int index) { + return buildLegendItem(context, _legendItems[index], widget.legend); + } + + Widget? _buildTooltipWidget( + BuildContext context, + TooltipInfo? info, + Size maxSize, + ) { + return buildTooltipWidget( + context, + info, + maxSize, + widget.tooltipBehavior, + _chartThemeData, + _themeData, + ); + } + + /// 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() { + _legendKey = GlobalKey(); + _tooltipKey = GlobalKey(); + 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() { + _localizations = SfLocalizations.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. + /// + /// 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) { + _themeData = Theme.of(context); + final ChartThemeData effectiveChartThemeData = ChartThemeData(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); + final core.LegendPosition legendPosition = effectiveLegendPosition( + widget.legend, + ); + final Axis orientation = effectiveLegendOrientation( + legendPosition, + widget.legend, + ); + Widget current = core.LegendLayout( + key: _legendKey, + padding: EdgeInsets.zero, + showLegend: widget.legend.isVisible, + legendPosition: legendPosition, + legendAlignment: effectiveLegendAlignment(widget.legend.alignment), + legendTitleAlignment: effectiveLegendAlignment( + widget.legend.title?.alignment, + ), + itemIconBorderColor: widget.legend.iconBorderColor, + itemIconBorderWidth: widget.legend.iconBorderWidth, + legendBorderColor: widget.legend.borderColor, + legendBackgroundColor: _chartThemeData.legendBackgroundColor, + legendBorderWidth: widget.legend.borderWidth, + itemOpacity: widget.legend.opacity, + legendWidthFactor: percentageToWidthFactor( + widget.legend.width, + legendPosition, + ), + legendHeightFactor: percentageToHeightFactor( + widget.legend.height, + legendPosition, + ), + itemInnerSpacing: widget.legend.padding, + itemSpacing: 0.0, + itemPadding: widget.legend.itemPadding, + itemRunSpacing: 0.0, + itemIconHeight: widget.legend.iconHeight, + itemIconWidth: widget.legend.iconWidth, + scrollbarVisibility: effectiveScrollbarVisibility(widget.legend), + enableToggling: widget.legend.toggleSeriesVisibility, + itemTextStyle: _chartThemeData.legendTextStyle!, + isResponsive: widget.legend.isResponsive, + legendWrapDirection: orientation, + legendTitle: buildLegendTitle(_chartThemeData, widget.legend), + legendOverflowMode: effectiveOverflowMode(widget.legend), + legendItemBuilder: + widget.legend.legendItemBuilder != null ? _buildLegendItem : null, + legendFloatingOffset: widget.legend.offset, + child: ChartArea( + legendKey: _legendKey, + legendItems: _legendItems, + onChartTouchInteractionDown: widget.onChartTouchInteractionDown, + onChartTouchInteractionMove: widget.onChartTouchInteractionMove, + onChartTouchInteractionUp: widget.onChartTouchInteractionUp, + children: [ + ChartPlotArea( + vsync: this, + localizations: _localizations, + legendKey: _legendKey, + backgroundColor: null, + borderWidth: widget.borderWidth, + legend: widget.legend, + onLegendItemRender: widget.onLegendItemRender, + onDataLabelRender: widget.onDataLabelRender, + onLegendTapped: widget.onLegendTapped, + onTooltipRender: widget.onTooltipRender, + onDataLabelTapped: widget.onDataLabelTapped, + palette: widget.palette ?? effectiveChartThemeData.palette, + selectionMode: SelectionType.point, + selectionGesture: widget.selectionGesture, + enableMultiSelection: widget.enableMultiSelection, + tooltipBehavior: widget.tooltipBehavior, + onSelectionChanged: widget.onSelectionChanged, + chartThemeData: _chartThemeData, + themeData: _themeData, + children: [widget.series], + ), + if (widget.tooltipBehavior != null) + BehaviorArea( + tooltipKey: _tooltipKey, + chartThemeData: _chartThemeData, + themeData: _themeData, + tooltipBehavior: widget.tooltipBehavior, + onTooltipRender: widget.onTooltipRender, + children: [ + if (widget.tooltipBehavior != null && + widget.tooltipBehavior!.enable) + CoreTooltip( + key: _tooltipKey, + builder: _buildTooltipWidget, + opacity: widget.tooltipBehavior!.opacity, + borderColor: widget.tooltipBehavior!.borderColor, + borderWidth: widget.tooltipBehavior!.borderWidth, + color: + (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, + showDuration: widget.tooltipBehavior!.duration.toInt(), + shadowColor: widget.tooltipBehavior!.shadowColor, + elevation: widget.tooltipBehavior!.elevation, + animationDuration: + widget.tooltipBehavior!.animationDuration, + shouldAlwaysShow: widget.tooltipBehavior!.shouldAlwaysShow, + ), + ], + ), + ], + ), + ); + + current = buildChartWithTitle(current, widget.title, _chartThemeData); + + return RepaintBoundary( + child: Container( + decoration: BoxDecoration( + image: + widget.backgroundImage != null + ? DecorationImage( + image: widget.backgroundImage!, + fit: BoxFit.fill, + ) + : null, + color: _chartThemeData.backgroundColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: Padding( + padding: widget.margin.resolve(Directionality.maybeOf(context)), + child: current, + ), + ), + ); + } + + /// Method to convert the [SfFunnelChart] as an 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. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfFunnelChart( + /// key: _key, + /// series: FunnelSeries<_FunnelData, String>( + /// dataSource: data, + /// xValueMapper: (_FunnelData data, _) => data.xData, + /// yValueMapper: (_FunnelData data, _) => data.yData, + /// ), + /// ), + /// RaisedButton( + /// child: Text( + /// 'To Image', + /// ), + /// onPressed: _renderImage, + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// Future _renderImage() async { + /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); + /// final bytes = await data.toByteData(format: dart_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.white, + /// child: Image.memory(bytes.buffer.asUint8List()), + /// ), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// } + /// ``` + Future toImage({double pixelRatio = 1.0}) async { + final RenderRepaintBoundary? boundary = + context.findRenderObject() as RenderRepaintBoundary?; + if (boundary != null) { + final Image image = await boundary.toImage(pixelRatio: pixelRatio); + return image; + } + + return null; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart new file mode 100644 index 000000000..6919cc45c --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart @@ -0,0 +1,451 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// This class holds the properties of the accumulation distribution indicator. +/// +/// Accumulation distribution indicator is a volume-based indicator designed to +/// measure the accumulative flow of money into and out of a security. +/// It requires [volumeValueMapper] property additionally with the data source +/// to calculate the signal line. +/// +/// It provides options for series visible, axis name, series name, animation +/// duration, legend visibility, signal line width, and color. +@immutable +class AccumulationDistributionIndicator extends TechnicalIndicator { + /// Creating an argument constructor of + /// [AccumulationDistributionIndicator] class. + AccumulationDistributionIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? closeValueMapper, + ChartValueMapper? volumeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + volumeValueMapper: + volumeValueMapper != null && dataSource != null + ? (int index) => volumeValueMapper(dataSource[index], index) + : null, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is AccumulationDistributionIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.closeValueMapper == closeValueMapper && + other.volumeValueMapper == volumeValueMapper && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + closeValueMapper, + volumeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'AD'; +} + +class ADIndicatorWidget extends IndicatorWidget { + const ADIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + /// Create the ADIndicatorRenderer renderer. + @override + ADIndicatorRenderer createRenderer() { + return ADIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final ADIndicatorRenderer renderer = + super.createRenderObject(context) as ADIndicatorRenderer; + final AccumulationDistributionIndicator adi = + indicator as AccumulationDistributionIndicator; + + renderer + ..highValueMapper = adi.highValueMapper + ..lowValueMapper = adi.lowValueMapper + ..closeValueMapper = adi.closeValueMapper + ..volumeValueMapper = adi.volumeValueMapper; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + ADIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final AccumulationDistributionIndicator adi = + indicator as AccumulationDistributionIndicator; + + renderObject + ..highValueMapper = adi.highValueMapper + ..lowValueMapper = adi.lowValueMapper + ..closeValueMapper = adi.closeValueMapper + ..volumeValueMapper = adi.volumeValueMapper; + } +} + +class ADIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + List _highValues = []; + List _lowValues = []; + List _closeValues = []; + List _volumeValues = []; + + ChartIndexedValueMapper? get volumeValueMapper => _volumeValueMapper; + ChartIndexedValueMapper? _volumeValueMapper; + set volumeValueMapper(ChartIndexedValueMapper? value) { + if (_volumeValueMapper != value) { + _volumeValueMapper = value; + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + closeValueMapper, + volumeValueMapper, + ], + >[_highValues, _lowValues, _closeValues, _volumeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _closeValues = series.closeValues; + _volumeValues = series.volumeValues; + dataCount = xValues.length; + } + } + } + + _signalLineActualValues.clear(); + if (signalLineWidth > 0 && + _highValues.isNotEmpty && + _lowValues.isNotEmpty && + _closeValues.isNotEmpty) { + _calculateSignalLineValues(); + } + + populateChartPoints(); + } + + void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + num sum = 0; + for (int i = 0; i < dataCount; i++) { + final num high = _highValues[i].isNaN ? 0 : _highValues[i]; + final num low = _lowValues[i].isNaN ? 0 : _lowValues[i]; + final num close = _closeValues[i].isNaN ? 0 : _closeValues[i]; + num value = ((close - low) - (high - close)) / (high - low); + if (value.isNaN) { + value = 0; + } + + if (_volumeValues.isNotEmpty) { + value *= _volumeValues[i]; + } + sum += value; + + final double x = xValues[i].toDouble(); + final double y = sum.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _closeValues, _volumeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.close, + ChartDataPointType.volume, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'AD'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); + if (nearestPointIndex != -1) { + final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); + return >[ + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: chartPoint, + series: this, + seriesIndex: index, + segmentIndex: nearestPointIndex, + pointIndex: nearestPointIndex, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), + color: signalLineColor, + ), + ]; + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: xRawValues[pointIndex], + xValue: xValues[pointIndex], + y: _signalLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + if (signalLinePoints.isNotEmpty) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + final int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + context.canvas.restore(); + } + } + + @override + void dispose() { + _highValues.clear(); + _lowValues.clear(); + _closeValues.clear(); + _volumeValues.clear(); + _signalLineActualValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart new file mode 100644 index 000000000..6e3597d97 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart @@ -0,0 +1,485 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// This class holds the properties of the average true range indicator. +/// +/// ATR indicator is a technical analysis volatility indicator. This indicator +/// does not indicate the price trend, simply the degree of price volatility. +/// The average true range is an N-day smoothed moving average (SMA) of the true +/// range values. +/// +/// Provides options for series visible, axis name, series name, animation +/// duration, legend visibility, signal line width, and color to customize +/// the appearance of indicator. +@immutable +class AtrIndicator extends TechnicalIndicator { + /// Creating an argument constructor of AtrIndicator class. + AtrIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// AtrIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is AtrIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'ATR'; +} + +class AtrIndicatorWidget extends IndicatorWidget { + const AtrIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + /// Create the ADIndicatorRenderer renderer. + @override + AtrIndicatorRenderer createRenderer() { + return AtrIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final AtrIndicatorRenderer renderer = + super.createRenderObject(context) as AtrIndicatorRenderer; + final AtrIndicator atr = indicator as AtrIndicator; + + renderer + ..highValueMapper = atr.highValueMapper + ..lowValueMapper = atr.lowValueMapper + ..closeValueMapper = atr.closeValueMapper + ..period = atr.period; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + AtrIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final AtrIndicator atr = indicator as AtrIndicator; + + renderObject + ..highValueMapper = atr.highValueMapper + ..lowValueMapper = atr.lowValueMapper + ..closeValueMapper = atr.closeValueMapper + ..period = atr.period; + } +} + +class AtrIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + List _highValues = []; + List _lowValues = []; + List _closeValues = []; + + final List _signalLineActualValues = []; + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + lowValueMapper, + highValueMapper, + closeValueMapper, + ], + >[_lowValues, _highValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _signalLineActualValues.clear(); + if (signalLineWidth > 0 && + _highValues.isNotEmpty && + _lowValues.isNotEmpty && + _closeValues.isNotEmpty) { + _calculateSignalLineValues(); + } + + populateChartPoints(); + } + + void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + final List averageOffsetValues = []; + num highClose = 0; + num lowClose = 0; + num average = 0; + num sum = 0; + for (int i = 0; i < dataCount; i++) { + final double x = xValues[i].toDouble(); + final num high = _highValues[i].isNaN ? 0 : _highValues[i]; + final num low = _lowValues[i].isNaN ? 0 : _lowValues[i]; + final num highLow = high - low; + + if (i > 0) { + final num high = _highValues[i].isNaN ? 0 : _highValues[i]; + final num low = _lowValues[i].isNaN ? 0 : _lowValues[i]; + final num close = _closeValues[i - 1].isNaN ? 0 : _closeValues[i - 1]; + highClose = (high - close).abs(); + lowClose = (low - close).abs(); + } + + final num range = max(highLow, highClose); + final num trueRange = max(range, lowClose); + sum += trueRange; + if (i >= period && period > 0) { + average = + (averageOffsetValues[averageOffsetValues.length - 1].dy * + (period - 1) + + trueRange) / + period; + + final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + } else { + average = sum / period; + if (i == period - 1) { + final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + } + } + averageOffsetValues.add(Offset(x, average.toDouble())); + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'ATR'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); + if (nearestPointIndex != -1) { + final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); + return >[ + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: chartPoint, + series: this, + pointIndex: nearestPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), + color: signalLineColor, + segmentIndex: nearestPointIndex, + ), + ]; + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: xRawValues[pointIndex + period - 1], + xValue: xValues[pointIndex + period - 1], + y: _signalLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + if (signalLinePoints.isNotEmpty) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + final int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + context.canvas.restore(); + } + } + + @override + void dispose() { + _highValues.clear(); + _lowValues.clear(); + _closeValues.clear(); + _signalLineActualValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart new file mode 100644 index 000000000..1396cd86c --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart @@ -0,0 +1,967 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// This class has the property of bollinger band indicator. +/// +/// This indicator also has [upperLineColor], [lowerLineColor] property for +/// defining the brushes for the indicator lines. Also, we can specify standard +/// deviation values for the [BollingerBandIndicator] using +/// [standardDeviation] property. +/// +/// Provides options for series visible, axis name, series name, animation +/// duration, legend visibility, band color to customize the appearance. +@immutable +class BollingerBandIndicator extends TechnicalIndicator { + /// Creating an argument constructor of BollingerBandIndicator class. + BollingerBandIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.standardDeviation = 2, + this.upperLineColor = Colors.red, + this.upperLineWidth = 2, + this.lowerLineColor = Colors.green, + this.lowerLineWidth = 2, + this.bandColor = const Color(0x409e9e9e), + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Standard deviation value of the bollinger band. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period: 2, + /// standardDeviation: 3 + /// ), + /// ], + /// ); + /// } + /// ``` + final int standardDeviation; + + /// Upper line color of the bollinger band. + /// + /// Defaults to `Colors.red`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period: 2, + /// standardDeviation: 3, + /// upperLineColor: Colors.yellow + /// ), + /// ], + /// ); + /// } + /// ``` + final Color upperLineColor; + + /// Upper line width value of the bollinger band. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period: 2, + /// standardDeviation: 3, + /// upperLineWidth: 3 + /// ), + /// ], + /// ); + /// } + /// ``` + final double upperLineWidth; + + /// Lower line color value of the bollinger band. + /// + /// Defaults to `Colors.green`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period: 2, + /// standardDeviation: 3, + /// lowerLineColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color lowerLineColor; + + /// Lower line width value of the bollinger band. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period: 2, + /// standardDeviation: 3, + /// lowerLineWidth: 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final double lowerLineWidth; + + /// Band color of the bollinger band. + /// + /// Default is `Color(0x409e9e9e)`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period: 2, + /// standardDeviation: 3, + /// bandColor: Colors.blue + /// ), + /// ], + /// ); + /// } + /// ``` + final Color bandColor; + + /// Period determines the start point for the rendering + /// of technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// BollingerBandIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is BollingerBandIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.standardDeviation == standardDeviation && + other.upperLineColor == upperLineColor && + other.upperLineWidth == upperLineWidth && + other.lowerLineColor == lowerLineColor && + other.lowerLineWidth == lowerLineWidth && + other.bandColor == bandColor; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + standardDeviation, + upperLineColor, + upperLineWidth, + lowerLineColor, + lowerLineWidth, + bandColor, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'Bollinger'; +} + +class BollingerIndicatorWidget extends IndicatorWidget { + const BollingerIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the BollingerIndicatorRenderer renderer. + @override + BollingerIndicatorRenderer createRenderer() { + return BollingerIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final BollingerIndicatorRenderer renderer = + super.createRenderObject(context) as BollingerIndicatorRenderer; + final BollingerBandIndicator bollinger = + indicator as BollingerBandIndicator; + + renderer + ..closeValueMapper = bollinger.closeValueMapper + ..standardDeviation = bollinger.standardDeviation + ..upperLineColor = bollinger.upperLineColor + ..upperLineWidth = bollinger.upperLineWidth + ..lowerLineColor = bollinger.lowerLineColor + ..lowerLineWidth = bollinger.lowerLineWidth + ..bandColor = bollinger.bandColor + ..period = bollinger.period; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + BollingerIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final BollingerBandIndicator bollinger = + indicator as BollingerBandIndicator; + + renderObject + ..closeValueMapper = bollinger.closeValueMapper + ..standardDeviation = bollinger.standardDeviation + ..upperLineColor = bollinger.upperLineColor + ..upperLineWidth = bollinger.upperLineWidth + ..lowerLineColor = bollinger.lowerLineColor + ..lowerLineWidth = bollinger.lowerLineWidth + ..bandColor = bollinger.bandColor + ..period = bollinger.period; + } +} + +class BollingerIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _lowerLineActualValues = []; + final List _upperLineActualValues = []; + final List _upperBandActualValues = []; + final List _lowerBandActualValues = []; + + final List _upperLinePoints = []; + final List _lowerLinePoints = []; + final Path _upperLinePath = Path(); + final Path _lowerLinePath = Path(); + final Path _bandPath = Path(); + List _closeValues = []; + + final List> _upperLineChartPoints = + >[]; + final List> _lowerLineChartPoints = + >[]; + + int get standardDeviation => _standardDeviation; + int _standardDeviation = 2; + set standardDeviation(int value) { + if (_standardDeviation != value) { + _standardDeviation = value; + markNeedsPopulateAndLayout(); + } + } + + Color get upperLineColor => _upperLineColor; + Color _upperLineColor = Colors.red; + set upperLineColor(Color value) { + if (_upperLineColor != value) { + _upperLineColor = value; + markNeedsPaint(); + } + } + + double get upperLineWidth => _upperLineWidth; + double _upperLineWidth = 2; + set upperLineWidth(double value) { + if (_upperLineWidth != value) { + _upperLineWidth = value; + markNeedsPaint(); + } + } + + Color get lowerLineColor => _lowerLineColor; + Color _lowerLineColor = Colors.green; + set lowerLineColor(Color value) { + if (_lowerLineColor != value) { + _lowerLineColor = value; + markNeedsPaint(); + } + } + + double get lowerLineWidth => _lowerLineWidth; + double _lowerLineWidth = 2; + set lowerLineWidth(double value) { + if (_lowerLineWidth != value) { + _lowerLineWidth = value; + markNeedsPaint(); + } + } + + Color get bandColor => _bandColor; + Color _bandColor = const Color(0x409e9e9e); + set bandColor(Color value) { + if (_bandColor != value) { + _bandColor = value; + markNeedsPaint(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[closeValueMapper], + >[_closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + if (period > 0) { + _signalLineActualValues.clear(); + _lowerLineActualValues.clear(); + _upperLineActualValues.clear(); + _upperBandActualValues.clear(); + _lowerBandActualValues.clear(); + _calculateBollingerBandsValues(); + } + + populateChartPoints(); + } + + void _calculateBollingerBandsValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + final bool enableBand = bandColor != Colors.transparent; + final List lowBands = List.filled(dataCount, -1); + final List midBands = List.filled(dataCount, -1); + final List upperBands = List.filled(dataCount, -1); + + if (dataCount >= period && period > 0) { + num sum = 0; + num deviationSum = 0; + final num multiplier = standardDeviation; + final int length = period; + final List smaPoints = List.filled(dataCount, -1); + final List deviations = List.filled(dataCount, -1); + + for (int i = 0; i < length; i++) { + sum += _closeValues[i]; + } + + num sma = sum / period; + for (int j = 0; j < dataCount; j++) { + final num y = _closeValues[j]; + if (j >= length - 1 && j < dataCount) { + if (j - period >= 0) { + final num diff = y - _closeValues[j - length]; + sum = sum + diff; + sma = sum / period; + smaPoints[j] = sma; + deviations[j] = pow(y - sma, 2); + deviationSum += deviations[j] - deviations[j - length]; + } else { + smaPoints[j] = sma; + deviations[j] = pow(y - sma, 2); + deviationSum += deviations[j]; + } + + final num range = sqrt(deviationSum / period); + final num lowerBand = smaPoints[j] - (multiplier * range); + final num upperBand = smaPoints[j] + (multiplier * range); + if (j + 1 == length) { + for (int k = 0; k < length - 1; k++) { + midBands[k] = smaPoints[j]; + lowBands[k] = + lowerBand.isNaN || lowerBand.isInfinite + ? smaPoints[j] + : lowerBand; + upperBands[k] = + upperBand.isNaN || upperBand.isInfinite + ? smaPoints[j] + : upperBand; + } + } + midBands[j] = smaPoints[j]; + lowBands[j] = + lowerBand.isNaN || lowerBand.isInfinite + ? smaPoints[j] + : lowerBand; + upperBands[j] = + upperBand.isNaN || upperBand.isInfinite + ? smaPoints[j] + : upperBand; + } else { + if (j < period - 1) { + smaPoints[j] = sma; + deviations[j] = pow(y - sma, 2); + deviationSum += deviations[j]; + } + } + } + + int l = -1; + int m = -1; + for (int n = 0; n < dataCount; n++) { + if (n >= (length - 1)) { + final double x = xValues[n].toDouble(); + final double upper = upperBands[n].toDouble(); + final double middle = midBands[n].toDouble(); + final double lower = lowBands[n].toDouble(); + + final double minY = min(middle, min(upper, lower)); + final double maxY = max(middle, max(upper, lower)); + + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, minY); + yMaximum = max(yMaximum, maxY); + + _upperLineActualValues.add(Offset(x, upper)); + _lowerLineActualValues.add(Offset(x, lower)); + _signalLineActualValues.add(Offset(x, middle)); + if (enableBand) { + _upperBandActualValues.add( + Offset(x, _upperLineActualValues[++l].dy), + ); + _lowerBandActualValues.add( + Offset(x, _lowerLineActualValues[++m].dy), + ); + } + } + } + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_closeValues]; + positions = [ChartDataPointType.close]; + + chartPoints.clear(); + _upperLineChartPoints.clear(); + _lowerLineChartPoints.clear(); + + if (parent == null || yLists.isEmpty) { + return; + } + + if (onRenderDetailsUpdate == null) { + return; + } + + final int yLength = yLists.length; + if (positions.length != yLength) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final num xValue = xValues[i]; + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: xValue, + ); + for (int j = 0; j < yLength; j++) { + point[positions[j]] = yLists[j][i]; + } + chartPoints.add(point); + } + + if (_upperLineActualValues.isNotEmpty) { + final int length = _upperLineActualValues.length; + for (int i = 0; i < length; i++) { + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: _upperLineActualValues[i].dx, + y: _upperLineActualValues[i].dy, + ); + _upperLineChartPoints.add(point); + } + } + + if (_lowerLineActualValues.isNotEmpty) { + final int length = _lowerLineActualValues.length; + for (int i = 0; i < length; i++) { + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: _lowerLineActualValues[i].dx, + y: _lowerLineActualValues[i].dy, + ); + _lowerLineChartPoints.add(point); + } + } + } + + @override + String defaultLegendItemText() => 'Bollinger'; + + @override + Color effectiveLegendIconColor() { + return signalLineColor; + } + + @override + void transformValues() { + signalLinePoints.clear(); + _upperLinePoints.clear(); + _lowerLinePoints.clear(); + _bandPath.reset(); + + if (_upperLineActualValues.isNotEmpty) { + final int length = _upperLineActualValues.length; + for (int i = 0; i < length; i++) { + final Offset upper = _upperLineActualValues[i]; + _upperLinePoints.add( + Offset( + pointToPixelX(upper.dx, upper.dy), + pointToPixelY(upper.dx, upper.dy), + ), + ); + } + } + + if (_lowerLineActualValues.isNotEmpty) { + final int length = _lowerLineActualValues.length; + for (int i = 0; i < length; i++) { + final Offset lower = _lowerLineActualValues[i]; + _lowerLinePoints.add( + Offset( + pointToPixelX(lower.dx, lower.dy), + pointToPixelY(lower.dx, lower.dy), + ), + ); + } + } + + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final Offset signal = _signalLineActualValues[i]; + signalLinePoints.add( + Offset( + pointToPixelX(signal.dx, signal.dy), + pointToPixelY(signal.dx, signal.dy), + ), + ); + } + } + + if (bandColor != Colors.transparent && _upperBandActualValues.isNotEmpty) { + final Offset upperStart = _upperBandActualValues[0]; + double rangeX = pointToPixelX(upperStart.dx, upperStart.dy); + double rangeY = pointToPixelY(upperStart.dx, upperStart.dy); + _bandPath.moveTo(rangeX, rangeY); + + final int upperLength = _upperBandActualValues.length; + for (int i = 0; i < upperLength; i++) { + final Offset upper = _upperBandActualValues[i]; + final num x = upper.dx; + final num high = upper.dy; + rangeX = pointToPixelX(x, high); + rangeY = pointToPixelY(x, high); + _bandPath.lineTo(rangeX, rangeY); + } + + final int lowerLastIndex = _lowerBandActualValues.length - 1; + for (int j = lowerLastIndex; j >= 0; j--) { + final Offset lower = _lowerBandActualValues[j]; + final num x = lower.dx; + final num low = lower.dy; + rangeX = pointToPixelX(x, low); + rangeY = pointToPixelY(x, low); + _bandPath.lineTo(rangeX, rangeY); + } + + _bandPath.close(); + } + } + + @override + List? trackballInfo(Offset position) { + final List> trackballInfo = + >[]; + if (signalLineWidth > 0) { + final int nearestPointIndex = _findNearestPoint( + signalLinePoints, + position, + ); + if (nearestPointIndex != -1) { + final CartesianChartPoint bollingerPoint = _chartPoint( + nearestPointIndex, + 'bollinger', + ); + final String bollingerText = defaultLegendItemText(); + trackballInfo.add( + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: bollingerPoint, + series: this, + pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, + seriesIndex: index, + name: bollingerText, + header: tooltipHeaderText(bollingerPoint), + text: trackballText(bollingerPoint, bollingerText), + color: signalLineColor, + ), + ); + } + } + if (upperLineWidth > 0) { + final int upperPointIndex = _findNearestPoint(_upperLinePoints, position); + if (upperPointIndex != -1) { + final CartesianChartPoint upperPoint = _chartPoint( + upperPointIndex, + 'upper', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _upperLinePoints[upperPointIndex], + point: upperPoint, + series: this, + pointIndex: upperPointIndex, + segmentIndex: upperPointIndex, + seriesIndex: index, + name: trackballUpperLineText, + header: tooltipHeaderText(upperPoint), + text: trackballText(upperPoint, trackballUpperLineText), + color: _upperLineColor, + ), + ); + } + } + if (lowerLineWidth > 0) { + final int lowerPointIndex = _findNearestPoint(_lowerLinePoints, position); + if (lowerPointIndex != -1) { + final CartesianChartPoint lowerPoint = _chartPoint( + lowerPointIndex, + 'lower', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _lowerLinePoints[lowerPointIndex], + point: lowerPoint, + series: this, + pointIndex: lowerPointIndex, + segmentIndex: lowerPointIndex, + seriesIndex: index, + name: trackballLowerLineText, + header: tooltipHeaderText(lowerPoint), + text: trackballText(lowerPoint, trackballLowerLineText), + color: _lowerLineColor, + ), + ); + } + } + return trackballInfo; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex, String type) { + return CartesianChartPoint( + x: xRawValues[pointIndex + period - 1], + xValue: xValues[pointIndex + period - 1], + y: + type == 'bollinger' + ? _signalLineActualValues[pointIndex].dy + : type == 'upper' + ? _upperLineActualValues[pointIndex].dy + : _lowerLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final BollingerBandIndicatorRenderParams params = + BollingerBandIndicatorRenderParams( + _upperLineChartPoints, + _lowerLineChartPoints, + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + if (bandColor != Colors.transparent) { + context.canvas.drawPath(_bandPath, Paint()..color = bandColor); + } + + int length = 0; + if (signalLinePoints.isNotEmpty) { + length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + } + + // Draw upper line. + if (_upperLinePoints.isNotEmpty && + upperLineWidth > 0 && + upperLineColor != Colors.transparent) { + _upperLinePath.reset(); + length = _upperLinePoints.length; + + _upperLinePath.moveTo( + _upperLinePoints.first.dx, + _upperLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + final Offset upper = _upperLinePoints[i]; + _upperLinePath.lineTo(upper.dx, upper.dy); + } + + drawDashes( + context.canvas, + _dashArray, + strokePaint + ..color = upperLineColor + ..strokeWidth = upperLineWidth, + path: _upperLinePath, + ); + } + + // Draw lower line. + if (_lowerLinePoints.isNotEmpty && + lowerLineWidth > 0 && + lowerLineColor != Colors.transparent) { + _lowerLinePath.reset(); + length = _lowerLinePoints.length; + + _lowerLinePath.moveTo( + _lowerLinePoints.first.dx, + _lowerLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + final Offset lower = _lowerLinePoints[i]; + _lowerLinePath.lineTo(lower.dx, lower.dy); + } + + drawDashes( + context.canvas, + _dashArray, + strokePaint + ..color = lowerLineColor + ..strokeWidth = lowerLineWidth, + path: _lowerLinePath, + ); + } + context.canvas.restore(); + } + + @override + void dispose() { + _signalLineActualValues.clear(); + _lowerLineActualValues.clear(); + _upperLineActualValues.clear(); + _upperBandActualValues.clear(); + _lowerBandActualValues.clear(); + _closeValues.clear(); + _upperLinePoints.clear(); + _lowerLinePoints.clear(); + _upperLinePath.reset(); + _lowerLinePath.reset(); + _bandPath.reset(); + _upperLineChartPoints.clear(); + _lowerLineChartPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart new file mode 100644 index 000000000..50a7588a5 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart @@ -0,0 +1,552 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../axis/axis.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders EMA indicator +/// +/// An EMA indicator is a simple, arithmetic moving average that is calculated +/// by adding the closing price for the number of time periods and dividing +/// the total value by the number of periods. +/// +/// It also has the [valueField] property. Based on this property indicator +/// will be rendered. +@immutable +class EmaIndicator extends TechnicalIndicator { + /// Creating an argument constructor of EmaIndicator class. + EmaIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.valueField = 'close', + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Value field property for EMA indicator. + /// + /// Based on the value field property, the moving average is calculated + /// and the indicator is rendered. + /// + /// Defaults to `close`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// EmaIndicator( + /// seriesName: 'Series1' + /// period: 4, + /// valueField: 'low' + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String valueField; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// EmaIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is EmaIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.valueField == valueField; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + valueField, + period, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'EMA'; +} + +class EmaIndicatorWidget extends IndicatorWidget { + const EmaIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the EmaIndicatorRenderer renderer. + @override + EmaIndicatorRenderer createRenderer() { + return EmaIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final EmaIndicatorRenderer renderer = + super.createRenderObject(context) as EmaIndicatorRenderer; + final EmaIndicator ema = indicator as EmaIndicator; + + renderer + ..highValueMapper = ema.highValueMapper + ..lowValueMapper = ema.lowValueMapper + ..openValueMapper = ema.openValueMapper + ..closeValueMapper = ema.closeValueMapper + ..valueField = ema.valueField + ..period = ema.period; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + EmaIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final EmaIndicator ema = indicator as EmaIndicator; + + renderObject + ..highValueMapper = ema.highValueMapper + ..lowValueMapper = ema.lowValueMapper + ..openValueMapper = ema.openValueMapper + ..closeValueMapper = ema.closeValueMapper + ..valueField = ema.valueField + ..period = ema.period; + } +} + +class EmaIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _yValues = []; + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + String get valueField => _valueField; + String _valueField = 'Close'; + set valueField(String value) { + if (_valueField != value) { + _valueField = value; + markNeedsPopulateAndLayout(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _signalLineActualValues.clear(); + _yValues.clear(); + if (dataCount >= period && period > 0 && signalLineWidth > 0) { + _calculateSignalLineValues(); + } + + populateChartPoints(); + } + + void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + num sum = 0; + final num value = 2 / (period + 1); + for (int i = 0; i < period; i++) { + sum += _fieldValue(i, valueField); + } + + final double x = xValues[period - 1].toDouble(); + final double y = sum / period; + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + + int index = period; + while (index < dataCount) { + final num previousAverage = _signalLineActualValues[index - period].dy; + final num yValue = + (_fieldValue(index, valueField) - previousAverage) * value + + previousAverage; + _yValues.add(yValue); + + final double x = xValues[index].toDouble(); + final double y = yValue.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + + index++; + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + + num _fieldValue(int index, String valueField) { + num? value; + if (valueField == 'low') { + value = _lowValues[index]; + } else if (valueField == 'high') { + value = _highValues[index]; + } else if (valueField == 'open') { + value = _openValues[index]; + } else if (valueField == 'y') { + value = _yValues[index]; + } else { + value = _closeValues[index]; + } + return value.isNaN ? 0 : value; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'EMA'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + DoubleRange range(RenderChartAxis axis) { + if (axis == xAxis) { + return DoubleRange(xMinimum, xMaximum); + } else { + return DoubleRange(yMinimum, yMaximum); + } + } + + @override + void transformValues() { + signalLinePoints.clear(); + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); + if (nearestPointIndex != -1) { + final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); + return >[ + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: chartPoint, + series: this, + pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), + color: signalLineColor, + ), + ]; + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: xRawValues[pointIndex + period - 1], + xValue: xValues[pointIndex + period - 1], + y: _signalLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + if (signalLinePoints.isNotEmpty) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + final int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + context.canvas.restore(); + } + } + + @override + void dispose() { + _signalLineActualValues.clear(); + _yValues.clear(); + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart new file mode 100644 index 000000000..cfebd9868 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart @@ -0,0 +1,1021 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// This class holds the properties of the Macd indicator. +/// +/// The macd indicator has [shortPeriod] and [longPeriod] for defining the +/// motion of the indicator. Also, you can draw line, histogram macd or both +/// using the [macdType] property. +/// +/// The [macdLineColor] property is used to define the color for the +/// macd line and the [histogramNegativeColor] and [histogramPositiveColor] +/// property is used to define the color for the macd histogram. +/// +/// Provides the options of macd type, name, short Period, long period and macd +/// line color is used to customize the appearance. +@immutable +class MacdIndicator extends TechnicalIndicator { + /// Creating an argument constructor of MacdIndicator class. + MacdIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + 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.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + /// Short period value of the macd indicator. + /// + /// Defaults to `12`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// shortPeriod: 2 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final int shortPeriod; + + /// Long period value of the macd indicator. + /// + /// Defaults to `26`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// longPeriod: 31 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final int longPeriod; + + /// Macd line color of the macd indicator. + /// + /// Defaults to `Colors.orange`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// macdLineColor: Colors.red + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color macdLineColor; + + /// Macd line width of the macd indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// macdLineWidth: 3 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double macdLineWidth; + + /// Macd type line of the macd indicator. + /// + /// Defaults to `MacdType.both`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// macdType: MacdType.histogram + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final MacdType macdType; + + /// Histogram positive color of the macd indicator. + /// + /// Defaults to `Colors.green`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// macdType: MacdType.histogram, + /// histogramPositiveColor: Colors.red + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color histogramPositiveColor; + + /// Histogram negative color of the macd indicator. + /// + /// Defaults to `Colors.red`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MacdIndicator( + /// seriesName: 'Series1', + /// macdType: MacdType.histogram, + /// histogramNegativeColor: Colors.green + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color histogramNegativeColor; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MacdIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.shortPeriod == shortPeriod && + other.longPeriod == longPeriod && + other.macdLineColor == macdLineColor && + other.macdLineWidth == macdLineWidth && + other.macdType == macdType && + other.histogramPositiveColor == histogramPositiveColor && + other.histogramNegativeColor == histogramNegativeColor; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + shortPeriod, + longPeriod, + macdLineColor, + macdLineWidth, + macdType, + histogramPositiveColor, + histogramNegativeColor, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'MACD'; +} + +class MacdIndicatorWidget extends IndicatorWidget { + const MacdIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the MacdIndicatorRenderer renderer. + @override + MacdIndicatorRenderer createRenderer() { + return MacdIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final MacdIndicatorRenderer renderer = + super.createRenderObject(context) as MacdIndicatorRenderer; + final MacdIndicator macd = indicator as MacdIndicator; + + renderer + ..closeValueMapper = macd.closeValueMapper + ..period = macd.period + ..shortPeriod = macd.shortPeriod + ..longPeriod = macd.longPeriod + ..macdLineColor = macd.macdLineColor + ..macdLineWidth = macd.macdLineWidth + ..macdType = macd.macdType + ..histogramPositiveColor = macd.histogramPositiveColor + ..histogramNegativeColor = macd.histogramNegativeColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + MacdIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final MacdIndicator macd = indicator as MacdIndicator; + + renderObject + ..closeValueMapper = macd.closeValueMapper + ..period = macd.period + ..shortPeriod = macd.shortPeriod + ..longPeriod = macd.longPeriod + ..macdLineColor = macd.macdLineColor + ..macdLineWidth = macd.macdLineWidth + ..macdType = macd.macdType + ..histogramPositiveColor = macd.histogramPositiveColor + ..histogramNegativeColor = macd.histogramNegativeColor; + } +} + +class MacdIndicatorRenderer extends IndicatorRenderer { + late num _bottom; + late List? _dashArray; + + final List _macdLineActualValues = []; + final List _signalLineActualValues = []; + final List _histogramActualValues = []; + final List _macdPoints = []; + final Path _macdPath = Path(); + final List _yValues = []; + final List _bounds = []; + List _closeValues = []; + + final List> _macdChartPoints = + >[]; + final List> _histogramChartPoints = + >[]; + + num _xMinimum = double.infinity; + num _xMaximum = double.negativeInfinity; + num _yMinimum = double.infinity; + num _yMaximum = double.negativeInfinity; + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + int get shortPeriod => _shortPeriod; + int _shortPeriod = 12; + set shortPeriod(int value) { + if (_shortPeriod != value) { + _shortPeriod = value; + markNeedsPopulateAndLayout(); + } + } + + int get longPeriod => _longPeriod; + int _longPeriod = 26; + set longPeriod(int value) { + if (_longPeriod != value) { + _longPeriod = value; + markNeedsPopulateAndLayout(); + } + } + + Color get macdLineColor => _macdLineColor; + Color _macdLineColor = Colors.orange; + set macdLineColor(Color value) { + if (_macdLineColor != value) { + _macdLineColor = value; + markNeedsPaint(); + } + } + + double get macdLineWidth => _macdLineWidth; + double _macdLineWidth = 2; + set macdLineWidth(double value) { + if (_macdLineWidth != value) { + _macdLineWidth = value; + markNeedsPaint(); + } + } + + MacdType get macdType => _macdType; + MacdType _macdType = MacdType.both; + set macdType(MacdType value) { + if (_macdType != value) { + _macdType = value; + markNeedsPopulateAndLayout(); + } + } + + Color get histogramPositiveColor => _histogramPositiveColor; + Color _histogramPositiveColor = Colors.green; + set histogramPositiveColor(Color value) { + if (_histogramPositiveColor != value) { + _histogramPositiveColor = value; + markNeedsPaint(); + } + } + + Color get histogramNegativeColor => _histogramNegativeColor; + Color _histogramNegativeColor = Colors.red; + set histogramNegativeColor(Color value) { + if (_histogramNegativeColor != value) { + _histogramNegativeColor = value; + markNeedsPaint(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[closeValueMapper], + >[_closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _macdLineActualValues.clear(); + _signalLineActualValues.clear(); + _histogramActualValues.clear(); + _macdPoints.clear(); + + final num fastPeriod = longPeriod; + final num slowPeriod = shortPeriod; + final num trigger = period; + final num length = fastPeriod + trigger; + + if (length < dataCount && + slowPeriod <= fastPeriod && + slowPeriod > 0 && + period > 0 && + (length - 2) >= 0) { + final List shortEMA = _emaValues(slowPeriod, 'close'); + final List longEMA = _emaValues(fastPeriod, 'close'); + final List macdValues = _macdValues(shortEMA, longEMA); + _calculateMACDValues(macdValues); + + final List signalEMA = _emaValues(trigger, 'y'); + _calculateSignalValues(signalEMA); + + if (macdType == MacdType.histogram || macdType == MacdType.both) { + _calculateHistogramValues(macdValues, signalEMA); + } + } + + xMin = _xMinimum.isInfinite ? xMin : _xMinimum; + xMax = _xMaximum.isInfinite ? xMax : _xMaximum; + yMin = _yMinimum.isInfinite ? yMin : _yMinimum; + yMax = _yMaximum.isInfinite ? yMax : _yMaximum; + + populateChartPoints(); + } + + List _emaValues(num period, String valueField) { + num sum = 0; + final List emaValues = []; + final num emaPercent = 2 / (period + 1); + for (int i = 0; i < period; i++) { + sum += _fieldValue(i, valueField); + } + + final num initialEMA = sum / period; + emaValues.add(initialEMA); + num emaAvg = initialEMA; + final int start = period.toInt(); + final int end = valueField == 'close' ? dataCount : _yValues.length; + for (int j = start; j < end; j++) { + emaAvg = (_fieldValue(j, valueField) - emaAvg) * emaPercent + emaAvg; + emaValues.add(emaAvg); + } + return emaValues; + } + + num _fieldValue(int index, String valueField) { + num? value; + if (valueField == 'close') { + value = _closeValues[index]; + } else { + value = _yValues[index]; + } + return value.isNaN ? 0 : value; + } + + List _macdValues(List shortEma, List longEma) { + final List macdPoints = []; + final int diff = longPeriod - shortPeriod; + for (int i = 0; i < longEma.length; i++) { + macdPoints.add(shortEma[i + diff] - longEma[i]); + } + return macdPoints; + } + + void _calculateMACDValues(List macdPoints) { + if (macdLineWidth > 0) { + int dataMACDIndex = longPeriod - 1; + int macdIndex = 0; + while (dataMACDIndex < dataCount) { + final double x = xValues[dataMACDIndex].toDouble(); + final double y = macdPoints[macdIndex].toDouble(); + + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + + _yValues.add(y); + _macdLineActualValues.add(Offset(x, y)); + + dataMACDIndex++; + macdIndex++; + } + } + } + + void _calculateSignalValues(List signalEma) { + if (signalLineWidth > 0) { + int index = longPeriod + period - 2; + int signalIndex = 0; + while (index < dataCount) { + final double x = xValues[index].toDouble(); + final double y = signalEma[signalIndex].toDouble(); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + + index++; + signalIndex++; + } + } + } + + void _calculateHistogramValues(List macdPoints, List signalEma) { + int index = longPeriod + period - 2; + int histogramIndex = 0; + while (index < dataCount) { + final double x = xValues[index].toDouble(); + final double y = + macdPoints[histogramIndex + (period - 1)] - + signalEma[histogramIndex].toDouble(); + + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + _histogramActualValues.add(Offset(x, y)); + + index++; + histogramIndex++; + } + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_closeValues]; + positions = [ChartDataPointType.close]; + + chartPoints.clear(); + _macdChartPoints.clear(); + _histogramChartPoints.clear(); + + if (parent == null || yLists.isEmpty) { + return; + } + + if (onRenderDetailsUpdate == null) { + return; + } + + final int yLength = yLists.length; + if (positions.length != yLength) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final num xValue = xValues[i]; + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: xValue, + ); + for (int j = 0; j < yLength; j++) { + point[positions[j]] = yLists[j][i]; + } + chartPoints.add(point); + } + + if (_macdLineActualValues.isNotEmpty) { + final int length = _macdLineActualValues.length; + for (int i = 0; i < length; i++) { + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: _macdLineActualValues[i].dx, + y: _macdLineActualValues[i].dy, + ); + _macdChartPoints.add(point); + } + } + + if (_histogramActualValues.isNotEmpty) { + final int length = _histogramActualValues.length; + for (int i = 0; i < length; i++) { + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: _histogramActualValues[i].dx, + y: _histogramActualValues[i].dy, + ); + _histogramChartPoints.add(point); + } + } + } + + @override + List? trackballInfo(Offset position) { + final List trackballInfo = []; + if (macdType == MacdType.both || macdType == MacdType.line) { + final int macdPointIndex = _findNearestPoint(signalLinePoints, position); + if (macdPointIndex != -1) { + final CartesianChartPoint macdPoint = _chartPoint( + macdPointIndex, + 'macd', + ); + final String text = defaultLegendItemText(); + trackballInfo.add( + ChartTrackballInfo( + position: signalLinePoints[macdPointIndex], + point: macdPoint, + series: this, + pointIndex: macdPointIndex, + segmentIndex: macdPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(macdPoint), + text: trackballText(macdPoint, text), + color: signalLineColor, + ), + ); + } + + final int macdLinePointIndex = _findNearestPoint(_macdPoints, position); + if (macdLinePointIndex != -1) { + final CartesianChartPoint macdLinePoint = _chartPoint( + macdLinePointIndex, + 'macdLine', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _macdPoints[macdLinePointIndex], + point: macdLinePoint, + series: this, + pointIndex: macdLinePointIndex, + segmentIndex: macdLinePointIndex, + seriesIndex: index, + name: trackballMACDLineText, + header: tooltipHeaderText(macdLinePoint), + text: trackballText(macdLinePoint, trackballMACDLineText), + color: _macdLineColor, + ), + ); + } + } + + if (macdType == MacdType.both || macdType == MacdType.histogram) { + final int histogramPointIndex = _findNearestPoint( + List.generate( + _bounds.length, + (int index) => _bounds[index].center, + ).toList(), + position, + ); + if (histogramPointIndex != -1) { + final CartesianChartPoint histogramPoint = _chartPoint( + histogramPointIndex, + 'histogram', + ); + final Offset histogramPosition = Offset( + xAxis!.pointToPixel(_histogramActualValues[histogramPointIndex].dx), + yAxis!.pointToPixel( + _histogramActualValues[histogramPointIndex].dy.abs(), + ), + ); + trackballInfo.add( + ChartTrackballInfo( + position: histogramPosition, + point: histogramPoint, + series: this, + pointIndex: histogramPointIndex, + segmentIndex: histogramPointIndex, + seriesIndex: index, + name: trackballHistogramText, + header: tooltipHeaderText(histogramPoint), + text: trackballText(histogramPoint, trackballHistogramText), + color: + histogramPoint.y!.isNegative + ? histogramNegativeColor + : histogramPositiveColor, + ), + ); + } + } + return trackballInfo; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex, String type) { + return CartesianChartPoint( + x: + type == 'macd' + ? xRawValues[pointIndex + + (signalLinePoints.length - xRawValues.length).abs()] + : type == 'macdLine' + ? xRawValues[pointIndex + + (_macdPoints.length - xRawValues.length).abs()] + : xRawValues[pointIndex + + (_histogramActualValues.length - xRawValues.length).abs()], + xValue: + type == 'macd' + ? xValues[pointIndex + + (signalLinePoints.length - xValues.length).abs()] + : type == 'macdLine' + ? xValues[pointIndex + + (_macdPoints.length - xValues.length).abs()] + : _histogramActualValues[pointIndex].dx, + y: + type == 'macd' + ? _signalLineActualValues[pointIndex].dy + : type == 'macdLine' + ? _macdLineActualValues[pointIndex].dy + : _histogramActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final MacdIndicatorRenderParams params = MacdIndicatorRenderParams( + _macdChartPoints, + _histogramChartPoints, + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + String defaultLegendItemText() => 'MACD'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + _bounds.clear(); + _macdPoints.clear(); + signalLinePoints.clear(); + _macdPath.reset(); + + if (_macdLineActualValues.isNotEmpty) { + _transformCollections(_macdLineActualValues, _macdPoints); + } + + if (_signalLineActualValues.isNotEmpty) { + _transformCollections(_signalLineActualValues, signalLinePoints); + } + + if (_histogramActualValues.isNotEmpty) { + _bottom = xAxis!.crossesAt ?? max(yAxis!.visibleRange!.minimum, 0); + num sbsMin = -0.35; + num sbsMax = 0.35; + if (dependent != null && dependent! is SbsSeriesMixin) { + sbsMin = (dependent! as SbsSeriesMixin).sbsInfo.minimum; + sbsMax = (dependent! as SbsSeriesMixin).sbsInfo.maximum; + } + + final int histoLength = _histogramActualValues.length; + for (int i = 0; i < histoLength; i++) { + final Offset point = _histogramActualValues[i]; + final num leftValue = point.dx + sbsMin; + final num rightValue = point.dx + sbsMax; + final num topValue = point.dy; + final num bottomValue = _bottom.toDouble(); + + final double left = pointToPixelX(leftValue, topValue); + final double top = pointToPixelY(leftValue, topValue); + final double right = pointToPixelX(rightValue, bottomValue); + final double bottoms = pointToPixelY(rightValue, bottomValue); + + _bounds.add(Rect.fromLTRB(left, top, right, bottoms)); + } + } + } + + void _transformCollections(List collections, List points) { + final int length = collections.length; + for (int i = 0; i < length; i++) { + final num x = collections[i].dx; + final num y = collections[i].dy; + points.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + if (macdType == MacdType.both || macdType == MacdType.line) { + if (_macdPoints.isNotEmpty) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = macdLineColor + ..strokeWidth = macdLineWidth + ..style = PaintingStyle.stroke; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + _macdPath.reset(); + length = _macdPoints.length; + + _macdPath.moveTo(_macdPoints.first.dx, _macdPoints.first.dy); + for (int i = 1; i < length; i++) { + _macdPath.lineTo(_macdPoints[i].dx, _macdPoints[i].dy); + } + + drawDashes(context.canvas, _dashArray, paint, path: _macdPath); + } + } + } + + if (macdType == MacdType.both || macdType == MacdType.histogram) { + if (_bounds.isNotEmpty && _histogramActualValues.isNotEmpty) { + length = _histogramActualValues.length; + for (int i = 0; i < length; i++) { + if (_histogramActualValues[i].dy > _bottom) { + fillPaint.color = histogramPositiveColor; + } else { + fillPaint.color = histogramNegativeColor; + } + + if (fillPaint.color != Colors.transparent) { + context.canvas.drawRect(_bounds[i], fillPaint); + } + } + } + } + context.canvas.restore(); + } + + @override + void dispose() { + _bounds.clear(); + _macdLineActualValues.clear(); + _macdPoints.clear(); + _signalLineActualValues.clear(); + _histogramActualValues.clear(); + signalLinePoints.clear(); + _macdPath.reset(); + _closeValues.clear(); + _yValues.clear(); + _macdChartPoints.clear(); + _histogramChartPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart new file mode 100644 index 000000000..8e4404915 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart @@ -0,0 +1,634 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders the momentum indicator. +/// +/// This class renders the momentum indicator, it also has a center line. +/// The [centerLineColor] and [centerLineWidth] property is used to +/// define center line. +/// +/// Provides the options for visibility, center line color, center line width, +/// and period values to customize the appearance. +@immutable +class MomentumIndicator extends TechnicalIndicator { + /// Creating an argument constructor of MomentumIndicator class. + MomentumIndicator({ + super.isVisible = true, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.centerLineColor = Colors.red, + this.centerLineWidth = 2, + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Center line color of the momentum indicator. + /// + /// Defaults to `Colors.red`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MomentumIndicator( + /// seriesName: 'Series1' + /// centerLineColor : Colors.green + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color centerLineColor; + + /// Center line width of the momentum indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MomentumIndicator( + /// seriesName: 'Series1' + /// centerLineWidth: 3 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double centerLineWidth; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// MomentumIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MomentumIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.centerLineColor == centerLineColor && + other.centerLineWidth == centerLineWidth; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + centerLineColor, + centerLineWidth, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'Momentum'; +} + +class MomentumIndicatorWidget extends IndicatorWidget { + const MomentumIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the MomentumIndicatorRenderer renderer. + @override + MomentumIndicatorRenderer createRenderer() { + return MomentumIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final MomentumIndicatorRenderer renderer = + super.createRenderObject(context) as MomentumIndicatorRenderer; + final MomentumIndicator momentum = indicator as MomentumIndicator; + + renderer + ..highValueMapper = momentum.highValueMapper + ..lowValueMapper = momentum.lowValueMapper + ..openValueMapper = momentum.openValueMapper + ..closeValueMapper = momentum.closeValueMapper + ..centerLineColor = momentum.centerLineColor + ..centerLineWidth = momentum.centerLineWidth + ..period = momentum.period; + + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + MomentumIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final MomentumIndicator momentum = indicator as MomentumIndicator; + + renderObject + ..highValueMapper = momentum.highValueMapper + ..lowValueMapper = momentum.lowValueMapper + ..openValueMapper = momentum.openValueMapper + ..closeValueMapper = momentum.closeValueMapper + ..centerLineColor = momentum.centerLineColor + ..centerLineWidth = momentum.centerLineWidth + ..period = momentum.period; + } +} + +class MomentumIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _centerLineActualValues = []; + final List _centerLinePoints = []; + final Path _centerLinePath = Path(); + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + Color get centerLineColor => _centerLineColor; + Color _centerLineColor = Colors.red; + set centerLineColor(Color value) { + if (_centerLineColor != value) { + _centerLineColor = value; + markNeedsPaint(); + } + } + + double get centerLineWidth => _centerLineWidth; + double _centerLineWidth = 2; + set centerLineWidth(double value) { + if (_centerLineWidth != value) { + _centerLineWidth = value; + markNeedsPaint(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _calculateCenterAndSignalValues(); + populateChartPoints(); + } + + void _calculateCenterAndSignalValues() { + if (dataCount >= period && period > 0) { + _signalLineActualValues.clear(); + _centerLineActualValues.clear(); + + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + final bool isCenterLineVisible = centerLineWidth > 0; + final bool isSignalLineVisible = signalLineWidth > 0; + + for (int i = 0; i < dataCount; i++) { + final double x = xValues[i].toDouble(); + if (isCenterLineVisible) { + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, 100); + yMaximum = max(yMaximum, 100); + _centerLineActualValues.add(Offset(x, 100)); + } + + if (isSignalLineVisible) { + if (!(i < period)) { + final num value = + (_closeValues[i]) / (_closeValues[i - period]) * 100; + if (value.isNaN) { + continue; + } + final double y = value.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + } + } + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'Momentum'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + _centerLinePoints.clear(); + + if (_centerLineActualValues.isNotEmpty) { + final int length = _centerLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _centerLineActualValues[i].dx; + final num y = _centerLineActualValues[i].dy; + _centerLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final List> trackballInfo = + >[]; + final int momentumPointIndex = _findNearestPoint( + signalLinePoints, + position, + ); + if (momentumPointIndex != -1) { + final CartesianChartPoint momentumPoint = _chartPoint( + momentumPointIndex, + 'momentum', + ); + final String text = defaultLegendItemText(); + trackballInfo.add( + ChartTrackballInfo( + position: signalLinePoints[momentumPointIndex], + point: momentumPoint, + series: this, + pointIndex: momentumPointIndex, + segmentIndex: momentumPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(momentumPoint), + text: trackballText(momentumPoint, text), + color: signalLineColor, + ), + ); + } + final int centerPointIndex = _findNearestPoint(_centerLinePoints, position); + if (centerPointIndex != -1) { + final CartesianChartPoint centerPoint = _chartPoint( + centerPointIndex, + 'center', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _centerLinePoints[centerPointIndex], + point: centerPoint, + series: this, + pointIndex: centerPointIndex, + segmentIndex: centerPointIndex, + seriesIndex: index, + name: trackballCenterText, + header: tooltipHeaderText(centerPoint), + text: trackballText(centerPoint, trackballCenterText), + color: _centerLineColor, + ), + ); + } + + return trackballInfo; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex, String type) { + return CartesianChartPoint( + x: + type == 'momentum' + ? xRawValues[pointIndex + period - 1] + : xRawValues[pointIndex], + xValue: + type == 'momentum' + ? xValues[pointIndex + period] + : xValues[pointIndex], + y: + type == 'momentum' + ? _signalLineActualValues[pointIndex].dy + : _centerLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final MomentumIndicatorRenderParams params = + MomentumIndicatorRenderParams( + _centerLineActualValues.first.dy, + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + int length = signalLinePoints.length - 1; + + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + if (_centerLinePoints.isNotEmpty) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = centerLineColor + ..strokeWidth = centerLineWidth + ..style = PaintingStyle.stroke; + + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + _centerLinePath.reset(); + length = _centerLinePoints.length; + + _centerLinePath.moveTo( + _centerLinePoints.first.dx, + _centerLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + _centerLinePath.lineTo( + _centerLinePoints[i].dx, + _centerLinePoints[i].dy, + ); + } + drawDashes(context.canvas, _dashArray, paint, path: _centerLinePath); + } + } + + context.canvas.restore(); + } + + @override + void dispose() { + _signalLineActualValues.clear(); + _centerLineActualValues.clear(); + _centerLinePoints.clear(); + _centerLinePath.reset(); + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/roc_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/roc_indicator.dart new file mode 100644 index 000000000..15ed26f5b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/roc_indicator.dart @@ -0,0 +1,747 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders the ROC indicator. +/// +/// It's commonly used in technical analysis to identify trends and potential reversal points, +/// and it suggests increasing momentum in the direction of the trend. A positive ROC indicates +/// that the price is increasing, a negative ROC indicates that the price is decreasing, and +/// the magnitude of the ROC indicates the strength of the price change. +/// +/// The indicator elements are: +/// +/// * The [dataSource], which is used provide data for the technical indicators without any series. +/// * The [xValueMapper], which is a value mapper to map the x values with the technical indicator. +/// * The [highValueMapper], which is a value mapper to map the high values with the technical indicator. +/// * The [lowValueMapper], which is a value mapper to map the low values with the technical indicator. +/// * The [openValueMapper], which is a value mapper to map the open values with the technical indicator. +/// * The [closeValueMapper], which is a value mapper to map the close values with the technical indicator. +/// * The [xAxisName] and [yAxisName], which is used map the technical indicator with the multiple axes. +/// * The [seriesName], which is used map the technical indicator with the series based on names. +/// * The [centerLineColor] and [centerLineWidth], which is used to customize center line color and its width. +/// * The [period], which is used determines the start point for the rendering of technical indicator. +/// +/// ## Formula +/// +/// Data (Closing Prices for Each Day): +/// +/// * Day 1: $100 +/// * Day 2: $105 +/// * Day 3: $110 +/// * Day 4: $120 +/// * Day 5: $103 +/// * ... +/// * Day 15: $110 +/// * Day 16: $108 +/// * … +/// * Day 29: $110 +/// * Day 30: $120 +/// +/// +/// Calculation of ROC: +/// * ROC = ((currentPrice - priceNPeriodsAgo) / priceNPeriodsAgo) * 100; +/// +/// * ROC on Day 3: +/// * If we need to calculate the 3rd day ROC, the current value is 110, and previous 2nd day value is 105 +/// (110-105/105) × 100 = 4.76% +/// +/// * ROC on Day 16: +/// * If we need to calculate the 16th day ROC, the current value is 108, the previous 15th day value is 110 +/// (108-110/110) × 100 = -1.8% +/// +/// * ROC on Day 30: +/// * (120−110/110) × 100 = 9.09% +/// +/// ## Example +/// +/// This snippet shows how to create a [RocIndicator] by mapping the series data source. +/// +/// ```dart +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: Center( +/// child: SfCartesianChart( +/// primaryXAxis: const DateTimeAxis(), +/// primaryYAxis: const NumericAxis(), +/// axes: const [ +/// NumericAxis( +/// majorGridLines: MajorGridLines(width: 0), +/// opposedPosition: true, +/// name: 'yAxis', +/// ), +/// ], +/// indicators: >[ +/// RocIndicator( +/// seriesName: 'AAPL', +/// yAxisName: 'yAxis', +/// period: 15, +/// ), +/// ], +/// series: >[ +/// HiloOpenCloseSeries( +/// dataSource: getChartData(), +/// xValueMapper: (ChartSampleData sales, _) => sales.x as DateTime, +/// lowValueMapper: (ChartSampleData sales, _) => sales.low, +/// highValueMapper: (ChartSampleData sales, _) => sales.high, +/// openValueMapper: (ChartSampleData sales, _) => sales.open, +/// closeValueMapper: (ChartSampleData sales, _) => sales.close, +/// name: 'AAPL', +/// ), +/// ], +/// ), +/// ), +/// ), +/// ); +/// } +/// ``` +/// This snippet shows how to create a [RocIndicator] using a direct data source. +/// +/// ```dart +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: Center( +/// child: SfCartesianChart( +/// primaryXAxis: const DateTimeAxis(), +/// primaryYAxis: const NumericAxis(), +/// axes: const [ +/// NumericAxis( +/// majorGridLines: MajorGridLines(width: 0), +/// opposedPosition: true, +/// name: 'yAxis', +/// ), +/// ], +/// indicators: >[ +/// RocIndicator( +/// dataSource: getChartData(), +/// xValueMapper: (ChartSampleData sales, _) => sales.x as DateTime, +/// lowValueMapper: (ChartSampleData sales, _) => sales.low, +/// highValueMapper: (ChartSampleData sales, _) => sales.high, +/// openValueMapper: (ChartSampleData sales, _) => sales.open, +/// closeValueMapper: (ChartSampleData sales, _) => sales.close, +/// yAxisName: 'yAxis', +/// period: 15, +/// ), +/// ], +/// ), +/// ), +/// ), +/// ); +/// } +/// ``` +/// +@immutable +class RocIndicator extends TechnicalIndicator { + /// Creating an argument constructor of ROCIndicator class. + RocIndicator({ + super.isVisible = true, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.centerLineColor = Colors.red, + this.centerLineWidth = 2, + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Center line color of the ROC indicator. + /// + /// Defaults to `Colors.red`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RocIndicator( + /// seriesName: 'Series1' + /// centerLineColor : Colors.green + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color centerLineColor; + + /// Center line width of the ROC indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RocIndicator( + /// seriesName: 'Series1' + /// centerLineWidth: 3 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double centerLineWidth; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RocIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is RocIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.centerLineColor == centerLineColor && + other.centerLineWidth == centerLineWidth; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + centerLineColor, + centerLineWidth, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'ROC'; +} + +class RocIndicatorWidget extends IndicatorWidget { + const RocIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the RocIndicatorRenderer renderer. + @override + RocIndicatorRenderer createRenderer() { + return RocIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final RocIndicatorRenderer renderer = + super.createRenderObject(context) as RocIndicatorRenderer; + final RocIndicator roc = indicator as RocIndicator; + renderer + ..highValueMapper = roc.highValueMapper + ..lowValueMapper = roc.lowValueMapper + ..openValueMapper = roc.openValueMapper + ..closeValueMapper = roc.closeValueMapper + ..centerLineColor = roc.centerLineColor + ..centerLineWidth = roc.centerLineWidth + ..period = roc.period; + + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RocIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final RocIndicator roc = indicator as RocIndicator; + renderObject + ..highValueMapper = roc.highValueMapper + ..lowValueMapper = roc.lowValueMapper + ..openValueMapper = roc.openValueMapper + ..closeValueMapper = roc.closeValueMapper + ..centerLineColor = roc.centerLineColor + ..centerLineWidth = roc.centerLineWidth + ..period = roc.period; + } +} + +class RocIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _centerLineActualValues = []; + final List _centerLinePoints = []; + final Path _centerLinePath = Path(); + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + Color get centerLineColor => _centerLineColor; + Color _centerLineColor = Colors.red; + set centerLineColor(Color value) { + if (_centerLineColor != value) { + _centerLineColor = value; + markNeedsPaint(); + } + } + + double get centerLineWidth => _centerLineWidth; + double _centerLineWidth = 2; + set centerLineWidth(double value) { + if (_centerLineWidth != value) { + _centerLineWidth = value; + markNeedsPaint(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _calculateCenterAndSignalValues(); + populateChartPoints(); + } + + void _calculateCenterAndSignalValues() { + if (dataCount >= period && period > 0) { + _signalLineActualValues.clear(); + _centerLineActualValues.clear(); + + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + final bool isCenterLineVisible = centerLineWidth > 0; + final bool isSignalLineVisible = signalLineWidth > 0; + + for (int i = 0; i < dataCount; i++) { + final double x = xValues[i].toDouble(); + if (isCenterLineVisible) { + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, 0); + yMaximum = max(yMaximum, 0); + _centerLineActualValues.add(Offset(x, 0)); + } + + if (isSignalLineVisible) { + if (!(i < period)) { + final num prevY = _closeValues[i - period]; + final double y = ((_closeValues[i] - prevY) / prevY) * 100; + if (y.isNaN) { + continue; + } + + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + } + } + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'ROC'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + _centerLinePoints.clear(); + + if (_centerLineActualValues.isNotEmpty) { + final int length = _centerLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _centerLineActualValues[i].dx; + final num y = _centerLineActualValues[i].dy; + _centerLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final List> trackballInfo = + >[]; + final int rocPointIndex = _findNearestPoint(signalLinePoints, position); + if (rocPointIndex != -1) { + final CartesianChartPoint rocPoint = _chartPoint(rocPointIndex, 'roc'); + final String text = defaultLegendItemText(); + trackballInfo.add( + ChartTrackballInfo( + position: signalLinePoints[rocPointIndex], + point: rocPoint, + series: this, + pointIndex: rocPointIndex, + segmentIndex: rocPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(rocPoint), + text: trackballText(rocPoint, text), + color: signalLineColor, + ), + ); + } + final int centerPointIndex = _findNearestPoint(_centerLinePoints, position); + if (centerPointIndex != -1) { + final CartesianChartPoint centerPoint = _chartPoint( + centerPointIndex, + 'center', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _centerLinePoints[centerPointIndex], + point: centerPoint, + series: this, + pointIndex: centerPointIndex, + segmentIndex: centerPointIndex, + seriesIndex: index, + name: trackballCenterText, + header: tooltipHeaderText(centerPoint), + text: trackballText(centerPoint, trackballCenterText), + color: _centerLineColor, + ), + ); + } + + return trackballInfo; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex, String type) { + return CartesianChartPoint( + x: + type == 'roc' + ? xRawValues[pointIndex + period - 1] + : xRawValues[pointIndex], + xValue: + type == 'roc' ? xValues[pointIndex + period] : xValues[pointIndex], + y: + type == 'roc' + ? _signalLineActualValues[pointIndex].dy + : _centerLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final RocIndicatorRenderParams params = RocIndicatorRenderParams( + _centerLineActualValues.first.dy, + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + if (_centerLinePoints.isNotEmpty) { + final Paint paint = + Paint() + ..isAntiAlias = true + ..color = centerLineColor + ..strokeWidth = centerLineWidth + ..style = PaintingStyle.stroke; + + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + _centerLinePath.reset(); + length = _centerLinePoints.length; + _centerLinePath.moveTo( + _centerLinePoints.first.dx, + _centerLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + _centerLinePath.lineTo( + _centerLinePoints[i].dx, + _centerLinePoints[i].dy, + ); + } + drawDashes(context.canvas, _dashArray, paint, path: _centerLinePath); + } + } + context.canvas.restore(); + } + + @override + void dispose() { + _signalLineActualValues.clear(); + _centerLineActualValues.clear(); + _centerLinePoints.clear(); + _centerLinePath.reset(); + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + _dashArray = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart new file mode 100644 index 000000000..1e7758ad7 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart @@ -0,0 +1,932 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders relative strength index (RSI) indicator. +/// +/// The relative strength index (RSI) is a momentum indicator that measures the +/// magnitude of recent price changes to evaluate [overbought] or [oversold] +/// conditions. +/// +/// The RSI indicator has additional two lines other than the signal line. They +/// indicate the [overbought] and [oversold] region. +/// +/// The [upperLineColor] property is used to define the color for the line that +/// indicates [overbought] region, and the [lowerLineColor] property is used +/// to define the color for the line that indicates [oversold] region. +@immutable +class RsiIndicator extends TechnicalIndicator { + /// Creating an argument constructor of RsiIndicator class. + RsiIndicator({ + super.isVisible = true, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.showZones = true, + this.overbought = 80, + this.oversold = 20, + this.upperLineColor = Colors.red, + this.upperLineWidth = 2, + this.lowerLineColor = Colors.green, + this.lowerLineWidth = 2, + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Show zones boolean value for RSI indicator. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// showZones : false + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final bool showZones; + + /// Overbought value for RSI indicator. + /// + /// Defaults to `80`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// overbought : 50 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double overbought; + + /// Oversold value for RSI indicator. + /// + /// Defaults to `20`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// oversold : 30 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double oversold; + + /// Color of the upper line for RSI indicator. + /// + /// Defaults to `Colors.red`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// upperLineColor : Colors.greenAccent + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color upperLineColor; + + /// Width of the upper line for RSI indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// upperLineWidth : 4.0 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double upperLineWidth; + + /// Color of the lower line for RSI indicator. + /// + /// Defaults to `Colors.green`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// lowerLineColor : Colors.blue + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color lowerLineColor; + + /// Width of the lower line for RSI indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// seriesName: 'Series1' + /// lowerLineWidth : 4.0 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double lowerLineWidth; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// RsiIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is RsiIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.showZones == showZones && + other.overbought == overbought && + other.oversold == oversold && + other.upperLineColor == upperLineColor && + other.upperLineWidth == upperLineWidth && + other.lowerLineColor == lowerLineColor && + other.lowerLineWidth == lowerLineWidth; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + showZones, + overbought, + oversold, + upperLineColor, + upperLineWidth, + lowerLineColor, + lowerLineWidth, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'RSI'; +} + +class RsiIndicatorWidget extends IndicatorWidget { + const RsiIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the RsiIndicatorRenderer renderer. + @override + RsiIndicatorRenderer createRenderer() { + return RsiIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final RsiIndicatorRenderer renderer = + super.createRenderObject(context) as RsiIndicatorRenderer; + final RsiIndicator rsi = indicator as RsiIndicator; + + renderer + ..highValueMapper = rsi.highValueMapper + ..lowValueMapper = rsi.lowValueMapper + ..closeValueMapper = rsi.closeValueMapper + ..period = rsi.period + ..showZones = rsi.showZones + ..overbought = rsi.overbought + ..oversold = rsi.oversold + ..upperLineColor = rsi.upperLineColor + ..upperLineWidth = rsi.upperLineWidth + ..lowerLineColor = rsi.lowerLineColor + ..lowerLineWidth = rsi.lowerLineWidth; + + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RsiIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final RsiIndicator rsi = indicator as RsiIndicator; + + renderObject + ..highValueMapper = rsi.highValueMapper + ..lowValueMapper = rsi.lowValueMapper + ..closeValueMapper = rsi.closeValueMapper + ..period = rsi.period + ..showZones = rsi.showZones + ..overbought = rsi.overbought + ..oversold = rsi.oversold + ..upperLineColor = rsi.upperLineColor + ..upperLineWidth = rsi.upperLineWidth + ..lowerLineColor = rsi.lowerLineColor + ..lowerLineWidth = rsi.lowerLineWidth; + } +} + +class RsiIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _lowerLineActualValues = []; + final List _upperLineActualValues = []; + final List _lowerLinePoints = []; + final List _upperLinePoints = []; + final Path _upperLinePath = Path(); + final Path _lowerLinePath = Path(); + + List _highValues = []; + List _lowValues = []; + List _closeValues = []; + + num _xMinimum = double.infinity; + num _xMaximum = double.negativeInfinity; + num _yMinimum = double.infinity; + num _yMaximum = double.negativeInfinity; + + bool get showZones => _showZones; + bool _showZones = true; + set showZones(bool value) { + if (_showZones != value) { + _showZones = value; + markNeedsPaint(); + } + } + + double get overbought => _overbought; + double _overbought = 80; + set overbought(double value) { + if (_overbought != value) { + _overbought = value; + markNeedsPopulateAndLayout(); + } + } + + double get oversold => _oversold; + double _oversold = 20; + set oversold(double value) { + if (_oversold != value) { + _oversold = value; + markNeedsPopulateAndLayout(); + } + } + + Color get upperLineColor => _upperLineColor; + Color _upperLineColor = Colors.red; + set upperLineColor(Color value) { + if (_upperLineColor != value) { + _upperLineColor = value; + markNeedsPaint(); + } + } + + double get upperLineWidth => _upperLineWidth; + double _upperLineWidth = 2; + set upperLineWidth(double value) { + if (_upperLineWidth != value) { + _upperLineWidth = value; + markNeedsPaint(); + } + } + + Color get lowerLineColor => _lowerLineColor; + Color _lowerLineColor = Colors.green; + set lowerLineColor(Color value) { + if (_lowerLineColor != value) { + _lowerLineColor = value; + markNeedsPaint(); + } + } + + double get lowerLineWidth => _lowerLineWidth; + double _lowerLineWidth = 2; + set lowerLineWidth(double value) { + if (_lowerLineWidth != value) { + _lowerLineWidth = value; + markNeedsPaint(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + if (dataCount >= period && period > 0) { + _upperLineActualValues.clear(); + _lowerLineActualValues.clear(); + _signalLineActualValues.clear(); + + _calculateZones(); + _calculateSignalLineValues(); + } + + xMin = _xMinimum.isInfinite ? xMin : _xMinimum; + xMax = _xMaximum.isInfinite ? xMax : _xMaximum; + yMin = _yMinimum.isInfinite ? yMin : _yMinimum; + yMax = _yMaximum.isInfinite ? yMax : _yMaximum; + + populateChartPoints(); + } + + void _calculateZones() { + if (!showZones || (upperLineWidth <= 0 && lowerLineWidth <= 0)) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final double x = xValues[i].toDouble(); + if (upperLineWidth > 0) { + _upperLineActualValues.add(Offset(x, overbought)); + } + + if (lowerLineWidth > 0) { + _lowerLineActualValues.add(Offset(x, oversold)); + } + + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + } + + _yMinimum = min(_yMinimum, min(overbought, oversold)); + _yMaximum = max(_yMaximum, max(overbought, oversold)); + } + + void _calculateSignalLineValues() { + if (signalLineWidth <= 0 || _closeValues.isEmpty) { + return; + } + + num previousClose = _closeValues[0]; + if (previousClose.isNaN) { + previousClose = 0; + } + num gain = 0; + num loss = 0; + + for (int i = 1; i <= period; i++) { + num close = _closeValues[i]; + if (close.isNaN) { + close = 0; + } + if (close > previousClose) { + gain += close - previousClose; + } else { + loss += previousClose - close; + } + previousClose = close; + } + + gain = gain / period; + loss = loss / period; + final num value = 100 - (100 / (1 + (gain / loss))); + + final double x = xValues[period].toDouble(); + final double y = value.toDouble(); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + + _signalLineActualValues.add(Offset(x, y)); + + for (int j = period + 1; j < dataCount; j++) { + num currentClose = _closeValues[j]; + if (currentClose.isNaN) { + currentClose = 0; + } + if (currentClose > previousClose) { + gain = (gain * (period - 1) + (currentClose - previousClose)) / period; + loss = (loss * (period - 1)) / period; + } else if (currentClose < previousClose) { + loss = (loss * (period - 1) + (previousClose - currentClose)) / period; + gain = (gain * (period - 1)) / period; + } + previousClose = currentClose; + final num value = 100 - (100 / (1 + (gain / loss))); + + final double x = xValues[j].toDouble(); + final double y = value.toDouble(); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + + _signalLineActualValues.add(Offset(x, y)); + } + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'RSI'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + _upperLinePoints.clear(); + _lowerLinePoints.clear(); + + if (showZones) { + if (_upperLineActualValues.isNotEmpty) { + final int length = _upperLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _upperLineActualValues[i].dx; + final num y = _upperLineActualValues[i].dy; + _upperLinePoints.add( + Offset(pointToPixelX(x, y), pointToPixelY(x, y)), + ); + } + } + + if (_lowerLineActualValues.isNotEmpty) { + final int length = _lowerLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _lowerLineActualValues[i].dx; + final num y = _lowerLineActualValues[i].dy; + _lowerLinePoints.add( + Offset(pointToPixelX(x, y), pointToPixelY(x, y)), + ); + } + } + } + + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final List> trackballInfo = + >[]; + final int rsiPointIndex = _findNearestPoint(signalLinePoints, position); + if (rsiPointIndex != -1) { + final CartesianChartPoint rsiPoint = _chartPoint(rsiPointIndex, 'rsi'); + final String rsiText = defaultLegendItemText(); + trackballInfo.add( + ChartTrackballInfo( + position: signalLinePoints[rsiPointIndex], + point: rsiPoint, + series: this, + pointIndex: rsiPointIndex, + segmentIndex: rsiPointIndex, + seriesIndex: index, + name: rsiText, + header: tooltipHeaderText(rsiPoint), + text: trackballText(rsiPoint, rsiText), + color: signalLineColor, + ), + ); + } + if (showZones) { + final int upperPointIndex = _findNearestPoint(_upperLinePoints, position); + if (upperPointIndex != -1) { + final CartesianChartPoint upperPoint = _chartPoint( + upperPointIndex, + 'upper', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _upperLinePoints[upperPointIndex], + point: upperPoint, + series: this, + pointIndex: upperPointIndex, + segmentIndex: upperPointIndex, + seriesIndex: index, + name: trackballUpperLineText, + header: tooltipHeaderText(upperPoint), + text: trackballText(upperPoint, trackballUpperLineText), + color: _upperLineColor, + ), + ); + } + final int lowerPointIndex = _findNearestPoint(_lowerLinePoints, position); + if (lowerPointIndex != -1) { + final CartesianChartPoint lowerPoint = _chartPoint( + lowerPointIndex, + 'lower', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _lowerLinePoints[lowerPointIndex], + point: lowerPoint, + series: this, + pointIndex: lowerPointIndex, + segmentIndex: lowerPointIndex, + seriesIndex: index, + name: trackballLowerLineText, + header: tooltipHeaderText(lowerPoint), + text: trackballText(lowerPoint, trackballLowerLineText), + color: _lowerLineColor, + ), + ); + } + } + return trackballInfo; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex, String type) { + return CartesianChartPoint( + x: + type == 'rsi' + ? xRawValues[pointIndex + period] + : xRawValues[pointIndex], + xValue: + type == 'rsi' ? xValues[pointIndex + period] : xValues[pointIndex], + y: + type == 'rsi' + ? _signalLineActualValues[pointIndex].dy + : type == 'upper' + ? _upperLineActualValues[pointIndex].dy + : _lowerLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + int length = signalLinePoints.length - 1; + + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + if (showZones && + _upperLinePoints.isNotEmpty && + upperLineWidth > 0 && + upperLineColor != Colors.transparent) { + _upperLinePath.reset(); + length = _upperLinePoints.length; + + _upperLinePath.moveTo( + _upperLinePoints.first.dx, + _upperLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + _upperLinePath.lineTo(_upperLinePoints[i].dx, _upperLinePoints[i].dy); + } + + drawDashes( + context.canvas, + _dashArray, + strokePaint + ..color = upperLineColor + ..strokeWidth = upperLineWidth, + path: _upperLinePath, + ); + } + + if (showZones && + _lowerLinePoints.isNotEmpty && + lowerLineWidth > 0 && + lowerLineColor != Colors.transparent) { + _lowerLinePath.reset(); + length = _lowerLinePoints.length; + + _lowerLinePath.moveTo( + _lowerLinePoints.first.dx, + _lowerLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + _lowerLinePath.lineTo(_lowerLinePoints[i].dx, _lowerLinePoints[i].dy); + } + + drawDashes( + context.canvas, + _dashArray, + strokePaint + ..color = lowerLineColor + ..strokeWidth = lowerLineWidth, + path: _lowerLinePath, + ); + } + + context.canvas.restore(); + } + + @override + void dispose() { + _signalLineActualValues.clear(); + _lowerLineActualValues.clear(); + _upperLineActualValues.clear(); + _lowerLinePoints.clear(); + _upperLinePoints.clear(); + _highValues.clear(); + _lowValues.clear(); + _closeValues.clear(); + _upperLinePath.reset(); + _lowerLinePath.reset(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart new file mode 100644 index 000000000..1c0543255 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart @@ -0,0 +1,542 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders simple moving average (SMA) indicator. +/// +/// A simple moving average (SMA) is an arithmetic moving average calculated by +/// adding recent closing prices and then dividing the total by the number of +/// time periods in the calculation average. +/// +/// It also has a [valueField] property. Based on this property, the indicator +/// will be rendered. +@immutable +class SmaIndicator extends TechnicalIndicator { + /// Creating an argument constructor of SmaIndicator class. + SmaIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.valueField = 'close', + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Value field value for SMA indicator. + /// + /// Value field determines the field for the rendering of SMA indicator. + /// + /// Defaults to `close`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// SmaIndicator( + /// seriesName: 'Series1' + /// period: 4, + /// valueField: 'low' + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String valueField; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// SmaIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is SmaIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.valueField == valueField; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + valueField, + period, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'SMA'; +} + +class SmaIndicatorWidget extends IndicatorWidget { + const SmaIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the SmaIndicatorRenderer renderer. + @override + SmaIndicatorRenderer createRenderer() { + return SmaIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final SmaIndicatorRenderer renderer = + super.createRenderObject(context) as SmaIndicatorRenderer; + final SmaIndicator sma = indicator as SmaIndicator; + + renderer + ..highValueMapper = sma.highValueMapper + ..lowValueMapper = sma.lowValueMapper + ..openValueMapper = sma.openValueMapper + ..closeValueMapper = sma.closeValueMapper + ..valueField = sma.valueField + ..period = sma.period; + + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + SmaIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final SmaIndicator sma = indicator as SmaIndicator; + + renderObject + ..highValueMapper = sma.highValueMapper + ..lowValueMapper = sma.lowValueMapper + ..openValueMapper = sma.openValueMapper + ..closeValueMapper = sma.closeValueMapper + ..valueField = sma.valueField + ..period = sma.period; + } +} + +class SmaIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _yValues = []; + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + String get valueField => _valueField; + String _valueField = 'Close'; + set valueField(String value) { + if (_valueField != value) { + _valueField = value; + markNeedsPopulateAndLayout(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _signalLineActualValues.clear(); + _yValues.clear(); + if (xValues.isNotEmpty && + dataCount >= period && + period > 0 && + signalLineWidth > 0) { + _calculateSignalLineValues(); + } + + populateChartPoints(); + } + + void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + num sum = 0; + for (int i = 0; i < period; i++) { + sum += _fieldValue(i, valueField); + } + + num average = sum / period; + _yValues.add(average); + + final double x = xValues[period - 1].toDouble(); + final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + + int j = period; + while (j < dataCount) { + sum -= _fieldValue(j - period, valueField); + sum += _fieldValue(j, valueField); + average = sum / period; + _yValues.add(average); + + final double x = xValues[j].toDouble(); + final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + + j++; + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + + num _fieldValue(int index, String valueField) { + num? value; + if (valueField == 'low') { + value = _lowValues[index]; + } else if (valueField == 'high') { + value = _highValues[index]; + } else if (valueField == 'open') { + value = _openValues[index]; + } else if (valueField == 'y') { + value = _yValues[index]; + } else { + value = _closeValues[index]; + } + return value.isNaN ? 0 : value; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'SMA'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); + if (nearestPointIndex != -1) { + final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); + return >[ + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: chartPoint, + series: this, + pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), + color: signalLineColor, + ), + ]; + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: xRawValues[pointIndex + period - 1], + xValue: xValues[pointIndex + period - 1], + y: _signalLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + if (signalLinePoints.isNotEmpty) { + final int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + + context.canvas.restore(); + } + } + } + + @override + void dispose() { + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + _yValues.clear(); + _signalLineActualValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart new file mode 100644 index 000000000..c876c987b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart @@ -0,0 +1,1280 @@ +import 'dart:math' as math; +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders stochastic indicator. +/// +/// The stochastic indicator is used to measure the range and momentum of price +/// movements. It contains kPeriod and dPeriod properties defining +/// the ‘k’ percentage and ‘d’ percentage respectively. +/// +/// In this indicator [upperLineColor], [lowerLineColor] and [periodLineColor] +/// property are used to define the color for the stochastic indicator lines. +@immutable +class StochasticIndicator extends TechnicalIndicator { + /// Creating an argument constructor of StochasticIndicator class. + StochasticIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + 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.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Show zones boolean value for stochastic indicator. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// showZones : false + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final bool showZones; + + /// Overbought value for stochastic indicator + /// + /// Defaults to `80`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// overbought: 50 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double overbought; + + /// Oversold value for stochastic Indicator. + /// + /// Defaults to `20`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// oversold: 30 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double oversold; + + /// Color of the upper line for stochastic Indicator. + /// + /// Defaults to `Colors.red`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// upperLineColor: Colors.greenAccent + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color upperLineColor; + + /// Width of the upper line for stochastic Indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// upperLineWidth: 4.0 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double upperLineWidth; + + /// Color of the lower line for stochastic Indicator. + /// + /// Defaults to `Colors.green`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// lowerLineColor: Colors.blue + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color lowerLineColor; + + /// Width of lower line for stochastic Indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// lowerLineWidth: 4.0 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double lowerLineWidth; + + /// Color of the period line for stochastic Indicator. + /// + /// Defaults to `Colors.yellow`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// periodLineColor: Colors.orange + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final Color periodLineColor; + + /// Width of the period line for stochastic Indicator. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// periodLineWidth: 5.0 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final double periodLineWidth; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + /// Value of kPeriod in stochastic Indicator. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// kPeriod: 4 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final num kPeriod; + + /// Value of dPeriod in stochastic Indicator. + /// + /// Defaults to `5`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// dPeriod: 4 + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final num dPeriod; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is StochasticIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.showZones == showZones && + other.overbought == overbought && + other.oversold == oversold && + other.upperLineColor == upperLineColor && + other.upperLineWidth == upperLineWidth && + other.lowerLineColor == lowerLineColor && + other.lowerLineWidth == lowerLineWidth && + other.periodLineColor == periodLineColor && + other.periodLineWidth == periodLineWidth && + other.kPeriod == kPeriod && + other.dPeriod == dPeriod; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + period, + showZones, + overbought, + oversold, + upperLineColor, + upperLineWidth, + lowerLineColor, + lowerLineWidth, + periodLineColor, + periodLineWidth, + kPeriod, + dPeriod, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'Stochastic'; +} + +class StochasticIndicatorWidget extends IndicatorWidget { + const StochasticIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the StochasticIndicatorRenderer renderer. + @override + StochasticIndicatorRenderer createRenderer() { + return StochasticIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final StochasticIndicatorRenderer renderer = + super.createRenderObject(context) as StochasticIndicatorRenderer; + final StochasticIndicator stochastic = indicator as StochasticIndicator; + + renderer + ..highValueMapper = stochastic.highValueMapper + ..lowValueMapper = stochastic.lowValueMapper + ..highValueMapper = stochastic.highValueMapper + ..closeValueMapper = stochastic.closeValueMapper + ..showZones = stochastic.showZones + ..overbought = stochastic.overbought + ..oversold = stochastic.oversold + ..upperLineColor = stochastic.upperLineColor + ..upperLineWidth = stochastic.upperLineWidth + ..lowerLineColor = stochastic.lowerLineColor + ..lowerLineWidth = stochastic.lowerLineWidth + ..periodLineColor = stochastic.periodLineColor + ..periodLineWidth = stochastic.periodLineWidth + ..period = stochastic.period + ..kPeriod = stochastic.kPeriod + ..dPeriod = stochastic.dPeriod; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StochasticIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final StochasticIndicator stochastic = indicator as StochasticIndicator; + + renderObject + ..highValueMapper = stochastic.highValueMapper + ..lowValueMapper = stochastic.lowValueMapper + ..highValueMapper = stochastic.highValueMapper + ..closeValueMapper = stochastic.closeValueMapper + ..showZones = stochastic.showZones + ..overbought = stochastic.overbought + ..oversold = stochastic.oversold + ..upperLineColor = stochastic.upperLineColor + ..upperLineWidth = stochastic.upperLineWidth + ..lowerLineColor = stochastic.lowerLineColor + ..lowerLineWidth = stochastic.lowerLineWidth + ..periodLineColor = stochastic.periodLineColor + ..periodLineWidth = stochastic.periodLineWidth + ..period = stochastic.period + ..kPeriod = stochastic.kPeriod + ..dPeriod = stochastic.dPeriod; + } +} + +class StochasticIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + final List _signalLineActualValues = []; + final List _periodLineActualValues = []; + final List _lowerLineActualValues = []; + final List _upperLineActualValues = []; + + final List _upperLinePoints = []; + final List _lowerLinePoints = []; + final List _periodLinePoints = []; + final Path _upperLinePath = Path(); + final Path _lowerLinePath = Path(); + + final List> _periodChartPoints = + >[]; + + num _xMinimum = double.infinity; + num _xMaximum = double.negativeInfinity; + num _yMinimum = double.infinity; + num _yMaximum = double.negativeInfinity; + + bool get showZones => _showZones; + bool _showZones = true; + set showZones(bool value) { + if (_showZones != value) { + _showZones = value; + markNeedsPaint(); + } + } + + double get overbought => _overbought; + double _overbought = 80; + set overbought(double value) { + if (_overbought != value) { + _overbought = value; + markNeedsPopulateAndLayout(); + } + } + + double get oversold => _oversold; + double _oversold = 20; + set oversold(double value) { + if (_oversold != value) { + _oversold = value; + markNeedsPopulateAndLayout(); + } + } + + Color get upperLineColor => _upperLineColor; + Color _upperLineColor = Colors.red; + set upperLineColor(Color value) { + if (_upperLineColor != value) { + _upperLineColor = value; + markNeedsPaint(); + } + } + + double get upperLineWidth => _upperLineWidth; + double _upperLineWidth = 2; + set upperLineWidth(double value) { + if (_upperLineWidth != value) { + _upperLineWidth = value; + markNeedsPaint(); + } + } + + Color get lowerLineColor => _lowerLineColor; + Color _lowerLineColor = Colors.green; + set lowerLineColor(Color value) { + if (_lowerLineColor != value) { + _lowerLineColor = value; + markNeedsPaint(); + } + } + + double get lowerLineWidth => _lowerLineWidth; + double _lowerLineWidth = 2; + set lowerLineWidth(double value) { + if (_lowerLineWidth != value) { + _lowerLineWidth = value; + markNeedsPaint(); + } + } + + Color get periodLineColor => _periodLineColor; + Color _periodLineColor = Colors.yellow; + set periodLineColor(Color value) { + if (_periodLineColor != value) { + _periodLineColor = value; + markNeedsPaint(); + } + } + + double get periodLineWidth => _periodLineWidth; + double _periodLineWidth = 2; + set periodLineWidth(double value) { + if (_periodLineWidth != value) { + _periodLineWidth = value; + markNeedsPaint(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + num get kPeriod => _kPeriod; + num _kPeriod = 3; + set kPeriod(num value) { + if (_kPeriod != value) { + _kPeriod = value; + markNeedsPopulateAndLayout(); + } + } + + num get dPeriod => _dPeriod; + num _dPeriod = 5; + set dPeriod(num value) { + if (_dPeriod != value) { + _dPeriod = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + if (dataCount >= period && period > 0) { + _upperLineActualValues.clear(); + _lowerLineActualValues.clear(); + _signalLineActualValues.clear(); + _periodLineActualValues.clear(); + + _calculateZones(); + _calculateStochasticIndicatorValues(); + } + + xMin = _xMinimum.isInfinite ? xMin : _xMinimum; + xMax = _xMaximum.isInfinite ? xMax : _xMaximum; + yMin = _yMinimum.isInfinite ? yMin : _yMinimum; + yMax = _yMaximum.isInfinite ? yMax : _yMaximum; + + populateChartPoints(); + } + + void _calculateZones() { + if (!showZones || (upperLineWidth <= 0 && lowerLineWidth <= 0)) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final double x = xValues[i].toDouble(); + if (upperLineWidth > 0) { + _upperLineActualValues.add(Offset(x, overbought)); + } + + if (lowerLineWidth > 0) { + _lowerLineActualValues.add(Offset(x, oversold)); + } + + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + } + + _yMinimum = min(_yMinimum, min(overbought, oversold)); + _yMaximum = max(_yMaximum, max(overbought, oversold)); + } + + void _calculateStochasticIndicatorValues() { + final List source = _calculatePeriodValues(period, kPeriod.toInt()); + if (periodLineWidth > 0) { + _periodLineActualValues.addAll( + _calculateStochasticValues(period, kPeriod.toInt(), source), + ); + } + + if (signalLineWidth > 0) { + _signalLineActualValues.addAll( + _calculateStochasticValues( + (period + kPeriod - 1).toInt(), + dPeriod.toInt(), + source, + ), + ); + } + } + + List _calculateStochasticValues( + int period, + int kPeriod, + List data, + ) { + final List points = []; + final int dataLength = data.length; + if (dataLength >= period + kPeriod && kPeriod > 0) { + final int count = period + (kPeriod - 1); + final List temp = [], values = []; + for (int i = 0; i < dataLength; i++) { + temp.add(data[i].dy); + } + + num tempCount = temp.length; + while (tempCount >= count) { + num sum = 0; + for (int i = period - 1; i < (period + kPeriod - 1); i++) { + sum = sum + temp[i]; + } + sum = sum / kPeriod; + final String stochasticSum = sum.toStringAsFixed(2); + values.add(double.parse(stochasticSum)); + temp.removeRange(0, 1); + tempCount = temp.length; + } + + final int total = count - 1; + for (int i = 0; i < dataLength; i++) { + if (!(i < total)) { + final double x = data[i].dx; + final double y = values[i - total].toDouble(); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + + final Offset offset = Offset(x, y); + points.add(offset); + data[i] = offset; + } + } + } + + return points; + } + + List _calculatePeriodValues(int period, int kPeriod) { + if (_lowValues.isEmpty || _highValues.isEmpty || _closeValues.isEmpty) { + return []; + } + + final List lowValues = List.filled(dataCount, -1); + final List highValues = List.filled(dataCount, -1); + final List closeValues = List.filled(dataCount, -1); + final List modifiedSourceValues = []; + + for (int j = 0; j < dataCount; j++) { + lowValues[j] = _lowValues[j].isNaN ? 0 : _lowValues[j]; + highValues[j] = _highValues[j].isNaN ? 0 : _highValues[j]; + closeValues[j] = _closeValues[j].isNaN ? 0 : _closeValues[j]; + } + + if (dataCount > period) { + final List minValues = []; + final List maxValues = []; + for (int i = 0; i < period - 1; ++i) { + maxValues.add(0); + minValues.add(0); + + final double x = xValues[i].toDouble(); + double y = _closeValues[i].toDouble(); + if (y.isNaN) { + y = 0.0; + } + + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + modifiedSourceValues.add(Offset(xValues[i].toDouble(), y)); + } + + num? minValue, maxValue; + for (int i = period - 1; i < dataCount; ++i) { + for (int j = 0; j < period; ++j) { + minValue ??= lowValues[i - j]; + maxValue ??= highValues[i - j]; + minValue = math.min(minValue, lowValues[i - j]); + maxValue = math.max(maxValue, highValues[i - j]); + } + maxValues.add(maxValue!); + minValues.add(minValue!); + minValue = null; + maxValue = null; + } + + for (int i = period - 1; i < dataCount; ++i) { + num top = 0, bottom = 0; + top += closeValues[i] - minValues[i]; + bottom += maxValues[i] - minValues[i]; + + final double x = xValues[i].toDouble(); + final double y = (top / bottom) * 100; + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + modifiedSourceValues.add(Offset(x, y)); + } + } + + return modifiedSourceValues; + } + + @override + List? trackballInfo(Offset position) { + final List trackballInfo = []; + final int stocasticPointIndex = _findNearestPoint( + signalLinePoints, + position, + ); + if (stocasticPointIndex != -1) { + final CartesianChartPoint stocasticPoint = _chartPoint( + stocasticPointIndex, + 'stocastic', + ); + final String stocasticText = defaultLegendItemText(); + trackballInfo.add( + ChartTrackballInfo( + position: signalLinePoints[stocasticPointIndex], + point: stocasticPoint, + series: this, + pointIndex: stocasticPointIndex, + segmentIndex: stocasticPointIndex, + seriesIndex: index, + name: stocasticText, + header: tooltipHeaderText(stocasticPoint), + text: trackballText(stocasticPoint, stocasticText), + color: signalLineColor, + ), + ); + } + final int periodPointIndex = _findNearestPoint(_periodLinePoints, position); + if (periodPointIndex != -1) { + final CartesianChartPoint periodPoint = _chartPoint( + periodPointIndex, + 'periodLine', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _periodLinePoints[periodPointIndex], + point: periodPoint, + series: this, + pointIndex: periodPointIndex, + segmentIndex: periodPointIndex, + seriesIndex: index, + name: trackballPeriodLineText, + header: tooltipHeaderText(periodPoint), + text: trackballText(periodPoint, trackballPeriodLineText), + color: periodLineColor, + ), + ); + } + if (showZones) { + final int upperPointIndex = _findNearestPoint(_upperLinePoints, position); + if (upperPointIndex != -1) { + final CartesianChartPoint upperPoint = _chartPoint( + upperPointIndex, + 'upperLine', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _upperLinePoints[upperPointIndex], + point: upperPoint, + series: this, + pointIndex: upperPointIndex, + segmentIndex: upperPointIndex, + seriesIndex: index, + name: trackballUpperLineText, + header: tooltipHeaderText(upperPoint), + text: trackballText(upperPoint, trackballUpperLineText), + color: upperLineColor, + ), + ); + } + final int lowerPointIndex = _findNearestPoint(_lowerLinePoints, position); + if (lowerPointIndex != -1) { + final CartesianChartPoint lowerPoint = _chartPoint( + lowerPointIndex, + 'lowerLine', + ); + trackballInfo.add( + ChartTrackballInfo( + position: _lowerLinePoints[lowerPointIndex], + point: lowerPoint, + series: this, + pointIndex: lowerPointIndex, + segmentIndex: lowerPointIndex, + seriesIndex: index, + name: trackballLowerLineText, + header: tooltipHeaderText(lowerPoint), + text: trackballText(lowerPoint, trackballLowerLineText), + color: lowerLineColor, + ), + ); + } + } + return trackballInfo; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex, String type) { + return CartesianChartPoint( + x: + type == 'stocastic' + ? xRawValues[pointIndex + + (signalLinePoints.length - xRawValues.length).abs()] + : type == 'periodLine' + ? xRawValues[pointIndex + + (_periodLinePoints.length - xRawValues.length).abs()] + : xRawValues[pointIndex], + xValue: + type == 'stocastic' + ? xValues[pointIndex + + (signalLinePoints.length - xRawValues.length).abs()] + : type == 'periodLine' + ? xValues[pointIndex + + (_periodLinePoints.length - xValues.length).abs()] + : xValues[pointIndex], + y: + type == 'stocastic' + ? _signalLineActualValues[pointIndex].dy + : type == 'periodLine' + ? _periodLineActualValues[pointIndex].dy + : type == 'upperLine' + ? _upperLineActualValues[pointIndex].dy + : _lowerLineActualValues[pointIndex].dy, + ); + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + + chartPoints.clear(); + _periodChartPoints.clear(); + + if (parent == null || yLists.isEmpty) { + return; + } + + if (onRenderDetailsUpdate == null) { + return; + } + + final int yLength = yLists.length; + if (positions.length != yLength) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final num xValue = xValues[i]; + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: xValue, + ); + for (int j = 0; j < yLength; j++) { + point[positions[j]] = yLists[j][i]; + } + chartPoints.add(point); + } + + if (_periodLineActualValues.isNotEmpty) { + final int length = _periodLineActualValues.length; + for (int i = 0; i < length; i++) { + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: _periodLineActualValues[i].dx, + y: _periodLineActualValues[i].dy, + ); + _periodChartPoints.add(point); + } + } + } + + @override + String defaultLegendItemText() => 'Stochastic'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + _upperLinePoints.clear(); + _lowerLinePoints.clear(); + _periodLinePoints.clear(); + + if (showZones) { + if (_upperLineActualValues.isNotEmpty) { + final int length = _upperLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _upperLineActualValues[i].dx; + final num y = _upperLineActualValues[i].dy; + _upperLinePoints.add( + Offset(pointToPixelX(x, y), pointToPixelY(x, y)), + ); + } + } + + if (_lowerLineActualValues.isNotEmpty) { + final int length = _lowerLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _lowerLineActualValues[i].dx; + final num y = _lowerLineActualValues[i].dy; + _lowerLinePoints.add( + Offset(pointToPixelX(x, y), pointToPixelY(x, y)), + ); + } + } + } + + if (_periodLineActualValues.isNotEmpty) { + final int length = _periodLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _periodLineActualValues[i].dx; + final num y = _periodLineActualValues[i].dy; + _periodLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final StochasticIndicatorRenderParams params = + StochasticIndicatorRenderParams( + _periodChartPoints, + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + int length = signalLinePoints.length - 1; + + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + + if (_periodLinePoints.isNotEmpty) { + length = _periodLinePoints.length - 1; + strokePaint + ..color = periodLineColor + ..strokeWidth = periodLineWidth; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: _periodLinePoints[i], + end: _periodLinePoints[i + 1], + ); + } + } + } + + if (showZones && + _upperLinePoints.isNotEmpty && + upperLineWidth > 0 && + upperLineColor != Colors.transparent) { + _upperLinePath.reset(); + length = _upperLinePoints.length; + + _upperLinePath.moveTo( + _upperLinePoints.first.dx, + _upperLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + _upperLinePath.lineTo(_upperLinePoints[i].dx, _upperLinePoints[i].dy); + } + + drawDashes( + context.canvas, + _dashArray, + strokePaint + ..color = upperLineColor + ..strokeWidth = upperLineWidth, + path: _upperLinePath, + ); + } + + // Draw lower line. + if (showZones && + _lowerLinePoints.isNotEmpty && + lowerLineWidth > 0 && + lowerLineColor != Colors.transparent) { + _lowerLinePath.reset(); + length = _lowerLinePoints.length; + + _lowerLinePath.moveTo( + _lowerLinePoints.first.dx, + _lowerLinePoints.first.dy, + ); + for (int i = 1; i < length; i++) { + _lowerLinePath.lineTo(_lowerLinePoints[i].dx, _lowerLinePoints[i].dy); + } + + drawDashes( + context.canvas, + _dashArray, + strokePaint + ..color = lowerLineColor + ..strokeWidth = lowerLineWidth, + path: _lowerLinePath, + ); + } + + context.canvas.restore(); + } + + @override + void dispose() { + signalLinePoints.clear(); + _upperLinePoints.clear(); + _lowerLinePoints.clear(); + _periodLinePoints.clear(); + _upperLineActualValues.clear(); + _lowerLineActualValues.clear(); + _signalLineActualValues.clear(); + _periodLineActualValues.clear(); + _upperLinePath.reset(); + _lowerLinePath.reset(); + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + _periodChartPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart new file mode 100644 index 000000000..965602699 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart @@ -0,0 +1,1186 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart'; +import '../common/legend.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; + +/// Customize the technical indicators. +/// +/// The technical indicator is a mathematical calculation based on historical +/// price, volume or (in the case of futures contracts) open interest +/// information, which is intended to predict the direction of the +/// financial market. +/// +/// Indicators generally overlay the chart data to show the data flow over +/// a period of time. +/// +/// _Note:_ This property is applicable only for financial chart series types. +abstract class TechnicalIndicator { + /// Creating an argument constructor of TechnicalIndicators class. + TechnicalIndicator({ + this.isVisible = true, + this.xAxisName, + this.yAxisName, + this.seriesName, + this.dashArray = const [0, 0], + this.animationDuration = 1500, + this.animationDelay = 0, + this.dataSource, + this.xValueMapper, + this.highValueMapper, + this.lowValueMapper, + this.openValueMapper, + this.closeValueMapper, + this.volumeValueMapper, + this.name, + this.onRenderDetailsUpdate, + this.isVisibleInLegend = true, + this.legendIconType = LegendIconType.seriesType, + this.legendItemText, + this.signalLineColor = Colors.blue, + this.signalLineWidth = 2, + }); + + /// Boolean property to change the visibility of the technical indicators. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// isVisible: false + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isVisible; + + /// Property to map the technical indicators with the axes. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// primaryXAxis: NumericAxis(name: 'xAxis') + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// xAxisName: 'xAxis', + /// showZones : false + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String? xAxisName; + + /// Property to map the technical indicators with the axes. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// primaryYAxis: NumericAxis(name: 'yAxis') + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1', + /// xAxisName: 'yAxis', + /// showZones : false + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String? yAxisName; + + /// Property to link indicators to a series based on names. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// seriesName: 'Series1' + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String? seriesName; + + /// Property to provide dash array for the technical indicators. + /// + /// Defaults to `[0, 0]`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// dashArray: [2, 3] + /// ), + /// ], + /// ); + /// } + /// ``` + final List dashArray; + + /// Animation duration for the technical indicators. + /// + /// Defaults to `1500`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// animationDuration: 1000 + /// ), + /// ], + /// ); + /// } + /// ``` + final double animationDuration; + + /// Delay duration of the technical indicator's animation. + /// It takes a millisecond value as input. + /// By default, the technical indicator will get animated for the specified + /// duration. If animationDelay is specified, then the technical indicator + /// will begin to animate after the specified duration. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// animationDelay: 500 + /// ), + /// ], + /// ); + /// } + /// ``` + final double animationDelay; + + /// Property to provide data for the technical indicators without any series. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// dataSource: chartData, + /// xValueMapper: (ChartData data, _) => data.x, + /// lowValueMapper: (ChartData data, _) => data.low, + /// highValueMapper: (ChartData data, _) => data.high, + /// openValueMapper: (ChartData data, _) => data.open, + /// closeValueMapper: (ChartData data, _) => data.close, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ChartData(1, 23, 50, 28, 38), + /// ChartData(2, 35, 80, 58, 78), + /// ChartData(3, 19, 90, 38, 58) + /// ]; + /// + /// class ChartData { + /// ChartData(this.x, this.low, this.high, this.open, this.close); + /// final double x; + /// final double low; + /// final double high; + /// final double open; + /// final double close; + /// } + /// ``` + final List? dataSource; + + /// Value mapper to map the x values with technical indicators. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// dataSource: chartData, + /// xValueMapper: (ChartData data, _) => data.x, + /// lowValueMapper: (ChartData data, _) => data.low, + /// highValueMapper: (ChartData data, _) => data.high, + /// openValueMapper: (ChartData data, _) => data.open, + /// closeValueMapper: (ChartData data, _) => data.close, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ChartData(1, 23, 50, 28, 38), + /// ChartData(2, 35, 80, 58, 78), + /// ChartData(3, 19, 90, 38, 58) + /// ]; + /// + /// class ChartData { + /// ChartData(this.x, this.low, this.high, this.open, this.close); + /// final double x; + /// final double low; + /// final double high; + /// final double open; + /// final double close; + /// } + /// ``` + final ChartIndexedValueMapper? xValueMapper; + + /// Value mapper to map the high values with technical indicators. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// AccumulationDistributionIndicator( + /// dataSource: chartData, + /// xValueMapper: (ChartData data, _) => data.x, + /// lowValueMapper: (ChartData data, _) => data.low, + /// highValueMapper: (ChartData data, _) => data.high, + /// volumeValueMapper: (ChartData data, _) => data.volume, + /// closeValueMapper: (ChartData data, _) => data.close, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ChartData(1, 23, 50, 28, 38), + /// ChartData(2, 35, 80, 58, 78), + /// ChartData(3, 19, 90, 38, 58) + /// ]; + /// + /// class ChartData { + /// ChartData(this.x, this.low, this.high, this.volume, this.close); + /// final double x; + /// final double low; + /// final double high; + /// final double volume; + /// final double close; + /// } + /// ``` + final ChartIndexedValueMapper? highValueMapper; + + /// Value mapper to map the low values with technical indicators. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// AccumulationDistributionIndicator( + /// dataSource: chartData, + /// xValueMapper: (ChartData data, _) => data.x, + /// lowValueMapper: (ChartData data, _) => data.low, + /// highValueMapper: (ChartData data, _) => data.high, + /// volumeValueMapper: (ChartData data, _) => data.volume, + /// closeValueMapper: (ChartData data, _) => data.close, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ChartData(1, 23, 50, 28, 38), + /// ChartData(2, 35, 80, 58, 78), + /// ChartData(3, 19, 90, 38, 58) + /// ]; + /// + /// class ChartData { + /// ChartData(this.x, this.low, this.high, this.volume, this.close); + /// final double x; + /// final double low; + /// final double high; + /// final double volume; + /// final double close; + /// } + /// ``` + final ChartIndexedValueMapper? lowValueMapper; + + /// Value mapper to map the open values with technical indicators. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// EmaIndicator( + /// dataSource: chartData, + /// xValueMapper: (ChartData data, _) => data.x, + /// lowValueMapper: (ChartData data, _) => data.low, + /// highValueMapper: (ChartData data, _) => data.high, + /// openValueMapper: (ChartData data, _) => data.open, + /// closeValueMapper: (ChartData data, _) => data.close, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ChartData(1, 23, 50, 28, 38), + /// ChartData(2, 35, 80, 58, 78), + /// ChartData(3, 19, 90, 38, 58) + /// ]; + /// + /// class ChartData { + /// ChartData(this.x, this.low, this.high, this.open, this.close); + /// final double x; + /// final double low; + /// final double high; + /// final double open; + /// final double close; + /// } + /// ``` + final ChartIndexedValueMapper? openValueMapper; + + /// Value mapper to map the close values with technical indicators. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// AccumulationDistributionIndicator( + /// dataSource: chartData, + /// xValueMapper: (ChartData data, _) => data.x, + /// lowValueMapper: (ChartData data, _) => data.low, + /// highValueMapper: (ChartData data, _) => data.high, + /// volumeValueMapper: (ChartData data, _) => data.volume, + /// closeValueMapper: (ChartData data, _) => data.close, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ChartData(1, 23, 50, 28, 38), + /// ChartData(2, 35, 80, 58, 78), + /// ChartData(3, 19, 90, 38, 58) + /// ]; + /// + /// class ChartData { + /// ChartData(this.x, this.low, this.high, this.volume, this.close); + /// final double x; + /// final double low; + /// final double high; + /// final double volume; + /// final double close; + /// } + /// ``` + final ChartIndexedValueMapper? closeValueMapper; + + /// Volume of series. + /// + /// This value is mapped to the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// AccumulationDistributionIndicator( + /// seriesName: 'Series1', + /// volumeValueMapper: (dynamic data, _) => data.y, + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// volumeValueMapper: (Sample sales, _) => sales.volume, + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final ChartIndexedValueMapper? volumeValueMapper; + + /// Property to provide name for the technical indicators. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// name: 'indicators' + /// ), + /// ], + /// ); + /// } + /// ``` + final String? name; + + /// Boolean property to determine the rendering of legends for the + /// technical indicators. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// isVisibleInLegend : false + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isVisibleInLegend; + + /// Property to provide icon type for the technical indicators legend. + /// + /// Defaults to `LegendIconType.seriesType`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// legendIconType: LegendIconType.diamond + /// ), + /// ], + /// ); + /// } + /// ``` + final LegendIconType legendIconType; + + /// Property to provide the text for the technical indicators legend. + /// + /// Defaults to 'null'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// legendItemText : 'SMA', + /// ), + /// ], + /// ); + /// } + /// ``` + final String? legendItemText; + + /// Property to provide the color of the signal line in the + /// technical indicators. + /// + /// Defaults to `Colors.blue`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// signalLineColor : Colors.red, + /// ), + /// ], + /// ); + /// } + /// ``` + final Color signalLineColor; + + /// Property to provide the width of the signal line in the + /// technical indicators. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// signalLineWidth : 4.0 + /// ), + /// ], + /// ); + /// } + /// ``` + final double signalLineWidth; + + /// Callback which gets called while rendering the indicators. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// StochasticIndicator( + /// onRenderDetailsUpdate: (IndicatorRenderParams params) { + /// return TechnicalIndicatorRenderDetails(Colors.cyan, 3.0, [5,5]); + /// }, + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartIndicatorRenderCallback? onRenderDetailsUpdate; +} + +abstract class IndicatorWidget extends LeafRenderObjectWidget { + const IndicatorWidget({ + super.key, + required this.vsync, + required this.isTransposed, + required this.indicator, + required this.index, + required this.onLegendTapped, + required this.onLegendItemRender, + }); + + final TickerProvider vsync; + final bool isTransposed; + final TechnicalIndicator indicator; + final int index; + final ChartLegendTapCallback? onLegendTapped; + final ChartLegendRenderCallback? onLegendItemRender; + + @protected + @factory + IndicatorRenderer createRenderer(); + + @override + RenderObject createRenderObject(BuildContext context) { + final IndicatorRenderer renderer = createRenderer(); + return renderer + ..vsync = vsync + ..isTransposed = isTransposed + ..index = index + ..onLegendTapped = onLegendTapped + ..onLegendItemRender = onLegendItemRender + ..isVisible = indicator.isVisible + ..xAxisName = indicator.xAxisName + ..yAxisName = indicator.yAxisName + ..seriesName = indicator.seriesName + ..dashArray = indicator.dashArray + ..animationDuration = indicator.animationDuration + ..animationDelay = indicator.animationDelay + ..dataSource = indicator.dataSource + ..xValueMapper = indicator.xValueMapper + ..name = indicator.name + ..isVisibleInLegend = indicator.isVisibleInLegend + ..legendIconType = indicator.legendIconType + ..legendItemText = indicator.legendItemText + ..signalLineColor = indicator.signalLineColor + ..signalLineWidth = indicator.signalLineWidth + ..onRenderDetailsUpdate = indicator.onRenderDetailsUpdate; + } + + @override + void updateRenderObject( + BuildContext context, + IndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..vsync = vsync + ..isTransposed = isTransposed + ..index = index + ..onLegendTapped = onLegendTapped + ..onLegendItemRender = onLegendItemRender + ..isVisible = indicator.isVisible + ..xAxisName = indicator.xAxisName + ..yAxisName = indicator.yAxisName + ..seriesName = indicator.seriesName + ..dashArray = indicator.dashArray + ..animationDuration = indicator.animationDuration + ..animationDelay = indicator.animationDelay + ..dataSource = indicator.dataSource + ..xValueMapper = indicator.xValueMapper + ..name = indicator.name + ..isVisibleInLegend = indicator.isVisibleInLegend + ..legendIconType = indicator.legendIconType + ..legendItemText = indicator.legendItemText + ..signalLineColor = indicator.signalLineColor + ..signalLineWidth = indicator.signalLineWidth + ..onRenderDetailsUpdate = indicator.onRenderDetailsUpdate; + } +} + +abstract class IndicatorRenderer extends RenderBox + with AxisDependent, LegendItemProviderMixin, ChartAreaUpdateMixin { + List xRawValues = []; + List xValues = []; + List> chartPoints = >[]; + + Path signalPath = Path(); + final List signalLinePoints = []; + + int index = 0; + int dataCount = 0; + + AxisDependent? dependent; + AnimationController? _animationController; + CurvedAnimation? _animation; + + ChartLegendTapCallback? onLegendTapped; + ChartLegendRenderCallback? onLegendItemRender; + ChartIndicatorRenderCallback? onRenderDetailsUpdate; + + final Paint _fillPaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.fill; + final Paint _strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + @override + bool get isRepaintBoundary => true; + + @override + bool get sizedByParent => true; + + @override + RenderIndicatorArea? get parent => super.parent as RenderIndicatorArea?; + + /// The [TickerProvider] for the [AnimationController] that + /// runs the animation. + TickerProvider? get vsync => _vsync; + TickerProvider? _vsync; + set vsync(TickerProvider? value) { + if (_vsync != value) { + _vsync = value; + } + } + + bool get isVisible => _isVisible; + bool _isVisible = true; + set isVisible(bool value) { + if (_isVisible != value) { + _isVisible = value; + effectiveIsVisible = value; + } + } + + bool get effectiveIsVisible => _effectiveIsVisible; + bool _effectiveIsVisible = true; + set effectiveIsVisible(bool value) { + if (_effectiveIsVisible != value) { + _effectiveIsVisible = value; + markNeedsUpdate(); + } + } + + String? get seriesName => _seriesName; + String? _seriesName; + set seriesName(String? value) { + if (_seriesName != value) { + _seriesName = value; + } + } + + List get dashArray => _dashArray; + List _dashArray = [0, 0]; + set dashArray(List value) { + if (_dashArray != value) { + _dashArray = value; + markNeedsPaint(); + } + } + + double get animationDuration => _animationDuration; + double _animationDuration = 0; + set animationDuration(double value) { + if (_animationDuration != value) { + _animationDuration = value; + } + } + + double get animationDelay => _animationDelay; + double _animationDelay = 0.0; + set animationDelay(double value) { + if (_animationDelay != value) { + _animationDelay = value; + } + } + + List? get dataSource => _dataSource; + List? _dataSource; + set dataSource(List? value) { + if (_dataSource != value || _dataSource?.length != value?.length) { + _dataSource = value; + markNeedsUpdate(); + } + } + + ChartIndexedValueMapper? get xValueMapper => _xValueMapper; + ChartIndexedValueMapper? _xValueMapper; + set xValueMapper(ChartIndexedValueMapper? value) { + if (_xValueMapper != value) { + _xValueMapper = value; + } + } + + ChartIndexedValueMapper? get lowValueMapper => _lowValueMapper; + ChartIndexedValueMapper? _lowValueMapper; + set lowValueMapper(ChartIndexedValueMapper? value) { + if (_lowValueMapper != value) { + _lowValueMapper = value; + } + } + + ChartIndexedValueMapper? get highValueMapper => _highValueMapper; + ChartIndexedValueMapper? _highValueMapper; + set highValueMapper(ChartIndexedValueMapper? value) { + if (_highValueMapper != value) { + _highValueMapper = value; + } + } + + ChartIndexedValueMapper? get openValueMapper => _openValueMapper; + ChartIndexedValueMapper? _openValueMapper; + set openValueMapper(ChartIndexedValueMapper? value) { + if (_openValueMapper != value) { + _openValueMapper = value; + } + } + + ChartIndexedValueMapper? get closeValueMapper => _closeValueMapper; + ChartIndexedValueMapper? _closeValueMapper; + set closeValueMapper(ChartIndexedValueMapper? value) { + if (_closeValueMapper != value) { + _closeValueMapper = value; + } + } + + String? get name => _name; + String? _name; + set name(String? value) { + if (_name != value) { + _name = value; + markNeedsLegendUpdate(); + } + } + + bool get isVisibleInLegend => _isVisibleInLegend; + bool _isVisibleInLegend = true; + set isVisibleInLegend(bool value) { + if (_isVisibleInLegend != value) { + _isVisibleInLegend = value; + markNeedsLegendUpdate(); + } + } + + String? get legendItemText => _legendItemText; + String? _legendItemText; + set legendItemText(String? value) { + if (_legendItemText != value) { + _legendItemText = value; + markNeedsLegendUpdate(); + } + } + + LegendIconType get legendIconType => _legendIconType; + LegendIconType _legendIconType = LegendIconType.seriesType; + set legendIconType(LegendIconType value) { + if (_legendIconType != value) { + _legendIconType = value; + markNeedsLegendUpdate(); + } + } + + Color get signalLineColor => _signalLineColor; + Color _signalLineColor = Colors.blue; + set signalLineColor(Color value) { + if (_signalLineColor != value) { + _signalLineColor = value; + markNeedsPaint(); + } + } + + double get signalLineWidth => _signalLineWidth; + double _signalLineWidth = 2.0; + set signalLineWidth(double value) { + if (_signalLineWidth != value) { + _signalLineWidth = value; + markNeedsPaint(); + } + } + + double get animationFactor => _animationFactor; + double _animationFactor = 0.0; + set animationFactor(double value) { + _animationFactor = value; + markNeedsPaint(); + } + + Paint get fillPaint => _fillPaint; + + Paint get strokePaint => _strokePaint; + + void animate() { + if (animationDuration > 0) { + Future.delayed(Duration(milliseconds: animationDelay.toInt()), () { + _animationController?.forward(from: 0); + }); + } else { + _animationFactor = 1.0; + } + } + + void _handleAnimationChange() { + animationFactor = _animation!.value; + } + + @override + void performUpdate() { + if (parent != null && parent is RenderIndicatorArea) { + dependent = parent!.series[seriesName]; + } + + markNeedsPopulateAndLayout(); + } + + void markNeedsPopulateAndLayout() { + if (xAxis == null || yAxis == null) { + return; + } + + populateDataSource(); + markNeedsLayout(); + } + + @protected + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + assert(yPaths != null); + assert(yList != null); + assert(yList != null && yPaths != null && yPaths.length == yList.length); + if (seriesDataSource == null || + seriesDataSource.isEmpty || + xPath == null || + yPaths == null || + yPaths.isEmpty || + yList == null || + yList.isEmpty) { + return; + } + + bool hasYPaths = false; + for (final ChartIndexedValueMapper? yPath in yPaths) { + hasYPaths |= yPath != null; + } + + if (!hasYPaths) { + return; + } + + final int length = seriesDataSource.length; + final int yPathLength = yPaths.length; + + xValues.clear(); + late num currentX; + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(D? value, num x) addXValue = _addRawAndPreferredXValue; + + for (int i = 0; i < length; i++) { + final D? rawX = xPath(i); + if (rawX == null) { + continue; + } + + currentX = preferredXValue(i, rawX); + addXValue(rawX, currentX); + for (int j = 0; j < yPathLength; j++) { + final ChartIndexedValueMapper? yPath = yPaths[j]; + if (yPath == null) { + continue; + } + + yList[j]!.add(yPath(i) ?? double.nan); + } + } + + dataCount = xValues.length; + } + + Function(int, D) _preferredXValue() { + if (xAxis is RenderNumericAxis || xAxis is RenderLogarithmicAxis) { + return _valueAsNum; + } else if (xAxis is RenderDateTimeAxis) { + return _dateToMilliseconds; + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + return _valueToIndex; + } + return _valueAsNum; + } + + num _valueAsNum(int index, D value) { + return value as num; + } + + num _dateToMilliseconds(int index, D value) { + final DateTime date = value as DateTime; + return date.millisecondsSinceEpoch; + } + + num _valueToIndex(int index, D value) { + return index; + } + + void _addRawAndPreferredXValue(D? raw, num preferred) { + xRawValues.add(raw); + xValues.add(preferred); + } + + /// Method excepts [BoxAndWhiskerSeries], and stacking series. + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + chartPoints.clear(); + if (parent == null || yLists == null || yLists.isEmpty) { + return; + } + + if (onRenderDetailsUpdate == null) { + return; + } + + final int yLength = yLists.length; + if (positions == null || positions.length != yLength) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final num xValue = xValues[i]; + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: xValue, + ); + for (int j = 0; j < yLength; j++) { + point[positions[j]] = yLists[j][i]; + } + chartPoints.add(point); + } + } + + @override + DoubleRange range(RenderChartAxis axis) { + if (axis == xAxis) { + return xRange; + } else { + return yRange; + } + } + + @override + void didRangeChange(RenderChartAxis axis) {} + + @override + void attach(PipelineOwner owner) { + if (vsync != null && _animationController == null) { + CurvedAnimation? dependentAnimation; + if (dependent is CartesianSeriesRenderer) { + dependentAnimation = (dependent! as CartesianSeriesRenderer).animation; + } + _animationController ??= AnimationController( + vsync: vsync!, + duration: Duration(milliseconds: animationDuration.toInt()), + value: animationDuration > 0 ? 0 : 1, + ); + _animation ??= CurvedAnimation( + parent: _animationController!, + curve: dependentAnimation?.curve ?? const Interval(0.1, 1.0), + ); + _animation!.addListener(_handleAnimationChange); + animate(); + } + + super.attach(owner); + } + + @override + void detach() { + _animationController?.dispose(); + _animationController = null; + _animation + ?..removeListener(_handleAnimationChange) + ..dispose(); + _animation = null; + super.detach(); + } + + @protected + String defaultLegendItemText() => 'Indicator'; + + @protected + Color effectiveLegendIconColor() { + return Colors.transparent; + } + + @override + ShapeMarkerType effectiveLegendIconType() { + return ShapeMarkerType.horizontalLine; + } + + @override + List? buildLegendItems(int index) { + if (parent != null && isVisibleInLegend) { + return [ + CartesianLegendItem( + text: legendItemText ?? name ?? defaultLegendItemText(), + iconType: toLegendShapeMarkerType(legendIconType, this), + iconColor: effectiveLegendIconColor(), + iconBorderWidth: 2, + seriesIndex: index, + isToggled: !effectiveIsVisible, + onTap: _handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ), + ]; + } else { + return null; + } + } + + void _handleLegendItemTapped(LegendItem item, bool isToggled) { + if (onLegendTapped != null) { + final CartesianLegendItem legendItem = item as CartesianLegendItem; + final LegendTapArgs args = LegendTapArgs( + legendItem.series, + legendItem.seriesIndex, + ); + onLegendTapped!(args); + } + effectiveIsVisible = !isToggled; + item.onToggled?.call(); + } + + void _handleLegendItemCreated(ItemRendererDetails details) { + if (onLegendItemRender != null) { + final CartesianLegendItem item = details.item as CartesianLegendItem; + final LegendIconType iconType = toLegendIconType(details.iconType); + final LegendRenderArgs args = + LegendRenderArgs(item.seriesIndex, item.pointIndex) + ..text = details.text + ..legendIconType = iconType + ..color = details.color; + onLegendItemRender!(args); + if (args.legendIconType != iconType) { + details.iconType = toLegendShapeMarkerType( + args.legendIconType ?? LegendIconType.seriesType, + this, + ); + } + + details + ..text = args.text ?? '' + ..color = args.color ?? Colors.transparent; + } + } + + @override + void performResize() { + size = constraints.biggest; + } + + @override + void performLayout() { + if (effectiveIsVisible) { + transformValues(); + customizeIndicator(); + } + } + + List? trackballInfo(Offset position) => null; + + @protected + void transformValues(); + + @nonVirtual + double pointToPixelX(num x, num y) { + return isTransposed ? yAxis!.pointToPixel(y) : xAxis!.pointToPixel(x); + } + + @nonVirtual + double pointToPixelY(num x, num y) { + return isTransposed ? xAxis!.pointToPixel(x) : yAxis!.pointToPixel(y); + } + + @protected + void customizeIndicator(); + + @override + @nonVirtual + void paint(PaintingContext context, Offset offset) { + if (effectiveIsVisible) { + onPaint(context, offset); + } + } + + @protected + void onPaint(PaintingContext context, Offset offset); + + @override + void dispose() { + _animationController?.dispose(); + _animationController = null; + _animation + ?..removeListener(_handleAnimationChange) + ..dispose(); + _animation = null; + signalLinePoints.clear(); + signalPath.reset(); + chartPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart new file mode 100644 index 000000000..8fb289059 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart @@ -0,0 +1,558 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders Triangular Moving Average (TMA) indicator. +/// +/// The Triangular Moving Average (TMA) is a technical indicator similar to +/// other moving averages. The TMA shows the average (or average) price of an +/// asset over a specified number of data points over a period of time. +/// +/// The technical indicator is rendered on the basis of the +/// [valueField] property. +@immutable +class TmaIndicator extends TechnicalIndicator { + /// Creating an argument constructor of TmaIndicator class. + TmaIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.valueField = 'close', + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// ValueField value for tma indicator. + /// + /// Value field determines the field for rendering the indicators. + /// + /// Defaults to `close`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// TmaIndicator( + /// seriesName: 'Series1' + /// period: 4, + /// valueField: 'low' + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String valueField; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// TmaIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is TmaIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.valueField == valueField; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + valueField, + period, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'TMA'; +} + +class TmaIndicatorWidget extends IndicatorWidget { + const TmaIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the TmaIndicatorRenderer renderer. + @override + TmaIndicatorRenderer createRenderer() { + return TmaIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final TmaIndicatorRenderer renderer = + super.createRenderObject(context) as TmaIndicatorRenderer; + final TmaIndicator tma = indicator as TmaIndicator; + + renderer + ..highValueMapper = tma.highValueMapper + ..lowValueMapper = tma.lowValueMapper + ..openValueMapper = tma.openValueMapper + ..closeValueMapper = tma.closeValueMapper + ..valueField = tma.valueField + ..period = tma.period; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + TmaIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final TmaIndicator tma = indicator as TmaIndicator; + + renderObject + ..highValueMapper = tma.highValueMapper + ..lowValueMapper = tma.lowValueMapper + ..openValueMapper = tma.openValueMapper + ..closeValueMapper = tma.closeValueMapper + ..valueField = tma.valueField + ..period = tma.period; + } +} + +class TmaIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _yValues = []; + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + String get valueField => _valueField; + String _valueField = 'Close'; + set valueField(String value) { + if (_valueField != value) { + _valueField = value; + markNeedsPopulateAndLayout(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _signalLineActualValues.clear(); + _yValues.clear(); + if (dataCount >= period && period > 0 && signalLineWidth > 0) { + _calculateSignalLineValues(); + } + + populateChartPoints(); + } + + void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + num sum = 0; + int l = 0; + List smaValues = []; + int length = dataCount; + + while (length >= period) { + sum = 0; + l = dataCount - length; + for (int i = l; i < l + period; i++) { + sum += _fieldValue(i, valueField); + } + sum = sum / period; + _yValues.add(sum); + smaValues.add(sum); + length--; + } + + for (int j = 0; j < period - 1; j++) { + sum = 0; + for (int k = 0; k < j + 1; k++) { + sum += _fieldValue(k, valueField); + } + sum = sum / (j + 1); + _yValues.add(sum); + smaValues = _splice(smaValues, j, sum); + } + + l = period; + while (l <= smaValues.length) { + sum = 0; + for (int j = l - period; j < l; j++) { + sum = sum + smaValues[j]; + } + sum = sum / period; + _yValues.add(sum); + + final double x = xValues[l - 1].toDouble(); + final double y = sum.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + + l++; + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + + List _splice(List list, int index, num? elements) { + if (elements != null) { + list.insertAll(index, [elements]); + } + return list; + } + + num _fieldValue(int index, String valueField) { + num value; + if (valueField == 'low') { + value = _lowValues[index]; + } else if (valueField == 'high') { + value = _highValues[index]; + } else if (valueField == 'open') { + value = _openValues[index]; + } else if (valueField == 'y') { + value = _yValues[index]; + } else { + value = _closeValues[index]; + } + return value.isNaN ? 0 : value; + } + + @override + List? trackballInfo(Offset position) { + final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); + if (nearestPointIndex != -1) { + final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); + return >[ + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: chartPoint, + series: this, + pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), + color: signalLineColor, + ), + ]; + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: xRawValues[pointIndex + period - 1], + xValue: xValues[pointIndex + period - 1], + y: _signalLineActualValues[pointIndex].dy, + ); + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'TMA'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + if (signalLinePoints.isNotEmpty) { + final int length = signalLinePoints.length - 1; + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + + context.canvas.restore(); + } + } + } + + @override + void dispose() { + _signalLineActualValues.clear(); + _yValues.clear(); + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/wma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/wma_indicator.dart new file mode 100644 index 000000000..905427f60 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/wma_indicator.dart @@ -0,0 +1,658 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'technical_indicator.dart'; + +/// Renders weighted moving average (WMA) indicator. +/// +/// A weighted moving average (WMA) is an arithmetic moving average calculated by +/// adding recent closing prices and then dividing the total by the number of +/// time periods in the calculation average. +/// +/// It also has a [valueField] property. Based on this property, the indicator +/// will be rendered. +/// +/// The indicator elements are: +/// +/// * The [dataSource], which is used provide data for the technical indicators without any series. +/// * The [xValueMapper], which is a value mapper to map the x values with the technical indicator. +/// * The [highValueMapper], which is a value mapper to map the high values with the technical indicator. +/// * The [lowValueMapper], which is a value mapper to map the low values with the technical indicator. +/// * The [openValueMapper], which is a value mapper to map the open values with the technical indicator. +/// * The [closeValueMapper], which is a value mapper to map the close values with the technical indicator. +/// * The [xAxisName] and [yAxisName], which is used map the technical indicator with the multiple axes. +/// * The [seriesName], which is used map the technical indicator with the series based on names. +/// * The [valueField], which is used to determines the field for the rendering of WMA indicator. +/// * The [period], which is used determines the start point for the rendering of technical indicator. +/// +/// ## Formula +/// +/// Data (Closing Prices for Each Day): +/// +/// * Day 1: $100 +/// * Day 2: $105 +/// * Day 3: $110 +/// * Day 4: $120 +/// * Day 5: $103 +/// * ... +/// * Day 15: $110 +/// * Day 16: $108 +/// * … +/// * Day 29: $110 +/// * Day 30: $120 +/// +/// +/// Calculation of WMA: +/// * WMA for single data = currentPrice * (n/ sum of period); +/// * The equation we use for weighing each number is the day number divided by the sum of +/// all day numbers. Since we are looking at five days, the sum of all day numbers in this example +/// is 15 (i.e., 5 + 4 + 3 + 2 + 1). +/// +/// * WMA on Period 3: +/// * If we need to calculate the 3rd day WMA means, current value is 110, period 3 days sum is 6 (3+2+1), then +/// * 100 * (1/6) = 6.25 +/// * 105 * (2/6) = 17.5 +/// * 110 * (3/6) = 18.33 +/// +/// * WMA on Period 16: +/// * If we need to calculate the 16th day WMA means, current value is 108, previous 16-day sum is +/// 136 (16+15+14+13+...+1). +/// * Day1: 100 * (1/136) = 0.7 +/// * .... +/// * Day 16: 108 * (16/136) = 12.070 +/// +/// ## Example +/// +/// This snippet shows how to create a [WmaIndicator] by mapping the series data source. +/// +/// ```dart +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: Center( +/// child: SfCartesianChart( +/// primaryXAxis: const DateTimeAxis(), +/// primaryYAxis: const NumericAxis(), +/// axes: const [ +/// NumericAxis( +/// majorGridLines: MajorGridLines(width: 0), +/// opposedPosition: true, +/// name: 'yAxis', +/// ), +/// ], +/// indicators: >[ +/// WmaIndicator( +/// seriesName: 'AAPL', +/// yAxisName: 'yAxis', +/// period: 15, +/// ), +/// ], +/// series: >[ +/// HiloOpenCloseSeries( +/// dataSource: getChartData(), +/// xValueMapper: (ChartSampleData sales, _) => sales.x as DateTime, +/// lowValueMapper: (ChartSampleData sales, _) => sales.low, +/// highValueMapper: (ChartSampleData sales, _) => sales.high, +/// openValueMapper: (ChartSampleData sales, _) => sales.open, +/// closeValueMapper: (ChartSampleData sales, _) => sales.close, +/// name: 'AAPL', +/// ), +/// ], +/// ), +/// ), +/// ), +/// ); +/// } +/// ``` +/// This snippet shows how to create a [WmaIndicator] using a direct data source. +/// +/// ```dart +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// body: Center( +/// child: SfCartesianChart( +/// primaryXAxis: const DateTimeAxis(), +/// primaryYAxis: const NumericAxis(), +/// axes: const [ +/// NumericAxis( +/// majorGridLines: MajorGridLines(width: 0), +/// opposedPosition: true, +/// name: 'yAxis', +/// ), +/// ], +/// indicators: >[ +/// WmaIndicator( +/// dataSource: getChartData(), +/// xValueMapper: (ChartSampleData sales, _) => sales.x as DateTime, +/// lowValueMapper: (ChartSampleData sales, _) => sales.low, +/// highValueMapper: (ChartSampleData sales, _) => sales.high, +/// openValueMapper: (ChartSampleData sales, _) => sales.open, +/// closeValueMapper: (ChartSampleData sales, _) => sales.close, +/// yAxisName: 'yAxis', +/// period: 15, +/// ), +/// ], +/// ), +/// ), +/// ), +/// ); +/// } +/// ``` +/// +@immutable +class WmaIndicator extends TechnicalIndicator { + /// Creating an argument constructor of WmaIndicator class. + WmaIndicator({ + super.isVisible, + super.xAxisName, + super.yAxisName, + super.seriesName, + super.dashArray, + super.animationDuration, + super.animationDelay, + super.dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + super.name, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.signalLineColor, + super.signalLineWidth, + this.period = 14, + this.valueField = 'close', + super.onRenderDetailsUpdate, + }) : super( + xValueMapper: + xValueMapper != null && dataSource != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + highValueMapper: + highValueMapper != null && dataSource != null + ? (int index) => highValueMapper(dataSource[index], index) + : null, + lowValueMapper: + lowValueMapper != null && dataSource != null + ? (int index) => lowValueMapper(dataSource[index], index) + : null, + openValueMapper: + openValueMapper != null && dataSource != null + ? (int index) => openValueMapper(dataSource[index], index) + : null, + closeValueMapper: + closeValueMapper != null && dataSource != null + ? (int index) => closeValueMapper(dataSource[index], index) + : null, + ); + + /// Value field value for WMA indicator. + /// + /// Value field determines the field for the rendering of WMA indicator. + /// + /// Defaults to `close`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// WmaIndicator( + /// seriesName: 'Series1' + /// period: 4, + /// valueField: 'low' + /// ), + /// ], + /// series: >[ + /// HiloOpenCloseSeries( + /// name: 'Series1' + /// ) + /// ] + /// ); + /// } + /// ``` + final String valueField; + + /// Period determines the start point for the rendering of + /// technical indicators. + /// + /// Defaults to `14`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// indicators: >[ + /// WmaIndicator( + /// period : 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final int period; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is WmaIndicator && + other.isVisible == isVisible && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.seriesName == seriesName && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.animationDelay == animationDelay && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.highValueMapper == highValueMapper && + other.lowValueMapper == lowValueMapper && + other.openValueMapper == openValueMapper && + other.closeValueMapper == closeValueMapper && + other.period == period && + other.name == name && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.signalLineColor == signalLineColor && + other.signalLineWidth == signalLineWidth && + other.valueField == valueField; + } + + @override + int get hashCode { + final List values = [ + isVisible, + xAxisName, + yAxisName, + seriesName, + dashArray, + animationDuration, + animationDelay, + dataSource, + xValueMapper, + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + name, + isVisibleInLegend, + legendIconType, + legendItemText, + signalLineColor, + signalLineWidth, + valueField, + period, + ]; + return Object.hashAll(values); + } + + @override + String toString() => 'WMA'; +} + +class WmaIndicatorWidget extends IndicatorWidget { + const WmaIndicatorWidget({ + super.key, + required super.vsync, + required super.isTransposed, + required super.indicator, + required super.index, + super.onLegendTapped, + super.onLegendItemRender, + }); + + // Create the WmaIndicatorRenderer renderer. + @override + WmaIndicatorRenderer createRenderer() { + return WmaIndicatorRenderer(); + } + + @override + RenderObject createRenderObject(BuildContext context) { + final WmaIndicatorRenderer renderer = + super.createRenderObject(context) as WmaIndicatorRenderer; + final WmaIndicator wma = indicator as WmaIndicator; + return renderer + ..highValueMapper = wma.highValueMapper + ..lowValueMapper = wma.lowValueMapper + ..openValueMapper = wma.openValueMapper + ..closeValueMapper = wma.closeValueMapper + ..valueField = wma.valueField + ..period = wma.period; + } + + @override + void updateRenderObject( + BuildContext context, + WmaIndicatorRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + final WmaIndicator wma = indicator as WmaIndicator; + renderObject + ..highValueMapper = wma.highValueMapper + ..lowValueMapper = wma.lowValueMapper + ..openValueMapper = wma.openValueMapper + ..closeValueMapper = wma.closeValueMapper + ..valueField = wma.valueField + ..period = wma.period; + } +} + +class WmaIndicatorRenderer extends IndicatorRenderer { + late List? _dashArray; + + final List _signalLineActualValues = []; + final List _yValues = []; + List _highValues = []; + List _lowValues = []; + List _openValues = []; + List _closeValues = []; + + String get valueField => _valueField; + String _valueField = 'Close'; + set valueField(String value) { + if (_valueField != value) { + _valueField = value; + markNeedsPopulateAndLayout(); + } + } + + int get period => _period; + int _period = 14; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsPopulateAndLayout(); + } + } + + @override + void populateDataSource([ + List? seriesDataSource, + ChartIndexedValueMapper? xPath, + List? xList, + List?>? yPaths, + List?>? yList, + ]) { + if (dataSource != null) { + super.populateDataSource( + dataSource, + xValueMapper, + xValues, + ?>[ + highValueMapper, + lowValueMapper, + openValueMapper, + closeValueMapper, + ], + >[_highValues, _lowValues, _openValues, _closeValues], + ); + } else { + if (seriesName != null) { + if (dependent is FinancialSeriesRendererBase) { + final FinancialSeriesRendererBase series = + dependent! as FinancialSeriesRendererBase; + seriesDataSource = series.dataSource; + xValues = series.xValues; + xRawValues = series.xRawValues; + _highValues = series.highValues; + _lowValues = series.lowValues; + _openValues = series.openValues; + _closeValues = series.closeValues; + dataCount = xValues.length; + } + } + } + + _calculateSignalLineValues(); + populateChartPoints(); + } + + void _calculateSignalLineValues() { + _signalLineActualValues.clear(); + _yValues.clear(); + + if (xValues.isNotEmpty && + dataCount >= period && + period > 0 && + signalLineWidth > 0) { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + // Calculate the total sum of periods. + int sumOfPeriods = 0; + for (int i = 0; i <= period; i++) { + sumOfPeriods += i; + } + + for (int i = period - 1; i < dataCount; i++) { + double weightedSum = 0.0; + for (int j = 0; j < period; j++) { + weightedSum += _fieldValue(i, valueField) * (j + 1); + } + + final double x = xValues[i].toDouble(); + final double y = weightedSum / sumOfPeriods; + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); + yMinimum = min(yMinimum, y); + yMaximum = max(yMaximum, y); + _signalLineActualValues.add(Offset(x, y)); + } + + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; + yMin = yMinimum.isInfinite ? yMin : yMinimum; + yMax = yMaximum.isInfinite ? yMax : yMaximum; + } + } + + num _fieldValue(int index, String valueField) { + num? value; + if (valueField == 'low') { + value = _lowValues[index]; + } else if (valueField == 'high') { + value = _highValues[index]; + } else if (valueField == 'open') { + value = _openValues[index]; + } else if (valueField == 'y') { + value = _yValues[index]; + } else { + value = _closeValues[index]; + } + return value.isNaN ? 0 : value; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + yLists = >[_highValues, _lowValues, _openValues, _closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + String defaultLegendItemText() => 'WMA'; + + @override + Color effectiveLegendIconColor() => signalLineColor; + + @override + void transformValues() { + signalLinePoints.clear(); + if (_signalLineActualValues.isNotEmpty) { + final int length = _signalLineActualValues.length; + for (int i = 0; i < length; i++) { + final num x = _signalLineActualValues[i].dx; + final num y = _signalLineActualValues[i].dy; + signalLinePoints.add(Offset(pointToPixelX(x, y), pointToPixelY(x, y))); + } + } + } + + @override + List? trackballInfo(Offset position) { + final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); + if (nearestPointIndex != -1) { + final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); + return >[ + ChartTrackballInfo( + position: signalLinePoints[nearestPointIndex], + point: chartPoint, + series: this, + pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, + seriesIndex: index, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), + color: signalLineColor, + ), + ]; + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= points[0].dx; + nearPointY ??= yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + if (isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: xRawValues[pointIndex + period - 1], + xValue: xValues[pointIndex + period - 1], + y: _signalLineActualValues[pointIndex].dy, + ); + } + + @override + void customizeIndicator() { + if (onRenderDetailsUpdate != null) { + final IndicatorRenderParams params = IndicatorRenderParams( + chartPoints, + legendItemText ?? name ?? defaultLegendItemText(), + signalLineWidth, + signalLineColor, + dashArray, + ); + final TechnicalIndicatorRenderDetails details = onRenderDetailsUpdate!( + params, + ); + strokePaint + ..color = details.signalLineColor + ..strokeWidth = details.signalLineWidth; + _dashArray = details.signalLineDashArray; + } else { + strokePaint + ..color = signalLineColor + ..strokeWidth = signalLineWidth; + _dashArray = dashArray; + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + + if (signalLinePoints.isNotEmpty) { + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + final int length = signalLinePoints.length - 1; + for (int i = 0; i < length; i++) { + drawDashes( + context.canvas, + _dashArray, + strokePaint, + start: signalLinePoints[i], + end: signalLinePoints[i + 1], + ); + } + } + } + + context.canvas.restore(); + } + + @override + void dispose() { + _highValues.clear(); + _lowValues.clear(); + _openValues.clear(); + _closeValues.clear(); + _yValues.clear(); + _signalLineActualValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart new file mode 100644 index 000000000..be62bd8d7 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart @@ -0,0 +1,626 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../axis/axis.dart'; +import '../base.dart'; +import '../behaviors/crosshair.dart'; +import '../behaviors/trackball.dart'; +import '../behaviors/zooming.dart'; +import '../common/callbacks.dart'; +import '../common/core_tooltip.dart'; +import '../series/chart_series.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/typedef.dart'; +import 'tooltip.dart'; + +class BehaviorArea extends MultiChildRenderObjectWidget { + const BehaviorArea({ + super.key, + required this.tooltipKey, + this.primaryXAxisName = primaryXAxisDefaultName, + this.primaryYAxisName = primaryYAxisDefaultName, + this.isTransposed = false, + this.tooltipBehavior, + this.crosshairBehavior, + this.zoomPanBehavior, + this.trackballBehavior, + this.onZooming, + this.onZoomStart, + this.onZoomEnd, + this.onZoomReset, + this.onTrackballPositionChanging, + this.onCrosshairPositionChanging, + this.onTooltipRender, + required this.chartThemeData, + required this.themeData, + super.children, + this.trackballBuilder, + this.textDirection, + }); + + final GlobalKey? tooltipKey; + final String primaryXAxisName; + final String primaryYAxisName; + final bool isTransposed; + final TooltipBehavior? tooltipBehavior; + final CrosshairBehavior? crosshairBehavior; + final ZoomPanBehavior? zoomPanBehavior; + final TrackballBehavior? trackballBehavior; + final SfChartThemeData chartThemeData; + final ThemeData? themeData; + final ChartZoomingCallback? onZooming; + final ChartZoomingCallback? onZoomStart; + final ChartZoomingCallback? onZoomEnd; + final ChartZoomingCallback? onZoomReset; + final ChartTrackballCallback? onTrackballPositionChanging; + final ChartCrosshairCallback? onCrosshairPositionChanging; + final ChartTooltipCallback? onTooltipRender; + final Function(List details)? trackballBuilder; + final TextDirection? textDirection; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderBehaviorArea() + .._tooltipKey = tooltipKey + ..primaryXAxisName = primaryXAxisName + ..primaryYAxisName = primaryYAxisName + ..isTransposed = isTransposed + ..tooltipBehavior = tooltipBehavior + ..crosshairBehavior = crosshairBehavior + ..zoomPanBehavior = zoomPanBehavior + ..trackballBehavior = trackballBehavior + ..onZooming = onZooming + ..onZoomStart = onZoomStart + ..onZoomEnd = onZoomEnd + ..onZoomReset = onZoomReset + ..onTrackballPositionChanging = onTrackballPositionChanging + ..onCrosshairPositionChanging = onCrosshairPositionChanging + ..trackballBuilder = trackballBuilder + ..onTooltipRender = onTooltipRender + ..chartThemeData = chartThemeData + ..themeData = themeData + ..textDirection = textDirection; + } + + @override + void updateRenderObject( + BuildContext context, + RenderBehaviorArea renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + .._tooltipKey = tooltipKey + ..primaryXAxisName = primaryXAxisName + ..primaryYAxisName = primaryYAxisName + ..isTransposed = isTransposed + ..tooltipBehavior = tooltipBehavior + ..crosshairBehavior = crosshairBehavior + ..zoomPanBehavior = zoomPanBehavior + ..trackballBehavior = trackballBehavior + ..onZooming = onZooming + ..onZoomStart = onZoomStart + ..onZoomEnd = onZoomEnd + ..onZoomReset = onZoomReset + ..onTrackballPositionChanging = onTrackballPositionChanging + ..onCrosshairPositionChanging = onCrosshairPositionChanging + ..trackballBuilder = trackballBuilder + ..onTooltipRender = onTooltipRender + ..chartThemeData = chartThemeData + ..themeData = themeData + ..textDirection = textDirection; + } +} + +class RenderBehaviorArea extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin, + ChartAreaUpdateMixin { + GlobalKey? _tooltipKey; + + bool _crosshairEnabled = false; + bool _trackballEnabled = false; + bool _zoomingEnabled = false; + bool performZoomThroughTouch = false; + TooltipInfo? _previousTooltipInfo; + + RenderCartesianAxes? cartesianAxes; + RenderIndicatorArea? indicatorArea; + RenderChartPlotArea? plotArea; + RenderLoadingIndicator? _loadingIndicator; + TooltipOpacityRenderBox? _tooltip; + TrackballOpacityRenderBox? _trackball; + + ChartZoomingCallback? onZooming; + ChartZoomingCallback? onZoomStart; + ChartZoomingCallback? onZoomEnd; + ChartZoomingCallback? onZoomReset; + TextDirection? textDirection; + + Function(List)? trackballBuilder; + ChartTrackballCallback? onTrackballPositionChanging; + ChartCrosshairCallback? onCrosshairPositionChanging; + ChartTooltipCallback? onTooltipRender; + + String get primaryXAxisName => _primaryXAxisName; + String _primaryXAxisName = primaryXAxisDefaultName; + set primaryXAxisName(String value) { + if (_primaryXAxisName != value) { + _primaryXAxisName = value; + markNeedsPaint(); + } + } + + String get primaryYAxisName => _primaryYAxisName; + String _primaryYAxisName = primaryYAxisDefaultName; + set primaryYAxisName(String value) { + if (_primaryYAxisName != value) { + _primaryYAxisName = value; + markNeedsPaint(); + } + } + + bool get isTransposed => _isTransposed; + bool _isTransposed = false; + set isTransposed(bool value) { + if (_isTransposed != value) { + _isTransposed = value; + markNeedsPaint(); + } + } + + TooltipBehavior? get tooltipBehavior => _tooltipBehavior; + TooltipBehavior? _tooltipBehavior; + set tooltipBehavior(TooltipBehavior? value) { + if (_tooltipBehavior != value) { + if (value != null) { + value.parentBox = this; + } + _tooltipBehavior = value; + } + } + + CrosshairBehavior? get crosshairBehavior => _crosshairBehavior; + CrosshairBehavior? _crosshairBehavior; + set crosshairBehavior(CrosshairBehavior? value) { + if (_crosshairBehavior != value) { + if (value != null) { + value.parentBox = this; + } + _crosshairBehavior = value; + _crosshairEnabled = value != null && value.enable; + } + } + + ZoomPanBehavior? get zoomPanBehavior => _zoomPanBehavior; + ZoomPanBehavior? _zoomPanBehavior; + set zoomPanBehavior(ZoomPanBehavior? value) { + if (_zoomPanBehavior != value) { + if (value != null) { + value.parentBox = this; + } + _zoomPanBehavior = value; + _zoomingEnabled = + value != null && + (value.enablePinching || + value.enablePanning || + value.enableMouseWheelZooming || + value.enableDoubleTapZooming || + value.enableSelectionZooming); + performZoomThroughTouch = + value != null && + (value.enablePinching || value.enableSelectionZooming); + } + } + + TrackballBehavior? get trackballBehavior => _trackballBehavior; + TrackballBehavior? _trackballBehavior; + set trackballBehavior(TrackballBehavior? value) { + if (_trackballBehavior != value) { + if (value != null) { + value.parentBox = this; + } + _trackballBehavior = value; + _trackballEnabled = value != null && value.enable; + } + } + + SfChartThemeData? get chartThemeData => _chartThemeData; + SfChartThemeData? _chartThemeData; + set chartThemeData(SfChartThemeData? value) { + _chartThemeData = value; + markNeedsPaint(); + } + + ThemeData? get themeData => _themeData; + ThemeData? _themeData; + set themeData(ThemeData? value) { + _themeData = value; + markNeedsPaint(); + } + + ZoomMode get directionalZoomMode => _directionalZoomMode; + ZoomMode _directionalZoomMode = ZoomMode.xy; + set directionalZoomMode(ZoomMode value) { + if (_directionalZoomMode != value) { + _directionalZoomMode = value; + } + } + + ZoomMode get effectiveZoomMode { + ZoomMode zoomMode = ZoomMode.xy; + if (zoomPanBehavior != null) { + zoomMode = + zoomPanBehavior!.enableDirectionalZooming && + zoomPanBehavior!.enablePinching && + zoomPanBehavior!.zoomMode == ZoomMode.xy + ? directionalZoomMode + : zoomPanBehavior!.zoomMode; + } + return zoomMode; + } + + RenderChartAxis? get xAxis => cartesianAxes?.axes[primaryXAxisName]; + + RenderChartAxis? get yAxis => cartesianAxes?.axes[primaryYAxisName]; + + @override + bool get isRepaintBoundary => true; + + @override + void attach(PipelineOwner owner) { + crosshairBehavior?.parentBox = this; + trackballBehavior?.parentBox = this; + zoomPanBehavior?.parentBox = this; + tooltipBehavior?.parentBox = this; + + plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child.selectionBehavior?.parentBox = this; + } + }); + super.attach(owner); + } + + @override + void detach() { + crosshairBehavior?.parentBox = null; + trackballBehavior?.parentBox = null; + zoomPanBehavior?.parentBox = null; + tooltipBehavior?.parentBox = null; + + plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child.selectionBehavior?.parentBox = null; + } + }); + super.detach(); + } + + @override + void insert(RenderBox child, {RenderBox? after}) { + super.insert(child, after: after); + if (child is RenderLoadingIndicator) { + _loadingIndicator = child; + } + if (child is TooltipOpacityRenderBox) { + _tooltip = child; + } + if (child is TrackballOpacityRenderBox) { + _trackball = child; + } + } + + @override + void remove(RenderBox child) { + super.remove(child); + if (child is RenderLoadingIndicator) { + _loadingIndicator = null; + } + if (child is TooltipOpacityRenderBox) { + _tooltip = null; + } + if (child is TrackballOpacityRenderBox) { + _trackball = null; + } + } + + @override + void setupParentData(RenderObject child) { + if (child is! StackParentData) { + child.parentData = StackParentData(); + } + } + + RenderChartAxis? axisFromName(String? name) { + return cartesianAxes?.axes[name]; + } + + RenderChartAxis? axisFromObject(ChartAxis axis) { + RenderChartAxis? renderChartAxis; + cartesianAxes?.axes.forEach((String? key, RenderChartAxis value) { + if (value.widget == axis) { + renderChartAxis = value; + } + }); + + return renderChartAxis; + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + bool isLoadingIndicatorHit = false; + bool isTooltipHit = false; + bool isTrackballHit = false; + if (size.contains(position)) { + if (_loadingIndicator != null) { + final StackParentData childParentData = + _loadingIndicator!.parentData! as StackParentData; + isLoadingIndicatorHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return _loadingIndicator!.hitTest(result, position: transformed); + }, + ); + } + + if (_tooltip != null) { + final StackParentData childParentData = + _tooltip!.parentData! as StackParentData; + isTooltipHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return _tooltip!.hitTest(result, position: transformed); + }, + ); + } + + if (_trackball != null) { + final StackParentData childParentData = + _trackball!.parentData! as StackParentData; + isTrackballHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return _trackball!.hitTest(result, position: transformed); + }, + ); + } + + return isLoadingIndicatorHit || + isTooltipHit || + isTrackballHit || + _crosshairEnabled || + _trackballEnabled || + _zoomingEnabled; + } + return false; + } + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + crosshairBehavior?.handleEvent(event, entry); + trackballBehavior?.handleEvent(event, entry); + zoomPanBehavior?.handleEvent(event, entry); + + if (event is PointerDownEvent) { + _loadingIndicator?.handlePointerDown(event); + } + } + + void handlePointerEnter(PointerEnterEvent details) { + crosshairBehavior?.handlePointerEnter(details); + trackballBehavior?.handlePointerEnter(details); + } + + void handlePointerExit(PointerExitEvent details) { + crosshairBehavior?.handlePointerExit(details); + trackballBehavior?.handlePointerExit(details); + } + + void handleLongPressStart(LongPressStartDetails details) { + crosshairBehavior?.handleLongPressStart(details); + trackballBehavior?.handleLongPressStart(details); + zoomPanBehavior?.handleLongPressStart(details); + } + + void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + crosshairBehavior?.handleLongPressMoveUpdate(details); + trackballBehavior?.handleLongPressMoveUpdate(details); + zoomPanBehavior?.handleLongPressMoveUpdate(details); + } + + void handleLongPressEnd(LongPressEndDetails details) { + crosshairBehavior?.handleLongPressEnd(details); + trackballBehavior?.handleLongPressEnd(details); + zoomPanBehavior?.handleLongPressEnd(details); + } + + void handleTapDown(TapDownDetails details) { + crosshairBehavior?.handleTapDown(details); + trackballBehavior?.handleTapDown(details); + } + + void handleTapUp(TapUpDetails details) { + crosshairBehavior?.handleTapUp(details); + trackballBehavior?.handleTapUp(details); + } + + void handleDoubleTap(Offset position) { + crosshairBehavior?.handleDoubleTap(position); + trackballBehavior?.handleDoubleTap(position); + zoomPanBehavior?.handleDoubleTap(position); + } + + void handleScaleStart(ScaleStartDetails details) { + zoomPanBehavior?.handleScaleStart(details); + _loadingIndicator?.handleScaleStart(details); + } + + void handleScaleUpdate(ScaleUpdateDetails details) { + zoomPanBehavior?.handleScaleUpdate(details); + _loadingIndicator?.handleScaleUpdate(details); + } + + void handleScaleEnd(ScaleEndDetails details) { + zoomPanBehavior?.handleScaleEnd(details); + _loadingIndicator?.handleScaleEnd(details); + } + + void handleHorizontalDragStart(DragStartDetails details) { + zoomPanBehavior?.handleHorizontalDragStart(details); + _loadingIndicator?.handleDragStart(details); + } + + void handleHorizontalDragUpdate(DragUpdateDetails details) { + zoomPanBehavior?.handleHorizontalDragUpdate(details); + _loadingIndicator?.handleDragUpdate(details); + } + + void handleHorizontalDragEnd(DragEndDetails details) { + zoomPanBehavior?.handleHorizontalDragEnd(details); + _loadingIndicator?.handleDragEnd(details); + } + + void handleVerticalDragStart(DragStartDetails details) { + zoomPanBehavior?.handleVerticalDragStart(details); + _loadingIndicator?.handleDragStart(details); + } + + void handleVerticalDragUpdate(DragUpdateDetails details) { + zoomPanBehavior?.handleVerticalDragUpdate(details); + _loadingIndicator?.handleDragUpdate(details); + } + + void handleVerticalDragEnd(DragEndDetails details) { + zoomPanBehavior?.handleVerticalDragEnd(details); + _loadingIndicator?.handleDragEnd(details); + } + + void raiseTooltip( + TooltipInfo info, [ + PointerDeviceKind kind = PointerDeviceKind.touch, + ]) { + if (tooltipBehavior != null && + tooltipBehavior!.shared && + info is! TrendlineTooltipInfo) { + final ChartTooltipInfo chartTooltipInfo = info as ChartTooltipInfo; + tooltipBehavior!.showByIndex( + chartTooltipInfo.seriesIndex, + chartTooltipInfo.pointIndex, + ); + } else { + showTooltip(info, kind); + } + } + + void showTooltip( + TooltipInfo info, [ + PointerDeviceKind kind = PointerDeviceKind.touch, + ]) { + if (tooltipBehavior == null || !tooltipBehavior!.enable) { + return; + } + + final CoreTooltipState? state = + _tooltipKey?.currentState as CoreTooltipState?; + if (state == null) { + return; + } + + if (info is ChartTooltipInfo && + onTooltipRender != null && + _previousTooltipInfo != info) { + final TooltipArgs tooltipRenderArgs = + TooltipArgs( + info.seriesIndex, + info.renderer.chartPoints, + info.renderer.viewportIndex(info.pointIndex), + info.pointIndex, + ) + ..text = info.text + ..header = tooltipBehavior!.header ?? info.header + ..locationX = info.primaryPosition.dx + ..locationY = info.primaryPosition.dy; + onTooltipRender!(tooltipRenderArgs); + assert( + tooltipRenderArgs.locationX != null && + tooltipRenderArgs.locationY != null, + ); + info = info.copyWith( + primaryPosition: Offset( + tooltipRenderArgs.locationX!, + tooltipRenderArgs.locationY!, + ), + text: tooltipRenderArgs.text, + name: tooltipRenderArgs.header, + ); + _previousTooltipInfo = info; + } + state.show( + info, + kind, + immediately: tooltipBehavior!.tooltipPosition == TooltipPosition.pointer, + ); + } + + void hideTooltip({bool immediately = false}) { + _previousTooltipInfo = null; + (_tooltipKey?.currentState as CoreTooltipState?)?.hide( + immediately: immediately, + ); + } + + void hideInteractiveTooltip() { + if (tooltipBehavior != null && tooltipBehavior!.enable) { + hideTooltip(immediately: true); + } + if (trackballBehavior != null && trackballBehavior!.enable) { + trackballBehavior!.hide(); + } + if (crosshairBehavior != null && crosshairBehavior!.enable) { + crosshairBehavior!.hide(); + } + } + + void invalidate() { + markNeedsPaint(); + } + + @override + void performLayout() { + RenderBox? child = firstChild; + while (child != null) { + child.layout(constraints); + final StackParentData childParentData = + child.parentData! as StackParentData; + child = childParentData.nextSibling; + } + size = constraints.biggest; + } + + @override + void paint(PaintingContext context, Offset offset) { + crosshairBehavior?.onPaint(context, offset, chartThemeData!, themeData!); + trackballBehavior?.onPaint(context, offset, chartThemeData!, themeData!); + zoomPanBehavior?.onPaint(context, offset, chartThemeData!, themeData!); + defaultPaint(context, offset); + } + + @override + void dispose() { + _previousTooltipInfo = null; + _crosshairEnabled = false; + _trackballEnabled = false; + _zoomingEnabled = false; + performZoomThroughTouch = false; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/selection.dart b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/selection.dart new file mode 100644 index 000000000..799b4680b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/selection.dart @@ -0,0 +1,500 @@ +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../series/chart_series.dart'; +import 'behavior.dart'; + +/// Provides options for the selection of series or data points. +/// +/// By using this class, The color and width of the selected and unselected +/// series or data points can be customized. +class SelectionBehavior extends ChartBehavior { + /// Creating an argument constructor of [SelectionBehavior] class. + SelectionBehavior({ + this.enable = false, + this.selectedColor, + this.selectedBorderColor, + this.selectedBorderWidth, + this.unselectedColor, + this.unselectedBorderColor, + this.unselectedBorderWidth, + this.selectedOpacity = 1.0, + this.unselectedOpacity = 0.5, + this.selectionController, + this.toggleSelection = true, + }); + + /// Enables or disables the selection. + /// + /// By enabling this, each data point or series in the chart can be selected. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final bool enable; + + /// Color of the selected data points or series. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// selectedColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? selectedColor; + + /// Border color of the selected data points or series. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// selectedBorderWidth: 4, + /// selectedBorderColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? selectedBorderColor; + + /// Border width of the selected data points or series. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// selectedBorderWidth: 4, + /// selectedBorderColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final double? selectedBorderWidth; + + /// Color of the unselected data points or series. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// unselectedColor: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? unselectedColor; + + /// Border color of the unselected data points or series. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// unselectedBorderWidth: 4, + /// unselectedBorderColor: Colors.grey + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? unselectedBorderColor; + + /// Border width of the unselected data points or series. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// unselectedBorderWidth: 4, + /// unselectedBorderColor: Colors.grey + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final double? unselectedBorderWidth; + + /// Opacity of the selected series or data point. + /// + /// Default to `1.0`. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// selectedOpacity: 0.5 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final double selectedOpacity; + + /// Opacity of the unselected series or data point. + /// + /// Defaults to `0.5`. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// unselectedOpacity: 0.4 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final double unselectedOpacity; + + /// Controller used to set the maximum and minimum values for the chart. + /// By providing the selection controller, the maximum and + /// The minimum range of selected series or points can be customized. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// final RangeController rangeController= RangeController( + /// start: 1.0, + /// end: 4.0 + /// ) + /// return SfRangeSelector ( + /// min: 1.0, + /// max: 4.0, + /// controller: rangeController, + /// child: SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// selectionController: rangeController + /// ), + /// ], + /// ) + /// ); + /// } + /// final List chartData = [ + /// SalesData(1, 23), + /// SalesData(2, 35), + /// SalesData(3, 19), + /// SalesData(4, 29), + /// SalesData(5, 50), + /// SalesData(6, 77) + /// ]; + /// + /// class SalesData { + /// SalesData(this.x, this.y); + /// final double x; + /// final double y; + /// } + /// ``` + final RangeController? selectionController; + + /// Decides whether to deselect the selected item or not. + /// + /// Provides an option to decide, whether to deselect the selected data point/series + /// or remain selected, when interacted with it again. + /// + /// If set to true, deselection will be performed else the point will not get + /// deselected. This works even while calling public methods, in various + /// selection modes, with multi-selection, and also on dynamic changes. + /// + /// Defaults to `true`. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true, + /// toggleSelection: false + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ), + /// ], + /// ); + /// } + /// ``` + final bool toggleSelection; + + /// Selects or deselects the specified data point in the series. + /// + /// The following are the arguments to be passed. + /// * `pointIndex` - index of the data point that needs to be selected. + /// * `seriesIndex` - index of the series in which the data point is selected. + /// + /// Where the `pointIndex` is a required argument and `seriesIndex` is an + /// optional argument. By default, 0 will be considered as the series index. + /// Thus it will take effect on the first series if no value is specified. + /// + /// For circular, pyramid and funnel charts, series index should always be 0, + /// as it has only one series. + /// + /// If the specified data point is already selected, it will be deselected, + /// else it will be selected. Selection type and multi-selection functionality + /// is also applicable for this, but it is based on + /// the API values specified in [ChartSelectionBehavior]. + /// + /// _Note:_ Even though, the [enable] property in [ChartSelectionBehavior] is + /// set to false, this method will work. + /// + /// ```dart + /// late SelectionBehavior selectionBehavior; + /// + /// void initState() { + /// selectionBehavior = SelectionBehavior( + /// enable: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Column( + /// children: [ + /// TextButton( + /// onPressed: () { + /// setState(() { + /// select(); + /// }); + /// }, + /// child: Text('Select data points') + /// ), + /// SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: selectionBehavior + /// ) + /// ] + /// ) + /// ] + /// ); + /// } + /// + /// void select() { + /// selectionBehavior.selectDataPoints(3); + /// } + /// ``` + void selectDataPoints(int pointIndex, [int seriesIndex = 0]) { + RenderChartPlotArea? plotArea; + if (parentBox is RenderChartPlotArea) { + plotArea = parentBox! as RenderChartPlotArea; + } else if (parentBox is RenderBehaviorArea) { + final RenderBehaviorArea behaviorArea = parentBox! as RenderBehaviorArea; + plotArea = behaviorArea.plotArea; + } + + if (plotArea == null) { + return; + } + + ChartSeriesRenderer? seriesRenderer; + RenderBox? child = plotArea.firstChild; + while (child != null) { + final ContainerParentDataMixin childParentData = + child.parentData! as ContainerParentDataMixin; + if (child is ChartSeriesRenderer && child.index == seriesIndex) { + seriesRenderer = child; + break; + } + child = childParentData.nextSibling; + } + + if (seriesRenderer != null && + seriesRenderer.selectionBehavior != null && + seriesRenderer.selectionBehavior!.enable) { + plotArea.selectionController.updateSelection( + seriesRenderer, + seriesIndex, + pointIndex, + seriesRenderer.selectionBehavior!.toggleSelection, + selectionType: plotArea.selectionMode, + ); + } + } + + /// Provides the list of selected point indices for given series. + List getSelectedDataPoints(CartesianSeries series) { + RenderChartPlotArea? plotArea; + if (parentBox is RenderChartPlotArea) { + plotArea = parentBox! as RenderChartPlotArea; + } else if (parentBox is RenderBehaviorArea) { + final RenderBehaviorArea behaviorArea = parentBox! as RenderBehaviorArea; + plotArea = behaviorArea.plotArea; + } + + if (plotArea == null) { + return []; + } + + int seriesIndex = -1; + RenderBox? child = plotArea.firstChild; + while (child != null) { + final ContainerParentDataMixin childParentData = + child.parentData! as ContainerParentDataMixin; + if (child is ChartSeriesRenderer && child.widget == series) { + seriesIndex = child.index; + break; + } + child = childParentData.nextSibling; + } + + if (seriesIndex != -1) { + final Map> selectedDataPoints = + plotArea.selectionController.selectedDataPoints; + if (selectedDataPoints.containsKey(seriesIndex)) { + return plotArea.selectionController.selectedDataPoints[seriesIndex]!; + } + } + + return []; + } + + SelectionBehavior copyWith({ + bool? enable, + Color? selectedColor, + Color? unselectedColor, + Color? selectedBorderColor, + Color? unselectedBorderColor, + double? selectedBorderWidth, + double? unselectedBorderWidth, + double? selectedOpacity, + double? unselectedOpacity, + bool? toggleSelection, + }) { + return SelectionBehavior( + enable: enable ?? this.enable, + selectedColor: selectedColor ?? this.selectedColor, + unselectedColor: unselectedColor ?? this.unselectedColor, + selectedBorderColor: selectedBorderColor ?? this.selectedBorderColor, + unselectedBorderColor: + unselectedBorderColor ?? this.unselectedBorderColor, + selectedBorderWidth: selectedBorderWidth ?? this.selectedBorderWidth, + unselectedBorderWidth: + unselectedBorderWidth ?? this.unselectedBorderWidth, + selectedOpacity: selectedOpacity ?? this.selectedOpacity, + unselectedOpacity: unselectedOpacity ?? this.unselectedOpacity, + toggleSelection: toggleSelection ?? this.toggleSelection, + ); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/tooltip.dart new file mode 100644 index 000000000..da34c316f --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/tooltip.dart @@ -0,0 +1,866 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart' show DataMarkerType; + +import '../axis/axis.dart'; +import '../base.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'behavior.dart'; + +/// Customizes the tooltip. +/// +/// This class provides options for customizing the properties of the tooltip. +class TooltipBehavior extends ChartBehavior { + /// Creating an argument constructor of TooltipBehavior class. + TooltipBehavior({ + this.enable = false, + this.textStyle, + this.activationMode = ActivationMode.singleTap, + this.animationDuration = 350, + this.opacity = 1.0, + this.borderColor = Colors.transparent, + this.borderWidth = 1, + this.duration = 3000, + this.shouldAlwaysShow = false, + this.elevation = 2.5, + this.canShowMarker = true, + this.textAlignment = ChartAlignment.center, + this.decimalPlaces = 3, + this.tooltipPosition = TooltipPosition.auto, + this.shared = false, + this.color, + this.header, + this.format, + this.builder, + this.shadowColor, + }); + + /// Toggles the visibility of the tooltip. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final bool enable; + + /// Color of the tooltip. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// color: Colors.red + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final Color? color; + + /// Header of the tooltip. By default, the series name will be displayed + /// in the header. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// header: 'Default' + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final String? header; + + /// Opacity of the tooltip. + /// + /// The value ranges from 0 to 1. + /// + /// Defaults to `1`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// opacity: 0.7 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final double opacity; + + /// Customizes the tooltip text. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// textStyle: TextStyle( + /// color: Colors.green + /// ) + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final TextStyle? textStyle; + + /// Specifies the number decimals to be displayed in tooltip text. + /// + /// Defaults to `3`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// decimalPlaces: 1 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + 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, and series name values in the + /// tooltip by formatting them. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// format:'point.y %' + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final String? format; + + /// Duration for animating the tooltip. + /// + /// Defaults to `350`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// animationDuration: 1000 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final int animationDuration; + + /// Toggles the visibility of the marker in the tooltip. + /// + /// Defaults to `true`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// canShowMarker: false + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final bool canShowMarker; + + /// Gesture for activating the tooltip. + /// + /// Tooltip can be activated in tap, double tap, and long press. + /// + /// Defaults to `ActivationMode.tap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// activationMode: ActivationMode.doubleTap + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final ActivationMode activationMode; + + /// Border color of the tooltip. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final Color borderColor; + + /// Border width of the tooltip. + /// + /// Defaults to `0`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// borderColor: Colors.red, + /// borderWidth: 3 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final double borderWidth; + + /// Builder of the tooltip. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// builder: (dynamic data, dynamic point, dynamic series, + /// int pointIndex, int seriesIndex) { + /// return Container( + /// height: 50, + /// width: 100, + /// decoration: const BoxDecoration( + /// color: Color.fromRGBO(66, 244, 164, 1), + /// ), + /// child: Text('$pointIndex'), + /// ); + /// } + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + ///``` + final ChartWidgetBuilder? builder; + + /// Color of the tooltip shadow. + /// + /// Defaults to `null`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// shadowColor: Colors.green + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final Color? shadowColor; + + /// Elevation of the tooltip. + /// + /// Defaults to `2.5`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// elevation: 10 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + 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`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// shouldAlwaysShow: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final bool shouldAlwaysShow; + + /// Duration for displaying the tooltip. + /// + /// Defaults to `3000`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// duration: 1000 + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final double duration; + + /// Alignment of the text in the tooltip. + /// + /// Defaults to `ChartAlignment.center`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// textAlignment : ChartAlignment.near + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final ChartAlignment textAlignment; + + /// Show tooltip at tapped position. + /// + /// Defaults to `TooltipPosition.auto`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// tooltipPosition: TooltipPosition.pointer + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final TooltipPosition tooltipPosition; + + /// Share the tooltip with same index points. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late TooltipBehavior tooltipBehavior; + /// + /// void initState() { + /// tooltipBehavior = TooltipBehavior( + /// enable: true, + /// shared: true + /// ); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: tooltipBehavior + /// ); + /// } + /// ``` + final bool shared; + + /// Displays the tooltip at the specified x and y-positions. + /// + /// * x & y - logical pixel values to position the tooltip. + void showByPixel(double x, double y) { + assert(!x.isNaN); + assert(!y.isNaN); + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent == null) { + return; + } + + final Offset primaryLocalPosition = Offset(x, y); + final Offset primaryPosition = parent.localToGlobal(primaryLocalPosition); + RenderBox? child = parent.plotArea?.lastChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + if (child is ChartSeriesRenderer) { + final bool isHit = child.hitInsideSegment(primaryLocalPosition); + if (isHit) { + final TooltipInfo? info = child.tooltipInfo( + position: primaryLocalPosition, + ); + if (info != null) { + parent.raiseTooltip(info); + } + break; + } + } + child = childParentData.previousSibling; + } + + parent.raiseTooltip( + TooltipInfo( + primaryPosition: primaryPosition, + secondaryPosition: primaryPosition, + ), + ); + } + + /// Displays the tooltip at the specified x and y-values. + /// + /// * x & y - x & y point values at which the tooltip needs to be shown. + /// + /// * 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]) { + assert(x != null); + assert(!y.isNaN); + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent != null) { + final RenderChartAxis? xAxis = + xAxisName != null ? parent.axisFromName(xAxisName) : parent.xAxis; + final RenderChartAxis? yAxis = + yAxisName != null ? parent.axisFromName(yAxisName) : parent.yAxis; + final Offset position = rawValueToPixelPoint( + x, + y, + xAxis, + yAxis, + parent.isTransposed, + ); + showByPixel(position.dx, position.dy); + } + } + + /// Displays the tooltip at the specified series and point index. + /// + /// * seriesIndex - index of the series for which the pointIndex is specified + /// + /// * pointIndex - index of the point for which the tooltip should be shown + void showByIndex(int seriesIndex, int pointIndex) { + final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; + if (parent != null) { + if (shared) { + String? text; + String? header; + num? baseXValue; + Offset? position; + ChartTooltipInfo? tooltipInfo; + final List markerColors = []; + final List tooltipInfoList = []; + final RenderBox? firstChild = parent.plotArea?.firstChild; + RenderBox? series = firstChild; + while (series != null && series.parentData != null) { + final ContainerParentDataMixin seriesParentData = + series.parentData! as ContainerParentDataMixin; + // It specifies for cartesian series renderer. + if (series is CartesianSeriesRenderer && + series.isVisible() && + series.enableTooltip) { + final ChartTooltipInfo? info = + series.tooltipInfoFromPointIndex(pointIndex) + as ChartTooltipInfo?; + if (info != null && series.index == seriesIndex) { + baseXValue = (info.point as CartesianChartPoint).xValue; + break; + } + } + + series = seriesParentData.nextSibling; + } + + RenderBox? child = firstChild; + while (child != null && child.parentData != null) { + final ContainerParentDataMixin childParentData = + child.parentData! as ContainerParentDataMixin; + if (child is ChartSeriesRenderer && + child.isVisible() && + child.enableTooltip) { + final ChartTooltipInfo? info = + child.tooltipInfoFromPointIndex(pointIndex) + as ChartTooltipInfo?; + if (info != null && info.text != null) { + if (child.index == seriesIndex) { + tooltipInfo ??= info; + position ??= info.primaryPosition; + } + } + + // It specifies for circular & funnel & pyramid series renderer. + if (baseXValue == null) { + if (tooltipInfo?.point.x == info?.point.x) { + tooltipInfoList.add(info!); + } + } else { + // It specifies for cartesian series renderer. + if (child.canFindLinearVisibleIndexes) { + final int binaryIndex = binarySearch( + child.xValues, + baseXValue.toDouble(), + 0, + child.dataCount - 1, + ); + if (binaryIndex >= 0) { + final ChartTooltipInfo? info = + child.tooltipInfoFromPointIndex(binaryIndex) + as ChartTooltipInfo?; + if (info != null && info.text != null) { + final num? infoXValue = + (info.point as CartesianChartPoint).xValue; + if (infoXValue != null && baseXValue == infoXValue) { + tooltipInfoList.add(info); + } + } + } + } else { + final int index = child.xValues.indexOf(baseXValue); + if (index >= 0) { + final ChartTooltipInfo? info = + child.tooltipInfoFromPointIndex(index) + as ChartTooltipInfo?; + if (info != null && info.text != null) { + tooltipInfoList.add(info); + } + } + } + } + } + + child = childParentData.nextSibling; + } + + for (final ChartTooltipInfo info in tooltipInfoList) { + if (text == null) { + text = '${info.text}'; + } else { + text += '\n'; + text += '${info.text}'; + } + if (info.markerColors.isNotEmpty) { + markerColors.add(info.markerColors[0]); + } + } + + if (tooltipInfo != null && text != null && position != null) { + parent.showTooltip( + ChartTooltipInfo( + primaryPosition: position, + secondaryPosition: tooltipInfo.secondaryPosition, + text: text, + data: tooltipInfo.data, + point: tooltipInfo.point, + series: tooltipInfo.series, + renderer: tooltipInfo.renderer, + header: header ?? tooltipInfo.header, + seriesIndex: seriesIndex, + pointIndex: pointIndex, + segmentIndex: tooltipInfo.segmentIndex, + markerColors: markerColors, + markerBorderColor: tooltipInfo.markerBorderColor, + markerType: tooltipInfo.markerType, + ), + ); + } + } else { + parent.plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer && + child.isVisible() && + child.enableTooltip) { + if (child.index == seriesIndex) { + final TooltipInfo? info = child.tooltipInfoFromPointIndex( + pointIndex, + ); + if (info != null) { + parent.showTooltip(info); + } + } + } + }); + } + } + } + + /// Hides the tooltip if it is displayed. + void hide() { + (parentBox as RenderBehaviorArea?)?.hideTooltip(); + } +} + +@immutable +class ChartTooltipInfo extends TooltipInfo { + const ChartTooltipInfo({ + required super.primaryPosition, + required super.secondaryPosition, + super.text, + super.surfaceBounds, + required this.data, + required this.point, + required this.series, + required this.renderer, + required this.header, + required this.seriesIndex, + required this.segmentIndex, + required this.pointIndex, + this.hasMultipleYValues = false, + this.markerColors = const [], + this.markerBorderColor, + this.markerType = DataMarkerType.circle, + }); + + final T data; + final ChartPoint point; + final ChartSeries series; + final ChartSeriesRenderer renderer; + final String header; + final int seriesIndex; + final int segmentIndex; + final int pointIndex; + final bool hasMultipleYValues; + final List markerColors; + final Color? markerBorderColor; + final DataMarkerType markerType; + + ChartTooltipInfo copyWith({ + Offset? primaryPosition, + Offset? secondaryPosition, + Rect? surfaceBounds, + String? text, + T? data, + ChartPoint? point, + ChartSeries? series, + ChartSeriesRenderer? renderer, + String? name, + int? seriesIndex, + int? segmentIndex, + int? pointIndex, + List? markerColors, + Color? markerBorderColor, + DataMarkerType? markerShape, + }) { + return ChartTooltipInfo( + primaryPosition: primaryPosition ?? this.primaryPosition, + secondaryPosition: secondaryPosition ?? this.secondaryPosition, + text: text ?? this.text, + surfaceBounds: surfaceBounds ?? this.surfaceBounds, + data: data ?? this.data, + point: point ?? this.point, + series: series ?? this.series, + renderer: renderer ?? this.renderer, + header: name ?? this.header, + seriesIndex: seriesIndex ?? this.seriesIndex, + segmentIndex: segmentIndex ?? this.segmentIndex, + pointIndex: pointIndex ?? this.pointIndex, + markerColors: markerColors ?? this.markerColors, + markerBorderColor: markerBorderColor ?? this.markerBorderColor, + markerType: markerShape ?? this.markerType, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is ChartTooltipInfo && + other.text == text && + other.surfaceBounds == surfaceBounds && + other.header == header && + other.seriesIndex == seriesIndex && + other.segmentIndex == segmentIndex && + other.pointIndex == pointIndex; + } + + @override + int get hashCode { + final List values = [ + primaryPosition, + secondaryPosition, + text, + surfaceBounds, + ]; + return Object.hashAll(values); + } +} + +@immutable +class TrendlineTooltipInfo extends ChartTooltipInfo { + const TrendlineTooltipInfo({ + required super.primaryPosition, + required super.secondaryPosition, + super.text, + super.surfaceBounds, + required super.data, + required super.point, + required super.series, + required super.renderer, + required super.header, + required super.seriesIndex, + required super.segmentIndex, + required super.pointIndex, + super.hasMultipleYValues = false, + super.markerColors = const [], + super.markerBorderColor, + super.markerType, + }); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart new file mode 100644 index 000000000..a2db855a8 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart @@ -0,0 +1,754 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import 'base.dart'; +import 'common/core_legend.dart' as core; +import 'common/core_tooltip.dart'; +import 'common/legend.dart'; +import 'common/title.dart'; +import 'interactions/behavior.dart'; +import 'interactions/tooltip.dart'; +import 'series/pyramid_series.dart'; +import 'theme.dart'; +import 'utils/enum.dart'; +import 'utils/helper.dart'; +import 'utils/typedef.dart'; + +/// Renders the pyramid chart. +/// +/// To render a pyramid chart, create an instance of PyramidSeries, and +/// add it to the series property of SfPyramidChart. +/// +/// Properties such as opacity, [borderWidth], [borderColor], pointColorMapper +/// are used to customize the appearance of a pyramid segment. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} +class SfPyramidChart extends StatefulWidget { + /// Creating an argument constructor of [SfPyramidChart] class. + const SfPyramidChart({ + Key? key, + this.backgroundColor, + this.backgroundImage, + this.borderColor = Colors.transparent, + this.borderWidth = 0.0, + this.onLegendItemRender, + this.onTooltipRender, + this.onDataLabelRender, + this.onDataLabelTapped, + this.onLegendTapped, + this.onSelectionChanged, + this.onChartTouchInteractionUp, + this.onChartTouchInteractionDown, + this.onChartTouchInteractionMove, + this.palette, + this.margin = const EdgeInsets.fromLTRB(10, 10, 10, 10), + this.series = const PyramidSeries(), + this.title = const ChartTitle(), + this.legend = const Legend(), + this.tooltipBehavior, + this.selectionGesture = ActivationMode.singleTap, + this.enableMultiSelection = false, + }) : super(key: key); + + /// Customizes the chart title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// title: ChartTitle(text: 'Pyramid Chart') + /// ) + /// ); + /// } + /// ``` + final ChartTitle title; + + /// Background color of the chart. + /// + /// Defaults to `null`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// backgroundColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + final Color? backgroundColor; + + /// Border color of the chart. + /// + /// Defaults to `Colors.transparent`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// borderColor: Colors.blue + /// ) + /// ); + /// } + /// ``` + final Color borderColor; + + /// Border width of the chart. + /// + /// Defaults to `0.0`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// borderWidth: 2 + /// ) + /// ); + /// } + /// ``` + final double borderWidth; + + /// Customizes the chart series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries<_PyramidData, String>( + /// dataSource: data, + /// xValueMapper: (_PyramidData data, _) => data.xData, + /// yValueMapper: (_PyramidData data, _) => data.yData, + /// ), + /// ), + /// ); + /// } + /// ``` + final PyramidSeries series; + + /// Customizes the chart. + /// + /// Defaults to `const EdgeInsets.all(10)`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// margin: const EdgeInsets.all(2), + /// ) + /// ); + /// } + /// ``` + final EdgeInsets margin; + + /// Customizes the legend in the chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// legend: Legend(isVisible: true) + /// ) + /// ); + /// } + /// ``` + final Legend legend; + + /// Color palette for the data points in the chart series. + /// + /// If the series color is not specified, then the series will be rendered + /// with the appropriate palette color. + /// Ten colors are available by default. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// palette: [Colors.red, Colors.green] + /// ) + /// ); + /// } + /// ``` + final List? palette; + + /// Customizes the tooltip in chart. + /// + /// ```dart + /// late TooltipBehavior _tooltipBehavior; + /// + /// @override + /// void initState() { + /// _tooltipBehavior = TooltipBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// tooltipBehavior: _tooltipBehavior + /// ) + /// ); + /// } + /// ``` + final TooltipBehavior? tooltipBehavior; + + /// Occurs while the legend is rendered. + /// + /// Here, you can get the legend's text, shape, series index, and + /// point index case of the pyramid series. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// legend: Legend(isVisible: true), + /// onLegendItemRender: (LegendRendererArgs args) => legend(args), + /// ) + /// ); + /// } + /// void legend(LegendRendererArgs args) { + /// args.legendIconType = LegendIconType.diamond; + /// } + /// ``` + final PyramidLegendRenderCallback? onLegendItemRender; + + /// Occurs when the tooltip is rendered. + /// + /// Here, you can get the tooltip arguments and customize the arguments. + final PyramidTooltipCallback? onTooltipRender; + + /// Occurs when the data label is rendered, + /// here data label arguments can be customized. + final PyramidDataLabelRenderCallback? onDataLabelRender; + + /// Occurs when the legend is tapped, the arguments can be used to customize + /// the legend arguments. + final ChartLegendTapCallback? onLegendTapped; + + /// 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. + /// + /// Defaults to `[]`. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// initialSelectedDataIndexes: [1,0], + /// selectionBehavior: _selectionBehavior + /// ), + /// ) + /// ); + /// } + /// ``` + + /// Gesture for activating the selection. + /// + /// Selection can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, + /// `ActivationMode.doubleTap` and + /// `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// selectionGesture: ActivationMode.singleTap, + /// series: PyramidSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ) + /// ); + /// } + /// ``` + final ActivationMode selectionGesture; + + /// Enables or disables the multiple data points selection. + /// + /// Defaults to `false`. + /// + /// ```dart + /// late SelectionBehavior _selectionBehavior; + /// + /// void initState() { + /// _selectionBehavior = SelectionBehavior(enable: true); + /// super.initState(); + /// } + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// enableMultiSelection: true, + /// series: PyramidSeries( + /// selectionBehavior: _selectionBehavior + /// ), + /// ) + /// ); + /// } + /// ``` + final bool enableMultiSelection; + + /// Background image for chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// backgroundImage: const AssetImage('image.png'), + /// ) + /// ); + /// } + /// ``` + final ImageProvider? backgroundImage; + + /// Occurs while selection changes. Here, you can get the series, selected + /// color, unselected color, selected border color, unselected border color, + /// selected border width, unselected border width, series index, and + /// point index. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// onSelectionChanged: (SelectionArgs args) => select(args), + /// ) + /// ); + /// } + /// void select(SelectionArgs args) { + /// print(args.selectedBorderColor); + /// } + /// ``` + final PyramidSelectionCallback? onSelectionChanged; + + /// Called when the data label is tapped. + /// + /// Whenever the data label is tapped, the `onDataLabelTapped` callback will + /// be called. Provides options to get the position of the data label, + /// series index, point index and its text. + /// + /// _Note:_ This callback will not be called, when the builder is specified + /// for data label (data label template). + /// For this case, custom widget specified in the `DataLabelSettings.builder` + /// property can be wrapped using the `GestureDetector` and this functionality + /// can be achieved at the application level. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// onDataLabelTapped: (DataLabelTapDetails args) { + /// print(arg.seriesIndex); + /// } + /// ) + /// ); + /// } + /// ``` + final DataLabelTapCallback? onDataLabelTapped; + + /// Occurs when tapped on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final PyramidTouchInteractionCallback? onChartTouchInteractionUp; + + /// Occurs when touched and moved on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final PyramidTouchInteractionCallback? onChartTouchInteractionMove; + + /// Occurs when touched on the chart area. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ + /// print(args.position.dx.toString()); + /// print(args.position.dy.toString()); + /// } + /// ) + /// ); + /// } + /// ``` + final PyramidTouchInteractionCallback? onChartTouchInteractionDown; + + @override + State createState() => SfPyramidChartState(); +} + +/// Represents the state class of [SfPyramidChart] widget. +/// +class SfPyramidChartState extends State + with TickerProviderStateMixin { + final List _legendItems = []; + + late GlobalKey _legendKey; + late GlobalKey _tooltipKey; + late SfChartThemeData _chartThemeData; + late ThemeData _themeData; + SfLocalizations? _localizations; + + SfChartThemeData _updateThemeData( + BuildContext context, + ChartThemeData effectiveChartThemeData, + ) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + backgroundColor: + widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: + widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, + legendBackgroundColor: + widget.legend.backgroundColor ?? + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + tooltipColor: + widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + plotAreaBackgroundColor: + chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: + chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor, + ) + .merge(chartThemeData.titleTextStyle) + .merge(widget.title.textStyle), + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: + chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor, + ) + .merge(chartThemeData.legendTitleTextStyle) + .merge(widget.legend.title?.textStyle), + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: + chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor, + ) + .merge(chartThemeData.legendTextStyle) + .merge(widget.legend.textStyle), + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.tooltipTextStyle) + .merge(widget.tooltipBehavior?.textStyle), + ); + return chartThemeData; + } + + Widget _buildLegendItem(BuildContext context, int index) { + return buildLegendItem(context, _legendItems[index], widget.legend); + } + + Widget? _buildTooltipWidget( + BuildContext context, + TooltipInfo? info, + Size maxSize, + ) { + return buildTooltipWidget( + context, + info, + maxSize, + widget.tooltipBehavior, + _chartThemeData, + _themeData, + ); + } + + /// 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() { + _legendKey = GlobalKey(); + _tooltipKey = GlobalKey(); + super.initState(); + } + + @override + void didChangeDependencies() { + _localizations = SfLocalizations.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. + /// + /// 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) { + _themeData = Theme.of(context); + final ChartThemeData effectiveChartThemeData = ChartThemeData(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); + final core.LegendPosition legendPosition = effectiveLegendPosition( + widget.legend, + ); + final Axis orientation = effectiveLegendOrientation( + legendPosition, + widget.legend, + ); + Widget current = core.LegendLayout( + key: _legendKey, + padding: EdgeInsets.zero, + showLegend: widget.legend.isVisible, + legendPosition: legendPosition, + legendAlignment: effectiveLegendAlignment(widget.legend.alignment), + legendTitleAlignment: effectiveLegendAlignment( + widget.legend.title?.alignment, + ), + itemIconBorderColor: widget.legend.iconBorderColor, + itemIconBorderWidth: widget.legend.iconBorderWidth, + legendBorderColor: widget.legend.borderColor, + legendBackgroundColor: _chartThemeData.legendBackgroundColor, + legendBorderWidth: widget.legend.borderWidth, + itemOpacity: widget.legend.opacity, + legendWidthFactor: percentageToWidthFactor( + widget.legend.width, + legendPosition, + ), + legendHeightFactor: percentageToHeightFactor( + widget.legend.height, + legendPosition, + ), + itemInnerSpacing: widget.legend.padding, + itemSpacing: 0.0, + itemPadding: widget.legend.itemPadding, + itemRunSpacing: 0.0, + itemIconHeight: widget.legend.iconHeight, + itemIconWidth: widget.legend.iconWidth, + scrollbarVisibility: effectiveScrollbarVisibility(widget.legend), + enableToggling: widget.legend.toggleSeriesVisibility, + itemTextStyle: _chartThemeData.legendTextStyle!, + isResponsive: widget.legend.isResponsive, + legendWrapDirection: orientation, + legendTitle: buildLegendTitle(_chartThemeData, widget.legend), + legendOverflowMode: effectiveOverflowMode(widget.legend), + legendItemBuilder: + widget.legend.legendItemBuilder != null ? _buildLegendItem : null, + legendFloatingOffset: widget.legend.offset, + child: ChartArea( + legendItems: _legendItems, + legendKey: _legendKey, + onChartTouchInteractionDown: widget.onChartTouchInteractionDown, + onChartTouchInteractionMove: widget.onChartTouchInteractionMove, + onChartTouchInteractionUp: widget.onChartTouchInteractionUp, + children: [ + ChartPlotArea( + vsync: this, + localizations: _localizations, + legendKey: _legendKey, + backgroundColor: null, + borderWidth: widget.borderWidth, + legend: widget.legend, + onLegendItemRender: widget.onLegendItemRender, + onDataLabelRender: widget.onDataLabelRender, + onLegendTapped: widget.onLegendTapped, + onTooltipRender: widget.onTooltipRender, + onDataLabelTapped: widget.onDataLabelTapped, + palette: widget.palette ?? effectiveChartThemeData.palette, + selectionMode: SelectionType.point, + selectionGesture: widget.selectionGesture, + enableMultiSelection: widget.enableMultiSelection, + tooltipBehavior: widget.tooltipBehavior, + onSelectionChanged: widget.onSelectionChanged, + chartThemeData: _chartThemeData, + themeData: _themeData, + children: [widget.series], + ), + if (widget.tooltipBehavior != null) + BehaviorArea( + tooltipKey: _tooltipKey, + chartThemeData: _chartThemeData, + themeData: _themeData, + tooltipBehavior: widget.tooltipBehavior, + onTooltipRender: widget.onTooltipRender, + children: [ + if (widget.tooltipBehavior != null && + widget.tooltipBehavior!.enable) + CoreTooltip( + key: _tooltipKey, + builder: _buildTooltipWidget, + opacity: widget.tooltipBehavior!.opacity, + borderColor: widget.tooltipBehavior!.borderColor, + borderWidth: widget.tooltipBehavior!.borderWidth, + color: + (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, + showDuration: widget.tooltipBehavior!.duration.toInt(), + shadowColor: widget.tooltipBehavior!.shadowColor, + elevation: widget.tooltipBehavior!.elevation, + animationDuration: + widget.tooltipBehavior!.animationDuration, + shouldAlwaysShow: widget.tooltipBehavior!.shouldAlwaysShow, + ), + ], + ), + ], + ), + ); + + current = buildChartWithTitle(current, widget.title, _chartThemeData); + + return RepaintBoundary( + child: Container( + decoration: BoxDecoration( + image: + widget.backgroundImage != null + ? DecorationImage( + image: widget.backgroundImage!, + fit: BoxFit.fill, + ) + : null, + color: _chartThemeData.backgroundColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: Padding( + padding: widget.margin.resolve(Directionality.maybeOf(context)), + child: current, + ), + ), + ); + } + + /// Method to convert the [SfPyramidChart] as an 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. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfPyramidChart( + /// key: _key, + /// series: PyramidSeries<_PyramidData, String>( + /// dataSource: data, + /// xValueMapper: (_PyramidData data, _) => data.xData, + /// yValueMapper: (_PyramidData data, _) => data.yData, + /// ), + /// ), + /// RaisedButton( + /// child: Text( + /// 'To Image', + /// ), + /// onPressed: _renderImage, + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// Future _renderImage() async { + /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); + /// final bytes = await data.toByteData(format: dart_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.white, + /// child: Image.memory(bytes.buffer.asUint8List()), + /// ), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// } + /// ``` + Future toImage({double pixelRatio = 1.0}) async { + final RenderRepaintBoundary? boundary = + context.findRenderObject() as RenderRepaintBoundary?; + if (boundary != null) { + final Image image = await boundary.toImage(pixelRatio: pixelRatio); + return image; + } + + return null; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart new file mode 100644 index 000000000..cb8e6023b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart @@ -0,0 +1,707 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// This class renders the area series. +/// +/// To render an area chart, create an instance of [AreaSeries], and add it +/// to the series collection property of [SfCartesianChart]. +/// The area chart shows the filled area to represent the data, but when there +/// are more than a series, this may hide the other series. +/// To get rid of this, increase or decrease the transparency of the series. +/// +/// It provides options for color, opacity, border color, and border width to +/// customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=E_odUnOsBtQ} +@immutable +class AreaSeries extends XyDataSeries { + /// Creating an argument constructor of AreaSeries class. + const AreaSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.trendlines, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + this.borderDrawMode = BorderDrawMode.top, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Border type of area series. + /// + /// It has three types of [BorderDrawMode], + /// + /// * [BorderDrawMode.all] renders border for all the sides of area. + /// + /// * [BorderDrawMode.top] renders border only for top side. + /// + /// * [BorderDrawMode.excludeBottom] renders border except bottom side. + /// + /// Defaults to `BorderDrawMode.top`. + /// + /// Also refer [BorderDrawMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// AreaSeries( + /// borderWidth: 3, + /// borderColor: Colors.red, + /// borderDrawMode: BorderDrawMode.all, + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderDrawMode borderDrawMode; + + final Color borderColor; + + /// Create the area series renderer. + @override + AreaSeriesRenderer createRenderer() { + AreaSeriesRenderer? renderer; + if (onCreateRenderer != null) { + renderer = onCreateRenderer!(this) as AreaSeriesRenderer?; + assert( + renderer != null, + 'This onCreateRenderer callback function should return value as ' + 'extends from ChartSeriesRenderer class and should not be return ' + 'value as null', + ); + } + + return renderer ?? AreaSeriesRenderer(); + } + + @override + AreaSeriesRenderer createRenderObject(BuildContext context) { + final AreaSeriesRenderer renderer = + super.createRenderObject(context) as AreaSeriesRenderer; + renderer + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + AreaSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + } +} + +/// Creates series renderer for area series. +class AreaSeriesRenderer extends XyDataSeriesRenderer + with ContinuousSeriesMixin { + /// Calling the default constructor of [AreaSeriesRenderer] class. + AreaSeriesRenderer(); + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + BorderDrawMode get borderDrawMode => _borderDrawMode; + BorderDrawMode _borderDrawMode = BorderDrawMode.top; + set borderDrawMode(BorderDrawMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + forceTransformValues = true; + markNeedsLayout(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + final num bottom = xAxis!.crossesAt ?? max(yAxis!.visibleRange!.minimum, 0); + segment as AreaSegment + ..series = this + ..currentSegmentIndex = index + ..xValues = xValues + ..yValues = yValues + ..bottom = bottom; + } + + /// To create area series segment. + @override + AreaSegment createSegment() => AreaSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.areaSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final AreaSegment areaSegment = segment as AreaSegment; + updateSegmentColor(areaSegment, borderColor, borderWidth); + updateSegmentGradient( + areaSegment, + gradientBounds: areaSegment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Creates the segments for area series. +/// +/// 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. +class AreaSegment extends ChartSegment { + late AreaSeriesRenderer series; + late List xValues; + late List yValues; + num bottom = 0.0; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _oldHighPoints = []; + final List _oldLowPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + } + } + + @override + void transformValues() { + if (xValues.isEmpty || yValues.isEmpty) { + return; + } + + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + + _fillPath.reset(); + _strokePath.reset(); + + _calculatePoints(xValues, yValues); + // Calculated path here for getting gradient bounds. + _createFillPath(_fillPath, _highPoints, _lowPoints); + } + + void _calculatePoints(List xValues, List yValues) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num high = yValues[i]; + if (high.isNaN && canDrop) { + continue; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, high), transformY(x, high)); + _highPoints.add(highPoint); + + final num low = high.isNaN ? double.nan : bottom; + final Offset lowPoint = Offset(transformX(x, low), transformY(x, low)); + _lowPoints.add(lowPoint); + + points.add(highPoint); + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, bottom)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, bottom)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, bottom)!, + ); + } + } + + return lerpPoints; + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createFillPath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + _createFillPath(source, highPoints.sublist(i), lowPoints.sublist(i)); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createTopStrokePath(Path source, List highPoints) { + Path? path; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + _createTopStrokePath(source, highPoints.sublist(i + 1)); + break; + } else { + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + + return source; + } + + Path _createExcludeBottomStrokePath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + final Offset lowPoint = lowPoints[i - 1]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + ); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + final Offset lowPoint = lowPoints[i]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + + return source; + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final ChartMarker marker = series.markerAt(pointIndex); + final num x = chartPoint.xValue!; + final num y = chartPoint.y!; + final double dx = series.pointToPixelX(x, y); + final double dy = series.pointToPixelY(x, y); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + + final Offset preferredPos = points[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + case BorderDrawMode.excludeBottom: + _createExcludeBottomStrokePath( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + } + } + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _fillPath.reset(); + _strokePath.reset(); + + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart new file mode 100644 index 000000000..88bd4616f --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart @@ -0,0 +1,631 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/legend.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// This class has the properties of the bar series. +/// +/// To render a bar chart, create an instance of [BarSeries], and add it to +/// the series collection property of [SfCartesianChart]. +/// The bar series are rectangular bars with heights or lengths proportional +/// to their represented values.It has the property of spacing to provide +/// spacing between bars. +/// +/// Provides options for color, opacity, border color and border width to +/// customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=MHQzCN_jD1Q} +@immutable +class BarSeries extends XyDataSeries { + /// Creating an argument constructor of BarSeries class. + const BarSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + this.isTrackVisible = false, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + this.spacing = 0.0, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.borderRadius = BorderRadius.zero, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1.0, + this.trackPadding = 0.0, + super.trendlines, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.dashArray, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Color of the track. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// isTrackVisible: true, + /// trackColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackColor; + + /// Color of the track border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackBorderColor; + + /// Width of the track border. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red, + /// trackBorderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackBorderWidth; + + /// Padding of the track. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// isTrackVisible: true, + /// trackPadding: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackPadding; + + /// Spacing between the bars. The value ranges from 0 to 1. + /// + /// 1 represents 100% and 0 represents 0% of the available space. + /// + /// Spacing also affects the height of the bar. For example, setting 20% + /// spacing and 100% height renders the bar with 80% of total height. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// spacing: 0.5 + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// Customizes the corners of the bar. + /// + /// Each corner can be customized with desired + /// value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// borderRadius: BorderRadius.all(Radius.circular(5)) + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + /// Renders bars with track. + /// + /// Track is a rectangular bar rendered from the start to the + /// end of the axis. + /// + /// Bar series will be rendered above the track. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// isTrackVisible: true + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isTrackVisible; + + final double width; + + final Color borderColor; + + @override + @nonVirtual + bool transposed() => true; + + /// Create the bar series renderer. + @override + BarSeriesRenderer createRenderer() { + BarSeriesRenderer? renderer; + if (onCreateRenderer != null) { + renderer = onCreateRenderer!(this) as BarSeriesRenderer?; + assert( + renderer != null, + 'This onCreateRenderer callback function should return value as ' + 'extends from ChartSeriesRenderer class and should not be return ' + 'value as null', + ); + } + + return renderer ?? BarSeriesRenderer(); + } + + @override + BarSeriesRenderer createRenderObject(BuildContext context) { + final BarSeriesRenderer renderer = + super.createRenderObject(context) as BarSeriesRenderer; + renderer + ..isTrackVisible = isTrackVisible + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..spacing = spacing + ..width = width + ..borderRadius = borderRadius + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + BarSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..isTrackVisible = isTrackVisible + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..spacing = spacing + ..width = width + ..borderRadius = borderRadius + ..borderColor = borderColor; + } +} + +// Creates series renderer for bar series. +class BarSeriesRenderer extends XyDataSeriesRenderer + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + /// Calling the default constructor of BarSeriesRenderer class. + BarSeriesRenderer(); + + Color get trackColor => _trackColor; + Color _trackColor = Colors.grey; + set trackColor(Color value) { + if (_trackColor != value) { + _trackColor = value; + markNeedsLayout(); + } + } + + Color get trackBorderColor => _trackBorderColor; + Color _trackBorderColor = Colors.transparent; + set trackBorderColor(Color value) { + if (_trackColor != value) { + _trackBorderColor = value; + markNeedsLayout(); + } + } + + double get trackBorderWidth => _trackBorderWidth; + double _trackBorderWidth = 1.0; + set trackBorderWidth(double value) { + if (_trackBorderWidth != value) { + _trackBorderWidth = value; + markNeedsLayout(); + } + } + + double get trackPadding => _trackPadding; + double _trackPadding = 0.0; + set trackPadding(double value) { + if (_trackPadding != value) { + _trackPadding = value; + markNeedsLayout(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + bool get isTrackVisible => _isTrackVisible; + bool _isTrackVisible = false; + set isTrackVisible(bool value) { + if (_isTrackVisible != value) { + _isTrackVisible = value; + markNeedsLayout(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as BarSegment + ..series = this + ..x = xValues[index] + ..y = yValues[index] + ..bottom = bottom + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + BarSegment createSegment() => BarSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.barSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final BarSegment barSegment = segment as BarSegment; + updateSegmentTrackerStyle( + barSegment, + trackColor, + trackBorderColor, + trackBorderWidth, + ); + updateSegmentColor(barSegment, borderColor, borderWidth); + updateSegmentGradient( + barSegment, + gradientBounds: barSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + if (parent != null && parent!.onLegendTapped != null) { + final CartesianLegendItem legendItem = item as CartesianLegendItem; + final LegendTapArgs args = LegendTapArgs( + legendItem.series, + legendItem.seriesIndex, + legendItem.pointIndex, + ); + parent!.onLegendTapped!(args); + } + parent!.behaviorArea?.hideInteractiveTooltip(); + + controller.isVisible = !isToggled; + if (controller.isVisible == !isToggled) { + item.onToggled?.call(); + visibilityBeforeTogglingLegend = isToggled; + animateAllBarSeries(parent!); + } + + if (trendlineContainer != null) { + trendlineContainer!.updateLegendState(item, isToggled); + markNeedsLegendUpdate(); + } + markNeedsUpdate(); + } + + @override + void onRealTimeAnimationUpdate() { + super.onRealTimeAnimationUpdate(); + transformValues(); + } +} + +/// Creates the segments for bar series. +/// +/// 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. +class BarSegment extends ChartSegment with BarSeriesTrackerMixin { + late BarSeriesRenderer series; + late num x; + late num y; + late num bottom; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + _oldSegmentRect = segmentRect; + } + + @override + void transformValues() { + if (x.isNaN || y.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, y); + final double y1 = transformY(left, y); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series.borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, bottom), + transformY(left, bottom), + x2, + y2, + borderRadius, + ); + + if (series.isTrackVisible) { + calculateTrackerBounds( + left, + right, + borderRadius, + series.trackPadding, + series.trackBorderWidth, + series, + ); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final Offset posFromRect = segmentRect!.outerRect.topCenter; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? posFromRect + : posFromRect; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, y), + series.pointToPixelY(x, y), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } + + if (segmentRect == null) { + return; + } + + RRect? paintRRect; + if (series.parent!.isLegendToggled && + _oldSegmentRect != null && + series.animationType != AnimationType.loading) { + paintRRect = performLegendToggleAnimation( + series, + segmentRect!, + _oldSegmentRect!, + series.borderRadius, + ); + } else { + paintRRect = RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); + } + + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + _oldSegmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart new file mode 100644 index 000000000..98c1acb8f --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart @@ -0,0 +1,1339 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/element_widget.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../trendline/trendline.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// 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]. +/// 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 +/// to customize the appearance. +@immutable +class BoxAndWhiskerSeries extends CartesianSeries { + /// Creating an argument constructor of BoxAndWhiskerSeries class. + const BoxAndWhiskerSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required this.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + this.spacing = 0.0, + super.markerSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.borderColor, + super.borderWidth = 1.0, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.trendlines, + this.boxPlotMode = BoxPlotMode.normal, + this.showMean = true, + }); + + final ChartValueMapper>? yValueMapper; + + /// To change the box plot rendering mode. + /// + /// The box plot series rendering mode can be changed by + /// using [BoxPlotMode] property. The below values are applicable for this, + /// + /// * `BoxPlotMode.normal` - The quartile values are calculated by splitting + /// the list and by getting the median values. + /// * `BoxPlotMode.exclusive` - The quartile values are calculated by using + /// the formula (N+1) * P (N count, P percentile), and their index value + /// starts from 1 in the list. + /// * `BoxPlotMode.inclusive` - The quartile values are calculated by using + /// the formula (N−1) * P (N count, P percentile), and their index value + /// starts from 0 in the list. + /// + /// Also refer [BoxPlotMode]. + /// + /// Defaults to `BoxPlotMode.normal`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BoxAndWhiskerSeries( + /// boxPlotMode: BoxPlotMode.normal + /// ), + /// ], + /// ); + /// } + /// ``` + final BoxPlotMode boxPlotMode; + + /// Indication for mean value in box plot. + /// + /// If [showMean] property value is set to true, a cross symbol will be + /// displayed at the mean value, for each data point in box plot. Else, + /// it will not be displayed. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BoxAndWhiskerSeries( + /// showMean: false + /// ), + /// ], + /// ); + /// } + /// ``` + final bool showMean; + + /// Spacing between the box plots. + /// + /// The value ranges from 0 to 1, where 1 represents 100% and 0 represents 0% + /// of the available space. + /// + /// Spacing affects the width of the box plots. For example, setting 20% + /// spacing and 100% width renders the box plots with 80% of total width. + /// + /// Also refer [CartesianSeries.width]. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BoxAndWhiskerSeries( + /// spacing: 0.5, + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + final double width; + + final Color? borderColor; + + @override + Widget? childForSlot(SeriesSlot slot) { + if (dataSource == null) { + return null; + } + switch (slot) { + case SeriesSlot.dataLabel: + return dataLabelSettings.isVisible + ? CartesianDataLabelContainer( + series: this, + dataSource: dataSource!, + mapper: dataLabelMapper, + builder: dataLabelSettings.builder, + settings: dataLabelSettings, + positions: positions, + ) + : null; + + case SeriesSlot.marker: + // TODO(VijayakumarM): Check bang operator. + return MarkerContainer( + series: this, + dataSource: dataSource!, + settings: markerSettings.copyWith(isVisible: true), + ); + + case SeriesSlot.trendline: + return trendlines != null + ? TrendlineContainer(trendlines: trendlines!) + : null; + } + } + + @override + List get positions => [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ChartDataPointType.median, + ChartDataPointType.outliers, + ]; + + /// Create the Box and Whisker series renderer. + @override + BoxAndWhiskerSeriesRenderer createRenderer() { + BoxAndWhiskerSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = + onCreateRenderer!(this) as BoxAndWhiskerSeriesRenderer; + return seriesRenderer; + } + return BoxAndWhiskerSeriesRenderer(); + } + + @override + BoxAndWhiskerSeriesRenderer createRenderObject(BuildContext context) { + final BoxAndWhiskerSeriesRenderer renderer = + super.createRenderObject(context) as BoxAndWhiskerSeriesRenderer; + renderer + ..yValueMapper = yValueMapper + ..boxPlotMode = boxPlotMode + ..showMean = showMean + ..spacing = spacing + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + BoxAndWhiskerSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..yValueMapper = yValueMapper + ..boxPlotMode = boxPlotMode + ..showMean = showMean + ..spacing = spacing + ..borderColor = borderColor; + } +} + +/// Creates series renderer for box and whisker series. +class BoxAndWhiskerSeriesRenderer + extends BoxAndWhiskerSeriesRendererBase + with SegmentAnimationMixin { + /// Calling the default constructor of [BoxAndWhiskerSeriesRenderer] class. + BoxAndWhiskerSeriesRenderer(); + + final List minimumValues = []; + final List maximumValues = []; + final List upperValues = []; + final List lowerValues = []; + + final List medianValues = []; + final List meanValues = []; + final List> outliersValues = >[]; + + @override + set color(Color? value) { + markerSettings = markerSettings.copyWith( + borderColor: value ?? paletteColor, + ); + super.color = value; + } + + @override + set paletteColor(Color value) { + markerSettings = markerSettings.copyWith(borderColor: color ?? value); + super.paletteColor = value; + } + + bool get showMean => _showMean; + bool _showMean = true; + set showMean(bool value) { + if (_showMean != value) { + _showMean = value; + } + } + + BoxPlotMode get boxPlotMode => _boxPlotMode; + BoxPlotMode _boxPlotMode = BoxPlotMode.normal; + set boxPlotMode(BoxPlotMode value) { + if (_boxPlotMode != value) { + _boxPlotMode = value; + } + } + + Color? get borderColor => _borderColor; + Color? _borderColor = Colors.transparent; + set borderColor(Color? value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsPaint(); + } + } + + @override + set markerSettings(MarkerSettings value) { + super.markerSettings = value.copyWith(isVisible: true); + } + + @override + double legendIconBorderWidth() { + return 2; + } + + void _resetDataSourceHolders() { + minimumValues.clear(); + maximumValues.clear(); + upperValues.clear(); + lowerValues.clear(); + medianValues.clear(); + meanValues.clear(); + outliersValues.clear(); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _resetDataSourceHolders(); + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + populateChartPoints(); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _resetDataSourceHolders(); + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + @override + num trackballYValue(int index) => maximumValues[index]; + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + chartPoints.clear(); + if (parent == null) { + return; + } + + if (parent!.onDataLabelRender == null && + parent!.onTooltipRender == null && + dataLabelSettings.builder == null) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: xValues[i], + ); + chartPoints.add(point); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + final BoxPlotQuartileValues boxPlotValues = BoxPlotQuartileValues(); + _findBoxPlotValues(yValues[index], boxPlotMode, boxPlotValues); + + final num minimum = boxPlotValues.minimum ?? double.nan; + final num maximum = boxPlotValues.maximum ?? double.nan; + final num lowerQuartile = boxPlotValues.lowerQuartile ?? double.nan; + final num upperQuartile = boxPlotValues.upperQuartile ?? double.nan; + final num mean = boxPlotValues.average ?? double.nan; + final num median = boxPlotValues.median ?? double.nan; + + maximumValues.add(maximum); + minimumValues.add(minimum); + lowerValues.add(lowerQuartile); + upperValues.add(upperQuartile); + meanValues.add(mean); + medianValues.add(median); + + if (boxPlotValues.outliers != null) { + if (boxPlotValues.outliers!.isEmpty) { + outliersValues.add([double.nan]); + } else { + outliersValues.add(boxPlotValues.outliers!); + } + } + + if (chartPoints.isNotEmpty && chartPoints.length > index) { + (chartPoints[index] as CartesianChartPoint) + ..maximum = maximum + ..minimum = minimum + ..lowerQuartile = lowerQuartile + ..upperQuartile = upperQuartile + ..mean = mean + ..median = median + ..outliers = boxPlotValues.outliers; + } + + segment as BoxAndWhiskerSegment + ..series = this + ..x = xValues[index] + .._minimum = minimum + .._maximum = maximum + .._lowerQuartile = lowerQuartile + .._upperQuartile = upperQuartile + .._median = median + .._mean = mean + ..outliers = boxPlotValues.outliers; + } + + @override + void performLayout() { + super.performLayout(); + + final List dataLabelMaximumValues = []; + final List dataLabelMinimumValues = []; + final List dataLabelLowerValues = []; + final List dataLabelUpperValues = []; + final List dataLabelMedianValues = []; + final List dataLabelXValues = []; + final List dataLabelOutliersValues = []; + final int length = outliersValues.length; + for (int i = 0; i < length; i++) { + final List outliers = outliersValues[i]; + final num currentXValue = xValues[i]; + final int outliersLength = outliers.length; + for (int j = 0; j < outliersLength; j++) { + dataLabelXValues.add(currentXValue); + dataLabelOutliersValues.add(outliers[j]); + if (j == 0) { + dataLabelMaximumValues.add(maximumValues[i]); + dataLabelMinimumValues.add(minimumValues[i]); + dataLabelLowerValues.add(lowerValues[i]); + dataLabelUpperValues.add(upperValues[i]); + dataLabelMedianValues.add(medianValues[i]); + } else { + dataLabelMaximumValues.add(double.nan); + dataLabelMinimumValues.add(double.nan); + dataLabelLowerValues.add(double.nan); + dataLabelUpperValues.add(double.nan); + dataLabelMedianValues.add(double.nan); + } + } + } + + if (markerContainer != null) { + markerContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = dataLabelXValues + ..yLists = >[dataLabelOutliersValues] + ..animation = markerAnimation + ..layout(constraints); + } + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = dataLabelXValues + ..yLists = >[ + dataLabelMaximumValues, + dataLabelMinimumValues, + dataLabelLowerValues, + dataLabelUpperValues, + dataLabelMedianValues, + dataLabelOutliersValues, + ] + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + + if (dataLabelSettings.isVisible && + dataLabelSettings.labelIntersectAction != LabelIntersectAction.none) { + dataLabelContainer!.handleDataLabelCollision(this); + } + } + } + + @override + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + final int index = current.dataPointIndex; + if (position == ChartDataPointType.open) { + final num lower = lowerValues[index]; + final num upper = upperValues[index]; + + return lower >= upper + ? ChartDataLabelAlignment.top + : ChartDataLabelAlignment.bottom; + } else if (position == ChartDataPointType.close) { + final num lower = lowerValues[index]; + final num upper = upperValues[index]; + return upper <= lower + ? ChartDataLabelAlignment.top + : ChartDataLabelAlignment.bottom; + } else if (position == ChartDataPointType.outliers) { + return ChartDataLabelAlignment.bottom; + } else if (position == ChartDataPointType.median) { + return ChartDataLabelAlignment.middle; + } + + return alignment == ChartDataLabelAlignment.auto + ? ChartDataLabelAlignment.outer + : alignment; + } + + void _findBoxPlotValues( + List yValues, + BoxPlotMode mode, + BoxPlotQuartileValues boxPlotValues, + ) { + final int yCount = yValues.length; + if (yCount == 0) { + return; + } + + boxPlotValues.average = + (yValues.fold(0, (num x, num? y) => (x.toDouble()) + y!)) / yCount; + if (mode == BoxPlotMode.exclusive) { + boxPlotValues.lowerQuartile = _exclusiveQuartileValue( + yValues, + yCount, + 0.25, + ); + boxPlotValues.upperQuartile = _exclusiveQuartileValue( + yValues, + yCount, + 0.75, + ); + boxPlotValues.median = _exclusiveQuartileValue(yValues, yCount, 0.5); + } else if (mode == BoxPlotMode.inclusive) { + boxPlotValues.lowerQuartile = _inclusiveQuartileValue( + yValues, + yCount, + 0.25, + ); + boxPlotValues.upperQuartile = _inclusiveQuartileValue( + yValues, + yCount, + 0.75, + ); + boxPlotValues.median = _inclusiveQuartileValue(yValues, yCount, 0.5); + } else { + boxPlotValues.median = _median(yValues); + _quartileValues(yValues, yCount, boxPlotValues); + } + _minMaxOutlier(yValues, yCount, boxPlotValues); + } + + double _exclusiveQuartileValue(List yValues, int count, num percentile) { + if (count == 0) { + return 0; + } else if (count == 1) { + return yValues[0].toDouble(); + } + num value = 0; + final num rank = percentile * (count + 1); + final int integerRank = rank.abs().floor(); + final num fractionRank = rank - integerRank; + if (integerRank == 0) { + value = yValues[0]; + } else if (integerRank > count - 1) { + value = yValues[count - 1]; + } else { + value = + fractionRank * (yValues[integerRank] - yValues[integerRank - 1]) + + yValues[integerRank - 1]; + } + return value.toDouble(); + } + + double _inclusiveQuartileValue(List yValues, int count, num percentile) { + if (count == 0) { + return 0; + } else if (count == 1) { + return yValues[0].toDouble(); + } + num value = 0; + final num rank = percentile * (count - 1); + final int integerRank = rank.abs().floor(); + final num fractionRank = rank - integerRank; + value = + fractionRank * (yValues[integerRank + 1] - yValues[integerRank]) + + yValues[integerRank]; + return value.toDouble(); + } + + double _median(List values) { + final int yLength = values.length; + if (yLength == 0) { + return 0; + } + + final int half = (yLength / 2).floor(); + return (yLength % 2 != 0 + ? values[half] + : ((values[half - 1] + values[half]) / 2.0)) + .toDouble(); + } + + void _quartileValues( + List yValues, + int count, + BoxPlotQuartileValues boxPlotQuartileValues, + ) { + if (count == 1) { + boxPlotQuartileValues.lowerQuartile = yValues[0].toDouble(); + boxPlotQuartileValues.upperQuartile = yValues[0].toDouble(); + } + final int halfLength = count ~/ 2; + final List lowerQuartileArray = yValues.sublist(0, halfLength); + final List upperQuartileArray = yValues.sublist( + count.isEven ? halfLength : halfLength + 1, + count, + ); + boxPlotQuartileValues.lowerQuartile = _median(lowerQuartileArray); + boxPlotQuartileValues.upperQuartile = _median(upperQuartileArray); + } + + void _minMaxOutlier( + List yValues, + int count, + BoxPlotQuartileValues boxPlotQuartileValues, + ) { + 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]); + } 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]); + } else { + boxPlotQuartileValues.maximum = yValues[i]; + break; + } + } + } + + @override + BoxAndWhiskerSegment createSegment() => BoxAndWhiskerSegment(); + + @override + void customizeSegment(ChartSegment segment) { + final BoxAndWhiskerSegment boxAndWhiskerSegment = + segment as BoxAndWhiskerSegment; + final Color? customBorderColor = + borderColor == Colors.transparent || borderColor == null + ? Colors.black + : borderColor; + updateSegmentColor(boxAndWhiskerSegment, customBorderColor, borderWidth); + updateSegmentGradient( + boxAndWhiskerSegment, + gradientBounds: boxAndWhiskerSegment.segmentRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.boxAndWhiskerSeries; + + @override + void dispose() { + _resetDataSourceHolders(); + super.dispose(); + } +} + +/// Creates the segments for box and whisker series. +/// +/// 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. +class BoxAndWhiskerSegment extends ChartSegment { + final List _medianLinePoints = []; + final List _meanLinePoints = []; + final List _outlierPoints = []; + + Rect? _oldSegmentRect; + Rect? segmentRect; + + final List _oldPoints = []; + + late BoxAndWhiskerSeriesRenderer series; + List? outliers = []; + + late num x; + + num get lowerQuartile => _lowerQuartile; + num _lowerQuartile = double.nan; + + num get upperQuartile => _upperQuartile; + num _upperQuartile = double.nan; + + num get minimum => _minimum; + num _minimum = double.nan; + + num get maximum => _maximum; + num _maximum = double.nan; + + num get median => _median; + num _median = double.nan; + + num get mean => _mean; + num _mean = double.nan; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + + _oldSegmentRect = Rect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldPoints.clear(); + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || + lowerQuartile.isNaN || + upperQuartile.isNaN || + minimum.isNaN || + maximum.isNaN || + median.isNaN || + mean.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + _oldPoints.clear(); + points.clear(); + return; + } + + points.clear(); + _medianLinePoints.clear(); + _meanLinePoints.clear(); + _outlierPoints.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double centerY = lowerQuartile + (upperQuartile - lowerQuartile) / 2; + if (!lowerQuartile.isNaN && !upperQuartile.isNaN) { + double x1 = transformX(left, upperQuartile); + double y1 = transformY(left, upperQuartile); + double x2 = transformX(right, lowerQuartile); + double y2 = transformY(right, lowerQuartile); + + if (y1 > y2) { + final double temp = y1; + y1 = y2; + y2 = temp; + } + + if (x1 > x2) { + final double temp = x1; + x1 = x2; + x2 = temp; + } + + segmentRect = Rect.fromLTRB(x1, y1, x2, y2); + _oldSegmentRect ??= Rect.fromLTRB( + transformX(left, centerY), + transformY(left, centerY), + transformX(right, centerY), + transformY(right, centerY), + ); + } + + if (segmentRect == null) { + return; + } + + final double centerX = (right + left) / 2; + _addMinMaxPoints(transformX, transformY, centerX, centerY, left, right); + + if (!median.isNaN) { + _medianLinePoints.add( + Offset(transformX(left, median), transformY(left, median)), + ); + _medianLinePoints.add( + Offset(transformX(right, median), transformY(right, median)), + ); + } + + if (series.showMean && !mean.isNaN) { + final double meanX = transformX(centerX, mean); + final double meanY = transformY(centerX, mean); + + final double markerHeight = series.markerSettings.height; + final double markerWidth = series.markerSettings.width; + _meanLinePoints.add( + Offset(meanX + markerWidth / 2, meanY - markerHeight / 2), + ); + _meanLinePoints.add( + Offset(meanX - markerWidth / 2, meanY + markerHeight / 2), + ); + + _meanLinePoints.add( + Offset(meanX + markerWidth / 2, meanY + markerHeight / 2), + ); + _meanLinePoints.add( + Offset(meanX - markerWidth / 2, meanY - markerHeight / 2), + ); + } + + if (outliers != null && outliers!.isNotEmpty) { + final int length = outliers!.length; + for (int i = 0; i < length; i++) { + final num outlier = outliers![i]; + final double outlierX = transformX(centerX, outlier); + final double outlierY = transformY(centerX, outlier); + _outlierPoints.add(Offset(outlierX, outlierY)); + } + } + } + + void _addMinMaxPoints( + PointToPixelCallback transformX, + PointToPixelCallback transformY, + double centerX, + double centerY, + num left, + num right, + ) { + if (!minimum.isNaN && !maximum.isNaN) { + final Offset maxStart = Offset( + transformX(left, maximum), + transformY(left, maximum), + ); + final Offset maxEnd = Offset( + transformX(right, maximum), + transformY(right, maximum), + ); + points.add(maxStart); + points.add(maxEnd); + + final Offset maxConnectorStart = Offset( + transformX(x, upperQuartile), + transformY(x, upperQuartile), + ); + final Offset maxConnectorEnd = Offset( + transformX(x, maximum), + transformY(x, maximum), + ); + points.add(maxConnectorStart); + points.add(maxConnectorEnd); + + final Offset minStart = Offset( + transformX(left, minimum), + transformY(left, minimum), + ); + final Offset minEnd = Offset( + transformX(right, minimum), + transformY(right, minimum), + ); + points.add(minStart); + points.add(minEnd); + + final Offset minConnectorStart = Offset( + transformX(x, lowerQuartile), + transformY(x, lowerQuartile), + ); + final Offset minConnectorEnd = Offset( + transformX(x, minimum), + transformY(x, minimum), + ); + points.add(minConnectorStart); + points.add(minConnectorEnd); + + if (_oldPoints.isEmpty) { + // Max points. + final Offset start = Offset( + transformX(left, centerY), + transformY(left, centerY), + ); + final Offset end = Offset( + transformX(right, centerY), + transformY(right, centerY), + ); + _oldPoints.add(start); + _oldPoints.add(end); + + final Offset center = Offset( + transformX(centerX, centerY), + transformY(centerX, centerY), + ); + _oldPoints.add(center); + _oldPoints.add(center); + + // Min points. + _oldPoints.add(start); + _oldPoints.add(end); + _oldPoints.add(center); + _oldPoints.add(center); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + } + + @override + bool contains(Offset position) { + if (_outlierPoints.isNotEmpty) { + final MarkerSettings marker = series.markerSettings; + final int length = _outlierPoints.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + _outlierPoints[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + } + + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + maximum: maximum, + minimum: minimum, + median: median, + mean: mean, + upperQuartile: upperQuartile, + lowerQuartile: lowerQuartile, + outliers: outliers, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + Rect outlierBounds = Rect.zero; + int outlierIndex = -1; + + if (position != null) { + final int length = _outlierPoints.length; + for (int i = 0; i < length; i++) { + final ChartMarker marker = series.markerAt(i); + final Rect outlierRect = Rect.fromCenter( + center: _outlierPoints[i], + width: marker.width, + height: marker.height, + ); + if (outlierRect.contains(position)) { + outlierBounds = outlierRect; + outlierIndex = i; + break; + } + } + } + + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + Offset primaryPos; + Offset secondaryPos; + if (outlierIndex != -1) { + primaryPos = series.localToGlobal(outlierBounds.topCenter); + secondaryPos = series.localToGlobal(outlierBounds.bottomCenter); + } else { + if (points.isNotEmpty && points.length == 8) { + primaryPos = secondaryPos = series.localToGlobal(points[3]); + } else { + primaryPos = + tooltipPosition == TooltipPosition.pointer + ? series.localToGlobal(position ?? segmentRect!.topCenter) + : series.localToGlobal(segmentRect!.topCenter); + secondaryPos = primaryPos; + } + } + return ChartTooltipInfo( + primaryPosition: primaryPos, + secondaryPosition: secondaryPos, + text: series.tooltipText(chartPoint, outlierIndex), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + Offset primaryPos; + if (points.isNotEmpty && points.length == 8) { + primaryPos = points[3]; + } else { + primaryPos = Offset( + series.pointToPixelX(x, chartPoint.upperQuartile!), + series.pointToPixelY(x, chartPoint.upperQuartile!), + ); + } + + return ChartTrackballInfo( + position: primaryPos, + maxYPos: series.pointToPixelY(x, chartPoint.maximum!), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + final Paint strokePaint = getStrokePaint(); + if (points.isNotEmpty && + points.length == 8 && + strokePaint.strokeWidth > 0 && + strokePaint.color != Colors.transparent) { + final Offset? maxStart = Offset.lerp( + _oldPoints[0], + points[0], + animationFactor, + ); + final Offset? maxEnd = Offset.lerp( + _oldPoints[1], + points[1], + animationFactor, + ); + if (maxStart != null && maxEnd != null) { + drawDashes( + canvas, + series.dashArray, + strokePaint, + start: maxStart, + end: maxEnd, + ); + } + + final Offset? maxConnectorStart = Offset.lerp( + _oldPoints[2], + points[2], + animationFactor, + ); + final Offset? maxConnectorEnd = Offset.lerp( + _oldPoints[3], + points[3], + animationFactor, + ); + if (maxConnectorStart != null && maxConnectorEnd != null) { + drawDashes( + canvas, + series.dashArray, + strokePaint, + start: maxConnectorStart, + end: maxConnectorEnd, + ); + } + + final Offset? minStart = Offset.lerp( + _oldPoints[4], + points[4], + animationFactor, + ); + final Offset? minEnd = Offset.lerp( + _oldPoints[5], + points[5], + animationFactor, + ); + if (minStart != null && minEnd != null) { + drawDashes( + canvas, + series.dashArray, + strokePaint, + start: minStart, + end: minEnd, + ); + } + + final Offset? minConnectorStart = Offset.lerp( + _oldPoints[6], + points[6], + animationFactor, + ); + final Offset? minConnectorEnd = Offset.lerp( + _oldPoints[7], + points[7], + animationFactor, + ); + if (minConnectorStart != null && minConnectorEnd != null) { + drawDashes( + canvas, + series.dashArray, + strokePaint, + start: minConnectorStart, + end: minConnectorEnd, + ); + } + } + + if (segmentRect == null) { + return; + } + + final Rect? paintRect = Rect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRect == null) { + return; + } + + final Paint fillPaint = getFillPaint(); + if (fillPaint.color != Colors.transparent && !paintRect.isEmpty) { + canvas.drawRect(paintRect, fillPaint); + } + + final double strokeWidth = strokePaint.strokeWidth; + if (strokeWidth > 0 && strokePaint.color != Colors.transparent) { + final Path strokePath = strokePathFromRRect( + RRect.fromRectAndRadius(paintRect, Radius.zero), + strokeWidth, + ); + drawDashes(canvas, series.dashArray, strokePaint, path: strokePath); + } + + if (_medianLinePoints.isNotEmpty) { + drawDashes( + canvas, + series.dashArray, + strokePaint, + start: _medianLinePoints[0], + end: _medianLinePoints[1], + ); + } + + if (animationFactor > 0.75 && _meanLinePoints.isNotEmpty) { + // Mean animation starts at 0.75 and ends at 1.0. So, the animation falls + // only 0.25. So, 0.25 * 4 = 1.0. So, the animation factor is multiplied + // by 4 to get the animation factor for mean line. + final double opacity = (animationFactor - 0.75) * 4.0; + final Paint meanPaint = + Paint() + ..color = strokePaint.color.withValues(alpha: opacity) + ..strokeWidth = strokePaint.strokeWidth + ..shader = strokePaint.shader + ..style = strokePaint.style + ..strokeCap = strokePaint.strokeCap + ..strokeJoin = strokePaint.strokeJoin + ..strokeMiterLimit = strokePaint.strokeMiterLimit; + drawDashes( + canvas, + series.dashArray, + meanPaint, + start: _meanLinePoints[0], + end: _meanLinePoints[1], + ); + drawDashes( + canvas, + series.dashArray, + meanPaint, + start: _meanLinePoints[2], + end: _meanLinePoints[3], + ); + } + } + + @override + void dispose() { + points.clear(); + _oldPoints.clear(); + _medianLinePoints.clear(); + _meanLinePoints.clear(); + _outlierPoints.clear(); + segmentRect = null; + super.dispose(); + } +} + +/// Represents the class for box plot quartile values. +class BoxPlotQuartileValues { + /// Creates an instance of box plot quartile values. + BoxPlotQuartileValues({ + this.minimum, + this.maximum, + this.upperQuartile, + this.lowerQuartile, + this.average, + this.median, + this.mean, + }); + + /// Specifies the value of minimum. + num? minimum; + + /// Specifies the value of maximum. + num? maximum; + + /// Specifies the list of outliers. + List? outliers = []; + + /// Specifies the value of the upper quartiles. + double? upperQuartile; + + /// Specifies the value of lower quartiles. + double? lowerQuartile; + + /// Specifies the value of average. + num? average; + + /// Specifies the value of median. + num? median; + + /// Specifies the mean value. + num? mean; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart new file mode 100644 index 000000000..fe16a9b32 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart @@ -0,0 +1,641 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/element_widget.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// This class holds the properties of the bubble series. +/// +/// To render a bubble chart, create an instance of [BubbleSeries], and add it +/// to the series collection property of [SfCartesianChart]. +/// A bubble chart requires three fields (X, Y, and Size) to plot a point. Here, +/// [sizeValueMapper] is used to map the size of each bubble segment +/// from the data source. +/// +/// Provide the options for color, opacity, border color, and border width to +/// customize the appearance. +@immutable +class BubbleSeries extends XyDataSeries { + /// Creating an argument constructor of BubbleSeries class. + const BubbleSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + this.sizeValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.color, + super.markerSettings, + super.trendlines, + this.minimumRadius = 3.0, + this.maximumRadius = 10.0, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.name, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.sortingOrder, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Maximum radius value of the bubble in the series. + /// + /// Defaults to `10`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// maximumRadius: 9 + /// ), + /// ], + /// ); + /// } + /// ``` + final double maximumRadius; + + /// Minimum radius value of the bubble in the series. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// minimumRadius: 9 + /// ), + /// ], + /// ); + /// } + /// ``` + final double minimumRadius; + + /// Field in the data source, which is considered as size of the bubble for + /// all the data points. + /// + /// _Note:_ This is applicable only for bubble series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// dataSource: chartData, + /// sizeValueMapper: (BubbleColors sales, _) => sales.bubbleSize, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// BubbleColors(92.2, 7.8, 1.347), + /// BubbleColors(74, 6.5, 1.241), + /// BubbleColors(90.4, 6.0, 0.238), + /// BubbleColors(99.4, 2.2, 0.197), + /// ]; + /// class BubbleColors { + /// BubbleColors(this.year, this.growth,[this.bubbleSize]); + /// final num year; + /// final num growth; + /// final num bubbleSize; + /// } + /// ``` + final ChartValueMapper? sizeValueMapper; + + final Color borderColor; + + /// Create the bubble series renderer. + @override + BubbleSeriesRenderer createRenderer() { + BubbleSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as BubbleSeriesRenderer; + return seriesRenderer; + } + return BubbleSeriesRenderer(); + } + + @override + BubbleSeriesRenderer createRenderObject(BuildContext context) { + final BubbleSeriesRenderer renderer = + super.createRenderObject(context) as BubbleSeriesRenderer; + renderer + ..maximumRadius = maximumRadius + ..minimumRadius = minimumRadius + ..sizeValueMapper = sizeValueMapper + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + BubbleSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..maximumRadius = maximumRadius + ..minimumRadius = minimumRadius + ..sizeValueMapper = sizeValueMapper + ..borderColor = borderColor; + } +} + +/// Creates series renderer for bubble series. +class BubbleSeriesRenderer extends XyDataSeriesRenderer + with SegmentAnimationMixin { + /// Calling the default constructor of BubbleSeriesRenderer class. + BubbleSeriesRenderer(); + + double get maximumRadius => _maximumRadius; + double _maximumRadius = 10; + set maximumRadius(double value) { + if (_maximumRadius != value) { + _maximumRadius = value; + assert( + _maximumRadius >= 0, + 'The maximum radius should be greater than or equal to 0.', + ); + canUpdateOrCreateSegments = true; + markNeedsLayout(); + } + } + + double get minimumRadius => _minimumRadius; + double _minimumRadius = 3; + set minimumRadius(double value) { + if (_minimumRadius != value) { + _minimumRadius = value; + assert( + _minimumRadius >= 0, + 'The minimum radius should be greater than or equal to 0.', + ); + canUpdateOrCreateSegments = true; + markNeedsLayout(); + } + } + + ChartValueMapper? sizeValueMapper; + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + final List _chaoticSizes = []; + final List _sizes = []; + + num _minBubbleSize = double.infinity; + num _maxBubbleSize = double.negativeInfinity; + + void _resetDataSourceHolders() { + _chaoticSizes.clear(); + _sizes.clear(); + } + + @override + void populateDataSource([ + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _resetDataSourceHolders(); + if (sortingOrder == SortingOrder.none) { + super.populateDataSource( + >[], + >[], + >[], + >[_sizeByMapper], + >[_sizes], + ); + } else { + super.populateDataSource( + >[], + >[], + >[], + >[_sizeByMapper], + >[_chaoticSizes], + >[_sizes], + ); + } + + _minBubbleSize = double.infinity; + _maxBubbleSize = double.negativeInfinity; + for (final num size in _sizes) { + _minBubbleSize = min(_minBubbleSize, size); + _maxBubbleSize = max(_maxBubbleSize, size); + } + populateChartPoints(); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (sortingOrder == SortingOrder.none) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + >[], + >[], + >[], + >[_sizeByMapper], + >[_sizes], + ); + } else { + _sizes.clear(); + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + >[], + >[], + >[], + >[_sizeByMapper], + >[_chaoticSizes], + >[_sizes], + ); + } + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[_sizes]; + positions = [ChartDataPointType.bubbleSize]; + } else { + yLists.add(_sizes); + positions!.add(ChartDataPointType.bubbleSize); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + num _sizeByMapper(T type, int value) { + if (sizeValueMapper != null) { + final num? fValue = sizeValueMapper!(type, value); + return fValue ?? minimumRadius; + } + return minimumRadius; + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + num radius = _sizes[index]; + if (radius.isNaN || sizeValueMapper == null) { + radius = minimumRadius; + } else { + final num sizeDelta = _maxBubbleSize - _minBubbleSize; + if (sizeDelta == 0) { + radius = radius == 0 ? minimumRadius : maximumRadius; + } else { + final num radiusDiff = maximumRadius - minimumRadius; + radius = minimumRadius + radiusDiff * (radius / _maxBubbleSize); + } + } + + segment as BubbleSegment + ..series = this + ..x = xValues[index] + ..y = yValues[index] + ..radius = radius + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + BubbleSegment createSegment() => BubbleSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final BubbleSegment bubbleSegment = segment as BubbleSegment; + updateSegmentColor(bubbleSegment, borderColor, borderWidth); + updateSegmentGradient( + bubbleSegment, + gradientBounds: bubbleSegment.segmentRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final ChartDataLabelAlignment alignment = label.labelAlignment; + final ChartSegment segment = segments[label.dataPointIndex]; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.bottom: + return super.dataLabelSurfaceColor(label); + + case ChartDataLabelAlignment.middle: + return segment.getFillPaint().color; + } + } + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.bubbleSeries; + + @override + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + return alignment == ChartDataLabelAlignment.auto + ? ChartDataLabelAlignment.outer + : alignment; + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.middle: + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return translateTransform( + current.x!, + current.y!, + translationX, + translationY, + ); + + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + final BubbleSegment segment = + segments[current.dataPointIndex] as BubbleSegment; + translationX = -margin.left; + translationY = + -(segment.radius + + dataLabelPadding + + size.height + + margin.vertical); + return translateTransform( + current.x!, + current.y!, + translationX, + translationY, + ); + + case ChartDataLabelAlignment.bottom: + final BubbleSegment segment = + segments[current.dataPointIndex] as BubbleSegment; + translationX = -margin.left; + translationY = segment.radius + dataLabelPadding; + return translateTransform( + current.x!, + current.y!, + translationX, + translationY, + ); + } + } + + @override + void dispose() { + _resetDataSourceHolders(); + super.dispose(); + } +} + +/// Creates the segments for bubble series. +/// +/// 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. +class BubbleSegment extends ChartSegment { + late BubbleSeriesRenderer series; + late num x; + late num y; + late num radius; + + Rect? _oldSegmentRect; + Rect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = Rect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || y.isNaN || radius.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final double pixelX = series.pointToPixelX(x, y); + final double pixelY = series.pointToPixelY(x, y); + final Offset center = Offset(pixelX, pixelY); + final double size = radius * 2.0; + segmentRect = Rect.fromCircle(center: center, radius: size); + _oldSegmentRect ??= Rect.fromCircle(center: center, radius: 0.0); + points.add(center); + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: x, + y: y, + bubbleSize: series._sizes[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal(segmentRect!.topCenter), + secondaryPosition: series.localToGlobal(segmentRect!.bottomCenter), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![currentSegmentIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + markerColors: [fillPaint.color], + markerType: series.markerAt(pointIndex ?? currentSegmentIndex).type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: segmentRect!.center, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (segmentRect == null) { + return; + } + final Rect? paintRect = Rect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRect == null || paintRect.isEmpty) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawOval(paintRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = + Path()..addOval(paintRect.deflate(strokeWidth / 2)); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart new file mode 100644 index 000000000..60f30aa08 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart @@ -0,0 +1,542 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../interactions/tooltip.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// 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]. +/// 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. +/// +/// Provides options for color, opacity, border color, and border width +/// to customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=g5cniDExpRw} +@immutable +class CandleSeries extends FinancialSeriesBase { + /// Creating an argument constructor of CandleSeries class. + const CandleSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.lowValueMapper, + required super.highValueMapper, + required super.openValueMapper, + required super.closeValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.bearColor, + super.bullColor, + super.enableSolidCandles, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + super.showIndicationForSameValues = false, + super.trendlines, + this.borderRadius = BorderRadius.zero, + super.width, + super.spacing, + }); + + /// Customize the corners of the candle. + /// + /// Each corner can be customized with a specific value or with the same value for all corners. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: [ + /// CandleSeries<_SalesData, String>( + /// borderRadius: BorderRadius.all(Radius.circular(5)) + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + @override + CandleSeriesRenderer createRenderObject(BuildContext context) { + final CandleSeriesRenderer renderer = + super.createRenderObject(context) as CandleSeriesRenderer; + return renderer..borderRadius = borderRadius; + } + + @override + void updateRenderObject( + BuildContext context, + CandleSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.borderRadius = borderRadius; + } + + /// Create the candle series renderer. + @override + CandleSeriesRenderer createRenderer() { + CandleSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as CandleSeriesRenderer; + return seriesRenderer; + } + return CandleSeriesRenderer(); + } +} + +/// Creates series renderer for candle series. +class CandleSeriesRenderer extends FinancialSeriesRendererBase + with SegmentAnimationMixin { + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (value != _borderRadius) { + _borderRadius = value; + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + final num open = openValues[index]; + final num close = closeValues[index]; + final bool isHollow = close > open; + + segment as CandleSegment + ..series = this + ..currentSegmentIndex = index + ..x = xValues[index] + ..high = highValues[index] + ..low = lowValues[index] + ..open = open + ..close = close + ..top = isHollow ? close : open + ..bottom = isHollow ? open : close + ..isEmpty = isEmpty(index); + } + + @override + CandleSegment createSegment() => CandleSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.candleSeries; + + @override + void customizeSegment(ChartSegment segment) { + final int index = segment.currentSegmentIndex; + num previousClose = double.negativeInfinity; + if (index != 0) { + previousClose = closeValues[index - 1]; + } + + final num open = openValues[index]; + final num close = closeValues[index]; + final bool isHollow = close > open; + final bool isBull = close > previousClose; + + // TODO(Natrayasf): Comment section is pending. + // Naming of colors. + // +--------+-----------+ + // | Color | colorName | + // +--------+-----------+ + // | Green | bullColor | + // | Red | bearColor | + // +--------+-----------+ + + // Set [enableSolidCandles: true]. + // +--------+--------+----------------+ + // | Hollow | Color | Rendering | + // +--------+--------+----------------+ + // | true | Green | Solid, Fill | + // | false | Red | Solid, Fill | + // +--------+--------+----------------+ + + // Set [enableSolidCandles: false]. + // +---------+----------+-------------+-------------+ + // | isBull | isHollow | FillColor | StrokeColor | + // +---------+----------+-------------+-------------+ + // | true | true | transparent | Green | + // | false | true | transparent | Red | + // | true | false | Green | Green | + // | false | false | Red | Red | + // +---------+----------+-------------+-------------+ + + late Color color; + if (enableSolidCandles) { + color = isHollow ? bullColor : bearColor; + final Color? segmentColor = + pointColorMapper != null && + pointColors[segment.currentSegmentIndex] != null + ? null + : color; + updateSegmentColor( + segment, + segmentColor, + borderWidth, + fillColor: segmentColor, + isLineType: true, + ); + } else { + color = isBull ? bullColor : bearColor; + final Color? segmentColor = + pointColorMapper != null && + pointColors[segment.currentSegmentIndex] != null + ? null + : color; + updateSegmentColor( + segment, + segmentColor, + borderWidth, + fillColor: isHollow ? Colors.transparent : segmentColor, + isLineType: true, + ); + } + updateSegmentGradient(segment); + } + + @override + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { + return chartThemeData.plotAreaBackgroundColor!; + } else if (chartThemeData.backgroundColor != Colors.transparent) { + return chartThemeData.backgroundColor!; + } + return themeData.colorScheme.surface; + } +} + +/// Segment class for candle series. +class CandleSegment extends ChartSegment { + late CandleSeriesRenderer series; + late num x; + late num high; + late num low; + late num open; + late num close; + late num top; + late num bottom; + + bool _isSameValue = false; + RRect? _oldSegmentRect; + RRect? segmentRect; + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldPoints.clear(); + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN || high.isNaN || low.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + _oldPoints.clear(); + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double centerX = (right + left) / 2; + double centerY = (top + bottom) / 2; + + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + + _oldSegmentRect ??= toRRect( + series.pointToPixelX(left, centerY), + series.pointToPixelY(left, centerY), + series.pointToPixelX(right, centerY), + series.pointToPixelY(right, centerY), + borderRadius, + ); + + _isSameValue = top == bottom; + + if (_isSameValue) { + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + } + + if (series.showIndicationForSameValues && high == low) { + centerY = (high + low) / 2; + final double x = transformX(centerX, centerY); + final double y = transformY(centerX, centerY); + if (series.isTransposed) { + points.add(Offset(x - 2, y)); + points.add(Offset(x, y)); + points.add(Offset(x + 2, y)); + points.add(Offset(x, y)); + } else { + points.add(Offset(x, y - 2)); + points.add(Offset(x, y)); + points.add(Offset(x, y + 2)); + points.add(Offset(x, y)); + } + } else { + points.add(Offset(transformX(centerX, high), transformY(centerX, high))); + points.add(Offset(transformX(centerX, top), transformY(centerX, top))); + + points.add(Offset(transformX(centerX, low), transformY(centerX, low))); + points.add( + Offset(transformX(centerX, bottom), transformY(centerX, bottom)), + ); + } + + if (_oldPoints.isEmpty) { + final Offset point = Offset( + transformX(centerX, centerY), + transformY(centerX, centerY), + ); + _oldPoints.add(point); + _oldPoints.add(point); + _oldPoints.add(point); + _oldPoints.add(point); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + high: high, + low: low, + open: open, + close: close, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect == null) { + return null; + } + + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + Offset primaryPos; + Offset secondaryPos; + if (points.isNotEmpty) { + primaryPos = series.localToGlobal(points[0]); + secondaryPos = primaryPos; + } else { + final Rect outerRect = segmentRect!.outerRect; + primaryPos = series.localToGlobal(outerRect.topCenter); + secondaryPos = series.localToGlobal(outerRect.bottomCenter); + } + return ChartTooltipInfo( + primaryPosition: primaryPos, + secondaryPosition: secondaryPos, + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + hasMultipleYValues: true, + markerColors: [series.paletteColor], + markerType: series.markerAt(pointIndex).type, + ); + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + Offset preferredPos; + if (points.isNotEmpty) { + preferredPos = Offset( + series.pointToPixelX(x, high), + series.pointToPixelY(x, high), + ); + } else { + preferredPos = Offset( + series.pointToPixelX(x, top), + series.pointToPixelX(x, top), + ); + } + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: series.pointToPixelY(x, high), + lowYPos: series.pointToPixelY(x, bottom), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: + fillPaint.color == Colors.transparent + ? strokePaint.color + : fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty || segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !_isSameValue) { + canvas.drawRRect(paintRRect!, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + Path strokePath; + if (_isSameValue) { + strokePath = Path()..addRRect(paintRRect!); + } else { + strokePath = strokePathFromRRect(paintRRect, paint.strokeWidth); + } + drawDashes(canvas, series.dashArray, paint, path: strokePath); + + final Offset start = + Offset.lerp(_oldPoints[0], points[0], animationFactor)!; + final Offset end = + Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + + final Offset start2 = + Offset.lerp(_oldPoints[2], points[2], animationFactor)!; + final Offset end2 = + Offset.lerp(_oldPoints[3], points[3], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start2, end: end2); + } + } + + @override + void dispose() { + points.clear(); + segmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart new file mode 100644 index 000000000..e863cbf11 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart @@ -0,0 +1,10801 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/circular_data_label.dart'; +import '../common/circular_data_label_helper.dart'; +import '../common/connector_line.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/element_widget.dart'; +import '../common/empty_points.dart'; +import '../common/legend.dart'; +import '../common/marker.dart'; +import '../interactions/selection.dart'; +import '../trendline/trendline.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/renderer_helper.dart'; +import '../utils/typedef.dart'; +import 'waterfall_series.dart'; + +enum SeriesSlot { trendline, marker, dataLabel } + +class ChartSeriesParentData extends ContainerBoxParentData {} + +/// This class holds the property of series. +/// +/// Chart series has property to render the series if the property data source +/// is empty it renders an empty chart. +/// [ChartSeries] is the base class, it has the property to set the name, +/// data source, border color and width to customize the series. +/// +/// Provides options that are extended by the other sub classes such as name, +/// point color mapper, data label mapper, animation +/// duration and border-width and color for customize the +/// appearance of the chart. +abstract class ChartSeries + extends SlottedMultiChildRenderObjectWidget { + /// Creating an argument constructor of ChartSeries class. + const ChartSeries({ + ValueKey? key, + this.dataSource, + this.xValueMapper, + this.pointColorMapper, + this.sortingOrder = SortingOrder.none, + this.sortFieldValueMapper, + this.dataLabelMapper, + this.name, + this.enableTooltip = true, + this.enableTrackball = true, + this.animationDuration = 1500, + this.color, + this.borderWidth = 2.0, + this.isVisibleInLegend = true, + this.legendIconType = LegendIconType.seriesType, + this.legendItemText, + this.opacity = 1.0, + this.animationDelay = 0, + this.initialIsVisible = true, + this.selectionBehavior, + this.initialSelectedDataIndexes, + this.emptyPointSettings = const EmptyPointSettings(), + this.dataLabelSettings = const DataLabelSettings(), + this.markerSettings = const MarkerSettings(), + this.onPointTap, + this.onPointDoubleTap, + this.onPointLongPress, + }) : super(key: key); + + /// Data required for rendering the series. If no data source is specified, + /// empty chart will be rendered without series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// SalesData(1, 23), + /// SalesData(2, 35), + /// SalesData(3, 19) + /// ]; + /// + /// class SalesData { + /// SalesData(this.x, this.y); + /// final double x; + /// final double y; + /// } + /// ``` + final List? dataSource; + + /// Field in the data source, which is considered as x-value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// SalesData(1, 23), + /// SalesData(2, 35), + /// SalesData(3, 19) + /// ]; + /// + /// class SalesData { + /// SalesData(this.x, this.y); + /// final double x; + /// final double y; + /// } + /// ``` + final ChartValueMapper? xValueMapper; + + /// Field in the data source, which is considered as fill color + /// for the data points. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (ColumnColors sales, _) => sales.x, + /// yValueMapper: (ColumnColors sales, _) => sales.y, + /// pointColorMapper: (ColumnColors sales, _) => + /// sales.pointColorMapper, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// ColumnColors(1991, 7.8, const Color.fromRGBO(0, 0, 255, 1)), + /// ColumnColors(1992, 6.5, const Color.fromRGBO(255, 0, 0, 1)), + /// ColumnColors(1993, 6.0, const Color.fromRGBO(255, 100, 102, 1)), + /// ]; + /// class ColumnColors { + /// ColumnColors(this.x, this.y,this.pointColorMapper); + /// final num x; + /// final num y; + /// final Color pointColorMapper; + /// } + /// ``` + final ChartValueMapper? pointColorMapper; + + /// Field in the data source, which is considered as text for the data points. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// primaryXAxis: DateTimeAxis(), + /// series: >[ + /// BarSeries( + /// dataSource: [ + /// SalesData(DateTime(2005, 0, 1), 'India', 16), + /// SalesData(DateTime(2006, 0, 1), 'China', 12), + /// SalesData(DateTime(2007, 0, 1), 'USA',18), + /// ], + /// dataLabelSettings: DataLabelSettings(isVisible:true), + /// dataLabelMapper: (SalesData data, _) => data.dataLabelText, + /// ), + /// ], + /// ); + /// } + /// class SalesData { + /// SalesData(this.year, this.dataLabelText, this.sales1); + /// final DateTime year; + /// final String dataLabelText; + /// final int sales1; + /// } + /// ``` + final ChartValueMapper? dataLabelMapper; + + /// Customizes the empty points, i.e. null data points in a series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// emptyPointSettings: EmptyPointSettings( + /// mode: EmptyPointMode.average + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final EmptyPointSettings emptyPointSettings; + + /// Customizes the data labels in a series. Data label is a text, which + /// displays the details about the data point. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataLabelSettings: DataLabelSettings(isVisible: true), + /// ), + /// ], + /// ); + /// } + /// ``` + final DataLabelSettings dataLabelSettings; + + /// Indication of data points. + /// + /// Marks the data point location with symbols for better + /// indication. The shape, color, border, and size of the marker + /// can be customized. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// markerSettings: MarkerSettings(isVisible: true) + /// ), + /// ], + /// ); + /// } + /// ``` + final MarkerSettings markerSettings; + + /// 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’ text prefix will be considered as series name. + /// + ///```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BubbleSeries( + /// name: 'Bubble Series' + /// ) + /// ] + /// ); + /// } + ///``` + 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. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// tooltipBehavior: TooltipBehavior(enable: true), + /// series: >[ + /// BubbleSeries( + /// enableTooltip: false, + /// ), + /// ], + /// ); + /// } + /// ``` + final bool enableTooltip; + + /// Enables or disables the trackball for this series. Trackball will display more + /// details about data points when tapping the data point region. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// trackballBehavior: TrackballBehavior( + /// enable: true, + /// ), + /// series: >[ + /// LineSeries<_SalesData, String>( + /// enableTrackball: false, + /// ), + /// ], + /// ); + /// } + /// ``` + + final bool enableTrackball; + + /// Duration of the series animation. It takes millisecond value as input. + /// + /// Defaults to `1500`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// animationDuration: 1000, + /// ), + /// ], + /// ); + /// } + /// ``` + final double animationDuration; + + /// Color of the series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// color: Colors.red, + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? color; + + /// Border width of the series. + /// + /// _Note:_ This is not applicable for line, spline, step line, + /// and fast line series types. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// borderColor: Colors.red, + /// borderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + 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. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend(isVisible:true), + /// series: >[ + /// LineSeries( + /// legendItemText: 'Legend' + /// ), + /// ], + /// ); + /// } + /// ``` + final String? legendItemText; + + /// Toggles the visibility of the legend item of this specific series + /// in the legend. + /// + /// If it is set to false, the legend item for this series + /// will not be visible in the legend. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend(isVisible:true), + /// series: >[ + /// LineSeries( + /// isVisibleInLegend: false + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isVisibleInLegend; + + /// 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. + /// + /// Defaults to `LegendIconType.seriesType`. + /// + /// Also refer [LegendIconType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// legend: Legend(isVisible:true), + /// series: >[ + /// LineSeries( + /// legendIconType: LegendIconType.diamond, + /// ), + /// ], + /// ); + /// } + /// ``` + final LegendIconType legendIconType; + + /// Customizes the data points or series on selection. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: SelectionBehavior( + /// enable:true + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final SelectionBehavior? selectionBehavior; + + /// List of data indexes to initially be selected. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// selectionBehavior: SelectionBehavior( + /// enable:true + /// ), + /// initialSelectedDataIndexes: [0] + /// ), + /// ], + /// ); + /// } + /// ``` + final List? initialSelectedDataIndexes; + + /// Opacity of the series. The value ranges from 0 to 1. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// opacity: 0.8 + /// ), + /// ], + /// ); + /// } + /// ``` + final double opacity; + + /// Field in the data source, which is considered for sorting the data points. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// sortFieldValueMapper: (SalesData sales, _) => sales.x, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// SalesData(1, 23), + /// SalesData(2, 35), + /// SalesData(3, 19) + /// ]; + /// + /// class SalesData { + /// SalesData(this.x, this.y); + /// final double x; + /// final double y; + /// } + /// ``` + final ChartValueMapper? 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. + /// + /// Default to `SortingOrder.none`. + /// + /// Also refer [SortingOrder]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// sortFieldValueMapper: (SalesData sales, _) => sales.x, + /// sortingOrder: SortingOrder.descending + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// SalesData(1, 23), + /// SalesData(2, 35), + /// SalesData(3, 19) + /// ]; + /// + /// class SalesData { + /// SalesData(this.x, this.y); + /// final double x; + /// final double y; + /// } + /// ``` + final SortingOrder sortingOrder; + + /// Visibility of the series, which applies only during load time. + /// + /// Default to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// initialIsVisible: false + /// ), + /// ], + /// ); + /// } + /// ``` + /// Use the onRendererCreated callback, as shown in the code below, to update the visibility + /// dynamically. + /// + /// ```dart + /// ChartSeriesController? controller; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Column( + /// children: [ + /// SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataSource: data, + /// xValueMapper: (SalesData sales, _) => sales.year, + /// yValueMapper: (SalesData sales, _) => sales.sales, + /// onRendererCreated: (ChartSeriesController seriesController) { + /// controller = seriesController; + /// }, + /// ), + /// ], + /// ), + /// TextButton( + /// onPressed: () { + /// if (controller != null) { + /// controller!.isVisible = true; + /// } + /// }, + /// child: const Text('Update Series Visibility'), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final bool initialIsVisible; + + /// Delay duration of the series animation.It takes a millisecond value as + /// input. By default, the series will get animated for the specified + /// duration. If animationDelay is specified, then the series will begin to + /// animate after the specified duration. + /// + /// Defaults to `0` for all the series except ErrorBarSeries. + /// The default value for the ErrorBarSeries is `1500`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// animationDelay: 300 + /// ), + /// ], + /// ); + /// } + /// ``` + final double animationDelay; + + /// Called when tapped on the chart data point. + /// + /// The user can fetch the series index, point index, viewport point index and + /// data of the tapped data point. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointTap: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartPointInteractionCallback? onPointTap; + + /// Called when double tapped on the chart data point. + /// + /// The user can fetch the series index, point index, viewport point index and + /// data of the double-tapped data point. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointDoubleTap: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartPointInteractionCallback? onPointDoubleTap; + + /// Called when long pressed on the chart data point. + /// + /// The user can fetch the series index, point index, viewport point index and + /// data of the long-pressed data point. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointLongPress: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartPointInteractionCallback? onPointLongPress; + + @override + Iterable get slots => SeriesSlot.values; + + @protected + List get positions; + + @override + Widget? childForSlot(SeriesSlot slot) { + return null; + } + + @protected + @factory + ChartSeriesRenderer createRenderer(); + + @mustCallSuper + @override + ChartSeriesRenderer createRenderObject(BuildContext context) { + final ChartSeriesRenderer renderer = createRenderer(); + renderer + ..xValueMapper = xValueMapper + ..dataSource = dataSource + ..pointColorMapper = pointColorMapper + ..dataLabelMapper = dataLabelMapper + ..emptyPointSettings = emptyPointSettings + ..dataLabelSettings = dataLabelSettings + ..markerSettings = markerSettings + ..name = name + ..enableTooltip = enableTooltip + ..enableTrackball = enableTrackball + ..animationDuration = animationDuration + ..color = color + ..borderWidth = borderWidth + ..isVisibleInLegend = isVisibleInLegend + ..legendItemText = legendItemText + ..legendIconType = legendIconType + ..selectionBehavior = selectionBehavior + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..opacity = opacity + ..sortFieldValueMapper = sortFieldValueMapper + ..sortingOrder = sortingOrder + ..initialIsVisible = initialIsVisible + ..animationDelay = animationDelay + ..onPointTap = onPointTap + ..onPointDoubleTap = onPointDoubleTap + ..onPointLongPress = onPointLongPress + ..textDirection = Directionality.of(context) + ..widget = this; + return renderer; + } + + @mustCallSuper + @override + void updateRenderObject( + BuildContext context, + ChartSeriesRenderer renderObject, + ) { + renderObject + ..xValueMapper = xValueMapper + ..dataSource = dataSource + ..pointColorMapper = pointColorMapper + ..dataLabelMapper = dataLabelMapper + ..emptyPointSettings = emptyPointSettings + ..dataLabelSettings = dataLabelSettings + ..markerSettings = markerSettings + ..name = name + ..enableTooltip = enableTooltip + ..enableTrackball = enableTrackball + ..animationDuration = animationDuration + ..color = color + ..borderWidth = borderWidth + ..isVisibleInLegend = isVisibleInLegend + ..legendItemText = legendItemText + ..legendIconType = legendIconType + ..selectionBehavior = selectionBehavior + ..opacity = opacity + ..sortFieldValueMapper = sortFieldValueMapper + ..sortingOrder = sortingOrder + ..animationDelay = animationDelay + ..onPointTap = onPointTap + ..onPointDoubleTap = onPointDoubleTap + ..onPointLongPress = onPointLongPress + ..textDirection = Directionality.of(context) + ..widget = this; + } +} + +enum AnimationType { loading, realtime, none } + +/// Creates a series renderer for chart series. +abstract class ChartSeriesRenderer extends RenderBox + with + SlottedContainerRenderObjectMixin, + ChartAreaUpdateMixin, + LegendItemProviderMixin { + ChartSeriesRenderer() { + _fetchMarkerImage(); + } + + late ChartSeries widget; + + AnimationController? get animationController => _animationController; + AnimationController? _animationController; + AnimationController? _markerAnimationController; + AnimationController? _dataLabelAnimationController; + + CurvedAnimation? get animation => _animation; + CurvedAnimation? _animation; + + CurvedAnimation? get markerAnimation => _markerAnimation; + CurvedAnimation? _markerAnimation; + + CurvedAnimation? get dataLabelAnimation => _dataLabelAnimation; + CurvedAnimation? _dataLabelAnimation; + + ChartSegment? _interactiveSegment; + bool _isXRangeChanged = true; + bool _isYRangeChanged = true; + bool _isResized = true; + Image? _markerImage; + bool _canInvokePointerUp = true; + + RenderChartElementLayoutBuilder? get dataLabelContainer => + childForSlot(SeriesSlot.dataLabel) + as RenderChartElementLayoutBuilder?; + + RenderChartElementLayoutBuilder? get markerContainer => + childForSlot(SeriesSlot.marker) as RenderChartElementLayoutBuilder?; + + RenderTrendlineStack? get trendlineContainer => + childForSlot(SeriesSlot.trendline) as RenderTrendlineStack?; + + @protected + bool canUpdateOrCreateSegments = true; + + @protected + bool forceTransformValues = false; + + bool _hasLinearDataSource = true; + bool visibilityBeforeTogglingLegend = false; + + final List _chaoticRawXValues = []; + List xRawValues = []; + final List _chaoticXValues = []; + List xValues = []; + final List _chaoticRawSortValues = []; + final List _sortValues = []; + final List _chaoticPointColors = []; + List pointColors = []; + + final List emptyPointIndexes = []; + final List _xNullPointIndexes = []; + List sortedIndexes = []; + + List> chartPoints = >[]; + + List get segments => _segments; + List _segments = []; + + ChartPointInteractionCallback? onPointTap; + ChartPointInteractionCallback? onPointDoubleTap; + ChartPointInteractionCallback? onPointLongPress; + + @override + RenderChartPlotArea? get parent => super.parent as RenderChartPlotArea?; + + @override + bool get sizedByParent => true; + + AnimationType? get animationType => _animationType; + AnimationType? _animationType; + set animationType(AnimationType? value) { + if (_animationType != value) { + _animationType = _animationType == null ? AnimationType.loading : value; + _startAnimations(); + } + } + + int get index => _index; + int _index = -1; + set index(int value) { + if (_index != value) { + // TODO(VijayakumarM): Add assertion. + _index = value; + } + } + + List get palette => _palette; + List _palette = []; + set palette(List value) { + if (_palette != value) { + // TODO(VijayakumarM): Add assertion. + _palette = value; + if (pointColorMapper == null) { + markNeedsSegmentsPaint(); + } + } + } + + int get dataCount => _dataCount; + int _dataCount = 0; + + double _oldAnimationFactor = 0.0; + double _oldSegmentAnimationFactor = 0.0; + + double get animationFactor => _animationFactor; + double _animationFactor = 0.0; + set animationFactor(double value) { + if (_animationFactor != value) { + _oldAnimationFactor = _animationFactor; + _animationFactor = value; + } + } + + double get segmentAnimationFactor => _segmentAnimationFactor; + double _segmentAnimationFactor = 0.0; + set segmentAnimationFactor(double value) { + if (_segmentAnimationFactor != value) { + _oldSegmentAnimationFactor = _segmentAnimationFactor; + _segmentAnimationFactor = value; + } + } + + bool get canFindLinearVisibleIndexes => _canFindLinearVisibleIndexes; + bool _canFindLinearVisibleIndexes = true; + + List? get dataSource => _dataSource; + List? _dataSource; + set dataSource(List? value) { + if ((value == null || value.isEmpty) && !listEquals(_dataSource, value)) { + _dataCount = 0; + segments.clear(); + markNeedsUpdate(); + } + + if (_dataCount != value?.length || !listEquals(_dataSource, value)) { + _dataSource = value; + canUpdateOrCreateSegments = true; + markNeedsUpdate(); + animationType = AnimationType.realtime; + } + } + + ChartValueMapper? get xValueMapper => _xValueMapper; + ChartValueMapper? _xValueMapper; + set xValueMapper(ChartValueMapper? value) { + if (_xValueMapper != value) { + _xValueMapper = value; + } + } + + ChartValueMapper? get dataLabelMapper => _dataLabelMapper; + ChartValueMapper? _dataLabelMapper; + set dataLabelMapper(ChartValueMapper? value) { + if (_dataLabelMapper != value) { + _dataLabelMapper = value; + } + } + + ChartValueMapper? get pointColorMapper => _pointColorMapper; + ChartValueMapper? _pointColorMapper; + set pointColorMapper(ChartValueMapper? value) { + if (_pointColorMapper != value) { + _pointColorMapper = value; + } + } + + ChartValueMapper? get sortFieldValueMapper => + _sortFieldValueMapper; + ChartValueMapper? _sortFieldValueMapper; + set sortFieldValueMapper(ChartValueMapper? value) { + if (_sortFieldValueMapper != value) { + _sortFieldValueMapper = value; + canUpdateOrCreateSegments = true; + } + } + + Color? get color => _color; + Color? _color; + set color(Color? value) { + if (_color != value) { + _color = value; + markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + } + } + + Color get paletteColor => _paletteColor; + Color _paletteColor = Colors.transparent; + set paletteColor(Color value) { + if (_paletteColor != value) { + _paletteColor = value; + // markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + } + } + + double get borderWidth => _borderWidth; + double _borderWidth = 2.0; + set borderWidth(double value) { + if (_borderWidth != value) { + _borderWidth = value; + markNeedsSegmentsPaint(); + } + } + + bool get initialIsVisible => _initialIsVisible; + bool _initialIsVisible = true; + set initialIsVisible(bool value) { + if (_initialIsVisible != value) { + _initialIsVisible = value; + markNeedsUpdate(); + } + } + + String get name => _name ?? localizedName(); + String? _name; + set name(String? value) { + if (_name != value) { + _name = value; + markNeedsLegendUpdate(); + } + } + + bool get enableTooltip => _enableTooltip; + bool _enableTooltip = true; + set enableTooltip(bool value) { + if (_enableTooltip != value) { + _enableTooltip = value; + } + } + + bool get enableTrackball => _enableTrackball; + bool _enableTrackball = true; + set enableTrackball(bool value) { + if (_enableTrackball != value) { + _enableTrackball = value; + } + } + + double get animationDuration => _animationDuration; + double _animationDuration = 0; + set animationDuration(double value) { + if (_animationDuration != value) { + _animationDuration = value; + if (parent != null) { + _initAnimations(); + } + } + } + + bool get isVisibleInLegend => _isVisibleInLegend; + bool _isVisibleInLegend = true; + set isVisibleInLegend(bool value) { + if (_isVisibleInLegend != value) { + _isVisibleInLegend = value; + markNeedsLegendUpdate(); + } + } + + String? get legendItemText => _legendItemText; + String? _legendItemText; + set legendItemText(String? value) { + if (_legendItemText != value) { + _legendItemText = value; + markNeedsLegendUpdate(); + } + } + + LegendIconType get legendIconType => _legendIconType; + LegendIconType _legendIconType = LegendIconType.seriesType; + set legendIconType(LegendIconType value) { + if (_legendIconType != value) { + _legendIconType = value; + markNeedsLegendUpdate(); + } + } + + SelectionBehavior? get selectionBehavior => _selectionBehavior; + SelectionBehavior? _selectionBehavior; + set selectionBehavior(SelectionBehavior? value) { + if (_selectionBehavior != value) { + _selectionBehavior?.selectionController?.removeListener( + _handleSelectionControllerChange, + ); + _selectionBehavior = value; + _effectiveSelectionBehavior = value; + _selectionBehavior?.selectionController?.addListener( + _handleSelectionControllerChange, + ); + if (_selectionEnabled) { + _initSelection(); + } else { + parent?.selectionController + ?..removeSelectionListener(_handleSelection) + ..removeDeselectionListener(_handleDeselection); + } + } + } + + void _handleSelectionControllerChange() {} + + SelectionBehavior? get effectiveSelectionBehavior => + _effectiveSelectionBehavior; + SelectionBehavior? _effectiveSelectionBehavior; + + List? get initialSelectedDataIndexes => _initialSelectedDataIndexes; + List? _initialSelectedDataIndexes; + set initialSelectedDataIndexes(List? value) { + if (_initialSelectedDataIndexes != value) { + _initialSelectedDataIndexes = value; + } + } + + double get opacity => _opacity; + double _opacity = 1.0; + set opacity(double value) { + if (_opacity != value) { + _opacity = value; + markNeedsSegmentsPaint(); + } + } + + SortingOrder get sortingOrder => _sortingOrder; + SortingOrder _sortingOrder = SortingOrder.none; + set sortingOrder(SortingOrder value) { + if (_sortingOrder != value) { + _sortingOrder = value; + canUpdateOrCreateSegments = true; + markNeedsUpdate(); + } + } + + double get animationDelay => _animationDelay; + double _animationDelay = 0.0; + set animationDelay(double value) { + if (_animationDelay != value) { + _animationDelay = value; + } + } + + DataLabelSettings get dataLabelSettings => _dataLabelSettings; + DataLabelSettings _dataLabelSettings = const DataLabelSettings(); + set dataLabelSettings(DataLabelSettings value) { + if (_dataLabelSettings != value) { + _dataLabelSettings = value; + markNeedsLayout(); + } + } + + MarkerSettings get markerSettings => _markerSettings; + MarkerSettings _markerSettings = const MarkerSettings(); + set markerSettings(MarkerSettings value) { + if (_markerSettings != value) { + _markerSettings = value; + _fetchMarkerImage(); + } + } + + EmptyPointSettings get emptyPointSettings => _emptyPointSettings; + EmptyPointSettings _emptyPointSettings = const EmptyPointSettings(); + set emptyPointSettings(EmptyPointSettings value) { + if (_emptyPointSettings != value) { + if (emptyPointSettings.mode != value.mode) { + _emptyPointSettings = value; + canUpdateOrCreateSegments = true; + markNeedsUpdate(); + } else { + _emptyPointSettings = value; + markNeedsSegmentsPaint(); + } + } + } + + SfChartThemeData? get chartThemeData => _chartThemeData; + SfChartThemeData? _chartThemeData; + set chartThemeData(SfChartThemeData? value) { + if (_chartThemeData != value) { + _chartThemeData = value; + markNeedsSegmentsPaint(); + } + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection = TextDirection.ltr; + set textDirection(TextDirection value) { + if (_textDirection != value) { + _textDirection = value; + markNeedsLayout(); + } + } + + bool get isSelected => _isSelected; + bool _isSelected = false; + set isSelected(bool value) { + _isSelected = value; + markNeedsSegmentsPaint(); + } + + bool get _tooltipEnabled => + enableTooltip && + parent != null && + parent!.tooltipBehavior != null && + parent!.tooltipBehavior!.enable; + + bool get _callbacksEnabled => + parent != null && parent!.onDataLabelRender != null || + parent!.onTooltipRender != null || + parent!.legend?.legendItemBuilder != null || + dataLabelSettings.builder != null || + onPointLongPress != null || + onPointTap != null || + onPointDoubleTap != null; + + bool get _selectionEnabled => + selectionBehavior != null && selectionBehavior!.enable; + + bool isVisible() => true; + + @override + void setupParentData(covariant RenderObject child) { + if (child is! ChartSeriesParentData) { + child.parentData = ChartSeriesParentData(); + } + } + + bool _isToggled() { + return true; + } + + @protected + Color legendIconColor() { + return color ?? paletteColor; + } + + @protected + Color? legendIconBorderColor() { + return null; + } + + @protected + double legendIconBorderWidth() { + if (legendIconType == LegendIconType.horizontalLine || + legendIconType == LegendIconType.verticalLine) { + return 2; + } + return 1; + } + + @protected + Shader? legendIconShader() { + return null; + } + + @protected + void handleLegendItemTapped(LegendItem item, bool isToggled) { + if (parent != null && parent!.onLegendTapped != null) { + final LegendTapArgs args = LegendTapArgs( + item.series, + item.seriesIndex, + item.pointIndex, + ); + parent!.onLegendTapped!(args); + } + parent!.behaviorArea?.hideInteractiveTooltip(); + } + + void _handleLegendItemCreated(ItemRendererDetails details) { + if (parent != null && parent!.onLegendItemRender != null) { + final LegendItem item = details.item; + final LegendIconType iconType = toLegendIconType(details.iconType); + final LegendRenderArgs args = + LegendRenderArgs(item.seriesIndex, item.pointIndex) + ..text = details.text + ..legendIconType = iconType + ..color = details.color; + parent!.onLegendItemRender!(args); + if (args.legendIconType != iconType) { + details.iconType = toLegendShapeMarkerType( + args.legendIconType ?? LegendIconType.seriesType, + this, + ); + } + + details + ..text = args.text ?? '' + ..color = args.color ?? Colors.transparent; + } + } + + @override + ShapeMarkerType effectiveLegendIconType() { + return ShapeMarkerType.circle; + } + + @override + void attach(PipelineOwner owner) { + _initSelection(); + _initAnimations(); + super.attach(owner); + } + + @override + void detach() { + _animationController + ?..removeStatusListener(_handleAnimationStatusChange) + ..dispose(); + _animationController = null; + _animation + ?..removeListener(_handleAnimationUpdate) + ..dispose(); + _animation = null; + + _selectionBehavior?.selectionController?.removeListener( + _handleSelectionControllerChange, + ); + _markerAnimationController?.dispose(); + _markerAnimationController = null; + _markerAnimation?.dispose(); + _markerAnimation = null; + + _dataLabelAnimationController?.dispose(); + _dataLabelAnimationController = null; + _dataLabelAnimation?.dispose(); + _dataLabelAnimation = null; + + parent?.selectionController + ?..removeSelectionListener(_handleSelection) + ..removeDeselectionListener(_handleDeselection); + super.detach(); + } + + void _initSelection() { + if (parent != null && _selectionEnabled) { + parent!.selectionController.updateSelectionParent(selectionBehavior!); + parent!.selectionController + ..addSelectionListener(_handleSelection) + ..addDeselectionListener(_handleDeselection); + } + } + + void _initAnimations() { + final int duration = animationDuration.toInt(); + const double seriesDuration = 1.0; + double markerDuration = 0.0; + double dataLabelAnimationDuration = 0.0; + if (markerSettings.isVisible) { + markerDuration = 0.15; + } + if (dataLabelSettings.isVisible) { + dataLabelAnimationDuration = 0.2; + } + + double curveStart = 0.05; + double curveEnd = + seriesDuration - (markerDuration + dataLabelAnimationDuration); + _animationController ??= AnimationController(vsync: parent!.vsync!) + ..addStatusListener(_handleAnimationStatusChange); + _animationController!.duration = Duration(milliseconds: duration); + _animation ??= CurvedAnimation( + parent: _animationController!, + curve: Interval(curveStart, curveEnd), + )..addListener(_handleAnimationUpdate); + + final double defaultElementAnimationValue = + (animationDuration == 0 || animationType == AnimationType.none) + ? 1.0 + : 0.0; + + curveStart = curveEnd; + curveEnd = curveStart + markerDuration; + _markerAnimationController ??= AnimationController(vsync: parent!.vsync!); + _markerAnimationController!.duration = Duration(milliseconds: duration); + _markerAnimationController!.value = defaultElementAnimationValue; + _markerAnimation ??= CurvedAnimation( + parent: _markerAnimationController!, + curve: Interval(curveStart, curveEnd), + ); + + curveStart = curveEnd; + curveEnd = curveStart + dataLabelAnimationDuration; + _dataLabelAnimationController ??= AnimationController( + vsync: parent!.vsync!, + ); + _dataLabelAnimationController!.duration = Duration(milliseconds: duration); + _dataLabelAnimationController!.value = defaultElementAnimationValue; + _dataLabelAnimation ??= CurvedAnimation( + parent: _dataLabelAnimationController!, + curve: Interval(curveStart, curveEnd), + ); + + if (animationDuration > 0) { + Future.delayed(Duration(milliseconds: animationDelay.toInt()), () { + _startAnimations(); + }); + } else { + _animationFactor = 1.0; + segmentAnimationFactor = 1.0; + } + } + + void _startAnimations({double from = 0}) { + if (animationType != AnimationType.none) { + _animationController?.forward(from: from); + _dataLabelAnimationController?.forward(from: from); + _markerAnimationController?.forward(from: from); + } + } + + void _handleAnimationStatusChange(AnimationStatus status) { + switch (status) { + case AnimationStatus.forward: + copyOldSegmentValues(_oldAnimationFactor, _oldSegmentAnimationFactor); + break; + + case AnimationStatus.completed: + _animationType = AnimationType.none; + forceTransformValues = true; + visibilityBeforeTogglingLegend = !_isToggled(); + markNeedsLayout(); + break; + + case AnimationStatus.dismissed: + case AnimationStatus.reverse: + break; + } + } + + void _handleAnimationUpdate() { + if (animationType == null) { + onLoadingAnimationUpdate(); + return; + } + + switch (animationType!) { + case AnimationType.loading: + onLoadingAnimationUpdate(); + break; + + case AnimationType.realtime: + onRealTimeAnimationUpdate(); + break; + + case AnimationType.none: + _animationFactor = 1.0; + segmentAnimationFactor = 1.0; + break; + } + markNeedsPaint(); + } + + @protected + void onLoadingAnimationUpdate() { + _animationFactor = _animation!.value; + segmentAnimationFactor = 1.0; + } + + @protected + void onRealTimeAnimationUpdate() { + _animationFactor = 1.0; + segmentAnimationFactor = _animation!.value; + } + + @protected + void _resetDataSourceHolders() { + _chaoticRawXValues.clear(); + _chaoticXValues.clear(); + _sortValues.clear(); + _chaoticRawSortValues.clear(); + _chaoticPointColors.clear(); + xRawValues.clear(); + xValues.clear(); + emptyPointIndexes.clear(); + pointColors.clear(); + _xNullPointIndexes.clear(); + sortedIndexes.clear(); + } + + bool _canPopulateDataPoints( + List>? yPaths, + List>? yLists, + ) { + return dataSource != null && + dataSource!.isNotEmpty && + xValueMapper != null && + yPaths != null && + yPaths.isNotEmpty && + yLists != null && + yLists.isNotEmpty; + } + + @protected + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + // Here fPath is widget specific feature path. + // For example, in pie series's pointRadiusMapper is a feature path. + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _resetDataSourceHolders(); + if (!_canPopulateDataPoints(yPaths, chaoticYLists)) { + _dataCount = _chaoticXValues.length; + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + final int length = dataSource!.length; + final int yPathLength = yPaths!.length; + final int fPathLength = fPaths.length; + + final Function(int, D) preferredXValue = _valueToIndex; + final Function(D? value, num x) addXValue = + _addXValueIntoRawAndChaoticXLists; + + for (int i = 0; i < length; i++) { + final T current = dataSource![i]; + final D? rawX = xValueMapper!(current, i); + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + final num currentX = preferredXValue(i, rawX); + addXValue(rawX, currentX); + for (int j = 0; j < yPathLength; j++) { + final ChartValueMapper yPath = yPaths[j]; + final num? yValue = yPath(current, i); + if (yValue == null || yValue.isNaN) { + chaoticYLists![j].add(double.nan); + if (!emptyPointIndexes.contains(i)) { + emptyPointIndexes.add(i); + } + } else { + chaoticYLists![j].add(yValue); + } + } + + for (int j = 0; j < fPathLength; j++) { + final ChartValueMapper fPath = fPaths[j]; + final Object? fValue = fPath(current, i); + chaoticFLists![j].add(fValue); + } + } + + _dataCount = _chaoticXValues.length; + _applyEmptyPointModeIfNeeded(chaoticYLists!); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); + } + + void _addPointColorMapper( + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + if (fPaths != null && pointColorMapper != null) { + fPaths.add(pointColorMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(pointColors); + } else { + pointColors.clear(); + chaoticFLists?.add(_chaoticPointColors); + fLists?.add(pointColors); + } + } + } + + void _addSortValueMapper( + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + if (fPaths != null && sortFieldValueMapper != null) { + fPaths.add(sortFieldValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(_sortValues); + } else { + _sortValues.clear(); + chaoticFLists?.add(_chaoticRawSortValues); + fLists?.add(_sortValues); + } + } + } + + num _valueAsNum(int index, D value) { + return value as num; + } + + num _dateToMilliseconds(int index, D value) { + final DateTime date = value as DateTime; + return date.millisecondsSinceEpoch; + } + + num _valueToIndex(int index, D value) { + return index; + } + + void _addXValueIntoRawAndChaoticXLists(D? raw, num preferred) { + _chaoticRawXValues.add(raw); + _chaoticXValues.add(preferred); + } + + @protected + void _applyEmptyPointModeIfNeeded(List> yLists) { + if (emptyPointIndexes.isNotEmpty) { + emptyPointIndexes.sort(); + switch (emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.drop: + break; + + case EmptyPointMode.zero: + _applyZeroEmptyPointMode(yLists); + break; + + case EmptyPointMode.average: + _applyAverageEmptyPointMode(yLists); + break; + } + } + } + + void _applyZeroEmptyPointMode(List> yLists) { + final int yLength = yLists.length; + for (int i = 0; i < dataCount; i++) { + for (int j = 0; j < yLength; j++) { + final List yValues = yLists[j]; + final num value = yValues[i]; + if (value.isNaN) { + yValues[i] = 0; + } + } + } + } + + void _applyAverageEmptyPointMode(List> yLists) { + final int lastIndex = dataCount - 1; + final int yLength = yLists.length; + for (int i = 0; i < dataCount; i++) { + for (int j = 0; j < yLength; j++) { + final List yValues = yLists[j]; + final num currentValue = yValues[i]; + num previousValue = i > 0 ? yValues[i - 1] : currentValue; + num nextValue = i < lastIndex ? yValues[i + 1] : currentValue; + previousValue = previousValue.isNaN ? 0 : previousValue; + nextValue = nextValue.isNaN ? 0 : nextValue; + if (currentValue.isNaN) { + if (i == 0) { + yValues[i] = i == lastIndex ? 0 : nextValue / 2; + } else if (i == lastIndex) { + yValues[i] = previousValue / 2; + } else { + yValues[i] = (previousValue + nextValue) / 2; + } + } + } + } + } + + void _doSortingIfNeeded( + List>? chaoticYLists, + List>? yLists, + List>? chaoticFLists, + List>? fLists, + ) { + if (sortingOrder != SortingOrder.none && + chaoticYLists != null && + chaoticYLists.isNotEmpty && + yLists != null && + yLists.isNotEmpty) { + if (_chaoticRawSortValues.isEmpty) { + if (_chaoticRawXValues.isNotEmpty) { + _chaoticRawSortValues.addAll(_chaoticRawXValues); + } else { + _chaoticRawSortValues.addAll(_chaoticXValues); + } + } + + switch (sortingOrder) { + case SortingOrder.ascending: + _sort(chaoticYLists, yLists, chaoticFLists, fLists); + break; + + case SortingOrder.descending: + _sort(chaoticYLists, yLists, chaoticFLists, fLists, ascending: false); + break; + + case SortingOrder.none: + break; + } + } else { + xValues.clear(); + xValues.addAll(_chaoticXValues); + xRawValues.clear(); + xRawValues.addAll(_chaoticRawXValues); + } + } + + void _sort( + List> chaoticYLists, + List> yLists, + List>? chaoticFLists, + List>? fLists, { + bool ascending = true, + }) { + _computeSortedIndexes(ascending); + if (sortedIndexes.isNotEmpty) { + final void Function(int index, num xValue) copyX = + _chaoticRawXValues.isNotEmpty ? _copyXAndRawXValue : _copyXValue; + final int yLength = yLists.length; + final int fLength = fLists!.length; + final int length = sortedIndexes.length; + + xValues.clear(); + xRawValues.clear(); + + for (int i = 0; i < length; i++) { + final int sortedIndex = sortedIndexes[i]; + final num xValue = _chaoticXValues[sortedIndex]; + copyX(sortedIndex, xValue); + for (int j = 0; j < yLength; j++) { + final List yValues = yLists[j]; + final List chaoticYValues = chaoticYLists[j]; + yValues.add(chaoticYValues[sortedIndex]); + } + + for (int k = 0; k < fLength; k++) { + final List fValues = fLists[k]; + final List chaoticFValues = chaoticFLists![k]; + fValues.add(chaoticFValues[sortedIndex]); + } + + // During sorting, determine data is linear or non-linear for + // calculating visibleIndexes for proper axis range & segment rendering. + if (_canFindLinearVisibleIndexes) { + _canFindLinearVisibleIndexes = isValueLinear(i, xValue, xValues); + } + } + } + } + + void _computeSortedIndexes(bool ascending) { + sortedIndexes.clear(); + int length = _chaoticRawSortValues.length; + for (int i = 0; i < length; i++) { + sortedIndexes.add(i); + } + final dynamic start = _chaoticRawSortValues[0]; + late dynamic canSwap; + if (start is num) { + canSwap = ascending ? _compareNumIsAscending : _compareNumIsDescending; + } else if (start is DateTime) { + canSwap = ascending ? _compareDateIsAscending : _compareDateIsDescending; + } else { + canSwap = ascending ? _compareStringAscending : _compareStringDescending; + } + + bool swapped; + do { + swapped = false; + for (int i = 0; i < length - 1; i++) { + final int currentIndex = sortedIndexes[i]; + final int nextIndex = sortedIndexes[i + 1]; + if (canSwap( + _chaoticRawSortValues[nextIndex], + _chaoticRawSortValues[currentIndex], + )) { + sortedIndexes[i] = nextIndex; + sortedIndexes[i + 1] = currentIndex; + swapped = true; + } + } + length--; + } while (swapped); + } + + bool _compareNumIsAscending(num? a, num? b) { + a ??= double.negativeInfinity; + b ??= double.negativeInfinity; + return a < b; + } + + bool _compareNumIsDescending(num? a, num? b) { + a ??= double.negativeInfinity; + b ??= double.negativeInfinity; + return a > b; + } + + bool _compareDateIsAscending(DateTime a, DateTime b) => a.isBefore(b); + + bool _compareDateIsDescending(DateTime a, DateTime b) => a.isAfter(b); + + bool _compareStringAscending(String a, String b) => a.compareTo(b) < 0; + + bool _compareStringDescending(String a, String b) => a.compareTo(b) > 0; + + void _copyXAndRawXValue(int index, num xValue) { + _copyXValue(index, xValue); + xRawValues.add(_chaoticRawXValues[index]); + } + + void _copyXValue(int index, num xValue) { + xValues.add(xValue); + } + + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + chartPoints.clear(); + if (parent == null || yLists == null || yLists.isEmpty) { + return; + } + + if (parent!.onDataLabelRender == null && + parent!.onTooltipRender == null && + parent!.legend?.legendItemBuilder == null && + dataLabelSettings.builder == null && + onPointLongPress == null && + onPointTap == null && + onPointDoubleTap == null) { + return; + } + + final int yLength = yLists.length; + if (positions == null || positions.length != yLength) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final ChartPoint point = ChartPoint(x: xRawValues[i]); + for (int j = 0; j < yLength; j++) { + point[positions[j]] = yLists[j][i]; + } + chartPoints.add(point); + } + } + + String localizedName() { + if (parent != null && parent!.localizations != null) { + return '${parent!.localizations!.series} $index'; + } + return 'Series $index'; + } + + void updateSegmentColor( + ChartSegment segment, + Color? borderColor, + double borderWidth, { + Color? fillColor, + bool isLineType = false, + }) { + Color color; + Color strokeColor; + double strokeWidth; + final Color effColor = effectiveColor(segment.currentSegmentIndex); + if (segment.isEmpty) { + color = + (isLineType && emptyPointSettings.mode == EmptyPointMode.zero) + ? fillColor ?? effColor + : emptyPointSettings.color; + // The purpose of isLineType is to set a default border color for + // both line-type series and financial-type series. + strokeColor = isLineType ? color : emptyPointSettings.borderColor; + strokeWidth = emptyPointSettings.borderWidth; + } else { + color = fillColor ?? effColor; + strokeColor = borderColor ?? effColor; + strokeWidth = borderWidth; + } + + if (opacity != 1.0) { + if (color != Colors.transparent) { + color = color.withValues(alpha: opacity); + } + if (strokeColor != Colors.transparent) { + strokeColor = strokeColor.withValues(alpha: opacity); + } + } + + if (effectiveSelectionBehavior != null && + effectiveSelectionBehavior!.enable && + parent != null && + parent!.selectionController.hasSelection) { + if (isSelected || segment.isSelected) { + final double opacity = effectiveSelectionBehavior!.selectedOpacity; + color = effectiveSelectionBehavior!.selectedColor ?? color; + if (color != Colors.transparent) { + color = color.withValues(alpha: opacity); + } + strokeColor = + effectiveSelectionBehavior!.selectedBorderColor ?? strokeColor; + if (strokeColor != Colors.transparent) { + strokeColor = strokeColor.withValues(alpha: opacity); + } + strokeWidth = + effectiveSelectionBehavior!.selectedBorderWidth ?? strokeWidth; + } else { + final double opacity = effectiveSelectionBehavior!.unselectedOpacity; + color = effectiveSelectionBehavior!.unselectedColor ?? color; + if (color != Colors.transparent) { + color = color.withValues(alpha: opacity); + } + strokeColor = + effectiveSelectionBehavior!.unselectedBorderColor ?? strokeColor; + if (strokeColor != Colors.transparent) { + strokeColor = strokeColor.withValues(alpha: opacity); + } + strokeWidth = + effectiveSelectionBehavior!.unselectedBorderWidth ?? strokeWidth; + } + } + + segment.fillPaint.color = color; + segment.strokePaint + ..color = strokeColor + ..strokeWidth = strokeWidth; + } + + Color effectiveColor(int segmentIndex) { + Color? pointColor; + if (pointColorMapper != null && + pointColors.isNotEmpty && + pointColors.length > segmentIndex) { + pointColor = pointColors[segmentIndex]; + } + return pointColor ?? color ?? paletteColor; + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + if (animationController != null && animationController!.isAnimating) { + return false; + } + + bool isDataLabelHit = false; + if (dataLabelContainer != null) { + final ChartSeriesParentData childParentData = + dataLabelContainer!.parentData! as ChartSeriesParentData; + isDataLabelHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return dataLabelContainer!.hitTest(result, position: transformed); + }, + ); + } + + bool isTrendlineHit = false; + if (trendlineContainer != null) { + final ChartSeriesParentData childParentData = + trendlineContainer!.parentData! as ChartSeriesParentData; + isTrendlineHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return trendlineContainer!.hitTest(result, position: transformed); + }, + ); + } + + final bool hasTouchCallback = + onPointLongPress != null || + onPointTap != null || + onPointDoubleTap != null; + bool isSeriesHit = false; + if (isVisible() && + (_tooltipEnabled || _selectionEnabled || hasTouchCallback)) { + if (hitInsideSegment(position)) { + isSeriesHit = true; + } + } + + return isTrendlineHit || isDataLabelHit || isSeriesHit; + } + + bool hitInsideSegment(Offset position) { + _interactiveSegment = visibleSegmentAt(position); + return _interactiveSegment != null; + } + + void handleTapDown(TapDownDetails details) {} + + void handlePointerDown(PointerDownEvent details) {} + + void handleScaleUpdate(ScaleUpdateDetails details) { + if (details.scale != 0) { + _canInvokePointerUp = false; + } + } + + void handlePointerUp(PointerUpEvent details) { + final Offset localPosition = globalToLocal(details.position); + if (onPointTap != null && + _interactiveSegment != null && + _canInvokePointerUp) { + final int pointIndex = dataPointIndex( + localPosition, + _interactiveSegment!, + ); + final int segPointIndex = segmentPointIndex( + localPosition, + _interactiveSegment!, + ); + final ChartPointDetails pointDetails = ChartPointDetails( + index, + viewportIndex(segPointIndex), + chartPoints, + pointIndex, + ); + onPointTap!(pointDetails); + } + _canInvokePointerUp = true; + } + + void handlePointerHover(PointerHoverEvent details) { + final Offset localPosition = globalToLocal(details.position); + if (parent != null && _interactiveSegment != null) { + const bool hasSelection = false; + final bool hasTooltip = + _tooltipEnabled && + parent!.tooltipBehavior!.activationMode == ActivationMode.singleTap; + _handleCurrentInteraction( + hasSelection, + hasTooltip, + localPosition, + kind: details.kind, + ); + } + dataLabelContainer?.handlePointerHover(localPosition); + trendlineContainer?.handlePointerHover(localPosition); + } + + void handleLongPressStart(LongPressStartDetails details) { + _canInvokePointerUp = false; + final Offset localPosition = globalToLocal(details.globalPosition); + if (onPointLongPress != null && _interactiveSegment != null) { + final int pointIndex = dataPointIndex( + localPosition, + _interactiveSegment!, + ); + final int segPointIndex = segmentPointIndex( + localPosition, + _interactiveSegment!, + ); + final ChartPointDetails pointDetails = ChartPointDetails( + index, + viewportIndex(segPointIndex), + chartPoints, + pointIndex, + ); + onPointLongPress!(pointDetails); + } + + if (parent != null && _interactiveSegment != null) { + final bool hasSelection = + _selectionEnabled && + parent!.selectionGesture == ActivationMode.longPress; + final bool hasTooltip = + _tooltipEnabled && + parent!.tooltipBehavior!.activationMode == ActivationMode.longPress; + _handleCurrentInteraction(hasSelection, hasTooltip, localPosition); + } + trendlineContainer?.handleLongPress(localPosition); + } + + void handleTapUp(TapUpDetails details) { + final Offset localPosition = globalToLocal(details.globalPosition); + if (parent != null && _interactiveSegment != null) { + final bool hasSelection = + _selectionEnabled && + parent!.selectionGesture == ActivationMode.singleTap; + final bool hasTooltip = + _tooltipEnabled && + parent!.tooltipBehavior!.activationMode == ActivationMode.singleTap; + _handleCurrentInteraction(hasSelection, hasTooltip, localPosition); + } + + dataLabelContainer?.handleTapUp(localPosition); + trendlineContainer?.handlePointerHover(localPosition); + } + + void handleDoubleTap(Offset position) { + final Offset localPosition = globalToLocal(position); + if (onPointDoubleTap != null && _interactiveSegment != null) { + final int pointIndex = dataPointIndex( + localPosition, + _interactiveSegment!, + ); + final int segPointIndex = segmentPointIndex( + localPosition, + _interactiveSegment!, + ); + final ChartPointDetails pointDetails = ChartPointDetails( + index, + viewportIndex(segPointIndex), + chartPoints, + pointIndex, + ); + onPointDoubleTap!(pointDetails); + } + + if (parent != null && _interactiveSegment != null) { + final bool hasSelection = + _selectionEnabled && + parent!.selectionGesture == ActivationMode.doubleTap; + final bool hasTooltip = + _tooltipEnabled && + parent!.tooltipBehavior!.activationMode == ActivationMode.doubleTap; + _handleCurrentInteraction(hasSelection, hasTooltip, localPosition); + } + trendlineContainer?.handleDoubleTap(localPosition); + } + + ChartSegment? visibleSegmentAt(Offset position) { + for (final ChartSegment segment in segments) { + if (segment.contains(position)) { + return segment; + } + } + + return null; + } + + int dataPointIndex(Offset position, ChartSegment segment) { + int pointIndex = segment.currentSegmentIndex; + if (_xNullPointIndexes.isNotEmpty) { + for (final int xNullPointIndex in _xNullPointIndexes) { + if (pointIndex >= xNullPointIndex) { + pointIndex++; + } + } + } + return pointIndex; + } + + int segmentPointIndex(Offset position, ChartSegment segment) { + return segment.currentSegmentIndex; + } + + int viewportIndex(int index, [List? visibleIndexes]) { + if (visibleIndexes != null && visibleIndexes.isNotEmpty) { + if (canFindLinearVisibleIndexes) { + final int start = visibleIndexes[0]; + final int end = visibleIndexes[1] + 1; + int viewportIndex = 0; + for (int i = start; i < end; i++) { + if (i == index) { + return viewportIndex; + } + viewportIndex++; + } + } else { + return visibleIndexes.indexOf(index); + } + } + return -1; + } + + void _handleCurrentInteraction( + bool hasSelection, + bool hasTooltip, + Offset position, { + PointerDeviceKind kind = PointerDeviceKind.touch, + }) { + if (parent != null && _interactiveSegment != null) { + if (hasSelection) { + _updateSelectionToController( + index, + _interactiveSegment!.currentSegmentIndex, + selectionBehavior!.toggleSelection, + selectionType: parent!.selectionMode, + ); + } + + if (hasTooltip && !parent!.isTooltipActivated) { + final TooltipInfo? info = tooltipInfo(position: position); + if (info != null) { + parent!.behaviorArea?.raiseTooltip(info, kind); + parent!.isTooltipActivated = true; + } + } + } + } + + void _updateSelectionToController( + int seriesIndex, + int segmentPointIndex, + bool togglingEnabled, { + SelectionType? selectionType, + bool forceSelection = false, + bool forceDeselection = false, + }) { + parent?.selectionController.updateSelection( + this, + seriesIndex, + segmentPointIndex, + togglingEnabled, + selectionType: selectionType, + forceSelection: forceSelection, + forceDeselection: forceDeselection, + ); + } + + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (_interactiveSegment != null) { + return _interactiveSegment!.tooltipInfo(position: position); + } + + return null; + } + + TooltipInfo? tooltipInfoFromPointIndex(int pointIndex) { + if (segments.isNotEmpty && pointIndex < segments.length) { + return segments[pointIndex].tooltipInfo(pointIndex: pointIndex); + } + return null; + } + + void _updateSelectionToVisual( + int seriesIndex, + int segmentPointIndex, { + bool elected = false, + }) { + if (parent == null || + selectionBehavior == null || + !selectionBehavior!.enable) { + return; + } + + _invokeSelectionChangedCallback(seriesIndex, segmentPointIndex); + switch (parent!.selectionMode) { + case SelectionType.point: + if (index == seriesIndex && segmentPointIndex < segments.length) { + final ChartSegment segment = segmentAt(segmentPointIndex); + segment.isSelected = elected; + } + break; + + case SelectionType.series: + if (seriesIndex == index) { + isSelected = elected; + } + break; + + case SelectionType.cluster: + if (segmentPointIndex < segments.length) { + segments[segmentPointIndex].isSelected = elected; + } + break; + } + + markNeedsSegmentsPaint(); + } + + void _invokeSelectionChangedCallback(int seriesIndex, int segmentPointIndex) { + if (parent != null && parent!.onSelectionChanged != null) { + final SelectionArgs selectionArgs = + SelectionArgs( + seriesRenderer: this, + seriesIndex: seriesIndex, + pointIndex: segmentPointIndex, + viewportPointIndex: viewportIndex(segmentPointIndex), + ) + ..selectedColor = selectionBehavior!.selectedColor + ..unselectedColor = selectionBehavior!.unselectedColor + ..selectedBorderColor = selectionBehavior!.selectedBorderColor + ..unselectedBorderColor = selectionBehavior!.unselectedBorderColor + ..selectedBorderWidth = selectionBehavior!.selectedBorderWidth + ..unselectedBorderWidth = selectionBehavior!.unselectedBorderWidth; + parent!.onSelectionChanged!(selectionArgs); + _effectiveSelectionBehavior = selectionBehavior!.copyWith( + selectedColor: selectionArgs.selectedColor, + unselectedColor: selectionArgs.unselectedColor, + selectedBorderColor: selectionArgs.selectedBorderColor, + unselectedBorderColor: selectionArgs.unselectedBorderColor, + selectedBorderWidth: selectionArgs.selectedBorderWidth, + unselectedBorderWidth: selectionArgs.unselectedBorderWidth, + ); + } + } + + ChartSegment segmentAt(int segmentPointIndex) { + return segments[segmentPointIndex]; + } + + @nonVirtual + bool isEmpty(int segmentIndex) { + // Handle sortedIndex for finding the empty point segment, + // when segment rearrange with sorting. + segmentIndex = + sortedIndexes.isNotEmpty ? sortedIndexes[segmentIndex] : segmentIndex; + int start = 0; + int end = emptyPointIndexes.length - 1; + while (start <= end) { + final int mid = (start + end) ~/ 2; + if (emptyPointIndexes[mid] == segmentIndex) { + return true; + } else if (emptyPointIndexes[mid] < segmentIndex) { + start = mid + 1; + } else { + end = mid - 1; + } + } + + return false; + } + + void _fetchMarkerImage() { + if (markerSettings.shape == DataMarkerType.image && + markerSettings.image != null) { + fetchImage(markerSettings.image).then((Image? value) { + _markerImage = value; + markNeedsPaint(); + }); + } else { + _markerImage = null; + } + } + + void _calculateEffectiveSelectedIndexes() { + if (parent != null && + _selectionEnabled && + effectiveSelectionBehavior != null && + effectiveSelectionBehavior!.enable) { + final List effectiveSelectedIndexes = []; + final RangeController? selectionController = + effectiveSelectionBehavior?.selectionController; + if (selectionController != null) { + dynamic startRange = selectionController.start; + dynamic endRange = selectionController.end; + if (startRange is DateTime && endRange is DateTime) { + startRange = startRange.millisecondsSinceEpoch; + endRange = endRange.millisecondsSinceEpoch; + } + + final int end = dataCount - 1; + final int startIndex = findIndex(startRange, xValues, end: end); + final int endIndex = findIndex(endRange, xValues, end: end); + if (startIndex != endIndex) { + for (int i = startIndex; i <= endIndex; i++) { + effectiveSelectedIndexes.add(i); + } + } + } else if (initialSelectedDataIndexes != null && + initialSelectedDataIndexes!.isNotEmpty) { + effectiveSelectedIndexes.addAll(initialSelectedDataIndexes!); + } + initialSelectedDataIndexes?.clear(); + + if (effectiveSelectedIndexes.isNotEmpty) { + final int length = effectiveSelectedIndexes.length; + for (int i = 0; i < length; i++) { + _updateSelectionToController( + index, + effectiveSelectedIndexes[i], + selectionBehavior!.toggleSelection, + selectionType: parent!.selectionMode, + forceSelection: true, + ); + } + + if ((initialSelectedDataIndexes == null || + initialSelectedDataIndexes!.isEmpty) && + selectionController != null) { + final List? base = + parent?.selectionController.selectedDataPoints[index]; + if (base != null) { + final List result = + base + .where( + (element) => !effectiveSelectedIndexes.contains(element), + ) + .toList(); + final int length = result.length; + for (int i = 0; i < length; i++) { + _updateSelectionToController( + index, + result[i], + selectionBehavior!.toggleSelection, + selectionType: parent!.selectionMode, + forceDeselection: true, + ); + } + } + } + + effectiveSelectedIndexes.clear(); + } + } + } + + void _handleSelection(int seriesIndex, int segmentPointIndex) { + _updateSelectionToVisual(seriesIndex, segmentPointIndex, elected: true); + } + + void _handleDeselection(int seriesIndex, int segmentPointIndex) { + _updateSelectionToVisual(seriesIndex, segmentPointIndex); + } + + @override + void performUpdate() { + populateDataSource(); + markNeedsLayout(); + } + + @override + void markNeedsLayout() { + super.markNeedsLayout(); + dataLabelContainer?.refresh(); + markerContainer?.refresh(); + trendlineContainer?.markNeedsLayout(); + } + + @protected + void markNeedsSegmentsPaint() { + segments.forEach(customizeSegment); + markNeedsPaint(); + } + + @override + void performResize() { + _isResized = !hasSize || size != constraints.biggest; + assert(!constraints.hasInfiniteWidth || !constraints.hasInfiniteWidth); + size = constraints.biggest; + } + + @override + @mustCallSuper + void performLayout() { + if (!isVisible() || + constraints.maxWidth <= 0.0 || + constraints.maxHeight <= 0.0) { + return; + } + + if (canUpdateOrCreateSegments) { + createOrUpdateSegments(); + } + + if (canUpdateOrCreateSegments || + _isXRangeChanged || + _isYRangeChanged || + _isResized || + forceTransformValues) { + transformValues(); + } + + canUpdateOrCreateSegments = false; + _isXRangeChanged = false; + _isYRangeChanged = false; + _isResized = false; + forceTransformValues = false; + } + + @protected + void createOrUpdateSegments() { + if (dataCount == 0) { + segments.clear(); + return; + } + + final int segmentsCount = segments.length; + if (segmentsCount == dataCount) { + for (int i = 0; i < segmentsCount; i++) { + final ChartSegment segment = segments[i]; + setData(i, segment); + } + } else if (segmentsCount > dataCount) { + _segments = segments.sublist(0, dataCount); + for (int i = 0; i < dataCount; i++) { + final ChartSegment segment = segments[i]; + setData(i, segment); + } + } else { + for (int i = 0; i < segmentsCount; i++) { + final ChartSegment segment = segments[i]; + setData(i, segment); + } + for (int i = segmentsCount; i < dataCount; i++) { + final ChartSegment segment = createSegment(); + setData(i, segment); + segments.add(segment); + } + } + + _calculateEffectiveSelectedIndexes(); + } + + /// To create segment for series. + @protected + ChartSegment createSegment(); + + @protected + @mustCallSuper + void setData(int index, ChartSegment segment) { + segment + ..currentSegmentIndex = index + ..animationFactor = segmentAnimationFactor; + } + + void copyOldSegmentValues( + double animationFactor, + double segmentAnimationFactor, + ) { + for (int i = 0; i < segments.length; i++) { + final ChartSegment segment = segments[i]; + segment.copyOldSegmentValues(animationFactor, segmentAnimationFactor); + } + } + + @protected + void transformValues() { + final int segmentsCount = segments.length; + for (int i = 0; i < dataCount; i++) { + if (i < segmentsCount) { + final ChartSegment segment = segments[i]; + segment.animationFactor = segmentAnimationFactor; + segment.transformValues(); + customizeSegment(segment); + } + } + } + + /// To customize each segments. + void customizeSegment(ChartSegment segment); + + @override + @nonVirtual + void paint(PaintingContext context, Offset offset) { + if (!isVisible() || size.isEmpty) { + return; + } + + context.canvas.save(); + context.canvas.translate(offset.dx, offset.dy); + onPaint(context, offset); + context.canvas.restore(); + } + + @protected + void onPaint(PaintingContext context, Offset offset) { + paintSegments(context, offset); + } + + @protected + void paintSegments(PaintingContext context, Offset offset) { + if (segments.isNotEmpty) { + for (final ChartSegment segment in segments) { + segment.animationFactor = segmentAnimationFactor; + segment.onPaint(context.canvas); + } + } + } + + @override + void dispose() { + _animationController + ?..removeStatusListener(_handleAnimationStatusChange) + ..dispose(); + _animationController = null; + _animation + ?..removeListener(_handleAnimationUpdate) + ..dispose(); + _animation = null; + + _markerAnimationController?.dispose(); + _markerAnimationController = null; + _markerAnimation?.dispose(); + _markerAnimation = null; + + _dataLabelAnimationController?.dispose(); + _dataLabelAnimationController = null; + _dataLabelAnimation?.dispose(); + _dataLabelAnimation = null; + + _resetDataSourceHolders(); + + for (final ChartSegment segment in segments) { + segment.dispose(); + } + + super.dispose(); + } +} + +/// Creates the segments for chart series. +/// +/// It has the public method and properties to customize the segment in the +/// chart series, User can customize the calculation of the segment points +/// by using the method [calculateSegmentPoints]. It has the property to +/// store the old value of the series to support dynamic animation. +/// +/// Provides the public properties color, stroke color, fill paint, +/// stroke paint, series and old series to customize and dynamically +/// change each segment in the chart. +abstract class ChartSegment { + // TODO(VijayakumarM): Mark it as abstract. + /// Transforms the x and y values to screen coordinates. + void transformValues() {} + + /// Gets the color of the series. + Paint getFillPaint(); + + /// Gets the border color of the series. + Paint getStrokePaint(); + + /// Calculates the rendering bounds of a segment. + // TODO(VijayakumarM): Check and remove this method. + // ! Breaking changes. + void calculateSegmentPoints(); + + /// Draws segment in series bounds. + void onPaint(Canvas canvas); + + bool isSelected = false; + + bool contains(Offset position) { + return false; + } + + /// Fill paint of the segment. + final Paint fillPaint = Paint()..isAntiAlias = true; + + /// Stroke paint of the segment. + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + /// Animation factor value. + double animationFactor = 0.0; + + // TODO(VijayakumarM): Check and remove this. + // ! Breaking changes. + /// Current point offset value. + List points = []; + + /// Current index value. + int currentSegmentIndex = -1; + + /// Specifies the segment has empty point. + bool isEmpty = false; + + /// Specifies the segment is visible or not for circular, funnel and pyramid segments only. + /// Not applicable for cartesian segments. + bool isVisible = true; + + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) {} + + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) => null; + + TrackballInfo? trackballInfo(Offset position, int pointIndex) => null; + + /// To dispose the objects. + void dispose() { + points.clear(); + fillPaint.shader?.dispose(); + strokePaint.shader?.dispose(); + } +} + +// 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. +class ChartSeriesController { + /// Creating an argument constructor of ChartSeriesController class. + ChartSeriesController(this.seriesRenderer); + + /// Used to access the series properties. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// print(controller.seriesRenderer is LineSeriesRenderer); + /// }, + /// ), + /// ], + /// ); + /// } + /// ``` + final CartesianSeriesRenderer seriesRenderer; + + final List _visibleListeners = []; + + bool get isVisible => _isVisible; + bool _isVisible = true; + set isVisible(bool value) { + if (_isVisible != value) { + _isVisible = value; + notifyVisibleListeners(); + } + } + + void _addVisibleListener(VoidCallback listener) { + _visibleListeners.add(listener); + } + + void _removeVisibleListener(VoidCallback listener) { + _visibleListeners.remove(listener); + } + + @protected + void notifyVisibleListeners() { + for (final VoidCallback listener in _visibleListeners) { + listener(); + } + } + + void _dispose() { + _visibleListeners.clear(); + } + + /// Used to process only the newly added, updated and removed data points + /// in a series, instead of processing all the data points. + /// + /// To re-render the chart with modified data points, + /// setState() will be called. + /// This will render the process and render the chart from scratch. + /// Thus, the app’s performance will be degraded on continuous update. + /// To overcome this problem, [updateDataSource] method can be called + /// by passing updated data points indexes. Chart will process only + /// that point and skip various steps like bounds calculation, old data points + /// processing, etc. Thus, this will improve the app’s performance. + /// + /// The following are the arguments of this method. + /// * addedDataIndexes – `List` type – Indexes of newly added data points + /// in the existing series. + /// * removedDataIndexes – `List` type – Indexes of removed data points + /// in the existing series. + /// * updatedDataIndexes – `List` type – Indexes of updated data points + /// in the existing series. + /// * addedDataIndex – `int` type – Index of newly added data point + /// in the existing series. + /// * removedDataIndex – `int` type – Index of removed data point + /// in the existing series. + /// * updatedDataIndex – `int` type – Index of updated data point + /// in the existing series. + /// + /// Returns `void`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController? _chartSeriesController; + /// return Column( + /// children: [ + /// SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// ), + /// ] + /// ), + /// TextButton( + /// child: Text("Update data source"), + /// onPressed: () { + /// chartData.removeAt(0); + /// chartData.add(ChartData(3,23)); + /// _chartSeriesController?.updateDataSource( + /// addedDataIndexes: [chartData.length -1], + /// removedDataIndexes: [0], + /// ); + /// } + /// ) + /// ] + /// ); + /// } + /// ``` + void updateDataSource({ + List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int addedDataIndex = -1, + int removedDataIndex = -1, + int updatedDataIndex = -1, + }) { + final CartesianRealTimeUpdateMixin? renderer = + seriesRenderer as CartesianRealTimeUpdateMixin?; + if (renderer != null) { + List? effectiveRemovedIndexes; + List? effectiveAddedIndexes; + List? effectiveReplacedIndexes; + + if (removedDataIndexes != null) { + effectiveRemovedIndexes = List.from(removedDataIndexes); + } + + if (addedDataIndexes != null) { + effectiveAddedIndexes = List.from(addedDataIndexes); + } + + if (updatedDataIndexes != null) { + effectiveReplacedIndexes = List.from(updatedDataIndexes); + } + + if (removedDataIndex != -1) { + effectiveRemovedIndexes ??= []; + effectiveRemovedIndexes.add(removedDataIndex); + } + + if (addedDataIndex != -1) { + effectiveAddedIndexes ??= []; + effectiveAddedIndexes.add(addedDataIndex); + } + + if (updatedDataIndex != -1) { + effectiveReplacedIndexes ??= []; + effectiveReplacedIndexes.add(updatedDataIndex); + } + + renderer.updateDataPoints( + effectiveRemovedIndexes, + effectiveAddedIndexes, + effectiveReplacedIndexes, + ); + } + } + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and returns + /// a chart data point. + /// + /// Since this method is in the series controller, x and y-axis associated + /// with this particular series will be considering for conversion value. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController? _chartSeriesController; + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// ), + /// ], + /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args) { + /// final Offset value = Offset(args.position.dx, args.position.dy); + /// final CartesianChartPoint? chartPoint = + /// _chartSeriesController?.pixelToPoint(value); + /// print('X point: ${chartPoint?.x}'); + /// print('Y point: ${chartPoint?.y}'); + /// }, + /// ); + /// } + /// ``` + CartesianChartPoint pixelToPoint(Offset position) { + if (seriesRenderer.parent == null || + seriesRenderer.parent!.parentData == null || + seriesRenderer.xAxis == null) { + return CartesianChartPoint(); + } + + final BoxParentData parentData = + seriesRenderer.parent!.parentData! as BoxParentData; + final Rect seriesBounds = seriesRenderer.paintBounds; + position -= parentData.offset; + double xValue = seriesRenderer.xAxis!.pixelToPoint( + seriesBounds, + position.dx, + position.dy, + ); + final num yValue = seriesRenderer.yAxis!.pixelToPoint( + seriesBounds, + position.dx, + position.dy, + ); + + if (seriesRenderer.xAxis is RenderCategoryAxis || + seriesRenderer.xAxis is RenderDateTimeCategoryAxis) { + xValue = xValue.round().toDouble(); + } + final dynamic rawX = _rawXValue(seriesRenderer, xValue) ?? xValue; + return CartesianChartPoint(x: rawX, xValue: xValue, y: yValue); + } + + D? _rawXValue(CartesianSeriesRenderer seriesRenderer, num xValue) { + final int index = seriesRenderer.xValues.indexOf(xValue); + final RenderChartAxis xAxis = seriesRenderer.xAxis!; + + if (index == -1) { + if (xAxis is RenderDateTimeAxis) { + return DateTime.fromMillisecondsSinceEpoch(xValue.toInt()) as D; + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + return xValue.toString() as D; + } else { + return xValue as D; + } + } + return index != -1 ? seriesRenderer.xRawValues[index] : null; + } + + /// Converts chart data point value to logical pixel value. + /// + /// The [pointToPixel] method takes chart data point value as input and + /// returns logical pixel value. + /// + /// Since this method is in the series controller, x and y-axis associated + /// with this particular series will be + /// considering for conversion value. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController? _chartSeriesController; + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// onPointTap: (ChartPointDetails args) { + /// final CartesianChartPoint chartPoint = + /// CartesianChartPoint( + /// chartData[args.pointIndex!].x, + /// chartData[args.pointIndex!].y); + /// final Offset? pointLocation = + /// _chartSeriesController?.pointToPixel(chartPoint); + /// print('X location: ${pointLocation!.dx}'); + /// print('Y location: ${pointLocation.dy}'); + /// }, + /// ), + /// ], + /// ); + /// } + /// ``` + Offset pointToPixel(CartesianChartPoint point) { + if (point.x == null || point.y == null || seriesRenderer.xAxis == null) { + return Offset.zero; + } + + final RenderChartAxis xAxis = seriesRenderer.xAxis!; + final D? x = point.x; + num pointX; + + if (x is int) { + pointX = x; + } else { + if (xAxis is RenderDateTimeAxis) { + assert(x is DateTime); + pointX = (x! as DateTime).millisecondsSinceEpoch; + } else if (xAxis is RenderDateTimeCategoryAxis) { + assert(x is DateTime); + pointX = xAxis.labels.indexOf((x! as DateTime).millisecondsSinceEpoch); + } else if (xAxis is RenderCategoryAxis) { + assert(x is String); + pointX = xAxis.labels.indexOf(x.toString()); + } else { + pointX = x! as num; + } + } + + final double pixelX = seriesRenderer.pointToPixelX(pointX, point.y!); + final double pixelY = seriesRenderer.pointToPixelY(pointX, point.y!); + return Offset(pixelX, pixelY); + } + + /// 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` + /// property's value in the series. If the value is 0, then the animation + /// will not be performed. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController? _chartSeriesController; + /// return Column( + /// children: [ + /// SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// ), + /// ] + /// ), + /// TextButton( + /// child: Text("Animate series"), + /// onPressed: () { + /// _chartSeriesController?.animate(); + /// } + /// ) + /// ] + /// ); + /// } + /// ``` + void animate() { + seriesRenderer.animationType = AnimationType.loading; + } +} + +/// This class has the properties of the cartesian series. +/// +/// The cartesian series provides a variety of options, such as animation, +/// dynamic animation, transpose, color palette, +/// color mapping to customize the Cartesian chart. The chart’s data source +/// can be sorted using the sorting order and +/// [sortFieldValueMapper] properties of series. +/// +/// Provides the options for animation, color palette, sorting, and empty point +/// mode to customize the charts. +/// +abstract class CartesianSeries extends ChartSeries { + /// Creating an argument constructor of [CartesianSeries] class. + const CartesianSeries({ + super.key, + super.xValueMapper, + super.dataLabelMapper, + super.name, + super.dataSource, + this.xAxisName, + this.yAxisName, + super.pointColorMapper, + super.color, + super.legendItemText, + super.sortFieldValueMapper, + this.gradient, + this.borderGradient, + this.trendlines, + super.markerSettings, + this.onRendererCreated, + this.onCreateRenderer, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + this.onCreateShader, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.emptyPointSettings, + super.dataLabelSettings, + super.animationDuration, + this.dashArray, + super.borderWidth, + super.selectionBehavior, + super.initialSelectedDataIndexes, + super.isVisibleInLegend, + super.legendIconType, + super.opacity, + super.animationDelay, + super.sortingOrder, + }); + + /// Used to create the renderer for custom series. + /// + /// This is applicable only when the custom series is defined in the sample + /// and for built-in series types, it is not applicable. + /// + /// Renderer created in this will hold the series state and + /// this should be created for each series. [onCreateRenderer] callback + /// function should return the renderer class and should not return null. + /// + /// Series state will be created only once per series and will not be created + /// again when we update the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataSource: chartData, + /// xValueMapper: (datum, index) => datum.x, + /// yValueMapper: (datum, index) => datum.y, + /// onCreateRenderer: (ChartSeries series) { + /// return CustomLinerSeriesRenderer( + /// series as LineSeries); + /// }), + /// ], + /// ); + /// } + /// + /// class CustomLinerSeriesRenderer extends LineSeriesRenderer { + /// CustomLinerSeriesRenderer(this.series); + /// final LineSeries series; + /// + /// @override + /// LineSegment createSegment() { + /// return _LineCustomPainter(series); + /// } + /// } + /// + /// class _LineCustomPainter extends LineSegment { + /// _LineCustomPainter(this._series); + /// final LineSeries _series; + /// + /// @override + /// int get currentSegmentIndex => super.currentSegmentIndex; + /// + /// @override + /// Paint getFillPaint() { + /// final Paint customerFillPaint = Paint(); + /// customerFillPaint.color = _series.dataSource![currentSegmentIndex].y > 30 + /// ? Colors.red + /// : Colors.green; + /// customerFillPaint.style = PaintingStyle.fill; + /// return customerFillPaint; + /// } + /// + /// @override + /// void onPaint(Canvas canvas) { + /// super.onPaint(canvas); + /// } + /// } + /// ``` + final ChartSeriesRendererFactory? onCreateRenderer; + + /// Triggers when the series renderer is created. + /// + /// Using this callback, able to get the [ChartSeriesController] instance, + /// which is used to access the public methods in the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// ChartSeriesController? _chartSeriesController; + /// return Column( + /// children: [ + /// SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// ), + /// ] + /// ), + /// TextButton( + /// child: Text("Animate series"), + /// onPressed: () { + /// _chartSeriesController?.animate(); + /// } + /// ) + /// ] + /// ); + /// } + /// ``` + final SeriesRendererCreatedCallback? onRendererCreated; + + /// Name of the x-axis to bind the series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// axes: [ + /// NumericAxis( + /// name: 'xAxis1' + /// ) + /// ], + /// series: >[ + /// BubbleSeries( + /// xAxisName: 'xAxis1', + /// ), + /// ], + /// ); + /// } + /// ``` + final String? xAxisName; + + /// Name of the y-axis to bind the series. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// axes: [ + /// NumericAxis( + /// name: 'yAxis1' + /// ) + /// ], + /// series: >[ + /// BubbleSeries( + /// yAxisName: 'yAxis1', + /// ), + /// ], + /// ); + /// } + /// ``` + final String? yAxisName; + + /// Customizes the trendlines. + /// + /// Trendline are used to mark the specific area of interest + /// in the plot area with texts, shapes, or images. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline( + /// type: TrendlineType.linear + /// ) + /// ] + /// ) + /// ], + /// ); + /// } + /// ``` + final List? trendlines; + + /// Fills the chart series with gradient color. + /// + /// Default to `null`. + /// + /// ```dart + /// final List color = [Colors.red, Colors.blue, Colors.pink]; + /// final List stops = [0.0, 0.5, 1.0]; + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// gradient: LinearGradient(colors: color, stops: stops) + /// ), + /// ], + /// ); + /// } + /// ``` + final LinearGradient? gradient; + + /// Fills the border of the chart series with gradient color. + /// + /// Default to `null`. + /// + /// ```dart + /// final List color = [Colors.red, Colors.blue, Colors.pink]; + /// final List stops = [0.0, 0.5, 1.0]; + /// + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// borderGradient: LinearGradient(colors: color, stops: stops), + /// borderWidth: 3 + /// ), + /// ], + /// ); + /// } + /// ``` + final LinearGradient? borderGradient; + + /// Dashes of the series. + /// + /// Any number of values can be provided in the list. Odd value + /// is considered as rendering size and even value is considered as gap. + /// + /// _Note:_ This is applicable for line, spline, step line, + /// and fast line series only. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dashArray: [10, 10], + /// ), + /// ], + /// ); + /// } + /// ``` + final List? dashArray; + + /// 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 SfCartesianChart( + /// series: >[ + /// BarSeries( + /// // To use the gradient for shader rendering, + /// // `import 'dart:ui' as ui;` this file should be imported. + /// onCreateShader: (ShaderDetails details) { + /// return ui.Gradient.linear( + /// details.rect.topRight, + /// details.rect.bottomLeft, + /// [Colors.yellow, Colors.lightBlue, Colors.brown], + /// [0.2,0.6,1] + /// ); + /// }, + /// ), + /// ] + /// ); + /// } + /// ``` + final CartesianShaderCallback? onCreateShader; + + bool transposed() => false; + + @override + Widget? childForSlot(SeriesSlot slot) { + if (dataSource == null) { + return null; + } + switch (slot) { + case SeriesSlot.dataLabel: + return dataLabelSettings.isVisible + ? CartesianDataLabelContainer( + series: this, + dataSource: dataSource!, + mapper: dataLabelMapper, + builder: dataLabelSettings.builder, + settings: dataLabelSettings, + positions: positions, + ) + : null; + + case SeriesSlot.marker: + return markerSettings.isVisible + // TODO(VijayakumarM): Check bang operator. + ? MarkerContainer( + series: this, + dataSource: dataSource!, + settings: markerSettings, + ) + : null; + + case SeriesSlot.trendline: + return trendlines != null + ? TrendlineContainer(trendlines: trendlines!) + : null; + } + } + + @override + CartesianSeriesRenderer createRenderObject(BuildContext context) { + final CartesianSeriesRenderer renderer = + super.createRenderObject(context) as CartesianSeriesRenderer; + renderer + ..xAxisName = xAxisName + ..yAxisName = yAxisName + ..color = color + ..trendlines = trendlines + ..gradient = gradient + ..borderGradient = borderGradient + ..dashArray = dashArray + ..isVisibleInLegend = isVisibleInLegend + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..onCreateShader = onCreateShader + ..onRendererCreated = onRendererCreated; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + CartesianSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..xAxisName = xAxisName + ..yAxisName = yAxisName + ..color = color + ..trendlines = trendlines + ..gradient = gradient + ..borderGradient = borderGradient + ..dashArray = dashArray + ..isVisibleInLegend = isVisibleInLegend + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..onCreateShader = onCreateShader; + } +} + +/// Creates a series renderer for Cartesian series. +abstract class CartesianSeriesRenderer extends ChartSeriesRenderer + with AxisDependent { + List visibleIndexes = []; + + ChartSeriesController get controller => _controller; + late final ChartSeriesController _controller = + ChartSeriesController(this); + + DoubleRange? _yVisibleRange; + + // TODO(VijayakumarM): Ensure the hit test order. + @override + Iterable get children { + return [ + if (trendlineContainer != null) trendlineContainer!, + if (markerContainer != null) markerContainer!, + if (dataLabelContainer != null) dataLabelContainer!, + ]; + } + + @override + RenderCartesianChartPlotArea? get parent => + super.parent as RenderCartesianChartPlotArea?; + + @override + set dataSource(List? value) { + if ((value == null || value.isEmpty) && !listEquals(_dataSource, value)) { + _dataCount = 0; + segments.clear(); + markNeedsUpdate(); + } + + includeRange = controller.isVisible && value != null && value.isNotEmpty; + + if (_dataCount != value?.length || !listEquals(_dataSource, value)) { + _dataSource = value; + canUpdateOrCreateSegments = true; + parent?.isLegendToggled = false; + if (xAxis != null && + yAxis != null && + parent != null && + parent!.enableAxisAnimation) { + populateDataSource(); + xAxis!.calculateVisibleRangeAndInvokeAnimation(); + yAxis!.calculateVisibleRangeAndInvokeAnimation(); + markNeedsLayout(); + } else { + markNeedsUpdate(); + } + animationType = AnimationType.realtime; + } + } + + @override + set initialIsVisible(bool value) { + if (initialIsVisible != value) { + super.initialIsVisible = value; + includeRange = value; + controller.isVisible = value; + } + } + + List? get trendlines => _trendlines; + List? _trendlines; + set trendlines(List? value) { + if (_trendlines != value) { + _trendlines = value; + markNeedsUpdate(); + } + } + + LinearGradient? get gradient => _gradient; + LinearGradient? _gradient; + set gradient(LinearGradient? value) { + if (_gradient != value) { + _gradient = value; + markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + } + } + + LinearGradient? get borderGradient => _borderGradient; + LinearGradient? _borderGradient; + set borderGradient(LinearGradient? value) { + if (_borderGradient != value) { + _borderGradient = value; + markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + } + } + + List? get dashArray => _dashArray; + List? _dashArray; + set dashArray(List? value) { + if (_dashArray != value) { + _dashArray = value; + markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + } + } + + CartesianShaderCallback? get onCreateShader => _onCreateShader; + CartesianShaderCallback? _onCreateShader; + set onCreateShader(CartesianShaderCallback? value) { + if (_onCreateShader != value) { + _onCreateShader = value; + markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + } + } + + SeriesRendererCreatedCallback? get onRendererCreated => + _onRendererCreated; + SeriesRendererCreatedCallback? _onRendererCreated; + set onRendererCreated(SeriesRendererCreatedCallback? value) { + if (_onRendererCreated != value) { + _onRendererCreated = value; + } + } + + @override + set xAxis(RenderChartAxis? value) { + super.xAxis = value; + trendlineContainer?.setXAxis(value); + } + + @override + set yAxis(RenderChartAxis? value) { + super.yAxis = value; + trendlineContainer?.setYAxis(value); + } + + @override + bool isVisible() => controller.isVisible; + + @override + List? buildLegendItems(int index) { + List? items = []; + if (isVisibleInLegend) { + final CartesianLegendItem legendItem = CartesianLegendItem( + text: legendItemText ?? name, + iconType: toLegendShapeMarkerType(legendIconType, this), + iconColor: legendIconColor(), + iconBorderColor: legendIconBorderColor(), + iconBorderWidth: legendIconBorderWidth(), + series: this, + seriesIndex: index, + pointIndex: 0, + isToggled: _isToggled(), + shader: legendIconShader(), + overlayMarkerType: + markerSettings.isVisible + ? toShapeMarkerType(markerSettings.shape) + : null, + imageProvider: + legendIconType == LegendIconType.image + ? parent?.legend?.image + : null, + onTap: handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ); + items = [legendItem]; + } else { + items = null; + } + + if (trendlineContainer != null && items != null) { + items.addAll(trendlineContainer!.buildLegendItems(index, this)!); + } + return items; + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + super.handleLegendItemTapped(item, isToggled); + controller.isVisible = !isToggled; + if (controller.isVisible == !isToggled) { + item.onToggled?.call(); + } + + if (trendlineContainer != null) { + trendlineContainer!.updateLegendState(item, isToggled); + markNeedsLegendUpdate(); + } + markNeedsUpdate(); + } + + @override + bool _isToggled() { + return !controller.isVisible; + } + + @override + Shader? legendIconShader() { + if (parent != null && parent!.legend != null) { + final Rect legendIconBounds = Rect.fromLTWH( + 0.0, + 0.0, + parent!.legend!.iconWidth, + parent!.legend!.iconHeight, + ); + if (onCreateShader != null) { + final ShaderDetails details = ShaderDetails(legendIconBounds, 'legend'); + return onCreateShader?.call(details); + } else { + return gradient?.createShader(legendIconBounds); + } + } + return null; + } + + @override + void attach(PipelineOwner owner) { + controller._addVisibleListener(_handleIsVisibleChange); + if (onRendererCreated != null) { + onRendererCreated?.call(controller); + } + super.attach(owner); + } + + @override + void detach() { + controller._removeVisibleListener(_handleIsVisibleChange); + super.detach(); + } + + @override + void _handleSelectionControllerChange() { + _calculateEffectiveSelectedIndexes(); + markNeedsSegmentsPaint(); + } + + void _handleIsVisibleChange() { + includeRange = controller.isVisible; + markNeedsUpdate(); + } + + @override + void copyOldSegmentValues( + double animationFactor, + double segmentAnimationFactor, + ) { + super.copyOldSegmentValues(animationFactor, segmentAnimationFactor); + forceTransformValues = true; + markNeedsLayout(); + } + + @override + void _resetDataSourceHolders() { + visibleIndexes.clear(); + final DoubleRange defaultXRange = + xAxis?.defaultRange() ?? DoubleRange(0, 1); + final DoubleRange defaultYRange = + yAxis?.defaultRange() ?? DoubleRange(0, 1); + xMin = defaultXRange.minimum; + xMax = defaultXRange.maximum; + yMin = defaultYRange.minimum; + yMax = defaultYRange.maximum; + super._resetDataSourceHolders(); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + // Here fPath is widget specific feature path. + // For example, in bubble series's bubbleSizeMapper is a feature path. + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _resetDataSourceHolders(); + if (!_canPopulateDataPoints(yPaths, chaoticYLists)) { + _dataCount = _chaoticXValues.length; + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + final int length = dataSource!.length; + final int yPathLength = yPaths!.length; + final int fPathLength = fPaths.length; + + num previousX = double.negativeInfinity; + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(D?, num) addXValue = _addXValueIntoRawAndChaoticXLists; + + for (int i = 0; i < length; i++) { + final T current = dataSource![i]; + final D? rawX = xValueMapper!(current, i); + if (rawX == null) { + _xNullPointIndexes.add(i); + continue; + } + + final num currentX = preferredXValue(i, rawX); + addXValue(rawX, currentX); + xMinimum = min(xMinimum, currentX); + xMaximum = max(xMaximum, currentX); + if (_hasLinearDataSource) { + _hasLinearDataSource = currentX >= previousX; + } + + for (int j = 0; j < yPathLength; j++) { + final ChartValueMapper yPath = yPaths[j]; + final num? yValue = yPath(current, i); + if (yValue == null || yValue.isNaN) { + chaoticYLists![j].add(double.nan); + if (!emptyPointIndexes.contains(i)) { + emptyPointIndexes.add(i); + } + } else { + chaoticYLists![j].add(yValue); + yMinimum = min(yMinimum, yValue); + yMaximum = max(yMaximum, yValue); + } + } + + for (int j = 0; j < fPathLength; j++) { + final ChartValueMapper fPath = fPaths[j]; + final Object? fValue = fPath(current, i); + chaoticFLists![j].add(fValue); + } + + previousX = currentX; + } + + xMin = xMinimum; + xMax = xMaximum; + yMin = yMinimum; + yMax = yMaximum; + _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; + + _applyEmptyPointModeIfNeeded(chaoticYLists!); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); + computeNonEmptyYValues(); + _populateTrendlineDataSource(); + _updateXValuesForCategoryTypeAxes(); + } + + Function(int, D) _preferredXValue() { + if (xAxis is RenderNumericAxis || xAxis is RenderLogarithmicAxis) { + return _valueAsNum; + } else if (xAxis is RenderDateTimeAxis) { + return _dateToMilliseconds; + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + return _valueToIndex; + } + return _valueAsNum; + } + + void _updateXValuesForCategoryTypeAxes() { + if (xAxis is RenderCategoryAxis) { + (xAxis! as RenderCategoryAxis).updateXValuesWithArrangeByIndex(); + } else if (xAxis is RenderDateTimeCategoryAxis) { + (xAxis! as RenderDateTimeCategoryAxis).updateXValues(); + } + + if (!_canFindLinearVisibleIndexes) { + return; + } + + for (int i = 0; i < dataCount; i++) { + if (_canFindLinearVisibleIndexes) { + _canFindLinearVisibleIndexes = isValueLinear(i, xValues[i], xValues); + if (!_canFindLinearVisibleIndexes) { + return; + } + } + } + } + + @protected + void _populateTrendlineDataSource() {} + + @protected + void computeNonEmptyYValues() {} + + num trackballYValue(int index) => index; + + /// Method excepts [BoxAndWhiskerSeries], and stacking series. + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + chartPoints.clear(); + if (parent == null || yLists == null || yLists.isEmpty) { + return; + } + + if (parent!.onDataLabelRender == null && + parent!.onTooltipRender == null && + parent!.legend?.legendItemBuilder == null && + dataLabelSettings.builder == null && + onPointLongPress == null && + onPointTap == null && + onPointDoubleTap == null) { + return; + } + + final int yLength = yLists.length; + if (positions == null || positions.length != yLength) { + return; + } + + for (int i = 0; i < dataCount; i++) { + final num xValue = xValues[i]; + final CartesianChartPoint point = CartesianChartPoint( + x: xRawValues[i], + xValue: xValue, + ); + for (int j = 0; j < yLength; j++) { + point[positions[j]] = yLists[j][i]; + } + chartPoints.add(point); + } + } + + @override + DoubleRange range(RenderChartAxis axis) { + final RenderCartesianChartPlotArea? plotArea = parent; + if (axis == yAxis && + axis.anchorRangeToVisiblePoints && + plotArea != null && + plotArea.zoomPanBehavior != null && + plotArea.behaviorArea != null && + plotArea.behaviorArea!.effectiveZoomMode == ZoomMode.x && + _yVisibleRange != null) { + return _yVisibleRange!.copyWith(); + } + + final DoubleRange actualRange = super.range(axis).copyWith(); + if (trendlineContainer == null) { + return actualRange; + } + + return _trendlineRange(actualRange, axis); + } + + DoubleRange _trendlineRange(DoubleRange actualRange, RenderChartAxis axis) { + num minimum = actualRange.minimum; + num maximum = actualRange.maximum; + final DoubleRange trendlineRange = trendlineContainer!.range( + axis, + actualRange, + ); + minimum = min(minimum, trendlineRange.minimum); + maximum = max(maximum, trendlineRange.maximum); + + return actualRange + ..minimum = minimum + ..maximum = maximum; + } + + @override + void didRangeChange(RenderChartAxis axis) { + if (parent == null) { + return; + } + + if (axis == xAxis) { + _isXRangeChanged = true; + _findVisibleIndexes(); + } + + if (axis == yAxis) { + _isYRangeChanged = true; + } + + if (_isXRangeChanged || _isYRangeChanged) { + parent!.behaviorArea?.hideInteractiveTooltip(); + } + + if (controller.isVisible) { + markNeedsLayout(); + } + } + + @protected + void _findVisibleIndexes() { + visibleIndexes.clear(); + if (xAxis == null || + xAxis!.visibleRange == null || + xAxis!.visibleInterval == 0) { + return; + } + + final DoubleRange baseRange = xAxis!.visibleRange!.copyWith(); + late DoubleRange range; + if (xAxis is RenderLogarithmicAxis) { + range = DoubleRange( + _valueAsPow(baseRange.minimum), + _valueAsPow(baseRange.maximum), + ); + } else { + range = baseRange; + } + + if (canFindLinearVisibleIndexes) { + final int end = dataCount - 1; + final int startIndex = findIndex(range.minimum, xValues, end: end); + final int endIndex = findIndex(range.maximum, xValues, end: end); + if (startIndex != -1) { + final num startValue = xValues[startIndex]; + if (startIndex != 0 && startValue > range.minimum) { + visibleIndexes.add(startIndex - 1); + } else { + visibleIndexes.add(startIndex); + } + } + + if (endIndex != -1) { + final num endValue = xValues[endIndex]; + if (endIndex != dataCount - 1 && endValue < range.maximum) { + visibleIndexes.add(endIndex + 1); + } else { + visibleIndexes.add(endIndex); + } + } + } else { + for (int i = 0; i < dataCount; i++) { + final num current = xValues[i]; + if (range.contains(current)) { + visibleIndexes.add(i); + } + } + + if (visibleIndexes.isNotEmpty) { + final int startIndex = visibleIndexes[0]; + final num startValue = xValues[startIndex]; + if (startIndex != 0 && startValue > range.minimum) { + visibleIndexes.insert(0, startIndex - 1); + } + + final int length = visibleIndexes.length; + final int endIndex = visibleIndexes[length - 1]; + final num endValue = xValues[endIndex]; + if (endIndex != dataCount - 1 && endValue < range.maximum) { + visibleIndexes.add(endIndex + 1); + } + } + } + + final RenderCartesianChartPlotArea? plotArea = parent; + if (controller.isVisible && + yAxis != null && + yAxis!.anchorRangeToVisiblePoints && + plotArea != null && + plotArea.zoomPanBehavior != null && + plotArea.behaviorArea != null && + plotArea.behaviorArea!.effectiveZoomMode == ZoomMode.x) { + final DoubleRange newYVisibleRange = _calculateYRange(); + if (_yVisibleRange != newYVisibleRange) { + _yVisibleRange = newYVisibleRange; + yAxis!.markNeedsRangeUpdate(); + } + } else { + _yVisibleRange = null; + } + } + + num _valueAsPow(num value) => (xAxis! as RenderLogarithmicAxis).toPow(value); + + DoubleRange _calculateYRange({List>? yLists}) { + num minimum = double.infinity; + num maximum = double.negativeInfinity; + if (canFindLinearVisibleIndexes) { + if (visibleIndexes.isNotEmpty) { + final int start = visibleIndexes[0]; + final int end = visibleIndexes[1]; + for (int i = start; i <= end; i++) { + for (final List list in yLists!) { + final num value = list[i]; + if (!value.isNaN) { + minimum = min(minimum, value); + maximum = max(maximum, value); + } + } + } + } + } else { + for (final int index in visibleIndexes) { + for (final List list in yLists!) { + final num value = list[index]; + if (!value.isNaN) { + minimum = min(minimum, value); + maximum = max(maximum, value); + } + } + } + } + return DoubleRange(minimum, maximum); + } + + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + if (alignment == ChartDataLabelAlignment.auto) { + final bool isPrevEmpty = previous == null || previous.y!.isNaN; + final bool isNextEmpty = next == null || next.y!.isNaN; + if (isPrevEmpty && isNextEmpty) { + return ChartDataLabelAlignment.top; + } else if (isPrevEmpty) { + return current.y! < next!.y! + ? ChartDataLabelAlignment.bottom + : ChartDataLabelAlignment.top; + } else if (isNextEmpty) { + return current.y! < previous.y! + ? ChartDataLabelAlignment.bottom + : ChartDataLabelAlignment.top; + } else { + final num slope = (next.y! - previous.y!) / 2; + final num intersectY = + (slope * index) + (next.y! - (slope * (index + 1))); + return current.y! < intersectY + ? ChartDataLabelAlignment.bottom + : ChartDataLabelAlignment.top; + } + } else { + return alignment; + } + } + + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { + return chartThemeData.plotAreaBackgroundColor!; + } else if (chartThemeData.backgroundColor != Colors.transparent) { + return chartThemeData.backgroundColor!; + } + return themeData.colorScheme.surface; + } + + // This method applicable for below mentioned series. + // - Line series, + // - StepLine series, + // - Spline series, + // - StackedLine series, + // - StackedLine100 series, + // - FastLine series, + // - Area series, + // - StepArea series, + // - StackedArea series, + // - StackedArea100 series, + // - SplineArea series, + // - SplineRangeArea series, + // - RangeArea series + // - Hilo series, + // - Scatter series. + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + double markerHeightWithPadding = dataLabelPadding; + double markerWidthWithPadding = dataLabelPadding; + if (markerSettings.isVisible) { + final ChartMarker marker = markerAt(current.dataPointIndex); + markerHeightWithPadding += marker.height / 2; + markerWidthWithPadding += marker.width / 2; + } + + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + final num y = current.y!; + switch (alignment) { + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + y.isNegative + ? -(markerWidthWithPadding + size.width + margin.horizontal) + : markerWidthWithPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + y.isNegative + ? markerHeightWithPadding + : -(markerHeightWithPadding + size.height + margin.vertical); + } + return translateTransform(current.x!, y, translationX, translationY); + + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + y.isNegative + ? markerWidthWithPadding + : -(markerWidthWithPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + y.isNegative + ? -(markerHeightWithPadding + size.height + margin.vertical) + : markerHeightWithPadding; + } + return translateTransform(current.x!, y, translationX, translationY); + + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.middle: + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return translateTransform( + current.x!, + current.y!, + translationX, + translationY, + ); + } + } + + @override + ChartSegment? visibleSegmentAt(Offset position) { + if (segments.isEmpty) { + return null; + } + + final int segmentsCount = segments.length; + if (canFindLinearVisibleIndexes) { + if (visibleIndexes.isNotEmpty) { + final int start = visibleIndexes[0]; + final int end = visibleIndexes[1]; + for (int i = start; i <= end; i++) { + if (i < segmentsCount) { + final ChartSegment segment = segments[i]; + if (segment.contains(position)) { + return segment; + } + } + } + } + } else { + for (final int index in visibleIndexes) { + if (index < segmentsCount) { + final ChartSegment segment = segments[index]; + if (segment.contains(position)) { + return segment; + } + } + } + } + + return null; + } + + @override + int viewportIndex(int index, [List? visibleIndexes]) { + return super.viewportIndex(index, visibleIndexes ?? this.visibleIndexes); + } + + List contains(Offset position) { + if (animationController != null && animationController!.isAnimating) { + return []; + } + return []; + } + + @nonVirtual + double pointToPixelX(num x, num y) { + return isTransposed + ? yAxis!.pointToPixel(y, range: yAxis!.effectiveVisibleRange) + : xAxis!.pointToPixel(x, range: xAxis!.effectiveVisibleRange); + } + + @nonVirtual + double pointToPixelY(num x, num y) { + return isTransposed + ? xAxis!.pointToPixel(x, range: xAxis!.effectiveVisibleRange) + : yAxis!.pointToPixel(y, range: yAxis!.effectiveVisibleRange); + } + + @override + void performUpdate() { + trendlineContainer?.performUpdate(this); + super.performUpdate(); + _findVisibleIndexes(); + } + + @override + void performLayout() { + super.performLayout(); + trendlineContainer?.layout(constraints); + } + + @override + void transformValues() { + if (xAxis == null || + yAxis == null || + segments.isEmpty || + xAxis!.visibleRange == null || + yAxis!.visibleRange == null) { + return; + } + + final int segmentsCount = segments.length; + if (canFindLinearVisibleIndexes) { + if (visibleIndexes.isNotEmpty) { + final int start = visibleIndexes[0]; + final int end = visibleIndexes[1]; + for (int i = start; i <= end; i++) { + if (i < segmentsCount) { + final ChartSegment segment = segments[i]; + segment.animationFactor = segmentAnimationFactor; + segment.transformValues(); + customizeSegment(segment); + } + } + } + } else { + for (final int index in visibleIndexes) { + if (index < segmentsCount) { + final ChartSegment segment = segments[index]; + segment.animationFactor = segmentAnimationFactor; + segment.transformValues(); + customizeSegment(segment); + } + } + } + } + + @nonVirtual + void updateSegmentGradient( + ChartSegment segment, { + Rect? gradientBounds, + LinearGradient? gradient, + LinearGradient? borderGradient, + }) { + segment.fillPaint.shader = null; + segment.strokePaint.shader = null; + + if (!segment.isEmpty) { + if (onCreateShader != null) { + final ShaderDetails details = ShaderDetails(paintBounds, 'series'); + segment.fillPaint.shader = onCreateShader!(details); + segment.strokePaint.shader = onCreateShader!(details); + } else if (gradient != null && gradientBounds != null) { + segment.fillPaint.shader = gradient.createShader(gradientBounds); + } + + if (borderGradient != null && gradientBounds != null) { + // Border gradient is not working when border color is transparent. + // Hence sets series color to border color. + if (segment.strokePaint.color == Colors.transparent) { + segment.strokePaint.color = segment.fillPaint.color; + } + + segment.strokePaint.shader = borderGradient.createShader( + gradientBounds.deflate(segment.strokePaint.strokeWidth / 2), + ); + } + } + } + + @override + void onPaint(PaintingContext context, Offset offset) { + paintSegments(context, offset); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } + + @override + void paintSegments(PaintingContext context, Offset offset) { + if (parent != null && parent!.render != SeriesRender.normal) { + return; + } + if (segments.isNotEmpty) { + context.canvas.save(); + context.canvas.clipRect(paintBounds); + if (canFindLinearVisibleIndexes) { + if (visibleIndexes.isNotEmpty) { + final int start = visibleIndexes[0]; + final int end = visibleIndexes[1]; + final int segmentsCount = segments.length; + for (int i = start; i <= end && i > -1; i++) { + if (i < segmentsCount) { + final ChartSegment segment = segments[i]; + segment.animationFactor = segmentAnimationFactor; + segment.onPaint(context.canvas); + } + } + } + } else { + for (final int index in visibleIndexes) { + final ChartSegment segment = segments[index]; + segment.animationFactor = segmentAnimationFactor; + segment.onPaint(context.canvas); + } + } + context.canvas.restore(); + } + } + + void paintTrendline(PaintingContext context, Offset offset) { + if (trendlineContainer != null && + parent != null && + parent!.render == SeriesRender.trendline) { + context.canvas.save(); + context.canvas.clipRect( + clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ), + ); + context.paintChild(trendlineContainer!, offset); + context.canvas.restore(); + } + } + + @protected + void paintMarkers(PaintingContext context, Offset offset) { + if (markerContainer != null && + parent != null && + parent!.render == SeriesRender.normal) { + context.paintChild(markerContainer!, offset); + } + } + + @protected + void paintDataLabels(PaintingContext context, Offset offset) { + if (dataLabelContainer != null && + parent != null && + parent!.render == SeriesRender.dataLabel) { + context.paintChild(dataLabelContainer!, offset); + } + } + + void drawDataMarker( + int index, + Canvas canvas, + Paint fillPaint, + Paint strokePaint, + Offset point, + Size size, + DataMarkerType type, [ + CartesianSeriesRenderer? seriesRenderer, + ]) { + if (point.isNaN) { + return; + } + + if (type == DataMarkerType.image) { + if (_markerImage != null) { + paintImage(canvas: canvas, rect: point & size, image: _markerImage!); + } + } else if (type != DataMarkerType.none) { + paint( + canvas: canvas, + rect: point & size, + shapeType: toShapeMarkerType(type), + paint: fillPaint, + borderPaint: strokePaint, + ); + } + } + + void drawDataLabelWithBackground( + int index, + Canvas canvas, + String dataLabel, + Offset offset, + int angle, + TextStyle style, + Paint fillPaint, + Paint strokePaint, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + if (!dataLabelSettings.showZeroValue && dataLabel == '0') { + return; + } + if (!offset.dx.isNaN && !offset.dy.isNaN) { + if (dataLabel.isNotEmpty) { + // TODO(VijayakumarM): Check and optimize. + if (fillPaint.color != Colors.transparent || + (strokePaint.color != const Color.fromARGB(0, 25, 5, 5) && + strokePaint.strokeWidth > 0)) { + final TextPainter textPainter = TextPainter( + text: TextSpan(text: dataLabel, style: style), + textDirection: TextDirection.ltr, + )..layout(); + final Rect dataLabelRect = Rect.fromLTWH( + offset.dx, + offset.dy, + textPainter.width + margin.horizontal, + textPainter.height + margin.vertical, + ); + RRect labelRect = RRect.fromRectAndRadius( + dataLabelRect, + Radius.circular(dataLabelSettings.borderRadius), + ); + // To check and update the label rect and offset by rotated + // label rect height overlaps with plotArea or not. + if (angle != 0) { + final Rect rotatedBounds = calculateRotatedBounds( + dataLabelRect, + dataLabelSettings.angle, + ); + final double heightFromCenter = rotatedBounds.height / 2; + if (paintBounds.bottom < labelRect.center.dy + heightFromCenter) { + labelRect = _rotatedRRect( + labelRect, + labelRect.bottom - heightFromCenter, + ); + offset = Offset(labelRect.left, labelRect.top); + } + if (paintBounds.top > labelRect.center.dy - heightFromCenter) { + labelRect = _rotatedRRect( + labelRect, + labelRect.top + heightFromCenter, + ); + offset = Offset(labelRect.left, labelRect.top); + } + } + + canvas.save(); + canvas.translate(labelRect.center.dx, labelRect.center.dy); + canvas.rotate((angle * pi) / 180); + canvas.translate(-labelRect.center.dx, -labelRect.center.dy); + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + canvas.drawRRect(labelRect, strokePaint); + } + + if (fillPaint.color != Colors.transparent) { + canvas.drawRRect(labelRect, fillPaint); + } + canvas.restore(); + } + } + } + drawDataLabel( + index, + canvas, + dataLabel, + offset.dx + margin.left, + offset.dy + margin.top, + angle, + style, + ); + } + + RRect _rotatedRRect(RRect labelRect, double labelY) { + return RRect.fromRectAndRadius( + Rect.fromCenter( + center: Offset(labelRect.center.dx, labelY), + width: labelRect.width, + height: labelRect.height, + ), + Radius.circular(dataLabelSettings.borderRadius), + ); + } + + /// To customize each data labels. + void drawDataLabel( + int index, + Canvas canvas, + String dataLabel, + double pointX, + double pointY, + int angle, + TextStyle style, + ) { + if (!pointX.isNaN && !pointY.isNaN) { + final TextSpan span = TextSpan(text: dataLabel, style: style); + final TextPainter textPainter = TextPainter( + text: span, + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + canvas.save(); + canvas.translate( + pointX + textPainter.width / 2, + pointY + textPainter.height / 2, + ); + canvas.rotate(degreeToRadian(angle)); + final Offset labelOffset = Offset( + -textPainter.width / 2, + -textPainter.height / 2, + ); + textPainter.paint(canvas, labelOffset); + canvas.restore(); + } + } + + @override + void dispose() { + _chaoticXValues.clear(); + xValues.clear(); + controller._dispose(); + super.dispose(); + } +} + +mixin ContinuousSeriesMixin on CartesianSeriesRenderer { + @override + void createOrUpdateSegments() { + if (dataCount == 0) { + segments.clear(); + return; + } + + final int segmentsCount = segments.length; + if (segmentsCount == 0) { + final ChartSegment segment = createSegment(); + setData(0, segment); + segments.add(segment); + } else { + final ChartSegment segment = segments[0]; + setData(0, segment); + } + } + + @override + void transformValues() { + if (xAxis == null || + yAxis == null || + segments.isEmpty || + xAxis!.visibleRange == null || + yAxis!.visibleRange == null) { + return; + } + + final ChartSegment segment = segments[0]; + segment.animationFactor = segmentAnimationFactor; + segment.transformValues(); + customizeSegment(segment); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + paintSegments(context, offset); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } + + @override + void paintSegments(PaintingContext context, Offset offset) { + if (parent != null && parent!.render != SeriesRender.normal) { + return; + } + + if (segments.isNotEmpty) { + context.canvas.save(); + context.canvas.clipRect(paintBounds); + final ChartSegment segment = segments[0]; + segment.animationFactor = segmentAnimationFactor; + segment.onPaint(context.canvas); + context.canvas.restore(); + } + } + + @override + TooltipInfo? tooltipInfoFromPointIndex(int pointIndex) { + if (segments.isNotEmpty && pointIndex < dataCount) { + return segments[0].tooltipInfo(pointIndex: pointIndex); + } + return null; + } + + @override + int dataPointIndex(Offset position, ChartSegment segment) { + final int segPointIndex = segmentPointIndex(position, segment); + if (segPointIndex != -1) { + return actualPointIndex(segPointIndex); + } + return segPointIndex; + } + + int actualPointIndex(int pointIndex) { + if (_xNullPointIndexes.isNotEmpty) { + for (final int xNullPointIndex in _xNullPointIndexes) { + if (pointIndex >= xNullPointIndex) { + pointIndex++; + } + } + } + return pointIndex; + } + + @override + int segmentPointIndex(Offset position, ChartSegment segment) { + final int length = segment.points.length; + for (int i = 0; i < length; i++) { + final Rect bounds = Rect.fromCenter( + center: segment.points[i], + width: tooltipPadding, + height: tooltipPadding, + ); + if (bounds.contains(position)) { + return i; + } + } + return -1; + } + + @override + ChartSegment segmentAt(int segmentPointIndex) { + return segments[0]; + } + + @override + ChartSegment? visibleSegmentAt(Offset position) { + if (segments.isEmpty) { + return null; + } + + final ChartSegment segment = segments[0]; + if (segment.contains(position)) { + return segment; + } + + return null; + } +} + +mixin RealTimeUpdateMixin on ChartSeriesRenderer { + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (xValueMapper == null || + yPaths == null || + yPaths.isEmpty || + chaoticYLists == null || + chaoticYLists.isEmpty) { + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + if (removedIndexes != null) { + _removeDataPoints( + removedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + if (addedIndexes != null) { + _addDataPoints( + addedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + if (replacedIndexes != null) { + _replaceDataPoints( + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + createOrUpdateSegments(); + markNeedsLegendUpdate(); + markNeedsLayout(); + } + + void _removeDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + final int chaoticYLength = chaoticYLists?.length ?? 0; + final int fPathLength = fPaths?.length ?? 0; + // Sort 'indexes' in descending order to remove higher indexes first, + // preventing shifting issues when removing lower ones. + final List sortedIndexes = List.from(indexes) + ..sort((a, b) => b.compareTo(a)); + for (final int index in sortedIndexes) { + _removeXValueAt(index); + _removeRawSortValueAt(index); + for (int i = 0; i < chaoticYLength; i++) { + if (index < chaoticYLists![i].length) { + chaoticYLists[i].removeAt(index); + } + } + + for (int k = 0; k < fPathLength; k++) { + chaoticFLists![k].removeAt(index); + } + + if (emptyPointIndexes.contains(index)) { + emptyPointIndexes.remove(index); + } + + if (_callbacksEnabled && chartPoints.length > index) { + chartPoints.removeAt(index); + } + } + + _dataCount = _chaoticXValues.length; + // Collecting previous and next index to update them. + final List mutableIndexes = _findMutableIndexes(indexes); + _replaceDataPoints( + mutableIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + void _addDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + final int yPathLength = yPaths!.length; + final int fPathLength = fPaths?.length ?? 0; + + for (final int index in indexes) { + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + for (int i = 0; i < yPathLength; i++) { + final num? yValue = yPaths[i]!(current, i); + if (yValue == null || yValue.isNaN) { + chaoticYLists![i].insert(index, double.nan); + if (!emptyPointIndexes.contains(index)) { + emptyPointIndexes.add(index); + } + } else { + index > chaoticYLists![i].length - 1 + ? chaoticYLists[i].add(yValue) + : chaoticYLists[i].insert(index, yValue); + index > xValues.length - 1 + ? _chaoticXValues.add(index) + : _chaoticXValues.insert(index, xValues[index]); + index > xRawValues.length - 1 + ? _chaoticRawXValues.add(rawX) + : _chaoticRawXValues.insert(index, rawX); + + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none) { + index > _chaoticRawSortValues.length - 1 + ? _chaoticRawSortValues.add(rawX) + : _chaoticRawSortValues.insert(index, rawX); + } + } + } + + if (_callbacksEnabled) { + final List positions = widget.positions; + final ChartPoint point = ChartPoint(x: _chaoticRawXValues[index]); + for (int j = 0; j < yPathLength; j++) { + point[positions[j]] = chaoticYLists![j][index]; + } + chartPoints.insert(index, point); + } + + for (int j = 0; j < fPathLength; j++) { + final Object? fValue = fPaths![j](current, j); + chaoticFLists![j].insert(index, fValue); + } + } + + _dataCount = _chaoticXValues.length; + _applyEmptyPointModeIfNeeded(chaoticYLists!); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); + } + + void _replaceDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + final int yPathLength = yPaths?.length ?? 0; + final int fPathLength = fPaths?.length ?? 0; + + for (final int index in indexes) { + if (index < dataSource!.length - 1) { + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (_xNullPointIndexes.contains(index)) { + _xNullPointIndexes.remove(index); + } + + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + for (int i = 0; i < yPathLength; i++) { + final num? yValue = yPaths![i]!(current, i); + if (yValue == null || yValue.isNaN) { + chaoticYLists![i][index] = double.nan; + if (!emptyPointIndexes.contains(index)) { + emptyPointIndexes.add(index); + } + } else { + if (index < chaoticYLists![i].length) { + chaoticYLists[i][index] = yValue; + _chaoticXValues[index] = xValues[index]; + _chaoticRawXValues[index] = rawX; + + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none) { + _chaoticRawSortValues[index] = rawX; + } + } + if (emptyPointIndexes.contains(index)) { + emptyPointIndexes.remove(index); + } + } + } + + if (_callbacksEnabled) { + final List positions = widget.positions; + final ChartPoint point = ChartPoint( + x: _chaoticRawXValues[index], + ); + for (int j = 0; j < yPathLength; j++) { + point[positions[j]] = chaoticYLists![j][index]; + } + chartPoints[index] = point; + } + + for (int j = 0; j < fPathLength; j++) { + chaoticFLists![j][index] = fPaths![j](current, j); + } + } + } + + _applyEmptyPointModeIfNeeded(chaoticYLists!); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); + } + + void _removeXValueAt(int index) { + _chaoticRawXValues.removeAt(index); + if (xRawValues.length > index) { + xRawValues.removeAt(index); + } + if (xValues.length > index) { + _chaoticXValues.removeAt(index); + } + } + + void _removeRawSortValueAt(int index) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues.removeAt(index); + } + } + + List _findMutableIndexes(List indexes) { + final List mutableIndexes = []; + for (final int index in indexes) { + final int previousIndex = index - 1; + final int nextIndex = index + 1; + if (previousIndex >= 0 && !indexes.contains(previousIndex)) { + mutableIndexes.add(previousIndex); + } + mutableIndexes.add(index); + if (nextIndex < dataCount && !indexes.contains(nextIndex)) { + mutableIndexes.add(nextIndex); + } + } + return mutableIndexes; + } +} + +mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + // Here fPath is widget specific feature path. + // For example, in bubble series's bubbleSizeMapper is a feature path. + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (xValueMapper == null || + yPaths == null || + yPaths.isEmpty || + chaoticYLists == null || + chaoticYLists.isEmpty) { + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + if (removedIndexes != null) { + _removeDataPoints( + removedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + if (addedIndexes != null) { + _addDataPoints( + addedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + if (replacedIndexes != null) { + _replaceDataPoints( + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + _applyEmptyPointModeIfNeeded(chaoticYLists); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); + final DoubleRange xRange = _findMinMaxXRange(xValues); + final DoubleRange yRange = _findMinMaxYRange(chaoticYLists); + _updateAxisRange( + xRange.minimum, + xRange.maximum, + yRange.minimum, + yRange.maximum, + ); + computeNonEmptyYValues(); + _populateTrendlineDataSource(); + _updateXValuesForCategoryTypeAxes(); + + canUpdateOrCreateSegments = true; + markNeedsLayout(); + } + + void _removeDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + // Removing a data point can cause the following: + // - Data points sorting will not be affected. + // - The minimum and maximum range will be affected. + // - Visible indexes may be affected. + // - Segment's index will be affected. + // - The bull segment of the candle series will be affected. + // - An existing null point may be removed. + // - The calculation of empty point averages will be affected. + // - Trendlines and indicators that rely on series data points + // will be affected. + // - The corresponding data label and marker will need to be removed. + // - The auto position of data labels will be affected for + // continuous series. + final int chaoticYLength = chaoticYLists?.length ?? 0; + final int fPathLength = fPaths?.length ?? 0; + // Sort 'indexes' in descending order to remove higher indexes first, + // preventing shifting issues when removing lower ones. + final List sortedIndexes = List.from(indexes) + ..sort((a, b) => b.compareTo(a)); + for (final int index in sortedIndexes) { + if (index < 0 || index >= _dataCount) { + continue; + } + + _removeXValueAt(index); + _removeRawSortValueAt(index); + for (int i = 0; i < chaoticYLength; i++) { + chaoticYLists![i].removeAt(index); + } + + for (int k = 0; k < fPathLength; k++) { + chaoticFLists![k].removeAt(index); + } + + if (emptyPointIndexes.contains(index)) { + emptyPointIndexes.remove(index); + } + + if (_callbacksEnabled && chartPoints.length > index) { + chartPoints.removeAt(index); + } + } + + _dataCount = _chaoticXValues.length; + // Collecting previous and next index to update them. + final List mutableIndexes = _findMutableIndexes(indexes); + _replaceDataPoints( + mutableIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + void _addDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + // Updating a data point can cause the following: + // - Data points sorting will be affected. + // - The minimum and maximum range will be affected. + // - Visible indexes will be affected. + // - Segment's index will be affected. + // - The bull segment of the candle series will be affected. + // - A new null point may be added. + // - The calculation of empty point averages will be affected. + // - Trendlines and indicators that rely on series data points + // will be affected. + // - The corresponding data label and marker needs to be added. + // - The auto position of data labels will be affected for + // continuous series. + final int yPathLength = yPaths!.length; + final int fPathLength = fPaths?.length ?? 0; + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(int, D?, num) insertXValue = + _insertXValueIntoRawAndChaoticXLists; + final Function(int, D?) insertRawSortValue = + _insertRawXValueIntoChaoticRawSortValue; + + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + for (final int index in indexes) { + if (index < 0 || index >= dataSource!.length) { + continue; + } + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + final num currentX = preferredXValue(index, rawX); + insertXValue(index, rawX, currentX); + insertRawSortValue(index, rawX); + xMinimum = min(xMinimum, currentX); + xMaximum = max(xMaximum, currentX); + if (_hasLinearDataSource) { + _hasLinearDataSource = isValueLinear(index, currentX, _chaoticXValues); + } + + for (int i = 0; i < yPathLength; i++) { + final num? yValue = yPaths[i]!(current, i); + if (yValue == null || yValue.isNaN) { + chaoticYLists![i].insert(index, double.nan); + if (!emptyPointIndexes.contains(index)) { + emptyPointIndexes.add(index); + } + } else { + chaoticYLists![i].insert(index, yValue); + yMinimum = min(yMinimum, yValue); + yMaximum = max(yMaximum, yValue); + } + } + + if (_callbacksEnabled) { + final List positions = widget.positions; + final num xValue = _chaoticXValues[index]; + final CartesianChartPoint point = CartesianChartPoint( + x: _chaoticRawXValues[index], + xValue: xValue, + ); + for (int j = 0; j < yPathLength; j++) { + point[positions[j]] = chaoticYLists![j][index]; + } + chartPoints.insert(index, point); + } + + for (int j = 0; j < fPathLength; j++) { + final Object? fValue = fPaths![j](current, index); + chaoticFLists![j].insert(index, fValue); + } + } + + _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; + } + + void _updateAxisRange( + num xMinimum, + num xMaximum, + num yMinimum, + num yMaximum, + ) { + if ((xMin.isInfinite && xMinimum.isFinite) || xMinimum != xMin) { + xMin = xMinimum; + _isXRangeChanged = true; + } + + if ((xMax.isInfinite && xMaximum.isFinite) || xMaximum != xMax) { + xMax = xMaximum; + _isXRangeChanged = true; + } + + // When adding data points between the default range, axis range won't + // change. So, visible indexes remains empty. + if (!_isXRangeChanged) { + _findVisibleIndexes(); + } else { + xAxis?.markNeedsLayout(); + } + + if ((yMin.isInfinite && yMinimum.isFinite) || yMinimum != yMin) { + yMin = yMinimum; + _isYRangeChanged = true; + yAxis?.markNeedsLayout(); + } + + if ((yMax.isInfinite && yMaximum.isFinite) || yMaximum != yMax) { + yMax = yMaximum; + _isYRangeChanged = true; + yAxis?.markNeedsLayout(); + } + + markNeedsLayout(); + } + + void _replaceDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + // Updating a data point can cause the following: + // - Data points sorting will be affected. + // - The minimum and maximum range will be affected. + // - Visible indexes will not be affected. + // - Segment's index will not be affected. + // - The bull segment of the candle series will be affected. + // - An existing null point may be removed or new null can be added. + // - The calculation of empty point averages will be affected. + // - Trendlines and indicators that rely on series data points + // will be affected. + // - The corresponding data label and marker will be affected. + // - The auto position of data labels will be affected for + // continuous series. + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(int, D?, num) replaceXValue = + _updateXValueIntoRawAndChaoticXLists; + final Function(int, D?) replaceRawSortValue = + _updateRawXValueIntoChaoticRawSortValue; + + final int yPathLength = yPaths?.length ?? 0; + final int fPathLength = fPaths?.length ?? 0; + + for (final int index in indexes) { + if (index < 0 || index >= dataSource!.length) { + continue; + } + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (_xNullPointIndexes.contains(index)) { + _xNullPointIndexes.remove(index); + } + + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + final num currentX = preferredXValue(index, rawX); + replaceXValue(index, rawX, currentX); + replaceRawSortValue(index, rawX); + for (int i = 0; i < yPathLength; i++) { + final num? yValue = yPaths![i]!(current, i); + if (yValue == null || yValue.isNaN) { + chaoticYLists![i][index] = double.nan; + if (!emptyPointIndexes.contains(index)) { + emptyPointIndexes.add(index); + } + } else { + chaoticYLists![i][index] = yValue; + if (emptyPointIndexes.contains(index)) { + emptyPointIndexes.remove(index); + } + } + } + + if (_callbacksEnabled) { + final List positions = widget.positions; + final num xValue = _chaoticXValues[index]; + final CartesianChartPoint point = CartesianChartPoint( + x: _chaoticRawXValues[index], + xValue: xValue, + ); + for (int j = 0; j < yPathLength; j++) { + point[positions[j]] = chaoticYLists![j][index]; + } + chartPoints[index] = point; + } + + for (int j = 0; j < fPathLength; j++) { + chaoticFLists![j][index] = fPaths![j](current, index); + } + } + } + + DoubleRange _findMinMaxXRange(List values) { + num min = double.infinity; + num max = double.negativeInfinity; + for (final num value in values) { + if (value < min) { + min = value; + } + if (value > max) { + max = value; + } + } + + return DoubleRange(min, max); + } + + DoubleRange _findMinMaxYRange(List> yLists) { + num min = double.infinity; + num max = double.negativeInfinity; + for (final List values in yLists) { + for (final num value in values) { + if (value < min) { + min = value; + } + if (value > max) { + max = value; + } + } + } + + return DoubleRange(min, max); + } + + void _removeXValueAt(int index) { + _chaoticRawXValues.removeAt(index); + if (index < xRawValues.length) { + xRawValues.removeAt(index); + } + + // For category type axis, the _chaoticXValues and xValues fields contain + // index values and the x-axis range updated by xValues. Here, the last + // index is removed from the list from _chaoticXValues and xValues to + // update the x-axis range correctly. + // + // Example: If a data source initially has four data points and the value + // stored in _chaoticXValues and xValues list for a category-type axis is + // [0, 1, 2, 3] and x-axis range min and max value was calculated based on + // xValues. If a data point is removed using the remove data points method, + // the corresponding value is removed from the xRawValues list and the data + // points length reduced from four to three, and by removing the last index + // the _chaoticXValues and xValues list is updated to [0, 1, 2] and x-axis + // range was updated correctly with min value as 0 and max value as 2. + if (xAxis is RenderCategoryAxis || xAxis is RenderDateTimeCategoryAxis) { + // If add and remove operations on data points are performed simultaneously, + // the values of _chaoticXValues and xValues remain the same, and the range + // is not updated. To resolve this, _isXRangeChanged is set to true for + // category-type axes. + _isXRangeChanged = true; + _chaoticXValues.removeLast(); + if (index < xValues.length) { + xValues.removeLast(); + } + } else { + _chaoticXValues.removeAt(index); + if (index < xValues.length) { + xValues.removeAt(index); + } + } + } + + void _updateXValueIntoRawAndChaoticXLists(int index, D? raw, num preferred) { + _chaoticRawXValues[index] = raw; + _chaoticXValues[index] = preferred; + } + + void _insertXValueIntoRawAndChaoticXLists(int index, D? raw, num preferred) { + _chaoticRawXValues.insert(index, raw); + _chaoticXValues.insert(index, preferred); + } + + void _removeRawSortValueAt(int index) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues.removeAt(index); + } + } + + void _updateRawXValueIntoChaoticRawSortValue(int index, D? raw) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues[index] = raw; + } + } + + void _insertRawXValueIntoChaoticRawSortValue(int index, D? raw) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues.insert(index, raw); + } + } + + List _findMutableIndexes(List indexes) { + final List mutableIndexes = []; + for (final int index in indexes) { + final int previousIndex = index - 1; + final int nextIndex = index + 1; + if (previousIndex >= 0 && !indexes.contains(previousIndex)) { + mutableIndexes.add(previousIndex); + } + mutableIndexes.add(index); + if (nextIndex < dataCount && !indexes.contains(nextIndex)) { + mutableIndexes.add(nextIndex); + } + } + + return mutableIndexes; + } +} + +/// Represents the side by side series. +mixin SbsSeriesMixin on CartesianSeriesRenderer { + double get spacing => _spacing; + double _spacing = 0; + set spacing(double value) { + assert( + value >= 0 && value <= 1, + 'The spacing of the series should be between 0 and 1', + ); + if (value != _spacing) { + _spacing = value; + } + } + + double get width => _width; + double _width = 0.7; + set width(double value) { + assert( + value >= 0 && value <= 1, + 'The width of the series should be between 0 and 1', + ); + if (value != _width) { + _width = value; + } + } + + num get bottom => _bottom; + num _bottom = 0; + + DoubleRange get sbsInfo => _sbsInfo; + DoubleRange _sbsInfo = DoubleRange.zero(); + set sbsInfo(DoubleRange value) { + if (_sbsInfo != value) { + _sbsInfo = value; + canUpdateOrCreateSegments = true; + markNeedsLayout(); + } + } + + num primaryAxisAdjacentDataPointsMinDiff = 1; + + late List _sortedXValues; + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + + if (dataCount < 1) { + return; + } + + _calculateSbsInfo(); + + if (this is! WaterfallSeriesRenderer) { + populateChartPoints(); + } + } + + void _calculateSbsInfo() { + if (canFindLinearVisibleIndexes) { + _sortedXValues = xValues; + } else { + final List xValuesCopy = [...xValues]; + xValuesCopy.sort(); + _sortedXValues = xValuesCopy; + } + + num minDelta = double.infinity; + final int length = _sortedXValues.length; + if (length == 1) { + DateTime? minDate; + num? minimumInSeconds; + if (xAxis is RenderDateTimeAxis) { + minDate = DateTime.fromMillisecondsSinceEpoch( + _sortedXValues[0]! as int, + ); + minDate = minDate.subtract(const Duration(days: 1)); + minimumInSeconds = minDate.millisecondsSinceEpoch; + } + final num seriesMin = + (xAxis is RenderDateTimeAxis && xRange.minimum == xRange.maximum) + ? minimumInSeconds! + : xRange.minimum; + final num minVal = xValues[0] - seriesMin; + if (minVal != 0) { + minDelta = min(minDelta, minVal); + } + } else { + for (int i = 0; i < length - 1; i++) { + final num? current = _sortedXValues[i]; + final num? next = _sortedXValues[i + 1]; + if (current == null || next == null) { + continue; + } + + final num delta = (next - current).abs(); + minDelta = min(delta == 0 ? minDelta : delta, minDelta); + } + } + + primaryAxisAdjacentDataPointsMinDiff = minDelta.isInfinite ? 1 : minDelta; + } + + DoubleRange _sbsRange(RenderChartAxis axis) { + if (axis == xAxis) { + if (axis is RenderCategoryAxis || axis is RenderDateTimeCategoryAxis) { + return xRange; + } + + final double diff = primaryAxisAdjacentDataPointsMinDiff / 2; + return xRange.copyWith( + minimum: xRange.minimum - diff, + maximum: xRange.maximum + diff, + ); + } else { + return super.range(axis); + } + } + + @override + DoubleRange range(RenderChartAxis axis) { + final RenderCartesianChartPlotArea? plotArea = parent; + if (axis == yAxis && + axis.anchorRangeToVisiblePoints && + plotArea != null && + plotArea.zoomPanBehavior != null && + plotArea.behaviorArea != null && + plotArea.behaviorArea!.effectiveZoomMode == ZoomMode.x && + _yVisibleRange != null) { + return _yVisibleRange!.copyWith(); + } + + final DoubleRange actualRange = _sbsRange(axis).copyWith(); + if (trendlineContainer == null) { + return actualRange; + } + + return _trendlineRange(actualRange, axis); + } + + @nonVirtual + void updateSegmentTrackerStyle( + ChartSegment segment, + Color trackColor, + Color trackBorderColor, + double trackBorderWidth, + ) { + if (segment is BarSeriesTrackerMixin) { + segment.trackerFillPaint.color = trackColor; + segment.trackerStrokePaint + ..color = trackBorderColor + ..strokeWidth = trackBorderWidth; + } + } + + @override + void performLayout() { + num minYValue = max(yAxis!.visibleRange!.minimum, 0); + if (yAxis! is RenderLogarithmicAxis) { + minYValue = (yAxis! as RenderLogarithmicAxis).toPow(minYValue); + } + + _bottom = xAxis!.crossesAt ?? minYValue; + markerContainer?.sbsInfo = sbsInfo; + super.performLayout(); + } + + @override + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + return alignment == ChartDataLabelAlignment.auto + ? this is StackedSeriesRenderer + ? ChartDataLabelAlignment.top + : ChartDataLabelAlignment.outer + : alignment; + } + + @override + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final ChartDataLabelAlignment alignment = label.labelAlignment; + final ChartSegment segment = segments[label.dataPointIndex]; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + return super.dataLabelSurfaceColor(label); + + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + case ChartDataLabelAlignment.bottom: + return segment.getFillPaint().color; + } + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + num y = current.y!; + switch (current.position) { + case ChartDataPointType.y: + if (alignment == ChartDataLabelAlignment.bottom) { + y = _bottom; + } else if (alignment == ChartDataLabelAlignment.middle) { + y = (y + _bottom) / 2; + } + return _calculateYPosition( + x, + y, + alignment, + size, + isNegative: current.y!.isNegative, + ); + + case ChartDataPointType.high: + return _calculateHighPosition(x, y, alignment, size); + + case ChartDataPointType.low: + return _calculateLowPosition(x, y, alignment, size); + + case ChartDataPointType.open: + return _calculateDataLabelOpenPosition(x, y, alignment, size); + + case ChartDataPointType.close: + return _calculateDataLabelClosePosition(x, y, alignment, size); + + case ChartDataPointType.median: + return _calculateMedianPosition(x, y, alignment, size); + + case ChartDataPointType.outliers: + return _calculateOutlierPosition(x, y, alignment, size); + + case ChartDataPointType.volume: + case ChartDataPointType.mean: + case ChartDataPointType.bubbleSize: + case ChartDataPointType.cumulative: + break; + } + + return Offset.zero; + } + + Offset _calculateYPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, { + bool isNegative = false, + }) { + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? -(dataLabelPadding + size.height + margin.vertical) + : dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.middle: + final Offset center = translateTransform(x, y); + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return center.translate(translationX, translationY); + } + } + + Offset _calculateHighPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.outer, size); + + case ChartDataLabelAlignment.top: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + } + } + + Offset _calculateLowPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + + case ChartDataLabelAlignment.top: + return _calculateYPosition(x, y, ChartDataLabelAlignment.bottom, size); + } + } + + Offset _calculateDataLabelOpenPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.outer, size); + + case ChartDataLabelAlignment.bottom: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + } + } + + Offset _calculateDataLabelClosePosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.outer, size); + + case ChartDataLabelAlignment.bottom: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + } + } + + Offset _calculateMedianPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + + case ChartDataLabelAlignment.bottom: + return _calculateYPosition(x, y, ChartDataLabelAlignment.bottom, size); + } + } + + Offset _calculateOutlierPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.outer, size); + + case ChartDataLabelAlignment.bottom: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + } + } +} + +/// Represents the stacking series. +mixin StackingSeriesMixin on AxisDependent { + String get groupName => _groupName; + String _groupName = ''; + set groupName(String value) { + if (_groupName != value) { + _groupName = value; + } + } +} + +/// Represents the column and bar type series for the tracker support. +mixin BarSeriesTrackerMixin on ChartSegment { + /// Gets the color of the tracker. + Paint getTrackerFillPaint() => trackerFillPaint; + + /// Gets the border color of the tracker. + Paint getTrackerStrokePaint() => trackerStrokePaint; + + /// Fill paint of the tracker segment. + final Paint trackerFillPaint = Paint()..isAntiAlias = true; + + /// Stroke paint of the tracker segment. + final Paint trackerStrokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + RRect? _trackerRect; + + void calculateTrackerBounds( + num x, + num y, + BorderRadius borderRadius, + double trackPadding, + double trackBorderWidth, + CartesianSeriesRenderer series, + ) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final bool isTransposed = series.isTransposed; + final bool isInversed = series.xAxis!.isInversed; + final num minimum = series.yAxis!.visibleRange!.minimum; + final num maximum = series.yAxis!.visibleRange!.maximum; + final double trackerPadding = trackBorderWidth + trackPadding; + + final num trackTop = max(maximum, minimum); + final num trackBottom = min(maximum, minimum); + double left = transformX(x, trackTop); + double top = transformY(x, trackTop); + double right = transformX(y, trackBottom); + double bottom = transformY(y, trackBottom); + + // TODO(Natrayansf): Avoid isTransposed case checking. + // And, calculate the tracker bounds within the plot area bounds. + if (!isTransposed) { + if (!isInversed) { + left -= trackerPadding; + right += trackerPadding; + } else { + left += trackerPadding; + right -= trackerPadding; + } + } else { + if (!isInversed) { + top += trackerPadding; + bottom -= trackerPadding; + } else { + top -= trackerPadding; + bottom += trackerPadding; + } + } + + _trackerRect = toRRect(left, top, right, bottom, borderRadius); + } + + @override + void onPaint(Canvas canvas) { + if (_trackerRect != null && !_trackerRect!.isEmpty) { + Paint paint = getTrackerFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawRRect(_trackerRect!, paint); + } + + paint = getTrackerStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawRRect(_trackerRect!.deflate(paint.strokeWidth / 2), paint); + } + } + } + + @override + void dispose() { + _trackerRect = null; + super.dispose(); + } +} + +mixin SegmentAnimationMixin on ChartSeriesRenderer { + @override + void onLoadingAnimationUpdate() { + _animationFactor = _animation!.value; + segmentAnimationFactor = _animationFactor; + } +} + +mixin LineSeriesMixin on CartesianSeriesRenderer { + @override + void paintSegments(PaintingContext context, Offset offset) { + if (parent != null && parent!.render != SeriesRender.normal) { + return; + } + if (segments.isEmpty) { + return; + } + + if (canFindLinearVisibleIndexes) { + if (visibleIndexes.isNotEmpty) { + final int start = visibleIndexes[0]; + int end = visibleIndexes[1]; + final int segmentsCount = segments.length; + // Internally added empty last segment for handling realtime data point + // update as common. While rendering, ignored the last segment paint. + final int lastIndex = segmentsCount - 1; + if (end == lastIndex) { + end = lastIndex - 1; + } + for (int i = start; i <= end && i > -1; i++) { + if (i < segmentsCount) { + final ChartSegment segment = segments[i]; + segment.animationFactor = segmentAnimationFactor; + segment.onPaint(context.canvas); + } + } + } + } else { + for (final int index in visibleIndexes) { + final ChartSegment segment = segments[index]; + segment.animationFactor = segmentAnimationFactor; + segment.onPaint(context.canvas); + } + } + } +} + +/// Renders the xy series. +/// +/// Cartesian charts uses two axis namely x and y, to render. +abstract class XyDataSeries extends CartesianSeries { + /// Creating an argument constructor of XyDataSeries class. + const XyDataSeries({ + super.key, + this.yValueMapper, + super.onCreateRenderer, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.xValueMapper, + super.dataLabelMapper, + super.name, + super.dataSource, + super.xAxisName, + super.yAxisName, + super.pointColorMapper, + super.legendItemText, + super.sortFieldValueMapper, + super.gradient, + super.borderGradient, + super.trendlines, + super.markerSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.emptyPointSettings, + super.dataLabelSettings, + super.animationDuration, + super.dashArray, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.opacity, + super.animationDelay, + super.color, + super.initialSelectedDataIndexes, + super.sortingOrder, + }); + + /// Field in the data source, which is considered as y-value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// dataSource: chartData, + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// ), + /// ], + /// ); + /// } + /// final List chartData = [ + /// SalesData(1, 23), + /// SalesData(2, 35), + /// SalesData(3, 19) + /// ]; + /// + /// class SalesData { + /// SalesData(this.x, this.y); + /// final double x; + /// final double y; + /// } + /// ``` + final ChartValueMapper? yValueMapper; + + @override + List get positions => [ + ChartDataPointType.y, + ]; + + @override + XyDataSeriesRenderer createRenderObject(BuildContext context) { + final XyDataSeriesRenderer renderer = + super.createRenderObject(context) as XyDataSeriesRenderer; + renderer.yValueMapper = yValueMapper; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + XyDataSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.yValueMapper = yValueMapper; + } +} + +abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer + with CartesianRealTimeUpdateMixin { + final List yValues = []; + List nonEmptyYValues = []; + final List _chaoticYValues = []; + + ChartValueMapper? yValueMapper; + + void _resetYLists() { + yValues.clear(); + nonEmptyYValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _chaoticYValues.clear(); + _resetYLists(); + super._resetDataSourceHolders(); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + if (this is! WaterfallSeriesRenderer) { + populateChartPoints(); + } + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + _resetYLists(); + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + @override + DoubleRange _calculateYRange({List>? yLists}) { + if (yLists == null) { + yLists = >[yValues]; + } else { + yLists.add(yValues); + } + return super._calculateYRange(yLists: yLists); + } + + @override + void computeNonEmptyYValues() { + nonEmptyYValues.clear(); + if (emptyPointSettings.mode == EmptyPointMode.gap || + emptyPointSettings.mode == EmptyPointMode.drop) { + final List yValuesCopy = [...yValues]; + nonEmptyYValues = yValuesCopy; + for (int i = 0; i < dataCount; i++) { + if (i == 0 && yValues[i].isNaN) { + nonEmptyYValues[i] = 0; + } else if (yValues[i].isNaN) { + nonEmptyYValues[i] = nonEmptyYValues[i - 1]; + } + } + } else { + final List yValuesCopy = [...yValues]; + nonEmptyYValues = yValuesCopy; + } + } + + @override + void _populateTrendlineDataSource() { + trendlineContainer?.populateDataSource( + xValues, + seriesYValues: nonEmptyYValues, + ); + } + + @override + num trackballYValue(int index) => yValues[index]; + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (this is WaterfallSeriesRenderer) { + super.populateChartPoints(positions: positions, yLists: yLists); + return; + } + + if (yLists == null) { + yLists = >[yValues]; + positions = [ChartDataPointType.y]; + } else { + yLists.add(yValues); + positions!.add(ChartDataPointType.y); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void performLayout() { + super.performLayout(); + + if (this is! StackedSeriesRenderer && this is! WaterfallSeriesRenderer) { + if (markerContainer != null) { + markerContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[yValues] + ..animation = markerAnimation + ..layout(constraints); + } + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[yValues] + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + + if (dataLabelSettings.isVisible && + dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + dataLabelContainer!.handleDataLabelCollision(this); + } + } + } + } + + @override + void dispose() { + _chaoticYValues.clear(); + _resetYLists(); + super.dispose(); + } +} + +/// Represents the stacked series base class. +abstract class StackedSeriesBase extends XyDataSeries { + /// Creates an instance of stacked series renderer. + const StackedSeriesBase({ + super.key, + super.onCreateRenderer, + super.dataSource, + super.xValueMapper, + super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.dashArray, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1.0, + this.trackPadding = 0.0, + this.isTrackVisible = false, + super.trendlines, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.borderWidth, + super.selectionBehavior, + super.initialSelectedDataIndexes, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.animationDelay, + super.opacity, + }); + + /// Renders column with track. Track is a rectangular bar rendered from the + /// start to the end of the axis. Column series + /// will be rendered above the track. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// isTrackVisible: true, + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isTrackVisible; + + /// Color of the track. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// isTrackVisible: true, + /// trackColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackColor; + + /// Color of the track border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackBorderColor; + + /// Width of the track border. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red , + /// trackBorderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackBorderWidth; + + /// Padding of the track. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// isTrackVisible: true, + /// trackPadding: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackPadding; + + @override + StackedSeriesRenderer createRenderObject(BuildContext context) { + final StackedSeriesRenderer renderer = + super.createRenderObject(context) as StackedSeriesRenderer; + renderer + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..isTrackVisible = isTrackVisible; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..isTrackVisible = isTrackVisible; + } +} + +/// Represents the stacked series renderer class. +abstract class StackedSeriesRenderer extends XyDataSeriesRenderer + with StackingSeriesMixin { + Color get trackBorderColor => _trackBorderColor; + Color _trackBorderColor = Colors.transparent; + set trackBorderColor(Color value) { + if (_trackBorderColor != value) { + _trackBorderColor = value; + } + } + + Color get trackColor => _trackColor; + Color _trackColor = Colors.grey; + set trackColor(Color value) { + if (_trackColor != value) { + _trackColor = value; + } + } + + double get trackBorderWidth => _trackBorderWidth; + double _trackBorderWidth = 1.0; + set trackBorderWidth(double value) { + if (_trackBorderWidth != value) { + _trackBorderWidth = value; + } + } + + double get trackPadding => _trackPadding; + double _trackPadding = 0.0; + set trackPadding(double value) { + if (_trackPadding != value) { + _trackPadding = value; + } + } + + bool get isTrackVisible => _isTrackVisible; + bool _isTrackVisible = false; + set isTrackVisible(bool value) { + if (_isTrackVisible != value) { + _isTrackVisible = value; + } + } + + List topValues = []; + List bottomValues = []; + + // It specifies the rendering of bottom values for the stacked area and + // stacked area 100 series while considering the 'drop empty point' mode. + List prevSeriesYValues = []; + + // It both specifies for stacking 100 series. + bool _isStacked100 = false; + Map _percentageValues = {}; + + // Stores StackYValues considering empty point modes with yValues. + List _stackYValues = []; + + @override + void _resetYLists() { + _resetStackedYLists(); + super._resetYLists(); + } + + void _resetStackedYLists() { + topValues.clear(); + bottomValues.clear(); + _percentageValues.clear(); + _stackYValues.clear(); + } + + @nonVirtual + void _applyDropOrGapEmptyPointModes(StackedSeriesRenderer series) { + if (yValues.isEmpty) { + return; + } + + if (emptyPointSettings.mode == EmptyPointMode.gap || + emptyPointSettings.mode == EmptyPointMode.drop) { + final List yValuesCopy = [...yValues]; + _stackYValues = yValuesCopy; + final String seriesType = series.runtimeType.toString().toLowerCase(); + final bool isStackedBar = + seriesType.contains('stackedcolumn') || + seriesType.contains('stackedbar'); + for (int i = 0; i < dataCount; i++) { + if (_stackYValues[i].isNaN) { + _stackYValues[i] = i != 0 && !isStackedBar ? _stackYValues[i - 1] : 0; + } + } + } else { + final List yValuesCopy = [...yValues]; + _stackYValues = yValuesCopy; + } + } + + @nonVirtual + void _calculateStackingValues(StackedSeriesRenderer series) { + if (series.xAxis == null || series.yAxis == null) { + return; + } + + final List yDependents = series.yAxis!.dependents; + if (yDependents.isEmpty) { + return; + } + + StackedSeriesRenderer? current; + StackedSeriesRenderer? previous; + String groupName = ''; + List<_StackingInfo>? positiveValues; + List<_StackingInfo>? negativeValues; + + if (series is Stacking100SeriesMixin) { + _isStacked100 = true; + _calculatePercentageValues(yDependents); + } + + for (final AxisDependent yDependant in yDependents) { + if (yDependant is! StackedSeriesRenderer) { + continue; + } + + current = yDependant; + + if (!current.controller.isVisible || current.dataCount == 0) { + continue; + } + + final String seriesType = current.runtimeType.toString().toLowerCase(); + final bool isStackedArea = seriesType.contains('stackedarea'); + groupName = isStackedArea ? 'stackedareagroup' : current.groupName; + _StackingInfo? currentPositiveStackInfo; + + if (positiveValues == null || negativeValues == null) { + positiveValues = <_StackingInfo>[]; + currentPositiveStackInfo = _StackingInfo(groupName, {}); + positiveValues.add(currentPositiveStackInfo); + negativeValues = <_StackingInfo>[]; + negativeValues.add(_StackingInfo(groupName, {})); + } + + _computeStackedValues( + current, + currentPositiveStackInfo, + positiveValues, + negativeValues, + _isStacked100, + groupName, + ); + + if (previous != null) { + current.prevSeriesYValues = previous.yValues; + } else { + current.prevSeriesYValues = current.yValues; + } + + previous = current; + } + } + + void _computeStackedValues( + StackedSeriesRenderer current, + _StackingInfo? currentPositiveStackInfo, + List<_StackingInfo> positiveValues, + List<_StackingInfo> negativeValues, + bool isStacked100, + String groupName, + ) { + final String seriesType = current.runtimeType.toString().toLowerCase(); + final bool isStackedLine = seriesType.contains('stackedline'); + final EmptyPointMode emptyPointMode = current.emptyPointSettings.mode; + final bool isDropOrGapMode = + emptyPointMode == EmptyPointMode.drop || + emptyPointMode == EmptyPointMode.gap; + final List actualYValues = [...current._stackYValues]; + _StackingInfo? currentNegativeStackInfo; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + current.topValues.clear(); + current.bottomValues.clear(); + + if (positiveValues.isNotEmpty) { + for (int k = 0; k < positiveValues.length; k++) { + if (groupName == positiveValues[k].groupName) { + currentPositiveStackInfo = positiveValues[k]; + break; + } else if (k == positiveValues.length - 1) { + currentPositiveStackInfo = _StackingInfo(groupName, {}); + positiveValues.add(currentPositiveStackInfo); + } + } + } + + if (negativeValues.isNotEmpty) { + for (int k = 0; k < negativeValues.length; k++) { + if (groupName == negativeValues[k].groupName) { + currentNegativeStackInfo = negativeValues[k]; + break; + } else if (k == negativeValues.length - 1) { + currentNegativeStackInfo = _StackingInfo(groupName, {}); + negativeValues.add(currentNegativeStackInfo); + } + } + } + + final int length = current.dataCount; + for (int i = 0; i < length; i++) { + final num xValue = current.xValues[i]; + num yValue = actualYValues[i]; + if (currentPositiveStackInfo?.stackingValues != null) { + if (!currentPositiveStackInfo!.stackingValues.containsKey(xValue)) { + currentPositiveStackInfo.stackingValues[xValue] = 0; + } + } + + if (currentNegativeStackInfo?.stackingValues != null) { + if (!currentNegativeStackInfo!.stackingValues.containsKey(xValue)) { + currentNegativeStackInfo.stackingValues[xValue] = 0; + } + } + + if (isStacked100) { + yValue = yValue / current._percentageValues[xValue]! * 100; + yValue = yValue.isNaN ? 0 : yValue; + } + + num stackValue = 0; + if (yValue >= 0) { + if (currentPositiveStackInfo!.stackingValues.containsKey(xValue)) { + stackValue = currentPositiveStackInfo.stackingValues[xValue]!; + currentPositiveStackInfo.stackingValues[xValue] = stackValue + yValue; + } + } else { + if (currentNegativeStackInfo!.stackingValues.containsKey(xValue)) { + stackValue = currentNegativeStackInfo.stackingValues[xValue]!; + currentNegativeStackInfo.stackingValues[xValue] = stackValue + yValue; + } + } + + // Add stacking top and bottom values. + current.bottomValues.add(stackValue.toDouble()); + current.topValues.add((stackValue + yValue).toDouble()); + if (isStacked100 && current.topValues[i] > 100) { + current.topValues[i] = 100; + } + + if (isStackedLine && current.yValues[i].isNaN && isDropOrGapMode) { + current.topValues[i] = double.nan; + } + + // Calculate current series minimum and maximum range. + num top = current.topValues[i]; + top = top.isNaN ? yMaximum : top; + num bottom = current.bottomValues[i]; + bottom = min(bottom.isNaN ? yMinimum : bottom, top); + yMinimum = min(yMinimum, bottom); + yMaximum = max(yMaximum, top); + } + + num minY = yMinimum; + num maxY = yMaximum; + if (yMinimum > yMaximum) { + minY = isStacked100 ? -100 : yMaximum; + } + + if (yMaximum < yMinimum) { + maxY = 0; + } + + yMin = min(yMin, minY); + yMax = max(yMax, maxY); + } + + void _calculatePercentageValues(List yDependents) { + StackedSeriesRenderer? current; + List<_StackingInfo>? percentageInfo; + for (final AxisDependent yDependant in yDependents) { + StackedSeriesRenderer? yDependantSeries; + if (yDependant is StackedSeriesRenderer) { + yDependantSeries = yDependant; + } + + if (yDependantSeries != null) { + current = yDependantSeries; + } + + if (current == null) { + continue; + } + + final int length = current.dataCount; + if (!current.controller.isVisible || length == 0) { + continue; + } + + final String seriesType = current.runtimeType.toString().toLowerCase(); + final bool isContainsStackedArea100 = seriesType.contains( + 'stackedarea100', + ); + final String groupName = + isContainsStackedArea100 ? 'stackedareagroup' : current.groupName; + + _StackingInfo? stackingInfo; + if (percentageInfo == null) { + percentageInfo = <_StackingInfo>[]; + stackingInfo = _StackingInfo(groupName, {}); + } + + for (int i = 0; i < length; i++) { + final num xValue = current.xValues[i]; + final num yValue = current._stackYValues[i]; + if (percentageInfo.isNotEmpty) { + final int percentageLength = percentageInfo.length; + for (int k = 0; k < percentageLength; k++) { + if (groupName == percentageInfo[k].groupName) { + stackingInfo = percentageInfo[k]; + break; + } else if (k == percentageLength - 1) { + stackingInfo = _StackingInfo(groupName, {}); + percentageInfo.add(stackingInfo); + } + } + } + + if (stackingInfo?.stackingValues != null) { + if (!stackingInfo!.stackingValues.containsKey(xValue)) { + stackingInfo.stackingValues[xValue] = 0; + } + } + + if (stackingInfo!.stackingValues.containsKey(xValue)) { + if (yValue >= 0) { + stackingInfo.stackingValues[xValue] = + stackingInfo.stackingValues[xValue]! + yValue; + } else { + stackingInfo.stackingValues[xValue] = + stackingInfo.stackingValues[xValue]! - yValue; + } + } + + if (i == length - 1) { + percentageInfo.add(stackingInfo); + } + } + + if (percentageInfo.isNotEmpty) { + final int percentageLength = percentageInfo.length; + for (int i = 0; i < percentageLength; i++) { + if (isContainsStackedArea100) { + current._percentageValues = percentageInfo[i].stackingValues; + } else if (current.groupName == percentageInfo[i].groupName) { + current._percentageValues = percentageInfo[i].stackingValues; + } + } + } + } + percentageInfo?.clear(); + } + + @override + void performUpdate() { + /// One stacking series rely on the top/bottom values of adjacent stacking + /// series. So, it is necessary to update the segment values whenever + /// the data population is done. + canUpdateOrCreateSegments = true; + super.performUpdate(); + } + + @override + void performLayout() { + super.performLayout(); + + if (markerContainer != null) { + markerContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[topValues] + ..animation = markerAnimation + ..layout(constraints); + } + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[topValues] + ..stackedYValues = yValues + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + + if (dataLabelSettings.isVisible && + dataLabelSettings.labelIntersectAction != LabelIntersectAction.none) { + dataLabelContainer!.handleDataLabelCollision(this); + } + } + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + + /// Calculate [StackYValues] based on empty point modes with yValues. + _applyDropOrGapEmptyPointModes(this); + + /// Calculate [topValues] and [bottomValues] as stacked values. + _calculateStackingValues(this); + + /// The [topValues] are not calculated, + /// when chartSeriesRenderer populateDataSource method calling. + /// So, only we invoke the trendline data source method calling after + /// stacking values calculated. + _populateTrendlineDataSource(); + populateChartPoints(); + } + + @override + DoubleRange _calculateYRange({List>? yLists}) { + if (yLists == null) { + yLists = >[topValues]; + } else { + yLists.add(topValues); + yLists.add(bottomValues); + } + return super._calculateYRange(yLists: yLists); + } + + @override + void drawDataLabelWithBackground( + int index, + Canvas canvas, + String dataLabel, + Offset offset, + int angle, + TextStyle style, + Paint fillPaint, + Paint strokePaint, + ) { + if (dataLabelMapper == null && + parent!.onDataLabelRender == null && + !dataLabelSettings.showCumulativeValues) { + final num value = yValues[index]; + + if ((!dataLabelSettings.showZeroValue && value == 0) || value.isNaN) { + return; + } + + dataLabel = formatNumericValue(value, yAxis); + } + super.drawDataLabelWithBackground( + index, + canvas, + dataLabel, + offset, + angle, + style, + fillPaint, + strokePaint, + ); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + + /// Clear `stackedYLists` alone instead of resetting `YLists`. + _resetStackedYLists(); + + /// Calculate [StackYValues] based on empty point modes with yValues. + _applyDropOrGapEmptyPointModes(this); + + /// Calculate [topValues] and [bottomValues] as stacked values. + _calculateStackingValues(this); + _populateTrendlineDataSource(); + } + + @override + void _populateTrendlineDataSource() { + trendlineContainer?.populateDataSource(xValues, seriesYValues: topValues); + } + + @override + num trackballYValue(int index) => topValues[index]; + + @override + void dispose() { + _resetYLists(); + super.dispose(); + } +} + +/// Represents the stacking info class. +class _StackingInfo { + /// Creates an instance of stacking info class. + _StackingInfo(this.groupName, this.stackingValues); + + /// Holds the group name. + String groupName; + + /// Holds the list of stacking values. + Map stackingValues; +} + +/// Renders the xy series. +/// +/// Cartesian charts uses two axis namely x and y, to render. +abstract class RangeSeriesBase extends CartesianSeries { + /// Creating an argument constructor of XyDataSeries class. + const RangeSeriesBase({ + super.key, + this.highValueMapper, + this.lowValueMapper, + super.onCreateRenderer, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.xValueMapper, + super.dataLabelMapper, + super.name, + super.dataSource, + super.xAxisName, + super.yAxisName, + super.pointColorMapper, + super.legendItemText, + super.sortFieldValueMapper, + super.gradient, + super.borderGradient, + super.trendlines, + super.markerSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.emptyPointSettings, + super.dataLabelSettings, + super.animationDuration, + super.dashArray, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.opacity, + super.animationDelay, + super.color, + super.initialSelectedDataIndexes, + super.sortingOrder, + }); + + /// Field in the data source, which is considered as high value for the + /// data points. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// dataSource: [ + /// SalesData(2005, 24, 16), + /// SalesData(2006, 22, 12), + /// SalesData(2007, 31, 18), + /// ], + /// xValueMapper: (SalesData data, _) => data.year, + /// lowValueMapper: (SalesData data, _) => data.low, + /// highValueMapper: (SalesData data, _) => data.high, + /// ), + /// ], + /// ); + /// } + /// class SalesData { + /// SalesData(this.year, this.high, this.low); + /// final num year; + /// final num high; + /// final num low; + /// } + /// ``` + final ChartValueMapper? highValueMapper; + + /// Field in the data source, which is considered as low value + /// for the data points. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// dataSource: [ + /// SalesData(2005, 24, 16), + /// SalesData(2006, 22, 12), + /// SalesData(2007, 31, 18), + /// ], + /// xValueMapper: (SalesData data, _) => data.year, + /// lowValueMapper: (SalesData data, _) => data.low, + /// highValueMapper: (SalesData data, _) => data.high, + /// ), + /// ], + /// ); + /// } + /// class SalesData { + /// SalesData(this.year, this.high, this.low); + /// final num year; + /// final num high; + /// final num low; + /// } + /// ``` + final ChartValueMapper? lowValueMapper; + + /// Border color of the series. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// borderColor: Colors.red, + /// borderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final Color borderColor; + + @override + List get positions => [ + ChartDataPointType.high, + ChartDataPointType.low, + ]; + + @override + RangeSeriesRendererBase createRenderObject(BuildContext context) { + final RangeSeriesRendererBase renderer = + super.createRenderObject(context) as RangeSeriesRendererBase; + renderer.highValueMapper = highValueMapper; + renderer.lowValueMapper = lowValueMapper; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RangeSeriesRendererBase renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.highValueMapper = highValueMapper; + renderObject.lowValueMapper = lowValueMapper; + } +} + +abstract class RangeSeriesRendererBase + extends CartesianSeriesRenderer + with CartesianRealTimeUpdateMixin { + final List _chaoticHighValues = []; + final List _chaoticLowValues = []; + final List highValues = []; + final List lowValues = []; + List nonEmptyHighValues = []; + List nonEmptyLowValues = []; + + ChartValueMapper? highValueMapper; + ChartValueMapper? lowValueMapper; + + void _resetYLists() { + highValues.clear(); + lowValues.clear(); + nonEmptyHighValues.clear(); + nonEmptyLowValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _resetYLists(); + super._resetDataSourceHolders(); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (highValueMapper != null && lowValueMapper != null) { + if (sortingOrder == SortingOrder.none) { + super.populateDataSource( + >[highValueMapper!, lowValueMapper!], + >[highValues, lowValues], + >[], + fPaths, + chaoticFLists, + fLists, + ); + } else { + super.populateDataSource( + >[highValueMapper!, lowValueMapper!], + >[_chaoticHighValues, _chaoticLowValues], + >[highValues, lowValues], + fPaths, + chaoticFLists, + fLists, + ); + } + } + + _applyDropOrGapEmptyPointModes(); + populateChartPoints(); + } + + @override + DoubleRange _calculateYRange({List>? yLists}) { + if (yLists == null) { + yLists = >[highValues, lowValues]; + } else { + yLists.add(highValues); + yLists.add(lowValues); + } + return super._calculateYRange(yLists: yLists); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (highValueMapper != null && lowValueMapper != null) { + if (sortingOrder == SortingOrder.none) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + >[highValueMapper!, lowValueMapper!], + >[highValues, lowValues], + >[], + fPaths, + chaoticFLists, + fLists, + ); + } else { + _resetYLists(); + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + >[highValueMapper!, lowValueMapper!], + >[_chaoticHighValues, _chaoticLowValues], + >[highValues, lowValues], + fPaths, + chaoticFLists, + fLists, + ); + } + } + + _applyDropOrGapEmptyPointModes(); + } + + @override + void computeNonEmptyYValues() { + // Swapping high and low values is necessary before invoking + // _populateTrendlineDataSource() to render trendline and + // spline range series based on nonEmptyHighValues and nonEmptyLowValues. + for (int i = 0; i < dataCount; i++) { + num high = highValues[i]; + num low = lowValues[i]; + if (low > high) { + final num temp = high; + high = low; + low = temp; + highValues[i] = high; + lowValues[i] = low; + } + } + + if (emptyPointSettings.mode == EmptyPointMode.gap || + emptyPointSettings.mode == EmptyPointMode.drop) { + final List highValuesCopy = [...highValues]; + final List lowValuesCopy = [...lowValues]; + nonEmptyHighValues = highValuesCopy; + nonEmptyLowValues = lowValuesCopy; + for (int i = 0; i < dataCount; i++) { + if (i == 0 && highValues[i].isNaN) { + nonEmptyHighValues[i] = 0; + } else if (i > 0 && highValues[i].isNaN) { + nonEmptyHighValues[i] = nonEmptyHighValues[i - 1]; + } + + if (i == 0 && lowValues[i].isNaN) { + nonEmptyLowValues[i] = 0; + } else if (i > 0 && lowValues[i].isNaN) { + nonEmptyLowValues[i] = nonEmptyLowValues[i - 1]; + } + } + } else { + nonEmptyHighValues = highValues; + nonEmptyLowValues = lowValues; + } + } + + @nonVirtual + void _applyDropOrGapEmptyPointModes() { + if (emptyPointSettings.mode == EmptyPointMode.gap || + emptyPointSettings.mode == EmptyPointMode.drop) { + for (int i = 0; i < dataCount; i++) { + if (highValues[i].isNaN || lowValues[i].isNaN) { + highValues[i] = double.nan; + lowValues[i] = double.nan; + } + } + } + } + + @override + void _applyZeroEmptyPointMode(List> yLists) { + if (yLists.isNotEmpty) { + for (int i = 0; i < dataCount; i++) { + final List highValues = yLists[0]; + final List lowValues = yLists[1]; + final num highValue = highValues[i]; + final num lowValue = lowValues[i]; + if (highValue.isNaN || lowValue.isNaN) { + highValues[i] = 0; + lowValues[i] = 0; + } + } + } + } + + @override + void _populateTrendlineDataSource() { + trendlineContainer?.populateDataSource( + xValues, + seriesHighValues: nonEmptyHighValues, + seriesLowValues: nonEmptyLowValues, + ); + } + + @override + num trackballYValue(int index) => highValues[index]; + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[highValues, lowValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ]; + } else { + yLists.add(highValues); + yLists.add(lowValues); + positions!.add(ChartDataPointType.high); + positions.add(ChartDataPointType.low); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void performLayout() { + super.performLayout(); + + if (markerContainer != null) { + markerContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[highValues, lowValues] + ..animation = markerAnimation + ..layout(constraints); + } + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[highValues, lowValues] + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + + if (dataLabelSettings.isVisible && + dataLabelSettings.labelIntersectAction != LabelIntersectAction.none) { + dataLabelContainer!.handleDataLabelCollision(this); + } + } + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (current.position) { + case ChartDataPointType.y: + case ChartDataPointType.high: + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + return super.dataLabelPosition( + current, + ChartDataLabelAlignment.outer, + size, + ); + + case ChartDataLabelAlignment.bottom: + return super.dataLabelPosition( + current, + ChartDataLabelAlignment.bottom, + size, + ); + + case ChartDataLabelAlignment.middle: + return super.dataLabelPosition( + current, + ChartDataLabelAlignment.middle, + size, + ); + } + + case ChartDataPointType.low: + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + return super.dataLabelPosition( + current, + ChartDataLabelAlignment.bottom, + size, + ); + + case ChartDataLabelAlignment.top: + return super.dataLabelPosition( + current, + ChartDataLabelAlignment.top, + size, + ); + + case ChartDataLabelAlignment.middle: + return super.dataLabelPosition( + current, + ChartDataLabelAlignment.middle, + size, + ); + } + + case ChartDataPointType.open: + case ChartDataPointType.close: + case ChartDataPointType.volume: + case ChartDataPointType.median: + case ChartDataPointType.mean: + case ChartDataPointType.outliers: + case ChartDataPointType.bubbleSize: + case ChartDataPointType.cumulative: + break; + } + + return Offset.zero; + } + + @override + void dispose() { + _chaoticHighValues.clear(); + _chaoticLowValues.clear(); + _resetYLists(); + super.dispose(); + } +} + +/// Represents the financial series base. +abstract class FinancialSeriesBase extends CartesianSeries { + /// Creates an instance of financial series base. + const FinancialSeriesBase({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + this.lowValueMapper, + this.highValueMapper, + this.openValueMapper, + this.closeValueMapper, + this.volumeValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.dashArray, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + this.spacing = 0, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.borderWidth = 2, + super.selectionBehavior, + super.initialSelectedDataIndexes, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + this.enableSolidCandles = false, + this.bearColor = Colors.red, + this.bullColor = Colors.green, + super.opacity, + super.animationDelay, + this.showIndicationForSameValues = false, + super.trendlines, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Specifies the volume value mapper. + final ChartValueMapper? volumeValueMapper; + + /// Specifies the open value mapper. + final ChartValueMapper? openValueMapper; + + /// Specifies the close value mapper. + final ChartValueMapper? closeValueMapper; + + /// Specifies the high value mapper. + final ChartValueMapper? highValueMapper; + + /// Specifies the low value mapper. + final ChartValueMapper? lowValueMapper; + + /// Specifies the bear color. + final Color bearColor; + + /// Specifies the bull color. + final Color bullColor; + + /// Specifies whether the solid candles. + final bool enableSolidCandles; + + /// Spacing between the columns. The value ranges from 0 to 1. + /// 1 represents 100% and 0 represents 0% of the available space. + /// + /// Spacing also affects the width of the column. For example, setting 20% + /// spacing and 100% width renders the column with 80% of total width. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HiloSeries( + /// spacing: 0.5, + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// If it is set to true, a small vertical line will be rendered. Else nothing + /// will be rendered for that specific data point and left as a blank area. + /// + /// This is applicable for the following series types: + /// * HiLo (High low) + /// * OHLC (Open high low close) + /// * Candle + /// + /// Defaults to `false`. + final bool showIndicationForSameValues; + + final double width; + + @override + List get positions => [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + + @override + FinancialSeriesRendererBase createRenderObject(BuildContext context) { + final FinancialSeriesRendererBase renderer = + super.createRenderObject(context) as FinancialSeriesRendererBase; + renderer + ..volumeValueMapper = volumeValueMapper + ..openValueMapper = openValueMapper + ..closeValueMapper = closeValueMapper + ..highValueMapper = highValueMapper + ..lowValueMapper = lowValueMapper + ..bearColor = bearColor + ..bullColor = bullColor + ..enableSolidCandles = enableSolidCandles + ..spacing = spacing + ..showIndicationForSameValues = showIndicationForSameValues + ..width = width; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + FinancialSeriesRendererBase renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..volumeValueMapper = volumeValueMapper + ..openValueMapper = openValueMapper + ..closeValueMapper = closeValueMapper + ..highValueMapper = highValueMapper + ..lowValueMapper = lowValueMapper + ..bearColor = bearColor + ..bullColor = bullColor + ..enableSolidCandles = enableSolidCandles + ..spacing = spacing + ..showIndicationForSameValues = showIndicationForSameValues + ..width = width; + } +} + +/// Creates series renderer for column series. +abstract class FinancialSeriesRendererBase + extends CartesianSeriesRenderer + with + SbsSeriesMixin, + ClusterSeriesMixin, + CartesianRealTimeUpdateMixin { + /// Calling the default constructor of ColumnSeriesRenderer class. + FinancialSeriesRendererBase(); + + final List _chaoticLowValues = []; + final List _chaoticHighValues = []; + final List _chaoticOpenValues = []; + final List _chaoticCloseValues = []; + final List _chaoticVolumeValues = []; + + final List lowValues = []; + final List highValues = []; + final List openValues = []; + final List closeValues = []; + final List volumeValues = []; + + ChartValueMapper? get volumeValueMapper => _volumeValueMapper; + ChartValueMapper? _volumeValueMapper; + set volumeValueMapper(ChartValueMapper? value) { + if (_volumeValueMapper != value) { + _volumeValueMapper = value; + } + } + + ChartValueMapper? get openValueMapper => _openValueMapper; + ChartValueMapper? _openValueMapper; + set openValueMapper(ChartValueMapper? value) { + if (_openValueMapper != value) { + _openValueMapper = value; + } + } + + ChartValueMapper? get closeValueMapper => _closeValueMapper; + ChartValueMapper? _closeValueMapper; + set closeValueMapper(ChartValueMapper? value) { + if (_closeValueMapper != value) { + _closeValueMapper = value; + } + } + + ChartValueMapper? get highValueMapper => _highValueMapper; + ChartValueMapper? _highValueMapper; + set highValueMapper(ChartValueMapper? value) { + if (_highValueMapper != value) { + _highValueMapper = value; + } + } + + ChartValueMapper? get lowValueMapper => _lowValueMapper; + ChartValueMapper? _lowValueMapper; + set lowValueMapper(ChartValueMapper? value) { + if (_lowValueMapper != value) { + _lowValueMapper = value; + } + } + + Color get bearColor => _bearColor; + Color _bearColor = Colors.red; + set bearColor(Color value) { + if (_bearColor != value) { + _bearColor = value; + } + } + + Color get bullColor => _bullColor; + Color _bullColor = Colors.green; + set bullColor(Color value) { + if (_bullColor != value) { + _bullColor = value; + } + } + + bool get enableSolidCandles => _enableSolidCandles; + bool _enableSolidCandles = false; + set enableSolidCandles(bool value) { + if (_enableSolidCandles != value) { + _enableSolidCandles = value; + } + } + + bool get showIndicationForSameValues => _showIndicationForSameValues; + bool _showIndicationForSameValues = false; + set showIndicationForSameValues(bool value) { + if (_showIndicationForSameValues != value) { + _showIndicationForSameValues = value; + } + } + + void _resetYLists() { + highValues.clear(); + lowValues.clear(); + openValues.clear(); + closeValues.clear(); + volumeValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _chaoticHighValues.clear(); + _chaoticLowValues.clear(); + _chaoticOpenValues.clear(); + _chaoticCloseValues.clear(); + _chaoticVolumeValues.clear(); + _resetYLists(); + super._resetDataSourceHolders(); + } + + @override + void populateDataSource([ + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (highValueMapper != null && + lowValueMapper != null && + openValueMapper != null && + closeValueMapper != null) { + final List> mappers = >[ + highValueMapper!, + lowValueMapper!, + openValueMapper!, + closeValueMapper!, + if (volumeValueMapper != null) volumeValueMapper!, + ]; + final List> finalYLists = >[ + highValues, + lowValues, + openValues, + closeValues, + if (volumeValueMapper != null) volumeValues, + ]; + + if (sortingOrder == SortingOrder.none) { + super.populateDataSource( + mappers, + finalYLists, + >[], + fPaths, + chaoticFLists, + fLists, + ); + } else { + super.populateDataSource( + mappers, + >[ + _chaoticHighValues, + _chaoticLowValues, + _chaoticOpenValues, + _chaoticCloseValues, + if (volumeValueMapper != null) _chaoticVolumeValues, + ], + finalYLists, + fPaths, + chaoticFLists, + fLists, + ); + } + } + + for (int i = 0; i < dataCount; i++) { + num high = highValues[i]; + num low = lowValues[i]; + if (low > high) { + final num temp = high; + high = low; + low = temp; + highValues[i] = high; + lowValues[i] = low; + } + } + + populateChartPoints(); + } + + @override + DoubleRange _calculateYRange({List>? yLists}) { + if (yLists == null) { + yLists = >[highValues, lowValues, openValues, closeValues]; + } else { + yLists.add(highValues); + yLists.add(lowValues); + yLists.add(openValues); + yLists.add(closeValues); + } + return super._calculateYRange(yLists: yLists); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (highValueMapper != null && + lowValueMapper != null && + openValueMapper != null && + closeValueMapper != null) { + final List> mappers = >[ + highValueMapper!, + lowValueMapper!, + openValueMapper!, + closeValueMapper!, + if (volumeValueMapper != null) volumeValueMapper!, + ]; + final List> finalYLists = >[ + highValues, + lowValues, + openValues, + closeValues, + if (volumeValueMapper != null) volumeValues, + ]; + + if (sortingOrder == SortingOrder.none) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + mappers, + finalYLists, + >[], + fPaths, + chaoticFLists, + fLists, + ); + } else { + _resetYLists(); + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + mappers, + >[ + _chaoticHighValues, + _chaoticLowValues, + _chaoticOpenValues, + _chaoticCloseValues, + if (volumeValueMapper != null) _chaoticVolumeValues, + ], + finalYLists, + fPaths, + chaoticFLists, + fLists, + ); + } + } + + for (int i = 0; i < dataCount; i++) { + num high = highValues[i]; + num low = lowValues[i]; + if (low > high) { + final num temp = high; + high = low; + low = temp; + highValues[i] = high; + lowValues[i] = low; + } + } + } + + @override + void _applyEmptyPointModeIfNeeded(List> yLists) { + if (emptyPointIndexes.isNotEmpty) { + emptyPointIndexes.sort(); + switch (emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.drop: + case EmptyPointMode.zero: + break; + + case EmptyPointMode.average: + _applyAverageEmptyPointMode(yLists); + break; + } + } + } + + @override + void _populateTrendlineDataSource() { + trendlineContainer?.populateDataSource( + xValues, + seriesHighValues: highValues, + seriesLowValues: lowValues, + ); + } + + @override + num trackballYValue(int index) => highValues[index]; + + @override + double legendIconBorderWidth() { + return 2; + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[highValues, lowValues, openValues, closeValues]; + positions = [ + ChartDataPointType.high, + ChartDataPointType.low, + ChartDataPointType.open, + ChartDataPointType.close, + ]; + } else { + yLists.add(highValues); + yLists.add(lowValues); + yLists.add(openValues); + yLists.add(closeValues); + positions!.add(ChartDataPointType.high); + positions.add(ChartDataPointType.low); + positions.add(ChartDataPointType.open); + positions.add(ChartDataPointType.close); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void performLayout() { + super.performLayout(); + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[highValues, lowValues, openValues, closeValues] + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + + if (dataLabelSettings.isVisible && + dataLabelSettings.labelIntersectAction != LabelIntersectAction.none) { + dataLabelContainer!.handleDataLabelCollision(this); + } + } + } + + @override + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + final int index = current.dataPointIndex; + if (position == ChartDataPointType.open) { + final num open = openValues[index]; + final num close = closeValues[index]; + return open <= close + ? ChartDataLabelAlignment.top + : ChartDataLabelAlignment.bottom; + } else if (position == ChartDataPointType.close) { + final num open = openValues[index]; + final num close = closeValues[index]; + return close <= open + ? ChartDataLabelAlignment.top + : ChartDataLabelAlignment.bottom; + } + + return alignment == ChartDataLabelAlignment.auto + ? ChartDataLabelAlignment.outer + : alignment; + } + + @override + Offset _calculateDataLabelOpenPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.outer, + size, + ChartDataPointType.open, + ); + + case ChartDataLabelAlignment.bottom: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.top, + size, + ChartDataPointType.open, + ); + } + } + + @protected + Offset _calculateOpenAndClosePosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ChartDataPointType position, + ) { + return _calculateYPosition(x, y, alignment, size); + } + + @override + Offset _calculateDataLabelClosePosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.outer, + size, + ChartDataPointType.close, + ); + + case ChartDataLabelAlignment.bottom: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.top, + size, + ChartDataPointType.close, + ); + } + } + + @override + void dispose() { + _chaoticHighValues.clear(); + _chaoticLowValues.clear(); + _chaoticOpenValues.clear(); + _chaoticCloseValues.clear(); + _chaoticVolumeValues.clear(); + _resetYLists(); + super.dispose(); + } +} + +/// This class holds the property of circular series. +/// +/// To render the Circular chart, create an instance of [PieSeries] or +/// [DoughnutSeries] or [RadialBarSeries], and add it to the +/// series collection property of [SfCircularChart]. You can use the radius +/// property to change the diameter of the circular chart for the plot area. +/// Also, explode the circular chart segment by enabling the explode property. +/// +/// Provide the options of stroke width, stroke color, opacity, and +/// point color mapper to customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} +abstract class CircularSeries extends ChartSeries { + /// Creating an argument constructor of CircularSeries class. + const CircularSeries({ + super.key, + super.xValueMapper, + super.dataLabelMapper, + super.name, + super.dataSource, + super.pointColorMapper, + super.legendItemText, + super.sortFieldValueMapper, + super.enableTooltip = true, + super.enableTrackball = true, + super.emptyPointSettings, + super.dataLabelSettings, + super.animationDuration, + super.initialSelectedDataIndexes, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.legendIconType, + super.opacity, + super.animationDelay, + super.sortingOrder, + this.onCreateRenderer, + this.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + this.yValueMapper, + this.pointShaderMapper, + this.pointRadiusMapper, + this.startAngle = 0, + this.endAngle = 360, + this.radius = '80%', + this.innerRadius = '50%', + this.groupTo, + this.groupMode, + this.pointRenderMode, + this.gap = '1%', + this.cornerStyle = CornerStyle.bothFlat, + this.onCreateShader, + }); + + /// Maps the field name, which will be considered as y-values. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// xValueMapper: (ChartData data, _) => data.xVal, + /// yValueMapper: (ChartData data, _) => data.yVal, + /// ), + /// ], + /// ) + /// ); + /// } + /// class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + /// } + /// ``` + final ChartValueMapper? yValueMapper; + + /// 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 a gradient + /// and another data point may have an image shader. + /// + /// The user can also get the data, index, color and rect values of the + /// specific data point from [ChartShaderMapper] and + /// can use this method, for creating shaders. + /// + /// Defaults to `null`. + /// + /// ```dart + /// import 'dart:ui' as ui; + /// + /// 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, + /// ]),), + /// ], + /// xValueMapper: (ChartData data, _) => data.xVal, + /// yValueMapper: (ChartData data, _) => data.yVal, + /// pointShaderMapper: + /// (data, index, color, rect) => data.pointShader, + /// ), + /// ], + /// ) + /// ); + /// } + /// class ChartData { + /// ChartData(this.xVal, this.yVal, [this.pointShader]); + /// 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. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataSource: [ + /// ChartData('USA', 10, '50%'), + /// ChartData('China', 11, '55%'), + /// ChartData('Russia', 9, '60%'), + /// ChartData('Germany', 10, '65%'), + /// ], + /// xValueMapper: (ChartData data, _) => data.xVal, + /// yValueMapper: (ChartData data, _) => data.yVal, + /// pointRadiusMapper: (ChartData data, _) => data.radius, + /// ), + /// ], + /// ) + /// ); + /// } + /// class ChartData { + /// ChartData(this.xVal, this.yVal, [this.radius]); + /// final String xVal; + /// final int yVal; + /// final String? radius; + /// } + /// ``` + final ChartValueMapper? pointRadiusMapper; + + /// Starting angle of the series. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// startAngle: 270, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final int startAngle; + + /// Ending angle of the series. + /// + /// Defaults to `360`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// endAngle: 270, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final int endAngle; + + /// Radius of the series. + /// + /// The value ranges from 0% to 100%. + /// + /// Defaults to `80%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// radius: '10%', + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String radius; + + /// Inner radius of the series. + /// + /// The value ranges from 0% to 100%. + /// + /// Defaults to `50%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// DoughnutSeries( + /// innerRadius: '20%', + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String innerRadius; + + /// Groups the data points of the series based on their index or values. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// groupTo: 4, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final double? groupTo; + + /// Slice can also be grouped based on the data points value or + /// based on index. + /// + /// Defaults to `CircularChartGroupMode.point`. + /// + /// Also refer [CircularChartGroupMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// groupMode: CircularChartGroupMode.value, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + 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 and + /// not applicable for radial bar series. + /// + /// Also refer [PointRenderMode]. + /// + /// Defaults to `pointRenderMode.segment`. + final PointRenderMode? pointRenderMode; + + /// Specifies the gap between the radial bars in percentage. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// gap: '10%', + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String gap; + + /// Specifies the radial bar’s corner type. + /// + /// _Note:_ This is applicable only for radial bar series type. + /// + /// Defaults to `CornerStyle.bothFlat`. + /// + /// Also refer [CornerStyle]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// cornerStyle: CornerStyle.bothCurve, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final CornerStyle cornerStyle; + + /// Border color of the series. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCircularChart( + /// series: >[ + /// PieSeries( + /// borderColor: Colors.red, + /// borderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final Color borderColor; + + /// Used to create the renderer for custom series. + /// + /// This is applicable only when the custom series is defined in the sample + /// and for built-in series types, it is not applicable. + /// + /// Renderer created in this will hold the series state and + /// this should be created for each series. [onCreateRenderer] callback + /// function should return the renderer class and should not return null. + /// + /// Series state will be created only once per series and will not be created + /// again when we update the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// onCreateRenderer: (ChartSeries series) { + /// return CustomPieSeriesRenderer( + /// series as PieSeries); + /// ), + /// ], + /// ) + /// ); + /// } + /// class CustomPieSeriesRenderer extends PieSeriesRenderer { + /// CustomPieSeriesRenderer(this.series); + /// final PieSeries series; + // custom implementation here... + /// } + /// ``` + final ChartSeriesRendererFactory? onCreateRenderer; + + /// Triggers when the series renderer is created. + /// + /// Using this callback, able to get the [ChartSeriesController] instance, + /// which is used to access the public methods in the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// CircularSeriesController _circularSeriesController; + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// onRendererCreated: (CircularSeriesController controller) { + /// _circularSeriesController = controller; + /// }, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final CircularSeriesRendererCreatedCallback? onRendererCreated; + + final CircularShaderCallback? onCreateShader; + + @override + Widget? childForSlot(SeriesSlot slot) { + switch (slot) { + case SeriesSlot.dataLabel: + return dataLabelSettings.isVisible + ? CircularDataLabelContainer( + series: this, + dataSource: dataSource!, + mapper: dataLabelMapper, + builder: dataLabelSettings.builder, + settings: dataLabelSettings, + ) + : null; + + case SeriesSlot.marker: + return null; + + case SeriesSlot.trendline: + return null; + } + } + + @override + CircularSeriesRenderer createRenderObject(BuildContext context) { + final CircularSeriesRenderer renderer = + super.createRenderObject(context) as CircularSeriesRenderer; + renderer + ..yValueMapper = yValueMapper + ..pointShaderMapper = pointShaderMapper + ..pointRadiusMapper = pointRadiusMapper + ..startAngle = startAngle + ..endAngle = endAngle + ..radius = radius + ..innerRadius = innerRadius + ..groupMode = groupMode + ..groupTo = groupTo + ..pointRenderMode = pointRenderMode + ..gap = gap + ..cornerStyle = cornerStyle + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..onCreateRenderer = onCreateRenderer + ..onCreateShader = onCreateShader + ..onRendererCreated = onRendererCreated + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + CircularSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..yValueMapper = yValueMapper + ..pointShaderMapper = pointShaderMapper + ..pointRadiusMapper = pointRadiusMapper + ..startAngle = startAngle + ..endAngle = endAngle + ..radius = radius + ..innerRadius = innerRadius + ..groupMode = groupMode + ..groupTo = groupTo + ..pointRenderMode = pointRenderMode + ..gap = gap + ..cornerStyle = cornerStyle + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..onCreateRenderer = onCreateRenderer + ..onCreateShader = onCreateShader + ..onRendererCreated = onRendererCreated + ..borderColor = borderColor; + } +} + +/// We can redraw the series by 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. +class CircularSeriesController { + /// Creating an argument constructor of CircularSeriesController class. + CircularSeriesController(this.seriesRenderer); + + /// Used to access the series properties. + /// + /// Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCircularChart( + /// series: PieSeries( + /// onRendererCreated: + /// (CircularSeriesController controller) { + /// _chartSeriesController = controller; + /// // prints series yAxisName + /// print(_chartSeriesController.seriesRenderer + /// .seriesRendererDetails.series.yAxisName); + /// }, + /// ), + /// ) + /// ); + ///} + ///``` + 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. + /// + /// To re-render the chart with modified data points, setState() will be + /// called. This will render the process and render the chart from scratch. + /// Thus, the app’s performance will be degraded on continuous update. + /// To overcome this problem, [updateDataSource] method can be called by + /// passing updated data points indexes. + /// Chart will process only that point and skip various steps like + /// bounds calculation, + /// old data points processing, etc. Thus, this will improve + /// the app’s performance. + /// + /// The following are the arguments of this method. + /// * addedDataIndexes – `List` type – Indexes of newly added data points + /// in the existing series. + /// * removedDataIndexes – `List` type – Indexes of removed data points + /// in the existing series. + /// * updatedDataIndexes – `List` type – Indexes of updated data points + /// in the existing series. + /// * addedDataIndex – `int` type – Index of newly added data point + /// in the existing series. + /// * removedDataIndex – `int` type – Index of removed data point + /// in the existing series. + /// * updatedDataIndex – `int` type – Index of updated data point + /// in the existing series. + /// + /// Returns `void`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// CircularSeriesController seriesController; + /// return Column( + /// children: [ + /// Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataSource: chartData, + /// onRendererCreated: + /// (CircularSeriesController controller) { + /// seriesController = controller; + /// }, + /// ), + /// ], + /// ) + /// ), + /// Container( + /// child: RaisedButton( + /// onPressed: () { + /// chartData.removeAt(0); + /// chartData.add(ChartData(3,23)); + /// seriesController.updateDataSource( + /// addedDataIndexes: [chartData.length -1], + /// removedDataIndexes: [0], + /// ); + /// }) + /// )] + /// ); + /// } + ///``` + void updateDataSource({ + List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int addedDataIndex = -1, + int removedDataIndex = -1, + int updatedDataIndex = -1, + }) { + final RealTimeUpdateMixin renderer = seriesRenderer; + + List? effectiveRemovedIndexes; + List? effectiveAddedIndexes; + List? effectiveReplacedIndexes; + + if (removedDataIndexes != null) { + effectiveRemovedIndexes = List.from(removedDataIndexes); + } + + if (addedDataIndexes != null) { + effectiveAddedIndexes = List.from(addedDataIndexes); + } + + if (updatedDataIndexes != null) { + effectiveReplacedIndexes = List.from(updatedDataIndexes); + } + + if (removedDataIndex != -1) { + effectiveRemovedIndexes ??= []; + effectiveRemovedIndexes.add(removedDataIndex); + } + + if (addedDataIndex != -1) { + effectiveAddedIndexes ??= []; + effectiveAddedIndexes.add(addedDataIndex); + } + + if (updatedDataIndex != -1) { + effectiveReplacedIndexes ??= []; + effectiveReplacedIndexes.add(updatedDataIndex); + } + + renderer.updateDataPoints( + effectiveRemovedIndexes, + effectiveAddedIndexes, + effectiveReplacedIndexes, + ); + } + + /// Converts chart data point value to logical pixel value. + /// + /// The [pointToPixel] method takes chart data point value as input and + /// returns logical pixel value. + /// + /// _Note_: It returns the data point's center location value. + /// + ///```dart + /// late CircularSeriesController seriesController; + /// SfCircularChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = + /// seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// ChartPoint chartPoint1 = seriesController.pixelToPoint(value); + /// }, + /// series: >[ + /// PieSeries( + /// onRendererCreated: (CircularSeriesController controller) { + /// seriesController = controller; + /// } + /// ) + /// ] + /// ) + /// ``` + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and + /// returns a chart data point. + /// + ///```dart + /// late CircularSeriesController seriesController; + /// SfCircularChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = + /// seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// }, + /// series: >[ + /// PieSeries( + /// onRendererCreated: (CircularSeriesController controller) { + /// seriesController = controller; + /// } + /// ) + /// ] + /// ) + /// ``` + + ChartPoint pixelToPoint(Offset position) { + int pointIndex = -1; + for (int i = 0; i < seriesRenderer.segments.length; i++) { + final ChartSegment segment = seriesRenderer.segments[i]; + if (segment.contains(position)) { + pointIndex = i; + } + } + final dynamic x = seriesRenderer.xValues[pointIndex]; + final num y = seriesRenderer.yValues[pointIndex]; + return ChartPoint(x: x, y: y); + } +} + +/// Creates a series renderer for circular series. +abstract class CircularSeriesRenderer extends ChartSeriesRenderer + with SegmentAnimationMixin, RealTimeUpdateMixin { + final List yValues = []; + final List _chaoticYValues = []; + final List pointRadii = []; + final List _chaoticPointRadii = []; + + final List dataLabelValues = []; + final List _chaoticDataLabelValues = []; + + List circularXValues = []; + List circularYValues = []; + List groupingDataLabelValues = []; + + ChartValueMapper? yValueMapper; + + ChartShaderMapper? pointShaderMapper; + + ChartValueMapper? pointRadiusMapper; + + CircularSeriesRendererCreatedCallback? onRendererCreated; + + CircularSeriesController get controller => _controller; + late final CircularSeriesController _controller = + CircularSeriesController(this); + + int get startAngle => _startAngle; + int _startAngle = 0; + set startAngle(int value) { + if (_startAngle != value) { + _startAngle = value; + markNeedsLayout(); + } + } + + int get endAngle => _endAngle; + int _endAngle = 360; + set endAngle(int value) { + if (_endAngle != value) { + _endAngle = value; + markNeedsLayout(); + } + } + + String get radius => _radius; + String _radius = '80%'; + set radius(String value) { + if (_radius != value) { + _radius = value; + markNeedsLayout(); + } + } + + String get innerRadius => _innerRadius; + String _innerRadius = '50%'; + set innerRadius(String value) { + if (_innerRadius != value) { + _innerRadius = value; + markNeedsLayout(); + } + } + + double? get groupTo => _groupTo; + double? _groupTo; + set groupTo(double? value) { + if (_groupTo != value) { + _groupTo = value; + markNeedsLayout(); + markNeedsLegendUpdate(); + } + } + + CircularChartGroupMode? get groupMode => _groupMode; + CircularChartGroupMode? _groupMode; + set groupMode(CircularChartGroupMode? value) { + if (_groupMode != value) { + _groupMode = value; + markNeedsLayout(); + markNeedsLegendUpdate(); + } + } + + PointRenderMode? get pointRenderMode => _pointRenderMode; + PointRenderMode? _pointRenderMode; + set pointRenderMode(PointRenderMode? value) { + if (_pointRenderMode != value) { + _pointRenderMode = value; + markNeedsSegmentsPaint(); + } + } + + String get gap => _gap; + String _gap = '1%'; + set gap(String value) { + if (_gap != value) { + _gap = value; + markNeedsLayout(); + } + } + + CornerStyle get cornerStyle => _cornerStyle; + CornerStyle _cornerStyle = CornerStyle.bothFlat; + set cornerStyle(CornerStyle value) { + if (_cornerStyle != value) { + _cornerStyle = value; + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + String get centerX => _centerX; + String _centerX = '50%'; + set centerX(String value) { + if (_centerX != value) { + _centerX = value; + markNeedsLayout(); + } + } + + String get centerY => _centerY; + String _centerY = '50%'; + set centerY(String value) { + if (_centerY != value) { + _centerY = value; + markNeedsLayout(); + } + } + + double sumOfY = 0.0; + int totalAngle = 0; + late double pointStartAngle; + late double currentRadius; + late double currentInnerRadius; + late Offset center; + late double ringSize; + double? segmentGap; + int firstVisibleIndex = 0; + + ChartSeriesRendererFactory? onCreateRenderer; + + CircularShaderCallback? get onCreateShader => _onCreateShader; + CircularShaderCallback? _onCreateShader; + set onCreateShader(CircularShaderCallback? value) { + if (_onCreateShader != value) { + _onCreateShader = value; + markNeedsLegendUpdate(); + markNeedsSegmentsPaint(); + dataLabelContainer?.refresh(); + } + } + + // TODO(Preethika): Marked as public to access from pie series. + ChartShaderDetails createShaderDetails() { + final Rect innerRect = Rect.fromCircle( + center: center, + radius: currentInnerRadius, + ); + final Rect outerRect = Rect.fromCircle( + center: center, + radius: currentRadius, + ); + return ChartShaderDetails(outerRect, innerRect, 'series'); + } + + @override + Iterable get children { + return [if (dataLabelContainer != null) dataLabelContainer!]; + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(controller); + super.attach(owner); + } + + void _resetYLists() { + yValues.clear(); + circularXValues.clear(); + circularYValues.clear(); + groupingDataLabelValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _chaoticYValues.clear(); + pointRadii.clear(); + _chaoticDataLabelValues.clear(); + dataLabelValues.clear(); + _resetYLists(); + super._resetDataSourceHolders(); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + + _addPointRadiusMapper(fPaths, chaoticFLists, fLists); + _addDataLabelMapper(fPaths, chaoticFLists, fLists); + + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + _calculateGroupingValues(); + markNeedsLegendUpdate(); + populateChartPoints(); + } + + void _addPointRadiusMapper( + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + if (fPaths != null && pointRadiusMapper != null) { + fPaths.add(pointRadiusMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(pointRadii); + } else { + pointRadii.clear(); + chaoticFLists?.add(_chaoticPointRadii); + fLists?.add(pointRadii); + } + } + } + + void _addDataLabelMapper( + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + if (fPaths != null && dataLabelMapper != null) { + fPaths.add(dataLabelMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(dataLabelValues); + } else { + dataLabelValues.clear(); + chaoticFLists?.add(_chaoticDataLabelValues); + fLists?.add(dataLabelValues); + } + } + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[yValues]; + positions = [ChartDataPointType.y]; + } else { + yLists.add(yValues); + positions!.add(ChartDataPointType.y); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + _resetYLists(); + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + + _addPointRadiusMapper(fPaths, chaoticFLists, fLists); + _addDataLabelMapper(fPaths, chaoticFLists, fLists); + + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + _calculateGroupingValues(); + } + + @override + void performLayout() { + // Need to recalculate the radius values based on plot area size, + // and its required to update the segment values in layout. + // So assigned true here to update the segment values. + canUpdateOrCreateSegments = true; + _calculateCircularValues(); + super.performLayout(); + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = circularXValues + ..xValues = xValues + ..yLists = >[circularYValues] + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + } + } + + @override + void onLoadingAnimationUpdate() { + super.onLoadingAnimationUpdate(); + transformValues(); + } + + @override + void onRealTimeAnimationUpdate() { + super.onRealTimeAnimationUpdate(); + transformValues(); + } + + void _calculateGroupingValues() { + if (groupTo == null || groupMode == null) { + circularXValues = xRawValues; + circularYValues = yValues; + groupingDataLabelValues = dataLabelValues; + return; + } + + final List groupingXValues = []; + final List groupingYValues = []; + final List groupingLabelValues = []; + double sumOfGroup = 0; + + for (int i = 0; i < dataCount; i++) { + final num yValue = yValues[i]; + if (groupTo != null && + ((groupMode == CircularChartGroupMode.point && i >= groupTo!) || + (groupMode == CircularChartGroupMode.value && + yValue <= groupTo!))) { + sumOfGroup += yValue.abs(); + } else { + groupingXValues.add(xRawValues[i]); + groupingYValues.add(yValue); + if (dataLabelValues.isNotEmpty) { + groupingLabelValues.add(dataLabelValues[i]); + } + } + } + + if (sumOfGroup > 0) { + groupingXValues.add('Others' as D?); + groupingYValues.add(sumOfGroup); + if (dataLabelValues.isNotEmpty) { + groupingLabelValues.add('Others' as D?); + } + + _dataCount = groupingXValues.length; + } + circularXValues = groupingXValues; + circularYValues = groupingYValues; + groupingDataLabelValues = groupingLabelValues; + } + + void _calculateCircularValues() { + sumOfY = 0; + int length = 0; + firstVisibleIndex = -1; + + for (int i = 0; i < dataCount; i++) { + bool isVisible = true; + if (_segments.isNotEmpty && i < _segments.length) { + isVisible = _segments[i].isVisible; + } + firstVisibleIndex = + firstVisibleIndex == -1 && isVisible ? i : firstVisibleIndex; + final num yValue = isVisible ? circularYValues[i] : 0; + length += isVisible ? 1 : 0; + if (!yValue.isNaN) { + sumOfY += yValue.abs(); + } + } + + final int startDegree = _calculateAngle(startAngle); + int endDegree = _calculateAngle(endAngle); + endDegree = startDegree == endDegree ? startDegree + 360 : endDegree; + totalAngle = + startDegree > endDegree + ? (startDegree - 360).abs() + endDegree + : (startDegree - endDegree).abs(); + + pointStartAngle = startDegree.toDouble(); + currentRadius = percentToValue(radius, (min(size.width, size.height)) / 2)!; + currentInnerRadius = percentToValue(innerRadius, currentRadius)!; + center = Offset( + percentToValue(centerX, size.width)!, + percentToValue(centerY, size.height)!, + ); + + ringSize = (currentRadius - currentInnerRadius) / length; + segmentGap = percentToValue(gap, currentRadius - currentInnerRadius); + firstVisibleIndex = firstVisibleIndex == -1 ? 0 : firstVisibleIndex; + } + + @override + @nonVirtual + Color effectiveColor(int segmentIndex) { + Color? pointColor; + if (pointColorMapper != null) { + pointColor = pointColors[segmentIndex]; + } + return pointColor ?? palette[segmentIndex % palette.length]; + } + + @override + List? buildLegendItems(int index) { + final List legendItems = []; + final int segmentsCount = segments.length; + // TODO(Lavanya): Ignore legend item for gap and drop mode. + for (int i = 0; i < dataCount; i++) { + final CircularLegendItem legendItem = CircularLegendItem( + text: circularXValues[i].toString(), + iconType: toLegendShapeMarkerType(legendIconType, this), + iconColor: effectiveColor(i), + shader: _legendIconShaders(i), + iconBorderWidth: legendIconBorderWidth(), + series: this, + seriesIndex: index, + pointIndex: i, + imageProvider: + legendIconType == LegendIconType.image + ? parent?.legend?.image + : null, + isToggled: i < segmentsCount && !segmentAt(i).isVisible, + onTap: handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ); + legendItems.add(legendItem); + } + return legendItems; + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + super.handleLegendItemTapped(item, isToggled); + + final CircularLegendItem legendItem = item as CircularLegendItem; + if (legendItem.pointIndex < segments.length) { + segmentAt(legendItem.pointIndex).isVisible = !isToggled; + } + + legendItem.onToggled?.call(); + animationType = AnimationType.realtime; + canUpdateOrCreateSegments = true; + markNeedsLayout(); + } + + Shader? _legendIconShaders(int pointIndex) { + if (parent != null && parent!.legend != null) { + final Rect legendIconBounds = Rect.fromLTWH( + 0.0, + 0.0, + parent!.legend!.iconWidth, + parent!.legend!.iconHeight, + ); + if (pointShaderMapper != null) { + return pointShaderMapper!( + dataSource![pointIndex], + pointIndex, + palette[pointIndex % palette.length], + legendIconBounds, + ); + } else if (onCreateShader != null) { + final ChartShaderDetails details = ChartShaderDetails( + legendIconBounds, + legendIconBounds, + 'legend', + ); + return onCreateShader?.call(details); + } + } + return null; + } + + @nonVirtual + int _calculateAngle(int angle) { + return (angle.abs() > 360 ? angle % 360 : angle) - 90; + } + + @nonVirtual + void updateSegmentGradient(ChartSegment segment) { + final int segmentIndex = segment.currentSegmentIndex; + + // This method is called by the data source setter before updating the + // segments, when the data source is updated dynamically. As a result, + // the length of the data source is updated while the length of the + // segments is not updated, which can lead to a range error exception. + // To prevent this, a condition has been added to ensure the segmentIndex + // is less than the data source length. + if (!segment.isEmpty && + dataSource != null && + segmentIndex < dataSource!.length) { + if (pointShaderMapper != null) { + final Shader shader = pointShaderMapper!( + dataSource![segmentIndex], + segmentIndex, + palette[segmentIndex % palette.length], + Rect.fromCircle(center: center, radius: currentRadius), + ); + segment.fillPaint.shader = shader; + } else if (onCreateShader != null) { + final ChartShaderDetails details = createShaderDetails(); + segment.fillPaint.shader = onCreateShader!(details); + } else if (pointRenderMode == PointRenderMode.gradient) { + final List colors = List.generate( + segments.length, + (int i) => palette[i % palette.length], + ); + final List stops = []; + num initialStops = 0; + for (int i = 0; i < segments.length; i++) { + final double segmentRatio = circularYValues[i] / sumOfY; + if (stops.isEmpty) { + initialStops = segmentRatio / 4; + stops.add(segmentRatio - initialStops); + } else { + if (stops.length == 1) { + stops.add((segmentRatio + stops.last) + initialStops / 1.5); + } else { + stops.add(segmentRatio + stops.last); + } + } + } + final SweepGradient sweep = SweepGradient( + startAngle: degreeToRadian(startAngle), + endAngle: degreeToRadian(endAngle), + colors: colors, + stops: stops, + transform: GradientRotation(degreeToRadian(-90)), + ); + segment.fillPaint.shader = sweep.createShader( + createShaderDetails().outerRect, + textDirection: TextDirection.ltr, + ); + } + } + } + + Offset dataLabelPosition(CircularDataLabelBoxParentData current, Size size) { + final num angle = dataLabelSettings.angle; + final int pointIndex = current.dataPointIndex; + Offset labelLocation; + const int labelPadding = 2; + TextStyle dataLabelStyle = + parent!.themeData!.textTheme.bodySmall! + ..merge(chartThemeData!.dataLabelTextStyle) + ..merge(dataLabelSettings.textStyle); + final CircularChartPoint point = current.point!; + if (point.isExplode) { + point.center = calculateExplodingCenter( + point.midAngle!, + point.outerRadius!.toDouble(), + point.center!, + point.explodeOffset, + ); + } + if (point.isVisible && (point.y != 0 || dataLabelSettings.showZeroValue)) { + dataLabelStyle = dataLabelStyle.copyWith( + color: + dataLabelStyle.color ?? + saturatedTextColor(findThemeColor(this, point, dataLabelSettings)), + ); + + if (dataLabelSettings.labelPosition == ChartDataLabelPosition.inside) { + labelLocation = calculateOffset( + point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, + point.center!, + ); + labelLocation = Offset( + labelLocation.dx - + (size.width / 2) + + (angle == 0 ? 0 : size.width / 2), + labelLocation.dy - + (size.height / 2) + + (angle == 0 ? 0 : size.height / 2), + ); + point.labelRect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + size.width + (2 * labelPadding), + size.height + (2 * labelPadding), + ); + bool isDataLabelCollide = findingCollision( + point.labelRect, + renderDataLabelRegions, + ); + if (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide || + dataLabelSettings.overflowMode == OverflowMode.hide) { + point.isVisible = !isDataLabelCollide; + } + + if (dataLabelSettings.builder == null) { + // TODO(Lavanya): Get text from DataLabelBoxParentData; + String label = point.text!; + point.overflowTrimmedText = point.overflowTrimmedText ?? label; + + if (dataLabelSettings.overflowMode == OverflowMode.shift) { + final String labelText = segmentOverflowTrimmedText( + this, + point.text!, + size, + point, + point.labelRect, + center, + labelLocation, + dataLabelSettings.overflowMode, + dataLabelStyle, + ); + if (labelText.contains('...') || labelText.isEmpty) { + isDataLabelCollide = true; + point.renderPosition = ChartDataLabelPosition.outside; + } + point.text = isDataLabelCollide ? point.text : labelText; + } else if (dataLabelSettings.overflowMode == OverflowMode.trim && + !point.text!.contains('...')) { + if (!isDataLabelCollide) { + point.text = segmentOverflowTrimmedText( + this, + point.text!, + size, + point, + point.labelRect, + center, + labelLocation, + dataLabelSettings.overflowMode, + dataLabelStyle, + ); + label = point.text!; + final Size trimmedTextSize = measureText(label, dataLabelStyle); + labelLocation = calculateOffset( + point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, + point.center!, + ); + labelLocation = Offset( + labelLocation.dx - + (trimmedTextSize.width / 2) + + (angle == 0 ? 0 : trimmedTextSize.width / 2), + labelLocation.dy - + (trimmedTextSize.height / 2) + + (angle == 0 ? 0 : trimmedTextSize.height / 2), + ); + point.labelLocation = labelLocation; + point.labelRect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + trimmedTextSize.width + (2 * labelPadding), + trimmedTextSize.height + (2 * labelPadding), + ); + } else { + point.isVisible = false; + } + } + } + + if (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift && + isDataLabelCollide && + dataLabelSettings.overflowMode != OverflowMode.trim) { + point.saturationRegionOutside = true; + point.renderPosition = ChartDataLabelPosition.outside; + renderOutsideDataLabel( + point, + size, + pointIndex, + this, + index, + dataLabelStyle, + renderDataLabelRegions, + ); + } else if (((dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift && + dataLabelSettings.overflowMode == OverflowMode.none) && + isDataLabelCollide && + dataLabelSettings.overflowMode != OverflowMode.trim) || + (isDataLabelCollide && + dataLabelSettings.overflowMode == OverflowMode.shift)) { + point.saturationRegionOutside = true; + point.renderPosition = ChartDataLabelPosition.outside; + renderOutsideDataLabel( + point, + size, + pointIndex, + this, + index, + dataLabelStyle, + renderDataLabelRegions, + ); + } else if (!isDataLabelCollide || + (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.none && + dataLabelSettings.overflowMode == OverflowMode.none)) { + point.renderPosition = ChartDataLabelPosition.inside; + // TODO(Lavanya): Apply saturation color with + //DataLabelRender callback. + + if (!isDataLabelCollide && + (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift && + dataLabelSettings.overflowMode != OverflowMode.hide)) { + renderDataLabelRegions.add(point.labelRect); + point.labelLocation = labelLocation; + } else if (!isDataLabelCollide && + (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide || + dataLabelSettings.overflowMode == OverflowMode.hide)) { + if (point.renderPosition == ChartDataLabelPosition.inside && + (dataLabelSettings.overflowMode == OverflowMode.hide)) { + point.text = segmentOverflowTrimmedText( + this, + point.text!, + size, + point, + point.labelRect, + center, + labelLocation, + dataLabelSettings.overflowMode, + dataLabelStyle, + ); + // label = point.text!; + } + + point.labelLocation = labelLocation; + // TODO(Lavanya): drawLabel method add renderDataLabelRegions. + if (dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) { + renderDataLabelRegions.add(point.labelRect); + } + } else { + point.labelLocation = labelLocation; + // TODO(Lavanya): drawLabel method add renderDataLabelRegions. + if (dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) { + renderDataLabelRegions.add(point.labelRect); + } + } + } + } else { + point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = dataLabelStyle.copyWith( + color: + dataLabelStyle.color ?? + saturatedTextColor( + findThemeColor(this, point, dataLabelSettings), + ), + ); + renderOutsideDataLabel( + point, + size, + pointIndex, + this, + index, + dataLabelStyle, + renderDataLabelRegions, + ); + } + } else { + point.labelRect = Rect.zero; + point.isVisible = false; + } + + return point.labelLocation; + } + + @override + void onPaint(PaintingContext context, Offset offset) { + super.onPaint(context, offset); + + paintDataLabels(context, offset); + } + + @protected + void paintDataLabels(PaintingContext context, Offset offset) { + if (dataLabelContainer != null) { + context.paintChild(dataLabelContainer!, offset); + } + } + + void drawDataLabelWithBackground( + CircularChartDataLabelPositioned dataLabelPositioned, + int index, + Canvas canvas, + String dataLabel, + Offset offset, + int angle, + TextStyle style, + Paint fillPaint, + Paint strokePaint, + ) { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + final ChartSegment segment = segments[index]; + Color surfaceColor = dataLabelSurfaceColor( + fillPaint.color, + index, + dataLabelSettings.labelPosition, + chartThemeData, + themeData, + segment, + ); + TextStyle effectiveTextStyle = saturatedTextStyle(surfaceColor, style); + final CircularChartPoint point = dataLabelPositioned.point!; + if (!point.isVisible || !segments[index].isVisible || point.text == '') { + return; + } + + if (point.connectorPath != null) { + // TODO(Lavanya): Recheck here. + drawConnectorLine(point.connectorPath!, canvas, index); + } + + if (point.saturationRegionOutside && + dataLabelSettings.labelPosition == ChartDataLabelPosition.inside && + dataLabelSettings.color == null && + !dataLabelSettings.useSeriesColor) { + if (style.color == Colors.transparent) { + surfaceColor = dataLabelSurfaceColor( + fillPaint.color, + index, + ChartDataLabelPosition.outside, + chartThemeData, + themeData, + segment, + ); + effectiveTextStyle = saturatedTextStyle(surfaceColor, style); + } + } + + // TODO(Lavanya): Handle data label animation. + final Rect labelRect = point.labelRect; + canvas.save(); + canvas.translate(labelRect.center.dx, labelRect.center.dy); + canvas.rotate((angle * pi) / 180); + canvas.translate(-labelRect.center.dx, -labelRect.center.dy); + if (dataLabelSettings.borderWidth > 0 && + strokePaint.color != Colors.transparent) { + _drawLabelRect( + strokePaint, + Rect.fromLTRB( + labelRect.left, + labelRect.top, + labelRect.right, + labelRect.bottom, + ), + dataLabelSettings.borderRadius, + canvas, + ); + } + + if (fillPaint.color != Colors.transparent) { + _drawLabelRect( + fillPaint, + Rect.fromLTRB( + labelRect.left, + labelRect.top, + labelRect.right, + labelRect.bottom, + ), + dataLabelSettings.borderRadius, + canvas, + ); + } + canvas.restore(); + + drawDataLabel( + canvas, + dataLabel, + offset, + effectiveTextStyle, + dataLabelSettings.angle, + ); + } + + void drawConnectorLine(Path connectorPath, Canvas canvas, int index) { + final ConnectorLineSettings line = dataLabelSettings.connectorLineSettings; + canvas.drawPath( + connectorPath, + Paint() + ..color = + line.width <= 0 + ? Colors.transparent + : line.color ?? segments[index].fillPaint.color + ..strokeWidth = line.width + ..style = PaintingStyle.stroke, + ); + } + + void _drawLabelRect( + Paint paint, + Rect labelRect, + double borderRadius, + Canvas canvas, + ) => canvas.drawRRect( + RRect.fromRectAndRadius(labelRect, Radius.circular(borderRadius)), + paint, + ); + + void drawDataLabel( + Canvas canvas, + String text, + Offset point, + TextStyle style, + int angle, [ + bool? isRtl, + ]) { + final int maxLines = getMaxLinesContent(text); + final TextSpan span = TextSpan(text: text, style: style); + final TextPainter tp = TextPainter( + text: span, + textDirection: (isRtl ?? false) ? TextDirection.rtl : TextDirection.ltr, + textAlign: TextAlign.center, + maxLines: maxLines, + ); + tp.layout(); + canvas.save(); + canvas.translate(point.dx + tp.width / 2, point.dy + tp.height / 2); + Offset labelOffset = Offset.zero; + canvas.rotate(degreeToRadian(angle)); + labelOffset = Offset(-tp.width / 2, -tp.height / 2); + tp.paint(canvas, labelOffset); + canvas.restore(); + } + + @override + int viewportIndex(int index, [List? visibleIndexes]) { + return index; + } + + @override + void dispose() { + renderDataLabelRegions.clear(); + _resetDataSourceHolders(); + super.dispose(); + } +} + +abstract class BoxAndWhiskerSeriesRendererBase + extends CartesianSeriesRenderer + with + SbsSeriesMixin, + ClusterSeriesMixin, + CartesianRealTimeUpdateMixin { + final List> _chaoticYValues = >[]; + List> yValues = >[]; + + ChartValueMapper>? yValueMapper; + + @override + void _resetDataSourceHolders() { + _chaoticYValues.clear(); + yValues.clear(); + super._resetDataSourceHolders(); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _resetDataSourceHolders(); + if (dataSource == null || + dataSource!.isEmpty || + xValueMapper == null || + yValueMapper == null) { + _dataCount = _chaoticXValues.length; + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + final int length = dataSource!.length; + final int fPathLength = fPaths.length; + + num previousX = double.negativeInfinity; + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(D?, num) addXValue = _addXValueIntoRawAndChaoticXLists; + + for (int i = 0; i < length; i++) { + final T current = dataSource![i]; + final D? rawX = xValueMapper!(current, i); + if (rawX == null) { + _xNullPointIndexes.add(i); + continue; + } + + final num currentX = preferredXValue(i, rawX); + addXValue(rawX, currentX); + xMinimum = min(xMinimum, currentX); + xMaximum = max(xMaximum, currentX); + if (_hasLinearDataSource) { + _hasLinearDataSource = currentX >= previousX; + } + + final List? yData = yValueMapper!(current, i); + if (yData == null) { + _chaoticYValues.add([]); + } else { + num minY = double.infinity; + num maxY = double.negativeInfinity; + final List nonNullYValues = []; + final int yLength = yData.length; + for (int j = 0; j < yLength; j++) { + final num? yVal = yData[j]; + if (yVal != null && !yVal.isNaN) { + nonNullYValues.add(yVal); + minY = min(minY, yVal); + maxY = max(maxY, yVal); + } + } + nonNullYValues.sort(); + _chaoticYValues.add(nonNullYValues); + yMinimum = min(yMinimum, minY); + yMaximum = max(yMaximum, maxY); + } + + for (int j = 0; j < fPathLength; j++) { + final ChartValueMapper fPath = fPaths[j]; + final Object? fValue = fPath(current, i); + chaoticFLists![j].add(fValue); + } + + previousX = currentX; + } + + xMin = xMinimum; + xMax = xMaximum; + yMin = yMinimum; + yMax = yMaximum; + _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); + super._calculateSbsInfo(); + populateChartPoints(); + _updateXValuesForCategoryTypeAxes(); + } + + @override + void _doSortingIfNeeded( + List>? chaoticYLists, + List>? yLists, + List>? chaoticFLists, + List>? fLists, + ) { + if (sortingOrder != SortingOrder.none && _chaoticYValues.isNotEmpty) { + if (_chaoticRawSortValues.isEmpty) { + if (_chaoticRawXValues.isNotEmpty) { + _chaoticRawSortValues.addAll(_chaoticRawXValues); + } else { + _chaoticRawSortValues.addAll(_chaoticXValues); + } + } + + switch (sortingOrder) { + case SortingOrder.ascending: + _sortBoxValues(chaoticFLists, fLists); + break; + + case SortingOrder.descending: + _sortBoxValues(chaoticFLists, fLists, ascending: false); + break; + + case SortingOrder.none: + break; + } + } else { + xValues.clear(); + xValues.addAll(_chaoticXValues); + xRawValues.clear(); + xRawValues.addAll(_chaoticRawXValues); + yValues.clear(); + yValues.addAll(_chaoticYValues); + } + } + + void _sortBoxValues( + List>? chaoticFLists, + List>? fLists, { + bool ascending = true, + }) { + _computeSortedIndexes(ascending); + if (sortedIndexes.isNotEmpty) { + final void Function(int index, num xValue) copyX = + _chaoticRawXValues.isNotEmpty ? _copyXAndRawXValue : _copyXValue; + final int fLength = fLists!.length; + final int length = sortedIndexes.length; + + for (int i = 0; i < length; i++) { + final int sortedIndex = sortedIndexes[i]; + final num xValue = _chaoticXValues[sortedIndex]; + copyX(sortedIndex, xValue); + yValues.add(_chaoticYValues[sortedIndex]); + + for (int k = 0; k < fLength; k++) { + final List fValues = fLists[k]; + final List chaoticFValues = chaoticFLists![k]; + fValues.add(chaoticFValues[sortedIndex]); + } + + // During sorting, determine data is linear or non-linear for + // calculating visibleIndexes for proper axis range & segment rendering. + if (_canFindLinearVisibleIndexes) { + _canFindLinearVisibleIndexes = isValueLinear(i, xValue, xValues); + } + } + } + } + + @override + DoubleRange _calculateYRange({List>? yLists}) { + num minimum = double.infinity; + num maximum = double.negativeInfinity; + for (final List yList in _chaoticYValues) { + for (final num yValue in yList) { + minimum = min(yMin, yValue); + maximum = max(yMax, yValue); + } + } + return DoubleRange(minimum, maximum); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (xValueMapper == null || yValueMapper == null) { + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + if (removedIndexes != null) { + _removeDataPoints( + removedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + if (addedIndexes != null) { + _addDataPoints( + addedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + if (replacedIndexes != null) { + _replaceDataPoints( + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + // During sorting, the x, y, and feature path values are recalculated. + // Therefore, it is necessary to clear the old values and update these lists + // with the newly recalculated values. + if (sortingOrder != SortingOrder.none) { + xValues.clear(); + xRawValues.clear(); + yValues.clear(); + } + + _applyEmptyPointModeIfNeeded(_chaoticYValues); + _doSortingIfNeeded(_chaoticYValues, yLists, chaoticFLists, fLists); + final DoubleRange xRange = _findMinMaxXRange(xValues); + final DoubleRange yRange = _findMinMaxYRange(_chaoticYValues); + _updateAxisRange( + xRange.minimum, + xRange.maximum, + yRange.minimum, + yRange.maximum, + ); + computeNonEmptyYValues(); + _populateTrendlineDataSource(); + _updateXValuesForCategoryTypeAxes(); + + canUpdateOrCreateSegments = true; + markNeedsLayout(); + } + + @override + void _removeDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + final int fPathLength = fPaths?.length ?? 0; + // Sort 'indexes' in descending order to remove higher indexes first, + // preventing shifting issues when removing lower ones. + final List sortedIndexes = List.from(indexes) + ..sort((a, b) => b.compareTo(a)); + for (final int index in sortedIndexes) { + _removeXValueAt(index); + _removeRawSortValueAt(index); + _chaoticYValues.removeAt(index); + + for (int k = 0; k < fPathLength; k++) { + chaoticFLists![k].removeAt(index); + } + + if (_callbacksEnabled && chartPoints.length > index) { + chartPoints.removeAt(index); + } + } + + _dataCount = _chaoticXValues.length; + // Collecting previous and next index to update them. + final List mutableIndexes = _findMutableIndexes(indexes); + _replaceDataPoints( + mutableIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + @override + void _addDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + final int fPathLength = fPaths?.length ?? 0; + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(int, D?, num) insertXValue = + _insertXValueIntoRawAndChaoticXLists; + final Function(int, D?) insertRawSortValue = + _insertRawXValueIntoChaoticRawSortValue; + + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + + for (final int index in indexes) { + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + final num currentX = preferredXValue(index, rawX); + insertXValue(index, rawX, currentX); + insertRawSortValue(index, rawX); + xMinimum = min(xMinimum, currentX); + xMaximum = max(xMaximum, currentX); + if (_hasLinearDataSource) { + _hasLinearDataSource = isValueLinear(index, currentX, _chaoticXValues); + } + + final List? yData = yValueMapper!(current, index); + if (yData == null) { + _chaoticYValues.add([]); + } else { + num minY = double.infinity; + num maxY = double.negativeInfinity; + final List nonNullYValues = []; + final int yLength = yData.length; + for (int j = 0; j < yLength; j++) { + final num? yVal = yData[j]; + if (yVal != null && !yVal.isNaN) { + nonNullYValues.add(yVal); + minY = min(minY, yVal); + maxY = max(maxY, yVal); + } + } + nonNullYValues.sort(); + _chaoticYValues.insert(index, nonNullYValues); + yMinimum = min(yMinimum, minY); + yMaximum = max(yMaximum, maxY); + } + + if (_callbacksEnabled) { + final num xValue = _chaoticXValues[index]; + final CartesianChartPoint point = CartesianChartPoint( + x: _chaoticRawXValues[index], + xValue: xValue, + ); + chartPoints.insert(index, point); + } + + for (int j = 0; j < fPathLength; j++) { + final Object? fValue = fPaths![j](current, j); + chaoticFLists![j].insert(index, fValue); + } + } + + _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; + } + + @override + void _replaceDataPoints( + List indexes, + List?>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ) { + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(int, D?, num) replaceXValue = + _updateXValueIntoRawAndChaoticXLists; + + final int fPathLength = fPaths?.length ?? 0; + + for (final int index in indexes) { + if (index < 0 || index >= dataSource!.length) { + continue; + } + + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (_xNullPointIndexes.contains(index)) { + _xNullPointIndexes.remove(index); + } + + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } + + final num currentX = preferredXValue(index, rawX); + replaceXValue(index, rawX, currentX); + + final List? yData = yValueMapper!(current, index); + if (yData == null) { + _chaoticYValues.add([]); + } else { + num minY = double.infinity; + num maxY = double.negativeInfinity; + final List nonNullYValues = []; + final int yLength = yData.length; + for (int j = 0; j < yLength; j++) { + final num? yVal = yData[j]; + if (yVal != null && !yVal.isNaN) { + nonNullYValues.add(yVal); + minY = min(minY, yVal); + maxY = max(maxY, yVal); + } + } + nonNullYValues.sort(); + _chaoticYValues[index] = nonNullYValues; + } + + if (_callbacksEnabled) { + final num xValue = _chaoticXValues[index]; + final CartesianChartPoint point = CartesianChartPoint( + x: _chaoticRawXValues[index], + xValue: xValue, + ); + chartPoints[index] = point; + } + + for (int j = 0; j < fPathLength; j++) { + chaoticFLists![j][index] = fPaths![j](current, j); + } + } + } + + @override + Offset _calculateMedianPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + return _calculateYPosition(x, y, ChartDataLabelAlignment.top, size); + + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.middle, size); + + case ChartDataLabelAlignment.bottom: + return _calculateYPosition(x, y, ChartDataLabelAlignment.bottom, size); + } + } + + @override + Offset _calculateDataLabelOpenPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateYPosition(x, y, ChartDataLabelAlignment.outer, size); + + case ChartDataLabelAlignment.bottom: + return _calculateYPosition(x, y, ChartDataLabelAlignment.bottom, size); + } + } + + @override + void dispose() { + _chaoticYValues.clear(); + yValues.clear(); + super.dispose(); + } +} + +abstract class HistogramSeriesRendererBase + extends XyDataSeriesRenderer + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + double? get binInterval => _binInterval; + double? _binInterval; + set binInterval(double? value) { + if (_binInterval != value) { + _binInterval = value; + markNeedsLayout(); + } + } + + bool get showNormalDistributionCurve => _showNormalDistributionCurve; + bool _showNormalDistributionCurve = false; + set showNormalDistributionCurve(bool value) { + if (_showNormalDistributionCurve != value) { + _showNormalDistributionCurve = value; + markNeedsLayout(); + } + } + + Color get curveColor => _curveColor; + Color _curveColor = Colors.blue; + set curveColor(Color value) { + if (_curveColor != value) { + _curveColor = value; + markNeedsSegmentsPaint(); + } + } + + double get curveWidth => _curveWidth; + double _curveWidth = 2.0; + set curveWidth(double value) { + if (_curveWidth != value) { + _curveWidth = value; + markNeedsSegmentsPaint(); + } + } + + List? get curveDashArray => _curveDashArray; + List? _curveDashArray; + set curveDashArray(List? value) { + if (_curveDashArray != value) { + _curveDashArray = value; + markNeedsLayout(); + } + } + + Color get trackBorderColor => _trackBorderColor; + Color _trackBorderColor = Colors.transparent; + set trackBorderColor(Color value) { + if (_trackBorderColor != value) { + _trackBorderColor = value; + markNeedsSegmentsPaint(); + } + } + + Color get trackColor => _trackColor; + Color _trackColor = Colors.grey; + set trackColor(Color value) { + if (_trackColor != value) { + _trackColor = value; + markNeedsSegmentsPaint(); + } + } + + double get trackBorderWidth => _trackBorderWidth; + double _trackBorderWidth = 1.0; + set trackBorderWidth(double value) { + if (_trackBorderWidth != value) { + _trackBorderWidth = value; + markNeedsSegmentsPaint(); + } + } + + double get trackPadding => _trackPadding; + double _trackPadding = 0.0; + set trackPadding(double value) { + if (_trackPadding != value) { + _trackPadding = value; + markNeedsLayout(); + } + } + + bool get isTrackVisible => _isTrackVisible; + bool _isTrackVisible = false; + set isTrackVisible(bool value) { + if (_isTrackVisible != value) { + _isTrackVisible = value; + markNeedsLayout(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + /// It holds the actual Histogram [X] and [Y] values. + final List _histogramXValues = []; + final List _histogramYValues = []; + + /// It holds the raw Y value from the [super.yValueMapper]. + final List _yRawValues = []; + + /// It holds actual [_histogramXValues] count. + int _histoXLength = 0; + num _deviation = 0.0; + num _mean = 0.0; + num binWidth = 0.0; + + final Path _distributionPath = Path(); + + void _resetDataHolders() { + _histoXLength = 0; + _yRawValues.clear(); + _histogramXValues.clear(); + _histogramYValues.clear(); + _distributionPath.reset(); + } + + @override + bool _canPopulateDataPoints( + List>? yPaths, + List>? yLists, + ) { + return dataSource != null && + dataSource!.isNotEmpty && + yPaths != null && + yPaths.isNotEmpty && + yLists != null && + yLists.isNotEmpty; + } + + void _populateDataSource() { + _resetDataHolders(); + if (dataSource == null || + dataSource!.isEmpty || + super.yValueMapper == null) { + return; + } + + final int length = dataSource!.length; + for (int i = 0; i < length; i++) { + final T current = dataSource![i]; + final num? yValue = super.yValueMapper!(current, i); + if (yValue == null || yValue.isNaN) { + _yRawValues.add(0); + } else { + _yRawValues.add(yValue); + } + } + + // Calculate the actual Histogram [X] and [Y] values. + _calculateStandardDeviation(); + _histoXLength = _histogramXValues.length; + } + + // Overrided the populateDataSource and used the _histogramXValues length only. + // Bacause the dataSource count and _histogramXValues count different. + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + _populateDataSource(); + + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + + _resetDataSourceHolders(); + if (!_canPopulateDataPoints(yPaths, chaoticYLists)) { + _dataCount = _chaoticXValues.length; + return; + } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); + + final int length = dataSource!.length; + final int yPathLength = yPaths.length; + final int fPathLength = fPaths.length; + + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; + num yMinimum = double.infinity; + num yMaximum = double.negativeInfinity; + final Function(int, D) preferredXValue = _preferredXValue(); + final Function(D?, num) addXValue = _addXValueIntoRawAndChaoticXLists; + + for (int i = 0; i < _histoXLength; i++) { + final D rawX = _histogramXValues[i] as D; + final num currentX = preferredXValue(i, rawX); + addXValue(rawX, currentX); + xMinimum = min(xMinimum, currentX); + xMaximum = max(xMaximum, currentX); + + for (int j = 0; j < yPathLength; j++) { + final num yValue = _histogramYValues[i]; + chaoticYLists![j].add(yValue); + yMinimum = min(yMinimum, yValue); + yMaximum = max(yMaximum, yValue); + } + + final int curIndex = i >= length ? length - 1 : i; + final T current = dataSource![curIndex]; + for (int j = 0; j < fPathLength; j++) { + final ChartValueMapper fPath = fPaths[j]; + final Object? fValue = fPath(current, i); + chaoticFLists![j].add(fValue); + } + } + + xMin = xMinimum; + xMax = xMaximum; + yMin = yMinimum; + yMax = yMaximum; + _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = true; + + _copyXAndRawXValues(); + computeNonEmptyYValues(); + _populateTrendlineDataSource(); + if (dataCount < 1) { + return; + } + + super._calculateSbsInfo(); + } + + void _copyXAndRawXValues() { + xValues.clear(); + xValues.addAll(_chaoticXValues); + xRawValues.clear(); + xRawValues.addAll(_chaoticRawXValues); + } + + @override + void computeNonEmptyYValues() { + nonEmptyYValues.clear(); + nonEmptyYValues.addAll(yValues); + } + + void _calculateStandardDeviation() { + if (_yRawValues.isEmpty) { + return; + } + + num sumOfY = 0; + num sumValue = 0; + _mean = 0; + final num yLength = _yRawValues.length; + num minValue = _yRawValues.reduce(min); + if (yLength == 1 && binInterval == null) { + _histogramYValues.add(1); + _histogramXValues.add(minValue); + return; + } + + for (int i = 0; i < yLength; i++) { + final num yValue = _yRawValues[i]; + sumOfY += yValue; + _mean = sumOfY / yLength; + } + + for (int i = 0; i < yLength; i++) { + final num yValue = _yRawValues[i]; + sumValue += (yValue - _mean) * (yValue - _mean); + } + + _deviation = sqrt(sumValue / (yLength - 1)); + binWidth = binInterval ?? (3.5 * _deviation) / pow(yLength, 1 / 3); + if (binWidth <= 0 || binWidth.isNaN) { + _histogramYValues.add(0); + _histogramXValues.add(minValue); + return; + } + + for (int i = 0; i < yLength;) { + final num count = + _yRawValues + .where((num y) => y >= minValue && y < (minValue + binWidth)) + .length; + if (count >= 0) { + _histogramYValues.add(count); + final num x = minValue + binWidth / 2; + _histogramXValues.add(x); + minValue += binWidth; + i += count as int; + } + } + } + + @override + num trackballYValue(int index) => _histogramYValues[index]; + + @override + void onLoadingAnimationUpdate() { + super.onLoadingAnimationUpdate(); + transformValues(); + } + + @override + void transformValues() { + super.transformValues(); + _createNormalDistributionPath(); + } + + void _createNormalDistributionPath() { + if (!showNormalDistributionCurve || + xAxis == null || + yAxis == null || + segments.isEmpty || + xAxis!.visibleRange == null || + yAxis!.visibleRange == null) { + return; + } + + _distributionPath.reset(); + final int dataCount = _yRawValues.length; + // TODO(Natrayansf): Add comment. + const num pointsCount = 500; + final num minimum = xAxis!.visibleRange!.minimum; + final num maximum = xAxis!.visibleRange!.maximum; + final num delta = (maximum - minimum) / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) { + final num xValue = minimum + i * delta; + final num yValue = + exp(-pow(xValue - _mean, 2) / (2 * pow(_deviation, 2))) / + (_deviation * sqrt(2 * pi)); + final num dx = yValue * binWidth * dataCount; + final double x = pointToPixelX(xValue, dx); + final double y = pointToPixelY(xValue, dx); + i == 0 ? _distributionPath.moveTo(x, y) : _distributionPath.lineTo(x, y); + } + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + // For Histogram series, it is not possible to add, remove, or replace + // a data point based on the indexes, because the xValues and yValues are + // calculated based on the entire range of data. Therefore, we need to + // re-calculate the histogram x and y values dynamically. + // To achieve this, we directly call the markNeedsUpdate() method. + canUpdateOrCreateSegments = true; + markNeedsUpdate(); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + paintSegments(context, offset); + if (showNormalDistributionCurve) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + segmentAnimationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + final Paint strokePaint = + Paint() + ..color = curveColor + ..strokeWidth = curveWidth + ..style = PaintingStyle.stroke; + curveDashArray == null + ? context.canvas.drawPath(_distributionPath, strokePaint) + : drawDashes( + context.canvas, + curveDashArray, + strokePaint, + path: _distributionPath, + ); + } + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } + + @override + void dispose() { + _resetDataHolders(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart new file mode 100644 index 000000000..a2147cef8 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart @@ -0,0 +1,619 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/legend.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// This class has the properties of the column series. +/// +/// To render a column chart, create an instance of [ColumnSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// The column series is a rectangular column with heights or lengths +/// proportional to the values that they represent. It has the spacing +/// property to separate the column. +/// +/// Provide the options of color, opacity, border color, and border width +/// to customize the appearance. +class ColumnSeries extends XyDataSeries { + /// Creating an argument constructor of ColumnSeries class. + const ColumnSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + super.markerSettings, + super.trendlines, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1.0, + this.trackPadding = 0.0, + this.borderRadius = BorderRadius.zero, + this.isTrackVisible = false, + this.spacing = 0.0, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.dashArray, + super.onRendererCreated, + super.initialSelectedDataIndexes, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Color of the track. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// isTrackVisible: true, + /// trackColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackColor; + + /// Color of the track border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackBorderColor; + + /// Width of the track border. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red, + /// trackBorderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackBorderWidth; + + /// Padding of the track. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// isTrackVisible: true, + /// trackPadding: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackPadding; + + /// Spacing between the columns. The value ranges from 0 to 1. + /// 1 represents 100% and 0 represents 0% of the available space. + /// + /// Spacing also affects the width of the column. For example, setting 20% + /// spacing and 100% width renders the column with 80% of total width. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// isTrackVisible: true, + /// spacing: 0.5 + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// Renders column with track. + /// + /// Track is a rectangular bar rendered from the start to the end of the axis. + /// Column series will be rendered above the track. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// isTrackVisible: true + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isTrackVisible; + + /// Customizes the corners of the column. + /// + /// Each corner can be customized with a desired value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ColumnSeries( + /// borderRadius: BorderRadius.all(Radius.circular(5)) + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double width; + + final Color borderColor; + + /// Create the column series renderer. + @override + ColumnSeriesRenderer createRenderer() { + ColumnSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as ColumnSeriesRenderer; + // ignore: unnecessary_null_comparison + return seriesRenderer; + } + return ColumnSeriesRenderer(); + } + + @override + ColumnSeriesRenderer createRenderObject(BuildContext context) { + final ColumnSeriesRenderer renderer = + super.createRenderObject(context) as ColumnSeriesRenderer; + renderer + ..isTrackVisible = isTrackVisible + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..spacing = spacing + ..width = width + ..borderRadius = borderRadius + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + ColumnSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..isTrackVisible = isTrackVisible + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..spacing = spacing + ..width = width + ..borderRadius = borderRadius + ..borderColor = borderColor; + } +} + +/// Creates series renderer for column series. +class ColumnSeriesRenderer extends XyDataSeriesRenderer + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + /// Calling the default constructor of ColumnSeriesRenderer class. + ColumnSeriesRenderer(); + + Color get trackBorderColor => _trackBorderColor; + Color _trackBorderColor = Colors.transparent; + set trackBorderColor(Color value) { + if (value != _trackBorderColor) { + _trackBorderColor = value; + markNeedsLayout(); + } + } + + Color get trackColor => _trackColor; + Color _trackColor = Colors.grey; + set trackColor(Color value) { + if (value != _trackColor) { + _trackColor = value; + markNeedsLayout(); + } + } + + double get trackBorderWidth => _trackBorderWidth; + double _trackBorderWidth = 1.0; + set trackBorderWidth(double value) { + if (value != _trackBorderWidth) { + _trackBorderWidth = value; + markNeedsLayout(); + } + } + + double get trackPadding => _trackPadding; + double _trackPadding = 0.0; + set trackPadding(double value) { + if (value != _trackPadding) { + _trackPadding = value; + markNeedsLayout(); + } + } + + bool get isTrackVisible => _isTrackVisible; + bool _isTrackVisible = false; + set isTrackVisible(bool value) { + if (value != _isTrackVisible) { + _isTrackVisible = value; + markNeedsLayout(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (value != _borderRadius) { + _borderRadius = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as ColumnSegment + ..series = this + ..x = xValues[index] + ..y = yValues[index] + ..bottom = bottom + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + ColumnSegment createSegment() => ColumnSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.columnSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final ColumnSegment columnSegment = segment as ColumnSegment; + updateSegmentTrackerStyle( + columnSegment, + trackColor, + trackBorderColor, + trackBorderWidth, + ); + updateSegmentColor(columnSegment, borderColor, borderWidth); + updateSegmentGradient( + columnSegment, + gradientBounds: columnSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + if (parent != null && parent!.onLegendTapped != null) { + final CartesianLegendItem legendItem = item as CartesianLegendItem; + final LegendTapArgs args = LegendTapArgs( + legendItem.series, + legendItem.seriesIndex, + legendItem.pointIndex, + ); + parent!.onLegendTapped!(args); + } + parent!.behaviorArea?.hideInteractiveTooltip(); + + controller.isVisible = !isToggled; + if (controller.isVisible == !isToggled) { + item.onToggled?.call(); + visibilityBeforeTogglingLegend = isToggled; + animateAllBarSeries(parent!); + } + + if (trendlineContainer != null) { + trendlineContainer!.updateLegendState(item, isToggled); + markNeedsLegendUpdate(); + } + markNeedsUpdate(); + } + + @override + void onRealTimeAnimationUpdate() { + super.onRealTimeAnimationUpdate(); + transformValues(); + } +} + +/// Creates the segments for column series. +/// +/// 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. +class ColumnSegment extends ChartSegment with BarSeriesTrackerMixin { + late ColumnSeriesRenderer series; + late num x; + late num y; + late num bottom; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + _oldSegmentRect = segmentRect; + } + + @override + void transformValues() { + if (x.isNaN || y.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, y); + final double y1 = transformY(left, y); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, bottom), + transformY(left, bottom), + x2, + y2, + borderRadius, + ); + + if (series.isTrackVisible) { + calculateTrackerBounds( + left, + right, + borderRadius, + series.trackPadding, + series.trackBorderWidth, + series, + ); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final Offset posFromRect = + y.isNegative && bottom == 0 + ? segmentRect!.outerRect.bottomCenter + : segmentRect!.outerRect.topCenter; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? posFromRect + : posFromRect; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + seriesIndex: series.index, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, y), + series.pointToPixelY(x, y), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + @override + void onPaint(Canvas canvas) { + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } + + if (segmentRect == null) { + return; + } + + RRect? paintRRect; + if (series.parent!.isLegendToggled && + _oldSegmentRect != null && + series.animationType != AnimationType.loading) { + paintRRect = performLegendToggleAnimation( + series, + segmentRect!, + _oldSegmentRect!, + series.borderRadius, + ); + } else { + paintRRect = RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); + } + + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + _oldSegmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart new file mode 100644 index 000000000..7e17542a1 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart @@ -0,0 +1,623 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/chart_point.dart'; +import '../common/circular_data_label.dart'; +import '../common/core_tooltip.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/renderer_helper.dart'; +import 'chart_series.dart'; + +/// This class has the properties of the Doughnut series. +/// +/// To render a doughnut chart, create an instance of [DoughnutSeries], +/// and add it to the series +/// collection property of [SfCircularChart]. +/// +/// Provide options for opacity, stroke width, stroke color, and +/// point color mapper to customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} +@immutable +class DoughnutSeries extends CircularSeries { + /// Creating an argument constructor of DoughnutSeries class. + const DoughnutSeries({ + super.key, + super.onCreateRenderer, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.pointColorMapper, + super.pointShaderMapper, + super.pointRadiusMapper, + super.dataLabelMapper, + super.sortFieldValueMapper, + super.startAngle = 0, + super.endAngle = 360, + super.radius = '80%', + super.innerRadius = '50%', + this.explode = false, + this.explodeAll = false, + this.explodeGesture = ActivationMode.singleTap, + this.explodeOffset = '10%', + this.explodeIndex, + super.groupTo, + super.groupMode, + super.pointRenderMode, + super.emptyPointSettings, + Color strokeColor = Colors.transparent, + double strokeWidth = 2.0, + super.dataLabelSettings, + super.enableTooltip = true, + super.name, + super.opacity, + super.animationDuration, + super.animationDelay, + super.selectionBehavior, + super.sortingOrder, + super.legendIconType, + super.cornerStyle = CornerStyle.bothFlat, + super.initialSelectedDataIndexes, + }) : super(borderColor: strokeColor, borderWidth: strokeWidth); + + /// Enables or disables the explode of slices on tap. + /// + /// Default to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// DoughnutSeries( + /// explode: true + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool explode; + + /// Enables or disables exploding all the slices at the initial rendering. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// DoughnutSeries( + /// explodeAll: true + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool explodeAll; + + /// Index of the slice to explode it at the initial rendering. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// DoughnutSeries( + /// explode: true, + /// explodeIndex: 2 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final int? explodeIndex; + + /// Offset of exploded slice. The value ranges from 0% to 100%. + /// + /// Defaults to `20%`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// DoughnutSeries( + /// explode: true, + /// explodeOffset: '30%' + /// ), + /// ], + /// ) + /// ); + ///} + ///``` + final String explodeOffset; + + /// Gesture for activating the explode. + /// + /// Explode can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, `ActivationMode.doubleTap`, + /// and `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// DoughnutSeries( + /// explode: true, + /// explodeGesture: ActivationMode.singleTap + /// ), + /// ], + /// ) + /// ); + ///} + ///``` + final ActivationMode explodeGesture; + + @override + List get positions => [ + ChartDataPointType.y, + ]; + + /// Create the circular series renderer. + @override + DoughnutSeriesRenderer createRenderer() { + DoughnutSeriesRenderer? seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as DoughnutSeriesRenderer; + // ignore: unnecessary_null_comparison + return seriesRenderer; + } + return DoughnutSeriesRenderer(); + } + + @override + DoughnutSeriesRenderer createRenderObject(BuildContext context) { + final DoughnutSeriesRenderer renderer = + super.createRenderObject(context) as DoughnutSeriesRenderer; + renderer + ..explode = explode + ..explodeAll = explodeAll + ..explodeIndex = explodeIndex + ..explodeOffset = explodeOffset + ..explodeGesture = explodeGesture; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + DoughnutSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..explode = explode + ..explodeAll = explodeAll + ..explodeIndex = explodeIndex + ..explodeOffset = explodeOffset + ..explodeGesture = explodeGesture; + } +} + +/// Creates series renderer for doughnut series. +class DoughnutSeriesRenderer extends CircularSeriesRenderer { + /// Calling the default constructor of [DoughnutSeriesRenderer] class. + DoughnutSeriesRenderer(); + + bool get explode => _explode; + bool _explode = false; + set explode(bool value) { + if (_explode != value) { + _explode = value; + _updateExploding(); + } + } + + bool get explodeAll => _explodeAll; + bool _explodeAll = false; + set explodeAll(bool value) { + if (_explodeAll != value) { + _explodeAll = value; + if (!_explodeAll) { + for (final ChartSegment segment in segments) { + final DoughnutSegment doughnutSegment = + segment as DoughnutSegment; + if (explode && segment.currentSegmentIndex == explodeIndex) { + doughnutSegment._isExploded = true; + } else { + doughnutSegment._isExploded = false; + } + } + + transformValues(); + markNeedsPaint(); + } else { + _updateExploding(); + } + } + } + + int? get explodeIndex => _explodeIndex; + int? _explodeIndex; + set explodeIndex(int? value) { + if (_explodeIndex != value) { + _explodeIndex = value; + _updateExploding(); + } + } + + String get explodeOffset => _explodeOffset; + String _explodeOffset = '10%'; + set explodeOffset(String value) { + if (_explodeOffset != value) { + _explodeOffset = value; + transformValues(); + markNeedsPaint(); + } + } + + ActivationMode get explodeGesture => _explodeGesture; + ActivationMode _explodeGesture = ActivationMode.singleTap; + set explodeGesture(ActivationMode value) { + if (_explodeGesture != value) { + _explodeGesture = value; + } + } + + /// Stores pointer down time to determine whether a long press interaction is handled at pointer up + DateTime? _pointerHoldingTime; + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + num yValue = circularYValues[index].abs(); + // Handled the empty point here. + yValue = yValue.isNaN || !segment.isVisible ? 0 : yValue; + final double degree = + (yValue.abs() / (sumOfY != 0 ? sumOfY : 1)) * totalAngle; + final double pointEndAngle = pointStartAngle + degree; + final double outerRadius = + pointRadii.isNotEmpty + ? percentToValue( + pointRadii[index], + (min(size.width, size.height)) / 2, + )! + : currentRadius; + + segment as DoughnutSegment + ..series = this + .._degree = degree + ..startAngle = pointStartAngle + ..endAngle = pointEndAngle + .._innerRadius = currentInnerRadius + .._outerRadius = outerRadius + .._center = center + .._isExploded = explode && (index == explodeIndex || explodeAll) + ..isEmpty = + (emptyPointSettings.mode != EmptyPointMode.drop && + emptyPointSettings.mode != EmptyPointMode.gap) && + isEmpty(index); + + pointStartAngle = pointEndAngle; + } + + @override + DoughnutSegment createSegment() => DoughnutSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.doughnutSeries; + + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, borderColor, borderWidth); + segment.fillPaint.shader = null; + updateSegmentGradient(segment); + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + final bool isHit = super.hitTest(result, position: position); + return explode || isHit; + } + + @override + void handlePointerDown(PointerDownEvent details) { + if (explode && explodeGesture == ActivationMode.singleTap) { + _pointerHoldingTime = DateTime.now(); + } + super.handlePointerDown(details); + } + + @override + void handlePointerUp(PointerUpEvent details) { + if (explode && + explodeGesture == ActivationMode.singleTap && + _pointerHoldingTime != null && + DateTime.now().difference(_pointerHoldingTime!).inMilliseconds < + kLongPressTimeout.inMilliseconds) { + _handleExploding(details.localPosition); + } + super.handlePointerUp(details); + } + + @override + void handleDoubleTap(Offset position) { + final Offset localPosition = globalToLocal(position); + if (explode && explodeGesture == ActivationMode.doubleTap) { + _handleExploding(localPosition); + } + super.handleDoubleTap(position); + } + + @override + void handleLongPressStart(LongPressStartDetails details) { + if (explode && explodeGesture == ActivationMode.longPress) { + _handleExploding(details.localPosition); + } + super.handleLongPressStart(details); + } + + void _handleExploding(Offset position) { + for (final ChartSegment segment in segments) { + final DoughnutSegment doughnutSegment = + segment as DoughnutSegment; + if (segment.contains(position)) { + doughnutSegment._isExploded = !doughnutSegment._isExploded; + if (doughnutSegment._isExploded) { + _explodeIndex = segment.currentSegmentIndex; + } else { + _explodeIndex = -1; + } + } else { + doughnutSegment._isExploded = false; + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + void _updateExploding() { + for (final ChartSegment segment in segments) { + final DoughnutSegment doughnutSegment = + segment as DoughnutSegment; + if (!explode) { + doughnutSegment._isExploded = false; + } else { + if (explodeAll || segment.currentSegmentIndex == explodeIndex) { + doughnutSegment._isExploded = true; + } else { + doughnutSegment._isExploded = false; + } + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + @override + Offset dataLabelPosition(CircularDataLabelBoxParentData current, Size size) { + final DoughnutSegment segment = + segments[current.dataPointIndex] as DoughnutSegment; + current.point! + ..degree = segment._degree + ..isExplode = segment._isExploded + ..isVisible = segment.isVisible + ..explodeOffset = explodeOffset + ..startAngle = segment._startAngle + ..endAngle = segment._endAngle + ..midAngle = (segment._startAngle + segment._endAngle) / 2 + ..innerRadius = segment._innerRadius + ..outerRadius = segment._outerRadius + ..center = center + ..fill = palette[current.dataPointIndex % palette.length]; + + _findDataLabelPosition(current.point!); + return super.dataLabelPosition(current, size); + } + + /// To find data label position. + void _findDataLabelPosition(CircularChartPoint point) { + point.midAngle = + point.midAngle! > 360 ? point.midAngle! - 360 : point.midAngle!; + point.dataLabelPosition = + ((point.midAngle! >= -90 && point.midAngle! < 0) || + (point.midAngle! >= 0 && point.midAngle! < 90) || + (point.midAngle! >= 270)) + ? Position.right + : Position.left; + } +} + +class DoughnutSegment extends ChartSegment { + late DoughnutSeriesRenderer series; + late double _degree; + late double _innerRadius; + late double _outerRadius; + late Offset _center; + bool _isExploded = false; + Path fillPath = Path(); + double _priorStartAngle = double.nan; + double _priorEndAngle = double.nan; + + double get startAngle => _startAngle; + double _startAngle = double.nan; + set startAngle(double value) { + _priorStartAngle = _startAngle; + _startAngle = value; + } + + double get endAngle => _endAngle; + double _endAngle = double.nan; + set endAngle(double value) { + _priorEndAngle = _endAngle; + _endAngle = value; + } + + @override + void transformValues() { + fillPath.reset(); + + double degree = _degree * animationFactor; + final double angle = calculateAngle( + series.animationFactor == 1, + series.startAngle, + series.endAngle, + ); + final double startAngle = + lerpDouble( + _priorEndAngle.isNaN ? angle : _priorStartAngle, + _startAngle, + animationFactor, + )!; + final double endAngle = + _priorEndAngle.isNaN + ? startAngle + degree + : lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; + degree = _priorEndAngle.isNaN ? degree : endAngle - startAngle; + + // If the startAngle and endAngle value is same, then degree will be 0. + // Hence no need to render segments. + if (!isVisible && degree == 0) { + return; + } + + if (series.explode && _isExploded) { + final double midAngle = (_startAngle + _endAngle) / 2; + _center = calculateExplodingCenter( + midAngle, + _outerRadius, + series.center, + series.explodeOffset, + ); + } else { + _center = series.center; + } + + final CornerStyle cornerStyle = series.cornerStyle; + if (cornerStyle == CornerStyle.bothFlat) { + fillPath = calculateArcPath( + _innerRadius, + _outerRadius, + _center, + startAngle, + endAngle, + degree, + isAnimate: true, + ); + } else { + final num angleDeviation = findAngleDeviation( + _innerRadius, + _outerRadius, + 360, + ); + final double actualStartAngle = + (cornerStyle == CornerStyle.startCurve || + cornerStyle == CornerStyle.bothCurve) + ? (startAngle + angleDeviation) + : startAngle; + final double actualEndAngle = + (cornerStyle == CornerStyle.endCurve || + cornerStyle == CornerStyle.bothCurve) + ? (endAngle - angleDeviation) + : endAngle; + + fillPath = calculateRoundedCornerArcPath( + cornerStyle, + _innerRadius, + _outerRadius, + _center, + actualStartAngle, + actualEndAngle, + ); + } + } + + @override + Paint getFillPaint() => fillPaint; + + @override + Paint getStrokePaint() => strokePaint; + + @override + void calculateSegmentPoints() {} + + @override + bool contains(Offset position) { + return fillPath.contains(position); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + final ChartPoint point = ChartPoint( + x: series.circularXValues[currentSegmentIndex], + y: series.circularYValues[currentSegmentIndex], + ); + final Offset location = calculateOffset( + (_startAngle + _endAngle) / 2, + (_innerRadius + _outerRadius) / 2, + _center, + ); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? series.localToGlobal(position ?? location) + : series.localToGlobal(location); + return ChartTooltipInfo( + primaryPosition: preferredPos, + secondaryPosition: preferredPos, + text: series.tooltipText(point), + header: '', + data: series.dataSource![currentSegmentIndex], + point: point, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + ); + } + + @override + void onPaint(Canvas canvas) { + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawPath(fillPath, paint); + } + } + + @override + void dispose() { + fillPath.reset(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart new file mode 100644 index 000000000..c0cdc0de6 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart @@ -0,0 +1,1291 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import '../axis/datetime_axis.dart'; +import '../common/callbacks.dart'; +import '../utils/enum.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// This class has the properties of the error bar series. +/// +/// To render a error bar chart, create an instance of [ErrorBarSeries], +/// and add it to the series collection property of [SfCartesianChart]. +class ErrorBarSeries extends XyDataSeries { + /// Creating an argument constructor of ErrorBarSeries class. + const ErrorBarSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + double width = 2.0, + super.emptyPointSettings, + super.initialIsVisible, + super.animationDuration, + super.opacity, + super.animationDelay = 1500, + super.dashArray, + super.onRendererCreated, + super.legendItemText, + super.isVisibleInLegend = false, + super.legendIconType = LegendIconType.verticalLine, + this.type = ErrorBarType.fixed, + this.direction = Direction.both, + this.mode = RenderingMode.vertical, + this.verticalErrorValue = 3.0, + this.horizontalErrorValue = 1.0, + this.verticalPositiveErrorValue = 3.0, + this.horizontalPositiveErrorValue = 1.0, + this.verticalNegativeErrorValue = 3.0, + this.horizontalNegativeErrorValue = 1.0, + this.capLength = 10.0, + this.onRenderDetailsUpdate, + super.onCreateShader, + }) : super(borderWidth: width); + + /// Type of the error bar. + /// + /// Defaults to `ErrorBarType.fixed`. + /// + /// Other values are `ErrorBarType.percentage`, + /// `ErrorBarType.standardDeviation`, + /// `ErrorBarType.custom`, `ErrorBarType.standardError`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.fixed, + /// ), + /// ], + /// ); + /// } + /// ``` + final ErrorBarType? type; + + /// Direction of error bar. + /// + /// Defaults to `Direction.both`. + /// + /// Also refer [Direction]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// direction: Direction.plus, + /// ), + /// ], + /// ); + /// } + /// ``` + final Direction? direction; + + /// Mode of error bar. + /// + /// Defaults to `RenderingMode.vertical`. + /// + /// Other values are `RenderingMode.horizontal` and `RenderingMode.both`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// mode: RenderingMode.both, + /// ), + /// ], + /// ); + /// } + /// ``` + final RenderingMode? mode; + + /// Vertical error value in Y direction. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// verticalErrorValue: 2, + /// mode: RenderingMode.vertical, + /// ), + /// ], + /// ); + /// } + /// ``` + final double? verticalErrorValue; + + /// Horizontal error value in X direction.. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// horizontalErrorValue:2, + /// mode: RenderingMode.horizontal, + /// ), + /// ], + /// ); + /// } + /// ``` + final double? horizontalErrorValue; + + /// Vertical error value in positive Y direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// verticalPositiveErrorValue:2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double? verticalPositiveErrorValue; + + /// Horizontal error value in positive X direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// horizontalPositiveErrorValue:2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double? horizontalPositiveErrorValue; + + /// Vertical error value in negative Y direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to `3`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// verticalNegativeErrorValue:2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double? verticalNegativeErrorValue; + + /// Horizontal error value in negative X direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// horizontalNegativeErrorValue:2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double? horizontalNegativeErrorValue; + + /// Length of the error bar's cap. + /// + /// Defaults to `10`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// capLength:20.0, + /// ), + /// ], + /// ); + /// } + /// ``` + final double? capLength; + + /// Callback which gets called on error bar render. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// onRenderDetailsUpdate: + /// (ErrorBarRenderDetails errorBarRenderDetails){ + /// print(errorBarRenderDetails.pointIndex); + /// print(errorBarRenderDetails.viewPortPointIndex); + /// print(errorBarRenderDetails.calculatedErrorBarValues!. + /// horizontalPositiveErrorValue); + /// print(errorBarRenderDetails.calculatedErrorBarValues!. + /// horizontalNegativeErrorValue); + /// print(errorBarRenderDetails.calculatedErrorBarValues!. + /// verticalPositiveErrorValue); + /// print(errorBarRenderDetails.calculatedErrorBarValues!. + /// verticalNegativeErrorValue); + /// } + /// ), + /// ], + /// ); + /// } + /// ``` + final ChartErrorBarRenderCallback? onRenderDetailsUpdate; + + /// Create the error bar series renderer. + @override + ErrorBarSeriesRenderer createRenderer() { + ErrorBarSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as ErrorBarSeriesRenderer; + return seriesRenderer; + } + return ErrorBarSeriesRenderer(); + } + + @override + ErrorBarSeriesRenderer createRenderObject(BuildContext context) { + final ErrorBarSeriesRenderer renderer = + super.createRenderObject(context) as ErrorBarSeriesRenderer; + renderer + ..type = type + ..direction = direction + ..mode = mode + ..verticalErrorValue = verticalErrorValue + ..horizontalErrorValue = horizontalErrorValue + ..verticalPositiveErrorValue = verticalPositiveErrorValue + ..horizontalPositiveErrorValue = horizontalPositiveErrorValue + ..verticalNegativeErrorValue = verticalNegativeErrorValue + ..horizontalNegativeErrorValue = horizontalNegativeErrorValue + ..capLength = capLength + ..onRenderDetailsUpdate = onRenderDetailsUpdate; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + ErrorBarSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..type = type + ..direction = direction + ..mode = mode + ..verticalErrorValue = verticalErrorValue + ..horizontalErrorValue = horizontalErrorValue + ..verticalPositiveErrorValue = verticalPositiveErrorValue + ..horizontalPositiveErrorValue = horizontalPositiveErrorValue + ..verticalNegativeErrorValue = verticalNegativeErrorValue + ..horizontalNegativeErrorValue = horizontalNegativeErrorValue + ..capLength = capLength + ..onRenderDetailsUpdate = onRenderDetailsUpdate; + } +} + +/// Creates series renderer for error bar series. +class ErrorBarSeriesRenderer extends XyDataSeriesRenderer + with ContinuousSeriesMixin, SegmentAnimationMixin { + /// Calling the default constructor of ErrorBarSeriesRenderer class. + ErrorBarSeriesRenderer(); + + ErrorBarType? get type => _type; + ErrorBarType? _type; + set type(ErrorBarType? value) { + if (_type != value) { + _type = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + Direction? get direction => _direction; + Direction? _direction; + set direction(Direction? value) { + if (_direction != value) { + _direction = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + RenderingMode? get mode => _mode; + RenderingMode? _mode; + set mode(RenderingMode? value) { + if (_mode != value) { + _mode = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get verticalErrorValue => _verticalErrorValue; + double? _verticalErrorValue; + set verticalErrorValue(double? value) { + if (_verticalErrorValue != value) { + _verticalErrorValue = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get horizontalErrorValue => _horizontalErrorValue; + double? _horizontalErrorValue; + set horizontalErrorValue(double? value) { + if (_horizontalErrorValue != value) { + _horizontalErrorValue = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get horizontalPositiveErrorValue => _horizontalPositiveErrorValue; + double? _horizontalPositiveErrorValue; + set horizontalPositiveErrorValue(double? value) { + if (_horizontalPositiveErrorValue != value) { + _horizontalPositiveErrorValue = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get verticalPositiveErrorValue => _verticalPositiveErrorValue; + double? _verticalPositiveErrorValue; + set verticalPositiveErrorValue(double? value) { + if (_verticalPositiveErrorValue != value) { + _verticalPositiveErrorValue = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get verticalNegativeErrorValue => _verticalNegativeErrorValue; + double? _verticalNegativeErrorValue; + set verticalNegativeErrorValue(double? value) { + if (_verticalNegativeErrorValue != value) { + _verticalNegativeErrorValue = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get horizontalNegativeErrorValue => _horizontalNegativeErrorValue; + double? _horizontalNegativeErrorValue; + set horizontalNegativeErrorValue(double? value) { + if (_horizontalNegativeErrorValue != value) { + _horizontalNegativeErrorValue = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + double? get capLength => _capLength; + double? _capLength; + set capLength(double? value) { + if (_capLength != value) { + _capLength = value; + _updateErrorBarValues(); + forceTransformValues = true; + markNeedsLayout(); + } + } + + ChartErrorBarRenderCallback? get onRenderDetailsUpdate => + _onRenderDetailsUpdate; + ChartErrorBarRenderCallback? _onRenderDetailsUpdate; + set onRenderDetailsUpdate(ChartErrorBarRenderCallback? value) { + if (_onRenderDetailsUpdate != value) { + _onRenderDetailsUpdate = value; + markNeedsLayout(); + } + } + + final List _errorBarValues = []; + num _xMin = 0.0; + num _xMax = 0.0; + num _yMin = 0.0; + num _yMax = 0.0; + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + _xMin = xMin; + _xMax = xMax; + _yMin = yMin; + _yMax = yMax; + _updateErrorBarValues(); + populateChartPoints(); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + _xMin = xMin; + _xMax = xMax; + _yMin = yMin; + _yMax = yMax; + _updateErrorBarValues(); + } + + void _updateErrorBarValues() { + _errorBarValues.clear(); + final AxesRange axesRange = AxesRange(_xMin, _xMax, _yMin, _yMax); + ErrorBarValues errorBarValues; + + if (type == ErrorBarType.standardDeviation || + type == ErrorBarType.standardError) { + num sumOfX = 0.0; + num sumOfY = 0.0; + num? verticalSquareRoot; + num? horizontalSquareRoot; + num? verticalMean; + num? horizontalMean; + + for (int i = 0; i < dataCount; i++) { + sumOfY += yValues[i]; + sumOfX += xValues[i]; + } + + verticalMean = + (mode == RenderingMode.horizontal) + ? verticalMean + : sumOfY / dataCount; + horizontalMean = + (mode == RenderingMode.vertical) + ? horizontalMean + : sumOfX / dataCount; + for (int i = 0; i < dataCount; i++) { + sumOfY = + (mode == RenderingMode.horizontal) + ? sumOfY + : sumOfY + pow(yValues[i] - verticalMean!, 2); + sumOfX = + (mode == RenderingMode.vertical) + ? sumOfX + : sumOfX + pow(xValues[i] - horizontalMean!, 2); + } + + verticalSquareRoot = sqrt(sumOfY / (dataCount - 1)); + horizontalSquareRoot = sqrt(sumOfX / (dataCount - 1)); + + final ErrorBarMean mean = ErrorBarMean( + verticalSquareRoot: verticalSquareRoot, + horizontalSquareRoot: horizontalSquareRoot, + verticalMean: verticalMean, + horizontalMean: horizontalMean, + ); + + for (int j = 0; j < dataCount; j++) { + final num xValue = xValues[j]; + final num yValue = yValues[j]; + final ErrorValues errorValues = _calculateErrorValues( + xValue, + yValue, + mean: mean, + ); + + errorBarValues = _calculateErrorBarValues(errorValues, xValue, yValue); + _errorBarValues.add(errorBarValues); + _findMinMax(errorBarValues, axesRange); + } + } else { + for (int k = 0; k < dataCount; k++) { + final num xValue = xValues[k]; + final num yValue = yValues[k]; + final ErrorValues errorValues = _calculateErrorValues(xValue, yValue); + errorBarValues = _calculateErrorBarValues(errorValues, xValue, yValue); + _errorBarValues.add(errorBarValues); + _findMinMax(errorBarValues, axesRange); + } + } + + xMin = axesRange.xMinimum; + xMax = axesRange.xMaximum; + yMin = axesRange.yMinimum; + yMax = axesRange.yMaximum; + } + + void _findMinMax(ErrorBarValues errorBarValues, AxesRange axesRange) { + if (errorBarValues.horizontalNegativeErrorValue != null) { + axesRange.xMinimum = min( + axesRange.xMinimum, + errorBarValues.horizontalNegativeErrorValue!, + ); + } + + if (errorBarValues.horizontalPositiveErrorValue != null) { + axesRange.xMaximum = max( + axesRange.xMaximum, + errorBarValues.horizontalPositiveErrorValue!, + ); + } + + if (errorBarValues.verticalNegativeErrorValue != null) { + axesRange.yMinimum = min( + axesRange.yMinimum, + errorBarValues.verticalNegativeErrorValue!, + ); + } + if (errorBarValues.verticalPositiveErrorValue != null) { + axesRange.yMaximum = max( + axesRange.yMaximum, + errorBarValues.verticalPositiveErrorValue!, + ); + } + } + + /// Calculating error values based on error bar type. + ErrorValues _calculateErrorValues( + num actualXValue, + num actualYValue, { + ErrorBarMean? mean, + }) { + num errorX = 0.0; + num errorY = 0.0; + num customNegativeErrorX = 0.0; + num customNegativeErrorY = 0.0; + ErrorValues? errorValues; + + if (type != ErrorBarType.custom) { + errorY = + (mode == RenderingMode.horizontal) ? errorY : verticalErrorValue!; + errorX = + (mode == RenderingMode.vertical) ? errorX : horizontalErrorValue!; + errorValues = ErrorValues(errorX: errorX, errorY: errorY); + + if (type == ErrorBarType.standardError) { + errorValues = _calculateStandardErrorValues(errorValues, mean!); + } else if (type == ErrorBarType.standardDeviation) { + errorValues = _calculateStandardDeviationValues(errorValues, mean!); + } else if (type == ErrorBarType.percentage) { + errorY = + (mode == RenderingMode.horizontal) + ? errorY + : (errorY / 100) * actualYValue; + errorX = + (mode == RenderingMode.vertical) + ? errorX + : (errorX / 100) * actualXValue; + errorValues = ErrorValues(errorX: errorX, errorY: errorY); + } + } else if (type == ErrorBarType.custom) { + errorY = + (mode == RenderingMode.horizontal) + ? errorY + : verticalPositiveErrorValue!; + customNegativeErrorY = + (mode == RenderingMode.horizontal) + ? customNegativeErrorY + : verticalNegativeErrorValue!; + errorX = + (mode == RenderingMode.vertical) + ? errorX + : horizontalPositiveErrorValue!; + customNegativeErrorX = + (mode == RenderingMode.vertical) + ? customNegativeErrorX + : horizontalNegativeErrorValue!; + errorValues = ErrorValues( + errorX: errorX, + errorY: errorY, + customNegativeX: customNegativeErrorX, + customNegativeY: customNegativeErrorY, + ); + } + return errorValues!; + } + + /// Calculate the error values of standard error type error bar. + ErrorValues _calculateStandardErrorValues( + ErrorValues errorValues, + ErrorBarMean mean, + ) { + final num errorX = + (errorValues.errorX! * mean.horizontalSquareRoot!) / sqrt(dataCount); + final num errorY = + (errorValues.errorY! * mean.verticalSquareRoot!) / sqrt(dataCount); + return ErrorValues(errorX: errorX, errorY: errorY); + } + + /// Calculate the error values of standard deviation type error bar. + ErrorValues _calculateStandardDeviationValues( + ErrorValues errorValues, + ErrorBarMean mean, + ) { + num errorY = errorValues.errorY!, errorX = errorValues.errorX!; + errorY = + (mode == RenderingMode.horizontal) + ? errorY + : errorY * (mean.verticalSquareRoot! + mean.verticalMean!); + errorX = + (mode == RenderingMode.vertical) + ? errorX + : errorX * (mean.horizontalSquareRoot! + mean.horizontalMean!); + return ErrorValues(errorX: errorX, errorY: errorY); + } + + /// Calculate the error bar values. + ErrorBarValues _calculateErrorBarValues( + ErrorValues errorValues, + num actualXValue, + num actualYValue, + ) { + final bool isDirectionPlus = direction == Direction.plus; + final bool isBothDirection = direction == Direction.both; + final bool isDirectionMinus = direction == Direction.minus; + final bool isCustomFixedType = + type == ErrorBarType.fixed || type == ErrorBarType.custom; + double? verticalPositiveErrorValue, + horizontalPositiveErrorValue, + verticalNegativeErrorValue, + horizontalNegativeErrorValue; + + if (mode == RenderingMode.horizontal || mode == RenderingMode.both) { + if (isDirectionPlus || isBothDirection) { + if (xAxis is RenderDateTimeAxis && isCustomFixedType) { + horizontalPositiveErrorValue = _updateDateTimeHorizontalErrorValue( + actualXValue, + errorValues.errorX!, + ); + } else { + horizontalPositiveErrorValue = + actualXValue + errorValues.errorX! as double; + } + } + if (isDirectionMinus || isBothDirection) { + if (xAxis is RenderDateTimeAxis && isCustomFixedType) { + horizontalNegativeErrorValue = _updateDateTimeHorizontalErrorValue( + actualXValue, + (type == ErrorBarType.custom) + ? -errorValues.customNegativeX! + : -errorValues.errorX!, + ); + } else { + horizontalNegativeErrorValue = + actualXValue - + ((type == ErrorBarType.custom) + ? errorValues.customNegativeX! + : errorValues.errorX!) + as double; + } + } + } + + if (mode == RenderingMode.vertical || mode == RenderingMode.both) { + if (isDirectionPlus || isBothDirection) { + verticalPositiveErrorValue = + actualYValue + errorValues.errorY! as double; + } + if (isDirectionMinus || isBothDirection) { + verticalNegativeErrorValue = + actualYValue - + ((type == ErrorBarType.custom) + ? errorValues.customNegativeY! + : errorValues.errorY!) + as double; + } + } + return ErrorBarValues( + horizontalPositiveErrorValue, + horizontalNegativeErrorValue, + verticalPositiveErrorValue, + verticalNegativeErrorValue, + ); + } + + /// Calculate the error values for DateTime axis. + double _updateDateTimeHorizontalErrorValue(num actualXValue, num errorValue) { + DateTime errorXValue = DateTime.fromMillisecondsSinceEpoch( + actualXValue.toInt(), + ); + final int errorX = errorValue.toInt(); + + final RenderDateTimeAxis dateTimeAxis = xAxis! as RenderDateTimeAxis; + final DateTimeIntervalType type = dateTimeAxis.intervalType; + switch (type) { + case DateTimeIntervalType.years: + errorXValue = DateTime( + errorXValue.year + errorX, + errorXValue.month, + errorXValue.day, + ); + break; + case DateTimeIntervalType.months: + errorXValue = DateTime( + errorXValue.year, + errorXValue.month + errorX, + errorXValue.day, + ); + break; + case DateTimeIntervalType.days: + errorXValue = DateTime( + errorXValue.year, + errorXValue.month, + errorXValue.day + errorX, + ); + break; + case DateTimeIntervalType.hours: + errorXValue = errorXValue.add(Duration(hours: errorX)); + break; + case DateTimeIntervalType.minutes: + errorXValue = errorXValue.add(Duration(minutes: errorX)); + break; + case DateTimeIntervalType.seconds: + errorXValue = errorXValue.add(Duration(seconds: errorX)); + break; + case DateTimeIntervalType.milliseconds: + errorXValue = errorXValue.add(Duration(milliseconds: errorX)); + break; + case DateTimeIntervalType.auto: + errorXValue = errorXValue.add(Duration(milliseconds: errorX)); + break; + } + return errorXValue.millisecondsSinceEpoch.toDouble(); + } + + @override + void createOrUpdateSegments() { + super.createOrUpdateSegments(); + + if (onRenderDetailsUpdate != null) { + final int length = _errorBarValues.length; + for (int i = 0; i < length; i++) { + final ErrorBarValues values = _errorBarValues[i]; + onRenderDetailsUpdate!(ErrorBarRenderDetails(i, i, values)); + } + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as ErrorBarSegment + ..series = this + ..currentSegmentIndex = 0 + .._xValues = xValues + .._yValues = yValues + .._errorBarValues = _errorBarValues; + } + + /// Creates a segment for a data point in the series. + @override + ErrorBarSegment createSegment() => ErrorBarSegment(); + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth); + updateSegmentGradient(segment); + } + + @override + void onLoadingAnimationUpdate() { + super.onLoadingAnimationUpdate(); + transformValues(); + } + + @override + bool hitTestSelf(Offset position) { + return false; + } +} + +/// Creates the segments for error bar series. +/// +/// This generates the error bar series points and has the +/// [calculateSegmentPoints] override method +/// used to customize the error bar series segment point calculation. +/// +/// It gets the path, stroke color and fill color from the `series` +/// to render the error bar segment. +class ErrorBarSegment extends ChartSegment { + late ErrorBarSeriesRenderer series; + final double _effectiveAnimationFactor = 0.05; + late double _capPointValue; + late double _errorBarMidPointX; + late double _errorBarMidPointY; + + late List _xValues; + late List _yValues; + late List _errorBarValues; + + final Path _verticalPath = Path(); + final Path _verticalCapPath = Path(); + final Path _horizontalPath = Path(); + final Path _horizontalCapPath = Path(); + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + if (_xValues.isEmpty || _yValues.isEmpty || _errorBarValues.isEmpty) { + return; + } + + _verticalPath.reset(); + _verticalCapPath.reset(); + _horizontalPath.reset(); + _horizontalCapPath.reset(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + for (int i = 0; i < series.dataCount; i++) { + double? verticalPositiveX; + double? verticalPositiveY; + double? verticalNegativeX; + double? verticalNegativeY; + double? horizontalPositiveX; + double? horizontalPositiveY; + double? horizontalNegativeX; + double? horizontalNegativeY; + + final num xValue = _xValues[i]; + final num yValue = _yValues[i]; + final ErrorBarValues errorBarValues = _errorBarValues[i]; + + if (animationFactor != 0) { + final bool isTransposedChart = series.isTransposed; + _errorBarMidPointX = transformX(xValue, yValue); + _errorBarMidPointY = transformY(xValue, yValue); + points.add(Offset(_errorBarMidPointX, _errorBarMidPointY)); + + if (errorBarValues.verticalPositiveErrorValue != null) { + verticalPositiveX = transformX( + xValue, + errorBarValues.verticalPositiveErrorValue!, + ); + verticalPositiveY = transformY( + xValue, + errorBarValues.verticalPositiveErrorValue!, + ); + points.add(Offset(verticalPositiveX, verticalPositiveY)); + } + + if (errorBarValues.verticalNegativeErrorValue != null) { + verticalNegativeX = transformX( + xValue, + errorBarValues.verticalNegativeErrorValue!, + ); + verticalNegativeY = transformY( + xValue, + errorBarValues.verticalNegativeErrorValue!, + ); + points.add(Offset(verticalNegativeX, verticalNegativeY)); + } + + if (errorBarValues.horizontalPositiveErrorValue != null) { + horizontalPositiveX = transformX( + errorBarValues.horizontalPositiveErrorValue!, + yValue, + ); + horizontalPositiveY = transformY( + errorBarValues.horizontalPositiveErrorValue!, + yValue, + ); + points.add(Offset(horizontalPositiveX, horizontalPositiveY)); + } + + if (errorBarValues.horizontalNegativeErrorValue != null) { + horizontalNegativeX = transformX( + errorBarValues.horizontalNegativeErrorValue!, + yValue, + ); + horizontalNegativeY = transformY( + errorBarValues.horizontalNegativeErrorValue!, + yValue, + ); + points.add(Offset(horizontalNegativeX, horizontalNegativeY)); + } + + double animatingPoint; + if (verticalPositiveX != null && verticalPositiveY != null) { + animatingPoint = + isTransposedChart + ? _errorBarMidPointX + + ((verticalPositiveX - _errorBarMidPointX) * + _effectiveAnimationFactor) + : _errorBarMidPointY - + ((_errorBarMidPointY - verticalPositiveY) * + _effectiveAnimationFactor); + + _capPointValue = + animatingPoint - + ((animatingPoint - + (isTransposedChart + ? verticalPositiveX + : verticalPositiveY)) * + animationFactor); + + _calculateVerticalPath( + _capPointValue, + animationFactor, + series.capLength!, + isTransposedChart, + ); + } + if (verticalNegativeX != null && verticalNegativeY != null) { + animatingPoint = + isTransposedChart + ? _errorBarMidPointX + + ((verticalNegativeX - _errorBarMidPointX) * + _effectiveAnimationFactor) + : _errorBarMidPointY + + ((verticalNegativeY - _errorBarMidPointY) * + _effectiveAnimationFactor); + + _capPointValue = + animatingPoint + + (((isTransposedChart ? verticalNegativeX : verticalNegativeY) - + animatingPoint) * + animationFactor); + + _calculateVerticalPath( + _capPointValue, + animationFactor, + series.capLength!, + isTransposedChart, + ); + } + if (horizontalPositiveX != null && horizontalPositiveY != null) { + animatingPoint = + isTransposedChart + ? _errorBarMidPointY - + ((_errorBarMidPointY - horizontalPositiveY) * + _effectiveAnimationFactor) + : _errorBarMidPointX + + ((horizontalPositiveX - _errorBarMidPointX) * + _effectiveAnimationFactor); + + _capPointValue = + animatingPoint + + (((isTransposedChart + ? horizontalPositiveY + : horizontalPositiveX) - + animatingPoint) * + animationFactor); + + _calculateHorizontalPath( + _capPointValue, + animationFactor, + series.capLength!, + isTransposedChart, + ); + } + if (horizontalNegativeX != null && horizontalNegativeY != null) { + animatingPoint = + isTransposedChart + ? _errorBarMidPointY + + ((horizontalNegativeY - _errorBarMidPointY) * + _effectiveAnimationFactor) + : _errorBarMidPointX - + ((_errorBarMidPointX - horizontalNegativeX) * + _effectiveAnimationFactor); + + _capPointValue = + animatingPoint - + ((animatingPoint - + (isTransposedChart + ? horizontalNegativeY + : horizontalNegativeX)) * + animationFactor); + + _calculateHorizontalPath( + _capPointValue, + animationFactor, + series.capLength!, + isTransposedChart, + ); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + } + } + + void _calculateVerticalPath( + double capPointValue, + double animationFactor, + double capLength, + bool isTransposedChart, + ) { + if (isTransposedChart) { + _verticalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + _verticalPath.lineTo(capPointValue, _errorBarMidPointY); + _verticalCapPath.moveTo( + capPointValue, + _errorBarMidPointY - (capLength / 2), + ); + _verticalCapPath.lineTo( + capPointValue, + _errorBarMidPointY + (capLength / 2), + ); + } else { + _verticalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + _verticalPath.lineTo(_errorBarMidPointX, capPointValue); + _verticalCapPath.moveTo( + _errorBarMidPointX - (capLength / 2), + capPointValue, + ); + _verticalCapPath.lineTo( + _errorBarMidPointX + (capLength / 2), + capPointValue, + ); + } + _verticalPath.close(); + _verticalCapPath.close(); + } + + void _calculateHorizontalPath( + double capPointValue, + double animationFactor, + double capLength, + bool isTransposedChart, + ) { + if (isTransposedChart) { + _horizontalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + _horizontalPath.lineTo(_errorBarMidPointX, capPointValue); + _horizontalCapPath.moveTo( + _errorBarMidPointX - (capLength / 2), + capPointValue, + ); + _horizontalCapPath.lineTo( + _errorBarMidPointX + (capLength / 2), + capPointValue, + ); + } else { + _horizontalPath.moveTo(capPointValue, _errorBarMidPointY); + _horizontalPath.lineTo(_errorBarMidPointX, _errorBarMidPointY); + + _horizontalCapPath.moveTo( + capPointValue, + _errorBarMidPointY - (capLength / 2), + ); + _horizontalCapPath.lineTo( + capPointValue, + _errorBarMidPointY + (capLength / 2), + ); + } + _horizontalPath.close(); + _horizontalCapPath.close(); + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => strokePaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + final Paint paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawPath(_verticalPath, paint); + canvas.drawPath(_verticalCapPath, paint); + + canvas.drawPath(_horizontalPath, paint); + canvas.drawPath(_horizontalCapPath, paint); + } + } + + @override + void dispose() { + _verticalPath.reset(); + _verticalCapPath.reset(); + _horizontalPath.reset(); + _horizontalCapPath.reset(); + super.dispose(); + } +} + +class ErrorValues { + /// Creates an instance of chart error values. + ErrorValues({ + this.errorX, + this.errorY, + this.customNegativeX, + this.customNegativeY, + }); + + /// Specifies the value of x. + num? errorX; + + /// Specifies the value of y. + num? errorY; + + /// Specifies the value of x in custom type error bar. + num? customNegativeX; + + /// Specifies the value of y in custom type error bar. + num? customNegativeY; +} + +/// Holds the values related to standard error and standard +/// deviation error bars. +class ErrorBarMean { + /// Creates an instance of ErrorBarMean. + ErrorBarMean({ + this.verticalSquareRoot, + this.horizontalSquareRoot, + this.verticalMean, + this.horizontalMean, + }); + + /// Mean's required square root value of all data points in y direction. + final num? verticalSquareRoot; + + /// Mean's required square root value of all data points in x direction. + final num? horizontalSquareRoot; + + /// Required mean value of all data points in y direction. + final num? verticalMean; + + /// Required mean value of all data points in x direction. + final num? horizontalMean; +} + +class AxesRange { + /// Creates an instance of MinMaxValues. + AxesRange(this.xMinimum, this.xMaximum, this.yMinimum, this.yMaximum); + + /// Holds the x axis minimum value. + num xMinimum; + + /// Holds the x axis maximum value. + num xMaximum; + + /// Holds the y axis minimum value. + num yMinimum; + + /// Holds the y axis maximum value. + num yMaximum; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart new file mode 100644 index 000000000..5182822ec --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart @@ -0,0 +1,528 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../axis/axis.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the [FastLineSeries]. +/// +/// [FastLineSeries] is a line chart, but it loads faster than [LineSeries]. +/// +/// You can use this when there are large number of points to be loaded +/// in a chart. To render a fast line chart, +/// create an instance of [FastLineSeries], and add it to the series collection +/// property of [SfCartesianChart]. +/// +/// The following properties are used to customize the appearance +/// of fast line segment: +/// +/// * color - Changes the color of the line. +/// * opacity - Controls the transparency of the chart series. +/// * width - Changes the stroke width of the line. +class FastLineSeries extends XyDataSeries { + /// Creating an argument constructor of FastLineSeries class. + const FastLineSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.color, + double width = 2, + super.dashArray, + super.gradient, + super.markerSettings, + super.emptyPointSettings, + super.trendlines, + super.dataLabelSettings, + super.sortingOrder, + super.initialIsVisible, + super.name, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }) : super(borderWidth: width); + + /// Create the fastline series renderer. + @override + FastLineSeriesRenderer createRenderer() { + FastLineSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as FastLineSeriesRenderer; + return seriesRenderer; + } + return FastLineSeriesRenderer(); + } +} + +/// Creates series renderer for fastline series. +class FastLineSeriesRenderer extends XyDataSeriesRenderer + with ContinuousSeriesMixin { + /// Calling the default constructor of FastLineSeriesRenderer class. + FastLineSeriesRenderer(); + + @override + double legendIconBorderWidth() { + return 3; + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as FastLineSegment + ..series = this + .._xValues = xValues + .._yValues = yValues; + } + + @override + int dataPointIndex(Offset position, ChartSegment segment) { + final FastLineSegment fastLineSegment = segment as FastLineSegment; + final int nearestPointIndex = fastLineSegment._findNearestPoint( + fastLineSegment.points, + position, + ); + final int pointIndex = fastLineSegment._drawIndexes[nearestPointIndex]; + if (pointIndex != -1) { + return super.actualPointIndex(pointIndex); + } + return pointIndex; + } + + /// Creates a segment for a data point in the series. + @override + FastLineSegment createSegment() => FastLineSegment(); + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + final Color fastlineColor = color ?? paletteColor; + updateSegmentColor(segment, fastlineColor, borderWidth); + updateSegmentGradient( + segment, + gradientBounds: paintBounds, + borderGradient: gradient, + ); + } + + @override + ShapeMarkerType effectiveLegendIconType() => + dashArray != null && !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.fastLineSeriesWithDashArray + : ShapeMarkerType.fastLineSeries; + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +// Creates the segments for fast line series. +/// +/// This generates the fast line series points and +/// has the [calculateSegmentPoints] method overridden to customize +/// the fast line segment point calculation. +/// +/// Gets the path and color from the `series`. +class FastLineSegment extends ChartSegment { + late FastLineSeriesRenderer series; + late List _xValues; + late List _yValues; + final List _drawIndexes = []; + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + if (series.canFindLinearVisibleIndexes) { + _linearPoints(); + } else { + _nonLinearPoints(); + } + } + + void _linearPoints() { + final RenderChartAxis xAxis = series.xAxis!; + final RenderChartAxis yAxis = series.yAxis!; + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + + final List visibleIndexes = series.visibleIndexes; + final int length = visibleIndexes.length; + final int start = visibleIndexes[0]; + final int end = visibleIndexes[length - 1]; + final num xTol = (xAxis.visibleRange!.delta / series.size.width).abs(); + final num yTol = (yAxis.visibleRange!.delta / series.size.height).abs(); + + final num startX = _xValues[start]; + final num startY = _yValues[start]; + final double startXValue = transformX(startX, startY); + final double startYValue = transformY(startX, startY); + + points.add(Offset(startXValue, startYValue)); + _drawIndexes.add(start); + + num previousX = startX; + num previousY = startY; + for (int i = start; i <= end; i++) { + final num currentX = _xValues[i]; + if (currentX.isNaN) { + if (canDrop) { + continue; + } + previousX = xAxis.visibleRange!.minimum; + } + final num currentY = _yValues[i]; + if (currentY.isNaN) { + if (canDrop) { + continue; + } + previousY = yAxis.visibleRange!.minimum; + } + if ((previousX - currentX).abs() >= xTol || + (previousY - currentY).abs() >= yTol) { + final double currentXValue = transformX(currentX, currentY); + final double currentYValue = transformY(currentX, currentY); + points.add(Offset(currentXValue, currentYValue)); + _drawIndexes.add(i); + previousX = currentX; + previousY = currentY; + } + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + void _nonLinearPoints() { + if (_xValues.isEmpty || _yValues.isEmpty) { + return; + } + + final RenderChartAxis xAxis = series.xAxis!; + final RenderChartAxis yAxis = series.yAxis!; + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + + final num xTol = (xAxis.visibleRange!.delta / series.size.width).abs(); + final num yTol = (yAxis.visibleRange!.delta / series.size.height).abs(); + + final List visibleIndexes = series.visibleIndexes; + final num startX = _xValues[0]; + final num startY = _yValues[0]; + num previousX = startX.isNaN ? 0 : startX; + num previousY = startY.isNaN ? 0 : startY; + + points.add(Offset(transformX(startX, startY), transformY(startX, startY))); + _drawIndexes.add(0); + + for (final int i in visibleIndexes) { + num currentX = _xValues[i]; + if (currentX.isNaN) { + if (canDrop) { + continue; + } + currentX = xAxis.visibleRange!.minimum; + } + num currentY = _yValues[i]; + if (currentY.isNaN) { + if (canDrop) { + continue; + } + currentY = yAxis.visibleRange!.minimum; + } + + if ((previousX - currentX).abs() >= xTol || + (previousY - currentY).abs() >= yTol) { + final double currentXValue = transformX(currentX, currentY); + final double currentYValue = transformY(currentX, currentY); + points.add(Offset(currentXValue, currentYValue)); + _drawIndexes.add(i); + previousX = currentX; + previousY = currentY; + } + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num y = chartPoint.y!; + final double dx = series.pointToPixelX(x, y); + final double dy = series.pointToPixelY(x, y); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![currentSegmentIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final int nearestPointIndex = _findNearestPoint(points, position); + if (nearestPointIndex != -1) { + final Offset preferredPos = points[nearestPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[nearestPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestPoint(List points, Offset position) { + double delta = 0; + num? nearPointX; + num? nearPointY; + int? pointIndex; + final int length = points.length; + for (int i = 0; i < length; i++) { + nearPointX ??= + series.isTransposed + ? series.xAxis!.visibleRange!.minimum + : points[0].dx; + nearPointY ??= + series.isTransposed + ? points[0].dy + : series.yAxis!.visibleRange!.minimum; + + final num touchXValue = position.dx; + final num touchYValue = position.dy; + final double curX = points[i].dx; + final double curY = points[i].dy; + + if (series.isTransposed) { + if (delta == touchYValue - curY) { + pointIndex = i; + } else if ((touchYValue - curY).abs() <= + (touchYValue - nearPointY).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchYValue - curY; + pointIndex = i; + } + } else { + if (delta == touchXValue - curX) { + pointIndex = i; + } else if ((touchXValue - curX).abs() <= + (touchXValue - nearPointX).abs()) { + nearPointX = curX; + nearPointY = curY; + delta = touchXValue - curX; + pointIndex = i; + } + } + } + return pointIndex ?? -1; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => getStrokePaint(); + + /// Gets the stroke color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty) { + return; + } + + final Paint paint = getStrokePaint(); + final List? dashedArray = series.dashArray; + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + if (dashedArray != null || series.emptyPointIndexes.isNotEmpty) { + drawDashesFromPoints(canvas, points, dashedArray, paint); + } else { + canvas.drawPoints(PointMode.polygon, points, paint); + } + } + } + + @override + void dispose() { + points.clear(); + _drawIndexes.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart new file mode 100644 index 000000000..06000c32f --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart @@ -0,0 +1,1801 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +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/theme.dart'; + +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/element_widget.dart'; +import '../common/funnel_data_label.dart'; +import '../common/legend.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders Funnel series. +/// +/// The FunnelSeries is the SfFunnelChart Type series. +/// To render a funnel chart, create an instance of FunnelSeries, and add it to +/// the series property of [SfFunnelChart]. +/// +/// Provides options to customize the [opacity], [borderWidth], [borderColor] +/// and [pointColorMapper] of the funnel segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} +class FunnelSeries extends ChartSeries { + /// Creating an argument constructor of [FunnelSeries] class. + const FunnelSeries({ + super.key, + this.onCreateRenderer, + this.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.dataSource, + super.xValueMapper, + super.emptyPointSettings, + super.dataLabelSettings, + this.yValueMapper, + super.pointColorMapper, + ChartValueMapper? textFieldMapper, + super.name, + this.explodeIndex, + this.neckWidth = '20%', + this.neckHeight = '20%', + this.height = '80%', + this.width = '80%', + this.gapRatio = 0, + this.explodeOffset = '10%', + this.explode = false, + this.explodeGesture = ActivationMode.singleTap, + this.borderColor = Colors.transparent, + super.borderWidth, + super.legendIconType, + super.animationDuration, + super.animationDelay, + super.opacity, + super.selectionBehavior, + super.initialSelectedDataIndexes, + }) : super(dataLabelMapper: textFieldMapper); + + /// Maps the field name, which will be considered as y-values. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// yValueMapper: (ChartData data, _) => data.yVal, + /// ) + /// ) + /// ); + /// } + /// class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + /// } + /// ``` + final ChartValueMapper? yValueMapper; + + /// Neck height of funnel. + /// + /// Defaults to `20%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// neckHeight: '10%' + /// ) + /// ) + /// ); + /// } + /// ``` + final String neckHeight; + + /// Neck width of funnel. + /// + /// Defaults to `20%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// neckWidth: '10%' + /// ) + /// ) + /// ); + /// } + /// ``` + final String neckWidth; + + /// Height of the series. + /// + /// Defaults to `80%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// height:'50%' + /// ) + /// ) + /// ); + /// } + /// ``` + final String height; + + /// Width of the series. + /// + /// Defaults to `80%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// width:'50%' + /// ) + /// ) + /// ); + /// } + /// ``` + final String width; + + /// Gap ratio between the segments of funnel. + /// + /// Ranges from 0 to 1. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// gapRatio: 0.3 + /// ) + /// ) + /// ); + /// } + /// ``` + final double gapRatio; + + /// Offset of exploded slice. + /// + /// The value ranges from 0% to 100%. + /// + /// Defaults to `10%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explodeOffset: '5%') + /// ) + /// ); + /// } + /// ``` + final String explodeOffset; + + /// Enables or disables the explode of slices on tap. + /// + /// Default to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explode: true) + /// ) + /// ); + /// } + /// ``` + final bool explode; + + /// Gesture for activating the explode. + /// + /// Explode can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, `ActivationMode.doubleTap`, + /// and `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explode: true, + /// explodeGesture: ActivationMode.singleTap + /// ) + /// ) + /// ); + /// } + /// ``` + final ActivationMode explodeGesture; + + final Color borderColor; + + /// Index of the slice to explode it at the initial rendering. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explodeIndex: 1, + /// explode: true + /// ) + /// ) + /// ); + /// } + /// ``` + final num? explodeIndex; + + /// Used to create the renderer for custom series. + /// + /// This is applicable only when the custom series is defined in the sample + /// and for built-in series types, it is not applicable. + /// + /// Renderer created in this will hold the series state and + /// this should be created for each series. [onCreateRenderer] callback + /// function should return the renderer class and should not return null. + /// + /// Series state will be created only once per series and will not be created + /// again when we update the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// onCreateRenderer: (ChartSeries series) { + /// return CustomFunnelSeriesRenderer( + /// series as FunnelSeries); + /// }, + /// ), + /// ) + /// ); + /// } + /// class CustomFunnelSeriesRenderer extends FunnelSeriesRenderer { + /// CustomFunnelSeriesRenderer(this.series); + /// final FunnelSeries series; + /// // custom implementation here... + /// } + /// ``` + final ChartSeriesRendererFactory? onCreateRenderer; + + /// Triggers when the series renderer is created. + /// + /// Using this callback, able to get the [ChartSeriesController] instance, + /// which is used to access the public methods in the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// FunnelSeriesController _funnelSeriesController; + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// onRendererCreated: (FunnelSeriesController controller) { + /// _funnelSeriesController = controller; + /// }, + /// ), + /// ) + /// ); + /// } + /// ``` + final FunnelSeriesRendererCreatedCallback? onRendererCreated; + + @override + List get positions => [ + ChartDataPointType.y, + ]; + + @override + Widget? childForSlot(SeriesSlot slot) { + switch (slot) { + case SeriesSlot.dataLabel: + return dataLabelSettings.isVisible + ? FunnelDataLabelContainer( + series: this, + dataSource: dataSource!, + mapper: dataLabelMapper, + builder: dataLabelSettings.builder, + settings: dataLabelSettings, + ) + : null; + + case SeriesSlot.marker: + return null; + + case SeriesSlot.trendline: + return null; + } + } + + @override + FunnelSeriesRenderer createRenderObject(BuildContext context) { + final FunnelSeriesRenderer renderer = + super.createRenderObject(context) as FunnelSeriesRenderer; + renderer + ..yValueMapper = yValueMapper + ..neckHeight = neckHeight + ..neckWidth = neckWidth + ..height = height + ..width = width + ..gapRatio = gapRatio + ..explodeOffset = explodeOffset + ..explode = explode + ..explodeGesture = explodeGesture + ..dataLabelMapper = dataLabelMapper + ..explodeIndex = explodeIndex + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..borderColor = borderColor + ..onCreateRenderer = onCreateRenderer + ..onRendererCreated = onRendererCreated + ..widget = this; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + FunnelSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..yValueMapper = yValueMapper + ..neckHeight = neckHeight + ..neckWidth = neckWidth + ..height = height + ..width = width + ..gapRatio = gapRatio + ..explodeOffset = explodeOffset + ..explode = explode + ..explodeGesture = explodeGesture + ..dataLabelMapper = dataLabelMapper + ..explodeIndex = explodeIndex + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..borderColor = borderColor + ..onCreateRenderer = onCreateRenderer + ..onRendererCreated = onRendererCreated + ..widget = this; + } + + /// Create the funnel series renderer. + @override + FunnelSeriesRenderer createRenderer() { + FunnelSeriesRenderer? renderer; + if (onCreateRenderer != null) { + renderer = onCreateRenderer!(this) as FunnelSeriesRenderer?; + assert( + renderer != null, + 'This onCreateRenderer callback function should return value as ' + 'extends from ChartSeriesRenderer class and should not be return ' + 'value as null', + ); + } + return renderer ?? FunnelSeriesRenderer(); + } +} + +/// Creates series renderer for Funnel series. +class FunnelSeriesRenderer extends ChartSeriesRenderer + with RealTimeUpdateMixin { + final List _chaoticYValues = []; + final List yValues = []; + + FunnelSeriesRendererCreatedCallback? onRendererCreated; + + FunnelSeriesController get controller => _controller; + late final FunnelSeriesController _controller = + FunnelSeriesController(this); + + ChartValueMapper? get yValueMapper => _yValueMapper; + ChartValueMapper? _yValueMapper; + set yValueMapper(ChartValueMapper? value) { + if (_yValueMapper != value) { + _yValueMapper = value; + } + } + + String get neckHeight => _neckHeight; + String _neckHeight = '20%'; + set neckHeight(String value) { + if (_neckHeight != value) { + _neckHeight = value; + markNeedsLayout(); + } + } + + String get neckWidth => _neckWidth; + String _neckWidth = '20%'; + set neckWidth(String value) { + if (_neckWidth != value) { + _neckWidth = value; + markNeedsLayout(); + } + } + + String get height => _height; + String _height = '80%'; + set height(String value) { + if (_height != value) { + _height = value; + markNeedsLayout(); + } + } + + String get width => _width; + String _width = '80%'; + set width(String value) { + if (_width != value) { + _width = value; + markNeedsLayout(); + } + } + + double get gapRatio => _gapRatio; + double _gapRatio = 0; + set gapRatio(double value) { + if (_gapRatio != value) { + _gapRatio = value; + assert( + _gapRatio >= 0 && _gapRatio <= 1, + 'The gap ratio for the funnel chart must be between 0 and 1.', + ); + _gapRatio = clampDouble(value, 0, 1); + markNeedsLayout(); + } + } + + String get explodeOffset => _explodeOffset; + String _explodeOffset = '10%'; + set explodeOffset(String value) { + if (_explodeOffset != value) { + _explodeOffset = value; + transformValues(); + markNeedsPaint(); + } + } + + bool get explode => _explode; + bool _explode = false; + set explode(bool value) { + if (_explode != value) { + _explode = value; + _updateExploding(); + } + } + + ActivationMode get explodeGesture => _explodeGesture; + ActivationMode _explodeGesture = ActivationMode.singleTap; + set explodeGesture(ActivationMode value) { + if (_explodeGesture != value) { + _explodeGesture = value; + transformValues(); + markNeedsPaint(); + } + } + + num? get explodeIndex => _explodeIndex; + num? _explodeIndex; + set explodeIndex(num? value) { + if (_explodeIndex != value) { + _explodeIndex = value; + _updateExploding(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + ChartSeriesRendererFactory? get onCreateRenderer => _onCreateRenderer; + ChartSeriesRendererFactory? _onCreateRenderer; + set onCreateRenderer(ChartSeriesRendererFactory? value) { + if (_onCreateRenderer != value) { + _onCreateRenderer = value; + } + } + + @override + Iterable get children { + return [if (dataLabelContainer != null) dataLabelContainer!]; + } + + /// Stores pointer down time to determine whether a long press interaction is handled at pointer up + DateTime? _pointerHoldingTime; + double _sumOfY = 0.0; + Rect _plotAreaBounds = Rect.zero; + Size _triangleSize = Size.zero; + Size _neckSize = Size.zero; + double _coefficient = 0.0; + double _spacing = 0.0; + double _y = 0.0; + + Offset dataLabelPosition(ChartElementParentData current, Size size) { + final int segmentsLastIndex = segments.length - 1; + final List points = + segments[segmentsLastIndex - current.dataPointIndex].points; + final Rect region = _calculateSegmentRect( + segments[segmentsLastIndex - current.dataPointIndex].points, + segmentsLastIndex - current.dataPointIndex, + ); + final List connectorPoints = segments[0].points; + final double connectorLength = _calculateConnectorLength( + connectorPoints, + dataLabelSettings, + ); + + final Offset insideLabelOffset = + dataLabelSettings.labelPosition == ChartDataLabelPosition.inside + ? Offset( + region.left + region.width / 2 - size.width / 2, + region.top + region.height / 2 - size.height / 2, + ) + : Offset( + (points[0].dx + points[1].dx) / 2 - size.width / 2, + (points[0].dy + points[2].dy) / 2 - size.height / 2, + ); + final double connectorLengthWithXValue = + insideLabelOffset.dx + connectorLength + size.width / 2; + final Offset outsideLabelOffset = Offset( + (connectorLengthWithXValue + + size.width + + dataLabelSettings.margin.left + + dataLabelSettings.margin.right > + paintBounds.right) + ? connectorLengthWithXValue - + (percentToValue(explodeOffset, paintBounds.width)!) + : connectorLengthWithXValue, + insideLabelOffset.dy, + ); + final Offset finalOffset = + dataLabelSettings.labelPosition == ChartDataLabelPosition.inside + ? insideLabelOffset + : outsideLabelOffset; + + return finalOffset; + } + + bool _isFunnelLabelIntersectOutside(RRect rect, RRect? previousRect) { + bool isIntersect = false; + const num padding = 2; + if (rect == previousRect) { + return false; + } + if (previousRect != null && (rect.bottom + padding) > previousRect.top) { + isIntersect = true; + } + return isIntersect; + } + + bool _isFunnelLabelIntersectInside(int index, List list) { + final Rect currentRect = list[index]; + Rect nextRect; + bool isCollide = false; + if (index != list.length - 1) { + nextRect = list[index + 1]; + isCollide = currentRect.overlaps(nextRect); + } + return isCollide; + } + + Path _calculateConnectorPath(int index, Offset offset, Size size) { + final int segmentsLastIndex = segments.length - 1; + final List points = segments[segmentsLastIndex - index].points; + final double startPoint = (points[1].dx + points[2].dx) / 2; + final double endPoint = offset.dx; + final double y = offset.dy + size.height / 2; + return Path() + ..moveTo(startPoint, y) + ..lineTo(endPoint, y); + } + + RRect _calculateRect( + Offset offset, + int padding, + EdgeInsets margin, + Size size, + ChartDataLabelPosition labelPosition, + ) { + if (labelPosition == ChartDataLabelPosition.inside) { + return RRect.fromRectAndRadius( + Rect.fromLTWH( + offset.dx - padding, + offset.dy - padding, + size.width + (2 * padding), + size.height + (2 * padding), + ), + Radius.circular(dataLabelSettings.borderRadius), + ); + } + return RRect.fromRectAndRadius( + Rect.fromLTWH( + offset.dx, + offset.dy - margin.top, + size.width + margin.top + margin.left, + size.height + margin.bottom + margin.right, + ), + Radius.circular(dataLabelSettings.borderRadius), + ); + } + + Rect _calculateSegmentRect(List points, int index) { + final List points = segments[index].points; + final int bottom = + points.length > 4 ? points.length - 2 : points.length - 1; + final double x = (points[0].dx + points[bottom].dx) / 2; + final double right = (points[1].dx + points[bottom - 1].dx) / 2; + final Rect region = Rect.fromLTWH( + x, + points[0].dy, + right - x, + points[bottom].dy - points[0].dy, + ); + return region; + } + + double _calculateConnectorLength( + List points, + DataLabelSettings settings, + ) { + final Path segmentPath = + Path() + ..moveTo(points[0].dx, points[0].dy) + ..lineTo(points[1].dx, points[1].dy) + ..lineTo(points[2].dx, points[2].dy) + ..lineTo(points[3].dx, points[3].dy) + ..close(); + final Rect segmentBounds = segmentPath.getBounds(); + final double connectorLength = + percentToValue( + settings.connectorLineSettings.length ?? '0%', + _plotAreaBounds.width / 2, + )! + + (segmentBounds.width / 2); + return connectorLength; + } + + bool _findingCollision(Rect rect, List regions, [Rect? pathRect]) { + bool isCollide = false; + if (pathRect != null && + (pathRect.left < rect.left && + pathRect.width > rect.width && + pathRect.top < rect.top && + pathRect.height > rect.height)) { + isCollide = false; + } else if (pathRect != null) { + isCollide = true; + } + for (int i = 0; i < regions.length; i++) { + final RRect regionRect = regions[i]; + if ((rect.left < regionRect.left + regionRect.width && + rect.left + rect.width > regionRect.left) && + (rect.top < regionRect.top + regionRect.height && + rect.top + rect.height > regionRect.top)) { + isCollide = true; + break; + } + } + return isCollide; + } + + String _getTrimmedText( + String? text, + Rect rect, + Offset labelLocation, + Rect region, + TextStyle style, + ) { + const int labelPadding = 2; + const String ellipse = '...'; + String label = text!; + bool isCollide = _findingCollision(rect, [], region); + while (isCollide) { + if (label == ellipse) { + label = ''; + break; + } + if (label.length > ellipse.length) { + label = _addEllipse(label, label.length, ellipse); + } else { + label = ''; + break; + } + final Size trimTextSize = measureText(label, style); + final Rect trimRect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + trimTextSize.width + (2 * labelPadding), + trimTextSize.height + (2 * labelPadding), + ); + isCollide = _isLabelsColliding(trimRect, region); + } + return label == ellipse ? '' : label; + } + + String _addEllipse( + String text, + int maxLength, + String ellipse, { + bool isRtl = false, + }) { + if (isRtl) { + if (text.contains(ellipse)) { + text = text.replaceAll(ellipse, ''); + text = text.substring(1, text.length); + } else { + text = text.substring(ellipse.length, text.length); + } + return ellipse + text; + } else { + maxLength--; + final int length = maxLength - ellipse.length; + final String trimText = text.substring(0, length); + return trimText + ellipse; + } + } + + bool _isLabelsColliding(Rect rect, Rect? pathRect) { + bool isCollide = false; + if (pathRect != null && + (pathRect.left > rect.left && + pathRect.width > rect.width && + pathRect.top < rect.top && + pathRect.height > rect.height)) { + isCollide = false; + } else if (pathRect != null) { + isCollide = true; + } + return isCollide; + } + + void drawDataLabelWithBackground( + int index, + Canvas canvas, + String dataLabel, + Size size, + Offset offset, + int angle, + TextStyle style, + Paint fillPaint, + Paint strokePaint, + Path connectorPath, + List previousRect, + ) { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + final int segmentsLastIndex = segments.length - 1; + final int dataPointIndex = segmentsLastIndex - index; + final ChartSegment segment = segments[dataPointIndex]; + Color surfaceColor = dataLabelSurfaceColor( + fillPaint.color, + dataPointIndex, + dataLabelSettings.labelPosition, + chartThemeData, + themeData, + segment, + ); + TextStyle effectiveTextStyle = saturatedTextStyle(surfaceColor, style); + final EdgeInsets margin = dataLabelSettings.margin; + final Radius radius = Radius.circular(dataLabelSettings.borderRadius); + Offset finalOffset = offset; + final List points = segments[segmentsLastIndex - index].points; + bool isOverlapRight = false; + final double startPoint = (points[1].dx + points[2].dx) / 2; + ChartDataLabelPosition labelPosition = dataLabelSettings.labelPosition; + const int labelPadding = 2; + const int connectorPadding = 15; + final List labels = []; + final List connectorPoints = segments[0].points; + final double connectorLength = _calculateConnectorLength( + connectorPoints, + dataLabelSettings, + ); + final Paint connectorPaint = + Paint() + ..color = + dataLabelSettings.connectorLineSettings.color ?? + segments[segmentsLastIndex - index].fillPaint.color + ..strokeWidth = dataLabelSettings.connectorLineSettings.width + ..style = PaintingStyle.stroke; + + if ((yValues[index] == 0 && !dataLabelSettings.showZeroValue) || + yValues[index].isNaN || + !segments[segmentsLastIndex - index].isVisible) { + return; + } + + for (int i = segmentsLastIndex; i >= 0; i--) { + final Rect region = _calculateSegmentRect(segmentAt(i).points, i); + labels.add( + Rect.fromLTWH( + region.left + region.width / 2 - (size.width / 2) - labelPadding, + region.top + region.height / 2 - (size.height / 2) - labelPadding, + size.width + (2 * labelPadding), + size.height + (2 * labelPadding), + ), + ); + } + + if (!offset.dx.isNaN && !offset.dy.isNaN) { + if (dataLabel.isNotEmpty) { + if (fillPaint.color != Colors.transparent || + (strokePaint.color != const Color.fromARGB(0, 25, 5, 5) && + strokePaint.strokeWidth > 0)) { + RRect labelRect = _calculateRect( + offset, + labelPadding, + margin, + size, + labelPosition, + ); + final Rect region = _calculateSegmentRect( + segments[segmentsLastIndex - index].points, + segmentsLastIndex - index, + ); + final bool isDataLabelCollide = + (_findingCollision(labels[index], previousRect, region)) && + dataLabelSettings.labelPosition != ChartDataLabelPosition.outside; + if (isDataLabelCollide) { + switch (dataLabelSettings.overflowMode) { + case OverflowMode.trim: + dataLabel = _getTrimmedText( + dataLabel, + labels[index], + finalOffset, + region, + effectiveTextStyle, + ); + final Size trimSize = measureText( + dataLabel, + effectiveTextStyle, + ); + finalOffset = Offset( + finalOffset.dx + size.width / 2 - trimSize.width / 2, + finalOffset.dy + size.height / 2 - trimSize.height / 2, + ); + labelRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + finalOffset.dx - labelPadding, + finalOffset.dy - labelPadding, + trimSize.width + (2 * labelPadding), + trimSize.height + (2 * labelPadding), + ), + radius, + ); + break; + case OverflowMode.hide: + dataLabel = ''; + break; + case OverflowMode.shift: + break; + // ignore: no_default_cases + default: + break; + } + } + final bool isIntersect = _isFunnelLabelIntersectInside(index, labels); + if (isIntersect && + dataLabelSettings.labelPosition == + ChartDataLabelPosition.inside && + dataLabelSettings.color == null && + !dataLabelSettings.useSeriesColor) { + if (style.color == Colors.transparent) { + surfaceColor = dataLabelSurfaceColor( + fillPaint.color, + dataPointIndex, + ChartDataLabelPosition.outside, + chartThemeData, + themeData, + segment, + ); + effectiveTextStyle = saturatedTextStyle(surfaceColor, style); + } + } + if ((isIntersect && + labelPosition == ChartDataLabelPosition.inside && + dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide) || + dataLabel == '' || + (index == 0 && + (dataLabelSettings.overflowMode == OverflowMode.hide || + dataLabelSettings.overflowMode == OverflowMode.trim))) { + return; + } else if (isIntersect && + labelPosition == ChartDataLabelPosition.inside && + dataLabelSettings.labelIntersectAction == + LabelIntersectAction.none) { + } else if ((isDataLabelCollide && + dataLabelSettings.overflowMode == OverflowMode.shift) || + isIntersect || + (dataLabelSettings.labelPosition == + ChartDataLabelPosition.outside)) { + labelPosition = ChartDataLabelPosition.outside; + offset = + dataLabelSettings.labelPosition == ChartDataLabelPosition.inside + ? Offset( + (points[0].dx + points[1].dx) / 2 - size.width / 2, + (points[0].dy + points[2].dy) / 2 - size.height / 2, + ) + : offset; + finalOffset = + dataLabelSettings.labelPosition == + ChartDataLabelPosition.outside + ? offset + : offset + Offset(connectorLength + size.width / 2, 0); + labelRect = _calculateRect( + finalOffset, + labelPadding, + margin, + size, + labelPosition, + ); + connectorPath = _calculateConnectorPath(index, finalOffset, size); + if (_plotAreaBounds.right < labelRect.right) { + isOverlapRight = true; + labelRect = RRect.fromRectAndRadius( + Rect.fromLTRB( + _plotAreaBounds.right - labelRect.width - labelPadding, + labelRect.top, + _plotAreaBounds.right - labelPadding, + labelRect.bottom, + ), + radius, + ); + } + final RRect previous = + previousRect.isEmpty + ? labelRect + : previousRect[previousRect.length - 1]; + final bool isIntersectOutside = _isFunnelLabelIntersectOutside( + labelRect, + previous, + ); + if (!isIntersectOutside && isOverlapRight) { + finalOffset = Offset( + labelRect.left, + (labelRect.top + labelRect.height / 2) - size.height / 2, + ); + connectorPath = _calculateConnectorPath(index, finalOffset, size); + } + if (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide && + isIntersectOutside) { + return; + } else if (isIntersectOutside && + dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + labelRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + labelRect.left, + previous.top - labelPadding - labelRect.height, + labelRect.width, + labelRect.height, + ), + radius, + ); + + connectorPath = + Path() + ..moveTo(startPoint, finalOffset.dy + size.height / 2) + ..lineTo( + labelRect.left - connectorPadding, + finalOffset.dy + size.height / 2, + ) + ..lineTo( + labelRect.left, + labelRect.top + labelRect.height / 2, + ); + } + finalOffset = Offset( + labelRect.left + margin.left, + (labelRect.top + labelRect.height / 2) - size.height / 2, + ); + } + previousRect.add(labelRect); + if (labelRect.top < dataLabelSettings.margin.top) { + return; + } + if (labelPosition == ChartDataLabelPosition.outside) { + canvas.drawPath(connectorPath, connectorPaint); + } + canvas.save(); + canvas.translate(labelRect.center.dx, labelRect.center.dy); + canvas.rotate((angle * pi) / 180); + canvas.translate(-labelRect.center.dx, -labelRect.center.dy); + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + canvas.drawRRect(labelRect, strokePaint); + } + if (fillPaint.color != Colors.transparent) { + canvas.drawRRect(labelRect, fillPaint); + } + canvas.restore(); + } + } + } + drawDataLabel(canvas, dataLabel, finalOffset, effectiveTextStyle, angle); + } + + void drawDataLabel( + Canvas canvas, + String text, + Offset point, + TextStyle style, + int angle, [ + bool? isRtl, + ]) { + final int maxLines = getMaxLinesContent(text); + final TextSpan span = TextSpan(text: text, style: style); + final TextPainter tp = TextPainter( + text: span, + textDirection: (isRtl ?? false) ? TextDirection.rtl : TextDirection.ltr, + textAlign: TextAlign.center, + maxLines: maxLines, + ); + tp.layout(); + canvas.save(); + canvas.translate(point.dx + tp.width / 2, point.dy + tp.height / 2); + Offset labelOffset = Offset.zero; + canvas.rotate(degreeToRadian(angle)); + labelOffset = Offset(-tp.width / 2, -tp.height / 2); + tp.paint(canvas, labelOffset); + canvas.restore(); + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(_controller); + super.attach(owner); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + yValues.clear(); + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + markNeedsLegendUpdate(); + populateChartPoints(); + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[yValues]; + positions = [ChartDataPointType.y]; + } else { + yLists.add(yValues); + positions!.add(ChartDataPointType.y); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + @override + void performLayout() { + // Need to recalculate the triangle and neck size values based on plot area, + // and its required to update the segment values in layout. + // So assigned true here to update the segment values. + canUpdateOrCreateSegments = true; + _calculateFunnelValues(); + super.performLayout(); + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[yValues] + ..animation = dataLabelAnimation + ..layout(constraints); + } + } + + void _calculateFunnelValues() { + _sumOfY = 0; + final int segmentsCount = segments.length; + for (int i = 0; i < dataCount; i++) { + bool isVisible = true; + if (i < segmentsCount) { + isVisible = segmentAt(i).isVisible; + } + final num yValue = isVisible ? yValues[i] : 0; + if (!yValue.isNaN) { + _sumOfY += yValue.abs(); + } + } + + _plotAreaBounds = Rect.fromLTWH(0, 0, size.width, size.height); + _triangleSize = Size( + percentToValue(width, _plotAreaBounds.width)!, + percentToValue(height, _plotAreaBounds.height)!, + ); + _neckSize = Size( + percentToValue(neckWidth, _plotAreaBounds.width)!, + percentToValue(neckHeight, _plotAreaBounds.height)!, + ); + _coefficient = 1 / (_sumOfY * (1 + gapRatio / (1 - gapRatio))); + _spacing = gapRatio / (dataCount - emptyPointIndexes.length - 1); + _y = 0; + } + + @override + @nonVirtual + Color effectiveColor(int segmentIndex) { + Color? pointColor; + if (pointColorMapper != null) { + pointColor = pointColors[segmentIndex]; + } + return pointColor ?? palette[segmentIndex % palette.length]; + } + + @override + void setData(int index, ChartSegment segment) { + final int i = dataCount - 1 - index; + super.setData(i, segment); + + num yValue = yValues[i].abs(); + // Handled the empty point here. + yValue = yValue.isNaN || !segment.isVisible ? 0 : yValue; + final num segmentHeight = _coefficient * yValue; + segment as FunnelSegment + ..series = this + ..y = _y + .._height = segmentHeight + .._triangleSize = _triangleSize + .._neckSize = _neckSize + .._plotAreaBounds = _plotAreaBounds + ..isExploded = explode && i == explodeIndex + ..isEmpty = + (emptyPointSettings.mode != EmptyPointMode.drop && + emptyPointSettings.mode != EmptyPointMode.gap) && + isEmpty(i); + + _y += segmentHeight + _spacing; + } + + @override + FunnelSegment createSegment() => FunnelSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.funnelSeries; + + /// Changes the series color, border color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, borderColor, borderWidth); + } + + @override + List? buildLegendItems(int index) { + final List legendItems = []; + final int segmentsCount = segments.length; + for (int i = 0; i < dataCount; i++) { + final int legendIndex = dataCount - 1 - i; + final FunnelLegendItem legendItem = FunnelLegendItem( + text: xRawValues[legendIndex].toString(), + iconType: toLegendShapeMarkerType(legendIconType, this), + iconColor: effectiveColor(legendIndex), + iconBorderWidth: legendIconBorderWidth(), + series: this, + seriesIndex: index, + pointIndex: legendIndex, + imageProvider: + legendIconType == LegendIconType.image + ? parent?.legend?.image + : null, + isToggled: i < segmentsCount && !segmentAt(i).isVisible, + onTap: handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ); + legendItems.add(legendItem); + } + return legendItems; + } + + void _handleLegendItemCreated(ItemRendererDetails details) { + if (parent != null && parent!.onLegendItemRender != null) { + final FunnelLegendItem item = details.item as FunnelLegendItem; + final LegendIconType iconType = toLegendIconType(details.iconType); + final LegendRenderArgs args = + LegendRenderArgs(item.seriesIndex, item.pointIndex) + ..text = details.text + ..legendIconType = iconType + ..color = details.color; + parent!.onLegendItemRender!(args); + if (args.legendIconType != iconType) { + details.iconType = toLegendShapeMarkerType( + args.legendIconType ?? LegendIconType.seriesType, + this, + ); + } + details + ..text = args.text ?? '' + ..color = args.color ?? Colors.transparent; + } + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + super.handleLegendItemTapped(item, isToggled); + + final FunnelLegendItem legendItem = item as FunnelLegendItem; + final int toggledIndex = legendItem.pointIndex; + if (toggledIndex < segments.length) { + segmentAt(toggledIndex).isVisible = !isToggled; + } + + canUpdateOrCreateSegments = true; + legendItem.onToggled?.call(); + markNeedsLayout(); + } + + // TODO(praveen): When we update data points, load time animation occurs. + // Need to handle this. + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isTransposed: true, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintDataLabels(context, offset); + } + + void paintDataLabels(PaintingContext context, Offset offset) { + if (dataLabelContainer != null) { + context.paintChild(dataLabelContainer!, offset); + } + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + final bool isHit = super.hitTest(result, position: position); + return explode || isHit; + } + + @override + void handlePointerDown(PointerDownEvent details) { + if (explode && explodeGesture == ActivationMode.singleTap) { + _pointerHoldingTime = DateTime.now(); + } + super.handlePointerDown(details); + } + + @override + void handlePointerUp(PointerUpEvent details) { + if (explode && + explodeGesture == ActivationMode.singleTap && + _pointerHoldingTime != null && + DateTime.now().difference(_pointerHoldingTime!).inMilliseconds < + kLongPressTimeout.inMilliseconds) { + _handleExploding(details.localPosition); + } + super.handlePointerUp(details); + } + + @override + void handleDoubleTap(Offset position) { + final Offset localPosition = globalToLocal(position); + if (explode && explodeGesture == ActivationMode.doubleTap) { + _handleExploding(localPosition); + } + super.handleDoubleTap(position); + } + + @override + void handleLongPressStart(LongPressStartDetails details) { + if (explode && explodeGesture == ActivationMode.longPress) { + _handleExploding(details.localPosition); + } + super.handleLongPressStart(details); + } + + void _handleExploding(Offset position) { + for (final ChartSegment segment in segments) { + final FunnelSegment funnelSegment = segment as FunnelSegment; + if (segment.contains(position)) { + funnelSegment.isExploded = !funnelSegment.isExploded; + if (funnelSegment.isExploded) { + _explodeIndex = segment.currentSegmentIndex; + } else { + _explodeIndex = -1; + } + } else { + funnelSegment.isExploded = false; + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + void _updateExploding() { + for (final ChartSegment segment in segments) { + final FunnelSegment funnelSegment = segment as FunnelSegment; + if (!explode) { + funnelSegment.isExploded = false; + } else { + if (segment.currentSegmentIndex == explodeIndex) { + funnelSegment.isExploded = true; + } else { + funnelSegment.isExploded = false; + } + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + @override + ChartSegment segmentAt(int segmentPointIndex) { + return segments.firstWhere( + (ChartSegment segment) => + segment.currentSegmentIndex == segmentPointIndex, + ); + } + + @override + int viewportIndex(int index, [List? visibleIndexes]) { + return index; + } + + @override + void dispose() { + _chaoticYValues.clear(); + yValues.clear(); + super.dispose(); + } +} + +class FunnelSegment extends ChartSegment { + late FunnelSeriesRenderer series; + late double y; + late num _height; + late Rect _plotAreaBounds; + late Size _triangleSize; + late Size _neckSize; + bool isExploded = false; + Path path = Path(); + + @override + void transformValues() { + points.clear(); + + double? leftEnd, neckJoiner; + double rightEnd = 0; + final double neckSizeWidth = _neckSize.width; + final double neckSizeHeight = _neckSize.height; + final double triangleWidth = _triangleSize.width; + final double triangleHeight = _triangleSize.height; + + final double funnelHeight = triangleHeight - neckSizeHeight; + final double left = + (isExploded + ? percentToValue(series.explodeOffset, _plotAreaBounds.width)! + : 0) + + (_plotAreaBounds.width - triangleWidth) / 2; + final double topSpace = (_plotAreaBounds.height - triangleHeight) / 2; + + double topY = y * triangleHeight; + double bottomY = topY + _height * triangleHeight; + + double funnelWidth = + neckSizeWidth + + (triangleWidth - neckSizeWidth) * + ((funnelHeight - topY) / funnelHeight); + double topX1 = (triangleWidth / 2) - funnelWidth / 2; + double topX2 = topX1 + funnelWidth; + + funnelWidth = + (bottomY > funnelHeight || triangleHeight == neckSizeHeight) + ? neckSizeWidth + : neckSizeWidth + + (triangleWidth - neckSizeWidth) * + ((funnelHeight - bottomY) / funnelHeight); + double bottomX1 = triangleWidth / 2 - funnelWidth / 2; + double bottomX2 = bottomX1 + funnelWidth; + + if (topY >= funnelHeight) { + topX1 = bottomX1 = leftEnd = triangleWidth / 2 - neckSizeWidth / 2; + topX2 = bottomX2 = rightEnd = triangleWidth / 2 + neckSizeWidth / 2; + } else if (bottomY > funnelHeight) { + leftEnd = bottomX1 = triangleWidth / 2 - funnelWidth / 2; + rightEnd = bottomX2 = leftEnd + funnelWidth; + neckJoiner = funnelHeight; + } + + topY += topSpace; + bottomY += topSpace; + neckJoiner = (neckJoiner != null) ? (neckJoiner + topSpace) : null; + + points + ..add(Offset(left + topX1, topY)) + ..add(Offset(left + topX2, topY)); + + if (neckJoiner != null) { + points + ..add(Offset(left + rightEnd, neckJoiner)) + ..add(Offset(left + bottomX2, bottomY)) + ..add(Offset(left + bottomX1, bottomY)) + ..add(Offset(left + ((leftEnd != null) ? leftEnd : 0), neckJoiner)); + } else { + points + ..add(Offset(left + bottomX2, bottomY)) + ..add(Offset(left + bottomX1, bottomY)); + } + + path + ..reset() + ..moveTo(left + topX1, topY) + ..lineTo(left + topX2, topY); + + if (neckJoiner != null) { + path + ..lineTo(left + rightEnd, neckJoiner) + ..lineTo(left + bottomX2, bottomY) + ..lineTo(left + bottomX1, bottomY) + ..lineTo(left + ((leftEnd != null) ? leftEnd : 0), neckJoiner) + ..close(); + } else { + path + ..lineTo(left + bottomX2, bottomY) + ..lineTo(left + bottomX1, bottomY) + ..close(); + } + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Get the stroke color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of the segment. + @override + void calculateSegmentPoints() {} + + @override + bool contains(Offset position) { + return path.contains(position); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + final ChartPoint point = ChartPoint( + x: series.xRawValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + ); + final Offset location = path.getBounds().center; + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? series.localToGlobal(position ?? location) + : series.localToGlobal(location); + return ChartTooltipInfo( + primaryPosition: preferredPos, + secondaryPosition: preferredPos, + text: series.tooltipText(point), + header: '', + data: series.dataSource![currentSegmentIndex], + point: point, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + ); + } + + /// Draw segment in series bounds. + @override + void onPaint(Canvas canvas) { + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(path, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawPath(path, paint); + } + } + + @override + void dispose() { + path.reset(); + super.dispose(); + } +} + +/// 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. +class FunnelSeriesController { + /// Creating an argument constructor of FunnelSeriesController class. + FunnelSeriesController(this.seriesRenderer); + + /// Used to access the series properties. + /// + /// Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// onRendererCreated: + /// (FunnelSeriesController controller) { + /// _chartSeriesController = controller; + /// // prints series yAxisName + /// print(_chartSeriesController.seriesRenderer. + /// seriesRendererDetails.series.yAxisName); + /// }, + /// ), + /// ) + /// ); + ///} + ///``` + final FunnelSeriesRenderer seriesRenderer; + + /// Used to process only the newly added, updated and removed data points in + /// a series, instead of processing all the data points. + /// + /// To re-render the chart with modified data points, setState() will be + /// called. This will render the process and render the chart from scratch. + /// Thus, the app’s performance will be degraded on continuous update. + /// To overcome this problem, [updateDataSource] method can be called by + /// passing updated data points indexes. Chart will process only that point + /// and skip various steps like bounds calculation, old data points + /// processing, etc. Thus, this will improve the app’s performance. + /// + /// The following are the arguments of this method. + /// * addedDataIndexes – `List` type – Indexes of newly added data points + /// in the existing series. + /// * removedDataIndexes – `List` type – Indexes of removed data points + /// in the existing series. + /// * updatedDataIndexes – `List` type – Indexes of updated data points + /// in the existing series. + /// * addedDataIndex – `int` type – Index of newly added data point + /// in the existing series. + /// * removedDataIndex – `int` type – Index of removed data point + /// in the existing series. + /// * updatedDataIndex – `int` type – Index of updated data point + /// in the existing series. + /// + /// Returns `void`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// FunnelSeriesController _funnelSeriesController; + /// return Column( + /// children: [ + /// Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// dataSource: chartData, + /// onRendererCreated: + /// (FunnelSeriesController controller) { + /// _funnelSeriesController = controller; + /// }, + /// ), + /// ) + /// ), + /// Container( + /// child: RaisedButton( + /// onPressed: () { + /// chartData.removeAt(0); + /// chartData.add(ChartData(3,23)); + /// _funnelSeriesController.updateDataSource( + /// addedDataIndexes: [chartData.length -1], + /// removedDataIndexes: [0], + /// ); + /// }) + /// )] + /// ); + /// } + ///``` + void updateDataSource({ + List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int addedDataIndex = -1, + int removedDataIndex = -1, + int updatedDataIndex = -1, + }) { + final RealTimeUpdateMixin renderer = seriesRenderer; + + List? effectiveRemovedIndexes; + List? effectiveAddedIndexes; + List? effectiveReplacedIndexes; + + if (removedDataIndexes != null) { + effectiveRemovedIndexes = List.from(removedDataIndexes); + } + + if (addedDataIndexes != null) { + effectiveAddedIndexes = List.from(addedDataIndexes); + } + + if (updatedDataIndexes != null) { + effectiveReplacedIndexes = List.from(updatedDataIndexes); + } + + if (removedDataIndex != -1) { + effectiveRemovedIndexes ??= []; + effectiveRemovedIndexes.add(removedDataIndex); + } + + if (addedDataIndex != -1) { + effectiveAddedIndexes ??= []; + effectiveAddedIndexes.add(addedDataIndex); + } + + if (updatedDataIndex != -1) { + effectiveReplacedIndexes ??= []; + effectiveReplacedIndexes.add(updatedDataIndex); + } + + renderer.updateDataPoints( + effectiveRemovedIndexes, + effectiveAddedIndexes, + effectiveReplacedIndexes, + ); + } + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and returns + /// a chart data point. + /// + ///```dart + /// late FunnelSeriesController seriesController; + /// SfFunnelChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// PointInfo chartPoint = + /// seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// }, + /// series: FunnelSeries( + /// dataSource: funnelData, + /// onRendererCreated: + /// (FunnelSeriesController funnelSeriesController) { + /// seriesController = FunnelSeriesController; + /// } + /// ) + /// ); + /// ``` + PointInfo pixelToPoint(Offset position) { + int pointIndex = -1; + final List segments = seriesRenderer.segments; + for (int i = 0; i < segments.length; i++) { + final FunnelSegment segment = segments[i] as FunnelSegment; + if (segment.contains(position)) { + pointIndex = i; + } + } + final dynamic x = seriesRenderer.xValues[pointIndex]; + final num y = seriesRenderer.yValues[pointIndex]; + return PointInfo(x, y); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart new file mode 100644 index 000000000..def82005c --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart @@ -0,0 +1,548 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/element_widget.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the hilo open close series. +/// +/// Hilo open close series is used to represent the low, high, +/// open and closing values over time. +/// +/// To render a hilo open close chart, create an instance of +/// [HiloOpenCloseSeries], and add it to the series collection +/// property of [SfCartesianChart]. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=g5cniDExpRw} +class HiloOpenCloseSeries extends FinancialSeriesBase { + /// Creating an argument constructor of HiloOpenCloseSeries class. + const HiloOpenCloseSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.lowValueMapper, + required super.highValueMapper, + required super.openValueMapper, + required super.closeValueMapper, + super.volumeValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.bearColor, + super.bullColor, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.spacing = 0, + super.initialSelectedDataIndexes, + super.showIndicationForSameValues = false, + super.trendlines, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Create the hilo open close series renderer. + @override + HiloOpenCloseSeriesRenderer createRenderer() { + HiloOpenCloseSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = + onCreateRenderer!(this) as HiloOpenCloseSeriesRenderer; + return seriesRenderer; + } + return HiloOpenCloseSeriesRenderer(); + } +} + +/// Creates series renderer for hilo open close series. +class HiloOpenCloseSeriesRenderer + extends FinancialSeriesRendererBase + with SegmentAnimationMixin { + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + if (current.position != ChartDataPointType.open || + current.position != ChartDataPointType.close) { + return super.dataLabelPosition(current, alignment, size); + } + + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + if (current.position == ChartDataPointType.open) { + return _calculateDataLabelOpenPosition(x, current.y!, alignment, size); + } else if (current.position == ChartDataPointType.close) { + return _calculateDataLabelClosePosition(x, current.y!, alignment, size); + } + + return Offset.zero; + } + + Offset _calculateDataLabelOpenPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.outer, + size, + ChartDataPointType.open, + ); + + case ChartDataLabelAlignment.bottom: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.top, + size, + ChartDataPointType.open, + ); + } + } + + Offset _calculateDataLabelClosePosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ) { + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.middle: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.outer, + size, + ChartDataPointType.close, + ); + + case ChartDataLabelAlignment.bottom: + return _calculateOpenAndClosePosition( + x, + y, + ChartDataLabelAlignment.top, + size, + ChartDataPointType.close, + ); + } + } + + Offset _calculateOpenAndClosePosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + ChartDataPointType position, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + final double openAndCloseDataLabelPadding = _openAndCloseDataLabelPadding( + position, + margin, + ); + + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.middle: + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = dataLabelPadding; + translationY = -margin.top + openAndCloseDataLabelPadding; + } else { + translationX = -margin.left + openAndCloseDataLabelPadding; + translationY = -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top + openAndCloseDataLabelPadding; + } else { + translationX = -margin.left + openAndCloseDataLabelPadding; + translationY = dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + } + } + + double _openAndCloseDataLabelPadding( + ChartDataPointType position, + EdgeInsets margin, + ) { + double paddingValue = dataLabelPadding + (2 * margin.left); + if (isTransposed) { + paddingValue = dataLabelPadding + (2 * margin.top); + return position == ChartDataPointType.open ? paddingValue : -paddingValue; + } + return position == ChartDataPointType.open ? -paddingValue : paddingValue; + } + + @override + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { + return chartThemeData.plotAreaBackgroundColor!; + } else if (chartThemeData.backgroundColor != Colors.transparent) { + return chartThemeData.backgroundColor!; + } + return themeData.colorScheme.surface; + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as HiloOpenCloseSegment + ..series = this + ..x = xValues[index] + ..high = highValues[index] + ..low = lowValues[index] + ..open = openValues[index] + ..close = closeValues[index] + ..isEmpty = isEmpty(index); + } + + @override + HiloOpenCloseSegment createSegment() => HiloOpenCloseSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.hiloOpenCloseSeries; + + @override + void customizeSegment(ChartSegment segment) { + final int index = segment.currentSegmentIndex; + final bool isBull = closeValues[index] > openValues[index]; + final Color color = isBull ? bullColor : bearColor; + final Color? segmentColor = + pointColorMapper != null && + pointColors[segment.currentSegmentIndex] != null + ? null + : color; + updateSegmentColor( + segment, + segmentColor, + borderWidth, + fillColor: segmentColor, + isLineType: true, + ); + updateSegmentGradient(segment); + } +} + +/// Segment class for hilo open close series. +class HiloOpenCloseSegment extends ChartSegment { + late HiloOpenCloseSeriesRenderer series; + bool _isSameValue = false; + + late num x; + num high = double.nan; + num low = double.nan; + num open = double.nan; + num close = double.nan; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + if (x.isNaN || high.isNaN || low.isNaN || open.isNaN || close.isNaN) { + return; + } + + points.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double centerY = (low + high) / 2; + _isSameValue = high == low; + + final double centerX = (right + left) / 2; + points.add(Offset(transformX(centerX, high), transformY(centerX, high))); + points.add(Offset(transformX(centerX, low), transformY(centerX, low))); + + points.add(Offset(transformX(left, open), transformY(left, open))); + points.add(Offset(transformX(centerX, open), transformY(centerX, open))); + + points.add(Offset(transformX(right, close), transformY(right, close))); + points.add(Offset(transformX(centerX, close), transformY(centerX, close))); + + if (_isSameValue && series.showIndicationForSameValues) { + final double x = transformX(centerX, centerY); + final double y = transformY(centerX, centerY); + if (series.isTransposed) { + points.add(Offset(x - 2, y)); + points.add(Offset(x + 2, y)); + } else { + points.add(Offset(x, y - 2)); + points.add(Offset(x, y + 2)); + } + } + + if (_oldPoints.isEmpty) { + final Offset center = Offset( + transformX(centerX, centerY), + transformY(centerX, centerY), + ); + _oldPoints.add(center); + _oldPoints.add(center); + _oldPoints.add( + Offset(transformX(left, centerY), transformY(left, centerY)), + ); + _oldPoints.add(center); + _oldPoints.add( + Offset(transformX(right, centerY), transformY(right, centerY)), + ); + _oldPoints.add(center); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + late Rect segmentRegion; + if (series.isTransposed) { + final Offset start = + series.yAxis != null && series.yAxis!.isInversed + ? points[0] + : points[1]; + final Offset end = + series.yAxis != null && series.yAxis!.isInversed + ? points[1] + : points[0]; + segmentRegion = Rect.fromLTRB( + start.dx, + start.dy - hiloPadding, + end.dx, + end.dy + hiloPadding, + ); + } else { + final Offset start = + series.yAxis != null && series.yAxis!.isInversed + ? points[1] + : points[0]; + final Offset end = + series.yAxis != null && series.yAxis!.isInversed + ? points[0] + : points[1]; + segmentRegion = Rect.fromLTRB( + start.dx - hiloPadding, + start.dy, + end.dx + hiloPadding, + end.dy, + ); + } + + if (segmentRegion.contains(position)) { + return true; + } + return false; + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + high: high, + low: low, + open: open, + close: close, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final Offset preferredPos = series.localToGlobal(points[0]); + return ChartTooltipInfo( + primaryPosition: preferredPos, + secondaryPosition: preferredPos, + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + hasMultipleYValues: true, + markerColors: [series.paletteColor], + markerType: series.markerAt(pointIndex).type, + ); + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = Offset( + series.pointToPixelX(x, high), + series.pointToPixelY(x, high), + ); + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(x, low), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty) { + return; + } + + final Paint paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + Offset start = Offset.lerp(_oldPoints[0], points[0], animationFactor)!; + Offset end = Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + + start = Offset.lerp(_oldPoints[2], points[2], animationFactor)!; + end = Offset.lerp(_oldPoints[3], points[3], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + + start = Offset.lerp(_oldPoints[4], points[4], animationFactor)!; + end = Offset.lerp(_oldPoints[5], points[5], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + + if (_isSameValue && series.showIndicationForSameValues) { + start = Offset.lerp(_oldPoints[6], points[6], animationFactor)!; + end = Offset.lerp(_oldPoints[7], points[7], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + } + } + } + + @override + void dispose() { + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart new file mode 100644 index 000000000..e702a6f36 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart @@ -0,0 +1,427 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the hilo series. +/// +/// [HiloSeries] illustrates the price movements in stock using the +/// high and low values. +/// +/// To render a hilo chart, create an instance of [HiloSeries], and add it to +/// the series collection property of [SfCartesianChart]. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} +class HiloSeries extends RangeSeriesBase { + /// Creating an argument constructor of HiloSeries class. + const HiloSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.lowValueMapper, + required super.highValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.borderWidth = 2, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + this.spacing = 0, + super.initialSelectedDataIndexes, + this.showIndicationForSameValues = false, + super.trendlines, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Spacing between the columns. The value ranges from 0 to 1. + /// 1 represents 100% and 0 represents 0% of the available space. + /// + /// Spacing also affects the width of the column. For example, setting 20% + /// spacing and 100% width renders the column with 80% of total width. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HiloSeries( + /// spacing: 0.5, + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// If it is set to true, a small vertical line will be rendered. Else nothing + /// will be rendered for that specific data point and left as a blank area. + /// + /// This is applicable for the following series types: + /// * HiLo (High low) + /// * OHLC (Open high low close) + /// * Candle + /// + /// Defaults to `false`. + final bool showIndicationForSameValues; + + /// Create the hilo series renderer. + @override + HiloSeriesRenderer createRenderer() { + HiloSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as HiloSeriesRenderer; + return seriesRenderer; + } + return HiloSeriesRenderer(); + } + + @override + HiloSeriesRenderer createRenderObject(BuildContext context) { + final HiloSeriesRenderer renderer = + super.createRenderObject(context) as HiloSeriesRenderer; + renderer + ..spacing = spacing + ..showIndicationForSameValues = showIndicationForSameValues; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + HiloSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..spacing = spacing + ..showIndicationForSameValues = showIndicationForSameValues; + } +} + +/// Creates series renderer for hilo series. +class HiloSeriesRenderer extends RangeSeriesRendererBase + with SegmentAnimationMixin { + double get spacing => _spacing; + double _spacing = 0; + set spacing(double value) { + if (_spacing != value) { + _spacing = value; + markNeedsUpdate(); + } + } + + bool get showIndicationForSameValues => _showIndicationForSameValues; + bool _showIndicationForSameValues = false; + set showIndicationForSameValues(bool value) { + if (_showIndicationForSameValues != value) { + _showIndicationForSameValues = value; + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as HiloSegment + ..series = this + ..xValue = xValues[index] + ..high = highValues[index] + ..low = lowValues[index] + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + HiloSegment createSegment() => HiloSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.hiloSeries; + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth, isLineType: true); + updateSegmentGradient(segment); + } +} + +/// Segment class for hilo series. +class HiloSegment extends ChartSegment { + late HiloSeriesRenderer series; + + num xValue = double.nan; + num high = double.nan; + num low = double.nan; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + if (xValue.isNaN || high.isNaN || low.isNaN) { + return; + } + + points.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final double centerY = (low + high) / 2; + if (high == low) { + if (series.showIndicationForSameValues) { + if (series.isTransposed) { + points.add( + Offset(transformX(xValue, high) - 2, transformY(xValue, high)), + ); + points.add( + Offset(transformX(xValue, low) + 2, transformY(xValue, low)), + ); + } else { + points.add( + Offset(transformX(xValue, high), transformY(xValue, high) - 2), + ); + points.add( + Offset(transformX(xValue, low), transformY(xValue, low) + 2), + ); + } + } + } else { + points.add(Offset(transformX(xValue, high), transformY(xValue, high))); + points.add(Offset(transformX(xValue, low), transformY(xValue, low))); + } + + if (_oldPoints.isEmpty) { + final Offset center = Offset( + transformX(xValue, centerY), + transformY(xValue, centerY), + ); + _oldPoints.addAll([center, center]); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + if (points.isEmpty) { + return false; + } + + late Rect segmentBounds; + if (series.isTransposed) { + final Offset start = + series.yAxis != null && series.yAxis!.isInversed + ? points[0] + : points[1]; + final Offset end = + series.yAxis != null && series.yAxis!.isInversed + ? points[1] + : points[0]; + segmentBounds = Rect.fromLTRB( + start.dx, + start.dy - hiloPadding, + end.dx, + end.dy + hiloPadding, + ); + } else { + final Offset start = + series.yAxis != null && series.yAxis!.isInversed + ? points[1] + : points[0]; + final Offset end = + series.yAxis != null && series.yAxis!.isInversed + ? points[0] + : points[1]; + segmentBounds = Rect.fromLTRB( + start.dx - hiloPadding, + start.dy, + end.dx + hiloPadding, + end.dy, + ); + } + + if (segmentBounds.contains(position)) { + return true; + } + + return false; + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + high: high, + low: low, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + final CartesianChartPoint chartPoint = _chartPoint(); + pointIndex ??= currentSegmentIndex; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = points[0]; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + hasMultipleYValues: true, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: points[0], + highXPos: series.pointToPixelX(xValue, high), + highYPos: series.pointToPixelY(xValue, high), + lowYPos: series.pointToPixelY(xValue, low), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => getStrokePaint(); + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty) { + return; + } + + final Paint paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + final Offset start = + Offset.lerp(_oldPoints[0], points[0], animationFactor)!; + final Offset end = + Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + } + } + + @override + void dispose() { + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart new file mode 100644 index 000000000..001a16542 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart @@ -0,0 +1,690 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// This class has the properties of the histogram series. +/// +/// To render a histogram chart, create an instance of [HistogramSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// The histogram series is a rectangular histogram with heights or +/// lengths proportional to the values that they represent. It has the spacing +/// property to separate the histogram. +/// +/// It supports only NumericAxis and LogarithmicAxis alone. +/// It does not support sortingOrder and emptyPointSettings. +/// +/// Provide the options of color, opacity, border color, and border width to +/// customize the appearance. +class HistogramSeries extends XyDataSeries { + /// Creating an argument constructor of HistogramSeries class. + const HistogramSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + this.isTrackVisible = false, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.95, + this.spacing = 0.0, + super.markerSettings, + super.trendlines, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.borderRadius = BorderRadius.zero, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1.0, + this.trackPadding = 0.0, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.dashArray, + this.binInterval, + this.showNormalDistributionCurve = false, + this.curveColor = Colors.blue, + this.curveWidth = 2.0, + this.curveDashArray, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Interval value by which the data points are grouped and rendered as bars, + /// in histogram series. + /// + /// For example, if the [binInterval] is set to 20, the x-axis will split + /// with 20 as the interval. The first bar in the histogram represents the + /// count of values lying between 0 to 20 in the provided data and the second + /// bar will represent 20 to 40. + /// + /// If no value is specified for this property, then the interval will be + /// calculated automatically based on the data points count and value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// binInterval: 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final double? 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. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// showNormalDistributionCurve: true + /// ), + /// ], + /// ); + /// } + /// ``` + final bool showNormalDistributionCurve; + + /// Color of the normal distribution spline curve. + /// + /// Defaults to `Colors.blue`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// curveColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color curveColor; + + /// Width of the normal distribution spline curve. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// curveWidth: 4 + /// ), + /// ], + /// ); + /// } + /// ``` + final double curveWidth; + + /// Dash array of the normal distribution spline curve. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// curveDashArray: [2, 3] + /// ), + /// ], + /// ); + /// } + /// ``` + final List? curveDashArray; + + /// Color of the track. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// isTrackVisible: true, + /// trackColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackColor; + + /// Color of the track border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackBorderColor; + + /// Width of the track border. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red, + /// trackBorderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackBorderWidth; + + /// Padding of the track. + /// + /// By default, track will be rendered based on the bar’s available width and + /// spacing. If you wish to change the track width, you can use this property. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// isTrackVisible: true, + /// trackPadding: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackPadding; + + /// Spacing between the bars in histogram series. + /// + /// The value ranges from 0 to 1. 1 represents 100% and 0 represents + /// 0% of the available space. + /// + /// Spacing also affects the width of the bar. For example, setting 20% + /// spacing and 100% width renders the bar with 80% of total width. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// spacing: 0.5, + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// Renders the bar in histogram series with track. + /// + /// Track is a rectangular bar rendered from the start to the end of the axis. + /// Bars in the histogram will be rendered above the track. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// isTrackVisible: true, + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isTrackVisible; + + /// Customizes the corners of the bars in histogram series. + /// + /// Each corner can be customized individually or can be customized together, + /// by specifying a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// HistogramSeries( + /// borderRadius: BorderRadius.circular(5), + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double width; + + final Color borderColor; + + /// Create the histogram series renderer. + @override + HistogramSeriesRenderer createRenderer() { + HistogramSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as HistogramSeriesRenderer; + return seriesRenderer; + } + return HistogramSeriesRenderer(); + } + + @override + HistogramSeriesRenderer createRenderObject(BuildContext context) { + final HistogramSeriesRenderer renderer = + super.createRenderObject(context) as HistogramSeriesRenderer; + renderer + ..binInterval = binInterval + ..showNormalDistributionCurve = showNormalDistributionCurve + ..curveColor = curveColor + ..curveWidth = curveWidth + ..curveDashArray = curveDashArray + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..width = width + ..spacing = spacing + ..width = width + ..borderRadius = borderRadius + ..isTrackVisible = isTrackVisible + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + HistogramSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..binInterval = binInterval + ..showNormalDistributionCurve = showNormalDistributionCurve + ..curveColor = curveColor + ..curveWidth = curveWidth + ..curveDashArray = curveDashArray + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..width = width + ..spacing = spacing + ..width = width + ..borderRadius = borderRadius + ..isTrackVisible = isTrackVisible + ..borderColor = borderColor; + } +} + +/// Creates series renderer for histogram series. +class HistogramSeriesRenderer extends HistogramSeriesRendererBase { + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + populateChartPoints(); + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as HistogramSegment + ..series = this + ..x = xValues[index] + ..y = yValues[index] + ..bottom = bottom + .._binWidth = binWidth + ..isEmpty = isEmpty(index); + } + + @override + HistogramSegment createSegment() => HistogramSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.histogramSeries; + + @override + void customizeSegment(ChartSegment segment) { + final HistogramSegment histogramSegment = + segment as HistogramSegment; + updateSegmentTrackerStyle( + histogramSegment, + trackColor, + trackBorderColor, + trackBorderWidth, + ); + updateSegmentColor(histogramSegment, borderColor, borderWidth); + updateSegmentGradient( + histogramSegment, + gradientBounds: histogramSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } +} + +class HistogramSegment extends ChartSegment with BarSeriesTrackerMixin { + late HistogramSeriesRenderer series; + num _binWidth = 0.0; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + late num x; + num y = double.nan; + num bottom = double.nan; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || y.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, y); + final double y1 = transformY(left, y); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series.borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, bottom), + transformY(left, bottom), + x2, + y2, + borderRadius, + ); + + if (series.isTrackVisible) { + calculateTrackerBounds( + left, + right, + borderRadius, + series.trackPadding, + series.trackBorderWidth, + series, + ); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + final int dataSourceLength = series.dataSource!.length; + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipBehavior tooltipBehavior = series.parent!.tooltipBehavior!; + final int digits = tooltipBehavior.decimalPlaces; + final TooltipPosition tooltipPosition = tooltipBehavior.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: _tooltipTextFormat(chartPoint, digits), + header: + tooltipBehavior.shared + ? _tooltipText(series.tooltipHeaderText(chartPoint), digits) + : series.name, + data: + series.dataSource![pointIndex >= dataSourceLength + ? dataSourceLength - 1 + : pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, y), + series.pointToPixelY(x, y), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + String _tooltipTextFormat(CartesianChartPoint chartPoint, int digits) { + if (series.parent == null) { + return ''; + } + + final TooltipBehavior tooltipBehavior = series.parent!.tooltipBehavior!; + if (tooltipBehavior.format != null) { + if (tooltipBehavior.format!.contains('point.x')) { + return _tooltipText(series.tooltipText(chartPoint), digits); + } else { + return series.tooltipText(chartPoint); + } + } + + if (tooltipBehavior.shared) { + return series.tooltipText(chartPoint); + } else { + return _tooltipText(series.tooltipText(chartPoint), digits); + } + } + + String _tooltipText(String text, int digits) { + if (series.parent == null) { + return ''; + } + + final num xValue = series.xValues[currentSegmentIndex]; + final num binWidth = _binWidth / 2; + final String x1 = formatNumericValue( + xValue - binWidth, + series.xAxis, + digits, + ); + final String x2 = formatNumericValue( + xValue + binWidth, + series.xAxis, + digits, + ); + if (text.contains(':')) { + // Tooltip text. + text = '$x1 - $x2 : ${text.split(':')[1]}'; + } else { + // Tooltip header text. + text = '$x1 - $x2'; + } + + return text; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } + + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart new file mode 100644 index 000000000..5d1188cc1 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart @@ -0,0 +1,392 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the line series. +/// +/// This class holds the properties of line series. To render a line chart, +/// create an instance of [LineSeries], and add it to the series collection +/// property of [SfCartesianChart]. A line chart requires two fields (X and Y) +/// to plot a point. +/// +/// Provide the options for color, opacity, border color, and border width +/// to customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=zhcxdh4-Jt8} +class LineSeries extends XyDataSeries { + /// Creating an argument constructor of [LineSeries] class. + const LineSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.color, + double width = 2, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.trendlines, + super.initialIsVisible, + super.name, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.sortingOrder, + super.legendItemText, + super.opacity, + super.animationDelay, + super.initialSelectedDataIndexes, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }) : super(borderWidth: width); + + /// Create the line series renderer. + @override + LineSeriesRenderer createRenderer() { + LineSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as LineSeriesRenderer; + // ignore: unnecessary_null_comparison + return seriesRenderer; + } + return LineSeriesRenderer(); + } +} + +/// Creates series renderer for line series. +class LineSeriesRenderer extends XyDataSeriesRenderer + with LineSeriesMixin { + /// Calling the default constructor of LineSeriesRenderer class. + LineSeriesRenderer(); + + @override + double legendIconBorderWidth() { + return 3; + } + + bool _hasNewSegmentAdded = false; + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + final num x1 = xValues[index]; + final num y1 = yValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + final int nextIndex = nextIndexConsideringEmptyPointMode( + index, + emptyPointSettings.mode, + yValues, + dataCount, + ); + if (nextIndex != -1) { + x2 = xValues[nextIndex]; + y2 = yValues[nextIndex]; + } + + segment as LineSegment + ..series = this + .._x1 = x1 + .._y1 = y1 + .._x2 = x2 + .._y2 = y2 + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + LineSegment createSegment() { + _hasNewSegmentAdded = true; + return LineSegment(); + } + + @override + ShapeMarkerType effectiveLegendIconType() => + dashArray != null && !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.lineSeriesWithDashArray + : ShapeMarkerType.lineSeries; + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth, isLineType: true); + updateSegmentGradient(segment); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + if (animationController!.isCompleted) { + _hasNewSegmentAdded = false; + } + } +} + +// Creates the segments for line series. +/// +/// Line segment is a part of a line series that is bounded +/// by two distinct end point. +/// 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`. +class LineSegment extends ChartSegment { + late LineSeriesRenderer series; + late num _x1; + late num _y1; + late num _x2; + late num _y2; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + @nonVirtual + void transformValues() { + points.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + if (!_x1.isNaN && !_y1.isNaN) { + points.add(Offset(transformX(_x1, _y1), transformY(_x1, _y1))); + } + + if (!_x2.isNaN && !_y2.isNaN) { + points.add(Offset(transformX(_x2, _y2), transformY(_x2, _y2))); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + if (points.isNotEmpty) { + final ChartMarker marker = series.markerAt(currentSegmentIndex); + return tooltipTouchBounds( + points[0], + marker.width, + marker.height, + ).contains(position); + } + return false; + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointIndex ??= currentSegmentIndex; + CartesianChartPoint chartPoint = _chartPoint(pointIndex); + List markerColors = [fillPaint.color]; + if (chartPoint.y != null && chartPoint.y!.isNaN) { + pointIndex += 1; + chartPoint = _chartPoint(pointIndex); + markerColors = [series.segments[pointIndex].fillPaint.color]; + } + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = points[0]; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: markerColors, + markerType: marker.type, + ); + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; + } + + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => getStrokePaint(); + + /// Gets the stroke color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty || points.length != 2) { + return; + } + + final Paint paint = getStrokePaint(); + if (paint.color == Colors.transparent || paint.strokeWidth < 0) { + return; + } + + final Offset start; + final Offset end; + if (animationFactor < 1) { + if (series.animationType == AnimationType.realtime && + series._hasNewSegmentAdded && + !isEmpty && + series.dataCount == currentSegmentIndex + 2) { + final double prevX = _oldPoints[0].dx; + final double prevY = _oldPoints[0].dy; + final double x1 = points[0].dx; + final double y1 = points[0].dy; + final double x2 = points[1].dx; + final double y2 = points[1].dy; + + final double newX1 = prevX + (x1 - prevX) * animationFactor; + final double newY1 = prevY + (y1 - prevY) * animationFactor; + final double newX2 = newX1 + (x2 - newX1) * animationFactor; + final double newY2 = newY1 + (y2 - newY1) * animationFactor; + + start = Offset(newX1, newY1); + end = Offset(newX2, newY2); + } else { + start = Offset.lerp(_oldPoints[0], points[0], animationFactor)!; + end = Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + } + } else { + start = points[0]; + end = points[1]; + } + + if (start.isNaN || end.isNaN) { + return; + } + drawDashes(canvas, series.dashArray, paint, start: start, end: end); + } + + @override + void dispose() { + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart new file mode 100644 index 000000000..f643f6388 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart @@ -0,0 +1,610 @@ +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 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/circular_data_label.dart'; +import '../common/core_tooltip.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/renderer_helper.dart'; +import 'chart_series.dart'; + +/// This class has the properties of the pie series. +/// +/// To render a pie chart, create an instance of [PieSeries], and add it to +/// the series collection property of [SfCircularChart]. +/// +/// It provides the options for color, opacity, border color, and border width +/// to customize the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} +class PieSeries extends CircularSeries { + /// Creating an argument constructor of PieSeries class. + const PieSeries({ + super.key, + super.onCreateRenderer, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.dataSource, + super.xValueMapper, + super.yValueMapper, + super.pointColorMapper, + super.pointShaderMapper, + super.pointRadiusMapper, + super.dataLabelMapper, + super.sortFieldValueMapper, + super.startAngle = 0, + super.endAngle = 360, + super.radius = '80%', + this.explode = false, + this.explodeAll = false, + this.explodeGesture = ActivationMode.singleTap, + this.explodeOffset = '10%', + this.explodeIndex, + super.groupTo, + super.groupMode, + super.pointRenderMode, + super.emptyPointSettings, + Color strokeColor = Colors.transparent, + double strokeWidth = 2.0, + super.opacity, + super.dataLabelSettings, + super.enableTooltip = true, + super.name, + super.animationDuration, + super.animationDelay, + super.selectionBehavior, + super.sortingOrder, + super.legendIconType, + super.initialSelectedDataIndexes, + }) : super(borderColor: strokeColor, borderWidth: strokeWidth); + + /// Enables or disables the explode of slices on tap. + /// + /// Default to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// explode: true + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool explode; + + /// Enables or disables exploding all the slices at the initial rendering. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// explodeAll: true + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool explodeAll; + + /// Index of the slice to explode it at the initial rendering. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// explode: true, + /// explodeIndex: 2 + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final int? explodeIndex; + + /// Offset of exploded slice. The value ranges from 0% to 100%. + /// + /// Defaults to `20%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// explode: true, + /// explodeOffset: '30%' + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final String explodeOffset; + + /// Gesture for activating the explode. + /// + /// Explode can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, `ActivationMode.doubleTap`, + /// and `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// explode: true, + /// explodeGesture: ActivationMode.singleTap + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final ActivationMode explodeGesture; + + @override + List get positions => [ + ChartDataPointType.y, + ]; + + /// Create the pie series renderer. + @override + PieSeriesRenderer createRenderer() { + PieSeriesRenderer? seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as PieSeriesRenderer; + return seriesRenderer; + } + return PieSeriesRenderer(); + } + + @override + PieSeriesRenderer createRenderObject(BuildContext context) { + final PieSeriesRenderer renderer = + super.createRenderObject(context) as PieSeriesRenderer; + renderer + ..explode = explode + ..explodeAll = explodeAll + ..explodeIndex = explodeIndex + ..explodeOffset = explodeOffset + ..explodeGesture = explodeGesture; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + PieSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..explode = explode + ..explodeAll = explodeAll + ..explodeIndex = explodeIndex + ..explodeOffset = explodeOffset + ..explodeGesture = explodeGesture; + } +} + +/// Creates a series renderer for pie series. +class PieSeriesRenderer extends CircularSeriesRenderer { + /// Calling the default constructor of [PieSeriesRenderer] class. + PieSeriesRenderer(); + + bool get explode => _explode; + bool _explode = false; + set explode(bool value) { + if (_explode != value) { + _explode = value; + _updateExploding(); + } + } + + bool get explodeAll => _explodeAll; + bool _explodeAll = false; + set explodeAll(bool value) { + if (_explodeAll != value) { + _explodeAll = value; + if (!_explodeAll) { + for (final ChartSegment segment in segments) { + final PieSegment pieSegment = segment as PieSegment; + if (explode && segment.currentSegmentIndex == explodeIndex) { + pieSegment._isExploded = true; + } else { + pieSegment._isExploded = false; + } + } + + transformValues(); + markNeedsPaint(); + } else { + _updateExploding(); + } + } + } + + int? get explodeIndex => _explodeIndex; + int? _explodeIndex; + set explodeIndex(int? value) { + if (_explodeIndex != value) { + _explodeIndex = value; + _updateExploding(); + } + } + + String get explodeOffset => _explodeOffset; + String _explodeOffset = '10%'; + set explodeOffset(String value) { + if (_explodeOffset != value) { + _explodeOffset = value; + transformValues(); + markNeedsPaint(); + } + } + + ActivationMode get explodeGesture => _explodeGesture; + ActivationMode _explodeGesture = ActivationMode.singleTap; + set explodeGesture(ActivationMode value) { + if (_explodeGesture != value) { + _explodeGesture = value; + } + } + + /// Stores pointer down time to determine whether a long press interaction is handled at pointer up + DateTime? _pointerHoldingTime; + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + num yValue = circularYValues[index].abs(); + // Handled the empty point here. + yValue = yValue.isNaN || !segment.isVisible ? 0 : yValue; + final double degree = + (yValue.abs() / (sumOfY != 0 ? sumOfY : 1)) * totalAngle; + final double pointEndAngle = pointStartAngle + degree; + final double outerRadius = + pointRadii.isNotEmpty + ? percentToValue( + pointRadii[index], + (min(size.width, size.height)) / 2, + )! + : currentRadius; + + segment as PieSegment + ..series = this + .._degree = degree + ..startAngle = pointStartAngle + ..endAngle = pointEndAngle + .._outerRadius = outerRadius + .._center = center + .._isExploded = explode && (index == explodeIndex || explodeAll) + ..isEmpty = + (emptyPointSettings.mode != EmptyPointMode.drop && + emptyPointSettings.mode != EmptyPointMode.gap) && + isEmpty(index); + + pointStartAngle = pointEndAngle; + } + + @override + PieSegment createSegment() => PieSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.pieSeries; + + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, borderColor, borderWidth); + segment.fillPaint.shader = null; + updateSegmentGradient(segment); + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + final bool isHit = super.hitTest(result, position: position); + return explode || isHit; + } + + @override + void handlePointerDown(PointerDownEvent details) { + if (explode && explodeGesture == ActivationMode.singleTap) { + _pointerHoldingTime = DateTime.now(); + } + super.handlePointerDown(details); + } + + @override + void handlePointerUp(PointerUpEvent details) { + if (explode && + explodeGesture == ActivationMode.singleTap && + _pointerHoldingTime != null && + DateTime.now().difference(_pointerHoldingTime!).inMilliseconds < + kLongPressTimeout.inMilliseconds) { + _handleExploding(details.localPosition); + } + super.handlePointerUp(details); + } + + @override + void handleDoubleTap(Offset position) { + final Offset localPosition = globalToLocal(position); + if (explode && explodeGesture == ActivationMode.doubleTap) { + _handleExploding(localPosition); + } + super.handleDoubleTap(position); + } + + @override + void handleLongPressStart(LongPressStartDetails details) { + if (explode && explodeGesture == ActivationMode.longPress) { + _handleExploding(details.localPosition); + } + super.handleLongPressStart(details); + } + + void _handleExploding(Offset position) { + for (final ChartSegment segment in segments) { + final PieSegment pieSegment = segment as PieSegment; + if (segment.contains(position)) { + pieSegment._isExploded = !pieSegment._isExploded; + if (pieSegment._isExploded) { + _explodeIndex = segment.currentSegmentIndex; + } else { + _explodeIndex = -1; + } + } else { + pieSegment._isExploded = false; + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + void _updateExploding() { + for (final ChartSegment segment in segments) { + final PieSegment pieSegment = segment as PieSegment; + if (!explode) { + pieSegment._isExploded = false; + } else { + if (explodeAll || segment.currentSegmentIndex == explodeIndex) { + pieSegment._isExploded = true; + } else { + pieSegment._isExploded = false; + } + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + // TODO(Preethika): Need to remove if the base method is mark as private + @override + @nonVirtual + ChartShaderDetails createShaderDetails() { + return ChartShaderDetails( + Rect.fromCircle(center: center, radius: currentRadius), + null, + 'series', + ); + } + + @override + Offset dataLabelPosition(CircularDataLabelBoxParentData current, Size size) { + final PieSegment segment = + segments[current.dataPointIndex] as PieSegment; + current.point! + ..degree = segment._degree + ..isExplode = segment._isExploded + ..isVisible = segment.isVisible + ..explodeOffset = explodeOffset + ..startAngle = segment._startAngle + ..endAngle = segment._endAngle + ..midAngle = (segment._startAngle + segment._endAngle) / 2 + ..innerRadius = 0.0 + ..outerRadius = segment._outerRadius + ..center = center + ..fill = palette[current.dataPointIndex % palette.length]; + + _findDataLabelPosition(current.point!); + return super.dataLabelPosition(current, size); + } + + /// To find data label position. + void _findDataLabelPosition(CircularChartPoint point) { + point.midAngle = + point.midAngle! > 360 ? point.midAngle! - 360 : point.midAngle!; + point.dataLabelPosition = + ((point.midAngle! >= -90 && point.midAngle! < 0) || + (point.midAngle! >= 0 && point.midAngle! < 90) || + (point.midAngle! >= 270)) + ? Position.right + : Position.left; + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + context.canvas.translate(center.dx, center.dy); + context.canvas.scale(animationFactor); + context.canvas.translate(-center.dx, -center.dy); + paintSegments(context, offset); + context.canvas.restore(); + paintDataLabels(context, offset); + } +} + +class PieSegment extends ChartSegment { + late PieSeriesRenderer series; + late double _degree; + late double _outerRadius; + late Offset _center; + final double _innerRadius = 0.0; + bool _isExploded = false; + Path fillPath = Path(); + double _priorStartAngle = double.nan; + double _priorEndAngle = double.nan; + + double get startAngle => _startAngle; + double _startAngle = double.nan; + set startAngle(double value) { + _priorStartAngle = _startAngle; + _startAngle = value; + } + + double get endAngle => _endAngle; + double _endAngle = double.nan; + set endAngle(double value) { + _priorEndAngle = _endAngle; + _endAngle = value; + } + + @override + void transformValues() { + fillPath.reset(); + + double degree = _degree * animationFactor; + final double angle = calculateAngle( + series.animationFactor == 1, + series.startAngle, + series.endAngle, + ); + final double startAngle = + lerpDouble( + _priorEndAngle.isNaN ? angle : _priorStartAngle, + _startAngle, + animationFactor, + )!; + final double endAngle = + _priorEndAngle.isNaN + ? startAngle + degree + : lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; + degree = _priorEndAngle.isNaN ? degree : endAngle - startAngle; + + // If the startAngle and endAngle value is same, then degree will be 0. + // Hence no need to render segments. + if (!isVisible && degree == 0) { + return; + } + + if (series.explode && _isExploded) { + final double midAngle = (_startAngle + _endAngle) / 2; + _center = calculateExplodingCenter( + midAngle, + _outerRadius, + series.center, + series.explodeOffset, + ); + } else { + _center = series.center; + } + + fillPath = calculateArcPath( + _innerRadius, + _outerRadius, + _center, + startAngle, + endAngle, + degree, + isAnimate: true, + ); + } + + @override + Paint getFillPaint() => fillPaint; + + @override + Paint getStrokePaint() => strokePaint; + + @override + void calculateSegmentPoints() {} + + @override + bool contains(Offset position) { + return fillPath.contains(position); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + final ChartPoint point = ChartPoint( + x: series.circularXValues[currentSegmentIndex], + y: series.circularYValues[currentSegmentIndex], + ); + final Offset location = calculateOffset( + (_startAngle + _endAngle) / 2, + (_innerRadius + _outerRadius) / 2, + _center, + ); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? series.localToGlobal(position ?? location) + : series.localToGlobal(location); + return ChartTooltipInfo( + primaryPosition: preferredPos, + secondaryPosition: preferredPos, + text: series.tooltipText(point), + header: '', + data: series.dataSource![currentSegmentIndex], + point: point, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + ); + } + + @override + void onPaint(Canvas canvas) { + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawPath(fillPath, paint); + } + } + + @override + void dispose() { + fillPath.reset(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart new file mode 100644 index 000000000..ac9cd17bb --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart @@ -0,0 +1,1754 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +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/theme.dart'; + +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/element_widget.dart'; +import '../common/legend.dart'; +import '../common/pyramid_data_label.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the pyramid series. +/// +/// To render a pyramid chart, create an instance of [PyramidSeries], and add +/// it to the series property of [SfPyramidChart], +/// it is the form of a triangle with lines dividing it into sections. +/// +/// Provides the property of color, opacity, border color and border width +/// for customizing the appearance. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} +class PyramidSeries extends ChartSeries { + /// Creates an instance of pyramid series base. + const PyramidSeries({ + super.key, + this.onCreateRenderer, + this.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.dataSource, + super.xValueMapper, + this.yValueMapper, + super.pointColorMapper, + ChartValueMapper? textFieldMapper, + super.name, + this.explodeIndex, + this.height = '80%', + this.width = '80%', + this.pyramidMode = PyramidMode.linear, + this.gapRatio = 0, + super.emptyPointSettings, + this.explodeOffset = '10%', + this.explode = false, + this.explodeGesture = ActivationMode.singleTap, + this.borderColor = Colors.transparent, + super.borderWidth, + super.legendIconType, + super.dataLabelSettings, + super.animationDuration, + super.animationDelay, + super.opacity, + super.selectionBehavior, + super.initialSelectedDataIndexes, + }) : super(dataLabelMapper: textFieldMapper); + + /// Maps the field name, which will be considered as y-values. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// yValueMapper: (ChartData data, _) => data.yVal, + /// ) + /// ], + /// ) + /// ); + /// } + /// class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + /// } + /// ``` + final ChartValueMapper? yValueMapper; + + /// Height of the series. + /// + /// Defaults to `80%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// height:'50%' + /// ) + /// ) + /// ); + /// } + /// ``` + final String height; + + /// Width of the series. + /// + /// Defaults to `80%`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// width:'50%' + /// ) + /// ) + /// ); + /// } + /// ``` + final String width; + + /// Specifies the rendering type of pyramid. + /// + /// Defaults to `PyramidMode.linear`. + /// + /// Also refer [PyramidMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// pyramidMode:PyramidMode.surface + /// ) + /// ) + /// ); + /// } + /// ``` + final PyramidMode pyramidMode; + + /// Gap ratio between the segments of pyramid. Ranges from 0 to 1. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// gapRatio: 0.3 + /// ) + /// ) + /// ); + /// } + /// ``` + final double gapRatio; + + /// Offset of exploded slice. The value ranges from 0% to 100%. + /// + /// Defaults to `10%`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explodeOffset: '5%') + /// ) + /// ); + /// } + /// ``` + final String explodeOffset; + + /// Enables or disables the explode of slices on tap. + /// + /// Default to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explode: true) + /// ) + /// ); + /// } + /// ``` + final bool explode; + + /// Gesture for activating the explode. + /// + /// Explode can be activated in `ActivationMode.none`, + /// `ActivationMode.singleTap`, `ActivationMode.doubleTap` + /// and `ActivationMode.longPress`. + /// + /// Defaults to `ActivationMode.singleTap`. + /// + /// Also refer [ActivationMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explode: true, + /// explodeGesture: ActivationMode.singleTap + /// ) + /// ) + /// ); + /// } + /// ``` + final ActivationMode explodeGesture; + + final Color borderColor; + + /// Index of the slice to explode it at the initial rendering. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explodeIndex: 1, + /// explode: true + /// ) + /// ) + /// ); + /// } + /// ``` + final num? explodeIndex; + + /// Used to create the renderer for custom series. + /// + /// This is applicable only when the custom series is defined in the sample + /// and for built-in series types, it is not applicable. + /// + /// Renderer created in this will hold the series state and + /// this should be created for each series. [onCreateRenderer] callback + /// function should return the renderer class and should not return null. + /// + /// Series state will be created only once per series and will not be created + /// again when we update the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// onCreateRenderer: (ChartSeries series) { + /// return CustomPyramidSeriesRenderer( + /// series as PyramidSeries); + /// } + /// ), + /// ) + /// ); + /// } + /// class CustomPyramidSeriesRenderer extends PyramidSeriesRenderer { + /// CustomPyramidSeriesRenderer(this.series); + /// final PyramidSeries series; + /// // custom implementation here... + /// } + /// ``` + final ChartSeriesRendererFactory? onCreateRenderer; + + /// Triggers when the series renderer is created. + /// Using this callback, able to get the [ChartSeriesController] instance, + /// which is used to access the public methods in the series. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// PyramidSeriesController _pyramidSeriesController; + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// onRendererCreated: + /// (PyramidSeriesController controller) { + /// _pyramidSeriesController = controller; + /// }, + /// ), + /// ) + /// ); + /// } + /// ``` + final PyramidSeriesRendererCreatedCallback? onRendererCreated; + + @override + Widget? childForSlot(SeriesSlot slot) { + switch (slot) { + case SeriesSlot.dataLabel: + return dataLabelSettings.isVisible + ? PyramidDataLabelContainer( + series: this, + dataSource: dataSource!, + mapper: dataLabelMapper, + builder: dataLabelSettings.builder, + settings: dataLabelSettings, + ) + : null; + + case SeriesSlot.marker: + return null; + + case SeriesSlot.trendline: + return null; + } + } + + @override + List get positions => [ + ChartDataPointType.y, + ]; + + // Create the pyramid series renderer. + @override + PyramidSeriesRenderer createRenderer() { + PyramidSeriesRenderer? renderer; + if (onCreateRenderer != null) { + renderer = onCreateRenderer!(this) as PyramidSeriesRenderer?; + assert( + renderer != null, + 'This onCreateRenderer callback function should return value as ' + 'extends from ChartSeriesRenderer class and should not be return ' + 'value as null', + ); + } + return renderer ?? PyramidSeriesRenderer(); + } + + @override + PyramidSeriesRenderer createRenderObject(BuildContext context) { + final PyramidSeriesRenderer renderer = + super.createRenderObject(context) as PyramidSeriesRenderer; + renderer + ..yValueMapper = yValueMapper + ..height = height + ..width = width + ..pyramidMode = pyramidMode + ..gapRatio = gapRatio + ..explodeOffset = explodeOffset + ..explode = explode + ..explodeGesture = explodeGesture + ..dataLabelMapper = dataLabelMapper + ..explodeIndex = explodeIndex + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..borderColor = borderColor + ..onCreateRenderer = onCreateRenderer + ..onRendererCreated = onRendererCreated + ..widget = this; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + PyramidSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..yValueMapper = yValueMapper + ..height = height + ..width = width + ..pyramidMode = pyramidMode + ..gapRatio = gapRatio + ..explodeOffset = explodeOffset + ..explode = explode + ..explodeGesture = explodeGesture + ..dataLabelMapper = dataLabelMapper + ..explodeIndex = explodeIndex + ..initialSelectedDataIndexes = initialSelectedDataIndexes + ..borderColor = borderColor + ..onCreateRenderer = onCreateRenderer + ..onRendererCreated = onRendererCreated + ..widget = this; + } +} + +/// Creates series renderer for Pyramid series. +class PyramidSeriesRenderer extends ChartSeriesRenderer + with RealTimeUpdateMixin { + final List _chaoticYValues = []; + final List yValues = []; + + PyramidSeriesRendererCreatedCallback? onRendererCreated; + + PyramidSeriesController get controller => _controller; + late final PyramidSeriesController _controller = + PyramidSeriesController(this); + + ChartValueMapper? get yValueMapper => _yValueMapper; + ChartValueMapper? _yValueMapper; + set yValueMapper(ChartValueMapper? value) { + if (_yValueMapper != value) { + _yValueMapper = value; + } + } + + String get height => _height; + String _height = '80%'; + set height(String value) { + if (_height != value) { + _height = value; + markNeedsLayout(); + } + } + + String get width => _width; + String _width = '80%'; + set width(String value) { + if (_width != value) { + _width = value; + markNeedsLayout(); + } + } + + double get gapRatio => _gapRatio; + double _gapRatio = 0; + set gapRatio(double value) { + if (_gapRatio != value) { + _gapRatio = value; + assert( + _gapRatio >= 0 && _gapRatio <= 1, + 'The gap ratio for the funnel chart must be between 0 and 1.', + ); + _gapRatio = clampDouble(value, 0, 1); + markNeedsLayout(); + } + } + + String get explodeOffset => _explodeOffset; + String _explodeOffset = '10%'; + set explodeOffset(String value) { + if (_explodeOffset != value) { + _explodeOffset = value; + transformValues(); + markNeedsPaint(); + } + } + + bool get explode => _explode; + bool _explode = false; + set explode(bool value) { + if (_explode != value) { + _explode = value; + _updateExploding(); + } + } + + ActivationMode get explodeGesture => _explodeGesture; + ActivationMode _explodeGesture = ActivationMode.singleTap; + set explodeGesture(ActivationMode value) { + if (_explodeGesture != value) { + _explodeGesture = value; + transformValues(); + markNeedsPaint(); + } + } + + PyramidMode get pyramidMode => _pyramidMode; + PyramidMode _pyramidMode = PyramidMode.linear; + set pyramidMode(PyramidMode value) { + if (_pyramidMode != value) { + _pyramidMode = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + num? get explodeIndex => _explodeIndex; + num? _explodeIndex; + set explodeIndex(num? value) { + if (_explodeIndex != value) { + _explodeIndex = value; + _updateExploding(); + } + } + + ChartSeriesRendererFactory? get onCreateRenderer => _onCreateRenderer; + ChartSeriesRendererFactory? _onCreateRenderer; + set onCreateRenderer(ChartSeriesRendererFactory? value) { + if (_onCreateRenderer != value) { + _onCreateRenderer = value; + } + } + + @override + Iterable get children { + return [if (dataLabelContainer != null) dataLabelContainer!]; + } + + /// Stores pointer down time to determine whether a long press interaction is handled at pointer up + DateTime? _pointerHoldingTime; + double _sumOfY = 0.0; + Rect _plotAreaBounds = Rect.zero; + Size _triangleSize = Size.zero; + double _coefficient = 0.0; + double _spacing = 0.0; + double _segmentHeight = 0.0; + double y = 0.0; + List _surfaceYValues = []; + List _heights = []; + double _surfaceHeight = 0.0; + + Offset dataLabelPosition(ChartElementParentData current, Size size) { + final List points = segments[current.dataPointIndex].points; + final List connectorPoints = segments[segments.length - 1].points; + final double connectorLength = _calculateConnectorLength( + connectorPoints, + dataLabelSettings, + ); + + final Offset insideLabelOffset = Offset( + (points[0].dx + points[1].dx) / 2 - size.width / 2, + (points[0].dy + points[2].dy) / 2 - size.height / 2, + ); + final double connectorLengthWithXValue = + insideLabelOffset.dx + connectorLength + size.width / 2; + final Offset outsideLabelOffset = Offset( + (connectorLengthWithXValue + + size.width + + dataLabelSettings.margin.left + + dataLabelSettings.margin.right > + paintBounds.right) + ? connectorLengthWithXValue - + (percentToValue(explodeOffset, paintBounds.width)!) + : connectorLengthWithXValue, + insideLabelOffset.dy, + ); + final Offset finalOffset = + dataLabelSettings.labelPosition == ChartDataLabelPosition.inside + ? insideLabelOffset + : outsideLabelOffset; + + return finalOffset; + } + + bool _isDataLabelIntersectOutside(RRect rect, RRect? previousRect) { + bool isIntersect = false; + const double padding = 2; + if (rect == previousRect) { + return isIntersect; + } + if (previousRect != null && rect.top - padding < previousRect.bottom) { + isIntersect = true; + } + return isIntersect; + } + + bool _isDataLabelIntersectInside(int index, List list) { + final Rect currentRect = list[index]; + Rect nextRect; + bool isIntersect = false; + if (index != list.length - 1) { + nextRect = list[index + 1]; + isIntersect = currentRect.overlaps(nextRect); + } + return isIntersect; + } + + Path _drawConnectorPath(int index, Offset offset, Size size) { + final List points = segments[index].points; + final double startPoint = (points[1].dx + points[2].dx) / 2; + final double endPoint = offset.dx; + final double y = offset.dy + size.height / 2; + return Path() + ..moveTo(startPoint, y) + ..lineTo(endPoint, y); + } + + RRect _calculateRect( + Offset offset, + int padding, + EdgeInsets margin, + Size size, + ChartDataLabelPosition labelPosition, + ) { + if (labelPosition == ChartDataLabelPosition.inside) { + return RRect.fromRectAndRadius( + Rect.fromLTWH( + offset.dx - padding, + offset.dy - padding, + size.width + (2 * padding), + size.height + (2 * padding), + ), + Radius.circular(dataLabelSettings.borderRadius), + ); + } + return RRect.fromRectAndRadius( + Rect.fromLTWH( + offset.dx, + offset.dy - margin.top, + size.width + margin.top + margin.left, + size.height + margin.bottom + margin.right, + ), + Radius.circular(dataLabelSettings.borderRadius), + ); + } + + Rect _calculateSegmentRect(List points, int index) { + final List points = segments[index].points; + final int bottom = points.length - 1; + final double x = (points[0].dx + points[bottom].dx) / 2; + final double right = (points[1].dx + points[bottom - 1].dx) / 2; + final Rect region = Rect.fromLTWH( + x, + points[0].dy, + right - x, + points[bottom].dy - points[0].dy, + ); + return region; + } + + double _calculateConnectorLength( + List points, + DataLabelSettings settings, + ) { + final Path segmentPath = + Path() + ..moveTo(points[0].dx, points[0].dy) + ..lineTo(points[1].dx, points[1].dy) + ..lineTo(points[2].dx, points[2].dy) + ..lineTo(points[3].dx, points[3].dy) + ..close(); + final Rect segmentBounds = segmentPath.getBounds(); + final double connectorLength = + percentToValue( + settings.connectorLineSettings.length ?? '0%', + _plotAreaBounds.width / 2, + )! + + (segmentBounds.width / 2); + return connectorLength; + } + + bool _findingCollision(Rect rect, List regions, [Rect? pathRect]) { + bool isCollide = false; + if (pathRect != null && + (pathRect.left < rect.left && + pathRect.width > rect.width && + pathRect.top < rect.top && + pathRect.height > rect.height)) { + isCollide = false; + } else if (pathRect != null) { + isCollide = true; + } + for (int i = 0; i < regions.length; i++) { + final RRect regionRect = regions[i]; + if ((rect.left < regionRect.left + regionRect.width && + rect.left + rect.width > regionRect.left) && + (rect.top < regionRect.top + regionRect.height && + rect.top + rect.height > regionRect.top)) { + isCollide = true; + break; + } + } + return isCollide; + } + + String _getTrimmedText( + String? text, + Rect rect, + Offset labelLocation, + Rect region, + TextStyle style, + ) { + const int labelPadding = 2; + const String ellipse = '...'; + String label = text!; + bool isCollide = _findingCollision(rect, [], region); + while (isCollide) { + if (label == ellipse) { + label = ''; + break; + } + if (label.length > ellipse.length) { + label = _addEllipse(label, label.length, ellipse); + } else { + label = ''; + break; + } + final Size trimTextSize = measureText(label, style); + final Rect trimRect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + trimTextSize.width + (2 * labelPadding), + trimTextSize.height + (2 * labelPadding), + ); + isCollide = _isLabelsColliding(trimRect, region); + } + return label == ellipse ? '' : label; + } + + String _addEllipse( + String text, + int maxLength, + String ellipse, { + bool isRtl = false, + }) { + if (isRtl) { + if (text.contains(ellipse)) { + text = text.replaceAll(ellipse, ''); + text = text.substring(1, text.length); + } else { + text = text.substring(ellipse.length, text.length); + } + return ellipse + text; + } else { + maxLength--; + final int length = maxLength - ellipse.length; + final String trimText = text.substring(0, length); + return trimText + ellipse; + } + } + + bool _isLabelsColliding(Rect rect, Rect? pathRect) { + bool isCollide = false; + if (pathRect != null && + (pathRect.left > rect.left && + pathRect.width > rect.width && + pathRect.top < rect.top && + pathRect.height > rect.height)) { + isCollide = false; + } else if (pathRect != null) { + isCollide = true; + } + return isCollide; + } + + void drawDataLabelWithBackground( + int index, + Canvas canvas, + String dataLabel, + Size size, + Offset offset, + int angle, + TextStyle style, + Paint fillPaint, + Paint strokePaint, + Path connectorPath, + List previousRect, + ) { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + final ChartSegment segment = segments[index]; + Color surfaceColor = dataLabelSurfaceColor( + fillPaint.color, + index, + dataLabelSettings.labelPosition, + chartThemeData, + themeData, + segment, + ); + TextStyle effectiveTextStyle = saturatedTextStyle(surfaceColor, style); + final EdgeInsets margin = dataLabelSettings.margin; + final Radius radius = Radius.circular(dataLabelSettings.borderRadius); + Offset finalOffset = offset; + final List points = segments[index].points; + bool isOverlapRight = false; + final double startPoint = (points[1].dx + points[2].dx) / 2; + ChartDataLabelPosition labelPosition = dataLabelSettings.labelPosition; + const int labelPadding = 2; + const int connectorPadding = 15; + final List connectorPoints = segments[segments.length - 1].points; + final List labels = []; + final double connectorLength = _calculateConnectorLength( + connectorPoints, + dataLabelSettings, + ); + final Paint connectorPaint = + Paint() + ..color = + dataLabelSettings.connectorLineSettings.color ?? + segments[index].fillPaint.color + ..strokeWidth = dataLabelSettings.connectorLineSettings.width + ..style = PaintingStyle.stroke; + + if ((yValues[index] == 0 && !dataLabelSettings.showZeroValue) || + yValues[index].isNaN || + !segments[index].isVisible) { + return; + } + + for (int i = 0; i < segments.length; i++) { + final List points = segmentAt(i).points; + final Offset point = Offset( + (points[0].dx + points[1].dx) / 2, + (points[0].dy + points[2].dy) / 2, + ); + labels.add( + Rect.fromLTWH( + point.dx - (size.width / 2) - labelPadding, + point.dy - (size.height / 2) - labelPadding, + size.width + (2 * labelPadding), + size.height + (2 * labelPadding), + ), + ); + } + + if (!offset.dx.isNaN && !offset.dy.isNaN) { + if (dataLabel.isNotEmpty) { + if (fillPaint.color != Colors.transparent || + (strokePaint.color != const Color.fromARGB(0, 25, 5, 5) && + strokePaint.strokeWidth > 0)) { + RRect labelRect = _calculateRect( + offset, + labelPadding, + margin, + size, + labelPosition, + ); + final Rect region = _calculateSegmentRect( + segments[index].points, + index, + ); + final bool isDataLabelCollide = + (_findingCollision(labels[index], previousRect, region)) && + dataLabelSettings.labelPosition != ChartDataLabelPosition.outside; + if (isDataLabelCollide) { + switch (dataLabelSettings.overflowMode) { + case OverflowMode.trim: + dataLabel = _getTrimmedText( + dataLabel, + labels[index], + finalOffset, + region, + effectiveTextStyle, + ); + final Size trimSize = measureText( + dataLabel, + effectiveTextStyle, + ); + finalOffset = Offset( + finalOffset.dx + size.width / 2 - trimSize.width / 2, + finalOffset.dy + size.height / 2 - trimSize.height / 2, + ); + labelRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + finalOffset.dx - labelPadding, + finalOffset.dy - labelPadding, + trimSize.width + (2 * labelPadding), + trimSize.height + (2 * labelPadding), + ), + radius, + ); + break; + case OverflowMode.hide: + dataLabel = ''; + break; + case OverflowMode.shift: + break; + // ignore: no_default_cases + default: + break; + } + } + final bool isIntersect = _isDataLabelIntersectInside(index, labels); + if (isIntersect && + dataLabelSettings.labelPosition == + ChartDataLabelPosition.inside && + dataLabelSettings.color == null && + !dataLabelSettings.useSeriesColor) { + if (style.color == Colors.transparent) { + surfaceColor = dataLabelSurfaceColor( + fillPaint.color, + index, + ChartDataLabelPosition.outside, + chartThemeData, + themeData, + segment, + ); + effectiveTextStyle = saturatedTextStyle(surfaceColor, style); + } + } + if ((isIntersect && + labelPosition == ChartDataLabelPosition.inside && + dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide) || + dataLabel == '') { + return; + } else if (isIntersect && + labelPosition == ChartDataLabelPosition.inside && + dataLabelSettings.labelIntersectAction == + LabelIntersectAction.none) { + } else if ((isDataLabelCollide && + dataLabelSettings.overflowMode == OverflowMode.shift) || + isIntersect || + (dataLabelSettings.labelPosition == + ChartDataLabelPosition.outside)) { + labelPosition = ChartDataLabelPosition.outside; + finalOffset = + dataLabelSettings.labelPosition == + ChartDataLabelPosition.outside + ? offset + : offset + Offset(connectorLength + size.width / 2, 0); + labelRect = _calculateRect( + finalOffset, + labelPadding, + margin, + size, + labelPosition, + ); + connectorPath = _drawConnectorPath(index, finalOffset, size); + if (_plotAreaBounds.right < labelRect.right) { + isOverlapRight = true; + labelRect = RRect.fromRectAndRadius( + Rect.fromLTRB( + _plotAreaBounds.right - labelRect.width - labelPadding, + labelRect.top, + _plotAreaBounds.right - labelPadding, + labelRect.bottom, + ), + radius, + ); + } + final RRect previous = + previousRect.isEmpty + ? labelRect + : previousRect[previousRect.length - 1]; + final bool isIntersectOutside = _isDataLabelIntersectOutside( + labelRect, + previous, + ); + if (!isIntersectOutside && isOverlapRight) { + finalOffset = Offset( + labelRect.left, + (labelRect.top + labelRect.height / 2) - size.height / 2, + ); + connectorPath = _drawConnectorPath(index, finalOffset, size); + } + if (dataLabelSettings.labelIntersectAction == + LabelIntersectAction.hide && + isIntersectOutside) { + return; + } else if ((isIntersectOutside && + dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) || + (isOverlapRight && isIntersectOutside) || + (isOverlapRight && + dataLabelSettings.labelPosition == + ChartDataLabelPosition.outside && + index != 0)) { + labelRect = RRect.fromRectAndRadius( + Rect.fromLTWH( + labelRect.left, + previous.bottom + 2, + labelRect.width, + labelRect.height, + ), + radius, + ); + connectorPath = + Path() + ..moveTo(startPoint, finalOffset.dy + size.height / 2) + ..lineTo( + labelRect.left - connectorPadding, + labelRect.top + labelRect.height / 2, + ) + ..lineTo( + labelRect.left, + labelRect.top + labelRect.height / 2, + ); + } + finalOffset = Offset( + labelRect.left + margin.left, + (labelRect.top + labelRect.height / 2) - size.height / 2, + ); + previousRect.add(labelRect); + } + if (_plotAreaBounds.height < labelRect.bottom + labelPadding) { + return; + } + if (labelPosition == ChartDataLabelPosition.outside) { + canvas.drawPath(connectorPath, connectorPaint); + } + canvas.save(); + canvas.translate(labelRect.center.dx, labelRect.center.dy); + canvas.rotate((angle * pi) / 180); + canvas.translate(-labelRect.center.dx, -labelRect.center.dy); + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + canvas.drawRRect(labelRect, strokePaint); + } + if (fillPaint.color != Colors.transparent) { + canvas.drawRRect(labelRect, fillPaint); + } + canvas.restore(); + } + } + } + drawDataLabel(canvas, dataLabel, finalOffset, effectiveTextStyle, angle); + } + + void drawDataLabel( + Canvas canvas, + String text, + Offset point, + TextStyle style, + int angle, [ + bool? isRtl, + ]) { + final int maxLines = getMaxLinesContent(text); + final TextSpan span = TextSpan(text: text, style: style); + final TextPainter tp = TextPainter( + text: span, + textDirection: (isRtl ?? false) ? TextDirection.rtl : TextDirection.ltr, + textAlign: TextAlign.center, + maxLines: maxLines, + ); + tp.layout(); + canvas.save(); + canvas.translate(point.dx + tp.width / 2, point.dy + tp.height / 2); + Offset labelOffset = Offset.zero; + canvas.rotate(degreeToRadian(angle)); + labelOffset = Offset(-tp.width / 2, -tp.height / 2); + tp.paint(canvas, labelOffset); + canvas.restore(); + } + + @override + void attach(PipelineOwner owner) { + onRendererCreated?.call(_controller); + super.attach(owner); + } + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + yValues.clear(); + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + markNeedsLegendUpdate(); + populateChartPoints(); + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[yValues]; + positions = [ChartDataPointType.y]; + } else { + yLists.add(yValues); + positions!.add(ChartDataPointType.y); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + if (yPaths == null) { + yPaths = >[]; + chaoticYLists = >[]; + yLists = >[]; + } + + if (yValueMapper != null) { + yPaths.add(yValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticYLists?.add(yValues); + } else { + chaoticYLists?.add(_chaoticYValues); + yLists?.add(yValues); + } + } + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + } + + @override + void performLayout() { + // Need to recalculate the triangle size based on plot area, + // and its required to update the segment values in layout. + // So assigned true here to update the segment values. + canUpdateOrCreateSegments = true; + _calculatePyramidValues(); + super.performLayout(); + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[yValues] + ..animation = dataLabelAnimation + ..layout(constraints); + } + } + + void _calculatePyramidValues() { + _sumOfY = 0; + final int segmentsCount = segments.length; + for (int i = 0; i < dataCount; i++) { + bool isVisible = true; + if (i < segmentsCount) { + isVisible = segmentAt(i).isVisible; + } + final num yValue = isVisible ? yValues[i] : 0; + if (!yValue.isNaN) { + _sumOfY += yValue.abs(); + } + } + + _plotAreaBounds = Rect.fromLTWH(0, 0, size.width, size.height); + _triangleSize = Size( + percentToValue(width, _plotAreaBounds.width)!, + percentToValue(height, _plotAreaBounds.height)!, + ); + _coefficient = 1 / (_sumOfY * (1 + gapRatio / (1 - gapRatio))); + _spacing = gapRatio / (dataCount - emptyPointIndexes.length - 1); + y = 0; + + if (_pyramidMode == PyramidMode.surface) { + _surfaceYValues = []; + _heights = []; + _surfaceHeight = _calculatesurfaceHeight(0, _sumOfY); + y = 0; + + for (int i = 0; i < dataCount; i++) { + _surfaceYValues.add(y); + bool isVisible = true; + if (segments.isNotEmpty) { + isVisible = segmentAt(i).isVisible; + } + final num yValue = isVisible ? yValues[i] : 0; + _heights.add(_calculatesurfaceHeight(y, yValue)); + y += _heights[i] + _spacing * _surfaceHeight; + } + + _coefficient = 1 / (y - _spacing * _surfaceHeight); + } + } + + @override + @nonVirtual + Color effectiveColor(int segmentIndex) { + Color? pointColor; + if (pointColorMapper != null) { + pointColor = pointColors[segmentIndex]; + } + return pointColor ?? palette[segmentIndex % palette.length]; + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + switch (pyramidMode) { + case PyramidMode.linear: + _buildLinearTypeSegment(index, segment); + break; + case PyramidMode.surface: + _buildSurfaceTypeSegment(index, segment); + break; + } + } + + void _buildLinearTypeSegment(int index, ChartSegment segment) { + num yValue = yValues[index].abs(); + // Handled the empty point here. + yValue = yValue.isNaN || !segment.isVisible ? 0 : yValue; + _segmentHeight = _coefficient * yValue; + + segment as PyramidSegment + ..series = this + .._height = _segmentHeight + ..y = y + .._triangleSize = _triangleSize + .._plotAreaBounds = _plotAreaBounds + ..isExploded = explode && index == explodeIndex + ..isEmpty = + (emptyPointSettings.mode != EmptyPointMode.drop && + emptyPointSettings.mode != EmptyPointMode.gap) && + isEmpty(index); + + y += _segmentHeight + _spacing; + } + + void _buildSurfaceTypeSegment(int index, ChartSegment segment) { + segment as PyramidSegment + ..series = this + ..y = _coefficient * _surfaceYValues[index] + .._height = _coefficient * _heights[index] + .._triangleSize = _triangleSize + .._plotAreaBounds = _plotAreaBounds + ..isExploded = explode && index == explodeIndex + ..isEmpty = + (emptyPointSettings.mode != EmptyPointMode.drop && + emptyPointSettings.mode != EmptyPointMode.gap) && + isEmpty(index); + } + + double _calculatesurfaceHeight(double y, num surface) { + const double a = 1; + final double b = 2 * y; + final num c = -surface; + final double d = b * b - 4 * a * c; + if (d >= 0) { + final double sd = sqrt(d); + final double root1 = (-b - sd) / (2 * a); + final double root2 = (-b + sd) / (2 * a); + return max(root1, root2); + } + return 0; + } + + @override + PyramidSegment createSegment() => PyramidSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.pyramidSeries; + + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, borderColor, borderWidth); + } + + @override + List? buildLegendItems(int index) { + final List legendItems = []; + final int segmentsCount = segments.length; + for (int i = 0; i < dataCount; i++) { + final PyramidLegendItem legendItem = PyramidLegendItem( + text: xRawValues[i].toString(), + iconType: toLegendShapeMarkerType(legendIconType, this), + iconColor: effectiveColor(i), + iconBorderWidth: legendIconBorderWidth(), + series: this, + seriesIndex: index, + pointIndex: i, + imageProvider: + legendIconType == LegendIconType.image + ? parent?.legend?.image + : null, + isToggled: i < segmentsCount && !segmentAt(i).isVisible, + onTap: handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ); + legendItems.add(legendItem); + } + return legendItems; + } + + void _handleLegendItemCreated(ItemRendererDetails details) { + if (parent != null && parent!.onLegendItemRender != null) { + final PyramidLegendItem item = details.item as PyramidLegendItem; + final LegendIconType iconType = toLegendIconType(details.iconType); + final LegendRenderArgs args = + LegendRenderArgs(item.seriesIndex, item.pointIndex) + ..text = details.text + ..legendIconType = iconType + ..color = details.color; + parent!.onLegendItemRender!(args); + if (args.legendIconType != iconType) { + details.iconType = toLegendShapeMarkerType( + args.legendIconType ?? LegendIconType.seriesType, + this, + ); + } + details + ..text = args.text ?? '' + ..color = args.color ?? Colors.transparent; + } + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + super.handleLegendItemTapped(item, isToggled); + + final PyramidLegendItem legendItem = item as PyramidLegendItem; + final int toggledIndex = legendItem.pointIndex; + segments[toggledIndex].isVisible = !isToggled; + + canUpdateOrCreateSegments = true; + markNeedsLayout(); + legendItem.onToggled?.call(); + } + + // TODO(praveen): When we update data points, load time animation occurs. + // Need to handle this. + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isTransposed: true, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintDataLabels(context, offset); + } + + void paintDataLabels(PaintingContext context, Offset offset) { + if (dataLabelContainer != null) { + context.paintChild(dataLabelContainer!, offset); + } + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + final bool isHit = super.hitTest(result, position: position); + return explode || isHit; + } + + @override + void handlePointerDown(PointerDownEvent details) { + if (explode && explodeGesture == ActivationMode.singleTap) { + _pointerHoldingTime = DateTime.now(); + } + super.handlePointerDown(details); + } + + @override + void handlePointerUp(PointerUpEvent details) { + if (explode && + explodeGesture == ActivationMode.singleTap && + _pointerHoldingTime != null && + DateTime.now().difference(_pointerHoldingTime!).inMilliseconds < + kLongPressTimeout.inMilliseconds) { + _handleExploding(details.localPosition); + } + super.handlePointerUp(details); + } + + @override + void handleDoubleTap(Offset position) { + final Offset localPosition = globalToLocal(position); + if (explode && explodeGesture == ActivationMode.doubleTap) { + _handleExploding(localPosition); + } + super.handleDoubleTap(position); + } + + @override + void handleLongPressStart(LongPressStartDetails details) { + if (explode && explodeGesture == ActivationMode.longPress) { + _handleExploding(details.localPosition); + } + super.handleLongPressStart(details); + } + + void _handleExploding(Offset position) { + for (final ChartSegment segment in segments) { + final PyramidSegment pyramidSegment = + segment as PyramidSegment; + if (segment.contains(position)) { + pyramidSegment.isExploded = !pyramidSegment.isExploded; + if (pyramidSegment.isExploded) { + _explodeIndex = segment.currentSegmentIndex; + } else { + _explodeIndex = -1; + } + } else { + pyramidSegment.isExploded = false; + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + void _updateExploding() { + for (final ChartSegment segment in segments) { + final PyramidSegment pyramidSegment = + segment as PyramidSegment; + if (!explode) { + pyramidSegment.isExploded = false; + } else { + if (segment.currentSegmentIndex == explodeIndex) { + pyramidSegment.isExploded = true; + } else { + pyramidSegment.isExploded = false; + } + } + } + + forceTransformValues = true; + markNeedsLayout(); + } + + @override + int viewportIndex(int index, [List? visibleIndexes]) { + return index; + } + + @override + void dispose() { + _chaoticYValues.clear(); + yValues.clear(); + super.dispose(); + } +} + +class PyramidSegment extends ChartSegment { + late PyramidSeriesRenderer series; + late double y; + late double _height; + late Size _triangleSize; + late Rect _plotAreaBounds; + bool isExploded = false; + Path path = Path(); + + @override + void transformValues() { + points.clear(); + + final double marginSpace = + (isExploded + ? percentToValue(series.explodeOffset, _plotAreaBounds.width)! + : 0) + + (_plotAreaBounds.width - _triangleSize.width) / 2; + final double pyramidTop = + _plotAreaBounds.top + + (_plotAreaBounds.height - _triangleSize.height) / 2; + final double pyramidLeft = marginSpace + _plotAreaBounds.left; + final double heightRatio = pyramidTop / _triangleSize.height; + + double top = y; + double bottom = y + _height; + + final double topRadius = (1 - top) / 2; + final double bottomRadius = (1 - bottom) / 2; + + top += heightRatio; + bottom += heightRatio; + + final double topX1 = pyramidLeft + topRadius * _triangleSize.width; + final double topX2 = pyramidLeft + (1 - topRadius) * _triangleSize.width; + final double bottomX1 = + pyramidLeft + (1 - bottomRadius) * _triangleSize.width; + final double bottomX2 = pyramidLeft + bottomRadius * _triangleSize.width; + final double topY = top * _triangleSize.height; + final double bottomY = bottom * _triangleSize.height; + + path + ..reset() + ..moveTo(topX1, topY) + ..lineTo(topX2, topY) + ..lineTo(bottomX1, bottomY) + ..lineTo(bottomX2, bottomY) + ..close(); + + points + ..add(Offset(topX1, topY)) + ..add(Offset(topX2, topY)) + ..add(Offset(bottomX1, bottomY)) + ..add(Offset(bottomX2, bottomY)); + } + + @override + Paint getFillPaint() => fillPaint; + + @override + Paint getStrokePaint() => strokePaint; + + @override + void calculateSegmentPoints() {} + + @override + bool contains(Offset position) { + return path.contains(position); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + final ChartPoint point = ChartPoint( + x: series.xRawValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + ); + final Offset location = path.getBounds().center; + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? series.localToGlobal(position ?? location) + : series.localToGlobal(location); + return ChartTooltipInfo( + primaryPosition: preferredPos, + secondaryPosition: preferredPos, + text: series.tooltipText(point), + header: '', + data: series.dataSource![currentSegmentIndex], + point: point, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + ); + } + + @override + void onPaint(Canvas canvas) { + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(path, paint); + } + + paint = getStrokePaint(); + if (strokePaint.color != Colors.transparent && + strokePaint.strokeWidth > 0) { + canvas.drawPath(path, paint); + } + } + + @override + void dispose() { + path.reset(); + super.dispose(); + } +} + +/// 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. +class PyramidSeriesController { + /// Creating an argument constructor of PyramidSeriesController class. + PyramidSeriesController(this.seriesRenderer); + + /// Used to access the series properties. + /// + /// Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// onRendererCreated: + /// (PyramidSeriesController controller) { + /// _chartSeriesController = controller; + /// // prints series yAxisName + /// print(_chartSeriesController.seriesRenderer. + /// seriesRendererDetails.series.yAxisName); + /// }, + /// ), + /// ) + /// ); + ///} + ///``` + final PyramidSeriesRenderer seriesRenderer; + + /// Used to process only the newly added, updated and removed data points + /// in a series, instead of processing all the data points. + /// + /// To re-render the chart with modified data points, setState() will be + /// called. This will render the process and render the chart from scratch. + /// Thus, the app’s performance will be degraded on continuous update. + /// To overcome this problem, [updateDataSource] method can be called by + /// passing updated data points indexes. Chart will process only that point + /// and skip various steps like bounds calculation, old data points + /// processing, etc. Thus, this will improve the app’s performance. + /// + /// The following are the arguments of this method. + /// * addedDataIndexes – `List` type – Indexes of newly added data points + /// in the existing series. + /// * removedDataIndexes – `List` type – Indexes of removed data points + /// in the existing series. + /// * updatedDataIndexes – `List` type – Indexes of updated data points + /// in the existing series. + /// * addedDataIndex – `int` type – Index of newly added data point + /// in the existing series. + /// * removedDataIndex – `int` type – Index of removed data point + /// in the existing series. + /// * updatedDataIndex – `int` type – Index of updated data point + /// in the existing series. + /// + /// Returns `void`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// PyramidSeriesController _pyramidSeriesController; + /// return Column( + /// children: [ + /// Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataSource: chartData, + /// onRendererCreated: + /// (PyramidSeriesController controller) { + /// _pyramidSeriesController = controller; + /// }, + /// ), + /// ) + /// ), + /// Container( + /// child: RaisedButton( + /// onPressed: () { + /// chartData.removeAt(0); + /// chartData.add(ChartData(3,23)); + /// _pyramidSeriesController.updateDataSource( + /// addedDataIndexes: [chartData.length -1], + /// removedDataIndexes: [0], + /// ); + /// }) + /// )] + /// ); + /// } + ///``` + void updateDataSource({ + List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int addedDataIndex = -1, + int removedDataIndex = -1, + int updatedDataIndex = -1, + }) { + final RealTimeUpdateMixin renderer = seriesRenderer; + + List? effectiveRemovedIndexes; + List? effectiveAddedIndexes; + List? effectiveReplacedIndexes; + + if (removedDataIndexes != null) { + effectiveRemovedIndexes = List.from(removedDataIndexes); + } + + if (addedDataIndexes != null) { + effectiveAddedIndexes = List.from(addedDataIndexes); + } + + if (updatedDataIndexes != null) { + effectiveReplacedIndexes = List.from(updatedDataIndexes); + } + + if (removedDataIndex != -1) { + effectiveRemovedIndexes ??= []; + effectiveRemovedIndexes.add(removedDataIndex); + } + + if (addedDataIndex != -1) { + effectiveAddedIndexes ??= []; + effectiveAddedIndexes.add(addedDataIndex); + } + + if (updatedDataIndex != -1) { + effectiveReplacedIndexes ??= []; + effectiveReplacedIndexes.add(updatedDataIndex); + } + + renderer.updateDataPoints( + effectiveRemovedIndexes, + effectiveAddedIndexes, + effectiveReplacedIndexes, + ); + } + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and returns + /// a chart data point. + /// + ///```dart + /// late PyramidSeriesController pyramidSeriesController; + /// SfPyramidChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = + /// seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// }, + /// series: PyramidSeries( + /// onRendererCreated: (PyramidSeriesController seriesController) { + /// pyramidSeriesController = seriesController; + /// } + /// ), + /// ); + /// ``` + PointInfo pixelToPoint(Offset position) { + int pointIndex = -1; + final List segments = seriesRenderer.segments; + for (int i = 0; i < segments.length; i++) { + final PyramidSegment segment = segments[i] as PyramidSegment; + if (segment.contains(position)) { + pointIndex = i; + } + } + final dynamic x = seriesRenderer.xValues[pointIndex]; + final num y = seriesRenderer.yValues[pointIndex]; + return PointInfo(x, y); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/radial_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/radial_bar_series.dart new file mode 100644 index 000000000..56dedd9e5 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/radial_bar_series.dart @@ -0,0 +1,935 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/circular_data_label.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/legend.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/renderer_helper.dart'; +import 'chart_series.dart'; + +/// Renders the radial bar series. +/// +/// The radial bar chart is used for showing the comparisons among the +/// categories using the circular shapes. To render a radial bar chart, create +/// an instance of RadialBarSeries, and add to the series collection property +/// of [SfCircularChart]. +/// +/// Provides options to customize the [maximumValue], [trackColor], +/// [trackBorderColor], [trackBorderWidth], [trackOpacity] +/// and [useSeriesColor] of the radial segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} +class RadialBarSeries extends CircularSeries { + /// Creating an argument constructor of RadialBarSeries class. + /// + const RadialBarSeries({ + super.key, + super.onCreateRenderer, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.pointColorMapper, + super.pointShaderMapper, + super.pointRadiusMapper, + super.dataLabelMapper, + super.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, + super.dataLabelSettings, + super.radius = '80%', + super.innerRadius = '50%', + super.gap = '1%', + super.opacity, + Color strokeColor = Colors.transparent, + double strokeWidth = 2.0, + super.enableTooltip = true, + super.name, + super.animationDuration, + super.animationDelay, + super.selectionBehavior, + super.sortingOrder, + super.legendIconType, + super.cornerStyle = CornerStyle.bothFlat, + super.initialSelectedDataIndexes, + }) : super(borderColor: strokeColor, borderWidth: strokeWidth); + + /// Color of the track. + /// + /// Defaults to `const Color.fromRGBO(234, 236, 239, 1.0)`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// trackColor: Colors.red, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final Color trackColor; + + /// Specifies the maximum value of the radial bar. + /// + /// By default, the sum of the data points values will be considered + /// as maximum value. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// maximumValue: 100, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final double? maximumValue; + + /// Border color of the track. + /// + /// Defaults to `Colors.Transparent`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// trackBorderColor: Colors.red, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final Color trackBorderColor; + + /// Border width of the track. + /// + /// Defaults to `0.0`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// trackBorderWidth: 2, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final double trackBorderWidth; + + /// Opacity of the track. + /// + /// Defaults to `1`. + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// trackOpacity: 0.2, + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final double trackOpacity; + + /// Uses the point color for filling the track. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// RadialBarSeries( + /// useSeriesColor:true + /// ), + /// ], + /// ) + /// ); + /// } + /// ``` + final bool useSeriesColor; + + @override + List get positions => [ + ChartDataPointType.y, + ]; + + /// Create the Radial bar series renderer. + @override + RadialBarSeriesRenderer createRenderer() { + RadialBarSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as RadialBarSeriesRenderer; + return seriesRenderer; + } + return RadialBarSeriesRenderer(); + } + + @override + RadialBarSeriesRenderer createRenderObject(BuildContext context) { + final RadialBarSeriesRenderer renderer = + super.createRenderObject(context) as RadialBarSeriesRenderer; + renderer + ..trackColor = trackColor + ..maximumValue = maximumValue + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackOpacity = trackOpacity + ..useSeriesColor = useSeriesColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RadialBarSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..trackColor = trackColor + ..maximumValue = maximumValue + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackOpacity = trackOpacity + ..useSeriesColor = useSeriesColor; + } +} + +/// Creates a series renderer for radial bar series. +class RadialBarSeriesRenderer extends CircularSeriesRenderer { + /// Calling the default constructor of [RadialBarSeriesRenderer] class. + RadialBarSeriesRenderer(); + + Color get trackColor => _trackColor; + Color _trackColor = const Color.fromRGBO(234, 236, 239, 1.0); + set trackColor(Color value) { + if (_trackColor != value) { + _trackColor = value; + markNeedsPaint(); + } + } + + double? get maximumValue => _maximumValue; + double? _maximumValue; + set maximumValue(double? value) { + if (_maximumValue != value) { + _maximumValue = value; + markNeedsLayout(); + } + } + + Color get trackBorderColor => _trackBorderColor; + Color _trackBorderColor = Colors.transparent; + set trackBorderColor(Color value) { + if (_trackBorderColor != value) { + _trackBorderColor = value; + markNeedsPaint(); + } + } + + double get trackBorderWidth => _trackBorderWidth; + double _trackBorderWidth = 0.0; + set trackBorderWidth(double value) { + if (_trackBorderWidth != value) { + _trackBorderWidth = value; + markNeedsPaint(); + } + } + + double get trackOpacity => _trackOpacity; + double _trackOpacity = 1; + set trackOpacity(double value) { + if (_trackOpacity != value) { + _trackOpacity = value; + markNeedsPaint(); + } + } + + bool get useSeriesColor => _useSeriesColor; + bool _useSeriesColor = false; + set useSeriesColor(bool value) { + if (_useSeriesColor != value) { + _useSeriesColor = value; + markNeedsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + final num yValue = segment.isVisible ? circularYValues[index] : 0; + double degree = yValue / (maximumValue ?? (sumOfY != 0 ? sumOfY : 1)); + degree = degree * fullAngle; + final double pointEndAngle = pointStartAngle + degree; + final double innerRadius = + currentInnerRadius = + segment.isVisible + ? (currentInnerRadius + + ((index == firstVisibleIndex) ? 0 : ringSize) - + (trackBorderWidth / 2) / dataCount) + : currentInnerRadius; + final double outerRadius = + ringSize < segmentGap! + ? 0 + : innerRadius + + ringSize - + segmentGap! - + (trackBorderWidth / 2) / dataCount; + + segment as RadialBarSegment + ..series = this + .._degree = degree + .._startAngle = pointStartAngle + ..endAngle = pointEndAngle + .._center = center + ..innerRadius = innerRadius + ..outerRadius = outerRadius; + } + + @override + RadialBarSegment createSegment() => RadialBarSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.radialBarSeries; + + @override + void customizeSegment(ChartSegment segment) { + if (segment is RadialBarSegment) { + updateSegmentColor(segment, borderColor, borderWidth); + + if (trackColor != Colors.transparent) { + if (useSeriesColor) { + segment.trackFillPaint.color = segment.fillPaint.color.withValues( + alpha: trackOpacity, + ); + } else { + segment.trackFillPaint.color = trackColor.withValues( + alpha: trackOpacity, + ); + } + } else { + if (useSeriesColor) { + segment.trackFillPaint.color = segment.fillPaint.color; + } else { + segment.trackFillPaint.color = trackColor; + } + } + + segment.trackStrokePaint + ..color = trackBorderColor + ..strokeWidth = trackBorderWidth; + + updateSegmentGradient(segment); + } + } + + @override + List? buildLegendItems(int index) { + if (circularYValues.isEmpty) { + return null; + } + final num sumOfY = circularYValues.reduce( + (num value, num element) => value + element.abs(), + ); + const double pointStartAngle = -90; + final List legendItems = []; + final int segmentsCount = segments.length; + for (int i = 0; i < dataCount; i++) { + double degree = circularYValues[i] / (maximumValue ?? sumOfY); + degree = (degree > 1 ? 1 : degree) * fullAngle; + final double pointEndAngle = pointStartAngle + degree; + + final CircularLegendItem legendItem = CircularLegendItem( + text: circularXValues[i].toString(), + iconType: toLegendShapeMarkerType(legendIconType, this), + iconColor: effectiveColor(i), + shader: _legendIconShaders(i), + series: this, + seriesIndex: index, + pointIndex: i, + startAngle: pointStartAngle, + endAngle: pointEndAngle, + degree: degree, + iconBorderColor: trackColor, + iconBorderWidth: legendIconBorderWidth(), + imageProvider: + legendIconType == LegendIconType.image + ? parent?.legend?.image + : null, + isToggled: i < segmentsCount && !segmentAt(i).isVisible, + onTap: handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ); + legendItems.add(legendItem); + } + return legendItems; + } + + @override + void handleLegendItemTapped(LegendItem item, bool isToggled) { + super.handleLegendItemTapped(item, isToggled); + // Resets `_isLegendToggled` to `true` to handle legend inner and outer radius animations. + if (item is CircularLegendItem && item.pointIndex != -1) { + final RadialBarSegment segment = + segmentAt(item.pointIndex) as RadialBarSegment; + segment._isLegendToggled = true; + } + } + + void _handleLegendItemCreated(ItemRendererDetails details) { + if (parent != null && parent!.onLegendItemRender != null) { + final CircularLegendItem item = details.item as CircularLegendItem; + final LegendIconType iconType = toLegendIconType(details.iconType); + final LegendRenderArgs args = + LegendRenderArgs(item.seriesIndex, item.pointIndex) + ..text = details.text + ..legendIconType = iconType + ..color = details.color; + parent!.onLegendItemRender!(args); + if (args.legendIconType != iconType) { + details.iconType = toLegendShapeMarkerType( + args.legendIconType ?? LegendIconType.seriesType, + this, + ); + } + + details + ..text = args.text ?? '' + ..color = args.color ?? Colors.transparent; + } + } + + Shader? _legendIconShaders(int pointIndex) { + if (parent != null && parent!.legend != null) { + final Rect legendIconBounds = Rect.fromLTWH( + 0.0, + 0.0, + parent!.legend!.iconWidth, + parent!.legend!.iconHeight, + ); + if (pointShaderMapper != null) { + return pointShaderMapper!( + dataSource![pointIndex], + pointIndex, + palette[pointIndex % palette.length], + legendIconBounds, + ); + } else if (onCreateShader != null) { + final ChartShaderDetails details = ChartShaderDetails( + legendIconBounds, + legendIconBounds, + 'legend', + ); + return onCreateShader?.call(details); + } + } + return null; + } + + @override + Offset dataLabelPosition(CircularDataLabelBoxParentData current, Size size) { + const int labelPadding = 2; + final num angle = dataLabelSettings.angle; + + final RadialBarSegment segment = + segments[current.dataPointIndex] as RadialBarSegment; + current.point! + ..degree = segment._degree + ..isVisible = segment.isVisible + ..startAngle = segment._startAngle + ..endAngle = segment._endAngle + ..midAngle = (segment._startAngle + segment._endAngle) / 2 + ..innerRadius = segment._innerRadius + ..outerRadius = segment._outerRadius + ..center = center + ..fill = palette[current.dataPointIndex % palette.length]; + final CircularChartPoint point = current.point!; + + Offset labelLocation = calculateOffset( + point.startAngle!, + (point.innerRadius! + point.outerRadius!) / 2, + point.center!, + ); + labelLocation = Offset( + (labelLocation.dx - size.width - 5) + (angle == 0 ? 0 : size.width / 2), + (labelLocation.dy - size.height / 2) + (angle == 0 ? 0 : size.height / 2), + ); + if (point.isVisible && (point.y == 0 && !dataLabelSettings.showZeroValue)) { + point.isVisible = false; + return labelLocation; + } + if (size.width > 0 && size.height > 0) { + point.labelRect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + size.width + (2 * labelPadding), + size.height + (2 * labelPadding), + ); + } else { + point.labelRect = Rect.zero; + } + return labelLocation; + } + + @override + void drawDataLabelWithBackground( + CircularChartDataLabelPositioned dataLabelPositioned, + int index, + Canvas canvas, + String dataLabel, + Offset offset, + int angle, + TextStyle style, + Paint fillPaint, + Paint strokePaint, + ) { + final TextStyle effectiveTextStyle = parent!.themeData!.textTheme.bodySmall! + .copyWith(color: Colors.black) + .merge(parent!.chartThemeData!.dataLabelTextStyle) + .merge(dataLabelSettings.textStyle); + + final CircularChartPoint point = dataLabelPositioned.point!; + if (!point.isVisible || !segments[index].isVisible) { + return; + } + + final Rect labelRect = point.labelRect; + canvas.save(); + canvas.translate(labelRect.center.dx, labelRect.center.dy); + canvas.rotate((angle * pi) / 180); + canvas.translate(-labelRect.center.dx, -labelRect.center.dy); + if (dataLabelSettings.borderWidth > 0 && + strokePaint.color != Colors.transparent) { + _drawLabelRect( + strokePaint, + Rect.fromLTRB( + labelRect.left, + labelRect.top, + labelRect.right, + labelRect.bottom, + ), + dataLabelSettings.borderRadius, + canvas, + ); + } + + if (fillPaint.color != Colors.transparent) { + _drawLabelRect( + fillPaint, + Rect.fromLTRB( + labelRect.left, + labelRect.top, + labelRect.right, + labelRect.bottom, + ), + dataLabelSettings.borderRadius, + canvas, + ); + } + canvas.restore(); + + drawDataLabel( + canvas, + dataLabel, + offset, + effectiveTextStyle, + dataLabelSettings.angle, + ); + } + + void _drawLabelRect( + Paint paint, + Rect labelRect, + double borderRadius, + Canvas canvas, + ) => canvas.drawRRect( + RRect.fromRectAndRadius(labelRect, Radius.circular(borderRadius)), + paint, + ); +} + +class RadialBarSegment extends ChartSegment { + late RadialBarSeriesRenderer series; + late double _degree; + late double _startAngle; + late Offset _center; + Path trackPath = Path(); + Path yValuePath = Path(); + Path shadowPath = Path(); + Path overFilledPath = Path(); + Paint? _shadowPaint; + Paint? _overFilledPaint; + double _priorEndAngle = double.nan; + double _priorInnerRadius = double.nan; + double _priorOuterRadius = double.nan; + + /// The `_isLegendToggled` field is used to manage animations in the radial bar series. + /// Set to `true` when a legend item is tapped to trigger the inner and outer radius animation. + /// Reset to `false` to ensure the end angle animation works correctly when data changes. + bool _isLegendToggled = false; + + double get endAngle => _endAngle; + double _endAngle = double.nan; + set endAngle(double value) { + _priorEndAngle = _endAngle; + _endAngle = value; + } + + double get innerRadius => _innerRadius; + double _innerRadius = double.nan; + set innerRadius(double value) { + _priorInnerRadius = _innerRadius; + _innerRadius = value; + } + + double get outerRadius => _outerRadius; + double _outerRadius = double.nan; + set outerRadius(double value) { + _priorOuterRadius = _outerRadius; + _outerRadius = value; + } + + /// Fill paint of the segment track. + final Paint trackFillPaint = Paint()..isAntiAlias = true; + + /// Stroke paint of the segment track. + final Paint trackStrokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + + @override + void transformValues() { + _reset(); + + double degree = _degree; + double startAngle = _startAngle; + double endAngle = _endAngle; + double innerRadius = _innerRadius; + double outerRadius = _outerRadius; + + if (animationFactor == 1) { + // Resets `_isLegendToggled` to `false` to handle endAngle animations during dynamically. + _isLegendToggled = false; + } + + if (!_isLegendToggled && + !degree.isNaN && + !_endAngle.isNaN && + !_priorEndAngle.isNaN && + _priorEndAngle != _endAngle) { + // Handles endAngle animation when the data is reset dynamically. + endAngle = lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; + degree = endAngle - startAngle; + } else { + if (!_priorInnerRadius.isNaN && !_priorOuterRadius.isNaN) { + // Handles inner and outer radius animation when the legend is toggled. + if (isVisible) { + innerRadius = + lerpDouble(_priorInnerRadius, _innerRadius, animationFactor)!; + outerRadius = + lerpDouble(_priorOuterRadius, _outerRadius, animationFactor)!; + } else { + final double halfRadii = (_priorOuterRadius + _priorInnerRadius) / 2; + endAngle = _priorEndAngle; + degree = endAngle - startAngle; + innerRadius = + _priorInnerRadius + + (halfRadii - _priorInnerRadius) * animationFactor; + outerRadius = + _priorOuterRadius - + (_priorOuterRadius - halfRadii) * animationFactor; + _innerRadius = innerRadius; + _outerRadius = outerRadius; + } + } else { + // Handles endAngle animation at load time. + degree = degree * animationFactor; + endAngle = startAngle + degree; + } + } + + trackPath = calculateArcPath( + innerRadius, + outerRadius, + _center, + 0, + fullAngle, + fullAngle, + isAnimate: true, + ); + + if (_outerRadius > 0 && degree > 0) { + final num angleDeviation = findAngleDeviation( + innerRadius, + outerRadius, + 360, + ); + final CornerStyle cornerStyle = series.cornerStyle; + if (cornerStyle == CornerStyle.bothCurve || + cornerStyle == CornerStyle.startCurve) { + startAngle += angleDeviation; + } + + if (cornerStyle == CornerStyle.bothCurve || + cornerStyle == CornerStyle.endCurve) { + endAngle -= angleDeviation; + } + + if (degree > 360) { + yValuePath = calculateRoundedCornerArcPath( + cornerStyle, + innerRadius, + outerRadius, + _center, + 0, + fullAngle, + ); + yValuePath.arcTo( + Rect.fromCircle(center: _center, radius: outerRadius), + degreesToRadians(_startAngle), + degreesToRadians(_endAngle - _startAngle), + true, + ); + yValuePath.arcTo( + Rect.fromCircle(center: _center, radius: innerRadius), + degreesToRadians(_endAngle), + degreesToRadians(_startAngle) - degreesToRadians(_endAngle), + false, + ); + } else { + yValuePath = calculateRoundedCornerArcPath( + cornerStyle, + innerRadius, + outerRadius, + _center, + startAngle, + endAngle, + ); + } + + if (degree > 360 && endAngle >= startAngle + 180) { + _calculateShadowPath(endAngle, degree, innerRadius, outerRadius); + } + } + } + + void _calculateShadowPath( + double endAngle, + double degree, + double innerRadius, + double outerRadius, + ) { + if (degree > 360) { + final double actualRadius = (innerRadius - outerRadius).abs() / 2; + final Offset midPoint = calculateOffset( + endAngle, + (innerRadius + outerRadius) / 2, + _center, + ); + if (actualRadius > 0) { + double shadowWidth = actualRadius * 0.2; + const double sigmaRadius = 3 * 0.57735 + 0.5; + shadowWidth = shadowWidth < 3 ? 3 : (shadowWidth > 5 ? 5 : shadowWidth); + _shadowPaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeWidth = shadowWidth + ..maskFilter = const MaskFilter.blur( + BlurStyle.normal, + sigmaRadius, + ); + _overFilledPaint = Paint()..isAntiAlias = true; + double newEndAngle = endAngle; + if (series.cornerStyle == CornerStyle.endCurve || + series.cornerStyle == CornerStyle.bothCurve) { + newEndAngle = + (newEndAngle > 360 ? newEndAngle : (newEndAngle - 360)) + 11.5; + shadowPath + ..reset() + ..addArc( + Rect.fromCircle( + center: midPoint, + radius: actualRadius - (actualRadius * 0.05), + ), + degreesToRadians(newEndAngle + 22.5), + degreesToRadians(118.125), + ); + overFilledPath = + Path()..addArc( + Rect.fromCircle(center: midPoint, radius: actualRadius), + degreesToRadians(newEndAngle - 20), + degreesToRadians(225), + ); + } else if (series.cornerStyle == CornerStyle.bothFlat || + series.cornerStyle == CornerStyle.startCurve) { + _overFilledPaint! + ..style = PaintingStyle.stroke + ..strokeWidth = series.borderWidth; + + final Offset shadowStartPoint = calculateOffset( + newEndAngle, + outerRadius - (outerRadius * 0.025), + _center, + ); + final Offset shadowEndPoint = calculateOffset( + newEndAngle, + innerRadius + (innerRadius * 0.025), + _center, + ); + + final Offset overFilledStartPoint = calculateOffset( + newEndAngle - 2, + outerRadius, + _center, + ); + final Offset overFilledEndPoint = calculateOffset( + newEndAngle - 2, + innerRadius, + _center, + ); + + shadowPath + ..reset() + ..moveTo(shadowStartPoint.dx, shadowStartPoint.dy) + ..lineTo(shadowEndPoint.dx, shadowEndPoint.dy); + + overFilledPath + ..reset() + ..moveTo(overFilledStartPoint.dx, overFilledStartPoint.dy) + ..lineTo(overFilledEndPoint.dx, overFilledEndPoint.dy); + } + } + } + } + + @override + Paint getFillPaint() => fillPaint; + + @override + Paint getStrokePaint() => strokePaint; + + /// Gets the color of the track. + Paint getTrackerFillPaint() => trackFillPaint; + + /// Gets the border color of the track. + Paint getTrackerStrokePaint() => trackStrokePaint; + + @override + void calculateSegmentPoints() {} + + @override + bool contains(Offset position) { + return yValuePath.contains(position); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + final ChartPoint point = ChartPoint( + x: series.circularXValues[currentSegmentIndex], + y: series.circularYValues[currentSegmentIndex], + ); + final Offset preferredPos = series.localToGlobal( + calculateOffset( + (_startAngle + _endAngle) / 2, + (_innerRadius + _outerRadius) / 2, + _center, + ), + ); + return ChartTooltipInfo( + primaryPosition: preferredPos, + secondaryPosition: preferredPos, + text: series.tooltipText(point), + header: '', + data: series.dataSource![currentSegmentIndex], + point: point, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + ); + } + + @override + void onPaint(Canvas canvas) { + Paint paint = getTrackerFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(trackPath, paint); + } + + paint = getTrackerStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawPath(trackPath, paint); + } + + paint = getFillPaint(); + if (paint.color != Colors.transparent && innerRadius != outerRadius) { + canvas.drawPath(yValuePath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + canvas.drawPath(yValuePath, paint); + } + + if (_shadowPaint != null && _overFilledPaint != null) { + canvas.drawPath(shadowPath, _shadowPaint!); + _overFilledPaint!.color = getFillPaint().color; + canvas.drawPath(overFilledPath, _overFilledPaint!); + } + } + + void _reset() { + trackPath.reset(); + yValuePath.reset(); + shadowPath.reset(); + overFilledPath.reset(); + _shadowPaint = null; + _overFilledPaint = null; + } + + @override + void dispose() { + _reset(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart new file mode 100644 index 000000000..580ce4ed9 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart @@ -0,0 +1,659 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the range area series. +/// +/// To render a range area chart, create an instance of [RangeAreaSeries] and +/// add to the series collection property of [SfCartesianChart]. +/// [RangeAreaSeries] is similar to [AreaSeries] requires two Y values for a +/// point, data should contain high and low values. +/// +/// High and low value specify the maximum and minimum range of the point. +/// +/// [highValueMapper] - Field in the data source, which is considered as +/// high value for the data points. +/// [lowValueMapper] - Field in the data source, which is considered as +/// low value for the data points. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} +class RangeAreaSeries extends RangeSeriesBase { + /// Creating an argument constructor of RangeAreaSeries class. + const RangeAreaSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.highValueMapper, + required super.lowValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.trendlines, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + super.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + this.borderDrawMode = RangeAreaBorderMode.all, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + }); + + /// Border type of area series. + /// + /// Defaults to `BorderDrawMode.top`. + /// + /// Also refer [BorderDrawMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeAreaSeries( + /// borderDrawMode: RangeAreaBorderMode.all, + /// ), + /// ], + /// ); + /// } + /// ``` + final RangeAreaBorderMode borderDrawMode; + + /// Create the range area series renderer. + @override + RangeAreaSeriesRenderer createRenderer() { + RangeAreaSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as RangeAreaSeriesRenderer; + // ignore: unnecessary_null_comparison + return seriesRenderer; + } + return RangeAreaSeriesRenderer(); + } + + @override + RangeAreaSeriesRenderer createRenderObject(BuildContext context) { + final RangeAreaSeriesRenderer renderer = + super.createRenderObject(context) as RangeAreaSeriesRenderer; + renderer + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RangeAreaSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + } +} + +/// Creates series renderer for range area series. +class RangeAreaSeriesRenderer extends RangeSeriesRendererBase + with ContinuousSeriesMixin { + RangeAreaSeriesRenderer(); + + RangeAreaBorderMode get borderDrawMode => _borderDrawMode; + RangeAreaBorderMode _borderDrawMode = RangeAreaBorderMode.all; + set borderDrawMode(RangeAreaBorderMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + forceTransformValues = true; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as RangeAreaSegment + ..series = this + ..currentSegmentIndex = 0 + .._xValues = xValues + .._lowValues = lowValues + .._highValues = highValues; + } + + /// Creates a segment for a data point in the series. + @override + RangeAreaSegment createSegment() => RangeAreaSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.rangeAreaSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final RangeAreaSegment rangeAreaSegment = + segment as RangeAreaSegment; + updateSegmentColor(rangeAreaSegment, borderColor, borderWidth); + updateSegmentGradient( + rangeAreaSegment, + gradientBounds: rangeAreaSegment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Segment class for range area series. +class RangeAreaSegment extends ChartSegment { + late RangeAreaSeriesRenderer series; + late List _xValues; + late List _highValues; + late List _lowValues; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _oldHighPoints = []; + final List _oldLowPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + if (_xValues.isEmpty || _lowValues.isEmpty || _highValues.isEmpty) { + return; + } + + _fillPath.reset(); + _strokePath.reset(); + + _calculatePoints(_xValues, _highValues, _lowValues); + _createFillPath(_fillPath, _highPoints, _lowPoints); + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } + + return lerpPoints; + } + + void _calculatePoints( + List xValues, + List highValues, + List lowValues, + ) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool isDropMode = + series.emptyPointSettings.mode == EmptyPointMode.drop; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + num topY = highValues[i]; + num bottomY = lowValues[i]; + if (topY.isNaN || bottomY.isNaN) { + if (isDropMode) { + continue; + } + topY = bottomY = double.nan; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); + _highPoints.add(highPoint); + final Offset lowPoint = Offset( + transformX(x, bottomY), + transformY(x, bottomY), + ); + _lowPoints.add(lowPoint); + points.add(lowPoint); + points.add(highPoint); + } + + length = _oldHighPoints.length; + if (_highPoints.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + + switch (series.borderDrawMode) { + case RangeAreaBorderMode.all: + _strokePath = _fillPath; + break; + case RangeAreaBorderMode.excludeSides: + _createStrokePathForExcludeSides( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + } + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int lastIndex = highPoints.length - 1; + for (int i = 0; i <= lastIndex; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createFillPath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + _createFillPath(source, highPoints.sublist(i), lowPoints.sublist(i)); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createStrokePathForExcludeSides( + Path source, + List highPoints, + List lowPoints, + ) { + Path? highPath; + Path? lowPath; + final int lastIndex = highPoints.length - 1; + for (int i = 0; i <= lastIndex; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (highPoint.isNaN) { + _createStrokePathForExcludeSides( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + if (i == 0) { + highPath = Path(); + lowPath = Path(); + highPath.moveTo(highPoint.dx, highPoint.dy); + lowPath.moveTo(lowPoint.dx, lowPoint.dy); + } else { + highPath!.lineTo(highPoint.dx, highPoint.dy); + lowPath!.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + + if (highPath != null) { + source.addPath(highPath, Offset.zero); + } + if (lowPath != null) { + source.addPath(lowPath, Offset.zero); + } + return source; + } + + @override + bool contains(Offset position) { + final int length = points.length; + for (int i = 0; i < length; i++) { + final Offset a = points[i]; + final Offset b = i + 1 < length ? points[i + 1] : a; + final Rect rect = Rect.fromPoints(a, b); + final Rect paddedRect = rect.inflate(tooltipPadding); + if (paddedRect.contains(position)) { + return true; + } + i++; + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + high: series.highValues[pointIndex], + low: series.lowValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num high = chartPoint.high!; + final double dx = series.pointToPixelX(x, high); + final double dy = series.pointToPixelY(x, high); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + hasMultipleYValues: true, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && _highPoints.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + final Offset preferredPos = _highPoints[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(chartPoint.xValue!, chartPoint.low!), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + final Offset a = points[i]; + final Offset b = i + 1 < points.length ? points[i + 1] : a; + final Rect rect = Rect.fromPoints(a, b); + final Rect paddedRect = rect.inflate(tooltipPadding); + if (paddedRect.contains(position)) { + return i ~/ 2; + } + i++; + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _fillPath.reset(); + _strokePath.reset(); + + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart new file mode 100644 index 000000000..55bac3d78 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart @@ -0,0 +1,611 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the range column series. +/// +/// To render a range column chart, create an instance of [RangeColumnSeries] +/// and add to the series collection property of [SfCartesianChart]. +/// +/// [RangeColumnSeries] is similar to column series but requires two Y values +/// for a point, your data should contain high and low values. +/// High and low value specify the maximum and minimum range of the point. +/// +/// * [highValueMapper] - Field in the data source, which is considered as +/// high value for the data points. +/// * [lowValueMapper] - Field in the data source, which is considered as +/// low value for the data points. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} +@immutable +class RangeColumnSeries extends RangeSeriesBase { + /// Creating an argument constructor of RangeColumnSeries class. + const RangeColumnSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.highValueMapper, + required super.lowValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + this.isTrackVisible = false, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + this.spacing = 0.0, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.borderRadius = BorderRadius.zero, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1.0, + this.trackPadding = 0.0, + super.borderColor = Colors.transparent, + super.trendlines, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.dashArray, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Color of the track. + /// + /// Defaults to `Colors.grey`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// isTrackVisible: true, + /// trackColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackColor; + + /// Color of the track border. + /// + /// Defaults to `Colors.transparent`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red + /// ), + /// ], + /// ); + /// } + /// ``` + final Color trackBorderColor; + + /// Width of the track border. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// isTrackVisible: true, + /// trackBorderColor: Colors.red, + /// trackBorderWidth: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackBorderWidth; + + /// Padding of the track. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// isTrackVisible: true, + /// trackPadding: 2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double trackPadding; + + /// Spacing between the columns. + /// + /// The value ranges from 0 to 1. 1 represents 100% and 0 represents 0% + /// of the available space. + /// + /// Spacing also affects the width of the range column. For example, setting + /// 20% spacing and 100% width renders the column with 80% of total width. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// spacing: 0.5, + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// Renders range column with track. + /// + /// Track is a rectangular bar rendered from the start to the end of the axis. + /// Range Column Series will be rendered above the track. + /// + /// Defaults to `false`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// isTrackVisible: true, + /// ), + /// ], + /// ); + /// } + /// ``` + final bool isTrackVisible; + + /// Customizes the corners of the range column. + /// + /// Each corner can be customized with a desired value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// RangeColumnSeries( + /// borderRadius: BorderRadius.circular(5), + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double width; + + /// Create the range column series renderer. + @override + RangeColumnSeriesRenderer createRenderer() { + RangeColumnSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = + onCreateRenderer!(this) as RangeColumnSeriesRenderer; + return seriesRenderer; + } + return RangeColumnSeriesRenderer(); + } + + @override + RangeColumnSeriesRenderer createRenderObject(BuildContext context) { + final RangeColumnSeriesRenderer renderer = + super.createRenderObject(context) as RangeColumnSeriesRenderer; + renderer + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..spacing = spacing + ..borderRadius = borderRadius + ..isTrackVisible = isTrackVisible + ..width = width + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + RangeColumnSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..trackColor = trackColor + ..trackBorderColor = trackBorderColor + ..trackBorderWidth = trackBorderWidth + ..trackPadding = trackPadding + ..spacing = spacing + ..borderRadius = borderRadius + ..isTrackVisible = isTrackVisible + ..width = width + ..borderColor = borderColor; + } +} + +/// Creates series renderer for range column series. +class RangeColumnSeriesRenderer extends RangeSeriesRendererBase + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + Color get trackBorderColor => _trackBorderColor; + Color _trackBorderColor = Colors.transparent; + set trackBorderColor(Color value) { + if (_trackBorderColor != value) { + _trackBorderColor = value; + markNeedsSegmentsPaint(); + } + } + + Color get trackColor => _trackColor; + Color _trackColor = Colors.grey; + set trackColor(Color value) { + if (_trackBorderColor != value) { + _trackColor = value; + markNeedsSegmentsPaint(); + } + } + + double get trackBorderWidth => _trackBorderWidth; + double _trackBorderWidth = 1.0; + set trackBorderWidth(double value) { + if (_trackBorderWidth != value) { + _trackBorderWidth = value; + markNeedsSegmentsPaint(); + } + } + + double get trackPadding => _trackPadding; + double _trackPadding = 0.0; + set trackPadding(double value) { + if (_trackPadding != value) { + _trackPadding = value; + markNeedsLayout(); + } + } + + bool get isTrackVisible => _isTrackVisible; + bool _isTrackVisible = false; + set isTrackVisible(bool value) { + if (_isTrackVisible != value) { + _isTrackVisible = value; + markNeedsLayout(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + Color _dataLabelSurfaceColor() { + final SfChartThemeData chartThemeData = parent!.chartThemeData!; + final ThemeData themeData = parent!.themeData!; + if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { + return chartThemeData.plotAreaBackgroundColor!; + } else if (chartThemeData.backgroundColor != Colors.transparent) { + return chartThemeData.backgroundColor!; + } + return themeData.colorScheme.surface; + } + + @override + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final ChartDataLabelAlignment alignment = label.labelAlignment; + final ChartSegment segment = segments[label.dataPointIndex]; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.middle: + case ChartDataLabelAlignment.bottom: + return _dataLabelSurfaceColor(); + + case ChartDataLabelAlignment.top: + return segment.getFillPaint().color; + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as RangeColumnSegment + ..series = this + ..x = xValues[index] + ..top = highValues[index] + ..bottom = lowValues[index] + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + RangeColumnSegment createSegment() => RangeColumnSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.rangeColumnSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final RangeColumnSegment rangeColumnSegment = + segment as RangeColumnSegment; + updateSegmentTrackerStyle( + rangeColumnSegment, + trackColor, + trackBorderColor, + trackBorderWidth, + ); + updateSegmentColor(rangeColumnSegment, borderColor, borderWidth); + updateSegmentGradient( + rangeColumnSegment, + gradientBounds: rangeColumnSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } +} + +/// Segment class for range column series. +class RangeColumnSegment extends ChartSegment with BarSeriesTrackerMixin { + late RangeColumnSeriesRenderer series; + late num x; + late num top; + late num bottom; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + final double centerY = (bottom + top) / 2; + _oldSegmentRect ??= toRRect( + transformX(left, centerY), + transformY(left, centerY), + transformX(right, centerY), + transformY(right, centerY), + borderRadius, + ); + + if (series.isTrackVisible) { + calculateTrackerBounds( + left, + right, + borderRadius, + series.trackPadding, + series.trackBorderWidth, + series, + ); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + high: top, + low: bottom, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + hasMultipleYValues: true, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + final Offset preferredPos = Offset( + series.pointToPixelX(x, top), + series.pointToPixelY(x, top), + ); + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(chartPoint.xValue!, bottom), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } + + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + _oldSegmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart new file mode 100644 index 000000000..ddc93f062 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart @@ -0,0 +1,324 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/data_label.dart'; +import '../interactions/tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'chart_series.dart'; + +/// Renders the scatter series. +/// +/// To render a scatter chart, create an instance of [ScatterSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// +/// The following properties, such as [color], [opacity], [borderWidth], +/// [borderColor] can be used to customize the appearance of the +/// scatter segment. +@immutable +class ScatterSeries extends XyDataSeries { + /// Creating an argument constructor of ScatterSeries class. + const ScatterSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.initialIsVisible, + super.dataLabelSettings, + super.enableTooltip = true, + super.enableTrackball = true, + super.trendlines, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.sortingOrder, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + final Color borderColor; + + /// Create the scatter series renderer. + @override + ScatterSeriesRenderer createRenderer() { + ScatterSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as ScatterSeriesRenderer; + return seriesRenderer; + } + return ScatterSeriesRenderer(); + } + + @override + ScatterSeriesRenderer createRenderObject(BuildContext context) { + final ScatterSeriesRenderer renderer = + super.createRenderObject(context) as ScatterSeriesRenderer; + renderer.borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + ScatterSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.borderColor = borderColor; + } +} + +/// Creates series renderer for scatter series +class ScatterSeriesRenderer extends XyDataSeriesRenderer + with SegmentAnimationMixin { + /// Calling the default constructor of ScatterSeriesRenderer class. + ScatterSeriesRenderer(); + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as ScatterSegment + ..series = this + ..xValue = xValues[index] + ..yValue = yValues[index] + ..width = markerSettings.width + ..height = markerSettings.height + ..shape = markerSettings.shape + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + ScatterSegment createSegment() => ScatterSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + toShapeMarkerType(markerSettings.shape); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final ScatterSegment scatterSegment = segment as ScatterSegment; + updateSegmentColor(scatterSegment, borderColor, borderWidth); + updateSegmentGradient( + scatterSegment, + gradientBounds: scatterSegment.segmentRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + Color dataLabelSurfaceColor(CartesianChartDataLabelPositioned label) { + final ChartDataLabelAlignment alignment = label.labelAlignment; + final ChartSegment segment = segments[label.dataPointIndex]; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.bottom: + return super.dataLabelSurfaceColor(label); + + case ChartDataLabelAlignment.middle: + return segment.getFillPaint().color; + } + } +} + +/// Creates the segments for scatter series. +/// +/// Generates the scatter series points and has the [calculateSegmentPoints] +/// method overrides to customize the scatter segment point calculation. +/// +/// Gets the path and color from the `series`. +class ScatterSegment extends ChartSegment { + late ScatterSeriesRenderer series; + num xValue = double.nan; + num yValue = double.nan; + double width = 8.0; + double height = 8.0; + + Rect? _oldSegmentRect; + Rect? segmentRect; + + DataMarkerType shape = DataMarkerType.circle; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = Rect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (xValue.isNaN || yValue.isNaN || width.isNaN || height.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final double pixelX = series.pointToPixelX(xValue, yValue); + final double pixelY = series.pointToPixelY(xValue, yValue); + final Offset center = Offset(pixelX, pixelY); + segmentRect = Rect.fromCenter(center: center, width: width, height: height); + _oldSegmentRect ??= Rect.fromCircle(center: center, radius: 0.0); + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: xValue, + y: yValue, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal(segmentRect!.topCenter), + secondaryPosition: series.localToGlobal(segmentRect!.bottomCenter), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: series.markerAt(pointIndex).type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: segmentRect!.center, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (segmentRect == null || + _oldSegmentRect == null || + segmentRect!.isEmpty || + shape == DataMarkerType.none) { + return; + } + + final Rect? paintRect = Rect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRect == null || paintRect.isEmpty) { + return; + } + + paint( + canvas: canvas, + rect: paintRect, + shapeType: toShapeMarkerType(shape), + paint: fillPaint, + borderPaint: strokePaint, + ); + } + + @override + void dispose() { + segmentRect = null; + _oldSegmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart new file mode 100644 index 000000000..67cc6a97d --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart @@ -0,0 +1,3025 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../axis/axis.dart'; +import '../axis/datetime_axis.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +// Creates mixin class and handle the control points of spline types are the +// Monotonic, cardinal, natural, and clamped. +mixin _SplineControlPointMixin on CartesianSeriesRenderer { + num _yMax = double.negativeInfinity; + + SplineType get splineType => _splineType; + SplineType _splineType = SplineType.natural; + set splineType(SplineType value) { + if (_splineType != value) { + _splineType = value; + canUpdateOrCreateSegments = true; + markNeedsUpdate(); + } + } + + double get cardinalSplineTension => _cardinalSplineTension; + double _cardinalSplineTension = 0.5; + set cardinalSplineTension(double value) { + if (_cardinalSplineTension != value) { + _cardinalSplineTension = clampDouble(value, 0.0, 1.0); + canUpdateOrCreateSegments = true; + markNeedsUpdate(); + } + } + + void _computeMonotonicSpline( + List yValues, + List yCoefficient, + List dx, + int length, + ) { + final List slope = List.filled(length - 1, null); + final int dxLength = dx.length; + final int slopeLength = slope.length; + int index = -1; + for (int i = 0; i < length - 1; i++) { + dx[i] = xValues[i + 1] - xValues[i]; + final double slopeValue = (yValues[i + 1] - yValues[i]) / dx[i]!; + slope[i] = slopeValue == double.infinity ? 0 : slopeValue; + } + + // Add the first and last coefficient value as Slope[0] and Slope[n - 1]. + if (slope.isEmpty) { + return; + } + + slope[0]!.isNaN + ? yCoefficient[++index] = 0 + : yCoefficient[++index] = slope[0]; + + for (int i = 0; i < dxLength - 1; i++) { + if (slopeLength > i + 1) { + final num m = slope[i]!, next = slope[i + 1]!; + if (m * next <= 0) { + yCoefficient[++index] = 0; + } else { + if (dx[i] == 0) { + yCoefficient[++index] = 0; + } else { + final num firstPoint = dx[i]!; + final num nextPoint = dx[i + 1]!; + final num interPoint = firstPoint + nextPoint; + yCoefficient[++index] = + 3 * + interPoint / + (((interPoint + nextPoint) / m) + + ((interPoint + firstPoint) / next)); + } + } + } + } + + slope[slopeLength - 1]!.isNaN + ? yCoefficient[++index] = 0 + : yCoefficient[++index] = slope[slopeLength - 1]; + } + + void _computeCardinalSpline( + List yCoefficient, + double cardinalSplineTension, + ) { + if (dataCount <= 2) { + for (int i = 0; i < dataCount; i++) { + yCoefficient[i] = 0; + } + } else { + for (int i = 0; i < dataCount; i++) { + num? coefficient; + if (i == 0 && dataCount > 2) { + final num x1 = xValues[i]; + final num x2 = xValues[i + 2]; + coefficient = cardinalSplineTension * (x2 - x1); + } else if (i == dataCount - 1 && dataCount - 3 >= 0) { + final num x1 = xValues[dataCount - 1]; + final num x2 = xValues[dataCount - 3]; + coefficient = cardinalSplineTension * (x1 - x2); + } else if (i - 1 >= 0 && dataCount > i + 1) { + final num x1 = xValues[i + 1]; + final num x2 = xValues[i - 1]; + coefficient = cardinalSplineTension * (x1 - x2); + } + + if (coefficient!.isNaN) { + coefficient = 0; + } + yCoefficient[i] = coefficient; + } + } + } + + void _computeNaturalSpline( + List yValues, + List yCoefficient, + SplineType splineType, + ) { + const double a = 6; + num d1, d2, d3, dy1, dy2, p; + + final List u = List.filled(dataCount, null); + if (splineType == SplineType.clamped && dataCount > 1) { + final num x1 = xValues[0]; + final num x2 = xValues[1]; + final num y1 = yValues[0]; + final num y2 = yValues[1]; + final num xDiff = x2 - x1; + final num yDiff = y2 - y1; + final num xEnd = xValues[dataCount - 1]; + final num xPenultimate = xValues[dataCount - 2]; + final num yEnd = yValues[dataCount - 1]; + final num yPenultimate = yValues[dataCount - 2]; + final num d0 = xDiff / yDiff; + final num dn = (xEnd - xPenultimate) / (yEnd - yPenultimate); + + u[0] = 0.5; + yCoefficient[0] = (3 * yDiff / xDiff) - (3 * d0); + yCoefficient[dataCount - 1] = + (3 * dn) - ((3 * (yEnd - yPenultimate)) / (xEnd - xPenultimate)); + if (yCoefficient[0] == double.infinity || yCoefficient[0]!.isNaN) { + yCoefficient[0] = 0; + } + + final num? endCoef = yCoefficient[dataCount - 1]; + if (endCoef == double.infinity || endCoef!.isNaN) { + yCoefficient[dataCount - 1] = 0; + } + } else { + yCoefficient[0] = u[0] = 0; + yCoefficient[dataCount - 1] = 0; + } + + final int segmentCount = dataCount - 1; + for (int i = 1; i < segmentCount; i++) { + yCoefficient[i] = 0; + final num x = xValues[i]; + final num y = yValues[i]; + final num nextX = xValues[i + 1]; + final num nextY = yValues[i + 1]; + final num previousX = xValues[i - 1]; + final num previousY = yValues[i - 1]; + if (!y.isNaN && !nextY.isNaN && !previousY.isNaN) { + d1 = x - previousX; + d2 = nextX - previousX; + d3 = nextX - x; + dy1 = nextY - y; + dy2 = y - previousY; + if (x == previousX || x == nextX) { + yCoefficient[i] = 0; + u[i] = 0; + } else { + p = 1 / ((d1 * yCoefficient[i - 1]!) + (2 * d2)); + yCoefficient[i] = -p * d3; + if (u[i - 1] != null) { + u[i] = p * ((a * ((dy1 / d3) - (dy2 / d1))) - (d1 * u[i - 1]!)); + } + } + } + } + + for (int i = dataCount - 2; i >= 0; i--) { + final num? yCoef1 = yCoefficient[i]; + final num? yCoef2 = yCoefficient[i + 1]; + if (u[i] != null && yCoef1 != null && yCoef2 != null) { + yCoefficient[i] = (yCoef1 * yCoef2) + u[i]!; + } + } + } + + /// Calculate the datetime interval for cardinal spline type. + num _cardinalSplineDateTimeInterval() { + DateTimeIntervalType visibleIntervalType = DateTimeIntervalType.auto; + if (xAxis is RenderDateTimeAxis) { + visibleIntervalType = (xAxis! as RenderDateTimeAxis).visibleIntervalType; + } + + switch (visibleIntervalType) { + case DateTimeIntervalType.years: + return 365 * 24 * 60 * 60 * 1000; + case DateTimeIntervalType.months: + return 30 * 24 * 60 * 60 * 1000; + case DateTimeIntervalType.days: + return 24 * 60 * 60 * 1000; + case DateTimeIntervalType.hours: + return 60 * 60 * 1000; + case DateTimeIntervalType.minutes: + return 60 * 1000; + case DateTimeIntervalType.seconds: + return 1000; + case DateTimeIntervalType.auto: + case DateTimeIntervalType.milliseconds: + return 30 * 24 * 60 * 60 * 1000; + } + } + + // This method common for SplineAreaSeries and SplineRangeAreaSeries. + // Not support for SplineSeries. + void _buildSplineAreaSegment( + List yCoefficients, + num x1, + num y1, + int nextIndex, + num x2, + num y2, + List splineYValues, + List startControlXPoints, + List startControlYPoints, + List endControlXPoints, + List endControlYPoints, + ) { + num controlX1 = 0; + num controlY1 = 0; + num controlX2 = 0; + num controlY2 = 0; + final int segmentCount = dataCount - 1; + for (int i = 0; i < segmentCount; i++) { + switch (splineType) { + case SplineType.natural: + case SplineType.clamped: + _computeNaturalSpline(splineYValues, yCoefficients, splineType); + + final num yCoef1 = yCoefficients[i]!; + num yCoef2 = yCoef1; + x1 = xValues[i]; + y1 = splineYValues[i]; + nextIndex = i + 1; + if (nextIndex < dataCount) { + x2 = xValues[nextIndex]; + y2 = splineYValues[nextIndex]; + yCoef2 = yCoefficients[nextIndex]!; + } + + const num oneThird = 1 / 3.0; + final num deltaXSquared = pow(x2.toDouble() - x1.toDouble(), 2); + + final num dx1 = (2 * x1) + x2; + final num dx2 = x1 + (2 * x2); + final num dy1 = (2 * y1) + y2; + final num dy2 = y1 + (2 * y2); + + controlX1 = dx1 * oneThird; + controlY1 = + oneThird * + (dy1 - (oneThird * deltaXSquared * (yCoef1 + (0.5 * yCoef2)))); + controlX2 = dx2 * oneThird; + controlY2 = + oneThird * + (dy2 - (oneThird * deltaXSquared * ((0.5 * yCoef1) + yCoef2))); + _yMax = max(_yMax, max(controlY1, max(controlY2, max(y1, y2)))); + break; + + case SplineType.monotonic: + final List dx = List.filled(dataCount, 0); + _computeMonotonicSpline(splineYValues, yCoefficients, dx, dataCount); + + final num yCoef1 = yCoefficients[i]!; + num yCoef2 = yCoef1; + x1 = xValues[i]; + y1 = splineYValues[i]; + nextIndex = i + 1; + if (nextIndex < dataCount) { + x2 = xValues[nextIndex]; + y2 = splineYValues[nextIndex]; + yCoef2 = yCoefficients[nextIndex]!; + } + + final num value = dx[i]! / 3; + controlX1 = x1 + value; + controlY1 = y1 + (yCoef1 * value); + controlX2 = x2 - value; + controlY2 = y2 - (yCoef2 * value); + _yMax = max(_yMax, max(controlY1, max(controlY2, max(y1, y2)))); + break; + + case SplineType.cardinal: + _computeCardinalSpline(yCoefficients, cardinalSplineTension); + + final num coefficientY = yCoefficients[i]!; + num coefficientY1 = coefficientY; + num yCoefficient = coefficientY; + num y1Coefficient = coefficientY1; + x1 = xValues[i]; + y1 = splineYValues[i]; + + nextIndex = i + 1; + if (nextIndex < dataCount) { + coefficientY1 = yCoefficients[nextIndex]!; + x2 = xValues[nextIndex]; + y2 = splineYValues[nextIndex]; + } + + if (xAxis is RenderDateTimeAxis) { + yCoefficient = coefficientY / _cardinalSplineDateTimeInterval(); + y1Coefficient = coefficientY1 / _cardinalSplineDateTimeInterval(); + } + + controlX1 = x1 + (coefficientY / 3); + controlY1 = y1 + (yCoefficient / 3); + controlX2 = x2 - (coefficientY1 / 3); + controlY2 = y2 - (y1Coefficient / 3); + _yMax = max(_yMax, max(controlY1, max(controlY2, max(y1, y2)))); + break; + } + startControlXPoints.add(controlX1); + startControlYPoints.add(controlY1); + endControlXPoints.add(controlX2); + endControlYPoints.add(controlY2); + } + } +} + +/// Renders the spline series. +/// +/// The spline chart draws a curved line between the points in a data series. +/// To render a spline chart, create an instance of [SplineSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// +/// Provides options to customize the color, opacity and width of the +/// spline series segments. +@immutable +class SplineSeries extends XyDataSeries { + /// Creating an argument constructor of SplineSeries class. + const SplineSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + double width = 2, + super.markerSettings, + this.splineType = SplineType.natural, + this.cardinalSplineTension = 0.5, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.trendlines, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.sortingOrder, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }) : super(borderWidth: width); + + /// Type of the spline curve. Various type of curves such as clamped, + /// cardinal, monotonic, and natural can be rendered between the data points. + /// + /// Defaults to `SplineType.natural`. + /// + /// Also refer [SplineType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// splineType: SplineType.monotonic, + /// ), + /// ], + /// ); + /// } + /// ``` + final SplineType splineType; + + /// Line tension of the cardinal spline. The value ranges from 0 to 1. + /// + /// Defaults to `0.5`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineSeries( + /// cardinalSplineTension: 0.4, + /// ), + /// ], + /// ); + /// } + /// ``` + final double cardinalSplineTension; + + /// Create the spline area series renderer. + @override + SplineSeriesRenderer createRenderer() { + SplineSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as SplineSeriesRenderer; + return seriesRenderer; + } + return SplineSeriesRenderer(); + } + + @override + SplineSeriesRenderer createRenderObject(BuildContext context) { + final SplineSeriesRenderer renderer = + super.createRenderObject(context) as SplineSeriesRenderer; + renderer + ..splineType = splineType + ..cardinalSplineTension = cardinalSplineTension; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + SplineSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..splineType = splineType + ..cardinalSplineTension = cardinalSplineTension; + } +} + +/// Creates series renderer for spline series. +class SplineSeriesRenderer extends XyDataSeriesRenderer + with _SplineControlPointMixin, LineSeriesMixin { + /// Calling the default constructor of SplineSeriesRenderer class. + SplineSeriesRenderer(); + + @override + DoubleRange range(RenderChartAxis axis) { + // TODO(VijayakumarM): Update [yMax]. + // yMax = _yMax; + return super.range(axis); + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + final List yCoefficients = List.filled(dataCount, 0); + _yMax = double.negativeInfinity; + + num x1 = xValues[index]; + num y1 = nonEmptyYValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + int nextIndex = index + 1; + if (nextIndex < dataCount) { + x2 = xValues[nextIndex]; + y2 = nonEmptyYValues[nextIndex]; + } + + num controlX1 = 0; + num controlY1 = 0; + num controlX2 = 0; + num controlY2 = 0; + + switch (splineType) { + case SplineType.natural: + case SplineType.clamped: + _computeNaturalSpline(nonEmptyYValues, yCoefficients, splineType); + + const num oneThird = 1 / 3.0; + final num deltaXSquared = pow(x2.toDouble() - x1.toDouble(), 2); + final num yCoef1 = yCoefficients[index]!; + num yCoef2 = yCoef1; + if (nextIndex < dataCount) { + yCoef2 = yCoefficients[nextIndex]!; + } + + final num dx1 = (2 * x1) + x2; + final num dx2 = x1 + (2 * x2); + final num dy1 = (2 * y1) + y2; + final num dy2 = y1 + (2 * y2); + + controlX1 = dx1 * oneThird; + controlY1 = + oneThird * + (dy1 - (oneThird * deltaXSquared * (yCoef1 + (0.5 * yCoef2)))); + controlX2 = dx2 * oneThird; + controlY2 = + oneThird * + (dy2 - (oneThird * deltaXSquared * ((0.5 * yCoef1) + yCoef2))); + _yMax = max(_yMax, max(controlY1, max(controlY2, max(y1, y2)))); + break; + + case SplineType.monotonic: + final List dx = List.filled(dataCount, 0); + _computeMonotonicSpline(nonEmptyYValues, yCoefficients, dx, dataCount); + + final num yCoef1 = yCoefficients[index]!; + num yCoef2 = yCoef1; + if (nextIndex < dataCount) { + yCoef2 = yCoefficients[nextIndex]!; + } + + final num value = dx[index]! / 3; + controlX1 = x1 + value; + controlY1 = y1 + (yCoef1 * value); + controlX2 = x2 - value; + controlY2 = y2 - (yCoef2 * value); + _yMax = max(_yMax, max(controlY1, max(controlY2, max(y1, y2)))); + break; + + case SplineType.cardinal: + _computeCardinalSpline(yCoefficients, cardinalSplineTension); + + final num coefficientY = yCoefficients[index]!; + num coefficientY1 = coefficientY; + num yCoefficient = coefficientY; + num y1Coefficient = coefficientY1; + if (nextIndex < dataCount) { + coefficientY1 = yCoefficients[nextIndex]!; + } + + if (xAxis is RenderDateTimeAxis) { + yCoefficient = coefficientY / _cardinalSplineDateTimeInterval(); + y1Coefficient = coefficientY1 / _cardinalSplineDateTimeInterval(); + } + + controlX1 = x1 + (coefficientY / 3); + controlY1 = y1 + (yCoefficient / 3); + controlX2 = x2 - (coefficientY1 / 3); + controlY2 = y2 - (y1Coefficient / 3); + _yMax = max(_yMax, max(controlY1, max(controlY2, max(y1, y2)))); + break; + } + + x1 = xValues[index]; + y1 = yValues[index]; + nextIndex = nextIndexConsideringEmptyPointMode( + index, + emptyPointSettings.mode, + yValues, + dataCount, + ); + if (nextIndex != -1) { + x2 = xValues[nextIndex]; + y2 = yValues[nextIndex]; + } + + segment as SplineSegment + ..series = this + ..currentSegmentIndex = index + .._x1 = x1 + .._y1 = y1 + .._controlX1 = controlX1 + .._controlY1 = controlY1 + .._controlX2 = controlX2 + .._controlY2 = controlY2 + .._x2 = x2 + .._y2 = y2 + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + SplineSegment createSegment() => SplineSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + dashArray != null && !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.splineSeriesWithDashArray + : ShapeMarkerType.splineSeries; + + @override + double legendIconBorderWidth() { + return 1.5; + } + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth, isLineType: true); + updateSegmentGradient(segment); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.save(); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Creates the segments for spline series. +/// +/// Generates the spline series points and has the [calculateSegmentPoints] +/// method overrides to customize the spline segment point calculation. +/// +/// Gets the path and color from the `series`. +class SplineSegment extends ChartSegment { + late SplineSeriesRenderer series; + late num _x1; + late num _y1; + late num _x2; + late num _y2; + + late num _controlX1; + late num _controlY1; + late num _controlX2; + late num _controlY2; + + double? startControlX; + double? startControlY; + double? endControlX; + double? endControlY; + + double? _oldStartControlX; + double? _oldStartControlY; + double? _oldEndControlX; + double? _oldEndControlY; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldStartControlX = null; + _oldStartControlY = null; + _oldEndControlX = null; + _oldEndControlY = null; + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + + _oldStartControlX = lerpDouble( + _oldStartControlX, + startControlX, + segmentAnimationFactor, + ); + _oldStartControlY = lerpDouble( + _oldStartControlY, + startControlY, + segmentAnimationFactor, + ); + _oldEndControlX = lerpDouble( + _oldEndControlX, + endControlX, + segmentAnimationFactor, + ); + _oldEndControlY = lerpDouble( + _oldEndControlY, + endControlY, + segmentAnimationFactor, + ); + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + if (!_x1.isNaN && !_y1.isNaN) { + points.add(Offset(transformX(_x1, _y1), transformY(_x1, _y1))); + } + + if (!_x2.isNaN && !_y2.isNaN) { + startControlX = transformX(_controlX1, _controlY1); + startControlY = transformY(_controlX1, _controlY1); + endControlX = transformX(_controlX2, _controlY2); + endControlY = transformY(_controlX2, _controlY2); + points.add(Offset(transformX(_x2, _y2), transformY(_x2, _y2))); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + _oldStartControlX ??= startControlX; + _oldStartControlY ??= startControlY; + _oldEndControlX ??= endControlX; + _oldEndControlY ??= endControlY; + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + int _nearestPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + final int nearestPointIndex = + position == null ? 0 : _nearestPointIndex(points, position); + if (nearestPointIndex != -1) { + pointIndex ??= + (position == null || nearestPointIndex == 0 + ? currentSegmentIndex + : currentSegmentIndex + 1); + CartesianChartPoint chartPoint = _chartPoint(pointIndex); + List markerColors = [fillPaint.color]; + if (chartPoint.y != null && chartPoint.y!.isNaN) { + pointIndex += 1; + chartPoint = _chartPoint(pointIndex); + markerColors = [series.segments[pointIndex].fillPaint.color]; + } + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = points[nearestPointIndex]; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: markerColors, + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; + } + + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => strokePaint; + + /// Gets the stroke color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty || points.length != 2) { + return; + } + + final Offset? start = Offset.lerp( + _oldPoints[0], + points[0], + animationFactor, + ); + final Offset? end = Offset.lerp(_oldPoints[1], points[1], animationFactor); + final double controlX1 = + lerpDouble(_oldStartControlX, startControlX, animationFactor)!; + final double controlY1 = + lerpDouble(_oldStartControlY, startControlY, animationFactor)!; + final double controlX2 = + lerpDouble(_oldEndControlX, endControlX, animationFactor)!; + final double controlY2 = + lerpDouble(_oldEndControlY, endControlY, animationFactor)!; + + if (start != null && end != null) { + final Path path = + Path() + ..moveTo(start.dx, start.dy) + ..cubicTo( + controlX1, + controlY1, + controlX2, + controlY2, + end.dx, + end.dy, + ); + final Paint paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: path); + } + } + } + + @override + void dispose() { + points.clear(); + _oldPoints.clear(); + super.dispose(); + } +} + +/// Renders the spline are series. +/// +/// To render a spline area chart, create an instance of [SplineAreaSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// Properties such as [color], [opacity], [width] are used to customize the +/// appearance of spline area chart. +@immutable +class SplineAreaSeries extends XyDataSeries { + /// Creating an argument constructor of SplineAreaSeries class. + const SplineAreaSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + this.splineType = SplineType.natural, + super.trendlines, + this.cardinalSplineTension = 0.5, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + this.borderDrawMode = BorderDrawMode.top, + }); + + /// Border type of area series. + /// + /// Defaults to `BorderDrawMode.top`. + /// + /// Also refer [BorderDrawMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineAreaSeries( + /// borderColor: Colors.red, + /// borderWidth: 3, + /// borderDrawMode: BorderDrawMode.all, + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderDrawMode borderDrawMode; + + /// Type of the spline curve. Various type of curves such as clamped, + /// cardinal, monotonic, and natural can be rendered between the data points. + /// + /// Defaults to `SplineType.natural`. + /// + /// Also refer [SplineType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineAreaSeries( + /// splineType: SplineType.monotonic, + /// ), + /// ], + /// ); + /// } + /// ``` + final SplineType splineType; + + /// Line tension of the cardinal spline. The value ranges from 0 to 1. + /// + /// Defaults to `0.5`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineAreaSeries( + /// cardinalSplineTension: 0.4, + /// ), + /// ], + /// ); + /// } + /// ``` + final double cardinalSplineTension; + + final Color borderColor; + + /// Create the spline area series renderer. + @override + SplineAreaSeriesRenderer createRenderer() { + SplineAreaSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = + onCreateRenderer!(this) as SplineAreaSeriesRenderer; + return seriesRenderer; + } + return SplineAreaSeriesRenderer(); + } + + @override + SplineAreaSeriesRenderer createRenderObject(BuildContext context) { + final SplineAreaSeriesRenderer renderer = + super.createRenderObject(context) as SplineAreaSeriesRenderer; + renderer + ..borderDrawMode = borderDrawMode + ..splineType = splineType + ..cardinalSplineTension = cardinalSplineTension + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + covariant SplineAreaSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..borderDrawMode = borderDrawMode + ..splineType = splineType + ..cardinalSplineTension = cardinalSplineTension + ..borderColor = borderColor; + } +} + +/// Creates series renderer for spline area series. +class SplineAreaSeriesRenderer extends XyDataSeriesRenderer + with _SplineControlPointMixin, ContinuousSeriesMixin { + /// Calling the default constructor of SplineAreaSeriesRenderer class. + SplineAreaSeriesRenderer(); + + BorderDrawMode get borderDrawMode => _borderDrawMode; + BorderDrawMode _borderDrawMode = BorderDrawMode.top; + set borderDrawMode(BorderDrawMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + final List _startControlXValues = []; + final List _startControlYValues = []; + final List _endControlXValues = []; + final List _endControlYValues = []; + + @override + void computeNonEmptyYValues() { + nonEmptyYValues.clear(); + if (emptyPointSettings.mode == EmptyPointMode.drop) { + final List yValuesCopy = [...yValues]; + nonEmptyYValues = yValuesCopy; + for (int i = 0; i < dataCount; i++) { + if (yValues[i].isNaN) { + nonEmptyYValues[i] = 0; + } + } + } else if (emptyPointSettings.mode == EmptyPointMode.gap) { + super.computeNonEmptyYValues(); + } else { + final List yValuesCopy = [...yValues]; + nonEmptyYValues = yValuesCopy; + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + _startControlXValues.clear(); + _startControlYValues.clear(); + _endControlXValues.clear(); + _endControlYValues.clear(); + + final List yCoefficients = List.filled(dataCount, 0); + _yMax = double.negativeInfinity; + + final num x1 = xValues[index]; + final num y1 = nonEmptyYValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + final int nextIndex = index + 1; + if (nextIndex < dataCount) { + x2 = xValues[nextIndex]; + y2 = nonEmptyYValues[nextIndex]; + } + + _buildSplineAreaSegment( + yCoefficients, + x1, + y1, + nextIndex, + x2, + y2, + nonEmptyYValues, + _startControlXValues, + _startControlYValues, + _endControlXValues, + _endControlYValues, + ); + + final num bottom = xAxis!.crossesAt ?? max(yAxis!.visibleRange!.minimum, 0); + segment as SplineAreaSegment + ..series = this + ..currentSegmentIndex = index + .._xValues = xValues + .._yValues = yValues + .._startControlHighXValues = _startControlXValues + .._startControlHighYValues = _startControlYValues + .._endControlHighXValues = _endControlXValues + .._endControlHighYValues = _endControlYValues + ..bottom = bottom; + } + + /// Creates a segment for a data point in the series. + @override + SplineAreaSegment createSegment() => SplineAreaSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.splineAreaSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SplineAreaSegment splineAreaSegment = + segment as SplineAreaSegment; + updateSegmentColor(splineAreaSegment, borderColor, borderWidth); + updateSegmentGradient( + splineAreaSegment, + gradientBounds: splineAreaSegment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + super.onPaint(context, offset); + } + + @override + void dispose() { + _startControlXValues.clear(); + _startControlYValues.clear(); + _endControlXValues.clear(); + _endControlYValues.clear(); + super.dispose(); + } +} + +/// Creates the segments for spline area series. +/// +/// Generates the spline area series points and has the [calculateSegmentPoints] +/// method overridden to customize the spline area segment point calculation. +/// +/// Gets the path and color from the `series`. +class SplineAreaSegment extends ChartSegment { + late SplineAreaSeriesRenderer series; + late num bottom; + late List _xValues; + late List _yValues; + late List _startControlHighXValues; + late List _startControlHighYValues; + late List _endControlHighXValues; + late List _endControlHighYValues; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _startControlHighPoints = []; + final List _endControlHighPoints = []; + + final List _oldHighPoints = []; + final List _oldLowPoints = []; + final List _oldStartControlHighPoints = []; + final List _oldEndControlHighPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + _oldStartControlHighPoints.clear(); + _oldEndControlHighPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + _oldStartControlHighPoints.clear(); + _oldEndControlHighPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + + final int oldControlPointsLength = _oldStartControlHighPoints.length; + final int newControlPointsLength = _startControlHighPoints.length; + if (oldControlPointsLength == newControlPointsLength) { + for (int i = 0; i < oldControlPointsLength; i++) { + _oldStartControlHighPoints[i] = + _oldStartControlHighPoints[i].lerp( + _startControlHighPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldEndControlHighPoints[i] = + _oldEndControlHighPoints[i].lerp( + _endControlHighPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + } else if (oldControlPointsLength < newControlPointsLength) { + for (int i = 0; i < oldControlPointsLength; i++) { + _oldStartControlHighPoints[i] = + _oldStartControlHighPoints[i].lerp( + _startControlHighPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldEndControlHighPoints[i] = + _oldEndControlHighPoints[i].lerp( + _endControlHighPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + _oldStartControlHighPoints.addAll( + _startControlHighPoints.sublist(oldControlPointsLength), + ); + _oldEndControlHighPoints.addAll( + _endControlHighPoints.sublist(oldControlPointsLength), + ); + } else { + for (int i = 0; i < newControlPointsLength; i++) { + _oldStartControlHighPoints[i] = + _oldStartControlHighPoints[i].lerp( + _startControlHighPoints[i], + segmentAnimationFactor, + bottom, + )!; + _oldEndControlHighPoints[i] = + _oldEndControlHighPoints[i].lerp( + _endControlHighPoints[i], + segmentAnimationFactor, + bottom, + )!; + } + _oldStartControlHighPoints.removeRange( + newControlPointsLength, + oldControlPointsLength, + ); + _oldEndControlHighPoints.removeRange( + newControlPointsLength, + oldControlPointsLength, + ); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + _oldStartControlHighPoints.clear(); + _oldEndControlHighPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + _startControlHighPoints.clear(); + _endControlHighPoints.clear(); + + _fillPath.reset(); + _strokePath.reset(); + if (_xValues.isEmpty || _yValues.isEmpty) { + return; + } + + _calculatePoints(_xValues, _yValues); + _createFillPath( + _fillPath, + _highPoints, + _lowPoints, + _startControlHighPoints, + _endControlHighPoints, + ); + } + + void _calculatePoints(List xValues, List yValues) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + int length = series.dataCount; + final int controlPointsLength = _startControlHighXValues.length; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num high = yValues[i]; + if (high.isNaN && canDrop) { + continue; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, high), transformY(x, high)); + _highPoints.add(highPoint); + points.add(highPoint); + + final num low = high.isNaN ? double.nan : bottom; + final Offset lowPoint = Offset(transformX(x, low), transformY(x, low)); + _lowPoints.add(lowPoint); + + if (i < controlPointsLength) { + final num startHighX = _startControlHighXValues[i]; + final num startHighY = _startControlHighYValues[i]; + final Offset startControlHighPoint = Offset( + transformX(startHighX, startHighY), + transformY(startHighX, startHighY), + ); + _startControlHighPoints.add(startControlHighPoint); + + final num endHighX = _endControlHighXValues[i]; + final num endHighY = _endControlHighYValues[i]; + final Offset endControlHighPoint = Offset( + transformX(endHighX, endHighY), + transformY(endHighX, endHighY), + ); + _endControlHighPoints.add(endControlHighPoint); + } + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + + length = _oldStartControlHighPoints.length; + if (_startControlHighPoints.length > length) { + _oldStartControlHighPoints.addAll( + _startControlHighPoints.sublist(length), + ); + _oldEndControlHighPoints.addAll(_endControlHighPoints.sublist(length)); + } + } + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + final List lerpStartControlHighPoints = _lerpPoints( + _oldStartControlHighPoints, + _startControlHighPoints, + ); + final List lerpEndControlHighPoints = _lerpPoints( + _oldEndControlHighPoints, + _endControlHighPoints, + ); + _createFillPath( + _fillPath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + ); + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + case BorderDrawMode.top: + _createTopStrokePath( + _strokePath, + lerpHighPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + ); + break; + case BorderDrawMode.excludeBottom: + _createExcludeBottomStrokePath( + _strokePath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + ); + break; + } + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, bottom)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, bottom)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, bottom)!, + ); + } + } + + return lerpPoints; + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + List startControlHighPoints, + List endControlHighPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i <= lastIndex; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (i == 0) { + if (lowPoint.isNaN) { + final int sublistFrom = i + 1; + final int controlPointsLength = startControlHighPoints.length; + if (sublistFrom < length && sublistFrom < controlPointsLength) { + _createFillPath( + source, + highPoints.sublist(sublistFrom), + lowPoints.sublist(sublistFrom), + startControlHighPoints.sublist(sublistFrom), + endControlHighPoints.sublist(sublistFrom), + ); + } + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + path.lineTo(highPoint.dx, highPoint.dy); + continue; + } + } + + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + final int controlPointsLength = startControlHighPoints.length; + if (i < length && i < controlPointsLength) { + _createFillPath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + startControlHighPoints.sublist(i), + endControlHighPoints.sublist(i), + ); + } + break; + } else { + final Offset startControlHigh = startControlHighPoints[i - 1]; + final Offset endControlHigh = endControlHighPoints[i - 1]; + path!.cubicTo( + startControlHigh.dx, + startControlHigh.dy, + endControlHigh.dx, + endControlHigh.dy, + highPoint.dx, + highPoint.dy, + ); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createTopStrokePath( + Path source, + List highPoints, + List startControlHighPoints, + List endControlHighPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i <= lastIndex; i++) { + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + final int sublistFrom = i + 1; + final int controlPointsLength = startControlHighPoints.length; + if (sublistFrom < length && sublistFrom < controlPointsLength) { + _createTopStrokePath( + source, + highPoints.sublist(sublistFrom), + startControlHighPoints.sublist(sublistFrom), + endControlHighPoints.sublist(sublistFrom), + ); + } + break; + } else { + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } else { + final Offset startControlHigh = startControlHighPoints[i - 1]; + final Offset endControlHigh = endControlHighPoints[i - 1]; + path!.cubicTo( + startControlHigh.dx, + startControlHigh.dy, + endControlHigh.dx, + endControlHigh.dy, + highPoint.dx, + highPoint.dy, + ); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createExcludeBottomStrokePath( + Path source, + List highPoints, + List lowPoints, + List startControlHighPoints, + List endControlHighPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i <= lastIndex; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (i == 0) { + if (lowPoint.isNaN) { + final int sublistFrom = i + 1; + final int controlPointsLength = startControlHighPoints.length; + if (sublistFrom < length && sublistFrom < controlPointsLength) { + _createExcludeBottomStrokePath( + source, + highPoints.sublist(sublistFrom), + lowPoints.sublist(sublistFrom), + startControlHighPoints.sublist(sublistFrom), + endControlHighPoints.sublist(sublistFrom), + ); + } + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + path.lineTo(highPoint.dx, highPoint.dy); + continue; + } + } + + if (highPoint.isNaN) { + final Offset lowPoint = lowPoints[i - 1]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + final int controlPointsLength = startControlHighPoints.length; + if (i < length && i < controlPointsLength) { + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + startControlHighPoints.sublist(i), + endControlHighPoints.sublist(i), + ); + } + break; + } else { + final Offset startControlHigh = startControlHighPoints[i - 1]; + final Offset endControlHigh = endControlHighPoints[i - 1]; + path!.cubicTo( + startControlHigh.dx, + startControlHigh.dy, + endControlHigh.dx, + endControlHigh.dy, + highPoint.dx, + highPoint.dy, + ); + if (i == lastIndex) { + final Offset lowPoint = lowPoints[i]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num y = chartPoint.y!; + final double dx = series.pointToPixelX(x, y); + final double dy = series.pointToPixelY(x, y); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + + final Offset preferredPos = points[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _startControlHighXValues.clear(); + _startControlHighYValues.clear(); + _endControlHighXValues.clear(); + _endControlHighYValues.clear(); + + _fillPath.reset(); + _strokePath.reset(); + + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + _startControlHighPoints.clear(); + _endControlHighPoints.clear(); + super.dispose(); + } +} + +/// Renders the spline range area series. +/// +/// To render a spline range area chart, create an instance of +/// [SplineRangeAreaSeries], and add it to the series collection property +/// of [SfCartesianChart]. Properties such as [color], [opacity], [width] are +/// used to customize the appearance of spline area chart. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} +@immutable +class SplineRangeAreaSeries extends RangeSeriesBase { + /// Creating an argument constructor of SplineRangeAreaSeries class. + const SplineRangeAreaSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.highValueMapper, + required super.lowValueMapper, + super.sortFieldValueMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + this.splineType = SplineType.natural, + super.trendlines, + this.cardinalSplineTension = 0.5, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + super.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + this.borderDrawMode = RangeAreaBorderMode.all, + }); + + /// Border type of the spline range area series. + /// + /// It takes the following two values: + /// + /// * [RangeAreaBorderMode.all] renders border for all the sides of + /// the series. + /// * [RangeAreaBorderMode.excludeSides] renders border at the top and bottom + /// of the series, + /// and excludes both sides. + /// + /// Defaults to `RangeAreaBorderMode.all`. + /// + /// Also refer [RangeAreaBorderMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineRangeAreaSeries( + /// borderColor: Colors.red, + /// borderWidth: 3, + /// borderDrawMode: RangeAreaBorderMode.excludeSides, + /// ), + /// ], + /// ); + /// } + /// ``` + final RangeAreaBorderMode borderDrawMode; + + /// Type of the spline curve in spline range area series. + /// + /// Various type of curves such as `SplineType.clamped`, + /// `SplineType.cardinal`, `SplineType.monotonic` + /// and `SplineType.natural` can be rendered between the data points. + /// + /// Defaults to `SplineType.natural`. + /// + /// Also refer [SplineType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineRangeAreaSeries( + /// splineType: SplineType.monotonic + /// ), + /// ], + /// ); + /// } + /// ``` + final SplineType splineType; + + /// Line tension of the cardinal spline curve. + /// + /// This is applicable only when `SplineType.cardinal` is set to [splineType] + /// property. + /// + /// Defaults to `0.5`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// SplineRangeAreaSeries( + /// splineType: SplineType.cardinal, + /// cardinalSplineTension: 0.2 + /// ), + /// ], + /// ); + /// } + /// ``` + final double cardinalSplineTension; + + /// Create the spline area series renderer. + @override + SplineRangeAreaSeriesRenderer createRenderer() { + SplineRangeAreaSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = + onCreateRenderer!(this) as SplineRangeAreaSeriesRenderer; + return seriesRenderer; + } + return SplineRangeAreaSeriesRenderer(); + } + + @override + SplineRangeAreaSeriesRenderer createRenderObject(BuildContext context) { + final SplineRangeAreaSeriesRenderer renderer = + super.createRenderObject(context) + as SplineRangeAreaSeriesRenderer; + renderer + ..borderDrawMode = borderDrawMode + ..splineType = splineType + ..cardinalSplineTension = cardinalSplineTension + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + SplineRangeAreaSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..borderDrawMode = borderDrawMode + ..splineType = splineType + ..cardinalSplineTension = cardinalSplineTension + ..borderColor = borderColor; + } +} + +/// Creates series renderer for spline range area series. +class SplineRangeAreaSeriesRenderer extends RangeSeriesRendererBase + with _SplineControlPointMixin, ContinuousSeriesMixin { + RangeAreaBorderMode get borderDrawMode => _borderDrawMode; + RangeAreaBorderMode _borderDrawMode = RangeAreaBorderMode.all; + set borderDrawMode(RangeAreaBorderMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + final List _startControlX1Values = []; + final List _startControlY1Values = []; + final List _endControlX1Values = []; + final List _endControlY1Values = []; + final List _startControlX2Values = []; + final List _startControlY2Values = []; + final List _endControlX2Values = []; + final List _endControlY2Values = []; + bool _isHigh = false; + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + _startControlX1Values.clear(); + _startControlY1Values.clear(); + _endControlX1Values.clear(); + _endControlY1Values.clear(); + _startControlX2Values.clear(); + _startControlY2Values.clear(); + _endControlX2Values.clear(); + _endControlY2Values.clear(); + + final List highCoefficients = List.filled(dataCount, 0); + final List lowCoefficients = List.filled(dataCount, 0); + _yMax = double.negativeInfinity; + + _isHigh = true; + for (int i = 0; i < 2; i++) { + final num x1 = xValues[index]; + final num y1 = + _isHigh ? nonEmptyHighValues[index] : nonEmptyLowValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + final int nextIndex = index + 1; + if (nextIndex < dataCount) { + x2 = xValues[nextIndex]; + y2 = + _isHigh + ? nonEmptyHighValues[nextIndex] + : nonEmptyLowValues[nextIndex]; + } + + _buildSplineAreaSegment( + _isHigh ? highCoefficients : lowCoefficients, + x1, + y1, + nextIndex, + x2, + y2, + _isHigh ? nonEmptyHighValues : nonEmptyLowValues, + _isHigh ? _startControlX1Values : _startControlX2Values, + _isHigh ? _startControlY1Values : _startControlY2Values, + _isHigh ? _endControlX1Values : _endControlX2Values, + _isHigh ? _endControlY1Values : _endControlY2Values, + ); + + _isHigh = false; + } + + segment as SplineRangeAreaSegment + ..series = this + ..currentSegmentIndex = 0 + .._xValues = xValues + .._highValues = highValues + .._lowValues = lowValues + .._startControlHighXValues = _startControlX1Values + .._startControlHighYValues = _startControlY1Values + .._endControlHighXValues = _endControlX1Values + .._endControlHighYValues = _endControlY1Values + .._startControlLowXValues = _startControlX2Values + .._startControlLowYValues = _startControlY2Values + .._endControlLowXValues = _endControlX2Values + .._endControlLowYValues = _endControlY2Values; + } + + /// Creates a segment for a data point in the series. + @override + SplineRangeAreaSegment createSegment() => + SplineRangeAreaSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.splineRangeAreaSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SplineRangeAreaSegment splineRangeAreaSegment = + segment as SplineRangeAreaSegment; + updateSegmentColor(splineRangeAreaSegment, borderColor, borderWidth); + updateSegmentGradient( + splineRangeAreaSegment, + gradientBounds: splineRangeAreaSegment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } + + @override + void dispose() { + _startControlX1Values.clear(); + _startControlY1Values.clear(); + _endControlX1Values.clear(); + _endControlY1Values.clear(); + _startControlX2Values.clear(); + _startControlY2Values.clear(); + _endControlX2Values.clear(); + _endControlY2Values.clear(); + super.dispose(); + } +} + +/// Segment class for spline range area series. +class SplineRangeAreaSegment extends ChartSegment { + late SplineRangeAreaSeriesRenderer series; + late List _startControlHighXValues; + late List _endControlHighXValues; + late List _startControlHighYValues; + late List _endControlHighYValues; + late List _startControlLowXValues; + late List _endControlLowXValues; + late List _startControlLowYValues; + late List _endControlLowYValues; + late List _xValues; + late List _highValues; + late List _lowValues; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _startControlHighPoints = []; + final List _endControlHighPoints = []; + final List _startControlLowPoints = []; + final List _endControlLowPoints = []; + + final List _oldHighPoints = []; + final List _oldLowPoints = []; + final List _oldStartControlHighPoints = []; + final List _oldEndControlHighPoints = []; + final List _oldStartControlLowPoints = []; + final List _oldEndControlLowPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + _oldStartControlHighPoints.clear(); + _oldEndControlHighPoints.clear(); + _oldStartControlLowPoints.clear(); + _oldEndControlLowPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + _oldStartControlHighPoints.clear(); + _oldEndControlHighPoints.clear(); + _oldStartControlLowPoints.clear(); + _oldEndControlLowPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + + final int oldControlPointsLength = _oldStartControlHighPoints.length; + final int newControlPointsLength = _startControlHighPoints.length; + if (oldControlPointsLength == newControlPointsLength) { + for (int i = 0; i < oldControlPointsLength; i++) { + _oldStartControlHighPoints[i] = + _oldStartControlHighPoints[i].lerp( + _startControlHighPoints[i], + segmentAnimationFactor, + _startControlHighPoints[i].dy, + )!; + _oldEndControlHighPoints[i] = + _oldEndControlHighPoints[i].lerp( + _endControlHighPoints[i], + segmentAnimationFactor, + _oldEndControlHighPoints[i].dy, + )!; + + _oldStartControlLowPoints[i] = + _oldStartControlLowPoints[i].lerp( + _startControlLowPoints[i], + segmentAnimationFactor, + _startControlLowPoints[i].dy, + )!; + _oldEndControlLowPoints[i] = + _oldEndControlLowPoints[i].lerp( + _endControlLowPoints[i], + segmentAnimationFactor, + _oldEndControlLowPoints[i].dy, + )!; + } + } else if (oldControlPointsLength < newControlPointsLength) { + for (int i = 0; i < oldControlPointsLength; i++) { + _oldStartControlHighPoints[i] = + _oldStartControlHighPoints[i].lerp( + _startControlHighPoints[i], + segmentAnimationFactor, + _startControlHighPoints[i].dy, + )!; + _oldEndControlHighPoints[i] = + _oldEndControlHighPoints[i].lerp( + _endControlHighPoints[i], + segmentAnimationFactor, + _oldEndControlHighPoints[i].dy, + )!; + _oldStartControlLowPoints[i] = + _oldStartControlLowPoints[i].lerp( + _startControlLowPoints[i], + segmentAnimationFactor, + _startControlLowPoints[i].dy, + )!; + _oldEndControlLowPoints[i] = + _oldEndControlLowPoints[i].lerp( + _endControlLowPoints[i], + segmentAnimationFactor, + _oldEndControlLowPoints[i].dy, + )!; + } + _oldStartControlHighPoints.addAll( + _startControlHighPoints.sublist(oldControlPointsLength), + ); + _oldEndControlHighPoints.addAll( + _endControlHighPoints.sublist(oldControlPointsLength), + ); + _oldStartControlLowPoints.addAll( + _startControlLowPoints.sublist(oldControlPointsLength), + ); + _oldEndControlLowPoints.addAll( + _endControlLowPoints.sublist(oldControlPointsLength), + ); + } else { + for (int i = 0; i < newControlPointsLength; i++) { + _oldStartControlHighPoints[i] = + _oldStartControlHighPoints[i].lerp( + _startControlHighPoints[i], + segmentAnimationFactor, + _startControlHighPoints[i].dy, + )!; + _oldEndControlHighPoints[i] = + _oldEndControlHighPoints[i].lerp( + _endControlHighPoints[i], + segmentAnimationFactor, + _oldEndControlHighPoints[i].dy, + )!; + + _oldStartControlLowPoints[i] = + _oldStartControlLowPoints[i].lerp( + _startControlLowPoints[i], + segmentAnimationFactor, + _startControlLowPoints[i].dy, + )!; + _oldEndControlLowPoints[i] = + _oldEndControlLowPoints[i].lerp( + _endControlLowPoints[i], + segmentAnimationFactor, + _oldEndControlLowPoints[i].dy, + )!; + } + _oldStartControlHighPoints.removeRange( + newControlPointsLength, + oldControlPointsLength, + ); + _oldEndControlHighPoints.removeRange( + newControlPointsLength, + oldControlPointsLength, + ); + _oldStartControlLowPoints.removeRange( + newControlPointsLength, + oldControlPointsLength, + ); + _oldEndControlLowPoints.removeRange( + newControlPointsLength, + oldControlPointsLength, + ); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + _oldStartControlHighPoints.clear(); + _oldEndControlHighPoints.clear(); + _oldStartControlLowPoints.clear(); + _oldEndControlLowPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + _startControlHighPoints.clear(); + _endControlHighPoints.clear(); + _startControlLowPoints.clear(); + _endControlLowPoints.clear(); + + _fillPath.reset(); + _strokePath.reset(); + if (_xValues.isEmpty || _highValues.isEmpty || _lowValues.isEmpty) { + return; + } + + _calculatePoints(); + _createFillPath( + _fillPath, + _highPoints, + _lowPoints, + _startControlHighPoints, + _endControlHighPoints, + _startControlLowPoints, + _endControlLowPoints, + ); + } + + void _calculatePoints() { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + final int length = series.dataCount; + final int controlPointsLength = _startControlHighXValues.length; + for (int i = 0; i < length; i++) { + final num x = _xValues[i]; + num highY = _highValues[i]; + num lowY = _lowValues[i]; + if (lowY.isNaN || highY.isNaN) { + if (canDrop) { + continue; + } + highY = lowY = double.nan; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset( + transformX(x, highY), + transformY(x, highY), + ); + _highPoints.add(highPoint); + final Offset lowPoint = Offset(transformX(x, lowY), transformY(x, lowY)); + _lowPoints.add(lowPoint); + points.add(lowPoint); + points.add(highPoint); + + if (i < controlPointsLength) { + num xValue = _startControlHighXValues[i]; + num yValue = _startControlHighYValues[i]; + Offset point = Offset( + transformX(xValue, yValue), + transformY(xValue, yValue), + ); + _startControlHighPoints.add(point); + + xValue = _endControlHighXValues[i]; + yValue = _endControlHighYValues[i]; + point = Offset(transformX(xValue, yValue), transformY(xValue, yValue)); + _endControlHighPoints.add(point); + + xValue = _startControlLowXValues[i]; + yValue = _startControlLowYValues[i]; + point = Offset(transformX(xValue, yValue), transformY(xValue, yValue)); + _startControlLowPoints.add(point); + + xValue = _endControlLowXValues[i]; + yValue = _endControlLowYValues[i]; + point = Offset(transformX(xValue, yValue), transformY(xValue, yValue)); + _endControlLowPoints.add(point); + } + } + + int oldLength = _oldHighPoints.length; + if (_highPoints.length > oldLength) { + _oldHighPoints.addAll(_highPoints.sublist(oldLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldLength)); + } + + oldLength = _oldStartControlHighPoints.length; + if (_startControlHighPoints.length > oldLength) { + _oldStartControlHighPoints.addAll( + _startControlHighPoints.sublist(oldLength), + ); + _oldEndControlHighPoints.addAll(_endControlHighPoints.sublist(oldLength)); + _oldStartControlLowPoints.addAll( + _startControlLowPoints.sublist(oldLength), + ); + _oldEndControlLowPoints.addAll(_endControlLowPoints.sublist(oldLength)); + } + } + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty || _lowPoints.isEmpty) { + return; + } + + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + final List lerpStartControlHighPoints = _lerpPoints( + _oldStartControlHighPoints, + _startControlHighPoints, + ); + final List lerpEndControlHighPoints = _lerpPoints( + _oldEndControlHighPoints, + _endControlHighPoints, + ); + final List lerpStartControlLowPoints = _lerpPoints( + _oldStartControlLowPoints, + _startControlLowPoints, + ); + final List lerpEndControlLowPoints = _lerpPoints( + _oldEndControlLowPoints, + _endControlLowPoints, + ); + _createFillPath( + _fillPath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + lerpStartControlLowPoints, + lerpEndControlLowPoints, + ); + + switch (series.borderDrawMode) { + case RangeAreaBorderMode.all: + _strokePath = _fillPath; + break; + case RangeAreaBorderMode.excludeSides: + _createStrokePathForExcludeSides( + _strokePath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + lerpStartControlLowPoints, + lerpEndControlLowPoints, + ); + break; + } + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } + + return lerpPoints; + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + List startControlHighPoints, + List endControlHighPoints, + List startControlLowPoints, + List endControlLowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (i == 0) { + if (lowPoint.isNaN) { + final int sublistFrom = i + 1; + final int controlPointsLength = startControlHighPoints.length; + if (sublistFrom < length && sublistFrom < controlPointsLength) { + _createFillPath( + source, + highPoints.sublist(sublistFrom), + lowPoints.sublist(sublistFrom), + startControlHighPoints.sublist(sublistFrom), + endControlHighPoints.sublist(sublistFrom), + startControlLowPoints.sublist(sublistFrom), + endControlLowPoints.sublist(sublistFrom), + ); + } + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + path.lineTo(highPoint.dx, highPoint.dy); + continue; + } + } + + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + if (j == i - 1) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + continue; + } + + final Offset lowPoint = lowPoints[j]; + final Offset startLow = startControlLowPoints[j]; + final Offset endLow = endControlLowPoints[j]; + path!.cubicTo( + endLow.dx, + endLow.dy, + startLow.dx, + startLow.dy, + lowPoint.dx, + lowPoint.dy, + ); + } + final int controlPointsLength = startControlHighPoints.length; + if (i < length && i < controlPointsLength) { + _createFillPath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + startControlHighPoints.sublist(i), + endControlHighPoints.sublist(i), + startControlLowPoints.sublist(i), + endControlLowPoints.sublist(i), + ); + } + break; + } else { + final Offset startHigh = startControlHighPoints[i - 1]; + final Offset endHigh = endControlHighPoints[i - 1]; + path!.cubicTo( + startHigh.dx, + startHigh.dy, + endHigh.dx, + endHigh.dy, + highPoint.dx, + highPoint.dy, + ); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + if (j == i) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + continue; + } + + final Offset lowPoint = lowPoints[j]; + final Offset startLow = startControlLowPoints[j]; + final Offset endLow = endControlLowPoints[j]; + path.cubicTo( + endLow.dx, + endLow.dy, + startLow.dx, + startLow.dy, + lowPoint.dx, + lowPoint.dy, + ); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createStrokePathForExcludeSides( + Path source, + List highPoints, + List lowPoints, + List startControlHighPoints, + List endControlHighPoints, + List startControlLowPoints, + List endControlLowPoints, + ) { + Path? highPath; + Path? lowPath; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (highPoint.isNaN) { + final int sublistFrom = i + 1; + final int controlPointsLength = startControlHighPoints.length; + if (sublistFrom < length && sublistFrom < controlPointsLength) { + _createStrokePathForExcludeSides( + source, + highPoints.sublist(sublistFrom), + lowPoints.sublist(sublistFrom), + startControlHighPoints.sublist(sublistFrom), + endControlHighPoints.sublist(sublistFrom), + startControlLowPoints.sublist(sublistFrom), + endControlLowPoints.sublist(sublistFrom), + ); + } + break; + } else { + if (i == 0) { + highPath = Path(); + lowPath = Path(); + highPath.moveTo(highPoint.dx, highPoint.dy); + lowPath.moveTo(lowPoint.dx, lowPoint.dy); + } else { + final Offset startHigh = startControlHighPoints[i - 1]; + final Offset endHigh = endControlHighPoints[i - 1]; + highPath!.cubicTo( + startHigh.dx, + startHigh.dy, + endHigh.dx, + endHigh.dy, + highPoint.dx, + highPoint.dy, + ); + + final Offset startLow = startControlLowPoints[i - 1]; + final Offset endLow = endControlLowPoints[i - 1]; + lowPath!.cubicTo( + startLow.dx, + startLow.dy, + endLow.dx, + endLow.dy, + lowPoint.dx, + lowPoint.dy, + ); + } + } + } + + if (highPath != null) { + source.addPath(highPath, Offset.zero); + } + if (lowPath != null) { + source.addPath(lowPath, Offset.zero); + } + return source; + } + + @override + bool contains(Offset position) { + final int length = points.length; + for (int i = 0; i < length; i++) { + final Offset a = points[i]; + final Offset b = i + 1 < length ? points[i + 1] : a; + final Rect rect = Rect.fromPoints(a, b); + final Rect paddedRect = rect.inflate(tooltipPadding); + if (paddedRect.contains(position)) { + return true; + } + i++; + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: _xValues[pointIndex], + high: _highValues[pointIndex], + low: _lowValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num high = chartPoint.high!; + final double dx = series.pointToPixelX(x, high); + final double dy = series.pointToPixelY(x, high); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + hasMultipleYValues: true, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && _highPoints.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + final Offset preferredPos = _highPoints[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(chartPoint.xValue!, chartPoint.low!), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + final Offset a = points[i]; + final Offset b = i + 1 < points.length ? points[i + 1] : a; + final Rect rect = Rect.fromPoints(a, b); + final Rect paddedRect = rect.inflate(tooltipPadding); + if (paddedRect.contains(position)) { + return i ~/ 2; + } + i++; + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _startControlHighXValues.clear(); + _startControlHighYValues.clear(); + _endControlHighXValues.clear(); + _endControlHighYValues.clear(); + _startControlLowXValues.clear(); + _startControlLowYValues.clear(); + _endControlLowXValues.clear(); + _endControlLowYValues.clear(); + _fillPath.reset(); + _strokePath.reset(); + + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + _startControlHighPoints.clear(); + _endControlHighPoints.clear(); + _startControlLowPoints.clear(); + _endControlLowPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart new file mode 100644 index 000000000..e14de03a2 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart @@ -0,0 +1,840 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the stacked area series. +/// +/// A stacked area chart is the extension of a basic area chart to display the +/// evolution of the value of several groups on the same graphic. +/// +/// The values of each group are displayed on top of each other. +/// +/// Stacked area 100 series show the percentage-of-the-whole of each group +/// and are plotted by the percentage of each value to the +/// total amount in each group. +/// +/// Provides options to customize the [color], [opacity], [borderWidth], +/// [borderColor], [borderDrawMode] of the stacked area 100 series segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedArea100Series extends StackedSeriesBase { + /// Creating an argument constructor of StackedArea100Series class. + const StackedArea100Series({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + this.groupName = '', + super.trendlines, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + this.borderDrawMode = BorderDrawMode.top, + }); + + /// Border type of stacked area 100 series. + /// + /// Defaults to `BorderDrawMode.top`. + /// + /// Also refer [BorderDrawMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedAreaSeries( + /// borderDrawMode: BorderDrawMode.all, + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderDrawMode borderDrawMode; + + /// Specifies the group name. + final String groupName; + + final Color borderColor; + + /// Create the stacked area series renderer. + @override + StackedArea100SeriesRenderer createRenderer() { + StackedArea100SeriesRenderer stackedAreaSeriesRenderer; + if (onCreateRenderer != null) { + stackedAreaSeriesRenderer = + onCreateRenderer!(this) as StackedArea100SeriesRenderer; + return stackedAreaSeriesRenderer; + } + return StackedArea100SeriesRenderer(); + } + + @override + StackedArea100SeriesRenderer createRenderObject(BuildContext context) { + final StackedArea100SeriesRenderer renderer = + super.createRenderObject(context) as StackedArea100SeriesRenderer; + renderer + ..borderDrawMode = borderDrawMode + ..groupName = groupName + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedArea100SeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..borderDrawMode = borderDrawMode + ..groupName = groupName + ..borderColor = borderColor; + } +} + +/// Creates series renderer for 100% stacked area series. +class StackedArea100SeriesRenderer extends StackedSeriesRenderer + with ContinuousSeriesMixin, Stacking100SeriesMixin { + BorderDrawMode get borderDrawMode => _borderDrawMode; + BorderDrawMode _borderDrawMode = BorderDrawMode.top; + set borderDrawMode(BorderDrawMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + forceTransformValues = true; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as StackedArea100Segment + ..series = this + ..currentSegmentIndex = 0 + .._xValues = xValues + .._topValues = topValues + .._bottomValues = bottomValues + .._bottom = xAxis!.crossesAt; + } + + /// Creates a segment for a data point in the series. + @override + StackedArea100Segment createSegment() => StackedArea100Segment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.stackedArea100Series; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedArea100Segment stackedArea100Segment = + segment as StackedArea100Segment; + updateSegmentColor(stackedArea100Segment, borderColor, borderWidth); + updateSegmentGradient( + stackedArea100Segment, + gradientBounds: stackedArea100Segment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Segment class for 100% stacked area series. +class StackedArea100Segment extends ChartSegment { + late StackedArea100SeriesRenderer series; + late List _xValues; + late List _topValues; + late List _bottomValues; + // ignore: unused_field + num? _bottom; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _oldHighPoints = []; + final List _oldLowPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + if (_xValues.isEmpty || _topValues.isEmpty || _bottomValues.isEmpty) { + return; + } + + _fillPath.reset(); + _strokePath.reset(); + + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _calculatePoints(_xValues, _topValues, _bottomValues); + break; + + case EmptyPointMode.drop: + _calculateDropPoints(_xValues, _topValues, _bottomValues); + break; + } + _createFillPath(_fillPath, _highPoints, _lowPoints); + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + void _calculatePoints( + List xValues, + List topValues, + List bottomValues, + ) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool isGap = series.emptyPointSettings.mode == EmptyPointMode.gap; + final List rawYValues = series.yValues; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + num topY = topValues[i]; + num bottomY = bottomValues[i]; + + final num rawY = rawYValues[i]; + if (rawY.isNaN && isGap) { + topY = bottomY = double.nan; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); + _highPoints.add(highPoint); + + final Offset lowPoint = Offset( + transformX(x, bottomY), + transformY(x, bottomY), + ); + _lowPoints.add(lowPoint); + + points.add(highPoint); + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + void _calculateDropPoints( + List xValues, + List topValues, + List bottomValues, + ) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + final List rawYValues = series.yValues; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num topY = topValues[i]; + + final num rawY = rawYValues[i]; + if (rawY.isNaN && canDrop) { + continue; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); + _highPoints.add(highPoint); + points.add(highPoint); + } + + final List rawPrevSeriesYValues = series.prevSeriesYValues; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num bottomY = bottomValues[i]; + + final num rawY = rawPrevSeriesYValues[i]; + if (rawY.isNaN && canDrop) { + continue; + } + + final Offset lowPoint = Offset( + transformX(x, bottomY), + transformY(x, bottomY), + ); + _lowPoints.add(lowPoint); + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + + case EmptyPointMode.drop: + _createDropFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + } + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + + case BorderDrawMode.excludeBottom: + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createExcludeBottomStrokePath( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + + case EmptyPointMode.drop: + _createExcludeBottomStrokePathForDrop( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + } + break; + } + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } + + return lerpPoints; + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createFillPath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + _createFillPath(source, highPoints.sublist(i), lowPoints.sublist(i)); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createDropFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } + path!.lineTo(highPoint.dx, highPoint.dy); + } + + length = lowPoints.length; + for (int j = length - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + + if (path != null) { + path.close(); + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createTopStrokePath(Path source, List highPoints) { + Path? path; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + _createTopStrokePath(source, highPoints.sublist(i + 1)); + break; + } else { + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + + return source; + } + + Path _createExcludeBottomStrokePath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + final Offset lowPoint = lowPoints[i - 1]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + ); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + final Offset lowPoint = lowPoints[i]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + + return source; + } + + Path _createExcludeBottomStrokePathForDrop( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + path!.lineTo(highPoint.dx, highPoint.dy); + } + + final int lastIndex = lowPoints.length - 1; + final Offset lowPoint = lowPoints[lastIndex]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + + source.addPath(path, Offset.zero); + return source; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + cumulative: series.topValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num y = series.topValues[actualPointIndex]; + final double dx = series.pointToPixelX(x, y); + final double dy = series.pointToPixelY(x, y); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + + final Offset preferredPos = points[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _fillPath.reset(); + _drawIndexes.clear(); + _strokePath.reset(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart new file mode 100644 index 000000000..adc914554 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart @@ -0,0 +1,847 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the stacked area series. +/// +/// A stacked area chart is the extension of a basic area chart to display +/// the evolution of the value of several groups on the same graphic. +/// +/// The values of each group are displayed on top of each other. +/// +/// To render a stacked area chart, create an instance of [StackedAreaSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// +/// Provides options to customize the [color], [opacity], [borderWidth], +/// [borderColor] of the stacked area segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedAreaSeries extends StackedSeriesBase { + /// Creating an argument constructor of StackedAreaSeries class. + const StackedAreaSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + this.groupName = '', + super.trendlines, + super.color, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + this.borderDrawMode = BorderDrawMode.top, + }); + + /// Border type of stacked area series. + /// + /// Defaults to `BorderDrawMode.top`. + /// + /// Also refer [BorderDrawMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedAreaSeries( + /// borderDrawMode: BorderDrawMode.all, + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderDrawMode borderDrawMode; + + /// Specifies the group name. + final String groupName; + + final Color borderColor; + + /// Create the stacked area series renderer. + @override + StackedAreaSeriesRenderer createRenderer() { + StackedAreaSeriesRenderer stackedAreaSeriesRenderer; + if (onCreateRenderer != null) { + stackedAreaSeriesRenderer = + onCreateRenderer!(this) as StackedAreaSeriesRenderer; + return stackedAreaSeriesRenderer; + } + return StackedAreaSeriesRenderer(); + } + + @override + StackedAreaSeriesRenderer createRenderObject(BuildContext context) { + final StackedAreaSeriesRenderer renderer = + super.createRenderObject(context) as StackedAreaSeriesRenderer; + renderer + ..groupName = groupName + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedAreaSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..groupName = groupName + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + } +} + +/// Creates series renderer for stacked area series. +class StackedAreaSeriesRenderer extends StackedSeriesRenderer + with ContinuousSeriesMixin { + BorderDrawMode get borderDrawMode => _borderDrawMode; + BorderDrawMode _borderDrawMode = BorderDrawMode.top; + set borderDrawMode(BorderDrawMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + forceTransformValues = true; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as StackedAreaSegment + ..series = this + ..currentSegmentIndex = 0 + .._xValues = xValues + .._topValues = topValues + .._bottomValues = bottomValues + .._bottom = xAxis!.crossesAt; + } + + /// Creates a segment for a data point in the series. + @override + StackedAreaSegment createSegment() => StackedAreaSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.stackedAreaSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedAreaSegment stackedAreaSegment = + segment as StackedAreaSegment; + updateSegmentColor(stackedAreaSegment, borderColor, borderWidth); + updateSegmentGradient( + stackedAreaSegment, + gradientBounds: stackedAreaSegment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Segment class for stacked area series. +class StackedAreaSegment extends ChartSegment { + late StackedAreaSeriesRenderer series; + late List _xValues; + late List _topValues; + late List _bottomValues; + // ignore: unused_field + num? _bottom; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _oldHighPoints = []; + final List _oldLowPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _highPoints[i].dy, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _lowPoints[i].dy, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + if (_xValues.isEmpty || _topValues.isEmpty || _bottomValues.isEmpty) { + return; + } + + _fillPath.reset(); + _strokePath.reset(); + + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _calculatePoints(_xValues, _topValues, _bottomValues); + break; + + case EmptyPointMode.drop: + _calculateDropPoints(_xValues, _topValues, _bottomValues); + break; + } + _createFillPath(_fillPath, _highPoints, _lowPoints); + } + + void _calculatePoints( + List xValues, + List topValues, + List bottomValues, + ) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool isGap = series.emptyPointSettings.mode == EmptyPointMode.gap; + final List rawYValues = series.yValues; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + num topY = topValues[i]; + num bottomY = bottomValues[i]; + + final num rawY = rawYValues[i]; + if (rawY.isNaN && isGap) { + topY = bottomY = double.nan; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); + _highPoints.add(highPoint); + + final Offset lowPoint = Offset( + transformX(x, bottomY), + transformY(x, bottomY), + ); + _lowPoints.add(lowPoint); + + points.add(highPoint); + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + void _calculateDropPoints( + List xValues, + List topValues, + List bottomValues, + ) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + final List rawYValues = series.yValues; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num topY = topValues[i]; + + final num rawY = rawYValues[i]; + if (rawY.isNaN && canDrop) { + continue; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); + _highPoints.add(highPoint); + points.add(highPoint); + } + + final List rawPrevSeriesYValues = series.prevSeriesYValues; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num bottomY = bottomValues[i]; + + final num rawY = rawPrevSeriesYValues[i]; + if (rawY.isNaN && canDrop) { + continue; + } + + final Offset lowPoint = Offset( + transformX(x, bottomY), + transformY(x, bottomY), + ); + _lowPoints.add(lowPoint); + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + + case EmptyPointMode.drop: + _createDropFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + } + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + + case BorderDrawMode.excludeBottom: + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createExcludeBottomStrokePath( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + + case EmptyPointMode.drop: + _createExcludeBottomStrokePathForDrop( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + } + break; + } + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, newPoints[i].dy)!, + ); + } + } + + return lerpPoints; + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createFillPath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + _createFillPath(source, highPoints.sublist(i), lowPoints.sublist(i)); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createDropFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } + path!.lineTo(highPoint.dx, highPoint.dy); + } + + length = lowPoints.length; + for (int j = length - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + + if (path != null) { + path.close(); + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createTopStrokePath(Path source, List highPoints) { + Path? path; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + _createTopStrokePath(source, highPoints.sublist(i + 1)); + break; + } else { + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + + return source; + } + + Path _createExcludeBottomStrokePath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + if (lowPoint.isNaN) { + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + final Offset lowPoint = lowPoints[i - 1]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + ); + break; + } else { + path!.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + final Offset lowPoint = lowPoints[i]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + + return source; + } + + Path _createExcludeBottomStrokePathForDrop( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + if (i == 0) { + final Offset lowPoint = lowPoints[i]; + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + } + } + + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + path!.lineTo(highPoint.dx, highPoint.dy); + } + + final int lastIndex = lowPoints.length - 1; + final Offset lowPoint = lowPoints[lastIndex]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + + source.addPath(path, Offset.zero); + return source; + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + cumulative: series.topValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num y = series.topValues[actualPointIndex]; + final double dx = series.pointToPixelX(x, y); + final double dy = series.pointToPixelY(x, y); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + + final Offset preferredPos = points[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _fillPath.reset(); + _strokePath.reset(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + + _xValues.clear(); + _topValues.clear(); + _bottomValues.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart new file mode 100644 index 000000000..87cdb22dc --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart @@ -0,0 +1,481 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/element_widget.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the 100% stacked bar series. +/// +/// A [StackedBar100Series] is a chart series type designed to show the relative +/// percentage of multiple data series in stacked bars, +/// where the total (cumulative) of each stacked bar always equals 100. +/// +/// To render a 100% stacked bar chart, create an instance of +/// [StackedBar100Series], and add it to the series collection +/// property of [SfCartesianChart]. +/// +/// Provides options to customize properties such as [color], [opacity], +/// [borderWidth], [borderColor], [borderRadius] of the +/// stacked bar 100 segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedBar100Series extends StackedSeriesBase { + /// Creating an argument constructor of StackedBar100Series class. + const StackedBar100Series({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + this.groupName = '', + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + this.spacing = 0.0, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.borderRadius = BorderRadius.zero, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.trendlines, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Customizes the corners of the bar. + /// + /// Each corner can be customized with desired + /// value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedBar100Series( + /// borderRadius: BorderRadius.all(Radius.circular(5)) + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double spacing; + + final double width; + + /// Specifies the group name. + final String groupName; + + final Color borderColor; + + @override + bool transposed() => true; + + /// Create the stacked area series renderer. + @override + StackedBar100SeriesRenderer createRenderer() { + StackedBar100SeriesRenderer stackedBarSeriesRenderer; + if (onCreateRenderer != null) { + stackedBarSeriesRenderer = + onCreateRenderer!(this) as StackedBar100SeriesRenderer; + return stackedBarSeriesRenderer; + } + return StackedBar100SeriesRenderer(); + } + + @override + StackedBar100SeriesRenderer createRenderObject(BuildContext context) { + final StackedBar100SeriesRenderer renderer = + super.createRenderObject(context) as StackedBar100SeriesRenderer; + renderer + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedBar100SeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + } +} + +/// Creates series renderer for 100% stacked bar series. +class StackedBar100SeriesRenderer extends StackedSeriesRenderer + with + SbsSeriesMixin, + ClusterSeriesMixin, + SegmentAnimationMixin, + Stacking100SeriesMixin { + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + final double bottomValue = bottomValues[current.dataPointIndex].toDouble(); + double y = current.y!.toDouble(); + if (alignment == ChartDataLabelAlignment.bottom) { + y = bottomValue; + } else if (alignment == ChartDataLabelAlignment.middle) { + y = (y + bottomValue) / 2; + } + return _calculateDataLabelPosition( + x, + y, + alignment, + size, + current.y!.isNegative, + ); + } + + Offset _calculateDataLabelPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + bool isNegative, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? -(dataLabelPadding + size.height + margin.vertical) + : dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.middle: + final Offset center = translateTransform(x, y); + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return center.translate(translationX, translationY); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as StackedBar100Segment + ..series = this + ..x = xValues[index] + ..top = topValues[index] + ..bottom = xAxis!.crossesAt ?? bottomValues[index] + .._actualBottom = bottom + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StackedBar100Segment createSegment() => StackedBar100Segment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.stackedBar100Series; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedBar100Segment stackedBar100Segment = + segment as StackedBar100Segment; + updateSegmentColor(stackedBar100Segment, borderColor, borderWidth); + updateSegmentGradient( + stackedBar100Segment, + gradientBounds: stackedBar100Segment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } +} + +/// Segment class for 100% stacked bar series. +class StackedBar100Segment extends ChartSegment { + late StackedBar100SeriesRenderer series; + late num x; + + num top = double.nan; + num bottom = double.nan; + num _actualBottom = double.nan; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, _actualBottom), + transformY(left, _actualBottom), + transformX(right, _actualBottom), + transformY(right, _actualBottom), + borderRadius, + ); + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + cumulative: series.topValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, top), + series.pointToPixelY(x, top), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart new file mode 100644 index 000000000..505d72460 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart @@ -0,0 +1,513 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/element_widget.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the stacked bar series. +/// +/// Stacked bar chart consists of multiple bar series stacked horizontally one +/// after another. The length of each series is determined by the value in each +/// data point. +/// +/// To render a stacked bar chart, create an instance of [StackedBarSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// +/// Provides options to customize properties such as [color], [opacity], +/// [borderWidth], [borderColor], [borderRadius] of the stacked bar segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedBarSeries extends StackedSeriesBase { + /// Creating an argument constructor of StackedBarSeries class. + const StackedBarSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.isTrackVisible = false, + this.groupName = '', + super.trackColor = Colors.grey, + super.trackBorderColor = Colors.transparent, + super.trackBorderWidth = 1.0, + super.trackPadding = 0.0, + this.borderRadius = BorderRadius.zero, + this.spacing = 0.0, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + super.trendlines, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Customizes the corners of the column. Each corner can be customized with + /// a desired value or with a single value. + /// + /// Defaults to `BorderRadius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedBarSeries( + /// borderRadius: BorderRadius.circular(5), + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double spacing; + + final double width; + + /// Specifies the group name. + final String groupName; + + final Color borderColor; + + @override + bool transposed() => true; + + /// Create the stacked bar series renderer. + @override + StackedBarSeriesRenderer createRenderer() { + StackedBarSeriesRenderer stackedAreaSeriesRenderer; + if (onCreateRenderer != null) { + stackedAreaSeriesRenderer = + onCreateRenderer!(this) as StackedBarSeriesRenderer; + return stackedAreaSeriesRenderer; + } + return StackedBarSeriesRenderer(); + } + + @override + StackedBarSeriesRenderer createRenderObject(BuildContext context) { + final StackedBarSeriesRenderer renderer = + super.createRenderObject(context) as StackedBarSeriesRenderer; + renderer + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedBarSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + } +} + +/// Creates series renderer for stacked bar series. +class StackedBarSeriesRenderer extends StackedSeriesRenderer + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + @override + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + return alignment == ChartDataLabelAlignment.auto || + alignment == ChartDataLabelAlignment.outer + ? ChartDataLabelAlignment.top + : alignment; + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + final num stackValue = yValues[current.dataPointIndex]; + double y = current.y!.toDouble(); + if (alignment == ChartDataLabelAlignment.bottom) { + y = y - stackValue; + } else if (alignment == ChartDataLabelAlignment.middle) { + y = (y + (y - stackValue)) / 2; + } + return _calculateDataLabelPosition( + x, + y, + alignment, + size, + current.y!.isNegative, + ); + } + + Offset _calculateDataLabelPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + bool isNegative, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? -(dataLabelPadding + size.height + margin.vertical) + : dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.middle: + final Offset center = translateTransform(x, y); + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return center.translate(translationX, translationY); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as StackedBarSegment + ..series = this + ..x = xValues[index] + ..top = topValues[index] + ..bottom = xAxis!.crossesAt ?? bottomValues[index] + .._actualBottom = bottom + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StackedBarSegment createSegment() => StackedBarSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedBarSegment stackedBarSegment = + segment as StackedBarSegment; + updateSegmentTrackerStyle( + stackedBarSegment, + trackColor, + trackBorderColor, + trackBorderWidth, + ); + updateSegmentColor(stackedBarSegment, borderColor, borderWidth); + updateSegmentGradient( + stackedBarSegment, + gradientBounds: stackedBarSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.stackedBarSeries; +} + +/// Segment class for stacked bar series. +class StackedBarSegment extends ChartSegment with BarSeriesTrackerMixin { + late StackedBarSeriesRenderer series; + late num x; + + num top = double.nan; + num bottom = double.nan; + num _actualBottom = double.nan; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, _actualBottom), + transformY(left, _actualBottom), + transformX(right, _actualBottom), + transformY(right, _actualBottom), + borderRadius, + ); + + if (series.isTrackVisible) { + calculateTrackerBounds( + left, + right, + borderRadius, + series.trackPadding, + series.trackBorderWidth, + series, + ); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + cumulative: series.topValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, top), + series.pointToPixelY(x, top), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } + + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart new file mode 100644 index 000000000..7db82166f --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart @@ -0,0 +1,476 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/element_widget.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the 100% stacked column series. +/// +/// A stacked column 100 is an chart series type meant to show the relative +/// percentage of multiple data series in stacked columns, where the total +/// (cumulative) of stacked columns always equals 100%. +/// +/// To render a 100% stacked column chart, create an instance of +/// [StackedColumn100Series], and add it to the series collection +/// property of [SfCartesianChart]. +/// +/// Provides options to customize properties such as [color], [opacity], +/// [borderWidth], [borderColor], [borderRadius] of the +/// stacked column 100 segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedColumn100Series extends StackedSeriesBase { + /// Creating an argument constructor of StackedColumn100Series class. + const StackedColumn100Series({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + super.markerSettings, + super.trendlines, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.groupName = '', + this.borderRadius = BorderRadius.zero, + this.spacing = 0.0, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Customizes the corners of the bar. + /// + /// Each corner can be customized with desired + /// value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// BarSeries( + /// borderRadius: BorderRadius.all(Radius.circular(5)) + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double width; + + final double spacing; + + /// Specifies the group name. + final String groupName; + + final Color borderColor; + + /// Create the stacked area series renderer. + @override + StackedColumn100SeriesRenderer createRenderer() { + StackedColumn100SeriesRenderer stackedAreaSeriesRenderer; + if (onCreateRenderer != null) { + stackedAreaSeriesRenderer = + onCreateRenderer!(this) as StackedColumn100SeriesRenderer; + return stackedAreaSeriesRenderer; + } + return StackedColumn100SeriesRenderer(); + } + + @override + StackedColumn100SeriesRenderer createRenderObject( + BuildContext context, + ) { + final StackedColumn100SeriesRenderer renderer = + super.createRenderObject(context) + as StackedColumn100SeriesRenderer; + renderer + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedColumn100SeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + } +} + +/// Creates series renderer for 100% stacked column series. +class StackedColumn100SeriesRenderer extends StackedSeriesRenderer + with + SbsSeriesMixin, + ClusterSeriesMixin, + SegmentAnimationMixin, + Stacking100SeriesMixin { + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + final double bottomValue = bottomValues[current.dataPointIndex].toDouble(); + double y = current.y!.toDouble(); + if (alignment == ChartDataLabelAlignment.bottom) { + y = bottomValue; + } else if (alignment == ChartDataLabelAlignment.middle) { + y = (y + bottomValue) / 2; + } + return _calculateDataLabelPosition( + x, + y, + alignment, + size, + current.y!.isNegative, + ); + } + + Offset _calculateDataLabelPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + bool isNegative, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? -(dataLabelPadding + size.height + margin.vertical) + : dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.middle: + final Offset center = translateTransform(x, y); + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return center.translate(translationX, translationY); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as StackedColumn100Segment + ..series = this + ..x = xValues[index] + ..top = topValues[index] + ..bottom = xAxis!.crossesAt ?? bottomValues[index] + .._actualBottom = bottom + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StackedColumn100Segment createSegment() => + StackedColumn100Segment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.stackedColumn100Series; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedColumn100Segment stackedColumn100Segment = + segment as StackedColumn100Segment; + updateSegmentColor(stackedColumn100Segment, borderColor, borderWidth); + updateSegmentGradient( + stackedColumn100Segment, + gradientBounds: stackedColumn100Segment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } +} + +/// Segment class for 100% stacked column series. +class StackedColumn100Segment extends ChartSegment { + late StackedColumn100SeriesRenderer series; + late num x; + + num top = double.nan; + num bottom = double.nan; + num _actualBottom = double.nan; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, _actualBottom), + transformY(left, _actualBottom), + transformX(right, _actualBottom), + transformY(right, _actualBottom), + borderRadius, + ); + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + cumulative: series.topValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, top), + series.pointToPixelY(x, top), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart new file mode 100644 index 000000000..a745e18f0 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart @@ -0,0 +1,514 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/element_widget.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the stacked column series. +/// +/// In a stacked column chart, data series are stacked one on top of the other +/// in vertical columns. +/// +/// To render a stacked column chart, create an instance of +/// [StackedColumnSeries], and add it to the series collection +/// property of [SfCartesianChart]. +/// +/// Provides options to customize properties such as [color], [opacity], +/// [borderWidth], [borderColor], [borderRadius] of the stacked column segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedColumnSeries extends StackedSeriesBase { + /// Creating an argument constructor of StackedColumnSeries class. + const StackedColumnSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.isTrackVisible = false, + this.groupName = '', + super.trackColor = Colors.grey, + super.trackBorderColor = Colors.transparent, + super.trackBorderWidth = 1.0, + super.trackPadding = 0.0, + this.borderRadius = BorderRadius.zero, + this.spacing = 0.0, + super.xAxisName, + super.yAxisName, + super.trendlines, + super.name, + super.color, + this.width = 0.7, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.dashArray, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Customizes the corners of the bar. + /// + /// Each corner can be customized with desired + /// value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StackedColumnSeries( + /// borderRadius: BorderRadius.all(Radius.circular(5)) + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + final double spacing; + + final double width; + + /// Specifies the group name. + final String groupName; + + final Color borderColor; + + /// Create the stacked area series renderer. + @override + StackedColumnSeriesRenderer createRenderer() { + StackedColumnSeriesRenderer stackedAreaSeriesRenderer; + if (onCreateRenderer != null) { + stackedAreaSeriesRenderer = + onCreateRenderer!(this) as StackedColumnSeriesRenderer; + return stackedAreaSeriesRenderer; + } + return StackedColumnSeriesRenderer(); + } + + @override + StackedColumnSeriesRenderer createRenderObject(BuildContext context) { + final StackedColumnSeriesRenderer renderer = + super.createRenderObject(context) as StackedColumnSeriesRenderer; + renderer + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedColumnSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..spacing = spacing + ..width = width + ..groupName = groupName + ..borderColor = borderColor + ..borderRadius = borderRadius; + } +} + +/// Creates series renderer for stacked column series. +class StackedColumnSeriesRenderer extends StackedSeriesRenderer + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + @override + ChartDataLabelAlignment effectiveDataLabelAlignment( + ChartDataLabelAlignment alignment, + ChartDataPointType position, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + ) { + return alignment == ChartDataLabelAlignment.auto || + alignment == ChartDataLabelAlignment.outer + ? ChartDataLabelAlignment.top + : alignment; + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + final num stackValue = yValues[current.dataPointIndex]; + double y = current.y!.toDouble(); + if (alignment == ChartDataLabelAlignment.bottom) { + y = y - stackValue; + } else if (alignment == ChartDataLabelAlignment.middle) { + y = (y + (y - stackValue)) / 2; + } + return _calculateDataLabelPosition( + x, + y, + alignment, + size, + current.y!.isNegative, + ); + } + + Offset _calculateDataLabelPosition( + num x, + num y, + ChartDataLabelAlignment alignment, + Size size, + bool isNegative, + ) { + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? -(dataLabelPadding + size.height + margin.vertical) + : dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.middle: + final Offset center = translateTransform(x, y); + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return center.translate(translationX, translationY); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as StackedColumnSegment + ..series = this + ..x = xValues[index] + ..top = topValues[index] + ..bottom = xAxis!.crossesAt ?? bottomValues[index] + .._actualBottom = bottom + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StackedColumnSegment createSegment() => StackedColumnSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + ShapeMarkerType.stackedColumnSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedColumnSegment stackedColumnSegment = + segment as StackedColumnSegment; + updateSegmentTrackerStyle( + stackedColumnSegment, + trackColor, + trackBorderColor, + trackBorderWidth, + ); + updateSegmentColor(stackedColumnSegment, borderColor, borderWidth); + updateSegmentGradient( + stackedColumnSegment, + gradientBounds: stackedColumnSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + } +} + +/// Segment class for stacked column series. +class StackedColumnSegment extends ChartSegment + with BarSeriesTrackerMixin { + late StackedColumnSeriesRenderer series; + late num x; + + num top = double.nan; + num bottom = double.nan; + num _actualBottom = double.nan; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num right = x + series.sbsInfo.maximum; + + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, _actualBottom), + transformY(left, _actualBottom), + transformX(right, _actualBottom), + transformY(right, _actualBottom), + borderRadius, + ); + + if (series.isTrackVisible) { + calculateTrackerBounds( + left, + right, + borderRadius, + series.trackPadding, + series.trackBorderWidth, + series, + ); + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: series.yValues[currentSegmentIndex], + cumulative: series.topValues[currentSegmentIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, top), + series.pointToPixelY(x, top), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } + + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + } + + @override + void dispose() { + segmentRect = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart new file mode 100644 index 000000000..8c893abf2 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart @@ -0,0 +1,385 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the 100% stacked line series. +/// +/// A stacked 100 line chart is a line chart in which lines do not overlap +/// because they are cumulative at each point. In the stacked 100 line chart, +/// the lines reach a total of 100% of the axis range at each point. +/// +/// To render a 100% stacked line chart, create an instance of +/// [StackedLine100Series], and add it to the series collection property of +/// [SfCartesianChart]. Provides options to customize color,opacity and width +/// of the StackedLine100 segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedLine100Series extends StackedSeriesBase { + /// Creating an argument constructor of StackedLine100Series class. + const StackedLine100Series({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.color, + double width = 2, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.name, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + super.trendlines, + this.groupName = '', + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.sortingOrder, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }) : super(borderWidth: width); + + /// Specifies the group name. + final String groupName; + + /// To create a stacked line 100 series renderer. + @override + StackedLine100SeriesRenderer createRenderer() { + StackedLine100SeriesRenderer stackedLine100SeriesRenderer; + if (onCreateRenderer != null) { + stackedLine100SeriesRenderer = + onCreateRenderer!(this) as StackedLine100SeriesRenderer; + return stackedLine100SeriesRenderer; + } + return StackedLine100SeriesRenderer(); + } + + @override + StackedLine100SeriesRenderer createRenderObject(BuildContext context) { + final StackedLine100SeriesRenderer renderer = + super.createRenderObject(context) as StackedLine100SeriesRenderer; + renderer.groupName = groupName; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedLine100SeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.groupName = groupName; + } +} + +/// Creates series renderer for 100% stacked line series. +class StackedLine100SeriesRenderer extends StackedSeriesRenderer + with LineSeriesMixin, Stacking100SeriesMixin { + @override + double legendIconBorderWidth() { + return 3; + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + final num x1 = xValues[index]; + final num y1 = topValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + final int nextIndex = nextIndexConsideringEmptyPointMode( + index, + emptyPointSettings.mode, + topValues, + dataCount, + ); + if (nextIndex != -1) { + x2 = xValues[nextIndex]; + y2 = topValues[nextIndex]; + } + + segment as StackedLine100Segment + ..series = this + .._x1 = x1 + .._y1 = y1 + .._x2 = x2 + .._y2 = y2 + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StackedLine100Segment createSegment() => StackedLine100Segment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + dashArray != null && !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.stackedLine100SeriesWithDashArray + : ShapeMarkerType.stackedLine100Series; + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth, isLineType: true); + updateSegmentGradient(segment); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Segment class for 100% stacked line series. +class StackedLine100Segment extends ChartSegment { + late StackedLine100SeriesRenderer series; + late num _x1, _y1, _x2, _y2; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + if (!_x1.isNaN && !_y1.isNaN) { + points.add(Offset(transformX(_x1, _y1), transformY(_x1, _y1))); + } + + if (!_x2.isNaN && !_y2.isNaN) { + points.add(Offset(transformX(_x2, _y2), transformY(_x2, _y2))); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + int _nearestPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + cumulative: series.topValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + final int nearestPointIndex = + position == null ? 0 : _nearestPointIndex(points, position); + if (nearestPointIndex != -1) { + pointIndex ??= + (position == null || nearestPointIndex == 0 + ? currentSegmentIndex + : currentSegmentIndex + 1); + CartesianChartPoint chartPoint = _chartPoint(pointIndex); + List markerColors = [fillPaint.color]; + if (chartPoint.y != null && chartPoint.y!.isNaN) { + pointIndex += 1; + chartPoint = _chartPoint(pointIndex); + markerColors = [series.segments[pointIndex].fillPaint.color]; + } + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = points[nearestPointIndex]; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: markerColors, + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; + } + + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => strokePaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty || points.length != 2) { + return; + } + + final Paint paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes( + canvas, + series.dashArray, + paint, + start: points[0], + end: points[1], + ); + } + } + + @override + void dispose() { + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart new file mode 100644 index 000000000..3f9cbd406 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart @@ -0,0 +1,387 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the stacked line series. +/// +/// A stacked line chart is a line chart in which lines do not overlap because +/// they are cumulative at each point. +/// +/// A stacked line chart displays series as a set of points connected by a line. +/// +/// To render a stacked line chart, create an instance of [StackedLineSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// Provides options to customize [color], [opacity], [width] of the +/// stacked line segments. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} +@immutable +class StackedLineSeries extends StackedSeriesBase { + /// Creating an argument constructor of StackedLineSeries class. + const StackedLineSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.color, + double width = 2, + super.markerSettings, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.name, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.groupName = '', + super.trendlines, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.sortingOrder, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }) : super(borderWidth: width); + + /// Specifies the group name. + final String groupName; + + /// To create a stacked line series renderer. + @override + StackedLineSeriesRenderer createRenderer() { + StackedLineSeriesRenderer stackedLineSeriesRenderer; + if (onCreateRenderer != null) { + stackedLineSeriesRenderer = + onCreateRenderer!(this) as StackedLineSeriesRenderer; + return stackedLineSeriesRenderer; + } + return StackedLineSeriesRenderer(); + } + + @override + StackedLineSeriesRenderer createRenderObject(BuildContext context) { + final StackedLineSeriesRenderer renderer = + super.createRenderObject(context) as StackedLineSeriesRenderer; + renderer.groupName = groupName; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StackedLineSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.groupName = groupName; + } +} + +/// Creates series renderer for stacked line series. +class StackedLineSeriesRenderer extends StackedSeriesRenderer + with LineSeriesMixin { + @override + double legendIconBorderWidth() { + return 3; + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + final num x1 = xValues[index]; + final num y1 = topValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + final int nextIndex = nextIndexConsideringEmptyPointMode( + index, + emptyPointSettings.mode, + topValues, + dataCount, + ); + if (nextIndex != -1) { + x2 = xValues[nextIndex]; + y2 = topValues[nextIndex]; + } + + segment as StackedLineSegment + ..series = this + .._x1 = x1 + .._y1 = y1 + .._x2 = x2 + .._y2 = y2 + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StackedLineSegment createSegment() => StackedLineSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + dashArray != null && !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.stackedLineSeriesWithDashArray + : ShapeMarkerType.stackedLineSeries; + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth, isLineType: true); + updateSegmentGradient(segment); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Segment class for stacked line series. +class StackedLineSegment extends ChartSegment { + late StackedLineSeriesRenderer series; + late num _x1, _y1, _x2, _y2; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + if (!_x1.isNaN && !_y1.isNaN) { + points.add(Offset(transformX(_x1, _y1), transformY(_x1, _y1))); + } + + if (!_x2.isNaN && !_y2.isNaN) { + points.add(Offset(transformX(_x2, _y2), transformY(_x2, _y2))); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + int _nearestPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + cumulative: series.topValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + final int nearestPointIndex = + position == null ? 0 : _nearestPointIndex(points, position); + if (nearestPointIndex != -1) { + pointIndex ??= + (position == null || nearestPointIndex == 0 + ? currentSegmentIndex + : currentSegmentIndex + 1); + CartesianChartPoint chartPoint = _chartPoint(pointIndex); + List markerColors = [fillPaint.color]; + if (chartPoint.y != null && chartPoint.y!.isNaN) { + pointIndex += 1; + chartPoint = _chartPoint(pointIndex); + markerColors = [series.segments[pointIndex].fillPaint.color]; + } + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = points[nearestPointIndex]; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: markerColors, + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; + } + + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => strokePaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty || points.length != 2) { + return; + } + + final Paint paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes( + canvas, + series.dashArray, + paint, + start: points[0], + end: points[1], + ); + } + } + + @override + void dispose() { + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart new file mode 100644 index 000000000..5f4a51da5 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart @@ -0,0 +1,684 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the step area series. +/// +/// A step area chart is a form of area chart which is useful for displaying +/// changing numeric values over a period of time. +/// +/// The x-axis in a step area chart represents a period of time, and +/// values are plotted on the y-axis at constant or irregular time intervals. +/// +/// Step area charts are similar to step line charts, except in a step area +/// chart the area occupied by the data series is filled in with color. +/// +/// To render a spline area chart, create an instance of [StepAreaSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// +/// Provides options to customize the [color], [opacity], [width] of the +/// step area segments. +@immutable +class StepAreaSeries extends XyDataSeries { + /// Creating an argument constructor of StepAreaSeries class. + const StepAreaSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + super.markerSettings, + super.trendlines, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + this.borderColor = Colors.transparent, + super.borderWidth, + super.gradient, + super.borderGradient, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + this.borderDrawMode = BorderDrawMode.top, + }); + + /// Border type of step area series. + /// + /// Defaults to `BorderDrawMode.top`. + /// + /// Also refer [BorderDrawMode]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// StepAreaSeries( + /// borderDrawMode: BorderDrawMode.all, + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderDrawMode borderDrawMode; + + final Color borderColor; + + /// To create a step area series renderer. + @override + StepAreaSeriesRenderer createRenderer() { + StepAreaSeriesRenderer stepAreaRenderer; + if (onCreateRenderer != null) { + stepAreaRenderer = + onCreateRenderer!(this) as StepAreaSeriesRenderer; + return stepAreaRenderer; + } + return StepAreaSeriesRenderer(); + } + + @override + StepAreaSeriesRenderer createRenderObject(BuildContext context) { + final StepAreaSeriesRenderer renderer = + super.createRenderObject(context) as StepAreaSeriesRenderer; + renderer + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + StepAreaSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..borderDrawMode = borderDrawMode + ..borderColor = borderColor; + } +} + +/// Creates series renderer for step area series. +class StepAreaSeriesRenderer extends XyDataSeriesRenderer + with ContinuousSeriesMixin { + BorderDrawMode get borderDrawMode => _borderDrawMode; + BorderDrawMode _borderDrawMode = BorderDrawMode.top; + set borderDrawMode(BorderDrawMode value) { + if (_borderDrawMode != value) { + _borderDrawMode = value; + forceTransformValues = true; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + final num bottom = xAxis!.crossesAt ?? max(yAxis!.visibleRange!.minimum, 0); + segment as StepAreaSegment + ..series = this + .._xValues = xValues + .._yValues = yValues + .._bottom = bottom; + } + + /// Creates a segment for a data point in the series. + @override + StepAreaSegment createSegment() => StepAreaSegment(); + + /// Changes the series color, border color and border width. + @override + void customizeSegment(ChartSegment segment) { + final StepAreaSegment stepAreaSegment = + segment as StepAreaSegment; + updateSegmentColor(stepAreaSegment, borderColor, borderWidth); + updateSegmentGradient( + stepAreaSegment, + gradientBounds: stepAreaSegment._fillPath.getBounds(), + gradient: gradient, + borderGradient: borderGradient, + ); + } + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.stepAreaSeries; + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Segment class for step area series. +class StepAreaSegment extends ChartSegment { + late StepAreaSeriesRenderer series; + late List _xValues; + late List _yValues; + late num _bottom; + + final Path _fillPath = Path(); + Path _strokePath = Path(); + + final List _drawIndexes = []; + final List _highPoints = []; + final List _lowPoints = []; + final List _oldHighPoints = []; + final List _oldLowPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _drawIndexes.clear(); + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + return; + } + + final int oldPointsLength = _oldHighPoints.length; + final int newPointsLength = _highPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _bottom, + )!; + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _bottom, + )!; + } + _oldHighPoints.addAll(_highPoints.sublist(oldPointsLength)); + _oldLowPoints.addAll(_lowPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + _oldHighPoints[i] = + _oldHighPoints[i].lerp( + _highPoints[i], + segmentAnimationFactor, + _bottom, + )!; + _oldLowPoints[i] = + _oldLowPoints[i].lerp( + _lowPoints[i], + segmentAnimationFactor, + _bottom, + )!; + } + _oldHighPoints.removeRange(newPointsLength, oldPointsLength); + _oldLowPoints.removeRange(newPointsLength, oldPointsLength); + } + } else { + _oldHighPoints.clear(); + _oldLowPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + + _fillPath.reset(); + _strokePath.reset(); + if (_xValues.isEmpty || _yValues.isEmpty) { + return; + } + + _calculatePoints(_xValues, _yValues); + _createFillPath(_fillPath, _highPoints, _lowPoints); + } + + void _calculatePoints(List xValues, List yValues) { + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + final bool canDrop = series.emptyPointSettings.mode == EmptyPointMode.drop; + int length = series.dataCount; + for (int i = 0; i < length; i++) { + final num x = xValues[i]; + final num high = yValues[i]; + if (high.isNaN && canDrop) { + continue; + } + + _drawIndexes.add(i); + final Offset highPoint = Offset(transformX(x, high), transformY(x, high)); + _highPoints.add(highPoint); + + final num low = high.isNaN ? double.nan : _bottom; + final Offset lowPoint = Offset(transformX(x, low), transformY(x, low)); + _lowPoints.add(lowPoint); + + points.add(highPoint); + } + + length = _oldHighPoints.length; + if (points.length > length) { + _oldHighPoints.addAll(_highPoints.sublist(length)); + _oldLowPoints.addAll(_lowPoints.sublist(length)); + } + } + + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + final List lerpHighPoints = _lerpPoints( + _oldHighPoints, + _highPoints, + ); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + case BorderDrawMode.excludeBottom: + _createExcludeBottomStrokePath( + _strokePath, + lerpHighPoints, + lerpLowPoints, + ); + break; + } + } + + List _lerpPoints(List oldPoints, List newPoints) { + final List lerpPoints = []; + final int oldPointsLength = oldPoints.length; + final int newPointsLength = newPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, _bottom)!, + ); + } + } else if (oldPointsLength < newPointsLength) { + for (int i = 0; i < oldPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, _bottom)!, + ); + } + lerpPoints.addAll(newPoints.sublist(oldPointsLength)); + } else { + for (int i = 0; i < newPointsLength; i++) { + lerpPoints.add( + oldPoints[i].lerp(newPoints[i], animationFactor, _bottom)!, + ); + } + } + + return lerpPoints; + } + + Path _createFillPath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (i == 0) { + if (lowPoint.isNaN) { + _createFillPath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + path.lineTo(highPoint.dx, highPoint.dy); + } + } else { + if (highPoint.isNaN) { + for (int j = i - 1; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + } + _createFillPath(source, highPoints.sublist(i), lowPoints.sublist(i)); + break; + } else { + path!.lineTo(highPoint.dx, highPoints[i - 1].dy); + path.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + for (int j = i; j >= 0; j--) { + final Offset lowPoint = lowPoints[j]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createTopStrokePath(Path source, List highPoints) { + Path? path; + final int length = highPoints.length; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + if (highPoint.isNaN) { + _createTopStrokePath(source, highPoints.sublist(i + 1)); + break; + } else { + if (i == 0) { + path = Path(); + path.moveTo(highPoint.dx, highPoint.dy); + } else { + path!.lineTo(highPoint.dx, highPoints[i - 1].dy); + path.lineTo(highPoint.dx, highPoint.dy); + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + Path _createExcludeBottomStrokePath( + Path source, + List highPoints, + List lowPoints, + ) { + Path? path; + final int length = highPoints.length; + final int lastIndex = length - 1; + for (int i = 0; i < length; i++) { + final Offset highPoint = highPoints[i]; + final Offset lowPoint = lowPoints[i]; + if (i == 0) { + if (lowPoint.isNaN) { + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i + 1), + lowPoints.sublist(i + 1), + ); + break; + } else { + path = Path(); + path.moveTo(lowPoint.dx, lowPoint.dy); + path.lineTo(highPoint.dx, highPoint.dy); + } + } else { + if (highPoint.isNaN) { + final Offset lowPoint = lowPoints[i - 1]; + path!.lineTo(lowPoint.dx, lowPoint.dy); + _createExcludeBottomStrokePath( + source, + highPoints.sublist(i), + lowPoints.sublist(i), + ); + break; + } else { + path!.lineTo(highPoint.dx, highPoints[i - 1].dy); + path.lineTo(highPoint.dx, highPoint.dy); + if (i == lastIndex) { + final Offset lowPoint = lowPoints[i]; + path.lineTo(lowPoint.dx, lowPoint.dy); + } + } + } + } + + if (path != null) { + source.addPath(path, Offset.zero); + } + return source; + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + pointDistance = series.markerSettings.width / 2; + pointIndex ??= _findNearestChartPointIndex(points, position!); + if (pointIndex != -1) { + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + final num x = chartPoint.xValue!; + final num y = chartPoint.y!; + final double dx = series.pointToPixelX(x, y); + final double dy = series.pointToPixelY(x, y); + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = Offset(dx, dy); + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final int drawPointIndex = drawIndex(pointIndex, _drawIndexes); + if (drawPointIndex == -1) { + return null; + } + + final Offset preferredPos = points[drawPointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[drawPointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); + return ChartTrackballInfo( + position: preferredPos, + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + int _findNearestChartPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _computeAreaPath(); + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent) { + canvas.drawPath(_fillPath, paint); + } + + paint = getStrokePaint(); + if (paint.color != Colors.transparent && paint.strokeWidth > 0) { + drawDashes(canvas, series.dashArray, paint, path: _strokePath); + } + } + + @override + void dispose() { + _fillPath.reset(); + _strokePath.reset(); + + points.clear(); + _drawIndexes.clear(); + _highPoints.clear(); + _lowPoints.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart new file mode 100644 index 000000000..37f85389b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart @@ -0,0 +1,378 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/core_tooltip.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the step line series. +/// +/// A step line chart is a line chart in which points are connected +/// by horizontal and vertical line segments, looking like steps of a staircase. +/// +/// To render a step line chart, create an instance of [StepLineSeries], and +/// add it to the series collection property of [SfCartesianChart]. +/// Provides option to customize the [color], [opacity], [width] of +/// the step line segments. +@immutable +class StepLineSeries extends XyDataSeries { + /// Creating an argument constructor of StepLineSeries class. + const StepLineSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.xAxisName, + super.yAxisName, + super.name, + super.color, + double width = 2, + super.markerSettings, + super.trendlines, + super.emptyPointSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.enableTooltip = true, + super.enableTrackball = true, + super.dashArray, + super.animationDuration, + super.selectionBehavior, + super.sortingOrder, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.animationDelay, + super.opacity, + }) : super(borderWidth: width); + + /// Create the stacked area series renderer. + @override + StepLineSeriesRenderer createRenderer() { + StepLineSeriesRenderer stepLineSeriesRenderer; + if (onCreateRenderer != null) { + stepLineSeriesRenderer = + onCreateRenderer!(this) as StepLineSeriesRenderer; + return stepLineSeriesRenderer; + } + return StepLineSeriesRenderer(); + } +} + +/// Creates series renderer for step line series. +class StepLineSeriesRenderer extends XyDataSeriesRenderer + with LineSeriesMixin { + /// Calling the default constructor of StepLineSeriesRenderer class. + StepLineSeriesRenderer(); + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + + final num x1 = xValues[index]; + final num y1 = yValues[index]; + num x2 = double.nan; + num y2 = double.nan; + + final int nextIndex = nextIndexConsideringEmptyPointMode( + index, + emptyPointSettings.mode, + yValues, + dataCount, + ); + if (nextIndex != -1) { + x2 = xValues[nextIndex]; + y2 = yValues[nextIndex]; + } + + segment as StepLineSegment + ..series = this + .._x1 = x1 + .._y1 = y1 + .._x2 = x2 + .._y2 = y2 + ..isEmpty = isEmpty(index); + } + + /// Creates a segment for a data point in the series. + @override + StepLineSegment createSegment() => StepLineSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => + dashArray != null && !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.stepLineSeriesWithDashArray + : ShapeMarkerType.stepLineSeries; + + @override + double legendIconBorderWidth() { + return 1.5; + } + + /// Changes the series color and border width. + @override + void customizeSegment(ChartSegment segment) { + updateSegmentColor(segment, color, borderWidth, isLineType: true); + updateSegmentGradient(segment); + } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + final Rect clip = clipRect( + paintBounds, + animationFactor, + isInversed: xAxis!.isInversed, + isTransposed: isTransposed, + ); + context.canvas.clipRect(clip); + paintSegments(context, offset); + context.canvas.restore(); + paintMarkers(context, offset); + paintDataLabels(context, offset); + paintTrendline(context, offset); + } +} + +/// Creates the segments for a step line series. +/// +/// Generates the step line series points and has the [calculateSegmentPoints] +/// method overrides to customize the step line segment point calculation. +/// +/// Gets the path and color from the `series`. +class StepLineSegment extends ChartSegment { + late StepLineSeriesRenderer series; + late num _x1; + late num _x2; + late num _y1; + late num _y2; + + final List _oldPoints = []; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldPoints.clear(); + return; + } + + if (series.animationDuration > 0) { + if (points.isEmpty) { + _oldPoints.clear(); + return; + } + + final int newPointsLength = points.length; + final int oldPointsLength = _oldPoints.length; + if (oldPointsLength == newPointsLength) { + for (int i = 0; i < newPointsLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + } else { + final int minLength = min(oldPointsLength, newPointsLength); + for (int i = 0; i < minLength; i++) { + _oldPoints[i] = + Offset.lerp(_oldPoints[i], points[i], segmentAnimationFactor)!; + } + + if (newPointsLength > oldPointsLength) { + _oldPoints.addAll(points.sublist(oldPointsLength)); + } else { + _oldPoints.removeRange(minLength, oldPointsLength); + } + } + } else { + _oldPoints.clear(); + } + } + + @override + void transformValues() { + points.clear(); + + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + + if (!_x1.isNaN && !_y1.isNaN) { + points.add(Offset(transformX(_x1, _y1), transformY(_x1, _y1))); + } + + if (!_x2.isNaN && !_y1.isNaN) { + points.add(Offset(transformX(_x2, _y1), transformY(_x2, _y1))); + } + + if (!_x2.isNaN && !_y2.isNaN) { + points.add(Offset(transformX(_x2, _y2), transformY(_x2, _y2))); + } + + if (points.length > _oldPoints.length) { + _oldPoints.addAll(points.sublist(_oldPoints.length)); + } + } + + @override + bool contains(Offset position) { + final MarkerSettings marker = series.markerSettings; + final int length = points.length; + for (int i = 0; i < length; i++) { + if (tooltipTouchBounds( + points[i], + marker.width, + marker.height, + ).contains(position)) { + return true; + } + } + return false; + } + + int _nearestPointIndex(List points, Offset position) { + for (int i = 0; i < points.length; i++) { + if ((points[i] - position).distance <= pointDistance) { + return i; + } + } + return -1; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + return CartesianChartPoint( + x: series.xRawValues[pointIndex], + xValue: series.xValues[pointIndex], + y: series.yValues[pointIndex], + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (points.isEmpty) { + return null; + } + + final List linePoints = [points.first, points.last]; + pointDistance = series.markerSettings.width / 2; + final int nearestPointIndex = + position == null ? 0 : _nearestPointIndex(linePoints, position); + if (nearestPointIndex != -1) { + pointIndex ??= + (position == null || nearestPointIndex == 0 + ? currentSegmentIndex + : currentSegmentIndex + 1); + CartesianChartPoint chartPoint = _chartPoint(pointIndex); + List markerColors = [fillPaint.color]; + if (chartPoint.y != null && chartPoint.y!.isNaN) { + pointIndex += 1; + chartPoint = _chartPoint(pointIndex); + markerColors = [series.segments[pointIndex].fillPaint.color]; + } + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = linePoints[nearestPointIndex]; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: markerColors, + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; + } + + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + + @override + Paint getFillPaint() => strokePaint; + + /// Gets the stroke color of the series. + @override + Paint getStrokePaint() => strokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (points.isEmpty || points.length != 3) { + return; + } + + final Paint paint = getStrokePaint(); + if (paint.color == Colors.transparent || paint.strokeWidth < 0) { + return; + } + + final Offset start = + Offset.lerp(_oldPoints[0], points[0], animationFactor)!; + final Offset mid = Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + final Offset end = Offset.lerp(_oldPoints[2], points[2], animationFactor)!; + drawDashes(canvas, series.dashArray, paint, start: start, end: mid); + drawDashes(canvas, series.dashArray, paint, start: mid, end: end); + } + + @override + void dispose() { + _oldPoints.clear(); + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart new file mode 100644 index 000000000..2abee1b9a --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart @@ -0,0 +1,1014 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/chart_point.dart'; +import '../common/connector_line.dart'; +import '../common/core_tooltip.dart'; +import '../common/element_widget.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../utils/constants.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'chart_series.dart'; + +/// Renders the waterfall series. +/// +/// To render a waterfall chart, create an instance of [WaterfallSeries] and +/// add to the series collection property of [SfCartesianChart]. +/// +/// [WaterfallSeries] is similar to range column series, +/// in range column high and low value should be there, but in waterfall +/// we have find the endValue and originValue of each data point. +@immutable +class WaterfallSeries extends XyDataSeries { + /// Creating an argument constructor of WaterfallSeries class. + const WaterfallSeries({ + super.key, + super.onCreateRenderer, + super.dataSource, + required super.xValueMapper, + required super.yValueMapper, + this.intermediateSumPredicate, + this.totalSumPredicate, + this.negativePointsColor, + this.intermediateSumColor, + this.totalSumColor, + super.sortFieldValueMapper, + super.pointColorMapper, + super.dataLabelMapper, + super.sortingOrder, + this.connectorLineSettings = const WaterfallConnectorLineSettings(), + super.xAxisName, + super.yAxisName, + super.name, + super.color, + this.width = 0.7, + this.spacing = 0.0, + super.markerSettings, + super.dataLabelSettings, + super.initialIsVisible, + super.gradient, + super.borderGradient, + this.borderRadius = BorderRadius.zero, + super.enableTooltip = true, + super.enableTrackball = true, + super.animationDuration, + this.borderColor = Colors.transparent, + super.trendlines, + super.borderWidth, + super.selectionBehavior, + super.isVisibleInLegend, + super.legendIconType, + super.legendItemText, + super.opacity, + super.animationDelay, + super.dashArray, + super.onRendererCreated, + super.onPointTap, + super.onPointDoubleTap, + super.onPointLongPress, + super.onCreateShader, + super.initialSelectedDataIndexes, + }); + + /// Color of the negative data points in the series. + /// + /// If no color is specified, then the negative data points will be rendered + /// with the series default color. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// negativePointsColor: Colors.red, + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? negativePointsColor; + + /// Color of the intermediate sum points in the series. + /// + /// If no color is specified, then the intermediate sum points will be + /// rendered with the series default color. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// intermediateSumColor: Colors.red, + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? intermediateSumColor; + + /// Color of the total sum points in the series. + /// + /// If no color is specified, then the total sum points will be rendered + /// with the series default color. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// totalSumColor: Colors.red, + /// ), + /// ], + /// ); + /// } + /// ``` + final Color? totalSumColor; + + /// Options to customize the waterfall chart connector line. + /// + /// Data points in waterfall chart are connected using the connector line. + /// Provides the options to change the width, color and dash array of the + /// connector line to customize the appearance. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// connectorLineSettings: WaterfallConnectorLineSettings( + /// width: 2, + /// color: Colors.black, + /// dashArray: [2,3] + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final WaterfallConnectorLineSettings connectorLineSettings; + + /// Spacing between the data points in waterfall chart. + /// + /// The value ranges from 0 to 1, where 1 represents 100% and 0 represents + /// 0% of the available space. + /// + /// Spacing affects the width of the bars in waterfall. For example, + /// setting 20% spacing and 100% width + /// renders the bars with 80% of total width. + /// + /// Also refer [CartesianSeries.width]. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// spacing: 0.5, + /// ), + /// ], + /// ); + /// } + /// ``` + final double spacing; + + /// Customizes the corners of the waterfall. + /// + /// Each corner can be customized with a desired value or with a single value. + /// + /// Defaults to `Radius.zero`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// borderRadius: BorderRadius.circular(5), + /// ), + /// ], + /// ); + /// } + /// ``` + final BorderRadius borderRadius; + + /// A boolean value, based on which the data point will be considered as + /// intermediate sum or not. + /// + /// If this has true value, then that point will be considered as an + /// intermediate sum. Else if it has false, then it will be considered as a + /// normal data point in chart. + /// + /// This callback will be called for all the data points to check + /// if the data is intermediate sum. + /// + /// _Note:_ This is applicable only for waterfall chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// dataSource: [ + /// SalesData(2, 24, true), + /// SalesData(3, 22, false), + /// SalesData(4, 31, true), + /// ], + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// intermediateSumPredicate: + /// (SalesData data, _) => data.isIntermediate, + /// ), + /// ], + /// ); + /// } + /// class SalesData { + /// SalesData(this.x, this.y, this.isIntermediate); + /// final num x; + /// final num y; + /// final bool isIntermediate; + /// } + /// ``` + final ChartValueMapper? intermediateSumPredicate; + + /// A boolean value, based on which the data point will be considered as + /// total sum or not. + /// + /// If this has true value, then that point will be considered as a total sum. + /// Else if it has false, then it will be considered as a + /// normal data point in chart. + /// + /// This callback will be called for all the data points to check + /// if the data is total sum. + /// + /// _Note:_ This is applicable only for waterfall chart. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// dataSource: [ + /// SalesData(2, 24, true), + /// SalesData(3, 22, true), + /// SalesData(4, 31, false), + /// ], + /// xValueMapper: (SalesData sales, _) => sales.x, + /// yValueMapper: (SalesData sales, _) => sales.y, + /// totalSumPredicate: (SalesData data, _) => data.isTotalSum, + /// ), + /// ], + /// ); + /// } + /// class SalesData { + /// SalesData(this.x, this.y, this.isTotalSum); + /// final num x; + /// final num y; + /// final bool isTotalSum; + /// } + /// ``` + final ChartValueMapper? totalSumPredicate; + + final double width; + + final Color borderColor; + + /// Create the waterfall series renderer. + @override + WaterfallSeriesRenderer createRenderer() { + WaterfallSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(this) as WaterfallSeriesRenderer; + return seriesRenderer; + } + return WaterfallSeriesRenderer(); + } + + @override + WaterfallSeriesRenderer createRenderObject(BuildContext context) { + final WaterfallSeriesRenderer renderer = + super.createRenderObject(context) as WaterfallSeriesRenderer; + renderer + ..negativePointsColor = negativePointsColor + ..intermediateSumColor = intermediateSumColor + ..totalSumColor = totalSumColor + ..connectorLineSettings = connectorLineSettings + ..width = width + ..spacing = spacing + ..borderRadius = borderRadius + ..borderColor = borderColor + ..intermediateSumPredicate = intermediateSumPredicate + ..totalSumPredicate = totalSumPredicate; + return renderer; + } + + @override + void updateRenderObject( + BuildContext context, + WaterfallSeriesRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..negativePointsColor = negativePointsColor + ..intermediateSumColor = intermediateSumColor + ..totalSumColor = totalSumColor + ..connectorLineSettings = connectorLineSettings + ..width = width + ..spacing = spacing + ..borderRadius = borderRadius + ..borderColor = borderColor + ..intermediateSumPredicate = intermediateSumPredicate + ..totalSumPredicate = totalSumPredicate; + } +} + +/// Options to customize the waterfall chart connector line. +/// +/// Data points in waterfall chart are connected using the connector line and +/// this class hold the properties to customize it. +/// +/// It provides the options to change the width, color and dash array of the +/// connector line to customize the appearance. +class WaterfallConnectorLineSettings extends ConnectorLineSettings { + /// Creating an argument constructor of WaterfallConnectorLineSettings class. + const WaterfallConnectorLineSettings({ + double? width, + Color? color, + this.dashArray = const [0, 0], + }) : super(color: color, width: width ?? 1); + + /// Dashes of the waterfall chart connector line. + /// + /// Any number of values can be provided in the list. Odd values are + /// considered as rendering size and even values are considered as gap. + /// + /// Defaults to `null.` + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// WaterfallSeries( + /// connectorLineSettings: WaterfallConnectorLineSettings( + /// dashArray: [2,3] + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + final List? dashArray; +} + +/// Creates series renderer for waterfall series. +class WaterfallSeriesRenderer extends XyDataSeriesRenderer + with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + /// Calling the default constructor of WaterfallSeriesRenderer class. + WaterfallSeriesRenderer(); + + Color? get negativePointsColor => _negativePointsColor; + Color? _negativePointsColor; + set negativePointsColor(Color? value) { + if (_negativePointsColor != value) { + _negativePointsColor = value; + markNeedsSegmentsPaint(); + } + } + + Color? get intermediateSumColor => _intermediateSumColor; + Color? _intermediateSumColor; + set intermediateSumColor(Color? value) { + if (_intermediateSumColor != value) { + _intermediateSumColor = value; + markNeedsSegmentsPaint(); + } + } + + Color? get totalSumColor => _totalSumColor; + Color? _totalSumColor; + set totalSumColor(Color? value) { + if (_totalSumColor != value) { + _totalSumColor = value; + markNeedsSegmentsPaint(); + } + } + + WaterfallConnectorLineSettings get connectorLineSettings => + _connectorLineSettings; + WaterfallConnectorLineSettings _connectorLineSettings = + const WaterfallConnectorLineSettings(); + set connectorLineSettings(WaterfallConnectorLineSettings value) { + if (_connectorLineSettings != value) { + _connectorLineSettings = value; + markNeedsLayout(); + } + } + + BorderRadius get borderRadius => _borderRadius; + BorderRadius _borderRadius = BorderRadius.zero; + set borderRadius(BorderRadius value) { + if (_borderRadius != value) { + _borderRadius = value; + markNeedsLayout(); + } + } + + ChartValueMapper? get intermediateSumPredicate => + _intermediateSumPredicate; + ChartValueMapper? _intermediateSumPredicate; + set intermediateSumPredicate(ChartValueMapper? value) { + if (_intermediateSumPredicate != value) { + _intermediateSumPredicate = value; + markNeedsLayout(); + } + } + + ChartValueMapper? get totalSumPredicate => _totalSumPredicate; + ChartValueMapper? _totalSumPredicate; + set totalSumPredicate(ChartValueMapper? value) { + if (_totalSumPredicate != value) { + _totalSumPredicate = value; + markNeedsLayout(); + } + } + + Color get borderColor => _borderColor; + Color _borderColor = Colors.transparent; + set borderColor(Color value) { + if (_borderColor != value) { + _borderColor = value; + markNeedsSegmentsPaint(); + } + } + + final List _intermediateSumValues = []; + final List _totalSumValues = []; + final List _highValues = []; + final List _lowValues = []; + final List _waterfallYValues = []; + + bool? _intermediateSumPredicateMapper(T data, int index) { + return intermediateSumPredicate!(data, index); + } + + bool? _totalSumPredicateMapper(T data, int index) { + return totalSumPredicate!(data, index); + } + + bool _defaultSumPredicate(T data, int index) => false; + + @override + void populateDataSource([ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.populateDataSource( + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + _calculateWaterfallValues(); + populateChartPoints(); + } + + @override + void populateChartPoints({ + List? positions, + List>? yLists, + }) { + if (yLists == null) { + yLists = >[_highValues]; + positions = [ChartDataPointType.y]; + } else { + yLists.add(_highValues); + positions!.add(ChartDataPointType.y); + } + + super.populateChartPoints(positions: positions, yLists: yLists); + } + + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); + _calculateWaterfallValues(); + } + + void _resetDataSourceHolders() { + _intermediateSumValues.clear(); + _totalSumValues.clear(); + _highValues.clear(); + _lowValues.clear(); + _waterfallYValues.clear(); + } + + void _calculateWaterfallValues() { + _resetDataSourceHolders(); + + num topValue = 0; + num bottomValue = 0; + num intermediateOrigin = 0; + num prevTop = 0; + num minY = 0; + num maxY = 0; + bool isIntermediateSum = false; + bool isTotalSum = false; + final bool? Function(T data, int index) intermediateSum = + intermediateSumPredicate != null + ? _intermediateSumPredicateMapper + : _defaultSumPredicate; + final bool? Function(T data, int index) totalSum = + totalSumPredicate != null + ? _totalSumPredicateMapper + : _defaultSumPredicate; + + for (int i = 0; i < dataCount; i++) { + final T current = dataSource![i]; + isIntermediateSum = intermediateSum(current, i) ?? false; + isTotalSum = totalSum(current, i) ?? false; + _intermediateSumValues.add(isIntermediateSum); + _totalSumValues.add(isTotalSum); + if (!(isIntermediateSum || isTotalSum) && !yValues[i].isNaN) { + topValue += yValues[i]; + } + + bottomValue = isIntermediateSum ? intermediateOrigin : prevTop; + bottomValue = isTotalSum ? 0 : bottomValue; + minY = bottomValue < minY ? bottomValue : minY; + maxY = topValue > maxY ? topValue : maxY; + _highValues.add(topValue); + _lowValues.add(bottomValue); + _waterfallYValues.add(topValue - bottomValue); + if (isIntermediateSum) { + intermediateOrigin = topValue; + } + prevTop = topValue; + } + + yMin = minY.isNaN ? yMin : minY; + yMax = maxY.isNaN ? yMax : maxY; + } + + @override + num trackballYValue(int index) => _highValues[index]; + + @override + void setData(int index, ChartSegment segment) { + super.setData(index, segment); + segment as WaterfallSegment + ..series = this + ..x = xValues[index] + ..top = _highValues[index] + ..bottom = _lowValues[index] + ..isTotalSum = _totalSumValues[index] + ..isIntermediateSum = _intermediateSumValues[index]; + } + + @override + void performLayout() { + super.performLayout(); + + if (markerContainer != null) { + markerContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[_highValues] + ..animation = markerAnimation + ..layout(constraints); + } + + if (dataLabelContainer != null) { + dataLabelContainer! + ..renderer = this + ..xRawValues = xRawValues + ..xValues = xValues + ..yLists = >[_highValues] + ..stackedYValues = _waterfallYValues + ..sortedIndexes = sortedIndexes + ..animation = dataLabelAnimation + ..layout(constraints); + + if (dataLabelSettings.isVisible && + dataLabelSettings.labelIntersectAction != LabelIntersectAction.none) { + dataLabelContainer!.handleDataLabelCollision(this); + } + } + } + + /// Creates a segment for a data point in the series. + @override + WaterfallSegment createSegment() => WaterfallSegment(); + + @override + ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.waterfallSeries; + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final WaterfallSegment waterfallSegment = + segment as WaterfallSegment; + final int index = waterfallSegment.currentSegmentIndex; + Color? color; + if (_intermediateSumValues[index] && intermediateSumColor != null) { + color = intermediateSumColor; + } else if (_totalSumValues[index] && totalSumColor != null) { + color = totalSumColor; + } else if (yValues[index] < 0 && negativePointsColor != null) { + color = negativePointsColor; + } + + updateSegmentColor( + waterfallSegment, + borderColor, + borderWidth, + fillColor: color, + ); + updateSegmentGradient( + waterfallSegment, + gradientBounds: waterfallSegment.segmentRect?.outerRect, + gradient: gradient, + borderGradient: borderGradient, + ); + + segment.connectorLineStrokePaint + ..color = + (connectorLineSettings.color ?? + chartThemeData!.waterfallConnectorLineColor)! + ..strokeWidth = connectorLineSettings.width; + } + + @override + Offset dataLabelPosition( + ChartElementParentData current, + ChartDataLabelAlignment alignment, + Size size, + ) { + final num x = current.x! + (sbsInfo.maximum + sbsInfo.minimum) / 2; + num y = current.y!; + final int dataPointIndex = current.dataPointIndex; + final num bottom = _lowValues[dataPointIndex]; + final bool isNegative = yValues[dataPointIndex] < 0; + + if (alignment == ChartDataLabelAlignment.bottom) { + y = bottom; + } else if (alignment == ChartDataLabelAlignment.middle) { + y = (y + bottom) / 2; + } + final EdgeInsets margin = dataLabelSettings.margin; + double translationX = 0.0; + double translationY = 0.0; + switch (alignment) { + case ChartDataLabelAlignment.auto: + case ChartDataLabelAlignment.outer: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? 0 + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.bottom: + if (isTransposed) { + translationX = + isNegative + ? -(dataLabelPadding + size.width + margin.horizontal) + : dataLabelPadding; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? dataLabelPadding + : -(dataLabelPadding + size.height + margin.vertical); + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.top: + if (isTransposed) { + translationX = + isNegative + ? 0 + : -(dataLabelPadding + size.width + margin.horizontal); + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = + isNegative + ? -(dataLabelPadding + size.height + margin.vertical) + : dataLabelPadding; + } + return translateTransform(x, y, translationX, translationY); + + case ChartDataLabelAlignment.middle: + final WaterfallSegment segment = + segments[dataPointIndex] as WaterfallSegment; + final Offset center = segment.segmentRect!.center; + if (isTransposed) { + translationX = -margin.left - size.width / 2; + translationY = -margin.top; + } else { + translationX = -margin.left; + translationY = -margin.top - size.height / 2; + } + return center.translate(translationX, translationY); + } + } + + @override + void dispose() { + _resetDataSourceHolders(); + super.dispose(); + } +} + +/// Creates the segments for waterfall series. +/// +/// Generates the waterfall series points and has the [calculateSegmentPoints] +/// method overridden to customize the waterfall segment point calculation. +/// +/// Gets the path and color from the `series`. +class WaterfallSegment extends ChartSegment { + late WaterfallSeriesRenderer series; + final Paint connectorLineStrokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + late num x; + late num top; + late num bottom; + + late bool isTotalSum; + late bool isIntermediateSum; + + RRect? _oldSegmentRect; + RRect? segmentRect; + + @override + void copyOldSegmentValues( + double seriesAnimationFactor, + double segmentAnimationFactor, + ) { + if (series.animationType == AnimationType.loading) { + points.clear(); + _oldSegmentRect = null; + segmentRect = null; + return; + } + + if (series.animationDuration > 0) { + _oldSegmentRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + segmentAnimationFactor, + ); + } else { + _oldSegmentRect = segmentRect; + } + } + + @override + void transformValues() { + if (x.isNaN || top.isNaN || bottom.isNaN) { + segmentRect = null; + _oldSegmentRect = null; + points.clear(); + return; + } + + points.clear(); + final PointToPixelCallback transformX = series.pointToPixelX; + final PointToPixelCallback transformY = series.pointToPixelY; + final num left = x + series.sbsInfo.minimum; + final num sbsMaximum = series.sbsInfo.maximum; + final num right = x + sbsMaximum; + + final double centerY = (bottom + top) / 2; + final double x1 = transformX(left, top); + final double y1 = transformY(left, top); + final double x2 = transformX(right, bottom); + final double y2 = transformY(right, bottom); + + final BorderRadius borderRadius = series._borderRadius; + segmentRect = toRRect(x1, y1, x2, y2, borderRadius); + _oldSegmentRect ??= toRRect( + transformX(left, centerY), + transformY(left, centerY), + transformX(right, centerY), + transformY(right, centerY), + borderRadius, + ); + + if (currentSegmentIndex > 0) { + final WaterfallSegment oldSegment = + series.segments[currentSegmentIndex - 1] as WaterfallSegment; + final num oldSegmentRight = oldSegment.x + sbsMaximum; + final num oldSegmentTop = oldSegment.top; + points.add( + Offset( + transformX(oldSegmentRight, oldSegmentTop), + transformY(oldSegmentRight, oldSegmentTop), + ), + ); + + if (isTotalSum || isIntermediateSum) { + points.add(Offset(x1, y1)); + } else { + series.isTransposed + ? points.add(Offset(x2, y1)) + : points.add(Offset(x1, y2)); + } + } + } + + @override + bool contains(Offset position) { + return segmentRect != null && segmentRect!.contains(position); + } + + CartesianChartPoint _chartPoint() { + return CartesianChartPoint( + x: series.xRawValues[currentSegmentIndex], + xValue: series.xValues[currentSegmentIndex], + y: top, + ); + } + + @override + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + if (segmentRect != null) { + pointIndex ??= currentSegmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(); + final TooltipPosition? tooltipPosition = + series.parent?.tooltipBehavior?.tooltipPosition; + final ChartMarker marker = series.markerAt(pointIndex); + final double markerHeight = + series.markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = + tooltipPosition == TooltipPosition.pointer + ? position ?? segmentRect!.outerRect.topCenter + : segmentRect!.outerRect.topCenter; + return ChartTooltipInfo( + primaryPosition: series.localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: series.localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series.tooltipText(chartPoint), + header: + series.parent!.tooltipBehavior!.shared + ? series.tooltipHeaderText(chartPoint) + : series.name, + data: series.dataSource![pointIndex], + point: chartPoint, + series: series.widget, + renderer: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + markerColors: [fillPaint.color], + markerType: marker.type, + ); + } + return null; + } + + @override + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: Offset( + series.pointToPixelX(x, top), + series.pointToPixelY(x, top), + ), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; + } + + /// Gets the color of the series. + @override + Paint getFillPaint() => fillPaint; + + /// Gets the border color of the series. + @override + Paint getStrokePaint() => strokePaint; + + Paint getConnectorLineStrokePaint() => connectorLineStrokePaint; + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + if (segmentRect == null) { + return; + } + + final RRect? paintRRect = RRect.lerp( + _oldSegmentRect, + segmentRect, + animationFactor, + ); + if (paintRRect == null) { + return; + } + + Paint paint = getFillPaint(); + if (paint.color != Colors.transparent && !paintRRect.isEmpty) { + canvas.drawRRect(paintRRect, paint); + } + + paint = getStrokePaint(); + final double strokeWidth = paint.strokeWidth; + if (paint.color != Colors.transparent && strokeWidth > 0) { + final Path strokePath = strokePathFromRRect(paintRRect, strokeWidth); + drawDashes(canvas, series.dashArray, paint, path: strokePath); + } + + final Paint connectorLinePaint = getConnectorLineStrokePaint(); + if (animationFactor == 1 && + points.isNotEmpty && + connectorLinePaint.strokeWidth > 0) { + drawDashes( + canvas, + series.connectorLineSettings.dashArray, + connectorLinePaint, + start: points[0], + end: points[1], + ); + } + } + + @override + void dispose() { + segmentRect = null; + points.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/theme.dart b/packages/syncfusion_flutter_charts/lib/src/charts/theme.dart new file mode 100644 index 000000000..5a5c0bd36 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/theme.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// Holds the value of [ChartThemeData] color properties +class ChartThemeData extends SfChartThemeData { + /// Creating an argument constructor of ChartThemeData class. + ChartThemeData(this.context); + + /// Specifies the build context of the chart widgets. + final BuildContext context; + + /// Obtain the color scheme from the current SfTheme based on the given BuildContext. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + /// Specifies the material app theme data based on the brightness. + late final TextTheme textTheme = Theme.of(context).textTheme; + + // TODO(Lavanya): Facing issue while using colorScheme.transparent. + // Because we are chekcing not equals to Colors.transparent for gets + // data label surface color. + @override + Color? get backgroundColor => Colors.transparent; + + List get palette => colorScheme.palettes; + + @override + Color? get axisLabelColor => colorScheme.onSurfaceVariant[104]; + + @override + Color? get axisTitleColor => colorScheme.onSurfaceVariant[66]; + + @override + Color? get axisLineColor => colorScheme.outlineVariant[181]; + + @override + Color? get majorGridLineColor => colorScheme.surfaceVariant[219]; + + @override + Color? get minorGridLineColor => colorScheme.surfaceVariant[219]; + + @override + Color? get majorTickLineColor => colorScheme.outlineVariant[182]; + + @override + Color? get minorTickLineColor => colorScheme.outlineVariant[182]; + + @override + Color? get titleTextColor => colorScheme.onSurfaceVariant[66]; + + @override + Color? get titleBackgroundColor => Colors.transparent; + + @override + Color? get legendTextColor => colorScheme.onSurfaceVariant[53]; + + @override + Color? get legendBackgroundColor => Colors.transparent; + + @override + Color? get legendTitleColor => colorScheme.onSurfaceVariant[66]; + + Color get markerFillColor => + colorScheme.brightness == Brightness.light ? Colors.white : Colors.black; + + Color get dataLabelBackgroundColor => + colorScheme.brightness == Brightness.light ? Colors.white : Colors.black; + + @override + Color? get plotAreaBackgroundColor => Colors.transparent; + + @override + Color? get plotAreaBorderColor => colorScheme.surfaceVariant[219]; + + @override + Color? get crosshairLineColor => colorScheme.onSurfaceVariant[79]; + + @override + Color? get crosshairBackgroundColor => colorScheme.inverseSurface[79]; + + @override + Color? get crosshairLabelColor => colorScheme.onInverseSurface[256]; + + @override + Color? get tooltipColor => colorScheme.inverseSurface[258]; + + @override + Color? get tooltipLabelColor => colorScheme.onInverseSurface[256]; + + @override + Color? get tooltipSeparatorColor => colorScheme.onInverseSurface[150]; + + @override + Color? get selectionRectColor => colorScheme.primary[27]; + + @override + Color? get selectionRectBorderColor => colorScheme.primary[28]; + + @override + Color? get selectionTooltipConnectorLineColor => + colorScheme.onSurfaceVariant[80]; + + @override + Color? get waterfallConnectorLineColor => colorScheme.onSurfaceVariant[255]; + + @override + TextStyle? get titleTextStyle => textTheme.bodyMedium?.copyWith(fontSize: 15); + + @override + TextStyle? get axisTitleTextStyle => + textTheme.bodyMedium?.copyWith(fontSize: 15); + + @override + TextStyle? get axisLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get axisMultiLevelLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get plotBandLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get legendTitleTextStyle => textTheme.bodySmall; + + @override + TextStyle? get legendTextStyle => textTheme.bodySmall?.copyWith(fontSize: 13); + + @override + TextStyle? get dataLabelTextStyle => textTheme.bodySmall; + + @override + TextStyle? get tooltipTextStyle => textTheme.bodySmall; + + @override + TextStyle? get trackballTextStyle => textTheme.bodySmall; + + @override + TextStyle? get crosshairTextStyle => textTheme.bodySmall; + + @override + TextStyle? get selectionZoomingTooltipTextStyle => textTheme.bodySmall; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart b/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart new file mode 100644 index 000000000..b7f0e51ee --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart @@ -0,0 +1,2667 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart'; +import '../common/core_tooltip.dart'; +import '../common/legend.dart'; +import '../common/marker.dart'; +import '../interactions/tooltip.dart'; +import '../series/chart_series.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; + +/// Renders the chart trendline. +/// +/// A trendline is a straight line that connects two or more price points +/// and then extends into the future to act as a line of support. +/// Trendlines provide support for forward and backward forecasting. +/// +/// Provides option to customize the trendline types, [width], [forwardForecast] +/// and [backwardForecast]. +class Trendline { + /// Creating an argument constructor of Trendline class. + Trendline({ + this.enableTooltip = true, + this.intercept, + this.name, + this.dashArray, + 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.animationDelay = 0, + this.valueField = 'high', + this.isVisibleInLegend = true, + this.legendIconType = LegendIconType.horizontalLine, + this.markerSettings = const MarkerSettings(), + this.polynomialOrder = 2, + this.period = 2, + this.onRenderDetailsUpdate, + }); + + /// Determines the animation time of trendline. + /// + /// Defaults to `1500 `. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(animationDuration: 150) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double animationDuration; + + /// Delay duration of the trendline animation. + /// It takes a millisecond value as input. + /// By default,the trendline will get animated for the specified duration. + /// If animationDelay is specified, then the trendline will begin to animate + /// after the specified duration. + /// + /// Defaults to '0'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(animationDelay: 500) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double? animationDelay; + + /// Specifies the backward forecasting of trendlines. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(backwardForecast: 3) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double backwardForecast; + + /// Specifies the forward forecasting of trendlines. + /// + /// Defaults to `0`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(forwardForecast: 3) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double forwardForecast; + + /// Width of trendlines. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(width: 4) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double width; + + /// Opacity of the trendline. + /// + /// Defaults to `1`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(opacity: 0.85) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double opacity; + + /// Pattern of dashes and gaps used to stroke the trendline. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(dashArray: [2,3]) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final List? dashArray; + + /// Enables the tooltip for trendlines. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(enableTooltip: false) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final bool enableTooltip; + + /// Color of the trendline. + /// + /// Defaults to `Colors.blue`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(color: Colors.greenAccent) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final Color color; + + /// Provides the name for trendline. + /// + /// Defaults to `type` of the trendline chosen. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(name: 'Trendline1') + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final String? name; + + /// Specifies the intercept value of the trendlines. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(intercept: 20) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final double? intercept; + + /// Determines the visibility of the trendline. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(isVisible: false) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final bool isVisible; + + /// Specifies the type of legend icon for trendline. + /// + /// Defaults to `LegendIconType.HorizontalLine`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(legendIconType: LegendIconType.circle) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final LegendIconType legendIconType; + + /// Specifies the intercept value of the trendlines. + /// + /// Defaults to `TrendlineType.linear`. + /// + /// Also refer [TrendlineType]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(type: TrendlineType.power) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final TrendlineType type; + + /// To choose the valueField(low or high) to render the trendline. + /// + /// Defaults to `high`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(valueField: 'low') + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final String valueField; + + /// Settings to configure the marker of trendline. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline( + /// markerSettings: MarkerSettings(isVisible: true) + /// ) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final MarkerSettings markerSettings; + + /// Show/hides the legend for trendline. + /// + /// Defaults to `true`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(isVisibleInLegend: false) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final bool isVisibleInLegend; + + /// Specifies the order of the polynomial for polynomial trendline. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline( + /// type: TrendlineType.polynomial, + /// polynomialOrder: 4 + /// ) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final int polynomialOrder; + + /// Specifies the period for moving average trendline. + /// + /// Defaults to `2`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(period: 3) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final int period; + + /// Callback which gets called while rendering the trendline. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline( + /// onRenderDetailsUpdate: (TrendlineRenderParams args) { + /// print('Slope value: ' + args.slope![0].toString()); + /// print('r-squared value: ' + args.rSquaredValue.toString()); + /// print('Intercept value (x): ' + args.intercept.toString()); + /// } + /// ) + /// ] + /// ) + /// ] + /// ); + /// } + /// ``` + final ChartTrendlineRenderCallback? onRenderDetailsUpdate; + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is Trendline && + other.enableTooltip == enableTooltip && + other.intercept == intercept && + other.name == name && + other.dashArray == dashArray && + other.color == color && + other.type == type && + other.backwardForecast == backwardForecast && + other.forwardForecast == forwardForecast && + other.opacity == opacity && + other.isVisible == isVisible && + other.width == width && + other.animationDuration == animationDuration && + other.valueField == valueField && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.markerSettings == markerSettings && + other.polynomialOrder == polynomialOrder && + other.period == period; + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode { + final List values = [ + enableTooltip, + intercept, + name, + dashArray, + color, + type, + backwardForecast, + forwardForecast, + opacity, + isVisible, + width, + animationDuration, + valueField, + isVisibleInLegend, + legendIconType, + markerSettings, + polynomialOrder, + period, + ]; + return Object.hashAll(values); + } +} + +class TrendlineContainer extends StatefulWidget { + const TrendlineContainer({super.key, required this.trendlines}); + + final List trendlines; + + @override + State createState() => _TrendlineContainerState(); +} + +class _TrendlineContainerState extends State { + @override + Widget build(BuildContext context) { + return TrendlineStack( + children: List.generate(widget.trendlines.length, (int index) { + final Trendline trendline = widget.trendlines[index]; + return TrendlineWidget( + index: index, + isVisible: trendline.isVisible, + enableTooltip: trendline.enableTooltip, + name: trendline.name, + color: trendline.color, + dashArray: trendline.dashArray, + opacity: trendline.opacity, + width: trendline.width, + type: trendline.type, + valueField: trendline.valueField, + period: trendline.period, + intercept: trendline.intercept, + polynomialOrder: trendline.polynomialOrder, + backwardForecast: trendline.backwardForecast, + forwardForecast: trendline.forwardForecast, + isVisibleInLegend: trendline.isVisibleInLegend, + legendIconType: trendline.legendIconType, + markerSettings: trendline.markerSettings, + onRenderDetailsUpdate: trendline.onRenderDetailsUpdate, + ); + }), + ); + } +} + +class TrendlineStack extends MultiChildRenderObjectWidget { + const TrendlineStack({super.key, super.children}); + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderTrendlineStack(); + } +} + +class TrendlineParentData extends ContainerBoxParentData {} + +class RenderTrendlineStack extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin< + TrendlineRenderer, + TrendlineParentData + > { + CartesianSeriesRenderer? renderer; + + bool get _isTooltipEnabled => + renderer != null && + renderer!.parent != null && + renderer!.parent!.tooltipBehavior != null && + renderer!.parent!.tooltipBehavior!.enable; + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + bool isHit = false; + RenderBox? child = lastChild; + while (child != null) { + final TrendlineParentData childParentData = + child.parentData! as TrendlineParentData; + final bool isChildHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + isHit = isHit || isChildHit; + child = childParentData.previousSibling; + } + + return isHit; + } + + void handlePointerHover(Offset localPosition) { + TrendlineRenderer? child = lastChild; + while (child != null) { + final bool hasTooltip = + _isTooltipEnabled && + renderer!.parent!.tooltipBehavior!.activationMode == + ActivationMode.singleTap; + if (child.enableTooltip && hasTooltip) { + child._handleCurrentInteraction(localPosition); + } + child = childBefore(child); + } + } + + void handleDoubleTap(Offset localPosition) { + TrendlineRenderer? child = lastChild; + while (child != null) { + final bool hasTooltip = + _isTooltipEnabled && + renderer!.parent!.tooltipBehavior!.activationMode == + ActivationMode.doubleTap; + if (child.enableTooltip && hasTooltip) { + child._handleCurrentInteraction(localPosition); + } + child = childBefore(child); + } + } + + void handleLongPress(Offset localPosition) { + TrendlineRenderer? child = lastChild; + while (child != null) { + final bool hasTooltip = + _isTooltipEnabled && + renderer!.parent!.tooltipBehavior!.activationMode == + ActivationMode.longPress; + if (child.enableTooltip && hasTooltip) { + child._handleCurrentInteraction(localPosition); + } + child = childBefore(child); + } + } + + void setXAxis(RenderChartAxis? value) { + TrendlineRenderer? child = firstChild; + while (child != null) { + child._xAxis = value; + child = childAfter(child); + } + } + + void setYAxis(RenderChartAxis? value) { + TrendlineRenderer? child = firstChild; + while (child != null) { + child._yAxis = value; + child = childAfter(child); + } + } + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! TrendlineParentData) { + child.parentData = TrendlineParentData(); + } + } + + void performUpdate(CartesianSeriesRenderer renderer) { + this.renderer = renderer; + TrendlineRenderer? child = firstChild; + while (child != null) { + child.series = renderer; + child.performUpdate(); + child = childAfter(child); + } + } + + DoubleRange range(RenderChartAxis axis, DoubleRange actualRange) { + num minimum = actualRange.minimum; + num maximum = actualRange.maximum; + + TrendlineRenderer? child = firstChild; + while (child != null && !child.isToggled) { + final DoubleRange current = child.range(axis); + minimum = min(minimum, current.minimum); + maximum = max(maximum, current.maximum); + child = childAfter(child); + } + return DoubleRange(minimum, maximum); + } + + List? buildLegendItems( + int seriesIndex, + LegendItemProviderMixin provider, + ) { + final List legendItems = []; + const int trendlineIndex = 0; + TrendlineRenderer? child = firstChild; + while (child != null) { + final List? items = child.buildLegendItems( + trendlineIndex, + provider, + ); + if (items != null) { + legendItems.addAll(items); + } + child = childAfter(child); + } + return legendItems; + } + + void updateLegendState(LegendItem seriesItem, bool seriesVisibility) { + TrendlineRenderer? child = firstChild; + while (child != null) { + child._updateLegendBasedOnSeries(seriesVisibility); + child = childAfter(child); + } + } + + void populateDataSource( + List seriesXValues, { + List? seriesYValues, + List? seriesHighValues, + List? seriesLowValues, + }) { + TrendlineRenderer? child = firstChild; + while (child != null) { + final String valueField = child.valueField!; + List currentYValues; + if (valueField == 'low' && seriesLowValues != null) { + currentYValues = seriesLowValues; + } else if (valueField == 'high' && seriesHighValues != null) { + currentYValues = seriesHighValues; + } else if (seriesYValues != null) { + currentYValues = seriesYValues; + } else { + currentYValues = []; + } + child.populateDataSource(seriesXValues, currentYValues); + child = childAfter(child); + } + } + + @override + void performLayout() { + TrendlineRenderer? child = firstChild; + while (child != null) { + child.layout(constraints); + child = childAfter(child); + } + size = constraints.biggest; + } + + @override + void paint(PaintingContext context, Offset offset) { + defaultPaint(context, offset); + } +} + +class TrendlineWidget extends LeafRenderObjectWidget { + const TrendlineWidget({ + super.key, + required this.index, + required this.isVisible, + required this.enableTooltip, + required this.name, + required this.dashArray, + required this.color, + required this.opacity, + required this.width, + required this.isVisibleInLegend, + required this.legendIconType, + required this.type, + required this.valueField, + required this.intercept, + required this.period, + required this.polynomialOrder, + required this.backwardForecast, + required this.forwardForecast, + required this.markerSettings, + this.animationDuration, + this.animationDelay, + this.onRenderDetailsUpdate, + }); + + final int index; + final bool isVisible; + final bool enableTooltip; + final String? name; + final Color color; + final double opacity; + final List? dashArray; + final double width; + final String valueField; + final LegendIconType legendIconType; + final TrendlineType type; + final MarkerSettings markerSettings; + final bool isVisibleInLegend; + final int period; + final int polynomialOrder; + final double? intercept; + final double forwardForecast; + final double backwardForecast; + final double? animationDuration; + final double? animationDelay; + final ChartTrendlineRenderCallback? onRenderDetailsUpdate; + + @override + RenderObject createRenderObject(BuildContext context) { + return TrendlineRenderer() + ..index = index + ..isVisible = isVisible + ..enableTooltip = enableTooltip + ..name = name + ..color = color + ..width = width + ..dashArray = dashArray + ..opacity = opacity + ..isVisibleInLegend = isVisibleInLegend + ..legendIconType = legendIconType + ..type = type + ..period = period + ..intercept = intercept + ..valueField = valueField + ..forwardForecast = forwardForecast + ..backwardForecast = backwardForecast + ..polynomialOrder = polynomialOrder + ..markerSettings = markerSettings + ..animationDelay = animationDelay + ..animationDuration = animationDuration + ..onRenderDetailsUpdate = onRenderDetailsUpdate; + } + + @override + void updateRenderObject( + BuildContext context, + TrendlineRenderer renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject + ..isVisible = isVisible + ..enableTooltip = enableTooltip + ..name = name + ..color = color + ..width = width + ..dashArray = dashArray + ..opacity = opacity + ..isVisibleInLegend = isVisibleInLegend + ..legendIconType = legendIconType + ..type = type + ..period = period + ..intercept = intercept + ..valueField = valueField + ..forwardForecast = forwardForecast + ..backwardForecast = backwardForecast + ..polynomialOrder = polynomialOrder + ..markerSettings = markerSettings + ..animationDelay = animationDelay + ..animationDuration = animationDuration + ..onRenderDetailsUpdate = onRenderDetailsUpdate; + } +} + +class TrendlineRenderer extends RenderBox { + TrendlineRenderer(); + + CartesianLegendItem? _legendItem; + + int get index => _index; + int _index = -1; + set index(int value) { + if (_index != value) { + _index = value; + } + } + + bool get isVisible => _isVisible; + bool _isVisible = true; + set isVisible(bool value) { + if (_isVisible != value) { + _isVisible = value; + isToggled = !value; + } + } + + bool get isToggled => _isToggled; + bool _isToggled = false; + set isToggled(bool value) { + if (_isToggled != value) { + _isToggled = value; + series?.markNeedsUpdate(); + } + } + + String? get name => _name; + String? _name; + set name(String? value) { + if (_name != value) { + _name = value; + series?.markNeedsLegendUpdate(); + } + } + + double get width => _width; + double _width = 2; + set width(double value) { + if (value != _width) { + _width = value; + markNeedsPaint(); + } + } + + Color? get color => _color; + Color? _color; + set color(Color? value) { + if (_color != value) { + _color = value; + markNeedsPaint(); + } + } + + double get opacity => _opacity; + double _opacity = 1.0; + set opacity(double value) { + if (_opacity != value) { + _opacity = value; + markNeedsPaint(); + } + } + + List? get dashArray => _dashArray; + List? _dashArray; + set dashArray(List? value) { + if (_dashArray != value) { + _dashArray = value; + markNeedsPaint(); + } + } + + bool get enableTooltip => _enableTooltip; + bool _enableTooltip = true; + set enableTooltip(bool value) { + if (_enableTooltip != value) { + _enableTooltip = value; + } + } + + double? get animationDuration => _animationDuration; + double? _animationDuration = 0.0; + set animationDuration(double? value) { + if (_animationDuration != value) { + _animationDuration = value; + } + } + + double? get animationDelay => _animationDelay; + double? _animationDelay = 0.0; + set animationDelay(double? value) { + if (_animationDelay != value) { + _animationDelay = value; + } + } + + bool get isVisibleInLegend => _isVisibleInLegend; + bool _isVisibleInLegend = true; + set isVisibleInLegend(bool value) { + if (_isVisibleInLegend != value) { + _isVisibleInLegend = value; + if (series != null) { + series!.markNeedsLegendUpdate(); + } + } + } + + LegendIconType get legendIconType => _legendIconType; + LegendIconType _legendIconType = LegendIconType.horizontalLine; + set legendIconType(LegendIconType value) { + if (_legendIconType != value) { + _legendIconType = value; + series?.markNeedsLegendUpdate(); + } + } + + TrendlineType get type => _type; + TrendlineType _type = TrendlineType.linear; + set type(TrendlineType value) { + if (_type != value) { + _type = value; + markNeedsLayout(); + } + } + + double get backwardForecast => _backwardForecast; + double _backwardForecast = 0; + set backwardForecast(double value) { + if (_backwardForecast != value) { + _backwardForecast = value; + markNeedsLayout(); + } + } + + double get forwardForecast => _forwardForecast; + double _forwardForecast = 0; + set forwardForecast(double value) { + if (_forwardForecast != value) { + _forwardForecast = value; + markNeedsLayout(); + } + } + + String? get valueField => _valueField; + String? _valueField = 'high'; + set valueField(String? value) { + if (_valueField != value) { + _valueField = value; + markNeedsLayout(); + } + } + + int get period => _period; + int _period = 2; + set period(int value) { + if (_period != value) { + _period = value; + markNeedsLayout(); + } + } + + int get polynomialOrder => _polynomialOrder; + int _polynomialOrder = 2; + set polynomialOrder(int value) { + if (_polynomialOrder != value) { + if (value >= 2 && value <= 6) { + _polynomialOrder = value; + markNeedsLayout(); + } + } + } + + double? get intercept => _intercept; + double? _intercept; + set intercept(double? value) { + if (_intercept != value) { + _intercept = value; + markNeedsLayout(); + } + } + + MarkerSettings get markerSettings => _markerSettings; + MarkerSettings _markerSettings = const MarkerSettings(); + set markerSettings(MarkerSettings value) { + if (_markerSettings != value) { + _markerSettings = value; + markNeedsLayout(); + } + } + + ChartTrendlineRenderCallback? onRenderDetailsUpdate; + + DoubleRange xRange = DoubleRange(double.infinity, double.negativeInfinity); + DoubleRange yRange = DoubleRange(double.infinity, double.negativeInfinity); + + CartesianSeriesRenderer? series; + final List _markers = []; + final List _points = []; + // The _points list updates with calculatedDataPoints values when the + // callback is invoked, causing the tooltip is not showing. To avoid this, + // the _renderPoints list is exposed and handled accordingly. + List _renderPoints = []; + final Path _path = Path(); + + List trendlineXValues = []; + List trendlineYValues = []; + List trendSegmentIndexes = []; + _SlopeIntercept _slopeIntercept = _SlopeIntercept(); + List? _slope = []; + double? _rSquaredValue; + + RenderChartAxis? get xAxis => _xAxis; + RenderChartAxis? _xAxis; + + RenderChartAxis? get yAxis => _yAxis; + RenderChartAxis? _yAxis; + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + return enableTooltip; + } + + void _handleCurrentInteraction(Offset localPosition) { + if (series != null && + series!.parent != null && + !series!.parent!.isTooltipActivated) { + final int nearestPointIndex = _nearestPointIndex(localPosition); + if (nearestPointIndex != -1) { + final TooltipInfo? info = tooltipInfo(pointIndex: nearestPointIndex); + if (info != null) { + series!.parent!.behaviorArea!.raiseTooltip(info); + series!.parent!.isTooltipActivated = true; + } + } + } + } + + int _nearestPointIndex(Offset position) { + if (_points.isNotEmpty) { + for (final int segmentIndex in trendSegmentIndexes) { + final ChartMarker marker = series!.markerAt(segmentIndex); + if (tooltipTouchBounds( + _points[segmentIndex], + marker.width, + marker.height, + ).contains(position)) { + return segmentIndex; + } + } + } + return -1; + } + + TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { + for (final int segmentIndex in trendSegmentIndexes) { + pointIndex ??= segmentIndex; + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final ChartMarker marker = + _markers.isNotEmpty ? _markers[pointIndex] : ChartMarker(); + final double markerHeight = + markerSettings.isVisible ? marker.height / 2 : 0; + final Offset preferredPos = _points[pointIndex]; + return TrendlineTooltipInfo( + primaryPosition: localToGlobal( + preferredPos.translate(0, -markerHeight), + ), + secondaryPosition: localToGlobal( + preferredPos.translate(0, markerHeight), + ), + text: series!.tooltipText(chartPoint), + header: + series!.parent!.tooltipBehavior!.shared + ? series!.tooltipHeaderText(chartPoint) + : series!.name, + data: series!.dataSource![pointIndex], + point: chartPoint, + series: series!.widget, + renderer: series!, + seriesIndex: series!.index, + segmentIndex: segmentIndex, + pointIndex: pointIndex, + markerColors: [color], + markerType: marker.type, + ); + } + return null; + } + + CartesianChartPoint _chartPoint(int pointIndex) { + final num xValue = trendlineXValues[pointIndex]; + return CartesianChartPoint( + x: _xRawValue(xValue), + xValue: xValue, + y: trendlineYValues[pointIndex], + ); + } + + dynamic _xRawValue(num value) { + if (xAxis is RenderDateTimeAxis) { + return DateTime.fromMillisecondsSinceEpoch(value.toInt()); + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + if (series != null && series!.xRawValues.isNotEmpty) { + return series!.xRawValues[value.toInt()]; + } + } + return value; + } + + DoubleRange range(RenderChartAxis axis) { + if (axis == xAxis) { + return xRange; + } else { + return yRange; + } + } + + void performUpdate() { + markNeedsLayout(); + } + + void _calculateLinearPoints( + List seriesXValues, + List seriesYValues, + List sortedXValues, + _SlopeIntercept slopeIntercept, + _SlopeIntercept slopeInterceptData, + Function(int, num) slopeValue, + Function(num, double) forecastValue, + List slopeInterceptXValues, + List linearYValues, + ) { + final int length = seriesXValues.length; + linearYValues.addAll(seriesYValues); + for (int i = 0; i < length; i++) { + slopeInterceptXValues.add(slopeValue(i, seriesXValues[i])); + } + + slopeIntercept = _computeSlopeInterceptValues( + seriesXValues, + linearYValues, + length, + slopeIntercept, + ); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) { + final double intercept = slopeIntercept.intercept!; + final double slope = slopeIntercept.slope!; + final double x1 = forecastValue(sortedXValues[0], -backwardForecast); + final double x2 = forecastValue( + sortedXValues[length - 1], + forwardForecast, + ); + final double y1 = slope * x1 + intercept; + final double y2 = slope * x2 + intercept; + + xRange = DoubleRange(x1, x2); + yRange = DoubleRange(y1, y2); + + trendlineXValues + ..add(x1) + ..add(x2); + + trendlineYValues + ..add(y1) + ..add(y2); + } + } + + void _calculateExponentialPoints( + List seriesXValues, + List seriesYValues, + List sortedXValues, + _SlopeIntercept slopeIntercept, + _SlopeIntercept slopeInterceptData, + Function(int, num) slopeValue, + Function(num, double) forecastValue, + List slopeInterceptXValues, + List exponentialYValues, + ) { + final int length = seriesXValues.length; + for (int i = 0; i < length; i++) { + slopeInterceptXValues.add(slopeValue(i, seriesXValues[i])); + exponentialYValues.add(log(seriesYValues[i])); + } + + slopeIntercept = _computeSlopeInterceptValues( + sortedXValues, + exponentialYValues, + length, + slopeIntercept, + ); + + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) { + final double intercept = slopeIntercept.intercept!; + final double slope = slopeIntercept.slope!; + final int midPoint = (length / 2).round(); + + final double x1 = forecastValue(sortedXValues[0], -backwardForecast); + final double x2 = sortedXValues[midPoint - 1].toDouble(); + final double x3 = forecastValue( + sortedXValues[length - 1], + forwardForecast, + ); + double y1 = intercept * exp(slope * x1); + double y2 = intercept * exp(slope * x2); + double y3 = intercept * exp(slope * x3); + + // Avoid rendering trendline when values are NaN. + if (y1.isNaN || y2.isNaN || y3.isNaN) { + y1 = 0; + y2 = 0; + y3 = 0; + } + + xRange = DoubleRange(x1, x3); + yRange = DoubleRange(y1, y3); + + trendlineXValues + ..add(x1) + ..add(x2) + ..add(x3); + + trendlineYValues + ..add(y1) + ..add(y2) + ..add(y3); + } + } + + void _calculatePowerPoints( + List seriesXValues, + List seriesYValues, + List sortedXValues, + _SlopeIntercept slopeIntercept, + _SlopeIntercept slopeInterceptData, + Function(int, num) slopeValue, + Function(num, double) forecastValue, + List slopeInterceptXValues, + List powerYValues, + ) { + final List powerXValues = []; + final int length = seriesXValues.length; + for (int i = 0; i < length; i++) { + final num x = seriesXValues[i]; + final double logX = log(x); + powerXValues.add(logX.isFinite ? logX : x); + slopeInterceptXValues.add(log(slopeValue(i, x))); + powerYValues.add(log(seriesYValues[i])); + } + + slopeIntercept = _computeSlopeInterceptValues( + powerXValues, + powerYValues, + length, + slopeIntercept, + ); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) { + final double intercept = slopeIntercept.intercept!; + final double slope = slopeIntercept.slope!; + final int midPoint = (length / 2).round(); + + double x1 = forecastValue(sortedXValues[0], -backwardForecast); + if (xAxis is! RenderDateTimeAxis) { + x1 = x1 > -1 ? x1 : 0; + } + final double x2 = sortedXValues[midPoint - 1].toDouble(); + final double x3 = forecastValue( + sortedXValues[length - 1], + forwardForecast, + ); + double y1 = x1 == 0 ? 0 : intercept * pow(x1, slope); + double y2 = intercept * pow(x2, slope); + double y3 = intercept * pow(x3, slope); + + // Avoid rendering trendline when values are NaN. + if (y1.isNaN || y2.isNaN || y3.isNaN) { + y1 = 0; + y2 = 0; + y3 = 0; + } + + xRange = DoubleRange(x1, x3); + yRange = DoubleRange(y1, y3); + + trendlineXValues + ..add(x1) + ..add(x2) + ..add(x3); + + trendlineYValues + ..add(y1) + ..add(y2) + ..add(y3); + } + } + + void _calculateLogarithmicPoints( + List seriesXValues, + List seriesYValues, + List sortedXValues, + _SlopeIntercept slopeIntercept, + _SlopeIntercept slopeInterceptData, + Function(int, num) slopeValue, + Function(num, double) forecastValue, + List slopeInterceptXValues, + List logYValues, + ) { + final List logXValues = []; + final int length = seriesXValues.length; + + logYValues.addAll(seriesYValues); + for (int i = 0; i < length; i++) { + final num x = seriesXValues[i]; + final double logX = log(x); + logXValues.add(logX.isFinite ? logX : slopeValue(i, x)); + slopeInterceptXValues.add(log(slopeValue(i, x))); + } + + slopeIntercept = _computeSlopeInterceptValues( + logXValues, + logYValues, + length, + slopeIntercept, + ); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) { + final double intercept = slopeIntercept.intercept!; + final double slope = slopeIntercept.slope!; + final int midPoint = (length / 2).round(); + + final double x1 = forecastValue(sortedXValues[0], -backwardForecast); + final double x2 = sortedXValues[midPoint - 1].toDouble(); + final double x3 = forecastValue( + sortedXValues[length - 1], + forwardForecast, + ); + final double y1 = intercept + (slope * (log(x1).isFinite ? log(x1) : x1)); + final double y2 = intercept + (slope * (log(x2).isFinite ? log(x2) : x2)); + final double y3 = intercept + (slope * (log(x3).isFinite ? log(x3) : x3)); + + xRange = DoubleRange(x1, x3); + yRange = DoubleRange(y1, y3); + + trendlineXValues + ..add(x1) + ..add(x2) + ..add(x3); + + trendlineYValues + ..add(y1) + ..add(y2) + ..add(y3); + } + } + + void _calculateMovingAveragePoints( + List sortedXValues, + List seriesYValues, + ) { + final int xLength = sortedXValues.length; + final int yLength = seriesYValues.length; + final int trendPeriod = period; + int periods = trendPeriod >= xLength ? xLength - 1 : trendPeriod; + periods = max(2, periods); + double? x1; + double? y1; + + for (int i = 0; i < xLength - 1; i++) { + y1 = 0.0; + int count = 0; + int nullCount = 0; + for (int j = i; count < periods; j++) { + count++; + if (j >= yLength) { + nullCount++; + } + y1 = y1! + (j >= yLength ? 0 : seriesYValues[j]); + } + y1 = ((periods - nullCount) <= 0) ? null : (y1! / (periods - nullCount)); + if (y1 != null && !y1.isNaN && i + periods < xLength + 1) { + x1 = sortedXValues[periods - 1 + i].toDouble(); + trendlineXValues.add(x1); + trendlineYValues.add(y1); + } + } + + if (x1 != null && y1 != null) { + xRange = DoubleRange(x1, x1); + yRange = DoubleRange(y1, y1); + } + } + + List? _calculatePolynomialPoints( + List sortedXValues, + List seriesYValues, + List? polynomialSlopes, + Function(int, num) slopeValue, + Function(num, [bool]) polynomialForeCastValue, + ) { + final int trendPolynomialOrder = polynomialOrder; + polynomialSlopes = List.filled(trendPolynomialOrder + 1, null); + final int length = sortedXValues.length; + for (int i = 0; i < length; i++) { + final num x = sortedXValues[i]; + final num y = seriesYValues[i]; + for (int j = 0; j <= trendPolynomialOrder; j++) { + polynomialSlopes[j] ??= 0; + polynomialSlopes[j] = polynomialSlopes[j]! + pow(x.toDouble(), j) * y; + } + } + + final List> matrix = _computeMatrix( + sortedXValues, + trendPolynomialOrder, + ); + // The trendline will not be generated if there is just one data point or + // if the x and y values are the same. For example (1,1), (1,1). + // So, the line was commented and now marker alone will be rendered + // in this case. + // _polynomialSlopes = null; + _gaussJordanElimination(matrix, polynomialSlopes); + _computePoints( + sortedXValues, + seriesYValues, + length, + polynomialSlopes, + polynomialForeCastValue, + ); + List? polynomialSlopeValues; + + if (onRenderDetailsUpdate != null) { + polynomialSlopeValues = List.filled(trendPolynomialOrder + 1, 0); + for (int i = 0; i < length; i++) { + final num y = seriesYValues[i]; + for (int j = 0; j <= trendPolynomialOrder; j++) { + polynomialSlopeValues[j] += + pow(slopeValue(i, sortedXValues[i]), j) * y; + } + } + + final List slopeValues = []; + for (int k = 0; k < length; k++) { + slopeValues.add(slopeValue(k, sortedXValues[k])); + } + + final List> matrix = _computeMatrix( + slopeValues, + trendPolynomialOrder, + ); + // To find the prompt polynomial slopes for the trendline equation, + // gaussJordanElimination method is used here. + _gaussJordanElimination(matrix, polynomialSlopeValues); + } + + return polynomialSlopeValues; + } + + List> _computeMatrix(List xValues, int polynomialOrder) { + final int length = xValues.length; + final List numArray = List.filled( + 2 * polynomialOrder + 1, + 0, + ); + final List> matrix = List>.generate( + polynomialOrder + 1, + (int _) => List.filled(polynomialOrder + 1, 0), + ); + for (int i = 0; i <= polynomialOrder; i++) { + matrix[i] = List.filled(polynomialOrder + 1, 0); + } + + num num1 = 0; + for (int i = 0; i < length; i++) { + final num d = xValues[i]; + num num2 = 1.0; + for (int j = 0; j < numArray.length; j++, num1++) { + numArray[j] += num2; + num2 *= d; + } + } + + for (int k = 0; k <= polynomialOrder; k++) { + for (int l = 0; l <= polynomialOrder; l++) { + matrix[k][l] = numArray[k + l]; + } + } + return matrix; + } + + void _computePoints( + List sortedXValues, + List seriesYValues, + int length, + List? polynomialSlopes, + Function(num, [bool]) polynomialForeCastValue, + ) { + final List polynomialSlopesList = polynomialSlopes!; + final int polynomialSlopesLength = polynomialSlopesList.length; + final double trendBackwardForecast = polynomialForeCastValue( + backwardForecast, + false, + ); + final double trendForwardForecast = polynomialForeCastValue( + forwardForecast, + true, + ); + + num xMin = double.infinity; + num xMax = double.negativeInfinity; + num yMin = double.infinity; + num yMax = double.negativeInfinity; + + double x1; + double y1; + num value = 1; + for (int i = 1; i <= polynomialSlopesLength; i++) { + if (i == 1) { + x1 = sortedXValues[0] - trendBackwardForecast; + y1 = _computePolynomialYValue(polynomialSlopesList, x1); + } else if (i == polynomialSlopesLength) { + x1 = sortedXValues[length - 1] + trendForwardForecast; + y1 = _computePolynomialYValue(polynomialSlopesList, x1); + } else { + value += (length + trendForwardForecast) / polynomialSlopesLength; + x1 = sortedXValues[value.floor() - 1] * 1.0; + y1 = _computePolynomialYValue(polynomialSlopesList, x1); + } + + xMin = min(xMin, x1); + xMax = max(xMax, x1); + yMin = min(yMin, y1); + yMax = max(yMax, y1); + + trendlineXValues.add(x1); + trendlineYValues.add(y1); + } + + xRange = DoubleRange(xMin, xMax); + yRange = DoubleRange(yMin, yMax); + } + + double _computePolynomialYValue(List slopes, num x) { + double sum = 0; + for (int i = 0; i < slopes.length; i++) { + sum += slopes[i]! * pow(x, i); + } + return sum; + } + + bool _gaussJordanElimination( + List> matrix, + List polynomialSlopes, + ) { + final int length = matrix.length; + final List list1 = List.filled(length, 0); + final List list2 = List.filled(length, 0); + final List list3 = List.filled(length, 0); + + for (int i = 0; i < length; i++) { + list3[i] = 0; + } + + int j = 0; + while (j < length) { + num value = 0; + int k = 0, l = 0, m = 0; + while (m < length) { + if (list3[m] != 1) { + int n = 0; + while (n < length) { + if (list3[n] == 0 && matrix[m][n].abs() >= value == true) { + value = matrix[m][n].abs(); + k = m; + l = n; + } + ++n; + } + } + ++m; + } + ++list3[l]; + if (k != l) { + int o = 0; + while (o < length) { + final double val = matrix[k][o]; + matrix[k][o] = matrix[l][o]; + matrix[l][o] = val; + ++o; + } + final num res = polynomialSlopes[k]!; + polynomialSlopes[k] = polynomialSlopes[l]; + polynomialSlopes[l] = res; + } + list2[j] = k; + list1[j] = l; + if (matrix[l][l] == 0.0) { + return false; + } + final num v = 1.0 / matrix[l][l]; + matrix[l][l] = 1.0; + int p = 0; + while (p < length) { + matrix[l][p] *= v; + ++p; + } + polynomialSlopes[l] = polynomialSlopes[l]! * v; + int q = 0; + while (q < length) { + if (q != l) { + final num mVal = matrix[q][l]; + matrix[q][l] = 0.0; + int r = 0; + while (r < length) { + matrix[q][r] -= matrix[l][r] * mVal; + ++r; + } + polynomialSlopes[q] = + polynomialSlopes[q]! - (polynomialSlopes[l]! * mVal); + } + ++q; + } + ++j; + } + + for (int s = length - 1; s >= 0; s--) { + if (list2[s] != list1[s]) { + for (int t = 0; t < length; t++) { + final List mat = matrix[t]; + final double number = mat[list2[s]]; + mat[list2[s]] = mat[list1[s]]; + mat[list1[s]] = number; + } + } + } + + return true; + } + + _SlopeIntercept _computeSlopeInterceptValues( + List xValues, + List yValues, + int length, + _SlopeIntercept slopeIntercept, + ) { + num xAvg = 0.0; + num yAvg = 0.0; + num xyAvg = 0.0; + num xxAvg = 0.0; + int index = 0; + double slope = 0.0; + double trendIntercept = 0.0; + while (index < length) { + final double x = xValues[index].toDouble(); + double y = yValues[index].toDouble(); + if (y.isNaN == true) { + y = (yValues[index - 1] + yValues[index + 1]) / 2; + } + xAvg += x; + yAvg += y; + xyAvg += x * y; + xxAvg += x * x; + index++; + } + + if (intercept != null && + intercept != 0 && + (type == TrendlineType.linear || type == TrendlineType.exponential)) { + trendIntercept = intercept!; + switch (type) { + case TrendlineType.linear: + slope = (xyAvg - (trendIntercept * xAvg)) / xxAvg; + break; + case TrendlineType.exponential: + slope = (xyAvg - (log(trendIntercept.abs()) * xAvg)) / xxAvg; + break; + // ignore: no_default_cases + default: + break; + } + } else { + slope = + ((length * xyAvg) - (xAvg * yAvg)) / + ((length * xxAvg) - (xAvg * xAvg)); + + trendIntercept = + (type == TrendlineType.exponential || type == TrendlineType.power) + ? exp((yAvg - (slope * xAvg)) / length) + : (yAvg - (slope * xAvg)) / length; + } + + slopeIntercept.slope = slope; + slopeIntercept.intercept = trendIntercept; + return slopeIntercept; + } + + DateTimeIntervalType _visibleIntervalType() { + DateTimeIntervalType visibleIntervalType = DateTimeIntervalType.auto; + if (xAxis is RenderDateTimeAxis) { + visibleIntervalType = (xAxis! as RenderDateTimeAxis).visibleIntervalType; + } else if (xAxis is RenderDateTimeCategoryAxis) { + visibleIntervalType = + (xAxis! as RenderDateTimeCategoryAxis).visibleIntervalType; + } + + return visibleIntervalType; + } + + /// It returns the date-time values of trendline series. + int _computeDateTimeForeCast(DateTime date, num interval) { + final int effectiveInterval = interval.floor(); + final DateTimeIntervalType visibleIntervalType = _visibleIntervalType(); + switch (visibleIntervalType) { + case DateTimeIntervalType.years: + return DateTime( + date.year + effectiveInterval, + date.month, + date.day, + date.hour, + date.minute, + date.second, + ).millisecondsSinceEpoch; + + case DateTimeIntervalType.months: + return DateTime( + date.year, + date.month + effectiveInterval, + date.day, + date.hour, + date.minute, + date.second, + ).millisecondsSinceEpoch; + + case DateTimeIntervalType.days: + return date + .add(Duration(days: effectiveInterval)) + .millisecondsSinceEpoch; + + case DateTimeIntervalType.hours: + return date + .add(Duration(hours: effectiveInterval)) + .millisecondsSinceEpoch; + + case DateTimeIntervalType.minutes: + return date + .add(Duration(minutes: effectiveInterval)) + .millisecondsSinceEpoch; + + case DateTimeIntervalType.seconds: + return date + .add(Duration(seconds: effectiveInterval)) + .millisecondsSinceEpoch; + + case DateTimeIntervalType.milliseconds: + return date + .add(Duration(milliseconds: effectiveInterval)) + .millisecondsSinceEpoch; + + case DateTimeIntervalType.auto: + break; + } + return date.millisecondsSinceEpoch; + } + + int _computeDateTimeDuration(bool isForward) { + Duration duration = Duration.zero; + final double forecast = isForward ? forwardForecast : backwardForecast; + final int foreCastRoundValue = forecast.round(); + final DateTimeIntervalType visibleIntervalType = _visibleIntervalType(); + switch (visibleIntervalType) { + case DateTimeIntervalType.auto: + duration = Duration.zero; + break; + case DateTimeIntervalType.years: + duration = Duration(days: (365.25 * forecast).round()); + break; + case DateTimeIntervalType.months: + duration = Duration(days: 31 * foreCastRoundValue); + break; + case DateTimeIntervalType.days: + duration = Duration(days: foreCastRoundValue); + break; + case DateTimeIntervalType.hours: + duration = Duration(hours: foreCastRoundValue); + break; + case DateTimeIntervalType.minutes: + duration = Duration(minutes: foreCastRoundValue); + break; + case DateTimeIntervalType.seconds: + duration = Duration(seconds: foreCastRoundValue); + break; + case DateTimeIntervalType.milliseconds: + duration = Duration(milliseconds: foreCastRoundValue); + } + return duration.inMilliseconds; + } + + void _transformLinearPoints() { + final Function(num, num) transformX = series!.pointToPixelX; + final Function(num, num) transformY = series!.pointToPixelY; + final num x1 = trendlineXValues[0]; + final num y1 = trendlineYValues[0]; + final num x2 = trendlineXValues[1]; + final num y2 = trendlineYValues[1]; + + final double x1Value = transformX(x1, y1); + final double y1Value = transformY(x1, y1); + final double x2Value = transformX(x2, y2); + final double y2Value = transformY(x2, y2); + + _points + ..add(Offset(x1Value, y1Value)) + ..add(Offset(x2Value, y2Value)); + } + + void _transformTrendlineWithControlPoints() { + final Function(num, num) transformX = series!.pointToPixelX; + final Function(num, num) transformY = series!.pointToPixelY; + final num x1 = trendlineXValues[0]; + final num y1 = trendlineYValues[0]; + final double moveX = transformX(x1, y1); + final double moveY = transformY(x1, y1); + _points.add(Offset(moveX, moveY)); + + final int length = trendlineXValues.length; + for (int i = 0; i < length - 1; i++) { + final num x2 = trendlineXValues[i + 1]; + final num y2 = trendlineYValues[i + 1]; + final double x2Value = transformX(x2, y2); + final double y2Value = transformY(x2, y2); + _points.add(Offset(x2Value, y2Value)); + } + } + + void _transformMovingAveragePoints() { + final Function(num, num) transformX = series!.pointToPixelX; + final Function(num, num) transformY = series!.pointToPixelY; + final num x1 = trendlineXValues[0]; + final num y1 = trendlineYValues[0]; + final double x1Value = transformX(x1, y1); + final double y1Value = transformY(x1, y1); + _points.add(Offset(x1Value, y1Value)); + + final int length = trendlineXValues.length; + for (int i = 1; i < length; i++) { + final num x2 = trendlineXValues[i]; + final num y2 = trendlineYValues[i]; + final double x2Value = transformX(x2, y2); + final double y2Value = transformY(x2, y2); + _points.add(Offset(x2Value, y2Value)); + } + } + + void _computeLinearPath() { + final Offset start = _renderPoints[0]; + _path.moveTo(start.dx, start.dy); + final int length = _renderPoints.length; + for (int i = 1; i < length; i++) { + final Offset next = _renderPoints[i]; + _path.lineTo(next.dx, next.dy); + } + } + + void _computeCubicPath() { + final Function(num, num) transformX = series!.pointToPixelX; + final Function(num, num) transformY = series!.pointToPixelY; + final Offset start = _renderPoints[0]; + _path.moveTo(start.dx, start.dy); + + final int lastIndex = _renderPoints.length - 1; + for (int i = 0; i < lastIndex; i++) { + final Offset next = _renderPoints[i + 1]; + final List controlPoints = _computeControlPoints(i); + final double controlX1 = controlPoints[0].dx; + final double controlY1 = controlPoints[0].dy; + final double controlX2 = controlPoints[1].dx; + final double controlY2 = controlPoints[1].dy; + final double controlX1Value = transformX(controlX1, controlY1); + final double controlY1Value = transformY(controlX1, controlY1); + final double controlX2Value = transformX(controlX2, controlY2); + final double controlY2Value = transformY(controlX2, controlY2); + _path.cubicTo( + controlX1Value, + controlY1Value, + controlX2Value, + controlY2Value, + next.dx, + next.dy, + ); + } + } + + List _computeControlPoints(int index) { + final List controlPoints = []; + final List xValues = [...trendlineXValues]; + final List yValues = [...trendlineYValues]; + final int length = xValues.length; + List yCoefficient = List.filled(length, 0); + yCoefficient = _computeNaturalSpline( + xValues, + yValues, + yCoefficient, + xValues.length, + SplineType.natural, + ); + return _controlPoints( + xValues, + yValues, + yCoefficient[index]!.toDouble(), + yCoefficient[index + 1]!.toDouble(), + index, + controlPoints, + ); + } + + List _computeNaturalSpline( + List xValues, + List yValues, + List yCoefficient, + int length, + SplineType splineType, + ) { + const double a = 6; + num d1, d2, d3, dy1, dy2, p; + + final List u = List.filled(length, null); + if (splineType == SplineType.clamped && length > 1) { + final num x1 = xValues[0]; + final num x2 = xValues[1]; + final num y1 = yValues[0]; + final num y2 = yValues[1]; + final num xDiff = x2 - x1; + final num yDiff = y2 - y1; + final num xEnd = xValues[length - 1]; + final num xPenultimate = xValues[length - 2]; + final num yEnd = yValues[length - 1]; + final num yPenultimate = yValues[length - 2]; + final num d0 = xDiff / yDiff; + final num dn = (xEnd - xPenultimate) / (yEnd - yPenultimate); + + u[0] = 0.5; + yCoefficient[0] = (3 * yDiff / xDiff) - (3 * d0); + yCoefficient[length - 1] = + (3 * dn) - ((3 * (yEnd - yPenultimate)) / (xEnd - xPenultimate)); + if (yCoefficient[0] == double.infinity || yCoefficient[0]!.isNaN) { + yCoefficient[0] = 0; + } + + final num? endCoef = yCoefficient[length - 1]; + if (endCoef == double.infinity || endCoef!.isNaN) { + yCoefficient[length - 1] = 0; + } + } else { + yCoefficient[0] = u[0] = 0; + yCoefficient[length - 1] = 0; + } + + final int segmentCount = length - 1; + for (int i = 1; i < segmentCount; i++) { + yCoefficient[i] = 0; + final num x = xValues[i]; + final num y = yValues[i]; + final num nextX = xValues[i + 1]; + final num nextY = yValues[i + 1]; + final num previousX = xValues[i - 1]; + final num previousY = yValues[i - 1]; + if (!y.isNaN && !nextY.isNaN && !previousY.isNaN) { + d1 = x - previousX; + d2 = nextX - previousX; + d3 = nextX - x; + dy1 = nextY - y; + dy2 = y - previousY; + if (x == previousX || x == nextX) { + yCoefficient[i] = 0; + u[i] = 0; + } else { + p = 1 / ((d1 * yCoefficient[i - 1]!) + (2 * d2)); + yCoefficient[i] = -p * d3; + if (u[i - 1] != null) { + u[i] = p * ((a * ((dy1 / d3) - (dy2 / d1))) - (d1 * u[i - 1]!)); + } + } + } + } + + for (int i = length - 2; i >= 0; i--) { + final num? yCoef1 = yCoefficient[i]; + final num? yCoef2 = yCoefficient[i + 1]; + if (u[i] != null && yCoef1 != null && yCoef2 != null) { + yCoefficient[i] = (yCoef1 * yCoef2) + u[i]!; + } + } + + return yCoefficient; + } + + List _controlPoints( + List xValues, + List yValues, + double yCoefficient, + double nextYCoefficient, + int i, + List controlPoints, + ) { + final List values = List.filled(4, null); + final num x = xValues[i]; + final num y = yValues[i]!; + final num nextX = xValues[i + 1]; + final num nextY = yValues[i + 1]!; + const double oneThird = 1 / 3.0; + num deltaX2 = nextX - x; + deltaX2 = deltaX2 * deltaX2; + final num dx1 = (2 * x) + nextX; + final num dx2 = x + (2 * nextX); + final num dy1 = (2 * y) + nextY; + final num dy2 = y + (2 * nextY); + final double y1 = + oneThird * + (dy1 - + (oneThird * deltaX2 * (yCoefficient + (0.5 * nextYCoefficient)))); + final double y2 = + oneThird * + (dy2 - + (oneThird * deltaX2 * ((0.5 * yCoefficient) + nextYCoefficient))); + 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; + } + + double? _computeRSquaredValue( + List xValues, + List yValues, + List? slope, + double? intercept, + Function(int, num) slopeValue, + ) { + double rSquare = 0.0; + const int power = 2; + final List xValue = []; + double yMean = 0; + for (int i = 0; i < xValues.length; i++) { + xValue.add(slopeValue(i, xValues[i])); + yMean += yValues[i]; + } + + final int yLength = yValues.length; + yMean = yMean / yLength; + // Total sum of square. + double sumOfSquare = 0.0; + for (int j = 0; j < yLength; j++) { + sumOfSquare += pow(yValues[j] - yMean, power); + } + // Sum of squares due to regression. + double sumOfSquareDueToRegression = 0.0; + switch (type) { + case TrendlineType.linear: + for (int k = 0; k < yLength; k++) { + sumOfSquareDueToRegression += pow( + ((slope![0] * xValue[k]) + intercept!) - yMean, + power, + ); + } + break; + + case TrendlineType.exponential: + for (int k = 0; k < yLength; k++) { + sumOfSquareDueToRegression += pow( + (intercept! * exp(slope![0] * xValue[k])) - yMean, + power, + ); + } + break; + + case TrendlineType.power: + for (int k = 0; k < yLength; k++) { + sumOfSquareDueToRegression += pow( + (intercept! * pow(xValue[k], slope![0])) - yMean, + power, + ); + } + break; + + case TrendlineType.logarithmic: + for (int k = 0; k < yLength; k++) { + sumOfSquareDueToRegression += pow( + ((slope![0] * log(xValue[k])) + intercept!) - yMean, + power, + ); + } + break; + + case TrendlineType.polynomial: + for (int k = 0; k < yLength; k++) { + double yCap = 0.0; + for (int i = 0; i < slope!.length; i++) { + yCap += slope[i] * pow(xValue[k], i); + } + sumOfSquareDueToRegression += pow(yCap - yMean, power); + } + break; + + case TrendlineType.movingAverage: + break; + } + + rSquare = sumOfSquareDueToRegression / sumOfSquare; + return rSquare.isNaN ? 0 : rSquare; + } + + num _slopeXValue(int index, num value) { + return index + 1; + } + + num _slopeLogXValue(int index, num value) { + return log(value); + } + + num _forecastValue(num value, double forecast) { + return value + forecast; + } + + num _slopeDateTimeXValue(int index, num value) { + final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( + value.toInt(), + ); + return dateTime.difference(DateTime(1900)).inDays; + } + + double _forecastDateTimeValue(num value, double forecast) { + final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( + value.toInt(), + ); + return _computeDateTimeForeCast(dateTime, forecast).toDouble(); + } + + num _polynomialForeCastValue(num forecast, [bool? isForeCast]) { + return forecast; + } + + double _polynomialForeCastDateTimeAxisValue( + num forecast, [ + bool? isForeCast, + ]) { + return _computeDateTimeDuration(isForeCast!).toDouble(); + } + + void _resetTrendlineDataHolders() { + trendlineXValues.clear(); + trendlineYValues.clear(); + trendSegmentIndexes.clear(); + _rSquaredValue = null; + _slope = null; + _slopeIntercept = _SlopeIntercept(); + } + + void _resetRenderPoints() { + _points.clear(); + _renderPoints.clear(); + _path.reset(); + _markers.clear(); + } + + void populateDataSource(List seriesXValues, List seriesYValues) { + final bool trendIsVisible = + !isToggled && + (type == TrendlineType.polynomial + ? (polynomialOrder >= 2 && polynomialOrder <= 6) + : !(type == TrendlineType.movingAverage) || + (period >= 2 && period <= seriesXValues.length - 1)); + + if (series == null || + !series!.controller.isVisible || + !trendIsVisible || + seriesXValues.isEmpty || + seriesYValues.isEmpty) { + return; + } + + final List yValues = []; + final List slopeInterceptXValues = []; + _SlopeIntercept slopeInterceptData = _SlopeIntercept(); + List? polynomialSlopes; + + late Function(int, num) slopeValue; + late Function(num, double) forecastValue; + late Function(num, [bool]) polynomialForeCastValue; + + late List sortedXValues; + if (series!.canFindLinearVisibleIndexes) { + sortedXValues = seriesXValues; + } else { + final List xValuesCopy = [...seriesXValues]; + xValuesCopy.sort(); + sortedXValues = xValuesCopy; + } + + _resetTrendlineDataHolders(); + if (xAxis is RenderDateTimeAxis) { + slopeValue = _slopeDateTimeXValue; + forecastValue = _forecastDateTimeValue; + polynomialForeCastValue = _polynomialForeCastDateTimeAxisValue; + } else if (xAxis is RenderLogarithmicAxis) { + slopeValue = _slopeLogXValue; + forecastValue = _forecastValue; + polynomialForeCastValue = _polynomialForeCastValue; + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + slopeValue = _slopeXValue; + forecastValue = _forecastValue; + polynomialForeCastValue = _polynomialForeCastValue; + } else { + slopeValue = _slopeXValue; + forecastValue = _forecastValue; + polynomialForeCastValue = _polynomialForeCastValue; + } + + switch (type) { + case TrendlineType.linear: + _calculateLinearPoints( + seriesXValues, + seriesYValues, + sortedXValues, + _slopeIntercept, + slopeInterceptData, + slopeValue, + forecastValue, + slopeInterceptXValues, + yValues, + ); + break; + + case TrendlineType.exponential: + _calculateExponentialPoints( + seriesXValues, + seriesYValues, + sortedXValues, + _slopeIntercept, + slopeInterceptData, + slopeValue, + forecastValue, + slopeInterceptXValues, + yValues, + ); + break; + + case TrendlineType.power: + _calculatePowerPoints( + seriesXValues, + seriesYValues, + sortedXValues, + _slopeIntercept, + slopeInterceptData, + slopeValue, + forecastValue, + slopeInterceptXValues, + yValues, + ); + break; + + case TrendlineType.logarithmic: + _calculateLogarithmicPoints( + seriesXValues, + seriesYValues, + sortedXValues, + _slopeIntercept, + slopeInterceptData, + slopeValue, + forecastValue, + slopeInterceptXValues, + yValues, + ); + break; + + case TrendlineType.polynomial: + _slope = _calculatePolynomialPoints( + sortedXValues, + seriesYValues, + polynomialSlopes, + slopeValue, + polynomialForeCastValue, + ); + break; + + case TrendlineType.movingAverage: + _calculateMovingAveragePoints(sortedXValues, seriesYValues); + _slope = null; + break; + } + + // Calculate slope and intercept values after calculated trendline points. + if (!(type == TrendlineType.movingAverage || + type == TrendlineType.polynomial)) { + slopeInterceptData = _computeSlopeInterceptValues( + slopeInterceptXValues, + yValues, + seriesXValues.length, + _slopeIntercept, + ); + _slope = [slopeInterceptData.slope!]; + } + + if (onRenderDetailsUpdate != null) { + _rSquaredValue = _computeRSquaredValue( + seriesXValues, + seriesYValues, + _slope, + _slopeIntercept.intercept, + slopeValue, + ); + } + + // Calculate segment index based on the trendXValues. + trendSegmentIndexes.clear(); + final int xLength = trendlineXValues.length; + for (int i = 0; i < xLength; i++) { + trendSegmentIndexes.add(i); + } + } + + List? buildLegendItems( + int index, + LegendItemProviderMixin provider, + ) { + if (isVisibleInLegend) { + _legendItem = CartesianLegendItem( + text: name ?? _defaultTrendlineName(), + iconType: _toShapeMarkerType( + legendIconType, + legendItemProvider: provider, + ), + iconColor: color!, + iconBorderWidth: 2, + series: series, + seriesIndex: series!.index, + pointIndex: index, + isToggled: isToggled, + onTap: _handleLegendItemTapped, + onRender: _handleLegendItemCreated, + ); + return [_legendItem!]; + } else { + _legendItem = null; + return null; + } + } + + void _updateLegendBasedOnSeries(bool seriesToggled) { + if (!isToggled) { + if (_legendItem != null) { + _legendItem!.onToggled?.call(); + } + } + } + + void _handleLegendItemTapped(LegendItem item, bool wasToggled) { + if (_legendItem != null && series != null && series!.controller.isVisible) { + if (series!.parent != null && series!.parent!.onLegendTapped != null) { + final LegendTapArgs args = LegendTapArgs( + _legendItem!.series, + _legendItem!.seriesIndex, + ); + series!.parent!.onLegendTapped!(args); + } + isToggled = wasToggled; + _legendItem!.onToggled?.call(); + } + } + + void _handleLegendItemCreated(ItemRendererDetails details) { + if (series != null && + series!.parent != null && + series!.parent!.onLegendItemRender != null) { + final CartesianLegendItem item = details.item as CartesianLegendItem; + final LegendIconType iconType = toLegendIconType(details.iconType); + final LegendRenderArgs args = + LegendRenderArgs(item.seriesIndex, item.pointIndex) + ..text = details.text + ..legendIconType = iconType + ..color = details.color; + series!.parent!.onLegendItemRender!(args); + if (args.legendIconType != iconType) { + details.iconType = _toShapeMarkerType( + args.legendIconType ?? LegendIconType.seriesType, + ); + } + + details + ..text = args.text ?? '' + ..color = args.color ?? Colors.transparent; + } + } + + ShapeMarkerType _toShapeMarkerType( + LegendIconType iconType, { + LegendItemProviderMixin? legendItemProvider, + }) { + switch (iconType) { + case LegendIconType.seriesType: + if (legendItemProvider != null) { + return legendItemProvider.effectiveLegendIconType(); + } + return dashArray != null && + !dashArray!.every((double value) => value <= 0) + ? ShapeMarkerType.lineSeriesWithDashArray + : ShapeMarkerType.lineSeries; + case LegendIconType.circle: + return ShapeMarkerType.circle; + case LegendIconType.rectangle: + return ShapeMarkerType.rectangle; + case LegendIconType.image: + return ShapeMarkerType.image; + case LegendIconType.pentagon: + return ShapeMarkerType.pentagon; + case LegendIconType.verticalLine: + return ShapeMarkerType.verticalLine; + case LegendIconType.horizontalLine: + return ShapeMarkerType.horizontalLine; + case LegendIconType.diamond: + return ShapeMarkerType.diamond; + case LegendIconType.triangle: + return ShapeMarkerType.triangle; + case LegendIconType.invertedTriangle: + return ShapeMarkerType.invertedTriangle; + } + } + + String _defaultTrendlineName() { + switch (type) { + case TrendlineType.linear: + return 'Linear'; + case TrendlineType.exponential: + return 'Exponential'; + case TrendlineType.power: + return 'Power'; + case TrendlineType.logarithmic: + return 'Logarithmic'; + case TrendlineType.polynomial: + return 'Polynomial'; + case TrendlineType.movingAverage: + return 'Moving average'; + } + } + + @override + void performLayout() { + if (!isToggled && (series != null && series!.controller.isVisible)) { + transformValues(); + } + size = constraints.biggest; + } + + void transformValues() { + if (series == null || + trendlineXValues.isEmpty || + trendlineYValues.isEmpty) { + return; + } + + _resetRenderPoints(); + _transformTrendlinePoints(); + if (markerSettings.isVisible) { + _calculateMarkerPositions(); + } + + _renderPoints = [..._points]; + + // Trigger the onRenderDetailsUpdate event. + if (onRenderDetailsUpdate != null) { + final TrendlineRenderParams args = TrendlineRenderParams( + _slopeIntercept.intercept, + series!.index, + name ?? _defaultTrendlineName(), + series!.name, + _renderPoints, + _slope, + _rSquaredValue, + ); + onRenderDetailsUpdate!(args); + } + + _computeTrendlinePath(); + } + + void _transformTrendlinePoints() { + switch (type) { + case TrendlineType.linear: + _transformLinearPoints(); + break; + + case TrendlineType.exponential: + case TrendlineType.logarithmic: + case TrendlineType.polynomial: + case TrendlineType.power: + _transformTrendlineWithControlPoints(); + break; + + case TrendlineType.movingAverage: + _transformMovingAveragePoints(); + break; + } + } + + void _computeTrendlinePath() { + if (_renderPoints.isEmpty) { + return; + } + + switch (type) { + case TrendlineType.linear: + case TrendlineType.movingAverage: + _computeLinearPath(); + break; + + case TrendlineType.exponential: + case TrendlineType.logarithmic: + case TrendlineType.polynomial: + case TrendlineType.power: + _computeCubicPath(); + break; + } + } + + void _calculateMarkerPositions() { + final Function(num, num) transformX = series!.pointToPixelX; + final Function(num, num) transformY = series!.pointToPixelY; + final Color themeFillColor = series!.parent!.themeData!.colorScheme.surface; + final int length = trendlineXValues.length; + for (int i = 0; i < length; i++) { + final ChartMarker marker = + ChartMarker() + ..x = trendlineXValues[i] + ..y = trendlineYValues[i] + ..index = i; + marker.merge( + borderColor: markerSettings.borderColor ?? color, + borderWidth: markerSettings.borderWidth, + color: markerSettings.color ?? themeFillColor, + height: markerSettings.height, + width: markerSettings.width, + image: markerSettings.image, + type: markerSettings.shape, + ); + final double positionX = + transformX(marker.x, marker.y) - marker.width / 2; + final double positionY = + transformY(marker.x, marker.y) - marker.height / 2; + marker.position = Offset(positionX, positionY); + _markers.add(marker); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (series == null || !series!.controller.isVisible || isToggled) { + return; + } + + final Paint trendlinePaint = + Paint() + ..color = color!.withValues(alpha: opacity) + ..strokeWidth = width + ..style = PaintingStyle.stroke; + + if (trendlinePaint.color != Colors.transparent && + trendlinePaint.strokeWidth > 0) { + drawDashes(context.canvas, dashArray, trendlinePaint, path: _path); + } + + if (_markers.isNotEmpty) { + final Paint fillPaint = Paint()..isAntiAlias = true; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + for (final ChartMarker marker in _markers) { + fillPaint.color = marker.color!; + strokePaint + ..color = marker.borderColor! + ..strokeWidth = marker.borderWidth / 2; + series!.drawDataMarker( + marker.index, + context.canvas, + fillPaint, + strokePaint, + marker.position, + Size(marker.width, marker.height), + marker.type, + series, + ); + } + } + } + + @override + void dispose() { + _resetTrendlineDataHolders(); + _resetRenderPoints(); + super.dispose(); + } +} + +class _SlopeIntercept { + double? slope; + double? intercept; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart new file mode 100644 index 000000000..9d3dde344 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +const String primaryXAxisDefaultName = 'primaryXAxis'; +const String primaryYAxisDefaultName = 'primaryYAxis'; + +const int angle90Degree = -90; +const int angle45Degree = -45; + +// Multilevel label text padding. +const double textPadding = 6; + +// Multilevel label curly brace text padding. +const double textPaddingOfCurlyBrace = 10; + +// Data label padding between the data point value. +const double dataLabelPadding = 5; + +// Trackball tooltip padding. +const double trackballPadding = 10; + +// Tooltip padding for smooth touch. +const double tooltipPadding = 15; + +// Hilo and HiloOpenClose series padding. +const double hiloPadding = 3; + +// Distance for nearest point. +double pointDistance = 10; + +// Specifies the padding value for group all display mode. +const double groupAllPadding = 10.0; + +// Full angle for circular series. +const double fullAngle = 359.999; + +// Default date-time value (Jan 31st, 1970) converted into milliseconds. +const int defaultTimeStamp = 2592000000; + +/// Specifies the tooltip marker size. +const double tooltipMarkerSize = 10.0; + +/// Specifies the tooltip marker padding. +const EdgeInsets tooltipMarkerPadding = EdgeInsets.all(2.0); + +const EdgeInsetsDirectional tooltipItemSpacing = EdgeInsetsDirectional.only( + end: 3.0, +); + +const EdgeInsets tooltipInnerPadding = EdgeInsets.all(6.0); + +const defaultLegendSizeFactor = 0.3; + +// Crosshair tooltip padding. +const double crosshairPadding = 10; + +// Indicator upper line text. +const String trackballUpperLineText = 'UpperLine'; + +// Indicator ceter line text. +const String trackballCenterText = 'CenterLine'; + +// Indicator lower line text. +const String trackballLowerLineText = 'LowerLine'; + +// Indicator period line text. +const String trackballPeriodLineText = 'PeriodLine'; + +// Indicator MACD line text. +const String trackballMACDLineText = 'MacdLine'; + +// Indicator histogram text. +const String trackballHistogramText = 'Histogram'; + +// Specifies for trackball tooltip marker size. +const double trackballTooltipMarkerSize = 20; + +// Specifies for trackball tooltip padding . +const double trackballTooltipPadding = 17; + +// Specifies for trackball text, tooltip marker padding. +const double defaultTrackballPadding = 5; + +// Specifies for default trackball tooltip width. +const double defaultTooltipWidth = 10; + +// Specifies the space between multiple axes. +const double spaceBetweenMultipleAxes = 3.0; + +// Specifies the tooltip event is touch or mouse. +bool isHover = false; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/enum.dart new file mode 100644 index 000000000..765908086 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/enum.dart @@ -0,0 +1,842 @@ +/// Legend Position in charts. +enum LegendPosition { + /// - `LegendPosition.auto`, places the legend either at the bottom when the + /// height is greater than the width, or at right when the width is + /// greater than height. + auto, + + /// - `LegendPosition.bottom`, places the legend below the plot area. + bottom, + + /// - `LegendPosition.left`, places the legend at the left to the plot area. + left, + + /// - `LegendPosition.right`, places the legend at the right to the plot area. + right, + + /// - `LegendPosition.top`, places the legend at the top of the plot area. + top, +} + +/// Alignment of various elements in chart. +enum ChartAlignment { + /// - `ChartAlignment.near` aligns to the near position. + near, + + /// - `ChartAlignment.center` aligns to the center position + center, + + /// - `ChartAlignment.far` aligns to the far position. + far, +} + +/// Mode to handle the legend items overflow. +enum LegendItemOverflowMode { + /// - `LegendItemOverflowMode.wrap`, will wrap and place the remaining legend + /// item to next line. + wrap, + + /// - `LegendItemOverflowMode.scroll`, will place all the + /// legend items in single line and enables scrolling. + scroll, + + /// - `LegendItemOverflowMode.none`, will simply hides the remaining + /// legend items. + none, +} + +/// Orientation of legend items. +enum LegendItemOrientation { + /// - `LegendItemOrientation.auto`, will align the legend items based + /// on the position. + auto, + + /// - `LegendItemOrientation.horizontal`, will align the + /// legend items horizontally. + horizontal, + + /// - `LegendItemOrientation.vertical`, will align the legend item vertically. + vertical, +} + +/// It used to change the legend icons in different type of series and +/// indicators. +enum LegendIconType { + ///`LegendIconType.seriesType`, icon is same as series type. + seriesType, + + ///`LegendIconType.circle`, icon is changed by circle shape. + circle, + + ///`LegendIconType.rectangle`, icon is changed by rectangle shape. + rectangle, + + ///`LegendIconType.image`, icon is changed by adding the assert image. + image, + + ///`LegendIconType.pentagon`, icon is changed by pentagon shape. + pentagon, + + ///`LegendIconType.verticalLine`, icon is changed by vertical line. + verticalLine, + + ///`LegendIconType.horizontalLine`, icon is changed by horizontal line. + horizontalLine, + + ///`LegendIconType.diamond`, icon is changed by diamond shape. + diamond, + + ///`LegendIconType.triangle`, icon is changed by triangle shape. + triangle, + + ///`LegendIconType.invertedTriangle`, icon is changed by invertedTriangle + // shape. + invertedTriangle, +} + +/// Position of data labels in Cartesian chart.The position of data labels +/// in cartesian charts can be changed using this property. +/// +/// Defaults to `ChartDataLabelAlignment.auto`. +enum ChartDataLabelAlignment { + /// - ChartDataLabelAlignment.auto places the data label either top or + /// bottom position of a point based on the position. + auto, + + /// - ChartDataLabelAlignment.outer places the data label at outside + /// of a point. + outer, + + /// - ChartDataLabelAlignment.top places the data label at the top position + /// of a point. + top, + + /// - ChartDataLabelAlignment.bottom places the data label at the + /// bottom position of a point. + bottom, + + /// - ChartDataLabelAlignment.middle places the data label at the + /// center position of a point. + middle, +} + +/// Position of data labels in Circular chart. +enum CircularLabelPosition { + /// - CircularLabelPosition.curve places the data label inside the point. + inside, + + /// - CircularLabelPosition.line places the data label outside the point. + outside, +} + +/// PyramidMode for pyramid charts. +/// +/// Defaults to `PyramidMode.linear` +enum PyramidMode { + /// - PyramidMode.linear, linear pyramid will be rendered + linear, + + /// - PyramidMode.surface, Surface pyramid will be displayed + surface, +} + +/// Orientation of an axis. +/// +/// The axis orientation can be changed either to vertical or horizontal. +enum AxisOrientation { + /// - AxisOrientation.vertical, renders the axis vertically. + vertical, + + /// - AxisOrientation.horizontal, renders the axis horizontally. + horizontal, +} + +/// Padding for axis ranges. +enum ChartRangePadding { + /// - ChartRangePadding.auto, will apply `none` as padding for + /// horizontal numeric axis, while the vertical numeric axis takes + /// `normal` as padding calculation. + auto, + + /// - ChartRangePadding.none, will not add any padding to the minimum and + /// maximum values. + none, + + /// - ChartRangePadding.normal, will apply padding to the axis based on the + /// default range calculation. + normal, + + /// - ChartRangePadding.additional, will add an interval to the minimum and + /// maximum of the axis. + additional, + + /// - ChartRangePadding.additionalStart, will add an interval to the minimum + /// value of the axis. + additionalStart, + + /// - ChartRangePadding.additionalEnd, will add an interval to the maximum + /// value of the axis. + additionalEnd, + + /// - ChartRangePadding.round, will round the minimum and maximum values to + /// the nearest possible value. + round, + + /// - ChartRangePadding.roundStart, will round the minimum value to + /// the nearest possible value. + roundStart, + + /// - ChartRangePadding.round, will round the maximum value to + /// the nearest possible value. + roundEnd, +} + +/// Placement of category axis labels. +enum LabelPlacement { + /// - LabelPlacement.betweenTicks, places the axis label between the ticks. + betweenTicks, + + /// - LabelPlacement.onTicks, places the axis label on the ticks. + onTicks, +} + +/// Action while the axis label intersects. Axis label placements can be +/// determined when the axis labels get intersected. +enum AxisLabelIntersectAction { + /// - AxisLabelIntersectAction.none, will not perform any action on + /// intersecting labels. + none, + + /// - AxisLabelIntersectAction.hide, hides the intersecting labels. + hide, + + /// - AxisLabelIntersectAction.wrap, wraps and places the intersecting labels + /// in the next line. + wrap, + + /// - AxisLabelIntersectAction.trim, trim the intersecting labels. The tooltip + /// will be shown by tapping or hovering the trimmed label + trim, + + /// - AxisLabelIntersectAction.multipleRows, places the axis labels in + /// multiple rows. + multipleRows, + + /// - AxisLabelIntersectAction.rotate45, rotates all the axis labels to 45°. + rotate45, + + /// - AxisLabelIntersectAction.rotate90, rotates all the axis labels to 90°. + rotate90, +} + +/// Interval type of the DateTime and DateTimeCategory axis. +enum DateTimeIntervalType { + /// - DateTimeIntervalType.auto, will calculate interval based on the + /// data points. + auto, + + /// - DateTimeIntervalType.years, will consider interval as years. + years, + + /// - DateTimeIntervalType.months, will consider interval as months. + months, + + /// - DateTimeIntervalType.days, will consider interval as days. + days, + + /// - DateTimeIntervalType.hours, will consider interval as hours. + hours, + + /// - DateTimeIntervalType.minutes, will consider interval as minutes. + minutes, + + /// - DateTimeIntervalType.seconds, will consider interval as seconds. + seconds, + + /// - DateTimeIntervalType.milliseconds, will consider interval as + /// milliseconds(ms). + milliseconds, +} + +/// Position of the axis labels. +enum ChartDataLabelPosition { + /// - ChartDataLabelPosition.inside places the axis label inside the plot area + inside, + + /// - ChartDataLabelPosition.outside places the axis label outside the + /// plot area. + outside, +} + +/// Renders a variety of splines +/// +/// Spline supports the following types. +/// If SplineType.cardinal type is specified, then specify the tension using +/// cardinalSplineTension property. +enum SplineType { + ///- SplineType.natural, will rendering normal spline. + natural, + + ///- SplineType.monotonic, will rendering monotonic type spline. + monotonic, + + ///- SplineType.cardinal, will rendering cardinal type spline. + cardinal, + + ///- SplineType.clamped, will rendering clamped type spline. + clamped, +} + +/// Placement of edge labels in the axis. +enum EdgeLabelPlacement { + /// - EdgeLabelPlacement.none, places the edge labels in its own position. + none, + + /// - EdgeLabelPlacement.hides, hide the edge labels. + hide, + + /// - EdgeLabelPlacement.shift, shift the edge labels inside the + /// plot area bounds. + shift, +} + +/// Mode of empty data points. +/// +/// When the data points are given a null value, EmptyPointMode +/// will be triggered. +enum EmptyPointMode { + /// - EmptyPointMode.gap, will consider as gap for the empty points. + gap, + + /// - EmptyPointMode.zero, will consider as 0 value for the empty points. + zero, + + /// - EmptyPointMode.drop, will drops to a minimum of the axis. + drop, + + /// - EmptyPointMode.average, will consider the average value of its previous + /// and next data points. + average, +} + +/// Sorting order of data points. +/// +/// Specifies the data points based on the specified sorting property. +enum SortingOrder { + /// - SortingOrder.ascending, arranges the points in ascending order. + ascending, + + /// - SortingOrder.descending, arranges the points in descending order. + descending, + + /// - SortingOrder.none renders the points without sorting. + none, +} + +/// Position of the ticks in the axis. +enum TickPosition { + /// - TickPosition.inside, places the ticks inside the plot area. + inside, + + /// - TickPosition.outside, places the ticks outside the plot area. + outside, +} + +/// Trendline type +/// +/// On the basis of the trendline type, the trendline is rendered +enum TrendlineType { + /// - TrendlineType.linear, displays linear trendline type. + linear, + + /// - TrendlineType.exponential, displays exponential trendline type. + exponential, + + /// - TrendlineType.power, displays power trendline type. + power, + + /// - TrendlineType.logarithmic, displays logarithmic trendline type. + logarithmic, + + /// - TrendlineType.polynomial, displays polynomial trendline type. + polynomial, + + /// - TrendlineType.movingAverage, displays movingAverage trendline type. + movingAverage, +} + +/// Mode to activate a specific interactive user feature. +/// +/// Based on the activation mode, the user interaction will be triggered. +enum ActivationMode { + /// - ActivationMode.singleTap, activates the appropriate feature on + /// single tap. + singleTap, + + /// - ActivationMode.doubleTap, activates on double tap. + doubleTap, + + /// - ActivationMode.longPress, activates on longPress. + longPress, + + /// - ActivationMode.none, does not activate any feature. + none, +} + +/// Trackball tooltip's display mode. +/// +/// Based on TrackballDisplayMode, the trackball tooltip will be displayed. +enum TrackballDisplayMode { + /// - TrackballDisplayMode.none, will not show the tooltip. + none, + + /// - TrackballDisplayMode.floatAllPoints, displays separate tooltip for the + /// points of different series. + floatAllPoints, + + /// - TrackballDisplayMode.groupAllPoints, groups the tooltip text of the + /// points of different series. + groupAllPoints, + + /// - TrackballDisplayMode.nearestPoint, displays the tooltip of + /// nearest point. + nearestPoint, +} + +/// Crosshair line type. +enum CrosshairLineType { + /// - CrosshairLineType.both, displays both horizontal and vertical lines. + both, + + /// - CrosshairLineType.horizontal, displays horizontal line. + horizontal, + + /// - CrosshairLineType.vertical, displays vertical line. + vertical, + + /// - CrosshairLineType.none, will not display crosshair line. + none, +} + +/// Trackball line type. +enum TrackballLineType { + /// - TrackballLineType.vertical, displays vertical trackball line. + vertical, + + /// - TrackballLineType.none, will not display trackball line. + none, +} + +/// Zooming mode in [SfCartesianChart] +enum ZoomMode { + /// - `ZoomMode.x`, zooms in horizontal direction. + x, + + /// - `ZoomMode.y`, zooms in vertical direction. + y, + + /// - `ZoomMode.xy`, zooms in both horizontal and vertical direction. + xy, +} + +/// Data point selection type. +enum SelectionType { + /// - SelectionType.point, selects the individual point. + point, + + /// - SelectionType.series, selects the entire series. + series, + + /// - SelectionType.cluster, selects the cluster of data points. + cluster, +} + +/// Coordinate unit for placing annotations. +enum CoordinateUnit { + /// - CoordinateUnit.point, places the annotation concerning to the points. + point, + + /// - CoordinateUnit.logicalPixel, places the annotation concerning + /// to the pixel value. + logicalPixel, + + /// - CoordinateUnit.percentage, places the annotation concerning to the + /// percentage value. + percentage, +} + +/// Annotation is a note by way of explanation or comment added to the chart. +/// +/// Region of annotation for positioning it. +enum AnnotationRegion { + /// - AnnotationRegion.chart, places the annotation anywhere in the + /// chart area. + chart, + + /// - AnnotationRegion.plotArea, places the annotation anywhere in the + /// plot area. + plotArea, +} + +enum ChartClipBehavior { + /// - ChartClipBehavior.none, will clip the exceeding portions. + clip, + + /// - ChartClipBehavior.hide, will hide when exceeds. + hide, +} + +/// Border mode of area type series. +/// +/// Borders rendered for area type series can be customized. +enum BorderDrawMode { + /// - BorderDrawMode.all, renders border for all the sides of area. + all, + + /// - BorderDrawMode.top, renders border only for top side. + top, + + /// - BorderDrawMode.excludeBottom, renders border except bottom side. + excludeBottom, +} + +/// Border mode of range area series. +enum RangeAreaBorderMode { + /// - RangeAreaBorderMode.all, renders border for all the sides of area. + all, + + /// - RangeAreaBorderMode.excludeSides, renders border except + /// left and right side. + excludeSides, +} + +/// Types of text rendering positions. +enum TextAnchor { + /// - TextAnchor.start, anchors the text at the start position. + start, + + /// - TextAnchor.middle, anchors the text at the middle position. + middle, + + /// - TextAnchor.end, anchors the text at the end position. + end, +} + +/// Tooltip positioning. +enum TooltipPosition { + /// - TooltipPosition.auto, position of tooltip gets tp the default position. + auto, + + /// - TooltipPosition.pointer, position of the tooltip will be at the + /// pointer position. + pointer, +} + +/// Macd indicator type. +enum MacdType { + /// - MacdType.both, indicator will have both line and histogram. + both, + + /// - MacdType.line, the indicator will have a line only. + line, + + /// - MacdType.histogram, the indicator will have a histogram line only. + histogram, +} + +/// Box plot series rendering mode. +enum BoxPlotMode { + /// - BoxPlotMode.normal, box plot mode set as normal. + /// + /// The quartile values are calculated by splitting the list and getting the + /// median values. + normal, + + /// - BoxPlotMode.inclusive, box plot mode set as inclusive. + /// + /// The quartile values are calculated using the formula + /// (N−1) * P (N count, P percentile), and their index value starts from 0 + /// in the list. + inclusive, + + /// - BoxPlotMode.exclusive, box plot mode set as exclusive. + /// + /// The quartile values are calculated using the formula + /// (N+1) * P (N count, P percentile), and their index value starts from 1 + /// in the list. + exclusive, +} + +/// Used to align the Cartesian data label positions. +/// +/// Aligns the data label text to near, center and far. +enum LabelAlignment { + /// `LabelAlignment.end`, data label alignment is greater distance to + /// series line. + end, + + /// `LabelAlignment.start`, data label alignment is closer to series line. + start, + + /// `LabelAlignment.center`, data label alignment is center of the + /// series line. + center, +} + +/// Whether marker should be visible or not when trackball is enabled. +enum TrackballVisibilityMode { + /// * TrackballVisibilityMode.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, + + /// * TrackballVisibilityMode.visible - Makes the trackball marker visible + /// for all the series, irrespective of considering the `isVisible` property's + /// value in the `markerSettings`. + visible, + + /// * TrackballVisibilityMode.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, +} + +/// Determines the type of the Error Bar. +enum ErrorBarType { + /// `ErrorBarType.fixed` - The horizontal and vertical error values should be + /// fixed. + fixed, + + /// `ErrorBarType.percentage` - The horizontal and vertical error values + /// changes into error percentages. + percentage, + + /// `ErrorBarType.standardDeviation` - The horizontal and vertical error + /// values changes depends on the deviation values. + standardDeviation, + + /// `ErrorBarType.standardError` - The horizontal and vertical error values + /// changes depends on approximate error values of all points. + standardError, + + /// `ErrorBarType.custom` - It determines the positive and negative error + /// values in both horizontal and vertical direction. + custom, +} + +/// Determines the error bar direction. +enum Direction { + /// `Direction.plus` - Determines the error bar direction in positive side. + plus, + + /// `Direction.minus` - Determines the error bar direction in negative side. + minus, + + /// `Direction.both` - Determines the error bar direction in both positive + /// and negative sides. + both, +} + +/// Determines mode of the error bar. +enum RenderingMode { + /// `RenderingMode.vertical` - Determines the vertical side of the error bar. + vertical, + + /// `RenderingMode.horizontal` - Determines the horizontal side of the + /// error bar. + horizontal, + + /// `RenderingMode.both` - Determines both the vertical and horizontal sides + /// of the error bar. + both, +} + +/// Border type of the chart axis label. +/// +/// Defaults to `AxisBorderType.rectangle`. +enum AxisBorderType { + /// `AxisBorderType.rectangle` renders border for axis label with + /// rectangle shape. + rectangle, + + /// `AxisBorderType.withoutTopAndBottom` renders border for axis label + /// without top and bottom in the rectangle. + withoutTopAndBottom, +} + +/// Border type of the chart axis multi-level label. +/// +/// Defaults to `MultiLevelBorderType.rectangle`. +enum MultiLevelBorderType { + /// `MultiLevelBorderType.rectangle` renders border for multi-level axis label + /// in rectangle shape. + rectangle, + + /// `MultiLevelBorderType.withoutTopAndBottom` renders border for multi-level + /// axis label without top and bottom in a rectangle shape. + withoutTopAndBottom, + + /// `MultiLevelBorderType.squareBrace` renders square brace as the border + /// for the multi-level label. + squareBrace, + + /// `MultiLevelBorderType.curlyBrace` renders curly brace as the border + /// for the multi-level label. + curlyBrace, +} + +/// Data points grouping mode. +enum CircularChartGroupMode { + /// - CircularChartGroupMode.point, groups the points based on length. + point, + + /// - CircularChartGroupMode.value, groups the points based on the y value. + value, +} + +/// Data label position of range bar series. +enum Position { + /// - Position.left, places the data label to the left side. + left, + + /// - Position.right, places the data label to the right side. + right, +} + +/// Data labels intersect action. +enum LabelIntersectAction { + ///- `LabelIntersectAction.hide`, hides the intersecting labels. + hide, + + /// - `LabelIntersectAction.none`, will not perform any action on + /// intersection. + none, + + /// - `LabelIntersectAction.shift`, will shift and position the intersecting + /// labels smartly. If the labels are moved out of the chart area, then the + /// labels will be trimmed and the eclipse will be shown for the + /// trimmed labels. + shift, +} + +/// Type of connector line. +enum ConnectorType { + /// - ConnectorType.curve, will render the data label connector line curly. + curve, + + /// - ConnectorType.line, will render the data label connector line + /// straightly. + line, +} + +/// Corner style of range bar series. +enum CornerStyle { + /// - CornerStyle.bothFlat, will render both the corners flatly. + bothFlat, + + /// - CornerStyle.bothCurve, will render both the corners curly. + bothCurve, + + /// - CornerStyle.startCurve, will render starting corner curly. + startCurve, + + /// - 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, +} + +/// Data label overflow action when it’s overflowing from its region area. +enum OverflowMode { + /// - OverflowMode.none, no action will be taken and priority goes for + /// `LabelIntersectAction.shift` of `labelIntersectAction` property. + none, + + /// - OverflowMode.trim, data label text will be trimmed. + trim, + + /// - OverflowMode.shift, data label text will be shifted to outside. + shift, + + /// - OverflowMode.hide, data label text will be hidden. + hide, +} + +enum ChartDataPointType { + /// It represents, XY data series [Y] point. + y, + + /// It represents, range and financial series [high] point + /// and, the box and whisker series [maximum] point. + high, + + /// It represents, range and financial series [low] point + /// and, the box and whisker series [minimum] point. + low, + + /// It represents, financial series [open] point + /// and, the box and whisker series [upperQuartile] point. + open, + + /// It represents, financial series [open] point + /// and, the box and whisker series [lowerQuartile] point. + close, + + /// It represents, financial series [open] point + /// and, the box and whisker series [lowerQuartile] point. + volume, + + /// It represents, box and whisker series [median] point. + median, + + /// It represents, box and whisker series [median] point. + mean, + + /// It represents, box and whisker series [outlier] point. + outliers, + + /// It represents, bubble series [bubbleSize]. + bubbleSize, + + /// It represents, stacking series [cumulative] value. + cumulative, +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart new file mode 100644 index 000000000..ecf8bd71e --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart @@ -0,0 +1,2708 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart' hide Image; +import 'package:intl/intl.dart' hide TextDirection; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; +import '../base.dart'; +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../common/core_legend.dart' as core; +import '../common/core_tooltip.dart'; +import '../common/layout_handler.dart'; +import '../common/legend.dart'; +import '../common/marker.dart'; +import '../common/title.dart'; +import '../indicators/technical_indicator.dart'; +import '../interactions/tooltip.dart'; +import '../series/bar_series.dart'; +import '../series/chart_series.dart'; +import '../series/column_series.dart'; +import '../utils/enum.dart'; +import 'constants.dart'; + +// A circular array for dash offsets and lengths. +class _IntervalList { + _IntervalList(this.dashArray); + + final List dashArray; + int _index = 0; + + double get next { + if (_index >= dashArray.length) { + _index = 0; + } + return dashArray[_index++]; + } +} + +Widget buildChartWithTitle( + Widget current, + ChartTitle title, + SfChartThemeData chartThemeData, +) { + if (title.text.isNotEmpty) { + return ChartLayoutHandler( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(bottom: 10.0), + alignment: alignmentFromChartAlignment(title.alignment), + child: Container( + decoration: BoxDecoration( + color: chartThemeData.titleBackgroundColor, + border: Border.all( + color: + title.borderWidth == 0 + ? Colors.transparent + : title.borderColor, + width: title.borderWidth, + ), + ), + child: Text( + title.text, + style: chartThemeData.titleTextStyle, + overflow: TextOverflow.clip, + textAlign: TextAlign.center, + textScaler: const TextScaler.linear(1.2), + ), + ), + ), + Expanded(child: current), + ], + ), + ); + } else { + return current; + } +} + +Alignment alignmentFromChartAlignment(ChartAlignment alignment) { + switch (alignment) { + case ChartAlignment.near: + return Alignment.centerLeft; + + case ChartAlignment.center: + return Alignment.center; + + case ChartAlignment.far: + return Alignment.centerRight; + } +} + +Rect calculateRotatedBounds(Rect labelBounds, int angle) { + final center = labelBounds.center; + final radius = angle * pi / 180; + final corner1 = _rotatePoint(labelBounds.topLeft, center, radius); + final corner2 = _rotatePoint(labelBounds.topRight, center, radius); + final corner3 = _rotatePoint(labelBounds.bottomRight, center, radius); + final corner4 = _rotatePoint(labelBounds.bottomLeft, center, radius); + + final left = min(corner1.dx, min(corner2.dx, min(corner3.dx, corner4.dx))); + final right = max(corner1.dx, max(corner2.dx, max(corner3.dx, corner4.dx))); + final top = min(corner1.dy, min(corner2.dy, min(corner3.dy, corner4.dy))); + final bottom = max(corner1.dy, max(corner2.dy, max(corner3.dy, corner4.dy))); + + return Rect.fromLTWH(left, top, right - left, bottom - top); +} + +Offset _rotatePoint(Offset point, Offset center, double radius) { + final double dx = point.dx - center.dx; + final double dy = point.dy - center.dy; + return Offset( + dx * cos(radius) - dy * sin(radius) + center.dx, + dx * sin(radius) + dy * cos(radius) + center.dy, + ); +} + +double factorFromValue(String? value) { + if (value != null && value.isNotEmpty) { + final double? factor = + value.contains('%') + ? double.tryParse(value.replaceAll(RegExp('%'), ''))! / 100.0 + : double.tryParse(value); + return factor != null ? clampDouble(factor, 0.0, 1.0) : 0.0; + } + return 0.0; +} + +Offset rawValueToPixelPoint( + dynamic x, + num y, + RenderChartAxis? xAxis, + RenderChartAxis? yAxis, + bool isTransposed, +) { + if (xAxis == null || yAxis == null) { + return Offset.zero; + } + + num xValue; + + if (x is int) { + xValue = x; + } else { + if (xAxis is RenderDateTimeAxis) { + assert(x is DateTime); + xValue = (x as DateTime).millisecondsSinceEpoch; + } else if (xAxis is RenderDateTimeCategoryAxis) { + assert(x is DateTime); + xValue = xAxis.labels.indexOf((x as DateTime).millisecondsSinceEpoch); + } else if (xAxis is RenderCategoryAxis) { + assert(x is String); + xValue = xAxis.labels.indexOf(x); + } else { + xValue = x; + } + } + + return Offset( + _pointToPixelX(xValue, y, xAxis, yAxis, isTransposed), + _pointToPixelY(xValue, y, xAxis, yAxis, isTransposed), + ); +} + +double _pointToPixelX( + num x, + num y, + RenderChartAxis xAxis, + RenderChartAxis yAxis, + bool isTransposed, +) { + return isTransposed ? yAxis.pointToPixel(y) : xAxis.pointToPixel(x); +} + +double _pointToPixelY( + num x, + num y, + RenderChartAxis xAxis, + RenderChartAxis yAxis, + bool isTransposed, +) { + return isTransposed ? xAxis.pointToPixel(x) : yAxis.pointToPixel(y); +} + +int findIndex(num target, List values, {int start = 0, int? end}) { + if (values.isEmpty) { + return -1; + } + + end ??= values.length - 1; + int mid = -1; + while (start <= end!) { + mid = start + ((end - start) ~/ 2); + final num midValue = values[mid]; + if (midValue == target) { + if (mid == 0 || values[mid - 1] < target) { + return mid; + } else { + end = mid - 1; + } + } else if (midValue < target) { + start = mid + 1; + } else { + end = mid - 1; + } + } + + return mid; +} + +int clampInt(int x, int min, int max) { + assert(min <= max && !max.isNaN && !min.isNaN); + if (x < min) { + return min; + } + if (x > max) { + return max; + } + if (x.isNaN) { + return max; + } + return x; +} + +Path createPath(List points) { + final Path path = Path(); + bool movePoint = true; + final int length = points.length; + for (int i = 0; i < length; i++) { + final Offset point = points[i]; + if (movePoint && !point.dy.isNaN) { + path.moveTo(point.dx, point.dy); + movePoint = false; + } else { + if (point.dy.isNaN) { + movePoint = true; + continue; + } + path.lineTo(point.dx, point.dy); + } + } + return path; +} + +void drawDashesFromPoints( + Canvas canvas, + List points, + List? dashArray, + Paint paint, +) { + if (points.isEmpty) { + return; + } + + final Path path = createPath(points); + drawDashes(canvas, dashArray, paint, path: path); +} + +void drawDashes( + Canvas canvas, + List? dashArray, + Paint paint, { + Path? path, + Offset? start, + Offset? end, +}) { + if (path == null && + (start == null || start.isNaN || end == null || end.isNaN)) { + return; + } + + bool even = true; + if (dashArray != null && !dashArray.every((double value) => value <= 0)) { + even = false; + for (int i = 1; i < dashArray.length; i = i + 2) { + if (dashArray[i] == 0) { + even = true; + } + } + } + + if (even) { + if (path == null && start != null && end != null) { + canvas.drawLine(start, end, paint); + } else if (path != null) { + canvas.drawPath(path, paint); + } + } else { + if (path == null && start != null && end != null) { + path = + Path() + ..moveTo(start.dx, start.dy) + ..lineTo(end.dx, end.dy); + } + + if (path == null) { + return; + } + + paint.isAntiAlias = false; + canvas.drawPath( + _dashPath(path, dashArray: _IntervalList(dashArray!))!, + paint, + ); + } +} + +/// To calculate dash array path for series. +Path? _dashPath(Path? source, {required _IntervalList dashArray}) { + if (source == null) { + return null; + } + const double initialValue = 0.0; + final Path path = Path(); + for (final PathMetric matric in source.computeMetrics()) { + double distance = initialValue; + bool canDraw = true; + while (distance < matric.length) { + final double length = dashArray.next; + if (canDraw) { + path.addPath( + matric.extractPath(distance, distance + length), + Offset.zero, + ); + } + distance += length; + canDraw = !canDraw; + } + } + return path; +} + +Color dataLabelSurfaceColor( + Color labelColor, + int dataPointIndex, + ChartDataLabelPosition labelPosition, + SfChartThemeData chartThemeData, + ThemeData themeData, + ChartSegment segment, +) { + switch (labelPosition) { + case ChartDataLabelPosition.inside: + return labelColor != Colors.transparent + ? labelColor + : segment.fillPaint.color; + case ChartDataLabelPosition.outside: + if (labelColor == Colors.transparent) { + if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { + return chartThemeData.plotAreaBackgroundColor!; + } else if (chartThemeData.backgroundColor != Colors.transparent) { + return chartThemeData.backgroundColor!; + } + return themeData.colorScheme.surface; + } + return labelColor; + } +} + +/// To get saturation color. +Color saturatedTextColor(Color color) { + final num contrast = + (((color.r * 255) * 299 + (color.g * 255) * 587 + (color.b * 255) * 114) / + 1000) + .round(); + return contrast >= 128 ? Colors.black : Colors.white; +} + +TextStyle saturatedTextStyle(Color surfaceColor, TextStyle baseTextStyle) { + if (baseTextStyle.color != Colors.transparent) { + return baseTextStyle; + } + return baseTextStyle.copyWith(color: saturatedTextColor(surfaceColor)); +} + +Path strokePathFromRRect(RRect? rect, double strokeWidth) { + final RRect bounds = rect!.deflate(strokeWidth / 2); + return Path()..addRRect(bounds); +} + +Path strokePathFromRect(Rect? rect, double strokeWidth) { + final Rect bounds = rect!.deflate(strokeWidth / 2); + return Path()..addRect(bounds); +} + +double? percentToValue(String? value, num size) { + return value != null + ? value.contains('%') + ? (size / 100) * + double.tryParse(value.replaceAll(RegExp('%'), ''))!.abs() + : double.tryParse(value)?.abs() + : null; +} + +Offset calculateOffset(double degree, double radius, Offset center) { + final double radian = degreesToRadians(degree); + return Offset( + center.dx + cos(radian) * radius, + center.dy + sin(radian) * radius, + ); +} + +double degreesToRadians(double deg) => deg * (pi / 180); + +bool isLies(num start, num end, DoubleRange range) { + final bool isStartInside = start >= range.minimum && start <= range.maximum; + final bool isEndInside = end >= range.minimum && end <= range.maximum; + final bool isRangeInside = range.minimum >= start && range.maximum <= end; + return isStartInside || isEndInside || isRangeInside; +} + +String trimmedText( + String text, + TextStyle labelStyle, + num labelsExtent, + int labelRotation, { + bool? isRtl, +}) { + String current = text; + num width = measureText(text, labelStyle, labelRotation).width; + if (width > labelsExtent) { + final int textLength = text.length; + if (isRtl ?? false) { + for (int i = 0; i < textLength - 1; i++) { + current = '...${text.substring(i + 1, textLength)}'; + width = measureText(current, labelStyle, labelRotation).width; + if (width <= labelsExtent) { + return current == '...' ? '' : current; + } + } + } else { + for (int i = textLength - 1; i >= 0; --i) { + current = '${text.substring(0, i)}...'; + width = measureText(current, labelStyle, labelRotation).width; + if (width <= labelsExtent) { + return current == '...' ? '' : current; + } + } + } + } + return current == '...' ? '' : current; +} + +double paddingFromSize(String? padding, double size) { + if (padding != null && padding.isNotEmpty) { + if (padding.contains('%')) { + return (size / 100) * num.tryParse(padding.replaceAll(RegExp('%'), ''))!; + } else if (padding.contains('px')) { + return double.parse(padding.replaceAll('px', '')); + } else { + return double.parse(padding); + } + } + return 0; +} + +Rect clipRect( + Rect bounds, + double animationFactor, { + bool isInversed = false, + bool isTransposed = false, +}) { + double left = bounds.left; + double top = bounds.top; + double width = bounds.width; + double height = bounds.height; + if (isTransposed) { + if (isInversed) { + height = bounds.height * animationFactor; + } else { + top = bounds.height * (1 - animationFactor); + } + } else { + if (isInversed) { + left = bounds.width * (1 - animationFactor); + } else { + width = bounds.width * animationFactor; + } + } + return Rect.fromLTWH(left, top, width, height); +} + +ShapeMarkerType toShapeMarkerType(DataMarkerType type) { + switch (type) { + case DataMarkerType.none: + case DataMarkerType.circle: + return ShapeMarkerType.circle; + case DataMarkerType.rectangle: + return ShapeMarkerType.rectangle; + case DataMarkerType.image: + return ShapeMarkerType.image; + case DataMarkerType.pentagon: + return ShapeMarkerType.pentagon; + case DataMarkerType.verticalLine: + return ShapeMarkerType.verticalLine; + case DataMarkerType.horizontalLine: + return ShapeMarkerType.horizontalLine; + case DataMarkerType.diamond: + return ShapeMarkerType.diamond; + case DataMarkerType.triangle: + return ShapeMarkerType.triangle; + case DataMarkerType.invertedTriangle: + return ShapeMarkerType.invertedTriangle; + } +} + +ShapeMarkerType toLegendShapeMarkerType( + LegendIconType iconType, + core.LegendItemProviderMixin provider, +) { + switch (iconType) { + case LegendIconType.seriesType: + return provider.effectiveLegendIconType(); + case LegendIconType.circle: + return ShapeMarkerType.circle; + case LegendIconType.rectangle: + return ShapeMarkerType.rectangle; + case LegendIconType.image: + return ShapeMarkerType.image; + case LegendIconType.pentagon: + return ShapeMarkerType.pentagon; + case LegendIconType.verticalLine: + return ShapeMarkerType.verticalLine; + case LegendIconType.horizontalLine: + return ShapeMarkerType.horizontalLine; + case LegendIconType.diamond: + return ShapeMarkerType.diamond; + case LegendIconType.triangle: + return ShapeMarkerType.triangle; + case LegendIconType.invertedTriangle: + return ShapeMarkerType.invertedTriangle; + } +} + +LegendIconType toLegendIconType(ShapeMarkerType iconType) { + switch (iconType) { + case ShapeMarkerType.circle: + return LegendIconType.circle; + case ShapeMarkerType.rectangle: + return LegendIconType.rectangle; + case ShapeMarkerType.image: + return LegendIconType.image; + case ShapeMarkerType.pentagon: + return LegendIconType.pentagon; + case ShapeMarkerType.verticalLine: + return LegendIconType.verticalLine; + case ShapeMarkerType.horizontalLine: + return LegendIconType.horizontalLine; + case ShapeMarkerType.diamond: + return LegendIconType.diamond; + case ShapeMarkerType.triangle: + return LegendIconType.triangle; + case ShapeMarkerType.invertedTriangle: + return LegendIconType.invertedTriangle; + // ignore: no_default_cases + default: + return LegendIconType.seriesType; + } +} + +String decimalLabelValue(num? value, [int? showDigits]) { + if (value != null && value.toString().split('.').length > 1) { + final String str = value.toString(); + final List list = str.split('.'); + value = double.parse(value.toStringAsFixed(showDigits ?? 3)); + value = + (list[1] == '0' || + list[1] == '00' || + list[1] == '000' || + list[1] == '0000' || + list[1] == '00000' || + list[1] == '000000' || + list[1] == '0000000') + ? value.round() + : value; + } + + return value == null ? '' : value.toString(); +} + +String formatNumericValue(num value, RenderChartAxis? axis, [int digits = 6]) { + final String text = value.toString(); + final List splitter = text.split('.'); + String fixedValue = text; + if (splitter.length > 1) { + fixedValue = value.toStringAsFixed(digits); + value = double.parse(fixedValue); + if (splitter[1] == '0' || + splitter[1] == '00' || + splitter[1] == '000' || + splitter[1] == '0000' || + splitter[1] == '00000' || + splitter[1] == '000000') { + value = value.round(); + } + fixedValue = value.toString(); + } + + NumberFormat? numberFormat; + String? labelFormat; + if (axis != null) { + if (axis is RenderNumericAxis) { + numberFormat = axis.numberFormat; + labelFormat = axis.labelFormat; + } else if (axis is RenderLogarithmicAxis) { + numberFormat = axis.numberFormat; + labelFormat = axis.labelFormat; + } + } + + String formattedText = fixedValue; + if (numberFormat != null) { + formattedText = numberFormat.format(value); + } + + if (labelFormat != null) { + formattedText = labelFormat.replaceAll(RegExp('{value}'), formattedText); + } + + return formattedText; +} + +String formatRTLText(String tooltipText) { + final List textCollection = tooltipText.split('\n'); + String resultantString = ''; + + for (final String text in textCollection) { + if (text.contains(':')) { + final List secondStringCollection = text.split(':'); + String string = ''; + for (int i = secondStringCollection.length - 1; i >= 0; i--) { + secondStringCollection[i] = secondStringCollection[i].trim(); + string += + (i == secondStringCollection.length - 1 + ? secondStringCollection[i] + : ' : ${secondStringCollection[i]}'); + } + resultantString += (resultantString.isEmpty ? '' : '\n') + string; + } else { + resultantString += (resultantString.isEmpty ? '' : '\n') + text; + } + } + + return resultantString; +} + +Widget? buildTooltipWidget( + BuildContext context, + TooltipInfo? info, + Size maxSize, + TooltipBehavior? tooltipBehavior, + SfChartThemeData chartThemeData, + ThemeData themeData, +) { + Widget? tooltip; + final TextStyle textStyle = chartThemeData.tooltipTextStyle!; + if (info is ChartTooltipInfo) { + if (tooltipBehavior != null) { + tooltip = tooltipBehavior.builder?.call( + info.data, + info.point, + info.series, + info.pointIndex, + info.seriesIndex, + ); + + if (tooltip == null) { + final bool hasMarker = + tooltipBehavior.canShowMarker && + info.markerColors.isNotEmpty && + info.markerColors.any((Color? color) => color != null); + + final TextStyle textStyle = chartThemeData.tooltipTextStyle!; + final TextStyle headerStyle = textStyle.copyWith( + fontWeight: FontWeight.bold, + ); + final String header = tooltipBehavior.header ?? info.header; + final Size headerSize = measureText(header, headerStyle); + final Size textSize = measureText(info.text!, textStyle); + + double headerAlignedSize = max(headerSize.width, textSize.width); + double dividerWidth = headerAlignedSize; + if (headerAlignedSize >= maxSize.width) { + headerAlignedSize = maxSize.width - tooltipInnerPadding.horizontal; + dividerWidth = headerAlignedSize; + } else { + dividerWidth += + tooltipBehavior.canShowMarker + ? tooltipMarkerSize + tooltipMarkerPadding.horizontal + : 0.0; + } + + final bool isLtr = Directionality.of(context) == TextDirection.ltr; + final bool hasHeader = header.isNotEmpty; + tooltip = Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (hasHeader) + SizedBox( + width: headerAlignedSize, + child: Center(child: Text(header, style: headerStyle)), + ), + if (hasHeader) + SizedBox( + width: dividerWidth, + child: Divider( + height: 10.0, + thickness: 0.5, + color: + tooltipBehavior.textStyle?.color ?? + chartThemeData.tooltipSeparatorColor, + ), + ), + if (info.text != null && info.text!.isNotEmpty) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (hasMarker) + Padding( + padding: tooltipItemSpacing, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: List.generate( + info.markerColors.length, + (int index) { + return Padding( + padding: tooltipMarkerPadding, + child: TooltipMarkerShapeRenderObject( + index: index, + colors: info.markerColors, + themeData: themeData, + image: info.renderer.markerSettings.image, + markerType: info.markerType, + series: info.renderer, + ), + ); + }, + ), + ), + ), + Text( + info.text!, + style: textStyle, + textAlign: isLtr ? TextAlign.left : TextAlign.right, + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ); + + tooltip = Padding(padding: tooltipInnerPadding, child: tooltip); + } + } + } else if (info != null && info.text != null && info.text!.isNotEmpty) { + // Axis label tooltip. + tooltip = Padding( + padding: tooltipInnerPadding, + child: Text(info.text!, style: textStyle), + ); + } + + return tooltip; +} + +class TooltipMarkerShapeRenderObject extends LeafRenderObjectWidget { + const TooltipMarkerShapeRenderObject({ + super.key, + required this.index, + required this.colors, + required this.themeData, + required this.image, + required this.markerType, + this.series, + }); + + final int index; + final List colors; + final ThemeData themeData; + final ImageProvider? image; + final DataMarkerType markerType; + final ChartSeriesRenderer? series; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderTooltipMarkerShape( + index: index, + colors: colors, + themeData: themeData, + image: image, + markerType: markerType, + )..series = series; + } + + @override + void updateRenderObject( + BuildContext context, + RenderTooltipMarkerShape renderObject, + ) { + renderObject + ..index = index + ..colors = colors + ..themeData = themeData + ..image = image + ..markerType = markerType + ..series = series; + } +} + +class RenderTooltipMarkerShape extends RenderBox { + RenderTooltipMarkerShape({ + required int index, + required List colors, + required ThemeData themeData, + required ImageProvider? image, + required DataMarkerType markerType, + }) { + _index = index; + _colors = colors; + _themeData = themeData; + _image = image; + _markerType = markerType; + _fetchMarkerImage(); + } + + int? get index => _index; + int? _index; + set index(int? value) { + if (_index != value) { + _index = value; + } + } + + List? get colors => _colors; + List? _colors; + set colors(List? value) { + if (_colors != value) { + _colors = value; + markNeedsPaint(); + } + } + + ThemeData? get themeData => _themeData; + ThemeData? _themeData; + set themeData(ThemeData? value) { + if (_themeData != value) { + _themeData = value; + markNeedsPaint(); + } + } + + ImageProvider? get image => _image; + ImageProvider? _image; + set image(ImageProvider? value) { + if (_image != value) { + _image = value; + _fetchMarkerImage(); + } + } + + DataMarkerType get markerType => _markerType; + DataMarkerType _markerType = DataMarkerType.circle; + set markerType(DataMarkerType value) { + if (_markerType != value) { + _markerType = value; + markNeedsPaint(); + } + } + + late ChartSeriesRenderer? series; + Image? _markerImage; + + void _fetchMarkerImage() { + if (markerType == DataMarkerType.image && image != null) { + fetchImage(image).then((Image? value) { + _markerImage = value; + markNeedsPaint(); + }); + } else { + _markerImage = null; + } + } + + @override + void performLayout() { + size = const Size(tooltipMarkerSize, tooltipMarkerSize); + } + + final Paint strokePaint = + Paint() + ..style = PaintingStyle.stroke + ..isAntiAlias = true + ..strokeWidth = 1.0; + final Paint fillPaint = + Paint() + ..style = PaintingStyle.fill + ..isAntiAlias = true; + + @override + void paint(PaintingContext context, Offset offset) { + if (index != null) { + final ColorScheme colorScheme = themeData!.colorScheme; + strokePaint.color = colorScheme.surface; + + fillPaint.color = colors![index!] ?? colorScheme.onSurface; + if (series != null) { + fillPaint.shader = (series! as CartesianSeriesRenderer) + .markerShader(offset & size); + } + + if (markerType == DataMarkerType.image) { + if (_markerImage != null) { + paintImage( + canvas: context.canvas, + rect: offset & size, + image: _markerImage!, + ); + } + } else { + _drawTooltipMarker( + context.canvas, + fillPaint, + strokePaint, + offset, + size, + markerType, + ); + } + } + } + + @override + void dispose() { + _markerImage = null; + fillPaint.shader?.dispose(); + super.dispose(); + } +} + +void _drawTooltipMarker( + Canvas canvas, + Paint fillPaint, + Paint strokePaint, + Offset point, + Size size, + DataMarkerType type, +) { + if (type != DataMarkerType.none) { + paint( + canvas: canvas, + rect: point & size, + shapeType: toShapeMarkerType(type), + paint: fillPaint, + borderPaint: strokePaint, + ); + } +} + +/// This method returns image from provider. +Future fetchImage(ImageProvider? imageProvider) async { + if (imageProvider == null) { + return null; + } + + final Completer completer = Completer(); + imageProvider + .resolve(ImageConfiguration.empty) + .addListener( + ImageStreamListener((ImageInfo info, bool _) { + completer.complete(info); + }), + ); + final ImageInfo imageInfo = await completer.future; + return imageInfo.image; +} + +extension ChartSeriesExtension on ChartSeriesRenderer { + String tooltipText(ChartPoint point) { + String result; + final int digits = parent!.tooltipBehavior!.decimalPlaces; + final String xValue = point.x.toString(); + final String yValue = decimalLabelValue(point.y, digits); + final bool isLtr = textDirection == TextDirection.ltr; + + if (parent!.tooltipBehavior!.format != null) { + final String resultantString = parent!.tooltipBehavior!.format! + .replaceAll('point.x', xValue) + .replaceAll('point.y', yValue) + .replaceAll('series.name', name); + result = isLtr ? resultantString : formatRTLText(resultantString); + } else { + result = isLtr ? '$xValue : $yValue' : '$yValue : $xValue'; + } + return result; + } +} + +extension CartesianSeriesExtension on CartesianSeriesRenderer { + String _formatTooltipLabel(num value, int digits, String text, bool isLtr) { + return isLtr + ? '$text : ${formatNumericValue(value, yAxis, digits)}' + : '${formatNumericValue(value, yAxis, digits)} : $text'; + } + + String _replace( + String tooltipText, + String replacingText, + num value, + int digits, + ) { + return tooltipText.replaceAll( + replacingText, + formatNumericValue(value, yAxis, digits), + ); + } + + String _formatTrackballLabel(num value, int digits, String text, bool isLtr) { + if (text.isEmpty) { + return formatNumericValue(value, yAxis, digits); + } + return isLtr + ? '$text: ${formatNumericValue(value, yAxis, digits)}' + : '${formatNumericValue(value, yAxis, digits)} :$text'; + } + + String tooltipText(CartesianChartPoint point, [int outlierIndex = -1]) { + if (xAxis == null || point.x == null) { + return ''; + } + + String text = ''; + if (parent == null || parent!.tooltipBehavior == null) { + return ''; + } + + final int digits = parent!.tooltipBehavior!.decimalPlaces; + final bool isLtr = textDirection == TextDirection.ltr; + final String? tooltipFormat = parent?.tooltipBehavior?.format; + if (tooltipFormat != null) { + text = tooltipHeaderText(point, digits); + String tooltipText = tooltipFormat.replaceAll('point.x', text); + + if (point.y != null) { + tooltipText = _replace(tooltipText, 'point.y', point.y!, digits); + } + + if (point.high != null) { + tooltipText = _replace(tooltipText, 'point.high', point.high!, digits); + } + + if (point.low != null) { + tooltipText = _replace(tooltipText, 'point.low', point.low!, digits); + } + + if (point.open != null) { + tooltipText = _replace(tooltipText, 'point.open', point.open!, digits); + } + + if (point.close != null) { + tooltipText = _replace( + tooltipText, + 'point.close', + point.close!, + digits, + ); + } + + if (outlierIndex != -1) { + if (point.outliers != null && point.outliers!.isNotEmpty) { + tooltipText = _replace( + tooltipText, + 'point.outliers', + point.outliers![outlierIndex], + digits, + ); + } + } else { + if (point.minimum != null) { + tooltipText = _replace( + tooltipText, + 'point.minimum', + point.minimum!, + digits, + ); + } + + if (point.maximum != null) { + tooltipText = _replace( + tooltipText, + 'point.maximum', + point.maximum!, + digits, + ); + } + + if (point.lowerQuartile != null) { + tooltipText = _replace( + tooltipText, + 'point.lowerQuartile', + point.lowerQuartile!, + digits, + ); + } + + if (point.upperQuartile != null) { + tooltipText = _replace( + tooltipText, + 'point.upperQuartile', + point.upperQuartile!, + digits, + ); + } + + if (point.mean != null) { + tooltipText = _replace( + tooltipText, + 'point.mean', + point.mean!, + digits, + ); + } + + if (point.median != null) { + tooltipText = _replace( + tooltipText, + 'point.median', + point.median!, + digits, + ); + } + } + + if (point.cumulative != null) { + tooltipText = _replace( + tooltipText, + 'point.cumulative', + point.cumulative!, + digits, + ); + } + + if (point.bubbleSize != null) { + tooltipText = _replace( + tooltipText, + 'point.size', + point.bubbleSize!, + digits, + ); + } + + tooltipText = tooltipText.replaceAll('series.name', name); + text = isLtr ? tooltipText : formatRTLText(tooltipText); + } else { + text = + parent!.tooltipBehavior!.shared + ? name + : tooltipHeaderText(point, digits); + + if (point.y != null) { + text = _formatTooltipLabel(point.y!, digits, text, isLtr); + } + + if (point.high != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTooltipLabel(point.high!, digits, 'High', isLtr); + } + + if (point.low != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.low!, digits, 'Low', isLtr); + } + + if (point.open != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTooltipLabel(point.open!, digits, 'Open', isLtr); + } + + if (point.close != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTooltipLabel(point.close!, digits, 'Close', isLtr); + } + + if (outlierIndex != -1) { + if (point.outliers != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel( + point.outliers![outlierIndex], + digits, + 'Outliers', + isLtr, + ); + } + } else { + if (point.minimum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.minimum!, digits, 'Minimum', isLtr); + } + + if (point.maximum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.maximum!, digits, 'Maximum', isLtr); + } + + if (point.median != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.median!, digits, 'Median', isLtr); + } + + if (point.mean != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.mean!, digits, 'Mean', isLtr); + } + + if (point.lowerQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel( + point.lowerQuartile!, + digits, + 'LQ', + isLtr, + ); + } + + if (point.upperQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel( + point.upperQuartile!, + digits, + 'HQ', + isLtr, + ); + } + } + } + + return text; + } + + String trackballText( + CartesianChartPoint point, + String seriesName, { + int outlierIndex = -1, + }) { + if (parent == null || + parent!.trackballBehavior == null || + xAxis == null || + point.x == null) { + return ''; + } + + String text = ''; + final int digits = parent!.trackballBehavior!.tooltipSettings.decimalPlaces; + if (parent!.trackballBehavior!.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) { + text = seriesName; + } + + final bool isLtr = textDirection == TextDirection.ltr; + final String? tooltipFormat = + parent!.trackballBehavior!.tooltipSettings.format; + if (tooltipFormat != null) { + text = tooltipHeaderText(point, digits); + String tooltipText = tooltipFormat.replaceAll('point.x', text); + + if (point.y != null) { + tooltipText = _replace(tooltipText, 'point.y', point.y!, digits); + } + + if (point.high != null) { + tooltipText = _replace(tooltipText, 'point.high', point.high!, digits); + } + + if (point.low != null) { + tooltipText = _replace(tooltipText, 'point.low', point.low!, digits); + } + + if (point.open != null) { + tooltipText = _replace(tooltipText, 'point.open', point.open!, digits); + } + + if (point.close != null) { + tooltipText = _replace( + tooltipText, + 'point.close', + point.close!, + digits, + ); + } + + if (outlierIndex != -1) { + if (point.outliers != null && point.outliers!.isNotEmpty) { + tooltipText = _replace( + tooltipText, + 'point.outliers', + point.outliers![outlierIndex], + digits, + ); + } + } else { + if (point.minimum != null) { + tooltipText = _replace( + tooltipText, + 'point.minimum', + point.minimum!, + digits, + ); + } + + if (point.maximum != null) { + tooltipText = _replace( + tooltipText, + 'point.maximum', + point.maximum!, + digits, + ); + } + + if (point.lowerQuartile != null) { + tooltipText = _replace( + tooltipText, + 'point.lowerQuartile', + point.lowerQuartile!, + digits, + ); + } + + if (point.upperQuartile != null) { + tooltipText = _replace( + tooltipText, + 'point.upperQuartile', + point.upperQuartile!, + digits, + ); + } + + if (point.mean != null) { + tooltipText = _replace( + tooltipText, + 'point.mean', + point.mean!, + digits, + ); + } + + if (point.median != null) { + tooltipText = _replace( + tooltipText, + 'point.median', + point.median!, + digits, + ); + } + } + + if (point.cumulative != null) { + tooltipText = _replace( + tooltipText, + 'point.cumulative', + point.cumulative!, + digits, + ); + } + + if (point.bubbleSize != null) { + tooltipText = _replace( + tooltipText, + 'point.size', + point.bubbleSize!, + digits, + ); + } + + tooltipText = tooltipText.replaceAll('series.name', seriesName); + text = isLtr ? tooltipText : formatRTLText(tooltipText); + } else { + if (point.y != null) { + text = _formatTrackballLabel(point.y!, digits, text, isLtr); + } + + if (point.high != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.high!, digits, 'High', isLtr); + } + + if (point.low != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.low!, digits, 'Low', isLtr); + } + + if (point.open != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.open!, digits, 'Open', isLtr); + } + + if (point.close != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.close!, digits, 'Close', isLtr); + } + + if (outlierIndex != -1) { + if (point.outliers != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.outliers![outlierIndex], + digits, + 'Outliers', + isLtr, + ); + } + } else { + if (point.minimum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.minimum!, + digits, + 'Minimum', + isLtr, + ); + } + + if (point.maximum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.maximum!, + digits, + 'Maximum', + isLtr, + ); + } + + if (point.median != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.median!, digits, 'Median', isLtr); + } + + if (point.mean != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.mean!, digits, 'Mean', isLtr); + } + + if (point.lowerQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.lowerQuartile!, + digits, + 'LowerQuartile', + isLtr, + ); + } + + if (point.upperQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.upperQuartile!, + digits, + 'UpperQuartile', + isLtr, + ); + } + } + } + + return text; + } + + String tooltipHeaderText(CartesianChartPoint point, [int digits = 3]) { + String text = ''; + if (xAxis is RenderNumericAxis || xAxis is RenderLogarithmicAxis) { + text = formatNumericValue(point.x! as num, xAxis, digits); + } else if (xAxis is RenderDateTimeAxis) { + final RenderDateTimeAxis axis = xAxis! as RenderDateTimeAxis; + final DateFormat dateFormat = + axis.dateFormat ?? _dateTimeLabelFormat(xAxis!); + text = dateFormat.format(point.x! as DateTime); + } else if (xAxis is RenderDateTimeCategoryAxis) { + final RenderDateTimeCategoryAxis axis = + xAxis! as RenderDateTimeCategoryAxis; + final DateFormat dateFormat = + axis.dateFormat ?? _dateTimeLabelFormat(xAxis!); + text = dateFormat.format(point.x! as DateTime); + } else if (xAxis is RenderCategoryAxis) { + text = point.x!.toString(); + } + + return text; + } + + DateFormat _dateTimeLabelFormat( + RenderChartAxis axis, [ + int? interval, + int? prevInterval, + ]) { + DateFormat? format; + final bool notDoubleInterval = + (axis.interval != null && axis.interval! % 1 == 0) || + axis.interval == null; + DateTimeIntervalType? actualIntervalType; + num? minimum; + minimum = axis.visibleRange!.minimum; + if (axis is RenderDateTimeAxis) { + actualIntervalType = axis.visibleIntervalType; + } else if (axis is RenderDateTimeCategoryAxis) { + actualIntervalType = axis.visibleIntervalType; + } + + switch (actualIntervalType!) { + case DateTimeIntervalType.years: + format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); + break; + case DateTimeIntervalType.months: + format = + (minimum == interval || interval == prevInterval) + ? _firstLabelFormat(actualIntervalType) + : _dateTimeFormat(actualIntervalType, interval, prevInterval); + + break; + case DateTimeIntervalType.days: + format = + (minimum == interval || interval == prevInterval) + ? _firstLabelFormat(actualIntervalType) + : _dateTimeFormat(actualIntervalType, interval, prevInterval); + break; + case DateTimeIntervalType.hours: + format = DateFormat.j(); + break; + case DateTimeIntervalType.minutes: + format = DateFormat.Hm(); + break; + case DateTimeIntervalType.seconds: + format = DateFormat.ms(); + break; + case DateTimeIntervalType.milliseconds: + final DateFormat dateFormat = DateFormat('ss.SSS'); + format = dateFormat; + break; + case DateTimeIntervalType.auto: + format ??= DateFormat(); + break; + } + return format!; + } + + DateFormat? _dateTimeFormat( + DateTimeIntervalType? actualIntervalType, + int? interval, + int? prevInterval, + ) { + final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); + final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); + DateFormat? format; + final bool isIntervalDecimal = interval % 1 == 0; + if (actualIntervalType == DateTimeIntervalType.months) { + format = + minimum.year == maximum.year + ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) + : DateFormat('yyy MMM'); + } else if (actualIntervalType == DateTimeIntervalType.days) { + format = + minimum.month != maximum.month + ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) + : DateFormat.d(); + } + + return format; + } + + DateFormat? _firstLabelFormat(DateTimeIntervalType? actualIntervalType) { + DateFormat? format; + + if (actualIntervalType == DateTimeIntervalType.months) { + format = DateFormat('yyy MMM'); + } else if (actualIntervalType == DateTimeIntervalType.days) { + format = DateFormat.MMMd(); + } else if (actualIntervalType == DateTimeIntervalType.minutes) { + format = DateFormat.Hm(); + } + + return format; + } + + Offset translateTransform( + num x, + num y, [ + double translationX = 0, + double translationY = 0, + ]) { + final double posX = pointToPixelX(x, y); + final double posY = pointToPixelY(x, y); + return Offset(posX + translationX, posY + translationY); + } + + ChartMarker markerAt(int pointIndex) { + if (markerContainer != null) { + return markerContainer!.markerAt(pointIndex); + } + return ChartMarker(); + } + + Shader? markerShader(Rect bounds) { + if (onCreateShader != null) { + final ShaderDetails details = ShaderDetails(bounds, 'marker'); + return onCreateShader!(details); + } else if (gradient != null) { + return gradient!.createShader(bounds); + } + return null; + } +} + +extension IndicatorExtension on IndicatorRenderer { + String _replace( + String tooltipText, + String replacingText, + num value, + int digits, + ) { + return tooltipText.replaceAll( + replacingText, + formatNumericValue(value, yAxis, digits), + ); + } + + String _formatTrackballLabel(num value, int digits, String text, bool isLtr) { + if (text.isEmpty) { + return formatNumericValue(value, yAxis, digits); + } + return isLtr + ? '$text: ${formatNumericValue(value, yAxis, digits)}' + : '${formatNumericValue(value, yAxis, digits)} :$text'; + } + + String trackballText( + CartesianChartPoint point, + String seriesName, { + int outlierIndex = -1, + }) { + if (parent == null || + parent!.trackballBehavior == null || + xAxis == null || + point.x == null) { + return ''; + } + + String text = ''; + final int digits = parent!.trackballBehavior!.tooltipSettings.decimalPlaces; + if (parent!.trackballBehavior!.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) { + text = '$seriesName '; + } + + return _behaviorText( + parent!.trackballBehavior!.tooltipSettings.format, + text, + point, + digits, + outlierIndex, + seriesName, + parent!.textDirection == TextDirection.ltr, + ); + } + + String _behaviorText( + String? tooltipFormat, + String text, + CartesianChartPoint point, + int digits, + int outlierIndex, + String seriesName, + bool isLtr, + ) { + if (tooltipFormat != null) { + text = tooltipHeaderText(point, digits); + String tooltipText = tooltipFormat.replaceAll('point.x', text); + + if (point.y != null) { + tooltipText = _replace(tooltipText, 'point.y', point.y!, digits); + } + + if (point.high != null) { + tooltipText = _replace(tooltipText, 'point.high', point.high!, digits); + } + + if (point.low != null) { + tooltipText = _replace(tooltipText, 'point.low', point.low!, digits); + } + + if (point.open != null) { + tooltipText = _replace(tooltipText, 'point.open', point.open!, digits); + } + + if (point.close != null) { + tooltipText = _replace( + tooltipText, + 'point.close', + point.close!, + digits, + ); + } + + if (outlierIndex != -1) { + if (point.outliers != null && point.outliers!.isNotEmpty) { + tooltipText = _replace( + tooltipText, + 'point.outliers', + point.outliers![outlierIndex], + digits, + ); + } + } else { + if (point.minimum != null) { + tooltipText = _replace( + tooltipText, + 'point.minimum', + point.minimum!, + digits, + ); + } + + if (point.maximum != null) { + tooltipText = _replace( + tooltipText, + 'point.maximum', + point.maximum!, + digits, + ); + } + + if (point.lowerQuartile != null) { + tooltipText = _replace( + tooltipText, + 'point.lowerQuartile', + point.lowerQuartile!, + digits, + ); + } + + if (point.upperQuartile != null) { + tooltipText = _replace( + tooltipText, + 'point.upperQuartile', + point.upperQuartile!, + digits, + ); + } + + if (point.mean != null) { + tooltipText = _replace( + tooltipText, + 'point.mean', + point.mean!, + digits, + ); + } + + if (point.median != null) { + tooltipText = _replace( + tooltipText, + 'point.median', + point.median!, + digits, + ); + } + } + + if (point.cumulative != null) { + tooltipText = _replace( + tooltipText, + 'point.cumulative', + point.cumulative!, + digits, + ); + } + + if (point.bubbleSize != null) { + tooltipText = _replace( + tooltipText, + 'point.size', + point.bubbleSize!, + digits, + ); + } + + tooltipText = tooltipText.replaceAll('series.name', seriesName); + text = isLtr ? tooltipText : formatRTLText(tooltipText); + } else { + if (point.y != null) { + text = _formatTrackballLabel(point.y!, digits, text, isLtr); + } + + if (point.high != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.high!, digits, 'High', isLtr); + } + + if (point.low != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.low!, digits, 'Low', isLtr); + } + + if (point.open != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.open!, digits, 'Open', isLtr); + } + + if (point.close != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.close!, digits, 'Close', isLtr); + } + + if (outlierIndex != -1) { + if (point.outliers != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.outliers![outlierIndex], + digits, + 'Outliers', + isLtr, + ); + } + } else { + if (point.minimum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.minimum!, + digits, + 'Minimum', + isLtr, + ); + } + + if (point.maximum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.maximum!, + digits, + 'Maximum', + isLtr, + ); + } + + if (point.median != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.median!, digits, 'Median', isLtr); + } + + if (point.mean != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.mean!, digits, 'Mean', isLtr); + } + + if (point.lowerQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.lowerQuartile!, + digits, + 'LowerQuartile', + isLtr, + ); + } + + if (point.upperQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.upperQuartile!, + digits, + 'UpperQuartile', + isLtr, + ); + } + } + } + return text; + } + + String tooltipHeaderText(CartesianChartPoint point, [int digits = 3]) { + String text = ''; + if (xAxis is RenderNumericAxis || xAxis is RenderLogarithmicAxis) { + text = formatNumericValue(point.x! as num, xAxis, digits); + } else if (xAxis is RenderDateTimeAxis) { + final RenderDateTimeAxis axis = xAxis! as RenderDateTimeAxis; + final DateFormat dateFormat = + axis.dateFormat ?? _dateTimeLabelFormat(xAxis!); + text = dateFormat.format(point.x! as DateTime); + } else if (xAxis is RenderDateTimeCategoryAxis) { + final RenderDateTimeCategoryAxis axis = + xAxis! as RenderDateTimeCategoryAxis; + final DateFormat dateFormat = + axis.dateFormat ?? _dateTimeLabelFormat(xAxis!); + text = dateFormat.format(point.x! as DateTime); + } else if (xAxis is RenderCategoryAxis) { + text = point.x!.toString(); + } + + return text; + } + + DateFormat _dateTimeLabelFormat( + RenderChartAxis axis, [ + int? interval, + int? prevInterval, + ]) { + DateFormat? format; + final bool notDoubleInterval = + (axis.interval != null && axis.interval! % 1 == 0) || + axis.interval == null; + DateTimeIntervalType? actualIntervalType; + num? minimum; + minimum = axis.visibleRange!.minimum; + if (axis is RenderDateTimeAxis) { + actualIntervalType = axis.visibleIntervalType; + } else if (axis is RenderDateTimeCategoryAxis) { + actualIntervalType = axis.visibleIntervalType; + } + + switch (actualIntervalType!) { + case DateTimeIntervalType.years: + format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); + break; + case DateTimeIntervalType.months: + format = + (minimum == interval || interval == prevInterval) + ? _firstLabelFormat(actualIntervalType) + : _dateTimeFormat(actualIntervalType, interval, prevInterval); + + break; + case DateTimeIntervalType.days: + format = + (minimum == interval || interval == prevInterval) + ? _firstLabelFormat(actualIntervalType) + : _dateTimeFormat(actualIntervalType, interval, prevInterval); + break; + case DateTimeIntervalType.hours: + format = DateFormat.j(); + break; + case DateTimeIntervalType.minutes: + format = DateFormat.Hm(); + break; + case DateTimeIntervalType.seconds: + format = DateFormat.ms(); + break; + case DateTimeIntervalType.milliseconds: + final DateFormat dateFormat = DateFormat('ss.SSS'); + format = dateFormat; + break; + case DateTimeIntervalType.auto: + format ??= DateFormat(); + break; + } + return format!; + } + + DateFormat? _dateTimeFormat( + DateTimeIntervalType? actualIntervalType, + int? interval, + int? prevInterval, + ) { + final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); + final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); + DateFormat? format; + final bool isIntervalDecimal = interval % 1 == 0; + if (actualIntervalType == DateTimeIntervalType.months) { + format = + minimum.year == maximum.year + ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) + : DateFormat('yyy MMM'); + } else if (actualIntervalType == DateTimeIntervalType.days) { + format = + minimum.month != maximum.month + ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) + : DateFormat.d(); + } + + return format; + } + + DateFormat? _firstLabelFormat(DateTimeIntervalType? actualIntervalType) { + DateFormat? format; + + if (actualIntervalType == DateTimeIntervalType.months) { + format = DateFormat('yyy MMM'); + } else if (actualIntervalType == DateTimeIntervalType.days) { + format = DateFormat.MMMd(); + } else if (actualIntervalType == DateTimeIntervalType.minutes) { + format = DateFormat.Hm(); + } + + return format; + } +} + +int nextIndexConsideringEmptyPointMode( + int index, + EmptyPointMode mode, + List yValues, + int dataCount, +) { + return mode == EmptyPointMode.drop + ? _nextValidIndex(index, dataCount, yValues) + : _nextIndex(index, dataCount); +} + +int _nextIndex(int index, int dataCount) { + return index + 1 < dataCount ? index + 1 : -1; +} + +int _nextValidIndex(int index, int dataCount, List yValues) { + final int lastIndex = dataCount - 1; + if (index < lastIndex) { + final num y = yValues[index]; + int nextIndex = index + 1; + if (y.isNaN) { + return nextIndex; + } else { + while (yValues[nextIndex].isNaN) { + nextIndex++; + if (nextIndex > lastIndex) { + return -1; + } + } + return nextIndex; + } + } + + return -1; +} + +RRect toRRect( + double left, + double top, + double right, + double bottom, + BorderRadius borderRadius, +) { + if (top > bottom) { + final double temp = top; + top = bottom; + bottom = temp; + } + + if (left > right) { + final double temp = left; + left = right; + right = temp; + } + + return RRect.fromLTRBAndCorners( + left, + top, + right, + bottom, + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + ); +} + +extension OffsetExtension on Offset { + bool get isNaN => dx.isNaN || dy.isNaN; + + Offset? lerp(Offset b, double t, num visibleMin) { + final Offset a = this; + return Offset( + _lerpDouble(a.dx, b.dx, t, visibleMin), + _lerpDouble(a.dy, b.dy, t, visibleMin), + ); + } +} + +double _lerpDouble(num a, double b, double t, num visibleMin) { + if (a.isNaN && !b.isNaN) { + a = visibleMin; + } + return a * (1.0 - t) + b * t; +} + +core.LegendAlignment effectiveLegendAlignment(ChartAlignment? alignment) { + alignment ??= ChartAlignment.center; + switch (alignment) { + case ChartAlignment.near: + return core.LegendAlignment.near; + case ChartAlignment.center: + return core.LegendAlignment.center; + case ChartAlignment.far: + return core.LegendAlignment.far; + } +} + +core.LegendOverflowMode effectiveOverflowMode(Legend legend) { + switch (legend.overflowMode) { + case LegendItemOverflowMode.wrap: + return core.LegendOverflowMode.wrapScroll; + case LegendItemOverflowMode.scroll: + return core.LegendOverflowMode.scroll; + case LegendItemOverflowMode.none: + return core.LegendOverflowMode.none; + } +} + +double percentageToWidthFactor(String? value, core.LegendPosition position) { + switch (position) { + case core.LegendPosition.left: + case core.LegendPosition.right: + return value == null ? defaultLegendSizeFactor : factorFromValue(value); + + case core.LegendPosition.top: + case core.LegendPosition.bottom: + return value == null ? double.nan : factorFromValue(value); + } +} + +double percentageToHeightFactor(String? value, core.LegendPosition position) { + switch (position) { + case core.LegendPosition.left: + case core.LegendPosition.right: + return value == null ? double.nan : factorFromValue(value); + + case core.LegendPosition.top: + case core.LegendPosition.bottom: + return value == null ? defaultLegendSizeFactor : factorFromValue(value); + } +} + +core.LegendScrollbarVisibility effectiveScrollbarVisibility(Legend legend) { + return legend.shouldAlwaysShowScrollbar + ? core.LegendScrollbarVisibility.visible + : core.LegendScrollbarVisibility.auto; +} + +core.LegendPosition effectiveLegendPosition(Legend legend) { + switch (legend.position) { + case LegendPosition.auto: + return defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS + ? core.LegendPosition.top + : core.LegendPosition.right; + case LegendPosition.bottom: + return core.LegendPosition.bottom; + case LegendPosition.left: + return core.LegendPosition.left; + case LegendPosition.right: + return core.LegendPosition.right; + case LegendPosition.top: + return core.LegendPosition.top; + } +} + +Axis effectiveLegendOrientation(core.LegendPosition position, Legend legend) { + switch (legend.orientation) { + case LegendItemOrientation.horizontal: + return Axis.horizontal; + case LegendItemOrientation.vertical: + return Axis.vertical; + case LegendItemOrientation.auto: + return position == core.LegendPosition.top || + position == core.LegendPosition.bottom + ? Axis.horizontal + : Axis.vertical; + } +} + +Widget? buildLegendTitle(SfChartThemeData? chartThemeData, Legend legend) { + if (legend.title != null && + legend.title!.text != null && + legend.title!.text!.isNotEmpty) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: Text( + legend.title!.text!, + style: chartThemeData!.legendTitleTextStyle, + ), + ); + } + return null; +} + +Widget buildLegendItem( + BuildContext context, + core.LegendItem legendItem, + Legend legend, +) { + ChartPoint point; + if (legendItem.series != null) { + final int length = legendItem.series!.chartPoints.length; + final int pointIndex = legendItem.pointIndex; + if (length > 0 && pointIndex > -1 && pointIndex < length) { + point = legendItem.series!.chartPoints[legendItem.pointIndex]; + } else { + point = ChartPoint(x: legendItem.text); + } + } else { + point = ChartPoint(x: legendItem.text); + } + + if (legendItem.series is! CartesianSeriesRenderer && + legendItem.series!.segments.isNotEmpty && + legendItem.pointIndex < legendItem.series!.segments.length) { + point.isVisible = + legendItem.series!.segmentAt(legendItem.pointIndex).isVisible; + } + + return legend.legendItemBuilder!( + legendItem.text, + legendItem.series?.widget, + point, + legendItem.series is CartesianSeriesRenderer + ? legendItem.seriesIndex + : legendItem.pointIndex, + ); +} + +mixin Stacking100SeriesMixin {} + +bool isValueLinear(int index, num value, List values) { + final int length = values.length; + if (length == 0) { + return true; + } + + if (index == 0) { + return length == 1 || value <= values[index + 1]; + } + + if (index == length - 1) { + return value >= values[index - 1]; + } + + return value >= values[index - 1] && value <= values[index + 1]; +} + +DateFormat dateTimeAxisLabelFormat( + RenderDateTimeAxis axis, + num current, + int previous, +) { + return _niceDateFormat( + current, + previous, + axis.visibleRange!.minimum, + axis.interval, + axis.visibleInterval, + axis.visibleIntervalType, + ); +} + +DateFormat dateTimeCategoryAxisLabelFormat( + RenderDateTimeCategoryAxis axis, + num current, + int previous, +) { + return _niceDateFormat( + current, + previous, + axis.visibleRange!.minimum, + axis.interval, + axis.visibleInterval, + axis.visibleIntervalType, + ); +} + +DateFormat _niceDateFormat( + num current, + int previous, + num minimum, + double? interval, + num visibleInterval, + DateTimeIntervalType intervalType, +) { + final bool notDoubleInterval = + (interval != null && interval % 1 == 0) || interval == null; + switch (intervalType) { + case DateTimeIntervalType.years: + return notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); + + case DateTimeIntervalType.months: + return (minimum == current || current == previous) + ? _firstLabelFormat(intervalType) + : _normalDateFormat(intervalType, visibleInterval, current, previous); + + case DateTimeIntervalType.days: + return (minimum == current || current == previous) + ? _firstLabelFormat(intervalType) + : _normalDateFormat(intervalType, visibleInterval, current, previous); + + case DateTimeIntervalType.hours: + return DateFormat.j(); + + case DateTimeIntervalType.minutes: + return DateFormat.Hm(); + + case DateTimeIntervalType.seconds: + return DateFormat.ms(); + + case DateTimeIntervalType.milliseconds: + return DateFormat('ss.SSS'); + + case DateTimeIntervalType.auto: + return DateFormat(); + } +} + +DateFormat _firstLabelFormat(DateTimeIntervalType visibleIntervalType) { + if (visibleIntervalType == DateTimeIntervalType.months) { + return DateFormat('yyy MMM'); + } else if (visibleIntervalType == DateTimeIntervalType.days) { + return DateFormat.MMMd(); + } else if (visibleIntervalType == DateTimeIntervalType.minutes) { + return DateFormat.Hm(); + } else { + return DateFormat(); + } +} + +DateFormat _normalDateFormat( + DateTimeIntervalType visibleIntervalType, + num visibleInterval, + num current, + int previousLabel, +) { + final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(current.toInt()); + final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(previousLabel); + final bool isIntervalDecimal = visibleInterval % 1 == 0; + if (visibleIntervalType == DateTimeIntervalType.months) { + return minimum.year == maximum.year + ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) + : DateFormat('yyy MMM'); + } else if (visibleIntervalType == DateTimeIntervalType.days) { + return minimum.month != maximum.month + ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) + : DateFormat.d(); + } else { + return DateFormat(); + } +} + +String numericAxisLabel(RenderNumericAxis axis, num value, int showDigits) { + return _labelValue(value, showDigits, axis.numberFormat, axis.labelFormat); +} + +String logAxisLabel(RenderLogarithmicAxis axis, num value, int showDigits) { + return _labelValue(value, showDigits, axis.numberFormat, axis.labelFormat); +} + +String _labelValue( + num value, + int showDigits, + NumberFormat? numberFormat, + String? labelFormat, +) { + final List pieces = value.toString().split('.'); + if (pieces.length > 1) { + value = double.parse(value.toStringAsFixed(showDigits)); + final String decimals = pieces[1]; + final bool isDecimalContainsZero = + decimals == '0' || + decimals == '00' || + decimals == '000' || + decimals == '0000' || + decimals == '00000' || + value % 1 == 0; + value = isDecimalContainsZero ? value.round() : value; + } + + String text = value.toString(); + if (numberFormat != null) { + text = numberFormat.format(value); + } + if (labelFormat != null && labelFormat != '') { + text = labelFormat.replaceAll(RegExp('{value}'), text); + } + return text; +} + +RRect performLegendToggleAnimation( + SbsSeriesMixin series, + RRect segmentRect, + RRect oldSegmentRect, + BorderRadius borderRadius, +) { + final double animationFactor = series.segmentAnimationFactor; + final bool oldSeriesVisible = series.visibilityBeforeTogglingLegend; + + if (series.parent!.isTransposed) { + return performTransposedLegendToggleAnimation( + series, + segmentRect, + oldSegmentRect, + oldSeriesVisible, + animationFactor, + borderRadius, + ); + } + + final RenderCartesianChartPlotArea plotArea = series.parent!; + final CartesianSeriesRenderer firstSeries = + plotArea.firstChild! as CartesianSeriesRenderer; + final CartesianSeriesRenderer lastSeries = + plotArea.lastChild! as CartesianSeriesRenderer; + + final bool isSingleBarSeries = _isSingleBarSeries(plotArea); + num right = 0; + final double height = segmentRect.height; + double width = segmentRect.width; + double left = segmentRect.left; + final double top = segmentRect.top; + + /// Left and right animation handled when toggling the legend. + if (oldSeriesVisible) { + final double oldRight = oldSegmentRect.right; + final double oldLeft = oldSegmentRect.left; + final double newRight = segmentRect.right; + final double newLeft = segmentRect.left; + + right = + oldRight > newRight + ? oldRight + (animationFactor * (newRight - oldRight)) + : oldRight - (animationFactor * (oldRight - newRight)); + left = + oldLeft > newLeft + ? oldLeft - (animationFactor * (oldLeft - newLeft)) + : oldLeft + (animationFactor * (newLeft - oldLeft)); + width = right - left; + } else { + final bool isInversed = series.xAxis!.isInversed; + if (series == firstSeries && !isSingleBarSeries) { + /// Handled the left to right side animation when re-toggling the first series legend. + if (isInversed) { + right = segmentRect.right; + left = right - (segmentRect.width * animationFactor); + width = right - left; + } else { + left = segmentRect.left; + width = segmentRect.width * animationFactor; + } + } else if (series == lastSeries && !isSingleBarSeries) { + /// Handled the right to left side animation when re-toggling the last series legend. + if (isInversed) { + left = segmentRect.left; + width = segmentRect.width * animationFactor; + } else { + right = segmentRect.right; + left = right - (segmentRect.width * animationFactor); + width = right - left; + } + } else { + /// Handled width animation when re-toggling middle series legend. + width = segmentRect.width * animationFactor; + left = segmentRect.center.dx - width / 2; + } + } + + return RRect.fromRectAndCorners( + Rect.fromLTWH(left, top, width, height), + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + ); +} + +RRect performTransposedLegendToggleAnimation( + SbsSeriesMixin series, + RRect segmentRect, + RRect oldSegmentRect, + bool oldSeriesVisible, + double animationFactor, + BorderRadius borderRadius, +) { + final RenderCartesianChartPlotArea plotArea = series.parent!; + final CartesianSeriesRenderer firstSeries = + plotArea.firstChild! as CartesianSeriesRenderer; + final CartesianSeriesRenderer lastSeries = + plotArea.lastChild! as CartesianSeriesRenderer; + + final bool isSingleBarSeries = _isSingleBarSeries(plotArea); + num bottom; + double height = segmentRect.height; + double top = segmentRect.top; + final double width = segmentRect.width; + final double left = segmentRect.left; + + /// Handled top and bottom animation when toggling the legend. + if (oldSeriesVisible) { + final double oldBottom = oldSegmentRect.bottom; + final double oldTop = oldSegmentRect.top; + final double newBottom = segmentRect.bottom; + final double newTop = segmentRect.top; + + bottom = + oldBottom > newBottom + ? oldBottom + (animationFactor * (newBottom - oldBottom)) + : oldBottom - (animationFactor * (oldBottom - newBottom)); + top = + oldTop > newTop + ? oldTop - (animationFactor * (oldTop - newTop)) + : oldTop + (animationFactor * (newTop - oldTop)); + height = bottom - top; + } else { + final bool isInversed = series.xAxis!.isInversed; + if (series == firstSeries && !isSingleBarSeries) { + /// Handled the bottom to top side animation when re-toggling the first series legend. + if (isInversed) { + top = segmentRect.top; + height = segmentRect.height * animationFactor; + } else { + bottom = segmentRect.bottom; + top = bottom - (segmentRect.height * animationFactor); + height = bottom - top; + } + } else if (series == lastSeries && !isSingleBarSeries) { + /// Handled the top to bottom side animation when re-toggling the last series legend. + if (isInversed) { + bottom = segmentRect.bottom; + top = bottom - (segmentRect.height * animationFactor); + height = bottom - top; + } else { + top = segmentRect.top; + height = segmentRect.height * animationFactor; + } + } else { + /// Handled height animation when re-toggling middle series legend. + height = segmentRect.height * animationFactor; + top = segmentRect.center.dy - height / 2; + } + } + + return RRect.fromRectAndCorners( + Rect.fromLTWH(left, top, width, height), + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + ); +} + +bool _isSingleBarSeries(RenderCartesianChartPlotArea plotArea) { + int count = 0; + plotArea.visitChildren((child) { + if (child is SbsSeriesMixin && child.controller.isVisible) { + count++; + } + }); + return count == 1; +} + +void animateAllBarSeries(RenderCartesianChartPlotArea plotArea) { + plotArea.isLegendToggled = true; + plotArea.visitChildren((child) { + if (child is CartesianSeriesRenderer) { + if ((child is ColumnSeriesRenderer || child is BarSeriesRenderer) && + child.animationType == AnimationType.none) { + child.animationType = AnimationType.realtime; + } + } + }); +} + +int binarySearch(List xValues, double touchValue, int min, int max) { + int closerIndex = 0; + double closerDelta = double.maxFinite; + while (min <= max) { + final int mid = (min + max) ~/ 2; + final double xValue = xValues[mid].toDouble(); + final double delta = (touchValue - xValue).abs(); + if (delta < closerDelta) { + closerDelta = delta; + closerIndex = mid; + } + + if (touchValue == xValue) { + return mid; + } else if (touchValue < xValue) { + max = mid - 1; + } else { + min = mid + 1; + } + } + return closerIndex; +} + +Rect tooltipTouchBounds(Offset center, double width, double height) { + // The Rect.fromCenter() method divides the width and height by 2. + // For smooth touch interaction, keep a 10-pixel padding for touch + // and a 4-pixel padding for mouse on all sides. + if (isHover) { + width = width < 8 ? 8 : width; + height = height < 8 ? 8 : height; + } else { + width = width < 20 ? 20 : width; + height = height < 20 ? 20 : height; + } + + return Rect.fromCenter(center: center, width: width, height: height); +} + +int drawIndex(int pointIndex, List drawIndexes) { + final int length = drawIndexes.length; + for (int i = 0; i < length; i++) { + if (drawIndexes[i] == pointIndex) { + return i; + } + } + return -1; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart new file mode 100644 index 000000000..eb5107509 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart @@ -0,0 +1,179 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'enum.dart'; +import 'helper.dart'; + +Offset calculateExplodingCenter( + double midAngle, + double currentRadius, + Offset center, + String explodeOffset, +) { + final double explodeCenter = percentToValue(explodeOffset, currentRadius)!; + return calculateOffset(midAngle, explodeCenter, center); +} + +/// Find the deviation angle. +num findAngleDeviation(num innerRadius, num outerRadius, num totalAngle) { + final num midRadius = (innerRadius + outerRadius) / 2; + final num circumference = 2 * pi * midRadius; + final num rimSize = (innerRadius - outerRadius).abs(); + final num deviation = ((rimSize / 2) / circumference) * 100; + return (deviation * 360) / 100; +} + +/// Calculate arc path for circular series segment. +Path calculateArcPath( + double innerRadius, + double radius, + Offset center, + double startAngle, + double endAngle, + double degree, { + bool isAnimate = false, +}) { + final Path path = Path(); + final double startRadian = degreesToRadians(startAngle); + final double endRadian = degreesToRadians(endAngle); + final double sweepRadian = degreesToRadians(degree); + + if (isAnimate) { + final Offset innerRadiusStartPoint = Offset( + innerRadius * cos(startRadian) + center.dx, + innerRadius * sin(startRadian) + center.dy, + ); + path.moveTo(innerRadiusStartPoint.dx, innerRadiusStartPoint.dy); + } + + final Offset radiusStartPoint = Offset( + radius * cos(startRadian) + center.dx, + radius * sin(startRadian) + center.dy, + ); + + // Check if the angle between startAngle and endAngle is equal to + // a full circle (2 * pi radians) by rounding both values to 5 decimal + // places and comparing them to avoid precision errors. + final bool isFullCircle = + (endRadian - startRadian).toStringAsFixed(5) == + (2 * pi).toStringAsFixed(5); + final double midAngle = (endRadian + startRadian) / 2; + + if (isFullCircle) { + path.arcTo( + Rect.fromCircle(center: center, radius: radius), + startRadian, + midAngle - startRadian, + true, + ); + path.arcTo( + Rect.fromCircle(center: center, radius: radius), + midAngle, + endRadian - midAngle, + true, + ); + } else { + path.lineTo(radiusStartPoint.dx, radiusStartPoint.dy); + path.arcTo( + Rect.fromCircle(center: center, radius: radius), + startRadian, + sweepRadian, + true, + ); + } + + if (isFullCircle) { + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + endRadian, + midAngle - endRadian, + true, + ); + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + midAngle, + startRadian - midAngle, + true, + ); + } else { + final Offset innerRadiusEndPoint = Offset( + innerRadius * cos(endRadian) + center.dx, + innerRadius * sin(endRadian) + center.dy, + ); + + path.lineTo(innerRadiusEndPoint.dx, innerRadiusEndPoint.dy); + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + endRadian, + startRadian - endRadian, + true, + ); + path.lineTo(radiusStartPoint.dx, radiusStartPoint.dy); + } + + return path; +} + +/// Calculate series start or end angle based on animation type. +double calculateAngle(bool isRealTimeAnimation, int startAngle, int endAngle) { + // Segment animation + if (isRealTimeAnimation) { + final int finalEndAngle = + startAngle == endAngle ? 360 + endAngle : endAngle; + return finalEndAngle - 90; + } + // Series animation + return startAngle - 90; +} + +/// Calculate rounded corners arc path. +Path calculateRoundedCornerArcPath( + CornerStyle cornerStyle, + double innerRadius, + double outerRadius, + Offset center, + double startAngle, + double endAngle, +) { + final Path path = Path(); + + if (cornerStyle == CornerStyle.startCurve || + cornerStyle == CornerStyle.bothCurve) { + final Offset startPoint = calculateOffset(startAngle, innerRadius, center); + final Offset endPoint = calculateOffset(startAngle, outerRadius, center); + + path.moveTo(startPoint.dx, startPoint.dy); + path.arcToPoint( + endPoint, + radius: Radius.circular((innerRadius - outerRadius).abs() / 2), + ); + } + + path.addArc( + Rect.fromCircle(center: center, radius: outerRadius), + degreesToRadians(startAngle), + degreesToRadians(endAngle - startAngle), + ); + + if (cornerStyle == CornerStyle.endCurve || + cornerStyle == CornerStyle.bothCurve) { + final Offset endPoint = calculateOffset(endAngle, innerRadius, center); + path.arcToPoint( + endPoint, + radius: Radius.circular((innerRadius - outerRadius).abs() / 2), + ); + } + + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + degreesToRadians(endAngle), + degreesToRadians(startAngle) - degreesToRadians(endAngle), + false, + ); + + if (cornerStyle == CornerStyle.endCurve) { + path.close(); + } + + return path; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/typedef.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/typedef.dart new file mode 100644 index 000000000..7347c4ee1 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/typedef.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; + +import '../common/callbacks.dart'; +import '../common/chart_point.dart'; +import '../series/chart_series.dart'; +import '../series/funnel_series.dart'; +import '../series/pyramid_series.dart'; +import '../utils/enum.dart'; + +/// Returns the TooltipArgs. +typedef ChartTooltipCallback = void Function(TooltipArgs tooltipArgs); + +/// Returns the ActualRangeChangedArgs. +typedef ChartActualRangeChangedCallback = + void Function(ActualRangeChangedArgs rangeChangedArgs); + +/// Signature for the [axisLabelFormatter] callback that returns +/// [ChartAxisLabel] class value to customize the axis label text and style. +typedef ChartLabelFormatterCallback = + ChartAxisLabel Function(AxisLabelRenderDetails axisLabelRenderArgs); + +/// Signature for the [multiLevelLabelFormatter] callback that returns +/// [ChartAxisLabel]. +typedef MultiLevelLabelFormatterCallback = + ChartAxisLabel Function( + MultiLevelLabelRenderDetails multiLevelLabelRenderArgs, + ); + +/// Returns the DataLabelRenderArgs. +typedef ChartDataLabelRenderCallback = + void Function(DataLabelRenderArgs dataLabelArgs); + +/// Returns the LegendRenderArgs. +typedef ChartLegendRenderCallback = + void Function(LegendRenderArgs legendRenderArgs); + +/// Returns the Trendline args +typedef ChartTrendlineRenderCallback = + void Function(TrendlineRenderParams trendlineRenderParams); + +///Returns the TrackballArgs. +typedef ChartTrackballCallback = void Function(TrackballArgs trackballArgs); + +/// Returns the CrosshairRenderArgs +typedef ChartCrosshairCallback = + void Function(CrosshairRenderArgs crosshairArgs); + +/// Returns the ZoomPanArgs. +typedef ChartZoomingCallback = void Function(ZoomPanArgs zoomingArgs); + +/// Returns the ChartPointDetails. +typedef ChartPointInteractionCallback = + void Function(ChartPointDetails pointInteractionDetails); + +/// Returns the AxisLabelTapArgs. +typedef ChartAxisLabelTapCallback = + void Function(AxisLabelTapArgs axisLabelTapArgs); + +/// Returns the LegendTapArgs. +typedef ChartLegendTapCallback = void Function(LegendTapArgs legendTapArgs); + +/// Returns the SelectionArgs. +typedef ChartSelectionCallback = void Function(SelectionArgs selectionArgs); + +/// Returns the offset. +typedef ChartTouchInteractionCallback = + void Function(ChartTouchInteractionArgs tapArgs); + +/// Returns the IndicatorRenderArgs. +typedef ChartIndicatorRenderCallback = + TechnicalIndicatorRenderDetails Function( + IndicatorRenderParams indicatorRenderParams, + ); + +/// 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); + +/// Called when the series renderer is created. +typedef SeriesRendererCreatedCallback = + void Function(ChartSeriesController controller); + +/// Returns the widget. +typedef ChartDataLabelTemplateBuilder = + Widget Function( + T data, + CartesianChartPoint point, + int pointIndex, { + int seriesIndex, + CartesianSeries series, + }); + +/// typedef common for all the chart types +/// +/// Signature for callback reporting that a data label is tapped. +/// +/// Also refer `onDataLabelTapped` event and [DataLabelTapDetails] class. +typedef DataLabelTapCallback = void Function(DataLabelTapDetails onTapArgs); + +/// Returns the widget. +/// +/// Customize the appearance of legend items with your template by +/// using legendItemBuilder property of legend. +typedef LegendItemBuilder = + Widget Function( + String legendText, + ChartSeries? series, + ChartPoint point, + int seriesIndex, + ); + +/// Maps the index value. +typedef ChartIndexedValueMapper = R? Function(int index); + +/// Maps the data from data source. +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( + T data, + ChartPoint point, + ChartSeries 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 +typedef ChartSeriesRendererFactory = + ChartSeriesRenderer Function(ChartSeries series); + +/// typedef belongs SfCircularChart + +/// Returns the LegendRenderArgs. +typedef CircularLegendRenderCallback = + void Function(LegendRenderArgs legendRenderArgs); + +/// Returns the TooltipArgs. +typedef CircularTooltipCallback = void Function(TooltipArgs tooltipArgs); + +/// Returns the DataLabelRenderArgs. +typedef CircularDataLabelRenderCallback = + void Function(DataLabelRenderArgs dataLabelArgs); + +/// Returns the SelectionArgs. +typedef CircularSelectionCallback = void Function(SelectionArgs selectionArgs); + +/// Returns the offset. +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); + +/// Return the controller for circular series. +typedef CircularSeriesRendererCreatedCallback = + void Function(CircularSeriesController controller); + +// typedef belongs to SfFunnelChart + +/// Returns the LegendRenderArgs. +typedef FunnelLegendRenderCallback = void Function(LegendRenderArgs args); + +/// Returns the TooltipArgs. +typedef FunnelTooltipCallback = void Function(TooltipArgs tooltipArgs); + +/// Returns the DataLabelRenderArgs. +typedef FunnelDataLabelRenderCallback = + void Function(DataLabelRenderArgs dataLabelArgs); + +/// Returns the SelectionArgs. +typedef FunnelSelectionCallback = void Function(SelectionArgs selectionArgs); + +/// Returns the offset. +typedef FunnelTouchInteractionCallback = + void Function(ChartTouchInteractionArgs tapArgs); + +/// Called when the renderer for the funnel series is created. +typedef FunnelSeriesRendererCreatedCallback = + void Function(FunnelSeriesController controller); + +// typedef belongs to SfPyramidChart + +/// Returns the LegendRenderArgs. +typedef PyramidLegendRenderCallback = void Function(LegendRenderArgs args); + +/// Returns the TooltipArgs. +typedef PyramidTooltipCallback = void Function(TooltipArgs tooltipArgs); + +/// Returns the DataLabelRenderArgs. +typedef PyramidDataLabelRenderCallback = + void Function(DataLabelRenderArgs dataLabelArgs); + +/// Returns the SelectionArgs. +typedef PyramidSelectionCallback = void Function(SelectionArgs selectionArgs); + +/// Returns the Offset. +typedef PyramidTouchInteractionCallback = + void Function(ChartTouchInteractionArgs tapArgs); + +/// Called when the pyramid series is created. +typedef PyramidSeriesRendererCreatedCallback = + void Function(PyramidSeriesController controller); + +/// Callback definition for error bar event. +typedef ChartErrorBarRenderCallback = + void Function(ErrorBarRenderDetails errorBarRenderDetails); + +//// Callback definition for cartesian shader events. +typedef CartesianShaderCallback = Shader Function(ShaderDetails shaderDetails); + +/// Signature for transform the data point to pixel value. +typedef PointToPixelCallback = double Function(num x, num y); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/zooming_helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/zooming_helper.dart new file mode 100644 index 000000000..98ae57756 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/zooming_helper.dart @@ -0,0 +1,411 @@ +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart'; + +import '../../sparkline/utils/helper.dart'; +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; +import '../common/interactive_tooltip.dart'; +import 'helper.dart'; + +/// Returns the tooltip label on zooming. +String tooltipValue( + Offset position, + RenderChartAxis axis, + Rect plotAreaBounds, +) { + final num value = + axis.isVertical + ? axis.pixelToPoint(axis.paintBounds, position.dx, position.dy) + : axis.pixelToPoint( + axis.paintBounds, + position.dx - plotAreaBounds.left, + position.dy - plotAreaBounds.top, + ); + + dynamic result = interactiveTooltipLabel(value, axis); + if (axis.interactiveTooltip.format != null) { + final String stringValue = axis.interactiveTooltip.format!.replaceAll( + '{value}', + result, + ); + result = stringValue; + } + return result.toString(); +} + +/// To get interactive tooltip label. +dynamic interactiveTooltipLabel(dynamic value, RenderChartAxis axis) { + if (axis is RenderCategoryAxis) { + final num index = value < 0 ? 0 : value; + final List labels = axis.labels; + final int labelsLength = labels.length; + return labels[(index.round() >= labelsLength + ? (index.round() > labelsLength ? labelsLength - 1 : index - 1) + : index) + .round()] + .toString(); + } else if (axis is RenderDateTimeCategoryAxis) { + final num index = value < 0 ? 0 : value; + final List labels = axis.labels; + final int labelsLength = labels.length; + final int milliseconds = + labels[(index.round() >= labelsLength + ? (index.round() > labelsLength ? labelsLength - 1 : index - 1) + : index) + .round()]; + final num interval = axis.visibleRange!.minimum.ceil(); + final num previousInterval = + labels.isNotEmpty ? labels[labelsLength - 1] : interval; + final DateFormat dateFormat = + axis.dateFormat ?? + dateTimeCategoryAxisLabelFormat( + axis, + interval.toInt(), + previousInterval.toInt(), + ); + return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(milliseconds)); + } else if (axis is RenderDateTimeAxis) { + final num interval = axis.visibleRange!.minimum.ceil(); + final List visibleLabels = axis.visibleLabels; + final num previousInterval = + visibleLabels.isNotEmpty + ? visibleLabels[visibleLabels.length - 1].value + : interval; + final DateFormat dateFormat = + axis.dateFormat ?? + dateTimeAxisLabelFormat( + axis, + interval.toInt(), + previousInterval.toInt(), + ); + return dateFormat.format( + DateTime.fromMillisecondsSinceEpoch(value.toInt()), + ); + } else if (axis is RenderLogarithmicAxis) { + return logAxisLabel( + axis, + axis.toPow(value), + axis.interactiveTooltip.decimalPlaces, + ); + } else if (axis is RenderNumericAxis) { + return numericAxisLabel(axis, value, axis.interactiveTooltip.decimalPlaces); + } else { + return ''; + } +} + +/// Validate the rect by comparing small and large rect. +Rect validateRect(Rect largeRect, Rect smallRect, String axisPosition) => + Rect.fromLTRB( + axisPosition == 'left' + ? (smallRect.left - (largeRect.width - smallRect.width)) + : smallRect.left, + smallRect.top, + axisPosition == 'right' + ? (smallRect.right + (largeRect.width - smallRect.width)) + : smallRect.right, + smallRect.bottom, + ); + +/// Calculate the interactive tooltip rect, based on the zoomed axis position. +Rect calculateRect(RenderChartAxis axis, Offset position, Size labelSize) { + const double paddingForRect = 10; + final Rect axisBound = (axis.parentData! as BoxParentData).offset & axis.size; + final double arrowLength = axis.interactiveTooltip.arrowLength; + double left, top; + final double width = labelSize.width + paddingForRect; + final double height = labelSize.height + paddingForRect; + + if (axis.isVertical) { + top = position.dy - height / 2; + if (axis.opposedPosition) { + left = axisBound.left + arrowLength; + } else { + left = axisBound.left - width - arrowLength; + } + } else { + left = position.dx - width / 2; + if (axis.opposedPosition) { + top = axisBound.top - height - arrowLength; + } else { + top = axisBound.top + arrowLength; + } + } + return Rect.fromLTWH(left, top, width, height); +} + +/// To draw connectors. +void drawConnector( + Canvas canvas, + Paint connectorLinePaint, + RRect startTooltipRect, + RRect endTooltipRect, + Offset startPosition, + Offset endPosition, + RenderChartAxis axis, +) { + final InteractiveTooltip tooltip = axis.interactiveTooltip; + if (!axis.isVertical && !axis.opposedPosition) { + startPosition = Offset( + startPosition.dx, + startTooltipRect.top - tooltip.arrowLength, + ); + endPosition = Offset( + endPosition.dx, + endTooltipRect.top - tooltip.arrowLength, + ); + } else if (!axis.isVertical && axis.opposedPosition) { + startPosition = Offset( + startPosition.dx, + startTooltipRect.bottom + tooltip.arrowLength, + ); + endPosition = Offset( + endPosition.dx, + endTooltipRect.bottom + tooltip.arrowLength, + ); + } else if (axis.isVertical && !axis.opposedPosition) { + startPosition = Offset( + startTooltipRect.right + tooltip.arrowLength, + startPosition.dy, + ); + endPosition = Offset( + endTooltipRect.right + tooltip.arrowLength, + endPosition.dy, + ); + } else { + startPosition = Offset( + startTooltipRect.left - tooltip.arrowLength, + startPosition.dy, + ); + endPosition = Offset( + endTooltipRect.left - tooltip.arrowLength, + endPosition.dy, + ); + } + drawDashedPath( + canvas, + connectorLinePaint, + startPosition, + endPosition, + tooltip.connectorLineDashArray, + ); +} + +/// To draw tooltip. +RRect calculateTooltipRect( + Canvas canvas, + Paint fillPaint, + Paint strokePaint, + Path path, + Offset position, + Rect labelRect, + RRect? rect, + String value, + Size labelSize, + Rect plotAreaBound, + TextStyle textStyle, + RenderChartAxis axis, + Offset plotAreaOffset, +) { + final Offset parentDataOffset = (axis.parentData! as BoxParentData).offset; + final Offset axisOffset = parentDataOffset.translate( + -plotAreaOffset.dx, + -plotAreaOffset.dy, + ); + final Rect axisRect = axisOffset & axis.size; + labelRect = validateRectBounds(labelRect, axisRect); + labelRect = + axis.isVertical + ? validateRectYPosition(labelRect, plotAreaBound) + : validateRectXPosition(labelRect, plotAreaBound); + path.reset(); + rect = RRect.fromRectAndRadius( + labelRect, + Radius.circular(axis.interactiveTooltip.borderRadius), + ); + path.addRRect(rect); + calculateNeckPositions( + canvas, + fillPaint, + strokePaint, + path, + position, + rect, + axis, + ); + drawText( + canvas, + value, + Offset( + (rect.left + rect.width / 2) - labelSize.width / 2, + (rect.top + rect.height / 2) - labelSize.height / 2, + ), + textStyle, + ); + return rect; +} + +/// To calculate tooltip neck positions. +void calculateNeckPositions( + Canvas canvas, + Paint fillPaint, + Paint strokePaint, + Path path, + Offset position, + RRect rect, + RenderChartAxis axis, +) { + final InteractiveTooltip tooltip = axis.interactiveTooltip; + double x1, x2, x3, x4, y1, y2, y3, y4; + if (!axis.isVertical && !axis.opposedPosition) { + x1 = position.dx; + y1 = rect.top - tooltip.arrowLength; + x2 = (rect.right - rect.width / 2) + tooltip.arrowWidth; + y2 = rect.top; + x3 = (rect.left + rect.width / 2) - tooltip.arrowWidth; + y3 = rect.top; + x4 = position.dx; + y4 = rect.top - tooltip.arrowLength; + } else if (!axis.isVertical && axis.opposedPosition) { + x1 = position.dx; + y1 = rect.bottom + tooltip.arrowLength; + x2 = (rect.right - rect.width / 2) + tooltip.arrowWidth; + y2 = rect.bottom; + x3 = (rect.left + rect.width / 2) - tooltip.arrowWidth; + y3 = rect.bottom; + x4 = position.dx; + y4 = rect.bottom + tooltip.arrowLength; + } else if (axis.isVertical && !axis.opposedPosition) { + x1 = rect.right; + y1 = rect.top + rect.height / 2 - tooltip.arrowWidth; + x2 = rect.right; + y2 = rect.bottom - rect.height / 2 + tooltip.arrowWidth; + x3 = rect.right + tooltip.arrowLength; + y3 = position.dy; + x4 = rect.right + tooltip.arrowLength; + y4 = position.dy; + } else { + x1 = rect.left; + y1 = rect.top + rect.height / 2 - tooltip.arrowWidth; + x2 = rect.left; + y2 = rect.bottom - rect.height / 2 + tooltip.arrowWidth; + x3 = rect.left - tooltip.arrowLength; + y3 = position.dy; + x4 = rect.left - tooltip.arrowLength; + y4 = position.dy; + } + drawTooltipArrowhead( + canvas, + path, + fillPaint, + strokePaint, + x1, + y1, + x2, + y2, + x3, + y3, + x4, + y4, + ); +} + +/// This method will validate whether the tooltip exceeds the screen or not. +Rect validateRectBounds(Rect tooltipRect, Rect boundary) { + Rect validatedRect = tooltipRect; + double difference = 0; + + /// Padding between the corners. + const double padding = 0.5; + + // Move the tooltip if it's outside of the boundary. + if (tooltipRect.left < boundary.left) { + difference = (boundary.left - tooltipRect.left) + padding; + validatedRect = validatedRect.translate(difference, 0); + } + if (tooltipRect.right > boundary.right) { + difference = (tooltipRect.right - boundary.right) + padding; + validatedRect = validatedRect.translate(-difference, 0); + } + if (tooltipRect.top < boundary.top) { + difference = (boundary.top - tooltipRect.top) + padding; + validatedRect = validatedRect.translate(0, difference); + } + + if (tooltipRect.bottom > boundary.bottom) { + difference = (tooltipRect.bottom - boundary.bottom) + padding; + validatedRect = validatedRect.translate(0, -difference); + } + return validatedRect; +} + +/// Gets the x position of validated rect. +Rect validateRectYPosition(Rect labelRect, Rect axisClipRect) { + Rect validatedRect = labelRect; + if (labelRect.bottom >= axisClipRect.bottom) { + validatedRect = Rect.fromLTRB( + labelRect.left, + labelRect.top - (labelRect.bottom - axisClipRect.bottom), + labelRect.right, + axisClipRect.bottom, + ); + } else if (labelRect.top <= axisClipRect.top) { + validatedRect = Rect.fromLTRB( + labelRect.left, + axisClipRect.top, + labelRect.right, + labelRect.bottom + (axisClipRect.top - labelRect.top), + ); + } + return validatedRect; +} + +/// Gets the x position of validated rect. +Rect validateRectXPosition(Rect labelRect, Rect axisClipRect) { + Rect validatedRect = labelRect; + if (labelRect.right >= axisClipRect.right) { + validatedRect = Rect.fromLTRB( + labelRect.left - (labelRect.right - axisClipRect.right), + labelRect.top, + axisClipRect.right, + labelRect.bottom, + ); + } else if (labelRect.left <= axisClipRect.left) { + validatedRect = Rect.fromLTRB( + axisClipRect.left, + labelRect.top, + labelRect.right + (axisClipRect.left - labelRect.left), + labelRect.bottom, + ); + } + return validatedRect; +} + +/// Draw tooltip arrow head. +void drawTooltipArrowhead( + Canvas canvas, + Path backgroundPath, + Paint fillPaint, + Paint strokePaint, + double x1, + double y1, + double x2, + double y2, + double x3, + double y3, + double x4, + double y4, +) { + backgroundPath.moveTo(x1, y1); + backgroundPath.lineTo(x2, y2); + backgroundPath.lineTo(x3, y3); + backgroundPath.lineTo(x4, y4); + backgroundPath.lineTo(x1, y1); + fillPaint.isAntiAlias = true; + canvas.drawPath(backgroundPath, strokePaint); + canvas.drawPath(backgroundPath, fillPaint); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart deleted file mode 100644 index b72e9a9ba..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart +++ /dev/null @@ -1,767 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; -import '../../chart/user_interaction/selection_renderer.dart'; -import '../../common/template/rendering.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/helper.dart'; -import '../renderer/common.dart'; -import '../renderer/data_label_renderer.dart'; -import '../renderer/renderer_extension.dart'; -import '../series_painter/doughnut_series_painter.dart'; -import '../series_painter/pie_chart_painter.dart'; -import '../series_painter/radial_bar_painter.dart'; -import '../utils/helper.dart'; -import 'circular_state_properties.dart'; - -/// Represents the circular chart area. -/// -// ignore: must_be_immutable -class CircularArea extends StatelessWidget { - /// Creates an instance for circular area. - // ignore: prefer_const_constructors_in_immutables - CircularArea({required this.stateProperties}); - - /// Here, we are using get keyword in order 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 => stateProperties.chart; - - /// Holds the chart state properties. - final CircularStateProperties stateProperties; - - /// Gets or sets the circular series. - CircularSeries? series; - - /// Holds the render box of the circular chart. - late RenderBox renderBox; - - /// Specifies the point region. - Region? pointRegion; - - /// Holds the tap down details. - late TapDownDetails tapDownDetails; - - /// Holds the double tap position. - Offset? doubleTapPosition; - - /// Specifies whether the mouse is hovered. - final bool _enableMouseHover = kIsWeb; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // ignore: avoid_unnecessary_containers - return Container( - child: MouseRegion( - // 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) { - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - }, - child: Listener( - onPointerUp: (PointerUpEvent event) => _onTapUp(event), - onPointerDown: (PointerDownEvent event) => _onTapDown(event), - onPointerMove: (PointerMoveEvent event) => - _performPointerMove(event), - child: GestureDetector( - onLongPress: _onLongPress, - onTapUp: (TapUpDetails details) { - if (chart.series[0].onPointTap != null && - pointRegion != null) { - calculatePointSeriesIndex(chart, stateProperties, null, - pointRegion, ActivationMode.singleTap); - } - }, - onDoubleTap: _onDoubleTap, - child: Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: _initializeChart(constraints, context), - decoration: const BoxDecoration(color: Colors.transparent), - )), - )), - ); - }); - } - - /// To perform the pointer down event. - void _onTapDown(PointerDownEvent event) { - ChartTouchInteractionArgs touchArgs; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isHovering = false; - stateProperties.renderingDetails.currentActive = null; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - pointRegion = getCircularPointRegion( - chart, - stateProperties.renderingDetails.tapPosition, - stateProperties.chartSeries.visibleSeriesRenderers[0]); - doubleTapPosition = stateProperties.renderingDetails.tapPosition; - if (stateProperties.renderingDetails.tapPosition != null && - pointRegion != null) { - stateProperties.renderingDetails.currentActive = ChartInteraction( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - stateProperties.chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex].series, - stateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - .renderPoints![pointRegion!.pointIndex], - pointRegion); - } else { - //hides the tooltip if the point of interaction is outside circular region of the chart - tooltipRenderingDetails.show = false; - tooltipRenderingDetails.hideTooltipTemplate(); - } - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - - /// To perform the pointer move event. - void _performPointerMove(PointerMoveEvent event) { - ChartTouchInteractionArgs touchArgs; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = position; - chart.onChartTouchInteractionMove!(touchArgs); - } - } - - /// To perform double tap touch interactions. - void _onDoubleTap() { - if (doubleTapPosition != null && pointRegion != null) { - if (chart.series[0].onPointDoubleTap != null && pointRegion != null) { - calculatePointSeriesIndex(chart, stateProperties, null, pointRegion, - ActivationMode.doubleTap); - } - stateProperties.renderingDetails.currentActive = ChartInteraction( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - stateProperties.chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex].series, - stateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - .renderPoints![pointRegion!.pointIndex], - pointRegion); - if (stateProperties.renderingDetails.currentActive != null) { - if (stateProperties - .renderingDetails.currentActive?.series.explodeGesture == - ActivationMode.doubleTap) { - stateProperties.chartSeries.seriesPointExplosion( - stateProperties.renderingDetails.currentActive?.region); - } - } - stateProperties.chartSeries - .seriesPointSelection(pointRegion, ActivationMode.doubleTap); - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap && - doubleTapPosition != null) { - stateProperties.requireDataLabelTooltip = null; - if (chart.tooltipBehavior.builder != null) { - showCircularTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition!.dx.toDouble(), - doubleTapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform long press touch interactions. - void _onLongPress() { - if (stateProperties.renderingDetails.tapPosition != null && - pointRegion != null) { - if (chart.series[0].onPointLongPress != null && pointRegion != null) { - calculatePointSeriesIndex(chart, stateProperties, null, pointRegion, - ActivationMode.longPress); - } - stateProperties.renderingDetails.currentActive = ChartInteraction( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - stateProperties.chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex].series, - stateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - .renderPoints![pointRegion!.pointIndex], - pointRegion); - stateProperties.chartSeries - .seriesPointSelection(pointRegion, ActivationMode.longPress); - if (stateProperties.renderingDetails.currentActive != null) { - if (stateProperties - .renderingDetails.currentActive?.series.explodeGesture == - ActivationMode.longPress) { - stateProperties.chartSeries.seriesPointExplosion( - stateProperties.renderingDetails.currentActive?.region); - } - } - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress && - stateProperties.renderingDetails.tapPosition != null) { - stateProperties.requireDataLabelTooltip = null; - if (chart.tooltipBehavior.builder != null) { - showCircularTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer.onLongPress( - stateProperties.renderingDetails.tapPosition!.dx.toDouble(), - stateProperties.renderingDetails.tapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform the pointer up event. - void _onTapUp(PointerUpEvent event) { - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - ChartTouchInteractionArgs touchArgs; - final CircularSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - final Offset position = renderBox.globalToLocal(event.position); - if (stateProperties.animationCompleted) { - _showTrimmedDataLabelTooltip(position, stateProperties, seriesRenderer); - } - if (chart.onDataLabelTapped != null) { - triggerCircularDataLabelEvent(chart, seriesRenderer, stateProperties, - stateProperties.renderingDetails.tapPosition); - } - if (stateProperties.renderingDetails.tapPosition != null) { - if (stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null && - stateProperties - .renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.singleTap) { - stateProperties.chartSeries.seriesPointExplosion( - stateProperties.renderingDetails.currentActive!.region); - } - - if (stateProperties.renderingDetails.tapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - stateProperties.chartSeries.seriesPointSelection( - stateProperties.renderingDetails.currentActive!.region, - ActivationMode.singleTap); - } - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null) { - stateProperties.requireDataLabelTooltip = null; - if (chart.tooltipBehavior.builder != null) { - showCircularTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - } - stateProperties.renderingDetails.tapPosition = null; - } - - /// To perform the hover event. - void _onHover(PointerEvent event) { - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - stateProperties.renderingDetails.currentActive = null; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - pointRegion = getCircularPointRegion( - chart, - stateProperties.renderingDetails.tapPosition, - stateProperties.chartSeries.visibleSeriesRenderers[0]); - final CircularSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - final Offset position = renderBox.globalToLocal(event.position); - if (stateProperties.animationCompleted) { - _showTrimmedDataLabelTooltip(position, stateProperties, seriesRenderer); - } - if (stateProperties.renderingDetails.tapPosition != null && - pointRegion != null) { - stateProperties.renderingDetails.currentActive = ChartInteraction( - pointRegion!.seriesIndex, - pointRegion!.pointIndex, - stateProperties.chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex].series, - stateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - .renderPoints![pointRegion!.pointIndex], - pointRegion); - } else { - //hides the tooltip when the mouse is hovering out of the circular region - tooltipRenderingDetails.hide(); - } - if (stateProperties.renderingDetails.tapPosition != null) { - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null) { - tooltipRenderingDetails.isHovering = true; - stateProperties.requireDataLabelTooltip = null; - if (chart.tooltipBehavior.builder != null) { - showCircularTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer - .onEnter(position.dx.toDouble(), position.dy.toDouble()); - } - } else { - tooltipRenderingDetails.prevTooltipValue = null; - tooltipRenderingDetails.currentTooltipValue = null; - } - } - stateProperties.renderingDetails.tapPosition = null; - } - - /// This method gets executed for showing tooltip when builder is provided in behavior. - /// The optional parameters will take values once the public method gets called. - void showCircularTooltipTemplate([int? seriesIndex, int? pointIndex]) { - stateProperties.isTooltipHidden = false; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - if (!tooltipRenderingDetails.isHovering) { - //assigning null for the previous and current tooltip values in case of touch interaction - tooltipRenderingDetails.prevTooltipValue = null; - tooltipRenderingDetails.currentTooltipValue = null; - } - final CircularSeries chartSeries = - stateProperties.renderingDetails.currentActive?.series ?? - chart.series[seriesIndex!]; - final ChartPoint point = pointIndex == null - ? stateProperties.renderingDetails.currentActive?.point - : stateProperties - .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)) { - tooltipRenderingDetails.showLocation = location; - tooltipRenderingDetails.chartTooltipState!.boundaryRect = - tooltipRenderingDetails.tooltipBounds = - stateProperties.renderingDetails.chartContainerRect; - tooltipRenderingDetails.tooltipTemplate = chart - .tooltipBehavior.builder!( - chartSeries.dataSource![pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!], - point, - chartSeries, - seriesIndex ?? - stateProperties.renderingDetails.currentActive!.seriesIndex!, - pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!); - if (tooltipRenderingDetails.isHovering) { - // assigning values for previous and current tooltip values when the mouse is hovering - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue; - tooltipRenderingDetails.currentTooltipValue = TooltipValue( - seriesIndex ?? - stateProperties.renderingDetails.currentActive!.seriesIndex!, - pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!); - } - if (!tooltipRenderingDetails.isHovering) { - tooltipRenderingDetails.hideTooltipTemplate(); - } - tooltipRenderingDetails.show = true; - tooltipRenderingDetails.performTooltip(); - } - } - } - - /// To initialize the chart widget. - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - _calculateContainerSize(constraints); - if (chart.series.isNotEmpty) { - stateProperties.chartSeries.calculateAngleAndCenterPositions( - stateProperties.chartSeries.visibleSeriesRenderers[0]); - } - return Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context)); - } - - /// To calculate chart rect area size. - void _calculateContainerSize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - stateProperties.renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final EdgeInsets margin = chart.margin; - stateProperties.renderingDetails.chartAreaRect = Rect.fromLTWH( - margin.left, - margin.top, - width - margin.right - margin.left, - height - margin.top - margin.bottom); - } - - /// To render chart widgets. - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _bindSeriesWidgets(context); - _findTemplates(); - _renderTemplates(); - _bindTooltipWidgets(constraints); - stateProperties.circularArea = this; - renderBox = context.findRenderObject() as RenderBox; - // ignore: avoid_unnecessary_containers - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: stateProperties.renderingDetails.chartWidgets!)); - } - - /// To add chart templates. - void _findTemplates() { - Offset labelLocation; - const num lineLength = 10; - ChartPoint point; - Widget labelWidget; - stateProperties.renderingDetails.templates = []; - stateProperties.renderingDetails.dataLabelTemplateRegions = []; - stateProperties.annotationRegions = []; - CircularSeriesRendererExtension seriesRenderer; - CircularSeries series; - ConnectorLineSettings connector; - ChartAlignment labelAlign; - num connectorLength; - for (int k = 0; - k < stateProperties.chartSeries.visibleSeriesRenderers.length; - k++) { - seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[k]; - series = seriesRenderer.series; - connector = series.dataLabelSettings.connectorLineSettings; - if (series.dataLabelSettings.isVisible && - series.dataLabelSettings.builder != null) { - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - point = seriesRenderer.renderPoints![i]; - if (point.isVisible) { - 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 = Offset(labelLocation.dx, labelLocation.dy); - labelAlign = ChartAlignment.center; - } else { - 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 - : labelLocation.dx - lineLength - 5, - labelLocation.dy); - labelAlign = point.dataLabelPosition == Position.left - ? ChartAlignment.far - : ChartAlignment.near; - } - stateProperties.renderingDetails.templates.add(ChartTemplateInfo( - key: GlobalKey(), - templateType: 'DataLabel', - pointIndex: i, - seriesIndex: k, - needMeasure: true, - clipRect: stateProperties.renderingDetails.chartAreaRect, - animationDuration: 500, - widget: labelWidget, - horizontalAlignment: labelAlign, - verticalAlignment: ChartAlignment.center, - location: labelLocation)); - } - } - } - } - - _setTemplateInfo(); - } - - /// Method to set the template info. - void _setTemplateInfo() { - CircularChartAnnotation annotation; - double radius, annotationHeight, annotationWidth; - ChartTemplateInfo templateInfo; - Offset point; - if (chart.annotations != null && chart.annotations!.isNotEmpty) { - for (int i = 0; i < chart.annotations!.length; i++) { - annotation = chart.annotations![i]; - if (annotation.widget != null) { - radius = percentToValue( - annotation.radius, stateProperties.chartSeries.size / 2)! - .toDouble(); - point = degreeToPoint( - annotation.angle, radius, stateProperties.centerLocation); - annotationHeight = percentToValue( - annotation.height, stateProperties.chartSeries.size / 2)! - .toDouble(); - annotationWidth = percentToValue( - annotation.width, stateProperties.chartSeries.size / 2)! - .toDouble(); - templateInfo = ChartTemplateInfo( - key: GlobalKey(), - templateType: 'Annotation', - needMeasure: true, - horizontalAlignment: annotation.horizontalAlignment, - verticalAlignment: annotation.verticalAlignment, - clipRect: stateProperties.renderingDetails.chartContainerRect, - widget: annotationHeight > 0 && annotationWidth > 0 - ? SizedBox( - height: annotationHeight, - width: annotationWidth, - child: annotation.widget) - : annotation.widget!, - pointIndex: i, - animationDuration: 500, - location: point); - stateProperties.renderingDetails.templates.add(templateInfo); - } - } - } - } - - /// To render chart templates. - void _renderTemplates() { - if (stateProperties.renderingDetails.templates.isNotEmpty) { - for (int i = 0; - i < stateProperties.renderingDetails.templates.length; - i++) { - stateProperties.renderingDetails.templates[i].animationDuration = - !stateProperties.renderingDetails.initialRender! - ? 0 - : stateProperties - .renderingDetails.templates[i].animationDuration; - } - stateProperties.renderingDetails.chartTemplate = ChartTemplate( - templates: stateProperties.renderingDetails.templates, - render: stateProperties.renderingDetails.animateCompleted, - stateProperties: stateProperties); - stateProperties.renderingDetails.chartWidgets! - .add(stateProperties.renderingDetails.chartTemplate!); - } - } - - /// To add tooltip widgets to chart. - void _bindTooltipWidgets(BoxConstraints constraints) { - TooltipHelper.setStateProperties(chart.tooltipBehavior, stateProperties); - final SfChartThemeData _chartTheme = - stateProperties.renderingDetails.chartTheme; - const int seriesIndex = 0; - final DataLabelSettings dataLabel = stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex].series.dataLabelSettings; - if (chart.tooltipBehavior.enable || - dataLabel.labelIntersectAction == LabelIntersectAction.shift || - dataLabel.overflowMode == OverflowMode.trim) { - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue = null; - tooltipRenderingDetails.chartTooltip = SfTooltip( - color: tooltip.color ?? _chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.borderWidth, - duration: tooltip.duration.toInt(), - 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, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? tooltipRenderingDetails.tooltipRenderingEvent - : null); - final Widget uiWidget = IgnorePointer( - ignoring: true, - child: - Stack(children: [tooltipRenderingDetails.chartTooltip!])); - stateProperties.renderingDetails.chartWidgets!.add(uiWidget); - } - } - - /// To add series widgets in chart. - void _bindSeriesWidgets(BuildContext context) { - late CustomPainter seriesPainter; - Animation? seriesAnimation; - stateProperties.renderingDetails.animateCompleted = false; - stateProperties.renderingDetails.chartWidgets ??= []; - CircularSeries series; - CircularSeriesRendererExtension seriesRenderer; - dynamic selectionBehavior; - SelectionBehaviorRenderer selectionBehaviorRenderer; - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[i]; - series = seriesRenderer.series; - selectionBehavior = - seriesRenderer.selectionBehavior = series.selectionBehavior; - selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, stateProperties); - SelectionHelper.setSelectionBehaviorRenderer( - series.selectionBehavior, selectionBehaviorRenderer); - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - selectionDetails.selectionRenderer ??= SelectionRenderer(); - selectionDetails.selectionRenderer!.chart = chart; - selectionDetails.selectionRenderer!.stateProperties = stateProperties; - selectionDetails.selectionRenderer!.seriesRendererDetails = - seriesRenderer; - if (series.initialSelectedDataIndexes.isNotEmpty) { - for (int index = 0; - index < series.initialSelectedDataIndexes.length; - index++) { - stateProperties.renderingDetails.selectionData - .add(series.initialSelectedDataIndexes[index]); - } - } - stateProperties.renderingDetails.animateCompleted = false; - if (series.animationDuration > 0 && - !stateProperties.renderingDetails.didSizeChange && - (stateProperties.renderingDetails.oldDeviceOrientation == - stateProperties.renderingDetails.deviceOrientation) && - (stateProperties.renderingDetails.initialRender! || - (stateProperties.renderingDetails.widgetNeedUpdate && - seriesRenderer.needsAnimation) || - stateProperties.renderingDetails.isLegendToggled)) { - final int totalAnimationDuration = - series.animationDuration.toInt() + series.animationDelay.toInt(); - stateProperties.renderingDetails.animationController.duration = - Duration(milliseconds: totalAnimationDuration); - const double maxSeriesInterval = 0.8; - double minSeriesInterval = 0.1; - minSeriesInterval = series.animationDelay.toInt() / - totalAnimationDuration * - (maxSeriesInterval - minSeriesInterval) + - minSeriesInterval; - seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: stateProperties.renderingDetails.animationController, - curve: Interval(minSeriesInterval, maxSeriesInterval, - curve: Curves.linear), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - stateProperties.renderingDetails.animateCompleted = true; - if (stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state.render(); - } - if (stateProperties.renderingDetails.chartTemplate != null) { - stateProperties.renderingDetails.chartTemplate!.state - .templateRender(); - } - } - })); - stateProperties.renderingDetails.chartElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: stateProperties.renderingDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - stateProperties.renderingDetails.animationController.forward(from: 0.0); - } else { - stateProperties.renderingDetails.animateCompleted = true; - } - seriesRenderer.repaintNotifier = - stateProperties.renderingDetails.seriesRepaintNotifier; - if (seriesRenderer.seriesType == 'pie') { - if (chart.onCreateShader != null) { - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - seriesPainter = PieChartPainter( - stateProperties: stateProperties, - index: i, - isRepaint: seriesRenderer.needsRepaint, - animationController: - stateProperties.renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: stateProperties.renderingDetails.seriesRepaintNotifier); - } else if (seriesRenderer.seriesType == 'doughnut') { - if (chart.onCreateShader != null) { - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - seriesPainter = DoughnutChartPainter( - stateProperties: stateProperties, - index: i, - isRepaint: seriesRenderer.needsRepaint, - animationController: - stateProperties.renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: stateProperties.renderingDetails.seriesRepaintNotifier); - } else if (seriesRenderer.seriesType == 'radialbar') { - seriesPainter = RadialBarPainter( - stateProperties: stateProperties, - index: i, - isRepaint: seriesRenderer.needsRepaint, - animationController: - stateProperties.renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: stateProperties.renderingDetails.seriesRepaintNotifier); - } - stateProperties.renderingDetails.chartWidgets! - .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); - stateProperties.renderDataLabel = CircularDataLabelRenderer( - stateProperties: stateProperties, - show: stateProperties.renderingDetails.animateCompleted); - stateProperties.renderingDetails.chartWidgets! - .add(stateProperties.renderDataLabel!); - } - } -} - -/// Show tooltip for trimmed data label. -void _showTrimmedDataLabelTooltip( - Offset position, - CircularStateProperties stateProperties, - CircularSeriesRendererExtension seriesRenderer) { - stateProperties.requireDataLabelTooltip = - (stateProperties.requireDataLabelTooltip == null && - !stateProperties.isTooltipHidden) - ? stateProperties.requireDataLabelTooltip - : false; - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - if (seriesRenderer.series.dataLabelSettings.isVisible && - seriesRenderer.renderPoints![i].labelRect - .contains(Offset(position.dx, position.dy)) && - seriesRenderer.renderPoints![i].trimmedText != null && - seriesRenderer.renderPoints![i].trimmedText!.contains('...')) { - stateProperties.requireDataLabelTooltip = true; - stateProperties.renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } -} 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 deleted file mode 100644 index d46274a7d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart +++ /dev/null @@ -1,1064 +0,0 @@ -import 'dart:ui' as dart_ui; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -import '../../chart/utils/enum.dart'; -import '../../chart/utils/helper.dart'; -import '../../common/common.dart'; -import '../../common/legend/legend.dart'; -import '../../common/legend/renderer.dart'; -import '../../common/rendering_details.dart'; -import '../../common/template/rendering.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../../common/utils/typedef.dart'; -import '../renderer/chart_point.dart'; -import '../renderer/circular_chart_annotation.dart'; -import '../renderer/circular_series.dart'; -import '../renderer/circular_series_controller.dart'; -import '../renderer/common.dart'; -import '../renderer/renderer_base.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/helper.dart'; -import 'circular_area.dart'; -import 'circular_state_properties.dart'; -import 'series_base.dart'; - -/// Renders the circular chart. -/// -/// The SfCircularChart widget supports pie, doughnut, and radial bar series that can be customized within the circular chart's class. -/// -///```dart -///Widget build(BuildContext context) { -/// return Center( -/// child:SfCircularChart( -/// title: ChartTitle(text: 'Sales by sales person'), -/// legend: Legend(isVisible: true), -/// series: >[ -/// PieSeries<_PieData, String>( -/// explode: true, -/// explodeIndex: 0, -/// dataSource: pieData, -/// xValueMapper: (_PieData data, _) => data.xData, -/// yValueMapper: (_PieData data, _) => data.yData, -/// dataLabelMapper: (_PieData data, _) => data.text, -/// dataLabelSettings: DataLabelSettings(isVisible: true)), -/// ] -/// ) -/// ); -/// } -/// -/// class _PieData { -/// _PieData(this.xData, this.yData, [this.text]); -/// final String xData; -/// final num yData; -/// final String text; -/// } -/// ``` -//ignore:must_be_immutable -class SfCircularChart extends StatefulWidget { - /// Creating an argument constructor of SfCircularChart class. - SfCircularChart( - {Key? key, - this.backgroundColor, - this.backgroundImage, - this.annotations, - this.borderColor = Colors.transparent, - this.borderWidth = 0.0, - this.onLegendItemRender, - this.onTooltipRender, - this.onDataLabelRender, - this.onDataLabelTapped, - this.onLegendTapped, - this.onSelectionChanged, - this.onChartTouchInteractionUp, - this.onChartTouchInteractionDown, - this.onChartTouchInteractionMove, - this.onCreateShader, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - 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}) - : series = series = series ?? >[], - title = title ?? ChartTitle(), - legend = legend ?? Legend(), - margin = margin ?? const EdgeInsets.fromLTRB(10, 10, 10, 10), - centerX = centerX ?? '50%', - centerY = centerY ?? '50%', - tooltipBehavior = tooltipBehavior ?? TooltipBehavior(), - selectionGesture = selectionGesture ?? ActivationMode.singleTap, - enableMultiSelection = enableMultiSelection ?? false, - super(key: key); - - /// Customizes the chart title. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// title: ChartTitle(text: 'Default rendering') - /// ) - /// ); - ///} - ///``` - final ChartTitle title; - - /// Customizes the chart series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// enableTooltip: true, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final List> series; - - /// Specifies the margin for circular chart. - /// - /// Defaults to `const EdgeInsets.fromLTRB(10, 10, 10, 10)`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// margin: const EdgeInsets.all(2) - /// ) - /// ); - ///} - ///``` - final EdgeInsets margin; - - /// Customizes the legend in the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// legend: Legend(isVisible: true) - /// ) - /// ); - ///} - ///``` - final Legend legend; - - /// Customizes the tooltip in chart. - /// - ///```dart - ///TooltipBehavior _tooltipBehavior; - /// - ///@override - ///void initState() { - /// _tooltipBehavior = TooltipBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// tooltipBehavior: _tooltipBehavior - /// ) - /// ); - ///} - ///``` - final TooltipBehavior tooltipBehavior; - - /// Background color of the chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// backgroundColor: Colors.blue - /// ) - /// ); - ///} - ///``` - 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. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// angle: 200, - /// radius: '80%', - /// widget: const Text('Circular Chart') - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final List? annotations; - - /// Border color of the chart. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// borderColor: Colors.red - /// ) - /// ); - ///} - ///``` - final Color borderColor; - - /// Border width of the chart. - /// - /// Defaults to `0.0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// borderWidth: 2 - /// ) - /// ); - ///} - ///``` - final double borderWidth; - - /// Background image for chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// backgroundImage: const AssetImage('image.png'), - /// ) - /// ); - ///} - ///``` - final ImageProvider? backgroundImage; - - /// X value for placing the chart. The value ranges from 0% to 100% and also - /// if values are mentioned in pixel then the chart will moved accordingly. - /// - /// For example, if set `50%` means the x value place center of the chart area or - /// we set `50` means the x value placed 50 pixel. - /// - /// Defaults to `50%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// centerX: '50%' - /// ) - /// ); - ///} - ///``` - final String centerX; - - /// Y value for placing the chart. The value ranges from 0% to 100% and also - /// if values are mentioned in pixel then the chart will moved accordingly. - /// - /// For example, if set `50%` means the y value place center of the chart area or - /// we set `50` means the y value placed 50 pixel. - /// - /// Defaults to `50%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// centerY: '50%' - /// ) - /// ); - ///} - ///``` - final String centerY; - - /// Occurs while legend is rendered. Here, you can get the legend's text, shape - /// series index, and point index case of circular series. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// legend: Legend(isVisible: true), - /// onLegendItemRender: (LegendRendererArgs args) => legend(args), - /// ) - /// ); - ///} - ///void legend(LegendRendererArgs args) { - /// args.legendIconType = LegendIconType.diamond; - ///} - ///``` - 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. - ///```dart - ///TooltipBehavior _tooltipBehavior; - /// - ///@override - ///void initState() { - /// _tooltipBehavior = TooltipBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// tooltipBehavior: _tooltipBehavior, - /// onTooltipRender: (TooltipArgs args) => tool(args) - /// ) - /// ); - ///} - ///void tool(TooltipArgs args) { - /// args.locationX = 30; - ///} - ///``` - 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 - /// index, text, and text style. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onDataLabelRender: (DataLabelRenderArgs args) => dataLabel(args), - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true) - /// ), - /// ], - /// ) - /// ); - ///} - ///void dataLabel(DataLabelRenderArgs args) { - /// args.text = 'dataLabel'; - ///} - ///``` - final CircularDatalabelRenderCallback? onDataLabelRender; - - /// 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 - ///final List colors = [ - /// const Color.fromRGBO(75, 135, 185, 1), - /// const Color.fromRGBO(192, 108, 132, 1), - /// const Color.fromRGBO(246, 114, 128, 1), - /// const Color.fromRGBO(248, 177, 149, 1), - /// const Color.fromRGBO(116, 180, 155, 1) - /// ]; - /// final List stops = [ - /// 0.2, - /// 0.4, - /// 0.6, - /// 0.8, - /// 1, - /// ]; - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onCreateShader: (ChartShaderDetails chartShaderDetails) { - /// return ui.Gradient.linear( - /// chartShaderDetails.outerRect.topRight, - /// chartShaderDetails.outerRect.centerLeft, - /// colors, - /// stops); - /// }, - /// ) - /// ); - ///} - ///``` - final CircularShaderCallback? onCreateShader; - - /// Called when the data label is tapped. - /// - /// 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. - /// - /// _Note:_ This callback will not be called, when the builder is specified for data label - /// (data label template). For this case, custom widget specified in the `DataLabelSettings.builder` property - /// can be wrapped using the `GestureDetector` and this functionality can be achieved in the application level. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onDataLabelTapped: (DataLabelTapDetails args) { - /// print(arg.seriesIndex); - /// } - /// ) - /// ); - ///} - /// - ///``` - - final DataLabelTapCallback? onDataLabelTapped; - - /// Occurs when the legend is tapped. Here, you can get the series, - /// series index, and point index. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onLegendTapped: (LegendTapArgs args) => legend(args), - /// legend: Legend(isVisible: true), - /// ) - /// ); - ///} - ///void legend(LegendTapArgs args) { - /// print(args.pointIndex); - ///} - ///``` - 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 width, unselected border width, series index, and point index. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onSelectionChanged: (SelectionArgs args) => select(args), - /// ) - /// ); - ///} - ///void select(SelectionArgs args) { - /// print(args.selectedBorderColor); - ///} - ///``` - final CircularSelectionCallback? onSelectionChanged; - - /// Occurs when tapped on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final CircularTouchInteractionCallback? onChartTouchInteractionUp; - - /// Occurs when touched on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - /// - final CircularTouchInteractionCallback? onChartTouchInteractionDown; - - /// Occurs when touched and moved on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - 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. - /// Ten colors are available by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// palette: [Colors.red, Colors.green] - /// ) - /// ); - ///} - ///``` - final List palette; - - /// Gesture for activating the selection. - /// - /// Selection can be activated in `ActivationMode.none`, `ActivationMode.singleTap`, - /// `ActivationMode.doubleTap`, and `ActivationMode.longPress`. - /// - /// Defaults to `ActivationMode.singleTap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// selectionGesture: ActivationMode.singleTap, - /// series: >[ - /// PieSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ActivationMode selectionGesture; - - /// Enables or disables the multiple data points or series selection. - /// - /// Defaults to `false`. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// enableMultiSelection: true, - /// series: >[ - /// PieSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final bool enableMultiSelection; - - @override - State createState() => SfCircularChartState(); -} - -/// Represents the state class of [SfCircularChart] widget. -/// -class SfCircularChartState extends State - with TickerProviderStateMixin { - /// Specifies the chart rendering details. - late CircularStateProperties _stateProperties; - - /// 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() { - _stateProperties = CircularStateProperties( - renderingDetails: RenderingDetails(), chartState: this); - - _stateProperties.isToggled = false; - _initializeDefaultValues(); - // Create the series renderer while initial rendering // - _createAndUpdateSeriesRenderer(); - 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() { - _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); - _stateProperties.isRtl = Directionality.of(context) == TextDirection.rtl; - 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 [SfCircularChart]. - - @override - void didUpdateWidget(SfCircularChart oldWidget) { - //Update and maintain the series state, when we update the series in the series collection // - _createAndUpdateSeriesRenderer(oldWidget); - - needsRepaintCircularChart( - _stateProperties.chartSeries.visibleSeriesRenderers, - [ - _stateProperties.prevSeriesRenderer - ]); - - _stateProperties.needExplodeAll = widget.series.isNotEmpty && - (widget.series[0].explodeAll && - widget.series[0].explode && - oldWidget.series[0].explodeAll != widget.series[0].explodeAll); - _stateProperties.renderingDetails.isLegendToggled = false; - _stateProperties.renderingDetails.widgetNeedUpdate = true; - if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { - _stateProperties.renderingDetails.legendWidgetContext.clear(); - } - - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.chartTooltipState != null) { - tooltipRenderingDetails.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) { - _stateProperties.renderingDetails.initialRender = - (_stateProperties.renderingDetails.widgetNeedUpdate && - !_stateProperties.renderingDetails.isLegendToggled) - ? _stateProperties.needExplodeAll - : (_stateProperties.renderingDetails.initialRender == null); - _stateProperties.renderingDetails.oldDeviceOrientation = - _stateProperties.renderingDetails.oldDeviceOrientation == null - ? MediaQuery.of(context).orientation - : _stateProperties.renderingDetails.deviceOrientation; - _stateProperties.renderingDetails.deviceOrientation = - MediaQuery.of(context).orientation; - _stateProperties.isTooltipOrientationChanged = false; - - final Widget container = ChartContainer( - child: GestureDetector( - child: Container( - decoration: BoxDecoration( - color: widget.backgroundColor ?? - _stateProperties - .renderingDetails.chartTheme.plotAreaBackgroundColor, - image: widget.backgroundImage != null - ? DecorationImage( - image: widget.backgroundImage!, fit: BoxFit.fill) - : null, - border: Border.all( - color: widget.borderColor, width: widget.borderWidth)), - child: Column( - children: [ - renderChartTitle(_stateProperties), - _renderChartElements() - ], - ), - )), - ); - return RepaintBoundary(child: container); - } - - /// 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 life cycle is terminal: there is no way to remount a [State] object that has been disposed. - /// - /// Sub classes 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( - _stateProperties.renderingDetails.animationController, - _repaintChartElements); - super.dispose(); - } - - /// 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`. - /// - /// ```dart - /// final GlobalKey _key = GlobalKey(); - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Column( - /// children: [SfCircularChart( - /// key: _key - /// series: >[ - /// PieSeries( - /// enableTooltip: true, - /// ), - /// ], - /// ), - /// RaisedButton( - /// child: Text( - /// 'To Image', - /// ), - /// onPressed: _renderImage, - /// shape: RoundedRectangleBorder( - /// borderRadius: BorderRadius.circular(20)), - /// ) - /// ], - /// ), - /// ); - /// } - /// - /// Future _renderImage() async { - /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); - /// final bytes = await data.toByteData(format: dart_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.white, - /// child: Image.memory(bytes.buffer.asUint8List()), - /// ), - /// ), - /// ); - /// }, - /// ), - /// ); - /// } - ///} - ///``` - - Future toImage({double pixelRatio = 1.0}) async { - 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 - return image; - } - - /// To initialize default values of circular chart. - void _initializeDefaultValues() { - _stateProperties.chartSeries = CircularSeriesBase(_stateProperties); - _stateProperties.circularArea = - CircularArea(stateProperties: _stateProperties); - _stateProperties.renderingDetails.chartLegend = - ChartLegend(_stateProperties); - _stateProperties.needToMoveFromCenter = true; - _stateProperties.renderingDetails.animateCompleted = false; - _stateProperties.renderingDetails.annotationController = - AnimationController(vsync: this); - _stateProperties.renderingDetails.seriesRepaintNotifier = - ValueNotifier(0); - _stateProperties.renderingDetails.legendWidgetContext = - []; - _stateProperties.renderingDetails.explodedPoints = []; - _stateProperties.renderingDetails.templates = []; - _stateProperties.renderingDetails.legendToggleStates = - []; - _stateProperties.renderingDetails.legendToggleTemplateStates = - []; - _stateProperties.renderingDetails.dataLabelTemplateRegions = []; - _stateProperties.annotationRegions = []; - _stateProperties.renderingDetails.widgetNeedUpdate = false; - _stateProperties.renderingDetails.isLegendToggled = false; - _stateProperties.renderingDetails.selectionData = []; - _stateProperties.renderingDetails.animationController = - AnimationController(vsync: this)..addListener(_repaintChartElements); - _stateProperties.renderingDetails.tooltipBehaviorRenderer = - TooltipBehaviorRenderer(_stateProperties); - _stateProperties.renderingDetails.legendRenderer = - LegendRenderer(widget.legend); - } - - /// In this method, create and update the series renderer for each series. - void _createAndUpdateSeriesRenderer([SfCircularChart? oldWidget]) { - if (widget.series.isNotEmpty) { - final CircularSeriesRendererExtension? oldSeriesRenderer = - oldWidget != null && oldWidget.series.isNotEmpty - ? _stateProperties.chartSeries.visibleSeriesRenderers[0] - : null; - dynamic series; - series = widget.series[0]; - - CircularSeriesRendererExtension? seriesRenderer; - - if (_stateProperties.prevSeriesRenderer != null && - !_stateProperties.prevSeriesRenderer!.stateProperties.isToggled && - isSameSeries(_stateProperties.prevSeriesRenderer!.series, series)) { - seriesRenderer = _stateProperties.prevSeriesRenderer!; - } else { - final CircularSeriesRenderer renderer = series.createRenderer(series); - if (renderer is CircularSeriesRendererExtension) { - seriesRenderer = renderer; - } else { - if (renderer is PieSeriesRenderer) { - seriesRenderer = PieSeriesRendererExtension(); - } else if (renderer is DoughnutSeriesRenderer) { - seriesRenderer = DoughnutSeriesRendererExtension(); - } else if (renderer is RadialBarSeriesRenderer) { - seriesRenderer = RadialBarSeriesRendererExtension(); - } - } - seriesRenderer!.renderer = ChartSeriesRender(); - if (seriesRenderer.controller == null && - series.onRendererCreated != null) { - seriesRenderer.controller = CircularSeriesController(seriesRenderer); - series.onRendererCreated!(seriesRenderer.controller); - } - } - if (oldWidget != null && oldWidget.series.isNotEmpty) { - _stateProperties.prevSeriesRenderer = oldSeriesRenderer; - _stateProperties.prevSeriesRenderer!.series = oldWidget.series[0]; - _stateProperties.prevSeriesRenderer!.oldRenderPoints = - >[] - //ignore: prefer_spread_collections - ..addAll(_stateProperties.prevSeriesRenderer!.renderPoints ?? - >[]); - _stateProperties.prevSeriesRenderer!.renderPoints = - >[]; - } - seriesRenderer.series = series; - seriesRenderer.isSelectionEnable = - series.selectionBehavior.enable == true; - seriesRenderer.stateProperties = _stateProperties; - _stateProperties.chartSeries.visibleSeriesRenderers - ..clear() - ..add(seriesRenderer); - } - } - - void _repaintChartElements() { - _stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - - void _refresh() { - final List legendContexts = - _stateProperties.renderingDetails.legendWidgetContext; - if (legendContexts.isNotEmpty) { - MeasureWidgetContext templateContext; - RenderBox renderBox; - for (int i = 0; i < legendContexts.length; i++) { - templateContext = legendContexts[i]; - renderBox = templateContext.context!.findRenderObject() as RenderBox; - templateContext.size = renderBox.size; - } - setState(() { - /// The chart will be rebuilding again, Once legend template sizes will be calculated. - }); - } - } - - /// To return widget with chart elements. - Widget _renderChartElements() { - return Expanded( - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - Widget element; - _initialize(constraints); - _stateProperties.renderingDetails.prevSize = - _stateProperties.renderingDetails.prevSize ?? constraints.biggest; - _stateProperties.renderingDetails.didSizeChange = - _stateProperties.renderingDetails.prevSize != constraints.biggest; - _stateProperties.renderingDetails.prevSize = constraints.biggest; - final ChartPoint tooltipPoint = - _getChartPoints(_stateProperties); - SchedulerBinding.instance!.addPostFrameCallback((_) { - _validateStateMaintenance(_stateProperties, tooltipPoint); - }); - _stateProperties.chartSeries.findVisibleSeries(); - if (_stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty) { - _stateProperties.chartSeries.processDataPoints( - _stateProperties.chartSeries.visibleSeriesRenderers[0]); - } - final List legendTemplates = - bindLegendTemplateWidgets(_stateProperties); - if (legendTemplates.isNotEmpty && - _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { - // ignore: avoid_unnecessary_containers - element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); - } else { - _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( - _stateProperties.renderingDetails.chartLegend.chartSize); - element = getElements(_stateProperties, - CircularArea(stateProperties: _stateProperties), constraints)!; - } - return element; - }), - ); - } - - /// To initialize chart widgets. - void _initialize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - final EdgeInsets margin = widget.margin; - final bool isMobilePlatform = - defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS; - _stateProperties.renderingDetails.legendRenderer.legendPosition = - (widget.legend.position == LegendPosition.auto) - ? (height > width - ? isMobilePlatform - ? LegendPosition.top - : LegendPosition.bottom - : LegendPosition.right) - : widget.legend.position; - _stateProperties.renderingDetails.chartLegend.chartSize = Size( - width - margin.left - margin.right, - height - margin.top - margin.bottom); - } - - /// This will return tooltip chart point. - ChartPoint _getChartPoints( - CircularStateProperties _stateProperties) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - - ChartPoint tooltipChartPoint = ChartPoint(null, null); - - if (_stateProperties.renderingDetails.oldDeviceOrientation != - _stateProperties.renderingDetails.deviceOrientation || - _stateProperties.renderingDetails.didSizeChange) { - if (tooltipRenderingDetails.showLocation != null && - _stateProperties.chart.tooltipBehavior.enable == true && - _stateProperties.isTooltipHidden == false && - _stateProperties.requireDataLabelTooltip == null) { - tooltipChartPoint = circularPixelToPoint( - tooltipRenderingDetails.showLocation!, _stateProperties); - } - } - return tooltipChartPoint; - } - - /// Here for orientation change/browser resize, the logic in this method will get executed. - void _validateStateMaintenance(CircularStateProperties _stateProperties, - ChartPoint tooltipChartPoint) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - if (_stateProperties.renderingDetails.oldDeviceOrientation != - _stateProperties.renderingDetails.deviceOrientation || - _stateProperties.renderingDetails.didSizeChange) { - if (tooltipRenderingDetails.showLocation != null && - _stateProperties.chart.tooltipBehavior.enable && - !_stateProperties.isTooltipHidden && - _stateProperties.requireDataLabelTooltip == null) { - _stateProperties.isTooltipOrientationChanged = true; - ChartPoint? point; - for (int i = 0; - i < - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints.length; - i++) { - if (_stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == - tooltipChartPoint.x && - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == - tooltipChartPoint.y) { - point = _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i]; - } - } - if (point != null) { - final Offset tooltipPosition = - circularPointToPixel(point, _stateProperties); - if (_stateProperties.chart.tooltipBehavior.builder != null) { - _stateProperties.circularArea - .showCircularTooltipTemplate(0, point.index); - } else { - tooltipRenderingDetails.internalShowByPixel( - tooltipPosition.dx, tooltipPosition.dy); - } - } - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart deleted file mode 100644 index b013e9907..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import '../../common/rendering_details.dart'; -import '../../common/state_properties.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../renderer/data_label_renderer.dart'; -import '../renderer/renderer_extension.dart'; -import 'circular_area.dart'; -import 'series_base.dart'; - -/// Specifies the circular state properties. -class CircularStateProperties extends StateProperties { - /// Creates an instance of circular chart properties. - CircularStateProperties( - {required RenderingDetails renderingDetails, required this.chartState}) - : super(renderingDetails, chartState) { - renderingDetails.didSizeChange = false; - } - - /// Specifies the circular chart. - @override - SfCircularChart get chart => chartState.widget; - - /// Specifies the circular chart state. - @override - final SfCircularChartState chartState; - - /// Specifies the center location - late Offset centerLocation; - - /// Specifies the annotation region. - late List annotationRegions; - - /// Specifies the data label renderer. - CircularDataLabelRenderer? renderDataLabel; - - /// Specifies the previous series renderer. - CircularSeriesRendererExtension? prevSeriesRenderer; - - /// Specifies the previous chart points. - List?>? oldPoints; - - /// Holds the information of the series base class. - late CircularSeriesBase chartSeries; - - /// Specifies the circular chart area. - late CircularArea circularArea; - - /// Specifies whether move the label from center. - late bool needToMoveFromCenter; - - /// Specifies whether to explode the segments. - late bool needExplodeAll; - - /// Gets or sets the value for is toggled - late bool isToggled; - - /// Specifies whether the tooltip needs to render for data label or not. - bool? requireDataLabelTooltip; - - /// Specifies whether the text direction of chart widget is RTL or LTR. - late bool isRtl; - - /// To redraw chart elements. - void redraw() { - renderingDetails.initialRender = false; - if (renderingDetails.isLegendToggled) { - isToggled = true; - prevSeriesRenderer = chartSeries.visibleSeriesRenderers[0]; - oldPoints = List?>.filled( - prevSeriesRenderer!.renderPoints!.length, null); - for (int i = 0; i < prevSeriesRenderer!.renderPoints!.length; i++) { - oldPoints![i] = prevSeriesRenderer!.renderPoints![i]; - } - } - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.chartTooltipState != null) { - tooltipRenderingDetails.show = false; - } - // ignore: invalid_use_of_protected_member - chartState.setState(() { - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); - } - - /// Method when called, once animation completed. - bool get animationCompleted { - return renderingDetails.animationController.status != - AnimationStatus.forward; - } - - /// Tooltip timer. - Timer? tooltipTimer; - - /// To check the tooltip orientation changes. - bool isTooltipOrientationChanged = false; - - /// To check if tooltip has been hidden or not. - bool isTooltipHidden = false; -} 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 deleted file mode 100644 index 97b83d0e0..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart +++ /dev/null @@ -1,648 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/event_args.dart'; -import '../../common/legend/renderer.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/typedef.dart'; - -import '../renderer/chart_point.dart'; -import '../renderer/circular_series.dart'; -import '../renderer/common.dart'; -import '../renderer/data_label_renderer.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'circular_base.dart'; -import 'circular_state_properties.dart'; - -/// Represents the circular series base. -/// -class CircularSeriesBase { - /// Creates the instance for the circular series base. - CircularSeriesBase(this.stateProperties); - - /// Represents the circular chart state. - final CircularStateProperties stateProperties; - - /// Gets the chart widget from the state properties. - SfCircularChart get chart => stateProperties.chart; - - /// Specifies the current circular series. - late CircularSeries currentSeries; - - /// Specifies the value of size. - late num size; - - /// Specifies the value of sum of group. - late num sumOfGroup; - - /// Specifies the value of exploded region. - late Region explodedRegion; - - /// Specifies the value of select region. - late Region selectRegion; - - /// Specifies the list that holds the visible series renderers. - List visibleSeriesRenderers = - []; - - /// To find the visible series. - void findVisibleSeries() { - CircularSeries series; - List>? _oldPoints; - CircularSeries? oldSeries; - int oldPointIndex = 0; - ChartPoint currentPoint; - for (final CircularSeriesRendererExtension seriesRenderer - in stateProperties.chartSeries.visibleSeriesRenderers) { - _setSeriesType(seriesRenderer); - series = seriesRenderer.series = chart.series[0]; - seriesRenderer.dataPoints = >[]; - seriesRenderer.needsAnimation = false; - _oldPoints = stateProperties.prevSeriesRenderer?.oldRenderPoints; - oldSeries = stateProperties.prevSeriesRenderer?.series; - oldPointIndex = 0; - if (series.dataSource != null) { - for (int pointIndex = 0; - pointIndex < series.dataSource!.length; - pointIndex++) { - currentPoint = getCircularPoint(seriesRenderer, pointIndex); - if (currentPoint.x != null) { - seriesRenderer.dataPoints.add(currentPoint); - if (!seriesRenderer.needsAnimation) { - if (oldSeries != null) { - seriesRenderer.needsAnimation = - (oldSeries.startAngle != series.startAngle) || - (oldSeries.endAngle != series.endAngle); - } - - seriesRenderer.needsAnimation = !(_oldPoints != null && - _oldPoints.isNotEmpty && - !seriesRenderer.needsAnimation && - oldPointIndex < _oldPoints.length) || - isDataUpdated(currentPoint, _oldPoints[oldPointIndex++]); - } - } - } - } - if (series.sortingOrder != SortingOrder.none && - series.sortFieldValueMapper != null) { - _sortDataSource(seriesRenderer); - } - visibleSeriesRenderers - ..clear() - ..add(seriesRenderer); - break; - } - } - - /// Method to check whether the data is updated. - bool isDataUpdated(ChartPoint point, ChartPoint oldPoint) { - return point.x != oldPoint.x || - point.y != oldPoint.y || - point.sortValue != oldPoint.sortValue; - } - - /// To calculate circular empty points in chart. - void _calculateCircularEmptyPoints( - CircularSeriesRendererExtension seriesRenderer) { - final List> points = seriesRenderer.dataPoints; - for (int i = 0; i < points.length; i++) { - if (points[i].y == null) { - seriesRenderer.series - .calculateEmptyPointValue(i, points[i], seriesRenderer); - } - } - } - - /// To process data points from series. - void processDataPoints(CircularSeriesRendererExtension seriesRenderer) { - currentSeries = seriesRenderer.series; - _calculateCircularEmptyPoints(seriesRenderer); - _findingGroupPoints(); - } - - /// Sort the dataSource. - void _sortDataSource(CircularSeriesRendererExtension seriesRenderer) { - seriesRenderer.dataPoints.sort( - // ignore: missing_return - (ChartPoint firstPoint, ChartPoint secondPoint) { - if (seriesRenderer.series.sortingOrder == SortingOrder.ascending) { - return (firstPoint.sortValue == null) - ? -1 - : (secondPoint.sortValue == null - ? 1 - : (firstPoint.sortValue is String - ? firstPoint.sortValue - .toLowerCase() - .compareTo(secondPoint.sortValue.toLowerCase()) - : firstPoint.sortValue - .compareTo(secondPoint.sortValue))) as int; - } else if (seriesRenderer.series.sortingOrder == - SortingOrder.descending) { - return (firstPoint.sortValue == null) - ? 1 - : (secondPoint.sortValue == null - ? -1 - : (firstPoint.sortValue! is String - ? secondPoint.sortValue! - .toLowerCase() - .compareTo(firstPoint.sortValue!.toLowerCase()) - : secondPoint.sortValue! - .compareTo(firstPoint.sortValue))) as int; - } else { - return 0; - } - }); - } - - /// To group points based on group mode. - void _findingGroupPoints() { - final List seriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - final CircularSeriesRendererExtension seriesRenderer = seriesRenderers[0]; - final double? groupValue = currentSeries.groupTo; - final CircularChartGroupMode? mode = currentSeries.groupMode; - late bool isYText; - final ChartIndexedValueMapper? textMapping = - currentSeries.dataLabelMapper; - ChartPoint point; - sumOfGroup = 0; - seriesRenderer.renderPoints = >[]; - for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { - point = seriesRenderer.dataPoints[i]; - //ignore: prefer_if_null_operators - point.text = point.text == null - ? textMapping != null - ? textMapping(i) ?? point.y.toString() - : point.y.toString() - : point.text; - isYText = point.text == point.y.toString(); - - if (point.isVisible) { - if (groupValue != null && - ((mode == CircularChartGroupMode.point && i >= groupValue) || - (mode == CircularChartGroupMode.value && - point.y! <= groupValue))) { - sumOfGroup += point.y!.abs(); - } else { - seriesRenderer.renderPoints!.add(point); - } - } - } - - if (sumOfGroup > 0) { - seriesRenderer.renderPoints! - .add(ChartPoint('Others', sumOfGroup)); - seriesRenderer.renderPoints![seriesRenderer.renderPoints!.length - 1] - .text = isYText == true ? 'Others : $sumOfGroup}' : 'Others'; - } - _setPointStyle(seriesRenderer); - } - - /// To set point properties. - void _setPointStyle(CircularSeriesRendererExtension seriesRenderer) { - final EmptyPointSettings empty = currentSeries.emptyPointSettings; - final List palette = chart.palette; - int i = 0; - List legendToggles; - MeasureWidgetContext item; - LegendRenderContext legendRenderContext; - for (final ChartPoint point in seriesRenderer.renderPoints!) { - // ignore: unnecessary_null_comparison - point.fill = point.isEmpty && empty.color != null - ? empty.color - : point.pointColor ?? palette[i % palette.length]; - point.color = point.fill; - // ignore: unnecessary_null_comparison - point.strokeColor = point.isEmpty && empty.borderColor != null - ? empty.borderColor - : currentSeries.borderColor; - // ignore: unnecessary_null_comparison - point.strokeWidth = point.isEmpty && empty.borderWidth != null - ? empty.borderWidth - : currentSeries.borderWidth; - point.strokeColor = - point.strokeWidth == 0 ? Colors.transparent : point.strokeColor; - - if (chart.legend.legendItemBuilder != null) { - legendToggles = - stateProperties.renderingDetails.legendToggleTemplateStates; - if (legendToggles.isNotEmpty) { - for (int j = 0; j < legendToggles.length; j++) { - item = legendToggles[j]; - if (i == item.pointIndex) { - point.isVisible = false; - break; - } - } - } - } else { - if (stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; - j < stateProperties.renderingDetails.legendToggleStates.length; - j++) { - legendRenderContext = - stateProperties.renderingDetails.legendToggleStates[j]; - if (i == legendRenderContext.seriesIndex) { - point.isVisible = false; - break; - } - } - } - } - i++; - } - } - - /// To calculate angle, radius and center positions of circular charts. - void calculateAngleAndCenterPositions( - CircularSeriesRendererExtension seriesRenderer) { - currentSeries = seriesRenderer.series; - _findSumOfPoints(seriesRenderer); - _calculateAngle(seriesRenderer); - _calculateRadius(seriesRenderer); - _calculateOrigin(seriesRenderer); - _calculateStartAndEndAngle(seriesRenderer); - _calculateCenterPosition(seriesRenderer); - } - - /// To calculate circular rect position for rendering chart. - void _calculateCenterPosition( - CircularSeriesRendererExtension seriesRenderer) { - if (stateProperties.needToMoveFromCenter && - currentSeries.pointRadiusMapper == null && - (seriesRenderer.seriesType == 'pie' || - seriesRenderer.seriesType == 'doughnut')) { - final Rect areaRect = stateProperties.renderingDetails.chartAreaRect; - bool needExecute = true; - double radius = - seriesRenderer.segmentRenderingValues['currentRadius']!.toDouble(); - while (needExecute) { - radius += 1; - final Rect circularRect = getArcPath( - 0.0, - radius, - seriesRenderer.center!, - seriesRenderer.segmentRenderingValues['start'], - seriesRenderer.segmentRenderingValues['end'], - seriesRenderer.segmentRenderingValues['totalAngle'], - chart, - true) - .getBounds(); - if (circularRect.width > areaRect.width || - circularRect.height > areaRect.height) { - needExecute = false; - seriesRenderer.rect = circularRect; - break; - } - } - seriesRenderer.rect = getArcPath( - 0.0, - seriesRenderer.segmentRenderingValues['currentRadius']!, - seriesRenderer.center!, - seriesRenderer.segmentRenderingValues['start'], - seriesRenderer.segmentRenderingValues['end'], - seriesRenderer.segmentRenderingValues['totalAngle'], - chart, - true) - .getBounds(); - for (final ChartPoint point in seriesRenderer.renderPoints!) { - point.outerRadius = - seriesRenderer.segmentRenderingValues['currentRadius']; - } - } - } - - /// To calculate start and end angle of circular charts. - void _calculateStartAndEndAngle( - CircularSeriesRendererExtension seriesRenderer) { - int pointIndex = 0; - num pointEndAngle; - num pointStartAngle = seriesRenderer.segmentRenderingValues['start']!; - final num innerRadius = - seriesRenderer.segmentRenderingValues['currentInnerRadius']!; - for (final ChartPoint point in seriesRenderer.renderPoints!) { - if (point.isVisible) { - point.innerRadius = - (seriesRenderer.seriesType == 'doughnut') ? innerRadius : 0.0; - point.degree = (point.y!.abs() / - (seriesRenderer.segmentRenderingValues['sumOfPoints']! != 0 - ? seriesRenderer.segmentRenderingValues['sumOfPoints']! - : 1)) * - seriesRenderer.segmentRenderingValues['totalAngle']!; - pointEndAngle = pointStartAngle + point.degree!; - point.startAngle = pointStartAngle; - point.endAngle = pointEndAngle; - point.midAngle = (pointStartAngle + pointEndAngle) / 2; - point.outerRadius = _calculatePointRadius(point.radius, point, - seriesRenderer.segmentRenderingValues['currentRadius']!); - point.center = _needExplode(pointIndex, currentSeries) - ? _findExplodeCenter( - point.midAngle!, seriesRenderer, point.outerRadius!) - : seriesRenderer.center; - // ignore: unnecessary_null_comparison - if (currentSeries.dataLabelSettings != null) { - findDataLabelPosition(point); - } - pointStartAngle = pointEndAngle; - } - pointIndex++; - } - } - - /// To check need for explode. - bool _needExplode(int pointIndex, CircularSeries series) { - bool isNeedExplode = false; - if (series.explode) { - if (stateProperties.renderingDetails.initialRender!) { - if (pointIndex == series.explodeIndex || series.explodeAll) { - stateProperties.renderingDetails.explodedPoints.add(pointIndex); - isNeedExplode = true; - } - } else if (!stateProperties.renderingDetails.initialRender!) { - if (!stateProperties.renderingDetails.explodedPoints - .contains(pointIndex) && - !stateProperties.renderingDetails.isLegendToggled) { - if (pointIndex == series.explodeIndex || series.explodeAll) { - if (!series.explodeAll && pointIndex == 0) { - stateProperties.renderingDetails.explodedPoints.clear(); - } - stateProperties.renderingDetails.explodedPoints.add(pointIndex); - isNeedExplode = true; - } else if (!series.explodeAll && - stateProperties.renderingDetails.explodedPoints.isNotEmpty && - pointIndex <= - stateProperties.renderingDetails.explodedPoints.length - 1) { - stateProperties.renderingDetails.explodedPoints - .removeAt(pointIndex); - } - } - isNeedExplode = stateProperties.renderingDetails.explodedPoints - .contains(pointIndex); - } - } - return isNeedExplode; - } - - /// To find sum of points in the series. - void _findSumOfPoints(CircularSeriesRendererExtension seriesRenderer) { - seriesRenderer.segmentRenderingValues['sumOfPoints'] = 0; - for (final ChartPoint point in seriesRenderer.renderPoints!) { - if (point.isVisible) { - seriesRenderer.segmentRenderingValues['sumOfPoints'] = - seriesRenderer.segmentRenderingValues['sumOfPoints']! + - point.y!.abs(); - } - } - } - - /// To calculate angle of series. - void _calculateAngle(CircularSeriesRendererExtension seriesRenderer) { - seriesRenderer.segmentRenderingValues['start'] = - currentSeries.startAngle < 0 - ? currentSeries.startAngle < -360 - ? (currentSeries.startAngle % 360) + 360 - : currentSeries.startAngle + 360 - : currentSeries.startAngle; - seriesRenderer.segmentRenderingValues['end'] = currentSeries.endAngle < 0 - ? currentSeries.endAngle < -360 - ? (currentSeries.endAngle % 360) + 360 - : currentSeries.endAngle + 360 - : currentSeries.endAngle; - seriesRenderer.segmentRenderingValues['start'] = - seriesRenderer.segmentRenderingValues['start']! > 360 == true - ? seriesRenderer.segmentRenderingValues['start']! % 360 - : seriesRenderer.segmentRenderingValues['start']!; - seriesRenderer.segmentRenderingValues['end'] = - seriesRenderer.segmentRenderingValues['end']! > 360 == true - ? seriesRenderer.segmentRenderingValues['end']! % 360 - : seriesRenderer.segmentRenderingValues['end']!; - seriesRenderer.segmentRenderingValues['start'] = - seriesRenderer.segmentRenderingValues['start']! - 90; - seriesRenderer.segmentRenderingValues['end'] = - seriesRenderer.segmentRenderingValues['end']! - 90; - seriesRenderer.segmentRenderingValues['end'] = - seriesRenderer.segmentRenderingValues['start']! == - seriesRenderer.segmentRenderingValues['end']! - ? seriesRenderer.segmentRenderingValues['start']! + 360 - : seriesRenderer.segmentRenderingValues['end']!; - seriesRenderer.segmentRenderingValues['totalAngle'] = - seriesRenderer.segmentRenderingValues['start']! > - seriesRenderer.segmentRenderingValues['end']! == - true - ? (seriesRenderer.segmentRenderingValues['start']! - 360).abs() + - seriesRenderer.segmentRenderingValues['end']! - : (seriesRenderer.segmentRenderingValues['start']! - - seriesRenderer.segmentRenderingValues['end']!) - .abs(); - } - - /// To calculate radius of circular chart. - void _calculateRadius(CircularSeriesRendererExtension seriesRenderer) { - final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; - size = min(chartAreaRect.width, chartAreaRect.height); - seriesRenderer.segmentRenderingValues['currentRadius'] = - percentToValue(currentSeries.radius, size / 2)!.toDouble(); - seriesRenderer.segmentRenderingValues['currentInnerRadius'] = - percentToValue(currentSeries.innerRadius, - seriesRenderer.segmentRenderingValues['currentRadius']!)!; - } - - /// To calculate center location of chart. - void _calculateOrigin(CircularSeriesRendererExtension seriesRenderer) { - final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; - final Rect chartContainerRect = - stateProperties.renderingDetails.chartContainerRect; - seriesRenderer.center = Offset( - percentToValue(chart.centerX, chartAreaRect.width)!.toDouble(), - percentToValue(chart.centerY, chartAreaRect.height)!.toDouble()); - seriesRenderer.center = Offset( - seriesRenderer.center!.dx + - (chartContainerRect.width - chartAreaRect.width).abs() / 2, - seriesRenderer.center!.dy + - (chartContainerRect.height - chartAreaRect.height).abs() / 2); - stateProperties.centerLocation = seriesRenderer.center!; - } - - /// To find explode center position. - Offset _findExplodeCenter(num midAngle, - CircularSeriesRendererExtension seriesRenderer, num currentRadius) { - final num explodeCenter = - 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) { - 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]) { - bool isPointAlreadySelected = false; - pointIndex = pointRegion != null ? pointRegion.pointIndex : pointIndex; - seriesIndex = pointRegion != null ? pointRegion.seriesIndex : seriesIndex; - final CircularSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex!]; - int? currentSelectedIndex; - if (seriesRenderer.isSelectionEnable && mode == chart.selectionGesture) { - if (stateProperties.renderingDetails.selectionData.isNotEmpty) { - if (!chart.enableMultiSelection && - stateProperties.renderingDetails.selectionData.isNotEmpty && - stateProperties.renderingDetails.selectionData.length > 1) { - if (stateProperties.renderingDetails.selectionData - .contains(pointIndex)) { - currentSelectedIndex = pointIndex!; - } - stateProperties.renderingDetails.selectionData.clear(); - if (currentSelectedIndex != null) { - stateProperties.renderingDetails.selectionData.add(pointIndex!); - } - } - - int selectionIndex; - for (int i = 0; - i < stateProperties.renderingDetails.selectionData.length; - i++) { - selectionIndex = stateProperties.renderingDetails.selectionData[i]; - if (!chart.enableMultiSelection) { - isPointAlreadySelected = - stateProperties.renderingDetails.selectionData.length == 1 && - pointIndex == selectionIndex; - if (seriesRenderer.selectionBehavior.toggleSelection == true || - !isPointAlreadySelected) { - stateProperties.renderingDetails.selectionData.removeAt(i); - } - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!(_getSelectionEventArgs( - seriesRenderer, seriesIndex, selectionIndex)); - } - } else if (pointIndex == selectionIndex) { - if (seriesRenderer.selectionBehavior.toggleSelection == true) { - stateProperties.renderingDetails.selectionData.removeAt(i); - } - isPointAlreadySelected = true; - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!(_getSelectionEventArgs( - seriesRenderer, seriesIndex, selectionIndex)); - } - break; - } - } - } - if (!isPointAlreadySelected) { - stateProperties.renderingDetails.selectionData.add(pointIndex!); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!( - _getSelectionEventArgs(seriesRenderer, seriesIndex, pointIndex)); - } - } - } - } - - /// To perform selection event and return Selection Args. - SelectionArgs _getSelectionEventArgs( - dynamic seriesRenderer, int seriesIndex, int pointIndex) { - if (seriesRenderer != null) { - final SelectionBehavior selectionBehavior = - seriesRenderer.selectionBehavior; - final SelectionArgs args = SelectionArgs( - seriesRenderer: seriesRenderer, - seriesIndex: seriesIndex, - viewportPointIndex: pointIndex, - pointIndex: pointIndex); - args.selectedBorderColor = selectionBehavior.selectedBorderColor; - args.selectedBorderWidth = selectionBehavior.selectedBorderWidth; - args.selectedColor = selectionBehavior.selectedColor; - args.unselectedBorderColor = selectionBehavior.unselectedBorderColor; - args.unselectedBorderWidth = selectionBehavior.unselectedBorderWidth; - args.unselectedColor = selectionBehavior.unselectedColor; - seriesRenderer.selectionArgs = args; - } - return seriesRenderer.selectionArgs as SelectionArgs; - } - - /// Method to explode the series point. - void seriesPointExplosion(Region? pointRegion) { - bool existExplodedRegion = false; - final CircularSeriesRendererExtension seriesRenderer = stateProperties - .chartSeries.visibleSeriesRenderers[pointRegion!.seriesIndex]; - final ChartPoint point = - seriesRenderer.renderPoints![pointRegion.pointIndex]; - int explodeIndex; - if (seriesRenderer.series.explode) { - if (stateProperties.renderingDetails.explodedPoints.isNotEmpty) { - if (stateProperties.renderingDetails.explodedPoints.length == 1 && - stateProperties.renderingDetails.explodedPoints - .contains(pointRegion.pointIndex)) { - existExplodedRegion = true; - point.center = seriesRenderer.center; - final int index = stateProperties.renderingDetails.explodedPoints - .indexOf(pointRegion.pointIndex); - stateProperties.renderingDetails.explodedPoints.removeAt(index); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - stateProperties - .renderDataLabel!.state.dataLabelRepaintNotifier.value++; - } else if (seriesRenderer.series.explodeAll && - stateProperties.renderingDetails.explodedPoints.length > 1 && - stateProperties.renderingDetails.explodedPoints - .contains(pointRegion.pointIndex)) { - for (int i = 0; - i < stateProperties.renderingDetails.explodedPoints.length; - i++) { - explodeIndex = stateProperties.renderingDetails.explodedPoints[i]; - seriesRenderer.renderPoints![explodeIndex].center = - seriesRenderer.center; - stateProperties.renderingDetails.explodedPoints.removeAt(i); - i--; - } - existExplodedRegion = true; - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - stateProperties - .renderDataLabel!.state.dataLabelRepaintNotifier.value++; - } else if (stateProperties.renderingDetails.explodedPoints.length == - 1) { - for (int i = 0; - i < stateProperties.renderingDetails.explodedPoints.length; - i++) { - explodeIndex = stateProperties.renderingDetails.explodedPoints[i]; - seriesRenderer.renderPoints![explodeIndex].center = - seriesRenderer.center; - stateProperties.renderingDetails.explodedPoints.removeAt(i); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - stateProperties - .renderDataLabel!.state.dataLabelRepaintNotifier.value++; - } - } - } - if (!existExplodedRegion) { - point.center = _findExplodeCenter( - point.midAngle!, seriesRenderer, point.outerRadius!); - stateProperties.renderingDetails.explodedPoints - .add(pointRegion.pointIndex); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - stateProperties.renderDataLabel!.state.dataLabelRepaintNotifier.value++; - } - } - } - - /// Setting series type. - void _setSeriesType(CircularSeriesRendererExtension seriesRenderer) { - if (seriesRenderer is PieSeriesRendererExtension) { - seriesRenderer.seriesType = 'pie'; - } else if (seriesRenderer is DoughnutSeriesRendererExtension) { - seriesRenderer.seriesType = 'doughnut'; - } else if (seriesRenderer is RadialBarSeriesRendererExtension) { - seriesRenderer.seriesType = 'radialbar'; - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart deleted file mode 100644 index 4cfac0022..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../utils/enum.dart'; -import 'circular_series.dart'; -import 'renderer_extension.dart'; - -/// It is the data type for the circular chart and it has the properties is used to assign at the value -/// declaration of the circular chart. -/// -/// It provides the options for color, stroke color, fill color, radius, angle to customize the circular chart. -/// -class ChartPoint { - /// Creating an argument constructor of ChartPoint class. - ChartPoint([this.x, this.y, this.radius, this.pointColor, this.sortValue]); - - /// X value of chart point. - dynamic x; - - /// Y value of chart point. - num? y; - - /// Degree of chart point. - num? degree; - - /// Start angle of chart point. - num? startAngle; - - /// End angle of chart point. - num? endAngle; - - /// Middle angle of chart point. - num? midAngle; - - /// Center position of chart point. - Offset? center; - - /// Text value of chart point. - String? text; - - /// Fill color of the chart point. - late Color fill; - - /// Color of chart point. - late Color color; - - /// Stroke color of chart point. - late Color strokeColor; - - /// Sort value of chart point. - D? sortValue; - - /// Stroke width of chart point. - late num strokeWidth; - - /// Inner radius of chart point. - num? innerRadius; - - /// Outer radius of chart point. - num? outerRadius; - - /// To set the explode value of chart point. - bool? isExplode; - - /// To set the shadow value of chart point. - bool? isShadow; - - /// To set the empty value of chart point. - bool isEmpty = false; - - /// To set the visibility of chart point. - bool isVisible = true; - - /// To set the selected or unselected of chart point. - bool isSelected = false; - - /// Data label position of chart point. - late Position dataLabelPosition; - - /// Render position of chart point. - ChartDataLabelPosition? renderPosition; - - /// Label rect of chart point. - late Rect labelRect; - - /// Size of the Data label of chart point. - Size dataLabelSize = Size.zero; - - /// Saturation region value of chart point. - bool saturationRegionOutside = false; - - /// Y ratio of chart point. - late num yRatio; - - /// Height Ratio of chart point. - late num heightRatio; - - /// Radius of the chart point. - String? radius; - - /// Color property of the chart point. - Color? pointColor; - - /// To store the trimmed text, if the label renders outside the container area. - String? trimmedText; - - /// Stores the segment overflow data label trimmed text. - String? overflowTrimmedText; - - /// To execute [onTooltipRender] event or not. - // ignore: prefer_final_fields - bool isTooltipRenderEvent = false; - - /// 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; - - /// Holds the value either 0 or 1 to denote whether the current label is positioned smartly. - num? _isLabelUpdated; - - /// Holds the shifted angle of chart point. - num? _newAngle; - - /// 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; -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class for handling private fields. -class PointHelper { - /// Returns the value of pathRect for given point. - static Rect? getPathRect(ChartPoint point) => point._pathRect; - - /// Returns the value that data label is updated or not. - static num? getLabelUpdated(ChartPoint point) => - point._isLabelUpdated; - - /// Stores the value of pathRect for a given point. - static void setPathRect(ChartPoint point, Rect? pathRect) { - point._pathRect = pathRect; - } - - /// Stores the value 1 or 0 to indicate the label is updated or not. - static void setLabelUpdated(ChartPoint point, num? isLabelUpdated) { - point._isLabelUpdated = isLabelUpdated; - } - - /// Returns the value of `_pointShaderMapper` for given point. - static ChartShaderMapper? getPointShaderMapper( - ChartPoint point) => - point._pointShaderMapper; - - /// Return the value of the shifted data label angle. - static num? getNewAngle(ChartPoint point) => point._newAngle; - - /// Stores the shifted data label angle. - static void setNewAngle(ChartPoint point, num? newAngle) { - point._newAngle = newAngle; - } -} - -/// To get current point -ChartPoint getCircularPoint( - CircularSeriesRendererExtension seriesRenderer, int pointIndex) { - final CircularSeries series = seriesRenderer.series; - 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; - 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); - } - - if (xVal != null) { - if (yMap != null) { - yVal = yMap(pointIndex); - } - - if (radiusMap != null) { - radiusVal = radiusMap(pointIndex); - } - - if (colorMap != null) { - colorVal = colorMap(pointIndex); - } - if (sortFieldMap != null) { - sortVal = sortFieldMap(pointIndex); - } - - 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; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart deleted file mode 100644 index 96b7796fd..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart +++ /dev/null @@ -1,333 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/utils/enum.dart'; -import '../utils/enum.dart'; - -/// Customizes the annotation of the circular chart. -/// -/// Circular chart allows you to mark the specific area of interest in the chart area. -/// You can add the custom widgets using this annotation feature, It has the properties for customizing the appearance. -/// -/// The angle, orientation, height, and width of the inserted annotation can all be customized. -/// -/// It provides options for angle, height, width, vertical and horizontal alignment to customize the appearance. -/// -class CircularChartAnnotation { - /// Creating an argument constructor of CircularChartAnnotation class. - CircularChartAnnotation( - {int? angle, - String? radius, - this.widget, - String? height, - String? width, - ChartAlignment? horizontalAlignment, - ChartAlignment? verticalAlignment}) - : angle = angle ?? 0, - radius = radius ?? '0%', - height = height ?? '0%', - width = width ?? '0%', - verticalAlignment = verticalAlignment ?? ChartAlignment.center, - horizontalAlignment = horizontalAlignment ?? ChartAlignment.center; - - /// Angle to rotate the annotation. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// angle: 40, - /// widget: const Text('Circular'), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final int angle; - - /// Radius for placing the annotation. The value ranges from 0% to 100% and also if values are - /// mentioned in pixel then the annotation will moved accordingly. - /// - /// For example, if set `50%` means the annotation starting text will be placed from the `50%` - /// chart area or we set `50` means the annotation starting text will be placed from 50 pixels. - /// - /// Defaults to `0%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// radius: '10%' - /// widget: const Text('Circular'), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String radius; - - /// Considers any widget as annotation. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// widget: const Text('Circular'), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final Widget? widget; - - /// Height of the annotation. - /// - /// Defaults to `0%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// height: '10%', - /// widget: const Text('Circular'), - /// ), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String height; - - /// Width of the annotation. - /// - /// Defaults to `0%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// width: '10%', - /// widget: const Text('Circular'), - /// ), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String width; - - /// Aligns the annotation horizontally. - /// - /// Alignment can be set to `ChartAlignment.center`, `ChartAlignment.far`, or `ChartAlignment.near`. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// Also refer [ChartAlignment]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// horizontalAlignment: ChartAlignment.near - /// widget: const Text('Circular'), - /// ), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartAlignment horizontalAlignment; - - /// Aligns the annotation vertically. - /// - /// Alignment can be set to `ChartAlignment.center`, `ChartAlignment.far`, or `ChartAlignment.near`. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// Also refer [ChartAlignment]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// verticalAlignment: ChartAlignment.near - /// widget: const Text('Circular'), - /// ), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartAlignment verticalAlignment; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is CircularChartAnnotation && - other.angle == angle && - other.radius == radius && - other.height == height && - other.horizontalAlignment == horizontalAlignment && - other.verticalAlignment == verticalAlignment && - other.widget == widget && - other.width == width; - } - - @override - int get hashCode { - final List values = [ - angle, - radius, - height, - horizontalAlignment, - verticalAlignment, - widget, - width - ]; - return hashList(values); - } -} - -/// This class holds the properties of the connector line. -/// -/// ConnectorLineSetting is the Argument type of [DataLabelSettings], It is used to customize the data label connected lines while the data label -/// position is outside the chart. It is enabled by setting the data label visibility. -/// -/// It provides the options for length, width, color, and enum type [ConnectorType] to customize the appearance. -/// -class ConnectorLineSettings { - /// Creating an argument constructor of ConnectorLineSettings class. - const ConnectorLineSettings( - {this.length, double? width, ConnectorType? type, this.color}) - : width = width ?? 1.0, - type = type ?? ConnectorType.line; - - /// Length of the connector line. The value range from 0% to 100%. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// length: '8%' - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final String? length; - - /// Width of the connector line. - /// - /// Defaults to `1.0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// width: 2 - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final double width; - - /// Color of the connector line. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// color: Colors.red, - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final Color? color; - - /// Type of the connector line. - /// - /// Defaults to `ConnectorType.line`. - /// - /// Also refer [ConnectorType]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// type: ConnectorType.curve - /// ) - /// ) - /// ) - /// ); - ///} - ///``` - final ConnectorType type; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ConnectorLineSettings && - other.length == length && - other.width == width && - other.color == color && - other.type == type; - } - - @override - int get hashCode { - final List values = [length, width, color, type]; - return hashList(values); - } -} 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 deleted file mode 100644 index b7e0a1bf9..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart +++ /dev/null @@ -1,1128 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/series/chart_series.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../base/circular_base.dart'; -import '../renderer/renderer_base.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/enum.dart'; -import 'chart_point.dart'; - -/// This class holds the property of circular series. -/// -/// To render the Circular chart, create an instance of [PieSeries] or [DoughnutSeries] or [RadialBarSeries], and add it to the -/// series collection property of [SfCircularChart]. You can use the radius property to change the diameter of the circular chart for the plot area. -/// Also, explode the circular chart segment by enabling the explode property. -/// -/// Provide the options of stroke width, stroke color, opacity, and point color mapper to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} -class CircularSeries extends ChartSeries - implements CircularChartEmptyPointBehavior { - /// Creating an argument constructor of CircularSeries class. - CircularSeries( - {this.key, - this.onCreateRenderer, - this.onRendererCreated, - this.onPointTap, - this.onPointDoubleTap, - this.onPointLongPress, - this.dataSource, - 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, - this.explodeIndex, - ActivationMode? explodeGesture, - String? explodeOffset, - this.groupTo, - this.groupMode, - this.pointRenderMode, - String? gap, - double? opacity, - EmptyPointSettings? emptyPointSettings, - Color? borderColor, - double? borderWidth, - DataLabelSettings? dataLabelSettings, - bool? enableTooltip, - this.name, - double? animationDuration, - double? animationDelay, - SelectionBehavior? selectionBehavior, - SortingOrder? sortingOrder, - LegendIconType? legendIconType, - CornerStyle? cornerStyle, - List? initialSelectedDataIndexes}) - : startAngle = startAngle ?? 0, - animationDuration = animationDuration ?? 1500, - animationDelay = animationDelay ?? 0, - endAngle = endAngle ?? 360, - radius = radius ?? '80%', - innerRadius = innerRadius ?? '50%', - explode = explode ?? false, - explodeAll = explodeAll ?? false, - explodeOffset = explodeOffset ?? '10%', - explodeGesture = explodeGesture ?? ActivationMode.singleTap, - gap = gap ?? '1%', - cornerStyle = cornerStyle ?? CornerStyle.bothFlat, - dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), - emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), - selectionBehavior = selectionBehavior ?? SelectionBehavior(), - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - opacity = opacity ?? 1, - enableTooltip = enableTooltip ?? true, - sortingOrder = sortingOrder ?? SortingOrder.none, - legendIconType = legendIconType ?? LegendIconType.seriesType, - initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - super(name: name); - - /// Opacity of the series. The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// opacity: 1, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final double opacity; - - /// Customizes the data labels in a series. Data label is a text, which displays - /// the details about the data point. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final DataLabelSettings dataLabelSettings; - - /// A collection of data required for rendering the series. - /// - /// If no data source is specified, - /// empty chart will be rendered without series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final List? dataSource; - - /// Maps the field name, which will be considered as x-values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? xValueMapper; - - /// Maps the field name, which will be considered as y-values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? yValueMapper; - - /// Maps the field name, which will be considered as data point color. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10, Colors.red), - /// ChartData('China', 11, Colors.green), - /// ChartData('Russia', 9, Colors.blue), - /// ChartData('Germany', 10, Colors.voilet), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// pointColorMapper: (ChartData data, _) => data.pointColor, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - @override - 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 a gradient - /// and another data point may have an image shader. - /// - /// The user can also get the data, index, color and rect values of the specific data point from [ChartShaderMapper] and - /// can use this method, for creating shaders. - /// - /// Defaults to `null`. - /// - ///```dart - ///import 'dart:ui' as ui; - /// - ///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, - /// ]),), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// pointShaderMapper: (ChartData data, _, Color color, Rect rect) => data.pointShader, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointShader]); - /// 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. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10, '50%'), - /// ChartData('China', 11, '55%'), - /// ChartData('Russia', 9, '60%'), - /// ChartData('Germany', 10, '65%'), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// pointRadiusMapper: (ChartData data, _) => data.radius, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.radius]); - /// final String xVal; - /// final int yVal; - /// final String radius; - ///} - ///``` - final ChartIndexedValueMapper? pointRadiusMapper; - - /// Maps the field name, which will be considered as a text for the data points. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// dataLabelMapper: (ChartData data, _) => data.xVal, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? dataLabelMapper; - - /// Field in the data source for performing sorting. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// sortFieldValueMapper: (ChartData data, _) => data.xVal, - /// ), - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? sortFieldValueMapper; - - /// 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. - /// - /// Defaults to `LegendIconType.seriesType`. - /// - /// Also refer [LegendIconType]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// legendIconType: LegendIconType.diamond, - /// ) - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final LegendIconType legendIconType; - - /// Type of sorting. - /// - /// 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. - /// - /// Default to `SortingOrder.none`. - /// - /// Also refer [SortingOrder]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// sortingOrder: SortingOrder.ascending, - /// ) - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final SortingOrder sortingOrder; - - /// Determines whether to enable tooltip. - /// - /// Defaults to `true`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// enableTooltip: true, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final bool enableTooltip; - - /// Border width of the data points in the series. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// borderWidth: 2 - /// ) - /// ); - ///} - ///``` - @override - final double borderWidth; - - /// Border color of the data points in the series. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// borderColor: Colors.red - /// ) - /// ); - ///} - ///``` - @override - final Color borderColor; - - /// Customizes the empty data points in the series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// emptyPointSettings: EmptyPointSettings (color: Colors.red) - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final EmptyPointSettings emptyPointSettings; - - /// Customizes the selection of series. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final SelectionBehavior selectionBehavior; - - /// Starting angle of the series. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// startAngle: 270; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final int startAngle; - - /// Ending angle of the series. - /// - /// Defaults to `360`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// endAngle: 270; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final int endAngle; - - /// Radius of the series. - /// - /// The value ranges from 0% to 100%. - /// - /// Defaults to `80%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// radius: '10%'; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String radius; - - /// Inner radius of the series. - /// - /// The value ranges from 0% to 100%. - /// - /// Defaults to `50%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// DoughnutSeries( - /// innerRadius: '20%'; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String innerRadius; - - /// Enables or disables the explode of slices on tap. - /// - /// Default to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// explode: true; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final bool explode; - - /// Enables or disables exploding all the slices at the initial rendering. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// explodeAll: true - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final bool explodeAll; - - /// Index of the slice to explode it at the initial rendering. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// explode: true, - /// explodeIndex: 2 - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final int? explodeIndex; - - /// Offset of exploded slice. The value ranges from 0% to 100%. - /// - /// Defaults to `20%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// explode: true, - /// explodeOffset: '30%' - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String explodeOffset; - - /// Gesture for activating the explode. - /// - /// Explode can be activated in `ActivationMode.none`, `ActivationMode.singleTap`, `ActivationMode.doubleTap`, - /// and `ActivationMode.longPress`. - /// - /// Defaults to `ActivationMode.singleTap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// explode: true, - /// explodeGesture: ActivationMode.singleTap - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ActivationMode explodeGesture; - - /// Groups the data points of the series based on their index or values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// groupTo: 4, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final double? groupTo; - - /// Slice can also be grouped based on the data points value or based on index. - /// - /// Defaults to `CircularChartGroupMode.point`. - /// - /// Also refer [CircularChartGroupMode]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// groupMode: CircularChartGroupMode.value, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - 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 and - /// not applicable for radial bar series. - /// - /// Also refer [PointRenderMode]. - /// - /// Defaults to `pointRenderMode.segment`. - final PointRenderMode? pointRenderMode; - - /// Specifies the gap between the radial bars in percentage. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// gap: '10%', - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final String gap; - - /// Specifies the radial bar’s corner type. - /// - /// _Note:_ This is applicable only for radial bar series type. - /// - /// Defaults to `CornerStyle.bothFlat`. - /// - /// Also refer [CornerStyle]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// cornerStyle: CornerStyle.bothCurve, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final CornerStyle cornerStyle; - - /// Name of the series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// name: 'default', - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final String? name; - - /// Duration for animating the data points. - /// - /// Defaults to `1500`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// animationDuration: 3000; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final double animationDuration; - - /// Delay duration of the series animation. It takes a millisecond value as input. - /// By default, the series will get animated for the specified duration. - /// If animationDelay is specified, then the series will begin to animate - /// after the specified duration. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// animationDelay: 500; - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - @override - final double animationDelay; - - /// List of data indexes initially selected. - /// - /// Defaults to `null`. - ///```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// child: SfCircularChart( - /// initialSelectedDataIndexes: [IndexesModel(1, 0)] - /// ) - /// ) - /// ) - /// ); - /// } - List initialSelectedDataIndexes; - - /// Key to identify a series in a collection. - /// - /// On specifying [ValueKey] as the series [key], existing series index can be changed in the series collection without losing its state. - /// - /// When a new series is added dynamically to the collection, existing series index will be changed. On that case, - /// the existing series and its state will be linked based on its chart type and this key value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// key: const ValueKey('pie_series_key'), - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ValueKey? key; - - /// Used to create the renderer for custom series. - /// - /// This is applicable only when the custom series is defined in the sample - /// and for built-in series types, it is not applicable. - /// - /// Renderer created in this will hold the series state and - /// this should be created for each series. [onCreateRenderer] callback - /// function should return the renderer class and should not return null. - /// - /// Series state will be created only once per series and will not be created - /// again when we update the series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// onCreateRenderer:(CircularSeries series){ - /// return CustomPieSeriesRenderer(); - /// } - /// ), - /// ], - /// ) - /// ); - /// } - /// class CustomPieSeriesRenderer extends PieSeriesRenderer { - /// // custom implementation here... - /// } - ///``` - final ChartSeriesRendererFactory? onCreateRenderer; - - /// Called when tapped on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointTap; - - /// Called when double tapped on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the double-tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointDoubleTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointDoubleTap; - - /// Called when long pressed on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the long-pressed data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointLongPress: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointLongPress; - - /// Triggers when the series renderer is created. - /// - /// Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final CircularSeriesRendererCreatedCallback? onRendererCreated; - - /// To calculate empty point values. - @override - void calculateEmptyPointValue( - int pointIndex, ChartPoint currentPoint, - [CircularSeriesRenderer? seriesRenderer]) { - final EmptyPointSettings empty = emptyPointSettings; - final List>? dataPoints = - (seriesRenderer as CircularSeriesRendererExtension?)?.dataPoints; - final int pointLength = dataPoints!.length; - final ChartPoint point = dataPoints[pointIndex]; - if (point.y == null) { - switch (empty.mode) { - case EmptyPointMode.average: - final num previous = - pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; - final num next = pointIndex + 1 <= pointLength - 1 - ? dataPoints[pointIndex + 1].y ?? 0 - : 0; - point.y = (previous + next).abs() / 2; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.zero: - point.y = 0; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.drop: - case EmptyPointMode.gap: - point.isEmpty = true; - point.isVisible = false; - break; - } - } - } -} - -/// To get visible point index. -int? getVisiblePointIndex( - List?> points, String loc, int index) { - if (loc == 'before') { - for (int i = index; i >= 0; i--) { - if (points[i - 1]!.isVisible) { - return i - 1; - } - } - } else { - for (int i = index; i < points.length; i++) { - if (points[i + 1]!.isVisible) { - return i + 1; - } - } - } - return null; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart deleted file mode 100644 index 018e105f2..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart +++ /dev/null @@ -1,247 +0,0 @@ -import 'dart:ui'; -import '../base/circular_state_properties.dart'; -import '../utils/helper.dart'; -import 'chart_point.dart'; -import 'circular_series.dart'; -import 'renderer_base.dart'; -import 'renderer_extension.dart'; - -/// We can redraw the series by 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. -class CircularSeriesController { - /// Creating an argument constructor of CircularSeriesController class. - CircularSeriesController(this.seriesRenderer); - - /// Used to access the series properties. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCircularChart( - /// series: PieSeries( - /// onRendererCreated: (CircularSeriesController controller) { - /// _chartSeriesController = controller; - /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); - /// }, - /// ), - /// ) - /// ); - ///} - ///``` - 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. - /// - /// To re-render the chart with modified data points, setState() will be called. - /// This will render the process and render the chart from scratch. - /// Thus, the app’s performance will be degraded on continuous update. - /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. - /// Chart will process only that point and skip various steps like bounds calculation, - /// old data points processing, etc. Thus, this will improve the app’s performance. - /// - /// The following are the arguments of this method. - /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. - /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. - /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. - /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. - /// * removedDataIndex – `int` type – Index of removed data point in the existing series. - /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. - /// - /// Returns `void`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// CircularSeriesController seriesController; - /// return Column( - /// children: [ - /// Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: chartData, - /// onRendererCreated: (CircularSeriesController controller) { - /// seriesController = controller; - /// }, - /// ), - /// ], - /// ) - /// ), - /// Container( - /// child: RaisedButton( - /// onPressed: () { - /// chartData.removeAt(0); - /// chartData.add(ChartData(3,23)); - /// seriesController.updateDataSource( - /// addedDataIndexes: [chartData.length -1], - /// removedDataIndexes: [0], - /// ); - /// }) - /// )] - /// ); - /// } - ///``` - void updateDataSource( - {List? addedDataIndexes, - List? removedDataIndexes, - List? updatedDataIndexes, - int? addedDataIndex, - int? removedDataIndex, - int? updatedDataIndex}) { - if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { - _removeDataPointsList(removedDataIndexes); - } else if (removedDataIndex != null) { - _removeDataPoint(removedDataIndex); - } - if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(addedDataIndexes, false); - } else if (addedDataIndex != null) { - _addOrUpdateDataPoint(addedDataIndex, false); - } - if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(updatedDataIndexes, true); - } else if (updatedDataIndex != null) { - _addOrUpdateDataPoint(updatedDataIndex, true); - } - _updateCircularSeries(); - } - - /// Add or update the data points on dynamic series update. - void _addOrUpdateDataPoints(List indexes, bool needUpdate) { - int dataIndex; - for (int i = 0; i < indexes.length; i++) { - dataIndex = indexes[i]; - _addOrUpdateDataPoint(dataIndex, needUpdate); - } - } - - /// Add or update a data point in the given index. - void _addOrUpdateDataPoint(int index, bool needUpdate) { - final CircularSeriesRendererExtension renderer = - seriesRenderer as CircularSeriesRendererExtension; - final CircularSeries series = renderer.series; - if (index >= 0 && - series.dataSource!.length > index && - series.dataSource![index] != null) { - final ChartPoint _currentPoint = - getCircularPoint(renderer, index); - if (_currentPoint.x != null) { - if (needUpdate) { - if (renderer.dataPoints.length > index) { - renderer.dataPoints[index] = _currentPoint; - } - } else { - if (renderer.dataPoints.length == index) { - renderer.dataPoints.add(_currentPoint); - } else if (renderer.dataPoints.length > index && index >= 0) { - renderer.dataPoints.insert(index, _currentPoint); - } - } - } - } - } - - /// Remove the list of points. - void _removeDataPointsList(List removedDataIndexes) { - //Remove the redundant index from the list - final List indexList = removedDataIndexes.toSet().toList(); - indexList.sort((int b, int a) => a.compareTo(b)); - int dataIndex; - for (int i = 0; i < indexList.length; i++) { - dataIndex = indexList[i]; - _removeDataPoint(dataIndex); - } - } - - /// Remove a data point in the given index. - void _removeDataPoint(int index) { - final CircularSeriesRendererExtension renderer = - seriesRenderer as CircularSeriesRendererExtension; - if (renderer.dataPoints.isNotEmpty && - index >= 0 && - index < renderer.dataPoints.length) { - renderer.dataPoints.removeAt(index); - } - } - - /// After add/remove/update data points, recalculate the chart angle and positions. - void _updateCircularSeries() { - final CircularSeriesRendererExtension renderer = - seriesRenderer as CircularSeriesRendererExtension; - final CircularStateProperties stateProperties = renderer.stateProperties; - stateProperties.chartSeries.processDataPoints(renderer); - stateProperties.chartSeries.calculateAngleAndCenterPositions(renderer); - renderer.repaintNotifier.value++; - if (renderer.series.dataLabelSettings.isVisible && - stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state.render(); - } - if (renderer.series.dataLabelSettings.isVisible && - stateProperties.renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - stateProperties.renderingDetails.chartTemplate!.state != null) { - stateProperties.renderingDetails.chartTemplate!.state.templateRender(); - } - } - - /// Converts chart data point value to logical pixel value. - /// - /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. - /// - /// _Note_: It returns the data point's center location value. - /// - ///```dart - /// late CircularSeriesController seriesController; - /// SfCircularChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { - /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); - /// Offset value = seriesController.pointToPixel(chartPoint); - /// ChartPoint chartPoint1 = seriesController.pixelToPoint(value); - /// }, - /// series: >[ - /// PieSeries( - /// onRendererCreated: (CircularSeriesController controller) { - /// seriesController = controller; - /// } - /// ) - /// ] - /// ) - /// ``` - - // ignore: unused_element - Offset _pointToPixel(ChartPoint point) { - return circularPointToPixel(point, - (seriesRenderer as CircularSeriesRendererExtension).stateProperties); - } - - /// Converts logical pixel value to the data point value. - /// - /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. - /// - ///```dart - /// late CircularSeriesController seriesController; - /// SfCircularChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { - /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); - /// Offset value = seriesController.pointToPixel(chartPoint); - /// }, - /// series: >[ - /// PieSeries( - /// onRendererCreated: (CircularSeriesController controller) { - /// seriesController = controller; - /// } - /// ) - /// ] - /// ) - /// ``` - - ChartPoint pixelToPoint(Offset position) { - final CircularSeriesRendererExtension renderer = - seriesRenderer as CircularSeriesRendererExtension; - return circularPixelToPoint(position, renderer.stateProperties); - } -} 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 deleted file mode 100644 index 025c31e4e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart +++ /dev/null @@ -1,364 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../base/circular_base.dart'; -import '../base/circular_state_properties.dart'; -import 'chart_point.dart'; -import 'circular_series.dart'; -import 'radial_bar_series.dart'; -import 'renderer_extension.dart'; - -/// Represents the region. -/// -class Region { - /// Creates the instance for region. - /// - Region(this.start, this.end, this.startAngle, this.endAngle, this.seriesIndex, - this.pointIndex, this.center, this.innerRadius, this.outerRadius); - - /// Specifies the series index. - int seriesIndex; - - /// Specifies the point index. - int pointIndex; - - /// Specifies the start angle. - num startAngle; - - /// Specifies the start value. - num start; - - /// Specifies the end value. - num end; - - /// Specifies the end angle. - num endAngle; - - ///Specifies the center value. - Offset? center; - - /// Specifies the value of inner radius. - num? innerRadius; - - /// Specifies the value of outer radius. - num outerRadius; -} - -/// Represents the style options. -/// -class StyleOptions { - /// Creates the instance of style options. - /// - StyleOptions({this.fill, this.strokeWidth, this.strokeColor, this.opacity}); - - /// Specifies the value of fill. - Color? fill; - - /// Specifies the value of stroke color. - Color? strokeColor; - - /// Specifies the value of opacity. - double? opacity; - - /// Specifies the value of stroke width. - num? strokeWidth; -} - -/// Represents the circular chart interaction. -/// -class ChartInteraction { - /// Creates the instance for circular chart interaction. - /// - ChartInteraction(this.seriesIndex, this.pointIndex, this.series, this.point, - [this.region]); - - /// Specifies the value of series index. - int? seriesIndex; - - /// Specifies the value of point index. - int? pointIndex; - - /// Specifies the value of series. - dynamic series; - - /// Specifies the point value. - dynamic point; - - /// Specifies the value of region. - Region? region; -} - -/// To get circular series data label saturation color. -Color getCircularDataLabelColor( - ChartPoint currentPoint, - CircularSeriesRendererExtension seriesRenderer, - CircularStateProperties stateProperties) { - Color color; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - final String seriesType = seriesRenderer.seriesType == 'pie' - ? 'Pie' - : seriesRenderer.seriesType == 'doughnut' - ? 'Doughnut' - : seriesRenderer.seriesType == 'radialbar' - ? 'RadialBar' - : 'Default'; - switch (seriesType) { - case 'Pie': - case 'Doughnut': - color = (currentPoint.renderPosition == ChartDataLabelPosition.inside && - !currentPoint.saturationRegionOutside) - ? getInnerColor(dataLabelSettingsRenderer.color, currentPoint.fill, - stateProperties.renderingDetails.chartTheme) - : getOuterColor( - dataLabelSettingsRenderer.color, - dataLabel.useSeriesColor - ? currentPoint.fill - : (stateProperties.chart.backgroundColor ?? - stateProperties - .renderingDetails.chartTheme.plotAreaBackgroundColor), - stateProperties.renderingDetails.chartTheme); - break; - case 'RadialBar': - final RadialBarSeries radialBar = - seriesRenderer.series as RadialBarSeries; - color = radialBar.trackColor; - break; - default: - color = Colors.white; - } - return getSaturationColor(color); -} - -/// To get inner data label color. -Color getInnerColor( - Color? dataLabelColor, Color? pointColor, SfChartThemeData theme) => - // ignore: prefer_if_null_operators - dataLabelColor != null - ? dataLabelColor - // ignore: prefer_if_null_operators - : pointColor != null - ? pointColor - : theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; - -/// To get outer data label color. -Color getOuterColor( - Color? dataLabelColor, Color backgroundColor, SfChartThemeData theme) => - // ignore: prefer_if_null_operators - dataLabelColor != null - ? dataLabelColor - : backgroundColor != Colors.transparent - ? backgroundColor - : theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; - -/// To check whether any point is selected. -bool checkIsAnyPointSelect(CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, SfCircularChart chart) { - bool isAnyPointSelected = false; - final CircularSeries series = seriesRenderer.series; - if (series.initialSelectedDataIndexes.isNotEmpty) { - int data; - for (int i = 0; i < series.initialSelectedDataIndexes.length; i++) { - data = series.initialSelectedDataIndexes[i]; - for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { - if (j == data) { - isAnyPointSelected = true; - break; - } - } - } - } - return isAnyPointSelected; -} - -/// Represents the circular chart segment. -abstract class CircularChartSegment { - /// To get point color of current point. - Color? getPointColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color color, - double opacity); - - /// To get opacity of current point. - double getOpacity( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - double opacity); - - /// To get Stroke color of current point. - Color getPointStrokeColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - Color strokeColor); - - /// To get Stroke width of current point. - num getPointStrokeWidth( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - num strokeWidth); -} - -/// Represents the label segment. -abstract class LabelSegment { - /// To get label text content. - String getLabelContent( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - String content); - - /// To get text style of current point. - TextStyle getDataLabelStyle( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - TextStyle style, - SfCircularChartState _chartState); - - /// To get data label color. - Color? getDataLabelColor(CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, int pointIndex, int seriesIndex, Color? color); - - /// To get the data label stroke color. - Color getDataLabelStrokeColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color strokeColor); - - /// To get label stroke width. - double getDataLabelStrokeWidth( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - double strokeWidth); -} - -/// Represents the chart series renderer. -class ChartSeriesRender with CircularChartSegment, LabelSegment { - /// Creates an instance for chart series renderer. - ChartSeriesRender(); - - /// Creates an instance for chart series renderer. - @override - Color? getPointColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color color, - double opacity) => - color.withOpacity(opacity); - - /// To get point stroke color. - @override - Color getPointStrokeColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - Color strokeColor) => - strokeColor; - - /// To get point stroke width. - @override - num getPointStrokeWidth( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - num strokeWidth) => - strokeWidth; - - /// To return label text. - @override - String getLabelContent( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - String content) => - content; - - /// To return text style of label. - @override - TextStyle getDataLabelStyle( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - TextStyle style, - SfCircularChartState _chartState) { - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final Color fontColor = dataLabel.textStyle.color ?? - getCircularDataLabelColor( - point, seriesRenderer, seriesRenderer.stateProperties); - final TextStyle textStyle = TextStyle( - color: fontColor, - fontSize: dataLabel.textStyle.fontSize, - fontFamily: dataLabel.textStyle.fontFamily, - fontStyle: dataLabel.textStyle.fontStyle, - fontWeight: dataLabel.textStyle.fontWeight); - return textStyle; - } - - /// To return label color. - @override - Color? getDataLabelColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color? color) => - color; - - /// To return label stroke color. - @override - Color getDataLabelStrokeColor( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color? strokeColor) => - strokeColor ?? point.fill; - - /// To return label stroke width. - @override - double getDataLabelStrokeWidth( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - double strokeWidth) => - strokeWidth; - - /// To return opacity of current point. - @override - double getOpacity( - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - double opacity) => - opacity; -} 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 deleted file mode 100644 index 40c9779dc..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart +++ /dev/null @@ -1,1403 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../chart/chart_series/xy_data_series.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../chart/utils/helper.dart'; -import '../../common/event_args.dart'; -import '../../common/utils/helper.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../base/circular_base.dart'; -import '../base/circular_state_properties.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'chart_point.dart'; -import 'circular_chart_annotation.dart'; -import 'renderer_extension.dart'; - -/// Represents the circular data label renderer. -// ignore: must_be_immutable -class CircularDataLabelRenderer extends StatefulWidget { - /// Creates the instance of circular data label renderer. - // ignore: prefer_const_constructors_in_immutables - CircularDataLabelRenderer( - {required this.stateProperties, required this.show}); - - /// Specifies the circular chart state. - final CircularStateProperties stateProperties; - - /// Specifies whether to show the data label. - bool show; - - /// Specifies the state of circular data label renderer. - late CircularDataLabelRendererState state; - - @override - State createState() { - return CircularDataLabelRendererState(); - } -} - -/// Represents the circular data label renderer state. -class CircularDataLabelRendererState extends State - with SingleTickerProviderStateMixin { - /// Specifies the animation controller list. - late List animationControllersList; - - /// Animation controller for series. - late AnimationController animationController; - - /// Repaint notifier for data label container. - late ValueNotifier dataLabelRepaintNotifier; - - @override - void initState() { - dataLabelRepaintNotifier = ValueNotifier(0); - animationController = AnimationController(vsync: this) - ..addListener(repaintDataLabelElements); - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - animationController.duration = Duration( - milliseconds: - widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); - final Animation dataLabelAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), - )); - animationController.forward(from: 0.0); - return !widget.show - ? Container() - // ignore: avoid_unnecessary_containers - : Container( - child: RepaintBoundary( - child: CustomPaint( - painter: _CircularDataLabelPainter( - stateProperties: widget.stateProperties, - animation: dataLabelAnimation, - state: this, - notifier: dataLabelRepaintNotifier, - animationController: animationController)))); - } - - @override - void dispose() { - disposeAnimationController(animationController, repaintDataLabelElements); - super.dispose(); - } - - /// Method to notify the repaint notifier. - void repaintDataLabelElements() { - dataLabelRepaintNotifier.value++; - } - - /// Method to render the data label. - void render() { - setState(() { - widget.show = true; - }); - } -} - -class _CircularDataLabelPainter extends CustomPainter { - _CircularDataLabelPainter( - {required this.stateProperties, - required this.state, - required this.animationController, - required this.animation, - required ValueNotifier notifier}) - : super(repaint: notifier); - - final CircularStateProperties stateProperties; - - final CircularDataLabelRendererState state; - - final AnimationController animationController; - - final Animation animation; - - /// To paint data labels. - @override - void paint(Canvas canvas, Size size) { - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - CircularSeriesRendererExtension seriesRenderer; - for (int seriesIndex = 0; - seriesIndex < visibleSeriesRenderers.length; - seriesIndex++) { - seriesRenderer = visibleSeriesRenderers[seriesIndex]; - // ignore: unnecessary_null_comparison - if (seriesRenderer.series.dataLabelSettings != null && - seriesRenderer.series.dataLabelSettings.isVisible) { - seriesRenderer.dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer.series.dataLabelSettings); - renderCircularDataLabel( - seriesRenderer, canvas, stateProperties, seriesIndex, animation); - } - } - } - - @override - bool shouldRepaint(_CircularDataLabelPainter oldDelegate) => true; -} - -/// Decides to increase the angle or not. -bool isIncreaseAngle = false; - -/// To store the points which render at left and positioned outside. -List> leftPoints = >[]; - -/// To store the points which render at right and positioned outside. -List> rightPoints = >[]; - -/// Decrease the angle of the label if it intersects with labels. -void _decreaseAngle( - ChartPoint currentPoint, - ChartPoint previousPoint, - CircularSeriesRendererExtension seriesRenderer, - bool isRightSide) { - int count = 1; - if (isRightSide) { - while (isOverlap(currentPoint.labelRect, previousPoint.labelRect) || - (seriesRenderer.series.pointRadiusMapper != null && - (!((previousPoint.labelRect.height + previousPoint.labelRect.top) < - currentPoint.labelRect.top)))) { - int newAngle = PointHelper.getNewAngle(previousPoint)!.toInt() - count; - if (newAngle < 0) { - newAngle = 360 + newAngle; - } - if (newAngle <= 270 && newAngle >= 90) { - newAngle = 270; - isIncreaseAngle = true; - break; - } - _changeLabelAngle(previousPoint, newAngle, seriesRenderer); - count++; - } - } else { - if (PointHelper.getNewAngle(currentPoint)! > 270) { - _changeLabelAngle(currentPoint, 270, seriesRenderer); - PointHelper.setNewAngle(previousPoint, 270); - } - while (isOverlap(currentPoint.labelRect, previousPoint.labelRect) || - (seriesRenderer.series.pointRadiusMapper != null && - ((currentPoint.labelRect.top + currentPoint.labelRect.height) > - previousPoint.labelRect.bottom))) { - int newAngle = PointHelper.getNewAngle(previousPoint)!.toInt() - count; - if (!(newAngle <= 270 && newAngle >= 90)) { - newAngle = 270; - isIncreaseAngle = true; - break; - } - _changeLabelAngle(previousPoint, newAngle, seriesRenderer); - if (isOverlap(currentPoint.labelRect, previousPoint.labelRect) && - // ignore: unnecessary_null_comparison - leftPoints.indexOf(previousPoint) == null && - (newAngle - 1 < 90 && newAngle - 1 > 270)) { - _changeLabelAngle(currentPoint, - PointHelper.getNewAngle(currentPoint)! + 1, seriesRenderer); - _arrangeLeftSidePoints(seriesRenderer); - break; - } - count++; - } - } -} - -/// Increase the angle of the label if it intersects labels. -void _increaseAngle( - ChartPoint currentPoint, - ChartPoint nextPoint, - CircularSeriesRendererExtension seriesRenderer, - bool isRightSide) { - int count = 1; - if (isRightSide) { - while (isOverlap(currentPoint.labelRect, nextPoint.labelRect) || - (seriesRenderer.series.pointRadiusMapper != null && - (!((currentPoint.labelRect.top + currentPoint.labelRect.height) < - nextPoint.labelRect.top)))) { - int newAngle = PointHelper.getNewAngle(nextPoint)!.toInt() + count; - if (newAngle < 270 && newAngle > 90) { - newAngle = 90; - isIncreaseAngle = true; - break; - } - _changeLabelAngle(nextPoint, newAngle, seriesRenderer); - if (isOverlap(currentPoint.labelRect, nextPoint.labelRect) && - (newAngle + 1 > 90 && newAngle + 1 < 270) && - rightPoints.indexOf(nextPoint) == rightPoints.length - 1) { - _changeLabelAngle(currentPoint, - PointHelper.getNewAngle(currentPoint)! - 1, seriesRenderer); - _arrangeRightSidePoints(seriesRenderer); - break; - } - count++; - } - } else { - while (isOverlap(currentPoint.labelRect, nextPoint.labelRect) || - (seriesRenderer.series.pointRadiusMapper != null && - (currentPoint.labelRect.top < - (nextPoint.labelRect.top + nextPoint.labelRect.height)))) { - int newAngle = PointHelper.getNewAngle(nextPoint)!.toInt() + count; - if (!(newAngle < 270 && newAngle > 90)) { - newAngle = 270; - isIncreaseAngle = false; - break; - } - _changeLabelAngle(nextPoint, newAngle, seriesRenderer); - count++; - } - } -} - -/// Change the label angle based on the given new angle. -void _changeLabelAngle(ChartPoint currentPoint, num newAngle, - CircularSeriesRendererExtension seriesRenderer) { - const String defaultConnectorLineLength = '10%'; - final DataLabelSettings dataLabel = - seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings; - final Size textSize = measureText(currentPoint.text!, dataLabel.textStyle); - final Path angleChangedConnectorPath = Path(); - final num connectorLength = percentToValue( - dataLabel.connectorLineSettings.length ?? defaultConnectorLineLength, - currentPoint.outerRadius!)!; - final Offset startPoint = - degreeToPoint(newAngle, currentPoint.outerRadius!, currentPoint.center!); - final Offset endPoint = degreeToPoint(newAngle, - currentPoint.outerRadius! + connectorLength, currentPoint.center!); - angleChangedConnectorPath.moveTo(startPoint.dx, startPoint.dy); - if (dataLabel.connectorLineSettings.type == ConnectorType.line) { - angleChangedConnectorPath.lineTo(endPoint.dx, endPoint.dy); - } - seriesRenderer - .renderPoints![seriesRenderer.renderPoints!.indexOf(currentPoint)] - .labelRect = - getDataLabelRect( - currentPoint.dataLabelPosition, - seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings - .connectorLineSettings.type, - dataLabel.margin, - angleChangedConnectorPath, - endPoint, - textSize)!; - PointHelper.setLabelUpdated(currentPoint, 1); - PointHelper.setNewAngle(currentPoint, newAngle); -} - -/// Left side points alignment calculation. -void _arrangeLeftSidePoints(CircularSeriesRendererExtension seriesRenderer) { - ChartPoint previousPoint; - ChartPoint currentPoint; - bool angleChanged = false; - bool startFresh = false; - for (int i = 1; i < leftPoints.length; i++) { - currentPoint = leftPoints[i]; - previousPoint = leftPoints[i - 1]; - if (isOverlapWithPrevious(currentPoint, leftPoints, i) && - currentPoint.isVisible || - !(PointHelper.getNewAngle(currentPoint)! < 270)) { - angleChanged = true; - if (startFresh) { - isIncreaseAngle = false; - } - if (!isIncreaseAngle) { - for (int k = i; k > 0; k--) { - _decreaseAngle( - leftPoints[k], leftPoints[k - 1], seriesRenderer, false); - for (int index = 1; index < leftPoints.length; index++) { - if (PointHelper.getLabelUpdated(leftPoints[index]) != null && - PointHelper.getNewAngle(leftPoints[index])! - 10 < 100) { - isIncreaseAngle = true; - } - } - } - } else { - for (int k = i; k < leftPoints.length; k++) { - _increaseAngle( - leftPoints[k - 1], leftPoints[k], seriesRenderer, false); - } - } - } else { - if (angleChanged && - // ignore: unnecessary_null_comparison - previousPoint != null && - PointHelper.getLabelUpdated(previousPoint) == 1) { - startFresh = true; - } - } - } -} - -/// Right side points alignments calculation. -void _arrangeRightSidePoints(CircularSeriesRendererExtension series) { - bool startFresh = false; - bool angleChanged = false; - num checkAngle; - ChartPoint currentPoint; - final ChartPoint? lastPoint = - rightPoints.length > 1 ? rightPoints[rightPoints.length - 1] : null; - ChartPoint nextPoint; - if (lastPoint != null) { - if (PointHelper.getNewAngle(lastPoint)! > 360) { - PointHelper.setNewAngle( - lastPoint, PointHelper.getNewAngle(lastPoint)! - 360); - } - if (PointHelper.getNewAngle(lastPoint)! > 90 && - PointHelper.getNewAngle(lastPoint)! < 270) { - isIncreaseAngle = true; - _changeLabelAngle(lastPoint, 89, series); - } - } - for (int i = rightPoints.length - 2; i >= 0; i--) { - currentPoint = rightPoints[i]; - nextPoint = rightPoints[i + 1]; - if (isOverlapWithNext(currentPoint, rightPoints, i) && - currentPoint.isVisible || - !(PointHelper.getNewAngle(currentPoint)! <= 90 || - PointHelper.getNewAngle(currentPoint)! >= 270)) { - checkAngle = PointHelper.getNewAngle(lastPoint!)! + 1; - angleChanged = true; - // If last's point change angle in beyond the limit, stop the increasing angle and do decrease the angle. - if (startFresh) { - isIncreaseAngle = false; - } else if (checkAngle > 90 && - checkAngle < 270 && - PointHelper.getLabelUpdated(nextPoint) == 1) { - isIncreaseAngle = true; - } - if (!isIncreaseAngle) { - for (int k = i + 1; k < rightPoints.length; k++) { - _increaseAngle(rightPoints[k - 1], rightPoints[k], series, true); - } - } else { - for (int k = i + 1; k > 0; k--) { - _decreaseAngle(rightPoints[k], rightPoints[k - 1], series, true); - } - } - } else { - //If a point did not overlapped with previous points, increase the angle always for right side points. - if (angleChanged && - // ignore: unnecessary_null_comparison - nextPoint != null && - PointHelper.getLabelUpdated(nextPoint) == 1) { - startFresh = true; - } - } - } -} - -/// To render data label. -void renderCircularDataLabel( - CircularSeriesRendererExtension seriesRenderer, - Canvas canvas, - CircularStateProperties stateProperties, - int seriesIndex, - Animation? animation) { - ChartPoint point; - final SfCircularChart chart = stateProperties.chart; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - final num angle = dataLabel.angle; - Offset labelLocation; - const int labelPadding = 2; - String? label; - final double animateOpacity = animation != null ? animation.value : 1; - DataLabelRenderArgs dataLabelArgs; - TextStyle dataLabelStyle; - final List renderDataLabelRegions = []; - Size textSize; - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints!.length; - pointIndex++) { - point = seriesRenderer.renderPoints![pointIndex]; - if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { - label = point.text; - label = seriesRenderer.renderer.getLabelContent( - seriesRenderer, point, pointIndex, seriesIndex, label!); - dataLabelStyle = dataLabel.textStyle; - dataLabelSettingsRenderer.color = - seriesRenderer.series.dataLabelSettings.color; - if (chart.onDataLabelRender != null && - !seriesRenderer.renderPoints![pointIndex].labelRenderEvent) { - dataLabelArgs = DataLabelRenderArgs(seriesRenderer, - seriesRenderer.renderPoints, pointIndex, pointIndex); - dataLabelArgs.text = label; - dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer.color; - chart.onDataLabelRender!(dataLabelArgs); - label = point.text = dataLabelArgs.text; - dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex!; - dataLabelSettingsRenderer.color = dataLabelArgs.color; - seriesRenderer.dataPoints[pointIndex].labelRenderEvent = true; - } - textSize = measureText(label, dataLabelStyle); - - /// condition check for labels after event. - if (label != '') { - if (seriesRenderer.seriesType == 'radialbar') { - dataLabelStyle = chart.onDataLabelRender == null - ? seriesRenderer.renderer.getDataLabelStyle( - seriesRenderer, - point, - pointIndex, - seriesIndex, - dataLabelStyle, - stateProperties.chartState) - : dataLabelStyle; - labelLocation = degreeToPoint(point.startAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - labelLocation = Offset( - (labelLocation.dx - textSize.width - 5) + - (angle == 0 ? 0 : textSize.width / 2), - (labelLocation.dy - textSize.height / 2) + - (angle == 0 ? 0 : textSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - textSize.width + (2 * labelPadding), - textSize.height + (2 * labelPadding)); - drawLabel( - point.labelRect, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - seriesIndex, - chart, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else { - setLabelPosition( - dataLabel, - point, - textSize, - stateProperties, - canvas, - renderDataLabelRegions, - pointIndex, - label, - seriesRenderer, - animateOpacity, - dataLabelStyle, - seriesIndex); - } - } - dataLabelStyle = chart.onDataLabelRender == null - ? seriesRenderer.renderer.getDataLabelStyle( - seriesRenderer, - point, - pointIndex, - seriesIndex, - dataLabelStyle, - stateProperties.chartState) - : dataLabelStyle; - } else { - point.labelRect = Rect.zero; - } - } - if (seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings - .labelIntersectAction == - LabelIntersectAction.shift && - seriesRenderer.seriesType != 'radialbar') { - const int labelPadding = 2; - leftPoints = >[]; - rightPoints = >[]; - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - if (seriesRenderer.renderPoints![i].isVisible) { - PointHelper.setNewAngle(seriesRenderer.renderPoints![i], - seriesRenderer.renderPoints![i].midAngle); - if (seriesRenderer.renderPoints![i].dataLabelPosition == - Position.left && - seriesRenderer.renderPoints![i].renderPosition == - ChartDataLabelPosition.outside) { - leftPoints.add(seriesRenderer.renderPoints![i]); - } else if (seriesRenderer.renderPoints![i].dataLabelPosition == - Position.right && - seriesRenderer.renderPoints![i].renderPosition == - ChartDataLabelPosition.outside) { - rightPoints.add(seriesRenderer.renderPoints![i]); - } - } - } - leftPoints.sort((ChartPoint a, ChartPoint b) => - PointHelper.getNewAngle(a)!.compareTo(PointHelper.getNewAngle(b)!)); - if (leftPoints.isNotEmpty) { - _arrangeLeftSidePoints(seriesRenderer); - } - isIncreaseAngle = false; - if (rightPoints.isNotEmpty) { - _arrangeRightSidePoints(seriesRenderer); - } - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints!.length; - pointIndex++) { - if (seriesRenderer.renderPoints![pointIndex].isVisible) { - final ChartPoint point = - seriesRenderer.renderPoints![pointIndex]; - final EdgeInsets margin = - seriesRenderer.series.dataLabelSettings.margin; - final Rect rect = point.labelRect; - TextStyle dataLabelStyle = dataLabel.textStyle; - dataLabelStyle = TextStyle( - color: (chart.onDataLabelRender != null && dataLabelSettingsRenderer.color != null) - ? getSaturationColor( - dataLabelSettingsRenderer.color ?? point.fill) - : ((dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - getSaturationColor( - point.renderPosition == ChartDataLabelPosition.outside - ? findthemecolor(stateProperties, point, dataLabel) - : dataLabelSettingsRenderer.color ?? point.fill)), - fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, - fontFamily: - dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, - fontStyle: - dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, - fontWeight: - dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit, - backgroundColor: dataLabelStyle.backgroundColor ?? - dataLabel.textStyle.backgroundColor, - letterSpacing: dataLabelStyle.letterSpacing ?? - dataLabel.textStyle.letterSpacing, - wordSpacing: - dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, - textBaseline: - dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, - height: dataLabelStyle.height ?? dataLabel.textStyle.height, - locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, - foreground: - dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, - background: - dataLabelStyle.background ?? dataLabel.textStyle.background, - shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, - fontFeatures: - dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, - decoration: - dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, - decorationColor: dataLabelStyle.decorationColor ?? - dataLabel.textStyle.decorationColor, - decorationStyle: dataLabelStyle.decorationStyle ?? dataLabel.textStyle.decorationStyle, - decorationThickness: dataLabelStyle.decorationThickness ?? dataLabel.textStyle.decorationThickness, - debugLabel: dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, - fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? dataLabel.textStyle.fontFamilyFallback); - textSize = measureText(label!, dataLabelStyle); - labelLocation = Offset( - rect.left + - (point.renderPosition == ChartDataLabelPosition.inside - ? labelPadding - : margin.left), - rect.top + rect.height / 2 - textSize.height / 2); - const String defaultConnectorLineLength = '10%'; - point.trimmedText = point.text; - Path shiftedConnectorPath = Path(); - final num connectorLength = percentToValue( - seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings - .connectorLineSettings.length ?? - defaultConnectorLineLength, - point.outerRadius!)!; - final Offset startPoint = degreeToPoint( - (point.startAngle! + point.endAngle!) / 2, - point.outerRadius!, - point.center!); - final Offset endPoint = degreeToPoint(PointHelper.getNewAngle(point)!, - point.outerRadius! + connectorLength, point.center!); - shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); - if (seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings - .connectorLineSettings.type == - ConnectorType.line) { - shiftedConnectorPath.lineTo(endPoint.dx, endPoint.dy); - } - getDataLabelRect( - point.dataLabelPosition, - seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings - .connectorLineSettings.type, - margin, - shiftedConnectorPath, - endPoint, - textSize); - final ChartLocation midAngle = getPerpendicularDistance( - ChartLocation(startPoint.dx, startPoint.dy), point); - if (seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings - .connectorLineSettings.type == - ConnectorType.curve && - PointHelper.getLabelUpdated(point) == 1) { - const int spacing = 10; - shiftedConnectorPath = Path(); - shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); - shiftedConnectorPath.quadraticBezierTo( - midAngle.x, - midAngle.y, - endPoint.dx - - (point.dataLabelPosition == Position.left - ? spacing - : -spacing), - endPoint.dy); - } - final Rect containerRect = - stateProperties.renderingDetails.chartAreaRect; - if (containerRect.left > rect.left) { - labelLocation = Offset(containerRect.left, - rect.top + rect.height / 2 - textSize.height / 2); - } - if (point.labelRect.left < containerRect.left && - point.renderPosition == ChartDataLabelPosition.outside) { - point.trimmedText = getTrimmedText(point.trimmedText!, - point.labelRect.right - containerRect.left, dataLabelStyle, - axisRenderer: null, isRtl: stateProperties.isRtl); - } - if (point.labelRect.right > containerRect.right && - point.renderPosition == ChartDataLabelPosition.outside) { - point.trimmedText = getTrimmedText(point.trimmedText!, - containerRect.right - point.labelRect.left, dataLabelStyle, - axisRenderer: null, isRtl: stateProperties.isRtl); - } - if (point.trimmedText != '' && - !isOverlapWithPrevious( - point, seriesRenderer.renderPoints!, pointIndex) && - rect != Rect.zero) { - drawLabel( - rect, - labelLocation, - point.trimmedText!, - point.renderPosition == ChartDataLabelPosition.outside - ? shiftedConnectorPath - : Path(), - canvas, - seriesRenderer, - point, - pointIndex, - seriesIndex, - chart, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To set data label position. -void setLabelPosition( - DataLabelSettings dataLabel, - ChartPoint point, - Size textSize, - CircularStateProperties stateProperties, - Canvas canvas, - List renderDataLabelRegions, - int pointIndex, - String label, - CircularSeriesRendererExtension seriesRenderer, - double animateOpacity, - TextStyle dataLabelStyle, - int seriesIndex) { - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - final SfCircularChart chart = stateProperties.chart; - final num angle = dataLabel.angle; - Offset labelLocation; - const int labelPadding = 2; - if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - labelLocation = degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - labelLocation = Offset( - labelLocation.dx - - (textSize.width / 2) + - (angle == 0 ? 0 : textSize.width / 2), - labelLocation.dy - - (textSize.height / 2) + - (angle == 0 ? 0 : textSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - textSize.width + (2 * labelPadding), - textSize.height + (2 * labelPadding)); - bool isDataLabelCollide = - findingCollision(point.labelRect, renderDataLabelRegions); - point.overflowTrimmedText = point.overflowTrimmedText ?? label; - if (dataLabel.overflowMode == OverflowMode.shift) { - final String labelText = _getSegmentOverflowTrimmedText( - point.text!, - textSize, - point, - point.labelRect, - stateProperties, - labelLocation, - dataLabel.overflowMode); - if (labelText.contains('...') || labelText.isEmpty) { - isDataLabelCollide = true; - point.renderPosition = ChartDataLabelPosition.outside; - } - point.text = isDataLabelCollide ? point.text : labelText; - } else if (dataLabel.overflowMode == OverflowMode.trim && - !point.text!.contains('...')) { - point.text = _getSegmentOverflowTrimmedText( - point.text!, - textSize, - point, - point.labelRect, - stateProperties, - labelLocation, - dataLabel.overflowMode); - label = point.text!; - final Size trimmedTextSize = measureText(label, dataLabelStyle); - labelLocation = degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - labelLocation = Offset( - labelLocation.dx - - (trimmedTextSize.width / 2) + - (angle == 0 ? 0 : trimmedTextSize.width / 2), - labelLocation.dy - - (trimmedTextSize.height / 2) + - (angle == 0 ? 0 : trimmedTextSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - trimmedTextSize.width + (2 * labelPadding), - trimmedTextSize.height + (2 * labelPadding)); - } - final TextStyle textStyle = TextStyle( - color: (dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - getSaturationColor( - findthemecolor(stateProperties, point, dataLabel)), - fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, - fontFamily: dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, - fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, - fontWeight: dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit, - backgroundColor: dataLabelStyle.backgroundColor ?? - dataLabel.textStyle.backgroundColor, - letterSpacing: - dataLabelStyle.letterSpacing ?? dataLabel.textStyle.letterSpacing, - wordSpacing: - dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, - textBaseline: - dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, - height: dataLabelStyle.height ?? dataLabel.textStyle.height, - locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, - foreground: dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, - background: dataLabelStyle.background ?? dataLabel.textStyle.background, - shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, - fontFeatures: - dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, - decoration: dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, - decorationColor: dataLabelStyle.decorationColor ?? - dataLabel.textStyle.decorationColor, - decorationStyle: dataLabelStyle.decorationStyle ?? - dataLabel.textStyle.decorationStyle, - decorationThickness: dataLabelStyle.decorationThickness ?? - dataLabel.textStyle.decorationThickness, - debugLabel: dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, - fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? - dataLabel.textStyle.fontFamilyFallback); - if (seriesRenderer.series.dataLabelSettings.labelIntersectAction == - LabelIntersectAction.shift && - isDataLabelCollide && - dataLabel.overflowMode != OverflowMode.trim) { - point.saturationRegionOutside = true; - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = textStyle; - renderOutsideDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - seriesIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if (((dataLabel.labelIntersectAction == LabelIntersectAction.shift && - dataLabel.overflowMode == OverflowMode.none) && - isDataLabelCollide && - dataLabel.overflowMode != OverflowMode.trim) || - (isDataLabelCollide && dataLabel.overflowMode == OverflowMode.shift)) { - point.saturationRegionOutside = true; - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = textStyle; - renderOutsideDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - seriesIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if (!isDataLabelCollide || - (dataLabel.labelIntersectAction == LabelIntersectAction.none && - dataLabel.overflowMode == OverflowMode.none)) { - point.renderPosition = ChartDataLabelPosition.inside; - dataLabelStyle = TextStyle( - color: (chart.onDataLabelRender != null && - dataLabelSettingsRenderer.color != null) - ? getSaturationColor( - dataLabelSettingsRenderer.color ?? point.fill) - : ((dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - getSaturationColor( - dataLabelSettingsRenderer.color ?? point.fill)), - fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, - fontFamily: - dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, - fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, - fontWeight: - dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit, - backgroundColor: dataLabelStyle.backgroundColor ?? - dataLabel.textStyle.backgroundColor, - letterSpacing: - dataLabelStyle.letterSpacing ?? dataLabel.textStyle.letterSpacing, - wordSpacing: - dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, - textBaseline: - dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, - height: dataLabelStyle.height ?? dataLabel.textStyle.height, - locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, - foreground: - dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, - background: - dataLabelStyle.background ?? dataLabel.textStyle.background, - shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, - fontFeatures: - dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, - decoration: - dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, - decorationColor: dataLabelStyle.decorationColor ?? - dataLabel.textStyle.decorationColor, - decorationStyle: dataLabelStyle.decorationStyle ?? - dataLabel.textStyle.decorationStyle, - decorationThickness: dataLabelStyle.decorationThickness ?? - dataLabel.textStyle.decorationThickness, - debugLabel: - dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, - fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? - dataLabel.textStyle.fontFamilyFallback); - if (!isDataLabelCollide && - (dataLabel.labelIntersectAction == LabelIntersectAction.shift && - dataLabel.overflowMode != OverflowMode.hide)) { - renderDataLabelRegions.add(point.labelRect); - } else if (!isDataLabelCollide && - (dataLabel.labelIntersectAction == LabelIntersectAction.hide || - dataLabel.overflowMode == OverflowMode.hide)) { - if (point.renderPosition == ChartDataLabelPosition.inside && - (dataLabel.overflowMode == OverflowMode.hide)) { - point.text = _getSegmentOverflowTrimmedText( - point.text!, - textSize, - point, - point.labelRect, - stateProperties, - labelLocation, - dataLabel.overflowMode); - label = point.text!; - } - drawLabel( - point.labelRect, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - seriesIndex, - chart, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else { - drawLabel( - point.labelRect, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - seriesIndex, - chart, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } else { - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = TextStyle( - color: (dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - getSaturationColor( - findthemecolor(stateProperties, point, dataLabel)), - fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, - fontFamily: dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, - fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, - fontWeight: dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit, - backgroundColor: dataLabelStyle.backgroundColor ?? - dataLabel.textStyle.backgroundColor, - letterSpacing: - dataLabelStyle.letterSpacing ?? dataLabel.textStyle.letterSpacing, - wordSpacing: - dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, - textBaseline: - dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, - height: dataLabelStyle.height ?? dataLabel.textStyle.height, - locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, - foreground: dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, - background: dataLabelStyle.background ?? dataLabel.textStyle.background, - shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, - fontFeatures: - dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, - decoration: dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, - decorationColor: dataLabelStyle.decorationColor ?? - dataLabel.textStyle.decorationColor, - decorationStyle: dataLabelStyle.decorationStyle ?? - dataLabel.textStyle.decorationStyle, - decorationThickness: dataLabelStyle.decorationThickness ?? - dataLabel.textStyle.decorationThickness, - debugLabel: dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, - fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? - dataLabel.textStyle.fontFamilyFallback); - renderOutsideDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - seriesIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } -} - -/// To render outside positioned data labels. -void renderOutsideDataLabel( - Canvas canvas, - String label, - ChartPoint point, - Size textSize, - int pointIndex, - CircularSeriesRendererExtension seriesRenderer, - int seriesIndex, - CircularStateProperties stateProperties, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Path connectorPath; - Rect? rect; - Offset labelLocation; - const String defaultConnectorLineLength = '10%'; - final EdgeInsets margin = seriesRenderer.series.dataLabelSettings.margin; - final ConnectorLineSettings connector = - seriesRenderer.series.dataLabelSettings.connectorLineSettings; - connectorPath = Path(); - final num connectorLength = percentToValue( - connector.length ?? defaultConnectorLineLength, point.outerRadius!)!; - final Offset startPoint = - degreeToPoint(point.midAngle!, point.outerRadius!, point.center!); - final Offset endPoint = degreeToPoint( - 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!; - labelLocation = Offset(rect.left + margin.left, - rect.top + rect.height / 2 - textSize.height / 2); - final Rect containerRect = stateProperties.renderingDetails.chartAreaRect; - if (seriesRenderer.series.dataLabelSettings.builder == null) { - if (seriesRenderer.series.dataLabelSettings.labelIntersectAction == - LabelIntersectAction.hide) { - if (!findingCollision(rect, renderDataLabelRegions) && - (rect.left > containerRect.left && - rect.left + rect.width < - containerRect.left + containerRect.width) && - rect.top > containerRect.top && - rect.top + rect.height < containerRect.top + containerRect.height) { - drawLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - seriesIndex, - stateProperties.chart, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } else if (seriesRenderer.series.dataLabelSettings.labelIntersectAction == - LabelIntersectAction.shift) { - renderDataLabelRegions.add(rect); - } else { - drawLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - seriesIndex, - stateProperties.chart, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } else { - if (seriesRenderer.series.dataLabelSettings.labelIntersectAction != - LabelIntersectAction.shift) { - _drawConnectorLine(labelLocation, connectorPath, canvas, seriesRenderer, - point, animateOpacity, seriesRenderer.series.dataLabelSettings); - } - } -} - -/// To draw label. -void drawLabel( - Rect labelRect, - Offset location, - String label, - Path? connectorPath, - Canvas canvas, - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - SfCircularChart chart, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Paint rectPaint; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - if (connectorPath != null) { - _drawConnectorLine(location, connectorPath, canvas, seriesRenderer, point, - animateOpacity, dataLabel); - } - - if (dataLabel.builder == null) { - final double strokeWidth = seriesRenderer.renderer.getDataLabelStrokeWidth( - seriesRenderer, point, pointIndex, seriesIndex, dataLabel.borderWidth); - final Color? labelFill = seriesRenderer.renderer.getDataLabelColor( - seriesRenderer, - point, - pointIndex, - seriesIndex, - dataLabelSettingsRenderer.color ?? - (dataLabel.useSeriesColor - ? point.fill - : dataLabelSettingsRenderer.color)); - final Color strokeColor = seriesRenderer.renderer.getDataLabelStrokeColor( - seriesRenderer, - point, - pointIndex, - seriesIndex, - dataLabel.borderColor.withOpacity(dataLabel.opacity)); - // ignore: unnecessary_null_comparison - if (strokeWidth != null && strokeWidth > 0) { - rectPaint = Paint() - ..color = strokeColor.withOpacity( - (animateOpacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : animateOpacity - (1 - dataLabel.opacity)) - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth; - drawLabelRect( - rectPaint, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - if (labelFill != null) { - drawLabelRect( - Paint() - ..color = labelFill.withOpacity( - (animateOpacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : animateOpacity - (1 - dataLabel.opacity)) - ..style = PaintingStyle.fill, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - drawText(canvas, label, location, textStyle, dataLabel.angle); - if (seriesRenderer.series.dataLabelSettings.labelIntersectAction != - LabelIntersectAction.shift) { - renderDataLabelRegions.add(labelRect); - } - } -} - -/// Method to trigger the data label event. -void triggerCircularDataLabelEvent( - SfCircularChart chart, - CircularSeriesRendererExtension seriesRenderer, - CircularStateProperties stateProperties, - Offset? position) { - const int seriesIndex = 0; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - Offset labelLocation; - num connectorLength; - ChartPoint point; - const String defaultConnectorLineLength = '10%'; - for (int index = 0; index < seriesRenderer.dataPoints.length; index++) { - point = seriesRenderer.dataPoints[index]; - if (dataLabel.isVisible && - // ignore: unnecessary_null_comparison - seriesRenderer.dataPoints[index].labelRect != null && - position != null && - seriesRenderer.dataPoints[index].labelRect.contains(position)) { - if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - labelLocation = degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - position = Offset(labelLocation.dx, labelLocation.dy); - } else { - connectorLength = percentToValue( - dataLabel.connectorLineSettings.length ?? - defaultConnectorLineLength, - point.outerRadius!)!; - labelLocation = degreeToPoint(point.midAngle!, - point.outerRadius! + connectorLength, point.center!); - position = Offset(labelLocation.dx, labelLocation.dy); - } - if (chart.onDataLabelTapped != null) { - dataLabelTapEvent(chart, seriesRenderer.series.dataLabelSettings, index, - point, position, seriesIndex); - } - } - } -} - -/// To draw data label rect -void drawLabelRect( - Paint paint, Rect labelRect, double borderRadius, Canvas canvas) => - canvas.drawRRect( - RRect.fromRectAndRadius(labelRect, Radius.circular(borderRadius)), - paint); - -/// To find data label position. -void findDataLabelPosition(ChartPoint point) { - point.midAngle = - point.midAngle! > 360 ? point.midAngle! - 360 : point.midAngle!; - 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 data label. -Color findthemecolor(CircularStateProperties stateProperties, - ChartPoint point, DataLabelSettings dataLabel) { - return dataLabel.color ?? - (dataLabel.useSeriesColor - ? point.fill - : (stateProperties.chart.backgroundColor ?? - (stateProperties.renderingDetails.chartTheme.brightness == - Brightness.light - ? Colors.white - : Colors.black))); -} - -/// Method to get a text when the text overlap with another segment/slice. -String _getSegmentOverflowTrimmedText( - String datalabelText, - Size size, - ChartPoint point, - Rect labelRect, - CircularStateProperties chartState, - Offset labelLocation, - OverflowMode action) { - const int index = 0; - bool isTextWithinRegion; - const String ellipse = '...'; - const int minCharacterLength = 3; - // To reduce the additional padding around label rects - // ignore: prefer_const_declarations - final double labelPadding = kIsWeb ? 4 : 2; - - final bool labelLeftEnd = _isInsideSegment( - point.labelRect.centerLeft - Offset(labelPadding, 0), - chartState.centerLocation, - point.outerRadius!, - point.startAngle!, - point.endAngle!); - - final bool labelRightEnd = _isInsideSegment( - point.labelRect.centerRight - Offset(labelPadding, 0), - chartState.centerLocation, - point.outerRadius!, - point.startAngle!, - point.endAngle!); - - if (labelLeftEnd && labelRightEnd) { - return datalabelText; - } else { - isTextWithinRegion = false; - while (!isTextWithinRegion) { - if (action == OverflowMode.trim) { - if (datalabelText == '') { - break; - } - if (datalabelText.length > minCharacterLength) - datalabelText = addEllipse( - datalabelText, datalabelText.length, ellipse, - isRtl: chartState.isRtl); - else { - datalabelText = ''; - break; - } - if (datalabelText == ellipse) { - datalabelText = ''; - break; - } - const num labelPadding = 0; - final Size trimSize = measureText( - datalabelText, - chartState.chartSeries.visibleSeriesRenderers[index].series - .dataLabelSettings.textStyle); - Offset trimmedlabelLocation = degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - trimmedlabelLocation = Offset( - (trimmedlabelLocation.dx - trimSize.width - 5) + - (chartState.chartSeries.visibleSeriesRenderers[index].series - .dataLabelSettings.angle == - 0 - ? 0 - : trimSize.width / 2), - (trimmedlabelLocation.dy - trimSize.height / 2) + - (chartState.chartSeries.visibleSeriesRenderers[index].series - .dataLabelSettings.angle == - 0 - ? 0 - : trimSize.height / 2)); - final Rect trimmedlabelRect = Rect.fromLTWH( - trimmedlabelLocation.dx - labelPadding, - trimmedlabelLocation.dy - labelPadding, - trimSize.width + (2 * labelPadding), - trimSize.height + (2 * labelPadding)); - - final bool trimmedLeftEnd = _isInsideSegment( - trimmedlabelRect.centerLeft, - chartState.centerLocation, - point.outerRadius!, - point.startAngle!, - point.endAngle!); - - final bool trimmedRightEnd = _isInsideSegment( - trimmedlabelRect.centerRight, - chartState.centerLocation, - point.outerRadius!, - point.startAngle!, - point.endAngle!); - if (trimmedLeftEnd && trimmedRightEnd) { - isTextWithinRegion = true; - point.labelRect = trimmedlabelRect; - } - } else { - datalabelText = ''; - isTextWithinRegion = true; - break; - } - } - } - return datalabelText; -} - -/// Method to check if a label is inside the point region based on the angle for pie and doughnut series. -bool _isInsideSegment( - Offset point, Offset center, num radius, num start, num end) { - final Offset labelOffset = point - center; - - final double labelRadius = labelOffset.distance; - - if (labelRadius < radius) { - final num originAngle = - math.atan2(-labelOffset.dy, labelOffset.dx) * 180 / math.pi; - - num labelAngle; - - labelAngle = 360 - originAngle; - - if (labelAngle > 270) { - labelAngle -= 360; - } - labelAngle = labelAngle.round(); - - return labelAngle >= start && labelAngle <= end; - } else { - return false; - } -} - -/// Method to render a connected line -void _drawConnectorLine( - Offset location, - Path connectorPath, - Canvas canvas, - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - double animateOpacity, - DataLabelSettings dataLabel) { - final ConnectorLineSettings line = dataLabel.connectorLineSettings; - if (dataLabel.builder != null) { - final List datalabelTemplate = seriesRenderer - .stateProperties.renderingDetails.dataLabelTemplateRegions; - final Offset dataLabelLocation = seriesRenderer - .stateProperties.renderingDetails.templates[point.index].location; - for (int i = 0; i < datalabelTemplate.length; i++) { - if (datalabelTemplate[i].contains(location) || - datalabelTemplate[i].contains(dataLabelLocation)) { - canvas.drawPath( - connectorPath, - Paint() - ..color = line.width <= 0 - ? Colors.transparent - : line.color ?? point.fill.withOpacity(animateOpacity) - ..strokeWidth = line.width - ..style = PaintingStyle.stroke); - } - } - } else { - canvas.drawPath( - connectorPath, - Paint() - ..color = line.width <= 0 - ? Colors.transparent - : line.color ?? point.fill.withOpacity(animateOpacity) - ..strokeWidth = line.width - ..style = PaintingStyle.stroke); - } -} 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 deleted file mode 100644 index af577d91b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../base/circular_base.dart'; -import '../utils/enum.dart'; -import 'circular_series.dart'; -import 'renderer_base.dart'; -import 'renderer_extension.dart'; - -/// This class has the properties of the Doughnut series. -/// -/// To render a doughnut chart, create an instance of [DoughnutSeries], and add it to the series -/// collection property of [SfCircularChart]. -/// -/// Provide options for opacity, stroke width, stroke color, and point color mapper to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} -@immutable -// ignore: must_be_immutable -class DoughnutSeries extends CircularSeries { - /// Creating an argument constructor of DoughnutSeries class. - DoughnutSeries( - {ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - CircularSeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - 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, - String? name, - double? opacity, - double? animationDuration, - double? animationDelay, - SelectionBehavior? selectionBehavior, - SortingOrder? sortingOrder, - LegendIconType? legendIconType, - CornerStyle? cornerStyle, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - dataSource: dataSource, - xValueMapper: (int index) => xValueMapper(dataSource![index], index), - yValueMapper: (int index) => yValueMapper(dataSource![index], index), - pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource![index], index) - : null, - pointRadiusMapper: pointRadiusMapper == null - ? null - : (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) - : null, - sortFieldValueMapper: sortFieldValueMapper != null - ? (int index) => sortFieldValueMapper(dataSource![index], index) - : null, - animationDuration: animationDuration, - animationDelay: animationDelay, - startAngle: startAngle, - endAngle: endAngle, - radius: radius, - innerRadius: innerRadius, - explode: explode, - opacity: opacity, - explodeAll: explodeAll, - explodeIndex: explodeIndex, - explodeOffset: explodeOffset, - explodeGesture: explodeGesture, - pointRenderMode: pointRenderMode, - groupMode: groupMode, - groupTo: groupTo, - emptyPointSettings: emptyPointSettings, - borderColor: strokeColor, - borderWidth: strokeWidth, - dataLabelSettings: dataLabelSettings, - enableTooltip: enableTooltip, - name: name, - selectionBehavior: selectionBehavior, - legendIconType: legendIconType, - sortingOrder: sortingOrder, - cornerStyle: cornerStyle, - initialSelectedDataIndexes: initialSelectedDataIndexes, - ); - - /// Create the circular series renderer. - DoughnutSeriesRenderer? createRenderer(CircularSeries series) { - DoughnutSeriesRenderer? seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as DoughnutSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return DoughnutSeriesRendererExtension(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is DoughnutSeries && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.cornerStyle == cornerStyle && - other.dataLabelMapper == dataLabelMapper && - other.dataLabelSettings == dataLabelSettings && - other.dataSource == dataSource && - other.emptyPointSettings == emptyPointSettings && - other.enableTooltip == enableTooltip && - other.endAngle == endAngle && - other.explode == explode && - other.explodeAll == explodeAll && - other.explodeGesture == explodeGesture && - other.explodeIndex == explodeIndex && - other.explodeOffset == explodeOffset && - other.groupMode == groupMode && - other.groupTo == groupTo && - listEquals( - other.initialSelectedDataIndexes, initialSelectedDataIndexes) && - other.innerRadius == innerRadius && - other.legendIconType == legendIconType && - other.name == name && - other.onCreateRenderer == onCreateRenderer && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.opacity == opacity && - other.pointColorMapper == pointColorMapper && - other.pointRadiusMapper == pointRadiusMapper && - other.pointRenderMode == pointRenderMode && - other.pointShaderMapper == pointShaderMapper && - other.radius == radius && - other.selectionBehavior == selectionBehavior && - other.sortFieldValueMapper == sortFieldValueMapper && - other.sortingOrder == sortingOrder && - other.startAngle == startAngle && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper; - } - - @override - int get hashCode { - final List values = [ - animationDuration, - animationDelay, - borderColor, - borderWidth, - cornerStyle, - dataLabelMapper, - dataLabelSettings, - dataSource, - emptyPointSettings, - enableTooltip, - endAngle, - explode, - explodeAll, - explodeGesture, - explodeIndex, - explodeOffset, - groupMode, - groupTo, - initialSelectedDataIndexes, - innerRadius, - legendIconType, - name, - onCreateRenderer, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress, - opacity, - pointColorMapper, - pointRadiusMapper, - pointRenderMode, - pointShaderMapper, - radius, - selectionBehavior, - sortFieldValueMapper, - sortingOrder, - startAngle, - xValueMapper, - yValueMapper - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index 235e2c68a..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../base/circular_base.dart'; -import '../utils/enum.dart'; -import 'circular_series.dart'; -import 'renderer_base.dart'; -import 'renderer_extension.dart'; - -/// This class has the properties of the pie series. -/// -/// To render a pie chart, create an instance of [PieSeries], and add it to the series collection property of [SfCircularChart]. -/// -/// It provides the options for color, opacity, border color, and border width to customize the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} -class PieSeries extends CircularSeries { - /// Creating an argument constructor of PieSeries class. - PieSeries( - {ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - CircularSeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - 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, - String? name, - double? animationDuration, - double? animationDelay, - SelectionBehavior? selectionBehavior, - SortingOrder? sortingOrder, - LegendIconType? legendIconType, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - animationDuration: animationDuration, - animationDelay: animationDelay, - dataSource: dataSource, - xValueMapper: (int index) => - xValueMapper!(dataSource![index], index), - yValueMapper: (int index) => - yValueMapper!(dataSource![index], index), - pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource![index], index) - : null, - pointRadiusMapper: pointRadiusMapper == null - ? null - : (int index) => pointRadiusMapper(dataSource![index], index), - dataLabelMapper: (int index) => dataLabelMapper != null - ? dataLabelMapper(dataSource![index], index) - : null, - sortFieldValueMapper: sortFieldValueMapper != null - ? (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, - radius: radius, - explode: explode, - explodeAll: explodeAll, - explodeIndex: explodeIndex, - explodeOffset: explodeOffset, - explodeGesture: explodeGesture, - groupTo: groupTo, - groupMode: groupMode, - pointRenderMode: pointRenderMode, - emptyPointSettings: emptyPointSettings, - initialSelectedDataIndexes: initialSelectedDataIndexes, - borderColor: strokeColor, - borderWidth: strokeWidth, - opacity: opacity, - dataLabelSettings: dataLabelSettings, - enableTooltip: enableTooltip, - name: name, - selectionBehavior: selectionBehavior, - legendIconType: legendIconType, - sortingOrder: sortingOrder); - - /// Create the pie series renderer. - PieSeriesRenderer? createRenderer(CircularSeries series) { - PieSeriesRenderer? seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as PieSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return PieSeriesRendererExtension(); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is PieSeries && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.dataLabelMapper == dataLabelMapper && - other.dataLabelSettings == dataLabelSettings && - other.dataSource == dataSource && - other.emptyPointSettings == emptyPointSettings && - other.enableTooltip == enableTooltip && - other.endAngle == endAngle && - other.explode == explode && - other.explodeAll == explodeAll && - other.explodeGesture == explodeGesture && - other.explodeIndex == explodeIndex && - other.explodeOffset == explodeOffset && - other.groupMode == groupMode && - other.groupTo == groupTo && - listEquals( - other.initialSelectedDataIndexes, initialSelectedDataIndexes) && - other.legendIconType == legendIconType && - other.name == name && - other.onCreateRenderer == onCreateRenderer && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.opacity == opacity && - other.pointColorMapper == pointColorMapper && - other.pointRadiusMapper == pointRadiusMapper && - other.pointRenderMode == pointRenderMode && - other.pointShaderMapper == pointShaderMapper && - other.radius == radius && - other.selectionBehavior == selectionBehavior && - other.sortFieldValueMapper == sortFieldValueMapper && - other.sortingOrder == sortingOrder && - other.startAngle == startAngle && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - animationDuration, - animationDelay, - borderColor, - borderWidth, - dataLabelMapper, - dataLabelSettings, - dataSource, - emptyPointSettings, - enableTooltip, - endAngle, - explode, - explodeAll, - explodeGesture, - explodeIndex, - explodeOffset, - groupMode, - groupTo, - initialSelectedDataIndexes, - legendIconType, - name, - onCreateRenderer, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress, - opacity, - pointColorMapper, - pointRadiusMapper, - pointRenderMode, - pointShaderMapper, - radius, - selectionBehavior, - sortFieldValueMapper, - sortingOrder, - startAngle, - xValueMapper, - yValueMapper - ]; - return hashList(values); - } -} 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 deleted file mode 100644 index e2057c235..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart +++ /dev/null @@ -1,322 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../base/circular_base.dart'; -import '../utils/enum.dart'; -import 'circular_series.dart'; -import 'renderer_base.dart'; -import 'renderer_extension.dart'; - -/// Renders the radial bar series. -/// -/// The radial bar chart is used for showing the comparisons among the categories using the circular shapes. -/// To render a radial bar chart, create an instance of RadialBarSeries, and add to the series collection property of [SfCircularChart]. -/// -/// Provides options to customize the [maximumValue], [trackColor], [trackBorderColor], [trackBorderWidth], [trackOpacity] -/// and [useSeriesColor] of the radial segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=VJxPp7-2nGk} -class RadialBarSeries extends CircularSeries { - /// Creating an argument constructor of RadialBarSeries class. - /// - RadialBarSeries( - {ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - CircularSeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - 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, - String? name, - double? animationDuration, - double? animationDelay, - SelectionBehavior? selectionBehavior, - SortingOrder? sortingOrder, - LegendIconType? legendIconType, - CornerStyle? cornerStyle, - List? initialSelectedDataIndexes}) - : super( - key: key, - onCreateRenderer: onCreateRenderer, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - dataSource: dataSource, - animationDuration: animationDuration, - animationDelay: animationDelay, - xValueMapper: (int index) => xValueMapper(dataSource![index], index), - yValueMapper: (int index) => yValueMapper(dataSource![index], index), - pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource![index], index) - : null, - pointRadiusMapper: (int index) => pointRadiusMapper != null - ? 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) - : null, - sortFieldValueMapper: sortFieldValueMapper != null - ? (int index) => sortFieldValueMapper(dataSource![index], index) - : null, - radius: radius, - innerRadius: innerRadius, - gap: gap, - borderColor: strokeColor, - borderWidth: strokeWidth, - opacity: opacity, - enableTooltip: enableTooltip, - dataLabelSettings: dataLabelSettings, - name: name, - selectionBehavior: selectionBehavior, - sortingOrder: sortingOrder, - legendIconType: legendIconType, - cornerStyle: cornerStyle, - initialSelectedDataIndexes: initialSelectedDataIndexes, - ); - - /// Color of the track. - /// - /// Defaults to `const Color.fromRGBO(234, 236, 239, 1.0)`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// trackColor: Colors.red, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final Color trackColor; - - /// Specifies the maximum value of the radial bar. - /// - /// By default, the sum of the data points values will be considered as maximum value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// maximumValue: 100, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final double? maximumValue; - - /// Border color of the track. - /// - /// Defaults to `Colors.Transparent`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// trackBorderColor: Colors.red, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final Color trackBorderColor; - - /// Border width of the track. - /// - /// Defaults to `0.0`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// trackBorderWidth: 2, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final double trackBorderWidth; - - /// Opacity of the track. - /// - /// Defaults to `1`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// trackOpacity: 0.2, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final double trackOpacity; - - /// Uses the point color for filling the track. - /// - /// Defaults to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// series: >[ - /// RadialBarSeries( - /// useSeriesColor:true - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final bool useSeriesColor; - - /// Create the Radial bar series renderer. - RadialBarSeriesRenderer createRenderer(CircularSeries series) { - RadialBarSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as RadialBarSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return RadialBarSeriesRendererExtension(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is RadialBarSeries && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.cornerStyle == cornerStyle && - other.dataLabelMapper == dataLabelMapper && - other.dataLabelSettings == dataLabelSettings && - other.dataSource == dataSource && - other.enableTooltip == enableTooltip && - other.gap == gap && - listEquals( - other.initialSelectedDataIndexes, initialSelectedDataIndexes) && - other.innerRadius == innerRadius && - other.legendIconType == legendIconType && - other.maximumValue == maximumValue && - other.name == name && - other.onCreateRenderer == onCreateRenderer && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.opacity == opacity && - other.pointColorMapper == pointColorMapper && - other.pointRadiusMapper == pointRadiusMapper && - other.pointShaderMapper == pointShaderMapper && - other.radius == radius && - other.selectionBehavior == selectionBehavior && - other.sortFieldValueMapper == sortFieldValueMapper && - other.sortingOrder == sortingOrder && - other.trackBorderColor == trackBorderColor && - other.trackBorderWidth == trackBorderWidth && - other.trackColor == trackColor && - other.trackOpacity == trackOpacity && - other.useSeriesColor == useSeriesColor && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper; - } - - @override - int get hashCode { - final List values = [ - animationDuration, - animationDelay, - borderColor, - borderWidth, - cornerStyle, - dataLabelMapper, - dataLabelSettings, - dataSource, - enableTooltip, - gap, - initialSelectedDataIndexes, - innerRadius, - legendIconType, - maximumValue, - name, - onCreateRenderer, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress, - opacity, - pointColorMapper, - pointRadiusMapper, - pointShaderMapper, - radius, - selectionBehavior, - sortFieldValueMapper, - sortingOrder, - trackBorderColor, - trackBorderWidth, - trackColor, - trackOpacity, - useSeriesColor, - xValueMapper, - yValueMapper - ]; - return hashList(values); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart deleted file mode 100644 index 138e0da8e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart +++ /dev/null @@ -1,25 +0,0 @@ -import '../../chart/chart_series/series.dart'; - -/// Creates a series renderer for circular series. -class CircularSeriesRenderer extends ChartSeriesRenderer { - /// Creates an instance of circular series renderer. - CircularSeriesRenderer(); -} - -/// Creates a series renderer for pie series. -class PieSeriesRenderer extends CircularSeriesRenderer { - /// Calling the default constructor of [PieSeriesRenderer] class. - PieSeriesRenderer(); -} - -/// Creates series renderer for doughnut series. -class DoughnutSeriesRenderer extends CircularSeriesRenderer { - /// Calling the default constructor of [DoughnutSeriesRenderer] class. - DoughnutSeriesRenderer(); -} - -/// Creates a series renderer for radial bar series. -class RadialBarSeriesRenderer extends CircularSeriesRenderer { - /// Calling the default constructor of [RadialBarSeriesRenderer] class. - RadialBarSeriesRenderer(); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart deleted file mode 100644 index af6df3dd5..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart +++ /dev/null @@ -1,998 +0,0 @@ -import 'dart:ui' as dart_ui; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../chart/common/data_label.dart'; -import '../../common/event_args.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../base/circular_base.dart'; -import '../base/circular_state_properties.dart'; -import '../utils/enum.dart'; -import '../utils/helper.dart'; -import 'chart_point.dart'; -import 'circular_series.dart'; -import 'circular_series_controller.dart'; -import 'common.dart'; -import 'radial_bar_series.dart'; -import 'renderer_base.dart'; - -/// Creates a series renderer for circular series. -class CircularSeriesRendererExtension implements CircularSeriesRenderer { - /// Specifies the circular series. - late CircularSeries series; - - /// Specifies the chart series renderer. - late ChartSeriesRender renderer; - - /// Specifies the series type. - late String seriesType; - - /// Specifies the list of data points. - late List> dataPoints; - - /// Specifies whether to repaint the series. - bool needsRepaint = true; - - /// Specifies the list of rendering points. - List>? renderPoints; - - /// Specifies the list of old render points. - List>? oldRenderPoints; - - /// Specifies the map collection that holds all the values for rendering - /// the segment. - Map segmentRenderingValues = {}; - - /// Specifies the value of center. - Offset? center; - - /// Specifies the value of point region - late List pointRegions; - - /// Specifies the value of rect. - // ignore:unused_field - late Rect rect; - - /// Path saved for radial bar series. - List renderPaths = []; - - /// Specifies the value of render list. - List renderList = []; - - /// Specifies the value of inner radial radius. - num? innerRadialradius; - - /// Specifies the value of selection args. - SelectionArgs? selectionArgs; - - /// Determines whether there is a need for animation. - late bool needsAnimation; - - /// We can redraw the series by 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; - - /// Specifies the circular state properties. - late CircularStateProperties stateProperties; - - /// Repaint notifier for series. - late ValueNotifier repaintNotifier; - - /// Specifies the data label setting renderer. - late DataLabelSettingsRenderer dataLabelSettingsRenderer; - - /// Specifies the selection behavior renderer. - late SelectionBehaviorRenderer selectionBehaviorRenderer; - - /// Specifies the selection behavior. - dynamic selectionBehavior; - - /// Check whether the selection is enabled. - // ignore: prefer_final_fields - bool isSelectionEnable = false; - - /// To set style properties for selected points. - StyleOptions? selectPoint( - int currentPointIndex, - CircularSeriesRendererExtension seriesRenderer, - SfCircularChart chart, - ChartPoint? point) { - StyleOptions? pointStyle; - final dynamic selection = series.selectionBehavior; - if (selection.enable == true) { - if (stateProperties.renderingDetails.selectionData.isNotEmpty) { - int selectionIndex; - for (int i = 0; - i < stateProperties.renderingDetails.selectionData.length; - i++) { - selectionIndex = stateProperties.renderingDetails.selectionData[i]; - if (currentPointIndex == selectionIndex) { - pointStyle = StyleOptions( - fill: seriesRenderer.selectionArgs != null - ? seriesRenderer.selectionArgs!.selectedColor - : selection.selectedColor, - strokeWidth: seriesRenderer.selectionArgs != null - ? seriesRenderer.selectionArgs!.selectedBorderWidth - : selection.selectedBorderWidth, - strokeColor: seriesRenderer.selectionArgs != null - ? seriesRenderer.selectionArgs!.selectedBorderColor - : selection.selectedBorderColor, - opacity: selection.selectedOpacity); - break; - } else if (i == - stateProperties.renderingDetails.selectionData.length - 1) { - pointStyle = StyleOptions( - fill: seriesRenderer.selectionArgs != null - ? seriesRenderer.selectionArgs!.unselectedColor - : selection.unselectedColor, - strokeWidth: seriesRenderer.selectionArgs != null - ? selectionArgs!.unselectedBorderWidth - : selection.unselectedBorderWidth, - strokeColor: seriesRenderer.selectionArgs != null - ? seriesRenderer.selectionArgs!.unselectedBorderColor - : selection.unselectedBorderColor, - opacity: selection.unselectedOpacity); - } - } - } - } - return pointStyle; - } - - /// To calculate point start and end angle. - num? circularRenderPoint( - SfCircularChart chart, - CircularSeriesRendererExtension seriesRenderer, - ChartPoint point, - num? pointStartAngle, - num? innerRadius, - num? outerRadius, - Canvas canvas, - int seriesIndex, - int pointIndex, - num animationDegreeValue, - num animationRadiusValue, - bool isAnyPointSelect, - ChartPoint? oldPoint, - List?>? oldPointList) { - final bool isDynamicUpdate = oldPoint != null; - 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.segmentRenderingValues['start']! - ? seriesRenderer.segmentRenderingValues['start']! - : 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.segmentRenderingValues['start'] && - (oldPoint.endAngle!.round() == - seriesRenderer.segmentRenderingValues['end'] || - oldPoint.endAngle!.round() == - 360 + seriesRenderer.segmentRenderingValues['end']!)) { - pointStartAngle = oldPoint.startAngle!; - pointEndAngle = oldPoint.endAngle! - - (oldPoint.endAngle! - oldPoint.startAngle!) * - animationDegreeValue; - } else if (oldPoint.startAngle == oldPoint.endAngle) { - pointStartAngle = pointEndAngle = oldPoint.startAngle!; - } else { - pointStartAngle = oldPoint.startAngle! - - (oldPoint.startAngle! - - (oldPoint.startAngle == - seriesRenderer.segmentRenderingValues['start']! - ? seriesRenderer.segmentRenderingValues['start']! - : seriesRenderer - .renderPoints![getVisiblePointIndex( - seriesRenderer.renderPoints!, - 'before', - pointIndex)!] - .endAngle!)) * - animationDegreeValue; - pointEndAngle = oldPoint.endAngle! - - (oldPoint.endAngle! - - ((oldPoint.endAngle!.round() == - seriesRenderer - .segmentRenderingValues['end'] || - oldPoint.endAngle!.round() == - 360 + - seriesRenderer - .segmentRenderingValues['end']!) - ? oldPoint.endAngle! - : seriesRenderer - .renderPoints![getVisiblePointIndex( - seriesRenderer.renderPoints!, - 'after', - pointIndex)!] - .startAngle!)) * - animationDegreeValue; - } - degree = pointEndAngle - pointStartAngle; - } else if (point.isVisible && oldPoint.isVisible) { - pointStartAngle = (point.startAngle! > oldStartAngle!) - ? oldStartAngle + - ((point.startAngle! - oldStartAngle) * animationDegreeValue) - : oldStartAngle - - ((oldStartAngle - point.startAngle!) * animationDegreeValue); - pointEndAngle = (point.endAngle! > oldEndAngle!) - ? oldEndAngle + - ((point.endAngle! - oldEndAngle) * animationDegreeValue) - : oldEndAngle - - ((oldEndAngle - point.endAngle!) * animationDegreeValue); - degree = pointEndAngle - pointStartAngle; - } - } else if (point.isVisible) { - degree = animationDegreeValue * point.degree!; - pointEndAngle = pointStartAngle! + degree; - } - outerRadius = stateProperties.renderingDetails.initialRender! - ? animationRadiusValue * outerRadius! - : outerRadius; - _calculatePath( - pointIndex, - seriesIndex, - chart, - seriesRenderer, - point, - oldPoint, - canvas, - degree, - innerRadius, - outerRadius, - pointStartAngle, - pointEndAngle, - isDynamicUpdate); - return pointEndAngle; - } - - /// Calculating the data point path. - void _calculatePath( - int pointIndex, - int seriesIndex, - SfCircularChart chart, - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - ChartPoint? oldPoint, - Canvas canvas, - num? degree, - num? innerRadius, - num? outerRadius, - num? pointStartAngle, - num? pointEndAngle, - bool isDynamicUpdate) { - Path? renderPath; - final CornerStyle cornerStyle = series.cornerStyle; - 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; - if (cornerStyle != CornerStyle.bothFlat) { - final num angleDeviation = - findAngleDeviation(innerRadius!, outerRadius!, 360); - actualStartAngle = (cornerStyle == CornerStyle.startCurve || - cornerStyle == CornerStyle.bothCurve) - ? (pointStartAngle! + angleDeviation) - : pointStartAngle!; - actualEndAngle = (cornerStyle == CornerStyle.endCurve || - cornerStyle == CornerStyle.bothCurve) - ? (pointEndAngle! - angleDeviation) - : pointEndAngle!; - } - renderPath = Path(); - renderPath = (cornerStyle == CornerStyle.startCurve || - cornerStyle == CornerStyle.endCurve || - cornerStyle == CornerStyle.bothCurve) - ? getRoundedCornerArcPath( - innerRadius!, - outerRadius!, - point!.center ?? oldPoint!.center, - actualStartAngle, - actualEndAngle, - degree, - cornerStyle, - point) - : getArcPath( - innerRadius!, - outerRadius!, - point!.center ?? oldPoint!.center!, - pointStartAngle, - pointEndAngle, - degree, - chart, - stateProperties.renderingDetails.animateCompleted); - } - drawDataPoints(pointIndex, seriesIndex, chart, seriesRenderer, point, - canvas, renderPath, degree, innerRadius); - } - - /// Draw slice path. - void drawDataPoints( - int pointIndex, - int seriesIndex, - SfCircularChart chart, - CircularSeriesRendererExtension seriesRenderer, - ChartPoint? point, - Canvas canvas, - 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!, - seriesIndex, - pointIndex, - point.center, - innerRadius, - point.outerRadius!); - seriesRenderer.pointRegions.add(pointRegion); - } - final StyleOptions? style = - selectPoint(pointIndex, seriesRenderer, chart, point); - - final Color? fillColor = style != null && style.fill != null - ? style.fill - : (point != null && point.fill != Colors.transparent - ? seriesRenderer.renderer.getPointColor( - seriesRenderer, - point, - pointIndex, - seriesIndex, - point.fill, - seriesRenderer.series.opacity) - : point!.fill); - - final Color? strokeColor = style != null && style.strokeColor != null - ? style.strokeColor - : seriesRenderer.renderer.getPointStrokeColor( - seriesRenderer, point, pointIndex, seriesIndex, point!.strokeColor); - - final num? strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth - : seriesRenderer.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 - ? style.opacity - : seriesRenderer.renderer.getOpacity(seriesRenderer, point, pointIndex, - seriesIndex, seriesRenderer.series.opacity); - - 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! / segmentRenderingValues['sumOfPoints']!) / 4; - stopsList.add( - point.y! / segmentRenderingValues['sumOfPoints']! - initStops); - } else { - if (stopsList.length == 1) { - stopsList.add((point.y! / segmentRenderingValues['sumOfPoints']! + - stopsList.last) + - initStops / 1.5); - } else { - stopsList.add(point.y! / segmentRenderingValues['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.isVisible && - point.index == seriesRenderer.dataPoints.length - 1 && - chart.onCreateShader != null))) { - PointHelper.setPathRect( - point, - Rect.fromCircle( - center: center!, - radius: point.outerRadius!.toDouble(), - )); - } - seriesRenderer.renderPaths.add(renderPath); - if (chart.onCreateShader != null && - point != null && - (point.isVisible || - (!point.isVisible && - point.index == seriesRenderer.dataPoints.length - 1 && - chart.onCreateShader != null)) && - 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 || - (!point.isVisible && - point.index == seriesRenderer.dataPoints.length - 1 && - chart.onCreateShader != null)) { - renderList.clear(); - seriesRenderer.renderList.add(StyleOptions( - fill: fillColor!, - strokeWidth: stateProperties.renderingDetails.animateCompleted - ? strokeWidth! - : 0, - strokeColor: strokeColor!, - opacity: opacity)); - seriesRenderer.renderList.add(PointHelper.getPathRect(point)); - seriesRenderer.renderList.add(innerRect); - } - } else { - drawPath( - canvas, - StyleOptions( - fill: fillColor!, - strokeWidth: stateProperties.renderingDetails.animateCompleted - ? strokeWidth! - : 0, - strokeColor: strokeColor!, - opacity: opacity), - renderPath, - PointHelper.getPathRect(point!), - point.shader ?? _renderModeShader); - // ignore: unnecessary_null_comparison - if (point != null && - (_renderModeShader != null || point.shader != null)) { - // ignore: unnecessary_null_comparison - if (strokeColor != null && - strokeWidth != null && - strokeWidth > 0 && - stateProperties.renderingDetails.animateCompleted) { - final Paint paint = Paint(); - paint.color = strokeColor; - paint.strokeWidth = strokeWidth.toDouble(); - paint.style = PaintingStyle.stroke; - canvas.drawPath(renderPath, paint); - } - } - } - } - } -} - -/// Creates series renderer for Pie series. -class PieSeriesRendererExtension extends PieSeriesRenderer - with CircularSeriesRendererExtension { - /// Calling the default constructor of PieSeriesRenderer class. - PieSeriesRendererExtension() { - seriesType = 'pie'; - } - - @override - late CircularSeries series; - - /// Specifies the chart point info. - ChartPoint? point; -} - -/// Creates series renderer for Doughnut series. -class DoughnutSeriesRendererExtension extends DoughnutSeriesRenderer - with CircularSeriesRendererExtension { - /// Calling the default constructor of DoughnutSeriesRenderer class. - DoughnutSeriesRendererExtension() { - seriesType = 'doughnut'; - } - - /// Stores the series of the corresponding series for renderer. - @override - late CircularSeries series; - - /// Specifies the inner radius value. - late num innerRadius; - - /// Specifies the radius value. - late num radius; -} - -/// Creates series renderer for RadialBar series. -/// -class RadialBarSeriesRendererExtension extends RadialBarSeriesRenderer - with CircularSeriesRendererExtension { - /// Calling the default constructor of RadialBarSeriesRenderer class. - RadialBarSeriesRendererExtension() { - shadowPaths = []; - overFilledPaths = []; - seriesType = 'radialbar'; - } - - @override - late CircularSeries series; - - /// Specifies the inner radius value. - late num innerRadius; - - /// Specifies the radius value. - late num radius; - - /// Specifies the value of fill color and stroke color. - late Color fillColor, strokeColor; - - /// Specifies the value of opacity and stroke width. - late double opacity, strokeWidth; - - /// Holds the value of shadow path. - late List shadowPaths; - - /// Holds the value of over filled path. - late List overFilledPaths; - - /// Represents the value of shadow paint. - late Paint shadowPaint; - - /// Represents the value of over filles paint - Paint? overFilledPaint; - - @override - Offset? center; - - /// Method to find first visible point. - int? getFirstVisiblePointIndex( - RadialBarSeriesRendererExtension seriesRenderer) { - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - if (seriesRenderer.renderPoints![i].isVisible) { - return i; - } - } - return null; - } - - /// 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) { - radius = i == 0 - ? point.outerRadius! - : (point.innerRadius! + - (point.outerRadius! - point.innerRadius!) * animationValue); - innerRadius = i == 0 - ? (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; - } - } - - /// To draw data points of the radial bar series. - /// - void drawDataPoint( - ChartPoint point, - num? degree, - num pointStartAngle, - num? pointEndAngle, - RadialBarSeriesRendererExtension seriesRenderer, - bool hide, - num? oldRadius, - num? oldInnerRadius, - ChartPoint? _oldPoint, - num? oldStart, - num? oldEnd, - int i, - Canvas canvas, - int index, - SfCircularChart chart, - double actualDegree) { - late RadialBarSeries series; - if (seriesRenderer.series is RadialBarSeries) { - series = seriesRenderer.series as RadialBarSeries; - } - drawPath( - canvas, - StyleOptions( - 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!, - 0, - 360 - 0.001, - 360 - 0.001, - chart, - true)); - if (radius > 0 && degree != null && degree > 0) { - _renderRadialPoints( - point, - degree, - pointStartAngle, - pointEndAngle, - seriesRenderer, - hide, - oldRadius, - oldInnerRadius, - _oldPoint, - oldStart, - oldEnd, - i, - canvas, - index, - chart, - actualDegree); - } - } - - /// Method to render radial data points. - /// - void _renderRadialPoints( - ChartPoint point, - num? degree, - num pointStartAngle, - num? pointEndAngle, - RadialBarSeriesRendererExtension seriesRenderer, - bool hide, - num? oldRadius, - num? oldInnerRadius, - ChartPoint? _oldPoint, - num? oldStart, - num? oldEnd, - int i, - Canvas canvas, - int index, - SfCircularChart chart, - double actualDegree) { - if (point.isVisible) { - final Region pointRegion = Region( - degreesToRadians(point.startAngle!), - degreesToRadians(point.endAngle!), - point.startAngle!, - point.endAngle!, - index, - i, - point.center, - innerRadius, - point.outerRadius!); - seriesRenderer.pointRegions.add(pointRegion); - } - - final num angleDeviation = findAngleDeviation( - hide ? oldInnerRadius! : innerRadius, hide ? oldRadius! : radius, 360); - final CornerStyle cornerStyle = series.cornerStyle; - if (cornerStyle == CornerStyle.bothCurve || - cornerStyle == CornerStyle.startCurve) { - hide - ? oldStart = _oldPoint!.startAngle! + angleDeviation - : pointStartAngle += angleDeviation; - } - if (cornerStyle == CornerStyle.bothCurve || - cornerStyle == CornerStyle.endCurve) { - hide - ? oldEnd = _oldPoint!.endAngle! - angleDeviation - : pointEndAngle = pointEndAngle! - angleDeviation; - } - final StyleOptions? style = - seriesRenderer.selectPoint(i, seriesRenderer, chart, point); - fillColor = style != null && style.fill != null - ? style.fill! - : (point.fill != Colors.transparent - ? seriesRenderer.renderer.getPointColor( - seriesRenderer, point, i, index, point.fill, series.opacity)! - : point.fill); - strokeColor = style != null && style.strokeColor != null - ? style.strokeColor! - : seriesRenderer.renderer.getPointStrokeColor( - seriesRenderer, point, i, index, point.strokeColor); - strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth!.toDouble() - : seriesRenderer.renderer - .getPointStrokeWidth( - seriesRenderer, point, i, index, point.strokeWidth) - .toDouble(); - opacity = style != null && style.opacity != null - ? style.opacity!.toDouble() - : seriesRenderer.renderer - .getOpacity(seriesRenderer, point, i, index, series.opacity); - seriesRenderer.innerRadialradius = - !point.isVisible || (seriesRenderer.innerRadialradius == null) - ? innerRadius - : seriesRenderer.innerRadialradius; - seriesRenderer.renderList.clear(); - - _drawRadialBarPath( - canvas, - point, - chart, - seriesRenderer, - hide, - pointStartAngle, - pointEndAngle, - oldRadius, - oldInnerRadius, - oldStart, - oldEnd, - degree, - actualDegree); - } - - /// Method to draw the radial bar series path. - /// - void _drawRadialBarPath( - Canvas canvas, - ChartPoint point, - SfCircularChart chart, - RadialBarSeriesRendererExtension seriesRenderer, - bool hide, - num pointStartAngle, - num? pointEndAngle, - num? oldRadius, - num? oldInnerRadius, - num? oldStart, - num? oldEnd, - num? degree, - double actualDegree) { - Path path; - if (degree! > 360) { - path = getRoundedCornerArcPath( - hide ? oldInnerRadius! : innerRadius, - hide ? oldRadius! : radius, - center!, - 0, - 360 - 0.001, - 360 - 0.001, - series.cornerStyle, - point); - final double currentInnerRadius = - hide ? oldInnerRadius!.toDouble() : innerRadius.toDouble(); - final double outerRadius = - hide ? oldRadius!.toDouble() : radius.toDouble(); - final double startAngle = - hide ? oldStart!.toDouble() : pointStartAngle.toDouble(); - final double endAngle = - hide ? oldEnd!.toDouble() : pointEndAngle!.toDouble(); - path.arcTo( - Rect.fromCircle(center: center!, radius: outerRadius.toDouble()), - degreesToRadians(startAngle).toDouble(), - degreesToRadians(endAngle - startAngle).toDouble(), - true); - path.arcTo( - Rect.fromCircle( - center: center!, radius: currentInnerRadius.toDouble()), - degreesToRadians(endAngle.toDouble()).toDouble(), - (degreesToRadians(startAngle.toDouble()) - - degreesToRadians(endAngle.toDouble())) - .toDouble(), - false); - } else { - path = getRoundedCornerArcPath( - hide ? oldInnerRadius! : innerRadius, - hide ? oldRadius! : radius, - center!, - hide ? oldStart! : pointStartAngle, - hide ? oldEnd! : pointEndAngle!, - degree, - series.cornerStyle, - point); - } - - seriesRenderer.renderPaths.add(path); - - if (chart.onCreateShader != null && point.shader == null) { - PointHelper.setPathRect( - point, - 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: stateProperties.renderingDetails.animateCompleted - ? strokeWidth - : 0, - strokeColor: strokeColor, - opacity: opacity)); - seriesRenderer.renderList.add(PointHelper.getPathRect(point)); - seriesRenderer.renderList.add(innerRect); - } else { - if (hide - ? (((oldEnd! - oldStart!) > 0) && (oldRadius != oldInnerRadius)) - : ((pointEndAngle! - pointStartAngle) > 0)) { - drawPath( - canvas, - StyleOptions( - fill: fillColor, - strokeWidth: stateProperties.renderingDetails.animateCompleted - ? strokeWidth - : 0, - strokeColor: strokeColor, - opacity: opacity), - path, - PointHelper.getPathRect(point), - point.shader); - // ignore: unnecessary_null_comparison - if (point.shader != null && - // ignore: unnecessary_null_comparison - strokeColor != null && - // ignore: unnecessary_null_comparison - strokeWidth != null && - strokeWidth > 0 && - stateProperties.renderingDetails.animateCompleted) { - final Paint paint = Paint(); - paint.color = strokeColor; - paint.strokeWidth = strokeWidth; - paint.style = PaintingStyle.stroke; - canvas.drawPath(path, paint); - } - } - } - - final num? angle = hide ? oldEnd : pointEndAngle; - final num? startAngle = hide ? oldStart : pointStartAngle; - if (actualDegree > 360 && - angle != null && - startAngle != null && - angle >= startAngle + 180) { - _applyShadow(hide, angle, actualDegree, canvas, chart, point, - oldInnerRadius, oldRadius); - } - } - - /// Method to apply shadow at segment's end. - void _applyShadow( - bool hide, - num? pointEndAngle, - double actualDegree, - Canvas canvas, - SfCircularChart chart, - ChartPoint point, - num? oldInnerRadius, - num? oldRadius) { - if (pointEndAngle != null && actualDegree > 360) { - final double currentInnerRadius = - hide ? oldInnerRadius!.toDouble() : innerRadius.toDouble(); - final double outerRadius = - hide ? oldRadius!.toDouble() : radius.toDouble(); - final double actualRadius = (currentInnerRadius - outerRadius).abs() / 2; - final Offset? midPoint = degreeToPoint( - pointEndAngle, (currentInnerRadius + outerRadius) / 2, center!); - if (actualRadius > 0) { - double strokeWidth = actualRadius * 0.2; - strokeWidth = strokeWidth < 3 ? 3 : (strokeWidth > 5 ? 5 : strokeWidth); - shadowPaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..maskFilter = - MaskFilter.blur(BlurStyle.normal, _getSigmaFromRadius(3)); - - overFilledPaint = Paint()..color = fillColor; - if (point.shader != null) { - overFilledPaint!.shader = point.shader; - } - - if (series.cornerStyle == CornerStyle.endCurve || - series.cornerStyle == CornerStyle.bothCurve) { - pointEndAngle = - (pointEndAngle > 360 ? pointEndAngle : (pointEndAngle - 360)) + - 11.5; - final Path path = Path() - ..addArc( - Rect.fromCircle( - center: midPoint!, - radius: actualRadius - (actualRadius * 0.05)), - degreesToRadians(pointEndAngle + 22.5).toDouble(), - degreesToRadians(118.125).toDouble()); - final Path overFilledPath = Path() - ..addArc( - Rect.fromCircle(center: midPoint, radius: actualRadius), - degreesToRadians(pointEndAngle - 20).toDouble(), - degreesToRadians(225).toDouble()); - if (chart.onCreateShader != null && point.shader == null) { - shadowPaths.add(path); - overFilledPaths.add(overFilledPath); - } else { - canvas.drawPath(path, shadowPaint); - canvas.drawPath(overFilledPath, overFilledPaint!); - } - } else if (series.cornerStyle == CornerStyle.bothFlat || - series.cornerStyle == CornerStyle.startCurve) { - overFilledPaint! - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..color = fillColor; - final Offset? startPoint = degreeToPoint( - pointEndAngle, outerRadius - (outerRadius * 0.025), center!); - - final Offset? endPoint = degreeToPoint(pointEndAngle, - currentInnerRadius + (currentInnerRadius * 0.025), center!); - - final Offset? overFilledStartPoint = - degreeToPoint(pointEndAngle - 2, outerRadius, center!); - final Offset? overFilledEndPoint = - degreeToPoint(pointEndAngle - 2, currentInnerRadius, center!); - if (chart.onCreateShader != null && point.shader == null) { - final Path path = Path() - ..moveTo(startPoint!.dx, startPoint.dy) - ..lineTo(endPoint!.dx, endPoint.dy); - path.close(); - final Path overFilledPath = Path() - ..moveTo(overFilledStartPoint!.dx, overFilledStartPoint.dy) - ..lineTo(overFilledEndPoint!.dx, overFilledEndPoint.dy); - path.close(); - shadowPaths.add(path); - overFilledPaths.add(overFilledPath); - } else { - canvas.drawLine(startPoint!, endPoint!, shadowPaint); - canvas.drawLine( - overFilledStartPoint!, overFilledEndPoint!, overFilledPaint!); - } - } - } - } - } - - /// Method to convert the radius to sigma. - double _getSigmaFromRadius(double radius) { - return radius * 0.57735 + 0.5; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart deleted file mode 100644 index f8d76de43..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/event_args.dart'; -import '../base/circular_state_properties.dart'; -import '../renderer/chart_point.dart'; -import '../renderer/common.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/helper.dart'; - -/// Represents the painter of doughnut chart. -class DoughnutChartPainter extends CustomPainter { - /// Creates the instance of doughnut chart painter. - DoughnutChartPainter({ - required this.stateProperties, - required this.index, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : super(repaint: notifier); - - /// Specifies the circular chart state. - final CircularStateProperties stateProperties; - - /// Specifies the value of index. - final int index; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController? animationController; - - /// Specifies the value of series animation. - final Animation? seriesAnimation; - - /// Specifies the value of series renderer. - late DoughnutSeriesRendererExtension seriesRenderer; - - /// To paint series. - @override - void paint(Canvas canvas, Size size) { - num? pointStartAngle; - seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[index] - as DoughnutSeriesRendererExtension; - pointStartAngle = seriesRenderer.segmentRenderingValues['start']; - seriesRenderer.innerRadius = - seriesRenderer.segmentRenderingValues['currentInnerRadius']!; - seriesRenderer.radius = - seriesRenderer.segmentRenderingValues['currentRadius']!; - ChartPoint point; - seriesRenderer.pointRegions = []; - ChartPoint? _oldPoint; - final DoughnutSeriesRendererExtension? oldSeriesRenderer = (stateProperties - .renderingDetails.widgetNeedUpdate && - !stateProperties.renderingDetails.isLegendToggled && - stateProperties.prevSeriesRenderer?.seriesType == 'doughnut') - ? stateProperties.prevSeriesRenderer as DoughnutSeriesRendererExtension - : 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] - : ((stateProperties.renderingDetails.isLegendToggled && - stateProperties.prevSeriesRenderer?.seriesType == 'doughnut') - ? stateProperties.oldPoints![i] - : null); - pointStartAngle = seriesRenderer.circularRenderPoint( - stateProperties.chart, - seriesRenderer, - point, - pointStartAngle, - point.innerRadius, - point.outerRadius, - canvas, - index, - i, - seriesAnimation?.value ?? 1, - 1, - checkIsAnyPointSelect(seriesRenderer, point, stateProperties.chart), - _oldPoint, - stateProperties.oldPoints); - } - - if (seriesRenderer.renderList.isNotEmpty) { - Shader? _chartShader; - if (stateProperties.chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = ChartShaderDetails(seriesRenderer.renderList[1], - seriesRenderer.renderList[2], 'series'); - _chartShader = - stateProperties.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 == true) { - 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 - bool shouldRepaint(DoughnutChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart deleted file mode 100644 index b06ca1b55..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/event_args.dart'; -import '../base/circular_base.dart'; -import '../base/circular_state_properties.dart'; -import '../renderer/chart_point.dart'; -import '../renderer/common.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/helper.dart'; - -/// Represents the pie chart painter. -class PieChartPainter extends CustomPainter { - /// Creates an instance of pie chart painter. - PieChartPainter({ - required this.stateProperties, - required this.index, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : chart = stateProperties.chart, - super(repaint: notifier); - - /// Specifies the circular state properties. - final CircularStateProperties stateProperties; - - /// Holds the circularchart. - final SfCircularChart chart; - - /// Holds the index value. - final int index; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Holds the animation controller. - final AnimationController? animationController; - - /// Specifies the series animation. - final Animation? seriesAnimation; - - /// Specifies the pie series renderer extension. - late PieSeriesRendererExtension seriesRenderer; - - /// To paint series. - @override - void paint(Canvas canvas, Size size) { - num? pointStartAngle; - seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[index] - as PieSeriesRendererExtension; - pointStartAngle = seriesRenderer.segmentRenderingValues['start']; - seriesRenderer.pointRegions = []; - bool isAnyPointNeedSelect = false; - if (stateProperties.renderingDetails.initialRender!) { - isAnyPointNeedSelect = - checkIsAnyPointSelect(seriesRenderer, seriesRenderer.point, chart); - } - ChartPoint? _oldPoint; - ChartPoint? point = seriesRenderer.point; - final PieSeriesRendererExtension? oldSeriesRenderer = - (stateProperties.renderingDetails.widgetNeedUpdate && - !stateProperties.renderingDetails.isLegendToggled && - stateProperties.prevSeriesRenderer != null && - stateProperties.prevSeriesRenderer!.seriesType == 'pie') - ? stateProperties.prevSeriesRenderer! as PieSeriesRendererExtension - : 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] - : ((stateProperties.renderingDetails.isLegendToggled && - stateProperties.prevSeriesRenderer?.seriesType == 'pie') - ? stateProperties.oldPoints![i] - : null); - point.innerRadius = 0.0; - pointStartAngle = seriesRenderer.circularRenderPoint( - chart, - seriesRenderer, - point, - pointStartAngle, - point.innerRadius, - point.outerRadius, - canvas, - index, - i, - seriesAnimation?.value ?? 1, - seriesAnimation?.value ?? 1, - isAnyPointNeedSelect, - _oldPoint, - stateProperties.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 == true) { - 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 - bool shouldRepaint(PieChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart deleted file mode 100644 index d12dcf97d..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart +++ /dev/null @@ -1,250 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../common/event_args.dart'; -import '../base/circular_state_properties.dart'; -import '../renderer/chart_point.dart'; -import '../renderer/common.dart'; -import '../renderer/radial_bar_series.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/helper.dart'; - -/// Represents the pointer to draw radial bar series. -class RadialBarPainter extends CustomPainter { - /// Creates the instance for radial bar series. - RadialBarPainter({ - required this.stateProperties, - required this.index, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - ValueNotifier? notifier, - }) : super(repaint: notifier); - - /// Specifies the value of circular state properties. - final CircularStateProperties stateProperties; - - /// Holds the index value. - final int index; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController? animationController; - - /// Specifies the value of series animation. - final Animation? seriesAnimation; - - /// Holds the value of radial bar series extension. - late RadialBarSeriesRendererExtension seriesRenderer; - late num _length, _sum, _ringSize, _animationValue, _actualStartAngle; - late int? _firstVisible; - late num? _gap; - late bool _isLegendToggle; - late RadialBarSeriesRendererExtension? _oldSeriesRenderer; - - /// Specifies the value of actual length. - late double actualDegree; - - /// Method to get length of the visible point. - num _getLength(Canvas canvas) { - num length = 0; - seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[index] - as RadialBarSeriesRendererExtension; - seriesRenderer.pointRegions = []; - seriesRenderer.innerRadius = - seriesRenderer.segmentRenderingValues['currentInnerRadius']!; - seriesRenderer.radius = - seriesRenderer.segmentRenderingValues['currentRadius']!; - seriesRenderer.center = seriesRenderer.center!; - canvas.clipRect(stateProperties.renderingDetails.chartAreaRect); - - // finding visible points count - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - length += seriesRenderer.renderPoints![i].isVisible ? 1 : 0; - } - return length; - } - - /// Method to initialize the values to draw the radial bar series. - void _initializeValues(Canvas canvas) { - _length = _getLength(canvas); - _sum = seriesRenderer.segmentRenderingValues['sumOfPoints']!; - _actualStartAngle = seriesRenderer.segmentRenderingValues['start']!; - - // finding first visible point - _firstVisible = seriesRenderer.getFirstVisiblePointIndex(seriesRenderer); - _ringSize = (seriesRenderer.segmentRenderingValues['currentRadius']! - - seriesRenderer.segmentRenderingValues['currentInnerRadius']!) - .abs() / - _length; - _gap = percentToValue( - seriesRenderer.series.gap, - (seriesRenderer.segmentRenderingValues['currentRadius']! - - seriesRenderer.segmentRenderingValues['currentInnerRadius']!) - .abs()); - _animationValue = seriesAnimation?.value ?? 1; - _isLegendToggle = stateProperties.renderingDetails.isLegendToggled; - _oldSeriesRenderer = (stateProperties.renderingDetails.widgetNeedUpdate && - !stateProperties.renderingDetails.isLegendToggled && - stateProperties.prevSeriesRenderer!.seriesType == 'radialbar') - ? stateProperties.prevSeriesRenderer! - as RadialBarSeriesRendererExtension - : null; - seriesRenderer.renderPaths.clear(); - } - - /// Method to paint radial bar series. - @override - void paint(Canvas canvas, Size size) { - num? pointStartAngle, pointEndAngle, degree; - ChartPoint? _oldPoint; - late ChartPoint point; - _initializeValues(canvas); - late RadialBarSeries series; - num? oldStart, oldEnd, oldRadius, oldInnerRadius; - late bool isDynamicUpdate, hide; - seriesRenderer.shadowPaths.clear(); - seriesRenderer.overFilledPaths.clear(); - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - num? value; - point = seriesRenderer.renderPoints![i]; - if (seriesRenderer.series is RadialBarSeries) { - series = seriesRenderer.series as RadialBarSeries; - } - _oldPoint = (_oldSeriesRenderer != null && - _oldSeriesRenderer!.oldRenderPoints != null && - (_oldSeriesRenderer!.oldRenderPoints!.length - 1 >= i)) - ? _oldSeriesRenderer!.oldRenderPoints![i] - : (_isLegendToggle ? stateProperties.oldPoints![i] : null); - pointStartAngle = _actualStartAngle; - isDynamicUpdate = _oldPoint != null; - hide = false; - actualDegree = 0; - if (!isDynamicUpdate || - ((_oldPoint.isVisible && point.isVisible) || - (_oldPoint.isVisible && !point.isVisible) || - (!_oldPoint.isVisible && point.isVisible))) { - 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; - } - degree = (value ?? point.y!).abs() / (series.maximumValue ?? _sum); - degree = degree * (360 - 0.001); - actualDegree = degree.toDouble(); - degree = isDynamicUpdate ? degree : degree * _animationValue; - pointEndAngle = pointStartAngle + degree; - point.midAngle = (pointStartAngle + pointEndAngle) / 2; - point.startAngle = pointStartAngle; - point.endAngle = pointEndAngle; - point.center = seriesRenderer.center!; - point.innerRadius = seriesRenderer.innerRadius = - seriesRenderer.innerRadius + - ((i == _firstVisible) ? 0 : _ringSize) - - (series.trackBorderWidth / 2) / series.dataSource!.length; - point.outerRadius = seriesRenderer.radius = _ringSize < _gap! - ? 0 - : seriesRenderer.innerRadius + - _ringSize - - _gap! - - (series.trackBorderWidth / 2) / series.dataSource!.length; - if (_isLegendToggle) { - seriesRenderer.calculateVisiblePointLegendToggleAnimation( - point, _oldPoint, i, _animationValue); - } - } //animate on hiding - else if (_isLegendToggle && !point.isVisible && _oldPoint!.isVisible) { - hide = true; - oldEnd = _oldPoint.endAngle; - oldStart = _oldPoint.startAngle; - degree = _oldPoint.y!.abs() / (series.maximumValue ?? _sum); - degree = degree * (360 - 0.001); - actualDegree = degree.toDouble(); - oldInnerRadius = _oldPoint.innerRadius! + - ((_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2 - - _oldPoint.innerRadius!) * - _animationValue; - oldRadius = _oldPoint.outerRadius! - - (_oldPoint.outerRadius! - - (_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2) * - _animationValue; - } - // ignore: unnecessary_type_check - if (seriesRenderer is RadialBarSeriesRendererExtension) { - seriesRenderer.drawDataPoint( - point, - degree, - pointStartAngle, - pointEndAngle, - seriesRenderer, - hide, - oldRadius, - oldInnerRadius, - _oldPoint, - oldStart, - oldEnd, - i, - canvas, - index, - stateProperties.chart, - actualDegree); - } - } - } - - _renderRadialBarSeries(canvas); - } - - /// Method to render the radial bar series. - void _renderRadialBarSeries(Canvas canvas) { - if (seriesRenderer.renderList.isNotEmpty) { - Shader? _chartShader; - if (stateProperties.chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = ChartShaderDetails(seriesRenderer.renderList[1], - seriesRenderer.renderList[2], 'series'); - _chartShader = - stateProperties.chart.onCreateShader!(chartShaderDetails); - } - - for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { - drawPath( - canvas, - seriesRenderer.renderList[0], - seriesRenderer.renderPaths[k], - seriesRenderer.renderList[1], - _chartShader!); - } - - for (int k = 0; k < seriesRenderer.shadowPaths.length; k++) { - canvas.drawPath( - seriesRenderer.shadowPaths[k], seriesRenderer.shadowPaint); - } - - if (_chartShader != null && seriesRenderer.overFilledPaint != null) { - seriesRenderer.overFilledPaint!.shader = _chartShader; - } - for (int k = 0; k < seriesRenderer.overFilledPaths.length; k++) { - canvas.drawPath( - seriesRenderer.overFilledPaths[k], seriesRenderer.overFilledPaint!); - } - - if (seriesRenderer.renderList[0].strokeColor != null && - seriesRenderer.renderList[0].strokeWidth != null && - (seriesRenderer.renderList[0].strokeWidth > 0) == true) { - 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 - bool shouldRepaint(CustomPainter oldDelegate) => isRepaint; -} 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 deleted file mode 100644 index efe870f94..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart +++ /dev/null @@ -1,78 +0,0 @@ -/// Data points grouping mode. -enum CircularChartGroupMode { - /// - CircularChartGroupMode.point, groups the points based on length. - point, - - /// - CircularChartGroupMode.value, groups the points based on the y value. - value -} - -/// Data label position of range bar series. -enum Position { - /// - Position.left, places the data label to the left side. - left, - - /// - Position.right, places the data label to the right side. - right -} - -/// Data labels intersect action. -enum LabelIntersectAction { - ///- `LabelIntersectAction.hide`, hides the intersecting labels. - hide, - - /// - `LabelIntersectAction.none`, will not perform any action on intersection. - none, - - /// - `LabelIntersectAction.shift`, will shift and position the intersecting labels smartly. If the labels are moved out of the chart area, then the labels will be trimmed and the eclipse will be shown for the trimmed labels. - shift -} - -/// Type of connector line. -enum ConnectorType { - /// - ConnectorType.curve, will render the data label connector line curly. - curve, - - /// - ConnectorType.line, will render the data label connector lien straightly. - line -} - -/// Corner style of range bar series. -enum CornerStyle { - /// - CornerStyle.bothFlat, will render both the corners flatly. - bothFlat, - - /// - CornerStyle.bothCurve, will render both the corners curly. - bothCurve, - - /// - CornerStyle.startCurve, will render starting corner curly. - startCurve, - - /// - 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, -} - -/// Data label overflow action when it’s overflowing from its region area. -enum OverflowMode { - /// - OverflowMode.none, no action will be taken and priority goes for - /// `LabelIntersectAction.shift` of `labelIntersectAction` property. - none, - - /// - OverflowMode.trim, data label text will be trimmed. - trim, - - /// - OverflowMode.shift, datalabel text will be shifted to outside. - shift, - - /// - OverflowMode.hide, data label text will be hidden. - hide, -} 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 deleted file mode 100644 index d924ea135..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart +++ /dev/null @@ -1,464 +0,0 @@ -import 'dart:math' as math; -import 'dart:math'; -import 'dart:typed_data'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/circular_chart/base/circular_state_properties.dart'; -import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import '../../chart/chart_series/xy_data_series.dart'; -import '../renderer/common.dart'; -import '../renderer/renderer_extension.dart'; - -/// To get equivalent value for the percentage. -num? percentToValue(String? value, num size) { - return value != null - ? value.contains('%') - ? (size / 100) * - (num.tryParse(value.replaceAll(RegExp('%'), '')))!.abs() - : (num.tryParse(value))?.abs() - : null; -} - -/// Convert degree to radian. -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) { - final Path path = Path(); - startAngle = degreesToRadians(startAngle!); - endAngle = degreesToRadians(endAngle!); - degree = degreesToRadians(degree!); - - final math.Point innerRadiusStartPoint = math.Point( - innerRadius * cos(startAngle) + center.dx, - innerRadius * sin(startAngle) + center.dy); - final math.Point innerRadiusEndPoint = math.Point( - innerRadius * cos(endAngle) + center.dx, - innerRadius * sin(endAngle) + center.dy); - - final math.Point radiusStartPoint = math.Point( - radius * cos(startAngle) + center.dx, - radius * sin(startAngle) + center.dy); - - if (isAnimate) { - path.moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); - } - - final bool isFullCircle = - // ignore: unnecessary_null_comparison - startAngle != null && endAngle != null && endAngle - startAngle == 2 * pi; - - final num midpointAngle = (endAngle + startAngle) / 2; - - if (isFullCircle) { - 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.lineTo(radiusStartPoint.x, radiusStartPoint.y); - path.arcTo(Rect.fromCircle(center: center, radius: radius.toDouble()), - startAngle.toDouble(), degree.toDouble(), true); - } - - if (isFullCircle) { - 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.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); - } - return path; -} - -/// To get rounded corners Arc path. -Path getRoundedCornerArcPath( - num innerRadius, - num outerRadius, - Offset? center, - num startAngle, - num endAngle, - num? degree, - CornerStyle cornerStyle, - ChartPoint point) { - final Path path = Path(); - - Offset _midPoint; - num midStartAngle, midEndAngle; - if (cornerStyle == CornerStyle.startCurve || - cornerStyle == CornerStyle.bothCurve) { - _midPoint = - degreeToPoint(startAngle, (innerRadius + outerRadius) / 2, center!); - - midStartAngle = degreesToRadians(180); - - midEndAngle = midStartAngle + degreesToRadians(180); - - path.addArc( - Rect.fromCircle( - center: _midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle.toDouble(), - midEndAngle.toDouble()); - } - - path.addArc( - Rect.fromCircle(center: center!, radius: outerRadius.toDouble()), - degreesToRadians(startAngle).toDouble(), - degreesToRadians(endAngle - startAngle).toDouble()); - - if (cornerStyle == CornerStyle.endCurve || - cornerStyle == CornerStyle.bothCurve) { - _midPoint = - degreeToPoint(endAngle, (innerRadius + outerRadius) / 2, center); - - midStartAngle = degreesToRadians(endAngle / 2); - - midEndAngle = midStartAngle + degreesToRadians(180); - - path.arcTo( - Rect.fromCircle( - center: _midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle.toDouble(), - midEndAngle.toDouble(), - false); - } - - path.arcTo( - 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, - CircularSeriesRendererExtension seriesRenderer) { - Region? pointRegion; - const num chartStartAngle = -.5 * pi; - num fromCenterX, - fromCenterY, - tapAngle, - pointStartAngle, - pointEndAngle, - distanceFromCenter; - // ignore: prefer_is_empty - if (seriesRenderer.renderPoints?.length == 0 || - seriesRenderer.renderPoints == null) { - seriesRenderer = seriesRenderer.stateProperties.prevSeriesRenderer!; - } - for (final Region region in seriesRenderer.pointRegions) { - fromCenterX = position!.dx - region.center!.dx; - fromCenterY = position.dy - region.center!.dy; - tapAngle = (atan2(fromCenterY, fromCenterX) - chartStartAngle) % (2 * pi); - pointStartAngle = region.start - degreesToRadians(-90); - 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); - } else if ((region.endAngle + 90) > 360) { - tapAngle = tapAngle > pointStartAngle ? tapAngle : 2 * pi + tapAngle; - } - if (tapAngle >= pointStartAngle && tapAngle <= pointEndAngle) { - distanceFromCenter = - sqrt(pow(fromCenterX.abs(), 2) + pow(fromCenterY.abs(), 2)); - if (distanceFromCenter <= region.outerRadius && - distanceFromCenter >= region.innerRadius!) { - pointRegion = region; - } - } - } - - return pointRegion; -} - -/// Draw the 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); - 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!.toDouble(); - paint.style = PaintingStyle.stroke; - canvas.drawPath(path, paint); - } -} - -/// To convert degree to point and return position. -Offset degreeToPoint(num degree, num radius, Offset center) { - degree = degreesToRadians(degree); - return Offset( - center.dx + cos(degree) * radius, center.dy + sin(degree) * radius); -} - -/// To repaint circular chart. -void needsRepaintCircularChart( - List currentSeriesRenderers, - List oldSeriesRenderers) { - if (currentSeriesRenderers.length == oldSeriesRenderers.length && - currentSeriesRenderers[0].series == oldSeriesRenderers[0]!.series) { - for (int seriesIndex = 0; - seriesIndex < oldSeriesRenderers.length; - seriesIndex++) { - canRepaintSeries(currentSeriesRenderers, oldSeriesRenderers, seriesIndex); - } - } else { - // ignore: avoid_function_literals_in_foreach_calls - currentSeriesRenderers.forEach( - (CircularSeriesRendererExtension seriesRenderer) => - seriesRenderer.needsRepaint = true); - } -} - -/// To repaint series. -void canRepaintSeries( - List currentSeriesRenderers, - List oldSeriesRenderers, - int seriesIndex) { - final CircularSeriesRendererExtension seriesRenderer = - currentSeriesRenderers[0]; - final CircularSeriesRendererExtension oldWidgetSeriesRenderer = - oldSeriesRenderers[seriesIndex]!; - final CircularSeries series = seriesRenderer.series; - final CircularSeries oldWidgetSeries = - oldWidgetSeriesRenderer.series; - if (seriesRenderer.center?.dy != oldWidgetSeriesRenderer.center?.dy || - seriesRenderer.center?.dx != oldWidgetSeriesRenderer.center?.dx || - series.borderWidth != oldWidgetSeries.borderWidth || - series.name != oldWidgetSeries.name || - series.borderColor.value != oldWidgetSeries.borderColor.value || - seriesRenderer.segmentRenderingValues['currentInnerRadius'] != - oldWidgetSeriesRenderer - .segmentRenderingValues['currentInnerRadius'] || - seriesRenderer.segmentRenderingValues['currentRadius'] != - oldWidgetSeriesRenderer.segmentRenderingValues['currentRadius'] || - seriesRenderer.segmentRenderingValues['start'] != - oldWidgetSeriesRenderer.segmentRenderingValues['start'] || - seriesRenderer.segmentRenderingValues['totalAngle'] != - oldWidgetSeriesRenderer.segmentRenderingValues['totalAngle'] || - 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.mode != - oldWidgetSeries.emptyPointSettings.mode || - series.dataSource?.length != oldWidgetSeries.dataSource?.length || - series.dataLabelSettings.isVisible != - oldWidgetSeries.dataLabelSettings.isVisible || - series.dataLabelSettings.color?.value != - oldWidgetSeries.dataLabelSettings.color?.value || - series.dataLabelSettings.borderRadius != - 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.labelIntersectAction != - oldWidgetSeries.dataLabelSettings.labelIntersectAction || - series.dataLabelSettings.labelPosition != - oldWidgetSeries.dataLabelSettings.labelPosition || - series.dataLabelSettings.connectorLineSettings.color?.value != - oldWidgetSeries - .dataLabelSettings.connectorLineSettings.color?.value || - series.dataLabelSettings.connectorLineSettings.width != - oldWidgetSeries.dataLabelSettings.connectorLineSettings.width || - series.dataLabelSettings.connectorLineSettings.length != - oldWidgetSeries.dataLabelSettings.connectorLineSettings.length || - series.dataLabelSettings.connectorLineSettings.type != - oldWidgetSeries.dataLabelSettings.connectorLineSettings.type || - series.xValueMapper != oldWidgetSeries.xValueMapper || - series.yValueMapper != oldWidgetSeries.yValueMapper || - series.enableTooltip != oldWidgetSeries.enableTooltip) { - seriesRenderer.needsRepaint = true; - } else { - seriesRenderer.needsRepaint = false; - } -} - -/// To return deviation angle. -num findAngleDeviation(num innerRadius, num outerRadius, num totalAngle) { - final num calcRadius = (innerRadius + outerRadius) / 2; - final num circumference = 2 * pi * calcRadius; - final num rimSize = (innerRadius - outerRadius).abs(); - final num deviation = ((rimSize / 2) / circumference) * 100; - return (deviation * 360) / 100; -} - -/// It returns the actual label value for tooltip and data label etc. -String getDecimalLabelValue(num? value, [int? showDigits]) { - if (value != null && value.toString().split('.').length > 1) { - final String str = value.toString(); - final List list = str.split('.'); - value = double.parse(value.toStringAsFixed(showDigits ?? 3)); - value = (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000' || - list[1] == '0000000') - ? value.round() - : value; - } - - return value.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; -} - -/// Circular pixel to point. -ChartPoint circularPixelToPoint( - Offset position, CircularStateProperties chartState) { - int pointIndex; - ChartPoint? dataPoint; - final Region? pointRegion = getCircularPointRegion(chartState.chart, position, - chartState.chartSeries.visibleSeriesRenderers[0]); - if (pointRegion != null) { - pointIndex = pointRegion.pointIndex; - // ignore: prefer_is_empty - if (chartState.chartSeries.visibleSeriesRenderers[0].renderPoints?.length == - 0 || - chartState.chartSeries.visibleSeriesRenderers[0].renderPoints == null) { - dataPoint = chartState.chartSeries.visibleSeriesRenderers[0] - .stateProperties.prevSeriesRenderer!.dataPoints[pointIndex]; - } else { - dataPoint = chartState - .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; - } - } - return dataPoint!; -} - -/// Circular point to pixel. -Offset circularPointToPixel( - ChartPoint point, CircularStateProperties chartState) { - Offset location; - - if (point.midAngle == null) { - final String x = point.x; - final num y = point.y!; - final CircularSeriesRendererExtension seriesRenderer = - chartState.chartSeries.visibleSeriesRenderers[0]; - for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { - if (seriesRenderer.dataPoints[i].x == x && - seriesRenderer.dataPoints[i].y == y) { - point = seriesRenderer.dataPoints[i]; - } - } - } - - location = degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - location = Offset(location.dx, location.dy); - return location; -} - -/// To find the current point overlapped with previous points. -bool isOverlapWithPrevious(ChartPoint currentPoint, - List> points, int currentPointIndex) { - for (int i = 0; i < currentPointIndex; i++) { - if (i != points.indexOf(currentPoint) && - points[i].isVisible && - isOverlap(currentPoint.labelRect, points[i].labelRect)) { - return true; - } - } - return false; -} - -/// To find the current point overlapped with next points. -bool isOverlapWithNext(ChartPoint point, - List> points, int pointIndex) { - for (int i = pointIndex; i < points.length; i++) { - if (i != points.indexOf(point) && - points[i].isVisible && - // ignore: unnecessary_null_comparison - (points[i].labelRect != null && point.labelRect != null) && - isOverlap(point.labelRect, points[i].labelRect)) { - return true; - } - } - return false; -} - -/// Calculate the connected line path for shifted data label. -ChartLocation getPerpendicularDistance( - ChartLocation startPoint, ChartPoint point) { - ChartLocation increasedLocation; - const num add = 10; - final num height = add + 10 * math.sin(point.midAngle! * math.pi / 360); - if (point.midAngle! > 270 && point.midAngle! < 360) { - increasedLocation = ChartLocation( - startPoint.x + - height * (math.cos((360 - point.midAngle!) * math.pi / 180)), - startPoint.y - - height * (math.sin((360 - point.midAngle!) * math.pi / 180))); - } else if (point.midAngle! > 0 && point.midAngle! < 90) { - increasedLocation = ChartLocation( - startPoint.x + height * (math.cos((point.midAngle)! * math.pi / 180)), - startPoint.y + height * (math.sin((point.midAngle)! * math.pi / 180))); - } else if (point.midAngle! > 0 && point.midAngle! < 90) { - increasedLocation = ChartLocation( - startPoint.x - - height * (math.cos((point.midAngle! - 90) * math.pi / 180)), - startPoint.y + - height * (math.sin((point.midAngle! - 90) * math.pi / 180))); - } else { - increasedLocation = ChartLocation( - startPoint.x - - height * (math.cos((point.midAngle! - 180) * math.pi / 180)), - startPoint.y - - height * (math.sin((point.midAngle! - 180) * math.pi / 180))); - } - return increasedLocation; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/common/common.dart deleted file mode 100644 index ab4449237..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/common.dart +++ /dev/null @@ -1,1091 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import '../chart/utils/enum.dart'; -import 'utils/enum.dart'; -import 'utils/helper.dart'; -import 'utils/typedef.dart'; - -/// Represents the chart container. -class ChartContainer extends SingleChildRenderObjectWidget { - /// Creates an instance for chart container. - const ChartContainer({required Widget child}) : super(child: child); - - @override - RenderObject createRenderObject(BuildContext context) { - return _ChartContainerBox(); - } -} - -class _ChartContainerBox extends RenderShiftedBox { - _ChartContainerBox() : super(null); - double minHeight = 300; - double minWidth = 300; - - @override - void performLayout() { - double height = constraints.maxHeight; - double width = constraints.maxWidth; - if (height == double.infinity) { - height = minHeight; - } - if (width == double.infinity) { - width = minWidth; - } - child!.layout( - BoxConstraints( - minHeight: 0.0, - maxHeight: height, - minWidth: 0.0, - maxWidth: width, - ), - parentUsesSize: - false); // True- Parent widget recomputes again respect to every build of child widget, False- Parent widget not rebuild respect to child widget build - size = Size(width, - height); // constraints.maxHeight become infinity when widget is placed inside row/column - } - - @override - // ignore: unnecessary_overrides - void paint(PaintingContext context, Offset offset) { - super.paint(context, offset); - } -} - -/// It has the properties of the chart title. -/// -/// ChartTitle can define and customize the Chart title using title property of SfCartesianChart. -/// The text property of ChartTitle is used to set the text for the title. -/// -/// It provides an option of text, text style, alignment border color and width to customize the appearance. -/// -class ChartTitle { - /// Creating an argument constructor of ChartTitle class. - ChartTitle( - {this.text = '', - TextStyle? textStyle, - this.alignment = ChartAlignment.center, - this.borderColor = Colors.transparent, - this.borderWidth = 0, - this.backgroundColor}) - : textStyle = getTextStyle( - textStyle: textStyle, - fontSize: 15.0, - fontFamily: 'Segoe UI', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal); - - /// Text to be displayed as chart title. Any desired text can be set as chart title. - /// If the width of the chart title exceeds the width of the chart, then the title will - /// be wrapped to multiple rows. - /// - /// Defaults to `''`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// title: ChartTitle( - /// text: 'Chart Title' - /// ) - /// ); - /// } - ///``` - final String text; - - /// Customizes the appearance of the chart title text. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// title: ChartTitle( - /// text: 'Chart Title' - /// textStyle: TextStyle( - /// color: Colors.red, - /// fontSize: 12, - /// fontStyle: FontStyle.normal, - /// fontWeight: FontWeight.w400, - /// fontFamily: 'Roboto' - /// ) - /// ) - /// ); - /// } - ///``` - final TextStyle textStyle; - - /// Aligns the chart title. - /// - /// The alignment change is applicable only when the width of the - /// chart title is less than the width of the chart. - /// - /// * `ChartAlignment.near` places the chart title at the beginning of the chart - /// - /// * `ChartAlignment.far` moves the chart title to end of the chart - /// - /// * `ChartAlignment.center` places the title at the center position of the chart’s width. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// Also refer [ChartAlignment]. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// title: ChartTitle( - /// text: 'Chart Title', - /// alignment: ChartAlignment.near - /// ) - /// ); - /// } - ///``` - final ChartAlignment alignment; - - /// Background color of the chart title. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// title: ChartTitle( - /// text: 'Chart Title', - /// backgroundColor: Colors.white - /// ) - /// ); - /// } - ///``` - final Color? backgroundColor; - - /// Border color of the chart title. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// title: ChartTitle( - /// text: 'Chart Title', - /// borderColor: Colors.red, - /// borderWidth: 4 - /// ) - /// ); - /// } - ///``` - final Color borderColor; - - /// Border width of the chart title. - /// - /// Defaults to `0`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// title: ChartTitle( - /// text: 'Chart Title', - /// borderColor: Colors.red, - /// borderWidth: 4 - /// ) - /// ); - /// } - ///``` - final double borderWidth; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ChartTitle && - other.textStyle == textStyle && - other.alignment == alignment && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.backgroundColor == backgroundColor; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - text, - textStyle, - alignment, - borderColor, - borderWidth, - backgroundColor - ]; - return hashList(values); - } -} - -/// Identify the series in chart. -/// -/// Legend contains list of chart series/data points in chart. It helps to -/// identify the corresponding data series in chart. The name property of -/// [SfCartesianChart] is used to define the label for the corresponding series -/// legend item and for [SfCircularChart] type chart by default values mapped with -/// xValueMapper will be displayed. -/// -/// Provides options such as isVisible, borderWidth, alignment, opacity, borderColor, -/// padding and so on to customize the appearance of the legend. -/// -@immutable -class Legend { - /// Creating an argument constructor of Legend class. - Legend( - {bool? isVisible, - LegendPosition? position, - ChartAlignment? alignment, - this.backgroundColor, - 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, - this.legendItemBuilder, - Color? iconBorderColor, - double? iconBorderWidth, - double? itemPadding, - this.offset, - this.image}) - : isVisible = isVisible ?? false, - position = position ?? LegendPosition.auto, - alignment = alignment ?? ChartAlignment.center, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - iconBorderColor = iconBorderColor ?? Colors.transparent, - iconBorderWidth = iconBorderWidth ?? 0.0, - opacity = opacity ?? 1.0, - padding = padding ?? 10.0, - textStyle = getTextStyle( - textStyle: textStyle, - fontSize: 13.0, - fontStyle: FontStyle.normal, - fontFamily: 'Segoe UI'), - iconHeight = iconHeight ?? 12.0, - iconWidth = iconWidth ?? 12.0, - toggleSeriesVisibility = toggleSeriesVisibility ?? true, - isResponsive = isResponsive ?? false, - orientation = orientation ?? LegendItemOrientation.auto, - overflowMode = overflowMode ?? LegendItemOverflowMode.scroll, - itemPadding = itemPadding ?? 15.0, - title = title ?? LegendTitle(); - - /// Toggles the visibility of the legend. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true - /// ) - /// ); - /// } - ///``` - final bool? isVisible; - - /// Position of the legend. - /// - /// If the chart width is greater than chart height, then the - /// legend will be placed at the right, else it will be placed - /// at the bottom of the chart.The available options are auto, - /// bottom, left, right, and top. - /// - /// Defaults to `LegendPosition.auto`. - /// - /// Also refer [LegendPosition]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// position: LegendPosition.bottom - /// ) - /// ); - /// } - ///``` - final LegendPosition position; - - /// Alignment of the legend. - /// - /// Alignment will work if the legend width is greater than - /// the total legend item's width. - /// - /// Defaults to `ChartAlignment.center`. - /// - ///Also refer [ChartAlignment]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// alignment: ChartAlignment.near - /// ) - /// ); - /// } - ///``` - final ChartAlignment alignment; - - /// Background color of the legend. - /// - /// Used to change the background color of legend shape. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// backgroundColor: Colors.grey - /// ) - /// ); - /// } - ///``` - final Color? backgroundColor; - - /// Border color of the legend. - /// - /// Used to change the stroke color of the legend shape. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ) - /// ); - /// } - ///``` - final Color borderColor; - - /// Border width of the legend. - /// - /// Used to change the stroke width of the legend shape. - /// - /// Defaults to `0.0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ) - /// ); - /// } - ///``` - final double borderWidth; - - /// Border color of the icon in the legend items. - /// - /// Used to change the stroke color of the legend icon shape. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// iconBorderColor: Colors.yellow, - /// iconBorderWidth: 4 - /// ) - /// ); - /// } - ///``` - final Color iconBorderColor; - - /// Border width of the icon in the legend items. - /// - /// Used to change the stroke width of the legend icon shape. - /// - /// Defaults to `0.0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// iconBorderColor: Colors.yellow, - /// iconBorderWidth: 4 - /// ) - /// ); - /// } - ///``` - final double iconBorderWidth; - - /// Opacity of the legend. - /// - /// Used to control the transparency of the legend icon shape. - /// The value ranges from 0 to 1. - /// - /// Defaults to `1.0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// opacity: 0.5 - /// ) - /// ); - /// } - ///``` - final double opacity; - - /// The height of the legend. - /// - /// It takes percentage value from the overall chart height. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// height: '30%' - /// ) - /// ); - /// } - ///``` - final String? height; - - /// The width of the legend. - /// - /// It takes percentage value from the overall chart width. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// width: '30%' - /// ) - /// ); - /// } - ///``` - final String? width; - - /// Padding between the legend items. - /// - /// Used to add padding between the icon shape and the text. - /// - /// Defaults to `5.0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// padding: 4.0 - /// ) - /// ); - /// } - ///``` - final double padding; - - /// Height of the icon in legend item. - /// - /// Used to change the height of the icon shape. - /// - /// Defaults to `12`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// iconHeight: 14 - /// ) - /// ); - /// } - ///``` - final double iconHeight; - - /// Width of the icon in legend item. - /// - /// Used to change the width of the icon shape. - /// - /// Defaults to `12`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// iconWidth: 14 - /// ) - /// ); - /// } - ///``` - final double iconWidth; - - /// Toggles the series visibility. - /// - /// If it is set to false, then on tapping the legend item, - /// series visibility will not be toggled. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// toggleSeriesVisibility: false - /// ) - /// ); - /// } - ///``` - final bool toggleSeriesVisibility; - - /// Customizes the legend item text. - /// - /// Used to change the text color, size, font family, font style, etc. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// textStyle: TextStyle(color: Colors.red) - /// ) - /// ); - /// } - ///``` - final TextStyle textStyle; - - /// Toggles the visibility of the legend. - /// - /// If the width or height of the legend is greater than the plot area bounds. - /// - /// Defaults to `false`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// isResponsive: true - /// ) - /// ); - /// } - ///``` - final bool isResponsive; - - /// Orientation of the legend. - /// - /// The legend items will be placed either in horizontal or - /// in vertical orientation. By default, it is set to auto, i.e. if the legend position - /// is top or bottom, orientation is set - /// to horizontal, else it is set to vertical. - /// - /// Defaults to `LegendItemOrientation.auto`. - /// - /// Also refer [LegendItemOrientation]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// orientation: LegendItemOrientation.vertical - /// ) - /// ); - /// } - ///``` - final LegendItemOrientation orientation; - - /// Customizes the legend title. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// title: ChartTitle( - /// text: 'Countries' - /// ) - /// ) - /// ); - /// } - ///``` - final LegendTitle title; - - /// Widget builder for legend items. - /// - /// Customize the appearance of legend items in - /// template by using `legendItemBuilder` property of legend. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// legendItemBuilder: (String name, dynamic series,dynamic point, int index) { - /// return Container( - /// height: 30, - /// width: 80, - /// child: Row( - /// children: [ - /// Container(child: Image.asset('images/bike.png')), - /// Container(child: Text(index.toString())), - /// ] - /// ) - /// ); - /// } - /// ) - /// ); - /// } - ///``` - final LegendItemBuilder? legendItemBuilder; - - /// Overflow legend items. - /// - /// The legend items can be placed in multiple rows or scroll can be enabled - /// using the overflowMode property if size of the total legend items exceeds the available size. - /// It can be scrolled, wrapped, or left. - /// - /// Defaults to `LegendItemOverflowMode.scroll`. - /// - /// Also refer [LegendItemOverflowMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// overflowMode: LegendOverflowMode.wrap - /// ) - /// ); - /// } - ///``` - final LegendItemOverflowMode overflowMode; - - /// Padding of the legend items. - /// - /// Used to add padding between the first legend text and the second legend icon shape. - /// - /// Defaults to `10.0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// itemPadding: 5 - /// ) - /// ); - /// } - ///``` - final double itemPadding; - - /// Places the legend in custom position. - /// - /// If the [offset] has been set, the legend is moved from its actual position. - /// For example, if the [position] is `top`, then the legend will be placed in the top - /// but in the position added to the actual top position. - /// - /// Also, the legend will not take a dedicated position for it and will be drawn - /// on the top of the chart's plot area. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// offset: Offset(20,40) - /// ) - /// ); - /// } - ///``` - final Offset? offset; - - /// Used to add image to the legend icon. - /// - /// Default image size is `10.0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// image: const AssetImage('images/bike.png') - /// ) - /// ); - /// } - ///``` - final ImageProvider? image; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is Legend && - other.isVisible == isVisible && - other.position == position && - other.alignment == alignment && - other.backgroundColor == backgroundColor && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.opacity == opacity && - other.height == height && - other.width == width && - other.padding == padding && - other.iconHeight == iconHeight && - other.iconWidth == iconWidth && - other.toggleSeriesVisibility == toggleSeriesVisibility && - other.textStyle == textStyle && - other.isResponsive == isResponsive && - other.orientation == orientation && - other.title == title && - other.overflowMode == overflowMode && - other.legendItemBuilder == legendItemBuilder && - other.iconBorderColor == iconBorderColor && - other.iconBorderWidth == iconBorderWidth && - other.itemPadding == itemPadding && - other.image == image; - } - - @override - int get hashCode { - final List values = [ - isVisible, - position, - alignment, - backgroundColor, - borderColor, - borderWidth, - opacity, - height, - width, - padding, - iconHeight, - iconWidth, - toggleSeriesVisibility, - textStyle, - isResponsive, - orientation, - title, - overflowMode, - legendItemBuilder, - iconBorderColor, - iconBorderWidth, - itemPadding, - image - ]; - return hashList(values); - } -} - -/// Legend renderer class for mutable fields and methods. -class LegendRenderer { - /// Creates an argument constructor for Legend renderer class. - LegendRenderer(this.legend); - - /// Holds the legend value. - final Legend? legend; - - /// Specifies the legend position value. - late LegendPosition legendPosition; - - /// Specifies the value of legend item orientation. - late LegendItemOrientation orientation; -} - -/// Represents the class of measure widget context. -class MeasureWidgetContext { - /// Creates an instance of measure widget context. - MeasureWidgetContext( - {this.context, this.key, this.widget, this.seriesIndex, this.pointIndex}); - - /// Specifies the context value. - BuildContext? context; - - /// Holds the series index value. - int? seriesIndex; - - /// Holds the point index value. - int? pointIndex; - - /// Holds the value of key. - Key? key; - - /// Holds the value of size. - Size? size; - - /// Specifies the widget value. - Widget? widget; - - /// Specifies whether to render the legend. - bool isRender = false; -} - -/// Customizes the legend title. -/// -/// Takes a string and display the legend title.By default,the legend title horizontally center -/// aligned to the chart's width and it will placed at the bottom of the chart. -/// -/// Provides Options to customize the [text], [textStyle] and [alignment] properties. -@immutable -class LegendTitle { - /// Creating an argument constructor of LegendTitle class. - LegendTitle({this.text, TextStyle? textStyle, ChartAlignment? alignment}) - : textStyle = getTextStyle( - textStyle: textStyle, - fontSize: 12.0, - fontStyle: FontStyle.normal, - fontFamily: 'Segoe UI'), - alignment = alignment ?? ChartAlignment.center; - - /// Legend title text. - /// - /// Used to change the text of the title. - /// - /// Defaults to `‘’`. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// title: ChartTitle( - /// text: 'Legend title' - /// ) - /// ) - /// ); - /// } - ///``` - final String? text; - - /// Customize the legend title text. - /// - /// Used to change the text color, size, font family, fontStyle, and font weight - /// for the legend title. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// title: ChartTitle( - /// text: 'Legend title', - /// textStyle: TextStyle(color: Colors.red) - /// ) - /// ) - /// ); - /// } - ///``` - final TextStyle textStyle; - - /// Alignment of the legend title. - /// - /// Used to change the alignment of the title text. - /// - /// It can be `ChartAlignment.near`,`ChartAlignment.center`, or - /// `ChartAlignment.far`. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// Also refer [ChartAlignment] - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend( - /// isVisible: true, - /// title: ChartTitle( - /// text: 'Legend title', - /// alignment: ChartAlignment.near - /// ) - /// ) - /// ); - /// } - ///``` - final ChartAlignment alignment; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is LegendTitle && - other.textStyle == textStyle && - other.alignment == alignment && - other.text == text; - } - - @override - int get hashCode { - final List values = [text, textStyle, alignment]; - return hashList(values); - } -} - -/// Handling empty points in charts -/// -/// Data points with a null value are considered empty points. Empty data points are ignored and are not plotted in the chart. -/// By using the emptyPointSettings property in series, you can decide on the action taken for empty points. -/// Disponible modes are gap, zero, drop, and average. -/// -/// Defaults to `EmptyPointMode.gap`. -/// -/// _Note:_ This is common for Cartesian, circular, pyramid and funnel charts. -class EmptyPointSettings { - /// Creating an argument constructor of EmptyPointSettings class. - EmptyPointSettings( - {this.color = Colors.grey, - this.mode = EmptyPointMode.gap, - this.borderColor = Colors.transparent, - this.borderWidth = 0}); - - /// Color of the empty data point. - /// - /// Defaults to `Colors.grey`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// emptyPointSettings: EmptyPointSettings( - /// color: Colors.black, - /// mode: EmptyPointMode.average - /// ) - /// ), - /// ], - /// ); - /// } - /// ``` - final Color color; - - /// Border color of the empty data point. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// emptyPointSettings: EmptyPointSettings( - /// borderColor: Colors.black, - /// borderWidth: 2, - /// mode:EmptyPointMode.average - /// ) - /// ), - /// ], - /// ); - /// } - /// ``` - final Color borderColor; - - /// Border width of the empty data point. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// emptyPointSettings: EmptyPointSettings( - /// borderColor: Colors.black, - /// borderWidth: 2, - /// mode:EmptyPointMode.average - /// ) - /// ), - /// ], - /// ); - /// } - /// ``` - final double borderWidth; - - /// By default, gap will be generated for empty points, i.e. data points with null value. - /// - /// The empty points display the values that can be considered as zero, average, or gap. - /// - /// Defaults to `EmptyPointMode.gap`. - /// - /// Also refer [EmptyPointMode]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// emptyPointSettings: EmptyPointSettings( - /// mode:EmptyPointMode.average - /// ) - /// ), - /// ], - /// ); - /// } - /// ``` - final EmptyPointMode mode; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is EmptyPointSettings && - other.color == color && - other.mode == mode && - other.borderColor == borderColor && - other.borderWidth == borderWidth; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - color, - mode, - borderColor, - borderWidth - ]; - return hashList(values); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart deleted file mode 100644 index 658b2bbd0..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart +++ /dev/null @@ -1,788 +0,0 @@ -import 'package:flutter/material.dart'; -import '../chart/axis/axis.dart'; -import '../chart/chart_series/xy_data_series.dart'; -import '../chart/common/data_label.dart'; -import '../chart/common/interactive_tooltip.dart'; -import '../chart/technical_indicators/technical_indicator.dart'; -import '../chart/utils/enum.dart'; -import 'utils/enum.dart'; - -/// Holds the arguments for the event onTooltipRender. -/// -/// Event is triggered when the tooltip is rendered, which allows you to customize tooltip arguments. -class TooltipArgs { - /// Creating an argument constructor of TooltipArgs class. - TooltipArgs( - [this.seriesIndex, - this.dataPoints, - this.viewportPointIndex, - this.pointIndex]); - - /// Get and set the tooltip text. - String? text; - - /// Get and set the header text of the tooltip. - String? header; - - /// Get and set the x location of the tooltip. - double? locationX; - - /// Get and set the y location of the tooltip. - double? locationY; - - /// Get the index of the current series. - final dynamic seriesIndex; - - /// Get the list of data points in the series. - final List? dataPoints; - - /// Get the overall index value of the tooltip. - final num? pointIndex; - - /// Get the view port index value of the tooltip. - final num? viewportPointIndex; -} - -/// Holds the `onActualRangeChanged` event arguments. -/// -/// 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 { - /// Creating an argument constructor of ActualRangeChangedArgs class. - ActualRangeChangedArgs( - [this.axisName, - this.axis, - this.actualMin, - this.actualMax, - this.actualInterval, - this.orientation]); - - /// Get the name of the axis. - final String? axisName; - - /// Get the axis type. - final ChartAxis? axis; - - /// Get the actual minimum range of an axis. - final dynamic actualMin; - - /// Get the actual maximum range of an axis. - final dynamic actualMax; - - /// Get the actual interval of an axis. - final dynamic actualInterval; - - /// Get and set the minimum visible range for an axis. - dynamic visibleMin; - - /// Get and set the maximum visible range for an axis. - dynamic visibleMax; - - /// Get and set the interval for the visible range for an axis. - dynamic visibleInterval; - - /// Get the orientation of an axis. - final AxisOrientation? orientation; -} - -/// 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 event. -class AxisLabelRenderDetails { - /// Creating an argument constructor of AxisLabelRenderDetails class. - AxisLabelRenderDetails(this.value, this.text, this.textStyle, this.axis); - - /// Actual text value of the axis label. - final String text; - - /// Get the value of the axis label. - final num value; - - /// Get the chart axis type and its properties. - final ChartAxis axis; - - /// Get the text style of an axis label. - final TextStyle textStyle; -} - -/// Holds multi-level label text, name of the axis, index, actual level of the -/// multi-level label, text style such as color, font size, etc arguments for -/// multi-level label formatter callback. -/// -/// The value in the `index` will be obtained as per the order of the labels specified in -/// the `multiLevelLabels` property irrespective of the value specified in -/// the `level` property. -/// -/// The level obtained in the `actualLevel`property is the re-ordered level -/// irrespective of the value specified in the `level` property. -class MultiLevelLabelRenderDetails { - /// Creating an argument constructor of MultiLevelLabelRenderDetails class. - MultiLevelLabelRenderDetails( - this.actualLevel, this.text, this.textStyle, this.index, this.axisName); - - /// Get the multi-level label text. - final String text; - - /// Get the text style of the multi-level label. - final TextStyle textStyle; - - /// Get the index value of the multi-level label. - final int index; - - /// Get the actual level of the multi-level label. - final int actualLevel; - - /// Get the axis name. - final String? axisName; -} - -/// 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 -/// triggered and provides options to customize the data label text, data label text style, the background color. -/// -/// It has the public properties of data label text, series, data points, and point index. -class DataLabelRenderArgs { - /// Creating an argument constructor of DataLabelRenderArgs class. - DataLabelRenderArgs( - [this.seriesRenderer, - this.dataPoints, - this.viewportPointIndex, - this.pointIndex]); - - /// Get and set the text value of a data label. - late String text; - - /// Get and set the style property of the data label text. - TextStyle textStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12); - - /// Get the current series. - /// - /// ```dart - /// SfCartesianChart( - /// onDataLabelRender: (DataLabelRenderArgs args) { - /// CartesianSeries series = args.seriesRenderer; - /// //Changed the background color of the data label based on the series type - /// if (series.name == 'Product A') { - /// args.color = Colors.blue; - /// } else if(series.name == 'Product B'){ - /// args.color = Colors.red; - /// } - /// }, - /// ) - /// ``` - /// - /// _Note:_ Series type may vary based on the chart type. - /// - /// * Cartesian chart: CartesianSeries series; - /// * Circular chart: CircularSeries series; - /// * Funnel Chart: FunnelSeries series; - /// * Pyramid Chart: PyramidSeries series; - /// - final dynamic seriesRenderer; - - /// Get the data points of the series. - final dynamic dataPoints; - - /// Get the overall index value of a data label. - final int? pointIndex; - - /// Get and set the background color of a data label. - 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; - - /// Get the view port index value of a data label. - final int? viewportPointIndex; -} - -/// Holds the onLegendItemRender event arguments. -/// -/// The onLegendItemRender event triggers when the legend item is rendering and can customize the [text], [legendIconType], and [color]. -/// -/// _Note:_ [pointIndex] and [color] is applicable for [SfCircularChart], [SfPyramidChart] and [SfFunnelChart]. -class LegendRenderArgs { - /// Creating an argument constructor of LegendRenderArgs class. - LegendRenderArgs([this.seriesIndex, this.pointIndex]); - - /// Get and set the legend text. - String? text; - - /// Get and set the shape of a legend. - LegendIconType? legendIconType; - - /// Get the current series index. - final int? seriesIndex; - - /// Get the current point index. - final int? pointIndex; - - /// Get and set the color of the legend icon. - Color? color; -} - -/// Holds the onRenderDetailsUpdate callback arguments of trendline. -class TrendlineRenderParams { - /// Creating an argument constructor of TrendlineRenderParams class. - TrendlineRenderParams( - [this.intercept, - this.seriesIndex, - this.trendlineName, - this.seriesName, - this.calculatedDataPoints, - this.slope, - this.rSquaredValue]); - - /// Get the intercept value. - final double? intercept; - - /// Get the index of the series. - final int? seriesIndex; - - /// Gets the name of the trendline. - /// - /// If the user specifies a value for the `name` property in the series, - /// that value can be fetched here. If it is null, then the name generated - /// internally for the trendline can be fetched here. - final String? trendlineName; - - /// Gets the name of the series. - /// - /// If the user specifies a value for the `name` property in the series, - /// that value can be fetched here. If it is null, then the name generated - /// internally for the series can be fetched here. - final String? seriesName; - - /// Get the data points of the trendline. - final List? calculatedDataPoints; - - /// Gets the r-squared value. - final double? rSquaredValue; - - /// Gets the slope value. - final List? slope; -} - -/// Holds arguments for onTrackballPositionChanging event. -/// -/// The event is triggered when the trackball is rendered and provides options to customize the label text. -class TrackballArgs { - /// Get and set the trackball tooltip text. - ChartPointInfo chartPointInfo = ChartPointInfo(); -} - -/// Holds the onCrosshairPositionChanging event arguments. -/// -/// CrosshairRenderArgs is the type of Argument to the onCrosshairPositionChanging event, whenever the crosshair position is changed, the onCrosshairPositionChanging event is -/// triggered and provides options to customize the text, line color. -/// -/// It has the public properties of text, line color, axis, axis name, value, and orientation. -class CrosshairRenderArgs { - /// Creating an argument constructor of CrosshairRenderArgs class. - CrosshairRenderArgs([this.axis, this.value, this.axisName, this.orientation]); - - /// Get the type of chart axis and its properties. - final ChartAxis? axis; - - /// Get and set the crosshair text. - late String text; - - /// Get and set the color of the crosshair line. - late Color lineColor; - - /// Get the visible range value. - final dynamic value; - - /// Get the name of the axis. - final String? axisName; - - /// Get the axis orientation. - final AxisOrientation? orientation; -} - -/// Holds the chart TouchUp event arguments. -/// -/// ChartTouchInteractionArgs is used to store the touch point coordinates when the touch event is triggered. -/// Detects the points or areas in the chart as the offset values of x and y. -class ChartTouchInteractionArgs { - /// Get the position of the touch interaction. - late Offset position; -} - -/// Holds the zooming event arguments. -/// -/// The zooming events are onZooming, onZoomStart, onZoomEnd and onZoomReset. -/// It contains [axis], [currentZoomPosition], [currentZoomFactor], [previousZoomPosition] -/// and [previousZoomFactor] arguments. -/// -/// _Note:_ This is only applicable for [SfCartesianChart]. -class ZoomPanArgs { - /// Creating an argument constructor of ZoomPanArgs class. - ZoomPanArgs([this.axis, this.previousZoomPosition, this.previousZoomFactor]); - - /// Get the chart axis types and properties. - final ChartAxis? axis; - - /// Get and set the current zoom position. - late double currentZoomPosition; - - /// Get and set the current zoom factor. - late double currentZoomFactor; - - /// Get the previous zoom position. - final double? previousZoomPosition; - - /// Get the previous zoom factor. - final double? previousZoomFactor; -} - -/// Holds the arguments of `onPointTap`, `onPointDoubleTap` and `onPointLongPress` callbacks. -/// -/// The user can fetch the series index, point index, view port point index and data of the current point. -class ChartPointDetails { - /// Creating an argument constructor of ChartPointDetails class. - ChartPointDetails( - [this.seriesIndex, - this.viewportPointIndex, - this.dataPoints, - this.pointIndex]); - - /// Get the series index. - final int? seriesIndex; - - /// Get the overall index value. - final int? pointIndex; - - /// Get the list of data points. - final List? dataPoints; - - /// Get the view port index value. - final num? viewportPointIndex; -} - -/// Holds the onAxisLabelTapped event arguments. -/// -/// This is the argument type of the onAxisLabelTapped event. Whenever the axis label is tapped, the onAxisLabelTapped event is triggered and provides options to get the axis type, label text, and axis name. -/// -class AxisLabelTapArgs { - /// Creating an argument constructor of AxisLabelTapArgs class. - AxisLabelTapArgs([this.axis, this.axisName]); - - /// Get the type of chart axis and its properties. - final ChartAxis? axis; - - /// Get the text of the axis label at the tapped position. - late String text; - - /// Get the value holds the properties of the visible label. - late num value; - - /// Get the axis name. - 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]. -/// -class LegendTapArgs { - /// Creating an argument constructor of LegendTapArgs class. - LegendTapArgs([this.series, this.seriesIndex, this.pointIndex]); - - /// Get the current series. - /// - /// ```dart - /// SfCartesianChart( - /// onDataLabelRender: (DataLabelRenderArgs args) { - /// CartesianSeries series = args.series; - /// //Changed the background color of the data label based on the series type - /// if (series.name == 'Product A') { - /// args.color = Colors.blue; - /// } else if(series.name == 'Product B'){ - /// args.color = Colors.red; - /// } - /// }, - /// ) - /// ``` - /// - /// _Note_: Series type may vary based on the chart type. - /// - /// * Cartesian chart: CartesianSeries series; - /// * Circular chart: CircularSeries series; - /// * Funnel Chart: FunnelSeries series; - /// * Pyramid Chart: PyramidSeries series; - /// - final dynamic series; - - /// Get the current series index. - final int? seriesIndex; - - /// Get the current point index. - final int? pointIndex; -} - -/// Holds the onSelectionChanged event arguments. -/// -/// Here [selectedColor], [unselectedColor], [selectedBorderColor], [selectedBorderWidth], [unselectedBorderColor] and [unselectedBorderWidth] can be customized. -/// -class SelectionArgs { - /// Creating an argument constructor of SelectionArgs class. - SelectionArgs( - {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; - - /// Get and set the color of unselected series or data points. - Color? unselectedColor; - - /// Get and set the border color of the selected series or data points. - Color? selectedBorderColor; - - /// Get and set the border width of the selected series or data points. - double? selectedBorderWidth; - - /// Get and set the border color of the unselected series or data points. - Color? unselectedBorderColor; - - /// Get and set the border width of the unselected series or data points. - double? unselectedBorderWidth; - - /// Get the series index. - final int seriesIndex; - - /// Get the overall index value of the selected data points. - final int pointIndex; - - /// Get the view port index value of the selected data points. - final int viewportPointIndex; -} - -@Deprecated('Use IndicatorRenderParams instead.') - -/// Holds the onRenderDetailsUpdate event arguments. -/// -/// Triggers when indicator is rendering. You can customize the [signalLineColor], [signalLineWidth], and [signalLineDashArray]. -/// -/// _Note:_ This is only applicable for [SfCartesianChart]. -class IndicatorRenderArgs { - /// Creating an argument constructor of IndicatorRenderArgs class. - @Deprecated('Use IndicatorRenderParams instead.') - IndicatorRenderArgs([ - this.indicator, - this.index, - this.seriesName, - this.dataPoints, - ]); - - /// Get the technical indicator information. - final TechnicalIndicators? indicator; - - /// Get the indicator name. - late String indicatorName; - - /// Get the current index of the technical indicator. - final int? index; - - /// Get and set the color of the signal line. - late Color signalLineColor; - - /// Get and set the width of the signal line. - late double signalLineWidth; - - /// Get and set the dash array size. - late List lineDashArray; - - /// Get the series name. - final String? seriesName; - - /// Get the current data points. - final List? dataPoints; -} - -/// Holds the onMarkerRender event arguments. -/// -/// MarkerRenderArgs is the argument type of onMarkerRender event. Whenever the onMarkerRender is triggered, the shape of the marker, color, marker width, height, border color, and border width can be customized. -/// -/// Has the public properties of point index, series index, shape, marker width, and height. -class MarkerRenderArgs { - /// Creating an argument constructor of MarkerRenderArgs class. - MarkerRenderArgs( - [this.viewportPointIndex, this.seriesIndex, this.pointIndex]); - - /// Get the overall index value of the marker. - final int? pointIndex; - - /// Get the series index of the marker. - final int? seriesIndex; - - /// Get and set the shape of the marker. - late DataMarkerType shape; - - /// Get and set the width of the marker. - late double markerWidth; - - /// Get and set the height of the marker. - late double markerHeight; - - /// Get and set the color of the marker. - Color? color; - - /// Get and set the border color of the marker. - Color? borderColor; - - /// Get and set the border width of marker. - late double borderWidth; - - /// Get the view port index value of the marker. - 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, -/// series index, point index and its text. - -class DataLabelTapDetails { - /// Creating an argument constructor of DataLabelTapDetails class. - DataLabelTapDetails(this.seriesIndex, this.viewportPointIndex, this.text, - this.dataLabelSettings, this.pointIndex); - - /// Get the position of the tapped data label in logical pixels. - late Offset position; - - /// Get the series index of the tapped data label. - final int seriesIndex; - - /// Get the overall index value of the tapped data label. - final int pointIndex; - - /// Get the text of the tapped data label. - final String text; - - /// Get the data label customization options specified in that particular series. - final DataLabelSettings dataLabelSettings; - - /// Get the view port 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; -} - -/// Holds the onCreateShader callback arguments. -class ShaderDetails { - /// Creating an argument constructor of ShaderDetails class. - ShaderDetails(this.rect, this.renderType); - - /// Holds the chart area rect. - final Rect rect; - - ///Conveys whether the current rendering element is 'series' or 'legend'. - final String renderType; -} - -/// Holds the onRenderDetailsUpdate callback arguments. -class IndicatorRenderParams { - /// Creating an argument constructor of IndicatorRenderParams class. - IndicatorRenderParams(this.calculatedDataPoints, this.name, - this.signalLineWidth, this.signalLineColor, this.signalLineDashArray); - - /// Gets the calculated indicator data points details. - final List>? calculatedDataPoints; - - /// Gets the width of the signal line. - late double signalLineWidth; - - /// Gets the color of the signal line. - late Color signalLineColor; - - /// Gets the name of the indicator. - /// - /// If the user specifies a value for the `name` property in the `TechnicalIndicators` class, - /// that value can be fetched here. If it is null, then the name generated internally for the - /// indicator can be fetched here. - final String name; - - /// Gets the dash array of the signal line. - late List signalLineDashArray; -} - -/// Holds the onRenderDetailsUpdate callback arguments. -class BollingerBandIndicatorRenderParams extends IndicatorRenderParams { - /// Creating an argument constructor of BollingerBandIndicatorRenderParams class. - BollingerBandIndicatorRenderParams( - this.upperLineValues, - this.lowerLineValues, - List>? calculatedDataPoints, - String name, - double signalLineWidth, - Color signalLineColor, - List signalLineDashArray) - : super(calculatedDataPoints, name, signalLineWidth, signalLineColor, - signalLineDashArray); - - /// Gets the calculated upper line values of the Bollinger band indicator. - final List>? upperLineValues; - - /// Gets the calculated lower line values of the Bollinger band indicator. - final List>? lowerLineValues; -} - -/// Holds the onRenderDetailsUpdate callback arguments. -class MomentumIndicatorRenderParams extends IndicatorRenderParams { - /// Creating an argument constructor of MomentumIndicatorRenderParams class. - MomentumIndicatorRenderParams( - this.centerLineValue, - List>? calculatedDataPoints, - String name, - double signalLineWidth, - Color signalLineColor, - List signalLineDashArray) - : super(calculatedDataPoints, name, signalLineWidth, signalLineColor, - signalLineDashArray); - - /// Gets the calculated center line value of the Momentum indicator. - final double? centerLineValue; -} - -/// Holds the onRenderDetailsUpdate callback arguments. -class StochasticIndicatorRenderParams extends IndicatorRenderParams { - /// Creating an argument constructor of StochasticIndicatorRenderParams class. - StochasticIndicatorRenderParams( - this.periodLineValues, - List>? calculatedDataPoints, - String name, - double signalLineWidth, - Color signalLineColor, - List signalLineDashArray) - : super(calculatedDataPoints, name, signalLineWidth, signalLineColor, - signalLineDashArray); - - /// Gets the calculated period line values of the stochastic indicator. - final List>? periodLineValues; -} - -/// Holds the onRenderDetailsUpdate callback arguments. -class MacdIndicatorRenderParams extends IndicatorRenderParams { - /// Creating an argument constructor of MacdIndicatorRenderParams class. - MacdIndicatorRenderParams( - this.macdLineValues, - this.macdHistogramValues, - List>? calculatedDataPoints, - String name, - double signalLineWidth, - Color signalLineColor, - List signalLineDashArray) - : super(calculatedDataPoints, name, signalLineWidth, signalLineColor, - signalLineDashArray); - - /// Gets the calculated Macd line values of the Macd indicator. - final List>? macdLineValues; - - /// Gets the calculated histogram values of the Macd indicator. - final List>? macdHistogramValues; -} - -/// Holds the TechnicalIndicatorRenderDetails values -class TechnicalIndicatorRenderDetails { - /// Creating an argument constructor of TechnicalIndicatorRenderDetails class. - TechnicalIndicatorRenderDetails( - this.signalLineColor, this.signalLineWidth, this.signalLineDashArray); - - /// Color of the signal line. - final Color? signalLineColor; - - /// Width of the signal line. - final double? signalLineWidth; - - /// Dash array of the signal line - final List? signalLineDashArray; -} - -/// Holds the ErrorBarRenderDetails values. -class ErrorBarRenderDetails { - /// Creating an argument constructor of ErrorBarRenderDetails class. - ErrorBarRenderDetails( - this.pointIndex, this.viewPortPointIndex, this.calculatedErrorBarValues); - - /// Specifies the overall point index. - final int? pointIndex; - - /// Specifies the current point index. - final int? viewPortPointIndex; - - /// Specifies the current data point's error values. - final ErrorBarValues? calculatedErrorBarValues; -} - -/// Holds the error values of data point. -class ErrorBarValues { - /// Creating an argument constructor of ErrorBarValues class. - ErrorBarValues( - this.horizontalPositiveErrorValue, - this.horizontalNegativeErrorValue, - this.verticalPositiveErrorValue, - this.verticalNegativeErrorValue, - ); - - /// Holds the positive error value in horizontal point. - final double? horizontalPositiveErrorValue; - - /// Holds the negative error value in horizontal point. - final double? horizontalNegativeErrorValue; - - /// Holds the positive error value in vertical point. - final double? verticalPositiveErrorValue; - - /// Holds the negative error value in vertical point. - final double? verticalNegativeErrorValue; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart deleted file mode 100644 index b57c08cc4..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/widgets.dart'; - -/// Provides hand cursor support for mouse pointer. -class HandCursor extends MouseRegion { - /// Creating an argument constructor of class. - //ignore: prefer_const_constructors_in_immutables - HandCursor({required Widget child}) : super(child: child); -} - -/// Changes the style of the cursor while entering|leaving the hover zone. -void changeCursorStyleOnNavigation() {} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/shared.dart b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/shared.dart deleted file mode 100644 index 046d5df43..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/shared.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'shared.dart' - if (dart.library.html) 'web.dart' - if (dart.library.io) 'mobile.dart'; diff --git a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart deleted file mode 100644 index ffe63a2e1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart +++ /dev/null @@ -1,37 +0,0 @@ -// ignore: avoid_web_libraries_in_flutter -import 'dart:html' as html; -import 'package:flutter/widgets.dart'; - -/// Provides hand cursor support for mouse pointer. -class HandCursor extends MouseRegion { - /// Creating an argument constructor of HandCursor class. - //ignore: prefer_const_constructors_in_immutables - HandCursor({required Widget child}) - : super( - child: child, - onHover: mouseHover, - onExit: mouseExit, - ); - - static final html.Element? _appContainer = - html.window.document.getElementById('app-container'); - - /// Method called when the mouse is hovered. - static void mouseHover(PointerEvent event) { - if (_appContainer != null) { - _appContainer!.style.cursor = 'pointer'; - } - } - - /// Method is called when the mouse point exit. - static void mouseExit(PointerEvent event) { - if (_appContainer != null) { - _appContainer!.style.cursor = 'default'; - } - } -} - -/// Sets the cursor style when leaving the hover zone. -void changeCursorStyleOnNavigation() { - 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 deleted file mode 100644 index 99cb69320..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart +++ /dev/null @@ -1,916 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/circular_chart/renderer/chart_point.dart'; -import 'package:syncfusion_flutter_charts/src/circular_chart/renderer/circular_series.dart'; -import 'package:syncfusion_flutter_charts/src/circular_chart/renderer/renderer_extension.dart'; -import 'package:syncfusion_flutter_charts/src/common/utils/typedef.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/legend_internal.dart' - hide LegendPosition; - -import '../../chart/base/chart_base.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/common/cartesian_state_properties.dart'; -import '../../chart/technical_indicators/technical_indicator.dart'; -import '../../chart/trendlines/trendlines.dart'; -import '../../chart/utils/helper.dart'; -import '../common.dart'; -import '../event_args.dart'; -import '../rendering_details.dart'; -import '../state_properties.dart'; -import '../utils/enum.dart'; -import 'renderer.dart'; - -/// Represents the chart legend class. -class ChartLegend { - /// Creates an instance of chart legend. - ChartLegend(this.stateProperties); - - /// Specifies the value of state properties. - final StateProperties stateProperties; - - /// Holds the chart. - dynamic get chart => stateProperties.chart; - - /// Specifies the value of legend. - Legend? legend; - - /// Specifies the list of legend renderer context. - List? legendCollections; - - /// Specifies the list of legend items for SfLegend widget. - late List legendItems; - - /// Specifies the value of row count. - late int rowCount; - - /// Specifies the value of column count. - late int columnCount; - - /// Specifies the legend size value. - Size legendSize = Size.zero; - - /// Specifies the value of chart size. - Size chartSize = Size.zero; - - /// Specifies whether to render the legend. - bool shouldRenderLegend = false; - - /// Specifies whether the legend is scrollable. - late bool isNeedScrollable; - - /// Specifies the legend's title height value. - double titleHeight = 0.0; - - /// Specifies the list of toggled legend indices for SfLegend. - List toggledIndices = []; - - /// Specifies the sum of points for circular chart types. - num sumOfPoints = 0; - - /// Specifies the toggled item color for Sflegend. - Color toggledItemColor = const Color.fromRGBO(211, 211, 211, 1); - - /// To calculate legend bounds. - void calculateLegendBounds(Size size) { - legend = chart.legend; - final LegendRenderer legendRenderer = - stateProperties.renderingDetails.legendRenderer; - final List legendWidgetContext = - stateProperties.renderingDetails.legendWidgetContext; - final ChartLegend chartLegend = - stateProperties.renderingDetails.chartLegend; - shouldRenderLegend = false; - assert( - !(legend != null && legend!.width != null) || - !legend!.width!.contains(RegExp(r'[a-z]')) && - !legend!.width!.contains(RegExp(r'[A-Z]')), - 'Legend width must be number or percentage value, it should not contain any alphabets in the string.'); - assert( - !(legend != null && legend!.height != null) || - !legend!.height!.contains(RegExp(r'[a-z]')) && - !legend!.height!.contains(RegExp(r'[A-Z]')), - 'Legend height must be number or percentage value, it should not contain any alphabets in the string.'); - if (legend != null && legend!.isVisible!) { - legendCollections = []; - legendItems = []; - _calculateSeriesLegends(); - // ignore: unnecessary_null_comparison - assert(!(legend!.itemPadding != null) || legend!.itemPadding >= 0, - 'The padding between the legend and chart area should not be less than 0.'); - if (legendCollections!.isNotEmpty || legendWidgetContext.isNotEmpty) { - num legendHeight = 0, - legendWidth = 0, - titleHeight = 0, - textHeight = 0, - textWidth = 0, - maxTextHeight = 0, - maxTextWidth = 0, - maxLegendWidth = 0, - maxLegendHeight = 0, - currentWidth = 0, - currentHeight = 0; - num? maxRenderWidth, maxRenderHeight; - Size titleSize; - const num titleSpace = 10; - final num padding = legend!.itemPadding; - chartLegend.isNeedScrollable = false; - final bool isBottomOrTop = - legendRenderer.legendPosition == LegendPosition.bottom || - legendRenderer.legendPosition == LegendPosition.top; - legendRenderer.orientation = - (legend!.orientation == LegendItemOrientation.auto) - ? (isBottomOrTop - ? LegendItemOrientation.horizontal - : LegendItemOrientation.vertical) - : legend!.orientation; - - 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) - : isBottomOrTop - ? size.width - : percentageToValue('30%', size.width); - // To reduce the container width based on offset. - if (chartLegend.legend!.offset != null && - (chartLegend.legend!.offset?.dx.isNegative == false)) { - maxRenderWidth = maxRenderWidth! - - (chartLegend.legend!.offset?.dx as num) * 1.5 + - padding; - } - - 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 int length = - isTemplate ? legendWidgetContext.length : legendCollections!.length; - late MeasureWidgetContext legendContext; - late LegendRenderContext legendRenderContext; - String legendText; - Size textSize; - // ignore: unnecessary_null_comparison - assert(!(legend!.iconWidth != null) || legend!.iconWidth >= 0, - 'The icon width of legend should not be less than 0.'); - // ignore: unnecessary_null_comparison - assert(!(legend!.iconHeight != null) || legend!.iconHeight >= 0, - 'The icon height of legend should not be less than 0.'); - // ignore: unnecessary_null_comparison - assert(!(legend!.padding != null) || legend!.padding >= 0, - 'The padding between legend text and legend icon should not be less than 0.'); - for (int i = 0; i < length; i++) { - if (isTemplate) { - legendContext = legendWidgetContext[i]; - currentWidth = legendContext.size!.width + padding; - currentHeight = legendContext.size!.height + padding; - } else { - legendRenderContext = legendCollections![i]; - legendText = legendRenderContext.text; - 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.toDouble(), currentHeight.toDouble()); - } - if (i == 0) { - maxRenderWidth = legend!.width == null && !isBottomOrTop - ? max(maxRenderWidth!, currentWidth) - : maxRenderWidth; - maxRenderHeight = (titleHeight - - (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!) { - legendWidth = currentWidth; - if (legendHeight + currentHeight > maxRenderHeight!) { - chartLegend.isNeedScrollable = true; - } else { - legendHeight = legendHeight + currentHeight; - } - maxTextHeight = textHeight; - } else { - legendWidth += currentWidth; - legendHeight = max(legendHeight, currentHeight); - } - } else if (legend!.overflowMode == LegendItemOverflowMode.scroll || - legend!.overflowMode == LegendItemOverflowMode.none) { - if (maxLegendWidth + currentWidth <= maxRenderWidth!) { - legendWidth += currentWidth; - legendHeight = currentHeight > maxRenderHeight! - ? maxRenderHeight - : max(legendHeight, currentHeight); - needRender = true; - } else { - needRender = false; - } - } - } else { - if (legend!.overflowMode == LegendItemOverflowMode.wrap) { - if ((legendHeight + currentHeight) > maxRenderHeight!) { - legendHeight = currentHeight; - if (legendWidth + currentWidth > maxRenderWidth!) { - chartLegend.isNeedScrollable = true; - } else { - legendWidth = legendWidth + currentWidth; - } - } else { - legendHeight += currentHeight; - legendWidth = max(legendWidth, currentWidth); - } - } else if (legend!.overflowMode == LegendItemOverflowMode.scroll || - legend!.overflowMode == LegendItemOverflowMode.none) { - if (maxLegendHeight + currentHeight <= maxRenderHeight!) { - legendHeight += currentHeight; - legendWidth = currentWidth > maxRenderWidth! - ? maxRenderWidth - : max(legendWidth, currentWidth); - needRender = true; - } else { - needRender = false; - } - } - } - if (isTemplate) { - legendContext.isRender = needRender; - } else { - legendRenderContext.isRender = needRender; - } - maxLegendWidth = max(maxLegendWidth, legendWidth); - maxLegendHeight = max(maxLegendHeight, legendHeight); - } - legendSize = Size((maxLegendWidth + padding).toDouble(), - maxLegendHeight + titleHeight.toDouble()); - } - } - } - - /// To calculate legends in chart. - void _calculateLegends(SfCartesianChart chart, int index, - SeriesRendererDetails seriesRendererDetails, - [Trendline? trendline, int? trendlineIndex]) { - LegendRenderArgs? legendEventArgs; - bool isTrendlineadded = false; - TrendlineRenderer? trendlineRenderer; - final CartesianSeries series = - seriesRendererDetails.series; - final CartesianStateProperties stateProperties = - this.stateProperties as CartesianStateProperties; - final RenderingDetails _renderingDetails = stateProperties.renderingDetails; - final List palette = stateProperties.chart.palette; - if (trendline != null) { - isTrendlineadded = true; - trendlineRenderer = - seriesRendererDetails.trendlineRenderer[trendlineIndex!]; - } - seriesRendererDetails.seriesName = - seriesRendererDetails.seriesName ?? 'series $index'; - if (series.isVisibleInLegend && - (seriesRendererDetails.seriesName != null || - series.legendItemText != null)) { - if (chart.onLegendItemRender != null) { - legendEventArgs = LegendRenderArgs(index); - legendEventArgs.text = series.legendItemText ?? - (isTrendlineadded - ? trendlineRenderer!.name! - : seriesRendererDetails.seriesName!); - legendEventArgs.legendIconType = isTrendlineadded - ? trendline!.legendIconType - : series.legendIconType; - legendEventArgs.color = - isTrendlineadded ? trendline!.color : series.color; - chart.onLegendItemRender!(legendEventArgs); - } - final LegendRenderContext legendRenderContext = LegendRenderContext( - seriesRenderer: seriesRendererDetails, - trendline: trendline, - seriesIndex: index, - trendlineIndex: isTrendlineadded ? trendlineIndex : null, - isSelect: stateProperties.isTrendlineToggled == true - ? (!isTrendlineadded || !trendlineRenderer!.visible) - : !series.isVisible, - text: legendEventArgs?.text ?? - series.legendItemText ?? - (isTrendlineadded - ? trendlineRenderer!.name! - : seriesRendererDetails.seriesName!), - iconColor: legendEventArgs?.color ?? - (isTrendlineadded ? trendline!.color : series.color), - isTrendline: isTrendlineadded, - iconType: legendEventArgs?.legendIconType ?? - (isTrendlineadded - ? trendline!.legendIconType - : series.legendIconType)); - legendCollections!.add(legendRenderContext); - final Shader? cartesianShader = - _getCartesianSeriesGradientShader(legendRenderContext, chart.legend); - final LegendItem legendItem = LegendItem( - text: legendRenderContext.text, - color: cartesianShader == null - ? (legendRenderContext.iconColor ?? palette[legendRenderContext.seriesIndex % palette.length]) - .withOpacity(legend!.opacity) - : const Color.fromRGBO(211, 211, 211, 1), - shader: cartesianShader, - imageProvider: legendRenderContext.iconType == LegendIconType.image && - chart.legend.image != null - ? chart.legend.image - : (seriesRendererDetails.seriesType == 'scatter' && - seriesRendererDetails.series.markerSettings.shape == - DataMarkerType.image) - ? seriesRendererDetails.series.markerSettings.image - : null, - iconType: legendRenderContext.iconType == LegendIconType.seriesType - ? _getEffectiveLegendIconType( - legendRenderContext.iconType, - legendRenderContext, - legendRenderContext.seriesRenderer.seriesType) - : _getEffectiveLegendIconType(legendRenderContext.iconType), - iconStrokeWidth: - legendRenderContext.iconType == LegendIconType.seriesType - ? (legendRenderContext.seriesRenderer.seriesType == 'line' || - legendRenderContext.seriesRenderer.seriesType == - 'fastline' || - legendRenderContext.seriesRenderer.seriesType.contains('stackedline') == - true) - ? (chart.legend.iconBorderWidth > 0 - ? chart.legend.iconBorderWidth - : 3) - : ((legendRenderContext.seriesRenderer.seriesType == 'candle' || - legendRenderContext.seriesRenderer.seriesType == - 'boxandWhisker' || - legendRenderContext.seriesRenderer.seriesType.contains('hilo') == - true) - ? (chart.legend.iconBorderWidth > 0 - ? chart.legend.iconBorderWidth - : 2) - : (legendRenderContext.seriesRenderer.seriesType == 'spline' || - legendRenderContext.seriesRenderer.seriesType == - 'stepline') - ? (chart.legend.iconBorderWidth > 0 - ? chart.legend.iconBorderWidth - : 1) - : null) - : ((legendRenderContext.iconType == LegendIconType.horizontalLine || - legendRenderContext.iconType == LegendIconType.verticalLine) - ? (chart.legend.iconBorderWidth > 0 ? chart.legend.iconBorderWidth : 2) - : null), - overlayMarkerType: _getOverlayMarkerType(legendRenderContext)); - legendItems.add(legendItem); - if (seriesRendererDetails.visible! == false && - series.isVisibleInLegend && - (_renderingDetails.widgetNeedUpdate || - _renderingDetails.initialRender!) && - (seriesRendererDetails.oldSeries == null || - (!series.isVisible && - seriesRendererDetails.oldSeries!.isVisible == true))) { - legendRenderContext.isSelect = true; - if (stateProperties.renderingDetails.legendToggleStates - .contains(legendRenderContext) == - false) { - stateProperties.renderingDetails.legendToggleStates - .add(legendRenderContext); - } - } else if (_renderingDetails.widgetNeedUpdate && - (seriesRendererDetails.oldSeries != null && - (series.isVisible && - stateProperties.legendToggling == false && - seriesRendererDetails.visible! == true))) { - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - final String legendItemText = - SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderers[index]) - .series - .legendItemText ?? - series.name ?? - 'Series $index'; - final int seriesIndex = - visibleSeriesRenderers.indexOf(seriesRendererDetails.renderer); - final List legendToggle = [] - //ignore: prefer_spread_collections - ..addAll(stateProperties.renderingDetails.legendToggleStates); - for (final LegendRenderContext legendContext - in stateProperties.renderingDetails.legendToggleStates) { - if (seriesIndex == legendContext.seriesIndex && - legendContext.text == legendItemText) { - legendToggle.remove(legendContext); - } - } - stateProperties.renderingDetails.legendToggleStates = legendToggle; - } - } - } - - /// To calculate series legends. - void _calculateSeriesLegends() { - LegendRenderArgs? legendEventArgs; - if (chart.legend.legendItemBuilder == null) { - if (chart is SfCartesianChart) { - final CartesianStateProperties stateProperties = - this.stateProperties as CartesianStateProperties; - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (seriesRendererDetails.isIndicator == false) { - _calculateLegends(chart, i, seriesRendererDetails); - } - // ignore: unnecessary_type_check - if (seriesRendererDetails.renderer is CartesianSeriesRenderer) { - final SeriesRendererDetails xYseriesRendererDetails = - seriesRendererDetails; - // ignore: unnecessary_null_comparison - if (xYseriesRendererDetails.series != null && - xYseriesRendererDetails.series.trendlines != null) { - for (int j = 0; - j < xYseriesRendererDetails.series.trendlines!.length; - j++) { - final Trendline trendline = - xYseriesRendererDetails.series.trendlines![j]; - if (trendline.isVisibleInLegend) { - stateProperties.renderingDetails.chartLegend - ._calculateLegends( - chart, i, xYseriesRendererDetails, trendline, j); - } - } - } - } - } - if (chart.indicators.isNotEmpty == true) { - _calculateIndicatorLegends(); - } - } else { - final dynamic stateProperties = this.stateProperties; - if (stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty == - true) { - final dynamic seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { - final dynamic chartPoint = seriesRenderer.renderPoints[j]; - if (chart.onLegendItemRender != null) { - legendEventArgs = LegendRenderArgs(0, j); - legendEventArgs.text = chartPoint.x; - legendEventArgs.legendIconType = - seriesRenderer.series.legendIconType; - legendEventArgs.color = chartPoint.fill; - chart.onLegendItemRender(legendEventArgs); - } - - final LegendRenderContext legendRenderContext = LegendRenderContext( - seriesRenderer: seriesRenderer, - seriesIndex: j, - isSelect: false, - point: chartPoint, - text: legendEventArgs?.text ?? chartPoint.x, - iconColor: legendEventArgs?.color ?? chartPoint.fill, - iconType: legendEventArgs?.legendIconType ?? - seriesRenderer.series.legendIconType); - legendCollections!.add(legendRenderContext); - - double? degree; - double? pointStartAngle; - double? totalAngle; - double? pointEndAngle; - final bool isRadialBarSeries = legendRenderContext.iconType == - LegendIconType.seriesType && - legendRenderContext.seriesRenderer.seriesType == 'radialbar'; - if (isRadialBarSeries) { - pointStartAngle = -90; - totalAngle = 360; - _getSumOfPoints(seriesRenderer); - degree = - legendRenderContext.seriesRenderer.renderPoints[j].y.abs() / - (legendRenderContext.series.maximumValue ?? sumOfPoints); - degree = (degree! > 1 ? 1 : degree) * (totalAngle - 0.001); - pointEndAngle = pointStartAngle + degree; - } - - final Shader? circularShader = - _getCircularSeriesShader(legendRenderContext, chart.legend); - final LegendItem legendItem = LegendItem( - text: legendRenderContext.text, - shader: circularShader, - color: circularShader == null - ? (legendRenderContext.iconColor)! - .withOpacity(legend!.opacity) - : const Color.fromRGBO(211, 211, 211, 1), - imageProvider: - legendRenderContext.iconType == LegendIconType.image && - chart.legend.image != null - ? chart.legend.image - : null, - iconType: legendRenderContext.iconType == LegendIconType.seriesType - ? _getEffectiveLegendIconType( - legendRenderContext.iconType, - legendRenderContext, - legendRenderContext.seriesRenderer.seriesType) - : _getEffectiveLegendIconType( - legendRenderContext.iconType, legendRenderContext), - iconStrokeWidth: ((legendRenderContext.iconType == - LegendIconType.seriesType && - (legendRenderContext.seriesRenderer.seriesType == 'radialbar' || - legendRenderContext.seriesRenderer.seriesType == - 'doughnut')) || - legendRenderContext.iconType == LegendIconType.horizontalLine || - legendRenderContext.iconType == LegendIconType.verticalLine) - ? (chart.legend.iconBorderWidth > 0 == true ? chart.legend.iconBorderWidth : 1) - : null, - degree: degree, - startAngle: pointStartAngle, - endAngle: pointEndAngle); - legendItems.add(legendItem); - } - } - } - } - } - - /// To calculate indicator legends. - void _calculateIndicatorLegends() { - LegendRenderArgs? legendEventArgs; - final List textCollection = []; - TechnicalIndicatorsRenderer? technicalIndicatorsRenderer; - final CartesianStateProperties stateProperties = - this.stateProperties as CartesianStateProperties; - for (int i = 0; i < chart.indicators.length; i++) { - final TechnicalIndicators indicator = - chart.indicators[i]; - technicalIndicatorsRenderer = - stateProperties.technicalIndicatorRenderer[i]; - stateProperties.chartSeries - .setIndicatorType(indicator, technicalIndicatorsRenderer); - 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)); - - final List indicatorTextCollection = []; - for (int i = 0; i < chart.indicators.length; i++) { - final TechnicalIndicators indicator = - chart.indicators[i]; - technicalIndicatorsRenderer = - stateProperties.technicalIndicatorRenderer[i]; - final int count = indicatorTextCollection - .contains(technicalIndicatorsRenderer.indicatorType) - ? stateProperties.chartSeries.getIndicatorId(indicatorTextCollection, - technicalIndicatorsRenderer.indicatorType) - : 0; - indicatorTextCollection.add(technicalIndicatorsRenderer.indicatorType); - technicalIndicatorsRenderer.name = indicator.name ?? - (technicalIndicatorsRenderer.indicatorType + - (_map[technicalIndicatorsRenderer.indicatorType] == 1 - ? '' - : ' $count')); - if (indicator.isVisible && indicator.isVisibleInLegend) { - if (chart.onLegendItemRender != null) { - legendEventArgs = LegendRenderArgs(i); - legendEventArgs.text = - indicator.legendItemText ?? technicalIndicatorsRenderer.name; - legendEventArgs.legendIconType = indicator.legendIconType; - legendEventArgs.color = indicator.signalLineColor; - chart.onLegendItemRender(legendEventArgs); - } - final LegendRenderContext legendRenderContext = LegendRenderContext( - seriesRenderer: indicator, - indicatorRenderer: stateProperties.technicalIndicatorRenderer[i], - seriesIndex: - stateProperties.chartSeries.visibleSeriesRenderers.length + i, - isSelect: !indicator.isVisible, - text: legendEventArgs?.text ?? - indicator.legendItemText ?? - technicalIndicatorsRenderer.name, - isTrendline: false, - iconColor: legendEventArgs?.color ?? indicator.signalLineColor, - iconType: - legendEventArgs?.legendIconType ?? indicator.legendIconType); - legendCollections!.add(legendRenderContext); - final LegendItem legendIndicatorItem = LegendItem( - text: legendRenderContext.text, - color: legendRenderContext.iconColor!.withOpacity(legend!.opacity), - imageProvider: legendRenderContext.iconType == LegendIconType.image && - chart.legend.image != null - ? chart.legend.image - : null, - iconType: legendRenderContext.series.legendIconType == - LegendIconType.seriesType - ? ShapeMarkerType.horizontalLine - : _getEffectiveLegendIconType( - legendRenderContext.iconType, legendRenderContext), - iconStrokeWidth: ((legendRenderContext.series - is TechnicalIndicators && - legendRenderContext.indicatorRenderer!.isIndicator) && - (legendRenderContext.iconType == LegendIconType.seriesType || - legendRenderContext.iconType == - LegendIconType.horizontalLine || - legendRenderContext.iconType == - LegendIconType.verticalLine)) - ? (chart.legend.iconBorderWidth > 0 == true - ? chart.legend.iconBorderWidth - : 2) - : null); - legendItems.add(legendIndicatorItem); - if (!indicator.isVisible && - indicator.isVisibleInLegend && - stateProperties.renderingDetails.initialRender! == true) { - legendRenderContext.isSelect = true; - stateProperties.renderingDetails.legendToggleStates - .add(legendRenderContext); - } - } - } - } - - /// To find sum of points in radial bar series. - void _getSumOfPoints(CircularSeriesRendererExtension seriesRenderer) { - num sum = 0; - for (final ChartPoint point in seriesRenderer.renderPoints!) { - if (point.isVisible) { - sum += point.y!.abs(); - } - } - sumOfPoints = sum; - } - - /// To get the cartesian series gradient shader for SfLegend. - Shader? _getCartesianSeriesGradientShader( - LegendRenderContext legendRenderContext, Legend legend) { - Shader? legendShader; - Shader? cartesianShader; - TrendlineRenderer? trendlineRenderer; - - final Size size = Size(legend.iconWidth, legend.iconHeight); - final Rect pathRect = Rect.fromCenter( - center: Offset(size.width / 2, size.height / 2), - width: size.width, - height: size.height); - final Rect rectBounds = Offset.zero & size; - final String seriesType = legendRenderContext.seriesRenderer.seriesType; - final LinearGradient? gradientFill = legendRenderContext.series.gradient; - final Shader toggledLegendShader = - LinearGradient(colors: [toggledItemColor, toggledItemColor]) - .createShader(pathRect); - - if (legendRenderContext.trendline != null) { - trendlineRenderer = legendRenderContext.seriesRenderer - .trendlineRenderer[legendRenderContext.trendlineIndex!]; - } - legendRenderContext.isSelect = (legendRenderContext.trendline != null) - ? !trendlineRenderer!.visible - : legendRenderContext.seriesRenderer - is TechnicalIndicators - ? !legendRenderContext.indicatorRenderer!.visible! - : legendRenderContext.seriesRenderer.visible == false; - if (legendRenderContext.series is CartesianSeries && - legendRenderContext.series.onCreateShader != null && - !legendRenderContext.isSelect) { - ShaderDetails shaderDetails; - shaderDetails = ShaderDetails(pathRect, 'legend'); - legendShader = legendRenderContext.series.onCreateShader(shaderDetails); - } - - // ignore: prefer_if_null_operators - cartesianShader = legendShader == null - ? !seriesType.contains('line') && - (legendRenderContext.series is CartesianSeries && - legendRenderContext.series.gradient != null && - !legendRenderContext.isTrendline!) - ? !legendRenderContext.isSelect - ? gradientFill!.createShader(rectBounds) - : toggledLegendShader - : null - : !legendRenderContext.isSelect - ? legendShader - : toggledLegendShader; - - return cartesianShader; - } - - /// To get the circular series shader for SfLegend. - Shader? _getCircularSeriesShader( - LegendRenderContext legendRenderContext, Legend legend) { - Shader? legendShader; - Shader? shader; - ChartShaderMapper? pointShaderMapper; - final Size size = Size(legend.iconWidth, legend.iconHeight); - final Rect pathRect = Rect.fromCenter( - center: Offset(size.width / 2, size.height / 2), - width: size.width, - height: size.height); - final Shader toggledLegendShader = - LinearGradient(colors: [toggledItemColor, toggledItemColor]) - .createShader(pathRect); - legendRenderContext.isSelect = legendRenderContext.point.isVisible == false; - if (legendRenderContext.series is CircularSeries && - stateProperties.chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = ChartShaderDetails(pathRect, null, 'legend'); - legendShader = stateProperties.chart.onCreateShader(chartShaderDetails); - } - if (legendRenderContext.series is CircularSeries) { - pointShaderMapper = - PointHelper.getPointShaderMapper(legendRenderContext.point); - } - - shader = legendRenderContext.series is CircularSeries && - (pointShaderMapper != null || legendShader != null) - ? pointShaderMapper != null - ? !legendRenderContext.isSelect - ? pointShaderMapper(null, legendRenderContext.point?.index, - legendRenderContext.point?.fill, pathRect) - : toggledLegendShader - : !legendRenderContext.isSelect - ? legendShader - : toggledLegendShader - : null; - return shader; - } - - /// To get overlayMarker type for line series type legend icons. - ShapeMarkerType? _getOverlayMarkerType( - LegendRenderContext? legendRenderContext) { - ShapeMarkerType? overlayMarkerType; - if (legendRenderContext!.iconType == LegendIconType.seriesType) { - overlayMarkerType = (legendRenderContext.seriesRenderer.seriesType == - 'line' || - legendRenderContext.seriesRenderer.seriesType == 'fastline' || - legendRenderContext.seriesRenderer.seriesType - .contains('stackedline') == - true) && - legendRenderContext.series.markerSettings.isVisible == true && - legendRenderContext.series.markerSettings.shape != - DataMarkerType.image - ? _getMarkerIconType(legendRenderContext.series.markerSettings.shape) - : null; - } - return overlayMarkerType; - } - - /// To get legend icon shape based on series marker shape. - ShapeMarkerType _getMarkerIconType(DataMarkerType shape) { - ShapeMarkerType? iconType; - switch (shape) { - case DataMarkerType.circle: - iconType = ShapeMarkerType.circle; - break; - case DataMarkerType.rectangle: - iconType = ShapeMarkerType.rectangle; - break; - case DataMarkerType.image: - iconType = ShapeMarkerType.image; - break; - case DataMarkerType.pentagon: - iconType = ShapeMarkerType.pentagon; - break; - case DataMarkerType.verticalLine: - iconType = ShapeMarkerType.verticalLine; - break; - case DataMarkerType.invertedTriangle: - iconType = ShapeMarkerType.invertedTriangle; - break; - case DataMarkerType.horizontalLine: - iconType = ShapeMarkerType.horizontalLine; - break; - case DataMarkerType.diamond: - iconType = ShapeMarkerType.diamond; - break; - case DataMarkerType.triangle: - iconType = ShapeMarkerType.triangle; - break; - case DataMarkerType.none: - break; - } - return iconType!; - } - - /// To get the legend icon type for SfLegend. - ShapeMarkerType _getEffectiveLegendIconType(LegendIconType iconType, - [LegendRenderContext? legendRenderContext, String? seriesType]) { - ShapeMarkerType legendIconType; - switch (iconType) { - case LegendIconType.circle: - legendIconType = ShapeMarkerType.circle; - break; - case LegendIconType.rectangle: - legendIconType = ShapeMarkerType.rectangle; - break; - case LegendIconType.pentagon: - legendIconType = ShapeMarkerType.pentagon; - break; - case LegendIconType.verticalLine: - legendIconType = ShapeMarkerType.verticalLine; - break; - case LegendIconType.horizontalLine: - legendIconType = ShapeMarkerType.horizontalLine; - break; - case LegendIconType.diamond: - legendIconType = ShapeMarkerType.diamond; - break; - case LegendIconType.triangle: - legendIconType = ShapeMarkerType.triangle; - break; - case LegendIconType.invertedTriangle: - legendIconType = ShapeMarkerType.invertedTriangle; - break; - case LegendIconType.image: - legendIconType = ShapeMarkerType.image; - break; - case LegendIconType.seriesType: - legendIconType = - _getSeriesLegendIconType(seriesType!, legendRenderContext!); - break; - } - return legendIconType; - } - - /// To get effective series type legend icon for SfLegend. - ShapeMarkerType _getSeriesLegendIconType( - String seriesType, LegendRenderContext context) { - switch (seriesType) { - case 'line': - case 'fastline': - case 'stackedline': - case 'stackedline100': - return context.series.dashArray[0] != 0 - ? ShapeMarkerType.lineSeriesWithDashArray - : ShapeMarkerType.lineSeries; - case 'spline': - return context.series.dashArray[0] != 0 - ? ShapeMarkerType.splineSeriesWithDashArray - : ShapeMarkerType.splineSeries; - case 'splinearea': - case 'splinerangearea': - return ShapeMarkerType.splineAreaSeries; - case 'bar': - case 'stackedbar': - case 'stackedbar100': - return ShapeMarkerType.barSeries; - case 'column': - case 'stackedcolumn': - case 'stackedcolumn100': - case 'rangecolumn': - case 'histogram': - return ShapeMarkerType.columnSeries; - case 'area': - case 'stackedarea': - case 'rangearea': - case 'stackedarea100': - return ShapeMarkerType.areaSeries; - case 'stepline': - return context.series.dashArray[0] != 0 - ? ShapeMarkerType.stepLineSeriesWithDashArray - : ShapeMarkerType.stepLineSeries; - case 'scatter': - return _getMarkerIconType(context.series.markerSettings.shape); - case 'bubble': - return ShapeMarkerType.bubbleSeries; - case 'hilo': - return ShapeMarkerType.hiloSeries; - case 'hiloopenclose': - case 'candle': - return ShapeMarkerType.hiloOpenCloseSeries; - case 'waterfall': - case 'boxandwhisker': - return ShapeMarkerType.waterfallSeries; - case 'pie': - return ShapeMarkerType.pieSeries; - case 'doughnut': - return ShapeMarkerType.doughnutSeries; - case 'radialbar': - return ShapeMarkerType.radialBarSeries; - case 'steparea': - return ShapeMarkerType.stepAreaSeries; - case 'pyramid': - return ShapeMarkerType.pyramidSeries; - case 'funnel': - return ShapeMarkerType.funnelSeries; - case 'errorbar': - return ShapeMarkerType.verticalLine; - default: - return ShapeMarkerType.circle; - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart deleted file mode 100644 index e1a048290..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/technical_indicators/technical_indicator.dart'; - -/// Represents the legend render context chart. -class LegendRenderContext { - /// Creates an instance of legend render context. - LegendRenderContext( - {this.size, - required this.text, - this.textSize, - required this.iconColor, - required this.iconType, - this.point, - required this.isSelect, - this.trendline, - required this.seriesIndex, - this.trendlineIndex, - this.seriesRenderer, - this.isTrendline, - this.indicatorRenderer}) - : series = seriesRenderer is TechnicalIndicators - ? seriesRenderer - : seriesRenderer is CartesianSeriesRenderer - ? SeriesHelper.getSeriesRendererDetails(seriesRenderer).series - : seriesRenderer.series; - - /// Specifies the value of text. - String text; - - /// Holds the value of icon color. - Color? iconColor; - - /// Holds the value of text size. - Size? textSize; - - /// Holds the value legend icon type. - LegendIconType iconType; - - /// Specifies the size value. - Size? size; - - /// Specifies the value of template size. - Size? templateSize; - - /// Specifies the value of series. - dynamic series; - - /// Holds the series renderer value. - dynamic seriesRenderer; - - /// Holds the indicator renderer. - TechnicalIndicatorsRenderer? indicatorRenderer; - - /// Holds the value if trendline. - Trendline? trendline; - - /// Holds the value of point. - dynamic point; - - /// Specifies the series index value. - int seriesIndex; - - /// Specifies the value of trendline index. - int? trendlineIndex; - - /// Specifies whether the legend is selected. - bool isSelect; - - /// Specifies whether the legend is rendered. - bool isRender = false; - - /// Specifies whether it is trendline legend. - bool? isTrendline = false; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart b/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart deleted file mode 100644 index 5035a7843..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import '../circular_chart/renderer/common.dart'; -import 'common.dart'; -import 'legend/legend.dart'; -import 'legend/renderer.dart'; -import 'template/rendering.dart'; -import 'user_interaction/tooltip.dart'; - -/// Represents the rendering details of the chart. -class RenderingDetails { - /// Specifies the animation controller of chart. - late AnimationController animationController; - - /// Specifies the animation controller of chart annotation controller. - late AnimationController annotationController; - - /// Specifies the series repaint notifier. - late ValueNotifier seriesRepaintNotifier; - - /// Specifies the chart element animation. - late Animation chartElementAnimation; - - /// Specifies the context for legend. - late List legendWidgetContext; - - /// Specifies the context for legend toggle template. - late List legendToggleTemplateStates; - - /// Specifies the legend toggle states. - late List legendToggleStates; - - /// Specifies whether the legend is toggled. - late bool isLegendToggled; - - /// Specifies the chart legend. - late ChartLegend chartLegend; - - /// Specifies the chart legend renderer. - late LegendRenderer legendRenderer; - - /// Specifies the tooltip behavior renderer. - late TooltipBehaviorRenderer tooltipBehaviorRenderer; - - /// Specifies the chart interaction. - ChartInteraction? currentActive; - - /// Specifies the tap position. - Offset? tapPosition; - - /// Specifies the list of selection data. - late List selectionData; - - /// Specifies the chart container rect. - late Rect chartContainerRect; - - /// Specifies the chart area rect. - late Rect chartAreaRect; - - /// Specifies the data label template region. - late List dataLabelTemplateRegions; - - /// Specifies the list of template info. - late List templates; - - /// Specifies the chart template. - ChartTemplate? chartTemplate; - - /// Specifies the chart theme. - late SfChartThemeData chartTheme; - - /// Specifies the exploded points. - late List explodedPoints; - - /// Specifies whether the chart is rendered at load time. - bool? initialRender; - - /// Specifies whether the animation is completed. - late bool animateCompleted; - - /// Specifies whether the widget needs to updated. - late bool widgetNeedUpdate; - - /// Specifies the previous orientation of the device. - Orientation? oldDeviceOrientation; - - /// Specifies the current device orientation. - late Orientation deviceOrientation; - - /// Specifies the previous chart size. - Size? prevSize; - - /// Specifies whether the current chart size is changed. - late bool didSizeChange; - - /// Specifies the list of chart widget. - List? chartWidgets; - - /// Specifies whether the text direction of chart widget is RTL or LTR. - late bool isRtl; -} 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 deleted file mode 100644 index 92b460be4..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart +++ /dev/null @@ -1,515 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/renderer/chart_point.dart'; -import '../../circular_chart/renderer/renderer_base.dart'; -import '../common.dart'; -import '../user_interaction/selection_behavior.dart'; -import '../utils/enum.dart'; -import '../utils/typedef.dart'; - -/// This class holds the property of series. -/// -/// Chart series has property to render the series if the property data source is empty it renders an empty chart. -/// [ChartSeries] is the base class, it has the property to set the name, data source, border color and width to customize the series. -/// -/// Provides options that are extended by the other sub classes such as name, point color mapper, data label mapper, animation -/// duration and border-width and color for customize the appearance of the chart. -class ChartSeries { - /// Creating an argument constructor of ChartSeries class. - const ChartSeries( - {this.xValueMapper, - this.yValueMapper, - this.dataLabelMapper, - this.name, - this.dataSource, - this.pointColorMapper, - this.sortFieldValueMapper, - bool? enableTooltip, - this.emptyPointSettings, - this.dataLabelSettings, - this.animationDuration, - this.borderColor, - this.borderWidth, - this.selectionBehavior, - this.legendItemText, - this.legendIconType, - this.opacity, - this.sortingOrder, - this.animationDelay, - this.isVisible}) - : enableTooltip = enableTooltip ?? true; - - /// Data required for rendering the series. If no data source is specified, empty - /// chart will be rendered without series. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - final List? dataSource; - - /// Field in the data source, which is considered as x-value. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - final ChartIndexedValueMapper? xValueMapper; - - /// Field in the data source, which is considered as y-value. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - final ChartIndexedValueMapper? yValueMapper; - - /// Field in the data source, which is considered as fill color for the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (ColumnColors sales, _) => sales.x, - /// yValueMapper: (ColumnColors sales, _) => sales.y, - /// pointColorMapper: (ColumnColors sales, _) => sales.pointColorMapper, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// ColumnColors(1991, 7.8, const Color.fromRGBO(0, 0, 255, 1)), - /// ColumnColors(1992, 6.5, const Color.fromRGBO(255, 0, 0, 1)), - /// ColumnColors(1993, 6.0, const Color.fromRGBO(255, 100, 102, 1)), - /// ]; - /// class ColumnColors { - /// ColumnColors(this.x, this.y,this.pointColorMapper); - /// final num x; - /// final num y; - /// final Color pointColorMapper; - /// } - /// ``` - final ChartIndexedValueMapper? pointColorMapper; - - /// Field in the data source, which is considered as text for the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// dataSource: [ - /// SalesData(DateTime(2005, 0, 1), 'India', 16), - /// SalesData(DateTime(2006, 0, 1), 'China', 12), - /// SalesData(DateTime(2007, 0, 1), 'USA',18), - /// ], - /// dataLabelSettings: DataLabelSettings(isVisible:true), - /// dataLabelMapper: (SalesData data, _) => data.dataLabelText, - /// ), - /// ], - /// ); - /// } - /// class SalesData { - /// SalesData(this.year, this.dataLabelText, this.sales1); - /// final DateTime year; - /// final String dataLabelText; - /// final int sales1; - /// } - /// ``` - final ChartIndexedValueMapper? dataLabelMapper; - - /// Customizes the empty points, i.e. null data points in a series. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// emptyPointSettings: EmptyPointSettings( - /// mode: EmptyPointMode.average - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final EmptyPointSettings? emptyPointSettings; - - /// Customizes the data labels in a series. Data label is a text, which displays - /// the details about the data point. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ), - /// ], - /// ); - /// } - /// ``` - 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’ - /// text prefix will be considered as series name. - /// - ///```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BubbleSeries( - /// name: 'Bubble Series' - /// ) - /// ] - /// ); - /// } - ///``` - 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. - /// - /// Defaults to `true`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: TooltipBehavior(enable: true), - /// series: >[ - /// BubbleSeries( - /// enableTooltip: false, - /// ), - /// ], - /// ); - /// } - /// ``` - final bool enableTooltip; - - /// Duration of the series animation. It takes millisecond value as input. - /// - /// Defaults to `1500`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// LineSeries( - /// animationDuration: 1000, - /// ), - /// ], - /// ); - /// } - /// ``` - final double? animationDuration; - - /// Border color of the series. - /// - /// _Note:_ This is not applicable for line, spline, step line, stacked line, stacked line 100 - /// and fast line series types. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? borderColor; - - /// Border width of the series. - /// - /// _Note:_ This is not applicable for line, spline, step line, and fast line series types. - /// - /// Defaults to `0`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// borderColor: Colors.red, - /// borderWidth: 2 - /// ), - /// ], - /// ); - /// } - /// ``` - 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. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend(isVisible:true), - /// series: >[ - /// LineSeries( - /// legendItemText: 'Legend' - /// ), - /// ], - /// ); - /// } - /// ``` - 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. - /// - /// Defaults to `LegendIconType.seriesType`. - /// - /// Also refer [LegendIconType]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// legend: Legend(isVisible:true), - /// series: >[ - /// LineSeries( - /// legendIconType: LegendIconType.diamond, - /// ), - /// ], - /// ); - /// } - /// ``` - final LegendIconType? legendIconType; - - /// Customizes the data points or series on selection. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: SelectionBehavior( - /// enable:true - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - final SelectionBehavior? selectionBehavior; - - /// Opacity of the series. The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// opacity: 0.8 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? opacity; - - /// Field in the data source, which is considered for sorting the data points. - /// - /// Defaults to `null`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// sortFieldValueMapper: (SalesData sales, _) => sales.x, - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - 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. - /// - /// Default to `SortingOrder.none`. - /// - /// Also refer [SortingOrder]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// sortFieldValueMapper: (SalesData sales, _) => sales.x, - /// sortingOrder: SortingOrder.descending - /// ), - /// ], - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - final SortingOrder? sortingOrder; - - /// Visibility of the series. - /// - /// Default to `true`. - /// - /// Also refer [SortingOrder]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// isVisible: false - /// ), - /// ], - /// ); - /// } - /// ``` - final bool? isVisible; - - /// Delay duration of the series animation.It takes a millisecond value as input. - /// By default, the series will get animated for the specified duration. - /// If animationDelay is specified, then the series will begin to animate - /// after the specified duration. - /// - /// Defaults to `0` for all the series except ErrorBarSeries. - /// The default value for the ErrorBarSeries is `1500`. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// animationDelay: 300 - /// ), - /// ], - /// ); - /// } - /// ``` - final double? animationDelay; -} - -/// This class provides method to calculate the empty point value. -abstract class CircularChartEmptyPointBehavior { - /// To calculate the values of the empty points. - void calculateEmptyPointValue(int pointIndex, - ChartPoint currentPoint, CircularSeriesRenderer seriesRenderer); -} - -/// Data points with a null value are considered empty points. -/// Empty data points are ignored and are not plotted in the chart. -/// -/// Provides Empty points value calculations. -abstract class TriangularChartEmptyPointBehavior { - /// To calculate the values of the empty points. - void calculateEmptyPointValue( - int pointIndex, dynamic currentPoint, dynamic seriesRenderer); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart deleted file mode 100644 index 60c75e326..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'rendering_details.dart'; - -/// Represents the state properties class. -class StateProperties { - /// Creates an instance of state properties class. - StateProperties(this.renderingDetails, this.chartState); - - /// Holds the value of rendering details. - final RenderingDetails renderingDetails; - - /// Holds the value of chart state. - final dynamic chartState; - - /// Specifies the chart instance. - dynamic get chart => chartState.widget; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart deleted file mode 100644 index 0400eb476..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart +++ /dev/null @@ -1,428 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/chart_series/series_renderer_properties.dart'; -import '../../chart/chart_series/xy_data_series.dart'; -import '../../chart/common/cartesian_state_properties.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/common/data_label_renderer.dart'; -import '../../chart/utils/helper.dart'; -import '../state_properties.dart'; - -import '../utils/enum.dart'; -import '../utils/helper.dart'; - -/// Represents the render template class. -// ignore: must_be_immutable -class RenderTemplate extends StatefulWidget { - /// Creates an instance of render template. - // ignore: prefer_const_constructors_in_immutables - RenderTemplate( - {required this.template, - this.needMeasure, - required this.templateLength, - required this.templateIndex, - required this.stateProperties}); - - /// Hold the value of chart template info. - final ChartTemplateInfo template; - - /// Specifies whether to measure the template. - bool? needMeasure; - - /// Specifies the template length. - final int templateLength; - - /// Specifies the template index value. - final int templateIndex; - - /// Holds the value of state properties. - final StateProperties stateProperties; - - /// Specifies whether it is annotation. - bool? isAnnotation; - - @override - State createState() => _RenderTemplateState(); -} - -class _RenderTemplateState extends State - with TickerProviderStateMixin { - late List templateControllerList; - AnimationController? animationController; - late Animation animation; - @override - void initState() { - templateControllerList = []; - animationController = AnimationController(vsync: this); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final ChartTemplateInfo templateInfo = widget.template; - Widget currentWidget = Container(); - Widget renderWidget; - if (templateInfo.templateType == 'DataLabel') { - renderWidget = _ChartTemplateRenderObject( - child: templateInfo.widget!, - templateInfo: templateInfo, - stateProperties: widget.stateProperties, - animationController: animationController); - } else { - renderWidget = _ChartTemplateRenderObject( - child: templateInfo.widget!, - templateInfo: templateInfo, - stateProperties: widget.stateProperties, - animationController: animationController); - } - if (templateInfo.animationDuration > 0) { - final dynamic stateProperties = widget.stateProperties; - final dynamic seriesRendererDetails = - (templateInfo.templateType == 'DataLabel') - ? stateProperties is CartesianStateProperties - ? SeriesHelper.getSeriesRendererDetails(stateProperties - .chartSeries - .visibleSeriesRenderers[templateInfo.seriesIndex!]) - : stateProperties.chartSeries - .visibleSeriesRenderers[templateInfo.seriesIndex!] - : null; - final Orientation? orientation = - widget.stateProperties.renderingDetails.oldDeviceOrientation; - final bool needsAnimate = - orientation == MediaQuery.of(context).orientation && - (!(seriesRendererDetails != null && - seriesRendererDetails is SeriesRendererDetails) || - seriesRendererDetails.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; - } - - @override - void dispose() { - if (templateControllerList.isNotEmpty) { - for (int index = 0; index < templateControllerList.length; index++) { - templateControllerList[index].dispose(); - } - templateControllerList.clear(); - } - super.dispose(); - } -} - -/// Represents the render object for annotation widget. -class _ChartTemplateRenderObject extends SingleChildRenderObjectWidget { - const _ChartTemplateRenderObject( - {Key? key, - required Widget child, - required this.templateInfo, - required this.stateProperties, - required this.animationController}) - : super(key: key, child: child); - - final ChartTemplateInfo templateInfo; - - final StateProperties stateProperties; - - final AnimationController? animationController; - - @override - RenderObject createRenderObject(BuildContext context) { - return _ChartTemplateRenderBox( - templateInfo, stateProperties, animationController); - } - - @override - void updateRenderObject( - BuildContext context, covariant _ChartTemplateRenderBox renderBox) { - renderBox.templateInfo = templateInfo; - } -} - -/// Render the annotation widget in the respective position. -class _ChartTemplateRenderBox extends RenderShiftedBox { - _ChartTemplateRenderBox( - this._templateInfo, this.stateProperties, this._animationController, - [RenderBox? child]) - : super(child); - - ChartTemplateInfo _templateInfo; - - final dynamic stateProperties; - - final AnimationController? _animationController; - - ChartTemplateInfo get templateInfo => _templateInfo; - - set templateInfo(ChartTemplateInfo value) { - if (_templateInfo != value) { - _templateInfo = value; - markNeedsLayout(); - } - } - - @override - void performLayout() { - double locationX, locationY; - bool isLabelWithInRange = true; - final BoxConstraints constraints = this.constraints; - if (child != null) { - locationX = _templateInfo.location.dx; - locationY = _templateInfo.location.dy; - - 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); - locationY = locationY - - (_templateInfo.verticalAlignment == ChartAlignment.near - ? 0 - : _templateInfo.verticalAlignment == ChartAlignment.center - ? child!.size.height / 2 - : child!.size.height); - if (_templateInfo.templateType == 'DataLabel' && - stateProperties is CartesianStateProperties) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(stateProperties.chartSeries - .visibleSeriesRenderers[_templateInfo.seriesIndex]); - seriesRendererDetails.stateProperties = stateProperties; - seriesRendererDetails.dataLabelSettingsRenderer = - DataLabelSettingsRenderer( - seriesRendererDetails.series.dataLabelSettings); - final CartesianChartPoint? point = - (seriesRendererDetails.dataPoints.isNotEmpty == true) - ? seriesRendererDetails.dataPoints[_templateInfo.pointIndex!] - : null; - if (seriesRendererDetails.isRectSeries == true || - seriesRendererDetails.seriesType.contains('hilo') == true || - seriesRendererDetails.seriesType.contains('candle') == true || - seriesRendererDetails.seriesType.contains('box') == true) { - seriesRendererDetails.sideBySideInfo = calculateSideBySideInfo( - seriesRendererDetails.renderer, stateProperties); - } - seriesRendererDetails.hasDataLabelTemplate = true; - if (seriesRendererDetails.seriesType.contains('spline') == true) { - if (seriesRendererDetails.drawControlPoints.isNotEmpty == true) { - seriesRendererDetails.drawControlPoints.clear(); - } - calculateSplineAreaControlPoints(seriesRendererDetails.renderer); - } - if (point != null && - seriesRendererDetails.seriesType != 'boxandwhisker') { - if (point.region == null) { - if (seriesRendererDetails.visibleDataPoints == null || - seriesRendererDetails.visibleDataPoints!.length >= - seriesRendererDetails.dataPoints.length == - true) { - seriesRendererDetails.visibleDataPoints = - >[]; - } - - seriesRendererDetails.setSeriesProperties(seriesRendererDetails); - seriesRendererDetails.calculateRegionData( - stateProperties, - seriesRendererDetails, - 0, - point, - _templateInfo.pointIndex!, - null, - null, - null, - null); - } - calculateDataLabelPosition( - seriesRendererDetails, - point, - _templateInfo.pointIndex!, - stateProperties, - seriesRendererDetails.dataLabelSettingsRenderer, - _animationController!, - size, - _templateInfo.location); - locationX = point.labelLocation!.x; - locationY = point.labelLocation!.y; - isLabelWithInRange = - isLabelWithinRange(seriesRendererDetails, point); - } - } - final Rect rect = - Rect.fromLTWH(locationX, locationY, size.width, size.height); - final List dataLabelTemplateRegions = - stateProperties.renderingDetails.dataLabelTemplateRegions; - final bool isCollide = (_templateInfo.templateType == 'DataLabel') && - findingCollision(rect, dataLabelTemplateRegions); - if (!isCollide && - _isTemplateWithinBounds(_templateInfo.clipRect, rect) && - isLabelWithInRange) { - (_templateInfo.templateType == 'DataLabel') - ? dataLabelTemplateRegions.add(rect) - : stateProperties.annotationRegions.add(rect); - childParentData.offset = Offset(locationX, locationY); - } else { - childParentData.offset = Offset.infinite; - } - } - } else { - size = Size.zero; - } - } - - /// To check template is within bounds. - bool _isTemplateWithinBounds(Rect bounds, Rect templateRect) => - templateRect.left >= bounds.left && - templateRect.left + templateRect.width <= bounds.left + bounds.width && - templateRect.top >= bounds.top && - templateRect.top + templateRect.height <= bounds.top + bounds.height; -} - -/// Represents the chart template class. -// ignore: must_be_immutable -class ChartTemplate extends StatefulWidget { - /// Creates an instance of chart template class. - // ignore: prefer_const_constructors_in_immutables - ChartTemplate( - {required this.templates, - required this.render, - required this.stateProperties}); - - /// Holds the list of chart template info. - List templates; - - /// Specifies whether the template is rendered. - bool render = false; - - /// Holds the value of state properties. - StateProperties stateProperties; - - /// Holds the value of chart template state. - // ignore: library_private_types_in_public_api - late _ChartTemplateState state; - - @override - State createState() => _ChartTemplateState(); -} - -class _ChartTemplateState extends State { - @override - void initState() { - widget.state = this; - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - Widget renderTemplate = Container(); - final bool animationCompleted = - widget.stateProperties.renderingDetails.animateCompleted; - if (animationCompleted && widget.templates.isNotEmpty) { - final List renderWidgets = []; - for (int i = 0; i < widget.templates.length; i++) { - renderWidgets.add(RenderTemplate( - template: widget.templates[i], - templateIndex: i, - templateLength: widget.templates.length, - stateProperties: widget.stateProperties, - )); - } - renderTemplate = Stack(children: renderWidgets); - } - return renderTemplate; - } - - void templateRender() { - if (mounted) { - setState(() { - widget.render = true; - }); - } - } -} - -/// Represents the chart template info class. -class ChartTemplateInfo { - /// Creates an instance of chart template info class. - ChartTemplateInfo( - {required this.key, - required this.widget, - required this.location, - required this.animationDuration, - this.seriesIndex, - this.pointIndex, - required this.templateType, - required this.clipRect, - this.needMeasure = true, - ChartAlignment? horizontalAlignment, - ChartAlignment? verticalAlignment}) - : horizontalAlignment = horizontalAlignment ?? ChartAlignment.center, - verticalAlignment = verticalAlignment ?? ChartAlignment.center; - - /// Specifies the key value. - Key? key; - - /// Specifies the widget. - Widget? widget; - - /// Holds the size value. - late Size size; - - /// Holds the point value. - late dynamic point; - - /// Holds the value of location. - Offset location; - - /// Specifies the build context value. - late BuildContext context; - - /// Specifies the animation duration. - int animationDuration; - - /// Specifies the value of animation controller. - late AnimationController animationController; - - /// Specifies the point index value. - int? pointIndex; - - /// Specifies the series index value. - int? seriesIndex; - - /// Specifies the clip rect value. - Rect clipRect; - - /// Holds the template type. - String templateType; - - /// Holds the value of horizontal alignment. - ChartAlignment horizontalAlignment; - - /// Holds the value of vertical alignment. - ChartAlignment verticalAlignment; - - /// Specifies whether to measure the template. - bool needMeasure; -} 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 deleted file mode 100644 index 6045f65c3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart +++ /dev/null @@ -1,272 +0,0 @@ -part of charts; - -// @Deprecated('Use SelectionBehavior instead. ' -// 'This feature was deprecated from next release onwards') - -// ///Provides options for the selection of series or data points. -// /// -// ///By using this class, The color and width of the selected and unselected series or data points can be customized. -// class SelectionSettings { -// /// Creating an argument constructor of SelectionSettings class. -// SelectionSettings( -// {bool? enable, -// this.selectedColor, -// this.selectedBorderColor, -// this.selectedBorderWidth, -// this.unselectedColor, -// this.unselectedBorderColor, -// this.unselectedBorderWidth, -// double? selectedOpacity, -// double? unselectedOpacity, -// this.selectionController}) -// : enable = enable ?? false, -// selectedOpacity = selectedOpacity ?? 1.0, -// unselectedOpacity = unselectedOpacity ?? 0.5; - -// ///Enables or disables the selection. -// /// -// ///By enabling this, each data point or series in the chart can be selected. -// /// -// ///Defaults to `false`. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// enable: true -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final bool enable; - -// ///Color of the selected data points or series. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// selectedColor: Colors.red -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final Color? selectedColor; - -// ///Border color of the selected data points or series. -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// selectedBorderColor: Colors.red, -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final Color? selectedBorderColor; - -// ///Border width of the selected data points or series. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// selectedColor: Colors.red, -// /// selectedBorderWidth: 2 -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final double? selectedBorderWidth; - -// ///Color of the unselected data points or series. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// unselectedColor: Colors.grey, -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final Color? unselectedColor; - -// ///Border color of the unselected data points or series. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// unselectedBorderColor: Colors.grey, -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final Color? unselectedBorderColor; - -// ///Border width of the unselected data points or series. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// unselectedBorderWidth: 2 -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final double? unselectedBorderWidth; - -// ///Opacity of the selected series or data point. -// /// -// ///Default to `1.0`. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// selectedOpacity: 0.5, -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///``` -// final double selectedOpacity; - -// ///Opacity of the unselected series or data point. -// /// -// ///Defaults to `0.5`. -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// series: >[ -// /// BarSeries( -// /// selectionSettings: SelectionSettings( -// /// unselectedOpacity: 0.4, -// /// ), -// /// ), -// /// ], -// /// )); -// ///} -// ///`` -// final double unselectedOpacity; - -// /// Controller used to set the maximum and minimum values for the chart.By providing the selection controller, the maximum and -// ///The minimum range of selected series or points can be customized -// /// -// ///```dart -// ///Widget build(BuildContext context) { -// /// return Container( -// /// child: SfCartesianChart( -// /// selectionSettings: SelectionSettings( -// /// selectionController: controller, -// /// ), -// /// )); -// ///} -// ///``` -// final RangeController? selectionController; - -// dynamic _chartState; - -// ////Selects or deselects the specified data point in the series. -// /// -// ///The following are the arguments to be passed. -// ///* `pointIndex` - index of the data point that needs to be selected. -// ///* `seriesIndex` - index of the series in which the data point is selected. -// /// -// ///Where the `pointIndex` is a required argument and `seriesIndex` is an optional argument. By default, 0 will -// /// be considered as the series index. Thus it will take effect on the first series if no value is specified. -// /// -// ///For Circular, Pyramid and Funnel charts, seriesIndex should always be 0, as it has only one series. -// /// -// ///If the specified data point is already selected, it will be deselected, else it will be selected. -// /// Selection type and multi-selection functionality is also applicable for this, but it is based on -// /// the API values specified in [ChartSelectionBehavior]. -// /// -// ///_Note:_ Even though, the [enable] property in [ChartSelectionBehavior] is set to false, this method -// /// will work. -// /// ```dart -// /// Widget build(BuildContext context) { -// /// selection = SelectionSettings(enable: true); -// /// chart = SfCartesianChart(series:getChartData); -// /// return Scaffold( -// /// child: Column( -// /// children: [ -// /// FlatButton( -// /// child: Text('Select'), -// /// onPressed: select), -// /// Container(child: chart) -// /// ])); -// ///} -// /// void select() { -// /// selection.selectionIndex(1, 0); -// ///} -// ///```dart - -// void selectDataPoints(int pointIndex, [int seriesIndex = 0]) { -// final dynamic seriesRenderer = -// _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; -// final SelectionBehaviorRenderer selectionBehaviorRenderer = -// seriesRenderer.seriesRendererDetails.selectionBehaviorRenderer; -// selectionBehaviorRenderer.selectionDetails.selectionRenderer -// ?.selectDataPoints(pointIndex, seriesIndex); -// } - -// /// provides the list of selected point indices for given series. -// List getSelectedDataPoints(CartesianSeries _series) { -// List selectedItems = []; -// final dynamic seriesRenderer = -// _stateProperties.chartSeries.visibleSeriesRenderers[0]; -// final SelectionBehaviorRenderer selectionBehaviorRenderer = -// seriesRenderer.seriesRendererDetails.selectionBehaviorRenderer; -// final List selectedPoints = []; -// selectedItems = -// selectionBehaviorRenderer.selectionDetails.selectionRenderer!.selectedSegments; -// for (int i = 0; i < selectedItems.length; i++) { -// 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 deleted file mode 100644 index 0cc4471c3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart +++ /dev/null @@ -1,658 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/cartesian_state_properties.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../chart/chart_behavior/selection_behavior.dart'; -import '../../chart/chart_segment/chart_segment.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/chart_series/series_renderer_properties.dart'; -import '../../chart/user_interaction/selection_renderer.dart'; -import '../../chart/utils/helper.dart'; -import '../../circular_chart/renderer/common.dart'; - -/// Provides options for the selection of series or data points. -/// -/// By using this class, The color and width of the selected and unselected series or data points can be customized. -class SelectionBehavior { - /// Creating an argument constructor of SelectionBehavior class. - SelectionBehavior( - {bool? enable, - this.selectedColor, - this.selectedBorderColor, - this.selectedBorderWidth, - this.unselectedColor, - this.unselectedBorderColor, - this.unselectedBorderWidth, - double? selectedOpacity, - double? unselectedOpacity, - this.selectionController, - bool? toggleSelection}) - : enable = enable ?? false, - selectedOpacity = selectedOpacity ?? 1.0, - unselectedOpacity = unselectedOpacity ?? 0.5, - toggleSelection = toggleSelection ?? true; - - /// Enables or disables the selection. - /// - /// By enabling this, each data point or series in the chart can be selected. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final bool enable; - - /// Color of the selected data points or series. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// selectedColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? selectedColor; - - /// Border color of the selected data points or series. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// selectedBorderWidth: 4, - /// selectedBorderColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? selectedBorderColor; - - /// Border width of the selected data points or series. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// selectedBorderWidth: 4, - /// selectedBorderColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final double? selectedBorderWidth; - - /// Color of the unselected data points or series. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// unselectedColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? unselectedColor; - - /// Border color of the unselected data points or series. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// unselectedBorderWidth: 4, - /// unselectedBorderColor: Colors.grey - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final Color? unselectedBorderColor; - - /// Border width of the unselected data points or series. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// unselectedBorderWidth: 4, - /// unselectedBorderColor: Colors.grey - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final double? unselectedBorderWidth; - - /// Opacity of the selected series or data point. - /// - /// Default to `1.0`. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// selectedOpacity: 0.5 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final double selectedOpacity; - - /// Opacity of the unselected series or data point. - /// - /// Defaults to `0.5`. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// unselectedOpacity: 0.4 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final double unselectedOpacity; - - /// Controller used to set the maximum and minimum values for the chart. - /// By providing the selection controller, the maximum and - /// The minimum range of selected series or points can be customized. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// final RangeController rangeController= RangeController( - /// start: 1, - /// end: 4 - /// ) - /// return SfRangeSelector ( - /// min: 1, - /// max: 4, - /// controller: rangeController, - /// child: SfCartesianChart( - /// series: >[ - /// ColumnSeries( - /// dataSource: chartData, - /// xValueMapper: (SalesData sales, _) => sales.x, - /// yValueMapper: (SalesData sales, _) => sales.y, - /// selectionController: rangeController - /// ), - /// ], - /// ) - /// ); - /// } - /// final List chartData = [ - /// SalesData(1, 23), - /// SalesData(2, 35), - /// SalesData(3, 19), - /// SalesData(4, 29), - /// SalesData(5, 50) - /// SalesData(6, 77) - /// ]; - /// - /// class SalesData { - /// SalesData(this.x, this.y); - /// final double x; - /// final double y; - /// } - /// ``` - final RangeController? selectionController; - - /// Decides whether to deselect the selected item or not. - /// - /// Provides an option to decide, whether to deselect the selected data point/series - /// or remain selected, when interacted with it again. - /// - /// If set to true, deselection will be performed else the point will not get deselected. - /// This works even while calling public methods, in various selection modes, with - /// multi-selection, and also on dynamic changes. - /// - /// Defaults to `true`. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true, - /// toggleSelection: false - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ), - /// ], - /// ); - /// } - /// ``` - final bool toggleSelection; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is SelectionBehavior && - other.enable == enable && - other.selectedColor == selectedColor && - other.selectedBorderColor == selectedBorderColor && - other.selectedBorderWidth == selectedBorderWidth && - other.unselectedColor == unselectedColor && - other.unselectedBorderColor == unselectedBorderColor && - other.unselectedBorderWidth == unselectedBorderWidth && - other.selectedOpacity == selectedOpacity && - other.unselectedOpacity == unselectedOpacity && - other.selectionController == selectionController; - } - - @override - int get hashCode { - final List values = [ - enable, - selectedColor, - selectedBorderColor, - selectedBorderWidth, - unselectedColor, - unselectedBorderColor, - unselectedBorderWidth, - selectedOpacity, - unselectedOpacity, - selectionController - ]; - return hashList(values); - } - - /// Specifies the value of selection behavior renderer. - SelectionBehaviorRenderer? _selectionBehaviorRenderer; - - /// Selects or deselects the specified data point in the series. - /// - /// The following are the arguments to be passed. - /// * `pointIndex` - index of the data point that needs to be selected. - /// * `seriesIndex` - index of the series in which the data point is selected. - /// - /// Where the `pointIndex` is a required argument and `seriesIndex` is an optional argument. By default, 0 will - /// be considered as the series index. Thus it will take effect on the first series if no value is specified. - /// - /// For circular, pyramid and funnel charts, series index should always be 0, as it has only one series. - /// - /// If the specified data point is already selected, it will be deselected, else it will be selected. - /// Selection type and multi-selection functionality is also applicable for this, but it is based on - /// the API values specified in [ChartSelectionBehavior]. - /// - /// _Note:_ Even though, the [enable] property in [ChartSelectionBehavior] is set to false, this method - /// will work. - /// - /// ```dart - /// late SelectionBehavior selectionBehavior; - /// - /// void initState() { - /// selectionBehavior = SelectionBehavior( - /// enable: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return Column( - /// children: [ - /// TextButton( - /// onPressed: () { - /// setState(() { - /// select(); - /// }); - /// }, - /// child: Text('Select data points') - /// ), - /// SfCartesianChart( - /// series: >[ - /// BarSeries( - /// selectionBehavior: selectionBehavior - /// ) - /// ] - /// ) - /// ] - /// ); - /// } - /// - /// void select() { - /// _selectionBehavior.selectDataPoints(3); - /// } - /// ``` - void selectDataPoints(int pointIndex, [int seriesIndex = 0]) { - if (_selectionBehaviorRenderer != null) { - final dynamic seriesRenderer = _selectionBehaviorRenderer! - ._selectionDetails - .stateProperties - .chartSeries - .visibleSeriesRenderers[seriesIndex]; - assert( - seriesRenderer is CartesianSeriesRenderer == false || - getVisibleDataPointIndex(pointIndex, - SeriesHelper.getSeriesRendererDetails(seriesRenderer)) != - null, - 'Provided point index is not in the visible range. Provide point index which is in the visible range.'); - _selectionBehaviorRenderer = seriesRenderer is CartesianSeriesRenderer - ? SeriesHelper.getSeriesRendererDetails(seriesRenderer) - .selectionBehaviorRenderer - : _selectionBehaviorRenderer; - _selectionBehaviorRenderer!._selectionDetails.selectionRenderer - ?.selectDataPoints(pointIndex, seriesIndex); - } - } - - /// Provides the list of selected point indices for given series. - List getSelectedDataPoints(CartesianSeries _series) { - List selectedItems = []; - final dynamic seriesRenderer = _selectionBehaviorRenderer!._selectionDetails - .stateProperties.chartSeries.visibleSeriesRenderers[0]; - SelectionBehaviorRenderer _selectionRenderer; - if (seriesRenderer is CartesianSeriesRenderer) { - _selectionRenderer = SeriesHelper.getSeriesRendererDetails(seriesRenderer) - .selectionBehaviorRenderer!; - } else { - _selectionRenderer = seriesRenderer.selectionBehaviorRenderer; - } - - final List selectedPoints = []; - selectedItems = _selectionRenderer - ._selectionDetails.selectionRenderer!.selectedSegments; - for (int i = 0; i < selectedItems.length; i++) { - selectedPoints.add(selectedItems[i].currentSegmentIndex!); - } - return selectedPoints; - } -} - -/// Selection renderer class for mutable fields and methods. -class SelectionBehaviorRenderer with ChartSelectionBehavior { - /// Creates an argument constructor for SelectionSettings renderer class. - SelectionBehaviorRenderer(SelectionBehavior selectionBehavior, dynamic chart, - dynamic stateProperties) { - _selectionDetails = - SelectionDetails(selectionBehavior, chart, stateProperties, this); - } - - /// Holds the selection details instance for the chart. - late SelectionDetails _selectionDetails; - - /// Specifies the index of the data point that needs to be selected initially while - /// rendering a chart. - /// ignore: unused_element - void _selectedDataPointIndex( - CartesianSeriesRenderer seriesRenderer, List selectedData) => - _selectionDetails.selectionRenderer - ?.selectedDataPointIndex(seriesRenderer, selectedData); - - /// Gets the selected item color of a Cartesian series. - @override - Paint getSelectedItemFill(Paint paint, int seriesIndex, int pointIndex, - List selectedSegments) => - paint; - - /// Gets the unselected item color of a Cartesian series. - @override - Paint getUnselectedItemFill(Paint paint, int seriesIndex, int pointIndex, - List unselectedSegments) => - paint; - - /// Gets the selected item border color of a Cartesian series. - @override - Paint getSelectedItemBorder(Paint paint, int seriesIndex, int pointIndex, - List selectedSegments) => - paint; - - /// Gets the unselected item border color of a Cartesian series. - @override - Paint getUnselectedItemBorder(Paint paint, int seriesIndex, int pointIndex, - List unselectedSegments) => - paint; - - /// Gets the selected item color of a circular series. - @override - Color getCircularSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List selectedRegions) => - color; - - /// Gets the unselected item color of a circular series. - @override - Color getCircularUnSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List unselectedRegions) => - color; - - /// Gets the selected item border color of a circular series. - @override - Color getCircularSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List selectedRegions) => - color; - - /// Gets the unselected item border color of a circular series. - @override - Color getCircularUnSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List unselectedRegions) => - color; - - /// Performs the double-tap action on the chart. - @override - void onDoubleTap(double xPos, double yPos) => - _selectionDetails.selectionRenderer?.performSelection(Offset(xPos, yPos)); - - /// Performs the long press action on the chart. - @override - void onLongPress(double xPos, double yPos) => - _selectionDetails.selectionRenderer?.performSelection(Offset(xPos, yPos)); - - /// Performs the touch-down action on the chart. - @override - void onTouchDown(double xPos, double yPos) => - _selectionDetails.selectionRenderer?.performSelection(Offset(xPos, yPos)); -} - -/// Holds the properties of the selection behavior renderer. -class SelectionDetails { - /// Argument constructor for SelectionDetails class. - SelectionDetails(this.selectionBehavior, this.chart, this.stateProperties, - this.selectionBehaviorRenderer); - - /// Specifies the chart instance. - final dynamic chart; - - /// Holds the state properties value. - final dynamic stateProperties; - - /// Holds the value of selection behavior. - // ignore: deprecated_member_use_from_same_package - final dynamic selectionBehavior; - - /// Holds the selection renderer value. - SelectionRenderer? selectionRenderer; - - /// Holds the selection behavior renderer instance. - final SelectionBehaviorRenderer selectionBehaviorRenderer; - - /// Method to select the range. - void selectRange() { - bool isSelect = false; - final SeriesRendererDetails seriesRendererDetails = - selectionRenderer!.seriesRendererDetails; - final CartesianStateProperties stateProperties = - selectionRenderer!.stateProperties; - if (selectionBehavior.enable == true && - selectionBehavior.selectionController != null) { - selectionBehavior.selectionController.addListener(() { - stateProperties.isRangeSelectionSlider = true; - selectionRenderer!.selectedSegments.clear(); - selectionRenderer!.unselectedSegments?.clear(); - final dynamic start = selectionBehavior.selectionController.start; - final dynamic end = selectionBehavior.selectionController.end; - for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { - final num xValue = seriesRendererDetails.dataPoints[i].xValue; - isSelect = start is DateTime - ? (xValue >= start.millisecondsSinceEpoch && - xValue <= end.millisecondsSinceEpoch) - : (xValue >= start && xValue <= end); - - isSelect - ? selectionRenderer!.selectedSegments - .add(seriesRendererDetails.segments[i]) - : selectionRenderer!.unselectedSegments - ?.add(seriesRendererDetails.segments[i]); - } - selectionRenderer! - .selectedSegmentsColors(selectionRenderer!.selectedSegments); - selectionRenderer! - .unselectedSegmentsColors(selectionRenderer!.unselectedSegments!); - - for (final CartesianSeriesRenderer _seriesRenderer - in stateProperties.chartSeries.visibleSeriesRenderers) { - ValueNotifier( - SeriesHelper.getSeriesRendererDetails(_seriesRenderer) - .repaintNotifier - .value++); - } - }); - } - if (stateProperties.renderingDetails.initialRender! == true) { - stateProperties.isRangeSelectionSlider = false; - } - selectionRenderer!.stateProperties = stateProperties; - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the selection details instance from its renderer. -class SelectionHelper { - /// Returns the selection details instance from its renderer. - static SelectionDetails getRenderingDetails( - SelectionBehaviorRenderer renderer) { - return renderer._selectionDetails; - } - - /// Method to set the selection behavior renderer. - static void setSelectionBehaviorRenderer(SelectionBehavior selectionBehavior, - SelectionBehaviorRenderer selectionBehaviorRenderer) { - selectionBehavior._selectionBehaviorRenderer = selectionBehaviorRenderer; - } -} 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 deleted file mode 100644 index 03b595b17..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart +++ /dev/null @@ -1,914 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; - -import '../../chart/axis/axis.dart'; -import '../../chart/axis/category_axis.dart'; -import '../../chart/axis/datetime_category_axis.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/chart_series/series_renderer_properties.dart'; -import '../../chart/chart_series/xy_data_series.dart'; -import '../../chart/utils/helper.dart'; -import '../rendering_details.dart'; -import 'tooltip_rendering_details.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Customizes the tooltip. -/// -/// This class provides options for customizing the properties of the tooltip. -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, - this.color, - this.header, - this.format, - this.builder, - this.shadowColor}) - : animationDuration = animationDuration ?? 350, - textAlignment = textAlignment ?? ChartAlignment.center, - textStyle = textStyle ?? const TextStyle(fontSize: 12), - activationMode = activationMode ?? ActivationMode.singleTap, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0, - duration = duration ?? 3000, - enable = enable ?? false, - opacity = opacity ?? 1, - shouldAlwaysShow = shouldAlwaysShow ?? false, - canShowMarker = canShowMarker ?? true, - tooltipPosition = tooltipPosition ?? TooltipPosition.auto, - elevation = elevation ?? 2.5, - decimalPlaces = decimalPlaces ?? 3, - shared = shared ?? false; - - /// Toggles the visibility of the tooltip. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final bool enable; - - /// Color of the tooltip. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// color: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final Color? color; - - /// Header of the tooltip. By default, the series name will be displayed in the header. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// header: 'Default' - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final String? header; - - /// Opacity of the tooltip. - /// - /// The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// opacity: 0.7 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final double opacity; - - /// Customizes the tooltip text. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// textStyle: TextStyle( - /// color: Colors.green - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final TextStyle textStyle; - - /// Specifies the number decimals to be displayed in tooltip text. - /// - /// Defaults to `3`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// decimalPlaces: 1 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - 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, and series name values in the - /// tooltip by formatting them. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// format:'point.y %' - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final String? format; - - /// Duration for animating the tooltip. - /// - /// Defaults to `350`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// animationDuration: 1000 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final int animationDuration; - - /// Toggles the visibility of the marker in the tooltip. - /// - /// Defaults to `true`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// canShowMarker: false - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final bool canShowMarker; - - /// Gesture for activating the tooltip. - /// - /// Tooltip can be activated in tap, double tap, and long press. - /// - /// Defaults to `ActivationMode.tap`. - /// - /// Also refer [ActivationMode]. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// activationMode: ActivationMode.doubleTap - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final ActivationMode activationMode; - - /// Border color of the tooltip. - /// - /// Defaults to `Colors.transparent`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final Color borderColor; - - /// Border width of the tooltip. - /// - /// Defaults to `0`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// borderColor: Colors.red, - /// borderWidth: 3 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final double borderWidth; - - /// Builder of the tooltip. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// builder: (dynamic data, dynamic point, dynamic series, int pointIndex, int seriesIndex) { - /// return Container( - /// height: 50, - /// width: 100, - /// decoration: const BoxDecoration( - /// color: Color.fromRGBO(66, 244, 164, 1), - /// child: Text('$pointIndex') - /// ) - /// ); - /// } - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - ///``` - final ChartWidgetBuilder? builder; - - /// Color of the tooltip shadow. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// shadowColor: Colors.green - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final Color? shadowColor; - - /// Elevation of the tooltip. - /// - /// Defaults to `2.5`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// elevation: 10 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - 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`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// shouldAlwaysShow: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final bool shouldAlwaysShow; - - /// Duration for displaying the tooltip. - /// - /// Defaults to `3000`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// duration: 1000 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final double duration; - - /// Alignment of the text in the tooltip. - /// - /// Defaults to `ChartAlignment.center`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// textAlignment : ChartAlignment.near - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final ChartAlignment textAlignment; - - /// Show tooltip at tapped position. - /// - /// Defaults to `TooltipPosition.auto`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// tooltipPosition: TooltipPosition.pointer - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final TooltipPosition tooltipPosition; - - /// Share the tooltip with same index points. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late TooltipBehavior tooltipBehavior; - /// - /// void initState() { - /// tooltipBehavior = TooltipBehavior( - /// enable: true, - /// shared: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// tooltipBehavior: tooltipBehavior - /// ); - /// } - /// ``` - final bool shared; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TooltipBehavior && - other.textStyle == textStyle && - other.activationMode == activationMode && - other.animationDuration == animationDuration && - other.enable == enable && - other.opacity == opacity && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.duration == duration && - other.shouldAlwaysShow == shouldAlwaysShow && - other.elevation == elevation && - other.canShowMarker == canShowMarker && - other.textAlignment == textAlignment && - other.decimalPlaces == decimalPlaces && - other.tooltipPosition == tooltipPosition && - other.shared == shared && - other.color == color && - other.header == header && - other.format == format && - other.builder == builder && - other.shadowColor == shadowColor; - } - - @override - int get hashCode { - final List values = [ - textStyle, - activationMode, - animationDuration, - enable, - opacity, - borderColor, - borderWidth, - duration, - shouldAlwaysShow, - elevation, - canShowMarker, - textAlignment, - decimalPlaces, - tooltipPosition, - shared, - color, - header, - format, - builder, - shadowColor - ]; - return hashList(values); - } - - /// Holds the state properties value. - dynamic _stateProperties; - - /// Displays the tooltip at the specified x and y-positions. - /// - /// * x & y - logical pixel values to position the tooltip. - void showByPixel(double x, double y) { - if (_stateProperties != null) { - final TooltipRenderingDetails renderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - renderingDetails.internalShowByPixel(x, y); - } - } - - /// Displays the tooltip at the specified x and y-values. - /// - /// - /// *x & y - x & y point values at which the tooltip needs to be shown. - /// - /// * 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]) { - if (_stateProperties != null && - _stateProperties.chart is SfCartesianChart) { - final dynamic chart = _stateProperties.chart; - final RenderingDetails renderingDetails = - _stateProperties.renderingDetails; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - renderingDetails.tooltipBehaviorRenderer; - bool? isInsidePointRegion = false; - ChartAxisRendererDetails? xAxisDetails, yAxisDetails; - if (xAxisName != null && yAxisName != null) { - for (final ChartAxisRenderer axisRenderer - in _stateProperties.chartAxis.axisRenderersCollection) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails(axisRenderer); - if (axisDetails.name == xAxisName) { - xAxisDetails = axisDetails; - } else if (axisDetails.name == yAxisName) { - yAxisDetails = axisDetails; - } - } - } else { - xAxisDetails = _stateProperties.chartAxis.primaryXAxisDetails; - yAxisDetails = _stateProperties.chartAxis.primaryYAxisDetails; - } - final ChartLocation position = calculatePoint( - (x is DateTime && - (xAxisDetails! is DateTimeCategoryAxisDetails) == false) - ? x.millisecondsSinceEpoch - : ((x is DateTime && xAxisDetails! is DateTimeCategoryAxisDetails) - ? (xAxisDetails as DateTimeCategoryAxisDetails) - .labels - .indexOf(xAxisDetails.dateFormat.format(x)) - : ((x is String && xAxisDetails is CategoryAxisDetails) - ? xAxisDetails.labels.indexOf(x) - : x)), - y, - xAxisDetails!, - yAxisDetails!, - _stateProperties.requireInvertedAxis, - null, - _stateProperties.chartAxis.axisClipRect); - for (int i = 0; - i < _stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (seriesRendererDetails.visible! == true && - seriesRendererDetails.series.enableTooltip == true && - seriesRendererDetails.regionalData != null) { - final double padding = (seriesRendererDetails.seriesType == - 'bubble' || - seriesRendererDetails.seriesType == 'scatter' || - seriesRendererDetails.seriesType.contains('column') == true || - seriesRendererDetails.seriesType.contains('bar') == true) - ? 0 - : 15; // regional padding to detect smooth touch - seriesRendererDetails.regionalData! - .forEach((dynamic regionRect, dynamic values) { - final Rect region = regionRect[0]; - final Rect paddedRegion = Rect.fromLTRB( - region.left - padding, - region.top - padding, - region.right + padding, - region.bottom + padding); - if (paddedRegion.contains(Offset(position.x, position.y))) { - isInsidePointRegion = true; - } - }); - } - } - if (renderingDetails.tooltipBehaviorRenderer._tooltipRenderingDetails - .tooltipTemplate == - null) { - final SfTooltipState? tooltipState = - tooltipBehaviorRenderer._tooltipRenderingDetails.chartTooltipState; - if (isInsidePointRegion ?? false) { - tooltipBehaviorRenderer._tooltipRenderingDetails - .showTooltip(position.x, position.y); - } else { - // To show tooltip when the position is out of point region. - tooltipBehaviorRenderer._tooltipRenderingDetails.show = true; - tooltipState?.needMarker = false; - renderingDetails.tooltipBehaviorRenderer._tooltipRenderingDetails - .showChartAreaTooltip(Offset(position.x, position.y), - xAxisDetails, yAxisDetails, chart); - } - } - tooltipBehaviorRenderer._tooltipRenderingDetails.isInteraction = false; - } - } - - /// Displays the tooltip at the specified series and point index. - /// - /// * seriesIndex - index of the series for which the pointIndex is specified - /// - /// * pointIndex - index of the point for which the tooltip should be shown - void showByIndex(int seriesIndex, int pointIndex) { - if (_stateProperties != null) { - final TooltipRenderingDetails renderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - renderingDetails.internalShowByIndex(seriesIndex, pointIndex); - } - } - - /// Hides the tooltip if it is displayed. - void hide() { - if (_stateProperties != null) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - // ignore: unnecessary_null_comparison - if (tooltipBehaviorRenderer != null) { - tooltipBehaviorRenderer._tooltipRenderingDetails.showLocation = null; - tooltipBehaviorRenderer._tooltipRenderingDetails.show = false; - } - if (builder != null) { - // Hides tooltip template. - tooltipBehaviorRenderer._tooltipRenderingDetails.chartTooltipState - ?.hide(hideDelay: 0); - } else { - // Hides default tooltip. - tooltipBehaviorRenderer._tooltipRenderingDetails.currentTooltipValue = - tooltipBehaviorRenderer._tooltipRenderingDetails.prevTooltipValue = - null; - - tooltipBehaviorRenderer._tooltipRenderingDetails.chartTooltipState - ?.hide(hideDelay: 0); - } - } - } -} - -/// Tooltip behavior renderer class for mutable fields and methods. -class TooltipBehaviorRenderer with ChartBehavior { - /// Creates an argument constructor for Tooltip renderer class. - TooltipBehaviorRenderer(this._stateProperties) { - _tooltipRenderingDetails = TooltipRenderingDetails(_stateProperties); - } - - final dynamic _stateProperties; - - /// Specifies the rendering details of tooltip. - late TooltipRenderingDetails _tooltipRenderingDetails; - - /// Hides the Mouse tooltip if it is displayed. - void _hideMouseTooltip() => _tooltipRenderingDetails.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) => - _tooltipRenderingDetails.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) => - _tooltipRenderingDetails.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) => - _tooltipRenderingDetails.tooltipBehavior.showByPixel(xPos, yPos); - - /// 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 - } - - /// 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) => - _tooltipRenderingDetails.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) => - _tooltipRenderingDetails.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 (_tooltipRenderingDetails.renderBox != null && - _tooltipRenderingDetails.tooltipBehavior.builder != null) { - _hideMouseTooltip(); - } else if (_tooltipRenderingDetails.tooltipTemplate != null) { - //ignore: unused_local_variable - _tooltipRenderingDetails.timer?.cancel(); - _tooltipRenderingDetails.timer = Timer( - Duration( - milliseconds: - _tooltipRenderingDetails.tooltipBehavior.duration.toInt()), - () {}); - } - } -} - -/// 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; - - /// Position of the pointer when the tooltip position mode is set as pointer. - Offset? pointerPosition; - - @override - //ignore: hash_and_equals - bool operator ==(Object other) { - if (other is TooltipValue) { - return seriesIndex == other.seriesIndex && - pointIndex == other.pointIndex && - outlierIndex == other.outlierIndex && - (pointerPosition == null || pointerPosition == other.pointerPosition); - } else { - return false; - } - } -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class to get the cross hair rendering details instance from its renderer. -class TooltipHelper { - /// Returns the cross hair rendering details instance from its renderer. - static TooltipRenderingDetails getRenderingDetails( - TooltipBehaviorRenderer renderer) { - return renderer._tooltipRenderingDetails; - } - - // /// Returns the Cartesian state properties from its instance - // static CartesianStateProperties getStateProperties( - // CrosshairBehavior crosshairBehavior) { - // return crosshairBehavior._stateProperties; - // } - - /// Method to set the Cartesian state properties. - static void setStateProperties( - TooltipBehavior tooltipBehavior, dynamic stateProperties) { - tooltipBehavior._stateProperties = stateProperties; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart deleted file mode 100644 index 8803b43f6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart +++ /dev/null @@ -1,1818 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/axis/multi_level_labels.dart' - show AxisMultiLevelLabel; -import 'package:syncfusion_flutter_charts/src/chart/chart_segment/chart_segment.dart'; -import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; -import 'package:syncfusion_flutter_charts/src/chart/common/cartesian_state_properties.dart'; -import 'package:syncfusion_flutter_charts/src/circular_chart/base/circular_state_properties.dart'; -import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; - -import '../../../charts.dart'; -import '../../chart/axis/axis.dart'; -import '../../chart/axis/category_axis.dart'; -import '../../chart/axis/datetime_axis.dart'; -import '../../chart/axis/datetime_category_axis.dart'; -import '../../chart/axis/logarithmic_axis.dart'; -import '../../chart/axis/numeric_axis.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/utils/helper.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../../circular_chart/utils/helper.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../rendering_details.dart'; -import 'tooltip.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; - -/// Represents the tooltip rendering details. -class TooltipRenderingDetails { - /// Creates an instance of tooltip rendering details. - TooltipRenderingDetails(this._stateProperties); - final dynamic _stateProperties; - - /// Gets the instance of tooltip behavior. - TooltipBehavior get tooltipBehavior => - _stateProperties.chart.tooltipBehavior as TooltipBehavior; - - /// Specifies the chart tooltip value. - SfTooltip? chartTooltip; - - /// Specifies whether interaction is done. - bool isInteraction = false; - - /// Specifies the tooltip is hovered. - bool isHovering = false, _mouseTooltip = false; - - /// Specifies the toopltip template. - Widget? tooltipTemplate; - - /// Specifies the value of tooltip render box. - TooltipRenderBox? get renderBox => chartTooltipState?.renderBox; - - /// Specifies the chart tooltip state. - SfTooltipState? get chartTooltipState { - State? state; - if (chartTooltip != null) { - state = (chartTooltip?.key as GlobalKey).currentState; - } - //ignore: avoid_as - return state != null ? state as SfTooltipState : null; - } - - List _textValues = []; - List _seriesRendererCollection = - []; - - /// Specifies the previous tooltip value. - TooltipValue? prevTooltipValue; - TooltipValue? _presentTooltipValue; - - /// Specifies the current tooltip value. - TooltipValue? currentTooltipValue; - - SeriesRendererDetails? _seriesRendererDetails; - - /// Specifies the value of current series and the current data point. - dynamic currentSeriesDetails, _dataPoint; - - /// Specifies tooltip point index. - int? pointIndex; - - /// Holds the series index value. - late int seriesIndex; - late Color _markerColor; - late DataMarkerType _markerType; - - /// Specifies the tooltip timer. - Timer? timer; - - /// Specifies whether the tooltip is shown. - bool show = false; - - /// Holds the offset value of show location. - Offset? showLocation; - - /// Holds the value of tooltip bounds. - Rect? tooltipBounds; - String? _stringVal, _header; - set _stringValue(String? value) { - _stringVal = value; - } - - /// To render chart tooltip. - // ignore:unused_element - void _renderTooltipView(Offset position) { - if (_stateProperties.chart is SfCartesianChart) { - _renderCartesianChartTooltip(position); - } else if (_stateProperties.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; - _mouseTooltip = false; - isHovering ? _showMouseTooltip(x, y) : showTooltipView(x, y); - } - } - - /// To show the chart tooltip. - void showTooltipView(double x, double y) { - if (tooltipBehavior.enable && - renderBox != null && - _stateProperties.animationCompleted == true) { - _renderTooltipView(Offset(x, y)); - if (_presentTooltipValue != null && - tooltipBehavior.tooltipPosition != TooltipPosition.pointer) { - chartTooltipState!.boundaryRect = tooltipBounds!; - if (showLocation != null) { - chartTooltipState?.needMarker = - _stateProperties.chart is SfCartesianChart; - _resolveLocation(); - chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: showLocation, - duration: _stateProperties.isTooltipOrientationChanged == true - ? 0 - : tooltipBehavior.animationDuration); - } - } else { - if (tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - ((_stateProperties.chart is SfCartesianChart) == false || - currentSeriesDetails.isRectSeries == true)) { - _presentTooltipValue?.pointerPosition = showLocation; - chartTooltipState!.boundaryRect = tooltipBounds!; - if (showLocation != null) { - chartTooltipState?.needMarker = - _stateProperties.chart is SfCartesianChart; - chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: showLocation, - duration: _stateProperties.isTooltipOrientationChanged == true - ? 0 - : tooltipBehavior.animationDuration); - } - currentTooltipValue = _presentTooltipValue; - } - } - assert( - // ignore: unnecessary_null_comparison - !(tooltipBehavior.duration != null) || tooltipBehavior.duration >= 0, - 'The duration time for the tooltip must not be less than 0.'); - if (!tooltipBehavior.shouldAlwaysShow) { - show = false; - currentTooltipValue = _presentTooltipValue = null; - if (chartTooltipState != null && renderBox != null) { - if (_stateProperties.isTooltipOrientationChanged == false) { - // Cancelled timer as we have used timer in chart now. - if (timer != null) { - timer?.cancel(); - } - timer = Timer( - Duration(milliseconds: tooltipBehavior.duration.toInt()), () { - chartTooltipState?.hide(hideDelay: 0); - _stateProperties.isTooltipHidden = true; - if (_stateProperties.isTooltipOrientationChanged == true) { - _stateProperties.isTooltipOrientationChanged = false; - } - }); - } - } - } - } - } - - /// This method resolves the position issue when the markerPoint is residing. - /// out of the axis cliprect - void _resolveLocation() { - if (_stateProperties.chart is SfCartesianChart && - tooltipBehavior.tooltipPosition == TooltipPosition.auto && - (_seriesRendererDetails!.isRectSeries == true || - _seriesRendererDetails!.seriesType.contains('bubble') == true || - _seriesRendererDetails!.seriesType.contains('candle') == true || - _seriesRendererDetails!.seriesType.contains('boxandwhisker') == - true || - _seriesRendererDetails!.seriesType.contains('waterfall') == true)) { - Offset position = showLocation!; - final CartesianStateProperties cartesianStateProperties = - _stateProperties as CartesianStateProperties; - final Rect bounds = cartesianStateProperties.chartAxis.axisClipRect; - if (!_isPointWithInRect(position, bounds)) { - if (position.dy < bounds.top) { - position = Offset(position.dx, bounds.top); - } - if (position.dx < bounds.left) { - position = Offset(bounds.left, position.dy); - } else if (position.dx > bounds.right) { - position = Offset(bounds.right, position.dy); - } - } - showLocation = position; - } - } - - /// This method shows the tooltip for any logical pixel outside point region. - //ignore: unused_element - void showChartAreaTooltip( - Offset position, - ChartAxisRendererDetails xAxisDetails, - ChartAxisRendererDetails yAxisDetails, - dynamic chart) { - showLocation = position; - final CartesianStateProperties cartesianStateProperties = - _stateProperties as CartesianStateProperties; - final ChartAxis xAxis = xAxisDetails.axis, yAxis = yAxisDetails.axis; - if (tooltipBehavior.enable && - renderBox != null && - cartesianStateProperties.animationCompleted == true) { - tooltipBounds = cartesianStateProperties.chartAxis.axisClipRect; - chartTooltipState!.boundaryRect = tooltipBounds!; - if (_isPointWithInRect( - position, cartesianStateProperties.chartAxis.axisClipRect)) { - currentSeriesDetails = SeriesHelper.getSeriesRendererDetails( - cartesianStateProperties.chartSeries.visibleSeriesRenderers[0]); - renderBox!.normalPadding = 5; - renderBox!.inversePadding = 5; - _header = ''; - dynamic xValue = pointToXValue( - cartesianStateProperties.requireInvertedAxis, - xAxisDetails.axisRenderer, - xAxisDetails.bounds, - position.dx - - (cartesianStateProperties.chartAxis.axisClipRect.left + - xAxis.plotOffset), - position.dy - - (cartesianStateProperties.chartAxis.axisClipRect.top + - xAxis.plotOffset)); - dynamic yValue = pointToYValue( - cartesianStateProperties.requireInvertedAxis, - yAxisDetails.axisRenderer, - yAxisDetails.bounds, - position.dx - - (cartesianStateProperties.chartAxis.axisClipRect.left + - yAxis.plotOffset), - position.dy - - (cartesianStateProperties.chartAxis.axisClipRect.top + - yAxis.plotOffset)); - if (xAxisDetails is DateTimeAxisDetails) { - final DateTimeAxis xAxis = xAxisDetails.axis as DateTimeAxis; - final num interval = xAxisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (xAxisDetails.visibleLabels.isNotEmpty) - ? xAxisDetails - .visibleLabels[xAxisDetails.visibleLabels.length - 1].value - : interval; - xValue = (xAxis.dateFormat ?? - getDateTimeLabelFormat(xAxisDetails.axisRenderer, - interval.toInt(), prevInterval.toInt())) - .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); - } else if (xAxisDetails is DateTimeCategoryAxisDetails) { - final DateTimeCategoryAxis xAxis = - xAxisDetails.axis as DateTimeCategoryAxis; - final num interval = xAxisDetails.visibleRange!.minimum.ceil(); - final num prevInterval = (xAxisDetails.visibleLabels.isNotEmpty) - ? xAxisDetails - .visibleLabels[xAxisDetails.visibleLabels.length - 1].value - : interval; - xValue = (xAxis.dateFormat ?? - getDateTimeLabelFormat(xAxisDetails.axisRenderer, - interval.toInt(), prevInterval.toInt())) - .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); - } else if (xAxisDetails is CategoryAxisDetails) { - xValue = xAxisDetails.visibleLabels[xValue.toInt()].text; - } else if (xAxisDetails is NumericAxisDetails) { - xValue = xValue.toStringAsFixed(2).contains('.00') == true - ? xValue.floor() - : xValue.toStringAsFixed(2); - } - if (yAxisDetails is NumericAxisDetails || - yAxisDetails is LogarithmicAxisDetails) { - yValue = yValue.toStringAsFixed(2).contains('.00') == true - ? yValue.floor() - : yValue.toStringAsFixed(2); - } - _stringValue = ' $xValue : $yValue '; - showLocation = position; - } - if (_isPointWithInRect( - position, cartesianStateProperties.chartAxis.axisClipRect)) { - if (showLocation != null && - _stringVal != null && - tooltipBounds != null) { - chartTooltipState?.needMarker = false; - chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: showLocation, - duration: _stateProperties.isTooltipOrientationChanged == true - ? 0 - : tooltipBehavior.animationDuration); - } - } - - if (!tooltipBehavior.shouldAlwaysShow) { - show = false; - if (chartTooltipState != null && renderBox != null) { - chartTooltipState?.hide(); - } - } - } - } - - /// Method to show the tooltip with template. - void showTemplateTooltip(Offset position, [dynamic xValue, dynamic yValue]) { - _stateProperties.isTooltipHidden = false; - final CartesianStateProperties cartesianStateProperties = - _stateProperties as CartesianStateProperties; - final dynamic chart = cartesianStateProperties.chart; - _presentTooltipValue = null; - tooltipBounds = cartesianStateProperties.chartAxis.axisClipRect; - dynamic series; - double yPadding = 0; - if (_isPointWithInRect( - position, cartesianStateProperties.chartAxis.axisClipRect) && - cartesianStateProperties.animationCompleted == true) { - int? seriesIndex; - int outlierIndex = -1; - bool isTooltipRegion = false; - if (!isHovering) { - // Assigning null for the previous and current tooltip values in case of mouse not hovering. - prevTooltipValue = null; - currentTooltipValue = null; - } - for (int i = 0; - i < - cartesianStateProperties - .chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - cartesianStateProperties.chartSeries.visibleSeriesRenderers[i]); - series = seriesRendererDetails.series; - - int j = 0; - if (seriesRendererDetails.visible! == true && - series.enableTooltip == true && - seriesRendererDetails.regionalData != null) { - seriesRendererDetails.regionalData! - .forEach((dynamic regionRect, dynamic values) { - final bool isTrendLine = values[values.length - 1].contains('true'); - final double padding = ((seriesRendererDetails.seriesType == - 'bubble' || - seriesRendererDetails.seriesType == 'scatter' || - seriesRendererDetails.seriesType.contains('column') == - true || - seriesRendererDetails.seriesType.contains('bar') == - true || - seriesRendererDetails.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]; - - if (outlierRegion != null) { - for (int rectIndex = 0; - rectIndex < outlierRegion.length; - rectIndex++) { - if (outlierRegion[rectIndex].contains(position)) { - paddedRegion = outlierRegion[rectIndex]; - outlierIndex = rectIndex; - } - } - } - - if (paddedRegion.contains(position)) { - seriesIndex = seriesIndex = i; - currentSeriesDetails = seriesRendererDetails; - pointIndex = - seriesRendererDetails.dataPoints.indexOf(regionRect[4]); - Offset tooltipPosition = !(seriesRendererDetails.isRectSeries == - true && - tooltipBehavior.tooltipPosition != TooltipPosition.auto) - ? ((outlierIndex >= 0) - ? regionRect[6][outlierIndex] - : regionRect[1]) - : position; - final List paddingData = getTooltipPaddingData( - seriesRendererDetails, - isTrendLine, - region, - paddedRegion, - tooltipPosition); - yPadding = paddingData[0]!.dy; - tooltipPosition = paddingData[1] ?? tooltipPosition; - showLocation = tooltipPosition; - _seriesRendererDetails = seriesRendererDetails; - renderBox!.normalPadding = - _seriesRendererDetails!.renderer is BubbleSeriesRenderer - ? 0 - : yPadding; - renderBox!.inversePadding = yPadding; - tooltipTemplate = chart.tooltipBehavior.builder( - series.dataSource[j], regionRect[4], series, pointIndex, i); - isTooltipRegion = true; - } - j++; - }); - } - } - if (isHovering && isTooltipRegion) { - prevTooltipValue = currentTooltipValue; - currentTooltipValue = - TooltipValue(seriesIndex, pointIndex!, outlierIndex); - } - final TooltipValue? presentTooltip = _presentTooltipValue; - if (presentTooltip == null || - seriesIndex != presentTooltip.seriesIndex || - outlierIndex != presentTooltip.outlierIndex || - (currentSeriesDetails != null && - currentSeriesDetails.isRectSeries == true && - tooltipBehavior.tooltipPosition != TooltipPosition.auto)) { - // Current point is different than previous one so tooltip re-renders. - if (seriesIndex != null && pointIndex != null) { - _presentTooltipValue = - TooltipValue(seriesIndex, pointIndex!, outlierIndex); - } - - if (isTooltipRegion && tooltipTemplate != null) { - show = isTooltipRegion; - performTooltip(); - if (!isHovering && renderBox != null) { - hideTooltipTemplate(); - } - } - } else { - // Current point is same as previous one so timer is reset and tooltip is not re-rendered. - if (!isHovering) { - hideTooltipTemplate(); - } - } - - if (!isTooltipRegion && - !isInteraction && - chart.series.isNotEmpty == true) { - // To show tooltip template when the position resides outside point region. - final dynamic x = xValue ?? - pointToXValue( - cartesianStateProperties.requireInvertedAxis, - cartesianStateProperties - .chartAxis.primaryXAxisDetails.axisRenderer, - cartesianStateProperties.chartAxis.primaryXAxisDetails.bounds, - position.dx - - (cartesianStateProperties.chartAxis.axisClipRect.left + - chart.primaryXAxis.plotOffset), - position.dy - - (cartesianStateProperties.chartAxis.axisClipRect.top + - chart.primaryXAxis.plotOffset)); - final dynamic y = yValue ?? - pointToYValue( - cartesianStateProperties.requireInvertedAxis, - cartesianStateProperties - .chartAxis.primaryYAxisDetails.axisRenderer, - cartesianStateProperties.chartAxis.primaryYAxisDetails.bounds, - position.dx - - (cartesianStateProperties.chartAxis.axisClipRect.left + - chart.primaryYAxis.plotOffset), - position.dy - - (cartesianStateProperties.chartAxis.axisClipRect.top + - chart.primaryYAxis.plotOffset)); - renderBox!.normalPadding = 5; - renderBox!.inversePadding = 5; - showLocation = position; - tooltipTemplate = chart.tooltipBehavior.builder( - null, CartesianChartPoint(x, y), null, null, null); - isTooltipRegion = true; - show = isTooltipRegion; - performTooltip(); - } - if (!isTooltipRegion) { - hideTooltipTemplate(); - } - } - isInteraction = false; - } - - /// Tooltip show by Index. - void internalShowByIndex(int seriesIndex, int pointIndex) { - final dynamic chart = _stateProperties.chart; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - dynamic x, y; - if (chart is SfCartesianChart) { - if (validIndex(pointIndex, seriesIndex, chart)) { - final CartesianSeriesRenderer currentSeriesRenderer = - _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - final SeriesRendererDetails cartesianSeriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(_stateProperties - .chartSeries.visibleSeriesRenderers[seriesIndex]); - if (pointIndex != null && - pointIndex.abs() < - cartesianSeriesRendererDetails.dataPoints.length) { - if (cartesianSeriesRendererDetails.visible! == true) { - if ((currentSeriesRenderer is BarSeriesRenderer || - currentSeriesRenderer is StackedBarSeriesRenderer || - currentSeriesRenderer is StackedBar100SeriesRenderer && - !chart.isTransposed) || - (currentSeriesRenderer is ColumnSeriesRenderer || - currentSeriesRenderer is StackedColumnSeriesRenderer || - currentSeriesRenderer is StackedColumn100SeriesRenderer && - chart.isTransposed) || - currentSeriesRenderer is RangeColumnSeriesRenderer && - chart.isTransposed || - currentSeriesRenderer is BoxAndWhiskerSeriesRenderer) { - if (currentSeriesRenderer is BoxAndWhiskerSeriesRenderer) { - x = cartesianSeriesRendererDetails - .dataPoints[pointIndex].region?.centerRight.dx; - y = cartesianSeriesRendererDetails - .dataPoints[pointIndex].region?.centerRight.dy; - } else { - x = cartesianSeriesRendererDetails - .dataPoints[pointIndex].region?.topCenter.dx; - y = cartesianSeriesRendererDetails - .dataPoints[pointIndex].region?.topCenter.dy; - } - } else { - x = cartesianSeriesRendererDetails - .dataPoints[pointIndex].markerPoint!.x; - y = cartesianSeriesRendererDetails - .dataPoints[pointIndex].markerPoint!.y; - } - } - } - } - if (x != null && y != null && chart.series[seriesIndex].enableTooltip) { - if (chart.tooltipBehavior.builder != null) { - tooltipRenderingDetails.showTemplateTooltip(Offset(x, y)); - } else if (chart.series[seriesIndex].enableTooltip) { - tooltipRenderingDetails.showTooltip(x, y); - } - } - } else if (chart is SfCircularChart) { - if (chart.tooltipBehavior.builder != null && - seriesIndex < chart.series.length && - pointIndex < - _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .dataPoints.length && - chart.series[seriesIndex].enableTooltip) { - // To show the tooltip template when the provided indices are valid. - _stateProperties.circularArea - ._showCircularTooltipTemplate(seriesIndex, pointIndex); - } else if (chart.tooltipBehavior.builder == null && - _stateProperties.animationCompleted == true && - pointIndex >= 0 && - (pointIndex + 1 <= - _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .renderPoints.length)) { - final ChartPoint chartPoint = _stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex].renderPoints[pointIndex]; - if (chartPoint.isVisible) { - final Offset position = degreeToPoint( - chartPoint.midAngle!, - (chartPoint.innerRadius! + chartPoint.outerRadius!) / 2, - chartPoint.center!); - x = position.dx; - y = position.dy; - tooltipRenderingDetails.showTooltip(x, y); - } - } - } else if (pointIndex != null && // ignore: unnecessary_null_comparison - pointIndex < - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints.length) { - // This shows the tooltip for triangular type of charts (funnel and pyramid). - if (chart.tooltipBehavior.builder == null) { - _stateProperties.tooltipPointIndex = pointIndex; - final Offset? position = _stateProperties.chartSeries - .visibleSeriesRenderers[0].dataPoints[pointIndex].region?.center; - x = position?.dx; - y = position?.dy; - tooltipRenderingDetails.showTooltip(x, y); - } else { - if (chart is SfFunnelChart && - _stateProperties.animationCompleted == true) { - _stateProperties.funnelplotArea.showFunnelTooltipTemplate(pointIndex); - } else if (chart is SfPyramidChart && - _stateProperties.animationCompleted == true) { - _stateProperties.chartPlotArea.showPyramidTooltipTemplate(pointIndex); - } - } - } - tooltipRenderingDetails.isInteraction = false; - } - - /// Tooltip show by pixel. - void internalShowByPixel(double x, double y) { - _stateProperties.isTooltipHidden = false; - final dynamic chart = _stateProperties.chart; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - bool? isInsidePointRegion; - String text = ''; - String trimmedText = ''; - Offset? axisLabelPosition; - if (chart is SfCartesianChart) { - _stateProperties.requireAxisTooltip = false; - for (int i = 0; - i < _stateProperties.chartAxis.axisRenderersCollection.length; - i++) { - final ChartAxisRendererDetails axisDetails = - AxisHelper.getAxisRendererDetails( - _stateProperties.chartAxis.axisRenderersCollection[i]); - final List labels = axisDetails.visibleLabels; - for (int k = 0; k < labels.length; k++) { - if (axisDetails.axis.isVisible == true && - AxisHelper.getLabelRegion(labels[k]) != null && - AxisHelper.getLabelRegion(labels[k])!.contains(Offset(x, y))) { - _stateProperties.requireAxisTooltip = true; - text = labels[k].text; - trimmedText = labels[k].renderText ?? ''; - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue; - axisLabelPosition = AxisHelper.getLabelRegion(labels[k])!.center; - // -3 to indicate axis tooltip - tooltipRenderingDetails.currentTooltipValue = - TooltipValue(null, k, 0); - } - } - if (axisDetails.visibleAxisMultiLevelLabels.isNotEmpty) { - final List multiLabelList = - axisDetails.visibleAxisMultiLevelLabels; - for (int k = 0; k < multiLabelList.length; k++) { - if (axisDetails.axis.isVisible == true && - multiLabelList[k].multiLabelRegion != null) { - final Rect labelRect = multiLabelList[k].multiLabelRegion!; - final Offset rectCenter = labelRect.center; - final Rect rectRegion = Rect.fromLTWH( - rectCenter.dx - labelRect.width.abs() / 2, - rectCenter.dy - labelRect.height.abs() / 2, - labelRect.width.abs(), - labelRect.height.abs()); - if (rectRegion.contains(Offset(x, y))) { - _stateProperties.requireAxisTooltip = true; - text = multiLabelList[k].text!; - trimmedText = multiLabelList[k].renderText ?? ''; - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue; - axisLabelPosition = multiLabelList[k].multiLabelRegion!.center; - tooltipRenderingDetails.currentTooltipValue = - TooltipValue(null, k, 0); - } - } - } - } - } - } - if (chart is SfCartesianChart && - _stateProperties.requireAxisTooltip == false) { - for (int i = 0; - i < _stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - _stateProperties.chartSeries.visibleSeriesRenderers[i]); - if (seriesRendererDetails.visible! == true && - seriesRendererDetails.series.enableTooltip == true && - seriesRendererDetails.regionalData != null) { - final String seriesType = seriesRendererDetails.seriesType; - final double padding = (seriesType == 'bubble' || - seriesType == 'scatter' || - seriesType.contains('column') || - seriesType.contains('bar')) - ? 0 - : tooltipRenderingDetails.isHovering == true - ? 0 - : 15; // regional padding to detect smooth touch - seriesRendererDetails.regionalData! - .forEach((dynamic regionRect, dynamic values) { - final Rect region = regionRect[0]; - final Rect paddedRegion = Rect.fromLTRB( - region.left - padding, - region.top - padding, - region.right + padding, - region.bottom + padding); - bool outlierTooltip = false; - if (seriesRendererDetails.seriesType == 'boxandwhisker') { - final List? outlierRegion = regionRect[5]; - if (outlierRegion != null) { - for (int rectIndex = 0; - rectIndex < outlierRegion.length; - rectIndex++) { - if (outlierRegion[rectIndex].contains(Offset(x, y))) { - outlierTooltip = true; - break; - } - } - } - } - if (paddedRegion.contains(Offset(x, y)) || outlierTooltip) { - isInsidePointRegion = true; - } - }); - } - } - } - if (chart is SfCartesianChart && - chart.tooltipBehavior.activationMode != ActivationMode.none && - // ignore: unnecessary_null_comparison - x != null && - // ignore: unnecessary_null_comparison - y != null && - _stateProperties.requireAxisTooltip == true) { - final SfTooltipState? tooltipState = - tooltipRenderingDetails.chartTooltipState; - if (trimmedText.contains('...')) { - tooltipRenderingDetails.show = true; - tooltipState?.needMarker = false; - tooltipRenderingDetails.showTrimmedTooltip( - axisLabelPosition!, chart, text); - } else { - tooltipRenderingDetails.show = false; - if (!chart.tooltipBehavior.shouldAlwaysShow) { - if (_stateProperties.isTooltipOrientationChanged == false) { - // Cancelled timer as we have used timer in chart now. - if (timer != null) { - timer?.cancel(); - } - timer = Timer( - Duration(milliseconds: chart.tooltipBehavior.duration.toInt()), - () { - tooltipRenderingDetails.chartTooltipState?.hide(hideDelay: 0); - _stateProperties.isTooltipHidden = true; - if (_stateProperties.isTooltipOrientationChanged == true) { - _stateProperties.isTooltipOrientationChanged = false; - } - }); - } - } - } - } else if (chart is SfCircularChart && - _stateProperties.requireDataLabelTooltip == true) { - final List> renderPoints = - _stateProperties.chartSeries.visibleSeriesRenderers[0].renderPoints; - final SfTooltipState? tooltipState = - tooltipRenderingDetails.chartTooltipState; - for (int i = 0; i < renderPoints.length; i++) { - if (renderPoints[i].labelRect.contains(Offset(x, y)) && - renderPoints[i].trimmedText != null && - renderPoints[i].trimmedText!.contains('...')) { - Offset position; - final num textWidth = measureText( - renderPoints[i].overflowTrimmedText ?? - renderPoints[i].trimmedText!, - _stateProperties.chartSeries.visibleSeriesRenderers[0] - .dataLabelSettingsRenderer.dataLabelSettings.textStyle) - .width; - if (renderPoints[i].dataLabelPosition == Position.left) { - position = Offset(renderPoints[i].labelRect.right - textWidth / 2, - renderPoints[i].labelRect.center.dy); - } else { - position = Offset(renderPoints[i].labelRect.left + textWidth / 2, - renderPoints[i].labelRect.center.dy); - } - if ((chart.tooltipBehavior.enable != true && - _stateProperties - .chartSeries - .visibleSeriesRenderers[0] - .dataLabelSettingsRenderer - .dataLabelSettings - .labelPosition == - ChartDataLabelPosition.inside) || - renderPoints[i].renderPosition == - ChartDataLabelPosition.outside) { - tooltipRenderingDetails.show = true; - tooltipState?.needMarker = false; - tooltipRenderingDetails.showTrimmedTooltip(position, chart, - renderPoints[i].overflowTrimmedText ?? renderPoints[i].text!); - } - } - } - } else if (tooltipRenderingDetails.chartTooltip != null && - // ignore: unnecessary_null_comparison - x != null && - // ignore: unnecessary_null_comparison - y != null) { - final SfTooltipState? tooltipState = - tooltipRenderingDetails.chartTooltipState; - if ((chart is SfCartesianChart) == false || - tooltipRenderingDetails.isInteraction == true || - (isInsidePointRegion ?? false)) { - final bool isHovering = tooltipRenderingDetails.isHovering; - if (isInsidePointRegion == true || - isHovering || - (chart is SfCartesianChart) == false) { - tooltipRenderingDetails.showTooltip(x, y); - } else { - tooltipRenderingDetails.show = false; - if (chart.tooltipBehavior.shouldAlwaysShow == false) { - hide(); - } - } - } else if (tooltipRenderingDetails.renderBox != null) { - tooltipRenderingDetails.show = true; - tooltipState?.needMarker = false; - tooltipRenderingDetails.showChartAreaTooltip( - Offset(x, y), - _stateProperties.chartAxis.primaryXAxisDetails, - _stateProperties.chartAxis.primaryYAxisDetails, - chart); - } - } - if (chart is SfCartesianChart && - chart.tooltipBehavior.builder != null && - x != null && // ignore: unnecessary_null_comparison - // ignore: unnecessary_null_comparison - y != null) { - tooltipRenderingDetails.showTemplateTooltip(Offset(x, y)); - } - // ignore: unnecessary_null_comparison - if (tooltipBehaviorRenderer != null) { - tooltipRenderingDetails.isInteraction = false; - } - } - - /// To hide the tooltip when the timer ends. - void hide() { - if (!tooltipBehavior.shouldAlwaysShow) { - show = false; - currentTooltipValue = _presentTooltipValue = null; - if (chartTooltipState != null && renderBox != null) { - if (_stateProperties.isTooltipOrientationChanged == false) { - // Cancelled timer as we have used timer in chart now. - if (timer != null) { - timer?.cancel(); - } - timer = Timer( - Duration(milliseconds: tooltipBehavior.duration.toInt()), () { - chartTooltipState?.hide(hideDelay: 0); - _stateProperties.isTooltipHidden = true; - if (_stateProperties.isTooltipOrientationChanged == true) { - _stateProperties.isTooltipOrientationChanged = false; - } - }); - } - } - } - } - - /// 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); - // } - // } - - /// To hide tooltip templates. - void hideTooltipTemplate() { - if (tooltipBehavior.shouldAlwaysShow == false) { - show = false; - if (_stateProperties.isTooltipOrientationChanged == false) { - // Cancelled timer as we have used timer in chart now. - if (timer != null) { - timer?.cancel(); - } - timer = - Timer(Duration(milliseconds: tooltipBehavior.duration.toInt()), () { - chartTooltipState?.hide(hideDelay: 0); - _stateProperties.isTooltipHidden = true; - if (_stateProperties.isTooltipOrientationChanged == true) { - _stateProperties.isTooltipOrientationChanged = false; - } - }); - } - prevTooltipValue = null; - currentTooltipValue = null; - _presentTooltipValue = 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 (show && - ((prevTooltipValue == null && currentTooltipValue == null) || - (_stateProperties.chartState is SfCartesianChartState && - (currentSeriesDetails?.isRectSeries ?? false) == true && - 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) { - chartTooltipState!.boundaryRect = tooltipBounds!; - if (tooltipBehavior.tooltipPosition != TooltipPosition.auto) - _presentTooltipValue!.pointerPosition = showLocation; - if (showLocation != null) { - _resolveLocation(); - chartTooltipState?.show( - tooltipData: _presentTooltipValue, - position: showLocation, - duration: _stateProperties.isTooltipOrientationChanged == true - ? 0 - : ((!reRender) - ? tooltipBehavior.animationDuration.toInt() - : 0), - template: tooltipTemplate); - } - } - } - } - - /// To show tooltip on mouse pointer actions. - void _showMouseTooltip(double x, double y) { - if (tooltipBehavior.enable && - renderBox != null && - _stateProperties.animationCompleted == true) { - _renderTooltipView(Offset(x, y)); - if (!_mouseTooltip) { - if (_stateProperties.isTooltipOrientationChanged == false) { - // Cancelled timer as we have used timer in chart now. - if (timer != null) { - timer?.cancel(); - } - timer = Timer( - Duration(milliseconds: tooltipBehavior.duration.toInt()), () { - chartTooltipState?.hide(hideDelay: 0); - _stateProperties.isTooltipHidden = true; - if (_stateProperties.isTooltipOrientationChanged == true) { - _stateProperties.isTooltipOrientationChanged = false; - } - }); - } - - currentTooltipValue = null; - } else { - if (_presentTooltipValue != null && - (currentTooltipValue == null || - tooltipBehavior.tooltipPosition == TooltipPosition.auto)) { - chartTooltipState!.boundaryRect = tooltipBounds!; - if (tooltipBehavior.tooltipPosition != TooltipPosition.auto) - _presentTooltipValue!.pointerPosition = showLocation; - if (showLocation != null) { - chartTooltipState?.needMarker = - _stateProperties.chart is SfCartesianChart; - _resolveLocation(); - chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: showLocation, - duration: _stateProperties.isTooltipOrientationChanged == true - ? 0 - : tooltipBehavior.animationDuration); - } - currentTooltipValue = _presentTooltipValue; - } else if (_presentTooltipValue != null && - currentTooltipValue != null && - tooltipBehavior.tooltipPosition != TooltipPosition.auto && - ((_seriesRendererDetails != null && - // ignore: unnecessary_type_check - _seriesRendererDetails!.renderer - is CartesianSeriesRenderer) == - false || - _seriesRendererDetails!.isRectSeries == true)) { - _presentTooltipValue!.pointerPosition = showLocation; - chartTooltipState!.boundaryRect = tooltipBounds!; - if (showLocation != null) { - chartTooltipState?.needMarker = - _stateProperties.chart is SfCartesianChart; - _resolveLocation(); - chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: showLocation, - duration: 0); - } - } - } - } - } - - /// Method to trigger the tooltip rendering event. - void tooltipRenderingEvent(TooltipRenderArgs args) { - String? header = args.header; - String? stringValue = args.text; - double? x = args.location?.dx, y = args.location?.dy; - String? tooltipHeaderText, tooltipLabelText; - TooltipArgs tooltipArgs; - if (x != null && - y != null && - stringValue != null && - currentSeriesDetails != null && - pointIndex != null) { - final int seriesIndex = _stateProperties.chart is SfCartesianChart - ? SegmentHelper.getSegmentProperties(currentSeriesDetails.segments[0]) - .seriesIndex - : 0; - if ((_stateProperties.chart is SfCartesianChart && - _stateProperties.requireAxisTooltip == false) || - (_stateProperties.chart is SfCartesianChart) == false) { - if (_stateProperties.chart.onTooltipRender != null && - _dataPoint != null && - (_dataPoint.isTooltipRenderEvent ?? false) == false) { - _dataPoint.isTooltipRenderEvent = true; - final bool isCartesian = _stateProperties.chart is SfCartesianChart; - late SeriesRendererDetails seriesRendererDetails; - if (isCartesian) { - seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - _stateProperties - .chartSeries.visibleSeriesRenderers[seriesIndex]); - } - - tooltipArgs = TooltipArgs( - seriesIndex, - isCartesian - ? seriesRendererDetails.dataPoints - : _stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex].dataPoints, - pointIndex, - isCartesian - ? seriesRendererDetails - .visibleDataPoints![pointIndex!].overallDataPointIndex - : pointIndex); - - tooltipArgs.text = stringValue; - tooltipArgs.header = header; - tooltipLabelText = stringValue; - tooltipHeaderText = header; - tooltipArgs.locationX = x; - tooltipArgs.locationY = y; - _stateProperties.chart.onTooltipRender(tooltipArgs); - stringValue = tooltipArgs.text; - header = tooltipArgs.header; - x = tooltipArgs.locationX; - y = tooltipArgs.locationY; - tooltipLabelText = tooltipArgs.text; - tooltipHeaderText = tooltipArgs.header; - _dataPoint.isTooltipRenderEvent = false; - args.text = stringValue!; - args.header = header; - args.location = Offset(x!, y!); - } else if (_stateProperties.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; - _stateProperties.chart.onTooltipRender(tooltipArgs); - args.text = tooltipArgs.text; - args.header = tooltipArgs.header; - args.location = - Offset(tooltipArgs.locationX!, tooltipArgs.locationY!); - } - if (_stateProperties.chart.onTooltipRender != null && - _dataPoint != null) { - stringValue = tooltipLabelText; - header = tooltipHeaderText; - } - } - } - } - - /// To render a chart tooltip for circular series. - void _renderCircularChartTooltip(Offset position) { - final CircularStateProperties circularStateProperties = - _stateProperties as CircularStateProperties; - final SfCircularChart chart = circularStateProperties.chart; - tooltipBounds = circularStateProperties.renderingDetails.chartContainerRect; - final bool isRtl = circularStateProperties.isRtl; - bool isContains = false; - final Region? pointRegion = getCircularPointRegion(chart, position, - circularStateProperties.chartSeries.visibleSeriesRenderers[0]); - if (pointRegion != null && - circularStateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - .series - .enableTooltip == - true) { - prevTooltipValue = - TooltipValue(pointRegion.seriesIndex, pointRegion.pointIndex); - _presentTooltipValue = prevTooltipValue; - if (prevTooltipValue != null && - currentTooltipValue != null && - prevTooltipValue!.pointIndex != currentTooltipValue!.pointIndex) { - currentTooltipValue = null; - } - final ChartPoint chartPoint = circularStateProperties - .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!); - currentSeriesDetails = pointRegion.seriesIndex; - pointIndex = pointRegion.pointIndex; - _dataPoint = circularStateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex!]; - final int digits = chart.tooltipBehavior.decimalPlaces; - String? header = chart.tooltipBehavior.header; - header = (header == null) - // ignore: prefer_if_null_operators - ? circularStateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - .series - .name != - null - ? circularStateProperties.chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex].series.name - : null - : header; - _header = header ?? ''; - _header = isRtl ? _getRtlFormattedText(_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', - circularStateProperties - .chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - .series - .name ?? - 'series.name'); - _stringValue = - isRtl ? _getRtlFormattedText(resultantString) : resultantString; - showLocation = location; - } else { - _stringValue = isRtl - ? '${getDecimalLabelValue(chartPoint.y, digits)} : ${chartPoint.x}' - : '${chartPoint.x} : ${getDecimalLabelValue(chartPoint.y, digits)}'; - showLocation = location; - } - if (chart.series[0].explode) { - _presentTooltipValue!.pointerPosition = showLocation; - } - isContains = true; - } else { - isContains = false; - } - _mouseTooltip = isContains; - if (!isContains) { - prevTooltipValue = currentTooltipValue = null; - } - } - - /// To render a chart tooltip for triangular series. - void _renderTriangularChartTooltip(Offset position) { - final dynamic chart = _stateProperties.chart; - final bool isRtl = _stateProperties.renderingDetails.isRtl == true; - tooltipBounds = _stateProperties.renderingDetails.chartContainerRect; - bool isContains = false; - const int seriesIndex = 0; - pointIndex = _stateProperties.tooltipPointIndex; - if (pointIndex == null && - _stateProperties.renderingDetails.currentActive == null) { - int? pointIndex; - bool isPoint; - final dynamic seriesRenderer = - _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { - if (seriesRenderer.renderPoints[j].isVisible == true) { - isPoint = isPointInPolygon( - seriesRenderer.renderPoints[j].pathRegion, position); - if (isPoint) { - pointIndex = j; - break; - } - } - } - _stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex, - seriesRenderer.series, - seriesRenderer.renderPoints[pointIndex], - ); - } - pointIndex ??= _stateProperties.renderingDetails.currentActive!.pointIndex; - _dataPoint = _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; - _stateProperties.tooltipPointIndex = null; - final int digits = chart.tooltipBehavior.decimalPlaces; - if (chart.tooltipBehavior.enable == true) { - prevTooltipValue = TooltipValue(seriesIndex, pointIndex!); - _presentTooltipValue = prevTooltipValue; - if (prevTooltipValue != null && - currentTooltipValue != null && - prevTooltipValue!.pointIndex != currentTooltipValue!.pointIndex) { - currentTooltipValue = null; - } - final PointInfo chartPoint = _stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex].renderPoints[pointIndex]; - final Offset location = chart.tooltipBehavior.tooltipPosition == - TooltipPosition.pointer && - _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .series.explode == - true - ? chartPoint.symbolLocation - : chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - _stateProperties.chartSeries - .visibleSeriesRenderers[seriesIndex].series.explode == - false - ? position - : chartPoint.symbolLocation; - currentSeriesDetails = seriesIndex; - String? header = chart.tooltipBehavior.header; - header = (header == null) - // ignore: prefer_if_null_operators - ? _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .series.name != - null - ? _stateProperties - .chartSeries.visibleSeriesRenderers[seriesIndex].series.name - : null - : header; - _header = header ?? ''; - _header = isRtl ? _getRtlFormattedText(_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', - _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .series.name ?? - 'series.name'); - _stringValue = - isRtl ? _getRtlFormattedText(resultantString) : resultantString; - showLocation = location; - } else { - _stringValue = isRtl - ? '${getDecimalLabelValue(chartPoint.y, digits)} : ${chartPoint.x}' - : '${chartPoint.x} : ${getDecimalLabelValue(chartPoint.y, digits)}'; - showLocation = location; - } - isContains = true; - } else { - isContains = false; - } - if (chart.series.explode == true) { - _presentTooltipValue!.pointerPosition = showLocation; - } - _mouseTooltip = isContains; - if (!isContains) { - prevTooltipValue = currentTooltipValue = null; - } - } - - /// To show the axis label tooltip for trimmed axes label texts. - void showTrimmedTooltip(Offset position, dynamic chart, String text) { - final RenderingDetails renderingDetails = _stateProperties.renderingDetails; - if (renderBox != null) { - _header = ''; - _stringValue = text; - showLocation = position; - tooltipBounds = renderingDetails.chartContainerRect; - renderBox!.inversePadding = 0; - chartTooltipState!.boundaryRect = tooltipBounds!; - if (showLocation != null) { - chartTooltipState?.needMarker = false; - chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: currentTooltipValue, - position: showLocation, - duration: 0); - } - if (!isHovering) { - if (_stateProperties.isTooltipOrientationChanged == false) { - // Cancelled timer as we have used timer in chart now. - if (timer != null) { - timer?.cancel(); - } - timer = Timer( - Duration(milliseconds: tooltipBehavior.duration.toInt()), () { - chartTooltipState?.hide(hideDelay: 0); - _stateProperties.isTooltipHidden = true; - if (_stateProperties.isTooltipOrientationChanged == true) { - _stateProperties.isTooltipOrientationChanged = false; - } - }); - } - } - } - } - - /// To render a chart tooltip for Cartesian series. - void _renderCartesianChartTooltip(Offset position) { - final CartesianStateProperties stateProperties = - _stateProperties as CartesianStateProperties; - bool isContains = false; - if (_isPointWithInRect(position, stateProperties.chartAxis.axisClipRect)) { - 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 < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - _seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - final CartesianSeries series = - _seriesRendererDetails!.series; - if (_seriesRendererDetails!.visible! == true && - series.enableTooltip && - _seriesRendererDetails?.regionalData != null) { - int count = 0; - _seriesRendererDetails!.regionalData! - .forEach((dynamic regionRect, dynamic values) { - isTrendLine = values[values.length - 1].contains('true'); - touchPadding = ((_seriesRendererDetails!.seriesType == 'bubble' || - _seriesRendererDetails!.seriesType == 'scatter' || - _seriesRendererDetails!.seriesType.contains('column') == - true || - _seriesRendererDetails!.seriesType.contains('bar') == - true || - _seriesRendererDetails!.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) == true) { - tooltipBounds = stateProperties.chartAxis.axisClipRect; - if (_seriesRendererDetails!.seriesType != 'boxandwhisker' - ? !region.contains(position) - : (paddedRegion.contains(position) || - !region.contains(position))) { - tooltipBounds = stateProperties.chartAxis.axisClipRect; - } - _presentTooltipValue = - TooltipValue(i, count, outlierTooltipIndex); - currentSeriesDetails = _seriesRendererDetails; - pointIndex = _stateProperties.chart is SfCartesianChart - ? isTrendLine == true - ? regionRect[4].index - : regionRect[4].visiblePointIndex - : count; - _dataPoint = regionRect[4]; - _markerType = _seriesRendererDetails!.series.markerSettings.shape; - Color? seriesColor = _seriesRendererDetails!.seriesColor; - if (_seriesRendererDetails!.seriesType == 'waterfall') { - seriesColor = getWaterfallSeriesColor( - _seriesRendererDetails!.series - as WaterfallSeries, - _seriesRendererDetails!.dataPoints[pointIndex!], - seriesColor)!; - } - _markerColor = regionRect[2] ?? - _seriesRendererDetails!.series.markerSettings.borderColor ?? - seriesColor!; - tooltipPosition = (outlierTooltipIndex >= 0) - ? regionRect[6][outlierTooltipIndex] - : regionRect[1]; - final Paint markerPaint = Paint(); - markerPaint.color = (!tooltipBehavior.shared - ? _markerColor - : _seriesRendererDetails! - .series.markerSettings.borderColor ?? - _seriesRendererDetails!.seriesColor ?? - _seriesRendererDetails!.series.color)! - .withOpacity(tooltipBehavior.opacity); - if (!tooltipBehavior.shared) { - markerGradients - ..clear() - ..add(_seriesRendererDetails!.series.gradient); - markerImages - ..clear() - ..add(_seriesRendererDetails!.markerSettingsRenderer?.image); - markerPaints - ..clear() - ..add(markerPaint); - markerTypes - ..clear() - ..add(_markerType); - } - final List paddingData = - !(_seriesRendererDetails!.isRectSeries == true && - tooltipBehavior.tooltipPosition != - TooltipPosition.auto) - ? getTooltipPaddingData(_seriesRendererDetails!, - 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) { - int indexValue = 0; - int tooltipElementsLength = 0; - final Paint markerPaint = Paint(); - markerPaint.color = - _seriesRendererDetails!.series.markerSettings.borderColor ?? - _seriesRendererDetails!.seriesColor ?? - _seriesRendererDetails!.series.color! - .withOpacity(tooltipBehavior.opacity); - markerGradients.add(_seriesRendererDetails!.series.gradient); - markerImages - .add(_seriesRendererDetails!.markerSettingsRenderer?.image); - markerTypes - .add(_seriesRendererDetails!.series.markerSettings.shape); - markerPaints.add(markerPaint); - if ((_seriesRendererDetails!.seriesType.contains('range') == true || - _seriesRendererDetails!.seriesType == 'hilo') && - !isTrendLine!) { - // Assigned value '2' for this variable because for range and - // hilo series there will be two display value types - // such as high and low. - tooltipElementsLength = 2; - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } else if (_seriesRendererDetails!.seriesType == 'hiloopenclose' || - _seriesRendererDetails!.seriesType == 'candle') { - // Assigned value '4' for this variable because for hiloopenclose - // and candle series there will be four display values - // such as high, low, open and close. - tooltipElementsLength = 4; - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } else if (_seriesRendererDetails!.seriesType == 'boxandwhisker') { - // Assigned value '1' or '6' this variable field because for the - // box and whiskers series there will be one display values if - // the outlier's tooltip is activated otherwise there will be - // six display values such as maximum, minimum, mean, median, - // lowerQuartile and upperQuartile. - tooltipElementsLength = outlierTooltip ? 1 : 6; - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } else { - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } - for (int j = 0; j < indexValue; j++) { - markerTypes.add(null); - markerImages.add(null); - markerGradients.add(null); - markerPaints.add(null); - } - } - } - } - if (isContains) { - renderBox!.markerGradients = markerGradients; - renderBox!.markerImages = markerImages; - renderBox!.markerPaints = markerPaints; - renderBox!.markerTypes = markerTypes; - _seriesRendererDetails = currentSeriesDetails ?? _seriesRendererDetails; - if (currentSeriesDetails.isRectSeries == true && - tooltipBehavior.tooltipPosition == TooltipPosition.pointer) { - tooltipPosition = position; - showLocation = tooltipPosition; - } - renderBox!.normalPadding = - _seriesRendererDetails!.renderer 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] - : currentSeriesDetails.series.name ?? - currentSeriesDetails.seriesName)) - : header; - _header = header ?? ''; - _header = stateProperties.renderingDetails.isRtl - ? _getRtlFormattedText(_header!) - : _header; - _stringValue = ''; - if (tooltipBehavior.shared) { - _textValues = []; - _seriesRendererCollection = []; - for (int j = 0; - j < stateProperties.chartSeries.visibleSeriesRenderers.length; - j++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[j]); - if (seriesRendererDetails.visible! == true && - seriesRendererDetails.series.enableTooltip == true) { - final int index = - seriesRendererDetails.xValues!.indexOf(dataRect[4].x); - if (index > -1) { - final String text = (_stringVal != '' ? '\n' : '') + - _calculateCartesianTooltipText( - seriesRendererDetails, - seriesRendererDetails.dataPoints[index], - dataValues, - tooltipPosition!, - outlierTooltip, - outlierTooltipIndex); - _stringValue = _stringVal! + text; - _textValues.add(text); - _seriesRendererCollection.add(seriesRendererDetails.renderer); - } - } - } - } else { - _stringValue = _calculateCartesianTooltipText( - currentSeriesDetails, - dataRect[4], - dataValues, - tooltipPosition!, - outlierTooltip, - outlierTooltipIndex); - } - showLocation = tooltipPosition; - } else { - _stringValue = null; - if (!isHovering) { - _presentTooltipValue = currentTooltipValue = null; - } else { - _mouseTooltip = isContains; - } - } - } - } - - /// It returns the tooltip text of Cartesian series. - String _calculateCartesianTooltipText( - SeriesRendererDetails seriesRendererDetails, - CartesianChartPoint point, - dynamic values, - Offset tooltipPosition, - bool outlierTooltip, - int outlierTooltipIndex) { - final bool isRtl = - seriesRendererDetails.stateProperties.renderingDetails.isRtl == true; - final bool isTrendLine = values[values.length - 1].contains('true'); - String resultantString; - final ChartAxisRendererDetails axisRenderer = - seriesRendererDetails.yAxisDetails!; - final TooltipBehavior tooltip = tooltipBehavior; - final int digits = - seriesRendererDetails.chart.tooltipBehavior.decimalPlaces; - String? minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - medianValue, - meanValue, - outlierValue, - highValue, - lowValue, - openValue, - closeValue, - cumulativeValue, - boxPlotString; - if (seriesRendererDetails.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 = isRtl - ? '\n$minimumValue : Minimum\n$maximumValue : Maximum\n$medianValue : Median\n$meanValue : Mean\n$lowerQuartileValue : LQ\n$upperQuartileValue : HQ' - : '\nMinimum : $minimumValue\nMaximum : $maximumValue\nMedian : $medianValue\nMean : $meanValue\nLQ : $lowerQuartileValue\nHQ : $upperQuartileValue'; - } else if (seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType == 'hilo' || - seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType == 'candle') { - highValue = getLabelValue(point.high, axisRenderer.axis, digits); - lowValue = getLabelValue(point.low, axisRenderer.axis, digits); - if (seriesRendererDetails.seriesType == 'candle' || - seriesRendererDetails.seriesType == 'hiloopenclose') { - openValue = getLabelValue(point.open, axisRenderer.axis, digits); - closeValue = getLabelValue(point.close, axisRenderer.axis, digits); - } - } else if (seriesRendererDetails.seriesType.contains('stacked') == true) { - cumulativeValue = - getLabelValue(point.cumulativeValue, axisRenderer.axis, digits); - } - if (tooltipBehavior.format != null) { - resultantString = (seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType == 'hilo') && - !isTrendLine - ? (tooltip.format! - .replaceAll('point.x', values[0]) - .replaceAll('point.high', highValue!) - .replaceAll('point.low', lowValue!) - .replaceAll( - 'series.name', - seriesRendererDetails.series.name ?? - seriesRendererDetails.seriesName!)) - : (seriesRendererDetails.seriesType.contains('hiloopenclose') == true || - seriesRendererDetails.seriesType.contains('candle') == - true) && - !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( - 'series.name', - seriesRendererDetails.series.name ?? - seriesRendererDetails.seriesName!)) - : (seriesRendererDetails.seriesType.contains('boxandwhisker') == true) && - !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( - 'series.name', seriesRendererDetails.series.name ?? seriesRendererDetails.seriesName!)) - : seriesRendererDetails.seriesType.contains('stacked') == true - ? tooltip.format!.replaceAll('point.cumulativeValue', cumulativeValue!) - : seriesRendererDetails.seriesType == 'bubble' - ? tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', getLabelValue(point.y, axisRenderer.axis, digits)).replaceAll('series.name', seriesRendererDetails.series.name ?? seriesRendererDetails.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', seriesRendererDetails.series.name ?? seriesRendererDetails.seriesName!); - resultantString = - isRtl ? _getRtlFormattedText(resultantString) : resultantString; - } else { - final String xValue = - '${tooltipBehavior.shared ? seriesRendererDetails.series.name ?? seriesRendererDetails.seriesName : values[0]}'; - String result = ''; - if ((seriesRendererDetails.seriesType.contains('range') == true || - seriesRendererDetails.seriesType == 'hilo') && - !isTrendLine) { - result = xValue + - (isRtl - ? '\n${highValue!} : High\n${lowValue!} : Low' - : '\nHigh : ${highValue!}\nLow : ${lowValue!}'); - } else if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType == 'candle') { - result = xValue + - (isRtl - ? '\n${highValue!} : High\n${lowValue!} : Low\n${openValue!} : Open\n${closeValue!} : Close' - : '\nHigh : ${highValue!}\nLow : ${lowValue!}\nOpen : ${openValue!}\nClose : ${closeValue!}'); - } else if (seriesRendererDetails.seriesType == 'boxandwhisker') { - result = xValue + - (outlierValue != null - ? isRtl - ? '\n$outlierValue : Outliers' - : '\nOutliers : $outlierValue' - : boxPlotString!); - } else { - final String yValue = getLabelValue(point.y, axisRenderer.axis, digits); - result = isRtl ? '$yValue : $xValue' : '$xValue : $yValue'; - } - resultantString = result; - } - return resultantString; - } - - String _getRtlFormattedText(String formattedText) { - String resultantString = ''; - List textCollection = []; - final List firstStringCollection = []; - if (formattedText.contains('\n')) { - textCollection = formattedText.split('\n'); - for (int j = 0; j < textCollection.length; j++) { - if (textCollection[j].contains(':')) { - final List secondStringCollection = - textCollection[j].split(':'); - firstStringCollection - .add(_getRtlStringWithColon(secondStringCollection)); - } else { - firstStringCollection.add(textCollection[j]); - } - } - } else if (formattedText.contains(':')) { - textCollection = formattedText.split(':'); - formattedText = _getRtlStringWithColon(textCollection); - } - if (firstStringCollection.isNotEmpty) { - for (int i = 0; i < firstStringCollection.length; i++) { - resultantString = - resultantString + (i == 0 ? '' : '\n') + firstStringCollection[i]; - } - } else { - resultantString = formattedText; - } - return resultantString; - } - - String _getRtlStringWithColon(List textCollection) { - String string = ''; - for (int i = textCollection.length - 1; i >= 0; i--) { - final bool containsBackSpace = textCollection[i].endsWith(' '); - final bool containsFrontSpace = textCollection[i].startsWith(' '); - textCollection[i] = textCollection[i].trim(); - if (containsFrontSpace) { - textCollection[i] = '${textCollection[i]} '; - } - if (containsBackSpace) { - textCollection[i] = ' ${textCollection[i]}'; - } - string = string + - (i == textCollection.length - 1 - ? textCollection[i] - : ':${textCollection[i]}'); - } - return string; - } - - /// Returns the cumulative length of the number of new line character '\n' - /// available in series name and tooltip format string. - int _getTooltipNewLineLength(int value) { - value += _seriesRendererDetails!.series.name != null - ? '\n'.allMatches(_seriesRendererDetails!.series.name!).length - : 0; - if (tooltipBehavior.format != null) { - value += '\n'.allMatches(tooltipBehavior.format!).length; - } - return value; - } - - /// Finds whether the point resides inside the given rect including its edges. - bool _isPointWithInRect(Offset point, Rect rect) { - // ignore: unnecessary_null_comparison - return point != null && - point.dx >= rect.left && - point.dx <= rect.right && - point.dy <= rect.bottom && - point.dy >= rect.top; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart deleted file mode 100644 index 8fcf8e8c6..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart +++ /dev/null @@ -1,129 +0,0 @@ -/// Legend Position in charts. -enum LegendPosition { - /// - `LegendPosition.auto`, places the legend either at the bottom when the height is - /// greater than the width, or at right when the width is greater than height. - auto, - - /// - `LegendPosition.bottom`, places the legend below the plot area. - bottom, - - /// - `LegendPosition.left`, places the legend at the left to the plot area. - left, - - /// - `LegendPosition.right`, places the legend at the right to the plot area. - right, - - /// - `LegendPosition.top`, places the legend at the top of the plot area. - top -} - -/// Alignment of various elements in chart. -enum ChartAlignment { - /// - `ChartAlignment.near` aligns to the near position. - near, - - /// - `ChartAlignment.center` aligns to the center position - center, - - /// - `ChartAlignment.far` aligns to the far position. - far -} - -/// Mode to handle the legend items overflow. -enum LegendItemOverflowMode { - /// - `LegendItemOverflowMode.wrap`, will wrap and place the remaining legend item to next line. - wrap, - - /// - `LegendItemOverflowMode.scroll`, will place all the legend items in single - /// line and enables scrolling. - scroll, - - /// - `LegendItemOverflowMode.none`, will simply hides the remaining legend items. - none -} - -/// Orientation of legend items. -enum LegendItemOrientation { - /// - `LegendItemOrientation.auto`, will align the legend items based on the position. - auto, - - /// - `LegendItemOrientation.horizontal`, will align the legend items horizontally. - horizontal, - - /// - `LegendItemOrientation.vertical`, will align the legend item vertically. - vertical -} - -/// It used to change the legend icons in different type of series and indicators. -enum LegendIconType { - ///`LegendIconType.seriesType`, icon is same as seriestype. - seriesType, - - ///`LegendIconType.circle`, icon is changed by circle shape. - circle, - - ///`LegendIconType.rectangle`, icon is changed by rectangle shape. - rectangle, - - ///`LegendIconType.image`, icon is changed by adding the assest image. - image, - - ///`LegendIconType.pentagon`, icon is changed by pentagon shape. - pentagon, - - ///`LegendIconType.verticalLine`, icon is changed by verticalline. - verticalLine, - - ///`LegendIconType.horizontalLine`, icon is changed by horizotalline. - horizontalLine, - - ///`LegendIconType.diamond`, icon is changed by diamond shape. - diamond, - - ///`LegendIconType.triangle`, icon is changed by triangle shape. - triangle, - - ///`LegendIconType.invertedTriangle`, icon is changed by invertedTriangle shape. - invertedTriangle -} - -/// Position of data labels in Cartesian chart.The position of data lables in cartesian charts can be changed using this property. -/// -/// Defaults to `ChartDataLabelAlignment.auto`. -enum ChartDataLabelAlignment { - /// - ChartDataLabelAlignment.auto places the data label either top or bottom position - /// of a point based on the position. - auto, - - /// - ChartDataLabelAlignment.outer places the data label at outside of a point. - outer, - - /// - ChartDataLabelAlignment.top places the data label at the top position of a point. - top, - - /// - ChartDataLabelAlignment.bottom places the data label at the bottom position of a point. - bottom, - - /// - ChartDataLabelAlignment.middle places the data label at the center position of a point. - middle -} - -/// Position of data labels in Circular chart. -enum CircularLabelPosition { - /// - CircularLabelPosition.curve places the data label inside the point. - inside, - - /// - CircularLabelPosition.line places the data label outside the point. - outside -} - -/// PyramidMode for pyramid charts. -/// -/// Defaults to `PyramidMode.linear` -enum PyramidMode { - /// - PyramidMode.linear, linear pyramid will be rendered - linear, - - /// - PyramidMode.surface, Surface pyramid will be displayed - surface -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart deleted file mode 100644 index 42a46e1f1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart +++ /dev/null @@ -1,1214 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/chart/axis/axis.dart'; -import 'package:syncfusion_flutter_charts/src/common/rendering_details.dart'; -import 'package:syncfusion_flutter_charts/src/pyramid_chart/utils/common.dart'; -import 'package:syncfusion_flutter_charts/src/pyramid_chart/utils/helper.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/legend_internal.dart' - hide LegendPosition; -import 'package:syncfusion_flutter_core/legend_internal.dart' as legend_common; -import 'package:syncfusion_flutter_core/theme.dart'; - -import '../../chart/base/chart_base.dart'; -import '../../chart/chart_series/financial_series_base.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/chart_series/series_renderer_properties.dart'; -import '../../chart/chart_series/xy_data_series.dart'; -import '../../chart/common/cartesian_state_properties.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/technical_indicators/technical_indicator.dart'; -import '../../chart/utils/enum.dart'; -import '../../chart/utils/helper.dart'; -import '../../circular_chart/base/circular_base.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../../funnel_chart/base/funnel_base.dart'; -import '../../pyramid_chart/base/pyramid_base.dart'; -import '../common.dart'; -import '../event_args.dart'; -import '../legend/legend.dart'; -import '../legend/renderer.dart'; -import '../state_properties.dart'; -import '../utils/enum.dart'; -import 'typedef.dart'; - -/// `onDataLabelTapped` event for all series. -void dataLabelTapEvent(dynamic chart, DataLabelSettings dataLabelSettings, - int pointIndex, dynamic point, Offset position, int seriesIndex) { - DataLabelTapDetails datalabelArgs; - datalabelArgs = DataLabelTapDetails( - seriesIndex, - pointIndex, - chart is SfCartesianChart ? point.label : point.text, - dataLabelSettings, - chart is SfCartesianChart ? point.overallDataPointIndex : pointIndex); - datalabelArgs.position = position; - chart.onDataLabelTapped(datalabelArgs); - position = datalabelArgs.position; -} - -/// To get saturation color. -Color getSaturationColor(Color color) { - Color saturationColor; - final num contrast = - ((color.red * 299 + color.green * 587 + color.blue * 114) / 1000).round(); - saturationColor = contrast >= 128 ? Colors.black : Colors.white; - return saturationColor; -} - -/// To get point from data and return point data. -CartesianChartPoint getPointFromData( - SeriesRendererDetails seriesRendererDetails, int pointIndex) { - final XyDataSeries series = - seriesRendererDetails.series as XyDataSeries; - final ChartIndexedValueMapper? xValue = series.xValueMapper; - final ChartIndexedValueMapper? yValue = series.yValueMapper; - final dynamic xVal = xValue!(pointIndex); - final dynamic yVal = (seriesRendererDetails.seriesType.contains('range') || - seriesRendererDetails.seriesType.contains('hilo') || - seriesRendererDetails.seriesType == 'candle') - ? null - : yValue!(pointIndex); - - final CartesianChartPoint point = - CartesianChartPoint(xVal, yVal); - if (seriesRendererDetails.seriesType.contains('range') || - seriesRendererDetails.seriesType.contains('hilo') || - seriesRendererDetails.seriesType == 'candle') { - final ChartIndexedValueMapper? highValue = series.highValueMapper; - final ChartIndexedValueMapper? lowValue = series.lowValueMapper; - point.high = highValue!(pointIndex); - point.low = lowValue!(pointIndex); - } - if (series is FinancialSeriesBase) { - if (seriesRendererDetails.seriesType == 'hiloopenclose' || - seriesRendererDetails.seriesType == 'candle') { - final ChartIndexedValueMapper? openValue = series.openValueMapper; - final ChartIndexedValueMapper? closeValue = series.closeValueMapper; - point.open = openValue!(pointIndex); - point.close = closeValue!(pointIndex); - } - } - return point; -} - -/// To calculate dash array path for series. -Path? dashPath( - Path? source, { - required CircularIntervalList dashArray, -}) { - if (source == null) { - return null; - } - const double intialValue = 0.0; - final Path path = Path(); - 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; - } - } - return path; -} - -/// To return textstyle. -TextStyle getTextStyle( - {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 && - (takeFontColorValue == null || !takeFontColorValue) - ? textStyle.color - : fontColor, - fontWeight: textStyle.fontWeight ?? fontWeight, - fontSize: textStyle.fontSize ?? fontSize, - fontStyle: textStyle.fontStyle ?? fontStyle, - fontFamily: textStyle.fontFamily ?? fontFamily, - 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, - ); - } else { - return TextStyle( - color: fontColor, - fontWeight: fontWeight, - fontSize: fontSize, - fontStyle: fontStyle, - fontFamily: fontFamily, - ); - } -} - -/// Method to get the elements. -Widget? getElements(StateProperties stateProperties, Widget chartWidget, - BoxConstraints constraints) { - final dynamic chart = stateProperties.chart; - final ChartLegend chartLegend = stateProperties.renderingDetails.chartLegend; - final LegendPosition legendPosition = - stateProperties.renderingDetails.legendRenderer.legendPosition; - final LegendRenderer legendRenderer = - stateProperties.renderingDetails.legendRenderer; - final Legend legend = chart.legend; - final List legendWidgetContext = - stateProperties.renderingDetails.legendWidgetContext; - - double legendHeight, legendWidth, chartHeight, chartWidth; - Widget? element; - - if (chartLegend.shouldRenderLegend && chart.legend.isResponsive == true) { - chartHeight = constraints.maxHeight - chartLegend.legendSize.height; - chartWidth = constraints.maxWidth - chartLegend.legendSize.width; - chartLegend.shouldRenderLegend = (legendPosition == LegendPosition.bottom || - legendPosition == LegendPosition.top) - ? (chartHeight > chartLegend.legendSize.height) - : (chartWidth > chartLegend.legendSize.width); - } - if (!chartLegend.shouldRenderLegend) { - element = SizedBox( - child: chartWidget, - width: constraints.maxWidth, - height: constraints.maxHeight); - } else { - legendHeight = chartLegend.legendSize.height; - legendWidth = chartLegend.legendSize.width; - chartHeight = chartLegend.chartSize.height - legendHeight; - chartWidth = chartLegend.chartSize.width - legendWidth; - - // To determine the toggled indices of the legend items. - final List toggledIndices = chartLegend.toggledIndices; - if (chart is SfCartesianChart) { - toggledIndices.clear(); - if (legend.legendItemBuilder == null) { - final List legendCollections = - stateProperties.renderingDetails.chartLegend.legendCollections!; - for (int i = 0; i < legendCollections.length; i++) { - final LegendRenderContext context = legendCollections[i]; - context.isSelect = (context.trendline != null) - ? context.seriesRenderer - .trendlineRenderer[context.trendlineIndex!]!.visible == - false - : context.seriesRenderer is TechnicalIndicators - ? !context.indicatorRenderer!.visible! - : context.seriesRenderer.visible == false; - if (context.isSelect) { - toggledIndices.add(i); - } - } - } else { - final List legendToggles = - stateProperties.renderingDetails.legendToggleTemplateStates; - for (final MeasureWidgetContext currentItem in legendWidgetContext) { - for (int i = 0; i < legendToggles.length; i++) { - final MeasureWidgetContext item = legendToggles[i]; - if (currentItem.seriesIndex == item.seriesIndex && - currentItem.pointIndex == item.pointIndex) { - toggledIndices.add(legendWidgetContext.indexOf(currentItem)); - break; - } - } - } - } - toggledIndices.sort(); - } - - if (legend.legendItemBuilder != null) { - element = SfLegend.builder( - title: getLegendTitleWidget(legend, stateProperties.renderingDetails), - child: chartWidget, - itemCount: legendWidgetContext.length, - toggledIndices: chartLegend.toggledIndices, - color: chartLegend.legend!.backgroundColor, - border: getLegendBorder( - chartLegend.legend!.borderColor, chartLegend.legend!.borderWidth), - position: getEffectiveChartLegendPosition(legendPosition), - direction: getEffectiveChartLegendOrientation( - chartLegend.legend!, legendRenderer), - scrollDirection: getEffectiveChartLegendOrientation( - chartLegend.legend!, legendRenderer), - alignment: getEffectiveLegendAlignment(chartLegend.legend!.alignment), - itemBuilder: (BuildContext context, int index) { - if (legendWidgetContext.isNotEmpty) { - final MeasureWidgetContext legendRenderContext = - legendWidgetContext[index]; - return legendRenderContext.widget!; - } - return Container(); - }, - overflowMode: getEffectiveLegendItemOverflowMode( - chartLegend.legend!.overflowMode, chartLegend), - width: legendWidth, - height: legendHeight, - spacing: chartLegend.legend!.padding, - itemSpacing: 0, - itemRunSpacing: 0, - padding: EdgeInsets.zero, - margin: getEffectiveLegendMargin(chartLegend, legendPosition), - toggledItemColor: - stateProperties.renderingDetails.chartTheme.brightness == - Brightness.light - ? Colors.white.withOpacity(0.5) - : Colors.grey[850]!.withOpacity(0.5), - onToggledIndicesChanged: - (List toggledIndices, int toggledIndex) { - if (chart is SfCartesianChart) { - cartesianToggle( - toggledIndex, stateProperties as CartesianStateProperties); - } else { - circularAndTriangularToggle(toggledIndex, stateProperties); - chartLegend.toggledIndices = toggledIndices; - } - }); - } else { - element = SfLegend( - title: getLegendTitleWidget(legend, stateProperties.renderingDetails), - child: chartWidget, - toggledIndices: chartLegend.toggledIndices, - items: chartLegend.legendItems, - offset: legend.offset, - width: legendWidth, - height: legendHeight, - onItemRenderer: (ItemRendererDetails args) { - args.text = chartLegend.legendItems[args.index].text; - if (chartLegend.legendItems[args.index].shader == null || - chartLegend.legendItems[args.index].iconStrokeWidth != null) { - args.color = chartLegend.legendItems[args.index].color; - } - args.iconType = chartLegend.legendItems[args.index].iconType!; - }, - color: chartLegend.legend!.backgroundColor, - border: getLegendBorder( - chartLegend.legend!.borderColor, chartLegend.legend!.borderWidth), - position: getEffectiveChartLegendPosition(legendPosition), - direction: getEffectiveChartLegendOrientation( - chartLegend.legend!, legendRenderer), - scrollDirection: getEffectiveChartLegendOrientation( - chartLegend.legend!, legendRenderer), - alignment: getEffectiveLegendAlignment(chartLegend.legend!.alignment), - overflowMode: getEffectiveLegendItemOverflowMode( - chartLegend.legend!.overflowMode, chartLegend), - iconSize: Size( - chartLegend.legend!.iconWidth, chartLegend.legend!.iconHeight), - iconBorder: getLegendIconBorder(chartLegend.legend!.iconBorderColor, - chartLegend.legend!.iconBorderWidth), - textStyle: chartLegend.legend!.textStyle.copyWith( - color: legend.textStyle.color ?? - stateProperties.renderingDetails.chartTheme.legendTextColor, - fontSize: legend.textStyle.fontSize! / - MediaQuery.of(stateProperties.chartState.context) - .textScaleFactor), - spacing: legend.padding, - itemSpacing: legend.itemPadding, - itemRunSpacing: legend.itemPadding, - padding: getEffectiveLegendPadding( - chartLegend, legendRenderer, legendPosition), - margin: getEffectiveLegendMargin(chartLegend, legendPosition), - toggledIconColor: const Color.fromRGBO(211, 211, 211, 1), - toggledTextOpacity: 0.2, - onToggledIndicesChanged: - (List toggledIndices, int toggledIndex) { - if (chart is SfCartesianChart) { - cartesianToggle( - toggledIndex, stateProperties as CartesianStateProperties); - } else { - circularAndTriangularToggle(toggledIndex, stateProperties); - chartLegend.toggledIndices = toggledIndices; - } - }); - } - } - return element; -} - -/// To get effective legend position for SfLegend. -legend_common.LegendPosition getEffectiveChartLegendPosition( - LegendPosition position) { - switch (position) { - case LegendPosition.top: - return legend_common.LegendPosition.top; - case LegendPosition.bottom: - return legend_common.LegendPosition.bottom; - case LegendPosition.left: - return legend_common.LegendPosition.left; - case LegendPosition.right: - return legend_common.LegendPosition.right; - default: - return legend_common.LegendPosition.bottom; - } -} - -/// To get effective legend orientation for SfLegend. -Axis getEffectiveChartLegendOrientation( - Legend legend, LegendRenderer renderer) { - final LegendItemOrientation legendOrientation = renderer.orientation; - if (legend.orientation == LegendItemOrientation.auto) { - if (legendOrientation == LegendItemOrientation.horizontal) { - return Axis.horizontal; - } else { - return Axis.vertical; - } - } else { - if (legend.orientation == LegendItemOrientation.horizontal) { - return Axis.horizontal; - } else { - return Axis.vertical; - } - } -} - -/// To get effective legend alignment for SfLegend. -LegendAlignment getEffectiveLegendAlignment(ChartAlignment alignment) { - LegendAlignment legendAlignment; - switch (alignment) { - case ChartAlignment.near: - legendAlignment = LegendAlignment.near; - break; - case ChartAlignment.far: - legendAlignment = LegendAlignment.far; - break; - case ChartAlignment.center: - legendAlignment = LegendAlignment.center; - break; - } - return legendAlignment; -} - -/// To get effective legend items overflow mode for SfLegend. -LegendOverflowMode getEffectiveLegendItemOverflowMode( - LegendItemOverflowMode overflowMode, ChartLegend chartLegend) { - LegendOverflowMode mode; - switch (overflowMode) { - case LegendItemOverflowMode.wrap: - if (chartLegend.isNeedScrollable) { - mode = LegendOverflowMode.wrapScroll; - } else { - mode = LegendOverflowMode.wrap; - } - break; - case LegendItemOverflowMode.scroll: - mode = LegendOverflowMode.scroll; - break; - case LegendItemOverflowMode.none: - mode = LegendOverflowMode.none; - break; - } - return mode; -} - -/// To get border for SfLegend container. -BorderSide? getLegendBorder(Color borderColor, double borderWidth) { - // ignore: unnecessary_null_comparison - if (borderColor != null && borderWidth > 0) { - return BorderSide(color: borderColor, width: borderWidth); - } - - return null; -} - -/// To get icon border for legendItemsin SfLegend. -BorderSide? getLegendIconBorder(Color iconBorderColor, double iconBorderWidth) { - // ignore: unnecessary_null_comparison - if (iconBorderColor != null && iconBorderWidth > 0) { - return BorderSide(color: iconBorderColor, width: iconBorderWidth); - } - - return null; -} - -/// To get legend title widget for SfLegend. -Widget? getLegendTitleWidget(Legend legend, RenderingDetails renderingDetails) { - final LegendTitle legendTitle = legend.title; - if (legendTitle.text != null && legendTitle.text!.isNotEmpty) { - final ChartAlignment titleAlign = legendTitle.alignment; - final Color color = legendTitle.textStyle.color ?? - renderingDetails.chartTheme.legendTitleColor; - final double? fontSize = legendTitle.textStyle.fontSize; - final String? fontFamily = legendTitle.textStyle.fontFamily; - final FontStyle? fontStyle = legendTitle.textStyle.fontStyle; - final FontWeight? fontWeight = legendTitle.textStyle.fontWeight; - final num titleHeight = - measureText(legend.title.text!, legend.title.textStyle).height + 10; - renderingDetails.chartLegend.titleHeight = titleHeight.toDouble(); - return Container( - height: titleHeight.toDouble(), - width: renderingDetails.chartLegend.legendSize.width, - alignment: titleAlign == ChartAlignment.center - ? Alignment.center - : titleAlign == ChartAlignment.near - ? Alignment.centerLeft - : Alignment.centerRight, - // ignore: avoid_unnecessary_containers - child: Container( - child: Text(legend.title.text!, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: color, - fontSize: fontSize, - fontFamily: fontFamily, - fontStyle: fontStyle, - fontWeight: fontWeight)), - )); - } else { - return null; - } -} - -/// To get outer padding or margin for legend container. -EdgeInsetsGeometry getEffectiveLegendMargin( - ChartLegend chartLegend, LegendPosition legendPosition) { - final dynamic chart = chartLegend.chart; - final EdgeInsets margin; - final bool needPadding = chart is SfCircularChart || - chart is SfPyramidChart || - chart is SfFunnelChart; - final ChartAlignment legendAlignment = chartLegend.legend!.alignment; - const double legendMargin = 5; - - if (legendPosition == LegendPosition.top) { - margin = EdgeInsets.fromLTRB( - needPadding && legendAlignment == ChartAlignment.near - ? legendMargin * 2 - : 0, - legendMargin, - needPadding && legendAlignment == ChartAlignment.far - ? legendMargin * 2 - : 0, - legendMargin); - } else if (legendPosition == LegendPosition.bottom) { - margin = EdgeInsets.fromLTRB( - needPadding && legendAlignment == ChartAlignment.near - ? legendMargin * 2 - : 0, - legendMargin, - needPadding && legendAlignment == ChartAlignment.far - ? legendMargin * 2 - : 0, - needPadding ? legendMargin : 0); - } else if (legendPosition == LegendPosition.right) { - margin = EdgeInsets.fromLTRB(legendMargin, 0, - needPadding ? 3 * legendMargin : 0, needPadding ? 3 * legendMargin : 0); - } else if (legendPosition == LegendPosition.left) { - margin = EdgeInsets.fromLTRB(needPadding ? legendMargin / 2 : 0, 0, 0, - needPadding ? 3 * legendMargin : 0); - } else { - margin = EdgeInsets.zero; - } - - return margin; -} - -/// To get internal padding for legend container. -EdgeInsetsGeometry getEffectiveLegendPadding(ChartLegend chartLegend, - LegendRenderer legendRenderer, LegendPosition legendPosition) { - final dynamic chart = chartLegend.chart; - final Legend legend = chartLegend.legend!; - final LegendItemOrientation legendOrientation = legendRenderer.orientation; - final LegendItemOverflowMode overflowMode = legend.overflowMode; - final EdgeInsetsGeometry padding; - final double legendPadding = legend.itemPadding; - final bool needPadding = chart is SfCircularChart || - chart is SfPyramidChart || - chart is SfFunnelChart; - final bool needTitle = - legend.title.text != null && legend.title.text!.isNotEmpty; - - if (legendPosition == LegendPosition.top || - legendPosition == LegendPosition.bottom) { - if (legendOrientation == LegendItemOrientation.horizontal) { - if (overflowMode == LegendItemOverflowMode.wrap) { - padding = !chartLegend.isNeedScrollable - ? EdgeInsets.fromLTRB(legendPadding, 0, 0, 0) - : EdgeInsets.fromLTRB( - legendPadding, legendPadding / 2, 0, legendPadding / 2); - } else if (overflowMode == LegendItemOverflowMode.scroll) { - padding = EdgeInsets.fromLTRB(legendPadding, 0, 0, 0); - } else { - padding = EdgeInsets.fromLTRB( - legendPadding, - needPadding && needTitle ? (legendPadding / 2) : 0, - needPadding && needTitle ? legendPadding : 0, - needPadding && needTitle ? (legendPadding / 2) : 0); - } - } else { - if (overflowMode == LegendItemOverflowMode.wrap) { - padding = !chartLegend.isNeedScrollable - ? EdgeInsets.fromLTRB(0, legendPadding / 2, 0, 0) - : EdgeInsets.fromLTRB(legendPadding, legendPadding / 2, 0, 0); - } else if (overflowMode == LegendItemOverflowMode.scroll) { - padding = EdgeInsets.fromLTRB(legendPadding / 2, legendPadding / 2, - needTitle ? (legendPadding / 2) : 0, legendPadding / 2); - } else { - padding = EdgeInsets.fromLTRB(needTitle ? legendPadding : 0, - legendPadding / 2, needTitle ? legendPadding : 0, 0); - } - } - } else if (legendPosition == LegendPosition.right || - legendPosition == LegendPosition.left) { - if (legendOrientation == LegendItemOrientation.horizontal) { - if (overflowMode == LegendItemOverflowMode.wrap) { - padding = !chartLegend.isNeedScrollable - ? EdgeInsets.fromLTRB( - needTitle ? 0 : legendPadding, - needTitle ? (legendPadding / 2) : 0, - 0, - needTitle ? (legendPadding / 2) : 0) - : EdgeInsets.fromLTRB( - needTitle ? (legendPadding / 2) : legendPadding, - legendPadding / 2, - 0, - legendPadding / 2); - } else if (overflowMode == LegendItemOverflowMode.scroll) { - padding = EdgeInsets.fromLTRB( - legendPadding, - needTitle ? (legendPadding / 2) : 0, - 0, - needTitle ? (legendPadding / 2) : 0); - } else { - padding = EdgeInsets.fromLTRB( - legendPadding, - needTitle ? (legendPadding / 2) : 0, - needTitle ? legendPadding : 0, - 0); - } - } else { - if (overflowMode == LegendItemOverflowMode.wrap) { - padding = !chartLegend.isNeedScrollable - ? EdgeInsets.fromLTRB(needPadding ? (legendPadding / 2) : 0, - legendPadding / 2, 0, needTitle ? (legendPadding / 2) : 0) - : EdgeInsets.fromLTRB(legendPadding, legendPadding / 2, 0, - needTitle ? (legendPadding / 2) : 0); - } else if (overflowMode == LegendItemOverflowMode.scroll) { - padding = EdgeInsets.fromLTRB(legendPadding / 2, legendPadding / 2, - legendPadding / 2, legendPadding / 2); - } else { - padding = EdgeInsets.fromLTRB( - 0, legendPadding / 2, 0, needTitle ? legendPadding / 2 : 0); - } - } - } else { - padding = EdgeInsets.zero; - } - return padding; -} - -/// Method to handle cartesian series legend toggling for SfLegend. -void cartesianToggle(int index, CartesianStateProperties stateProperties) { - LegendTapArgs legendTapArgs; - MeasureWidgetContext _measureWidgetContext; - LegendRenderContext _legendRenderContext; - final SfCartesianChart chart = stateProperties.chart; - stateProperties.isTooltipHidden = true; - if (chart.onLegendTapped != null) { - if (chart.legend.legendItemBuilder != null) { - _measureWidgetContext = - stateProperties.renderingDetails.legendWidgetContext[index]; - legendTapArgs = LegendTapArgs( - stateProperties.chartSeries - .visibleSeriesRenderers[_measureWidgetContext.seriesIndex!], - _measureWidgetContext.seriesIndex!, - 0); - } else { - _legendRenderContext = stateProperties - .renderingDetails.chartLegend.legendCollections![index]; - legendTapArgs = LegendTapArgs( - _legendRenderContext.series, _legendRenderContext.seriesIndex, 0); - } - chart.onLegendTapped!(legendTapArgs); - } - if (chart.legend.toggleSeriesVisibility == true) { - if (chart.legend.legendItemBuilder != null) { - legendToggleTemplateState( - stateProperties.renderingDetails.legendWidgetContext[index], - stateProperties, - ''); - } else { - cartesianLegendToggleState( - stateProperties - .renderingDetails.chartLegend.legendCollections![index], - stateProperties); - } - stateProperties.renderingDetails.isLegendToggled = true; - stateProperties.legendToggling = true; - stateProperties.redraw(); - } -} - -/// Method to handle Circular and triangular series legend toggling for SfLegend. -void circularAndTriangularToggle(int index, dynamic stateProperties) { - LegendTapArgs legendTapArgs; - const int seriesIndex = 0; - final dynamic chart = stateProperties.chart; - final ChartLegend chartLegend = stateProperties.renderingDetails.chartLegend; - stateProperties.isTooltipHidden = true; - if (chart.onLegendTapped != null) { - if (chart != null) { - legendTapArgs = LegendTapArgs(chart.series, seriesIndex, index); - } else { - legendTapArgs = - LegendTapArgs(chart._series[seriesIndex], seriesIndex, index); - } - chart.onLegendTapped(legendTapArgs); - } - if (chart.legend.toggleSeriesVisibility == true) { - if (chart.legend.legendItemBuilder != null) { - final MeasureWidgetContext legendWidgetContext = - stateProperties.renderingDetails.legendWidgetContext[index]; - legendToggleTemplateState(legendWidgetContext, stateProperties, ''); - } else { - legendToggleState(chartLegend.legendCollections![index], stateProperties); - } - stateProperties.renderingDetails.isLegendToggled = true; - stateProperties.redraw(); - } -} - -/// Represents the value of measure widget size. -class MeasureWidgetSize extends StatelessWidget { - /// Creates an instance of measure widget size. - const MeasureWidgetSize( - {required this.stateProperties, - this.currentWidget, - this.opacityValue, - this.currentKey, - this.seriesIndex, - this.pointIndex, - this.type}); - - /// Holds the state properties value. - final StateProperties stateProperties; - - /// Holds the value of current widget. - final Widget? currentWidget; - - /// Holds the opacity value. - final double? opacityValue; - - /// Holds the current key value. - final Key? currentKey; - - /// Holds the series index value. - final int? seriesIndex; - - /// Holds the point index value. - final int? pointIndex; - - /// Holds the value of type. - final String? type; - @override - Widget build(BuildContext context) { - final List templates = - stateProperties.renderingDetails.legendWidgetContext; - templates.add(MeasureWidgetContext( - widget: currentWidget, - key: currentKey, - context: context, - seriesIndex: seriesIndex, - pointIndex: pointIndex)); - return Container( - key: currentKey, - child: Opacity(opacity: opacityValue!, child: currentWidget)); - } -} - -/// To return legend template toggled state. -bool legendToggleTemplateState(MeasureWidgetContext currentItem, - StateProperties stateProperties, String checkType) { - bool needSelect = false; - final List legendToggles = - stateProperties.renderingDetails.legendToggleTemplateStates; - if (legendToggles.isNotEmpty) { - for (int i = 0; i < legendToggles.length; i++) { - final MeasureWidgetContext item = legendToggles[i]; - if (currentItem.seriesIndex == item.seriesIndex && - currentItem.pointIndex == item.pointIndex) { - if (checkType != 'isSelect') { - needSelect = true; - legendToggles.removeAt(i); - } - break; - } - } - } - if (!needSelect) { - needSelect = false; - if (checkType != 'isSelect') { - legendToggles.add(currentItem); - } - } - return needSelect; -} - -/// To add legend toggle states in legend toggles list. -void legendToggleState( - LegendRenderContext currentItem, StateProperties stateProperties) { - bool needSelect = false; - final List legendToggles = - stateProperties.renderingDetails.legendToggleStates; - if (legendToggles.isNotEmpty) { - for (int i = 0; i < legendToggles.length; i++) { - final LegendRenderContext item = legendToggles[i]; - if (currentItem.seriesIndex == item.seriesIndex) { - needSelect = true; - legendToggles.removeAt(i); - break; - } - } - } - if (!needSelect) { - needSelect = false; - legendToggles.add(currentItem); - } -} - -/// To add cartesian legend toggle states. -void cartesianLegendToggleState( - LegendRenderContext currentItem, CartesianStateProperties stateProperties) { - bool needSelect = false; - final List legendToggles = - stateProperties.renderingDetails.legendToggleStates; - if (currentItem.trendline == null || - SeriesHelper.getSeriesRendererDetails(stateProperties - .chartSeries.visibleSeriesRenderers[currentItem.seriesIndex]) - .visible! == - true) { - if (legendToggles.isNotEmpty) { - for (int i = 0; i < legendToggles.length; i++) { - final LegendRenderContext item = legendToggles[i]; - if (currentItem.trendline != null && - currentItem.trendline == item.trendline && - (currentItem.seriesIndex == item.seriesIndex && - currentItem.trendlineIndex == item.trendlineIndex)) { - needSelect = true; - legendToggles.removeAt(i); - break; - } else if (currentItem.trendline == null && - currentItem.seriesIndex == item.seriesIndex && - !item.isTrendline! && - item.seriesRenderer is! TechnicalIndicators - ? currentItem.series == item.series - : currentItem.text == item.text) { - needSelect = true; - legendToggles.removeAt(i); - break; - } - } - } - if (!needSelect) { - if (!(currentItem.seriesRenderer is TechnicalIndicators - ? !(currentItem.indicatorRenderer!.visible! && - currentItem.indicatorRenderer!.technicalIndicatorRenderer.period > - 0) - : SeriesHelper.getSeriesRendererDetails( - currentItem.seriesRenderer.renderer) - .visible == - false && - stateProperties.isTrendlineToggled == false)) { - needSelect = false; - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails(stateProperties - .chartSeries.visibleSeriesRenderers[currentItem.seriesIndex]); - if (currentItem.trendlineIndex != null) { - seriesRendererDetails.minimumX = 1 / 0; - seriesRendererDetails.minimumY = 1 / 0; - seriesRendererDetails.maximumX = -1 / 0; - seriesRendererDetails.maximumY = -1 / 0; - } - stateProperties - .chartSeries.visibleSeriesRenderers[currentItem.seriesIndex] = - seriesRendererDetails.renderer; - if (!legendToggles.contains(currentItem)) { - legendToggles.add(currentItem); - } - } - } - } -} - -/// For checking whether elements collide. -bool findingCollision(Rect rect, List regions, [Rect? pathRect]) { - bool isCollide = false; - if (pathRect != null && - (pathRect.left < rect.left && - pathRect.width > rect.width && - pathRect.top < rect.top && - pathRect.height > rect.height)) { - isCollide = false; - } else if (pathRect != null) { - isCollide = true; - } - for (int i = 0; i < regions.length; i++) { - final Rect regionRect = regions[i]; - if ((rect.left < regionRect.left + regionRect.width && - rect.left + rect.width > regionRect.left) && - (rect.top < regionRect.top + regionRect.height && - rect.top + rect.height > regionRect.top)) { - isCollide = true; - break; - } - } - return isCollide; -} - -/// To find the labels are intersect. -bool isOverlap(Rect currentRect, Rect rect) { - return currentRect.left < rect.left + rect.width && - currentRect.left + currentRect.width > rect.left && - currentRect.top < (rect.top + rect.height) && - (currentRect.height + currentRect.top) > rect.top; -} - -/// To trim the text by given width. -String getTrimmedText(String text, num labelsExtent, TextStyle labelStyle, - {ChartAxisRenderer? axisRenderer, bool? isRtl}) { - String label = text; - ChartAxisRendererDetails? axisRendererDetails; - axisRendererDetails = axisRenderer != null - ? AxisHelper.getAxisRendererDetails(axisRenderer) - : null; - num size = axisRenderer != null - ? measureText(text, axisRendererDetails!.axis.labelStyle, - axisRendererDetails.labelRotation) - .width - : measureText(label, labelStyle).width; - if (size > labelsExtent) { - final int textLength = text.length; - if (isRtl == true) { - for (int i = 0; i < textLength - 1; i++) { - label = '...${text.substring(i + 1, textLength)}'; - size = axisRenderer != null - ? measureText(label, labelStyle, axisRendererDetails!.labelRotation) - .width - : measureText(label, labelStyle).width; - if (size <= labelsExtent) { - return label == '...' ? '' : label; - } - } - } else { - for (int i = textLength - 1; i >= 0; --i) { - label = '${text.substring(0, i)}...'; - size = axisRenderer != null - ? measureText(label, labelStyle, axisRendererDetails!.labelRotation) - .width - : measureText(label, labelStyle).width; - if (size <= labelsExtent) { - return label == '...' ? '' : label; - } - } - } - } - return label == '...' ? '' : label; -} - -/// To get equivalent value for the percentage. -num getValueByPercentage(num value1, num value2) { - return value1.isNegative - ? (num.tryParse( - '-${num.tryParse(value1.toString().replaceAll(RegExp('-'), ''))! % value2}'))! - : (value1 % value2); -} - -/// Method to render the chart title. -Widget renderChartTitle(StateProperties stateProperties) { - Widget titleWidget; - final dynamic widget = stateProperties.chart; - if (widget.title.text != null && widget.title.text.isNotEmpty == true) { - final SfChartThemeData chartTheme = - stateProperties.renderingDetails.chartTheme; - final Color color = - widget.title.textStyle.color ?? chartTheme.titleTextColor; - final double fontSize = widget.title.textStyle.fontSize; - final String fontFamily = widget.title.textStyle.fontFamily; - final FontStyle fontStyle = widget.title.textStyle.fontStyle; - final FontWeight fontWeight = widget.title.textStyle.fontWeight; - titleWidget = Container( - margin: EdgeInsets.fromLTRB(widget.margin.left, widget.margin.top, - widget.margin.right, widget.margin.bottom), - child: Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), - decoration: BoxDecoration( - color: widget.title.backgroundColor ?? - chartTheme.titleBackgroundColor, - border: Border.all( - color: widget.title.borderColor ?? chartTheme.titleTextColor, - width: widget.title.borderWidth)), - child: Text(widget.title.text, - style: TextStyle( - color: color, - fontSize: fontSize, - fontFamily: fontFamily, - fontStyle: fontStyle, - fontWeight: fontWeight), - textScaleFactor: 1.2, - overflow: TextOverflow.clip, - textAlign: TextAlign.center)), - alignment: (widget.title.alignment == ChartAlignment.near) - ? Alignment.topLeft - : (widget.title.alignment == ChartAlignment.far) - ? Alignment.topRight - : (widget.title.alignment == ChartAlignment.center) - ? Alignment.topCenter - : Alignment.topCenter, - ); - } else { - titleWidget = Container(); - } - return titleWidget; -} - -/// To get the legend template widgets. -List bindLegendTemplateWidgets(dynamic stateProperties) { - Widget legendWidget; - final dynamic widget = stateProperties.chart; - final List templates = []; - stateProperties.renderingDetails.chartWidgets = []; - - if (widget.legend.isVisible == true && - widget.legend.legendItemBuilder != null) { - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final dynamic seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[i]; - for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { - legendWidget = widget.legend.legendItemBuilder( - seriesRenderer.renderPoints[j].x, - seriesRenderer, - seriesRenderer.renderPoints[j], - j); - templates.add(MeasureWidgetSize( - stateProperties: stateProperties, - type: 'Legend', - seriesIndex: i, - pointIndex: j, - currentKey: GlobalKey(), - currentWidget: legendWidget, - opacityValue: 0.0)); - } - } - } - return templates; -} - -/// To check whether indexes are valid. -bool validIndex(int? _pointIndex, int? _seriesIndex, dynamic chart) { - return _seriesIndex != null && - _pointIndex != null && - _seriesIndex >= 0 && - _seriesIndex < chart.series.length && - _pointIndex >= 0 && - _pointIndex < chart.series[_seriesIndex].dataSource.length; -} - -/// This method removes the given listener from the animation controller and then dsiposes it. -void disposeAnimationController( - AnimationController? animationController, VoidCallback listener) { - if (animationController != null) { - animationController.removeListener(listener); - animationController.dispose(); - animationController = null; - } -} - -/// Method to calculate the series point index. -void calculatePointSeriesIndex( - dynamic chart, dynamic stateProperties, Offset? position, - [Region? pointRegion, ActivationMode? activationMode]) { - if (chart is SfCartesianChart) { - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - final SeriesRendererDetails seriesRendererDetails = - SeriesHelper.getSeriesRendererDetails( - stateProperties.chartSeries.visibleSeriesRenderers[i]); - final String _seriesType = seriesRendererDetails.seriesType; - int? pointIndex; - final double padding = (_seriesType == 'bubble') || - (_seriesType == 'scatter') || - (_seriesType == 'bar') || - (_seriesType == 'column' || - _seriesType == 'rangecolumn' || - _seriesType.contains('stackedcolumn') || - _seriesType.contains('stackedbar') || - _seriesType == 'waterfall') - ? 0 - : 15; - - /// Regional padding to detect smooth touch. - seriesRendererDetails.regionalData! - .forEach((dynamic regionRect, dynamic values) { - 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; - final Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); - if (paddedRegion.contains(position!)) { - pointIndex = regionRect[4].visiblePointIndex; - } - }); - - if (pointIndex != null && seriesRendererDetails.visible! == true) { - if ((seriesRendererDetails.series.onPointTap != null || - seriesRendererDetails.series.onPointDoubleTap != null || - seriesRendererDetails.series.onPointLongPress != null) && - activationMode != null) { - ChartPointDetails pointInteractionDetails; - pointInteractionDetails = ChartPointDetails( - i, - pointIndex!, - seriesRendererDetails.dataPoints, - seriesRendererDetails - .visibleDataPoints![pointIndex!].overallDataPointIndex); - activationMode == ActivationMode.singleTap - ? seriesRendererDetails - .series.onPointTap!(pointInteractionDetails) - : activationMode == ActivationMode.doubleTap - ? seriesRendererDetails - .series.onPointDoubleTap!(pointInteractionDetails) - : seriesRendererDetails - .series.onPointLongPress!(pointInteractionDetails); - } - } - } - } else if (chart is SfCircularChart) { - const int seriesIndex = 0; - if ((chart.series[seriesIndex].onPointTap != null || - chart.series[seriesIndex].onPointDoubleTap != null || - chart.series[seriesIndex].onPointLongPress != null) && - activationMode != null) { - ChartPointDetails pointInteractionDetails; - pointInteractionDetails = ChartPointDetails( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - stateProperties - .chartSeries.visibleSeriesRenderers[seriesIndex].dataPoints, - pointRegion?.pointIndex); - activationMode == ActivationMode.singleTap - ? chart.series[seriesIndex].onPointTap!(pointInteractionDetails) - : activationMode == ActivationMode.doubleTap - ? chart.series[seriesIndex] - .onPointDoubleTap!(pointInteractionDetails) - : chart.series[seriesIndex] - .onPointLongPress!(pointInteractionDetails); - } - } else { - int? index; - const int seriesIndex = 0; - for (int i = 0; i < stateProperties.renderPoints!.length; i++) { - if (stateProperties.renderPoints![i].region != null && - stateProperties.renderPoints![i].region!.contains(position) == true) { - index = i; - break; - } - } - if (index != null) { - if ((chart.series.onPointTap != null || - chart.series.onPointDoubleTap != null || - chart.series.onPointLongPress != null) && - activationMode != null) { - ChartPointDetails pointInteractionDetails; - pointInteractionDetails = ChartPointDetails( - seriesIndex, index, stateProperties.dataPoints, index); - activationMode == ActivationMode.singleTap - ? chart.series.onPointTap!(pointInteractionDetails) - : activationMode == ActivationMode.doubleTap - ? chart.series.onPointDoubleTap!(pointInteractionDetails) - : chart.series.onPointLongPress!(pointInteractionDetails); - } - } - } -} - -/// Point to pixel. -/// Used dynamic as the seriesRenderer can either be funnel or pyramid type series renderer. -Offset pyramidFunnelPointToPixel( - PointInfo point, dynamic seriesRenderer) { - Offset location; - if (point.region == null) { - final dynamic x = point.x; - final num y = point.y!; - for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { - if (seriesRenderer.dataPoints[i].x == x && - seriesRenderer.dataPoints[i].y == y) { - point = seriesRenderer.dataPoints[i]; - } - } - } - if (seriesRenderer.series.dataLabelSettings.isVisible == false) { - point.symbolLocation = Offset(point.region!.left + point.region!.width / 2, - point.region!.top + point.region!.height / 2); - } - location = point.symbolLocation; - location = Offset(location.dx, location.dy); - return location; -} - -/// Pixel to point. -PointInfo pyramidFunnelPixelToPoint( - Offset position, dynamic seriesRenderer) { - final dynamic chartState = seriesRenderer.stateProperties; - const int seriesIndex = 0; - - late int? pointIndex; - bool isPoint; - for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { - if (seriesRenderer.renderPoints![j].isVisible == true) { - isPoint = isPointInPolygon( - seriesRenderer.renderPoints![j].pathRegion, position); - if (isPoint) { - pointIndex = j; - break; - } - } - } - final PointInfo chartPoint = chartState - .chartSeries.visibleSeriesRenderers[seriesIndex].renderPoints[pointIndex]; - - return chartPoint; -} - -/// Add the ellipse with trimmed text. -String addEllipse(String text, int maxLength, String ellipse, {bool? isRtl}) { - if (isRtl == true) { - if (text.contains(ellipse)) { - text = text.replaceAll(ellipse, ''); - text = text.substring(1, text.length); - } else { - text = text.substring(ellipse.length, text.length); - } - return ellipse + text; - } else { - maxLength--; - final int length = maxLength - ellipse.length; - final String trimText = text.substring(0, length); - return trimText + ellipse; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart deleted file mode 100644 index 2f7edd0be..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/chart_series/xy_data_series.dart'; -import '../../chart/common/trackball_marker_settings.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/renderer/circular_series_controller.dart'; -import '../../funnel_chart/renderer/funnel_series.dart'; -import '../../pyramid_chart/renderer/series_controller.dart'; -import '../event_args.dart'; -import '../series/chart_series.dart'; - -/// typedef belongs SfCartesianChart - -/// Returns the TooltipArgs. -typedef ChartTooltipCallback = void Function(TooltipArgs tooltipArgs); - -/// Returns the ActualRangeChangedArgs. -typedef ChartActualRangeChangedCallback = void Function( - ActualRangeChangedArgs rangeChangedArgs); - -/// Signature for the [axisLabelFormatter] callback that returns [ChartAxisLabel] class value to -/// customize the axis label text and style. -typedef ChartLabelFormatterCallback = ChartAxisLabel Function( - AxisLabelRenderDetails axisLabelRenderArgs); - -/// Signature for the [multiLevelLabelFormatter] callback that returns [ChartAxisLabel]. -typedef MultiLevelLabelFormatterCallback = ChartAxisLabel Function( - MultiLevelLabelRenderDetails multiLevelLabelRenderArgs); - -/// Returns the DataLabelRenderArgs. -typedef ChartDataLabelRenderCallback = void Function( - DataLabelRenderArgs dataLabelArgs); - -/// Returns the LegendRenderArgs. -typedef ChartLegendRenderCallback = void Function( - LegendRenderArgs legendRenderArgs); - -/// Returns the Trendline args -typedef ChartTrendlineRenderCallback = void Function( - TrendlineRenderParams trendlineRenderParams); - -///Returns the TrackballArgs. -typedef ChartTrackballCallback = void Function(TrackballArgs trackballArgs); - -/// Returns the CrosshairRenderArgs -typedef ChartCrosshairCallback = void Function( - CrosshairRenderArgs crosshairArgs); - -/// Returns the ZoomPanArgs. -typedef ChartZoomingCallback = void Function(ZoomPanArgs zoomingArgs); - -/// Returns the ChartPointDetails. -typedef ChartPointInteractionCallback = void Function( - ChartPointDetails pointInteractionDetails); - -/// Returns the AxisLabelTapArgs. -typedef ChartAxisLabelTapCallback = void Function( - AxisLabelTapArgs axisLabelTapArgs); - -/// Returns the LegendTapArgs. -typedef ChartLegendTapCallback = void Function(LegendTapArgs legendTapArgs); - -/// Returns the SelectionArgs. -typedef ChartSelectionCallback = void Function(SelectionArgs selectionArgs); - -/// Returns the offset. -typedef ChartTouchInteractionCallback = void Function( - ChartTouchInteractionArgs tapArgs); - -/// Returns the IndicatorRenderArgs. -typedef ChartIndicatorRenderCallback = TechnicalIndicatorRenderDetails Function( - IndicatorRenderParams indicatorRenderParams); - -/// 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); - -/// Called when the series renderer is created. -typedef SeriesRendererCreatedCallback = void Function( - ChartSeriesController controller); - -/// Returns the widget. -typedef ChartDataLabelTemplateBuilder = Widget Function( - T data, CartesianChartPoint point, int pointIndex, - {int seriesIndex, CartesianSeries series}); - -/// typedef common for all the chart types -/// -/// Signature for callback reporting that a data label is tapped. -/// -/// Also refer `onDataLabelTapped` event and [DataLabelTapDetails] class. -typedef DataLabelTapCallback = void Function(DataLabelTapDetails onTapArgs); - -/// Returns the widget. -/// -/// Customize the appearance of legend items with your template by -/// using legendItemBuilder property of legend. -typedef LegendItemBuilder = Widget Function( - String legendText, dynamic series, dynamic point, int seriesIndex); - -/// Maps the index value. -typedef ChartIndexedValueMapper = R? Function(int index); - -/// Maps the data from data source. -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 -typedef ChartSeriesRendererFactory = ChartSeriesRenderer Function( - ChartSeries series); - -/// typedef belongs SfCircularChart - -/// Returns the LegendRenderArgs. -typedef CircularLegendRenderCallback = void Function( - LegendRenderArgs legendRenderArgs); - -/// Returns the TooltipArgs. -typedef CircularTooltipCallback = void Function(TooltipArgs tooltipArgs); - -/// Returns the DataLabelRenderArgs. -typedef CircularDatalabelRenderCallback = void Function( - DataLabelRenderArgs dataLabelArgs); - -/// Returns the SelectionArgs. -typedef CircularSelectionCallback = void Function(SelectionArgs selectionArgs); - -/// Returns the offset. -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); - -/// Return the controller for circular series. -typedef CircularSeriesRendererCreatedCallback = void Function( - CircularSeriesController controller); - -// typedef belongs to SfFunnelChart - -/// Returns the LegendRenderArgs. -typedef FunnelLegendRenderCallback = void Function( - LegendRenderArgs legendRenderArganimateCompleteds); - -/// Returns the TooltipArgs. -typedef FunnelTooltipCallback = void Function(TooltipArgs tooltipArgs); - -/// Returns the DataLabelRenderArgs. -typedef FunnelDataLabelRenderCallback = void Function( - DataLabelRenderArgs dataLabelArgs); - -/// Returns the SelectionArgs. -typedef FunnelSelectionCallback = void Function(SelectionArgs selectionArgs); - -/// Returns the offset. -typedef FunnelTouchInteractionCallback = void Function( - ChartTouchInteractionArgs tapArgs); - -/// Called when the renderer for the funnel series is created. -typedef FunnelSeriesRendererCreatedCallback = void Function( - FunnelSeriesController controller); - -// typedef belongs to SfPyramidChart - -/// Returns the LegendRenderArgs. -typedef PyramidLegendRenderCallback = void Function( - LegendRenderArgs legendRenderArganimateCompleteds); - -/// Returns the TooltipArgs. -typedef PyramidTooltipCallback = void Function(TooltipArgs tooltipArgs); - -/// Returns the DataLabelRenderArgs. -typedef PyramidDataLabelRenderCallback = void Function( - DataLabelRenderArgs dataLabelArgs); - -/// Returns the SelectionArgs. -typedef PyramidSelectionCallback = void Function(SelectionArgs selectionArgs); - -/// Returns the Offset. -typedef PyramidTouchInteractionCallback = void Function( - ChartTouchInteractionArgs tapArgs); - -/// Called when the pyramid series is created. -typedef PyramidSeriesRendererCreatedCallback = void Function( - PyramidSeriesController controller); - -/// Callback definition for error bar event. -typedef ChartErrorBarRenderCallback = void Function( - ErrorBarRenderDetails errorBarRenderDetails); - -//// Callback definition for cartesian shader events. -typedef CartesianShaderCallback = Shader Function(ShaderDetails shaderDetails); 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 deleted file mode 100644 index d9b843884..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart +++ /dev/null @@ -1,855 +0,0 @@ -import 'dart:ui' as dart_ui; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -import '../../chart/utils/helper.dart'; -import '../../common/common.dart'; -import '../../common/legend/legend.dart'; -import '../../common/legend/renderer.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/helper.dart'; -import '../base/funnel_plot_area.dart'; -import '../base/funnel_state_properties.dart'; -import '../base/series_base.dart'; -import '../renderer/renderer_extension.dart'; - -/// Renders the funnel chart. -/// -/// A funnel chart is a specialized chart type that demonstrates the flow of users through a business or sales process. -/// The chart begins with a broad head and ends in a narrow neck. -/// -/// The number of users at each stage of the process are indicated from the funnel's width as it narrows. -/// -/// To render a funnel chart, create an instance of FunnelSeries, and add it to the series property of [SfFunnelChart]. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} -//ignore:must_be_immutable -class SfFunnelChart extends StatefulWidget { - /// Creating an argument constructor of SfFunnelChart class. - SfFunnelChart({ - Key? key, - this.backgroundColor, - this.backgroundImage, - this.borderColor = Colors.transparent, - this.borderWidth = 0.0, - this.onLegendItemRender, - this.onTooltipRender, - this.onDataLabelRender, - this.onLegendTapped, - this.onDataLabelTapped, - this.onSelectionChanged, - this.onChartTouchInteractionUp, - this.onChartTouchInteractionDown, - this.onChartTouchInteractionMove, - ChartTitle? title, - FunnelSeries? series, - EdgeInsets? margin, - Legend? legend, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - Color.fromRGBO(255, 240, 219, 1), - Color.fromRGBO(238, 238, 238, 1) - ], - TooltipBehavior? tooltipBehavior, - ActivationMode? selectionGesture, - bool? enableMultiSelection, - }) : title = title ?? ChartTitle(), - series = series ?? FunnelSeries(), - margin = margin ?? const EdgeInsets.fromLTRB(10, 10, 10, 10), - legend = legend ?? Legend(), - tooltipBehavior = tooltipBehavior ?? TooltipBehavior(), - selectionGesture = selectionGesture ?? ActivationMode.singleTap, - enableMultiSelection = enableMultiSelection ?? false, - super(key: key); - - /// Customizes the chart title. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// title: ChartTitle(text: 'Funnel Chart') - /// ) - /// ); - ///} - ///``` - final ChartTitle title; - - /// Background color of the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// backgroundColor: Colors.blue - /// ) - /// ); - ///} - ///``` - final Color? backgroundColor; - - /// Border color of the chart. - /// - /// Defaults to `Colors.transparent`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// borderColor: Colors.blue - /// ) - /// ); - ///} - ///``` - final Color borderColor; - - /// Border width of the chart. - /// - /// Defaults to `0.0`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// borderWidth: 2 - /// ) - /// ); - ///} - ///``` - final double borderWidth; - - /// Customizes the chart series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries<_FunnelData, String>( - /// dataSource: data, - /// xValueMapper: (_FunnelData data, _) => data.xData, - /// yValueMapper: (_FunnelData data, _) => data.yData) - /// ) - /// ); - ///} - ///``` - final FunnelSeries series; - - /// Margin for chart. - /// - /// Defaults to `const EdgeInsets.fromLTRB(10, 10, 10, 10)`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// margin: const EdgeInsets.all(2), - /// ) - /// ); - ///} - ///``` - final EdgeInsets margin; - - /// Customizes the legend in the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// legend: Legend(isVisible: true) - /// ) - /// ); - ///} - ///``` - final Legend legend; - - /// 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. - /// - /// Ten colors are available by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// palette: [Colors.red, Colors.green] - /// ) - /// ); - ///} - ///``` - final List palette; - - /// Customizes the tooltip in chart. - /// - ///```dart - ///TooltipBehavior _tooltipBehavior; - /// - ///@override - ///void initState() { - /// _tooltipBehavior = TooltipBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// tooltipBehavior: _tooltipBehavior - /// ) - /// ); - ///} - ///``` - final TooltipBehavior tooltipBehavior; - - /// Occurs while legend is rendered. - /// - /// Here, you can get the legend's text, shape, series index, and point index case of funnel series. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// legend: Legend(isVisible: true), - /// onLegendItemRender: (LegendRendererArgs args) => legend(args), - /// ) - /// ); - ///} - ///void legend(LegendRendererArgs args) { - /// args.legendIconType = LegendIconType.diamond; - ///} - ///``` - final FunnelLegendRenderCallback? onLegendItemRender; - - /// Occurs while tooltip is rendered. - /// - /// Here, you can get the Tooltip render arguments and customize them. - final FunnelTooltipCallback? onTooltipRender; - - /// Occurs when the data label is rendered. - /// - /// Here we can get get the data label render arguments and customize the data label parameters. - final FunnelDataLabelRenderCallback? onDataLabelRender; - - /// Occurs when the legend is tapped, using this event the legend tap arguments can be customized. - final ChartLegendTapCallback? onLegendTapped; - - /// Customizes the points or series selection behavior. - /// - /// It can also be selected at the initial rendering using this property. - /// - /// Defaults to `[]`. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// initialSelectedDataIndexes: [1,0], - /// selectionBehavior: _selectionBehavior - /// ), - /// ) - /// ); - ///} - ///``` - - /// Gesture for activating the selection. - /// - /// Selection can be activated in `ActivationMode.none`, `ActivationMode.singleTap`, - /// `ActivationMode.doubleTap`, and `ActivationMode.longPress`. - /// - /// Defaults to `ActivationMode.singleTap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// selectionGesture: ActivationMode.singleTap, - /// series: FunnelSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ) - /// ); - ///} - ///``` - final ActivationMode selectionGesture; - - /// Enables or disables the multiple data points selection. - /// - /// Defaults to `false`. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// enableMultiSelection: true, - /// series: FunnelSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ) - /// ); - ///} - ///``` - final bool enableMultiSelection; - - /// Background image for chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// backgroundImage: const AssetImage('image.png'), - /// ) - /// ); - ///} - ///``` - final ImageProvider? backgroundImage; - - /// Occurs while selection changes. Here, you can get the series, selected color, - /// unselected color, selected border color, unselected border color, selected - /// border width, unselected border width, series index, and point index. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// onSelectionChanged: (SelectionArgs args) => select(args), - /// ) - /// ); - ///} - ///void select(SelectionArgs args) { - /// print(args.selectedBorderColor); - ///} - ///``` - final FunnelSelectionCallback? onSelectionChanged; - - /// Occurs when tapped on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final FunnelTouchInteractionCallback? onChartTouchInteractionUp; - - /// Occurs when touched on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final FunnelTouchInteractionCallback? onChartTouchInteractionDown; - - /// Occurs when touched and moved on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final FunnelTouchInteractionCallback? onChartTouchInteractionMove; - - /// Called when the data label is tapped. - /// - /// 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. - /// - /// _Note:_ This callback will not be called, when the builder is specified for data label - /// (data label template). For this case, custom widget specified in the `DataLabelSettings.builder` property - /// can be wrapped using the `GestureDetector` and this functionality can be achieved in the application level. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// onDataLabelTapped: (DataLabelTapDetails args) { - /// print(arg.seriesIndex); - /// } - /// ) - /// ); - ///} - /// - ///``` - - final DataLabelTapCallback? onDataLabelTapped; - - @override - State createState() => SfFunnelChartState(); -} - -/// Represents the state class of [SfFunnelChart] widget. -class SfFunnelChartState extends State - with TickerProviderStateMixin { - late FunnelStateProperties _stateProperties; - - /// 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() { - _stateProperties = FunnelStateProperties( - renderingDetails: RenderingDetails(), chartState: this); - _initializeDefaultValues(); - // Create the series renderer while initial rendering // - _createAndUpdateSeriesRenderer(); - 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() { - _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); - _stateProperties.renderingDetails.isRtl = - Directionality.of(context) == TextDirection.rtl; - 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 // - _createAndUpdateSeriesRenderer(oldWidget); - - super.didUpdateWidget(oldWidget); - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.chartTooltipState != null) { - tooltipRenderingDetails.show = false; - } - _stateProperties.renderingDetails.isLegendToggled = false; - _stateProperties.renderingDetails.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) { - _stateProperties.renderingDetails.oldDeviceOrientation = - _stateProperties.renderingDetails.oldDeviceOrientation == null - ? MediaQuery.of(context).orientation - : _stateProperties.renderingDetails.deviceOrientation; - _stateProperties.renderingDetails.deviceOrientation = - MediaQuery.of(context).orientation; - _stateProperties.isTooltipOrientationChanged = false; - return RepaintBoundary( - child: ChartContainer( - child: GestureDetector( - child: Container( - decoration: BoxDecoration( - color: widget.backgroundColor, - image: widget.backgroundImage != null - ? DecorationImage( - image: widget.backgroundImage!, - fit: BoxFit.fill) - : null, - border: Border.all( - color: widget.borderColor, - width: widget.borderWidth)), - child: Column( - children: [ - renderChartTitle(_stateProperties), - _renderChartElements() - ], - ))))); - } - - /// 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 life cycle is terminal: there is no way to remount a [State] object that has been disposed. - /// - /// Sub classes 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( - _stateProperties.renderingDetails.animationController, - _repaintChartElements); - super.dispose(); - } - - /// Method to convert the [SfFunnelChart] as an 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. - /// - /// ```dart - /// final GlobalKey _key = GlobalKey(); - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Column( - /// children: [SfFunnelChart( - /// key: _key - /// series: FunnelSeries<_FunnelData, String>( - /// dataSource: data, - /// xValueMapper: (_FunnelData data, _) => data.xData, - /// yValueMapper: (_FunnelData data, _) => data.yData) - /// ), - /// ], - /// ), - /// RaisedButton( - /// child: Text( - /// 'To Image', - /// ), - /// onPressed: _renderImage, - /// shape: RoundedRectangleBorder( - /// borderRadius: BorderRadius.circular(20)), - /// ) - /// ], - /// ), - /// ); - /// } - /// - /// Future _renderImage() async { - /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); - /// final bytes = await data.toByteData(format: dart_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.white, - /// child: Image.memory(bytes.buffer.asUint8List()), - /// ), - /// ), - /// ); - /// }, - /// ), - /// ); - /// } - ///} - ///``` - - Future toImage({double pixelRatio = 1.0}) async { - 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 - return image; - } - - /// To initialize chart default values. - void _initializeDefaultValues() { - _stateProperties.chartSeries = FunnelChartBase(_stateProperties); - _stateProperties.renderingDetails.chartLegend = - ChartLegend(_stateProperties); - _stateProperties.renderingDetails.initialRender = true; - _stateProperties.renderingDetails.annotationController = - AnimationController(vsync: this); - _stateProperties.renderingDetails.seriesRepaintNotifier = - ValueNotifier(0); - _stateProperties.renderingDetails.legendToggleStates = - []; - _stateProperties.renderingDetails.legendToggleTemplateStates = - []; - _stateProperties.renderingDetails.explodedPoints = []; - _stateProperties.renderingDetails.animateCompleted = false; - _stateProperties.renderingDetails.isLegendToggled = false; - _stateProperties.renderingDetails.widgetNeedUpdate = false; - _stateProperties.renderingDetails.dataLabelTemplateRegions = []; - _stateProperties.renderingDetails.selectionData = []; - _stateProperties.renderingDetails.legendWidgetContext = - []; - _stateProperties.renderingDetails.animationController = - AnimationController(vsync: this)..addListener(_repaintChartElements); - _stateProperties.renderingDetails.tooltipBehaviorRenderer = - TooltipBehaviorRenderer(_stateProperties); - _stateProperties.renderingDetails.legendRenderer = - LegendRenderer(widget.legend); - } - - // In this method, create and update the series renderer for each series // - void _createAndUpdateSeriesRenderer([SfFunnelChart? oldWidget]) { - // ignore: unnecessary_null_comparison - if (widget.series != null) { - final FunnelSeriesRenderer? oldSeriesRenderer = - // ignore: unnecessary_null_comparison - oldWidget != null && oldWidget.series != null - ? _stateProperties.chartSeries.visibleSeriesRenderers[0] - : null; - - FunnelSeries series; - series = widget.series; - - // Create and update the series list here - FunnelSeriesRendererExtension seriesRenderers; - - if (oldSeriesRenderer != null && - isSameSeries(oldWidget!.series, series)) { - seriesRenderers = oldSeriesRenderer as FunnelSeriesRendererExtension; - } else { - final FunnelSeriesRenderer renderer = series.createRenderer(series); - seriesRenderers = renderer is FunnelSeriesRendererExtension - ? renderer - : FunnelSeriesRendererExtension(); - if (seriesRenderers.controller == null && - series.onRendererCreated != null) { - seriesRenderers.controller = FunnelSeriesController(seriesRenderers); - series.onRendererCreated!(seriesRenderers.controller!); - } - } - - seriesRenderers.series = series; - seriesRenderers.isSelectionEnable = series.selectionBehavior.enable; - seriesRenderers.stateProperties = _stateProperties; - _stateProperties.chartSeries.visibleSeriesRenderers - ..clear() - ..add(seriesRenderers); - } - } - - void _repaintChartElements() { - _stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - - /// To render chart elements. - Widget _renderChartElements() { - return Expanded(child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - Widget element; - if (widget.series.dataSource != null) { - _initialize(constraints); - _stateProperties.renderingDetails.prevSize = - _stateProperties.renderingDetails.prevSize ?? constraints.biggest; - _stateProperties.renderingDetails.didSizeChange = - _stateProperties.renderingDetails.prevSize != constraints.biggest; - _stateProperties.renderingDetails.prevSize = constraints.biggest; - final PointInfo tooltipPoint = - _getChartPoints(_stateProperties); - SchedulerBinding.instance!.addPostFrameCallback((_) { - _validateStateMaintenance(_stateProperties, tooltipPoint); - }); - _stateProperties.chartSeries.findVisibleSeries(); - _stateProperties.chartSeries.processDataPoints( - _stateProperties.chartSeries.visibleSeriesRenderers[0]); - final List legendTemplates = - bindLegendTemplateWidgets(_stateProperties); - if (legendTemplates.isNotEmpty && - _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { - // ignore: avoid_unnecessary_containers - element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); - } else { - _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( - _stateProperties.renderingDetails.chartLegend.chartSize); - _stateProperties.funnelplotArea = - FunnelPlotArea(stateProperties: _stateProperties); - element = getElements( - _stateProperties, _stateProperties.funnelplotArea, constraints)!; - } - } else { - element = Container(); - } - return element; - })); - } - - /// To refresh chart elements. - void _refresh() { - if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { - MeasureWidgetContext templateContext; - RenderBox renderBox; - for (int i = 0; - i < _stateProperties.renderingDetails.legendWidgetContext.length; - i++) { - templateContext = - _stateProperties.renderingDetails.legendWidgetContext[i]; - renderBox = templateContext.context!.findRenderObject() as RenderBox; - templateContext.size = renderBox.size; - } - setState(() { - /// The chart will be rebuilding again, Once legend template sizes will be calculated. - }); - } - } - - /// To initialize chart container. - void _initialize(BoxConstraints constraints) { - _stateProperties.renderingDetails.chartWidgets = []; - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - final EdgeInsets margin = widget.margin; - final bool isMobilePlatform = - defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS; - _stateProperties.renderingDetails.legendRenderer.legendPosition = - widget.legend.position == LegendPosition.auto - ? (height > width - ? isMobilePlatform - ? LegendPosition.top - : LegendPosition.bottom - : LegendPosition.right) - : widget.legend.position; - _stateProperties.renderingDetails.chartLegend.chartSize = Size( - width - margin.left - margin.right, - height - margin.top - margin.bottom); - } - - /// This will return tooltip chart point. - PointInfo _getChartPoints(FunnelStateProperties _stateProperties) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - - PointInfo tooltipChartPoint = PointInfo(null, null); - - if (_stateProperties.renderingDetails.oldDeviceOrientation != - _stateProperties.renderingDetails.deviceOrientation || - _stateProperties.renderingDetails.didSizeChange) { - if (tooltipRenderingDetails.showLocation != null && - _stateProperties.chart.tooltipBehavior.enable == true && - _stateProperties.isTooltipHidden == false) { - tooltipChartPoint = pyramidFunnelPixelToPoint( - tooltipRenderingDetails.showLocation!, - _stateProperties.chartSeries.visibleSeriesRenderers[0]); - } - } - return tooltipChartPoint; - } - - /// Here for orientation change/browser resize, the logic in this method will get executed. - void _validateStateMaintenance(FunnelStateProperties _stateProperties, - PointInfo tooltipChartPoint) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - if (_stateProperties.renderingDetails.oldDeviceOrientation != - _stateProperties.renderingDetails.deviceOrientation || - _stateProperties.renderingDetails.didSizeChange) { - if (tooltipRenderingDetails.showLocation != null && - _stateProperties.chart.tooltipBehavior.enable == true && - _stateProperties.isTooltipHidden == false) { - _stateProperties.isTooltipOrientationChanged = true; - late PointInfo point; - late int index; - for (int i = 0; - i < - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints.length; - i++) { - if (_stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == - tooltipChartPoint.x && - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == - tooltipChartPoint.y) { - index = i; - point = _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i]; - } - } - final Offset tooltipPosition = pyramidFunnelPointToPixel( - point, _stateProperties.chartSeries.visibleSeriesRenderers[0]); - if (_stateProperties.chart.tooltipBehavior.builder != null) { - _stateProperties.funnelplotArea.showFunnelTooltipTemplate(index); - } else { - tooltipRenderingDetails.internalShowByPixel( - tooltipPosition.dx, tooltipPosition.dy); - } - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart deleted file mode 100644 index 1a7c59832..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart +++ /dev/null @@ -1,655 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; - -import '../../chart/user_interaction/selection_renderer.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../../common/event_args.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/helper.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../base/funnel_base.dart'; -import '../base/funnel_state_properties.dart'; -import '../renderer/data_label_renderer.dart'; -import '../renderer/funnel_chart_painter.dart'; -import '../renderer/funnel_series.dart'; -import '../renderer/renderer_extension.dart'; - -/// Represents the funnel plot area. -// ignore: must_be_immutable -class FunnelPlotArea extends StatelessWidget { - /// Creates an instance of funnel plot area. - // ignore: prefer_const_constructors_in_immutables - FunnelPlotArea({required this.stateProperties}); - - /// Specifies the value of funnel state properties. - final FunnelStateProperties stateProperties; - - /// Gets the chart widget from the stateProperties. - SfFunnelChart get chart => stateProperties.chart; - - /// Specifies the value of funnel series renderer. - late FunnelSeriesRendererExtension seriesRenderer; - - /// Holds the render box value. - late RenderBox renderBox; - - /// Holds the value of point region. - Region? pointRegion; - - /// Holds the value of tap down details. - late TapDownDetails tapDownDetails; - - /// Specifies the double tap position. - Offset? doubleTapPosition; - final bool _enableMouseHover = kIsWeb; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // ignore: avoid_unnecessary_containers - return Container( - child: MouseRegion( - // 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) { - TooltipHelper.getRenderingDetails(stateProperties - .renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - }, - child: Stack(textDirection: TextDirection.ltr, children: [ - _initializeChart(constraints, context), - Listener( - onPointerUp: (PointerUpEvent event) => _onTapUp(event), - onPointerDown: (PointerDownEvent event) => - _onTapDown(event), - onPointerMove: (PointerMoveEvent event) => - _performPointerMove(event), - child: GestureDetector( - onLongPress: _onLongPress, - onDoubleTap: _onDoubleTap, - onTapUp: (TapUpDetails details) { - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(details.globalPosition); - if (chart.series.onPointTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - calculatePointSeriesIndex( - chart, - seriesRenderer, - stateProperties.renderingDetails.tapPosition!, - null, - ActivationMode.singleTap); - } - }, - child: Container( - decoration: - const BoxDecoration(color: Colors.transparent), - ))) - ]))); - }); - } - - /// To initialize chart elements. - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - _calculateContainerSize(constraints); - return GestureDetector( - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context))); - } - - /// To calculate size of chart. - void _calculateContainerSize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - stateProperties.renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final EdgeInsets margin = chart.margin; - stateProperties.renderingDetails.chartAreaRect = Rect.fromLTWH( - margin.left, - margin.top, - width - margin.right - margin.left, - height - margin.top - margin.bottom); - } - - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _bindSeriesWidgets(); - _calculatePathRegion(); - findTemplates(stateProperties); - renderTemplates(stateProperties); - _bindTooltipWidgets(constraints); - renderBox = context.findRenderObject() as RenderBox; - stateProperties.funnelplotArea = this; - // ignore: avoid_unnecessary_containers - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: stateProperties.renderingDetails.chartWidgets!)); - } - - /// To calculate region path for rendering funnel chart. - void _calculatePathRegion() { - if (stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty) { - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - for (int i = 0; i < seriesRenderer.renderPoints.length; i++) { - if (seriesRenderer.renderPoints[i].isVisible) { - stateProperties.chartSeries - .calculateFunnelPathRegion(i, seriesRenderer); - } - } - } - } - - /// To bind series widgets in chart. - void _bindSeriesWidgets() { - CustomPainter seriesPainter; - Animation? seriesAnimation; - FunnelSeries series; - SelectionBehaviorRenderer selectionBehaviorRenderer; - dynamic selectionBehavior; - for (int i = 0; - i < stateProperties.chartSeries.visibleSeriesRenderers.length; - i++) { - seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[i]; - series = seriesRenderer.series; - stateProperties.chartSeries.initializeSeriesProperties(seriesRenderer); - selectionBehavior = - seriesRenderer.selectionBehavior = series.selectionBehavior; - selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, stateProperties); - SelectionHelper.setSelectionBehaviorRenderer( - series.selectionBehavior, selectionBehaviorRenderer); - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - selectionDetails.selectionRenderer ??= SelectionRenderer(); - selectionDetails.selectionRenderer?.chart = chart; - selectionDetails.selectionRenderer!.stateProperties = stateProperties; - selectionDetails.selectionRenderer?.seriesRendererDetails = - seriesRenderer; - if (series.initialSelectedDataIndexes.isNotEmpty) { - for (int index = 0; - index < series.initialSelectedDataIndexes.length; - index++) { - stateProperties.renderingDetails.selectionData - .add(series.initialSelectedDataIndexes[index]); - } - } - - if (series.animationDuration > 0 && - !stateProperties.renderingDetails.didSizeChange && - (stateProperties.renderingDetails.oldDeviceOrientation == - stateProperties.renderingDetails.deviceOrientation) && - ((!stateProperties.renderingDetails.widgetNeedUpdate && - stateProperties.renderingDetails.initialRender!) || - stateProperties.renderingDetails.isLegendToggled)) { - final int totalAnimationDuration = - series.animationDuration.toInt() + series.animationDelay.toInt(); - stateProperties.renderingDetails.animationController.duration = - Duration(milliseconds: totalAnimationDuration); - const double maxSeriesInterval = 0.8; - double minSeriesInterval = 0.1; - minSeriesInterval = series.animationDelay.toInt() / - totalAnimationDuration * - (maxSeriesInterval - minSeriesInterval) + - minSeriesInterval; - seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: stateProperties.renderingDetails.animationController, - curve: Interval(minSeriesInterval, maxSeriesInterval, - curve: Curves.linear), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - stateProperties.renderingDetails.animateCompleted = true; - stateProperties.renderingDetails.initialRender = false; - if (stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state!.render(); - } - if (stateProperties.renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - stateProperties.renderingDetails.chartTemplate!.state != - null) { - stateProperties.renderingDetails.chartTemplate!.state - .templateRender(); - } - } - })); - stateProperties.renderingDetails.chartElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: stateProperties.renderingDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - stateProperties.renderingDetails.animationController.forward(from: 0.0); - } else { - stateProperties.renderingDetails.animateCompleted = true; - stateProperties.renderingDetails.initialRender = false; - if (stateProperties.renderDataLabel?.state != null) { - stateProperties.renderDataLabel?.state!.render(); - } - } - seriesRenderer.repaintNotifier = - stateProperties.renderingDetails.seriesRepaintNotifier; - seriesPainter = FunnelChartPainter( - stateProperties: stateProperties, - seriesIndex: i, - isRepaint: seriesRenderer.needsRepaint, - animationController: - stateProperties.renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: stateProperties.renderingDetails.seriesRepaintNotifier); - stateProperties.renderingDetails.chartWidgets! - .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); - stateProperties.renderDataLabel = FunnelDataLabelRenderer( - key: GlobalKey(), - stateProperties: stateProperties, - //ignore: avoid_bool_literals_in_conditional_expressions - show: stateProperties.renderingDetails.animateCompleted); - stateProperties.renderingDetails.chartWidgets! - .add(stateProperties.renderDataLabel!); - } - } - - /// To bind tooltip widgets to chart. - void _bindTooltipWidgets(BoxConstraints constraints) { - TooltipHelper.setStateProperties(chart.tooltipBehavior, stateProperties); - final SfChartThemeData _chartTheme = - stateProperties.renderingDetails.chartTheme; - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - if (chart.tooltipBehavior.enable) { - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue = null; - tooltipRenderingDetails.chartTooltip = SfTooltip( - color: tooltip.color ?? _chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.borderWidth, - duration: tooltip.duration.toInt(), - 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, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? tooltipRenderingDetails.tooltipRenderingEvent - : null); - stateProperties.renderingDetails.chartWidgets! - .add(tooltipRenderingDetails.chartTooltip!); - } - } - - /// To perform pointer down event. - void _onTapDown(PointerDownEvent event) { - // renderBox = context.findRenderObject(); - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isHovering = false; - stateProperties.renderingDetails.currentActive = null; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool isPoint = false; - const int seriesIndex = 0; - int? pointIndex; - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - ChartTouchInteractionArgs touchArgs; - 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, - stateProperties.renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - if (chart.onDataLabelRender == null) { - break; - } - } - } - } - doubleTapPosition = stateProperties.renderingDetails.tapPosition!; - // ignore: unnecessary_null_comparison - if (stateProperties.renderingDetails.tapPosition != null && isPoint) { - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - 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) { - tooltipRenderingDetails.show = false; - tooltipRenderingDetails.hideTooltipTemplate(); - } - } - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - - /// To perform pointer move event. - void _performPointerMove(PointerMoveEvent event) { - ChartTouchInteractionArgs touchArgs; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = position; - chart.onChartTouchInteractionMove!(touchArgs); - } - } - - /// To perform double tap touch interactions. - void _onDoubleTap() { - const int seriesIndex = 0; - if (doubleTapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - if (chart.series.onPointDoubleTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - calculatePointSeriesIndex( - chart, - seriesRenderer, - stateProperties.renderingDetails.tapPosition!, - null, - ActivationMode.doubleTap); - stateProperties.renderingDetails.tapPosition = null; - } - final int? pointIndex = - stateProperties.renderingDetails.currentActive!.pointIndex; - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex, - stateProperties - .chartSeries.visibleSeriesRenderers[seriesIndex].series, - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .renderPoints[pointIndex!]); - if (stateProperties.renderingDetails.currentActive != null) { - if (stateProperties - .renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.doubleTap) { - stateProperties.chartSeries.pointExplode(pointIndex); - final GlobalKey key = - stateProperties.renderDataLabel!.key as GlobalKey; - final FunnelDataLabelRendererState _funnelDataLabelRendererState = - key.currentState as FunnelDataLabelRendererState; - _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - stateProperties.chartSeries - .seriesPointSelection(pointIndex, ActivationMode.doubleTap); - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { - if (chart.tooltipBehavior.builder != null) { - showFunnelTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition!.dx.toDouble(), - doubleTapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform long press touch interactions. - void _onLongPress() { - const int seriesIndex = 0; - if (stateProperties.renderingDetails.tapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - if (chart.series.onPointLongPress != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - calculatePointSeriesIndex( - chart, - seriesRenderer, - stateProperties.renderingDetails.tapPosition!, - null, - ActivationMode.longPress); - stateProperties.renderingDetails.tapPosition = null; - } - final int pointIndex = - stateProperties.renderingDetails.currentActive!.pointIndex!; - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex, - stateProperties - .chartSeries.visibleSeriesRenderers[seriesIndex].series, - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .renderPoints[pointIndex], - pointRegion); - stateProperties.chartSeries - .seriesPointSelection(pointIndex, ActivationMode.longPress); - if (stateProperties.renderingDetails.currentActive != null) { - if (stateProperties - .renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.longPress) { - stateProperties.chartSeries.pointExplode(pointIndex); - final GlobalKey key = - stateProperties.renderDataLabel!.key as GlobalKey; - final FunnelDataLabelRendererState _funnelDataLabelRendererState = - key.currentState as FunnelDataLabelRendererState; - _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress) { - if (chart.tooltipBehavior.builder != null) { - showFunnelTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer.onLongPress( - stateProperties.renderingDetails.tapPosition!.dx.toDouble(), - stateProperties.renderingDetails.tapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform pointer up event. - void _onTapUp(PointerUpEvent event) { - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - ChartTouchInteractionArgs touchArgs; - // ignore: unnecessary_null_comparison - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - triggerFunnelDataLabelEvent( - chart, - seriesRenderer, - stateProperties.chartState, - stateProperties.renderingDetails.tapPosition!); - } - if (stateProperties.renderingDetails.tapPosition != null) { - if (stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null && - stateProperties - .renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.singleTap) { - stateProperties.chartSeries.pointExplode( - stateProperties.renderingDetails.currentActive!.pointIndex!); - final GlobalKey key = stateProperties.renderDataLabel!.key as GlobalKey; - final FunnelDataLabelRendererState _funnelDataLabelRendererState = - key.currentState as FunnelDataLabelRendererState; - _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - if (stateProperties.renderingDetails.tapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - stateProperties.chartSeries.seriesPointSelection( - stateProperties.renderingDetails.currentActive!.pointIndex!, - ActivationMode.singleTap); - } - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null) { - if (chart.tooltipBehavior.builder != null) { - showFunnelTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - stateProperties.renderingDetails.tooltipBehaviorRenderer - // ignore: noop_primitive_operations - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - } - if (chart.series.onPointTap == null && - chart.series.onPointDoubleTap == null && - chart.series.onPointLongPress == null) { - stateProperties.renderingDetails.tapPosition = null; - } - } - - /// To perform event on mouse hover. - void _onHover(PointerEvent event) { - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - stateProperties.renderingDetails.currentActive = null; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool? isPoint; - const int seriesIndex = 0; - int? pointIndex; - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { - if (seriesRenderer.renderPoints[j].isVisible) { - isPoint = isPointInPolygon(seriesRenderer.renderPoints[j].pathRegion, - stateProperties.renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - break; - } - } - } - if (stateProperties.renderingDetails.tapPosition != null && isPoint!) { - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex!, - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex].series, - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .renderPoints[pointIndex], - ); - } else { - //hides the tooltip if the point of interaction is outside funnel region of the chart - tooltipRenderingDetails.hide(); - } - if (stateProperties.renderingDetails.tapPosition != null) { - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null) { - tooltipRenderingDetails.isHovering = true; - if (chart.tooltipBehavior.builder != null) { - showFunnelTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - stateProperties.renderingDetails.tooltipBehaviorRenderer - .onEnter(position.dx.toDouble(), position.dy.toDouble()); - } - } else { - tooltipRenderingDetails.prevTooltipValue = null; - tooltipRenderingDetails.currentTooltipValue = null; - } - } - stateProperties.renderingDetails.tapPosition = null; - } - - /// This method gets executed for showing tooltip when builder is provided in behavior. - void showFunnelTooltipTemplate([int? pointIndex]) { - stateProperties.isTooltipHidden = false; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.isHovering == false) { - //assigning null for the previous and current tooltip values in case of touch interaction - tooltipRenderingDetails.prevTooltipValue = null; - tooltipRenderingDetails.currentTooltipValue = null; - } - final FunnelSeries chartSeries = - stateProperties.renderingDetails.currentActive?.series ?? chart.series; - final PointInfo point = pointIndex == null - ? stateProperties.renderingDetails.currentActive?.point - : stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; - final Offset location = - chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - !stateProperties - .chartSeries.visibleSeriesRenderers[0].series.explode - ? stateProperties.renderingDetails.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; - } - } - } - // ignore: unnecessary_null_comparison - if (location != null && isPoint && (chartSeries.enableTooltip)) { - tooltipRenderingDetails.showLocation = location; - tooltipRenderingDetails.chartTooltipState!.boundaryRect = - tooltipRenderingDetails.tooltipBounds = - stateProperties.renderingDetails.chartContainerRect; - // tooltipTemplate.rect = Rect.fromLTWH(location.dx, location.dy, 0, 0); - tooltipRenderingDetails.tooltipTemplate = chart.tooltipBehavior.builder!( - chartSeries.dataSource![pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!], - point, - chartSeries, - 0, - pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!); - if (tooltipRenderingDetails.isHovering == true) { - //assigning values for the previous and current tooltip values on mouse hover - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue; - tooltipRenderingDetails.currentTooltipValue = TooltipValue( - 0, - pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!); - } else { - tooltipRenderingDetails.hideTooltipTemplate(); - } - tooltipRenderingDetails.show = true; - tooltipRenderingDetails.performTooltip(); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart deleted file mode 100644 index 3d9df1db5..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import '../../common/rendering_details.dart'; -import '../../common/state_properties.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../base/funnel_plot_area.dart'; -import '../base/series_base.dart'; -import '../renderer/data_label_renderer.dart'; - -/// Specifies the funnel state properties. -class FunnelStateProperties extends StateProperties { - /// Creates an instance of funnel chart properties. - FunnelStateProperties( - {required this.renderingDetails, required this.chartState}) - : super(renderingDetails, chartState) { - renderingDetails.didSizeChange = false; - } - - /// Specifies the funnel chart. - @override - SfFunnelChart get chart => chartState.widget; - - /// Specifies the funnel chart state. - @override - final SfFunnelChartState chartState; - - /// Specifies the rendering details value. - @override - final RenderingDetails renderingDetails; - - /// Specifies the funnel data label renderer. - FunnelDataLabelRenderer? renderDataLabel; - - /// Specifies the tooltip point index. - int? tooltipPointIndex; - - /// Specifies the series type. - late String seriesType; - - /// Specifies the data points. - late List> dataPoints; - - /// Specifies the render points - late List> renderPoints; - - /// Specifies the data label rects. - late List labelRects = []; - - /// Specifies the outside render labels. - late List outsideRects = []; - - /// Specifies the funnel series. - late FunnelChartBase chartSeries; - - /// Specifies the funnel plot area. - late FunnelPlotArea funnelplotArea; - - /// Specifies whether the text direction of chart widget is RTL or LTR. - late bool isRtl; - - /// To redraw chart elements. - void redraw() { - renderingDetails.initialRender = false; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.chartTooltipState != null) { - tooltipRenderingDetails.show = false; - } - // ignore: invalid_use_of_protected_member - chartState.setState(() { - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); - } - - /// Method when called, once animation completed. - bool get animationCompleted { - return renderingDetails.animationController.status != - AnimationStatus.forward; - } - - /// Tooltip timer. - Timer? tooltipTimer; - - /// To check the tooltip orientation changes. - bool isTooltipOrientationChanged = false; - - /// To check if tooltip has been hidden or not. - bool isTooltipHidden = false; -} 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 deleted file mode 100644 index e6186c754..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart +++ /dev/null @@ -1,558 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../../circular_chart/utils/helper.dart'; -import '../../common/common.dart'; -import '../../common/event_args.dart'; -import '../../common/legend/renderer.dart'; -import '../../common/utils/typedef.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../base/funnel_base.dart'; -import '../base/funnel_state_properties.dart'; -import '../renderer/funnel_series.dart'; -import '../renderer/renderer_extension.dart'; -import '../renderer/series_base.dart'; - -/// Represents the funnel series base. -class FunnelChartBase { - /// Creates an instance for funnel series base. - FunnelChartBase(this.stateProperties); - - /// Specifies the funnel chart state. - final FunnelStateProperties stateProperties; - - /// Specifies the current series. - late FunnelSeriesBase currentSeries; - - /// Specifies the list of visible series renderer. - List visibleSeriesRenderers = - []; - SelectionArgs? _selectionArgs; - - /// To find the visible series. - void findVisibleSeries() { - stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints = - >[]; - - //Considered the first series, since in triangular series one series will be considered for rendering - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - currentSeries = seriesRenderer.series; - //Setting series type - seriesRenderer.seriesType = 'funnel'; - final ChartIndexedValueMapper? xValue = currentSeries.xValueMapper; - final ChartIndexedValueMapper? yValue = currentSeries.yValueMapper; - - for (int pointIndex = 0; - pointIndex < currentSeries.dataSource!.length; - pointIndex++) { - if (xValue!(pointIndex) != null) { - seriesRenderer.dataPoints - .add(PointInfo(xValue(pointIndex), yValue!(pointIndex))); - } - } - visibleSeriesRenderers - ..clear() - ..add(seriesRenderer); - } - - /// To calculate empty point values for null values in chart. - void _calculateFunnelEmptyPoints( - FunnelSeriesRendererExtension seriesRenderer) { - for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { - if (seriesRenderer.dataPoints[i].y == null) { - seriesRenderer.series.calculateEmptyPointValue( - i, seriesRenderer.dataPoints[i], seriesRenderer); - } - } - } - - /// To process the data points for series render. - void processDataPoints(FunnelSeriesRendererExtension seriesRenderer) { - currentSeries = seriesRenderer.series; - _calculateFunnelEmptyPoints(seriesRenderer); - _calculateVisiblePoints(seriesRenderer); - _setPointStyle(seriesRenderer); - _findSumOfPoints(seriesRenderer); - } - - /// To calculate the visible points in the series. - void _calculateVisiblePoints(FunnelSeriesRendererExtension seriesRenderer) { - final List> points = seriesRenderer.dataPoints; - seriesRenderer.renderPoints = >[]; - for (int i = 0; i < points.length; i++) { - if (points[i].isVisible) { - seriesRenderer.renderPoints.add(points[i]); - } - } - } - - /// To set point style properties. - void _setPointStyle(FunnelSeriesRendererExtension seriesRenderer) { - currentSeries = seriesRenderer.series; - final List palette = stateProperties.chart.palette; - final ChartIndexedValueMapper? pointColor = - currentSeries.pointColorMapper; - final EmptyPointSettings empty = currentSeries.emptyPointSettings; - final ChartIndexedValueMapper? textMapping = - currentSeries.textFieldMapper; - final List> points = seriesRenderer.renderPoints; - PointInfo currentPoint; - List legendToggles; - MeasureWidgetContext item; - LegendRenderContext legendRenderContext; - for (int i = 0; i < points.length; i++) { - currentPoint = points[i]; - // ignore: unnecessary_null_comparison - currentPoint.fill = currentPoint.isEmpty && empty.color != null - ? empty.color - : pointColor!(i) ?? palette[i % palette.length]; - currentPoint.color = currentPoint.fill; - currentPoint.borderColor = - // ignore: unnecessary_null_comparison - currentPoint.isEmpty && empty.borderColor != null - ? empty.borderColor - : currentSeries.borderColor; - currentPoint.borderWidth = - // ignore: unnecessary_null_comparison - currentPoint.isEmpty && empty.borderWidth != null - ? empty.borderWidth - : currentSeries.borderWidth; - currentPoint.borderColor = currentPoint.borderWidth == 0 - ? Colors.transparent - : currentPoint.borderColor; - - currentPoint.text = currentPoint.text ?? - (textMapping != null - ? textMapping(i) ?? currentPoint.y!.toString() - : currentPoint.y!.toString()); - - if (stateProperties.chart.legend.legendItemBuilder != null) { - legendToggles = - stateProperties.renderingDetails.legendToggleTemplateStates; - if (legendToggles.isNotEmpty) { - for (int j = 0; j < legendToggles.length; j++) { - item = legendToggles[j]; - if (i == item.pointIndex) { - currentPoint.isVisible = false; - break; - } - } - } - } else { - if (stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; - j < stateProperties.renderingDetails.legendToggleStates.length; - j++) { - legendRenderContext = - stateProperties.renderingDetails.legendToggleStates[j]; - if (i == legendRenderContext.seriesIndex) { - currentPoint.isVisible = false; - break; - } - } - } - } - } - } - - /// To find the sum of data points. - void _findSumOfPoints(FunnelSeriesRendererExtension seriesRenderer) { - seriesRenderer.sumOfPoints = 0; - for (final PointInfo point in seriesRenderer.renderPoints) { - if (point.isVisible) { - seriesRenderer.sumOfPoints += point.y!.abs(); - } - } - } - - /// To initialize series properties. - void initializeSeriesProperties( - FunnelSeriesRendererExtension seriesRenderer) { - final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; - final FunnelSeries series = seriesRenderer.series; - final bool reverse = seriesRenderer.seriesType == 'pyramid'; - seriesRenderer.triangleSize = Size( - 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()); - seriesRenderer.explodeDistance = - percentToValue(series.explodeOffset, chartAreaRect.width)!; - _initializeSizeRatio(seriesRenderer, reverse); - } - - /// To initialize size ratio for the funnel. - void _initializeSizeRatio(FunnelSeriesRendererExtension seriesRenderer, - [bool? reverse]) { - final List> points = seriesRenderer.renderPoints; - double y; - assert( - // ignore: unnecessary_null_comparison - !(seriesRenderer.series.gapRatio != null) || - seriesRenderer.series.gapRatio >= 0 && - seriesRenderer.series.gapRatio <= 1, - 'The gap ratio for the funnel chart must be between 0 and 1.'); - final double gapRatio = min(max(seriesRenderer.series.gapRatio, 0), 1); - final double coEff = - 1 / (seriesRenderer.sumOfPoints * (1 + gapRatio / (1 - gapRatio))); - final double spacing = gapRatio / (points.length - 1); - y = 0; - int index; - num height; - 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!; - points[index].yRatio = y; - points[index].heightRatio = height; - y += height + spacing; - } - } - } - - /// To calculate the segment path. - void _calculatePathSegment(String seriesType, PointInfo point) { - final List pathRegion = point.pathRegion; - 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.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(int pointIndex) { - bool existExplodedRegion = false; - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - final PointInfo point = seriesRenderer.renderPoints[pointIndex]; - if (seriesRenderer.series.explode) { - if (stateProperties.renderingDetails.explodedPoints.isNotEmpty) { - existExplodedRegion = true; - final int previousIndex = - stateProperties.renderingDetails.explodedPoints[0]; - seriesRenderer.renderPoints[previousIndex].explodeDistance = 0; - point.explodeDistance = - previousIndex == pointIndex ? 0 : seriesRenderer.explodeDistance; - stateProperties.renderingDetails.explodedPoints[0] = pointIndex; - if (previousIndex == pointIndex) { - stateProperties.renderingDetails.explodedPoints = []; - } - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - if (!existExplodedRegion) { - point.explodeDistance = seriesRenderer.explodeDistance; - stateProperties.renderingDetails.explodedPoints.add(pointIndex); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - calculateFunnelPathRegion(pointIndex, seriesRenderer); - } - } - - /// To calculate Path for the segment regions. - void calculateFunnelPathRegion( - int pointIndex, FunnelSeriesRendererExtension seriesRenderer) { - num lineWidth, topRadius, bottomRadius, endTop, endBottom, top, bottom; - num? minRadius, bottomY; - num endMin = 0; - final Size area = seriesRenderer.triangleSize; - const num offset = 0; - final PointInfo currentPoint = - seriesRenderer.renderPoints[pointIndex]; - currentPoint.pathRegion = []; - final Rect rect = stateProperties.renderingDetails.chartContainerRect; - //ignore: prefer_if_null_operators - final num extraSpace = (currentPoint.explodeDistance != null - ? currentPoint.explodeDistance! - : isNeedExplode(pointIndex, currentSeries, stateProperties) - ? seriesRenderer.explodeDistance - : 0) + - (rect.width - seriesRenderer.triangleSize.width) / 2; - final num emptySpaceAtLeft = extraSpace + rect.left; - final num seriesTop = rect.top + (rect.height - area.height) / 2; - top = currentPoint.yRatio * area.height; - bottom = top + currentPoint.heightRatio * area.height; - final Size neckSize = seriesRenderer.neckSize; - lineWidth = neckSize.width + - (area.width - neckSize.width) * - ((area.height - neckSize.height - top) / - (area.height - neckSize.height)); - topRadius = (area.width / 2) - lineWidth / 2; - endTop = topRadius + lineWidth; - lineWidth = (bottom > area.height - neckSize.height || - area.height == neckSize.height) - ? neckSize.width - : neckSize.width + - (area.width - neckSize.width) * - ((area.height - neckSize.height - bottom) / - (area.height - neckSize.height)); - - bottomRadius = (area.width / 2) - (lineWidth / 2); - endBottom = bottomRadius + lineWidth; - if (top >= area.height - neckSize.height) { - topRadius = - bottomRadius = minRadius = (area.width / 2) - neckSize.width / 2; - endTop = endBottom = endMin = (area.width / 2) + neckSize.width / 2; - } else if (bottom > (area.height - neckSize.height)) { - minRadius = bottomRadius = (area.width / 2) - lineWidth / 2; - endMin = endBottom = minRadius + lineWidth; - bottomY = area.height - neckSize.height; - } - top += seriesTop; - bottom += seriesTop; - bottomY = (bottomY != null) ? (bottomY + seriesTop) : null; - final List values = [ - emptySpaceAtLeft, - offset, - topRadius, - endTop, - endBottom, - bottomRadius, - endMin, - top, - bottom - ]; - _addPathToCurrentPoint(currentPoint, values, minRadius, bottomY); - _calculatePathSegment(seriesRenderer.seriesType, currentPoint); - } - - void _addPathToCurrentPoint(PointInfo currentPoint, List values, - num? minRadius, num? bottomY) { - late num line1X, - line1Y, - line2X, - line2Y, - line3X, - line3Y, - line4X, - line4Y, - line5X, - line5Y, - line6X, - line6Y; - line1X = values[0] + values[1] + values[2]; - line1Y = values[7]; - line2X = values[0] + values[1] + values[3]; - line2Y = values[7]; - line4X = values[0] + values[1] + values[4]; - line4Y = values[8]; - line5X = values[0] + values[1] + values[5]; - line5Y = values[8]; - line3X = values[0] + values[1] + values[4]; - line3Y = values[8]; - line6X = values[0] + values[1] + values[5]; - line6Y = values[8]; - if (bottomY != null) { - line3X = values[0] + values[1] + values[6]; - line3Y = bottomY; - line6X = values[0] + values[1] + ((minRadius != null) ? minRadius : 0); - line6Y = bottomY; - } - 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())); - } - - /// To calculate the funnel segments and render path. - void calculateFunnelSegments(Canvas canvas, int pointIndex, - FunnelSeriesRendererExtension seriesRenderer) { - calculateFunnelPathRegion(pointIndex, seriesRenderer); - final PointInfo currentPoint = - 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[1].dy); - path.lineTo(currentPoint.pathRegion[2].dx, currentPoint.pathRegion[2].dy); - path.lineTo(currentPoint.pathRegion[3].dx, currentPoint.pathRegion[3].dy); - path.lineTo(currentPoint.pathRegion[4].dx, currentPoint.pathRegion[4].dy); - path.lineTo(currentPoint.pathRegion[5].dx, currentPoint.pathRegion[5].dy); - path.close(); - 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, int pointIndex, - FunnelSeriesRendererExtension seriesRenderer) { - final PointInfo point = seriesRenderer.renderPoints[pointIndex]; - final StyleOptions? style = _getPointStyle( - pointIndex, seriesRenderer, stateProperties.chartState, point); - - final Color fillColor = - style != null && style.fill != null ? style.fill! : point.fill; - - final Color strokeColor = style != null && style.strokeColor != null - ? style.strokeColor! - : point.borderColor; - - final double strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth!.toDouble() - : point.borderWidth.toDouble(); - - final double opacity = style != null && style.opacity != null - ? style.opacity!.toDouble() - : currentSeries.opacity.toDouble(); - - drawPath( - canvas, - StyleOptions( - fill: fillColor, - strokeWidth: stateProperties.renderingDetails.animateCompleted - ? strokeWidth - : 0, - strokeColor: strokeColor, - opacity: opacity), - path); - } - - /// To add selection points to selection list. - void seriesPointSelection(int pointIndex, ActivationMode mode) { - bool isPointAlreadySelected = false; - final SfFunnelChart chart = stateProperties.chart; - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - final List selectionData = - stateProperties.renderingDetails.selectionData; - int? currentSelectedIndex; - const int seriesIndex = 0; - if (seriesRenderer.isSelectionEnable && mode == chart.selectionGesture) { - if (selectionData.isNotEmpty) { - if (!chart.enableMultiSelection && - stateProperties.renderingDetails.selectionData.isNotEmpty && - stateProperties.renderingDetails.selectionData.length > 1) { - if (stateProperties.renderingDetails.selectionData - .contains(pointIndex)) { - currentSelectedIndex = pointIndex; - } - stateProperties.renderingDetails.selectionData.clear(); - if (currentSelectedIndex != null) { - stateProperties.renderingDetails.selectionData.add(pointIndex); - } - } - - int selectionIndex; - for (int i = 0; i < selectionData.length; i++) { - selectionIndex = selectionData[i]; - if (!chart.enableMultiSelection) { - isPointAlreadySelected = - selectionData.length == 1 && pointIndex == selectionIndex; - if (seriesRenderer.selectionBehavior.toggleSelection == true || - !isPointAlreadySelected) { - selectionData.removeAt(i); - } - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!(_getSelectionEventArgs( - seriesRenderer, seriesIndex, selectionIndex)); - } - } else if (pointIndex == selectionIndex) { - if (seriesRenderer.selectionBehavior.toggleSelection == true) { - selectionData.removeAt(i); - } - isPointAlreadySelected = true; - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!(_getSelectionEventArgs( - seriesRenderer, seriesIndex, selectionIndex)); - } - break; - } - } - } - if (!isPointAlreadySelected) { - selectionData.add(pointIndex); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!( - _getSelectionEventArgs(seriesRenderer, seriesIndex, pointIndex)); - } - } - } - } - - /// To return style options for the point on selection. - StyleOptions? _getPointStyle( - int currentPointIndex, - FunnelSeriesRendererExtension seriesRenderer, - SfFunnelChartState _chartState, - PointInfo point) { - StyleOptions? pointStyle; - final dynamic selection = seriesRenderer.series.selectionBehavior; - final List selectionData = - stateProperties.renderingDetails.selectionData; - if (selection.enable == true) { - if (selectionData.isNotEmpty) { - int selectionIndex; - for (int i = 0; i < selectionData.length; i++) { - selectionIndex = selectionData[i]; - if (currentPointIndex == selectionIndex) { - pointStyle = StyleOptions( - 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 == selectionData.length - 1) { - pointStyle = StyleOptions( - fill: _selectionArgs != null - ? _selectionArgs!.unselectedColor - : selection.unselectedColor, - strokeWidth: _selectionArgs != null - ? _selectionArgs!.unselectedBorderWidth - : selection.unselectedBorderWidth, - strokeColor: _selectionArgs != null - ? _selectionArgs!.unselectedBorderColor - : selection.unselectedBorderColor, - opacity: selection.unselectedOpacity); - } - } - } - } - return pointStyle; - } - - /// To perform selection event and return selectionArgs. - SelectionArgs _getSelectionEventArgs( - FunnelSeriesRendererExtension seriesRenderer, - int seriesIndex, - int pointIndex) { - final SfFunnelChart chart = seriesRenderer.stateProperties.chart; - if (pointIndex < chart.series.dataSource!.length) { - final dynamic selectionBehavior = seriesRenderer.selectionBehavior; - _selectionArgs = SelectionArgs( - seriesRenderer: seriesRenderer, - seriesIndex: seriesIndex, - viewportPointIndex: pointIndex, - pointIndex: pointIndex); - _selectionArgs!.selectedBorderColor = - selectionBehavior.selectedBorderColor; - _selectionArgs!.selectedBorderWidth = - selectionBehavior.selectedBorderWidth; - _selectionArgs!.unselectedBorderColor = - selectionBehavior.unselectedBorderColor; - _selectionArgs!.unselectedBorderWidth = - selectionBehavior.unselectedBorderWidth; - _selectionArgs!.selectedColor = selectionBehavior.selectedColor; - _selectionArgs!.unselectedColor = selectionBehavior.unselectedColor; - } - 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 deleted file mode 100644 index 6072fa327..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart +++ /dev/null @@ -1,671 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; - -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../chart/utils/helper.dart'; -import '../../circular_chart/renderer/circular_chart_annotation.dart'; -import '../../circular_chart/renderer/data_label_renderer.dart'; -import '../../circular_chart/utils/enum.dart'; -import '../../circular_chart/utils/helper.dart'; -import '../../common/event_args.dart'; -import '../../common/utils/helper.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../base/funnel_base.dart'; -import '../base/funnel_state_properties.dart'; -import 'funnel_series.dart'; -import 'renderer_extension.dart'; - -/// Represents the data label renderer of the funnel chart. -// ignore: must_be_immutable -class FunnelDataLabelRenderer extends StatefulWidget { - /// Creates an instance for funnel data label renderer. - // ignore: prefer_const_constructors_in_immutables - FunnelDataLabelRenderer( - {required Key key, required this.stateProperties, required this.show}) - : super(key: key); - - /// Creates the instance of funnel chart state. - final FunnelStateProperties stateProperties; - - /// Specifies whether to show data label renderer. - bool show; - - /// Specifies the state of funnel data label renderer. - FunnelDataLabelRendererState? state; - - @override - State createState() => FunnelDataLabelRendererState(); -} - -/// Represents the data label renderer state. -class FunnelDataLabelRendererState extends State - with SingleTickerProviderStateMixin { - /// Specifies the animation controller list. - late List animationControllersList; - - /// Animation controller for series. - late AnimationController animationController; - - /// Repaint notifier for data label container. - late ValueNotifier dataLabelRepaintNotifier; - - @override - void initState() { - dataLabelRepaintNotifier = ValueNotifier(0); - animationController = AnimationController(vsync: this) - ..addListener(repaintDataLabelElements); - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - animationController.duration = Duration( - milliseconds: - widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); - final Animation dataLabelAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), - )); - animationController.forward(from: 0.0); - return !widget.show - ? Container() - // ignore: avoid_unnecessary_containers - : Container( - child: RepaintBoundary( - child: CustomPaint( - painter: _FunnelDataLabelPainter( - stateProperties: widget.stateProperties, - animation: dataLabelAnimation, - state: this, - notifier: dataLabelRepaintNotifier, - animationController: animationController)))); - } - - @override - void dispose() { - disposeAnimationController(animationController, repaintDataLabelElements); - super.dispose(); - } - - /// Method to repaint the data label element. - void repaintDataLabelElements() { - dataLabelRepaintNotifier.value++; - } - - /// Method to render the widget. - void render() { - setState(() { - widget.show = true; - }); - } -} - -class _FunnelDataLabelPainter extends CustomPainter { - _FunnelDataLabelPainter( - {required this.stateProperties, - required this.state, - required this.animationController, - required this.animation, - required ValueNotifier notifier}) - : super(repaint: notifier); - - final FunnelStateProperties stateProperties; - - final FunnelDataLabelRendererState state; - - final AnimationController animationController; - - final Animation? animation; - - /// To paint funnel data label. - @override - void paint(Canvas canvas, Size size) { - final FunnelSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - final FunnelSeries series = seriesRenderer.series; - // ignore: unnecessary_null_comparison - if (series.dataLabelSettings != null && - series.dataLabelSettings.isVisible) { - seriesRenderer.dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer.series.dataLabelSettings); - _renderFunnelDataLabel( - seriesRenderer, canvas, stateProperties, animation, series); - } - } - - @override - bool shouldRepaint(_FunnelDataLabelPainter oldDelegate) => true; -} - -/// To render funnel data label. -void _renderFunnelDataLabel( - FunnelSeriesRendererExtension seriesRenderer, - Canvas canvas, - FunnelStateProperties stateProperties, - Animation? animation, - FunnelSeries series) { - PointInfo point; - final SfFunnelChart chart = stateProperties.chart; - final DataLabelSettings dataLabel = series.dataLabelSettings; - String? label; - final double animateOpacity = animation != null ? animation.value : 1; - DataLabelRenderArgs dataLabelArgs; - TextStyle dataLabelStyle; - final List renderDataLabelRegions = []; - DataLabelSettingsRenderer dataLabelSettingsRenderer; - Size textSize; - PointInfo nextPoint; - stateProperties.outsideRects.clear(); - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints.length; - pointIndex++) { - dataLabelSettingsRenderer = seriesRenderer.dataLabelSettingsRenderer; - 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) { - dataLabelArgs = DataLabelRenderArgs(seriesRenderer, - seriesRenderer.renderPoints, pointIndex, pointIndex); - dataLabelArgs.text = label!; - dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer.color; - chart.onDataLabelRender!(dataLabelArgs); - label = point.text = dataLabelArgs.text; - dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex!; - dataLabelSettingsRenderer.color = dataLabelArgs.color; - seriesRenderer.dataPoints[pointIndex].labelRenderEvent = true; - } - dataLabelStyle = chart.onDataLabelRender == null - ? getDataLabelTextStyle( - seriesRenderer, point, stateProperties, animateOpacity) - : dataLabelStyle; - textSize = measureText(label!, dataLabelStyle); - - /// Label check after event. - if (label != '') { - stateProperties.labelRects.clear(); - for (int index = 0; - index < seriesRenderer.renderPoints.length; - index++) { - nextPoint = seriesRenderer.renderPoints[index]; - final num angle = dataLabel.angle; - Offset labelLocation; - const int labelPadding = 2; - final Size nextDataLabelSize = - measureText(nextPoint.text!, dataLabelStyle); - if (nextPoint.isVisible) { - labelLocation = nextPoint.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (nextDataLabelSize.width / 2) + - (angle == 0 ? 0 : nextDataLabelSize.width / 2), - labelLocation.dy - - (nextDataLabelSize.height / 2) + - (angle == 0 ? 0 : nextDataLabelSize.height / 2)); - stateProperties.labelRects.add(Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - nextDataLabelSize.width + (2 * labelPadding), - nextDataLabelSize.height + (2 * labelPadding))); - } else { - stateProperties.labelRects.add(Rect.zero); - } - } - PointInfoHelper.setIsLabelCollide( - point, checkCollide(pointIndex, stateProperties.labelRects)); - if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - _setFunnelInsideLabelPosition( - dataLabel, - point, - textSize, - stateProperties, - canvas, - renderDataLabelRegions, - pointIndex, - label, - seriesRenderer, - animateOpacity, - dataLabelStyle); - } else { - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = getDataLabelTextStyle( - seriesRenderer, point, stateProperties, animateOpacity); - _renderOutsideFunnelDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To render inside positioned funnel data labels. -void _setFunnelInsideLabelPosition( - DataLabelSettings dataLabel, - PointInfo point, - Size textSize, - FunnelStateProperties stateProperties, - Canvas canvas, - List renderDataLabelRegions, - int pointIndex, - String? label, - FunnelSeriesRendererExtension seriesRenderer, - double animateOpacity, - TextStyle dataLabelStyle) { - final num angle = dataLabel.angle; - Offset labelLocation; - const int labelPadding = 2; - labelLocation = point.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (textSize.width / 2) + - (angle == 0 ? 0 : textSize.width / 2), - labelLocation.dy - - (textSize.height / 2) + - (angle == 0 ? 0 : textSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - textSize.width + (2 * labelPadding), - textSize.height + (2 * labelPadding)); - final bool isDataLabelCollide = - findingCollision(point.labelRect!, renderDataLabelRegions, point.region); - if (isDataLabelCollide) { - switch (dataLabel.overflowMode) { - case OverflowMode.trim: - label = getSegmentOverflowTrimmedText(dataLabel, point, textSize, - stateProperties, labelLocation, renderDataLabelRegions); - break; - case OverflowMode.hide: - label = ''; - break; - default: - break; - } - } - final bool isLabelCollide = PointInfoHelper.getIsLabelCollide(point); - if ((isLabelCollide && - dataLabel.labelIntersectAction == LabelIntersectAction.shift && - dataLabel.overflowMode == OverflowMode.none) || - isDataLabelCollide && dataLabel.overflowMode == OverflowMode.shift) { - point.saturationRegionOutside = true; - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = getDataLabelTextStyle( - seriesRenderer, point, stateProperties, animateOpacity); - _renderOutsideFunnelDataLabel( - canvas, - label!, - point, - textSize, - pointIndex, - seriesRenderer, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if ((dataLabel.labelIntersectAction == LabelIntersectAction.none || - (!isLabelCollide && - dataLabel.labelIntersectAction == LabelIntersectAction.shift) || - (!isLabelCollide && - dataLabel.labelIntersectAction == LabelIntersectAction.hide)) && - (!isDataLabelCollide && dataLabel.overflowMode == OverflowMode.hide || - (dataLabel.overflowMode == OverflowMode.none)) && - label != '') { - point.renderPosition = ChartDataLabelPosition.inside; - _drawFunnelLabel( - point.labelRect!, - labelLocation, - label!, - null, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if ((!isLabelCollide && - dataLabel.labelIntersectAction != LabelIntersectAction.hide) && - label != '') { - point.renderPosition = ChartDataLabelPosition.inside; - final Size trimmedTextSize = measureText(label!, dataLabel.textStyle); - labelLocation = point.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (trimmedTextSize.width / 2) + - (angle == 0 ? 0 : textSize.width / 2), - labelLocation.dy - - (trimmedTextSize.height / 2) + - (angle == 0 ? 0 : trimmedTextSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - trimmedTextSize.width + (2 * labelPadding), - trimmedTextSize.height + (2 * labelPadding)); - _drawFunnelLabel( - point.labelRect!, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } -} - -/// To render outside position funnel data labels. -void _renderOutsideFunnelDataLabel( - Canvas canvas, - String? label, - PointInfo point, - Size textSize, - int pointIndex, - FunnelSeriesRendererExtension seriesRenderer, - FunnelStateProperties stateProperties, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Path connectorPath; - Rect? rect; - Offset labelLocation; - // Maximum available space for rendering data label. - const int maximumAvailableWidth = 22; - final EdgeInsets margin = seriesRenderer.series.dataLabelSettings.margin; - final ConnectorLineSettings connector = - seriesRenderer.series.dataLabelSettings.connectorLineSettings; - const num regionPadding = 10; - bool isPreviousRectIntersect = false; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - connectorPath = Path(); - final num connectorLength = percentToValue(connector.length ?? '0%', - stateProperties.renderingDetails.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( - (dx + textSize.width + margin.left + margin.right) > - stateProperties.renderingDetails.chartAreaRect.right - ? dx - - (percentToValue(seriesRenderer.series.explodeOffset, - stateProperties.renderingDetails.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; - rect = getDataLabelRect(point.dataLabelPosition!, connector.type, margin, - connectorPath, endPoint, textSize); - if (rect != null) { - final Rect containerRect = stateProperties.renderingDetails.chartAreaRect; - point.labelRect = rect; - labelLocation = Offset(rect.left + margin.left, - rect.top + rect.height / 2 - textSize.height / 2); - if (dataLabel.labelIntersectAction == LabelIntersectAction.shift || - dataLabel.overflowMode == OverflowMode.shift) { - if (seriesRenderer.series.dataLabelSettings.builder == null) { - Rect? lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - if (stateProperties.outsideRects.isNotEmpty) { - isPreviousRectIntersect = - _isFunnelLabelIntersect(rect, stateProperties.outsideRects.last); - } else { - isPreviousRectIntersect = - _isFunnelLabelIntersect(rect, lastRenderedLabelRegion); - } - if (rect.left > containerRect.left && - rect.right <= containerRect.right && - rect.top > containerRect.top && - rect.bottom < containerRect.bottom) { - if (!isPreviousRectIntersect) { - _drawFunnelLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } else { - if (pointIndex != 0) { - const num connectorLinePadding = 15; - const num padding = 2; - final Rect previousRenderedRect = stateProperties - .outsideRects.isNotEmpty - ? stateProperties - .outsideRects[stateProperties.outsideRects.length - 1] - : 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, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } - } else if (dataLabel.labelIntersectAction == LabelIntersectAction.none) { - _drawFunnelLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } else if (dataLabel.labelIntersectAction == LabelIntersectAction.hide) { - if (seriesRenderer.series.dataLabelSettings.builder == null) { - stateProperties.outsideRects.add(rect); - Rect? lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - isPreviousRectIntersect = - _isFunnelLabelIntersect(rect, lastRenderedLabelRegion); - if (!isPreviousRectIntersect) { - _drawFunnelLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To check whether labels intersect. -bool _isFunnelLabelIntersect(Rect rect, Rect? previousRect) { - bool isIntersect = false; - const num padding = 2; - if (previousRect != null && (rect.bottom + padding) > previousRect.top) { - isIntersect = true; - } - return isIntersect; -} - -/// To draw funnel data label. -void _drawFunnelLabel( - Rect labelRect, - Offset location, - String? label, - Path? connectorPath, - Canvas canvas, - FunnelSeriesRendererExtension seriesRenderer, - PointInfo point, - int pointIndex, - FunnelStateProperties stateProperties, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Paint rectPaint; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - final ConnectorLineSettings connector = dataLabel.connectorLineSettings; - if (connectorPath != null) { - canvas.drawPath( - connectorPath, - Paint() - ..color = connector.width <= 0 - ? Colors.transparent - : connector.color ?? - point.fill.withOpacity( - !stateProperties.renderingDetails.isLegendToggled - ? animateOpacity - : dataLabel.opacity) - ..strokeWidth = connector.width - ..style = PaintingStyle.stroke); - } - - if (dataLabel.builder == null) { - final double strokeWidth = dataLabel.borderWidth; - final Color? labelFill = dataLabelSettingsRenderer.color ?? - (dataLabel.useSeriesColor - ? point.fill - : dataLabelSettingsRenderer.color); - final Color? strokeColor = - dataLabel.borderColor.withOpacity(dataLabel.opacity); - // ignore: unnecessary_null_comparison - if (strokeWidth != null && strokeWidth > 0) { - rectPaint = Paint() - ..color = strokeColor!.withOpacity( - !stateProperties.renderingDetails.isLegendToggled - ? animateOpacity - : dataLabel.opacity) - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth; - drawLabelRect( - rectPaint, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - if (labelFill != null) { - drawLabelRect( - Paint() - ..color = labelFill - .withOpacity(!stateProperties.renderingDetails.isLegendToggled - ? (animateOpacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : animateOpacity - (1 - dataLabel.opacity) - : dataLabel.opacity) - ..style = PaintingStyle.fill, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - drawText(canvas, label!, location, textStyle, dataLabel.angle); - renderDataLabelRegions.add(labelRect); - } -} - -/// Method to trigger the funnel data label event. -void triggerFunnelDataLabelEvent( - SfFunnelChart chart, - FunnelSeriesRendererExtension seriesRenderer, - SfFunnelChartState chartState, - Offset position) { - const int seriesIndex = 0; - PointInfo point; - DataLabelSettings dataLabel; - Offset labelLocation; - for (int index = 0; index < seriesRenderer.renderPoints.length; index++) { - point = seriesRenderer.renderPoints[index]; - dataLabel = seriesRenderer.series.dataLabelSettings; - labelLocation = point.symbolLocation; - if (dataLabel.isVisible && - seriesRenderer.renderPoints[index].labelRect != null && - 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_chart_painter.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_chart_painter.dart deleted file mode 100644 index 7b82ffd31..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_chart_painter.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../base/funnel_state_properties.dart'; -import 'renderer_extension.dart'; - -/// Represents the funnel chart painter. -class FunnelChartPainter extends CustomPainter { - /// Creates an instance of funnel chart painter. - FunnelChartPainter({ - required this.stateProperties, - required this.seriesIndex, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : super(repaint: notifier); - - /// Specifies the value of funnel state properties. - final FunnelStateProperties stateProperties; - - /// Holds the series index details. - final int seriesIndex; - - /// Specifies whether to repaint the chart. - final bool isRepaint; - - /// Specifies the value of animation controller. - final AnimationController? animationController; - - /// Specifies the value of series animation. - final Animation? seriesAnimation; - - /// Specifies the value of funnel series renderer extension. - late FunnelSeriesRendererExtension seriesRenderer; - - /// Holds the point value. - static late PointInfo point; - - @override - void paint(Canvas canvas, Size size) { - seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - double animationFactor; - double factor; - double height; - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints.length; - pointIndex++) { - if (seriesRenderer.renderPoints[pointIndex].isVisible) { - animationFactor = seriesAnimation != null ? seriesAnimation!.value : 1; - if (seriesRenderer.series.animationDuration > 0 && - !stateProperties.renderingDetails.isLegendToggled) { - factor = (stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height) - - animationFactor * - (stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height); - height = stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height - - factor; - canvas.clipRect(Rect.fromLTRB( - 0, - stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height - - height, - stateProperties.renderingDetails.chartAreaRect.left + - stateProperties.renderingDetails.chartAreaRect.width, - stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height)); - } - stateProperties.chartSeries - .calculateFunnelSegments(canvas, pointIndex, seriesRenderer); - } - } - } - - @override - bool shouldRepaint(FunnelChartPainter oldDelegate) => true; -} 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 deleted file mode 100644 index fce9b9441..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart +++ /dev/null @@ -1,432 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; - -import '../../chart/chart_series/series.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../base/funnel_base.dart'; -import '../base/funnel_state_properties.dart'; -import 'renderer_extension.dart'; -import 'series_base.dart'; - -/// Renders Funnel series. -/// -/// The FunnelSeries is the SfFunnelChart Type series. -/// To render a funnel chart, create an instance of FunnelSeries, and add it to the series property of [SfFunnelChart]. -/// -/// Provides options to customize the [opacity], [borderWidth], [borderColor] and [pointColorMapper] of the funnel segments. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} -class FunnelSeries extends FunnelSeriesBase { - /// Creating an argument constructor of FunnelSeries class. - FunnelSeries({ - ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - FunnelSeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - 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? animationDelay, - double? opacity, - Color? borderColor, - double? borderWidth, - bool? explode, - ActivationMode? explodeGesture, - String? explodeOffset, - SelectionBehavior? selectionBehavior, - num? explodeIndex, - List? initialSelectedDataIndexes, - }) : super( - key: key, - onCreateRenderer: onCreateRenderer, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - dataSource: dataSource, - xValueMapper: (int index) => - xValueMapper!(dataSource![index], index), - yValueMapper: (int index) => - yValueMapper!(dataSource![index], index), - pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource![index], index) - : null, - textFieldMapper: (int index) => textFieldMapper != null - ? textFieldMapper(dataSource![index], index) - : null, - name: name, - neckWidth: neckWidth, - neckHeight: neckHeight, - height: height, - width: width, - gapRatio: gapRatio, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - legendIconType: legendIconType, - opacity: opacity, - borderColor: borderColor, - borderWidth: borderWidth, - animationDuration: animationDuration, - animationDelay: animationDelay, - explode: explode, - explodeIndex: explodeIndex, - explodeGesture: explodeGesture, - explodeOffset: explodeOffset, - selectionBehavior: selectionBehavior, - initialSelectedDataIndexes: initialSelectedDataIndexes); - - /// Create the funnel series renderer. - FunnelSeriesRenderer createRenderer(FunnelSeries series) { - FunnelSeriesRenderer? seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as FunnelSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return FunnelSeriesRendererExtension(); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is FunnelSeries && - other.onCreateRenderer == onCreateRenderer && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.pointColorMapper == pointColorMapper && - other.textFieldMapper == textFieldMapper && - other.name == name && - other.neckWidth == neckWidth && - other.neckHeight == neckHeight && - other.height == height && - other.width == width && - other.gapRatio == gapRatio && - other.legendIconType == legendIconType && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.opacity == opacity && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.explode == explode && - other.explodeGesture == explodeGesture && - other.explodeOffset == explodeOffset && - other.selectionBehavior == selectionBehavior && - other.explodeIndex == explodeIndex && - listEquals( - other.initialSelectedDataIndexes, initialSelectedDataIndexes); - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - onCreateRenderer, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress, - dataSource, - xValueMapper, - yValueMapper, - pointColorMapper, - textFieldMapper, - name, - neckWidth, - neckHeight, - height, - width, - gapRatio, - legendIconType, - emptyPointSettings, - dataLabelSettings, - animationDuration, - animationDelay, - opacity, - borderColor, - borderWidth, - explode, - explodeGesture, - explodeOffset, - selectionBehavior, - explodeIndex, - initialSelectedDataIndexes - ]; - return hashList(values); - } -} - -/// Creates series renderer for Funnel series. -class FunnelSeriesRenderer extends ChartSeriesRenderer { - /// Calling the default constructor of FunnelSeriesRenderer class. - FunnelSeriesRenderer(); -} - -/// 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. -class FunnelSeriesController { - /// Creating an argument constructor of FunnelSeriesController class. - FunnelSeriesController(this.seriesRenderer); - - /// Used to access the series properties. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// onRendererCreated: (FunnelSeriesController controller) { - /// _chartSeriesController = controller; - /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); - /// }, - /// ), - /// ) - /// ); - ///} - ///``` - final FunnelSeriesRenderer seriesRenderer; - - /// Used to process only the newly added, updated and removed data points in a series, - /// instead of processing all the data points. - /// - /// To re-render the chart with modified data points, setState() will be called. - /// This will render the process and render the chart from scratch. - /// Thus, the app’s performance will be degraded on continuous update. - /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. - /// Chart will process only that point and skip various steps like bounds calculation, - /// old data points processing, etc. Thus, this will improve the app’s performance. - /// - /// The following are the arguments of this method. - /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. - /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. - /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. - /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. - /// * removedDataIndex – `int` type – Index of removed data point in the existing series. - /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. - /// - /// Returns `void`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// FunnelSeriesController _funnelSeriesController; - /// return Column( - /// children: [ - /// Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: chartData, - /// onRendererCreated: (FunnelSeriesController controller) { - /// _funnelSeriesController = controller; - /// }, - /// ), - /// ) - /// ), - /// Container( - /// child: RaisedButton( - /// onPressed: () { - /// chartData.removeAt(0); - /// chartData.add(ChartData(3,23)); - /// _funnelSeriesController.updateDataSource( - /// addedDataIndexes: [chartData.length -1], - /// removedDataIndexes: [0], - /// ); - /// }) - /// )] - /// ); - /// } - ///``` - void updateDataSource( - {List? addedDataIndexes, - List? removedDataIndexes, - List? updatedDataIndexes, - int? addedDataIndex, - int? removedDataIndex, - int? updatedDataIndex}) { - if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { - _removeDataPointsList(removedDataIndexes); - } else if (removedDataIndex != null) { - _removeDataPoint(removedDataIndex); - } - if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(addedDataIndexes, false); - } else if (addedDataIndex != null) { - _addOrUpdateDataPoint(addedDataIndex, false); - } - if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(updatedDataIndexes, true); - } else if (updatedDataIndex != null) { - _addOrUpdateDataPoint(updatedDataIndex, true); - } - _updateFunnelSeries(); - } - - /// Add or update the data points on dynamic series update. - void _addOrUpdateDataPoints(List indexes, bool needUpdate) { - int dataIndex; - for (int i = 0; i < indexes.length; i++) { - dataIndex = indexes[i]; - _addOrUpdateDataPoint(dataIndex, needUpdate); - } - } - - /// Add or update a data point in the given index. - void _addOrUpdateDataPoint(int index, bool needUpdate) { - final FunnelSeriesRendererExtension renderer = - seriesRenderer as FunnelSeriesRendererExtension; - final FunnelSeries series = renderer.series; - if (index >= 0 && - 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)); - if (currentPoint.x != null) { - if (needUpdate) { - if (renderer.dataPoints.length > index) { - renderer.dataPoints[index] = currentPoint; - } - } else { - if (renderer.dataPoints.length == index) { - renderer.dataPoints.add(currentPoint); - } else if (renderer.dataPoints.length > index && index >= 0) { - renderer.dataPoints.insert(index, currentPoint); - } - } - } - } - } - - /// Remove list of points. - void _removeDataPointsList(List removedDataIndexes) { - /// Remove the redundant index from the list. - final List indexList = removedDataIndexes.toSet().toList(); - indexList.sort((int b, int a) => a.compareTo(b)); - int dataIndex; - for (int i = 0; i < indexList.length; i++) { - dataIndex = indexList[i]; - _removeDataPoint(dataIndex); - } - } - - /// Remove a data point in the given index. - void _removeDataPoint(int index) { - final FunnelSeriesRendererExtension renderer = - seriesRenderer as FunnelSeriesRendererExtension; - if (renderer.dataPoints.isNotEmpty && - index >= 0 && - index < renderer.dataPoints.length) { - renderer.dataPoints.removeAt(index); - } - } - - /// After add/remove/update data points, recalculate the chart segments. - void _updateFunnelSeries() { - final FunnelSeriesRendererExtension renderer = - seriesRenderer as FunnelSeriesRendererExtension; - final FunnelStateProperties stateProperties = renderer.stateProperties; - stateProperties.chartSeries.processDataPoints(renderer); - stateProperties.chartSeries.initializeSeriesProperties(renderer); - renderer.repaintNotifier.value++; - if (renderer.series.dataLabelSettings.isVisible && - stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state!.render(); - } - if (renderer.series.dataLabelSettings.isVisible && - stateProperties.renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - stateProperties.renderingDetails.chartTemplate!.state != null) { - stateProperties.renderingDetails.chartTemplate!.state.templateRender(); - } - } - - /// Converts chart data point value to logical pixel value. - /// - /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. - /// - /// _Note_: It returns the data point's center location value. - /// - ///```Dart - /// late FunnelSeriesController seriesController; - /// SfFunnelChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { - /// PointInfo chartPoint = seriesController.pixelToPoint(args.position); - /// Offset value = seriesController.pointToPixel(chartPoint); - /// PointInfo chartPoint1 = seriesController.pixelToPoint(value); - /// }, - /// series: FunnelSeries( - /// dataSource: funnelData, - /// onRendererCreated: (FunnelSeriesController funnelSeriesController) { - /// seriesController = FunnelSeriesController; - /// } - /// ) - /// ); - /// ``` - // ignore: unused_element - Offset _pointToPixel(PointInfo point) { - return pyramidFunnelPointToPixel(point, seriesRenderer); - } - - /// Converts logical pixel value to the data point value. - /// - /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. - /// - ///```dart - /// late FunnelSeriesController seriesController; - /// SfFunnelChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { - /// PointInfo chartPoint = seriesController.pixelToPoint(args.position); - /// Offset value = seriesController.pointToPixel(chartPoint); - /// }, - /// series: FunnelSeries( - /// dataSource: funnelData, - /// onRendererCreated: (FunnelSeriesController funnelSeriesController) { - /// seriesController = FunnelSeriesController; - /// } - /// ) - /// ); - /// ``` - PointInfo pixelToPoint(Offset position) { - return pyramidFunnelPixelToPoint(position, seriesRenderer); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart deleted file mode 100644 index ee8e84c6b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../base/funnel_state_properties.dart'; -import 'funnel_series.dart'; - -/// Creates a series renderer extension for Funnel series. -class FunnelSeriesRendererExtension extends FunnelSeriesRenderer { - /// Calling the default constructor of FunnelSeriesRendererBase class. - FunnelSeriesRendererExtension() { - seriesType = 'funnel'; - } - - /// Specifies the funnel series. - late FunnelSeries series; - - /// Specifies the funnel series. - late String seriesType; - - /// Specifies the data points. - late List> dataPoints; - - /// Specifies the render points. - late List> renderPoints; - - /// Specifies the value of sum of points. - late num sumOfPoints; - - /// Specifies the triangular size. - late Size triangleSize; - - /// Specifies the funnel neck size. - late Size neckSize; - - /// Specifies the distance explode. - late num explodeDistance; - - /// Specifies the maximum data label region. - late Rect maximumDataLabelRegion; - - /// Specifies the funnel series controller. - FunnelSeriesController? controller; - - /// Specifies the funnel state properties. - late FunnelStateProperties stateProperties; - - /// Specifies the repaint notifier. - late ValueNotifier repaintNotifier; - - /// Specifies the data label setting renderer. - late DataLabelSettingsRenderer dataLabelSettingsRenderer; - - /// Specifies the selection behavior renderer. - late SelectionBehaviorRenderer selectionBehaviorRenderer; - - /// Specifies the value of selection behavior. - late SelectionBehavior selectionBehavior; - - /// Specifies whether the selection is enables. - bool isSelectionEnable = false; - - /// Specifies whether to repaint the chart. - bool needsRepaint = true; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart deleted file mode 100644 index ac2e3b996..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart +++ /dev/null @@ -1,741 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/chart_series/series.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/series/chart_series.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../../pyramid_chart/utils/common.dart'; - -/// Represents the funnel series base. -class FunnelSeriesBase extends ChartSeries - implements TriangularChartEmptyPointBehavior { - /// Creates an instance of funnel series base. - FunnelSeriesBase({ - this.key, - this.onCreateRenderer, - this.onRendererCreated, - this.onPointTap, - this.onPointDoubleTap, - this.onPointLongPress, - this.dataSource, - this.xValueMapper, - this.yValueMapper, - this.pointColorMapper, - 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? animationDelay, - double? opacity, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - }) : neckWidth = neckWidth ?? '20%', - neckHeight = neckHeight ?? '20%', - height = height ?? '80%', - width = width ?? '80%', - gapRatio = gapRatio ?? 0, - emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), - explodeOffset = explodeOffset ?? '10%', - explode = explode ?? false, - explodeGesture = explodeGesture ?? ActivationMode.singleTap, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - legendIconType = legendIconType ?? LegendIconType.seriesType, - dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), - animationDuration = animationDuration ?? 1500, - animationDelay = animationDelay ?? 0, - opacity = opacity ?? 1, - initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - selectionBehavior = selectionBehavior ?? SelectionBehavior(), - super( - name: name, - dataSource: dataSource, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper) { - // _renderer = _FunnelSeriesRender(); - } - - /// A collection of data required for rendering the series. - /// - /// If no data source is specified, empty chart will be rendered without series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// ) - /// ) - /// ); - ///} - @override - final List? dataSource; - - /// Maps the field name, which will be considered as x-values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// ) - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? xValueMapper; - - /// Maps the field name, which will be considered as y-values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// yValueMapper: (ChartData data, _) => data.yVal, - /// ) - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? yValueMapper; - - /// Name of the series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// name: 'Pyramid' - /// ) - /// ); - ///} - ///``` - @override - final String? name; - - /// Neck height of funnel. - /// - /// Defaults to `20%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// neckHeight: '10%' - /// ) - /// ); - ///} - ///``` - final String neckHeight; - - /// Neck width of funnel. - /// - /// Defaults to `20%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// neckWidth: '10%' - /// ) - /// ); - ///} - ///``` - final String neckWidth; - - /// Height of the series. - /// - /// Defaults to `80%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// height:'50%' - /// ) - /// ); - ///} - ///``` - final String height; - - /// Width of the series. - /// - /// Defaults to `80%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// width:'50%' - /// ) - /// ); - ///} - ///``` - final String width; - - /// Gap ratio between the segments of funnel. - /// - /// Ranges from 0 to 1. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// gapRatio: 0.3 - /// ) - /// ); - ///} - ///``` - final double gapRatio; - - /// Customizes the empty data points in the series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// emptyPointSettings: EmptyPointSettings (color: Colors.red)) - /// ) - /// ); - ///} - ///``` - @override - final EmptyPointSettings emptyPointSettings; - - /// Offset of exploded slice. - /// - /// The value ranges from 0% to 100%. - /// - /// Defaults to `10%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explodeOffset: '5%') - /// ) - /// ); - ///} - ///``` - final String explodeOffset; - - /// Enables or disables the explode of slices on tap. - /// - /// Default to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explode: true) - /// ) - /// ); - ///} - ///``` - final bool explode; - - /// Gesture for activating the explode. - /// - /// Explode can be activated in `ActivationMode.none`, `ActivationMode.singleTap`, `ActivationMode.doubleTap`, - /// and `ActivationMode.longPress`. - /// - /// Defaults to `ActivationMode.singleTap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explode: true, - /// explodeGesture: ActivationMode.singleTap - /// ) - /// ) - /// ); - ///} - ///``` - final ActivationMode explodeGesture; - - /// Border width of the data points in the series. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// borderWidth: 2 - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double borderWidth; - - /// Border color of the data points in the series. - /// - /// Defaults to `Colors.transparent`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// borderColor: Colors.red - /// ) - /// ) - /// ); - ///} - ///``` - @override - final Color borderColor; - - /// Shape of the legend icon. - /// - /// Any shape in the LegendIconType can be applied to this property. - /// - /// Defaults to `LegendIconType.seriesType`. - /// - /// Also refer [LegendIconType]. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// legendIconType: LegendIconType.diamond, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final LegendIconType legendIconType; - - /// Customizes the data labels in a series. Data label is a text, which displays - /// the details about the data point. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ) - /// ) - /// ); - ///} - ///``` - @override - final DataLabelSettings dataLabelSettings; - - /// Duration for animating the data points. - /// - /// Defaults to `1500`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// animationDuration: 2000, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double animationDuration; - - /// Delay duration of the series animation.It takes a millisecond value as input. - /// By default, the series will get animated for the specified duration. - /// If animationDelay is specified, then the series will begin to animate - /// after the specified duration. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// animationDelay: 500, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double animationDelay; - - /// Maps the field name, which will be considered as data point color. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// pointColorMapper: (ChartData data, _) => data.pointColor, - /// ) - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - @override - final ChartIndexedValueMapper? pointColorMapper; - - /// Maps the field name, which will be considered as a text. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// textFieldMapper: (ChartData data, _) => data.xVal, - /// ) - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - final ChartIndexedValueMapper? textFieldMapper; - - /// Opacity of the series. - /// - /// The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// opacity: 0.5, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double opacity; - - /// Customizes the selection of series. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// selectionBehavior: _selectionBehavior - /// ) - /// ) - /// ); - ///} - ///``` - @override - final SelectionBehavior selectionBehavior; - - /// Index of the slice to explode it at the initial rendering. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explodeIndex: 1, - /// explode: true - /// ) - /// ) - /// ); - ///} - ///``` - final num? explodeIndex; - - /// List of data indexes initially selected. - /// - /// Defaults to `null`. - ///```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// child: SfFunnelChart( - /// initialSelectedDataIndexes: [IndexesModel(1, 0)] - /// ) - /// ) - /// ) - /// ); - /// } - List initialSelectedDataIndexes; - - /// Key to identify a series in a collection. - /// - /// On specifying [ValueKey] as the series [key], existing series index can be - /// changed in the series collection without losing its state. - /// - /// When a new series is added dynamically to the collection, existing series index will be changed. On that case, - /// the existing series and its state will be linked based on its chart type and this key value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// key: const ValueKey('funnel_series_key'), - /// ), - /// ) - /// ); - ///} - ///``` - final ValueKey? key; - - /// Used to create the renderer for custom series. - /// - /// This is applicable only when the custom series is defined in the sample - /// and for built-in series types, it is not applicable. - /// - /// Renderer created in this will hold the series state and - /// this should be created for each series. [onCreateRenderer] callback - /// function should return the renderer class and should not return null. - /// - /// Series state will be created only once per series and will not be created - /// again when we update the series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// onCreateRenderer:(FunnelSeries series){ - /// return CustomFunnelSeriesRenderer(); - /// } - /// ), - /// ) - /// ); - /// } - /// class CustomFunnelSeriesRenderer extends FunnelSeriesRenderer { - /// // custom implementation here... - /// } - ///``` - final ChartSeriesRendererFactory? onCreateRenderer; - - /// Triggers when the series renderer is created. - /// - /// Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ], - /// ) - /// ); - ///} - ///``` - final FunnelSeriesRendererCreatedCallback? onRendererCreated; - - /// Called when tapped on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointTap; - - /// Called when double tapped on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the double-tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointDoubleTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointDoubleTap; - - /// Called when long pressed on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the long-pressed data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointLongPress: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointLongPress; - - /// To calculate empty point values if null values are provided. - @override - void calculateEmptyPointValue( - int pointIndex, dynamic currentPoint, dynamic seriesRenderer) { - final List> dataPoints = seriesRenderer.dataPoints; - final EmptyPointSettings empty = emptyPointSettings; - final int pointLength = dataPoints.length; - final PointInfo point = dataPoints[pointIndex]; - if (point.y == null) { - switch (empty.mode) { - case EmptyPointMode.average: - final num previous = - pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; - final num next = pointIndex + 1 <= pointLength - 1 - ? dataPoints[pointIndex + 1].y ?? 0 - : 0; - point.y = (previous + next).abs() / 2; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.zero: - point.y = 0; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.drop: - case EmptyPointMode.gap: - point.isEmpty = true; - point.isVisible = false; - break; - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/chart_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/chart_base.dart deleted file mode 100644 index 85045fffb..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/chart_base.dart +++ /dev/null @@ -1,551 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../../circular_chart/utils/helper.dart'; -import '../../common/common.dart'; -import '../../common/event_args.dart'; -import '../../common/legend/renderer.dart'; - -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../renderer/pyramid_series.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/common.dart'; -import '../utils/helper.dart'; -import 'pyramid_base.dart'; -import 'pyramid_state_properties.dart'; - -/// Represents the pyramid chart base. -class PyramidChartBase { - /// Creates an instance of pyramid chart base. - PyramidChartBase(this.stateProperties); - - /// Specifies the pyramid state properties. - final PyramidStateProperties stateProperties; - - /// Specifies the current pyramid series. - late PyramidSeries currentSeries; - - /// Specifies the list of the visible series renderers. - List visibleSeriesRenderers = - []; - - /// Specifies the selection args. - SelectionArgs? _selectionArgs; - - /// To find the visible series. - void findVisibleSeries() { - stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints = - >[]; - - //Considered the first series, since in triangular series one series will be considered for rendering - final PyramidSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - currentSeries = seriesRenderer.series; - //Setting series type - seriesRenderer.seriesType = 'pyramid'; - final ChartIndexedValueMapper? xValue = currentSeries.xValueMapper; - final ChartIndexedValueMapper? yValue = currentSeries.yValueMapper; - for (int pointIndex = 0; - pointIndex < currentSeries.dataSource!.length; - pointIndex++) { - if (xValue!(pointIndex) != null) { - seriesRenderer.dataPoints - .add(PointInfo(xValue(pointIndex), yValue!(pointIndex))); - } - } - visibleSeriesRenderers - ..clear() - ..add(seriesRenderer); - } - - /// To calculate empty point values if null values are provided. - void _calculatePyramidEmptyPoints( - PyramidSeriesRendererExtension seriesRenderer) { - for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { - if (seriesRenderer.dataPoints[i].y == null) { - seriesRenderer.series.calculateEmptyPointValue( - i, seriesRenderer.dataPoints[i], seriesRenderer); - } - } - } - - /// To process the data points for series render. - void processDataPoints() { - for (final PyramidSeriesRendererExtension seriesRenderer - in visibleSeriesRenderers) { - currentSeries = seriesRenderer.series; - _calculatePyramidEmptyPoints(seriesRenderer); - _calculateVisiblePoints(seriesRenderer); - _setPointStyle(seriesRenderer); - _findSumOfPoints(seriesRenderer); - } - } - - /// To calculate the visible points in a series. - void _calculateVisiblePoints(PyramidSeriesRendererExtension seriesRenderer) { - final List> points = seriesRenderer.dataPoints; - seriesRenderer.renderPoints = >[]; - for (int i = 0; i < points.length; i++) { - if (points[i].isVisible) { - seriesRenderer.renderPoints!.add(points[i]); - } - } - } - - /// To set style properties for the current point. - void _setPointStyle(PyramidSeriesRendererExtension seriesRenderer) { - currentSeries = seriesRenderer.series; - final List palette = stateProperties.chart.palette; - final ChartIndexedValueMapper? pointColor = - currentSeries.pointColorMapper; - final EmptyPointSettings empty = currentSeries.emptyPointSettings; - final ChartIndexedValueMapper? textMapping = - currentSeries.textFieldMapper; - final List> points = seriesRenderer.renderPoints!; - PointInfo currentPoint; - List legendToggles; - MeasureWidgetContext item; - LegendRenderContext legendRenderContext; - for (int i = 0; i < points.length; i++) { - currentPoint = points[i]; - currentPoint.fill = currentPoint.isEmpty && - empty.color != null // ignore: unnecessary_null_comparison - ? empty.color - : pointColor!(i) ?? palette[i % palette.length]; - currentPoint.color = currentPoint.fill; - currentPoint.borderColor = currentPoint.isEmpty && - empty.borderColor != null // ignore: unnecessary_null_comparison - ? empty.borderColor - : currentSeries.borderColor; - currentPoint.borderWidth = currentPoint.isEmpty && - empty.borderWidth != null // ignore: unnecessary_null_comparison - ? empty.borderWidth - : currentSeries.borderWidth; - currentPoint.borderColor = currentPoint.borderWidth == 0 - ? Colors.transparent - : currentPoint.borderColor; - - currentPoint.text = currentPoint.text ?? - (textMapping != null - ? textMapping(i) ?? currentPoint.y.toString() - : currentPoint.y.toString()); - - if (stateProperties.chart.legend.legendItemBuilder != null) { - legendToggles = - stateProperties.renderingDetails.legendToggleTemplateStates; - if (legendToggles.isNotEmpty) { - for (int j = 0; j < legendToggles.length; j++) { - item = legendToggles[j]; - if (i == item.pointIndex) { - currentPoint.isVisible = false; - break; - } - } - } - } else { - if (stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; - j < stateProperties.renderingDetails.legendToggleStates.length; - j++) { - legendRenderContext = - stateProperties.renderingDetails.legendToggleStates[j]; - if (i == legendRenderContext.seriesIndex) { - currentPoint.isVisible = false; - break; - } - } - } - } - } - } - - /// To find the sum of points. - void _findSumOfPoints(PyramidSeriesRendererExtension seriesRenderer) { - seriesRenderer.sumOfPoints = 0; - for (final PointInfo point in seriesRenderer.renderPoints!) { - if (point.isVisible) { - seriesRenderer.sumOfPoints += point.y!.abs(); - } - } - } - - /// To initialize the series properties in the chart. - void initializeSeriesProperties( - PyramidSeriesRendererExtension seriesRenderer) { - final PyramidSeries series = seriesRenderer.series; - final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; - final bool reverse = seriesRenderer.seriesType == 'pyramid'; - seriesRenderer.triangleSize = Size( - percentToValue(series.width, chartAreaRect.width)!.toDouble(), - percentToValue(series.height, chartAreaRect.height)!.toDouble()); - seriesRenderer.explodeDistance = - percentToValue(series.explodeOffset, chartAreaRect.width)!; - if (series.pyramidMode == PyramidMode.linear) { - _initializeSizeRatio(seriesRenderer, reverse); - } else { - _initializeSurfaceSizeRatio(seriesRenderer); - } - } - - /// To initialize the surface size ratio in the chart. - void _initializeSurfaceSizeRatio( - PyramidSeriesRendererExtension seriesRenderer) { - final num count = seriesRenderer.renderPoints!.length; - final num sumOfValues = seriesRenderer.sumOfPoints; - List y; - List height; - y = []; - height = []; - final num gapRatio = min(max(seriesRenderer.series.gapRatio, 0), 1); - final num gapHeight = gapRatio / (count - 1); - final num preSum = _getSurfaceHeight(0, sumOfValues); - num currY = 0; - PointInfo point; - for (int i = 0; i < count; i++) { - point = seriesRenderer.renderPoints![i]; - if (point.isVisible) { - y.add(currY); - height.add(_getSurfaceHeight(currY, point.y!.abs())); - currY += height[i] + gapHeight * preSum; - } - } - final num coeff = 1 / (currY - gapHeight * preSum); - for (int i = 0; i < count; i++) { - point = seriesRenderer.renderPoints![i]; - if (point.isVisible) { - point.yRatio = coeff * y[i]; - point.heightRatio = coeff * height[i]; - } - } - } - - /// To get the surface height. - num _getSurfaceHeight(num y, num surface) => - _solveQuadraticEquation(1, 2 * y, -surface); - - /// To solve quadratic equations. - num _solveQuadraticEquation(num a, num b, num c) { - num root1; - num root2; - final num d = b * b - 4 * a * c; - if (d >= 0) { - final num sd = sqrt(d); - root1 = (-b - sd) / (2 * a); - root2 = (-b + sd) / (2 * a); - return max(root1, root2); - } - return 0; - } - - /// To initialize the size ratio for the pyramid. - void _initializeSizeRatio(PyramidSeriesRendererExtension seriesRenderer, - [bool? reverse]) { - final List> points = seriesRenderer.renderPoints!; - double y; - assert( - // ignore: unnecessary_null_comparison - !(seriesRenderer.series.gapRatio != null) || - seriesRenderer.series.gapRatio >= 0 && - seriesRenderer.series.gapRatio <= 1, - 'The gap ratio for the pyramid chart must be between 0 and 1.'); - final double gapRatio = min(max(seriesRenderer.series.gapRatio, 0), 1); - final double coEff = - 1 / (seriesRenderer.sumOfPoints * (1 + gapRatio / (1 - gapRatio))); - final double spacing = gapRatio / (points.length - 1); - y = 0; - int index; - num height; - 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!; - points[index].yRatio = y; - points[index].heightRatio = height; - y += height + spacing; - } - } - } - - /// To explode current point index. - void pointExplode(int pointIndex) { - bool existExplodedRegion = false; - final PyramidSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - final PointInfo point = seriesRenderer.renderPoints![pointIndex]; - if (seriesRenderer.series.explode) { - if (stateProperties.renderingDetails.explodedPoints.isNotEmpty) { - existExplodedRegion = true; - final int previousIndex = - stateProperties.renderingDetails.explodedPoints[0]; - seriesRenderer.renderPoints![previousIndex].explodeDistance = 0; - point.explodeDistance = - previousIndex == pointIndex ? 0 : seriesRenderer.explodeDistance; - stateProperties.renderingDetails.explodedPoints[0] = pointIndex; - if (previousIndex == pointIndex) { - stateProperties.renderingDetails.explodedPoints = []; - } - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - if (!existExplodedRegion) { - point.explodeDistance = seriesRenderer.explodeDistance; - stateProperties.renderingDetails.explodedPoints.add(pointIndex); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - calculatePathRegion(pointIndex, seriesRenderer); - } - } - - /// To calculate region path for rendering chart. - void calculatePathRegion( - int pointIndex, PyramidSeriesRendererExtension seriesRenderer) { - final PointInfo currentPoint = - seriesRenderer.renderPoints![pointIndex]; - currentPoint.pathRegion = []; - final Size area = seriesRenderer.triangleSize; - final Rect rect = stateProperties.renderingDetails.chartContainerRect; - final num seriesTop = rect.top + (rect.height - area.height) / 2; - const num offset = 0; - - // ignore: prefer_if_null_operators - final num extraSpace = (currentPoint.explodeDistance != null - ? currentPoint.explodeDistance! - : isNeedExplode(pointIndex, currentSeries, stateProperties) - ? seriesRenderer.explodeDistance - : 0) + - (rect.width - seriesRenderer.triangleSize.width) / 2; - final num emptySpaceAtLeft = extraSpace + rect.left; - num top = currentPoint.yRatio; - num bottom = currentPoint.yRatio + currentPoint.heightRatio; - final num topRadius = 0.5 * (1 - currentPoint.yRatio); - final num bottomRadius = 0.5 * (1 - bottom); - top += seriesTop / area.height; - bottom += seriesTop / area.height; - num line1X, line1Y, line2X, line2Y, line3X, line3Y, line4X, line4Y; - line1X = emptySpaceAtLeft + offset + topRadius * area.width; - line1Y = top * area.height; - line2X = emptySpaceAtLeft + offset + (1 - topRadius) * area.width; - line2Y = top * area.height; - line3X = emptySpaceAtLeft + offset + (1 - bottomRadius) * area.width; - line3Y = bottom * area.height; - line4X = emptySpaceAtLeft + offset + bottomRadius * area.width; - line4Y = bottom * area.height; - 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, int pointIndex, - PyramidSeriesRendererExtension seriesRenderer) { - calculatePathRegion(pointIndex, seriesRenderer); - final PointInfo currentPoint = - 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); - path.lineTo(currentPoint.pathRegion[1].dx, currentPoint.pathRegion[1].dy); - 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) { - seriesRenderer.maximumDataLabelRegion = path.getBounds(); - } - _segmentPaint(canvas, path, pointIndex, seriesRenderer); - } - - /// To paint the pyramid segments. - void _segmentPaint(Canvas canvas, Path path, int pointIndex, - PyramidSeriesRendererExtension seriesRenderer) { - final PointInfo point = seriesRenderer.renderPoints![pointIndex]; - final StyleOptions? style = _getPointStyle( - pointIndex, seriesRenderer, stateProperties.chart, point); - - final Color fillColor = - style != null && style.fill != null ? style.fill! : point.fill; - - final Color strokeColor = style != null && style.strokeColor != null - ? style.strokeColor! - : point.borderColor; - - final double strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth!.toDouble() - : point.borderWidth.toDouble(); - - final double opacity = style != null && style.opacity != null - ? style.opacity! - : currentSeries.opacity; - - drawPath( - canvas, - StyleOptions( - fill: fillColor, - strokeWidth: stateProperties.renderingDetails.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 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.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(int pointIndex, ActivationMode mode) { - bool isPointAlreadySelected = false; - final SfPyramidChart chart = stateProperties.chart; - final PyramidSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - int? currentSelectedIndex; - const int seriesIndex = 0; - if (seriesRenderer.isSelectionEnable && mode == chart.selectionGesture) { - if (stateProperties.renderingDetails.selectionData.isNotEmpty) { - if (!chart.enableMultiSelection && - stateProperties.renderingDetails.selectionData.isNotEmpty && - stateProperties.renderingDetails.selectionData.length > 1) { - if (stateProperties.renderingDetails.selectionData - .contains(pointIndex)) { - currentSelectedIndex = pointIndex; - } - stateProperties.renderingDetails.selectionData.clear(); - if (currentSelectedIndex != null) { - stateProperties.renderingDetails.selectionData.add(pointIndex); - } - } - - int selectionIndex; - for (int i = 0; - i < stateProperties.renderingDetails.selectionData.length; - i++) { - selectionIndex = stateProperties.renderingDetails.selectionData[i]; - if (!chart.enableMultiSelection) { - isPointAlreadySelected = - stateProperties.renderingDetails.selectionData.length == 1 && - pointIndex == selectionIndex; - if (seriesRenderer.selectionBehavior.toggleSelection == true || - !isPointAlreadySelected) { - stateProperties.renderingDetails.selectionData.removeAt(i); - } - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!(_getSelectionEventArgs( - seriesRenderer, seriesIndex, selectionIndex)); - } - } else if (pointIndex == selectionIndex) { - if (seriesRenderer.selectionBehavior.toggleSelection == true) { - stateProperties.renderingDetails.selectionData.removeAt(i); - } - isPointAlreadySelected = true; - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!(_getSelectionEventArgs( - seriesRenderer, seriesIndex, selectionIndex)); - } - break; - } - } - } - if (!isPointAlreadySelected) { - stateProperties.renderingDetails.selectionData.add(pointIndex); - stateProperties.renderingDetails.seriesRepaintNotifier.value++; - if (chart.onSelectionChanged != null) { - chart.onSelectionChanged!( - _getSelectionEventArgs(seriesRenderer, seriesIndex, pointIndex)); - } - } - } - } - - /// To return style options for the point on selection. - StyleOptions? _getPointStyle( - int currentPointIndex, - PyramidSeriesRendererExtension seriesRenderer, - SfPyramidChart chart, - PointInfo point) { - StyleOptions? pointStyle; - final dynamic selection = seriesRenderer.series.selectionBehavior; - if (selection.enable == true) { - if (stateProperties.renderingDetails.selectionData.isNotEmpty) { - int selectionIndex; - for (int i = 0; - i < stateProperties.renderingDetails.selectionData.length; - i++) { - selectionIndex = stateProperties.renderingDetails.selectionData[i]; - if (currentPointIndex == selectionIndex) { - pointStyle = StyleOptions( - 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 == - stateProperties.renderingDetails.selectionData.length - 1) { - pointStyle = StyleOptions( - fill: _selectionArgs != null - ? _selectionArgs!.unselectedColor - : selection.unselectedColor, - strokeWidth: _selectionArgs != null - ? _selectionArgs!.unselectedBorderWidth - : selection.unselectedBorderWidth, - strokeColor: _selectionArgs != null - ? _selectionArgs!.unselectedBorderColor - : selection.unselectedBorderColor, - opacity: selection.unselectedOpacity); - } - } - } - } - return pointStyle; - } - - /// To perform selection event and return selectionArgs. - SelectionArgs _getSelectionEventArgs( - PyramidSeriesRendererExtension seriesRenderer, - int seriesIndex, - int pointIndex) { - final SfPyramidChart chart = seriesRenderer.stateProperties.chart; - // ignore: unnecessary_null_comparison - if (seriesRenderer != null && - //ignore: unnecessary_null_comparison - chart.series != null && - pointIndex < chart.series.dataSource!.length) { - final dynamic selectionBehavior = seriesRenderer.selectionBehavior; - _selectionArgs = SelectionArgs( - seriesRenderer: seriesRenderer, - seriesIndex: seriesIndex, - viewportPointIndex: pointIndex, - pointIndex: pointIndex); - _selectionArgs!.selectedBorderColor = - selectionBehavior.selectedBorderColor; - _selectionArgs!.selectedBorderWidth = - selectionBehavior.selectedBorderWidth; - _selectionArgs!.selectedColor = selectionBehavior.selectedColor; - _selectionArgs!.unselectedBorderColor = - selectionBehavior.unselectedBorderColor; - _selectionArgs!.unselectedBorderWidth = - selectionBehavior.unselectedBorderWidth; - _selectionArgs!.unselectedColor = selectionBehavior.unselectedColor; - } - return _selectionArgs!; - } -} 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 deleted file mode 100644 index 101bc37f0..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart +++ /dev/null @@ -1,850 +0,0 @@ -import 'dart:ui' as dart_ui; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -import '../../chart/utils/helper.dart'; -import '../../common/common.dart'; -import '../../common/legend/legend.dart'; -import '../../common/legend/renderer.dart'; -import '../../common/rendering_details.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/helper.dart'; -import '../renderer/renderer_extension.dart'; -import 'chart_base.dart'; -import 'pyramid_plot_area.dart'; -import 'pyramid_state_properties.dart'; - -/// Renders the pyramid chart. -/// -/// To render a pyramid chart, create an instance of PyramidSeries, and add it to the series property of SfPyramidChart. -/// -/// Properties such as opacity, [borderWidth], [borderColor], pointColorMapper -/// are used to customize the appearance of a pyramid segment. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} -//ignore:must_be_immutable -class SfPyramidChart extends StatefulWidget { - /// Creating an argument constructor of SfPyramidChart class. - SfPyramidChart({ - Key? key, - this.backgroundColor, - this.backgroundImage, - this.borderColor = Colors.transparent, - this.borderWidth = 0.0, - this.onLegendItemRender, - this.onTooltipRender, - this.onDataLabelRender, - this.onDataLabelTapped, - this.onLegendTapped, - this.onSelectionChanged, - this.onChartTouchInteractionUp, - this.onChartTouchInteractionDown, - this.onChartTouchInteractionMove, - ChartTitle? title, - PyramidSeries? series, - EdgeInsets? margin, - Legend? legend, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - Color.fromRGBO(255, 240, 219, 1), - Color.fromRGBO(238, 238, 238, 1) - ], - TooltipBehavior? tooltipBehavior, - ActivationMode? selectionGesture, - bool? enableMultiSelection, - }) : title = title ?? ChartTitle(), - series = series ?? PyramidSeries(), - margin = margin ?? const EdgeInsets.fromLTRB(10, 10, 10, 10), - legend = legend ?? Legend(), - tooltipBehavior = tooltipBehavior ?? TooltipBehavior(), - selectionGesture = selectionGesture ?? ActivationMode.singleTap, - enableMultiSelection = enableMultiSelection ?? false, - super(key: key); - - /// Customizes the chart title. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// title: ChartTitle(text: 'Pyramid Chart') - /// ) - /// ); - ///} - ///``` - final ChartTitle title; - - /// Background color of the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// backgroundColor: Colors.blue - /// ) - /// ); - ///} - ///``` - final Color? backgroundColor; - - /// Border color of the chart. - /// - /// Defaults to `Colors.transparent`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// borderColor: Colors.blue - /// ) - /// ); - ///} - ///``` - final Color borderColor; - - /// Border width of the chart. - /// - /// Defaults to `0.0`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// borderWidth: 2 - /// ) - /// ); - ///} - ///``` - final double borderWidth; - - /// Customizes the chart series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries<_PyramidData, String>( - /// dataSource: data, - /// xValueMapper: (_PyramidData data, _) => data.xData, - /// yValueMapper: (_PyramidData data, _) => data.yData) - /// ) - /// ); - ///} - ///``` - final PyramidSeries series; - - /// Customizes the chart. - /// - /// Defaults to `const EdgeInsets.fromLTRB(10, 10, 10, 10)`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// margin: const EdgeInsets.all(2), - /// ) - /// ); - ///} - ///``` - final EdgeInsets margin; - - /// Customizes the legend in the chart. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// legend: Legend(isVisible: true) - /// ) - /// ); - ///} - ///``` - final Legend legend; - - /// Color palette for the data points in the chart series. - /// - /// If the series color is not specified, then the series will be rendered with the appropriate palette color. - /// Ten colors are available by default. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// palette: [Colors.red, Colors.green] - /// ) - /// ); - ///} - ///``` - final List palette; - - /// Customizes the tooltip in chart. - /// - ///```dart - ///TooltipBehavior _tooltipBehavior; - /// - ///@override - ///void initState() { - /// _tooltipBehavior = TooltipBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// tooltipBehavior: _tooltipBehavior - /// ) - /// ); - ///} - ///``` - final TooltipBehavior tooltipBehavior; - - /// Occurs while the legend is rendered. - /// - /// Here, you can get the legend's text, shape, series index, and point index case of the pyramid series. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// legend: Legend(isVisible: true), - /// onLegendItemRender: (LegendRendererArgs args) => legend(args), - /// ) - /// ); - ///} - ///void legend(LegendRendererArgs args) { - /// args.legendIconType = LegendIconType.diamond; - ///} - ///``` - final PyramidLegendRenderCallback? onLegendItemRender; - - /// Occurs when the tooltip is rendered. - /// - /// Here, you can get the tooltip arguments and customize the arguments. - final PyramidTooltipCallback? onTooltipRender; - - /// Occurs when the data label is rendered, here data label arguments can be customized. - final PyramidDataLabelRenderCallback? onDataLabelRender; - - /// Occurs when the legend is tapped, the arguments can be used to customize the legend arguments. - final ChartLegendTapCallback? onLegendTapped; - - /// 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. - /// - /// Defaults to `[]`. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// initialSelectedDataIndexes: [1,0], - /// selectionBehavior: _selectionBehavior - /// ), - /// ) - /// ); - ///} - ///``` - - /// Gesture for activating the selection. - /// - /// Selection can be activated in `ActivationMode.none`, `ActivationMode.singleTap`, - /// `ActivationMode.doubleTap` and `ActivationMode.longPress`. - /// - /// Defaults to `ActivationMode.singleTap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// selectionGesture: ActivationMode.singleTap, - /// series: PyramidSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ) - /// ); - ///} - ///``` - final ActivationMode selectionGesture; - - /// Enables or disables the multiple data points selection. - /// - /// Defaults to `false`. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// enableMultiSelection: true, - /// series: PyramidSeries( - /// selectionBehavior: _selectionBehavior - /// ), - /// ) - /// ); - ///} - ///``` - final bool enableMultiSelection; - - /// Background image for chart. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// backgroundImage: const AssetImage('image.png'), - /// ) - /// ); - ///} - ///``` - final ImageProvider? backgroundImage; - - /// Occurs while selection changes. Here, you can get the series, selected color, - /// unselected color, selected border color, unselected border color, selected - /// border width, unselected border width, series index, and point index. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// onSelectionChanged: (SelectionArgs args) => select(args), - /// ) - /// ); - ///} - ///void select(SelectionArgs args) { - /// print(args.selectedBorderColor); - ///} - ///``` - final PyramidSelectionCallback? onSelectionChanged; - - /// Called when the data label is tapped. - /// - /// Whenever the data label is tapped, the `onDataLabelTapped` callback will be called. Provides options to - /// get the position of the data label, series index, point index and its text. - /// - /// _Note:_ This callback will not be called, when the builder is specified for data label - /// (data label template). For this case, custom widget specified in the `DataLabelSettings.builder` property - /// can be wrapped using the `GestureDetector` and this functionality can be achieved at the application level. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// onDataLabelTapped: (DataLabelTapDetails args) { - /// print(arg.seriesIndex); - /// } - /// ) - /// ); - ///} - /// - ///``` - final DataLabelTapCallback? onDataLabelTapped; - - /// Occurs when tapped on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// onChartTouchInteractionUp: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final PyramidTouchInteractionCallback? onChartTouchInteractionUp; - - /// Occurs when touched and moved on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// onChartTouchInteractionMove: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final PyramidTouchInteractionCallback? onChartTouchInteractionMove; - - /// Occurs when touched on the chart area. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args){ - /// print(args.position.dx.toString()); - /// print(args.position.dy.toString()); - /// } - /// ) - /// ); - ///} - ///``` - final PyramidTouchInteractionCallback? onChartTouchInteractionDown; - - @override - State createState() => SfPyramidChartState(); -} - -/// Represents the state class of [SfPyramidChart] widget. -/// -class SfPyramidChartState extends State - with TickerProviderStateMixin { - /// Specifies the pyramid state properties. - late PyramidStateProperties _stateProperties; - - /// 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() { - _stateProperties = PyramidStateProperties( - renderingDetails: RenderingDetails(), chartState: this); - - _initializeDefaultValues(); - //Update and maintain the series state, when we update the series in the series collection // - _createAndUpdateSeriesRenderer(); - 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() { - _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); - _stateProperties.renderingDetails.isRtl = - Directionality.of(context) == TextDirection.rtl; - super.didChangeDependencies(); - } - - /// Called whenever the widget configuration changes. - /// - /// If the parent widget rebuilds and requests that this location in the tree update 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 is 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); - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - _stateProperties.renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.chartTooltipState != null) { - tooltipRenderingDetails.show = false; - } - super.didUpdateWidget(oldWidget); - _stateProperties.renderingDetails.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) { - _stateProperties.renderingDetails.oldDeviceOrientation = - _stateProperties.renderingDetails.oldDeviceOrientation == null - ? MediaQuery.of(context).orientation - : _stateProperties.renderingDetails.deviceOrientation; - _stateProperties.renderingDetails.deviceOrientation = - MediaQuery.of(context).orientation; - _stateProperties.isTooltipOrientationChanged = false; - - return RepaintBoundary( - child: ChartContainer( - child: GestureDetector( - child: Container( - decoration: BoxDecoration( - color: widget.backgroundColor, - image: widget.backgroundImage != null - ? DecorationImage( - image: widget.backgroundImage!, - fit: BoxFit.fill) - : null, - border: Border.all( - color: widget.borderColor, - width: widget.borderWidth)), - child: Column( - children: [ - renderChartTitle(_stateProperties), - _renderChartElements() - ], - ))))); - } - - /// 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 life cycle is terminal: there is no way to remount a [State] object that has been disposed. - /// - /// Sub classes 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( - _stateProperties.renderingDetails.animationController, - _repaintChartElements); - super.dispose(); - } - - /// Method to convert the [SfPyramidChart] as an 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. - /// - /// ```dart - ///final GlobalKey _key = GlobalKey(); - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Column( - /// children: [SfPyramidChart( - /// key: _key - /// series: PyramidSeries<_PyramidData, String>( - /// dataSource: data, - /// xValueMapper: (_PyramidData data, _) => data.xData, - /// yValueMapper: (_PyramidData data, _) => data.yData) - /// ), - /// ], - /// ), - /// RaisedButton( - /// child: Text( - /// 'To Image', - /// ), - /// onPressed: _renderImage, - /// shape: RoundedRectangleBorder( - /// borderRadius: BorderRadius.circular(20)), - /// ) - /// ], - /// ), - /// ); - /// } - /// Future _renderImage() async { - /// dart_ui.Image data = await _key.currentState.toImage(pixelRatio: 3.0); - /// final bytes = await data.toByteData(format: dart_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.white, - /// child: Image.memory(bytes.buffer.asUint8List()), - /// ), - /// ), - /// ); - /// }, - /// ), - /// ); - /// } - ///} - ///``` - - Future toImage({double pixelRatio = 1.0}) async { - 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 - return image; - } - - /// To initialize default chart elements value. - void _initializeDefaultValues() { - _stateProperties.chartSeries = PyramidChartBase(_stateProperties); - _stateProperties.renderingDetails.chartLegend = - ChartLegend(_stateProperties); - _stateProperties.renderingDetails.initialRender = true; - _stateProperties.renderingDetails.seriesRepaintNotifier = - ValueNotifier(0); - _stateProperties.renderingDetails.legendToggleStates = - []; - _stateProperties.renderingDetails.legendToggleTemplateStates = - []; - _stateProperties.renderingDetails.explodedPoints = []; - _stateProperties.renderingDetails.animateCompleted = false; - _stateProperties.renderingDetails.isLegendToggled = false; - _stateProperties.renderingDetails.widgetNeedUpdate = false; - _stateProperties.renderingDetails.legendWidgetContext = - []; - _stateProperties.renderingDetails.dataLabelTemplateRegions = []; - _stateProperties.renderingDetails.selectionData = []; - _stateProperties.renderingDetails.animationController = - AnimationController(vsync: this)..addListener(_repaintChartElements); - _stateProperties.renderingDetails.tooltipBehaviorRenderer = - TooltipBehaviorRenderer(_stateProperties); - _stateProperties.renderingDetails.legendRenderer = - LegendRenderer(widget.legend); - } - - // In this method, create and update the series renderer for each series // - void _createAndUpdateSeriesRenderer([SfPyramidChart? oldWidget]) { - // ignore: unnecessary_null_comparison - if (widget.series != null) { - final PyramidSeriesRenderer? oldSeriesRenderer = - // ignore: unnecessary_null_comparison - oldWidget != null && oldWidget.series != null - ? _stateProperties.chartSeries.visibleSeriesRenderers[0] - : null; - PyramidSeries series; - series = widget.series; - - // Create and update the series list here - PyramidSeriesRendererExtension seriesRenderers; - - if (oldSeriesRenderer != null && - isSameSeries(oldWidget!.series, series)) { - seriesRenderers = oldSeriesRenderer as PyramidSeriesRendererExtension; - } else { - final PyramidSeriesRenderer renderer = series.createRenderer(series); - seriesRenderers = renderer is PyramidSeriesRendererExtension - ? renderer - : PyramidSeriesRendererExtension(); - - if (seriesRenderers.controller == null && - series.onRendererCreated != null) { - seriesRenderers.controller = PyramidSeriesController(seriesRenderers); - series.onRendererCreated!(seriesRenderers.controller!); - } - } - - seriesRenderers.series = series; - seriesRenderers.isSelectionEnable = series.selectionBehavior.enable; - seriesRenderers.stateProperties = _stateProperties; - _stateProperties.chartSeries.visibleSeriesRenderers - ..clear() - ..add(seriesRenderers); - } - } - - void _repaintChartElements() { - _stateProperties.renderingDetails.seriesRepaintNotifier.value++; - } - - /// To render chart elements. - Widget _renderChartElements() { - return Expanded(child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - Widget element; - if (widget.series.dataSource != null) { - _initialize(constraints); - _stateProperties.renderingDetails.prevSize = - _stateProperties.renderingDetails.prevSize ?? constraints.biggest; - _stateProperties.renderingDetails.didSizeChange = - _stateProperties.renderingDetails.prevSize != constraints.biggest; - _stateProperties.renderingDetails.prevSize = constraints.biggest; - - final PointInfo tooltipPoint = - _getChartPoints(_stateProperties); - SchedulerBinding.instance!.addPostFrameCallback((_) { - _validateStateMaintenance(_stateProperties, tooltipPoint); - }); - _stateProperties.chartSeries.findVisibleSeries(); - _stateProperties.chartSeries.processDataPoints(); - final List legendTemplates = - bindLegendTemplateWidgets(_stateProperties); - if (legendTemplates.isNotEmpty && - _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { - // ignore: avoid_unnecessary_containers - element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); - } else { - _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( - _stateProperties.renderingDetails.chartLegend.chartSize); - _stateProperties.chartPlotArea = - PyramidPlotArea(stateProperties: _stateProperties); - element = getElements( - _stateProperties, _stateProperties.chartPlotArea, constraints)!; - } - } else { - element = Container(); - } - return element; - })); - } - - void _refresh() { - final List legendWidgetContexts = - _stateProperties.renderingDetails.legendWidgetContext; - if (legendWidgetContexts.isNotEmpty) { - MeasureWidgetContext templateContext; - RenderBox renderBox; - for (int i = 0; i < legendWidgetContexts.length; i++) { - templateContext = legendWidgetContexts[i]; - renderBox = templateContext.context!.findRenderObject() as RenderBox; - templateContext.size = renderBox.size; - } - setState(() { - /// The chart will be rebuilding again, Once legend template sizes will be calculated. - }); - } - } - - /// To initialize chart container area. - void _initialize(BoxConstraints constraints) { - _stateProperties.renderingDetails.chartWidgets = []; - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - final EdgeInsets margin = widget.margin; - final bool isMobilePlatform = - defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS; - final LegendRenderer legendRenderer = - _stateProperties.renderingDetails.legendRenderer; - legendRenderer.legendPosition = - widget.legend.position == LegendPosition.auto - ? (height > width - ? isMobilePlatform - ? LegendPosition.top - : LegendPosition.bottom - : LegendPosition.right) - : widget.legend.position; - - _stateProperties.renderingDetails.chartLegend.chartSize = Size( - width - margin.left - margin.right, - height - margin.top - margin.bottom); - } - - /// This will return tooltip chart point. - PointInfo _getChartPoints(PyramidStateProperties _stateProperties) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - - PointInfo tooltipPoint = PointInfo(null, null); - - if (_stateProperties.renderingDetails.oldDeviceOrientation != - _stateProperties.renderingDetails.deviceOrientation || - _stateProperties.renderingDetails.didSizeChange) { - if (tooltipRenderingDetails.showLocation != null && - _stateProperties.chart.tooltipBehavior.enable == true && - _stateProperties.isTooltipHidden == false) { - tooltipPoint = pyramidFunnelPixelToPoint( - tooltipRenderingDetails.showLocation!, - _stateProperties.chartSeries.visibleSeriesRenderers[0]); - } - } - - return tooltipPoint; - } - - /// Here for orientation change/browser resize, the logic in this method will get executed. - void _validateStateMaintenance(PyramidStateProperties _stateProperties, - PointInfo tooltipPoint) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - if (_stateProperties.renderingDetails.oldDeviceOrientation != - _stateProperties.renderingDetails.deviceOrientation || - _stateProperties.renderingDetails.didSizeChange) { - if (tooltipRenderingDetails.showLocation != null && - _stateProperties.chart.tooltipBehavior.enable && - !_stateProperties.isTooltipHidden) { - _stateProperties.isTooltipOrientationChanged = true; - late PointInfo point; - late int index; - for (int i = 0; - i < - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints.length; - i++) { - if (_stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == - tooltipPoint.x && - _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == - tooltipPoint.y) { - index = i; - point = _stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[i]; - } - } - final Offset tooltipPosition = pyramidFunnelPointToPixel( - point, _stateProperties.chartSeries.visibleSeriesRenderers[0]); - if (_stateProperties.chart.tooltipBehavior.builder != null) { - _stateProperties.chartPlotArea.showPyramidTooltipTemplate(index); - } else { - tooltipRenderingDetails.internalShowByPixel( - tooltipPosition.dx, tooltipPosition.dy); - } - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart deleted file mode 100644 index f8e1bd2be..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart +++ /dev/null @@ -1,679 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; - -import '../../chart/user_interaction/selection_renderer.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/renderer/common.dart'; -import '../../common/event_args.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../../common/utils/helper.dart'; -import '../../pyramid_chart/utils/common.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../renderer/data_label_renderer.dart'; -import '../renderer/pyramid_chart_painter.dart'; -import '../renderer/pyramid_series.dart'; -import '../renderer/renderer_extension.dart'; -import 'pyramid_base.dart'; -import 'pyramid_state_properties.dart'; - -/// Represents the pyramid plot areas. -// ignore: must_be_immutable -class PyramidPlotArea extends StatelessWidget { - /// Creates an instance of a pyramid plot area. - // ignore: prefer_const_constructors_in_immutables - PyramidPlotArea({required this.stateProperties}); - - /// Creates the pyramid state properties. - final PyramidStateProperties stateProperties; - - /// Here, we are using get keyword in order 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 => stateProperties.chart; - - /// Represents the pyramid series renderer. - late PyramidSeriesRendererExtension seriesRenderer; - - /// Represents the value of the render box. - late RenderBox renderBox; - - /// Represents the value of the point region. - Region? pointRegion; - - /// Represents the value of tap down details. - late TapDownDetails tapDownDetails; - - /// Represents the series animation. - Animation? seriesAnimation; - - /// Represents the value of double tap position. - Offset? doubleTapPosition; - final bool _enableMouseHover = kIsWeb; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // ignore: avoid_unnecessary_containers - return Container( - child: MouseRegion( - // 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) { - TooltipHelper.getRenderingDetails(stateProperties - .renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - }, - child: Stack(textDirection: TextDirection.ltr, children: [ - _initializeChart(constraints, context), - Listener( - onPointerUp: (PointerUpEvent event) => _onTapUp(event), - onPointerDown: (PointerDownEvent event) => _onTapDown(event), - onPointerMove: (PointerMoveEvent event) => - _performPointerMove(event), - child: GestureDetector( - onLongPress: _onLongPress, - onDoubleTap: _onDoubleTap, - onTapUp: (TapUpDetails details) { - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(details.globalPosition); - if (chart.series.onPointTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - calculatePointSeriesIndex( - chart, - seriesRenderer, - stateProperties.renderingDetails.tapPosition!, - null, - ActivationMode.singleTap); - } - }, - child: Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: - const BoxDecoration(color: Colors.transparent), - )), - ), - ]))); - }); - } - - /// To initialize the chart. - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - _calculateContainerSize(constraints); - return GestureDetector( - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context))); - } - - /// To calculate chart plot area. - void _calculateContainerSize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - stateProperties.renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final EdgeInsets margin = chart.margin; - stateProperties.renderingDetails.chartAreaRect = Rect.fromLTWH( - margin.left, - margin.top, - width - margin.right - margin.left, - height - margin.top - margin.bottom); - } - - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _bindSeriesWidgets(); - _calculatePathRegion(); - findTemplates(stateProperties); - renderTemplates(stateProperties); - _bindTooltipWidgets(constraints); - renderBox = context.findRenderObject() as RenderBox; - stateProperties.chartPlotArea = this; - // ignore: avoid_unnecessary_containers - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: stateProperties.renderingDetails.chartWidgets!)); - } - - /// To calculate region path of pyramid. - void _calculatePathRegion() { - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - if (visibleSeriesRenderers.isNotEmpty) { - seriesRenderer = visibleSeriesRenderers[0]; - for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { - if (seriesRenderer.renderPoints![i].isVisible) { - stateProperties.chartSeries.calculatePathRegion(i, seriesRenderer); - } - } - } - } - - /// To bind series widget together. - void _bindSeriesWidgets() { - late CustomPainter seriesPainter; - PyramidSeries series; - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - SelectionBehaviorRenderer selectionBehaviorRenderer; - dynamic selectionBehavior; - for (int i = 0; i < visibleSeriesRenderers.length; i++) { - seriesRenderer = visibleSeriesRenderers[i]; - series = seriesRenderer.series; - stateProperties.chartSeries.initializeSeriesProperties(seriesRenderer); - selectionBehavior = - seriesRenderer.selectionBehavior = series.selectionBehavior; - selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, stateProperties); - SelectionHelper.setSelectionBehaviorRenderer( - series.selectionBehavior, selectionBehaviorRenderer); - selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer; - final SelectionDetails selectionDetails = - SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); - selectionDetails.selectionRenderer ??= SelectionRenderer(); - selectionDetails.selectionRenderer!.chart = chart; - selectionDetails.selectionRenderer!.seriesRendererDetails = - seriesRenderer; - if (series.initialSelectedDataIndexes.isNotEmpty) { - for (int index = 0; - index < series.initialSelectedDataIndexes.length; - index++) { - stateProperties.renderingDetails.selectionData - .add(series.initialSelectedDataIndexes[index]); - } - } - if (series.animationDuration > 0 && - !stateProperties.renderingDetails.didSizeChange && - (stateProperties.renderingDetails.deviceOrientation == - stateProperties.renderingDetails.oldDeviceOrientation) && - ((!stateProperties.renderingDetails.widgetNeedUpdate && - stateProperties.renderingDetails.initialRender!) || - stateProperties.renderingDetails.isLegendToggled)) { - final int totalAnimationDuration = - series.animationDuration.toInt() + series.animationDelay.toInt(); - stateProperties.renderingDetails.animationController.duration = - Duration(milliseconds: totalAnimationDuration); - const double maxSeriesInterval = 0.8; - double minSeriesInterval = 0.1; - minSeriesInterval = series.animationDelay.toInt() / - totalAnimationDuration * - (maxSeriesInterval - minSeriesInterval) + - minSeriesInterval; - seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: stateProperties.renderingDetails.animationController, - curve: Interval(minSeriesInterval, maxSeriesInterval, - curve: Curves.linear), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - stateProperties.renderingDetails.animateCompleted = true; - stateProperties.renderingDetails.initialRender = false; - if (stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state?.render(); - } - if (stateProperties.renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - stateProperties.renderingDetails.chartTemplate!.state != - null) { - stateProperties.renderingDetails.chartTemplate!.state - .templateRender(); - } - } - })); - stateProperties.renderingDetails.chartElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: stateProperties.renderingDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - stateProperties.renderingDetails.animationController.forward(from: 0.0); - } else { - stateProperties.renderingDetails.animateCompleted = true; - stateProperties.renderingDetails.initialRender = false; - if (stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state?.render(); - } - } - seriesRenderer.repaintNotifier = - stateProperties.renderingDetails.seriesRepaintNotifier; - if (seriesRenderer.seriesType == 'pyramid') { - seriesPainter = PyramidChartPainter( - stateProperties: stateProperties, - seriesIndex: i, - isRepaint: seriesRenderer.needsRepaint, - animationController: - stateProperties.renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: stateProperties.renderingDetails.seriesRepaintNotifier); - } - stateProperties.renderingDetails.chartWidgets! - .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); - stateProperties.renderDataLabel = PyramidDataLabelRenderer( - key: GlobalKey(), - stateProperties: stateProperties, - //ignore: avoid_bool_literals_in_conditional_expressions - show: stateProperties.renderingDetails.animateCompleted); - stateProperties.renderingDetails.chartWidgets! - .add(stateProperties.renderDataLabel!); - } - } - - /// To bind tooltip widgets. - void _bindTooltipWidgets(BoxConstraints constraints) { - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - stateProperties.renderingDetails.tooltipBehaviorRenderer; - TooltipHelper.setStateProperties(chart.tooltipBehavior, stateProperties); - if (tooltip.enable) { - final SfChartThemeData _chartTheme = - stateProperties.renderingDetails.chartTheme; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue = null; - tooltipRenderingDetails.chartTooltip = SfTooltip( - color: tooltip.color ?? _chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.borderWidth, - duration: tooltip.duration.toInt(), - 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, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? tooltipRenderingDetails.tooltipRenderingEvent - : null); - stateProperties.renderingDetails.chartWidgets! - .add(tooltipRenderingDetails.chartTooltip!); - } - } - - /// To perform pointer down event. - void _onTapDown(PointerDownEvent event) { - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - tooltipRenderingDetails.isHovering = false; - //renderBox = context.findRenderObject(); - stateProperties.renderingDetails.currentActive = null; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool isPoint = false; - const int seriesIndex = 0; - late int pointIndex; - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - final PyramidSeriesRendererExtension seriesRenderer = - visibleSeriesRenderers[seriesIndex]; - ChartTouchInteractionArgs touchArgs; - 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, - stateProperties.renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - if (chart.onDataLabelRender == null) { - break; - } - } - } - } - doubleTapPosition = stateProperties.renderingDetails.tapPosition; - if (stateProperties.renderingDetails.tapPosition != null && isPoint) { - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex, - visibleSeriesRenderers[seriesIndex].series, - visibleSeriesRenderers[seriesIndex].renderPoints![pointIndex], - ); - } else { - //hides the tooltip if the point of interaction is outside pyramid region of the chart - tooltipRenderingDetails.show = false; - tooltipRenderingDetails.hideTooltipTemplate(); - } - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - - /// To perform pointer move event. - void _performPointerMove(PointerMoveEvent event) { - ChartTouchInteractionArgs touchArgs; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = position; - chart.onChartTouchInteractionMove!(touchArgs); - } - } - - /// To perform double tap touch interactions. - void _onDoubleTap() { - const int seriesIndex = 0; - if (doubleTapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - if (chart.series.onPointDoubleTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - calculatePointSeriesIndex( - chart, - seriesRenderer, - stateProperties.renderingDetails.tapPosition!, - null, - ActivationMode.doubleTap); - stateProperties.renderingDetails.tapPosition = null; - } - final int pointIndex = - stateProperties.renderingDetails.currentActive!.pointIndex!; - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex, - visibleSeriesRenderers[seriesIndex].series, - visibleSeriesRenderers[seriesIndex].renderPoints![pointIndex]); - if (stateProperties.renderingDetails.currentActive != null) { - if (stateProperties - .renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.doubleTap) { - stateProperties.chartSeries.pointExplode(pointIndex); - final GlobalKey key = - stateProperties.renderDataLabel!.key as GlobalKey; - final PyramidDataLabelRendererState _pyramidDataLabelRendererState = - key.currentState as PyramidDataLabelRendererState; - _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - stateProperties.chartSeries - .seriesPointSelection(pointIndex, ActivationMode.doubleTap); - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { - if (chart.tooltipBehavior.builder != null) { - showPyramidTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition!.dx.toDouble(), - doubleTapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform long press touch interactions. - void _onLongPress() { - const int seriesIndex = 0; - if (stateProperties.renderingDetails.tapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - if (chart.series.onPointLongPress != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - calculatePointSeriesIndex( - chart, - seriesRenderer, - stateProperties.renderingDetails.tapPosition!, - null, - ActivationMode.longPress); - stateProperties.renderingDetails.tapPosition = null; - } - final List visibleSeriesRenderers = - stateProperties.chartSeries.visibleSeriesRenderers; - final int pointIndex = - stateProperties.renderingDetails.currentActive!.pointIndex!; - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex, - visibleSeriesRenderers[seriesIndex].series, - visibleSeriesRenderers[seriesIndex].renderPoints![pointIndex], - pointRegion); - stateProperties.chartSeries - .seriesPointSelection(pointIndex, ActivationMode.longPress); - if (stateProperties.renderingDetails.currentActive != null) { - if (stateProperties - .renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.longPress) { - stateProperties.chartSeries.pointExplode(pointIndex); - final GlobalKey key = - stateProperties.renderDataLabel!.key as GlobalKey; - final PyramidDataLabelRendererState _pyramidDataLabelRendererState = - key.currentState as PyramidDataLabelRendererState; - _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress) { - if (chart.tooltipBehavior.builder != null) { - showPyramidTooltipTemplate(); - } else { - stateProperties.renderingDetails.tooltipBehaviorRenderer.onLongPress( - // ignore: noop_primitive_operations - stateProperties.renderingDetails.tapPosition!.dx.toDouble(), - stateProperties.renderingDetails.tapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform pointer up event. - void _onTapUp(PointerUpEvent event) { - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer) - .isHovering = false; - bool isPoint = false; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { - if (seriesRenderer.renderPoints![j].isVisible) { - isPoint = isPointInPolygon(seriesRenderer.renderPoints![j].pathRegion, - stateProperties.renderingDetails.tapPosition!); - if (isPoint) { - break; - } - } - } - final ChartInteraction? currentActive = isPoint - ? stateProperties.renderingDetails.currentActive != null - ? stateProperties.renderingDetails.currentActive! - : null - : null; - ChartTouchInteractionArgs touchArgs; - if (currentActive != null) { - // ignore: unnecessary_null_comparison - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - triggerPyramidDataLabelEvent(chart, seriesRenderer, stateProperties, - stateProperties.renderingDetails.tapPosition!); - } - if (stateProperties.renderingDetails.tapPosition != null && - stateProperties.renderingDetails.currentActive != null) { - if (currentActive.series != null && - currentActive.series.explodeGesture == ActivationMode.singleTap) { - stateProperties.chartSeries.pointExplode(currentActive.pointIndex!); - final GlobalKey key = - stateProperties.renderDataLabel!.key as GlobalKey; - final PyramidDataLabelRendererState _pyramidDataLabelRendererState = - key.currentState as PyramidDataLabelRendererState; - _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - - if (stateProperties - .chartSeries.visibleSeriesRenderers[0].isSelectionEnable) { - stateProperties.chartSeries.seriesPointSelection( - currentActive.pointIndex!, ActivationMode.singleTap); - } - - if (chart.tooltipBehavior.enable && - stateProperties.renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - currentActive.series != null) { - if (chart.tooltipBehavior.builder != null) { - showPyramidTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - stateProperties.renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } - } - } - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - if (chart.series.onPointTap == null && - chart.series.onPointDoubleTap == null && - chart.series.onPointLongPress == null) { - stateProperties.renderingDetails.tapPosition = null; - } - } - - /// To perform event on mouse hover. - void _onHover(PointerEvent event) { - stateProperties.renderingDetails.currentActive = null; - stateProperties.renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool? isPoint; - const int seriesIndex = 0; - int? pointIndex; - final PyramidSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - stateProperties.renderingDetails.tooltipBehaviorRenderer; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { - if (seriesRenderer.renderPoints![j].isVisible) { - isPoint = isPointInPolygon(seriesRenderer.renderPoints![j].pathRegion, - stateProperties.renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - break; - } - } - } - if (stateProperties.renderingDetails.tapPosition != null && - isPoint != null && - isPoint) { - stateProperties.renderingDetails.currentActive = ChartInteraction( - seriesIndex, - pointIndex!, - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex].series, - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] - .renderPoints![pointIndex], - ); - } else if (tooltip.builder != null) { - tooltipRenderingDetails.hide(); - } - if (stateProperties.renderingDetails.tapPosition != null) { - if (tooltip.enable && - stateProperties.renderingDetails.currentActive != null && - stateProperties.renderingDetails.currentActive!.series != null) { - tooltipRenderingDetails.isHovering = true; - if (tooltip.builder != null && - stateProperties.renderingDetails.animateCompleted) { - showPyramidTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - tooltipBehaviorRenderer.onEnter( - position.dx.toDouble(), position.dy.toDouble()); - } - } else { - tooltipRenderingDetails.prevTooltipValue = null; - tooltipRenderingDetails.currentTooltipValue = null; - tooltipRenderingDetails.hide(); - } - } - stateProperties.renderingDetails.tapPosition = null; - } - - /// This method gets executed for showing tooltip when builder is provided in behavior. - void showPyramidTooltipTemplate([int? pointIndex]) { - stateProperties.isTooltipHidden = false; - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - stateProperties.renderingDetails.tooltipBehaviorRenderer); - if (!tooltipRenderingDetails.isHovering) { - //assigning null for the previous and current tooltip values in case of touch interaction - tooltipRenderingDetails.prevTooltipValue = null; - tooltipRenderingDetails.currentTooltipValue = null; - } - final PyramidSeries chartSeries = - stateProperties.renderingDetails.currentActive?.series ?? chart.series; - final PointInfo point = pointIndex == null - ? stateProperties.renderingDetails.currentActive?.point - : stateProperties - .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; - final Offset? location = - chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - !stateProperties - .chartSeries.visibleSeriesRenderers[0].series.explode - ? stateProperties.renderingDetails.tapPosition! - : point.symbolLocation; - bool isPoint = false; - for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { - if (seriesRenderer.renderPoints![j].isVisible == true) { - isPoint = isPointInPolygon( - seriesRenderer.renderPoints![j].pathRegion, location!); - if (isPoint) { - pointIndex = j; - break; - } - } - } - if (location != null && isPoint && (chartSeries.enableTooltip)) { - tooltipRenderingDetails.showLocation = location; - tooltipRenderingDetails.chartTooltipState!.boundaryRect = - tooltipRenderingDetails.tooltipBounds = - stateProperties.renderingDetails.chartContainerRect; - tooltipRenderingDetails.tooltipTemplate = tooltip.builder!( - chartSeries.dataSource![pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!], - point, - chartSeries, - stateProperties.renderingDetails.currentActive?.seriesIndex ?? 0, - pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!); - if (tooltipRenderingDetails.isHovering == true) { - //assigning values for the previous and current tooltip values on mouse hover - tooltipRenderingDetails.prevTooltipValue = - tooltipRenderingDetails.currentTooltipValue; - tooltipRenderingDetails.currentTooltipValue = TooltipValue( - 0, - pointIndex ?? - stateProperties.renderingDetails.currentActive!.pointIndex!); - } else { - tooltipRenderingDetails.hideTooltipTemplate(); - } - tooltipRenderingDetails.show = true; - tooltipRenderingDetails.performTooltip(); - tooltipRenderingDetails.chartTooltipState! - .hide(hideDelay: tooltip.duration.toInt()); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart deleted file mode 100644 index 2a248d015..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; -import '../../common/rendering_details.dart'; -import '../../common/state_properties.dart'; -import '../../common/user_interaction/tooltip.dart'; -import '../base/pyramid_plot_area.dart'; -import '../renderer/data_label_renderer.dart'; -import 'chart_base.dart'; - -/// Represents the pyramid state properties. -class PyramidStateProperties extends StateProperties { - /// Creates an instance of pyramid state properties. - PyramidStateProperties( - {required this.renderingDetails, required this.chartState}) - : super(renderingDetails, chartState) { - renderingDetails.didSizeChange = false; - } - - /// Specifies the pyramid chart. - @override - SfPyramidChart get chart => chartState.widget; - - /// Specifies the pyramid chart state. - @override - final SfPyramidChartState chartState; - - /// Specifies the rendering details value. - @override - final RenderingDetails renderingDetails; - - /// Specifies the pyramid data label renderer. - PyramidDataLabelRenderer? renderDataLabel; - - /// Specifies the tooltip point index value - int? tooltipPointIndex; - - /// Specifies the series type. - late String seriesType; - - /// Specifies the list of data points - late List> dataPoints; - - /// Specifies the list of render points. - List>? renderPoints; - - /// Specifies the data label rects. - late List labelRects = []; - - /// Specifies the outside rendered label rects - late List outsideRects = []; - - /// Specifies the pyramid chart base. - late PyramidChartBase chartSeries; - - /// Specifies the pyramid plot area. - late PyramidPlotArea chartPlotArea; - - /// Specifies whether the text direction of chart widget is RTL or LTR. - late bool isRtl; - - /// Method called when animation is completed. - bool get animationCompleted { - return renderingDetails.animationController.status != - AnimationStatus.forward; - } - - /// Method to redraw the chart. - void redraw() { - renderingDetails.initialRender = false; - final TooltipRenderingDetails tooltipRenderingDetails = - TooltipHelper.getRenderingDetails( - renderingDetails.tooltipBehaviorRenderer); - if (tooltipRenderingDetails.chartTooltipState != null) { - tooltipRenderingDetails.show = false; - } - - // ignore: invalid_use_of_protected_member - chartState.setState(() { - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); - } - - /// Tooltip timer. - Timer? tooltipTimer; - - /// To check the tooltip orientation changes. - bool isTooltipOrientationChanged = false; - - /// To check if tooltip has been hidden or not. - bool isTooltipHidden = false; -} 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 deleted file mode 100644 index 6c937a7e5..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../common/utils/helper.dart'; -import '../../pyramid_chart/utils/helper.dart'; -import '../base/pyramid_state_properties.dart'; -import 'renderer_extension.dart'; - -/// Represents the data label renderer of the pyramid chart. -// ignore: must_be_immutable -class PyramidDataLabelRenderer extends StatefulWidget { - /// Creates an instance of pyramid data label renderer. - // ignore: prefer_const_constructors_in_immutables - PyramidDataLabelRenderer( - {required Key key, required this.stateProperties, required this.show}) - : super(key: key); - - /// Represents the pyramid chart state. - final PyramidStateProperties stateProperties; - - /// Specifies whether to show the data label. - bool show; - - /// Specifies the state instance of data label renderer. - PyramidDataLabelRendererState? state; - - @override - State createState() { - return PyramidDataLabelRendererState(); - } -} - -/// Represents the state class of data label renderer state. -class PyramidDataLabelRendererState extends State - with SingleTickerProviderStateMixin { - /// Specifies the animation controller list. - late List animationControllersList; - - /// Animation controller for series. - late AnimationController animationController; - - /// Repaint notifier for data label container. - late ValueNotifier dataLabelRepaintNotifier; - - @override - void initState() { - dataLabelRepaintNotifier = ValueNotifier(0); - animationController = AnimationController(vsync: this) - ..addListener(repaintDataLabelElements); - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - animationController.duration = Duration( - milliseconds: - widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); - final Animation dataLabelAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), - )); - animationController.forward(from: 0.0); - return !widget.show - ? Container() - // ignore: avoid_unnecessary_containers - : Container( - child: RepaintBoundary( - child: CustomPaint( - painter: PyramidDataLabelPainter( - stateProperties: widget.stateProperties, - animation: dataLabelAnimation, - state: this, - notifier: dataLabelRepaintNotifier, - animationController: animationController)))); - } - - @override - void dispose() { - disposeAnimationController(animationController, repaintDataLabelElements); - super.dispose(); - } - - /// Method to repaint the data label element. - void repaintDataLabelElements() { - dataLabelRepaintNotifier.value++; - } - - /// Method to render the widget. - void render() { - setState(() { - widget.show = true; - }); - } -} - -/// Represents the pyramid data label painter. -class PyramidDataLabelPainter extends CustomPainter { - /// Creates an instance of pyramid data label painter. - PyramidDataLabelPainter( - {required this.stateProperties, - required this.state, - required this.animationController, - required this.animation, - required ValueNotifier notifier}) - : super(repaint: notifier); - - /// Represents the pyramid state properties. - final PyramidStateProperties stateProperties; - - /// Represents the data label renderer state. - final PyramidDataLabelRendererState state; - - /// Represents the animation controller. - final AnimationController animationController; - - /// Specifies the series animation. - final Animation animation; - - @override - void paint(Canvas canvas, Size size) { - final PyramidSeriesRendererExtension seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[0]; - // ignore: unnecessary_null_comparison - if (seriesRenderer.series.dataLabelSettings != null && - seriesRenderer.series.dataLabelSettings.isVisible == true) { - seriesRenderer.dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer.series.dataLabelSettings); - stateProperties.outsideRects.clear(); - renderPyramidDataLabel( - seriesRenderer, canvas, stateProperties, animation); - } - } - - @override - bool shouldRepaint(PyramidDataLabelPainter oldDelegate) => true; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart deleted file mode 100644 index 4dd357d9f..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import '../base/pyramid_state_properties.dart'; -import '../utils/common.dart'; -import 'renderer_extension.dart'; - -/// Represents the pyramid chart painter. -class PyramidChartPainter extends CustomPainter { - /// Creates an instance of pyramid chart painter. - PyramidChartPainter({ - required this.stateProperties, - required this.seriesIndex, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : super(repaint: notifier); - - /// Specifies the pyramid state properties. - final PyramidStateProperties stateProperties; - - /// Specifies the series index value. - final int seriesIndex; - - /// Specifies whether to repaint the series. - final bool isRepaint; - - /// Specifies the animation controller of series. - final AnimationController? animationController; - - /// Specifies the pyramid series animation. - final Animation? seriesAnimation; - - /// Specifies the pyramid series renderer. - late PyramidSeriesRendererExtension seriesRenderer; - - /// Specifies the point info. - static late PointInfo point; - - @override - void paint(Canvas canvas, Size size) { - seriesRenderer = - stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; - - double animationFactor, factor, height; - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints!.length; - pointIndex++) { - if (seriesRenderer.renderPoints![pointIndex].isVisible) { - animationFactor = seriesAnimation != null ? seriesAnimation!.value : 1; - if (seriesRenderer.series.animationDuration > 0 && - !stateProperties.renderingDetails.isLegendToggled) { - factor = (stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height) - - animationFactor * - (stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height); - height = stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height - - factor; - canvas.clipRect(Rect.fromLTRB( - 0, - stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height - - height, - stateProperties.renderingDetails.chartAreaRect.left + - stateProperties.renderingDetails.chartAreaRect.width, - stateProperties.renderingDetails.chartAreaRect.top + - stateProperties.renderingDetails.chartAreaRect.height)); - } - stateProperties.chartSeries - .calculatePyramidSegments(canvas, pointIndex, seriesRenderer); - } - } - } - - @override - bool shouldRepaint(PyramidChartPainter oldDelegate) => true; -} 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 deleted file mode 100644 index 633ece391..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart +++ /dev/null @@ -1,184 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import 'renderer_extension.dart'; -import 'series_base.dart'; -import 'series_controller.dart'; - -/// Renders the pyramid series. -/// -/// To render a pyramid chart, create an instance of [PyramidSeries], and add it to the series property of [SfPyramidChart], -/// it is the form of a triangle with lines dividing it into sections. -/// -/// Provides the property of color, opacity, border color and border width for customizing the appearance. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} -/// -@immutable -// ignore: must_be_immutable -class PyramidSeries extends PyramidSeriesBase { - /// Creating an argument constructor of PyramidSeries class. - PyramidSeries({ - ValueKey? key, - ChartSeriesRendererFactory? onCreateRenderer, - PyramidSeriesRendererCreatedCallback? onRendererCreated, - ChartPointInteractionCallback? onPointTap, - ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress, - 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? animationDelay, - double? opacity, - Color? borderColor, - double? borderWidth, - bool? explode, - num? explodeIndex, - ActivationMode? explodeGesture, - String? explodeOffset, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - }) : super( - key: key, - onCreateRenderer: onCreateRenderer, - onRendererCreated: onRendererCreated, - onPointTap: onPointTap, - onPointDoubleTap: onPointDoubleTap, - onPointLongPress: onPointLongPress, - dataSource: dataSource, - xValueMapper: (int index) => xValueMapper!(dataSource![index], index), - yValueMapper: (int index) => yValueMapper!(dataSource![index], index), - pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource![index], index) - : null, - textFieldMapper: (int index) => textFieldMapper != null - ? textFieldMapper(dataSource![index], index) - : null, - name: name, - height: height, - width: width, - pyramidMode: pyramidMode, - gapRatio: gapRatio, - emptyPointSettings: emptyPointSettings, - dataLabelSettings: dataLabelSettings, - legendIconType: legendIconType, - opacity: opacity, - borderColor: borderColor, - borderWidth: borderWidth, - animationDuration: animationDuration, - animationDelay: animationDelay, - explode: explode, - explodeIndex: explodeIndex, - explodeOffset: explodeOffset, - explodeGesture: explodeGesture, - selectionBehavior: selectionBehavior, - initialSelectedDataIndexes: initialSelectedDataIndexes, - ); - - /// Create the pyramid series renderer. - PyramidSeriesRenderer createRenderer(PyramidSeries series) { - PyramidSeriesRenderer seriesRenderer; - if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer!(series) as PyramidSeriesRenderer; - // ignore: unnecessary_null_comparison - 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; - } - return PyramidSeriesRendererExtension(); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is PyramidSeries && - other.onCreateRenderer == onCreateRenderer && - other.onRendererCreated == onRendererCreated && - other.onPointTap == onPointTap && - other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress && - other.dataSource == dataSource && - other.xValueMapper == xValueMapper && - other.yValueMapper == yValueMapper && - other.pointColorMapper == pointColorMapper && - other.textFieldMapper == textFieldMapper && - other.name == name && - other.height == height && - other.width == width && - other.pyramidMode == pyramidMode && - other.gapRatio == gapRatio && - other.legendIconType == legendIconType && - other.emptyPointSettings == emptyPointSettings && - other.dataLabelSettings == dataLabelSettings && - other.animationDuration == animationDuration && - other.animationDelay == animationDelay && - other.opacity == opacity && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.explode == explode && - other.explodeIndex == explodeIndex && - other.explodeGesture == explodeGesture && - other.explodeOffset == explodeOffset && - other.selectionBehavior == selectionBehavior && - listEquals( - other.initialSelectedDataIndexes, initialSelectedDataIndexes); - } - - @override - int get hashCode { - final List values = [ - onCreateRenderer, - onRendererCreated, - onPointTap, - onPointDoubleTap, - onPointLongPress, - dataSource, - xValueMapper, - yValueMapper, - pointColorMapper, - textFieldMapper, - name, - height, - width, - pyramidMode, - gapRatio, - legendIconType, - emptyPointSettings, - dataLabelSettings, - animationDuration, - animationDelay, - opacity, - borderColor, - borderWidth, - explode, - explodeIndex, - explodeGesture, - explodeOffset, - selectionBehavior, - initialSelectedDataIndexes - ]; - return hashList(values); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart deleted file mode 100644 index 0f5d1561b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../base/pyramid_state_properties.dart'; -import '../utils/common.dart'; -import 'pyramid_series.dart'; -import 'series_controller.dart'; - -/// Creates series renderer for Pyramid series. -class PyramidSeriesRendererExtension extends PyramidSeriesRenderer { - /// Calling the default constructor of PyramidSeriesRenderer class. - PyramidSeriesRendererExtension() { - seriesType = 'pyramid'; - } - - /// Specifies the pyramid series. - late PyramidSeries series; - - /// Specifies the series type. - late String seriesType; - - /// Represents the list of data points. - late List> dataPoints; - - /// Represents the list of render points. - List>? renderPoints; - - /// Gets or sets the value of the sum of points. - late num sumOfPoints; - - /// Specifies the triangle size value. - late Size triangleSize; - - /// Specifies the explode distance value. - late num explodeDistance; - - /// Specifies the maximum data label region. - late Rect maximumDataLabelRegion; - - /// Specifies the value of series controller. - PyramidSeriesController? controller; - - /// Specifies the state properties. - late PyramidStateProperties stateProperties; - - /// Specifies the repaint notifier of the series. - late ValueNotifier repaintNotifier; - - /// Represents the data label setting renderer. - late DataLabelSettingsRenderer dataLabelSettingsRenderer; - - /// Represents the selection behavior renderer. - late SelectionBehaviorRenderer selectionBehaviorRenderer; - - /// Specifies the selection behavior. - late SelectionBehavior selectionBehavior; - - /// Specifies whether the selection is enabled. - // ignore: prefer_final_fields - bool isSelectionEnable = false; - - /// Specifies whether to repaint the chart. - bool needsRepaint = true; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart deleted file mode 100644 index cdb40c87e..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart +++ /dev/null @@ -1,716 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../common/common.dart'; -import '../../common/series/chart_series.dart'; -import '../../common/user_interaction/selection_behavior.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/typedef.dart'; -import '../utils/common.dart'; - -/// Represents the class of pyramid series base. -class PyramidSeriesBase extends ChartSeries - implements TriangularChartEmptyPointBehavior { - /// Creates an instance of pyramid series base. - PyramidSeriesBase({ - this.key, - this.onCreateRenderer, - this.onRendererCreated, - this.onPointTap, - this.onPointDoubleTap, - this.onPointLongPress, - this.dataSource, - this.xValueMapper, - this.yValueMapper, - this.pointColorMapper, - 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? animationDelay, - double? opacity, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - }) : height = height ?? '80%', - width = width ?? '80%', - pyramidMode = pyramidMode ?? PyramidMode.linear, - gapRatio = gapRatio ?? 0, - emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), - explodeOffset = explodeOffset ?? '10%', - explode = explode ?? false, - explodeGesture = explodeGesture ?? ActivationMode.singleTap, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - legendIconType = legendIconType ?? LegendIconType.seriesType, - dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), - animationDuration = animationDuration ?? 1500, - animationDelay = animationDelay ?? 0, - opacity = opacity ?? 1, - initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - selectionBehavior = selectionBehavior ?? SelectionBehavior(), - super( - name: name, - dataSource: dataSource, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper) { - // _renderer = _PyramidSeriesRender(); - } - - /// A collection of data required for rendering the series. If no data source is specified, - /// empty chart will be rendered without series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// ) - /// ], - /// ) - /// ); - ///} - @override - final List? dataSource; - - /// Maps the field name, which will be considered as x-values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// ) - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? xValueMapper; - - /// Maps the field name, which will be considered as y-values. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// yValueMapper: (ChartData data, _) => data.yVal, - /// ) - /// ], - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? yValueMapper; - - /// Name of the series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// name: 'Pyramid' - /// ) - /// ); - ///} - ///``` - @override - final String? name; - - /// Height of the series. - /// - /// Defaults to `80%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// height:'50%' - /// ) - /// ); - ///} - ///``` - final String height; - - /// Width of the series. - /// - /// Defaults to `80%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// width:'50%' - /// ) - /// ); - ///} - ///``` - final String width; - - /// Specifies the rendering type of pyramid. - /// - /// Defaults to `PyramidMode.linear`. - /// - /// Also refer [PyramidMode]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// pyramidMode:PyramidMode.surface - /// ) - /// ); - ///} - ///``` - final PyramidMode pyramidMode; - - /// Gap ratio between the segments of pyramid. Ranges from 0 to 1. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// gapRatio: 0.3 - /// ) - /// ); - ///} - ///``` - final double gapRatio; - - /// Customizes the empty data points in the series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// emptyPointSettings: EmptyPointSettings (color: Colors.red)) - /// ) - /// ); - ///} - ///``` - @override - final EmptyPointSettings emptyPointSettings; - - /// Offset of exploded slice. The value ranges from 0% to 100%. - /// - /// Defaults to `10%`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explodeOffset: '5%') - /// ) - /// ); - ///} - ///``` - final String explodeOffset; - - /// Enables or disables the explode of slices on tap. - /// - /// Default to `false`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explode: true) - /// ) - /// ); - ///} - ///``` - final bool explode; - - /// Gesture for activating the explode. - /// - /// Explode can be activated in `ActivationMode.none`, `ActivationMode.singleTap` , - /// `ActivationMode.doubleTap` and `ActivationMode.longPress`. - /// - /// Defaults to `ActivationMode.singleTap`. - /// - /// Also refer [ActivationMode]. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explode: true, - /// explodeGesture: ActivationMode.singleTap - /// ) - /// ) - /// ); - ///} - ///``` - final ActivationMode explodeGesture; - - /// Border width of the data points in the series. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// borderWidth: 2 - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double borderWidth; - - /// Border color of the data points in the series. - /// - /// Defaults to `Colors.transparent`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// borderColor: Colors.red - /// ) - /// ) - /// ); - ///} - ///``` - @override - final Color borderColor; - - /// Shape of the legend icon. - /// - /// Defaults to `LegendIconType.seriesType`. - /// - /// Also refer [LegendIconType]. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// legendIconType: LegendIconType.diamond, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final LegendIconType legendIconType; - - /// Customizes the data labels in a series. Data label is a text, which displays - /// the details about the data point. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ) - /// ) - /// ); - ///} - ///``` - @override - final DataLabelSettings dataLabelSettings; - - /// Duration for animating the data points. - /// - /// Defaults to `1500`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// animationDuration: 2000, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double animationDuration; - - /// Delay duration of the series animation.It takes a millisecond value as input. - /// By default, the series will get animated for the specified duration. - /// If animationDelay is specified, then the series will begin to animate - /// after the specified duration. - /// - /// Defaults to `0`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// animationDelay: 2000, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double animationDelay; - - /// Maps the field name, which will be considered as data point color. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// pointColorMapper: (ChartData data, _) => data.pointColor, - /// ) - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - @override - final ChartIndexedValueMapper? pointColorMapper; - - /// Maps the field name, which will be considered as a text. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// xValueMapper: (ChartData data, _) => data.xVal, - /// yValueMapper: (ChartData data, _) => data.yVal, - /// textFieldMapper: (ChartData data, _) => data.xVal, - /// ) - /// ) - /// ); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - final ChartIndexedValueMapper? textFieldMapper; - - /// Opacity of the series. The value ranges from 0 to 1. - /// - /// Defaults to `1`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// opacity: 0.5, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final double opacity; - - /// Customizes the selection of series. - /// - ///```dart - ///SelectionBehavior _selectionBehavior; - /// - ///void initState() { - /// _selectionBehavior = SelectionBehavior(enable: true); - /// super.initState(); - ///} - /// - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// selectionBehavior: _selectionBehavior, - /// ) - /// ) - /// ); - ///} - ///``` - @override - final SelectionBehavior selectionBehavior; - - /// Index of the slice to explode it at the initial rendering. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explodeIndex: 1, - /// explode: true - /// ) - /// ) - /// ); - ///} - ///``` - final num? explodeIndex; - - /// List of data indexes initially selected. - /// - /// Defaults to `null`. - ///```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// child: SfPyramidChart( - /// initialSelectedDataIndexes: [IndexesModel(1, 0)] - /// ) - /// ) - /// ) - /// ); - /// } - List initialSelectedDataIndexes; - - /// Key to identify a series in a collection. - /// - /// On specifying [ValueKey] as the series [key], existing series index - /// can be changed in the series collection without losing its state. - /// - /// When a new series is added dynamically to the collection, - /// existing series index will be changed. On that case, - /// the existing series and its state will be linked based on its chart type and this key value. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// key: const ValueKey('line_series_key'), - /// ), - /// ) - /// ); - ///} - ///``` - final ValueKey? key; - - /// Used to create the renderer for custom series. - /// - /// This is applicable only when the custom series is defined in the sample - /// and for built-in series types, it is not applicable. - /// - /// Renderer created in this will hold the series state and - /// this should be created for each series. [onCreateRenderer] callback - /// function should return the renderer class and should not return null. - /// - /// Series state will be created only once per series and will not be created - /// again when we update the series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// onCreateRenderer:(PyramidSeries series){ - /// return CustomPyramidSeriesRenderer(); - /// } - /// ), - /// ) - /// ); - /// } - /// class CustomPyramidSeriesRenderer extends PyramidSeriesRenderer { - /// // custom implementation here... - /// } - ///``` - final ChartSeriesRendererFactory? onCreateRenderer; - - /// Triggers when the series renderer is created. - /// Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// onRendererCreated: (PyramidSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ], - /// ) - /// ); - ///} - ///``` - final PyramidSeriesRendererCreatedCallback? onRendererCreated; - - /// Called when tapped on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointTap; - - /// Called when double tapped on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the double-tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointDoubleTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointDoubleTap; - - /// Called when long pressed on the chart data point. - /// - /// The user can fetch the series index, point index, view port index and - /// data of the long-pressed data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointLongPress: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// ) - /// ); - ///} - ///``` - final ChartPointInteractionCallback? onPointLongPress; - - @override - void calculateEmptyPointValue( - int pointIndex, dynamic currentPoint, dynamic seriesRenderer) { - final List> dataPoints = seriesRenderer.dataPoints; - final EmptyPointSettings empty = emptyPointSettings; - final int pointLength = dataPoints.length; - final PointInfo point = dataPoints[pointIndex]; - if (point.y == null) { - switch (empty.mode) { - case EmptyPointMode.average: - final num previous = - pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; - final num next = pointIndex + 1 <= pointLength - 1 - ? dataPoints[pointIndex + 1].y ?? 0 - : 0; - point.y = (previous + next).abs() / 2; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.zero: - point.y = 0; - point.isVisible = true; - point.isEmpty = true; - break; - default: - point.isEmpty = true; - point.isVisible = false; - break; - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart deleted file mode 100644 index fb844d2d3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart +++ /dev/null @@ -1,245 +0,0 @@ -import 'dart:ui'; -import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; -import '../../chart/chart_series/series.dart'; -import '../../common/utils/typedef.dart'; -import '../base/pyramid_state_properties.dart'; -import '../utils/common.dart'; -import 'pyramid_series.dart'; -import 'renderer_extension.dart'; - -/// 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. -class PyramidSeriesController { - /// Creating an argument constructor of PyramidSeriesController class. - PyramidSeriesController(this.seriesRenderer); - - /// Used to access the series properties. - /// - /// Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// onRendererCreated: (PyramidSeriesController controller) { - /// _chartSeriesController = controller; - /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); - /// }, - /// ), - /// ) - /// ); - ///} - ///``` - final PyramidSeriesRenderer seriesRenderer; - - /// Used to process only the newly added, updated and removed data points in a series, - /// instead of processing all the data points. - /// - /// To re-render the chart with modified data points, setState() will be called. - /// This will render the process and render the chart from scratch. - /// Thus, the app’s performance will be degraded on continuous update. - /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. - /// Chart will process only that point and skip various steps like bounds calculation, - /// old data points processing, etc. Thus, this will improve the app’s performance. - /// - /// The following are the arguments of this method. - /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. - /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. - /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. - /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. - /// * removedDataIndex – `int` type – Index of removed data point in the existing series. - /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. - /// - /// Returns `void`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// PyramidSeriesController _pyramidSeriesController; - /// return Column( - /// children: [ - /// Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: chartData, - /// onRendererCreated: (PyramidSeriesController controller) { - /// _pyramidSeriesController = controller; - /// }, - /// ), - /// ) - /// ), - /// Container( - /// child: RaisedButton( - /// onPressed: () { - /// chartData.removeAt(0); - /// chartData.add(ChartData(3,23)); - /// _pyramidSeriesController.updateDataSource( - /// addedDataIndexes: [chartData.length -1], - /// removedDataIndexes: [0], - /// ); - /// }) - /// )] - /// ); - /// } - ///``` - void updateDataSource( - {List? addedDataIndexes, - List? removedDataIndexes, - List? updatedDataIndexes, - int? addedDataIndex, - int? removedDataIndex, - int? updatedDataIndex}) { - if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { - _removeDataPointsList(removedDataIndexes); - } else if (removedDataIndex != null) { - _removeDataPoint(removedDataIndex); - } - if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(addedDataIndexes, false); - } else if (addedDataIndex != null) { - _addOrUpdateDataPoint(addedDataIndex, false); - } - if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(updatedDataIndexes, true); - } else if (updatedDataIndex != null) { - _addOrUpdateDataPoint(updatedDataIndex, true); - } - _updatePyramidSeries(); - } - - /// Add or update the data points on dynamic series update. - void _addOrUpdateDataPoints(List indexes, bool needUpdate) { - int dataIndex; - for (int i = 0; i < indexes.length; i++) { - dataIndex = indexes[i]; - _addOrUpdateDataPoint(dataIndex, needUpdate); - } - } - - /// Add or update a data point in the given index. - void _addOrUpdateDataPoint(int index, bool needUpdate) { - final PyramidSeriesRendererExtension renderer = - seriesRenderer as PyramidSeriesRendererExtension; - final PyramidSeries series = renderer.series; - if (index >= 0 && - 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)); - if (_currentPoint.x != null) { - if (needUpdate) { - if (renderer.dataPoints.length > index) { - renderer.dataPoints[index] = _currentPoint; - } - } else { - if (renderer.dataPoints.length == index) { - renderer.dataPoints.add(_currentPoint); - } else if (renderer.dataPoints.length > index && index >= 0) { - renderer.dataPoints.insert(index, _currentPoint); - } - } - } - } - } - - /// Remove list of points. - void _removeDataPointsList(List removedDataIndexes) { - //Remove the redundant index from the list - final List indexList = removedDataIndexes.toSet().toList(); - indexList.sort((int b, int a) => a.compareTo(b)); - int dataIndex; - for (int i = 0; i < indexList.length; i++) { - dataIndex = indexList[i]; - _removeDataPoint(dataIndex); - } - } - - /// Remove a data point in the given index. - void _removeDataPoint(int index) { - final PyramidSeriesRendererExtension renderer = - seriesRenderer as PyramidSeriesRendererExtension; - if (renderer.dataPoints.isNotEmpty && - index >= 0 && - index < renderer.dataPoints.length) { - renderer.dataPoints.removeAt(index); - } - } - - /// After add/remove/update data points, recalculate the chart angle and positions. - void _updatePyramidSeries() { - final PyramidSeriesRendererExtension renderer = - seriesRenderer as PyramidSeriesRendererExtension; - final PyramidStateProperties stateProperties = renderer.stateProperties; - stateProperties.chartSeries.processDataPoints(); - stateProperties.chartSeries.initializeSeriesProperties(renderer); - renderer.repaintNotifier.value++; - if (renderer.series.dataLabelSettings.isVisible && - stateProperties.renderDataLabel != null) { - stateProperties.renderDataLabel!.state?.render(); - } - if (renderer.series.dataLabelSettings.isVisible && - stateProperties.renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - stateProperties.renderingDetails.chartTemplate!.state != null) { - stateProperties.renderingDetails.chartTemplate!.state.templateRender(); - } - } - - /// Converts chart data point value to logical pixel value. - /// - /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. - /// - /// _Note_: It returns the data point's center location value. - /// - ///```dart - /// late PyramidSeriesController pyramidSeriesController; - /// SfPyramidChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { - /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); - /// Offset value = seriesController.pointToPixel(chartPoint); - /// PointInfo chartPoint1 = seriesController.pixelToPoint(value); - /// }, - /// series: PyramidSeries( - /// onRendererCreated: (PyramidSeriesController seriesController) { - /// pyramidSeriesController = seriesController; - /// } - /// ), - /// ); - /// ``` - // ignore: unused_element - Offset _pointToPixel(PointInfo point) { - return pyramidFunnelPointToPixel(point, seriesRenderer); - } - - /// Converts logical pixel value to the data point value. - /// - /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. - /// - ///```dart - /// late PyramidSeriesController pyramidSeriesController; - /// SfPyramidChart( - /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { - /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); - /// Offset value = seriesController.pointToPixel(chartPoint); - /// }, - /// series: PyramidSeries( - /// onRendererCreated: (PyramidSeriesController seriesController) { - /// pyramidSeriesController = seriesController; - /// } - /// ), - /// ); - /// ``` - PointInfo pixelToPoint(Offset position) { - return pyramidFunnelPixelToPoint(position, seriesRenderer); - } -} - -/// Creates series renderer for Pyramid series. -class PyramidSeriesRenderer extends ChartSeriesRenderer { - /// Calling the default constructor of PyramidSeriesRenderer class. - PyramidSeriesRenderer(); -} 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 deleted file mode 100644 index cb66b060b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import '../../chart/utils/enum.dart'; -import '../../circular_chart/utils/enum.dart'; - -/// This is similar to the point of the Cartesian chart. -class PointInfo { - /// Creating an argument constructor of PointInfo class. - PointInfo(this.x, this.y); - - /// X value of point info. - dynamic x; - - /// Y value of point info. - num? y; - - /// Text value of point info. - String? text; - - /// Fill color of point info. - late Color fill; - - /// Stores the point info color. - late Color color; - - /// Stores the border color of point info. - late Color borderColor; - - /// Stores the sort value. - D? sortValue; - - /// Stores the border width value. - late num borderWidth; - - /// To set the property of explode. - bool isExplode = false; - - /// To set the property of shadow. - bool isShadow = false; - - /// To set the property of empty. - bool isEmpty = false; - - /// To set the property of visible. - bool isVisible = true; - - /// To set the property of selection. - bool isSelected = false; - - /// Stores the value of label position. - Position? dataLabelPosition; - - /// Stores the value of chart data label position. - ChartDataLabelPosition? renderPosition; - - /// Stores the value of label rect. - Rect? labelRect; - - /// To check if labels collide. - bool _isLabelCollide = false; - - /// Stores the value data label size. - Size dataLabelSize = Size.zero; - - /// To set the saturation region. - bool saturationRegionOutside = false; - - /// Stores the value of Y ratio. - late num yRatio; - - /// Stores the value of height ratio. - late num heightRatio; - - /// Stores the list value of path region. - late List pathRegion; - - /// Stores the value of region. - Rect? region; - - /// Stores the offset value of symbol location. - late Offset symbolLocation; - - /// Stores the value of explode Distance. - num? explodeDistance; - - /// To execute onTooltipRender event or not. - // ignore: prefer_final_fields - bool isTooltipRenderEvent = false; - - /// To execute OnDataLabelRender event or not. - // ignore: prefer_final_fields - bool labelRenderEvent = false; -} - -// ignore: avoid_classes_with_only_static_members -/// Helper class handling PointInfo class private fields. -class PointInfoHelper { - /// Returns the value of isLabelCollide flag for the given point. - static bool getIsLabelCollide(PointInfo point) => - point._isLabelCollide; - - /// Sets the value of isLabelCollide flag for the given point. - static void setIsLabelCollide(PointInfo point, bool isLabelCollide) { - point._isLabelCollide = isLabelCollide; - } -} 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 deleted file mode 100644 index c1d953bb1..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart +++ /dev/null @@ -1,848 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import '../../chart/common/data_label.dart'; -import '../../chart/utils/enum.dart'; -import '../../chart/utils/helper.dart'; -import '../../circular_chart/renderer/circular_chart_annotation.dart'; -import '../../circular_chart/renderer/data_label_renderer.dart'; -import '../../circular_chart/utils/enum.dart'; -import '../../circular_chart/utils/helper.dart'; -import '../../common/event_args.dart'; -import '../../common/state_properties.dart'; -import '../../common/template/rendering.dart'; -import '../../common/utils/enum.dart'; -import '../../common/utils/helper.dart'; -import '../base/pyramid_base.dart'; -import '../base/pyramid_state_properties.dart'; -import '../renderer/renderer_extension.dart'; -import '../utils/common.dart'; - -/// Method for checking if point is within polygon. -bool isPointInPolygon(List polygon, Offset point) { - bool p = false; - 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)) && - (point.dx < - (polygon[j].dx - polygon[i].dx) * - (point.dy - polygon[i].dy) / - (polygon[j].dy - polygon[i].dy) + - polygon[i].dx) && - // ignore: unnecessary_statements - (p = !p); - } - return p; -} - -/// To add chart templates. -void findTemplates(dynamic _stateProperties) { - Offset labelLocation; - const num lineLength = 10; - PointInfo point; - Widget labelWidget; - _stateProperties.renderingDetails.dataLabelTemplateRegions = []; - _stateProperties.renderingDetails.templates = []; - dynamic series; - dynamic seriesRenderer; - ChartAlignment labelAlign; - for (int k = 0; - k < _stateProperties.chartSeries.visibleSeriesRenderers.length; - k++) { - seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[k]; - series = seriesRenderer.series; - if (series.dataLabelSettings.isVisible == true && - series.dataLabelSettings.builder != null) { - for (int i = 0; i < seriesRenderer.renderPoints.length; i++) { - point = seriesRenderer.renderPoints[i]; - if (point.isVisible) { - labelWidget = series.dataLabelSettings - .builder(series.dataSource[i], point, series, i, k); - if (series.dataLabelSettings.labelPosition == - ChartDataLabelPosition.inside) { - labelLocation = point.symbolLocation; - labelAlign = ChartAlignment.center; - } else { - labelLocation = point.symbolLocation; - labelLocation = Offset( - point.dataLabelPosition == Position.right - ? labelLocation.dx + lineLength + 5 - : labelLocation.dx - lineLength - 5, - labelLocation.dy); - labelAlign = point.dataLabelPosition == Position.left - ? ChartAlignment.far - : ChartAlignment.near; - } - _stateProperties.renderingDetails.templates.add(ChartTemplateInfo( - key: GlobalKey(), - templateType: 'DataLabel', - pointIndex: i, - seriesIndex: k, - needMeasure: true, - clipRect: _stateProperties.renderingDetails.chartAreaRect, - animationDuration: 500, - widget: labelWidget, - horizontalAlignment: labelAlign, - verticalAlignment: ChartAlignment.center, - location: labelLocation)); - } - } - } - } -} - -/// To render a template. -void renderTemplates(StateProperties stateProperties) { - if (stateProperties.renderingDetails.templates.isNotEmpty == true) { - ChartTemplateInfo chartTemplateInfo; - for (int i = 0; - i < stateProperties.renderingDetails.templates.length; - i++) { - chartTemplateInfo = stateProperties.renderingDetails.templates[i]; - chartTemplateInfo.animationDuration = - stateProperties.renderingDetails.initialRender == false - ? 0 - : chartTemplateInfo.animationDuration; - } - stateProperties.renderingDetails.chartTemplate = ChartTemplate( - templates: stateProperties.renderingDetails.templates, - render: stateProperties.renderingDetails.animateCompleted, - stateProperties: stateProperties); - stateProperties.renderingDetails.chartWidgets! - .add(stateProperties.renderingDetails.chartTemplate!); - } -} - -/// To get pyramid series data label saturation color. -Color getPyramidFunnelColor(PointInfo currentPoint, - dynamic seriesRenderer, dynamic _stateProperties) { - Color color; - final dynamic series = seriesRenderer.series; - final DataLabelSettings dataLabel = series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - color = (currentPoint.renderPosition == null || - currentPoint.renderPosition == ChartDataLabelPosition.inside && - !currentPoint.saturationRegionOutside) - ? innerColor(dataLabelSettingsRenderer.color, currentPoint.fill, - _stateProperties.renderingDetails.chartTheme) - : outerColor( - dataLabelSettingsRenderer.color, - dataLabel.useSeriesColor - ? currentPoint.fill - : (_stateProperties.chart.backgroundColor != null - ? _stateProperties - .renderingDetails.chartTheme.plotAreaBackgroundColor - : null), - _stateProperties.renderingDetails.chartTheme); - - return getSaturationColor(color); -} - -/// To get inner data label color. -Color innerColor( - Color? dataLabelColor, Color? pointColor, SfChartThemeData theme) => - dataLabelColor ?? pointColor ?? Colors.black; - -/// To get outer data label color. -Color outerColor(Color? dataLabelColor, Color? backgroundColor, - SfChartThemeData theme) => - // ignore: prefer_if_null_operators - dataLabelColor != null - ? dataLabelColor - // ignore: prefer_if_null_operators - : backgroundColor != null - ? backgroundColor - : theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; - -/// To get outer data label text style. -TextStyle getDataLabelTextStyle( - dynamic seriesRenderer, PointInfo point, dynamic stateProperties, - [double? animateOpacity]) { - final dynamic series = seriesRenderer.series; - final DataLabelSettings dataLabel = series.dataLabelSettings; - final Color fontColor = dataLabel.textStyle.color ?? - getPyramidFunnelColor(point, seriesRenderer, stateProperties); - final TextStyle textStyle = TextStyle( - color: fontColor.withOpacity(animateOpacity ?? 1), - fontSize: dataLabel.textStyle.fontSize, - fontFamily: dataLabel.textStyle.fontFamily, - fontStyle: dataLabel.textStyle.fontStyle, - fontWeight: dataLabel.textStyle.fontWeight, - inherit: dataLabel.textStyle.inherit, - backgroundColor: dataLabel.textStyle.backgroundColor, - letterSpacing: dataLabel.textStyle.letterSpacing, - wordSpacing: dataLabel.textStyle.wordSpacing, - textBaseline: dataLabel.textStyle.textBaseline, - height: dataLabel.textStyle.height, - locale: dataLabel.textStyle.locale, - foreground: dataLabel.textStyle.foreground, - background: dataLabel.textStyle.background, - shadows: dataLabel.textStyle.shadows, - fontFeatures: dataLabel.textStyle.fontFeatures, - decoration: dataLabel.textStyle.decoration, - decorationColor: dataLabel.textStyle.decorationColor, - decorationStyle: dataLabel.textStyle.decorationStyle, - decorationThickness: dataLabel.textStyle.decorationThickness, - debugLabel: dataLabel.textStyle.debugLabel, - fontFamilyFallback: dataLabel.textStyle.fontFamilyFallback); - return textStyle; -} - -/// To check the point explosion. -bool isNeedExplode(int pointIndex, dynamic series, dynamic stateProperties) { - bool isNeedExplode = false; - if (series.explode == true) { - if (stateProperties.renderingDetails.initialRender == true) { - if (pointIndex == series.explodeIndex) { - stateProperties.renderingDetails.explodedPoints.add(pointIndex); - isNeedExplode = true; - } - } else if (stateProperties.renderingDetails.widgetNeedUpdate == true || - stateProperties.renderingDetails.isLegendToggled == true) { - isNeedExplode = - stateProperties.renderingDetails.explodedPoints.contains(pointIndex); - } - } - return isNeedExplode; -} - -/// To return data label rect calculation method based on position. -Rect? getDataLabelRect(Position position, ConnectorType connectorType, - EdgeInsets margin, Path connectorPath, Offset endPoint, Size textSize) { - Rect? rect; - const int lineLength = 10; - switch (position) { - case Position.right: - connectorType == ConnectorType.line - ? connectorPath.lineTo(endPoint.dx + lineLength, endPoint.dy) - : connectorPath.quadraticBezierTo( - endPoint.dx, endPoint.dy, endPoint.dx + lineLength, endPoint.dy); - rect = Rect.fromLTWH( - endPoint.dx + lineLength, - endPoint.dy - (textSize.height / 2) - margin.top, - textSize.width + margin.left + margin.right, - textSize.height + margin.top + margin.bottom); - break; - case Position.left: - connectorType == ConnectorType.line - ? connectorPath.lineTo(endPoint.dx - lineLength, endPoint.dy) - : connectorPath.quadraticBezierTo( - endPoint.dx, endPoint.dy, endPoint.dx - lineLength, endPoint.dy); - rect = Rect.fromLTWH( - endPoint.dx - - lineLength - - margin.right - - textSize.width - - margin.left, - endPoint.dy - (textSize.height / 2) - margin.top, - textSize.width + margin.left + margin.right, - textSize.height + margin.top + margin.bottom); - break; - default: - rect = null; - break; - } - return rect; -} - -/// To render pyramid data labels. -void renderPyramidDataLabel( - PyramidSeriesRendererExtension seriesRenderer, - Canvas canvas, - PyramidStateProperties stateProperties, - Animation animation) { - PointInfo point; - PointInfo nextPoint; - final SfPyramidChart chart = stateProperties.chart; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - String? label; - // ignore: unnecessary_null_comparison - final double animateOpacity = animation != null ? animation.value : 1; - DataLabelRenderArgs dataLabelArgs; - TextStyle dataLabelStyle; - final List renderDataLabelRegions = []; - Size textSize; - stateProperties.outsideRects.clear(); - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints!.length; - 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) { - dataLabelArgs = DataLabelRenderArgs(seriesRenderer, - seriesRenderer.renderPoints, pointIndex, pointIndex); - dataLabelArgs.text = label!; - dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer.color; - chart.onDataLabelRender!(dataLabelArgs); - label = point.text = dataLabelArgs.text; - dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex!; - dataLabelSettingsRenderer.color = dataLabelArgs.color; - seriesRenderer.dataPoints[pointIndex].labelRenderEvent = true; - } - dataLabelStyle = chart.onDataLabelRender == null - ? getDataLabelTextStyle( - seriesRenderer, point, stateProperties, animateOpacity) - : dataLabelStyle; - textSize = measureText(label!, dataLabelStyle); - - // Label check after event - if (label != '') { - if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - stateProperties.labelRects.clear(); - for (int index = 0; - index < seriesRenderer.renderPoints!.length; - index++) { - nextPoint = seriesRenderer.renderPoints![index]; - final num angle = dataLabel.angle; - Offset labelLocation; - const int labelPadding = 2; - final Size nextDataLabelSize = - measureText(nextPoint.text!, dataLabelStyle); - if (nextPoint.isVisible) { - labelLocation = nextPoint.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (nextDataLabelSize.width / 2) + - (angle == 0 ? 0 : nextDataLabelSize.width / 2), - labelLocation.dy - - (nextDataLabelSize.height / 2) + - (angle == 0 ? 0 : nextDataLabelSize.height / 2)); - stateProperties.labelRects.add(Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - nextDataLabelSize.width + (2 * labelPadding), - nextDataLabelSize.height + (2 * labelPadding))); - } else { - stateProperties.labelRects.add(Rect.zero); - } - } - PointInfoHelper.setIsLabelCollide( - point, checkCollide(pointIndex, stateProperties.labelRects)); - _setPyramidInsideLabelPosition( - dataLabel, - point, - textSize, - stateProperties, - canvas, - renderDataLabelRegions, - pointIndex, - label, - seriesRenderer, - animateOpacity, - dataLabelStyle); - } else { - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = getDataLabelTextStyle( - seriesRenderer, point, stateProperties, animateOpacity); - _renderOutsidePyramidDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To calculate pyramid inside label position. -void _setPyramidInsideLabelPosition( - DataLabelSettings dataLabel, - PointInfo point, - Size textSize, - PyramidStateProperties stateProperties, - Canvas canvas, - List renderDataLabelRegions, - int pointIndex, - String label, - PyramidSeriesRendererExtension seriesRenderer, - double animateOpacity, - TextStyle dataLabelStyle) { - final num angle = dataLabel.angle; - Offset labelLocation; - const int labelPadding = 2; - labelLocation = point.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (textSize.width / 2) + - (angle == 0 ? 0 : textSize.width / 2), - labelLocation.dy - - (textSize.height / 2) + - (angle == 0 ? 0 : textSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - textSize.width + (2 * labelPadding), - textSize.height + (2 * labelPadding)); - final bool isDataLabelCollide = - findingCollision(point.labelRect!, renderDataLabelRegions, point.region); - if (isDataLabelCollide) { - switch (dataLabel.overflowMode) { - case OverflowMode.trim: - label = getSegmentOverflowTrimmedText(dataLabel, point, textSize, - stateProperties, labelLocation, renderDataLabelRegions); - break; - case OverflowMode.hide: - label = ''; - break; - default: - break; - } - } - final bool isLabelCollide = PointInfoHelper.getIsLabelCollide(point); - if ((isLabelCollide && - dataLabel.labelIntersectAction == LabelIntersectAction.shift && - dataLabel.overflowMode == OverflowMode.none) || - isDataLabelCollide && dataLabel.overflowMode == OverflowMode.shift) { - point.saturationRegionOutside = true; - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = getDataLabelTextStyle( - seriesRenderer, point, stateProperties, animateOpacity); - _renderOutsidePyramidDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if ((dataLabel.labelIntersectAction == LabelIntersectAction.none || - (!isLabelCollide && - dataLabel.labelIntersectAction == LabelIntersectAction.shift) || - (!isLabelCollide && - dataLabel.labelIntersectAction == LabelIntersectAction.hide)) && - (!isDataLabelCollide && dataLabel.overflowMode == OverflowMode.hide || - (dataLabel.overflowMode == OverflowMode.none)) && - label != '') { - point.renderPosition = ChartDataLabelPosition.inside; - _drawPyramidLabel( - point.labelRect!, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if (((!isLabelCollide && - dataLabel.labelIntersectAction != LabelIntersectAction.hide) || - (dataLabel.overflowMode == OverflowMode.trim)) && - (label != '')) { - point.renderPosition = ChartDataLabelPosition.inside; - final Size trimmedTextSize = measureText(label, dataLabel.textStyle); - labelLocation = point.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (trimmedTextSize.width / 2) + - (angle == 0 ? 0 : textSize.width / 2), - labelLocation.dy - - (trimmedTextSize.height / 2) + - (angle == 0 ? 0 : trimmedTextSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - trimmedTextSize.width + (2 * labelPadding), - trimmedTextSize.height + (2 * labelPadding)); - _drawPyramidLabel( - point.labelRect!, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } -} - -/// To render outside pyramid data label. -void _renderOutsidePyramidDataLabel( - Canvas canvas, - String label, - PointInfo point, - Size textSize, - int pointIndex, - PyramidSeriesRendererExtension seriesRenderer, - PyramidStateProperties stateProperties, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Path connectorPath; - Rect? rect; - Offset labelLocation; - final EdgeInsets margin = seriesRenderer.series.dataLabelSettings.margin; - final ConnectorLineSettings connector = - seriesRenderer.series.dataLabelSettings.connectorLineSettings; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - const num regionPadding = 12; - connectorPath = Path(); - bool isPreviousRectIntersect = false; - final num connectorLength = percentToValue(connector.length ?? '0%', - stateProperties.renderingDetails.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); - final double dx = seriesRenderer.renderPoints![pointIndex].symbolLocation.dx + - connectorLength; - final Offset endPoint = Offset( - (dx + textSize.width + margin.left + margin.right > - stateProperties.renderingDetails.chartAreaRect.right) - ? dx - - (percentToValue(seriesRenderer.series.explodeOffset, - stateProperties.renderingDetails.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, - connectorPath, endPoint, textSize); - if (rect != null) { - point.labelRect = rect; - labelLocation = Offset(rect.left + margin.left, - rect.top + rect.height / 2 - textSize.height / 2); - final Rect containerRect = stateProperties.renderingDetails.chartAreaRect; - if (dataLabel.labelIntersectAction == LabelIntersectAction.shift || - dataLabel.overflowMode == OverflowMode.shift) { - if (seriesRenderer.series.dataLabelSettings.builder == null) { - Rect? lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - if (stateProperties.outsideRects.isNotEmpty) { - isPreviousRectIntersect = - _isPyramidLabelIntersect(rect, stateProperties.outsideRects.last); - } else { - isPreviousRectIntersect = - _isPyramidLabelIntersect(rect, lastRenderedLabelRegion); - } - if (!isPreviousRectIntersect && - (rect.left > containerRect.left && - rect.left + rect.width < - containerRect.left + containerRect.width) && - rect.top > containerRect.top && - rect.top + rect.height < containerRect.top + containerRect.height) { - stateProperties.outsideRects.add(rect); - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } else { - if (pointIndex != 0) { - const num connectorLinePadding = 15; - const num padding = 2; - final Rect previousRenderedRect = - stateProperties.outsideRects.isNotEmpty - ? stateProperties - .outsideRects[stateProperties.outsideRects.length - 1] - : 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 < - stateProperties.renderingDetails.chartAreaRect.bottom) { - stateProperties.outsideRects.add(rect); - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } else if (dataLabel.labelIntersectAction == LabelIntersectAction.none) { - if (seriesRenderer.series.dataLabelSettings.builder == null) { - stateProperties.outsideRects.add(rect); - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } else if (dataLabel.labelIntersectAction == LabelIntersectAction.hide) { - if (seriesRenderer.series.dataLabelSettings.builder == null) { - stateProperties.outsideRects.add(rect); - Rect? lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - isPreviousRectIntersect = - _isPyramidLabelIntersect(rect, lastRenderedLabelRegion); - if (!isPreviousRectIntersect) { - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - stateProperties, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To check whether labels intersect. -bool _isPyramidLabelIntersect(Rect rect, Rect? previousRect) { - bool isIntersect = false; - const num padding = 2; - if (previousRect != null && (rect.top - padding) < previousRect.bottom) { - isIntersect = true; - } - return isIntersect; -} - -/// To draw pyramid data label. -void _drawPyramidLabel( - Rect labelRect, - Offset location, - String? label, - Path? connectorPath, - Canvas canvas, - PyramidSeriesRendererExtension seriesRenderer, - PointInfo point, - int pointIndex, - PyramidStateProperties stateProperties, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Paint rectPaint; - final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer.dataLabelSettingsRenderer; - final ConnectorLineSettings connector = dataLabel.connectorLineSettings; - if (connectorPath != null) { - canvas.drawPath( - connectorPath, - Paint() - ..color = connector.width <= 0 - ? Colors.transparent - : connector.color ?? - point.fill.withOpacity( - !stateProperties.renderingDetails.isLegendToggled - ? animateOpacity - : dataLabel.opacity) - ..strokeWidth = connector.width - ..style = PaintingStyle.stroke); - } - - if (dataLabel.builder == null) { - final double strokeWidth = dataLabel.borderWidth; - final Color? labelFill = dataLabelSettingsRenderer.color ?? - (dataLabel.useSeriesColor - ? point.fill - : dataLabelSettingsRenderer.color); - final Color? strokeColor = - dataLabel.borderColor.withOpacity(dataLabel.opacity); - // ignore: unnecessary_null_comparison - if (strokeWidth != null && strokeWidth > 0) { - rectPaint = Paint() - ..color = strokeColor!.withOpacity( - !stateProperties.renderingDetails.isLegendToggled - ? animateOpacity - : dataLabel.opacity) - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth; - drawLabelRect( - rectPaint, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - if (labelFill != null) { - drawLabelRect( - Paint() - ..color = labelFill - .withOpacity(!stateProperties.renderingDetails.isLegendToggled - ? (animateOpacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : animateOpacity - (1 - dataLabel.opacity) - : dataLabel.opacity) - ..style = PaintingStyle.fill, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - drawText(canvas, label!, location, textStyle, dataLabel.angle); - renderDataLabelRegions.add(labelRect); - } -} - -/// Method to trigger the pyramid data label event. -void triggerPyramidDataLabelEvent( - SfPyramidChart chart, - PyramidSeriesRendererExtension seriesRenderer, - PyramidStateProperties chartState, - Offset position) { - const int seriesIndex = 0; - DataLabelSettings dataLabel; - PointInfo point; - Offset labelLocation; - for (int pointIndex = 0; - pointIndex < seriesRenderer.renderPoints!.length; - pointIndex++) { - dataLabel = seriesRenderer.series.dataLabelSettings; - point = seriesRenderer.renderPoints![pointIndex]; - labelLocation = point.symbolLocation; - if (dataLabel.isVisible && - 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); - } - } -} - -/// Method to get a text when the text overlap with another segment/slice. -String getSegmentOverflowTrimmedText( - DataLabelSettings dataLabel, - PointInfo point, - Size textSize, - StateProperties stateProperties, - Offset labelLocation, - List renderDataLabelRegions, -) { - bool isCollide; - String label = point.text!; - const int labelPadding = 2; - const String ellipse = '...'; - const int minCharacterLength = 3; - - isCollide = findingCollision(point.labelRect!, [], point.region); - while (isCollide) { - if (label == ellipse) { - label = ''; - break; - } - if (label.length > minCharacterLength) - label = addEllipse(label, label.length, ellipse, - isRtl: stateProperties.renderingDetails.isRtl); - else { - label = ''; - break; - } - final Size trimTextSize = measureText(label, dataLabel.textStyle); - final Rect trimRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - trimTextSize.width + (2 * labelPadding), - trimTextSize.height + (2 * labelPadding)); - isCollide = isLabelsColliding(trimRect, point.region); - } - return label == ellipse ? '' : label; -} - -/// To check collide. -bool isLabelsColliding(Rect rect, Rect? pathRect) { - bool isCollide = false; - if (pathRect != null && - (pathRect.left > rect.left && - pathRect.width > rect.width && - pathRect.top < rect.top && - pathRect.height > rect.height)) { - isCollide = false; - } else if (pathRect != null) { - isCollide = true; - } - return isCollide; -} - -/// To check if labels collide. -bool checkCollide(int index, List list) { - final Rect currentRect = list[index]; - Rect nextRect; - bool isCollide = false; - for (int i = index + 1; i < list.length; i++) { - nextRect = list[i]; - isCollide = currentRect.overlaps(nextRect); - if (isCollide == true) { - break; - } - } - return isCollide; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart index 56cceeff1..780f64217 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart @@ -29,13 +29,14 @@ class SparkChartMarker { /// ); /// } /// ``` - const SparkChartMarker( - {this.displayMode = SparkChartMarkerDisplayMode.none, - this.borderColor, - this.borderWidth = 2, - this.color, - this.size = 5, - this.shape = SparkChartMarkerShape.circle}); + const SparkChartMarker({ + this.displayMode = SparkChartMarkerDisplayMode.none, + this.borderColor, + this.borderWidth = 2, + this.color, + this.size = 5, + this.shape = SparkChartMarkerShape.circle, + }); /// Enables the markers in different modes. /// @@ -205,6 +206,6 @@ class SparkChartMarker { borderColor!, borderWidth, ]; - return hashList(values); + return Object.hashAll(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 6ebabe954..1781530ed 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart @@ -30,12 +30,13 @@ class SparkChartPlotBand { /// ); /// } /// ``` - const SparkChartPlotBand( - {this.color = const Color.fromRGBO(191, 212, 252, 0.5), - this.start, - this.end, - this.borderColor, - this.borderWidth = 0}); + const SparkChartPlotBand({ + this.color = const Color.fromRGBO(191, 212, 252, 0.5), + this.start, + this.end, + this.borderColor, + this.borderWidth = 0, + }); /// 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 @@ -166,6 +167,6 @@ class SparkChartPlotBand { borderColor!, borderWidth, ]; - return hashList(values); + return Object.hashAll(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 c37a0fe34..f45bf297e 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 @@ -10,29 +10,29 @@ import '../utils/helper.dart'; @immutable abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { /// Creates the render object for spark chart. - const SfSparkChartRenderObjectWidget( - {Key? key, - this.data, - this.dataCount, - this.xValueMapper, - this.yValueMapper, - this.isInversed, - this.axisCrossesAt, - this.axisLineColor, - this.axisLineWidth, - this.axisLineDashArray, - this.firstPointColor, - this.lowPointColor, - this.highPointColor, - this.lastPointColor, - this.negativePointColor, - this.color, - this.plotBand, - this.sparkChartDataDetails, - this.themeData, - this.dataPoints, - this.coordinatePoints}) - : super(key: key); + const SfSparkChartRenderObjectWidget({ + Key? key, + this.data, + this.dataCount, + this.xValueMapper, + this.yValueMapper, + this.isInversed, + this.axisCrossesAt, + this.axisLineColor, + this.axisLineWidth, + this.axisLineDashArray, + this.firstPointColor, + this.lowPointColor, + this.highPointColor, + this.lastPointColor, + this.negativePointColor, + this.color, + this.plotBand, + this.sparkChartDataDetails, + this.themeData, + this.dataPoints, + this.coordinatePoints, + }) : super(key: key); /// Specifies the data source for the series. final List? data; @@ -86,7 +86,7 @@ abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { final SparkChartDataDetails? sparkChartDataDetails; /// Specfies the theme of the spark chart. - final SfChartThemeData? themeData; + final SfSparkChartThemeData? themeData; /// Specifies the series screen coordinate points. final List? coordinatePoints; @@ -98,52 +98,51 @@ abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { /// Represents the RenderSparkChart class. abstract class RenderSparkChart extends RenderBox { /// Creates the render object widget. - RenderSparkChart( - { - //ignore: avoid_unused_constructor_parameters - 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, - SfChartThemeData? themeData, - List? coordinatePoints, - List? dataPoints}) - : _data = data, - _dataCount = dataCount, - _xValueMapper = xValueMapper, - _yValueMapper = yValueMapper, - _isInversed = isInversed, - _axisCrossesAt = axisCrossesAt, - _axisLineWidth = axisLineWidth, - _axisLineDashArray = axisLineDashArray, - _axisLineColor = axisLineColor, - _color = color, - _firstPointColor = firstPointColor, - _lastPointColor = lastPointColor, - _highPointColor = highPointColor, - _lowPointColor = lowPointColor, - _negativePointColor = negativePointColor, - _plotBand = plotBand, - _sparkChartDataDetails = sparkChartDataDetails, - _themeData = themeData, - _dataPoints = dataPoints, - _coordinatePoints = coordinatePoints { + RenderSparkChart({ + //ignore: avoid_unused_constructor_parameters + 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, + SfSparkChartThemeData? themeData, + List? coordinatePoints, + List? dataPoints, + }) : _data = data, + _dataCount = dataCount, + _xValueMapper = xValueMapper, + _yValueMapper = yValueMapper, + _isInversed = isInversed, + _axisCrossesAt = axisCrossesAt, + _axisLineWidth = axisLineWidth, + _axisLineDashArray = axisLineDashArray, + _axisLineColor = axisLineColor, + _color = color, + _firstPointColor = firstPointColor, + _lastPointColor = lastPointColor, + _highPointColor = highPointColor, + _lowPointColor = lowPointColor, + _negativePointColor = negativePointColor, + _plotBand = plotBand, + _sparkChartDataDetails = sparkChartDataDetails, + _themeData = themeData, + _dataPoints = dataPoints, + _coordinatePoints = coordinatePoints { processDataSource(); - if (isInversed == true) { + if (isInversed ?? false) { inverseDataPoints(); } } @@ -268,13 +267,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the spark chart theme. - SfChartThemeData? _themeData; + SfSparkChartThemeData? _themeData; /// Returns the spark chart theme. - SfChartThemeData? get themeData => _themeData; + SfSparkChartThemeData? get themeData => _themeData; /// Sets the spark chart theme. - set themeData(SfChartThemeData? value) { + set themeData(SfSparkChartThemeData? value) { if (_themeData != value) { _themeData = value; markNeedsPaint(); @@ -463,6 +462,9 @@ abstract class RenderSparkChart extends RenderBox { /// Specifies the area size. Size? areaSize; + /// Spark chart area size + Rect? sparkChartAreaRect; + /// Specifies the data label values. List? dataLabels; @@ -516,12 +518,13 @@ abstract class RenderSparkChart extends RenderBox { xValue = xValueMapper!(i); actualX = xValue; if (xValue is String) { - labelX = xValue.toString(); + labelX = xValue; xValue = i.toDouble(); } else if (xValue is DateTime) { xValue = xValue.millisecondsSinceEpoch; - labelX = DateFormat.yMd() - .format(DateTime.fromMillisecondsSinceEpoch(xValue)); + labelX = DateFormat.yMd().format( + DateTime.fromMillisecondsSinceEpoch(xValue), + ); } else if (xValue is num) { labelX = _getDataLabel(xValue); } @@ -548,8 +551,8 @@ abstract class RenderSparkChart extends RenderBox { String dataLabel = value.toString(); if (value is double) { value = double.parse(value.toStringAsFixed(3)); - final List? list = dataLabel.split('.'); - if (list != null && list.length > 1 && num.parse(list[1]) == 0) { + final List list = dataLabel.split('.'); + if (list.length > 1 && num.parse(list[1]) == 0) { value = value.round(); } } @@ -560,16 +563,20 @@ 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 - ? 0 - : (minY! < 0 && maxY! > 0) + final double areaHeight = areaSize!.height; + double? axisLineHeight = areaHeight - ((areaHeight / diffY!) * (-minY!)); + axisLineHeight = + minY! < value && maxY! <= value + ? 0 + : (minY! < value && maxY! > value) ? axisHeight - : areaSize!.height; + : areaHeight; if (value >= minY! && value <= maxY!) { - axisLineHeight = areaSize!.height - - (areaSize!.height * ((value - minY!) / diffY!)).roundToDouble(); + axisLineHeight = + (minY! == maxY!) + ? 0 + : areaHeight - + (areaHeight * ((value - minY!) / diffY!)).roundToDouble(); } return axisLineHeight; } @@ -589,42 +596,67 @@ 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 = diffX == 0 ? 1 : diffX; - diffY = diffY == 0 ? 1 : diffY; - axisHeight = getAxisHeight(); - 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); - } - coordinatePoints = sortScreenCoordiantePoints(coordinatePoints!); - } + if (minX == null || + maxX == null || + minY == null || + maxY == null || + dataPoints == null || + areaSize == null) { + return; + } + + diffX = maxX! - minX!; + diffY = maxY! - minY!; + axisHeight = getAxisHeight(); + 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); + } + coordinatePoints = sortScreenCoordinatePoints(coordinatePoints!); } /// Method to calculate the plot band position. void calculatePlotBandPosition() { + if (minX == null || + maxX == null || + minY == null || + maxY == null || + areaSize == null) { + return; + } + final double height = areaSize!.height; - final double? start = plotBand == null - ? 0 - : (plotBand!.start ?? minY!) < minY! + final double? start = + plotBand == null + ? 0 + : (plotBand!.start ?? minY!) < minY! ? minY : (plotBand!.start ?? minY); - final double? end = plotBand == null - ? 0 - : (plotBand!.end ?? maxY!) > maxY! + final double? end = + plotBand == null + ? 0 + : (plotBand!.end ?? maxY!) > maxY! ? maxY : (plotBand!.end ?? maxY); plotBandStartHeight = height - ((height / diffY!) * (start! - minY!)); @@ -633,18 +665,19 @@ abstract class RenderSparkChart extends RenderBox { /// Method to render axis line. void renderAxisline(Canvas canvas, Offset offset) { - if (axisLineWidth! > 0 && axisHeight != null) { + if (axisLineWidth! > 0 && axisHeight != null && !axisHeight!.isNaN) { final double x1 = offset.dx; 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! - ..style = PaintingStyle.stroke - ..color = axisLineColor!; + final Paint paint = + Paint() + ..strokeWidth = axisLineWidth! + ..style = PaintingStyle.stroke + ..color = axisLineColor!; if (axisLineDashArray != null && axisLineDashArray!.isNotEmpty) { - drawDashedPath(canvas, paint, point1, point2, axisLineDashArray!); + drawDashedPath(canvas, paint, point1, point2, axisLineDashArray); } else { canvas.drawLine(point1, point2, paint); } @@ -653,30 +686,44 @@ abstract class RenderSparkChart extends RenderBox { /// Method to render plot band. void renderPlotBand(Canvas canvas, Offset offset) { + if (plotBandStartHeight == null || + plotBandEndHeight == null || + areaSize == null) { + return; + } + if (plotBandStartHeight != plotBandEndHeight) { final Paint paint = Paint()..color = plotBand!.color; final Rect plotBandRect = Rect.fromLTRB( - offset.dx, - offset.dy + plotBandStartHeight!, - offset.dx + areaSize!.width, - offset.dy + plotBandEndHeight!); - canvas.drawRect(plotBandRect, paint); - if (plotBand!.borderColor != Colors.transparent && - plotBand!.borderWidth > 0) { - final Paint borderPaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = plotBand!.borderWidth - ..color = plotBand!.borderColor!; - canvas.drawRect(plotBandRect, borderPaint); + offset.dx, + offset.dy + plotBandStartHeight!, + offset.dx + areaSize!.width, + offset.dy + plotBandEndHeight!, + ); + if (plotBandRect.top >= sparkChartAreaRect!.top && + plotBandRect.bottom <= sparkChartAreaRect!.bottom) { + canvas.drawRect(plotBandRect, paint); + if (plotBand!.borderColor != Colors.transparent && + plotBand!.borderWidth > 0) { + final Paint borderPaint = + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = plotBand!.borderWidth + ..color = plotBand!.borderColor!; + canvas.drawRect(plotBandRect, borderPaint); + } } } else { - final Paint paint = Paint() - ..color = plotBand!.color - ..style = PaintingStyle.stroke - ..strokeWidth = 3; + final Paint paint = + Paint() + ..color = plotBand!.color + ..style = PaintingStyle.stroke + ..strokeWidth = 3; final Offset point1 = Offset(offset.dx, offset.dy + plotBandStartHeight!); - final Offset point2 = - Offset(offset.dx + areaSize!.width, offset.dy + plotBandStartHeight!); + final Offset point2 = Offset( + offset.dx + areaSize!.width, + offset.dy + plotBandStartHeight!, + ); canvas.drawLine(point1, point2, paint); } } @@ -706,7 +753,7 @@ abstract class RenderSparkChart extends RenderBox { @override void paint(PaintingContext context, Offset offset) { renderAxisline(context.canvas, offset); - + sparkChartAreaRect = context.estimatedBounds; if (plotBand != null) { renderPlotBand(context.canvas, offset); } 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 792c44fcd..3d7545848 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 @@ -33,32 +33,33 @@ class SfSparkAreaChartRenderObjectWidget this.marker, this.labelDisplayMode, this.labelStyle, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, SparkChartDataDetails? sparkChartDataDetails, List? coordinatePoints, List? dataPoints, }) : super( - key: key, - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lowPointColor: lowPointColor, - highPointColor: highPointColor, - lastPointColor: lastPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - dataPoints: dataPoints, - coordinatePoints: coordinatePoints); + key: key, + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lowPointColor: lowPointColor, + highPointColor: highPointColor, + lastPointColor: lastPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + dataPoints: dataPoints, + coordinatePoints: coordinatePoints, + ); /// Specifies the area chart border width. final double? borderWidth; @@ -78,38 +79,40 @@ class SfSparkAreaChartRenderObjectWidget @override RenderObject createRenderObject(BuildContext context) { return _RenderSparkAreaChart( - dataCount: dataCount, - data: data, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - borderColor: borderColor, - borderWidth: borderWidth, - marker: marker, - labelDisplayMode: labelDisplayMode, - labelStyle: labelStyle, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - dataPoints: dataPoints, - coordinatePoints: coordinatePoints); + dataCount: dataCount, + data: data, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + borderColor: borderColor, + borderWidth: borderWidth, + marker: marker, + labelDisplayMode: labelDisplayMode, + labelStyle: labelStyle, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + dataPoints: dataPoints, + coordinatePoints: coordinatePoints, + ); } @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderSparkAreaChart renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderSparkAreaChart renderObject, + ) { renderObject ..dataCount = dataCount ..data = data @@ -141,58 +144,59 @@ class SfSparkAreaChartRenderObjectWidget /// Represents the render spark area chart class. 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, - SfChartThemeData? themeData, - List? coordinatePoints, - List? dataPoints}) - : _borderWidth = borderWidth, - _borderColor = borderColor, - _marker = marker, - _labelDisplayMode = labelDisplayMode, - _labelStyle = labelStyle, - super( - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineWidth: axisLineWidth, - axisLineColor: axisLineColor, - axisLineDashArray: axisLineDashArray, - color: color, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + _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, + SfSparkChartThemeData? themeData, + List? coordinatePoints, + List? dataPoints, + }) : _borderWidth = borderWidth, + _borderColor = borderColor, + _marker = marker, + _labelDisplayMode = labelDisplayMode, + _labelStyle = labelStyle, + super( + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineWidth: axisLineWidth, + axisLineColor: axisLineColor, + axisLineDashArray: axisLineDashArray, + color: color, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Defines the border width. double? _borderWidth; @@ -211,7 +215,7 @@ class _RenderSparkAreaChart extends RenderSparkChart { /// Defines the dash array. Color? _borderColor; - /// Returns the dash arry value. + /// Returns the dash array value. Color? get borderColor => _borderColor; /// Set the line width value. @@ -265,28 +269,30 @@ class _RenderSparkAreaChart extends RenderSparkChart { } /// Specifies the low point in series. - num? _lowPoint; + late num? _lowPoint; /// Specifies the high point in series. - num? _highPoint; + late num? _highPoint; @override void processDataSource() { super.processDataSource(); if (dataPoints != null && dataPoints!.isNotEmpty) { - final List temp = - List.from(dataPoints!); + 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!.toDouble()); + 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); - final SparkChartPoint point2 = - SparkChartPoint(x: temp[temp.length - 1].x, y: minY!.toDouble()); + 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); @@ -298,9 +304,10 @@ class _RenderSparkAreaChart extends RenderSparkChart { /// Render area series. void _renderAreaSeries(Canvas canvas, Offset offset) { - final Paint paint = Paint() - ..color = color! - ..style = PaintingStyle.fill; + final Paint paint = + Paint() + ..color = color! + ..style = PaintingStyle.fill; final Path path = Path(); Size size; _highPoint = coordinatePoints![0].dy; @@ -316,13 +323,17 @@ class _RenderSparkAreaChart extends RenderSparkChart { } 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); + path.lineTo( + offset.dx + coordinatePoints![i + 1].dx, + offset.dy + coordinatePoints![i + 1].dy, + ); } if (i >= 1 && @@ -331,50 +342,54 @@ class _RenderSparkAreaChart extends RenderSparkChart { labelStyle != null) { 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 - - 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))); + (offset.dx + coordinatePoints![i].dx) - size.width / 2, + offset.dy + + (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 + size.height)), + ); if (dataPoints![i].dataLabelOffset!.dx <= offset.dx) { - dataPoints![i].dataLabelOffset = - Offset(offset.dx, dataPoints![i].dataLabelOffset!.dy); + 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); + (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, - offset.dy + - (marker != null && - marker!.displayMode != - SparkChartMarkerDisplayMode.none - ? marker!.size / 2 + size.height - : size.height)); + dataPoints![i].dataLabelOffset!.dx, + offset.dy + + (marker != null && + 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) - - (marker != null && - marker!.displayMode != - SparkChartMarkerDisplayMode.none - ? marker!.size / 2 + size.height - : size.height)); + dataPoints![i].dataLabelOffset!.dx, + (offset.dy + areaSize!.height) - + (marker != null && + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height + : size.height), + ); } } } @@ -390,21 +405,26 @@ 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! - ..style = PaintingStyle.stroke; + final Paint strokePaint = + Paint() + ..color = borderColor! + ..strokeWidth = borderWidth! + ..style = PaintingStyle.stroke; final Path strokePath = Path(); 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); + strokePath.lineTo( + offset.dx + coordinatePoints![i + 1].dx, + offset.dy + coordinatePoints![i + 1].dy, + ); } } @@ -423,38 +443,40 @@ class _RenderSparkAreaChart extends RenderSparkChart { marker!.displayMode != SparkChartMarkerDisplayMode.none && marker!.borderWidth > 0) { renderMarker( - context.canvas, - offset, - marker!, - coordinatePoints!, - dataPoints!, - color!, - 'Area', - _highPoint!, - _lowPoint!, - axisCrossesAt!, - themeData!, - lowPointColor, - highPointColor, - negativePointColor, - firstPointColor, - lastPointColor); + context.canvas, + offset, + marker!, + coordinatePoints!, + dataPoints!, + color!, + 'Area', + _highPoint!, + _lowPoint!, + axisCrossesAt!, + themeData!, + lowPointColor, + highPointColor, + negativePointColor, + firstPointColor, + lastPointColor, + ); } if (labelDisplayMode != null && labelDisplayMode != SparkChartLabelDisplayMode.none) { renderDataLabel( - context.canvas, - dataLabels!, - dataPoints!, - coordinatePoints!, - labelStyle!, - labelDisplayMode!, - 'Area', - themeData!, - offset, - color!, - _highPoint!, - _lowPoint!); + context.canvas, + dataLabels!, + dataPoints!, + coordinatePoints!, + labelStyle!, + labelDisplayMode!, + 'Area', + themeData!, + offset, + 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 df53577c0..cba5048c7 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 @@ -8,54 +8,55 @@ import 'renderer_base.dart'; /// Represents the render object for spark chart. class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart. - const SfSparkBarChartRenderObjectWidget( - {Key? key, - this.borderWidth, - this.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, - this.labelDisplayMode, - this.labelStyle, - SfChartThemeData? themeData, - SparkChartDataDetails? sparkChartDataDetails, - List? coordinatePoints, - List? dataPoints}) - : super( - key: key, - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lowPointColor: lowPointColor, - highPointColor: highPointColor, - lastPointColor: lastPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + const SfSparkBarChartRenderObjectWidget({ + Key? key, + this.borderWidth, + this.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, + this.labelDisplayMode, + this.labelStyle, + SfSparkChartThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints, + }) : super( + key: key, + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lowPointColor: lowPointColor, + highPointColor: highPointColor, + lastPointColor: lastPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Specifies the bar chart border width. final double? borderWidth; @@ -72,40 +73,42 @@ class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) { return _RenderSparkBarChart( - dataCount: dataCount, - data: data, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - borderColor: borderColor, - borderWidth: borderWidth, - labelDisplayMode: labelDisplayMode, - labelStyle: labelStyle, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + dataCount: dataCount, + data: data, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + borderColor: borderColor, + borderWidth: borderWidth, + labelDisplayMode: labelDisplayMode, + labelStyle: labelStyle, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); } @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderSparkBarChart renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderSparkBarChart renderObject, + ) { renderObject ..isInversed = isInversed - ..axisCrossesAt = axisCrossesAt! + ..axisCrossesAt = axisCrossesAt ..axisLineColor = axisLineColor ..axisLineWidth = axisLineWidth ..axisLineDashArray = axisLineDashArray @@ -133,57 +136,58 @@ class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Represents the render spark bar chart class. 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, - SfChartThemeData? themeData, - List? coordinatePoints, - List? dataPoints}) - : _borderWidth = borderWidth, - _borderColor = borderColor, - _labelDisplayMode = labelDisplayMode, - _labelStyle = labelStyle, - _axisCrossesAt = axisCrossesAt, - super( - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineWidth: axisLineWidth, - axisLineColor: axisLineColor, - axisLineDashArray: axisLineDashArray, - color: color, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + _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, + SfSparkChartThemeData? themeData, + List? coordinatePoints, + List? dataPoints, + }) : _borderWidth = borderWidth, + _borderColor = borderColor, + _labelDisplayMode = labelDisplayMode, + _labelStyle = labelStyle, + _axisCrossesAt = axisCrossesAt, + super( + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineWidth: axisLineWidth, + axisLineColor: axisLineColor, + axisLineDashArray: axisLineDashArray, + color: color, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Defines the border width. double? _borderWidth; @@ -252,7 +256,7 @@ class _RenderSparkBarChart extends RenderSparkChart { @override set axisCrossesAt(double? value) { if (_axisCrossesAt != value) { - _axisCrossesAt = value!; + _axisCrossesAt = value; calculateRenderingPoints(); markNeedsPaint(); } @@ -269,15 +273,24 @@ class _RenderSparkBarChart extends RenderSparkChart { @override void calculateRenderingPoints() { + if (minX == null || + maxX == null || + minY == null || + maxY == null || + dataPoints == null || + areaSize == null) { + return; + } + diffX = maxX! - minX!; diffY = maxY! - minY!; diffX = diffX == 0 ? 1 : diffX; - diffY = diffY == 0 ? 1 : diffY; _segments = []; - final double xInterval = dataPoints!.length > 1 - ? dataPoints![1].x.toDouble() - dataPoints![0].x.toDouble() - : dataPoints!.length.toDouble(); + final double xInterval = + dataPoints!.length > 1 + ? dataPoints![1].x.toDouble() - dataPoints![0].x.toDouble() + : dataPoints!.length.toDouble(); const double columnSpace = 0.5; // Default space for column and winloss const double space = columnSpace * 2; final double? axisBaseValue = minY! < 0 ? minY : 0; @@ -287,6 +300,7 @@ class _RenderSparkBarChart extends RenderSparkChart { double columnWidth = areaSize!.width / (((maxX! - minX!) / xInterval) + 1); columnWidth -= space; diffY = maxY! - axisBaseValue!; + diffY = diffY == 0 ? 1 : diffY; axisHeight = getAxisHeight(); if (coordinatePoints!.isNotEmpty) { coordinatePoints!.clear(); @@ -298,12 +312,13 @@ class _RenderSparkBarChart extends RenderSparkChart { visibleXPoint = (((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)) - .toDouble() - : (y == maxY && + currentColumnHeight = + (y == axisBaseValue && y > axisCrossesAt) + ? ((dataPoints!.length != 1 && diffY != 1) + ? (areaSize!.height / diffY!) * axisBaseValue + : (columnHeight.toInt() | 1)) + .toDouble() + : (y == maxY && y < axisCrossesAt && dataPoints!.length != 1 && diffY != 1) @@ -311,8 +326,12 @@ class _RenderSparkBarChart extends RenderSparkChart { : columnHeight; y2 = (areaSize!.height - currentColumnHeight).abs(); top = (y2 > axisHeight!) ? axisHeight! : y2; - rect = Rect.fromLTRB(visibleXPoint, top, visibleXPoint + columnWidth, - top + (y2 - axisHeight!).abs()); + rect = Rect.fromLTRB( + visibleXPoint, + top, + visibleXPoint + columnWidth, + top + (y2 - axisHeight!).abs(), + ); _segments!.add(rect); yPoint = y >= axisCrossesAt ? rect.top : rect.bottom; coordinatePoints!.add(Offset(visibleXPoint + columnWidth / 2, yPoint)); @@ -326,13 +345,15 @@ class _RenderSparkBarChart extends RenderSparkChart { 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) + axisLineHeight = + (minY! < 0 && maxY! <= 0) + ? 0 + : (minY! < 0 && maxY! > 0) ? axisHeight : areaSize!.height; if (value >= minimumColumnValue && value <= maxY!) { - axisLineHeight = areaSize!.height - + axisLineHeight = + areaSize!.height - (areaSize!.height * ((value - minimumColumnValue) / diffY!)) .roundToDouble(); } @@ -342,6 +363,14 @@ class _RenderSparkBarChart extends RenderSparkChart { /// Method to calculate the plot band position. @override void calculatePlotBandPosition() { + if (minX == null || + maxX == null || + minY == null || + maxY == null || + areaSize == null) { + return; + } + final double height = areaSize!.height; final double start = (plotBand!.start ?? minY!) < minY! ? minY! : (plotBand!.start ?? minY!); @@ -356,14 +385,16 @@ class _RenderSparkBarChart extends RenderSparkChart { void _renderBarSeries(Canvas canvas, Offset offset) { Color currentColor; Paint paint; - final Paint strokePaint = Paint() - ..color = borderColor ?? Colors.transparent - ..strokeWidth = borderWidth ?? 0 - ..style = PaintingStyle.stroke; + final Paint strokePaint = + Paint() + ..color = borderColor ?? Colors.transparent + ..strokeWidth = borderWidth ?? 0 + ..style = PaintingStyle.stroke; Size size; double yPosition; - final bool canDrawBorder = borderColor != null && + final bool canDrawBorder = + borderColor != null && borderColor != Colors.transparent && borderWidth != null && borderWidth! > 0; @@ -379,10 +410,11 @@ class _RenderSparkBarChart extends RenderSparkChart { _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); + _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) { @@ -407,22 +439,27 @@ 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); + 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); + (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); + 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); + dataPoints![i].dataLabelOffset!.dx, + (offset.dy + areaSize!.height) - size.height, + ); } } } @@ -439,19 +476,20 @@ class _RenderSparkBarChart extends RenderSparkChart { if (labelDisplayMode != null && labelDisplayMode != SparkChartLabelDisplayMode.none) { renderDataLabel( - context.canvas, - dataLabels!, - dataPoints!, - coordinatePoints!, - labelStyle!, - labelDisplayMode!, - 'Bar', - themeData!, - offset, - color!, - _highPoint, - _lowPoint, - _segments!); + context.canvas, + dataLabels!, + dataPoints!, + coordinatePoints!, + labelStyle!, + labelDisplayMode!, + 'Bar', + themeData!, + offset, + color!, + _highPoint, + _lowPoint, + _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 c120eb7b8..979b00cc1 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 @@ -10,55 +10,56 @@ import 'renderer_base.dart'; class SfSparkLineChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart. - const SfSparkLineChartRenderObjectWidget( - {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, - SfChartThemeData? themeData, - SparkChartDataDetails? sparkChartDataDetails, - List? coordinatePoints, - List? dataPoints}) - : super( - key: key, - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lowPointColor: lowPointColor, - highPointColor: highPointColor, - lastPointColor: lastPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + const SfSparkLineChartRenderObjectWidget({ + 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, + SfSparkChartThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints, + }) : super( + key: key, + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lowPointColor: lowPointColor, + highPointColor: highPointColor, + lastPointColor: lastPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Specifies the line width. final double? width; @@ -78,38 +79,40 @@ class SfSparkLineChartRenderObjectWidget @override RenderObject createRenderObject(BuildContext context) { return _RenderSparkLineChart( - dataCount: dataCount, - data: data, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - width: width, - dashArray: dashArray, - marker: marker, - labelDisplayMode: labelDisplayMode, - labelStyle: labelStyle, - themeData: themeData!, - sparkChartDataDetails: sparkChartDataDetails, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + dataCount: dataCount, + data: data, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + width: width, + dashArray: dashArray, + marker: marker, + labelDisplayMode: labelDisplayMode, + labelStyle: labelStyle, + themeData: themeData, + sparkChartDataDetails: sparkChartDataDetails, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); } @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderSparkLineChart renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderSparkLineChart renderObject, + ) { renderObject ..isInversed = isInversed ..axisCrossesAt = axisCrossesAt @@ -141,58 +144,59 @@ class SfSparkLineChartRenderObjectWidget /// Represents the render spark line class. 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, - SfChartThemeData? themeData, - List? coordinatePoints, - List? dataPoints}) - : _width = width, - _dashArray = dashArray, - _marker = marker, - _labelDisplayMode = labelDisplayMode, - _labelStyle = labelStyle, - super( - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineWidth: axisLineWidth, - axisLineColor: axisLineColor, - axisLineDashArray: axisLineDashArray, - color: color, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - plotBand: plotBand, - themeData: themeData, - sparkChartDataDetails: sparkChartDataDetails, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + _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, + SfSparkChartThemeData? themeData, + List? coordinatePoints, + List? dataPoints, + }) : _width = width, + _dashArray = dashArray, + _marker = marker, + _labelDisplayMode = labelDisplayMode, + _labelStyle = labelStyle, + super( + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineWidth: axisLineWidth, + axisLineColor: axisLineColor, + axisLineDashArray: axisLineDashArray, + color: color, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + plotBand: plotBand, + themeData: themeData, + sparkChartDataDetails: sparkChartDataDetails, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Defines the line width. double? _width; @@ -273,10 +277,11 @@ class _RenderSparkLineChart extends RenderSparkChart { /// Render line series. void _renderLineSeries(Canvas canvas, Offset offset) { if (width != null && width! > 0) { - final Paint paint = Paint() - ..strokeWidth = width! - ..style = PaintingStyle.stroke - ..color = color!; + final Paint paint = + Paint() + ..strokeWidth = width! + ..style = PaintingStyle.stroke + ..color = color!; Size size; double yPosition; @@ -294,26 +299,34 @@ class _RenderSparkLineChart extends RenderSparkChart { } 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); + 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!); - 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 + 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) - size.width / 2, - offset.dy + yPosition); + (offset.dx + coordinatePoints![i].dx) - size.width / 2, + offset.dy + yPosition, + ); _positionDataLabels(dataPoints![i], size, offset); } } @@ -329,29 +342,37 @@ class _RenderSparkLineChart extends RenderSparkChart { } 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); + 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!); - 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 + 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) - size.width / 2, - offset.dy + yPosition); + (offset.dx + coordinatePoints![i].dx) - size.width / 2, + offset.dy + yPosition, + ); _positionDataLabels(dataPoints![i], size, offset); } } @@ -362,35 +383,43 @@ class _RenderSparkLineChart extends RenderSparkChart { } void _positionDataLabels( - SparkChartPoint dataPoint, Size size, Offset offset) { + SparkChartPoint dataPoint, + Size size, + Offset offset, + ) { if (dataPoint.dataLabelOffset!.dx <= offset.dx) { - dataPoint.dataLabelOffset = - Offset(offset.dx, dataPoint.dataLabelOffset!.dy); + dataPoint.dataLabelOffset = Offset( + offset.dx, + dataPoint.dataLabelOffset!.dy, + ); } 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) { dataPoint.dataLabelOffset = Offset( - dataPoint.dataLabelOffset!.dx, - offset.dy + - (marker != null && - marker!.displayMode != SparkChartMarkerDisplayMode.none - ? marker!.size / 2 + size.height - : size.height)); + dataPoint.dataLabelOffset!.dx, + offset.dy + + (marker != null && + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height + : size.height), + ); } if (dataPoint.dataLabelOffset!.dy >= offset.dy + areaSize!.height) { dataPoint.dataLabelOffset = Offset( - dataPoint.dataLabelOffset!.dx, - (offset.dy + areaSize!.height) - - (marker != null && - marker!.displayMode != SparkChartMarkerDisplayMode.none - ? marker!.size / 2 + size.height - : size.height)); + dataPoint.dataLabelOffset!.dx, + (offset.dy + areaSize!.height) - + (marker != null && + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height + : size.height), + ); } } @@ -407,38 +436,40 @@ class _RenderSparkLineChart extends RenderSparkChart { marker!.displayMode != SparkChartMarkerDisplayMode.none && marker!.borderWidth > 0) { renderMarker( - context.canvas, - offset, - marker!, - coordinatePoints!, - dataPoints!, - color!, - 'Line', - _highPoint, - _lowPoint, - axisCrossesAt!, - themeData!, - lowPointColor, - highPointColor, - negativePointColor, - firstPointColor, - lastPointColor); + context.canvas, + offset, + marker!, + coordinatePoints!, + dataPoints!, + color!, + 'Line', + _highPoint, + _lowPoint, + axisCrossesAt!, + themeData!, + lowPointColor, + highPointColor, + negativePointColor, + firstPointColor, + lastPointColor, + ); } if (labelDisplayMode != null && labelDisplayMode != SparkChartLabelDisplayMode.none) { renderDataLabel( - context.canvas, - dataLabels!, - dataPoints!, - coordinatePoints!, - labelStyle!, - labelDisplayMode!, - 'Line', - themeData!, - offset, - color!, - _highPoint, - _lowPoint); + context.canvas, + dataLabels!, + dataPoints!, + coordinatePoints!, + labelStyle!, + labelDisplayMode!, + 'Line', + themeData!, + offset, + 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 d2e1c2f66..7f7c9bcb8 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 @@ -9,53 +9,54 @@ import 'renderer_base.dart'; class SfSparkWinLossChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart. - const SfSparkWinLossChartRenderObjectWidget( - {Key? key, - List? data, - int? dataCount, - SparkChartIndexedValueMapper? xValueMapper, - SparkChartIndexedValueMapper? yValueMapper, - Color? color, - SparkChartPlotBand? plotBand, - this.borderWidth, - this.borderColor, - this.tiePointColor, - bool? isInversed, - double? axisCrossesAt, - Color? axisLineColor, - double? axisLineWidth, - List? axisLineDashArray, - Color? firstPointColor, - Color? lowPointColor, - Color? highPointColor, - Color? lastPointColor, - Color? negativePointColor, - SparkChartDataDetails? sparkChartDataDetails, - SfChartThemeData? themeData, - List? coordinatePoints, - List? dataPoints}) - : super( - key: key, - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lowPointColor: lowPointColor, - highPointColor: highPointColor, - lastPointColor: lastPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + const SfSparkWinLossChartRenderObjectWidget({ + Key? key, + List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + Color? color, + SparkChartPlotBand? plotBand, + this.borderWidth, + this.borderColor, + this.tiePointColor, + bool? isInversed, + double? axisCrossesAt, + Color? axisLineColor, + double? axisLineWidth, + List? axisLineDashArray, + Color? firstPointColor, + Color? lowPointColor, + Color? highPointColor, + Color? lastPointColor, + Color? negativePointColor, + SparkChartDataDetails? sparkChartDataDetails, + SfSparkChartThemeData? themeData, + List? coordinatePoints, + List? dataPoints, + }) : super( + key: key, + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lowPointColor: lowPointColor, + highPointColor: highPointColor, + lastPointColor: lastPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Specifies the area chart border width. final double? borderWidth; @@ -69,36 +70,38 @@ class SfSparkWinLossChartRenderObjectWidget @override RenderObject createRenderObject(BuildContext context) { return _RenderSparkWinLossChart( - dataCount: dataCount, - data: data, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineColor: axisLineColor, - axisLineWidth: axisLineWidth, - axisLineDashArray: axisLineDashArray, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - color: color, - plotBand: plotBand, - tiePointColor: tiePointColor, - borderColor: borderColor, - borderWidth: borderWidth, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + dataCount: dataCount, + data: data, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineColor: axisLineColor, + axisLineWidth: axisLineWidth, + axisLineDashArray: axisLineDashArray, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + color: color, + plotBand: plotBand, + tiePointColor: tiePointColor, + borderColor: borderColor, + borderWidth: borderWidth, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); } @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderSparkWinLossChart renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderSparkWinLossChart renderObject, + ) { renderObject ..isInversed = isInversed ..axisCrossesAt = axisCrossesAt @@ -128,54 +131,55 @@ class SfSparkWinLossChartRenderObjectWidget /// Represents the render spark win loss chart class. 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, - SfChartThemeData? themeData, - SparkChartDataDetails? sparkChartDataDetails, - List? coordinatePoints, - List? dataPoints}) - : _tiePointColor = tiePointColor, - _borderWidth = borderWidth, - _borderColor = borderColor, - super( - data: data, - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper, - isInversed: isInversed, - axisCrossesAt: axisCrossesAt, - axisLineWidth: axisLineWidth, - axisLineColor: axisLineColor, - axisLineDashArray: axisLineDashArray, - color: color, - firstPointColor: firstPointColor, - lastPointColor: lastPointColor, - highPointColor: highPointColor, - lowPointColor: lowPointColor, - negativePointColor: negativePointColor, - plotBand: plotBand, - sparkChartDataDetails: sparkChartDataDetails, - themeData: themeData, - coordinatePoints: coordinatePoints, - dataPoints: dataPoints); + _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, + SfSparkChartThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints, + }) : _tiePointColor = tiePointColor, + _borderWidth = borderWidth, + _borderColor = borderColor, + super( + data: data, + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + isInversed: isInversed, + axisCrossesAt: axisCrossesAt, + axisLineWidth: axisLineWidth, + axisLineColor: axisLineColor, + axisLineDashArray: axisLineDashArray, + color: color, + firstPointColor: firstPointColor, + lastPointColor: lastPointColor, + highPointColor: highPointColor, + lowPointColor: lowPointColor, + negativePointColor: negativePointColor, + plotBand: plotBand, + sparkChartDataDetails: sparkChartDataDetails, + themeData: themeData, + coordinatePoints: coordinatePoints, + dataPoints: dataPoints, + ); /// Defines the border width. double? _borderWidth; @@ -224,15 +228,25 @@ class _RenderSparkWinLossChart extends RenderSparkChart { @override void calculateRenderingPoints() { + if (minX == null || + maxX == null || + minY == null || + maxY == null || + dataPoints == null || + areaSize == null) { + return; + } + diffX = maxX! - minX!; diffY = maxY! - minY!; diffX = diffX == 0 ? 1 : diffX; diffY = diffY == 0 ? 1 : diffY; _segments = []; - final double xInterval = dataPoints!.length > 1 - ? dataPoints![1].x.toDouble() - dataPoints![0].x.toDouble() - : dataPoints!.length.toDouble(); + final double xInterval = + dataPoints!.length > 1 + ? dataPoints![1].x.toDouble() - dataPoints![0].x.toDouble() + : dataPoints!.length.toDouble(); const double columnSpace = 0.5; // Default space for column and winloss const double space = columnSpace * 2; const double winLossFactor = 0.5; @@ -255,17 +269,22 @@ class _RenderSparkWinLossChart extends RenderSparkChart { visibleXPoint = (((x - minX!) / xInterval) * (columnWidth + space)) + (space / 2); - y2 = (y > axisCrossesAt!) - ? (areaSize!.height / 2) - : (y < axisCrossesAt!) + 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; - rect = - Rect.fromLTRB(visibleXPoint, y2, visibleXPoint + columnWidth, bottom); + rect = Rect.fromLTRB( + visibleXPoint, + y2, + visibleXPoint + columnWidth, + bottom, + ); _segments.add(rect); coordinatePoints!.add(Offset(visibleXPoint + columnWidth / 2, y2)); } @@ -273,17 +292,19 @@ class _RenderSparkWinLossChart extends RenderSparkChart { /// Method to render win loss series. void _renderWinLossSeries(Canvas canvas, Offset offset) { - final Paint tiePointPaint = Paint() - ..color = tiePointColor ?? Colors.deepPurple; - final Paint negativePointPaint = Paint() - ..color = negativePointColor ?? Colors.red; + final Paint tiePointPaint = + Paint()..color = tiePointColor ?? Colors.deepPurple; + final Paint negativePointPaint = + Paint()..color = negativePointColor ?? Colors.red; final Paint paint = Paint()..color = color!; - final Paint strokePaint = Paint() - ..color = borderColor ?? Colors.transparent - ..strokeWidth = borderWidth ?? 0 - ..style = PaintingStyle.stroke; + final Paint strokePaint = + Paint() + ..color = borderColor ?? Colors.transparent + ..strokeWidth = borderWidth ?? 0 + ..style = PaintingStyle.stroke; - final bool canDrawBorder = borderColor != null && + final bool canDrawBorder = + borderColor != null && borderColor != Colors.transparent && borderWidth != null && borderWidth! > 0; @@ -291,10 +312,11 @@ class _RenderSparkWinLossChart extends RenderSparkChart { 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); + _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!) { canvas.drawRect(rect, negativePointPaint); } else if (dataPoints![i].y == axisCrossesAt) { 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 30d007f1b..79f45e510 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 @@ -3,6 +3,7 @@ import 'package:syncfusion_flutter_core/theme.dart'; import '../marker.dart'; import '../plot_band.dart'; import '../renderers/spark_area_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -13,7 +14,7 @@ import '../utils/helper.dart'; /// 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. +/// 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 @@ -38,39 +39,35 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - SfSparkAreaChart( - {Key? key, - List? data, - this.plotBand, - this.borderWidth = 0, - this.borderColor, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.marker, - this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails(data: data!), - super(key: key); + SfSparkAreaChart({ + Key? key, + List? data, + this.plotBand, + this.borderWidth = 0, + this.borderColor, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.marker, + this.labelDisplayMode, + this.labelStyle, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails(data: data), + super(key: key); /// 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 `data` property of the default constructor is of type `List`. /// /// The custom constructor uses its [dataCount], [xValueMapper] and /// [yValueMapper] to get the input data. @@ -118,44 +115,41 @@ class SfSparkAreaChart extends StatefulWidget { /// } /// ``` - SfSparkAreaChart.custom( - {Key? key, - - /// Data count for the spark charts. - int? dataCount, - - /// Specifies the x-value mapping field. - SparkChartIndexedValueMapper? xValueMapper, - - /// Specifies the y-value maping field. - SparkChartIndexedValueMapper? yValueMapper, - this.plotBand, - this.borderWidth = 2, - this.borderColor, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.marker, - this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails( - dataCount: dataCount!, - xValueMapper: xValueMapper!, - yValueMapper: yValueMapper!), - super(key: key); + SfSparkAreaChart.custom({ + Key? key, + + /// Data count for the spark charts. + int? dataCount, + + /// Specifies the x-value mapping field. + SparkChartIndexedValueMapper? xValueMapper, + + /// Specifies the y-value mapping field. + SparkChartIndexedValueMapper? yValueMapper, + this.plotBand, + this.borderWidth = 2, + this.borderColor, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.marker, + this.labelDisplayMode, + this.labelStyle, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails( + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + ), + super(key: key); /// Inverts the axis from right to left. /// @@ -222,7 +216,7 @@ class SfSparkAreaChart extends StatefulWidget { /// Customizes the color of the axis line. /// Colors.transparent can be set to [axisLineColor] to hide the axis line. /// - /// Defaults to `Colors.black`. + /// Defaults to null. /// /// ```dart /// @override @@ -237,7 +231,7 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// 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. @@ -372,7 +366,7 @@ class SfSparkAreaChart extends StatefulWidget { /// Customizes the spark area chart color. /// - /// Defaults to `Colors.blue`. + /// Defaults to null. /// /// ```dart /// @override @@ -386,11 +380,11 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// - /// Plot band is also known as stripline, which is used to shade the different + /// 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. /// @@ -502,7 +496,7 @@ class SfSparkAreaChart extends StatefulWidget { /// /// Also refer [SparkChartLabelDisplayMode]. /// - /// Defaults to `SparkChartDislayMode.none`. + /// Defaults to `SparkChartDisplayMode.none`. /// /// ```dart /// @override @@ -522,8 +516,7 @@ class SfSparkAreaChart extends StatefulWidget { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` and font - /// family `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -539,7 +532,7 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Enables and customizes the trackball. /// @@ -579,7 +572,7 @@ class SfSparkAreaChart extends StatefulWidget { /// Represents the state class for spark area widget. class _SfSparkAreaChartState extends State { /// Specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -587,6 +580,50 @@ class _SfSparkAreaChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = SparkChartThemeData( + context, + ); + chartThemeData = chartThemeData.copyWith( + color: + widget.color ?? chartThemeData.color ?? effectiveChartThemeData.color, + axisLineColor: + widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: + chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: + chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: + chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + dataLabelTextStyle: theme.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(chartThemeData.dataLabelTextStyle) + .merge(widget.labelStyle), + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: + widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle), + ); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -628,7 +665,6 @@ class _SfSparkAreaChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -643,12 +679,16 @@ class _SfSparkAreaChartState extends State { @override Widget build(BuildContext context) { + _chartThemeData = _updateThemeData(context); if (widget.marker != null && widget.marker!.displayMode != SparkChartMarkerDisplayMode.none) { final double padding = widget.marker!.size / 2; return RepaintBoundary( - child: Padding( - padding: EdgeInsets.all(padding), child: _getSparkAreaChart())); + child: Padding( + padding: EdgeInsets.all(padding), + child: _getSparkAreaChart(), + ), + ); } else { return RepaintBoundary(child: _getSparkAreaChart()); } @@ -657,38 +697,44 @@ class _SfSparkAreaChartState extends State { /// Method to return the spark area chart widget. Widget _getSparkAreaChart() { return SparkChartContainer( - child: Stack(children: [ - SfSparkAreaChartRenderObjectWidget( - data: widget._sparkChartDataDetails.data, - dataCount: widget._sparkChartDataDetails.dataCount, - xValueMapper: widget._sparkChartDataDetails.xValueMapper, - yValueMapper: widget._sparkChartDataDetails.yValueMapper, - isInversed: widget.isInversed, - axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, - axisLineWidth: widget.axisLineWidth, - axisLineDashArray: widget.axisLineDashArray, - highPointColor: widget.highPointColor, - lowPointColor: widget.lowPointColor, - firstPointColor: widget.firstPointColor, - lastPointColor: widget.lastPointColor, - negativePointColor: widget.negativePointColor, - color: widget.color, - borderColor: widget.borderColor, - borderWidth: widget.borderWidth, - plotBand: widget.plotBand, - marker: widget.marker, - labelDisplayMode: widget.labelDisplayMode, - labelStyle: widget.labelStyle, - themeData: _chartThemeData, - sparkChartDataDetails: widget._sparkChartDataDetails, - dataPoints: _dataPoints, - coordinatePoints: _coordinatePoints), - SparkChartTrackballRenderer( - trackball: widget.trackball, - coordinatePoints: _coordinatePoints, - dataPoints: _dataPoints, - sparkChart: widget) - ])); + child: Stack( + children: [ + SfSparkAreaChartRenderObjectWidget( + data: widget._sparkChartDataDetails.data, + dataCount: widget._sparkChartDataDetails.dataCount, + xValueMapper: widget._sparkChartDataDetails.xValueMapper, + yValueMapper: widget._sparkChartDataDetails.yValueMapper, + isInversed: widget.isInversed, + axisCrossesAt: widget.axisCrossesAt, + axisLineColor: _chartThemeData.axisLineColor, + axisLineWidth: widget.axisLineWidth, + axisLineDashArray: widget.axisLineDashArray, + highPointColor: widget.highPointColor, + lowPointColor: widget.lowPointColor, + firstPointColor: widget.firstPointColor, + lastPointColor: widget.lastPointColor, + negativePointColor: widget.negativePointColor, + color: _chartThemeData.color, + borderColor: widget.borderColor, + borderWidth: widget.borderWidth, + plotBand: widget.plotBand, + marker: widget.marker, + labelDisplayMode: widget.labelDisplayMode, + labelStyle: _chartThemeData.dataLabelTextStyle, + themeData: _chartThemeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints, + ), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + themeData: _chartThemeData, + sparkChart: widget, + ), + ], + ), + ); } } 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 d24d2792c..67e8df7e0 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 @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../plot_band.dart'; import '../renderers/spark_bar_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -12,7 +13,7 @@ import '../utils/helper.dart'; /// 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 +/// 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 @@ -36,39 +37,35 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - SfSparkBarChart( - {Key? key, - List? data, - this.plotBand, - this.borderWidth = 0, - this.borderColor, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails(data: data), - super(key: key); + SfSparkBarChart({ + Key? key, + List? data, + this.plotBand, + this.borderWidth = 0, + this.borderColor, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.labelDisplayMode, + this.labelStyle, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails(data: data), + super(key: key); /// 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. + /// of type `List`. /// /// The custom constructor uses its [dataCount], [xValueMapper] and /// [yValueMapper] to get the input data. @@ -114,43 +111,40 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - SfSparkBarChart.custom( - {Key? key, - - /// Data count for the spark charts. - int? dataCount, - - /// Specifies the x-value mapping field. - SparkChartIndexedValueMapper? xValueMapper, - - /// Specifies the y-value maping field. - SparkChartIndexedValueMapper? yValueMapper, - this.plotBand, - this.borderWidth = 2, - this.borderColor, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails( - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper), - super(key: key); + SfSparkBarChart.custom({ + Key? key, + + /// Data count for the spark charts. + int? dataCount, + + /// Specifies the x-value mapping field. + SparkChartIndexedValueMapper? xValueMapper, + + /// Specifies the y-value maping field. + SparkChartIndexedValueMapper? yValueMapper, + this.plotBand, + this.borderWidth = 2, + this.borderColor, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.labelDisplayMode, + this.labelStyle, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails( + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + ), + super(key: key); /// Inverts the axis from right to left. /// @@ -233,7 +227,7 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// 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. @@ -376,7 +370,7 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -482,8 +476,7 @@ class SfSparkBarChart extends StatefulWidget { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` - /// and font family `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -498,7 +491,7 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Enables and customizes the trackball. /// @@ -537,7 +530,7 @@ class SfSparkBarChart extends StatefulWidget { /// Represents the state class for spark bar widget. class _SfSparkBarChartState extends State { /// specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -545,6 +538,50 @@ class _SfSparkBarChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = SparkChartThemeData( + context, + ); + chartThemeData = chartThemeData.copyWith( + color: + widget.color ?? chartThemeData.color ?? effectiveChartThemeData.color, + axisLineColor: + widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: + chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: + chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: + chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + dataLabelTextStyle: theme.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(chartThemeData.dataLabelTextStyle) + .merge(widget.labelStyle), + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: + widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle), + ); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -570,7 +607,6 @@ class _SfSparkBarChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -601,40 +637,47 @@ class _SfSparkBarChartState extends State { @override Widget build(BuildContext context) { + _chartThemeData = _updateThemeData(context); return RepaintBoundary( - child: SparkChartContainer( - child: Stack(children: [ - SfSparkBarChartRenderObjectWidget( - data: widget._sparkChartDataDetails.data, - dataCount: widget._sparkChartDataDetails.dataCount, - xValueMapper: widget._sparkChartDataDetails.xValueMapper, - yValueMapper: widget._sparkChartDataDetails.yValueMapper, - isInversed: widget.isInversed, - axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, - axisLineWidth: widget.axisLineWidth, - axisLineDashArray: widget.axisLineDashArray, - highPointColor: widget.highPointColor, - lowPointColor: widget.lowPointColor, - firstPointColor: widget.firstPointColor, - lastPointColor: widget.lastPointColor, - negativePointColor: widget.negativePointColor, - color: widget.color, - borderColor: widget.borderColor, - borderWidth: widget.borderWidth, - plotBand: widget.plotBand, - labelDisplayMode: widget.labelDisplayMode, - labelStyle: widget.labelStyle, - themeData: _chartThemeData, - sparkChartDataDetails: widget._sparkChartDataDetails, - dataPoints: _dataPoints, - coordinatePoints: _coordinatePoints), - SparkChartTrackballRenderer( - trackball: widget.trackball, - coordinatePoints: _coordinatePoints, - dataPoints: _dataPoints, - sparkChart: widget, - ) - ]))); + child: SparkChartContainer( + child: Stack( + children: [ + SfSparkBarChartRenderObjectWidget( + data: widget._sparkChartDataDetails.data, + dataCount: widget._sparkChartDataDetails.dataCount, + xValueMapper: widget._sparkChartDataDetails.xValueMapper, + yValueMapper: widget._sparkChartDataDetails.yValueMapper, + isInversed: widget.isInversed, + axisCrossesAt: widget.axisCrossesAt, + axisLineColor: _chartThemeData.axisLineColor, + axisLineWidth: widget.axisLineWidth, + axisLineDashArray: widget.axisLineDashArray, + highPointColor: widget.highPointColor, + lowPointColor: widget.lowPointColor, + firstPointColor: widget.firstPointColor, + lastPointColor: widget.lastPointColor, + negativePointColor: widget.negativePointColor, + color: _chartThemeData.color, + borderColor: widget.borderColor, + borderWidth: widget.borderWidth, + plotBand: widget.plotBand, + labelDisplayMode: widget.labelDisplayMode, + labelStyle: _chartThemeData.dataLabelTextStyle, + themeData: _chartThemeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints, + ), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + themeData: _chartThemeData, + sparkChart: widget, + ), + ], + ), + ), + ); } } 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 e199cf5e7..09e8793e4 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 @@ -3,6 +3,7 @@ import 'package:syncfusion_flutter_core/theme.dart'; import '../marker.dart'; import '../plot_band.dart'; import '../renderers/spark_line_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -13,7 +14,7 @@ import '../utils/helper.dart'; /// 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 +/// 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 @@ -37,39 +38,35 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - SfSparkLineChart( - {Key? key, - List? data, - this.plotBand, - this.width = 2, - this.dashArray, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.marker, - this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails(data: data), - super(key: key); + SfSparkLineChart({ + Key? key, + List? data, + this.plotBand, + this.width = 2, + this.dashArray, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.marker, + this.labelDisplayMode, + this.labelStyle, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails(data: data), + super(key: key); /// 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. + /// 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. @@ -117,44 +114,41 @@ class SfSparkLineChart extends StatefulWidget { /// } /// ``` - SfSparkLineChart.custom( - {Key? key, - - /// Data count for the spark charts. - int? dataCount, - - /// Specifies the x-value mapping field. - SparkChartIndexedValueMapper? xValueMapper, - - /// Specifies the y-value maping field. - SparkChartIndexedValueMapper? yValueMapper, - this.plotBand, - this.width = 2, - this.dashArray, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.trackball, - this.marker, - this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12)}) - : _sparkChartDataDetails = SparkChartDataDetails( - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper), - super(key: key); + SfSparkLineChart.custom({ + Key? key, + + /// Data count for the spark charts. + int? dataCount, + + /// Specifies the x-value mapping field. + SparkChartIndexedValueMapper? xValueMapper, + + /// Specifies the y-value maping field. + SparkChartIndexedValueMapper? yValueMapper, + this.plotBand, + this.width = 2, + this.dashArray, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.trackball, + this.marker, + this.labelDisplayMode, + this.labelStyle, + }) : _sparkChartDataDetails = SparkChartDataDetails( + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + ), + super(key: key); /// Inverts the axis from right to left. /// @@ -222,7 +216,7 @@ class SfSparkLineChart extends StatefulWidget { /// Customizes the color of the axis line. /// Colors.transparent can be set to [axisLineColor] to hide the axis line. /// - /// Defaults to `Colors.black`. + /// Defaults to null. /// /// ```dart /// @override @@ -237,7 +231,7 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// 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. @@ -372,7 +366,7 @@ class SfSparkLineChart extends StatefulWidget { /// Customizes the spark line chart color. /// - /// Defaults to `Colors.blue`. + /// Defaults to null. /// /// ```dart /// @override @@ -386,7 +380,7 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -542,8 +536,7 @@ class SfSparkLineChart extends StatefulWidget { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` and font family - /// `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -559,7 +552,7 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Specifies the spark chart data details. final SparkChartDataDetails _sparkChartDataDetails; @@ -573,7 +566,7 @@ class SfSparkLineChart extends StatefulWidget { /// Represents the state class for spark line chart widget. class _SfSparkLineChartState extends State { /// Specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -581,6 +574,50 @@ class _SfSparkLineChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = SparkChartThemeData( + context, + ); + chartThemeData = chartThemeData.copyWith( + color: + widget.color ?? chartThemeData.color ?? effectiveChartThemeData.color, + axisLineColor: + widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: + chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: + chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: + chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + dataLabelTextStyle: theme.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(chartThemeData.dataLabelTextStyle) + .merge(widget.labelStyle), + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: + widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle), + ); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -606,7 +643,6 @@ class _SfSparkLineChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -641,8 +677,11 @@ class _SfSparkLineChartState extends State { widget.marker!.displayMode != SparkChartMarkerDisplayMode.none) { final double padding = widget.marker!.size / 2; return RepaintBoundary( - child: Padding( - padding: EdgeInsets.all(padding), child: _getSparkLineChart())); + child: Padding( + padding: EdgeInsets.all(padding), + child: _getSparkLineChart(), + ), + ); } else { return RepaintBoundary(child: _getSparkLineChart()); } @@ -650,39 +689,45 @@ class _SfSparkLineChartState extends State { /// Method to return the spark line chart widget. Widget _getSparkLineChart() { + _chartThemeData = _updateThemeData(context); return SparkChartContainer( - child: Stack(children: [ - SfSparkLineChartRenderObjectWidget( - data: widget._sparkChartDataDetails.data, - dataCount: widget._sparkChartDataDetails.dataCount, - xValueMapper: widget._sparkChartDataDetails.xValueMapper, - yValueMapper: widget._sparkChartDataDetails.yValueMapper, - width: widget.width, - dashArray: widget.dashArray, - isInversed: widget.isInversed, - axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, - axisLineWidth: widget.axisLineWidth, - axisLineDashArray: widget.axisLineDashArray, - highPointColor: widget.highPointColor, - lowPointColor: widget.lowPointColor, - firstPointColor: widget.firstPointColor, - lastPointColor: widget.lastPointColor, - negativePointColor: widget.negativePointColor, - color: widget.color, - plotBand: widget.plotBand, - marker: widget.marker, - labelDisplayMode: widget.labelDisplayMode, - labelStyle: widget.labelStyle, - themeData: _chartThemeData, - sparkChartDataDetails: widget._sparkChartDataDetails, - dataPoints: _dataPoints, - coordinatePoints: _coordinatePoints), - SparkChartTrackballRenderer( - trackball: widget.trackball, - coordinatePoints: _coordinatePoints, - dataPoints: _dataPoints, - ) - ])); + child: Stack( + children: [ + SfSparkLineChartRenderObjectWidget( + data: widget._sparkChartDataDetails.data, + dataCount: widget._sparkChartDataDetails.dataCount, + xValueMapper: widget._sparkChartDataDetails.xValueMapper, + yValueMapper: widget._sparkChartDataDetails.yValueMapper, + width: widget.width, + dashArray: widget.dashArray, + isInversed: widget.isInversed, + axisCrossesAt: widget.axisCrossesAt, + axisLineColor: _chartThemeData.axisLineColor, + axisLineWidth: widget.axisLineWidth, + axisLineDashArray: widget.axisLineDashArray, + highPointColor: widget.highPointColor, + lowPointColor: widget.lowPointColor, + firstPointColor: widget.firstPointColor, + lastPointColor: widget.lastPointColor, + negativePointColor: widget.negativePointColor, + color: _chartThemeData.color, + plotBand: widget.plotBand, + marker: widget.marker, + labelDisplayMode: widget.labelDisplayMode, + labelStyle: _chartThemeData.dataLabelTextStyle, + themeData: _chartThemeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints, + ), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + themeData: _chartThemeData, + ), + ], + ), + ); } } 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 d4d5272de..822673d53 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 @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../plot_band.dart'; import '../renderers/spark_win_loss_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -14,7 +15,7 @@ import '../utils/helper.dart'; /// 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 +/// 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 @@ -38,34 +39,34 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - SfSparkWinLossChart( - {Key? key, - List? data, - this.plotBand, - this.borderWidth = 0, - this.borderColor, - this.tiePointColor, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails(data: data), - super(key: key); + SfSparkWinLossChart({ + Key? key, + List? data, + this.plotBand, + this.borderWidth = 0, + this.borderColor, + this.tiePointColor, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails(data: data), + super(key: key); /// 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 `data` property of the default constructor is of type `List`. /// /// The custom constructor uses its [dataCount], [xValueMapper] and /// [yValueMapper] to get the input data. @@ -112,38 +113,39 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - SfSparkWinLossChart.custom( - {Key? key, - - /// Data count for the spark charts. - int? dataCount, - - /// Specifies the x-value mapping field. - SparkChartIndexedValueMapper? xValueMapper, - - /// Specifies the y-value maping field. - SparkChartIndexedValueMapper? yValueMapper, - this.plotBand, - this.borderWidth = 2, - this.borderColor, - this.tiePointColor, - this.color = Colors.blue, - this.isInversed = false, - this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, - this.axisLineWidth = 2, - this.axisLineDashArray, - this.highPointColor, - this.lowPointColor, - this.negativePointColor, - this.firstPointColor, - this.lastPointColor, - this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails( - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper), - super(key: key); + SfSparkWinLossChart.custom({ + Key? key, + + /// Data count for the spark charts. + int? dataCount, + + /// Specifies the x-value mapping field. + SparkChartIndexedValueMapper? xValueMapper, + + /// Specifies the y-value maping field. + SparkChartIndexedValueMapper? yValueMapper, + this.plotBand, + this.borderWidth = 2, + this.borderColor, + this.tiePointColor, + this.color, + this.isInversed = false, + this.axisCrossesAt = 0, + this.axisLineColor, + this.axisLineWidth = 2, + this.axisLineDashArray, + this.highPointColor, + this.lowPointColor, + this.negativePointColor, + this.firstPointColor, + this.lastPointColor, + this.trackball, + }) : _sparkChartDataDetails = SparkChartDataDetails( + dataCount: dataCount, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + ), + super(key: key); /// Inverts the axis from right to left. /// @@ -226,7 +228,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// 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. @@ -370,7 +372,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -499,7 +501,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// Represents the state class for spark win loss chart widget. class _SfSparkWinLossChartState extends State { /// specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -507,6 +509,46 @@ class _SfSparkWinLossChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = SparkChartThemeData( + context, + ); + chartThemeData = chartThemeData.copyWith( + color: + widget.color ?? chartThemeData.color ?? effectiveChartThemeData.color, + axisLineColor: + widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: + chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: + chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: + chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: + widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle), + ); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -532,7 +574,6 @@ class _SfSparkWinLossChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -563,38 +604,45 @@ class _SfSparkWinLossChartState extends State { @override Widget build(BuildContext context) { + _chartThemeData = _updateThemeData(context); return RepaintBoundary( - child: SparkChartContainer( - child: Stack(children: [ - SfSparkWinLossChartRenderObjectWidget( - data: widget._sparkChartDataDetails.data, - dataCount: widget._sparkChartDataDetails.dataCount, - xValueMapper: widget._sparkChartDataDetails.xValueMapper, - yValueMapper: widget._sparkChartDataDetails.yValueMapper, - isInversed: widget.isInversed, - axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, - axisLineWidth: widget.axisLineWidth, - axisLineDashArray: widget.axisLineDashArray, - highPointColor: widget.highPointColor, - lowPointColor: widget.lowPointColor, - firstPointColor: widget.firstPointColor, - lastPointColor: widget.lastPointColor, - negativePointColor: widget.negativePointColor, - color: widget.color, - tiePointColor: widget.tiePointColor, - borderColor: widget.borderColor, - borderWidth: widget.borderWidth, - plotBand: widget.plotBand, - themeData: _chartThemeData, - sparkChartDataDetails: widget._sparkChartDataDetails, - dataPoints: _dataPoints, - coordinatePoints: _coordinatePoints), - SparkChartTrackballRenderer( - trackball: widget.trackball, - coordinatePoints: _coordinatePoints, - dataPoints: _dataPoints, - ) - ]))); + child: SparkChartContainer( + child: Stack( + children: [ + SfSparkWinLossChartRenderObjectWidget( + data: widget._sparkChartDataDetails.data, + dataCount: widget._sparkChartDataDetails.dataCount, + xValueMapper: widget._sparkChartDataDetails.xValueMapper, + yValueMapper: widget._sparkChartDataDetails.yValueMapper, + isInversed: widget.isInversed, + axisCrossesAt: widget.axisCrossesAt, + axisLineColor: _chartThemeData.axisLineColor, + axisLineWidth: widget.axisLineWidth, + axisLineDashArray: widget.axisLineDashArray, + highPointColor: widget.highPointColor, + lowPointColor: widget.lowPointColor, + firstPointColor: widget.firstPointColor, + lastPointColor: widget.lastPointColor, + negativePointColor: widget.negativePointColor, + color: _chartThemeData.color, + tiePointColor: widget.tiePointColor, + borderColor: widget.borderColor, + borderWidth: widget.borderWidth, + plotBand: widget.plotBand, + themeData: _chartThemeData, + sparkChartDataDetails: widget._sparkChartDataDetails, + dataPoints: _dataPoints, + coordinatePoints: _coordinatePoints, + ), + SparkChartTrackballRenderer( + trackball: widget.trackball, + coordinatePoints: _coordinatePoints, + dataPoints: _dataPoints, + themeData: _chartThemeData, + ), + ], + ), + ), + ); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/theme.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/theme.dart new file mode 100644 index 000000000..f57e5b454 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/theme.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// Holds the value of [SfSparkChartThemeData] color properties for +/// material 2 and material 3 theme based on the brightness. +class SparkChartThemeData extends SfSparkChartThemeData { + /// Creating an argument constructor of SparkChartThemeData class. + SparkChartThemeData(this.context); + + /// Specifies the build context of the chart widgets. + final BuildContext context; + + /// Specifies the material app color scheme based on the brightness. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get color => + colorScheme.useMaterial3 + ? colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(150, 60, 112, 1) + : const Color.fromRGBO(77, 170, 255, 1) + : Colors.blue; + + @override + Color? get axisLineColor => + colorScheme.useMaterial3 + ? colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(73, 69, 79, 1) + : const Color.fromRGBO(202, 196, 208, 1) + : Colors.black; + + @override + Color? get markerFillColor => colorScheme.surface[150]; + + @override + Color? get dataLabelBackgroundColor => colorScheme.surface[255]; + + @override + Color? get tooltipColor => colorScheme.inverseSurface[258]; + + @override + Color? get trackballLineColor => colorScheme.onSurfaceVariant[79]; + + @override + Color? get tooltipLabelColor => colorScheme.onInverseSurface[256]; +} 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 b88f091c3..2ce4ac823 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 @@ -30,23 +30,20 @@ class SparkChartTrackball { /// ); /// } /// ``` - const SparkChartTrackball( - {this.width = 2, - this.color, - this.dashArray, - this.activationMode = SparkChartActivationMode.tap, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), - this.tooltipFormatter, - this.backgroundColor, - this.shouldAlwaysShow = false, - this.hideDelay = 0, - this.borderColor, - this.borderWidth = 0, - this.borderRadius = const BorderRadius.all(Radius.circular(5))}); + const SparkChartTrackball({ + this.width = 2, + this.color, + this.dashArray, + this.activationMode = SparkChartActivationMode.tap, + this.labelStyle, + this.tooltipFormatter, + this.backgroundColor, + this.shouldAlwaysShow = false, + this.hideDelay = 0, + this.borderColor, + this.borderWidth = 0, + this.borderRadius = const BorderRadius.all(Radius.circular(5)), + }); /// Customizes the width of the trackball line. /// @@ -141,8 +138,7 @@ class SparkChartTrackball { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` and - /// font family `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -158,7 +154,7 @@ class SparkChartTrackball { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Customizes the background color of the trackball tooltip. /// The color is set based on the current application theme, if its value is @@ -343,7 +339,7 @@ class SparkChartTrackball { width, color!, activationMode, - labelStyle, + labelStyle!, backgroundColor!, borderColor!, borderWidth, @@ -351,8 +347,8 @@ class SparkChartTrackball { shouldAlwaysShow, hideDelay, borderRadius, - tooltipFormatter! + tooltipFormatter!, ]; - return hashList(values); + return Object.hashAll(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 07bd76454..3add1e0cb 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 @@ -13,13 +13,14 @@ import 'spark_chart_trackball.dart'; @immutable class SparkChartTrackballRenderer extends StatefulWidget { /// Creates the trackball renderer. - const SparkChartTrackballRenderer( - {Key? key, - this.trackball, - this.coordinatePoints, - this.dataPoints, - this.sparkChart}) - : super(key: key); + const SparkChartTrackballRenderer({ + Key? key, + this.trackball, + this.coordinatePoints, + this.dataPoints, + this.themeData, + this.sparkChart, + }) : super(key: key); /// Specifies the spark chart trackball. final SparkChartTrackball? trackball; @@ -30,20 +31,23 @@ class SparkChartTrackballRenderer extends StatefulWidget { /// Specifies the spark chart data points. final List? dataPoints; - /// Specifie the spark chart widget. + /// Specifies the theme of the chart. + final SfSparkChartThemeData? themeData; + + /// Specifies the spark chart widget. final Widget? sparkChart; @override State createState() { - return _SparckChartTrackballRendererState(); + return _SparkChartTrackballRendererState(); } } /// Represents the state class of spark chart trackball renderer. -class _SparckChartTrackballRendererState +class _SparkChartTrackballRendererState extends State { /// Holds the trackball repaint notifier. - ValueNotifier? _trackballRepaintNotifier; + late ValueNotifier? _trackballRepaintNotifier; /// Specifies whether the track ball is enabled. bool _isTrackballEnabled = false; @@ -55,7 +59,7 @@ class _SparckChartTrackballRendererState Rect? _areaBounds; /// Specifies the local rect. - Rect? _localBounds; + late Rect? _localBounds; /// Specifies the nearest point index. int? _currentIndex; @@ -64,7 +68,7 @@ class _SparckChartTrackballRendererState Offset? _globalPosition; /// Specifies the theme of the chart. - SfChartThemeData? _themeData; + SfSparkChartThemeData? _themeData; /// Specifies the current data point. SparkChartPoint? _currentDataPoint; @@ -93,7 +97,7 @@ class _SparckChartTrackballRendererState @override void didChangeDependencies() { - _themeData = SfChartTheme.of(context); + _themeData = widget.themeData; super.didChangeDependencies(); } @@ -109,46 +113,83 @@ class _SparckChartTrackballRendererState @override Widget build(BuildContext context) { return MouseRegion( - // 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: (PointerExitEvent event) => _hide(), - child: Listener( - onPointerUp: (PointerUpEvent event) => _hide(), - child: GestureDetector( - onVerticalDragStart: (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) + // 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: (PointerExitEvent event) => _hide(), + child: Listener( + onPointerUp: (PointerUpEvent event) => _hide(), + child: GestureDetector( + 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) + ? (DragUpdateDetails details) => + _updateDragValue(context, details.globalPosition) + : null, + onHorizontalDragStart: + (widget.trackball != null && + widget.trackball!.activationMode != + SparkChartActivationMode.doubleTap) ? (DragStartDetails details) => _updateDragValue(context, details.globalPosition) : null, - onVerticalDragUpdate: (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) + onHorizontalDragUpdate: + (widget.trackball != null && + widget.trackball!.activationMode != + SparkChartActivationMode.doubleTap) ? (DragUpdateDetails details) => _updateDragValue(context, details.globalPosition) : null, - onHorizontalDragStart: - (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) - ? (DragStartDetails details) => - _updateDragValue(context, details.globalPosition) - : null, - onHorizontalDragUpdate: - (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) => _enableAndUpdateTrackball(context, details.globalPosition) : null, - onDoubleTap: (widget.trackball != null && widget.trackball!.activationMode == SparkChartActivationMode.doubleTap) ? () => _updateDragValue(context, _globalPosition!) : null, - child: _addTrackballPainter()), - )); + 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. @@ -160,10 +201,12 @@ class _SparckChartTrackballRendererState _trackballRepaintNotifier!.value++; _timer = Timer( - Duration(milliseconds: widget.trackball!.hideDelay.toInt()), () { - _trackballRepaintNotifier!.value++; - _endTrackballDragging(); - }); + Duration(milliseconds: widget.trackball!.hideDelay.toInt()), + () { + _trackballRepaintNotifier!.value++; + _endTrackballDragging(); + }, + ); } } @@ -175,9 +218,14 @@ class _SparckChartTrackballRendererState return Container( child: RepaintBoundary( child: CustomPaint( - painter: TrackballPainter(_trackballRepaintNotifier!, - _isTrackballEnabled, widget.trackball, this), - size: Size(constraints.maxWidth, constraints.maxHeight)), + painter: TrackballPainter( + _trackballRepaintNotifier!, + _isTrackballEnabled, + widget.trackball, + this, + ), + size: Size(constraints.maxWidth, constraints.maxHeight), + ), ), ); }, @@ -186,13 +234,21 @@ class _SparckChartTrackballRendererState /// Method to enable the trackball behavior. void _enableTrackballBehavior(BuildContext context, Offset globalPosition) { - final RenderBox renderBox = context.findRenderObject() as RenderBox; + 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, - renderBoxSize.width, renderBoxSize.height); - _localBounds = - Rect.fromLTWH(0, 0, renderBoxSize.width, renderBoxSize.height); + _areaBounds = Rect.fromLTWH( + renderBoxOffset.dx, + renderBoxOffset.dy, + renderBoxSize.width, + renderBoxSize.height, + ); + _localBounds = Rect.fromLTWH( + 0, + 0, + renderBoxSize.width, + renderBoxSize.height, + ); _globalPosition = globalPosition; _touchPosition = renderBox.globalToLocal(globalPosition); if (_localBounds!.contains(_touchPosition!)) { @@ -219,7 +275,7 @@ class _SparckChartTrackballRendererState _isTop = false; int? index; if (_isTrackballEnabled) { - final RenderBox renderBox = context.findRenderObject() as RenderBox; + final RenderBox renderBox = context.findRenderObject()! as RenderBox; _touchPosition = renderBox.globalToLocal(globalPosition); final double currentXPoint = _touchPosition!.dx; double xPoint; @@ -259,9 +315,13 @@ class _SparckChartTrackballRendererState /// Represnts the painter to render the trackball. class TrackballPainter extends CustomPainter { /// Creates the painter to render the trackball. - TrackballPainter(ValueNotifier notifier, this._isRepaint, - this._trackball, this._rendererState) - : super(repaint: notifier); + TrackballPainter( + ValueNotifier notifier, + this._isRepaint, + this._trackball, + // ignore: library_private_types_in_public_api + this._rendererState, + ) : super(repaint: notifier); /// Specifies whether to repaint the series. final bool _isRepaint; @@ -270,7 +330,7 @@ class TrackballPainter extends CustomPainter { final SparkChartTrackball? _trackball; /// Specifies the trackball renderer state. - final _SparckChartTrackballRendererState _rendererState; + final _SparkChartTrackballRendererState _rendererState; @override void paint(Canvas canvas, Size size) { @@ -284,10 +344,14 @@ class TrackballPainter extends CustomPainter { /// Method to render the trackball tooltip. void _renderTrackballTooltip( - Canvas canvas, Offset? screenPoint, num index, Size size) { + Canvas canvas, + Offset? screenPoint, + num index, + Size size, + ) { Offset labelOffset = screenPoint!; final String dataLabel = _getTrackballLabel(); - final TextStyle labelStyle = _getTrackballLabelStyle(); + final TextStyle labelStyle = _rendererState._themeData!.trackballTextStyle!; final Size textSize = getTextSize(dataLabel, labelStyle); final Rect areaBounds = _rendererState._areaBounds!; BorderRadius borderRadius = _trackball!.borderRadius; @@ -299,8 +363,12 @@ class TrackballPainter extends CustomPainter { final double textWidth = textSize.height / 2; borderRadius = _getBorderRadius(borderRadius, textWidth); - Rect labelRect = Rect.fromLTWH(screenPoint.dx, screenPoint.dy, - textSize.width + 15, textSize.height + 10); + Rect labelRect = Rect.fromLTWH( + screenPoint.dx, + screenPoint.dy, + textSize.width + 15, + textSize.height + 10, + ); const double tooltipPadding = 5; const double pointerWidth = 5; const double pointerLength = 7; @@ -317,7 +385,8 @@ class TrackballPainter extends CustomPainter { xPosition = screenPoint.dx + pointerLength + tooltipPadding; yPosition = screenPoint.dy - labelRect.height / 2; if ((xPosition + labelRect.width) > totalWidth) { - xPosition = (xPosition - labelRect.width - 2 * tooltipPadding) - + xPosition = + (xPosition - labelRect.width - 2 * tooltipPadding) - 2 * pointerLength; isRight = true; } else if (xPosition >= totalWidth) { @@ -341,29 +410,46 @@ class TrackballPainter extends CustomPainter { screenPoint.dy - labelRect.height - padding - pointerLength; } else { isBottom = true; - yPosition = (screenPoint.dy > 0 ? screenPoint.dy : 0) + + 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) + + yPosition = + (screenPoint.dy > 0 ? screenPoint.dy : 0) + pointerLength + padding; } } - xPosition = xPosition < 0 - ? 0 - : (tooltipRight > totalWidth - ? totalWidth - labelRect.width - : xPosition); + xPosition = + xPosition < 0 + ? 0 + : (tooltipRight > totalWidth + ? totalWidth - labelRect.width + : xPosition); } labelRect = Rect.fromLTWH( - xPosition, yPosition, labelRect.width, labelRect.height); - _drawTrackballRect(canvas, textSize, labelRect, isRight, borderRadius, - pointerWidth, pointerLength, screenPoint, isTop, isBottom); + xPosition, + yPosition, + labelRect.width, + labelRect.height, + ); + _drawTrackballRect( + canvas, + textSize, + labelRect, + isRight, + borderRadius, + pointerWidth, + pointerLength, + screenPoint, + isTop, + isBottom, + ); final double labelOffsetX = (labelRect.left + labelRect.width / 2) - textSize.width / 2; @@ -383,57 +469,54 @@ class TrackballPainter extends CustomPainter { if (_trackball!.tooltipFormatter != null) { final TooltipFormatterDetails tooltipFormatterDetails = TooltipFormatterDetails( - x: currentPoint.actualX, y: currentPoint.y, label: dataLabel); - dataLabel = _trackball!.tooltipFormatter!(tooltipFormatterDetails); + x: currentPoint.actualX, + y: currentPoint.y, + label: dataLabel, + ); + dataLabel = _trackball.tooltipFormatter!(tooltipFormatterDetails); } return dataLabel; } - /// Method to return the trackball label style. - TextStyle _getTrackballLabelStyle() { - 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))); - } - /// Method to get the border radius. BorderRadius _getBorderRadius(BorderRadius borderRadius, double value) { return BorderRadius.only( - topLeft: borderRadius.topLeft.x > value - ? BorderRadius.circular(value).topLeft - : borderRadius.topLeft, - topRight: borderRadius.topRight.x > value - ? BorderRadius.circular(value).topRight - : borderRadius.topRight, - bottomLeft: borderRadius.bottomLeft.x > value - ? BorderRadius.circular(value).bottomLeft - : borderRadius.bottomLeft, - bottomRight: borderRadius.bottomRight.x > value - ? BorderRadius.circular(value).bottomRight - : borderRadius.bottomRight); + topLeft: + borderRadius.topLeft.x > value + ? BorderRadius.circular(value).topLeft + : borderRadius.topLeft, + topRight: + borderRadius.topRight.x > value + ? BorderRadius.circular(value).topRight + : borderRadius.topRight, + bottomLeft: + borderRadius.bottomLeft.x > value + ? BorderRadius.circular(value).bottomLeft + : borderRadius.bottomLeft, + bottomRight: + borderRadius.bottomRight.x > value + ? BorderRadius.circular(value).bottomRight + : borderRadius.bottomRight, + ); } /// Method to draw the trackball rect. void _drawTrackballRect( - Canvas canvas, - Size textSize, - Rect rect, - bool isRight, - BorderRadius borderRadius, - double pointerWidth, - double pointerLength, - Offset screenPoint, - bool isTop, - bool isBottom) { - final Color backgroundColor = - _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; + Canvas canvas, + Size textSize, + Rect rect, + bool isRight, + BorderRadius borderRadius, + double pointerWidth, + double pointerLength, + Offset screenPoint, + bool isTop, + bool isBottom, + ) { + final Color backgroundColor = _rendererState._themeData!.tooltipColor!; + final Paint paint = + Paint()..color = _trackball!.backgroundColor ?? backgroundColor; final Path path = Path(); if (!isTop) { if (isRight) { @@ -453,7 +536,9 @@ class TrackballPainter extends CustomPainter { path.moveTo(rect.left + rect.width / 2 - pointerWidth, yValue); path.lineTo(rect.left + rect.width / 2 + pointerWidth, yValue); path.lineTo( - screenPoint.dx, yValue + (isBottom ? -pointerLength : pointerLength)); + screenPoint.dx, + yValue + (isBottom ? -pointerLength : pointerLength), + ); path.lineTo(rect.left + rect.width / 2 - pointerWidth, yValue); } @@ -468,31 +553,36 @@ class TrackballPainter extends CustomPainter { path.addRRect(roundedRect); canvas.drawPath(path, paint); - if (_trackball!.borderColor != null && - _trackball!.borderColor != Colors.transparent && - _trackball!.borderWidth > 0) { - final Paint borderPaint = Paint() - ..color = _trackball!.borderColor! - ..strokeWidth = _trackball!.borderWidth - ..style = PaintingStyle.stroke; + if (_trackball.borderColor != null && + _trackball.borderColor != Colors.transparent && + _trackball.borderWidth > 0) { + final Paint borderPaint = + Paint() + ..color = _trackball.borderColor! + ..strokeWidth = _trackball.borderWidth + ..style = PaintingStyle.stroke; canvas.drawPath(path, borderPaint); } } /// Method to render the trackball line. void _drawTrackLine( - Canvas canvas, Rect areaBounds, Offset screenPoint, Size size) { - final Paint paint = Paint() - ..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 - ..style = PaintingStyle.stroke; + Canvas canvas, + Rect areaBounds, + Offset screenPoint, + Size size, + ) { + final Paint paint = + Paint() + ..color = + (_trackball!.color ?? + _rendererState._themeData!.trackballLineColor)! + ..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 fee04141f..08e89e343 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart @@ -6,8 +6,8 @@ typedef SparkChartIndexedValueMapper = R Function(int index); /// 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); +typedef SparkChartTooltipCallback = + String Function(TooltipFormatterDetails details); /// Displays the marker on the spark chart widget in different modes. enum SparkChartMarkerDisplayMode { @@ -33,7 +33,7 @@ enum SparkChartMarkerDisplayMode { /// SparkChartMarkerDisplayMode.last allows displaying marker only on the last data /// points in the spark chart widget. - last + last, } /// Displays the marker in different types of shape. @@ -52,7 +52,7 @@ enum SparkChartMarkerShape { /// SparkChartMarkerShape.invertedTriangle displays the marker in an inverted /// triangle shape - invertedTriangle + invertedTriangle, } /// Activates the trackball in different gestures. @@ -93,7 +93,7 @@ enum SparkChartLabelDisplayMode { /// SparkChartLabelDisplayMode.first allows displaying data label only on the /// last data points in the spark chart widget. - last + last, } /// Passes as the argument in the `tooltipFormatter` callback of trackball. 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 0a2570723..c8bb9a389 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart @@ -13,7 +13,9 @@ import 'enum.dart'; Color getSaturationColor(Color color) { Color saturationColor; final num contrast = - ((color.red * 299 + color.green * 587 + color.blue * 114) / 1000).round(); + (((color.r * 255) * 299 + (color.g * 255) * 587 + (color.b * 255) * 114) / + 1000) + .round(); saturationColor = contrast >= 128 ? Colors.black : Colors.white; return saturationColor; } @@ -25,13 +27,15 @@ Size getTextSize(String textValue, TextStyle textStyle) { textAlign: TextAlign.center, textDirection: TextDirection.ltr, text: TextSpan( - text: textValue, - style: TextStyle( - color: textStyle.color, - fontSize: textStyle.fontSize, - fontFamily: textStyle.fontFamily, - fontStyle: textStyle.fontStyle, - fontWeight: textStyle.fontWeight)), + text: textValue, + style: TextStyle( + color: textStyle.color, + fontSize: textStyle.fontSize, + fontFamily: textStyle.fontFamily, + fontStyle: textStyle.fontStyle, + fontWeight: textStyle.fontWeight, + ), + ), ); textPainter.layout(); size = Size(textPainter.width, textPainter.height); @@ -43,10 +47,11 @@ void drawText(Canvas canvas, String dataLabel, Offset point, TextStyle style) { 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.toInt()); + text: span, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + maxLines: maxLines.toInt(), + ); tp.layout(); canvas.save(); canvas.translate(point.dx, point.dy); @@ -57,8 +62,12 @@ void drawText(Canvas canvas, String dataLabel, Offset point, TextStyle style) { /// Draw the dashed line. void drawDashedPath( - Canvas canvas, Paint paint, Offset moveToPoint, Offset lineToPoint, - [List? dashArray]) { + Canvas canvas, + Paint paint, + Offset moveToPoint, + Offset lineToPoint, [ + List? dashArray, +]) { bool even = false; final Path path = Path(); path.moveTo(moveToPoint.dx, moveToPoint.dy); @@ -72,11 +81,9 @@ void drawDashedPath( } if (even == false) { canvas.drawPath( - _dashPath( - path, - dashArray: DashArrayIntervalList(dashArray), - )!, - paint); + _dashPath(path, dashArray: DashArrayIntervalList(dashArray))!, + paint, + ); } } else { canvas.drawPath(path, paint); @@ -91,18 +98,20 @@ Path? _dashPath( if (source == null) { return null; } - const double intialValue = 0.0; + const double initialValue = 0.0; final Path path = Path(); double distance, length; bool draw; for (final PathMetric measurePath in source.computeMetrics()) { - distance = intialValue; + distance = initialValue; draw = true; while (distance < measurePath.length) { length = dashArray!.next; if (draw) { path.addPath( - measurePath.extractPath(distance, distance + length), Offset.zero); + measurePath.extractPath(distance, distance + length), + Offset.zero, + ); } distance += length; draw = !draw; @@ -114,16 +123,18 @@ Path? _dashPath( /// Returns the Rectangle marker type. Path drawRectangle(Path path, double x, double y, double size) { path.addRect( - Rect.fromLTRB(x - size / 2, y - size / 2, x + size / 2, y + size / 2)); + Rect.fromLTRB(x - size / 2, y - size / 2, x + size / 2, y + size / 2), + ); return path; } /// Returns the circle marker type. Path drawCircle(Path path, double x, double y, double size) { path.addArc( - Rect.fromLTRB(x - size / 2, y - size / 2, x + size / 2, y + size / 2), - 0.0, - 2 * math.pi); + Rect.fromLTRB(x - size / 2, y - size / 2, x + size / 2, y + size / 2), + 0.0, + 2 * math.pi, + ); return path; } @@ -161,8 +172,9 @@ Path drawTriangle(Path path, double x, double y, double size) { /// Method to find the sorted spark chart points. List sortSparkChartPoints(List dataPoints) { - final List sortedPoints = - List.from(dataPoints); + final List sortedPoints = List.from( + dataPoints, + ); sortedPoints.sort((SparkChartPoint firstPoint, SparkChartPoint secondPoint) { firstPoint.x.compareTo(secondPoint.x); if (firstPoint.x < secondPoint.x == true) { @@ -178,7 +190,7 @@ List sortSparkChartPoints(List dataPoints) { } /// Method to find the sorted visible points. -List sortScreenCoordiantePoints(List coordinatePoints) { +List sortScreenCoordinatePoints(List coordinatePoints) { coordinatePoints.sort((Offset firstPoint, Offset secondPoint) { firstPoint.dx.compareTo(secondPoint.dx); if (firstPoint.dx < secondPoint.dx) { @@ -195,22 +207,25 @@ List sortScreenCoordiantePoints(List coordinatePoints) { /// Converts the provided data point to visible point for rendering. Offset transformToCoordinatePoint( - double minX, - double maxX, - double minY, - double maxY, - double diffX, - double diffY, - Size size, - double x, - double y, - int dataLength) { - final double visibleYPoint = (minY != maxY && dataLength != 1) - ? (size.height * (1 - ((y - minY) / diffY))).roundToDouble() - : 0; - final double visibleXPoint = (minX != maxX) - ? (size.width * ((x - minX) / diffX)).roundToDouble() - : size.width / 2; + double minX, + double maxX, + double minY, + double maxY, + double diffX, + double diffY, + Size size, + double x, + double y, + int dataLength, +) { + final double visibleYPoint = + (minY != maxY && dataLength != 1) + ? (size.height * (1 - ((y - minY) / diffY))).roundToDouble() + : 0; + final double visibleXPoint = + (minX != maxX) + ? (size.width * ((x - minX) / diffX)).roundToDouble() + : size.width / 2; return Offset(visibleXPoint, visibleYPoint); } @@ -271,7 +286,7 @@ class SparkChartPoint { /// Specifies the y point. num y; - /// Specifes the pixel location of data label. + /// Specifies the pixel location of data label. Offset? dataLabelOffset; /// Specifies the x label. @@ -290,10 +305,14 @@ class SparkChartPoint { /// Represents the spark chart data details. class SparkChartDataDetails { /// Creates the spark chart container box. - SparkChartDataDetails( - {this.data, this.dataCount, this.xValueMapper, this.yValueMapper}); + SparkChartDataDetails({ + this.data, + this.dataCount, + this.xValueMapper, + this.yValueMapper, + }); - /// Speficies the list of spark chart data. + /// Specifies the list of spark chart data. final List? data; /// Specifies the spark chart data count. @@ -309,7 +328,7 @@ class SparkChartDataDetails { /// Represents the spark chart container. class SparkChartContainer extends SingleChildRenderObjectWidget { /// Creates the spark chart container. - const SparkChartContainer({Widget? child}) : super(child: child); + const SparkChartContainer({super.key, super.child}); @override RenderObject createRenderObject(BuildContext context) { @@ -328,14 +347,8 @@ class _SparKChartContainerBox extends RenderShiftedBox { size = getLayoutSize(constraints, context); child!.layout( - BoxConstraints( - minHeight: 0.0, - maxHeight: size.height, - minWidth: 0.0, - maxWidth: size.width, - ), - parentUsesSize: - false); // True- Parent widget recomputes again respect to + BoxConstraints(maxHeight: size.height, maxWidth: size.width), + ); // True- Parent widget recomputes again respect to // every build of child widget, // False- Parent widget not rebuild respect to child widget build } @@ -349,7 +362,10 @@ class _SparKChartContainerBox extends RenderShiftedBox { /// To draw the respective shapes for marker. Path getMarkerShapes( - SparkChartMarkerShape markerShape, Offset position, double size) { + SparkChartMarkerShape markerShape, + Offset position, + double size, +) { final Path path = Path(); switch (markerShape) { case SparkChartMarkerShape.circle: @@ -379,9 +395,6 @@ Path getMarkerShapes( drawTriangle(path, position.dx, position.dy, size); } break; - - default: - break; } return path; @@ -389,55 +402,59 @@ Path getMarkerShapes( /// To render the marker for line and area series. void renderMarker( - Canvas canvas, - Offset offset, - SparkChartMarker marker, - List coordinatePoints, - List dataPoints, - Color color, - String type, - num highPoint, - num lowPoint, - double axisCrossesAt, - SfChartThemeData themeData, - Color? lowPointColor, - Color? highPointColor, - Color? negativePointColor, - Color? firstPointColor, - Color? lastPointColor) { + Canvas canvas, + Offset offset, + SparkChartMarker marker, + List coordinatePoints, + List dataPoints, + Color color, + String type, + num highPoint, + num lowPoint, + double axisCrossesAt, + SfSparkChartThemeData themeData, + 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 - ..style = PaintingStyle.stroke - ..strokeWidth = marker.borderWidth; + final Paint strokePaint = + Paint() + ..color = marker.borderColor ?? color + ..style = PaintingStyle.stroke + ..strokeWidth = marker.borderWidth; final SparkChartMarkerShape markerShape = marker.shape; final double markerSize = marker.size; final SparkChartMarkerDisplayMode markerDisplayMode = marker.displayMode; - final Color themeBasedColor = - themeData.brightness == Brightness.light ? Colors.white : Colors.black; + final Color themeBasedColor = themeData.markerFillColor!; Path markerPath; final Offset lastMarkerOffset = Offset( - offset.dx + - coordinatePoints[type == 'Line' - ? coordinatePoints.length - 1 - : coordinatePoints.length - 2] - .dx, - offset.dy + - coordinatePoints[type == 'Line' - ? coordinatePoints.length - 1 - : coordinatePoints.length - 2] - .dy); + offset.dx + + coordinatePoints[type == 'Line' + ? coordinatePoints.length - 1 + : coordinatePoints.length - 2] + .dx, + offset.dy + + coordinatePoints[type == 'Line' + ? coordinatePoints.length - 1 + : coordinatePoints.length - 2] + .dy, + ); final Offset firstMarkerOffset = Offset( - offset.dx + coordinatePoints[type == 'Line' ? 0 : 1].dx, - offset.dy + coordinatePoints[type == 'Line' ? 0 : 1].dy); + offset.dx + coordinatePoints[type == 'Line' ? 0 : 1].dx, + offset.dy + coordinatePoints[type == 'Line' ? 0 : 1].dy, + ); switch (markerDisplayMode) { case SparkChartMarkerDisplayMode.all: { - final int length = type == 'Line' - ? coordinatePoints.length - : coordinatePoints.length - 1; + final int length = + type == 'Line' + ? coordinatePoints.length + : coordinatePoints.length - 1; int i = type == 'Line' ? 0 : 1; for (i = i; i < length; i++) { fillPaint.color = marker.color ?? themeBasedColor; @@ -466,10 +483,13 @@ void renderMarker( } markerPath = getMarkerShapes( - markerShape, - Offset(offset.dx + coordinatePoints[i].dx, - offset.dy + coordinatePoints[i].dy), - markerSize); + markerShape, + Offset( + offset.dx + coordinatePoints[i].dx, + offset.dy + coordinatePoints[i].dy, + ), + markerSize, + ); canvas.drawPath(markerPath, fillPaint); canvas.drawPath(markerPath, strokePaint); } @@ -485,8 +505,11 @@ void renderMarker( dataPoints[type == 'Line' ? 0 : 1].y < 0) { fillPaint.color = negativePointColor; } - markerPath = - getMarkerShapes(markerShape, firstMarkerOffset, markerSize); + markerPath = getMarkerShapes( + markerShape, + firstMarkerOffset, + markerSize, + ); canvas.drawPath(markerPath, fillPaint); canvas.drawPath(markerPath, strokePaint); } @@ -519,9 +542,10 @@ void renderMarker( ? fillPaint.color = lowPointColor : fillPaint.color = fillPaint.color; - final int length = type == 'Line' - ? coordinatePoints.length - : coordinatePoints.length - 1; + final int length = + type == 'Line' + ? coordinatePoints.length + : coordinatePoints.length - 1; final int index = type == 'Line' ? 0 : 1; for (int j = index; j < length; j++) { @@ -532,10 +556,13 @@ void renderMarker( } if (highPoint == coordinatePoints[j].dy) { markerPath = getMarkerShapes( - markerShape, - Offset(offset.dx + coordinatePoints[j].dx, - offset.dy + coordinatePoints[j].dy), - markerSize); + markerShape, + Offset( + offset.dx + coordinatePoints[j].dx, + offset.dy + coordinatePoints[j].dy, + ), + markerSize, + ); canvas.drawPath(markerPath, fillPaint); canvas.drawPath(markerPath, strokePaint); } @@ -550,9 +577,10 @@ void renderMarker( ? fillPaint.color = highPointColor : fillPaint.color = fillPaint.color; - final int length = type == 'Line' - ? coordinatePoints.length - : coordinatePoints.length - 1; + final int length = + type == 'Line' + ? coordinatePoints.length + : coordinatePoints.length - 1; final int index = type == 'Line' ? 0 : 1; for (int j = index; j < length; j++) { if (negativePointColor != null && @@ -562,10 +590,13 @@ void renderMarker( } if (lowPoint == coordinatePoints[j].dy) { markerPath = getMarkerShapes( - markerShape, - Offset(offset.dx + coordinatePoints[j].dx, - offset.dy + coordinatePoints[j].dy), - markerSize); + markerShape, + Offset( + offset.dx + coordinatePoints[j].dx, + offset.dy + coordinatePoints[j].dy, + ), + markerSize, + ); canvas.drawPath(markerPath, fillPaint); canvas.drawPath(markerPath, strokePaint); } @@ -579,38 +610,31 @@ void renderMarker( } Color _getDataLabelSaturationColor( - Offset dataLabelOffset, - Offset coordinateOffset, - SfChartThemeData theme, - Offset offset, - Color seriesColor, - String type, - [Rect? segment, - num? yValue]) { + Offset dataLabelOffset, + Offset coordinateOffset, + SfSparkChartThemeData theme, + Offset offset, + Color seriesColor, + String type, [ + Rect? segment, + num? yValue, +]) { Color color; if (type == 'Area') { dataLabelOffset.dy >= (offset.dy + coordinateOffset.dy) ? color = seriesColor - : color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + : color = theme.dataLabelBackgroundColor!; } else if (type == 'Line') { - color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + color = theme.dataLabelBackgroundColor!; } else { yValue! > 0 ? dataLabelOffset.dy > (segment!.top + offset.dy) ? color = seriesColor - : color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black + : color = theme.dataLabelBackgroundColor! : dataLabelOffset.dy < (segment!.top + offset.dy) - ? color = seriesColor - : color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + ? color = seriesColor + : color = theme.dataLabelBackgroundColor!; } color = getSaturationColor(color); @@ -619,82 +643,102 @@ Color _getDataLabelSaturationColor( } TextStyle _getTextStyle( - TextStyle labelStyle, - Offset dataLabelOffset, - Offset coordinateOffset, - Offset offset, - SfChartThemeData theme, - Color seriesColor, - String type, - [Rect? segment, - num? yValue]) { + TextStyle labelStyle, + Offset dataLabelOffset, + Offset coordinateOffset, + Offset offset, + SfSparkChartThemeData theme, + Color seriesColor, + String type, [ + Rect? segment, + num? yValue, +]) { final TextStyle font = labelStyle; - final Color fontColor = font.color ?? - _getDataLabelSaturationColor(dataLabelOffset, coordinateOffset, theme, - offset, seriesColor, type, segment, yValue); - - final TextStyle _textStyle = TextStyle( - color: fontColor, - fontFamily: font.fontFamily, - fontSize: font.fontSize, - fontStyle: font.fontStyle, - fontWeight: font.fontWeight, - inherit: font.inherit, - backgroundColor: font.backgroundColor, - letterSpacing: font.letterSpacing, - wordSpacing: font.wordSpacing, - textBaseline: font.textBaseline, - height: font.height, - locale: font.locale, - foreground: font.foreground, - background: font.background, - shadows: font.shadows, - fontFeatures: font.fontFeatures, - decoration: font.decoration, - decorationColor: font.decorationColor, - decorationStyle: font.decorationStyle, - decorationThickness: font.decorationThickness, - debugLabel: font.debugLabel, - fontFamilyFallback: font.fontFamilyFallback); - - return _textStyle; + final Color fontColor = + font.color != Colors.transparent + ? font.color! + : _getDataLabelSaturationColor( + dataLabelOffset, + coordinateOffset, + theme, + offset, + seriesColor, + type, + segment, + yValue, + ); + + final TextStyle textStyle = TextStyle( + color: fontColor, + fontFamily: font.fontFamily, + fontSize: font.fontSize, + fontStyle: font.fontStyle, + fontWeight: font.fontWeight, + inherit: font.inherit, + backgroundColor: font.backgroundColor, + letterSpacing: font.letterSpacing, + wordSpacing: font.wordSpacing, + textBaseline: font.textBaseline, + height: font.height, + locale: font.locale, + foreground: font.foreground, + background: font.background, + shadows: font.shadows, + fontFeatures: font.fontFeatures, + decoration: font.decoration, + decorationColor: font.decorationColor, + decorationStyle: font.decorationStyle, + decorationThickness: font.decorationThickness, + debugLabel: font.debugLabel, + fontFamilyFallback: font.fontFamilyFallback, + ); + + return textStyle; } /// To render the data label. void renderDataLabel( - Canvas canvas, - List dataLabels, - List dataPoints, - List coordinatePoints, - TextStyle labelStyle, - SparkChartLabelDisplayMode labelDisplayMode, - String type, - SfChartThemeData theme, - Offset offset, - Color seriesColor, - num highPoint, - num lowPoint, - [List? segments]) { - TextStyle _textStyle; + Canvas canvas, + List dataLabels, + List dataPoints, + List coordinatePoints, + TextStyle labelStyle, + SparkChartLabelDisplayMode labelDisplayMode, + String type, + SfSparkChartThemeData theme, + Offset offset, + Color seriesColor, + num highPoint, + num lowPoint, [ + List? segments, +]) { + TextStyle textStyle; switch (labelDisplayMode) { case SparkChartLabelDisplayMode.all: { - for (int i = type == 'Area' ? 1 : 0; - type == 'Area' ? i < dataPoints.length - 1 : i < dataPoints.length; - i++) { - _textStyle = _getTextStyle( - labelStyle, - dataPoints[i].dataLabelOffset!, - coordinatePoints[i], - offset, - theme, - type == 'Bar' ? dataPoints[i].color! : seriesColor, - type, - type == 'Bar' ? segments![i] : null, - dataPoints[i].y); - drawText(canvas, dataLabels[i], dataPoints[i].dataLabelOffset!, - _textStyle); + for ( + int i = type == 'Area' ? 1 : 0; + type == 'Area' ? i < dataPoints.length - 1 : i < dataPoints.length; + i++ + ) { + textStyle = _getTextStyle( + labelStyle, + dataPoints[i].dataLabelOffset!, + coordinatePoints[i], + offset, + theme, + type == 'Bar' ? dataPoints[i].color! : seriesColor, + type, + type == 'Bar' ? segments![i] : null, + dataPoints[i].y, + ); + drawText( + canvas, + dataLabels[i], + dataPoints[i].dataLabelOffset!, + textStyle, + ); } } @@ -702,73 +746,88 @@ void renderDataLabel( case SparkChartLabelDisplayMode.first: { - _textStyle = _getTextStyle( - labelStyle, - dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset!, - coordinatePoints[type == 'Area' ? 1 : 0], - offset, - theme, - type == 'Bar' ? dataPoints[0].color! : seriesColor, - type, - type == 'Bar' ? segments![0] : null, - dataPoints[0].y); - drawText(canvas, dataLabels[type == 'Area' ? 1 : 0], - dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset!, _textStyle); + textStyle = _getTextStyle( + labelStyle, + dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset!, + coordinatePoints[type == 'Area' ? 1 : 0], + offset, + theme, + type == 'Bar' ? dataPoints[0].color! : seriesColor, + type, + type == 'Bar' ? segments![0] : null, + dataPoints[0].y, + ); + drawText( + canvas, + dataLabels[type == 'Area' ? 1 : 0], + dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset!, + textStyle, + ); } break; case SparkChartLabelDisplayMode.last: { - _textStyle = _getTextStyle( - labelStyle, - dataPoints[type == 'Area' - ? dataPoints.length - 2 - : dataPoints.length - 1] - .dataLabelOffset!, - coordinatePoints[ - type == 'Area' ? dataPoints.length - 2 : dataPoints.length - 1], - offset, - theme, - type == 'Bar' - ? dataPoints[dataPoints.length - 1].color! - : seriesColor, - type, - type == 'Bar' ? segments![dataPoints.length - 1] : null, - dataPoints[dataPoints.length - 1].y); + textStyle = _getTextStyle( + labelStyle, + dataPoints[type == 'Area' + ? dataPoints.length - 2 + : dataPoints.length - 1] + .dataLabelOffset!, + coordinatePoints[type == 'Area' + ? dataPoints.length - 2 + : dataPoints.length - 1], + offset, + theme, + type == 'Bar' + ? dataPoints[dataPoints.length - 1].color! + : seriesColor, + type, + type == 'Bar' ? segments![dataPoints.length - 1] : null, + dataPoints[dataPoints.length - 1].y, + ); drawText( - canvas, - dataLabels[ - type == 'Area' ? dataPoints.length - 2 : dataPoints.length - 1], - dataPoints[type == 'Area' - ? dataPoints.length - 2 - : dataPoints.length - 1] - .dataLabelOffset!, - _textStyle); + canvas, + dataLabels[type == 'Area' + ? dataPoints.length - 2 + : dataPoints.length - 1], + dataPoints[type == 'Area' + ? dataPoints.length - 2 + : dataPoints.length - 1] + .dataLabelOffset!, + textStyle, + ); } break; case SparkChartLabelDisplayMode.low: { - final int length = type == 'Area' - ? coordinatePoints.length - 1 - : coordinatePoints.length; + final int length = + type == 'Area' + ? coordinatePoints.length - 1 + : coordinatePoints.length; final int index = type == 'Area' ? 1 : 0; for (int j = index; j < length; j++) { if (highPoint == coordinatePoints[j].dy) { - _textStyle = _getTextStyle( - labelStyle, - dataPoints[j].dataLabelOffset!, - coordinatePoints[j], - offset, - theme, - type == 'Bar' ? dataPoints[j].color! : seriesColor, - type, - type == 'Bar' ? segments![j] : null, - dataPoints[j].y); - drawText(canvas, dataLabels[j], dataPoints[j].dataLabelOffset!, - _textStyle); + textStyle = _getTextStyle( + labelStyle, + dataPoints[j].dataLabelOffset!, + coordinatePoints[j], + offset, + theme, + type == 'Bar' ? dataPoints[j].color! : seriesColor, + type, + type == 'Bar' ? segments![j] : null, + dataPoints[j].y, + ); + drawText( + canvas, + dataLabels[j], + dataPoints[j].dataLabelOffset!, + textStyle, + ); } } } @@ -777,25 +836,31 @@ void renderDataLabel( case SparkChartLabelDisplayMode.high: { - final int length = type == 'Area' - ? coordinatePoints.length - 1 - : coordinatePoints.length; + final int length = + type == 'Area' + ? coordinatePoints.length - 1 + : coordinatePoints.length; final int index = type == 'Area' ? 1 : 0; for (int j = index; j < length; j++) { if (lowPoint == coordinatePoints[j].dy) { - _textStyle = _getTextStyle( - labelStyle, - dataPoints[j].dataLabelOffset!, - coordinatePoints[j], - offset, - theme, - type == 'Bar' ? dataPoints[j].color! : seriesColor, - type, - type == 'Bar' ? segments![j] : null, - dataPoints[j].y); - drawText(canvas, dataLabels[j], dataPoints[j].dataLabelOffset!, - _textStyle); + textStyle = _getTextStyle( + labelStyle, + dataPoints[j].dataLabelOffset!, + coordinatePoints[j], + offset, + theme, + type == 'Bar' ? dataPoints[j].color! : seriesColor, + type, + type == 'Bar' ? segments![j] : null, + dataPoints[j].y, + ); + 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 7ff721f55..34297920b 100644 --- a/packages/syncfusion_flutter_charts/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/pubspec.yaml @@ -1,17 +1,38 @@ name: syncfusion_flutter_charts description: A Flutter Charts library which includes data visualization widgets such as cartesian and circular charts, to create real-time, interactive, high-performance, animated charts. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_charts +screenshots: + - description: 'This screenshot shows the area type charts image.' + path: screenshots/flutter-area-type-charts.png + - description: 'This screenshot shows the circular charts image.' + path: screenshots/flutter-circular-charts.png + - description: 'This screenshot shows the column type charts image.' + path: screenshots/flutter-column-type-charts.png + - description: 'This screenshot shows the line type charts image.' + path: screenshots/flutter-line-type-charts.png + - description: 'This screenshot shows the sfcartesian charts image.' + path: screenshots/flutter-SfCaretesian-charts.png + - description: 'This screenshot shows the sparkcharts image.' + path: screenshots/flutter-SparkChart.png + environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: sdk: flutter - intl: ^0.17.0 + intl: '>=0.18.1 <0.21.0' vector_math: ">=2.1.0 <=3.0.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core + + + flutter: + assets: + - images/ + - assets/fonts/Roboto-Medium.ttf + - assets/fonts/Times-New-Roman.ttf diff --git a/packages/syncfusion_flutter_charts/screenshots/flutter-SfCaretesian-charts.png b/packages/syncfusion_flutter_charts/screenshots/flutter-SfCaretesian-charts.png new file mode 100644 index 000000000..2d42a6725 Binary files /dev/null and b/packages/syncfusion_flutter_charts/screenshots/flutter-SfCaretesian-charts.png differ diff --git a/packages/syncfusion_flutter_charts/screenshots/flutter-SparkChart.png b/packages/syncfusion_flutter_charts/screenshots/flutter-SparkChart.png new file mode 100644 index 000000000..54a0fa687 Binary files /dev/null and b/packages/syncfusion_flutter_charts/screenshots/flutter-SparkChart.png differ diff --git a/packages/syncfusion_flutter_charts/screenshots/flutter-area-type-charts.png b/packages/syncfusion_flutter_charts/screenshots/flutter-area-type-charts.png new file mode 100644 index 000000000..9655c6064 Binary files /dev/null and b/packages/syncfusion_flutter_charts/screenshots/flutter-area-type-charts.png differ diff --git a/packages/syncfusion_flutter_charts/screenshots/flutter-circular-charts.png b/packages/syncfusion_flutter_charts/screenshots/flutter-circular-charts.png new file mode 100644 index 000000000..f6cf57c19 Binary files /dev/null and b/packages/syncfusion_flutter_charts/screenshots/flutter-circular-charts.png differ diff --git a/packages/syncfusion_flutter_charts/screenshots/flutter-column-type-charts.png b/packages/syncfusion_flutter_charts/screenshots/flutter-column-type-charts.png new file mode 100644 index 000000000..3188f1903 Binary files /dev/null and b/packages/syncfusion_flutter_charts/screenshots/flutter-column-type-charts.png differ diff --git a/packages/syncfusion_flutter_charts/screenshots/flutter-line-type-charts.png b/packages/syncfusion_flutter_charts/screenshots/flutter-line-type-charts.png new file mode 100644 index 000000000..119d5aece Binary files /dev/null and b/packages/syncfusion_flutter_charts/screenshots/flutter-line-type-charts.png differ diff --git a/packages/syncfusion_flutter_chat/.gitignore b/packages/syncfusion_flutter_chat/.gitignore new file mode 100644 index 000000000..ac5aa9893 --- /dev/null +++ b/packages/syncfusion_flutter_chat/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/packages/syncfusion_flutter_chat/.metadata b/packages/syncfusion_flutter_chat/.metadata new file mode 100644 index 000000000..8f7b6ac10 --- /dev/null +++ b/packages/syncfusion_flutter_chat/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49" + channel: "stable" + +project_type: package diff --git a/packages/syncfusion_flutter_chat/CHANGELOG.md b/packages/syncfusion_flutter_chat/CHANGELOG.md new file mode 100644 index 000000000..59f244028 --- /dev/null +++ b/packages/syncfusion_flutter_chat/CHANGELOG.md @@ -0,0 +1,182 @@ +## Unreleased + +**General** + +* The compatible version of our Flutter chat and AI assistView widget has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter chat and AI assistView widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter chat and AI assistView example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter chat and AI assistView example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## Chat + +### Breaking Changes + +Following breaking changes will occur in Chat. + +## SfChat: + +- The `bubbleHeaderBuilder` property has been renamed to `messageHeaderBuilder`. +- The `bubbleAvatarBuilder` property has been renamed to `messageAvatarBuilder`. +- The `bubbleContentBuilder` property has been renamed to `messageContentBuilder`. +- The `bubbleFooterBuilder` property has been renamed to `messageFooterBuilder`. +- The `incomingBubbleSettings` property has been renamed to `incomingMessageSettings`. +- The `outgoingBubbleSettings` property has been renamed to `outgoingMessageSettings`. +- The `ChatBubbleSettings` class has been renamed to `ChatMessageSettings`. + +## ChatComposer: + +- The `padding` property has been renamed to `margin`. + +## ChatComposer.builder(): + +- The `padding` property has been renamed to `margin`. + +## ChatActionButton: + +- The `padding` property has been renamed to `margin`. + +## ChatMessageSettings: + +- The `showUserName` property has been renamed to `showAuthorName`. +- The `showUserAvatar` property has been renamed to `showAuthorAvatar`. +- The `contentBackgroundColor` property has been renamed to `backgroundColor`. +- The `contentShape` property has been renamed to `shape`. +- The `padding` property has been renamed to `margin`. +- The `contentPadding` property has been renamed to `padding`. + +## AssistView + +### Breaking Changes + +Following breaking changes will occur in AIAssistView. + +## SfAIAssistView: + +- The `bubbleHeaderBuilder` property has been renamed to `messageHeaderBuilder`. +- The `bubbleAvatarBuilder` property has been renamed to `messageAvatarBuilder`. +- The `bubbleContentBuilder` property has been renamed to `messageContentBuilder`. +- The `bubbleFooterBuilder` property has been renamed to `messageFooterBuilder`. +- The `bubbleAlignment` property has been renamed to `messageAlignment`. +- The `AssistBubbleAlignment` enum has been renamed to `AssistMessageAlignment`. +- The `responseBubbleSettings` property has been renamed to `responseMessageSettings`. +- The `requestBubbleSettings` property has been renamed to `requestMessageSettings`. +- The `onBubbleToolbarItemSelected` property has been renamed to `onToolbarItemSelected`. +- The `AssistBubbleToolbarItemSelectedCallback` typedef has been renamed to `AssistToolbarItemSelectedCallback`. +- The `AssistBubbleSettings` class has been renamed to `AssistMessageSettings`. + +## AssistMessageSettings: + +- The `showUserName` property has been renamed to `showAuthorName`. +- The `showUserAvatar` property has been renamed to `showAuthorAvatar`. +- The `contentBackgroundColor` property has been renamed to `backgroundColor`. +- The `contentShape` property has been renamed to `shape`. +- The `padding` property has been renamed to `margin`. +- The `contentPadding` property has been renamed to `padding`. + +## AssistSuggestionSettings: + +- The `padding` property has been renamed to `margin`. + +## AssistComposer: + +- The `padding` property has been renamed to `margin`. + +## AssistComposer.builder(): + +- The `padding` property has been renamed to `margin`. + +## AssistActionButton: + +- The `padding` property has been renamed to `margin`. + +## AssistMessageToolbarSettings: + +- The `padding` property has been renamed to `margin`. + +## Core + +### Breaking Changes + +Following breaking changes will occur in core. + +## SfChatThemeData: + +- The `outgoingBubbleContentBackgroundColor` property has been renamed to `outgoingMessageBackgroundColor`. +- The `incomingBubbleContentBackgroundColor` property has been renamed to `incomingMessageBackgroundColor`. +- The `outgoingBubbleContentShape` property has been renamed to `outgoingMessageShape`. +- The `incomingBubbleContentShape` property has been renamed to `incomingMessageShape`. + +## SfAIAssistViewThemeData: + +- The `requestBubbleContentBackgroundColor` property has been renamed to `requestMessageBackgroundColor`. +- The `responseBubbleContentBackgroundColor` property has been renamed to `responseMessageBackgroundColor`. +- The `requestBubbleContentShape` property has been renamed to `requestMessageShape`. +- The `responseBubbleContentShape` property has been renamed to `responseMessageShape`. + +## [28.2.7] - 25/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter chat and AI assistView widgets has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +### General + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +### AI AssistView (Preview) + +Initial release. + +**Features** +1. Request and response message. +2. Composer - The primary text editor where the user can compose request messages. +3. Action button - This represents the send button, which invokes a callback with the text entered in the default composer, where the user can request their preferred AI to respond to their request. +4. Placeholder - This allows you to specify a custom widget to display when there are no messages in the chat or in the header of all messages. +5. Bubble - This holds each message's header, content, footer, and avatar. +6. Suggestions - This allows you to specify a list of suggestions for the response message that can align with the request message. +7. Loading indicator - This indicates the loading status of the AI response. + +### Chat (Preview) + +**Features** + +* Added suggestion support to chat messages. + +## [27.1.51] - 30/09/2024 + +**General** + +* Added a description to the library. +* API documentation link has been redirected to https://pub.dev/documentation/syncfusion_flutter_chat/latest/chat/chat-library.html from +https://pub.dev/documentation/syncfusion_flutter_chat/latest/syncfusion_flutter_chat/syncfusion_flutter_chat-library.html + +## [27.1.48-beta] - 09/24/2024 + +Initial release. + +**Features** +1. Messages - A list of ChatMessage objects that will be displayed in the chat interface as either incoming or outgoing messages, depending on the current user. +2. Composer - The primary text editor where the user can compose new chat messages. +3. Action button - This represents the send button, which invokes a callback with the text entered in the default composer. +4. Placeholder - This allows you to specify a custom widget to display when there are no messages in the chat. +5. Bubble - This holds each message's header, content, footer, and avatar. diff --git a/packages/syncfusion_flutter_chat/LICENSE b/packages/syncfusion_flutter_chat/LICENSE new file mode 100644 index 000000000..4150abeba --- /dev/null +++ b/packages/syncfusion_flutter_chat/LICENSE @@ -0,0 +1,12 @@ +Syncfusion® License + +Syncfusion® Flutter Chat 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® 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® 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_chat/README.md b/packages/syncfusion_flutter_chat/README.md new file mode 100644 index 000000000..cc1520d19 --- /dev/null +++ b/packages/syncfusion_flutter_chat/README.md @@ -0,0 +1,265 @@ +![Syncfusion Flutter Chat banner](https://cdn.syncfusion.com/content/images/Flutter/chat/chat_pub_banner.png) + +# Flutter Chat library + +Flutter Chat library contains the following widgets: +* The [Flutter Chat](https://www.syncfusion.com/flutter-widgets/flutter-Chat?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-Chat-pubdev) package is a UI library designed to help you create a robust chat application within your Flutter projects. +* The [Flutter AI AssistView](https://www.syncfusion.com/flutter-widgets/flutter-assistview?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-assistview-pubdev) package is a UI library designed to help you create a robust AI chat application within your Flutter projects. + +## Overview +Chat +* Offers a modern conversation UI to facilitate communication among users. +* Displays both one-on-one and group conversations. +* Provides a wide range of customization options for message content, headers, footers, avatars, editors, action buttons, and suggestions. + +AI AssistView +* Offers a modern conversation UI to facilitate communication between users and AI. +* Displays request and response messages. +* Provides a wide range of customization options for message content, headers, footers, avatars, editors, action buttons, suggestions, and toolbar items. + +**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). + +## Table of content: +- [Chat Features](#chat-features) +- [AI AssistView Features](#ai-assistview-features) +- [Get the demo application](#get-the-demo-application) +- [Useful links](#other-useful-links) +- [Installation](#installation) +- [Flutter Chat example](#flutter-chat-example) +- [Flutter AI AssistView example](#flutter-ai-assistview-example) +- [Support and feedback](#support-and-feedback) +- [About Syncfusion®](#about-syncfusion) + +## Chat Features: + +**Placeholder:** Define a custom widget to show when there are no messages in the chat. This feature is especially beneficial for providing users with an engaging or pertinent message that signifies the conversation is currently empty. + +**Composer:** This is the primary text editor where users can compose new chat messages. There is an option to define a custom widget to serve as a composer, allowing one or more widgets to provide multiple options for composing a message. + +**Action button:** This is used for the Send button. Pressing this action button invokes the callback, where the outgoing user rebuilds the chat widget with their composed message. There is an option to define a custom widget consisting of one or more interactive widgets to serve as action buttons, such as a send button, microphone button, or file attachment button. + +**Conversation:** This displays the content of incoming and outgoing messages. Each message includes details such as the message text, time stamp, and author. There is an option available to individually customize incoming and outgoing messages, providing a distinct appearance for each message type. + +- **Incoming:** The content of incoming messages can be customize. Change the background color, content shape, and other features according to the message user or specific conditions. + +- **Outgoing:** The content of outgoing messages can be customized. Change the background color, content shape, and other features according to the message user or specific conditions. + +- **Header:** The header displays the username of the message's author along with the time stamp. Additionally, users can build a custom widget to display more information about the chat message. + +- **Footer:** Define a custom widget to display additional information about the chat message, such as the time stamp or message delivery status. + +- **Avatar:** The author's avatar displays either an image or the initials of their name. By default, if the avatar image source is not defined, the user's initials will be displayed. Additionally, users can create a custom widget that shows more information about them. + +- **Content:** Users can customize the message content's background color, content shape, and other features based on the user or other specific conditions. By default, the text content is shown as not selectable, but there is an option to display the message content using a custom widget. + +## AI AssistView Features + +**Placeholder:** Define a custom widget to show when there are no messages in the chat or header of all messages. + +**Composer:** This is the primary text editor where users can compose new chat messages. There is an option to define a custom widget to serve as a composer, allowing one or more widgets to provide multiple options for composing a message. + +**Action button:** This is used for the Send button. Pressing this action button invokes a callback, where the user requests a response to their composed message. There is an option to define a custom widget consisting of one or more interactive widgets to serve as action buttons, such as a send button, microphone button, or file attachment button. + +**Conversation:** This displays the content of user request and its response messages. Each message includes details such as the message text, time stamp, and author. There is an option available to individually customize request and response messages, providing a distinct appearance for each message type. + +- **Header:** This displays the username of the message's author along with the time stamp. Additionally, users can build a custom widget to display more information about the request/response message. + +- **Footer:** Define a custom widget to display additional information about the request/response message, such as the time stamp, AI model and etc. + +- **Avatar:** The author's avatar displays either an image or the initials of their name. By default, if the avatar image source is not defined, the user's initials will be displayed. Additionally, users can create a custom widget that shows more information about them. + +- **Content:** Users can customize the request/response message content's background color, content shape, and other features based on the user or other specific conditions. + +- **Suggestion:** This enables the addition of suggestions for response messages, thereby improving the user experience by presenting relevant options that align with the context of the conversation. + +- **Loading indicator:** This is a loading effect that indicates a response message is currently being generated. It is activated automatically when a request message is added to the assist view widget and will be removed from the display once a response message is added. + +## Get the demo application + +Explore the full capabilities of our Flutter widgets on your device by installing our sample browser applications from the below app stores, and view samples code in GitHub. + +

+ + + +

+

+ + + +

+ +## Other useful links + +Take a look at the following to learn more about the Syncfusion® Flutter Chat: + +* [Syncfusion® Flutter Chat product page](https://www.syncfusion.com/flutter-widgets/flutter-chat) +* [Syncfusion® Flutter AI AssistView product page](https://www.syncfusion.com/flutter-widgets/flutter-aiassistview) +* [User guide documentation for Chat](https://help.syncfusion.com/flutter/chat) +* [User guide documentation for AI AssistView](https://help.syncfusion.com/flutter/aiassistView) +* [Knowledge base](https://support.syncfusion.com/kb/cross-platforms/category/79) + +## Installation + +Add the [latest version](https://pub.dev/packages/syncfusion_flutter_chat/install) of [Syncfusion® Flutter Chat](https://pub.dev/packages/syncfusion_flutter_chat) widget to your `pubspec.yaml` file.. + +## Flutter Chat example + +Import Chat library using the code provided below. + +```dart +import 'package:syncfusion_flutter_chat/chat.dart'; +``` +### Add Chat widget + +Initialize the Chat widget as a child of any widget. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: SfChat(), + ); +} +``` + +### Bind data source + +To bind the message conversations to the `Chat` widget, the predefined data model class `ChatMessage` is used. This class contains three essential properties: `text`, `time`, and `author`, which are required for rendering a chat message. The following code snippet illustrates how to assign data to the `Chat` widget. + +```dart +late List _messages; + +@override +void initState() { + _messages = [ + ChatMessage( + text: 'Hello, how can I help you today?', + time: DateTime.now(), + author: const ChatAuthor( + id: 'a2c4-56h8-9x01-2a3d', + name: 'Incoming user name', + ), + ), + ]; + super.initState(); +} + +@override +Widget build(BuildContext context) { + return SfChat( + messages: _messages, + outgoingUser: '8ob3-b720-g9s6-25s8', + composer: const ChatComposer( + decoration: InputDecoration( + hintText: 'Type a message', + ), + ), + actionButton: ChatActionButton( + onPressed: (String newMessage) { + setState(() { + _messages.add(ChatMessage( + text: newMessage, + time: DateTime.now(), + author: const ChatAuthor( + id: '8ob3-b720-g9s6-25s8', + name: 'Outgoing user name', + ), + )); + }); + }, + ), + ); +} + +@override +void dispose() { + _messages.clear(); + super.dispose(); +} +``` + +The following output illustrates the result of the above code sample. + +![Default Chat demo](https://cdn.syncfusion.com/content/images/Flutter/chat/chat_pub_getting_started_demo.gif) + +## Flutter AI AssistView example + +Import AI AssistView library using the code provided below. + +```dart +import 'package:syncfusion_flutter_chat/assist_view.dart'; +``` +### Add AI AssistView widget + +Initialize the AI AssistView widget as a child of any widget. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: SfAIAssistView(), + ); +} +``` + +### Bind data source + +To bind the message conversations to the `AssistView` widget, a predefined data model class called `AssistMessage` is used. This class has separate named constructors for obtaining request and response messages. The following code snippet illustrates how to assign data to the `AssistView` widget. + +```dart +late List _messages; + +void _generateResponse(String data) async { + String response = ''; + // Connect with your preferred AI to generate a response to the prompt. + final String response = await _getAIResponse(data); + setState(() { + _messages.add(AssistMessage.response(data: response)); + }); +} + +@override +void initState() { + _messages = []; + super.initState(); +} + +@override +Widget build(BuildContext context) { + return SfAIAssistView( + messages: _messages, + composer: const AssistComposer( + decoration: InputDecoration( + hintText: 'Type a message', + ), + ), + actionButton: AssistActionButton( + onPressed: (String data) { + setState(() { + _messages.add(AssistMessage.request(data: data)); + }); + _generateResponse(data); + }); + }, + ), + ); +} + +@override +void dispose() { + _messages.clear(); + super.dispose(); +} +``` + +## Support and feedback + +* For questions, reach out to our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_chat/analysis_options.yaml b/packages/syncfusion_flutter_chat/analysis_options.yaml new file mode 100644 index 000000000..8b639d50d --- /dev/null +++ b/packages/syncfusion_flutter_chat/analysis_options.yaml @@ -0,0 +1,9 @@ +include: package:syncfusion_flutter_core/analysis_options.yaml + +analyzer: + errors: + todo: ignore + +linter: + rules: + require_trailing_commas: true \ No newline at end of file diff --git a/packages/syncfusion_flutter_chat/assets/Roboto-Medium.ttf b/packages/syncfusion_flutter_chat/assets/Roboto-Medium.ttf new file mode 100644 index 000000000..d629e9848 Binary files /dev/null and b/packages/syncfusion_flutter_chat/assets/Roboto-Medium.ttf differ diff --git a/packages/syncfusion_flutter_chat/assets/images/AI_in_Avatar.png b/packages/syncfusion_flutter_chat/assets/images/AI_in_Avatar.png new file mode 100644 index 000000000..067e11db2 Binary files /dev/null and b/packages/syncfusion_flutter_chat/assets/images/AI_in_Avatar.png differ diff --git a/packages/syncfusion_flutter_chat/assets/images/People_Circle23.png b/packages/syncfusion_flutter_chat/assets/images/People_Circle23.png new file mode 100644 index 000000000..d7a12ee96 Binary files /dev/null and b/packages/syncfusion_flutter_chat/assets/images/People_Circle23.png differ diff --git a/packages/syncfusion_flutter_chat/assets/images/People_Circle5.png b/packages/syncfusion_flutter_chat/assets/images/People_Circle5.png new file mode 100644 index 000000000..9a1f4b34a Binary files /dev/null and b/packages/syncfusion_flutter_chat/assets/images/People_Circle5.png differ diff --git a/packages/syncfusion_flutter_chat/example/README.md b/packages/syncfusion_flutter_chat/example/README.md new file mode 100644 index 000000000..3dbe6c57d --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/README.md @@ -0,0 +1,3 @@ +# chat_example + +A new Flutter project. diff --git a/packages/syncfusion_flutter_chat/example/analysis_options.yaml b/packages/syncfusion_flutter_chat/example/analysis_options.yaml new file mode 100644 index 000000000..f9b303465 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/packages/syncfusion_flutter_chat/example/android/.gitignore b/packages/syncfusion_flutter_chat/example/android/.gitignore new file mode 100644 index 000000000..be3943c96 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/packages/syncfusion_flutter_chat/example/android/app/build.gradle b/packages/syncfusion_flutter_chat/example/android/app/build.gradle new file mode 100644 index 000000000..8b5d7a8e8 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +android { + namespace = "com.syncfusion.chat_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.syncfusion.chat_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + 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 = "../.." +} diff --git a/packages/syncfusion_flutter_chat/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_chat/example/android/app/build.gradle.kts new file mode 100644 index 000000000..139cb1dd4 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.chat_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.chat_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_chat/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_chat/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..27748dc14 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/kotlin/com/example/chat_example/MainActivity.kt b/packages/syncfusion_flutter_chat/example/android/app/src/main/kotlin/com/example/chat_example/MainActivity.kt new file mode 100644 index 000000000..30e7e9a11 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/main/kotlin/com/example/chat_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.chat_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_chat/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_chat/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_chat/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_chat/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_chat/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_chat/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_chat/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_chat/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_chat/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_chat/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_chat/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_chat/example/android/build.gradle b/packages/syncfusion_flutter_chat/example/android/build.gradle new file mode 100644 index 000000000..d2ffbffa4 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/syncfusion_flutter_chat/example/android/build.gradle.kts b/packages/syncfusion_flutter_chat/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_chat/example/android/gradle.properties b/packages/syncfusion_flutter_chat/example/android/gradle.properties new file mode 100644 index 000000000..f018a6181 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/syncfusion_flutter_chat/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_chat/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..afa1e8eb0 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_chat/example/android/settings.gradle b/packages/syncfusion_flutter_chat/example/android/settings.gradle new file mode 100644 index 000000000..536165d35 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_chat/example/android/settings.gradle.kts b/packages/syncfusion_flutter_chat/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_chat/example/ios/.gitignore b/packages/syncfusion_flutter_chat/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.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/ephemeral/ +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_chat/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_chat/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..7c5696400 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_chat/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_chat/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_chat/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..611dbceb4 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,638 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 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 */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + 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 */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 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; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 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; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 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; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + 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; + alwaysOutOfDate = 1; + 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 */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency 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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + 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; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + 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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + 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; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample; + 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; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample; + 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 */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 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 */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..15c313e20 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_chat/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..626664468 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@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_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cd7b0099 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..321773cd8 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..502f463a9 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..e9f5fea27 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..84ac32ae7 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..8953cba09 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_chat/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_chat/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_chat/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_chat/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_chat/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_chat/example/ios/Runner/Info.plist new file mode 100644 index 000000000..1d323d519 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Chat Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + chat_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 + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/packages/syncfusion_flutter_chat/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_chat/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_chat/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_chat/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_chat/example/lib/main.dart b/packages/syncfusion_flutter_chat/example/lib/main.dart new file mode 100644 index 000000000..ac63954ca --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/lib/main.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_chat/chat.dart'; +import 'package:syncfusion_flutter_chat/assist_view.dart'; + +void main() { + runApp(const MaterialApp(debugShowCheckedModeBanner: false, home: MainApp())); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Syncfusion Conversational UI Sample')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => const ChatSample(), + ), + ); + }, + child: const Text('Chat Sample'), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => const AssistViewSample(), + ), + ); + }, + child: const Text('AI AssistView Sample'), + ), + ], + ), + ), + ); + } +} + +// Chat Sample +class ChatSample extends StatefulWidget { + const ChatSample({super.key}); + + @override + State createState() => ChatSampleState(); +} + +class ChatSampleState extends State { + late List _messages; + + @override + void initState() { + _messages = [ + ChatMessage( + text: 'Hi! How are you?', + time: DateTime.now(), + author: const ChatAuthor( + id: '8ob3-b720-g9s6-25s8', + name: 'Outgoing user name', + ), + ), + ChatMessage( + text: 'Fine, how about you?', + time: DateTime.now(), + author: const ChatAuthor( + id: 'a2c4-56h8-9x01-2a3d', + name: 'Incoming user name', + ), + ), + ]; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Syncfusion Flutter Chat')), + body: Padding( + padding: const EdgeInsets.all(10.0), + child: SfChat( + messages: _messages, + outgoingUser: '8ob3-b720-g9s6-25s8', + composer: const ChatComposer( + decoration: InputDecoration(hintText: 'Type a message'), + ), + actionButton: ChatActionButton( + onPressed: (String newMessage) { + setState(() { + _messages.add( + ChatMessage( + text: newMessage, + time: DateTime.now(), + author: const ChatAuthor( + id: '8ob3-b720-g9s6-25s8', + name: 'Outgoing user name', + ), + ), + ); + }); + }, + ), + ), + ), + ); + } + + @override + void dispose() { + _messages.clear(); + super.dispose(); + } +} + +// AssistView Sample +class AssistViewSample extends StatefulWidget { + const AssistViewSample({super.key}); + + @override + State createState() => AssistViewSampleState(); +} + +class AssistViewSampleState extends State { + late List _messages; + + void _generativeResponse(String data) async { + final String response = await _getAIResponse(data); + setState(() { + _messages.add(AssistMessage.response(data: response)); + }); + } + + Future _getAIResponse(String data) async { + String response = ''; + // Connect with your preferred AI to generate a response to the request. + return response; + } + + @override + void initState() { + _messages = []; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Syncfusion Flutter AI AssistView')), + body: Padding( + padding: const EdgeInsets.all(10.0), + child: SfAIAssistView( + messages: _messages, + composer: const AssistComposer( + decoration: InputDecoration(hintText: 'Type a message'), + ), + actionButton: AssistActionButton( + onPressed: (String data) { + setState(() { + _messages.add(AssistMessage.request(data: data)); + _generativeResponse(data); + }); + }, + ), + ), + ), + ); + } + + @override + void dispose() { + _messages.clear(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_chat/example/linux/.gitignore b/packages/syncfusion_flutter_chat/example/linux/.gitignore new file mode 100644 index 000000000..d3896c984 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/syncfusion_flutter_chat/example/linux/CMakeLists.txt b/packages/syncfusion_flutter_chat/example/linux/CMakeLists.txt new file mode 100644 index 000000000..c144b4b22 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "chat_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.syncfusion.chat_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/syncfusion_flutter_chat/example/linux/flutter/CMakeLists.txt b/packages/syncfusion_flutter_chat/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000..d5bd01648 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..e71a16d23 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..2e1de87a7 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_chat/example/linux/main.cc b/packages/syncfusion_flutter_chat/example/linux/main.cc new file mode 100644 index 000000000..e7c5c5437 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/syncfusion_flutter_chat/example/linux/my_application.cc b/packages/syncfusion_flutter_chat/example/linux/my_application.cc new file mode 100644 index 000000000..37c6d3ba0 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "chat_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "chat_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/syncfusion_flutter_chat/example/linux/my_application.h b/packages/syncfusion_flutter_chat/example/linux/my_application.h new file mode 100644 index 000000000..72271d5e4 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/syncfusion_flutter_chat/example/macos/.gitignore b/packages/syncfusion_flutter_chat/example/macos/.gitignore new file mode 100644 index 000000000..746adbb6b --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/syncfusion_flutter_chat/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_chat/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..c2efd0b60 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_chat/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_chat/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..c2efd0b60 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_chat/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_chat/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..cccf817a5 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..7ec414330 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,727 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* chat_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "chat_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* chat_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* chat_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/chat_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/chat_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/chat_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/chat_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/chat_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/chat_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..2011d9317 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_chat/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_chat/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_chat/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..b3c176141 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 000000000..82b6f9d9a Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 000000000..13b35eba5 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 000000000..0a3f5fa40 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 000000000..bdb57226d Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 000000000..f083318e0 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 000000000..326c0e72c Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 000000000..2f1632cfd Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_chat/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000..80e867a4e --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..23ee3ab88 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = chat_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.chatExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.syncfusion. All rights reserved. diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Debug.xcconfig b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Release.xcconfig b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Warnings.xcconfig b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/DebugProfile.entitlements b/packages/syncfusion_flutter_chat/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000..dddb8a30c --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Info.plist b/packages/syncfusion_flutter_chat/example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_chat/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..3cc05eb23 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/syncfusion_flutter_chat/example/macos/Runner/Release.entitlements b/packages/syncfusion_flutter_chat/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000..852fa1a47 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/syncfusion_flutter_chat/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_chat/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_chat/example/pubspec.yaml b/packages/syncfusion_flutter_chat/example/pubspec.yaml new file mode 100644 index 000000000..c898c3c2c --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: chat_example +description: "A Chat widget example project." +version: 1.0.0 +publish_to: 'none' + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + syncfusion_flutter_chat: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + +flutter: + uses-material-design: true diff --git a/packages/syncfusion_flutter_chat/example/web/favicon.png b/packages/syncfusion_flutter_chat/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/web/favicon.png differ diff --git a/packages/syncfusion_flutter_chat/example/web/icons/Icon-192.png b/packages/syncfusion_flutter_chat/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/web/icons/Icon-192.png differ diff --git a/packages/syncfusion_flutter_chat/example/web/icons/Icon-512.png b/packages/syncfusion_flutter_chat/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/web/icons/Icon-512.png differ diff --git a/packages/syncfusion_flutter_chat/example/web/icons/Icon-maskable-192.png b/packages/syncfusion_flutter_chat/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/syncfusion_flutter_chat/example/web/icons/Icon-maskable-512.png b/packages/syncfusion_flutter_chat/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/syncfusion_flutter_chat/example/web/index.html b/packages/syncfusion_flutter_chat/example/web/index.html new file mode 100644 index 000000000..b465081b1 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + chat_example + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/web/manifest.json b/packages/syncfusion_flutter_chat/example/web/manifest.json new file mode 100644 index 000000000..56b7a4fbf --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "chat_example", + "short_name": "chat_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "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" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/syncfusion_flutter_chat/example/windows/.gitignore b/packages/syncfusion_flutter_chat/example/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/syncfusion_flutter_chat/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_chat/example/windows/CMakeLists.txt new file mode 100644 index 000000000..52ea19b5d --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(chat_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "chat_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/syncfusion_flutter_chat/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_chat/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..903f4899d --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..b93c4c30c --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_chat/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..394917c05 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_chat/example/windows/runner/Runner.rc new file mode 100644 index 000000000..e94f54d21 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "chat_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "chat_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.syncfusion. All rights reserved." "\0" + VALUE "OriginalFilename", "chat_example.exe" "\0" + VALUE "ProductName", "chat_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_chat/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..955ee3038 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/flutter_window.h b/packages/syncfusion_flutter_chat/example/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/main.cpp b/packages/syncfusion_flutter_chat/example/windows/runner/main.cpp new file mode 100644 index 000000000..7aa3d61cb --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"chat_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/resource.h b/packages/syncfusion_flutter_chat/example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/resources/app_icon.ico b/packages/syncfusion_flutter_chat/example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000..c04e20caf Binary files /dev/null and b/packages/syncfusion_flutter_chat/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_chat/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..a42ea7687 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_chat/example/windows/runner/utils.cpp new file mode 100644 index 000000000..3a0b46511 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/utils.h b/packages/syncfusion_flutter_chat/example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_chat/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..60608d0fe --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/syncfusion_flutter_chat/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_chat/example/windows/runner/win32_window.h new file mode 100644 index 000000000..e901dde68 --- /dev/null +++ b/packages/syncfusion_flutter_chat/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/syncfusion_flutter_chat/lib/assist_view.dart b/packages/syncfusion_flutter_chat/lib/assist_view.dart new file mode 100644 index 000000000..06a4c3b76 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/assist_view.dart @@ -0,0 +1,14 @@ +/// The Syncfusion Flutter Chat package is a UI library that enables +/// developers to build powerful AI chat applications in their +/// Flutter projects, offering extensive customization options. +/// +/// To use, `import package:syncfusion_flutter_chat/assist_view.dart`; +/// +/// See also: +/// * [Syncfusion Flutter AI AssistView product page](https://www.syncfusion.com/flutter-widgets/flutter-aiassistview) +/// * [User guide documentation for AI AssistView ](https://help.syncfusion.com/flutter/aiassistview/overview) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter/aiassistview) +library assist_view; + +export 'src/assist_view/assist_view.dart'; +export 'src/assist_view/settings.dart'; diff --git a/packages/syncfusion_flutter_chat/lib/chat.dart b/packages/syncfusion_flutter_chat/lib/chat.dart new file mode 100644 index 000000000..3d4501b3b --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/chat.dart @@ -0,0 +1,14 @@ +/// Syncfusion Flutter Chat package is a UI library designed to help you create +/// a robust chat application within your Flutter projects with highly customized +/// options. +/// +/// To use, `import package:syncfusion_flutter_chat/chat.dart`; +/// +/// See also: +/// * [Syncfusion Flutter Chat product page](https://www.syncfusion.com/flutter-widgets/flutter-chat) +/// * [User guide documentation for Chat](https://help.syncfusion.com/flutter/chat/overview) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter/chat) +library chat; + +export 'src/chat/chat.dart'; +export 'src/chat/settings.dart'; diff --git a/packages/syncfusion_flutter_chat/lib/src/assist_view/assist_view.dart b/packages/syncfusion_flutter_chat/lib/src/assist_view/assist_view.dart new file mode 100644 index 000000000..a30ad0c6c --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/assist_view/assist_view.dart @@ -0,0 +1,960 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../assist_view/theme.dart'; +import '../base_layout.dart'; +import '../composer_area.dart'; +import '../settings.dart'; +import 'conversion_area.dart'; +import 'settings.dart'; + +/// The [SfAIAssistView] widget is designed to build a chat interface +/// for chatbots powered by AI. +/// +/// It provides extensive customization options, allowing developers to modify +/// the appearance and behavior of chat bubbles, input composer, +/// action buttons, and more. +/// +/// The [SfAIAssistView] includes the following elements and features: +/// +/// * **Messages**: A list of [AssistMessage] objects that will be displayed +/// in the chat interface as either a request message from the user or a +/// response message from AI. Each [AssistMessage] includes details such as the +/// message text, timestamp, and author information. +/// * **Composer**: This is the primary text editor where the user can +/// compose new request messages. +/// * **Action Button**: This represents the send button. Pressing this +/// action button invokes the [AssistActionButton.onPressed] callback +/// with the text entered in the default [AssistComposer]. +/// * **Suggestions**: The response set for a message can be included with the +/// response itself, and choosing this suggestion can be treated as a +/// new request message. +/// * **Footer items**: This is a collection action bar items for a response +/// message. Particularly useful for adding a action items such as like, +/// dislike, copy, retry and etc. +/// * **Placeholder Builder**: The [SfAIAssistView.placeholderBuilder] allows +/// you to specify a custom widget to display when there are no messages in +/// the chat. This is particularly useful for presenting users with a relevant +/// or visually appealing message indicating that the conversation is +/// currently empty. +/// * **Bubble Header Builder**: The [SfAIAssistView.messageHeaderBuilder] allows +/// you to specify a custom widget to display as a header for each chat bubble. +/// This is particularly useful for displaying additional information such as +/// the sender's name and the timestamp associated with each message. +/// * **Bubble Avatar Builder**: The [SfAIAssistView.messageAvatarBuilder] allows +/// you to specify a custom widget to display as an avatar within each +/// chat bubble. This feature is especially useful for showing user avatars or +/// profile pictures within the chat interface. +/// * **Bubble Content Builder**: The [SfAIAssistView.messageContentBuilder] +/// allows you to specify a custom widget to display as the content within each +/// chat bubble. This is useful for customizing how the message content is +/// presented, such as using different background colors, borders, or padding. +/// * **Bubble Footer Builder**: The [SfAIAssistView.messageFooterBuilder] allows +/// you to specify a custom widget that will be displayed as a footer within +/// each chat bubble. This is particularly useful for displaying timestamps or +/// other additional information related to the message. +/// +/// The following example demonstrates how to create a simple chat interface +/// using the [SfAIAssistView] widget. +/// +/// ```dart +/// List _messages = []; +/// +/// void _generateAIResponse(String request) { +/// // Connect to your preferred AI and get response for the request. +/// String response = 'AI response'; // Replace with actual AI response. +/// setState(() { +/// _messages.add(AssistMessage.response(data: response)); +/// }); +/// } +/// +/// SfAIAssistView view = SfAIAssistView( +/// messages: _messages, +/// actionButton: AssistActionButton( +/// onPressed: (String data) { +/// _messages.add(AssistMessage.request(data: data)); +/// _generateAIResponse(data); +/// }, +/// ), +/// ); +/// ``` +class SfAIAssistView extends StatefulWidget { + const SfAIAssistView({ + super.key, + required this.messages, + this.composer = const AssistComposer(), + this.actionButton, + this.placeholderBuilder, + this.messageHeaderBuilder, + this.messageAvatarBuilder, + this.messageContentBuilder, + this.messageFooterBuilder, + this.responseLoadingBuilder, + this.placeholderBehavior = AssistPlaceholderBehavior.scrollWithMessage, + this.messageAlignment = AssistMessageAlignment.auto, + this.onSuggestionItemSelected, + this.onToolbarItemSelected, + this.requestMessageSettings = const AssistMessageSettings(), + this.responseMessageSettings = const AssistMessageSettings(), + this.responseToolbarSettings = const AssistMessageToolbarSettings(), + }); + + /// A list of [AssistMessage] objects that will be displayed in the chat + /// interface. + /// + /// Each message includes details such as the message text, timestamp, + /// and author information. + /// + /// [AssistMessage.request] message will be treated as a user request. + /// [AssistMessage.response] message will be treated as a AI response. + /// + /// ```dart + /// List _messages = []; + /// + /// void _generateAIResponse(String request) { + /// // Connect to your preferred AI and get response for the request. + /// String response = 'AI response'; // Replace with actual AI response. + /// setState(() { + /// _messages.add(AssistMessage.response(data: response)); + /// }); + /// } + /// + /// SfAIAssistView view = SfAIAssistView( + /// messages: _messages, + /// actionButton: AssistActionButton( + /// onPressed: (String data) { + /// _messages.add(AssistMessage.request(data: data)); + /// _generateAIResponse(data); + /// }, + /// ), + /// ); + /// ``` + final List messages; + + /// A primary text editor for composing the request messages. + /// + /// The [composer] is a customizable text editor designed for typing + /// new messages. It offers options to adjust the appearance and behavior + /// of the text editor, including settings for the minimum and maximum + /// number of lines, decoration, and text style. + /// + /// By default, the text editor does not include hint text. + /// Hint text can be added using the [InputDecoration.hintText] property + /// within [InputDecoration]. + /// + /// If the [composer] is disabled by setting [InputDecoration.enabled] to + /// `false`. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer( + /// minLines: 1, + /// maxLines: 3, + /// decoration: InputDecoration( + /// hintText: 'Type a message...', + /// ), + /// ), + /// ); + /// } + /// ``` + /// + /// ## Builder + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer.builder( + /// builder: (BuildContext context) { + /// return TextFormField( + /// decoration: InputDecoration( + /// hintText: 'Type a message...', + /// prefixIcon: IconButton( + /// icon: Icon(Icons.attach_file), + /// onPressed: () { + /// // Handle attachment button click. + /// }, + /// ), + /// suffixIcon: IconButton( + /// icon: Icon(Icons.mic), + /// onPressed: () { + /// // Handle mic button click. + /// }, + /// ), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// ``` + final AssistComposer? composer; + + /// Represents a send button. + /// + /// The [SfAIAssistView] widget does not include an action button by default. + /// To add, create a new instance of [AssistActionButton]. + /// + /// If the [AssistActionButton.onPressed] callback is null, the button + /// remains disabled. + /// + /// If the default [composer]'s text is empty or if the [composer] is + /// disabled, the button remains disabled. + /// + /// If the [composer] is null, the button always stays enabled. + /// + /// If last message is [AssistMessage.request], the button will be disabled + /// until the next [AssistMessage.response] is added. + /// + /// Pressing the action button invokes the [AssistActionButton.onPressed] + /// callback with the text entered in the default [composer]. By default, + /// [SfAIAssistView] will not update its state until the parent widget + /// rebuilds the chat with new messages. + /// + /// If [AssistComposer.builder] is used, the button always stays enabled. + /// + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// ), + /// ]; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssist( + /// messages: _messages, + /// outgoingUser: '8ob3-b720-g9s6-25s8', + /// actionButton: AssistActionButton( + /// onPressed: (String newMessage) { + /// setState(() { + /// _messages.add( + /// AssistMessage.request( + /// data: newMessage, + /// ), + /// ); + /// }); + /// }, + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// * [AssistComposer] to customize the default text editor. + /// * [AssistComposer.builder] to create a custom text editor. + /// * [AssistActionButton.onPressed] to handle the action button click. + /// * [AssistActionButton.child] to add new action buttons. + final AssistActionButton? actionButton; + + /// A callback function creates a widget to display initially or + /// top of all messages. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// placeholderBuilder: + /// (BuildContext context) { + /// return Center(child: Text('No messages yet')); + /// }, + /// ); + /// } + /// ``` + /// + /// See also: + /// * [placeholderBehavior], to control the visibility of the placeholder. + final WidgetBuilder? placeholderBuilder; + + /// A callback function creates a widget to serve as a header for each + /// message bubble. + /// + /// The [messageHeaderBuilder] allows you to specify a custom widget that will + /// be shown as a header within each chat bubble. This is particularly useful + /// for displaying additional information such as the sender's name and + /// timestamp associated with each message. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [AssistMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default header widget. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messageHeaderBuilder: + /// (BuildContext context, int index, AssistMessage message) { + /// return Padding( + /// padding: const EdgeInsets.all(8.0), + /// child: Text( + /// message.author.name, + /// style: const TextStyle(fontWeight: FontWeight.bold), + /// ), + /// ); + /// }, + /// ); + /// } + /// ``` + final AssistWidgetBuilder? messageHeaderBuilder; + + /// A callback function creates a widget to display as an avatar within each + /// message bubble. + /// + /// The [messageAvatarBuilder] allows you to specify a custom widget that will + /// be shown as an avatar within each message bubble. This is particularly + /// useful for displaying user avatars or profile pictures in the chat + /// interface. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [AssistMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default avatar widget. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messageAvatarBuilder: + /// (BuildContext context, int index, AssistMessage message) { + /// return CircleAvatar( + /// backgroundImage: NetworkImage( + /// message.author.id == '123-001' + /// ? 'https://example.com/user-avatar.jpg' + /// : 'https://example.com/ai-avatar.jpg', + /// ), + /// ); + /// }, + /// ); + /// } + /// ``` + final AssistWidgetBuilder? messageAvatarBuilder; + + /// A callback function creates a widget to display as the content of each + /// message bubble. + /// + /// The [messageContentBuilder] allows you to specify a custom widget to + /// display as the content within each message bubble. This is useful for + /// customizing how the message content is presented, such as using different + /// background colors, borders, or padding. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [AssistMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default content widget. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messageContentBuilder: + /// (BuildContext context, int index, AssistMessage message) { + /// return Padding( + /// padding: const EdgeInsets.all(8.0), + /// child: Text(message.text), + /// ); + /// }, + /// ); + /// } + /// ``` + final AssistWidgetBuilder? messageContentBuilder; + + /// A callback function creates a widget to display as a footer within each + /// message bubble. + /// + /// The [messageFooterBuilder] allows you to specify a custom widget that will + /// be shown as a footer within each message bubble. This is particularly useful + /// for displaying timestamps or other additional information related to the + /// message. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [AssistMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default footer widget. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messageFooterBuilder: + /// (BuildContext context, int index, AssistMessage message) { + /// return Row( + /// children: [ + /// IconButton( + /// icon: const Icon(Icons.thumb_up), + /// onPressed: () { + /// // Handle thumb up button click. + /// }, + /// ), + /// IconButton( + /// icon: const Icon(Icons.thumb_down), + /// onPressed: () { + /// // Handle thumb down button click. + /// }, + /// ), + /// ], + /// ); + /// }, + /// ); + /// } + /// ``` + final AssistWidgetBuilder? messageFooterBuilder; + + /// A callback function creates a widget to display as a loading indicator + /// while waiting for a response. + /// + /// This is particularly useful for displaying a loading spinner or other + /// visual indicator to let the user know that the AI is processing the + /// request. + /// + /// By default shimmer effect will be shown as a loading indicator. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseLoadingBuilder:(BuildContext context, int index, AssistMessage message) { + /// return const Center( + /// child: CustomLoadingIndicatorWidget(), + /// ); + /// }, + /// ); + /// } + /// ``` + final AssistWidgetBuilder? responseLoadingBuilder; + + /// Manages the visibility of the placeholder widget. + /// + /// Allowing it to either hide when a message is added or or stay visible, + /// positioned above all messages. + /// + /// Defaults to [AssistPlaceholderBehavior.scrollWithMessage]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// placeholderBehavior: AssistPlaceholderBehavior.hide, + /// placeholderBuilder: (BuildContext context) { + /// return Text('No messages yet'); + /// }, + /// ); + /// } + /// ``` + final AssistPlaceholderBehavior placeholderBehavior; + + /// Determines the alignment of the message bubbles. + /// + /// Defaults to [AssistMessageAlignment.auto]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messageAlignment: AssistMessageAlignment.start, + /// ); + /// } + /// ``` + final AssistMessageAlignment messageAlignment; + + /// Called when the suggestion is selected. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// onSuggestionItemSelected: (bool selected, int messageIndex, + /// AssistMessageSuggestion suggestion, int suggestionIndex) { + /// _handleSuggestionItemSelected(); + /// }, + /// ); + /// } + /// ``` + final AssistSuggestionItemSelectedCallback? onSuggestionItemSelected; + + /// Called when the response bubble toolbar is selected. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// onBubbleToolbarItemSelected: (bool selected, int messageIndex, + /// AssistMessageToolbarItem item, int toolbarItemIndex) { + /// _handleToolbarItemSelected(); + /// }, + /// ); + /// } + /// ``` + final AssistToolbarItemSelectedCallback? onToolbarItemSelected; + + /// Options for changing the appearance and behavior of + /// request message bubble. + /// + /// The [requestMessageSettings] property allows you to configure how + /// request chat bubbles are displayed. This includes customization options + /// for the user's avatar, username, timestamp, content background color, and + /// various padding and shape options. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistMessageSettings( + /// showAuthorName: true, + /// showTimestamp: true, + /// showAuthorAvatar: true, + /// widthFactor: 0.8, + /// avatarSize: Size.square(32.0), + /// headerPadding: EdgeInsetsDirectional.only(bottom: 3.0), + /// footerPadding: EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// ); + /// } + /// ``` + final AssistMessageSettings requestMessageSettings; + + /// Options for changing the appearance and behavior of + /// response message bubble. + /// + /// The [responseMessageSettings] property allows you to configure how + /// response chat bubbles are displayed. This includes customization options + /// for the user's avatar, username, timestamp, content background color, and + /// various padding and shape options. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseMessageSettings: const AssistMessageSettings( + /// showAuthorName: true, + /// showTimestamp: true, + /// showAuthorAvatar: true, + /// widthFactor: 0.8, + /// avatarSize: Size.square(32.0), + /// headerPadding: EdgeInsetsDirectional.only(bottom: 3.0), + /// footerPadding: EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// ); + /// } + /// ``` + final AssistMessageSettings responseMessageSettings; + + /// Options for changing the appearance of the response message toolbar. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20.0), + /// ), + /// backgroundColor: Colors.red, + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set state) { + /// return const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(8.0))); + /// }, + /// ), + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (Set state) { + /// if (state.contains(WidgetState.selected)) { + /// return Colors.blue; + /// } else if (state.contains(WidgetState.focused)) { + /// return Colors.blueGrey; + /// } else if (state.contains(WidgetState.hovered)) { + /// return Colors.lightBlueAccent; + /// } else if (state.contains(WidgetState.disabled)) { + /// return Colors.grey; + /// } + /// return Colors.lightBlue; + /// }, + /// ), + /// margin: const EdgeInsets.all(10), + /// itemPadding: const EdgeInsets.all(10), + /// spacing: 10, + /// runSpacing: 10, + /// ), + /// ); + /// } + /// ``` + final AssistMessageToolbarSettings responseToolbarSettings; + + @override + State createState() => _SfAIAssistViewState(); +} + +class _SfAIAssistViewState extends State { + final AssistMessageSettings _requestMessageSettings = + const AssistMessageSettings( + avatarPadding: EdgeInsetsDirectional.only(start: 16.0), + padding: EdgeInsets.all(8.0), + margin: EdgeInsetsDirectional.only(top: 24.0), + showAuthorAvatar: true, + ); + final AssistMessageSettings _responseMessageSettings = + const AssistMessageSettings( + avatarPadding: EdgeInsetsDirectional.only(end: 16.0), + padding: EdgeInsetsDirectional.symmetric(vertical: 8.0), + margin: EdgeInsetsDirectional.only(top: 24.0), + showAuthorAvatar: true, + ); + final InputBorder _defaultInputDecorBorder = const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(42.0)), + ); + final EdgeInsetsGeometry _defaultInputDecorContentPadding = + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 18.0); + + late FocusNode _focusNode; + late TextEditingController _textController; + + late ThemeData _themeData; + late SfAIAssistViewThemeData _defaultThemeData; + late SfAIAssistViewThemeData _userDefinedThemeData; + late SfAIAssistViewThemeData _effectiveAssistThemeData; + + void _updateThemeData(BuildContext context) { + _themeData = Theme.of(context); + _defaultThemeData = + _themeData.useMaterial3 + ? AIAssistViewM3ThemeData(context) + : AIAssistViewM2ThemeData(context); + _userDefinedThemeData = SfAIAssistViewTheme.of(context); + final TextStyle contentBaseTextStyle = _themeData.textTheme.bodyMedium! + .copyWith(color: _themeData.colorScheme.onSurface); + final TextStyle primaryHeaderBaseTextStyle = _themeData + .textTheme + .labelMedium! + .copyWith(color: _themeData.colorScheme.primary); + final TextStyle secondaryHeaderBaseTextStyle = _themeData + .textTheme + .labelSmall! + .copyWith(color: _themeData.colorScheme.onSurfaceVariant); + + _effectiveAssistThemeData = _userDefinedThemeData.copyWith( + actionButtonForegroundColor: + widget.actionButton?.foregroundColor ?? + _userDefinedThemeData.actionButtonForegroundColor ?? + _defaultThemeData.actionButtonForegroundColor, + actionButtonBackgroundColor: + widget.actionButton?.backgroundColor ?? + _userDefinedThemeData.actionButtonBackgroundColor ?? + _defaultThemeData.actionButtonBackgroundColor, + actionButtonDisabledForegroundColor: + _userDefinedThemeData.actionButtonDisabledForegroundColor ?? + _defaultThemeData.actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor: + _userDefinedThemeData.actionButtonDisabledBackgroundColor ?? + _defaultThemeData.actionButtonDisabledBackgroundColor, + actionButtonFocusColor: + widget.actionButton?.focusColor ?? + _userDefinedThemeData.actionButtonFocusColor ?? + _defaultThemeData.actionButtonFocusColor, + actionButtonHoverColor: + widget.actionButton?.hoverColor ?? + _userDefinedThemeData.actionButtonHoverColor ?? + _defaultThemeData.actionButtonHoverColor, + actionButtonSplashColor: + widget.actionButton?.splashColor ?? + _userDefinedThemeData.actionButtonSplashColor ?? + _defaultThemeData.actionButtonSplashColor, + actionButtonElevation: + widget.actionButton?.elevation ?? + _userDefinedThemeData.actionButtonElevation, + actionButtonFocusElevation: + widget.actionButton?.focusElevation ?? + _userDefinedThemeData.actionButtonFocusElevation, + actionButtonHoverElevation: + widget.actionButton?.hoverElevation ?? + _userDefinedThemeData.actionButtonHoverElevation, + actionButtonHighlightElevation: + widget.actionButton?.highlightElevation ?? + _userDefinedThemeData.actionButtonHighlightElevation, + actionButtonMouseCursor: + widget.actionButton?.mouseCursor ?? + _userDefinedThemeData.actionButtonMouseCursor ?? + _defaultThemeData.actionButtonMouseCursor, + actionButtonShape: + widget.actionButton?.shape ?? + _userDefinedThemeData.actionButtonShape ?? + _defaultThemeData.actionButtonShape, + requestAvatarBackgroundColor: + _userDefinedThemeData.requestAvatarBackgroundColor ?? + _defaultThemeData.requestAvatarBackgroundColor, + responseAvatarBackgroundColor: + _userDefinedThemeData.responseAvatarBackgroundColor ?? + _defaultThemeData.responseAvatarBackgroundColor, + requestMessageBackgroundColor: + widget.requestMessageSettings.backgroundColor ?? + _userDefinedThemeData.requestMessageBackgroundColor ?? + _defaultThemeData.requestMessageBackgroundColor, + responseMessageBackgroundColor: + widget.responseMessageSettings.backgroundColor ?? + _userDefinedThemeData.responseMessageBackgroundColor ?? + _defaultThemeData.responseMessageBackgroundColor, + editorTextStyle: contentBaseTextStyle + .merge(_userDefinedThemeData.editorTextStyle) + .merge(widget.composer?.textStyle), + requestContentTextStyle: contentBaseTextStyle + .merge(_userDefinedThemeData.requestContentTextStyle) + .merge(widget.requestMessageSettings.textStyle), + responseContentTextStyle: contentBaseTextStyle + .merge(_userDefinedThemeData.responseContentTextStyle) + .merge(widget.responseMessageSettings.textStyle), + requestPrimaryHeaderTextStyle: primaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.requestPrimaryHeaderTextStyle) + .merge(widget.requestMessageSettings.headerTextStyle), + responsePrimaryHeaderTextStyle: primaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.responsePrimaryHeaderTextStyle) + .merge(widget.responseMessageSettings.headerTextStyle), + requestSecondaryHeaderTextStyle: secondaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.requestSecondaryHeaderTextStyle) + .merge(widget.requestMessageSettings.headerTextStyle), + responseSecondaryHeaderTextStyle: secondaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.responseSecondaryHeaderTextStyle) + .merge(widget.responseMessageSettings.headerTextStyle), + requestMessageShape: + widget.requestMessageSettings.shape ?? + _userDefinedThemeData.requestMessageShape ?? + _defaultThemeData.requestMessageShape, + responseMessageShape: + widget.responseMessageSettings.shape ?? + _userDefinedThemeData.responseMessageShape ?? + _defaultThemeData.responseMessageShape, + suggestionItemBackgroundColor: + _userDefinedThemeData.suggestionItemBackgroundColor ?? + _defaultThemeData.suggestionItemBackgroundColor, + suggestionItemShape: + _userDefinedThemeData.suggestionItemShape ?? + _defaultThemeData.suggestionItemShape, + responseToolbarItemBackgroundColor: + _userDefinedThemeData.responseToolbarItemBackgroundColor ?? + _defaultThemeData.responseToolbarItemBackgroundColor, + responseToolbarItemShape: + _userDefinedThemeData.responseToolbarItemShape ?? + _defaultThemeData.responseToolbarItemShape, + ); + } + + TextStyle _suggestionTextStyle(Set states) { + TextStyle? userTextStyle; + final TextStyle baseTextStyle = _themeData.textTheme.bodyMedium!.copyWith( + color: _themeData.colorScheme.onSurface, + ); + final TextStyle defaultTextStyle = + _defaultThemeData.suggestionItemTextStyle!.resolve(states)!; + if (_userDefinedThemeData.suggestionItemTextStyle != null) { + userTextStyle = _userDefinedThemeData.suggestionItemTextStyle?.resolve( + states, + ); + } + + return baseTextStyle.merge(userTextStyle).merge(defaultTextStyle); + } + + Widget? _buildEditor(BuildContext context) { + if (widget.composer == null) { + return null; + } else { + return TextEditor( + composer: widget.composer!, + decoration: _effectiveInputDecoration(), + actionButton: widget.actionButton, + focusNode: _focusNode, + textController: _textController, + textStyle: _effectiveAssistThemeData.editorTextStyle, + ); + } + } + + InputDecoration? _effectiveInputDecoration() { + if (widget.composer == null) { + return null; + } else { + if (widget.composer!.builder != null) { + return null; + } + + final InputDecoration? decoration = widget.composer!.decoration; + if (decoration != null) { + final InputBorder effectiveBorder = + decoration.border ?? _defaultInputDecorBorder; + final EdgeInsetsGeometry effectiveContentPadding = + decoration.contentPadding ?? _defaultInputDecorContentPadding; + return decoration.copyWith( + border: effectiveBorder, + contentPadding: effectiveContentPadding, + ); + } + } + return null; + } + + Widget? _buildActionButton(BuildContext context) { + if (widget.actionButton == null) { + return null; + } else { + return ActionButtonWidget( + enabled: _isActionButtonEnabled(), + settings: widget.actionButton!, + composer: widget.composer, + textController: _textController, + actionButtonForegroundColor: + _effectiveAssistThemeData.actionButtonForegroundColor, + actionButtonDisabledForegroundColor: + _effectiveAssistThemeData.actionButtonDisabledForegroundColor, + actionButtonBackgroundColor: + _effectiveAssistThemeData.actionButtonBackgroundColor, + actionButtonDisabledBackgroundColor: + _effectiveAssistThemeData.actionButtonDisabledBackgroundColor, + actionButtonFocusColor: + _effectiveAssistThemeData.actionButtonFocusColor, + actionButtonHoverColor: + _effectiveAssistThemeData.actionButtonHoverColor, + actionButtonSplashColor: + _effectiveAssistThemeData.actionButtonSplashColor, + actionButtonElevation: _effectiveAssistThemeData.actionButtonElevation, + actionButtonFocusElevation: + _effectiveAssistThemeData.actionButtonFocusElevation, + actionButtonHoverElevation: + _effectiveAssistThemeData.actionButtonHoverElevation, + actionButtonHighlightElevation: + _effectiveAssistThemeData.actionButtonHighlightElevation, + actionButtonDisabledElevation: + _effectiveAssistThemeData.actionButtonDisabledElevation, + actionButtonShape: _effectiveAssistThemeData.actionButtonShape, + ); + } + } + + bool _isActionButtonEnabled() { + if (widget.messages.isEmpty) { + return true; + } else { + return !widget.messages.last.isRequested; + } + } + + BubbleAlignment _toBubbleAlignment(AssistMessageAlignment value) { + switch (value) { + case AssistMessageAlignment.start: + return BubbleAlignment.start; + case AssistMessageAlignment.end: + return BubbleAlignment.end; + case AssistMessageAlignment.auto: + return BubbleAlignment.auto; + } + } + + PlaceholderBehavior _toPlaceholderBehavior(AssistPlaceholderBehavior value) { + switch (value) { + case AssistPlaceholderBehavior.hideOnMessage: + return PlaceholderBehavior.hideOnMessage; + case AssistPlaceholderBehavior.scrollWithMessage: + return PlaceholderBehavior.scrollWithMessage; + } + } + + @override + void initState() { + _textController = TextEditingController(); + _focusNode = FocusNode(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _updateThemeData(context); + final Widget? editor = _buildEditor(context); + final Widget? actionButton = _buildActionButton(context); + + return LayoutHandler( + children: [ + AssistConversationArea( + messages: widget.messages, + outgoingBubbleSettings: _requestMessageSettings.mergeWith( + widget.requestMessageSettings, + ), + incomingBubbleSettings: _responseMessageSettings.mergeWith( + widget.responseMessageSettings, + ), + bubbleAlignment: _toBubbleAlignment(widget.messageAlignment), + placeholderBehavior: _toPlaceholderBehavior( + widget.placeholderBehavior, + ), + placeholderBuilder: widget.placeholderBuilder, + bubbleHeaderBuilder: widget.messageHeaderBuilder, + bubbleAvatarBuilder: widget.messageAvatarBuilder, + bubbleContentBuilder: widget.messageContentBuilder, + bubbleFooterBuilder: widget.messageFooterBuilder, + responseLoadingBuilder: widget.responseLoadingBuilder, + outgoingAvatarBackgroundColor: + _effectiveAssistThemeData.requestAvatarBackgroundColor, + incomingAvatarBackgroundColor: + _effectiveAssistThemeData.responseAvatarBackgroundColor, + outgoingBubbleContentBackgroundColor: + _effectiveAssistThemeData.requestMessageBackgroundColor, + incomingBubbleContentBackgroundColor: + _effectiveAssistThemeData.responseMessageBackgroundColor, + outgoingPrimaryHeaderTextStyle: + _effectiveAssistThemeData.requestPrimaryHeaderTextStyle, + incomingPrimaryHeaderTextStyle: + _effectiveAssistThemeData.responsePrimaryHeaderTextStyle, + outgoingSecondaryHeaderTextStyle: + _effectiveAssistThemeData.requestSecondaryHeaderTextStyle, + incomingSecondaryHeaderTextStyle: + _effectiveAssistThemeData.responseSecondaryHeaderTextStyle, + outgoingContentTextStyle: + _effectiveAssistThemeData.requestContentTextStyle, + incomingContentTextStyle: + _effectiveAssistThemeData.responseContentTextStyle, + suggestionItemTextStyle: _suggestionTextStyle, + outgoingBubbleContentShape: + _effectiveAssistThemeData.requestMessageShape, + incomingBubbleContentShape: + _effectiveAssistThemeData.responseMessageShape, + suggestionBackgroundColor: + _effectiveAssistThemeData.suggestionBackgroundColor, + suggestionBackgroundShape: + _effectiveAssistThemeData.suggestionBackgroundShape, + suggestionItemBackgroundColor: + _effectiveAssistThemeData.suggestionItemBackgroundColor, + suggestionItemShape: _effectiveAssistThemeData.suggestionItemShape, + responseToolbarBackgroundColor: + _effectiveAssistThemeData.responseToolbarBackgroundColor, + responseToolbarBackgroundShape: + _effectiveAssistThemeData.responseToolbarBackgroundShape, + responseToolbarItemBackgroundColor: + _effectiveAssistThemeData.responseToolbarItemBackgroundColor, + responseToolbarItemShape: + _effectiveAssistThemeData.responseToolbarItemShape, + onSuggestionItemSelected: widget.onSuggestionItemSelected, + onBubbleToolbarItemSelected: widget.onToolbarItemSelected, + responseToolbarSettings: widget.responseToolbarSettings, + themeData: _themeData, + ), + if (editor != null || actionButton != null) + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (editor != null) Expanded(child: editor), + if (actionButton != null) actionButton, + ], + ), + ], + ); + } + + @override + void dispose() { + _focusNode.dispose(); + _textController.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_chat/lib/src/assist_view/conversion_area.dart b/packages/syncfusion_flutter_chat/lib/src/assist_view/conversion_area.dart new file mode 100644 index 000000000..3ebc03aa9 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/assist_view/conversion_area.dart @@ -0,0 +1,1210 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' hide TextDirection; + +import '../../assist_view.dart'; +import '../conversion_area.dart'; +import '../settings.dart'; + +class AssistConversationArea extends ConversationArea { + const AssistConversationArea({ + super.key, + required super.messages, + required super.outgoingBubbleSettings, + required super.incomingBubbleSettings, + super.bubbleAlignment = BubbleAlignment.auto, + super.placeholderBehavior = PlaceholderBehavior.hideOnMessage, + super.placeholderBuilder, + super.bubbleHeaderBuilder, + super.bubbleAvatarBuilder, + super.bubbleContentBuilder, + super.bubbleFooterBuilder, + this.responseLoadingBuilder, + super.outgoingAvatarBackgroundColor, + super.incomingAvatarBackgroundColor, + super.outgoingBubbleContentBackgroundColor, + super.incomingBubbleContentBackgroundColor, + super.outgoingPrimaryHeaderTextStyle, + super.incomingPrimaryHeaderTextStyle, + super.outgoingSecondaryHeaderTextStyle, + super.incomingSecondaryHeaderTextStyle, + super.outgoingContentTextStyle, + super.incomingContentTextStyle, + required super.suggestionItemTextStyle, + super.outgoingBubbleContentShape, + super.incomingBubbleContentShape, + super.suggestionBackgroundColor, + super.suggestionBackgroundShape, + super.suggestionItemBackgroundColor, + super.suggestionItemShape, + this.responseToolbarBackgroundColor, + this.responseToolbarBackgroundShape, + this.responseToolbarItemBackgroundColor, + this.responseToolbarItemShape, + this.onSuggestionItemSelected, + this.onBubbleToolbarItemSelected, + required this.responseToolbarSettings, + required super.themeData, + }); + + final AssistWidgetBuilder? responseLoadingBuilder; + final AssistSuggestionItemSelectedCallback? onSuggestionItemSelected; + final AssistToolbarItemSelectedCallback? onBubbleToolbarItemSelected; + final AssistMessageToolbarSettings responseToolbarSettings; + final Color? responseToolbarBackgroundColor; + final ShapeBorder? responseToolbarBackgroundShape; + final WidgetStateProperty? responseToolbarItemBackgroundColor; + final WidgetStateProperty? responseToolbarItemShape; + + @override + State> createState() => + _AssistConversationAreaState(); +} + +class _AssistConversationAreaState + extends ConversationAreaState { + @override + AssistConversationArea get widget => super.widget as AssistConversationArea; + + @override + EdgeInsetsGeometry effectiveAvatarPadding( + bool isOutgoingMessage, + EdgeInsetsGeometry? avatarPadding, + ) { + return avatarPadding ?? EdgeInsets.zero; + } + + @override + bool isFirstInGroup(int index, AssistMessage message) { + return true; + } + + @override + bool isOutgoingMessage(AssistMessage message) { + return message.isRequested; + } + + int listItemCount() { + if (widget.messages.isNotEmpty) { + // If the last message is 'request', add an additional item for + // the loading indicator internally. + final AssistMessage lastMessage = widget.messages.last; + if (lastMessage.isRequested) { + return messageCount + 1; + } + } + return messageCount; + } + + @override + Widget buildListView(double width, double height) { + final int itemCount = listItemCount(); + return ListView.builder( + itemCount: itemCount, + reverse: true, + itemBuilder: (BuildContext context, int index) { + index = itemCount - index - 1; + if (index == messageCount) { + return _buildLoaderMessageBubble(index, width, height); + } + return buildMessageBubble(index, width, height); + }, + findChildIndexCallback: (Key key) { + final IndexedValueKey? valueKey = key as IndexedValueKey?; + if (valueKey != null) { + return messageCount - valueKey.value - 1; + } + return null; + }, + ); + } + + Widget _buildLoaderMessageBubble(int index, double width, double height) { + return _AssistMessageBubble.loader( + key: IndexedValueKey(index), + index: index, + maxWidth: width, + widthFactor: widget.incomingBubbleSettings.widthFactor, + message: const AssistMessage.response( + data: '', + author: AssistMessageAuthor(id: 'AI', name: 'AI'), + ), + showUserAvatar: widget.incomingBubbleSettings.showAuthorAvatar, + padding: widget.incomingBubbleSettings.margin ?? EdgeInsets.zero, + avatarPadding: effectiveAvatarPadding( + false, + widget.incomingBubbleSettings.avatarPadding, + ), + avatarSize: widget.incomingBubbleSettings.avatarSize, + themeData: widget.themeData, + alignment: effectiveBubbleAlignment(false), + responseLoadingBuilder: widget.responseLoadingBuilder, + responseToolbarSettings: widget.responseToolbarSettings, + textDirection: textDirection, + alignmentDirection: alignmentBasedTextDirection(false, textDirection), + ); + } + + @override + Widget buildMessageBubble(int index, double width, double height) { + final AssistMessage message = widget.messages[index]; + final bool isFromCurrentUser = message.isRequested; + + ShapeBorder? contentShape; + Color? avatarBackgroundColor; + Color? contentBackgroundColor; + TextStyle? contentTextStyle; + TextStyle? primaryHeaderTextStyle; + TextStyle? secondaryHeaderTextStyle; + MessageSettings settings; + if (isFromCurrentUser) { + contentShape = widget.outgoingBubbleContentShape; + avatarBackgroundColor = widget.outgoingAvatarBackgroundColor; + contentBackgroundColor = widget.outgoingBubbleContentBackgroundColor; + contentTextStyle = widget.outgoingContentTextStyle; + primaryHeaderTextStyle = widget.outgoingPrimaryHeaderTextStyle; + secondaryHeaderTextStyle = widget.outgoingSecondaryHeaderTextStyle; + settings = widget.outgoingBubbleSettings; + } else { + contentShape = widget.incomingBubbleContentShape; + avatarBackgroundColor = widget.incomingAvatarBackgroundColor; + contentBackgroundColor = widget.incomingBubbleContentBackgroundColor; + contentTextStyle = widget.incomingContentTextStyle; + primaryHeaderTextStyle = widget.incomingPrimaryHeaderTextStyle; + secondaryHeaderTextStyle = widget.incomingSecondaryHeaderTextStyle; + settings = widget.incomingBubbleSettings; + } + + Widget result = _AssistMessageBubble( + index: index, + maxWidth: width, + widthFactor: settings.widthFactor, + message: message, + isOutgoing: isFromCurrentUser, + isFirstInGroup: true, + headerBuilder: widget.bubbleHeaderBuilder, + avatarBuilder: widget.bubbleAvatarBuilder, + contentBuilder: widget.bubbleContentBuilder, + footerBuilder: widget.bubbleFooterBuilder, + responseLoadingBuilder: widget.responseLoadingBuilder, + showUserAvatar: settings.showAuthorAvatar, + showUserName: settings.showAuthorName, + showTimestamp: settings.showTimestamp, + timestampFormat: settings.timestampFormat, + alignment: effectiveBubbleAlignment(isFromCurrentUser), + contentShape: contentShape, + avatarBackgroundColor: avatarBackgroundColor, + contentBackgroundColor: contentBackgroundColor, + contentTextStyle: contentTextStyle, + primaryHeaderTextStyle: primaryHeaderTextStyle, + secondaryHeaderTextStyle: secondaryHeaderTextStyle, + suggestionItemTextStyle: widget.suggestionItemTextStyle, + padding: settings.margin ?? EdgeInsets.zero, + contentPadding: settings.padding ?? EdgeInsets.zero, + avatarPadding: effectiveAvatarPadding( + isFromCurrentUser, + settings.avatarPadding, + ), + headerPadding: settings.headerPadding, + footerPadding: settings.footerPadding, + avatarSize: settings.avatarSize, + suggestionBackgroundColor: widget.suggestionBackgroundColor, + suggestionBackgroundShape: widget.suggestionBackgroundShape, + suggestionItemBackgroundColor: widget.suggestionItemBackgroundColor, + suggestionItemShape: widget.suggestionItemShape, + responseToolbarBackgroundColor: widget.responseToolbarBackgroundColor, + responseToolbarBackgroundShape: widget.responseToolbarBackgroundShape, + responseToolbarItemBackgroundColor: + widget.responseToolbarItemBackgroundColor, + responseToolbarItemShape: widget.responseToolbarItemShape, + onSuggestionItemSelected: widget.onSuggestionItemSelected, + onToolbarItemSelected: widget.onBubbleToolbarItemSelected, + responseToolbarSettings: widget.responseToolbarSettings, + themeData: widget.themeData, + textDirection: textDirection, + alignmentDirection: alignmentBasedTextDirection( + isFromCurrentUser, + textDirection, + ), + ); + + if (index == 0 && + widget.placeholderBehavior == PlaceholderBehavior.scrollWithMessage && + widget.placeholderBuilder != null) { + result = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: width, maxHeight: height), + child: widget.placeholderBuilder!(context), + ), + ), + result, + ], + ); + } + + return KeyedSubtree(key: IndexedValueKey(index), child: result); + } +} + +class _AssistMessageBubble extends MessageBubble { + const _AssistMessageBubble({ + required super.index, + required super.maxWidth, + super.widthFactor = 0.8, + required super.message, + required super.isOutgoing, + required super.isFirstInGroup, + super.headerBuilder, + super.avatarBuilder, + super.contentBuilder, + super.footerBuilder, + this.responseLoadingBuilder, + super.showUserAvatar, + super.showUserName = false, + super.showTimestamp = false, + super.timestampFormat, + required super.alignment, + super.contentShape, + super.avatarBackgroundColor, + super.contentBackgroundColor, + super.contentTextStyle, + super.primaryHeaderTextStyle, + super.secondaryHeaderTextStyle, + super.suggestionItemTextStyle, + super.padding = const EdgeInsets.all(2.0), + super.contentPadding = const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + required super.avatarPadding, + super.headerPadding = const EdgeInsetsDirectional.only( + top: 14.0, + bottom: 4.0, + ), + super.footerPadding = const EdgeInsetsDirectional.only(top: 4.0), + super.avatarSize = const Size.square(32.0), + super.suggestionBackgroundColor, + super.suggestionBackgroundShape, + super.suggestionItemBackgroundColor, + super.suggestionItemShape, + this.responseToolbarBackgroundColor, + this.responseToolbarBackgroundShape, + this.responseToolbarItemBackgroundColor, + this.responseToolbarItemShape, + this.onSuggestionItemSelected, + this.onToolbarItemSelected, + required this.responseToolbarSettings, + required super.themeData, + required super.textDirection, + required super.alignmentDirection, + }) : showLoadingIndicator = false; + + const _AssistMessageBubble.loader({ + super.key, + required super.index, + required super.maxWidth, + super.widthFactor = 0.8, + required super.message, + super.isOutgoing = false, + super.isFirstInGroup = true, + required super.showUserAvatar, + super.showUserName = false, + super.showTimestamp = false, + required super.alignment, + super.padding = const EdgeInsets.all(2.0), + required super.avatarPadding, + super.avatarSize = const Size.square(32.0), + required this.responseLoadingBuilder, + required this.responseToolbarSettings, + required super.themeData, + required super.textDirection, + required super.alignmentDirection, + }) : showLoadingIndicator = true, + onSuggestionItemSelected = null, + onToolbarItemSelected = null, + responseToolbarBackgroundColor = null, + responseToolbarBackgroundShape = null, + responseToolbarItemBackgroundColor = null, + responseToolbarItemShape = null; + + final bool showLoadingIndicator; + final AssistWidgetBuilder? responseLoadingBuilder; + final AssistSuggestionItemSelectedCallback? onSuggestionItemSelected; + final AssistToolbarItemSelectedCallback? onToolbarItemSelected; + final AssistMessageToolbarSettings responseToolbarSettings; + final Color? responseToolbarBackgroundColor; + final ShapeBorder? responseToolbarBackgroundShape; + final WidgetStateProperty? responseToolbarItemBackgroundColor; + final WidgetStateProperty? responseToolbarItemShape; + + @override + State createState() => _AssistMessageBubbleState(); +} + +class _AssistMessageBubbleState extends MessageBubbleState { + @override + _AssistMessageBubble get widget => super.widget as _AssistMessageBubble; + + @override + Widget? buildDefaultHeader() { + if (widget.showLoadingIndicator) { + return null; + } + + final Widget? userName = _buildUserName(); + final Widget? timestamp = _buildTimestamp(); + if (userName != null || timestamp != null) { + return Row( + mainAxisSize: MainAxisSize.min, + textDirection: TextDirection.ltr, + children: [ + if (userName != null) userName, + if (userName != null && timestamp != null) const SizedBox(width: 4.0), + if (timestamp != null) timestamp, + ], + ); + } + + return null; + } + + Widget? _buildUserName() { + if (!widget.showUserName || + widget.message.author == null || + widget.message.author!.name.isEmpty) { + return null; + } + + return Text( + widget.message.author!.name, + style: widget.primaryHeaderTextStyle, + textDirection: TextDirection.ltr, + ); + } + + Widget? _buildTimestamp() { + if (!widget.showTimestamp || widget.message.time == null) { + return null; + } + + final DateFormat effectiveDateFormat = + widget.timestampFormat ?? MessageBubbleState.defaultTimestampFormat; + return Text( + effectiveDateFormat.format(widget.message.time!), + style: widget.secondaryHeaderTextStyle, + textDirection: TextDirection.ltr, + ); + } + + @override + Widget buildMessage(BuildContext context) { + return Column( + crossAxisAlignment: + widget.alignment == BubbleAlignment.end + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + textDirection: TextDirection.ltr, + children: _buildElements(context), + ); + } + + List _buildElements(BuildContext context) { + if (widget.showLoadingIndicator) { + return [ + _buildLoadingIndicator(context, widget.showUserAvatar ?? false), + ]; + } + + final List children = []; + final Widget? header = buildHeader(context); + if (header != null) { + children.add(header); + } + + final Widget? avatar = buildAvatar(context); + children.add( + BubbleLayout( + showUserAvatar: hasAvatar(), + avatarSize: widget.avatarSize, + avatarPadding: widget.avatarPadding.resolve(widget.alignmentDirection), + alignment: widget.alignment, + children: [if (avatar != null) avatar, buildContent(context)], + ), + ); + + final Widget? suggestion = buildSuggestion(context); + if (suggestion != null) { + children.add(suggestion); + } + + final Widget? toolbar = _buildToolbar(context); + if (toolbar != null) { + children.add(toolbar); + } + + final Widget? footer = buildFooter(context); + if (footer != null) { + children.add(footer); + } + + return children; + } + + Widget? _buildToolbar(BuildContext context) { + if (widget.showLoadingIndicator || + widget.message.isRequested || + widget.message.toolbarItems == null || + widget.message.toolbarItems!.isEmpty) { + return null; + } + + final Widget result = _ToolbarArea( + messageIndex: widget.index, + message: widget.message, + toolbarItems: widget.message.toolbarItems!, + onToolbarItemSelected: widget.onToolbarItemSelected, + toolbarSettings: widget.responseToolbarSettings, + backgroundColor: widget.responseToolbarBackgroundColor, + backgroundShape: widget.responseToolbarBackgroundShape, + itemBackgroundColor: widget.responseToolbarItemBackgroundColor, + itemShape: widget.responseToolbarItemShape, + ); + + return buildWithAvatarGap(result); + } + + Widget _buildLoadingIndicator(BuildContext context, bool showAvatar) { + if (widget.responseLoadingBuilder != null) { + return widget.responseLoadingBuilder!.call( + context, + widget.index, + widget.message, + ); + } + + final Color edgeColor; + final Color midColor; + final ColorScheme colorScheme = widget.themeData.colorScheme; + if (widget.themeData.brightness == Brightness.light) { + edgeColor = colorScheme.surfaceContainer; + midColor = Colors.white; + } else { + edgeColor = colorScheme.surfaceContainer; + midColor = colorScheme.surfaceContainer.withValues(alpha: 0.12); + } + return _Shimmer( + bubbleWidth: availableContentWidth() * widget.widthFactor, + edgeColor: edgeColor, + midColor: midColor, + avatarSize: showAvatar ? widget.avatarSize : Size.zero, + avatarPadding: + showAvatar + ? widget.avatarPadding.resolve(widget.alignmentDirection) + : null, + alignment: widget.alignment, + ); + } + + @override + bool displayAvatar() { + return widget.showUserAvatar ?? !widget.isOutgoing; + } + + @override + bool hasAvatar() { + return displayAvatar() && + (widget.avatarBuilder != null || + (widget.message.author != null && + (widget.message.author!.avatar != null || + widget.message.author!.name.isNotEmpty))); + } + + @override + Widget? buildAvatar(BuildContext context, {Widget? result}) { + if (widget.showLoadingIndicator || !hasAvatar()) { + return null; + } + + Widget? result; + if (widget.avatarBuilder != null) { + result = widget.avatarBuilder!(context, widget.index, widget.message); + } else { + result = _buildDefaultAvatar(result); + } + + return super.buildAvatar(context, result: result); + } + + Widget? _buildDefaultAvatar(Widget? result) { + if (widget.message.author!.avatar != null) { + result = CircleAvatar( + backgroundImage: widget.message.author!.avatar, + backgroundColor: widget.avatarBackgroundColor, + ); + } else { + if (widget.message.author!.name.isNotEmpty) { + result = CircleAvatar( + backgroundColor: widget.avatarBackgroundColor, + child: Text( + widget.message.author!.name[0], + style: widget.contentTextStyle, + ), + ); + } + } + return result; + } + + @override + Widget buildText() { + return Text( + widget.message.text, + style: widget.contentTextStyle, + textDirection: TextDirection.ltr, + ); + } + + @override + Widget? buildSuggestion(BuildContext context) { + if (widget.showLoadingIndicator || + widget.message.suggestions == null || + widget.message.suggestions!.isEmpty) { + return null; + } + + final List suggestions = widget.message.suggestions!; + final SuggestionSettings settings = + widget.message.suggestionSettings ?? const AssistSuggestionSettings(); + Widget result = _AssistSuggestionArea( + message: widget.message, + messageIndex: widget.index, + suggestions: suggestions, + selectionType: settings.selectionType, + shape: settings.shape ?? widget.suggestionBackgroundShape, + itemShape: settings.itemShape ?? widget.suggestionItemShape, + itemTextStyle: widget.suggestionItemTextStyle, + orientation: settings.orientation, + itemOverflow: settings.itemOverflow, + spacing: settings.spacing, + runSpacing: settings.runSpacing, + backgroundColor: + settings.backgroundColor ?? widget.suggestionBackgroundColor, + itemBackgroundColor: + settings.itemBackgroundColor ?? widget.suggestionItemBackgroundColor, + padding: settings.margin.resolve(widget.alignmentDirection), + itemPadding: settings.itemPadding.resolve(widget.alignmentDirection), + onSuggestionItemSelected: widget.onSuggestionItemSelected, + ); + result = ClipRect( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: availableContentWidth() * widget.widthFactor, + ), + child: result, + ), + ); + return buildWithAvatarGap(result); + } +} + +class _AssistSuggestionArea extends SuggestionArea { + const _AssistSuggestionArea({ + required super.message, + required super.messageIndex, + required super.suggestions, + required super.selectionType, + super.shape, + super.itemShape, + required super.itemTextStyle, + required super.orientation, + required super.itemOverflow, + required super.spacing, + required super.runSpacing, + super.backgroundColor, + super.itemBackgroundColor, + required super.padding, + required super.itemPadding, + this.onSuggestionItemSelected, + }); + + final AssistSuggestionItemSelectedCallback? onSuggestionItemSelected; + + @override + State createState() => _AssistSuggestionAreaState(); +} + +class _AssistSuggestionAreaState extends SuggestionAreaState { + @override + _AssistSuggestionArea get widget => super.widget as _AssistSuggestionArea; + + @override + Widget buildSuggestionItem(int index, bool enabled, bool selected) { + return _AssistSuggestionItem( + index: index, + message: widget.message, + messageIndex: widget.messageIndex, + enabled: enabled, + selected: selected, + details: widget.message.suggestions![index], + itemBackgroundColor: widget.itemBackgroundColor, + itemShape: widget.itemShape, + textStyle: widget.itemTextStyle, + itemPadding: widget.itemPadding, + selectionType: widget.selectionType, + selectedIndices: selectedIndices, + onSuggestionItemSelected: widget.onSuggestionItemSelected, + ); + } +} + +class _AssistSuggestionItem extends SuggestionItem { + const _AssistSuggestionItem({ + required super.index, + required super.message, + required super.messageIndex, + required super.enabled, + required super.selected, + required super.details, + required super.itemBackgroundColor, + required super.itemShape, + required super.textStyle, + required super.itemPadding, + required super.selectionType, + required super.selectedIndices, + this.onSuggestionItemSelected, + }); + + final AssistSuggestionItemSelectedCallback? onSuggestionItemSelected; + + @override + State createState() => _AssistSuggestionItemState(); +} + +class _AssistSuggestionItemState extends SuggestionItemState { + @override + _AssistSuggestionItem get widget => super.widget as _AssistSuggestionItem; + + @override + void invokeSelectedCallback(int suggestionIndex, {required bool selected}) { + final AssistMessageSuggestion suggestion = + widget.message.suggestions![suggestionIndex]; + widget.onSuggestionItemSelected?.call( + selected, + widget.messageIndex, + suggestion, + suggestionIndex, + ); + } +} + +class _Shimmer extends StatefulWidget { + const _Shimmer({ + required this.bubbleWidth, + required this.edgeColor, + required this.midColor, + required this.avatarSize, + required this.avatarPadding, + required this.alignment, + }); + + final double bubbleWidth; + final Color edgeColor; + final Color midColor; + final Size avatarSize; + final EdgeInsets? avatarPadding; + final BubbleAlignment alignment; + + @override + _ShimmerState createState() => _ShimmerState(); +} + +class _ShimmerState extends State<_Shimmer> + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + _animationController = AnimationController.unbounded(vsync: this) + ..repeat(min: -1.0, max: 1.0, period: const Duration(milliseconds: 750)); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return _ShimmerRenderObjectWidget( + bubbleWidth: widget.bubbleWidth, + edgeColor: widget.edgeColor, + midColor: widget.midColor, + avatarSize: widget.avatarSize, + avatarPadding: widget.avatarPadding, + alignment: widget.alignment, + animation: _animationController, + ); + } + + @override + void dispose() { + _animationController + ..stop() + ..dispose(); + super.dispose(); + } +} + +class _ShimmerRenderObjectWidget extends LeafRenderObjectWidget { + const _ShimmerRenderObjectWidget({ + required this.bubbleWidth, + required this.animation, + required this.edgeColor, + required this.midColor, + required this.avatarSize, + required this.alignment, + required this.avatarPadding, + }); + + final double bubbleWidth; + final Color edgeColor; + final Color midColor; + final Size avatarSize; + final EdgeInsets? avatarPadding; + final BubbleAlignment alignment; + final Animation animation; + + @override + RenderObject createRenderObject(BuildContext context) { + return _ShimmerRenderBox( + bubbleWidth: bubbleWidth, + edgeColor: edgeColor, + midColor: midColor, + avatarSize: avatarSize, + avatarPadding: avatarPadding, + alignment: alignment, + animation: animation, + ); + } + + @override + void updateRenderObject( + BuildContext context, + _ShimmerRenderBox renderObject, + ) { + renderObject + ..bubbleWidth = bubbleWidth + ..edgeColor = edgeColor + ..midColor = midColor + ..avatarSize = avatarSize + ..avatarPadding = avatarPadding + ..alignment = alignment; + } +} + +class _ShimmerRenderBox extends RenderBox { + _ShimmerRenderBox({ + required double bubbleWidth, + required Color edgeColor, + required Color midColor, + required Size avatarSize, + required EdgeInsets? avatarPadding, + required BubbleAlignment alignment, + required Animation animation, + }) : _bubbleWidth = bubbleWidth, + _edgeColor = edgeColor, + _midColor = midColor, + _avatarSize = avatarSize, + _avatarPadding = avatarPadding, + _alignment = alignment, + _animation = animation; + + final Animation _animation; + final double _stripeHeight = 16.0; + final double _spaceBetweenStripes = 8.0; + final Radius _stripeBorderRadius = const Radius.circular(4.0); + + double _stripeWidth = 0.0; + + Color get edgeColor => _edgeColor; + Color _edgeColor; + set edgeColor(Color value) { + if (_edgeColor == value) { + _edgeColor = value; + markNeedsPaint(); + } + } + + Color get midColor => _midColor; + Color _midColor; + set midColor(Color value) { + if (_midColor != value) { + _midColor = value; + markNeedsPaint(); + } + } + + double get bubbleWidth => _bubbleWidth; + double _bubbleWidth = 0.0; + set bubbleWidth(double value) { + if (_bubbleWidth != value) { + _bubbleWidth = value; + markNeedsPaint(); + } + } + + Size get avatarSize => _avatarSize; + Size _avatarSize = Size.zero; + set avatarSize(Size value) { + if (_avatarSize != value) { + _avatarSize = value; + markNeedsPaint(); + } + } + + EdgeInsets? get avatarPadding => _avatarPadding; + EdgeInsets? _avatarPadding; + set avatarPadding(EdgeInsets? value) { + if (_avatarPadding != value) { + _avatarPadding = value; + markNeedsPaint(); + } + } + + BubbleAlignment get alignment => _alignment; + BubbleAlignment _alignment; + set alignment(BubbleAlignment value) { + if (_alignment != value) { + _alignment = value; + markNeedsLayout(); + } + } + + double _avatarWidth() { + return _avatarSize.width + (_avatarPadding?.horizontal ?? 0.0); + } + + Shader? _createShimmerShader(Offset offset) { + return LinearGradient( + colors: [edgeColor, midColor, edgeColor], + transform: _ShimmerTransform(animationValue: _animation.value), + ).createShader(offset & size); + } + + void _drawCircle(PaintingContext context, Offset offset, Paint paint) { + context.canvas.drawOval(offset & avatarSize, paint); + } + + void _drawRect( + PaintingContext context, + Offset offset, + Size size, + Paint paint, + ) { + final RRect rRect = RRect.fromRectAndRadius( + offset & size, + _stripeBorderRadius, + ); + context.canvas.drawRRect(rRect, paint); + } + + @override + void attach(PipelineOwner owner) { + _animation.addListener(markNeedsLayout); + super.attach(owner); + } + + @override + void detach() { + _animation.removeListener(markNeedsLayout); + super.detach(); + } + + @override + void performLayout() { + const int stripeCount = 3; + final double totalStripeHeight = _stripeHeight * stripeCount; + final double totalStripeSpacing = _spaceBetweenStripes * (stripeCount - 1); + final double totalHeight = totalStripeHeight + totalStripeSpacing; + + final double avatarWidth = _avatarWidth(); + _stripeWidth = clampDouble(bubbleWidth - avatarWidth, 0, bubbleWidth); + size = Size(bubbleWidth, totalHeight); + } + + @override + void paint(PaintingContext context, Offset offset) { + final Paint stripePaint = Paint()..shader = _createShimmerShader(offset); + final EdgeInsets avatarPad = avatarPadding ?? EdgeInsets.zero; + final double avatarWidth = _avatarWidth(); + + Offset circleOffset = offset; + double stripeStartX = 0.0; + switch (alignment) { + case BubbleAlignment.start: + stripeStartX = offset.dx + avatarWidth; + if (avatarWidth > 0.0) { + circleOffset = offset.translate(avatarPad.left, avatarPad.top); + } + break; + + case BubbleAlignment.end: + case BubbleAlignment.auto: + stripeStartX = offset.dx; + if (avatarWidth > 0.0) { + circleOffset = offset.translate( + _stripeWidth + avatarPad.left, + avatarPad.top, + ); + } + break; + } + + // Draw avatar. + if (avatarWidth > 0.0) { + _drawCircle(context, circleOffset, stripePaint); + } + + double stripeStartY = 0.0; + // First stripe with full width. + _drawRect( + context, + Offset(stripeStartX, offset.dy + stripeStartY), + Size(_stripeWidth, _stripeHeight), + stripePaint, + ); + + stripeStartY += _stripeHeight + _spaceBetweenStripes; + // Second stripe with full width. + _drawRect( + context, + Offset(stripeStartX, offset.dy + stripeStartY), + Size(_stripeWidth, _stripeHeight), + stripePaint, + ); + + stripeStartY += _stripeHeight + _spaceBetweenStripes; + // Third stripe with half width. + _drawRect( + context, + Offset(stripeStartX, offset.dy + stripeStartY), + Size(_stripeWidth / 2, _stripeHeight), + stripePaint, + ); + } +} + +class _ShimmerTransform extends GradientTransform { + const _ShimmerTransform({required this.animationValue}); + + final double animationValue; + + @override + Matrix4 transform(Rect bounds, {TextDirection? textDirection}) { + return Matrix4.translationValues(bounds.width * animationValue, 0.0, 0.0); + } +} + +class _ToolbarArea extends StatelessWidget { + const _ToolbarArea({ + required this.messageIndex, + required this.message, + required this.toolbarItems, + required this.onToolbarItemSelected, + required this.toolbarSettings, + required this.backgroundColor, + required this.backgroundShape, + required this.itemBackgroundColor, + required this.itemShape, + }); + + final int messageIndex; + final AssistMessage message; + final List toolbarItems; + final AssistToolbarItemSelectedCallback? onToolbarItemSelected; + final AssistMessageToolbarSettings toolbarSettings; + final Color? backgroundColor; + final ShapeBorder? backgroundShape; + final WidgetStateProperty? itemBackgroundColor; + final WidgetStateProperty? itemShape; + + @override + Widget build(BuildContext context) { + final int itemCount = toolbarItems.length; + Widget result = Wrap( + spacing: toolbarSettings.spacing, + runSpacing: toolbarSettings.runSpacing, + children: List.generate(itemCount, (int index) { + final AssistMessageToolbarItem toolbarItem = toolbarItems[index]; + Widget result = _ToolbarItem( + messageIndex: messageIndex, + index: index, + item: toolbarItem, + selected: toolbarItem.isSelected, + shape: toolbarSettings.itemShape ?? itemShape, + padding: toolbarSettings.itemPadding, + backgroundColor: + toolbarSettings.itemBackgroundColor ?? itemBackgroundColor, + onItemSelected: onToolbarItemSelected, + ); + + if (toolbarItem.tooltip != null && toolbarItem.tooltip!.isNotEmpty) { + result = Tooltip(message: toolbarItem.tooltip, child: result); + } + + return result; + }), + ); + + if (toolbarSettings.margin != EdgeInsets.zero) { + result = Padding(padding: toolbarSettings.margin, child: result); + } + + final Color? effectiveBackgroundColor = + toolbarSettings.backgroundColor ?? backgroundColor; + final ShapeBorder? effectiveBackgroundShape = + toolbarSettings.shape ?? backgroundShape; + if (effectiveBackgroundShape != null && + effectiveBackgroundColor != null && + effectiveBackgroundColor != Colors.transparent) { + result = BorderShape( + color: effectiveBackgroundColor, + shape: effectiveBackgroundShape, + child: result, + ); + } + + return result; + } +} + +class _ToolbarItem extends StatefulWidget { + const _ToolbarItem({ + required this.messageIndex, + required this.index, + required this.item, + required this.selected, + required this.shape, + required this.padding, + required this.backgroundColor, + required this.onItemSelected, + }); + + final int messageIndex; + final int index; + final AssistMessageToolbarItem item; + final bool selected; + final WidgetStateProperty? shape; + final EdgeInsetsGeometry? padding; + final WidgetStateProperty? backgroundColor; + final AssistToolbarItemSelectedCallback? onItemSelected; + + @override + State<_ToolbarItem> createState() => _ToolbarItemState(); +} + +class _ToolbarItemState extends State<_ToolbarItem> { + late final ValueNotifier> _stateChangeNotifier; + + Widget _buildItem() { + Widget result = widget.item.content; + if (widget.padding != null && widget.padding != EdgeInsets.zero) { + result = Padding(padding: widget.padding!, child: result); + } + + return Focus( + onFocusChange: (bool hasFocus) { + if (hasFocus) { + _addState(WidgetState.focused); + } else { + _removeState(WidgetState.focused); + } + }, + child: GestureDetector( + onTap: () { + _removeStates(); + _invokeSelectedCallback(); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (PointerEnterEvent event) { + if (!widget.selected) { + _addState(WidgetState.hovered); + } + }, + onExit: (PointerExitEvent event) { + if (_stateChangeNotifier.value.contains(WidgetState.hovered)) { + _removeStates(); + } + }, + child: result, + ), + ), + ); + } + + void _invokeSelectedCallback() { + widget.onItemSelected?.call( + !widget.selected, + widget.messageIndex, + widget.item, + widget.index, + ); + } + + void _addState(WidgetState state) { + _stateChangeNotifier.value.clear(); + _stateChangeNotifier.value = Set.from(_stateChangeNotifier.value) + ..add(state); + } + + void _removeStates() { + _stateChangeNotifier.value.clear(); + _stateChangeNotifier.value = Set.from(_stateChangeNotifier.value); + } + + void _removeState(WidgetState state) { + if (_stateChangeNotifier.value.contains(state)) { + _stateChangeNotifier.value = Set.from(_stateChangeNotifier.value) + ..remove(state); + } + } + + @override + void initState() { + if (widget.selected) { + _stateChangeNotifier = ValueNotifier>({ + WidgetState.selected, + }); + } else { + _stateChangeNotifier = ValueNotifier>({}); + } + super.initState(); + } + + @override + void didUpdateWidget(_ToolbarItem oldWidget) { + _removeStates(); + if (widget.selected) { + _addState(WidgetState.selected); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder>( + valueListenable: _stateChangeNotifier, + builder: (BuildContext context, Set states, Widget? child) { + return BorderShape( + shape: widget.shape?.resolve(states), + color: widget.backgroundColor?.resolve(states), + child: child, + ); + }, + child: _buildItem(), + ); + } +} diff --git a/packages/syncfusion_flutter_chat/lib/src/assist_view/settings.dart b/packages/syncfusion_flutter_chat/lib/src/assist_view/settings.dart new file mode 100644 index 000000000..b4aaf664c --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/assist_view/settings.dart @@ -0,0 +1,2151 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../settings.dart'; + +typedef AssistWidgetBuilder = BaseWidgetBuilder; + +/// Callback that get invoked when the toolbar item got selected. +typedef AssistSuggestionItemSelectedCallback = + void Function( + bool selected, + int messageIndex, + AssistMessageSuggestion suggestion, + int suggestionIndex, + ); + +typedef AssistToolbarItemSelectedCallback = + void Function( + bool selected, + int messageIndex, + AssistMessageToolbarItem toolbarItem, + int toolbarItemIndex, + ); + +/// It determined the behavior of the placeholder which is need to be scroll or +/// hide when new message added. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// placeholderBehavior: AssistPlaceholderBehavior.hide, +/// placeholderBuilder: (BuildContext context) { +/// return Text('No messages yet'); +/// }, +/// ); +/// } +/// ``` +/// +enum AssistPlaceholderBehavior { + /// - `AssistPlaceholderBehavior.hideOnMessage`, hide placeholder when a new + /// message is added. + hideOnMessage, + + /// - `AssistPlaceholderBehavior.scrollWithMessage`, placeholder get scroll + /// along with messages. + scrollWithMessage, +} + +/// Used to align an assist message bubble to the right, left, or default +/// position. +/// +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// onSuggestionItemSelected: (bool selected, int messageIndex, +/// AssistMessageSuggestion suggestion, int suggestionIndex) { +/// _handleSuggestionItemSelected(); +/// }, +/// ); +/// } +/// ``` +enum AssistMessageAlignment { + /// - `AssistMessageAlignment.start`, aligned all the chat bubble to the start + /// position. + start, + + /// - `AssistMessageAlignment.end`, aligned all the chat bubble to the end + /// position. + end, + + /// - `AssistMessageAlignment.auto`, aligned all the chat bubble to the default + /// position. + auto, +} + +/// Used to determine how suggestions are displayed when there are too many +/// to fit in the available space. +/// +/// Example: +/// ```dart +/// List _messages = [ +/// AssistMessage.response( +/// suggestionSettings: AssistSuggestionSettings( +/// itemOverflow: AssistSuggestionOverflow.wrap, +/// ), +/// ), +/// ]; +/// ``` +enum AssistSuggestionOverflow { + /// - `AssistSuggestionOverflow.wrap`, move the suggestion to next line when + /// it exceed the available size. + wrap, + + /// - `AssistSuggestionOverflow.scroll`, keep the suggestions in a scrollable + /// view when exceed the available size. + scroll, +} + +/// Represents a selection mode of the suggestion, it will be either single or +/// multiple. +/// +/// Example: +/// ```dart +/// List _messages = [ +/// AssistMessage.response( +/// suggestionSettings: AssistSuggestionSettings( +/// selectionType: AssistSuggestionSelectionType.multiple, +/// ), +/// ), +/// ]; +/// ``` +enum AssistSuggestionSelectionType { + /// - `AssistSuggestionSelectionType.single`, allow select only one + /// suggestion. + single, + + /// - `AssistSuggestionSelectionType.multiple`, allow select multiple + /// suggestions. + multiple, +} + +/// Represents a assist message. +/// +/// The [AssistMessage] has two method constructor named [AssistMessage.request] +/// and [AssistMessage.response] to store a message information such as message +/// content, timestamp when the was sent, and the author details. +/// +/// Example: +/// ```dart +/// late List _messages; +/// +/// @override +/// void initState() { +/// _messages = [ +/// AssistMessage.response( +/// data: 'Hello, how can I help you today?', +/// ), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// messages: _messages, +/// actionButton: AssistActionButton( +/// onPressed: (String prompt) { +/// _messages.add( +/// AssistMessage.request( +/// data: prompt, +/// ), +/// ); +/// }, +/// ), +/// ); +/// } +/// ``` +class AssistMessage extends Message { + const AssistMessage.request({required this.data, this.time, this.author}) + : text = data, + isRequested = true, + suggestions = null, + suggestionSettings = null, + toolbarItems = null; + + const AssistMessage.response({ + required this.data, + this.time, + this.author, + this.suggestions, + this.suggestionSettings, + this.toolbarItems, + }) : text = data, + isRequested = false; + + /// Content of the message. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// ), + /// ], + /// ); + /// } + /// ``` + final String data; + + /// Content of the message. + @override + final String text; + + /// Timestamp when the message was sent. + /// + /// It is used to store a timestamp value of the message has been sent. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// time: DateTime.now(), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final DateTime? time; + + /// Author of the message. + /// + /// The [AssistMessageAuthor] contains a unique id, name of the author, and + /// an optional avatar image field. + /// + /// Also, it has a [AssistMessageAuthor.empty] constructor, which is used + /// when there is no information about the author of the assist message. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// author: const AssistMessageAuthor( + /// id: '123-001', + /// name: 'AI Assistant', + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final AssistMessageAuthor? author; + + /// Decides whether the message is a request or not. + final bool isRequested; + + /// Suggestions for the assist message. + /// + /// The suggestion is used to provide users with quick responses or actions + /// they can take based on the message content.It can be a list of predefined + /// options that the user can select from to enhance the interaction + /// experience. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// data: + /// "Flutter is a UI toolkit for building natively compiled apps across" + /// "mobile, web, and desktop.", + /// suggestions: [ + /// AssistMessageSuggestion( + /// content: const TextSpan(text: 'Widgets'), + /// ), + /// AssistMessageSuggestion( + /// content: const TextSpan(text: 'State management'), + /// ), + /// AssistMessageSuggestion( + /// content: const TextSpan(text: 'Layouts and constraints'), + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final List? suggestions; + + /// Settings for the suggestions. + /// + /// The [suggestionSettings] is used to customize the appearance of the + /// suggestion items such as colors, fonts, item padding, and overflow to + /// ensure a better user experience. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// backgroundColor: Colors.grey[300], + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.grey[400]!; + /// } + /// return Colors.grey; + /// }, + /// ), + /// shape: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(4.0)), + /// ), + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(5), + /// ); + /// }), + /// margin: const EdgeInsets.only(top: 10, bottom: 10), + /// itemPadding: + /// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), + /// orientation: Orientation.portrait, + /// runSpacing: 10.0, + /// spacing: 10.0, + /// ), + /// ), + /// ]; + /// ``` + @override + final AssistSuggestionSettings? suggestionSettings; + + /// Toolbar items for the assist message. + /// + /// The [toolbarItems] is a list of [AssistMessageToolbarItem]. It is used to + /// display custom widgets below the message bubble. Each toolbar item + /// contains field `content` used to display a custom widget, `tooltip` used + /// to show additional information about the item when hover, and `isSelected` + /// to indicate if the item is currently selected. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// data: responseText, + /// toolbarItems: [ + /// const AssistMessageToolbarItem( + /// tooltip: 'Like', + /// content: Icon(Icons.thumb_up_outlined), + /// ), + /// const AssistMessageToolbarItem( + /// tooltip: 'DisLike', + /// content: Icon(Icons.thumb_down_outlined), + /// ), + /// const AssistMessageToolbarItem( + /// tooltip: 'Copy', + /// content: Icon(Icons.copy_all), + /// ), + /// const AssistMessageToolbarItem( + /// tooltip: 'Restart', + /// content: Icon(Icons.restart_alt), + /// ), + /// ], + /// ) + /// ]; + /// ``` + final List? toolbarItems; +} + +/// Represents a author of the assist message. +/// +/// The [AssistMessageAuthor] contains a details about the author, such as their +/// unique id, name, and optional avatar image. +/// +/// Also it has a method constructor named [AssistMessageAuthor.empty] which +/// can be used when no author information is available. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// messages: [ +/// AssistMessage.response( +/// data: 'Hello, how can I help you today?', +/// author: const AssistMessageAuthor( +/// id: '123-001', +/// name: 'AI Assistant', +/// ), +/// ), +/// ], +/// ); +/// } +/// ``` +class AssistMessageAuthor extends MessageAuthor { + /// Creates a new [AssistMessageAuthor] with the specified [id], [name], and + /// optional [avatar]. + const AssistMessageAuthor({this.id, required this.name, this.avatar}); + + const AssistMessageAuthor.empty() : id = '', name = '', avatar = null; + + /// Unique identifier of the author, it can be used for customize the message + /// appearance and behavior. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// author: const AssistMessageAuthor( + /// id: '123-001', + /// name: 'AI Assistant', + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final String? id; + + /// The name of the author who sent the message. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// author: const AssistMessageAuthor( + /// id: '123-001', + /// name: 'AI Assistant', + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final String name; + + /// The [avatar] is an optional [ImageProvider] property representing the + /// author's avatar image in a message . + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: [ + /// AssistMessage.response( + /// data: 'Hello, how can I help you today?', + /// author: const AssistMessageAuthor( + /// id: '123-001', + /// name: 'AI Assistant', + /// avatar: NetworkImage('https://example.com/user-avatar.jpg') + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final ImageProvider? avatar; +} + +/// Represents a suggestion related to a assist message. +/// +/// The [AssistMessageSuggestion] class is used to provide a set of predefined +/// request as suggestion along with the response. It contains a details about +/// context text and selected field used to decide the suggestion is selected +/// or not. +/// +/// Example: +/// ```dart +/// List _messages = [ +/// AssistMessage.response( +/// data: +/// "Flutter is a UI toolkit for building natively compiled apps across" +/// "mobile, web, and desktop.", +/// suggestions: [ +/// AssistMessageSuggestion( +/// content: const TextSpan(text: 'Widgets'), +/// ), +/// AssistMessageSuggestion( +/// content: const TextSpan(text: 'State management'), +/// ), +/// AssistMessageSuggestion( +/// content: const TextSpan(text: 'Layouts and constraints'), +/// ), +/// ], +/// ), +/// ]; +///``` +/// +/// ## Builder +/// +/// Example: +/// ```dart +/// List _messages = [ +/// AssistMessage.response( +/// suggestions: [ +/// AssistMessageSuggestion.builder( +/// builder: (BuildContext context) { +/// return Container( +/// decoration: BoxDecoration( +/// color: Colors.green,/ +/// borderRadius: BorderRadius.circular(8.0), +/// ), +/// child: const Text('State management'), +/// ); +/// }, +/// selected: true, +/// ), +/// ], +/// ), +/// ]; +///``` +class AssistMessageSuggestion extends MessageSuggestion { + /// Creates a new [AssistMessageSuggestion] with the [data], and optional + /// [selected] field. + const AssistMessageSuggestion({required this.data, this.selected = false}) + : builder = null; + + const AssistMessageSuggestion.builder({ + required this.builder, + this.data = '', + this.selected = false, + }); + + /// Holds the text content for the suggestion. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestions: [ + /// AssistMessageSuggestion( + /// data: 'Time', + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final String? data; + + /// Indicates whether the suggestion is initially selected. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestions: [ + /// AssistMessageSuggestion( + /// selected: false, + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final bool selected; + + /// Optional builder function for creating a custom widget for the suggestion. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestions: [ + /// AssistMessageSuggestion.builder( + /// builder: (context) { + /// return Text('State management'); + /// }, + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final WidgetBuilder? builder; + + /// Creates a copy of this suggestion with the given fields replaced by the + /// new values. + AssistMessageSuggestion copyWith({ + String? data, + bool? selected, + WidgetBuilder? builder, + }) { + if (this.builder != null) { + return AssistMessageSuggestion.builder( + builder: builder ?? this.builder, + data: data ?? this.data, + selected: selected ?? this.selected, + ); + } else { + return AssistMessageSuggestion( + data: data ?? this.data, + selected: selected ?? this.selected, + ); + } + } +} + +/// Represents the settings for assist message suggestions. +/// +/// The [AssistSuggestionSettings] class used to store the customization +/// details of the assist message suggestions, such as colors, padding, +/// and text styles, etc. +/// +/// Example: +/// ```dart +/// List _messages = [ +/// AssistMessage.response( +/// suggestionSettings: AssistSuggestionSettings( +/// backgroundColor: Colors.grey[300], +/// itemBackgroundColor: WidgetStateProperty.resolveWith( +/// (states) { +/// if (states.contains(WidgetState.hovered)) { +/// return Colors.grey[400]!; +/// } +/// return Colors.grey; +/// }, +/// ), +/// shape: const RoundedRectangleBorder( +/// borderRadius: BorderRadius.all(Radius.circular(4.0)), +/// ), +/// itemShape: WidgetStateProperty.resolveWith( +/// (Set states) { +/// if (states.contains(WidgetState.hovered)) { +/// return RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(20), +/// ); +/// } +/// return RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(5), +/// ); +/// }), +/// margin: const EdgeInsets.only(top: 10, bottom: 10), +/// itemPadding: +/// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), +/// orientation: Orientation.portrait, +/// runSpacing: 10.0, +/// spacing: 10.0, +/// ), +/// ), +/// ]; +/// ``` +class AssistSuggestionSettings extends SuggestionSettings { + const AssistSuggestionSettings({ + this.backgroundColor, + this.itemBackgroundColor, + this.shape, + this.itemShape, + this.textStyle, + this.margin = const EdgeInsetsDirectional.symmetric(vertical: 5.0), + this.itemPadding = const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, + ), + this.orientation = Axis.horizontal, + AssistSuggestionOverflow itemOverflow = AssistSuggestionOverflow.wrap, + AssistSuggestionSelectionType selectionType = + AssistSuggestionSelectionType.single, + this.runSpacing = 12.0, + this.spacing = 16.0, + }) : itemOverflow = + itemOverflow == AssistSuggestionOverflow.wrap + ? SuggestionOverflow.wrap + : SuggestionOverflow.scroll, + selectionType = + selectionType == AssistSuggestionSelectionType.single + ? SuggestionSelectionType.single + : SuggestionSelectionType.multiple; + + /// The [backgroundColor] property sets the background color for the + /// suggestion area. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// backgroundColor: Colors.grey[300], + /// ) + /// ), + /// ]; + /// ``` + @override + final Color? backgroundColor; + + /// The [itemBackgroundColor] property sets the background color for the + /// individual suggestion items based on their state, such as hovered or + /// pressed. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.grey[400]!; + /// } + /// return Colors.grey; + /// }, + /// ), + /// ) + /// ), + /// ]; + /// ``` + @override + final WidgetStateProperty? itemBackgroundColor; + + /// Used to customize the overall shape of the suggestion area. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// shape: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(4.0)), + /// ), + /// ) + /// ), + /// ]; + /// ``` + @override + final ShapeBorder? shape; + + /// Used to customize the shape of individual suggestion items based on their + /// state, such as hovered or pressed. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(5), + /// ); + /// }), + /// ) + /// ), + /// ]; + /// ``` + @override + final WidgetStateProperty? itemShape; + + /// The [textStyle] used to sets the text style for the suggestion item based + /// on their state, such as hovered or pressed. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// textStyle: WidgetStateProperty.resolveWith( + /// (Set state) { + /// if (state.contains(WidgetState.selected)) { + /// return const TextStyle( + /// color: Colors.blue, + /// fontSize: 16.0, + /// fontWeight: FontWeight.bold); + /// } else if (state.contains(WidgetState.focused)) { + /// return const TextStyle( + /// color: Colors.blueGrey, + /// fontSize: 16.0, + /// fontWeight: FontWeight.bold); + /// } else if (state.contains(WidgetState.hovered)) { + /// return const TextStyle( + /// color: Colors.lightBlueAccent, + /// fontSize: 16.0, + /// fontWeight: FontWeight.bold); + /// } else if (state.contains(WidgetState.disabled)) { + /// return const TextStyle( + /// color: Colors.grey, + /// fontSize: 16.0, + /// fontWeight: FontWeight.bold); + /// } + /// return const TextStyle( + /// color: Colors.black, + /// fontSize: 16.0, + /// fontWeight: FontWeight.bold); + /// }, + /// ), + /// ) + /// ), + /// ]; + /// ``` + @override + final WidgetStateProperty? textStyle; + + /// To sets the margin between the suggestion area and individual suggestion + /// items. + /// + /// Defaults to `EdgeInsets.all(5.0)`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// margin: const EdgeInsets.only(top: 10, bottom: 10), + /// ) + /// ), + /// ]; + /// ``` + @override + final EdgeInsetsGeometry margin; + + /// To set the padding between the content of each individual suggestion item. + /// + /// Defaults to `EdgeInsets.all(5.0)`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// itemPadding: + /// const EdgeInsets.all(10), + /// ) + /// ), + /// ]; + /// ``` + @override + final EdgeInsetsGeometry itemPadding; + + /// The [orientation] is used to determine the rendering orientation of the + /// suggestion items and the direction in which the suggestions are scrolled. + /// + /// Defaults to [Axis.horizontal]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// orientation: Axis.vertical, + /// ) + /// ), + /// ]; + /// ``` + @override + final Axis orientation; + + /// The [runSpacing] determines a vertical spacing between runs of suggestion + /// items. + /// + /// Defaults to `8.0`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// runSpacing: 10.0, + /// ) + /// ), + /// ]; + /// ``` + @override + final double runSpacing; + + /// The [spacing] determines a horizontal spacing between individual + /// suggestion items. + /// + /// Defaults to `8.0`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// spacing: 10.0, + /// ) + /// ), + /// ]; + /// ``` + @override + final double spacing; + + /// The [itemOverflow] property determines whether suggestion items will wrap + /// to fit within available space or scroll beyond it + /// + /// Defaults to [SuggestionOverflow.wrap]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// itemOverflow: SuggestionOverflow.scroll, + /// ), + /// ), + /// ]; + /// ``` + @override + final SuggestionOverflow itemOverflow; + + /// The [selectionType] property specifies how many suggestion items can be + /// selected at once. + /// + /// Defaults to [SuggestionSelectionType.single]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// suggestionSettings: AssistSuggestionSettings( + /// selectionType: SuggestionSelectionType.single, + /// ), + /// ), + /// ]; + /// ``` + @override + final SuggestionSelectionType selectionType; +} + +/// Represents the settings for the assist bubble. +/// +/// The [AssistBubbleSettings] class used to store a customization details of +/// request and response bubble and it's elements, that control the appearance +/// and behavior of the assist bubble. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// requestMessageSettings: const AssistBubbleSettings( +/// showAuthorName: true, +/// showTimestamp: true, +/// showAuthorAvatar: true, +/// widthFactor: 0.5, +/// avatarSize: Size.square(40.0), +/// margin: EdgeInsets.all(2.0), +/// padding: +/// EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), +/// headerPadding: +/// EdgeInsetsDirectional.only(top: 10.0, bottom: 14.0), +/// footerPadding: EdgeInsetsDirectional.only(top: 14.0), +/// ), +/// responseMessageSettings: const AssistBubbleSettings( +/// showAuthorName: true, +/// showTimestamp: true, +/// showAuthorAvatar: true, +/// widthFactor: 0.9, +/// avatarSize: Size.square(24.0), +/// margin: EdgeInsets.all(2.0), +/// padding: +/// EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), +/// headerPadding: EdgeInsetsDirectional.only(bottom: 10.0), +/// footerPadding: EdgeInsetsDirectional.only(top: 10.0), +/// ), +/// ); +/// } +/// ``` +class AssistMessageSettings extends MessageSettings { + const AssistMessageSettings({ + this.showAuthorName = false, + this.showTimestamp = false, + this.showAuthorAvatar, + this.timestampFormat, + this.textStyle, + this.headerTextStyle, + this.backgroundColor, + this.shape, + this.widthFactor = 0.8, + this.avatarSize = const Size.square(32.0), + this.margin, + this.padding, + this.avatarPadding, + this.headerPadding = const EdgeInsetsDirectional.only(bottom: 3.0), + this.footerPadding = const EdgeInsetsDirectional.only(top: 4.0), + }); + + /// The [showAuthorName] property is to determines whether the user name + /// is displayed or not. + /// + /// Defaults to `false`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// showAuthorName: true, + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// showAuthorName: true, + /// ), + /// ); + /// } + /// ``` + @override + final bool showAuthorName; + + /// The [showTimestamp] property is to determines whether the time stamp + /// is displayed or not. + /// + /// Defaults to `false`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// showTimestamp: true, + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// showTimestamp: true, + /// ), + /// ); + /// } + /// ``` + @override + final bool showTimestamp; + + /// The [showAuthorAvatar] property is to determines whether the user avatar + /// is displayed or not. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// showAuthorAvatar: true, + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// showAuthorAvatar: true, + /// ), + /// ); + /// } + /// ``` + @override + final bool? showAuthorAvatar; + + /// The [timestampFormat] property specifies the format used for displaying + /// timestamps. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// timestampFormat: DateFormat('hh:mm a'), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// timestampFormat: DateFormat('hh:mm a'), + /// ), + /// ); + /// } + /// ``` + @override + final DateFormat? timestampFormat; + + /// Text style for the request and response. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// textStyle: const TextStyle(fontSize: 14), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// textStyle: const TextStyle(fontSize: 14), + /// ), + /// ); + /// } + /// ``` + @override + final TextStyle? textStyle; + + /// Text style for the header. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// headerTextStyle: const TextStyle(fontSize: 14), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// headerTextStyle: const TextStyle(fontSize: 14), + /// ), + /// ); + /// } + /// ``` + @override + final TextStyle? headerTextStyle; + + /// Used to set background color for the request and response bubble content. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// backgroundColor: Colors.blue, + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// backgroundColor: Colors.blue, + /// ), + /// ); + /// } + /// ``` + @override + final Color? backgroundColor; + + /// To set the custom shape of the request and response bubble. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// ); + /// } + /// ``` + @override + final ShapeBorder? shape; + + /// The [widthFactor] property specifies the proportional width of a bubble. + /// + /// Defaults to `0.8`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// widthFactor: 0.6, + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// widthFactor: 1.0, + /// ), + /// ); + /// } + /// ``` + @override + final double widthFactor; + + /// Size of the avatar. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// avatarSize: Size.square(32.0), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// avatarSize: Size.square(32.0), + /// ), + /// ); + /// } + /// ``` + @override + final Size avatarSize; + + /// Determine margin around the bubble. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// margin: EdgeInsets.all(2.0), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// margin: EdgeInsets.all(2.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry? margin; + + /// It determines a padding around the content with in the message bubble. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// padding: + /// EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// padding: + /// EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry? padding; + + /// Determine the padding around the avatar. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// avatarPadding: EdgeInsets.all(10.0), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// avatarPadding: EdgeInsets.all(10.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry? avatarPadding; + + /// Padding for the header. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// headerPadding: + /// EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// headerPadding: + /// EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry headerPadding; + + /// Padding for the footer. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// requestMessageSettings: const AssistBubbleSettings( + /// footerPadding: EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// responseMessageSettings: const AssistBubbleSettings( + /// footerPadding: EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry footerPadding; + + AssistMessageSettings mergeWith(AssistMessageSettings settings) { + return copyWith( + showAuthorName: settings.showAuthorName, + showTimestamp: settings.showTimestamp, + showAuthorAvatar: settings.showAuthorAvatar, + timestampFormat: settings.timestampFormat, + textStyle: settings.textStyle, + headerTextStyle: settings.headerTextStyle, + backgroundColor: settings.backgroundColor, + shape: settings.shape, + widthFactor: settings.widthFactor, + avatarSize: settings.avatarSize, + margin: settings.margin, + padding: settings.padding, + avatarPadding: settings.avatarPadding, + headerPadding: settings.headerPadding, + footerPadding: settings.footerPadding, + ); + } + + /// Creates a copy of this bubble settings with the given fields replaced by + /// the new values. + AssistMessageSettings copyWith({ + bool? showAuthorName, + bool? showTimestamp, + bool? showAuthorAvatar, + DateFormat? timestampFormat, + TextStyle? textStyle, + TextStyle? headerTextStyle, + Color? backgroundColor, + ShapeBorder? shape, + double? widthFactor, + Size? avatarSize, + EdgeInsetsGeometry? margin, + EdgeInsetsGeometry? padding, + EdgeInsetsGeometry? avatarPadding, + EdgeInsetsGeometry? headerPadding, + EdgeInsetsGeometry? footerPadding, + }) { + return AssistMessageSettings( + showAuthorName: showAuthorName ?? this.showAuthorName, + showTimestamp: showTimestamp ?? this.showTimestamp, + showAuthorAvatar: showAuthorAvatar ?? this.showAuthorAvatar, + timestampFormat: timestampFormat ?? this.timestampFormat, + textStyle: textStyle ?? this.textStyle, + headerTextStyle: headerTextStyle ?? this.headerTextStyle, + backgroundColor: backgroundColor ?? this.backgroundColor, + shape: shape ?? this.shape, + widthFactor: widthFactor ?? this.widthFactor, + avatarSize: avatarSize ?? this.avatarSize, + margin: margin ?? this.margin, + padding: padding ?? this.padding, + avatarPadding: avatarPadding ?? this.avatarPadding, + headerPadding: headerPadding ?? this.headerPadding, + footerPadding: footerPadding ?? this.footerPadding, + ); + } +} + +/// Represents a composer for the assist message. +/// +/// The [AssistComposer] allows customization of text input areas, including +/// text style, line limits, and decoration. It can also be initialized with +/// a custom builder. +/// +/// Example with default composer: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// composer: AssistComposer( +/// textStyle: TextStyle(fontSize: 16.0, color: Colors.black), +/// minLines: 2, +/// maxLines: 5, +/// decoration: InputDecoration( +/// border: OutlineInputBorder( +/// borderRadius: BorderRadius.all(Radius.circular(42.0)))), +/// hintText: 'Type a message...', +/// ), +/// margin: const EdgeInsets.only(top: 16.0), +/// ) +/// ); +/// } +/// +/// Example with custom Composer: +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// composer: AssistComposer.builder( +/// builder: (context) => CustomAssistInputWidget(), +/// margin: const EdgeInsets.only(top: 16.0), +/// ) +/// ); +/// } +/// ``` +class AssistComposer extends Composer { + /// Creates a [AssistComposer] with the given [textStyle], line limits, + /// [decoration], and [margin]. + const AssistComposer({ + this.textStyle, + this.minLines = 1, + this.maxLines = 6, + this.decoration = const InputDecoration(), + this.margin = const EdgeInsets.only(top: 24.0), + }) : builder = null; + + /// Named constructor to create a composer using a custom widget. + const AssistComposer.builder({ + required this.builder, + this.margin = const EdgeInsets.only(top: 24.0), + }) : maxLines = 0, + minLines = 0, + textStyle = null, + decoration = null; + + /// The [maxLines] property defines the maximum number of lines the composer + /// can occupy. + /// + /// Defaults to `6`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer( + /// maxLines: 4, + /// ) + /// ); + /// } + /// ``` + @override + final int maxLines; + + /// The [minLines] property sets the minimum number of lines the composer + /// will occupy. + /// + /// Defaults to `1`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer( + /// minLines: 2, + /// ) + /// ); + /// } + /// ``` + @override + final int minLines; + + /// The [textStyle] property defines the style of the composer input text. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer( + /// textStyle: TextStyle( + /// fontSize: 16.0, + /// color: Colors.black, + /// ), + /// ) + /// ); + /// } + /// ``` + @override + final TextStyle? textStyle; + + /// The [decoration] property specifies the visual styling and layout for the + /// composer input text. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer( + /// decoration: InputDecoration( + /// border: OutlineInputBorder( + /// borderRadius: BorderRadius.all(Radius.circular(42.0)))), + /// hintText: 'Type a message...', + /// ), + /// ) + /// ); + /// } + /// ``` + @override + final InputDecoration? decoration; + + /// The margin around the composer. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer( + /// margin: const EdgeInsets.only(top: 10.0), + /// ) + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry margin; + + /// The builder to have the custom widget as composer. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// composer: AssistComposer.builder( + /// builder: (context) => CustomAssistInputWidget(), + /// margin: const EdgeInsets.only(top: 16.0), + /// ) + /// ); + /// } + /// ``` + @override + final WidgetBuilder? builder; +} + +/// Represents a assist action button. +/// +/// The [AssistActionButton] allows extensive customizations, including +/// appearance, size, and behavior. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// actionButton: AssistActionButton( +/// onPressed: (String newMessage) { +/// setState(() { +/// _messages.add( +/// AssistMessage.request( +/// data: newMessage, +/// ), +/// ); +/// }); +/// }, +/// ), +/// ); +/// } +/// ``` +class AssistActionButton extends ActionButton { + /// Creates a [AssistActionButton] with the given parameters. + const AssistActionButton({ + this.child, + this.tooltip, + this.foregroundColor, + this.backgroundColor, + this.focusColor, + this.hoverColor, + this.splashColor, + this.elevation, + this.focusElevation, + this.hoverElevation, + this.highlightElevation, + this.mouseCursor, + this.shape, + this.margin = const EdgeInsetsDirectional.only(start: 8.0), + this.size = const Size.square(40.0), + required this.onPressed, + }); + + /// To display a widget inside the action button, such as icons and custom + /// widgets. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// child: const Icon(Icons.send), + /// ), + /// ); + /// } + /// ``` + @override + final Widget? child; + + /// Tooltip message to be displayed when the action button is hovered. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// tooltip: 'Send', + /// ), + /// ); + /// } + /// ``` + @override + final String? tooltip; + + /// It specifies the foreground color of the action button. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// foregroundColor: Colors.blue, + /// ), + /// ); + /// } + /// ``` + @override + final Color? foregroundColor; + + /// Background color of the action button. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// backgroundColor: Colors.red, + /// ), + /// ); + /// } + /// ``` + @override + final Color? backgroundColor; + + /// Color of the action button when it is focused. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// focusColor: Colors.green, + /// ), + /// ); + /// } + /// ``` + @override + final Color? focusColor; + + /// Color of the action button when it is hovered. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// hoverColor: Colors.yellow, + /// ), + /// ); + /// } + /// ``` + @override + final Color? hoverColor; + + /// Color of the action button when it is splashed. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// splashColor: Colors.purple, + /// ), + /// ); + /// } + /// ``` + @override + final Color? splashColor; + + /// The [elevation] property specifies the shadow depth of the action button. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// elevation: 4.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? elevation; + + /// Specifies the shadow depth of the action button when it is focused. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// focusElevation: 8.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? focusElevation; + + /// Specifies the shadow depth of the action button when it is hovered. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// hoverElevation: 6.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? hoverElevation; + + /// Specifies the shadow depth of the action button when it is highlighted. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// highlightElevation: 4.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? highlightElevation; + + /// Defines the mouse cursor appearance when hovering over the action button. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// mouseCursor: SystemMouseCursors.click, + /// ), + /// ); + /// } + /// ``` + @override + final MouseCursor? mouseCursor; + + /// Used to customize the shape of action button. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ), + /// ), + /// ); + /// } + /// ``` + @override + final ShapeBorder? shape; + + /// Padding of the action button. + /// + /// Defaults to `EdgeInsetsDirectional.only(start: 8.0)`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// margin: EdgeInsets.all(12.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry margin; + + /// Specifies the dimensions of the action button. + /// + /// Defaults to `Size.square(40.0)`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// size: Size(50.0, 50.0), + /// ), + /// ); + /// } + /// ``` + @override + final Size size; + + /// Callback gets triggered when the action button is pressed. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// actionButton: AssistActionButton( + /// onPressed: (String newMessage) { + /// setState(() { + /// _messages.add( + /// AssistMessage.request( + /// data: newMessage, + /// ), + /// ); + /// }); + /// }, + /// ), + /// ); + /// } + /// ``` + @override + final ValueChanged? onPressed; +} + +/// Toolbar item for assist message, which is show below the response +/// message bubble. +/// +/// The [AssistMessageToolbarItem] class stores widget as content, tooltip +/// value to show when hover on the toolbar item, and isSelected to indicate +/// whether the item is currently selected +/// +/// Example: +/// ```dart +/// List _messages = [ +/// AssistMessage.response( +/// data: responseText, +/// toolbarItems: [ +/// const AssistMessageToolbarItem( +/// tooltip: 'Like', +/// content: Icon(Icons.thumb_up_outlined), +/// ), +/// const AssistMessageToolbarItem( +/// tooltip: 'DisLike', +/// content: Icon(Icons.thumb_down_outlined), +/// ), +/// const AssistMessageToolbarItem( +/// tooltip: 'Copy', +/// content: Icon(Icons.copy_all), +/// ), +/// const AssistMessageToolbarItem( +/// tooltip: 'Restart', +/// content: Icon(Icons.restart_alt), +/// ), +/// ], +/// ) +/// ]; +/// ``` +class AssistMessageToolbarItem { + const AssistMessageToolbarItem({ + required this.content, + this.tooltip, + this.isSelected = false, + }); + + /// Holds the widget to display as the content of a toolbar item, and it can + /// be any type of widget. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// data: responseText, + /// toolbarItems: [ + /// const AssistMessageToolbarItem( + /// content: Icon(Icons.thumb_up_outlined), + /// ), + /// ], + /// ) + /// ]; + /// ``` + final Widget content; + + /// Hold the string value to display as a tooltip when hovering over a + /// toolbar item. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// data: responseText, + /// toolbarItems: [ + /// const AssistMessageToolbarItem( + /// content: Icon(Icons.thumb_up_outlined), + /// tooltip: 'Like', + /// ), + /// ], + /// ) + /// ]; + /// ``` + final String? tooltip; + + /// To indicates whether a toolbar item is currently selected or not. + /// + /// Defaults to `false`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// AssistMessage.response( + /// data: responseText, + /// toolbarItems: [ + /// const AssistMessageToolbarItem( + /// content: Icon(Icons.thumb_up_outlined), + /// isSelected: true, + /// ), + /// ], + /// ) + /// ]; + /// ``` + final bool isSelected; + + AssistMessageToolbarItem copyWith({ + Widget? content, + String? tooltip, + bool? isSelected, + }) { + return AssistMessageToolbarItem( + content: content ?? this.content, + tooltip: tooltip ?? this.tooltip, + isSelected: isSelected ?? this.isSelected, + ); + } +} + +/// Represents the settings for assist message footers. +/// +/// The [AssistMessageToolbarSettings] is used to store customize details of a +/// toolbar item in the response bubble. It allows setting shapes, colors, +/// margin, and spacing for both the toolbar and its individual items. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfAIAssistView( +/// responseToolbarSettings: AssistMessageToolbarSettings( +/// shape: RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(20.0), +/// ), +/// itemShape: WidgetStateProperty.resolveWith( +/// (Set state) { +/// return const RoundedRectangleBorder( +/// borderRadius: BorderRadius.all(Radius.circular(8.0))); +/// }, +/// ), +/// backgroundColor: Colors.red, +/// itemBackgroundColor: WidgetStateProperty.resolveWith( +/// (Set state) { +/// if (state.contains(WidgetState.selected)) { +/// return Colors.blue; +/// } else if (state.contains(WidgetState.focused)) { +/// return Colors.blueGrey; +/// } else if (state.contains(WidgetState.hovered)) { +/// return Colors.lightBlueAccent; +/// } else if (state.contains(WidgetState.disabled)) { +/// return Colors.grey; +/// } +/// return Colors.lightBlue; +/// }, +/// ), +/// margin: const EdgeInsets.all(10), +/// itemPadding: const EdgeInsets.all(10), +/// spacing: 10, +/// runSpacing: 10, +/// ), +/// ); +/// } +/// ``` +class AssistMessageToolbarSettings { + /// Creates a [AssistMessageToolbarSettings] with the given parameters. + const AssistMessageToolbarSettings({ + this.shape, + this.itemShape, + this.backgroundColor, + this.itemBackgroundColor, + this.margin = const EdgeInsetsDirectional.symmetric(vertical: 4.0), + this.itemPadding = const EdgeInsets.all(9.0), + this.spacing = 8.0, + this.runSpacing = 8.0, + }); + + /// To set the background color of the footer area. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// backgroundColor: Colors.red, + /// ), + /// ); + /// } + /// ``` + final Color? backgroundColor; + + /// To set the background color of individual footer items based on their + /// state, such as hovered or pressed. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (Set state) { + /// if (state.contains(WidgetState.selected)) { + /// return Colors.blue; + /// } else if (state.contains(WidgetState.focused)) { + /// return Colors.blueGrey; + /// } else if (state.contains(WidgetState.hovered)) { + /// return Colors.lightBlueAccent; + /// } else if (state.contains(WidgetState.disabled)) { + /// return Colors.grey; + /// } + /// return Colors.lightBlue; + /// }, + /// ), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? itemBackgroundColor; + + /// To set a customize overall shape of the footer area. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// messages: _messages, + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20.0), + /// ), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? shape; + + /// To set a customize shape of individual footer items based on their state, + /// such as hovered or pressed. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set state) { + /// return const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(8.0))); + /// }, + /// ), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? itemShape; + + /// To set margin between the footer area and individual footer items. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// margin: const EdgeInsets.all(10), + /// ), + /// ); + /// } + /// ``` + final EdgeInsetsGeometry margin; + + /// To set the margin between the content of each individual footer item. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// itemPadding: const EdgeInsets.all(10), + /// ), + /// ); + /// } + /// ``` + final EdgeInsetsGeometry itemPadding; + + /// Used to sets a horizontal spacing between individual footer items. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// spacing: 10, + /// ), + /// ); + /// } + /// ``` + final double spacing; + + /// Used to sets a vertical spacing between runs of footer items. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfAIAssistView( + /// responseToolbarSettings: AssistMessageToolbarSettings( + /// runSpacing: 10, + /// ), + /// ); + /// } + /// ``` + final double runSpacing; +} diff --git a/packages/syncfusion_flutter_chat/lib/src/assist_view/theme.dart b/packages/syncfusion_flutter_chat/lib/src/assist_view/theme.dart new file mode 100644 index 000000000..56382a841 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/assist_view/theme.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +Color _saturatedColor(Color color, double factor) { + if (color == Colors.transparent) { + return color; + } else { + const Color mix = Color(0x4D000000); + final double mixFactor = 1 - factor; + return Color.fromRGBO( + (mixFactor * (color.r * 255) + factor * (mix.r * 255)).toInt(), + (mixFactor * (color.g * 255) + factor * (mix.g * 255)).toInt(), + (mixFactor * (color.b * 255) + factor * (mix.b * 255)).toInt(), + 1, + ); + } +} + +class AIAssistViewM2ThemeData extends SfAIAssistViewThemeData { + AIAssistViewM2ThemeData(this.context) + : super( + requestMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + responseMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ); + + final BuildContext context; + late final ColorScheme _colorScheme = Theme.of(context).colorScheme; + + @override + double get actionButtonHighlightElevation => 6.0; + + @override + Color? get actionButtonForegroundColor => _colorScheme.onPrimary; + + @override + Color? get actionButtonBackgroundColor => _colorScheme.primary; + + @override + Color? get actionButtonFocusColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonHoverColor => + _colorScheme.primary.withValues(alpha: 0.91); + + @override + Color? get actionButtonSplashColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonDisabledForegroundColor => + _colorScheme.onSurface.withValues(alpha: 0.38); + + @override + Color? get actionButtonDisabledBackgroundColor => + _colorScheme.surface.withValues(alpha: 0.12); + + @override + ShapeBorder? get actionButtonShape => const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ); + + @override + Color? get requestMessageBackgroundColor => _colorScheme.surfaceContainer; + + @override + Color? get responseMessageBackgroundColor => Colors.transparent; + + @override + Color? get requestAvatarBackgroundColor => _colorScheme.surfaceContainer; + + @override + Color? get responseAvatarBackgroundColor => _colorScheme.surfaceContainer; + + @override + WidgetStateProperty get suggestionItemBackgroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered) || + states.contains(WidgetState.focused)) { + return _saturatedColor(_colorScheme.surfaceContainer, 0.08); + } + if (states.contains(WidgetState.disabled)) { + return _saturatedColor(_colorScheme.surfaceContainer, 0.12); + } + return _colorScheme.surfaceContainer; + }); + + @override + WidgetStateProperty? get suggestionItemShape => + WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + ); + + @override + WidgetStateProperty get responseToolbarItemBackgroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered) || + states.contains(WidgetState.focused)) { + return _colorScheme.onSurface.withValues(alpha: 0.08); + } + if (states.contains(WidgetState.pressed) || + states.contains(WidgetState.selected)) { + return _colorScheme.onSurface.withValues(alpha: 0.01); + } + return _colorScheme.surface; + }); + + @override + WidgetStateProperty? get responseToolbarItemShape => + WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + ); + + @override + WidgetStateProperty? get suggestionItemTextStyle => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return TextStyle( + color: _colorScheme.onSurface.withValues(alpha: 0.38), + ); + } + return TextStyle(color: _colorScheme.onSurface); + }); +} + +class AIAssistViewM3ThemeData extends SfAIAssistViewThemeData { + AIAssistViewM3ThemeData(this.context) + : super( + requestMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + responseMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ); + + final BuildContext context; + late final ColorScheme _colorScheme = Theme.of(context).colorScheme; + + @override + double get actionButtonHighlightElevation => 6.0; + + @override + Color? get actionButtonForegroundColor => _colorScheme.onPrimary; + + @override + Color? get actionButtonBackgroundColor => _colorScheme.primary; + + @override + Color? get actionButtonFocusColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonHoverColor => + _colorScheme.primary.withValues(alpha: 0.91); + + @override + Color? get actionButtonSplashColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonDisabledForegroundColor => + _colorScheme.onSurface.withValues(alpha: 0.38); + + @override + Color? get actionButtonDisabledBackgroundColor => + _colorScheme.surface.withValues(alpha: 0.12); + + @override + ShapeBorder? get actionButtonShape => const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ); + + @override + Color? get requestMessageBackgroundColor => _colorScheme.surfaceContainer; + + @override + Color? get responseMessageBackgroundColor => Colors.transparent; + + @override + Color? get requestAvatarBackgroundColor => _colorScheme.surfaceContainer; + + @override + Color? get responseAvatarBackgroundColor => _colorScheme.surfaceContainer; + + @override + WidgetStateProperty get suggestionItemBackgroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered) || + states.contains(WidgetState.focused)) { + return _saturatedColor(_colorScheme.surfaceContainer, 0.08); + } + if (states.contains(WidgetState.disabled)) { + return _saturatedColor(_colorScheme.surfaceContainer, 0.12); + } + return _colorScheme.surfaceContainer; + }); + + @override + WidgetStateProperty? get suggestionItemShape => + WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + ); + + @override + WidgetStateProperty get responseToolbarItemBackgroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered) || + states.contains(WidgetState.focused)) { + return _colorScheme.onSurface.withValues(alpha: 0.08); + } + if (states.contains(WidgetState.pressed) || + states.contains(WidgetState.selected)) { + return _colorScheme.onSurface.withValues(alpha: 0.01); + } + return _colorScheme.surface; + }); + + @override + WidgetStateProperty? get responseToolbarItemShape => + WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + ); + + @override + WidgetStateProperty? get suggestionItemTextStyle => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return TextStyle( + color: _colorScheme.onSurface.withValues(alpha: 0.38), + ); + } + return TextStyle(color: _colorScheme.onSurface); + }); +} diff --git a/packages/syncfusion_flutter_chat/lib/src/base_layout.dart b/packages/syncfusion_flutter_chat/lib/src/base_layout.dart new file mode 100644 index 000000000..a96b767a0 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/base_layout.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class _LayoutParentData extends ContainerBoxParentData {} + +class LayoutHandler extends MultiChildRenderObjectWidget { + const LayoutHandler({super.key, required super.children}); + + @override + RenderBox createRenderObject(BuildContext context) { + return _RenderLayoutHandler(); + } +} + +class _RenderLayoutHandler extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + final LayerHandle _clipRectLayer = + LayerHandle(); + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! _LayoutParentData) { + child.parentData = _LayoutParentData(); + } + super.setupParentData(child); + } + + @override + void performLayout() { + const Size minConstraints = Size(300.0, 300.0); + final double boxWidth = + constraints.maxWidth.isFinite + ? constraints.maxWidth + : minConstraints.width; + final double boxHeight = + constraints.maxHeight.isFinite + ? constraints.maxHeight + : minConstraints.height; + + double availableHeight = boxHeight; + RenderBox? child = lastChild; + while (child != null) { + child.layout( + BoxConstraints.loose(Size(boxWidth, availableHeight)), + parentUsesSize: true, + ); + availableHeight -= child.size.height; + child = (child.parentData! as _LayoutParentData).previousSibling; + } + + child = firstChild; + Offset effectiveOffset = Offset.zero; + while (child != null) { + final _LayoutParentData childParentData = + child.parentData! as _LayoutParentData; + childParentData.offset = effectiveOffset; + effectiveOffset += Offset(0.0, child.size.height); + child = childParentData.nextSibling; + } + + size = Size(boxWidth, boxHeight); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return defaultHitTestChildren(result, position: position); + } + + @override + void paint(PaintingContext context, Offset offset) { + _clipRectLayer.layer = context.pushClipRect( + needsCompositing, + offset, + Offset.zero & size, + defaultPaint, + clipBehavior: Clip.antiAlias, + oldLayer: _clipRectLayer.layer, + ); + } + + @override + void dispose() { + _clipRectLayer.layer = null; + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_chat/lib/src/chat/chat.dart b/packages/syncfusion_flutter_chat/lib/src/chat/chat.dart new file mode 100644 index 000000000..e5a0841f3 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/chat/chat.dart @@ -0,0 +1,1137 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../base_layout.dart'; +import '../composer_area.dart'; +import 'conversion_area.dart'; +import 'settings.dart'; +import 'theme.dart'; + +/// The [SfChat] widget is a customizable chat interface designed for +/// one-on-one or group conversations. +/// +/// It provides extensive customization options, allowing developers to modify +/// the appearance and behavior of chat bubbles, the input composer, +/// action buttons, and more. +/// +/// The [SfChat] includes the following elements and features: +/// +/// * **Outgoing User**: This refers to the unique ID of the user +/// sending the message. This ID distinguishes messages sent by the current user +/// from those sent by others, ensuring accurate presentation of the chat. +/// * **Messages**: A list of [ChatMessage] objects that will be displayed +/// in the chat interface as either incoming or outgoing messages based on +/// the [outgoingUser]. Each [ChatMessage] includes details such as the +/// message text, timestamp, author information, list of suggestions and +/// suggestion settings. Additionally, there is an option to extend this class +/// to include more information about the chat message. +/// * **Chat Message Suggestion**: The [ChatMessage.suggestions] allows you to +/// add the list of suggestion items for a message added in the message list. +/// The list of suggestion can be selected and will be displayed in the chat +/// interface as either incoming or outgoing messages based on the user who +/// selected the suggestion item. +/// * **Chat Suggestion Settings**: The[ChatMessage.suggestionSettings] allows +/// you to customize the suggestion items in the suggestions list using the +/// properties like color, shape, padding, textStyle, and more. +/// * **Composer**: This is the primary text editor where the user can +/// compose new chat messages. +/// * **Action Button**: This represents the send button, which becomes enabled +/// when the default text editor starts adding text. Pressing this action button +/// invokes the [ChatActionButton.onPressed] callback with the text entered +/// in the default [ChatComposer]. +/// * **Placeholder Builder**: The [SfChat.placeholderBuilder] allows you to +/// specify a custom widget to display when there are no messages in the chat. +/// This is particularly useful for presenting users with a relevant or +/// visually appealing message indicating that the conversation is +/// currently empty. +/// * **Bubble Header Builder**: The [SfChat.messageHeaderBuilder] allows you to +/// specify a custom widget to display as a header for each chat bubble. This is +/// particularly useful for displaying additional information such as the +/// sender's name and the timestamp associated with each message. +/// * **Bubble Avatar Builder**: The [SfChat.messageAvatarBuilder] allows you to +/// specify a custom widget to display as an avatar within each chat bubble. +/// This feature is especially useful for showing user avatars or +/// profile pictures within the chat interface. +/// * **Bubble Content Builder**: The [SfChat.messageContentBuilder] allows you +/// to specify a custom widget to display as the content within each +/// chat bubble. This is useful for customizing how the message content is +/// presented, such as using different background colors, borders, or padding. +/// * **Bubble Footer Builder**: The [SfChat.messageFooterBuilder] allows you to +/// specify a custom widget that will be displayed as a footer within each +/// chat bubble. This is particularly useful for displaying timestamps or +/// other additional information related to the message. +/// +/// The following example demonstrates how to create a simple chat interface +/// using the [SfChat] widget. +/// ```dart +/// late List _messages; +/// +/// @override +/// void initState() { +/// _messages = [ +/// ChatMessage( +/// text: 'Hello, how can I help you today?', +/// time: DateTime.now(), +/// author: const ChatAuthor( +/// id: 'a2c4-56h8-9x01-2a3d', +/// name: 'Incoming user name', +/// ), +/// ), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// messages: _messages, +/// outgoingUser: '8ob3-b720-g9s6-25s8', +/// composer: const ChatComposer( +/// decoration: InputDecoration( +/// hintText: 'Type a message', +/// ), +/// ), +/// actionButton: ChatActionButton( +/// onPressed: (String newMessage) { +/// setState(() { +/// _messages.add(ChatMessage( +/// text: newMessage, +/// time: DateTime.now(), +/// author: const ChatAuthor( +/// id: '8ob3-b720-g9s6-25s8', +/// name: 'Outgoing user name', +/// ), +/// )); +/// }); +/// }, +/// ), +/// ); +/// } +/// +/// @override +/// void dispose() { +/// _messages.clear(); +/// super.dispose(); +/// } +/// ``` +/// +/// The following example demonstrates how to create a chat interface with +/// the builder properties of the [SfChat] widget. +/// +/// ```dart +/// late TextEditingController _textController; +/// late List _messages; +/// +/// @override +/// void initState() { +/// _textController = TextEditingController(); +/// _messages = [ +/// ChatMessage( +/// text: 'Hello, how can I help you today?', +/// time: DateTime.now(), +/// author: const ChatAuthor( +/// id: 'a2c4-56h8-9x01-2a3d', +/// name: 'Incoming user name', +/// ), +/// ), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// messages: _messages, +/// outgoingUser: '8ob3-b720-g9s6-25s8', +/// composer: ChatComposer.builder( +/// builder: (BuildContext context) { +/// return TextFormField( +/// controller: _textController, +/// decoration: InputDecoration( +/// icon: const Icon(Icons.add), +/// hintText: 'Type a message', +/// border: const OutlineInputBorder( +/// borderRadius: BorderRadius.all(Radius.circular(5.0)), +/// ), +/// suffix: IconButton( +/// onPressed: () { +/// // Handle send button click. +/// }, +/// icon: const Icon(Icons.send), +/// ), +/// ), +/// ); +/// }, +/// ), +/// actionButton: ChatActionButton( +/// size: const Size(90, 40), +/// backgroundColor: Colors.transparent, +/// hoverColor: Colors.transparent, +/// focusColor: Colors.transparent, +/// onPressed: (String newMessage) { +/// // Handle send button click. +/// }, +/// child: Row( +/// mainAxisSize: MainAxisSize.min, +/// children: [ +/// IconButton( +/// constraints: BoxConstraints.tight(const Size(40, 40)), +/// icon: const Icon(Icons.camera_alt_outlined), +/// onPressed: () { +/// // Handle camera button click. +/// }, +/// ), +/// const SizedBox(width: 5), +/// IconButton( +/// constraints: BoxConstraints.tight(const Size(40, 40)), +/// icon: const Icon(Icons.mic_none), +/// onPressed: () { +/// // Handle mic button click. +/// }, +/// ), +/// ], +/// ), +/// ), +/// ); +/// } +/// +/// @override +/// void dispose() { +/// _textController.dispose(); +/// _messages.clear(); +/// super.dispose(); +/// } +/// ``` +/// See also: +/// * [SfChatTheme] and [SfChatThemeData] for information about controlling +/// the visual appearance of the [SfChat]. +class SfChat extends StatefulWidget { + /// Creates an [SfChat] widget that displays the content of either incoming or + /// outgoing messages based on the current user. + /// + /// Each message includes details such as the message text, timestamp, + /// author information, list of suggestions and suggestion settings. + const SfChat({ + super.key, + required this.messages, + required this.outgoingUser, + this.composer = const ChatComposer(), + this.actionButton, + this.placeholderBuilder, + this.messageHeaderBuilder, + this.messageAvatarBuilder, + this.messageContentBuilder, + this.messageFooterBuilder, + this.onSuggestionItemSelected, + this.incomingMessageSettings = const ChatMessageSettings(), + this.outgoingMessageSettings = const ChatMessageSettings(), + }); + + /// A list of [ChatMessage] objects that will be displayed in the chat + /// interface. + /// + /// Each message includes details such as the message text, timestamp, + /// author information, list of suggestions and suggestion settings. + /// + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// text: + /// "Hi, I have planned to go on a trip. Can you suggest some places?", + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: 'a2c4-56h8-9x01-2a3d', + /// name: 'User name', + /// ), + /// suggestions: [ + /// ChatMessageSuggestion( + /// data: 'Paris', + /// ), + /// ChatMessageSuggestion( + /// data: 'New York', + /// ), + /// ChatMessageSuggestion( + /// data: 'Tokyo', + /// ), + /// ChatMessageSuggestion( + /// data: 'London', + /// ), + /// ], + /// suggestionSettings: ChatSuggestionSettings( + /// backgroundColor: Colors.grey[300], + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.grey[400]!; + /// } + /// return Colors.grey; + /// }, + /// ), + /// shape: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all( + /// Radius.circular(4.0), + /// ), + /// ), + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(5), + /// ); + /// }, + /// ), + /// textStyle: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.disabled)) { + /// return const TextStyle(fontSize: 16); + /// } + /// return const TextStyle(fontSize: 14); + /// }, + /// ), + /// margin: const EdgeInsets.all(10), + /// itemPadding: + /// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), + /// orientation: Axis.horizontal, + /// itemOverflow: ChatSuggestionOverflow.scroll, + /// runSpacing: 10.0, + /// spacing: 10.0, + /// ), + /// ), + /// ]; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: _messages, + /// ); + /// } + /// ``` + /// + /// Additionally, the message content can be extended to include more + /// information about the chat message. + /// + /// ```dart + /// class ChatMessageExt extends ChatMessage { + /// const ChatMessageExtend({ + /// required super.text, + /// required super.time, + /// required super.author, + /// required this.displayName, + /// required this.aboutMessage, + /// }); + /// + /// final String displayName; + /// final String aboutMessage; + /// } + /// + /// List _messages = [ + /// ChatMessageExt( + /// text: 'Hello, how can I help you today?', + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: 'a2c4-56h8-9x01-2a3d', + /// name: 'User name', + /// ), + /// displayName: 'UN', + /// aboutMessage: 'A coding enthusiast', + /// ), + /// ]; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: _messages, + /// ); + /// } + /// ``` + final List messages; + + /// The distinct identifier of the user who is sending the message. + /// + /// This distinctive ID differentiates messages from the current user + /// (outgoing message) from those sent by others (incoming message), + /// ensuring an accurate display of the chat. + /// + /// If [ChatMessage.id] is equal to [outgoingUser], the message is considered + /// an outgoing message; otherwise, it is considered an incoming message. + /// + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// text: 'Hello, how can I help you today?', + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: 'a2c4-56h8-9x01-2a3d', + /// name: 'Incoming user name', + /// ), + /// ), + /// ChatMessage( + /// text: 'I'm looking for help with the Flutter Chat widget.', + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: '8ob3-b720-g9s6-25s8', + /// name: 'Outgoing user name', + /// ), + /// ), + /// ]; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// outgoingUser: '8ob3-b720-g9s6-25s8', + /// ); + /// } + /// ``` + /// See also: + /// * [messages] to load message conversations. + /// * [incomingMessageSettings] to customize the appearance of incoming chat + /// bubbles. + /// * [outgoingMessageSettings] to customize the appearance of outgoing chat + /// bubbles. + final String outgoingUser; + + /// A primary text editor for composing outgoing messages. + /// + /// The [composer] is a customizable text editor designed for typing + /// new messages. It offers options to adjust the appearance and behavior + /// of the text editor, including settings for the minimum and maximum + /// number of lines, decoration, and text style. + /// + /// By default, the text editor does not include hint text. + /// Hint text can be added using the [InputDecoration.hintText] property + /// within [InputDecoration]. + /// + /// The [actionButton] becomes enabled when text is entered into the editor. + /// + /// If the [composer] is disabled by setting [InputDecoration.enabled] to + /// false, the [actionButton] will also be disabled. + /// + /// If [composer] is null, the [actionButton] remains enabled. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer( + /// minLines: 1, + /// maxLines: 3, + /// decoration: InputDecoration( + /// hintText: 'Type a message...', + /// border: OutlineInputBorder( + /// borderRadius: BorderRadius.all(Radius.circular(10.0)), + /// ), + /// ), + /// ), + /// ); + /// } + /// ``` + /// + /// ## Builder + /// + /// The [ChatComposer.builder] enables an option to create a custom composer. + /// + /// It is useful for integrating additional features such as icons, buttons, + /// or other widgets. + /// + /// When [ChatComposer.builder] is used, the [actionButton] will always + /// remain enabled. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer.builder( + /// builder: (BuildContext context) { + /// return TextFormField( + /// decoration: InputDecoration( + /// hintText: 'Type a message...', + /// border: OutlineInputBorder( + /// borderRadius: BorderRadius.all(Radius.circular(10.0)), + /// ), + /// prefixIcon: IconButton( + /// icon: Icon(Icons.attach_file), + /// onPressed: () { + /// // Handle attachment button click. + /// }, + /// ), + /// suffixIcon: IconButton( + /// icon: Icon(Icons.mic), + /// onPressed: () { + /// // Handle mic button click. + /// }, + /// ), + /// ), + /// ); + /// }, + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// * [ChatComposer.builder] to create a custom text editor. + /// * [actionButton] to customize the send button. + /// * [placeholderBuilder] to create a custom widget when there are no + /// messages in the chat. + final ChatComposer? composer; + + /// Represents a send button. + /// + /// The [SfChat] widget does not include an action button by default. + /// To add, create a new instance of [ChatActionButton]. + /// + /// If the [ChatActionButton.onPressed] callback is null, the button + /// remains disabled. + /// + /// If the default [composer]'s text is empty or if the [composer] is + /// disabled, the button remains disabled. + /// + /// If the [composer] is null, the button always stays enabled. + /// + /// Pressing the action button invokes the [ChatActionButton.onPressed] + /// callback with the text entered in the default [composer]. By default, + /// [SfChat] will not update its state until the parent widget + /// rebuilds the chat with new messages. + /// + /// If [ChatComposer.builder] is used, the button always stays enabled. + /// + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// text: 'Hello, how can I help you today?', + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: 'a2c4-56h8-9x01-2a3d', + /// name: 'Incoming user name', + /// ), + /// ), + /// ]; + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: _messages, + /// outgoingUser: '8ob3-b720-g9s6-25s8', + /// actionButton: ChatActionButton( + /// onPressed: (String newMessage) { + /// setState(() { + /// _messages.add( + /// ChatMessage( + /// text: newMessage, + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: '8ob3-b720-g9s6-25s8', + /// name: 'Outgoing user name', + /// ), + /// ), + /// ); + /// }); + /// }, + /// ), + /// ); + /// } + /// ``` + /// + /// See also: + /// * [ChatComposer] to customize the default text editor. + /// * [ChatComposer.builder] to create a custom text editor. + /// * [ChatActionButton.onPressed] to handle the send button click function. + /// * [ChatActionButton.shape] to customize the shape of the send button. + final ChatActionButton? actionButton; + + /// A callback function creates a widget to display when there + /// are no messages in the chat. + /// + /// You can use the [placeholderBuilder] to create a custom widget that + /// appears when the conversation is idle. This is particularly handy for + /// presenting users with a relevant or visually appealing message indicating + /// that the conversation is currently empty. + /// + /// The callback accepts the [BuildContext] as a parameter and should return + /// a [Widget]. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// placeholderBuilder: + /// (BuildContext context) { + /// return Center(child: Text('No messages yet')); + /// }, + /// ); + /// } + /// ``` + final WidgetBuilder? placeholderBuilder; + + /// A callback function creates a widget to serve as a header for each chat + /// bubble. + /// + /// The [messageHeaderBuilder] allows you to specify a custom widget that will + /// be shown as a header within each chat bubble. This is particularly useful + /// for displaying additional information such as the sender's name and + /// timestamp associated with each message. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [ChatMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default header widget. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messageHeaderBuilder: + /// (BuildContext context, ChatMessage message) { + /// return Padding( + /// padding: EdgeInsets.all(8.0), + /// child: Text( + /// message.author.name, + /// style: TextStyle(fontWeight: FontWeight.bold) + /// ), + /// ); + /// }, + /// ); + /// } + /// ``` + /// + /// See also: + /// + /// * The `headerPadding` in [ChatMessageSettings] for adjusting the + /// padding of the messageHeaderBuilder. + final ChatWidgetBuilder? messageHeaderBuilder; + + /// A callback function creates a widget to display as an avatar within each + /// chat bubble. + /// + /// The [messageAvatarBuilder] allows you to specify a custom widget that will + /// be shown as an avatar within each chat bubble. This is particularly + /// useful for displaying user avatars or profile pictures in the chat + /// interface. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [ChatMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default avatar widget. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messageAvatarBuilder: + /// (BuildContext context, int index, ChatMessage message) { + /// return CircleAvatar( + /// backgroundImage: NetworkImage( + /// message.author.id == '123-001' + /// ? 'https://example.com/outgoing-avatar.jpg' + /// : 'https://example.com/incoming-avatar.jpg', + /// ), + /// ); + /// }, + /// ); + /// } + /// ``` + /// + /// See also: + /// + /// * The `avatarPadding` in [ChatMessageSettings] for adjusting the + /// padding of the messageAvatarBuilder. + final ChatWidgetBuilder? messageAvatarBuilder; + + /// A callback function creates a widget to display as the content of each + /// chat bubble. + /// + /// The [messageContentBuilder] allows you to specify a custom widget to + /// display as the content within each chat bubble. This is useful for + /// customizing how the message content is presented, such as using different + /// background colors, borders, or padding. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [ChatMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default content widget. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messageContentBuilder: + /// (BuildContext context, int index, ChatMessage message) { + /// return Padding( + /// padding: EdgeInsets.all(8.0), + /// child: Text(message.text), + /// ); + /// }, + /// ); + /// } + /// ``` + /// + /// See also + /// + /// * The `padding` in [ChatMessageSettings] for adjusting the + /// padding around bubble's content of the messageContentBuilder. + final ChatWidgetBuilder? messageContentBuilder; + + /// A callback function creates a widget to display as a footer within each + /// chat bubble. + /// + /// The [messageFooterBuilder] allows you to specify a custom widget that will + /// be shown as a footer within each chat bubble. This is particularly useful + /// for displaying timestamps or other additional information related to the + /// message. + /// + /// The callback accepts three parameters: [BuildContext], message index in + /// the list, and [ChatMessage] and returns a [Widget]. + /// + /// If a new instance is not assigned to this property, it will use the + /// default footer widget. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messageFooterBuilder: + /// (BuildContext context, int index, ChatMessage message) { + /// return Padding( + /// padding: EdgeInsets.all(4.0), + /// child: Text( + /// DateFormat('hh:mm a').format(message.time), + /// style: TextStyle(fontSize: 12.0, color: Colors.grey), + /// ), + /// ); + /// }, + /// ); + /// } + /// + /// String _formatTimestamp(DateTime timestamp) { + /// return DateFormat('h:mm a').format(timestamp); + /// } + /// ``` + /// + /// See also: + /// + /// * The `footerPadding` in [ChatMessageSettings] for adjusting the + /// padding of the messageFooterBuilder. + final ChatWidgetBuilder? messageFooterBuilder; + + /// Optional callback function that will be executed when the suggestion + /// is selected. + /// + /// The [onSuggestionItemSelected] callback is triggered when the user + /// selects a suggestion from the suggestion list. This callback provides + /// details about the selected suggestion, suggestion index, and the + /// message index. + /// + /// When a suggestion is selected, you can use this callback to update the + /// UI to reflect the selected state of the suggestion and the selected + /// suggestion is added as a new message in the conversation area. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// onSuggestionItemSelected: + /// (selected, messageIndex, suggestion, suggestionIndex) { + /// setState(() { + /// _messages[messageIndex].suggestions![messageIndex] = + /// suggestion.copyWith(selected: true); + /// _messages.add( + /// ChatMessage( + /// text: _messages[messageIndex] + /// .suggestions![messageIndex] + /// .data!, + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: 'a2c4-56h8-9x01-2a3d', + /// name: 'Incoming user name', + /// ), + /// ), + /// ); + /// }); + /// }, + /// ); + /// } + /// ``` + final ChatSuggestionItemSelectedCallback? onSuggestionItemSelected; + + /// Options for changing the appearance and behavior of incoming chat bubbles. + /// + /// The [incomingMessageSettings] property allows you to configure how + /// incoming chat bubbles are displayed. This includes customization options + /// for the user's avatar, username, timestamp, content background color, and + /// various padding and shape options. + /// + /// If a new instance is not assigned to this property, the default settings + /// for incoming chat bubbles will be used. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// showAuthorName: true, + /// showTimestamp: true, + /// showAuthorAvatar: true, + /// widthFactor: 0.8, + /// avatarSize: const Size.square(32.0), + /// margin: const EdgeInsets.all(2.0), + /// padding: + /// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + /// headerPadding: + /// const EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), + /// footerPadding: const EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// ); + /// } + /// ``` + final ChatMessageSettings incomingMessageSettings; + + /// Options for changing the appearance and behavior of outgoing chat bubbles. + /// + /// The [outgoingMessageSettings] property allows you to configure how + /// incoming chat bubbles are displayed. This includes customization options + /// for the user's avatar, username, timestamp, content background color, and + /// various padding and shape options. + /// + /// If a new instance is not assigned to this property, the default settings + /// for outgoing chat bubbles will be used. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// outgoingMessageSettings: ChatMessageSettings( + /// showAuthorName: true, + /// showTimestamp: true, + /// showAuthorAvatar: true, + /// widthFactor: 0.8, + /// avatarSize: const Size.square(32.0), + /// margin: const EdgeInsets.all(2.0), + /// padding: + /// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + /// headerPadding: + /// const EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), + /// footerPadding: const EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// ); + /// } + /// ``` + final ChatMessageSettings outgoingMessageSettings; + + @override + State createState() => _SfChatState(); +} + +class _SfChatState extends State { + final InputBorder _defaultInputDecorBorder = const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(42.0)), + ); + final EdgeInsetsGeometry _defaultInputDecorContentPadding = + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 18.0); + + late TextEditingController _textController; + late FocusNode _focusNode; + + late ThemeData _themeData; + late SfChatThemeData _defaultThemeData; + late SfChatThemeData _userDefinedThemeData; + late SfChatThemeData _effectiveChatThemeData; + + void _updateThemeData(BuildContext context) { + _themeData = Theme.of(context); + _defaultThemeData = + _themeData.useMaterial3 + ? ChatM3ThemeData(context) + : ChatM2ThemeData(context); + _userDefinedThemeData = SfChatTheme.of(context); + final TextStyle contentBaseTextStyle = _themeData.textTheme.bodyMedium! + .copyWith(color: _themeData.colorScheme.onSurface); + final TextStyle primaryHeaderBaseTextStyle = _themeData + .textTheme + .labelMedium! + .copyWith(color: _themeData.colorScheme.primary); + final TextStyle secondaryHeaderBaseTextStyle = _themeData + .textTheme + .labelSmall! + .copyWith(color: _themeData.colorScheme.onSurfaceVariant); + + _effectiveChatThemeData = _userDefinedThemeData.copyWith( + actionButtonForegroundColor: + widget.actionButton?.foregroundColor ?? + _userDefinedThemeData.actionButtonForegroundColor ?? + _defaultThemeData.actionButtonForegroundColor, + actionButtonBackgroundColor: + widget.actionButton?.backgroundColor ?? + _userDefinedThemeData.actionButtonBackgroundColor ?? + _defaultThemeData.actionButtonBackgroundColor, + actionButtonDisabledForegroundColor: + _userDefinedThemeData.actionButtonDisabledForegroundColor ?? + _defaultThemeData.actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor: + _userDefinedThemeData.actionButtonDisabledBackgroundColor ?? + _defaultThemeData.actionButtonDisabledBackgroundColor, + actionButtonFocusColor: + widget.actionButton?.focusColor ?? + _userDefinedThemeData.actionButtonFocusColor ?? + _defaultThemeData.actionButtonFocusColor, + actionButtonHoverColor: + widget.actionButton?.hoverColor ?? + _userDefinedThemeData.actionButtonHoverColor ?? + _defaultThemeData.actionButtonHoverColor, + actionButtonSplashColor: + widget.actionButton?.splashColor ?? + _userDefinedThemeData.actionButtonSplashColor ?? + _defaultThemeData.actionButtonSplashColor, + actionButtonElevation: + widget.actionButton?.elevation ?? + _userDefinedThemeData.actionButtonElevation, + actionButtonFocusElevation: + widget.actionButton?.focusElevation ?? + _userDefinedThemeData.actionButtonFocusElevation, + actionButtonHoverElevation: + widget.actionButton?.hoverElevation ?? + _userDefinedThemeData.actionButtonHoverElevation, + actionButtonHighlightElevation: + widget.actionButton?.highlightElevation ?? + _userDefinedThemeData.actionButtonHighlightElevation, + actionButtonMouseCursor: + widget.actionButton?.mouseCursor ?? + _userDefinedThemeData.actionButtonMouseCursor ?? + _defaultThemeData.actionButtonMouseCursor, + actionButtonShape: + widget.actionButton?.shape ?? + _userDefinedThemeData.actionButtonShape ?? + _defaultThemeData.actionButtonShape, + outgoingAvatarBackgroundColor: + _userDefinedThemeData.outgoingAvatarBackgroundColor ?? + _defaultThemeData.outgoingAvatarBackgroundColor, + incomingAvatarBackgroundColor: + _userDefinedThemeData.incomingAvatarBackgroundColor ?? + _defaultThemeData.incomingAvatarBackgroundColor, + outgoingMessageBackgroundColor: + widget.outgoingMessageSettings.backgroundColor ?? + _userDefinedThemeData.outgoingMessageBackgroundColor ?? + _defaultThemeData.outgoingMessageBackgroundColor, + incomingMessageBackgroundColor: + widget.incomingMessageSettings.backgroundColor ?? + _userDefinedThemeData.incomingMessageBackgroundColor ?? + _defaultThemeData.incomingMessageBackgroundColor, + editorTextStyle: contentBaseTextStyle + .merge(_userDefinedThemeData.editorTextStyle) + .merge(widget.composer?.textStyle), + outgoingContentTextStyle: contentBaseTextStyle + .merge(_userDefinedThemeData.outgoingContentTextStyle) + .merge(widget.outgoingMessageSettings.textStyle), + incomingContentTextStyle: contentBaseTextStyle + .merge(_userDefinedThemeData.incomingContentTextStyle) + .merge(widget.incomingMessageSettings.textStyle), + outgoingPrimaryHeaderTextStyle: primaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.outgoingPrimaryHeaderTextStyle) + .merge(widget.outgoingMessageSettings.headerTextStyle), + incomingPrimaryHeaderTextStyle: primaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.incomingPrimaryHeaderTextStyle) + .merge(widget.incomingMessageSettings.headerTextStyle), + outgoingSecondaryHeaderTextStyle: secondaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.outgoingSecondaryHeaderTextStyle) + .merge(widget.outgoingMessageSettings.headerTextStyle), + incomingSecondaryHeaderTextStyle: secondaryHeaderBaseTextStyle + .merge(_userDefinedThemeData.incomingSecondaryHeaderTextStyle) + .merge(widget.incomingMessageSettings.headerTextStyle), + outgoingMessageShape: + widget.outgoingMessageSettings.shape ?? + _userDefinedThemeData.outgoingMessageShape ?? + _defaultThemeData.outgoingMessageShape, + incomingMessageShape: + widget.incomingMessageSettings.shape ?? + _userDefinedThemeData.incomingMessageShape ?? + _defaultThemeData.incomingMessageShape, + suggestionItemBackgroundColor: + _userDefinedThemeData.suggestionItemBackgroundColor ?? + _defaultThemeData.suggestionItemBackgroundColor, + suggestionItemShape: + _userDefinedThemeData.suggestionItemShape ?? + _defaultThemeData.suggestionItemShape, + ); + } + + TextStyle _suggestionTextStyle(Set states) { + TextStyle? userTextStyle; + final TextStyle baseTextStyle = _themeData.textTheme.bodyMedium!.copyWith( + color: _themeData.colorScheme.onSurface, + ); + final TextStyle defaultTextStyle = + _defaultThemeData.suggestionItemTextStyle!.resolve(states)!; + if (_userDefinedThemeData.suggestionItemTextStyle != null) { + userTextStyle = _userDefinedThemeData.suggestionItemTextStyle?.resolve( + states, + ); + } + + return baseTextStyle.merge(userTextStyle).merge(defaultTextStyle); + } + + Widget? _buildEditor(BuildContext context) { + if (widget.composer == null) { + return null; + } else { + return TextEditor( + composer: widget.composer!, + decoration: _effectiveInputDecoration(), + actionButton: widget.actionButton, + focusNode: _focusNode, + textController: _textController, + textStyle: _effectiveChatThemeData.editorTextStyle, + ); + } + } + + InputDecoration? _effectiveInputDecoration() { + if (widget.composer == null) { + return null; + } else { + if (widget.composer!.builder != null) { + return null; + } + + final InputDecoration? decoration = widget.composer!.decoration; + if (decoration != null) { + final InputBorder effectiveBorder = + decoration.border ?? _defaultInputDecorBorder; + final EdgeInsetsGeometry effectiveContentPadding = + decoration.contentPadding ?? _defaultInputDecorContentPadding; + return decoration.copyWith( + border: effectiveBorder, + contentPadding: effectiveContentPadding, + ); + } + } + return null; + } + + Widget? _buildActionButton(BuildContext context) { + if (widget.actionButton == null) { + return null; + } else { + return ActionButtonWidget( + settings: widget.actionButton!, + composer: widget.composer, + textController: _textController, + actionButtonForegroundColor: + _effectiveChatThemeData.actionButtonForegroundColor, + actionButtonDisabledForegroundColor: + _effectiveChatThemeData.actionButtonDisabledForegroundColor, + actionButtonBackgroundColor: + _effectiveChatThemeData.actionButtonBackgroundColor, + actionButtonDisabledBackgroundColor: + _effectiveChatThemeData.actionButtonDisabledBackgroundColor, + actionButtonFocusColor: _effectiveChatThemeData.actionButtonFocusColor, + actionButtonHoverColor: _effectiveChatThemeData.actionButtonHoverColor, + actionButtonSplashColor: + _effectiveChatThemeData.actionButtonSplashColor, + actionButtonElevation: _effectiveChatThemeData.actionButtonElevation, + actionButtonFocusElevation: + _effectiveChatThemeData.actionButtonFocusElevation, + actionButtonHoverElevation: + _effectiveChatThemeData.actionButtonHoverElevation, + actionButtonHighlightElevation: + _effectiveChatThemeData.actionButtonHighlightElevation, + actionButtonDisabledElevation: + _effectiveChatThemeData.actionButtonDisabledElevation, + actionButtonShape: _effectiveChatThemeData.actionButtonShape, + ); + } + } + + @override + void initState() { + _textController = TextEditingController(); + _focusNode = FocusNode(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _updateThemeData(context); + final Widget? editor = _buildEditor(context); + final Widget? actionButton = _buildActionButton(context); + + return LayoutHandler( + children: [ + ChatConversationArea( + outgoingUser: widget.outgoingUser, + messages: widget.messages, + incomingBubbleSettings: widget.incomingMessageSettings, + outgoingBubbleSettings: widget.outgoingMessageSettings, + placeholderBuilder: widget.placeholderBuilder, + bubbleHeaderBuilder: widget.messageHeaderBuilder, + bubbleAvatarBuilder: widget.messageAvatarBuilder, + bubbleContentBuilder: widget.messageContentBuilder, + bubbleFooterBuilder: widget.messageFooterBuilder, + outgoingAvatarBackgroundColor: + _effectiveChatThemeData.outgoingAvatarBackgroundColor, + incomingAvatarBackgroundColor: + _effectiveChatThemeData.incomingAvatarBackgroundColor, + outgoingBubbleContentBackgroundColor: + _effectiveChatThemeData.outgoingMessageBackgroundColor, + incomingBubbleContentBackgroundColor: + _effectiveChatThemeData.incomingMessageBackgroundColor, + outgoingPrimaryHeaderTextStyle: + _effectiveChatThemeData.outgoingPrimaryHeaderTextStyle, + incomingPrimaryHeaderTextStyle: + _effectiveChatThemeData.incomingPrimaryHeaderTextStyle, + outgoingSecondaryHeaderTextStyle: + _effectiveChatThemeData.outgoingSecondaryHeaderTextStyle, + incomingSecondaryHeaderTextStyle: + _effectiveChatThemeData.incomingSecondaryHeaderTextStyle, + outgoingContentTextStyle: + _effectiveChatThemeData.outgoingContentTextStyle, + incomingContentTextStyle: + _effectiveChatThemeData.incomingContentTextStyle, + suggestionItemTextStyle: _suggestionTextStyle, + outgoingBubbleContentShape: + _effectiveChatThemeData.outgoingMessageShape, + incomingBubbleContentShape: + _effectiveChatThemeData.incomingMessageShape, + suggestionBackgroundColor: + _effectiveChatThemeData.suggestionBackgroundColor, + suggestionBackgroundShape: + _effectiveChatThemeData.suggestionBackgroundShape, + suggestionItemBackgroundColor: + _effectiveChatThemeData.suggestionItemBackgroundColor, + suggestionItemShape: _effectiveChatThemeData.suggestionItemShape, + onSuggestionItemSelected: widget.onSuggestionItemSelected, + themeData: _themeData, + ), + if (editor != null || actionButton != null) + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (editor != null) Expanded(child: editor), + if (actionButton != null) actionButton, + ], + ), + ], + ); + } + + @override + void dispose() { + _focusNode.dispose(); + _textController.dispose(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_chat/lib/src/chat/conversion_area.dart b/packages/syncfusion_flutter_chat/lib/src/chat/conversion_area.dart new file mode 100644 index 000000000..6b773580d --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/chat/conversion_area.dart @@ -0,0 +1,497 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart' hide TextDirection; + +import '../conversion_area.dart'; +import '../settings.dart'; +import 'settings.dart'; + +class ChatConversationArea extends ConversationArea { + const ChatConversationArea({ + super.key, + required this.outgoingUser, + required super.messages, + required super.outgoingBubbleSettings, + required super.incomingBubbleSettings, + super.bubbleAlignment = BubbleAlignment.auto, + super.placeholderBehavior = PlaceholderBehavior.hideOnMessage, + super.placeholderBuilder, + super.bubbleHeaderBuilder, + super.bubbleAvatarBuilder, + super.bubbleContentBuilder, + super.bubbleFooterBuilder, + super.outgoingAvatarBackgroundColor, + super.incomingAvatarBackgroundColor, + super.outgoingBubbleContentBackgroundColor, + super.incomingBubbleContentBackgroundColor, + super.outgoingPrimaryHeaderTextStyle, + super.incomingPrimaryHeaderTextStyle, + super.outgoingSecondaryHeaderTextStyle, + super.incomingSecondaryHeaderTextStyle, + required super.suggestionItemTextStyle, + super.outgoingContentTextStyle, + super.incomingContentTextStyle, + super.outgoingBubbleContentShape, + super.incomingBubbleContentShape, + super.suggestionBackgroundColor, + super.suggestionBackgroundShape, + super.suggestionItemBackgroundColor, + super.suggestionItemShape, + this.onSuggestionItemSelected, + required super.themeData, + }); + + final String outgoingUser; + final ChatSuggestionItemSelectedCallback? onSuggestionItemSelected; + + @override + State> createState() => + _ChatConversationAreaState(); +} + +class _ChatConversationAreaState extends ConversationAreaState { + @override + ChatConversationArea get widget => super.widget as ChatConversationArea; + + @override + EdgeInsetsGeometry effectiveAvatarPadding( + bool isOutgoingMessage, + EdgeInsetsGeometry? avatarPadding, + ) { + EdgeInsetsGeometry padding; + if (avatarPadding != null) { + padding = avatarPadding; + } else { + if (isOutgoingMessage) { + padding = const EdgeInsetsDirectional.only(start: 4.0); + } else { + padding = const EdgeInsetsDirectional.only(end: 4.0); + } + } + return padding; + } + + @override + bool isFirstInGroup(int index, ChatMessage message) { + return index == 0 || + message.author.id != widget.messages[index - 1].author.id; + } + + @override + bool isOutgoingMessage(ChatMessage message) { + return message.author.id == widget.outgoingUser; + } + + @override + Widget buildMessageBubble(int index, double width, double height) { + final ChatMessage message = widget.messages[index]; + final bool isLeadMessage = isFirstInGroup(index, message); + final bool isFromCurrentUser = isOutgoingMessage(message); + + ShapeBorder? contentShape; + Color? avatarBackgroundColor; + Color? contentBackgroundColor; + TextStyle? contentTextStyle; + TextStyle? primaryHeaderTextStyle; + TextStyle? secondaryHeaderTextStyle; + MessageSettings settings; + if (isFromCurrentUser) { + contentShape = widget.outgoingBubbleContentShape; + avatarBackgroundColor = widget.outgoingAvatarBackgroundColor; + contentBackgroundColor = widget.outgoingBubbleContentBackgroundColor; + contentTextStyle = widget.outgoingContentTextStyle; + primaryHeaderTextStyle = widget.outgoingPrimaryHeaderTextStyle; + secondaryHeaderTextStyle = widget.outgoingSecondaryHeaderTextStyle; + settings = widget.outgoingBubbleSettings; + } else { + contentShape = widget.incomingBubbleContentShape; + avatarBackgroundColor = widget.incomingAvatarBackgroundColor; + contentBackgroundColor = widget.incomingBubbleContentBackgroundColor; + contentTextStyle = widget.incomingContentTextStyle; + primaryHeaderTextStyle = widget.incomingPrimaryHeaderTextStyle; + secondaryHeaderTextStyle = widget.incomingSecondaryHeaderTextStyle; + settings = widget.incomingBubbleSettings; + } + + Widget result = _ChatMessageBubble( + index: index, + maxWidth: width, + widthFactor: settings.widthFactor, + messages: widget.messages, + message: message, + isOutgoing: isFromCurrentUser, + isFirstInGroup: isLeadMessage, + headerBuilder: widget.bubbleHeaderBuilder, + avatarBuilder: widget.bubbleAvatarBuilder, + contentBuilder: widget.bubbleContentBuilder, + footerBuilder: widget.bubbleFooterBuilder, + showUserAvatar: settings.showAuthorAvatar, + showUserName: settings.showAuthorName, + showTimestamp: settings.showTimestamp, + timestampFormat: settings.timestampFormat, + alignment: effectiveBubbleAlignment(isFromCurrentUser), + contentShape: contentShape, + avatarBackgroundColor: avatarBackgroundColor, + contentBackgroundColor: contentBackgroundColor, + contentTextStyle: contentTextStyle, + primaryHeaderTextStyle: primaryHeaderTextStyle, + secondaryHeaderTextStyle: secondaryHeaderTextStyle, + suggestionItemTextStyle: widget.suggestionItemTextStyle, + padding: settings.padding ?? EdgeInsets.zero, + contentPadding: settings.padding ?? EdgeInsets.zero, + avatarPadding: effectiveAvatarPadding( + isFromCurrentUser, + settings.avatarPadding, + ), + headerPadding: settings.headerPadding, + footerPadding: settings.footerPadding, + avatarSize: settings.avatarSize, + suggestionBackgroundColor: widget.suggestionBackgroundColor, + suggestionBackgroundShape: widget.suggestionBackgroundShape, + suggestionItemBackgroundColor: widget.suggestionItemBackgroundColor, + suggestionItemShape: widget.suggestionItemShape, + onSuggestionItemSelected: widget.onSuggestionItemSelected, + themeData: widget.themeData, + textDirection: textDirection, + alignmentDirection: alignmentBasedTextDirection( + isFromCurrentUser, + textDirection, + ), + ); + + if (index == 0 && + widget.placeholderBehavior == PlaceholderBehavior.scrollWithMessage && + widget.placeholderBuilder != null) { + result = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: width, maxHeight: height), + child: widget.placeholderBuilder!(context), + ), + ), + result, + ], + ); + } + + return KeyedSubtree(key: IndexedValueKey(index), child: result); + } +} + +class _ChatMessageBubble extends MessageBubble { + const _ChatMessageBubble({ + required super.index, + required super.maxWidth, + super.widthFactor = 0.8, + required this.messages, + required super.message, + required super.isOutgoing, + required super.isFirstInGroup, + super.headerBuilder, + super.avatarBuilder, + super.contentBuilder, + super.footerBuilder, + super.showUserAvatar, + super.showUserName = false, + super.showTimestamp = false, + super.timestampFormat, + super.alignment = BubbleAlignment.auto, + super.contentShape, + super.avatarBackgroundColor, + super.contentBackgroundColor, + super.contentTextStyle, + super.primaryHeaderTextStyle, + super.secondaryHeaderTextStyle, + super.suggestionItemTextStyle, + super.padding = const EdgeInsets.all(2.0), + super.contentPadding = const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + required super.avatarPadding, + super.headerPadding = const EdgeInsetsDirectional.only( + top: 14.0, + bottom: 4.0, + ), + super.footerPadding = const EdgeInsetsDirectional.only(top: 4.0), + super.avatarSize = const Size.square(32.0), + super.suggestionBackgroundColor, + super.suggestionBackgroundShape, + super.suggestionItemBackgroundColor, + super.suggestionItemShape, + this.onSuggestionItemSelected, + required super.themeData, + required super.textDirection, + required super.alignmentDirection, + }); + final List messages; + final ChatSuggestionItemSelectedCallback? onSuggestionItemSelected; + + @override + State createState() => _ChatMessageBubbleState(); +} + +class _ChatMessageBubbleState extends MessageBubbleState { + @override + _ChatMessageBubble get widget => super.widget as _ChatMessageBubble; + + @override + EdgeInsets effectiveMessagePadding() { + final int messageCount = widget.messages.length; + final ChatMessage current = widget.message; + final ChatMessage? previous = + widget.index - 1 >= 0 ? widget.messages[widget.index - 1] : null; + final ChatMessage? next = + widget.index + 1 < messageCount + ? widget.messages[widget.index + 1] + : null; + + final EdgeInsets padding = super.effectiveMessagePadding(); + double top = padding.top; + double bottom = padding.bottom; + if (previous != null && current.author.id == previous.author.id) { + top = 2.0; + } + + if (next != null && current.author.id == next.author.id) { + bottom = 2.0; + } + + return EdgeInsets.only( + top: top, + bottom: bottom, + left: padding.left, + right: padding.right, + ); + } + + @override + Widget? buildDefaultHeader() { + final bool canAddSpace = widget.showUserName && widget.showTimestamp; + if (widget.showUserName || widget.showTimestamp) { + if (widget.isFirstInGroup) { + return Row( + mainAxisSize: MainAxisSize.min, + textDirection: TextDirection.ltr, + children: [ + if (widget.showUserName) _buildUserName(), + if (canAddSpace) const SizedBox(width: 4.0), + if (widget.showTimestamp) _buildTimestamp(), + ], + ); + } + } + + return null; + } + + Widget _buildUserName() { + return Text( + widget.message.author.name, + style: widget.primaryHeaderTextStyle, + textDirection: TextDirection.ltr, + ); + } + + Widget _buildTimestamp() { + final DateFormat effectiveDateFormat = + widget.timestampFormat ?? MessageBubbleState.defaultTimestampFormat; + return Text( + effectiveDateFormat.format(widget.message.time), + style: widget.secondaryHeaderTextStyle, + textDirection: TextDirection.ltr, + ); + } + + @override + bool displayAvatar() { + return widget.showUserAvatar ?? false; + } + + @override + bool hasAvatar() { + return displayAvatar() && + (widget.avatarBuilder != null || + widget.message.author.avatar != null || + widget.message.author.name.isNotEmpty); + } + + @override + Widget? buildAvatar(BuildContext context, {Widget? result}) { + if (!hasAvatar()) { + return null; + } + + Widget? result; + if (widget.avatarBuilder != null) { + result = widget.avatarBuilder!(context, widget.index, widget.message); + } else { + if (widget.isFirstInGroup) { + result = _buildDefaultAvatar(result); + } + } + + return super.buildAvatar(context, result: result); + } + + Widget? _buildDefaultAvatar(Widget? result) { + if (widget.message.author.avatar != null) { + result = CircleAvatar( + backgroundImage: widget.message.author.avatar, + backgroundColor: widget.avatarBackgroundColor, + ); + } else { + if (widget.message.author.name.isNotEmpty) { + result = CircleAvatar( + backgroundColor: widget.avatarBackgroundColor, + child: Text( + widget.message.author.name[0], + style: widget.contentTextStyle, + ), + ); + } + } + return result; + } + + @override + Widget buildText() { + return Text( + widget.message.text, + style: widget.contentTextStyle, + textDirection: TextDirection.ltr, + ); + } + + @override + Widget? buildSuggestion(BuildContext context) { + if (widget.message.suggestions == null || + widget.message.suggestions!.isEmpty) { + return null; + } + + final List suggestions = widget.message.suggestions!; + final SuggestionSettings settings = + widget.message.suggestionSettings ?? const ChatSuggestionSettings(); + Widget result = _ChatSuggestionArea( + message: widget.message, + messageIndex: widget.index, + suggestions: suggestions, + selectionType: settings.selectionType, + shape: settings.shape ?? widget.suggestionBackgroundShape, + itemShape: settings.itemShape ?? widget.suggestionItemShape, + itemTextStyle: widget.suggestionItemTextStyle, + orientation: settings.orientation, + itemOverflow: settings.itemOverflow, + spacing: settings.spacing, + runSpacing: settings.runSpacing, + backgroundColor: + settings.backgroundColor ?? widget.suggestionBackgroundColor, + itemBackgroundColor: + settings.itemBackgroundColor ?? widget.suggestionItemBackgroundColor, + padding: settings.margin.resolve(widget.alignmentDirection), + itemPadding: settings.itemPadding.resolve(widget.alignmentDirection), + onSuggestionItemSelected: widget.onSuggestionItemSelected, + ); + result = ClipRect( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: availableContentWidth() * widget.widthFactor, + ), + child: result, + ), + ); + return buildWithAvatarGap(result); + } +} + +class _ChatSuggestionArea extends SuggestionArea { + const _ChatSuggestionArea({ + required super.message, + required super.messageIndex, + required super.suggestions, + required super.selectionType, + super.shape, + super.itemShape, + required super.itemTextStyle, + required super.orientation, + required super.itemOverflow, + required super.spacing, + required super.runSpacing, + super.backgroundColor, + super.itemBackgroundColor, + required super.padding, + required super.itemPadding, + this.onSuggestionItemSelected, + }); + + final ChatSuggestionItemSelectedCallback? onSuggestionItemSelected; + + @override + State createState() => _ChatSuggestionAreaState(); +} + +class _ChatSuggestionAreaState extends SuggestionAreaState { + @override + _ChatSuggestionArea get widget => super.widget as _ChatSuggestionArea; + + @override + Widget buildSuggestionItem(int index, bool enabled, bool selected) { + return _ChatSuggestionItem( + index: index, + message: widget.message, + messageIndex: widget.messageIndex, + enabled: enabled, + selected: selected, + details: widget.message.suggestions![index], + itemBackgroundColor: widget.itemBackgroundColor, + itemShape: widget.itemShape, + textStyle: widget.itemTextStyle, + itemPadding: widget.itemPadding, + selectionType: widget.selectionType, + selectedIndices: selectedIndices, + onSuggestionItemSelected: widget.onSuggestionItemSelected, + ); + } +} + +class _ChatSuggestionItem extends SuggestionItem { + const _ChatSuggestionItem({ + required super.index, + required super.message, + required super.messageIndex, + required super.enabled, + required super.selected, + required super.details, + required super.itemBackgroundColor, + required super.itemShape, + required super.textStyle, + required super.itemPadding, + required super.selectionType, + required super.selectedIndices, + this.onSuggestionItemSelected, + }); + + final ChatSuggestionItemSelectedCallback? onSuggestionItemSelected; + + @override + State createState() => _ChatSuggestionItemState(); +} + +class _ChatSuggestionItemState extends SuggestionItemState { + @override + _ChatSuggestionItem get widget => super.widget as _ChatSuggestionItem; + + @override + void invokeSelectedCallback(int suggestionIndex, {required bool selected}) { + final ChatMessageSuggestion suggestion = + widget.message.suggestions![suggestionIndex]; + widget.onSuggestionItemSelected?.call( + selected, + widget.messageIndex, + suggestion, + suggestionIndex, + ); + } +} diff --git a/packages/syncfusion_flutter_chat/lib/src/chat/settings.dart b/packages/syncfusion_flutter_chat/lib/src/chat/settings.dart new file mode 100644 index 000000000..f8289d413 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/chat/settings.dart @@ -0,0 +1,1783 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../settings.dart'; + +/// The [ChatWidgetBuilder] typedef defines a function signature for creating +/// custom chat message widgets. It takes a [BuildContext], an [index] for +/// the message position, and a [ChatMessage] object as parameters, and +/// returns a [Widget] to customize how chat messages are displayed. +typedef ChatWidgetBuilder = BaseWidgetBuilder; + +/// The [ChatSuggestionItemSelectedCallback] typedef represents a callback +/// function that is called when a chat suggestion item is selected. +/// It takes a [selected] flag, the [messageIndex] for the message, +/// the [suggestion] being interacted with, and the [suggestionIndex] +/// indicating the position of the suggestion. +typedef ChatSuggestionItemSelectedCallback = + void Function( + bool selected, + int messageIndex, + ChatMessageSuggestion suggestion, + int suggestionIndex, + ); + +/// Mode to handle the chat Suggestion items overflow. +enum ChatSuggestionOverflow { + /// - `ChatSuggestionOverflow.scroll`, will wrap and place the remaining + /// suggestion item to next line. + scroll, + + /// - `ChatSuggestionOverflow.wrap`, will place all the suggestion + /// items in single line and enables scrolling. + wrap, +} + +/// Represents a selection mode of the suggestion, it will be +/// either single or multiple. +enum ChatSuggestionSelectionType { + /// - `ChatSuggestionSelectionType.single`, allows to select only + /// one suggestion. + single, + + /// - `ChatSuggestionSelectionType.multiple`, allows to select + /// multiple suggestions. + multiple, +} + +/// Represents a chat message. +/// +/// The [ChatMessage] class stores the details of a message within a chat +/// system, including the text content, the time when the message was sent, +/// the author who sent the message, list of suggestion items for a message, +/// and the suggestion settings to customize the suggestion items. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// messages: [ +/// ChatMessage( +/// text: +/// 'Hi, I have planned to go on a trip. Can you suggest some places?', +/// time: DateTime.now(), +/// author: const ChatAuthor(id: '123-001', name: 'Chat A'), +/// suggestions: [ +/// ChatMessageSuggestion( +/// data: 'Paris', +/// ), +/// ChatMessageSuggestion( +/// data: 'Tokyo', +/// ), +/// ChatMessageSuggestion( +/// data: 'New York', +/// ), +/// ChatMessageSuggestion( +/// data: 'London', +/// ), +/// ], +/// suggestionSettings: ChatSuggestionSettings( +/// backgroundColor: Colors.grey[300], +/// itemBackgroundColor: WidgetStateProperty.resolveWith( +/// (states) { +/// if (states.contains(WidgetState.hovered)) { +/// return Colors.grey[400]!; +/// } +/// return Colors.grey; +/// }, +/// ), +/// shape: const RoundedRectangleBorder( +/// borderRadius: BorderRadius.all( +/// Radius.circular(4.0), +/// ), +/// ), +/// itemShape: WidgetStateProperty.resolveWith( +/// (Set states) { +/// if (states.contains(WidgetState.hovered)) { +/// return RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(20), +/// ); +/// } +/// return RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(5), +/// ); +/// }, +/// ), +/// textStyle: WidgetStateProperty.resolveWith( +/// (Set states) { +/// if (states.contains(WidgetState.disabled)) { +/// return const TextStyle(fontSize: 16); +/// } +/// return const TextStyle(fontSize: 14); +/// }, +/// ), +/// margin: const EdgeInsets.all(10), +/// itemPadding: +/// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), +/// orientation: Axis.horizontal, +/// itemOverflow: ChatSuggestionOverflow.scroll, +/// ), +/// ), +/// ], +/// ); +/// } +/// ``` +class ChatMessage extends Message { + /// Creates a [ChatMessage] instance with the given [text], [time], + /// [author] and optional [suggestions] and [suggestionSettings]. + const ChatMessage({ + required this.text, + required this.time, + required this.author, + this.suggestions, + this.suggestionSettings, + }); + + /// Content of the message. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: [ + /// ChatMessage( + /// text: 'Hi', + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final String text; + + /// Timestamp when the message was sent. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: [ + /// ChatMessage( + /// time: DateTime.now(), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final DateTime time; + + /// Author of the message. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: [ + /// ChatMessage( + /// author: ChatAuthor(id: '123-001', name: 'Chat A'), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final ChatAuthor author; + + /// List of suggestion items for the message. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// text: + /// "Hi, I have planned to go on a trip. Can you suggest some places?", + /// suggestions: [ + /// ChatMessageSuggestion( + /// data: 'Paris', + /// ), + /// ChatMessageSuggestion( + /// data: 'New York', + /// ), + /// ChatMessageSuggestion( + /// data: 'Tokyo', + /// ), + /// ChatMessageSuggestion( + /// data: 'London', + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final List? suggestions; + + /// Settings for customizing the appearance and layout of + /// message suggestions in [ChatMessage]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// backgroundColor: Colors.grey[300], + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.grey[400]!; + /// } + /// return Colors.grey; + /// }, + /// ), + /// shape: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all( + /// Radius.circular(4.0), + /// ), + /// ), + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(5), + /// ); + /// }, + /// ), + /// textStyle: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.disabled)) { + /// return const TextStyle(fontSize: 16); + /// } + /// return const TextStyle(fontSize: 14); + /// }, + /// ), + /// margin: const EdgeInsets.all(10), + /// itemPadding: + /// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), + /// orientation: Axis.horizontal, + /// itemOverflow: ChatSuggestionOverflow.scroll, + /// runSpacing: 10.0, + /// spacing: 10.0, + /// ), + /// ), + /// ]; + /// ``` + @override + final ChatSuggestionSettings? suggestionSettings; +} + +/// Represents the author of a chat message. +/// +/// The [ChatAuthor] class contains details about the person who sent the +/// message, such as their unique ID, name, and optional avatar image. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// messages: [ +/// ChatMessage( +/// author: ChatAuthor(id: '123-001', name: 'Chat A'), +/// ), +/// ], +/// ); +/// } +/// ``` +class ChatAuthor extends MessageAuthor { + /// Creates a new [ChatAuthor] with the specified [id], [name], and optional + /// [avatar]. + const ChatAuthor({required this.id, required this.name, this.avatar}); + + /// Unique identifier for the author which contains information about + /// user name and user ID. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: [ + /// ChatMessage( + /// author: ChatAuthor(id: '123-001'), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final String id; + + /// Name of the author. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: [ + /// ChatMessage( + /// author: ChatAuthor(name: 'Chat A'), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final String name; + + /// Optional avatar image for the author. This field holds an optional image + /// that represents the author. If no avatar is provided, the chat application + /// will display the initial letter of the author's name. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// messages: [ + /// ChatMessage( + /// author: ChatAuthor( + /// avatar: NetworkImage('https://example.com/outgoing-avatar.jpg') + /// ), + /// ), + /// ], + /// ); + /// } + /// ``` + @override + final ImageProvider? avatar; +} + +/// Represents a chat message suggestion. +/// +/// Example with default suggestion: +/// +/// ```dart +/// List _messages = [ +/// ChatMessage( +/// text: +/// "Hi, I have planned to go on a trip. Can you suggest some places?", +/// suggestions: [ +/// ChatMessageSuggestion( +/// data: 'Paris', +/// ), +/// ChatMessageSuggestion( +/// data: 'New York', +/// ), +/// ChatMessageSuggestion( +/// data: 'Tokyo', +/// ), +/// ChatMessageSuggestion( +/// data: 'London', +/// ), +/// ], +/// ), +/// ]; +/// ``` +/// +/// Example with builder suggestion: +/// +/// ```dart +/// List _messages = [ +/// ChatMessage( +/// suggestions: [ +/// ChatMessageSuggestion.builder( +/// builder: (context) { +/// return const DecoratedBox( +/// decoration: BoxDecoration( +/// color: Colors.green, +/// ), +/// child: Text('Paris'), +/// ); +/// }, +/// ), +/// ], +/// ), +/// ]; +/// ``` +class ChatMessageSuggestion extends MessageSuggestion { + /// Creates a [ChatMessageSuggestion] with the given [data] and + /// optional [selected]. + const ChatMessageSuggestion({required this.data, this.selected = false}) + : builder = null; + + const ChatMessageSuggestion.builder({ + required this.builder, + this.selected = false, + }) : data = ''; + + /// Text content for the suggestion. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestions: [ + /// const ChatMessageSuggestion( + /// data: 'Paris', + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final String? data; + + /// Indicates whether the suggestion is initially selected. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestions: [ + /// const ChatMessageSuggestion( + /// data: 'Paris', + /// selected: false, + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final bool selected; + + /// Optional builder function for creating a custom widget for the suggestion. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestions: [ + /// ChatMessageSuggestion.builder( + /// builder: (context) { + /// return Text('Paris'); + /// }, + /// ), + /// ], + /// ), + /// ]; + /// ``` + @override + final WidgetBuilder? builder; + + /// Creates a copy of this suggestion with the given fields replaced by the + /// new values. + ChatMessageSuggestion copyWith({ + String? data, + bool? selected, + WidgetBuilder? builder, + }) { + if (builder != null) { + return ChatMessageSuggestion.builder( + builder: builder, + selected: selected ?? this.selected, + ); + } else { + return ChatMessageSuggestion( + data: data ?? this.data, + selected: selected ?? this.selected, + ); + } + } +} + +/// Represents the settings for chat message suggestions. +/// +/// The [ChatSuggestionSettings] class allows you to customize the appearance +/// of the entire suggestion area as well as each individual suggestion +/// items in the suggestion list. +/// +/// For the suggestion area, you can set the background color, shape and add +/// padding to control the spacing around the suggestion list inside the +/// suggestion area. You can also set the orientation of the suggestion +/// area to be either vertical or horizontal. +/// +/// For each individual suggestion item, you can customize the shape, background +/// color, and item padding to add space around the each suggestion items. +/// you can also add the space between the each items using the run spacing +/// and spacing property. +/// +/// Example: +/// ```dart +/// List _messages = [ +/// ChatMessage( +/// suggestionSettings: ChatSuggestionSettings( +/// backgroundColor: Colors.grey[300], +/// itemBackgroundColor: WidgetStateProperty.resolveWith( +/// (states) { +/// if (states.contains(WidgetState.hovered)) { +/// return Colors.grey[400]!; +/// } +/// return Colors.grey; +/// }, +/// ), +/// shape: const RoundedRectangleBorder( +/// borderRadius: BorderRadius.all( +/// Radius.circular(4.0), +/// ), +/// ), +/// itemShape: WidgetStateProperty.resolveWith( +/// (Set states) { +/// if (states.contains(WidgetState.hovered)) { +/// return RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(20), +/// ); +/// } +/// return RoundedRectangleBorder( +/// borderRadius: BorderRadius.circular(5), +/// ); +/// }, +/// ), +/// textStyle: WidgetStateProperty.resolveWith( +/// (Set states) { +/// if (states.contains(WidgetState.disabled)) { +/// return const TextStyle(fontSize: 16); +/// } +/// return const TextStyle(fontSize: 14); +/// }, +/// ), +/// margin: const EdgeInsets.all(10), +/// itemPadding: +/// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), +/// orientation: Axis.horizontal, +/// itemOverflow: ChatSuggestionOverflow.scroll, +/// runSpacing: 10.0, +/// spacing: 10.0, +/// ), +/// ), +/// ]; +/// ``` +class ChatSuggestionSettings extends SuggestionSettings { + /// Creates a [ChatSuggestionSettings] with the given customization options. + const ChatSuggestionSettings({ + this.backgroundColor, + this.itemBackgroundColor, + this.shape, + this.itemShape, + this.textStyle, + this.margin = const EdgeInsetsDirectional.symmetric(vertical: 5.0), + this.itemPadding = const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, + ), + this.orientation = Axis.horizontal, + ChatSuggestionOverflow itemOverflow = ChatSuggestionOverflow.wrap, + ChatSuggestionSelectionType selectionType = + ChatSuggestionSelectionType.single, + this.runSpacing = 12.0, + this.spacing = 16.0, + }) : itemOverflow = + itemOverflow == ChatSuggestionOverflow.scroll + ? SuggestionOverflow.scroll + : SuggestionOverflow.wrap, + selectionType = + selectionType == ChatSuggestionSelectionType.multiple + ? SuggestionSelectionType.multiple + : SuggestionSelectionType.single; + + /// Background color of the suggestion area. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// backgroundColor: Colors.grey[300], + /// ) + /// ), + /// ]; + /// ``` + @override + final Color? backgroundColor; + + /// The [itemBackgroundColor] property sets the background color for the + /// individual suggestion items based on their state, such as hovered or + /// pressed. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// itemBackgroundColor: WidgetStateProperty.resolveWith( + /// (states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.grey[400]!; + /// } + /// return Colors.grey; + /// }, + /// ), + /// ) + /// ), + /// ]; + /// ``` + @override + final WidgetStateProperty? itemBackgroundColor; + + /// Used to customize the overall shape of the suggestion area. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// shape: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(4.0)), + /// ), + /// ) + /// ), + /// ]; + /// ``` + @override + final ShapeBorder? shape; + + /// Used to customize the shape of individual suggestion items based on their + /// state, such as hovered or pressed. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// itemShape: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(20), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(5), + /// ); + /// }), + /// ) + /// ), + /// ]; + /// ``` + @override + final WidgetStateProperty? itemShape; + + /// The [textStyle] used to sets the text style for the suggestion item based + /// on their state, such as hovered or pressed. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// textStyle: WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.disabled)) { + /// return const TextStyle(fontSize: 16); + /// } + /// return const TextStyle(fontSize: 14); + /// }, + /// ), + /// ), + /// ), + /// ]; + /// ``` + @override + final WidgetStateProperty? textStyle; + + /// Padding between the suggestion area and individual suggestion items. + /// + /// Defaults to `EdgeInsets.all(5.0)`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// margin: const EdgeInsets.only(top: 10, bottom: 10), + /// ) + /// ), + /// ]; + /// ``` + @override + final EdgeInsetsGeometry margin; + + /// Padding between the content of each individual suggestion item. + /// + /// Defaults to `EdgeInsets.all(5.0)`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// itemPadding: + /// const EdgeInsets.symmetric(horizontal: 12, vertical: 8.0), + /// ) + /// ), + /// ]; + /// ``` + @override + final EdgeInsetsGeometry itemPadding; + + /// The [orientation] is used to determine the rendering orientation of the + /// suggestion items and the direction in which the suggestions are scrolled. + /// + /// Defaults to [Axis.horizontal]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// orientation: Axis.vertical, + /// ) + /// ), + /// ]; + /// ``` + @override + final Axis orientation; + + /// The [runSpacing] determines a vertical spacing between runs of suggestion + /// items. + /// + /// Defaults to `8.0`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// runSpacing: 10.0, + /// ) + /// ), + /// ]; + /// ``` + @override + final double runSpacing; + + /// The [spacing] determines a horizontal spacing between individual + /// suggestion items. + /// + /// Defaults to `8.0`. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// spacing: 10.0, + /// ) + /// ), + /// ]; + /// ``` + @override + final double spacing; + + /// The [itemOverflow] property determines whether suggestion items will wrap + /// to fit within available space or scroll beyond it + /// + /// Defaults to [SuggestionOverflow.wrap]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// itemOverflow: SuggestionOverflow.wrap, + /// ), + /// ), + /// ]; + /// ``` + @override + final SuggestionOverflow itemOverflow; + + /// The [selectionType] property specifies how many suggestion items can be + /// selected at once. + /// + /// Defaults to [SuggestionSelectionType.single]. + /// + /// Example: + /// ```dart + /// List _messages = [ + /// ChatMessage( + /// suggestionSettings: ChatSuggestionSettings( + /// selectionType: SuggestionSelectionType.multiple, + /// ), + /// ), + /// ]; + /// ``` + @override + final SuggestionSelectionType selectionType; +} + +/// Customizes chat bubbles with these settings for a better user experience. +/// +/// The [ChatMessageSettings] class provides options to customize the appearance +/// and layout of chat bubbles, including styling, and padding, and display +/// options for elements such as the username, timestamp, and avatar. +/// +/// Example: +/// ```dart +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// incomingMessageSettings: ChatMessageSettings( +/// showAuthorName: true, +/// showTimestamp: true, +/// showAuthorAvatar: true, +/// widthFactor: 0.8, +/// avatarSize: const Size.square(32.0), +/// margin: const EdgeInsets.all(2.0), +/// padding: +/// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), +/// headerPadding: +/// const EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), +/// footerPadding: const EdgeInsetsDirectional.only(top: 4.0), +/// ), +/// outgoingMessageSettings: ChatMessageSettings( +/// showAuthorName: true, +/// showTimestamp: true, +/// showAuthorAvatar: true, +/// widthFactor: 0.8, +/// avatarSize: const Size.square(32.0), +/// margin: const EdgeInsets.all(2.0), +/// padding: +/// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), +/// headerPadding: +/// const EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), +/// footerPadding: const EdgeInsetsDirectional.only(top: 4.0), +/// ), +/// ); +/// } +/// ``` +class ChatMessageSettings extends MessageSettings { + /// Creates a [ChatMessageSettings] with the given customization options. + const ChatMessageSettings({ + this.showAuthorName = true, + this.showTimestamp = true, + this.showAuthorAvatar = true, + this.timestampFormat, + this.textStyle, + this.headerTextStyle, + this.backgroundColor, + this.shape, + this.widthFactor = 0.8, + this.avatarSize = const Size.square(32.0), + this.margin = const EdgeInsetsDirectional.only(top: 16.0), + this.padding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + this.avatarPadding, + this.headerPadding = const EdgeInsetsDirectional.only(bottom: 4.0), + this.footerPadding = const EdgeInsetsDirectional.only(top: 4.0), + }); + + /// Customizes whether to display the sender's username within the chat + /// bubble. + /// + /// Defaults to `true`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// showAuthorName: true, + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// showAuthorName: true, + /// ), + /// ); + /// } + /// ``` + @override + final bool showAuthorName; + + /// Customizes whether to display the timestamp within the chat bubble. + /// + /// Defaults to `true`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// showTimestamp: true, + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// showTimestamp: true, + /// ), + /// ); + /// } + /// ``` + @override + final bool showTimestamp; + + /// Customizes whether to display the sender's avatar within the chat bubble. + /// + /// Defaults to `true`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// showAuthorAvatar: true, + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// showAuthorAvatar: true, + /// ), + /// ); + /// } + /// ``` + @override + final bool showAuthorAvatar; + + /// Customizes the format for displaying the timestamp of the message. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// timestampFormat: DateFormat('hh:mm a'), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// timestampFormat: DateFormat('hh:mm a'), + /// ), + /// ); + /// } + /// ``` + @override + final DateFormat? timestampFormat; + + /// Style for the chat bubble's text. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// textStyle: TextStyle(fontSize: 14), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// textStyle: TextStyle(fontSize: 14), + /// ), + /// ); + /// } + /// + /// ``` + @override + final TextStyle? textStyle; + + /// Style for the header text in the chat bubble. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// headerTextStyle: TextStyle(fontWeight: FontWeight.bold), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// headerTextStyle: TextStyle(fontWeight: FontWeight.bold), + /// ), + /// ); + /// } + /// ``` + @override + final TextStyle? headerTextStyle; + + /// Background color of the message content. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// backgroundColor: Colors.grey[200], + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// backgroundColor: Colors.grey[400], + /// ), + /// ); + /// } + /// ``` + @override + final Color? backgroundColor; + + /// Shape of the message content, including border radius. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// ); + /// } + /// ``` + @override + final ShapeBorder? shape; + + /// Customizes the chat bubble's width as a fraction of the screen width. + /// + /// Defaults to `0.8`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// widthFactor: 0.8, + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// widthFactor: 0.8, + /// ), + /// ); + /// } + /// ``` + @override + final double widthFactor; + + /// Customizes the avatar's size within the chat bubble. + /// + /// Defaults to `Size.square(32.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// avatarSize: const Size.square(35.0), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// avatarSize: const Size.square(35.0), + /// ), + /// ); + /// } + /// ``` + @override + final Size avatarSize; + + /// Customizes the padding around the entire bubble. + /// + /// Defaults to `EdgeInsets.all(2.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// margin: const EdgeInsets.all(2.0), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// margin: const EdgeInsets.all(2.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry? margin; + + /// Customizes the padding around the bubble's content. + /// + /// Defaults to `EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// padding: + /// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// padding: + /// const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry? padding; + + /// Customizes the padding around the avatar within the bubble. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// avatarPadding: const EdgeInsets.all(4.0), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// avatarPadding: const EdgeInsets.all(4.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry? avatarPadding; + + /// Customizes padding to the header section of the bubble (if applicable). + /// + /// Defaults to `EdgeInsetsDirectional.only(top:14.0, bottom: 4.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// headerPadding: + /// const EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// headerPadding: + /// const EdgeInsetsDirectional.only(top: 14.0, bottom: 4.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry headerPadding; + + /// Customizes the padding around the footer section of the + /// bubble (if applicable). + /// + /// Defaults to `EdgeInsetsDirectional.only(top: 4.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// incomingMessageSettings: ChatMessageSettings( + /// footerPadding: const EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// outgoingMessageSettings: ChatMessageSettings( + /// footerPadding: const EdgeInsetsDirectional.only(top: 4.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry footerPadding; +} + +/// A widget for composing chat messages. +/// +/// The [ChatComposer] allows customization of text input areas, including text +/// style, line limits, and decoration. It can also be initialized with a custom +/// builder. +/// +/// Example with default composer: +/// ```dart +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// composer: ChatComposer( +/// textStyle: TextStyle(fontSize: 16.0, color: Colors.black), +/// minLines: 1, +/// maxLines: 6, +/// decoration: InputDecoration( +/// border: OutlineInputBorder( +/// borderRadius: BorderRadius.all(Radius.circular(42.0)))), +/// hintText: 'Type a message...', +/// ), +/// margin: const EdgeInsets.only(top: 16.0), +/// ) +/// ); +/// } +/// +/// Example with custom Composer: +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// composer: ChatComposer.builder( +/// builder: (context) => CustomChatInputWidget(), +/// margin: const EdgeInsets.only(top: 16.0), +/// ) +/// ); +/// } +/// ``` +class ChatComposer extends Composer { + /// Creates a [ChatComposer] with the given [textStyle], line limits, + /// [decoration], and [margin]. + const ChatComposer({ + this.textStyle, + this.minLines = 1, + this.maxLines = 6, + this.decoration = const InputDecoration(), + this.margin = const EdgeInsets.only(top: 16.0), + }) : builder = null; + + /// Creates a [ChatComposer] using a custom [builder] for the widget's + /// construction. + const ChatComposer.builder({ + required this.builder, + this.margin = const EdgeInsets.only(top: 16.0), + }) : textStyle = null, + decoration = null, + minLines = 0, + maxLines = 0; + + /// Maximum number of lines the text input field can contain. + /// + /// Defaults to `6`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer( + /// maxLines: 6, + /// ) + /// ); + /// } + /// ``` + @override + final int maxLines; + + /// Minimum number of lines the text input field can contain. + /// + /// Defaults to `1`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer( + /// minLines: 1, + /// ) + /// ); + /// } + /// ``` + @override + final int minLines; + + /// Customizes the style of the text input field. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer( + /// textStyle: TextStyle(fontSize: 16.0, color: Colors.black), + /// ) + /// ); + /// } + /// ``` + @override + final TextStyle? textStyle; + + /// Customizes the decoration of the text input field. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer( + /// decoration: InputDecoration( + /// border: OutlineInputBorder( + /// borderRadius: BorderRadius.all(Radius.circular(42.0)))), + /// hintText: 'Type a message...', + /// ), + /// ) + /// ); + /// } + /// ``` + @override + final InputDecoration? decoration; + + /// Customizes the margin around the text input field. + /// + /// Defaults to `EdgeInsets.only(top: 16.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer( + /// margin: const EdgeInsets.only(top: 16.0), + /// ) + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry margin; + + /// Custom builder for constructing the widget. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// composer: ChatComposer.builder( + /// builder: (context) => CustomChatInputWidget(), + /// margin: const EdgeInsets.only(top: 16.0), + /// ) + /// ); + /// } + /// ``` + @override + final WidgetBuilder? builder; +} + +/// A customizable action button for chat interactions. +/// +/// The [ChatActionButton] allows extensive customizations, including +/// appearance, size, and behavior. +/// +/// Example: +/// ```dart +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfChat( +/// actionButton: ChatActionButton( +/// onPressed: (String newMessage) { +/// setState(() { +/// _messages.add( +/// ChatMessage( +/// text: newMessage, +/// time: DateTime.now(), +/// author: const ChatAuthor( +/// id: '123-001', +/// name: 'Chat A', +/// ), +/// ), +/// ); +/// }); +/// }, +/// ), +/// ); +/// } +/// ``` +class ChatActionButton extends ActionButton { + /// Creates a [ChatActionButton] with the given parameters. + const ChatActionButton({ + this.child, + this.tooltip, + this.foregroundColor, + this.backgroundColor, + this.focusColor, + this.hoverColor, + this.splashColor, + this.elevation, + this.focusElevation, + this.hoverElevation, + this.highlightElevation, + this.mouseCursor, + this.shape, + this.margin = const EdgeInsetsDirectional.only(start: 8.0), + this.size = const Size.square(40.0), + required this.onPressed, + }); + + /// Widget displayed inside the button. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// child: null, + /// ), + /// ); + /// } + /// ``` + @override + final Widget? child; + + /// Text displayed when the button is hovered. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// tooltip: 'Send Message', + /// ), + /// ); + /// } + /// ``` + @override + final String? tooltip; + + /// Color of the button's content (icons or text). + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// foregroundColor: Colors.blue, + /// ), + /// ); + /// } + /// ``` + @override + final Color? foregroundColor; + + /// Background color of the button. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// backgroundColor: Colors.white, + /// ), + /// ); + /// } + /// ``` + @override + final Color? backgroundColor; + + /// Color of the button while it is focused. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// focusColor: Colors.green, + /// ), + /// ); + /// } + /// + /// ``` + @override + final Color? focusColor; + + /// Color of the button while the mouse is hovering over it. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// hoverColor: Colors.grey, + /// ), + /// ); + /// } + /// + /// ``` + @override + final Color? hoverColor; + + /// Color of the splash effect generated when the button is pressed. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// splashColor: Colors.red, + /// ), + /// ); + /// } + /// ``` + @override + final Color? splashColor; + + /// Depth of the button's shadow, which influences its appearance. + /// + /// Defaults to `0.0`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// elevation: 4.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? elevation; + + /// Depth of the button's shadow while focused. + /// + /// Defaults to `0.0`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// focusElevation: 8.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? focusElevation; + + /// Depth of the button's shadow when the mouse hovers over it. + /// + /// Defaults to `0.0`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// hoverElevation: 6.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? hoverElevation; + + /// Depth of the button's shadow when highlighted. + /// + /// Defaults to `0.0`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// highlightElevation: 5.0, + /// ), + /// ); + /// } + /// ``` + @override + final double? highlightElevation; + + /// Customizes the cursor that appears when you hover over the button. + /// + /// Defaults to `null` + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// mouseCursor: SystemMouseCursors.click, + /// ), + /// ); + /// } + /// ``` + @override + final MouseCursor? mouseCursor; + + /// Shape of the button border. + /// + /// Defaults to `null`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// shape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ), + /// ), + /// ); + /// } + /// ``` + @override + final ShapeBorder? shape; + + /// Customizes the padding around the action button. + /// + /// Defaults to `EdgeInsetsDirectional.only(start: 8.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// margin: EdgeInsets.all(12.0), + /// ), + /// ); + /// } + /// ``` + @override + final EdgeInsetsGeometry margin; + + /// Size of the button. + /// + /// Defaults to `Size.square(40.0)`. + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// size: Size(50.0, 50.0), + /// ), + /// ); + /// } + /// ``` + @override + final Size size; + + /// A callback function that is called whenever the button is pressed. This + /// parameter is a function that receives a `String` argument representing the + /// new message. It is Invoked when the button is pressed. + /// + /// Defaults to `null`, + /// + /// Example: + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfChat( + /// actionButton: ChatActionButton( + /// onPressed: (String newMessage) { + /// setState(() { + /// _messages.add( + /// ChatMessage( + /// text: newMessage, + /// time: DateTime.now(), + /// author: const ChatAuthor( + /// id: '123-001', + /// name: 'Chat A', + /// ), + /// ), + /// ); + /// }); + /// }, + /// ), + /// ); + /// } + /// ``` + @override + final ValueChanged? onPressed; +} diff --git a/packages/syncfusion_flutter_chat/lib/src/chat/theme.dart b/packages/syncfusion_flutter_chat/lib/src/chat/theme.dart new file mode 100644 index 000000000..386e718f7 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/chat/theme.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +class ChatM2ThemeData extends SfChatThemeData { + ChatM2ThemeData(this.context) + : super( + outgoingMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + incomingMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ); + + final BuildContext context; + late final ColorScheme _colorScheme = Theme.of(context).colorScheme; + + @override + double get actionButtonHighlightElevation => 6.0; + + @override + Color? get actionButtonForegroundColor => _colorScheme.onPrimary; + + @override + Color? get actionButtonBackgroundColor => _colorScheme.primary; + + @override + Color? get actionButtonFocusColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonHoverColor => + _colorScheme.primary.withValues(alpha: 0.91); + + @override + Color? get actionButtonSplashColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonDisabledForegroundColor => + _colorScheme.onSurface.withValues(alpha: 0.38); + + @override + Color? get actionButtonDisabledBackgroundColor => + _colorScheme.surface.withValues(alpha: 0.12); + + @override + ShapeBorder? get actionButtonShape => const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ); + + @override + Color? get outgoingMessageBackgroundColor => _colorScheme.primaryContainer; + + @override + Color? get incomingMessageBackgroundColor => _colorScheme.surfaceContainer; + + @override + Color? get outgoingAvatarBackgroundColor => _colorScheme.primaryContainer; + + @override + Color? get incomingAvatarBackgroundColor => _colorScheme.surfaceContainer; + + @override + WidgetStateProperty get suggestionItemBackgroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered) || + states.contains(WidgetState.focused)) { + return _colorScheme.surfaceContainer.withValues(alpha: 0.8); + } + if (states.contains(WidgetState.pressed) || + states.contains(WidgetState.disabled)) { + return _colorScheme.surfaceContainer.withValues(alpha: 0.12); + } + return _colorScheme.surfaceContainer; + }); + + @override + WidgetStateProperty? get suggestionItemShape => + WidgetStateProperty.resolveWith((Set states) { + return const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ); + }); + + @override + WidgetStateProperty? get suggestionItemTextStyle => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return TextStyle( + color: _colorScheme.onSurface.withValues(alpha: 0.38), + ); + } + return TextStyle(color: _colorScheme.onSurface); + }); +} + +class ChatM3ThemeData extends SfChatThemeData { + ChatM3ThemeData(this.context) + : super( + outgoingMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + incomingMessageShape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + ); + + final BuildContext context; + late final ColorScheme _colorScheme = Theme.of(context).colorScheme; + + @override + double get actionButtonHighlightElevation => 6.0; + + @override + Color? get actionButtonForegroundColor => _colorScheme.onPrimary; + + @override + Color? get actionButtonBackgroundColor => _colorScheme.primary; + + @override + Color? get actionButtonFocusColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonHoverColor => + _colorScheme.primary.withValues(alpha: 0.91); + + @override + Color? get actionButtonSplashColor => + _colorScheme.primary.withValues(alpha: 0.86); + + @override + Color? get actionButtonDisabledForegroundColor => + _colorScheme.onSurface.withValues(alpha: 0.38); + + @override + Color? get actionButtonDisabledBackgroundColor => + _colorScheme.surface.withValues(alpha: 0.12); + + @override + ShapeBorder? get actionButtonShape => const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ); + + @override + Color? get outgoingMessageBackgroundColor => _colorScheme.primaryContainer; + + @override + Color? get incomingMessageBackgroundColor => _colorScheme.surfaceContainer; + + @override + Color? get outgoingAvatarBackgroundColor => _colorScheme.primaryContainer; + + @override + Color? get incomingAvatarBackgroundColor => _colorScheme.surfaceContainer; + + @override + WidgetStateProperty get suggestionItemBackgroundColor => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.hovered) || + states.contains(WidgetState.focused)) { + return _colorScheme.surfaceContainer.withValues(alpha: 0.8); + } + if (states.contains(WidgetState.pressed) || + states.contains(WidgetState.disabled)) { + return _colorScheme.surfaceContainer.withValues(alpha: 0.12); + } + return _colorScheme.surfaceContainer; + }); + + @override + WidgetStateProperty? get suggestionItemShape => + WidgetStateProperty.resolveWith((Set states) { + return const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ); + }); + + @override + WidgetStateProperty? get suggestionItemTextStyle => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return TextStyle( + color: _colorScheme.onSurface.withValues(alpha: 0.38), + ); + } + return TextStyle(color: _colorScheme.onSurface); + }); +} diff --git a/packages/syncfusion_flutter_chat/lib/src/composer_area.dart b/packages/syncfusion_flutter_chat/lib/src/composer_area.dart new file mode 100644 index 000000000..33b26e227 --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/composer_area.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; + +import 'settings.dart'; + +class TextEditor extends StatelessWidget { + const TextEditor({ + super.key, + required this.composer, + this.decoration, + this.actionButton, + required this.focusNode, + required this.textController, + required this.textStyle, + }); + + final Composer composer; + final InputDecoration? decoration; + final ActionButton? actionButton; + final FocusNode focusNode; + final TextEditingController textController; + final TextStyle? textStyle; + + @override + Widget build(BuildContext context) { + Widget result; + if (composer.builder != null) { + result = composer.builder!(context); + } else { + result = TextField( + focusNode: focusNode, + controller: textController, + style: textStyle, + minLines: composer.minLines, + maxLines: composer.maxLines, + decoration: decoration, + ); + } + + if (composer.margin != EdgeInsets.zero) { + result = Padding(padding: composer.margin, child: result); + } + + return result; + } +} + +class ActionButtonWidget extends StatefulWidget { + const ActionButtonWidget({ + super.key, + this.enabled = true, + required this.settings, + this.composer, + required this.textController, + required this.actionButtonForegroundColor, + required this.actionButtonDisabledForegroundColor, + required this.actionButtonBackgroundColor, + required this.actionButtonDisabledBackgroundColor, + required this.actionButtonFocusColor, + required this.actionButtonHoverColor, + required this.actionButtonSplashColor, + required this.actionButtonElevation, + required this.actionButtonFocusElevation, + required this.actionButtonHoverElevation, + required this.actionButtonHighlightElevation, + required this.actionButtonDisabledElevation, + required this.actionButtonShape, + }); + + final bool enabled; + final ActionButton settings; + final Composer? composer; + final TextEditingController textController; + final Color? actionButtonForegroundColor; + final Color? actionButtonDisabledForegroundColor; + final Color? actionButtonBackgroundColor; + final Color? actionButtonDisabledBackgroundColor; + final Color? actionButtonFocusColor; + final Color? actionButtonHoverColor; + final Color? actionButtonSplashColor; + final double actionButtonElevation; + final double actionButtonFocusElevation; + final double actionButtonHoverElevation; + final double actionButtonHighlightElevation; + final double actionButtonDisabledElevation; + final ShapeBorder? actionButtonShape; + + @override + State createState() => _ActionButtonWidgetState(); +} + +class _ActionButtonWidgetState extends State { + static const double _defaultActionButtonIconSize = 24.0; + String _previousText = ''; + bool _editorIsEmpty = true; + + void _handleOnPressed() { + widget.settings.onPressed?.call(widget.textController.text); + widget.textController.clear(); + } + + Widget _buildChild(bool enabled) { + if (widget.settings.child != null) { + return widget.settings.child!; + } else { + return Icon( + Icons.send, + size: _defaultActionButtonIconSize, + color: + enabled + ? widget.actionButtonForegroundColor + : widget.actionButtonDisabledForegroundColor, + ); + } + } + + void _handleTextChange() { + final String currentText = widget.textController.text; + if (_previousText.isEmpty && currentText.isNotEmpty) { + _previousText = currentText; + _editorIsEmpty = false; + _relayout(); + } else if (_previousText.isNotEmpty && currentText.isEmpty) { + _previousText = ''; + _editorIsEmpty = true; + _relayout(); + } + _previousText = currentText; + } + + void _relayout() { + setState(() {}); + } + + @override + void initState() { + widget.textController.addListener(_handleTextChange); + _editorIsEmpty = + widget.composer != null && + widget.composer!.builder == null && + widget.textController.text.isEmpty; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final bool enabled = + widget.enabled && !_editorIsEmpty && widget.settings.onPressed != null; + Widget result = RawMaterialButton( + onPressed: enabled ? _handleOnPressed : null, + mouseCursor: _ChatEffectiveMouseCursor(widget.settings.mouseCursor), + constraints: BoxConstraints( + maxWidth: widget.settings.size.width, + maxHeight: widget.settings.size.height, + minWidth: widget.settings.size.width, + minHeight: widget.settings.size.height, + ), + elevation: widget.actionButtonElevation, + focusElevation: widget.actionButtonFocusElevation, + hoverElevation: widget.actionButtonHoverElevation, + highlightElevation: widget.actionButtonHighlightElevation, + disabledElevation: widget.actionButtonDisabledElevation, + fillColor: + enabled + ? widget.actionButtonBackgroundColor + : widget.actionButtonDisabledBackgroundColor, + focusColor: widget.actionButtonFocusColor, + hoverColor: widget.actionButtonHoverColor, + splashColor: widget.actionButtonSplashColor, + shape: widget.actionButtonShape!, + child: _buildChild(enabled), + ); + + if (widget.settings.margin != EdgeInsets.zero) { + result = Padding(padding: widget.settings.margin, child: result); + } + + if (widget.settings.tooltip != null) { + result = Tooltip( + preferBelow: false, + message: widget.settings.tooltip, + child: result, + ); + } + + return MergeSemantics(child: result); + } + + @override + void dispose() { + widget.textController.removeListener(_handleTextChange); + super.dispose(); + } +} + +class _ChatEffectiveMouseCursor extends WidgetStateMouseCursor { + const _ChatEffectiveMouseCursor(this.cursor); + + final MouseCursor? cursor; + + @override + MouseCursor resolve(Set states) { + return WidgetStateProperty.resolveAs(cursor, states) ?? + WidgetStateMouseCursor.clickable.resolve(states); + } + + @override + String get debugDescription => cursor.toString(); +} diff --git a/packages/syncfusion_flutter_chat/lib/src/conversion_area.dart b/packages/syncfusion_flutter_chat/lib/src/conversion_area.dart new file mode 100644 index 000000000..fc3f6374f --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/conversion_area.dart @@ -0,0 +1,1152 @@ +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' hide TextDirection; + +import 'settings.dart'; + +abstract class ConversationArea extends StatefulWidget { + const ConversationArea({ + super.key, + required this.messages, + required this.outgoingBubbleSettings, + required this.incomingBubbleSettings, + this.bubbleAlignment = BubbleAlignment.auto, + this.placeholderBehavior = PlaceholderBehavior.hideOnMessage, + this.placeholderBuilder, + this.bubbleHeaderBuilder, + this.bubbleAvatarBuilder, + this.bubbleContentBuilder, + this.bubbleFooterBuilder, + this.outgoingAvatarBackgroundColor, + this.incomingAvatarBackgroundColor, + this.outgoingBubbleContentBackgroundColor, + this.incomingBubbleContentBackgroundColor, + this.outgoingPrimaryHeaderTextStyle, + this.incomingPrimaryHeaderTextStyle, + this.outgoingSecondaryHeaderTextStyle, + this.incomingSecondaryHeaderTextStyle, + this.outgoingContentTextStyle, + this.incomingContentTextStyle, + required this.suggestionItemTextStyle, + this.outgoingBubbleContentShape, + this.incomingBubbleContentShape, + this.suggestionBackgroundColor, + this.suggestionBackgroundShape, + this.suggestionItemBackgroundColor, + this.suggestionItemShape, + required this.themeData, + }); + + final List messages; + final MessageSettings outgoingBubbleSettings; + final MessageSettings incomingBubbleSettings; + final BubbleAlignment bubbleAlignment; + final PlaceholderBehavior placeholderBehavior; + final WidgetBuilder? placeholderBuilder; + final BaseWidgetBuilder? bubbleHeaderBuilder; + final BaseWidgetBuilder? bubbleAvatarBuilder; + final BaseWidgetBuilder? bubbleContentBuilder; + final BaseWidgetBuilder? bubbleFooterBuilder; + final Color? outgoingAvatarBackgroundColor; + final Color? incomingAvatarBackgroundColor; + final Color? outgoingBubbleContentBackgroundColor; + final Color? incomingBubbleContentBackgroundColor; + final TextStyle? outgoingPrimaryHeaderTextStyle; + final TextStyle? incomingPrimaryHeaderTextStyle; + final TextStyle? outgoingSecondaryHeaderTextStyle; + final TextStyle? incomingSecondaryHeaderTextStyle; + final TextStyle? outgoingContentTextStyle; + final TextStyle? incomingContentTextStyle; + final TextStyle Function(Set states) suggestionItemTextStyle; + final ShapeBorder? outgoingBubbleContentShape; + final ShapeBorder? incomingBubbleContentShape; + final Color? suggestionBackgroundColor; + final ShapeBorder? suggestionBackgroundShape; + final WidgetStateProperty? suggestionItemBackgroundColor; + final WidgetStateProperty? suggestionItemShape; + final ThemeData themeData; +} + +abstract class ConversationAreaState extends State> { + late int messageCount; + TextDirection textDirection = TextDirection.ltr; + + Widget buildListView(double width, double height) { + return ListView.builder( + itemCount: messageCount, + reverse: true, + itemBuilder: (BuildContext context, int index) { + index = messageCount - index - 1; + return buildMessageBubble(index, width, height); + }, + findChildIndexCallback: (Key key) { + final IndexedValueKey? valueKey = key as IndexedValueKey?; + if (valueKey != null) { + return messageCount - valueKey.value - 1; + } + return null; + }, + ); + } + + Widget buildMessageBubble(int index, double width, double height); + + bool isFirstInGroup(int index, T message); + + bool isOutgoingMessage(T message); + + BubbleAlignment effectiveBubbleAlignment(bool isOutgoingMessage) { + final bool isRTL = textDirection == TextDirection.rtl; + switch (widget.bubbleAlignment) { + case BubbleAlignment.start: + return isRTL ? BubbleAlignment.end : BubbleAlignment.start; + case BubbleAlignment.end: + return isRTL ? BubbleAlignment.start : BubbleAlignment.end; + case BubbleAlignment.auto: + if (isOutgoingMessage) { + return isRTL ? BubbleAlignment.start : BubbleAlignment.end; + } else { + return isRTL ? BubbleAlignment.end : BubbleAlignment.start; + } + } + } + + TextDirection alignmentBasedTextDirection( + bool isOutgoing, + TextDirection textDirection, + ) { + switch (textDirection) { + case TextDirection.ltr: + if (isOutgoing) { + switch (widget.bubbleAlignment) { + case BubbleAlignment.start: + return TextDirection.rtl; + + case BubbleAlignment.auto: + case BubbleAlignment.end: + return TextDirection.ltr; + } + } else { + switch (widget.bubbleAlignment) { + case BubbleAlignment.end: + return TextDirection.rtl; + + case BubbleAlignment.auto: + case BubbleAlignment.start: + return TextDirection.ltr; + } + } + + case TextDirection.rtl: + if (isOutgoing) { + switch (widget.bubbleAlignment) { + case BubbleAlignment.start: + return TextDirection.ltr; + + case BubbleAlignment.auto: + case BubbleAlignment.end: + return TextDirection.rtl; + } + } else { + switch (widget.bubbleAlignment) { + case BubbleAlignment.end: + return TextDirection.ltr; + + case BubbleAlignment.auto: + case BubbleAlignment.start: + return TextDirection.rtl; + } + } + } + } + + EdgeInsetsGeometry effectiveAvatarPadding( + bool isOutgoingMessage, + EdgeInsetsGeometry? avatarPadding, + ); + + @override + void initState() { + messageCount = widget.messages.length; + super.initState(); + } + + @override + void didUpdateWidget(ConversationArea oldWidget) { + messageCount = widget.messages.length; + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + textDirection = Directionality.of(context); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final double width = constraints.maxWidth; + final double height = constraints.maxHeight; + if (messageCount == 0 && widget.placeholderBuilder != null) { + return ListView( + children: [ + ConstrainedBox( + constraints: constraints, + child: widget.placeholderBuilder!(context), + ), + ], + ); + } else { + return buildListView(width, height); + } + }, + ); + } +} + +class IndexedValueKey extends ValueKey { + const IndexedValueKey(int value) : super(value); +} + +abstract class MessageBubble extends StatefulWidget { + const MessageBubble({ + super.key, + required this.index, + required this.maxWidth, + this.widthFactor = 0.8, + required this.message, + required this.isOutgoing, + required this.isFirstInGroup, + this.headerBuilder, + this.avatarBuilder, + this.contentBuilder, + this.footerBuilder, + this.showUserAvatar, + required this.showUserName, + required this.showTimestamp, + this.timestampFormat, + this.alignment = BubbleAlignment.auto, + this.contentShape, + this.avatarBackgroundColor, + this.contentBackgroundColor, + this.contentTextStyle, + this.primaryHeaderTextStyle, + this.secondaryHeaderTextStyle, + this.suggestionItemTextStyle, + this.padding = const EdgeInsets.all(2.0), + this.contentPadding = const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + required this.avatarPadding, + this.headerPadding = const EdgeInsetsDirectional.only( + top: 14.0, + bottom: 4.0, + ), + this.footerPadding = const EdgeInsetsDirectional.only(top: 4.0), + this.avatarSize = const Size.square(32.0), + this.suggestionBackgroundColor, + this.suggestionBackgroundShape, + this.suggestionItemBackgroundColor, + this.suggestionItemShape, + required this.themeData, + required this.textDirection, + required this.alignmentDirection, + }); + + final int index; + final double maxWidth; + final T message; + final bool isOutgoing; + final bool isFirstInGroup; + final BaseWidgetBuilder? headerBuilder; + final BaseWidgetBuilder? avatarBuilder; + final BaseWidgetBuilder? contentBuilder; + final BaseWidgetBuilder? footerBuilder; + final BubbleAlignment alignment; + final bool showUserName; + final bool showTimestamp; + final bool? showUserAvatar; + final DateFormat? timestampFormat; + final double widthFactor; + final Size avatarSize; + final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry contentPadding; + final EdgeInsetsGeometry avatarPadding; + final EdgeInsetsGeometry headerPadding; + final EdgeInsetsGeometry footerPadding; + final ShapeBorder? contentShape; + final Color? avatarBackgroundColor; + final Color? contentBackgroundColor; + final TextStyle? contentTextStyle; + final TextStyle? primaryHeaderTextStyle; + final TextStyle? secondaryHeaderTextStyle; + final TextStyle Function(Set states)? suggestionItemTextStyle; + final Color? suggestionBackgroundColor; + final ShapeBorder? suggestionBackgroundShape; + final WidgetStateProperty? suggestionItemBackgroundColor; + final WidgetStateProperty? suggestionItemShape; + final ThemeData themeData; + final TextDirection textDirection; + final TextDirection alignmentDirection; +} + +abstract class MessageBubbleState extends State> { + static DateFormat defaultTimestampFormat = DateFormat('d/M/y : hh:mm a'); + + Widget buildMessage(BuildContext context) { + final Widget? header = buildHeader(context); + final Widget? avatar = buildAvatar(context); + final Widget? suggestion = buildSuggestion(context); + final Widget? footer = buildFooter(context); + return Column( + crossAxisAlignment: + widget.alignment == BubbleAlignment.end + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + textDirection: TextDirection.ltr, + children: [ + if (header != null) header, + BubbleLayout( + showUserAvatar: hasAvatar(), + avatarSize: widget.avatarSize, + avatarPadding: widget.avatarPadding.resolve( + widget.alignmentDirection, + ), + alignment: widget.alignment, + children: [if (avatar != null) avatar, buildContent(context)], + ), + if (suggestion != null) suggestion, + if (footer != null) footer, + ], + ); + } + + Widget? buildHeader(BuildContext context) { + Widget? result; + if (widget.headerBuilder != null) { + result = widget.headerBuilder!(context, widget.index, widget.message); + } else { + result = buildDefaultHeader(); + } + + if (result != null && widget.headerPadding != EdgeInsets.zero) { + result = Padding( + padding: widget.headerPadding.resolve(widget.alignmentDirection), + child: result, + ); + } + + return buildWithAvatarGap(result); + } + + Widget? buildDefaultHeader(); + + @mustCallSuper + Widget? buildAvatar(BuildContext context, {Widget? result}) { + if (result != null) { + result = OverflowBox( + minWidth: widget.avatarSize.width, + minHeight: widget.avatarSize.height, + maxWidth: widget.avatarSize.width, + maxHeight: widget.avatarSize.height, + child: result, + ); + + final EdgeInsets effectiveAvatarPadding = widget.avatarPadding.resolve( + widget.alignmentDirection, + ); + if (effectiveAvatarPadding != EdgeInsets.zero) { + result = Padding(padding: effectiveAvatarPadding, child: result); + } + } + + return result; + } + + bool hasAvatar(); + + Widget buildContent(BuildContext context) { + Widget result; + if (widget.contentBuilder != null) { + result = widget.contentBuilder!(context, widget.index, widget.message); + } else { + result = buildText(); + } + + if (widget.contentPadding != EdgeInsets.zero) { + result = Padding( + padding: widget.contentPadding.resolve(widget.alignmentDirection), + child: result, + ); + } + + result = ConstrainedBox( + constraints: BoxConstraints( + maxWidth: availableContentWidth() * widget.widthFactor, + ), + child: result, + ); + + if (widget.contentShape != null) { + result = BorderShape( + shape: widget.contentShape, + color: widget.contentBackgroundColor ?? Colors.transparent, + child: result, + ); + } + + return widget.isFirstInGroup ? result : buildWithAvatarGap(result)!; + } + + Widget buildText(); + + double availableContentWidth() { + final double sizeToBeExcluded = + hasAvatar() + ? widget.avatarSize.width + + widget.avatarPadding.horizontal + + widget.padding.horizontal + : widget.padding.horizontal; + final double availableWidth = widget.maxWidth - sizeToBeExcluded; + return clampDouble( + availableWidth * widget.widthFactor, + 0.0, + availableWidth, + ); + } + + Widget? buildSuggestion(BuildContext context); + + Widget? buildFooter(BuildContext context) { + Widget? result = widget.footerBuilder?.call( + context, + widget.index, + widget.message, + ); + if (result != null && widget.footerPadding != EdgeInsets.zero) { + result = Padding( + padding: widget.footerPadding.resolve(widget.alignmentDirection), + child: result, + ); + } + + return buildWithAvatarGap(result); + } + + bool displayAvatar(); + + Widget? buildWithAvatarGap(Widget? result) { + if (result != null) { + if (!hasAvatar()) { + return result; + } + + final double avartarSize = + widget.avatarSize.width + widget.avatarPadding.horizontal; + double startPadding = avartarSize; + double endPadding = 0; + + if ((widget.alignment == BubbleAlignment.end) ^ + (widget.textDirection == TextDirection.rtl)) { + startPadding = 0; + endPadding = avartarSize; + } + + result = Padding( + padding: EdgeInsetsDirectional.only( + start: startPadding, + end: endPadding, + ), + child: result, + ); + } + + return result; + } + + EdgeInsets effectiveMessagePadding() { + return widget.padding.resolve(widget.alignmentDirection); + } + + @override + @nonVirtual + Widget build(BuildContext context) { + Widget result = buildMessage(context); + final EdgeInsets effectivePadding = effectiveMessagePadding(); + if (effectivePadding != EdgeInsets.zero) { + result = Padding(padding: effectivePadding, child: result); + } + + return result; + } +} + +class _MessageParentData extends ContainerBoxParentData {} + +class BubbleLayout extends MultiChildRenderObjectWidget { + const BubbleLayout({ + super.key, + this.showUserAvatar = true, + this.avatarSize = const Size.square(32.0), + this.avatarPadding = EdgeInsets.zero, + this.alignment = BubbleAlignment.auto, + super.children, + }); + + final bool showUserAvatar; + final Size avatarSize; + final EdgeInsets avatarPadding; + final BubbleAlignment alignment; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderBubbleLayout( + showUserAvatar: showUserAvatar, + avatarSize: avatarSize, + avatarPadding: avatarPadding, + alignment: alignment, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderBubbleLayout renderObject, + ) { + renderObject + ..showUserAvatar = showUserAvatar + ..avatarSize = avatarSize + ..avatarPadding = avatarPadding + ..alignment = alignment; + } +} + +class RenderBubbleLayout extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + RenderBubbleLayout({ + required bool showUserAvatar, + required Size avatarSize, + required EdgeInsets avatarPadding, + required BubbleAlignment alignment, + }) : _showUserAvatar = showUserAvatar, + _avatarSize = avatarSize, + _avatarPadding = avatarPadding, + _alignment = alignment; + + BubbleAlignment get alignment => _alignment; + BubbleAlignment _alignment = BubbleAlignment.auto; + set alignment(BubbleAlignment value) { + if (_alignment != value) { + _alignment = value; + markNeedsLayout(); + } + } + + bool get showUserAvatar => _showUserAvatar; + bool _showUserAvatar = true; + set showUserAvatar(bool value) { + if (_showUserAvatar != value) { + _showUserAvatar = value; + markNeedsLayout(); + } + } + + Size get avatarSize => _avatarSize; + Size _avatarSize = const Size.square(32.0); + set avatarSize(Size value) { + if (_avatarSize != value) { + _avatarSize = value; + markNeedsLayout(); + } + } + + EdgeInsets get avatarPadding => _avatarPadding; + EdgeInsets _avatarPadding = EdgeInsets.zero; + set avatarPadding(EdgeInsets value) { + if (_avatarPadding != value) { + _avatarPadding = value; + markNeedsLayout(); + } + } + + @override + void setupParentData(RenderObject child) { + if (child.parentData is! _MessageParentData) { + child.parentData = _MessageParentData(); + } + super.setupParentData(child); + } + + @override + void performLayout() { + RenderBox? avatar; + RenderBox? message; + if (showUserAvatar && childCount > 1) { + avatar = firstChild; + message = childAfter(firstChild!); + } else { + message = firstChild; + } + + Size effectiveAvatarSize = Size.zero; + if (showUserAvatar && childCount > 1) { + final BoxConstraints avatarConstraints = BoxConstraints( + maxWidth: avatarSize.width + avatarPadding.horizontal, + maxHeight: avatarSize.height + avatarPadding.vertical, + ); + if (avatar != null) { + avatar.layout(avatarConstraints); + } + effectiveAvatarSize = avatarConstraints.biggest; + } + + Size messageSize = Size.zero; + if (message != null) { + final BoxConstraints messageConstraints = BoxConstraints( + maxWidth: max(0, constraints.maxWidth - effectiveAvatarSize.width), + maxHeight: constraints.maxHeight, + ); + message.layout(messageConstraints, parentUsesSize: true); + messageSize = message.size; + } + + if (alignment == BubbleAlignment.start) { + if (avatar != null) { + (avatar.parentData! as _MessageParentData).offset = Offset.zero; + } + if (message != null) { + final _MessageParentData messageParentData = + message.parentData! as _MessageParentData; + messageParentData.offset = Offset(effectiveAvatarSize.width, 0); + } + } else { + if (avatar != null) { + final _MessageParentData avatarParentData = + avatar.parentData! as _MessageParentData; + avatarParentData.offset = Offset(messageSize.width, 0); + } + if (message != null) { + (message.parentData! as _MessageParentData).offset = Offset.zero; + } + } + + size = Size( + effectiveAvatarSize.width + messageSize.width, + messageSize.height, + ); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + return defaultHitTestChildren(result, position: position); + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox? avatar; + RenderBox? message; + if (showUserAvatar && childCount > 1) { + avatar = firstChild; + message = childAfter(firstChild!); + } else { + message = firstChild; + } + + if (avatar != null) { + final _MessageParentData avatarParentData = + avatar.parentData! as _MessageParentData; + context.paintChild(avatar, avatarParentData.offset + offset); + } + + if (message != null) { + final _MessageParentData messageParentData = + message.parentData! as _MessageParentData; + context.paintChild(message, messageParentData.offset + offset); + } + } +} + +abstract class SuggestionArea extends StatefulWidget { + const SuggestionArea({ + super.key, + required this.message, + required this.messageIndex, + required this.suggestions, + required this.selectionType, + this.shape, + this.itemShape, + required this.itemTextStyle, + required this.orientation, + required this.itemOverflow, + required this.spacing, + required this.runSpacing, + this.backgroundColor, + this.itemBackgroundColor, + required this.padding, + required this.itemPadding, + }); + + final T message; + final int messageIndex; + final List suggestions; + final SuggestionSelectionType selectionType; + final ShapeBorder? shape; + final WidgetStateProperty? itemShape; + final TextStyle Function(Set states)? itemTextStyle; + final Axis orientation; + final SuggestionOverflow itemOverflow; + final double spacing; + final double runSpacing; + final Color? backgroundColor; + final WidgetStateProperty? itemBackgroundColor; + final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry itemPadding; +} + +abstract class SuggestionAreaState extends State> { + late List selectedIndices; + + Widget _buildSuggestionLayout(List items) { + Widget result = Wrap( + spacing: widget.spacing, + runSpacing: widget.runSpacing, + direction: widget.orientation, + children: items, + ); + + if (widget.itemOverflow == SuggestionOverflow.scroll) { + result = SingleChildScrollView( + scrollDirection: widget.orientation, + clipBehavior: Clip.none, + child: result, + ); + } + + if (widget.padding != EdgeInsets.zero) { + result = Padding(padding: widget.padding, child: result); + } + + if (widget.shape != null && + widget.backgroundColor != null && + widget.backgroundColor != Colors.transparent) { + result = BorderShape( + shape: widget.shape, + color: widget.backgroundColor, + child: result, + ); + } + + return result; + } + + Widget buildSuggestionItem(int index, bool enabled, bool selected); + + @override + void initState() { + final int suggestionCount = widget.suggestions.length; + final List tempSelectedIndices = []; + for (int i = 0; i < suggestionCount; i++) { + final MessageSuggestion suggestion = widget.suggestions[i]; + if (suggestion.selected) { + tempSelectedIndices.add(i); + } + } + + switch (widget.selectionType) { + case SuggestionSelectionType.single: + final int? lastIndex = tempSelectedIndices.lastOrNull; + if (lastIndex != null) { + tempSelectedIndices.retainWhere((int index) => index == lastIndex); + } + break; + case SuggestionSelectionType.multiple: + break; + } + selectedIndices = tempSelectedIndices; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final List suggestionItems = []; + final int suggestionCount = widget.suggestions.length; + for (int i = 0; i < suggestionCount; i++) { + bool enabled; + bool selected; + final MessageSuggestion suggestion = widget.suggestions[i]; + switch (widget.selectionType) { + case SuggestionSelectionType.single: + selected = suggestion.selected; + enabled = selectedIndices.isEmpty || selected; + break; + case SuggestionSelectionType.multiple: + enabled = true; + selected = false; + break; + } + suggestionItems.add(buildSuggestionItem(i, enabled, selected)); + } + return _buildSuggestionLayout(suggestionItems); + } + + @override + void dispose() { + selectedIndices.clear(); + super.dispose(); + } +} + +abstract class SuggestionItem extends StatefulWidget { + const SuggestionItem({ + super.key, + required this.index, + required this.message, + required this.messageIndex, + required this.enabled, + required this.selected, + required this.details, + required this.itemBackgroundColor, + required this.itemShape, + required this.textStyle, + required this.itemPadding, + required this.selectionType, + required this.selectedIndices, + }); + + final int index; + final T message; + final int messageIndex; + final bool enabled; + final bool selected; + final MessageSuggestion details; + final WidgetStateProperty? itemBackgroundColor; + final WidgetStateProperty? itemShape; + final TextStyle Function(Set states)? textStyle; + final EdgeInsetsGeometry itemPadding; + final SuggestionSelectionType selectionType; + final List selectedIndices; +} + +abstract class SuggestionItemState extends State> { + late final ValueNotifier> _stateChangeNotifier; + + Widget _buildItem() { + return Focus( + onFocusChange: (bool hasFocus) { + if (hasFocus) { + _addState(WidgetState.focused); + } else { + _removeState(WidgetState.focused); + } + }, + child: GestureDetector( + onTap: () { + _removeStates(); + _updateSelectionIfNeeded(widget.index); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (PointerEnterEvent event) { + if (!widget.selected) { + _addState(WidgetState.hovered); + } + }, + onExit: (PointerExitEvent event) { + if (_stateChangeNotifier.value.contains(WidgetState.hovered)) { + _removeStates(); + } + }, + child: _buildContent(context), + ), + ), + ); + } + + void _updateSelectionIfNeeded(int selectedIndex) { + switch (widget.selectionType) { + case SuggestionSelectionType.single: + _handleSingleSelection(selectedIndex); + break; + case SuggestionSelectionType.multiple: + _handleMultiselection(selectedIndex); + break; + } + } + + void _handleSingleSelection(int selectedIndex) { + final int selectedIndicesCount = widget.selectedIndices.length; + if (selectedIndicesCount > 0) { + if (selectedIndicesCount == 1 && + widget.selectedIndices.first == selectedIndex) { + return; + } + + for (final int pIndex in widget.selectedIndices) { + if (pIndex == selectedIndex) { + continue; + } + invokeSelectedCallback(pIndex, selected: false); + } + } + + widget.selectedIndices + ..clear() + ..add(selectedIndex); + invokeSelectedCallback(selectedIndex, selected: true); + } + + void _handleMultiselection(int selectedIndex) { + if (!widget.selectedIndices.contains(selectedIndex)) { + widget.selectedIndices.add(selectedIndex); + } + invokeSelectedCallback(selectedIndex, selected: true); + } + + void invokeSelectedCallback(int suggestionIndex, {required bool selected}); + + Widget _buildContent(BuildContext context) { + Widget? result; + if (widget.details.builder != null) { + result = widget.details.builder!(context); + } else { + result = Text( + widget.details.data ?? '', + style: widget.textStyle!(_stateChangeNotifier.value), + ); + } + + if (widget.itemPadding != EdgeInsets.zero) { + result = Padding(padding: widget.itemPadding, child: result); + } + + return result; + } + + void _addState(WidgetState state) { + _stateChangeNotifier.value.clear(); + _stateChangeNotifier.value = Set.from(_stateChangeNotifier.value) + ..add(state); + } + + void _removeStates() { + _stateChangeNotifier.value.clear(); + _stateChangeNotifier.value = Set.from(_stateChangeNotifier.value); + } + + void _removeState(WidgetState state) { + if (_stateChangeNotifier.value.contains(state)) { + _stateChangeNotifier.value = Set.from(_stateChangeNotifier.value) + ..remove(state); + } + } + + @override + void initState() { + if (widget.enabled) { + if (widget.selected) { + if (widget.selectionType == SuggestionSelectionType.single) { + _stateChangeNotifier = ValueNotifier>({ + WidgetState.selected, + }); + } else { + _stateChangeNotifier = ValueNotifier>({}); + } + } else { + _stateChangeNotifier = ValueNotifier>({}); + } + } else { + _stateChangeNotifier = ValueNotifier>({ + WidgetState.disabled, + }); + } + super.initState(); + } + + @override + void didUpdateWidget(SuggestionItem oldWidget) { + _removeStates(); + if (widget.enabled) { + if (widget.selected && + widget.selectionType == SuggestionSelectionType.single) { + _addState(WidgetState.selected); + } + } else { + _addState(WidgetState.disabled); + } + super.didUpdateWidget(oldWidget); + } + + List _itemShadows(Set states) { + if (states.contains(WidgetState.disabled)) { + return []; + } + + const Color startColor = Color(0x26000000); + const BoxShadow endShadow = BoxShadow( + color: Color(0x4D000000), + offset: Offset(0, 1), + blurRadius: 2.0, + ); + if (states.contains(WidgetState.focused)) { + return [ + const BoxShadow( + color: startColor, + offset: Offset(0, 2), + blurRadius: 6.0, + spreadRadius: 2.0, + ), + endShadow, + ]; + } + + return [ + const BoxShadow( + color: startColor, + offset: Offset(0, 1), + blurRadius: 3.0, + spreadRadius: 1.0, + ), + endShadow, + ]; + } + + @override + Widget build(BuildContext context) { + return AbsorbPointer( + absorbing: !widget.enabled || widget.selected, + child: ValueListenableBuilder>( + valueListenable: _stateChangeNotifier, + builder: ( + BuildContext context, + Set states, + Widget? child, + ) { + return BorderShape( + shape: widget.itemShape?.resolve(states), + color: widget.itemBackgroundColor?.resolve(states), + states: states, + shadowDetails: _itemShadows, + child: child, + ); + }, + child: _buildItem(), + ), + ); + } + + @override + void dispose() { + _stateChangeNotifier.dispose(); + super.dispose(); + } +} + +class BorderShape extends SingleChildRenderObjectWidget { + const BorderShape({ + super.key, + required this.shape, + required this.color, + this.states, + this.shadowDetails, + required super.child, + }); + + final ShapeBorder? shape; + final Color? color; + final Set? states; + final List Function(Set)? shadowDetails; + + @override + RenderBox createRenderObject(BuildContext context) { + return RenderBorderShapeBox( + shape: shape, + color: color, + states: states, + shadowDetails: shadowDetails, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderBorderShapeBox renderObject, + ) { + renderObject + ..shape = shape + ..color = color + ..states = states + ..shadowDetails = shadowDetails; + } +} + +class RenderBorderShapeBox extends RenderProxyBox { + RenderBorderShapeBox({ + ShapeBorder? shape, + Color? color, + this.states, + this.shadowDetails, + }) : _shape = shape, + _color = color; + + Set? states; + List Function(Set)? shadowDetails; + + ShapeBorder? get shape => _shape; + ShapeBorder? _shape; + set shape(ShapeBorder? value) { + if (_shape == value) { + return; + } + _shape = value; + markNeedsPaint(); + } + + Color? get color => _color; + Color? _color; + set color(Color? value) { + if (_color == value) { + return; + } + _color = value; + markNeedsPaint(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child == null) { + return; + } + + final Rect bounds = offset & size; + if (shape != null && color != null && color != Colors.transparent) { + if (shadowDetails != null) { + final List shadows = shadowDetails!(states!); + if (shadows.isNotEmpty) { + for (final BoxShadow shadow in shadows) { + context.canvas.drawShadow( + shape!.getOuterPath(bounds.shift(shadow.offset)), + shadow.color, + shadow.blurRadius, + true, + ); + } + } + } + + context.canvas.drawPath( + shape!.getOuterPath(bounds), + Paint()..color = color!, + ); + } + + context.paintChild(child!, offset); + if (shape != null) { + shape!.paint(context.canvas, bounds); + } + } +} diff --git a/packages/syncfusion_flutter_chat/lib/src/settings.dart b/packages/syncfusion_flutter_chat/lib/src/settings.dart new file mode 100644 index 000000000..1408b23da --- /dev/null +++ b/packages/syncfusion_flutter_chat/lib/src/settings.dart @@ -0,0 +1,228 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +typedef BaseWidgetBuilder = + Widget Function(BuildContext context, int index, T message); + +enum PlaceholderBehavior { hideOnMessage, scrollWithMessage } + +enum BubbleAlignment { start, end, auto } + +enum SuggestionOverflow { scroll, wrap } + +enum SuggestionSelectionType { single, multiple } + +/// Represents a message. +abstract class Message { + const Message(); + + /// Content of the message. + abstract final String text; + + /// Timestamp when the message was sent. + abstract final DateTime? time; + + /// Author of the message. + abstract final MessageAuthor? author; + + /// List of suggestion items for the message. + abstract final List? suggestions; + + /// Settings for customizing the appearance and layout of message suggestions. + abstract final SuggestionSettings? suggestionSettings; +} + +/// Represents a author of the message. +abstract class MessageAuthor { + const MessageAuthor(); + + /// Unique identifier of the author. + abstract final String? id; + + /// Name of the author. + abstract final String name; + + /// Avatar of the author. + abstract final ImageProvider? avatar; +} + +/// Represents a suggestion related to a message. +abstract class MessageSuggestion { + const MessageSuggestion(); + + /// Text content for the suggestion. + abstract final String? data; + + /// Indicates whether the suggestion is initially selected. + abstract final bool selected; + + /// Optional builder function for creating a custom widget for the suggestion. + abstract final WidgetBuilder? builder; +} + +/// Represents the settings for message suggestions. +abstract class SuggestionSettings { + const SuggestionSettings(); + + /// Background color of the suggestion area. + abstract final Color? backgroundColor; + + /// Background color of individual suggestion items. + abstract final WidgetStateProperty? itemBackgroundColor; + + /// Overall shape of the suggestion area. + abstract final ShapeBorder? shape; + + /// Shape of individual suggestion items. + abstract final WidgetStateProperty? itemShape; + + /// Text style for the suggestion item. + abstract final WidgetStateProperty? textStyle; + + /// Padding between the suggestion area and individual suggestion items. + abstract final EdgeInsetsGeometry margin; + + /// Padding between the content of each individual suggestion item. + abstract final EdgeInsetsGeometry itemPadding; + + /// Orientation of the axis along which the suggestions scrolled. + abstract final Axis orientation; + + /// Layout style for arranging suggestion items (wrap or scroll). + abstract final SuggestionOverflow itemOverflow; + + /// Selection mode for suggestion items. + abstract final SuggestionSelectionType selectionType; + + /// Vertical spacing between runs of suggestion items. + abstract final double runSpacing; + + /// Horizontal spacing between individual suggestion items. + abstract final double spacing; +} + +/// Represents a message bubble settings. +abstract class MessageSettings { + const MessageSettings(); + + /// Whether to show the user name or not. + abstract final bool showAuthorName; + + /// Whether to show the timestamp or not. + abstract final bool showTimestamp; + + /// Whether to show the user avatar or not. + abstract final bool? showAuthorAvatar; + + /// Format of the timestamp. + abstract final DateFormat? timestampFormat; + + /// Style of the message content. + abstract final TextStyle? textStyle; + + /// Style of the message header. + abstract final TextStyle? headerTextStyle; + + /// Background color of the message content. + abstract final Color? backgroundColor; + + /// Shape of the message content. + abstract final ShapeBorder? shape; + + /// Width factor of the message content. + abstract final double widthFactor; + + /// Size of the avatar. + abstract final Size avatarSize; + + /// Padding of the message content. + abstract final EdgeInsetsGeometry? margin; + + /// Padding of the message content. + abstract final EdgeInsetsGeometry? padding; + + /// Padding of the avatar. + abstract final EdgeInsetsGeometry? avatarPadding; + + /// Padding of the header. + abstract final EdgeInsetsGeometry headerPadding; + + /// Padding of the footer. + abstract final EdgeInsetsGeometry footerPadding; +} + +/// Represents a message bubble. +abstract class Composer { + const Composer(); + + /// Represent maximum number of line shown in composer. + abstract final int maxLines; + + /// Represent minimum number of line shown in composer. + abstract final int minLines; + + /// Style of the text. + abstract final TextStyle? textStyle; + + /// Decoration of the composer. + abstract final InputDecoration? decoration; + + /// Padding of the composer. + abstract final EdgeInsetsGeometry margin; + + /// Builder to create custom composer. + abstract final WidgetBuilder? builder; +} + +/// Represents an action button. +abstract class ActionButton { + const ActionButton(); + + /// Widget displayed inside the button. + abstract final Widget? child; + + /// Tooltip message will be shown when hover a action button. + abstract final String? tooltip; + + /// Foreground color of the button. + abstract final Color? foregroundColor; + + /// Background color of the button. + abstract final Color? backgroundColor; + + /// Focus color of the button. + abstract final Color? focusColor; + + /// Hover color of the button. + abstract final Color? hoverColor; + + /// Splash color of the button. + abstract final Color? splashColor; + + /// Elevation of the button. + abstract final double? elevation; + + /// Focus elevation of the button. + abstract final double? focusElevation; + + /// Hover elevation of the button. + abstract final double? hoverElevation; + + /// Highlight elevation of the button. + abstract final double? highlightElevation; + + /// Mouse cursor of the button. + abstract final MouseCursor? mouseCursor; + + /// Shape of the button. + abstract final ShapeBorder? shape; + + /// Padding of the button. + abstract final EdgeInsetsGeometry margin; + + /// Size of the button. + abstract final Size size; + + /// Callback when the button is pressed. + abstract final ValueChanged? onPressed; +} diff --git a/packages/syncfusion_flutter_chat/pubspec.yaml b/packages/syncfusion_flutter_chat/pubspec.yaml new file mode 100644 index 000000000..c9ca5e491 --- /dev/null +++ b/packages/syncfusion_flutter_chat/pubspec.yaml @@ -0,0 +1,31 @@ +name: syncfusion_flutter_chat +description: The Flutter Chat package is a UI library designed for creating customizable chat applications, both standard and AI-driven, using Flutter. +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_chat + +screenshots: + - description: 'This screenshot shows the customized chat user interface demo.' + path: screenshots/flutter_customized_chat.gif + - description: 'This screenshot shows the appointment selection chat user interface demo.' + path: screenshots/flutter_appointment_selection_chat.gif + +environment: + sdk: ^3.7.0 + flutter: ">=3.22.0" + +dependencies: + flutter: + sdk: flutter + intl: '>=0.18.1 <0.21.0' + syncfusion_flutter_core: + path: ../syncfusion_flutter_core + + + + + +flutter: + uses-material-design: true + assets: + - assets/Roboto-Medium.ttf + - assets/images/ \ No newline at end of file diff --git a/packages/syncfusion_flutter_chat/screenshots/flutter_appointment_selection_chat.gif b/packages/syncfusion_flutter_chat/screenshots/flutter_appointment_selection_chat.gif new file mode 100644 index 000000000..c3e1238ce Binary files /dev/null and b/packages/syncfusion_flutter_chat/screenshots/flutter_appointment_selection_chat.gif differ diff --git a/packages/syncfusion_flutter_chat/screenshots/flutter_customized_chat.gif b/packages/syncfusion_flutter_chat/screenshots/flutter_customized_chat.gif new file mode 100644 index 000000000..11ce528a7 Binary files /dev/null and b/packages/syncfusion_flutter_chat/screenshots/flutter_customized_chat.gif differ diff --git a/packages/syncfusion_flutter_core/CHANGELOG.md b/packages/syncfusion_flutter_core/CHANGELOG.md index c33915118..6c538493f 100644 --- a/packages/syncfusion_flutter_core/CHANGELOG.md +++ b/packages/syncfusion_flutter_core/CHANGELOG.md @@ -1,5 +1,71 @@ ## Unreleased +**General** + +* The compatible version of all our Flutter widgets has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of all our Flutter widgets has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter core example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter core example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +### Breaking Changes + +Following breaking changes will occur in core. + +## SfChatThemeData: + +- The `outgoingBubbleContentBackgroundColor` property has been renamed to `outgoingMessageBackgroundColor`. +- The `incomingBubbleContentBackgroundColor` property has been renamed to `incomingMessageBackgroundColor`. +- The `outgoingBubbleContentShape` property has been renamed to `outgoingMessageShape`. +- The `incomingBubbleContentShape` property has been renamed to `incomingMessageShape`. + +## SfAIAssistViewThemeData: + +- The `requestBubbleContentBackgroundColor` property has been renamed to `requestMessageBackgroundColor`. +- The `responseBubbleContentBackgroundColor` property has been renamed to `responseMessageBackgroundColor`. +- The `requestBubbleContentShape` property has been renamed to `requestMessageShape`. +- The `responseBubbleContentShape` property has been renamed to `responseMessageShape`. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of all our Flutter widgets has been updated to Flutter SDK 3.27.0. + +## Unreleased + +**General** + +* The compatible version of all our Flutter widgets has been updated to Flutter SDK 3.24.0. + +## Unreleased + +**General** +* Provided th​e Material 3 themes support. + +## [20.2.38] - 07/12/2022 + +**Features** +* The [Flutter PDF Library](https://www.syncfusion.com/document-processing/pdf-framework/flutter/pdf-library) has been developed to meet industry standards and is now marked as a production-ready. + +## [20.1.47] - 04/04/2022 + **Features** * The [Flutter Maps](https://www.syncfusion.com/flutter-widgets/flutter-maps) and [Flutter Linear Gauge](https://www.syncfusion.com/flutter-widgets/flutter-linear-gauge) widgets have been developed to meet industry standards and are now marked as production-ready widgets. @@ -7,7 +73,7 @@ **Breaking changes** -* The license key is not required now to run the application with our widgets. However, you need to still have either Syncfusion commercial license or community license to use our widgets and libraries. +* The license key is not required now to run the application with our widgets. However, you need to still have either Syncfusion® commercial license or community license to use our widgets and libraries. ## [18.2.59] - 09/23/2020 diff --git a/packages/syncfusion_flutter_core/LICENSE b/packages/syncfusion_flutter_core/LICENSE index 33e241d5f..185c7a743 100644 --- a/packages/syncfusion_flutter_core/LICENSE +++ b/packages/syncfusion_flutter_core/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Core 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. +Syncfusion® Flutter Core 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_core/README.md b/packages/syncfusion_flutter_core/README.md index 07cafb6a9..4346429b7 100644 --- a/packages/syncfusion_flutter_core/README.md +++ b/packages/syncfusion_flutter_core/README.md @@ -1,6 +1,6 @@ -# Syncfusion Flutter Core +# Syncfusion® Flutter Core -Syncfusion Flutter Core is a dependent package for the following Syncfusion Flutter widgets. +Syncfusion® Flutter Core is a dependent package for the following Syncfusion® Flutter widgets. * [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_charts) * [syncfusion_flutter_calendar](https://pub.dev/packages/syncfusion_flutter_calendar) @@ -19,13 +19,13 @@ Syncfusion Flutter Core is a dependent package for the following Syncfusion Flut * [syncfusion_officecore](https://pub.dev/packages/syncfusion_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 [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. +**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) - [Other useful links](#other-useful-links) - [Support and Feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Get the Demo application @@ -33,33 +33,30 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about Syncfusion Flutter widgets: +Take a look at the following to learn more about Syncfusion® Flutter widgets: -* [Syncfusion Flutter product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation](https://help.syncfusion.com/flutter/introduction/overview) * [Video tutorials](https://www.syncfusion.com/tutorial-videos/flutter) * [Knowledge base](https://www.syncfusion.com/kb) ## 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +## 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [.NET MAUI](https://www.syncfusion.com/maui-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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [.NET MAUI](https://www.syncfusion.com/maui-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 diff --git a/packages/syncfusion_flutter_core/analysis_options.yaml b/packages/syncfusion_flutter_core/analysis_options.yaml index 24a000bd2..59e7227c9 100644 --- a/packages/syncfusion_flutter_core/analysis_options.yaml +++ b/packages/syncfusion_flutter_core/analysis_options.yaml @@ -1,7 +1 @@ -include: lib/analysis_options.yaml - -analyzer: - errors: - library_private_types_in_public_api: ignore - lines_longer_than_80_chars: ignore - avoid_dynamic_calls: ignore +include: lib/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_core/example/README.md b/packages/syncfusion_flutter_core/example/README.md new file mode 100644 index 000000000..84c6fbda9 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/README.md @@ -0,0 +1,3 @@ +# core_example + +How to register a license key with Syncfsion Flutter widgets? diff --git a/packages/syncfusion_flutter_core/example/android/.gitignore b/packages/syncfusion_flutter_core/example/android/.gitignore index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_flutter_core/example/android/.gitignore +++ b/packages/syncfusion_flutter_core/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_core/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_core/example/android/app/build.gradle.kts new file mode 100644 index 000000000..18462a4df --- /dev/null +++ b/packages/syncfusion_flutter_core/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.core_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.core_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_flutter_core/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_core/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_core/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_core/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_core/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_core/example/android/app/src/main/AndroidManifest.xml index 3f41384db..260dff82b 100644 --- a/packages/syncfusion_flutter_core/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_core/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_core/example/android/app/src/main/kotlin/com/example/core_example/MainActivity.kt b/packages/syncfusion_flutter_core/example/android/app/src/main/kotlin/com/example/core_example/MainActivity.kt new file mode 100644 index 000000000..9cab24d37 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/android/app/src/main/kotlin/com/example/core_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.core_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_core/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_core/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_core/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_core/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_core/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_core/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_core/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_core/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_core/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_core/example/android/build.gradle.kts b/packages/syncfusion_flutter_core/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_core/example/android/gradle.properties b/packages/syncfusion_flutter_core/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_flutter_core/example/android/gradle.properties +++ b/packages/syncfusion_flutter_core/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_core/example/android/settings.gradle.kts b/packages/syncfusion_flutter_core/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_core/example/ios/.gitignore b/packages/syncfusion_flutter_core/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_core/example/ios/.gitignore +++ b/packages/syncfusion_flutter_core/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_core/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_core/example/ios/Flutter/Debug.xcconfig index e8efba114..592ceee85 100644 --- a/packages/syncfusion_flutter_core/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_core/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_core/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_core/example/ios/Flutter/Release.xcconfig index 399e9340e..592ceee85 100644 --- a/packages/syncfusion_flutter_core/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_core/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_core/example/ios/Podfile b/packages/syncfusion_flutter_core/example/ios/Podfile new file mode 100644 index 000000000..5a69b8902 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/ios/Podfile @@ -0,0 +1,84 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj index 2f60d33eb..442bb5d6b 100644 --- a/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,19 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,14 +43,15 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; 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; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 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 = ""; }; @@ -51,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -74,7 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -82,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -89,30 +111,38 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -129,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -139,11 +172,17 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; }; }; }; @@ -156,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -182,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -196,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -211,18 +264,33 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -245,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -277,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -285,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -299,29 +368,74 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -351,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -365,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -375,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -407,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -415,10 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -429,20 +547,19 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample; 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; @@ -452,20 +569,18 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -473,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -494,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_core/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_core/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_core/example/ios/Runner/Info.plist index 5474fae12..d6d01947f 100644 --- a/packages/syncfusion_flutter_core/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_core/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Core Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_core/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_core/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_flutter_core/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_core/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_core/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_core/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_core/example/lib/main.dart b/packages/syncfusion_flutter_core/example/lib/main.dart index 8e7d27b1a..ac1c9ae53 100644 --- a/packages/syncfusion_flutter_core/example/lib/main.dart +++ b/packages/syncfusion_flutter_core/example/lib/main.dart @@ -2,34 +2,75 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; void main() { - return runApp(ChartApp()); + return runApp(const ChartApp()); } -///Renders the chart widget class ChartApp extends StatelessWidget { + const ChartApp({super.key}); + @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Chart Demo', - theme: ThemeData(primarySwatch: Colors.blue), - home: _MyHomePage()); + return MaterialApp(title: 'Chart Demo', home: _SalesAnalysisPage()); } } -class _MyHomePage extends StatefulWidget { - //ignore: prefer_const_constructors_in_immutables - _MyHomePage({Key? key}) : super(key: key); - +class _SalesAnalysisPage extends StatefulWidget { @override - _MyHomePageState createState() => _MyHomePageState(); + State createState() => _SalesAnalysisPageState(); } -class _MyHomePageState extends State<_MyHomePage> { +class _SalesAnalysisPageState extends State<_SalesAnalysisPage> { + late List<_SalesData> _sales; + + @override + void initState() { + _sales = <_SalesData>[ + _SalesData('Jan', 35), + _SalesData('Feb', 28), + _SalesData('Mar', 34), + _SalesData('Apr', 32), + _SalesData('May', 40), + _SalesData('Jun', 47), + ]; + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Syncfusion Flutter Chart')), - body: - SfCartesianChart()); // Commented until the chart moves to null safety; + appBar: AppBar(title: const Text('Syncfusion Flutter Chart')), + body: SfCartesianChart( + primaryXAxis: const CategoryAxis(), + // Chart title. + title: const ChartTitle(text: 'Half yearly sales analysis'), + // Enable legend. + legend: const Legend(isVisible: true), + // Enable tooltip. + tooltipBehavior: TooltipBehavior(enable: true), + series: >[ + LineSeries( + name: 'Sales', + dataSource: _sales, + xValueMapper: (_SalesData sales, int index) => sales.year, + yValueMapper: (_SalesData sales, int index) => sales.sales, + // Enable data label. + dataLabelSettings: const DataLabelSettings(isVisible: true), + ), + ], + ), + ); } + + @override + void dispose() { + _sales.clear(); + super.dispose(); + } +} + +class _SalesData { + _SalesData(this.year, this.sales); + + final String year; + final double sales; } diff --git a/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_core/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_core/example/macos/.gitignore b/packages/syncfusion_flutter_core/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_core/example/macos/.gitignore +++ b/packages/syncfusion_flutter_core/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..2762722c6 100644 --- a/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* core_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "core_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* core_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* core_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/core_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/core_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/core_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/core_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/core_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/core_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..853bdc6fa 100644 --- a/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_core/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_core/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_core/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_core/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_core/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_core/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_core/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_core/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_core/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..a376dd9e8 100644 --- a/packages/syncfusion_flutter_core/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_core/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = core_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.coreExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_core/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_core/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_core/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_core/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_core/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_core/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_core/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_core/example/pubspec.yaml b/packages/syncfusion_flutter_core/example/pubspec.yaml index fdabf4577..22fb6ed42 100644 --- a/packages/syncfusion_flutter_core/example/pubspec.yaml +++ b/packages/syncfusion_flutter_core/example/pubspec.yaml @@ -3,14 +3,14 @@ description: This project holds information about registering a license key with version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: sdk: flutter - syncfusion_flutter_charts: ^19.1.57 + syncfusion_flutter_charts: ^26.2.8 - cupertino_icons: ^0.1.3 + cupertino_icons: '>=0.1.3 <1.0.7' dev_dependencies: flutter_test: diff --git a/packages/syncfusion_flutter_core/example/web/index.html b/packages/syncfusion_flutter_core/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_core/example/web/index.html +++ b/packages/syncfusion_flutter_core/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_core/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_core/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_core/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_core/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_core/lib/analysis_options.yaml b/packages/syncfusion_flutter_core/lib/analysis_options.yaml index f37760541..09c080084 100644 --- a/packages/syncfusion_flutter_core/lib/analysis_options.yaml +++ b/packages/syncfusion_flutter_core/lib/analysis_options.yaml @@ -10,13 +10,13 @@ include: package:flutter_lints/flutter.yaml # Specify analysis options. -# +# # Until there are meta linter rules, each desired lint must be explicitly enabled. # See: https://github.com/dart-lang/linter/issues/288 # # For a list of lints, see: http://dart-lang.github.io/linter/lints/ # See the configuration guide for more -# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# https://github.com/dart-lang/sdk/tree/main/pkg/analyzer#configuring-the-analyzer # # There are other similar analysis options files in the flutter repos, # which should be kept in sync with this file: @@ -30,36 +30,22 @@ include: package:flutter_lints/flutter.yaml # Android Studio, and the `flutter analyze` command. analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false + language: + strict-raw-types: true errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODOs in the code + # allow having TODO comments in the code todo: ignore - # allow self-reference to deprecated members (we do this because otherwise we have - # to annotate every member in every test, assert, etc, when we deprecate something) - deprecated_member_use_from_same_package: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore - # Turned off until null-safe rollout is complete. - unnecessary_null_comparison: ignore - + #The below rules are ignored manually - omit_local_variable_types: ignore - argument_type_not_assignable: ignore overridden_fields: ignore include_file_not_found: ignore invalid_dependency: ignore - - use_key_in_widget_constructors: ignore + avoid_dynamic_calls: ignore - use_late_for_private_fields_and_variables: ignore + lines_longer_than_80_chars: ignore + public_member_api_docs: ignore + always_specify_types: ignore + exclude: - "bin/cache/**" @@ -73,39 +59,37 @@ linter: # https://github.com/dart-lang/linter/blob/master/example/all.yaml - always_declare_return_types - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_require_non_null_named_parameters - always_specify_types # - always_use_package_imports # we do this commonly - annotate_overrides # - avoid_annotating_with_dynamic # conflicts with always_specify_types - avoid_bool_literals_in_conditional_expressions - # - avoid_catches_without_on_clauses # we do this commonly - # - avoid_catching_errors # we do this commonly + # - avoid_catches_without_on_clauses # blocked on https://github.com/dart-lang/linter/issues/3023 + # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/3023 - avoid_classes_with_only_static_members - # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_double_and_int_checks - avoid_dynamic_calls - avoid_empty_else - avoid_equals_and_hash_code_on_mutable_classes - avoid_escaping_inner_quotes - avoid_field_initializers_in_const_classes + # - avoid_final_parameters # incompatible with prefer_final_parameters - avoid_function_literals_in_foreach_calls - # - avoid_implementing_value_types # not yet tested + - avoid_implementing_value_types - avoid_init_to_null - # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_js_rounded_ints + # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - # - avoid_print # not yet tested + # - avoid_positional_boolean_parameters # would have been nice to enable this but by now there's too many places that break it + - avoid_print # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - # - avoid_redundant_argument_values # not yet tested + - avoid_redundant_argument_values - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - # - avoid_returning_null # there are plenty of valid reasons to return null - # - avoid_returning_null_for_future # not yet tested - avoid_returning_null_for_void - # - avoid_returning_this # there are plenty of valid reasons to return this - # - avoid_setters_without_getters # not yet tested + # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives + - avoid_setters_without_getters - avoid_shadowing_type_parameters - avoid_single_cascade_in_expression_statements - avoid_slow_async_io @@ -115,55 +99,59 @@ linter: - avoid_unnecessary_containers - avoid_unused_constructor_parameters - avoid_void_async - # - avoid_web_libraries_in_flutter # not yet tested + # - avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere - await_only_futures - camel_case_extensions - camel_case_types - cancel_subscriptions - # - cascade_invocations # not yet tested + # - cascade_invocations # doesn't match the typical style of this repo - cast_nullable_to_non_nullable # - close_sinks # not reliable enough # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 + # - conditional_uri_does_not_exist # not yet tested # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - control_flow_in_finally # - curly_braces_in_flow_control_structures # not required by flutter style + - depend_on_referenced_packages - deprecated_consistency - # - diagnostic_describe_all_properties # not yet tested + # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) - directives_ordering - # - do_not_use_environment # we do this commonly + # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic - empty_catches - empty_constructor_bodies - empty_statements + - eol_at_end_of_file - exhaustive_cases - file_names - flutter_style_todos - hash_and_equals - implementation_imports # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 - - iterable_contains_unrelated_type + - collection_methods_unrelated_type # - join_return_with_assignment # not required by flutter style - leading_newlines_in_multiline_strings - library_names - library_prefixes - library_private_types_in_public_api # - lines_longer_than_80_chars # not required by flutter style - - list_remove_unrelated_type - # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/linter/issues/453 - missing_whitespace_between_adjacent_strings - no_adjacent_strings_in_list - # - no_default_cases # too many false positives + - no_default_cases - no_duplicate_case_values + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers - no_logic_in_create_state # - no_runtimeType_toString # ok in tests; we enable this only in packages/ - - noop_primitive_operations - non_constant_identifier_names + - noop_primitive_operations - null_check_on_nullable_type_parameter - null_closures # - omit_local_variable_types # opposite of always_specify_types # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al - overridden_fields - - package_api_docs + # - package_api_docs - package_names - package_prefixed_library_names # - parameter_assignments # we do this commonly @@ -179,11 +167,12 @@ linter: # - prefer_constructors_over_static_methods # far too many false positives - prefer_contains # - prefer_double_quotes # opposite of prefer_single_quotes - - prefer_equal_for_default_values + # - prefer_equal_for_default_values # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals + # - prefer_final_parameters # we should enable this one day when it can be auto-fixed (https://github.com/dart-lang/linter/issues/3104), see also parameter_assignments - prefer_for_elements_to_map_fromIterable - prefer_foreach - prefer_function_declarations_over_variables @@ -198,9 +187,10 @@ linter: - prefer_is_not_empty - prefer_is_not_operator - prefer_iterable_whereType - # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018 + # - prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere - prefer_null_aware_operators - # - prefer_relative_imports # incompatible with sub-package imports + - prefer_relative_imports - prefer_single_quotes - prefer_spread_collections - prefer_typing_uninitialized_variables @@ -208,9 +198,12 @@ linter: - provide_deprecation_message # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml - recursive_getters + # - require_trailing_commas # blocked on https://github.com/dart-lang/sdk/issues/47441 + - secure_pubspec_urls - sized_box_for_whitespace + # - sized_box_shrink_expand # not yet tested - slash_for_doc_comments - # - sort_child_properties_last # not yet tested + - sort_child_properties_last - sort_constructors_first # - sort_pub_dependencies # prevents separating pinned transitive dependencies - sort_unnamed_constructors_first @@ -219,13 +212,15 @@ linter: - tighten_type_of_initializing_formals # - type_annotate_public_apis # subset of always_specify_types - type_init_formals - # - unawaited_futures # too many false positives + # - unawaited_futures # too many false positives, especially with the way AnimationController works - unnecessary_await_in_return - unnecessary_brace_in_string_interps - unnecessary_const + - unnecessary_constructor_name # - unnecessary_final # conflicts with prefer_final_locals - unnecessary_getters_setters # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_late - unnecessary_new - unnecessary_null_aware_assignments - unnecessary_null_checks @@ -233,22 +228,25 @@ linter: - unnecessary_nullable_for_final_variable_declarations - unnecessary_overrides - unnecessary_parenthesis - # - unnecessary_raw_strings # not yet tested + # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint - unnecessary_statements - unnecessary_string_escapes - unnecessary_string_interpolations - unnecessary_this - unrelated_type_equality_checks - # - unsafe_html # not yet tested + # - unsafe_html + - use_build_context_synchronously + # - use_decorated_box # not yet tested - use_full_hex_values_for_flutter_colors - use_function_type_syntax_for_parameters - # - use_if_null_to_convert_nulls_to_bools # not yet tested + - use_if_null_to_convert_nulls_to_bools - use_is_even_rather_than_modulo + - use_key_in_widget_constructors - use_late_for_private_fields_and_variables - use_named_constants - use_raw_strings - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested + - use_setters_to_change_properties # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - use_test_throws_matchers # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review diff --git a/packages/syncfusion_flutter_core/lib/core.dart b/packages/syncfusion_flutter_core/lib/core.dart index 7bb6198d7..f5525b704 100644 --- a/packages/syncfusion_flutter_core/lib/core.dart +++ b/packages/syncfusion_flutter_core/lib/core.dart @@ -1,13 +1,14 @@ 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'; export './src/utils/shape_helper.dart'; -part './src/license.dart'; part './src/calendar/calendar_helper.dart'; part './src/calendar/hijri_date_time.dart'; +part './src/license.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 4a44279cb..f912089a1 100644 --- a/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart @@ -15,8 +15,9 @@ part of core; dynamic addDuration(dynamic date, Duration duration) { dynamic currentDate = date.add(duration); if (date.timeZoneOffset != currentDate.timeZoneOffset) { - currentDate = - currentDate.add(date.timeZoneOffset - currentDate.timeZoneOffset); + currentDate = currentDate.add( + date.timeZoneOffset - currentDate.timeZoneOffset, + ); } return currentDate; @@ -38,8 +39,9 @@ dynamic addDuration(dynamic date, Duration duration) { dynamic subtractDuration(dynamic date, Duration duration) { dynamic currentDate = date.subtract(duration); if (date.timeZoneOffset != currentDate.timeZoneOffset) { - currentDate = - currentDate.add(date.timeZoneOffset - currentDate.timeZoneOffset); + currentDate = currentDate.add( + date.timeZoneOffset - currentDate.timeZoneOffset, + ); } return currentDate; @@ -53,8 +55,8 @@ dynamic getPreviousMonthDate(dynamic date) { : HijriDateTime(date.year, date.month - 1, 1); } return date.month == 1 - ? DateTime(date.year - 1, 12, 1) - : DateTime(date.year, date.month - 1, 1); + ? DateTime(date.year - 1, 12) + : DateTime(date.year, date.month - 1); } /// Returns the next month start date for the given date.. @@ -65,8 +67,8 @@ dynamic getNextMonthDate(dynamic date) { : HijriDateTime(date.year, date.month + 1, 1); } return date.month == 12 - ? DateTime(date.year + 1, 1, 1) - : DateTime(date.year, date.month + 1, 1); + ? DateTime(date.year + 1) + : DateTime(date.year, date.month + 1); } /// Return the given date if the date in between first and last date @@ -136,10 +138,14 @@ bool isSameOrAfterDate(dynamic firstDate, dynamic date) { } /// Get the visible dates based on the date value and visible dates count. -// ignore: always_specify_types -List getVisibleDates(dynamic date, List? nonWorkingDays, - int firstDayOfWeek, int visibleDatesCount) { - // ignore: always_specify_types +// ignore: always_specify_types, strict_raw_type +List getVisibleDates( + dynamic date, + List? nonWorkingDays, + int firstDayOfWeek, + int visibleDatesCount, +) { + // ignore: always_specify_types, strict_raw_type List datesCollection; if (date is HijriDateTime) { datesCollection = []; @@ -150,7 +156,10 @@ List getVisibleDates(dynamic date, List? nonWorkingDays, final int nonWorkingDaysCount = nonWorkingDays == null ? 0 : nonWorkingDays.length; final dynamic currentDate = getFirstDayOfWeekDate( - visibleDatesCount + nonWorkingDaysCount, date, firstDayOfWeek); + visibleDatesCount + nonWorkingDaysCount, + date, + firstDayOfWeek, + ); for (int i = 0; i < visibleDatesCount; i++) { final dynamic visibleDate = addDays(currentDate, i); @@ -178,7 +187,10 @@ dynamic addDays(dynamic date, int days) { /// Calculate first day of week date value based original date with first day of /// week value. dynamic getFirstDayOfWeekDate( - int visibleDatesCount, dynamic date, int firstDayOfWeek) { + int visibleDatesCount, + dynamic date, + int firstDayOfWeek, +) { if (visibleDatesCount % 7 != 0) { return date; } @@ -189,7 +201,7 @@ dynamic getFirstDayOfWeekDate( if (currentDate is HijriDateTime) { currentDate = HijriDateTime(currentDate.year, currentDate.month, 1); } else { - currentDate = DateTime(currentDate.year, currentDate.month, 1); + currentDate = DateTime(currentDate.year, currentDate.month); } } 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 eb338cbda..943cad033 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 @@ -45,9 +45,13 @@ class CustomScrollViewerLayout extends MultiChildRenderObjectWidget { /// } /// /// ``` - CustomScrollViewerLayout(List children, this._navigationDirection, - this._position, this._currentChildIndex) - : super(children: children); + const CustomScrollViewerLayout( + List children, + this._navigationDirection, + this._position, + this._currentChildIndex, { + super.key, + }) : super(children: children); final CustomScrollDirection _navigationDirection; final double _position; final int _currentChildIndex; @@ -55,12 +59,17 @@ class CustomScrollViewerLayout extends MultiChildRenderObjectWidget { @override _CustomScrollViewLayout createRenderObject(BuildContext context) { return _CustomScrollViewLayout( - _navigationDirection, _position, _currentChildIndex); + _navigationDirection, + _position, + _currentChildIndex, + ); } @override void updateRenderObject( - BuildContext context, _CustomScrollViewLayout renderObject) { + BuildContext context, + _CustomScrollViewLayout renderObject, + ) { renderObject ..position = _position ..navigationDirection = _navigationDirection @@ -70,7 +79,10 @@ class CustomScrollViewerLayout extends MultiChildRenderObjectWidget { class _CustomScrollViewLayout extends RenderWrap { _CustomScrollViewLayout( - this._navigationDirection, this._position, this._currentChildIndex); + this._navigationDirection, + this._position, + this._currentChildIndex, + ); CustomScrollDirection _navigationDirection; @@ -196,14 +208,12 @@ class _CustomScrollViewLayout extends RenderWrap { currentChildParentData.offset = Offset(currentChildXPos, currentChildYPos); lastChildParentData.offset = Offset(lastChildXPos, lastChildYPos); - children.forEach((dynamic child) => child.layout( - BoxConstraints( - minWidth: 0, - minHeight: 0, - maxWidth: width, - maxHeight: height, - ), - parentUsesSize: true)); + children.forEach( + (dynamic child) => child.layout( + BoxConstraints(maxWidth: width, maxHeight: height), + parentUsesSize: true, + ), + ); size = Size(constraints.maxWidth, constraints.maxHeight); } 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 52bdf581a..70e003e4a 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 @@ -1742,7 +1742,7 @@ const List _kDateCollection = [ 79900, 79930, 79960, - 79990 + 79990, ]; /// An HijriDateTime data. @@ -1757,8 +1757,7 @@ 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); + : _date = convertToGregorianDate(null, year: year, month: month, day: day); /// returns the hijri date value based on the current date /// @@ -1958,7 +1957,7 @@ class HijriDateTime { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1975,7 +1974,7 @@ class HijriDateTime { @override int get hashCode { - return hashValues(month, day, year); + return Object.hash(month, day, year); } } @@ -1992,7 +1991,8 @@ HijriDateTime convertToHijriDate(DateTime date) { } int yPrefix = (tYear / 100).floor(); int julianOffset = yPrefix - (yPrefix / 4.0).floor() - 2; - final int julianNumber = (365.25 * (tYear + 4716)).floor() + + final int julianNumber = + (365.25 * (tYear + 4716)).floor() + (30.6001 * (tMonth + 1)).floor() + day - julianOffset - @@ -2048,8 +2048,12 @@ HijriDateTime convertToHijriDate(DateTime date) { } /// Converts and returns the gregorian date from the given hijri date values. -DateTime convertToGregorianDate(HijriDateTime? date, - {int year = 0, int month = 0, int day = 0}) { +DateTime convertToGregorianDate( + HijriDateTime? date, { + int year = 0, + int month = 0, + int day = 0, +}) { if (date != null) { return date._date; } diff --git a/packages/syncfusion_flutter_core/lib/src/legend/legend.dart b/packages/syncfusion_flutter_core/lib/src/legend/legend.dart index 54a7c896f..d75922dec 100644 --- a/packages/syncfusion_flutter_core/lib/src/legend/legend.dart +++ b/packages/syncfusion_flutter_core/lib/src/legend/legend.dart @@ -1,22 +1,24 @@ import 'dart:async'; + import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; + import '../utils/shape_helper.dart'; import '../utils/shape_helper.dart' as shape_helper; /// Callback which returns toggled indices and teh current toggled index. -typedef ToggledIndicesChangedCallback = void Function( - List indices, int currentIndex); +typedef ToggledIndicesChangedCallback = + void Function(List indices, int currentIndex); /// Called with the details of single legend item. typedef ItemRenderCallback = void Function(ItemRendererDetails); /// Signature to return a [Widget] for the given value. -typedef LegendPointerBuilder = Widget Function( - BuildContext context, dynamic value); +typedef LegendPointerBuilder = + Widget Function(BuildContext context, dynamic value); enum _LegendType { vector, solidBar, gradientBar } @@ -60,7 +62,7 @@ enum LegendLabelsPlacement { /// [LegendLabelsPlacement.betweenItems] places labels /// in-between two bars. - betweenItems + betweenItems, } /// Placement of edge labels in the bar legend. @@ -70,7 +72,7 @@ enum LegendEdgeLabelsPlacement { /// Place the edge labels in the center of the starting position of the /// legend bars. - center + center, } /// Behavior of the labels when it overflowed from the shape. @@ -83,7 +85,7 @@ enum LegendLabelOverflow { /// It trims the labels based on the available space in their respective /// legend item. - ellipsis + ellipsis, } /// Applies gradient or solid color for the bar segments. @@ -92,7 +94,7 @@ enum LegendPaintingStyle { solid, /// Applies gradient color for bar segments. - gradient + gradient, } /// Specifies the alignment of legend. @@ -137,18 +139,18 @@ class ItemRendererDetails { /// Represents the class of items in legends. class LegendItem { /// Creates a [LegendItem]. - const LegendItem( - {required this.text, - this.color, - this.shader, - this.imageProvider, - this.iconType, - this.iconStrokeWidth, - this.overlayMarkerType, - this.degree, - this.endAngle, - this.startAngle}) - : assert(color != null || shader != null || imageProvider != null); + const LegendItem({ + required this.text, + this.color, + this.shader, + this.imageProvider, + this.iconType, + this.iconStrokeWidth, + this.overlayMarkerType, + this.degree, + this.endAngle, + this.startAngle, + }) : assert(color != null || shader != null || imageProvider != null); /// Specifies the text of the legend. final String text; @@ -187,6 +189,7 @@ class SfLegend extends StatefulWidget { const SfLegend({ Key? key, required this.items, + this.shouldAlwaysShowScrollbar = false, this.title, this.color, this.border, @@ -215,23 +218,23 @@ class SfLegend extends StatefulWidget { this.isComplex = false, this.toggledIndices, required this.child, - }) : _type = _LegendType.vector, - segmentSize = null, - labelsPlacement = null, - edgeLabelsPlacement = null, - labelOverflow = null, - segmentPaintingStyle = null, - itemBuilder = null, - itemCount = 0, - toggledItemColor = null, - pointerBuilder = null, - pointerSize = Size.zero, - pointerColor = null, - pointerController = null, - assert(itemSpacing >= 0), - assert(spacing >= 0), - assert(!isComplex || (isComplex && offset == null)), - super(key: key); + }) : _type = _LegendType.vector, + segmentSize = null, + labelsPlacement = null, + edgeLabelsPlacement = null, + labelOverflow = null, + segmentPaintingStyle = null, + itemBuilder = null, + itemCount = 0, + toggledItemColor = null, + pointerBuilder = null, + pointerSize = Size.zero, + pointerColor = null, + pointerController = null, + assert(itemSpacing >= 0), + assert(spacing >= 0), + assert(!isComplex || (isComplex && offset == null)), + super(key: key); /// Creates a [SfLegend]. const SfLegend.builder({ @@ -239,6 +242,7 @@ class SfLegend extends StatefulWidget { required this.itemCount, required this.itemBuilder, this.title, + this.shouldAlwaysShowScrollbar = false, this.color, this.border, this.offset, @@ -259,32 +263,33 @@ class SfLegend extends StatefulWidget { this.isComplex = false, this.toggledIndices, required this.child, - }) : _type = _LegendType.vector, - items = null, - iconSize = Size.zero, - textStyle = null, - imageProvider = null, - iconType = null, - iconBorder = null, - segmentSize = null, - labelsPlacement = null, - edgeLabelsPlacement = null, - labelOverflow = null, - segmentPaintingStyle = null, - toggledIconColor = null, - toggledTextOpacity = 0.5, - onItemRenderer = null, - pointerBuilder = null, - pointerSize = Size.zero, - pointerColor = null, - pointerController = null, - assert(!isComplex || (isComplex && offset == null)), - super(key: key); + }) : _type = _LegendType.vector, + items = null, + iconSize = Size.zero, + textStyle = null, + imageProvider = null, + iconType = null, + iconBorder = null, + segmentSize = null, + labelsPlacement = null, + edgeLabelsPlacement = null, + labelOverflow = null, + segmentPaintingStyle = null, + toggledIconColor = null, + toggledTextOpacity = 0.5, + onItemRenderer = null, + pointerBuilder = null, + pointerSize = Size.zero, + pointerColor = null, + pointerController = null, + assert(!isComplex || (isComplex && offset == null)), + super(key: key); /// Creates a [SfLegend]. const SfLegend.bar({ Key? key, required this.items, + this.shouldAlwaysShowScrollbar = false, this.title, this.color, this.border, @@ -309,28 +314,29 @@ class SfLegend extends StatefulWidget { this.pointerColor, this.pointerController, required this.child, - }) : _type = segmentPaintingStyle == LegendPaintingStyle.solid - ? _LegendType.solidBar - : _LegendType.gradientBar, - iconType = null, - imageProvider = null, - iconSize = Size.zero, - iconBorder = null, - itemRunSpacing = null, - spacing = 0.0, - itemBuilder = null, - itemCount = 0, - alignment = null, - width = null, - height = null, - toggledIconColor = null, - toggledItemColor = null, - toggledTextOpacity = 0.0, - onToggledIndicesChanged = null, - onItemRenderer = null, - assert(itemSpacing >= 0), - assert(!isComplex || (isComplex && offset == null)), - super(key: key); + }) : _type = + segmentPaintingStyle == LegendPaintingStyle.solid + ? _LegendType.solidBar + : _LegendType.gradientBar, + iconType = null, + imageProvider = null, + iconSize = Size.zero, + iconBorder = null, + itemRunSpacing = null, + spacing = 0.0, + itemBuilder = null, + itemCount = 0, + alignment = null, + width = null, + height = null, + toggledIconColor = null, + toggledItemColor = null, + toggledTextOpacity = 0.0, + onToggledIndicesChanged = null, + onItemRenderer = null, + assert(itemSpacing >= 0), + assert(!isComplex || (isComplex && offset == null)), + super(key: key); /// Specifies the legend items. final List? items; @@ -350,6 +356,14 @@ class SfLegend extends StatefulWidget { /// Wraps or scrolls the legend items when it overflows. final LegendOverflowMode overflowMode; + ///Toggles the scrollbar visibility. + /// + ///When set to false, the scrollbar appears only when scrolling else the + ///scrollbar fades out. When true, the scrollbar will never fade out and + ///will always be visible when the items are overflown. + /// + final bool shouldAlwaysShowScrollbar; + /// Specifies the space between the legend text and the icon. final double spacing; @@ -471,9 +485,11 @@ class SfLegend extends StatefulWidget { class _SfLegendState extends State { bool _omitLegend = false; TextStyle? _textStyle; - - Widget _buildResponsiveLayout(Widget? current, - [BoxConstraints? baseConstraints]) { + final ScrollController _scrollController = ScrollController(); + Widget _buildResponsiveLayout( + Widget? current, [ + BoxConstraints? baseConstraints, + ]) { if (current == null) { return widget.child; } @@ -481,42 +497,60 @@ class _SfLegendState extends State { if (widget.offset == null) { switch (widget.position) { case LegendPosition.top: - current = Column(children: [ - Align( - alignment: _getEffectiveLegendItemsAlignment( - widget.position, widget.alignment ?? LegendAlignment.center), - child: current, - ), - _buildChild(baseConstraints) - ]); + current = Column( + children: [ + Align( + alignment: _getEffectiveLegendItemsAlignment( + widget.position, + widget.alignment ?? LegendAlignment.center, + ), + child: current, + ), + _buildChild(baseConstraints), + ], + ); break; case LegendPosition.bottom: - current = Column(children: [ - _buildChild(baseConstraints), - Align( - alignment: _getEffectiveLegendItemsAlignment( - widget.position, widget.alignment ?? LegendAlignment.center), - child: current, - ) - ]); + current = Column( + children: [ + _buildChild(baseConstraints), + Align( + alignment: _getEffectiveLegendItemsAlignment( + widget.position, + widget.alignment ?? LegendAlignment.center, + ), + child: current, + ), + ], + ); break; case LegendPosition.left: - current = Row(children: [ - Align( - alignment: _getEffectiveLegendItemsAlignment(widget.position, - widget.alignment ?? LegendAlignment.center), - child: current), - _buildChild(baseConstraints) - ]); + current = Row( + children: [ + Align( + alignment: _getEffectiveLegendItemsAlignment( + widget.position, + widget.alignment ?? LegendAlignment.center, + ), + child: current, + ), + _buildChild(baseConstraints), + ], + ); break; case LegendPosition.right: - current = Row(children: [ - _buildChild(baseConstraints), - Align( - alignment: _getEffectiveLegendItemsAlignment(widget.position, - widget.alignment ?? LegendAlignment.center), - child: current) - ]); + current = Row( + children: [ + _buildChild(baseConstraints), + Align( + alignment: _getEffectiveLegendItemsAlignment( + widget.position, + widget.alignment ?? LegendAlignment.center, + ), + child: current, + ), + ], + ); break; } } else { @@ -545,13 +579,13 @@ class _SfLegendState extends State { childConstraints.biggest) { child = widget.child; } else { - SchedulerBinding.instance!.addPostFrameCallback( - (Duration timeStamp) { - setState(() { - _omitLegend = true; - }); - }, - ); + SchedulerBinding.instance.addPostFrameCallback(( + Duration timeStamp, + ) { + setState(() { + _omitLegend = true; + }); + }); } return SizedBox( @@ -639,72 +673,162 @@ class _SfLegendState extends State { current = Padding(padding: widget.padding!, child: current); } + final double scrollbarThickness = + defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.android + ? 4 + : 5; + if (widget.title == null) { if (widget.overflowMode == LegendOverflowMode.scroll) { - current = SingleChildScrollView( - scrollDirection: widget.scrollDirection ?? - (widget.position == LegendPosition.top || - widget.position == LegendPosition.bottom - ? Axis.horizontal - : Axis.vertical), - child: current, + current = Scrollbar( + thickness: scrollbarThickness, + controller: _scrollController, + thumbVisibility: widget.shouldAlwaysShowScrollbar, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: + widget.scrollDirection ?? + (widget.position == LegendPosition.top || + widget.position == LegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + child: current, + ), + ), ); } else if (widget.overflowMode == LegendOverflowMode.wrapScroll) { - current = SingleChildScrollView( - scrollDirection: widget.direction == Axis.horizontal - ? Axis.vertical - : Axis.horizontal, - child: current, + current = Scrollbar( + thickness: scrollbarThickness, + controller: _scrollController, + thumbVisibility: widget.shouldAlwaysShowScrollbar, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: + widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, + child: current, + ), + ), ); } else if (widget.overflowMode == LegendOverflowMode.none) { - current = SingleChildScrollView( - scrollDirection: widget.scrollDirection == Axis.horizontal - ? Axis.horizontal - : Axis.vertical, + current = ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: SingleChildScrollView( + scrollDirection: + widget.scrollDirection == Axis.horizontal + ? Axis.horizontal + : Axis.vertical, physics: const NeverScrollableScrollPhysics(), - child: current); + child: current, + ), + ); } } else { if (widget.position == LegendPosition.top || widget.position == LegendPosition.bottom) { current = Column( - mainAxisAlignment: widget.position == LegendPosition.top - ? MainAxisAlignment.start - : MainAxisAlignment.end, + mainAxisAlignment: + widget.position == LegendPosition.top + ? MainAxisAlignment.start + : MainAxisAlignment.end, children: [ widget.title!, if (widget.overflowMode == LegendOverflowMode.scroll) (widget.width != null || widget.height != null) ? Expanded( - child: SingleChildScrollView( + child: Scrollbar( + thickness: scrollbarThickness, + controller: _scrollController, + thumbVisibility: widget.shouldAlwaysShowScrollbar, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _scrollController, scrollDirection: - widget.scrollDirection ?? Axis.horizontal, - child: current), - ) - : SingleChildScrollView( - scrollDirection: - widget.scrollDirection ?? Axis.horizontal, - child: current) + widget.scrollDirection ?? + (widget.position == LegendPosition.top || + widget.position == LegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + child: current, + ), + ), + ), + ) + : Scrollbar( + thickness: scrollbarThickness, + controller: _scrollController, + thumbVisibility: widget.shouldAlwaysShowScrollbar, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: + widget.scrollDirection ?? + (widget.position == LegendPosition.top || + widget.position == LegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + child: current, + ), + ), + ) else if (widget.overflowMode == LegendOverflowMode.wrapScroll) Expanded( - child: SingleChildScrollView( - scrollDirection: widget.direction == Axis.horizontal - ? Axis.vertical - : Axis.horizontal, - child: current), + child: Scrollbar( + thickness: scrollbarThickness, + controller: _scrollController, + thumbVisibility: widget.shouldAlwaysShowScrollbar, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: + widget.scrollDirection ?? + (widget.position == LegendPosition.top || + widget.position == LegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + child: current, + ), + ), + ), ) else if (widget.overflowMode == LegendOverflowMode.none) - Expanded( - child: SingleChildScrollView( - scrollDirection: widget.direction == Axis.horizontal + ScrollConfiguration( + behavior: ScrollConfiguration.of( + context, + ).copyWith(scrollbars: false), + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: + widget.scrollDirection == Axis.horizontal ? Axis.horizontal : Axis.vertical, - physics: const NeverScrollableScrollPhysics(), - child: current)) + physics: const NeverScrollableScrollPhysics(), + child: current, + ), + ) else (widget.width != null || widget.height != null) ? Expanded(child: current) - : current + : current, ], ); } else { @@ -713,26 +837,31 @@ class _SfLegendState extends State { children: [ widget.title!, Flexible( - fit: FlexFit.loose, - child: widget.overflowMode == LegendOverflowMode.scroll - ? SingleChildScrollView( - scrollDirection: widget.scrollDirection ?? Axis.vertical, - child: current) - : widget.overflowMode == LegendOverflowMode.wrapScroll + child: + widget.overflowMode == LegendOverflowMode.scroll + ? SingleChildScrollView( + scrollDirection: + widget.scrollDirection ?? Axis.vertical, + child: current, + ) + : widget.overflowMode == LegendOverflowMode.wrapScroll ? SingleChildScrollView( - scrollDirection: widget.direction == Axis.horizontal - ? Axis.vertical - : Axis.horizontal, - child: current) + scrollDirection: + widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, + child: current, + ) : widget.overflowMode == LegendOverflowMode.none - ? SingleChildScrollView( - scrollDirection: - widget.direction == Axis.horizontal - ? Axis.vertical - : Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - child: current) - : current, + ? SingleChildScrollView( + scrollDirection: + widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + child: current, + ) + : current, ), ], ); @@ -743,10 +872,13 @@ class _SfLegendState extends State { current = DecoratedBox( decoration: BoxDecoration( color: widget.color, - border: widget.border != null - ? Border.all( - color: widget.border!.color, width: widget.border!.width) - : null, + border: + widget.border != null + ? Border.all( + color: widget.border!.color, + width: widget.border!.width, + ) + : null, ), child: current, ); @@ -781,7 +913,9 @@ class _SfLegendState extends State { } AlignmentGeometry _getEffectiveLegendItemsAlignment( - LegendPosition position, LegendAlignment alignment) { + LegendPosition position, + LegendAlignment alignment, + ) { switch (position) { case LegendPosition.top: case LegendPosition.bottom: @@ -810,24 +944,28 @@ class _SfLegendState extends State { switch (legendPosition) { case LegendPosition.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); + 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 LegendPosition.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); + 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 LegendPosition.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); + 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 LegendPosition.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); + 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, + ); } } @@ -835,9 +973,12 @@ class _SfLegendState extends State { Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); if (widget.itemBuilder == null) { - _textStyle = themeData.textTheme.caption! + _textStyle = themeData.textTheme.bodySmall! .copyWith( - color: themeData.textTheme.caption!.color!.withOpacity(0.87)) + color: themeData.textTheme.bodySmall!.color!.withValues( + alpha: 0.87, + ), + ) .merge(widget.textStyle); } if (!widget.isComplex) { @@ -947,40 +1088,44 @@ class _VectorLegendState extends State<_VectorLegend> final int length = widget.items!.length; for (int index = 0; index < length; index++) { final LegendItem item = widget.items![index]; - items.add(_LegendItem( - index: index, - text: item.text, - textStyle: widget.textStyle, - iconType: item.iconType ?? widget.iconType, - iconStrokeWidth: item.iconStrokeWidth, - imageProvider: item.imageProvider ?? widget.imageProvider, - shader: item.shader, - iconSize: widget.iconSize, - iconColor: item.color, - iconBorder: widget.iconBorder, - spacing: widget.spacing, - toggledIndices: widget.toggledIndices, - toggledColor: _getEffectiveToggledColor(themeData), - toggledTextOpacity: widget.toggledTextOpacity, - onToggledIndicesChanged: widget.onToggledIndicesChanged, - onItemRenderer: widget.onItemRenderer, - overlayMarkerType: item.overlayMarkerType, - degree: item.degree, - startAngle: item.startAngle, - endAngle: item.endAngle, - )); + items.add( + _LegendItem( + index: index, + text: item.text, + textStyle: widget.textStyle, + iconType: item.iconType ?? widget.iconType, + iconStrokeWidth: item.iconStrokeWidth, + imageProvider: item.imageProvider ?? widget.imageProvider, + shader: item.shader, + iconSize: widget.iconSize, + iconColor: item.color, + iconBorder: widget.iconBorder, + spacing: widget.spacing, + toggledIndices: widget.toggledIndices, + toggledColor: _getEffectiveToggledColor(themeData), + toggledTextOpacity: widget.toggledTextOpacity, + onToggledIndicesChanged: widget.onToggledIndicesChanged, + onItemRenderer: widget.onItemRenderer, + overlayMarkerType: item.overlayMarkerType, + degree: item.degree, + startAngle: item.startAngle, + endAngle: item.endAngle, + ), + ); } } else if (widget.itemCount != null && widget.itemCount! > 0 && widget.itemBuilder != null) { for (int index = 0; index < widget.itemCount!; index++) { - items.add(_LegendItem( - index: index, - itemBuilder: widget.itemBuilder, - toggledColor: _getEffectiveToggledColor(themeData), - toggledIndices: widget.toggledIndices, - onToggledIndicesChanged: widget.onToggledIndicesChanged, - )); + items.add( + _LegendItem( + index: index, + itemBuilder: widget.itemBuilder, + toggledColor: _getEffectiveToggledColor(themeData), + toggledIndices: widget.toggledIndices, + onToggledIndicesChanged: widget.onToggledIndicesChanged, + ), + ); } } return items; @@ -991,26 +1136,23 @@ class _VectorLegendState extends State<_VectorLegend> if (widget.onToggledIndicesChanged != null) { toggledColor = widget.toggledIconColor ?? widget.toggledItemColor; if (toggledColor == null || toggledColor == Colors.transparent) { - toggledColor = themeData.brightness == Brightness.light - ? const Color.fromRGBO(230, 230, 230, 1) - : const Color.fromRGBO(66, 66, 66, 1); + toggledColor = + themeData.brightness == Brightness.light + ? const Color.fromRGBO(230, 230, 230, 1) + : const Color.fromRGBO(66, 66, 66, 1); } } return toggledColor; } - @override - void dispose() { - super.dispose(); - } - @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); final Widget current = Wrap( - direction: widget.direction ?? + direction: + widget.direction ?? (widget.position == LegendPosition.top || widget.position == LegendPosition.bottom ? Axis.horizontal @@ -1018,7 +1160,6 @@ class _VectorLegendState extends State<_VectorLegend> spacing: widget.itemSpacing!, runSpacing: widget.itemRunSpacing ?? 6.0, runAlignment: WrapAlignment.center, - alignment: WrapAlignment.start, children: _buildLegendItems(themeData), ); @@ -1129,24 +1270,27 @@ class _LegendItemState extends State<_LegendItem> ImageInfo? _imageInfo; ImageStream? _imageStream; - Completer? _completer; + late Completer? _completer; Future? _obtainImage; Widget _buildCustomPaint( - ItemRendererDetails details, AsyncSnapshot snapshot) { + ItemRendererDetails details, + AsyncSnapshot snapshot, + ) { Widget current = CustomPaint( size: widget.iconSize, painter: _LegendIconShape( - color: details.color, - iconType: details.iconType, - iconBorder: details.iconBorder, - iconStrokeWidth: widget.iconStrokeWidth, - image: snapshot.data, - shader: widget.shader, - overlayMarkerType: widget.overlayMarkerType, - degree: widget.degree, - startAngle: widget.startAngle, - endAngle: widget.endAngle), + color: details.color, + iconType: details.iconType, + iconBorder: details.iconBorder, + iconStrokeWidth: widget.iconStrokeWidth, + image: snapshot.data, + shader: widget.shader, + overlayMarkerType: widget.overlayMarkerType, + degree: widget.degree, + startAngle: widget.startAngle, + endAngle: widget.endAngle, + ), ); if (widget.shader != null && @@ -1162,8 +1306,9 @@ class _LegendItemState extends State<_LegendItem> return ShaderMask( blendMode: BlendMode.srcATop, shaderCallback: (Rect bounds) { - return LinearGradient(colors: [color, color]) - .createShader(bounds); + return LinearGradient( + colors: [color, color], + ).createShader(bounds); }, child: current, ); @@ -1171,8 +1316,9 @@ class _LegendItemState extends State<_LegendItem> void _handleTapUp(TapUpDetails details) { if (widget.toggledIndices != null) { - final List newToggledIndices = - List.from(widget.toggledIndices!); + final List newToggledIndices = List.from( + widget.toggledIndices!, + ); if (!newToggledIndices.contains(widget.index)) { newToggledIndices.add(widget.index); } else { @@ -1212,14 +1358,19 @@ class _LegendItemState extends State<_LegendItem> @override void initState() { _toggleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); _toggleAnimation = CurvedAnimation( - parent: _toggleAnimationController, curve: Curves.easeInOut); + parent: _toggleAnimationController, + curve: Curves.easeInOut, + ); _toggleAnimation.addListener(rebuild); - final Color? begin = widget.shader == null && widget.imageProvider == null - ? widget.iconColor - : null; + final Color? begin = + widget.shader == null && widget.imageProvider == null + ? widget.iconColor + : null; _iconColorTween = ColorTween(begin: begin, end: widget.toggledColor); _opacityTween = Tween(begin: 1.0, end: widget.toggledTextOpacity); @@ -1238,9 +1389,10 @@ class _LegendItemState extends State<_LegendItem> @override void didUpdateWidget(_LegendItem oldWidget) { if (widget.iconColor != oldWidget.iconColor) { - final Color? begin = widget.shader == null && widget.imageProvider == null - ? widget.iconColor - : null; + final Color? begin = + widget.shader == null && widget.imageProvider == null + ? widget.iconColor + : null; _iconColorTween.begin = begin; } @@ -1291,8 +1443,9 @@ class _LegendItemState extends State<_LegendItem> } } } else { - final Color? referenceIconColor = - _iconColorTween.evaluate(_toggleAnimation); + final Color? referenceIconColor = _iconColorTween.evaluate( + _toggleAnimation, + ); final ItemRendererDetails details = ItemRendererDetails( index: widget.index, text: widget.text!, @@ -1314,12 +1467,14 @@ class _LegendItemState extends State<_LegendItem> Text( details.text, style: widget.textStyle!.copyWith( - color: widget.textStyle!.foreground == null - ? widget.textStyle!.color! - .withOpacity(_opacityTween.evaluate(_toggleAnimation)) - : widget.textStyle!.foreground!.color, + color: + widget.textStyle!.foreground == null + ? widget.textStyle!.color!.withValues( + alpha: _opacityTween.evaluate(_toggleAnimation), + ) + : widget.textStyle!.foreground!.color, ), - ) + ), ], ); } @@ -1344,17 +1499,18 @@ class _LegendItemState extends State<_LegendItem> /// Represents the class for rendering icon shape. class _LegendIconShape extends CustomPainter { /// Represents [LegendIconShape] - _LegendIconShape( - {required this.color, - required this.iconType, - this.iconBorder, - this.iconStrokeWidth, - this.image, - this.shader, - this.overlayMarkerType, - this.degree, - this.startAngle, - this.endAngle}); + _LegendIconShape({ + required this.color, + required this.iconType, + this.iconBorder, + this.iconStrokeWidth, + this.image, + this.shader, + this.overlayMarkerType, + this.degree, + this.startAngle, + this.endAngle, + }); /// Specifies the color of the icon. final Color? color; @@ -1414,15 +1570,16 @@ class _LegendIconShape extends CustomPainter { paintImage(canvas: canvas, rect: Offset.zero & size, image: image!); } else { shape_helper.paint( - canvas: canvas, - rect: Offset.zero & size, - shapeType: iconType!, - paint: _getFillPaint(), - borderPaint: _getStrokePaint(), - overlayMarkerType: overlayMarkerType, - degree: degree, - startAngle: startAngle, - endAngle: endAngle); + canvas: canvas, + rect: Offset.zero & size, + shapeType: iconType!, + paint: _getFillPaint(), + borderPaint: _getStrokePaint(), + overlayMarkerType: overlayMarkerType, + degree: degree, + startAngle: startAngle, + endAngle: endAngle, + ); } } @@ -1519,15 +1676,17 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { Widget build(BuildContext context) { _segmentSize = widget.segmentSize ?? const Size(80.0, 12.0); final TextDirection textDirection = Directionality.of(context); - _direction = widget.direction ?? + _direction = + widget.direction ?? (widget.position == LegendPosition.top || widget.position == LegendPosition.bottom ? Axis.horizontal : Axis.vertical); - _textDirection = textDirection == TextDirection.ltr - ? textDirection - : (_direction == Axis.vertical ? TextDirection.ltr : textDirection); - _textPainter.textScaleFactor = MediaQuery.of(context).textScaleFactor; + _textDirection = + textDirection == TextDirection.ltr + ? textDirection + : (_direction == Axis.vertical ? TextDirection.ltr : textDirection); + _textPainter.textScaler = MediaQuery.of(context).textScaler; final Widget child = Directionality( textDirection: _textDirection, @@ -1536,7 +1695,6 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { spacing: widget.itemSpacing!, runSpacing: 6, runAlignment: WrapAlignment.center, - alignment: WrapAlignment.start, children: _getBarSegments(), ), ); @@ -1560,8 +1718,9 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { currentText = _getTrimmedText(item.text, currentText, i, length); } else { if (i == 0) { - final List firstSegmentLabels = - _getStartSegmentLabel(item.text); + final List firstSegmentLabels = _getStartSegmentLabel( + item.text, + ); if (firstSegmentLabels.length > 1) { startText = firstSegmentLabels[0]; currentText = firstSegmentLabels[1]; @@ -1574,7 +1733,11 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } currentText = _getTrimmedText( - currentText, widget.items![i + 1].text, i, length); + currentText, + widget.items![i + 1].text, + i, + length, + ); } } else { currentText = item.text; @@ -1589,24 +1752,26 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } } } - legendItems.add(_SolidBarLegendItem( - labelsPlacement: widget.labelsPlacement, - labelOverflow: widget.labelOverflow, - segmentSize: _segmentSize, - iconColor: item.color, - direction: _direction, - textStyle: widget.textStyle, - index: i, - length: length, - startText: startText, - text: currentText, - itemSpacing: widget.itemSpacing, - edgeLabelsPlacement: widget.edgeLabelsPlacement, - pointerSize: widget.pointerSize, - pointerColor: widget.pointerColor, - pointerBuilder: widget.pointerBuilder, - pointerController: widget.pointerController, - )); + legendItems.add( + _SolidBarLegendItem( + labelsPlacement: widget.labelsPlacement, + labelOverflow: widget.labelOverflow, + segmentSize: _segmentSize, + iconColor: item.color, + direction: _direction, + textStyle: widget.textStyle, + index: i, + length: length, + startText: startText, + text: currentText, + itemSpacing: widget.itemSpacing, + edgeLabelsPlacement: widget.edgeLabelsPlacement, + pointerSize: widget.pointerSize, + pointerColor: widget.pointerColor, + pointerBuilder: widget.pointerBuilder, + pointerController: widget.pointerController, + ), + ); } } @@ -1628,7 +1793,11 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } String _getTrimmedText( - String currentText, String? nextText, int index, int length) { + String currentText, + String? nextText, + int index, + int length, + ) { if (widget.labelOverflow == LegendLabelOverflow.visible || currentText.isEmpty || (nextText != null && nextText.isEmpty) || @@ -1646,17 +1815,21 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { isLastInsideItem = widget.edgeLabelsPlacement == LegendEdgeLabelsPlacement.inside; refNextTextWidth = _getTextWidth(nextText) / 2; - refCurrentTextWidth = isLastInsideItem - ? _getTextWidth(currentText) - : _getTextWidth(currentText) / 2; + refCurrentTextWidth = + isLastInsideItem + ? _getTextWidth(currentText) + : _getTextWidth(currentText) / 2; } else { refCurrentTextWidth = _getTextWidth(currentText) / 2; - refNextTextWidth = index + 1 == length - 1 && - widget.edgeLabelsPlacement == LegendEdgeLabelsPlacement.inside - ? _getTextWidth(nextText) - : _getTextWidth(nextText) / 2; + refNextTextWidth = + index + 1 == length - 1 && + widget.edgeLabelsPlacement == + LegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; } - _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > + _isOverlapSegmentText = + refCurrentTextWidth + refNextTextWidth > barSize.width + widget.itemSpacing!; // Returning empty string in case of text overlapping the segment size @@ -1667,13 +1840,14 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } else if (widget.labelOverflow == LegendLabelOverflow.ellipsis) { final double textWidth = refCurrentTextWidth + refNextTextWidth; return _getTrimText( - currentText, - widget.textStyle!, - _segmentSize.width + widget.itemSpacing! / 2, - _textPainter, - textWidth, - refNextTextWidth, - isLastInsideItem); + currentText, + widget.textStyle!, + _segmentSize.width + widget.itemSpacing! / 2, + _textPainter, + textWidth, + refNextTextWidth, + isLastInsideItem, + ); } } @@ -1687,9 +1861,15 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } } -String _getTrimText(String text, TextStyle style, double maxWidth, - TextPainter painter, double width, - [double? nextTextHalfWidth, bool isInsideLastItem = false]) { +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 @@ -1701,7 +1881,10 @@ String _getTrimText(String text, TextStyle style, double maxWidth, break; } else { trimmedText = text.replaceRange( - actualTextLength - trimLength, actualTextLength, '...'); + actualTextLength - trimLength, + actualTextLength, + '...', + ); painter.text = TextSpan(style: style, text: trimmedText); painter.layout(); trimLength++; @@ -1710,9 +1893,10 @@ String _getTrimText(String text, TextStyle style, double maxWidth, if (isInsideLastItem && nextTextHalfWidth != null) { width = painter.width + nextTextHalfWidth; } else { - width = nextTextHalfWidth != null - ? painter.width / 2 + nextTextHalfWidth - : painter.width; + width = + nextTextHalfWidth != null + ? painter.width / 2 + nextTextHalfWidth + : painter.width; } } @@ -1798,7 +1982,11 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { } Offset _getTextOffset( - int index, String text, int dataSourceLength, bool isStartText) { + int index, + String text, + int dataSourceLength, + bool isStartText, + ) { _textPainter.text = TextSpan(text: text, style: widget.textStyle); _textPainter.layout(); @@ -1827,14 +2015,18 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { } Offset _getHorizontalTextOffset( - int index, String text, int dataSourceLength) { + int index, + String text, + int dataSourceLength, + ) { _textPainter.text = TextSpan(text: text, style: widget.textStyle); _textPainter.layout(); if (widget.labelsPlacement == LegendLabelsPlacement.betweenItems) { - final double width = _textDirection == TextDirection.rtl && - widget.segmentSize!.width < _textPainter.width - ? _textPainter.width - : widget.segmentSize!.width; + final double width = + _textDirection == TextDirection.rtl && + widget.segmentSize!.width < _textPainter.width + ? _textPainter.width + : widget.segmentSize!.width; if (index == dataSourceLength - 1) { if (widget.edgeLabelsPlacement == LegendEdgeLabelsPlacement.inside) { return Offset(width - _textPainter.width, 0.0); @@ -1843,12 +2035,15 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { } return Offset( - width - _textPainter.width / 2 + widget.itemSpacing! / 2, 0.0); + width - _textPainter.width / 2 + widget.itemSpacing! / 2, + 0.0, + ); } else { - final double xPosition = _textDirection == TextDirection.rtl && - widget.segmentSize!.width < _textPainter.width - ? _textPainter.width / 2 - widget.segmentSize!.width / 2 - : widget.segmentSize!.width / 2 - _textPainter.width / 2; + final double xPosition = + _textDirection == TextDirection.rtl && + widget.segmentSize!.width < _textPainter.width + ? _textPainter.width / 2 - widget.segmentSize!.width / 2 + : widget.segmentSize!.width / 2 - _textPainter.width / 2; return Offset(xPosition, 0.0); } } @@ -1865,13 +2060,16 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { } return Offset( - 0.0, - widget.segmentSize!.width - - _textPainter.height / 2 + - widget.itemSpacing! / 2); + 0.0, + widget.segmentSize!.width - + _textPainter.height / 2 + + widget.itemSpacing! / 2, + ); } else { return Offset( - 0.0, widget.segmentSize!.width / 2 - _textPainter.height / 2); + 0.0, + widget.segmentSize!.width / 2 - _textPainter.height / 2, + ); } } @@ -1883,41 +2081,53 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { return Directionality( textDirection: TextDirection.ltr, - child: offset != Offset.zero - ? Transform.translate( - offset: offset, - child: Text( + 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: TextOverflow.visible, + overflow: + widget.labelOverflow == LegendLabelOverflow.ellipsis && + widget.labelsPlacement == + LegendLabelsPlacement.onItem + ? TextOverflow.ellipsis + : TextOverflow.visible, style: widget.textStyle, ), - ) - : Text( - text, - textAlign: TextAlign.center, - softWrap: false, - overflow: widget.labelOverflow == LegendLabelOverflow.ellipsis && - widget.labelsPlacement == LegendLabelsPlacement.onItem - ? TextOverflow.ellipsis - : TextOverflow.visible, - style: widget.textStyle, - ), ); } Widget _getTextWidget(String? startText, String text) { Offset? startTextOffset; if (widget.index == 0 && startText != null) { + startTextOffset = _getTextOffset( + widget.index!, + startText, + widget.length!, + true, + ); startTextOffset = - _getTextOffset(widget.index!, startText, widget.length!, true); - startTextOffset = _textDirection == TextDirection.rtl && - widget.direction == Axis.horizontal - ? -startTextOffset - : startTextOffset; + _textDirection == TextDirection.rtl && + widget.direction == Axis.horizontal + ? -startTextOffset + : startTextOffset; } - Offset textOffset = - _getTextOffset(widget.index!, text, widget.length!, false); + Offset textOffset = _getTextOffset( + widget.index!, + text, + widget.length!, + false, + ); textOffset = _textDirection == TextDirection.rtl ? -textOffset : textOffset; if (widget.index == 0 && @@ -1936,7 +2146,7 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { Widget _buildPointer() { Widget? current; - Matrix4 _matrix4; + Matrix4 matrix4; if (widget.index == widget.pointerController!.segmentIndex && widget.pointerController!.position != null) { @@ -1945,14 +2155,17 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { current = SizedBox( width: widget.pointerSize!.width, height: widget.pointerSize!.height, - child: widget.pointerBuilder! - .call(context, widget.pointerController!.colorValue), + child: widget.pointerBuilder!.call( + context, + widget.pointerController!.colorValue, + ), ); } else { current = CustomPaint( size: widget.pointerSize!, painter: _LegendIconShape( - color: widget.pointerColor ?? + color: + widget.pointerColor ?? (Theme.of(context).brightness == Brightness.light ? const Color.fromRGBO(0, 0, 0, 0.54) : const Color.fromRGBO(255, 255, 255, 0.7)), @@ -1962,34 +2175,38 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { } if (widget.direction == Axis.horizontal) { - _matrix4 = Matrix4.identity() - ..translate( + matrix4 = + Matrix4.identity()..translate( widget.pointerController!.position!.dx * widget.segmentSize!.width - (widget.pointerSize!.width / 2), - 0.0); + ); if (_textDirection == TextDirection.rtl) { - _matrix4.invert(); + matrix4.invert(); } - current = Transform(transform: _matrix4, child: current); + current = Transform(transform: matrix4, child: current); } else { current = RotatedBox(quarterTurns: 3, child: current); - _matrix4 = Matrix4.identity() - ..translate( + matrix4 = + Matrix4.identity()..translate( 0.0, widget.pointerController!.position!.dy * widget.segmentSize!.width - - (widget.pointerSize!.width / 2)); - current = Transform(transform: _matrix4, child: current); + (widget.pointerSize!.width / 2), + ); + current = Transform(transform: matrix4, child: current); } } else { - current = widget.direction == Axis.horizontal - ? SizedBox( - height: widget.pointerSize!.height, - width: widget.pointerSize!.width) - : SizedBox( - height: widget.pointerSize!.width, - width: widget.pointerSize!.height); + current = + widget.direction == Axis.horizontal + ? SizedBox( + height: widget.pointerSize!.height, + width: widget.pointerSize!.width, + ) + : SizedBox( + height: widget.pointerSize!.width, + width: widget.pointerSize!.height, + ); } return current; @@ -2005,10 +2222,12 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { crossAxisAlignment: _getCrossAxisAlignment(), children: [ Align( - alignment: _textDirection == TextDirection.ltr - ? Alignment.centerLeft - : Alignment.centerRight, - child: _buildPointer()), + alignment: + _textDirection == TextDirection.ltr + ? Alignment.centerLeft + : Alignment.centerRight, + child: _buildPointer(), + ), Padding( // Gap between segment text and icon. padding: const EdgeInsets.only(bottom: 7.0), @@ -2017,7 +2236,7 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { color: widget.iconColor, ), ), - _getTextWidget(widget.startText, widget.text!) + _getTextWidget(widget.startText, widget.text!), ], ), ); @@ -2036,7 +2255,7 @@ class __SolidBarLegendItemState extends State<_SolidBarLegendItem> { color: widget.iconColor, ), ), - _getTextWidget(widget.startText, widget.text!) + _getTextWidget(widget.startText, widget.text!), ], ), ); @@ -2126,29 +2345,35 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { void _updateSegmentSize(double shortestSide) { if (_direction == Axis.horizontal) { - final double availableWidth = widget.padding != null - ? shortestSide - widget.padding!.horizontal - : shortestSide; - _segmentSize = widget.segmentSize == null - ? Size(availableWidth, 12.0) - : Size( - widget.segmentSize!.width > availableWidth - ? availableWidth - : widget.segmentSize!.width, - widget.segmentSize!.height); + final double availableWidth = + widget.padding != null + ? shortestSide - widget.padding!.horizontal + : shortestSide; + _segmentSize = + widget.segmentSize == null + ? Size(availableWidth, 12.0) + : Size( + widget.segmentSize!.width > availableWidth + ? availableWidth + : widget.segmentSize!.width, + widget.segmentSize!.height, + ); return; } - final double availableHeight = widget.padding != null - ? shortestSide - widget.padding!.vertical - : shortestSide; - _segmentSize = widget.segmentSize == null - ? Size(12.0, availableHeight) - : Size( - widget.segmentSize!.width, - widget.segmentSize!.height > availableHeight - ? availableHeight - : widget.segmentSize!.height); + final double availableHeight = + widget.padding != null + ? shortestSide - widget.padding!.vertical + : shortestSide; + _segmentSize = + widget.segmentSize == null + ? Size(12.0, availableHeight) + : Size( + widget.segmentSize!.width, + widget.segmentSize!.height > availableHeight + ? availableHeight + : widget.segmentSize!.height, + ); } void _collectLabelsAndColors() { @@ -2158,13 +2383,15 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { /// Creating new instance at this point, since we are modifying /// the same list during the run time. _colors = []; - _referenceArea = _direction == Axis.horizontal - ? _segmentSize.width - : _segmentSize.height; + _referenceArea = + _direction == Axis.horizontal + ? _segmentSize.width + : _segmentSize.height; if (widget.items != null) { final int length = widget.items!.length; - final double slab = _referenceArea / + final double slab = + _referenceArea / (widget.labelsPlacement == LegendLabelsPlacement.betweenItems && widget.items![0].text[0] != '{' ? length - 1 @@ -2176,9 +2403,10 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { String text; if (i == 0) { final List firstSegmentLabels = _getStartSegmentLabel(item); - text = firstSegmentLabels.length > 1 - ? firstSegmentLabels[1] - : firstSegmentLabels[0]; + text = + firstSegmentLabels.length > 1 + ? firstSegmentLabels[1] + : firstSegmentLabels[0]; } else { text = item.text; } @@ -2195,13 +2423,21 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { text = _getTrimmedText(text, i, length, slab); } else if (i < length - 1) { text = _getTrimmedText( - text, i, length, slab, widget.items![i + 1].text); + text, + i, + length, + slab, + widget.items![i + 1].text, + ); } - _labels.add(_GradientBarLabel( + _labels.add( + _GradientBarLabel( text, _getTextOffset(text, positionIndex, length - 1, slab), - _isOverlapSegmentText)); + _isOverlapSegmentText, + ), + ); } _colors.add(item.color!); } @@ -2209,7 +2445,12 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { } void _collectRageColorMapperLabels( - int i, LegendItem item, String text, double slab, int length) { + int i, + LegendItem item, + String text, + double slab, + int length, + ) { if (i == 0 && widget.labelsPlacement == LegendLabelsPlacement.betweenItems) { String startText; @@ -2229,26 +2470,47 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { if (widget.labelOverflow == LegendLabelOverflow.ellipsis) { if (widget.labelsPlacement == LegendLabelsPlacement.betweenItems) { final double textWidth = refCurrentTextWidth + refNextTextWidth; - startText = _getTrimText(startText, widget.textStyle!, slab, - _textPainter, textWidth, refNextTextWidth); + startText = _getTrimText( + startText, + widget.textStyle!, + slab, + _textPainter, + textWidth, + refNextTextWidth, + ); } } } - _labels.add(_GradientBarLabel(startText, - _getTextOffset(startText, i, length, slab), _isOverlapSegmentText)); + _labels.add( + _GradientBarLabel( + startText, + _getTextOffset(startText, i, length, slab), + _isOverlapSegmentText, + ), + ); } else if (i < length - 1) { text = _getTrimmedText(text, i, length, slab, widget.items![i + 1].text); } // 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)); + _labels.add( + _GradientBarLabel( + text, + _getTextOffset(text, i + 1, length, slab), + _isOverlapSegmentText, + ), + ); } - String _getTrimmedText(String currentText, int index, int length, double slab, - [String? nextText]) { + String _getTrimmedText( + String currentText, + int index, + int length, + double slab, [ + String? nextText, + ]) { if (widget.labelOverflow == LegendLabelOverflow.visible || currentText.isEmpty || (nextText != null && nextText.isEmpty) || @@ -2273,18 +2535,27 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { } } else { refCurrentTextWidth = _getTextWidth(currentText) / 2; - refNextTextWidth = index + 1 == length - 1 && - widget.edgeLabelsPlacement == LegendEdgeLabelsPlacement.inside - ? _getTextWidth(nextText) - : _getTextWidth(nextText) / 2; + refNextTextWidth = + index + 1 == length - 1 && + widget.edgeLabelsPlacement == + LegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; } _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > slab; if (widget.labelOverflow == LegendLabelOverflow.ellipsis && _isOverlapSegmentText) { if (widget.labelsPlacement == LegendLabelsPlacement.betweenItems) { final double textWidth = refCurrentTextWidth + refNextTextWidth; - return _getTrimText(currentText, widget.textStyle!, slab, - _textPainter, textWidth, refNextTextWidth, isLastInsideItem); + return _getTrimText( + currentText, + widget.textStyle!, + slab, + _textPainter, + textWidth, + refNextTextWidth, + isLastInsideItem, + ); } } } else if (_direction == Axis.horizontal && @@ -2293,7 +2564,12 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { _isOverlapSegmentText = textWidth > slab; if (_isOverlapSegmentText) { return _getTrimText( - currentText, widget.textStyle!, slab, _textPainter, textWidth); + currentText, + widget.textStyle!, + slab, + _textPainter, + textWidth, + ); } } @@ -2317,41 +2593,57 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { return splitText; } else { - return [(item.text)]; + return [item.text]; } } Offset _getTextOffset( - String? text, int positionIndex, int length, double slab) { + String? text, + int positionIndex, + int length, + double slab, + ) { _textPainter.text = TextSpan(text: text, style: widget.textStyle); _textPainter.layout(); final bool canAdjustLabelToCenter = widget.edgeLabelsPlacement == LegendEdgeLabelsPlacement.center && - (positionIndex == 0 || positionIndex == length) || - (positionIndex > 0 && positionIndex < length) || - widget.labelsPlacement == LegendLabelsPlacement.onItem; + (positionIndex == 0 || positionIndex == length) || + (positionIndex > 0 && positionIndex < length) || + widget.labelsPlacement == LegendLabelsPlacement.onItem; if (_direction == Axis.horizontal) { return _getHorizontalOffset( - canAdjustLabelToCenter, positionIndex, slab, length); + canAdjustLabelToCenter, + positionIndex, + slab, + length, + ); } else { - final double referenceTextWidth = canAdjustLabelToCenter - ? _textPainter.height / 2 - : (positionIndex == length ? _textPainter.height : 0.0); + final double referenceTextWidth = + canAdjustLabelToCenter + ? _textPainter.height / 2 + : (positionIndex == length ? _textPainter.height : 0.0); if (widget.labelsPlacement == LegendLabelsPlacement.betweenItems) { return Offset(0.0, slab * positionIndex - referenceTextWidth); } return Offset( - 0.0, (slab * positionIndex) - referenceTextWidth - slab / 2); + 0.0, + (slab * positionIndex) - referenceTextWidth - slab / 2, + ); } } Offset _getHorizontalOffset( - bool canAdjustLabelToCenter, int positionIndex, double slab, int length) { + bool canAdjustLabelToCenter, + int positionIndex, + double slab, + int length, + ) { if (_isRTL) { - final double referenceTextWidth = canAdjustLabelToCenter - ? -_textPainter.width / 2 - : (positionIndex == 0 ? -_textPainter.width : 0.0); + final double referenceTextWidth = + canAdjustLabelToCenter + ? -_textPainter.width / 2 + : (positionIndex == 0 ? -_textPainter.width : 0.0); double dx = _segmentSize.width - (slab * positionIndex - referenceTextWidth); @@ -2364,25 +2656,30 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { return Offset(dx + slab / 2 - _textPainter.width / 2, 0.0); } - final double referenceTextWidth = canAdjustLabelToCenter - ? _textPainter.width / 2 - : (positionIndex == length ? _textPainter.width : 0.0); + final double referenceTextWidth = + canAdjustLabelToCenter + ? _textPainter.width / 2 + : (positionIndex == length ? _textPainter.width : 0.0); if (widget.labelsPlacement == LegendLabelsPlacement.betweenItems) { return Offset(slab * positionIndex - referenceTextWidth, 0.0); } return Offset( - slab * positionIndex - _textPainter.width / 2 - slab / 2, 0.0); + slab * positionIndex - _textPainter.width / 2 - slab / 2, + 0.0, + ); } Widget _buildGradientBar() { return _direction == Axis.horizontal ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _getChildren()) + crossAxisAlignment: CrossAxisAlignment.start, + children: _getChildren(), + ) : Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: _getChildren()); + crossAxisAlignment: CrossAxisAlignment.start, + children: _getChildren(), + ); } List _getChildren() { @@ -2417,20 +2714,27 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { height: _segmentSize.height, decoration: BoxDecoration( gradient: LinearGradient( - begin: startAlignment, end: endAlignment, colors: _colors), + begin: startAlignment, + end: endAlignment, + colors: _colors, + ), ), ), SizedBox( - width: _direction == Axis.vertical ? 7.0 : 0.0, - height: _direction == Axis.horizontal ? 7.0 : 0.0), + width: _direction == Axis.vertical ? 7.0 : 0.0, + height: _direction == Axis.horizontal ? 7.0 : 0.0, + ), SizedBox( - width: labelBoxWidth, height: labelBoxHeight, child: _getLabels()), + width: labelBoxWidth, + height: labelBoxHeight, + child: _getLabels(), + ), ]; } Widget _buildPointer(ThemeData themeData) { Widget? current; - Matrix4 _matrix4; + Matrix4 matrix4; if (widget.pointerController!.position != null) { if (widget.pointerBuilder != null && @@ -2438,14 +2742,17 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { current = SizedBox( width: widget.pointerSize!.width, height: widget.pointerSize!.height, - child: widget.pointerBuilder! - .call(context, widget.pointerController!.colorValue), + child: widget.pointerBuilder!.call( + context, + widget.pointerController!.colorValue, + ), ); } else { current = CustomPaint( size: widget.pointerSize!, painter: _LegendIconShape( - color: widget.pointerColor ?? + color: + widget.pointerColor ?? (themeData.brightness == Brightness.light ? const Color.fromRGBO(0, 0, 0, 0.54) : const Color.fromRGBO(255, 255, 255, 0.7)), @@ -2455,32 +2762,36 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { } if (_direction == Axis.horizontal) { - _matrix4 = Matrix4.identity() - ..translate( + matrix4 = + Matrix4.identity()..translate( widget.pointerController!.position!.dx * _segmentSize.width - (widget.pointerSize!.width / 2), - 0.0); + ); if (_isRTL) { - _matrix4.invert(); + matrix4.invert(); } - current = Transform(transform: _matrix4, child: current); + current = Transform(transform: matrix4, child: current); } else { current = RotatedBox(quarterTurns: 3, child: current); - _matrix4 = Matrix4.identity() - ..translate( + matrix4 = + Matrix4.identity()..translate( 0.0, widget.pointerController!.position!.dy * _segmentSize.height - - (widget.pointerSize!.width / 2)); - current = Transform(transform: _matrix4, child: current); + (widget.pointerSize!.width / 2), + ); + current = Transform(transform: matrix4, child: current); } } else { - current = _direction == Axis.horizontal - ? SizedBox( - height: widget.pointerSize!.height, - width: widget.pointerSize!.width) - : SizedBox( - height: widget.pointerSize!.width, - width: widget.pointerSize!.height); + current = + _direction == Axis.horizontal + ? SizedBox( + height: widget.pointerSize!.height, + width: widget.pointerSize!.width, + ) + : SizedBox( + height: widget.pointerSize!.width, + width: widget.pointerSize!.height, + ); } return current; @@ -2538,29 +2849,33 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { TextDirection textDirection = Directionality.of(context); _isRTL = textDirection == TextDirection.rtl; _textPainter = TextPainter( - textDirection: TextDirection.ltr, - textScaleFactor: MediaQuery.of(context).textScaleFactor); - _direction = widget.direction ?? + textDirection: TextDirection.ltr, + textScaler: MediaQuery.of(context).textScaler, + ); + _direction = + widget.direction ?? (widget.position == LegendPosition.top || widget.position == LegendPosition.bottom ? Axis.horizontal : Axis.vertical); - textDirection = _isRTL - ? (_direction == Axis.vertical ? TextDirection.ltr : textDirection) - : textDirection; + 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(); - }), + 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(); + }, + ), ); return child; @@ -2568,8 +2883,11 @@ class _GradientBarLegendState extends State<_GradientBarLegend> { } class _GradientBarLabel { - _GradientBarLabel(this.label, - [this.offset = Offset.zero, this.isOverlapping = false]); + _GradientBarLabel( + this.label, [ + this.offset = Offset.zero, + this.isOverlapping = false, + ]); String label; Offset offset; @@ -2593,7 +2911,7 @@ class PointerController extends ChangeNotifier { dynamic colorValue; /// Specifies the hovered segment index to render the marker pointer - /// on that particulat segment in case of Solid bar legend type. + /// on that particular segment in case of Solid bar legend type. int? get segmentIndex => _segmentIndex; int? _segmentIndex; set segmentIndex(int? value) { @@ -2604,7 +2922,7 @@ class PointerController extends ChangeNotifier { _segmentIndex = value; } - /// Sepcifies the previous hovered segment index to + /// Specifies the previous hovered segment index to /// rebuild the older solid bar segment without marker pointer. int? previousSegmentIndex; } 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 68f026de5..fa251b8ab 100644 --- a/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart +++ b/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart @@ -160,6 +160,166 @@ abstract class SfLocalizations { /// Label that is displayed the rowsPerPages of datapager. String get rowsPerPageDataPagerLabel; + /// The label that is displayed in the filter view in SfDataGrid for + /// `Equals` option in drop down widget. + String get equalsDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Does Not Equal` option in drop down widget. + String get doesNotEqualDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Begins With` option in drop down widget. + String get beginsWithDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Ends With` option in drop down widget. + String get endsWithDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Does Not Begin With` option in drop down widget. + String get doesNotBeginWithDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Does Not End With` option in drop down widget. + String get doesNotEndWithDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Contains` option in drop down widget. + String get containsDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Does Not Contain` option in drop down widget. + String get doesNotContainDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Empty` option in drop down widget. + String get emptyDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Not Empty` option in drop down widget. + String get notEmptyDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Null` option in drop down widget. + String get nullDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Not Null` option in drop down widget. + String get notNullDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Before` option in drop down widget. + String get beforeDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Before Or Equal` option in drop down widget. + String get beforeOrEqualDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `After` option in drop down widget. + String get afterDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `After Or Equal` option in drop down widget. + String get afterOrEqualDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Less Than` option in drop down widget. + String get lessThanDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Less Than Or Equal` option in drop down widget. + String get lessThanOrEqualDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Greater Than` option in drop down widget. + String get greaterThanDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Greater Than Or Equal` option in drop down widget. + String get greaterThanOrEqualDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Sort Smallest to Largest` option in the popup menu. + String get sortSmallestToLargestDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// ` Sort Largest to Smallest ` option in the popup menu. + String get sortLargestToSmallestDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Sort A to Z` option in the popup menu. + String get sortAToZDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Sort Z to A` option in the popup menu. + String get sortZToADataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Sort Oldest to Newest` option in the popup menu.. + String get sortOldestToNewestDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Sort Newest to Oldest` option in the popup menu. + String get sortNewestToOldestDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Clear Filter` text in `Clear Filter From` option in the popup menu. + String get clearFilterDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `From` text in `Clear Filter From` option in the popup menu. + String get fromDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Text Filters` option in the popup menu. + String get textFiltersDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Number Filters` option in the popup menu. + String get numberFiltersDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Date Filters` option in the popup menu. + String get dateFiltersDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Search` option in the popup menu. + String get searchDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `No matches` option in the popup menu. + String get noMatchesDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `OK ` option in the popup menu. + String get okDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Cancel` option in the popup menu. + String get cancelDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Show rows where` option in the popup menu. + String get showRowsWhereDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `And` option in the popup menu. + String get andDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Or` option in the popup menu. + String get orDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Select All` option in the popup menu. + String get selectAllDataGridFilteringLabel; + + /// The label that is displayed in the filter view in SfDataGrid for + /// `Sort and Filter` option in the popup menu. + String get sortAndFilterDataGridFilteringLabel; + /// Label that is displayed in the bookmark view header of PdfViewer. String get pdfBookmarksLabel; @@ -192,6 +352,20 @@ abstract class SfLocalizations { /// the CANCEL confirmation button. String get pdfPaginationDialogCancelLabel; + /// Label that is displayed in the hyperlink dialog header of PdfViewer. + String get pdfHyperlinkLabel; + + /// Label that is displayed in the url of the hyperlink. + String get pdfHyperlinkContentLabel; + + /// Label that is displayed in the hyperlink dialog of PdfViewer to represent + /// the OPEN confirmation button. + String get pdfHyperlinkDialogOpenLabel; + + /// Label that is displayed in the hyperlink dialog of PdfViewer to represent + /// the CANCEL confirmation button. + String get pdfHyperlinkDialogCancelLabel; + /// Label that is displayed in the header of password dialog in PdfViewer String get passwordDialogHeaderTextLabel; @@ -214,6 +388,41 @@ abstract class SfLocalizations { /// the CANCEL confirmation button. String get pdfPasswordDialogCancelLabel; + /// Label that is displayed in the header of signature pad dialog in PdfViewer + String get pdfSignaturePadDialogHeaderTextLabel; + + /// Label that is displayed in the signature pad dialog in PdfViewer to + /// represent the Pen Color text + String get pdfSignaturePadDialogPenColorLabel; + + /// Label that is displayed in the signature pad dialog of PdfViewer to + /// represent the CLEAR confirmation button. + String get pdfSignaturePadDialogClearLabel; + + /// Label that is displayed in the signature pad dialog of PdfViewer to + /// represent the SAVE confirmation button. + String get pdfSignaturePadDialogSaveLabel; + + /// Label that is displayed in the text selection context menu of + /// PdfViewer for copying the selected text. + String get pdfTextSelectionMenuCopyLabel; + + /// Label that is displayed in the text selection context menu of + /// PdfViewer for highlighting the selected text. + String get pdfTextSelectionMenuHighlightLabel; + + /// Label that is displayed in the text selection context menu of + /// PdfViewer for striking out the selected text. + String get pdfTextSelectionMenuStrikethroughLabel; + + /// Label that is displayed in the text selection context menu of + /// PdfViewer for underlining the selected text. + String get pdfTextSelectionMenuUnderlineLabel; + + /// Label that is displayed in the text selection context menu of + /// PdfViewer for underlining the selected text with squiggly style. + String get pdfTextSelectionMenuSquigglyLabel; + /// The label is displayed as the text for the legend in the cartesian chart. /// When the name of the series is not specified, then this label with the /// series count is displayed as a legend. @@ -405,6 +614,131 @@ class _DefaultLocalizations implements SfLocalizations { @override String get rowsPerPageDataPagerLabel => 'Rows per page'; + @override + String get afterDataGridFilteringLabel => 'After'; + + @override + String get afterOrEqualDataGridFilteringLabel => 'After Or Equal'; + + @override + String get beforeDataGridFilteringLabel => 'Before'; + + @override + String get beforeOrEqualDataGridFilteringLabel => 'Before Or Equal'; + + @override + String get beginsWithDataGridFilteringLabel => 'Begins With'; + + @override + String get containsDataGridFilteringLabel => 'Contains'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => 'Does Not Begin With'; + + @override + String get doesNotContainDataGridFilteringLabel => 'Does Not Contain'; + + @override + String get doesNotEndWithDataGridFilteringLabel => 'Does Not End With'; + + @override + String get doesNotEqualDataGridFilteringLabel => 'Does Not Equal'; + + @override + String get emptyDataGridFilteringLabel => 'Empty'; + + @override + String get endsWithDataGridFilteringLabel => 'Ends With'; + + @override + String get equalsDataGridFilteringLabel => 'Equals'; + + @override + String get greaterThanDataGridFilteringLabel => 'Greater Than'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + 'Greater Than Or Equal'; + + @override + String get lessThanDataGridFilteringLabel => 'Less Than'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => 'Less Than Or Equal'; + + @override + String get notEmptyDataGridFilteringLabel => 'Not Empty'; + + @override + String get notNullDataGridFilteringLabel => 'Not Null'; + + @override + String get nullDataGridFilteringLabel => 'Null'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + 'Sort Smallest to Largest'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + 'Sort Largest to Smallest'; + + @override + String get sortAToZDataGridFilteringLabel => 'Sort A to Z'; + + @override + String get sortZToADataGridFilteringLabel => 'Sort Z to A'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + 'Sort Oldest to Newest'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + 'Sort Newest to Oldest'; + + @override + String get clearFilterDataGridFilteringLabel => 'Clear Filter'; + + @override + String get fromDataGridFilteringLabel => 'From'; + + @override + String get textFiltersDataGridFilteringLabel => 'Text Filters'; + + @override + String get numberFiltersDataGridFilteringLabel => 'Number Filters'; + + @override + String get dateFiltersDataGridFilteringLabel => 'Date Filters'; + + @override + String get searchDataGridFilteringLabel => 'Search'; + + @override + String get noMatchesDataGridFilteringLabel => 'No matches'; + + @override + String get okDataGridFilteringLabel => 'OK'; + + @override + String get cancelDataGridFilteringLabel => 'Cancel'; + + @override + String get showRowsWhereDataGridFilteringLabel => 'Show rows where'; + + @override + String get andDataGridFilteringLabel => 'And'; + + @override + String get orDataGridFilteringLabel => 'Or'; + + @override + String get selectAllDataGridFilteringLabel => 'Select All'; + + @override + String get sortAndFilterDataGridFilteringLabel => 'Sort and Filter'; + @override String get pdfBookmarksLabel => 'Bookmarks'; @@ -429,6 +763,18 @@ class _DefaultLocalizations implements SfLocalizations { @override String get pdfPaginationDialogCancelLabel => 'CANCEL'; + @override + String get pdfHyperlinkLabel => 'Open Web Page'; + + @override + String get pdfHyperlinkContentLabel => 'Do you want to open the page at'; + + @override + String get pdfHyperlinkDialogOpenLabel => 'OPEN'; + + @override + String get pdfHyperlinkDialogCancelLabel => 'CANCEL'; + @override String get passwordDialogHeaderTextLabel => 'Password Protected'; @@ -448,6 +794,33 @@ class _DefaultLocalizations implements SfLocalizations { @override String get pdfPasswordDialogCancelLabel => 'CANCEL'; + @override + String get pdfSignaturePadDialogHeaderTextLabel => 'Draw your signature'; + + @override + String get pdfSignaturePadDialogPenColorLabel => 'Pen Color'; + + @override + String get pdfSignaturePadDialogClearLabel => 'CLEAR'; + + @override + String get pdfSignaturePadDialogSaveLabel => 'SAVE'; + + @override + String get pdfTextSelectionMenuCopyLabel => 'Copy'; + + @override + String get pdfTextSelectionMenuHighlightLabel => 'Highlight'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => 'Strikethrough'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => 'Underline'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => 'Squiggly'; + @override String get series => 'Series'; diff --git a/packages/syncfusion_flutter_core/lib/src/slider_controller.dart b/packages/syncfusion_flutter_core/lib/src/slider_controller.dart index 692c9a82f..83ae2ea33 100644 --- a/packages/syncfusion_flutter_core/lib/src/slider_controller.dart +++ b/packages/syncfusion_flutter_core/lib/src/slider_controller.dart @@ -190,12 +190,12 @@ class RangeController extends DiagnosticableTree with ChangeNotifier { /// /// [start] and [end] RangeController({@required dynamic start, @required dynamic end}) - : assert(start != null), - assert(end != null), - _previousStart = start, - _previousEnd = end, - _start = start, - _end = end; + : assert(start != null), + assert(end != null), + _previousStart = start, + _previousEnd = end, + _start = start, + _end = end; /// The current selected start value. /// diff --git a/packages/syncfusion_flutter_core/lib/src/theme/assistview_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/assistview_theme.dart new file mode 100644 index 000000000..2546db57c --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/theme/assistview_theme.dart @@ -0,0 +1,1542 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'theme_widget.dart'; + +/// Applies a theme to descendant Syncfusion assist widgets. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfAIAssistViewTheme( +/// data: SfAIAssistViewThemeData( +/// brightness: Brightness.dark, +/// ), +/// child: SfAIAssistView() +/// ), +/// ); +/// } +/// ``` +class SfAIAssistViewTheme extends InheritedTheme { + /// Applies the given theme [data] to [child]. + /// + /// The [data] and [child] arguments must not be null. + const SfAIAssistViewTheme({ + super.key, + required this.data, + required super.child, + }); + + /// Specifies the color and typography values for descendant assist widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// brightness: Brightness.dark, + /// ), + /// child: SfAIAssistView() + /// ), + /// ); + /// } + /// ``` + final SfAIAssistViewThemeData data; + + /// The data from the closest [SfAIAssistViewTheme] instance + /// that encloses the given context. + /// + /// Defaults to [SfThemeData.assistThemeData] if there is no + /// [SfAIAssistViewTheme] in the given build context. + static SfAIAssistViewThemeData of(BuildContext context) { + final SfAIAssistViewTheme? assistTheme = + context.dependOnInheritedWidgetOfExactType(); + return assistTheme?.data ?? SfTheme.of(context).assistThemeData; + } + + @override + bool updateShouldNotify(SfAIAssistViewTheme oldWidget) => + data != oldWidget.data; + + @override + Widget wrap(BuildContext context, Widget child) { + final SfAIAssistViewTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : SfAIAssistViewTheme(data: data, child: child); + } +} + +/// Holds the color and typography values for a [SfAIAssistViewTheme]. +/// Use this class to configure a [SfAIAssistViewTheme] widget, or to set the +/// [SfThemeData.assistThemeData] for a [SfTheme] widget. +/// +/// To obtain the current theme, use [SfAIAssistViewTheme.of]. +@immutable +class SfAIAssistViewThemeData with Diagnosticable { + /// Create a [SfAIAssistViewThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to create + /// intermediate themes based on two themes created with the + /// [SfAIAssistViewThemeData] constructor. + const SfAIAssistViewThemeData({ + this.actionButtonForegroundColor, + this.actionButtonBackgroundColor, + this.actionButtonFocusColor, + this.actionButtonHoverColor, + this.actionButtonSplashColor, + this.actionButtonDisabledForegroundColor, + this.actionButtonDisabledBackgroundColor, + this.actionButtonElevation = 0.0, + this.actionButtonFocusElevation = 0.0, + this.actionButtonHoverElevation = 0.0, + this.actionButtonHighlightElevation = 0.0, + this.actionButtonDisabledElevation = 0.0, + this.actionButtonMouseCursor, + this.actionButtonShape, + this.requestAvatarBackgroundColor, + this.responseAvatarBackgroundColor, + this.requestMessageBackgroundColor, + this.responseMessageBackgroundColor, + this.editorTextStyle, + this.requestContentTextStyle, + this.responseContentTextStyle, + this.requestPrimaryHeaderTextStyle, + this.responsePrimaryHeaderTextStyle, + this.requestSecondaryHeaderTextStyle, + this.responseSecondaryHeaderTextStyle, + this.suggestionItemTextStyle, + this.requestMessageShape, + this.responseMessageShape, + this.suggestionBackgroundColor, + this.suggestionBackgroundShape, + this.suggestionItemBackgroundColor, + this.suggestionItemShape, + this.responseToolbarBackgroundColor, + this.responseToolbarBackgroundShape, + this.responseToolbarItemBackgroundColor, + this.responseToolbarItemShape, + }); + + /// Returns a new instance of [SfAIAssistViewThemeData.raw] for the given + /// values. + /// + /// If any of the values are null, the default values will be set. + factory SfAIAssistViewThemeData.raw({ + Color? actionButtonForegroundColor, + Color? actionButtonBackgroundColor, + Color? actionButtonFocusColor, + Color? actionButtonHoverColor, + Color? actionButtonSplashColor, + Color? actionButtonDisabledForegroundColor, + Color? actionButtonDisabledBackgroundColor, + double? actionButtonElevation, + double? actionButtonFocusElevation, + double? actionButtonHoverElevation, + double? actionButtonDisabledElevation, + double? actionButtonHighlightElevation, + ShapeBorder? actionButtonShape, + MouseCursor? actionButtonMouseCursor, + Color? requestAvatarBackgroundColor, + Color? responseAvatarBackgroundColor, + Color? requestMessageBackgroundColor, + Color? responseMessageBackgroundColor, + TextStyle? editorTextStyle, + TextStyle? requestContentTextStyle, + TextStyle? responseContentTextStyle, + TextStyle? requestPrimaryHeaderTextStyle, + TextStyle? responsePrimaryHeaderTextStyle, + TextStyle? requestSecondaryHeaderTextStyle, + TextStyle? responseSecondaryHeaderTextStyle, + WidgetStateProperty? suggestionItemTextStyle, + ShapeBorder? requestMessageShape, + ShapeBorder? responseMessageShape, + Color? suggestionBackgroundColor, + ShapeBorder? suggestionBackgroundShape, + WidgetStateProperty? suggestionItemBackgroundColor, + WidgetStateProperty? suggestionItemShape, + Color? responseToolbarBackgroundColor, + ShapeBorder? responseToolbarBackgroundShape, + WidgetStateProperty? responseToolbarItemBackgroundColor, + WidgetStateProperty? responseToolbarItemShape, + }) { + return SfAIAssistViewThemeData( + actionButtonForegroundColor: actionButtonForegroundColor, + actionButtonBackgroundColor: actionButtonBackgroundColor, + actionButtonFocusColor: actionButtonFocusColor, + actionButtonHoverColor: actionButtonHoverColor, + actionButtonSplashColor: actionButtonSplashColor, + actionButtonDisabledForegroundColor: actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor: actionButtonDisabledBackgroundColor, + actionButtonElevation: actionButtonElevation ?? 0.0, + actionButtonFocusElevation: actionButtonFocusElevation ?? 0.0, + actionButtonHoverElevation: actionButtonHoverElevation ?? 0.0, + actionButtonDisabledElevation: actionButtonDisabledElevation ?? 0.0, + actionButtonHighlightElevation: actionButtonHighlightElevation ?? 0.0, + actionButtonShape: actionButtonShape, + actionButtonMouseCursor: actionButtonMouseCursor, + requestAvatarBackgroundColor: requestAvatarBackgroundColor, + responseAvatarBackgroundColor: responseAvatarBackgroundColor, + requestMessageBackgroundColor: requestMessageBackgroundColor, + responseMessageBackgroundColor: responseMessageBackgroundColor, + editorTextStyle: editorTextStyle, + requestContentTextStyle: requestContentTextStyle, + responseContentTextStyle: responseContentTextStyle, + requestPrimaryHeaderTextStyle: requestPrimaryHeaderTextStyle, + responsePrimaryHeaderTextStyle: responsePrimaryHeaderTextStyle, + requestSecondaryHeaderTextStyle: requestSecondaryHeaderTextStyle, + responseSecondaryHeaderTextStyle: responseSecondaryHeaderTextStyle, + suggestionItemTextStyle: suggestionItemTextStyle, + requestMessageShape: requestMessageShape, + responseMessageShape: responseMessageShape, + suggestionBackgroundColor: suggestionBackgroundColor, + suggestionBackgroundShape: suggestionBackgroundShape, + suggestionItemBackgroundColor: suggestionItemBackgroundColor, + suggestionItemShape: suggestionItemShape, + responseToolbarBackgroundColor: responseToolbarBackgroundColor, + responseToolbarBackgroundShape: responseToolbarBackgroundShape, + responseToolbarItemBackgroundColor: responseToolbarItemBackgroundColor, + responseToolbarItemShape: responseToolbarItemShape, + ); + } + + /// Color for the foreground elements (text or icons) of action buttons. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonForegroundColor: Colors.white, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonForegroundColor; + + /// Color for the background of action buttons. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonBackgroundColor: Colors.blue, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonBackgroundColor; + + /// Color for the action button when it is focused. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonFocusColor: Colors.green, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonFocusColor; + + /// Color for the action button when hovered over. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonHoverColor: Colors.orange, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonHoverColor; + + /// Color for the splash effect of action buttons. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonSplashColor: Colors.red, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonSplashColor; + + /// Color for the foreground elements (text or icons) of + /// action buttons when disabled. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonDisabledForegroundColor: Colors.grey, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonDisabledForegroundColor; + + /// Color for the background of action buttons when disabled. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonDisabledBackgroundColor: Colors.grey[400], + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonDisabledBackgroundColor; + + /// Depth of the action button's shadow in its default state. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonElevation: 4.0, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonElevation; + + /// Depth of the action button's shadow when focused. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonFocusElevation: 8.0, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonFocusElevation; + + /// Depth of the action button's shadow when hovered over. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonHoverElevation: 6.0, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonHoverElevation; + + /// Depth of the action button's shadow when disabled. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonDisabledElevation: 2.0, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonDisabledElevation; + + /// Depth of the action button's shadow when highlighted. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonHighlightElevation: 12.0, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonHighlightElevation; + + /// Shape of the action button. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? actionButtonShape; + + /// Customizes the cursor that appears when you hover + /// over the button. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// actionButtonMouseCursor: SystemMouseCursors.click, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final MouseCursor? actionButtonMouseCursor; + + /// Background color of request avatar. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// requestAvatarBackgroundColor: Colors.blueAccent, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? requestAvatarBackgroundColor; + + /// Background color of response avatar. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseAvatarBackgroundColor: Colors.blue, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? responseAvatarBackgroundColor; + + /// Background color of request message bubbles. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// requestMessageBackgroundColor: Colors.blueAccent, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? requestMessageBackgroundColor; + + /// Background color of response message bubbles. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseMessageBackgroundColor: Colors.grey[300], + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? responseMessageBackgroundColor; + + /// Text style for the message editor. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// editorTextStyle: TextStyle( + /// fontSize: 16.0, + /// color: Colors.black, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? editorTextStyle; + + /// Text style for request message content. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// requestContentTextStyle: TextStyle( + /// fontSize: 14.0, + /// color: Colors.white, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? requestContentTextStyle; + + /// Text style for response message content. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseContentTextStyle: TextStyle( + /// fontSize: 14.0, + /// color: Colors.black, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? responseContentTextStyle; + + /// Text style for the primary header of request messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// requestPrimaryHeaderTextStyle: TextStyle( + /// fontSize: 12.0, + /// fontWeight: FontWeight.bold, + /// color: Colors.white, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? requestPrimaryHeaderTextStyle; + + /// Text style for the primary header of response messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responsePrimaryHeaderTextStyle: TextStyle( + /// fontSize: 12.0, + /// fontWeight: FontWeight.bold, + /// color: Colors.black, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? responsePrimaryHeaderTextStyle; + + /// Text style for the secondary header of request messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// requestSecondaryHeaderTextStyle: TextStyle( + /// fontSize: 10.0, + /// color: Colors.white70, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? requestSecondaryHeaderTextStyle; + + /// Text style for the secondary header of response messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseSecondaryHeaderTextStyle: TextStyle( + /// fontSize: 10.0, + /// color: Colors.black54, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? responseSecondaryHeaderTextStyle; + + /// Text style for the suggestion items. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// suggestionItemTextStyle: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return TextStyle(fontSize: 14.0, color: Colors.blue); + /// } + /// return TextStyle(fontSize: 15.0, color: Colors.green); + /// }, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? suggestionItemTextStyle; + + /// Shape of the request message bubble. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// requestMessageShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? requestMessageShape; + + /// Shape of the response message bubble. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseMessageShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? responseMessageShape; + + /// Background color of the suggestion area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// suggestionBackgroundColor: Colors.blue, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? suggestionBackgroundColor; + + /// Shape of the suggestion area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// suggestionBackgroundShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? suggestionBackgroundShape; + + /// Color of the suggestion item background, which can vary based on the + /// widget's state. + /// This uses a [WidgetStateProperty] to allow state-specific changes. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// suggestionItemBackgroundColor: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.blueAccent; + /// } + /// return Colors.blue; + /// }, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? suggestionItemBackgroundColor; + + /// Shape of the suggestion item, which can vary based on the widget's state. + /// This uses a [WidgetStateProperty] to allow state-specific changes. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// suggestionItemShape: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ); + /// }, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? suggestionItemShape; + + /// Background color of the footer area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseToolbarBackgroundColor: Colors.blue, + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final Color? responseToolbarBackgroundColor; + + /// Shape of the footer area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseToolbarBackgroundShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? responseToolbarBackgroundShape; + + /// Color of the footer item background, which can vary based on the + /// widget's state. + /// This uses a [WidgetStateProperty] to allow state-specific changes. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseToolbarItemBackgroundColor: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.blueAccent; + /// } + /// return Colors.blue; + /// }, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? responseToolbarItemBackgroundColor; + + /// Shape of the footer item, which can vary based on the widget's state. + /// This uses a [WidgetStateProperty] to allow state-specific changes. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfAIAssistViewTheme( + /// data: SfAIAssistViewThemeData( + /// responseToolbarItemShape: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ); + /// }, + /// ), + /// ), + /// child: SfAIAssistView(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? responseToolbarItemShape; + + SfAIAssistViewThemeData copyWith({ + Color? actionButtonForegroundColor, + Color? actionButtonBackgroundColor, + Color? actionButtonFocusColor, + Color? actionButtonHoverColor, + Color? actionButtonSplashColor, + Color? actionButtonDisabledForegroundColor, + Color? actionButtonDisabledBackgroundColor, + double? actionButtonElevation, + double? actionButtonFocusElevation, + double? actionButtonHoverElevation, + double? actionButtonDisabledElevation, + double? actionButtonHighlightElevation, + ShapeBorder? actionButtonShape, + MouseCursor? actionButtonMouseCursor, + Color? requestAvatarBackgroundColor, + Color? responseAvatarBackgroundColor, + Color? requestMessageBackgroundColor, + Color? responseMessageBackgroundColor, + TextStyle? editorTextStyle, + TextStyle? requestContentTextStyle, + TextStyle? responseContentTextStyle, + TextStyle? requestPrimaryHeaderTextStyle, + TextStyle? responsePrimaryHeaderTextStyle, + TextStyle? requestSecondaryHeaderTextStyle, + TextStyle? responseSecondaryHeaderTextStyle, + WidgetStateProperty? suggestionItemTextStyle, + ShapeBorder? requestMessageShape, + ShapeBorder? responseMessageShape, + Color? suggestionBackgroundColor, + ShapeBorder? suggestionBackgroundShape, + WidgetStateProperty? suggestionItemBackgroundColor, + WidgetStateProperty? suggestionItemShape, + Color? responseToolbarBackgroundColor, + ShapeBorder? responseToolbarBackgroundShape, + WidgetStateProperty? responseToolbarItemBackgroundColor, + WidgetStateProperty? responseToolbarItemShape, + }) { + return SfAIAssistViewThemeData.raw( + actionButtonForegroundColor: + actionButtonForegroundColor ?? this.actionButtonForegroundColor, + actionButtonBackgroundColor: + actionButtonBackgroundColor ?? this.actionButtonBackgroundColor, + actionButtonFocusColor: + actionButtonFocusColor ?? this.actionButtonFocusColor, + actionButtonHoverColor: + actionButtonHoverColor ?? this.actionButtonHoverColor, + actionButtonSplashColor: + actionButtonSplashColor ?? this.actionButtonSplashColor, + actionButtonDisabledForegroundColor: + actionButtonDisabledForegroundColor ?? + this.actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor: + actionButtonDisabledBackgroundColor ?? + this.actionButtonDisabledBackgroundColor, + actionButtonElevation: + actionButtonElevation ?? this.actionButtonElevation, + actionButtonFocusElevation: + actionButtonFocusElevation ?? this.actionButtonFocusElevation, + actionButtonHoverElevation: + actionButtonHoverElevation ?? this.actionButtonHoverElevation, + actionButtonDisabledElevation: + actionButtonDisabledElevation ?? this.actionButtonDisabledElevation, + actionButtonHighlightElevation: + actionButtonHighlightElevation ?? this.actionButtonHighlightElevation, + actionButtonShape: actionButtonShape ?? this.actionButtonShape, + actionButtonMouseCursor: + actionButtonMouseCursor ?? this.actionButtonMouseCursor, + requestAvatarBackgroundColor: + requestAvatarBackgroundColor ?? this.requestAvatarBackgroundColor, + responseAvatarBackgroundColor: + responseAvatarBackgroundColor ?? this.responseAvatarBackgroundColor, + requestMessageBackgroundColor: + requestMessageBackgroundColor ?? this.requestMessageBackgroundColor, + responseMessageBackgroundColor: + responseMessageBackgroundColor ?? this.responseMessageBackgroundColor, + editorTextStyle: editorTextStyle ?? this.editorTextStyle, + requestContentTextStyle: + requestContentTextStyle ?? this.requestContentTextStyle, + responseContentTextStyle: + responseContentTextStyle ?? this.responseContentTextStyle, + requestPrimaryHeaderTextStyle: + requestPrimaryHeaderTextStyle ?? this.requestPrimaryHeaderTextStyle, + responsePrimaryHeaderTextStyle: + responsePrimaryHeaderTextStyle ?? this.responsePrimaryHeaderTextStyle, + requestSecondaryHeaderTextStyle: + requestSecondaryHeaderTextStyle ?? + this.requestSecondaryHeaderTextStyle, + responseSecondaryHeaderTextStyle: + responseSecondaryHeaderTextStyle ?? + this.responseSecondaryHeaderTextStyle, + suggestionItemTextStyle: + suggestionItemTextStyle ?? this.suggestionItemTextStyle, + requestMessageShape: requestMessageShape ?? this.requestMessageShape, + responseMessageShape: responseMessageShape ?? this.responseMessageShape, + suggestionBackgroundColor: + suggestionBackgroundColor ?? this.suggestionBackgroundColor, + suggestionBackgroundShape: + suggestionBackgroundShape ?? this.suggestionBackgroundShape, + suggestionItemBackgroundColor: + suggestionItemBackgroundColor ?? this.suggestionItemBackgroundColor, + suggestionItemShape: suggestionItemShape ?? this.suggestionItemShape, + responseToolbarBackgroundColor: + responseToolbarBackgroundColor ?? this.responseToolbarBackgroundColor, + responseToolbarBackgroundShape: + responseToolbarBackgroundShape ?? this.responseToolbarBackgroundShape, + responseToolbarItemBackgroundColor: + responseToolbarItemBackgroundColor ?? + this.responseToolbarItemBackgroundColor, + responseToolbarItemShape: + responseToolbarItemShape ?? this.responseToolbarItemShape, + ); + } + + static SfAIAssistViewThemeData? lerp( + SfAIAssistViewThemeData? a, + SfAIAssistViewThemeData? b, + double t, + ) { + if (a == null && b == null) { + return null; + } + return SfAIAssistViewThemeData( + actionButtonForegroundColor: Color.lerp( + a!.actionButtonForegroundColor, + b!.actionButtonForegroundColor, + t, + ), + actionButtonBackgroundColor: Color.lerp( + a.actionButtonBackgroundColor, + b.actionButtonBackgroundColor, + t, + ), + actionButtonFocusColor: Color.lerp( + a.actionButtonFocusColor, + b.actionButtonFocusColor, + t, + ), + actionButtonHoverColor: Color.lerp( + a.actionButtonHoverColor, + b.actionButtonHoverColor, + t, + ), + actionButtonSplashColor: Color.lerp( + a.actionButtonSplashColor, + b.actionButtonSplashColor, + t, + ), + actionButtonDisabledForegroundColor: Color.lerp( + a.actionButtonDisabledForegroundColor, + b.actionButtonDisabledForegroundColor, + t, + ), + actionButtonDisabledBackgroundColor: Color.lerp( + a.actionButtonDisabledBackgroundColor, + b.actionButtonDisabledBackgroundColor, + t, + ), + actionButtonElevation: + lerpDouble(a.actionButtonElevation, b.actionButtonElevation, t) ?? + 0.0, + actionButtonFocusElevation: + lerpDouble( + a.actionButtonFocusElevation, + b.actionButtonFocusElevation, + t, + ) ?? + 0.0, + actionButtonHoverElevation: + lerpDouble( + a.actionButtonHoverElevation, + b.actionButtonHoverElevation, + t, + ) ?? + 0.0, + actionButtonDisabledElevation: + lerpDouble( + a.actionButtonDisabledElevation, + b.actionButtonDisabledElevation, + t, + ) ?? + 0.0, + actionButtonHighlightElevation: + lerpDouble( + a.actionButtonHighlightElevation, + b.actionButtonHighlightElevation, + t, + ) ?? + 0.0, + actionButtonShape: ShapeBorder.lerp( + a.actionButtonShape, + b.actionButtonShape, + t, + ), + actionButtonMouseCursor: + t < 0.5 ? a.actionButtonMouseCursor : b.actionButtonMouseCursor, + requestAvatarBackgroundColor: Color.lerp( + a.requestAvatarBackgroundColor, + b.requestAvatarBackgroundColor, + t, + ), + responseAvatarBackgroundColor: Color.lerp( + a.responseAvatarBackgroundColor, + b.responseAvatarBackgroundColor, + t, + ), + requestMessageBackgroundColor: Color.lerp( + a.requestMessageBackgroundColor, + b.requestMessageBackgroundColor, + t, + ), + responseMessageBackgroundColor: Color.lerp( + a.responseMessageBackgroundColor, + b.responseMessageBackgroundColor, + t, + ), + editorTextStyle: TextStyle.lerp(a.editorTextStyle, b.editorTextStyle, t), + requestContentTextStyle: TextStyle.lerp( + a.requestContentTextStyle, + b.requestContentTextStyle, + t, + ), + responseContentTextStyle: TextStyle.lerp( + a.responseContentTextStyle, + b.responseContentTextStyle, + t, + ), + requestPrimaryHeaderTextStyle: TextStyle.lerp( + a.requestPrimaryHeaderTextStyle, + b.requestPrimaryHeaderTextStyle, + t, + ), + responsePrimaryHeaderTextStyle: TextStyle.lerp( + a.responsePrimaryHeaderTextStyle, + b.responsePrimaryHeaderTextStyle, + t, + ), + requestSecondaryHeaderTextStyle: TextStyle.lerp( + a.requestSecondaryHeaderTextStyle, + b.requestSecondaryHeaderTextStyle, + t, + ), + responseSecondaryHeaderTextStyle: TextStyle.lerp( + a.responseSecondaryHeaderTextStyle, + b.responseSecondaryHeaderTextStyle, + t, + ), + suggestionItemTextStyle: WidgetStateProperty.lerp( + a.suggestionItemTextStyle, + b.suggestionItemTextStyle, + t, + TextStyle.lerp, + ), + requestMessageShape: ShapeBorder.lerp( + a.requestMessageShape, + b.requestMessageShape, + t, + ), + responseMessageShape: ShapeBorder.lerp( + a.responseMessageShape, + b.responseMessageShape, + t, + ), + suggestionBackgroundColor: Color.lerp( + a.suggestionBackgroundColor, + b.suggestionBackgroundColor, + t, + ), + suggestionBackgroundShape: ShapeBorder.lerp( + a.suggestionBackgroundShape, + b.suggestionBackgroundShape, + t, + ), + suggestionItemBackgroundColor: WidgetStateProperty.lerp( + a.suggestionItemBackgroundColor, + b.suggestionItemBackgroundColor, + t, + Color.lerp, + ), + suggestionItemShape: WidgetStateProperty.lerp( + a.suggestionItemShape, + b.suggestionItemShape, + t, + ShapeBorder.lerp, + ), + responseToolbarBackgroundColor: Color.lerp( + a.responseToolbarBackgroundColor, + b.responseToolbarBackgroundColor, + t, + ), + responseToolbarBackgroundShape: ShapeBorder.lerp( + a.responseToolbarBackgroundShape, + b.responseToolbarBackgroundShape, + t, + ), + responseToolbarItemBackgroundColor: WidgetStateProperty.lerp( + a.responseToolbarItemBackgroundColor, + b.responseToolbarItemBackgroundColor, + t, + Color.lerp, + ), + responseToolbarItemShape: WidgetStateProperty.lerp( + a.responseToolbarItemShape, + b.responseToolbarItemShape, + t, + ShapeBorder.lerp, + ), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + return other is SfAIAssistViewThemeData && + other.actionButtonForegroundColor == actionButtonForegroundColor && + other.actionButtonBackgroundColor == actionButtonBackgroundColor && + other.actionButtonFocusColor == actionButtonFocusColor && + other.actionButtonHoverColor == actionButtonHoverColor && + other.actionButtonSplashColor == actionButtonSplashColor && + other.actionButtonDisabledForegroundColor == + actionButtonDisabledForegroundColor && + other.actionButtonDisabledBackgroundColor == + actionButtonDisabledBackgroundColor && + other.actionButtonElevation == actionButtonElevation && + other.actionButtonFocusElevation == actionButtonFocusElevation && + other.actionButtonHoverElevation == actionButtonHoverElevation && + other.actionButtonDisabledElevation == actionButtonDisabledElevation && + other.actionButtonHighlightElevation == + actionButtonHighlightElevation && + other.actionButtonShape == actionButtonShape && + other.actionButtonMouseCursor == actionButtonMouseCursor && + other.requestAvatarBackgroundColor == requestAvatarBackgroundColor && + other.responseAvatarBackgroundColor == responseAvatarBackgroundColor && + other.requestMessageBackgroundColor == requestMessageBackgroundColor && + other.responseMessageBackgroundColor == + responseMessageBackgroundColor && + other.editorTextStyle == editorTextStyle && + other.requestContentTextStyle == requestContentTextStyle && + other.responseContentTextStyle == responseContentTextStyle && + other.requestPrimaryHeaderTextStyle == requestPrimaryHeaderTextStyle && + other.responsePrimaryHeaderTextStyle == + responsePrimaryHeaderTextStyle && + other.requestSecondaryHeaderTextStyle == + requestSecondaryHeaderTextStyle && + other.responseSecondaryHeaderTextStyle == + responseSecondaryHeaderTextStyle && + other.suggestionItemTextStyle == suggestionItemTextStyle && + other.requestMessageShape == requestMessageShape && + other.responseMessageShape == responseMessageShape && + other.suggestionBackgroundColor == suggestionBackgroundColor && + other.suggestionBackgroundShape == suggestionBackgroundShape && + other.suggestionItemBackgroundColor == suggestionItemBackgroundColor && + other.suggestionItemShape == suggestionItemShape && + other.responseToolbarBackgroundColor == + responseToolbarBackgroundColor && + other.responseToolbarBackgroundShape == + responseToolbarBackgroundShape && + other.responseToolbarItemBackgroundColor == + responseToolbarItemBackgroundColor && + other.responseToolbarItemShape == responseToolbarItemShape; + } + + @override + int get hashCode { + final List values = [ + actionButtonForegroundColor, + actionButtonBackgroundColor, + actionButtonFocusColor, + actionButtonHoverColor, + actionButtonSplashColor, + actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor, + actionButtonElevation, + actionButtonFocusElevation, + actionButtonHoverElevation, + actionButtonDisabledElevation, + actionButtonHighlightElevation, + actionButtonShape, + actionButtonMouseCursor, + requestAvatarBackgroundColor, + responseAvatarBackgroundColor, + requestMessageBackgroundColor, + responseMessageBackgroundColor, + editorTextStyle, + requestContentTextStyle, + responseContentTextStyle, + requestPrimaryHeaderTextStyle, + responsePrimaryHeaderTextStyle, + requestSecondaryHeaderTextStyle, + responseSecondaryHeaderTextStyle, + suggestionItemTextStyle, + requestMessageShape, + responseMessageShape, + suggestionBackgroundColor, + suggestionBackgroundShape, + suggestionItemBackgroundColor, + suggestionItemShape, + responseToolbarBackgroundColor, + responseToolbarBackgroundShape, + responseToolbarItemBackgroundColor, + responseToolbarItemShape, + ]; + return Object.hashAll(values); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + const SfAIAssistViewThemeData defaultData = SfAIAssistViewThemeData(); + properties.add( + ColorProperty( + 'actionButtonForegroundColor', + actionButtonForegroundColor, + defaultValue: defaultData.actionButtonForegroundColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonBackgroundColor', + actionButtonBackgroundColor, + defaultValue: defaultData.actionButtonBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonFocusColor', + actionButtonFocusColor, + defaultValue: defaultData.actionButtonFocusColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonHoverColor', + actionButtonHoverColor, + defaultValue: defaultData.actionButtonHoverColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonSplashColor', + actionButtonSplashColor, + defaultValue: defaultData.actionButtonSplashColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonDisabledForegroundColor', + actionButtonDisabledForegroundColor, + defaultValue: defaultData.actionButtonDisabledForegroundColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonDisabledBackgroundColor', + actionButtonDisabledBackgroundColor, + defaultValue: defaultData.actionButtonDisabledBackgroundColor, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonElevation', + actionButtonElevation, + defaultValue: defaultData.actionButtonElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonFocusElevation', + actionButtonFocusElevation, + defaultValue: defaultData.actionButtonFocusElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonHoverElevation', + actionButtonHoverElevation, + defaultValue: defaultData.actionButtonHoverElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonDisabledElevation', + actionButtonDisabledElevation, + defaultValue: defaultData.actionButtonDisabledElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonHighlightElevation', + actionButtonHighlightElevation, + defaultValue: defaultData.actionButtonHighlightElevation, + ), + ); + properties.add( + DiagnosticsProperty( + 'actionButtonShape', + actionButtonShape, + defaultValue: defaultData.actionButtonShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'actionButtonMouseCursor', + actionButtonMouseCursor, + defaultValue: defaultData.actionButtonMouseCursor, + ), + ); + properties.add( + ColorProperty( + 'requestAvatarBackgroundColor', + requestAvatarBackgroundColor, + defaultValue: defaultData.requestAvatarBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'responseAvatarBackgroundColor', + responseAvatarBackgroundColor, + defaultValue: defaultData.responseAvatarBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'requestMessageBackgroundColor', + requestMessageBackgroundColor, + defaultValue: defaultData.requestMessageBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'responseMessageBackgroundColor', + responseMessageBackgroundColor, + defaultValue: defaultData.responseMessageBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'editorTextStyle', + editorTextStyle, + defaultValue: defaultData.editorTextStyle, + ), + ); + + properties.add( + DiagnosticsProperty( + 'requestContentTextStyle', + requestContentTextStyle, + defaultValue: defaultData.requestContentTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'responseContentTextStyle', + responseContentTextStyle, + defaultValue: defaultData.responseContentTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'requestPrimaryHeaderTextStyle', + requestPrimaryHeaderTextStyle, + defaultValue: defaultData.requestPrimaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'responsePrimaryHeaderTextStyle', + responsePrimaryHeaderTextStyle, + defaultValue: defaultData.responsePrimaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'requestSecondaryHeaderTextStyle', + requestSecondaryHeaderTextStyle, + defaultValue: defaultData.requestSecondaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'responseSecondaryHeaderTextStyle', + responseSecondaryHeaderTextStyle, + defaultValue: defaultData.responseSecondaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty>( + 'suggestionItemTextStyle', + suggestionItemTextStyle, + defaultValue: defaultData.suggestionItemTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'requestMessageShape', + requestMessageShape, + defaultValue: defaultData.requestMessageShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'responseMessageShape', + responseMessageShape, + defaultValue: defaultData.responseMessageShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'suggestionBackgroundColor', + suggestionBackgroundColor, + defaultValue: defaultData.suggestionBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'suggestionBackgroundShape', + suggestionBackgroundShape, + defaultValue: defaultData.suggestionBackgroundShape, + ), + ); + properties.add( + DiagnosticsProperty>( + 'suggestionItemBackgroundColor', + suggestionItemBackgroundColor, + defaultValue: defaultData.suggestionItemBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty>( + 'suggestionItemShape', + suggestionItemShape, + defaultValue: defaultData.suggestionItemShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'responseToolbarBackgroundColor', + responseToolbarBackgroundColor, + defaultValue: defaultData.responseToolbarBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'responseToolbarBackgroundShape', + responseToolbarBackgroundShape, + defaultValue: defaultData.responseToolbarBackgroundShape, + ), + ); + properties.add( + DiagnosticsProperty>( + 'responseToolbarItemBackgroundColor', + responseToolbarItemBackgroundColor, + defaultValue: defaultData.responseToolbarItemBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty>( + 'responseToolbarItemShape', + responseToolbarItemShape, + defaultValue: defaultData.responseToolbarItemShape, + ), + ); + } +} 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 5fbbbebf9..62ab8701c 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, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant barcode widgets. /// @@ -127,47 +127,37 @@ class SfBarcodeTheme extends InheritedTheme { @immutable class SfBarcodeThemeData with Diagnosticable { /// Initialize the SfBarcode theme data - factory SfBarcodeThemeData({ + const SfBarcodeThemeData({ + this.backgroundColor, + this.barColor, + this.textColor, + this.textStyle, + }); + + /// Create a [SfBarcodeThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfBarcodeThemeData] constructor. + factory SfBarcodeThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? barColor, Color? textColor, + TextStyle? textStyle, }) { brightness = brightness ?? Brightness.light; - final bool isLight = brightness == Brightness.light; - backgroundColor ??= Colors.transparent; - barColor ??= isLight ? const Color(0xFF212121) : const Color(0xFFE0E0E0); - textColor ??= isLight ? const Color(0xFF212121) : const Color(0xFFE0E0E0); - return SfBarcodeThemeData.raw( - brightness: brightness, - backgroundColor: backgroundColor, - barColor: barColor, - textColor: textColor); + return SfBarcodeThemeData( + backgroundColor: backgroundColor, + barColor: barColor, + textColor: textColor, + textStyle: textStyle, + ); } - /// Create a [SfBarcodeThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// 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, - }); - - /// The brightness of the overall theme of the - /// application for the barcode widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// barcode widgets will be applied. - /// - /// Also refer [Brightness]. - /// + /// Specifies the background color of barcode widgets. /// /// ```dart /// Widget build(BuildContext context) { @@ -177,7 +167,7 @@ class SfBarcodeThemeData with Diagnosticable { /// child: SfTheme( /// data: SfThemeData( /// barcodeThemeData: SfBarcodeThemeData( - /// brightness: Brightness.dark + /// backgroundColor: Colors.yellow /// ), /// ), /// child: SfBarcodeGenerator( @@ -190,9 +180,9 @@ class SfBarcodeThemeData with Diagnosticable { /// ); ///} /// ``` - final Brightness brightness; + final Color? backgroundColor; - /// Specifies the background color of barcode widgets. + /// Specifies the color for barcodes. /// /// ```dart /// Widget build(BuildContext context) { @@ -202,7 +192,7 @@ class SfBarcodeThemeData with Diagnosticable { /// child: SfTheme( /// data: SfThemeData( /// barcodeThemeData: SfBarcodeThemeData( - /// backgroundColor: Colors.yellow + /// barColor: Colors.green /// ), /// ), /// child: SfBarcodeGenerator( @@ -215,9 +205,9 @@ class SfBarcodeThemeData with Diagnosticable { /// ); ///} /// ``` - final Color backgroundColor; + final Color? barColor; - /// Specifies the color for barcodes. + /// Specifies the color for barcode text. /// /// ```dart /// Widget build(BuildContext context) { @@ -227,7 +217,7 @@ class SfBarcodeThemeData with Diagnosticable { /// child: SfTheme( /// data: SfThemeData( /// barcodeThemeData: SfBarcodeThemeData( - /// barColor: Colors.green + /// textColor: Colors.blue /// ), /// ), /// child: SfBarcodeGenerator( @@ -240,9 +230,9 @@ class SfBarcodeThemeData with Diagnosticable { /// ); ///} /// ``` - final Color barColor; + final Color? textColor; - /// Specifies the color for barcode text. + /// Specifies the text style for barcode text. /// /// ```dart /// Widget build(BuildContext context) { @@ -252,7 +242,7 @@ class SfBarcodeThemeData with Diagnosticable { /// child: SfTheme( /// data: SfThemeData( /// barcodeThemeData: SfBarcodeThemeData( - /// textColor: Colors.blue + /// textStyle: TestStyle(color: Colors.blue) /// ), /// ), /// child: SfBarcodeGenerator( @@ -263,9 +253,9 @@ class SfBarcodeThemeData with Diagnosticable { /// ), /// ) /// ); - ///} + /// } /// ``` - final Color textColor; + final TextStyle? textStyle; /// Creates a copy of this barcode theme data object with the matching fields /// replaced with the non-null parameter values. @@ -274,18 +264,23 @@ class SfBarcodeThemeData with Diagnosticable { Color? backgroundColor, Color? barColor, Color? textColor, + TextStyle? textStyle, }) { return SfBarcodeThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, barColor: barColor ?? this.barColor, textColor: textColor ?? this.textColor, + textStyle: textStyle ?? this.textStyle, ); } /// Returns the barcode theme data static SfBarcodeThemeData? lerp( - SfBarcodeThemeData? a, SfBarcodeThemeData? b, double t) { + SfBarcodeThemeData? a, + SfBarcodeThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } @@ -293,6 +288,7 @@ class SfBarcodeThemeData with Diagnosticable { backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), barColor: Color.lerp(a.barColor, b.barColor, t), textColor: Color.lerp(a.textColor, b.textColor, t), + textStyle: TextStyle.lerp(a.textStyle, b.textStyle, t), ); } @@ -308,26 +304,48 @@ class SfBarcodeThemeData with Diagnosticable { return other is SfBarcodeThemeData && other.backgroundColor == backgroundColor && other.barColor == barColor && - other.textColor == textColor; + other.textColor == textColor && + other.textStyle == textStyle; } @override int get hashCode { - final List values = [backgroundColor, barColor, textColor]; - return hashList(values); + final List values = [ + backgroundColor, + barColor, + textColor, + textStyle, + ]; + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfBarcodeThemeData defaultData = SfBarcodeThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('barColor', barColor, - defaultValue: defaultData.barColor)); - properties.add(ColorProperty('textColor', textColor, - defaultValue: defaultData.textColor)); + const SfBarcodeThemeData defaultData = SfBarcodeThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty('barColor', barColor, defaultValue: defaultData.barColor), + ); + properties.add( + ColorProperty( + 'textColor', + textColor, + defaultValue: defaultData.textColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'textStyle', + textStyle, + defaultValue: defaultData.textStyle, + ), + ); } } 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 e8e4a3e88..31640507a 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 @@ class SfCalendarTheme extends InheritedTheme { /// Constructor for the calendar theme class, which applies a theme to /// descendant Syncfusion calendar widgets. const SfCalendarTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant calendar widgets. /// @@ -102,9 +102,45 @@ class SfCalendarTheme extends InheritedTheme { /// ``` @immutable class SfCalendarThemeData with Diagnosticable { + /// Create a [SfCalendarThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfCalendarThemeData] constructor. + const SfCalendarThemeData({ + this.backgroundColor, + this.headerTextStyle, + this.headerBackgroundColor, + this.agendaBackgroundColor, + this.cellBorderColor, + this.viewHeaderDateTextStyle, + this.viewHeaderDayTextStyle, + this.viewHeaderBackgroundColor, + this.agendaDayTextStyle, + this.agendaDateTextStyle, + this.timeTextStyle, + this.activeDatesTextStyle, + this.activeDatesBackgroundColor, + this.todayBackgroundColor, + this.trailingDatesBackgroundColor, + this.leadingDatesBackgroundColor, + this.trailingDatesTextStyle, + this.blackoutDatesTextStyle, + this.displayNameTextStyle, + this.leadingDatesTextStyle, + this.todayTextStyle, + this.todayHighlightColor, + this.weekNumberBackgroundColor, + this.selectionBorderColor, + this.weekNumberTextStyle, + this.timeIndicatorTextStyle, + this.allDayPanelColor, + }); + /// Create a [SfCalendarThemeData] that's used to configure a /// [SfCalendarTheme]. - factory SfCalendarThemeData({ + factory SfCalendarThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? headerBackgroundColor, @@ -134,101 +170,38 @@ class SfCalendarThemeData with Diagnosticable { TextStyle? weekNumberTextStyle, TextStyle? timeIndicatorTextStyle, }) { - return SfCalendarThemeData.raw( - brightness: brightness, - backgroundColor: backgroundColor, - headerTextStyle: headerTextStyle, - headerBackgroundColor: headerBackgroundColor, - agendaBackgroundColor: agendaBackgroundColor, - viewHeaderDateTextStyle: viewHeaderDateTextStyle, - viewHeaderDayTextStyle: viewHeaderDayTextStyle, - agendaDayTextStyle: agendaDayTextStyle, - agendaDateTextStyle: agendaDateTextStyle, - cellBorderColor: cellBorderColor, - timeTextStyle: timeTextStyle, - activeDatesTextStyle: activeDatesTextStyle, - activeDatesBackgroundColor: activeDatesBackgroundColor, - todayBackgroundColor: todayBackgroundColor, - trailingDatesBackgroundColor: trailingDatesBackgroundColor, - leadingDatesBackgroundColor: leadingDatesBackgroundColor, - allDayPanelColor: allDayPanelColor, - trailingDatesTextStyle: trailingDatesTextStyle, - blackoutDatesTextStyle: blackoutDatesTextStyle, - displayNameTextStyle: displayNameTextStyle, - leadingDatesTextStyle: leadingDatesTextStyle, - todayTextStyle: todayTextStyle, - todayHighlightColor: todayHighlightColor, - viewHeaderBackgroundColor: viewHeaderBackgroundColor, - weekNumberBackgroundColor: weekNumberBackgroundColor, - selectionBorderColor: selectionBorderColor, - weekNumberTextStyle: weekNumberTextStyle, - timeIndicatorTextStyle: timeIndicatorTextStyle); + brightness = brightness ?? Brightness.light; + return SfCalendarThemeData( + backgroundColor: backgroundColor, + headerTextStyle: headerTextStyle, + headerBackgroundColor: headerBackgroundColor, + agendaBackgroundColor: agendaBackgroundColor, + viewHeaderDateTextStyle: viewHeaderDateTextStyle, + viewHeaderDayTextStyle: viewHeaderDayTextStyle, + agendaDayTextStyle: agendaDayTextStyle, + agendaDateTextStyle: agendaDateTextStyle, + cellBorderColor: cellBorderColor, + timeTextStyle: timeTextStyle, + activeDatesTextStyle: activeDatesTextStyle, + activeDatesBackgroundColor: activeDatesBackgroundColor, + todayBackgroundColor: todayBackgroundColor, + trailingDatesBackgroundColor: trailingDatesBackgroundColor, + leadingDatesBackgroundColor: leadingDatesBackgroundColor, + allDayPanelColor: allDayPanelColor, + trailingDatesTextStyle: trailingDatesTextStyle, + blackoutDatesTextStyle: blackoutDatesTextStyle, + displayNameTextStyle: displayNameTextStyle, + leadingDatesTextStyle: leadingDatesTextStyle, + todayTextStyle: todayTextStyle, + todayHighlightColor: todayHighlightColor, + viewHeaderBackgroundColor: viewHeaderBackgroundColor, + weekNumberBackgroundColor: weekNumberBackgroundColor, + selectionBorderColor: selectionBorderColor, + weekNumberTextStyle: weekNumberTextStyle, + timeIndicatorTextStyle: timeIndicatorTextStyle, + ); } - /// Create a [SfCalendarThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// 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.weekNumberBackgroundColor, - required this.selectionBorderColor, - required this.weekNumberTextStyle, - required this.timeIndicatorTextStyle, - required this.allDayPanelColor}); - - /// The brightness of the overall theme of the - /// application for the calendar widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// calendar widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// calendarThemeData: SfCalendarThemeData( - /// brightness: Brightness.light - /// ) - /// ), - /// child: SfCalendar(), - /// ), - /// ) - /// ); - ///} - /// ``` - final Brightness? brightness; - /// Specifies the background color of calendar widgets. /// /// ```dart @@ -814,110 +787,145 @@ 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, - TextStyle? weekNumberTextStyle, - TextStyle? timeIndicatorTextStyle, - Color? todayHighlightColor, - Color? viewHeaderBackgroundColor, - Color? weekNumberBackgroundColor, - Color? selectionBorderColor, - Color? allDayPanelColor}) { + 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, + TextStyle? weekNumberTextStyle, + TextStyle? timeIndicatorTextStyle, + Color? todayHighlightColor, + Color? viewHeaderBackgroundColor, + Color? weekNumberBackgroundColor, + Color? selectionBorderColor, + Color? allDayPanelColor, + }) { return SfCalendarThemeData.raw( - brightness: brightness ?? this.brightness, - backgroundColor: backgroundColor ?? this.backgroundColor, - headerTextStyle: headerTextStyle ?? this.headerTextStyle, - headerBackgroundColor: - headerBackgroundColor ?? this.headerBackgroundColor, - agendaBackgroundColor: - agendaBackgroundColor ?? this.agendaBackgroundColor, - cellBorderColor: cellBorderColor ?? this.cellBorderColor, - viewHeaderDateTextStyle: - viewHeaderDateTextStyle ?? this.viewHeaderDateTextStyle, - viewHeaderDayTextStyle: - viewHeaderDayTextStyle ?? this.viewHeaderDayTextStyle, - agendaDayTextStyle: agendaDayTextStyle ?? this.agendaDayTextStyle, - agendaDateTextStyle: agendaDateTextStyle ?? this.agendaDateTextStyle, - timeTextStyle: timeTextStyle ?? this.timeTextStyle, - activeDatesTextStyle: activeDatesTextStyle ?? this.activeDatesTextStyle, - activeDatesBackgroundColor: - activeDatesBackgroundColor ?? this.activeDatesBackgroundColor, - todayBackgroundColor: todayBackgroundColor ?? this.todayBackgroundColor, - trailingDatesBackgroundColor: - trailingDatesBackgroundColor ?? this.trailingDatesBackgroundColor, - leadingDatesBackgroundColor: - leadingDatesBackgroundColor ?? this.leadingDatesBackgroundColor, - trailingDatesTextStyle: - trailingDatesTextStyle ?? this.trailingDatesTextStyle, - blackoutDatesTextStyle: - blackoutDatesTextStyle ?? this.blackoutDatesTextStyle, - displayNameTextStyle: displayNameTextStyle ?? this.displayNameTextStyle, - leadingDatesTextStyle: - leadingDatesTextStyle ?? this.leadingDatesTextStyle, - todayTextStyle: todayTextStyle ?? this.todayTextStyle, - weekNumberTextStyle: weekNumberTextStyle ?? this.weekNumberTextStyle, - todayHighlightColor: todayHighlightColor ?? this.todayHighlightColor, - timeIndicatorTextStyle: - timeIndicatorTextStyle ?? this.timeIndicatorTextStyle, - viewHeaderBackgroundColor: - viewHeaderBackgroundColor ?? this.viewHeaderBackgroundColor, - weekNumberBackgroundColor: - weekNumberBackgroundColor ?? this.weekNumberBackgroundColor, - selectionBorderColor: selectionBorderColor ?? this.selectionBorderColor, - allDayPanelColor: allDayPanelColor ?? this.allDayPanelColor); + brightness: brightness, + backgroundColor: backgroundColor ?? this.backgroundColor, + headerTextStyle: headerTextStyle ?? this.headerTextStyle, + headerBackgroundColor: + headerBackgroundColor ?? this.headerBackgroundColor, + agendaBackgroundColor: + agendaBackgroundColor ?? this.agendaBackgroundColor, + cellBorderColor: cellBorderColor ?? this.cellBorderColor, + viewHeaderDateTextStyle: + viewHeaderDateTextStyle ?? this.viewHeaderDateTextStyle, + viewHeaderDayTextStyle: + viewHeaderDayTextStyle ?? this.viewHeaderDayTextStyle, + agendaDayTextStyle: agendaDayTextStyle ?? this.agendaDayTextStyle, + agendaDateTextStyle: agendaDateTextStyle ?? this.agendaDateTextStyle, + timeTextStyle: timeTextStyle ?? this.timeTextStyle, + activeDatesTextStyle: activeDatesTextStyle ?? this.activeDatesTextStyle, + activeDatesBackgroundColor: + activeDatesBackgroundColor ?? this.activeDatesBackgroundColor, + todayBackgroundColor: todayBackgroundColor ?? this.todayBackgroundColor, + trailingDatesBackgroundColor: + trailingDatesBackgroundColor ?? this.trailingDatesBackgroundColor, + leadingDatesBackgroundColor: + leadingDatesBackgroundColor ?? this.leadingDatesBackgroundColor, + trailingDatesTextStyle: + trailingDatesTextStyle ?? this.trailingDatesTextStyle, + blackoutDatesTextStyle: + blackoutDatesTextStyle ?? this.blackoutDatesTextStyle, + displayNameTextStyle: displayNameTextStyle ?? this.displayNameTextStyle, + leadingDatesTextStyle: + leadingDatesTextStyle ?? this.leadingDatesTextStyle, + todayTextStyle: todayTextStyle ?? this.todayTextStyle, + weekNumberTextStyle: weekNumberTextStyle ?? this.weekNumberTextStyle, + todayHighlightColor: todayHighlightColor ?? this.todayHighlightColor, + timeIndicatorTextStyle: + timeIndicatorTextStyle ?? this.timeIndicatorTextStyle, + viewHeaderBackgroundColor: + viewHeaderBackgroundColor ?? this.viewHeaderBackgroundColor, + weekNumberBackgroundColor: + weekNumberBackgroundColor ?? this.weekNumberBackgroundColor, + selectionBorderColor: selectionBorderColor ?? this.selectionBorderColor, + allDayPanelColor: allDayPanelColor ?? this.allDayPanelColor, + ); } /// Linearly interpolate between two themes. static SfCalendarThemeData? lerp( - SfCalendarThemeData? a, SfCalendarThemeData? b, double t) { + SfCalendarThemeData? a, + SfCalendarThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfCalendarThemeData( - backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), - headerBackgroundColor: - Color.lerp(a.headerBackgroundColor, b.headerBackgroundColor, t), - agendaBackgroundColor: - Color.lerp(a.agendaBackgroundColor, b.agendaBackgroundColor, t), - cellBorderColor: Color.lerp(a.cellBorderColor, b.cellBorderColor, t), - selectionBorderColor: - Color.lerp(a.selectionBorderColor, b.selectionBorderColor, t), - activeDatesBackgroundColor: Color.lerp( - a.activeDatesBackgroundColor, b.activeDatesBackgroundColor, t), - todayBackgroundColor: - Color.lerp(a.todayBackgroundColor, b.todayBackgroundColor, t), - trailingDatesBackgroundColor: Color.lerp( - a.trailingDatesBackgroundColor, b.trailingDatesBackgroundColor, t), - leadingDatesBackgroundColor: Color.lerp( - a.leadingDatesBackgroundColor, b.leadingDatesBackgroundColor, t), - todayHighlightColor: - Color.lerp(a.todayHighlightColor, b.todayHighlightColor, t), - viewHeaderBackgroundColor: Color.lerp( - a.viewHeaderBackgroundColor, b.viewHeaderBackgroundColor, t), - weekNumberBackgroundColor: Color.lerp( - a.weekNumberBackgroundColor, b.weekNumberBackgroundColor, t), - allDayPanelColor: - Color.lerp(a.allDayPanelColor, b.allDayPanelColor, t)); + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + headerBackgroundColor: Color.lerp( + a.headerBackgroundColor, + b.headerBackgroundColor, + t, + ), + agendaBackgroundColor: Color.lerp( + a.agendaBackgroundColor, + b.agendaBackgroundColor, + t, + ), + cellBorderColor: Color.lerp(a.cellBorderColor, b.cellBorderColor, t), + selectionBorderColor: Color.lerp( + a.selectionBorderColor, + b.selectionBorderColor, + t, + ), + activeDatesBackgroundColor: Color.lerp( + a.activeDatesBackgroundColor, + b.activeDatesBackgroundColor, + t, + ), + todayBackgroundColor: Color.lerp( + a.todayBackgroundColor, + b.todayBackgroundColor, + t, + ), + trailingDatesBackgroundColor: Color.lerp( + a.trailingDatesBackgroundColor, + b.trailingDatesBackgroundColor, + t, + ), + leadingDatesBackgroundColor: Color.lerp( + a.leadingDatesBackgroundColor, + b.leadingDatesBackgroundColor, + t, + ), + todayHighlightColor: Color.lerp( + a.todayHighlightColor, + b.todayHighlightColor, + t, + ), + viewHeaderBackgroundColor: Color.lerp( + a.viewHeaderBackgroundColor, + b.viewHeaderBackgroundColor, + t, + ), + weekNumberBackgroundColor: Color.lerp( + a.weekNumberBackgroundColor, + b.weekNumberBackgroundColor, + t, + ), + allDayPanelColor: Color.lerp(a.allDayPanelColor, b.allDayPanelColor, t), + ); } @override @@ -988,45 +996,103 @@ class SfCalendarThemeData with Diagnosticable { allDayPanelColor, timeIndicatorTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfCalendarThemeData defaultData = SfCalendarThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('headerBackgroundColor', headerBackgroundColor, - defaultValue: defaultData.headerBackgroundColor)); - properties.add(ColorProperty('agendaBackgroundColor', agendaBackgroundColor, - defaultValue: defaultData.agendaBackgroundColor)); - properties.add(ColorProperty('cellBorderColor', cellBorderColor, - defaultValue: defaultData.cellBorderColor)); - properties.add(ColorProperty( - 'activeDatesBackgroundColor', activeDatesBackgroundColor, - defaultValue: defaultData.activeDatesBackgroundColor)); - properties.add(ColorProperty('todayBackgroundColor', todayBackgroundColor, - defaultValue: defaultData.todayBackgroundColor)); - properties.add(ColorProperty( - 'trailingDatesBackgroundColor', trailingDatesBackgroundColor, - defaultValue: defaultData.trailingDatesBackgroundColor)); - properties.add(ColorProperty( - 'leadingDatesBackgroundColor', leadingDatesBackgroundColor, - defaultValue: defaultData.leadingDatesBackgroundColor)); - properties.add(ColorProperty('todayHighlightColor', todayHighlightColor, - defaultValue: defaultData.todayHighlightColor)); - properties.add(ColorProperty( - 'viewHeaderBackgroundColor', viewHeaderBackgroundColor, - defaultValue: defaultData.viewHeaderBackgroundColor)); - properties.add(ColorProperty( - 'weekNumberBackgroundColor', weekNumberBackgroundColor, - defaultValue: defaultData.weekNumberBackgroundColor)); - properties.add(ColorProperty('selectionBorderColor', selectionBorderColor, - defaultValue: defaultData.selectionBorderColor)); - properties.add(ColorProperty('allDayPanelColor ', allDayPanelColor, - defaultValue: defaultData.allDayPanelColor)); + const SfCalendarThemeData defaultData = SfCalendarThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty( + 'headerBackgroundColor', + headerBackgroundColor, + defaultValue: defaultData.headerBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'agendaBackgroundColor', + agendaBackgroundColor, + defaultValue: defaultData.agendaBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'cellBorderColor', + cellBorderColor, + defaultValue: defaultData.cellBorderColor, + ), + ); + properties.add( + ColorProperty( + 'activeDatesBackgroundColor', + activeDatesBackgroundColor, + defaultValue: defaultData.activeDatesBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'todayBackgroundColor', + todayBackgroundColor, + defaultValue: defaultData.todayBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'trailingDatesBackgroundColor', + trailingDatesBackgroundColor, + defaultValue: defaultData.trailingDatesBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'leadingDatesBackgroundColor', + leadingDatesBackgroundColor, + defaultValue: defaultData.leadingDatesBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'todayHighlightColor', + todayHighlightColor, + defaultValue: defaultData.todayHighlightColor, + ), + ); + properties.add( + ColorProperty( + 'viewHeaderBackgroundColor', + viewHeaderBackgroundColor, + defaultValue: defaultData.viewHeaderBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'weekNumberBackgroundColor', + weekNumberBackgroundColor, + defaultValue: defaultData.weekNumberBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'selectionBorderColor', + selectionBorderColor, + defaultValue: defaultData.selectionBorderColor, + ), + ); + properties.add( + ColorProperty( + 'allDayPanelColor ', + allDayPanelColor, + defaultValue: defaultData.allDayPanelColor, + ), + ); } } 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 ee40afd71..7bdec1354 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, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant chart widgets. /// @@ -119,133 +119,45 @@ class SfChartTheme extends InheritedTheme { @immutable 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 ?? Brightness.light; - final bool isLight = brightness == Brightness.light; - backgroundColor ??= Colors.transparent; - axisLabelColor ??= isLight - ? const Color.fromRGBO(104, 104, 104, 1) - : const Color.fromRGBO(242, 242, 242, 1); - axisTitleColor ??= isLight - ? const Color.fromRGBO(66, 66, 66, 1) - : const Color.fromRGBO(255, 255, 255, 1); - axisLineColor ??= isLight - ? const Color.fromRGBO(181, 181, 181, 1) - : const Color.fromRGBO(101, 101, 101, 1); - majorGridLineColor ??= isLight - ? const Color.fromRGBO(219, 219, 219, 1) - : const Color.fromRGBO(70, 74, 86, 1); - minorGridLineColor ??= isLight - ? const Color.fromRGBO(234, 234, 234, 1) - : const Color.fromRGBO(70, 74, 86, 1); - majorTickLineColor ??= isLight - ? const Color.fromRGBO(181, 181, 181, 1) - : const Color.fromRGBO(191, 191, 191, 1); - minorTickLineColor ??= isLight - ? const Color.fromRGBO(214, 214, 214, 1) - : const Color.fromRGBO(150, 150, 150, 1); - titleTextColor ??= isLight - ? const Color.fromRGBO(66, 66, 66, 1) - : const Color.fromRGBO(255, 255, 255, 1); - titleBackgroundColor ??= Colors.transparent; - legendTextColor ??= isLight - ? const Color.fromRGBO(53, 53, 53, 1) - : const Color.fromRGBO(255, 255, 255, 1); - legendBackgroundColor ??= isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(0, 0, 0, 1); - legendTitleColor ??= isLight - ? const Color.fromRGBO(66, 66, 66, 1) - : const Color.fromRGBO(255, 255, 255, 1); - plotAreaBackgroundColor ??= Colors.transparent; - plotAreaBorderColor ??= isLight - ? const Color.fromRGBO(219, 219, 219, 1) - : const Color.fromRGBO(101, 101, 101, 1); - crosshairLineColor ??= isLight - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(255, 255, 255, 1); - crosshairBackgroundColor ??= isLight - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(255, 255, 255, 1); - crosshairLabelColor ??= isLight - ? const Color.fromRGBO(229, 229, 229, 1) - : const Color.fromRGBO(0, 0, 0, 1); - tooltipColor ??= isLight - ? const Color.fromRGBO(0, 8, 22, 0.75) - : const Color.fromRGBO(255, 255, 255, 1); - tooltipLabelColor ??= isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(0, 0, 0, 1); - tooltipSeparatorColor ??= isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(150, 150, 150, 1); - selectionRectColor ??= isLight - ? const Color.fromRGBO(41, 171, 226, 0.1) - : const Color.fromRGBO(255, 217, 57, 0.3); - selectionRectBorderColor ??= isLight - ? const Color.fromRGBO(41, 171, 226, 1) - : const Color.fromRGBO(255, 255, 255, 1); - selectionTooltipConnectorLineColor ??= isLight - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(150, 150, 150, 1); - waterfallConnectorLineColor ??= isLight - ? const Color.fromRGBO(0, 0, 0, 1) - : const Color.fromRGBO(255, 255, 255, 1); - - return SfChartThemeData.raw( - brightness: brightness, - axisLabelColor: axisLabelColor, - axisLineColor: axisLineColor, - axisTitleColor: axisTitleColor, - backgroundColor: backgroundColor, - titleTextColor: titleTextColor, - crosshairBackgroundColor: crosshairBackgroundColor, - crosshairLabelColor: crosshairLabelColor, - crosshairLineColor: crosshairLineColor, - legendBackgroundColor: legendBackgroundColor, - legendTextColor: legendTextColor, - legendTitleColor: legendTitleColor, - majorGridLineColor: majorGridLineColor, - majorTickLineColor: majorTickLineColor, - minorGridLineColor: minorGridLineColor, - minorTickLineColor: minorTickLineColor, - plotAreaBackgroundColor: plotAreaBackgroundColor, - plotAreaBorderColor: plotAreaBorderColor, - selectionRectColor: selectionRectColor, - selectionRectBorderColor: selectionRectBorderColor, - selectionTooltipConnectorLineColor: selectionTooltipConnectorLineColor, - titleBackgroundColor: titleBackgroundColor, - tooltipColor: tooltipColor, - tooltipSeparatorColor: tooltipSeparatorColor, - tooltipLabelColor: tooltipLabelColor, - waterfallConnectorLineColor: waterfallConnectorLineColor); - } + const SfChartThemeData({ + this.axisLabelColor, + this.axisLineColor, + this.axisTitleColor, + this.backgroundColor, + this.titleTextColor, + this.crosshairBackgroundColor, + this.crosshairLabelColor, + this.crosshairLineColor, + this.legendBackgroundColor, + this.legendTextColor, + this.legendTitleColor, + this.majorGridLineColor, + this.majorTickLineColor, + this.minorGridLineColor, + this.minorTickLineColor, + this.plotAreaBackgroundColor, + this.plotAreaBorderColor, + this.selectionRectColor, + this.selectionRectBorderColor, + this.selectionTooltipConnectorLineColor, + this.titleBackgroundColor, + this.tooltipColor, + this.tooltipSeparatorColor, + this.tooltipLabelColor, + this.waterfallConnectorLineColor, + this.titleTextStyle, + this.axisTitleTextStyle, + this.axisLabelTextStyle, + this.axisMultiLevelLabelTextStyle, + this.plotBandLabelTextStyle, + this.legendTitleTextStyle, + this.legendTextStyle, + this.dataLabelTextStyle, + this.tooltipTextStyle, + this.trackballTextStyle, + this.crosshairTextStyle, + this.selectionZoomingTooltipTextStyle, + }); /// Create a [SfChartThemeData] given a set of exact values. /// All the values must be specified. @@ -254,60 +166,88 @@ class SfChartThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [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}); - - /// The brightness of the overall theme of the - /// application for the chart widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// chart widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// chartThemeData: SfChartThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfCartesianChart() - /// ), - /// ) - /// ); - /// } - ///``` - final Brightness brightness; + factory SfChartThemeData.raw({ + 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, + TextStyle? titleTextStyle, + TextStyle? axisTitleTextStyle, + TextStyle? axisLabelTextStyle, + TextStyle? axisMultiLevelLabelTextStyle, + TextStyle? plotBandLabelTextStyle, + TextStyle? legendTitleTextStyle, + TextStyle? legendTextStyle, + TextStyle? dataLabelTextStyle, + TextStyle? tooltipTextStyle, + TextStyle? trackballTextStyle, + TextStyle? crosshairTextStyle, + TextStyle? selectionZoomingTooltipTextStyle, + }) { + brightness = brightness ?? Brightness.light; + + return SfChartThemeData( + axisLabelColor: axisLabelColor, + axisLineColor: axisLineColor, + axisTitleColor: axisTitleColor, + backgroundColor: backgroundColor, + titleTextColor: titleTextColor, + crosshairBackgroundColor: crosshairBackgroundColor, + crosshairLabelColor: crosshairLabelColor, + crosshairLineColor: crosshairLineColor, + legendBackgroundColor: legendBackgroundColor, + legendTextColor: legendTextColor, + legendTitleColor: legendTitleColor, + majorGridLineColor: majorGridLineColor, + majorTickLineColor: majorTickLineColor, + minorGridLineColor: minorGridLineColor, + minorTickLineColor: minorTickLineColor, + plotAreaBackgroundColor: plotAreaBackgroundColor, + plotAreaBorderColor: plotAreaBorderColor, + selectionRectColor: selectionRectColor, + selectionRectBorderColor: selectionRectBorderColor, + selectionTooltipConnectorLineColor: selectionTooltipConnectorLineColor, + titleBackgroundColor: titleBackgroundColor, + tooltipColor: tooltipColor, + tooltipSeparatorColor: tooltipSeparatorColor, + tooltipLabelColor: tooltipLabelColor, + waterfallConnectorLineColor: waterfallConnectorLineColor, + titleTextStyle: titleTextStyle, + axisTitleTextStyle: axisTitleTextStyle, + axisLabelTextStyle: axisLabelTextStyle, + axisMultiLevelLabelTextStyle: axisMultiLevelLabelTextStyle, + plotBandLabelTextStyle: plotBandLabelTextStyle, + legendTitleTextStyle: legendTitleTextStyle, + legendTextStyle: legendTextStyle, + dataLabelTextStyle: dataLabelTextStyle, + tooltipTextStyle: tooltipTextStyle, + trackballTextStyle: trackballTextStyle, + crosshairTextStyle: crosshairTextStyle, + selectionZoomingTooltipTextStyle: selectionZoomingTooltipTextStyle, + ); + } /// Specifies the background color of chart widgets. /// @@ -327,7 +267,7 @@ class SfChartThemeData with Diagnosticable { /// ); /// } ///``` - final Color backgroundColor; + final Color? backgroundColor; /// Specifies the color for axis labels. /// @@ -348,7 +288,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color axisLabelColor; + final Color? axisLabelColor; /// Specifies the color for axis title. /// @@ -373,7 +313,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color axisTitleColor; + final Color? axisTitleColor; /// Specifies the color for axis line. /// @@ -394,7 +334,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color axisLineColor; + final Color? axisLineColor; /// Specifies the color for major grid line of an axis. /// @@ -415,7 +355,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color majorGridLineColor; + final Color? majorGridLineColor; /// Specifies the color for minor grid line of an axis. /// @@ -438,7 +378,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color minorGridLineColor; + final Color? minorGridLineColor; /// Specifies the color for major tick line of an axis. /// @@ -459,7 +399,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color majorTickLineColor; + final Color? majorTickLineColor; /// Specifies the color for minor tick line of an axis. /// @@ -482,7 +422,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color minorTickLineColor; + final Color? minorTickLineColor; /// Specifies the color of the chart title. /// @@ -504,7 +444,7 @@ class SfChartThemeData with Diagnosticable { /// ); /// } ///``` - final Color titleTextColor; + final Color? titleTextColor; /// Specifies the background color for title of the chart. /// @@ -527,7 +467,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color titleBackgroundColor; + final Color? titleBackgroundColor; /// Specifies the text color of the legend. /// @@ -557,7 +497,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color legendTextColor; + final Color? legendTextColor; /// Specifies the title color of the legend. /// @@ -588,7 +528,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color legendTitleColor; + final Color? legendTitleColor; /// Specifies the background color of the legend. /// @@ -618,7 +558,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color legendBackgroundColor; + final Color? legendBackgroundColor; /// Specifies the background color of the plot area of the chart. /// @@ -639,7 +579,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color plotAreaBackgroundColor; + final Color? plotAreaBackgroundColor; /// Specifies the border color of the plot area of the chart. /// @@ -660,7 +600,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color plotAreaBorderColor; + final Color? plotAreaBorderColor; /// Specifies the crosshair line color. /// @@ -691,7 +631,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color crosshairLineColor; + final Color? crosshairLineColor; /// Specifies the background color of the crosshair. /// @@ -722,7 +662,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color crosshairBackgroundColor; + final Color? crosshairBackgroundColor; /// Specifies the color of the crosshair text. /// @@ -753,7 +693,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color crosshairLabelColor; + final Color? crosshairLabelColor; /// Specifies the color of the tooltip. /// @@ -783,7 +723,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color tooltipColor; + final Color? tooltipColor; /// Specifies the text color of the tooltip. /// @@ -813,7 +753,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color tooltipLabelColor; + final Color? tooltipLabelColor; /// Specifies the line color of the tooltip /// which separates the header and values. @@ -844,7 +784,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color tooltipSeparatorColor; + final Color? tooltipSeparatorColor; /// Specifies the color of an rectangle which is used for selection zooming. /// @@ -876,7 +816,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color selectionRectColor; + final Color? selectionRectColor; /// Specifies the stroke color of an rectangle /// which is used for selection zooming. @@ -909,7 +849,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color selectionRectBorderColor; + final Color? selectionRectBorderColor; /// Specifies the connector line color which is used in selection zooming. /// @@ -941,7 +881,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color selectionTooltipConnectorLineColor; + final Color? selectionTooltipConnectorLineColor; /// Specifies the connector line color for the waterfall chart. /// @@ -970,78 +910,363 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color waterfallConnectorLineColor; + final Color? waterfallConnectorLineColor; + + /// Specifies the text style for title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// titleTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? titleTextStyle; + + /// Specifies the text style for axis title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// axisTitleTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? axisTitleTextStyle; + + /// Specifies the text style for axis label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// axisLabelTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? axisLabelTextStyle; + + /// Specifies the text style for axis multi-level label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// axisMultiLevelLabelTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? axisMultiLevelLabelTextStyle; + + /// Specifies the text style for plot band label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// plotBandLabelTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? plotBandLabelTextStyle; + + /// Specifies the text style for legend title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// legendTitleTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? legendTitleTextStyle; + + /// Specifies the text style for legend text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// legendTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? legendTextStyle; + + /// Specifies the text style for data label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// dataLabelTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? dataLabelTextStyle; + + /// Specifies the text style for tooltip label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// tooltipTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? tooltipTextStyle; + + /// Specifies the text style for trackball label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// trackballTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? trackballTextStyle; + + /// Specifies the text style for crosshair label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// crosshairTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? crosshairTextStyle; + + /// Specifies the text style for selection zooming interactive label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chartThemeData: SfChartThemeData( + /// selectionZoomingTooltipTextStyle: + /// TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + + final TextStyle? selectionZoomingTooltipTextStyle; /// 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}) { + 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, + TextStyle? titleTextStyle, + TextStyle? axisTitleTextStyle, + TextStyle? axisLabelTextStyle, + TextStyle? axisMultiLevelLabelTextStyle, + TextStyle? plotBandLabelTextStyle, + TextStyle? legendTitleTextStyle, + TextStyle? legendTextStyle, + TextStyle? dataLabelTextStyle, + TextStyle? tooltipTextStyle, + TextStyle? trackballTextStyle, + TextStyle? crosshairTextStyle, + TextStyle? selectionZoomingTooltipTextStyle, + }) { return SfChartThemeData.raw( - brightness: brightness ?? this.brightness, - axisLabelColor: axisLabelColor ?? this.axisLabelColor, - axisLineColor: axisLineColor ?? this.axisLineColor, - axisTitleColor: axisTitleColor ?? this.axisTitleColor, - backgroundColor: backgroundColor ?? this.backgroundColor, - titleTextColor: titleTextColor ?? this.titleTextColor, - crosshairBackgroundColor: - crosshairBackgroundColor ?? this.crosshairBackgroundColor, - crosshairLabelColor: crosshairLabelColor ?? this.crosshairLabelColor, - crosshairLineColor: crosshairLineColor ?? this.crosshairLineColor, - legendBackgroundColor: - legendBackgroundColor ?? this.legendBackgroundColor, - legendTextColor: legendTextColor ?? this.legendTextColor, - legendTitleColor: legendTitleColor ?? this.legendTitleColor, - majorGridLineColor: majorGridLineColor ?? this.majorGridLineColor, - majorTickLineColor: majorTickLineColor ?? this.majorTickLineColor, - minorGridLineColor: minorGridLineColor ?? this.minorGridLineColor, - minorTickLineColor: minorTickLineColor ?? this.minorTickLineColor, - plotAreaBackgroundColor: - plotAreaBackgroundColor ?? this.plotAreaBackgroundColor, - plotAreaBorderColor: - plotAreaBorderColor ?? this.plotAreaBackgroundColor, - selectionRectColor: selectionRectColor ?? this.selectionRectColor, - selectionRectBorderColor: - selectionRectBorderColor ?? this.selectionRectBorderColor, - selectionTooltipConnectorLineColor: - selectionTooltipConnectorLineColor ?? - this.selectionTooltipConnectorLineColor, - titleBackgroundColor: titleBackgroundColor ?? this.titleBackgroundColor, - tooltipColor: tooltipColor ?? this.tooltipColor, - tooltipSeparatorColor: - tooltipSeparatorColor ?? this.tooltipSeparatorColor, - tooltipLabelColor: tooltipLabelColor ?? this.tooltipLabelColor, - waterfallConnectorLineColor: - waterfallConnectorLineColor ?? this.waterfallConnectorLineColor); + brightness: brightness, + axisLabelColor: axisLabelColor ?? this.axisLabelColor, + axisLineColor: axisLineColor ?? this.axisLineColor, + axisTitleColor: axisTitleColor ?? this.axisTitleColor, + backgroundColor: backgroundColor ?? this.backgroundColor, + titleTextColor: titleTextColor ?? this.titleTextColor, + crosshairBackgroundColor: + crosshairBackgroundColor ?? this.crosshairBackgroundColor, + crosshairLabelColor: crosshairLabelColor ?? this.crosshairLabelColor, + crosshairLineColor: crosshairLineColor ?? this.crosshairLineColor, + legendBackgroundColor: + legendBackgroundColor ?? this.legendBackgroundColor, + legendTextColor: legendTextColor ?? this.legendTextColor, + legendTitleColor: legendTitleColor ?? this.legendTitleColor, + majorGridLineColor: majorGridLineColor ?? this.majorGridLineColor, + majorTickLineColor: majorTickLineColor ?? this.majorTickLineColor, + minorGridLineColor: minorGridLineColor ?? this.minorGridLineColor, + minorTickLineColor: minorTickLineColor ?? this.minorTickLineColor, + plotAreaBackgroundColor: + plotAreaBackgroundColor ?? this.plotAreaBackgroundColor, + plotAreaBorderColor: plotAreaBorderColor ?? this.plotAreaBackgroundColor, + selectionRectColor: selectionRectColor ?? this.selectionRectColor, + selectionRectBorderColor: + selectionRectBorderColor ?? this.selectionRectBorderColor, + selectionTooltipConnectorLineColor: + selectionTooltipConnectorLineColor ?? + this.selectionTooltipConnectorLineColor, + titleBackgroundColor: titleBackgroundColor ?? this.titleBackgroundColor, + tooltipColor: tooltipColor ?? this.tooltipColor, + tooltipSeparatorColor: + tooltipSeparatorColor ?? this.tooltipSeparatorColor, + tooltipLabelColor: tooltipLabelColor ?? this.tooltipLabelColor, + waterfallConnectorLineColor: + waterfallConnectorLineColor ?? this.waterfallConnectorLineColor, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + axisTitleTextStyle: axisTitleTextStyle ?? this.axisTitleTextStyle, + axisLabelTextStyle: axisLabelTextStyle ?? this.axisLabelTextStyle, + axisMultiLevelLabelTextStyle: + axisMultiLevelLabelTextStyle ?? this.axisMultiLevelLabelTextStyle, + plotBandLabelTextStyle: + plotBandLabelTextStyle ?? this.plotBandLabelTextStyle, + legendTitleTextStyle: legendTitleTextStyle ?? this.legendTitleTextStyle, + legendTextStyle: legendTextStyle ?? this.legendTextStyle, + dataLabelTextStyle: dataLabelTextStyle ?? this.dataLabelTextStyle, + tooltipTextStyle: tooltipTextStyle ?? this.tooltipTextStyle, + trackballTextStyle: trackballTextStyle ?? this.trackballTextStyle, + crosshairTextStyle: crosshairTextStyle ?? this.crosshairTextStyle, + selectionZoomingTooltipTextStyle: + selectionZoomingTooltipTextStyle ?? + this.selectionZoomingTooltipTextStyle, + ); } /// Linearly interpolate between two themes. static SfChartThemeData? lerp( - SfChartThemeData? a, SfChartThemeData? b, double t) { + SfChartThemeData? a, + SfChartThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } @@ -1051,45 +1276,146 @@ class SfChartThemeData with Diagnosticable { axisTitleColor: Color.lerp(a.axisTitleColor, b.axisTitleColor, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), titleTextColor: Color.lerp(a.titleTextColor, b.titleTextColor, t), - crosshairBackgroundColor: - Color.lerp(a.crosshairBackgroundColor, b.crosshairBackgroundColor, t), - crosshairLabelColor: - Color.lerp(a.crosshairLabelColor, b.crosshairLabelColor, t), - crosshairLineColor: - Color.lerp(a.crosshairLineColor, b.crosshairLineColor, t), - legendBackgroundColor: - Color.lerp(a.legendBackgroundColor, b.legendBackgroundColor, t), + crosshairBackgroundColor: Color.lerp( + a.crosshairBackgroundColor, + b.crosshairBackgroundColor, + t, + ), + crosshairLabelColor: Color.lerp( + a.crosshairLabelColor, + b.crosshairLabelColor, + t, + ), + crosshairLineColor: Color.lerp( + a.crosshairLineColor, + b.crosshairLineColor, + t, + ), + legendBackgroundColor: Color.lerp( + a.legendBackgroundColor, + b.legendBackgroundColor, + t, + ), legendTextColor: Color.lerp(a.legendTextColor, b.legendTextColor, t), legendTitleColor: Color.lerp(a.legendTitleColor, b.legendTitleColor, t), - majorGridLineColor: - Color.lerp(a.majorGridLineColor, b.majorGridLineColor, t), - majorTickLineColor: - Color.lerp(a.majorTickLineColor, b.majorTickLineColor, t), - minorGridLineColor: - Color.lerp(a.minorGridLineColor, b.minorGridLineColor, t), - minorTickLineColor: - Color.lerp(a.minorTickLineColor, b.minorTickLineColor, t), - plotAreaBackgroundColor: - Color.lerp(a.plotAreaBackgroundColor, b.plotAreaBackgroundColor, t), - plotAreaBorderColor: - Color.lerp(a.plotAreaBorderColor, b.plotAreaBorderColor, t), - selectionRectColor: - Color.lerp(a.selectionRectColor, b.selectionRectColor, t), - selectionRectBorderColor: - Color.lerp(a.selectionRectBorderColor, b.selectionRectBorderColor, t), + majorGridLineColor: Color.lerp( + a.majorGridLineColor, + b.majorGridLineColor, + t, + ), + majorTickLineColor: Color.lerp( + a.majorTickLineColor, + b.majorTickLineColor, + t, + ), + minorGridLineColor: Color.lerp( + a.minorGridLineColor, + b.minorGridLineColor, + t, + ), + minorTickLineColor: Color.lerp( + a.minorTickLineColor, + b.minorTickLineColor, + t, + ), + plotAreaBackgroundColor: Color.lerp( + a.plotAreaBackgroundColor, + b.plotAreaBackgroundColor, + t, + ), + plotAreaBorderColor: Color.lerp( + a.plotAreaBorderColor, + b.plotAreaBorderColor, + t, + ), + selectionRectColor: Color.lerp( + a.selectionRectColor, + b.selectionRectColor, + t, + ), + selectionRectBorderColor: Color.lerp( + a.selectionRectBorderColor, + b.selectionRectBorderColor, + t, + ), selectionTooltipConnectorLineColor: Color.lerp( - a.selectionTooltipConnectorLineColor, - b.selectionTooltipConnectorLineColor, - t), - titleBackgroundColor: - Color.lerp(a.titleBackgroundColor, b.titleBackgroundColor, t), + a.selectionTooltipConnectorLineColor, + b.selectionTooltipConnectorLineColor, + t, + ), + titleBackgroundColor: Color.lerp( + a.titleBackgroundColor, + b.titleBackgroundColor, + t, + ), tooltipColor: Color.lerp(a.tooltipColor, b.tooltipColor, t), - tooltipSeparatorColor: - Color.lerp(a.tooltipSeparatorColor, b.tooltipSeparatorColor, t), - tooltipLabelColor: - Color.lerp(a.tooltipLabelColor, b.tooltipLabelColor, t), + tooltipSeparatorColor: Color.lerp( + a.tooltipSeparatorColor, + b.tooltipSeparatorColor, + t, + ), + tooltipLabelColor: Color.lerp( + a.tooltipLabelColor, + b.tooltipLabelColor, + t, + ), waterfallConnectorLineColor: Color.lerp( - a.waterfallConnectorLineColor, b.waterfallConnectorLineColor, t), + a.waterfallConnectorLineColor, + b.waterfallConnectorLineColor, + t, + ), + titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), + axisTitleTextStyle: TextStyle.lerp( + a.axisTitleTextStyle, + b.axisTitleTextStyle, + t, + ), + axisLabelTextStyle: TextStyle.lerp( + a.axisLabelTextStyle, + b.axisLabelTextStyle, + t, + ), + axisMultiLevelLabelTextStyle: TextStyle.lerp( + a.axisMultiLevelLabelTextStyle, + b.axisMultiLevelLabelTextStyle, + t, + ), + plotBandLabelTextStyle: TextStyle.lerp( + a.plotBandLabelTextStyle, + b.plotBandLabelTextStyle, + t, + ), + legendTitleTextStyle: TextStyle.lerp( + a.legendTitleTextStyle, + b.legendTitleTextStyle, + t, + ), + legendTextStyle: TextStyle.lerp(a.legendTextStyle, b.legendTextStyle, t), + dataLabelTextStyle: TextStyle.lerp( + a.dataLabelTextStyle, + b.dataLabelTextStyle, + t, + ), + tooltipTextStyle: TextStyle.lerp( + a.tooltipTextStyle, + b.tooltipTextStyle, + t, + ), + trackballTextStyle: TextStyle.lerp( + a.trackballTextStyle, + b.trackballTextStyle, + t, + ), + crosshairTextStyle: TextStyle.lerp( + a.crosshairTextStyle, + b.crosshairTextStyle, + t, + ), + selectionZoomingTooltipTextStyle: TextStyle.lerp( + a.selectionZoomingTooltipTextStyle, + b.selectionZoomingTooltipTextStyle, + t, + ), ); } @@ -1128,12 +1454,25 @@ class SfChartThemeData with Diagnosticable { other.tooltipColor == tooltipColor && other.tooltipSeparatorColor == tooltipSeparatorColor && other.tooltipLabelColor == tooltipLabelColor && - other.waterfallConnectorLineColor == waterfallConnectorLineColor; + other.waterfallConnectorLineColor == waterfallConnectorLineColor && + other.titleTextStyle == titleTextStyle && + other.axisTitleTextStyle == axisTitleTextStyle && + other.axisLabelTextStyle == axisLabelTextStyle && + other.axisMultiLevelLabelTextStyle == axisMultiLevelLabelTextStyle && + other.plotBandLabelTextStyle == plotBandLabelTextStyle && + other.legendTitleTextStyle == legendTitleTextStyle && + other.legendTextStyle == legendTextStyle && + other.dataLabelTextStyle == dataLabelTextStyle && + other.tooltipTextStyle == tooltipTextStyle && + other.trackballTextStyle == trackballTextStyle && + other.crosshairTextStyle == crosshairTextStyle && + other.selectionZoomingTooltipTextStyle == + selectionZoomingTooltipTextStyle; } @override int get hashCode { - final List values = [ + final List values = [ axisLabelColor, axisLineColor, axisTitleColor, @@ -1158,71 +1497,285 @@ class SfChartThemeData with Diagnosticable { tooltipColor, tooltipSeparatorColor, tooltipLabelColor, - waterfallConnectorLineColor + waterfallConnectorLineColor, + titleTextStyle, + axisTitleTextStyle, + axisLabelTextStyle, + axisMultiLevelLabelTextStyle, + plotBandLabelTextStyle, + legendTitleTextStyle, + legendTextStyle, + dataLabelTextStyle, + tooltipTextStyle, + trackballTextStyle, + crosshairTextStyle, + selectionZoomingTooltipTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfChartThemeData defaultData = SfChartThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('axisLabelColor', axisLabelColor, - defaultValue: defaultData.axisLabelColor)); - properties.add(ColorProperty('axisLineColor', axisLineColor, - defaultValue: defaultData.axisLineColor)); - properties.add(ColorProperty('axisTitleColor', axisTitleColor, - defaultValue: defaultData.axisTitleColor)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('titleTextColor', titleTextColor, - defaultValue: defaultData.titleTextColor)); - properties.add(ColorProperty( - 'crosshairBackgroundColor', crosshairBackgroundColor, - defaultValue: defaultData.crosshairBackgroundColor)); - properties.add(ColorProperty('crosshairLabelColor', crosshairLabelColor, - defaultValue: defaultData.crosshairLabelColor)); - properties.add(ColorProperty('crosshairLineColor', crosshairLineColor, - defaultValue: defaultData.crosshairLineColor)); - properties.add(ColorProperty('legendBackgroundColor', legendBackgroundColor, - defaultValue: defaultData.legendBackgroundColor)); - properties.add(ColorProperty('legendTextColor', legendTextColor, - defaultValue: defaultData.legendTextColor)); - properties.add(ColorProperty('legendTitleColor', legendTitleColor, - defaultValue: defaultData.legendTitleColor)); - properties.add(ColorProperty('majorGridLineColor', majorGridLineColor, - defaultValue: defaultData.majorGridLineColor)); - properties.add(ColorProperty('majorTickLineColor', majorTickLineColor, - defaultValue: defaultData.majorTickLineColor)); - properties.add(ColorProperty('minorGridLineColor', minorGridLineColor, - defaultValue: defaultData.minorGridLineColor)); - properties.add(ColorProperty('minorTickLineColor', minorTickLineColor, - defaultValue: defaultData.minorTickLineColor)); - properties.add(ColorProperty( - 'plotAreaBackgroundColor', plotAreaBackgroundColor, - defaultValue: defaultData.plotAreaBackgroundColor)); - properties.add(ColorProperty('plotAreaBorderColor', plotAreaBorderColor, - defaultValue: defaultData.plotAreaBorderColor)); - properties.add(ColorProperty('selectionRectColor', selectionRectColor, - defaultValue: defaultData.selectionRectColor)); - properties.add(ColorProperty( - 'selectionRectBorderColor', selectionRectBorderColor, - defaultValue: defaultData.selectionRectBorderColor)); - properties.add(ColorProperty('selectionTooltipConnectorLineColor', + const SfChartThemeData defaultData = SfChartThemeData(); + properties.add( + ColorProperty( + 'axisLabelColor', + axisLabelColor, + defaultValue: defaultData.axisLabelColor, + ), + ); + properties.add( + ColorProperty( + 'axisLineColor', + axisLineColor, + defaultValue: defaultData.axisLineColor, + ), + ); + properties.add( + ColorProperty( + 'axisTitleColor', + axisTitleColor, + defaultValue: defaultData.axisTitleColor, + ), + ); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty( + 'titleTextColor', + titleTextColor, + defaultValue: defaultData.titleTextColor, + ), + ); + properties.add( + ColorProperty( + 'crosshairBackgroundColor', + crosshairBackgroundColor, + defaultValue: defaultData.crosshairBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'crosshairLabelColor', + crosshairLabelColor, + defaultValue: defaultData.crosshairLabelColor, + ), + ); + properties.add( + ColorProperty( + 'crosshairLineColor', + crosshairLineColor, + defaultValue: defaultData.crosshairLineColor, + ), + ); + properties.add( + ColorProperty( + 'legendBackgroundColor', + legendBackgroundColor, + defaultValue: defaultData.legendBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'legendTextColor', + legendTextColor, + defaultValue: defaultData.legendTextColor, + ), + ); + properties.add( + ColorProperty( + 'legendTitleColor', + legendTitleColor, + defaultValue: defaultData.legendTitleColor, + ), + ); + properties.add( + ColorProperty( + 'majorGridLineColor', + majorGridLineColor, + defaultValue: defaultData.majorGridLineColor, + ), + ); + properties.add( + ColorProperty( + 'majorTickLineColor', + majorTickLineColor, + defaultValue: defaultData.majorTickLineColor, + ), + ); + properties.add( + ColorProperty( + 'minorGridLineColor', + minorGridLineColor, + defaultValue: defaultData.minorGridLineColor, + ), + ); + properties.add( + ColorProperty( + 'minorTickLineColor', + minorTickLineColor, + defaultValue: defaultData.minorTickLineColor, + ), + ); + properties.add( + ColorProperty( + 'plotAreaBackgroundColor', + plotAreaBackgroundColor, + defaultValue: defaultData.plotAreaBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'plotAreaBorderColor', + plotAreaBorderColor, + defaultValue: defaultData.plotAreaBorderColor, + ), + ); + properties.add( + ColorProperty( + 'selectionRectColor', + selectionRectColor, + defaultValue: defaultData.selectionRectColor, + ), + ); + properties.add( + ColorProperty( + 'selectionRectBorderColor', + selectionRectBorderColor, + defaultValue: defaultData.selectionRectBorderColor, + ), + ); + properties.add( + ColorProperty( + 'selectionTooltipConnectorLineColor', selectionTooltipConnectorLineColor, - defaultValue: defaultData.selectionTooltipConnectorLineColor)); - properties.add(ColorProperty('titleBackgroundColor', titleBackgroundColor, - defaultValue: defaultData.titleBackgroundColor)); - properties.add(ColorProperty('tooltipColor', tooltipColor, - defaultValue: defaultData.tooltipColor)); - properties.add(ColorProperty('tooltipSeparatorColor', tooltipSeparatorColor, - defaultValue: defaultData.tooltipSeparatorColor)); - properties.add(ColorProperty('tooltipLabelColor', tooltipLabelColor, - defaultValue: defaultData.tooltipLabelColor)); - properties.add(ColorProperty( - 'waterfallConnectorLineColor', waterfallConnectorLineColor, - defaultValue: defaultData.waterfallConnectorLineColor)); + defaultValue: defaultData.selectionTooltipConnectorLineColor, + ), + ); + properties.add( + ColorProperty( + 'titleBackgroundColor', + titleBackgroundColor, + defaultValue: defaultData.titleBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipColor', + tooltipColor, + defaultValue: defaultData.tooltipColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipSeparatorColor', + tooltipSeparatorColor, + defaultValue: defaultData.tooltipSeparatorColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipLabelColor', + tooltipLabelColor, + defaultValue: defaultData.tooltipLabelColor, + ), + ); + properties.add( + ColorProperty( + 'waterfallConnectorLineColor', + waterfallConnectorLineColor, + defaultValue: defaultData.waterfallConnectorLineColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'titleTextStyle', + titleTextStyle, + defaultValue: defaultData.titleTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'axisTitleTextStyle', + axisTitleTextStyle, + defaultValue: defaultData.axisTitleTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'axisLabelTextStyle', + axisLabelTextStyle, + defaultValue: defaultData.axisLabelTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'axisMultiLevelLabelTextStyle', + axisMultiLevelLabelTextStyle, + defaultValue: defaultData.axisMultiLevelLabelTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'plotBandLabelTextStyle', + plotBandLabelTextStyle, + defaultValue: defaultData.plotBandLabelTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'legendTitleTextStyle', + legendTitleTextStyle, + defaultValue: defaultData.legendTitleTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'legendTextStyle', + legendTextStyle, + defaultValue: defaultData.legendTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'dataLabelTextStyle', + dataLabelTextStyle, + defaultValue: defaultData.dataLabelTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'tooltipTextStyle', + tooltipTextStyle, + defaultValue: defaultData.tooltipTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'trackballTextStyle', + trackballTextStyle, + defaultValue: defaultData.trackballTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'crosshairTextStyle', + crosshairTextStyle, + defaultValue: defaultData.crosshairTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'selectionZoomingTooltipTextStyle', + selectionZoomingTooltipTextStyle, + defaultValue: defaultData.selectionZoomingTooltipTextStyle, + ), + ); } } diff --git a/packages/syncfusion_flutter_core/lib/src/theme/chat_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/chat_theme.dart new file mode 100644 index 000000000..845a5e38b --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/theme/chat_theme.dart @@ -0,0 +1,1363 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'theme_widget.dart'; + +/// Applies a theme to descendant Syncfusion chat widgets. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfChatTheme( +/// data: SfChatThemeData( +/// brightness: Brightness.dark, +/// ), +/// child: SfChat() +/// ), +/// ); +/// } +/// ``` +class SfChatTheme extends InheritedTheme { + /// Applies the given theme [data] to [child]. + /// + /// The [data] and [child] arguments must not be null. + const SfChatTheme({super.key, required this.data, required super.child}); + + /// Specifies the color and typography values for descendant Chat widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// brightness: Brightness.dark, + /// ), + /// child: SfChat() + /// ), + /// ); + /// } + /// ``` + final SfChatThemeData data; + + /// The data from the closest [SfChatTheme] instance + /// that encloses the given context. + /// + /// Defaults to [SfThemeData.chatThemeData] if there is no + /// [SfChatTheme] in the given build context. + static SfChatThemeData of(BuildContext context) { + final SfChatTheme? chatTheme = + context.dependOnInheritedWidgetOfExactType(); + return chatTheme?.data ?? SfTheme.of(context).chatThemeData; + } + + @override + bool updateShouldNotify(SfChatTheme oldWidget) => data != oldWidget.data; + + @override + Widget wrap(BuildContext context, Widget child) { + final SfChatTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : SfChatTheme(data: data, child: child); + } +} + +/// Holds the color and typography values for a [SfChatTheme]. +/// Use this class to configure a [SfChatTheme] widget, or to set the +/// [SfThemeData.chatThemeData] for a [SfTheme] widget. +/// +/// To obtain the current theme, use [SfChatTheme.of]. +@immutable +class SfChatThemeData with Diagnosticable { + /// Create a [SfChatThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to create + /// intermediate themes based on two themes created with the + /// [SfChatThemeData] constructor. + const SfChatThemeData({ + this.actionButtonForegroundColor, + this.actionButtonBackgroundColor, + this.actionButtonFocusColor, + this.actionButtonHoverColor, + this.actionButtonSplashColor, + this.actionButtonDisabledForegroundColor, + this.actionButtonDisabledBackgroundColor, + this.actionButtonElevation = 0.0, + this.actionButtonFocusElevation = 0.0, + this.actionButtonHoverElevation = 0.0, + this.actionButtonHighlightElevation = 0.0, + this.actionButtonDisabledElevation = 0.0, + this.actionButtonMouseCursor, + this.actionButtonShape, + this.outgoingAvatarBackgroundColor, + this.incomingAvatarBackgroundColor, + this.outgoingMessageBackgroundColor, + this.incomingMessageBackgroundColor, + this.editorTextStyle, + this.outgoingContentTextStyle, + this.incomingContentTextStyle, + this.outgoingPrimaryHeaderTextStyle, + this.incomingPrimaryHeaderTextStyle, + this.outgoingSecondaryHeaderTextStyle, + this.incomingSecondaryHeaderTextStyle, + this.suggestionItemTextStyle, + this.outgoingMessageShape, + this.incomingMessageShape, + this.suggestionBackgroundColor, + this.suggestionBackgroundShape, + this.suggestionItemBackgroundColor, + this.suggestionItemShape, + }); + + /// Returns a new instance of [SfChatThemeData.raw] for the given values. + /// + /// If any of the values are null, the default values will be set. + factory SfChatThemeData.raw({ + Color? actionButtonForegroundColor, + Color? actionButtonBackgroundColor, + Color? actionButtonFocusColor, + Color? actionButtonHoverColor, + Color? actionButtonSplashColor, + Color? actionButtonDisabledForegroundColor, + Color? actionButtonDisabledBackgroundColor, + double? actionButtonElevation, + double? actionButtonFocusElevation, + double? actionButtonHoverElevation, + double? actionButtonDisabledElevation, + double? actionButtonHighlightElevation, + ShapeBorder? actionButtonShape, + MouseCursor? actionButtonMouseCursor, + Color? outgoingAvatarBackgroundColor, + Color? incomingAvatarBackgroundColor, + Color? outgoingMessageBackgroundColor, + Color? incomingMessageBackgroundColor, + TextStyle? editorTextStyle, + TextStyle? outgoingContentTextStyle, + TextStyle? incomingContentTextStyle, + TextStyle? outgoingPrimaryHeaderTextStyle, + TextStyle? incomingPrimaryHeaderTextStyle, + TextStyle? outgoingSecondaryHeaderTextStyle, + TextStyle? incomingSecondaryHeaderTextStyle, + WidgetStateProperty? suggestionItemTextStyle, + ShapeBorder? outgoingMessageShape, + ShapeBorder? incomingMessageShape, + Color? suggestionBackgroundColor, + ShapeBorder? suggestionBackgroundShape, + WidgetStateProperty? suggestionItemBackgroundColor, + WidgetStateProperty? suggestionItemShape, + }) { + return SfChatThemeData( + actionButtonForegroundColor: actionButtonForegroundColor, + actionButtonBackgroundColor: actionButtonBackgroundColor, + actionButtonFocusColor: actionButtonFocusColor, + actionButtonHoverColor: actionButtonHoverColor, + actionButtonSplashColor: actionButtonSplashColor, + actionButtonDisabledForegroundColor: actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor: actionButtonDisabledBackgroundColor, + actionButtonElevation: actionButtonElevation ?? 0.0, + actionButtonFocusElevation: actionButtonFocusElevation ?? 0.0, + actionButtonHoverElevation: actionButtonHoverElevation ?? 0.0, + actionButtonDisabledElevation: actionButtonDisabledElevation ?? 0.0, + actionButtonHighlightElevation: actionButtonHighlightElevation ?? 0.0, + actionButtonShape: actionButtonShape, + actionButtonMouseCursor: actionButtonMouseCursor, + outgoingAvatarBackgroundColor: outgoingAvatarBackgroundColor, + incomingAvatarBackgroundColor: incomingAvatarBackgroundColor, + outgoingMessageBackgroundColor: outgoingMessageBackgroundColor, + incomingMessageBackgroundColor: incomingMessageBackgroundColor, + editorTextStyle: editorTextStyle, + outgoingContentTextStyle: outgoingContentTextStyle, + incomingContentTextStyle: incomingContentTextStyle, + outgoingPrimaryHeaderTextStyle: outgoingPrimaryHeaderTextStyle, + incomingPrimaryHeaderTextStyle: incomingPrimaryHeaderTextStyle, + outgoingSecondaryHeaderTextStyle: outgoingSecondaryHeaderTextStyle, + incomingSecondaryHeaderTextStyle: incomingSecondaryHeaderTextStyle, + suggestionItemTextStyle: suggestionItemTextStyle, + outgoingMessageShape: outgoingMessageShape, + incomingMessageShape: incomingMessageShape, + suggestionBackgroundColor: suggestionBackgroundColor, + suggestionBackgroundShape: suggestionBackgroundShape, + suggestionItemBackgroundColor: suggestionItemBackgroundColor, + suggestionItemShape: suggestionItemShape, + ); + } + + /// Color for the foreground elements (text or icons) of action buttons. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonForegroundColor: Colors.white, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonForegroundColor; + + /// Color for the background of action buttons. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonBackgroundColor: Colors.blue, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonBackgroundColor; + + /// Color for the action button when it is focused. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonFocusColor: Colors.green, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonFocusColor; + + /// Color for the action button when hovered over. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonHoverColor: Colors.orange, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonHoverColor; + + /// Color for the splash effect of action buttons. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonSplashColor: Colors.red, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonSplashColor; + + /// Color for the foreground elements (text or icons) of + /// action buttons when disabled. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonDisabledForegroundColor: Colors.grey, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonDisabledForegroundColor; + + /// Color for the background of action buttons when disabled. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonDisabledBackgroundColor: Colors.grey[400], + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? actionButtonDisabledBackgroundColor; + + /// Depth of the action button's shadow in its default state. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonElevation: 4.0, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonElevation; + + /// Depth of the action button's shadow when focused. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonFocusElevation: 8.0, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonFocusElevation; + + /// Depth of the action button's shadow when hovered over. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonHoverElevation: 6.0, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonHoverElevation; + + /// Depth of the action button's shadow when disabled. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonDisabledElevation: 2.0, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonDisabledElevation; + + /// Depth of the action button's shadow when highlighted. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonHighlightElevation: 12.0, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final double actionButtonHighlightElevation; + + /// Shape of the action button. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? actionButtonShape; + + /// Customizes the cursor that appears when you hover + /// over the button. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// actionButtonMouseCursor: SystemMouseCursors.click, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final MouseCursor? actionButtonMouseCursor; + + /// Background color of outgoing avatar. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// outgoingAvatarBackgroundColor: Colors.blueAccent, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? outgoingAvatarBackgroundColor; + + /// Background color of incoming avatar. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// incomingAvatarBackgroundColor: Colors.blue, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? incomingAvatarBackgroundColor; + + /// Background color of outgoing message bubbles. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// outgoingMessagetBackgroundColor: Colors.blueAccent, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? outgoingMessageBackgroundColor; + + /// Background color of incoming message bubbles. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// incomingMessageBackgroundColor: Colors.grey[300], + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? incomingMessageBackgroundColor; + + /// Text style for the message editor. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// editorTextStyle: TextStyle( + /// fontSize: 16.0, + /// color: Colors.black, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? editorTextStyle; + + /// Text style for outgoing message content. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// outgoingContentTextStyle: TextStyle( + /// fontSize: 14.0, + /// color: Colors.white, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? outgoingContentTextStyle; + + /// Text style for incoming message content. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// incomingContentTextStyle: TextStyle( + /// fontSize: 14.0, + /// color: Colors.black, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? incomingContentTextStyle; + + /// Text style for the primary header of outgoing messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// outgoingPrimaryHeaderTextStyle: TextStyle( + /// fontSize: 12.0, + /// fontWeight: FontWeight.bold, + /// color: Colors.white, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? outgoingPrimaryHeaderTextStyle; + + /// Text style for the primary header of incoming messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// incomingPrimaryHeaderTextStyle: TextStyle( + /// fontSize: 12.0, + /// fontWeight: FontWeight.bold, + /// color: Colors.black, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? incomingPrimaryHeaderTextStyle; + + /// Text style for the secondary header of outgoing messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// outgoingSecondaryHeaderTextStyle: TextStyle( + /// fontSize: 10.0, + /// color: Colors.white70, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? outgoingSecondaryHeaderTextStyle; + + /// Text style for the secondary header of incoming messages. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// incomingSecondaryHeaderTextStyle: TextStyle( + /// fontSize: 10.0, + /// color: Colors.black54, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final TextStyle? incomingSecondaryHeaderTextStyle; + + /// Text style for the suggestion items. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// suggestionItemTextStyle: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return TextStyle(fontSize: 14.0, color: Colors.blue); + /// } + /// return TextStyle(fontSize: 15.0, color: Colors.green); + /// }, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? suggestionItemTextStyle; + + /// Shape of the outgoing message bubble. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// outgoingMessageShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? outgoingMessageShape; + + /// Shape of the incoming message bubble. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// incomingMessageShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? incomingMessageShape; + + /// Background color of the suggestion area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// suggestionBackgroundColor: Colors.blue, + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final Color? suggestionBackgroundColor; + + /// Shape of the suggestion area. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// suggestionBackgroundShape: RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final ShapeBorder? suggestionBackgroundShape; + + /// Color of the suggestion item background, which can vary based on the + /// widget's state. + /// This uses a [WidgetStateProperty] to allow state-specific changes. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// suggestionItemBackgroundColor: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return Colors.blueAccent; + /// } + /// return Colors.blue; + /// }, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? suggestionItemBackgroundColor; + + /// Shape of the suggestion item, which can vary based on the widget's state. + /// This uses a [WidgetStateProperty] to allow state-specific changes. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfChatTheme( + /// data: SfChatThemeData( + /// suggestionItemShape: + /// WidgetStateProperty.resolveWith( + /// (Set states) { + /// if (states.contains(WidgetState.hovered)) { + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(12.0), + /// ); + /// } + /// return RoundedRectangleBorder( + /// borderRadius: BorderRadius.circular(8.0), + /// ); + /// }, + /// ), + /// ), + /// child: SfChat(), + /// ), + /// ); + /// } + /// ``` + final WidgetStateProperty? suggestionItemShape; + + SfChatThemeData copyWith({ + Color? actionButtonForegroundColor, + Color? actionButtonBackgroundColor, + Color? actionButtonFocusColor, + Color? actionButtonHoverColor, + Color? actionButtonSplashColor, + Color? actionButtonDisabledForegroundColor, + Color? actionButtonDisabledBackgroundColor, + double? actionButtonElevation, + double? actionButtonFocusElevation, + double? actionButtonHoverElevation, + double? actionButtonDisabledElevation, + double? actionButtonHighlightElevation, + ShapeBorder? actionButtonShape, + MouseCursor? actionButtonMouseCursor, + Color? outgoingAvatarBackgroundColor, + Color? incomingAvatarBackgroundColor, + Color? outgoingMessageBackgroundColor, + Color? incomingMessageBackgroundColor, + TextStyle? editorTextStyle, + TextStyle? outgoingContentTextStyle, + TextStyle? incomingContentTextStyle, + TextStyle? outgoingPrimaryHeaderTextStyle, + TextStyle? incomingPrimaryHeaderTextStyle, + TextStyle? outgoingSecondaryHeaderTextStyle, + TextStyle? incomingSecondaryHeaderTextStyle, + WidgetStateProperty? suggestionItemTextStyle, + ShapeBorder? outgoingMessageShape, + ShapeBorder? incomingMessageShape, + Color? suggestionBackgroundColor, + ShapeBorder? suggestionBackgroundShape, + WidgetStateProperty? suggestionItemBackgroundColor, + WidgetStateProperty? suggestionItemShape, + }) { + return SfChatThemeData.raw( + actionButtonForegroundColor: + actionButtonForegroundColor ?? this.actionButtonForegroundColor, + actionButtonBackgroundColor: + actionButtonBackgroundColor ?? this.actionButtonBackgroundColor, + actionButtonFocusColor: + actionButtonFocusColor ?? this.actionButtonFocusColor, + actionButtonHoverColor: + actionButtonHoverColor ?? this.actionButtonHoverColor, + actionButtonSplashColor: + actionButtonSplashColor ?? this.actionButtonSplashColor, + actionButtonDisabledForegroundColor: + actionButtonDisabledForegroundColor ?? + this.actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor: + actionButtonDisabledBackgroundColor ?? + this.actionButtonDisabledBackgroundColor, + actionButtonElevation: + actionButtonElevation ?? this.actionButtonElevation, + actionButtonFocusElevation: + actionButtonFocusElevation ?? this.actionButtonFocusElevation, + actionButtonHoverElevation: + actionButtonHoverElevation ?? this.actionButtonHoverElevation, + actionButtonDisabledElevation: + actionButtonDisabledElevation ?? this.actionButtonDisabledElevation, + actionButtonHighlightElevation: + actionButtonHighlightElevation ?? this.actionButtonHighlightElevation, + actionButtonShape: actionButtonShape ?? this.actionButtonShape, + actionButtonMouseCursor: + actionButtonMouseCursor ?? this.actionButtonMouseCursor, + outgoingAvatarBackgroundColor: + outgoingAvatarBackgroundColor ?? this.outgoingAvatarBackgroundColor, + incomingAvatarBackgroundColor: + incomingAvatarBackgroundColor ?? this.incomingAvatarBackgroundColor, + outgoingMessageBackgroundColor: + outgoingMessageBackgroundColor ?? this.outgoingMessageBackgroundColor, + incomingMessageBackgroundColor: + incomingMessageBackgroundColor ?? this.incomingMessageBackgroundColor, + editorTextStyle: editorTextStyle ?? this.editorTextStyle, + outgoingContentTextStyle: + outgoingContentTextStyle ?? this.outgoingContentTextStyle, + incomingContentTextStyle: + incomingContentTextStyle ?? this.incomingContentTextStyle, + outgoingPrimaryHeaderTextStyle: + outgoingPrimaryHeaderTextStyle ?? this.outgoingPrimaryHeaderTextStyle, + incomingPrimaryHeaderTextStyle: + incomingPrimaryHeaderTextStyle ?? this.incomingPrimaryHeaderTextStyle, + outgoingSecondaryHeaderTextStyle: + outgoingSecondaryHeaderTextStyle ?? + this.outgoingSecondaryHeaderTextStyle, + incomingSecondaryHeaderTextStyle: + incomingSecondaryHeaderTextStyle ?? + this.incomingSecondaryHeaderTextStyle, + suggestionItemTextStyle: + suggestionItemTextStyle ?? this.suggestionItemTextStyle, + outgoingMessageShape: outgoingMessageShape ?? this.outgoingMessageShape, + incomingMessageShape: incomingMessageShape ?? this.incomingMessageShape, + suggestionBackgroundColor: + suggestionBackgroundColor ?? this.suggestionBackgroundColor, + suggestionBackgroundShape: + suggestionBackgroundShape ?? this.suggestionBackgroundShape, + suggestionItemBackgroundColor: + suggestionItemBackgroundColor ?? this.suggestionItemBackgroundColor, + suggestionItemShape: suggestionItemShape ?? this.suggestionItemShape, + ); + } + + static SfChatThemeData? lerp( + SfChatThemeData? a, + SfChatThemeData? b, + double t, + ) { + if (a == null && b == null) { + return null; + } + return SfChatThemeData( + actionButtonForegroundColor: Color.lerp( + a!.actionButtonForegroundColor, + b!.actionButtonForegroundColor, + t, + ), + actionButtonBackgroundColor: Color.lerp( + a.actionButtonBackgroundColor, + b.actionButtonBackgroundColor, + t, + ), + actionButtonFocusColor: Color.lerp( + a.actionButtonFocusColor, + b.actionButtonFocusColor, + t, + ), + actionButtonHoverColor: Color.lerp( + a.actionButtonHoverColor, + b.actionButtonHoverColor, + t, + ), + actionButtonSplashColor: Color.lerp( + a.actionButtonSplashColor, + b.actionButtonSplashColor, + t, + ), + actionButtonDisabledForegroundColor: Color.lerp( + a.actionButtonDisabledForegroundColor, + b.actionButtonDisabledForegroundColor, + t, + ), + actionButtonDisabledBackgroundColor: Color.lerp( + a.actionButtonDisabledBackgroundColor, + b.actionButtonDisabledBackgroundColor, + t, + ), + actionButtonElevation: + lerpDouble(a.actionButtonElevation, b.actionButtonElevation, t) ?? + 0.0, + actionButtonFocusElevation: + lerpDouble( + a.actionButtonFocusElevation, + b.actionButtonFocusElevation, + t, + ) ?? + 0.0, + actionButtonHoverElevation: + lerpDouble( + a.actionButtonHoverElevation, + b.actionButtonHoverElevation, + t, + ) ?? + 0.0, + actionButtonDisabledElevation: + lerpDouble( + a.actionButtonDisabledElevation, + b.actionButtonDisabledElevation, + t, + ) ?? + 0.0, + actionButtonHighlightElevation: + lerpDouble( + a.actionButtonHighlightElevation, + b.actionButtonHighlightElevation, + t, + ) ?? + 0.0, + actionButtonShape: ShapeBorder.lerp( + a.actionButtonShape, + b.actionButtonShape, + t, + ), + actionButtonMouseCursor: + t < 0.5 ? a.actionButtonMouseCursor : b.actionButtonMouseCursor, + outgoingAvatarBackgroundColor: Color.lerp( + a.outgoingAvatarBackgroundColor, + b.outgoingAvatarBackgroundColor, + t, + ), + incomingAvatarBackgroundColor: Color.lerp( + a.incomingAvatarBackgroundColor, + b.incomingAvatarBackgroundColor, + t, + ), + outgoingMessageBackgroundColor: Color.lerp( + a.outgoingMessageBackgroundColor, + b.outgoingMessageBackgroundColor, + t, + ), + incomingMessageBackgroundColor: Color.lerp( + a.incomingMessageBackgroundColor, + b.incomingMessageBackgroundColor, + t, + ), + editorTextStyle: TextStyle.lerp(a.editorTextStyle, b.editorTextStyle, t), + outgoingContentTextStyle: TextStyle.lerp( + a.outgoingContentTextStyle, + b.outgoingContentTextStyle, + t, + ), + incomingContentTextStyle: TextStyle.lerp( + a.incomingContentTextStyle, + b.incomingContentTextStyle, + t, + ), + outgoingPrimaryHeaderTextStyle: TextStyle.lerp( + a.outgoingPrimaryHeaderTextStyle, + b.outgoingPrimaryHeaderTextStyle, + t, + ), + incomingPrimaryHeaderTextStyle: TextStyle.lerp( + a.incomingPrimaryHeaderTextStyle, + b.incomingPrimaryHeaderTextStyle, + t, + ), + outgoingSecondaryHeaderTextStyle: TextStyle.lerp( + a.outgoingSecondaryHeaderTextStyle, + b.outgoingSecondaryHeaderTextStyle, + t, + ), + incomingSecondaryHeaderTextStyle: TextStyle.lerp( + a.incomingSecondaryHeaderTextStyle, + b.incomingSecondaryHeaderTextStyle, + t, + ), + suggestionItemTextStyle: WidgetStateProperty.lerp( + a.suggestionItemTextStyle, + b.suggestionItemTextStyle, + t, + TextStyle.lerp, + ), + outgoingMessageShape: ShapeBorder.lerp( + a.outgoingMessageShape, + b.outgoingMessageShape, + t, + ), + incomingMessageShape: ShapeBorder.lerp( + a.incomingMessageShape, + b.incomingMessageShape, + t, + ), + suggestionBackgroundColor: Color.lerp( + a.suggestionBackgroundColor, + b.suggestionBackgroundColor, + t, + ), + suggestionBackgroundShape: ShapeBorder.lerp( + a.suggestionBackgroundShape, + b.suggestionBackgroundShape, + t, + ), + suggestionItemBackgroundColor: WidgetStateProperty.lerp( + a.suggestionItemBackgroundColor, + b.suggestionItemBackgroundColor, + t, + Color.lerp, + ), + suggestionItemShape: WidgetStateProperty.lerp( + a.suggestionItemShape, + b.suggestionItemShape, + t, + ShapeBorder.lerp, + ), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + return other is SfChatThemeData && + other.actionButtonForegroundColor == actionButtonForegroundColor && + other.actionButtonBackgroundColor == actionButtonBackgroundColor && + other.actionButtonFocusColor == actionButtonFocusColor && + other.actionButtonHoverColor == actionButtonHoverColor && + other.actionButtonSplashColor == actionButtonSplashColor && + other.actionButtonDisabledForegroundColor == + actionButtonDisabledForegroundColor && + other.actionButtonDisabledBackgroundColor == + actionButtonDisabledBackgroundColor && + other.actionButtonElevation == actionButtonElevation && + other.actionButtonFocusElevation == actionButtonFocusElevation && + other.actionButtonHoverElevation == actionButtonHoverElevation && + other.actionButtonDisabledElevation == actionButtonDisabledElevation && + other.actionButtonHighlightElevation == + actionButtonHighlightElevation && + other.actionButtonShape == actionButtonShape && + other.actionButtonMouseCursor == actionButtonMouseCursor && + other.outgoingAvatarBackgroundColor == outgoingAvatarBackgroundColor && + other.incomingAvatarBackgroundColor == incomingAvatarBackgroundColor && + other.outgoingMessageBackgroundColor == + outgoingMessageBackgroundColor && + other.incomingMessageBackgroundColor == + incomingMessageBackgroundColor && + other.editorTextStyle == editorTextStyle && + other.outgoingContentTextStyle == outgoingContentTextStyle && + other.incomingContentTextStyle == incomingContentTextStyle && + other.outgoingPrimaryHeaderTextStyle == + outgoingPrimaryHeaderTextStyle && + other.incomingPrimaryHeaderTextStyle == + incomingPrimaryHeaderTextStyle && + other.outgoingSecondaryHeaderTextStyle == + outgoingSecondaryHeaderTextStyle && + other.incomingSecondaryHeaderTextStyle == + incomingSecondaryHeaderTextStyle && + other.suggestionItemTextStyle == suggestionItemTextStyle && + other.outgoingMessageShape == outgoingMessageShape && + other.incomingMessageShape == incomingMessageShape && + other.suggestionBackgroundColor == suggestionBackgroundColor && + other.suggestionBackgroundShape == suggestionBackgroundShape && + other.suggestionItemBackgroundColor == suggestionItemBackgroundColor && + other.suggestionItemShape == suggestionItemShape; + } + + @override + int get hashCode { + final List values = [ + actionButtonForegroundColor, + actionButtonBackgroundColor, + actionButtonFocusColor, + actionButtonHoverColor, + actionButtonSplashColor, + actionButtonDisabledForegroundColor, + actionButtonDisabledBackgroundColor, + actionButtonElevation, + actionButtonFocusElevation, + actionButtonHoverElevation, + actionButtonDisabledElevation, + actionButtonHighlightElevation, + actionButtonShape, + actionButtonMouseCursor, + outgoingAvatarBackgroundColor, + incomingAvatarBackgroundColor, + outgoingMessageBackgroundColor, + incomingMessageBackgroundColor, + editorTextStyle, + outgoingContentTextStyle, + incomingContentTextStyle, + outgoingPrimaryHeaderTextStyle, + incomingPrimaryHeaderTextStyle, + outgoingSecondaryHeaderTextStyle, + incomingSecondaryHeaderTextStyle, + suggestionItemTextStyle, + outgoingMessageShape, + incomingMessageShape, + suggestionBackgroundColor, + suggestionBackgroundShape, + suggestionItemBackgroundColor, + suggestionItemShape, + ]; + return Object.hashAll(values); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + const SfChatThemeData defaultData = SfChatThemeData(); + properties.add( + ColorProperty( + 'actionButtonForegroundColor', + actionButtonForegroundColor, + defaultValue: defaultData.actionButtonForegroundColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonBackgroundColor', + actionButtonBackgroundColor, + defaultValue: defaultData.actionButtonBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonFocusColor', + actionButtonFocusColor, + defaultValue: defaultData.actionButtonFocusColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonHoverColor', + actionButtonHoverColor, + defaultValue: defaultData.actionButtonHoverColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonSplashColor', + actionButtonSplashColor, + defaultValue: defaultData.actionButtonSplashColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonDisabledForegroundColor', + actionButtonDisabledForegroundColor, + defaultValue: defaultData.actionButtonDisabledForegroundColor, + ), + ); + properties.add( + ColorProperty( + 'actionButtonDisabledBackgroundColor', + actionButtonDisabledBackgroundColor, + defaultValue: defaultData.actionButtonDisabledBackgroundColor, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonElevation', + actionButtonElevation, + defaultValue: defaultData.actionButtonElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonFocusElevation', + actionButtonFocusElevation, + defaultValue: defaultData.actionButtonFocusElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonHoverElevation', + actionButtonHoverElevation, + defaultValue: defaultData.actionButtonHoverElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonDisabledElevation', + actionButtonDisabledElevation, + defaultValue: defaultData.actionButtonDisabledElevation, + ), + ); + properties.add( + DoubleProperty( + 'actionButtonHighlightElevation', + actionButtonHighlightElevation, + defaultValue: defaultData.actionButtonHighlightElevation, + ), + ); + properties.add( + DiagnosticsProperty( + 'actionButtonShape', + actionButtonShape, + defaultValue: defaultData.actionButtonShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'actionButtonMouseCursor', + actionButtonMouseCursor, + defaultValue: defaultData.actionButtonMouseCursor, + ), + ); + properties.add( + ColorProperty( + 'outgoingAvatarBackgroundColor', + outgoingAvatarBackgroundColor, + defaultValue: defaultData.outgoingAvatarBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'incomingAvatarBackgroundColor', + incomingAvatarBackgroundColor, + defaultValue: defaultData.incomingAvatarBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'outgoingMessageBackgroundColor', + outgoingMessageBackgroundColor, + defaultValue: defaultData.outgoingMessageBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'incomingMessageBackgroundColor', + incomingMessageBackgroundColor, + defaultValue: defaultData.incomingMessageBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'editorTextStyle', + editorTextStyle, + defaultValue: defaultData.editorTextStyle, + ), + ); + + properties.add( + DiagnosticsProperty( + 'outgoingContentTextStyle', + outgoingContentTextStyle, + defaultValue: defaultData.outgoingContentTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'incomingContentTextStyle', + incomingContentTextStyle, + defaultValue: defaultData.incomingContentTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'outgoingPrimaryHeaderTextStyle', + outgoingPrimaryHeaderTextStyle, + defaultValue: defaultData.outgoingPrimaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'incomingPrimaryHeaderTextStyle', + incomingPrimaryHeaderTextStyle, + defaultValue: defaultData.incomingPrimaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'outgoingSecondaryHeaderTextStyle', + outgoingSecondaryHeaderTextStyle, + defaultValue: defaultData.outgoingSecondaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'incomingSecondaryHeaderTextStyle', + incomingSecondaryHeaderTextStyle, + defaultValue: defaultData.incomingSecondaryHeaderTextStyle, + ), + ); + properties.add( + DiagnosticsProperty>( + 'suggestionItemTextStyle', + suggestionItemTextStyle, + defaultValue: defaultData.suggestionItemTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'outgoingMessageShape', + outgoingMessageShape, + defaultValue: defaultData.outgoingMessageShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'incomingMessageShape', + incomingMessageShape, + defaultValue: defaultData.incomingMessageShape, + ), + ); + properties.add( + DiagnosticsProperty( + 'suggestionBackgroundColor', + suggestionBackgroundColor, + defaultValue: defaultData.suggestionBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'suggestionBackgroundShape', + suggestionBackgroundShape, + defaultValue: defaultData.suggestionBackgroundShape, + ), + ); + properties.add( + DiagnosticsProperty>( + 'suggestionItemBackgroundColor', + suggestionItemBackgroundColor, + defaultValue: defaultData.suggestionItemBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty>( + 'suggestionItemShape', + suggestionItemShape, + defaultValue: defaultData.suggestionItemShape, + ), + ); + } +} diff --git a/packages/syncfusion_flutter_core/lib/src/theme/color_scheme.dart b/packages/syncfusion_flutter_core/lib/src/theme/color_scheme.dart new file mode 100644 index 000000000..52ecc79f5 --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/theme/color_scheme.dart @@ -0,0 +1,549 @@ +import 'package:flutter/material.dart'; + +/// A material spec colors that can be used to configure the color properties +/// of Syncfusion components. +class SfColorScheme { + /// Creating an argument constructor of [SfColorScheme] class with M2 design. + SfColorScheme.m2({ + required this.useMaterial3, + required this.brightness, + required Color primary, + required Color onPrimary, + required Color primaryContainer, + required Color secondaryContainer, + required Color surface, + required Color onSurface, + required Color surfaceVariant, + required Color onSurfaceVariant, + required Color inverseSurface, + required Color onInverseSurface, + required this.outline, + required Color outlineVariant, + required this.splashColor, + required this.hoverColor, + required this.highlightColor, + required this.valueIndicatorColor, + required this.textColor, + required Color transparent, + required this.palettes, + }) { + this.primary = MaterialColor(primary.toInt32, { + 1: primary, + 26: primary.withValues(alpha: 0.1), + 27: + brightness == Brightness.light + ? const Color.fromRGBO(41, 171, 226, 0.1) + : const Color.fromRGBO(255, 217, 57, 0.3), + 28: + brightness == Brightness.light + ? const Color.fromRGBO(41, 171, 226, 1) + : const Color.fromRGBO(255, 255, 255, 1), + 20: primary.withValues(alpha: 0.08), + 30: primary.withValues(alpha: 0.1), + 31: primary.withValues(alpha: 0.12), + 61: primary.withValues(alpha: 0.24), + 138: primary.withValues(alpha: 0.54), + 97: + brightness == Brightness.light + ? const Color.fromRGBO(97, 97, 97, 1) + : const Color.fromRGBO(224, 224, 224, 1), + 98: + brightness == Brightness.light + ? const Color.fromRGBO(98, 0, 238, 1) + : const Color.fromRGBO(187, 134, 252, 1), + }); + + this.onPrimary = MaterialColor(onPrimary.toInt32, { + 74: onPrimary.withValues(alpha: 0.29), + 75: + brightness == Brightness.light + ? onPrimary.withValues(alpha: 0.29) + : surface.withValues(alpha: 0.56), + 31: onPrimary.withValues(alpha: 0.12), + 138: onPrimary.withValues(alpha: 0.54), + }); + + this.primaryContainer = + MaterialColor(primaryContainer.toInt32, { + 20: + brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.08) + : Colors.white.withValues(alpha: 0.12), + }); + + this.secondaryContainer = + MaterialColor(secondaryContainer.toInt32, { + 204: secondaryContainer.withValues(alpha: 0.8), + 205: secondaryContainer.withValues(alpha: 0.8), + }); + + this.surface = MaterialColor(surface.toInt32, { + 0: surface.withValues(alpha: 0.0001), + 31: surface.withValues(alpha: 0.12), + 143: surface.withValues(alpha: 0.56), + 150: surface, + 250: + brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF303030), + 251: + brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF303030), + 255: surface, + }); + + this.onSurface = MaterialColor(onSurface.toInt32, { + 0: onSurface.withValues(alpha: 0.001), + 10: onSurface.withValues(alpha: 0.04), + 11: onSurface.withValues(alpha: 0.04), + 19: onSurface.withValues(alpha: 0.08), + 20: onSurface.withValues(alpha: 0.08), + 21: onSurface.withValues(alpha: 0.081), + 22: onSurface.withValues(alpha: 0.08), + 24: + brightness == Brightness.light + ? onPrimary + : onSurface.withValues(alpha: 0.09), + 23: onSurface.withValues(alpha: 0.09), + 28: onSurface.withValues(alpha: 0.11), + 29: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.11) + : onSurface.withValues(alpha: 0.24), + 31: onSurface.withValues(alpha: 0.12), + 32: onSurface.withValues(alpha: 0.12), + 33: onSurface.withValues(alpha: 0.12), + 34: onSurface.withValues(alpha: 0.12), + 35: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.12) + : onSurface.withValues(alpha: 0.24), + 41: onSurface.withValues(alpha: 0.16), + 42: onSurface.withValues(alpha: 0.16), + 43: onSurface.withValues(alpha: 0.17), + 46: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.18) + : onSurface.withValues(alpha: 0.27), + 47: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.18) + : onSurface.withValues(alpha: 0.43), + 61: onSurface.withValues(alpha: 0.24), + 66: onSurface.withValues(alpha: 0.26), + 69: onSurface.withValues(alpha: 0.27), + 70: + brightness == Brightness.light + ? const Color(0xFF212121) + : const Color(0xFFE0E0E0), + 71: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.28) + : onSurface.withValues(alpha: 0.33), + 76: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.26) + : onSurface.withValues(alpha: 0.30), + 77: onSurface.withValues(alpha: 0.30), + 82: onSurface.withValues(alpha: 0.32), + 84: onSurface.withValues(alpha: 0.33), + 92: onSurface.withValues(alpha: 0.36), + 94: onSurface.withValues(alpha: 0.37), + 95: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.37) + : onSurface.withValues(alpha: 0.17), + 97: onSurface.withValues(alpha: 0.38), + 98: onSurface.withValues(alpha: 0.38), + 110: onSurface.withValues(alpha: 0.43), + 135: onSurface.withValues(alpha: 0.53), + 138: onSurface.withValues(alpha: 0.54), + 153: onSurface.withValues(alpha: 0.6), + 154: onSurface.withValues(alpha: 0.6), + 179: onSurface.withValues(alpha: 0.7), + 184: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.72) + : onSurface, + 217: onSurface.withValues(alpha: 0.85), + 222: onSurface.withValues(alpha: 0.87), + 223: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.87) + : onSurface, + 224: + brightness == Brightness.light + ? const Color.fromRGBO(97, 97, 97, 1) + : const Color.fromRGBO(224, 224, 224, 1), + 227: onSurface.withValues(alpha: 0.89), + 228: onSurface.withValues(alpha: 0.89), + 255: onSurface, + 256: + brightness == Brightness.light + ? const Color.fromRGBO(117, 117, 117, 1) + : const Color.fromRGBO(245, 245, 245, 1), + }); + + this.surfaceVariant = MaterialColor(surfaceVariant.toInt32, { + 219: + brightness == Brightness.light + ? const Color.fromRGBO(219, 219, 219, 1) + : const Color.fromRGBO(70, 74, 86, 1), + }); + + this.onSurfaceVariant = + MaterialColor(onSurfaceVariant.toInt32, { + 97: onSurfaceVariant.withValues(alpha: 0.38), + 138: + brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.54) + : Colors.white.withValues(alpha: 0.54), + 153: + brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + 104: + brightness == Brightness.light + ? const Color.fromRGBO(104, 104, 104, 1) + : const Color.fromRGBO(242, 242, 242, 1), + 66: + brightness == Brightness.light + ? const Color.fromRGBO(66, 66, 66, 1) + : const Color.fromRGBO(255, 255, 255, 1), + 79: + brightness == Brightness.light + ? const Color.fromRGBO(79, 79, 79, 1) + : const Color.fromRGBO(255, 255, 255, 1), + 80: + brightness == Brightness.light + ? const Color.fromRGBO(79, 79, 79, 1) + : const Color.fromRGBO(150, 150, 150, 1), + 53: + brightness == Brightness.light + ? const Color.fromRGBO(53, 53, 53, 1) + : const Color.fromRGBO(255, 255, 255, 1), + 255: + brightness == Brightness.light + ? const Color.fromRGBO(0, 0, 0, 1) + : const Color.fromRGBO(255, 255, 255, 1), + }); + + this.inverseSurface = MaterialColor(inverseSurface.toInt32, { + 255: + brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF424242), + 257: Colors.transparent, + 79: + brightness == Brightness.light + ? const Color.fromRGBO(79, 79, 79, 1) + : const Color.fromRGBO(255, 255, 255, 1), + 258: + brightness == Brightness.light + ? const Color.fromRGBO(0, 8, 22, 1) + : const Color.fromRGBO(255, 255, 255, 1), + }); + + this.onInverseSurface = + MaterialColor(onInverseSurface.toInt32, { + 150: + brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(150, 150, 150, 1), + 255: Colors.white, + 256: + brightness == Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(0, 0, 0, 1), + }); + + this.outlineVariant = MaterialColor(outlineVariant.toInt32, { + 41: + brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.16) + : Colors.white.withValues(alpha: 0.16), + 255: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.53) + : onSurface.withValues(alpha: 0.85), + 181: + brightness == Brightness.light + ? const Color.fromRGBO(181, 181, 181, 1) + : const Color.fromRGBO(101, 101, 101, 1), + 182: + brightness == Brightness.light + ? const Color.fromRGBO(181, 181, 181, 1) + : const Color.fromRGBO(191, 191, 191, 1), + }); + + this.transparent = MaterialColor(transparent.toInt32, { + 0: transparent.withValues(alpha: 0.0001), + 20: transparent, + 255: transparent, + }); + + scrim = MaterialColor(onSurface.toInt32, { + 82: + brightness == Brightness.light + ? Colors.white.withValues(alpha: 0.75) + : const Color.fromRGBO(48, 48, 48, 1).withValues(alpha: 0.75), + }); + } + + /// Creating an argument constructor of [SfColorScheme] class with M3 design. + SfColorScheme.m3({ + required this.useMaterial3, + required this.brightness, + required Color primary, + required Color onPrimary, + required Color primaryContainer, + required Color secondaryContainer, + required Color surface, + required Color onSurface, + required Color surfaceVariant, + required Color onSurfaceVariant, + required Color inverseSurface, + required Color onInverseSurface, + required this.outline, + required Color outlineVariant, + required this.splashColor, + required this.hoverColor, + required this.highlightColor, + required this.valueIndicatorColor, + required this.textColor, + required Color transparent, + required Color scrim, + required this.palettes, + }) { + this.primary = MaterialColor(primary.toInt32, { + 1: primaryContainer, + 27: + brightness == Brightness.light + ? primary.withValues(alpha: 0.1) + : primary.withValues(alpha: 0.3), + 28: primary, + 30: primary.withValues(alpha: 0.12), + 31: primary.withValues(alpha: 0.08), + 61: surfaceVariant, + 138: onSurfaceVariant.withValues(alpha: 0.38), + 97: primary, + 98: primary, + }); + + this.onPrimary = MaterialColor(onPrimary.toInt32, { + 31: onSurfaceVariant.withValues(alpha: 0.38), + 75: outlineVariant, + 138: onPrimary.withValues(alpha: 0.38), + }); + + this.primaryContainer = MaterialColor( + primaryContainer.toInt32, + {20: primaryContainer}, + ); + + this.secondaryContainer = MaterialColor( + secondaryContainer.toInt32, + { + 204: secondaryContainer.withValues(alpha: 0.8), + 205: surfaceVariant, + }, + ); + + this.surface = MaterialColor(surface.toInt32, { + 0: surface.withValues(alpha: 0.0001), + 31: surface.withValues(alpha: 0.12), + 150: + brightness == Brightness.light + ? const Color.fromRGBO(150, 60, 112, 1) + : const Color.fromRGBO(77, 170, 255, 1), + 250: surface, + 251: + brightness == Brightness.light + ? const Color(0xFFEEE8F4) + : const Color(0xFF302D38), + 255: surface, + }); + + this.onSurface = MaterialColor(onSurface.toInt32, { + 0: + brightness == Brightness.light + ? const Color(0xFFEEE8F4) + : const Color(0xFF302D38), + 10: primary.withValues(alpha: 0.08), + 11: onSurface.withValues(alpha: 0.04), + 19: primaryContainer, + 20: primary.withValues(alpha: 0.12), + 22: surfaceVariant, + 24: + brightness == Brightness.light + ? onPrimary + : onSurface.withValues(alpha: 0.09), + 29: surfaceVariant, + 31: onSurface.withValues(alpha: 0.12), + 32: outline, + 33: outlineVariant, + 34: onSurfaceVariant.withValues(alpha: 0.38), + 35: surfaceVariant, + 42: outlineVariant, + 46: outlineVariant, + 47: outlineVariant, + 61: onSurface.withValues(alpha: 0.38), + 66: primary, + 70: onSurface, + 71: outlineVariant, + 76: surfaceVariant, + 82: onSurface.withValues(alpha: 0.38), + 92: onSurface.withValues(alpha: 0.36), + 94: outlineVariant, + 95: + brightness == Brightness.light + ? onSurface.withValues(alpha: 0.37) + : onSurface.withValues(alpha: 0.17), + 97: onSurface.withValues(alpha: 0.38), + 98: outline, + 153: onSurface.withValues(alpha: 0.6), + 154: onSurfaceVariant, + 184: onSurface, + 222: onSurface.withValues(alpha: 0.87), + 223: onSurfaceVariant, + 224: inverseSurface, + 227: onSurface.withValues(alpha: 0.89), + 228: const Color(0xFF49454F), + 255: onSurfaceVariant, + 256: onSurface, + }); + + this.surfaceVariant = MaterialColor(surfaceVariant.toInt32, { + 219: surfaceVariant, + }); + + this.onSurfaceVariant = + MaterialColor(onSurfaceVariant.toInt32, { + 138: onSurfaceVariant, + 153: onSurfaceVariant, + 104: onSurfaceVariant, + 66: onSurfaceVariant, + 79: onSurfaceVariant, + 80: onSurfaceVariant, + 53: onSurfaceVariant, + 255: onSurfaceVariant, + }); + + this.inverseSurface = MaterialColor(inverseSurface.toInt32, { + 255: inverseSurface, + 257: inverseSurface, + 79: inverseSurface, + 258: inverseSurface, + }); + + this.onInverseSurface = MaterialColor( + onInverseSurface.toInt32, + { + 150: onInverseSurface, + 255: onInverseSurface, + 256: onInverseSurface, + }, + ); + + this.outlineVariant = MaterialColor(outlineVariant.toInt32, { + 41: outlineVariant, + 255: outlineVariant, + 181: outlineVariant, + 182: outlineVariant, + }); + + this.transparent = MaterialColor(transparent.toInt32, { + 0: transparent.withValues(alpha: 0.0001), + 20: primary.withValues(alpha: 0.08), + 255: Colors.white, + }); + + this.scrim = MaterialColor(onSurface.toInt32, { + 82: scrim.withValues(alpha: 0.32), + }); + } + + /// A boolean property to decide whether to use material 3 or not. + bool useMaterial3; + + /// A property to decide the brightness of the color scheme. + Brightness brightness; + + /// A primary color of the color scheme. + late MaterialColor primary; + + /// A color that is used to paint the text on the primary color. + late MaterialColor onPrimary; + + /// A color that is used to paint the background of the primary color. + late MaterialColor primaryContainer; + + /// A secondary color of the color scheme. + late MaterialColor secondaryContainer; + + /// A surface color of the color scheme. + late MaterialColor surface; + + /// A color that is used to paint the text on the surface color. + late MaterialColor onSurface; + + /// A color that is used to paint the background of the surface color. + late MaterialColor surfaceVariant; + + /// A color that is used to paint the text on the surface variant color. + late MaterialColor onSurfaceVariant; + + /// A color that is used to paint the inverse surface color. + late MaterialColor inverseSurface; + + /// A color that is used to paint the text on the inverse surface color. + late MaterialColor onInverseSurface; + + /// A color that is used to paint the outline of the components. + Color outline; + + /// A color that is used to paint the outline of the components. + late MaterialColor outlineVariant; + + /// A color that is used to paint the splash effect of the components. + Color splashColor; + + /// A color that is used to paint the hover effect of the components. + Color hoverColor; + + /// A color that is used to paint the highlight effect of the components. + Color highlightColor; + + /// A color that is used to paint the value indicator of the components. + Color valueIndicatorColor; + + /// A color that is used to paint the text on the components. + Color textColor; + + /// A color that is used to paint the transparent color. + late MaterialColor transparent; + + /// A color that is used to paint the scrim color. + late MaterialColor scrim; + + /// A list of colors that can be used to paint the components. + List palettes; +} + +/// The value property in the color class is deprecated in Flutter 3.27.0 +/// version. An alternate property for value is not provided in the framework. +/// Therefore, we have explicitly implemented the functionality of value and +/// used it in the source. This logic was taken from the framework 3.24 channel. +extension _ColorExtension on Color { + static int _floatToInt8(double x) { + return (x * 255.0).round() & 0xff; + } + + int get toInt32 { + return _floatToInt8(a) << 24 | + _floatToInt8(r) << 16 | + _floatToInt8(g) << 8 | + _floatToInt8(b) << 0; + } +} 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 849b69395..68e6fea85 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart @@ -21,7 +21,7 @@ import '../../theme.dart'; class SfDataGridTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. const SfDataGridTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant [SfDataGrid] /// widgets. @@ -107,7 +107,73 @@ class SfDataGridTheme extends InheritedTheme { class SfDataGridThemeData with Diagnosticable { /// Create a [SfDataGridThemeData] that's used to configure a /// [SfDataGridTheme]. - factory SfDataGridThemeData({ + const SfDataGridThemeData({ + this.gridLineColor, + this.gridLineStrokeWidth, + this.selectionColor, + this.currentCellStyle, + this.frozenPaneLineColor, + this.frozenPaneLineWidth, + this.sortIconColor, + this.headerColor, + this.headerHoverColor, + this.frozenPaneElevation, + this.columnResizeIndicatorColor, + this.columnResizeIndicatorStrokeWidth, + this.rowHoverColor, + this.rowHoverTextStyle, + this.sortIcon, + this.filterIcon, + this.filterIconColor, + this.filterIconHoverColor, + this.sortOrderNumberColor, + this.sortOrderNumberBackgroundColor, + this.filterPopupTextStyle, + this.filterPopupDisabledTextStyle, + this.columnDragIndicatorColor, + this.columnDragIndicatorStrokeWidth, + this.groupExpanderIcon, + this.indentColumnWidth, + this.indentColumnColor, + this.captionSummaryRowColor, + this.filterPopupCheckColor, + this.filterPopupCheckboxFillColor, + this.filterPopupInputBorderColor, + this.filterPopupBackgroundColor, + this.filterPopupIconColor, + this.filterPopupDisabledIconColor, + this.advancedFilterPopupDropdownColor, + this.noMatchesFilteringLabelColor, + this.okFilteringLabelColor, + this.okFilteringLabelButtonColor, + this.cancelFilteringLabelColor, + this.cancelFilteringLabelButtonColor, + this.searchAreaFocusedBorderColor, + this.searchAreaCursorColor, + this.andRadioActiveColor, + this.andRadioFillColor, + this.orRadioActiveColor, + this.orRadioFillColor, + this.advancedFilterValueDropdownFocusedBorderColor, + this.advancedFilterTypeDropdownFocusedBorderColor, + this.calendarIconColor, + this.advancedFilterValueTextAreaCursorColor, + this.caseSensitiveIconColor, + this.caseSensitiveIconActiveColor, + this.advancedFilterPopupDropdownIconColor, + this.searchIconColor, + this.closeIconColor, + this.advancedFilterValueDropdownIconColor, + this.advancedFilterTypeDropdownIconColor, + this.filterPopupTopDividerColor, + this.filterPopupBottomDividerColor, + this.okFilteringLabelDisabledButtonColor, + this.appBarBottomBorderColor, + }); + + /// Create a [SfDataGridThemeData] that's used to configure a + /// [SfDataGridTheme]. + factory SfDataGridThemeData.raw({ Brightness? brightness, Color? gridLineColor, double? gridLineStrokeWidth, @@ -124,78 +190,124 @@ class SfDataGridThemeData with Diagnosticable { double? columnResizeIndicatorStrokeWidth, TextStyle? rowHoverTextStyle, Widget? sortIcon, + Widget? filterIcon, + Color? filterIconColor, + Color? filterIconHoverColor, + Color? sortOrderNumberColor, + Color? sortOrderNumberBackgroundColor, + TextStyle? filterPopupTextStyle, + TextStyle? filterPopupDisabledTextStyle, + Color? columnDragIndicatorColor, + double? columnDragIndicatorStrokeWidth, + Widget? groupExpanderIcon, + double? indentColumnWidth, + Color? indentColumnColor, + Color? captionSummaryRowColor, + Color? filterPopupCheckColor, + WidgetStateProperty? filterPopupCheckboxFillColor, + Color? filterPopupInputBorderColor, + Color? filterPopupBackgroundColor, + Color? filterPopupIconColor, + Color? filterPopupDisabledIconColor, + Color? advancedFilterPopupDropdownColor, + Color? noMatchesFilteringLabelColor, + Color? okFilteringLabelColor, + Color? okFilteringLabelButtonColor, + Color? cancelFilteringLabelColor, + Color? cancelFilteringLabelButtonColor, + Color? searchAreaCursorColor, + Color? searchAreaFocusedBorderColor, + Color? andRadioActiveColor, + WidgetStateProperty? andRadioFillColor, + Color? orRadioActiveColor, + WidgetStateProperty? orRadioFillColor, + Color? advancedFilterValueDropdownFocusedBorderColor, + Color? advancedFilterTypeDropdownFocusedBorderColor, + Color? calendarIconColor, + Color? advancedFilterValueTextAreaCursorColor, + Color? searchIconColor, + Color? closeIconColor, + Color? advancedFilterPopupDropdownIconColor, + Color? caseSensitiveIconActiveColor, + Color? caseSensitiveIconColor, + Color? advancedFilterTypeDropdownIconColor, + Color? advancedFilterValueDropdownIconColor, + Color? filterPopupTopDividerColor, + Color? filterPopupBottomDividerColor, + Color? okFilteringLabelDisabledButtonColor, + Color? appBarBottomBorderColor, }) { - return SfDataGridThemeData.raw( - brightness: brightness, - gridLineColor: gridLineColor, - gridLineStrokeWidth: gridLineStrokeWidth, - selectionColor: selectionColor, - currentCellStyle: currentCellStyle, - frozenPaneLineColor: frozenPaneLineColor, - frozenPaneLineWidth: frozenPaneLineWidth, - headerHoverColor: headerHoverColor, - sortIconColor: sortIconColor, - headerColor: headerColor, - frozenPaneElevation: frozenPaneElevation, - rowHoverColor: rowHoverColor, - columnResizeIndicatorColor: columnResizeIndicatorColor, - columnResizeIndicatorStrokeWidth: columnResizeIndicatorStrokeWidth, - rowHoverTextStyle: rowHoverTextStyle, - sortIcon: sortIcon); + brightness = brightness ?? Brightness.light; + return SfDataGridThemeData( + gridLineColor: gridLineColor, + gridLineStrokeWidth: gridLineStrokeWidth, + selectionColor: selectionColor, + currentCellStyle: currentCellStyle, + frozenPaneLineColor: frozenPaneLineColor, + frozenPaneLineWidth: frozenPaneLineWidth, + headerHoverColor: headerHoverColor, + sortIconColor: sortIconColor, + headerColor: headerColor, + frozenPaneElevation: frozenPaneElevation, + rowHoverColor: rowHoverColor, + columnResizeIndicatorColor: columnResizeIndicatorColor, + columnResizeIndicatorStrokeWidth: columnResizeIndicatorStrokeWidth, + rowHoverTextStyle: rowHoverTextStyle, + sortIcon: sortIcon, + filterIcon: filterIcon, + filterIconColor: filterIconColor, + filterIconHoverColor: filterIconHoverColor, + sortOrderNumberColor: sortOrderNumberColor, + sortOrderNumberBackgroundColor: sortOrderNumberBackgroundColor, + filterPopupTextStyle: filterPopupTextStyle, + filterPopupDisabledTextStyle: filterPopupDisabledTextStyle, + columnDragIndicatorColor: columnDragIndicatorColor, + columnDragIndicatorStrokeWidth: columnDragIndicatorStrokeWidth, + groupExpanderIcon: groupExpanderIcon, + indentColumnWidth: indentColumnWidth, + indentColumnColor: indentColumnColor, + captionSummaryRowColor: captionSummaryRowColor, + filterPopupCheckColor: filterPopupCheckColor, + filterPopupCheckboxFillColor: filterPopupCheckboxFillColor, + filterPopupInputBorderColor: filterPopupInputBorderColor, + filterPopupBackgroundColor: filterPopupBackgroundColor, + filterPopupIconColor: filterPopupIconColor, + filterPopupDisabledIconColor: filterPopupDisabledIconColor, + advancedFilterPopupDropdownColor: advancedFilterPopupDropdownColor, + noMatchesFilteringLabelColor: noMatchesFilteringLabelColor, + okFilteringLabelColor: okFilteringLabelColor, + okFilteringLabelButtonColor: okFilteringLabelButtonColor, + cancelFilteringLabelColor: cancelFilteringLabelColor, + cancelFilteringLabelButtonColor: cancelFilteringLabelButtonColor, + searchAreaCursorColor: searchAreaCursorColor, + searchAreaFocusedBorderColor: searchAreaFocusedBorderColor, + andRadioActiveColor: andRadioActiveColor, + andRadioFillColor: andRadioFillColor, + orRadioActiveColor: orRadioActiveColor, + orRadioFillColor: orRadioFillColor, + calendarIconColor: calendarIconColor, + advancedFilterTypeDropdownFocusedBorderColor: + advancedFilterTypeDropdownFocusedBorderColor, + advancedFilterValueDropdownFocusedBorderColor: + advancedFilterValueDropdownFocusedBorderColor, + advancedFilterValueTextAreaCursorColor: + advancedFilterValueTextAreaCursorColor, + searchIconColor: searchIconColor, + closeIconColor: closeIconColor, + advancedFilterPopupDropdownIconColor: + advancedFilterPopupDropdownIconColor, + caseSensitiveIconActiveColor: caseSensitiveIconActiveColor, + caseSensitiveIconColor: caseSensitiveIconColor, + advancedFilterTypeDropdownIconColor: advancedFilterTypeDropdownIconColor, + advancedFilterValueDropdownIconColor: + advancedFilterValueDropdownIconColor, + filterPopupBottomDividerColor: filterPopupBottomDividerColor, + filterPopupTopDividerColor: filterPopupTopDividerColor, + okFilteringLabelDisabledButtonColor: okFilteringLabelDisabledButtonColor, + appBarBottomBorderColor: appBarBottomBorderColor, + ); } - /// Create a [SfDataGridThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfDataGridThemeData] constructor. - /// - 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, - required this.columnResizeIndicatorColor, - required this.columnResizeIndicatorStrokeWidth, - required this.rowHoverColor, - required this.rowHoverTextStyle, - required this.sortIcon}); - - /// The brightness of the overall theme of the - /// application for the [SfDataGrid] widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).colorScheme.brightness], brightness for - /// datagrid widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// dataGridThemeData: SfDataGridThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfDataGrid(), - /// ), - /// ) - /// ); - /// } - /// ``` - final Brightness? brightness; - /// The color for grid line. /// /// ```dart @@ -330,14 +442,273 @@ class SfDataGridThemeData with Diagnosticable { /// - /// If icon is given, the animation will be automatically applied and - /// rotated according to sorting order. + /// If the [Icon] type is assigned, + /// the animation will be automatically applied and rotated + /// according to sorting order. + /// + /// If you want to change the icon based on each state of the sorting, + /// you can use the [Builder] widget and return + /// the respective icon for the state. + /// You have to return the icons for all the three states even + /// if you want to change the icon for specific state. + /// + /// The below example shows how to load different icon for each state + /// i.e. ascending, descending and unsorted order. + /// /// - /// [SfDataGridThemeData.sortIconColor] – The color of the sort icon. + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: const Text('Flutter SfDataGrid')), + /// body: SfDataGridTheme( + /// data: SfDataGridThemeData( + /// sortIcon: Builder( + /// builder: (context) { + /// Widget? icon; + /// String columnName = ''; + /// context.visitAncestorElements((element) { + /// if (element is GridHeaderCellElement) { + /// columnName = element.column.columnName; + /// } + /// return true; + /// }); + + /// var column = _employeeDataSource.sortedColumns + /// .where((element) => element.name == columnName) + /// .firstOrNull; + /// if (column != null) { + /// if (column.sortDirection == + /// DataGridSortDirection.ascending) { + /// icon = const Icon(Icons.arrow_circle_up_rounded, + /// size: 16); + /// } else if (column.sortDirection == + /// DataGridSortDirection.descending) { + /// icon = const Icon(Icons.arrow_circle_down_rounded, + /// size: 16); + /// } + /// } + + /// return icon ?? const Icon(Icons.sort_rounded, size: 16); + /// }, + /// ), + /// ), + /// child: SfDataGrid(), + /// ), + /// ); + /// } + /// ``` final Widget? sortIcon; + /// The icon to indicate the filtering applied in column. + /// + /// If you want to change the icon filter or filtered state, you can use the + /// [Builder](https://api.flutter.dev/flutter/widgets/Builder-class.html) + /// widget and return the respective icon for the state. You have to return + /// the icons for both the states even if you want to change the icon + /// for specific state. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid', + /// overflow: TextOverflow.ellipsis), + /// ), + /// body: SfDataGridTheme( + /// data: SfDataGridThemeData(filterIcon: Builder( + /// builder: (context) { + /// Widget? icon; + /// String columnName = ''; + /// context.visitAncestorElements((element) { + /// if (element is GridHeaderCellElement) { + /// columnName = element.column.columnName; + /// } + /// return true; + /// }); + /// var column = _employeeDataSource.filterConditions.keys + /// .where((element) => element == columnName) + /// .firstOrNull; + + /// if (column != null) { + /// icon = const Icon( + /// Icons.filter_alt_outlined, + /// size: 20, + /// color: Colors.purple, + /// ); + /// } + /// return icon ?? + /// const Icon( + /// Icons.filter_alt_off_outlined, + /// size: 20, + /// color: Colors.deepOrange, + /// ); + /// }, + /// )), + /// child: SfDataGrid( + /// source: _employeeDataSource, + /// allowFiltering: true, + /// allowSorting: true, + /// columns: getColumns(), + /// ), + /// ), + /// ); + /// } + /// ``` + final Widget? filterIcon; + + /// The color of the filter icon which indicates whether + /// the column is filtered or not. + /// + /// This is not applicable when `filterIcon` property is set. + /// This applies the color to default filter icon only. + final Color? filterIconColor; + + /// The color for the filter icon when a pointer is hovering over it. + /// + /// This is not applicable when `filterIcon` property is set. + /// This applies the color to default filter icon only. + final Color? filterIconHoverColor; + + /// The color of the number displayed when the order of the sorting is shown. + final Color? sortOrderNumberColor; + + /// The color of the rounded background displayed + /// when the order of the sorting is shown. + final Color? sortOrderNumberBackgroundColor; + + /// The [TextStyle] of the options in filter popup menu except the items + /// which are already selected. + final TextStyle? filterPopupTextStyle; + + /// The [TextStyle] of the disabled options in filter popup menu. + final TextStyle? filterPopupDisabledTextStyle; + + /// The stroke width of the column drag indicator. + final double? columnDragIndicatorStrokeWidth; + + /// The color of the column drag indicator. + final Color? columnDragIndicatorColor; + + /// This icon indicates the expand-collapse state of a group in a + /// caption summary row. + /// + /// It will be displayed only if the + /// [SfDataGrid.autoExpandCollapseGroup] property is set to true. + final Widget? groupExpanderIcon; + + /// The width of an indent column. + /// + /// Defaults to 40.0. + final double? indentColumnWidth; + + /// The color of an indent column. + final Color? indentColumnColor; + + /// The color of the caption summary row. + final Color? captionSummaryRowColor; + + /// The checkmark color of the checkbox in the filter popup. + final Color? filterPopupCheckColor; + + /// The fill color of the checkbox in the filter popup. + final WidgetStateProperty? filterPopupCheckboxFillColor; + + /// The border color of the text field (input box) inside the filter popup. + final Color? filterPopupInputBorderColor; + + /// The background color of the filter popup. + final Color? filterPopupBackgroundColor; + + /// The color of icons displayed in the filter popup. + final Color? filterPopupIconColor; + + /// The color of disabled icons in the filter popup. + final Color? filterPopupDisabledIconColor; + + /// The background color of the dropdown in the advanced filter popup. + final Color? advancedFilterPopupDropdownColor; + + /// The color of the "No Matches" filtering label when no results are found. + final Color? noMatchesFilteringLabelColor; + + /// The color of the OK label in the filtering popup. + final Color? okFilteringLabelColor; + + /// The color of the OK button in the filtering popup. + final Color? okFilteringLabelButtonColor; + + /// The color of the cancel label in the filtering popup. + final Color? cancelFilteringLabelColor; + + /// The color of the cancel button in the filtering popup. + final Color? cancelFilteringLabelButtonColor; + + /// The cursor color in the search area. + final Color? searchAreaCursorColor; + + /// The focused border color of the search area. + final Color? searchAreaFocusedBorderColor; + + /// The active (selected) color of the "AND" radio button. + final Color? andRadioActiveColor; + + /// The fill color of the "AND" radio button. + final WidgetStateProperty? andRadioFillColor; + + /// The active (selected) color of the "OR" radio button. + final Color? orRadioActiveColor; + + /// The fill color of the "OR" radio button. + final WidgetStateProperty? orRadioFillColor; + + /// The color of the calendar icon. + final Color? calendarIconColor; + + /// The focused border color of the advanced filter type dropdown. + final Color? advancedFilterTypeDropdownFocusedBorderColor; + + /// The focused border color of the advanced filter value dropdown. + final Color? advancedFilterValueDropdownFocusedBorderColor; + + /// The cursor color in the advanced filter value text area. + final Color? advancedFilterValueTextAreaCursorColor; + + /// The color of the search icon. + final Color? searchIconColor; + + /// The color of the close icon. + final Color? closeIconColor; + + /// The color of the dropdown icon of the advanced filter popup. + final Color? advancedFilterPopupDropdownIconColor; + + /// The active color of the case-sensitive icon. + final Color? caseSensitiveIconActiveColor; + + /// The default color of the case-sensitive icon. + final Color? caseSensitiveIconColor; + + /// The background color of the advanced filter type dropdown icon. + final Color? advancedFilterTypeDropdownIconColor; + + /// The background color of the advanced filter value dropdown icon. + final Color? advancedFilterValueDropdownIconColor; + + /// The color of the bottom divider in the filter popup. + final Color? filterPopupBottomDividerColor; + + /// The color of the top divider in the filter popup. + final Color? filterPopupTopDividerColor; + + /// The color of the disabled OK button in the filtering popup. + final Color? okFilteringLabelDisabledButtonColor; + + /// The color of the bottom border of the app bar. + final Color? appBarBottomBorderColor; + /// Creates a copy of this theme but with the given /// fields replaced with the new values. SfDataGridThemeData copyWith({ @@ -357,9 +728,55 @@ class SfDataGridThemeData with Diagnosticable { Color? rowHoverColor, TextStyle? rowHoverTextStyle, Widget? sortIcon, + Widget? filterIcon, + Color? filterIconColor, + Color? filterIconHoverColor, + Color? sortOrderNumberColor, + Color? sortOrderNumberBackgroundColor, + TextStyle? filterPopupTextStyle, + TextStyle? filterPopupDisabledTextStyle, + double? columnDragIndicatorStrokeWidth, + Color? columnDragIndicatorColor, + Widget? groupExpanderIcon, + double indentColumnWidth = 40.0, + Color? indentColumnColor, + Color? captionSummaryRowColor, + Color? filterPopupCheckColor, + WidgetStateProperty? filterPopupCheckboxFillColor, + Color? filterPopupInputBorderColor, + Color? filterPopupBackgroundColor, + Color? filterPopupIconColor, + Color? filterPopupDisabledIconColor, + Color? advancedFilterPopupDropdownColor, + Color? noMatchesFilteringLabelColor, + Color? okFilteringLabelColor, + Color? okFilteringLabelButtonColor, + Color? cancelFilteringLabelColor, + Color? cancelFilteringLabelButtonColor, + Color? searchAreaCursorColor, + Color? searchAreaFocusedBorderColor, + Color? andRadioActiveColor, + WidgetStateProperty? andRadioFillColor, + Color? orRadioActiveColor, + WidgetStateProperty? orRadioFillColor, + Color? advancedFilterTypeDropdownFocusedBorderColor, + Color? advancedFilterValueDropdownFocusedBorderColor, + Color? calendarIconColor, + Color? advancedFilterValueTextAreaCursorColor, + Color? searchIconColor, + Color? closeIconColor, + Color? advancedFilterPopupDropdownIconColor, + Color? caseSensitiveIconActiveColor, + Color? caseSensitiveIconColor, + Color? advancedFilterTypeDropdownIconColor, + Color? advancedFilterValueDropdownIconColor, + Color? filterPopupTopDividerColor, + Color? filterPopupBottomDividerColor, + Color? okFilteringLabelDisabledButtonColor, + Color? appBarBottomBorderColor, }) { return SfDataGridThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, gridLineColor: gridLineColor ?? this.gridLineColor, gridLineStrokeWidth: gridLineStrokeWidth ?? this.gridLineStrokeWidth, selectionColor: selectionColor ?? this.selectionColor, @@ -372,45 +789,371 @@ class SfDataGridThemeData with Diagnosticable { frozenPaneElevation: frozenPaneElevation ?? this.frozenPaneElevation, columnResizeIndicatorColor: columnResizeIndicatorColor ?? this.columnResizeIndicatorColor, - columnResizeIndicatorStrokeWidth: columnResizeIndicatorStrokeWidth ?? + columnResizeIndicatorStrokeWidth: + columnResizeIndicatorStrokeWidth ?? this.columnResizeIndicatorStrokeWidth, rowHoverColor: rowHoverColor ?? this.rowHoverColor, rowHoverTextStyle: rowHoverTextStyle ?? this.rowHoverTextStyle, sortIcon: sortIcon ?? this.sortIcon, + filterIcon: filterIcon ?? this.filterIcon, + filterIconColor: filterIconColor ?? this.filterIconColor, + filterIconHoverColor: filterIconHoverColor ?? this.filterIconHoverColor, + sortOrderNumberColor: sortOrderNumberColor ?? this.sortOrderNumberColor, + sortOrderNumberBackgroundColor: + sortOrderNumberBackgroundColor ?? this.sortOrderNumberBackgroundColor, + filterPopupTextStyle: filterPopupTextStyle ?? this.filterPopupTextStyle, + filterPopupDisabledTextStyle: + filterPopupDisabledTextStyle ?? this.filterPopupDisabledTextStyle, + columnDragIndicatorColor: + columnDragIndicatorColor ?? this.columnDragIndicatorColor, + columnDragIndicatorStrokeWidth: + columnDragIndicatorStrokeWidth ?? this.columnDragIndicatorStrokeWidth, + groupExpanderIcon: groupExpanderIcon ?? this.groupExpanderIcon, + indentColumnWidth: indentColumnWidth, + indentColumnColor: indentColumnColor ?? this.indentColumnColor, + captionSummaryRowColor: + captionSummaryRowColor ?? this.captionSummaryRowColor, + filterPopupCheckColor: + filterPopupCheckColor ?? this.filterPopupCheckColor, + filterPopupCheckboxFillColor: + filterPopupCheckboxFillColor ?? this.filterPopupCheckboxFillColor, + filterPopupInputBorderColor: + filterPopupInputBorderColor ?? this.filterPopupInputBorderColor, + filterPopupBackgroundColor: + filterPopupBackgroundColor ?? this.filterPopupBackgroundColor, + filterPopupIconColor: filterPopupIconColor ?? this.filterPopupIconColor, + filterPopupDisabledIconColor: + filterPopupDisabledIconColor ?? this.filterPopupDisabledIconColor, + advancedFilterPopupDropdownColor: + advancedFilterPopupDropdownColor ?? + this.advancedFilterPopupDropdownColor, + noMatchesFilteringLabelColor: + noMatchesFilteringLabelColor ?? this.noMatchesFilteringLabelColor, + okFilteringLabelColor: + okFilteringLabelColor ?? this.okFilteringLabelColor, + okFilteringLabelButtonColor: + okFilteringLabelButtonColor ?? this.okFilteringLabelButtonColor, + cancelFilteringLabelColor: + cancelFilteringLabelColor ?? this.cancelFilteringLabelColor, + cancelFilteringLabelButtonColor: + cancelFilteringLabelButtonColor ?? + this.cancelFilteringLabelButtonColor, + searchAreaCursorColor: + searchAreaCursorColor ?? this.searchAreaCursorColor, + searchAreaFocusedBorderColor: + searchAreaFocusedBorderColor ?? this.searchAreaFocusedBorderColor, + andRadioActiveColor: andRadioActiveColor ?? this.andRadioActiveColor, + andRadioFillColor: andRadioFillColor ?? this.andRadioFillColor, + orRadioActiveColor: orRadioActiveColor ?? this.orRadioActiveColor, + orRadioFillColor: orRadioFillColor ?? this.orRadioFillColor, + calendarIconColor: calendarIconColor ?? this.calendarIconColor, + advancedFilterValueDropdownFocusedBorderColor: + advancedFilterValueDropdownFocusedBorderColor ?? + this.advancedFilterValueDropdownFocusedBorderColor, + advancedFilterTypeDropdownFocusedBorderColor: + advancedFilterTypeDropdownFocusedBorderColor ?? + this.advancedFilterTypeDropdownFocusedBorderColor, + advancedFilterValueTextAreaCursorColor: + advancedFilterValueTextAreaCursorColor ?? + this.advancedFilterValueTextAreaCursorColor, + searchIconColor: searchIconColor ?? this.searchIconColor, + closeIconColor: closeIconColor ?? this.closeIconColor, + advancedFilterPopupDropdownIconColor: + advancedFilterPopupDropdownIconColor ?? + this.advancedFilterPopupDropdownIconColor, + caseSensitiveIconActiveColor: + caseSensitiveIconActiveColor ?? this.caseSensitiveIconActiveColor, + caseSensitiveIconColor: + caseSensitiveIconColor ?? this.caseSensitiveIconColor, + advancedFilterTypeDropdownIconColor: + advancedFilterTypeDropdownIconColor ?? + this.advancedFilterTypeDropdownIconColor, + advancedFilterValueDropdownIconColor: + advancedFilterValueDropdownIconColor ?? + this.advancedFilterValueDropdownIconColor, + filterPopupBottomDividerColor: + filterPopupBottomDividerColor ?? this.filterPopupBottomDividerColor, + filterPopupTopDividerColor: + filterPopupTopDividerColor ?? this.filterPopupTopDividerColor, + okFilteringLabelDisabledButtonColor: + okFilteringLabelDisabledButtonColor ?? + this.okFilteringLabelDisabledButtonColor, + appBarBottomBorderColor: + appBarBottomBorderColor ?? this.appBarBottomBorderColor, ); } /// Linearly interpolate between two themes. static SfDataGridThemeData? lerp( - SfDataGridThemeData? a, SfDataGridThemeData? b, double t) { + SfDataGridThemeData? a, + SfDataGridThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfDataGridThemeData( gridLineColor: Color.lerp(a!.gridLineColor, b!.gridLineColor, t), - gridLineStrokeWidth: - lerpDouble(a.gridLineStrokeWidth, b.gridLineStrokeWidth, t), + gridLineStrokeWidth: lerpDouble( + a.gridLineStrokeWidth, + b.gridLineStrokeWidth, + 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), + a.currentCellStyle, + b.currentCellStyle, + t, + ), + frozenPaneLineColor: Color.lerp( + a.frozenPaneLineColor, + b.frozenPaneLineColor, + t, + ), + frozenPaneLineWidth: 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), + frozenPaneElevation: lerpDouble( + a.frozenPaneElevation, + b.frozenPaneElevation, + t, + ), rowHoverColor: Color.lerp(a.rowHoverColor, b.rowHoverColor, t), columnResizeIndicatorColor: Color.lerp( - a.columnResizeIndicatorColor, b.columnResizeIndicatorColor, t), + a.columnResizeIndicatorColor, + b.columnResizeIndicatorColor, + t, + ), columnResizeIndicatorStrokeWidth: lerpDouble( - a.columnResizeIndicatorStrokeWidth, - b.columnResizeIndicatorStrokeWidth, - t), - rowHoverTextStyle: - TextStyle.lerp(a.rowHoverTextStyle, b.rowHoverTextStyle, t), + a.columnResizeIndicatorStrokeWidth, + b.columnResizeIndicatorStrokeWidth, + t, + ), + rowHoverTextStyle: TextStyle.lerp( + a.rowHoverTextStyle, + b.rowHoverTextStyle, + t, + ), + filterIconColor: Color.lerp(a.filterIconColor, b.filterIconColor, t), + filterIconHoverColor: Color.lerp( + a.filterIconHoverColor, + b.filterIconHoverColor, + t, + ), + sortOrderNumberColor: Color.lerp( + a.sortOrderNumberColor, + b.sortOrderNumberColor, + t, + ), + sortOrderNumberBackgroundColor: Color.lerp( + a.sortOrderNumberBackgroundColor, + b.sortOrderNumberBackgroundColor, + t, + ), + filterPopupTextStyle: TextStyle.lerp( + a.filterPopupTextStyle, + b.filterPopupTextStyle, + t, + ), + filterPopupDisabledTextStyle: TextStyle.lerp( + a.filterPopupDisabledTextStyle, + b.filterPopupDisabledTextStyle, + t, + ), + columnDragIndicatorColor: Color.lerp( + a.columnDragIndicatorColor, + b.columnDragIndicatorColor, + t, + ), + columnDragIndicatorStrokeWidth: lerpDouble( + a.columnDragIndicatorStrokeWidth, + b.columnDragIndicatorStrokeWidth, + t, + ), + indentColumnColor: Color.lerp( + a.indentColumnColor, + b.indentColumnColor, + t, + ), + captionSummaryRowColor: Color.lerp( + a.captionSummaryRowColor, + b.captionSummaryRowColor, + t, + ), + filterPopupCheckColor: Color.lerp( + a.filterPopupCheckColor, + b.filterPopupCheckColor, + t, + ), + filterPopupCheckboxFillColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return Color.lerp( + a.filterPopupCheckboxFillColor?.resolve(states), + b.filterPopupCheckboxFillColor?.resolve(states), + t, + ); + }), + filterPopupInputBorderColor: Color.lerp( + a.filterPopupInputBorderColor, + b.filterPopupInputBorderColor, + t, + ), + filterPopupBackgroundColor: Color.lerp( + a.filterPopupBackgroundColor, + b.filterPopupBackgroundColor, + t, + ), + filterPopupIconColor: Color.lerp( + a.filterPopupIconColor, + b.filterPopupIconColor, + t, + ), + filterPopupDisabledIconColor: Color.lerp( + a.filterPopupDisabledIconColor, + b.filterPopupDisabledIconColor, + t, + ), + advancedFilterPopupDropdownColor: Color.lerp( + a.advancedFilterPopupDropdownColor, + b.advancedFilterPopupDropdownColor, + t, + ), + noMatchesFilteringLabelColor: Color.lerp( + a.noMatchesFilteringLabelColor, + b.noMatchesFilteringLabelColor, + t, + ), + okFilteringLabelColor: Color.lerp( + a.okFilteringLabelColor, + b.okFilteringLabelColor, + t, + ), + okFilteringLabelButtonColor: Color.lerp( + a.okFilteringLabelButtonColor, + b.okFilteringLabelButtonColor, + t, + ), + cancelFilteringLabelColor: Color.lerp( + a.cancelFilteringLabelColor, + b.cancelFilteringLabelColor, + t, + ), + cancelFilteringLabelButtonColor: Color.lerp( + a.cancelFilteringLabelButtonColor, + b.cancelFilteringLabelButtonColor, + t, + ), + searchAreaFocusedBorderColor: Color.lerp( + a.searchAreaFocusedBorderColor, + b.searchAreaFocusedBorderColor, + t, + ), + searchAreaCursorColor: Color.lerp( + a.searchAreaCursorColor, + b.searchAreaCursorColor, + t, + ), + andRadioActiveColor: Color.lerp( + a.andRadioActiveColor, + b.andRadioActiveColor, + t, + ), + andRadioFillColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return Color.lerp( + a.andRadioFillColor?.resolve(states), + b.andRadioFillColor?.resolve(states), + t, + ); + }), + orRadioActiveColor: Color.lerp( + a.orRadioActiveColor, + b.orRadioActiveColor, + t, + ), + orRadioFillColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + return Color.lerp( + a.orRadioFillColor?.resolve(states), + b.orRadioFillColor?.resolve(states), + t, + ); + }), + calendarIconColor: Color.lerp( + a.calendarIconColor, + b.calendarIconColor, + t, + ), + advancedFilterValueDropdownFocusedBorderColor: Color.lerp( + a.advancedFilterValueDropdownFocusedBorderColor, + b.advancedFilterValueDropdownFocusedBorderColor, + t, + ), + advancedFilterTypeDropdownFocusedBorderColor: Color.lerp( + a.advancedFilterTypeDropdownFocusedBorderColor, + b.advancedFilterTypeDropdownFocusedBorderColor, + t, + ), + advancedFilterValueTextAreaCursorColor: Color.lerp( + a.advancedFilterValueTextAreaCursorColor, + b.advancedFilterValueTextAreaCursorColor, + t, + ), + searchIconColor: Color.lerp(a.searchIconColor, b.searchIconColor, t), + closeIconColor: Color.lerp(a.closeIconColor, b.closeIconColor, t), + advancedFilterPopupDropdownIconColor: Color.lerp( + a.advancedFilterPopupDropdownIconColor, + b.advancedFilterPopupDropdownIconColor, + t, + ), + caseSensitiveIconActiveColor: Color.lerp( + a.caseSensitiveIconActiveColor, + b.caseSensitiveIconActiveColor, + t, + ), + caseSensitiveIconColor: Color.lerp( + a.caseSensitiveIconColor, + b.caseSensitiveIconColor, + t, + ), + advancedFilterValueDropdownIconColor: Color.lerp( + a.advancedFilterValueDropdownIconColor, + b.advancedFilterValueDropdownIconColor, + t, + ), + advancedFilterTypeDropdownIconColor: Color.lerp( + a.advancedFilterTypeDropdownIconColor, + b.advancedFilterTypeDropdownIconColor, + t, + ), + filterPopupBottomDividerColor: Color.lerp( + a.filterPopupBottomDividerColor, + b.filterPopupBottomDividerColor, + t, + ), + filterPopupTopDividerColor: Color.lerp( + a.filterPopupTopDividerColor, + b.filterPopupTopDividerColor, + t, + ), + okFilteringLabelDisabledButtonColor: Color.lerp( + a.okFilteringLabelDisabledButtonColor, + b.okFilteringLabelDisabledButtonColor, + t, + ), + appBarBottomBorderColor: Color.lerp( + a.appBarBottomBorderColor, + b.appBarBottomBorderColor, + t, + ), ); } @@ -424,7 +1167,6 @@ class SfDataGridThemeData with Diagnosticable { } return other is SfDataGridThemeData && - other.brightness == brightness && other.gridLineColor == gridLineColor && other.gridLineStrokeWidth == gridLineStrokeWidth && other.selectionColor == selectionColor && @@ -440,7 +1182,64 @@ class SfDataGridThemeData with Diagnosticable { other.columnResizeIndicatorStrokeWidth == columnResizeIndicatorStrokeWidth && other.rowHoverTextStyle == rowHoverTextStyle && - other.sortIcon == sortIcon; + other.sortIcon == sortIcon && + other.filterIcon == filterIcon && + other.filterIconColor == filterIconColor && + other.filterIconHoverColor == filterIconHoverColor && + other.sortOrderNumberColor == sortOrderNumberColor && + other.sortOrderNumberBackgroundColor == + sortOrderNumberBackgroundColor && + other.filterPopupTextStyle == filterPopupTextStyle && + other.filterPopupDisabledTextStyle == filterPopupDisabledTextStyle && + other.columnDragIndicatorColor == columnDragIndicatorColor && + other.columnDragIndicatorStrokeWidth == + columnDragIndicatorStrokeWidth && + other.groupExpanderIcon == groupExpanderIcon && + other.indentColumnWidth == indentColumnWidth && + other.indentColumnColor == indentColumnColor && + other.captionSummaryRowColor == captionSummaryRowColor && + other.filterPopupCheckColor == filterPopupCheckColor && + other.filterPopupCheckboxFillColor == filterPopupCheckboxFillColor && + other.filterPopupInputBorderColor == filterPopupInputBorderColor && + other.filterPopupBackgroundColor == filterPopupBackgroundColor && + other.filterPopupIconColor == filterPopupIconColor && + other.filterPopupDisabledIconColor == filterPopupDisabledIconColor && + other.advancedFilterPopupDropdownColor == + advancedFilterPopupDropdownColor && + other.noMatchesFilteringLabelColor == noMatchesFilteringLabelColor && + other.okFilteringLabelColor == okFilteringLabelColor && + other.okFilteringLabelButtonColor == okFilteringLabelButtonColor && + other.cancelFilteringLabelColor == cancelFilteringLabelColor && + other.cancelFilteringLabelButtonColor == + cancelFilteringLabelButtonColor && + other.searchAreaFocusedBorderColor == searchAreaFocusedBorderColor && + other.searchAreaCursorColor == searchAreaCursorColor && + other.andRadioActiveColor == andRadioActiveColor && + other.andRadioFillColor == andRadioFillColor && + other.orRadioActiveColor == orRadioActiveColor && + other.orRadioFillColor == orRadioFillColor && + other.calendarIconColor == calendarIconColor && + other.advancedFilterValueDropdownFocusedBorderColor == + advancedFilterValueDropdownFocusedBorderColor && + other.advancedFilterTypeDropdownFocusedBorderColor == + advancedFilterTypeDropdownFocusedBorderColor && + other.advancedFilterValueTextAreaCursorColor == + advancedFilterValueTextAreaCursorColor && + other.searchIconColor == searchIconColor && + other.closeIconColor == closeIconColor && + other.advancedFilterPopupDropdownIconColor == + advancedFilterPopupDropdownIconColor && + other.caseSensitiveIconActiveColor == caseSensitiveIconActiveColor && + other.caseSensitiveIconColor == caseSensitiveIconColor && + other.advancedFilterValueDropdownIconColor == + advancedFilterValueDropdownIconColor && + other.advancedFilterTypeDropdownIconColor == + advancedFilterTypeDropdownIconColor && + other.filterPopupBottomDividerColor == filterPopupBottomDividerColor && + other.filterPopupTopDividerColor == filterPopupTopDividerColor && + other.okFilteringLabelDisabledButtonColor == + okFilteringLabelDisabledButtonColor && + other.appBarBottomBorderColor == appBarBottomBorderColor; } @override @@ -460,49 +1259,488 @@ class SfDataGridThemeData with Diagnosticable { columnResizeIndicatorColor, columnResizeIndicatorStrokeWidth, rowHoverTextStyle, - sortIcon + sortIcon, + filterIcon, + filterIconColor, + filterIconHoverColor, + sortOrderNumberColor, + sortOrderNumberBackgroundColor, + filterPopupTextStyle, + filterPopupDisabledTextStyle, + columnDragIndicatorColor, + columnDragIndicatorStrokeWidth, + groupExpanderIcon, + indentColumnWidth, + indentColumnColor, + captionSummaryRowColor, + filterPopupCheckColor, + filterPopupCheckboxFillColor, + filterPopupInputBorderColor, + filterPopupBackgroundColor, + filterPopupIconColor, + filterPopupDisabledIconColor, + advancedFilterPopupDropdownColor, + noMatchesFilteringLabelColor, + okFilteringLabelColor, + okFilteringLabelButtonColor, + cancelFilteringLabelColor, + cancelFilteringLabelButtonColor, + searchAreaFocusedBorderColor, + searchAreaCursorColor, + andRadioActiveColor, + andRadioFillColor, + orRadioActiveColor, + orRadioFillColor, + calendarIconColor, + advancedFilterValueDropdownFocusedBorderColor, + advancedFilterTypeDropdownFocusedBorderColor, + advancedFilterValueTextAreaCursorColor, + searchIconColor, + closeIconColor, + advancedFilterPopupDropdownIconColor, + caseSensitiveIconActiveColor, + caseSensitiveIconColor, + advancedFilterTypeDropdownIconColor, + advancedFilterValueDropdownIconColor, + filterPopupBottomDividerColor, + filterPopupTopDividerColor, + okFilteringLabelDisabledButtonColor, + appBarBottomBorderColor, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfDataGridThemeData defaultData = SfDataGridThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('gridLineColor', gridLineColor, - defaultValue: defaultData.gridLineColor)); - properties.add(DoubleProperty('gridLineStrokeWidth', gridLineStrokeWidth, - defaultValue: defaultData.gridLineStrokeWidth)); - properties.add(ColorProperty('selectionColor', selectionColor, - defaultValue: defaultData.selectionColor)); - properties.add(DiagnosticsProperty( - 'currentCellStyle', currentCellStyle, - defaultValue: defaultData.currentCellStyle)); - properties.add(ColorProperty('frozenPaneLineColor', frozenPaneLineColor, - defaultValue: defaultData.frozenPaneLineColor)); - properties.add(DoubleProperty('frozenPaneLineWidth', frozenPaneLineWidth, - defaultValue: defaultData.frozenPaneLineWidth)); - 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)); - properties.add(ColorProperty( - 'columnResizeIndicatorColor', columnResizeIndicatorColor, - defaultValue: defaultData.columnResizeIndicatorColor)); - properties.add(DoubleProperty( - 'columnResizeIndicatorStrokeWidth', columnResizeIndicatorStrokeWidth, - defaultValue: defaultData.columnResizeIndicatorStrokeWidth)); - properties.add(ColorProperty('rowHoverColor', rowHoverColor, - defaultValue: defaultData.rowHoverColor)); - properties.add(DiagnosticsProperty( - 'rowHoverTextStyle', rowHoverTextStyle, - defaultValue: defaultData.rowHoverTextStyle)); + const SfDataGridThemeData defaultData = SfDataGridThemeData(); + properties.add( + ColorProperty( + 'gridLineColor', + gridLineColor, + defaultValue: defaultData.gridLineColor, + ), + ); + properties.add( + DoubleProperty( + 'gridLineStrokeWidth', + gridLineStrokeWidth, + defaultValue: defaultData.gridLineStrokeWidth, + ), + ); + properties.add( + ColorProperty( + 'selectionColor', + selectionColor, + defaultValue: defaultData.selectionColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'currentCellStyle', + currentCellStyle, + defaultValue: defaultData.currentCellStyle, + ), + ); + properties.add( + ColorProperty( + 'frozenPaneLineColor', + frozenPaneLineColor, + defaultValue: defaultData.frozenPaneLineColor, + ), + ); + properties.add( + DoubleProperty( + 'frozenPaneLineWidth', + frozenPaneLineWidth, + defaultValue: defaultData.frozenPaneLineWidth, + ), + ); + 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, + ), + ); + properties.add( + ColorProperty( + 'columnResizeIndicatorColor', + columnResizeIndicatorColor, + defaultValue: defaultData.columnResizeIndicatorColor, + ), + ); + properties.add( + DoubleProperty( + 'columnResizeIndicatorStrokeWidth', + columnResizeIndicatorStrokeWidth, + defaultValue: defaultData.columnResizeIndicatorStrokeWidth, + ), + ); + properties.add( + ColorProperty( + 'rowHoverColor', + rowHoverColor, + defaultValue: defaultData.rowHoverColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'rowHoverTextStyle', + rowHoverTextStyle, + defaultValue: defaultData.rowHoverTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'sortIcon', + sortIcon, + defaultValue: defaultData.sortIcon, + ), + ); + properties.add( + DiagnosticsProperty( + 'filterIcon', + filterIcon, + defaultValue: defaultData.filterIcon, + ), + ); + properties.add( + ColorProperty( + 'filterIconColor', + filterIconColor, + defaultValue: defaultData.filterIconColor, + ), + ); + properties.add( + ColorProperty( + 'filterIconHoverColor', + filterIconHoverColor, + defaultValue: defaultData.filterIconHoverColor, + ), + ); + properties.add( + ColorProperty( + 'sortOrderNumberColor', + sortOrderNumberColor, + defaultValue: defaultData.sortOrderNumberColor, + ), + ); + properties.add( + ColorProperty( + 'sortOrderNumberBackgroundColor', + sortOrderNumberBackgroundColor, + defaultValue: defaultData.sortOrderNumberBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'filterPopupTextStyle', + filterPopupTextStyle, + defaultValue: defaultData.filterPopupTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'filterPopupDisabledTextStyle', + filterPopupDisabledTextStyle, + defaultValue: defaultData.filterPopupDisabledTextStyle, + ), + ); + properties.add( + ColorProperty( + 'columnDragIndicatorColor', + columnDragIndicatorColor, + defaultValue: defaultData.columnDragIndicatorColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'columnDragIndicatorStrokeWidth', + columnDragIndicatorStrokeWidth, + defaultValue: defaultData.columnDragIndicatorStrokeWidth, + ), + ); + properties.add( + DiagnosticsProperty( + 'groupExpanderIcon', + groupExpanderIcon, + defaultValue: defaultData.groupExpanderIcon, + ), + ); + properties.add( + DiagnosticsProperty( + 'indentColumnWidth', + indentColumnWidth, + defaultValue: defaultData.indentColumnWidth, + ), + ); + properties.add( + ColorProperty( + 'indentColumnColor', + indentColumnColor, + defaultValue: defaultData.indentColumnColor, + ), + ); + properties.add( + ColorProperty( + 'captionSummaryRowColor', + captionSummaryRowColor, + defaultValue: defaultData.captionSummaryRowColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupCheckColor', + filterPopupCheckColor, + defaultValue: defaultData.filterPopupCheckColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupCheckboxFillColor', + filterPopupCheckboxFillColor?.resolve({}), + defaultValue: defaultData.filterPopupCheckboxFillColor?.resolve({}), + ), + ); + properties.add( + ColorProperty( + 'filterPopupInputBorderColor', + filterPopupInputBorderColor, + defaultValue: defaultData.filterPopupInputBorderColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupBackgroundColor', + filterPopupBackgroundColor, + defaultValue: defaultData.filterPopupBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupIconColor', + filterPopupIconColor, + defaultValue: defaultData.filterPopupIconColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupDisabledIconColor', + filterPopupDisabledIconColor, + defaultValue: defaultData.filterPopupDisabledIconColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterPopupDropdownColor', + advancedFilterPopupDropdownColor, + defaultValue: defaultData.advancedFilterPopupDropdownColor, + ), + ); + properties.add( + ColorProperty( + 'noMatchesFilteringLabelColor', + noMatchesFilteringLabelColor, + defaultValue: defaultData.noMatchesFilteringLabelColor, + ), + ); + properties.add( + ColorProperty( + 'okFilteringLabelColor', + okFilteringLabelColor, + defaultValue: defaultData.okFilteringLabelColor, + ), + ); + properties.add( + ColorProperty( + 'okFilteringLabelButtonColor', + okFilteringLabelButtonColor, + defaultValue: defaultData.okFilteringLabelButtonColor, + ), + ); + properties.add( + ColorProperty( + 'cancelFilteringLabelColor', + cancelFilteringLabelColor, + defaultValue: defaultData.cancelFilteringLabelColor, + ), + ); + properties.add( + ColorProperty( + 'cancelFilteringLabelButtonColor', + cancelFilteringLabelButtonColor, + defaultValue: defaultData.cancelFilteringLabelButtonColor, + ), + ); + properties.add( + ColorProperty( + 'searchAreaFocusedBorderColor', + searchAreaFocusedBorderColor, + defaultValue: defaultData.searchAreaFocusedBorderColor, + ), + ); + properties.add( + ColorProperty( + 'searchAreaCursorColor', + searchAreaCursorColor, + defaultValue: defaultData.searchAreaCursorColor, + ), + ); + properties.add( + ColorProperty( + 'andRadioActiveColor', + andRadioActiveColor, + defaultValue: defaultData.andRadioActiveColor, + ), + ); + properties.add( + ColorProperty( + 'andRadioFillColor', + andRadioFillColor?.resolve({}), + defaultValue: defaultData.andRadioFillColor?.resolve({}), + ), + ); + properties.add( + ColorProperty( + 'orRadioActiveColor', + orRadioActiveColor, + defaultValue: defaultData.orRadioActiveColor, + ), + ); + properties.add( + ColorProperty( + 'orRadioFillColor', + orRadioFillColor?.resolve({}), + defaultValue: defaultData.orRadioFillColor?.resolve({}), + ), + ); + properties.add( + ColorProperty( + 'calendarIconColor', + calendarIconColor, + defaultValue: defaultData.calendarIconColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterValueDropdownFocusedBorderColor', + advancedFilterValueDropdownFocusedBorderColor, + defaultValue: defaultData.advancedFilterValueDropdownFocusedBorderColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterTypeDropdownFocusedBorderColor', + advancedFilterTypeDropdownFocusedBorderColor, + defaultValue: defaultData.advancedFilterTypeDropdownFocusedBorderColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterValueTextAreaCursorColor', + advancedFilterValueTextAreaCursorColor, + defaultValue: defaultData.advancedFilterValueTextAreaCursorColor, + ), + ); + properties.add( + ColorProperty( + 'searchIconColor', + searchIconColor, + defaultValue: defaultData.searchIconColor, + ), + ); + properties.add( + ColorProperty( + 'closeIconColor', + closeIconColor, + defaultValue: defaultData.closeIconColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterPopupDropdownIconColor', + advancedFilterPopupDropdownIconColor, + defaultValue: defaultData.advancedFilterPopupDropdownIconColor, + ), + ); + properties.add( + ColorProperty( + 'caseSensitiveIconActiveColor', + caseSensitiveIconActiveColor, + defaultValue: defaultData.caseSensitiveIconActiveColor, + ), + ); + properties.add( + ColorProperty( + 'caseSensitiveIconColor', + caseSensitiveIconColor, + defaultValue: defaultData.caseSensitiveIconColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterValueDropdownIconColor', + advancedFilterValueDropdownIconColor, + defaultValue: defaultData.advancedFilterValueDropdownIconColor, + ), + ); + properties.add( + ColorProperty( + 'advancedFilterTypeDropdownIconColor', + advancedFilterTypeDropdownIconColor, + defaultValue: defaultData.advancedFilterTypeDropdownIconColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupBottomDividerColor', + filterPopupBottomDividerColor, + defaultValue: defaultData.filterPopupBottomDividerColor, + ), + ); + properties.add( + ColorProperty( + 'filterPopupTopDividerColor', + filterPopupTopDividerColor, + defaultValue: defaultData.filterPopupTopDividerColor, + ), + ); + properties.add( + ColorProperty( + 'okFilteringLabelDisabledButtonColor', + okFilteringLabelDisabledButtonColor, + defaultValue: defaultData.okFilteringLabelDisabledButtonColor, + ), + ); + properties.add( + ColorProperty( + 'appBarBottomBorderColor', + appBarBottomBorderColor, + defaultValue: defaultData.appBarBottomBorderColor, + ), + ); } } @@ -510,8 +1748,10 @@ class SfDataGridThemeData with Diagnosticable { class DataGridCurrentCellStyle { /// Create a [DataGridCurrentCellStyle] that's used to configure /// a style for the current cell in [SfDataGrid]. - const DataGridCurrentCellStyle( - {required this.borderColor, required this.borderWidth}); + const DataGridCurrentCellStyle({ + required this.borderColor, + required this.borderWidth, + }); /// The color of the border in current cell. final Color borderColor; @@ -536,21 +1776,22 @@ class DataGridCurrentCellStyle { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - final List values = [ - borderColor, - borderWidth, - ]; - return hashList(values); + final List values = [borderColor, borderWidth]; + return Object.hashAll(values); } /// Linearly interpolate between two styles. static DataGridCurrentCellStyle? lerp( - DataGridCurrentCellStyle? a, DataGridCurrentCellStyle? b, double t) { + 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 7b563a941..7010a60bb 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart @@ -9,7 +9,7 @@ import '../../theme.dart'; class SfDataPagerTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. const SfDataPagerTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant [SfDataPager] /// widgets. @@ -51,57 +51,51 @@ class SfDataPagerTheme extends InheritedTheme { 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, - Color? dropdownButtonBorderColor}) { - return SfDataPagerThemeData.raw( - brightness: brightness, - backgroundColor: backgroundColor, - itemColor: itemColor, - itemTextStyle: itemTextStyle, - selectedItemColor: selectedItemColor, - selectedItemTextStyle: selectedItemTextStyle, - disabledItemColor: disabledItemColor, - disabledItemTextStyle: disabledItemTextStyle, - itemBorderColor: itemBorderColor, - itemBorderWidth: itemBorderWidth, - itemBorderRadius: itemBorderRadius, - dropdownButtonBorderColor: dropdownButtonBorderColor); - } - - /// Create a [SfDataPagerThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// 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.dropdownButtonBorderColor}); + const SfDataPagerThemeData({ + this.backgroundColor, + this.itemColor, + this.itemTextStyle, + this.selectedItemColor, + this.selectedItemTextStyle, + this.disabledItemColor, + this.disabledItemTextStyle, + this.itemBorderColor, + this.itemBorderWidth, + this.itemBorderRadius, + this.dropdownButtonBorderColor, + }); - /// The brightness of the overall theme of the - /// application for the [SfDataPager] widgets. - final Brightness? brightness; + /// Create a [SfDataPagerThemeData] that's used to configure a + /// [SfDataPagerTheme]. + factory SfDataPagerThemeData.raw({ + Brightness? brightness, + Color? backgroundColor, + Color? itemColor, + TextStyle? itemTextStyle, + Color? selectedItemColor, + TextStyle? selectedItemTextStyle, + Color? disabledItemColor, + TextStyle? disabledItemTextStyle, + Color? itemBorderColor, + double? itemBorderWidth, + BorderRadiusGeometry? itemBorderRadius, + Color? dropdownButtonBorderColor, + }) { + brightness = brightness ?? Brightness.light; + return SfDataPagerThemeData( + backgroundColor: backgroundColor, + itemColor: itemColor, + itemTextStyle: itemTextStyle, + selectedItemColor: selectedItemColor, + selectedItemTextStyle: selectedItemTextStyle, + disabledItemColor: disabledItemColor, + disabledItemTextStyle: disabledItemTextStyle, + itemBorderColor: itemBorderColor, + itemBorderWidth: itemBorderWidth, + itemBorderRadius: itemBorderRadius, + dropdownButtonBorderColor: dropdownButtonBorderColor, + ); + } /// The color of the page Items final Color? itemColor; @@ -144,61 +138,85 @@ 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, - Color? dropdownButtonBorderColor}) { + SfDataPagerThemeData copyWith({ + Brightness? brightness, + Color? backgroundColor, + Color? itemColor, + TextStyle? itemTextStyle, + Color? selectedItemColor, + TextStyle? selectedItemTextStyle, + Color? disabledItemColor, + TextStyle? disabledItemTextStyle, + Color? itemBorderColor, + double? itemBorderWidth, + BorderRadiusGeometry? itemBorderRadius, + Color? dropdownButtonBorderColor, + }) { return SfDataPagerThemeData.raw( - brightness: brightness ?? this.brightness, - backgroundColor: backgroundColor ?? this.backgroundColor, - itemColor: itemColor ?? this.itemColor, - itemTextStyle: itemTextStyle ?? this.itemTextStyle, - selectedItemColor: selectedItemColor ?? this.selectedItemColor, - selectedItemTextStyle: - selectedItemTextStyle ?? this.selectedItemTextStyle, - disabledItemColor: disabledItemColor ?? this.disabledItemColor, - disabledItemTextStyle: - disabledItemTextStyle ?? this.disabledItemTextStyle, - itemBorderColor: itemBorderColor ?? this.itemBorderColor, - itemBorderWidth: itemBorderWidth ?? this.itemBorderWidth, - itemBorderRadius: itemBorderRadius ?? this.itemBorderRadius, - dropdownButtonBorderColor: - dropdownButtonBorderColor ?? this.dropdownButtonBorderColor); + brightness: brightness, + backgroundColor: backgroundColor ?? this.backgroundColor, + itemColor: itemColor ?? this.itemColor, + itemTextStyle: itemTextStyle ?? this.itemTextStyle, + selectedItemColor: selectedItemColor ?? this.selectedItemColor, + selectedItemTextStyle: + selectedItemTextStyle ?? this.selectedItemTextStyle, + disabledItemColor: disabledItemColor ?? this.disabledItemColor, + disabledItemTextStyle: + disabledItemTextStyle ?? this.disabledItemTextStyle, + itemBorderColor: itemBorderColor ?? this.itemBorderColor, + itemBorderWidth: itemBorderWidth ?? this.itemBorderWidth, + itemBorderRadius: itemBorderRadius ?? this.itemBorderRadius, + dropdownButtonBorderColor: + dropdownButtonBorderColor ?? this.dropdownButtonBorderColor, + ); } /// Linearly interpolate between two themes. static SfDataPagerThemeData? lerp( - SfDataPagerThemeData? a, SfDataPagerThemeData? b, double t) { + SfDataPagerThemeData? a, + SfDataPagerThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfDataPagerThemeData( - 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: - Color.lerp(a.selectedItemColor, b.selectedItemColor, t), - selectedItemTextStyle: - TextStyle.lerp(a.selectedItemTextStyle, b.selectedItemTextStyle, t), - disabledItemColor: - Color.lerp(a.disabledItemColor, b.disabledItemColor, t), - disabledItemTextStyle: - TextStyle.lerp(a.disabledItemTextStyle, b.disabledItemTextStyle, t), - itemBorderColor: Color.lerp(a.itemBorderColor, b.itemBorderColor, t), - itemBorderWidth: lerpDouble(a.itemBorderWidth, b.itemBorderWidth, t), - itemBorderRadius: BorderRadiusGeometry.lerp( - a.itemBorderRadius, b.itemBorderRadius, t), - dropdownButtonBorderColor: Color.lerp( - a.dropdownButtonBorderColor, b.dropdownButtonBorderColor, 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: Color.lerp( + a.selectedItemColor, + b.selectedItemColor, + t, + ), + selectedItemTextStyle: TextStyle.lerp( + a.selectedItemTextStyle, + b.selectedItemTextStyle, + t, + ), + disabledItemColor: Color.lerp( + a.disabledItemColor, + b.disabledItemColor, + t, + ), + disabledItemTextStyle: TextStyle.lerp( + a.disabledItemTextStyle, + b.disabledItemTextStyle, + t, + ), + itemBorderColor: Color.lerp(a.itemBorderColor, b.itemBorderColor, t), + itemBorderWidth: lerpDouble(a.itemBorderWidth, b.itemBorderWidth, t), + itemBorderRadius: BorderRadiusGeometry.lerp( + a.itemBorderRadius, + b.itemBorderRadius, + t, + ), + dropdownButtonBorderColor: Color.lerp( + a.dropdownButtonBorderColor, + b.dropdownButtonBorderColor, + t, + ), + ); } @override @@ -211,7 +229,6 @@ class SfDataPagerThemeData with Diagnosticable { } return other is SfDataPagerThemeData && - other.brightness == brightness && other.itemColor == itemColor && other.backgroundColor == backgroundColor && other.itemTextStyle == itemTextStyle && @@ -238,43 +255,91 @@ class SfDataPagerThemeData with Diagnosticable { itemBorderColor, itemBorderWidth, itemBorderRadius, - dropdownButtonBorderColor + dropdownButtonBorderColor, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfDataPagerThemeData defaultData = SfDataPagerThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('itemColor', itemColor, - defaultValue: defaultData.itemColor)); - properties.add(ColorProperty('selectedItemColor', selectedItemColor, - defaultValue: defaultData.selectedItemColor)); - properties.add(ColorProperty('disabledItemColor', disabledItemColor, - defaultValue: defaultData.disabledItemColor)); - properties.add(ColorProperty('itemBorderColor', itemBorderColor, - defaultValue: defaultData.itemBorderColor)); - properties.add(DiagnosticsProperty( - 'itemTextStyle', itemTextStyle, - defaultValue: defaultData.itemTextStyle)); - properties.add(DiagnosticsProperty( - 'selectedItemTextStyle', selectedItemTextStyle, - defaultValue: defaultData.selectedItemTextStyle)); - properties.add(DiagnosticsProperty( - 'disabledItemTextStyle', disabledItemTextStyle, - defaultValue: defaultData.disabledItemTextStyle)); - properties.add(DoubleProperty('itemBorderWidth', itemBorderWidth, - defaultValue: defaultData.itemBorderWidth)); - properties.add(DiagnosticsProperty( - 'itemBorderRadius', itemBorderRadius, - defaultValue: defaultData.itemBorderRadius)); - properties.add(ColorProperty( - 'dropdownButtonBorderColor', dropdownButtonBorderColor, - defaultValue: defaultData.dropdownButtonBorderColor)); + const SfDataPagerThemeData defaultData = SfDataPagerThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty( + 'itemColor', + itemColor, + defaultValue: defaultData.itemColor, + ), + ); + properties.add( + ColorProperty( + 'selectedItemColor', + selectedItemColor, + defaultValue: defaultData.selectedItemColor, + ), + ); + properties.add( + ColorProperty( + 'disabledItemColor', + disabledItemColor, + defaultValue: defaultData.disabledItemColor, + ), + ); + properties.add( + ColorProperty( + 'itemBorderColor', + itemBorderColor, + defaultValue: defaultData.itemBorderColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'itemTextStyle', + itemTextStyle, + defaultValue: defaultData.itemTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'selectedItemTextStyle', + selectedItemTextStyle, + defaultValue: defaultData.selectedItemTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'disabledItemTextStyle', + disabledItemTextStyle, + defaultValue: defaultData.disabledItemTextStyle, + ), + ); + properties.add( + DoubleProperty( + 'itemBorderWidth', + itemBorderWidth, + defaultValue: defaultData.itemBorderWidth, + ), + ); + properties.add( + DiagnosticsProperty( + 'itemBorderRadius', + itemBorderRadius, + defaultValue: defaultData.itemBorderRadius, + ), + ); + properties.add( + ColorProperty( + 'dropdownButtonBorderColor', + dropdownButtonBorderColor, + defaultValue: defaultData.dropdownButtonBorderColor, + ), + ); } } 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 45b9fc635..fdb7dedbc 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart @@ -20,9 +20,11 @@ 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, required this.data, required this.child}) - : super(key: key, child: 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. /// @@ -105,9 +107,44 @@ class SfDateRangePickerTheme extends InheritedTheme { /// ``` @immutable class SfDateRangePickerThemeData with Diagnosticable { + /// Create a [SfDateRangePickerThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfDateRangePickerThemeData] constructor. + const SfDateRangePickerThemeData({ + this.backgroundColor, + this.viewHeaderTextStyle, + this.headerTextStyle, + this.trailingDatesTextStyle, + this.leadingCellTextStyle, + this.activeDatesTextStyle, + this.cellTextStyle, + this.rangeSelectionTextStyle, + this.rangeSelectionColor, + this.leadingDatesTextStyle, + this.disabledDatesTextStyle, + this.disabledCellTextStyle, + this.selectionColor, + this.selectionTextStyle, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.headerBackgroundColor, + this.viewHeaderBackgroundColor, + this.weekNumberBackgroundColor, + this.blackoutDatesTextStyle, + this.todayHighlightColor, + this.todayTextStyle, + this.todayCellTextStyle, + this.weekendDatesTextStyle, + this.specialDatesTextStyle, + this.weekNumberTextStyle, + }); + /// Create a [SfDateRangePickerThemeData] that's used to configure a /// [SfDateRangePickerTheme]. - factory SfDateRangePickerThemeData({ + factory SfDateRangePickerThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? startRangeSelectionColor, @@ -136,99 +173,37 @@ class SfDateRangePickerThemeData with Diagnosticable { TextStyle? specialDatesTextStyle, TextStyle? weekNumberTextStyle, }) { - return SfDateRangePickerThemeData.raw( - brightness: brightness, - backgroundColor: backgroundColor, - viewHeaderTextStyle: viewHeaderTextStyle, - headerTextStyle: headerTextStyle, - trailingDatesTextStyle: trailingDatesTextStyle, - leadingCellTextStyle: leadingCellTextStyle, - activeDatesTextStyle: activeDatesTextStyle, - cellTextStyle: cellTextStyle, - rangeSelectionTextStyle: rangeSelectionTextStyle, - rangeSelectionColor: rangeSelectionColor, - weekNumberBackgroundColor: weekNumberBackgroundColor, - leadingDatesTextStyle: leadingDatesTextStyle, - disabledDatesTextStyle: disabledDatesTextStyle, - disabledCellTextStyle: disabledCellTextStyle, - selectionColor: selectionColor, - selectionTextStyle: selectionTextStyle, - startRangeSelectionColor: startRangeSelectionColor, - endRangeSelectionColor: endRangeSelectionColor, - headerBackgroundColor: headerBackgroundColor, - viewHeaderBackgroundColor: viewHeaderBackgroundColor, - blackoutDatesTextStyle: blackoutDatesTextStyle, - todayHighlightColor: todayHighlightColor, - todayTextStyle: todayTextStyle, - todayCellTextStyle: todayCellTextStyle, - weekendDatesTextStyle: weekendDatesTextStyle, - specialDatesTextStyle: specialDatesTextStyle, - weekNumberTextStyle: weekNumberTextStyle); + brightness = brightness ?? Brightness.light; + return SfDateRangePickerThemeData( + backgroundColor: backgroundColor, + viewHeaderTextStyle: viewHeaderTextStyle, + headerTextStyle: headerTextStyle, + trailingDatesTextStyle: trailingDatesTextStyle, + leadingCellTextStyle: leadingCellTextStyle, + activeDatesTextStyle: activeDatesTextStyle, + cellTextStyle: cellTextStyle, + rangeSelectionTextStyle: rangeSelectionTextStyle, + rangeSelectionColor: rangeSelectionColor, + weekNumberBackgroundColor: weekNumberBackgroundColor, + leadingDatesTextStyle: leadingDatesTextStyle, + disabledDatesTextStyle: disabledDatesTextStyle, + disabledCellTextStyle: disabledCellTextStyle, + selectionColor: selectionColor, + selectionTextStyle: selectionTextStyle, + startRangeSelectionColor: startRangeSelectionColor, + endRangeSelectionColor: endRangeSelectionColor, + headerBackgroundColor: headerBackgroundColor, + viewHeaderBackgroundColor: viewHeaderBackgroundColor, + blackoutDatesTextStyle: blackoutDatesTextStyle, + todayHighlightColor: todayHighlightColor, + todayTextStyle: todayTextStyle, + todayCellTextStyle: todayCellTextStyle, + weekendDatesTextStyle: weekendDatesTextStyle, + specialDatesTextStyle: specialDatesTextStyle, + weekNumberTextStyle: weekNumberTextStyle, + ); } - /// Create a [SfDateRangePickerThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// 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.weekNumberBackgroundColor, - required this.blackoutDatesTextStyle, - required this.todayHighlightColor, - required this.todayTextStyle, - required this.todayCellTextStyle, - required this.weekendDatesTextStyle, - required this.specialDatesTextStyle, - required this.weekNumberTextStyle}); - - /// The brightness of the overall theme of the - /// application for the date picker widget. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// date range picker widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// dateRangePickerThemeData: SfDateRangePickerThemeData( - /// brightness: Brightness.light - /// ) - /// ), - /// child: SfDateRangePicker(), - /// ), - /// ) - /// ); - /// } - /// ``` - final Brightness? brightness; - /// Specifies the background color of date picker widget. /// /// ```dart @@ -821,7 +796,7 @@ class SfDateRangePickerThemeData with Diagnosticable { TextStyle? weekNumberTextStyle, }) { return SfDateRangePickerThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, viewHeaderTextStyle: viewHeaderTextStyle ?? this.viewHeaderTextStyle, headerTextStyle: headerTextStyle ?? this.headerTextStyle, @@ -866,27 +841,51 @@ class SfDateRangePickerThemeData with Diagnosticable { /// Linearly interpolate between two themes. static SfDateRangePickerThemeData? lerp( - SfDateRangePickerThemeData? a, SfDateRangePickerThemeData? b, double t) { + SfDateRangePickerThemeData? a, + SfDateRangePickerThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfDateRangePickerThemeData( backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), - rangeSelectionColor: - Color.lerp(a.rangeSelectionColor, b.rangeSelectionColor, t), + rangeSelectionColor: Color.lerp( + a.rangeSelectionColor, + b.rangeSelectionColor, + t, + ), selectionColor: Color.lerp(a.selectionColor, b.selectionColor, t), - startRangeSelectionColor: - Color.lerp(a.startRangeSelectionColor, b.startRangeSelectionColor, t), - endRangeSelectionColor: - Color.lerp(a.endRangeSelectionColor, b.endRangeSelectionColor, t), - headerBackgroundColor: - Color.lerp(a.headerBackgroundColor, b.headerBackgroundColor, t), + startRangeSelectionColor: Color.lerp( + a.startRangeSelectionColor, + b.startRangeSelectionColor, + t, + ), + endRangeSelectionColor: Color.lerp( + a.endRangeSelectionColor, + b.endRangeSelectionColor, + t, + ), + headerBackgroundColor: Color.lerp( + a.headerBackgroundColor, + b.headerBackgroundColor, + t, + ), viewHeaderBackgroundColor: Color.lerp( - a.viewHeaderBackgroundColor, b.viewHeaderBackgroundColor, t), - todayHighlightColor: - Color.lerp(a.todayHighlightColor, b.todayHighlightColor, t), + a.viewHeaderBackgroundColor, + b.viewHeaderBackgroundColor, + t, + ), + todayHighlightColor: Color.lerp( + a.todayHighlightColor, + b.todayHighlightColor, + t, + ), weekNumberBackgroundColor: Color.lerp( - a.weekNumberBackgroundColor, b.weekNumberBackgroundColor, t), + a.weekNumberBackgroundColor, + b.weekNumberBackgroundColor, + t, + ), ); } @@ -956,38 +955,77 @@ class SfDateRangePickerThemeData with Diagnosticable { todayCellTextStyle, weekendDatesTextStyle, specialDatesTextStyle, - weekNumberTextStyle + weekNumberTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfDateRangePickerThemeData defaultData = SfDateRangePickerThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('rangeSelectionColor', rangeSelectionColor, - defaultValue: defaultData.rangeSelectionColor)); - properties.add(ColorProperty( - 'weekNumberBackgroundColor', weekNumberBackgroundColor, - defaultValue: defaultData.weekNumberBackgroundColor)); - properties.add(ColorProperty('selectionColor', selectionColor, - defaultValue: defaultData.selectionColor)); - properties.add(ColorProperty( - 'startRangeSelectionColor', startRangeSelectionColor, - defaultValue: defaultData.startRangeSelectionColor)); - properties.add(ColorProperty( - 'endRangeSelectionColor', endRangeSelectionColor, - defaultValue: defaultData.endRangeSelectionColor)); - properties.add(ColorProperty('headerBackgroundColor', headerBackgroundColor, - defaultValue: defaultData.headerBackgroundColor)); - properties.add(ColorProperty( - 'viewHeaderBackgroundColor', viewHeaderBackgroundColor, - defaultValue: defaultData.viewHeaderBackgroundColor)); - properties.add(ColorProperty('todayHighlightColor', todayHighlightColor, - defaultValue: defaultData.todayHighlightColor)); + const SfDateRangePickerThemeData defaultData = SfDateRangePickerThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty( + 'rangeSelectionColor', + rangeSelectionColor, + defaultValue: defaultData.rangeSelectionColor, + ), + ); + properties.add( + ColorProperty( + 'weekNumberBackgroundColor', + weekNumberBackgroundColor, + defaultValue: defaultData.weekNumberBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'selectionColor', + selectionColor, + defaultValue: defaultData.selectionColor, + ), + ); + properties.add( + ColorProperty( + 'startRangeSelectionColor', + startRangeSelectionColor, + defaultValue: defaultData.startRangeSelectionColor, + ), + ); + properties.add( + ColorProperty( + 'endRangeSelectionColor', + endRangeSelectionColor, + defaultValue: defaultData.endRangeSelectionColor, + ), + ); + properties.add( + ColorProperty( + 'headerBackgroundColor', + headerBackgroundColor, + defaultValue: defaultData.headerBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'viewHeaderBackgroundColor', + viewHeaderBackgroundColor, + defaultValue: defaultData.viewHeaderBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'todayHighlightColor', + todayHighlightColor, + defaultValue: defaultData.todayHighlightColor, + ), + ); } } 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 ea5ce1ec8..886e60075 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart @@ -4,7 +4,7 @@ import '../../theme.dart'; /// Applies a theme to descendant Syncfusion radial gauges widgets. /// -/// To obtain the current theme, use [SfBarcodeTheme.of]. +/// To obtain the current theme, use [SfGaugeTheme.of]. /// /// ```dart /// Widget build(BuildContext context) { @@ -29,7 +29,7 @@ import '../../theme.dart'; class SfGaugeTheme extends InheritedTheme { /// Initialize the gauge theme const SfGaugeTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant gauges widgets. /// @@ -80,7 +80,6 @@ class SfGaugeTheme extends InheritedTheme { @override bool updateShouldNotify(SfGaugeTheme oldWidget) => data != oldWidget.data; - @override Widget wrap(BuildContext context, Widget child) { final SfGaugeTheme? ancestorTheme = @@ -119,7 +118,36 @@ class SfGaugeTheme extends InheritedTheme { @immutable class SfGaugeThemeData with Diagnosticable { /// Initialize the gauge theme data - factory SfGaugeThemeData({ + const SfGaugeThemeData({ + this.backgroundColor = Colors.transparent, + this.titleColor, + this.axisLabelColor, + this.axisLineColor, + this.majorTickColor, + this.minorTickColor, + this.markerColor, + this.markerBorderColor = Colors.transparent, + this.needleColor, + this.knobColor, + this.knobBorderColor = Colors.transparent, + this.tailColor, + this.tailBorderColor = Colors.transparent, + this.rangePointerColor, + this.rangeColor, + this.titleBorderColor = Colors.transparent, + this.titleBackgroundColor = Colors.transparent, + this.titleTextStyle, + this.axisLabelTextStyle, + this.markerTextStyle, + }); + + /// Create a [SfGaugeThemeData] given a set of exact values. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfGaugeThemeData] + /// + factory SfGaugeThemeData.raw({ Brightness? brightness, Color backgroundColor = Colors.transparent, Color? titleColor, @@ -138,26 +166,33 @@ class SfGaugeThemeData with Diagnosticable { Color? rangeColor, Color titleBorderColor = Colors.transparent, Color titleBackgroundColor = Colors.transparent, + TextStyle? titleTextStyle, + TextStyle? axisLabelTextStyle, + TextStyle? markerTextStyle, }) { - return SfGaugeThemeData.raw( - brightness: brightness, - backgroundColor: backgroundColor, - titleColor: titleColor, - axisLabelColor: axisLabelColor, - axisLineColor: axisLineColor, - majorTickColor: majorTickColor, - minorTickColor: minorTickColor, - markerColor: markerColor, - markerBorderColor: markerBorderColor, - needleColor: needleColor, - knobColor: knobColor, - knobBorderColor: knobBorderColor, - tailColor: tailColor, - tailBorderColor: tailBorderColor, - rangePointerColor: rangePointerColor, - rangeColor: rangeColor, - titleBorderColor: titleBorderColor, - titleBackgroundColor: titleBackgroundColor); + brightness = brightness ?? Brightness.light; + return SfGaugeThemeData( + backgroundColor: backgroundColor, + titleColor: titleColor, + axisLabelColor: axisLabelColor, + axisLineColor: axisLineColor, + majorTickColor: majorTickColor, + minorTickColor: minorTickColor, + markerColor: markerColor, + markerBorderColor: markerBorderColor, + needleColor: needleColor, + knobColor: knobColor, + knobBorderColor: knobBorderColor, + tailColor: tailColor, + tailBorderColor: tailBorderColor, + rangePointerColor: rangePointerColor, + rangeColor: rangeColor, + titleBorderColor: titleBorderColor, + titleBackgroundColor: titleBackgroundColor, + titleTextStyle: titleTextStyle, + axisLabelTextStyle: axisLabelTextStyle, + markerTextStyle: markerTextStyle, + ); } /// Create a [SfGaugeThemeData] given a set of exact values. @@ -167,53 +202,6 @@ class SfGaugeThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [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}); - - /// The brightness of the overall theme of the - /// application for the gauge widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// radial gauge widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// gaugeThemeData: SfGaugeThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfRadialGauge(), - /// ), - /// ) - /// ); - /// } - ///``` - final Brightness? brightness; /// Specifies the background color of gauge widgets. /// @@ -704,6 +692,75 @@ class SfGaugeThemeData with Diagnosticable { ///``` final Color titleBackgroundColor; + /// Specifies the text style for gauges title. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// gaugeThemeData: SfGaugeThemeData( + /// titleTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfRadialGauge( + /// title: GaugeTitle(text: 'Title'), + /// ), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? titleTextStyle; + + /// Specifies the text style for axis label. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// gaugeThemeData: SfGaugeThemeData( + /// axisLabelTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfRadialGauge( + /// title: GaugeTitle(text: 'Title'), + /// ), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? axisLabelTextStyle; + + /// Specifies the text style for marker text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// gaugeThemeData: SfGaugeThemeData( + /// markerTextStyle: TextStyle(color: Colors.red) + /// ) + /// ), + /// child: SfRadialGauge( + /// title: GaugeTitle(text: 'Title'), + /// ), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? markerTextStyle; + /// Creates a copy of this gauge theme data object with the matching fields /// replaced with the non-null parameter values. SfGaugeThemeData copyWith({ @@ -725,9 +782,12 @@ class SfGaugeThemeData with Diagnosticable { Color? rangeColor, Color? titleBorderColor, Color? titleBackgroundColor, + TextStyle? titleTextStyle, + TextStyle? axisLabelTextStyle, + TextStyle? markerTextStyle, }) { return SfGaugeThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, titleColor: titleColor ?? this.titleColor, axisLabelColor: axisLabelColor ?? this.axisLabelColor, @@ -745,12 +805,18 @@ class SfGaugeThemeData with Diagnosticable { rangeColor: rangeColor ?? this.rangeColor, titleBorderColor: titleBorderColor ?? this.titleBorderColor, titleBackgroundColor: titleBackgroundColor ?? this.titleBackgroundColor, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + axisLabelTextStyle: axisLabelTextStyle ?? this.axisLabelTextStyle, + markerTextStyle: markerTextStyle ?? this.markerTextStyle, ); } /// Returns the gauge theme data static SfGaugeThemeData? lerp( - SfGaugeThemeData? a, SfGaugeThemeData? b, double t) { + SfGaugeThemeData? a, + SfGaugeThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } @@ -769,12 +835,22 @@ class SfGaugeThemeData with Diagnosticable { knobBorderColor: Color.lerp(a.knobBorderColor, b.knobBorderColor, t)!, tailColor: Color.lerp(a.tailColor, b.tailColor, t), tailBorderColor: Color.lerp(a.tailBorderColor, b.tailBorderColor, t)!, - rangePointerColor: - Color.lerp(a.rangePointerColor, b.rangePointerColor, t), + rangePointerColor: Color.lerp( + a.rangePointerColor, + b.rangePointerColor, + t, + ), rangeColor: Color.lerp(a.rangeColor, b.rangeColor, t), titleBorderColor: Color.lerp(a.titleBorderColor, b.titleBorderColor, t)!, titleBackgroundColor: Color.lerp(a.titleBackgroundColor, b.titleBackgroundColor, t)!, + titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), + axisLabelTextStyle: TextStyle.lerp( + a.axisLabelTextStyle, + b.axisLabelTextStyle, + t, + ), + markerTextStyle: TextStyle.lerp(a.markerTextStyle, b.markerTextStyle, t), ); } @@ -786,7 +862,6 @@ class SfGaugeThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - return other is SfGaugeThemeData && other.backgroundColor == backgroundColor && other.titleColor == titleColor && @@ -804,7 +879,10 @@ class SfGaugeThemeData with Diagnosticable { other.rangePointerColor == rangePointerColor && other.rangeColor == rangeColor && other.titleBorderColor == titleBorderColor && - other.titleBackgroundColor == titleBackgroundColor; + other.titleBackgroundColor == titleBackgroundColor && + other.titleTextStyle == titleTextStyle && + other.axisLabelTextStyle == axisLabelTextStyle && + other.markerTextStyle == markerTextStyle; } @override @@ -826,50 +904,157 @@ class SfGaugeThemeData with Diagnosticable { rangePointerColor, rangeColor, titleBorderColor, - titleBackgroundColor + titleBackgroundColor, + titleTextStyle, + axisLabelTextStyle, + markerTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfGaugeThemeData defaultData = SfGaugeThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('titleColor', titleColor, - defaultValue: defaultData.titleColor)); - properties.add(ColorProperty('axisLabelColor', axisLabelColor, - defaultValue: defaultData.axisLabelColor)); - properties.add(ColorProperty('axisLineColor', axisLineColor, - defaultValue: defaultData.axisLineColor)); - properties.add(ColorProperty('majorTickColor', majorTickColor, - defaultValue: defaultData.majorTickColor)); - properties.add(ColorProperty('minorTickColor', minorTickColor, - defaultValue: defaultData.minorTickColor)); - properties.add(ColorProperty('markerColor', markerColor, - defaultValue: defaultData.markerColor)); - properties.add(ColorProperty('markerBorderColor', markerBorderColor, - defaultValue: defaultData.markerBorderColor)); - properties.add(ColorProperty('needleColor', needleColor, - defaultValue: defaultData.needleColor)); - properties.add(ColorProperty('knobColor', knobColor, - defaultValue: defaultData.knobColor)); - properties.add(ColorProperty('knobBorderColor', knobBorderColor, - defaultValue: defaultData.knobBorderColor)); - properties.add(ColorProperty('tailColor', tailColor, - defaultValue: defaultData.tailColor)); - properties.add(ColorProperty('tailBorderColor', tailBorderColor, - defaultValue: defaultData.tailBorderColor)); - properties.add(ColorProperty('rangePointerColor', rangePointerColor, - defaultValue: defaultData.rangePointerColor)); - properties.add(ColorProperty('rangeColor', rangeColor, - defaultValue: defaultData.rangeColor)); - properties.add(ColorProperty('titleBorderColor', titleBorderColor, - defaultValue: defaultData.titleBorderColor)); - properties.add(ColorProperty('titleBackgroundColor', titleBackgroundColor, - defaultValue: defaultData.titleBackgroundColor)); + const SfGaugeThemeData defaultData = SfGaugeThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty( + 'titleColor', + titleColor, + defaultValue: defaultData.titleColor, + ), + ); + properties.add( + ColorProperty( + 'axisLabelColor', + axisLabelColor, + defaultValue: defaultData.axisLabelColor, + ), + ); + properties.add( + ColorProperty( + 'axisLineColor', + axisLineColor, + defaultValue: defaultData.axisLineColor, + ), + ); + properties.add( + ColorProperty( + 'majorTickColor', + majorTickColor, + defaultValue: defaultData.majorTickColor, + ), + ); + properties.add( + ColorProperty( + 'minorTickColor', + minorTickColor, + defaultValue: defaultData.minorTickColor, + ), + ); + properties.add( + ColorProperty( + 'markerColor', + markerColor, + defaultValue: defaultData.markerColor, + ), + ); + properties.add( + ColorProperty( + 'markerBorderColor', + markerBorderColor, + defaultValue: defaultData.markerBorderColor, + ), + ); + properties.add( + ColorProperty( + 'needleColor', + needleColor, + defaultValue: defaultData.needleColor, + ), + ); + properties.add( + ColorProperty( + 'knobColor', + knobColor, + defaultValue: defaultData.knobColor, + ), + ); + properties.add( + ColorProperty( + 'knobBorderColor', + knobBorderColor, + defaultValue: defaultData.knobBorderColor, + ), + ); + properties.add( + ColorProperty( + 'tailColor', + tailColor, + defaultValue: defaultData.tailColor, + ), + ); + properties.add( + ColorProperty( + 'tailBorderColor', + tailBorderColor, + defaultValue: defaultData.tailBorderColor, + ), + ); + properties.add( + ColorProperty( + 'rangePointerColor', + rangePointerColor, + defaultValue: defaultData.rangePointerColor, + ), + ); + properties.add( + ColorProperty( + 'rangeColor', + rangeColor, + defaultValue: defaultData.rangeColor, + ), + ); + properties.add( + ColorProperty( + 'titleBorderColor', + titleBorderColor, + defaultValue: defaultData.titleBorderColor, + ), + ); + properties.add( + ColorProperty( + 'titleBackgroundColor', + titleBackgroundColor, + defaultValue: defaultData.titleBackgroundColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'titleTextStyle', + titleTextStyle, + defaultValue: defaultData.titleTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'axisLabelTextStyle', + axisLabelTextStyle, + defaultValue: defaultData.axisLabelTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'markerTextStyle', + markerTextStyle, + defaultValue: defaultData.markerTextStyle, + ), + ); } } 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 fb0543753..992dbd87c 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart @@ -24,7 +24,7 @@ class SfMapsTheme extends InheritedTheme { /// /// The [data] and [child] arguments must not be null. const SfMapsTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant maps widgets. /// @@ -127,10 +127,46 @@ class SfMapsTheme extends InheritedTheme { /// ``` @immutable class SfMapsThemeData with Diagnosticable { + /// Create a [SfMapsThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfMapsThemeData] constructor. + const SfMapsThemeData({ + this.layerColor, + this.layerStrokeColor, + this.layerStrokeWidth = 1.0, + this.shapeHoverColor, + this.shapeHoverStrokeColor, + this.shapeHoverStrokeWidth, + this.legendTextStyle, + this.markerIconColor, + this.markerIconStrokeColor, + this.markerIconStrokeWidth = 1.0, + this.dataLabelTextStyle, + this.bubbleColor, + this.bubbleStrokeColor, + this.bubbleStrokeWidth = 1.0, + this.bubbleHoverColor, + this.bubbleHoverStrokeColor, + this.bubbleHoverStrokeWidth, + this.selectionColor, + this.selectionStrokeColor, + this.selectionStrokeWidth = 0.5, + this.tooltipColor, + this.tooltipStrokeColor, + this.tooltipStrokeWidth = 1.0, + this.tooltipBorderRadius = const BorderRadius.all(Radius.circular(4.0)), + this.toggledItemColor, + this.toggledItemStrokeColor, + this.toggledItemStrokeWidth, + }); + /// Returns a new instance of [SfMapsThemeData.raw] for the given values. /// /// If any of the values are null, the default values will be set. - factory SfMapsThemeData({ + factory SfMapsThemeData.raw({ Brightness? brightness, Color? layerColor, Color? layerStrokeColor, @@ -168,8 +204,7 @@ class SfMapsThemeData with Diagnosticable { tooltipStrokeWidth ??= 1.0; tooltipBorderRadius ??= const BorderRadius.all(Radius.circular(4.0)); - return SfMapsThemeData.raw( - brightness: brightness, + return SfMapsThemeData( layerColor: layerColor, layerStrokeColor: layerStrokeColor, shapeHoverColor: shapeHoverColor, @@ -200,70 +235,6 @@ class SfMapsThemeData with Diagnosticable { ); } - /// Create a [SfMapsThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfMapsThemeData] constructor. - const SfMapsThemeData.raw({ - 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 - /// application for the maps widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// maps widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// mapsThemeData: SfMapsThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfMaps(), - /// ), - /// ) - /// ); - /// } - /// ``` - final Brightness brightness; - /// Specifies the fill color for maps layer. /// /// ```dart @@ -888,7 +859,7 @@ class SfMapsThemeData with Diagnosticable { double? toggledItemStrokeWidth, }) { return SfMapsThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, layerColor: layerColor ?? this.layerColor, layerStrokeColor: layerStrokeColor ?? this.layerStrokeColor, layerStrokeWidth: layerStrokeWidth ?? this.layerStrokeWidth, @@ -931,54 +902,94 @@ class SfMapsThemeData with Diagnosticable { /// /// The arguments must not be null. static SfMapsThemeData? lerp( - SfMapsThemeData? a, SfMapsThemeData? b, double t) { + SfMapsThemeData? a, + SfMapsThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfMapsThemeData( layerColor: Color.lerp(a!.layerColor, b!.layerColor, t), layerStrokeColor: Color.lerp(a.layerStrokeColor, b.layerStrokeColor, t), - layerStrokeWidth: lerpDouble(a.layerStrokeWidth, b.layerStrokeWidth, t), + layerStrokeWidth: lerpDouble(a.layerStrokeWidth, b.layerStrokeWidth, t)!, shapeHoverColor: Color.lerp(a.shapeHoverColor, b.shapeHoverColor, t), - shapeHoverStrokeColor: - Color.lerp(a.shapeHoverStrokeColor, b.shapeHoverStrokeColor, t), - shapeHoverStrokeWidth: - lerpDouble(a.shapeHoverStrokeWidth, b.shapeHoverStrokeWidth, t), + shapeHoverStrokeColor: Color.lerp( + a.shapeHoverStrokeColor, + b.shapeHoverStrokeColor, + t, + ), + shapeHoverStrokeWidth: lerpDouble( + a.shapeHoverStrokeWidth, + b.shapeHoverStrokeWidth, + t, + ), legendTextStyle: TextStyle.lerp(a.legendTextStyle, b.legendTextStyle, t), markerIconColor: Color.lerp(a.markerIconColor, b.markerIconColor, t), - markerIconStrokeColor: - Color.lerp(a.markerIconStrokeColor, b.markerIconStrokeColor, t), + markerIconStrokeColor: Color.lerp( + a.markerIconStrokeColor, + b.markerIconStrokeColor, + t, + ), markerIconStrokeWidth: - lerpDouble(a.markerIconStrokeWidth, b.markerIconStrokeWidth, t), - dataLabelTextStyle: - TextStyle.lerp(a.dataLabelTextStyle, b.dataLabelTextStyle, t), + lerpDouble(a.markerIconStrokeWidth, b.markerIconStrokeWidth, t)!, + dataLabelTextStyle: TextStyle.lerp( + a.dataLabelTextStyle, + b.dataLabelTextStyle, + t, + ), bubbleColor: Color.lerp(a.bubbleColor, b.bubbleColor, t), - bubbleStrokeColor: - Color.lerp(a.bubbleStrokeColor, b.bubbleStrokeColor, t), + bubbleStrokeColor: Color.lerp( + a.bubbleStrokeColor, + b.bubbleStrokeColor, + t, + ), bubbleStrokeWidth: - lerpDouble(a.bubbleStrokeWidth, b.bubbleStrokeWidth, t), + lerpDouble(a.bubbleStrokeWidth, b.bubbleStrokeWidth, t)!, bubbleHoverColor: Color.lerp(a.bubbleHoverColor, b.bubbleHoverColor, t), - bubbleHoverStrokeColor: - Color.lerp(a.bubbleHoverStrokeColor, b.bubbleHoverStrokeColor, t), - bubbleHoverStrokeWidth: - lerpDouble(a.bubbleHoverStrokeWidth, b.bubbleHoverStrokeWidth, t), + bubbleHoverStrokeColor: Color.lerp( + a.bubbleHoverStrokeColor, + b.bubbleHoverStrokeColor, + t, + ), + bubbleHoverStrokeWidth: lerpDouble( + a.bubbleHoverStrokeWidth, + b.bubbleHoverStrokeWidth, + t, + ), selectionColor: Color.lerp(a.selectionColor, b.selectionColor, t), - selectionStrokeColor: - Color.lerp(a.selectionStrokeColor, b.selectionStrokeColor, t), + selectionStrokeColor: Color.lerp( + a.selectionStrokeColor, + b.selectionStrokeColor, + t, + ), selectionStrokeWidth: - lerpDouble(a.selectionStrokeWidth, b.selectionStrokeWidth, t), + lerpDouble(a.selectionStrokeWidth, b.selectionStrokeWidth, t)!, tooltipColor: Color.lerp(a.tooltipColor, b.tooltipColor, t), - tooltipStrokeColor: - Color.lerp(a.tooltipStrokeColor, b.tooltipStrokeColor, t), + tooltipStrokeColor: Color.lerp( + a.tooltipStrokeColor, + b.tooltipStrokeColor, + t, + ), tooltipStrokeWidth: - lerpDouble(a.tooltipStrokeWidth, b.tooltipStrokeWidth, t), - tooltipBorderRadius: BorderRadiusGeometry.lerp( - a.tooltipBorderRadius, b.tooltipBorderRadius, t), + lerpDouble(a.tooltipStrokeWidth, b.tooltipStrokeWidth, t)!, + tooltipBorderRadius: + BorderRadiusGeometry.lerp( + a.tooltipBorderRadius, + b.tooltipBorderRadius, + t, + )!, toggledItemColor: Color.lerp(a.toggledItemColor, b.toggledItemColor, t), - toggledItemStrokeColor: - Color.lerp(a.toggledItemStrokeColor, b.toggledItemStrokeColor, t), - toggledItemStrokeWidth: - lerpDouble(a.toggledItemStrokeWidth, b.toggledItemStrokeWidth, t), + toggledItemStrokeColor: Color.lerp( + a.toggledItemStrokeColor, + b.toggledItemStrokeColor, + t, + ), + toggledItemStrokeWidth: lerpDouble( + a.toggledItemStrokeWidth, + b.toggledItemStrokeWidth, + t, + ), ); } @@ -1051,77 +1062,201 @@ class SfMapsThemeData with Diagnosticable { toggledItemStrokeColor, toggledItemStrokeWidth, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfMapsThemeData defaultData = SfMapsThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('layerColor', layerColor, - defaultValue: defaultData.layerColor)); - properties.add(ColorProperty('layerStrokeColor', layerStrokeColor, - defaultValue: defaultData.layerStrokeColor)); - properties.add(DoubleProperty('layerStrokeWidth', layerStrokeWidth, - defaultValue: defaultData.layerStrokeWidth)); - properties.add(ColorProperty('shapeHoverColor', shapeHoverColor, - defaultValue: defaultData.shapeHoverColor)); - properties.add(ColorProperty('shapeHoverStrokeColor', shapeHoverStrokeColor, - defaultValue: defaultData.shapeHoverStrokeColor)); - properties.add(DoubleProperty( - 'shapeHoverStrokeWidth', shapeHoverStrokeWidth, - defaultValue: defaultData.shapeHoverStrokeWidth)); - properties.add(DiagnosticsProperty( - 'legendTextStyle', legendTextStyle, - defaultValue: defaultData.legendTextStyle)); - properties.add(ColorProperty('markerIconColor', markerIconColor, - defaultValue: defaultData.markerIconColor)); - properties.add(ColorProperty('markerIconStrokeColor', markerIconStrokeColor, - defaultValue: defaultData.markerIconStrokeColor)); - properties.add(DoubleProperty( - 'markerIconStrokeWidth', markerIconStrokeWidth, - defaultValue: defaultData.markerIconStrokeWidth)); - properties.add(DiagnosticsProperty( - 'dataLabelTextStyle', dataLabelTextStyle, - defaultValue: defaultData.dataLabelTextStyle)); - properties.add(ColorProperty('bubbleColor', bubbleColor, - defaultValue: defaultData.bubbleColor)); - properties.add(ColorProperty('bubbleStrokeColor', bubbleStrokeColor, - defaultValue: defaultData.bubbleStrokeColor)); - properties.add(DoubleProperty('bubbleStrokeWidth', bubbleStrokeWidth, - defaultValue: defaultData.bubbleStrokeWidth)); - properties.add(ColorProperty('bubbleHoverColor', bubbleHoverColor, - defaultValue: defaultData.bubbleHoverColor)); - properties.add(ColorProperty( - 'bubbleHoverStrokeColor', bubbleHoverStrokeColor, - defaultValue: defaultData.bubbleHoverStrokeColor)); - properties.add(DoubleProperty( - 'bubbleHoverStrokeWidth', bubbleHoverStrokeWidth, - defaultValue: defaultData.bubbleHoverStrokeWidth)); - properties.add(ColorProperty('selectionColor', selectionColor, - defaultValue: defaultData.selectionColor)); - properties.add(ColorProperty('selectionStrokeColor', selectionStrokeColor, - defaultValue: defaultData.selectionStrokeColor)); - properties.add(DoubleProperty('selectionStrokeWidth', selectionStrokeWidth, - defaultValue: defaultData.selectionStrokeWidth)); - properties.add(ColorProperty('tooltipColor', tooltipColor, - defaultValue: defaultData.tooltipColor)); - properties.add(ColorProperty('tooltipStrokeColor', tooltipStrokeColor, - defaultValue: defaultData.tooltipStrokeColor)); - properties.add(DoubleProperty('tooltipStrokeWidth', tooltipStrokeWidth, - defaultValue: defaultData.tooltipStrokeWidth)); - properties.add(DiagnosticsProperty( - 'tooltipBorderRadius', tooltipBorderRadius, - defaultValue: defaultData.tooltipBorderRadius)); - properties.add(ColorProperty('toggledItemColor', toggledItemColor, - defaultValue: defaultData.toggledItemColor)); - properties.add(ColorProperty( - 'toggledItemStrokeColor', toggledItemStrokeColor, - defaultValue: defaultData.toggledItemStrokeColor)); - properties.add(DoubleProperty( - 'toggledItemStrokeWidth', toggledItemStrokeWidth, - defaultValue: defaultData.toggledItemStrokeWidth)); + const SfMapsThemeData defaultData = SfMapsThemeData(); + properties.add( + ColorProperty( + 'layerColor', + layerColor, + defaultValue: defaultData.layerColor, + ), + ); + properties.add( + ColorProperty( + 'layerStrokeColor', + layerStrokeColor, + defaultValue: defaultData.layerStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'layerStrokeWidth', + layerStrokeWidth, + defaultValue: defaultData.layerStrokeWidth, + ), + ); + properties.add( + ColorProperty( + 'shapeHoverColor', + shapeHoverColor, + defaultValue: defaultData.shapeHoverColor, + ), + ); + properties.add( + ColorProperty( + 'shapeHoverStrokeColor', + shapeHoverStrokeColor, + defaultValue: defaultData.shapeHoverStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'shapeHoverStrokeWidth', + shapeHoverStrokeWidth, + defaultValue: defaultData.shapeHoverStrokeWidth, + ), + ); + properties.add( + DiagnosticsProperty( + 'legendTextStyle', + legendTextStyle, + defaultValue: defaultData.legendTextStyle, + ), + ); + properties.add( + ColorProperty( + 'markerIconColor', + markerIconColor, + defaultValue: defaultData.markerIconColor, + ), + ); + properties.add( + ColorProperty( + 'markerIconStrokeColor', + markerIconStrokeColor, + defaultValue: defaultData.markerIconStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'markerIconStrokeWidth', + markerIconStrokeWidth, + defaultValue: defaultData.markerIconStrokeWidth, + ), + ); + properties.add( + DiagnosticsProperty( + 'dataLabelTextStyle', + dataLabelTextStyle, + defaultValue: defaultData.dataLabelTextStyle, + ), + ); + properties.add( + ColorProperty( + 'bubbleColor', + bubbleColor, + defaultValue: defaultData.bubbleColor, + ), + ); + properties.add( + ColorProperty( + 'bubbleStrokeColor', + bubbleStrokeColor, + defaultValue: defaultData.bubbleStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'bubbleStrokeWidth', + bubbleStrokeWidth, + defaultValue: defaultData.bubbleStrokeWidth, + ), + ); + properties.add( + ColorProperty( + 'bubbleHoverColor', + bubbleHoverColor, + defaultValue: defaultData.bubbleHoverColor, + ), + ); + properties.add( + ColorProperty( + 'bubbleHoverStrokeColor', + bubbleHoverStrokeColor, + defaultValue: defaultData.bubbleHoverStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'bubbleHoverStrokeWidth', + bubbleHoverStrokeWidth, + defaultValue: defaultData.bubbleHoverStrokeWidth, + ), + ); + properties.add( + ColorProperty( + 'selectionColor', + selectionColor, + defaultValue: defaultData.selectionColor, + ), + ); + properties.add( + ColorProperty( + 'selectionStrokeColor', + selectionStrokeColor, + defaultValue: defaultData.selectionStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'selectionStrokeWidth', + selectionStrokeWidth, + defaultValue: defaultData.selectionStrokeWidth, + ), + ); + properties.add( + ColorProperty( + 'tooltipColor', + tooltipColor, + defaultValue: defaultData.tooltipColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipStrokeColor', + tooltipStrokeColor, + defaultValue: defaultData.tooltipStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'tooltipStrokeWidth', + tooltipStrokeWidth, + defaultValue: defaultData.tooltipStrokeWidth, + ), + ); + properties.add( + DiagnosticsProperty( + 'tooltipBorderRadius', + tooltipBorderRadius, + defaultValue: defaultData.tooltipBorderRadius, + ), + ); + properties.add( + ColorProperty( + 'toggledItemColor', + toggledItemColor, + defaultValue: defaultData.toggledItemColor, + ), + ); + properties.add( + ColorProperty( + 'toggledItemStrokeColor', + toggledItemStrokeColor, + defaultValue: defaultData.toggledItemStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'toggledItemStrokeWidth', + toggledItemStrokeWidth, + defaultValue: defaultData.toggledItemStrokeWidth, + ), + ); } } 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 1586d0692..3eb02145f 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart @@ -23,7 +23,7 @@ import '../../theme.dart'; class SfPdfViewerTheme extends InheritedTheme { /// Creates an argument constructor of [SfPdfViewerTheme] class. const SfPdfViewerTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant /// [SfPdfViewer] widgets. @@ -109,73 +109,45 @@ class SfPdfViewerTheme extends InheritedTheme { /// ``` @immutable 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, - PdfPasswordDialogStyle? passwordDialogStyle}) { - return SfPdfViewerThemeData.raw( - brightness: brightness, - backgroundColor: backgroundColor, - progressBarColor: progressBarColor, - scrollStatusStyle: scrollStatusStyle, - scrollHeadStyle: scrollHeadStyle, - bookmarkViewStyle: bookmarkViewStyle, - paginationDialogStyle: paginationDialogStyle, - passwordDialogStyle: passwordDialogStyle); - } - /// Create a [SfPdfViewerThemeData] given a set of exact values. /// All the values must be specified. /// /// This will rarely be used directly. It is used by [lerp] to /// create intermediate themes based on two themes created with the /// [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.passwordDialogStyle, + const SfPdfViewerThemeData({ + this.backgroundColor, + this.progressBarColor, + this.scrollStatusStyle, + this.scrollHeadStyle, + this.bookmarkViewStyle, + this.paginationDialogStyle, + this.hyperlinkDialogStyle, + this.passwordDialogStyle, }); - /// The brightness of the overall theme of the - /// application for [SfPdfViewer] widget. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).colorScheme.brightness], brightness for - /// [SfPdfViewer] widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// pdfViewerThemeData: SfPdfViewerThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfPdfViewer.asset( - /// 'assets/flutter-succinctly.pdf', - /// ), - /// ), - /// ) - /// ); - /// } - ///``` - final Brightness? brightness; + /// Creating an argument constructor of SfPdfViewerThemeData class. + factory SfPdfViewerThemeData.raw({ + Color? backgroundColor, + Color? progressBarColor, + PdfScrollStatusStyle? scrollStatusStyle, + PdfScrollHeadStyle? scrollHeadStyle, + PdfBookmarkViewStyle? bookmarkViewStyle, + PdfPaginationDialogStyle? paginationDialogStyle, + PdfHyperlinkDialogStyle? hyperlinkDialogStyle, + PdfPasswordDialogStyle? passwordDialogStyle, + }) { + return SfPdfViewerThemeData( + backgroundColor: backgroundColor, + progressBarColor: progressBarColor, + scrollStatusStyle: scrollStatusStyle, + scrollHeadStyle: scrollHeadStyle, + bookmarkViewStyle: bookmarkViewStyle, + paginationDialogStyle: paginationDialogStyle, + hyperlinkDialogStyle: hyperlinkDialogStyle, + passwordDialogStyle: passwordDialogStyle, + ); + } /// Specifies the background color of [SfPdfViewer] widget. /// @@ -334,6 +306,35 @@ class SfPdfViewerThemeData with Diagnosticable { /// ``` final PdfPaginationDialogStyle? paginationDialogStyle; + /// Specifies the hyperlink dialog style of [SfPdfViewer] widget. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// pdfViewerThemeData: SfPdfViewerThemeData( + /// hyperlinkDialogStyle: PdfHyperlinkDialogStyle( + /// backgroundColor: Colors.black, + /// headerTextStyle: TextStyle(color: Colors.white) + /// contentTextStyle: TextStyle(color: Colors.grey) + /// openTextStyle: TextStyle(color: Colors.white) + /// cancelTextStyle: TextStyle(color: Colors.white) + /// closeIconColor: Colors.black, + /// ) + /// ) + /// ), + /// child: SfPdfViewer.asset( + /// 'assets/flutter-succinctly.pdf', + /// ), + /// ), + /// ) + /// ); + /// } + /// ``` + final PdfHyperlinkDialogStyle? hyperlinkDialogStyle; + /// Specifies the password dialog style of [SfPdfViewer] widget. /// /// ```dart @@ -370,17 +371,17 @@ 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, - PdfPasswordDialogStyle? passwordDialogStyle}) { + SfPdfViewerThemeData copyWith({ + Color? backgroundColor, + Color? progressBarColor, + PdfScrollStatusStyle? scrollStatusStyle, + PdfScrollHeadStyle? scrollHeadStyle, + PdfBookmarkViewStyle? bookmarkViewStyle, + PdfPaginationDialogStyle? paginationDialogStyle, + PdfHyperlinkDialogStyle? hyperlinkDialogStyle, + PdfPasswordDialogStyle? passwordDialogStyle, + }) { return SfPdfViewerThemeData.raw( - brightness: brightness ?? this.brightness, backgroundColor: backgroundColor ?? this.backgroundColor, progressBarColor: progressBarColor ?? this.progressBarColor, scrollStatusStyle: scrollStatusStyle ?? this.scrollStatusStyle, @@ -388,29 +389,54 @@ class SfPdfViewerThemeData with Diagnosticable { bookmarkViewStyle: bookmarkViewStyle ?? this.bookmarkViewStyle, paginationDialogStyle: paginationDialogStyle ?? this.paginationDialogStyle, + hyperlinkDialogStyle: hyperlinkDialogStyle ?? this.hyperlinkDialogStyle, passwordDialogStyle: passwordDialogStyle ?? this.passwordDialogStyle, ); } /// Linearly interpolate between two themes. static SfPdfViewerThemeData? lerp( - SfPdfViewerThemeData? a, SfPdfViewerThemeData? b, double t) { + SfPdfViewerThemeData? a, + SfPdfViewerThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfPdfViewerThemeData( - backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), - progressBarColor: Color.lerp(a.progressBarColor, b.progressBarColor, t), - scrollStatusStyle: PdfScrollStatusStyle.lerp( - a.scrollStatusStyle, b.scrollStatusStyle, t), - scrollHeadStyle: - PdfScrollHeadStyle.lerp(a.scrollHeadStyle, b.scrollHeadStyle, t), - bookmarkViewStyle: PdfBookmarkViewStyle.lerp( - a.bookmarkViewStyle, b.bookmarkViewStyle, t), - paginationDialogStyle: PdfPaginationDialogStyle.lerp( - a.paginationDialogStyle, b.paginationDialogStyle, t), - passwordDialogStyle: PdfPasswordDialogStyle.lerp( - a.passwordDialogStyle, b.passwordDialogStyle, 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, + ), + scrollHeadStyle: PdfScrollHeadStyle.lerp( + a.scrollHeadStyle, + b.scrollHeadStyle, + t, + ), + bookmarkViewStyle: PdfBookmarkViewStyle.lerp( + a.bookmarkViewStyle, + b.bookmarkViewStyle, + t, + ), + paginationDialogStyle: PdfPaginationDialogStyle.lerp( + a.paginationDialogStyle, + b.paginationDialogStyle, + t, + ), + hyperlinkDialogStyle: PdfHyperlinkDialogStyle.lerp( + a.hyperlinkDialogStyle, + b.hyperlinkDialogStyle, + t, + ), + passwordDialogStyle: PdfPasswordDialogStyle.lerp( + a.passwordDialogStyle, + b.passwordDialogStyle, + t, + ), + ); } @override @@ -423,13 +449,13 @@ class SfPdfViewerThemeData with Diagnosticable { } 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 && + other.hyperlinkDialogStyle == hyperlinkDialogStyle && other.passwordDialogStyle == passwordDialogStyle; } @@ -442,36 +468,72 @@ class SfPdfViewerThemeData with Diagnosticable { scrollHeadStyle, bookmarkViewStyle, paginationDialogStyle, + hyperlinkDialogStyle, passwordDialogStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfPdfViewerThemeData defaultData = SfPdfViewerThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(ColorProperty('backgroundColor', backgroundColor, - defaultValue: defaultData.backgroundColor)); - properties.add(ColorProperty('progressBarColor', progressBarColor, - defaultValue: defaultData.progressBarColor)); - properties.add(DiagnosticsProperty( - 'scrollStatusStyle', scrollStatusStyle, - defaultValue: defaultData.scrollStatusStyle)); - properties.add(DiagnosticsProperty( - 'scrollHeadStyle', scrollHeadStyle, - defaultValue: defaultData.scrollHeadStyle)); - properties.add(DiagnosticsProperty( - 'bookmarkViewStyle', bookmarkViewStyle, - defaultValue: defaultData.bookmarkViewStyle)); - properties.add(DiagnosticsProperty( - 'paginationDialogStyle', paginationDialogStyle, - defaultValue: defaultData.paginationDialogStyle)); - properties.add(DiagnosticsProperty( - 'passwordDialogStyle', passwordDialogStyle, - defaultValue: defaultData.passwordDialogStyle)); + const SfPdfViewerThemeData defaultData = SfPdfViewerThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty( + 'progressBarColor', + progressBarColor, + defaultValue: defaultData.progressBarColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'scrollStatusStyle', + scrollStatusStyle, + defaultValue: defaultData.scrollStatusStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'scrollHeadStyle', + scrollHeadStyle, + defaultValue: defaultData.scrollHeadStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'bookmarkViewStyle', + bookmarkViewStyle, + defaultValue: defaultData.bookmarkViewStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'paginationDialogStyle', + paginationDialogStyle, + defaultValue: defaultData.paginationDialogStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'hyperlinkDialogStyle', + hyperlinkDialogStyle, + defaultValue: defaultData.hyperlinkDialogStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'passwordDialogStyle', + passwordDialogStyle, + defaultValue: defaultData.passwordDialogStyle, + ), + ); } } @@ -491,7 +553,7 @@ class PdfScrollStatusStyle { // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { final List values = [backgroundColor, pageInfoTextStyle]; - return hashList(values); + return Object.hashAll(values); } @override @@ -510,14 +572,21 @@ class PdfScrollStatusStyle { /// Linearly interpolate between two styles. static PdfScrollStatusStyle? lerp( - PdfScrollStatusStyle? a, PdfScrollStatusStyle? b, double t) { + PdfScrollStatusStyle? a, + PdfScrollStatusStyle? b, + double t, + ) { if (a == null && b == null) { return null; } return PdfScrollStatusStyle( - backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), - pageInfoTextStyle: - TextStyle.lerp(a.pageInfoTextStyle, b.pageInfoTextStyle, t)); + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + pageInfoTextStyle: TextStyle.lerp( + a.pageInfoTextStyle, + b.pageInfoTextStyle, + t, + ), + ); } } @@ -538,9 +607,9 @@ class PdfScrollHeadStyle { int get hashCode { final List values = [ backgroundColor, - pageNumberTextStyle + pageNumberTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override @@ -559,14 +628,21 @@ class PdfScrollHeadStyle { /// Linearly interpolate between two styles. static PdfScrollHeadStyle? lerp( - PdfScrollHeadStyle? a, PdfScrollHeadStyle? b, double t) { + PdfScrollHeadStyle? a, + PdfScrollHeadStyle? b, + double t, + ) { if (a == null && b == null) { return null; } return PdfScrollHeadStyle( - backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), - pageNumberTextStyle: - TextStyle.lerp(a.pageNumberTextStyle, b.pageNumberTextStyle, t)); + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + pageNumberTextStyle: TextStyle.lerp( + a.pageNumberTextStyle, + b.pageNumberTextStyle, + t, + ), + ); } } @@ -574,16 +650,17 @@ class PdfScrollHeadStyle { class PdfBookmarkViewStyle { /// Creates a [PdfBookmarkViewStyle] that's used to configure styles for the /// bookmark view in [SfPdfViewer]. - const PdfBookmarkViewStyle( - {this.backgroundColor, - this.headerBarColor, - this.closeIconColor, - this.backIconColor, - this.navigationIconColor, - this.selectionColor, - this.titleSeparatorColor, - this.titleTextStyle, - this.headerTextStyle}); + const PdfBookmarkViewStyle({ + this.backgroundColor, + this.headerBarColor, + this.closeIconColor, + this.backIconColor, + this.navigationIconColor, + this.selectionColor, + this.titleSeparatorColor, + this.titleTextStyle, + this.headerTextStyle, + }); /// The background color of bookmark view in [SfPdfViewer]. final Color? backgroundColor; @@ -624,9 +701,9 @@ class PdfBookmarkViewStyle { selectionColor, titleSeparatorColor, titleTextStyle, - headerTextStyle + headerTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override @@ -652,23 +729,32 @@ class PdfBookmarkViewStyle { /// Linearly interpolate between two styles. static PdfBookmarkViewStyle? lerp( - PdfBookmarkViewStyle? a, PdfBookmarkViewStyle? b, double t) { + PdfBookmarkViewStyle? a, + PdfBookmarkViewStyle? b, + double t, + ) { if (a == null && b == null) { return null; } return PdfBookmarkViewStyle( - 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), - navigationIconColor: - Color.lerp(a.navigationIconColor, b.navigationIconColor, t), - selectionColor: Color.lerp(a.selectionColor, b.selectionColor, t), - titleSeparatorColor: - Color.lerp(a.titleSeparatorColor, b.titleSeparatorColor, t), - titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - headerTextStyle: - TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, 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), + navigationIconColor: Color.lerp( + a.navigationIconColor, + b.navigationIconColor, + t, + ), + selectionColor: Color.lerp(a.selectionColor, b.selectionColor, t), + titleSeparatorColor: Color.lerp( + a.titleSeparatorColor, + b.titleSeparatorColor, + t, + ), + titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), + headerTextStyle: TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, t), + ); } } @@ -677,15 +763,16 @@ class PdfBookmarkViewStyle { class PdfPaginationDialogStyle { /// Creates a [PdfPaginationDialogStyle] that's used to configure styles for /// the pagination dialog in [SfPdfViewer]. - const PdfPaginationDialogStyle( - {this.backgroundColor, - this.headerTextStyle, - this.inputFieldTextStyle, - this.hintTextStyle, - this.pageInfoTextStyle, - this.validationTextStyle, - this.okTextStyle, - this.cancelTextStyle}); + const PdfPaginationDialogStyle({ + this.backgroundColor, + this.headerTextStyle, + this.inputFieldTextStyle, + this.hintTextStyle, + this.pageInfoTextStyle, + this.validationTextStyle, + this.okTextStyle, + this.cancelTextStyle, + }); /// The background color of pagination dialog in [SfPdfViewer]. final Color? backgroundColor; @@ -724,9 +811,9 @@ class PdfPaginationDialogStyle { pageInfoTextStyle, validationTextStyle, okTextStyle, - cancelTextStyle + cancelTextStyle, ]; - return hashList(values); + return Object.hashAll(values); } @override @@ -751,24 +838,124 @@ class PdfPaginationDialogStyle { /// Linearly interpolate between two styles. static PdfPaginationDialogStyle? lerp( - PdfPaginationDialogStyle? a, PdfPaginationDialogStyle? b, double t) { + PdfPaginationDialogStyle? a, + PdfPaginationDialogStyle? b, + double t, + ) { if (a == null && b == null) { return null; } return PdfPaginationDialogStyle( - backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), - headerTextStyle: - TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, t), - inputFieldTextStyle: - TextStyle.lerp(a.inputFieldTextStyle, b.inputFieldTextStyle, t), - hintTextStyle: TextStyle.lerp(a.hintTextStyle, b.hintTextStyle, t), - pageInfoTextStyle: - TextStyle.lerp(a.pageInfoTextStyle, b.pageInfoTextStyle, t), - validationTextStyle: - TextStyle.lerp(a.validationTextStyle, b.validationTextStyle, t), - okTextStyle: TextStyle.lerp(a.okTextStyle, b.okTextStyle, t), - cancelTextStyle: - TextStyle.lerp(a.cancelTextStyle, b.cancelTextStyle, t)); + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + headerTextStyle: TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, t), + inputFieldTextStyle: TextStyle.lerp( + a.inputFieldTextStyle, + b.inputFieldTextStyle, + t, + ), + hintTextStyle: TextStyle.lerp(a.hintTextStyle, b.hintTextStyle, t), + pageInfoTextStyle: TextStyle.lerp( + a.pageInfoTextStyle, + b.pageInfoTextStyle, + t, + ), + validationTextStyle: TextStyle.lerp( + a.validationTextStyle, + b.validationTextStyle, + t, + ), + okTextStyle: TextStyle.lerp(a.okTextStyle, b.okTextStyle, t), + cancelTextStyle: TextStyle.lerp(a.cancelTextStyle, b.cancelTextStyle, t), + ); + } +} + +/// Holds the color and text styles for the hyperlink dialog in +/// the [SfPdfViewer]. +class PdfHyperlinkDialogStyle { + /// Creates a [PdfHyperlinkDialogStyle] that's used to configure styles for + /// the hyperlink dialog in [SfPdfViewer]. + const PdfHyperlinkDialogStyle({ + this.backgroundColor, + this.headerTextStyle, + this.contentTextStyle, + this.openTextStyle, + this.cancelTextStyle, + this.closeIconColor, + }); + + /// The background color of hyperlink dialog in [SfPdfViewer]. + final Color? backgroundColor; + + /// The style for the header text of hyperlink dialog in [SfPdfViewer]. + final TextStyle? headerTextStyle; + + /// The style for the content text of hyperlink dialog + /// in [SfPdfViewer]. + final TextStyle? contentTextStyle; + + /// The style for the Open button text of hyperlink dialog in [SfPdfViewer]. + final TextStyle? openTextStyle; + + /// The style for the Cancel button of hyperlink dialog in [SfPdfViewer]. + final TextStyle? cancelTextStyle; + + /// The close icon color of hyperlink dialog in [SfPdfViewer]. + final Color? closeIconColor; + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode { + final List values = [ + backgroundColor, + headerTextStyle, + contentTextStyle, + openTextStyle, + cancelTextStyle, + closeIconColor, + ]; + return Object.hashAll(values); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is PdfHyperlinkDialogStyle && + other.backgroundColor == backgroundColor && + other.headerTextStyle == headerTextStyle && + other.contentTextStyle == contentTextStyle && + other.openTextStyle == openTextStyle && + other.cancelTextStyle == cancelTextStyle && + other.closeIconColor == closeIconColor; + } + + /// Linearly interpolate between two styles. + static PdfHyperlinkDialogStyle? lerp( + PdfHyperlinkDialogStyle? a, + PdfHyperlinkDialogStyle? b, + double t, + ) { + if (a == null && b == null) { + return null; + } + return PdfHyperlinkDialogStyle( + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + headerTextStyle: TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, t), + contentTextStyle: TextStyle.lerp( + a.contentTextStyle, + b.contentTextStyle, + t, + ), + openTextStyle: TextStyle.lerp(a.openTextStyle, b.openTextStyle, t), + cancelTextStyle: TextStyle.lerp(a.cancelTextStyle, b.cancelTextStyle, t), + closeIconColor: Color.lerp(a.closeIconColor, b.closeIconColor, t), + ); } } @@ -851,9 +1038,9 @@ class PdfPasswordDialogStyle { closeIconColor, visibleIconColor, inputFieldBorderColor, - errorBorderColor + errorBorderColor, ]; - return hashList(values); + return Object.hashAll(values); } @override @@ -883,28 +1070,46 @@ class PdfPasswordDialogStyle { /// Linearly interpolate between two styles. static PdfPasswordDialogStyle? lerp( - PdfPasswordDialogStyle? a, PdfPasswordDialogStyle? b, double t) { + PdfPasswordDialogStyle? a, + PdfPasswordDialogStyle? b, + double t, + ) { if (a == null && b == null) { return null; } return PdfPasswordDialogStyle( backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), headerTextStyle: TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, t), - contentTextStyle: - TextStyle.lerp(a.contentTextStyle, b.contentTextStyle, t), - inputFieldTextStyle: - TextStyle.lerp(a.inputFieldTextStyle, b.inputFieldTextStyle, t), + contentTextStyle: TextStyle.lerp( + a.contentTextStyle, + b.contentTextStyle, + t, + ), + inputFieldTextStyle: TextStyle.lerp( + a.inputFieldTextStyle, + b.inputFieldTextStyle, + t, + ), inputFieldHintTextStyle: TextStyle.lerp( - a.inputFieldHintTextStyle, b.inputFieldHintTextStyle, t), + a.inputFieldHintTextStyle, + b.inputFieldHintTextStyle, + t, + ), inputFieldLabelTextStyle: TextStyle.lerp( - a.inputFieldLabelTextStyle, b.inputFieldLabelTextStyle, t), + a.inputFieldLabelTextStyle, + b.inputFieldLabelTextStyle, + t, + ), errorTextStyle: TextStyle.lerp(a.errorTextStyle, b.errorTextStyle, t), openTextStyle: TextStyle.lerp(a.openTextStyle, b.openTextStyle, t), cancelTextStyle: TextStyle.lerp(a.cancelTextStyle, b.cancelTextStyle, t), closeIconColor: Color.lerp(a.closeIconColor, b.closeIconColor, t), visibleIconColor: Color.lerp(a.visibleIconColor, b.visibleIconColor, t), - inputFieldBorderColor: - Color.lerp(a.inputFieldBorderColor, b.inputFieldBorderColor, t), + inputFieldBorderColor: Color.lerp( + a.inputFieldBorderColor, + b.inputFieldBorderColor, + t, + ), errorBorderColor: Color.lerp(a.errorBorderColor, b.errorBorderColor, t), ); } 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 944b80389..b6b885cdc 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 @@ -10,9 +10,11 @@ class SfRangeSelectorTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. /// /// The [data] and [child] arguments must not be null. - const SfRangeSelectorTheme( - {Key? key, required this.data, required this.child}) - : super(key: key, child: child); + const SfRangeSelectorTheme({ + Key? key, + required this.data, + required this.child, + }) : super(key: key, child: child); /// Specifies the color and typography values for descendant /// range selector widgets. @@ -149,56 +151,145 @@ class SfRangeSelectorTheme extends InheritedTheme { /// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), /// for customizing the visual appearance of the range selector. class SfRangeSelectorThemeData extends SfRangeSliderThemeData { - /// Returns a new instance of [SfRangeSelectorThemeData.raw] - /// for the given values. - /// - /// 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? inactiveDividerColor, - Color? activeDividerColor, - Color? disabledActiveTrackColor, - Color? disabledInactiveTrackColor, - Color? disabledActiveDividerColor, - Color? disabledInactiveDividerColor, - Color? disabledThumbColor, - Color? activeRegionColor, - Color? inactiveRegionColor, - Color? tooltipBackgroundColor, - Color? overlappingTooltipStrokeColor, - Color? thumbStrokeColor, - Color? overlappingThumbStrokeColor, - Color? activeDividerStrokeColor, - Color? inactiveDividerStrokeColor, - double? trackCornerRadius, - double? overlayRadius, - double? thumbRadius, - double? activeDividerRadius, - double? inactiveDividerRadius, - double? thumbStrokeWidth, - double? activeDividerStrokeWidth, - double? inactiveDividerStrokeWidth}) { + /// Creating an argument constructor of SfRangeSelectorThemeData class. + const SfRangeSelectorThemeData({ + double activeTrackHeight = 6.0, + double inactiveTrackHeight = 4.0, + 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? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + this.activeRegionColor, + this.inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + double? trackCornerRadius, + double overlayRadius = 24.0, + double thumbRadius = 10.0, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) : super( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + overlappingThumbStrokeColor: overlappingThumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius, + ); + + /// Returns a new instance of [SfRangeSelectorThemeData] for the given values. + factory SfRangeSelectorThemeData.raw({ + 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? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; @@ -207,151 +298,54 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { overlayRadius ??= 24.0; thumbRadius ??= 10.0; - return SfRangeSelectorThemeData.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - overlappingThumbStrokeColor: overlappingThumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - activeRegionColor: activeRegionColor, - inactiveRegionColor: inactiveRegionColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); + return SfRangeSelectorThemeData( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + overlappingThumbStrokeColor: overlappingThumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + activeRegionColor: activeRegionColor, + inactiveRegionColor: inactiveRegionColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius, + ); } - /// Create a [SfRangeSelectorThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// 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? activeDividerStrokeColor, - required Color? inactiveDividerStrokeColor, - 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? inactiveDividerColor, - required Color? activeDividerColor, - required Color? disabledActiveTrackColor, - required Color? disabledInactiveTrackColor, - required Color? disabledActiveDividerColor, - required Color? disabledInactiveDividerColor, - 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? activeDividerRadius, - required double? inactiveDividerRadius, - required double? thumbStrokeWidth, - required double? activeDividerStrokeWidth, - required double? inactiveDividerStrokeWidth, - }) : super.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - overlappingThumbStrokeColor: overlappingThumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); - /// Specifies the color for the active region of the /// child in the [SfRangeSelector]. /// @@ -479,7 +473,7 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { double? inactiveDividerStrokeWidth, }) { return SfRangeSelectorThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, activeTrackHeight: activeTrackHeight ?? this.activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight ?? this.inactiveTrackHeight, tickSize: tickSize ?? this.tickSize, @@ -548,89 +542,187 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { /// /// The arguments must not be null. static SfRangeSelectorThemeData? lerp( - SfRangeSelectorThemeData? a, SfRangeSelectorThemeData? b, double t) { + SfRangeSelectorThemeData? a, + SfRangeSelectorThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfRangeSelectorThemeData( - activeTrackHeight: - lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), - inactiveTrackHeight: - lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), - tickSize: Size.lerp(a.tickSize, b.tickSize, t), - minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), - tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), - labelOffset: Offset.lerp(a.labelOffset, b.labelOffset, t), - inactiveLabelStyle: - TextStyle.lerp(a.inactiveLabelStyle, b.inactiveLabelStyle, t), - activeLabelStyle: - TextStyle.lerp(a.activeLabelStyle, b.activeLabelStyle, t), - tooltipTextStyle: - TextStyle.lerp(a.tooltipTextStyle, b.tooltipTextStyle, t), - inactiveTrackColor: - Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t), - activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), - thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), - thumbStrokeColor: Color.lerp(a.thumbStrokeColor, b.thumbStrokeColor, t), - overlappingThumbStrokeColor: Color.lerp( - a.overlappingThumbStrokeColor, b.overlappingThumbStrokeColor, t), - activeDividerStrokeColor: Color.lerp( - a.activeDividerStrokeColor, b.activeDividerStrokeColor, t), - inactiveDividerStrokeColor: Color.lerp( - a.inactiveDividerStrokeColor, b.inactiveDividerStrokeColor, t), - activeTickColor: Color.lerp(a.activeTickColor, b.activeTickColor, t), - inactiveTickColor: - Color.lerp(a.inactiveTickColor, b.inactiveTickColor, t), - disabledActiveTickColor: - Color.lerp(a.disabledActiveTickColor, b.disabledActiveTickColor, t), - disabledInactiveTickColor: Color.lerp( - a.disabledInactiveTickColor, b.disabledInactiveTickColor, t), - activeMinorTickColor: - Color.lerp(a.activeMinorTickColor, b.activeMinorTickColor, t), - inactiveMinorTickColor: - Color.lerp(a.inactiveMinorTickColor, b.inactiveMinorTickColor, t), - disabledActiveMinorTickColor: Color.lerp( - a.disabledActiveMinorTickColor, b.disabledActiveMinorTickColor, t), - disabledInactiveMinorTickColor: Color.lerp( - a.disabledInactiveMinorTickColor, - b.disabledInactiveMinorTickColor, - t), - overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), - inactiveDividerColor: - Color.lerp(a.inactiveDividerColor, b.inactiveDividerColor, t), - activeDividerColor: - Color.lerp(a.activeDividerColor, b.activeDividerColor, t), - disabledActiveTrackColor: Color.lerp( - a.disabledActiveTrackColor, b.disabledActiveTrackColor, t), - disabledInactiveTrackColor: Color.lerp( - a.disabledInactiveTrackColor, b.disabledInactiveTrackColor, t), - disabledActiveDividerColor: Color.lerp( - a.disabledActiveDividerColor, b.disabledActiveDividerColor, t), - disabledInactiveDividerColor: Color.lerp( - a.disabledInactiveDividerColor, b.disabledInactiveDividerColor, t), - disabledThumbColor: - Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t), - activeRegionColor: - Color.lerp(a.activeRegionColor, b.activeRegionColor, t), - inactiveRegionColor: - Color.lerp(a.inactiveRegionColor, b.inactiveRegionColor, t), - tooltipBackgroundColor: - Color.lerp(a.tooltipBackgroundColor, b.tooltipBackgroundColor, t), - // ignore: lines_longer_than_80_chars - overlappingTooltipStrokeColor: Color.lerp(a.overlappingTooltipStrokeColor, b.overlappingTooltipStrokeColor, t), - // ignore: lines_longer_than_80_chars - trackCornerRadius: lerpDouble(a.trackCornerRadius, b.trackCornerRadius, t), - overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t), - thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t), - // ignore: lines_longer_than_80_chars - activeDividerRadius: lerpDouble(a.activeDividerRadius, b.activeDividerRadius, t), - // ignore: lines_longer_than_80_chars - inactiveDividerRadius: lerpDouble(a.inactiveDividerRadius, b.inactiveDividerRadius, t), - thumbStrokeWidth: lerpDouble(a.thumbStrokeWidth, b.thumbStrokeWidth, t), - // ignore: lines_longer_than_80_chars - activeDividerStrokeWidth: lerpDouble(a.activeDividerStrokeWidth, b.activeDividerStrokeWidth, t), - // ignore: lines_longer_than_80_chars - inactiveDividerStrokeWidth: lerpDouble(a.inactiveDividerStrokeWidth, b.inactiveDividerStrokeWidth, t)); + activeTrackHeight: + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t)!, + inactiveTrackHeight: + lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t)!, + tickSize: Size.lerp(a.tickSize, b.tickSize, t), + minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), + tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), + labelOffset: Offset.lerp(a.labelOffset, b.labelOffset, t), + inactiveLabelStyle: TextStyle.lerp( + a.inactiveLabelStyle, + b.inactiveLabelStyle, + t, + ), + activeLabelStyle: TextStyle.lerp( + a.activeLabelStyle, + b.activeLabelStyle, + t, + ), + tooltipTextStyle: TextStyle.lerp( + a.tooltipTextStyle, + b.tooltipTextStyle, + t, + ), + inactiveTrackColor: Color.lerp( + a.inactiveTrackColor, + b.inactiveTrackColor, + t, + ), + activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), + thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), + thumbStrokeColor: Color.lerp(a.thumbStrokeColor, b.thumbStrokeColor, t), + overlappingThumbStrokeColor: Color.lerp( + a.overlappingThumbStrokeColor, + b.overlappingThumbStrokeColor, + t, + ), + activeDividerStrokeColor: Color.lerp( + a.activeDividerStrokeColor, + b.activeDividerStrokeColor, + t, + ), + inactiveDividerStrokeColor: Color.lerp( + a.inactiveDividerStrokeColor, + b.inactiveDividerStrokeColor, + t, + ), + activeTickColor: Color.lerp(a.activeTickColor, b.activeTickColor, t), + inactiveTickColor: Color.lerp( + a.inactiveTickColor, + b.inactiveTickColor, + t, + ), + disabledActiveTickColor: Color.lerp( + a.disabledActiveTickColor, + b.disabledActiveTickColor, + t, + ), + disabledInactiveTickColor: Color.lerp( + a.disabledInactiveTickColor, + b.disabledInactiveTickColor, + t, + ), + activeMinorTickColor: Color.lerp( + a.activeMinorTickColor, + b.activeMinorTickColor, + t, + ), + inactiveMinorTickColor: Color.lerp( + a.inactiveMinorTickColor, + b.inactiveMinorTickColor, + t, + ), + disabledActiveMinorTickColor: Color.lerp( + a.disabledActiveMinorTickColor, + b.disabledActiveMinorTickColor, + t, + ), + disabledInactiveMinorTickColor: Color.lerp( + a.disabledInactiveMinorTickColor, + b.disabledInactiveMinorTickColor, + t, + ), + overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), + inactiveDividerColor: Color.lerp( + a.inactiveDividerColor, + b.inactiveDividerColor, + t, + ), + activeDividerColor: Color.lerp( + a.activeDividerColor, + b.activeDividerColor, + t, + ), + disabledActiveTrackColor: Color.lerp( + a.disabledActiveTrackColor, + b.disabledActiveTrackColor, + t, + ), + disabledInactiveTrackColor: Color.lerp( + a.disabledInactiveTrackColor, + b.disabledInactiveTrackColor, + t, + ), + disabledActiveDividerColor: Color.lerp( + a.disabledActiveDividerColor, + b.disabledActiveDividerColor, + t, + ), + disabledInactiveDividerColor: Color.lerp( + a.disabledInactiveDividerColor, + b.disabledInactiveDividerColor, + t, + ), + disabledThumbColor: Color.lerp( + a.disabledThumbColor, + b.disabledThumbColor, + t, + ), + activeRegionColor: Color.lerp( + a.activeRegionColor, + b.activeRegionColor, + t, + ), + inactiveRegionColor: Color.lerp( + a.inactiveRegionColor, + b.inactiveRegionColor, + t, + ), + tooltipBackgroundColor: Color.lerp( + a.tooltipBackgroundColor, + b.tooltipBackgroundColor, + t, + ), + // ignore: lines_longer_than_80_chars + overlappingTooltipStrokeColor: Color.lerp( + a.overlappingTooltipStrokeColor, + b.overlappingTooltipStrokeColor, + t, + ), + // ignore: lines_longer_than_80_chars + trackCornerRadius: lerpDouble( + a.trackCornerRadius, + b.trackCornerRadius, + t, + ), + overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t)!, + thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t)!, + // ignore: lines_longer_than_80_chars + activeDividerRadius: lerpDouble( + a.activeDividerRadius, + b.activeDividerRadius, + t, + ), + // ignore: lines_longer_than_80_chars + inactiveDividerRadius: lerpDouble( + a.inactiveDividerRadius, + b.inactiveDividerRadius, + t, + ), + thumbStrokeWidth: lerpDouble(a.thumbStrokeWidth, b.thumbStrokeWidth, t), + // ignore: lines_longer_than_80_chars + activeDividerStrokeWidth: lerpDouble( + a.activeDividerStrokeWidth, + b.activeDividerStrokeWidth, + t, + ), + // ignore: lines_longer_than_80_chars + inactiveDividerStrokeWidth: lerpDouble( + a.inactiveDividerStrokeWidth, + b.inactiveDividerStrokeWidth, + t, + ), + ); } @override @@ -643,7 +735,6 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { } return other is SfRangeSelectorThemeData && - other.brightness == brightness && other.activeTrackHeight == activeTrackHeight && other.inactiveTrackHeight == inactiveTrackHeight && other.tickSize == tickSize && @@ -693,8 +784,7 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { @override int get hashCode { - return hashList([ - brightness, + return Object.hashAll([ activeTrackHeight, inactiveTrackHeight, tickSize, @@ -745,116 +835,314 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfRangeSelectorThemeData defaultData = SfRangeSelectorThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(DoubleProperty('activeTrackHeight', activeTrackHeight, - defaultValue: defaultData.activeTrackHeight)); - properties.add(DoubleProperty('inactiveTrackHeight', inactiveTrackHeight, - defaultValue: defaultData.inactiveTrackHeight)); - properties.add(DiagnosticsProperty('tickSize', tickSize, - defaultValue: defaultData.tickSize)); - properties.add(DiagnosticsProperty('minorTickSize', minorTickSize, - defaultValue: defaultData.minorTickSize)); - properties.add(DiagnosticsProperty('tickOffset', tickOffset, - defaultValue: defaultData.tickOffset)); - properties.add(DiagnosticsProperty('labelOffset', labelOffset, - defaultValue: defaultData.labelOffset)); - properties.add(DiagnosticsProperty( - 'inactiveLabelStyle', inactiveLabelStyle, - defaultValue: defaultData.inactiveLabelStyle)); - properties.add(DiagnosticsProperty( - 'activeLabelStyle', activeLabelStyle, - defaultValue: defaultData.activeLabelStyle)); - properties.add(DiagnosticsProperty( - 'tooltipTextStyle', tooltipTextStyle, - defaultValue: defaultData.tooltipTextStyle)); - properties.add(ColorProperty('inactiveTrackColor', inactiveTrackColor, - defaultValue: defaultData.inactiveTrackColor)); - properties.add(ColorProperty('activeTrackColor', activeTrackColor, - defaultValue: defaultData.activeTrackColor)); - properties.add(ColorProperty('thumbColor', thumbColor, - defaultValue: defaultData.thumbColor)); - properties.add(ColorProperty('thumbStrokeColor', thumbStrokeColor, - defaultValue: defaultData.thumbStrokeColor)); - properties.add(ColorProperty( - 'overlappingThumbStrokeColor', overlappingThumbStrokeColor, - defaultValue: defaultData.overlappingThumbStrokeColor)); - properties.add(ColorProperty( - 'activeDividerStrokeColor', activeDividerStrokeColor, - defaultValue: defaultData.activeDividerStrokeColor)); - properties.add(ColorProperty( - 'inactiveDividerStrokeColor', inactiveDividerStrokeColor, - defaultValue: defaultData.inactiveDividerStrokeColor)); - properties.add(ColorProperty('activeTickColor', activeTickColor, - defaultValue: defaultData.activeTickColor)); - properties.add(ColorProperty('inactiveTickColor', inactiveTickColor, - defaultValue: defaultData.inactiveTickColor)); - properties.add(ColorProperty( - 'disabledActiveTickColor', disabledActiveTickColor, - defaultValue: defaultData.disabledActiveTickColor)); - properties.add(ColorProperty( - 'disabledInactiveTickColor', disabledInactiveTickColor, - defaultValue: defaultData.disabledInactiveTickColor)); - properties.add(ColorProperty('activeMinorTickColor', activeMinorTickColor, - defaultValue: defaultData.activeMinorTickColor)); - properties.add(ColorProperty( - 'inactiveMinorTickColor', inactiveMinorTickColor, - defaultValue: defaultData.inactiveMinorTickColor)); - properties.add(ColorProperty( - 'disabledActiveMinorTickColor', disabledActiveMinorTickColor, - defaultValue: defaultData.disabledActiveMinorTickColor)); - properties.add(ColorProperty( - 'disabledInactiveMinorTickColor', disabledInactiveMinorTickColor, - defaultValue: defaultData.disabledInactiveMinorTickColor)); - properties.add(ColorProperty('overlayColor', overlayColor, - defaultValue: defaultData.overlayColor)); - properties.add(ColorProperty('inactiveDividerColor', inactiveDividerColor, - defaultValue: defaultData.inactiveDividerColor)); - properties.add(ColorProperty('activeDividerColor', activeDividerColor, - defaultValue: defaultData.activeDividerColor)); - properties.add(ColorProperty( - 'disabledActiveTrackColor', disabledActiveTrackColor, - defaultValue: defaultData.disabledActiveTrackColor)); - properties.add(ColorProperty( - 'disabledInactiveTrackColor', disabledInactiveTrackColor, - defaultValue: defaultData.disabledInactiveTrackColor)); - properties.add(ColorProperty( - 'disabledActiveDividerColor', disabledActiveDividerColor, - defaultValue: defaultData.disabledActiveDividerColor)); - properties.add(ColorProperty( - 'disabledInactiveDividerColor', disabledInactiveDividerColor, - defaultValue: defaultData.disabledInactiveDividerColor)); - properties.add(ColorProperty('disabledThumbColor', disabledThumbColor, - defaultValue: defaultData.disabledThumbColor)); - properties.add(ColorProperty('activeRegionColor', activeRegionColor, - defaultValue: defaultData.activeRegionColor)); - properties.add(ColorProperty('inactiveRegionColor', inactiveRegionColor, - defaultValue: defaultData.inactiveRegionColor)); - properties.add(ColorProperty( - 'tooltipBackgroundColor', tooltipBackgroundColor, - defaultValue: defaultData.tooltipBackgroundColor)); - properties.add(ColorProperty( - 'overlappingTooltipStrokeColor', overlappingTooltipStrokeColor, - defaultValue: defaultData.overlappingTooltipStrokeColor)); - properties.add(DoubleProperty('trackCornerRadius', trackCornerRadius, - defaultValue: defaultData.trackCornerRadius)); - properties.add(DoubleProperty('overlayRadius', overlayRadius, - defaultValue: defaultData.overlayRadius)); - properties.add(DoubleProperty('thumbRadius', thumbRadius, - defaultValue: defaultData.thumbRadius)); - properties.add(DoubleProperty('activeDividerRadius', activeDividerRadius, - defaultValue: defaultData.activeDividerRadius)); - properties.add(DoubleProperty( - 'inactiveDividerRadius', inactiveDividerRadius, - defaultValue: defaultData.inactiveDividerRadius)); - properties.add(DoubleProperty('thumbStrokeWidth', thumbStrokeWidth, - defaultValue: defaultData.thumbStrokeWidth)); - properties.add(DoubleProperty( - 'activeDividerStrokeWidth', activeDividerStrokeWidth, - defaultValue: defaultData.activeDividerStrokeWidth)); - properties.add(DoubleProperty( - 'inactiveDividerStrokeWidth', inactiveDividerStrokeWidth, - defaultValue: defaultData.inactiveDividerStrokeWidth)); + const SfRangeSelectorThemeData defaultData = SfRangeSelectorThemeData(); + properties.add( + DoubleProperty( + 'activeTrackHeight', + activeTrackHeight, + defaultValue: defaultData.activeTrackHeight, + ), + ); + properties.add( + DoubleProperty( + 'inactiveTrackHeight', + inactiveTrackHeight, + defaultValue: defaultData.inactiveTrackHeight, + ), + ); + properties.add( + DiagnosticsProperty( + 'tickSize', + tickSize, + defaultValue: defaultData.tickSize, + ), + ); + properties.add( + DiagnosticsProperty( + 'minorTickSize', + minorTickSize, + defaultValue: defaultData.minorTickSize, + ), + ); + properties.add( + DiagnosticsProperty( + 'tickOffset', + tickOffset, + defaultValue: defaultData.tickOffset, + ), + ); + properties.add( + DiagnosticsProperty( + 'labelOffset', + labelOffset, + defaultValue: defaultData.labelOffset, + ), + ); + properties.add( + DiagnosticsProperty( + 'inactiveLabelStyle', + inactiveLabelStyle, + defaultValue: defaultData.inactiveLabelStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'activeLabelStyle', + activeLabelStyle, + defaultValue: defaultData.activeLabelStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'tooltipTextStyle', + tooltipTextStyle, + defaultValue: defaultData.tooltipTextStyle, + ), + ); + properties.add( + ColorProperty( + 'inactiveTrackColor', + inactiveTrackColor, + defaultValue: defaultData.inactiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'activeTrackColor', + activeTrackColor, + defaultValue: defaultData.activeTrackColor, + ), + ); + properties.add( + ColorProperty( + 'thumbColor', + thumbColor, + defaultValue: defaultData.thumbColor, + ), + ); + properties.add( + ColorProperty( + 'thumbStrokeColor', + thumbStrokeColor, + defaultValue: defaultData.thumbStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'overlappingThumbStrokeColor', + overlappingThumbStrokeColor, + defaultValue: defaultData.overlappingThumbStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'activeDividerStrokeColor', + activeDividerStrokeColor, + defaultValue: defaultData.activeDividerStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveDividerStrokeColor', + inactiveDividerStrokeColor, + defaultValue: defaultData.inactiveDividerStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'activeTickColor', + activeTickColor, + defaultValue: defaultData.activeTickColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveTickColor', + inactiveTickColor, + defaultValue: defaultData.inactiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveTickColor', + disabledActiveTickColor, + defaultValue: defaultData.disabledActiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveTickColor', + disabledInactiveTickColor, + defaultValue: defaultData.disabledInactiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'activeMinorTickColor', + activeMinorTickColor, + defaultValue: defaultData.activeMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveMinorTickColor', + inactiveMinorTickColor, + defaultValue: defaultData.inactiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveMinorTickColor', + disabledActiveMinorTickColor, + defaultValue: defaultData.disabledActiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveMinorTickColor', + disabledInactiveMinorTickColor, + defaultValue: defaultData.disabledInactiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'overlayColor', + overlayColor, + defaultValue: defaultData.overlayColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveDividerColor', + inactiveDividerColor, + defaultValue: defaultData.inactiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'activeDividerColor', + activeDividerColor, + defaultValue: defaultData.activeDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveTrackColor', + disabledActiveTrackColor, + defaultValue: defaultData.disabledActiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveTrackColor', + disabledInactiveTrackColor, + defaultValue: defaultData.disabledInactiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveDividerColor', + disabledActiveDividerColor, + defaultValue: defaultData.disabledActiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveDividerColor', + disabledInactiveDividerColor, + defaultValue: defaultData.disabledInactiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledThumbColor', + disabledThumbColor, + defaultValue: defaultData.disabledThumbColor, + ), + ); + properties.add( + ColorProperty( + 'activeRegionColor', + activeRegionColor, + defaultValue: defaultData.activeRegionColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveRegionColor', + inactiveRegionColor, + defaultValue: defaultData.inactiveRegionColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipBackgroundColor', + tooltipBackgroundColor, + defaultValue: defaultData.tooltipBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'overlappingTooltipStrokeColor', + overlappingTooltipStrokeColor, + defaultValue: defaultData.overlappingTooltipStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'trackCornerRadius', + trackCornerRadius, + defaultValue: defaultData.trackCornerRadius, + ), + ); + properties.add( + DoubleProperty( + 'overlayRadius', + overlayRadius, + defaultValue: defaultData.overlayRadius, + ), + ); + properties.add( + DoubleProperty( + 'thumbRadius', + thumbRadius, + defaultValue: defaultData.thumbRadius, + ), + ); + properties.add( + DoubleProperty( + 'activeDividerRadius', + activeDividerRadius, + defaultValue: defaultData.activeDividerRadius, + ), + ); + properties.add( + DoubleProperty( + 'inactiveDividerRadius', + inactiveDividerRadius, + defaultValue: defaultData.inactiveDividerRadius, + ), + ); + properties.add( + DoubleProperty( + 'thumbStrokeWidth', + thumbStrokeWidth, + defaultValue: defaultData.thumbStrokeWidth, + ), + ); + properties.add( + DoubleProperty( + 'activeDividerStrokeWidth', + activeDividerStrokeWidth, + defaultValue: defaultData.activeDividerStrokeWidth, + ), + ); + properties.add( + DoubleProperty( + 'inactiveDividerStrokeWidth', + inactiveDividerStrokeWidth, + defaultValue: defaultData.inactiveDividerStrokeWidth, + ), + ); } } 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 c2d0d3f5a..a459d6d22 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 @@ -1,8 +1,6 @@ import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; - import '../../theme.dart'; /// Applies a theme to descendant Syncfusion range slider widgets. @@ -11,7 +9,7 @@ class SfRangeSliderTheme extends InheritedTheme { /// /// The [data] and [child] arguments must not be null. const SfRangeSliderTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for /// descendant range slider widgets. @@ -35,7 +33,6 @@ class SfRangeSliderTheme extends InheritedTheme { @override bool updateShouldNotify(SfRangeSliderTheme oldWidget) => data != oldWidget.data; - @override Widget wrap(BuildContext context, Widget child) { final SfRangeSliderTheme? ancestorTheme = @@ -85,104 +82,189 @@ class SfRangeSliderTheme extends InheritedTheme { /// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), /// for customizing the visual appearance of the range slider. class SfRangeSliderThemeData extends SfSliderThemeData { - /// Returns a new instance of [SfRangeSliderThemeData.raw] - /// for the given values. - /// - /// 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? inactiveDividerColor, - Color? activeDividerColor, - Color? disabledActiveTrackColor, - Color? disabledInactiveTrackColor, - Color? disabledActiveDividerColor, - Color? disabledInactiveDividerColor, - Color? disabledThumbColor, - Color? tooltipBackgroundColor, - Color? overlappingTooltipStrokeColor, - Color? thumbStrokeColor, - Color? overlappingThumbStrokeColor, - Color? activeDividerStrokeColor, - Color? inactiveDividerStrokeColor, - double? trackCornerRadius, - double? overlayRadius, - double? thumbRadius, - double? activeDividerRadius, - double? inactiveDividerRadius, - double? thumbStrokeWidth, - double? activeDividerStrokeWidth, - double? inactiveDividerStrokeWidth}) { + /// Creating an argument constructor of SfRangeSliderThemeData class. + const SfRangeSliderThemeData({ + double activeTrackHeight = 6.0, + double inactiveTrackHeight = 4.0, + 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? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? tooltipBackgroundColor, + this.overlappingTooltipStrokeColor, + Color? thumbStrokeColor, + this.overlappingThumbStrokeColor, + Color? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + double? trackCornerRadius, + double overlayRadius = 24.0, + double thumbRadius = 10.0, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) : super( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius, + ); + + /// Returns a new instance of [SfRangeSliderThemeData] for the given values. + factory SfRangeSliderThemeData.raw({ + 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? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; overlayRadius ??= 24.0; thumbRadius ??= 10.0; - return SfRangeSliderThemeData.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - overlappingThumbStrokeColor: overlappingThumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); + return SfRangeSliderThemeData( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + overlappingThumbStrokeColor: overlappingThumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius, + ); } /// Create a [SfRangeSliderThemeData] given a set of exact values. @@ -191,92 +273,6 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// This will rarely be used directly. It is used by [lerp] to /// 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? activeDividerStrokeColor, - required Color? inactiveDividerStrokeColor, - 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? inactiveDividerColor, - required Color? activeDividerColor, - required Color? disabledActiveTrackColor, - required Color? disabledInactiveTrackColor, - required Color? disabledActiveDividerColor, - required Color? disabledInactiveDividerColor, - required Color? disabledThumbColor, - required Color? tooltipBackgroundColor, - required this.overlappingTooltipStrokeColor, - required double? trackCornerRadius, - required double overlayRadius, - required double thumbRadius, - required double? activeDividerRadius, - required double? inactiveDividerRadius, - required double? thumbStrokeWidth, - required double? activeDividerStrokeWidth, - required double? inactiveDividerStrokeWidth, - }) : super.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); /// Specifies the stroke color for the thumbs when they overlap in the /// [SfRangeSlider], and [SfRangeSelector]. @@ -391,7 +387,7 @@ class SfRangeSliderThemeData extends SfSliderThemeData { double? inactiveDividerStrokeWidth, }) { return SfRangeSliderThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, activeTrackHeight: activeTrackHeight ?? this.activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight ?? this.inactiveTrackHeight, tickSize: tickSize ?? this.tickSize, @@ -458,90 +454,177 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// /// The arguments must not be null. static SfRangeSliderThemeData? lerp( - SfRangeSliderThemeData? a, SfRangeSliderThemeData? b, double t) { + SfRangeSliderThemeData? a, + SfRangeSliderThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfRangeSliderThemeData( - activeTrackHeight: - lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), - inactiveTrackHeight: - lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), - tickSize: Size.lerp(a.tickSize, b.tickSize, t), - minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), - tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), - labelOffset: Offset.lerp(a.labelOffset, b.labelOffset, t), - inactiveLabelStyle: - TextStyle.lerp(a.inactiveLabelStyle, b.inactiveLabelStyle, t), - activeLabelStyle: - TextStyle.lerp(a.activeLabelStyle, b.activeLabelStyle, t), - tooltipTextStyle: - TextStyle.lerp(a.tooltipTextStyle, b.tooltipTextStyle, t), - inactiveTrackColor: - Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t), - activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), - thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), - thumbStrokeColor: Color.lerp(a.thumbStrokeColor, b.thumbStrokeColor, t), - overlappingThumbStrokeColor: Color.lerp( - a.overlappingThumbStrokeColor, b.overlappingThumbStrokeColor, t), - activeDividerStrokeColor: Color.lerp( - a.activeDividerStrokeColor, b.activeDividerStrokeColor, t), - inactiveDividerStrokeColor: Color.lerp( - a.inactiveDividerStrokeColor, b.inactiveDividerStrokeColor, t), - activeTickColor: Color.lerp(a.activeTickColor, b.activeTickColor, t), - inactiveTickColor: - Color.lerp(a.inactiveTickColor, b.inactiveTickColor, t), - disabledActiveTickColor: - Color.lerp(a.disabledActiveTickColor, b.disabledActiveTickColor, t), - disabledInactiveTickColor: Color.lerp( - a.disabledInactiveTickColor, b.disabledInactiveTickColor, t), - activeMinorTickColor: - Color.lerp(a.activeMinorTickColor, b.activeMinorTickColor, t), - inactiveMinorTickColor: - Color.lerp(a.inactiveMinorTickColor, b.inactiveMinorTickColor, t), - disabledActiveMinorTickColor: Color.lerp( - a.disabledActiveMinorTickColor, b.disabledActiveMinorTickColor, t), - disabledInactiveMinorTickColor: Color.lerp( - a.disabledInactiveMinorTickColor, - b.disabledInactiveMinorTickColor, - t), - overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), - inactiveDividerColor: - Color.lerp(a.inactiveDividerColor, b.inactiveDividerColor, t), - activeDividerColor: - Color.lerp(a.activeDividerColor, b.activeDividerColor, t), - disabledActiveTrackColor: Color.lerp( - a.disabledActiveTrackColor, b.disabledActiveTrackColor, t), - disabledInactiveTrackColor: Color.lerp( - a.disabledInactiveTrackColor, b.disabledInactiveTrackColor, t), - disabledActiveDividerColor: Color.lerp( - a.disabledActiveDividerColor, b.disabledActiveDividerColor, t), - disabledInactiveDividerColor: Color.lerp( - a.disabledInactiveDividerColor, b.disabledInactiveDividerColor, t), - disabledThumbColor: - Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t), - tooltipBackgroundColor: - Color.lerp(a.tooltipBackgroundColor, b.tooltipBackgroundColor, t), - overlappingTooltipStrokeColor: Color.lerp( - // ignore: lines_longer_than_80_chars - a.overlappingTooltipStrokeColor, - b.overlappingTooltipStrokeColor, - t), - // ignore: lines_longer_than_80_chars - trackCornerRadius: - lerpDouble(a.trackCornerRadius, b.trackCornerRadius, t), - overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t), - thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t), - // ignore: lines_longer_than_80_chars - activeDividerRadius: - lerpDouble(a.activeDividerRadius, b.activeDividerRadius, t), + activeTrackHeight: + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t)!, + inactiveTrackHeight: + lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t)!, + tickSize: Size.lerp(a.tickSize, b.tickSize, t), + minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), + tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), + labelOffset: Offset.lerp(a.labelOffset, b.labelOffset, t), + inactiveLabelStyle: TextStyle.lerp( + a.inactiveLabelStyle, + b.inactiveLabelStyle, + t, + ), + activeLabelStyle: TextStyle.lerp( + a.activeLabelStyle, + b.activeLabelStyle, + t, + ), + tooltipTextStyle: TextStyle.lerp( + a.tooltipTextStyle, + b.tooltipTextStyle, + t, + ), + inactiveTrackColor: Color.lerp( + a.inactiveTrackColor, + b.inactiveTrackColor, + t, + ), + activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), + thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), + thumbStrokeColor: Color.lerp(a.thumbStrokeColor, b.thumbStrokeColor, t), + overlappingThumbStrokeColor: Color.lerp( + a.overlappingThumbStrokeColor, + b.overlappingThumbStrokeColor, + t, + ), + activeDividerStrokeColor: Color.lerp( + a.activeDividerStrokeColor, + b.activeDividerStrokeColor, + t, + ), + inactiveDividerStrokeColor: Color.lerp( + a.inactiveDividerStrokeColor, + b.inactiveDividerStrokeColor, + t, + ), + activeTickColor: Color.lerp(a.activeTickColor, b.activeTickColor, t), + inactiveTickColor: Color.lerp( + a.inactiveTickColor, + b.inactiveTickColor, + t, + ), + disabledActiveTickColor: Color.lerp( + a.disabledActiveTickColor, + b.disabledActiveTickColor, + t, + ), + disabledInactiveTickColor: Color.lerp( + a.disabledInactiveTickColor, + b.disabledInactiveTickColor, + t, + ), + activeMinorTickColor: Color.lerp( + a.activeMinorTickColor, + b.activeMinorTickColor, + t, + ), + inactiveMinorTickColor: Color.lerp( + a.inactiveMinorTickColor, + b.inactiveMinorTickColor, + t, + ), + disabledActiveMinorTickColor: Color.lerp( + a.disabledActiveMinorTickColor, + b.disabledActiveMinorTickColor, + t, + ), + disabledInactiveMinorTickColor: Color.lerp( + a.disabledInactiveMinorTickColor, + b.disabledInactiveMinorTickColor, + t, + ), + overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), + inactiveDividerColor: Color.lerp( + a.inactiveDividerColor, + b.inactiveDividerColor, + t, + ), + activeDividerColor: Color.lerp( + a.activeDividerColor, + b.activeDividerColor, + t, + ), + disabledActiveTrackColor: Color.lerp( + a.disabledActiveTrackColor, + b.disabledActiveTrackColor, + t, + ), + disabledInactiveTrackColor: Color.lerp( + a.disabledInactiveTrackColor, + b.disabledInactiveTrackColor, + t, + ), + disabledActiveDividerColor: Color.lerp( + a.disabledActiveDividerColor, + b.disabledActiveDividerColor, + t, + ), + disabledInactiveDividerColor: Color.lerp( + a.disabledInactiveDividerColor, + b.disabledInactiveDividerColor, + t, + ), + disabledThumbColor: Color.lerp( + a.disabledThumbColor, + b.disabledThumbColor, + t, + ), + tooltipBackgroundColor: Color.lerp( + a.tooltipBackgroundColor, + b.tooltipBackgroundColor, + t, + ), + overlappingTooltipStrokeColor: Color.lerp( // ignore: lines_longer_than_80_chars - inactiveDividerRadius: lerpDouble(a.inactiveDividerRadius, b.inactiveDividerRadius, t), - thumbStrokeWidth: lerpDouble(a.thumbStrokeWidth, b.thumbStrokeWidth, t), - // ignore: lines_longer_than_80_chars - activeDividerStrokeWidth: lerpDouble(a.activeDividerStrokeWidth, b.activeDividerStrokeWidth, t), - // ignore: lines_longer_than_80_chars - inactiveDividerStrokeWidth: lerpDouble(a.inactiveDividerStrokeWidth, b.inactiveDividerStrokeWidth, t)); + a.overlappingTooltipStrokeColor, + b.overlappingTooltipStrokeColor, + t, + ), + // ignore: lines_longer_than_80_chars + trackCornerRadius: lerpDouble( + a.trackCornerRadius, + b.trackCornerRadius, + t, + ), + overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t)!, + thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t)!, + // ignore: lines_longer_than_80_chars + activeDividerRadius: lerpDouble( + a.activeDividerRadius, + b.activeDividerRadius, + t, + ), + // ignore: lines_longer_than_80_chars + inactiveDividerRadius: lerpDouble( + a.inactiveDividerRadius, + b.inactiveDividerRadius, + t, + ), + thumbStrokeWidth: lerpDouble(a.thumbStrokeWidth, b.thumbStrokeWidth, t), + // ignore: lines_longer_than_80_chars + activeDividerStrokeWidth: lerpDouble( + a.activeDividerStrokeWidth, + b.activeDividerStrokeWidth, + t, + ), + // ignore: lines_longer_than_80_chars + inactiveDividerStrokeWidth: lerpDouble( + a.inactiveDividerStrokeWidth, + b.inactiveDividerStrokeWidth, + t, + ), + ); } @override @@ -554,7 +637,6 @@ class SfRangeSliderThemeData extends SfSliderThemeData { } return other is SfRangeSliderThemeData && - other.brightness == brightness && other.activeTrackHeight == activeTrackHeight && other.inactiveTrackHeight == inactiveTrackHeight && other.tickSize == tickSize && @@ -602,8 +684,7 @@ class SfRangeSliderThemeData extends SfSliderThemeData { @override int get hashCode { - return hashList([ - brightness, + return Object.hashAll([ activeTrackHeight, inactiveTrackHeight, tickSize, @@ -652,112 +733,300 @@ class SfRangeSliderThemeData extends SfSliderThemeData { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfRangeSliderThemeData defaultData = SfRangeSliderThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(DoubleProperty('activeTrackHeight', activeTrackHeight, - defaultValue: defaultData.activeTrackHeight)); - properties.add(DoubleProperty('inactiveTrackHeight', inactiveTrackHeight, - defaultValue: defaultData.inactiveTrackHeight)); - properties.add(DiagnosticsProperty('tickSize', tickSize, - defaultValue: defaultData.tickSize)); - properties.add(DiagnosticsProperty('minorTickSize', minorTickSize, - defaultValue: defaultData.minorTickSize)); - properties.add(DiagnosticsProperty('tickOffset', tickOffset, - defaultValue: defaultData.tickOffset)); - properties.add(DiagnosticsProperty('labelOffset', labelOffset, - defaultValue: defaultData.labelOffset)); - properties.add(DiagnosticsProperty( - 'inactiveLabelStyle', inactiveLabelStyle, - defaultValue: defaultData.inactiveLabelStyle)); - properties.add(DiagnosticsProperty( - 'activeLabelStyle', activeLabelStyle, - defaultValue: defaultData.activeLabelStyle)); - properties.add(DiagnosticsProperty( - 'tooltipTextStyle', tooltipTextStyle, - defaultValue: defaultData.tooltipTextStyle)); - properties.add(ColorProperty('inactiveTrackColor', inactiveTrackColor, - defaultValue: defaultData.inactiveTrackColor)); - properties.add(ColorProperty('activeTrackColor', activeTrackColor, - defaultValue: defaultData.activeTrackColor)); - properties.add(ColorProperty('thumbColor', thumbColor, - defaultValue: defaultData.thumbColor)); - properties.add(ColorProperty('thumbStrokeColor', thumbStrokeColor, - defaultValue: defaultData.thumbStrokeColor)); - properties.add(ColorProperty( - 'overlappingThumbStrokeColor', overlappingThumbStrokeColor, - defaultValue: defaultData.overlappingThumbStrokeColor)); - properties.add(ColorProperty( - 'activeDividerStrokeColor', activeDividerStrokeColor, - defaultValue: defaultData.activeDividerStrokeColor)); - properties.add(ColorProperty( - 'inactiveDividerStrokeColor', inactiveDividerStrokeColor, - defaultValue: defaultData.inactiveDividerStrokeColor)); - properties.add(ColorProperty('activeTickColor', activeTickColor, - defaultValue: defaultData.activeTickColor)); - properties.add(ColorProperty('inactiveTickColor', inactiveTickColor, - defaultValue: defaultData.inactiveTickColor)); - properties.add(ColorProperty( - 'disabledActiveTickColor', disabledActiveTickColor, - defaultValue: defaultData.disabledActiveTickColor)); - properties.add(ColorProperty( - 'disabledInactiveTickColor', disabledInactiveTickColor, - defaultValue: defaultData.disabledInactiveTickColor)); - properties.add(ColorProperty('activeMinorTickColor', activeMinorTickColor, - defaultValue: defaultData.activeMinorTickColor)); - properties.add(ColorProperty( - 'inactiveMinorTickColor', inactiveMinorTickColor, - defaultValue: defaultData.inactiveMinorTickColor)); - properties.add(ColorProperty( - 'disabledActiveMinorTickColor', disabledActiveMinorTickColor, - defaultValue: defaultData.disabledActiveMinorTickColor)); - properties.add(ColorProperty( - 'disabledInactiveMinorTickColor', disabledInactiveMinorTickColor, - defaultValue: defaultData.disabledInactiveMinorTickColor)); - properties.add(ColorProperty('overlayColor', overlayColor, - defaultValue: defaultData.overlayColor)); - properties.add(ColorProperty('inactiveDividerColor', inactiveDividerColor, - defaultValue: defaultData.inactiveDividerColor)); - properties.add(ColorProperty('activeDividerColor', activeDividerColor, - defaultValue: defaultData.activeDividerColor)); - properties.add(ColorProperty( - 'disabledActiveTrackColor', disabledActiveTrackColor, - defaultValue: defaultData.disabledActiveTrackColor)); - properties.add(ColorProperty( - 'disabledInactiveTrackColor', disabledInactiveTrackColor, - defaultValue: defaultData.disabledInactiveTrackColor)); - properties.add(ColorProperty( - 'disabledActiveDividerColor', disabledActiveDividerColor, - defaultValue: defaultData.disabledActiveDividerColor)); - properties.add(ColorProperty( - 'disabledInactiveDividerColor', disabledInactiveDividerColor, - defaultValue: defaultData.disabledInactiveDividerColor)); - properties.add(ColorProperty('disabledThumbColor', disabledThumbColor, - defaultValue: defaultData.disabledThumbColor)); - properties.add(ColorProperty( - 'tooltipBackgroundColor', tooltipBackgroundColor, - defaultValue: defaultData.tooltipBackgroundColor)); - properties.add(ColorProperty( - 'overlappingTooltipStrokeColor', overlappingTooltipStrokeColor, - defaultValue: defaultData.overlappingTooltipStrokeColor)); - properties.add(DoubleProperty('trackCornerRadius', trackCornerRadius, - defaultValue: defaultData.trackCornerRadius)); - properties.add(DoubleProperty('overlayRadius', overlayRadius, - defaultValue: defaultData.overlayRadius)); - properties.add(DoubleProperty('thumbRadius', thumbRadius, - defaultValue: defaultData.thumbRadius)); - properties.add(DoubleProperty('activeDividerRadius', activeDividerRadius, - defaultValue: defaultData.activeDividerRadius)); - properties.add(DoubleProperty( - 'inactiveDividerRadius', inactiveDividerRadius, - defaultValue: defaultData.inactiveDividerRadius)); - properties.add(DoubleProperty('thumbStrokeWidth', thumbStrokeWidth, - defaultValue: defaultData.thumbStrokeWidth)); - properties.add(DoubleProperty( - 'activeDividerStrokeWidth', activeDividerStrokeWidth, - defaultValue: defaultData.activeDividerStrokeWidth)); - properties.add(DoubleProperty( - 'inactiveDividerStrokeWidth', inactiveDividerStrokeWidth, - defaultValue: defaultData.inactiveDividerStrokeWidth)); + const SfRangeSliderThemeData defaultData = SfRangeSliderThemeData(); + properties.add( + DoubleProperty( + 'activeTrackHeight', + activeTrackHeight, + defaultValue: defaultData.activeTrackHeight, + ), + ); + properties.add( + DoubleProperty( + 'inactiveTrackHeight', + inactiveTrackHeight, + defaultValue: defaultData.inactiveTrackHeight, + ), + ); + properties.add( + DiagnosticsProperty( + 'tickSize', + tickSize, + defaultValue: defaultData.tickSize, + ), + ); + properties.add( + DiagnosticsProperty( + 'minorTickSize', + minorTickSize, + defaultValue: defaultData.minorTickSize, + ), + ); + properties.add( + DiagnosticsProperty( + 'tickOffset', + tickOffset, + defaultValue: defaultData.tickOffset, + ), + ); + properties.add( + DiagnosticsProperty( + 'labelOffset', + labelOffset, + defaultValue: defaultData.labelOffset, + ), + ); + properties.add( + DiagnosticsProperty( + 'inactiveLabelStyle', + inactiveLabelStyle, + defaultValue: defaultData.inactiveLabelStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'activeLabelStyle', + activeLabelStyle, + defaultValue: defaultData.activeLabelStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'tooltipTextStyle', + tooltipTextStyle, + defaultValue: defaultData.tooltipTextStyle, + ), + ); + properties.add( + ColorProperty( + 'inactiveTrackColor', + inactiveTrackColor, + defaultValue: defaultData.inactiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'activeTrackColor', + activeTrackColor, + defaultValue: defaultData.activeTrackColor, + ), + ); + properties.add( + ColorProperty( + 'thumbColor', + thumbColor, + defaultValue: defaultData.thumbColor, + ), + ); + properties.add( + ColorProperty( + 'thumbStrokeColor', + thumbStrokeColor, + defaultValue: defaultData.thumbStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'overlappingThumbStrokeColor', + overlappingThumbStrokeColor, + defaultValue: defaultData.overlappingThumbStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'activeDividerStrokeColor', + activeDividerStrokeColor, + defaultValue: defaultData.activeDividerStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveDividerStrokeColor', + inactiveDividerStrokeColor, + defaultValue: defaultData.inactiveDividerStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'activeTickColor', + activeTickColor, + defaultValue: defaultData.activeTickColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveTickColor', + inactiveTickColor, + defaultValue: defaultData.inactiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveTickColor', + disabledActiveTickColor, + defaultValue: defaultData.disabledActiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveTickColor', + disabledInactiveTickColor, + defaultValue: defaultData.disabledInactiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'activeMinorTickColor', + activeMinorTickColor, + defaultValue: defaultData.activeMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveMinorTickColor', + inactiveMinorTickColor, + defaultValue: defaultData.inactiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveMinorTickColor', + disabledActiveMinorTickColor, + defaultValue: defaultData.disabledActiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveMinorTickColor', + disabledInactiveMinorTickColor, + defaultValue: defaultData.disabledInactiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'overlayColor', + overlayColor, + defaultValue: defaultData.overlayColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveDividerColor', + inactiveDividerColor, + defaultValue: defaultData.inactiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'activeDividerColor', + activeDividerColor, + defaultValue: defaultData.activeDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveTrackColor', + disabledActiveTrackColor, + defaultValue: defaultData.disabledActiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveTrackColor', + disabledInactiveTrackColor, + defaultValue: defaultData.disabledInactiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveDividerColor', + disabledActiveDividerColor, + defaultValue: defaultData.disabledActiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveDividerColor', + disabledInactiveDividerColor, + defaultValue: defaultData.disabledInactiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledThumbColor', + disabledThumbColor, + defaultValue: defaultData.disabledThumbColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipBackgroundColor', + tooltipBackgroundColor, + defaultValue: defaultData.tooltipBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'overlappingTooltipStrokeColor', + overlappingTooltipStrokeColor, + defaultValue: defaultData.overlappingTooltipStrokeColor, + ), + ); + properties.add( + DoubleProperty( + 'trackCornerRadius', + trackCornerRadius, + defaultValue: defaultData.trackCornerRadius, + ), + ); + properties.add( + DoubleProperty( + 'overlayRadius', + overlayRadius, + defaultValue: defaultData.overlayRadius, + ), + ); + properties.add( + DoubleProperty( + 'thumbRadius', + thumbRadius, + defaultValue: defaultData.thumbRadius, + ), + ); + properties.add( + DoubleProperty( + 'activeDividerRadius', + activeDividerRadius, + defaultValue: defaultData.activeDividerRadius, + ), + ); + properties.add( + DoubleProperty( + 'inactiveDividerRadius', + inactiveDividerRadius, + defaultValue: defaultData.inactiveDividerRadius, + ), + ); + properties.add( + DoubleProperty( + 'thumbStrokeWidth', + thumbStrokeWidth, + defaultValue: defaultData.thumbStrokeWidth, + ), + ); + properties.add( + DoubleProperty( + 'activeDividerStrokeWidth', + activeDividerStrokeWidth, + defaultValue: defaultData.activeDividerStrokeWidth, + ), + ); + properties.add( + DoubleProperty( + 'inactiveDividerStrokeWidth', + inactiveDividerStrokeWidth, + defaultValue: defaultData.inactiveDividerStrokeWidth, + ), + ); } } 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 6dba86c5a..572194ef8 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart @@ -11,7 +11,7 @@ class SfSliderTheme extends InheritedTheme { /// /// The [data] and [child] arguments must not be null. const SfSliderTheme({Key? key, required this.data, required this.child}) - : super(key: key, child: child); + : super(key: key, child: child); /// Specifies the color and typography values for descendant slider widgets. final SfSliderThemeData data; @@ -81,151 +81,144 @@ class SfSliderTheme extends InheritedTheme { /// for customizing the visual appearance of the slider. @immutable class SfSliderThemeData with Diagnosticable { - /// Returns a new instance of [SfSliderThemeData.raw] for the given values. - /// - /// 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? inactiveDividerColor, - Color? activeDividerColor, - Color? disabledActiveTrackColor, - Color? disabledInactiveTrackColor, - Color? disabledActiveDividerColor, - Color? disabledInactiveDividerColor, - Color? disabledThumbColor, - Color? tooltipBackgroundColor, - Color? thumbStrokeColor, - Color? activeDividerStrokeColor, - Color? inactiveDividerStrokeColor, - double? trackCornerRadius, - double? overlayRadius, - double? thumbRadius, - double? activeDividerRadius, - double? inactiveDividerRadius, - double? thumbStrokeWidth, - double? activeDividerStrokeWidth, - double? inactiveDividerStrokeWidth}) { + /// Creating an argument constructor of SfSliderThemeData class. + const SfSliderThemeData({ + this.activeTrackHeight = 6.0, + this.inactiveTrackHeight = 4.0, + this.tickSize, + this.minorTickSize, + this.tickOffset, + this.labelOffset, + this.inactiveLabelStyle, + this.activeLabelStyle, + this.tooltipTextStyle, + this.inactiveTrackColor, + this.activeTrackColor, + this.thumbColor, + this.thumbStrokeColor, + this.activeDividerStrokeColor, + this.inactiveDividerStrokeColor, + this.activeTickColor, + this.inactiveTickColor, + this.disabledActiveTickColor, + this.disabledInactiveTickColor, + this.activeMinorTickColor, + this.inactiveMinorTickColor, + this.disabledActiveMinorTickColor, + this.disabledInactiveMinorTickColor, + this.overlayColor, + this.inactiveDividerColor, + this.activeDividerColor, + this.disabledActiveTrackColor, + this.disabledInactiveTrackColor, + this.disabledActiveDividerColor, + this.disabledInactiveDividerColor, + this.disabledThumbColor, + this.tooltipBackgroundColor, + this.trackCornerRadius, + this.overlayRadius = 24.0, + this.thumbRadius = 10.0, + this.activeDividerRadius, + this.inactiveDividerRadius, + this.thumbStrokeWidth, + this.activeDividerStrokeWidth, + this.inactiveDividerStrokeWidth, + }); + + /// Returns a new instance of [SfSliderThemeData] for the given values. + factory SfSliderThemeData.raw({ + 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? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? tooltipBackgroundColor, + Color? thumbStrokeColor, + Color? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; overlayRadius ??= 24.0; thumbRadius ??= 10.0; - return SfSliderThemeData.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); + return SfSliderThemeData( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius, + ); } - /// Create a [SfSliderThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// 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.activeDividerStrokeColor, - required this.inactiveDividerStrokeColor, - 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.inactiveDividerColor, - required this.activeDividerColor, - required this.disabledActiveTrackColor, - required this.disabledInactiveTrackColor, - required this.disabledActiveDividerColor, - required this.disabledInactiveDividerColor, - required this.disabledThumbColor, - required this.tooltipBackgroundColor, - required this.trackCornerRadius, - required this.overlayRadius, - required this.thumbRadius, - required this.activeDividerRadius, - required this.inactiveDividerRadius, - required this.thumbStrokeWidth, - required this.activeDividerStrokeWidth, - required this.inactiveDividerStrokeWidth, - }); - /// Creates a copy of this theme but with the given fields /// replaced with the new values. SfSliderThemeData copyWith({ @@ -274,7 +267,7 @@ class SfSliderThemeData with Diagnosticable { double? inactiveDividerStrokeWidth, }) { return SfSliderThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, activeTrackHeight: activeTrackHeight ?? this.activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight ?? this.inactiveTrackHeight, tickSize: tickSize ?? this.tickSize, @@ -337,82 +330,160 @@ class SfSliderThemeData with Diagnosticable { /// /// The arguments must not be null. static SfSliderThemeData? lerp( - SfSliderThemeData? a, SfSliderThemeData? b, double t) { + SfSliderThemeData? a, + SfSliderThemeData? b, + double t, + ) { if (a == null && b == null) { return null; } return SfSliderThemeData( - activeTrackHeight: - lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), - inactiveTrackHeight: - lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), - tickSize: Size.lerp(a.tickSize, b.tickSize, t), - minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), - tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), - labelOffset: Offset.lerp(a.labelOffset, b.labelOffset, t), - inactiveLabelStyle: - TextStyle.lerp(a.inactiveLabelStyle, b.inactiveLabelStyle, t), - activeLabelStyle: - TextStyle.lerp(a.activeLabelStyle, b.activeLabelStyle, t), - tooltipTextStyle: - TextStyle.lerp(a.tooltipTextStyle, b.tooltipTextStyle, t), - inactiveTrackColor: - Color.lerp(a.inactiveTrackColor, b.inactiveTrackColor, t), - activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), - thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), - thumbStrokeColor: Color.lerp(a.thumbStrokeColor, b.thumbStrokeColor, t), - activeDividerStrokeColor: Color.lerp( - a.activeDividerStrokeColor, b.activeDividerStrokeColor, t), - inactiveDividerStrokeColor: Color.lerp( - a.inactiveDividerStrokeColor, b.inactiveDividerStrokeColor, t), - activeTickColor: Color.lerp(a.activeTickColor, b.activeTickColor, t), - inactiveTickColor: - Color.lerp(a.inactiveTickColor, b.inactiveTickColor, t), - disabledActiveTickColor: - Color.lerp(a.disabledActiveTickColor, b.disabledActiveTickColor, t), - disabledInactiveTickColor: Color.lerp( - a.disabledInactiveTickColor, b.disabledInactiveTickColor, t), - activeMinorTickColor: - Color.lerp(a.activeMinorTickColor, b.activeMinorTickColor, t), - inactiveMinorTickColor: - Color.lerp(a.inactiveMinorTickColor, b.inactiveMinorTickColor, t), - disabledActiveMinorTickColor: Color.lerp( - a.disabledActiveMinorTickColor, b.disabledActiveMinorTickColor, t), - disabledInactiveMinorTickColor: Color.lerp( - a.disabledInactiveMinorTickColor, - b.disabledInactiveMinorTickColor, - t), - overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), - inactiveDividerColor: - Color.lerp(a.inactiveDividerColor, b.inactiveDividerColor, t), - activeDividerColor: - Color.lerp(a.activeDividerColor, b.activeDividerColor, t), - disabledActiveTrackColor: Color.lerp( - a.disabledActiveTrackColor, b.disabledActiveTrackColor, t), - disabledInactiveTrackColor: Color.lerp( - a.disabledInactiveTrackColor, b.disabledInactiveTrackColor, t), - disabledActiveDividerColor: Color.lerp( - a.disabledActiveDividerColor, b.disabledActiveDividerColor, t), - disabledInactiveDividerColor: Color.lerp( - a.disabledInactiveDividerColor, b.disabledInactiveDividerColor, t), - disabledThumbColor: - Color.lerp(a.disabledThumbColor, b.disabledThumbColor, t), - tooltipBackgroundColor: - Color.lerp(a.tooltipBackgroundColor, b.tooltipBackgroundColor, t), - trackCornerRadius: - lerpDouble(a.trackCornerRadius, b.trackCornerRadius, t), - overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t), - thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t), - activeDividerRadius: - lerpDouble(a.activeDividerRadius, b.activeDividerRadius, t), - inactiveDividerRadius: - lerpDouble(a.inactiveDividerRadius, b.inactiveDividerRadius, t), - thumbStrokeWidth: lerpDouble(a.thumbStrokeWidth, b.thumbStrokeWidth, t), - activeDividerStrokeWidth: - // ignore: lines_longer_than_80_chars - lerpDouble(a.activeDividerStrokeWidth, b.activeDividerStrokeWidth, t), - // ignore: lines_longer_than_80_chars - inactiveDividerStrokeWidth: lerpDouble(a.inactiveDividerStrokeWidth, b.inactiveDividerStrokeWidth, t)); + activeTrackHeight: + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t)!, + inactiveTrackHeight: + lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t)!, + tickSize: Size.lerp(a.tickSize, b.tickSize, t), + minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), + tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), + labelOffset: Offset.lerp(a.labelOffset, b.labelOffset, t), + inactiveLabelStyle: TextStyle.lerp( + a.inactiveLabelStyle, + b.inactiveLabelStyle, + t, + ), + activeLabelStyle: TextStyle.lerp( + a.activeLabelStyle, + b.activeLabelStyle, + t, + ), + tooltipTextStyle: TextStyle.lerp( + a.tooltipTextStyle, + b.tooltipTextStyle, + t, + ), + inactiveTrackColor: Color.lerp( + a.inactiveTrackColor, + b.inactiveTrackColor, + t, + ), + activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t), + thumbColor: Color.lerp(a.thumbColor, b.thumbColor, t), + thumbStrokeColor: Color.lerp(a.thumbStrokeColor, b.thumbStrokeColor, t), + activeDividerStrokeColor: Color.lerp( + a.activeDividerStrokeColor, + b.activeDividerStrokeColor, + t, + ), + inactiveDividerStrokeColor: Color.lerp( + a.inactiveDividerStrokeColor, + b.inactiveDividerStrokeColor, + t, + ), + activeTickColor: Color.lerp(a.activeTickColor, b.activeTickColor, t), + inactiveTickColor: Color.lerp( + a.inactiveTickColor, + b.inactiveTickColor, + t, + ), + disabledActiveTickColor: Color.lerp( + a.disabledActiveTickColor, + b.disabledActiveTickColor, + t, + ), + disabledInactiveTickColor: Color.lerp( + a.disabledInactiveTickColor, + b.disabledInactiveTickColor, + t, + ), + activeMinorTickColor: Color.lerp( + a.activeMinorTickColor, + b.activeMinorTickColor, + t, + ), + inactiveMinorTickColor: Color.lerp( + a.inactiveMinorTickColor, + b.inactiveMinorTickColor, + t, + ), + disabledActiveMinorTickColor: Color.lerp( + a.disabledActiveMinorTickColor, + b.disabledActiveMinorTickColor, + t, + ), + disabledInactiveMinorTickColor: Color.lerp( + a.disabledInactiveMinorTickColor, + b.disabledInactiveMinorTickColor, + t, + ), + overlayColor: Color.lerp(a.overlayColor, b.overlayColor, t), + inactiveDividerColor: Color.lerp( + a.inactiveDividerColor, + b.inactiveDividerColor, + t, + ), + activeDividerColor: Color.lerp( + a.activeDividerColor, + b.activeDividerColor, + t, + ), + disabledActiveTrackColor: Color.lerp( + a.disabledActiveTrackColor, + b.disabledActiveTrackColor, + t, + ), + disabledInactiveTrackColor: Color.lerp( + a.disabledInactiveTrackColor, + b.disabledInactiveTrackColor, + t, + ), + disabledActiveDividerColor: Color.lerp( + a.disabledActiveDividerColor, + b.disabledActiveDividerColor, + t, + ), + disabledInactiveDividerColor: Color.lerp( + a.disabledInactiveDividerColor, + b.disabledInactiveDividerColor, + t, + ), + disabledThumbColor: Color.lerp( + a.disabledThumbColor, + b.disabledThumbColor, + t, + ), + tooltipBackgroundColor: Color.lerp( + a.tooltipBackgroundColor, + b.tooltipBackgroundColor, + t, + ), + trackCornerRadius: lerpDouble( + a.trackCornerRadius, + b.trackCornerRadius, + t, + ), + overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t)!, + thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t)!, + activeDividerRadius: lerpDouble( + a.activeDividerRadius, + b.activeDividerRadius, + t, + ), + inactiveDividerRadius: lerpDouble( + a.inactiveDividerRadius, + b.inactiveDividerRadius, + t, + ), + thumbStrokeWidth: lerpDouble(a.thumbStrokeWidth, b.thumbStrokeWidth, t), + activeDividerStrokeWidth: + // ignore: lines_longer_than_80_chars + lerpDouble(a.activeDividerStrokeWidth, b.activeDividerStrokeWidth, t), + // ignore: lines_longer_than_80_chars + inactiveDividerStrokeWidth: lerpDouble( + a.inactiveDividerStrokeWidth, + b.inactiveDividerStrokeWidth, + t, + ), + ); } @override @@ -425,7 +496,6 @@ class SfSliderThemeData with Diagnosticable { } return other is SfSliderThemeData && - other.brightness == brightness && other.activeTrackHeight == activeTrackHeight && other.inactiveTrackHeight == inactiveTrackHeight && other.tickSize == tickSize && @@ -471,8 +541,7 @@ class SfSliderThemeData with Diagnosticable { @override int get hashCode { - return hashList([ - brightness, + return Object.hashAll([ activeTrackHeight, inactiveTrackHeight, tickSize, @@ -519,151 +588,289 @@ class SfSliderThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfSliderThemeData defaultData = SfSliderThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(DoubleProperty('activeTrackHeight', activeTrackHeight, - defaultValue: defaultData.activeTrackHeight)); - properties.add(DoubleProperty('inactiveTrackHeight', inactiveTrackHeight, - defaultValue: defaultData.inactiveTrackHeight)); - properties.add(DiagnosticsProperty('tickSize', tickSize, - defaultValue: defaultData.tickSize)); - properties.add(DiagnosticsProperty('minorTickSize', minorTickSize, - defaultValue: defaultData.minorTickSize)); - properties.add(DiagnosticsProperty('tickOffset', tickOffset, - defaultValue: defaultData.tickOffset)); - properties.add(DiagnosticsProperty('labelOffset', labelOffset, - defaultValue: defaultData.labelOffset)); - properties.add(DiagnosticsProperty( - 'inactiveLabelStyle', inactiveLabelStyle, - defaultValue: defaultData.inactiveLabelStyle)); - properties.add(DiagnosticsProperty( - 'activeLabelStyle', activeLabelStyle, - defaultValue: defaultData.activeLabelStyle)); - properties.add(DiagnosticsProperty( - 'tooltipTextStyle', tooltipTextStyle, - defaultValue: defaultData.tooltipTextStyle)); - properties.add(ColorProperty('inactiveTrackColor', inactiveTrackColor, - defaultValue: defaultData.inactiveTrackColor)); - properties.add(ColorProperty('activeTrackColor', activeTrackColor, - defaultValue: defaultData.activeTrackColor)); - properties.add(ColorProperty('thumbColor', thumbColor, - defaultValue: defaultData.thumbColor)); - properties.add(ColorProperty('thumbStrokeColor', thumbStrokeColor, - defaultValue: defaultData.thumbStrokeColor)); - properties.add(ColorProperty( - 'activeDividerStrokeColor', activeDividerStrokeColor, - defaultValue: defaultData.activeDividerStrokeColor)); - properties.add(ColorProperty( - 'inactiveDividerStrokeColor', inactiveDividerStrokeColor, - defaultValue: defaultData.inactiveDividerStrokeColor)); - properties.add(ColorProperty('activeTickColor', activeTickColor, - defaultValue: defaultData.activeTickColor)); - properties.add(ColorProperty('inactiveTickColor', inactiveTickColor, - defaultValue: defaultData.inactiveTickColor)); - properties.add(ColorProperty( - 'disabledActiveTickColor', disabledActiveTickColor, - defaultValue: defaultData.disabledActiveTickColor)); - properties.add(ColorProperty( - 'disabledInactiveTickColor', disabledInactiveTickColor, - defaultValue: defaultData.disabledInactiveTickColor)); - properties.add(ColorProperty('activeMinorTickColor', activeMinorTickColor, - defaultValue: defaultData.activeMinorTickColor)); - properties.add(ColorProperty( - 'inactiveMinorTickColor', inactiveMinorTickColor, - defaultValue: defaultData.inactiveMinorTickColor)); - properties.add(ColorProperty( - 'disabledActiveMinorTickColor', disabledActiveMinorTickColor, - defaultValue: defaultData.disabledActiveMinorTickColor)); - properties.add(ColorProperty( - 'disabledInactiveMinorTickColor', disabledInactiveMinorTickColor, - defaultValue: defaultData.disabledInactiveMinorTickColor)); - properties.add(ColorProperty('overlayColor', overlayColor, - defaultValue: defaultData.overlayColor)); - properties.add(ColorProperty('inactiveDividerColor', inactiveDividerColor, - defaultValue: defaultData.inactiveDividerColor)); - properties.add(ColorProperty('activeDividerColor', activeDividerColor, - defaultValue: defaultData.activeDividerColor)); - properties.add(ColorProperty( - 'disabledActiveTrackColor', disabledActiveTrackColor, - defaultValue: defaultData.disabledActiveTrackColor)); - properties.add(ColorProperty( - 'disabledInactiveTrackColor', disabledInactiveTrackColor, - defaultValue: defaultData.disabledInactiveTrackColor)); - properties.add(ColorProperty( - 'disabledActiveDividerColor', disabledActiveDividerColor, - defaultValue: defaultData.disabledActiveDividerColor)); - properties.add(ColorProperty( - 'disabledInactiveDividerColor', disabledInactiveDividerColor, - defaultValue: defaultData.disabledInactiveDividerColor)); - properties.add(ColorProperty('disabledThumbColor', disabledThumbColor, - defaultValue: defaultData.disabledThumbColor)); - properties.add(ColorProperty( - 'tooltipBackgroundColor', tooltipBackgroundColor, - defaultValue: defaultData.tooltipBackgroundColor)); - properties.add(DoubleProperty('trackCornerRadius', trackCornerRadius, - defaultValue: defaultData.trackCornerRadius)); - properties.add(DoubleProperty('overlayRadius', overlayRadius, - defaultValue: defaultData.overlayRadius)); - properties.add(DoubleProperty('thumbRadius', thumbRadius, - defaultValue: defaultData.thumbRadius)); - properties.add(DoubleProperty('activeDividerRadius', activeDividerRadius, - defaultValue: defaultData.activeDividerRadius)); - properties.add(DoubleProperty( - 'inactiveDividerRadius', inactiveDividerRadius, - defaultValue: defaultData.inactiveDividerRadius)); - properties.add(DoubleProperty('thumbStrokeWidth', thumbStrokeWidth, - defaultValue: defaultData.thumbStrokeWidth)); - properties.add(DoubleProperty( - 'activeDividerStrokeWidth', activeDividerStrokeWidth, - defaultValue: defaultData.activeDividerStrokeWidth)); - properties.add(DoubleProperty( - 'inactiveDividerStrokeWidth', inactiveDividerStrokeWidth, - defaultValue: defaultData.inactiveDividerStrokeWidth)); + const SfSliderThemeData defaultData = SfSliderThemeData(); + properties.add( + DoubleProperty( + 'activeTrackHeight', + activeTrackHeight, + defaultValue: defaultData.activeTrackHeight, + ), + ); + properties.add( + DoubleProperty( + 'inactiveTrackHeight', + inactiveTrackHeight, + defaultValue: defaultData.inactiveTrackHeight, + ), + ); + properties.add( + DiagnosticsProperty( + 'tickSize', + tickSize, + defaultValue: defaultData.tickSize, + ), + ); + properties.add( + DiagnosticsProperty( + 'minorTickSize', + minorTickSize, + defaultValue: defaultData.minorTickSize, + ), + ); + properties.add( + DiagnosticsProperty( + 'tickOffset', + tickOffset, + defaultValue: defaultData.tickOffset, + ), + ); + properties.add( + DiagnosticsProperty( + 'labelOffset', + labelOffset, + defaultValue: defaultData.labelOffset, + ), + ); + properties.add( + DiagnosticsProperty( + 'inactiveLabelStyle', + inactiveLabelStyle, + defaultValue: defaultData.inactiveLabelStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'activeLabelStyle', + activeLabelStyle, + defaultValue: defaultData.activeLabelStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'tooltipTextStyle', + tooltipTextStyle, + defaultValue: defaultData.tooltipTextStyle, + ), + ); + properties.add( + ColorProperty( + 'inactiveTrackColor', + inactiveTrackColor, + defaultValue: defaultData.inactiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'activeTrackColor', + activeTrackColor, + defaultValue: defaultData.activeTrackColor, + ), + ); + properties.add( + ColorProperty( + 'thumbColor', + thumbColor, + defaultValue: defaultData.thumbColor, + ), + ); + properties.add( + ColorProperty( + 'thumbStrokeColor', + thumbStrokeColor, + defaultValue: defaultData.thumbStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'activeDividerStrokeColor', + activeDividerStrokeColor, + defaultValue: defaultData.activeDividerStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveDividerStrokeColor', + inactiveDividerStrokeColor, + defaultValue: defaultData.inactiveDividerStrokeColor, + ), + ); + properties.add( + ColorProperty( + 'activeTickColor', + activeTickColor, + defaultValue: defaultData.activeTickColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveTickColor', + inactiveTickColor, + defaultValue: defaultData.inactiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveTickColor', + disabledActiveTickColor, + defaultValue: defaultData.disabledActiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveTickColor', + disabledInactiveTickColor, + defaultValue: defaultData.disabledInactiveTickColor, + ), + ); + properties.add( + ColorProperty( + 'activeMinorTickColor', + activeMinorTickColor, + defaultValue: defaultData.activeMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveMinorTickColor', + inactiveMinorTickColor, + defaultValue: defaultData.inactiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveMinorTickColor', + disabledActiveMinorTickColor, + defaultValue: defaultData.disabledActiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveMinorTickColor', + disabledInactiveMinorTickColor, + defaultValue: defaultData.disabledInactiveMinorTickColor, + ), + ); + properties.add( + ColorProperty( + 'overlayColor', + overlayColor, + defaultValue: defaultData.overlayColor, + ), + ); + properties.add( + ColorProperty( + 'inactiveDividerColor', + inactiveDividerColor, + defaultValue: defaultData.inactiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'activeDividerColor', + activeDividerColor, + defaultValue: defaultData.activeDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveTrackColor', + disabledActiveTrackColor, + defaultValue: defaultData.disabledActiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveTrackColor', + disabledInactiveTrackColor, + defaultValue: defaultData.disabledInactiveTrackColor, + ), + ); + properties.add( + ColorProperty( + 'disabledActiveDividerColor', + disabledActiveDividerColor, + defaultValue: defaultData.disabledActiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledInactiveDividerColor', + disabledInactiveDividerColor, + defaultValue: defaultData.disabledInactiveDividerColor, + ), + ); + properties.add( + ColorProperty( + 'disabledThumbColor', + disabledThumbColor, + defaultValue: defaultData.disabledThumbColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipBackgroundColor', + tooltipBackgroundColor, + defaultValue: defaultData.tooltipBackgroundColor, + ), + ); + properties.add( + DoubleProperty( + 'trackCornerRadius', + trackCornerRadius, + defaultValue: defaultData.trackCornerRadius, + ), + ); + properties.add( + DoubleProperty( + 'overlayRadius', + overlayRadius, + defaultValue: defaultData.overlayRadius, + ), + ); + properties.add( + DoubleProperty( + 'thumbRadius', + thumbRadius, + defaultValue: defaultData.thumbRadius, + ), + ); + properties.add( + DoubleProperty( + 'activeDividerRadius', + activeDividerRadius, + defaultValue: defaultData.activeDividerRadius, + ), + ); + properties.add( + DoubleProperty( + 'inactiveDividerRadius', + inactiveDividerRadius, + defaultValue: defaultData.inactiveDividerRadius, + ), + ); + properties.add( + DoubleProperty( + 'thumbStrokeWidth', + thumbStrokeWidth, + defaultValue: defaultData.thumbStrokeWidth, + ), + ); + properties.add( + DoubleProperty( + 'activeDividerStrokeWidth', + activeDividerStrokeWidth, + defaultValue: defaultData.activeDividerStrokeWidth, + ), + ); + properties.add( + DoubleProperty( + 'inactiveDividerStrokeWidth', + inactiveDividerStrokeWidth, + defaultValue: defaultData.inactiveDividerStrokeWidth, + ), + ); } - /// Specifies the light and dark theme for the [SfSlider], - /// [SfRangeSlider], and [SfRangeSelector]. - /// - /// This theme applies to all the [SfSlider] which are - /// added as children of [SfSliderTheme]. - /// - /// This theme applies to all the [SfRangeSlider] which are - /// added as children of [SfRangeSliderTheme]. - /// - /// This theme applies to all the [SfRangeSelector] which are - /// added as children of [SfRangeSelectorTheme]. - /// - /// This snippet shows how to set brightness in [SfRangeSliderThemeData]. - /// - /// ```dart - /// SfRangeValues _values = SfRangeValues(4.0, 7.0); - /// - /// Scaffold( - /// body: Center( - /// child: SfRangeSliderTheme( - /// data: SfRangeSliderThemeData( - /// brightness: Brightness.dark, - /// ), - /// child: SfRangeSlider( - /// min: 2.0, - /// max: 10.0, - /// values: _values, - /// interval: 2, - /// showTicks: true, - /// showLabels: true, - /// onChanged: (SfRangeValues newValues){ - /// setState(() { - /// _values = newValues; - /// }); - /// }, - /// ) - /// ), - /// ) - /// ) - /// ``` - final Brightness brightness; - /// Specifies the height for the active track in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. /// diff --git a/packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart new file mode 100644 index 000000000..0a0d31eb3 --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/theme/spark_charts_theme.dart @@ -0,0 +1,563 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../../theme.dart'; + +/// Applies a theme to descendant Syncfusion spark chart widgets. +/// +/// To obtain the current theme, use [SfSparkChartTheme.of]. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfSparkChartTheme( +/// data: SfSparkChartThemeData( +/// brightness: Brightness.light +/// ), +/// child: SfSparkLineChart(), +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SfTheme](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfTheme-class.html) +/// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), +/// for customizing the visual appearance of the spark chart widgets. +/// +class SfSparkChartTheme extends InheritedTheme { + /// Initialize the class of SfSparkChartTheme + const SfSparkChartTheme({Key? key, required this.data, required this.child}) + : super(key: key, child: child); + + /// Specifies the color and typography values for descendant + /// spark chart widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfSparkChartTheme( + /// data: SfSparkChartThemeData( + /// brightness: Brightness.light + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ); + /// } + /// ``` + + final SfSparkChartThemeData data; + + /// Specifies a widget that can hold single child. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfSparkChartTheme( + /// data: SfSparkChartThemeData( + /// brightness: Brightness.light + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ); + /// } + /// ``` + @override + final Widget child; + + /// The data from the closest [SfSparkChartTheme] instance + /// that encloses the given context. + /// + /// Defaults to [SfSparkChartTheme.SparkChartThemeData] + /// if there is no [SfSparkChartTheme] in the given build context. + static SfSparkChartThemeData of(BuildContext context) { + final SfSparkChartTheme? sfSparkChartTheme = + context.dependOnInheritedWidgetOfExactType(); + return sfSparkChartTheme?.data ?? SfTheme.of(context).sparkChartThemeData; + } + + @override + bool updateShouldNotify(SfSparkChartTheme oldWidget) => + data != oldWidget.data; + + @override + Widget wrap(BuildContext context, Widget child) { + final SfSparkChartTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : SfSparkChartTheme(data: data, child: child); + } +} + +/// Holds the color and typography values for a [SfSparkChartTheme]. +/// Applies a theme to descendant Syncfusion spark charts widgets. +/// +/// To obtain the current theme, use [SfSparkChartTheme.of]. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfSparkChartTheme( +/// data: SfSparkChartThemeData( +/// brightness: Brightness.light +/// ), +/// child: SfSparkLineChart(), +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SfTheme](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfTheme-class.html) +/// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), +/// for customizing the visual appearance of the spark chart widgets. +/// +@immutable +class SfSparkChartThemeData with Diagnosticable { + /// Initialize the Sfspark chart theme data + const SfSparkChartThemeData({ + this.backgroundColor, + this.color, + this.axisLineColor, + this.markerFillColor, + this.dataLabelBackgroundColor, + this.tooltipColor, + this.trackballLineColor, + this.tooltipLabelColor, + this.dataLabelTextStyle, + this.trackballTextStyle, + }); + + /// Create a [SfSparkChartThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfSparkChartThemeData] constructor. + factory SfSparkChartThemeData.raw({ + Brightness? brightness, + Color? backgroundColor, + Color? color, + Color? axisLineColor, + Color? markerFillColor, + Color? dataLabelBackgroundColor, + Color? tooltipColor, + Color? trackballLineColor, + Color? tooltipLabelColor, + TextStyle? dataLabelTextStyle, + TextStyle? trackballTextStyle, + }) { + brightness = brightness ?? Brightness.light; + + return SfSparkChartThemeData( + backgroundColor: backgroundColor, + color: color, + axisLineColor: axisLineColor, + markerFillColor: markerFillColor, + dataLabelBackgroundColor: dataLabelBackgroundColor, + tooltipColor: tooltipColor, + trackballLineColor: trackballLineColor, + tooltipLabelColor: tooltipLabelColor, + dataLabelTextStyle: dataLabelTextStyle, + trackballTextStyle: trackballTextStyle, + ); + } + + /// Specifies the background color of spark chart widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// SparkChartThemeData: SfSparkChartThemeData( + /// backgroundColor: Colors.yellow + /// ), + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + ///} + /// ``` + final Color? backgroundColor; + + /// Specifies the color for axis line. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// axisLineColor: Colors.blue + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? axisLineColor; + + /// Specifies the color of the spark chart. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// color: Colors.blue + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? color; + + /// Specifies the color for chart marker. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// markerFillColor: Colors.red + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? markerFillColor; + + /// Specifies the background color for data labels. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// dataLabelBackgroundColor: Colors.red + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? dataLabelBackgroundColor; + + /// Specifies the color of the tooltip. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// tooltipColor: Colors.teal + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? tooltipColor; + + /// Specifies the tooltip line color. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// trackballLineColor: Colors.red + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? trackballLineColor; + + /// Specifies the text color of the tooltip. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkChartThemeData: SfSparkChartThemeData( + /// tooltipLabelColor: Colors.amber + /// ) + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + ///``` + + final Color? tooltipLabelColor; + + /// Specifies the data label text style for spark chart label text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// SparkChartThemeData: SfSparkChartThemeData( + /// dataLabelTextStyle: TextStyle(color: Colors.blue) + /// ), + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? dataLabelTextStyle; + + /// Specifies the trackball text style for spark chart label text. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// SparkChartThemeData: SfSparkChartThemeData( + /// trackballTextStyle: TextStyle(color: Colors.blue) + /// ), + /// ), + /// child: SfSparkLineChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + final TextStyle? trackballTextStyle; + + /// Creates a copy of this spark chart theme data object with the + /// matching fields + /// replaced with the non-null parameter values. + SfSparkChartThemeData copyWith({ + Brightness? brightness, + Color? backgroundColor, + Color? color, + Color? axisLineColor, + Color? markerFillColor, + Color? dataLabelBackgroundColor, + Color? tooltipColor, + Color? trackballLineColor, + Color? tooltipLabelColor, + TextStyle? dataLabelTextStyle, + TextStyle? trackballTextStyle, + }) { + return SfSparkChartThemeData.raw( + brightness: brightness, + backgroundColor: backgroundColor ?? this.backgroundColor, + color: color ?? this.color, + axisLineColor: axisLineColor ?? this.axisLineColor, + markerFillColor: markerFillColor ?? this.markerFillColor, + dataLabelBackgroundColor: + dataLabelBackgroundColor ?? this.dataLabelBackgroundColor, + tooltipColor: tooltipColor ?? this.tooltipColor, + trackballLineColor: trackballLineColor ?? this.trackballLineColor, + tooltipLabelColor: tooltipLabelColor ?? this.tooltipLabelColor, + dataLabelTextStyle: dataLabelTextStyle ?? this.dataLabelTextStyle, + trackballTextStyle: trackballTextStyle ?? this.trackballTextStyle, + ); + } + + /// Returns the spark chart theme data + static SfSparkChartThemeData? lerp( + SfSparkChartThemeData? a, + SfSparkChartThemeData? b, + double t, + ) { + if (a == null && b == null) { + return null; + } + return SfSparkChartThemeData( + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), + color: Color.lerp(a.color, b.color, t), + axisLineColor: Color.lerp(a.axisLineColor, b.axisLineColor, t), + markerFillColor: Color.lerp(a.markerFillColor, b.markerFillColor, t), + dataLabelBackgroundColor: Color.lerp( + a.dataLabelBackgroundColor, + b.dataLabelBackgroundColor, + t, + ), + tooltipColor: Color.lerp(a.tooltipColor, b.tooltipColor, t), + trackballLineColor: Color.lerp( + a.trackballLineColor, + b.trackballLineColor, + t, + ), + tooltipLabelColor: Color.lerp( + a.tooltipLabelColor, + b.tooltipLabelColor, + t, + ), + dataLabelTextStyle: TextStyle.lerp( + a.dataLabelTextStyle, + b.dataLabelTextStyle, + t, + ), + trackballTextStyle: TextStyle.lerp( + a.trackballTextStyle, + b.trackballTextStyle, + t, + ), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is SfSparkChartThemeData && + other.backgroundColor == backgroundColor && + other.color == color && + other.axisLineColor == axisLineColor && + other.markerFillColor == markerFillColor && + other.dataLabelBackgroundColor == dataLabelBackgroundColor && + other.tooltipColor == tooltipColor && + other.trackballLineColor == trackballLineColor && + other.tooltipLabelColor == tooltipLabelColor && + other.dataLabelTextStyle == dataLabelTextStyle && + other.trackballTextStyle == trackballTextStyle; + } + + @override + int get hashCode { + final List values = [ + backgroundColor, + color, + axisLineColor, + markerFillColor, + dataLabelBackgroundColor, + tooltipColor, + trackballLineColor, + tooltipLabelColor, + dataLabelTextStyle, + trackballTextStyle, + ]; + return Object.hashAll(values); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + const SfSparkChartThemeData defaultData = SfSparkChartThemeData(); + properties.add( + ColorProperty( + 'backgroundColor', + backgroundColor, + defaultValue: defaultData.backgroundColor, + ), + ); + properties.add( + ColorProperty('color', color, defaultValue: defaultData.color), + ); + properties.add( + ColorProperty( + 'axisLineColor', + axisLineColor, + defaultValue: defaultData.axisLineColor, + ), + ); + properties.add( + ColorProperty( + 'markerFillColor', + markerFillColor, + defaultValue: defaultData.markerFillColor, + ), + ); + properties.add( + ColorProperty( + 'dataLabelBackgroundColor', + dataLabelBackgroundColor, + defaultValue: defaultData.dataLabelBackgroundColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipColor', + tooltipColor, + defaultValue: defaultData.tooltipColor, + ), + ); + properties.add( + ColorProperty( + 'trackballLineColor', + trackballLineColor, + defaultValue: defaultData.trackballLineColor, + ), + ); + properties.add( + ColorProperty( + 'tooltipLabelColor', + tooltipLabelColor, + defaultValue: defaultData.tooltipLabelColor, + ), + ); + properties.add( + DiagnosticsProperty( + 'dataLabelTextStyle', + dataLabelTextStyle, + defaultValue: defaultData.dataLabelTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'trackballTextStyle', + trackballTextStyle, + defaultValue: defaultData.trackballTextStyle, + ), + ); + } +} 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 e5b9a2b9f..71a73cc8e 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart @@ -1,10 +1,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/src/theme/range_slider_theme.dart'; -import 'package:syncfusion_flutter_core/src/theme/slider_theme.dart'; + +import 'assistview_theme.dart'; import 'barcodes_theme.dart'; import 'calendar_theme.dart'; import 'charts_theme.dart'; +import 'chat_theme.dart'; +import 'color_scheme.dart'; import 'datagrid_theme.dart'; import 'datapager_theme.dart'; import 'daterangepicker_theme.dart'; @@ -12,6 +14,10 @@ import 'gauges_theme.dart'; import 'maps_theme.dart'; import 'pdfviewer_theme.dart'; import 'range_selector_theme.dart'; +import 'range_slider_theme.dart'; +import 'slider_theme.dart'; +import 'spark_charts_theme.dart'; +import 'treemap_theme.dart'; /// Applies a theme to descendant Syncfusion widgets. /// @@ -38,12 +44,8 @@ import 'range_selector_theme.dart'; /// } /// ``` class SfTheme extends StatelessWidget { - /// Creating an argument constructor of SfTheme class. - const SfTheme({ - Key? key, - this.data, - required this.child, - }) : super(key: key); + /// Creating an argument constructor of [SfTheme] class. + const SfTheme({Key? key, this.data, required this.child}) : super(key: key); /// Specifies a widget that can hold single child. /// @@ -89,15 +91,11 @@ class SfTheme extends StatelessWidget { /// ``` final SfThemeData? data; - //ignore: unused_field - static final SfThemeData _kFallbackTheme = SfThemeData.fallback(); - /// The data from the closest [SfTheme] instance that encloses the given /// context. /// /// Defaults to [SfThemeData.fallback] if there is no [SfTheme] in the given /// build context. - /// static SfThemeData of(BuildContext context) { final _SfInheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_SfInheritedTheme>(); @@ -107,6 +105,106 @@ class SfTheme extends StatelessWidget { : SfThemeData.dark()); } + /// Returns [SfColorScheme] based on the [useMaterial3]. + static SfColorScheme colorScheme(BuildContext context) { + final ThemeData themeData = Theme.of(context); + if (themeData.useMaterial3) { + return SfColorScheme.m3( + useMaterial3: themeData.useMaterial3, + brightness: themeData.brightness, + primary: themeData.colorScheme.primary, + onPrimary: themeData.colorScheme.onPrimary, + primaryContainer: themeData.colorScheme.primaryContainer, + secondaryContainer: themeData.colorScheme.secondaryContainer, + surface: themeData.colorScheme.surface, + onSurface: themeData.colorScheme.onSurface, + surfaceVariant: themeData.colorScheme.surfaceContainerHighest, + onSurfaceVariant: themeData.colorScheme.onSurfaceVariant, + inverseSurface: themeData.colorScheme.inverseSurface, + onInverseSurface: themeData.colorScheme.onInverseSurface, + outline: themeData.colorScheme.outline, + outlineVariant: themeData.colorScheme.outlineVariant, + textColor: themeData.colorScheme.onSurface, + splashColor: themeData.splashColor, + hoverColor: themeData.hoverColor, + highlightColor: themeData.highlightColor, + valueIndicatorColor: Colors.transparent, + transparent: Colors.transparent, + scrim: themeData.colorScheme.scrim, + palettes: _palettesM3(themeData), + ); + } else { + return SfColorScheme.m2( + useMaterial3: themeData.useMaterial3, + brightness: themeData.brightness, + primary: themeData.colorScheme.primary, + onPrimary: themeData.colorScheme.onPrimary, + primaryContainer: themeData.colorScheme.primaryContainer, + secondaryContainer: themeData.colorScheme.secondaryContainer, + surface: themeData.colorScheme.surface, + onSurface: themeData.colorScheme.onSurface, + surfaceVariant: themeData.colorScheme.surfaceContainerHighest, + onSurfaceVariant: themeData.colorScheme.onSurfaceVariant, + inverseSurface: themeData.colorScheme.inverseSurface, + onInverseSurface: themeData.colorScheme.onInverseSurface, + outline: themeData.colorScheme.outline, + outlineVariant: themeData.colorScheme.outlineVariant, + textColor: themeData.colorScheme.onSurface, + splashColor: themeData.splashColor, + hoverColor: themeData.hoverColor, + highlightColor: themeData.highlightColor, + valueIndicatorColor: Colors.transparent, + transparent: Colors.transparent, + palettes: _palettesM2(), + ); + } + } + + static List _palettesM3(ThemeData themeData) { + if (themeData.colorScheme.brightness == Brightness.light) { + return const [ + Color.fromRGBO(6, 174, 224, 1), + Color.fromRGBO(99, 85, 199, 1), + Color.fromRGBO(49, 90, 116, 1), + Color.fromRGBO(255, 180, 0, 1), + Color.fromRGBO(150, 60, 112, 1), + Color.fromRGBO(33, 150, 245, 1), + Color.fromRGBO(71, 59, 137, 1), + Color.fromRGBO(236, 92, 123, 1), + Color.fromRGBO(59, 163, 26, 1), + Color.fromRGBO(236, 131, 23, 1), + ]; + } else { + return const [ + Color.fromRGBO(255, 245, 0, 1), + Color.fromRGBO(51, 182, 119, 1), + Color.fromRGBO(218, 150, 70, 1), + Color.fromRGBO(201, 88, 142, 1), + Color.fromRGBO(77, 170, 255, 1), + Color.fromRGBO(255, 157, 69, 1), + Color.fromRGBO(178, 243, 46, 1), + Color.fromRGBO(185, 60, 228, 1), + Color.fromRGBO(48, 167, 6, 1), + Color.fromRGBO(207, 142, 14, 1), + ]; + } + } + + static List _palettesM2() { + return const [ + Color.fromRGBO(75, 135, 185, 1), + Color.fromRGBO(192, 108, 132, 1), + Color.fromRGBO(246, 114, 128, 1), + Color.fromRGBO(248, 177, 149, 1), + Color.fromRGBO(116, 180, 155, 1), + Color.fromRGBO(0, 168, 181, 1), + Color.fromRGBO(73, 76, 162, 1), + Color.fromRGBO(255, 205, 96, 1), + Color.fromRGBO(255, 240, 219, 1), + Color.fromRGBO(238, 238, 238, 1), + ]; + } + @override Widget build(BuildContext context) { return _SfInheritedTheme(data: data, child: child); @@ -115,14 +213,11 @@ class SfTheme extends StatelessWidget { class _SfInheritedTheme extends InheritedTheme { const _SfInheritedTheme({Key? key, this.data, required Widget child}) - : super(key: key, child: child); - + : super(key: key, child: child); final SfThemeData? data; - @override bool updateShouldNotify(_SfInheritedTheme oldWidget) => data != oldWidget.data; - @override Widget wrap(BuildContext context, Widget child) { final _SfInheritedTheme? ancestorTheme = @@ -159,56 +254,78 @@ class _SfInheritedTheme extends InheritedTheme { @immutable 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}) { + factory SfThemeData({ + Brightness? brightness, + SfPdfViewerThemeData? pdfViewerThemeData, + SfChartThemeData? chartThemeData, + SfSparkChartThemeData? sparkChartThemeData, + SfCalendarThemeData? calendarThemeData, + SfDataGridThemeData? dataGridThemeData, + SfDataPagerThemeData? dataPagerThemeData, + SfDateRangePickerThemeData? dateRangePickerThemeData, + SfBarcodeThemeData? barcodeThemeData, + SfGaugeThemeData? gaugeThemeData, + SfSliderThemeData? sliderThemeData, + SfRangeSliderThemeData? rangeSliderThemeData, + SfRangeSelectorThemeData? rangeSelectorThemeData, + SfMapsThemeData? mapsThemeData, + SfTreemapThemeData? treemapThemeData, + SfChatThemeData? chatThemeData, + SfAIAssistViewThemeData? assistThemeData, + }) { brightness ??= Brightness.light; - pdfViewerThemeData = - pdfViewerThemeData ?? SfPdfViewerThemeData(brightness: brightness); - chartThemeData = chartThemeData ?? SfChartThemeData(brightness: brightness); + pdfViewerThemeData = pdfViewerThemeData ?? SfPdfViewerThemeData.raw(); + sparkChartThemeData = + sparkChartThemeData ?? + SfSparkChartThemeData.raw(brightness: brightness); + chartThemeData = + chartThemeData ?? SfChartThemeData.raw(brightness: brightness); calendarThemeData = - calendarThemeData ?? SfCalendarThemeData(brightness: brightness); + calendarThemeData ?? SfCalendarThemeData.raw(brightness: brightness); dataGridThemeData = - dataGridThemeData ?? SfDataGridThemeData(brightness: brightness); - dateRangePickerThemeData = dateRangePickerThemeData ?? - SfDateRangePickerThemeData(brightness: brightness); + dataGridThemeData ?? SfDataGridThemeData.raw(brightness: brightness); + dateRangePickerThemeData = + dateRangePickerThemeData ?? + SfDateRangePickerThemeData.raw(brightness: brightness); barcodeThemeData = - barcodeThemeData ?? SfBarcodeThemeData(brightness: brightness); - gaugeThemeData = gaugeThemeData ?? SfGaugeThemeData(brightness: brightness); + barcodeThemeData ?? SfBarcodeThemeData.raw(brightness: brightness); + gaugeThemeData = + gaugeThemeData ?? SfGaugeThemeData.raw(brightness: brightness); sliderThemeData = - sliderThemeData ?? SfSliderThemeData(brightness: brightness); - rangeSelectorThemeData = rangeSelectorThemeData ?? - SfRangeSelectorThemeData(brightness: brightness); + sliderThemeData ?? SfSliderThemeData.raw(brightness: brightness); + rangeSelectorThemeData = + rangeSelectorThemeData ?? + SfRangeSelectorThemeData.raw(brightness: brightness); rangeSliderThemeData = - rangeSliderThemeData ?? SfRangeSliderThemeData(brightness: brightness); - mapsThemeData = mapsThemeData ?? SfMapsThemeData(brightness: brightness); + rangeSliderThemeData ?? + SfRangeSliderThemeData.raw(brightness: brightness); + mapsThemeData = + mapsThemeData ?? SfMapsThemeData.raw(brightness: brightness); + treemapThemeData = + treemapThemeData ?? SfTreemapThemeData.raw(brightness: brightness); dataPagerThemeData = - dataPagerThemeData ?? SfDataPagerThemeData(brightness: brightness); + dataPagerThemeData ?? SfDataPagerThemeData.raw(brightness: brightness); + chatThemeData = chatThemeData ?? SfChatThemeData.raw(); + assistThemeData = assistThemeData ?? SfAIAssistViewThemeData.raw(); return SfThemeData.raw( - brightness: brightness, - pdfViewerThemeData: pdfViewerThemeData, - chartThemeData: chartThemeData, - calendarThemeData: calendarThemeData, - dataGridThemeData: dataGridThemeData, - dataPagerThemeData: dataPagerThemeData, - dateRangePickerThemeData: dateRangePickerThemeData, - barcodeThemeData: barcodeThemeData, - gaugeThemeData: gaugeThemeData, - sliderThemeData: sliderThemeData, - rangeSelectorThemeData: rangeSelectorThemeData, - rangeSliderThemeData: rangeSliderThemeData, - mapsThemeData: mapsThemeData); + brightness: brightness, + pdfViewerThemeData: pdfViewerThemeData, + chartThemeData: chartThemeData, + sparkChartThemeData: sparkChartThemeData, + calendarThemeData: calendarThemeData, + dataGridThemeData: dataGridThemeData, + dataPagerThemeData: dataPagerThemeData, + dateRangePickerThemeData: dateRangePickerThemeData, + barcodeThemeData: barcodeThemeData, + gaugeThemeData: gaugeThemeData, + sliderThemeData: sliderThemeData, + rangeSelectorThemeData: rangeSelectorThemeData, + rangeSliderThemeData: rangeSliderThemeData, + mapsThemeData: mapsThemeData, + treemapThemeData: treemapThemeData, + chatThemeData: chatThemeData, + assistThemeData: assistThemeData, + ); } /// Create a [SfThemeData] given a set of exact values. All the values must be @@ -218,20 +335,25 @@ class SfThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [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}); + const SfThemeData.raw({ + required this.brightness, + required this.pdfViewerThemeData, + required this.chartThemeData, + required this.sparkChartThemeData, + 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, + required this.treemapThemeData, + required this.chatThemeData, + required this.assistThemeData, + }); /// This method returns the light theme when no theme has been specified. factory SfThemeData.light() => SfThemeData(brightness: Brightness.light); @@ -310,6 +432,25 @@ class SfThemeData with Diagnosticable { /// ``` final SfChartThemeData chartThemeData; + /// Defines the default configuration of spark chart widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkchartThemeData: SfSparkChartThemeData() + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + final SfSparkChartThemeData sparkChartThemeData; + /// Defines the default configuration of datagrid widgets. /// /// ```dart @@ -481,74 +622,199 @@ class SfThemeData with Diagnosticable { /// ``` final SfMapsThemeData mapsThemeData; - ///ToDO + /// Defines the default configuration of dataPager widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// dataPagerThemeData: SfDataPagerThemeData() + /// ), + /// child: SfDataPager(), + /// ), + /// ) + /// ); + /// } + /// ``` final SfDataPagerThemeData dataPagerThemeData; + /// Defines the default configuration of treemap widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// treemapThemeData: SfTreemapThemeData() + /// ), + /// child: SfTreemap(), + /// ), + /// ) + /// ); + /// } + /// ``` + final SfTreemapThemeData treemapThemeData; + + /// Defines the default configuration of chat widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// chatThemeData: SfChatThemeData() + /// ), + /// child: SfChat(), + /// ), + /// ) + /// ); + /// } + /// ``` + final SfChatThemeData chatThemeData; + + /// Defines the default configuration of assistview widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// assistThemeData: SfAIAssistViewThemeData() + /// ), + /// child: SfAIAssistView(), + /// ), + /// ) + /// ); + /// } + /// ``` + final SfAIAssistViewThemeData assistThemeData; + /// 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, - SfGaugeThemeData? gaugeThemeData, - SfSliderThemeData? sliderThemeData, - SfRangeSelectorThemeData? rangeSelectorThemeData, - SfRangeSliderThemeData? rangeSliderThemeData, - SfMapsThemeData? mapsThemeData, - SfDataPagerThemeData? dataPagerThemeData}) { + SfThemeData copyWith({ + Brightness? brightness, + SfPdfViewerThemeData? pdfViewerThemeData, + SfChartThemeData? chartThemeData, + SfSparkChartThemeData? sparkChartThemeData, + SfCalendarThemeData? calendarThemeData, + SfDataGridThemeData? dataGridThemeData, + SfDateRangePickerThemeData? dateRangePickerThemeData, + SfBarcodeThemeData? barcodeThemeData, + SfGaugeThemeData? gaugeThemeData, + SfSliderThemeData? sliderThemeData, + SfRangeSelectorThemeData? rangeSelectorThemeData, + SfRangeSliderThemeData? rangeSliderThemeData, + SfMapsThemeData? mapsThemeData, + SfDataPagerThemeData? dataPagerThemeData, + SfTreemapThemeData? treemapThemeData, + SfChatThemeData? chatThemeData, + SfAIAssistViewThemeData? assistThemeData, + }) { return SfThemeData.raw( - brightness: brightness ?? this.brightness, - pdfViewerThemeData: pdfViewerThemeData ?? this.pdfViewerThemeData, - chartThemeData: chartThemeData ?? this.chartThemeData, - calendarThemeData: calendarThemeData ?? this.calendarThemeData, - dataGridThemeData: dataGridThemeData ?? this.dataGridThemeData, - dataPagerThemeData: dataPagerThemeData ?? this.dataPagerThemeData, - dateRangePickerThemeData: - dateRangePickerThemeData ?? this.dateRangePickerThemeData, - barcodeThemeData: barcodeThemeData ?? this.barcodeThemeData, - gaugeThemeData: gaugeThemeData ?? this.gaugeThemeData, - sliderThemeData: sliderThemeData ?? this.sliderThemeData, - rangeSelectorThemeData: - rangeSelectorThemeData ?? this.rangeSelectorThemeData, - rangeSliderThemeData: rangeSliderThemeData ?? this.rangeSliderThemeData, - mapsThemeData: mapsThemeData ?? this.mapsThemeData); + brightness: brightness ?? this.brightness, + pdfViewerThemeData: pdfViewerThemeData ?? this.pdfViewerThemeData, + chartThemeData: chartThemeData ?? this.chartThemeData, + sparkChartThemeData: sparkChartThemeData ?? this.sparkChartThemeData, + calendarThemeData: calendarThemeData ?? this.calendarThemeData, + dataGridThemeData: dataGridThemeData ?? this.dataGridThemeData, + dataPagerThemeData: dataPagerThemeData ?? this.dataPagerThemeData, + dateRangePickerThemeData: + dateRangePickerThemeData ?? this.dateRangePickerThemeData, + barcodeThemeData: barcodeThemeData ?? this.barcodeThemeData, + gaugeThemeData: gaugeThemeData ?? this.gaugeThemeData, + sliderThemeData: sliderThemeData ?? this.sliderThemeData, + rangeSelectorThemeData: + rangeSelectorThemeData ?? this.rangeSelectorThemeData, + rangeSliderThemeData: rangeSliderThemeData ?? this.rangeSliderThemeData, + mapsThemeData: mapsThemeData ?? this.mapsThemeData, + treemapThemeData: treemapThemeData ?? this.treemapThemeData, + chatThemeData: chatThemeData ?? this.chatThemeData, + assistThemeData: assistThemeData ?? this.assistThemeData, + ); } /// Linearly interpolate between two themes. static SfThemeData lerp(SfThemeData? a, SfThemeData? b, double t) { assert(a != null); assert(b != null); - return SfThemeData.raw( - brightness: t < 0.5 ? a!.brightness : b!.brightness, - pdfViewerThemeData: SfPdfViewerThemeData.lerp( - a!.pdfViewerThemeData, b!.pdfViewerThemeData, t)!, - chartThemeData: - SfChartThemeData.lerp(a.chartThemeData, b.chartThemeData, t)!, - calendarThemeData: SfCalendarThemeData.lerp( - a.calendarThemeData, b.calendarThemeData, t)!, - dataGridThemeData: SfDataGridThemeData.lerp( - a.dataGridThemeData, b.dataGridThemeData, t)!, - dataPagerThemeData: SfDataPagerThemeData.lerp( - a.dataPagerThemeData, b.dataPagerThemeData, t)!, - dateRangePickerThemeData: SfDateRangePickerThemeData.lerp( - a.dateRangePickerThemeData, b.dateRangePickerThemeData, t)!, - barcodeThemeData: - SfBarcodeThemeData.lerp(a.barcodeThemeData, b.barcodeThemeData, t)!, - gaugeThemeData: - SfGaugeThemeData.lerp(a.gaugeThemeData, b.gaugeThemeData, t)!, - sliderThemeData: - SfSliderThemeData.lerp(a.sliderThemeData, b.sliderThemeData, t)!, - rangeSelectorThemeData: SfRangeSelectorThemeData.lerp( - a.rangeSelectorThemeData, b.rangeSelectorThemeData, t)!, - rangeSliderThemeData: SfRangeSliderThemeData.lerp( - a.rangeSliderThemeData, b.rangeSliderThemeData, t)!, - mapsThemeData: - SfMapsThemeData.lerp(a.mapsThemeData, b.mapsThemeData, t)!); + brightness: t < 0.5 ? a!.brightness : b!.brightness, + pdfViewerThemeData: + SfPdfViewerThemeData.lerp( + a!.pdfViewerThemeData, + b!.pdfViewerThemeData, + t, + )!, + chartThemeData: + SfChartThemeData.lerp(a.chartThemeData, b.chartThemeData, t)!, + sparkChartThemeData: + SfSparkChartThemeData.lerp( + a.sparkChartThemeData, + b.sparkChartThemeData, + t, + )!, + calendarThemeData: + SfCalendarThemeData.lerp( + a.calendarThemeData, + b.calendarThemeData, + t, + )!, + dataGridThemeData: + SfDataGridThemeData.lerp( + a.dataGridThemeData, + b.dataGridThemeData, + t, + )!, + dataPagerThemeData: + SfDataPagerThemeData.lerp( + a.dataPagerThemeData, + b.dataPagerThemeData, + t, + )!, + dateRangePickerThemeData: + SfDateRangePickerThemeData.lerp( + a.dateRangePickerThemeData, + b.dateRangePickerThemeData, + t, + )!, + barcodeThemeData: + SfBarcodeThemeData.lerp(a.barcodeThemeData, b.barcodeThemeData, t)!, + gaugeThemeData: + SfGaugeThemeData.lerp(a.gaugeThemeData, b.gaugeThemeData, t)!, + sliderThemeData: + SfSliderThemeData.lerp(a.sliderThemeData, b.sliderThemeData, t)!, + rangeSelectorThemeData: + SfRangeSelectorThemeData.lerp( + a.rangeSelectorThemeData, + b.rangeSelectorThemeData, + t, + )!, + rangeSliderThemeData: + SfRangeSliderThemeData.lerp( + a.rangeSliderThemeData, + b.rangeSliderThemeData, + t, + )!, + mapsThemeData: SfMapsThemeData.lerp(a.mapsThemeData, b.mapsThemeData, t)!, + treemapThemeData: + SfTreemapThemeData.lerp(a.treemapThemeData, b.treemapThemeData, t)!, + chatThemeData: SfChatThemeData.lerp(a.chatThemeData, b.chatThemeData, t)!, + assistThemeData: + SfAIAssistViewThemeData.lerp( + a.assistThemeData, + b.assistThemeData, + t, + )!, + ); } @override @@ -556,11 +822,11 @@ class SfThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - return other is SfThemeData && other.brightness == brightness && other.pdfViewerThemeData == pdfViewerThemeData && other.chartThemeData == chartThemeData && + other.sparkChartThemeData == sparkChartThemeData && other.calendarThemeData == calendarThemeData && other.dataGridThemeData == dataGridThemeData && other.dataPagerThemeData == dataPagerThemeData && @@ -570,7 +836,10 @@ class SfThemeData with Diagnosticable { other.sliderThemeData == sliderThemeData && other.rangeSelectorThemeData == rangeSelectorThemeData && other.rangeSliderThemeData == rangeSliderThemeData && - other.mapsThemeData == mapsThemeData; + other.mapsThemeData == mapsThemeData && + other.treemapThemeData == treemapThemeData && + other.chatThemeData == chatThemeData && + other.assistThemeData == assistThemeData; } @override @@ -579,6 +848,7 @@ class SfThemeData with Diagnosticable { brightness, pdfViewerThemeData, chartThemeData, + sparkChartThemeData, calendarThemeData, dataGridThemeData, dataPagerThemeData, @@ -588,52 +858,136 @@ class SfThemeData with Diagnosticable { sliderThemeData, rangeSelectorThemeData, rangeSliderThemeData, - mapsThemeData + mapsThemeData, + treemapThemeData, + chatThemeData, + assistThemeData, ]; - return hashList(values); + return Object.hashAll(values); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); final SfThemeData defaultData = SfThemeData.fallback(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); - properties.add(DiagnosticsProperty( - 'pdfViewerThemeData', pdfViewerThemeData, - defaultValue: defaultData.pdfViewerThemeData)); - properties.add(DiagnosticsProperty( - 'chartThemeData', chartThemeData, - defaultValue: defaultData.chartThemeData)); - properties.add(DiagnosticsProperty( - 'calendarThemeData', calendarThemeData, - defaultValue: defaultData.calendarThemeData)); - properties.add(DiagnosticsProperty( - 'dataGridThemeData', dataGridThemeData, - defaultValue: defaultData.dataGridThemeData)); - properties.add(DiagnosticsProperty( - 'dataPagerThemeData', dataPagerThemeData, - defaultValue: defaultData.dataPagerThemeData)); - properties.add(DiagnosticsProperty( - 'dateRangePickerThemeData', dateRangePickerThemeData, - defaultValue: defaultData.dateRangePickerThemeData)); - properties.add(DiagnosticsProperty( - 'barcodeThemeData', barcodeThemeData, - defaultValue: defaultData.barcodeThemeData)); - properties.add(DiagnosticsProperty( - 'gaugeThemeData', gaugeThemeData, - defaultValue: defaultData.gaugeThemeData)); - properties.add(DiagnosticsProperty( - 'rangeSelectorThemeData', rangeSelectorThemeData, - defaultValue: defaultData.rangeSelectorThemeData)); - properties.add(DiagnosticsProperty( - 'rangeSliderThemeData', rangeSliderThemeData, - defaultValue: defaultData.rangeSliderThemeData)); - properties.add(DiagnosticsProperty( - 'sliderThemeData', sliderThemeData, - defaultValue: defaultData.sliderThemeData)); - properties.add(DiagnosticsProperty( - 'mapsThemeData', mapsThemeData, - defaultValue: defaultData.mapsThemeData)); + properties.add( + EnumProperty( + 'brightness', + brightness, + defaultValue: defaultData.brightness, + ), + ); + properties.add( + DiagnosticsProperty( + 'pdfViewerThemeData', + pdfViewerThemeData, + defaultValue: defaultData.pdfViewerThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'chartThemeData', + chartThemeData, + defaultValue: defaultData.chartThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'sparkChartThemeData', + sparkChartThemeData, + defaultValue: defaultData.sparkChartThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'calendarThemeData', + calendarThemeData, + defaultValue: defaultData.calendarThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'dataGridThemeData', + dataGridThemeData, + defaultValue: defaultData.dataGridThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'dataPagerThemeData', + dataPagerThemeData, + defaultValue: defaultData.dataPagerThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'dateRangePickerThemeData', + dateRangePickerThemeData, + defaultValue: defaultData.dateRangePickerThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'barcodeThemeData', + barcodeThemeData, + defaultValue: defaultData.barcodeThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'gaugeThemeData', + gaugeThemeData, + defaultValue: defaultData.gaugeThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'rangeSelectorThemeData', + rangeSelectorThemeData, + defaultValue: defaultData.rangeSelectorThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'rangeSliderThemeData', + rangeSliderThemeData, + defaultValue: defaultData.rangeSliderThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'sliderThemeData', + sliderThemeData, + defaultValue: defaultData.sliderThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'mapsThemeData', + mapsThemeData, + defaultValue: defaultData.mapsThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'treemapThemeData', + treemapThemeData, + defaultValue: defaultData.treemapThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'chatThemeData', + chatThemeData, + defaultValue: defaultData.chatThemeData, + ), + ); + properties.add( + DiagnosticsProperty( + 'assistThemeData', + assistThemeData, + defaultValue: defaultData.assistThemeData, + ), + ); } } diff --git a/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart new file mode 100644 index 000000000..a77d2491d --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart @@ -0,0 +1,218 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import '../../theme.dart'; + +/// Applies a theme to descendant Syncfusion treemap widgets. +/// +/// To obtain the current theme, use [SfTreemapTheme.of]. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemapTheme( +/// data: SfTreemapThemeData( +/// brightness: Brightness.light +/// ), +/// child: SfTreemap(), +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SfTheme](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfTheme-class.html) +/// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), +/// for customizing the visual appearance of the treemap widgets. +/// +class SfTreemapTheme extends InheritedTheme { + ///Initialize the class of SfTreemapTheme + const SfTreemapTheme({Key? key, required this.data, required this.child}) + : super(key: key, child: child); + + /// Specifies the color and typography values for descendant Treemap widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemapTheme( + /// data: SfTreemapThemeData( + /// brightness: Brightness.light + /// ), + /// child: SfTreemap(), + /// ), + /// ); + /// } + /// ``` + + final SfTreemapThemeData data; + + /// Specifies a widget that can hold single child. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemapTheme( + /// data: SfTreemapThemeData( + /// brightness: Brightness.light + /// ), + /// child: SfTreemap(), + /// ), + /// ); + /// } + /// ``` + @override + final Widget child; + + /// The data from the closest [SfTreemapTheme] instance + /// that encloses the given context. + /// + /// Defaults to [SfTreemapTheme.treemapThemeData] + /// if there is no [SfTreemapTheme] in the given build context. + static SfTreemapThemeData of(BuildContext context) { + final SfTreemapTheme? sfTreemapTheme = + context.dependOnInheritedWidgetOfExactType(); + return sfTreemapTheme?.data ?? SfTheme.of(context).treemapThemeData; + } + + @override + bool updateShouldNotify(SfTreemapTheme oldWidget) => data != oldWidget.data; + + @override + Widget wrap(BuildContext context, Widget child) { + final SfTreemapTheme? ancestorTheme = + context.findAncestorWidgetOfExactType(); + return identical(this, ancestorTheme) + ? child + : SfTreemapTheme(data: data, child: child); + } +} + +/// Holds the color and typography values for a [SfTreemapTheme]. +/// Applies a theme to descendant Syncfusion treemap widgets. +/// +/// To obtain the current theme, use [SfTreemapTheme.of]. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemapTheme( +/// data: SfTreemapThemeData( +/// brightness: Brightness.light +/// ), +/// child: SfTreemap(), +/// ), +/// ); +/// } +/// ``` +/// +/// See also: +/// +/// * [SfTheme](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfTheme-class.html) +/// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), +/// for customizing the visual appearance of the treemap widgets. + +@immutable +class SfTreemapThemeData with Diagnosticable { + /// Create a [SfTreemapThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfTreemapThemeData] constructor. + const SfTreemapThemeData({this.legendTextStyle}); + + ///Initialize the sfTreemap theme data + factory SfTreemapThemeData.raw({ + Brightness? brightness, + TextStyle? legendTextStyle, + }) { + brightness = brightness ?? Brightness.light; + + return SfTreemapThemeData(legendTextStyle: legendTextStyle); + } + + /// Specifies the legend text style of treemap widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// treemapThemeData: SfTreemapThemeData( + /// legendTextStyle: TextStyle(color: Colors.blue), + /// ), + /// ), + /// child: SfTreemap(), + /// ), + /// ) + /// ); + ///} + /// ``` + final TextStyle? legendTextStyle; + + /// Creates a copy of this treemap theme data object with the matching fields + /// replaced with the non-null parameter values. + SfTreemapThemeData copyWith({ + Brightness? brightness, + TextStyle? legendTextStyle, + }) { + return SfTreemapThemeData.raw( + brightness: brightness, + legendTextStyle: legendTextStyle ?? this.legendTextStyle, + ); + } + + /// Returns the treemap theme data + static SfTreemapThemeData? lerp( + SfTreemapThemeData? a, + SfTreemapThemeData? b, + double t, + ) { + if (a == null && b == null) { + return null; + } + return SfTreemapThemeData( + legendTextStyle: TextStyle.lerp( + a!.legendTextStyle, + b!.legendTextStyle, + t, + ), + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is SfTreemapThemeData && + other.legendTextStyle == legendTextStyle; + } + + @override + int get hashCode { + final List values = [legendTextStyle]; + return Object.hashAll(values); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + const SfTreemapThemeData defaultData = SfTreemapThemeData(); + properties.add( + DiagnosticsProperty( + 'legendTextStyle', + legendTextStyle, + defaultValue: defaultData.legendTextStyle, + ), + ); + } +} diff --git a/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart b/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart index 28dfbbbf6..2befed42d 100644 --- a/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart +++ b/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_setters_without_getters + part of tooltip_internal; /// Renders the tooltip widget. @@ -6,28 +8,28 @@ part of tooltip_internal; class SfTooltip extends StatefulWidget { /// Creating an argument constructor of SfTooltip class. // ignore: prefer_const_constructors_in_immutables - SfTooltip( - {this.textStyle = const TextStyle(), - this.animationDuration = 500, - this.animationCurve = const Interval(0.0, 1.0, curve: Curves.linear), - 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.shadowColor, - Key? key, - this.onTooltipRender}) - : super(key: key); + SfTooltip({ + this.textStyle = const TextStyle(), + this.animationDuration = 500, + this.animationCurve = const Interval(0.0, 1.0), + 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.shadowColor, + Key? key, + this.onTooltipRender, + }) : super(key: key); ///Toggles the visibility of the tooltip. /// @@ -153,6 +155,8 @@ class SfTooltipState extends State Object? _previousTooltipData; + Offset? _previouslocation; + late int _showDuration; ///Setter for the boundary rect within which the tooltip could be shown @@ -174,13 +178,14 @@ class SfTooltipState extends State /// *template - the widget that will be rendered instead of default tooltip /// *tooltipData - the data which allows this widget to decide whether it is /// activated for the same point - void show( - {int? duration, - Offset? position, - Object? tooltipData, - String? tooltipContent, - String? tooltipHeader, - Widget? template}) { + void show({ + int? duration, + Offset? position, + Object? tooltipData, + String? tooltipContent, + String? tooltipHeader, + Widget? template, + }) { duration ??= widget.animationDuration; _hidden = false; animationController!.duration = Duration(milliseconds: duration); @@ -189,7 +194,9 @@ class SfTooltipState extends State } _timer?.cancel(); if (_previousTooltipData == null || - !(_previousTooltipData == tooltipData)) { + !(_previousTooltipData == tooltipData) || + (_previouslocation == null || _previouslocation != position)) { + _previouslocation = position; _show = true; _template = template; _previousTooltipData = tooltipData; @@ -240,8 +247,9 @@ class SfTooltipState extends State _show = false; needMarker = widget.canShowMarker; animationController = AnimationController( - duration: Duration(milliseconds: widget.animationDuration), vsync: this) - ..addStatusListener(_animationStatusListener); + duration: Duration(milliseconds: widget.animationDuration), + vsync: this, + )..addStatusListener(_animationStatusListener); super.initState(); } @@ -253,11 +261,15 @@ class SfTooltipState extends State @override Widget build(BuildContext context) { - final Animation tooltipAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController!, - curve: widget.animationCurve, - )); + final Animation tooltipAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate( + CurvedAnimation( + parent: animationController!, + curve: widget.animationCurve, + ), + ); if (_show && !_didUpdate) { if (animationController?.status != AnimationStatus.forward) { animationController!.forward(from: 0.0); @@ -277,10 +289,11 @@ class SfTooltipState extends State return child!; }, child: TooltipRenderObject( - template: _template, - tooltipAnimation: tooltipAnimation, - tooltipState: this, - animationController: animationController!), + template: _template, + tooltipAnimation: tooltipAnimation, + tooltipState: this, + animationController: animationController!, + ), ); } @@ -309,15 +322,16 @@ class SfTooltipState extends State class TooltipRenderObject extends SingleChildRenderObjectWidget { /// Creating an argument constructor of TooltipRenderObject class. // ignore: prefer_const_constructors_in_immutables - TooltipRenderObject( - {Widget? template, - required SfTooltipState tooltipState, - required Animation tooltipAnimation, - required AnimationController animationController}) - : _tooltipState = tooltipState, - _tooltipAnimation = tooltipAnimation, - _animationController = animationController, - super(child: template); + TooltipRenderObject({ + super.key, + 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; @@ -326,7 +340,10 @@ class TooltipRenderObject extends SingleChildRenderObjectWidget { @override TooltipRenderBox createRenderObject(BuildContext context) { _tooltipState.renderBox = TooltipRenderBox( - _tooltipState, _tooltipAnimation, _animationController); + _tooltipState, + _tooltipAnimation, + _animationController, + ); return _tooltipState.renderBox!; } @@ -344,9 +361,11 @@ class TooltipRenderObject extends SingleChildRenderObjectWidget { class TooltipRenderBox extends RenderShiftedBox { /// Creating an argument constructor of TooltipRenderBox class. TooltipRenderBox( - this._tooltipState, this._tooltipAnimation, this._animationController, - [RenderBox? child]) - : super(child); + this._tooltipState, + this._tooltipAnimation, + this._animationController, [ + RenderBox? child, + ]) : super(child); SfTooltip get _tooltip => _tooltipState.widget; SfTooltipState _tooltipState; @@ -456,7 +475,7 @@ class TooltipRenderBox extends RenderShiftedBox { _markerImages = images; } - List? _markerGradients; + late List? _markerGradients; ///Setter for the tooltip marker gradient set markerGradients(List values) { @@ -482,12 +501,15 @@ class TooltipRenderBox extends RenderShiftedBox { if (child == null || _tooltipRect == null) { return false; } else { - return child!.hitTest(result, - position: position - - (_isOutOfBoundInTop - ? _tooltipRect!.topLeft + - Offset(0, _templateArrowHeight + _tooltipRect!.height) - : _tooltipRect!.topLeft)); + return child!.hitTest( + result, + position: + position - + (_isOutOfBoundInTop + ? _tooltipRect!.topLeft + + Offset(0, _templateArrowHeight + _tooltipRect!.height) + : _tooltipRect!.topLeft), + ); } } @@ -500,18 +522,21 @@ class TooltipRenderBox extends RenderShiftedBox { size = Size.copy(child!.size); } } else { - size = Size(constraints.maxWidth.isFinite ? constraints.maxWidth : 0, - constraints.maxHeight.isFinite ? constraints.maxHeight : 0); + size = Size( + constraints.maxWidth.isFinite ? constraints.maxWidth : 0, + constraints.maxHeight.isFinite ? constraints.maxHeight : 0, + ); child?.layout(constraints); } } @override void paint(PaintingContext context, Offset offset) { - final Offset parentOffset = parentData is BoxParentData - //ignore: avoid_as - ? (parentData! as BoxParentData).offset - : Offset.zero; + final Offset parentOffset = + parentData is BoxParentData + //ignore: avoid_as + ? (parentData! as BoxParentData).offset + : Offset.zero; final Offset relativeOffset = offset - parentOffset; calculateLocation(_position != null ? (_position!) : parentOffset); context.canvas.translate(relativeOffset.dx, relativeOffset.dy); @@ -520,8 +545,11 @@ class TooltipRenderBox extends RenderShiftedBox { _tooltipState._showDuration == 0) && _animationController.status != AnimationStatus.dismissed) && _tooltipState.widget.onTooltipRender != null) { - final TooltipRenderArgs tooltipRenderArgs = TooltipRenderArgs(_header, - _stringValue, _x != null && _y != null ? Offset(_x!, _y!) : 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; @@ -543,18 +571,24 @@ class TooltipRenderBox extends RenderShiftedBox { void _renderTemplateTooltipView(PaintingContext context, Offset offset) { _templateSize = Size.copy(child!.size); _tooltipRect = Rect.fromLTWH( - _x! - _templateSize.width / 2, - _y! - _templateSize.height - _templateArrowHeight, - _templateSize.width, - _templateSize.height); + _x! - _templateSize.width / 2, + _y! - _templateSize.height - _templateArrowHeight, + _templateSize.width, + _templateSize.height, + ); double top = _y!; double paddingTop = 0; final Rect bounds = _boundaryRect.translate(-offset.dx, -offset.dy); - final Offset tooltipLocation = - _getTemplateLocation(_tooltipRect!, bounds, offset); - Offset arrowLocation = Offset(_x! - _templateSize.width / 2, - _isOutOfBoundInTop ? _y! : _y! - _templateArrowHeight); + final Offset tooltipLocation = _getTemplateLocation( + _tooltipRect!, + bounds, + offset, + ); + Offset arrowLocation = Offset( + _x! - _templateSize.width / 2, + _isOutOfBoundInTop ? _y! : _y! - _templateArrowHeight, + ); if (_y! < bounds.top + offset.dy) { paddingTop = bounds.top + offset.dy + _templateArrowHeight; top = tooltipLocation.dy; @@ -564,53 +598,70 @@ class TooltipRenderBox extends RenderShiftedBox { if (_y! >= bounds.top + offset.dy) { paddingTop = top; } - final Offset renderOffset = (_isOutOfBoundInTop + final Offset renderOffset = + (_isOutOfBoundInTop ? Offset(tooltipLocation.dx, tooltipLocation.dy + paddingTop) : tooltipLocation) + offset; - context.pushTransform(true, Offset(_x!, arrowLocation.dy) + offset, - Matrix4.diagonal3Values(_animationFactor, _animationFactor, 1), - (PaintingContext tooltipTemplateContext, Offset tooltipTemplateOffset) { - _renderArrowAndTemplatePath( - tooltipTemplateContext.canvas, arrowLocation + offset, renderOffset); - tooltipTemplateContext.paintChild(child!, renderOffset); - }); + context.pushTransform( + true, + Offset(_x!, arrowLocation.dy) + offset, + Matrix4.diagonal3Values(_animationFactor, _animationFactor, 1), + (PaintingContext tooltipTemplateContext, Offset tooltipTemplateOffset) { + _renderArrowAndTemplatePath( + tooltipTemplateContext.canvas, + arrowLocation + offset, + renderOffset, + ); + tooltipTemplateContext.paintChild(child!, renderOffset); + }, + ); } /// This method renders the path for tooltip template mode void _renderArrowAndTemplatePath( - Canvas canvas, Offset location, Offset templateLocation) { - final Paint strokePaint = Paint() - ..style = PaintingStyle.stroke - ..color = - _tooltip.borderWidth == 0 ? Colors.transparent : _tooltip.borderColor - ..strokeWidth = _tooltip.borderWidth; - final Paint fillPaint = Paint() - ..color = (_tooltip.color).withOpacity(_tooltip.opacity) - ..style = PaintingStyle.fill; + Canvas canvas, + Offset location, + Offset templateLocation, + ) { + final Paint strokePaint = + Paint() + ..style = PaintingStyle.stroke + ..color = + _tooltip.borderWidth == 0 + ? Colors.transparent + : _tooltip.borderColor + ..strokeWidth = _tooltip.borderWidth; + final Paint fillPaint = + Paint() + ..color = (_tooltip.color).withValues(alpha: _tooltip.opacity) + ..style = PaintingStyle.fill; const double currentHeight = 5.0; const double arrowWidth = 8; const double padding = 0.1; 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; - double locationY = _isOutOfBoundInTop - ? centerTemplateY - (templateHeight / 2) - arrowHeight - : centerTemplateY + templateHeight / 2; + final num centerTemplateY = + _isOutOfBoundInTop + ? location.dy + currentSize.height + templateHeight / 2 + padding + : location.dy - templateHeight / 2 - padding; + double locationY = + _isOutOfBoundInTop + ? centerTemplateY - (templateHeight / 2) - arrowHeight + : centerTemplateY + templateHeight / 2; final num centerX = location.dx + currentSize.width / 2; final Path path = Path(); final RRect rect = RRect.fromLTRBAndCorners( - templateLocation.dx, - templateLocation.dy, - templateLocation.dx + _templateSize.width, - templateLocation.dy + _templateSize.height, - topLeft: Radius.circular(_borderRadius), - topRight: Radius.circular(_borderRadius), - bottomRight: Radius.circular(_borderRadius), - bottomLeft: Radius.circular(_borderRadius)); + templateLocation.dx, + templateLocation.dy, + templateLocation.dx + _templateSize.width, + templateLocation.dy + _templateSize.height, + topLeft: Radius.circular(_borderRadius), + topRight: Radius.circular(_borderRadius), + bottomRight: Radius.circular(_borderRadius), + bottomLeft: Radius.circular(_borderRadius), + ); bool isVTypeArrow = true; final String side = (centerX < rect.left + rect.width / 2) ? 'left' : 'right'; @@ -618,11 +669,17 @@ class TooltipRenderBox extends RenderShiftedBox { locationY += arrowHeight - padding; } path.moveTo(rect.left + rect.tlRadiusX, rect.top); - path.arcToPoint(Offset(rect.left, rect.top + rect.tlRadiusY), - radius: rect.tlRadius, clockwise: false); + path.arcToPoint( + Offset(rect.left, rect.top + rect.tlRadiusY), + radius: rect.tlRadius, + clockwise: false, + ); path.lineTo(rect.left, rect.bottom - rect.blRadiusY); - path.arcToPoint(Offset(rect.left + rect.blRadiusX, rect.bottom), - radius: rect.blRadius, clockwise: false); + path.arcToPoint( + Offset(rect.left + rect.blRadiusX, rect.bottom), + radius: rect.blRadius, + clockwise: false, + ); if ((centerX > rect.left + rect.blRadiusX + arrowWidth / 2) && (centerX < rect.right - rect.brRadiusX - arrowWidth / 2)) { path.lineTo(centerX - arrowWidth / 2, rect.bottom); @@ -636,19 +693,26 @@ class TooltipRenderBox extends RenderShiftedBox { //bottom arrow path.lineTo(centerX.toDouble(), locationY); path.lineTo( - isVTypeArrow - ? (centerX + arrowWidth / 2) - : side == 'left' - ? (rect.left + rect.blRadiusX + arrowWidth * 2) - : (rect.right - rect.brRadiusX), - rect.bottom); + isVTypeArrow + ? (centerX + arrowWidth / 2) + : side == 'left' + ? (rect.left + rect.blRadiusX + arrowWidth * 2) + : (rect.right - rect.brRadiusX), + rect.bottom, + ); } path.lineTo(rect.right - rect.brRadiusX, rect.bottom); - path.arcToPoint(Offset(rect.right, rect.bottom - rect.brRadiusY), - radius: rect.brRadius, clockwise: false); + path.arcToPoint( + Offset(rect.right, rect.bottom - rect.brRadiusY), + radius: rect.brRadius, + clockwise: false, + ); path.lineTo(rect.right, rect.top + rect.trRadiusY); - path.arcToPoint(Offset(rect.right - rect.brRadiusX, rect.top), - radius: rect.trRadius, clockwise: false); + path.arcToPoint( + Offset(rect.right - rect.brRadiusX, rect.top), + radius: rect.trRadius, + clockwise: false, + ); if (isVTypeArrow) { path.lineTo(centerX + arrowWidth / 2, rect.top); } else { @@ -660,12 +724,13 @@ class TooltipRenderBox extends RenderShiftedBox { //top arrow path.lineTo(centerX.toDouble(), locationY); path.lineTo( - isVTypeArrow - ? (centerX - arrowWidth / 2) - : side == 'left' - ? (rect.left + rect.blRadiusX) - : (rect.right - rect.brRadiusX - arrowWidth * 2), - rect.top); + isVTypeArrow + ? (centerX - arrowWidth / 2) + : side == 'left' + ? (rect.left + rect.blRadiusX) + : (rect.right - rect.brRadiusX - arrowWidth * 2), + rect.top, + ); } path.lineTo(rect.left + rect.tlRadiusX, rect.top); @@ -688,15 +753,17 @@ class TooltipRenderBox extends RenderShiftedBox { _markerSize = 0; _totalWidth = _boundaryRect.left + _boundaryRect.width; //ignore: prefer_final_locals - TextStyle _textStyle = _tooltip.textStyle; - final TextStyle textStyle = - _textStyle.copyWith(color: _textStyle.color ?? _tooltip.labelColor); + TextStyle style = _tooltip.textStyle; + final TextStyle textStyle = style.copyWith( + color: style.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); + final TextStyle headerTextStyle = style.copyWith( + color: style.color ?? _tooltip.labelColor, + fontWeight: FontWeight.bold, + ); headerTextWidth = measureText(_header!, headerTextStyle).width; headerTextHeight = measureText(_header!, headerTextStyle).height + 10; width = width > headerTextWidth ? width : headerTextWidth; @@ -713,33 +780,42 @@ class TooltipRenderBox extends RenderShiftedBox { _y != null && (_inversePadding != null || _normalPadding != null) && (_stringValue != '' || _header != '')) { - final Rect backRect = - _calculateBackgroundRect(canvas, height, width, headerTextHeight); + 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); + 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 for default tooltip mode Rect _calculateBackgroundRect( - Canvas canvas, double height, double width, double headerTextHeight) { + Canvas canvas, + double height, + double width, + double headerTextHeight, + ) { double widthPadding = 15; if (_tooltip.canShowMarker && _tooltipState.needMarker) { _markerSize = 5; @@ -747,19 +823,29 @@ class TooltipRenderBox extends RenderShiftedBox { } 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); + _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); + _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; @@ -776,25 +862,29 @@ class TooltipRenderBox extends RenderShiftedBox { _nosePointY = rect.top - (_normalPadding ?? 0); _nosePointX = rect.left; final double tooltipRightEnd = _x! + (rect.width / 2); - _xPos = _xPos! < _boundaryRect.left - ? _boundaryRect.left - : tooltipRightEnd > _totalWidth + _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) + + _yPos = + ((_y! >= _boundaryRect.top ? _y! : _boundaryRect.top) + _pointerLength / 2) + (_inversePadding ?? 0); _nosePointX = rect.left; - _nosePointY = (_y! >= _boundaryRect.top ? _y! : _boundaryRect.top) + + _nosePointY = + (_y! >= _boundaryRect.top ? _y! : _boundaryRect.top) + (_inversePadding ?? 0); final double tooltipRightEnd = _x! + (rect.width / 2); - _xPos = _xPos! < _boundaryRect.left - ? _boundaryRect.left - : tooltipRightEnd > _totalWidth + _xPos = + _xPos! < _boundaryRect.left + ? _boundaryRect.left + : tooltipRightEnd > _totalWidth ? _totalWidth - rect.width : _xPos; } @@ -809,20 +899,21 @@ class TooltipRenderBox extends RenderShiftedBox { /// This method renders the tooltip background on which the content is to be /// displayed 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) { + 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(); @@ -835,10 +926,11 @@ class TooltipRenderBox extends RenderShiftedBox { } 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); + 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; @@ -849,11 +941,24 @@ class TooltipRenderBox extends RenderShiftedBox { topLeft: Radius.circular(borderRadius), topRight: Radius.circular(borderRadius), ); - _drawTooltipPath(canvas, tooltipRect, rect, backgroundPath, isTop, isLeft, - isRight, startX, endX, animationFactor, xPosition, yPosition); + _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) ?? + color: + _tooltip.textStyle.color?.withValues(alpha: _tooltip.opacity) ?? _tooltip.labelColor, fontSize: (_tooltip.textStyle.fontSize ?? 12.0) * animationFactor, ); @@ -864,8 +969,11 @@ class TooltipRenderBox extends RenderShiftedBox { _tooltipState.needMarker && _markerTypes.isNotEmpty) { if (_markerTypes.length == 1) { - final Offset markerPoint = - _getMarkerPosition(result, markerSize, tooltipRect); + final Offset markerPoint = _getMarkerPosition( + result, + markerSize, + tooltipRect, + ); _drawMarkers(markerPoint, canvas, animationFactor, 0); } else { double height = 0; @@ -876,10 +984,11 @@ class TooltipRenderBox extends RenderShiftedBox { str += textValues[i]; final Size result1 = measureText(str, textStyle); final Offset markerPoint = Offset( - _tooltipState._isRtl - ? tooltipRect.right - tooltipRect.width / 2 + result.width / 2 - : tooltipRect.left + tooltipRect.width / 2 - result.width / 2, - (_markerPointY + height) - markerSize); + _tooltipState._isRtl + ? tooltipRect.right - tooltipRect.width / 2 + result.width / 2 + : tooltipRect.left + tooltipRect.width / 2 - result.width / 2, + (_markerPointY + height) - markerSize, + ); textSize = result1; height += textSize.height; if (_markerTypes[i] != null) { @@ -894,93 +1003,124 @@ class TooltipRenderBox extends RenderShiftedBox { /// To get the marker offset. Offset _getMarkerPosition( - Size textSize, double markerSize, RRect tooltipRect) => - Offset( - _tooltipState._isRtl - ? tooltipRect.right - tooltipRect.width / 2 + textSize.width / 2 - : tooltipRect.left + tooltipRect.width / 2 - textSize.width / 2, - ((tooltipRect.top + tooltipRect.height) - textSize.height / 2) - - markerSize); + Size textSize, + double markerSize, + RRect tooltipRect, + ) => Offset( + _tooltipState._isRtl + ? tooltipRect.right - tooltipRect.width / 2 + textSize.width / 2 + : tooltipRect.left + tooltipRect.width / 2 - textSize.width / 2, + ((tooltipRect.top + tooltipRect.height) - textSize.height / 2) - markerSize, + ); /// This method renders the tooltip marker shapes at the specific line indices /// of the marker paint list void _drawMarkers( - Offset markerPoint, Canvas canvas, double animationFactor, int i) { + 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), + 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; + _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]!); // ignore: omit_local_variable_types final Paint markerBorderPaint = Paint(); - markerBorderPaint.color = Colors.white.withOpacity(_tooltip.opacity); + markerBorderPaint.color = Colors.white.withValues( + alpha: _tooltip.opacity, + ); markerBorderPaint.strokeWidth = 1; markerBorderPaint.style = PaintingStyle.stroke; canvas.drawPath(markerPath, markerBorderPaint); } else { _markerSize *= 2 * animationFactor; // ignore: omit_local_variable_types - final Rect positionRect = Rect.fromLTWH(markerPoint.dx - _markerSize / 2, - markerPoint.dy - _markerSize / 2, _markerSize, _markerSize); + 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); + canvas: canvas, + image: _markerImages[i], + rect: positionRect, + fit: BoxFit.fill, + ); } } /// This method renders the tooltip rect and arrow for default tooltip mode 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) { + 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.'); + 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, + 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, + 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; @@ -989,18 +1129,21 @@ class TooltipRenderBox extends RenderShiftedBox { 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; + final Paint fillPaint = + Paint() + ..color = (_tooltip.color).withValues(alpha: _tooltip.opacity) + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.fill; + + final Paint strokePaint = + Paint() + ..color = + _tooltip.borderColor == Colors.transparent + ? Colors.transparent + : _tooltip.borderColor.withValues(alpha: _tooltip.opacity) + ..strokeCap = StrokeCap.butt + ..style = PaintingStyle.stroke + ..strokeWidth = _tooltip.borderWidth; _tooltip.borderWidth == 0 ? strokePaint.color = Colors.transparent : strokePaint.color = strokePaint.color; @@ -1009,11 +1152,19 @@ class TooltipRenderBox extends RenderShiftedBox { 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( + _arrowPath, + _tooltip.shadowColor ?? fillPaint.color, + _tooltip.elevation, + true, + ); } - canvas.drawShadow(tooltipPath, _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) { @@ -1026,36 +1177,46 @@ class TooltipRenderBox extends RenderShiftedBox { /// This method renders the tooltip header text, content text and the divider /// line for default tooltip mode - void _drawTooltipText(Canvas canvas, RRect tooltipRect, TextStyle textStyle, - Size result, double animationFactor) { + void _drawTooltipText( + Canvas canvas, + RRect tooltipRect, + TextStyle textStyle, + Size result, + double animationFactor, + ) { const double padding = 10; - final int _maxLinesOfTooltipContent = getMaxLinesContent(_stringValue); + final int maxLinesOfTooltipContent = getMaxLinesContent(_stringValue); if (_header!.isNotEmpty) { final TextStyle headerTextStyle = _tooltip.textStyle.copyWith( - color: textStyle.color?.withOpacity(_tooltip.opacity) ?? + color: + textStyle.color?.withValues(alpha: _tooltip.opacity) ?? _tooltip.labelColor, fontSize: (textStyle.fontSize ?? 12) * animationFactor, fontWeight: FontWeight.bold, ); final Size headerResult = measureText(_header!, headerTextStyle); - _markerPointY = tooltipRect.top + + _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, - maxLines: maxLinesOfHeader); + _tooltip, + canvas, + _header!, + Offset( + (tooltipRect.left + tooltipRect.width / 2) - headerResult.width / 2, + tooltipRect.top + padding / 2, + ), + headerTextStyle, + maxLines: maxLinesOfHeader, + ); final Paint dividerPaint = Paint(); - dividerPaint.color = _tooltip.labelColor.withOpacity(_tooltip.opacity); + dividerPaint.color = _tooltip.labelColor.withValues( + alpha: _tooltip.opacity, + ); dividerPaint.strokeWidth = 0.5 * animationFactor; dividerPaint.style = PaintingStyle.stroke; num lineOffset = 0; @@ -1068,25 +1229,45 @@ class TooltipRenderBox extends RenderShiftedBox { } 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); + Offset( + tooltipRect.left + padding - lineOffset, + tooltipRect.top + headerResult.height + padding, + ), + Offset( + tooltipRect.right - padding - lineOffset, + tooltipRect.top + headerResult.height + padding, + ), + dividerPaint, + ); } } _renderTooltipText( - canvas, tooltipRect, result, textStyle, _maxLinesOfTooltipContent); + canvas, + tooltipRect, + result, + textStyle, + maxLinesOfTooltipContent, + ); } - void _renderTooltipText(Canvas canvas, RRect tooltipRect, Size textSize, - TextStyle textStyle, int maxLines) { + void _renderTooltipText( + Canvas canvas, + RRect tooltipRect, + Size textSize, + TextStyle textStyle, + int maxLines, + ) { if (_tooltipState._isRtl && _stringValue!.contains('\n')) { - _drawMultiLineRtlText(_tooltip, canvas, _stringValue!, - _getTextPosition(textSize, tooltipRect, maxLines), textStyle, - markerSize: markerSize, - tooltipRect: tooltipRect, - totalTextSize: textSize); + _drawMultiLineRtlText( + _tooltip, + canvas, + _stringValue!, + _getTextPosition(textSize, tooltipRect, maxLines), + textStyle, + markerSize: markerSize, + tooltipRect: tooltipRect, + totalTextSize: textSize, + ); } else { _drawText( _tooltip, @@ -1106,7 +1287,8 @@ class TooltipRenderBox extends RenderShiftedBox { textOffsetX = tooltipRect.left + tooltipRect.width / 2 - textSize.width / 2; } else { - textOffsetX = tooltipRect.right - + textOffsetX = + tooltipRect.right - tooltipRect.width / 2 - textSize.width / 2 - 2 * markerSize; @@ -1114,34 +1296,46 @@ class TooltipRenderBox extends RenderShiftedBox { } else { textOffsetX = (tooltipRect.left + 2 * markerSize + tooltipRect.width / 2) - - textSize.width / 2; + textSize.width / 2; } return Offset( - textOffsetX, - _header!.isNotEmpty - ? (tooltipRect.top + tooltipRect.height) - textSize.height - 5 - : (tooltipRect.top + tooltipRect.height / 2) - textSize.height / 2); + textOffsetX, + _header!.isNotEmpty + ? (tooltipRect.top + tooltipRect.height) - textSize.height - 5 + : (tooltipRect.top + tooltipRect.height / 2) - textSize.height / 2, + ); } - void _drawMultiLineRtlText(SfTooltip tooltip, Canvas canvas, String text, - Offset point, TextStyle style, - {double? markerSize, RRect? tooltipRect, Size? totalTextSize}) { + void _drawMultiLineRtlText( + SfTooltip tooltip, + Canvas canvas, + String text, + Offset point, + TextStyle style, { + double? markerSize, + RRect? tooltipRect, + Size? totalTextSize, + }) { final List textCollections = text.split('\n'); - final double rectEndPointBeforeMarker = _getMarkerPosition( - totalTextSize ?? Size.zero, - markerSize ?? 0.0, - tooltipRect ?? RRect.zero) - .dx - + final double rectEndPointBeforeMarker = + _getMarkerPosition( + totalTextSize ?? Size.zero, + markerSize ?? 0.0, + tooltipRect ?? RRect.zero, + ).dx - (2 * (markerSize ?? 0.0)); for (int count = 0; count < textCollections.length; count++) { final Size currentTextSize = measureText(textCollections[count], style); _drawText( - tooltip, - canvas, - textCollections[count], - Offset(rectEndPointBeforeMarker - currentTextSize.width, - point.dy + (currentTextSize.height * count)), - style); + tooltip, + canvas, + textCollections[count], + Offset( + rectEndPointBeforeMarker - currentTextSize.width, + point.dy + (currentTextSize.height * count), + ), + style, + ); } } @@ -1172,19 +1366,22 @@ class TooltipRenderBox extends RenderShiftedBox { } if (kIsWeb) { if (_animationFactor < 0.5) { - style = - style.copyWith(color: style.color!.withOpacity(_animationFactor)); + style = style.copyWith( + color: style.color!.withValues(alpha: _animationFactor), + ); } else if (_animationFactor <= 1) { - style = - style.copyWith(color: style.color!.withOpacity(tooltip.opacity)); + style = style.copyWith( + color: style.color!.withValues(alpha: tooltip.opacity), + ); } } final TextSpan span = TextSpan(text: text, style: style); final TextPainter tp = TextPainter( - text: span, - textDirection: TextDirection.ltr, - textAlign: tooltipTextAlign, - maxLines: maxLines ?? 1); + text: span, + textDirection: TextDirection.ltr, + textAlign: tooltipTextAlign, + maxLines: maxLines ?? 1, + ); tp.layout(); canvas.save(); canvas.translate(pointX, point.dy); @@ -1209,7 +1406,8 @@ class TooltipRenderBox extends RenderShiftedBox { } if (tooltipRect.left + tooltipRect.width > bounds.left + offset.dx + bounds.width) { - left = (bounds.left + bounds.width + offset.dx) - + left = + (bounds.left + bounds.width + offset.dx) - tooltipRect.width - padding; } @@ -1224,7 +1422,11 @@ class TooltipRenderBox extends RenderShiftedBox { /// This method returns the path of marker shapes at the position at which the /// marker needs to be rendered Path _getMarkerShapesPath( - DataMarkerType markerType, Offset position, dynamic image, Size size) { + DataMarkerType markerType, + Offset position, + dynamic image, + Size size, +) { final Path path = Path(); switch (markerType) { case DataMarkerType.circle: @@ -1248,19 +1450,34 @@ Path _getMarkerShapesPath( case DataMarkerType.verticalLine: { drawVerticalLine( - path, position.dx, position.dy, size.width, size.height); + path, + position.dx, + position.dy, + size.width, + size.height, + ); } break; case DataMarkerType.invertedTriangle: { drawInvertedTriangle( - path, position.dx, position.dy, size.width, size.height); + path, + position.dx, + position.dy, + size.width, + size.height, + ); } break; case DataMarkerType.horizontalLine: { drawHorizontalLine( - path, position.dx, position.dy, size.width, size.height); + path, + position.dx, + position.dy, + size.width, + size.height, + ); } break; case DataMarkerType.diamond: diff --git a/packages/syncfusion_flutter_core/lib/src/utils/helper.dart b/packages/syncfusion_flutter_core/lib/src/utils/helper.dart index fdea113bf..794b5d779 100644 --- a/packages/syncfusion_flutter_core/lib/src/utils/helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/utils/helper.dart @@ -18,7 +18,7 @@ class TooltipRenderArgs { Offset? location; } -/// Used to align the tootip content. +/// Used to align the tooltip content. /// /// Tooltip alignment supports the below alignments. enum TooltipAlignment { @@ -29,7 +29,7 @@ enum TooltipAlignment { center, ///- TooltipAlignment.far, will align the tooltip content far from center. - far + far, } /// Data marker shapes. @@ -38,7 +38,7 @@ enum TooltipAlignment { /// 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. + ///- DataMarkerType.circle, will render marker shape circle. circle, ///- DataMarkerType.rectangle, will render marker shape rectangle. @@ -73,16 +73,17 @@ enum DataMarkerType { /// Draw the circle shape marker 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); + Rect.fromLTRB(x - width / 2, y - height / 2, x + width / 2, y + height / 2), + 0.0, + 2 * math.pi, + ); } /// Draw the Rectangle shape marker 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)); + path.addRect( + Rect.fromLTRB(x - width / 2, y - height / 2, x + width / 2, y + height / 2), + ); } ///Draw the Pentagon shape marker @@ -102,14 +103,24 @@ void drawPentagon(Path path, double x, double y, double width, double height) { ///Draw the Vertical line shape marker void drawVerticalLine( - Path path, double x, double y, double width, double height) { + 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 void drawInvertedTriangle( - Path path, double x, double y, double width, double height) { + Path path, + double x, + double y, + double width, + double height, +) { path.moveTo(x + width / 2, y - height / 2); path.lineTo(x, y + height / 2); @@ -120,7 +131,12 @@ void drawInvertedTriangle( ///Draw the Horizontal line shape marker void drawHorizontalLine( - Path path, double x, double y, double width, double height) { + Path path, + double x, + double y, + double width, + double height, +) { path.moveTo(x - width / 2, y); path.lineTo(x + width / 2, y); } @@ -144,13 +160,14 @@ void drawTriangle(Path path, double x, double y, double width, double height) { path.close(); } -/// This method measures the size for given text and textstyle +/// This method measures the size for given text and text style. 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)); + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + text: TextSpan(text: textValue, style: textStyle), + ); textPainter.layout(); if (angle != null) { @@ -165,36 +182,41 @@ Size measureText(String textValue, TextStyle textStyle, [int? angle]) { /// 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 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; + 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); + topLeft = transform(rotatorMatrix, topLeft); + topRight = transform(rotatorMatrix, topRight); + bottomLeft = transform(rotatorMatrix, bottomLeft); + bottomRight = transform(rotatorMatrix, bottomRight); final List rotOffsets = [ - _topLeft, - _topRight, - _bottomLeft, - _bottomRight + 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 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), @@ -214,10 +236,7 @@ vector.Vector2 offsetToVector2(Offset 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, -) { +Offset transform(vector.Matrix2 matrix, Offset offset) { return vector2ToOffset(matrix * offsetToVector2(offset)); } diff --git a/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart b/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart index 104513863..556b03785 100644 --- a/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart @@ -177,389 +177,440 @@ enum ShapeMarkerType { pyramidSeries, /// ShapeMarkerType.funnelSeries which draws the funnel series marker. - funnelSeries + funnelSeries, } /// Draws the different marker shapes. -void paint( - {required Canvas canvas, - required Rect rect, - required ShapeMarkerType shapeType, - required Paint paint, - ShapeMarkerType? overlayMarkerType, - Path? path, - double? elevation, - Color? elevationColor, - Paint? borderPaint, - double? degree, - double? startAngle, - double? endAngle}) { +void paint({ + required Canvas canvas, + required Rect rect, + required ShapeMarkerType shapeType, + required Paint paint, + ShapeMarkerType? overlayMarkerType, + Path? path, + double? elevation, + Color? elevationColor, + Paint? borderPaint, + double? degree, + double? startAngle, + double? endAngle, +}) { _processShapes( - canvas: canvas, - rect: rect, - shapeType: shapeType, - paint: paint, - path: path ?? Path(), - borderPaint: borderPaint, - isNeedToReturnPath: false, - elevation: elevation, - elevationColor: elevationColor, - overlayMarkerType: overlayMarkerType, - degree: degree, - startAngle: startAngle, - endAngle: endAngle); + canvas: canvas, + rect: rect, + shapeType: shapeType, + paint: paint, + path: path ?? Path(), + borderPaint: borderPaint, + isNeedToReturnPath: false, + elevation: elevation, + elevationColor: elevationColor, + overlayMarkerType: overlayMarkerType, + degree: degree, + startAngle: startAngle, + endAngle: endAngle, + ); } /// Get the various shape path -Path getShapesPath( - {Canvas? canvas, - Paint? paint, - Paint? borderPaint, - required Rect rect, - required ShapeMarkerType shapeType, - Path? path, - double? pentagonRotation = -pi / 2, - double? radius, - double? degree, - double? startAngle, - double? endAngle}) { +Path getShapesPath({ + Canvas? canvas, + Paint? paint, + Paint? borderPaint, + required Rect rect, + required ShapeMarkerType shapeType, + Path? path, + double? pentagonRotation = -pi / 2, + double? radius, + double? degree, + double? startAngle, + double? endAngle, +}) { return _processShapes( - canvas: canvas ?? Canvas(PictureRecorder()), - paint: paint ?? Paint(), - borderPaint: borderPaint, - rect: rect, - path: path ?? Path(), - shapeType: shapeType, - isNeedToReturnPath: true, - pentagonRotation: pentagonRotation, - radius: radius, - degree: degree, - startAngle: startAngle, - endAngle: endAngle); + canvas: canvas ?? Canvas(PictureRecorder()), + paint: paint ?? Paint(), + borderPaint: borderPaint, + rect: rect, + path: path ?? Path(), + shapeType: shapeType, + isNeedToReturnPath: true, + pentagonRotation: pentagonRotation, + radius: radius, + degree: degree, + startAngle: startAngle, + endAngle: endAngle, + ); } -Path _processShapes( - {required Canvas canvas, - required Rect rect, - required ShapeMarkerType shapeType, - required Paint paint, - required bool isNeedToReturnPath, - required Path path, - ShapeMarkerType? overlayMarkerType, - double? elevation, - Color? elevationColor, - Paint? borderPaint, - double? pentagonRotation = -pi / 2, - double? radius, - double? degree, - double? startAngle, - double? endAngle}) { +Paint _lineTypePaint(Paint fillPaint, {Paint? strokePaint}) { + final Paint paint = Paint()..style = PaintingStyle.stroke; + if (strokePaint != null) { + paint + ..color = strokePaint.color + ..strokeWidth = strokePaint.strokeWidth + ..shader = strokePaint.shader ?? fillPaint.shader; + } + + if (paint.color == Colors.transparent) { + paint.color = fillPaint.color; + } + return paint; +} + +Path _processShapes({ + required Canvas canvas, + required Rect rect, + required ShapeMarkerType shapeType, + required Paint paint, + required bool isNeedToReturnPath, + required Path path, + ShapeMarkerType? overlayMarkerType, + double? elevation, + Color? elevationColor, + Paint? borderPaint, + double? pentagonRotation = -pi / 2, + double? radius, + double? degree, + double? startAngle, + double? endAngle, +}) { switch (shapeType) { case ShapeMarkerType.circle: return _processCircleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.rectangle: return _processRectangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.diamond: return _processDiamondShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.triangle: return _processTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.invertedTriangle: return _processInvertedTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.verticalTriangle: return _processVerticalTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.verticalInvertedTriangle: return _processVerticalInvertedTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.pentagon: return _processPentagonShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - rotation: pentagonRotation, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + rotation: pentagonRotation, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint, + ); case ShapeMarkerType.verticalLine: return _processVerticalLineShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + ); case ShapeMarkerType.horizontalLine: return _processHorizontalLineShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null); + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + ); case ShapeMarkerType.lineSeries: case ShapeMarkerType.fastLineSeries: case ShapeMarkerType.stackedLineSeries: case ShapeMarkerType.stackedLine100Series: return _processLineShape( - canvas: canvas, - path: path, - rect: rect, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath, - isNeedMarker: true, - overlayMarkerType: overlayMarkerType, - isDashArray: false); + canvas: canvas, + path: path, + rect: rect, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isNeedMarker: true, + overlayMarkerType: overlayMarkerType, + isDashArray: false, + ); case ShapeMarkerType.lineSeriesWithDashArray: case ShapeMarkerType.fastLineSeriesWithDashArray: case ShapeMarkerType.stackedLineSeriesWithDashArray: case ShapeMarkerType.stackedLine100SeriesWithDashArray: return _processLineShape( - canvas: canvas, - path: path, - rect: rect, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath, - isNeedMarker: true, - overlayMarkerType: overlayMarkerType, - isDashArray: true); + canvas: canvas, + path: path, + rect: rect, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isNeedMarker: true, + overlayMarkerType: overlayMarkerType, + isDashArray: true, + ); case ShapeMarkerType.splineSeries: return _processSplineShape( - canvas: canvas, - rect: rect, - path: path, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath, - isDashArray: false); + canvas: canvas, + rect: rect, + path: path, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: false, + ); case ShapeMarkerType.splineSeriesWithDashArray: return _processSplineShape( - canvas: canvas, - rect: rect, - path: path, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath, - isDashArray: true); + canvas: canvas, + rect: rect, + path: path, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: true, + ); case ShapeMarkerType.splineAreaSeries: case ShapeMarkerType.splineRangeAreaSeries: return _processSplineAreaShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.areaSeries: case ShapeMarkerType.stackedAreaSeries: case ShapeMarkerType.rangeAreaSeries: case ShapeMarkerType.stackedArea100Series: return _processAreaShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.stepAreaSeries: return _processStepAreaShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.stepLineSeries: return _processStepLineShape( - canvas: canvas, - rect: rect, - path: path, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath, - isDashArray: false); + canvas: canvas, + rect: rect, + path: path, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: false, + ); case ShapeMarkerType.stepLineSeriesWithDashArray: return _processStepLineShape( - canvas: canvas, - rect: rect, - path: path, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath, - isDashArray: true); + canvas: canvas, + rect: rect, + path: path, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: true, + ); case ShapeMarkerType.bubbleSeries: return _processBubbleShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.columnSeries: case ShapeMarkerType.stackedColumnSeries: case ShapeMarkerType.stackedColumn100Series: case ShapeMarkerType.rangeColumnSeries: case ShapeMarkerType.histogramSeries: return _processColumnShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.barSeries: case ShapeMarkerType.stackedBarSeries: case ShapeMarkerType.stackedBar100Series: return _processBarShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.hiloSeries: return _processHiloShape( - canvas: canvas, - rect: rect, - path: path, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.hiloOpenCloseSeries: case ShapeMarkerType.candleSeries: return _processHiloOpenCloseShape( - canvas: canvas, - rect: rect, - path: path, - paint: borderPaint, - shaderPaint: paint.shader != null ? paint : null, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: _lineTypePaint(paint, strokePaint: borderPaint), + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.waterfallSeries: case ShapeMarkerType.boxAndWhiskerSeries: return _processWaterfallShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.pieSeries: return _processPieShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.doughnutSeries: return _processDoughnutShape( - canvas: canvas, - rect: rect, - radius: radius, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + radius: radius, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.radialBarSeries: return _processRadialBarShape( - rect: rect, - canvas: canvas, - radius: radius, - path: path, - paint: paint, - borderPaint: borderPaint, - degree: degree, - startAngle: startAngle, - endAngle: endAngle, - isNeedToReturnPath: isNeedToReturnPath); + rect: rect, + canvas: canvas, + radius: radius, + path: path, + paint: paint, + borderPaint: borderPaint, + degree: degree, + startAngle: startAngle, + endAngle: endAngle, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.pyramidSeries: return _processPyramidShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.funnelSeries: return _processFunnelShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath, + ); case ShapeMarkerType.image: return Path(); } } /// Draw the circle shape marker. -Path _processCircleShape( - {required Canvas canvas, - required Rect rect, - required Paint paint, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - Paint? borderPaint}) { +Path _processCircleShape({ + required Canvas canvas, + required Rect rect, + required Paint paint, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + Paint? borderPaint, +}) { path.addOval(rect); if (isNeedToReturnPath) { @@ -572,22 +623,25 @@ Path _processCircleShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } /// Draw the rectangle shape marker. -Path _processRectangleShape( - {required Canvas canvas, - required Rect rect, - required Paint paint, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - Paint? borderPaint}) { +Path _processRectangleShape({ + required Canvas canvas, + required Rect rect, + required Paint paint, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + Paint? borderPaint, +}) { path.addRect(rect); if (isNeedToReturnPath) { @@ -600,22 +654,25 @@ Path _processRectangleShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } /// Draw the inverted triangle shape marker. -Path _processInvertedTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { +Path _processInvertedTriangleShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, +}) { path.moveTo(rect.left, rect.top); path.lineTo(rect.left + rect.width, rect.top); path.lineTo(rect.left + (rect.width / 2), rect.top + rect.height); @@ -631,7 +688,9 @@ Path _processInvertedTriangleShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -639,15 +698,16 @@ Path _processInvertedTriangleShape( } /// Draw the triangle shape marker. -Path _processTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { +Path _processTriangleShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, +}) { path.moveTo(rect.left + (rect.width / 2), rect.top); path.lineTo(rect.left, rect.top + rect.height); path.lineTo(rect.left + rect.width, rect.top + rect.height); @@ -663,22 +723,25 @@ Path _processTriangleShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } ///Draw the triangle shape marker -Path _processVerticalTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { +Path _processVerticalTriangleShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, +}) { path.moveTo(rect.left, rect.top + (rect.height / 2)); path.lineTo(rect.left + rect.width, rect.top); path.lineTo(rect.left + rect.width, rect.top + rect.height); @@ -694,22 +757,25 @@ Path _processVerticalTriangleShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } ///Draw the vertical inverted triangle shape marker -Path _processVerticalInvertedTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { +Path _processVerticalInvertedTriangleShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, +}) { path.moveTo(rect.left, rect.top); path.lineTo(rect.left + rect.width, rect.top + (rect.height / 2)); path.lineTo(rect.left, rect.top + rect.height); @@ -725,22 +791,25 @@ Path _processVerticalInvertedTriangleShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } /// Draw the diamond shape marker. -Path _processDiamondShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { +Path _processDiamondShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, +}) { path.moveTo(rect.left + rect.width / 2.0, rect.top); path.lineTo(rect.left, rect.top + rect.height / 2.0); path.lineTo(rect.left + rect.width / 2.0, rect.top + rect.height); @@ -757,7 +826,9 @@ Path _processDiamondShape( canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -765,16 +836,17 @@ Path _processDiamondShape( } ///Draw the pentagon shape marker -Path _processPentagonShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint, - double? rotation}) { +Path _processPentagonShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, + double? rotation, +}) { const int numberOfSides = 5; final double left = rect.left + rect.width / 2; final double top = rect.top + rect.height / 2; @@ -795,20 +867,23 @@ Path _processPentagonShape( canvas.drawShadow(path, elevationColor, elevation, true); } canvas.drawPath(path, paint); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } /// Draw the vertical line shape marker. -Path _processVerticalLineShape( - {required Canvas canvas, - required bool isNeedToReturnPath, - required Path path, - required Rect rect, - required Paint? paint, - Paint? shaderPaint}) { +Path _processVerticalLineShape({ + required Canvas canvas, + required bool isNeedToReturnPath, + required Path path, + required Rect rect, + required Paint? paint, + Paint? shaderPaint, +}) { final double left = rect.left + rect.width / 2; final double top = rect.top + rect.height / 2; @@ -828,13 +903,14 @@ Path _processVerticalLineShape( } /// Draw the horizontal line shape marker. -Path _processHorizontalLineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - required Paint? paint, - Paint? shaderPaint}) { +Path _processHorizontalLineShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + required Paint? paint, + Paint? shaderPaint, +}) { final double left = rect.left + rect.width / 2; final double top = rect.top + rect.height / 2; @@ -853,14 +929,15 @@ Path _processHorizontalLineShape( } /// Draw the step line series type. -Path _processStepLineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? shaderPaint, - bool? isDashArray}) { +Path _processStepLineShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, + bool? isDashArray, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -885,24 +962,34 @@ Path _processStepLineShape( if (paint != null) { paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; canvas.drawPath( - isDashArray! - ? _processDashPath(path, - dashArray: _CircularIntervalList([3, 2])) - : path, - paint..style = PaintingStyle.stroke); + isDashArray! + ? _processDashPath( + path, + dashArray: _CircularIntervalList([3, 2]), + ) + : path, + paint..style = PaintingStyle.stroke, + ); } return path; } /// Draw the pie series type. -Path _processPieShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processPieShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { + rect = Rect.fromLTWH( + rect.left, + rect.top + 1, + rect.width - 1, + rect.height - 1, + ); final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -911,13 +998,21 @@ Path _processPieShape( final double r = 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), + _degreesToRadians(270), + 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 + 1, y - 1), radius: r), + _degreesToRadians(0), + _degreesToRadians(-90), + false, + ); path.close(); if (isNeedToReturnPath) { @@ -925,7 +1020,9 @@ Path _processPieShape( } canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -933,101 +1030,187 @@ Path _processPieShape( } /// Draw the doughnut series type. -Path _processDoughnutShape( - {required Canvas canvas, - required Rect rect, - required Path path, - required bool isNeedToReturnPath, - double? radius, - Paint? paint, - Paint? borderPaint}) { +Path _processDoughnutShape({ + required Canvas canvas, + required Rect rect, + required Path path, + required bool isNeedToReturnPath, + double? radius, + Paint? paint, + Paint? borderPaint, +}) { + rect = Rect.fromLTWH( + rect.left, + rect.top + 1, + rect.width - 1, + rect.height - 1, + ); final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; late Path path1, path2; radius ??= (rect.width + rect.height) / 2; - + final bool hasBorder = + borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0; if (isNeedToReturnPath) { - if (borderPaint != null) { + if (hasBorder) { path1 = _getArcPath( - path, radius / 4, radius / 2, Offset(x, y), 0, 270, 270, true); + path, + radius / 4, + radius / 2, + Offset(x, y), + 0, + 270, + 270, + true, + ); } else { - path2 = _getArcPath(path, radius / 4, radius / 2, Offset(x + 1, y - 1), - -5, -85, -85, true); + path2 = _getArcPath( + path, + radius / 4, + radius / 2, + Offset(x + 1, y - 1), + 0, + -90, + -90, + true, + ); } return path; } path1 = _getArcPath( - path, radius / 4, radius / 2, Offset(x, y), 0, 270, 270, true); + path, + radius / 4, + radius / 2, + Offset(x, y), + 0, + 270, + 270, + true, + ); path2 = _getArcPath( - Path(), radius / 4, radius / 2, Offset(x + 1, y - 1), -5, -85, -85, true); + Path(), + radius / 4, + radius / 2, + Offset(x + 1, y - 1), + 0, + -90, + -90, + true, + ); canvas.drawPath(path1, paint!); - if (borderPaint != null) { + if (hasBorder) { canvas.drawPath( - path1, borderPaint..color = Colors.grey.shade300.withOpacity(0.5)); + path1, + borderPaint..color = Colors.grey.shade300.withValues(alpha: 0.5), + ); } canvas.drawPath(path2, paint); - if (borderPaint != null) { + if (hasBorder) { canvas.drawPath( - path2, borderPaint..color = Colors.grey.shade300.withOpacity(0.5)); + path2, + borderPaint..color = Colors.grey.shade300.withValues(alpha: 0.5), + ); } return path; } /// Draw the radial bar series type. -Path _processRadialBarShape( - {required Canvas canvas, - required Rect rect, - required Path path, - required bool isNeedToReturnPath, - double? degree, - double? startAngle, - double? endAngle, - double? radius, - Paint? paint, - Paint? borderPaint}) { +Path _processRadialBarShape({ + required Canvas canvas, + required Rect rect, + required Path path, + required bool isNeedToReturnPath, + double? degree, + double? startAngle, + double? endAngle, + double? radius, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; late Path path1, path2; + final bool hasBorder = + borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0; radius ??= (rect.width + rect.height) / 2; if (isNeedToReturnPath) { - if (borderPaint != null) { - path1 = _getArcPath(path, (radius / 2) - 2, radius / 2, Offset(x, y), 0, - 360 - 0.01, 360 - 0.01, true); + if (hasBorder) { + path1 = _getArcPath( + path, + (radius / 2) - 2, + radius / 2, + Offset(x, y), + 0, + 360 - 0.01, + 360 - 0.01, + true, + ); } else { - path2 = _getArcPath(path, (radius / 2) - 2, radius / 2, Offset(x, y), - startAngle!, endAngle!, degree!, true); + path2 = _getArcPath( + path, + (radius / 2) - 2, + radius / 2, + Offset(x, y), + startAngle!, + endAngle!, + degree!, + true, + ); } return path; } - path1 = _getArcPath(path, (radius / 2) - 2, radius / 2, Offset(x, y), 0, - 360 - 0.01, 360 - 0.01, true); - - path2 = _getArcPath(Path(), (radius / 2) - 2, radius / 2, Offset(x, y), - startAngle!, endAngle!, degree!, true); + path1 = _getArcPath( + path, + (radius / 2) - 2, + radius / 2, + Offset(x, y), + 0, + 360 - 0.01, + 360 - 0.01, + true, + ); - if (borderPaint != null) { + path2 = _getArcPath( + Path(), + (radius / 2) - 2, + radius / 2, + Offset(x, y), + startAngle!, + endAngle!, + degree!, + true, + ); + + if (hasBorder) { canvas.drawPath( - path1, - Paint() - ..color = Colors.grey.shade100 - ..strokeWidth = borderPaint.strokeWidth); + path1, + Paint() + ..color = Colors.grey.shade100 + ..strokeWidth = borderPaint.strokeWidth, + ); canvas.drawPath( - path1, borderPaint..color = Colors.grey.shade300.withOpacity(0.5)); + path1, + borderPaint..color = Colors.grey.shade300.withValues(alpha: 0.5), + ); } canvas.drawPath(path2, paint!); - if (borderPaint != null) { + if (hasBorder) { canvas.drawPath(path2, borderPaint..color = Colors.transparent); } @@ -1035,56 +1218,85 @@ Path _processRadialBarShape( } /// Get the path of arc -Path _getArcPath(Path path, double innerRadius, double radius, Offset center, - double startAngle, double endAngle, double degree, bool isAnimate) { +Path _getArcPath( + Path path, + double innerRadius, + double radius, + Offset center, + double startAngle, + double endAngle, + double degree, + bool isAnimate, +) { startAngle = _degreesToRadians(startAngle); endAngle = _degreesToRadians(endAngle); degree = _degreesToRadians(degree); final Point innerRadiusStartPoint = Point( - innerRadius * cos(startAngle) + center.dx, - innerRadius * sin(startAngle) + center.dy); + innerRadius * cos(startAngle) + center.dx, + innerRadius * sin(startAngle) + center.dy, + ); final Point innerRadiusEndPoint = Point( - innerRadius * cos(endAngle) + center.dx, - innerRadius * sin(endAngle) + center.dy); + innerRadius * cos(endAngle) + center.dx, + innerRadius * sin(endAngle) + center.dy, + ); final Point radiusStartPoint = Point( - radius * cos(startAngle) + center.dx, - radius * sin(startAngle) + center.dy); + radius * cos(startAngle) + center.dx, + radius * sin(startAngle) + center.dy, + ); if (isAnimate) { path.moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); } - final bool isFullCircle = - // ignore: unnecessary_null_comparison - startAngle != null && - // ignore: unnecessary_null_comparison - endAngle != null && - endAngle - startAngle == 2 * pi; - + final bool isFullCircle = endAngle - startAngle == 2 * pi; final num midpointAngle = (endAngle + startAngle) / 2; if (isFullCircle) { - path.arcTo(Rect.fromCircle(center: center, radius: radius), startAngle, - midpointAngle.toDouble() - startAngle, true); - path.arcTo(Rect.fromCircle(center: center, radius: radius), - midpointAngle.toDouble(), endAngle - midpointAngle.toDouble(), true); + path.arcTo( + Rect.fromCircle(center: center, radius: radius), + startAngle, + midpointAngle.toDouble() - startAngle, + true, + ); + path.arcTo( + Rect.fromCircle(center: center, radius: radius), + midpointAngle.toDouble(), + endAngle - midpointAngle.toDouble(), + true, + ); } else { path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - path.arcTo(Rect.fromCircle(center: center, radius: radius), startAngle, - degree, true); + path.arcTo( + Rect.fromCircle(center: center, radius: radius), + startAngle, + degree, + true, + ); } if (isFullCircle) { - path.arcTo(Rect.fromCircle(center: center, radius: innerRadius), endAngle, - midpointAngle.toDouble() - endAngle, true); - path.arcTo(Rect.fromCircle(center: center, radius: innerRadius), - midpointAngle.toDouble(), startAngle - midpointAngle.toDouble(), true); + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + endAngle, + midpointAngle.toDouble() - endAngle, + true, + ); + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + midpointAngle.toDouble(), + startAngle - midpointAngle.toDouble(), + true, + ); } else { path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); - path.arcTo(Rect.fromCircle(center: center, radius: innerRadius), endAngle, - startAngle - endAngle, true); + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius), + endAngle, + startAngle - endAngle, + true, + ); path.lineTo(radiusStartPoint.x, radiusStartPoint.y); } return path; @@ -1094,13 +1306,14 @@ Path _getArcPath(Path path, double innerRadius, double radius, Offset center, double _degreesToRadians(double deg) => deg * (pi / 180); /// Draw the hilo series type. -Path _processHiloShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? shaderPaint}) { +Path _processHiloShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double height = rect.height; @@ -1121,13 +1334,14 @@ Path _processHiloShape( } /// Draw the hilo open close series type. -Path _processHiloOpenCloseShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? shaderPaint}) { +Path _processHiloOpenCloseShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1148,26 +1362,30 @@ Path _processHiloOpenCloseShape( } /// Draw the waterfall series type. -Path _processWaterfallShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processWaterfallShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; final double height = rect.height; - path.addRect(Rect.fromLTRB( - x - width / 2, y - height / 2, x + width / 2, y + height / 2)); + path.addRect( + Rect.fromLTRB(x - width / 2, y - height / 2, x + width / 2, y + height / 2), + ); if (isNeedToReturnPath) { return path; } canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1175,13 +1393,14 @@ Path _processWaterfallShape( } /// Draw the pyramid series type. -Path _processPyramidShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processPyramidShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1197,7 +1416,9 @@ Path _processPyramidShape( } canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1205,13 +1426,14 @@ Path _processPyramidShape( } /// Draw the funnel series type. -Path _processFunnelShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processFunnelShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1228,7 +1450,9 @@ Path _processFunnelShape( } canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1236,27 +1460,33 @@ Path _processFunnelShape( } /// Draw the bubble series type. -Path _processBubbleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processBubbleShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; final double height = rect.height; path.addArc( - Rect.fromLTWH(x - width / 2, y - height / 2, width, height), 0.0, 2 * pi); + Rect.fromLTWH(x - width / 2, y - height / 2, width, height), + 0.0, + 2 * pi, + ); if (isNeedToReturnPath) { return path; } canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1264,13 +1494,14 @@ Path _processBubbleShape( } /// Draw the step are series type. -Path _processStepAreaShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processStepAreaShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1302,13 +1533,14 @@ Path _processStepAreaShape( } /// Draw the spline area series type. -Path _processSplineAreaShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processSplineAreaShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1317,14 +1549,20 @@ Path _processSplineAreaShape( path.moveTo(x - width / 2, y + height / 2); path.quadraticBezierTo(x, y - height, x, y + height / 5); path.quadraticBezierTo( - x + width / 2, y - height / 2, x + width / 2, y + height / 2); + x + width / 2, + y - height / 2, + x + width / 2, + y + height / 2, + ); if (isNeedToReturnPath) { return path; } canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1332,23 +1570,27 @@ Path _processSplineAreaShape( } /// Draw the line series type. -Path _processLineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? shaderPaint, - bool? isNeedMarker, - ShapeMarkerType? overlayMarkerType, - bool? isDashArray}) { +Path _processLineShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, + bool? isNeedMarker, + ShapeMarkerType? overlayMarkerType, + bool? isDashArray, +}) { if (isNeedMarker! && overlayMarkerType != null) { final Rect pathRect = Rect.fromCenter( - center: rect.center, - width: rect.width / 1.5, - height: rect.height / 1.5); - canvas.drawPath(getShapesPath(rect: pathRect, shapeType: overlayMarkerType), - Paint()..color = paint!.color); + center: rect.center, + width: rect.width / 1.5, + height: rect.height / 1.5, + ); + canvas.drawPath( + getShapesPath(rect: pathRect, shapeType: overlayMarkerType), + Paint()..color = paint!.color, + ); } final double left = rect.left + rect.width / 2; @@ -1364,23 +1606,27 @@ Path _processLineShape( paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; canvas.drawPath( - isDashArray! - ? _processDashPath(path, - dashArray: _CircularIntervalList([3, 2])) - : path, - paint..style = PaintingStyle.stroke); + isDashArray! + ? _processDashPath( + path, + dashArray: _CircularIntervalList([3, 2]), + ) + : path, + paint..style = PaintingStyle.stroke, + ); } return path; } /// Draw the column series type. -Path _processColumnShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processColumnShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double left = rect.left + rect.width / 2; final double top = rect.top + rect.height / 2; @@ -1390,19 +1636,29 @@ Path _processColumnShape( path.moveTo(left - space * (rect.width / temp), top - (rect.height / temp)); path.lineTo( - left + space * (-rect.width / padding), top - (rect.height / temp)); + left + space * (-rect.width / padding), + top - (rect.height / temp), + ); path.lineTo(left + space * (-rect.width / padding), top + (rect.height / 2)); path.lineTo(left - space * (rect.width / temp), top + (rect.height / 2)); path.close(); - path.moveTo(left - (rect.width / padding) - (rect.width / (padding * 2)), - top - (rect.height / 4) - (padding / 2)); - path.lineTo(left + (rect.width / padding) + (rect.width / (padding * 2)), - top - (rect.height / 4) - (padding / 2)); - path.lineTo(left + (rect.width / padding) + (rect.width / (padding * 2)), - top + (rect.height / 2)); - path.lineTo(left - (rect.width / padding) - (rect.width / (padding * 2)), - top + (rect.height / 2)); + path.moveTo( + left - (rect.width / padding) - (rect.width / (padding * 2)), + top - (rect.height / 4) - (padding / 2), + ); + path.lineTo( + left + (rect.width / padding) + (rect.width / (padding * 2)), + top - (rect.height / 4) - (padding / 2), + ); + path.lineTo( + left + (rect.width / padding) + (rect.width / (padding * 2)), + top + (rect.height / 2), + ); + path.lineTo( + left - (rect.width / padding) - (rect.width / (padding * 2)), + top + (rect.height / 2), + ); path.close(); path.moveTo(left + space * (rect.width / padding), top); @@ -1417,20 +1673,23 @@ Path _processColumnShape( canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } return path; } /// Draw the area series type. -Path _processAreaShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processAreaShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1450,7 +1709,9 @@ Path _processAreaShape( canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1458,13 +1719,14 @@ Path _processAreaShape( } /// Draw the bar series type. -Path _processBarShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { +Path _processBarShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1478,20 +1740,32 @@ Path _processBarShape( path.lineTo(x - (width / 2) - padding / 4, y - 3 * (height / 10)); path.close(); path.moveTo( - x - (width / 2) - (padding / 4), y - (height / 5) + (padding / 20)); + x - (width / 2) - (padding / 4), + y - (height / 5) + (padding / 20), + ); path.lineTo( - x + (width / 2) + (padding / 4), y - (height / 5) + (padding / 20)); + x + (width / 2) + (padding / 4), + y - (height / 5) + (padding / 20), + ); path.lineTo( - x + (width / 2) + (padding / 4), y + (height / 10) + (padding / 20)); + x + (width / 2) + (padding / 4), + y + (height / 10) + (padding / 20), + ); path.lineTo( - x - (width / 2) - (padding / 4), y + (height / 10) + (padding / 20)); + x - (width / 2) - (padding / 4), + y + (height / 10) + (padding / 20), + ); path.close(); path.moveTo( - x - (width / 2) - (padding / 4), y + (height / 5) + (padding / 10)); + x - (width / 2) - (padding / 4), + y + (height / 5) + (padding / 10), + ); path.lineTo(x - width / 4, y + (height / 5) + (padding / 10)); path.lineTo(x - width / 4, y + (height / 2) + (padding / 10)); path.lineTo( - x - (width / 2) - (padding / 4), y + (height / 2) + (padding / 10)); + x - (width / 2) - (padding / 4), + y + (height / 2) + (padding / 10), + ); path.close(); if (isNeedToReturnPath) { @@ -1500,7 +1774,9 @@ Path _processBarShape( canvas.drawPath(path, paint!); - if (borderPaint != null) { + if (borderPaint != null && + borderPaint.color != Colors.transparent && + borderPaint.strokeWidth > 0) { canvas.drawPath(path, borderPaint); } @@ -1508,14 +1784,15 @@ Path _processBarShape( } /// Draw the spline series type. -Path _processSplineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? shaderPaint, - bool? isDashArray}) { +Path _processSplineShape({ + required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, + bool? isDashArray, +}) { final double x = rect.left + rect.width / 2; final double y = rect.top + rect.height / 2; final double width = rect.width; @@ -1525,7 +1802,11 @@ Path _processSplineShape( path.quadraticBezierTo(x, y - height, x, y + height / 5); path.moveTo(x, y + height / 5); path.quadraticBezierTo( - x + width / 2, y + height / 2, x + width / 2, y - height / 2); + x + width / 2, + y + height / 2, + x + width / 2, + y - height / 2, + ); if (isNeedToReturnPath) { return path; @@ -1535,11 +1816,14 @@ Path _processSplineShape( paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; canvas.drawPath( - isDashArray! - ? _processDashPath(path, - dashArray: _CircularIntervalList([3, 2])) - : path, - paint..style = PaintingStyle.stroke); + isDashArray! + ? _processDashPath( + path, + dashArray: _CircularIntervalList([3, 2]), + ) + : path, + paint..style = PaintingStyle.stroke, + ); } return path; @@ -1549,16 +1833,18 @@ Path _processDashPath( Path source, { required _CircularIntervalList dashArray, }) { - const double intialValue = 0.0; + const double initialValue = 0.0; final Path path = Path(); for (final PathMetric measurePath in source.computeMetrics()) { - double distance = intialValue; + double distance = initialValue; bool draw = true; while (distance < measurePath.length) { final double length = dashArray.next; if (draw) { path.addPath( - measurePath.extractPath(distance, distance + length), Offset.zero); + measurePath.extractPath(distance, distance + length), + Offset.zero, + ); } distance += length; draw = !draw; diff --git a/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart b/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart index 5354c1145..bfc9fc4bb 100644 --- a/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart +++ b/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart @@ -1,32 +1,33 @@ part of interactive_scroll_viewer_internal; /// Triggers when double tap zoom invoked. -typedef _DoubleTapZoomInvokedCallback = Offset Function( - Offset value, Offset tapPosition); +typedef _DoubleTapZoomInvokedCallback = + Offset Function(Offset value, Offset tapPosition); /// [InteractiveScrollViewer] enables pan and zoom interactions with its child. @immutable class InteractiveScrollViewer extends StatefulWidget { /// Constructor for InteractiveScrollable. - const InteractiveScrollViewer(this.child, - {Key? key, - this.clipBehavior = Clip.hardEdge, - this.onDoubleTapZoomInvoked, - this.alignPanAxis = false, - this.boundaryMargin = EdgeInsets.zero, - // These default scale values were eyeballed as reasonable - //limits for common use cases. - this.maxScale = 3, - this.minScale = 1, - this.onInteractionStart, - this.onInteractionUpdate, - this.onInteractionEnd, - this.panEnabled = true, - this.scaleEnabled = true, - this.constrained = true, - this.enableDoubleTapZooming = true, - this.transformationController}) - : super(key: key); + const InteractiveScrollViewer( + this.child, { + Key? key, + this.clipBehavior = Clip.hardEdge, + this.onDoubleTapZoomInvoked, + this.panAxis = PanAxis.free, + this.boundaryMargin = EdgeInsets.zero, + // These default scale values were eyeballed as reasonable + //limits for common use cases. + this.maxScale = 3, + this.minScale = 1, + this.onInteractionStart, + this.onInteractionUpdate, + this.onInteractionEnd, + this.panEnabled = true, + this.scaleEnabled = true, + this.constrained = true, + this.enableDoubleTapZooming = true, + this.transformationController, + }) : super(key: key); /// Whether the normal size constraints at this point in the widget tree are /// applied to the child. @@ -45,18 +46,18 @@ class InteractiveScrollViewer extends StatefulWidget { /// Defaults to [Clip.hardEdge]. final Clip clipBehavior; - /// If true, panning is only allowed in the direction of the horizontal axis - /// or the vertical axis. + /// When set to [PanAxis.aligned], panning is only allowed in the horizontal + /// axis or the vertical axis, diagonal panning is not allowed. /// - /// In other words, when this is true, diagonal panning is not allowed. A - /// single gesture begun along one axis cannot also cause panning along the - /// other axis without stopping and beginning a new gesture. This is a common - /// pattern in tables where data is displayed in columns and rows. + /// When set to [PanAxis.vertical] or [PanAxis.horizontal] panning is only + /// allowed in the specified axis. For example, if set to [PanAxis.vertical], + /// panning will only be allowed in the vertical axis. And if set to + /// [PanAxis.horizontal],panning will only be allowed in the horizontal axis. /// - /// See also: - /// * [constrained], which has an example of creating a table that uses - /// alignPanAxis. - final bool alignPanAxis; + /// When set to [PanAxis.free] panning is allowed in all directions. + /// + /// Defaults to [PanAxis.free]. + final PanAxis panAxis; /// A margin for the visible boundaries of the child. /// @@ -184,12 +185,14 @@ class InteractiveScrollViewerState extends State { /// ``` void scrollTo(Offset offset) { if (widget.transformationController != null) { - final Offset previousOffset = - widget.transformationController!.toScene(Offset.zero); + final Offset previousOffset = widget.transformationController!.toScene( + Offset.zero, + ); widget.transformationController?.value = - widget.transformationController!.value.clone() - ..translate( - previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); + widget.transformationController!.value.clone()..translate( + previousOffset.dx - offset.dx, + previousOffset.dy - offset.dy, + ); } } @@ -228,8 +231,9 @@ class InteractiveScrollViewerState extends State { scale = scale.clamp(widget.minScale, widget.maxScale); final double zoomLevel = widget.transformationController!.value.getMaxScaleOnAxis(); - final Offset previousOffset = - widget.transformationController!.toScene(Offset.zero); + final Offset previousOffset = widget.transformationController!.toScene( + Offset.zero, + ); widget.transformationController?.value = widget.transformationController!.value.clone() ..scale(scale / zoomLevel, scale / zoomLevel); @@ -252,12 +256,14 @@ class InteractiveScrollViewerState extends State { Offset normalizedOffset; Offset offset; if (zoomLevel <= minimumZoomLevel) { - normalizedOffset = (-_tapPosition - + normalizedOffset = + (-_tapPosition - widget.transformationController!.toScene(Offset.zero) * zoomLevel) / zoomLevel; scaleTo(maximumZoomLevel); - offset = (-_tapPosition - normalizedOffset * maximumZoomLevel) / + offset = + (-_tapPosition - normalizedOffset * maximumZoomLevel) / maximumZoomLevel; // Triggered when double tap if (widget.onDoubleTapZoomInvoked != null) { @@ -265,12 +271,14 @@ class InteractiveScrollViewerState extends State { } scrollTo(offset); } else { - normalizedOffset = (-_tapPosition - + normalizedOffset = + (-_tapPosition - widget.transformationController!.toScene(Offset.zero) * zoomLevel) / zoomLevel; scaleTo(minimumZoomLevel); - offset = (-_tapPosition - normalizedOffset * minimumZoomLevel) / + offset = + (-_tapPosition - normalizedOffset * minimumZoomLevel) / minimumZoomLevel; // Triggered when double tap if (widget.onDoubleTapZoomInvoked != null) { @@ -296,7 +304,7 @@ class InteractiveScrollViewerState extends State { onInteractionEnd: widget.onInteractionEnd, scaleEnabled: widget.scaleEnabled, panEnabled: widget.panEnabled, - alignPanAxis: widget.alignPanAxis, + panAxis: widget.panAxis, transformationController: widget.transformationController, boundaryMargin: widget.boundaryMargin, clipBehavior: widget.clipBehavior, diff --git a/packages/syncfusion_flutter_core/lib/src/zoomable/zoomable.dart b/packages/syncfusion_flutter_core/lib/src/zoomable/zoomable.dart index 1e3a081de..d72227d95 100644 --- a/packages/syncfusion_flutter_core/lib/src/zoomable/zoomable.dart +++ b/packages/syncfusion_flutter_core/lib/src/zoomable/zoomable.dart @@ -34,7 +34,7 @@ enum ActionType { fling, /// Denotes there is no action currently. - none + none, } /// Contains details about the current zooming and panning action. @@ -94,9 +94,9 @@ class Zoomable extends StatefulWidget { this.onWillUpdate, this.onComplete, this.child, - }) : assert(minZoomLevel >= 1 && minZoomLevel <= maxZoomLevel), - assert(frictionCoefficient > 0.0), - super(key: key); + }) : assert(minZoomLevel >= 1 && minZoomLevel <= maxZoomLevel), + assert(frictionCoefficient > 0.0), + super(key: key); /// Holds the details of the current zoom level and actual rect. These details /// can be used in multiple widget which used the same zoomable widget. @@ -182,7 +182,7 @@ class _ZoomableState extends State with TickerProviderStateMixin { late Offset _scaleFocalPoint; Timer? _doubleTapTimer; - Offset? _referenceFocalPoint; + late Offset? _referenceFocalPoint; double _maxAttainedScale = 1.0; double _currentScale = 1.0; @@ -196,8 +196,10 @@ class _ZoomableState extends State with TickerProviderStateMixin { } Size _getAbsoluteChildSize(double zoomLevel) { - return Size((widget.boundary.width / 2) * pow(2, zoomLevel).toDouble(), - (widget.boundary.height / 2) * pow(2, zoomLevel).toDouble()); + return Size( + (widget.boundary.width / 2) * pow(2, zoomLevel).toDouble(), + (widget.boundary.height / 2) * pow(2, zoomLevel).toDouble(), + ); } void _handlePointerDown(PointerDownEvent event) { @@ -229,8 +231,10 @@ class _ZoomableState extends State with TickerProviderStateMixin { _scaleFocalPoint = details.localFocalPoint; _referenceScale = widget.controller.matrix.getMaxScaleOnAxis(); - _referenceFocalPoint = - _getPointOnChild(widget.controller.matrix, _scaleFocalPoint); + _referenceFocalPoint = _getPointOnChild( + widget.controller.matrix, + _scaleFocalPoint, + ); } } @@ -239,7 +243,9 @@ class _ZoomableState extends State with TickerProviderStateMixin { !_translationController.isAnimating) { if (widget.controller._action == ActionType.none) { widget.controller._action = _getActionTypes( - details.scale, details.localFocalPoint - _scaleFocalPoint); + details.scale, + details.localFocalPoint - _scaleFocalPoint, + ); } if (widget.controller._action == ActionType.none) { @@ -262,10 +268,12 @@ class _ZoomableState extends State with TickerProviderStateMixin { _currentScale = details.scale; double newScale = (_referenceScale! * details.scale) / prevScale; newScale = (newScale * prevScale).clamp( - Zoomable.getScale(widget.minZoomLevel), - Zoomable.getScale(widget.maxZoomLevel)); - final Size childSize = - _getAbsoluteChildSize(Zoomable.getZoomLevel(newScale)); + Zoomable.getScale(widget.minZoomLevel), + Zoomable.getScale(widget.maxZoomLevel), + ); + final Size childSize = _getAbsoluteChildSize( + Zoomable.getZoomLevel(newScale), + ); newScale /= prevScale; if (newScale == 1.0) { return; @@ -282,8 +290,13 @@ class _ZoomableState extends State with TickerProviderStateMixin { } _scale(newScale, pinchCenter, matrix); - _invokeOnWillUpdate(matrix, details.localFocalPoint, - details.focalPoint, details.scale, _scaleFocalPoint); + _invokeOnWillUpdate( + matrix, + details.localFocalPoint, + details.focalPoint, + details.scale, + _scaleFocalPoint, + ); break; case ActionType.pan: @@ -292,13 +305,19 @@ class _ZoomableState extends State with TickerProviderStateMixin { } _translate( - matrix, - _getPointOnChild(matrix, details.localFocalPoint) - - _referenceFocalPoint!); - _referenceFocalPoint = - _getPointOnChild(matrix, details.localFocalPoint); + matrix, + _getPointOnChild(matrix, details.localFocalPoint) - + _referenceFocalPoint!, + ); + _referenceFocalPoint = _getPointOnChild( + matrix, + details.localFocalPoint, + ); _invokeOnWillUpdate( - matrix, details.localFocalPoint, details.focalPoint); + matrix, + details.localFocalPoint, + details.focalPoint, + ); break; case ActionType.tap: @@ -321,14 +340,19 @@ class _ZoomableState extends State with TickerProviderStateMixin { if (_pointerCount == 2) { _resetDoubleTapTimer(); // By default, we have increased the zoom level by 1 while double tapping. - final double prevZoomLevel = - Zoomable.getZoomLevel(widget.controller.matrix.getMaxScaleOnAxis()); + final double prevZoomLevel = Zoomable.getZoomLevel( + widget.controller.matrix.getMaxScaleOnAxis(), + ); double newZoomLevel = prevZoomLevel + 1; - newZoomLevel = - newZoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); + newZoomLevel = newZoomLevel.clamp( + widget.minZoomLevel, + widget.maxZoomLevel, + ); if (newZoomLevel != prevZoomLevel) { _handleDoubleTap( - event.localPosition, pow(2, newZoomLevel - 1).toDouble()); + event.localPosition, + pow(2, newZoomLevel - 1).toDouble(), + ); } } } @@ -343,8 +367,11 @@ class _ZoomableState extends State with TickerProviderStateMixin { } final double newScale = exp(-event.scrollDelta.dy / 200); - final Size childSize = _getAbsoluteChildSize(Zoomable.getZoomLevel( - newScale * widget.controller.matrix.getMaxScaleOnAxis())); + final Size childSize = _getAbsoluteChildSize( + Zoomable.getZoomLevel( + newScale * widget.controller.matrix.getMaxScaleOnAxis(), + ), + ); final Size viewportSize = (context.findRenderObject()! as RenderBox).size; Offset pinchCenter = event.localPosition; if (childSize.width < viewportSize.width) { @@ -398,8 +425,9 @@ class _ZoomableState extends State with TickerProviderStateMixin { Offset _getPointOnChild(Matrix4 matrix, Offset viewportPoint) { final Matrix4 invertedMatrix = Matrix4.inverted(matrix); - final Vector3 untransformed = invertedMatrix - .transform3(Vector3(viewportPoint.dx, viewportPoint.dy, 0)); + final Vector3 untransformed = invertedMatrix.transform3( + Vector3(viewportPoint.dx, viewportPoint.dy, 0), + ); return Offset(untransformed.x, untransformed.y); } @@ -435,7 +463,8 @@ class _ZoomableState extends State with TickerProviderStateMixin { void _translate(Matrix4 matrix, Offset translation) { final Size childSize = _getAbsoluteChildSize( - Zoomable.getZoomLevel(matrix.getMaxScaleOnAxis())); + Zoomable.getZoomLevel(matrix.getMaxScaleOnAxis()), + ); final Rect viewport = Offset.zero & (context.findRenderObject()! as RenderBox).size; final bool widthExceeds = childSize.width > viewport.width; @@ -447,10 +476,14 @@ class _ZoomableState extends State with TickerProviderStateMixin { matrix.translate(0.0, translation.dy); } - final Offset effectiveViewportTopLeft = - _getPointOnChild(matrix, viewport.topLeft); - final Offset effectiveViewportBottomRight = - _getPointOnChild(matrix, viewport.bottomRight); + final Offset effectiveViewportTopLeft = _getPointOnChild( + matrix, + viewport.topLeft, + ); + final Offset effectiveViewportBottomRight = _getPointOnChild( + matrix, + viewport.bottomRight, + ); Offset correctedTranslation = Offset.zero; if (widthExceeds) { @@ -468,13 +501,17 @@ class _ZoomableState extends State with TickerProviderStateMixin { if (heightExceeds) { double exceed = effectiveViewportTopLeft.dy - widget.boundary.top; if (exceed < 0.0) { - correctedTranslation = - Offset(correctedTranslation.dx, correctedTranslation.dy + exceed); + correctedTranslation = Offset( + correctedTranslation.dx, + correctedTranslation.dy + exceed, + ); } else { exceed = effectiveViewportBottomRight.dy - widget.boundary.bottom; if (exceed > 0.0) { - correctedTranslation = - Offset(correctedTranslation.dx, correctedTranslation.dy + exceed); + correctedTranslation = Offset( + correctedTranslation.dx, + correctedTranslation.dy + exceed, + ); } } } @@ -484,11 +521,13 @@ class _ZoomableState extends State with TickerProviderStateMixin { } } - void _invokeOnWillUpdate(Matrix4 matrix, - [Offset? localFocalPoint, - Offset? globalFocalPoint, - double scale = 1.0, - Offset? pinchCenter]) { + void _invokeOnWillUpdate( + Matrix4 matrix, [ + Offset? localFocalPoint, + Offset? globalFocalPoint, + double scale = 1.0, + Offset? pinchCenter, + ]) { final double zoomLevel = Zoomable.getZoomLevel(matrix.getMaxScaleOnAxis()); if (localFocalPoint == null) { final RenderBox box = context.findRenderObject()! as RenderBox; @@ -499,7 +538,8 @@ class _ZoomableState extends State with TickerProviderStateMixin { globalFocalPoint = renderBox.localToGlobal(localFocalPoint); } - final Rect bounds = _getPointOnChild(matrix, widget.boundary.topLeft) & + final Rect bounds = + _getPointOnChild(matrix, widget.boundary.topLeft) & _getAbsoluteChildSize(zoomLevel); final ZoomableDetails details = ZoomableDetails( localFocalPoint: localFocalPoint, @@ -524,13 +564,15 @@ class _ZoomableState extends State with TickerProviderStateMixin { return widget.onWillUpdate?.call(details) ?? true; } - void _invokeOnComplete( - [Offset? localFocalPoint, - Offset? globalFocalPoint, - double scale = 1.0, - Offset? pinchCenter]) { - final double zoomLevel = - Zoomable.getZoomLevel(widget.controller.matrix.getMaxScaleOnAxis()); + void _invokeOnComplete([ + Offset? localFocalPoint, + Offset? globalFocalPoint, + double scale = 1.0, + Offset? pinchCenter, + ]) { + final double zoomLevel = Zoomable.getZoomLevel( + widget.controller.matrix.getMaxScaleOnAxis(), + ); if (localFocalPoint == null) { final RenderBox box = context.findRenderObject()! as RenderBox; localFocalPoint = box.size.center(Offset.zero); @@ -574,10 +616,12 @@ class _ZoomableState extends State with TickerProviderStateMixin { // This methods performs fling animation for pinching. void _startPinchFlingAnimation(ScaleEndDetails details) { - final double zoomLevel = - Zoomable.getZoomLevel(widget.controller.matrix.getMaxScaleOnAxis()); + final double zoomLevel = Zoomable.getZoomLevel( + widget.controller.matrix.getMaxScaleOnAxis(), + ); final int direction = _currentScale >= _maxAttainedScale ? 1 : -1; - double newZoomLevel = zoomLevel + + double newZoomLevel = + zoomLevel + (direction * (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * widget.maxZoomLevel); @@ -587,18 +631,21 @@ class _ZoomableState extends State with TickerProviderStateMixin { ..end = Zoomable.getScale(newZoomLevel); widget.controller._action = ActionType.fling; _zoomLevelController - ..duration = - _getFlingAnimationDuration(details.velocity.pixelsPerSecond.distance) + ..duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, + ) ..forward(from: 0.0); } void _handleZoomLevelAnimation() { - _zoomLevelAnimation.curve = widget.controller._action == ActionType.fling - ? Curves.decelerate - : Curves.easeInOut; + _zoomLevelAnimation.curve = + widget.controller._action == ActionType.fling + ? Curves.decelerate + : Curves.easeInOut; final double newScale = _zoomLevelTween.evaluate(_zoomLevelAnimation); - final Size childSize = - _getAbsoluteChildSize(Zoomable.getZoomLevel(newScale)); + final Size childSize = _getAbsoluteChildSize( + Zoomable.getZoomLevel(newScale), + ); final Size viewportSize = (context.findRenderObject()! as RenderBox).size; Offset pinchCenter = _scaleFocalPoint; @@ -607,12 +654,15 @@ class _ZoomableState extends State with TickerProviderStateMixin { pinchCenter = viewportSize.center(Offset.zero); } final Matrix4 matrix = _scale( - newScale / widget.controller.matrix.getMaxScaleOnAxis(), pinchCenter); + newScale / widget.controller.matrix.getMaxScaleOnAxis(), + pinchCenter, + ); if (widget.controller._action == ActionType.none || widget.controller._action == ActionType.fling) { - final double zoomLevel = - Zoomable.getZoomLevel(matrix.getMaxScaleOnAxis()); + final double zoomLevel = Zoomable.getZoomLevel( + matrix.getMaxScaleOnAxis(), + ); final Offset translation = _getCurrentTranslation(matrix); final Rect actualRect = translation & _getAbsoluteChildSize(zoomLevel); widget.controller._internalSetValues(matrix, zoomLevel, actualRect); @@ -625,8 +675,10 @@ class _ZoomableState extends State with TickerProviderStateMixin { if (widget.enablePinching && zoomLevel != widget.controller.zoomLevel) { final RenderBox box = context.findRenderObject()! as RenderBox; _scaleFocalPoint = box.size.center(Offset.zero); - _referenceFocalPoint = - _getPointOnChild(widget.controller.matrix, _scaleFocalPoint); + _referenceFocalPoint = _getPointOnChild( + widget.controller.matrix, + _scaleFocalPoint, + ); zoomLevel = zoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); _zoomLevelTween ..begin = widget.controller.matrix.getMaxScaleOnAxis() @@ -669,28 +721,34 @@ class _ZoomableState extends State with TickerProviderStateMixin { ..end = Offset(frictionSimulationX.finalX, frictionSimulationY.finalX); widget.controller._action = ActionType.fling; _translationController - ..duration = - _getFlingAnimationDuration(details.velocity.pixelsPerSecond.distance) + ..duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, + ) ..forward(from: 0.0); } void _handleTranslationAnimation() { - _translationAnimation.curve = widget.controller._action == ActionType.fling - ? Curves.decelerate - : Curves.easeInOut; + _translationAnimation.curve = + widget.controller._action == ActionType.fling + ? Curves.decelerate + : Curves.easeInOut; final Matrix4 matrix = widget.controller.matrix.clone(); final Offset prevTranslation = _getCurrentTranslation(matrix); - final Offset newTranslation = - _translationTween.evaluate(_translationAnimation); - final Offset translation = _getPointOnChild(matrix, newTranslation) - + final Offset newTranslation = _translationTween.evaluate( + _translationAnimation, + ); + final Offset translation = + _getPointOnChild(matrix, newTranslation) - _getPointOnChild(matrix, prevTranslation); _translate(matrix, translation); if (widget.controller._action == ActionType.none || widget.controller._action == ActionType.fling) { - final double zoomLevel = - Zoomable.getZoomLevel(matrix.getMaxScaleOnAxis()); - final Rect bounds = _getPointOnChild(matrix, widget.boundary.topLeft) & + final double zoomLevel = Zoomable.getZoomLevel( + matrix.getMaxScaleOnAxis(), + ); + final Rect bounds = + _getPointOnChild(matrix, widget.boundary.topLeft) & _getAbsoluteChildSize(zoomLevel); widget.controller._internalSetValues(matrix, zoomLevel, bounds); } else { @@ -736,17 +794,21 @@ class _ZoomableState extends State with TickerProviderStateMixin { ..addListener(_handleTranslationAnimation) ..addStatusListener(_handleTranslationAnimationStatusChange); - _zoomLevelAnimation = - CurvedAnimation(parent: _zoomLevelController, curve: Curves.easeInOut); + _zoomLevelAnimation = CurvedAnimation( + parent: _zoomLevelController, + curve: Curves.easeInOut, + ); _translationAnimation = CurvedAnimation( - parent: _translationController, curve: Curves.easeInOut); + parent: _translationController, + curve: Curves.easeInOut, + ); _translationTween = Tween(); _zoomLevelTween = Tween(); - final double initialZoomLevel = - Zoomable.getZoomLevel(widget.controller.matrix.getMaxScaleOnAxis()) - .clamp(widget.minZoomLevel, widget.maxZoomLevel); + final double initialZoomLevel = Zoomable.getZoomLevel( + widget.controller.matrix.getMaxScaleOnAxis(), + ).clamp(widget.minZoomLevel, widget.maxZoomLevel); widget.controller .._zoomLevel = initialZoomLevel .._absoluteBounds = widget.boundary @@ -793,12 +855,12 @@ class _ZoomableState extends State with TickerProviderStateMixin { class ZoomableController extends ChangeNotifier { /// Creates a [ZoomableController]. ZoomableController({Matrix4? matrix}) - : _matrix = matrix ?? Matrix4.identity(); + : _matrix = matrix ?? Matrix4.identity(); // ignore: unused_field - ScaleCallback? _onZoomLevelChange; + late ScaleCallback? _onZoomLevelChange; // ignore: unused_field - TranslationCallback? _onAbsoluteBoundsChange; + late TranslationCallback? _onAbsoluteBoundsChange; /// By defaults it will be a identity matrix, which corresponds to no /// transformation. diff --git a/packages/syncfusion_flutter_core/lib/theme.dart b/packages/syncfusion_flutter_core/lib/theme.dart index 1666f956d..ef3668764 100644 --- a/packages/syncfusion_flutter_core/lib/theme.dart +++ b/packages/syncfusion_flutter_core/lib/theme.dart @@ -1,6 +1,9 @@ +export './src/theme/assistview_theme.dart'; export './src/theme/barcodes_theme.dart'; export './src/theme/calendar_theme.dart'; export './src/theme/charts_theme.dart'; +export './src/theme/chat_theme.dart'; +export './src/theme/color_scheme.dart'; export './src/theme/datagrid_theme.dart'; export './src/theme/datapager_theme.dart'; export './src/theme/daterangepicker_theme.dart'; @@ -10,4 +13,6 @@ export './src/theme/pdfviewer_theme.dart'; export './src/theme/range_selector_theme.dart'; export './src/theme/range_slider_theme.dart'; export './src/theme/slider_theme.dart'; +export './src/theme/spark_charts_theme.dart'; export './src/theme/theme_widget.dart'; +export './src/theme/treemap_theme.dart'; diff --git a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart index 8da59636c..45e8ecaf8 100644 --- a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart +++ b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart @@ -4,6 +4,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; - +// ignore: depend_on_referenced_packages 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 9da0bf7fa..1a0901b6b 100644 --- a/packages/syncfusion_flutter_core/pubspec.yaml +++ b/packages/syncfusion_flutter_core/pubspec.yaml @@ -1,17 +1,17 @@ name: syncfusion_flutter_core description: Syncfusion Flutter Core is a dependent package for all the Syncfusion Flutter widgets. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_core environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: - vector_math: ">=2.1.0 <=3.0.0" + vector_math: ">=2.1.0 <=4.0.0" flutter: sdk: flutter dev_dependencies: - flutter_lints: ^1.0.4 + flutter_lints: ^2.0.1 flutter: diff --git a/packages/syncfusion_flutter_datagrid/CHANGELOG.md b/packages/syncfusion_flutter_datagrid/CHANGELOG.md index 458f08976..fd6b53b19 100644 --- a/packages/syncfusion_flutter_datagrid/CHANGELOG.md +++ b/packages/syncfusion_flutter_datagrid/CHANGELOG.md @@ -1,92 +1,426 @@ ## Unreleased +**General** + +* The compatible version of our Flutter DataGrid widget has been updated to Flutter SDK 3.32.0. + +**Features** + +- Provided support to customize the default appearance of checkbox and advanced filtering popup menus. +- Provided support to obtain the row details by row index using a helper method. +- Provided built-in support to customize the background color of the caption summary row. + +## [29.1.39] - 04/22/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 03/25/2025 + +**General** + +* The compatible version of our Flutter DataGrid widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter DataGrid example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter DataGrid example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +**Features** + +* Provided callback support to listen column sort changes. +* Provided callback support to listen value changes in the checkbox column. +* Provided support to display a widget (placeholder) when the data source is empty. + **Bugs** -* Other cell will not be moved into edit mode when trying to click it and returning false from `canSubmitCell` method for currentcell. +* The `SfDataPager` now properly displays the navigation button when `initialPageIndex` is set to the last page. + +## [28.2.11] - 03/11/2025 + +**Bugs** + +* The `SfDataGrid` now handles selection and provides selected details based on visible rows. + +## [28.2.3] - 01/29/2025 + +**Bugs** + +* The `SfDataGrid` now refreshes the grouping when filtering is removed using the `Select All` option. + +## [28.1.38] - 01/07/2025 + +**Bugs** + +* The `SfDataGrid` now creates new rows instead of reusing them when the `rowCacheExtent` is set. + +## [27.1.58] - 11/05/2024 + +**Bugs** + +* The datagrid now properly selects rows with checkboxes when using shift selection. + +## [27.1.55] - 10/22/2024 + +**Bugs** + +* The rows are now visible when performing pagination with grouped rows. + +## [27.1.53] - 10/15/2024 + +**Bugs** + +* The RangeError exception will no longer occur when collapsing a group with the column width mode set to fill the last column for all rows. + +## [27.0.0] - 09/09/2024 + +**General** + +* The compatible version of our Flutter DataGrid widget has been updated to Flutter SDK 3.24.0. + +## [26.2.10] - 08/20/2024 + +**Bugs** + +* The `SfDataGrid` now calculates cell width according to the `columnWidthMode` when there is only a single row. + +## [26.2.9] - 08/13/2024 + +**Bugs** + +* The selection is now disabled on secondary mouse tap when the `onCellDoubleTap` callback is enabled. + +## [26.1.40] - 07/02/2024 + +**Bugs** + +* The `ScrollController not attached to any scroll views exception` will not be thrown when navigating to the new page from datagrid. + +## [26.1.39] - 06/25/2024 + +**Bugs** + +* The datagrid now arranges header cells correctly when resizing a column from the maximum scroll extent position. + +## [25.2.7] - 06/04/2024 + +**Bugs** + +* The datagrid now draws the outer border on all sides when gridline visibility is not set to none. +* The datagrid now properly selects rows during shift selection. + +## [25.2.6] - 05/28/2024 + +**Bugs** + +* The custom filter icons now align properly during horizontal scrolling. +* The "index out of range" exception will no longer be thrown when resizing a column with an empty data source. + +## [25.1.38] - 04/02/2024 + +**Bugs** + +* The page navigation callbacks will only be invoked when the page is changed interactively. + +## [25.1.35] - 03/15/2024 **Features** -* Provided the support to check whether the currentcell is in editing mode using `DataGridController.isCurrentCellInEditing` property. -* Provided the support to set the custom sort icon using `SfDataGridThemeData.sortIcon` property. +* Implemented support for scrolling to a specific position upon initial loading. +* Implemented support to filter the custom data types using the `strongDataType` behavior. -## [19.4.54] - 03/01/2022 +**Bugs** + +* The auto row height is now working properly for the data rows when the sort and filter icons show in the column header. + +**Breaking changes** + +* Removed the deprecated `showFilterIconOnHover` property. The `showColumnHeaderIconOnHover` property is commonly used for showing both sort and filter icons. + +## [24.2.8] - 02/27/2024 **Bugs** -* Horizontal scrollbar will no longer be shown when you type the text beyond the cell width on editing. +* The app now responds to the back key in Android when the datagrid is in focus. +* The datagrid rows are now properly refreshed when adjusting the rows per page using the checkbox column. -## [19.4.50] - 02/08/2022 +## [24.2.7] - 02/20/2024 **Bugs** -* Cell will no longer be navigated to non visible column's cell which is placed in 0th index. +* The current page index now updates correctly when changing the number of rows per page at runtime. +* The datagrid now correctly resets the frozen column count during runtime. +* The filtering popup menu now displays correctly with Material 2. -## [19.4.42] - 01/11/2022 +## [24.1.47] - 01/23/2024 **Bugs** -* The null check operator exception will no longer be thrown when long press the table summary rows +* The null exception will no longer be thrown when interactions are performed in the caption summary row with the interaction callbacks. -## [19.4.38] - 12/17/2021 +## [24.1.43] - 12/27/2023 + +**Bugs** + +* Now, the DataGrid properly refreshes the cell value when updated, triggered by calling `notifyDataSourceListeners` alongside the DataPager. + +## [24.1.41] - 12/18/2023 **Features** -* Provided the support to set the different swipe offset for right and left swiping. -* Provided the support to select multiple rows when tapping another row and press and hold the SHIFT key -* Provided the support to wrap the DataGrid’s width and height based on number of rows and columns available when DataGrid’s parent size is infinity. -* Provided the support to show a dropdown button for choosing a different number of rows to show on each page. -* Provided the support to set the number of rows to be added with the currently visible items in viewport size for reusing during vertical scrolling. +* Provided support for grouping a collection of records based on particular categories, offering various customization options. This includes single and multiple column grouping, along with options to customize appearances and interactively expand and collapse a group. +* Provided support for retrieving the start and end index of visible rows and columns based on the row region in the DataGrid. + +## [23.2.7] - 12/06/2023 **Bugs** -* Range exception will no longer be thrown when DataGridSource is changed at run time with multiple rows are selected. +* The scroll offset is now appropriately reset when changing the data source during runtime. -## [19.3.55] - 11/23/2021 +## [23.1.36] - 09/15/2023 + +**Features** + +* Provided support for displaying column header icons, such as sort and filter icons, when hovering over the column header cells. + +## [22.2.9] - 08/15/2023 **Bugs** -* The `assertion failed` exception will no longer be thrown when you scroll horizontally using scrollbar thumb track and `isScrollbarAlwaysShown` is enabled. +* The RangeError exception will no longer be thrown when changing the showCheckboxColumn property along with selection mode. -## [19.3.54] - 11/17/2021 +## [22.2.5] - 07/27/2023 + +**Features** + +* Now, the current date is automatically selected as the initial date in the date picker within the Advanced UI filter menu when it falls between the first and last dates of the column or matches either of them. + +## [22.1.39] - 07/18/2023 **Bugs** -* Now, `onQueryRowHeight` callback will be called for all the rows in view when all the rows are available in view. +* The state of the header checkbox cell is reset when updating the data grid rows. -## [19.3.53] - 11/12/2021 +## [22.1.34] - 06/21/2023 -**Breaking changes** +**Features** -* Now, [onCellLongPress](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/SfDataGrid/onCellLongPress.html) callback will be called when long a pointer has remained in contact with the screen at the same location for a long period of time. +* Provided the support to customize the visibility of horizontal and vertical scrollbars in the DataGrid. +* Provide the support to show the filter icon when the mouse hovers over the column header. Users have the option to enable or disable this hover feature by `showFilterIconOnHover`. +* Provided the support to set a `key` to the `DataGridRow`, it helps automate the data grid rows during automated testing. +* Provided the support to rearrange columns by dragging and dropping them. -## [19.3.47] - 26/10/2021 +## [21.2.10] - 06/13/2023 **Bugs** -* `debugDisposed` and `debugDuringDeviceUpdate` errors are no longer occurred in debug mode when rebuilding the app from any of the DataGrid's `onCellDoubleTap` callback. +* The DataGrid now properly retains focus on the current cell even when the keyboard is opened on Android and iOS platforms -## [19.3.44] - 10/05/2021 +## [21.2.4] - 05/09/2023 + +**Bugs** + +* The `ColumnWidthMode` now calculates column width for header cells properly by taking into account the `maximumWidth` and `minimumWidth` properties, even when the source is empty. + +## [21.1.41] - 04/18/2023 + +**Bugs** + +* The `OK` button is now properly displayed in the UI filter menu when using the `Material3` design. + +## [21.1.35] - 03/23/2023 **Features** -* Provided the support to export the DataGrid content with sorted order. + +* Provided the support to change the position (left or right) of sort and filter icons in the column headers when sorting or filtering is applied. +* The `onCellSubmit`, `canSubmitCell`, and `performSorting` methods are marked async so that cell submission and sorting can be asynchronous. +* Provided the support to change the text style of all the elements in the filter pop menu. Users can set different text styles for enabled and disabled items. +* Provided the support to get the `currentcell` details when `navigationMode` is `row`. + +## [20.4.53] - 03/07/2023 **Bugs** -* The focus is now retained in the `TextField`, which is outside the DataGrid, when calling the `notifyListeners` from TextField’s `onPressed` callback to update the data in DataGrid. -## [19.3.43] - 10/01/2021 - +* The scrolling performance is improved when autofitting the rows using `onQueryRowHeight` callback with large collection of rows. + +## [20.4.51] - 02/21/2023 + +**Bugs** + +* The RangeError exception is no longer be thrown when rebuilding the DataGrid after applying the filtering. +* The filter is now applied properly to the DataGrid when changing the DataGridSource dynamically. + +## [20.4.48] - 02/01/2023 + **Features** -* Provided the support to resize the columns by tapping and dragging the right border of the column header. -* Provided the support to show an additional unbound row to display a summary or totals. Users can display a minimum, maximum, average, and count in columns. -* Provided the support to export the DataGrid content, such as rows, stacked header rows, and table summary rows, to Excel and PDF format with several customization options. -* Provided the support to show a checkbox in each row to select entire rows when the boxes are checked. Users can select or deselect all the rows by selecting the checkbox in the header. -* Provided the support to sort all the rows in DataGrid instead of current page alone when the paging is used. -* Provided the support to set the size for the page buttons in `SfDataPager`. - + +* Added support for restricting column resizing by checking the `columnIndex` in the `onColumnResizeStart` callback. + +**Bugs** + +* Fixed a LateInitializationError that occurred when opening the filter popup menu after rebuilding the DataGrid with an applied filter. +* Fixed a Type mismatch error that occurred when using an integer value in advanced filtering for a double type column. + **Breaking changes** -* The `onCellRenderersCreated` callback has been removed from the `SfDataGrid`. -## [19.2.44-beta] - 06/30/2021 +* `Command` key operations for selection and multi-column sorting have been replaced with `Control` key operations on the macOS platform. + +## [20.4.43] - 01/10/2023 + +**Bugs** + +* Edited rows from the grid when filtering and paging are applied. If the currently applied filter condition is not satisfied, the edited row will no longer be displayed in the DataGrid. + +## [20.4.38] - 12/21/2022 + +**Features** + +* Provided the support to change the shape of the built-in checkbox column. +* Provided the support to change the filter icon and its color. The padding around the filter icon can also be customized. +* Provided the support to customize the filter options in the filter popup. Users can hide the sorting and “Clear Filter” options and show only the checked listbox view or advanced filter popup view to apply filtering. +* Provided the support to customize the color of the sort order number and its rounded background. +* Provided the support to change the elevation effect when setting the `frozenPaneLineColor` property. + +**Bugs** + +* The filtering is now properly applied when programmatically adding the filter conditions and filter popup menu is now opened with proper items in checked listbox. + +## [20.3.60] - 12/06/2022 + +**Bugs** + +* The `onFilterChanging` and `onFilterChanged` callbacks will be called now when tapping `Select All` option to select all the rows in the checked listbox filtering. +* The current cell is now properly removed when setting the `selectedIndex` property as -1 programmatically. + +## [20.3.59] - 11/29/2022 + +**Bugs** + +* The focus is now moved to widgets that are outside the DataGrid when the canSubmitCell method returns true. + +## [20.3.57] - 11/15/2022 + +**Bugs** + +* The listeners in `SelectionController` class are properly disposed. + +## [20.3.49] - 10/11/2022 + +**Bugs** + +* The current cell is now updating properly while adding a row at runtime through the `RowSelectionManager`. +* SfDataPager is now working properly when changing the `pageCount` property at run time. + +## [20.3.48] - 10/05/2022 + +**Bugs** + +* Filtering is now working properly when page count is set for paging to apply whole data sorting. + +## [20.3.47] - 09/29/2022 + +**Breaking changes** + +* The left and border is now drawn by default in DataGrid. Hence, there is no need to add `Container` widget as parent for DataGrid to set left and top borders. +* If sorting is enabled for columns, an icon to notify the unsorted state of columns will be shown by default. + +**Bugs** + +* The `onSelectionChanged` callback is now properly called with the collection of deselected rows while deselecting through the checkbox in the column header. +* The parent scrollview widget of DataGrid is not now scrolled at application level when swiping a row in DataGrid. + +**Features** + +* Provided the support to perform Excel-like UI filtering and programmatic filtering of columns. Users can filter numeric, text, and date type columns with different filtering options. +* Provided the support to show the unsort icon in header cells when sorting is not applied to columns. When sorting, the ascending or descending icon will be shown. +* Using the `canSubmitCell` method, disallow the focus from the cell to other widgets outside the DataGrid or other cells in the DataGrid when editing is canceled. + +## [20.2.43] - 08/23/2022 + +**Bugs** + +* The `handlePageChange` method will not be called infinite times when using it asynchronously and switching between the pages very fast. + +## [20.2.39] - 07/19/2022 + +**Bugs** + +* The `handlePageChange` method is now called only once on initial loading of `SfDataPager`. +* The widths are properly set to columns when hiding some columns and using `columnWidthMode`. + +## [20.2.38] - 07/12/2022 + +**Bugs** + +* The next and previous buttons in `SfDataPager` are disabled even though the currently selected page is the last and the first page, respectively. + +## [20.2.36] - 06/30/2022 + +**Bugs** + +* The [verticalScrollController](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/SfDataGrid/verticalScrollController.html) and [horizontalScrollController](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/SfDataGrid/horizontalScrollController.html) are no longer disposed when the user sets in the application level. + +## [20.1.58] - 05/31/2022 + +**Bugs** + +* The null check operator used on a null value exception will no longer be thrown when deselecting all the selected rows programmatically. + +## [20.1.57] - 05/24/2022 + +**Bugs** + +* Checkbox is now properly unchecked when it is unchecked after being ticked + +## [20.1.55] - 05/12/2022 + +**Bugs** + +* BoxConstraints has a negative minimum height exception will no longer be thrown when opening the keyboard for `TextField` which is adjacent to `DataGrid`. +* Back key works properly while tapping the back key on an `Android` device and `DataGrid` has focus. + +## [20.1.52] - 05/03/2022 + +**Bugs** + +* DataGrid is now extended to its maximum height when setting the shrinkWrapRows to true and onQueryRowHeight callback. + +## [20.1.48] - 04/12/2022 + +**Bugs** + +* The animation will no longer be visible for checkbox column while scrolling when the `rowCacheExtent` property is set to the number of rows available in DataGrid. +* The `DataGridSource.handlePageChange` method will now wait asynchronously until the `Future.delayed` value specified in the method. +* Other cells will not be moved into edit mode when the last row is removed and a cell in that row is in edit mode. + +## [20.1.47] - 04/04/2022 + +**Bugs** + +* Other cell will not be moved into edit mode when trying to click it and returning false from `canSubmitCell` method for currentcell. + +**Features** + +* Provided the support to check whether the currentcell is in editing mode using `DataGridController.isCurrentCellInEditing` property. +* Provided the support to set the custom sort icon using `SfDataGridThemeData.sortIcon` property. + +## [19.4.54] - 03/01/2022 + +**Bugs** + +* Horizontal scrollbar will no longer be shown when you type the text beyond the cell width on editing. + +## [19.4.50] - 02/08/2022 + +**Bugs** + +* Cell will no longer be navigated to non visible column's cell which is placed in 0th index. + +## [19.4.42] - 01/11/2022 + +**Bugs** + +* The null check operator exception will no longer be thrown when long press the table summary rows. + +## [19.4.38] - 12/17/2021 **Features** diff --git a/packages/syncfusion_flutter_datagrid/README.md b/packages/syncfusion_flutter_datagrid/README.md index 01e55f8ab..c095b2491 100644 --- a/packages/syncfusion_flutter_datagrid/README.md +++ b/packages/syncfusion_flutter_datagrid/README.md @@ -8,7 +8,6 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta ## Table of contents - [DataGrid features](#datagrid-features) -- [Coming soon](#coming-soon) - [Get the demo application](#get-the-demo-application) - [Useful links](#other-useful-links) - [Installation](#installation) @@ -76,6 +75,10 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta ![First row and column are frozen in flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-freeze-panes.gif) +**Grouping** - Allows users to organize and categorize data based on specific criteria, facilitating efficient interaction with summarized information. + +**Column drag and drop** - Interactively customize the arrangement of columns by dragging and dropping them, enhancing user flexibility and personalization in organizing and viewing data. + **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. @@ -88,27 +91,18 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta **Right to Left (RTL)** - Right-to-left direction support for users working in RTL languages like Hebrew and Arabic. -## Coming soon - -* Column drag and drop -* Grouping -* Row drag and drop - ## Get the demo application Explore the full capabilities of our Flutter widgets on your device by installing our sample browser applications from the following app stores, and view sample code in GitHub.

- - + +

- -

-

@@ -290,11 +284,11 @@ The following screenshot illustrates the result of the above code sample. ## Support and Feedback -* If you have any questions, you can reach the [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post queries to the [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug report through our [feedback portal](https://www.syncfusion.com/feedback/flutter). +* If you have any questions, you can reach the [Syncfusion support team](https://support.syncfusion.com/support/tickets/create) or post queries to the [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug report 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 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,600+ 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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-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,600+ 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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_datagrid/analysis_options.yaml b/packages/syncfusion_flutter_datagrid/analysis_options.yaml index 18db5ebed..07fa312a6 100644 --- a/packages/syncfusion_flutter_datagrid/analysis_options.yaml +++ b/packages/syncfusion_flutter_datagrid/analysis_options.yaml @@ -2,9 +2,5 @@ include: package:syncfusion_flutter_core/analysis_options.yaml analyzer: errors: - lines_longer_than_80_chars: ignore include_file_not_found: ignore - invalid_dependency: ignore - avoid_as: ignore - - + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid/assets/font/FilterIcon.ttf b/packages/syncfusion_flutter_datagrid/assets/font/FilterIcon.ttf new file mode 100644 index 000000000..0bdbc3a38 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/assets/font/FilterIcon.ttf differ diff --git a/packages/syncfusion_flutter_datagrid/assets/font/UnsortIcon.ttf b/packages/syncfusion_flutter_datagrid/assets/font/UnsortIcon.ttf new file mode 100644 index 000000000..4e1cd2672 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/assets/font/UnsortIcon.ttf differ diff --git a/packages/syncfusion_flutter_datagrid/example/README.md b/packages/syncfusion_flutter_datagrid/example/README.md new file mode 100644 index 000000000..68d56c14e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/example/README.md @@ -0,0 +1,7 @@ +# datagrid_example + +This example shows how to get started with the Flutter DataGrid. + +For more information, refer the below links, +* [User guide documentation](https://help.syncfusion.com/flutter/datagrid/getting-started) +* [Syncfusion Flutter DataGrid product page](https://www.syncfusion.com/flutter-widgets/flutter-datagrid) \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid/example/android/.gitignore b/packages/syncfusion_flutter_datagrid/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/.gitignore +++ b/packages/syncfusion_flutter_datagrid/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_datagrid/example/android/app/build.gradle b/packages/syncfusion_flutter_datagrid/example/android/app/build.gradle index 5fe3c929f..b5511a9a3 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_datagrid/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_datagrid/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_datagrid/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_datagrid/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_datagrid/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_datagrid/example/android/app/src/main/AndroidManifest.xml index 3f41384db..74a78b939 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_datagrid/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/packages/syncfusion_flutter_datagrid/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_datagrid/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000d..70f8f08f2 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/syncfusion_flutter_datagrid/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_datagrid/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_datagrid/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_datagrid/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_datagrid/example/android/build.gradle b/packages/syncfusion_flutter_datagrid/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/build.gradle +++ b/packages/syncfusion_flutter_datagrid/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_datagrid/example/android/gradle.properties b/packages/syncfusion_flutter_datagrid/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/gradle.properties +++ b/packages/syncfusion_flutter_datagrid/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_datagrid/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_datagrid/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..e1ca574ef 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_datagrid/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/packages/syncfusion_flutter_datagrid/example/android/settings.gradle b/packages/syncfusion_flutter_datagrid/example/android/settings.gradle index 44e62bcf0..536165d35 100644 --- a/packages/syncfusion_flutter_datagrid/example/android/settings.gradle +++ b/packages/syncfusion_flutter_datagrid/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_datagrid/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/packages/syncfusion_flutter_datagrid/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 000000000..a88caf99d --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/packages/syncfusion_flutter_datagrid/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_datagrid/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/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_datagrid/example/lib/main.dart b/packages/syncfusion_flutter_datagrid/example/lib/main.dart index 9ad1bda7a..91e9cc9de 100644 --- a/packages/syncfusion_flutter_datagrid/example/lib/main.dart +++ b/packages/syncfusion_flutter_datagrid/example/lib/main.dart @@ -11,7 +11,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Syncfusion DataGrid Demo', - theme: ThemeData(primarySwatch: Colors.blue), + theme: ThemeData(useMaterial3: false), home: MyHomePage(), ); } @@ -40,42 +40,43 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Syncfusion Flutter DataGrid'), - ), + appBar: AppBar(title: const Text('Syncfusion Flutter DataGrid')), body: SfDataGrid( source: employeeDataSource, columnWidthMode: ColumnWidthMode.fill, columns: [ GridColumn( - columnName: 'id', - label: Container( - padding: EdgeInsets.all(16.0), - alignment: Alignment.center, - child: Text( - 'ID', - ))), + columnName: 'id', + label: Container( + padding: EdgeInsets.all(16.0), + alignment: Alignment.center, + child: Text('ID'), + ), + ), GridColumn( - columnName: 'name', - label: Container( - padding: EdgeInsets.all(8.0), - alignment: Alignment.center, - child: Text('Name'))), + columnName: 'name', + label: Container( + padding: EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text('Name'), + ), + ), GridColumn( - columnName: 'designation', - label: Container( - padding: EdgeInsets.all(8.0), - alignment: Alignment.center, - child: Text( - 'Designation', - overflow: TextOverflow.ellipsis, - ))), + columnName: 'designation', + label: Container( + padding: EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text('Designation', overflow: TextOverflow.ellipsis), + ), + ), GridColumn( - columnName: 'salary', - label: Container( - padding: EdgeInsets.all(8.0), - alignment: Alignment.center, - child: Text('Salary'))), + columnName: 'salary', + label: Container( + padding: EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text('Salary'), + ), + ), ], ), ); @@ -92,7 +93,7 @@ class _MyHomePageState extends State { Employee(10007, 'Balnc', 'Developer', 15000), Employee(10008, 'Perry', 'Developer', 15000), Employee(10009, 'Gable', 'Developer', 15000), - Employee(10010, 'Grimes', 'Developer', 15000) + Employee(10010, 'Grimes', 'Developer', 15000), ]; } } @@ -121,15 +122,22 @@ class Employee { 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(); + _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 = []; @@ -140,12 +148,14 @@ class EmployeeDataSource extends DataGridSource { @override 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()); + 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/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_datagrid/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_datagrid/example/pubspec.yaml b/packages/syncfusion_flutter_datagrid/example/pubspec.yaml index 1a9eb6866..ad116ff64 100644 --- a/packages/syncfusion_flutter_datagrid/example/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid/example/pubspec.yaml @@ -3,7 +3,7 @@ description: This project demonstrates how to use Syncfusion Flutter DataGrid wi version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: diff --git a/packages/syncfusion_flutter_datagrid/example/web/icons/Icon-maskable-192.png b/packages/syncfusion_flutter_datagrid/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/syncfusion_flutter_datagrid/example/web/icons/Icon-maskable-512.png b/packages/syncfusion_flutter_datagrid/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/syncfusion_flutter_datagrid/example/web/index.html b/packages/syncfusion_flutter_datagrid/example/web/index.html index 1460b5e9b..1aa025dd6 100644 --- a/packages/syncfusion_flutter_datagrid/example/web/index.html +++ b/packages/syncfusion_flutter_datagrid/example/web/index.html @@ -8,10 +8,13 @@ The path provided below has to start and end with a slash "/" in order for it to work correctly. - Fore more details: + For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + @@ -30,16 +33,6 @@ - - - + diff --git a/packages/syncfusion_flutter_datagrid/example/web/manifest.json b/packages/syncfusion_flutter_datagrid/example/web/manifest.json index 8c012917d..096edf8fe 100644 --- a/packages/syncfusion_flutter_datagrid/example/web/manifest.json +++ b/packages/syncfusion_flutter_datagrid/example/web/manifest.json @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/packages/syncfusion_flutter_datagrid/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datagrid/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_datagrid/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_datagrid/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart index 9291a8bf7..f33e8d47f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart @@ -17,7 +17,6 @@ export './src/datagrid_widget/sfdatagrid.dart' hide updateSelectedIndex, updateSelectedRow, - updateCurrentCellIndex, updateVerticalOffset, updateHorizontalOffset, notifyDataGridPropertyChangeListeners, @@ -28,13 +27,21 @@ export './src/datagrid_widget/sfdatagrid.dart' setPageCount, setChildColumnIndexes, getChildColumnIndexes, + addFilterConditions, + removeFilterConditions, + refreshEffectiveRows, + performSorting, + updateDataPager, + getRowsPerPage, + performGrouping, DataGridThemeHelper; export './src/datapager/sfdatapager.dart' hide SfDataPagerState, DataPagerThemeHelper; export './src/grid_common/row_column_index.dart'; export 'src/datagrid_widget/helper/callbackargs.dart' hide setColumnSizerInRowHeightDetailsArgs; -export 'src/datagrid_widget/helper/enums.dart'; +export 'src/datagrid_widget/helper/enums.dart' + hide FilteredFrom, AdvancedFilterType; export 'src/datagrid_widget/runtime/column.dart' hide ColumnResizeController, @@ -43,9 +50,17 @@ export 'src/datagrid_widget/runtime/column.dart' resetAutoCalculation, updateColumnSizerLoadedInitiallyFlag, getSortIconWidth, + getFilterIconWidth, getAutoFitRowHeight, setStateDetailsInColumnSizer, isColumnSizerLoadedInitially, - GridCheckboxColumn; + FilterElement, + GridCheckboxColumn, + DataGridFilterHelper, + DataGridCheckboxFilterHelper, + DataGridAdvancedFilterHelper, + ColumnDragAndDropController; export 'src/datagrid_widget/selection/selection_manager.dart' show RowSelectionManager, SelectionManagerBase; +export 'src/datagrid_widget/widgets/cell_widget.dart' + show GridHeaderCellElement; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/grouping/grouping.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/grouping/grouping.dart new file mode 100644 index 000000000..dd34d1807 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/grouping/grouping.dart @@ -0,0 +1,668 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import '../../../datagrid.dart'; +import '../helper/datagrid_configuration.dart'; +import '../sfdatagrid.dart'; + +/// Represents a grouping of records in the data grid. +class Group extends DataRowEntry { + /// Creates a new instance of the [Group] class. + Group() { + hasGroups = true; + rows = []; + subGroups = []; + groups = []; + } + + /// The key used for sorting and grouping. + dynamic key; + + /// Contains list of grouping and DataGridRows. + DataRowEntry? displayElements; + + /// List of groups within this grouping. + late List groups; + + /// List of rows associated with this grouping. + late List rows; + + /// List of subgroups within this grouping. + late List subGroups; + + /// The top-level group to which this grouping belongs. + TopLevelGroup? topLevelGroup; + + /// Refreshes the top-level group for the DataGridSource. + void refreshTopLevelGroup( + DataGridConfiguration dataGridConfiguration, + List groupedColumn, + bool autoExpandGroup, + ) { + if (hasGroups) { + initializeTopLevelGroup(dataGridConfiguration, autoExpandGroup); + } else { + if (topLevelGroup != null) { + topLevelGroup = null; + } + dataGridConfiguration.group = null; + } + } + + /// used to initalize the grouping. + void initializeTopLevelGroup( + DataGridConfiguration dataGridConfiguration, + bool autoExpandGroup, + ) { + final List groupedColumn = []; + for (final ColumnGroup columnGroupDescription + in dataGridConfiguration.source.groupedColumns) { + groupedColumn.add(columnGroupDescription.name); + } + + /// Creates top level group. + topLevelGroup = TopLevelGroup(this); + + if (groupedColumn.isNotEmpty) { + final List result = _performGroupBy( + effectiveRows(dataGridConfiguration.source), + groupedColumn, + dataGridConfiguration.source, + ); + topLevelGroup?._populate(result, autoExpandGroup); + } + dataGridConfiguration.group = topLevelGroup; + } + + List _performGroupBy( + List rows, + List columnNames, + DataGridSource? source, + ) { + final Map groupedData = {}; + + for (final DataGridRow row in rows) { + final dynamic key = performGrouping(source, columnNames[0], row); + + if (columnNames.length <= 1) { + if (groupedData.containsKey(key)) { + groupedData[key].add(row); + } else { + groupedData[key] = [row]; + } + } else { + dynamic currentGroup = groupedData.putIfAbsent( + key, + () => {}, + ); + + for (int i = 1; i < columnNames.length; i++) { + final dynamic innerKey = performGrouping(source, columnNames[i], row); + + if (i != columnNames.length - 1) { + currentGroup = currentGroup.putIfAbsent( + innerKey, + () => {}, + ); + } else { + currentGroup.putIfAbsent(innerKey, () => []).add(row); + } + } + } + } + + final List groupResults = []; + + for (final MapEntry entry in groupedData.entries) { + final Group groupResult = Group()..key = entry.key; + + final Group innerGroupResult = _createGroupResult( + entry: entry, + groupResult: groupResult, + ); + groupResult.rows = innerGroupResult.rows; + groupResults.add(groupResult); + } + + return groupResults; + } + + dynamic _createGroupResult({ + MapEntry? entry, + Group? groupResult, + }) { + if (entry!.value is List) { + entry.value.forEach((dynamic item) { + groupResult!.rows.add(item); + }); + + return groupResult; + } else { + entry.value.entries.forEach((dynamic newEntry) { + Group newGroupResult = + Group() + ..key = newEntry.key + ..length = newEntry.value.length; + + newGroupResult = _createGroupResult( + entry: newEntry, + groupResult: newGroupResult, + ); + groupResult!.rows.addAll(newGroupResult.rows); + + groupResult.subGroups.add(newGroupResult); + groupResult.length = groupResult.rows.length; + }); + return groupResult; + } + } + + /// Expands all the groups in SfDataGrid. + Group? expandAll(Group? newGrouping) { + bool expand = false; + Group groupsToPopulate; + final Group newGroup = Group(); + newGroup.displayElements = DataRowEntry(); + + for (final dynamic group in newGrouping!.displayElements!.grouped) { + if (group is Group) { + if (group.level == 1) { + expand = true; + groupsToPopulate = group; + _expandAndCollapseGroup(groupsToPopulate, expand, newGroup); + } + } + } + + newGrouping.displayElements = newGroup.displayElements; + newGrouping.displayElements?.length = + newGroup.displayElements!.grouped.length; + newGrouping.autoExpandGroup = true; + return newGrouping; + } + + /// Collapses all the groups in SfDataGrid. + Group? collapseAll(Group? newGrouping) { + bool expand = false; + Group groupsToPopulate; + final Group newGroup = Group(); + newGroup.displayElements = DataRowEntry(); + + for (final dynamic group in newGrouping!.displayElements!.grouped) { + if (group is Group) { + if (group.level == 1) { + expand = false; + } + + groupsToPopulate = group; + _expandAndCollapseGroup(groupsToPopulate, expand, newGroup); + } + } + + newGrouping.displayElements = newGroup.displayElements; + newGrouping.displayElements?.length = + newGroup.displayElements!.grouped.length; + newGrouping.autoExpandGroup = false; + return newGrouping; + } + + /// Initialize newGroup with a new instance of Grouping. + Group? clear(Group? newGroup) { + newGroup = null; + return newGroup; + } + + /// Used to reset the display elements in grouping. + void resetDisplayElements() { + displayElements = DataRowEntry(); + } + + /// clear the display elements. + void clearDisplayElements(DataGridConfiguration dataGridConfiguration) { + dataGridConfiguration.group!.displayElements = null; + } + + Group? _expandAndCollapseGroup( + Group groupsToPopulate, + bool expand, + Group? newGroup, + ) { + final Group group = groupsToPopulate; + + if (group.subGroups.isEmpty) { + if (expand || (!expand && groupsToPopulate.level == 1)) { + group.isExpanded = false; + newGroup?.displayElements?.grouped.add(group); + } + if (expand) { + group.isExpanded = true; + newGroup?.displayElements?.grouped.addAll(group.rows); + } + } else { + if (expand || (!expand && groupsToPopulate.level == 1)) { + newGroup?.displayElements?.grouped.add(group); + } + for (final Group subGroups in group.subGroups) { + group.isExpanded = expand; + _expandAndCollapseGroup(subGroups, expand, newGroup); + } + } + return newGroup; + } + + /// Expands a specific group in SfDataGrid. + Group? expandGroups(dynamic individualGroup, Group? newGrouping, int index) { + if (individualGroup is DataGridRow || individualGroup.isExpanded) { + return newGrouping; + } + newGrouping!.displayElements!.grouped[index].isExpanded = true; + index = _addSubGroups(individualGroup, newGrouping, index); + + return newGrouping; + } + + int _addSubGroups( + dynamic individualGroup, + Group? newGrouping, + int startIndex, + ) { + int groupIndex = startIndex + 1; + + if (individualGroup.subGroups.isNotEmpty) { + for (final Group nestedSubGroup in individualGroup.subGroups) { + newGrouping!.displayElements!.grouped.insert( + groupIndex, + nestedSubGroup, + ); + if (nestedSubGroup.isExpanded) { + nestedSubGroup.isExpanded = true; + groupIndex = _addSubGroups(nestedSubGroup, newGrouping, groupIndex); + } else { + groupIndex++; + } + } + } else if (individualGroup.isExpanded) { + for (final DataGridRow row in individualGroup.rows) { + newGrouping!.displayElements!.grouped.insert(groupIndex, row); + groupIndex++; + } + } + + return groupIndex; + } + + /// Collapses a specific group in SfDataGrid. + Group? collapseGroups( + dynamic individualGroup, + Group? newGrouping, + int groupIndex, + ) { + if (individualGroup is DataGridRow || !individualGroup.isExpanded) { + return newGrouping; + } + final int subGroupCount = _countSubGroups(individualGroup); + newGrouping!.displayElements!.grouped[groupIndex].isExpanded = false; + final int startIndex = groupIndex + 1; + final int endIndex = subGroupCount + groupIndex; + newGrouping.displayElements!.grouped.removeRange(startIndex, endIndex); + return newGrouping; + } + + int _countSubGroups(dynamic individualGroup) { + int subgroupCount = 1; + + if (individualGroup.subGroups.isNotEmpty) { + for (final Group nestedSubGroup in individualGroup.subGroups) { + subgroupCount += + nestedSubGroup.isExpanded ? _countSubGroups(nestedSubGroup) : 1; + } + } else if (individualGroup.isExpanded) { + subgroupCount += individualGroup.rows.length as int; + } + + return subgroupCount; + } + + /// Collapses the groups based on the respective level. + Group? collapseGroupAtLevel(int level, Group? newGrouping) { + final List newGroups = []; + for (final dynamic group in newGrouping!.displayElements!.grouped) { + if (group is Group && group.level <= level) { + if (group.level == level) { + group.isExpanded = false; + } + newGroups.add(group); + } + } + newGrouping.displayElements!.grouped = newGroups; + return newGrouping; + } + + /// Expands the groups based on the respective level. + Group? expandGroupsAtLevel(int level, Group? newGrouping) { + final List newGroups = []; + for (final dynamic group in newGrouping!.displayElements!.grouped) { + if (group is Group && group.level <= level) { + if (group.level == level) { + group.isExpanded = true; + newGroups.add(group); + _addSubGroupsAtLevel(newGroups, group); + } else { + newGroups.add(group); + } + } + } + newGrouping.displayElements!.grouped = newGroups; + return newGrouping; + } + + dynamic _addSubGroupsAtLevel(List newGroups, dynamic group) { + if (group.subGroups.isNotEmpty) { + for (final Group nestedSubGroup in group.subGroups) { + newGroups.add(nestedSubGroup); + if (nestedSubGroup.isExpanded) { + nestedSubGroup.isExpanded = true; + newGroups = _addSubGroupsAtLevel(newGroups, nestedSubGroup); + } + } + } else if (group.isExpanded) { + group.rows.forEach((DataGridRow row) { + newGroups.add(row); + }); + } + return newGroups; + } + + /// Called when the sorting is applied to the column. + @protected + void performSorting( + DataGridConfiguration dataGridConfiguration, + List sortedColumn, + ) { + bool expand = false; + Group groupsToPopulate; + final Group newGroup = Group(); + newGroup.displayElements = DataRowEntry(); + + for (final dynamic group + in dataGridConfiguration.group!.displayElements!.grouped) { + if (group is Group) { + if (group.level == 1) { + expand = true; + groupsToPopulate = group; + _sortGroupedRows( + dataGridConfiguration, + groupsToPopulate, + expand, + newGroup, + sortedColumn, + ); + } + } + } + + displayElements = newGroup.displayElements; + displayElements?.length = newGroup.displayElements!.grouped.length; + autoExpandGroup = true; + } + + Group? _sortGroupedRows( + DataGridConfiguration dataGridConfiguration, + Group groupsToPopulate, + bool expand, + Group? newGroup, + List sortedColumn, + ) { + final Group group = groupsToPopulate; + + if (group.subGroups.isEmpty) { + if (expand || (!expand && groupsToPopulate.level == 1)) { + group.isExpanded = false; + newGroup?.displayElements?.grouped.add(group); + } + if (expand) { + group.isExpanded = true; + _performSorting(group.rows, sortedColumn); + newGroup?.displayElements?.grouped.addAll(group.rows); + } + } else { + if (expand || (!expand && groupsToPopulate.level == 1)) { + newGroup?.displayElements?.grouped.add(group); + } + + for (final Group subGroups in group.subGroups) { + group.isExpanded = expand; + _sortGroupedRows( + dataGridConfiguration, + subGroups, + expand, + newGroup, + sortedColumn, + ); + } + } + return newGroup; + } + + void _performSorting(List rows, List sortedColumns) { + if (sortedColumns.isEmpty) { + return; + } + rows.sort((DataGridRow a, DataGridRow b) { + return _compareValues(sortedColumns, a, b); + }); + } + + int _compareValues(List sortedColumns, DataGridRow a, DataGridRow b) { + if (sortedColumns.length > 1) { + for (final int i = 0; i < sortedColumns.length;) { + final String sortColumn = sortedColumns[i]; + final int compareResult = _compare(a, b, sortColumn); + if (compareResult == 0) { + return compareResult; + } else { + final List remainingSortColumns = sortedColumns.sublist( + i + 1, + ); + return _compareValues(remainingSortColumns, a, b); + } + } + } + final String sortColumn = sortedColumns.last; + return _compare(a, b, sortColumn); + } + + int _compare(DataGridRow? a, DataGridRow? b, String sortColumn) { + final Object? valueA = _getCellValue(a?.getCells(), sortColumn); + final Object? valueB = _getCellValue(b?.getCells(), sortColumn); + return _compareTo(valueA, valueB); + } + + int _compareTo(dynamic value1, dynamic value2) { + if (value1 == null) { + return -1; + } else if (value2 == null) { + return 1; + } + return value1.toString().compareTo(value2.toString()); + } + + Object? _getCellValue(List? cells, String columnName) { + return cells + ?.firstWhereOrNull( + (DataGridCell element) => element.columnName == columnName, + ) + ?.value; + } +} + +/// Maintain the property for the grouping. +class GroupPopulate extends Group { + /// Constructs a [GroupPopulate] instance with the specified [level]. + /// + /// The [level] parameter indicates the grouping level. + GroupPopulate({required int level}) { + isBottomLevel = false; + displayElements?.level = level; + } + + /// Constructs a [GroupPopulate] instance from a list of [source] data grid rows. + /// + /// This constructor initializes the [dataGridRows] property with the provided + /// list of data grid rows. + GroupPopulate.fromRows(List source) : super() { + dataGridRows = source; + } + + /// + late Group details; + + /// List of data grid rows associated with this grouping. + List? dataGridRows; + + /// Number of rows in the cache. + int rowsCacheCount = -1; + + /// Indicates whether this is a top-level group. + bool isTopLevelGroup = false; + + /// Indicates whether this is a bottom-level group. + bool isBottomLevel = false; + + @override + List get groups { + if (details.hasGroups) { + return details.groups; + } + return []; + } + + int _getRowsCacheCount() { + return rowsCacheCount; + } + + /// Holds the list of dataGrid rows. + List? get source { + return (details as GroupPopulate).dataGridRows; + } + + int _populate(List? groupsToPopulate, bool autoExpandGroups) { + displayElements = DataRowEntry(); + autoExpandGroup = autoExpandGroups; + hasGroups = true; + + if (groupsToPopulate != null) { + rowsCacheCount = _populateGroup( + groupsToPopulate, + groups, + this, + level + 1, + ); + displayElements?.length = rowsCacheCount; + } + return rowsCacheCount; + } + + int _populateGroup( + List groupsToPopulate, + List groupPopulates, + GroupPopulate parent, + int level, + ) { + int parentCounter = 0; + final List groups = groupsToPopulate.asMap().values.toList(); + + for (final Group newGroup in groups) { + newGroup.level = level; + final Group groupResult = newGroup; + final GroupPopulate group = GroupPopulate(level: level); + group.level = level; + int counter = 1; + groupPopulates.add(group); + + if (newGroup.subGroups.isEmpty) { + if (autoExpandGroup || (!autoExpandGroup && level == 1)) { + if (autoExpandGroup) { + newGroup.isExpanded = false; + } + displayElements?.grouped.add(newGroup); + } + group._createDetailsForRows(groupResult.rows, level); + counter += group.source!.length; + if (autoExpandGroup) { + newGroup.isExpanded = true; + displayElements?.grouped.addAll(newGroup.rows); + } + group._getRowsCacheCount(); + parentCounter += counter; + } else { + group.details = Group(); + if (autoExpandGroup || (!autoExpandGroup && level == 1)) { + newGroup.isExpanded = autoExpandGroup; + displayElements?.grouped.add(newGroup); + } + counter += _populateGroup( + groupResult.subGroups, + group.groups, + group, + level + 1, + ); + group._getRowsCacheCount(); + parentCounter += counter; + counter = 0; + } + + final TopLevelGroup topLevelGroup = TopLevelGroup(this); + if (isTopLevelGroup) { + if (topLevelGroup.group.autoExpandGroup && + topLevelGroup.group.hasGroups) { + group.isExpanded = true; + } + } else { + if (topLevelGroup.group.autoExpandGroup) { + group.isExpanded = true; + } + } + } + return parentCounter; + } + + void _createDetailsForRows(List source, int level) { + isBottomLevel = true; + details = GroupPopulate.fromRows(source); + } +} + +/// This class represents a top-level group. +class TopLevelGroup extends GroupPopulate { + /// Constructor for the [TopLevelGroup] class. + /// + /// [group] is the grouping associated with this top-level group. + TopLevelGroup(this.group) : super(level: 0) { + isTopLevelGroup = true; + details = Group(); + } + + /// Hold details of the grouping. + late Group group; +} + +/// This class represents a record entry. +class DataRowEntry { + /// List of dynamic elements grouped together. + List grouped = []; + + /// The length of the record entry. + int length = 0; + + /// Flag indicating whether to auto-expand the group. + bool autoExpandGroup = false; + + /// Flag indicating whether there are groups. + bool hasGroups = true; + + /// Flag indicating whether the group is expanded. + bool isExpanded = false; + + /// The level of the record entry. + int level = 0; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart index 86e5213f3..a40f68e11 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart @@ -1,10 +1,12 @@ +// ignore_for_file: use_setters_to_change_properties + import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_datagrid/src/datagrid_widget/helper/datagrid_configuration.dart'; -import '../../grid_common/row_column_index.dart'; + +import '../../../datagrid.dart'; import '../runtime/column.dart'; -import 'enums.dart'; +import 'datagrid_configuration.dart'; /// A base class which provides the details for callbacks that use /// [DataGridCellTapDetails], [DataGridCellDoubleTapDetails] and @@ -21,8 +23,10 @@ import 'enums.dart'; class DataGridCellDetails { /// Creates a [DataGridCellDetails] with the specified [rowColumnIndex] and /// [column]. - const DataGridCellDetails( - {required this.rowColumnIndex, required this.column}); + const DataGridCellDetails({ + required this.rowColumnIndex, + required this.column, + }); /// The coordinates of the cell in [SfDataGrid]. final RowColumnIndex rowColumnIndex; @@ -40,13 +44,13 @@ class DataGridCellDetails { class DataGridCellTapDetails extends DataGridCellDetails { /// Creates a [DataGridCellTapDetails] with the specified [rowColumnIndex], /// [column], [globalPosition], [localPosition] and [kind]. - const DataGridCellTapDetails( - {required RowColumnIndex rowColumnIndex, - required GridColumn column, - required this.globalPosition, - required this.localPosition, - required this.kind}) - : super(rowColumnIndex: rowColumnIndex, column: column); + const DataGridCellTapDetails({ + required RowColumnIndex rowColumnIndex, + required GridColumn column, + required this.globalPosition, + required this.localPosition, + required this.kind, + }) : super(rowColumnIndex: rowColumnIndex, column: column); /// The global position at which the pointer contacted the screen. final Offset globalPosition; @@ -93,12 +97,17 @@ class RowHeightDetails { /// [SfDataGrid.columnSizer] property. /// /// The auto size is calculated based on default [TextStyle] of the datagrid. - double getIntrinsicRowHeight(int rowIndex, - {bool canIncludeHiddenColumns = false, - List excludedColumns = const []}) { - return getAutoFitRowHeight(_columnSizer, rowIndex, - canIncludeHiddenColumns: canIncludeHiddenColumns, - excludedColumns: excludedColumns); + double getIntrinsicRowHeight( + int rowIndex, { + bool canIncludeHiddenColumns = false, + List excludedColumns = const [], + }) { + return getAutoFitRowHeight( + _columnSizer, + rowIndex, + canIncludeHiddenColumns: canIncludeHiddenColumns, + excludedColumns: excludedColumns, + ); } } @@ -111,9 +120,10 @@ class RowHeightDetails { class DataGridCellDoubleTapDetails extends DataGridCellDetails { /// Creates a [DataGridCellDoubleTapDetails] with the specified [rowColumnIndex] /// and [column]. - const DataGridCellDoubleTapDetails( - {required RowColumnIndex rowColumnIndex, required GridColumn column}) - : super(rowColumnIndex: rowColumnIndex, column: column); + const DataGridCellDoubleTapDetails({ + required RowColumnIndex rowColumnIndex, + required GridColumn column, + }) : super(rowColumnIndex: rowColumnIndex, column: column); } /// Details for callbacks that use [DataGridCellLongPressDetails]. @@ -184,10 +194,11 @@ class DataGridSwipeStartDetails { @immutable class DataGridSwipeUpdateDetails { /// Creates a [DataGridSwipeUpdateDetails] class for [SfDataGrid]. - const DataGridSwipeUpdateDetails( - {required this.rowIndex, - required this.swipeOffset, - required this.swipeDirection}); + const DataGridSwipeUpdateDetails({ + required this.rowIndex, + required this.swipeOffset, + required this.swipeDirection, + }); /// An index of a row which is swiped. final int rowIndex; @@ -203,8 +214,10 @@ class DataGridSwipeUpdateDetails { @immutable class DataGridSwipeEndDetails { /// Creates a [DataGridSwipeEndDetails] class for [SfDataGrid]. - const DataGridSwipeEndDetails( - {required this.rowIndex, required this.swipeDirection}); + const DataGridSwipeEndDetails({ + required this.rowIndex, + required this.swipeDirection, + }); /// An index of a row which is swiped. final int rowIndex; @@ -217,50 +230,201 @@ class DataGridSwipeEndDetails { @immutable class ColumnResizeStartDetails { /// Creates the [ColumnResizeStartDetails] with the specified [column] and [width]. - const ColumnResizeStartDetails({required this.column, required this.width}); + const ColumnResizeStartDetails({ + required this.column, + required this.width, + required this.columnIndex, + }); /// A column that is going to be resized. final GridColumn column; /// Current width of a column. final double width; + + /// An index of a column that is going to be resized. + final int columnIndex; } /// Holds the arguments for the [SfDataGrid.onColumnResizeUpdate] callback. @immutable class ColumnResizeUpdateDetails { /// Creates the [ColumnResizeUpdateDetails] with the specified [column] and [width]. - const ColumnResizeUpdateDetails({required this.column, required this.width}); + const ColumnResizeUpdateDetails({ + required this.column, + required this.width, + required this.columnIndex, + }); /// A column that is being resized. final GridColumn column; /// Currently resized width of a column. final double width; + + /// An index of a column that is being resized. + final int columnIndex; } /// Holds the arguments for the [SfDataGrid.onColumnResizeEnd] callback. @immutable class ColumnResizeEndDetails { /// Creates the [ColumnResizeEndDetails] with the specified [column] and [width]. - const ColumnResizeEndDetails({required this.column, required this.width}); + const ColumnResizeEndDetails({ + required this.column, + required this.width, + required this.columnIndex, + }); /// A column that is resized. final GridColumn column; /// Currently resized width of a column. final double width; + + /// An index of a column that is resized. + final int columnIndex; +} + +/// Details for callbacks that use [DataGridFilterChangeDetails]. +/// +/// See also: +/// +/// * [DataGridFilterChangingCallback] +/// * [DataGridFilterChangedCallback] +@immutable +class DataGridFilterChangeDetails { + /// Creates the [DataGridFilterChangeDetails] for the + /// `DataGridFilterChangingCallback` and `DataGridFilterChangedCallback`. + const DataGridFilterChangeDetails({ + required this.column, + required this.filterConditions, + }); + + /// The column where the current filtering is applied. + final GridColumn column; + + /// Holds the collection of [FilterCondition] which are applied currently to + /// the column. + final List filterConditions; } /// Sets the `columnSizer` instance to the [RowHeightDetails] class. void setColumnSizerInRowHeightDetailsArgs( - RowHeightDetails rowHeightDetails, ColumnSizer columnSizer) { + RowHeightDetails rowHeightDetails, + ColumnSizer columnSizer, +) { rowHeightDetails._columnSizer = columnSizer; } ///Sets the `dataGridConfiguration` instance to the [DataGridSwipeStartDetails] class void setSwipeOffsetInDataGridSwipeStartDetailsArgs( - DataGridConfiguration dataGridConfiguration, - DataGridSwipeStartDetails swipeStartDetails) { + DataGridConfiguration dataGridConfiguration, + DataGridSwipeStartDetails swipeStartDetails, +) { swipeStartDetails._dataGridConfiguration = dataGridConfiguration; } + +///Holds the arguments of the [SfDataGrid.onColumnDragging] callback. +@immutable +class DataGridColumnDragDetails { + /// Creates the [DataGridColumnDragDetails] for the + /// `DataGridColumnDraggingCallback`. + const DataGridColumnDragDetails({ + required this.from, + required this.to, + required this.action, + required this.offset, + }); + + //// An index of a column that is being dragged. + final int from; + + /// An index of a column where the already dragged column is being dropped. + final int? to; + + /// The current action of the drag and drop operation. + final DataGridColumnDragAction action; + + /// The global position at which the pointer contacted the screen. + final Offset offset; +} + +/// The collection of [GroupColumnDetails] to group the columns in the [SfDataGrid]. +class ColumnGroup { + /// + ColumnGroup({required this.name, required this.sortGroupRows}); + + /// The name of a column to be grouped. + late String name; + + /// Decides whether to group the column with sort ascending. + late bool sortGroupRows; +} + +/// Signature for [SfDataGrid.groupExpanding], [SfDataGrid.groupCollapsing], callbacks. +@immutable +class DataGridGroupChangingDetails { + /// + const DataGridGroupChangingDetails({ + required this.key, + required this.groupLevel, + required this.isExpanded, + }); + + /// Specifies the key in a caption summary row. + final String key; + + /// Specifies the group level in a caption summary row. + /// + /// It will be zero if it is a top-level group, and it will be equal to the length of [DataGridSource.groupedColumns] if it is a bottom-level group. + final int groupLevel; + + /// Decides whether the group is expanded. + final bool isExpanded; +} + +/// Signature for [SfDataGrid.groupExpanded], [SfDataGrid.groupCollapsed] callbacks. +@immutable +class DataGridGroupChangedDetails { + /// + const DataGridGroupChangedDetails({ + required this.key, + required this.groupLevel, + required this.isExpanded, + }); + + /// Specifies the key in a caption summary row. + final String key; + + /// Specifies the group level in a caption summary row. + /// + /// It will be zero if it is a top-level group, and it will be equal to the length of [DataGridSource.groupedColumns] if it is a bottom-level group. + final int groupLevel; + + /// Decides whether the group is expanded. + final bool isExpanded; +} + +/// Signature for [SfDataGrid.onCheckboxValueChanged] callbacks. +class DataGridCheckboxValueChangedDetails { + /// + const DataGridCheckboxValueChangedDetails({ + required this.value, + required this.row, + required this.rowType, + }); + + /// Specifies the current value of the checkbox. + final bool value; + + /// Specifies the [DataGridRow] that associates with the check box in the checkbox column. + /// + /// The value will be `null` if the checkbox is part of a column header, indicating that this checkbox is not associated with a specific data row. + final DataGridRow? row; + + /// Specifies the row type of the checkbox in the checkbox column. + /// + /// It notifies whether the checkbox value of the data row is changed, or header row changed. + final RowType rowType; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart index 637baa98a..74825467b 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart' hide SelectionChangedCallback; import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../../../datagrid.dart'; +import '../grouping/grouping.dart'; import '../runtime/cell_renderers.dart'; import '../runtime/column.dart'; import '../runtime/generator.dart'; import '../selection/selection_manager.dart'; import '../sfdatagrid.dart'; import '../widgets/scrollview_widget.dart'; -import 'enums.dart'; /// Signature for the `_DataGridConfiguration` callback. typedef DataGridStateDetails = DataGridConfiguration Function(); @@ -58,6 +60,9 @@ class DataGridConfiguration { /// The [ColumnResizeController] used to control the column resizing operations. late ColumnResizeController columnResizeController; + /// Provides the base functionalities to process the filtering in [SfDataGrid]. + DataGridFilterHelper? dataGridFilterHelper; + /// The width of the current datagrid view. late double viewWidth; @@ -186,6 +191,17 @@ class DataGridConfiguration { ///Defaults to false bool shrinkWrapColumns = false; + /// Decides whether the UI filtering should be enabled for all the columns. + /// + /// [GridColumn.allowFiltering] has the highest priority over this property. + bool allowFiltering = false; + + /// Called when the filtering is being applied through UI filtering. + DataGridFilterChangingCallback? onFilterChanging; + + /// Called after the UI filtering is applied to [SfDataGrid]. + DataGridFilterChangedCallback? onFilterChanged; + /// Contains all the properties of the checkbox column. DataGridCheckboxColumnSettings checkboxColumnSettings = const DataGridCheckboxColumnSettings(); @@ -247,7 +263,7 @@ class DataGridConfiguration { /// Animates the swiping to the swipeMaxOffset or beginning position. Animation? swipingAnimation; - /// The `AnimationContoller` is used to animate the swiping offsets. + /// The `AnimationController` is used to animate the swiping offsets. AnimationController? swipingAnimationController; /// A collection of [StackedHeaderRow]. @@ -266,6 +282,10 @@ class DataGridConfiguration { /// this class to configure a [SfDataGridTheme] widget. DataGridThemeHelper? dataGridThemeHelper; + /// Instance of a [SfLocalizations] class that provide the localized resource + /// values to the UI filtering labels. + late SfLocalizations localizations; + /// Controls a vertical scrolling in DataGrid. ScrollController? verticalScrollController; @@ -298,6 +318,15 @@ class DataGridConfiguration { /// Invoked when the row is selected. SelectionChangedCallback? onSelectionChanged; + /// Invoked when the Checkbox is being selected or being unselected + DataGridCheckboxValueChangedCallback? onCheckboxValueChanged; + + /// Invoked when the column is being sorted. + DataGridColumnSortChangingCallback? onColumnSortChanging; + + /// Invoked when the column is sorted. + DataGridColumnSortChangedCallback? onColumnSortChanged; + /// Invoked when the cell is activated. CurrentCellActivatedCallback? onCurrentCellActivated; @@ -336,7 +365,94 @@ class DataGridConfiguration { /// The widget to show over the bottom of the [SfDataGrid]. Widget? footer; - /// An instance of a [ThemeData.colorScheme] that can be used to configure the + /// The widget to display when the data source of the [SfDataGrid] is empty. + Widget? placeholder; + + /// An instance of a [SfColorScheme.colorScheme] that can be used to configure the /// color properties in the [SfDataGrid]. - ColorScheme? colorScheme; + SfColorScheme? colorScheme; + + /// Decides whether the Vertical ScrollController can be disposed of in the source itself. + /// Default to true. + bool disposeVerticalScrollController = true; + + /// Decides whether the Horizontal ScrollController can be disposed of in the source itself. + /// Default to true. + bool disposeHorizontalScrollController = true; + + /// Defines the OutlinedBorder for the shape of the checkbox. + OutlinedBorder? checkboxShape; + + /// Checks whether the platform is mac or not. + bool isMacPlatform = false; + + /// Checks whether the command key is pressed or not for enable multi sorting + /// in mac platform. + bool isCommandKeyPressed = false; + + /// Decides whether the horizontal scrollbar should be shown. + /// Defaults to true. + bool showHorizontalScrollbar = true; + + /// Decides whether the vertical scrollbar should be shown. + /// Defaults to true. + bool showVerticalScrollbar = true; + + /// A boolean flag to indicate whether column dragging is allowed or not. + bool allowColumnsDragging = false; + + /// Called when a column has been dragged and dropped to new location. + DataGridColumnDraggingCallback? onColumnDragging; + + /// Called to obtain the feedback widget for the column when it is about to drag. + /// If null, a [Text] widget will be loaded by default with the dragging header cell constraints. + ColumnDragFeedbackBuilderCallback? columnDragFeedbackBuilder; + + /// A global key used to uniquely identify the DataGrid widget. + GlobalKey dataGridKey = GlobalKey(); + + /// Controller for handling column drag and drop operation. + late ColumnDragAndDropController columnDragAndDropController; + + /// Decides whether the column header icons should be shown when hovering the header cells. + /// + /// Defaults to false. + late bool showColumnHeaderIconOnHover = false; + + /// Hold instance of grouping + late Group? group; + + /// Decides whether the caption summary row should be expanded initially when applying column grouping. + /// + /// The default value is true. + late bool autoExpandGroups; + + /// Checks whether to expand and collapse a specific group interactively by tapping the caption summary row. The [groupExpanderIcon] will be displayed leading in a caption summary row if it is set to true to indicate the group's expand-collapse state. + /// + /// The default value is false. + late bool allowExpandCollapseGroup; + + /// Invoked when a group is being expanded. + /// + /// Return false to restrict a specific group from being expanded. + late GroupChangingCallback? groupExpanding; + + /// Invoked when a group is expanded. + late GroupChangedCallback? groupExpanded; + + /// Invoked when a group is being collapsed. + /// + /// Return false to restrict a specific group from being collapsed. + late GroupChangingCallback? groupCollapsing; + + /// Invoked when a group is collapsed. + late GroupChangedCallback? groupCollapsed; + + /// Displays the title in the group caption summary row using the provided format. + /// + /// The default format is {ColumnName} : {Key} – {ItemsCount} Items. The name of a [ColumnGroup] will be replaced by the {ColumnName}, the key in a caption summary row will be replaced by the {Key}, and the number of items grouped in a given group will be replaced by the {ItemsCount}. + late String groupCaptionTitleFormat; + + /// Holds the expand and collapse row index. + late int groupExpandCollapseRowIndex = -1; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart index 392369edc..e37535502 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart @@ -2,11 +2,14 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart' hide DataCell, DataRow; +import 'package:syncfusion_flutter_core/localizations.dart'; import '../../grid_common/row_column_index.dart'; import '../../grid_common/scroll_axis.dart'; import '../../grid_common/visible_line_info.dart'; +import '../grouping/grouping.dart'; import '../runtime/column.dart'; +import '../runtime/generator.dart'; import '../sfdatagrid.dart'; import 'datagrid_configuration.dart'; import 'enums.dart'; @@ -24,7 +27,9 @@ int getHeaderIndex(DataGridConfiguration dataGridConfiguration) { // actual column header row index. if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { headerIndex -= getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.top); + dataGridConfiguration, + GridTableSummaryRowPosition.top, + ); } return headerIndex < 0 ? 0 : headerIndex; @@ -34,7 +39,14 @@ int getHeaderIndex(DataGridConfiguration dataGridConfiguration) { /// In this we will not include the column like, indent column, row /// header etc int resolveToGridVisibleColumnIndex( - DataGridConfiguration dataGridConfiguration, int columnIndex) { + DataGridConfiguration dataGridConfiguration, + int columnIndex, +) { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final int length = dataGridConfiguration.source.groupedColumns.length; + columnIndex -= length; + } + if (columnIndex >= dataGridConfiguration.container.columnCount) { return -1; } @@ -45,14 +57,21 @@ int resolveToGridVisibleColumnIndex( /// Helps to resolve the column index based on the [DataGridRowAdapter.cells] /// count when indent cell, row header, [SfDataGrid.showCheckboxColumn] are enabled. int resolveToDataGridRowAdapterCellIndex( - DataGridConfiguration dataGridConfiguration, int columnIndex) { - if (columnIndex >= dataGridConfiguration.container.columnCount) { + DataGridConfiguration dataGridConfiguration, + int columnIndex, +) { + final int totalColumnCount = + dataGridConfiguration.container.columnCount + + dataGridConfiguration.source.groupedColumns.length; + + if (columnIndex >= totalColumnCount) { return -1; } if (columnIndex == 0) { return columnIndex; } else { + columnIndex -= dataGridConfiguration.source.groupedColumns.length; columnIndex -= dataGridConfiguration.showCheckboxColumn ? 1 : 0; return columnIndex; } @@ -60,7 +79,9 @@ int resolveToDataGridRowAdapterCellIndex( /// Help to resolve the record index to [SfDataGrid] position row index. int resolveToRowIndex( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { if (rowIndex < 0) { return -1; } @@ -73,6 +94,78 @@ int resolveToRowIndex( } } +/// Help to resolve the record index to display element position row index. +int resolveStartRecordIndex( + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { + if (rowIndex < 0) { + return -1; + } + final int headerLineCount = resolveStartIndexBasedOnPosition( + dataGridConfiguration, + ); + rowIndex -= headerLineCount; + final int totalCount = + dataGridConfiguration.source.groupedColumns.isNotEmpty + ? dataGridConfiguration.group!.displayElements!.grouped.length + : dataGridConfiguration.container.rowCount; + if (rowIndex >= 0 && rowIndex < totalCount) { + return rowIndex; + } else { + return -1; + } +} + +/// Help to resolve the group from the display elements. +dynamic getGroupElement( + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { + if (rowIndex >= 0 && + rowIndex < dataGridConfiguration.group!.displayElements!.grouped.length) { + return dataGridConfiguration.group!.displayElements!.grouped[rowIndex]; + } + return null; +} + +/// Help to get the row by the row index. +DataGridRow? getDataRow( + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final dynamic element = getGroupElement(dataGridConfiguration, rowIndex); + return element is DataGridRow ? element : null; + } + + final List rows = effectiveRows(dataGridConfiguration.source); + return (rowIndex >= 0 && rowIndex < rows.length) ? rows[rowIndex] : null; +} + +/// Help to resolves the effective row count. +int resolveEffectiveRowCount(DataGridConfiguration dataGridConfiguration) { + return dataGridConfiguration.source.groupedColumns.isNotEmpty + ? dataGridConfiguration.group!.displayElements!.grouped.length + : effectiveRows(dataGridConfiguration.source).length; +} + +/// Help to resolve the next grouped row from the display elements. +dynamic getNextGroupInfo( + dynamic rowData, + DataGridConfiguration dataGridConfiguration, +) { + final List grouped = + dataGridConfiguration.group!.displayElements!.grouped; + final int index = grouped.indexOf(rowData) + 1; + + if (index >= 0 && index < grouped.length) { + return grouped[index]; + } else { + return null; + } +} + /// Help to get the [SfDataGrid] swipeMaxOffset when it changing at runtime double getSwipeMaxOffset(DataGridConfiguration dataGridConfiguration) { return dataGridConfiguration.effectiveSwipeMaxOffset ?? @@ -81,7 +174,9 @@ double getSwipeMaxOffset(DataGridConfiguration dataGridConfiguration) { /// Help to resolve the [SfDataGrid] position row index to record index. int resolveToRecordIndex( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { if (rowIndex < 0) { return -1; } @@ -91,8 +186,11 @@ int resolveToRecordIndex( } rowIndex = rowIndex - resolveStartIndexBasedOnPosition(dataGridConfiguration); - if (rowIndex >= 0 && - rowIndex <= effectiveRows(dataGridConfiguration.source).length - 1) { + final int endIndex = + dataGridConfiguration.source.groupedColumns.isNotEmpty + ? dataGridConfiguration.group!.displayElements!.grouped.length + : effectiveRows(dataGridConfiguration.source).length; + if (rowIndex >= 0 && (rowIndex < endIndex)) { return rowIndex; } else { return -1; @@ -101,29 +199,40 @@ int resolveToRecordIndex( /// Helps to resolve the [SfDataGrid] row column index to record [RowColumnIndex]. RowColumnIndex resolveToRecordRowColumnIndex( - DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { - final int rowIndex = - resolveToRecordIndex(dataGridConfiguration, rowColumnIndex.rowIndex); + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, +) { + final int rowIndex = resolveToRecordIndex( + dataGridConfiguration, + rowColumnIndex.rowIndex, + ); final int columnIndex = resolveToGridVisibleColumnIndex( - dataGridConfiguration, rowColumnIndex.columnIndex); + dataGridConfiguration, + rowColumnIndex.columnIndex, + ); return RowColumnIndex(rowIndex, columnIndex); } /// Helps to resolve the row and column index according to DataGrid alignment RowColumnIndex resolveToRowColumnIndex( - DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { - final int rowIndex = - resolveToRowIndex(dataGridConfiguration, rowColumnIndex.rowIndex); + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, +) { + final int rowIndex = resolveToRowIndex( + dataGridConfiguration, + rowColumnIndex.rowIndex, + ); final int columnIndex = resolveToGridVisibleColumnIndex( - dataGridConfiguration, rowColumnIndex.columnIndex); + dataGridConfiguration, + rowColumnIndex.columnIndex, + ); return RowColumnIndex(rowIndex, columnIndex); } /// Helps to find the exact starting scrolling row index. int resolveStartIndexBasedOnPosition( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { return dataGridConfiguration.headerLineCount; } @@ -136,8 +245,14 @@ int resolveToStartColumnIndex(DataGridConfiguration dataGridConfiguration) => 0; /// order. Its ignore the indent column, row header and provide the exact /// column index int resolveToScrollColumnIndex( - DataGridConfiguration dataGridConfiguration, int gridColumnIndex) => - gridColumnIndex; + DataGridConfiguration dataGridConfiguration, + int gridColumnIndex, +) { + final int indentColumnCount = + dataGridConfiguration.source.groupedColumns.length; + + return gridColumnIndex + indentColumnCount; +} /// Get the last index of frozen column in left side view, it will /// consider the row header,indent column and frozen column @@ -154,7 +269,8 @@ int getLastFrozenColumnIndex(DataGridConfiguration dataGridConfiguration) { /// Get the starting index of frozen column in right side view, it /// will consider the right frozen pane int getStartFooterFrozenColumnIndex( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { final int columnsCount = dataGridConfiguration.container.columnCount; if (columnsCount <= 0 || dataGridConfiguration.footerFrozenColumnsCount <= 0) { @@ -167,7 +283,9 @@ int getStartFooterFrozenColumnIndex( /// Get the last frozen row index in top of data grid, it /// will consider the stacked header, header and top frozen pane int getLastFrozenRowIndex(DataGridConfiguration dataGridConfiguration) { - if (dataGridConfiguration.frozenRowsCount <= 0) { + final int frozenRowCount = dataGridConfiguration.frozenRowsCount; + if (frozenRowCount <= 0 || + frozenRowCount > resolveEffectiveRowCount(dataGridConfiguration)) { return -1; } @@ -178,8 +296,16 @@ int getLastFrozenRowIndex(DataGridConfiguration dataGridConfiguration) { /// Get the starting frozen row index in bottom of data grid, it /// will consider the bottom table summary and bottom frozen pane int getStartFooterFrozenRowIndex(DataGridConfiguration dataGridConfiguration) { + final int effectiveRowCount = resolveEffectiveRowCount(dataGridConfiguration); + // Check footer frozen rows count exceeds the effective row count. + final bool isFrozenRowCountExceeded = + (effectiveRowCount - dataGridConfiguration.frozenRowsCount).isNegative || + dataGridConfiguration.footerFrozenRowsCount > effectiveRowCount; + final int rowCount = dataGridConfiguration.container.rowCount; - if (rowCount <= 0 || dataGridConfiguration.footerFrozenRowsCount <= 0) { + if (rowCount <= 0 || + dataGridConfiguration.footerFrozenRowsCount <= 0 || + isFrozenRowCountExceeded) { return -1; } @@ -192,33 +318,66 @@ int getStartFooterFrozenRowIndex(DataGridConfiguration dataGridConfiguration) { /// Checks whether the row is a footer widget row or not. bool isFooterWidgetRow( - int rowIndex, DataGridConfiguration dataGridConfiguration) { + int rowIndex, + DataGridConfiguration dataGridConfiguration, +) { final int bottomSummariesCount = getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); final int footerIndex = dataGridConfiguration.container.rowCount - bottomSummariesCount - 1; return dataGridConfiguration.footer != null && rowIndex == footerIndex; } +/// Checks whether the row is a caption summary row or not. +bool isCaptionSummaryRow( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + bool canResolveIndex, +) { + if (canResolveIndex) { + if (rowIndex == getHeaderIndex(dataGridConfiguration)) { + return false; + } + rowIndex = resolveToRecordIndex(dataGridConfiguration, rowIndex); + } + + if (rowIndex >= 0 && dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final dynamic group = + dataGridConfiguration.group?.displayElements?.grouped[rowIndex]; + return group != null && group is Group; + } + + return false; +} + /// Returns the row index of a footer widget row. int getFooterViewRowIndex(DataGridConfiguration dataGridConfiguration) { final int bottomSummaryRowsCount = getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); return dataGridConfiguration.container.rowCount - bottomSummaryRowsCount - 1; } //-------------------- Table summary row helper methods ----------------------// -/// Checks whether the givem row is a top summary row or not. +/// Checks whether the given row is a top summary row or not. bool isTopTableSummaryRow( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { if (dataGridConfiguration.tableSummaryRows.isNotEmpty && rowIndex < dataGridConfiguration.headerLineCount) { final int tableSummaryStartIndex = dataGridConfiguration.stackedHeaderRows.length + 1; - final int tableSummaryEndIndex = tableSummaryStartIndex + + final int tableSummaryEndIndex = + tableSummaryStartIndex + getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.top); + dataGridConfiguration, + GridTableSummaryRowPosition.top, + ); return rowIndex >= tableSummaryStartIndex && rowIndex < tableSummaryEndIndex; } @@ -227,12 +386,17 @@ bool isTopTableSummaryRow( /// Checks whether the given row is a bottom summary row or not. bool isBottomTableSummaryRow( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { final int tableSummaryEndIndex = dataGridConfiguration.container.rowCount; - final int tableSummaryStartIndex = tableSummaryEndIndex - + final int tableSummaryStartIndex = + tableSummaryEndIndex - getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); return rowIndex >= tableSummaryStartIndex && rowIndex < tableSummaryEndIndex; } @@ -241,7 +405,9 @@ bool isBottomTableSummaryRow( /// Checks whether the given row index is a table summary row or not. bool isTableSummaryIndex( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { return isTopTableSummaryRow(dataGridConfiguration, rowIndex) || isBottomTableSummaryRow(dataGridConfiguration, rowIndex); } @@ -249,13 +415,17 @@ bool isTableSummaryIndex( /// Checks whether the row index is a start index of a bottom summary rows or not. int getStartBottomSummaryRowIndex(DataGridConfiguration dataGridConfiguration) { final int bottomSummariesCount = getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); return dataGridConfiguration.container.rowCount - bottomSummariesCount; } /// Returns the table summary count based on position. -int getTableSummaryCount(DataGridConfiguration dataGridConfiguration, - GridTableSummaryRowPosition position) { +int getTableSummaryCount( + DataGridConfiguration dataGridConfiguration, + GridTableSummaryRowPosition position, +) { if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { return dataGridConfiguration.tableSummaryRows .where((GridTableSummaryRow row) => row.position == position) @@ -265,26 +435,46 @@ int getTableSummaryCount(DataGridConfiguration dataGridConfiguration, } /// Returns the title column span to the table summary column. -int getSummaryTitleColumnSpan(DataGridConfiguration dataGridConfiguration, - GridTableSummaryRow tableSummaryRow) { - int titleSpan = dataGridConfiguration.frozenColumnsCount > 0 - ? min(dataGridConfiguration.frozenColumnsCount, - tableSummaryRow.titleColumnSpan) - : tableSummaryRow.titleColumnSpan; +int getSummaryTitleColumnSpan( + DataGridConfiguration dataGridConfiguration, + GridTableSummaryRow tableSummaryRow, +) { + int titleSpan = + dataGridConfiguration.frozenColumnsCount > 0 + ? min( + dataGridConfiguration.frozenColumnsCount, + tableSummaryRow.titleColumnSpan, + ) + : tableSummaryRow.titleColumnSpan; titleSpan = min(titleSpan, dataGridConfiguration.container.columnCount); return titleSpan; } /// Returns the span count for the table summary column. -int getSummaryColumnSpan(DataGridConfiguration dataGridConfiguration, int index, - RowType rowType, GridTableSummaryRow? tableSummaryRow) { +int getSummaryColumnSpan( + DataGridConfiguration dataGridConfiguration, + int index, + RowType rowType, + GridTableSummaryRow? tableSummaryRow, [ + int rowLevel = 0, +]) { int span = 0; - final int columnCount = dataGridConfiguration.container.columnCount; + int columnCount = dataGridConfiguration.container.columnCount; + if (rowType == RowType.captionSummaryCoveredRow) { + columnCount -= rowLevel; + } else if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + (rowType == RowType.tableSummaryCoveredRow || + rowType == RowType.tableSummaryRow)) { + final int length = dataGridConfiguration.source.groupedColumns.length; + columnCount -= length; + } index = resolveToScrollColumnIndex(dataGridConfiguration, index); if (rowType == RowType.tableSummaryRow) { if (tableSummaryRow != null) { - final int titleSpan = - getSummaryTitleColumnSpan(dataGridConfiguration, tableSummaryRow); + final int titleSpan = getSummaryTitleColumnSpan( + dataGridConfiguration, + tableSummaryRow, + ); if (titleSpan > 0 && index < titleSpan) { span = titleSpan - 1; } @@ -298,15 +488,18 @@ int getSummaryColumnSpan(DataGridConfiguration dataGridConfiguration, int index, /// Helps to get the table summary row for the given rowIndex from the /// `SfDataGrid.tableSummaryRows` collection. GridTableSummaryRow? getTableSummaryRow( - DataGridConfiguration dataGridConfiguration, - int rowIndex, - GridTableSummaryRowPosition position) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, + GridTableSummaryRowPosition position, +) { GridTableSummaryRow getSummaryRowByPosition( - GridTableSummaryRowPosition position, int index) { - final List summaryRows = dataGridConfiguration - .tableSummaryRows - .where((GridTableSummaryRow row) => row.position == position) - .toList(); + GridTableSummaryRowPosition position, + int index, + ) { + final List summaryRows = + dataGridConfiguration.tableSummaryRows + .where((GridTableSummaryRow row) => row.position == position) + .toList(); return summaryRows[index]; } @@ -314,8 +507,10 @@ GridTableSummaryRow? getTableSummaryRow( if (position == GridTableSummaryRowPosition.bottom) { final int startTableSummaryIndex = dataGridConfiguration.container.rowCount - - getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + getTableSummaryCount( + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); currentSummaryRowIndex = rowIndex - startTableSummaryIndex; } else if (position == GridTableSummaryRowPosition.top) { final int startTableSummaryIndex = @@ -328,7 +523,9 @@ GridTableSummaryRow? getTableSummaryRow( /// Calculates the summary value for the given summary column based on the /// summary type. String getSummaryValue( - GridSummaryColumn summaryColumn, List rows) { + GridSummaryColumn summaryColumn, + List rows, +) { switch (summaryColumn.summaryType) { case GridSummaryType.sum: return calculateSum(rows, summaryColumn); @@ -349,8 +546,8 @@ String calculateSum(List rows, GridSummaryColumn summaryColumn) { bool isNumericColumn = false; for (final DataGridRow row in rows) { final DataGridCell? cell = row.getCells().firstWhereOrNull( - (DataGridCell element) => - element.columnName == summaryColumn.columnName); + (DataGridCell element) => element.columnName == summaryColumn.columnName, + ); if (cell != null && cell.value != null) { if (!isNumericColumn && cell.value is! num) { break; @@ -369,13 +566,15 @@ String calculateSum(List rows, GridSummaryColumn summaryColumn) { /// Calculates the minimum value for the given summary column. String calculateMinimum( - List rows, GridSummaryColumn summaryColumn) { + List rows, + GridSummaryColumn summaryColumn, +) { dynamic minimum; bool isNumericColumn = false; for (final DataGridRow row in rows) { final DataGridCell? cell = row.getCells().firstWhereOrNull( - (DataGridCell element) => - element.columnName == summaryColumn.columnName); + (DataGridCell element) => element.columnName == summaryColumn.columnName, + ); if (cell != null && cell.value != null) { if (!isNumericColumn && cell.value is! num) { break; @@ -395,13 +594,15 @@ String calculateMinimum( /// Calculates the maximum value for the given summary column. String calculateMaximum( - List rows, GridSummaryColumn summaryColumn) { + List rows, + GridSummaryColumn summaryColumn, +) { dynamic maximum; bool isNumericColumn = false; for (final DataGridRow row in rows) { final DataGridCell? cell = row.getCells().firstWhereOrNull( - (DataGridCell element) => - element.columnName == summaryColumn.columnName); + (DataGridCell element) => element.columnName == summaryColumn.columnName, + ); if (cell != null && cell.value != null) { if (!isNumericColumn && cell.value is! num) { break; @@ -421,14 +622,16 @@ String calculateMaximum( /// Calculates the average value for the given summary column. String calculateAverage( - List rows, GridSummaryColumn summaryColumn) { + List rows, + GridSummaryColumn summaryColumn, +) { num? sum; int count = 0; bool isNumericColumn = false; for (final DataGridRow row in rows) { final DataGridCell? cell = row.getCells().firstWhereOrNull( - (DataGridCell element) => - element.columnName == summaryColumn.columnName); + (DataGridCell element) => element.columnName == summaryColumn.columnName, + ); if (cell != null && cell.value != null) { if (!isNumericColumn && cell.value is! num) { @@ -447,6 +650,278 @@ String calculateAverage( return sum != null ? (sum / count).toString() : ''; } +//--------------------- Filtering helper methods ------------------------// + +/// Compare the AND logical operator for the filtering support. +bool compareAnd(bool? a, bool b) { + return (a == null || a) ? b : a; +} + +/// Compare the OR logical operator for the filtering support. +bool compareOr(bool? a, bool b) { + return (a != null && a) || b; +} + +/// Compare the old and new comparer values based on `predicateType`. +bool compare(bool? comparerValue, bool result, FilterOperator predicateType) { + return predicateType == FilterOperator.or + ? compareOr(comparerValue, result) + : compareAnd(comparerValue, result); +} + +/// Checkes whether the cell value and the filter value is equal or not. +bool compareEquals(FilterCondition condition, Object? cellValue) { + // To handle the null value. + if (condition.value == null && cellValue == null) { + return true; + } + + // To handle the null and empty values. + if ((condition.value == null && cellValue != null) || + (condition.value != null && cellValue == null)) { + return false; + } + + if (cellValue is String || + condition.filterBehavior == FilterBehavior.stringDataType) { + return _compareByType(condition, cellValue, 'equals'); + } + + final int? value = _getCompareValue(cellValue, condition.value); + return value != null && value == 0; +} + +/// Checkes whether the filter value contains in any cell value or not. +bool compareContains(FilterCondition condition, Object? cellValue) { + return _compareByType(condition, cellValue, 'contains'); +} + +/// Checkes whether the filter value begins with any cell value. +bool compareBeginsWith(FilterCondition condition, Object? cellValue) { + return _compareByType(condition, cellValue, 'startsWidth'); +} + +/// Checkes whether the filter value ends with any cell value. +bool compareEndsWith(FilterCondition condition, Object? cellValue) { + return _compareByType(condition, cellValue, 'endsWidth'); +} + +bool _compareByType(FilterCondition condition, Object? cellValue, String type) { + String displayText = cellValue?.toString() ?? ''; + String filterText = condition.value?.toString() ?? ''; + if (!condition.isCaseSensitive) { + filterText = filterText.toLowerCase(); + displayText = displayText.toLowerCase(); + } + + switch (type) { + case 'contains': + return displayText.contains(filterText); + case 'startsWidth': + return displayText.startsWith(filterText); + case 'endsWidth': + return displayText.endsWith(filterText); + case 'equals': + return displayText == filterText; + default: + return false; + } +} + +/// Checkes whether any cell has greater value than the filter value. +bool compareGreaterThan( + FilterCondition condition, + Object? cellValue, [ + bool checkEqual = false, +]) { + if (condition.value == null || cellValue == null) { + return false; + } + + final int? value = _getCompareValue(cellValue, condition.value); + if (value != null) { + return checkEqual ? (value == 0 || value == 1) : value == 1; + } + + return false; +} + +/// Checkes whether any cell has less value than the filter value. +bool compareLessThan( + FilterCondition condition, + Object? cellValue, [ + bool checkEqual = false, +]) { + if (condition.value == null || cellValue == null) { + return false; + } + + final int? value = _getCompareValue(cellValue, condition.value); + if (value != null) { + return checkEqual ? (value == 0 || value == -1) : value == -1; + } + + return false; +} + +int? _getCompareValue(Object? cellValue, Object? filterValue) { + if (cellValue == null || filterValue == null) { + return null; + } + + if (cellValue is num) { + return cellValue.compareTo(filterValue as num); + } else if (cellValue is DateTime) { + return cellValue.compareTo(filterValue as DateTime); + } else if (cellValue is Comparable) { + return cellValue.compareTo(filterValue); + } + return null; +} + +/// Gets the advanced filter name. +String getFilterTileText( + SfLocalizations localizations, + AdvancedFilterType type, +) { + switch (type) { + case AdvancedFilterType.text: + return localizations.textFiltersDataGridFilteringLabel; + case AdvancedFilterType.numeric: + return localizations.numberFiltersDataGridFilteringLabel; + case AdvancedFilterType.date: + return localizations.dateFiltersDataGridFilteringLabel; + } +} + +/// Returns the Sort button text based on the filter type. +String getSortButtonText( + SfLocalizations localizations, + bool isAscending, + AdvancedFilterType type, +) { + switch (type) { + case AdvancedFilterType.text: + return isAscending + ? localizations.sortAToZDataGridFilteringLabel + : localizations.sortZToADataGridFilteringLabel; + case AdvancedFilterType.numeric: + return isAscending + ? localizations.sortSmallestToLargestDataGridFilteringLabel + : localizations.sortLargestToSmallestDataGridFilteringLabel; + case AdvancedFilterType.date: + return isAscending + ? localizations.sortOldestToNewestDataGridFilteringLabel + : localizations.sortNewestToOldestDataGridFilteringLabel; + } +} + +/// Returns the `FilterType` based on the given value. +FilterType getFilterType( + DataGridConfiguration dataGridConfiguration, + String value, +) { + bool isEqual(String labelValue) { + return labelValue == value; + } + + final SfLocalizations localizations = dataGridConfiguration.localizations; + + if (isEqual(localizations.equalsDataGridFilteringLabel) || + isEqual(localizations.emptyDataGridFilteringLabel) || + isEqual(localizations.nullDataGridFilteringLabel)) { + return FilterType.equals; + } else if (isEqual(localizations.doesNotEqualDataGridFilteringLabel) || + isEqual(localizations.notEmptyDataGridFilteringLabel) || + isEqual(localizations.notNullDataGridFilteringLabel)) { + return FilterType.notEqual; + } else if (isEqual(localizations.beginsWithDataGridFilteringLabel)) { + return FilterType.beginsWith; + } else if (isEqual(localizations.doesNotBeginWithDataGridFilteringLabel)) { + return FilterType.doesNotBeginWith; + } else if (isEqual(localizations.endsWithDataGridFilteringLabel)) { + return FilterType.endsWith; + } else if (isEqual(localizations.doesNotEndWithDataGridFilteringLabel)) { + return FilterType.doesNotEndsWith; + } else if (isEqual(localizations.containsDataGridFilteringLabel)) { + return FilterType.contains; + } else if (isEqual(localizations.doesNotContainDataGridFilteringLabel)) { + return FilterType.doesNotContain; + } else if (isEqual(localizations.lessThanDataGridFilteringLabel) || + isEqual(localizations.beforeDataGridFilteringLabel)) { + return FilterType.lessThan; + } else if (isEqual(localizations.beforeOrEqualDataGridFilteringLabel) || + isEqual(localizations.lessThanOrEqualDataGridFilteringLabel)) { + return FilterType.lessThanOrEqual; + } else if (isEqual(localizations.greaterThanDataGridFilteringLabel) || + isEqual(localizations.afterDataGridFilteringLabel)) { + return FilterType.greaterThan; + } else if (isEqual(localizations.greaterThanOrEqualDataGridFilteringLabel) || + isEqual(localizations.afterOrEqualDataGridFilteringLabel)) { + return FilterType.greaterThanOrEqual; + } + return FilterType.equals; +} + +/// Gets the name of the given `FilterType`. +String getFilterName( + DataGridConfiguration dataGridConfiguration, + FilterType type, + Object? value, +) { + final SfLocalizations localizations = dataGridConfiguration.localizations; + switch (type) { + case FilterType.equals: + if (value == null) { + return localizations.nullDataGridFilteringLabel; + } else if (value is String && value.isEmpty) { + return localizations.emptyDataGridFilteringLabel; + } else { + return localizations.equalsDataGridFilteringLabel; + } + case FilterType.notEqual: + if (value == null) { + return localizations.notNullDataGridFilteringLabel; + } else if (value is String && value.isEmpty) { + return localizations.notEmptyDataGridFilteringLabel; + } else { + return localizations.doesNotEqualDataGridFilteringLabel; + } + case FilterType.beginsWith: + return localizations.beginsWithDataGridFilteringLabel; + case FilterType.doesNotBeginWith: + return localizations.doesNotBeginWithDataGridFilteringLabel; + case FilterType.endsWith: + return localizations.endsWithDataGridFilteringLabel; + case FilterType.doesNotEndsWith: + return localizations.doesNotEndWithDataGridFilteringLabel; + case FilterType.contains: + return localizations.containsDataGridFilteringLabel; + case FilterType.doesNotContain: + return localizations.doesNotContainDataGridFilteringLabel; + case FilterType.lessThan: + if (value is DateTime) { + return localizations.beforeDataGridFilteringLabel; + } + return localizations.lessThanDataGridFilteringLabel; + case FilterType.lessThanOrEqual: + if (value is DateTime) { + return localizations.beforeOrEqualDataGridFilteringLabel; + } + return localizations.lessThanOrEqualDataGridFilteringLabel; + case FilterType.greaterThan: + if (value is DateTime) { + return localizations.afterDataGridFilteringLabel; + } + return localizations.greaterThanDataGridFilteringLabel; + case FilterType.greaterThanOrEqual: + if (value is DateTime) { + return localizations.afterOrEqualDataGridFilteringLabel; + } + return localizations.greaterThanOrEqualDataGridFilteringLabel; + } +} + //--------------------DataGridIndex-Resolving-Helpers-End---------------------// //----------------------------------------------------------------------------// @@ -454,8 +929,11 @@ String calculateAverage( //----------------------------------------------------------------------------// /// Helps to get the sequence of spanned cell indexes -List getChildSequence(DataGridConfiguration dataGridConfiguration, - StackedHeaderCell? column, int rowIndex) { +List getChildSequence( + DataGridConfiguration dataGridConfiguration, + StackedHeaderCell? column, + int rowIndex, +) { final List childSequenceNo = []; if (column != null && column.columnNames.isNotEmpty) { @@ -473,17 +951,24 @@ List getChildSequence(DataGridConfiguration dataGridConfiguration, } /// Helps to find the total count of row spanned. -int getRowSpan(DataGridConfiguration dataGridConfiguration, int rowIndex, - int columnIndex, bool isStackedHeader, - {String? mappingName, StackedHeaderCell? stackedHeaderCell}) { +int getRowSpan( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + int columnIndex, + bool isStackedHeader, { + String? mappingName, + StackedHeaderCell? stackedHeaderCell, +}) { int rowSpan = 0; int startIndex = 0; int endIndex = 0; if (isStackedHeader && stackedHeaderCell != null) { - final List> spannedColumns = - getConsecutiveRanges(getChildColumnIndexes(stackedHeaderCell)); - final List? spannedColumn = spannedColumns - .singleWhereOrNull((List element) => element.first == columnIndex); + final List> spannedColumns = getConsecutiveRanges( + getChildColumnIndexes(stackedHeaderCell), + ); + final List? spannedColumn = spannedColumns.singleWhereOrNull( + (List element) => element.first == columnIndex, + ); if (spannedColumn != null) { startIndex = spannedColumn.reduce(min); endIndex = startIndex + spannedColumn.length - 1; @@ -500,8 +985,9 @@ int getRowSpan(DataGridConfiguration dataGridConfiguration, int rowIndex, dataGridConfiguration.stackedHeaderRows[rowIndex]; for (final StackedHeaderCell stackedColumn in stackedHeaderRow.cells) { if (isStackedHeader) { - final List> columnsRange = - getConsecutiveRanges(getChildColumnIndexes(stackedColumn)); + final List> columnsRange = getConsecutiveRanges( + getChildColumnIndexes(stackedColumn), + ); for (final List column in columnsRange) { if ((startIndex >= column.first && startIndex <= column.last) || (endIndex >= column.first && endIndex <= column.last)) { @@ -557,20 +1043,24 @@ List> getConsecutiveRanges(List columnsIndex) { /// Get the visible line based on view size and scroll offset. /// Based on the [TextDirection] it will return visible lines info. VisibleLinesCollection getVisibleLines( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { if (dataGridConfiguration.textDirection == TextDirection.rtl) { dataGridConfiguration.container.scrollColumns.markDirty(); } return dataGridConfiguration.container.scrollColumns.getVisibleLines( - dataGridConfiguration.textDirection == TextDirection.rtl); + dataGridConfiguration.textDirection == TextDirection.rtl, + ); } /// Helps to scroll the [SfDataGrid] vertically. /// [canAnimate]: decide to apply animation on scrolling or not. Future scrollVertical( - DataGridConfiguration dataGridConfiguration, double verticalOffset, - [bool canAnimate = false]) async { + DataGridConfiguration dataGridConfiguration, + double verticalOffset, [ + bool canAnimate = false, +]) async { final ScrollController? verticalController = dataGridConfiguration.verticalScrollController; @@ -578,18 +1068,23 @@ Future scrollVertical( return; } - verticalOffset = verticalOffset > verticalController.position.maxScrollExtent - ? verticalController.position.maxScrollExtent - : verticalOffset; - verticalOffset = verticalOffset.isNegative || verticalOffset == 0.0 - ? verticalController.position.minScrollExtent - : verticalOffset; + final double maxScrollExtent = max( + dataGridConfiguration.container.rowHeights.totalExtent - + dataGridConfiguration.viewHeight, + 0.0, + ); + verticalOffset = min(verticalOffset, maxScrollExtent); + verticalOffset = + verticalOffset.isNegative || verticalOffset == 0.0 + ? verticalController.position.minScrollExtent + : verticalOffset; if (canAnimate) { await dataGridConfiguration.verticalScrollController!.animateTo( - verticalOffset, - duration: const Duration(milliseconds: 1000), - curve: Curves.fastOutSlowIn); + verticalOffset, + duration: const Duration(milliseconds: 1000), + curve: Curves.fastOutSlowIn, + ); } else { dataGridConfiguration.verticalScrollController!.jumpTo(verticalOffset); } @@ -599,8 +1094,10 @@ Future scrollVertical( /// Helps to scroll the [SfDataGrid] horizontally. /// [canAnimate]: decide to apply animation on scrolling or not. Future scrollHorizontal( - DataGridConfiguration dataGridConfiguration, double horizontalOffset, - [bool canAnimate = false]) async { + DataGridConfiguration dataGridConfiguration, + double horizontalOffset, [ + bool canAnimate = false, +]) async { final ScrollController? horizontalController = dataGridConfiguration.horizontalScrollController; @@ -608,19 +1105,23 @@ Future scrollHorizontal( return; } + final double maxScrollExtent = max( + dataGridConfiguration.container.columnWidths.totalExtent - + dataGridConfiguration.viewWidth, + 0.0, + ); + horizontalOffset = min(horizontalOffset, maxScrollExtent); horizontalOffset = - horizontalOffset > horizontalController.position.maxScrollExtent - ? horizontalController.position.maxScrollExtent + horizontalOffset.isNegative || horizontalOffset == 0.0 + ? horizontalController.position.minScrollExtent : horizontalOffset; - horizontalOffset = horizontalOffset.isNegative || horizontalOffset == 0.0 - ? horizontalController.position.minScrollExtent - : horizontalOffset; if (canAnimate) { await dataGridConfiguration.horizontalScrollController!.animateTo( - horizontalOffset, - duration: const Duration(milliseconds: 1000), - curve: Curves.fastOutSlowIn); + horizontalOffset, + duration: const Duration(milliseconds: 1000), + curve: Curves.fastOutSlowIn, + ); } else { dataGridConfiguration.horizontalScrollController!.jumpTo(horizontalOffset); } @@ -628,8 +1129,11 @@ Future scrollHorizontal( } /// Decide to enable swipe in [SfDataGrid] -bool canSwipeRow(DataGridConfiguration dataGridConfiguration, - DataGridRowSwipeDirection swipeDirection, double swipeOffset) { +bool canSwipeRow( + DataGridConfiguration dataGridConfiguration, + DataGridRowSwipeDirection swipeDirection, + double swipeOffset, +) { if (dataGridConfiguration.container.horizontalOffset == 0) { if ((dataGridConfiguration.container.extentWidth > dataGridConfiguration.viewWidth) && @@ -657,32 +1161,41 @@ bool canSwipeRow(DataGridConfiguration dataGridConfiguration, /// Decide the swipe direction based on [TextDirection]. DataGridRowSwipeDirection getSwipeDirection( - DataGridConfiguration dataGridConfiguration, double swipingOffset) { + DataGridConfiguration dataGridConfiguration, + double swipingOffset, +) { return swipingOffset >= 0 ? dataGridConfiguration.textDirection == TextDirection.ltr ? DataGridRowSwipeDirection.startToEnd : DataGridRowSwipeDirection.endToStart : dataGridConfiguration.textDirection == TextDirection.ltr - ? DataGridRowSwipeDirection.endToStart - : DataGridRowSwipeDirection.startToEnd; + ? DataGridRowSwipeDirection.endToStart + : DataGridRowSwipeDirection.startToEnd; } /// Helps to get the [DataGridRow] based on respective rowIndex. DataGridRow getDataGridRow( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { final int recordIndex = resolveToRecordIndex(dataGridConfiguration, rowIndex); return effectiveRows(dataGridConfiguration.source)[recordIndex]; } /// Helps to get the [DataGridRowAdapter] based on respective [DataGridRow]. DataGridRowAdapter? getDataGridRowAdapter( - DataGridConfiguration dataGridConfiguration, DataGridRow dataGridRow) { + DataGridConfiguration dataGridConfiguration, + DataGridRow dataGridRow, +) { DataGridRowAdapter buildBlankRow(DataGridRow dataGridRow) { return DataGridRowAdapter( - cells: dataGridConfiguration.columns - .map( - (GridColumn dataCell) => SizedBox.fromSize(size: Size.zero)) - .toList()); + cells: + dataGridConfiguration.columns + .map( + (GridColumn dataCell) => SizedBox.fromSize(size: Size.zero), + ) + .toList(), + ); } return dataGridConfiguration.source.buildRow(dataGridRow) ?? @@ -691,8 +1204,12 @@ DataGridRowAdapter? getDataGridRowAdapter( /// Check the length of two list. /// If its not satisfies it throw a exception. -bool debugCheckTheLength(DataGridConfiguration dataGridConfiguration, - int columnLength, int cellLength, String message) { +bool debugCheckTheLength( + DataGridConfiguration dataGridConfiguration, + int columnLength, + int cellLength, + String message, +) { cellLength += dataGridConfiguration.showCheckboxColumn ? 1 : 0; assert(() { if (columnLength != cellLength) { @@ -708,7 +1225,8 @@ bool debugCheckTheLength(DataGridConfiguration dataGridConfiguration, /// Return the cumulative distance of frozen top rows. The cumulative distance /// covered the header, stacked header and freeze pane double getCumulativeFrozenRowsHeight( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { final int topFrozenRowsLength = dataGridConfiguration.container.frozenRows; double cumulativeFrozenRowsHeight = 0.0; for (int index = 0; index < topFrozenRowsLength; index++) { @@ -720,7 +1238,8 @@ double getCumulativeFrozenRowsHeight( /// Return the cumulative distance of frozen bottom rows. double getCumulativeFooterFrozenRowsHeight( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { final int bottomFrozenRowsLength = dataGridConfiguration.container.footerFrozenRows; double cumulativeFooterFrozenRowsHeight = 0.0; @@ -735,7 +1254,8 @@ double getCumulativeFooterFrozenRowsHeight( /// Return the cumulative distance of frozen column on left side. The /// cumulative distance covered the row header, indent cell, freeze pane double getCumulativeFrozenColumnsWidth( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { final int leftColumnCount = dataGridConfiguration.container.frozenColumns; double cumulativeFrozenColumnWidth = 0.0; for (int index = 0; index < leftColumnCount; index++) { @@ -747,7 +1267,8 @@ double getCumulativeFrozenColumnsWidth( /// Return the cumulative distance of frozen right side columns. double getCumulativeFooterFrozenColumnsWidth( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { final int rightColumnCount = dataGridConfiguration.container.footerFrozenColumns; double cumulativeFooterFrozenColumnWidth = 0.0; @@ -761,21 +1282,29 @@ double getCumulativeFooterFrozenColumnsWidth( /// Resolve the cumulative horizontal offset with frozen rows. double resolveVerticalScrollOffset( - DataGridConfiguration dataGridConfiguration, double verticalOffset) { - final double leftOffset = - getCumulativeFrozenRowsHeight(dataGridConfiguration); - final double rightOffset = - getCumulativeFooterFrozenRowsHeight(dataGridConfiguration); + DataGridConfiguration dataGridConfiguration, + double verticalOffset, +) { + final double leftOffset = getCumulativeFrozenRowsHeight( + dataGridConfiguration, + ); + final double rightOffset = getCumulativeFooterFrozenRowsHeight( + dataGridConfiguration, + ); final double bottomOffset = dataGridConfiguration.container.extentHeight - rightOffset; if (verticalOffset >= bottomOffset) { return dataGridConfiguration - .verticalScrollController!.position.maxScrollExtent; + .verticalScrollController! + .position + .maxScrollExtent; } if (verticalOffset <= leftOffset) { return dataGridConfiguration - .verticalScrollController!.position.minScrollExtent; + .verticalScrollController! + .position + .minScrollExtent; } for (int i = 0; i < dataGridConfiguration.container.frozenRows; i++) { @@ -787,21 +1316,29 @@ double resolveVerticalScrollOffset( /// Resolve the cumulative horizontal offset with frozen column. double resolveHorizontalScrollOffset( - DataGridConfiguration dataGridConfiguration, double horizontalOffset) { - final double topOffset = - getCumulativeFrozenColumnsWidth(dataGridConfiguration); - final double bottomOffset = - getCumulativeFooterFrozenColumnsWidth(dataGridConfiguration); + DataGridConfiguration dataGridConfiguration, + double horizontalOffset, +) { + final double topOffset = getCumulativeFrozenColumnsWidth( + dataGridConfiguration, + ); + final double bottomOffset = getCumulativeFooterFrozenColumnsWidth( + dataGridConfiguration, + ); final double rightOffset = dataGridConfiguration.container.extentWidth - bottomOffset; if (horizontalOffset >= rightOffset) { return dataGridConfiguration - .horizontalScrollController!.position.maxScrollExtent; + .horizontalScrollController! + .position + .maxScrollExtent; } if (horizontalOffset <= topOffset) { return dataGridConfiguration - .horizontalScrollController!.position.minScrollExtent; + .horizontalScrollController! + .position + .minScrollExtent; } for (int i = 0; i < dataGridConfiguration.container.frozenColumns; i++) { @@ -813,7 +1350,9 @@ double resolveHorizontalScrollOffset( /// Get the vertical offset with reduction of frozen rows double getVerticalOffset( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { if (rowIndex < 0) { return dataGridConfiguration.container.verticalOffset; } @@ -826,7 +1365,9 @@ double getVerticalOffset( /// Get the vertical offset with reduction of frozen columns double getHorizontalOffset( - DataGridConfiguration dataGridConfiguration, int columnIndex) { + DataGridConfiguration dataGridConfiguration, + int columnIndex, +) { if (columnIndex < 0) { return dataGridConfiguration.container.horizontalOffset; } @@ -841,21 +1382,24 @@ double getHorizontalOffset( /// It's helps to get the position of rows and column scroll into desired /// DataGridScrollPosition. double resolveScrollOffsetToPosition( - DataGridScrollPosition position, - ScrollAxisBase scrollAxisBase, - double measuredScrollOffset, - double viewDimension, - double headerExtent, - double bottomExtent, - double defaultDimension, - double defaultScrollOffset, - int index) { + DataGridScrollPosition position, + ScrollAxisBase scrollAxisBase, + double measuredScrollOffset, + double viewDimension, + double headerExtent, + double bottomExtent, + double defaultDimension, + double defaultScrollOffset, + int index, +) { if (position == DataGridScrollPosition.center) { - measuredScrollOffset = measuredScrollOffset - + measuredScrollOffset = + measuredScrollOffset - ((viewDimension - bottomExtent - headerExtent) / 2) + (defaultDimension / 2); } else if (position == DataGridScrollPosition.end) { - measuredScrollOffset = measuredScrollOffset - + measuredScrollOffset = + measuredScrollOffset - (viewDimension - bottomExtent - headerExtent) + defaultDimension; } else if (position == DataGridScrollPosition.makeVisible) { @@ -869,7 +1413,8 @@ double resolveScrollOffsetToPosition( measuredScrollOffset = defaultScrollOffset; } if (defaultScrollOffset - measuredScrollOffset < 0) { - measuredScrollOffset = measuredScrollOffset - + measuredScrollOffset = + measuredScrollOffset - (viewDimension - bottomExtent - headerExtent) + defaultDimension; } @@ -877,3 +1422,24 @@ double resolveScrollOffsetToPosition( return measuredScrollOffset; } + +/// This method helps to resolve getting the column for the tap interaction callbacks. +GridColumn? getGridColumn( + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, +) { + GridColumn? column = dataCell.gridColumn; + if (dataCell.dataRow != null && + (dataCell.dataRow!.rowType == RowType.captionSummaryCoveredRow || + dataCell.dataRow!.rowType == RowType.tableSummaryCoveredRow)) { + final int startIndex = dataGridConfiguration.showCheckboxColumn ? 1 : 0; + + column = dataGridConfiguration.columns.firstWhereOrNull( + (GridColumn element) => + element.actualWidth > 0 && + element.visible && + dataGridConfiguration.columns.indexOf(element) >= startIndex, + ); + } + return column; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart index 9fb7c2b3c..0238b0d0f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart @@ -33,6 +33,9 @@ enum RowType { /// Specifies the SpannedDataRow that displays the table summary value in row. tableSummaryCoveredRow, + + /// Specifies the SpannedDataRow that displays the caption summary value in row. + captionSummaryCoveredRow, } /// Describes the possible values for cell types. @@ -57,6 +60,9 @@ enum CellType { /// Specifies the table summary cell tableSummaryCell, + + /// Specifies the caption summary cell + captionSummaryCell, } /// Determines how border lines should be shown in [SfDataGrid]. @@ -148,7 +154,7 @@ enum SelectionMode { singleDeselect, /// Specifies that multiple rows can be selected. - multiple + multiple, } /// Decides whether the navigation in the [SfDataGrid] should be cell wise @@ -160,7 +166,7 @@ enum GridNavigationMode { /// The selection can be moved among /// the rows alone and current cell will not be shown. - row + row, } /// Specifies the direction of the sort operation. @@ -169,7 +175,7 @@ enum DataGridSortDirection { ascending, /// Sorts in descending order. - descending + descending, } /// Specifies the different tap actions available for applying sorting. @@ -224,7 +230,7 @@ enum EditingGestureType { tap, /// Editing is triggered on double tap. - doubleTap + doubleTap, } /// Determines how table summary row should be positioned in [SfDataGrid]. @@ -253,3 +259,130 @@ enum GridSummaryType { /// Specifies the count of rows available in [SfDataGrid]. count, } + +/// Determines the type of the filter condition which should be compared +/// between rows. +enum FilterType { + /// Checks whether the cell value contain the given filter value. + contains, + + /// Checks whether the cell value ends with the given filter value. + endsWith, + + /// Checks whether the cell value is equivalent to the given filter value. + equals, + + /// Checks whether the cell value is greater than the given filter value. + greaterThan, + + /// Checks whether the cell value is greater than or equal to the given filter + /// value. + greaterThanOrEqual, + + /// Checks whether the cell value is lesser than the given filter value. + lessThan, + + /// Checks whether the cell value is lesser than or equal to the given filter + /// value. + lessThanOrEqual, + + /// Checks whether the cell value does not contain the given filter value. + doesNotContain, + + /// Checks whether the cell value does not end with the given filter value. + doesNotEndsWith, + + /// Checks whether the cell value is not equivalent to the given filter value. + notEqual, + + /// Checks whether the cell value does not begin with the given filter value. + doesNotBeginWith, + + /// Checks whether the cell value begins with the given filter value. + beginsWith, +} + +/// Determines the type of the logical operator to be applied between multiple +/// filter conditions. +enum FilterOperator { + /// AND logical operator is applied between multiple filter conditions. + and, + + /// OR logical operator is applied between multiple filter conditions. + or, +} + +/// Determines how the filter comparison should behave. +enum FilterBehavior { + /// Converts the cell value as string and compare the condition. + stringDataType, + + /// Compares the cell value with its actual data type. + strongDataType, +} + +/// Determines how the filter menu should be opened. +enum FilteredFrom { + /// Specifies the checkbox filter. + checkboxFilter, + + /// Specifies the advanced filter. + advancedFilter, + + /// Specifies the none. + none, +} + +/// Determines how the filter comparison should behave. +enum AdvancedFilterType { + /// Specifies that the filtering options should be available based on string + /// comparison. + text, + + /// Specifies that the filtering options should be available based on numeric + /// values. + numeric, + + /// Specifies that the filtering options should be available based on date + /// values. + date, +} + +/// Decides how the checked listbox and advanced filter options should be shown in filter popup +enum FilterMode { + /// Specifies whether the checked listbox only should be shown along with other options. + checkboxFilter, + + /// Specifies whether the advanced filter dropdown only should be shown along with other options. + advancedFilter, + + /// Specifies whether both the checked listbox and advanced filter dropdown options should be shown. + both, +} + +/// The position of the icon in the column headers. +enum ColumnHeaderIconPosition { + /// Specifies that an icon is positioned at the starting position of the column header. + start, + + /// Specifies that an icon is positioned at the ending position of the column header. + end, +} + +/// Determine the status of the current dragging column. +enum DataGridColumnDragAction { + /// Specifies the dragging operation is about to initialize for a column. + starting, + + /// Specifies the dragging operation is initialized for a column. + started, + + /// Specifies the column is being dragged. + update, + + /// Specifies the dragging operation is about to end for a column. + dropping, + + /// Specifies the dragging operation is ended for a column. + dropped, +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart index 0bab531b0..4d76ed989 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart @@ -4,26 +4,35 @@ import 'package:flutter/material.dart' hide DataCell, DataRow; import '../../grid_common/row_column_index.dart'; import '../../grid_common/scroll_axis.dart'; import '../../grid_common/visible_line_info.dart'; +import '../grouping/grouping.dart'; import '../runtime/column.dart'; import '../selection/selection_manager.dart'; import '../sfdatagrid.dart'; import 'datagrid_configuration.dart'; import 'datagrid_helper.dart' as grid_helper; +import 'datagrid_helper.dart'; import 'enums.dart'; /// Get the row index in data grid based provided DataGridRow int resolveToRowIndex( - DataGridConfiguration dataGridConfiguration, DataGridRow record) { - final DataGridRow? rec = effectiveRows(dataGridConfiguration.source) - .firstWhereOrNull((DataGridRow rec) => rec == record); + DataGridConfiguration dataGridConfiguration, + DataGridRow record, +) { + final DataGridRow? rec = effectiveRows( + dataGridConfiguration.source, + ).firstWhereOrNull((DataGridRow rec) => rec == record); if (rec == null) { return -1; } - int recordIndex = effectiveRows(dataGridConfiguration.source).indexOf(rec); + int recordIndex = + dataGridConfiguration.source.groupedColumns.isNotEmpty + ? dataGridConfiguration.group!.displayElements!.grouped.indexOf(rec) + : effectiveRows(dataGridConfiguration.source).indexOf(rec); - recordIndex += - grid_helper.resolveStartIndexBasedOnPosition(dataGridConfiguration); + recordIndex += grid_helper.resolveStartIndexBasedOnPosition( + dataGridConfiguration, + ); return recordIndex.isNegative ? -1 : recordIndex; } @@ -31,6 +40,14 @@ int resolveToRowIndex( /// Helps to get the DataGridRow based on provided row index DataGridRow? getRecord(DataGridConfiguration dataGridConfiguration, int index) { // Restricts the index if it doesn't exist in the `DataGridSource.rows` range. + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final dynamic record = getGroupElement(dataGridConfiguration, index); + if (record is DataGridRow) { + return record; + } + return null; + } + if (effectiveRows(dataGridConfiguration.source).isEmpty || index < 0 || index >= effectiveRows(dataGridConfiguration.source).length) { @@ -56,8 +73,10 @@ int getFirstNavigatingRowIndex(DataGridConfiguration dataGridConfiguration) { final int index = getFirstRowIndex(dataGridConfiguration); const int count = 0; for (int start = index; start >= 0; start--) { - final List hiddenRowInfo = - dataGridConfiguration.container.rowHeights.getHidden(start, count); + final List hiddenRowInfo = dataGridConfiguration + .container + .rowHeights + .getHidden(start, count); final bool isHiddenRow = hiddenRowInfo.first as bool; if (!isHiddenRow) { return start; @@ -88,12 +107,16 @@ int getLastNavigatingRowIndex(DataGridConfiguration dataGridConfiguration) { } if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { lastRowIndex -= grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); } for (int start = lastRowIndex; start >= 0; start--) { - final List hiddenRowInfo = - dataGridConfiguration.container.rowHeights.getHidden(start, count); + final List hiddenRowInfo = dataGridConfiguration + .container + .rowHeights + .getHidden(start, count); final bool isHiddenRow = hiddenRowInfo.first as bool; if (!isHiddenRow) { return start; @@ -113,14 +136,19 @@ int getFirstRowIndex(DataGridConfiguration dataGridConfiguration) { if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { index += grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.top); + dataGridConfiguration, + GridTableSummaryRowPosition.top, + ); } return index; } /// Help to get the last row index in data grid -int getLastRowIndex(DataGridConfiguration dataGridConfiguration) { +int getLastRowIndex( + DataGridConfiguration dataGridConfiguration, [ + bool isLastRow = false, +]) { if (getRecordsCount(dataGridConfiguration) == 0) { return -1; } @@ -132,18 +160,24 @@ int getLastRowIndex(DataGridConfiguration dataGridConfiguration) { /// Eg : Total rowCount is 30, we will start the row index from 0 of header /// in SfDataGrid. The actual last row index of data grid is 29. int index = dataGridConfiguration.container.rowCount - 1; + if (dataGridConfiguration.footer != null) { index--; } - - if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { - index -= grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + if (!isLastRow) { + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + index -= grid_helper.getTableSummaryCount( + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); + } } for (int start = index; start >= 0; start--) { - final List hiddenRowInfo = - dataGridConfiguration.container.rowHeights.getHidden(start, count); + final List hiddenRowInfo = dataGridConfiguration + .container + .rowHeights + .getHidden(start, count); final bool isHiddenRow = hiddenRowInfo.first as bool; if (!isHiddenRow) { return start; @@ -155,12 +189,34 @@ int getLastRowIndex(DataGridConfiguration dataGridConfiguration) { /// Help to get the first column index in row int getFirstCellIndex(DataGridConfiguration dataGridConfiguration) { - final GridColumn? gridColumn = dataGridConfiguration.columns - .firstWhereOrNull((GridColumn col) => col.visible); + final GridColumn? gridColumn = dataGridConfiguration.columns.firstWhereOrNull( + (GridColumn col) => col.visible, + ); if (gridColumn == null) { return -1; } - + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { + final int rowIndex = resolveStartRecordIndex( + dataGridConfiguration, + dataGridConfiguration.currentCell.rowIndex, + ); + final dynamic group = getGroupElement(dataGridConfiguration, rowIndex); + final dynamic nextGroup = getNextGroupInfo(group, dataGridConfiguration); + if ((group is Group && nextGroup is Group) || rowIndex < 0) { + return 0; + } else if (group is Group && + group.level == + dataGridConfiguration.source.groupedColumns.length || + (group is DataGridRow && nextGroup is DataGridRow)) { + return dataGridConfiguration.source.groupedColumns.length; + } else { + return dataGridConfiguration.source.groupedColumns.length; + } + } else { + return dataGridConfiguration.source.groupedColumns.length; + } + } final int firstIndex = dataGridConfiguration.columns.indexOf(gridColumn); if (firstIndex < 0) { return firstIndex; @@ -172,9 +228,14 @@ int getFirstCellIndex(DataGridConfiguration dataGridConfiguration) { /// Help to get the last column index in row int getLastCellIndex(DataGridConfiguration dataGridConfiguration) { final GridColumn? lastColumn = dataGridConfiguration.columns.lastWhereOrNull( - (GridColumn col) => col.visible && col.actualWidth > 0.0); + (GridColumn col) => col.visible && col.actualWidth > 0.0, + ); if (lastColumn != null) { - return dataGridConfiguration.columns.indexOf(lastColumn); + int lastCellIndex = dataGridConfiguration.columns.indexOf(lastColumn); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + lastCellIndex += dataGridConfiguration.source.groupedColumns.length; + } + return lastCellIndex; } return -1; } @@ -231,13 +292,20 @@ int getNextPageIndex(DataGridConfiguration dataGridConfiguration) { /// Help to get the previous cell index from current cell index. int getPreviousColumnIndex( - DataGridConfiguration dataGridConfiguration, int currentColumnIndex) { + DataGridConfiguration dataGridConfiguration, + int currentColumnIndex, +) { int previousCellIndex = currentColumnIndex; - final GridColumn? column = - getNextGridColumn(dataGridConfiguration, currentColumnIndex - 1, false); + final GridColumn? column = getNextGridColumn( + dataGridConfiguration, + currentColumnIndex - 1, + false, + ); if (column != null) { previousCellIndex = grid_helper.resolveToScrollColumnIndex( - dataGridConfiguration, dataGridConfiguration.columns.indexOf(column)); + dataGridConfiguration, + dataGridConfiguration.columns.indexOf(column), + ); } // Need to set column index as -1 to disable navigation since there are no @@ -251,16 +319,23 @@ int getPreviousColumnIndex( /// Help to get the next cell index from current cell index. int getNextColumnIndex( - DataGridConfiguration dataGridConfiguration, int currentColumnIndex) { + DataGridConfiguration dataGridConfiguration, + int currentColumnIndex, +) { int nextCellIndex = currentColumnIndex; final int lastCellIndex = getLastCellIndex(dataGridConfiguration); - final GridColumn? column = - getNextGridColumn(dataGridConfiguration, currentColumnIndex + 1, true); + final GridColumn? column = getNextGridColumn( + dataGridConfiguration, + currentColumnIndex + 1, + true, + ); if (column != null) { final int columnIndex = dataGridConfiguration.columns.indexOf(column); nextCellIndex = grid_helper.resolveToScrollColumnIndex( - dataGridConfiguration, columnIndex); + dataGridConfiguration, + columnIndex, + ); } // Need to set column index to -1 to disable navigation since there are no @@ -276,8 +351,37 @@ int getNextColumnIndex( /// Help to get the previous row index from current cell index. int getPreviousRowIndex( - DataGridConfiguration dataGridConfiguration, int currentRowIndex) { + DataGridConfiguration dataGridConfiguration, + int currentRowIndex, +) { final int lastRowIndex = getLastNavigatingRowIndex(dataGridConfiguration); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + if (dataGridConfiguration.selectionMode != SelectionMode.multiple) { + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final List dataGridRowIndexes = + dataGridConfiguration.group!.displayElements!.grouped + .whereType() + .map( + (DataGridRow row) => dataGridConfiguration + .group! + .displayElements! + .grouped + .indexOf(row), + ) + .toList(); + if (currentCell.rowIndex == + dataGridConfiguration.source.groupedColumns.length + 1) { + return currentCell.rowIndex; + } else { + final int indexInDataGridRowIndexes = dataGridRowIndexes.indexOf( + currentCell.rowIndex - 1, + ); + return dataGridRowIndexes[indexInDataGridRowIndexes - 1] + + grid_helper.resolveStartIndexBasedOnPosition(dataGridConfiguration); + } + } + } + if (currentRowIndex > lastRowIndex) { return lastRowIndex; } @@ -297,10 +401,61 @@ int getPreviousRowIndex( return previousIndex; } +/// Help to get the column index for down key. +int getDownKeyColumnIndex( + DataGridConfiguration dataGridConfiguration, + CurrentCellManager currentCell, +) { + return currentCell.columnIndex; +} + +/// Help to get the column index for down key. +int getUpKeyColumnIndex( + DataGridConfiguration dataGridConfiguration, + CurrentCellManager currentCell, +) { + return currentCell.columnIndex; +} + /// Help to get the next row index from current cell index. int getNextRowIndex( - DataGridConfiguration dataGridConfiguration, int currentRowIndex) { + DataGridConfiguration dataGridConfiguration, + int currentRowIndex, +) { final int lastRowIndex = getLastNavigatingRowIndex(dataGridConfiguration); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + if (dataGridConfiguration.selectionMode != SelectionMode.multiple) { + final List dataGridRowIndexes = + dataGridConfiguration.group!.displayElements!.grouped + .whereType() + .map( + (DataGridRow row) => dataGridConfiguration + .group! + .displayElements! + .grouped + .indexOf(row), + ) + .toList(); + + if (currentCell.rowIndex >= lastRowIndex) { + return lastRowIndex; + } else if (currentCell.rowIndex == -1) { + return dataGridRowIndexes.first + + grid_helper.resolveStartIndexBasedOnPosition(dataGridConfiguration); + } else { + final int indexInDataGridRowIndexes = dataGridRowIndexes.indexOf( + currentCell.rowIndex - + grid_helper.resolveStartIndexBasedOnPosition( + dataGridConfiguration, + ), + ); + return dataGridRowIndexes[indexInDataGridRowIndexes + 1] + + grid_helper.resolveStartIndexBasedOnPosition(dataGridConfiguration); + } + } + } + if (currentRowIndex >= lastRowIndex) { return lastRowIndex; } @@ -316,8 +471,9 @@ int getNextRowIndex( } int nextIndex = 0; - nextIndex = dataGridConfiguration.container.scrollRows - .getNextScrollLineIndex(currentRowIndex); + nextIndex = dataGridConfiguration.container.scrollRows.getNextScrollLineIndex( + currentRowIndex, + ); if (nextIndex == currentRowIndex) { nextIndex = nextIndex + 1; } @@ -329,10 +485,15 @@ int getNextRowIndex( /// index /// moveToRight: Consider to check on next column else it will check the /// previous column -GridColumn? getNextGridColumn(DataGridConfiguration dataGridConfiguration, - int columnIndex, bool moveToRight) { +GridColumn? getNextGridColumn( + DataGridConfiguration dataGridConfiguration, + int columnIndex, + bool moveToRight, +) { final int resolvedIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, columnIndex); + dataGridConfiguration, + columnIndex, + ); if (resolvedIndex < 0 || resolvedIndex >= dataGridConfiguration.columns.length) { return null; @@ -340,8 +501,11 @@ GridColumn? getNextGridColumn(DataGridConfiguration dataGridConfiguration, GridColumn? gridColumn = dataGridConfiguration.columns[resolvedIndex]; if (!gridColumn.visible || gridColumn.actualWidth == 0.0) { - gridColumn = getNextGridColumn(dataGridConfiguration, - moveToRight ? columnIndex + 1 : columnIndex - 1, moveToRight); + gridColumn = getNextGridColumn( + dataGridConfiguration, + moveToRight ? columnIndex + 1 : columnIndex - 1, + moveToRight, + ); } return gridColumn; @@ -349,29 +513,36 @@ GridColumn? getNextGridColumn(DataGridConfiguration dataGridConfiguration, /// Helps to get the cumulative distance of provided row index double getVerticalCumulativeDistance( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, +) { double verticalOffset = 0.0; final int headerRowIndex = grid_helper.getHeaderIndex(dataGridConfiguration); rowIndex = rowIndex > headerRowIndex ? rowIndex : headerRowIndex + 1; - final PixelScrollAxis _scrollRows = + final PixelScrollAxis scrollRows = dataGridConfiguration.container.scrollRows as PixelScrollAxis; - verticalOffset = _scrollRows.distances!.getCumulatedDistanceAt(rowIndex); + verticalOffset = scrollRows.distances!.getCumulatedDistanceAt(rowIndex); return verticalOffset; } /// Helps to get the cumulative distance of provided column index double getHorizontalCumulativeDistance( - DataGridConfiguration dataGridConfiguration, int columnIndex) { + DataGridConfiguration dataGridConfiguration, + int columnIndex, +) { double horizontalOffset = 0.0; - final int firstVisibleColumnIndex = - grid_helper.resolveToStartColumnIndex(dataGridConfiguration); - columnIndex = columnIndex < firstVisibleColumnIndex - ? firstVisibleColumnIndex - : columnIndex; - final PixelScrollAxis _scrollColumns = + final int firstVisibleColumnIndex = grid_helper.resolveToStartColumnIndex( + dataGridConfiguration, + ); + columnIndex = + columnIndex < firstVisibleColumnIndex + ? firstVisibleColumnIndex + : columnIndex; + final PixelScrollAxis scrollColumns = dataGridConfiguration.container.scrollColumns as PixelScrollAxis; - horizontalOffset = - _scrollColumns.distances!.getCumulatedDistanceAt(columnIndex); + horizontalOffset = scrollColumns.distances!.getCumulatedDistanceAt( + columnIndex, + ); return horizontalOffset; } @@ -381,7 +552,9 @@ double getHorizontalCumulativeDistance( /// If next row index not present in view it will return true, else it will /// return false bool needToScrollDown( - DataGridConfiguration dataGridConfiguration, int nextRowIndex) { + DataGridConfiguration dataGridConfiguration, + int nextRowIndex, +) { final VisibleLinesCollection visibleLines = dataGridConfiguration.container.scrollRows.getVisibleLines(); if (visibleLines.isEmpty) { @@ -389,7 +562,8 @@ bool needToScrollDown( } final VisibleLineInfo? nextRowInfo = dataGridConfiguration - .container.scrollRows + .container + .scrollRows .getVisibleLineAtLineIndex(nextRowIndex); return nextRowInfo == null || nextRowInfo.isClipped; } @@ -398,7 +572,9 @@ bool needToScrollDown( /// If previous row index not present in view it will return true, else it will /// return false bool needToScrollUp( - DataGridConfiguration dataGridConfiguration, int previousRowIndex) { + DataGridConfiguration dataGridConfiguration, + int previousRowIndex, +) { final VisibleLinesCollection visibleLineCollection = dataGridConfiguration.container.scrollRows.getVisibleLines(); if (visibleLineCollection.isEmpty) { @@ -406,7 +582,8 @@ bool needToScrollUp( } final VisibleLineInfo? previousRowLineInfo = dataGridConfiguration - .container.scrollRows + .container + .scrollRows .getVisibleLineAtLineIndex(previousRowIndex); return previousRowLineInfo == null || previousRowLineInfo.isClipped; } @@ -414,10 +591,12 @@ bool needToScrollUp( /// Decide to scroll toward left or not based on previous cell index /// If previous cell index not present in view it will return true, else it will /// return false -bool needToScrollLeft(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { - final VisibleLinesCollection visibleLineCollection = - grid_helper.getVisibleLines(dataGridConfiguration); +bool needToScrollLeft( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, +) { + final VisibleLinesCollection visibleLineCollection = grid_helper + .getVisibleLines(dataGridConfiguration); if (visibleLineCollection.isEmpty) { return false; } @@ -430,10 +609,12 @@ bool needToScrollLeft(DataGridConfiguration dataGridConfiguration, /// Decide to scroll toward right or not based on previous cell index /// If next cell index not present in view it will return true, else it will /// return false -bool needToScrollRight(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { - final VisibleLinesCollection visibleLineCollection = - grid_helper.getVisibleLines(dataGridConfiguration); +bool needToScrollRight( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, +) { + final VisibleLinesCollection visibleLineCollection = grid_helper + .getVisibleLines(dataGridConfiguration); if (visibleLineCollection.isEmpty) { return false; } @@ -443,18 +624,22 @@ bool needToScrollRight(DataGridConfiguration dataGridConfiguration, } /// Perform to scroll the view to left -void scrollInViewFromLeft(DataGridConfiguration dataGridConfiguration, - {int nextCellIndex = -1, bool needToScrollMaxExtent = false}) { +void scrollInViewFromLeft( + DataGridConfiguration dataGridConfiguration, { + int nextCellIndex = -1, + bool needToScrollMaxExtent = false, +}) { if (dataGridConfiguration.horizontalScrollController != null) { final ScrollController horizontalController = dataGridConfiguration.horizontalScrollController!; double measuredHorizontalOffset = 0.0; - final int lastFrozenColumnIndex = - grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); + final int lastFrozenColumnIndex = grid_helper.getLastFrozenColumnIndex( + dataGridConfiguration, + ); - final int firstFooterFrozenColumnIndex = - grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration); + final int firstFooterFrozenColumnIndex = grid_helper + .getStartFooterFrozenColumnIndex(dataGridConfiguration); if (dataGridConfiguration.frozenColumnsCount > 0 && lastFrozenColumnIndex + 1 == nextCellIndex) { @@ -467,38 +652,47 @@ void scrollInViewFromLeft(DataGridConfiguration dataGridConfiguration, if (dataGridConfiguration.currentCell.columnIndex != -1 && nextCellIndex == dataGridConfiguration.currentCell.columnIndex + 1) { final List nextCellIndexHeight = dataGridConfiguration - .container.columnWidthsProvider + .container + .columnWidthsProvider .getSize(nextCellIndex, 0); - final VisibleLinesCollection visibleInfoCollection = - grid_helper.getVisibleLines(dataGridConfiguration); - final VisibleLineInfo? nextCellInfo = - visibleInfoCollection.getVisibleLineAtLineIndex(nextCellIndex); - measuredHorizontalOffset = nextCellInfo != null - ? dataGridConfiguration.textDirection == TextDirection.rtl - ? nextCellInfo.clippedSize - - (~nextCellInfo.clippedOrigin.toInt()) - : nextCellInfo.size - - (nextCellInfo.size - nextCellInfo.clippedCornerExtent) - : nextCellIndexHeight.first as double; + final VisibleLinesCollection visibleInfoCollection = grid_helper + .getVisibleLines(dataGridConfiguration); + final VisibleLineInfo? nextCellInfo = visibleInfoCollection + .getVisibleLineAtLineIndex(nextCellIndex); + measuredHorizontalOffset = + nextCellInfo != null + ? dataGridConfiguration.textDirection == TextDirection.rtl + ? nextCellInfo.clippedSize - + (~nextCellInfo.clippedOrigin.toInt()) + : nextCellInfo.size - + (nextCellInfo.size - nextCellInfo.clippedCornerExtent) + : nextCellIndexHeight.first as double; measuredHorizontalOffset = horizontalController.offset + measuredHorizontalOffset; } else { - final VisibleLinesCollection visibleInfoCollection = - grid_helper.getVisibleLines(dataGridConfiguration); - final int firstBodyVisibleLineIndex = visibleInfoCollection - .firstBodyVisibleIndex < - visibleInfoCollection.length - ? visibleInfoCollection[visibleInfoCollection.firstBodyVisibleIndex] - .lineIndex - : 0; + final VisibleLinesCollection visibleInfoCollection = grid_helper + .getVisibleLines(dataGridConfiguration); + final int firstBodyVisibleLineIndex = + visibleInfoCollection.firstBodyVisibleIndex < + visibleInfoCollection.length + ? visibleInfoCollection[visibleInfoCollection + .firstBodyVisibleIndex] + .lineIndex + : 0; if (nextCellIndex < firstBodyVisibleLineIndex) { - scrollInViewFromRight(dataGridConfiguration, - previousCellIndex: nextCellIndex, needToScrollToMinExtent: false); + scrollInViewFromRight( + dataGridConfiguration, + previousCellIndex: nextCellIndex, + ); } else { measuredHorizontalOffset = getHorizontalCumulativeDistance( - dataGridConfiguration, nextCellIndex); + dataGridConfiguration, + nextCellIndex, + ); measuredHorizontalOffset = grid_helper.resolveHorizontalScrollOffset( - dataGridConfiguration, measuredHorizontalOffset); + dataGridConfiguration, + measuredHorizontalOffset, + ); measuredHorizontalOffset = horizontalController.offset + measuredHorizontalOffset; } @@ -506,23 +700,29 @@ void scrollInViewFromLeft(DataGridConfiguration dataGridConfiguration, } grid_helper.scrollHorizontal( - dataGridConfiguration, measuredHorizontalOffset); + dataGridConfiguration, + measuredHorizontalOffset, + ); } } /// Perform to scroll the view to right -void scrollInViewFromRight(DataGridConfiguration dataGridConfiguration, - {int previousCellIndex = -1, bool needToScrollToMinExtent = false}) { +void scrollInViewFromRight( + DataGridConfiguration dataGridConfiguration, { + int previousCellIndex = -1, + bool needToScrollToMinExtent = false, +}) { double measuredHorizontalOffset = 0.0; if (dataGridConfiguration.horizontalScrollController != null) { final ScrollController horizontalController = dataGridConfiguration.horizontalScrollController!; - final int startingFooterFrozenColumnIndex = - grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration); - final int lastFrozenColumnIndex = - grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); + final int startingFooterFrozenColumnIndex = grid_helper + .getStartFooterFrozenColumnIndex(dataGridConfiguration); + final int lastFrozenColumnIndex = grid_helper.getLastFrozenColumnIndex( + dataGridConfiguration, + ); if (dataGridConfiguration.footerFrozenColumnsCount > 0 && startingFooterFrozenColumnIndex - 1 == previousCellIndex) { @@ -536,37 +736,49 @@ void scrollInViewFromRight(DataGridConfiguration dataGridConfiguration, previousCellIndex == dataGridConfiguration.currentCell.columnIndex - 1) { final List previousCellIndexWidth = dataGridConfiguration - .container.columnWidthsProvider + .container + .columnWidthsProvider .getSize(previousCellIndex, 0); - final VisibleLinesCollection visibleInfoCollection = - grid_helper.getVisibleLines(dataGridConfiguration); - final VisibleLineInfo? previousCellInfo = - visibleInfoCollection.getVisibleLineAtLineIndex(previousCellIndex); - measuredHorizontalOffset = previousCellInfo != null - ? previousCellInfo.size - - (previousCellInfo.clippedSize - - previousCellInfo.clippedCornerExtent) - : previousCellIndexWidth.first as double; + final VisibleLinesCollection visibleInfoCollection = grid_helper + .getVisibleLines(dataGridConfiguration); + final VisibleLineInfo? previousCellInfo = visibleInfoCollection + .getVisibleLineAtLineIndex(previousCellIndex); + measuredHorizontalOffset = + previousCellInfo != null + ? previousCellInfo.size - + (previousCellInfo.clippedSize - + previousCellInfo.clippedCornerExtent) + : previousCellIndexWidth.first as double; measuredHorizontalOffset = horizontalController.offset - measuredHorizontalOffset; } else { measuredHorizontalOffset = getHorizontalCumulativeDistance( - dataGridConfiguration, previousCellIndex); + dataGridConfiguration, + previousCellIndex, + ); measuredHorizontalOffset = grid_helper.resolveHorizontalScrollOffset( - dataGridConfiguration, measuredHorizontalOffset); - measuredHorizontalOffset = horizontalController.offset - + dataGridConfiguration, + measuredHorizontalOffset, + ); + measuredHorizontalOffset = + horizontalController.offset - (horizontalController.offset - measuredHorizontalOffset); } } grid_helper.scrollHorizontal( - dataGridConfiguration, measuredHorizontalOffset); + dataGridConfiguration, + measuredHorizontalOffset, + ); } } /// Perform to scroll the view to top -void scrollInViewFromTop(DataGridConfiguration dataGridConfiguration, - {int nextRowIndex = -1, bool needToScrollToMaxExtent = false}) { +void scrollInViewFromTop( + DataGridConfiguration dataGridConfiguration, { + int nextRowIndex = -1, + bool needToScrollToMaxExtent = false, +}) { double measuredVerticalOffset = 0.0; if (dataGridConfiguration.verticalScrollController != null) { @@ -583,34 +795,44 @@ void scrollInViewFromTop(DataGridConfiguration dataGridConfiguration, if (dataGridConfiguration.currentCell.rowIndex != -1 && nextRowIndex == dataGridConfiguration.currentCell.rowIndex + 1) { final List nextRowIndexHeight = dataGridConfiguration - .container.rowHeightsProvider + .container + .rowHeightsProvider .getSize(nextRowIndex, 0); final VisibleLineInfo? nextRowInfo = dataGridConfiguration - .container.scrollRows + .container + .scrollRows .getVisibleLineAtLineIndex(nextRowIndex); - measuredVerticalOffset = nextRowInfo != null - ? nextRowInfo.size - - (nextRowInfo.size - nextRowInfo.clippedCornerExtent) - : nextRowIndexHeight.first as double; + measuredVerticalOffset = + nextRowInfo != null + ? nextRowInfo.size - + (nextRowInfo.size - nextRowInfo.clippedCornerExtent) + : nextRowIndexHeight.first as double; measuredVerticalOffset = verticalController.offset + measuredVerticalOffset; } else { final VisibleLinesCollection visibleInfoCollection = dataGridConfiguration.container.scrollRows.getVisibleLines(); - final int firstBodyVisibleLineIndex = visibleInfoCollection - .firstBodyVisibleIndex < - visibleInfoCollection.length - ? visibleInfoCollection[visibleInfoCollection.firstBodyVisibleIndex] - .lineIndex - : 0; + final int firstBodyVisibleLineIndex = + visibleInfoCollection.firstBodyVisibleIndex < + visibleInfoCollection.length + ? visibleInfoCollection[visibleInfoCollection + .firstBodyVisibleIndex] + .lineIndex + : 0; if (nextRowIndex < firstBodyVisibleLineIndex) { - scrollInViewFromDown(dataGridConfiguration, - previousRowIndex: nextRowIndex, needToScrollToMinExtent: false); + scrollInViewFromDown( + dataGridConfiguration, + previousRowIndex: nextRowIndex, + ); } else { measuredVerticalOffset = getVerticalCumulativeDistance( - dataGridConfiguration, nextRowIndex); + dataGridConfiguration, + nextRowIndex, + ); measuredVerticalOffset = grid_helper.resolveVerticalScrollOffset( - dataGridConfiguration, measuredVerticalOffset); + dataGridConfiguration, + measuredVerticalOffset, + ); } } } @@ -620,8 +842,11 @@ void scrollInViewFromTop(DataGridConfiguration dataGridConfiguration, } /// Perform to scroll the view to down -void scrollInViewFromDown(DataGridConfiguration dataGridConfiguration, - {int previousRowIndex = -1, bool needToScrollToMinExtent = false}) { +void scrollInViewFromDown( + DataGridConfiguration dataGridConfiguration, { + int previousRowIndex = -1, + bool needToScrollToMinExtent = false, +}) { double measuredVerticalOffset = 0.0; if (dataGridConfiguration.verticalScrollController != null) { @@ -638,24 +863,32 @@ void scrollInViewFromDown(DataGridConfiguration dataGridConfiguration, if (dataGridConfiguration.currentCell.rowIndex != -1 && previousRowIndex == dataGridConfiguration.currentCell.rowIndex - 1) { final List previousRowIndexHeight = dataGridConfiguration - .container.rowHeightsProvider + .container + .rowHeightsProvider .getSize(previousRowIndex, 0); final VisibleLineInfo? previousRowInfo = dataGridConfiguration - .container.scrollRows + .container + .scrollRows .getVisibleLineAtLineIndex(previousRowIndex); - measuredVerticalOffset = previousRowInfo != null - ? previousRowInfo.size - - (previousRowInfo.clippedSize - - previousRowInfo.clippedCornerExtent) - : previousRowIndexHeight.first as double; + measuredVerticalOffset = + previousRowInfo != null + ? previousRowInfo.size - + (previousRowInfo.clippedSize - + previousRowInfo.clippedCornerExtent) + : previousRowIndexHeight.first as double; measuredVerticalOffset = verticalController.offset - measuredVerticalOffset; } else { measuredVerticalOffset = getVerticalCumulativeDistance( - dataGridConfiguration, previousRowIndex); + dataGridConfiguration, + previousRowIndex, + ); measuredVerticalOffset = grid_helper.resolveVerticalScrollOffset( - dataGridConfiguration, measuredVerticalOffset); - measuredVerticalOffset = verticalController.offset - + dataGridConfiguration, + measuredVerticalOffset, + ); + measuredVerticalOffset = + verticalController.offset - (verticalController.offset - measuredVerticalOffset); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart index 55ecad466..42c533b6e 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart' hide DataCell, DataRow; import '../../../datagrid.dart'; +import '../grouping/grouping.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/datagrid_helper.dart'; import '../selection/selection_manager.dart' as selection_manager; import '../widgets/cell_widget.dart'; import 'generator.dart'; @@ -32,19 +34,19 @@ class GridStackedHeaderCellRenderer final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); Widget? label = DefaultTextStyle( - style: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87)), - child: dataCell.stackedHeaderCell!.child); + style: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ), + child: dataCell.stackedHeaderCell!.child, + ); dataCell.columnElement = GridCell( key: dataCell.key!, dataCell: dataCell, - backgroundColor: - dataGridConfiguration.colorScheme!.surface.withOpacity(0.0001), + backgroundColor: dataGridConfiguration.colorScheme!.surface[0]!, isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, dataGridStateDetails: _dataGridStateDetails, child: label, @@ -56,7 +58,7 @@ class GridStackedHeaderCellRenderer /// A cell renderer which displays the String value in the cell. /// -/// This renderer is typically used for `GridTextColumn`. +/// This renderer is typically used for `GridColumn`. class GridCellTextFieldRenderer extends GridVirtualizingCellRendererBase { @override @@ -70,26 +72,28 @@ class GridCellTextFieldRenderer } TextStyle _getCellTextStyle( - DataGridConfiguration dataGridConfiguration, DataCellBase dataCell) { + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, + ) { final DataRowBase? dataRow = dataCell.dataRow; if (dataRow != null && dataRow.isSelectedRow) { return dataRow.isHoveredRow - ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle + ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle! : TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: dataGridConfiguration.colorScheme!.onSurface - .withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ); } else { return dataRow!.isHoveredRow - ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle + ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle! : TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: dataGridConfiguration.colorScheme!.onSurface - .withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ); } } } @@ -106,30 +110,36 @@ class GridHeaderCellRenderer void onInitializeDisplayWidget(DataCellBase dataCell) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); final Widget child = DefaultTextStyle( - key: dataCell.key, - style: dataCell.textStyle!, - child: dataCell.gridColumn!.label); - if (dataGridConfiguration.showCheckboxColumn && dataCell.columnIndex == 0) { + key: dataCell.key, + style: dataCell.textStyle!, + child: dataCell.gridColumn!.label, + ); + if (dataGridConfiguration.showCheckboxColumn && + dataCell.columnIndex == + dataGridConfiguration.source.groupedColumns.length) { dataCell.columnElement = GridCell( - key: dataCell.key!, - dataCell: dataCell, - backgroundColor: Colors.transparent, - isDirty: dataGridConfiguration.container.isDirty || - dataCell.isDirty || - dataCell.dataRow!.isDirty, - dataGridStateDetails: _dataGridStateDetails, - child: - _getCheckboxHeaderWidget(dataGridConfiguration, dataCell, child)); + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: dataGridConfiguration.colorScheme!.transparent, + isDirty: + dataGridConfiguration.container.isDirty || + dataCell.isDirty || + dataCell.dataRow!.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: _getCheckboxHeaderWidget(dataGridConfiguration, dataCell, child), + ); } else { dataCell.columnElement = GridHeaderCell( - key: dataCell.key!, - dataCell: dataCell, - backgroundColor: Colors.transparent, - isDirty: dataGridConfiguration.container.isDirty || - dataCell.isDirty || - dataCell.dataRow!.isDirty, - dataGridStateDetails: _dataGridStateDetails, - child: child); + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: Colors.transparent, + isDirty: + dataGridConfiguration.container.isDirty || + dataCell.isDirty || + dataCell.dataRow!.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: child, + ); } } @@ -137,11 +147,11 @@ class GridHeaderCellRenderer void setCellStyle(DataCellBase dataCell) { TextStyle getDefaultHeaderTextStyle() { return TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: - _dataGridStateDetails().colorScheme!.onSurface.withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: _dataGridStateDetails().colorScheme!.onSurface[222], + ); } dataCell.textStyle = getDefaultHeaderTextStyle(); @@ -149,44 +159,60 @@ class GridHeaderCellRenderer /// Creates a widget which displays label by default. Also, it creates with Checkbox, /// only when the [DataGridConfiguration.showCheckboxOnHeader] is true. - Widget _getCheckboxHeaderWidget(DataGridConfiguration dataGridConfiguration, - DataCellBase dataCell, Widget child) { + Widget _getCheckboxHeaderWidget( + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, + Widget child, + ) { final Widget label = Flexible( - child: DefaultTextStyle( - overflow: TextOverflow.ellipsis, - key: dataCell.key, - style: dataCell.textStyle!, - child: child)); + child: DefaultTextStyle( + overflow: TextOverflow.ellipsis, + key: dataCell.key, + style: dataCell.textStyle!, + child: child, + ), + ); return Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Visibility( - visible: dataGridConfiguration - .checkboxColumnSettings.showCheckboxOnHeader, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: + dataGridConfiguration + .checkboxColumnSettings + .showCheckboxOnHeader, child: Checkbox( - tristate: true, - value: dataGridConfiguration.headerCheckboxState, - onChanged: (bool? newValue) { - if (dataGridConfiguration.selectionMode == - SelectionMode.multiple) { - selection_manager.handleSelectionFromCheckbox( - dataGridConfiguration, - dataCell, - dataGridConfiguration.headerCheckboxState, - newValue); - } - })), - label - ], - )); + shape: dataGridConfiguration.checkboxShape, + tristate: true, + value: dataGridConfiguration.headerCheckboxState, + onChanged: (bool? newValue) { + if (dataGridConfiguration.selectionMode == + SelectionMode.multiple) { + _requestFocus(dataGridConfiguration); + selection_manager.handleSelectionFromCheckbox( + dataGridConfiguration, + dataCell, + dataGridConfiguration.headerCheckboxState, + newValue, + ); + } + }, + ), + ), + label, + ], + ), + ); } } /// A base class for cell renderer classes which displays widget in a cell. -abstract class GridVirtualizingCellRendererBase extends GridCellRendererBase { +abstract class GridVirtualizingCellRendererBase< + T1 extends Widget, + T2 extends Widget +> + extends GridCellRendererBase { /// Creates the [GridVirtualizingCellRendererBase] for [SfDataGrid] widget. GridVirtualizingCellRendererBase(); @@ -204,7 +230,19 @@ abstract class GridVirtualizingCellRendererBase= dataCell.dataRow!.dataGridRowAdapter!.cells.length) { + return; + } + final Widget child = dataCell.dataRow!.dataGridRowAdapter!.cells[index]; Widget getChild() { @@ -219,12 +257,16 @@ abstract class GridVirtualizingCellRendererBase 0 && dataCell.columnIndex < titleColumnSpan)) { final GridSummaryColumn? summaryColumn = dataCell.summaryColumn; - final RowColumnIndex rowColumnIndex = - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex); + final RowColumnIndex rowColumnIndex = RowColumnIndex( + dataCell.rowIndex, + dataCell.columnIndex, + ); final String title = dataGridConfiguration.source .calculateSummaryValue( - tableSummaryRow, summaryColumn, rowColumnIndex); + tableSummaryRow, + summaryColumn, + rowColumnIndex, + ); cell = dataGridConfiguration.source.buildTableSummaryCellWidget( - tableSummaryRow, summaryColumn, rowColumnIndex, title); + tableSummaryRow, + summaryColumn, + rowColumnIndex, + title, + ); } } cell ??= Container(); @@ -301,13 +370,14 @@ class GridTableSummaryCellRenderer } Widget? label = DefaultTextStyle( - style: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87)), - child: getSummaryCell()); + style: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ), + child: getSummaryCell(), + ); dataCell.columnElement = GridCell( key: dataCell.key!, @@ -322,8 +392,219 @@ class GridTableSummaryCellRenderer } } +/// A cell renderer which displays the widgets to the caption summary rows. +class GridCaptionSummaryCellRenderer + extends GridVirtualizingCellRendererBase { + @override + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + Widget getCaptionSummaryCell() { + Widget? result; + + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final int rowIndex = resolveStartRecordIndex( + dataGridConfiguration, + dataCell.rowIndex, + ); + + if (rowIndex >= 0) { + final dynamic groupItems = getGroupElement( + dataGridConfiguration, + rowIndex, + ); + + if (groupItems is Group) { + final int level = groupItems.level; + final int length = + dataGridConfiguration.source.groupedColumns.length; + + if (level > 0 && level <= length) { + final String groupedColumn = + dataGridConfiguration.source.groupedColumns[level - 1].name; + + String groupCaptionTitleFormat = + dataGridConfiguration.groupCaptionTitleFormat; + groupCaptionTitleFormat = groupCaptionTitleFormat + .replaceAll('{ColumnName}', groupedColumn) + .replaceAll('{Key}', '${groupItems.key}') + .replaceAll('{ItemsCount}', '${groupItems.rows.length}'); + final RowColumnIndex rowColumnIndex = RowColumnIndex( + dataCell.rowIndex, + dataCell.columnIndex, + ); + final Widget? cell = dataGridConfiguration.source + .buildGroupCaptionCellWidget( + rowColumnIndex, + groupCaptionTitleFormat, + ); + + result = cell; + } + } + } + } + + return result ?? const SizedBox(); + } + + final DefaultTextStyle label = DefaultTextStyle( + style: TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ), + child: getCaptionSummaryCell(), + ); + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: + dataGridConfiguration.dataGridThemeHelper!.captionSummaryRowColor, + dataGridStateDetails: _dataGridStateDetails, + isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, + child: label, + ); + } +} + +/// A cell renderer which displays the widgets to the Indent cell. +class GridIndentCellRenderer + extends GridVirtualizingCellRendererBase { + @override + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + Widget newIcon = const SizedBox(); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + dataCell.dataRow!.rowType == RowType.captionSummaryCoveredRow && + dataGridConfiguration.allowExpandCollapseGroup) { + final int rowIndex = resolveStartRecordIndex( + dataGridConfiguration, + dataCell.rowIndex, + ); + if (rowIndex >= 0) { + final dynamic groupItem = getGroupElement( + dataGridConfiguration, + rowIndex, + ); + + if (groupItem is Group) { + final int iconIndex = groupItem.level - 1; + newIcon = + (iconIndex == dataCell.columnIndex) + ? Center( + child: GroupExpanderIcon( + // Issue: + // When sorting or navigating through pages with grouping, the group icon's state (expanded/collapsed) was incorrect. + // + // Fix: + // The issue occurred because the widget’s state persisted due to unchanged rowIndex during actions like sorting. + // To resolve this, the key is updated to combine both the rowIndex and isExpanded state, + // ensuring uniqueness and preventing old state from being reused. + key: ObjectKey('${rowIndex}_${groupItem.isExpanded}'), + isExpanded: groupItem.isExpanded, + dataGridConfiguration: dataGridConfiguration, + rowIndex: dataCell.rowIndex, + ), + ) + : const SizedBox(); + } + } + } + + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: + dataCell.rowIndex >= + grid_helper.resolveStartIndexBasedOnPosition( + dataGridConfiguration, + ) + ? dataGridConfiguration.dataGridThemeHelper!.indentColumnColor! + : dataGridConfiguration.colorScheme!.transparent, + dataGridStateDetails: _dataGridStateDetails, + isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, + child: newIcon, + ); + } +} + +/// Represents a collapsed and expanded icon. +class GroupExpanderIcon extends StatefulWidget { + /// Provide an icon with the required details. + const GroupExpanderIcon({ + Key? key, + required this.isExpanded, + required this.dataGridConfiguration, + required this.rowIndex, + }) : super(key: key); + + /// Check the expand and collapse states of the group. + final bool isExpanded; + + /// Holds the [DataGridStateDetails]. + final DataGridConfiguration dataGridConfiguration; + + /// Hold the current dataCell index. + final int rowIndex; + + @override + GroupExpanderIconState createState() => GroupExpanderIconState(); +} + +/// Represents a collapsed and expanded icon. +class GroupExpanderIconState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 150), + ); + + if (widget.dataGridConfiguration.groupExpandCollapseRowIndex == + widget.rowIndex) { + _controller.value = widget.isExpanded ? 0.0 : 1.0; + if (widget.isExpanded) { + _controller.forward(); + } else { + _controller.reverse(); + } + widget.dataGridConfiguration.groupExpandCollapseRowIndex = -1; + } else { + _controller.value = widget.isExpanded ? 1.0 : 0.0; + } + } + + @override + Widget build(BuildContext context) { + return RotationTransition( + turns: Tween(begin: 0.0, end: 0.5).animate(_controller), + child: + widget.dataGridConfiguration.dataGridThemeHelper?.groupExpanderIcon ?? + Icon( + Icons.expand_less, + color: widget.dataGridConfiguration.colorScheme!.onSurface[153], + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} + /// Sets the `dataGridConfiguration` to the cell renderers. -void setStateDetailsInCellRendererBase(GridCellRendererBase cellRendererBase, - DataGridStateDetails dataGridStateDetails) { +void setStateDetailsInCellRendererBase( + GridCellRendererBase cellRendererBase, + DataGridStateDetails dataGridStateDetails, +) { cellRendererBase._dataGridStateDetails = dataGridStateDetails; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart index fc80db196..427c18445 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart @@ -1,33 +1,45 @@ +// ignore_for_file: no_default_cases + import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_datagrid/src/grid_common/enums.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import '../../grid_common/enums.dart'; import '../../grid_common/line_size_host.dart'; import '../../grid_common/visible_line_info.dart'; import '../helper/callbackargs.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/datagrid_helper.dart'; import '../helper/enums.dart'; +import '../helper/selection_helper.dart'; +import '../selection/selection_manager.dart' as selection_manager; import '../sfdatagrid.dart'; import 'generator.dart'; /// Provides the base functionalities for all the column types in [SfDataGrid]. class GridColumn { /// Creates the [GridColumn] for [SfDataGrid] widget. - GridColumn( - {required this.columnName, - required this.label, - this.columnWidthMode = ColumnWidthMode.none, - this.visible = true, - this.allowSorting = true, - this.autoFitPadding = const EdgeInsets.all(16.0), - this.minimumWidth = double.nan, - this.maximumWidth = double.nan, - this.width = double.nan, - this.allowEditing = true}) { + GridColumn({ + required this.columnName, + required this.label, + this.columnWidthMode = ColumnWidthMode.none, + this.visible = true, + this.allowSorting = true, + this.sortIconPosition = ColumnHeaderIconPosition.end, + this.filterIconPosition = ColumnHeaderIconPosition.end, + this.autoFitPadding = const EdgeInsets.all(16.0), + this.minimumWidth = double.nan, + this.maximumWidth = double.nan, + this.width = double.nan, + this.allowEditing = true, + this.allowFiltering = true, + this.filterPopupMenuOptions, + this.filterIconPadding = const EdgeInsets.symmetric(horizontal: 8.0), + }) { _actualWidth = double.nan; _autoWidth = double.nan; } @@ -67,6 +79,9 @@ class GridColumn { double get actualWidth => _actualWidth; late double _actualWidth; + /// Determines how the filter menu is supposed to be shown. + FilteredFrom _filterFrom = FilteredFrom.none; + /// The minimum width of the column. /// /// The column width could not be set or auto sized lesser than the @@ -136,51 +151,33 @@ class GridColumn { /// [ColumnWidthMode.fitByCellValue] or [ColumnWidthMode.fitByColumnName] /// option. final EdgeInsets autoFitPadding; -} -/// A column which displays the values of the string in its cells. -/// -/// This column has all the required APIs to customize the widget [Text] as it -/// displays [Text] for all the cells. -/// -/// ``` dart -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: employeeDataSource, -/// columns: [ -/// GridTextColumn(columnName: 'name', label: Text('Name')), -/// GridTextColumn(columnName: 'designation', label: Text('Designation')), -/// ], -/// ); -/// } -/// ``` -@Deprecated('Use GridColumn instead.') -class GridTextColumn extends GridColumn { - /// Creates a String column using [columnName] and [label]. - @Deprecated('Use GridColumn instead.') - GridTextColumn({ - required String columnName, - required Widget label, - ColumnWidthMode columnWidthMode = ColumnWidthMode.none, - EdgeInsets autoFitPadding = const EdgeInsets.all(16.0), - bool visible = true, - bool allowSorting = true, - double minimumWidth = double.nan, - double maximumWidth = double.nan, - double width = double.nan, - bool allowEditing = true, - }) : super( - columnName: columnName, - label: label, - columnWidthMode: columnWidthMode, - autoFitPadding: autoFitPadding, - visible: visible, - allowSorting: allowSorting, - minimumWidth: minimumWidth, - maximumWidth: maximumWidth, - width: width, - allowEditing: allowEditing); + /// Decides whether the UI filtering should be enabled for this column. + /// + /// This property has the highest priority over [SfDataGrid.allowFiltering] + /// property. + /// + /// See also, + /// * [SfDataGrid.onFilterChanging] – This callback will be called if the + /// column is being filtered through UI filtering. + /// * [SfDataGrid.onFilterChanged] – This callback will be called if the + /// column is filtered through UI filtering. + /// * [DataGridSource.filterConditions] – This property holds the collection + /// of the filter conditions which are applied for various columns. + final bool allowFiltering; + + /// Decides how the checked listbox and advanced filter options should be shown in filter popup. + final FilterPopupMenuOptions? filterPopupMenuOptions; + + /// The amount of space which should be added with the filter icon + final EdgeInsetsGeometry filterIconPadding; + + /// The position of the sort icon in the column headers. + final ColumnHeaderIconPosition sortIconPosition; + + /// The position of the filter icon in the column headers. + /// Typically, filter icon is placed next to sort icon. + final ColumnHeaderIconPosition filterIconPosition; } /// A column which displays the checkbox column in its cells. @@ -190,11 +187,7 @@ class GridCheckboxColumn extends GridColumn { required String columnName, required Widget label, double width = double.nan, - }) : super( - columnName: columnName, - label: label, - width: width, - ); + }) : super(columnName: columnName, label: label, width: width); } /// Contains all the properties of the checkbox column. @@ -289,7 +282,7 @@ class ColumnSizer { _isColumnSizerLoadedInitially = false; } - DataGridStateDetails? _dataGridStateDetails; + late DataGridStateDetails? _dataGridStateDetails; GridColumn? _autoFillColumn; @@ -297,6 +290,13 @@ class ColumnSizer { static const double _sortIconWidth = 20.0; static const double _sortNumberWidth = 18.0; + static const double _filterIconWidth = 18.0; + + /// Defines the outer padding of the sort and filter icon's container. We need + /// to consider this padding to measure the auto-width and height calculation. + EdgeInsetsGeometry iconsOuterPadding = const EdgeInsets.symmetric( + horizontal: 4.0, + ); void _initialRefresh(double availableWidth) { final LineSizeCollection lineSizeCollection = @@ -311,10 +311,11 @@ class ColumnSizer { _dataGridStateDetails!(); final bool hasAnySizerColumn = dataGridConfiguration.columns.any( - (GridColumn column) => - (column.columnWidthMode != ColumnWidthMode.none) || - (column.width != double.nan) || - !column.visible); + (GridColumn column) => + (column.columnWidthMode != ColumnWidthMode.none) || + (!column.width.isNaN) || + !column.visible, + ); final PaddedEditableLineSizeHostBase paddedEditableLineSizeHostBase = dataGridConfiguration.container.columnWidths; @@ -340,9 +341,18 @@ class ColumnSizer { void _ensureColumnVisibility(DataGridConfiguration dataGridConfiguration) { for (final GridColumn column in dataGridConfiguration.columns) { - final int index = dataGridConfiguration.columns.indexOf(column); - dataGridConfiguration.container.columnWidths - .setHidden(index, index, !column.visible); + int index = dataGridConfiguration.columns.indexOf(column); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + index = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + index, + ); + } + dataGridConfiguration.container.columnWidths.setHidden( + index, + index, + !column.visible, + ); } // Columns will be auto sized only if Columns doesn't have explicit width // defined. @@ -350,86 +360,115 @@ class ColumnSizer { } void _sizerColumnWidth( - DataGridConfiguration dataGridConfiguration, double viewPortWidth) { + DataGridConfiguration dataGridConfiguration, + double viewPortWidth, + ) { double totalColumnSize = 0.0; final List calculatedColumns = []; _autoFillColumn = _getColumnToFill(dataGridConfiguration); // Hide Hidden columns - final List hiddenColumns = dataGridConfiguration.columns - .where((GridColumn column) => !column.visible) - .toList(); + final List hiddenColumns = + dataGridConfiguration.columns + .where((GridColumn column) => !column.visible) + .toList(); for (final GridColumn column in hiddenColumns) { final int index = grid_helper.resolveToScrollColumnIndex( - dataGridConfiguration, dataGridConfiguration.columns.indexOf(column)); - dataGridConfiguration.container.columnWidths - .setHidden(index, index, true); + dataGridConfiguration, + dataGridConfiguration.columns.indexOf(column), + ); + dataGridConfiguration.container.columnWidths.setHidden( + index, + index, + true, + ); calculatedColumns.add(column); } // Set width based on Column.Width - final List widthColumns = dataGridConfiguration.columns - .skipWhile((GridColumn column) => !column.visible) - .where((GridColumn column) => !(column.width).isNaN) - .toList(); + final List widthColumns = + dataGridConfiguration.columns + .where((GridColumn column) => column.visible && !column.width.isNaN) + .toList(); for (final GridColumn column in widthColumns) { - totalColumnSize += - _setColumnWidth(dataGridConfiguration, column, column.width); + totalColumnSize += _setColumnWidth( + dataGridConfiguration, + column, + column.width, + ); calculatedColumns.add(column); } // Set width based on fitByCellValue mode - final List fitByCellValueColumns = dataGridConfiguration.columns - .skipWhile((GridColumn column) => !column.visible) - .where((GridColumn column) => - column.columnWidthMode == ColumnWidthMode.fitByCellValue && - column.width.isNaN) - .toList(); + final List fitByCellValueColumns = + dataGridConfiguration.columns + .where( + (GridColumn column) => + column.visible && + column.columnWidthMode == ColumnWidthMode.fitByCellValue && + column.width.isNaN, + ) + .toList(); for (final GridColumn column in fitByCellValueColumns) { if (column._autoWidth.isNaN) { final double columnWidth = _getWidthBasedOnColumn( - dataGridConfiguration, column, ColumnWidthMode.fitByCellValue); + dataGridConfiguration, + column, + ColumnWidthMode.fitByCellValue, + ); totalColumnSize += columnWidth; _setAutoWidth(column, columnWidth); } else { - totalColumnSize += - _setColumnWidth(dataGridConfiguration, column, column._autoWidth); + totalColumnSize += _setColumnWidth( + dataGridConfiguration, + column, + column._autoWidth, + ); } calculatedColumns.add(column); } // Set width based on fitByColumnName mode - final List fitByColumnNameColumns = dataGridConfiguration - .columns - .skipWhile((GridColumn column) => !column.visible) - .where((GridColumn column) => - column.columnWidthMode == ColumnWidthMode.fitByColumnName && - column.width.isNaN) - .toList(); + final List fitByColumnNameColumns = + dataGridConfiguration.columns + .where( + (GridColumn column) => + column.visible && + column.columnWidthMode == ColumnWidthMode.fitByColumnName && + column.width.isNaN, + ) + .toList(); for (final GridColumn column in fitByColumnNameColumns) { totalColumnSize += _getWidthBasedOnColumn( - dataGridConfiguration, column, ColumnWidthMode.fitByColumnName); + dataGridConfiguration, + column, + ColumnWidthMode.fitByColumnName, + ); calculatedColumns.add(column); } // Set width based on auto and lastColumnFill - List autoColumns = dataGridConfiguration.columns - .where((GridColumn column) => - column.columnWidthMode == ColumnWidthMode.auto && - column.visible && - column.width.isNaN) - .toList(); + List autoColumns = + dataGridConfiguration.columns + .where( + (GridColumn column) => + column.columnWidthMode == ColumnWidthMode.auto && + column.visible && + column.width.isNaN, + ) + .toList(); final List lastColumnFill = dataGridConfiguration.shrinkWrapColumns ? [] : dataGridConfiguration.columns - .skipWhile( - (GridColumn column) => calculatedColumns.contains(column)) - .where((GridColumn col) => - col.columnWidthMode == ColumnWidthMode.lastColumnFill && - !_isLastFillColum(col)) + .where( + (GridColumn col) => + !calculatedColumns.contains(col) && + col.columnWidthMode == ColumnWidthMode.lastColumnFill && + !_isLastFillColum(col), + ) .toList(); autoColumns = (autoColumns + lastColumnFill).toSet().toList(); @@ -437,26 +476,43 @@ class ColumnSizer { for (final GridColumn column in autoColumns) { if (column._autoWidth.isNaN) { final double columnWidth = _getWidthBasedOnColumn( - dataGridConfiguration, column, ColumnWidthMode.auto); + dataGridConfiguration, + column, + ColumnWidthMode.auto, + ); totalColumnSize += columnWidth; _setAutoWidth(column, columnWidth); } else { - totalColumnSize += - _setColumnWidth(dataGridConfiguration, column, column._autoWidth); + totalColumnSize += _setColumnWidth( + dataGridConfiguration, + column, + column._autoWidth, + ); } calculatedColumns.add(column); } - _setWidthBasedOnGrid(dataGridConfiguration, totalColumnSize, - calculatedColumns, viewPortWidth); + + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + totalColumnSize += + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth * + dataGridConfiguration.source.groupedColumns.length; + } + _setWidthBasedOnGrid( + dataGridConfiguration, + totalColumnSize, + calculatedColumns, + viewPortWidth, + ); _autoFillColumn = null; } GridColumn? _getColumnToFill(DataGridConfiguration dataGridConfiguration) { final GridColumn? column = dataGridConfiguration.columns.lastWhereOrNull( - (GridColumn c) => - c.visible && - c.width.isNaN && - c.columnWidthMode == ColumnWidthMode.lastColumnFill); + (GridColumn c) => + c.visible && + c.width.isNaN && + c.columnWidthMode == ColumnWidthMode.lastColumnFill, + ); if (column != null) { return column; } else { @@ -477,10 +533,11 @@ class ColumnSizer { } void _setWidthBasedOnGrid( - DataGridConfiguration dataGridConfiguration, - double totalColumnSize, - List calculatedColumns, - double viewPortWidth) { + DataGridConfiguration dataGridConfiguration, + double totalColumnSize, + List calculatedColumns, + double viewPortWidth, + ) { for (final GridColumn column in dataGridConfiguration.columns) { if (calculatedColumns.contains(column) || column.columnWidthMode == ColumnWidthMode.fill || @@ -492,18 +549,27 @@ class ColumnSizer { case ColumnWidthMode.fitByCellValue: if (column._autoWidth.isNaN) { final double columnWidth = _getWidthBasedOnColumn( - dataGridConfiguration, column, ColumnWidthMode.fitByCellValue); + dataGridConfiguration, + column, + ColumnWidthMode.fitByCellValue, + ); totalColumnSize += columnWidth; _setAutoWidth(column, columnWidth); } else { totalColumnSize += _setColumnWidth( - dataGridConfiguration, column, column._autoWidth); + dataGridConfiguration, + column, + column._autoWidth, + ); } calculatedColumns.add(column); break; case ColumnWidthMode.fitByColumnName: totalColumnSize += _getWidthBasedOnColumn( - dataGridConfiguration, column, ColumnWidthMode.fitByColumnName); + dataGridConfiguration, + column, + ColumnWidthMode.fitByColumnName, + ); calculatedColumns.add(column); break; case ColumnWidthMode.auto: @@ -513,19 +579,28 @@ class ColumnSizer { } if (column._autoWidth.isNaN) { final double columnWidth = _getWidthBasedOnColumn( - dataGridConfiguration, column, ColumnWidthMode.auto); + dataGridConfiguration, + column, + ColumnWidthMode.auto, + ); totalColumnSize += columnWidth; _setAutoWidth(column, columnWidth); } else { totalColumnSize += _setColumnWidth( - dataGridConfiguration, column, column._autoWidth); + dataGridConfiguration, + column, + column._autoWidth, + ); } calculatedColumns.add(column); break; case ColumnWidthMode.none: if (column.visible) { - totalColumnSize += _setColumnWidth(dataGridConfiguration, column, - dataGridConfiguration.container.columnWidths.defaultLineSize); + totalColumnSize += _setColumnWidth( + dataGridConfiguration, + column, + dataGridConfiguration.container.columnWidths.defaultLineSize, + ); calculatedColumns.add(column); } break; @@ -548,19 +623,27 @@ class ColumnSizer { !dataGridConfiguration.shrinkWrapColumns && (totalColumnSize != 0 || (totalColumnSize == 0 && remainingColumns.length == 1) || - (dataGridConfiguration.columns.any((GridColumn col) => - col.columnWidthMode == ColumnWidthMode.fill) || + (dataGridConfiguration.columns.any( + (GridColumn col) => + col.columnWidthMode == ColumnWidthMode.fill, + ) || dataGridConfiguration.columnWidthMode == ColumnWidthMode.fill))) { _setFillWidth( - dataGridConfiguration, remainingColumnWidths, remainingColumns); + dataGridConfiguration, + remainingColumnWidths, + remainingColumns, + ); } else { _setRemainingColumnsWidth(dataGridConfiguration, remainingColumns); } } - double _getWidthBasedOnColumn(DataGridConfiguration dataGridConfiguration, - GridColumn column, ColumnWidthMode columnWidthMode) { + double _getWidthBasedOnColumn( + DataGridConfiguration dataGridConfiguration, + GridColumn column, + ColumnWidthMode columnWidthMode, + ) { double width = 0.0; switch (columnWidthMode) { case ColumnWidthMode.fitByCellValue: @@ -579,23 +662,36 @@ class ColumnSizer { } double _calculateAllCellsWidth(GridColumn column) { - final double headerWidth = - _calculateColumnHeaderWidth(column, setWidth: false); - final double cellWidth = - _calculateAllCellsExceptHeaderWidth(column, setWidth: false); + final double headerWidth = _calculateColumnHeaderWidth( + column, + setWidth: false, + ); + final double cellWidth = _calculateAllCellsExceptHeaderWidth( + column, + setWidth: false, + ); return _getColumnWidth(column, max(cellWidth, headerWidth)); } - double _calculateColumnHeaderWidth(GridColumn column, - {bool setWidth = true}) { - final double width = - _getHeaderCellWidth(column) + _getSortIconWidth(column); + double _calculateColumnHeaderWidth( + GridColumn column, { + bool setWidth = true, + }) { + double iconsWidth = _getSortIconWidth(column) + _getFilterIconWidth(column); + + if (iconsWidth > 0) { + iconsWidth += iconsOuterPadding.horizontal; + } + + final double width = _getHeaderCellWidth(column) + iconsWidth; _updateSetWidth(setWidth, column, width); return width; } - double _calculateAllCellsExceptHeaderWidth(GridColumn column, - {bool setWidth = true}) { + double _calculateAllCellsExceptHeaderWidth( + GridColumn column, { + bool setWidth = true, + }) { final double width = _calculateCellWidth(column); _updateSetWidth(setWidth, column, width); return width; @@ -614,7 +710,7 @@ class ColumnSizer { _dataGridStateDetails!(); if (dataGridConfiguration.source.rows.isEmpty) { - return double.nan; + return 0; } switch (dataGridConfiguration.columnWidthCalculationRange) { @@ -623,16 +719,28 @@ class ColumnSizer { endRowIndex = dataGridConfiguration.source.rows.length - 1; break; case ColumnWidthCalculationRange.visibleRows: - final VisibleLinesCollection visibleLines = - dataGridConfiguration.container.scrollRows.getVisibleLines( - dataGridConfiguration.textDirection == TextDirection.rtl); + final VisibleLinesCollection visibleLines = dataGridConfiguration + .container + .scrollRows + .getVisibleLines( + dataGridConfiguration.textDirection == TextDirection.rtl, + ); startRowIndex = visibleLines.firstBodyVisibleIndex <= visibleLines.length - 1 - ? visibleLines.firstBodyVisibleIndex + ? grid_helper.resolveToRecordIndex( + dataGridConfiguration, + visibleLines.firstBodyVisibleIndex, + ) : 0; - endRowIndex = visibleLines.lastBodyVisibleIndex; + endRowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, + visibleLines.lastBodyVisibleIndex, + ); break; } + if (getFirstRowIndex(dataGridConfiguration) < 0) { + return column._actualWidth; + } for (int rowIndex = startRowIndex; rowIndex <= endRowIndex; rowIndex++) { autoFitWidth = max(_getCellWidth(column, rowIndex), autoFitWidth); @@ -643,41 +751,68 @@ class ColumnSizer { double _getHeaderCellWidth(GridColumn column) { return computeHeaderCellWidth( - column, _getDefaultTextStyle(_dataGridStateDetails!(), true)); + column, + _getDefaultTextStyle(_dataGridStateDetails!(), true), + ); } double _getCellWidth(GridColumn column, int rowIndex) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration) || - grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex)) { + if (dataGridConfiguration.columnWidthCalculationRange == + ColumnWidthCalculationRange.visibleRows && + (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration) || + grid_helper.isCaptionSummaryRow( + dataGridConfiguration, + rowIndex, + false, + ))) { + return 0.0; + } + + rowIndex = _resolveRowIndex(dataGridConfiguration, rowIndex); + + if (rowIndex == -1) { return 0.0; } late DataGridRow dataGridRow; switch (dataGridConfiguration.columnWidthCalculationRange) { case ColumnWidthCalculationRange.allRows: - dataGridRow = effectiveRows(dataGridConfiguration.source)[rowIndex]; + // Issue: + // FLUT-7340 - The RangeError exception is thrown when rebuilding the DataGrid after applying the filtering. + // + // Fix: + // The issue occurred because the rows were being fetched from the effective rows collection, + // which contains only the filtered rows instead of all the rows. + // Now, we fetched the rows from the entire collection to calculate the width for all the rows. + dataGridRow = dataGridConfiguration.source.rows[rowIndex]; break; case ColumnWidthCalculationRange.visibleRows: - dataGridRow = - grid_helper.getDataGridRow(dataGridConfiguration, rowIndex); + dataGridRow = effectiveRows(dataGridConfiguration.source)[rowIndex]; break; } return _measureCellWidth( - _getCellValue(dataGridRow, column), column, dataGridRow); + _getCellValue(dataGridRow, column), + column, + dataGridRow, + ); } Object? _getCellValue(DataGridRow dataGridRow, GridColumn column) { return dataGridRow .getCells() .firstWhereOrNull( - (DataGridCell cell) => cell.columnName == column.columnName) + (DataGridCell cell) => cell.columnName == column.columnName, + ) ?.value; } - void _setFillWidth(DataGridConfiguration dataGridConfiguration, - double remainingColumnWidth, List remainingColumns) { + void _setFillWidth( + DataGridConfiguration dataGridConfiguration, + double remainingColumnWidth, + List remainingColumns, + ) { final List removedColumns = []; final List columns = remainingColumns; double totalRemainingFillValue = remainingColumnWidth; @@ -700,8 +835,11 @@ class ColumnSizer { continue; } - final double computedWidth = - _setColumnWidth(dataGridConfiguration, column, fillWidth); + final double computedWidth = _setColumnWidth( + dataGridConfiguration, + column, + fillWidth, + ); if (fillWidth != computedWidth && fillWidth > 0) { isRemoved = true; columns.remove(column); @@ -733,24 +871,34 @@ class ColumnSizer { columnWidth = fillColumn._autoWidth; } - _setColumnWidth(dataGridConfiguration, fillColumn, - max(totalRemainingFillValue, columnWidth)); + _setColumnWidth( + dataGridConfiguration, + fillColumn, + max(totalRemainingFillValue, columnWidth), + ); } } - void _setRemainingColumnsWidth(DataGridConfiguration dataGridConfiguration, - List remainingColumns) { + void _setRemainingColumnsWidth( + DataGridConfiguration dataGridConfiguration, + List remainingColumns, + ) { for (final GridColumn column in remainingColumns) { if (_isLastFillColum(column) || !_isFillColumn(dataGridConfiguration, column)) { - _setColumnWidth(dataGridConfiguration, column, - dataGridConfiguration.container.columnWidths.defaultLineSize); + _setColumnWidth( + dataGridConfiguration, + column, + dataGridConfiguration.container.columnWidths.defaultLineSize, + ); } } } bool _isFillColumn( - DataGridConfiguration dataGridConfiguration, GridColumn column) { + DataGridConfiguration dataGridConfiguration, + GridColumn column, + ) { if (!column.width.isNaN) { return false; } else { @@ -774,13 +922,23 @@ class ColumnSizer { for (final GridColumn column in dataGridConfiguration.columns) { column._autoWidth = double.nan; } + + // Need to set `needToSetHorizontalOffset` property to true when the column + // widths change in the RTL mode to get proper visible columns. + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + dataGridConfiguration.container.needToSetHorizontalOffset = true; + } } double _getSortIconWidth(GridColumn column) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); double width = 0.0; - if (column.allowSorting && dataGridConfiguration.allowSorting) { + final bool isSortedColumn = dataGridConfiguration.source.sortedColumns.any( + (SortColumnDetails element) => element.name == column.columnName, + ); + if (isSortedColumn || + (column.allowSorting && dataGridConfiguration.allowSorting)) { width += _sortIconWidth; if (dataGridConfiguration.allowMultiColumnSorting && dataGridConfiguration.showSortNumbers) { @@ -790,13 +948,26 @@ class ColumnSizer { return width; } - double _setColumnWidth(DataGridConfiguration dataGridConfiguration, - GridColumn column, double columnWidth) { + double _getFilterIconWidth(GridColumn column) { + if (_dataGridStateDetails!().allowFiltering && column.allowFiltering) { + return _filterIconWidth + column.filterIconPadding.horizontal; + } + return 0.0; + } + + double _setColumnWidth( + DataGridConfiguration dataGridConfiguration, + GridColumn column, + double columnWidth, + ) { final int columnIndex = dataGridConfiguration.columns.indexOf(column); final double width = _getColumnWidth(column, columnWidth); column._actualWidth = width; - dataGridConfiguration.container.columnWidths[columnIndex] = - column._actualWidth; + final int index = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + columnIndex, + ); + dataGridConfiguration.container.columnWidths[index] = column._actualWidth; return width; } @@ -804,17 +975,23 @@ class ColumnSizer { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final int columnIndex = dataGridConfiguration.columns.indexOf(column); + final int index = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + columnIndex, + ); if (column.width < column._actualWidth) { return columnWidth; } - final double width = - dataGridConfiguration.container.columnWidths[columnIndex]; + final double width = dataGridConfiguration.container.columnWidths[index]; return _checkWidthConstraints(column, columnWidth, width); } double _checkWidthConstraints( - GridColumn column, double width, double columnWidth) { + GridColumn column, + double width, + double columnWidth, + ) { if (!column.minimumWidth.isNaN || !column.maximumWidth.isNaN) { if (!column.maximumWidth.isNaN) { if (!width.isNaN && column.maximumWidth > width) { @@ -843,6 +1020,30 @@ class ColumnSizer { return columnWidth; } + int _resolveRowIndex( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + ) { + if (dataGridConfiguration.source.groupedColumns.isEmpty || + dataGridConfiguration.columnWidthCalculationRange == + ColumnWidthCalculationRange.allRows) { + return rowIndex; + } + + final dynamic row = + dataGridConfiguration.group?.displayElements?.grouped[rowIndex]; + + if (row == null || row is! DataGridRow) { + return -1; + } + + final int recordIndex = effectiveRows( + dataGridConfiguration.source, + ).indexOf(row); + + return recordIndex; + } + /// Calculates the width of the header cell based on the [GridColumn.columnName]. /// You can override this method to perform the custom calculation for height. /// @@ -872,7 +1073,7 @@ class ColumnSizer { width: double.infinity, value: column.columnName, rowIndex: grid_helper.getHeaderIndex(_dataGridStateDetails!()), - ).width.roundToDouble(); + ).width.ceilToDouble(); } /// Calculates the width of the cell based on the [DataGridCell.value]. You @@ -899,21 +1100,26 @@ class ColumnSizer { /// /// The auto size is calculated based on default [TextStyle] of the datagrid. @protected - double computeCellWidth(GridColumn column, DataGridRow row, Object? cellValue, - TextStyle textStyle) { + double computeCellWidth( + GridColumn column, + DataGridRow row, + Object? cellValue, + TextStyle textStyle, + ) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final int rowIndex = grid_helper.resolveToRowIndex(dataGridConfiguration, - effectiveRows(dataGridConfiguration.source).indexOf(row)); + final int rowIndex = grid_helper.resolveToRowIndex( + dataGridConfiguration, + effectiveRows(dataGridConfiguration.source).indexOf(row), + ); return _calculateTextSize( - column: column, - value: cellValue, - rowIndex: rowIndex, - textStyle: textStyle, - width: double.infinity) - .width - .roundToDouble(); + column: column, + value: cellValue, + rowIndex: rowIndex, + textStyle: textStyle, + width: double.infinity, + ).width.ceilToDouble(); } /// Calculates the height of the header cell based on the [GridColumn.columnName]. @@ -935,10 +1141,12 @@ class ColumnSizer { @protected double computeHeaderCellHeight(GridColumn column, TextStyle textStyle) { return _measureCellHeight( - column, - grid_helper.getHeaderIndex(_dataGridStateDetails!()), - column.columnName, - textStyle); + column, + grid_helper.getHeaderIndex(_dataGridStateDetails!()), + column.columnName, + textStyle, + isHeaderCell: true, + ); } /// Calculates the height of the cell based on the [DataGridCell.value]. @@ -963,30 +1171,46 @@ class ColumnSizer { /// /// The auto size is calculated based on default [TextStyle] of the datagrid. @protected - double computeCellHeight(GridColumn column, DataGridRow row, - Object? cellValue, TextStyle textStyle) { + double computeCellHeight( + GridColumn column, + DataGridRow row, + Object? cellValue, + TextStyle textStyle, + ) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final int rowIndex = grid_helper.resolveToRowIndex(dataGridConfiguration, - effectiveRows(dataGridConfiguration.source).indexOf(row)); + final int rowIndex = grid_helper.resolveToRowIndex( + dataGridConfiguration, + effectiveRows(dataGridConfiguration.source).indexOf(row), + ); return _measureCellHeight(column, rowIndex, cellValue, textStyle); } - double _getAutoFitRowHeight(int rowIndex, - {bool canIncludeHiddenColumns = false, - List excludedColumns = const []}) { + double _getAutoFitRowHeight( + int rowIndex, { + bool canIncludeHiddenColumns = false, + List excludedColumns = const [], + }) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); double autoFitHeight = 0.0; if (dataGridConfiguration.stackedHeaderRows.isNotEmpty && rowIndex <= dataGridConfiguration.stackedHeaderRows.length - 1) { - return dataGridConfiguration.headerRowHeight; + // We did not provide support to automatically calculate the height of a + // stacked header based on the content. + // So we have returned the default header row height . + return 56.0; } if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration)) { return dataGridConfiguration.footerHeight; } - if (grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex)) { + if (grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex) || + grid_helper.isCaptionSummaryRow( + dataGridConfiguration, + rowIndex, + true, + )) { return dataGridConfiguration.rowHeight; } @@ -1007,39 +1231,99 @@ class ColumnSizer { _dataGridStateDetails!(); if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration)) { return computeHeaderCellHeight( - column, _getDefaultTextStyle(dataGridConfiguration, true)); + column, + _getDefaultTextStyle(dataGridConfiguration, true), + ); } else { - final DataGridRow row = - grid_helper.getDataGridRow(dataGridConfiguration, rowIndex); - return computeCellHeight(column, row, _getCellValue(row, column), - _getDefaultTextStyle(dataGridConfiguration, false)); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + rowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, + rowIndex, + ); + + if (rowIndex < 0) { + return dataGridConfiguration.rowHeight; + } + + final dynamic row = grid_helper.getGroupElement( + dataGridConfiguration, + rowIndex, + ); + if (row is! DataGridRow) { + return dataGridConfiguration.rowHeight; + } + return computeCellHeight( + column, + row, + _getCellValue(row, column), + _getDefaultTextStyle(dataGridConfiguration, false), + ); + } else { + final DataGridRow row = grid_helper.getDataGridRow( + dataGridConfiguration, + rowIndex, + ); + return computeCellHeight( + column, + row, + _getCellValue(row, column), + _getDefaultTextStyle(dataGridConfiguration, false), + ); + } } } double _measureCellWidth( - Object? cellValue, GridColumn column, DataGridRow dataGridRow) { - return computeCellWidth(column, dataGridRow, cellValue, - _getDefaultTextStyle(_dataGridStateDetails!(), false)); + Object? cellValue, + GridColumn column, + DataGridRow dataGridRow, + ) { + return computeCellWidth( + column, + dataGridRow, + cellValue, + _getDefaultTextStyle(_dataGridStateDetails!(), false), + ); } double _measureCellHeight( - GridColumn column, int rowIndex, Object? cellValue, TextStyle textStyle) { + GridColumn column, + int rowIndex, + Object? cellValue, + TextStyle textStyle, { + bool isHeaderCell = false, + }) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final int columnIndex = dataGridConfiguration.columns.indexOf(column); - double columnWidth = !column.visible || column.width == 0.0 - ? dataGridConfiguration.defaultColumnWidth - : dataGridConfiguration.container.columnWidths[columnIndex]; + final int columnIndex = resolveToScrollColumnIndex( + dataGridConfiguration, + dataGridConfiguration.columns.indexOf(column), + ); + double columnWidth = + !column.visible || column.width == 0.0 + ? dataGridConfiguration.defaultColumnWidth + : dataGridConfiguration.container.columnWidths[columnIndex]; - final double strokeWidth = _getGridLineStrokeWidth( - rowIndex: rowIndex, dataGridConfiguration: dataGridConfiguration) - .width; + final double strokeWidth = + _getGridLineStrokeWidth( + rowIndex: rowIndex, + dataGridConfiguration: dataGridConfiguration, + column: column, + ).width; final double horizontalPadding = column.autoFitPadding.horizontal; + double iconsWidth = 0.0; + if (isHeaderCell) { + iconsWidth = _getSortIconWidth(column) + _getFilterIconWidth(column); + if (iconsWidth > 0) { + iconsWidth += iconsOuterPadding.horizontal; + } + } + // Removed the padding and gridline stroke width from the column width to // measure the accurate height for the cell content. - columnWidth -= _getSortIconWidth(column) + horizontalPadding + strokeWidth; + columnWidth -= iconsWidth + horizontalPadding + strokeWidth; return _calculateTextSize( column: column, @@ -1047,48 +1331,79 @@ class ColumnSizer { width: columnWidth, textStyle: textStyle, rowIndex: rowIndex, - ).height.roundToDouble(); + ).height.ceilToDouble(); } TextStyle _getDefaultTextStyle( - DataGridConfiguration dataGridConfiguration, bool isHeader) { + DataGridConfiguration dataGridConfiguration, + bool isHeader, + ) { if (isHeader) { return TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ); } else { return TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ); } } - Size _getGridLineStrokeWidth( - {required int rowIndex, - required DataGridConfiguration dataGridConfiguration}) { + Size _getGridLineStrokeWidth({ + required int rowIndex, + required DataGridConfiguration dataGridConfiguration, + required GridColumn column, + }) { final double strokeWidth = - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth; + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth!; final GridLinesVisibility gridLinesVisibility = rowIndex <= grid_helper.getHeaderIndex(dataGridConfiguration) ? dataGridConfiguration.headerGridLinesVisibility : dataGridConfiguration.gridLinesVisibility; + final GridColumn firstVisibleColumn = dataGridConfiguration.columns + .firstWhere( + (GridColumn column) => column.visible && column.width != 0.0, + ); + final GridColumn lastVisibleColumn = dataGridConfiguration.columns + .lastWhere( + (GridColumn column) => column.visible && column.width != 0.0, + ); + bool isFirstColumn = firstVisibleColumn.columnName == column.columnName; + final bool isLastColumn = lastVisibleColumn.columnName == column.columnName; + + isFirstColumn = + isFirstColumn && + (dataGridConfiguration.source.groupedColumns.isEmpty || + (dataGridConfiguration.source.groupedColumns.isNotEmpty && + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth <= + 0)); + switch (gridLinesVisibility) { case GridLinesVisibility.none: return Size.zero; case GridLinesVisibility.both: - return Size(strokeWidth, strokeWidth); + return Size( + isFirstColumn ? (strokeWidth + strokeWidth) : strokeWidth, + rowIndex == 0 ? (strokeWidth + strokeWidth) : strokeWidth, + ); case GridLinesVisibility.vertical: - return Size(strokeWidth, 0); + return Size( + isFirstColumn ? (strokeWidth + strokeWidth) : strokeWidth, + rowIndex == 0 ? strokeWidth : 0, + ); case GridLinesVisibility.horizontal: - return Size(0, strokeWidth); + return Size( + (isFirstColumn || isLastColumn) ? strokeWidth : 0, + rowIndex == 0 ? (strokeWidth + strokeWidth) : strokeWidth, + ); } } @@ -1109,21 +1424,25 @@ class ColumnSizer { _dataGridStateDetails!(); final Size strokeWidthSize = _getGridLineStrokeWidth( - rowIndex: rowIndex, dataGridConfiguration: dataGridConfiguration); + rowIndex: rowIndex, + dataGridConfiguration: dataGridConfiguration, + column: column, + ); final TextPainter textPainter = TextPainter( - text: TextSpan(text: value?.toString() ?? '', style: textStyle), - textScaleFactor: dataGridConfiguration.textScaleFactor, - textDirection: dataGridConfiguration.textDirection) - ..layout(maxWidth: width); + text: TextSpan(text: value?.toString() ?? '', style: textStyle), + textScaler: TextScaler.linear(dataGridConfiguration.textScaleFactor), + textDirection: dataGridConfiguration.textDirection, + )..layout(maxWidth: width); textSize = Size( - textPainter.size.width + - strokeWidthSize.width + - column.autoFitPadding.horizontal, - textPainter.size.height + - strokeWidthSize.height + - column.autoFitPadding.vertical); + textPainter.size.width + + strokeWidthSize.width + + column.autoFitPadding.horizontal, + textPainter.size.height + + strokeWidthSize.height + + column.autoFitPadding.vertical, + ); return textSize; } @@ -1146,7 +1465,9 @@ void resetAutoCalculation(ColumnSizer columnSizer) { /// Updates the column sizer's state whether its loaded or not initially. void updateColumnSizerLoadedInitiallyFlag( - ColumnSizer columnSizer, bool isLoaded) { + ColumnSizer columnSizer, + bool isLoaded, +) { columnSizer._isColumnSizerLoadedInitially = isLoaded; } @@ -1155,18 +1476,30 @@ double getSortIconWidth(ColumnSizer columnSizer, GridColumn column) { return columnSizer._getSortIconWidth(column); } +/// Returns the width of a filter icon. +double getFilterIconWidth(ColumnSizer columnSizer, GridColumn column) { + return columnSizer._getFilterIconWidth(column); +} + /// Returns the auto fit row height of the given row based on index. -double getAutoFitRowHeight(ColumnSizer columnSizer, int rowIndex, - {bool canIncludeHiddenColumns = false, - List excludedColumns = const []}) { - return columnSizer._getAutoFitRowHeight(rowIndex, - canIncludeHiddenColumns: canIncludeHiddenColumns, - excludedColumns: excludedColumns); +double getAutoFitRowHeight( + ColumnSizer columnSizer, + int rowIndex, { + bool canIncludeHiddenColumns = false, + List excludedColumns = const [], +}) { + return columnSizer._getAutoFitRowHeight( + rowIndex, + canIncludeHiddenColumns: canIncludeHiddenColumns, + excludedColumns: excludedColumns, + ); } /// Sets `dataGridConfiguration` to the [ColumnSizer]. void setStateDetailsInColumnSizer( - ColumnSizer columnSizer, DataGridStateDetails dataGridCellDetails) { + ColumnSizer columnSizer, + DataGridStateDetails dataGridCellDetails, +) { columnSizer._dataGridStateDetails = dataGridCellDetails; } @@ -1234,8 +1567,11 @@ class ColumnResizeController { dataRow.rowType == RowType.headerRow || dataRow.rowType == RowType.stackedHeaderRow; - VisibleLineInfo? _getHitTestResult(double dx, - {bool isPressed = false, bool canAllowBuffer = true}) { + VisibleLineInfo? _getHitTestResult( + double dx, { + bool isPressed = false, + bool canAllowBuffer = true, + }) { if (!isResizing) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); @@ -1245,13 +1581,23 @@ class ColumnResizeController { _resizingLine = _getResizingLine(dx, canAllowBuffer); + int startColumnIndex = grid_helper.resolveToStartColumnIndex( + dataGridConfiguration, + ); + // Need to disable column resizing for the indent columns. + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + startColumnIndex += dataGridConfiguration.source.groupedColumns.length; + } + if (_resizingLine != null && - _resizingLine!.lineIndex >= - grid_helper.resolveToStartColumnIndex(dataGridConfiguration)) { + _resizingLine!.lineIndex >= startColumnIndex) { // To ensure the resizing line for the stacked header row. if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { - if (!_canAllowResizing(dx, resizingDataCell, - canAllowBuffer: canAllowBuffer)) { + if (!_canAllowResizing( + dx, + resizingDataCell, + canAllowBuffer: canAllowBuffer, + )) { return null; } } @@ -1265,7 +1611,7 @@ class ColumnResizeController { // Fix: // An issue occurred due to not considering the column which is corner clipped by the frozen columns. // Now, we have restricted the clipped columns to disable resizing for the columns. - if (_resizingLine!.isClippedCorner) { + if (_resizingLine!.isClippedCorner && _resizingLine!.clippedSize > 0) { return null; } _ensureDataCell(dx, resizingDataCell); @@ -1282,13 +1628,28 @@ class ColumnResizeController { if (_resizingLine != null && !isResizeIndicatorVisible) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); - _currentResizingColumn = - dataGridConfiguration.columns[_resizingLine!.lineIndex]; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + if (_resizingLine!.lineIndex >= + dataGridConfiguration.source.groupedColumns.length && + _resizingLine!.lineIndex < + dataGridConfiguration.container.columnCount) { + final int lineIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, + _resizingLine!.lineIndex, + ); + _currentResizingColumn = dataGridConfiguration.columns[lineIndex]; + } + } else { + _currentResizingColumn = + dataGridConfiguration.columns[_resizingLine!.lineIndex]; + } if (_raiseColumnResizeStart()) { isResizeIndicatorVisible = true; - indicatorPosition = _getIndicatorPosition(_resizingLine!, - dataGridConfiguration.textDirection == TextDirection.ltr, - isDefault: true); + indicatorPosition = _getIndicatorPosition( + _resizingLine!, + dataGridConfiguration.textDirection == TextDirection.ltr, + isDefault: true, + ); _resizingColumnWidth = _resizingLine!.size; @@ -1310,9 +1671,11 @@ class ColumnResizeController { // Need to update the resizing line to get the line's updated details after // resizing it. _resizingLine = dataGridConfiguration.container.scrollColumns - .getVisibleLineAtLineIndex(_resizingLine!.lineIndex, - isRightToLeft: - dataGridConfiguration.textDirection == TextDirection.rtl); + .getVisibleLineAtLineIndex( + _resizingLine!.lineIndex, + isRightToLeft: + dataGridConfiguration.textDirection == TextDirection.rtl, + ); if (dataGridConfiguration.columnResizeMode == ColumnResizeMode.onResize) { dataGridConfiguration.container.isDirty = true; @@ -1337,8 +1700,11 @@ class ColumnResizeController { // * Helper methods - bool _canAllowResizing(double downX, DataCellBase? dataColumn, - {bool canAllowBuffer = true}) { + bool _canAllowResizing( + double downX, + DataCellBase? dataColumn, { + bool canAllowBuffer = true, + }) { if (dataColumn != null && _resizingLine != null && dataColumn.dataRow!.rowType == RowType.stackedHeaderRow && @@ -1349,14 +1715,14 @@ class ColumnResizeController { final DataCellBase? dataCell = dataColumn.dataRow!.visibleColumns .firstWhereOrNull((DataCellBase dataCell) { - cellLeft = dataCell.columnIndex; - cellRight = dataCell.columnIndex + dataCell.columnSpan; - lineIndex = _resizingLine!.lineIndex; + cellLeft = dataCell.columnIndex; + cellRight = dataCell.columnIndex + dataCell.columnSpan; + lineIndex = _resizingLine!.lineIndex; - return cellLeft == lineIndex || - cellRight == lineIndex || - (lineIndex > cellLeft && lineIndex < cellRight); - }); + return cellLeft == lineIndex || + cellRight == lineIndex || + (lineIndex > cellLeft && lineIndex < cellRight); + }); if (dataCell != null) { final bool isLTR = @@ -1398,12 +1764,14 @@ class ColumnResizeController { final bool isLTR = dataGridConfiguration.textDirection == TextDirection.ltr; final VisibleLineInfo? visibleLine = dataGridConfiguration - .container.scrollColumns + .container + .scrollColumns .getVisibleLineAtLineIndex(dataCell.columnIndex, isRightToLeft: !isLTR); if (visibleLine != null) { - final bool canCheckNearCell = !(visibleLine.isLastLine && - visibleLine.isClippedCorner && - visibleLine.isClippedOrigin); + final bool canCheckNearCell = + !(visibleLine.isLastLine && + visibleLine.isClippedCorner && + visibleLine.isClippedOrigin); final double origin = isLTR ? visibleLine.clippedOrigin : visibleLine.corner; final double corner = @@ -1411,11 +1779,17 @@ class ColumnResizeController { if (canCheckNearCell) { if (_hitTestPrecision! > (corner - x).abs()) { - nearDataCell = _getDataCell(dataGridConfiguration, dataCell.rowIndex, - dataCell.columnIndex + 1); + nearDataCell = _getDataCell( + dataGridConfiguration, + dataCell.rowIndex, + dataCell.columnIndex + 1, + ); } else if (_hitTestPrecision! > (origin - x).abs()) { - nearDataCell = _getDataCell(dataGridConfiguration, dataCell.rowIndex, - dataCell.columnIndex - 1); + nearDataCell = _getDataCell( + dataGridConfiguration, + dataCell.rowIndex, + dataCell.columnIndex - 1, + ); } } @@ -1427,24 +1801,36 @@ class ColumnResizeController { } } - DataCellBase? _getDataCell(DataGridConfiguration dataGridConfiguration, - int rowIndex, int columnIndex) { + DataCellBase? _getDataCell( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + int columnIndex, + ) { final DataRowBase? dataRow = dataGridConfiguration - .container.rowGenerator.items - .firstWhereOrNull((DataRowBase element) => - rowIndex >= 0 && element.rowIndex == rowIndex); + .container + .rowGenerator + .items + .firstWhereOrNull( + (DataRowBase element) => + rowIndex >= 0 && element.rowIndex == rowIndex, + ); if (dataRow == null || dataRow.visibleColumns.isEmpty) { return null; } - return dataRow.visibleColumns.firstWhereOrNull((DataCellBase dataCell) => - columnIndex >= 0 && dataCell.columnIndex == columnIndex); + return dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => + columnIndex >= 0 && dataCell.columnIndex == columnIndex, + ); } - double _getIndicatorPosition(VisibleLineInfo line, bool isLTR, - {bool isDefault = false, - ScrollController? scrollController, - double? currentColumnWidth}) { + double _getIndicatorPosition( + VisibleLineInfo line, + bool isLTR, { + bool isDefault = false, + ScrollController? scrollController, + double? currentColumnWidth, + }) { late double indicatorLeft; final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (isDefault) { @@ -1458,16 +1844,19 @@ class ColumnResizeController { scrollController.position.extentAfter == 0))) { indicatorLeft = isLTR ? line.corner : line.clippedOrigin; } else { - indicatorLeft = isLTR - ? (line.origin + currentColumnWidth!) - : (line.corner + line.scrollOffset - currentColumnWidth!); + indicatorLeft = + isLTR + ? (line.origin + currentColumnWidth!) + : (line.corner + line.scrollOffset - currentColumnWidth!); } } // To remove the half of stroke width to show the indicator to the // center of grid line. - indicatorLeft -= dataGridConfiguration - .dataGridThemeHelper!.columnResizeIndicatorStrokeWidth / + indicatorLeft -= + dataGridConfiguration + .dataGridThemeHelper! + .columnResizeIndicatorStrokeWidth! / 2; return indicatorLeft; @@ -1499,13 +1888,19 @@ class ColumnResizeController { } } - VisibleLineInfo? _getVisibleLineAtPoint(double position, bool isRTL, - {bool checkNearLine = false}) { + VisibleLineInfo? _getVisibleLineAtPoint( + double position, + bool isRTL, { + bool checkNearLine = false, + }) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (checkNearLine) { return dataGridConfiguration.container.scrollColumns.getLineNearCorner( - position, _hitTestPrecision!, CornerSide.both, - isRightToLeft: isRTL); + position, + _hitTestPrecision!, + CornerSide.both, + isRightToLeft: isRTL, + ); } else { return dataGridConfiguration.container.scrollColumns .getVisibleLineAtPoint(position, true, isRTL); @@ -1514,7 +1909,9 @@ class ColumnResizeController { /// Resolves the point of the current local position to get the visibleLine. double getXPosition( - DataGridConfiguration dataGridConfiguration, double localPosition) { + DataGridConfiguration dataGridConfiguration, + double localPosition, + ) { final ScrollController scrollController = dataGridConfiguration.horizontalScrollController!; if (dataGridConfiguration.textDirection == TextDirection.ltr) { @@ -1548,10 +1945,21 @@ class ColumnResizeController { bool _raiseColumnResizeStart() { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + + /// The indexOf method is utilized to iterate through the properties of the column collection and determine the index of the desired element. + /// In this scenario, while resizing, the actualWidth property does not match the currentResizing column. + /// As a result, the indexOf method returns -1. To obtain the correct currentResizing column index, the indexWhere method is employed instead. if (dataGridConfiguration.onColumnResizeStart != null) { return dataGridConfiguration.onColumnResizeStart!( - ColumnResizeStartDetails( - column: _currentResizingColumn!, width: _resizingColumnWidth)); + ColumnResizeStartDetails( + columnIndex: dataGridConfiguration.columns.indexWhere( + (GridColumn element) => + element.columnName == _currentResizingColumn!.columnName, + ), + column: _currentResizingColumn!, + width: _resizingColumnWidth, + ), + ); } return true; } @@ -1560,8 +1968,15 @@ class ColumnResizeController { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (dataGridConfiguration.onColumnResizeUpdate != null) { return dataGridConfiguration.onColumnResizeUpdate!( - ColumnResizeUpdateDetails( - column: _currentResizingColumn!, width: currentColumnWidth)); + ColumnResizeUpdateDetails( + columnIndex: dataGridConfiguration.columns.indexWhere( + (GridColumn element) => + element.columnName == _currentResizingColumn!.columnName, + ), + column: _currentResizingColumn!, + width: currentColumnWidth, + ), + ); } return true; } @@ -1569,25 +1984,40 @@ class ColumnResizeController { void _raiseColumnResizeEnd(double currentColumnWidth) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (dataGridConfiguration.onColumnResizeEnd != null) { - dataGridConfiguration.onColumnResizeEnd!(ColumnResizeEndDetails( - column: _currentResizingColumn!, width: currentColumnWidth)); + dataGridConfiguration.onColumnResizeEnd!( + ColumnResizeEndDetails( + columnIndex: dataGridConfiguration.columns.indexWhere( + (GridColumn element) => + element.columnName == _currentResizingColumn!.columnName, + ), + column: _currentResizingColumn!, + width: currentColumnWidth, + ), + ); } } // * Pointer Events /// Handles the pointer down event for the column resizing. - void onPointerDown(PointerDownEvent event, DataRowBase dataRow) { + Future onPointerDown( + PointerDownEvent event, + DataRowBase dataRow, + ) async { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (dataGridConfiguration.isDesktop || _canStartResizeInMobile) { if (_isHeaderRow(dataRow)) { // Clears the editing before start resizing a column. if (dataGridConfiguration.currentCell.isEditing) { - dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + ); } - final VisibleLineInfo? resizingLine = - _getHitTestResult(event.localPosition.dx, isPressed: true); + final VisibleLineInfo? resizingLine = _getHitTestResult( + event.localPosition.dx, + isPressed: true, + ); _canStartResizeInMobile = false; @@ -1641,16 +2071,21 @@ class ColumnResizeController { // To restricts the width of current resizing column from its minimum and // maximum width. _resizingColumnWidth = dataGridConfiguration.columnSizer - ._checkWidthConstraints(_currentResizingColumn!, - _resizingColumnWidth, currentColumnWidth); + ._checkWidthConstraints( + _currentResizingColumn!, + _resizingColumnWidth, + currentColumnWidth, + ); // To avoid resizing of column width after reached zero; _resizingColumnWidth = max(0.0, _resizingColumnWidth); final double indicatorPosition = _getIndicatorPosition( - _resizingLine!, isLTR, - scrollController: scrollController, - currentColumnWidth: _resizingColumnWidth); + _resizingLine!, + isLTR, + scrollController: scrollController, + currentColumnWidth: _resizingColumnWidth, + ); _onResizing(_resizingColumnWidth, indicatorPosition); } @@ -1699,9 +2134,10 @@ class ColumnResizeController { !dataGridConfiguration.isDesktop) { if (_isHeaderRow(dataRow)) { final VisibleLineInfo? resizingLine = _getHitTestResult( - details.localPosition.dx, - isPressed: true, - canAllowBuffer: false); + details.localPosition.dx, + isPressed: true, + canAllowBuffer: false, + ); if (resizingLine != null && isResizeIndicatorVisible) { _isLongPressEnabled = true; @@ -1724,6 +2160,16 @@ class ColumnResizeController { } canSwitchResizeColumnCursor = _getHitTestResult(localPosition.dx) != null; + + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (canSwitchResizeColumnCursor && + dataGridConfiguration.columnDragAndDropController + .canAllowColumnDragAndDrop()) { + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'columnDragAndDrop', + ); + } } void _resetColumnResize({bool canResetDataCell = true}) { @@ -1738,7 +2184,1748 @@ class ColumnResizeController { void _rebuild() { dataGridStateDetails().container.isDirty = true; - notifyDataGridPropertyChangeListeners(dataGridStateDetails().source, - propertyName: 'columnResizing'); + notifyDataGridPropertyChangeListeners( + dataGridStateDetails().source, + propertyName: 'columnResizing', + ); + } +} + +/// Controls how the filtering should be applied in [SfDataGrid]. +@immutable +class FilterCondition { + /// Creates the [FilterCondition] for [SfDataGrid]. + const FilterCondition({ + required this.type, + required this.value, + this.isCaseSensitive = false, + this.filterOperator = FilterOperator.or, + this.filterBehavior = FilterBehavior.strongDataType, + }); + + /// The type of the filter should be applied for filter condition. + final FilterType type; + + /// The value which should be compared for filtering. + final Object? value; + + /// Decides whether the filtering should be considered based on case sensitive. + final bool isCaseSensitive; + + /// The type of the logical operator. + final FilterOperator filterOperator; + + /// The behavior of the filtering for the filter condition. + final FilterBehavior filterBehavior; + + @override + bool operator ==(Object other) { + return other is FilterCondition && + type == other.type && + value == other.value && + isCaseSensitive == other.isCaseSensitive && + filterOperator == other.filterOperator && + filterBehavior == other.filterBehavior; + } + + @override + int get hashCode { + final List values = [ + type, + value, + isCaseSensitive, + filterOperator, + filterBehavior, + ]; + return Object.hashAll(values); + } +} + +/// Provides the base functionalities to process the filtering in [SfDataGrid]. +class DataGridFilterHelper { + /// Creates the [DataGridFilterHelper] for [SfDataGrid]. + DataGridFilterHelper(this._dataGridStateDetails) { + checkboxFilterHelper = DataGridCheckboxFilterHelper(); + advancedFilterHelper = DataGridAdvancedFilterHelper(_dataGridStateDetails); + } + + /// Holds the data rows that before apply filtering to the current column. + /// Sets the rows when generating the checkbox list view items and use it to + /// apply filtering to optimize the filtering instead of filter whole rows again. + List _previousDataRows = []; + + /// This flag is used to check whether the filtering popup menu is currently + /// showing or not in the view. + bool isFilterPopupMenuShowing = false; + + /// Determines how the filter menu should be opened + FilteredFrom filterFrom = FilteredFrom.none; + + final DataGridStateDetails _dataGridStateDetails; + + late int _checkedItemsCount, _unCheckedItemsCount; + + /// Holds the instance of a [DataGridCheckboxFilterHelper] class. + late DataGridCheckboxFilterHelper checkboxFilterHelper; + + /// Holds the instance of a [DataGridAdvancedFilterHelper] class. + late DataGridAdvancedFilterHelper advancedFilterHelper; + + /// Provides the height of the popup menu tile. + double get tileHeight => + _dataGridStateDetails().isDesktop + ? _dataGridStateDetails() + .dataGridThemeHelper! + .filterPopupTextStyle! + .fontSize! + + 26 + : _dataGridStateDetails() + .dataGridThemeHelper! + .filterPopupTextStyle! + .fontSize! + + 38; + + /// Provides the primary color. + Color get primaryColor => _dataGridStateDetails().colorScheme!.primary; + + /// Provides the text style to the tiles. + TextStyle get textStyle => + _dataGridStateDetails().dataGridThemeHelper!.filterPopupTextStyle!; + + /// Provides the text style to the disabled tiles. + TextStyle get disableTextStyle => + _dataGridStateDetails() + .dataGridThemeHelper! + .filterPopupDisabledTextStyle!; + + /// Apply filter to the effective rows based on `filterConditions`. + void applyFilter() { + if (_dataGridStateDetails().source.filterConditions.isNotEmpty) { + _refreshFilter(); + } + } + + /// Creates filter conditions based on the UI filtering. + void createFilterConditions(bool isCheckboxFilter, GridColumn column) { + // Creates filter conditions if it's a checkbox filter. + if (isCheckboxFilter) { + _checkedItemsCount = + checkboxFilterHelper.items + .where((FilterElement element) => element.isSelected) + .length; + _unCheckedItemsCount = + checkboxFilterHelper.items.length - _checkedItemsCount; + + _createCheckboxFilterConditions(column); + } else { + _createAdvancedFilterConditions(column); + } + } + + void _createCheckboxFilterConditions(GridColumn column) { + final DataGridSource source = _dataGridStateDetails().source; + if (_unCheckedItemsCount == 0 && + checkboxFilterHelper._searchedItems.isEmpty) { + // Need to invoke `onFilterChanging` and `onFilterChanged` callback to notify + // the filtering changes when tapping `SelectAll` button to select all the + // rows in the Checkbox UI filtering. + if (source.filterConditions.containsKey(column.columnName)) { + if (_invokeFilterChangingCallback(column, [])) { + removeFilterConditions(source, column.columnName); + } else { + return; + } + } + } else { + final bool useSelected = + !(_checkedItemsCount > _unCheckedItemsCount && + _unCheckedItemsCount > 0); + final List conditions = []; + for (final FilterElement value in checkboxFilterHelper.items) { + if (value.isSelected == useSelected) { + final FilterType filterType = + useSelected ? FilterType.equals : FilterType.notEqual; + FilterOperator filterOperator = + useSelected ? FilterOperator.or : FilterOperator.and; + final String? filterValue = + value.value == '(Blanks)' ? null : value.value.toString(); + + // Sets the first filter condition's filter operator as 'AND' to + // perform multi-column filtering. + if (conditions.isEmpty) { + filterOperator = FilterOperator.and; + } + + conditions.add( + FilterCondition( + type: filterType, + isCaseSensitive: true, + value: filterValue, + filterBehavior: FilterBehavior.stringDataType, + filterOperator: filterOperator, + ), + ); + } + } + + addFilterConditions(source, column.columnName, conditions); + } + + if (source.filterConditions.isEmpty) { + setFilterFrom(column, FilteredFrom.none); + } else { + setFilterFrom(column, FilteredFrom.checkboxFilter); + } + + if (checkboxFilterHelper._searchedItems.isNotEmpty) { + checkboxFilterHelper._searchedItems.clear(); + } + + _applyViewFilter(column); + } + + /// Sets the given value to the column's `filterFrom` property. + void setFilterFrom(GridColumn column, FilteredFrom filteredfrom) { + filterFrom = column._filterFrom = filteredfrom; + } + + /// Gets the column's `filterFrom` property. + FilteredFrom getFilterForm(GridColumn column) { + return column._filterFrom; + } + + /// Reset the column `filter From` property when the datagrid is disposed. + void resetColumnProperties(DataGridConfiguration dataGridConfiguration) { + for (final GridColumn column in dataGridConfiguration.columns) { + column._filterFrom = FilteredFrom.none; + column._actualWidth = double.nan; + } + } + + /// Format the given cell value to the string data type to display. + String getDisplayValue(Object? value) { + if (value != null) { + // Should return if the value defines the blank filter. + if (value == '(Blanks)') { + return '(Blanks)'; + } + + switch (advancedFilterHelper.advancedFilterType) { + case AdvancedFilterType.text: + case AdvancedFilterType.numeric: + return value is! String ? value.toString() : value; + case AdvancedFilterType.date: + final DateTime date = value as DateTime; + return date.toString().split(' ').first; + } + } + return ''; + } + + /// Format the given string value to the actual cell value with same data type. + Object? getActualValue(Object? value) { + if (value != null) { + switch (advancedFilterHelper.advancedFilterType) { + case AdvancedFilterType.text: + return value is! String ? value.toString() : value; + case AdvancedFilterType.numeric: + return value is! num ? num.tryParse(value.toString()) : value; + case AdvancedFilterType.date: + if (value is! DateTime) { + // To convert a given string to the DateTime format. + final List values = value.toString().split('-'); + if (values.length > 2 && + values.every((String element) => element.isNotEmpty)) { + // To validate the day and month. + if (int.parse(values[1]) > 12 || int.parse(values[2]) > 31) { + return null; + } + return DateTime.tryParse(value.toString()); + } + return null; + } + return value; + } + } else { + return value; + } + } + + void _debugCheckDataType(DataGridConfiguration dataGridConfiguration) { + Object? getFirstCellValue(List rows, String columnName) { + Object? cellValue; + for (final DataGridRow row in rows) { + cellValue = _getCellValue(row, columnName); + if (cellValue != null) { + break; + } + } + return cellValue; + } + + void throwAssertFailure(String message) { + throw FlutterError.fromParts([ErrorSummary(message)]); + } + + final DataGridSource source = dataGridConfiguration.source; + // Should avoid the type checking if the `effectiveRows` contains an empty list. + if (source.effectiveRows.isNotEmpty && source.filterConditions.isNotEmpty) { + for (final String columnName in source.filterConditions.keys) { + final GridColumn? column = dataGridConfiguration.columns + .firstWhereOrNull( + (GridColumn column) => column.columnName == columnName, + ); + if (column == null) { + throwAssertFailure( + "The $columnName doesn't exist in the SfDataGrid.columns collection", + ); + continue; + } + + final Object? cellValue = getFirstCellValue( + source.effectiveRows, + columnName, + ); + for (final FilterCondition condition + in source.filterConditions[columnName]!) { + assert(() { + if (condition.filterBehavior == FilterBehavior.strongDataType) { + // Issue: + // FLUT-7286 - Type mismatch error has been thrown when giving an integer value for double type column. + // + // Fix: + // The issue arose because we didn't check the cellValue and condition type is num or not. + // Now, we checked the condition of whether both types are num, and we allow when it's num. + if ((cellValue is num && condition.value is num) && + (condition.type == FilterType.greaterThan || + condition.type == FilterType.greaterThanOrEqual || + condition.type == FilterType.lessThan || + condition.type == FilterType.lessThanOrEqual)) { + return true; + } else { + if (cellValue?.runtimeType != condition.value?.runtimeType && + (cellValue is! num && condition.value is! num)) { + throwAssertFailure( + '${condition.value?.runtimeType} and ${cellValue.runtimeType} are not the same data type', + ); + } else if (condition.type == FilterType.contains || + condition.type == FilterType.doesNotContain || + condition.type == FilterType.beginsWith || + condition.type == FilterType.doesNotBeginWith || + condition.type == FilterType.endsWith || + condition.type == FilterType.doesNotEndsWith) { + throwAssertFailure( + 'FilterBehaviour and FilterType are not correct', + ); + } else if (condition.type == FilterType.greaterThan || + condition.type == FilterType.greaterThanOrEqual || + condition.type == FilterType.lessThan || + condition.type == FilterType.lessThanOrEqual) { + if (cellValue is String) { + final String filterType = + condition.type.toString().split('.').last; + throwAssertFailure( + "The filter type $filterType can't check with the String type", + ); + } + } + } + } else { + if (condition.type == FilterType.greaterThan || + condition.type == FilterType.greaterThanOrEqual || + condition.type == FilterType.lessThan || + condition.type == FilterType.lessThanOrEqual) { + throwAssertFailure( + 'FilterBehaviour and FilterType are not correct', + ); + } + } + return true; + }()); + } + } + } + } + + void _refreshFilter() { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + // Checks whether the filter value and cell value have the same data type or not. + // If not, it throws an assert failure. + _debugCheckDataType(dataGridConfiguration); + + if (dataGridConfiguration.source.filterConditions.isNotEmpty) { + final DataGridSource source = dataGridConfiguration.source; + final List filteredRows = _getFilterRows( + source.rows, + source.filterConditions, + ); + refreshEffectiveRows(source, filteredRows); + } + } + + void _applyViewFilter(GridColumn column) { + final DataGridSource source = _dataGridStateDetails().source; + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (source.filterConditions.containsKey(column.columnName)) { + final List? filterConditions = + source.filterConditions[column.columnName]; + if (!_invokeFilterChangingCallback(column, filterConditions!)) { + removeFilterConditions(source, column.columnName); + return; + } + + List filteredRows = []; + if (_previousDataRows.isNotEmpty) { + filteredRows = _getFilterRows( + _previousDataRows, + >{column.columnName: filterConditions}, + ); + } else { + filteredRows = _getFilterRows(source.rows, source.filterConditions); + } + + if (_previousDataRows.isNotEmpty) { + _previousDataRows.clear(); + } + + // Need to apply sorting to the filtered rows. + performSorting(source, filteredRows); + refreshEffectiveRows(source, filteredRows); + updateDataPager(source); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + updateDataSource(source, true); + } + selection_manager.refreshSelectedRows(dataGridConfiguration); + notifyDataGridPropertyChangeListeners(source, propertyName: 'Filtering'); + _invokeFilterChangedCallback(column, filterConditions); + } else { + updateDataSource(source, true); + selection_manager.refreshSelectedRows(dataGridConfiguration); + notifyDataGridPropertyChangeListeners(source, propertyName: 'Filtering'); + _invokeFilterChangedCallback(column, []); + } + } + + // Gets rows based on current filtered conditions. + List _getPreviousFilteredRows(String columnName) { + List? items; + final DataGridSource source = _dataGridStateDetails().source; + final List? conditions = + source.filterConditions[columnName]; + + if (conditions != null && conditions.isNotEmpty) { + removeFilterConditions(source, columnName); + items = + source.filterConditions.isEmpty + ? source.rows + : _getFilterRows(source.rows, source.filterConditions); + _previousDataRows = items.toList(); + addFilterConditions(source, columnName, conditions); + } else { + _previousDataRows.clear(); + } + + return items ?? source.effectiveRows; + } + + List _getCellValues( + GridColumn column, + List items, + ) { + bool hasBlankValues = false; + final DataGridSource source = _dataGridStateDetails().source; + final List conditions = + source.filterConditions[column.columnName] ?? []; + + bool isSelected(Object? value) { + if (conditions.isNotEmpty) { + // Checkes the previous filtered data rows with current effective rows to + // find selected and unselected items in the checkbox list view. + for (final DataGridRow row in source.effectiveRows) { + final DataGridCell? cell = row.getCells().firstWhereOrNull( + (DataGridCell element) => element.columnName == column.columnName, + ); + if (cell?.value?.toString() == value?.toString()) { + return true; + } + } + return false; + } + return true; + } + + final List cellValues = []; + final List filterElements = []; + for (final DataGridRow row in items) { + final DataGridCell? cell = row.getCells().firstWhereOrNull( + (DataGridCell element) => element.columnName == column.columnName, + ); + if (cell != null) { + if (cell.value != null) { + cellValues.add(cell.value); + } else if (!hasBlankValues) { + hasBlankValues = true; + } + } + } + + if (hasBlankValues) { + filterElements.add( + FilterElement(value: '(Blanks)', isSelected: isSelected(null)), + ); + } + + if (cellValues.isNotEmpty) { + final Object cellValue = cellValues.first; + final bool convertToString = + !(cellValue is num || cellValue is DateTime || cellValue is String); + + // Sort the items to display in the ascending order. + cellValues.sort((Object a, Object b) { + final dynamic value1 = convertToString ? a.toString() : a; + final dynamic value2 = convertToString ? b.toString() : b; + + return value1.compareTo(value2); + }); + + filterElements.addAll( + cellValues + .toSet() + .map( + (Object e) => FilterElement(value: e, isSelected: isSelected(e)), + ) + .toList(), + ); + } + + return filterElements; + } + + /// Helps to end edit the current cell. + void endEdit() { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + canRefresh: false, + ); + } + } + + /// Sets all the cell values to the check box filter. + void setDataGridSource(GridColumn column) { + final List items = _getPreviousFilteredRows(column.columnName); + final List distinctCollection = _getCellValues( + column, + items, + ); + + checkboxFilterHelper._previousDataGridSource = []; + + if (distinctCollection.isNotEmpty) { + checkboxFilterHelper.filterCheckboxItems = distinctCollection; + } + + if (filterFrom == FilteredFrom.checkboxFilter) { + _setPreviousDataGridSource(); + } + + checkboxFilterHelper.items = distinctCollection.toList(); + advancedFilterHelper.items = distinctCollection.toList(); + + if (advancedFilterHelper.items.isNotEmpty) { + bool isNullOrEmpty(String value) => value == '(Blanks)' || value == ''; + // Remove null and empty values from the items collection since it's not + // applicable for the AdvancedFilter. + advancedFilterHelper.items.removeWhere( + (FilterElement element) => isNullOrEmpty(element.value.toString()), + ); + } + + checkboxFilterHelper.ensureSelectAllCheckboxState(); + } + + List _getFilterRows( + List rows, + Map> conditions, + ) { + return rows + .where((DataGridRow row) => _filterRow(row, conditions)) + .toList(); + } + + void _setPreviousDataGridSource() { + final bool useSelected = + !(_checkedItemsCount > _unCheckedItemsCount && + _unCheckedItemsCount > 0); + final List items = + checkboxFilterHelper.filterCheckboxItems + .where((FilterElement i) => useSelected) + .toList(); + checkboxFilterHelper._previousDataGridSource.addAll(items); + } + + /// Handles the filter form's sort buttons callback. + void onSortButtonClick(GridColumn column, DataGridSortDirection direction) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + endEdit(); + if (dataGridConfiguration.source.sortedColumns.isNotEmpty) { + dataGridConfiguration.source.sortedColumns.clear(); + } + + dataGridConfiguration.source.sortedColumns.add( + SortColumnDetails(name: column.columnName, sortDirection: direction), + ); + dataGridConfiguration.source.sort(); + } + + /// Handles the filter form's clear filter button callback. + void onClearFilterButtonClick(GridColumn column) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + endEdit(); + setFilterFrom(column, FilteredFrom.none); + removeFilterConditions(dataGridConfiguration.source, column.columnName); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + dataGridConfiguration.group!.clearDisplayElements(dataGridConfiguration); + } + updateDataSource(dataGridConfiguration.source); + selection_manager.refreshSelectedRows(dataGridConfiguration); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'Filtering', + ); + _invokeFilterChangedCallback(column, []); + } + + bool _invokeFilterChangingCallback( + GridColumn column, + List filterConditions, + ) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataGridConfiguration.onFilterChanging != null) { + final DataGridFilterChangeDetails details = DataGridFilterChangeDetails( + column: column, + filterConditions: List.unmodifiable(filterConditions), + ); + return dataGridConfiguration.onFilterChanging!(details); + } + return true; + } + + void _invokeFilterChangedCallback( + GridColumn column, + List filterConditions, + ) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataGridConfiguration.onFilterChanged != null) { + final DataGridFilterChangeDetails details = DataGridFilterChangeDetails( + column: column, + filterConditions: List.unmodifiable(filterConditions), + ); + dataGridConfiguration.onFilterChanged!(details); + } + } + + Object? _getCellValue(DataGridRow row, String columnName) { + final GridColumn? column = _dataGridStateDetails().columns.firstWhereOrNull( + (GridColumn column) => column.columnName == columnName, + ); + if (column != null) { + final DataGridCell? cellValue = row.getCells().firstWhereOrNull( + (DataGridCell element) => element.columnName == column.columnName, + ); + if (cellValue != null && cellValue.value != null) { + return cellValue.value; + } + } + return null; + } + + bool _filterRow( + DataGridRow row, + Map> filterConditions, + ) { + bool? isEqual; + // Holds the previous column's comparer value of the current row to help to + // perform multi-column filtering. + bool previousComparer = true; + for (final String columnName in filterConditions.keys) { + for (final FilterCondition condition in filterConditions[columnName]!) { + final Object? cellValue = _getCellValue(row, columnName); + + // Resets the previous column's comparer value if a column is not + // applicable to the multi-column filtering. + if (condition == filterConditions[columnName]!.first && + condition.filterOperator == FilterOperator.or) { + previousComparer = true; + } + + /// Holds the current filter type result. + bool comparerValue = false; + switch (condition.type) { + case FilterType.equals: + comparerValue = grid_helper.compareEquals(condition, cellValue); + break; + case FilterType.notEqual: + comparerValue = !grid_helper.compareEquals(condition, cellValue); + break; + case FilterType.contains: + comparerValue = grid_helper.compareContains(condition, cellValue); + break; + case FilterType.doesNotContain: + comparerValue = !grid_helper.compareContains(condition, cellValue); + break; + case FilterType.beginsWith: + comparerValue = grid_helper.compareBeginsWith(condition, cellValue); + break; + case FilterType.doesNotBeginWith: + comparerValue = + !grid_helper.compareBeginsWith(condition, cellValue); + break; + case FilterType.endsWith: + comparerValue = grid_helper.compareEndsWith(condition, cellValue); + break; + case FilterType.doesNotEndsWith: + comparerValue = !grid_helper.compareEndsWith(condition, cellValue); + break; + case FilterType.greaterThan: + comparerValue = grid_helper.compareGreaterThan( + condition, + cellValue, + ); + break; + case FilterType.greaterThanOrEqual: + comparerValue = grid_helper.compareGreaterThan( + condition, + cellValue, + true, + ); + break; + case FilterType.lessThan: + comparerValue = grid_helper.compareLessThan(condition, cellValue); + break; + case FilterType.lessThanOrEqual: + comparerValue = grid_helper.compareLessThan( + condition, + cellValue, + true, + ); + break; + } + + isEqual = + previousComparer && + grid_helper.compare( + isEqual, + comparerValue, + condition.filterOperator, + ); + } + previousComparer = isEqual != null && isEqual; + } + return isEqual != null && isEqual; + } + + void _createAdvancedFilterConditions(GridColumn column) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + final SfLocalizations localizations = dataGridConfiguration.localizations; + final FilterOperator filterOperator = + advancedFilterHelper.isOrPredicate + ? FilterOperator.or + : FilterOperator.and; + + final List filterConditions = + dataGridConfiguration.source.filterConditions[column.columnName] ?? + []; + + final Object? filterValue1 = advancedFilterHelper.filterValue1; + final Object? filterValue2 = advancedFilterHelper.filterValue2; + final String? filterType1 = advancedFilterHelper.filterType1; + final String? filterType2 = advancedFilterHelper.filterType2; + final FilterType type1 = grid_helper.getFilterType( + dataGridConfiguration, + filterType1 ?? '', + ); + final FilterType type2 = grid_helper.getFilterType( + dataGridConfiguration, + filterType2 ?? '', + ); + + if (filterConditions.isNotEmpty) { + filterConditions.clear(); + } + + bool canCreateFilterCondition( + Object? filterValue, + String? filterType, + bool isFirstCondition, + ) { + void setFilterValue(String? value) { + if (isFirstCondition) { + advancedFilterHelper.filterValue1 = value; + } else { + advancedFilterHelper.filterValue2 = value; + } + } + + if (filterValue != null && filterType != null) { + if (filterValue == '') { + setFilterValue(null); + } + return true; + } else if (filterValue == null && filterType != null) { + if (filterType == localizations.nullDataGridFilteringLabel || + filterType == localizations.notNullDataGridFilteringLabel) { + setFilterValue(null); + return true; + } else if (filterType == localizations.emptyDataGridFilteringLabel || + filterType == localizations.notEmptyDataGridFilteringLabel) { + setFilterValue(''); + return true; + } + } + return false; + } + + // Sets the first filter condition's filter operator as 'AND' to perform + // multi-column filtering. + FilterOperator getFilterOperator() => + filterConditions.isEmpty ? FilterOperator.and : filterOperator; + + switch (advancedFilterHelper.advancedFilterType) { + case AdvancedFilterType.text: + { + // Condition 1 + if (canCreateFilterCondition(filterValue1, filterType1, true)) { + final FilterCondition condition = FilterCondition( + type: type1, + filterOperator: getFilterOperator(), + value: advancedFilterHelper.filterValue1, + filterBehavior: FilterBehavior.stringDataType, + isCaseSensitive: advancedFilterHelper.isCaseSensitive1, + ); + filterConditions.add(condition); + } + + // Condition 2 + if (canCreateFilterCondition(filterValue2, filterType2, false)) { + final FilterCondition condition = FilterCondition( + type: type2, + filterOperator: getFilterOperator(), + value: advancedFilterHelper.filterValue2, + filterBehavior: FilterBehavior.stringDataType, + isCaseSensitive: advancedFilterHelper.isCaseSensitive2, + ); + filterConditions.add(condition); + } + } + break; + case AdvancedFilterType.numeric: + { + // Condition 1 + if (canCreateFilterCondition(filterValue1, filterType1, true)) { + final FilterCondition condition = FilterCondition( + type: type1, + filterOperator: getFilterOperator(), + value: advancedFilterHelper.filterValue1, + ); + filterConditions.add(condition); + } + + // Condition 2 + if (canCreateFilterCondition(filterValue2, filterType2, false)) { + final FilterCondition condition = FilterCondition( + type: type2, + filterOperator: getFilterOperator(), + value: advancedFilterHelper.filterValue2, + ); + filterConditions.add(condition); + } + } + break; + case AdvancedFilterType.date: + { + // Condition 1 + if (canCreateFilterCondition(filterValue1, filterType1, true)) { + final FilterCondition condition = FilterCondition( + type: type1, + filterOperator: getFilterOperator(), + value: advancedFilterHelper.filterValue1, + ); + filterConditions.add(condition); + } + + // Condition 2 + if (canCreateFilterCondition(filterValue2, filterType2, false)) { + final FilterCondition condition = FilterCondition( + type: type2, + filterOperator: getFilterOperator(), + value: advancedFilterHelper.filterValue2, + ); + filterConditions.add(condition); + } + } + break; + } + + if (filterConditions.isNotEmpty) { + setFilterFrom(column, FilteredFrom.advancedFilter); + addFilterConditions( + dataGridConfiguration.source, + column.columnName, + filterConditions, + ); + _applyViewFilter(column); + } + } +} + +/// A class [DataGridCheckboxFilterHelper] that holds the helper properties +/// for the checkbox filter. +class DataGridCheckboxFilterHelper { + /// Holds all the cell values of corresponding filter column as a + /// `FilterElement` collection. + List items = []; + + /// Holds the searched check box items. + List _searchedItems = []; + + /// Holds the check box filter items. + List filterCheckboxItems = []; + + /// Maintain the previous item source for using it when searched text field + /// is empty. + List _previousDataGridSource = []; + + /// A `TextEditingController` of the search box. + final TextEditingController textController = TextEditingController(); + + /// A `FocusNode` of the search box. + final FocusNode searchboxFocusNode = FocusNode(); + + /// Checks whether the selectAll checkbox is checked or not. + late bool? isSelectAllChecked; + + /// Checks whether the selectAll checkbox is in tri-state or not. + late bool isSelectAllInTriState; + + /// Ensures the `selectAll` checkbox state. + void ensureSelectAllCheckboxState() { + final List unCheckedItems = + items.where((FilterElement item) => !item.isSelected).toList(); + + if (unCheckedItems.isEmpty || unCheckedItems.length == items.length) { + isSelectAllInTriState = false; + isSelectAllChecked = unCheckedItems.isEmpty; + } else { + isSelectAllInTriState = true; + isSelectAllChecked = null; + } + } + + /// Handles the search box's text changed callback. + void onSearchTextFieldTextChanged(String searchText) { + if (filterCheckboxItems.isEmpty) { + return; + } + + if (searchText.isEmpty) { + _searchedItems = []; + if (_previousDataGridSource.isNotEmpty) { + final int checkedCount = + _previousDataGridSource + .where((FilterElement element) => element.isSelected) + .length; + final bool isSelected = checkedCount > 0; + for (final FilterElement item in filterCheckboxItems) { + final FilterElement? filterElement = _previousDataGridSource + .firstWhereOrNull((FilterElement i) => item.value == i.value); + item.isSelected = + filterElement != null ? filterElement.isSelected : !isSelected; + } + } + items = filterCheckboxItems; + ensureSelectAllCheckboxState(); + return; + } + + _searchedItems = + filterCheckboxItems + .where( + (FilterElement element) => element.value + .toString() + .toLowerCase() + .contains(searchText.toLowerCase()), + ) + .toList(); + + for (final FilterElement element in _searchedItems) { + element.isSelected = true; + } + + items = _searchedItems; + ensureSelectAllCheckboxState(); + } +} + +/// A class [DataGridAdvancedFilterHelper] that holds the helper properties +/// for the advance filter. +class DataGridAdvancedFilterHelper { + /// Creates `DataGridAdvanceFilterHelper` for `SfDataGrid`. + DataGridAdvancedFilterHelper(this._dataGridStateDetails); + + final DataGridStateDetails _dataGridStateDetails; + + /// Holds the filter type dropdown items. + List filterTypeItems = []; + + /// Holds all the cell values of a corresponding filter column as a + /// `FilterElement` collection. + List items = []; + + /// Defines the advance filter type. + AdvancedFilterType advancedFilterType = AdvancedFilterType.text; + + /// Defines the filter types. + String? filterType1, filterType2; + + /// Defines the filter values + Object? filterValue1, filterValue2; + + /// Defines the first drop down button's case sensitive option. + bool isCaseSensitive1 = false; + + /// Defines the second drop down button's case sensitive option. + bool isCaseSensitive2 = false; + + /// Checkes whether the `OR` radio button is enabled or not. + bool isOrPredicate = true; + + /// Holds the list of filter types that used to disable filter value's drop + /// down button. If a filterType contains any of these item, need to disable + /// the filter value dropdown button. + List disableFilterTypes = []; + + /// Holds the list of filter types that used to display the text field instead + /// of dropdown button in the Advanced filter menu. + List textFieldFilterTypes = []; + + /// A [TextEditingController] for the first text field in the Advanced filter. + TextEditingController firstValueTextController = TextEditingController(); + + /// A [TextEditingController] for the second text field in the Advanced filter. + TextEditingController secondValueTextController = TextEditingController(); + + /// Initializes the localized resource values to the localization required + /// internal properties. + void initProperties() { + final SfLocalizations localizations = _dataGridStateDetails().localizations; + filterType1 = localizations.equalsDataGridFilteringLabel; + filterType2 = localizations.equalsDataGridFilteringLabel; + + disableFilterTypes = [ + localizations.nullDataGridFilteringLabel, + localizations.notNullDataGridFilteringLabel, + localizations.emptyDataGridFilteringLabel, + localizations.notEmptyDataGridFilteringLabel, + ]; + + textFieldFilterTypes = [ + localizations.beginsWithDataGridFilteringLabel, + localizations.endsWithDataGridFilteringLabel, + localizations.doesNotBeginWithDataGridFilteringLabel, + localizations.doesNotEndWithDataGridFilteringLabel, + localizations.containsDataGridFilteringLabel, + localizations.doesNotContainDataGridFilteringLabel, + localizations.beforeDataGridFilteringLabel, + localizations.beforeOrEqualDataGridFilteringLabel, + localizations.afterDataGridFilteringLabel, + localizations.afterOrEqualDataGridFilteringLabel, + localizations.lessThanDataGridFilteringLabel, + localizations.lessThanOrEqualDataGridFilteringLabel, + localizations.greaterThanDataGridFilteringLabel, + localizations.greaterThanOrEqualDataGridFilteringLabel, + ]; + } + + /// Generates the filter type dropdown items. + void generateFilterTypeItems(GridColumn column) { + if (filterTypeItems.isNotEmpty) { + filterTypeItems.clear(); + } + + final List items = []; + final SfLocalizations localizations = _dataGridStateDetails().localizations; + switch (advancedFilterType) { + case AdvancedFilterType.text: + items.add(localizations.equalsDataGridFilteringLabel); + items.add(localizations.doesNotEqualDataGridFilteringLabel); + items.add(localizations.beginsWithDataGridFilteringLabel); + items.add(localizations.doesNotBeginWithDataGridFilteringLabel); + items.add(localizations.endsWithDataGridFilteringLabel); + items.add(localizations.doesNotEndWithDataGridFilteringLabel); + items.add(localizations.containsDataGridFilteringLabel); + items.add(localizations.doesNotContainDataGridFilteringLabel); + items.add(localizations.emptyDataGridFilteringLabel); + items.add(localizations.notEmptyDataGridFilteringLabel); + items.add(localizations.nullDataGridFilteringLabel); + items.add(localizations.notNullDataGridFilteringLabel); + break; + case AdvancedFilterType.numeric: + items.add(localizations.equalsDataGridFilteringLabel); + items.add(localizations.doesNotEqualDataGridFilteringLabel); + items.add(localizations.lessThanDataGridFilteringLabel); + items.add(localizations.lessThanOrEqualDataGridFilteringLabel); + items.add(localizations.greaterThanDataGridFilteringLabel); + items.add(localizations.greaterThanOrEqualDataGridFilteringLabel); + items.add(localizations.nullDataGridFilteringLabel); + items.add(localizations.notNullDataGridFilteringLabel); + break; + case AdvancedFilterType.date: + items.add(localizations.equalsDataGridFilteringLabel); + items.add(localizations.doesNotEqualDataGridFilteringLabel); + items.add(localizations.beforeDataGridFilteringLabel); + items.add(localizations.beforeOrEqualDataGridFilteringLabel); + items.add(localizations.afterDataGridFilteringLabel); + items.add(localizations.afterOrEqualDataGridFilteringLabel); + items.add(localizations.nullDataGridFilteringLabel); + items.add(localizations.notNullDataGridFilteringLabel); + break; + } + + filterTypeItems = items; + } + + /// Sets the advanced filter type based on the column type. + void setAdvancedFilterType( + DataGridConfiguration dataGridConfiguration, + GridColumn column, + ) { + Object? value; + for (final DataGridRow row in dataGridConfiguration.source.rows) { + final DataGridCell? cellValue = row.getCells().firstWhereOrNull( + (DataGridCell element) => element.columnName == column.columnName, + ); + if (cellValue != null && cellValue.value != null) { + value = cellValue.value; + break; + } + } + + if (value != null && value is num) { + advancedFilterType = AdvancedFilterType.numeric; + } else if (value != null && value is DateTime) { + advancedFilterType = AdvancedFilterType.date; + } else { + advancedFilterType = AdvancedFilterType.text; + } + } + + /// Sets the advanced filter values. + void setAdvancedFilterValues( + DataGridConfiguration dataGridConfiguration, + List filterConditions, + DataGridFilterHelper helper, + ) { + Object? getValue(Object? value, String? filterType) { + if (items.any((FilterElement element) => element.value == value) || + (filterType != null && textFieldFilterTypes.contains(filterType))) { + return value; + } + return null; + } + + if (filterConditions.isNotEmpty) { + final FilterCondition condition = filterConditions.first; + filterType1 = grid_helper.getFilterName( + dataGridConfiguration, + condition.type, + condition.value, + ); + filterValue1 = getValue(condition.value, filterType1); + isCaseSensitive1 = condition.isCaseSensitive; + isOrPredicate = condition.filterOperator == FilterOperator.or; + if (filterConditions.length == 1) { + filterType2 = + dataGridConfiguration.localizations.equalsDataGridFilteringLabel; + filterValue2 = null; + isCaseSensitive2 = false; + } + } + if (filterConditions.length == 2) { + final FilterCondition condition = filterConditions.last; + filterType2 = grid_helper.getFilterName( + dataGridConfiguration, + condition.type, + condition.value, + ); + filterValue2 = getValue(condition.value, filterType2); + isCaseSensitive2 = condition.isCaseSensitive; + isOrPredicate = condition.filterOperator == FilterOperator.or; + } + + firstValueTextController.text = dataGridConfiguration.dataGridFilterHelper! + .getDisplayValue(filterValue1); + secondValueTextController.text = dataGridConfiguration.dataGridFilterHelper! + .getDisplayValue(filterValue2); + } + + /// Resets the advanced filter values. + void resetAdvancedFilterValues(DataGridConfiguration dataGridConfiguration) { + filterType1 = + filterType2 = + dataGridConfiguration.localizations.equalsDataGridFilteringLabel; + filterValue1 = filterValue2 = null; + isCaseSensitive1 = isCaseSensitive2 = false; + isOrPredicate = true; + firstValueTextController.clear(); + secondValueTextController.clear(); + } +} + +/// A class [FilterElement] that helps to maintain the cell values with its +/// checkbox state for the filtering support. +class FilterElement { + /// Creates [FilterElement] for the `SfDataGrid`. + FilterElement({required this.value, required this.isSelected}); + + /// Defines the value of the cell. + Object value; + + /// Defines the check box state of the cell. + bool isSelected; +} + +/// Controls how the filtering menu options can be customized. +@immutable +class FilterPopupMenuOptions { + /// + const FilterPopupMenuOptions({ + this.filterMode = FilterMode.both, + this.canShowClearFilterOption = true, + this.canShowSortingOptions = true, + this.showColumnName = true, + }); + + /// Decides how the checked listbox and advanced filter options should be shown in filter popup. + final FilterMode filterMode; + + /// Decides whether the `Clear Filter From {Column Name}` option should be displayed in filtering popup. + final bool canShowClearFilterOption; + + /// Decides whether the ascending and descending sorting options should be displayed in filtering popup. + final bool canShowSortingOptions; + + /// Decides whether the column name should be displayed along with the content of `Clear Filter` option . + final bool showColumnName; +} + +/// Process column resizing operation in [SfDataGrid]. +class ColumnDragAndDropController { + /// Creates the [ColumnDragAndDropController] for the [SfDataGrid]. + ColumnDragAndDropController({required this.dataGridStateDetails}); + + /// Holds the [DataGridStateDetails]. + DataGridStateDetails dataGridStateDetails; + + /// The index of the column being dragged. + /// + /// This integer value represents the index of the column that is being dragged by the user + /// during a drag-and-drop operation. It is set to null by default, and is updated during drag events + /// to indicate the index of the column that is being dragged. + int? dragColumnStartIndex; + + /// The index of the column being dropped. + /// + /// This integer value represents the index of the column that is being dropped by the user + /// during a drag-and-drop operation. It is set to null by default, and is updated during drag events + /// to indicate the index of the column that is being dropped. + int? dragColumnEndIndex; + + /// The offset of the dragged column from its original position. + /// + /// This [Offset] value represents the distance between the original position of the dragged column + /// and its current position during a drag-and-drop operation. + /// It is set to null by default, and is updated during drag events to indicate the current offset. + Offset? offset; + + /// A flag indicating which indicator should be drawn. + bool? canDrawRightIndicator = false; + + /// The index of the column that is currently being dragged. + /// + /// This integer value represents the index of the column that is currently being dragged by the user. + /// It is set to null by default, and is updated during drag events to indicate the index + /// of the column that is currently being dragged. + int? columnIndex; + + /// A boolean flag to indicate whether auto-scrolling is enabled or not. + /// + /// This boolean flag is used to determine whether the auto-scrolling functionality is enabled or disabled. + /// If the flag is set to true, then auto-scrolling is enabled, otherwise it is disabled. + bool autoScrolling = false; + + /// The delta value for drag events. + /// + /// This double value represents the delta value for drag events in the widget. + /// It is set to null by default, and is updated during drag events to indicate the distance + /// the user has dragged since the last event. + double dragDelta = 0; + + /// A boolean flag to indicate whether scrolling is disabled or not. + /// + /// This boolean flag is used to determine whether the scrolling functionality is enabled or disabled. + /// If the flag is set to true, then scrolling is disabled, otherwise scrolling is enabled. + bool disableScrolling = true; + + /// The current scroll position of the widget. + /// + /// This property holds the current scroll position of the widget. It is of type ScrollPosition, + /// which allows querying and manipulating the position of the scrollable widget. + ScrollPosition? position; + + /// A boolean flag to indicate whether column dragging is allowed or not. + /// + /// This boolean flag is used to determine whether the column dragging functionality is enabled or disabled. + /// If the flag is set to true, then column dragging is allowed, otherwise column dragging is not allowed. + bool allowColumnDrag = false; + + /// A boolean flag to indicate whether hover is disabled or not. + /// + /// This boolean flag is used to determine whether the hover functionality is enabled or disabled. + /// If the flag is set to true, then hover is disabled, otherwise hover is enabled. + bool isHoverDisabled = false; + + /// A boolean flag to indicate whether can reset column sizing or not. + /// + bool canResetColumnWidthCalculation = false; + + /// DataGrid origin position + Offset? scrollOrigin; + + /// A boolean flag to indicate whether can wrap draggable view or not. + /// If the flag is set to true, then draggable view is wrapped, otherwise draggable view is not wrapped. + bool canWrapDraggableView = true; + + /// A boolean flag to indicate whether it is a windows platform or not. + bool? isWindowsPlatform; + + /// The drag direction changed position. + double? _dragDirectionChangedPosition; + + /// The boolean flag to indicate whether drag direction is changed or not. + bool? _isLeftToRightDrag; + + /// The double value indicates the drag indicator position threshold when the direction is changed. + double indicatorPositionThreshold = 10; + + void _rebuild(DataGridConfiguration dataGridConfiguration) { + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'columnDragAndDrop', + ); + } + + Future _autoScrollIfNecessary( + DataGridConfiguration dataGridConfiguration, + PointerMoveEvent details, + ) async { + if (!autoScrolling && !disableScrolling) { + final ScrollPosition position = + dataGridConfiguration.horizontalScrollController!.position; + + double? newOffset; + const Duration duration = Duration(milliseconds: 14); + const double step = 3.0; + const double overDragMax = 20.0; + const double overDragCoef = 10; + final double dragThreshold = dataGridConfiguration.isDesktop ? 20 : 30; + + final double scrollStart = scrollOrigin!.dx + dragThreshold; + final double scrollEnd = + scrollOrigin!.dx + dataGridConfiguration.viewWidth - dragThreshold; + + if (position.axisDirection == AxisDirection.left) { + if (dragDelta > scrollEnd && + position.pixels > position.minScrollExtent) { + final double overDrag = max(dragDelta - scrollEnd, overDragMax); + newOffset = max( + position.minScrollExtent, + position.pixels - step * overDrag / overDragCoef, + ); + } else if (dragDelta < scrollStart && + position.pixels < position.maxScrollExtent) { + final double overDrag = max(scrollStart - dragDelta, overDragMax); + newOffset = min( + position.maxScrollExtent, + position.pixels + step * overDrag / overDragCoef, + ); + } + } else { + if (dragDelta < scrollStart && + position.pixels > position.minScrollExtent) { + final double overDrag = max(scrollStart - dragDelta, overDragMax); + newOffset = max( + position.minScrollExtent, + position.pixels - step * overDrag / overDragCoef, + ); + } else if (dragDelta > scrollEnd && + position.pixels < position.maxScrollExtent) { + final double overDrag = max(dragDelta - scrollEnd, overDragMax); + newOffset = min( + position.maxScrollExtent, + position.pixels + step * overDrag / overDragCoef, + ); + } + } + + if (newOffset != null && (newOffset - position.pixels).abs() >= 1.0) { + autoScrolling = true; + await position.animateTo( + newOffset, + duration: duration, + curve: Curves.linear, + ); + dataGridConfiguration.container.scrollColumns + ..markDirty() + ..updateScrollbar(); + autoScrolling = false; + columnIndex = + getColumnLineInfo( + dataGridConfiguration, + details.position.dx, + )?.lineIndex; + + _autoScrollIfNecessary(dataGridConfiguration, details); + + // Need to notify the listeners when auto scrolling is performed. + // This is required to update the column drag indicator. + _rebuild(dataGridConfiguration); + } + } + } + + /// Returns the visible line information for a given horizontal position in the data grid. + /// + /// This function takes a [DataGridConfiguration] object and a double [position] as input, and returns a [VisibleLineInfo] object. + /// The [VisibleLineInfo] object represents the line information of a visible column in the data grid for the given [position]. + /// The [getVisibleLineAtPoint] method of the [scrollColumns] object of the [configuration] is used to obtain the [VisibleLineInfo]. + /// The [resolveTextDirection] method of the [configuration] is used to obtain the [TextDirection] of the data grid. + /// The [getVisibleLineAtPoint] method of the [scrollColumns] object is called with the resolved [TextDirection] and the [position] to get the [VisibleLineInfo]. + /// + VisibleLineInfo? getColumnLineInfo( + DataGridConfiguration dataGridConfiguration, + double position, + ) { + final bool isRTL = dataGridConfiguration.textDirection == TextDirection.rtl; + if (isRTL) { + dataGridConfiguration.container.scrollColumns.resetVisibleLines(); + } + + // This code retrieves the visible line at the given position within the SfDataGrid container. + // The position is adjusted for the data grid's origin position, and the function checks for RTL layout. + return dataGridConfiguration.container.scrollColumns.getVisibleLineAtPoint( + position - getDataGridOriginPosition(dataGridConfiguration).dx, + false, + isRTL, + ); + } + + /// Returns the visible line information for a given vertical position in the data grid. + /// + /// This function takes a [DataGridConfiguration] object and a double [position] as input, and returns a [VisibleLineInfo] object. + /// The [VisibleLineInfo] object represents the line information of a visible row in the data grid for the given [position]. + /// The [getVisibleLineAtPoint] method of the [scrollRows] object of the [configuration] is used to obtain the [VisibleLineInfo]. + /// + VisibleLineInfo? getRowLineInfo( + DataGridConfiguration dataGridConfiguration, + double position, + ) { + return dataGridConfiguration.container.scrollRows.getVisibleLineAtPoint( + position, + ); + } + + /// Returns the start index adjusted for a checkbox column. + /// + /// If [showCheckboxColumn] is true, subtracts 1 from [startIndex] to account for the checkbox column. + /// Returns the adjusted start index as an int value. + int? getStartIndex( + int startIndex, + bool showCheckboxColumn, + DataGridConfiguration dataGridConfiguration, + ) { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + startIndex -= dataGridConfiguration.source.groupedColumns.length; + } + return showCheckboxColumn ? startIndex - 1 : startIndex; + } + + /// Returns the end index if it's different from the start index, or null if they are the same. + /// + /// If the [showCheckboxColumn] is true, then decrement the [endIndex] by 1. + /// If [startIndex] is equal to [endIndex], then return null. + int? getEndIndex( + int startIndex, + int endIndex, + bool showCheckboxColumn, + DataGridConfiguration dataGridConfiguration, + ) { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + endIndex -= dataGridConfiguration.source.groupedColumns.length; + } + if (showCheckboxColumn) { + endIndex--; + } + return startIndex == endIndex || endIndex < 0 + ? null + : showCheckboxColumn && endIndex == -1 + ? null + : endIndex; + } + + /// Returns the origin position of the data grid within the screen coordinates. + /// + /// This function takes a [DataGridConfiguration] object as an argument and returns an [Offset] object. + /// The offset represents the screen coordinates of the top-left corner of the data grid. + /// The [RenderBox] of the datagrid is obtained from the [dataGridKey] of the [configuration]. + /// The [RenderBox.localToGlobal] method is then called on the [scrollRenderBox] to get its global position. + /// + Offset getDataGridOriginPosition( + DataGridConfiguration dataGridConfiguration, + ) { + final RenderBox scrollRenderBox = + dataGridConfiguration.dataGridKey.currentContext!.findRenderObject()! + as RenderBox; + + return scrollRenderBox.localToGlobal(Offset.zero); + } + + /// Returns the whether the column drag and drop is allowed or not. + /// The column drag and drop is allowed only when the [allowColumnsDragging] is true and [onColumnDragging] is not null. + bool canAllowColumnDragAndDrop() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + return dataGridConfiguration.allowColumnsDragging && + dataGridConfiguration.onColumnDragging != null; + } + + /// Handles the pointer Down event for the column dragging. + void onPointerDown(DataCellBase? dataCell) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + canResetColumnWidthCalculation = false; + + if (dataCell != null && dataCell.cellType == CellType.headerCell) { + dragColumnStartIndex = getStartIndex( + dataCell.columnIndex, + dataGridConfiguration.showCheckboxColumn, + dataGridConfiguration, + ); + dragColumnEndIndex = + dataCell.columnIndex - + dataGridConfiguration.source.groupedColumns.length; + canWrapDraggableView = dataGridConfiguration.onColumnDragging!( + _invokeOnColumnDragging(action: DataGridColumnDragAction.starting), + ); + + if (!canWrapDraggableView) { + // need to remove draggableView when the onColumnDragging returns false. + _rebuild(dataGridConfiguration); + } + + if (canWrapDraggableView && dragColumnStartIndex != null) { + canWrapDraggableView = dataGridConfiguration.onColumnDragging!( + _invokeOnColumnDragging(action: DataGridColumnDragAction.started), + ); + if (!canWrapDraggableView) { + _rebuild(dataGridConfiguration); + } + } + } + } + + /// Handles the pointer move event for the column dragging. + void onPointerMove(PointerMoveEvent event) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (canWrapDraggableView && dragColumnStartIndex != null) { + offset = event.localPosition; + dragDelta = dragDelta + event.delta.dx; + columnIndex = + getColumnLineInfo( + dataGridConfiguration, + event.position.dx, + )?.lineIndex; + final int? rowIndex = + getRowLineInfo( + dataGridConfiguration, + event.position.dy - + getDataGridOriginPosition(dataGridConfiguration).dy, + )?.lineIndex; + + if (columnIndex != null && rowIndex != null) { + final int headerIndex = grid_helper.getHeaderIndex( + dataGridConfiguration, + ); + + isHoverDisabled = true; + scrollOrigin = getDataGridOriginPosition(dataGridConfiguration); + if (rowIndex == headerIndex && + event.position.dy >= scrollOrigin!.dy && + columnIndex != null) { + disableScrolling = false; + position = dataGridConfiguration.horizontalScrollController!.position; + offset = event.localPosition; + columnIndex = columnIndex; + + final bool isLeftToRightDrag = + _isLeftToRightDrag != null && + (dataGridConfiguration.textDirection == TextDirection.ltr + ? _isLeftToRightDrag! + : !_isLeftToRightDrag!); + + if ((isLeftToRightDrag && event.delta.dx.sign < 0) || + (!isLeftToRightDrag && event.delta.dx.sign > 0)) { + _dragDirectionChangedPosition = event.position.dx; + } + + if (_dragDirectionChangedPosition != null) { + if (event.delta.dx.sign < 0 && + event.position.dx < + _dragDirectionChangedPosition! - + indicatorPositionThreshold) { + canDrawRightIndicator = _isLeftToRightDrag; + } else if (event.delta.dx.sign > 0 && + event.position.dx > + _dragDirectionChangedPosition! + + indicatorPositionThreshold) { + canDrawRightIndicator = _isLeftToRightDrag; + } else if (event.delta.dx.sign == 0) { + canDrawRightIndicator = _isLeftToRightDrag; + } + } + + _isLeftToRightDrag = + dataGridConfiguration.textDirection == TextDirection.ltr + ? event.delta.dx.sign >= 0 + : event.delta.dx.sign <= 0; + + if (_dragDirectionChangedPosition == null) { + canDrawRightIndicator = _isLeftToRightDrag; + } + + _autoScrollIfNecessary(dataGridConfiguration, event); + allowColumnDrag = dataGridConfiguration.onColumnDragging!( + _invokeOnColumnDragging( + action: DataGridColumnDragAction.update, + showCheckboxColumn: dataGridConfiguration.showCheckboxColumn, + ), + ); + } else { + disableScrolling = true; + canDrawRightIndicator = null; + } + } + + if (!allowColumnDrag) { + disableScrolling = false; + isHoverDisabled = true; + offset = null; + canDrawRightIndicator = null; + } + _rebuild(dataGridConfiguration); + } + } + + /// Handles the pointer up event for the column dragging. + void onPointerUp(PointerUpEvent event) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + disableScrolling = true; + if (allowColumnDrag && + scrollOrigin != null && + event.position.dy >= scrollOrigin!.dy) { + columnIndex = + getColumnLineInfo( + dataGridConfiguration, + event.position.dx, + )?.lineIndex; + final int? rowIndex = + getRowLineInfo( + dataGridConfiguration, + event.position.dy - + getDataGridOriginPosition(dataGridConfiguration).dy, + )?.lineIndex; + + final int headerIndex = grid_helper.getHeaderIndex(dataGridConfiguration); + + if (columnIndex != null && + rowIndex == headerIndex && + dataGridConfiguration.onColumnDragging != null) { + dragColumnEndIndex = getEndIndex( + dragColumnStartIndex!, + columnIndex!, + dataGridConfiguration.showCheckboxColumn, + dataGridConfiguration, + ); + columnIndex = + columnIndex! - dataGridConfiguration.source.groupedColumns.length; + offset = event.localPosition; + + allowColumnDrag = dataGridConfiguration.onColumnDragging!( + _invokeOnColumnDragging(action: DataGridColumnDragAction.dropping), + ); + if (allowColumnDrag) { + allowColumnDrag = dataGridConfiguration.onColumnDragging!( + _invokeOnColumnDragging(action: DataGridColumnDragAction.dropped), + ); + } + } + } + + allowColumnDrag = false; + isHoverDisabled = false; + canDrawRightIndicator = null; + disableScrolling = true; + dragDelta = 0; + isHoverDisabled = false; + offset = null; + dragColumnStartIndex = null; + canResetColumnWidthCalculation = true; + canWrapDraggableView = true; + _dragDirectionChangedPosition = null; + _isLeftToRightDrag = null; + _rebuild(dataGridConfiguration); + } + + DataGridColumnDragDetails _invokeOnColumnDragging({ + required DataGridColumnDragAction action, + bool showCheckboxColumn = false, + }) { + int? to; + if (action == DataGridColumnDragAction.update && columnIndex != null) { + to = showCheckboxColumn ? (columnIndex! - 1) : columnIndex; + } else { + if (canDrawRightIndicator != null && !canDrawRightIndicator!) { + if (columnIndex != null && + dragColumnStartIndex! < columnIndex! && + dragColumnEndIndex != null) { + to = dragColumnEndIndex! - 1; + } else { + to = dragColumnEndIndex; + } + } else { + if (columnIndex != null && + dragColumnStartIndex! > columnIndex! && + dragColumnEndIndex != null) { + to = dragColumnEndIndex! + 1; + } else { + to = dragColumnEndIndex; + } + } + } + return DataGridColumnDragDetails( + from: dragColumnStartIndex!, + to: to, + offset: offset!, + action: action, + ); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart index 9f55fdf1a..344eb56b2 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart @@ -7,9 +7,11 @@ import '../../grid_common/enums.dart'; import '../../grid_common/row_column_index.dart'; import '../../grid_common/utility_helper.dart'; import '../../grid_common/visible_line_info.dart'; +import '../grouping/grouping.dart'; import '../helper/callbackargs.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/datagrid_helper.dart'; import '../helper/enums.dart'; import '../selection/selection_manager.dart' as selection_manager; import '../selection/selection_manager.dart'; @@ -32,7 +34,7 @@ abstract class DataCellBase { /// Decides whether [DataCell] is dirty, to refresh it. bool isDirty = false; - /// Decides whether the [DataCell] has the currentcell. + /// Decides whether the [DataCell] has the current-cell. bool isCurrentCell = false; /// Decide whether the [DataCell] is in edit mode. @@ -98,10 +100,13 @@ abstract class DataCellBase { return; } - final RowColumnIndex currentRowColumnIndex = - RowColumnIndex(rowIndex, columnIndex); - dataGridConfiguration.rowSelectionManager - .handleTap(currentRowColumnIndex); + final RowColumnIndex currentRowColumnIndex = RowColumnIndex( + rowIndex, + columnIndex, + ); + dataGridConfiguration.rowSelectionManager.handleTap( + currentRowColumnIndex, + ); } } } @@ -110,13 +115,9 @@ abstract class DataCellBase { class DataCell extends DataCellBase { @override Widget? _onInitializeColumnElement(bool isInEdit) { - if (cellType != CellType.indentCell) { - if (renderer != null) { - renderer!.setCellStyle(this); - return renderer!.onPrepareWidgets(this); - } else { - return null; - } + if (renderer != null) { + renderer!.setCellStyle(this); + return renderer!.onPrepareWidgets(this); } else { return null; } @@ -178,6 +179,13 @@ abstract class DataRowBase { /// Holds the details of row configuration. DataGridRow? dataGridRow; + /// Holds the details of Group row configuration. + Object? rowData; + + /// By default row will be in row level 1. + /// With Group the level will be based on group level. + int rowLevel = 0; + /// Holds the configuration of collection [DataGridCell]. DataGridRowAdapter? dataGridRowAdapter; @@ -211,10 +219,9 @@ abstract class DataRowBase { /// Returns the `VisibleLineInfo` for the given column index. VisibleLineInfo? getColumnVisibleLineInfo(int index) => - dataGridStateDetails!() - .container - .scrollColumns - .getVisibleLineAtLineIndex(index); + dataGridStateDetails!().container.scrollColumns.getVisibleLineAtLineIndex( + index, + ); /// Returns the `VisibleLineInfo` for the given row index. VisibleLineInfo? getRowVisibleLineInfo(int index) => dataGridStateDetails!() @@ -283,6 +290,7 @@ class DataRow extends DataRowBase { @override void _onGenerateVisibleColumns(VisibleLinesCollection visibleColumnLines) { visibleColumns.clear(); + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); int startColumnIndex = 0; int endColumnIndex = -1; @@ -329,9 +337,13 @@ class DataRow extends DataRowBase { } for (int index = startColumnIndex; index <= endColumnIndex; index++) { - DataCellBase? dc = _createColumn(index); - visibleColumns.add(dc); - dc = null; + final int length = dataGridConfiguration.source.groupedColumns.length; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + index < length) { + visibleColumns.add(_createIndentColumns(index)); + } else { + visibleColumns.add(_createColumn(index)); + } } } } @@ -340,6 +352,8 @@ class DataRow extends DataRowBase { void _ensureColumns(VisibleLinesCollection visibleColumnLines) { // Need to ignore footer view Row. because footer view row doesn't have // visible columns. + + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); if (rowType == RowType.footerRow) { return; } @@ -394,18 +408,54 @@ class DataRow extends DataRowBase { for (int index = startColumnIndex; index <= endColumnIndex; index++) { DataCellBase? dc = _indexer(index); - if (dc == null) { - DataCellBase? dataCell = _reUseCell(startColumnIndex, endColumnIndex); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + index < dataGridConfiguration.source.groupedColumns.length) { + if (dc == null || dc.cellType != CellType.indentCell) { + dc = _indexer(index); + } + if (dc != null) { + if (dc.isVisible != true) { + dc.isVisible = true; + } - dataCell ??= visibleColumns.firstWhereOrNull((DataCellBase col) => - col.columnIndex == -1 && col.cellType != CellType.indentCell); + dc.isEnsured = true; + dc.columnIndex = index; + } else { + _updateColumn(dc, index); + dc ??= visibleColumns.firstWhereOrNull( + (DataCellBase col) => col.columnIndex == index, + ); + + if (dc != null) { + if (!dc.isVisible) { + dc.isVisible = true; + } + } else { + dc = _createColumn(index); + visibleColumns.add(dc); + } + + dc.isEnsured = true; + dc = null; + } + + continue; + } + if (dc == null) { + DataCellBase? dataCell; + dataCell = _reUseCell(startColumnIndex, endColumnIndex); + dataCell ??= visibleColumns.firstWhereOrNull( + (DataCellBase col) => + col.columnIndex == -1 && col.cellType != CellType.indentCell, + ); _updateColumn(dataCell, index); dataCell = null; } - dc ??= visibleColumns - .firstWhereOrNull((DataCellBase col) => col.columnIndex == index); + dc ??= visibleColumns.firstWhereOrNull( + (DataCellBase col) => col.columnIndex == index, + ); if (dc != null) { if (!dc.isVisible) { @@ -428,17 +478,39 @@ class DataRow extends DataRowBase { } } + DataCellBase _createIndentColumns(int index) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + final DataCellBase dr = DataCell(); + + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + dr + ..dataRow = this + ..columnIndex = index + ..rowIndex = rowIndex + ..key = ObjectKey(dr) + ..renderer = dataGridConfiguration.cellRenderers['IndentCell'] + ..cellType = CellType.indentCell + ..columnElement = dr._onInitializeColumnElement(false); + } + + return dr; + } + DataCellBase _createColumn(int index) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); - final bool canIncrementHeight = rowType == RowType.headerRow && + final bool canIncrementHeight = + rowType == RowType.headerRow && dataGridConfiguration.stackedHeaderRows.isNotEmpty; - final DataCellBase dc = DataCell() - ..dataRow = this - ..columnIndex = index - ..rowIndex = rowIndex; + final DataCellBase dc = + DataCell() + ..dataRow = this + ..columnIndex = index + ..rowIndex = rowIndex; dc.key = ObjectKey(dc); final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, index); + dataGridConfiguration, + index, + ); dc.gridColumn = dataGridConfiguration.columns[columnIndex]; _checkForCurrentCell(dataGridConfiguration, dc); if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration) && @@ -449,7 +521,8 @@ class DataRow extends DataRowBase { } else { if (dataGridConfiguration.showCheckboxColumn && dc.gridColumn != null && - dc.columnIndex == 0) { + dc.columnIndex == + dataGridConfiguration.source.groupedColumns.length) { dc.renderer = dataGridConfiguration.cellRenderers['Checkbox']; dc.cellType = CellType.checkboxCell; } else { @@ -460,8 +533,12 @@ class DataRow extends DataRowBase { } if (canIncrementHeight) { final int rowSpan = grid_helper.getRowSpan( - dataGridConfiguration, dc.dataRow!.rowIndex - 1, index, false, - mappingName: dc.gridColumn!.columnName); + dataGridConfiguration, + dc.dataRow!.rowIndex - 1, + index, + false, + mappingName: dc.gridColumn!.columnName, + ); dc.rowSpan = rowSpan; } @@ -472,7 +549,8 @@ class DataRow extends DataRowBase { DataCellBase? _indexer(int index) { for (final DataCellBase column in visibleColumns) { if (rowType == RowType.tableSummaryRow || - rowType == RowType.tableSummaryCoveredRow) { + rowType == RowType.tableSummaryCoveredRow || + rowType == RowType.captionSummaryCoveredRow) { if (index >= column.columnIndex && index <= column.columnIndex + column.columnSpan) { return column; @@ -487,17 +565,20 @@ class DataRow extends DataRowBase { } DataCellBase? _reUseCell(int startColumnIndex, int endColumnIndex) => - visibleColumns.firstWhereOrNull((DataCellBase cell) => - cell.gridColumn != null && - (cell.columnIndex < 0 || - cell.columnIndex < startColumnIndex || - cell.columnIndex > endColumnIndex) && - !cell.isEnsured && - !cell.isEditing); + visibleColumns.firstWhereOrNull( + (DataCellBase cell) => + cell.gridColumn != null && + (cell.columnIndex < 0 || + cell.columnIndex < startColumnIndex || + cell.columnIndex > endColumnIndex) && + !cell.isEnsured && + !cell.isEditing, + ); void _updateColumn(DataCellBase? dc, int index) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); - final bool canIncrementHeight = rowType == RowType.headerRow && + final bool canIncrementHeight = + rowType == RowType.headerRow && dataGridConfiguration.stackedHeaderRows.isNotEmpty; if (dc != null) { if (index < 0 || index >= dataGridConfiguration.container.columnCount) { @@ -509,14 +590,20 @@ class DataRow extends DataRowBase { ..key = dc.key ..isVisible = true; final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, index); + dataGridConfiguration, + index, + ); dc.gridColumn = dataGridConfiguration.columns[columnIndex]; _checkForCurrentCell(dataGridConfiguration, dc); _updateRenderer(dataGridConfiguration, dc, dc.gridColumn); if (canIncrementHeight) { final int rowSpan = grid_helper.getRowSpan( - dataGridConfiguration, dc.dataRow!.rowIndex - 1, index, false, - mappingName: dc.gridColumn!.columnName); + dataGridConfiguration, + dc.dataRow!.rowIndex - 1, + index, + false, + mappingName: dc.gridColumn!.columnName, + ); dc.rowSpan = rowSpan; } else { dc.rowSpan = 0; @@ -530,13 +617,22 @@ class DataRow extends DataRowBase { dc.isVisible = true; } } else { - dc = _createColumn(index); - visibleColumns.add(dc); + final int length = dataGridConfiguration.source.groupedColumns.length; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + index < length) { + visibleColumns.add(_createIndentColumns(index)); + } else { + dc = _createColumn(index); + visibleColumns.add(dc); + } } } - void _updateRenderer(DataGridConfiguration dataGridConfiguration, - DataCellBase dataCell, GridColumn? column) { + void _updateRenderer( + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, + GridColumn? column, + ) { GridCellRendererBase? newRenderer; if (rowRegion == RowRegion.header && rowType == RowType.headerRow) { newRenderer = dataGridConfiguration.cellRenderers['ColumnHeader']; @@ -544,7 +640,8 @@ class DataRow extends DataRowBase { } else { if (dataGridConfiguration.showCheckboxColumn && column != null && - dataCell.columnIndex == 0) { + dataCell.columnIndex == + dataGridConfiguration.source.groupedColumns.length) { newRenderer = dataGridConfiguration.cellRenderers['Checkbox']; dataCell.cellType = CellType.checkboxCell; } else { @@ -558,7 +655,9 @@ class DataRow extends DataRowBase { } void _checkForCurrentCell( - DataGridConfiguration dataGridConfiguration, DataCellBase dc) { + DataGridConfiguration dataGridConfiguration, + DataCellBase dc, + ) { if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { final CurrentCellManager currentCellManager = dataGridConfiguration.currentCell; @@ -591,8 +690,10 @@ class RowGenerator { /// Generates the rows for the [SfDataGrid] by using [DataGridSource]. /// It's generating rows for the data grid initially. - void preGenerateRows(VisibleLinesCollection? visibleRows, - VisibleLinesCollection? visibleColumns) { + void preGenerateRows( + VisibleLinesCollection? visibleRows, + VisibleLinesCollection? visibleColumns, + ) { if (items.isNotEmpty || _dataGridConfiguration.container.rowCount <= 0) { return; } @@ -621,20 +722,32 @@ class RowGenerator { } /// Ensures the data grid rows based on current visible rows. - void ensureRows(VisibleLinesCollection visibleRows, - VisibleLinesCollection visibleColumns) { + void ensureRows( + VisibleLinesCollection visibleRows, + VisibleLinesCollection visibleColumns, + ) { List? actualStartAndEndIndex = []; RowRegion? region = RowRegion.header; List reUseRows() => items - .where((DataRowBase row) => - (row.rowIndex < 0 || - row.rowIndex < actualStartAndEndIndex![0] || - row.rowIndex > actualStartAndEndIndex[1]) && - !row.isEnsured && - !row.isEditing) + .where( + (DataRowBase row) => + (row.rowIndex < 0 || + row.rowIndex < actualStartAndEndIndex![0] || + row.rowIndex > actualStartAndEndIndex[1]) && + !row.isEnsured && + !row.isEditing, + ) .toList(growable: false); + void reuseRow(int rowIndex, RowRegion rowRegion) { + List? rows = reUseRows(); + if (rows.isNotEmpty) { + _updateRow(rows, rowIndex, rowRegion); + rows = null; + } + } + for (final DataRowBase row in items) { row.isEnsured = false; } @@ -651,38 +764,36 @@ class RowGenerator { actualStartAndEndIndex = _container.getStartEndIndex(visibleRows, i); } - for (int index = actualStartAndEndIndex[0]; - index <= actualStartAndEndIndex[1]; - index++) { + for ( + int index = actualStartAndEndIndex[0]; + index <= actualStartAndEndIndex[1]; + index++ + ) { DataRowBase? dr = _indexer(index); if (dr == null) { - int cacheLength = 0; - int footerRowsCount = 0; - if (dataGridStateDetails().rowsCacheExtent != null) { - cacheLength += (visibleRows.length - 1) + - dataGridStateDetails().rowsCacheExtent!; - footerRowsCount += dataGridStateDetails().footerFrozenRowsCount + - grid_helper.getTableSummaryCount( - dataGridStateDetails(), GridTableSummaryRowPosition.bottom); - } - if (dataGridStateDetails().rowsCacheExtent != null && - items.length < cacheLength && - index >= (items.length - footerRowsCount) && - region == RowRegion.body) { - dr = _createDataRow(index, visibleColumns); - dr.isEnsured = true; - items.add(dr); - } else { - List? rows = reUseRows(); - if (rows.isNotEmpty) { - _updateRow(rows, index, region); - rows = null; + final DataGridConfiguration dataGridConfiguration = + dataGridStateDetails(); + if (region == RowRegion.body && + dataGridConfiguration.rowsCacheExtent != null && + dataGridConfiguration.rowsCacheExtent! > 0) { + final int cacheLength = + visibleRows.length + dataGridConfiguration.rowsCacheExtent!; + + if (items.length <= cacheLength) { + dr = _createDataRow(index, visibleColumns); + dr.isEnsured = true; + items.add(dr); + } else { + reuseRow(index, region); } + } else { + reuseRow(index, region); } } - dr ??= - items.firstWhereOrNull((DataRowBase row) => row.rowIndex == index); + dr ??= items.firstWhereOrNull( + (DataRowBase row) => row.rowIndex == index, + ); if (dr != null) { if (!dr.isVisible) { @@ -729,53 +840,97 @@ class RowGenerator { } DataRowBase _createDataRow( - int rowIndex, VisibleLinesCollection visibleColumns, - {RowRegion rowRegion = RowRegion.body}) { + int rowIndex, + VisibleLinesCollection visibleColumns, { + RowRegion rowRegion = RowRegion.body, + }) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); DataRowBase dr; if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration)) { - dr = DataRow() - ..rowIndex = rowIndex - ..rowRegion = rowRegion - ..rowType = RowType.footerRow - ..dataGridStateDetails = dataGridStateDetails; + dr = + DataRow() + ..rowIndex = rowIndex + ..rowRegion = rowRegion + ..rowType = RowType.footerRow + ..dataGridStateDetails = dataGridStateDetails; dr.key = ObjectKey(dr); dr.footerView = _buildFooterWidget(dataGridConfiguration, rowIndex); return dr; } else { - dr = DataRow() - ..rowIndex = rowIndex - ..rowRegion = rowRegion - ..rowType = RowType.dataRow - ..dataGridStateDetails = dataGridStateDetails; - dr.key = ObjectKey(dr); - dr - ..dataGridRow = - grid_helper.getDataGridRow(dataGridConfiguration, rowIndex) - ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( - dataGridConfiguration, dr.dataGridRow!); - assert(grid_helper.debugCheckTheLength( + dynamic displayElement; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + rowIndex >= 0) { + final int index = resolveStartRecordIndex( dataGridConfiguration, - dataGridConfiguration.columns.length, - dr.dataGridRowAdapter!.cells.length, - 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); - _checkForCurrentRow(dr); - _checkForSelection(dr); - dr._initializeDataRow(visibleColumns); - return dr; + rowIndex, + ); + displayElement = + (index >= 0) ? getGroupElement(dataGridConfiguration, index) : null; + } else { + displayElement = null; + } + if (displayElement == null || displayElement is DataGridRow) { + dr = + DataRow() + ..rowIndex = rowIndex + ..rowRegion = rowRegion + ..rowType = RowType.dataRow + ..rowData = displayElement + ..dataGridStateDetails = dataGridStateDetails; + + dr + ..dataGridRow = + displayElement ?? + grid_helper.getDataGridRow(dataGridConfiguration, rowIndex) + ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + dataGridConfiguration, + dr.dataGridRow!, + ); + + dr.key = dr.dataGridRowAdapter?.key; + assert( + grid_helper.debugCheckTheLength( + dataGridConfiguration, + dataGridConfiguration.columns.length, + dr.dataGridRowAdapter!.cells.length, + 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length', + ), + ); + + _checkForCurrentRow(dr); + _checkForSelection(dr); + dr._initializeDataRow(visibleColumns); + return dr; + } else { + final _SpannedDataRow spanDataRow = _SpannedDataRow(); + spanDataRow + ..key = ObjectKey(spanDataRow) + ..rowData = displayElement + ..rowLevel = displayElement.level + ..rowIndex = rowIndex + ..rowRegion = RowRegion.body + ..rowType = RowType.captionSummaryCoveredRow + ..dataGridStateDetails = dataGridStateDetails; + + spanDataRow._initializeDataRow(visibleColumns); + return spanDataRow; + } } } DataRowBase _createHeaderRow( - int rowIndex, VisibleLinesCollection visibleColumns) { + int rowIndex, + VisibleLinesCollection visibleColumns, + ) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; DataRowBase dr; if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration)) { - dr = DataRow() - ..rowIndex = rowIndex - ..rowRegion = RowRegion.header - ..rowType = RowType.headerRow - ..dataGridStateDetails = dataGridStateDetails; + dr = + DataRow() + ..rowIndex = rowIndex + ..rowRegion = RowRegion.header + ..rowType = RowType.headerRow + ..dataGridStateDetails = dataGridStateDetails; dr ..key = ObjectKey(dr) .._initializeDataRow(visibleColumns); @@ -788,11 +943,15 @@ class RowGenerator { dr.rowType = RowType.stackedHeaderRow; dr.dataGridStateDetails = dataGridStateDetails; _createStackedHeaderCell( - dataGridConfiguration.stackedHeaderRows[rowIndex], rowIndex); + dataGridConfiguration.stackedHeaderRows[rowIndex], + rowIndex, + ); dr._initializeDataRow(visibleColumns); return dr; } else if (grid_helper.isTopTableSummaryRow( - dataGridConfiguration, rowIndex)) { + dataGridConfiguration, + rowIndex, + )) { dr = _createTableSummaryRow(rowIndex, GridTableSummaryRowPosition.top); dr ..key = ObjectKey(dr) @@ -801,23 +960,31 @@ class RowGenerator { .._initializeDataRow(visibleColumns); return dr; } else { - return _createDataRow(rowIndex, visibleColumns, - rowRegion: RowRegion.header); + return _createDataRow( + rowIndex, + visibleColumns, + rowRegion: RowRegion.header, + ); } } void _createStackedHeaderCell(StackedHeaderRow header, int rowIndex) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; for (final StackedHeaderCell column in header.cells) { - final List childSequence = - grid_helper.getChildSequence(dataGridConfiguration, column, rowIndex); + final List childSequence = grid_helper.getChildSequence( + dataGridConfiguration, + column, + rowIndex, + ); childSequence.sort(); setChildColumnIndexes(column, childSequence); } } DataRowBase _createFooterRow( - int rowIndex, VisibleLinesCollection visibleColumns) { + int rowIndex, + VisibleLinesCollection visibleColumns, + ) { DataRowBase dr; final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (grid_helper.isBottomTableSummaryRow(dataGridConfiguration, rowIndex)) { @@ -828,14 +995,19 @@ class RowGenerator { ..rowRegion = RowRegion.footer .._initializeDataRow(visibleColumns); } else { - dr = - _createDataRow(rowIndex, visibleColumns, rowRegion: RowRegion.footer); + dr = _createDataRow( + rowIndex, + visibleColumns, + rowRegion: RowRegion.footer, + ); } return dr; } Widget? _buildFooterWidget( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, + ) { if (dataGridConfiguration.footer == null) { return null; } @@ -846,47 +1018,56 @@ class RowGenerator { final GridLinesVisibility gridLinesVisibility = dataGridConfiguration.gridLinesVisibility; - final bool canDrawHorizontalBorder = (gridLinesVisibility == - GridLinesVisibility.horizontal || + final bool canDrawHorizontalBorder = + (gridLinesVisibility == GridLinesVisibility.horizontal || gridLinesVisibility == GridLinesVisibility.both) && grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom) <= + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ) <= 0; final bool canDrawVerticalBorder = gridLinesVisibility == GridLinesVisibility.vertical || - gridLinesVisibility == GridLinesVisibility.both; + gridLinesVisibility == GridLinesVisibility.both; final bool canDrawTopFrozenBorder = dataGridConfiguration.footerFrozenRowsCount.isFinite && - dataGridConfiguration.footerFrozenRowsCount > 0 && - grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) == - rowIndex; + dataGridConfiguration.footerFrozenRowsCount > 0 && + grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) == + rowIndex; return BoxDecoration( border: BorderDirectional( - top: canDrawTopFrozenBorder && themeData.frozenPaneElevation <= 0.0 - ? BorderSide( - color: themeData.frozenPaneLineColor, - width: themeData.frozenPaneLineWidth) - : BorderSide.none, - bottom: canDrawHorizontalBorder - ? BorderSide( - color: themeData.gridLineColor, - width: themeData.gridLineStrokeWidth) - : BorderSide.none, - end: canDrawVerticalBorder - ? BorderSide( - color: themeData.gridLineColor, - width: themeData.gridLineStrokeWidth) - : BorderSide.none, + top: + canDrawTopFrozenBorder && themeData.frozenPaneElevation! <= 0.0 + ? BorderSide( + color: themeData.frozenPaneLineColor!, + width: themeData.frozenPaneLineWidth!, + ) + : BorderSide.none, + bottom: + canDrawHorizontalBorder + ? BorderSide( + color: themeData.gridLineColor!, + width: themeData.gridLineStrokeWidth!, + ) + : BorderSide.none, + end: + canDrawVerticalBorder + ? BorderSide( + color: themeData.gridLineColor!, + width: themeData.gridLineStrokeWidth!, + ) + : BorderSide.none, ), ); } return Container( - decoration: drawBorder(), - clipBehavior: Clip.antiAlias, - child: dataGridConfiguration.footer); + decoration: drawBorder(), + clipBehavior: Clip.antiAlias, + child: dataGridConfiguration.footer, + ); } DataRowBase? _indexer(int index) { @@ -901,46 +1082,70 @@ class RowGenerator { void _updateRow(List rows, int index, RowRegion region) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; - final VisibleLinesCollection visibleColumns = - grid_helper.getVisibleLines(dataGridConfiguration); + final VisibleLinesCollection visibleColumns = grid_helper.getVisibleLines( + dataGridConfiguration, + ); switch (region) { case RowRegion.header: _updateHeaderRow( - rows, index, region, visibleColumns, dataGridConfiguration); + rows, + index, + region, + visibleColumns, + dataGridConfiguration, + ); break; case RowRegion.body: _updateDataRow( - rows, index, region, visibleColumns, dataGridConfiguration); + rows, + index, + region, + visibleColumns, + dataGridConfiguration, + ); break; case RowRegion.footer: _updateFooterRow( - rows, index, region, visibleColumns, dataGridConfiguration); + rows, + index, + region, + visibleColumns, + dataGridConfiguration, + ); break; } } DataRowBase _createTableSummaryRow( - int rowIndex, GridTableSummaryRowPosition position) { + int rowIndex, + GridTableSummaryRowPosition position, + ) { final GridTableSummaryRow? tableSummaryRow = grid_helper.getTableSummaryRow( - _dataGridConfiguration, rowIndex, position); - final _SpannedDataRow spannedDataRow = _SpannedDataRow() - ..rowIndex = rowIndex - ..tableSummaryRow = tableSummaryRow; + _dataGridConfiguration, + rowIndex, + position, + ); + final _SpannedDataRow spannedDataRow = + _SpannedDataRow() + ..rowIndex = rowIndex + ..tableSummaryRow = tableSummaryRow; if (tableSummaryRow != null) { - spannedDataRow.rowType = tableSummaryRow.showSummaryInRow - ? RowType.tableSummaryCoveredRow - : RowType.tableSummaryRow; + spannedDataRow.rowType = + tableSummaryRow.showSummaryInRow + ? RowType.tableSummaryCoveredRow + : RowType.tableSummaryRow; } return spannedDataRow; } void _updateHeaderRow( - List rows, - int index, - RowRegion region, - VisibleLinesCollection visibleColumns, - DataGridConfiguration dataGridConfiguration) { + List rows, + int index, + RowRegion region, + VisibleLinesCollection visibleColumns, + DataGridConfiguration dataGridConfiguration, + ) { DataRowBase? dr; void createHeaderRow() { dr = _createHeaderRow(index, visibleColumns); @@ -950,7 +1155,8 @@ class RowGenerator { if (index == grid_helper.getHeaderIndex(dataGridConfiguration)) { dr = rows.firstWhereOrNull( - (DataRowBase row) => row.rowType == RowType.headerRow); + (DataRowBase row) => row.rowType == RowType.headerRow, + ); if (dr != null) { dr! ..key = dr!.key @@ -964,7 +1170,8 @@ class RowGenerator { } } else if (index < dataGridConfiguration.stackedHeaderRows.length) { dr = rows.firstWhereOrNull( - (DataRowBase r) => r.rowType == RowType.stackedHeaderRow); + (DataRowBase r) => r.rowType == RowType.stackedHeaderRow, + ); if (dr != null) { dr! ..key = dr!.key @@ -973,17 +1180,22 @@ class RowGenerator { ..rowType = RowType.stackedHeaderRow ..rowIndexChanged(); _createStackedHeaderCell( - dataGridConfiguration.stackedHeaderRows[index], index); + dataGridConfiguration.stackedHeaderRows[index], + index, + ); dr!._initializeDataRow( - dataGridConfiguration.container.scrollRows.getVisibleLines()); + dataGridConfiguration.container.scrollRows.getVisibleLines(), + ); } else { createHeaderRow(); } } else if (grid_helper.isTopTableSummaryRow(dataGridConfiguration, index)) { - dr = rows.firstWhereOrNull((DataRowBase r) => - r.rowRegion == region && - (r.rowType == RowType.tableSummaryRow || - r.rowType == RowType.tableSummaryCoveredRow)); + dr = rows.firstWhereOrNull( + (DataRowBase r) => + r.rowRegion == region && + (r.rowType == RowType.tableSummaryRow || + r.rowType == RowType.tableSummaryCoveredRow), + ); if (dr != null) { dr! ..key = dr!.key @@ -997,13 +1209,15 @@ class RowGenerator { } void _updateDataRow( - List rows, - int index, - RowRegion region, - VisibleLinesCollection visibleColumns, - DataGridConfiguration dataGridConfiguration) { + List rows, + int index, + RowRegion region, + VisibleLinesCollection visibleColumns, + DataGridConfiguration dataGridConfiguration, + ) { DataRowBase? row = rows.firstWhereOrNull( - (DataRowBase row) => row is DataRow && row.rowType == RowType.dataRow); + (DataRowBase row) => row is DataRow && row.rowType == RowType.dataRow, + ); void createDataRow() { row = _createDataRow(index, visibleColumns); items.add(row!); @@ -1011,8 +1225,9 @@ class RowGenerator { } if (grid_helper.isFooterWidgetRow(index, dataGridConfiguration)) { - row = rows - .firstWhereOrNull((DataRowBase r) => r.rowType == RowType.footerRow); + row = rows.firstWhereOrNull( + (DataRowBase r) => r.rowType == RowType.footerRow, + ); if (row != null) { row! ..key = row!.key @@ -1027,22 +1242,96 @@ class RowGenerator { if (index < 0 || index >= _container.scrollRows.lineCount) { row!.isVisible = false; } else { - row! - ..key = row!.key - ..rowIndex = index - ..rowRegion = region - ..dataGridRow = - grid_helper.getDataGridRow(dataGridConfiguration, index) - ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( - dataGridConfiguration, row!.dataGridRow!); - assert(grid_helper.debugCheckTheLength( + dynamic displayElement; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + index >= 0) { + final int rowIndex = resolveStartRecordIndex( dataGridConfiguration, - dataGridConfiguration.columns.length, - row!.dataGridRowAdapter!.cells.length, - 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); - _checkForCurrentRow(row!); - _checkForSelection(row!); - row!.rowIndexChanged(); + index, + ); + displayElement = + (index >= 0) + ? getGroupElement(dataGridConfiguration, rowIndex) + : null; + } else { + displayElement = null; + } + if (displayElement == null || displayElement is DataGridRow) { + if (rows.any( + (DataRowBase row) => + row is DataRow && row.rowType == RowType.dataRow, + )) { + final DataRowBase? row = rows.firstWhereOrNull( + (DataRowBase row) => + row is DataRow && row.rowType == RowType.dataRow, + ); + + row! + ..key = row.key + ..rowIndex = index + ..rowRegion = region + ..rowData = displayElement + ..dataGridRow = + displayElement ?? + grid_helper.getDataGridRow(dataGridConfiguration, index) + ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + dataGridConfiguration, + row.dataGridRow!, + ); + assert( + grid_helper.debugCheckTheLength( + dataGridConfiguration, + dataGridConfiguration.columns.length, + row.dataGridRowAdapter!.cells.length, + 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length', + ), + ); + _checkForCurrentRow(row); + _checkForSelection(row); + row.rowIndexChanged(); + } else { + createDataRow(); + } + } else { + if (rows.any( + (DataRowBase row) => + row is _SpannedDataRow && + row.rowType == RowType.captionSummaryCoveredRow, + )) { + final _SpannedDataRow spanDataRow = + rows.firstWhereOrNull( + (DataRowBase row) => + row.rowType == RowType.captionSummaryCoveredRow, + )! + as _SpannedDataRow; + final dynamic rowData = spanDataRow.rowData; + spanDataRow + ..key = ObjectKey(spanDataRow) + ..rowData = displayElement + ..rowLevel = displayElement.level + ..rowIndex = index + ..rowRegion = RowRegion.body + ..rowType = RowType.captionSummaryCoveredRow + ..dataGridStateDetails = dataGridStateDetails + ..rowIndexChanged(); + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, + 0, + spanDataRow.rowType, + null, + spanDataRow.rowLevel, + ); + _updataSpannedRow(spanDataRow, displayElement, columnSpan); + if (rowData.level > displayElement.level) { + for (int i = displayElement.level; i < rowData.level; i++) { + spanDataRow.visibleColumns[i].columnIndex = -1; + } + } + spanDataRow.ensureIndentCell(visibleColumns); + } else { + createDataRow(); + } + } } row = null; } else { @@ -1050,18 +1339,32 @@ class RowGenerator { } } + void _updataSpannedRow(_SpannedDataRow row, Group groupItem, int columnSpan) { + if (row.rowType == RowType.captionSummaryCoveredRow) { + for (final DataCellBase spannedCell in row.visibleColumns) { + if (spannedCell is _SpannedDataColumn) { + spannedCell.columnIndex = groupItem.level; + spannedCell.columnSpan = columnSpan; + } + } + } + } + void _updateFooterRow( - List rows, - int index, - RowRegion region, - VisibleLinesCollection visibleColumns, - DataGridConfiguration dataGridConfiguration) { + List rows, + int index, + RowRegion region, + VisibleLinesCollection visibleColumns, + DataGridConfiguration dataGridConfiguration, + ) { DataRowBase? dr; if (grid_helper.isBottomTableSummaryRow(dataGridConfiguration, index)) { - dr = rows.firstWhereOrNull((DataRowBase r) => - r.rowRegion == region && - (r.rowType == RowType.tableSummaryRow || - r.rowType == RowType.tableSummaryCoveredRow)); + dr = rows.firstWhereOrNull( + (DataRowBase r) => + r.rowRegion == region && + (r.rowType == RowType.tableSummaryRow || + r.rowType == RowType.tableSummaryCoveredRow), + ); if (dr != null) { dr ..key = dr.key @@ -1075,7 +1378,12 @@ class RowGenerator { } } else { _updateDataRow( - rows, index, region, visibleColumns, dataGridConfiguration); + rows, + index, + region, + visibleColumns, + dataGridConfiguration, + ); } } @@ -1086,7 +1394,9 @@ class RowGenerator { if (dataGridConfiguration.onQueryRowHeight != null) { final RowHeightDetails details = RowHeightDetails(rowIndex, height); setColumnSizerInRowHeightDetailsArgs( - details, dataGridConfiguration.columnSizer); + details, + dataGridConfiguration.columnSizer, + ); rowHeight = dataGridConfiguration.onQueryRowHeight!(details); } @@ -1096,12 +1406,31 @@ class RowGenerator { void _checkForSelection(DataRowBase row) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; if (dataGridConfiguration.selectionMode != SelectionMode.none) { - final int recordIndex = - grid_helper.resolveToRecordIndex(dataGridConfiguration, row.rowIndex); - final DataGridRow record = - effectiveRows(dataGridConfiguration.source)[recordIndex]; - row._isSelectedRow = - selection_manager.isSelectedRow(dataGridConfiguration, record); + final DataGridRow record; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final int rowIndex = resolveStartRecordIndex( + dataGridConfiguration, + row.rowIndex, + ); + final dynamic element = getGroupElement( + dataGridConfiguration, + rowIndex, + ); + if (element is! DataGridRow) { + return; + } + record = element; + } else { + final int recordIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, + row.rowIndex, + ); + record = effectiveRows(dataGridConfiguration.source)[recordIndex]; + } + row._isSelectedRow = selection_manager.isSelectedRow( + dataGridConfiguration, + record, + ); } } @@ -1117,8 +1446,9 @@ class RowGenerator { } final DataCellBase? dc = dr.visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => - dataCell.columnIndex == currentCellManager.columnIndex); + (DataCellBase dataCell) => + dataCell.columnIndex == currentCellManager.columnIndex, + ); return dc; } @@ -1153,17 +1483,45 @@ class _SpannedDataRow extends DataRow { rowType == RowType.tableSummaryCoveredRow) { if (rowType == RowType.tableSummaryRow) { for (int i = 0; i < dataGridConfiguration.container.columnCount; i++) { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + i < dataGridConfiguration.source.groupedColumns.length) { + final DataCellBase dr = _createIndentColumns(i); + visibleColumns.add(dr); + continue; + } if (!_isEnsuredSpannedCell(i)) { final int columnSpan = grid_helper.getSummaryColumnSpan( - dataGridConfiguration, i, rowType, tableSummaryRow); + dataGridConfiguration, + i, + rowType, + tableSummaryRow, + ); dc = _createSpannedColumn(i, columnSpan, null); visibleColumns.add(dc); } } } else { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + for ( + int i = 0; + i < dataGridConfiguration.source.groupedColumns.length; + i++ + ) { + final DataCellBase dr = _createIndentColumns(i); + visibleColumns.add(dr); + } + } final int columnSpan = grid_helper.getSummaryColumnSpan( - dataGridConfiguration, 0, rowType, tableSummaryRow); - dc = _createSpannedColumn(0, columnSpan, null); + dataGridConfiguration, + 0, + rowType, + tableSummaryRow, + ); + dc = _createSpannedColumn( + dataGridConfiguration.source.groupedColumns.length, + columnSpan, + null, + ); visibleColumns.add(dc); } } else if (rowType == RowType.stackedHeaderRow) { @@ -1171,39 +1529,81 @@ class _SpannedDataRow extends DataRow { if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { final List stackedColumns = dataGridConfiguration.stackedHeaderRows[rowIndex].cells; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + for ( + int i = 0; + i < dataGridConfiguration.source.groupedColumns.length; + i++ + ) { + final DataCellBase dr = _createIndentColumns(i); + visibleColumns.add(dr); + } + } for (final StackedHeaderCell column in stackedColumns) { - final List> columnsSequence = - grid_helper.getConsecutiveRanges(getChildColumnIndexes(column)); - + final List> columnsSequence = grid_helper + .getConsecutiveRanges(getChildColumnIndexes(column)); for (final List columns in columnsSequence) { dc = _createSpannedColumn( - columns.reduce(min), columns.length - 1, column); + columns.reduce(min), + columns.length - 1, + column, + ); visibleColumns.add(dc); } } } } + } else if (rowType == RowType.captionSummaryCoveredRow) { + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, + 0, + rowType, + null, + rowLevel, + ); + for (int i = 0; i < rowLevel; i++) { + final DataCellBase dr = _createIndentColumns(i); + visibleColumns.add(dr); + } + dc = _createSpannedColumn(rowLevel, columnSpan, null); + visibleColumns.add(dc); } } - int _getRowSpan(int columnIndex, StackedHeaderCell? stackedHeaderCell, - String mappingName) { + int _getRowSpan( + int columnIndex, + StackedHeaderCell? stackedHeaderCell, + String mappingName, + ) { int rowSpan = 0; final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); if (rowType == RowType.stackedHeaderRow) { rowSpan = grid_helper.getRowSpan( - dataGridConfiguration, rowIndex, columnIndex, true, - stackedHeaderCell: stackedHeaderCell); + dataGridConfiguration, + rowIndex, + columnIndex, + true, + stackedHeaderCell: stackedHeaderCell, + ); } else if (rowType == RowType.headerRow) { rowSpan = grid_helper.getRowSpan( - dataGridConfiguration, rowIndex - 1, columnIndex, false, - mappingName: mappingName); + dataGridConfiguration, + rowIndex - 1, + columnIndex, + false, + mappingName: mappingName, + ); } return rowSpan; } - _SpannedDataColumn _createSpannedCell(_SpannedDataColumn dc, int columnIndex, - int columnSpan, int rowSpan, GridColumn? gridColumn) { + _SpannedDataColumn _createSpannedCell( + _SpannedDataColumn dc, + int columnIndex, + int columnSpan, + int rowSpan, + GridColumn? gridColumn, + ) { dc ..dataRow = this ..columnIndex = columnIndex @@ -1218,26 +1618,60 @@ class _SpannedDataRow extends DataRow { } _SpannedDataColumn _createSpannedColumn( - int index, int columnSpan, StackedHeaderCell? stackedHeaderCell) { + int index, + int columnSpan, + StackedHeaderCell? stackedHeaderCell, + ) { _SpannedDataColumn dc = _SpannedDataColumn(); final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); if (index >= grid_helper.resolveToStartColumnIndex(dataGridConfiguration)) { - final int startColumnIndex = - grid_helper.resolveToScrollColumnIndex(dataGridConfiguration, index); - final GridColumn gridColumn = - dataGridConfiguration.columns[startColumnIndex]; - final int rowSpan = - _getRowSpan(index, stackedHeaderCell, gridColumn.columnName); + int startColumnIndex = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + index, + ); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + (rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow)) { + startColumnIndex = resolveToGridVisibleColumnIndex( + dataGridConfiguration, + index, + ); + } + final int indentColumnCount = + dataGridConfiguration.source.groupedColumns.length; if (rowType == RowType.stackedHeaderRow) { - dc = _createSpannedCell(dc, index, columnSpan, rowSpan, gridColumn); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + startColumnIndex = index; + } + final GridColumn gridColumn = + dataGridConfiguration.columns[startColumnIndex]; + final int rowSpan = _getRowSpan( + index, + stackedHeaderCell, + gridColumn.columnName, + ); + dc = _createSpannedCell( + dc, + index + indentColumnCount, + columnSpan, + rowSpan, + gridColumn, + ); dc ..renderer = dataGridConfiguration.cellRenderers['StackedHeader'] ..stackedHeaderCell = stackedHeaderCell ..cellType = CellType.stackedHeaderCell; } else if (rowType == RowType.tableSummaryRow || rowType == RowType.tableSummaryCoveredRow) { + final GridColumn gridColumn = + dataGridConfiguration.columns[startColumnIndex]; + final int rowSpan = _getRowSpan( + index, + stackedHeaderCell, + gridColumn.columnName, + ); dc = _createSpannedCell(dc, index, columnSpan, rowSpan, gridColumn); dc ..renderer = dataGridConfiguration.cellRenderers['TableSummary'] @@ -1247,14 +1681,22 @@ class _SpannedDataRow extends DataRow { if (tableSummaryRow != null && tableSummaryRow!.columns.isNotEmpty) { if (rowType == RowType.tableSummaryRow) { final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( - dataGridConfiguration, tableSummaryRow!); + dataGridConfiguration, + tableSummaryRow!, + ); if (dc.columnIndex >= titleColumnSpan) { dc.summaryColumn = tableSummaryRow!.columns.firstWhereOrNull( - (GridSummaryColumn element) => - element.columnName == gridColumn.columnName); + (GridSummaryColumn element) => + element.columnName == gridColumn.columnName, + ); } } } + } else if (rowType == RowType.captionSummaryCoveredRow) { + dc = _createSpannedCell(dc, index, columnSpan, 0, null); + dc + ..renderer = dataGridConfiguration.cellRenderers['CaptionSummary'] + ..cellType = CellType.captionSummaryCell; } } else { // To create an indent cell if it exist in the spanned row. @@ -1265,25 +1707,35 @@ class _SpannedDataRow extends DataRow { return dc; } - void _ensureSpannedColumn( - {required int columnIndex, - required int columnSpan, - StackedHeaderCell? stackedHeaderCell}) { + void _ensureSpannedColumn({ + required int columnIndex, + required int columnSpan, + StackedHeaderCell? stackedHeaderCell, + }) { DataCellBase? dc = _indexer(columnIndex); if (dc == null) { - DataCellBase? dataCell = - _reUseCell(columnIndex, columnIndex + columnSpan); - dataCell ??= visibleColumns.firstWhereOrNull((DataCellBase col) => - col.columnIndex == -1 && col.cellType != CellType.indentCell); + DataCellBase? dataCell = _reUseCell( + columnIndex, + columnIndex + columnSpan, + ); + dataCell ??= visibleColumns.firstWhereOrNull( + (DataCellBase col) => + col.columnIndex == -1 && col.cellType != CellType.indentCell, + ); _updateSpannedColumn( - dataCell, columnIndex, columnSpan, stackedHeaderCell); + dataCell, + columnIndex, + columnSpan, + stackedHeaderCell, + ); dataCell = null; } - dc ??= visibleColumns - .firstWhereOrNull((DataCellBase col) => col.columnIndex == columnIndex); + dc ??= visibleColumns.firstWhereOrNull( + (DataCellBase col) => col.columnIndex == columnIndex, + ); if (dc != null) { if (!dc.isVisible) { @@ -1310,19 +1762,24 @@ class _SpannedDataRow extends DataRow { final StackedHeaderRow stackedHeaderRow = dataGridConfiguration.stackedHeaderRows[rowIndex]; final List stackedColumns = stackedHeaderRow.cells; - dataGridConfiguration.rowGenerator - ._createStackedHeaderCell(stackedHeaderRow, rowIndex); + dataGridConfiguration.rowGenerator._createStackedHeaderCell( + stackedHeaderRow, + rowIndex, + ); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + ensureIndentCell(visibleColumnLines); + } for (final StackedHeaderCell column in stackedColumns) { - final List> columnsSequence = - grid_helper.getConsecutiveRanges(getChildColumnIndexes(column)); - + final List> columnsSequence = grid_helper + .getConsecutiveRanges(getChildColumnIndexes(column)); for (final List columns in columnsSequence) { final int columnIndex = columns.reduce(min); final int columnSpan = columns.length - 1; _ensureSpannedColumn( - columnSpan: columnSpan, - columnIndex: columnIndex, - stackedHeaderCell: column); + columnSpan: columnSpan, + columnIndex: columnIndex, + stackedHeaderCell: column, + ); } } } else { @@ -1334,13 +1791,33 @@ class _SpannedDataRow extends DataRow { final List startAndEndIndex = dataGridConfiguration.container .getStartEndIndex(visibleColumnLines, i); - for (int index = startAndEndIndex[0]; - index <= startAndEndIndex[1]; - index++) { + for ( + int index = startAndEndIndex[0]; + index <= startAndEndIndex[1]; + index++ + ) { if (!_isEnsuredSpannedCell(index)) { - final int columnSpan = grid_helper.getSummaryColumnSpan( - dataGridConfiguration, index, rowType, tableSummaryRow); - _ensureSpannedColumn(columnIndex: index, columnSpan: columnSpan); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + (rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow) && + index >= dataGridConfiguration.source.groupedColumns.length) { + ensureIndentCell(visibleColumnLines); + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, + index, + rowType, + tableSummaryRow, + ); + _ensureSpannedColumn(columnIndex: index, columnSpan: columnSpan); + } else { + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, + index, + rowType, + tableSummaryRow, + ); + _ensureSpannedColumn(columnIndex: index, columnSpan: columnSpan); + } } } } @@ -1353,19 +1830,72 @@ class _SpannedDataRow extends DataRow { } } - void _updateSpannedColumn(DataCellBase? dc, int index, int columnSpan, - StackedHeaderCell? stackedHeaderCell) { + void ensureIndentCell(VisibleLinesCollection visibleColumnLines) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final int length = + (rowType == RowType.stackedHeaderRow || + rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow) + ? dataGridConfiguration.source.groupedColumns.length + : rowLevel; + for (int i = 0; i < length; i++) { + if (i < length) { + final DataCellBase? ic = visibleColumns.firstWhereOrNull( + (DataCellBase column) => column.columnIndex == i, + ); + if (ic != null) { + if (!ic.isVisible) { + ic.isVisible = true; + } + ic.isEnsured = true; + ic.columnIndex = i; + } else { + final DataCellBase indentCell = _createIndentColumns(i); + visibleColumns.add(indentCell); + } + } + continue; + } + visibleColumns.removeWhere( + (DataCellBase column) => column.columnIndex < 0, + ); + visibleColumns.sort( + (DataCellBase a, DataCellBase b) => + a.columnIndex.compareTo(b.columnIndex), + ); + } + } + + void _updateSpannedColumn( + DataCellBase? dc, + int index, + int columnSpan, + StackedHeaderCell? stackedHeaderCell, + ) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); if (dc != null) { if (index < 0 || index >= dataGridConfiguration.container.columnCount) { dc.isVisible = false; } else { - final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, index); + int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, + index, + ); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + (rowType == RowType.tableSummaryRow)) { + columnIndex = resolveToGridVisibleColumnIndex( + dataGridConfiguration, + index, + ); + } final GridColumn gridColumn = dataGridConfiguration.columns[columnIndex]; - final int rowSpan = - _getRowSpan(index, stackedHeaderCell, gridColumn.columnName); + final int rowSpan = _getRowSpan( + index, + stackedHeaderCell, + gridColumn.columnName, + ); dc ..columnIndex = index @@ -1403,10 +1933,12 @@ class _SpannedDataRow extends DataRow { } bool _isEnsuredSpannedCell(int index) { - return visibleColumns.any((DataCellBase cell) => - cell.isEnsured && - (index >= cell.columnIndex && - index <= cell.columnIndex + cell.columnSpan)); + return visibleColumns.any( + (DataCellBase cell) => + cell.isEnsured && + (index >= cell.columnIndex && + index <= cell.columnIndex + cell.columnSpan), + ); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart index d6c4bb0ad..53a8bceed 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart' hide DataCell, DataRow; import 'package:flutter/services.dart'; import '../../grid_common/row_column_index.dart'; +import '../helper/callbackargs.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; import '../helper/enums.dart'; @@ -18,14 +19,14 @@ class SelectionManagerBase extends ChangeNotifier { final List _shiftSelectedRows = []; /// Holds the [DataGridStateDetails]. - DataGridStateDetails? _dataGridStateDetails; + late DataGridStateDetails? _dataGridStateDetails; /// Processes the selection operation when tap a cell. void handleTap(RowColumnIndex rowColumnIndex) {} /// Processes the selection operation when [SfDataGrid] receives raw keyboard /// event. - void handleKeyEvent(RawKeyEvent keyEvent) {} + void handleKeyEvent(KeyEvent keyEvent) {} /// Called when the [SfDataGrid.selectionMode] is changed at run time. void onGridSelectionModeChanged() {} @@ -101,12 +102,14 @@ class RowSelectionManager extends SelectionManagerBase { void _applySelection(RowColumnIndex rowColumnIndex) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final int recordIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, rowColumnIndex.rowIndex); - DataGridRow? record = - selection_helper.getRecord(dataGridConfiguration, recordIndex); - + dataGridConfiguration, + rowColumnIndex.rowIndex, + ); + DataGridRow? record = selection_helper.getRecord( + dataGridConfiguration, + recordIndex, + ); final List addedItems = []; final List removeItems = []; @@ -124,9 +127,16 @@ class RowSelectionManager extends SelectionManagerBase { } if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems)) { + newItems: addedItems, + oldItems: removeItems, + )) { _clearSelectedRow(dataGridConfiguration); _addSelection(record, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: true, + row: record, + rowType: RowType.dataRow, + ); notifyListeners(); _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); } @@ -150,12 +160,27 @@ class RowSelectionManager extends SelectionManagerBase { } if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems)) { + newItems: addedItems, + oldItems: removeItems, + )) { if (record != null && !_selectedRows.contains(record)) { _clearSelectedRow(dataGridConfiguration); _addSelection(record, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: true, + row: record, + rowType: RowType.dataRow, + ); } else { _clearSelectedRow(dataGridConfiguration); + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + _clearCurrentCell(dataGridConfiguration); + } + _raiseCheckboxValueChanged( + value: false, + row: record, + rowType: RowType.dataRow, + ); } notifyListeners(); @@ -180,20 +205,31 @@ class RowSelectionManager extends SelectionManagerBase { } if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems) && + newItems: addedItems, + oldItems: removeItems, + ) && record != null) { if (!_selectedRows.contains(record)) { _addSelection(record, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: true, + row: record, + rowType: RowType.dataRow, + ); } else { _removeSelection(record, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: false, + row: record, + rowType: RowType.dataRow, + ); } notifyListeners(); _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); } //If user, return false in selection changing, and we should need to // update the current cell. - else if (dataGridConfiguration.navigationMode == - GridNavigationMode.cell) { + else { notifyListeners(); } } @@ -201,81 +237,159 @@ class RowSelectionManager extends SelectionManagerBase { record = null; } + /// Check whether the index is outside the shift selected rows range. + bool isIndexOutsideRange( + int pressedRowIndex, + int currentRecordIndex, + int index, + ) { + if (pressedRowIndex < currentRecordIndex) { + return index < pressedRowIndex || index > currentRecordIndex; + } else { + return index < currentRecordIndex || index > pressedRowIndex; + } + } + void _processShiftKeySelection( - RowColumnIndex rowColumnIndex, int currentRecordIndex) { + RowColumnIndex rowColumnIndex, + int currentRecordIndex, + ) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); List addedItems = []; List removedItems = []; removedItems = _shiftSelectedRows.toList(); + for (final DataGridRow selectedRow in List.from( + _selectedRows, + )) { + final int selectedIndex = dataGridConfiguration.source.effectiveRows + .indexOf(selectedRow); + if (isIndexOutsideRange( + _pressedRowIndex, + currentRecordIndex, + selectedIndex, + )) { + _removeSelection(selectedRow, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: false, + row: selectedRow, + rowType: RowType.dataRow, + ); + } + } if (dataGridConfiguration.onSelectionChanging != null || dataGridConfiguration.onSelectionChanged != null) { addedItems = _getAddedItems( - _pressedRowIndex, currentRecordIndex, dataGridConfiguration); - final List commonItemsList = addedItems - .toSet() - .where((DataGridRow record) => removedItems.toSet().contains(record)) - .toList(); - addedItems = addedItems - .toSet() - .where( - (DataGridRow record) => !_selectedRows.toSet().contains(record)) - .toList(); - removedItems = removedItems - .toSet() - .where( - (DataGridRow record) => !commonItemsList.toSet().contains(record)) - .toList(); + _pressedRowIndex, + currentRecordIndex, + dataGridConfiguration, + ); + final List commonItemsList = + addedItems + .toSet() + .where( + (DataGridRow record) => removedItems.toSet().contains(record), + ) + .toList(); + addedItems = + addedItems + .toSet() + .where( + (DataGridRow record) => !_selectedRows.toSet().contains(record), + ) + .toList(); + removedItems = + removedItems + .toSet() + .where( + (DataGridRow record) => + !commonItemsList.toSet().contains(record), + ) + .toList(); } if (_raiseSelectionChanging(newItems: addedItems, oldItems: removedItems)) { - _addSelectionForShiftKey(currentRecordIndex); + _addSelectionForShiftKey(currentRecordIndex, removedItems); notifyListeners(); _raiseSelectionChanged(newItems: addedItems, oldItems: removedItems); } } - void _addSelectionForShiftKey(int currentRecordIndex) { + void _addSelectionForShiftKey( + int currentRecordIndex, + List removedItems, + ) { late DataGridRow? record; final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - if (_shiftSelectedRows.isNotEmpty) { - for (final DataGridRow record in _shiftSelectedRows) { - if (record != - selection_helper.getRecord( - dataGridConfiguration, _pressedRowIndex)) { - _removeSelection(record, dataGridConfiguration); + final DataGridRow? pressedRecord = selection_helper.getRecord( + dataGridConfiguration, + _pressedRowIndex, + ); + if (!_selectedRows.contains(pressedRecord)) { + _addSelection(pressedRecord, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: true, + row: pressedRecord, + rowType: RowType.dataRow, + ); + } + + if (removedItems.isNotEmpty) { + for (final DataGridRow items in removedItems) { + if (_selectedRows.contains(items)) { + _removeSelection(items, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: false, + row: items, + rowType: RowType.dataRow, + ); } } - _shiftSelectedRows.removeWhere((DataGridRow record) => - record != - selection_helper.getRecord(dataGridConfiguration, _pressedRowIndex)!); } + if (_pressedRowIndex < currentRecordIndex) { - for (int rowIndex = _pressedRowIndex + 1; - rowIndex <= currentRecordIndex; - rowIndex++) { + for ( + int rowIndex = _pressedRowIndex + 1; + rowIndex <= currentRecordIndex; + rowIndex++ + ) { record = selection_helper.getRecord(dataGridConfiguration, rowIndex); - if (record != null && !_selectedRows.contains(record)) { + if (record != null) { _shiftSelectedRows.add(record); _addSelection(record, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: true, + row: record, + rowType: RowType.dataRow, + ); } } } else if (_pressedRowIndex > currentRecordIndex) { - for (int rowIndex = _pressedRowIndex - 1; - rowIndex >= currentRecordIndex; - rowIndex--) { + for ( + int rowIndex = _pressedRowIndex - 1; + rowIndex >= currentRecordIndex; + rowIndex-- + ) { record = selection_helper.getRecord(dataGridConfiguration, rowIndex); - if (record != null && !_selectedRows.contains(record)) { + if (record != null) { _shiftSelectedRows.add(record); _addSelection(record, dataGridConfiguration); + _raiseCheckboxValueChanged( + value: true, + row: record, + rowType: RowType.dataRow, + ); } } } } List _getAddedItems( - int startIndex, int endIndex, DataGridConfiguration configuration) { + int startIndex, + int endIndex, + DataGridConfiguration configuration, + ) { final List addedItems = []; if (startIndex > endIndex) { final int tempIndex = startIndex; @@ -289,10 +403,14 @@ class RowSelectionManager extends SelectionManagerBase { } void _addSelection( - DataGridRow? record, DataGridConfiguration dataGridConfiguration) { + DataGridRow? record, + DataGridConfiguration dataGridConfiguration, + ) { if (record != null && !_selectedRows.contains(record)) { - final int rowIndex = - selection_helper.resolveToRowIndex(dataGridConfiguration, record); + final int rowIndex = selection_helper.resolveToRowIndex( + dataGridConfiguration, + record, + ); if (!_selectedRows.contains(record)) { _selectedRows.add(record); dataGridConfiguration.controller.selectedRows.add(record); @@ -306,10 +424,14 @@ class RowSelectionManager extends SelectionManagerBase { } void _removeSelection( - DataGridRow? record, DataGridConfiguration dataGridConfiguration) { + DataGridRow? record, + DataGridConfiguration dataGridConfiguration, + ) { if (record != null && _selectedRows.contains(record)) { - final int rowIndex = - selection_helper.resolveToRowIndex(dataGridConfiguration, record); + final int rowIndex = selection_helper.resolveToRowIndex( + dataGridConfiguration, + record, + ); _selectedRows.remove(record); dataGridConfiguration.controller.selectedRows.remove(record); _refreshSelection(); @@ -329,11 +451,18 @@ class RowSelectionManager extends SelectionManagerBase { void _clearSelectedRows(DataGridConfiguration dataGridConfiguration) { if (_selectedRows.isNotEmpty) { - _selectedRows.clear(); + _selectedRows.removeWhere( + (row) => dataGridConfiguration.source.effectiveRows.contains(row), + ); dataGridConfiguration.controller.selectedRows.clear(); _refreshSelection(); dataGridConfiguration.container.isDirty = true; - if (dataGridConfiguration.headerCheckboxState != false) { + // Issue: + // FLUT-6620-The null check operator used on the null value exception occurred + // While headerCheckboxState is in the intermediate state and deselecting all the selected rows in the Datagrid by using DataGridController. + // We have resolved the issue by checking if it's null + if (dataGridConfiguration.headerCheckboxState == null || + dataGridConfiguration.headerCheckboxState!) { _updateCheckboxStateOnHeader(dataGridConfiguration); } } @@ -341,8 +470,11 @@ class RowSelectionManager extends SelectionManagerBase { _clearSelection(dataGridConfiguration); } - void _setRowSelection(int rowIndex, - DataGridConfiguration dataGridConfiguration, bool isRowSelected) { + void _setRowSelection( + int rowIndex, + DataGridConfiguration dataGridConfiguration, + bool isRowSelected, + ) { if (dataGridConfiguration.rowGenerator.items.isEmpty) { return; } @@ -353,7 +485,9 @@ class RowSelectionManager extends SelectionManagerBase { row ..isDirty = true ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( - dataGridConfiguration, row.dataGridRow!) + dataGridConfiguration, + row.dataGridRow!, + ) ..isSelectedRow = isRowSelected; } @@ -361,7 +495,9 @@ class RowSelectionManager extends SelectionManagerBase { } void _clearSelection(DataGridConfiguration dataGridConfiguration) { - _selectedRows.clear(); + _selectedRows.removeWhere( + (row) => dataGridConfiguration.source.effectiveRows.contains(row), + ); dataGridConfiguration.controller.selectedRows.clear(); updateSelectedRow(dataGridConfiguration.controller, null); updateSelectedIndex(dataGridConfiguration.controller, -1); @@ -382,20 +518,30 @@ class RowSelectionManager extends SelectionManagerBase { void _refreshSelection() { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final DataGridRow? _selectedRow = - _selectedRows.isNotEmpty ? _selectedRows.last : null; - final int _recordIndex = _selectedRow == null - ? -1 - : effectiveRows(dataGridConfiguration.source).indexOf(_selectedRow); - updateSelectedRow(dataGridConfiguration.controller, _selectedRow); - updateSelectedIndex(dataGridConfiguration.controller, _recordIndex); + final DataGridRow? selectedRow = _selectedRows.reversed.firstWhereOrNull( + (row) => effectiveRows(dataGridConfiguration.source).contains(row), + ); + final int recordIndex = + selectedRow == null + ? -1 + : dataGridConfiguration.source.groupedColumns.isNotEmpty + ? dataGridConfiguration.group!.displayElements!.grouped.indexOf( + selectedRow, + ) + : effectiveRows(dataGridConfiguration.source).indexOf(selectedRow); + updateSelectedRow(dataGridConfiguration.controller, selectedRow); + updateSelectedIndex(dataGridConfiguration.controller, recordIndex); } void _addCurrentCell( - DataGridRow record, DataGridConfiguration dataGridConfiguration, - {bool isSelectionChanging = false}) { - final int rowIndex = - selection_helper.resolveToRowIndex(dataGridConfiguration, record); + DataGridRow record, + DataGridConfiguration dataGridConfiguration, { + bool isSelectionChanging = false, + }) { + final int rowIndex = selection_helper.resolveToRowIndex( + dataGridConfiguration, + record, + ); if (rowIndex <= grid_helper.getHeaderIndex(dataGridConfiguration)) { return; @@ -403,17 +549,19 @@ class RowSelectionManager extends SelectionManagerBase { if (dataGridConfiguration.currentCell.columnIndex > 0) { dataGridConfiguration.currentCell._moveCurrentCellTo( - dataGridConfiguration, - RowColumnIndex( - rowIndex, dataGridConfiguration.currentCell.columnIndex), - isSelectionChanged: isSelectionChanging); + dataGridConfiguration, + RowColumnIndex(rowIndex, dataGridConfiguration.currentCell.columnIndex), + isSelectionChanged: isSelectionChanging, + ); } else { - final int firstVisibleColumnIndex = - grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + final int firstVisibleColumnIndex = grid_helper.resolveToStartColumnIndex( + dataGridConfiguration, + ); dataGridConfiguration.currentCell._moveCurrentCellTo( - dataGridConfiguration, - RowColumnIndex(rowIndex, firstVisibleColumnIndex), - isSelectionChanged: isSelectionChanging); + dataGridConfiguration, + RowColumnIndex(rowIndex, firstVisibleColumnIndex), + isSelectionChanged: isSelectionChanging, + ); } } @@ -422,12 +570,14 @@ class RowSelectionManager extends SelectionManagerBase { _dataGridStateDetails!(); if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { final RowColumnIndex currentRowColumnIndex = RowColumnIndex( - dataGridConfiguration.currentCell.rowIndex, - dataGridConfiguration.currentCell.columnIndex); + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex, + ); _clearCurrentCell(dataGridConfiguration); dataGridConfiguration.currentCell._updateBorderForMultipleSelection( - dataGridConfiguration, - nextRowColumnIndex: currentRowColumnIndex); + dataGridConfiguration, + nextRowColumnIndex: currentRowColumnIndex, + ); } else { if (dataGridConfiguration.selectionMode != SelectionMode.none) { final DataGridRow? lastRecord = @@ -437,10 +587,10 @@ class RowSelectionManager extends SelectionManagerBase { return; } - final RowColumnIndex _currentRowColumnIndex = + final RowColumnIndex currentRowColumnIndex = _getRowColumnIndexOnModeChanged(dataGridConfiguration, lastRecord); - if (_currentRowColumnIndex.rowIndex <= 0) { + if (currentRowColumnIndex.rowIndex <= 0) { return; } @@ -449,35 +599,43 @@ class RowSelectionManager extends SelectionManagerBase { // index's are same. final CurrentCellManager currentCell = dataGridConfiguration.currentCell; - if (currentCell.rowIndex == _currentRowColumnIndex.rowIndex && - currentCell.columnIndex == _currentRowColumnIndex.columnIndex) { + if (currentCell.rowIndex == currentRowColumnIndex.rowIndex && + currentCell.columnIndex == currentRowColumnIndex.columnIndex) { currentCell._updateCurrentCell( dataGridConfiguration, - _currentRowColumnIndex.rowIndex, - _currentRowColumnIndex.columnIndex, + currentRowColumnIndex.rowIndex, + currentRowColumnIndex.columnIndex, ); } else { currentCell._moveCurrentCellTo( - dataGridConfiguration, - RowColumnIndex(_currentRowColumnIndex.rowIndex, - _currentRowColumnIndex.columnIndex), - isSelectionChanged: true); + dataGridConfiguration, + RowColumnIndex( + currentRowColumnIndex.rowIndex, + currentRowColumnIndex.columnIndex, + ), + isSelectionChanged: true, + ); } } } } RowColumnIndex _getRowColumnIndexOnModeChanged( - DataGridConfiguration dataGridConfiguration, DataGridRow? lastRecord) { + DataGridConfiguration dataGridConfiguration, + DataGridRow? lastRecord, + ) { final int rowIndex = lastRecord == null && _pressedRowColumnIndex.rowIndex > 0 ? _pressedRowColumnIndex.rowIndex : selection_helper.resolveToRowIndex( - dataGridConfiguration, lastRecord!); + dataGridConfiguration, + lastRecord!, + ); - final int columnIndex = _pressedRowColumnIndex.columnIndex != -1 - ? _pressedRowColumnIndex.columnIndex - : grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + final int columnIndex = + _pressedRowColumnIndex.columnIndex != -1 + ? _pressedRowColumnIndex.columnIndex + : grid_helper.resolveToStartColumnIndex(dataGridConfiguration); return RowColumnIndex(rowIndex, columnIndex); } @@ -490,7 +648,8 @@ class RowSelectionManager extends SelectionManagerBase { /// When the selection is applied to a row, we should update the state of the check box also. void _updateCheckboxStateOnHeader( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, + ) { if (!dataGridConfiguration.showCheckboxColumn || !dataGridConfiguration.checkboxColumnSettings.showCheckboxOnHeader || dataGridConfiguration.selectionMode == SelectionMode.none) { @@ -499,7 +658,12 @@ class RowSelectionManager extends SelectionManagerBase { final DataRowBase? headerDataRow = dataGridConfiguration.rowGenerator.items .firstWhereOrNull( - (DataRowBase dataRow) => dataRow.rowType == RowType.headerRow); + (DataRowBase dataRow) => dataRow.rowType == RowType.headerRow, + ); + + final bool isRowInEffectiveList = _selectedRows.any( + (row) => effectiveRows(dataGridConfiguration.source).contains(row), + ); if (headerDataRow == null) { if (dataGridConfiguration.controller.selectedRows.length == @@ -512,10 +676,19 @@ class RowSelectionManager extends SelectionManagerBase { } final DataCellBase? headerDataCell = headerDataRow.visibleColumns - .firstWhereOrNull((DataCellBase cell) => cell.columnIndex == 0); - - if (_selectedRows.isEmpty && - dataGridConfiguration.headerCheckboxState != false) { + .firstWhereOrNull( + (DataCellBase cell) => + cell.columnIndex == + dataGridConfiguration.source.groupedColumns.length, + ); + + // Issue: + // FLUT-6617-The null check operator used on the null value exception occurred + // While selecting and deselecting the same row in the datagrid since the selected rows are empty and headerCheckboxState is null + // We have resolved the issue by checking the if it's null + if ((_selectedRows.isEmpty || !isRowInEffectiveList) && + (dataGridConfiguration.headerCheckboxState == null || + dataGridConfiguration.headerCheckboxState!)) { dataGridConfiguration.headerCheckboxState = false; headerDataCell?.updateColumn(); } else if (dataGridConfiguration.controller.selectedRows.length != @@ -523,8 +696,10 @@ class RowSelectionManager extends SelectionManagerBase { dataGridConfiguration.headerCheckboxState != null) { dataGridConfiguration.headerCheckboxState = null; headerDataCell?.updateColumn(); - } else if (dataGridConfiguration.controller.selectedRows.length == - dataGridConfiguration.source.rows.length && + } else if (((dataGridConfiguration.controller.selectedRows.length == + dataGridConfiguration.source.rows.length) || + (dataGridConfiguration.controller.selectedRows.length == + effectiveRows(dataGridConfiguration.source).length)) && dataGridConfiguration.headerCheckboxState != true) { dataGridConfiguration.headerCheckboxState = true; headerDataCell?.updateColumn(); @@ -540,34 +715,52 @@ class RowSelectionManager extends SelectionManagerBase { return; } final int recordIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, rowColumnIndex.rowIndex); + dataGridConfiguration, + rowColumnIndex.rowIndex, + ); final RowColumnIndex previousRowColumnIndex = RowColumnIndex( - dataGridConfiguration.currentCell.rowIndex, - dataGridConfiguration.currentCell.columnIndex); - if (!dataGridConfiguration.currentCell - ._handlePointerOperation(dataGridConfiguration, rowColumnIndex)) { + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex, + ); + if (!dataGridConfiguration.currentCell._handlePointerOperation( + dataGridConfiguration, + rowColumnIndex, + )) { return; } - if (!dataGridConfiguration.isShiftKeyPressed) { + final bool isShiftPressed = dataGridConfiguration.isShiftKeyPressed; + // Issue: + // Header checkbox selection causes shift selection to fail. + // + // Fix: + // After header checkbox selection, the pressed rowIndex is set to -1. + // To fix this, when performing shift or normal selection, if rowIndex is -1, + // set it to the currently tapped rowIndex. + if (_pressedRowIndex < 0 || !isShiftPressed) { _pressedRowIndex = recordIndex; - _shiftSelectedRows.clear(); - _processSelection( - dataGridConfiguration, rowColumnIndex, previousRowColumnIndex); } - if (dataGridConfiguration.isShiftKeyPressed && - dataGridConfiguration.selectionMode == SelectionMode.multiple && - _selectedRows.contains(selection_helper.getRecord( - dataGridConfiguration, _pressedRowIndex))) { + // Issue: + // FLUT-920028-The HeaderCheckboxState changes when tapping on the header checkbox and normal rows, while setting onSelectionChanging to false and onCurrentCellActivating to true, with the selection mode set to multiple. + // We resolved the issue by verifying that onSelectionChanging is set to false + if (!isShiftPressed && _raiseSelectionChanging()) { + _shiftSelectedRows.clear(); + _processSelection( + dataGridConfiguration, + rowColumnIndex, + previousRowColumnIndex, + ); + } else if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { _processShiftKeySelection(rowColumnIndex, recordIndex); } } void _processSelection( - DataGridConfiguration dataGridConfiguration, - RowColumnIndex nextRowColumnIndex, - RowColumnIndex previousRowColumnIndex) { + DataGridConfiguration dataGridConfiguration, + RowColumnIndex nextRowColumnIndex, + RowColumnIndex previousRowColumnIndex, + ) { // If selectionMode is single. next current cell is going to present in the // same selected row. // In this case, we don't update the whole data row. Instead of that @@ -608,18 +801,27 @@ class RowSelectionManager extends SelectionManagerBase { dataGridConfiguration.selectionMode != SelectionMode.multiple; //If newValue is negative we have clear the whole selection data. - //In multiple case we shouldn't to clear the collection as well - // source properties. - if (newValue == null && canClearSelections()) { - _clearSelectedRow(dataGridConfiguration); + if (newValue == null && _selectedRows.isNotEmpty) { + // If selection mode is multiple we need to clear all the selected rows. + if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { + _clearSelectedRows(dataGridConfiguration); + } else { + _clearSelectedRow(dataGridConfiguration); + } + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + _clearCurrentCell(dataGridConfiguration); + } notifyListeners(); return; } - final int recordIndex = - effectiveRows(dataGridConfiguration.source).indexOf(newValue!); - final int rowIndex = - grid_helper.resolveToRowIndex(dataGridConfiguration, recordIndex); + final int recordIndex = effectiveRows( + dataGridConfiguration.source, + ).indexOf(newValue!); + final int rowIndex = grid_helper.resolveToRowIndex( + dataGridConfiguration, + recordIndex, + ); if (rowIndex < grid_helper.getHeaderIndex(dataGridConfiguration)) { return; @@ -633,15 +835,23 @@ class RowSelectionManager extends SelectionManagerBase { } if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { - _addCurrentCell(newValue, dataGridConfiguration, - isSelectionChanging: true); + _addCurrentCell( + newValue, + dataGridConfiguration, + isSelectionChanging: true, + ); } else { - final int columnIndex = - grid_helper.resolveToStartColumnIndex(dataGridConfiguration); - final int rowIndex = - selection_helper.resolveToRowIndex(dataGridConfiguration, newValue); - dataGridConfiguration.currentCell - ._updateCurrentRowColumnIndex(rowIndex, columnIndex); + final int columnIndex = grid_helper.resolveToStartColumnIndex( + dataGridConfiguration, + ); + final int rowIndex = selection_helper.resolveToRowIndex( + dataGridConfiguration, + newValue, + ); + dataGridConfiguration.currentCell._updateCurrentRowColumnIndex( + rowIndex, + columnIndex, + ); } _addSelection(newValue, dataGridConfiguration); @@ -660,7 +870,7 @@ class RowSelectionManager extends SelectionManagerBase { final int newValue = dataGridConfiguration.controller.selectedIndex; if (effectiveRows(dataGridConfiguration.source).isEmpty || - newValue > effectiveRows(dataGridConfiguration.source).length) { + newValue > effectiveRows(dataGridConfiguration.source).length - 1) { return; } @@ -669,16 +879,29 @@ class RowSelectionManager extends SelectionManagerBase { dataGridConfiguration.selectionMode != SelectionMode.multiple; //If newValue is negative we have to clear the whole selection data. - //In multiple case we shouldn't to clear the collection as - // well source properties. - if (newValue == -1 && canClearSelections()) { - _clearSelectedRow(dataGridConfiguration); + if (newValue == -1 && _selectedRows.isNotEmpty) { + // If selection mode is multiple we need to clear all the selected rows. + if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { + _clearSelectedRows(dataGridConfiguration); + } else { + _clearSelectedRow(dataGridConfiguration); + } + + // Issue: + // FLUT-7123-The current cell is not removed when setting the selected index as -1 through the SelectionController + // We removed the selected rows only when setting the selected index as -1 from the controller + // We have resolved the issue by removing the current cell too. + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + _clearCurrentCell(dataGridConfiguration); + } notifyListeners(); return; } - final DataGridRow? record = - selection_helper.getRecord(dataGridConfiguration, newValue); + final DataGridRow? record = selection_helper.getRecord( + dataGridConfiguration, + newValue, + ); if (record != null && !_selectedRows.contains(record)) { //In multiple case we shouldn't to clear the collection as // well source properties. @@ -687,15 +910,23 @@ class RowSelectionManager extends SelectionManagerBase { } if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { - _addCurrentCell(record, dataGridConfiguration, - isSelectionChanging: true); + _addCurrentCell( + record, + dataGridConfiguration, + isSelectionChanging: true, + ); } else { - final int rowIndex = - selection_helper.resolveToRowIndex(dataGridConfiguration, record); - final int columnIndex = - grid_helper.resolveToStartColumnIndex(dataGridConfiguration); - dataGridConfiguration.currentCell - ._updateCurrentRowColumnIndex(rowIndex, columnIndex); + final int rowIndex = selection_helper.resolveToRowIndex( + dataGridConfiguration, + record, + ); + final int columnIndex = grid_helper.resolveToStartColumnIndex( + dataGridConfiguration, + ); + dataGridConfiguration.currentCell._updateCurrentRowColumnIndex( + rowIndex, + columnIndex, + ); } _addSelection(record, dataGridConfiguration); @@ -713,8 +944,10 @@ class RowSelectionManager extends SelectionManagerBase { return; } - final List newValue = - dataGridConfiguration.controller.selectedRows.toList(growable: false); + final List newValue = dataGridConfiguration + .controller + .selectedRows + .toList(growable: false); if (newValue.isEmpty) { _clearSelectedRows(dataGridConfiguration); @@ -723,8 +956,15 @@ class RowSelectionManager extends SelectionManagerBase { } _clearSelectedRows(dataGridConfiguration); - _selectedRows.addAll(newValue); - dataGridConfiguration.controller.selectedRows.addAll(newValue); + final List visibleRows = + newValue + .where( + (row) => + effectiveRows(dataGridConfiguration.source).contains(row), + ) + .toList(); + _selectedRows.addAll(visibleRows); + dataGridConfiguration.controller.selectedRows.addAll(visibleRows); _refreshSelection(); dataGridConfiguration.container ..isDirty = true @@ -734,16 +974,22 @@ class RowSelectionManager extends SelectionManagerBase { if (dataGridConfiguration.navigationMode == GridNavigationMode.cell && _selectedRows.isNotEmpty) { final DataGridRow lastRecord = _selectedRows.last; - _addCurrentCell(lastRecord, dataGridConfiguration, - isSelectionChanging: true); + _addCurrentCell( + lastRecord, + dataGridConfiguration, + isSelectionChanging: true, + ); } else if (dataGridConfiguration.isDesktop && dataGridConfiguration.navigationMode == GridNavigationMode.row) { final DataGridRow lastRecord = _selectedRows.last; - final int rowIndex = - selection_helper.resolveToRowIndex(dataGridConfiguration, lastRecord); + final int rowIndex = selection_helper.resolveToRowIndex( + dataGridConfiguration, + lastRecord, + ); dataGridConfiguration.currentCell._updateBorderForMultipleSelection( - dataGridConfiguration, - nextRowColumnIndex: RowColumnIndex(rowIndex, -1)); + dataGridConfiguration, + nextRowColumnIndex: RowColumnIndex(rowIndex, -1), + ); } notifyListeners(); @@ -763,25 +1009,32 @@ class RowSelectionManager extends SelectionManagerBase { _clearSelection(dataGridConfiguration); if (dataGridConfiguration.navigationMode == GridNavigationMode.cell && lastRecord != null) { - final RowColumnIndex _currentRowColumnIndex = + final RowColumnIndex currentRowColumnIndex = _getRowColumnIndexOnModeChanged(dataGridConfiguration, lastRecord); - if (_currentRowColumnIndex.rowIndex <= 0) { + if (currentRowColumnIndex.rowIndex <= 0) { return; } - lastRecord = dataGridConfiguration.selectionMode == SelectionMode.single - ? selection_helper.getRecord( - dataGridConfiguration, - grid_helper.resolveToRecordIndex( - dataGridConfiguration, _currentRowColumnIndex.rowIndex)) - : lastRecord; + lastRecord = + dataGridConfiguration.selectionMode == SelectionMode.single + ? selection_helper.getRecord( + dataGridConfiguration, + grid_helper.resolveToRecordIndex( + dataGridConfiguration, + currentRowColumnIndex.rowIndex, + ), + ) + : lastRecord; dataGridConfiguration.currentCell._moveCurrentCellTo( - dataGridConfiguration, - RowColumnIndex(_currentRowColumnIndex.rowIndex, - _currentRowColumnIndex.columnIndex), - isSelectionChanged: true); + dataGridConfiguration, + RowColumnIndex( + currentRowColumnIndex.rowIndex, + currentRowColumnIndex.columnIndex, + ), + isSelectionChanged: true, + ); } if (lastRecord != null) { @@ -789,11 +1042,14 @@ class RowSelectionManager extends SelectionManagerBase { } } else if (dataGridConfiguration.isDesktop && dataGridConfiguration.selectionMode == SelectionMode.multiple) { - final RowColumnIndex currentRowColumnIndex = - RowColumnIndex(dataGridConfiguration.currentCell.rowIndex, -1); + final RowColumnIndex currentRowColumnIndex = RowColumnIndex( + dataGridConfiguration.currentCell.rowIndex, + -1, + ); dataGridConfiguration.currentCell._updateBorderForMultipleSelection( - dataGridConfiguration, - nextRowColumnIndex: currentRowColumnIndex); + dataGridConfiguration, + nextRowColumnIndex: currentRowColumnIndex, + ); } } @@ -807,35 +1063,48 @@ class RowSelectionManager extends SelectionManagerBase { } final int rowIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, currentCell.rowIndex); + dataGridConfiguration, + currentCell.rowIndex, + ); if (recordLength > 0 && rowIndex >= recordLength && currentCell.rowIndex != -1) { final int startRowIndex = selection_helper.getPreviousRowIndex( - dataGridConfiguration, currentCell.rowIndex); - currentCell._moveCurrentCellTo(dataGridConfiguration, - RowColumnIndex(startRowIndex, currentCell.columnIndex), - needToUpdateColumn: false); + dataGridConfiguration, + currentCell.rowIndex, + ); + currentCell._moveCurrentCellTo( + dataGridConfiguration, + RowColumnIndex(startRowIndex, currentCell.columnIndex), + needToUpdateColumn: false, + ); _refreshSelection(); } final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, currentCell.columnIndex); + dataGridConfiguration, + currentCell.columnIndex, + ); if (columnLength > 0 && columnIndex >= columnLength && currentCell.columnIndex != -1) { final int startColumnIndex = selection_helper.getPreviousColumnIndex( - dataGridConfiguration, currentCell.columnIndex); - currentCell._moveCurrentCellTo(dataGridConfiguration, - RowColumnIndex(currentCell.rowIndex, startColumnIndex), - needToUpdateColumn: false); + dataGridConfiguration, + currentCell.columnIndex, + ); + currentCell._moveCurrentCellTo( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, startColumnIndex), + needToUpdateColumn: false, + ); } } - void _updateSelectionController( - {bool isSelectionModeChanged = false, - bool isNavigationModeChanged = false, - bool isDataSourceChanged = false}) { + void _updateSelectionController({ + bool isSelectionModeChanged = false, + bool isNavigationModeChanged = false, + bool isDataSourceChanged = false, + }) { if (isDataSourceChanged) { _onDataSourceChanged(); } @@ -849,10 +1118,11 @@ class RowSelectionManager extends SelectionManagerBase { } } - void _handleSelectionPropertyChanged( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { + void _handleSelectionPropertyChanged({ + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false, + }) { switch (propertyName) { case 'selectedIndex': onSelectedIndexChanged(); @@ -870,18 +1140,21 @@ class RowSelectionManager extends SelectionManagerBase { //KeyNavigation @override - void handleKeyEvent(RawKeyEvent keyEvent) { + Future handleKeyEvent(KeyEvent keyEvent) async { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); if (dataGridConfiguration.currentCell.isEditing && keyEvent.logicalKey != LogicalKeyboardKey.escape) { - if (!dataGridConfiguration.currentCell - .canSubmitCell(dataGridConfiguration)) { + if (!await dataGridConfiguration.currentCell.canSubmitCell( + dataGridConfiguration, + )) { return; } - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true, + ); } if (keyEvent.logicalKey == LogicalKeyboardKey.tab) { @@ -933,7 +1206,7 @@ class RowSelectionManager extends SelectionManagerBase { } if (keyEvent.logicalKey == LogicalKeyboardKey.keyA) { - if (keyEvent.isControlPressed) { + if (HardwareKeyboard.instance.isControlPressed) { _processSelectedAll(); } } @@ -946,12 +1219,14 @@ class RowSelectionManager extends SelectionManagerBase { if (dataGridConfiguration.allowEditing && dataGridConfiguration.navigationMode == GridNavigationMode.cell) { final RowColumnIndex rowColumnIndex = RowColumnIndex( - dataGridConfiguration.currentCell.rowIndex, - dataGridConfiguration.currentCell.columnIndex); + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex, + ); dataGridConfiguration.currentCell.onCellBeginEdit( - editingRowColumnIndex: rowColumnIndex, - isProgrammatic: true, - needToResolveIndex: false); + editingRowColumnIndex: rowColumnIndex, + isProgrammatic: true, + needToResolveIndex: false, + ); } } @@ -959,65 +1234,95 @@ class RowSelectionManager extends SelectionManagerBase { if (dataGridConfiguration.allowEditing && dataGridConfiguration.navigationMode == GridNavigationMode.cell && dataGridConfiguration.currentCell.isEditing) { - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, isCellCancelEdit: true); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + isCellCancelEdit: true, + ); } } } void _processEndKey( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, + KeyEvent keyEvent, + ) { final CurrentCellManager currentCell = dataGridConfiguration.currentCell; - final int lastCellIndex = - selection_helper.getLastCellIndex(dataGridConfiguration); + final int lastCellIndex = selection_helper.getLastCellIndex( + dataGridConfiguration, + ); final bool needToScrollToMinOrMaxExtent = dataGridConfiguration.container.extentWidth > - dataGridConfiguration.viewWidth; + dataGridConfiguration.viewWidth; if (needToScrollToMinOrMaxExtent) { - selection_helper.scrollInViewFromLeft(dataGridConfiguration, - needToScrollMaxExtent: true); + selection_helper.scrollInViewFromLeft( + dataGridConfiguration, + needToScrollMaxExtent: true, + ); } - if (keyEvent.isControlPressed && + if ((dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && keyEvent.logicalKey != LogicalKeyboardKey.arrowRight) { - final int lastRowIndex = - selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); - selection_helper.scrollInViewFromTop(dataGridConfiguration, - needToScrollToMaxExtent: true); + final int lastRowIndex = selection_helper.getLastNavigatingRowIndex( + dataGridConfiguration, + ); + selection_helper.scrollInViewFromTop( + dataGridConfiguration, + needToScrollToMaxExtent: true, + ); _processSelectionAndCurrentCell( - dataGridConfiguration, RowColumnIndex(lastRowIndex, lastCellIndex)); + dataGridConfiguration, + RowColumnIndex(lastRowIndex, lastCellIndex), + ); } else { - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(currentCell.rowIndex, lastCellIndex)); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, lastCellIndex), + ); } } void _processHomeKey( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, + KeyEvent keyEvent, + ) { final CurrentCellManager currentCell = dataGridConfiguration.currentCell; - final int firstCellIndex = - selection_helper.getFirstCellIndex(dataGridConfiguration); + final int firstCellIndex = selection_helper.getFirstCellIndex( + dataGridConfiguration, + ); final bool needToScrollToMinOrMaxExtend = dataGridConfiguration.container.extentWidth > - dataGridConfiguration.viewWidth; + dataGridConfiguration.viewWidth; if (needToScrollToMinOrMaxExtend) { - selection_helper.scrollInViewFromRight(dataGridConfiguration, - needToScrollToMinExtent: true); + selection_helper.scrollInViewFromRight( + dataGridConfiguration, + needToScrollToMinExtent: true, + ); } - if (keyEvent.isControlPressed && + if ((dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && keyEvent.logicalKey != LogicalKeyboardKey.arrowLeft) { - final int firstRowIndex = - selection_helper.getFirstNavigatingRowIndex(dataGridConfiguration); - selection_helper.scrollInViewFromDown(dataGridConfiguration, - needToScrollToMinExtent: true); + final int firstRowIndex = selection_helper.getFirstNavigatingRowIndex( + dataGridConfiguration, + ); + selection_helper.scrollInViewFromDown( + dataGridConfiguration, + needToScrollToMinExtent: true, + ); _processSelectionAndCurrentCell( - dataGridConfiguration, RowColumnIndex(firstRowIndex, firstCellIndex)); + dataGridConfiguration, + RowColumnIndex(firstRowIndex, firstCellIndex), + ); } else { - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(currentCell.rowIndex, firstCellIndex)); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, firstCellIndex), + ); } } @@ -1027,8 +1332,10 @@ class RowSelectionManager extends SelectionManagerBase { final CurrentCellManager currentCell = dataGridConfiguration.currentCell; final int index = selection_helper.getNextPageIndex(dataGridConfiguration); if (currentCell.rowIndex != index && index != -1) { - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(index, currentCell.columnIndex)); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(index, currentCell.columnIndex), + ); } } @@ -1036,91 +1343,140 @@ class RowSelectionManager extends SelectionManagerBase { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; - final int index = - selection_helper.getPreviousPageIndex(dataGridConfiguration); + final int index = selection_helper.getPreviousPageIndex( + dataGridConfiguration, + ); if (currentCell.rowIndex != index) { - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(index, currentCell.columnIndex)); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(index, currentCell.columnIndex), + ); } } - void _processKeyDown(RawKeyEvent keyEvent) { + void _processKeyDown(KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; final int nextRowIndex = selection_helper.getNextRowIndex( - dataGridConfiguration, currentCell.rowIndex); - final int lastRowIndex = - selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); - int nextColumnIndex = currentCell.columnIndex; - + dataGridConfiguration, + currentCell.rowIndex, + ); + final int lastRowIndex = selection_helper.getLastNavigatingRowIndex( + dataGridConfiguration, + ); + int nextColumnIndex = selection_helper.getDownKeyColumnIndex( + dataGridConfiguration, + currentCell, + ); if (nextColumnIndex <= 0) { - nextColumnIndex = - selection_helper.getFirstCellIndex(dataGridConfiguration); + nextColumnIndex = selection_helper.getFirstCellIndex( + dataGridConfiguration, + ); } if (nextRowIndex > lastRowIndex || currentCell.rowIndex == nextRowIndex) { return; } - if (keyEvent.isControlPressed) { - selection_helper.scrollInViewFromTop(dataGridConfiguration, - needToScrollToMaxExtent: true); + if (dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { + selection_helper.scrollInViewFromTop( + dataGridConfiguration, + needToScrollToMaxExtent: true, + ); _processSelectionAndCurrentCell( - dataGridConfiguration, RowColumnIndex(lastRowIndex, nextColumnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + dataGridConfiguration, + RowColumnIndex(lastRowIndex, nextColumnIndex), + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed, + ); } else { _processSelectionAndCurrentCell( - dataGridConfiguration, RowColumnIndex(nextRowIndex, nextColumnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + dataGridConfiguration, + RowColumnIndex(nextRowIndex, nextColumnIndex), + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed, + ); } } - void _processKeyUp(RawKeyEvent keyEvent) { + void _processKeyUp(KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; final int previousRowIndex = selection_helper.getPreviousRowIndex( - dataGridConfiguration, currentCell.rowIndex); + dataGridConfiguration, + currentCell.rowIndex, + ); + final int columnIndex = selection_helper.getUpKeyColumnIndex( + dataGridConfiguration, + currentCell, + ); if (previousRowIndex == currentCell.rowIndex) { return; } - if (keyEvent.isControlPressed) { - final int firstRowIndex = - selection_helper.getFirstRowIndex(dataGridConfiguration); - selection_helper.scrollInViewFromDown(dataGridConfiguration, - needToScrollToMinExtent: true); - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(firstRowIndex, currentCell.columnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + if (dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { + final int firstRowIndex = selection_helper.getFirstRowIndex( + dataGridConfiguration, + ); + selection_helper.scrollInViewFromDown( + dataGridConfiguration, + needToScrollToMinExtent: true, + ); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(firstRowIndex, columnIndex), + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed, + ); } else { - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(previousRowIndex, currentCell.columnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(previousRowIndex, columnIndex), + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed, + ); } } void _processKeyRight( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, + KeyEvent keyEvent, + ) { if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { return; } final CurrentCellManager currentCell = dataGridConfiguration.currentCell; - final int lastCellIndex = - selection_helper.getLastCellIndex(dataGridConfiguration); + + // Need to ignore arrow right key for caption summary row. + if (currentCell.dataCell?.dataRow != null && + currentCell.dataCell!.dataRow!.rowType == + RowType.captionSummaryCoveredRow) { + return; + } + + final int lastCellIndex = selection_helper.getLastCellIndex( + dataGridConfiguration, + ); int nextCellIndex; // Need to get previous column index only if the control key is // pressed in RTL mode since it will perform the home key event. - if (keyEvent.isControlPressed && + if ((dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && dataGridConfiguration.textDirection == TextDirection.rtl) { nextCellIndex = selection_helper.getPreviousColumnIndex( - dataGridConfiguration, currentCell.columnIndex); + dataGridConfiguration, + currentCell.columnIndex, + ); } else { nextCellIndex = selection_helper.getNextColumnIndex( - dataGridConfiguration, currentCell.columnIndex); + dataGridConfiguration, + currentCell.columnIndex, + ); } if (currentCell.rowIndex <= @@ -1130,16 +1486,20 @@ class RowSelectionManager extends SelectionManagerBase { return; } - if (keyEvent.isControlPressed) { + if (dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { if (dataGridConfiguration.textDirection == TextDirection.rtl) { _processHomeKey(dataGridConfiguration, keyEvent); } else { _processEndKey(dataGridConfiguration, keyEvent); } } else { - currentCell._processCurrentCell(dataGridConfiguration, - RowColumnIndex(currentCell.rowIndex, nextCellIndex), - isSelectionChanged: true); + currentCell._processCurrentCell( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, nextCellIndex), + isSelectionChanged: true, + ); if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { notifyListeners(); } @@ -1147,22 +1507,38 @@ class RowSelectionManager extends SelectionManagerBase { } void _processKeyLeft( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, + KeyEvent keyEvent, + ) { if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { return; } final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + + // Need to ignore arrow left key for caption summary row. + if (currentCell.dataCell?.dataRow != null && + currentCell.dataCell!.dataRow!.rowType == + RowType.captionSummaryCoveredRow) { + return; + } + int previousCellIndex; // Need to get next column index only if the control key is // pressed in RTL mode since it will perform the end key event. - if (keyEvent.isControlPressed && + if ((dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && dataGridConfiguration.textDirection == TextDirection.rtl) { previousCellIndex = selection_helper.getNextColumnIndex( - dataGridConfiguration, currentCell.columnIndex); + dataGridConfiguration, + currentCell.columnIndex, + ); } else { previousCellIndex = selection_helper.getPreviousColumnIndex( - dataGridConfiguration, currentCell.columnIndex); + dataGridConfiguration, + currentCell.columnIndex, + ); } if (currentCell.rowIndex <= @@ -1172,79 +1548,111 @@ class RowSelectionManager extends SelectionManagerBase { return; } - if (keyEvent.isControlPressed) { + if (dataGridConfiguration.isMacPlatform + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { if (dataGridConfiguration.textDirection == TextDirection.rtl) { _processEndKey(dataGridConfiguration, keyEvent); } else { _processHomeKey(dataGridConfiguration, keyEvent); } } else { - currentCell._processCurrentCell(dataGridConfiguration, - RowColumnIndex(currentCell.rowIndex, previousCellIndex), - isSelectionChanged: true); + currentCell._processCurrentCell( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, previousCellIndex), + isSelectionChanged: true, + ); if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { notifyListeners(); } } } - void _processKeyTab(RawKeyEvent keyEvent) { + void _processKeyTab(KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; - final int lastCellIndex = - selection_helper.getLastCellIndex(dataGridConfiguration); - int firstCellIndex = - selection_helper.getFirstCellIndex(dataGridConfiguration); - final int firstRowIndex = - selection_helper.getFirstRowIndex(dataGridConfiguration); + final int lastCellIndex = selection_helper.getLastCellIndex( + dataGridConfiguration, + ); + int firstCellIndex = selection_helper.getFirstCellIndex( + dataGridConfiguration, + ); + + final int firstRowIndex = selection_helper.getFirstRowIndex( + dataGridConfiguration, + ); + + final bool isCaptionSummaryCoveredRow = + currentCell.dataCell?.dataRow != null && + currentCell.dataCell!.dataRow!.rowType == + RowType.captionSummaryCoveredRow; if (dataGridConfiguration.navigationMode == GridNavigationMode.row || (currentCell.rowIndex < 0 && currentCell.columnIndex < 0)) { _processSelectionAndCurrentCell( - dataGridConfiguration, RowColumnIndex(firstRowIndex, firstCellIndex)); + dataGridConfiguration, + RowColumnIndex(firstRowIndex, firstCellIndex), + ); notifyListeners(); return; } final bool needToScrollToMinOrMaxExtend = dataGridConfiguration.container.extentWidth > - dataGridConfiguration.viewWidth; + dataGridConfiguration.viewWidth; - if (keyEvent.isShiftPressed) { + if (HardwareKeyboard.instance.isShiftPressed) { if (currentCell.columnIndex == firstCellIndex && currentCell.rowIndex == firstRowIndex) { return; } - if (currentCell.columnIndex == firstCellIndex) { + if (currentCell.columnIndex == firstCellIndex || + isCaptionSummaryCoveredRow) { final int previousRowIndex = selection_helper.getPreviousRowIndex( - dataGridConfiguration, currentCell.rowIndex); + dataGridConfiguration, + currentCell.rowIndex, + ); if (needToScrollToMinOrMaxExtend) { - selection_helper.scrollInViewFromLeft(dataGridConfiguration, - needToScrollMaxExtent: needToScrollToMinOrMaxExtend); + selection_helper.scrollInViewFromLeft( + dataGridConfiguration, + needToScrollMaxExtent: needToScrollToMinOrMaxExtend, + ); } - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(previousRowIndex, lastCellIndex)); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(previousRowIndex, lastCellIndex), + ); } else { _processKeyLeft(dataGridConfiguration, keyEvent); } } else { - if (currentCell.columnIndex == lastCellIndex) { + if (currentCell.columnIndex == lastCellIndex || + (dataGridConfiguration.source.groupedColumns.isNotEmpty && + isCaptionSummaryCoveredRow)) { final int nextRowIndex = selection_helper.getNextRowIndex( - dataGridConfiguration, currentCell.rowIndex); + dataGridConfiguration, + currentCell.rowIndex, + ); if (needToScrollToMinOrMaxExtend) { - selection_helper.scrollInViewFromRight(dataGridConfiguration, - needToScrollToMinExtent: needToScrollToMinOrMaxExtend); + selection_helper.scrollInViewFromRight( + dataGridConfiguration, + needToScrollToMinExtent: needToScrollToMinOrMaxExtend, + ); + } + if (dataGridConfiguration.source.groupedColumns.isEmpty) { + firstCellIndex = + (nextRowIndex == currentCell.rowIndex && + lastCellIndex == currentCell.columnIndex) + ? currentCell.columnIndex + : firstCellIndex; } - firstCellIndex = (nextRowIndex == currentCell.rowIndex && - lastCellIndex == currentCell.columnIndex) - ? currentCell.columnIndex - : firstCellIndex; - - _processSelectionAndCurrentCell(dataGridConfiguration, - RowColumnIndex(nextRowIndex, firstCellIndex)); + _processSelectionAndCurrentCell( + dataGridConfiguration, + RowColumnIndex(nextRowIndex, firstCellIndex), + ); } else { _processKeyRight(dataGridConfiguration, keyEvent); } @@ -1267,9 +1675,21 @@ class RowSelectionManager extends SelectionManagerBase { if (_raiseSelectionChanging(oldItems: removeItems, newItems: addedItems)) { dataGridConfiguration.controller.selectedRows.clear(); - _selectedRows.addAll(effectiveRows(dataGridConfiguration.source)); - dataGridConfiguration.controller.selectedRows - .addAll(effectiveRows(dataGridConfiguration.source)); + _selectedRows.addAll( + effectiveRows( + dataGridConfiguration.source, + ).where((row) => !_selectedRows.contains(row)), + ); + dataGridConfiguration.controller.selectedRows.addAll( + effectiveRows(dataGridConfiguration.source), + ); + + _raiseCheckboxValueChanged( + value: true, + row: null, + rowType: RowType.headerRow, + ); + _refreshSelection(); dataGridConfiguration.container ..isDirty = true @@ -1290,37 +1710,52 @@ class RowSelectionManager extends SelectionManagerBase { final CurrentCellManager currentCell = dataGridConfiguration.currentCell; final int recordIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, currentCell.rowIndex); + dataGridConfiguration, + currentCell.rowIndex, + ); _shiftSelectedRows.clear(); _pressedRowIndex = recordIndex; _applySelection( - RowColumnIndex(currentCell.rowIndex, currentCell.columnIndex)); + RowColumnIndex(currentCell.rowIndex, currentCell.columnIndex), + ); } void _processSelectionAndCurrentCell( - DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex, - {bool isShiftKeyPressed = false, - bool isProgrammatic = false}) { + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, { + bool isShiftKeyPressed = false, + bool isProgrammatic = false, + }) { final RowColumnIndex previousRowColumnIndex = RowColumnIndex( - dataGridConfiguration.currentCell.rowIndex, - dataGridConfiguration.currentCell.columnIndex); + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex, + ); if (isProgrammatic) { - dataGridConfiguration.currentCell - ._moveCurrentCellTo(dataGridConfiguration, rowColumnIndex); + dataGridConfiguration.currentCell._moveCurrentCellTo( + dataGridConfiguration, + rowColumnIndex, + ); } else { - dataGridConfiguration.currentCell - ._processCurrentCell(dataGridConfiguration, rowColumnIndex); + dataGridConfiguration.currentCell._processCurrentCell( + dataGridConfiguration, + rowColumnIndex, + ); } if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { dataGridConfiguration.currentCell._updateBorderForMultipleSelection( - dataGridConfiguration, - nextRowColumnIndex: rowColumnIndex, - previousRowColumnIndex: previousRowColumnIndex); + dataGridConfiguration, + nextRowColumnIndex: rowColumnIndex, + previousRowColumnIndex: previousRowColumnIndex, + ); if (isShiftKeyPressed) { - _processSelection( - dataGridConfiguration, rowColumnIndex, previousRowColumnIndex); + _processShiftKeySelection( + rowColumnIndex, + grid_helper.resolveToRecordIndex( + dataGridConfiguration, + rowColumnIndex.rowIndex, + ), + ); } else { notifyListeners(); } @@ -1329,9 +1764,10 @@ class RowSelectionManager extends SelectionManagerBase { _pressedRowColumnIndex = rowColumnIndex; } - bool _raiseSelectionChanging( - {List oldItems = const [], - List newItems = const []}) { + bool _raiseSelectionChanging({ + List oldItems = const [], + List newItems = const [], + }) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); if (dataGridConfiguration.onSelectionChanging == null) { @@ -1341,9 +1777,10 @@ class RowSelectionManager extends SelectionManagerBase { return dataGridConfiguration.onSelectionChanging!(newItems, oldItems); } - void _raiseSelectionChanged( - {List oldItems = const [], - List newItems = const []}) { + void _raiseSelectionChanged({ + List oldItems = const [], + List newItems = const [], + }) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); if (dataGridConfiguration.onSelectionChanged == null) { @@ -1353,6 +1790,27 @@ class RowSelectionManager extends SelectionManagerBase { dataGridConfiguration.onSelectionChanged!(newItems, oldItems); } + void _raiseCheckboxValueChanged({ + required bool value, + required DataGridRow? row, + required RowType rowType, + }) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (!dataGridConfiguration.showCheckboxColumn || + dataGridConfiguration.onCheckboxValueChanged == null) { + return; + } + + dataGridConfiguration.onCheckboxValueChanged!( + DataGridCheckboxValueChangedDetails( + value: value, + row: row, + rowType: rowType, + ), + ); + } + /// Refresh the selection state in header and cell check box when selection /// gets cleared. void _refreshCheckboxSelection() { @@ -1360,6 +1818,8 @@ class RowSelectionManager extends SelectionManagerBase { } } +final FocusScopeNode _focusScopeNode = FocusScopeNode(); + /// A class that can be used to manage the current cell operations in the /// [SfDataGrid]. class CurrentCellManager { @@ -1381,36 +1841,57 @@ class CurrentCellManager { /// Indicate the any [DataGridCell] is in editing state. bool isEditing = false; - bool _handlePointerOperation(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { + bool _handlePointerOperation( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, + ) { if (dataGridConfiguration.allowSwiping) { dataGridConfiguration.container.resetSwipeOffset(); } - final RowColumnIndex previousRowColumnIndex = - RowColumnIndex(rowIndex, columnIndex); + final RowColumnIndex previousRowColumnIndex = RowColumnIndex( + rowIndex, + columnIndex, + ); if (!rowColumnIndex.equals(previousRowColumnIndex) && dataGridConfiguration.navigationMode != GridNavigationMode.row) { if (!_raiseCurrentCellActivating(rowColumnIndex)) { return false; } - _setCurrentCell(dataGridConfiguration, rowColumnIndex.rowIndex, - rowColumnIndex.columnIndex); + _setCurrentCell( + dataGridConfiguration, + rowColumnIndex.rowIndex, + rowColumnIndex.columnIndex, + ); _raiseCurrentCellActivated(previousRowColumnIndex); } else if (dataGridConfiguration.navigationMode == GridNavigationMode.row && rowIndex != rowColumnIndex.rowIndex) { _updateCurrentRowColumnIndex( - rowColumnIndex.rowIndex, rowColumnIndex.columnIndex); - _updateBorderForMultipleSelection(dataGridConfiguration, - previousRowColumnIndex: previousRowColumnIndex, - nextRowColumnIndex: rowColumnIndex); + rowColumnIndex.rowIndex, + rowColumnIndex.columnIndex, + ); + _updateBorderForMultipleSelection( + dataGridConfiguration, + previousRowColumnIndex: previousRowColumnIndex, + nextRowColumnIndex: rowColumnIndex, + ); + } else if (dataGridConfiguration.navigationMode == GridNavigationMode.row && + dataGridConfiguration.selectionMode == SelectionMode.singleDeselect) { + // Issue: FLUT-858176-The current row gets removed while selecting a row and then deselecting it. + // Fix: We have to set the current row and column index as -1 when the selection mode is singleDeselect. + // This will ensure that the current row and column index will be not be cleared + // when the selection mode is multiple or single. + _updateCurrentRowColumnIndex(-1, -1); } return true; } - void _setCurrentCell(DataGridConfiguration dataGridConfiguration, - int rowIndex, int columnIndex, - [bool needToUpdateColumn = true]) { + void _setCurrentCell( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + int columnIndex, [ + bool needToUpdateColumn = true, + ]) { if (this.rowIndex == rowIndex && this.columnIndex == columnIndex) { return; } @@ -1418,14 +1899,23 @@ class CurrentCellManager { _removeCurrentCell(dataGridConfiguration, needToUpdateColumn); _updateCurrentRowColumnIndex(rowIndex, columnIndex); _updateCurrentCell( - dataGridConfiguration, rowIndex, columnIndex, needToUpdateColumn); - } - - void _updateCurrentCell(DataGridConfiguration dataGridConfiguration, - int rowIndex, int columnIndex, - [bool needToUpdateColumn = true]) { - final DataRowBase? dataRowBase = - _getDataRow(dataGridConfiguration, rowIndex); + dataGridConfiguration, + rowIndex, + columnIndex, + needToUpdateColumn, + ); + } + + void _updateCurrentCell( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + int columnIndex, [ + bool needToUpdateColumn = true, + ]) { + final DataRowBase? dataRowBase = _getDataRow( + dataGridConfiguration, + rowIndex, + ); if (dataRowBase != null && needToUpdateColumn) { final DataCellBase? dataCellBase = _getDataCell(dataRowBase, columnIndex); if (dataCellBase != null) { @@ -1434,43 +1924,48 @@ class CurrentCellManager { dataCellBase.updateColumn(); } } - - updateCurrentCellIndex( - dataGridConfiguration.controller, - grid_helper.resolveToRecordRowColumnIndex( - dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex))); } - void _removeCurrentCell(DataGridConfiguration dataGridConfiguration, - [bool needToUpdateColumn = true]) { + void _removeCurrentCell( + DataGridConfiguration dataGridConfiguration, [ + bool needToUpdateColumn = true, + ]) { if (rowIndex == -1 && columnIndex == -1) { return; } - final DataRowBase? dataRowBase = - _getDataRow(dataGridConfiguration, rowIndex); - if (dataRowBase != null && needToUpdateColumn) { - final DataCellBase? dataCellBase = _getDataCell(dataRowBase, columnIndex); - if (dataCellBase != null) { - setCurrentCellDirty(dataRowBase, dataCellBase, false); - dataCellBase.updateColumn(); + // Remove the current cell from rows where the `isCurrentRow` property is true. + if (needToUpdateColumn && + dataGridConfiguration.rowGenerator.items.isNotEmpty) { + for (final DataRowBase dataRowBase + in dataGridConfiguration.rowGenerator.items) { + if (dataRowBase.isCurrentRow || dataRowBase.rowIndex == rowIndex) { + final dataCellBase = _getDataCell(dataRowBase, columnIndex); + if (dataCellBase != null) { + setCurrentCellDirty(dataRowBase, dataCellBase, false); + dataCellBase.updateColumn(); + } + } } } - _updateCurrentRowColumnIndex(-1, -1); - updateCurrentCellIndex( - dataGridConfiguration.controller, RowColumnIndex(-1, -1)); } DataRowBase? _getDataRow( - DataGridConfiguration dataGridConfiguration, int rowIndex) { + DataGridConfiguration dataGridConfiguration, + int rowIndex, + ) { final List dataRows = dataGridConfiguration.rowGenerator.items; if (dataRows.isEmpty) { return null; } - return dataRows - .firstWhereOrNull((DataRowBase row) => row.rowIndex == rowIndex); + // If attempt to obtain a current row after calling the `refreshView` method, + // all the row indexes will be -1 in the `items` collection. So, need to + // consider the `isCurrentRow` property additionally to get the current row. + return dataRows.firstWhereOrNull( + (DataRowBase row) => row.rowIndex == rowIndex || row.isCurrentRow, + ); } DataCellBase? _getDataCell(DataRowBase dataRow, int columnIndex) { @@ -1478,8 +1973,17 @@ class CurrentCellManager { return null; } + if (dataRow.rowType == RowType.captionSummaryCoveredRow) { + return dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => + columnIndex >= dataCell.columnIndex && + dataCell.columnIndex + dataCell.columnSpan <= columnIndex, + ); + } + return dataRow.visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); + (DataCellBase dataCell) => dataCell.columnIndex == columnIndex, + ); } void _updateCurrentRowColumnIndex(int rowIndex, int columnIndex) { @@ -1489,7 +1993,10 @@ class CurrentCellManager { /// Sets the current data cell as dirty to refresh the cell. void setCurrentCellDirty( - DataRowBase? dataRow, DataCellBase? dataCell, bool enableCurrentCell) { + DataRowBase? dataRow, + DataCellBase? dataCell, + bool enableCurrentCell, + ) { dataCell?.isCurrentCell = enableCurrentCell; dataCell?.isDirty = true; dataRow?.isCurrentRow = enableCurrentCell; @@ -1504,11 +2011,15 @@ class CurrentCellManager { final RowColumnIndex newRowColumnIndex = grid_helper .resolveToRecordRowColumnIndex(dataGridConfiguration, rowColumnIndex); - final RowColumnIndex oldRowColumnIndex = - grid_helper.resolveToRecordRowColumnIndex( - dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex)); + final RowColumnIndex oldRowColumnIndex = grid_helper + .resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(rowIndex, columnIndex), + ); return dataGridConfiguration.onCurrentCellActivating!( - newRowColumnIndex, oldRowColumnIndex); + newRowColumnIndex, + oldRowColumnIndex, + ); } void _raiseCurrentCellActivated(RowColumnIndex previousRowColumnIndex) { @@ -1517,32 +2028,48 @@ class CurrentCellManager { return; } - final RowColumnIndex newRowColumnIndex = - grid_helper.resolveToRecordRowColumnIndex( - dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex)); - final RowColumnIndex oldRowColumnIndex = - grid_helper.resolveToRecordRowColumnIndex( - dataGridConfiguration, previousRowColumnIndex); + final RowColumnIndex newRowColumnIndex = grid_helper + .resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(rowIndex, columnIndex), + ); + final RowColumnIndex oldRowColumnIndex = grid_helper + .resolveToRecordRowColumnIndex( + dataGridConfiguration, + previousRowColumnIndex, + ); dataGridConfiguration.onCurrentCellActivated!( - newRowColumnIndex, oldRowColumnIndex); + newRowColumnIndex, + oldRowColumnIndex, + ); } - void _moveCurrentCellTo(DataGridConfiguration dataGridConfiguration, - RowColumnIndex nextRowColumnIndex, - {bool isSelectionChanged = false, bool needToUpdateColumn = true}) { + void _moveCurrentCellTo( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex nextRowColumnIndex, { + bool isSelectionChanged = false, + bool needToUpdateColumn = true, + }) { final RowColumnIndex previousRowColumnIndex = RowColumnIndex( - dataGridConfiguration.currentCell.rowIndex, - dataGridConfiguration.currentCell.columnIndex); + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex, + ); _scrollVertical(dataGridConfiguration, nextRowColumnIndex); _scrollHorizontal(dataGridConfiguration, nextRowColumnIndex); if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { - _setCurrentCell(dataGridConfiguration, nextRowColumnIndex.rowIndex, - nextRowColumnIndex.columnIndex, needToUpdateColumn); + _setCurrentCell( + dataGridConfiguration, + nextRowColumnIndex.rowIndex, + nextRowColumnIndex.columnIndex, + needToUpdateColumn, + ); } else { _updateCurrentRowColumnIndex( - nextRowColumnIndex.rowIndex, nextRowColumnIndex.columnIndex); + nextRowColumnIndex.rowIndex, + nextRowColumnIndex.columnIndex, + ); } if (dataGridConfiguration.selectionMode != SelectionMode.none && @@ -1553,95 +2080,131 @@ class CurrentCellManager { if (rowSelectionController is RowSelectionManager) { rowSelectionController .._processSelection( - dataGridConfiguration, nextRowColumnIndex, previousRowColumnIndex) + dataGridConfiguration, + nextRowColumnIndex, + previousRowColumnIndex, + ) .._pressedRowColumnIndex = nextRowColumnIndex; } } } - void _processCurrentCell(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex, - {bool isSelectionChanged = false}) { + void _processCurrentCell( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, { + bool isSelectionChanged = false, + }) { if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { - _moveCurrentCellTo(dataGridConfiguration, rowColumnIndex, - isSelectionChanged: isSelectionChanged); + _moveCurrentCellTo( + dataGridConfiguration, + rowColumnIndex, + isSelectionChanged: isSelectionChanged, + ); return; } if (_raiseCurrentCellActivating(rowColumnIndex)) { - _moveCurrentCellTo(dataGridConfiguration, rowColumnIndex, - isSelectionChanged: isSelectionChanged); + _moveCurrentCellTo( + dataGridConfiguration, + rowColumnIndex, + isSelectionChanged: isSelectionChanged, + ); _raiseCurrentCellActivated(rowColumnIndex); } } - void _scrollHorizontal(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { + void _scrollHorizontal( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, + ) { if (rowColumnIndex.columnIndex < columnIndex) { if (selection_helper.needToScrollLeft( - dataGridConfiguration, rowColumnIndex)) { - selection_helper.scrollInViewFromRight(dataGridConfiguration, - previousCellIndex: rowColumnIndex.columnIndex); + dataGridConfiguration, + rowColumnIndex, + )) { + selection_helper.scrollInViewFromRight( + dataGridConfiguration, + previousCellIndex: rowColumnIndex.columnIndex, + ); } } if (rowColumnIndex.columnIndex > columnIndex) { if (selection_helper.needToScrollRight( - dataGridConfiguration, rowColumnIndex)) { - selection_helper.scrollInViewFromLeft(dataGridConfiguration, - nextCellIndex: rowColumnIndex.columnIndex); + dataGridConfiguration, + rowColumnIndex, + )) { + selection_helper.scrollInViewFromLeft( + dataGridConfiguration, + nextCellIndex: rowColumnIndex.columnIndex, + ); } } } - void _scrollVertical(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex) { + void _scrollVertical( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, + ) { if (rowColumnIndex.rowIndex < rowIndex) { if (selection_helper.needToScrollUp( - dataGridConfiguration, rowColumnIndex.rowIndex)) { - selection_helper.scrollInViewFromDown(dataGridConfiguration, - previousRowIndex: rowColumnIndex.rowIndex); + dataGridConfiguration, + rowColumnIndex.rowIndex, + )) { + selection_helper.scrollInViewFromDown( + dataGridConfiguration, + previousRowIndex: rowColumnIndex.rowIndex, + ); } } if (rowColumnIndex.rowIndex > rowIndex) { if (selection_helper.needToScrollDown( - dataGridConfiguration, rowColumnIndex.rowIndex)) { - selection_helper.scrollInViewFromTop(dataGridConfiguration, - nextRowIndex: rowColumnIndex.rowIndex); + dataGridConfiguration, + rowColumnIndex.rowIndex, + )) { + selection_helper.scrollInViewFromTop( + dataGridConfiguration, + nextRowIndex: rowColumnIndex.rowIndex, + ); } } } void _updateBorderForMultipleSelection( - DataGridConfiguration dataGridConfiguration, - {RowColumnIndex? previousRowColumnIndex, - RowColumnIndex? nextRowColumnIndex}) { + DataGridConfiguration dataGridConfiguration, { + RowColumnIndex? previousRowColumnIndex, + RowColumnIndex? nextRowColumnIndex, + }) { if (dataGridConfiguration.isDesktop && dataGridConfiguration.navigationMode == GridNavigationMode.row && dataGridConfiguration.selectionMode == SelectionMode.multiple) { if (previousRowColumnIndex != null) { - dataGridConfiguration.currentCell + dataGridConfiguration + .currentCell ._getDataRow(dataGridConfiguration, previousRowColumnIndex.rowIndex) ?.isDirty = true; } if (nextRowColumnIndex != null) { - final int firstVisibleColumnIndex = - grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + final int firstVisibleColumnIndex = grid_helper + .resolveToStartColumnIndex(dataGridConfiguration); _updateCurrentRowColumnIndex( - nextRowColumnIndex.rowIndex >= 0 - ? nextRowColumnIndex.rowIndex - : rowIndex, - nextRowColumnIndex.columnIndex >= 0 - ? nextRowColumnIndex.columnIndex - : firstVisibleColumnIndex); - dataGridConfiguration.currentCell + nextRowColumnIndex.rowIndex >= 0 + ? nextRowColumnIndex.rowIndex + : rowIndex, + nextRowColumnIndex.columnIndex >= 0 + ? nextRowColumnIndex.columnIndex + : firstVisibleColumnIndex, + ); + dataGridConfiguration + .currentCell ._getDataRow( - dataGridConfiguration, - nextRowColumnIndex.rowIndex >= 0 - ? nextRowColumnIndex.rowIndex - : rowIndex) + dataGridConfiguration, + nextRowColumnIndex.rowIndex >= 0 + ? nextRowColumnIndex.rowIndex + : rowIndex, + ) ?.isDirty = true; } } @@ -1649,19 +2212,24 @@ class CurrentCellManager { // ------------------------------Editing------------------------------------- /// Called when the editing is begin to the data cell. - void onCellBeginEdit( - {DataCellBase? editingDataCell, - RowColumnIndex? editingRowColumnIndex, - bool isProgrammatic = false, - bool needToResolveIndex = true}) { + void onCellBeginEdit({ + DataCellBase? editingDataCell, + RowColumnIndex? editingRowColumnIndex, + bool isProgrammatic = false, + bool needToResolveIndex = true, + }) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); - final bool checkEditingIsEnabled = dataGridConfiguration.allowEditing && + final bool checkEditingIsEnabled = + dataGridConfiguration.allowEditing && dataGridConfiguration.selectionMode != SelectionMode.none && dataGridConfiguration.navigationMode != GridNavigationMode.row; bool checkDataCellIsValidForEditing(DataCellBase? editingDataCell) => editingDataCell != null && + editingDataCell.dataRow!.rowType != RowType.captionSummaryCoveredRow && + editingDataCell.cellType != CellType.captionSummaryCell && + editingDataCell.cellType != CellType.indentCell && editingDataCell.gridColumn!.allowEditing && !editingDataCell.isEditing && editingDataCell.renderer != null && @@ -1684,10 +2252,13 @@ class CurrentCellManager { // When editing is initiate from the f2 key, we need not to to resolve // the editing row column index because its already resolved based on the // SfDataGrid. - editingRowColumnIndex = needToResolveIndex - ? grid_helper.resolveToRowColumnIndex( - dataGridConfiguration, editingRowColumnIndex) - : editingRowColumnIndex; + editingRowColumnIndex = + needToResolveIndex + ? grid_helper.resolveToRowColumnIndex( + dataGridConfiguration, + editingRowColumnIndex, + ) + : editingRowColumnIndex; if (editingRowColumnIndex.rowIndex.isNegative || editingRowColumnIndex.columnIndex.isNegative || @@ -1698,21 +2269,56 @@ class CurrentCellManager { return; } + // In programmatic begin edit, need to update current cell when the + // dataCell doesn't contain the proper current cell index. So, we commonly + // update the current cell here for programmatic and F2 key to begin edit + // the cell. + void setCurrentCell() { + final DataRowBase? dataRow = _getDataRow( + dataGridConfiguration, + editingRowColumnIndex!.rowIndex, + ); + if (dataRow != null) { + dataCell = _getDataCell( + dataRow, + grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + editingRowColumnIndex.columnIndex, + ), + ); + } else { + return; + } + } + // If the editing is initiate from f2 key, need not to process the // handleTap. if (needToResolveIndex) { - dataGridConfiguration.rowSelectionManager - .handleTap(editingRowColumnIndex); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + editingRowColumnIndex.columnIndex = grid_helper + .resolveToScrollColumnIndex( + dataGridConfiguration, + editingRowColumnIndex.columnIndex, + ); + } + dataGridConfiguration.rowSelectionManager.handleTap( + editingRowColumnIndex, + ); + + // In programmatic begin edit, if the `editingRowColumnIndex` has valid + // row and column index and the current cell has a previous current cell + // value, need to update the current cell based on the + // `editingRowColumnIndex` property. + if (dataCell != null && + !editingRowColumnIndex.equals( + RowColumnIndex(dataCell!.rowIndex, dataCell!.columnIndex), + )) { + setCurrentCell(); + } } else { // Need to skip the editing when current cell is not in view and we // process initiate the editing from f2 key. - final DataRowBase? dataRow = - _getDataRow(dataGridConfiguration, editingRowColumnIndex.rowIndex); - if (dataRow != null) { - dataCell = _getDataCell(dataRow, editingRowColumnIndex.columnIndex); - } else { - return; - } + setCurrentCell(); } editingDataCell = dataCell; @@ -1723,8 +2329,9 @@ class CurrentCellManager { } editingRowColumnIndex = grid_helper.resolveToRecordRowColumnIndex( - dataGridConfiguration, - RowColumnIndex(editingDataCell!.rowIndex, editingDataCell.columnIndex)); + dataGridConfiguration, + RowColumnIndex(editingDataCell!.rowIndex, editingDataCell.columnIndex), + ); if (editingRowColumnIndex.rowIndex.isNegative || editingRowColumnIndex.columnIndex.isNegative) { @@ -1732,18 +2339,22 @@ class CurrentCellManager { } final bool beginEdit = _raiseCellBeginEdit( - dataGridConfiguration, editingRowColumnIndex, editingDataCell); + dataGridConfiguration, + editingRowColumnIndex, + editingDataCell, + ); if (beginEdit) { - void submitCell() { - onCellSubmit(dataGridConfiguration); + Future submitCell() async { + await onCellSubmit(dataGridConfiguration); } final Widget? child = dataGridConfiguration.source.buildEditWidget( - editingDataCell.dataRow!.dataGridRow!, - editingRowColumnIndex, - editingDataCell.gridColumn!, - submitCell); + editingDataCell.dataRow!.dataGridRow!, + editingRowColumnIndex, + editingDataCell.gridColumn!, + submitCell, + ); /// If child is null, we will not initiate the editing if (child != null) { @@ -1751,21 +2362,50 @@ class CurrentCellManager { /// To bring the focus automatically to editing widget. /// canRequestFocus need to set true to auto detect the focus /// User need to set the autoFocus to true in their editable widget. - editingDataCell.editingWidget = - FocusScope(canRequestFocus: true, child: child); + editingDataCell.editingWidget = FocusScope( + canRequestFocus: true, + node: _focusScopeNode, + onFocusChange: (bool details) async { + /// We should not allow the focus to the other widgets + /// when the cell is in the edit mode and return false from the canSubmitCell + /// So, we need to request the focus here. + /// Also, if we return false from the canSubmitCell method and tap other cells + /// We need to retain the focus on the text field instead of losing focus. + /// + // Issue: + // FLUT-7120-The focus did not go to the other widgets when DataGrid's current cell is in edit mode. + // We have checked whether the current cell is editing or not based on the `isCurrentCellInEditing` property. + // In this case, it is true. So we fixed it by checking the value of the `canCellSubmit` method. + if (!_focusScopeNode.hasFocus && + !dataGridConfiguration.dataGridFocusNode!.hasFocus && + !await canSubmitCell(dataGridConfiguration)) { + _focusScopeNode.requestFocus(); + } + }, + child: child, + ); editingDataCell.isEditing = editingDataCell.dataRow!.isEditing = isEditing = true; - notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, - rowColumnIndex: editingRowColumnIndex, propertyName: 'editing'); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + rowColumnIndex: editingRowColumnIndex, + propertyName: 'editing', + ); } } } - bool _raiseCellBeginEdit(DataGridConfiguration dataGridConfiguration, - RowColumnIndex rowColumnIndex, DataCellBase dataCell) { + bool _raiseCellBeginEdit( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, + DataCellBase dataCell, + ) { return dataGridConfiguration.source.onCellBeginEdit( - dataCell.dataRow!.dataGridRow!, rowColumnIndex, dataCell.gridColumn!); + dataCell.dataRow!.dataGridRow!, + rowColumnIndex, + dataCell.gridColumn!, + ); } /// Help to end-edit editable widget and refresh the [DataGridCell]. @@ -1789,10 +2429,12 @@ class CurrentCellManager { /// 1) _onCellSubmit is call from handleDataGridSource we no need to call the /// _notifyDataGridPropertyChangeListeners to refresh twice.By, set value false /// it will skip the refreshing. - void onCellSubmit(DataGridConfiguration dataGridConfiguration, - {bool isCellCancelEdit = false, - bool cancelCanSubmitCell = false, - bool canRefresh = true}) { + Future onCellSubmit( + DataGridConfiguration dataGridConfiguration, { + bool isCellCancelEdit = false, + bool cancelCanSubmitCell = false, + bool canRefresh = true, + }) async { if (!isEditing) { return; } @@ -1810,9 +2452,10 @@ class CurrentCellManager { } if (isEditing) { - final RowColumnIndex rowColumnIndex = - grid_helper.resolveToRecordRowColumnIndex(dataGridConfiguration, - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex)); + RowColumnIndex rowColumnIndex = grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + ); if (rowColumnIndex.rowIndex.isNegative || rowColumnIndex.columnIndex.isNegative) { @@ -1834,34 +2477,74 @@ class CurrentCellManager { /// moving to other cell or another row. so we need to skip the /// canCellSubmit method calling once again if (!cancelCanSubmitCell) { - canSubmitCell = dataGridConfiguration.source - .canSubmitCell(dataGridRow, rowColumnIndex, dataCell.gridColumn!); + canSubmitCell = await dataGridConfiguration.source.canSubmitCell( + dataGridRow, + rowColumnIndex, + dataCell.gridColumn!, + ); } else { canSubmitCell = true; } if (canSubmitCell) { resetEditing(); - dataGridConfiguration.source - .onCellSubmit(dataGridRow, rowColumnIndex, dataCell.gridColumn!); + await dataGridConfiguration.source.onCellSubmit( + dataGridRow, + rowColumnIndex, + dataCell.gridColumn!, + ); + + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + rowColumnIndex: rowColumnIndex, + propertyName: 'editing', + ); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + updateDataSource(dataGridConfiguration.source, true); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } } } else { resetEditing(); dataGridConfiguration.source.onCellCancelEdit( - dataGridRow, rowColumnIndex, dataCell.gridColumn!); + dataGridRow, + rowColumnIndex, + dataCell.gridColumn!, + ); } if (canRefresh) { /// Refresh the visible [DataRow]'s on editing the [DataCell] when - /// sorting enabled - if (dataGridConfiguration.allowSorting) { + /// sorting or filtering is enabled. + if (dataGridConfiguration.allowSorting || + dataGridConfiguration.allowFiltering) { + final DataGridRow? row = grid_helper.getDataRow( + dataGridConfiguration, + rowColumnIndex.rowIndex, + ); + if (row == null) { + return; + } updateDataSource(dataGridConfiguration.source); + final int rowIndex = effectiveRows( + dataGridConfiguration.source, + ).indexOf(row); + rowColumnIndex = RowColumnIndex(rowIndex, rowColumnIndex.columnIndex); + if (dataGridConfiguration.source.filterConditions.isNotEmpty) { + dataGridConfiguration.container.updateRowAndColumnCount(); + } dataGridConfiguration.container ..updateDataGridRows(dataGridConfiguration) ..isDirty = true; } - notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, - rowColumnIndex: rowColumnIndex, propertyName: 'editing'); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + rowColumnIndex: rowColumnIndex, + propertyName: 'editing', + ); } // Allow focus only if any data cell is in editing state and the @@ -1875,17 +2558,21 @@ class CurrentCellManager { } DataRowBase? _getEditingRow(DataGridConfiguration dataGridConfiguration) { - return dataGridConfiguration.rowGenerator.items - .firstWhereOrNull((DataRowBase dataRow) => dataRow.isEditing); + return dataGridConfiguration.rowGenerator.items.firstWhereOrNull( + (DataRowBase dataRow) => dataRow.isEditing, + ); } DataCellBase? _getEditingCell(DataRowBase dataRow) { - return dataRow.visibleColumns - .firstWhereOrNull((DataCellBase dataCell) => dataCell.isEditing); + return dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => dataCell.isEditing, + ); } /// Called when the editing is submitted in the data cell. - bool canSubmitCell(DataGridConfiguration dataGridConfiguration) { + Future canSubmitCell( + DataGridConfiguration dataGridConfiguration, + ) async { final DataRowBase? dataRow = _getEditingRow(dataGridConfiguration); if (dataRow == null) { @@ -1898,9 +2585,11 @@ class CurrentCellManager { return false; } - final RowColumnIndex rowColumnIndex = - grid_helper.resolveToRecordRowColumnIndex(dataGridConfiguration, - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex)); + final RowColumnIndex rowColumnIndex = grid_helper + .resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + ); if (rowColumnIndex.rowIndex.isNegative || rowColumnIndex.columnIndex.isNegative) { @@ -1909,14 +2598,20 @@ class CurrentCellManager { final DataGridRow dataGridRow = dataCell.dataRow!.dataGridRow!; - return dataGridConfiguration.source - .canSubmitCell(dataGridRow, rowColumnIndex, dataCell.gridColumn!); + return dataGridConfiguration.source.canSubmitCell( + dataGridRow, + rowColumnIndex, + dataCell.gridColumn!, + ); } } /// -void onRowColumnChanged(DataGridConfiguration dataGridConfiguration, - int recordLength, int columnLength) { +void onRowColumnChanged( + DataGridConfiguration dataGridConfiguration, + int recordLength, + int columnLength, +) { if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { final RowSelectionManager rowSelectionManager = dataGridConfiguration.rowSelectionManager as RowSelectionManager; @@ -1931,8 +2626,9 @@ void removeUnWantedDataGridRows(DataGridConfiguration dataGridConfiguration) { final List duplicateSelectedRows = rowSelectionManager._selectedRows.toList(); for (final DataGridRow selectedRow in duplicateSelectedRows) { - final int rowIndex = - effectiveRows(dataGridConfiguration.source).indexOf(selectedRow); + final int rowIndex = effectiveRows( + dataGridConfiguration.source, + ).indexOf(selectedRow); if (rowIndex.isNegative) { rowSelectionManager._selectedRows.remove(selectedRow); dataGridConfiguration.controller.selectedRows.remove(selectedRow); @@ -1941,53 +2637,65 @@ void removeUnWantedDataGridRows(DataGridConfiguration dataGridConfiguration) { } /// -void handleSelectionPropertyChanged( - {required DataGridConfiguration dataGridConfiguration, - RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { +void handleSelectionPropertyChanged({ + required DataGridConfiguration dataGridConfiguration, + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false, +}) { if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { final RowSelectionManager rowSelectionManager = dataGridConfiguration.rowSelectionManager as RowSelectionManager; rowSelectionManager._handleSelectionPropertyChanged( - propertyName: propertyName, - rowColumnIndex: rowColumnIndex, - recalculateRowHeight: recalculateRowHeight); + propertyName: propertyName, + rowColumnIndex: rowColumnIndex, + recalculateRowHeight: recalculateRowHeight, + ); } } /// -void updateSelectionController( - {required DataGridConfiguration dataGridConfiguration, - bool isSelectionModeChanged = false, - bool isNavigationModeChanged = false, - bool isDataSourceChanged = false}) { +void updateSelectionController({ + required DataGridConfiguration dataGridConfiguration, + bool isSelectionModeChanged = false, + bool isNavigationModeChanged = false, + bool isDataSourceChanged = false, +}) { if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { final RowSelectionManager rowSelectionManager = dataGridConfiguration.rowSelectionManager as RowSelectionManager; rowSelectionManager._updateSelectionController( - isDataSourceChanged: isDataSourceChanged, - isNavigationModeChanged: isNavigationModeChanged, - isSelectionModeChanged: isSelectionModeChanged); + isDataSourceChanged: isDataSourceChanged, + isNavigationModeChanged: isNavigationModeChanged, + isSelectionModeChanged: isSelectionModeChanged, + ); } } /// Ensures the selection and current cell update in the [RowSelectionManager]. void processSelectionAndCurrentCell( - DataGridConfiguration dataGridConfiguration, RowColumnIndex rowColumnIndex, - {bool isShiftKeyPressed = false, bool isProgrammatic = false}) { + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, { + bool isShiftKeyPressed = false, + bool isProgrammatic = false, +}) { if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { final RowSelectionManager rowSelectionManager = dataGridConfiguration.rowSelectionManager as RowSelectionManager; rowSelectionManager._processSelectionAndCurrentCell( - dataGridConfiguration, rowColumnIndex, - isShiftKeyPressed: isShiftKeyPressed, isProgrammatic: isProgrammatic); + dataGridConfiguration, + rowColumnIndex, + isShiftKeyPressed: isShiftKeyPressed, + isProgrammatic: isProgrammatic, + ); } } /// Need to clarify bool isSelectedRow( - DataGridConfiguration dataGridConfiguration, DataGridRow dataGridRow) { + DataGridConfiguration dataGridConfiguration, + DataGridRow dataGridRow, +) { if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { final RowSelectionManager rowSelectionManager = dataGridConfiguration.rowSelectionManager as RowSelectionManager; @@ -1999,15 +2707,20 @@ bool isSelectedRow( /// Set the DataGridStateDetails in SelectionManagerBase void setStateDetailsInSelectionManagerBase( - SelectionManagerBase selectionManagerBase, - DataGridStateDetails dataGridStateDetails) { + SelectionManagerBase selectionManagerBase, + DataGridStateDetails dataGridStateDetails, +) { selectionManagerBase._dataGridStateDetails = dataGridStateDetails; } /// Helps to handle the selection from header and grid cell check box /// interaction. -void handleSelectionFromCheckbox(DataGridConfiguration dataGridConfiguration, - DataCellBase dataCell, bool? oldValue, bool? newValue) { +void handleSelectionFromCheckbox( + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, + bool? oldValue, + bool? newValue, +) { if (dataGridConfiguration.rowSelectionManager is RowSelectionManager && dataGridConfiguration.selectionMode != SelectionMode.none) { final RowSelectionManager rowSelectionManager = @@ -2026,19 +2739,39 @@ void handleSelectionFromCheckbox(DataGridConfiguration dataGridConfiguration, if (oldValue == null || oldValue == false) { dataGridConfiguration.headerCheckboxState = true; dataCell.updateColumn(); + rowSelectionManager._shiftSelectedRows.clear(); + rowSelectionManager._pressedRowIndex = -1; rowSelectionManager._processSelectedAll(); } else if (oldValue) { dataGridConfiguration.headerCheckboxState = false; dataCell.updateColumn(); + + // Issue: + // FLUT-6838-The onSelectionChanged callback is not being called with deselected rows + // while deselecting through the checkbox column header + // We have resolved the issue by creating the list instead of the reference. final List oldSelectedItems = - rowSelectionManager._selectedRows; + rowSelectionManager._selectedRows.toList(); if (rowSelectionManager._raiseSelectionChanging( - newItems: [], oldItems: oldSelectedItems)) { + newItems: [], + oldItems: oldSelectedItems, + )) { + rowSelectionManager._shiftSelectedRows.clear(); + rowSelectionManager._pressedRowIndex = -1; rowSelectionManager._clearSelectedRows(dataGridConfiguration); + rowSelectionManager._raiseCheckboxValueChanged( + value: dataGridConfiguration.headerCheckboxState!, + row: null, + rowType: RowType.headerRow, + ); rowSelectionManager._refreshCheckboxSelection(); rowSelectionManager._raiseSelectionChanged( - oldItems: oldSelectedItems, newItems: []); + oldItems: oldSelectedItems, + newItems: [], + ); } + // Cleared the oldSelectedItems list after the callback is called. + oldSelectedItems.clear(); } } else { dataCell.onTouchUp(); @@ -2046,3 +2779,35 @@ void handleSelectionFromCheckbox(DataGridConfiguration dataGridConfiguration, } } } + +/// Need to refresh the selection state and selected rows after applying and clearing filters. +void refreshSelectedRows(DataGridConfiguration dataGridConfiguration) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + if (dataGridConfiguration.selectionMode != SelectionMode.none && + rowSelectionManager._selectedRows.isNotEmpty) { + // Filter selected rows to include only those that exist in effectiveRows. + List selectedRows = + rowSelectionManager._selectedRows + .where(dataGridConfiguration.source.effectiveRows.contains) + .toList(); + + /// When changing the selection mode to single-row selection after filtering, + /// we need to retain only the last selected row. + if (dataGridConfiguration.selectionMode != SelectionMode.multiple && + selectedRows.isNotEmpty) { + selectedRows = [selectedRows.last]; + rowSelectionManager._selectedRows + ..clear() + ..addAll(selectedRows); + } + + dataGridConfiguration.controller.selectedRows + ..clear() + ..addAll(selectedRows); + rowSelectionManager._refreshSelection(); + if (dataGridConfiguration.showCheckboxColumn) { + rowSelectionManager._updateCheckboxStateOnHeader(dataGridConfiguration); + } + } +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart index 57eb1eabe..46ee012b7 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart @@ -1,11 +1,14 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../../datagrid.dart'; +import '../grid_common/enums.dart'; import '../grid_common/line_size_host.dart'; import '../grid_common/scroll_axis.dart'; +import 'grouping/grouping.dart'; import 'helper/datagrid_configuration.dart'; import 'helper/datagrid_helper.dart' as grid_helper; import 'runtime/cell_renderers.dart'; @@ -19,80 +22,133 @@ import 'widgets/scrollview_widget.dart'; typedef QueryRowHeightCallback = double Function(RowHeightDetails details); /// Signature for [SfDataGrid.onSelectionChanging] callback. -typedef SelectionChangingCallback = bool Function( - List addedRows, List removedRows); +typedef SelectionChangingCallback = + bool Function(List addedRows, List removedRows); /// Signature for [SfDataGrid.onSelectionChanged] callback. -typedef SelectionChangedCallback = void Function( - List addedRows, List removedRows); +typedef SelectionChangedCallback = + void Function(List addedRows, List removedRows); + +/// Signature for [SfDataGrid.onCheckboxValueChanged] callback. +typedef DataGridCheckboxValueChangedCallback = + void Function(DataGridCheckboxValueChangedDetails details); + +/// Signature for [SfDataGrid.onColumnSortChanging] callback. +typedef DataGridColumnSortChangingCallback = + bool Function( + SortColumnDetails? newSortedColumn, + SortColumnDetails? oldSortedColumn, + ); + +/// Signature for [SfDataGrid.onColumnSortChanged] callback. +typedef DataGridColumnSortChangedCallback = + void Function( + SortColumnDetails? newSortedColumn, + SortColumnDetails? oldSortedColumn, + ); /// Signature for [SfDataGrid.onCurrentCellActivating] callback. -typedef CurrentCellActivatingCallback = bool Function( - RowColumnIndex newRowColumnIndex, RowColumnIndex oldRowColumnIndex); +typedef CurrentCellActivatingCallback = + bool Function( + RowColumnIndex newRowColumnIndex, + RowColumnIndex oldRowColumnIndex, + ); /// Signature for [SfDataGrid.onCurrentCellActivated] callback. -typedef CurrentCellActivatedCallback = void Function( - RowColumnIndex newRowColumnIndex, RowColumnIndex oldRowColumnIndex); +typedef CurrentCellActivatedCallback = + void Function( + RowColumnIndex newRowColumnIndex, + RowColumnIndex oldRowColumnIndex, + ); /// Signature for [SfDataGrid.onCellTap] and [SfDataGrid.onCellSecondaryTap] /// callbacks. typedef DataGridCellTapCallback = void Function(DataGridCellTapDetails details); /// Signature for [SfDataGrid.onCellDoubleTap] callback. -typedef DataGridCellDoubleTapCallback = void Function( - DataGridCellDoubleTapDetails details); +typedef DataGridCellDoubleTapCallback = + void Function(DataGridCellDoubleTapDetails details); /// Signature for [SfDataGrid.onCellLongPress] callback. -typedef DataGridCellLongPressCallback = void Function( - DataGridCellLongPressDetails details); +typedef DataGridCellLongPressCallback = + void Function(DataGridCellLongPressDetails details); /// The signature of [DataGridSource.handleLoadMoreRows] function. typedef LoadMoreRows = Future Function(); /// Signature for the [SfDataGrid.loadMoreViewBuilder] function. -typedef LoadMoreViewBuilder = Widget? Function( - BuildContext context, LoadMoreRows loadMoreRows); +typedef LoadMoreViewBuilder = + Widget? Function(BuildContext context, LoadMoreRows loadMoreRows); /// Signature for the [SfDataGrid.onSwipeStart] callback. -typedef DataGridSwipeStartCallback = bool Function( - DataGridSwipeStartDetails swipeStartDetails); +typedef DataGridSwipeStartCallback = + bool Function(DataGridSwipeStartDetails swipeStartDetails); /// Signature for the [SfDataGrid.onSwipeUpdate] callback. -typedef DataGridSwipeUpdateCallback = bool Function( - DataGridSwipeUpdateDetails swipeUpdateDetails); +typedef DataGridSwipeUpdateCallback = + bool Function(DataGridSwipeUpdateDetails swipeUpdateDetails); /// Signature for the [SfDataGrid.onSwipeEnd] callback. -typedef DataGridSwipeEndCallback = void Function( - DataGridSwipeEndDetails swipeEndDetails); +typedef DataGridSwipeEndCallback = + void Function(DataGridSwipeEndDetails swipeEndDetails); /// Holds the arguments for the [SfDataGrid.startSwipeActionsBuilder] callback. -typedef DataGridSwipeActionsBuilder = Widget? Function( - BuildContext context, DataGridRow dataGridRow, int rowIndex); +typedef DataGridSwipeActionsBuilder = + Widget? Function( + BuildContext context, + DataGridRow dataGridRow, + int rowIndex, + ); /// The signature of [DataGridSource.canSubmitCell] and /// [DataGridSource.onCellSubmit] methods. typedef CellSubmit = void Function(); /// Signature for the [SfDataGrid.onColumnResizeStart] callback. -typedef ColumnResizeStartCallback = bool Function( - ColumnResizeStartDetails details); +typedef ColumnResizeStartCallback = + bool Function(ColumnResizeStartDetails details); /// Signature for the [SfDataGrid.onColumnResizeUpdate] callback. -typedef ColumnResizeUpdateCallback = bool Function( - ColumnResizeUpdateDetails details); +typedef ColumnResizeUpdateCallback = + bool Function(ColumnResizeUpdateDetails details); /// Signature for the [SfDataGrid.onColumnResizeEnd] callback. typedef ColumnResizeEndCallback = void Function(ColumnResizeEndDetails details); +/// Signature for the [SfDataGrid.onFilterChanging] callback. +typedef DataGridFilterChangingCallback = + bool Function(DataGridFilterChangeDetails details); + +/// Signature for the [SfDataGrid.onFilterChanged] callback. +typedef DataGridFilterChangedCallback = + void Function(DataGridFilterChangeDetails details); + /// Signature for the [DataGridSourceChangeNotifier] listener. -typedef _DataGridSourceListener = void Function( - {RowColumnIndex? rowColumnIndex}); +typedef _DataGridSourceListener = + void Function({RowColumnIndex? rowColumnIndex}); /// Signature for the [DataGridSourceChangeNotifier] listener. -typedef _DataGridPropertyChangeListener = void Function( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight}); +typedef _DataGridPropertyChangeListener = + void Function({ + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight, + }); + +/// Signature for [SfDataGrid.onColumnDragging] callback. +typedef DataGridColumnDraggingCallback = + bool Function(DataGridColumnDragDetails details); + +/// Signature for [SfDataGrid. columnDragFeedbackBuilder] callback. +typedef ColumnDragFeedbackBuilderCallback = + Widget Function(BuildContext context, GridColumn column); + +/// Signature for [SfDataGrid.groupExpanding], [SfDataGrid.groupCollapsing], callbacks. +typedef GroupChangingCallback = + bool Function(DataGridGroupChangingDetails group); + +/// Signature for [SfDataGrid.groupExpanded], [SfDataGrid.groupCollapsed] callbacks. +typedef GroupChangedCallback = void Function(DataGridGroupChangedDetails group); /// Row configuration and cell data for a [SfDataGrid]. /// @@ -180,8 +236,11 @@ class StackedHeaderRow { /// [StackedHeaderRow] – which provides configuration for stacked header row. class StackedHeaderCell { /// Creates the [StackedHeaderCell] for [StackedHeaderRow]. - StackedHeaderCell( - {this.text, required this.columnNames, required this.child}) { + StackedHeaderCell({ + this.text, + required this.columnNames, + required this.child, + }) { _childColumnIndexes = []; } @@ -226,14 +285,14 @@ class StackedHeaderCell { /// to DataGrid. class GridTableSummaryRow { /// Creates the [GridTableSummaryRow] to the [SfDataGrid]. - GridTableSummaryRow( - {this.title, - this.color, - required this.columns, - required this.position, - this.titleColumnSpan = 0, - this.showSummaryInRow = true}) - : assert(titleColumnSpan >= 0); + GridTableSummaryRow({ + this.title, + this.color, + required this.columns, + required this.position, + this.titleColumnSpan = 0, + this.showSummaryInRow = true, + }) : assert(titleColumnSpan >= 0); /// A string that has the format and summary column information to be /// displayed in row. @@ -273,10 +332,11 @@ class GridTableSummaryRow { /// * [GridTableSummaryRow] – Which provides configuration for table summary row. class GridSummaryColumn { /// Creates the [GridSummaryColumn] to the [GridTableSummaryRow]. - const GridSummaryColumn( - {required this.name, - required this.columnName, - required this.summaryType}); + const GridSummaryColumn({ + required this.name, + required this.columnName, + required this.summaryType, + }); /// Indicates the name of the summary column. final String name; @@ -409,6 +469,9 @@ class SfDataGrid extends StatefulWidget { this.onQueryRowHeight, this.onSelectionChanged, this.onSelectionChanging, + this.onCheckboxValueChanged, + this.onColumnSortChanging, + this.onColumnSortChanged, this.onCurrentCellActivating, this.onCurrentCellActivated, this.onCellTap, @@ -439,6 +502,7 @@ class SfDataGrid extends StatefulWidget { this.onColumnResizeEnd, this.allowEditing = false, this.editingGestureType = EditingGestureType.doubleTap, + this.placeholder, this.footer, this.footerHeight = 49.0, this.showCheckboxColumn = false, @@ -448,11 +512,28 @@ class SfDataGrid extends StatefulWidget { this.shrinkWrapColumns = false, this.shrinkWrapRows = false, this.rowsCacheExtent, - }) : assert(frozenColumnsCount >= 0), - assert(footerFrozenColumnsCount >= 0), - assert(frozenRowsCount >= 0), - assert(footerFrozenRowsCount >= 0), - super(key: key); + this.allowFiltering = false, + this.onFilterChanging, + this.onFilterChanged, + this.checkboxShape, + this.showHorizontalScrollbar = true, + this.showVerticalScrollbar = true, + this.allowColumnsDragging = false, + this.onColumnDragging, + this.columnDragFeedbackBuilder, + this.showColumnHeaderIconOnHover = false, + this.autoExpandGroups = true, + this.allowExpandCollapseGroup = false, + this.groupExpanding, + this.groupExpanded, + this.groupCollapsing, + this.groupCollapsed, + this.groupCaptionTitleFormat = '{ColumnName} : {Key} - {ItemsCount} Items', + }) : assert(frozenColumnsCount >= 0), + assert(footerFrozenColumnsCount >= 0), + assert(frozenRowsCount >= 0), + assert(footerFrozenRowsCount >= 0), + super(key: key); /// The height of each row except the column header. /// @@ -561,6 +642,24 @@ class SfDataGrid extends StatefulWidget { /// Otherwise, return true. final SelectionChangingCallback? onSelectionChanging; + /// Signature for a callback that is called when value of a checkbox in the checkbox column is changed. + final DataGridCheckboxValueChangedCallback? onCheckboxValueChanged; + + /// Signature for a callback that is called after sorting has been completed. + /// + /// This callback is triggered after the [SfDataGrid] has performed the sort + /// operation. + final DataGridColumnSortChangedCallback? onColumnSortChanged; + + /// Signature for a callback that is called when sorting is about to occur. + /// + /// This callback is triggered before the sort operation is performed on + /// the [SfDataGrid]. + /// + /// If the callback returns `true`, the [SfDataGrid] will proceed with + /// sorting. Returning `false` will cancel the sort operation. + final DataGridColumnSortChangingCallback? onColumnSortChanging; + /// The [SelectionManagerBase] used to control the selection operations /// in [SfDataGrid]. /// @@ -1242,7 +1341,6 @@ class SfDataGrid extends StatefulWidget { /// keyboardType: TextInputType.number, /// onChanged: (String value) { /// if (value.isNotEmpty) { - /// print(value); /// newCellValue = int.parse(value); /// } else { /// newCellValue = null; @@ -1371,6 +1469,28 @@ class SfDataGrid extends StatefulWidget { /// * [allowEditing] – This will enable the editing option for cells. final EditingGestureType editingGestureType; + /// The widget to display when the data source of the [SfDataGrid] is empty. + /// + /// This widget is displayed only when the data source does not contain + /// any items. If this property is null, the [SfDataGrid] will remain + /// empty with no widget to indicate the absence of data. + /// + /// ```dart + /// SfDataGrid( + /// source: dataSource, + /// columns: columns, + /// placeholder: Center( + /// child: Text('No data available'), + /// ), + /// ) + /// ``` + /// + /// In the example above, a [Center] widget with a [Text] message is + /// displayed when the data source is empty. + /// + /// Defaults to null. + final Widget? placeholder; + /// The widget to show over the bottom of the [SfDataGrid]. /// /// This footer will be displayed like normal row and shown below to last row. @@ -1545,6 +1665,129 @@ class SfDataGrid extends StatefulWidget { /// animation can be seen when vertical scrolling is perform final int? rowsCacheExtent; + /// Decides whether the UI filtering should be enabled for all the columns. + /// + /// [GridColumn.allowFiltering] has the highest priority over this property. + /// + /// See also, + /// * [SfDataGrid.onFilterChanging] – This callback will be called if the + /// column is being filtered through UI filtering. + /// * [SfDataGrid.onFilterChanged] – This callback will be called if the + /// column is filtered through UI filtering. + /// * [DataGridSource.filterConditions] – This property holds the + /// collection of the filter conditions which are applied for various columns. + final bool allowFiltering; + + /// Called when the filtering is being applied through UI filtering. + /// + /// You can return `false` from this callback to restrict the column from + /// being filtered. + final DataGridFilterChangingCallback? onFilterChanging; + + /// Called after the UI filtering is applied to [SfDataGrid]. + /// + /// This callback will not be triggered when the filter conditions are added + /// programmatically. + final DataGridFilterChangedCallback? onFilterChanged; + + /// The shape of the checkbox. + /// + /// This is applicable for checkbox which is shown when enable the [showCheckboxColumn] property. + /// + /// See also, + /// + /// [Checkbox.shape] + final OutlinedBorder? checkboxShape; + + /// Decides whether the horizontal scrollbar should be shown. + /// Defaults to true. + final bool showHorizontalScrollbar; + + /// Decides whether the vertical scrollbar should be shown. + /// Defaults to true. + final bool showVerticalScrollbar; + + /// Decides whether the column can be dragged and dropped to the required position. + /// + /// Columns will not be automatically reordered from one position to another position. You must use the [SfDataGrid.onColumnDragging] callback. For this, you must maintain the columns in variable and assign to [SfDataGrid.columns] property. Then, you can reorder a column in the collection inside the `setState` method through [SfDataGrid.onColumnDragging] callback. + /// + /// Defaults to false. + /// + /// The following example shows how to reorder the columns, + /// + // @override + // Widget build(BuildContext context) { + // return Scaffold( + // appBar: AppBar( + // title: const Text('Syncfusion Flutter DataGrid'), + // ), + // body: SfDataGrid( + // columns: columns, + // source: employeeDataSource, + // allowColumnsDragging: true, + // onColumnDragging: (DataGridColumnDragDetails details) { + // if (details.action == DataGridColumnDragAction. dropping) { + // setState(() { + // final GridColumn dragColumn = columns[details.from]; + // columns[details.from] = columns[details.to]; + // columns[details.to] = dragColumn; + // }); + // } + // return true; + // }, + // ), + // ); + // } + /// + /// See also, + /// + /// * [SfDataGrid.onColumnDragging] - Used to reorder a column from one position to another position. + /// * [SfDataGrid.columnDragFeedbackBuilder] - Used to show any widget under the pointer when a drag is under way. + final bool allowColumnsDragging; + + /// Called when a column has been dragged and dropped to new location. + final DataGridColumnDraggingCallback? onColumnDragging; + + /// Called to obtain the feedback widget for the column when it is about to drag. + /// If null, a [Text] widget will be loaded by default with the dragging header cell constraints. + final ColumnDragFeedbackBuilderCallback? columnDragFeedbackBuilder; + + /// Decides whether the column header icons should be shown when hovering the header cells. + /// + /// Defaults to false. + final bool showColumnHeaderIconOnHover; + + /// Decides whether the caption summary row should be expanded initially when applying column grouping. + /// + /// The default value is true. + final bool autoExpandGroups; + + /// Checks whether to expand and collapse a specific group interactively by tapping the caption summary row. The [groupExpanderIcon] will be displayed leading in a caption summary row if it is set to true to indicate the group's expand-collapse state. + /// + /// The default value is false. + final bool allowExpandCollapseGroup; + + /// Invoked when a group is being expanded. + /// + /// Return false to restrict a specific group from being expanded. + final GroupChangingCallback? groupExpanding; + + /// Invoked when a group is expanded. + final GroupChangedCallback? groupExpanded; + + /// Invoked when a group is being collapsed. + /// + /// Return false to restrict a specific group from being collapsed. + final GroupChangingCallback? groupCollapsing; + + /// Invoked when a group is collapsed. + final GroupChangedCallback? groupCollapsed; + + /// Displays the title in the group caption summary row using the provided format. + /// + /// The default format is {ColumnName} : {Key} – {ItemsCount} Items. The name of a [ColumnGroup] will be replaced by the {ColumnName}, the key in a caption summary row will be replaced by the {Key}, and the number of items grouped in a given group will be replaced by the {ItemsCount}. + final String groupCaptionTitleFormat; + @override State createState() => SfDataGridState(); } @@ -1568,6 +1811,7 @@ class SfDataGridState extends State TextDirection _textDirection = TextDirection.ltr; SfDataGridThemeData? _dataGridThemeData; DataGridThemeHelper? _dataGridThemeHelper; + SfLocalizations? _localizations; DataGridSource? _source; List? _columns; SelectionManagerBase? _rowSelectionManager; @@ -1575,21 +1819,33 @@ class SfDataGridState extends State Animation? _swipingAnimation; DataGridStateDetails? _dataGridStateDetails; + /// It maintains the actual columns collection without a check box column to verify the column collection changes in the didUpdateWidget. + List? _actualColumns; + + Size? _screenSize; + @override void initState() { _columns = []; + _actualColumns = []; _dataGridConfiguration = DataGridConfiguration(); _dataGridStateDetails = _onDataGridStateDetailsChanged; _dataGridConfiguration.gridPaint = Paint(); _rowGenerator = RowGenerator(dataGridStateDetails: _dataGridStateDetails!); _container = VisualContainerHelper( - rowGenerator: _rowGenerator, - dataGridStateDetails: _dataGridStateDetails!); + rowGenerator: _rowGenerator, + dataGridStateDetails: _dataGridStateDetails!, + ); _swipingAnimationController = AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); + duration: const Duration(milliseconds: 200), + vsync: this, + ); _setUp(); _updateDataGridStateDetails(); + + // To perform sort and filter operations based on the `DataGridSource`. + updateDataSource(_dataGridConfiguration.source); super.initState(); } @@ -1604,9 +1860,11 @@ class SfDataGridState extends State } void _onDataGridThemeDataChanged( - SfDataGridThemeData? newThemeData, ColorScheme? newColorScheme) { + SfDataGridThemeData? newThemeData, + SfColorScheme? newColorScheme, + ) { // Refreshes the data grid rows when the `SfDataGridThemeData` or - // `ThemeData.colorScheme` is changed at runtime. + // `SfColorScheme.colorScheme` is changed at runtime. if (_dataGridThemeData == newThemeData && _dataGridConfiguration.colorScheme == newColorScheme) { return; @@ -1625,11 +1883,13 @@ class SfDataGridState extends State } if (canRefreshView) { - _dataGridThemeHelper = DataGridThemeHelper( - _dataGridThemeData, _dataGridConfiguration.colorScheme); + _dataGridThemeHelper = DataGridThemeHelper(_dataGridThemeData!, context); _dataGridConfiguration.dataGridThemeHelper = _dataGridThemeHelper; _updateDecoration(); _container.refreshViewStyle(); + if (_dataGridConfiguration.container.isGridLoaded) { + _container.updateIndentColumnWidths(_dataGridConfiguration); + } } } @@ -1639,16 +1899,18 @@ class SfDataGridState extends State } _dataGridConfiguration.textScaleFactor = textScaleFactor; - _dataGridConfiguration.headerRowHeight = widget.headerRowHeight.isNaN - ? (_dataGridConfiguration.textScaleFactor > 1.0) - ? _headerRowHeight * _dataGridConfiguration.textScaleFactor - : _headerRowHeight - : widget.headerRowHeight; - _dataGridConfiguration.rowHeight = widget.rowHeight.isNaN - ? (_dataGridConfiguration.textScaleFactor > 1.0) - ? _rowHeight * _dataGridConfiguration.textScaleFactor - : _rowHeight - : widget.rowHeight; + _dataGridConfiguration.headerRowHeight = + widget.headerRowHeight.isNaN + ? (_dataGridConfiguration.textScaleFactor > 1.0) + ? _headerRowHeight * _dataGridConfiguration.textScaleFactor + : _headerRowHeight + : widget.headerRowHeight; + _dataGridConfiguration.rowHeight = + widget.rowHeight.isNaN + ? (_dataGridConfiguration.textScaleFactor > 1.0) + ? _rowHeight * _dataGridConfiguration.textScaleFactor + : _rowHeight + : widget.rowHeight; // Refreshes the default line size, column widths and row heights in initial // `SfDataGrid` build. So, restricts the codes in initial loading by using // the `_container.isGridLoaded` property. @@ -1670,6 +1932,15 @@ class SfDataGridState extends State } } + void _onDataGridLocalizationsChanged(SfLocalizations newLocalizations) { + if (_localizations != newLocalizations) { + _localizations = newLocalizations; + _dataGridConfiguration + ..localizations = newLocalizations + ..dataGridFilterHelper!.advancedFilterHelper.initProperties(); + } + } + void _updateHeaderRowHeight() { final LineSizeCollection lineSizeCollection = _container.columnWidths as LineSizeCollection; @@ -1685,16 +1956,27 @@ class SfDataGridState extends State } void _setUp() { - _initializeDataGridDataSource(); + // Initializes the source + _source = widget.source.._dataGridStateDetails = _dataGridStateDetails; + _addDataGridSourceListeners(); + _initializeCellRendererCollection(); //DataGrid Controller - _controller = _dataGridConfiguration.controller = - widget.controller ?? DataGridController() - .._dataGridStateDetails = _dataGridStateDetails; + _controller = + _dataGridConfiguration.controller = + widget.controller ?? DataGridController() + .._dataGridStateDetails = _dataGridStateDetails; _controller!._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); + _handleDataGridPropertyChangeListeners, + ); + if (widget.verticalScrollController != null) { + _dataGridConfiguration.disposeVerticalScrollController = false; + } + if (widget.horizontalScrollController != null) { + _dataGridConfiguration.disposeHorizontalScrollController = false; + } _dataGridConfiguration.verticalScrollController = widget.verticalScrollController ?? ScrollController(); @@ -1704,23 +1986,41 @@ class SfDataGridState extends State //AutoFit controller initializing _dataGridConfiguration.columnSizer = widget.columnSizer ?? ColumnSizer(); setStateDetailsInColumnSizer( - _dataGridConfiguration.columnSizer, _dataGridStateDetails!); + _dataGridConfiguration.columnSizer, + _dataGridStateDetails!, + ); //CurrentCell Manager initializing - _dataGridConfiguration.currentCell = - CurrentCellManager(_dataGridStateDetails!); + _dataGridConfiguration.currentCell = CurrentCellManager( + _dataGridStateDetails!, + ); + _dataGridConfiguration.dataGridFilterHelper = DataGridFilterHelper( + _dataGridStateDetails!, + ); //Selection Manager initializing - _rowSelectionManager = _dataGridConfiguration.rowSelectionManager = - widget.selectionManager ?? RowSelectionManager(); + _rowSelectionManager = + _dataGridConfiguration.rowSelectionManager = + widget.selectionManager ?? RowSelectionManager(); selection_manager.setStateDetailsInSelectionManagerBase( - _rowSelectionManager!, _dataGridStateDetails!); + _rowSelectionManager!, + _dataGridStateDetails!, + ); + + _controller!._addDataGridPropertyChangeListener( + _handleSelectionPropertyChanged, + ); - _controller! - ._addDataGridPropertyChangeListener(_handleSelectionPropertyChanged); + _dataGridConfiguration.columnResizeController = ColumnResizeController( + dataGridStateDetails: _dataGridStateDetails!, + ); + _dataGridConfiguration + .columnDragAndDropController = ColumnDragAndDropController( + dataGridStateDetails: _dataGridStateDetails!, + ); - _dataGridConfiguration.columnResizeController = - ColumnResizeController(dataGridStateDetails: _dataGridStateDetails!); + // Grouping initializing + _dataGridConfiguration.group = Group(); _initializeProperties(); } @@ -1735,8 +2035,9 @@ class SfDataGridState extends State void _refreshContainerAndView({bool isDataSourceChanged = false}) { if (isDataSourceChanged) { selection_manager.updateSelectionController( - dataGridConfiguration: _dataGridConfiguration, - isDataSourceChanged: isDataSourceChanged); + dataGridConfiguration: _dataGridConfiguration, + isDataSourceChanged: isDataSourceChanged, + ); } _ensureSelectionProperties(); @@ -1775,7 +2076,28 @@ class SfDataGridState extends State _source = widget.source.._dataGridStateDetails = _dataGridStateDetails; _addDataGridSourceListeners(); } - _source?._updateDataSource(); + + // Issue: + // FLUT-7337 - The filter is not functioning properly when changing the source at runtime + // + // Fix: + // The issue occurred because we did not update the source and columns before applying the filter. + // Instead, we updated those properties after applying the filter. + // To fix this issue, we need to reset the source and columns in the _dataGridConfiguration + // before applying the filter when the source is changed at runtime. + _dataGridConfiguration.source = _source!; + + if (_columns != widget.columns) { + _dataGridConfiguration.columns = widget.columns; + } + + /// To achieve grouping for each page + if (_dataGridConfiguration.source.groupedColumns.isNotEmpty && + _dataGridConfiguration.source._pageCount > 0.0) { + _source?._updateDataSource(true); + } else { + _source?._updateDataSource(); + } } void _initializeProperties() { @@ -1785,6 +2107,12 @@ class SfDataGridState extends State ..clear() ..addAll(widget.columns); } + + if (_actualColumns != null) { + _actualColumns! + ..clear() + ..addAll(widget.columns); + } } _updateDataGridStateDetails(); @@ -1798,50 +2126,145 @@ class SfDataGridState extends State _cellRenderers = {}; _cellRenderers['TextField'] = GridCellTextFieldRenderer(); setStateDetailsInCellRendererBase( - _cellRenderers['TextField']!, _dataGridStateDetails!); + _cellRenderers['TextField']!, + _dataGridStateDetails!, + ); _cellRenderers['ColumnHeader'] = GridHeaderCellRenderer(); setStateDetailsInCellRendererBase( - _cellRenderers['ColumnHeader']!, _dataGridStateDetails!); + _cellRenderers['ColumnHeader']!, + _dataGridStateDetails!, + ); _cellRenderers['StackedHeader'] = GridStackedHeaderCellRenderer(); setStateDetailsInCellRendererBase( - _cellRenderers['StackedHeader']!, _dataGridStateDetails!); + _cellRenderers['StackedHeader']!, + _dataGridStateDetails!, + ); _cellRenderers['Checkbox'] = GridCheckboxRenderer(); setStateDetailsInCellRendererBase( - _cellRenderers['Checkbox']!, _dataGridStateDetails!); + _cellRenderers['Checkbox']!, + _dataGridStateDetails!, + ); _cellRenderers['TableSummary'] = GridTableSummaryCellRenderer(); setStateDetailsInCellRendererBase( - _cellRenderers['TableSummary']!, _dataGridStateDetails!); + _cellRenderers['TableSummary']!, + _dataGridStateDetails!, + ); + _cellRenderers['CaptionSummary'] = GridCaptionSummaryCellRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['CaptionSummary']!, + _dataGridStateDetails!, + ); + _cellRenderers['IndentCell'] = GridIndentCellRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['IndentCell']!, + _dataGridStateDetails!, + ); } - void _processCellUpdate(RowColumnIndex rowColumnIndex) { + void _processCellUpdate( + RowColumnIndex rowColumnIndex, { + bool canRefreshGrouping = false, + }) { if (rowColumnIndex != RowColumnIndex(-1, -1)) { - final int rowIndex = grid_helper.resolveToRowIndex( - _dataGridConfiguration, rowColumnIndex.rowIndex); + final bool isGrouping = + _dataGridConfiguration.source.groupedColumns.isNotEmpty; + int rowIndex = 0; final int columnIndex = grid_helper.resolveToScrollColumnIndex( - _dataGridConfiguration, rowColumnIndex.columnIndex); + _dataGridConfiguration, + rowColumnIndex.columnIndex, + ); + if (isGrouping && canRefreshGrouping) { + final DataGridRow row = + _dataGridConfiguration.source.effectiveRows[rowColumnIndex + .rowIndex]; + rowColumnIndex.rowIndex = _dataGridConfiguration + .group! + .displayElements! + .grouped + .indexOf(row); + rowIndex = + rowColumnIndex.rowIndex + + grid_helper.resolveStartIndexBasedOnPosition( + _dataGridConfiguration, + ); + } else { + rowIndex = grid_helper.resolveToRowIndex( + _dataGridConfiguration, + rowColumnIndex.rowIndex, + ); + } + + // Issue: + // FLUT-7231 - Editing of filtered data when paging is applied does not work properly + // + // Fix: + // After editing a filtered record, the collection of effective rows contained an empty record + // or the record is not contained in the collection. + // To fix this issue, we added a check to return immediately if the collection is empty + // or the datagrid row is not available in collection i.e., rowIndex ==-1. + if (rowIndex == -1) { + if (mounted) { + setState(() {}); + } + return; + } final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase dataRow) => dataRow.rowIndex == rowIndex); + (DataRowBase dataRow) => dataRow.rowIndex == rowIndex, + ); if (dataRow == null) { return; } - dataRow.dataGridRow = _source!._effectiveRows[rowColumnIndex.rowIndex]; - dataRow.dataGridRowAdapter = grid_helper.getDataGridRowAdapter( - _dataGridConfiguration, dataRow.dataGridRow!); - final DataCellBase? dataCell = dataRow.visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); + (DataCellBase dataCell) => dataCell.columnIndex == columnIndex, + ); if (dataCell == null) { return; } - setState(() { - _refreshCell(dataCell); - _updateSummaryColumns(columnIndex); - }); + // Need to update the data source; otherwise, the `effectiveRows` collection will not + // be updated based on the changes made in the `rows` collection on the user's end. + updateDataSource(_source!); + + // Issue: + // FLUT-7231-Editing of filtered data when paging is applied does not work properly. + // + // Fix: + // We encountered an issue when attempting to retrieve a row from the data grid using the paginated row index. + // To fix this issue, we implemented a check to determine whether pagination is being used, + // and we now retrieve the data grid row from the paginated effective rows + // instead of the entire set of effective rows in the data grid + final DataGridRow? row = grid_helper.getDataRow( + _dataGridConfiguration, + rowColumnIndex.rowIndex, + ); + if (row == null) { + return; + } + dataRow.dataGridRow = row; + dataRow.dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + _dataGridConfiguration, + dataRow.dataGridRow!, + ); + + if (mounted) { + setState(() { + _refreshCell(dataCell); + _updateSummaryColumns(columnIndex); + }); + } + + if (canRefreshGrouping && + _dataGridConfiguration.source.groupedColumns.isNotEmpty) { + updateDataSource(_dataGridConfiguration.source, true); + notifyDataGridPropertyChangeListeners( + _dataGridStateDetails!().source, + propertyName: 'grouping', + ); + } } } @@ -1852,36 +2275,46 @@ class SfDataGridState extends State } void _updateSummaryColumns(int columnIndex) { - final String columnName = - _dataGridConfiguration.columns[columnIndex].columnName; - if (_dataGridConfiguration.tableSummaryRows.isNotEmpty) { - for (final GridTableSummaryRow tableSummaryRow - in _dataGridConfiguration.tableSummaryRows) { - final GridSummaryColumn? summaryColumn = tableSummaryRow.columns - .firstWhereOrNull( - (GridSummaryColumn column) => column.columnName == columnName); - // Returns if the updated cell doesn't exist in the table summary row. - if (summaryColumn == null) { - return; - } + final int curretColumnIndex = + columnIndex - _dataGridConfiguration.source.groupedColumns.length; + if (curretColumnIndex >= 0 && + curretColumnIndex < _dataGridConfiguration.columns.length) { + final String columnName = + _dataGridConfiguration.columns[curretColumnIndex].columnName; + if (_dataGridConfiguration.tableSummaryRows.isNotEmpty) { + for (final GridTableSummaryRow tableSummaryRow + in _dataGridConfiguration.tableSummaryRows) { + final GridSummaryColumn? summaryColumn = tableSummaryRow.columns + .firstWhereOrNull( + (GridSummaryColumn column) => column.columnName == columnName, + ); + // Returns if the updated cell doesn't exist in the table summary row. + if (summaryColumn == null) { + return; + } - final DataRowBase? summaryDataRow = _rowGenerator.items - .firstWhereOrNull( - (DataRowBase row) => row.tableSummaryRow == tableSummaryRow); - if (summaryDataRow != null) { - for (final DataCellBase column in summaryDataRow.visibleColumns) { - final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( - _dataGridConfiguration, tableSummaryRow); - if (tableSummaryRow.showSummaryInRow || - (titleColumnSpan > 0 && column.columnIndex < titleColumnSpan)) { - if (tableSummaryRow.title != null) { - if (tableSummaryRow.title!.contains(summaryColumn.name)) { - _refreshCell(column); + final DataRowBase? summaryDataRow = _rowGenerator.items + .firstWhereOrNull( + (DataRowBase row) => row.tableSummaryRow == tableSummaryRow, + ); + if (summaryDataRow != null) { + for (final DataCellBase column in summaryDataRow.visibleColumns) { + final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( + _dataGridConfiguration, + tableSummaryRow, + ); + if (tableSummaryRow.showSummaryInRow || + (titleColumnSpan > 0 && + column.columnIndex < titleColumnSpan)) { + if (tableSummaryRow.title != null) { + if (tableSummaryRow.title!.contains(summaryColumn.name)) { + _refreshCell(column); + } } + } else if (column.summaryColumn != null && + column.summaryColumn!.columnName == columnName) { + _refreshCell(column); } - } else if (column.summaryColumn != null && - column.summaryColumn!.columnName == columnName) { - _refreshCell(column); } } } @@ -1889,63 +2322,102 @@ class SfDataGridState extends State } } - void _processUpdateDataSource() { + Future _processUpdateDataSource() async { if (_dataGridConfiguration.source._suspendDataPagerUpdate) { + // Issue: + // Bug 914469: DataGrid rows are not visible when using paging with grouping. + // + // Fix: + // We have reinitialized the grouping to update the rows in the UI while using SfDataGrid.rowsPerPage. + if (_dataGridConfiguration.source.groupedColumns.isNotEmpty && + _dataGridConfiguration.group!.displayElements == null) { + _dataGridConfiguration.group?.initializeTopLevelGroup( + _dataGridConfiguration, + _dataGridConfiguration.autoExpandGroups, + ); + } return; } - setState(() { - // Need to endEdit the editing [DataGridCell] before perform refreshing. - if (_dataGridConfiguration.currentCell.isEditing) { - _dataGridConfiguration.currentCell - .onCellSubmit(_dataGridConfiguration, canRefresh: false); - } + // Resets the editing before processing the `onCellSubmit`. + _processEditing(); + + // Need to endEdit the editing [DataGridCell] before perform refreshing. + if (_dataGridConfiguration.currentCell.isEditing) { + await _dataGridConfiguration.currentCell.onCellSubmit( + _dataGridConfiguration, + canRefresh: false, + ); + } - _initializeDataGridDataSource(); - _dataGridConfiguration.source = _source!; + _initializeDataGridDataSource(); + _dataGridConfiguration.source = _source!; - if (!listEquals(_columns, widget.columns)) { - if (widget.selectionMode != SelectionMode.none && - widget.navigationMode == GridNavigationMode.cell && - _rowSelectionManager != null) { - selection_manager.onRowColumnChanged( - _dataGridConfiguration, -1, widget.columns.length); - } + if (widget.selectionMode != SelectionMode.none) { + selection_manager.removeUnWantedDataGridRows(_dataGridConfiguration); + } + if (widget.selectionMode != SelectionMode.none && + widget.navigationMode == GridNavigationMode.cell && + _rowSelectionManager != null) { + selection_manager.onRowColumnChanged( + _dataGridConfiguration, + widget.source._effectiveRows.length, + -1, + ); + } - _resetColumn(); - } - if (widget.selectionMode != SelectionMode.none) - selection_manager.removeUnWantedDataGridRows(_dataGridConfiguration); + if (!listEquals(_columns, widget.columns)) { if (widget.selectionMode != SelectionMode.none && widget.navigationMode == GridNavigationMode.cell && _rowSelectionManager != null) { selection_manager.onRowColumnChanged( - _dataGridConfiguration, widget.source._effectiveRows.length, -1); - } - - if (widget.allowSwiping) { - _dataGridConfiguration.container.resetSwipeOffset(); + _dataGridConfiguration, + -1, + widget.columns.length, + ); } - - if (widget.footer != null) { - final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => - row.rowType == RowType.footerRow && row.rowIndex >= 0); - if (footerRow != null) { - // Need to reset the old footer row height in rowHeights collection. - _container.rowHeights[footerRow.rowIndex] = - _dataGridConfiguration.rowHeight; + if (_dataGridConfiguration.showCheckboxColumn && + _dataGridConfiguration.checkboxColumnSettings.showCheckboxOnHeader && + _dataGridConfiguration.selectionMode != SelectionMode.none) { + if (_dataGridConfiguration.controller.selectedRows.isEmpty) { + _dataGridConfiguration.headerCheckboxState = false; + } else if (_dataGridConfiguration.controller.selectedRows.length != + effectiveRows(_dataGridConfiguration.source).length) { + _dataGridConfiguration.headerCheckboxState = null; + } else if (_dataGridConfiguration.controller.selectedRows.length == + _dataGridConfiguration.source.rows.length) { + _dataGridConfiguration.headerCheckboxState = true; } } + _resetColumn(); + } - _container - ..updateRowAndColumnCount() - ..refreshView() - ..isDirty = true; + if (widget.allowSwiping) { + _dataGridConfiguration.container.resetSwipeOffset(); + } - // FLUT-3219 Need to refresh the scrolling offsets if the container's - // offsets and the ScrollController's offsets are not identical. - _refreshScrollOffsets(); - }); + if (widget.footer != null) { + final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase row) => + row.rowType == RowType.footerRow && row.rowIndex >= 0, + ); + if (footerRow != null) { + // Need to reset the old footer row height in rowHeights collection. + _container.rowHeights[footerRow.rowIndex] = + _dataGridConfiguration.rowHeight; + } + } + + _container + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + + // FLUT-3219 Need to refresh the scrolling offsets if the container's + // offsets and the ScrollController's offsets are not identical. + _refreshScrollOffsets(); + if (mounted) { + setState(() {}); + } if (_dataGridConfiguration.source.shouldRecalculateColumnWidths()) { resetAutoCalculation(_dataGridConfiguration.columnSizer); } @@ -1976,8 +2448,11 @@ class SfDataGridState extends State if (_dataGridConfiguration.horizontalScrollController != null && _dataGridConfiguration.horizontalScrollController!.hasClients && _dataGridConfiguration.horizontalScrollController!.offset == 0) { - final double maxScrollExtent = _dataGridConfiguration - .horizontalScrollController!.position.maxScrollExtent; + final double maxScrollExtent = + _dataGridConfiguration + .horizontalScrollController! + .position + .maxScrollExtent; if (_dataGridConfiguration.textDirection == TextDirection.ltr && _dataGridConfiguration.container.horizontalOffset > 0.0) { _dataGridConfiguration.container @@ -1995,12 +2470,62 @@ class SfDataGridState extends State } void _processSorting() { - setState(() { - _container - ..updateRowAndColumnCount() - ..refreshView() - ..isDirty = true; - }); + if (mounted) { + setState(() { + _container + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + }); + } + } + + void _processEditing() { + if (!_dataGridConfiguration.currentCell.isEditing) { + return; + } + + final DataRowBase? dataRow = _dataGridConfiguration.rowGenerator.items + .firstWhereOrNull((DataRowBase dataRow) => dataRow.isEditing); + + if (dataRow == null) { + return; + } + + final DataCellBase? dataCell = dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => dataCell.isEditing, + ); + + if (dataCell == null || !dataCell.isEditing) { + return; + } + + final RowColumnIndex rowColumnIndex = grid_helper + .resolveToRecordRowColumnIndex( + _dataGridConfiguration, + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + ); + + /// Issue: + /// FLUT-6409 - Other cells are not moving into edit mode when removing + /// last row and the cell in that row is in edit mode + /// + /// Fix: + /// The issue occurred due to not resetting the editing properties after + /// removing a row or column from the collection. If a row or column has + /// a negative index, that row or column currently does not exist in the + /// data grid. We have fixed the issue by resetting the editing properties. + if (rowColumnIndex.rowIndex.isNegative || + rowColumnIndex.columnIndex.isNegative) { + dataCell.editingWidget = null; + dataCell.isDirty = true; + dataCell.isEditing = dataRow.isEditing = false; + + _dataGridConfiguration.currentCell + ..isEditing = false + ..rowIndex = -1 + ..columnIndex = -1; + } } void _resetColumn({bool clearEditing = true}) { @@ -2015,6 +2540,12 @@ class SfDataGridState extends State } } + if (_actualColumns != null) { + _actualColumns! + ..clear() + ..addAll(widget.columns); + } + for (final DataRowBase dataRow in _rowGenerator.items) { if (!clearEditing && dataRow.isEditing) { return; @@ -2032,48 +2563,63 @@ class SfDataGridState extends State } void _handleListeners() { + // We have cleared the display elements to reinitialize the grouping and update the rows in the UI. + if (_dataGridConfiguration.source.groupedColumns.isNotEmpty) { + _dataGridConfiguration.group!.clearDisplayElements( + _dataGridConfiguration, + ); + } _processUpdateDataSource(); } void _handleNotifyListeners({RowColumnIndex? rowColumnIndex}) { if (rowColumnIndex != null) { - _processCellUpdate(rowColumnIndex); + _processCellUpdate(rowColumnIndex, canRefreshGrouping: true); } if (rowColumnIndex == null) { _processUpdateDataSource(); } } - void _handleDataGridPropertyChangeListeners( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { + Future _handleDataGridPropertyChangeListeners({ + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false, + }) async { if (propertyName == 'refreshRow') { if (rowColumnIndex != null) { // Need to endEdit before refreshing the row. - _dataGridConfiguration.currentCell - .onCellSubmit(_dataGridConfiguration, canRefresh: false); + await _dataGridConfiguration.currentCell.onCellSubmit( + _dataGridConfiguration, + canRefresh: false, + ); final int rowIndex = grid_helper.resolveToRowIndex( - _dataGridConfiguration, rowColumnIndex.rowIndex); + _dataGridConfiguration, + rowColumnIndex.rowIndex, + ); final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase dataRow) => dataRow.rowIndex == rowIndex); + (DataRowBase dataRow) => dataRow.rowIndex == rowIndex, + ); if (dataRow == null) { return; } - setState(() { - dataRow - ..isDirty = true - ..rowIndexChanged(); - if (recalculateRowHeight) { - _dataGridConfiguration.container.rowHeightManager - .setDirty(rowIndex); - _dataGridConfiguration.container - ..needToRefreshColumn = true - ..setRowHeights(); - } - }); + if (mounted) { + setState(() { + dataRow + ..isDirty = true + ..rowIndexChanged(); + if (recalculateRowHeight) { + _dataGridConfiguration.container.rowHeightManager.setDirty( + rowIndex, + ); + _dataGridConfiguration.container + ..needToRefreshColumn = true + ..setRowHeights(); + } + }); + } } } @@ -2082,31 +2628,94 @@ class SfDataGridState extends State } if (propertyName == 'hoverOnCell') { - setState(() { - // To rebuild the datagrid on hovering the header cell. isDirty already - // been set in header cell widget itself - }); + if (mounted) { + setState(() { + // To rebuild the datagrid on hovering the header cell. isDirty already + // been set in header cell widget itself + }); + } } if (propertyName == 'Swiping') { - setState(() { - // Need to end-edit the editing [DataGridCell] before swiping a - // [DataGridRow] or refreshing - _dataGridConfiguration.currentCell - .onCellSubmit(_dataGridConfiguration, canRefresh: false); - _container.isDirty = true; - }); + // Need to end-edit the editing [DataGridCell] before swiping a + // [DataGridRow] or refreshing + await _dataGridConfiguration.currentCell.onCellSubmit( + _dataGridConfiguration, + canRefresh: false, + ); + _container.isDirty = true; + if (mounted) { + setState(() {}); + } } if (propertyName == 'columnResizing') { - setState(() { - // Rebuild the DataGrid to change the column width and indicator visibility when the column resize is processed. - }); + if (mounted) { + setState(() { + // Rebuild the DataGrid to change the column width and indicator visibility when the column resize is processed. + }); + } } if (propertyName == 'editing' && rowColumnIndex != null) { _processCellUpdate(rowColumnIndex); } + + if (propertyName == 'Filtering') { + if (mounted) { + setState(() { + _dataGridConfiguration.container + ..resetSwipeOffset() + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + + _refreshScrollOffsets(); + }); + } + } + + if (propertyName == 'columnDragAndDrop') { + final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase dataRow) => dataRow.rowType == RowType.headerRow, + ); + + if (dataRow == null) { + return; + } + + if (_dataGridConfiguration + .columnDragAndDropController + .canResetColumnWidthCalculation) { + if (_dataGridConfiguration.columnWidthMode == + ColumnWidthMode.lastColumnFill || + _dataGridConfiguration.columns.firstWhereOrNull( + (GridColumn element) => + element.columnWidthMode == ColumnWidthMode.lastColumnFill, + ) != + null) { + resetAutoCalculation(_dataGridConfiguration.columnSizer); + } + } + + if (mounted) { + setState(() { + dataRow + ..isDirty = true + ..rowIndexChanged(); + }); + } + } + + if (propertyName == 'grouping') { + setState(() { + _dataGridConfiguration.container + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + _resetColumn(); + }); + } } void _updateDataGridStateDetails() { @@ -2127,6 +2736,9 @@ class SfDataGridState extends State ..selectionMode = widget.selectionMode ..onSelectionChanged = widget.onSelectionChanged ..onSelectionChanging = widget.onSelectionChanging + ..onCheckboxValueChanged = widget.onCheckboxValueChanged + ..onColumnSortChanging = widget.onColumnSortChanging + ..onColumnSortChanged = widget.onColumnSortChanged ..navigationMode = widget.navigationMode ..onCurrentCellActivated = widget.onCurrentCellActivated ..onCurrentCellActivating = widget.onCurrentCellActivating @@ -2144,6 +2756,7 @@ class SfDataGridState extends State ..sortingGestureType = widget.sortingGestureType ..showSortNumbers = widget.showSortNumbers ..isControlKeyPressed = false + ..isCommandKeyPressed = false ..stackedHeaderRows = widget.stackedHeaderRows ..isScrollbarAlwaysShown = widget.isScrollbarAlwaysShown ..horizontalScrollPhysics = widget.horizontalScrollPhysics @@ -2169,21 +2782,25 @@ class SfDataGridState extends State ..onColumnResizeEnd = widget.onColumnResizeEnd ..editingGestureType = widget.editingGestureType ..allowEditing = widget.allowEditing - ..rowHeight = (widget.rowHeight.isNaN - ? _dataGridConfiguration.rowHeight.isNaN - ? _rowHeight - : _dataGridConfiguration.rowHeight - : widget.rowHeight) - ..headerRowHeight = (widget.headerRowHeight.isNaN - ? _dataGridConfiguration.headerRowHeight.isNaN - ? _headerRowHeight - : _dataGridConfiguration.headerRowHeight - : widget.headerRowHeight) - ..defaultColumnWidth = (widget.defaultColumnWidth.isNaN - ? _dataGridConfiguration.defaultColumnWidth - : widget.defaultColumnWidth) + ..rowHeight = + (widget.rowHeight.isNaN + ? _dataGridConfiguration.rowHeight.isNaN + ? _rowHeight + : _dataGridConfiguration.rowHeight + : widget.rowHeight) + ..headerRowHeight = + (widget.headerRowHeight.isNaN + ? _dataGridConfiguration.headerRowHeight.isNaN + ? _headerRowHeight + : _dataGridConfiguration.headerRowHeight + : widget.headerRowHeight) + ..defaultColumnWidth = + (widget.defaultColumnWidth.isNaN + ? _dataGridConfiguration.defaultColumnWidth + : widget.defaultColumnWidth) ..footer = widget.footer ..footerHeight = widget.footerHeight + ..placeholder = widget.placeholder ..showCheckboxColumn = widget.showCheckboxColumn ..checkboxColumnSettings = widget.checkboxColumnSettings ..tableSummaryRows = widget.tableSummaryRows @@ -2191,7 +2808,24 @@ class SfDataGridState extends State ..shrinkWrapColumns = widget.shrinkWrapColumns ..shrinkWrapRows = widget.shrinkWrapRows ..rowsCacheExtent = widget.rowsCacheExtent - ..dataGridThemeHelper = _dataGridThemeHelper; + ..dataGridThemeHelper = _dataGridThemeHelper + ..allowFiltering = widget.allowFiltering + ..onFilterChanging = widget.onFilterChanging + ..onFilterChanged = widget.onFilterChanged + ..checkboxShape = widget.checkboxShape + ..showHorizontalScrollbar = widget.showHorizontalScrollbar + ..showVerticalScrollbar = widget.showVerticalScrollbar + ..allowColumnsDragging = widget.allowColumnsDragging + ..onColumnDragging = widget.onColumnDragging + ..columnDragFeedbackBuilder = widget.columnDragFeedbackBuilder + ..showColumnHeaderIconOnHover = widget.showColumnHeaderIconOnHover + ..autoExpandGroups = widget.autoExpandGroups + ..allowExpandCollapseGroup = widget.allowExpandCollapseGroup + ..groupExpanding = widget.groupExpanding + ..groupExpanded = widget.groupExpanded + ..groupCollapsing = widget.groupCollapsing + ..groupCollapsed = widget.groupCollapsed + ..groupCaptionTitleFormat = widget.groupCaptionTitleFormat; if (widget.allowPullToRefresh) { _dataGridConfiguration.refreshIndicatorKey ??= @@ -2202,12 +2836,12 @@ class SfDataGridState extends State DataGridConfiguration _onDataGridStateDetailsChanged() => _dataGridConfiguration; - void _updateProperties(SfDataGrid oldWidget) { + Future _updateProperties(SfDataGrid oldWidget) async { final bool isSourceChanged = widget.source != oldWidget.source; final bool isDataSourceChanged = !listEquals(oldWidget.source.rows, widget.source.rows); final bool isColumnsChanged = - !listEquals(_columns, widget.columns); + !listEquals(_actualColumns, widget.columns); // Issue: // FLUT-5815 - Range error occurs while doing column manipulations at // runtime and calling setstate to refresh the changes. @@ -2217,54 +2851,81 @@ class SfDataGridState extends State // collection at every time. So, we have used the column's length to check // whether the columns collection is changed or not at runtime // and update required changes in the data grid based on it. + + // Issue: + // Bug 870510: The DataGrid did not update properly when changing rowsPerPage + //along with the DataPager and Checkbox column. + // + // Fix: + // we've taken out the count of checkboxes from the collection of internal columns. + // This is done to prevent updating paginated rows before initializing the number of rows per page. + final int columnLength = + _dataGridConfiguration.showCheckboxColumn + ? _columns!.length - 1 + : _columns!.length; final bool isColumnsCollectionChanged = - _columns!.length != widget.columns.length; + columnLength != widget.columns.length; final bool isSelectionManagerChanged = oldWidget.selectionManager != widget.selectionManager || - oldWidget.selectionMode != widget.selectionMode; + oldWidget.selectionMode != widget.selectionMode; final bool isColumnSizerChanged = oldWidget.columnSizer != widget.columnSizer || - oldWidget.columnWidthMode != widget.columnWidthMode || - oldWidget.columnWidthCalculationRange != - widget.columnWidthCalculationRange; + oldWidget.columnWidthMode != widget.columnWidthMode || + oldWidget.columnWidthCalculationRange != + widget.columnWidthCalculationRange; final bool isDataGridControllerChanged = oldWidget.controller != widget.controller; - final bool isFrozenColumnPaneChanged = oldWidget.frozenColumnsCount != - widget.frozenColumnsCount || + final bool isFrozenColumnPaneChanged = + oldWidget.frozenColumnsCount != widget.frozenColumnsCount || oldWidget.footerFrozenColumnsCount != widget.footerFrozenColumnsCount; final bool isFrozenRowPaneChanged = oldWidget.frozenRowsCount != widget.frozenRowsCount || - oldWidget.footerFrozenRowsCount != widget.footerFrozenRowsCount; + oldWidget.footerFrozenRowsCount != widget.footerFrozenRowsCount; final bool isSortingChanged = oldWidget.allowSorting != widget.allowSorting; final bool isMultiColumnSortingChanged = oldWidget.allowMultiColumnSorting != widget.allowMultiColumnSorting; final bool isShowSortNumbersChanged = oldWidget.showSortNumbers != widget.showSortNumbers; - final bool isStackedHeaderRowsChanged = !listEquals( - oldWidget.stackedHeaderRows, widget.stackedHeaderRows); + final bool isStackedHeaderRowsChanged = + !listEquals( + oldWidget.stackedHeaderRows, + widget.stackedHeaderRows, + ); final bool isPullToRefreshPropertiesChanged = oldWidget.allowPullToRefresh != widget.allowPullToRefresh || - oldWidget.refreshIndicatorDisplacement != - widget.refreshIndicatorDisplacement || - oldWidget.refreshIndicatorStrokeWidth != - widget.refreshIndicatorStrokeWidth; + oldWidget.refreshIndicatorDisplacement != + widget.refreshIndicatorDisplacement || + oldWidget.refreshIndicatorStrokeWidth != + widget.refreshIndicatorStrokeWidth; final bool isSwipingChanged = widget.allowSwiping != oldWidget.allowSwiping; final bool isMaxSwipeOffsetChanged = widget.swipeMaxOffset != oldWidget.swipeMaxOffset; - final bool isFooterRowChanged = widget.footer != oldWidget.footer || + final bool isFooterRowChanged = + widget.footer != oldWidget.footer || widget.footerHeight != oldWidget.footerHeight; final bool isTableSummaryRowsChanged = widget.tableSummaryRows != oldWidget.tableSummaryRows; final bool isRowsPerPageChanged = widget.rowsPerPage != oldWidget.rowsPerPage; + // To apply filtering to the runtime changes of columns. + final bool canApplyFiltering = + isColumnsChanged && isColumnsCollectionChanged; + final bool isFilteringChanged = + oldWidget.allowFiltering != widget.allowFiltering; if (oldWidget.verticalScrollController != widget.verticalScrollController) { + if (widget.verticalScrollController != null) { + _dataGridConfiguration.disposeVerticalScrollController = false; + } _dataGridConfiguration.verticalScrollController = widget.verticalScrollController ?? ScrollController(); } if (oldWidget.horizontalScrollController != widget.horizontalScrollController) { + if (widget.horizontalScrollController != null) { + _dataGridConfiguration.disposeHorizontalScrollController = false; + } _dataGridConfiguration.horizontalScrollController = widget.horizontalScrollController ?? ScrollController(); } @@ -2275,10 +2936,11 @@ class SfDataGridState extends State final bool isEditingChanged = oldWidget.allowEditing != widget.allowEditing || - oldWidget.editingGestureType != widget.editingGestureType; + oldWidget.editingGestureType != widget.editingGestureType; - void refreshEditing() { - bool isEditingImpactAPIsChanged = isSourceChanged || + Future refreshEditing() async { + bool isEditingImpactAPIsChanged = + isSourceChanged || isDataSourceChanged || oldWidget.stackedHeaderRows.length != widget.stackedHeaderRows.length; @@ -2286,23 +2948,25 @@ class SfDataGridState extends State /// refreshing isEditingImpactAPIsChanged = (isSortingChanged || isMultiColumnSortingChanged) && - (oldWidget.source.sortedColumns.isNotEmpty || - widget.source.sortedColumns.isNotEmpty || - oldWidget.source.sortedColumns.length != - widget.source.sortedColumns.length); + (oldWidget.source.sortedColumns.isNotEmpty || + widget.source.sortedColumns.isNotEmpty || + oldWidget.source.sortedColumns.length != + widget.source.sortedColumns.length); if (isEditingChanged || isEditingImpactAPIsChanged || isSelectionManagerChanged || oldWidget.navigationMode != widget.navigationMode) { - isEditingImpactAPIsChanged = isEditingImpactAPIsChanged || + isEditingImpactAPIsChanged = + isEditingImpactAPIsChanged || isColumnsChanged || isStackedHeaderRowsChanged; if (_dataGridConfiguration.currentCell.isEditing) { - _dataGridConfiguration.currentCell.onCellSubmit( - _dataGridConfiguration, - canRefresh: !isEditingImpactAPIsChanged); + await _dataGridConfiguration.currentCell.onCellSubmit( + _dataGridConfiguration, + canRefresh: !isEditingImpactAPIsChanged, + ); } } } @@ -2310,8 +2974,9 @@ class SfDataGridState extends State void refreshFooterView() { if (oldWidget.footer != null) { final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => - row.rowType == RowType.footerRow && row.rowIndex >= 0); + (DataRowBase row) => + row.rowType == RowType.footerRow && row.rowIndex >= 0, + ); if (footerRow != null) { if (isFooterRowChanged) { // Need to reset the old footer row height in rowHeights collection. @@ -2334,9 +2999,11 @@ class SfDataGridState extends State void refreshTableSummaryRows() { if (isTableSummaryRowsChanged) { if (oldWidget.tableSummaryRows.isNotEmpty) { - _container.rowGenerator.items.removeWhere((DataRowBase row) => - row.rowType == RowType.tableSummaryRow || - row.rowType == RowType.tableSummaryCoveredRow); + _container.rowGenerator.items.removeWhere( + (DataRowBase row) => + row.rowType == RowType.tableSummaryRow || + row.rowType == RowType.tableSummaryCoveredRow, + ); } _container.refreshHeaderLineCount(); } @@ -2367,11 +3034,13 @@ class SfDataGridState extends State isSwipingChanged || isFooterRowChanged || isTableSummaryRowsChanged || + isFilteringChanged || oldWidget.rowHeight != widget.rowHeight || oldWidget.headerRowHeight != widget.headerRowHeight || oldWidget.defaultColumnWidth != widget.defaultColumnWidth || oldWidget.navigationMode != widget.navigationMode || oldWidget.showCheckboxColumn != widget.showCheckboxColumn || + oldWidget.checkboxShape != widget.checkboxShape || oldWidget.rowsCacheExtent != widget.rowsCacheExtent || isRowsPerPageChanged) { // Need to endEdit before refreshing @@ -2380,7 +3049,7 @@ class SfDataGridState extends State // Need to initialize the data source when the `isColumnsCollectionChanged` // property is true to adapt the `DataGridRow.cells` changes that made by // the user based on the `columns` collection changes. - if (isSourceChanged || isColumnsCollectionChanged) { + if (isSourceChanged || isColumnsCollectionChanged || canApplyFiltering) { _initializeDataGridDataSource(); } if (isSortingChanged || isMultiColumnSortingChanged) { @@ -2398,21 +3067,26 @@ class SfDataGridState extends State if (isDataGridControllerChanged) { oldWidget.controller?._removeDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); + _handleDataGridPropertyChangeListeners, + ); - _controller = _dataGridConfiguration.controller = - widget.controller ?? _controller!; + _controller = + _dataGridConfiguration.controller = + widget.controller ?? _controller!; _controller!._dataGridStateDetails = _dataGridStateDetails; _controller?._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); + _handleDataGridPropertyChangeListeners, + ); } if (oldWidget.columnSizer != widget.columnSizer) { _dataGridConfiguration.columnSizer = widget.columnSizer ?? ColumnSizer(); setStateDetailsInColumnSizer( - _dataGridConfiguration.columnSizer, _dataGridStateDetails!); + _dataGridConfiguration.columnSizer, + _dataGridStateDetails!, + ); } _initializeProperties(); @@ -2431,28 +3105,52 @@ class SfDataGridState extends State _container.refreshDefaultLineSize(); - _updateSelectionController(oldWidget, - isDataGridControlChanged: isDataGridControllerChanged, - isSelectionManagerChanged: isSelectionManagerChanged, - isSourceChanged: isSourceChanged, - isDataSourceChanged: isDataSourceChanged); - _container.updateRowAndColumnCount(); + _updateSelectionController( + oldWidget, + isDataGridControlChanged: isDataGridControllerChanged, + isSelectionManagerChanged: isSelectionManagerChanged, + isSourceChanged: isSourceChanged, + isDataSourceChanged: isDataSourceChanged, + ); + if (isSourceChanged || isColumnsChanged || isColumnSizerChanged || isFrozenColumnPaneChanged || - isSortingChanged || isStackedHeaderRowsChanged || oldWidget.showCheckboxColumn != widget.showCheckboxColumn || + oldWidget.checkboxShape != widget.checkboxShape) { + _resetColumn(clearEditing: false); + if (isColumnSizerChanged) { + resetAutoCalculation(_dataGridConfiguration.columnSizer); + } + } + + // Need to reset the auto calculation when sorting, filtering or show + // sort number properties are changed at runtime. then only, the auto-width + // calculation will be calculated for all the columns again. Otherwise, + // all the columns will retain the previously calculated width. + if (isSortingChanged || + isFilteringChanged || widget.allowSorting && isMultiColumnSortingChanged || widget.allowSorting && widget.allowMultiColumnSorting && isShowSortNumbersChanged) { - _resetColumn(clearEditing: false); - if (isColumnSizerChanged) { - resetAutoCalculation(_dataGridConfiguration.columnSizer); + // To reset the auto width calculation. + resetAutoCalculation(_dataGridConfiguration.columnSizer); + + final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase element) => element.rowType == RowType.headerRow, + ); + // To refresh the header row to update the sort and filter icon changes + // in the header cells. + if (dataRow != null) { + for (final DataCellBase dataCell in dataRow.visibleColumns) { + dataCell.columnIndex = -1; + } + _container.needToRefreshColumn = true; } } @@ -2475,6 +3173,7 @@ class SfDataGridState extends State isMaxSwipeOffsetChanged || isFrozenRowPaneChanged || isFrozenColumnPaneChanged || + canApplyFiltering || (oldWidget.allowSwiping && !widget.allowSwiping || isRowsPerPageChanged)) { _container.resetSwipeOffset(); @@ -2487,6 +3186,13 @@ class SfDataGridState extends State _refreshScrollOffsets(true); } + // Need to set `needToSetHorizontalOffset` property to when the column + // widths change in the RTL mode to get proper visible columns. + if (oldWidget.defaultColumnWidth != widget.defaultColumnWidth && + _dataGridConfiguration.textDirection == TextDirection.rtl) { + _container.needToSetHorizontalOffset = true; + } + _container.isDirty = true; } else { if (oldWidget.gridLinesVisibility != widget.gridLinesVisibility || @@ -2494,11 +3200,17 @@ class SfDataGridState extends State oldWidget.headerGridLinesVisibility != widget.headerGridLinesVisibility || oldWidget.sortingGestureType != widget.sortingGestureType || + (oldWidget.allowColumnsDragging != widget.allowColumnsDragging) || + (oldWidget.onColumnDragging != widget.onColumnDragging) || + (oldWidget.columnDragFeedbackBuilder != + widget.columnDragFeedbackBuilder) || isEditingChanged) { // Need to endEdit before refreshing if (isEditingChanged && _dataGridConfiguration.currentCell.isEditing) { - _dataGridConfiguration.currentCell - .onCellSubmit(_dataGridConfiguration, canRefresh: false); + await _dataGridConfiguration.currentCell.onCellSubmit( + _dataGridConfiguration, + canRefresh: false, + ); } _initializeProperties(); _container.isDirty = true; @@ -2506,36 +3218,78 @@ class SfDataGridState extends State _initializeProperties(); } } + + if (oldWidget.allowColumnsDragging != widget.allowColumnsDragging || + oldWidget.onColumnDragging != widget.onColumnDragging) { + _handleDataGridPropertyChangeListeners(propertyName: 'columnDragAndDrop'); + } + + if (isColumnSizerChanged || + oldWidget.allowExpandCollapseGroup != widget.allowExpandCollapseGroup || + oldWidget.groupCaptionTitleFormat != widget.groupCaptionTitleFormat || + oldWidget.showCheckboxColumn != widget.showCheckboxColumn) { + _dataGridConfiguration.container.refreshView(); + } + + if (oldWidget.autoExpandGroups != widget.autoExpandGroups || + isSourceChanged || + isRowsPerPageChanged || + isColumnsChanged || + isColumnSizerChanged) { + if (isColumnsChanged || isColumnsCollectionChanged) { + _dataGridConfiguration.source.sortedColumns.removeWhere( + (SortColumnDetails sortColumn) => + !_dataGridConfiguration.columns.any( + (GridColumn dataGridColumn) => + dataGridColumn.columnName == sortColumn.name, + ), + ); + } + updateDataSource(_dataGridConfiguration.source, true); + _dataGridConfiguration.container + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + } } - void _handleSelectionPropertyChanged( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { + void _handleSelectionPropertyChanged({ + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false, + }) { selection_manager.handleSelectionPropertyChanged( - dataGridConfiguration: _dataGridStateDetails!(), - propertyName: propertyName, - rowColumnIndex: rowColumnIndex, - recalculateRowHeight: recalculateRowHeight); + dataGridConfiguration: _dataGridStateDetails!(), + propertyName: propertyName, + rowColumnIndex: rowColumnIndex, + recalculateRowHeight: recalculateRowHeight, + ); } - void _updateSelectionController(SfDataGrid oldWidget, - {bool isSelectionManagerChanged = false, - bool isDataGridControlChanged = false, - bool isSourceChanged = false, - bool isDataSourceChanged = false}) { + void _updateSelectionController( + SfDataGrid oldWidget, { + bool isSelectionManagerChanged = false, + bool isDataGridControlChanged = false, + bool isSourceChanged = false, + bool isDataSourceChanged = false, + }) { if (isSourceChanged) { oldWidget.controller?._removeDataGridPropertyChangeListener( - _handleSelectionPropertyChanged); - widget.controller - ?._addDataGridPropertyChangeListener(_handleSelectionPropertyChanged); + _handleSelectionPropertyChanged, + ); + widget.controller?._addDataGridPropertyChangeListener( + _handleSelectionPropertyChanged, + ); } if (isSelectionManagerChanged) { - _rowSelectionManager = _dataGridConfiguration.rowSelectionManager = - widget.selectionManager ?? _rowSelectionManager!; + _rowSelectionManager = + _dataGridConfiguration.rowSelectionManager = + widget.selectionManager ?? _rowSelectionManager!; selection_manager.setStateDetailsInSelectionManagerBase( - _rowSelectionManager!, _dataGridStateDetails!); + _rowSelectionManager!, + _dataGridStateDetails!, + ); } if (isSourceChanged) { @@ -2543,11 +3297,12 @@ class SfDataGridState extends State } selection_manager.updateSelectionController( - dataGridConfiguration: _dataGridConfiguration, - isSelectionModeChanged: oldWidget.selectionMode != widget.selectionMode, - isNavigationModeChanged: - oldWidget.navigationMode != widget.navigationMode, - isDataSourceChanged: isDataSourceChanged); + dataGridConfiguration: _dataGridConfiguration, + isSelectionModeChanged: oldWidget.selectionMode != widget.selectionMode, + isNavigationModeChanged: + oldWidget.navigationMode != widget.navigationMode, + isDataSourceChanged: isDataSourceChanged, + ); if (isDataGridControlChanged) { _ensureSelectionProperties(); @@ -2555,11 +3310,14 @@ class SfDataGridState extends State } void _onStackedHeaderRowsPropertyChanged( - SfDataGrid oldWidget, SfDataGrid widget) { + SfDataGrid oldWidget, + SfDataGrid widget, + ) { _container.refreshHeaderLineCount(); if (oldWidget.stackedHeaderRows.isNotEmpty) { _rowGenerator.items.removeWhere( - (DataRowBase row) => row.rowType == RowType.stackedHeaderRow); + (DataRowBase row) => row.rowType == RowType.stackedHeaderRow, + ); } if (widget.onQueryRowHeight != null) { _container.rowHeightManager.reset(); @@ -2600,9 +3358,12 @@ class SfDataGridState extends State void _updateBoxPainter() { if (widget.selectionMode == SelectionMode.multiple && - widget.navigationMode == GridNavigationMode.row) { - _dataGridConfiguration.configuration ??= - createLocalImageConfiguration(context); + (widget.navigationMode == GridNavigationMode.row) || + (_dataGridConfiguration.source.groupedColumns.isNotEmpty && + widget.navigationMode == GridNavigationMode.cell)) { + _dataGridConfiguration.configuration ??= createLocalImageConfiguration( + context, + ); if (_dataGridConfiguration.boxPainter == null) { _updateDecoration(); } @@ -2611,28 +3372,36 @@ class SfDataGridState extends State void _updateDecoration() { final BorderSide borderSide = BorderSide( - color: _dataGridConfiguration - .dataGridThemeHelper!.currentCellStyle.borderColor); + color: + _dataGridConfiguration + .dataGridThemeHelper! + .currentCellStyle! + .borderColor, + ); final BoxDecoration decoration = BoxDecoration( - border: Border( - bottom: borderSide, - top: borderSide, - left: borderSide, - right: borderSide)); + border: Border( + bottom: borderSide, + top: borderSide, + left: borderSide, + right: borderSide, + ), + ); _dataGridConfiguration.boxPainter = decoration.createBoxPainter(); } void _addDataGridSourceListeners() { _source?._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); + _handleDataGridPropertyChangeListeners, + ); _source?._addDataGridSourceListener(_handleNotifyListeners); _source?.addListener(_handleListeners); } void _removeDataGridSourceListeners() { _source?._removeDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); + _handleDataGridPropertyChangeListeners, + ); _source?._removeDataGridSourceListener(_handleNotifyListeners); _source?.removeListener(_handleListeners); } @@ -2641,11 +3410,13 @@ class SfDataGridState extends State void _addCheckboxColumn(DataGridConfiguration dataGridConfiguration) { if (widget.showCheckboxColumn) { dataGridConfiguration.columns.insert( - 0, - GridCheckboxColumn( - columnName: '', - label: widget.checkboxColumnSettings.label ?? const SizedBox(), - width: widget.checkboxColumnSettings.width)); + 0, + GridCheckboxColumn( + columnName: '', + label: widget.checkboxColumnSettings.label ?? const SizedBox(), + width: widget.checkboxColumnSettings.width, + ), + ); } } @@ -2676,10 +3447,18 @@ class SfDataGridState extends State @override void didChangeDependencies() { final ThemeData themeData = Theme.of(context); - _dataGridConfiguration.isDesktop = kIsWeb || + final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + _dataGridConfiguration.isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; + + _dataGridConfiguration.isMacPlatform = + themeData.platform == TargetPlatform.macOS; + _dataGridConfiguration.columnDragAndDropController.isWindowsPlatform = + themeData.platform == TargetPlatform.windows && !kIsWeb; // Sets column resizing hitTestPrecision based on the platform. if (_dataGridConfiguration.allowColumnsResizing) { @@ -2688,15 +3467,36 @@ class SfDataGridState extends State _onDataGridTextDirectionChanged(Directionality.of(context)); - _onDataGridThemeDataChanged( - SfDataGridTheme.of(context), themeData.colorScheme); - _onDataGridTextScaleFactorChanged(MediaQuery.textScaleFactorOf(context)); + _onDataGridThemeDataChanged(SfDataGridTheme.of(context), colorScheme); + _onDataGridTextScaleFactorChanged( + MediaQuery.textScalerOf(context).scale(1), + ); _updateVisualDensity(themeData.visualDensity); - _dataGridConfiguration.defaultColumnWidth = widget.defaultColumnWidth.isNaN - ? _dataGridConfiguration.isDesktop - ? 100 - : 90 - : widget.defaultColumnWidth; + _dataGridConfiguration.defaultColumnWidth = + widget.defaultColumnWidth.isNaN + ? _dataGridConfiguration.isDesktop + ? 100 + : 90 + : widget.defaultColumnWidth; + _onDataGridLocalizationsChanged(SfLocalizations.of(context)); + + // This is used to dismiss the filtering popup menu manually when resizing + // the current window size. By default, the popup menu will not be + // dismissed when resizing the window. So, we have used this workaround to + // achieve this behavior. + if (_dataGridConfiguration.isDesktop) { + final Size currentScreenSize = MediaQuery.of(context).size; + _screenSize ??= currentScreenSize; + if (_screenSize != currentScreenSize && + _dataGridConfiguration + .dataGridFilterHelper! + .isFilterPopupMenuShowing) { + Navigator.pop(context); + _dataGridConfiguration.dataGridFilterHelper!.isFilterPopupMenuShowing = + false; + } + _screenSize = currentScreenSize; + } super.didChangeDependencies(); } @@ -2714,36 +3514,65 @@ class SfDataGridState extends State } return LayoutBuilder( - builder: (BuildContext _context, BoxConstraints _constraints) { - final double _measuredHeight = _dataGridConfiguration.viewHeight = - _constraints.maxHeight.isInfinite - ? _minHeight - : _constraints.maxHeight; - final double _measuredWidth = _dataGridConfiguration.viewWidth = - _constraints.maxWidth.isInfinite ? _minWidth : _constraints.maxWidth; - - if (!_container.isGridLoaded) { - _gridLoaded(); - if (_textDirection == TextDirection.rtl) { - _container.needToSetHorizontalOffset = true; + builder: (BuildContext context, BoxConstraints constraints) { + final double measuredHeight = + _dataGridConfiguration.viewHeight = + constraints.maxHeight.isInfinite + ? _minHeight + : constraints.maxHeight; + double measuredWidth = + _dataGridConfiguration.viewWidth = + constraints.maxWidth.isInfinite + ? _minWidth + : constraints.maxWidth; + + // FLUT-6545 if shrinkWrapColumns is true, we need to set the container extended width value to the viewWidth + // because the row selection colors are applied based on this size while cell is in editing + if (_dataGridConfiguration.shrinkWrapColumns) { + measuredWidth = + _dataGridConfiguration.viewWidth = + _dataGridConfiguration.container.extentWidth; + } + if (!_container.isGridLoaded) { + _gridLoaded(); + if (_textDirection == TextDirection.rtl) { + _container.needToSetHorizontalOffset = true; + } + _container.isDirty = true; + updateColumnSizerLoadedInitiallyFlag( + _dataGridConfiguration.columnSizer, + true, + ); } - _container.isDirty = true; - updateColumnSizerLoadedInitiallyFlag( - _dataGridConfiguration.columnSizer, true); - } - return ScrollViewWidget( - width: _measuredWidth, - height: _measuredHeight, - dataGridStateDetails: _dataGridStateDetails!, - ); - }); + return ScrollViewWidget( + width: measuredWidth, + height: measuredHeight, + dataGridStateDetails: _dataGridStateDetails!, + ); + }, + ); } @override void dispose() { _removeDataGridSourceListeners(); - _controller?.removeListener(_handleDataGridPropertyChangeListeners); + + // Issue: + // FLUT-7056 - The dataGridPropertyChanged listener is not removed properly + // when DataGrid is disposed + // + // Fix: + // The issue occurred due to we didn't remove the listener properly. + // It's added to `_dataGridPropertyChangeListeners` but we didn't remove it from the _dataGridPropertyChangeListeners. + // We have fixed the issue by removing the respective listener from the_dataGridPropertyChangeListeners + // through the _removeDataGridPropertyChangeListener method. + _controller?._removeDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners, + ); + _controller?._removeDataGridPropertyChangeListener( + _handleSelectionPropertyChanged, + ); _dataGridConfiguration ..gridPaint = null ..boxPainter = null @@ -2753,6 +3582,25 @@ class SfDataGridState extends State _swipingAnimationController!.dispose(); _swipingAnimationController = null; } + _dataGridConfiguration.dataGridFilterHelper!.checkboxFilterHelper + ..textController.dispose() + ..searchboxFocusNode.dispose(); + _dataGridConfiguration.dataGridFilterHelper!.advancedFilterHelper + ..firstValueTextController.dispose() + ..secondValueTextController.dispose(); + + // Issue: + // FLUT-6123 - LateInitializationError has been thrown + // when opening the filter UI after rebuilding the filtered DataGrid. + // + // Fix: + // The filterFrom property is maintained at the sample level within the columns instance. + // Therefore, it does not reset to "FilteredFrom.none" when the DataGrid is dispose. + // We have now implemented a reset of the "filterFrom" property + // when the DataGrid is dispose + _dataGridConfiguration.dataGridFilterHelper!.resetColumnProperties( + _dataGridConfiguration, + ); super.dispose(); } } @@ -2832,12 +3680,37 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// Holds the collection of [DataGridRow] to be displayed in the [SfDataPager] page. List _paginatedRows = []; + /// To check whether the listener is added to the source. + bool _hasListeners() { + return super.hasListeners; + } + /// Helps to suspend the multiple update on SfDataGrid using with /// SfDataPager. bool _suspendDataPagerUpdate = false; DataGridStateDetails? _dataGridStateDetails; + final Map> _filterConditions = + >{}; + + /// Holds the collection of [FilterCondition] based on the columns. + /// + /// Here, key is the name of the column. Value is the collection of filter + /// conditions. + /// + /// Use [DataGridSource.addFilterCondition] and + /// [DataGridSource.removeFilterCondition] to add or remove the filter + /// conditions for columns. + Map> get filterConditions => + Map>.unmodifiable(_filterConditions); + + final List _groupedColumns = []; + + /// The collection of [ColumnGroup] to be grouped in the [SfDataGrid]. + List get groupedColumns => + List.unmodifiable(_groupedColumns); + /// Called whenever you call [notifyListeners] or [notifyDataSourceListeners] /// in the DataGridSource class. If you want to recalculate all columns /// width (may be when underlying data gets changed), return true. @@ -2957,7 +3830,7 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// [DataGridSource.compare] – To write the custom sorting for most of the use /// cases. @protected - void performSorting(List rows) { + Future performSorting(List rows) async { if (sortedColumns.isEmpty) { return; } @@ -2966,10 +3839,33 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier }); } - /// To update the sorted collection in _paginatedRows, notifyListener should be + /// Called when grouping is applied to the [SfDataGrid.groupedColumns]. + /// + /// Overriding this method provides complete control over grouping. It is invoked when each row is being grouped based on a key. Custom grouping can be achieved by returning a key for the rows. + @protected + String performGrouping(String columnName, DataGridRow row) { + return row + .getCells() + .firstWhereOrNull( + (DataGridCell cell) => cell.columnName == columnName, + ) + ?.value + ?.toString() ?? + ''; + } + + /// To update the sorted or filtered collection in _paginatedRows, notifyListener should be /// called instead notifyDataGridPropertyChangeListener. Because, notifyListener is common for /// in DataPagerDelegate and DataGridSource will get notified. - void _updateDataPagerOnSorting() { + void _updateDataPager() { + // Need to check whether the _dataGridStateDetails is null or not because + // when the sort method is called in initState from the sample level, + // the _dataGridStateDetails will be null. So, we have to check the _dataGridStateDetails is null + // or not before accessing the rowsPerPage property. + // The _rowsPerPage value will be set when it will calling updateDataSource from the source initState method. + if (_dataGridStateDetails != null) { + _rowsPerPage = _dataGridStateDetails!().rowsPerPage; + } if (_pageCount > 0 && _paginatedRows.isNotEmpty && !_suspendDataPagerUpdate) { @@ -2980,16 +3876,19 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier } int _compareValues( - List sortedColumns, DataGridRow a, DataGridRow b) { + List sortedColumns, + DataGridRow a, + DataGridRow b, + ) { if (sortedColumns.length > 1) { - for (int i = 0; i < sortedColumns.length; i++) { + for (final int i = 0; i < sortedColumns.length;) { final SortColumnDetails sortColumn = sortedColumns[i]; final int compareResult = compare(a, b, sortColumn); if (compareResult != 0) { return compareResult; } else { final List remainingSortColumns = sortedColumns - .skipWhile((SortColumnDetails value) => value == sortColumn) + .where((SortColumnDetails value) => value != sortColumn) .toList(growable: false); return _compareValues(remainingSortColumns, a, b); } @@ -3067,20 +3966,24 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// ``` @protected int compare(DataGridRow? a, DataGridRow? b, SortColumnDetails sortColumn) { - Object? _getCellValue(List? cells, String columnName) { + Object? getCellValue(List? cells, String columnName) { return cells ?.firstWhereOrNull( - (DataGridCell element) => element.columnName == columnName) + (DataGridCell element) => element.columnName == columnName, + ) ?.value; } - final Object? valueA = _getCellValue(a?.getCells(), sortColumn.name); - final Object? valueB = _getCellValue(b?.getCells(), sortColumn.name); + final Object? valueA = getCellValue(a?.getCells(), sortColumn.name); + final Object? valueB = getCellValue(b?.getCells(), sortColumn.name); return _compareTo(valueA, valueB, sortColumn.sortDirection); } int _compareTo( - dynamic value1, dynamic value2, DataGridSortDirection sortDirection) { + dynamic value1, + dynamic value2, + DataGridSortDirection sortDirection, + ) { if (sortDirection == DataGridSortDirection.ascending) { if (value1 == null) { return -1; @@ -3098,19 +4001,48 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier } } - void _updateDataSource() { + Future _updateDataSource([bool isClearGrouping = false]) async { + // Clear grouped display elements during CRUD operations. + if (isClearGrouping && _dataGridStateDetails != null) { + final DataGridConfiguration dataGridStateDetails = + _dataGridStateDetails!(); + if (dataGridStateDetails.source.groupedColumns.isNotEmpty) { + dataGridStateDetails.group?.clearDisplayElements(dataGridStateDetails); + } + } if (sortedColumns.isNotEmpty) { _unSortedRows = rows.toList(); _effectiveRows = _unSortedRows; } else { _effectiveRows = rows; } + + // Should refresh filtering when the filterConditions is not empty. + if (_dataGridStateDetails != null && + _dataGridStateDetails!().dataGridFilterHelper != null && + _filterConditions.isNotEmpty) { + _dataGridStateDetails!().dataGridFilterHelper!.applyFilter(); + } + // Should refresh sorting when the data grid source is updated. performSorting(_effectiveRows); - /// Helps to update the sorted collection in _paginatedRows - /// by call the DataPagerDelegate.handlePageChange after sorting. - _updateDataPagerOnSorting(); + // Should refresh grouping when data grid source is updated + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridStateDetails = + _dataGridStateDetails!(); + if (dataGridStateDetails.source.groupedColumns.isNotEmpty && + dataGridStateDetails.group!.displayElements == null) { + dataGridStateDetails.group?.initializeTopLevelGroup( + dataGridStateDetails, + dataGridStateDetails.autoExpandGroups, + ); + } + } + + /// Helps to update the sorted or filtered collection in _paginatedRows + /// by call the DataPagerDelegate.handlePageChange after sorting or filtering. + _updateDataPager(); } /// Call this method when you are adding the [SortColumnDetails] @@ -3148,8 +4080,8 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// ); /// } /// ``` - void sort() { - _updateDataSource(); + Future sort() async { + await _updateDataSource(true); _notifyDataGridPropertyChangeListeners(propertyName: 'Sorting'); } @@ -3181,6 +4113,157 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier @protected Future handleRefresh() async {} + /// Call this method to add the [FilterCondition] programmatically. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Column( + /// children: [ + /// Expanded( + /// child: SfDataGrid(source: _employeeDataSource, columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ]), + /// ), + /// MaterialButton( + /// child: Text('Add Filter'), + /// onPressed: () { + /// _employeeDataSource.addFilter('id', + /// FilterCondition(type: FilterType.greaterThan, value: 1005)); + /// }), + /// ], + /// ); + /// } + /// ``` + void addFilter(String columnName, FilterCondition filterCondition) { + final List conditions = [ + if (_filterConditions.containsKey(columnName)) + ..._filterConditions[columnName]!, + filterCondition, + ]; + + _filterConditions[columnName] = conditions; + + if (_dataGridStateDetails != null) { + _refreshFilter(_dataGridStateDetails!()); + } + } + + /// Remove the [FilterCondition] from the given column. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Column( + /// children: [ + /// Expanded( + /// child: SfDataGrid(source: _employeeDataSource, columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ]), + /// ), + /// MaterialButton( + /// child: Text('Remove Filter'), + /// onPressed: () { + /// _employeeDataSource.removeFilter('name', + /// FilterCondition(type: FilterType.equals, value: 'James')); + /// }), + /// ], + /// ); + /// } + /// ``` + void removeFilter(String columnName, FilterCondition filterCondition) { + if (!_filterConditions.containsKey(columnName) || + !_filterConditions[columnName]!.contains(filterCondition)) { + return; + } + + final List conditions = + _filterConditions[columnName]!..remove(filterCondition); + + if (conditions.isEmpty) { + _filterConditions.remove(columnName); + } else { + _filterConditions[columnName] = conditions; + } + + if (_dataGridStateDetails != null) { + _refreshFilter(_dataGridStateDetails!()); + } + } + + /// Clear the [FilterCondition] from a given column or clear all the filter + /// conditions from all the columns. + /// + /// Pass the required [columnName] to remove the filter conditions from the + /// specific column. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Column( + /// children: [ + /// Expanded( + /// child: SfDataGrid(source: _employeeDataSource, columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ]), + /// ), + /// MaterialButton( + /// child: Text('Clear Filters'), + /// onPressed: () { + /// _employeeDataSource.clearFilters(); + /// }), + /// ], + /// ); + /// } + /// ``` + void clearFilters({String? columnName}) { + if (_filterConditions.isNotEmpty) { + if (columnName != null && _filterConditions.containsKey(columnName)) { + _filterConditions.remove(columnName); + if (_dataGridStateDetails != null) { + _refreshFilter(_dataGridStateDetails!()); + } + } else if (columnName == null) { + _filterConditions.clear(); + if (_dataGridStateDetails != null) { + _refreshFilter(_dataGridStateDetails!()); + } + } + } + } + + void _refreshFilter(DataGridConfiguration dataGridConfiguration) { + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + canRefresh: false, + ); + } + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + dataGridConfiguration.group!.clearDisplayElements(dataGridConfiguration); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + + _updateDataSource(); + selection_manager.refreshSelectedRows(dataGridConfiguration); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'Filtering', + ); + } + /// Called to obtain the widget when a cell is moved into edit mode. /// /// The following example shows how to override this method and return the @@ -3224,7 +4307,6 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// keyboardType: TextInputType.number, /// onChanged: (String value) { /// if (value.isNotEmpty) { - /// print(value); /// newCellValue = int.parse(value); /// } else { /// newCellValue = null; @@ -3243,8 +4325,12 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// values. When you call this method, it will call [canSubmitCell] and /// [onCellSubmit] methods. So, your usual cell value updation will be done /// in single place. - Widget? buildEditWidget(DataGridRow dataGridRow, - RowColumnIndex rowColumnIndex, GridColumn column, CellSubmit submitCell) { + Widget? buildEditWidget( + DataGridRow dataGridRow, + RowColumnIndex rowColumnIndex, + GridColumn column, + CellSubmit submitCell, + ) { return null; } @@ -3258,8 +4344,11 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// actual row index even after sorting is applied, you can use /// `DataGridSource.rows.indexOf` method and pass the [dataGridRow]. It will /// provide the actual row index from unsorted [DataGridRow] collection. - bool onCellBeginEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) { + bool onCellBeginEdit( + DataGridRow dataGridRow, + RowColumnIndex rowColumnIndex, + GridColumn column, + ) { return true; } @@ -3304,8 +4393,11 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// } ///``` /// This method will never be called when you return false from [onCellBeginEdit]. - void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) {} + Future onCellSubmit( + DataGridRow dataGridRow, + RowColumnIndex rowColumnIndex, + GridColumn column, + ) async {} /// Called whenever the cell’s editing is completed i.e. prior to /// [onCellSubmit] method. @@ -3313,15 +4405,21 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// If you want to restrict the cell from being end its editing, you can /// return false. Otherwise, return true. [onCellSubmit] will be called only /// if the [canSubmitCell] returns true. - bool canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) { + Future canSubmitCell( + DataGridRow dataGridRow, + RowColumnIndex rowColumnIndex, + GridColumn column, + ) async { return true; } /// Called when you press the [LogicalKeyboardKey.escape] key when /// the [DataGridCell] on editing to cancel the editing. - void onCellCancelEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) {} + void onCellCancelEdit( + DataGridRow dataGridRow, + RowColumnIndex rowColumnIndex, + GridColumn column, + ) {} @override Future handlePageChange(int oldPageIndex, int newPageIndex) { @@ -3333,21 +4431,21 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final int rowsPerPage = dataGridConfiguration.rowsPerPage ?? + final int rowsPerPage = + dataGridConfiguration.rowsPerPage ?? (effectiveRows.length / _pageCount).ceil(); - final int _startIndex = newPageIndex * rowsPerPage; - int _endIndex = _startIndex + rowsPerPage; + final int startIndex = newPageIndex * rowsPerPage; + int endIndex = startIndex + rowsPerPage; /// Need to calculate endIndex for the last page, when the number of rows is /// lesser than rowsPerPage. - if (_endIndex > effectiveRows.length) { - _endIndex = effectiveRows.length; + if (endIndex > effectiveRows.length) { + endIndex = effectiveRows.length; } /// Get particular range from the sorted collection. - if (_startIndex < effectiveRows.length && - _endIndex <= effectiveRows.length) { - _paginatedRows = effectiveRows.getRange(_startIndex, _endIndex).toList(); + if (startIndex < effectiveRows.length && endIndex <= effectiveRows.length) { + _paginatedRows = effectiveRows.getRange(startIndex, endIndex).toList(); } else { _paginatedRows = []; } @@ -3364,10 +4462,15 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// summary. /// /// The `summaryColumn` will be null for the spanned table summary columns. - String calculateSummaryValue(GridTableSummaryRow summaryRow, - GridSummaryColumn? summaryColumn, RowColumnIndex rowColumnIndex) { + String calculateSummaryValue( + GridTableSummaryRow summaryRow, + GridSummaryColumn? summaryColumn, + RowColumnIndex rowColumnIndex, + ) { final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( - _dataGridStateDetails!(), summaryRow); + _dataGridStateDetails!(), + summaryRow, + ); if (summaryRow.showSummaryInRow || (!summaryRow.showSummaryInRow && @@ -3377,8 +4480,10 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier if (summaryRow.title != null) { for (final GridSummaryColumn cell in summaryRow.columns) { if (title.contains(cell.name)) { - final String summaryValue = - grid_helper.getSummaryValue(cell, _effectiveRows); + final String summaryValue = grid_helper.getSummaryValue( + cell, + _effectiveRows, + ); title = title.replaceAll('{${cell.name}}', summaryValue); } } @@ -3399,18 +4504,130 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier /// /// The `summaryColumn` will be null for the spanned table summary columns. Widget? buildTableSummaryCellWidget( - GridTableSummaryRow summaryRow, - GridSummaryColumn? summaryColumn, - RowColumnIndex rowColumnIndex, - String summaryValue) { + GridTableSummaryRow summaryRow, + GridSummaryColumn? summaryColumn, + RowColumnIndex rowColumnIndex, + String summaryValue, + ) { + return null; + } + + /// Call this method to add a specified [ColumnGroup] to the column grouping. + void addColumnGroup(ColumnGroup columnGroup) { + _groupedColumns.add(columnGroup); + + if (columnGroup.sortGroupRows) { + if (!sortedColumns.any( + (SortColumnDetails element) => element.name == columnGroup.name, + )) { + sortedColumns.add( + SortColumnDetails( + name: columnGroup.name, + sortDirection: DataGridSortDirection.ascending, + ), + ); + } + } + + if (_dataGridStateDetails != null) { + _refreshGrouping(_dataGridStateDetails!()); + } + } + + /// Call this method to remove a specified [ColumnGroup] from the column grouping. + void removeColumnGroup(ColumnGroup columnGroup) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (_groupedColumns.isNotEmpty && _groupedColumns.contains(columnGroup)) { + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + canRefresh: false, + ); + } + if (columnGroup.sortGroupRows) { + final SortColumnDetails? sortedColumn = sortedColumns.firstWhereOrNull( + (SortColumnDetails element) => element.name == columnGroup.name, + ); + if (sortedColumn != null) { + sortedColumns.remove(sortedColumn); + } + } + + _groupedColumns.remove(columnGroup); + if (_dataGridStateDetails != null) { + _refreshGrouping(dataGridConfiguration); + } + } + } + + /// Clear all the [ColumnGroup] from the column grouping. + void clearColumnGroups() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + canRefresh: false, + ); + } + for (final ColumnGroup column in _groupedColumns) { + if (column.sortGroupRows) { + final SortColumnDetails? sortedColumn = dataGridConfiguration + .source + .sortedColumns + .firstWhereOrNull( + (SortColumnDetails element) => element.name == column.name, + ); + if (sortedColumn != null) { + sortedColumns.remove(sortedColumn); + } + } + } + + _groupedColumns.clear(); + if (_dataGridStateDetails != null) { + _refreshGrouping(dataGridConfiguration); + } + } + + void _refreshGrouping(DataGridConfiguration dataGridConfiguration) { + _updateDataSource(true); + notifyDataGridPropertyChangeListeners( + _dataGridStateDetails!().source, + propertyName: 'grouping', + ); + } + + /// Called to obtain each cell in the caption summary row. + Widget? buildGroupCaptionCellWidget( + RowColumnIndex rowColumnIndex, + String summaryValue, + ) { return null; } } +/// Refreshes the effective rows based on the given `filterRows`. +void refreshEffectiveRows(DataGridSource source, List filterRows) { + source._effectiveRows = filterRows; +} + +/// Apply sorting to the given rows. It is used to invoke the +/// `DataGridSource.performSorting` method internally. +void performSorting(DataGridSource source, List rows) { + source.performSorting(rows); +} + +/// Helps to refresh the data pager. +void updateDataPager(DataGridSource source) { + source._updateDataPager(); +} + /// Controls a [SfDataGrid] widget. /// -/// This can be used to control the selection and currentcell operations such -/// as programmatically select a row or rows, move the currentcell to +/// This can be used to control the selection and current-cell operations such +/// as programmatically select a row or rows, move the current-cell to /// required position. /// /// DataGrid controllers are typically stored as member variables in [State] @@ -3418,14 +4635,13 @@ abstract class DataGridSource extends DataGridSourceChangeNotifier class DataGridController extends DataGridSourceChangeNotifier { /// Creates the [DataGridController] with the [selectedIndex], [selectedRow] /// and [selectedRows]. - DataGridController( - {int selectedIndex = -1, - DataGridRow? selectedRow, - List selectedRows = const []}) - : _selectedRow = selectedRow, - _selectedIndex = selectedIndex, - _selectedRows = selectedRows.toList() { - _currentCell = RowColumnIndex(-1, -1); + DataGridController({ + int selectedIndex = -1, + DataGridRow? selectedRow, + List selectedRows = const [], + }) : _selectedRow = selectedRow, + _selectedIndex = selectedIndex, + _selectedRows = selectedRows.toList() { _horizontalOffset = 0.0; _verticalOffset = 0.0; } @@ -3515,19 +4731,34 @@ class DataGridController extends DataGridSourceChangeNotifier { /// using [SfDataGrid.onQueryRowHeight] callback. void refreshRow(int rowIndex, {bool recalculateRowHeight = false}) { _notifyDataGridPropertyChangeListeners( - rowColumnIndex: RowColumnIndex(rowIndex, -1), - propertyName: 'refreshRow', - recalculateRowHeight: recalculateRowHeight); + rowColumnIndex: RowColumnIndex(rowIndex, -1), + propertyName: 'refreshRow', + recalculateRowHeight: recalculateRowHeight, + ); } /// A cell which is currently active. /// /// This is used to identify the currently active cell to process the /// key navigation. - RowColumnIndex get currentCell => _currentCell; - RowColumnIndex _currentCell = RowColumnIndex.empty; + RowColumnIndex get currentCell { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { + return grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, -1), + ); + } else { + return grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, currentCell.columnIndex), + ); + } + } - /// Moves the currentcell to the specified cell coordinates. + /// Moves the current-cell to the specified cell coordinates. void moveCurrentCellTo(RowColumnIndex rowColumnIndex) { if (_dataGridStateDetails != null) { final DataGridConfiguration dataGridConfiguration = @@ -3536,9 +4767,19 @@ class DataGridController extends DataGridSourceChangeNotifier { dataGridConfiguration.selectionMode != SelectionMode.none && dataGridConfiguration.navigationMode != GridNavigationMode.row) { final int rowIndex = grid_helper.resolveToRowIndex( - dataGridConfiguration, rowColumnIndex.rowIndex); - final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, rowColumnIndex.columnIndex); + dataGridConfiguration, + rowColumnIndex.rowIndex, + ); + final int columnIndex = + dataGridConfiguration.source.groupedColumns.isNotEmpty + ? grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + rowColumnIndex.columnIndex, + ) + : grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, + rowColumnIndex.columnIndex, + ); // Ignore the scrolling when the row index or column index are in negative // or invalid. if (rowIndex.isNegative || columnIndex.isNegative) { @@ -3548,8 +4789,10 @@ class DataGridController extends DataGridSourceChangeNotifier { dataGridConfiguration.rowSelectionManager; if (rowSelectionController is RowSelectionManager) { selection_manager.processSelectionAndCurrentCell( - dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex), - isProgrammatic: true); + dataGridConfiguration, + RowColumnIndex(rowIndex, columnIndex), + isProgrammatic: true, + ); } } } @@ -3563,11 +4806,13 @@ class DataGridController extends DataGridSourceChangeNotifier { /// 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 { + Future scrollToCell( + double rowIndex, + double columnIndex, { + bool canAnimate = false, + DataGridScrollPosition rowPosition = DataGridScrollPosition.start, + DataGridScrollPosition columnPosition = DataGridScrollPosition.start, + }) async { if (_dataGridStateDetails != null) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); @@ -3582,54 +4827,71 @@ class DataGridController extends DataGridSourceChangeNotifier { return; } - final int _rowIndex = grid_helper.resolveToRowIndex( - dataGridConfiguration, rowIndex.toInt()); - final int _columnIndex = grid_helper.resolveToGridVisibleColumnIndex( - dataGridConfiguration, columnIndex.toInt()); - double verticalOffset = - grid_helper.getVerticalOffset(dataGridConfiguration, _rowIndex); - double horizontalOffset = - grid_helper.getHorizontalOffset(dataGridConfiguration, _columnIndex); + final int getRowIndex = grid_helper.resolveToRowIndex( + dataGridConfiguration, + rowIndex.toInt(), + ); + final int getColumnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, + columnIndex.toInt(), + ); + double verticalOffset = grid_helper.getVerticalOffset( + dataGridConfiguration, + getRowIndex, + ); + double horizontalOffset = grid_helper.getHorizontalOffset( + dataGridConfiguration, + getColumnIndex, + ); if (dataGridConfiguration.textDirection == TextDirection.rtl && columnIndex == -1) { - horizontalOffset = dataGridConfiguration.container.extentWidth - + horizontalOffset = + dataGridConfiguration.container.extentWidth - + dataGridConfiguration.viewWidth - + horizontalOffset > + 0 + ? dataGridConfiguration.container.extentWidth - dataGridConfiguration.viewWidth - - horizontalOffset > - 0 - ? dataGridConfiguration.container.extentWidth - - dataGridConfiguration.viewWidth - - horizontalOffset - : 0; + horizontalOffset + : 0; } verticalOffset = grid_helper.resolveScrollOffsetToPosition( - rowPosition, - scrollRows, - verticalOffset, - dataGridConfiguration.viewHeight, - scrollRows.headerExtent, - scrollRows.footerExtent, - dataGridConfiguration.rowHeight, - dataGridConfiguration.container.verticalOffset, - _rowIndex); + rowPosition, + scrollRows, + verticalOffset, + dataGridConfiguration.viewHeight, + scrollRows.headerExtent, + scrollRows.footerExtent, + dataGridConfiguration.rowHeight, + dataGridConfiguration.container.verticalOffset, + getRowIndex, + ); horizontalOffset = grid_helper.resolveScrollOffsetToPosition( - columnPosition, - scrollColumns, - horizontalOffset, - dataGridConfiguration.viewWidth, - scrollColumns.headerExtent, - scrollColumns.footerExtent, - dataGridConfiguration.defaultColumnWidth, - dataGridConfiguration.container.horizontalOffset, - _columnIndex); + columnPosition, + scrollColumns, + horizontalOffset, + dataGridConfiguration.viewWidth, + scrollColumns.headerExtent, + scrollColumns.footerExtent, + dataGridConfiguration.defaultColumnWidth, + dataGridConfiguration.container.horizontalOffset, + getColumnIndex, + ); grid_helper.scrollVertical( - dataGridConfiguration, verticalOffset, canAnimate); + dataGridConfiguration, + verticalOffset, + canAnimate, + ); // Need to add await for the horizontal scrolling alone, to avoid the delay time between vertical and horizontal scrolling. await grid_helper.scrollHorizontal( - dataGridConfiguration, horizontalOffset, canAnimate); + dataGridConfiguration, + horizontalOffset, + canAnimate, + ); } } @@ -3639,11 +4901,17 @@ class DataGridController extends DataGridSourceChangeNotifier { /// /// 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); + 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. @@ -3652,18 +4920,26 @@ class DataGridController extends DataGridSourceChangeNotifier { /// /// 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); + 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 { + Future scrollToVerticalOffset( + double offset, { + bool canAnimate = false, + }) async { if (_dataGridStateDetails != null) { final DataGridConfiguration dataGridSettings = _dataGridStateDetails!(); return grid_helper.scrollVertical(dataGridSettings, offset, canAnimate); @@ -3673,8 +4949,10 @@ class DataGridController extends DataGridSourceChangeNotifier { /// 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 { + Future scrollToHorizontalOffset( + double offset, { + bool canAnimate = false, + }) async { if (_dataGridStateDetails != null) { final DataGridConfiguration dataGridSettings = _dataGridStateDetails!(); return grid_helper.scrollHorizontal(dataGridSettings, offset, canAnimate); @@ -3682,7 +4960,7 @@ class DataGridController extends DataGridSourceChangeNotifier { } /// Begins the edit to the given [RowColumnIndex] in [SfDataGrid]. - void beginEdit(RowColumnIndex rowColumnIndex) { + Future beginEdit(RowColumnIndex rowColumnIndex) async { if (_dataGridStateDetails != null) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); @@ -3691,14 +4969,27 @@ class DataGridController extends DataGridSourceChangeNotifier { dataGridConfiguration.navigationMode == GridNavigationMode.row) { return; } + if (isCurrentCellInEditing) { + if (!await dataGridConfiguration.currentCell.canSubmitCell( + dataGridConfiguration, + )) { + return; + } + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true, + ); + } dataGridConfiguration.currentCell.onCellBeginEdit( - editingRowColumnIndex: rowColumnIndex, isProgrammatic: true); + editingRowColumnIndex: rowColumnIndex, + isProgrammatic: true, + ); } } /// Ends the current editing of a cell in [SfDataGrid]. - void endEdit() { + Future endEdit() async { if (_dataGridStateDetails != null) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); @@ -3708,43 +4999,314 @@ class DataGridController extends DataGridSourceChangeNotifier { return; } - dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + ); } } -} -/// A delegate that provides the row count details and method to listen the -/// page navigation in [SfDataPager]. -/// -/// The following code initializes the data source and controller. -/// -/// ```dart -/// finalListpaginatedDataTable=[]; -/// ``` -/// -/// The following code example shows how to initialize the [DataPagerDelegate]. -/// -/// ```dart -/// class PaginatedDataGridSource extends DataPagerDelegate{ -/// @override -/// Future handlePageChange(int oldPageIndex, int newPageIndex) async { -/// _paginatedData = paginatedDataTable -/// .getRange(startRowIndex, endRowIndex ) -/// .toList(growable: false); -/// notifyListeners(); -/// return true; -/// } -/// } -/// ``` -class DataPagerDelegate { - /// Number of pages to be displayed in the [SfDataPager]. - double _pageCount = 0; + /// Expands all the groups in the [SfDataGrid]. + void expandAllGroup() { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final Group grouping = dataGridConfiguration.group!; + grouping.expandAll(grouping); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + } - /// 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) async { + /// Collapses all the groups in the [SfDataGrid]. + void collapseAllGroup() { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final Group grouping = dataGridConfiguration.group!; + grouping.collapseAll(grouping); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + } + + /// Collapses the groups based on the respective level. + void collapseGroupsAtLevel(int level) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final Group grouping = dataGridConfiguration.group!; + final int length = dataGridConfiguration.source.groupedColumns.length; + + if (level > 0 && level <= length) { + grouping.collapseGroupAtLevel(level, grouping); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + } + } + + /// Expands the groups based on the respective level. + void expandGroupsAtLevel(int level) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final Group grouping = _dataGridStateDetails!().group!; + final int length = dataGridConfiguration.source.groupedColumns.length; + if (level > 0 && level <= length) { + grouping.expandGroupsAtLevel(level, grouping); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + } + } + + /// Retrieves the start index of visible row based on the specified row region. + /// + /// Returns null if there are no rows available in the given region. + int? getVisibleRowStartIndex(RowRegion rowRegion) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final ScrollAxisRegion scrollAxisRegion = _getScrollAxisRegion(rowRegion); + return dataGridConfiguration.container.scrollRows + .getVisibleLinesRange(scrollAxisRegion) + ?.start; + } + return null; + } + + /// Retrieves the end index of visible row based on the specified row region. + /// + /// Returns null if there are no rows available in the given region. + int? getVisibleRowEndIndex(RowRegion rowRegion) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final ScrollAxisRegion scrollAxisRegion = _getScrollAxisRegion(rowRegion); + return dataGridConfiguration.container.scrollRows + .getVisibleLinesRange(scrollAxisRegion) + ?.end; + } + return null; + } + + /// Retrieves the start index of visible column based on the specified row region. + /// + /// Returns null if there are no rows available in the given region. + int? getVisibleColumnStartIndex(RowRegion rowRegion) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final ScrollAxisRegion scrollAxisRegion = _getScrollAxisRegion(rowRegion); + return dataGridConfiguration.container.scrollColumns + .getVisibleLinesRange(scrollAxisRegion) + ?.start; + } + return null; + } + + /// Retrieves the end index of visible column based on the specified row region. + /// + /// Returns null if there are no rows available in the given region. + int? getVisibleColumnEndIndex(RowRegion rowRegion) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final ScrollAxisRegion scrollAxisRegion = _getScrollAxisRegion(rowRegion); + return dataGridConfiguration.container.scrollColumns + .getVisibleLinesRange(scrollAxisRegion) + ?.end; + } + return null; + } + + ScrollAxisRegion _getScrollAxisRegion(RowRegion rowRegion) { + switch (rowRegion) { + case RowRegion.header: + return ScrollAxisRegion.header; + case RowRegion.body: + return ScrollAxisRegion.body; + case RowRegion.footer: + return ScrollAxisRegion.footer; + } + } + + /// Returns the details of the row at the given [rowIndex]. + /// + /// The returned [DataGridRowDetails] object contains: + /// - [DataGridRowDetails.dataGridRow]: The data associated with the row, or `null` if the row is not a default row. + /// - [DataGridRowDetails.isSelected]: Indicates whether the row is currently selected. + /// - [DataGridRowDetails.rowType]: Specifies the type of the row. + /// + /// Returns `null` if the [rowIndex] is not within the valid range of the data grid. + DataGridRowDetails? getRowDetails(int rowIndex) { + if (_dataGridStateDetails == null) { + return null; + } + + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final RowType? rowType = _getRowType(dataGridConfiguration, rowIndex); + + if (rowType == null) { + return null; + } + + if (rowType != RowType.dataRow) { + return DataGridRowDetails( + rowType: rowType, + dataGridRow: null, + isSelected: false, + ); + } + + final int recordIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, + rowIndex, + ); + + final DataGridRow? dataGridRow = + (recordIndex >= 0) + ? grid_helper.getDataRow(dataGridConfiguration, recordIndex) + : null; + + final bool isSelected = + dataGridRow != null && + selection_manager.isSelectedRow(dataGridConfiguration, dataGridRow); + + return DataGridRowDetails( + rowType: rowType, + dataGridRow: dataGridRow, + isSelected: isSelected, + ); + } + + /// Determines the row type based on the given row index. + /// Returns a [RowType] that represents different types of rows such as + /// header, footer, summary, or dataRows. + RowType? _getRowType(dataGridConfiguration, rowIndex) { + if (rowIndex < dataGridConfiguration.stackedHeaderRows.length) { + return RowType.stackedHeaderRow; + } + + if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration)) { + return RowType.headerRow; + } + + if (grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex)) { + return _getTableSummaryRowType(dataGridConfiguration, rowIndex); + } + + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + grid_helper.isCaptionSummaryRow( + dataGridConfiguration, + rowIndex, + true, + )) { + return RowType.captionSummaryCoveredRow; + } + if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration)) { + return RowType.footerRow; + } + + final int recordIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, + rowIndex, + ); + + final int rowCount = grid_helper.resolveEffectiveRowCount( + dataGridConfiguration, + ); + + if (recordIndex >= 0 && recordIndex < rowCount) { + return RowType.dataRow; + } + + return null; + } + + /// Determines the row type for a table summary row based on its position. + /// Returns [RowType.tableSummaryCoveredRow] if the summary should be covered, + /// otherwise returns [RowType.tableSummaryRow]. + RowType _getTableSummaryRowType( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + ) { + final GridTableSummaryRow? tableSummaryRow = grid_helper.getTableSummaryRow( + dataGridConfiguration, + rowIndex, + grid_helper.isTopTableSummaryRow(dataGridConfiguration, rowIndex) + ? GridTableSummaryRowPosition.top + : GridTableSummaryRowPosition.bottom, + ); + + return tableSummaryRow != null && tableSummaryRow.showSummaryInRow + ? RowType.tableSummaryCoveredRow + : RowType.tableSummaryRow; + } +} + +/// Returns the row details for the specified row index. +class DataGridRowDetails { + DataGridRowDetails({ + required this.rowType, + required this.dataGridRow, + required this.isSelected, + }); + + /// The instance of [DataGridRow] that associated with the row. + final RowType rowType; + + /// Specifies whether the row is selected. + final DataGridRow? dataGridRow; + + /// The type of the row. + final bool isSelected; +} + +/// A delegate that provides the row count details and method to listen the +/// page navigation in [SfDataPager]. +/// +/// The following code initializes the data source and controller. +/// +/// ```dart +/// finalListpaginatedDataTable=[]; +/// ``` +/// +/// The following code example shows how to initialize the [DataPagerDelegate]. +/// +/// ```dart +/// class PaginatedDataGridSource extends DataPagerDelegate{ +/// @override +/// Future handlePageChange(int oldPageIndex, int newPageIndex) async { +/// _paginatedData = paginatedDataTable +/// .getRange(startRowIndex, endRowIndex ) +/// .toList(growable: false); +/// notifyListeners(); +/// return true; +/// } +/// } +/// ``` +mixin class DataPagerDelegate { + /// Number of pages to be displayed in the [SfDataPager]. + double _pageCount = 0; + + /// The total number of rows in a page. + int? _rowsPerPage; + + /// 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) async { return true; } } @@ -3763,20 +5325,21 @@ class DataGridSourceChangeNotifier extends ChangeNotifier { } final ObserverList<_DataGridPropertyChangeListener> - _dataGridPropertyChangeListeners = + _dataGridPropertyChangeListeners = ObserverList<_DataGridPropertyChangeListener>(); void _addDataGridPropertyChangeListener( - _DataGridPropertyChangeListener listener) { + _DataGridPropertyChangeListener listener, + ) { _dataGridPropertyChangeListeners.add(listener); } void _removeDataGridPropertyChangeListener( - _DataGridPropertyChangeListener listener) { + _DataGridPropertyChangeListener listener, + ) { _dataGridPropertyChangeListeners.remove(listener); } - @protected @override void notifyListeners() { super.notifyListeners(); @@ -3792,29 +5355,34 @@ class DataGridSourceChangeNotifier extends ChangeNotifier { } /// 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}) { + void _notifyDataGridPropertyChangeListeners({ + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false, + }) { for (final Function listener in _dataGridPropertyChangeListeners) { listener( - rowColumnIndex: rowColumnIndex, - propertyName: propertyName, - recalculateRowHeight: recalculateRowHeight); + rowColumnIndex: rowColumnIndex, + propertyName: propertyName, + recalculateRowHeight: recalculateRowHeight, + ); } } } /// Call this method whenever the rowColumnIndex, propertyName and recalculateRowHeight of the /// underlying data are updated internally. -void notifyDataGridPropertyChangeListeners(DataGridSource source, - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { +void notifyDataGridPropertyChangeListeners( + DataGridSource source, { + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false, +}) { source._notifyDataGridPropertyChangeListeners( - rowColumnIndex: rowColumnIndex, - recalculateRowHeight: recalculateRowHeight, - propertyName: propertyName); + rowColumnIndex: rowColumnIndex, + recalculateRowHeight: recalculateRowHeight, + propertyName: propertyName, + ); } /// Invokes the `handleLoadMoreRows` method in [DataGridSource]. @@ -3828,8 +5396,8 @@ Future handleRefresh(DataGridSource source) async { } /// Refreshes the current [DataGridSource]. -void updateDataSource(DataGridSource source) { - source._updateDataSource(); +void updateDataSource(DataGridSource source, [bool isClearGrouping = false]) { + source._updateDataSource(isClearGrouping); } /// Gets the `effectiveRows` from the [DataGridSource]. @@ -3841,6 +5409,15 @@ List effectiveRows(DataGridSource source) { } } +/// Called when grouping is applied to the columns. +String performGrouping( + DataGridSource? source, + String columnNames, + DataGridRow row, +) { + return source!.performGrouping(columnNames, row); +} + /// Helps to set the page count in DataGridSource. void setPageCount(DataPagerDelegate delegate, double pageCount) { final DataGridSource source = delegate as DataGridSource; @@ -3850,8 +5427,10 @@ void setPageCount(DataPagerDelegate delegate, double pageCount) { /// If the pageCount is changed during CRUD operation(while removing last page), we should update the /// current page. if (oldPageCount != pageCount) { - WidgetsBinding.instance!.addPostFrameCallback((Duration timeStamp) { - source.notifyListeners(); + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + if (source._hasListeners()) { + source.notifyListeners(); + } }); } @@ -3860,7 +5439,7 @@ void setPageCount(DataPagerDelegate delegate, double pageCount) { if (source._paginatedRows.isNotEmpty && source._pageCount == 0.0) { /// We couldn't refresh the view at same time when framework doing an /// frame refreshing. - WidgetsBinding.instance!.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { source._paginatedRows = []; source.notifyDataSourceListeners(); }); @@ -3874,16 +5453,12 @@ void updateSelectedIndex(DataGridController controller, int newSelectedIndex) { /// Updates the given [DataGridRow] to the controller's `selectedRow` property. void updateSelectedRow( - DataGridController controller, DataGridRow? newSelectedRow) { + DataGridController controller, + DataGridRow? newSelectedRow, +) { controller._selectedRow = newSelectedRow; } -/// Updates the given index to the controller's `currentCell` property. -void updateCurrentCellIndex( - DataGridController controller, RowColumnIndex newCurrentCellIndex) { - controller._currentCell = newCurrentCellIndex; -} - /// Updates the given offset to the controller's `verticalOffset` property. void updateVerticalOffset(DataGridController controller, double offset) { controller._verticalOffset = offset; @@ -3896,7 +5471,9 @@ void updateHorizontalOffset(DataGridController controller, double offset) { /// Sets the `childColumnIndexes` property in the [StackedHeaderCell]. void setChildColumnIndexes( - StackedHeaderCell stackedHeaderCell, List childSequence) { + StackedHeaderCell stackedHeaderCell, + List childSequence, +) { stackedHeaderCell._childColumnIndexes = childSequence; } @@ -3905,105 +5482,779 @@ List getChildColumnIndexes(StackedHeaderCell stackedHeaderCell) { return stackedHeaderCell._childColumnIndexes; } -/// ToDo +/// Call this method to add the [FilterCondition] in the UI filtering. +void addFilterConditions( + DataGridSource source, + String columnName, + List conditions, +) { + source._filterConditions[columnName] = conditions; +} + +/// Call this method to remove the [FilterCondition] in the UI filtering. +void removeFilterConditions(DataGridSource source, String columnName) { + source._filterConditions.remove(columnName); +} + +/// Call this method to get the rowsperpage from the [DataPagerDelegate]. +int? getRowsPerPage(DataPagerDelegate delegate) { + return delegate._rowsPerPage; +} + +/// To Do class DataGridThemeHelper { - /// ToDo + /// To Do DataGridThemeHelper( - SfDataGridThemeData? dataGridThemeData, ColorScheme? colorScheme) { - brightness = dataGridThemeData!.brightness ?? colorScheme!.brightness; - headerColor = - dataGridThemeData.headerColor ?? Colors.transparent.withOpacity(0.0001); - gridLineColor = dataGridThemeData.gridLineColor ?? - colorScheme!.onSurface.withOpacity(0.12); - gridLineStrokeWidth = dataGridThemeData.gridLineStrokeWidth ?? 1; - frozenPaneElevation = dataGridThemeData.frozenPaneElevation ?? 5; - frozenPaneLineWidth = dataGridThemeData.frozenPaneLineWidth ?? 2; - selectionColor = dataGridThemeData.selectionColor ?? - colorScheme!.onSurface.withOpacity(0.08); - headerHoverColor = dataGridThemeData.headerHoverColor ?? - colorScheme!.onSurface.withOpacity(0.04); - rowHoverColor = dataGridThemeData.rowHoverColor ?? - colorScheme!.onSurface.withOpacity(0.04); - sortIconColor = dataGridThemeData.sortIconColor ?? - colorScheme!.onSurface.withOpacity(0.6); - frozenPaneLineColor = dataGridThemeData.frozenPaneLineColor ?? - colorScheme!.onSurface.withOpacity(0.38); + SfDataGridThemeData dataGridThemeData, + BuildContext context, + ) { + final SfDataGridThemeData defaults = SfDataGridTheme.of(context); + final _SfDataGridThemeData sfDataGridThemeData = _SfDataGridThemeData( + context, + dataGridThemeData, + ); + + headerColor = defaults.headerColor ?? sfDataGridThemeData.headerColor; + gridLineColor = defaults.gridLineColor ?? sfDataGridThemeData.gridLineColor; + gridLineStrokeWidth = + defaults.gridLineStrokeWidth ?? sfDataGridThemeData.gridLineStrokeWidth; + frozenPaneElevation = + defaults.frozenPaneElevation ?? sfDataGridThemeData.frozenPaneElevation; + frozenPaneLineWidth = + defaults.frozenPaneLineWidth ?? sfDataGridThemeData.frozenPaneLineWidth; + selectionColor = + defaults.selectionColor ?? sfDataGridThemeData.selectionColor; + headerHoverColor = + defaults.headerHoverColor ?? sfDataGridThemeData.headerHoverColor; + rowHoverColor = defaults.rowHoverColor ?? sfDataGridThemeData.rowHoverColor; + sortIconColor = defaults.sortIconColor ?? sfDataGridThemeData.sortIconColor; + frozenPaneLineColor = + defaults.frozenPaneLineColor ?? sfDataGridThemeData.frozenPaneLineColor; columnResizeIndicatorColor = - dataGridThemeData.columnResizeIndicatorColor ?? colorScheme!.primary; + defaults.columnResizeIndicatorColor ?? + sfDataGridThemeData.columnResizeIndicatorColor; columnResizeIndicatorStrokeWidth = - dataGridThemeData.columnResizeIndicatorStrokeWidth ?? 2; - currentCellStyle = dataGridThemeData.currentCellStyle ?? - DataGridCurrentCellStyle( - borderColor: colorScheme!.onSurface.withOpacity(0.26), - borderWidth: 1.0); - rowHoverTextStyle = dataGridThemeData.rowHoverTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: colorScheme!.onSurface.withOpacity(0.87)); - sortIcon = dataGridThemeData.sortIcon; - } - - ///ToDo - late Brightness brightness; - -// ignore: public_member_api_docs - late Color headerColor; + defaults.columnResizeIndicatorStrokeWidth ?? + sfDataGridThemeData.columnResizeIndicatorStrokeWidth; + currentCellStyle = + defaults.currentCellStyle ?? sfDataGridThemeData.currentCellStyle; + + rowHoverTextStyle = + defaults.rowHoverTextStyle ?? sfDataGridThemeData.rowHoverTextStyle; + sortIcon = defaults.sortIcon ?? sfDataGridThemeData.sortIcon; + filterIcon = defaults.filterIcon ?? sfDataGridThemeData.filterIcon; + filterIconColor = + defaults.filterIconColor ?? sfDataGridThemeData.filterIconColor; + filterIconHoverColor = + defaults.filterIconHoverColor ?? + sfDataGridThemeData.filterIconHoverColor; + sortOrderNumberColor = + defaults.sortOrderNumberColor ?? + sfDataGridThemeData.sortOrderNumberColor; + sortOrderNumberBackgroundColor = + defaults.sortOrderNumberBackgroundColor ?? + sfDataGridThemeData.sortOrderNumberBackgroundColor; + _filterPopupTextStyle = sfDataGridThemeData.filterPopupTextStyle; + + _filterPopupDisabledTextStyle = + sfDataGridThemeData.filterPopupDisabledTextStyle; + + filterPopupTextStyle = _filterPopupTextStyle!.merge( + defaults.filterPopupTextStyle ?? sfDataGridThemeData.filterPopupTextStyle, + ); + filterPopupDisabledTextStyle = _filterPopupDisabledTextStyle!.merge( + defaults.filterPopupDisabledTextStyle, + ); + + columnDragIndicatorColor = + defaults.columnDragIndicatorColor ?? + sfDataGridThemeData.columnDragIndicatorColor; + columnDragIndicatorStrokeWidth = + defaults.columnDragIndicatorStrokeWidth ?? + sfDataGridThemeData.columnDragIndicatorStrokeWidth; + groupExpanderIcon = defaults.groupExpanderIcon; + indentColumnWidth = + defaults.indentColumnWidth ?? sfDataGridThemeData.indentColumnWidth; + indentColumnColor = + defaults.indentColumnColor ?? sfDataGridThemeData.indentColumnColor; + filterPopupIconColor = + defaults.filterPopupIconColor ?? + sfDataGridThemeData.filterPopupIconColor; + filterPopupDisabledIconColor = + defaults.filterPopupDisabledIconColor ?? + sfDataGridThemeData.filterPopupDisabledIconColor; + filterPopupBorderColor = sfDataGridThemeData.filterPopupBorderColor; + filterPopupBackgroundColor = + defaults.filterPopupBackgroundColor ?? + sfDataGridThemeData.filterPopupBackgroundColor; + filterPopupTextColor = sfDataGridThemeData.filterPopupTextColor; + filterPopupOuterColor = sfDataGridThemeData.filterPopupOuterColor; + feedBackWidgetColor = sfDataGridThemeData.feedBackWidgetColor; + captionSummaryRowColor = + defaults.captionSummaryRowColor ?? + sfDataGridThemeData.captionSummaryRowColor!; + captionSummaryRowHoverColor = + sfDataGridThemeData.captionSummaryRowHoverColor!; + tableSummaryRowColor = sfDataGridThemeData.tableSummaryRowColor; + filterPopupCheckColor = + defaults.filterPopupCheckColor ?? + sfDataGridThemeData.filterPopupCheckColor; + filterPopupCheckboxFillColor = + defaults.filterPopupCheckboxFillColor ?? + sfDataGridThemeData.filterPopupCheckboxFillColor; + noMatchesFilteringLabelColor = + defaults.noMatchesFilteringLabelColor ?? + sfDataGridThemeData.noMatchesFilteringLabelColor; + okFilteringLabelColor = + defaults.okFilteringLabelColor ?? + sfDataGridThemeData.okFilteringLabelColor; + okFilteringLabelButtonColor = + defaults.okFilteringLabelButtonColor ?? + sfDataGridThemeData.okFilteringLabelButtonColor; + cancelFilteringLabelColor = + defaults.cancelFilteringLabelColor ?? + sfDataGridThemeData.cancelFilteringLabelColor; + cancelFilteringLabelButtonColor = + defaults.cancelFilteringLabelButtonColor ?? + sfDataGridThemeData.cancelFilteringLabelButtonColor; + searchAreaFocusedBorderColor = + defaults.searchAreaFocusedBorderColor ?? + sfDataGridThemeData.searchAreaFocusedBorderColor; + searchAreaCursorColor = + defaults.searchAreaCursorColor ?? + sfDataGridThemeData.searchAreaCursorColor; + andRadioActiveColor = + defaults.andRadioActiveColor ?? sfDataGridThemeData.andRadioActiveColor; + andRadioFillColor = + defaults.andRadioFillColor ?? sfDataGridThemeData.andRadioFillColor; + orRadioActiveColor = + defaults.orRadioActiveColor ?? sfDataGridThemeData.orRadioActiveColor; + orRadioFillColor = + defaults.orRadioFillColor ?? sfDataGridThemeData.orRadioFillColor; + calendarIconColor = + defaults.calendarIconColor ?? sfDataGridThemeData.calendarIconColor; + advancedFilterValueDropdownFocusedBorderColor = + defaults.advancedFilterValueDropdownFocusedBorderColor ?? + sfDataGridThemeData.advancedFilterValueDropdownFocusedBorderColor; + advancedFilterTypeDropdownFocusedBorderColor = + defaults.advancedFilterTypeDropdownFocusedBorderColor ?? + sfDataGridThemeData.advancedFilterTypeDropdownFocusedBorderColor; + advancedFilterValueTextAreaCursorColor = + defaults.advancedFilterValueTextAreaCursorColor ?? + sfDataGridThemeData.advancedFilterValueTextAreaCursorColor; + searchIconColor = + defaults.searchIconColor ?? sfDataGridThemeData.searchIconColor; + closeIconColor = + defaults.closeIconColor ?? sfDataGridThemeData.closeIconColor; + advancedFilterPopupDropdownIconColor = + defaults.advancedFilterPopupDropdownIconColor ?? + sfDataGridThemeData.advancedFilterPopupDropdownIconColor; + caseSensitiveIconActiveColor = + defaults.caseSensitiveIconActiveColor ?? + sfDataGridThemeData.caseSensitiveIconActiveColor; + caseSensitiveIconColor = + defaults.caseSensitiveIconColor ?? + sfDataGridThemeData.caseSensitiveIconColor; + advancedFilterValueDropdownIconColor = + defaults.advancedFilterValueDropdownIconColor ?? + sfDataGridThemeData.advancedFilterValueDropdownIconColor; + advancedFilterTypeDropdownIconColor = + defaults.advancedFilterTypeDropdownIconColor ?? + sfDataGridThemeData.advancedFilterTypeDropdownIconColor; + filterPopupBottomDividerColor = + defaults.filterPopupBottomDividerColor ?? + sfDataGridThemeData.filterPopupBottomDividerColor; + filterPopupTopDividerColor = + defaults.filterPopupTopDividerColor ?? + sfDataGridThemeData.filterPopupTopDividerColor; + okFilteringLabelDisabledButtonColor = + defaults.okFilteringLabelDisabledButtonColor ?? + sfDataGridThemeData.okFilteringLabelDisabledButtonColor; + appBarBottomBorderColor = + defaults.appBarBottomBorderColor ?? + sfDataGridThemeData.appBarBottomBorderColor; + filterPopupInputBorderColor = + defaults.filterPopupInputBorderColor ?? + sfDataGridThemeData.filterPopupInputBorderColor; + advancedFilterPopupDropdownColor = + defaults.advancedFilterPopupDropdownColor ?? + sfDataGridThemeData.advancedFilterPopupDropdownColor; + } + + // ignore: public_member_api_docs + late final Color? headerColor; /// To do - late Color gridLineColor; + late final Color? gridLineColor; /// To do - late double gridLineStrokeWidth; + late final double? gridLineStrokeWidth; /// To do - late Color selectionColor; + late final Color? selectionColor; /// To do - late DataGridCurrentCellStyle currentCellStyle; + late final DataGridCurrentCellStyle? currentCellStyle; /// To do - late double frozenPaneLineWidth; + late final double? frozenPaneLineWidth; /// To do - late Color frozenPaneLineColor; + late final Color? frozenPaneLineColor; /// To do - late Color sortIconColor; + late final Color? sortIconColor; /// To do - late Color headerHoverColor; + late final Color? headerHoverColor; /// To do - late double frozenPaneElevation; + late final double? frozenPaneElevation; /// To do - late Color columnResizeIndicatorColor; + late final Color? columnResizeIndicatorColor; /// To do - late double columnResizeIndicatorStrokeWidth; + late final double? columnResizeIndicatorStrokeWidth; /// To do - late Color rowHoverColor; + late final Color? rowHoverColor; /// To do - late TextStyle rowHoverTextStyle; + late final TextStyle? rowHoverTextStyle; /// To do - late Widget? sortIcon; + late final Widget? sortIcon; + + /// The icon to indicate the filtering applied in column. + /// + /// If you want to change the icon filter or filtered state, you can use the [Builder] + /// widget and return the respective icon for the state. You have to return + /// the icons for both the states even if you want to change the icon + /// for specific state. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid', + /// overflow: TextOverflow.ellipsis), + /// ), + /// body: SfDataGridTheme( + /// data: SfDataGridThemeData(filterIcon: Builder( + /// builder: (context) { + /// Widget? icon; + /// String columnName = ''; + /// context.visitAncestorElements((element) { + /// if (element is GridHeaderCellElement) { + /// columnName = element.column.columnName; + /// } + /// return true; + /// }); + /// var column = _employeeDataSource.filterConditions.keys + /// .where((element) => element == columnName) + /// .firstOrNull; + + /// if (column != null) { + /// icon = const Icon( + /// Icons.filter_alt_outlined, + /// size: 20, + /// color: Colors.purple, + /// ); + /// } + /// return icon ?? + /// const Icon( + /// Icons.filter_alt_off_outlined, + /// size: 20, + /// color: Colors.deepOrange, + /// ); + /// }, + /// )), + /// child: SfDataGrid( + /// source: _employeeDataSource, + /// allowFiltering: true, + /// allowSorting: true, + /// columns: getColumns(), + /// ), + /// ), + /// ); + /// } + /// ``` + late final Widget? filterIcon; + + /// The color of the filter icon which indicates whether the column is filtered or not. + /// + /// This is not applicable when `filterIcon` property is set. + /// This applies the color to default filter icon only. + late final Color? filterIconColor; + + /// The color for the filter icon when a pointer is hovering over it. + /// + /// This is not applicable when `filterIcon` property is set. + /// This applies the color to default filter icon only. + late final Color? filterIconHoverColor; + + /// The color of the number displayed when the order of the sorting is shown. + late final Color? sortOrderNumberColor; + + /// Creates a copy of this theme but with the given fields replaced with the new values. + late final Color? sortOrderNumberBackgroundColor; + + /// The [TextStyle] of the options in filter popup menu except the items which are already selected. + late final TextStyle? filterPopupTextStyle; + + /// The [TextStyle] of the disabled options in filter popup menu. + late final TextStyle? filterPopupDisabledTextStyle; + + /// Default filter popup menu textStyle + late final TextStyle? _filterPopupTextStyle; + + /// Default filter popup menu disable textStyle + late final TextStyle? _filterPopupDisabledTextStyle; + + ///To do + late final Color? columnDragIndicatorColor; + + ///To do + late final double? columnDragIndicatorStrokeWidth; + + /// This icon indicates the expand-collapse state of a group in a caption summary row. + /// + /// It will be displayed only if the [SfDataGrid.autoExpandCollapseGroup] property is set to true. + late final Widget? groupExpanderIcon; + + /// The width of an indent column. + /// + /// Defaults to 40.0. + late final double indentColumnWidth; + + /// The color of an indent column. + late final Color? indentColumnColor; + + /// The color of icons displayed in the filter popup. + late final Color? filterPopupIconColor; + + /// The color of disabled icons in the filter popup. + late final Color? filterPopupDisabledIconColor; + + /// Provides the border color. + late final Color? filterPopupBorderColor; + + /// The background color of the filter popup. + late final Color? filterPopupBackgroundColor; + + /// Provides the text color. + late final Color? filterPopupTextColor; + + /// Provides the filter popup outer widgetcolor. + late final Color? filterPopupOuterColor; + + /// Provides the feedBack widgetcolor. + late final Color? feedBackWidgetColor; + + /// Provides the caption summaryrow color. + late final Color captionSummaryRowColor; + + /// Provides the caption summaryrow hover color. + late final Color captionSummaryRowHoverColor; + + /// Provides the table summary row color. + late final Color tableSummaryRowColor; + + /// The checkmark color of the checkbox in the filter popup. + late final Color? filterPopupCheckColor; + + /// The fill color of the checkbox in the filter popup. + late final WidgetStateProperty? filterPopupCheckboxFillColor; + + /// The color of the "No Matches" filtering label when no results are found. + late final Color? noMatchesFilteringLabelColor; + + /// The color of the OK label in the filtering popup. + late final Color? okFilteringLabelColor; + + /// The color of the OK button in the filtering popup. + late final Color? okFilteringLabelButtonColor; + + /// The color of the cancel label in the filtering popup. + late final Color? cancelFilteringLabelColor; + + /// The color of the cancel button in the filtering popup. + late final Color? cancelFilteringLabelButtonColor; + + /// The cursor color in the search area. + late final Color? searchAreaCursorColor; + + /// The focused border color of the search area. + late final Color? searchAreaFocusedBorderColor; + + /// The active (selected) color of the "AND" radio button. + late final Color? andRadioActiveColor; + + /// The fill color of the "AND" radio button. + late final WidgetStateProperty? andRadioFillColor; + + /// The active (selected) color of the "OR" radio button. + late final Color? orRadioActiveColor; + + /// The fill color of the "OR" radio button. + late final WidgetStateProperty? orRadioFillColor; + + /// The color of the calendar icon. + late final Color? calendarIconColor; + + /// The focused border color of the advanced filter type dropdown. + late final Color? advancedFilterTypeDropdownFocusedBorderColor; + + /// The focused border color of the advanced filter value dropdown. + late final Color? advancedFilterValueDropdownFocusedBorderColor; + + /// The cursor color in the advanced filter value text area. + late final Color? advancedFilterValueTextAreaCursorColor; + + /// The color of the search icon. + late final Color? searchIconColor; + + /// The color of the close icon. + late final Color? closeIconColor; + + /// The color of the dropdown icon of the advanced filter popup. + late final Color? advancedFilterPopupDropdownIconColor; + + /// The active color of the case-sensitive icon. + late final Color? caseSensitiveIconActiveColor; + + /// The default color of the case-sensitive icon. + late final Color? caseSensitiveIconColor; + + /// The background color of the advanced filter type dropdown icon. + late final Color? advancedFilterTypeDropdownIconColor; + + /// The background color of the advanced filter value dropdown icon. + late final Color? advancedFilterValueDropdownIconColor; + + /// The color of the bottom divider in the filter popup. + late final Color? filterPopupBottomDividerColor; + + /// The color of the top divider in the filter popup. + late final Color? filterPopupTopDividerColor; + + /// The color of the disabled OK button in the filtering popup. + late final Color? okFilteringLabelDisabledButtonColor; + + /// The color of the bottom border of the app bar. + late final Color? appBarBottomBorderColor; + + /// The border color of the text field (input box) inside the filter popup. + late final Color? filterPopupInputBorderColor; + + /// The background color of the dropdown in the advanced filter popup. + late final Color? advancedFilterPopupDropdownColor; +} + +/// +/// Defines the theme data for the [SfDataGrid] widget. +/// +class _SfDataGridThemeData extends SfDataGridThemeData { + /// Constructs the [_SfDataGridThemeData] + _SfDataGridThemeData(this.context, this.dataGridThemeData); + + /// The build context. + final BuildContext context; + + /// The data grid theme data. + final SfDataGridThemeData dataGridThemeData; + + /// The color scheme derived from the current SfTheme context. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get gridLineColor => colorScheme.onSurface[33]; + + @override + double get gridLineStrokeWidth => 1; + + @override + Color? get selectionColor => colorScheme.onSurface[19]; + + @override + DataGridCurrentCellStyle get currentCellStyle => DataGridCurrentCellStyle( + borderColor: colorScheme.onSurface[66]!, + borderWidth: 1.0, + ); + + @override + double get frozenPaneLineWidth => 2; + + @override + Color? get frozenPaneLineColor => colorScheme.onSurface[98]; + + @override + Color? get sortIconColor => colorScheme.onSurface[154]; + + @override + Color? get headerHoverColor => colorScheme.onSurface[10]; + + @override + double get frozenPaneElevation => 5; + + @override + Color? get headerColor => colorScheme.surface[0]; + + @override + Color get columnResizeIndicatorColor => colorScheme.primary; + + @override + double get columnResizeIndicatorStrokeWidth => 2; + + @override + Color? get rowHoverColor => colorScheme.onSurface[10]; + + @override + TextStyle get rowHoverTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onSurface[222], + ); + + @override + Widget? get sortIcon => dataGridThemeData.sortIcon; + + @override + Widget? get filterIcon => dataGridThemeData.filterIcon; + + @override + Color? get filterIconColor => dataGridThemeData.filterIconColor; + + @override + Color? get filterIconHoverColor => dataGridThemeData.filterIconHoverColor; + + @override + Color? get sortOrderNumberColor => dataGridThemeData.sortOrderNumberColor; + + @override + Color? get sortOrderNumberBackgroundColor => + dataGridThemeData.sortOrderNumberBackgroundColor; + + @override + TextStyle get filterPopupTextStyle => TextStyle( + fontSize: 14.0, + color: colorScheme.onSurface[227], + fontFamily: 'Roboto', + fontWeight: FontWeight.normal, + ); + + @override + TextStyle get filterPopupDisabledTextStyle => TextStyle( + fontSize: 14.0, + color: colorScheme.onSurface[97], + fontFamily: 'Roboto', + fontWeight: FontWeight.normal, + ); + + @override + Color? get columnDragIndicatorColor => colorScheme.primary; + + @override + double get columnDragIndicatorStrokeWidth => 2; + + @override + Widget? get groupExpanderIcon => dataGridThemeData.groupExpanderIcon; + + @override + double get indentColumnWidth => 40; + + @override + Color? get indentColumnColor => colorScheme.onSurface[10]; + + /// The color of icons displayed in the filter popup. + @override + Color? get filterPopupIconColor => colorScheme.onSurface[154]; + + /// The color of disabled icons in the filter popup. + @override + Color? get filterPopupDisabledIconColor => colorScheme.onSurface[97]; + + /// Provides the border color. + Color? get filterPopupBorderColor => colorScheme.onSurface[31]; + + /// The background color of the filter popup. + @override + Color? get filterPopupBackgroundColor => colorScheme.onSurface[0]; + + /// Provides the text color. + Color? get filterPopupTextColor => colorScheme.onSurface[228]; + + /// Provides the caption summaryrow color. + @override + Color? get captionSummaryRowColor => colorScheme.onSurface[10]; + + /// Provides the caption summary row hover color. + Color? get captionSummaryRowHoverColor => colorScheme.onSurface[20]; + + /// Provides the table summary row color. + Color get tableSummaryRowColor => colorScheme.transparent; + + /// Provides the filter popup outer widgetcolor. + Color get filterPopupOuterColor => colorScheme.surface[251]!; + + /// Provides the feedBack widgetcolor. + Color get feedBackWidgetColor => colorScheme.surface[250]!; + + /// The checkmark color of the checkbox in the filter popup. + @override + Color? get filterPopupCheckColor => dataGridThemeData.filterPopupCheckColor; + + /// The fill color of the checkbox in the filter popup. + @override + WidgetStateProperty? get filterPopupCheckboxFillColor => + dataGridThemeData.filterPopupCheckboxFillColor; + + /// The color of the "No Matches" filtering label when no results are found. + @override + Color? get noMatchesFilteringLabelColor => + dataGridThemeData.noMatchesFilteringLabelColor; + + /// The color of the OK label in the filtering popup. + @override + Color? get okFilteringLabelColor => dataGridThemeData.okFilteringLabelColor; + + /// The color of the OK button in the filtering popup. + @override + Color? get okFilteringLabelButtonColor => + dataGridThemeData.okFilteringLabelButtonColor; + + /// The color of the cancel label in the filtering popup. + @override + Color? get cancelFilteringLabelColor => + dataGridThemeData.cancelFilteringLabelColor; + + /// The color of the cancel button in the filtering popup. + @override + Color? get cancelFilteringLabelButtonColor => + dataGridThemeData.cancelFilteringLabelButtonColor; + + /// The focused border color of the search area. + @override + Color? get searchAreaFocusedBorderColor => + dataGridThemeData.searchAreaFocusedBorderColor; + + /// The cursor color in the search area. + @override + Color? get searchAreaCursorColor => dataGridThemeData.searchAreaCursorColor; + + /// The active (selected) color of the "AND" radio button. + @override + Color? get andRadioActiveColor => dataGridThemeData.andRadioActiveColor; + + /// The fill color of the "AND" radio button. + @override + WidgetStateProperty? get andRadioFillColor => + dataGridThemeData.andRadioFillColor; + + /// The active (selected) color of the "OR" radio button. + @override + Color? get orRadioActiveColor => dataGridThemeData.orRadioActiveColor; + + /// The fill color of the "OR" radio button. + @override + WidgetStateProperty? get orRadioFillColor => + dataGridThemeData.orRadioFillColor; + + /// The color of the calendar icon. + @override + Color? get calendarIconColor => dataGridThemeData.calendarIconColor; + + /// Provides the border color of the advanced filter type dropdown when focused. + @override + Color? get advancedFilterValueDropdownFocusedBorderColor => + dataGridThemeData.advancedFilterValueDropdownFocusedBorderColor; + + /// The focused border color of the advanced filter type dropdown. + @override + Color? get advancedFilterTypeDropdownFocusedBorderColor => + dataGridThemeData.advancedFilterTypeDropdownFocusedBorderColor; + + /// The cursor color in the advanced filter value text area. + @override + Color? get advancedFilterValueTextAreaCursorColor => + dataGridThemeData.advancedFilterValueTextAreaCursorColor; + + /// The color of the search icon. + @override + Color? get searchIconColor => dataGridThemeData.searchIconColor; + + /// The color of the close icon. + @override + Color? get closeIconColor => dataGridThemeData.closeIconColor; + + /// The color of the dropdown icon of the advanced filter popup. + @override + Color? get advancedFilterPopupDropdownIconColor => + dataGridThemeData.advancedFilterPopupDropdownIconColor; + + /// The active color of the case-sensitive icon. + @override + Color? get caseSensitiveIconActiveColor => + dataGridThemeData.caseSensitiveIconActiveColor; + + /// The default color of the case-sensitive icon. + @override + Color? get caseSensitiveIconColor => dataGridThemeData.caseSensitiveIconColor; + + /// The background color of the advanced filter type dropdown icon. + @override + Color? get advancedFilterTypeDropdownIconColor => + dataGridThemeData.advancedFilterTypeDropdownIconColor; + + /// The background color of the advanced filter value dropdown icon. + @override + Color? get advancedFilterValueDropdownIconColor => + dataGridThemeData.advancedFilterValueDropdownIconColor; + + /// The color of the bottom divider in the filter popup. + @override + Color? get filterPopupBottomDividerColor => + dataGridThemeData.filterPopupBottomDividerColor; + + /// The color of the top divider in the filter popup. + @override + Color? get filterPopupTopDividerColor => + dataGridThemeData.filterPopupTopDividerColor; + + /// The color of the disabled OK button in the filtering popup. + @override + Color? get okFilteringLabelDisabledButtonColor => + dataGridThemeData.okFilteringLabelDisabledButtonColor; + + /// The color of the bottom border of the app bar. + @override + Color? get appBarBottomBorderColor => + dataGridThemeData.appBarBottomBorderColor; + + /// The border color of the text field (input box) inside the filter popup. + @override + Color? get filterPopupInputBorderColor => + dataGridThemeData.filterPopupInputBorderColor; + + /// The background color of the dropdown in the advanced filter popup. + @override + Color? get advancedFilterPopupDropdownColor => + dataGridThemeData.advancedFilterPopupDropdownColor; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart index d5fe47032..d3c30502d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart @@ -3,13 +3,19 @@ import 'dart:math'; import 'dart:ui'; import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; import '../../grid_common/row_column_index.dart'; +import '../grouping/grouping.dart'; import '../helper/callbackargs.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/datagrid_helper.dart'; import '../helper/enums.dart'; +import '../helper/selection_helper.dart' as selection_helper; import '../runtime/column.dart'; import '../runtime/generator.dart'; import '../sfdatagrid.dart'; @@ -18,14 +24,14 @@ import 'rendering_widget.dart'; /// A widget which displays in the cells. class GridCell extends StatefulWidget { /// Creates the [GridCell] for [SfDataGrid] widget. - const GridCell( - {required Key key, - required this.dataCell, - required this.isDirty, - required this.backgroundColor, - required this.child, - required this.dataGridStateDetails}) - : super(key: key); + const GridCell({ + required Key key, + required this.dataCell, + required this.isDirty, + required this.backgroundColor, + required this.child, + required this.dataGridStateDetails, + }) : super(key: key); /// Holds the information required to display the cell. final DataCellBase dataCell; @@ -59,7 +65,10 @@ class _GridCellState extends State { dataGridConfiguration.editingGestureType == EditingGestureType.doubleTap); - void _handleOnTapDown(TapDownDetails details, bool isSecondaryTapDown) { + Future _handleOnTapDown( + TapDownDetails details, + bool isSecondaryTapDown, + ) async { _kind = details.kind!; final DataCellBase dataCell = widget.dataCell; final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); @@ -67,17 +76,21 @@ class _GridCellState extends State { // Clear editing when tap on the stacked header cell. if (widget.dataCell.cellType == CellType.stackedHeaderCell && dataGridConfiguration.currentCell.isEditing) { - dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + ); } - if (_isDoubleTapEnabled(dataGridConfiguration)) { - _handleDoubleTapOnEditing( - dataGridConfiguration, dataCell, details, isSecondaryTapDown); + if (_isDoubleTapEnabled(dataGridConfiguration) && !isSecondaryTapDown) { + _handleDoubleTapOnEditing(dataGridConfiguration, dataCell, details); } } - void _handleDoubleTapOnEditing(DataGridConfiguration dataGridConfiguration, - DataCellBase dataCell, TapDownDetails details, bool isSecondaryTapDown) { + void _handleDoubleTapOnEditing( + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, + TapDownDetails details, + ) { if (tapTimer != null && tapTimer!.isActive) { tapTimer!.cancel(); } else { @@ -90,12 +103,12 @@ class _GridCellState extends State { return; } _handleOnTapUp( - isSecondaryTapDown: isSecondaryTapDown, - tapDownDetails: details, - tapUpDetails: null, - dataGridConfiguration: dataGridConfiguration, - dataCell: dataCell, - kind: _kind); + tapDownDetails: details, + tapUpDetails: null, + dataGridConfiguration: dataGridConfiguration, + dataCell: dataCell, + kind: _kind, + ); tapTimer?.cancel(); }); } @@ -110,26 +123,30 @@ class _GridCellState extends State { // callback final bool isDoubleTapEnabled = _isDoubleTapEnabled(dataGridConfiguration); return GestureDetector( - onTapUp: isDoubleTapEnabled - ? null - : (TapUpDetails details) { - _handleOnTapUp( + onTapUp: + isDoubleTapEnabled + ? null + : (TapUpDetails details) { + _handleOnTapUp( tapUpDetails: details, tapDownDetails: null, dataGridConfiguration: dataGridConfiguration, dataCell: dataCell, - kind: _kind); - }, + kind: _kind, + ); + }, onTapDown: (TapDownDetails details) => _handleOnTapDown(details, false), - onTap: isDoubleTapEnabled - ? () { - if (tapTimer != null && !tapTimer!.isActive) { - _handleOnDoubleTap( + onTap: + isDoubleTapEnabled + ? () { + if (tapTimer != null && !tapTimer!.isActive) { + _handleOnDoubleTap( dataCell: dataCell, - dataGridConfiguration: dataGridConfiguration); + dataGridConfiguration: dataGridConfiguration, + ); + } } - } - : null, + : null, onTapCancel: () { if (tapTimer != null && tapTimer!.isActive) { tapTimer?.cancel(); @@ -137,30 +154,33 @@ class _GridCellState extends State { }, onSecondaryTapUp: (TapUpDetails details) { _handleOnSecondaryTapUp( - tapUpDetails: details, - dataGridConfiguration: dataGridConfiguration, - dataCell: dataCell, - kind: _kind); + tapUpDetails: details, + dataGridConfiguration: dataGridConfiguration, + dataCell: dataCell, + kind: _kind, + ); }, - onSecondaryTapDown: (TapDownDetails details) => - _handleOnTapDown(details, true), + onSecondaryTapDown: + (TapDownDetails details) => _handleOnTapDown(details, true), child: _wrapInsideContainer(), ); } Widget _wrapInsideContainer() => Container( - key: widget.key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - border: _getCellBorder(dataGridStateDetails(), widget.dataCell)), - alignment: Alignment.center, - child: _wrapInsideCellContainer( - dataGridConfiguration: dataGridStateDetails(), - child: widget.child, - dataCell: widget.dataCell, - key: widget.key!, - backgroundColor: widget.backgroundColor, - )); + key: widget.key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + border: _getCellBorder(dataGridStateDetails(), widget.dataCell), + ), + alignment: Alignment.center, + child: _wrapInsideCellContainer( + dataGridConfiguration: dataGridStateDetails(), + child: widget.child, + dataCell: widget.dataCell, + key: widget.key!, + backgroundColor: widget.backgroundColor, + ), + ); @override Widget build(BuildContext context) { @@ -168,8 +188,8 @@ class _GridCellState extends State { key: widget.key, dataCell: widget.dataCell, isDirty: widget.isDirty, - child: _wrapInsideGestureDetector(), dataGridStateDetails: dataGridStateDetails, + child: _wrapInsideGestureDetector(), ); } @@ -185,14 +205,14 @@ class _GridCellState extends State { /// A widget which displays in the header cells. class GridHeaderCell extends StatefulWidget { /// Creates the [GridHeaderCell] for [SfDataGrid] widget. - const GridHeaderCell( - {required Key key, - required this.dataCell, - required this.backgroundColor, - required this.isDirty, - required this.child, - required this.dataGridStateDetails}) - : super(key: key); + const GridHeaderCell({ + required Key key, + required this.dataCell, + required this.backgroundColor, + required this.isDirty, + required this.child, + required this.dataGridStateDetails, + }) : super(key: key); /// Holds the information required to display the cell. final DataCellBase dataCell; @@ -212,6 +232,29 @@ class GridHeaderCell extends StatefulWidget { @override State createState() => _GridHeaderCellState(); + + @override + GridHeaderCellElement createElement() { + return GridHeaderCellElement(this, dataCell.gridColumn!); + } +} + +/// An instantiation of a [GridHeaderCell] widget at a particular location in the tree. +class GridHeaderCellElement extends StatefulElement { + /// Creates the [GridHeaderCellElement] for [GridHeaderCell] widget. + GridHeaderCellElement(GridHeaderCell gridHeaderCell, this.column) + : super(gridHeaderCell); + + /// A GridColumn which displays in the header cells. + GridColumn column; + + @override + void update(covariant GridHeaderCell newWidget) { + super.update(newWidget); + if (column != newWidget.dataCell.gridColumn) { + column = newWidget.dataCell.gridColumn!; + } + } } class _GridHeaderCellState extends State { @@ -222,6 +265,7 @@ class _GridHeaderCellState extends State { Color _sortNumberTextColor = Colors.transparent; late PointerDeviceKind _kind; late Widget? _sortIcon; + bool isHovered = false; DataGridStateDetails get dataGridStateDetails => widget.dataGridStateDetails; @@ -230,21 +274,21 @@ class _GridHeaderCellState extends State { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); // Clear editing when tap on the header cell _clearEditing(dataGridConfiguration); - if (dataGridConfiguration.onCellTap != null) { - final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapUpDetails.globalPosition, - localPosition: tapUpDetails.localPosition, - kind: _kind); - dataGridConfiguration.onCellTap!(details); - } - dataGridConfiguration.dataGridFocusNode?.requestFocus(); if (dataGridConfiguration.sortingGestureType == SortingGestureType.tap) { _sort(dataCell); } + + if (dataGridConfiguration.onCellTap != null) { + final DataGridCellTapDetails details = DataGridCellTapDetails( + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, + kind: _kind, + ); + dataGridConfiguration.onCellTap!(details); + } } void _handleOnDoubleTap() { @@ -252,19 +296,19 @@ class _GridHeaderCellState extends State { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); // Clear editing when tap on the header cell _clearEditing(dataGridConfiguration); - if (dataGridConfiguration.onCellDoubleTap != null) { - final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!); - dataGridConfiguration.onCellDoubleTap!(details); - } - dataGridConfiguration.dataGridFocusNode?.requestFocus(); if (dataGridConfiguration.sortingGestureType == SortingGestureType.doubleTap) { _sort(dataCell); } + + if (dataGridConfiguration.onCellDoubleTap != null) { + final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + ); + dataGridConfiguration.onCellDoubleTap!(details); + } } void _handleOnSecondaryTapUp(TapUpDetails tapUpDetails) { @@ -274,12 +318,12 @@ class _GridHeaderCellState extends State { _clearEditing(dataGridConfiguration); if (dataGridConfiguration.onCellSecondaryTap != null) { final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapUpDetails.globalPosition, - localPosition: tapUpDetails.localPosition, - kind: _kind); + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, + kind: _kind, + ); dataGridConfiguration.onCellSecondaryTap!(details); } } @@ -292,28 +336,36 @@ class _GridHeaderCellState extends State { } /// Helps to clear the editing cell when tap on header cells - void _clearEditing(DataGridConfiguration dataGridConfiguration) { + Future _clearEditing( + DataGridConfiguration dataGridConfiguration, + ) async { if (dataGridConfiguration.currentCell.isEditing) { - dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + ); } } Widget _wrapInsideGestureDetector() { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); return GestureDetector( - onTapUp: dataGridConfiguration.onCellTap != null || - dataGridConfiguration.sortingGestureType == SortingGestureType.tap - ? _handleOnTapUp - : null, + onTapUp: + dataGridConfiguration.onCellTap != null || + dataGridConfiguration.sortingGestureType == + SortingGestureType.tap + ? _handleOnTapUp + : null, onTapDown: _handleOnTapDown, - onDoubleTap: dataGridConfiguration.onCellDoubleTap != null || - dataGridConfiguration.sortingGestureType == - SortingGestureType.doubleTap - ? _handleOnDoubleTap - : null, - onSecondaryTapUp: dataGridConfiguration.onCellSecondaryTap != null - ? _handleOnSecondaryTapUp - : null, + onDoubleTap: + dataGridConfiguration.onCellDoubleTap != null || + dataGridConfiguration.sortingGestureType == + SortingGestureType.doubleTap + ? _handleOnDoubleTap + : null, + onSecondaryTapUp: + dataGridConfiguration.onCellSecondaryTap != null + ? _handleOnSecondaryTapUp + : null, onSecondaryTapDown: _handleOnTapDown, child: _wrapInsideContainer(), ); @@ -324,32 +376,95 @@ class _GridHeaderCellState extends State { final GridColumn? column = widget.dataCell.gridColumn; Widget checkHeaderCellConstraints(Widget child) { - final double iconWidth = - getSortIconWidth(dataGridConfiguration.columnSizer, column!); return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (_sortDirection == null || constraints.maxWidth < iconWidth) { - return child; - } else { - return _getCellWithSortIcon(child); - } - }); + builder: (BuildContext context, BoxConstraints constraints) { + return _buildHeaderCell(child, _sortDirection, constraints.maxWidth); + }, + ); + } + + _ensureSortIconVisibility(column!, dataGridConfiguration); + + Widget child = _wrapInsideCellContainer( + dataGridConfiguration: dataGridConfiguration, + child: checkHeaderCellConstraints(widget.child), + dataCell: widget.dataCell, + key: widget.key!, + backgroundColor: widget.backgroundColor, + ); + + Widget getFeedbackWidget(DataGridConfiguration configuration) { + return dataGridConfiguration.columnDragFeedbackBuilder != null + ? dataGridConfiguration.columnDragFeedbackBuilder!( + context, + widget.dataCell.gridColumn!, + ) + : Container( + width: widget.dataCell.gridColumn!.actualWidth, + height: dataGridConfiguration.headerRowHeight, + decoration: BoxDecoration( + color: + dataGridConfiguration + .dataGridThemeHelper! + .feedBackWidgetColor, + border: Border.all( + color: + dataGridConfiguration.dataGridThemeHelper!.gridLineColor!, + width: + dataGridConfiguration + .dataGridThemeHelper! + .gridLineStrokeWidth!, + ), + ), + child: widget.child, + ); + } + + Widget buildDraggableHeaderCell(Widget child) { + final DataGridConfiguration configuration = dataGridStateDetails(); + final bool isWindowsPlatform = + configuration.columnDragAndDropController.isWindowsPlatform!; + return Draggable( + onDragStarted: () { + if (widget.dataCell.cellType != CellType.indentCell) { + configuration.columnDragAndDropController.onPointerDown( + widget.dataCell, + ); + } + }, + ignoringFeedbackPointer: isWindowsPlatform, + feedback: MouseRegion( + cursor: + isWindowsPlatform + ? MouseCursor.defer + : (dataGridConfiguration.isMacPlatform && !kIsWeb) + ? SystemMouseCursors.grabbing + : SystemMouseCursors.move, + child: getFeedbackWidget(configuration), + ), + child: child, + ); } - _ensureSortIconVisiblity(column!, dataGridConfiguration); + if (dataGridConfiguration.columnDragAndDropController + .canAllowColumnDragAndDrop() && + dataGridConfiguration + .columnDragAndDropController + .canWrapDraggableView && + !dataGridConfiguration + .columnResizeController + .canSwitchResizeColumnCursor) { + child = buildDraggableHeaderCell(child); + } return Container( - key: widget.key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - border: _getCellBorder(dataGridConfiguration, widget.dataCell)), - child: _wrapInsideCellContainer( - dataGridConfiguration: dataGridConfiguration, - child: checkHeaderCellConstraints(widget.child), - dataCell: widget.dataCell, - key: widget.key!, - backgroundColor: widget.backgroundColor, - )); + key: widget.key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + border: _getCellBorder(dataGridConfiguration, widget.dataCell), + ), + child: child, + ); } @override @@ -358,30 +473,36 @@ class _GridHeaderCellState extends State { key: widget.key, dataCell: widget.dataCell, isDirty: widget.isDirty, - child: _wrapInsideGestureDetector(), dataGridStateDetails: dataGridStateDetails, + child: _wrapInsideGestureDetector(), ); } - void _ensureSortIconVisiblity( - GridColumn column, DataGridConfiguration? dataGridConfiguration) { + void _ensureSortIconVisibility( + GridColumn column, + DataGridConfiguration? dataGridConfiguration, + ) { if (dataGridConfiguration != null) { final SortColumnDetails? sortColumn = dataGridConfiguration - .source.sortedColumns - .firstWhereOrNull((SortColumnDetails sortColumn) => - sortColumn.name == column.columnName); + .source + .sortedColumns + .firstWhereOrNull( + (SortColumnDetails sortColumn) => + sortColumn.name == column.columnName, + ); if (dataGridConfiguration.source.sortedColumns.isNotEmpty && sortColumn != null) { final int sortNumber = dataGridConfiguration.source.sortedColumns.indexOf(sortColumn) + 1; _sortDirection = sortColumn.sortDirection; - _sortIconColor = - dataGridConfiguration.dataGridThemeHelper!.sortIconColor; - _sortIcon = dataGridConfiguration.dataGridThemeHelper!.sortIcon; _sortNumberBackgroundColor = - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.12); + dataGridConfiguration + .dataGridThemeHelper! + .sortOrderNumberBackgroundColor ?? + dataGridConfiguration.colorScheme!.onSurface[31]!; _sortNumberTextColor = - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87); + (dataGridConfiguration.dataGridThemeHelper!.sortOrderNumberColor ?? + dataGridConfiguration.colorScheme!.onSurface[222])!; if (dataGridConfiguration.source.sortedColumns.length > 1 && dataGridConfiguration.showSortNumbers) { _sortNumber = sortNumber; @@ -395,30 +516,190 @@ class _GridHeaderCellState extends State { } } - Widget _getCellWithSortIcon(Widget child) { - final List children = []; + Widget _buildHeaderCell( + Widget child, + DataGridSortDirection? sortDirection, + double availableWidth, + ) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + final GridColumn gridColumn = widget.dataCell.gridColumn!; + final bool isSortedColumn = dataGridConfiguration.source.sortedColumns.any( + (SortColumnDetails element) => element.name == gridColumn.columnName, + ); + final bool isSortNumberVisible = _sortNumber != -1; + + if ((isSortedColumn || + (gridColumn.allowSorting && dataGridConfiguration.allowSorting)) || + (gridColumn.allowFiltering && dataGridConfiguration.allowFiltering)) { + final double sortIconWidth = getSortIconWidth( + dataGridConfiguration.columnSizer, + gridColumn, + ); + final double filterIconWidth = getFilterIconWidth( + dataGridConfiguration.columnSizer, + gridColumn, + ); - children.add(_SortIcon( - sortDirection: _sortDirection!, - sortIconColor: _sortIconColor, - sortIcon: _sortIcon, - )); + if ((sortIconWidth > 0 && sortIconWidth < availableWidth) || + (filterIconWidth > 0 && filterIconWidth < availableWidth)) { + final Map children = {}; + + if (sortIconWidth > 0 && + availableWidth > sortIconWidth + filterIconWidth) { + _sortIconColor = + dataGridConfiguration.dataGridThemeHelper!.sortIconColor!; + _sortIcon = dataGridConfiguration.dataGridThemeHelper!.sortIcon; + + if (_sortDirection != null) { + if (_sortIcon == null || _sortIcon is Icon) { + children['sortIcon'] = _SortIcon( + sortDirection: _sortDirection!, + sortIconColor: _sortIconColor, + sortIcon: _sortIcon, + ); + } else { + if (sortDirection == DataGridSortDirection.ascending) { + children['sortIcon'] = _BuilderSortIconAscending( + sortIcon: _sortIcon, + ); + } else if (sortDirection == DataGridSortDirection.descending) { + children['sortIcon'] = _BuilderSortIconDescending( + sortIcon: _sortIcon, + ); + } + } + if (_sortNumber != -1) { + children['sortNumber'] = _getSortNumber(); + } + } else if (gridColumn.allowSorting && + dataGridConfiguration.allowSorting) { + const IconData unsortIconData = IconData( + 0xe700, + fontFamily: 'UnsortIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ); + + children['sortIcon'] = + _sortIcon ?? + Icon(unsortIconData, color: _sortIconColor, size: 16); + } + } - if (_sortNumber != -1) { - children.add(_getSortNumber()); - } + if (filterIconWidth > 0 && availableWidth > filterIconWidth) { + children['filterIcon'] = _FilterIcon( + dataGridConfiguration: dataGridConfiguration, + column: gridColumn, + ); + } - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Flexible( - child: Container(child: child), - ), - Container( - padding: const EdgeInsets.only(left: 4.0, right: 4.0), - child: Center(child: Row(children: children)), - ) - ]); + bool canShowColumnHeaderIcon() { + final bool isFilteredColumn = dataGridConfiguration + .source + .filterConditions + .containsKey(gridColumn.columnName); + if (dataGridConfiguration.showColumnHeaderIconOnHover && + dataGridConfiguration.isDesktop) { + return isHovered || + dataGridConfiguration + .dataGridFilterHelper! + .isFilterPopupMenuShowing || + isFilteredColumn || + isSortedColumn; + } else { + return true; + } + } + + Widget buildHeaderCellIcons(bool isColumnHeaderIconVisible) { + return Container( + padding: dataGridConfiguration.columnSizer.iconsOuterPadding, + child: Center( + child: + isColumnHeaderIconVisible + ? Row( + children: [ + if (children.containsKey('sortIcon')) + children['sortIcon']!, + if (children.containsKey('sortNumber')) + children['sortNumber']!, + if (children.containsKey('filterIcon')) + children['filterIcon']!, + ], + ) + : const SizedBox(), + ), + ); + } + + late Widget headerCell; + final bool isColumnHeaderIconVisible = canShowColumnHeaderIcon(); + if (gridColumn.sortIconPosition == ColumnHeaderIconPosition.end && + gridColumn.filterIconPosition == ColumnHeaderIconPosition.end) { + headerCell = Row( + children: [ + Flexible(child: Container(child: child)), + buildHeaderCellIcons(isColumnHeaderIconVisible), + ], + ); + } else if (gridColumn.sortIconPosition == + ColumnHeaderIconPosition.start && + gridColumn.filterIconPosition == ColumnHeaderIconPosition.start) { + headerCell = Row( + children: [ + buildHeaderCellIcons(isColumnHeaderIconVisible), + Flexible(child: child), + ], + ); + } else if (gridColumn.sortIconPosition == + ColumnHeaderIconPosition.end && + gridColumn.filterIconPosition == ColumnHeaderIconPosition.start) { + headerCell = Row( + children: [ + if (isColumnHeaderIconVisible) + Center(child: children['filterIcon'] ?? const SizedBox()), + Flexible(child: Container(child: child)), + if (isColumnHeaderIconVisible) + Container( + padding: dataGridConfiguration.columnSizer.iconsOuterPadding, + child: Row( + children: [ + Center(child: children['sortIcon'] ?? const SizedBox()), + if (isSortNumberVisible) + Center(child: children['sortNumber']), + ], + ), + ), + ], + ); + } else { + headerCell = Row( + children: [ + if (isColumnHeaderIconVisible) + Container( + padding: dataGridConfiguration.columnSizer.iconsOuterPadding, + child: Row( + children: [ + Center(child: children['sortIcon'] ?? const SizedBox()), + if (isSortNumberVisible) + Center(child: children['sortNumber']), + ], + ), + ), + Flexible(child: Container(child: child)), + if (isColumnHeaderIconVisible) + Center(child: children['filterIcon'] ?? const SizedBox()), + ], + ); + } + + return MouseRegion( + onEnter: (_) => setState(() => isHovered = true), + onExit: (_) => setState(() => isHovered = false), + child: headerCell, + ); + } + } + return child; } Widget _getSortNumber() { @@ -429,12 +710,42 @@ class _GridHeaderCellState extends State { color: _sortNumberBackgroundColor, ), child: Center( - child: Text(_sortNumber.toString(), - style: TextStyle(fontSize: 12, color: _sortNumberTextColor)), + child: Text( + _sortNumber.toString(), + style: TextStyle(fontSize: 12, color: _sortNumberTextColor), + ), ), ); } + bool _raiseColumnSortChanging( + SortColumnDetails? newSortedColumn, + SortColumnDetails? oldSortedColumn, + ) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onColumnSortChanging == null) { + return true; + } + return dataGridConfiguration.onColumnSortChanging!( + newSortedColumn, + oldSortedColumn, + ); + } + + void _raiseColumnSortChanged( + SortColumnDetails? newSortedColumn, + SortColumnDetails? oldSortedColumn, + ) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onColumnSortChanged == null) { + return; + } + dataGridConfiguration.onColumnSortChanged!( + newSortedColumn, + oldSortedColumn, + ); + } + void _sort(DataCellBase dataCell) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (dataCell.dataRow?.rowType == RowType.headerRow && @@ -444,87 +755,135 @@ class _GridHeaderCellState extends State { } } - void _makeSort(DataCellBase dataCell) { + Future _makeSort(DataCellBase dataCell) async { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); //End-edit before perform sorting if (dataGridConfiguration.currentCell.isEditing) { - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, canRefresh: false); + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + canRefresh: false, + ); } final GridColumn column = dataCell.gridColumn!; if (column.allowSorting && dataGridConfiguration.allowSorting) { final String sortColumnName = column.columnName; - final bool allowMultiSort = dataGridConfiguration.isDesktop - ? (dataGridConfiguration.isControlKeyPressed && - dataGridConfiguration.allowMultiColumnSorting) - : dataGridConfiguration.allowMultiColumnSorting; + final bool allowMultiSort = + dataGridConfiguration.isMacPlatform + ? (dataGridConfiguration.isCommandKeyPressed && + dataGridConfiguration.allowMultiColumnSorting) + : dataGridConfiguration.isDesktop + ? (dataGridConfiguration.isControlKeyPressed && + dataGridConfiguration.allowMultiColumnSorting) + : dataGridConfiguration.allowMultiColumnSorting; final DataGridSource source = dataGridConfiguration.source; final List sortedColumns = source.sortedColumns; if (sortedColumns.isNotEmpty && allowMultiSort) { SortColumnDetails? sortedColumn = sortedColumns.firstWhereOrNull( - (SortColumnDetails sortColumn) => - sortColumn.name == sortColumnName); + (SortColumnDetails sortColumn) => sortColumn.name == sortColumnName, + ); if (sortedColumn == null) { final SortColumnDetails newSortColumn = SortColumnDetails( - name: sortColumnName, - sortDirection: DataGridSortDirection.ascending); - sortedColumns.add(newSortColumn); + name: sortColumnName, + sortDirection: DataGridSortDirection.ascending, + ); + if (_raiseColumnSortChanging(newSortColumn, sortedColumn)) { + sortedColumns.add(newSortColumn); + _raiseColumnSortChanged(newSortColumn, sortedColumn); + } } else { if (sortedColumn.sortDirection == DataGridSortDirection.descending && dataGridConfiguration.allowTriStateSorting) { - final SortColumnDetails? removedSortColumn = - sortedColumns.firstWhereOrNull((SortColumnDetails sortColumn) => - sortColumn.name == sortColumnName); - sortedColumns.remove(removedSortColumn); + final SortColumnDetails? removedSortColumn = sortedColumns + .firstWhereOrNull( + (SortColumnDetails sortColumn) => + sortColumn.name == sortColumnName, + ); + if (_raiseColumnSortChanging(null, removedSortColumn)) { + sortedColumns.remove(removedSortColumn); + _raiseColumnSortChanged(null, removedSortColumn); + } } else { sortedColumn = SortColumnDetails( - name: sortedColumn.name, - sortDirection: sortedColumn.sortDirection == - DataGridSortDirection.ascending - ? DataGridSortDirection.descending - : DataGridSortDirection.ascending); - final SortColumnDetails? removedSortColumn = - sortedColumns.firstWhereOrNull((SortColumnDetails sortColumn) => - sortColumn.name == sortedColumn!.name); - sortedColumns - ..remove(removedSortColumn) - ..add(sortedColumn); + name: sortedColumn.name, + sortDirection: + sortedColumn.sortDirection == DataGridSortDirection.ascending + ? DataGridSortDirection.descending + : DataGridSortDirection.ascending, + ); + final SortColumnDetails? removedSortColumn = sortedColumns + .firstWhereOrNull( + (SortColumnDetails sortColumn) => + sortColumn.name == sortedColumn!.name, + ); + if (_raiseColumnSortChanging(sortedColumn, removedSortColumn)) { + sortedColumns + ..remove(removedSortColumn) + ..add(sortedColumn); + _raiseColumnSortChanged(sortedColumn, removedSortColumn); + } } } } else { SortColumnDetails? currentSortColumn = sortedColumns.firstWhereOrNull( - (SortColumnDetails sortColumn) => - sortColumn.name == sortColumnName); + (SortColumnDetails sortColumn) => sortColumn.name == sortColumnName, + ); if (sortedColumns.isNotEmpty && currentSortColumn != null) { if (currentSortColumn.sortDirection == DataGridSortDirection.descending && dataGridConfiguration.allowTriStateSorting) { - sortedColumns.clear(); + if (_raiseColumnSortChanging(null, currentSortColumn)) { + sortedColumns.clear(); + _raiseColumnSortChanged(null, currentSortColumn); + } } else { currentSortColumn = SortColumnDetails( - name: currentSortColumn.name, - sortDirection: currentSortColumn.sortDirection == - DataGridSortDirection.ascending - ? DataGridSortDirection.descending - : DataGridSortDirection.ascending); - sortedColumns - ..clear() - ..add(currentSortColumn); + name: currentSortColumn.name, + sortDirection: + currentSortColumn.sortDirection == + DataGridSortDirection.ascending + ? DataGridSortDirection.descending + : DataGridSortDirection.ascending, + ); + final SortColumnDetails oldSortColumn = SortColumnDetails( + name: currentSortColumn.name, + sortDirection: + currentSortColumn.sortDirection == + DataGridSortDirection.ascending + ? DataGridSortDirection.descending + : DataGridSortDirection.ascending, + ); + if (_raiseColumnSortChanging(currentSortColumn, oldSortColumn)) { + sortedColumns + ..clear() + ..add(currentSortColumn); + _raiseColumnSortChanged(currentSortColumn, oldSortColumn); + } } } else { final SortColumnDetails sortColumn = SortColumnDetails( - name: sortColumnName, - sortDirection: DataGridSortDirection.ascending); + name: sortColumnName, + sortDirection: DataGridSortDirection.ascending, + ); if (sortedColumns.isNotEmpty) { - sortedColumns - ..clear() - ..add(sortColumn); + final SortColumnDetails oldSortColumn = SortColumnDetails( + name: sortedColumns.last.name, + sortDirection: sortedColumns.last.sortDirection, + ); + if (_raiseColumnSortChanging(sortColumn, oldSortColumn)) { + sortedColumns + ..clear() + ..add(sortColumn); + _raiseColumnSortChanged(sortColumn, oldSortColumn); + } } else { - sortedColumns.add(sortColumn); + if (_raiseColumnSortChanging(sortColumn, null)) { + sortedColumns.add(sortColumn); + _raiseColumnSortChanged(sortColumn, null); + } } } } @@ -535,11 +894,34 @@ class _GridHeaderCellState extends State { } } +class _BuilderSortIconAscending extends StatelessWidget { + const _BuilderSortIconAscending({required this.sortIcon}); + + final Widget? sortIcon; + + @override + Widget build(BuildContext context) { + return sortIcon!; + } +} + +class _BuilderSortIconDescending extends StatelessWidget { + const _BuilderSortIconDescending({required this.sortIcon}); + + final Widget? sortIcon; + + @override + Widget build(BuildContext context) { + return sortIcon!; + } +} + class _SortIcon extends StatefulWidget { - const _SortIcon( - {required this.sortDirection, - required this.sortIconColor, - required this.sortIcon}); + const _SortIcon({ + required this.sortDirection, + required this.sortIconColor, + required this.sortIcon, + }); final DataGridSortDirection sortDirection; final Color sortIconColor; final Widget? sortIcon; @@ -550,7 +932,6 @@ class _SortIcon extends StatefulWidget { class _SortIconState extends State<_SortIcon> with SingleTickerProviderStateMixin { late AnimationController _animationController; - late Animation _sortingAnimation; @override void initState() { @@ -559,8 +940,7 @@ class _SortIconState extends State<_SortIcon> duration: const Duration(milliseconds: 150), vsync: this, ); - _sortingAnimation = Tween(begin: 0.0, end: pi).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeIn)); + if (widget.sortDirection == DataGridSortDirection.descending) { _animationController.value = 1.0; } @@ -580,15 +960,12 @@ class _SortIconState extends State<_SortIcon> @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _animationController, - builder: (BuildContext context, Widget? child) { - return Transform.rotate( - angle: _sortingAnimation.value, - child: widget.sortIcon ?? - Icon(Icons.arrow_upward, - color: widget.sortIconColor, size: 16)); - }); + return RotationTransition( + turns: Tween(begin: 0.0, end: 0.5).animate(_animationController), + child: + widget.sortIcon ?? + Icon(Icons.arrow_upward, color: widget.sortIconColor, size: 16), + ); } @override @@ -598,152 +975,2184 @@ class _SortIconState extends State<_SortIcon> } } -BorderDirectional _getCellBorder( - DataGridConfiguration dataGridConfiguration, DataCellBase dataCell) { - final Color borderColor = - dataGridConfiguration.dataGridThemeHelper!.gridLineColor; - final double borderWidth = - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth; +class _FilterIcon extends StatelessWidget { + const _FilterIcon({ + Key? key, + required this.column, + required this.dataGridConfiguration, + }) : super(key: key); + + final GridColumn column; + final DataGridConfiguration dataGridConfiguration; + + void onHandleTap(TapUpDetails details, BuildContext context) { + if (dataGridConfiguration.isDesktop) { + // The `showMenu` displays the popup view relative to the topmost of the + // material app. If using more than one material app in the parent of the + // data grid, it will be laid out based on the top most material apps' + // global position. So, it will be displayed in the wrong position. Since + // the overlay is the parent of every material app widget, we resolved + // the issue by converting the global to local position of the current + // overlay and used that new offset to display the show menu. + final RenderBox renderBox = + Overlay.of(context).context.findRenderObject()! as RenderBox; + final Offset newOffset = renderBox.globalToLocal(details.globalPosition); + final Size viewSize = renderBox.size; + showMenu( + surfaceTintColor: Colors.transparent, + context: context, + color: + dataGridConfiguration + .dataGridThemeHelper! + .filterPopupBackgroundColor, + constraints: const BoxConstraints(maxWidth: 274.0), + position: RelativeRect.fromSize(newOffset & Size.zero, viewSize), + items: >[ + _FilterPopupMenuItem( + column: column, + dataGridConfiguration: dataGridConfiguration, + ), + ], + ).then((_) { + if (dataGridConfiguration.isDesktop) { + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'Filtering', + ); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + dataGridConfiguration.dataGridFilterHelper!.isFilterPopupMenuShowing = + false; + } + }); + } else { + Navigator.push<_FilterPopup>( + context, + MaterialPageRoute<_FilterPopup>( + builder: + (BuildContext context) => _FilterPopup( + column: column, + dataGridConfiguration: dataGridConfiguration, + ), + ), + ); + } + } - final int rowIndex = (dataCell.rowSpan > 0) - ? dataCell.rowIndex - dataCell.rowSpan - : dataCell.rowIndex; - final int columnIndex = dataCell.columnIndex; - final bool isStackedHeaderCell = - dataCell.cellType == CellType.stackedHeaderCell; - final bool isHeaderCell = dataCell.cellType == CellType.headerCell; - final bool isTableSummaryCell = - dataCell.cellType == CellType.tableSummaryCell; + @override + Widget build(BuildContext context) { + bool isHovered = false; + final bool isFiltered = dataGridConfiguration.source.filterConditions + .containsKey(column.columnName); - // To skip bottom border for the top data row of the starting row of bottom table - // summary rows and draw top border for the bottom summary start row instead. - final bool canSkipBottomBorder = grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom) > - 0 && - dataCell.rowIndex == - grid_helper.getStartBottomSummaryRowIndex(dataGridConfiguration) - 1; + return GestureDetector( + onTapUp: (TapUpDetails details) => onHandleTap(details, context), + child: Padding( + padding: column.filterIconPadding, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return MouseRegion( + onEnter: (_) { + setState(() { + isHovered = true; + }); + }, + onExit: (_) { + setState(() { + isHovered = false; + }); + }, + child: + isFiltered + ? _FilteredIcon( + iconColor: + isHovered + ? (dataGridConfiguration + .dataGridThemeHelper! + .filterIconHoverColor ?? + dataGridConfiguration + .colorScheme! + .onSurface[222]!) + : (dataGridConfiguration + .dataGridThemeHelper! + .filterIconColor ?? + dataGridConfiguration + .dataGridThemeHelper! + .filterPopupIconColor!), + filterIcon: + dataGridConfiguration + .dataGridThemeHelper! + .filterIcon, + gridColumnName: column.columnName, + ) + : _UnfilteredIcon( + iconColor: + isHovered + ? (dataGridConfiguration + .dataGridThemeHelper! + .filterIconHoverColor ?? + dataGridConfiguration + .colorScheme! + .onSurface[222]!) + : (dataGridConfiguration + .dataGridThemeHelper! + .filterIconColor ?? + dataGridConfiguration + .dataGridThemeHelper! + .filterPopupIconColor!), + filterIcon: + dataGridConfiguration + .dataGridThemeHelper! + .filterIcon, + gridColumnName: column.columnName, + ), + ); + }, + ), + ), + ); + } +} - // To draw the top border for the starting row of the bottom table summary row. - final bool canDrawStartBottomSummaryRowTopBorder = isTableSummaryCell && - dataCell.rowIndex == - grid_helper.getStartBottomSummaryRowIndex(dataGridConfiguration); +class _UnfilteredIcon extends StatelessWidget { + const _UnfilteredIcon({ + Key? key, + required this.iconColor, + required this.filterIcon, + required this.gridColumnName, + }) : super(key: key); - final bool canDrawHeaderHorizontalBorder = - (dataGridConfiguration.headerGridLinesVisibility == - GridLinesVisibility.horizontal || - dataGridConfiguration.headerGridLinesVisibility == - GridLinesVisibility.both) && - (isHeaderCell || isStackedHeaderCell); + final Color iconColor; + final Widget? filterIcon; + final String? gridColumnName; - final bool canDrawHeaderVerticalBorder = - (dataGridConfiguration.headerGridLinesVisibility == - GridLinesVisibility.vertical || - dataGridConfiguration.headerGridLinesVisibility == - GridLinesVisibility.both) && - (isHeaderCell || isStackedHeaderCell); + @override + Widget build(BuildContext context) { + return filterIcon ?? + Icon( + const IconData( + 0xe702, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ), + size: 18.0, + color: iconColor, + key: ValueKey( + 'datagrid_filtering_${gridColumnName}_filterIcon', + ), + ); + } +} - final bool canDrawHorizontalBorder = - (dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.horizontal || - dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.both) && - !isHeaderCell && - !isStackedHeaderCell; +class _FilteredIcon extends StatelessWidget { + const _FilteredIcon({ + Key? key, + required this.iconColor, + required this.filterIcon, + required this.gridColumnName, + }) : super(key: key); - final bool canDrawVerticalBorder = - (dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.vertical || - dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.both) && - !isStackedHeaderCell && - !isTableSummaryCell && - !isHeaderCell; + final Color iconColor; + final Widget? filterIcon; + final String? gridColumnName; - // Frozen column and row checking - final bool canDrawBottomFrozenBorder = - dataGridConfiguration.frozenRowsCount.isFinite && - dataGridConfiguration.frozenRowsCount > 0 && - grid_helper.getLastFrozenRowIndex(dataGridConfiguration) == rowIndex; + @override + Widget build(BuildContext context) { + return filterIcon ?? + Icon( + const IconData( + 0xe704, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ), + size: 18.0, + color: iconColor, + key: ValueKey( + 'datagrid_filtering_${gridColumnName}_filterIcon', + ), + ); + } +} - final bool canDrawTopFrozenBorder = - dataGridConfiguration.footerFrozenRowsCount.isFinite && - dataGridConfiguration.footerFrozenRowsCount > 0 && - grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) == - rowIndex; +class _FilterPopupMenuItem extends PopupMenuItem { + const _FilterPopupMenuItem({ + required this.column, + required this.dataGridConfiguration, + }) : super(child: null); - final bool canDrawRightFrozenBorder = - dataGridConfiguration.frozenColumnsCount.isFinite && - dataGridConfiguration.frozenColumnsCount > 0 && - grid_helper.getLastFrozenColumnIndex(dataGridConfiguration) == - columnIndex; + final GridColumn column; - final bool canDrawLeftFrozenBorder = - dataGridConfiguration.footerFrozenColumnsCount.isFinite && - dataGridConfiguration.footerFrozenColumnsCount > 0 && - grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration) == - columnIndex; + final DataGridConfiguration dataGridConfiguration; + @override + _FilterPopupMenuItemState createState() => _FilterPopupMenuItemState(); +} - final bool isFrozenPaneElevationApplied = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation > 0.0; +class _FilterPopupMenuItemState + extends PopupMenuItemState> { + @override + Widget build(BuildContext context) { + return _FilterPopup( + column: widget.column, + dataGridConfiguration: widget.dataGridConfiguration, + ); + } +} - final Color frozenPaneLineColor = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineColor; +class _FilterPopup extends StatefulWidget { + const _FilterPopup({ + Key? key, + required this.column, + required this.dataGridConfiguration, + }) : super(key: key); - final double frozenPaneLineWidth = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineWidth; + final GridColumn column; - BorderSide _getLeftBorder() { - if ((columnIndex == 0 && - (canDrawVerticalBorder || canDrawHeaderVerticalBorder)) || - canDrawLeftFrozenBorder) { - if (canDrawLeftFrozenBorder && - !isStackedHeaderCell && - !isFrozenPaneElevationApplied) { - return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (columnIndex > 0 && - ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && - !canDrawLeftFrozenBorder)) { - return BorderSide(width: borderWidth, color: borderColor); - } else { - return BorderSide.none; - } - } else { - return BorderSide.none; - } - } + final DataGridConfiguration dataGridConfiguration; - BorderSide _getTopBorder() { - if ((rowIndex == 0 && - (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder)) || - canDrawTopFrozenBorder || - canDrawStartBottomSummaryRowTopBorder) { - if (canDrawTopFrozenBorder && - !isStackedHeaderCell && - !isFrozenPaneElevationApplied) { - return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (canDrawHorizontalBorder && - canDrawStartBottomSummaryRowTopBorder) { - return BorderSide(width: borderWidth, color: borderColor); - } else { - return BorderSide.none; - } - } else { - return BorderSide.none; - } - } + @override + _FilterPopupState createState() => _FilterPopupState(); +} - BorderSide _getRightBorder() { +class _FilterPopupState extends State<_FilterPopup> { + late bool isMobile; + + late bool isAdvancedFilter; + + late DataGridFilterHelper filterHelper; + + late DataGridThemeHelper dataGridThemeHelper; + @override + void initState() { + super.initState(); + _initializeFilterProperties(); + filterHelper.isFilterPopupMenuShowing = true; + } + + @override + Widget build(BuildContext context) { + return Visibility( + visible: isMobile, + replacement: Material(child: _buildPopupView()), + child: Theme( + data: ThemeData( + colorScheme: Theme.of(context).colorScheme, + // Issue: FLUT-869897-The color of the filter pop-up menu was not working properly + // on the Mobile platform when using the Material 2. + // + // Fix: We have to set the useMaterial3 property to the theme data to resolve the above issue. + useMaterial3: Theme.of(context).useMaterial3, + ), + child: SafeArea( + child: Scaffold( + backgroundColor: dataGridThemeHelper.filterPopupOuterColor, + appBar: buildAppBar(context), + resizeToAvoidBottomInset: true, + body: _buildPopupView(), + ), + ), + ), + ); + } + + @override + void dispose() { + filterHelper.isFilterPopupMenuShowing = false; + super.dispose(); + } + + @override + void deactivate() { + filterHelper.isFilterPopupMenuShowing = false; + super.deactivate(); + } + + void _initializeFilterProperties() { + isMobile = !widget.dataGridConfiguration.isDesktop; + filterHelper = widget.dataGridConfiguration.dataGridFilterHelper!; + dataGridThemeHelper = widget.dataGridConfiguration.dataGridThemeHelper!; + filterHelper.filterFrom = filterHelper.getFilterForm(widget.column); + isAdvancedFilter = filterHelper.filterFrom == FilteredFrom.advancedFilter; + filterHelper.checkboxFilterHelper.textController.clear(); + + // Need to end edit the current cell to commit the cell value before showing + // the filtering popup menu. + filterHelper.endEdit(); + + final DataGridAdvancedFilterHelper advancedFilterHelper = + filterHelper.advancedFilterHelper; + final List? filterConditions = + widget.dataGridConfiguration.source.filterConditions[widget + .column + .columnName]; + + if (filterConditions == null) { + filterHelper.setFilterFrom(widget.column, FilteredFrom.none); + } + + /// Initializes the data grid source for filtering. + filterHelper.setDataGridSource(widget.column); + + // Need to initialize the filter values before set the values. + advancedFilterHelper + ..setAdvancedFilterType(widget.dataGridConfiguration, widget.column) + ..generateFilterTypeItems(widget.column); + + /// Initializes the advanced filter properties. + if (filterConditions != null && isAdvancedFilter) { + advancedFilterHelper.setAdvancedFilterValues( + widget.dataGridConfiguration, + filterConditions, + filterHelper, + ); + } else { + advancedFilterHelper.resetAdvancedFilterValues( + widget.dataGridConfiguration, + ); + } + } + + PreferredSize buildAppBar(BuildContext context) { + return PreferredSize( + preferredSize: const Size.fromHeight(52.0), + child: AppBar( + elevation: 0.0, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1.0), + child: Container( + height: 1.0, + color: + dataGridThemeHelper.appBarBottomBorderColor ?? + dataGridThemeHelper.filterPopupBorderColor, + ), + ), + backgroundColor: dataGridThemeHelper.filterPopupBackgroundColor, + leading: IconButton( + key: const ValueKey('datagrid_filtering_cancelFilter_icon'), + onPressed: closePage, + icon: Icon( + Icons.close, + size: 22.0, + color: + dataGridThemeHelper.cancelFilteringLabelButtonColor ?? + dataGridThemeHelper.filterPopupIconColor, + ), + ), + centerTitle: false, + titleSpacing: 0, + title: Text( + widget + .dataGridConfiguration + .localizations + .sortAndFilterDataGridFilteringLabel, + style: filterHelper.textStyle, + ), + actions: [ + IconButton( + key: const ValueKey('datagrid_filtering_applyFilter_icon'), + onPressed: canDisableOkButton() ? null : onHandleOkButtonTap, + icon: Icon( + Icons.check, + size: 22.0, + color: + canDisableOkButton() + ? dataGridThemeHelper + .okFilteringLabelDisabledButtonColor ?? + widget + .dataGridConfiguration + .colorScheme! + .onSurface[97] + : dataGridThemeHelper.okFilteringLabelButtonColor ?? + filterHelper.primaryColor, + ), + ), + ], + ), + ); + } + + Widget _buildPopupView() { + final Color iconColor = dataGridThemeHelper.filterPopupIconColor!; + final AdvancedFilterType filterType = + filterHelper.advancedFilterHelper.advancedFilterType; + final SfLocalizations localizations = + widget.dataGridConfiguration.localizations; + final bool isSortAscendingEnabled = canEnableSortButton( + DataGridSortDirection.ascending, + ); + final bool isSortDescendingEnabled = canEnableSortButton( + DataGridSortDirection.descending, + ); + final bool isClearFilterEnabled = hasFilterConditions(); + const FilterPopupMenuOptions filterPopupMenuOptions = + FilterPopupMenuOptions(); + bool isCheckboxFilterEnabled = + filterPopupMenuOptions.filterMode == FilterMode.checkboxFilter; + bool isAdvancedFilterEnabled = + filterPopupMenuOptions.filterMode == FilterMode.advancedFilter; + bool isBothFilterEnabled = + filterPopupMenuOptions.filterMode == FilterMode.both; + bool canShowSortingOptions = filterPopupMenuOptions.canShowSortingOptions; + bool canShowClearFilterOption = + filterPopupMenuOptions.canShowClearFilterOption; + bool showColumnName = filterPopupMenuOptions.showColumnName; + double advanceFilterTopPadding = 12; + + if (widget.column.filterPopupMenuOptions != null) { + isCheckboxFilterEnabled = + widget.column.filterPopupMenuOptions!.filterMode == + FilterMode.checkboxFilter; + isAdvancedFilterEnabled = + widget.column.filterPopupMenuOptions!.filterMode == + FilterMode.advancedFilter; + isBothFilterEnabled = + widget.column.filterPopupMenuOptions!.filterMode == FilterMode.both; + canShowSortingOptions = + widget.column.filterPopupMenuOptions!.canShowSortingOptions; + canShowClearFilterOption = + widget.column.filterPopupMenuOptions!.canShowClearFilterOption; + showColumnName = widget.column.filterPopupMenuOptions!.showColumnName; + } + Widget buildPopup({Size? viewSize}) { + widget.dataGridConfiguration.dataGridFocusNode!.requestFocus(); + final Widget advanceFilterPopupMenu = _AdvancedFilterPopupMenu( + setState: setState, + dataGridConfiguration: widget.dataGridConfiguration, + advanceFilterTopPadding: advanceFilterTopPadding, + ); + return SingleChildScrollView( + key: const ValueKey('datagrid_filtering_scrollView'), + child: Container( + width: isMobile ? null : 274.0, + color: dataGridThemeHelper.filterPopupBackgroundColor, + child: Column( + children: [ + if (canShowSortingOptions) + _FilterPopupMenuTile( + style: + isSortAscendingEnabled + ? filterHelper.textStyle + : filterHelper.disableTextStyle, + height: filterHelper.tileHeight, + prefix: Icon( + const IconData( + 0xe700, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ), + color: + isSortAscendingEnabled + ? iconColor + : dataGridThemeHelper.filterPopupDisabledIconColor, + size: filterHelper.textStyle.fontSize! + 10, + ), + prefixPadding: EdgeInsets.only( + left: 4.0, + right: filterHelper.textStyle.fontSize!, + bottom: + filterHelper.textStyle.fontSize! > 14 + ? filterHelper.textStyle.fontSize! - 14 + : 0, + ), + onTap: + isSortAscendingEnabled ? onHandleSortAscendingTap : null, + child: Text( + grid_helper.getSortButtonText( + localizations, + true, + filterType, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (canShowSortingOptions) + _FilterPopupMenuTile( + style: + isSortDescendingEnabled + ? filterHelper.textStyle + : filterHelper.disableTextStyle, + height: filterHelper.tileHeight, + prefix: Icon( + const IconData( + 0xe701, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ), + color: + isSortDescendingEnabled + ? iconColor + : dataGridThemeHelper.filterPopupDisabledIconColor, + size: filterHelper.textStyle.fontSize! + 10, + ), + prefixPadding: EdgeInsets.only( + left: 4.0, + right: filterHelper.textStyle.fontSize!, + bottom: + filterHelper.textStyle.fontSize! > 14 + ? filterHelper.textStyle.fontSize! - 14 + : 0, + ), + onTap: + isSortDescendingEnabled + ? onHandleSortDescendingTap + : null, + child: Text( + grid_helper.getSortButtonText( + localizations, + false, + filterType, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (canShowSortingOptions) + Divider( + indent: 8.0, + endIndent: 8.0, + color: dataGridThemeHelper.filterPopupTopDividerColor, + ), + if (canShowClearFilterOption) + _FilterPopupMenuTile( + style: + isClearFilterEnabled + ? filterHelper.textStyle + : filterHelper.disableTextStyle, + height: filterHelper.tileHeight, + prefix: Icon( + const IconData( + 0xe703, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ), + size: filterHelper.textStyle.fontSize! + 8, + color: + isClearFilterEnabled + ? iconColor + : dataGridThemeHelper.filterPopupDisabledIconColor, + ), + prefixPadding: EdgeInsets.only( + left: 4.0, + right: filterHelper.textStyle.fontSize!, + bottom: + filterHelper.textStyle.fontSize! > 14 + ? filterHelper.textStyle.fontSize! - 14 + : 0, + ), + onTap: isClearFilterEnabled ? onHandleClearFilterTap : null, + child: Text( + getClearFilterText(localizations, showColumnName), + overflow: TextOverflow.ellipsis, + ), + ), + if (isAdvancedFilterEnabled) + _AdvancedFilterPopupMenu( + setState: setState, + dataGridConfiguration: widget.dataGridConfiguration, + advanceFilterTopPadding: advanceFilterTopPadding, + ), + if (isBothFilterEnabled) + _FilterPopupMenuTile( + style: filterHelper.textStyle, + height: filterHelper.tileHeight, + onTap: onHandleExpansionTileTap, + prefix: Icon( + filterHelper.getFilterForm(widget.column) == + FilteredFrom.advancedFilter + ? const IconData( + 0xe704, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ) + : const IconData( + 0xe702, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ), + size: filterHelper.textStyle.fontSize! + 6, + color: iconColor, + ), + suffix: Icon( + isAdvancedFilter + ? Icons.keyboard_arrow_down + : Icons.keyboard_arrow_right, + size: filterHelper.textStyle.fontSize! + 6, + color: + dataGridThemeHelper + .advancedFilterPopupDropdownIconColor ?? + iconColor, + ), + prefixPadding: EdgeInsets.only( + left: 4.0, + right: filterHelper.textStyle.fontSize!, + bottom: + filterHelper.textStyle.fontSize! > 14 + ? filterHelper.textStyle.fontSize! - 14 + : 0, + ), + child: Text( + grid_helper.getFilterTileText(localizations, filterType), + overflow: TextOverflow.ellipsis, + ), + ), + if (isCheckboxFilterEnabled || isBothFilterEnabled) + Visibility( + visible: isAdvancedFilter, + replacement: _CheckboxFilterMenu( + column: widget.column, + setState: setState, + viewSize: viewSize, + dataGridConfiguration: widget.dataGridConfiguration, + ), + child: + isMobile + ? ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewSize!.height, + ), + child: advanceFilterPopupMenu, + ) + : advanceFilterPopupMenu, + ), + if (!isMobile) + Divider( + height: 10, + color: dataGridThemeHelper.filterPopupBottomDividerColor, + ), + if (!isMobile) + Padding( + padding: const EdgeInsets.symmetric(vertical: 6.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + width: 120.0, + height: filterHelper.tileHeight - 8, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith< + Color? + >((Set states) { + // Issue: + // FLUT-7487-The buttons UX in the filter popup menu is not very intuitive when using Material 3 design. + // + // Fix: + // There is an issue with the button user experience (UX) in the filter popup menu, + // which is caused by the default background color of the "ElevatedButton" widget + // being set to the surface color in the Material 3 design. To address this issue, + // we set the background color of the button to the primary color if it is not disabled. + // This means that the default value is ignored, and the given color is used instead. + if (states.contains(WidgetState.disabled)) { + return dataGridThemeHelper + .okFilteringLabelDisabledButtonColor; + } else { + return dataGridThemeHelper + .okFilteringLabelButtonColor ?? + filterHelper.primaryColor; + } + }), + ), + onPressed: + canDisableOkButton() ? null : onHandleOkButtonTap, + child: Text( + localizations.okDataGridFilteringLabel, + style: TextStyle( + color: + dataGridThemeHelper.okFilteringLabelColor ?? + const Color(0xFFFFFFFF), + fontSize: filterHelper.textStyle.fontSize, + fontFamily: filterHelper.textStyle.fontFamily, + ), + ), + ), + ), + SizedBox( + width: 120.0, + height: filterHelper.tileHeight - 8, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: + dataGridThemeHelper + .cancelFilteringLabelButtonColor, + ), + onPressed: closePage, + child: Text( + localizations.cancelDataGridFilteringLabel, + style: TextStyle( + color: + dataGridThemeHelper + .cancelFilteringLabelColor ?? + filterHelper.primaryColor, + fontSize: filterHelper.textStyle.fontSize, + fontFamily: filterHelper.textStyle.fontFamily, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + if (isAdvancedFilterEnabled) { + isAdvancedFilter = true; + } + if (isAdvancedFilterEnabled && + !canShowClearFilterOption && + !canShowSortingOptions) { + advanceFilterTopPadding = 6; + } + + if (isMobile) { + return LayoutBuilder( + builder: + (BuildContext context, BoxConstraints constraints) => + buildPopup(viewSize: constraints.biggest), + ); + } else { + return buildPopup(); + } + } + + void onHandleSortTap(DataGridSortDirection direction) { + if (!widget.dataGridConfiguration.allowSorting) { + return; + } + + final sortedColumns = widget.dataGridConfiguration.source.sortedColumns; + + final oldSortedColumn = + sortedColumns.isNotEmpty + ? SortColumnDetails( + name: sortedColumns.last.name, + sortDirection: sortedColumns.last.sortDirection, + ) + : null; + + final newSortedColumn = SortColumnDetails( + name: widget.column.columnName, + sortDirection: direction, + ); + + final onColumnSortChangingHandler = + widget.dataGridConfiguration.onColumnSortChanging; + + if (onColumnSortChangingHandler?.call(newSortedColumn, oldSortedColumn) ?? + true) { + filterHelper.onSortButtonClick(widget.column, direction); + widget.dataGridConfiguration.onColumnSortChanged?.call( + newSortedColumn, + oldSortedColumn, + ); + } + Navigator.pop(context); + } + + void onHandleSortAscendingTap() { + onHandleSortTap(DataGridSortDirection.ascending); + } + + void onHandleSortDescendingTap() { + onHandleSortTap(DataGridSortDirection.descending); + } + + void onHandleClearFilterTap() { + filterHelper.onClearFilterButtonClick(widget.column); + Navigator.pop(context); + } + + void onHandleExpansionTileTap() { + setState(() { + isAdvancedFilter = !isAdvancedFilter; + }); + } + + void onHandleOkButtonTap() { + filterHelper.createFilterConditions(!isAdvancedFilter, widget.column); + Navigator.pop(context); + } + + void closePage() { + Navigator.pop(context); + } + + bool hasFilterConditions() { + return widget.dataGridConfiguration.source.filterConditions.containsKey( + widget.column.columnName, + ); + } + + bool canDisableOkButton() { + if (isAdvancedFilter) { + final DataGridAdvancedFilterHelper helper = + filterHelper.advancedFilterHelper; + return (helper.filterValue1 == null && helper.filterValue2 == null) && + !helper.disableFilterTypes.contains(helper.filterType1) && + !helper.disableFilterTypes.contains(helper.filterType2); + } else { + final bool? isSelectAllChecked = + filterHelper.checkboxFilterHelper.isSelectAllChecked; + return (isSelectAllChecked != null && !isSelectAllChecked) || + filterHelper.checkboxFilterHelper.items.isEmpty; + } + } + + bool canEnableSortButton(DataGridSortDirection sortDirection) { + final DataGridConfiguration configuration = widget.dataGridConfiguration; + if (configuration.allowSorting && widget.column.allowSorting) { + return configuration.source.sortedColumns.isEmpty || + !configuration.source.sortedColumns.any( + (SortColumnDetails column) => + column.name == widget.column.columnName && + column.sortDirection == sortDirection, + ); + } + return false; + } + + String getClearFilterText(SfLocalizations localization, bool showColumnName) { + if (showColumnName) { + return '${localization.clearFilterDataGridFilteringLabel} ${localization.fromDataGridFilteringLabel} "${widget.column.columnName}"'; + } else { + return localization.clearFilterDataGridFilteringLabel; + } + } +} + +class _FilterPopupMenuTile extends StatelessWidget { + const _FilterPopupMenuTile({ + Key? key, + required this.child, + this.onTap, + this.prefix, + this.suffix, + this.height, + required this.style, + this.prefixPadding = EdgeInsets.zero, + }) : super(key: key); + + final Widget child; + + final Widget? prefix; + + final Widget? suffix; + + final double? height; + + final TextStyle style; + + final VoidCallback? onTap; + + final EdgeInsets prefixPadding; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: height, + child: MaterialButton( + onPressed: onTap, + child: Row( + children: [ + Padding( + padding: prefixPadding, + child: SizedBox(width: 24.0, height: 24.0, child: prefix), + ), + Expanded(child: DefaultTextStyle(style: style, child: child)), + if (suffix != null) SizedBox(width: 40.0, child: suffix), + ], + ), + ), + ); + } +} + +class _FilterMenuDropdown extends StatelessWidget { + const _FilterMenuDropdown({ + required this.child, + required this.padding, + required this.height, + this.suffix, + Key? key, + }) : super(key: key); + + final Widget child; + + final Widget? suffix; + + final double height; + + final EdgeInsets padding; + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: SizedBox( + height: height, + child: Row( + children: [ + Expanded(child: child), + if (suffix != null) + Padding(padding: const EdgeInsets.only(left: 10), child: suffix), + ], + ), + ), + ); + } +} + +class _CheckboxFilterMenu extends StatelessWidget { + _CheckboxFilterMenu({ + Key? key, + required this.setState, + required this.column, + required this.viewSize, + required this.dataGridConfiguration, + }) : super(key: key); + + final StateSetter setState; + + final DataGridConfiguration dataGridConfiguration; + + final GridColumn column; + + final Size? viewSize; + + final FocusNode checkboxFocusNode = FocusNode(skipTraversal: true); + + bool get isMobile { + return !dataGridConfiguration.isDesktop; + } + + DataGridCheckboxFilterHelper get filterHelper { + return dataGridConfiguration.dataGridFilterHelper!.checkboxFilterHelper; + } + + @override + Widget build(BuildContext context) { + final Color onSurface = dataGridConfiguration.colorScheme!.onSurface; + + return Column( + children: [ + _buildSearchBox(onSurface, context), + _buildCheckboxListView(context), + ], + ); + } + + Widget _buildCheckboxListView(BuildContext context) { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + + // 340.0 it's a occupied height in the current view by the other widgets. + double occupiedHeight = 340.0; + + // Need to set the Checkbox Filter height in the mobile platform + // based on the options enabled in the Filter popup menu + if (column.filterPopupMenuOptions != null && isMobile) { + if (!column.filterPopupMenuOptions!.canShowSortingOptions) { + // 16.0 is the height of the divider shown below the sorting options + occupiedHeight -= (helper.tileHeight * 2) + 16.0; + } + if (!column.filterPopupMenuOptions!.canShowClearFilterOption) { + occupiedHeight -= helper.tileHeight; + } + if (column.filterPopupMenuOptions!.filterMode == + FilterMode.checkboxFilter) { + occupiedHeight -= helper.tileHeight; + } + } + + // Gets the remaining height of the current view to fill the checkbox + // listview in the mobile platform. + final double checkboxHeight = + isMobile ? max(viewSize!.height - occupiedHeight, 120.0) : 200.0; + final double selectAllButtonHeight = + isMobile ? helper.tileHeight - 4 : helper.tileHeight; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + + return Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Visibility( + visible: filterHelper.items.isNotEmpty, + replacement: SizedBox( + height: checkboxHeight + selectAllButtonHeight, + child: Center( + child: Text( + dataGridConfiguration + .localizations + .noMatchesDataGridFilteringLabel, + style: TextStyle( + color: dataGridThemeHelper.noMatchesFilteringLabelColor, + ), + ), + ), + ), + child: CheckboxTheme( + data: CheckboxThemeData( + side: BorderSide( + width: 2.0, + color: dataGridConfiguration.colorScheme!.onSurface[153]!, + ), + + // Issue: The checkbox fill color is applied even when the checkbox is not selected. + // The framework changed this behavior in Flutter 3.13.0 onwards. + // Refer to the issue: https://github.com/flutter/flutter/issues/130295 + // Guide: https://github.com/flutter/website/commit/224bdc9cc3e8dfb8af94d76f275824cdcf76ba4d + // Fix: As per the framework guide, we have to set the fillColor property to transparent + // when the checkbox is not selected. + fillColor: WidgetStateProperty.resolveWith(( + Set states, + ) { + if (!states.contains(WidgetState.selected)) { + return dataGridConfiguration.colorScheme!.transparent; + } + return helper.primaryColor; + }), + ), + child: Column( + children: [ + _FilterPopupMenuTile( + style: helper.textStyle, + height: selectAllButtonHeight, + prefixPadding: const EdgeInsets.only(left: 4.0, right: 10.0), + prefix: Checkbox( + checkColor: dataGridThemeHelper.filterPopupCheckColor, + fillColor: dataGridThemeHelper.filterPopupCheckboxFillColor, + focusNode: checkboxFocusNode, + tristate: filterHelper.isSelectAllInTriState, + value: filterHelper.isSelectAllChecked, + onChanged: (_) => onHandleSelectAllCheckboxTap(), + ), + onTap: onHandleSelectAllCheckboxTap, + child: Text( + dataGridConfiguration + .localizations + .selectAllDataGridFilteringLabel, + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox( + height: checkboxHeight, + child: ListView.builder( + key: const ValueKey( + 'datagrid_filtering_checkbox_listView', + ), + prototypeItem: buildCheckboxTile( + filterHelper.items.length - 1, + helper.textStyle, + ), + itemCount: filterHelper.items.length, + itemBuilder: + (BuildContext context, int index) => + buildCheckboxTile(index, helper.textStyle), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildSearchBox(Color onSurface, BuildContext context) { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + + void onSearchboxSubmitted(String value) { + if (filterHelper.items.isNotEmpty) { + helper.createFilterConditions(true, column); + Navigator.pop(context); + } else { + filterHelper.searchboxFocusNode.requestFocus(); + } + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: SizedBox( + height: isMobile ? helper.tileHeight : helper.tileHeight - 4, + child: TextField( + cursorColor: dataGridThemeHelper.searchAreaCursorColor, + style: helper.textStyle, + key: const ValueKey('datagrid_filtering_search_textfield'), + focusNode: filterHelper.searchboxFocusNode, + controller: filterHelper.textController, + onChanged: onHandleSearchTextFieldChanged, + onSubmitted: onSearchboxSubmitted, + decoration: InputDecoration( + focusedBorder: + dataGridThemeHelper.searchAreaFocusedBorderColor == null + ? null + : OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper.searchAreaFocusedBorderColor!, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper.filterPopupInputBorderColor ?? + dataGridThemeHelper.filterPopupBorderColor!, + ), + ), + suffixIcon: Visibility( + visible: filterHelper.textController.text.isEmpty, + replacement: IconButton( + key: const ValueKey( + 'datagrid_filtering_clearSearch_icon', + ), + iconSize: helper.textStyle.fontSize! + 8, + padding: EdgeInsets.zero, + constraints: const BoxConstraints.tightFor( + width: 22.0, + height: 22.0, + ), + onPressed: () { + filterHelper.textController.clear(); + onHandleSearchTextFieldChanged(''); + }, + icon: Icon( + Icons.close, + color: + dataGridThemeHelper.closeIconColor ?? + dataGridThemeHelper.filterPopupIconColor, + ), + ), + child: Icon( + Icons.search, + size: helper.textStyle.fontSize! + 8, + color: + dataGridThemeHelper.searchIconColor ?? + dataGridThemeHelper.filterPopupIconColor, + ), + ), + contentPadding: + isMobile + ? const EdgeInsets.all(16.0) + : const EdgeInsets.all(8.0), + border: const OutlineInputBorder(), + hintStyle: helper.textStyle, + hintText: + dataGridConfiguration + .localizations + .searchDataGridFilteringLabel, + ), + ), + ), + ); + } + + Widget? buildCheckboxTile(int index, TextStyle style) { + if (filterHelper.items.isNotEmpty) { + final FilterElement element = filterHelper.items[index]; + final String displayText = dataGridConfiguration.dataGridFilterHelper! + .getDisplayValue(element.value); + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + return _FilterPopupMenuTile( + style: style, + height: isMobile ? style.fontSize! + 34 : style.fontSize! + 26, + prefixPadding: const EdgeInsets.only(left: 4.0, right: 10.0), + prefix: Checkbox( + checkColor: dataGridThemeHelper.filterPopupCheckColor, + fillColor: dataGridThemeHelper.filterPopupCheckboxFillColor, + focusNode: checkboxFocusNode, + value: element.isSelected, + onChanged: (_) => onHandleCheckboxTap(element), + ), + onTap: () => onHandleCheckboxTap(element), + child: Text(displayText, overflow: TextOverflow.ellipsis), + ); + } + return null; + } + + void onHandleCheckboxTap(FilterElement element) { + element.isSelected = !element.isSelected; + filterHelper.ensureSelectAllCheckboxState(); + setState(() {}); + } + + void onHandleSelectAllCheckboxTap() { + final bool useSelected = + filterHelper.isSelectAllInTriState || + (filterHelper.isSelectAllChecked != null && + filterHelper.isSelectAllChecked!); + for (final FilterElement item in filterHelper.filterCheckboxItems) { + item.isSelected = !useSelected; + } + + filterHelper.ensureSelectAllCheckboxState(); + setState(() {}); + } + + void onHandleSearchTextFieldChanged(String value) { + filterHelper.onSearchTextFieldTextChanged(value); + setState(() {}); + } +} + +class _AdvancedFilterPopupMenu extends StatelessWidget { + const _AdvancedFilterPopupMenu({ + Key? key, + required this.setState, + required this.dataGridConfiguration, + required this.advanceFilterTopPadding, + }) : super(key: key); + + final StateSetter setState; + + final DataGridConfiguration dataGridConfiguration; + + final double advanceFilterTopPadding; + + bool get isMobile { + return !dataGridConfiguration.isDesktop; + } + + DataGridAdvancedFilterHelper get filterHelper { + return dataGridConfiguration.dataGridFilterHelper!.advancedFilterHelper; + } + + @override + Widget build(BuildContext context) { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Column( + children: [ + _FilterMenuDropdown( + height: helper.textStyle.fontSize! + 2, + padding: EdgeInsets.only(top: advanceFilterTopPadding, bottom: 8.0), + child: Text( + '${dataGridConfiguration.localizations.showRowsWhereDataGridFilteringLabel}:', + style: TextStyle( + fontFamily: helper.textStyle.fontFamily, + fontSize: helper.textStyle.fontSize, + color: helper.textStyle.color, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + _FilterMenuDropdown( + height: isMobile ? helper.tileHeight + 4 : helper.tileHeight - 4, + padding: const EdgeInsets.only(top: 8.0), + child: _buildFilterTypeDropdown(isFirstButton: true), + ), + _FilterMenuDropdown( + height: isMobile ? helper.tileHeight + 4 : helper.tileHeight - 4, + padding: const EdgeInsets.only(top: 8.0, bottom: 16.0), + suffix: _getTrailingWidget(context, true), + child: _buildFilterValueDropdown(isTopButton: true), + ), + _buildRadioButtons(), + _FilterMenuDropdown( + height: isMobile ? helper.tileHeight + 4 : helper.tileHeight - 4, + padding: const EdgeInsets.only(top: 16.0, bottom: 8.0), + child: _buildFilterTypeDropdown(isFirstButton: false), + ), + _FilterMenuDropdown( + height: isMobile ? helper.tileHeight + 4 : helper.tileHeight - 4, + padding: const EdgeInsets.only(bottom: 8.0), + suffix: _getTrailingWidget(context, false), + child: _buildFilterValueDropdown(isTopButton: false), + ), + ], + ), + ); + } + + Widget _buildRadioButtons() { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + final SfLocalizations localizations = dataGridConfiguration.localizations; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + + void handleChanged(bool? value) { + setState(() { + filterHelper.isOrPredicate = !filterHelper.isOrPredicate; + }); + } + + return Row( + children: [ + Row( + children: [ + SizedBox.fromSize( + size: const Size(24.0, 24.0), + child: Radio( + key: const ValueKey('datagrid_filtering_and_button'), + value: false, + activeColor: + dataGridThemeHelper.andRadioActiveColor ?? + helper.primaryColor, + fillColor: dataGridThemeHelper.andRadioFillColor, + onChanged: handleChanged, + groupValue: filterHelper.isOrPredicate, + ), + ), + const SizedBox(width: 8.0), + Text( + localizations.andDataGridFilteringLabel, + style: helper.textStyle, + ), + ], + ), + const SizedBox(width: 16.0), + Row( + children: [ + SizedBox.fromSize( + size: const Size(24.0, 24.0), + child: Radio( + key: const ValueKey('datagrid_filtering_or_button'), + value: true, + activeColor: + dataGridThemeHelper.orRadioActiveColor ?? + helper.primaryColor, + fillColor: dataGridThemeHelper.orRadioFillColor, + onChanged: handleChanged, + groupValue: filterHelper.isOrPredicate, + ), + ), + const SizedBox(width: 8.0), + Text( + localizations.orDataGridFilteringLabel, + style: helper.textStyle, + ), + ], + ), + ], + ); + } + + Widget _buildFilterValueDropdown({required bool isTopButton}) { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + + void setValue(Object? value) { + if (isTopButton) { + filterHelper.filterValue1 = value; + } else { + filterHelper.filterValue2 = value; + } + setState(() {}); + } + + TextInputType getTextInputType() { + if (filterHelper.advancedFilterType == AdvancedFilterType.text) { + return TextInputType.text; + } + return TextInputType.number; + } + + List? getInputFormatters() { + if (filterHelper.advancedFilterType == AdvancedFilterType.date) { + return [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9-]')), + ]; + } else if (filterHelper.advancedFilterType == + AdvancedFilterType.numeric) { + return [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9.]')), + ]; + } + return null; + } + + Widget buildDropdownFormField() { + return DropdownButtonHideUnderline( + child: DropdownButtonFormField( + dropdownColor: + dataGridThemeHelper.advancedFilterPopupDropdownColor ?? + dataGridThemeHelper.filterPopupOuterColor, + key: + isTopButton + ? const ValueKey( + 'datagrid_filtering_filterValue_first_button', + ) + : const ValueKey( + 'datagrid_filtering_filterValue_second_button', + ), + decoration: InputDecoration( + focusedBorder: + dataGridThemeHelper + .advancedFilterValueDropdownFocusedBorderColor == + null + ? null + : OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper + .advancedFilterValueDropdownFocusedBorderColor!, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper.filterPopupInputBorderColor ?? + dataGridThemeHelper.filterPopupBorderColor!, + ), + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 12.0, + ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!, + ), + ), + ), + icon: Icon( + Icons.keyboard_arrow_down, + size: helper.textStyle.fontSize! + 8, + color: + dataGridThemeHelper.advancedFilterValueDropdownIconColor ?? + dataGridThemeHelper.filterPopupIconColor, + ), + isExpanded: true, + value: + isTopButton + ? filterHelper.filterValue1 + : filterHelper.filterValue2, + style: helper.textStyle, + items: + filterHelper.items + .map>( + (FilterElement value) => DropdownMenuItem( + value: value.value, + child: Text(helper.getDisplayValue(value.value)), + ), + ) + .toList(), + onChanged: enableDropdownButton(isTopButton) ? setValue : null, + ), + ); + } + + Widget buildTextField() { + return TextField( + cursorColor: dataGridThemeHelper.advancedFilterValueTextAreaCursorColor, + style: helper.textStyle, + key: + isTopButton + ? const ValueKey( + 'datagrid_filtering_filterValue_first_button', + ) + : const ValueKey( + 'datagrid_filtering_filterValue_second_button', + ), + controller: + isTopButton + ? filterHelper.firstValueTextController + : filterHelper.secondValueTextController, + maxLengthEnforcement: MaxLengthEnforcement.enforced, + keyboardType: getTextInputType(), + inputFormatters: getInputFormatters(), + onChanged: (String? value) { + value = value != null && value.isEmpty ? null : value; + setValue(helper.getActualValue(value)); + }, + decoration: InputDecoration( + focusedBorder: + dataGridThemeHelper + .advancedFilterValueDropdownFocusedBorderColor == + null + ? null + : OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper + .advancedFilterValueDropdownFocusedBorderColor!, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper.filterPopupInputBorderColor ?? + dataGridThemeHelper.filterPopupBorderColor!, + ), + ), + contentPadding: + isMobile ? const EdgeInsets.all(16.0) : const EdgeInsets.all(8.0), + border: const OutlineInputBorder(), + hintStyle: const TextStyle(fontSize: 14.0), + ), + ); + } + + return canBuildTextField(isTopButton) + ? buildTextField() + : buildDropdownFormField(); + } + + Widget _buildFilterTypeDropdown({required bool isFirstButton}) { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + + void handleChanged(String? value) { + if (isFirstButton) { + filterHelper.filterType1 = value; + } else { + filterHelper.filterType2 = value; + } + + // Need to set the filter values to null if the type is null or empty. + if (filterHelper.disableFilterTypes.contains(value)) { + if (isFirstButton) { + filterHelper.filterValue1 = null; + } else { + filterHelper.filterValue2 = null; + } + } + + // Need to set the current filter value to the controller's text to retains + // the same value in the text field itself. + if (filterHelper.textFieldFilterTypes.contains(value)) { + if (isFirstButton) { + filterHelper.firstValueTextController.text = helper.getDisplayValue( + filterHelper.filterValue1, + ); + } else { + filterHelper.secondValueTextController.text = helper.getDisplayValue( + filterHelper.filterValue2, + ); + } + } else { + // Need to set the filter values to null if that value doesn't exist in + // the data source when the filter type switching from the text field to + // dropdown. + bool isInValidText(Object? filterValue) => + !filterHelper.items.any( + (FilterElement element) => element.value == filterValue, + ); + if (isFirstButton) { + if (isInValidText(filterHelper.filterValue1)) { + filterHelper.filterValue1 = null; + } + } else { + if (isInValidText(filterHelper.filterValue2)) { + filterHelper.filterValue2 = null; + } + } + } + setState(() {}); + } + + return DropdownButtonHideUnderline( + child: DropdownButtonFormField( + dropdownColor: + dataGridThemeHelper.advancedFilterPopupDropdownColor ?? + dataGridThemeHelper.filterPopupOuterColor, + key: + isFirstButton + ? const ValueKey( + 'datagrid_filtering_filterType_first_button', + ) + : const ValueKey( + 'datagrid_filtering_filterType_second_button', + ), + decoration: InputDecoration( + focusedBorder: + dataGridThemeHelper + .advancedFilterTypeDropdownFocusedBorderColor == + null + ? null + : OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper + .advancedFilterTypeDropdownFocusedBorderColor!, + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + dataGridThemeHelper.filterPopupInputBorderColor ?? + dataGridThemeHelper.filterPopupBorderColor!, + ), + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 12.0, + ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!, + ), + ), + ), + icon: Icon( + Icons.keyboard_arrow_down, + size: helper.textStyle.fontSize! + 8, + color: + dataGridThemeHelper.advancedFilterTypeDropdownIconColor ?? + dataGridThemeHelper.filterPopupIconColor, + ), + isExpanded: true, + value: + isFirstButton ? filterHelper.filterType1 : filterHelper.filterType2, + style: helper.textStyle, + items: + filterHelper.filterTypeItems + .map>( + (String value) => DropdownMenuItem( + value: value, + child: Text(value), + ), + ) + .toList(), + onChanged: handleChanged, + ), + ); + } + + Widget? _getTrailingWidget(BuildContext context, bool isFirstButton) { + final DataGridFilterHelper helper = + dataGridConfiguration.dataGridFilterHelper!; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + + if (filterHelper.advancedFilterType == AdvancedFilterType.numeric) { + return null; + } + + Future handleDatePickerTap() async { + final DateTime currentDate = DateTime.now(); + final DateTime firstDate = filterHelper.items.first.value as DateTime; + final DateTime lastDate = filterHelper.items.last.value as DateTime; + DateTime initialDate = firstDate; + + if ((currentDate.isAfter(firstDate) && currentDate.isBefore(lastDate)) || + (lastDate.day == currentDate.day && + lastDate.month == currentDate.month && + lastDate.year == currentDate.year)) { + initialDate = currentDate; + } + DateTime? selectedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + helpText: 'Select a date', + ); + + // Need to return if user presses the cancel button to close the data picker view. + if (selectedDate == null) { + return; + } + + final bool isVaildDate = filterHelper.items.any( + (FilterElement element) => element.value == selectedDate, + ); + final String? filterType = + isFirstButton ? filterHelper.filterType1 : filterHelper.filterType2; + final bool isValidType = + filterType != null && + filterHelper.textFieldFilterTypes.contains(filterType); + selectedDate = isVaildDate || isValidType ? selectedDate : null; + + setState(() { + final String newValue = helper.getDisplayValue(selectedDate); + if (isFirstButton) { + filterHelper.filterValue1 = selectedDate; + filterHelper.firstValueTextController.text = newValue; + } else { + filterHelper.filterValue2 = selectedDate; + filterHelper.secondValueTextController.text = newValue; + } + }); + } + + void handleCaseSensitiveTap() { + setState(() { + if (isFirstButton) { + filterHelper.isCaseSensitive1 = !filterHelper.isCaseSensitive1; + } else { + filterHelper.isCaseSensitive2 = !filterHelper.isCaseSensitive2; + } + }); + } + + Color? getColor() { + final bool isSelected = + isFirstButton + ? filterHelper.isCaseSensitive1 + : filterHelper.isCaseSensitive2; + return isSelected + ? dataGridThemeHelper.caseSensitiveIconActiveColor ?? + helper.primaryColor + : dataGridThemeHelper.caseSensitiveIconColor ?? + dataGridThemeHelper.filterPopupIconColor!; + } + + bool canEnableButton() { + final String? value = + isFirstButton ? filterHelper.filterType1 : filterHelper.filterType2; + return value != null && !filterHelper.disableFilterTypes.contains(value); + } + + if (filterHelper.advancedFilterType == AdvancedFilterType.text) { + const IconData caseSensitiveIcon = IconData( + 0xe705, + fontFamily: 'FilterIcon', + fontPackage: 'syncfusion_flutter_datagrid', + ); + return IconButton( + key: + isFirstButton + ? const ValueKey( + 'datagrid_filtering_case_sensitive_first_button', + ) + : const ValueKey( + 'datagrid_filtering_case_sensitive_second_button', + ), + iconSize: 22.0, + splashRadius: 20.0, + padding: EdgeInsets.zero, + constraints: const BoxConstraints.tightFor(width: 22.0, height: 22.0), + onPressed: canEnableButton() ? handleCaseSensitiveTap : null, + icon: Icon(caseSensitiveIcon, size: 22.0, color: getColor()), + ); + } else { + return IconButton( + key: + isFirstButton + ? const ValueKey( + 'datagrid_filtering_date_picker_first_button', + ) + : const ValueKey( + 'datagrid_filtering_date_picker_second_button', + ), + iconSize: 22.0, + splashRadius: 20.0, + padding: EdgeInsets.zero, + constraints: const BoxConstraints.tightFor(width: 22.0, height: 22.0), + onPressed: canEnableButton() ? handleDatePickerTap : null, + icon: Icon( + Icons.calendar_today_outlined, + size: 22.0, + color: + dataGridThemeHelper.calendarIconColor ?? + dataGridConfiguration.colorScheme!.onSurface[153], + ), + ); + } + } + + bool enableDropdownButton(bool isTopButton) { + return !filterHelper.disableFilterTypes.contains( + isTopButton ? filterHelper.filterType1 : filterHelper.filterType2, + ); + } + + bool canBuildTextField(bool isTopButton) { + final String filterType = + isTopButton ? filterHelper.filterType1! : filterHelper.filterType2!; + if (filterHelper.textFieldFilterTypes.contains(filterType)) { + return true; + } + return false; + } +} + +BorderDirectional _getCellBorder( + DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, +) { + final Color borderColor = + dataGridConfiguration.dataGridThemeHelper!.gridLineColor!; + final double borderWidth = + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth!; + + final int rowIndex = + (dataCell.rowSpan > 0) + ? dataCell.rowIndex - dataCell.rowSpan + : dataCell.rowIndex; + final int columnIndex = dataCell.columnIndex; + final bool isStackedHeaderCell = + dataCell.cellType == CellType.stackedHeaderCell; + final bool isHeaderCell = dataCell.cellType == CellType.headerCell; + final bool isTableSummaryCell = + dataCell.cellType == CellType.tableSummaryCell; + final bool isRowCell = dataCell.cellType == CellType.gridCell; + final bool isIndentCell = dataCell.cellType == CellType.indentCell; + final bool isCaptionSummaryCell = + dataCell.cellType == CellType.captionSummaryCell; + final bool isCaptionSummaryCoveredRow = + dataCell.dataRow!.rowType == RowType.captionSummaryCoveredRow; + final bool isStackedHeaderRow = + dataCell.dataRow!.rowType == RowType.stackedHeaderRow; + final bool isTableSummaryCoveredRow = + dataCell.dataRow!.rowType == RowType.tableSummaryCoveredRow; + final bool isHeaderRow = dataCell.dataRow!.rowType == RowType.headerRow; + final bool isDataRow = dataCell.dataRow!.rowType == RowType.dataRow; + final bool isTableSummaryRow = + dataCell.dataRow!.rowType == RowType.tableSummaryRow; + + // To skip bottom border for the top data row of the starting row of bottom table + // summary rows and draw top border for the bottom summary start row instead. + final bool canSkipBottomBorder = + grid_helper.getTableSummaryCount( + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ) > + 0 && + dataCell.rowIndex == + grid_helper.getStartBottomSummaryRowIndex(dataGridConfiguration) - 1; + + // To draw the top border for the starting row of the bottom table summary row. + final bool canDrawStartBottomSummaryRowTopBorder = + isTableSummaryCell && + dataCell.rowIndex == + grid_helper.getStartBottomSummaryRowIndex(dataGridConfiguration); + + final int groupedColumnsLength = + dataGridConfiguration.source.groupedColumns.length; + + final bool isGrouping = + dataGridConfiguration.source.groupedColumns.isNotEmpty; + + final bool canDrawHeaderHorizontalBorder = + (dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.both) && + (isHeaderCell || + isStackedHeaderCell || + (isIndentCell && (isHeaderRow || isStackedHeaderRow))); + + final bool canDrawHeaderVerticalBorder = + (dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.vertical || + dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.both) && + (isHeaderCell || + isStackedHeaderCell || + (isIndentCell && (isHeaderRow || isStackedHeaderRow))); + + final ColumnDragAndDropController dragAndDropController = + dataGridConfiguration.columnDragAndDropController; + + final int indentCount = dataGridConfiguration.source.groupedColumns.length; + + final bool canDrawLeftColumnDragAndDropIndicator = + dataGridConfiguration.allowColumnsDragging && + dragAndDropController.canDrawRightIndicator != null && + !dragAndDropController.canDrawRightIndicator! && + dragAndDropController.columnIndex == dataCell.columnIndex && + (dragAndDropController.dragColumnStartIndex! + + indentCount + + (dataGridConfiguration.showCheckboxColumn ? 1 : 0)) != + dataCell.columnIndex && + isHeaderCell; + + final bool canDrawRightColumnDragAndDropIndicator = + dataGridConfiguration.allowColumnsDragging && + dragAndDropController.canDrawRightIndicator != null && + dragAndDropController.canDrawRightIndicator! && + dragAndDropController.columnIndex == dataCell.columnIndex && + (dragAndDropController.dragColumnStartIndex! + + indentCount + + (dataGridConfiguration.showCheckboxColumn ? 1 : 0)) != + dataCell.columnIndex && + isHeaderCell; + + final bool canSkipLeftColumnDragAndDropIndicator = + canDrawLeftColumnDragAndDropIndicator && + (dragAndDropController.dragColumnStartIndex! + + indentCount + + (dataGridConfiguration.showCheckboxColumn ? 2 : 1) == + dataCell.columnIndex || + (dataGridConfiguration.showCheckboxColumn && + dragAndDropController.columnIndex == 0)); + + final bool canSkipRightColumnDragAndDropIndicator = + canDrawRightColumnDragAndDropIndicator && + (dragAndDropController.dragColumnStartIndex! + + indentCount - + (dataGridConfiguration.showCheckboxColumn ? 0 : 1) == + dataCell.columnIndex || + (dataGridConfiguration.showCheckboxColumn && + dragAndDropController.columnIndex == 0)); + + final bool canDrawHorizontalBorder = + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both) && + !isHeaderCell && + !isStackedHeaderCell; + + final bool canDrawVerticalBorder = + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.vertical || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both) && + !isStackedHeaderCell && + !isTableSummaryCell && + !isHeaderCell; + + final GridColumn firstVisibleColumn = dataGridConfiguration.columns + .firstWhere((GridColumn column) => column.visible && column.width != 0.0); + + final GridColumn lastVisibleColumn = dataGridConfiguration.columns.lastWhere( + (GridColumn column) => column.visible && column.width != 0.0, + ); + + final int firstVisibleColumnIndex = + (isGrouping && + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth > 0) + ? 0 + : grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + dataGridConfiguration.columns.indexOf(firstVisibleColumn), + ); + + final int lastVisibleColumnIndex = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + dataGridConfiguration.columns.indexOf(lastVisibleColumn), + ); + + final int lastRowIndex = selection_helper.getLastRowIndex( + dataGridConfiguration, + true, + ); + + final bool isLastStackedHeaderCell = + isStackedHeaderCell && + (columnIndex == 0 || columnIndex > firstVisibleColumnIndex) && + ((dataCell.columnSpan + columnIndex) >= lastVisibleColumnIndex); + + // To draw the top outer border for the DataGrid. + final bool canDrawGridTopOuterBorder = + rowIndex == 0 && + dataGridConfiguration.headerGridLinesVisibility != + GridLinesVisibility.none; + + // To draw the bottom outer border for the DataGrid. + final bool canDrawGridBottomOuterBorder = + rowIndex == lastRowIndex && + dataGridConfiguration.gridLinesVisibility != GridLinesVisibility.none; + + // To draw the right outer border for the DataGrid Headers. + final bool canDrawGridHeaderRightOuterBorder = + dataGridConfiguration.headerGridLinesVisibility != + GridLinesVisibility.none && + ((isHeaderRow && columnIndex == lastVisibleColumnIndex) || + isLastStackedHeaderCell); + + // To draw the right outer border for the DataGrid Rows. + final bool canDrawGridRightOuterBorder = + dataGridConfiguration.gridLinesVisibility != GridLinesVisibility.none && + (((isRowCell || isTableSummaryCell) && + columnIndex == lastVisibleColumnIndex) || + isCaptionSummaryCoveredRow || + isTableSummaryCoveredRow); + + // To draw the left outer border for the DataGrid Headers. + final bool canDrawGridHeaderLeftOuterBorder = + dataGridConfiguration.headerGridLinesVisibility != + GridLinesVisibility.none && + (isHeaderCell || + isStackedHeaderCell || + (isIndentCell && (isHeaderRow || isStackedHeaderRow))) && + (columnIndex <= firstVisibleColumnIndex); + + // To draw the left outer border for the DataGrid Rows. + final bool canDrawGridLeftOuterBorder = + dataGridConfiguration.gridLinesVisibility != GridLinesVisibility.none && + columnIndex <= firstVisibleColumnIndex && + (!isHeaderRow && !isStackedHeaderRow); + + // Frozen column and row checking + final bool canDrawBottomFrozenBorder = + dataGridConfiguration.frozenRowsCount.isFinite && + dataGridConfiguration.frozenRowsCount > 0 && + grid_helper.getLastFrozenRowIndex(dataGridConfiguration) == rowIndex; + + final bool canDrawTopFrozenBorder = + dataGridConfiguration.footerFrozenRowsCount.isFinite && + dataGridConfiguration.footerFrozenRowsCount > 0 && + grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) == + rowIndex; + + final bool canDrawRightFrozenBorder = + dataGridConfiguration.frozenColumnsCount.isFinite && + dataGridConfiguration.frozenColumnsCount > 0 && + grid_helper.getLastFrozenColumnIndex(dataGridConfiguration) == + columnIndex && + !isStackedHeaderCell; + + final bool canDrawLeftFrozenBorder = + dataGridConfiguration.footerFrozenColumnsCount.isFinite && + dataGridConfiguration.footerFrozenColumnsCount > 0 && + grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration) == + columnIndex; + + final bool isFrozenPaneElevationApplied = + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation! > 0.0; + + final Color frozenPaneLineColor = + dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineColor!; + + final double frozenPaneLineWidth = + dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineWidth!; + + final bool canDrawIndentRightBorder = + canDrawVerticalBorder && + (dataGridConfiguration.source.groupedColumns.isNotEmpty && + (columnIndex >= 0 && + columnIndex < groupedColumnsLength && + isIndentCell && + columnIndex < dataCell.dataRow!.rowLevel - 1) || + (isDataRow && isIndentCell && columnIndex < groupedColumnsLength)); + final Object? rowData = dataCell.dataRow!.rowData; + + final bool canDrawTableSummaryRowIndentBorder = + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both) && + (isIndentCell && isTableSummaryRow); + + BorderSide getLeftBorder() { + if ((columnIndex == 0 && + (canDrawVerticalBorder || + canDrawHeaderVerticalBorder || + canDrawLeftColumnDragAndDropIndicator)) || + canDrawLeftFrozenBorder || + canDrawGridHeaderLeftOuterBorder || + canDrawGridLeftOuterBorder) { + if (canDrawLeftColumnDragAndDropIndicator && + !canSkipLeftColumnDragAndDropIndicator) { + return BorderSide( + width: + dataGridConfiguration + .dataGridThemeHelper! + .columnDragIndicatorStrokeWidth!, + color: + dataGridConfiguration + .dataGridThemeHelper! + .columnDragIndicatorColor!, + ); + } + if (canDrawLeftFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { + return BorderSide( + width: frozenPaneLineWidth, + color: frozenPaneLineColor, + ); + } else if ((columnIndex > 0 && + ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && + !canDrawLeftFrozenBorder)) || + (canDrawGridLeftOuterBorder || canDrawGridHeaderLeftOuterBorder)) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } + } else if (canDrawLeftColumnDragAndDropIndicator && + !canSkipLeftColumnDragAndDropIndicator) { + return BorderSide( + width: + dataGridConfiguration + .dataGridThemeHelper! + .columnDragIndicatorStrokeWidth!, + color: + dataGridConfiguration + .dataGridThemeHelper! + .columnDragIndicatorColor!, + ); + } else { + return BorderSide.none; + } + } + + BorderSide getTopBorder() { + if ((rowIndex == 0 && + (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder)) || + canDrawTopFrozenBorder || + canDrawStartBottomSummaryRowTopBorder || + canDrawGridTopOuterBorder) { + if (canDrawTopFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { + return BorderSide( + width: frozenPaneLineWidth, + color: frozenPaneLineColor, + ); + } else if ((canDrawHorizontalBorder && + canDrawStartBottomSummaryRowTopBorder) || + canDrawGridTopOuterBorder) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } + } else { + return BorderSide.none; + } + } + + BorderSide getRightBorder() { if (canDrawVerticalBorder || canDrawHeaderVerticalBorder || - canDrawRightFrozenBorder) { + canDrawRightFrozenBorder || + canDrawRightColumnDragAndDropIndicator || + canDrawIndentRightBorder || + canDrawGridHeaderRightOuterBorder || + canDrawGridRightOuterBorder) { if (canDrawRightFrozenBorder && !isStackedHeaderCell && !isFrozenPaneElevationApplied) { return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && + width: frozenPaneLineWidth, + color: frozenPaneLineColor, + ); + } else if (canDrawRightColumnDragAndDropIndicator && + !canSkipRightColumnDragAndDropIndicator) { + return BorderSide( + width: + dataGridConfiguration + .dataGridThemeHelper! + .columnDragIndicatorStrokeWidth!, + color: + dataGridConfiguration + .dataGridThemeHelper! + .columnDragIndicatorColor!, + ); + } else if ((canDrawVerticalBorder || + canDrawHeaderVerticalBorder || + canDrawGridHeaderRightOuterBorder || + canDrawGridRightOuterBorder) && + !canDrawRightFrozenBorder && + !isCaptionSummaryCell && + !isIndentCell) { + return BorderSide(width: borderWidth, color: borderColor); + } else if ((canDrawIndentRightBorder || + canDrawHeaderVerticalBorder || + isCaptionSummaryCell) && !canDrawRightFrozenBorder) { return BorderSide(width: borderWidth, color: borderColor); } else { @@ -754,17 +3163,37 @@ BorderDirectional _getCellBorder( } } - BorderSide _getBottomBorder() { + BorderSide getBottomBorder() { if (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder || - canDrawBottomFrozenBorder) { + canDrawBottomFrozenBorder || + canDrawGridBottomOuterBorder) { if (canDrawBottomFrozenBorder && !isStackedHeaderCell && !isFrozenPaneElevationApplied) { return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (!canDrawBottomFrozenBorder && !canSkipBottomBorder) { + width: frozenPaneLineWidth, + color: frozenPaneLineColor, + ); + } else if ((!canDrawBottomFrozenBorder && + !canSkipBottomBorder && + !isIndentCell) || + canDrawGridBottomOuterBorder) { return BorderSide(width: borderWidth, color: borderColor); + } else if (isGrouping) { + if (canDrawHeaderHorizontalBorder || + canDrawTableSummaryRowIndentBorder) { + return BorderSide(width: borderWidth, color: borderColor); + } + final dynamic group = getNextGroupInfo(rowData, dataGridConfiguration); + if (group is Group && + isIndentCell && + columnIndex >= group.level - 1 && + rowIndex >= dataGridConfiguration.headerLineCount) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } } else { return BorderSide.none; } @@ -774,39 +3203,44 @@ BorderDirectional _getCellBorder( } return BorderDirectional( - start: _getLeftBorder(), - top: _getTopBorder(), - end: _getRightBorder(), - bottom: _getBottomBorder(), + start: getLeftBorder(), + top: getTopBorder(), + end: getRightBorder(), + bottom: getBottomBorder(), ); } -Widget _wrapInsideCellContainer( - {required DataGridConfiguration dataGridConfiguration, - required DataCellBase dataCell, - required Key key, - required Color backgroundColor, - required Widget child}) { +Widget _wrapInsideCellContainer({ + required DataGridConfiguration dataGridConfiguration, + required DataCellBase dataCell, + required Key key, + required Color backgroundColor, + required Widget child, +}) { final Color color = - dataGridConfiguration.dataGridThemeHelper!.currentCellStyle.borderColor; + dataGridConfiguration.dataGridThemeHelper!.currentCellStyle!.borderColor; final double borderWidth = - dataGridConfiguration.dataGridThemeHelper!.currentCellStyle.borderWidth; + dataGridConfiguration.dataGridThemeHelper!.currentCellStyle!.borderWidth; Border getBorder() { final bool isCurrentCell = dataCell.isCurrentCell; return Border( - bottom: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - left: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - top: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - right: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, + bottom: + isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + left: + isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + top: + isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + right: + isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, ); } @@ -819,7 +3253,9 @@ Widget _wrapInsideCellContainer( double height; if (dataCell.rowSpan > 0) { height = dataCell.dataRow!.getRowHeight( - dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex); + dataCell.rowIndex - dataCell.rowSpan, + dataCell.rowIndex, + ); } else { height = defaultHeight; } @@ -835,7 +3271,15 @@ Widget _wrapInsideCellContainer( double width; if (dataCell.columnSpan > 0) { width = dataCell.dataRow!.getColumnWidth( - dataCell.columnIndex, dataCell.columnIndex + dataCell.columnSpan); + dataCell.columnIndex, + dataCell.columnIndex + dataCell.columnSpan, + ); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + dataCell.dataRow!.rowType == RowType.tableSummaryCoveredRow) { + width += + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth * + dataGridConfiguration.source.groupedColumns.length; + } } else { width = defaultWidth; } @@ -846,139 +3290,285 @@ Widget _wrapInsideCellContainer( final double width = getCellWidth(dataCell, constraint.maxWidth); final double height = getCellHeight(dataCell, constraint.maxHeight); - if (dataCell.isCurrentCell) { + if (dataCell.isCurrentCell && + dataCell.cellType != CellType.indentCell && + dataCell.dataRow!.dataGridRow != null) { return Stack( children: [ Container( - width: width, - height: height, - child: child, - color: backgroundColor), + width: width, + height: height, + color: backgroundColor, + child: child, + ), Positioned( - left: 0, - top: 0, - width: width, - height: height, - child: IgnorePointer( - ignoring: true, - child: Container( - key: key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration(border: getBorder())), - )), + left: 0, + top: 0, + width: width, + height: height, + child: IgnorePointer( + child: Container( + key: key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(border: getBorder()), + ), + ), + ), ], ); } else { return Container( - width: width, height: height, child: child, color: backgroundColor); + width: width, + height: height, + color: backgroundColor, + child: child, + ); } } return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraint) { - return getChild(constraint); - }); + builder: (BuildContext context, BoxConstraints constraint) { + return getChild(constraint); + }, + ); +} + +bool _invokeGroupChangingCallback( + DataGridConfiguration dataGridConfiguration, + Group group, +) { + final DataGridGroupChangingDetails details = DataGridGroupChangingDetails( + key: group.key, + groupLevel: group.level, + isExpanded: group.isExpanded, + ); + if (group.isExpanded) { + if (dataGridConfiguration.groupCollapsing != null) { + return dataGridConfiguration.groupCollapsing!(details); + } + return true; + } else { + if (dataGridConfiguration.groupExpanding != null) { + return dataGridConfiguration.groupExpanding!(details); + } + return true; + } +} + +void _invokeGroupChangedCallback( + DataGridConfiguration dataGridConfiguration, + Group group, + bool isExpanded, +) { + final DataGridGroupChangedDetails details = DataGridGroupChangedDetails( + key: group.key, + groupLevel: group.level, + isExpanded: isExpanded, + ); + if (dataGridConfiguration.groupCollapsed != null && !isExpanded) { + dataGridConfiguration.groupCollapsed!(details); + } else if (dataGridConfiguration.groupExpanded != null && isExpanded) { + dataGridConfiguration.groupExpanded!(details); + } } // Gesture Events -void _handleOnTapUp( - {required TapUpDetails? tapUpDetails, - required TapDownDetails? tapDownDetails, - required DataCellBase dataCell, - required DataGridConfiguration dataGridConfiguration, - required PointerDeviceKind kind, - bool isSecondaryTapDown = false}) { +Future _handleOnTapUp({ + required TapUpDetails? tapUpDetails, + required TapDownDetails? tapDownDetails, + required DataCellBase dataCell, + required DataGridConfiguration dataGridConfiguration, + required PointerDeviceKind kind, +}) async { // End edit the current editing cell if its editing mode is differed if (dataGridConfiguration.currentCell.isEditing) { - if (dataGridConfiguration.currentCell - .canSubmitCell(dataGridConfiguration)) { - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + if (await dataGridConfiguration.currentCell.canSubmitCell( + dataGridConfiguration, + )) { + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true, + ); } else { return; } } - if (!isSecondaryTapDown && dataGridConfiguration.onCellTap != null) { + dataGridConfiguration.dataGridFocusNode?.requestFocus(); + dataCell.onTouchUp(); + + // Expand or collpase the individual group by tap. + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + dataGridConfiguration.allowExpandCollapseGroup && + dataCell.dataRow!.rowType == RowType.captionSummaryCoveredRow) { + final int rowIndex = resolveStartRecordIndex( + dataGridConfiguration, + dataCell.dataRow!.rowIndex, + ); + if (rowIndex >= 0) { + final dynamic group = getGroupElement(dataGridConfiguration, rowIndex); + if (group is Group) { + if (group.isExpanded) { + if (_invokeGroupChangingCallback(dataGridConfiguration, group)) { + dataGridConfiguration.group!.collapseGroups( + group, + dataGridConfiguration.group, + rowIndex, + ); + dataGridConfiguration.groupExpandCollapseRowIndex = + dataCell.dataRow!.rowIndex; + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + _invokeGroupChangedCallback(dataGridConfiguration, group, false); + } + } else { + if (_invokeGroupChangingCallback(dataGridConfiguration, group)) { + dataGridConfiguration.group!.expandGroups( + group, + dataGridConfiguration.group, + rowIndex, + ); + dataGridConfiguration.groupExpandCollapseRowIndex = + dataCell.dataRow!.rowIndex; + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + _invokeGroupChangedCallback(dataGridConfiguration, group, true); + } + } + } + } + } + + if (dataGridConfiguration.onCellTap != null) { + // Issue: + // FLUT-865739-A null exception occurred when expanding the group alongside the onCellTap callback. + // + // Reason for the issue: The gridcolumn is null when the tapping the caption summary cell. + // + // Fix: We need to check the gridcolumn is null or not before invoking the onCellDoubleTap callback. + // For the caption summary cell, we need to get the first visible column from the columns collection. + final GridColumn? column = grid_helper.getGridColumn( + dataGridConfiguration, + dataCell, + ); + + if (column == null) { + return; + } + final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapDownDetails != null - ? tapDownDetails.globalPosition - : tapUpDetails!.globalPosition, - localPosition: tapDownDetails != null - ? tapDownDetails.localPosition - : tapUpDetails!.localPosition, - kind: kind); + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: column, + globalPosition: + tapDownDetails != null + ? tapDownDetails.globalPosition + : tapUpDetails!.globalPosition, + localPosition: + tapDownDetails != null + ? tapDownDetails.localPosition + : tapUpDetails!.localPosition, + kind: kind, + ); dataGridConfiguration.onCellTap!(details); } - dataGridConfiguration.dataGridFocusNode?.requestFocus(); - dataCell.onTouchUp(); - // Init the editing based on the editing mode if (dataGridConfiguration.editingGestureType == EditingGestureType.tap) { - dataGridConfiguration.currentCell - .onCellBeginEdit(editingDataCell: dataCell); + dataGridConfiguration.currentCell.onCellBeginEdit( + editingDataCell: dataCell, + ); } } -void _handleOnDoubleTap( - {required DataCellBase dataCell, - required DataGridConfiguration dataGridConfiguration}) { +Future _handleOnDoubleTap({ + required DataCellBase dataCell, + required DataGridConfiguration dataGridConfiguration, +}) async { // End edit the current editing cell if its editing mode is differed if (dataGridConfiguration.currentCell.isEditing) { - if (dataGridConfiguration.currentCell - .canSubmitCell(dataGridConfiguration)) { - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + if (await dataGridConfiguration.currentCell.canSubmitCell( + dataGridConfiguration, + )) { + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true, + ); } else { return; } } + dataGridConfiguration.dataGridFocusNode?.requestFocus(); + dataCell.onTouchUp(); + if (dataGridConfiguration.onCellDoubleTap != null) { + final GridColumn? column = grid_helper.getGridColumn( + dataGridConfiguration, + dataCell, + ); + + if (column == null) { + return; + } + final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!); + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: column, + ); dataGridConfiguration.onCellDoubleTap!(details); } - dataGridConfiguration.dataGridFocusNode?.requestFocus(); - dataCell.onTouchUp(); - // Init the editing based on the editing mode if (dataGridConfiguration.editingGestureType == EditingGestureType.doubleTap) { - dataGridConfiguration.currentCell - .onCellBeginEdit(editingDataCell: dataCell); + dataGridConfiguration.currentCell.onCellBeginEdit( + editingDataCell: dataCell, + ); } } -void _handleOnSecondaryTapUp( - {required TapUpDetails tapUpDetails, - required DataCellBase dataCell, - required DataGridConfiguration dataGridConfiguration, - required PointerDeviceKind kind}) { +Future _handleOnSecondaryTapUp({ + required TapUpDetails tapUpDetails, + required DataCellBase dataCell, + required DataGridConfiguration dataGridConfiguration, + required PointerDeviceKind kind, +}) async { // Need to end the editing cell when interacting with other tap gesture if (dataGridConfiguration.currentCell.isEditing) { - if (dataGridConfiguration.currentCell - .canSubmitCell(dataGridConfiguration)) { - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + if (await dataGridConfiguration.currentCell.canSubmitCell( + dataGridConfiguration, + )) { + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true, + ); } else { return; } } if (dataGridConfiguration.onCellSecondaryTap != null) { + final GridColumn? column = grid_helper.getGridColumn( + dataGridConfiguration, + dataCell, + ); + + if (column == null) { + return; + } + final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapUpDetails.globalPosition, - localPosition: tapUpDetails.localPosition, - kind: kind); + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: column, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, + kind: kind, + ); dataGridConfiguration.onCellSecondaryTap!(details); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart index e0371b5d3..16fe46314 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart @@ -5,15 +5,17 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_datagrid/src/datagrid_widget/helper/datagrid_helper.dart'; import '../../grid_common/row_column_index.dart'; import '../../grid_common/scroll_axis.dart'; import '../../grid_common/visible_line_info.dart'; +import '../grouping/grouping.dart'; import '../helper/callbackargs.dart'; import '../helper/datagrid_configuration.dart'; +import '../helper/datagrid_helper.dart'; import '../helper/datagrid_helper.dart' as grid_helper; import '../helper/enums.dart'; +import '../runtime/column.dart'; import '../runtime/generator.dart'; import '../sfdatagrid.dart'; import 'scrollview_widget.dart'; @@ -29,8 +31,9 @@ class VisualContainerRenderObjectWidget extends MultiChildRenderObjectWidget { required this.dataGridStateDetails, required this.children, }) : super( - key: key, - children: RepaintBoundary.wrapAll(List.from(children))); + key: key, + children: RepaintBoundary.wrapAll(List.from(children)), + ); @override final List children; @@ -48,13 +51,16 @@ class VisualContainerRenderObjectWidget extends MultiChildRenderObjectWidget { @override RenderVisualContainer createRenderObject(BuildContext context) => RenderVisualContainer( - containerSize: containerSize, - isDirty: isDirty, - dataGridStateDetails: dataGridStateDetails); + containerSize: containerSize, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails, + ); @override void updateRenderObject( - BuildContext context, RenderVisualContainer renderObject) { + BuildContext context, + RenderVisualContainer renderObject, + ) { super.updateRenderObject(context, renderObject); renderObject ..containerSize = containerSize @@ -84,15 +90,22 @@ class RenderVisualContainer extends RenderBox ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { /// Creates a [RenderVisualContainer] for `SfDataGrid`. - RenderVisualContainer( - {List? children, - required Size containerSize, - required bool isDirty, - required DataGridStateDetails? dataGridStateDetails}) - : _containerSize = containerSize, - _isDirty = isDirty, - _dataGridStateDetails = dataGridStateDetails! { + RenderVisualContainer({ + List? children, + required Size containerSize, + required bool isDirty, + required DataGridStateDetails? dataGridStateDetails, + }) : _containerSize = containerSize, + _isDirty = isDirty, + _dataGridStateDetails = dataGridStateDetails! { addAll(children); + _gestureArenaTeam = GestureArenaTeam(); + _panGestureRecognizer = + PanGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = ((DragStartDetails details) {}) + ..dragStartBehavior = DragStartBehavior.down; + _gestureArenaTeam.captain = _panGestureRecognizer; } /// Decides whether the visual container needs to be refreshed or not. @@ -124,6 +137,9 @@ class RenderVisualContainer extends RenderBox final DataGridStateDetails _dataGridStateDetails; + late PanGestureRecognizer _panGestureRecognizer; + late GestureArenaTeam _gestureArenaTeam; + @override bool get isRepaintBoundary => true; @@ -135,6 +151,25 @@ class RenderVisualContainer extends RenderBox } } + // Issue: + // FLUT-6822 - ScrollView is scrolled at sample level when swiping the row in DataGrid. + // + // Fix: + // An issue occurred because the Datagrid could not handle the drag gesture + // while swiping since we set `NeverScrollableScrollPhysics` to the Datagrid. + // So, that is handled by the parent widget which is added to the Datagrid and + // it scrolls the Datagrid while swiping. Now we controlled the drag gesture to the parent + // by using the `_panGestureRecognizer` and `_gestureArenaTeam`. By setting + // `_gestureArenaTeam.captain` as `_panGestureRecognizer` to handling the onDragStart. + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + super.handleEvent(event, entry); + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataGridConfiguration.allowSwiping && event is PointerDownEvent) { + _panGestureRecognizer.addPointer(event); + } + } + @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { RenderBox? child = lastChild; @@ -176,13 +211,15 @@ class RenderVisualContainer extends RenderBox if (dataGridConfiguration.textDirection == TextDirection.rtl && viewWidth > extentWidth) { - dxPosition = (dataGridConfiguration.swipingOffset >= 0) - ? viewWidth - extentWidth - : viewWidth - maxSwipeOffset; + dxPosition = + (dataGridConfiguration.swipingOffset >= 0) + ? viewWidth - extentWidth + : viewWidth - maxSwipeOffset; } else { - dxPosition = (dataGridConfiguration.swipingOffset >= 0) - ? 0.0 - : extentWidth - maxSwipeOffset; + dxPosition = + (dataGridConfiguration.swipingOffset >= 0) + ? 0.0 + : extentWidth - maxSwipeOffset; } return Offset(dxPosition, swipeRowRect.top); @@ -191,8 +228,9 @@ class RenderVisualContainer extends RenderBox // Provides the `Rect` to clip outside bounds of a swipe widget. Rect _getSwipeWidgetClipRect() { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); - final Rect swipeRowRect = - _swipeWholeRowElement!._measureRowRect(size.width); + final Rect swipeRowRect = _swipeWholeRowElement!._measureRowRect( + size.width, + ); final double swipeValue = dataGridConfiguration.swipingAnimation!.value; final VisibleLineInfo? lineInfo = _swipeWholeRowElement!._dataRow .getRowVisibleLineInfo(_swipeWholeRowElement!._dataRow.rowIndex); @@ -213,7 +251,11 @@ class RenderVisualContainer extends RenderBox if (dataGridConfiguration.swipingOffset.isNegative) { final double maxOffset = getSwipeMaxOffset(dataGridConfiguration); return Rect.fromLTWH( - maxOffset - swipeValue.abs(), top, maxOffset, height); + maxOffset - swipeValue.abs(), + top, + maxOffset, + height, + ); } else { return Rect.fromLTWH(0.0, top, swipeValue, height); } @@ -221,15 +263,19 @@ class RenderVisualContainer extends RenderBox @override void performLayout() { - size = - constraints.constrain(Size(containerSize.width, containerSize.height)); - - void layout( - {required RenderBox child, - required double width, - required double height}) { - child.layout(BoxConstraints.tightFor(width: width, height: height), - parentUsesSize: true); + size = constraints.constrain( + Size(containerSize.width, containerSize.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; @@ -251,13 +297,13 @@ class RenderVisualContainer extends RenderBox ..height = rowRect.height ..rowClipRect = wholeRowElement._rowClipRect ..offset = Offset( - (wholeRowElement.dataRow.isSwipingRow && - dataGridConfiguration.swipingOffset != 0.0 && - dataGridConfiguration.swipingAnimation != null) - ? rowRect.left + - dataGridConfiguration.swipingAnimation!.value - : rowRect.left, - rowRect.top); + (wholeRowElement.dataRow.isSwipingRow && + dataGridConfiguration.swipingOffset != 0.0 && + dataGridConfiguration.swipingAnimation != null) + ? rowRect.left + dataGridConfiguration.swipingAnimation!.value + : rowRect.left, + rowRect.top, + ); if (wholeRowElement.dataRow.isSwipingRow && dataGridConfiguration.swipingOffset.abs() > 0.0) { @@ -265,7 +311,10 @@ class RenderVisualContainer extends RenderBox } layout( - child: child, width: parentData.width, height: parentData.height); + child: child, + width: parentData.width, + height: parentData.height, + ); } else { child.layout(const BoxConstraints.tightFor(width: 0.0, height: 0.0)); parentData.reset(); @@ -275,8 +324,9 @@ class RenderVisualContainer extends RenderBox // So, we can get it directly from lastChild property. final RenderBox? swipingWidget = lastChild; if (swipingWidget != null && _swipeWholeRowElement != null) { - final Rect swipeRowRect = - _swipeWholeRowElement!._measureRowRect(size.width); + final Rect swipeRowRect = _swipeWholeRowElement!._measureRowRect( + size.width, + ); parentData ..width = getSwipeMaxOffset(dataGridConfiguration) @@ -284,9 +334,10 @@ class RenderVisualContainer extends RenderBox ..offset = _getSwipingChildOffset(swipeRowRect); layout( - child: swipingWidget, - width: parentData.width, - height: parentData.height); + child: swipingWidget, + width: parentData.width, + height: parentData.height, + ); } } @@ -314,11 +365,14 @@ class RenderVisualContainer extends RenderBox if (swipeWidget != null) { final _VisualContainerParentData childParentData = swipeWidget.parentData! as _VisualContainerParentData; - context.pushClipRect(needsCompositing, - childParentData.offset + offset, _getSwipeWidgetClipRect(), - (PaintingContext context, Offset offset) { - context.paintChild(swipeWidget, offset); - }); + context.pushClipRect( + needsCompositing, + childParentData.offset + offset, + _getSwipeWidgetClipRect(), + (PaintingContext context, Offset offset) { + context.paintChild(swipeWidget, offset); + }, + ); } } @@ -346,15 +400,13 @@ class RenderVisualContainer extends RenderBox class VirtualizingCellsRenderObjectWidget extends MultiChildRenderObjectWidget { /// Creates the [VirtualizingCellsRenderObjectWidget] for the /// [RenderVirtualizingCellsWidget]. - VirtualizingCellsRenderObjectWidget( - {required Key key, - required this.dataRow, - required this.isDirty, - required this.children, - required this.dataGridStateDetails}) - : super( - key: key, - children: RepaintBoundary.wrapAll(List.from(children))); + VirtualizingCellsRenderObjectWidget({ + super.key, + required this.dataRow, + required this.isDirty, + required this.children, + required this.dataGridStateDetails, + }) : super(children: RepaintBoundary.wrapAll(List.from(children))); @override final List children; @@ -372,13 +424,16 @@ class VirtualizingCellsRenderObjectWidget extends MultiChildRenderObjectWidget { @override RenderVirtualizingCellsWidget createRenderObject(BuildContext context) => RenderVirtualizingCellsWidget( - dataRow: dataRow, - isDirty: isDirty, - dataGridStateDetails: dataGridStateDetails); + dataRow: dataRow, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails, + ); @override void updateRenderObject( - BuildContext context, RenderVirtualizingCellsWidget renderObject) { + BuildContext context, + RenderVirtualizingCellsWidget renderObject, + ) { super.updateRenderObject(context, renderObject); renderObject ..dataRow = dataRow @@ -406,21 +461,24 @@ class _VirtualizingCellWidgetParentData /// virtualizing cells in the data grid. class RenderVirtualizingCellsWidget extends RenderBox with - ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin - implements - MouseTrackerAnnotation { + ContainerRenderObjectMixin< + RenderBox, + _VirtualizingCellWidgetParentData + >, + RenderBoxContainerDefaultsMixin< + RenderBox, + _VirtualizingCellWidgetParentData + > + implements MouseTrackerAnnotation { /// Creates a [RenderVirtualizingCellsWidget] for `SfDataGrid`. RenderVirtualizingCellsWidget({ List? children, required DataRowBase dataRow, required bool isDirty, required DataGridStateDetails? dataGridStateDetails, - }) : _dataRow = dataRow, - _isDirty = isDirty, - _dataGridStateDetails = dataGridStateDetails! { + }) : _dataRow = dataRow, + _isDirty = isDirty, + _dataGridStateDetails = dataGridStateDetails! { addAll(children); // Provides the `postAcceptSlopTolerance` to restrict the call of // long press event from the framework when executing the PointerMoveEvent. @@ -488,8 +546,8 @@ class RenderVirtualizingCellsWidget extends RenderBox _dataGridStateDetails(); final VisualContainerHelper container = dataGridConfiguration.container; - final VisibleLineInfo? lineInfo = - container.scrollRows.getVisibleLineAtLineIndex(dataRow.rowIndex); + final VisibleLineInfo? lineInfo = container.scrollRows + .getVisibleLineAtLineIndex(dataRow.rowIndex); final double lineSize = lineInfo != null ? lineInfo.size : 0.0; double origin = (lineInfo != null) ? lineInfo.origin : 0.0; @@ -498,10 +556,14 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowIndex > grid_helper.getHeaderIndex(dataGridConfiguration)) { - final double headerRowsHeight = container.scrollRows - .rangeToRegionPoints( - 0, grid_helper.getHeaderIndex(dataGridConfiguration), true)[1] - .length; + final double headerRowsHeight = + container.scrollRows + .rangeToRegionPoints( + 0, + grid_helper.getHeaderIndex(dataGridConfiguration), + true, + )[1] + .length; origin -= headerRowsHeight; } @@ -515,7 +577,10 @@ class RenderVirtualizingCellsWidget extends RenderBox } Rect? _getRowClipRect( - VisibleLineInfo? lineInfo, double lineHeight, double width) { + VisibleLineInfo? lineInfo, + double lineHeight, + double width, + ) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); // Clipping the row when the frozen rows are applied. @@ -531,41 +596,46 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataGridConfiguration.allowSwiping && dataRow.isSwipingRow) { return _getSwipingRowClipRect( - top: top, - height: height, - dataGridConfiguration: dataGridConfiguration); + top: top, + height: height, + dataGridConfiguration: dataGridConfiguration, + ); } else { return Rect.fromLTWH(0.0, top, width, height); } } else if (dataGridConfiguration.allowSwiping && dataRow.isSwipingRow) { return _getSwipingRowClipRect( - top: 0.0, - height: lineHeight, - dataGridConfiguration: dataGridConfiguration); + top: 0.0, + height: lineHeight, + dataGridConfiguration: dataGridConfiguration, + ); } else { return null; } } - Rect? _getSwipingRowClipRect( - {required DataGridConfiguration dataGridConfiguration, - required double top, - required double height}) { + Rect? _getSwipingRowClipRect({ + required DataGridConfiguration dataGridConfiguration, + required double top, + required double height, + }) { if (dataGridConfiguration.swipingAnimation == null) { return null; } double leftPosition = 0.0; final double viewWidth = dataGridConfiguration.viewWidth; final double extentWidth = dataGridConfiguration.container.extentWidth; - final double swipingDelta = dataGridConfiguration.swipingOffset >= 0 - ? dataGridConfiguration.swipingAnimation!.value - : -dataGridConfiguration.swipingAnimation!.value; + final double swipingDelta = + dataGridConfiguration.swipingOffset >= 0 + ? dataGridConfiguration.swipingAnimation!.value + : -dataGridConfiguration.swipingAnimation!.value; if (dataGridConfiguration.textDirection == TextDirection.rtl && viewWidth > extentWidth) { - leftPosition = dataGridConfiguration.swipingOffset >= 0 - ? viewWidth - extentWidth - : (viewWidth - extentWidth) + swipingDelta; + leftPosition = + dataGridConfiguration.swipingOffset >= 0 + ? viewWidth - extentWidth + : (viewWidth - extentWidth) + swipingDelta; } else { leftPosition = dataGridConfiguration.swipingOffset >= 0 ? 0 : swipingDelta; @@ -573,8 +643,12 @@ class RenderVirtualizingCellsWidget extends RenderBox return Rect.fromLTWH(leftPosition, top, extentWidth - swipingDelta, height); } - Rect _getRowRect(DataGridConfiguration dataGridConfiguration, Offset offset, - {bool isHoveredLayer = false}) { + Rect _getRowRect( + DataGridConfiguration dataGridConfiguration, + Offset offset, { + bool isHoveredLayer = false, + bool isCurrentRowBorderLayer = false, + }) { bool needToSetMaxConstraint() => dataGridConfiguration.container.extentWidth < dataGridConfiguration.viewWidth && @@ -582,23 +656,96 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowType == RowType.footerRow) { return Rect.fromLTWH( - offset.dx + dataGridConfiguration.container.horizontalOffset, - offset.dy, - dataGridConfiguration.viewWidth, - dataGridConfiguration.footerHeight); + offset.dx + dataGridConfiguration.container.horizontalOffset, + offset.dy, + dataGridConfiguration.viewWidth, + dataGridConfiguration.footerHeight, + ); } else { - return Rect.fromLTWH( + late int indentCount; + final bool isRTLDirection = + dataGridConfiguration.textDirection == TextDirection.rtl; + + // Current row border is applicable for both data row and caption summary covered row + // So, considered the row type with this `isCurrentRowBorderLayer` flag itself. + isCurrentRowBorderLayer = + isCurrentRowBorderLayer && + (dataRow.rowType == RowType.dataRow || + dataRow.rowType == RowType.captionSummaryCoveredRow); + + if (isCurrentRowBorderLayer && + dataRow.rowType == RowType.captionSummaryCoveredRow) { + final dynamic rowData = dataRow.rowData; + if (rowData != null && rowData is Group) { + indentCount = rowData.level - 1; + } + } else { + indentCount = dataGridConfiguration.source.groupedColumns.length; + } + + final double indentColumnsWidth = + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth * + indentCount; + + double dx = needToSetMaxConstraint() ? constraints.maxWidth - - min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth) - - (offset.dx + dataGridConfiguration.container.horizontalOffset) - : offset.dx + dataGridConfiguration.container.horizontalOffset, - offset.dy, + min( + dataGridConfiguration.container.extentWidth, + dataGridConfiguration.viewWidth, + ) - + dataGridConfiguration.container.horizontalOffset + : dataGridConfiguration.container.horizontalOffset; + + if (isCurrentRowBorderLayer || + (isHoveredLayer && dataRow.rowType == RowType.dataRow)) { + if (indentColumnsWidth > 0) { + if (!isRTLDirection) { + dx = max(dx, indentColumnsWidth); + } + } + } + + double width = needToSetMaxConstraint() ? constraints.maxWidth - : min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth), + : min( + dataGridConfiguration.container.extentWidth, + dataGridConfiguration.viewWidth, + ); + + if (isCurrentRowBorderLayer || + (isHoveredLayer && dataRow.rowType == RowType.dataRow)) { + if (indentColumnsWidth > 0) { + // Need to remove the left side view space from the width in RTL mode when + // the datagrid is unscrollable. + if (needToSetMaxConstraint() && + (isHoveredLayer && dataRow.rowType == RowType.dataRow)) { + width -= + dataGridConfiguration.viewWidth - + dataGridConfiguration.container.extentWidth; + } + + double scrollOffset = 0.0; + if (isRTLDirection) { + if (dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth) { + scrollOffset = + (dataGridConfiguration.container.extentWidth - + dataGridConfiguration.viewWidth) - + dataGridConfiguration.container.horizontalOffset; + } + } else { + scrollOffset = dataGridConfiguration.container.horizontalOffset; + } + + if (scrollOffset < indentColumnsWidth) { + width -= indentColumnsWidth - scrollOffset; + } + } + } + + final double height = (isHoveredLayer && dataRow.isHoveredRow && (dataGridConfiguration.gridLinesVisibility == @@ -606,23 +753,38 @@ class RenderVirtualizingCellsWidget extends RenderBox dataGridConfiguration.gridLinesVisibility == GridLinesVisibility.both)) ? constraints.maxHeight - - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth - : constraints.maxHeight); + dataGridConfiguration + .dataGridThemeHelper! + .gridLineStrokeWidth! + : constraints.maxHeight; + + return Rect.fromLTWH(offset.dx + dx, offset.dy, width, height); } } - void _drawRowBackground(DataGridConfiguration dataGridConfiguration, - PaintingContext context, Offset offset) { - final Rect rect = _getRowRect(dataGridConfiguration, offset); + void _drawRowBackground( + DataGridConfiguration dataGridConfiguration, + PaintingContext context, + Offset offset, + ) { + // Need to consider the selected row as hovered layer to avoid apply + // selection for the indent columns. + final Rect rect = _getRowRect( + dataGridConfiguration, + offset, + isHoveredLayer: dataRow.isSelectedRow, + ); + Color? backgroundColor; Color getDefaultRowBackgroundColor() { - return Colors.transparent.withOpacity(0.0001); + return dataGridConfiguration.colorScheme!.transparent; } void drawSpannedRowBackgroundColor(Color backgroundColor) { - final bool isRowSpanned = dataRow.visibleColumns - .any((DataCellBase dataCell) => dataCell.rowSpan > 0); + final bool isRowSpanned = dataRow.visibleColumns.any( + (DataCellBase dataCell) => dataCell.rowSpan > 0, + ); final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); @@ -632,13 +794,16 @@ class RenderVirtualizingCellsWidget extends RenderBox final _VirtualizingCellWidgetParentData childParentData = child.parentData! as _VirtualizingCellWidgetParentData; final DataCellBase dataCell = child.dataCell; - final VisibleLineInfo? lineInfo = - dataRow.getColumnVisibleLineInfo(dataCell.columnIndex); + final VisibleLineInfo? lineInfo = dataRow.getColumnVisibleLineInfo( + dataCell.columnIndex, + ); if (dataCell.rowSpan > 0 && lineInfo != null) { final Rect? columnRect = child._columnRect; final Rect? cellClipRect = child._cellClipRect; final double height = dataRow.getRowHeight( - dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex); + dataCell.rowIndex - dataCell.rowSpan, + dataCell.rowIndex, + ); Rect cellRect = Rect.zero; if (cellClipRect != null) { double left = columnRect!.left; @@ -653,7 +818,11 @@ 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, + ); } dataGridConfiguration.gridPaint?.color = backgroundColor; context.canvas.drawRect(cellRect, dataGridConfiguration.gridPaint!); @@ -671,18 +840,23 @@ class RenderVirtualizingCellsWidget extends RenderBox dataRow.rowType == RowType.stackedHeaderRow) { backgroundColor = dataGridConfiguration.dataGridThemeHelper!.headerColor; - drawSpannedRowBackgroundColor(backgroundColor); + drawSpannedRowBackgroundColor(backgroundColor!); } else if (dataRow.rowType == RowType.footerRow) { backgroundColor = getDefaultRowBackgroundColor(); } else if (dataRow.rowType == RowType.tableSummaryRow || dataRow.rowType == RowType.tableSummaryCoveredRow) { - backgroundColor = dataRow.tableSummaryRow?.color; + backgroundColor = + dataRow.tableSummaryRow?.color ?? + dataGridConfiguration.dataGridThemeHelper!.tableSummaryRowColor; + } else if (dataRow.rowType == RowType.captionSummaryCoveredRow) { + backgroundColor = getDefaultRowBackgroundColor(); } else { /// Need to check the rowStyle Please look the previous version and /// selection preference - backgroundColor = dataRow.isSelectedRow - ? dataGridConfiguration.dataGridThemeHelper!.selectionColor - : dataRow.dataGridRowAdapter!.color; + backgroundColor = + dataRow.isSelectedRow + ? dataGridConfiguration.dataGridThemeHelper!.selectionColor + : dataRow.dataGridRowAdapter!.color; } // Default theme color are common for both the HeaderBackgroundColor and @@ -700,37 +874,51 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataGridConfiguration.boxPainter != null && dataGridConfiguration.selectionMode == SelectionMode.multiple && - dataGridConfiguration.navigationMode == GridNavigationMode.row && - dataGridConfiguration.currentCell.rowIndex == dataRow.rowIndex) { + dataGridConfiguration.currentCell.rowIndex == dataRow.rowIndex && + ((dataGridConfiguration.navigationMode == GridNavigationMode.cell && + dataRow.rowType == RowType.captionSummaryCoveredRow) || + dataGridConfiguration.navigationMode == GridNavigationMode.row)) { bool needToSetMaxConstraint() => dataGridConfiguration.container.extentWidth < dataGridConfiguration.viewWidth && dataGridConfiguration.textDirection == TextDirection.rtl; const double stokeWidth = 1; - final int origin = (stokeWidth / 2 + - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth) - .ceil(); + final int origin = + (stokeWidth / 2 + + dataGridConfiguration + .dataGridThemeHelper! + .gridLineStrokeWidth!) + .ceil(); + + final Rect rowRect = _getRowRect( + dataGridConfiguration, + offset, + isCurrentRowBorderLayer: true, + ); - final Rect rowRect = _getRowRect(dataGridConfiguration, offset); - final double maxWidth = needToSetMaxConstraint() - ? rowRect.width - rowRect.left - : rowRect.right - rowRect.left; + final double maxWidth = + needToSetMaxConstraint() + ? rowRect.width - rowRect.left + : rowRect.right - rowRect.left; final bool isHorizontalGridLinesEnabled = dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.both || - dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.horizontal; + GridLinesVisibility.both || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal; dataGridConfiguration.boxPainter!.paint( - context.canvas, - Offset(rowRect.left + origin, rowRect.top + (origin / 2)), - dataGridConfiguration.configuration!.copyWith( - size: Size( - maxWidth - (origin * 2), - constraints.maxHeight - - (origin * (isHorizontalGridLinesEnabled ? 1.5 : 1))))); + context.canvas, + Offset(rowRect.left + origin, rowRect.top + (origin / 2)), + dataGridConfiguration.configuration!.copyWith( + size: Size( + maxWidth - (origin * 2), + constraints.maxHeight - + (origin * (isHorizontalGridLinesEnabled ? 1.5 : 1)), + ), + ), + ); } } @@ -745,23 +933,27 @@ class RenderVirtualizingCellsWidget extends RenderBox void _handleSwipingListener() { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); - notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, - propertyName: 'Swiping'); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'Swiping', + ); } @override void attach(PipelineOwner owner) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); - dataGridConfiguration.swipingAnimationController - ?.addListener(_handleSwipingListener); + dataGridConfiguration.swipingAnimationController?.addListener( + _handleSwipingListener, + ); super.attach(owner); } @override void detach() { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); - dataGridConfiguration.swipingAnimationController - ?.removeListener(_handleSwipingListener); + dataGridConfiguration.swipingAnimationController?.removeListener( + _handleSwipingListener, + ); super.detach(); } @@ -799,8 +991,9 @@ class RenderVirtualizingCellsWidget extends RenderBox @override bool hitTest(BoxHitTestResult result, {required Offset position}) { - final bool isRowSpanned = dataRow.visibleColumns - .any((DataCellBase dataCell) => dataCell.rowSpan > 0); + final bool isRowSpanned = dataRow.visibleColumns.any( + (DataCellBase dataCell) => dataCell.rowSpan > 0, + ); if (isRowSpanned) { RenderBox? child = lastChild; @@ -833,17 +1026,22 @@ class RenderVirtualizingCellsWidget extends RenderBox } void _updateSwipingAnimation( - DataGridConfiguration dataGridConfiguration, double swipeMaxOffset) { + DataGridConfiguration dataGridConfiguration, + double swipeMaxOffset, + ) { dataGridConfiguration.swipingAnimation = Tween( - begin: 0.0, - end: dataGridConfiguration.swipingOffset.sign >= 0 - ? swipeMaxOffset - : -swipeMaxOffset) - .animate(dataGridConfiguration.swipingAnimationController!); + begin: 0.0, + end: + dataGridConfiguration.swipingOffset.sign >= 0 + ? swipeMaxOffset + : -swipeMaxOffset, + ).animate(dataGridConfiguration.swipingAnimationController!); } void _handleSwipeStart( - PointerDownEvent event, DataGridConfiguration dataGridConfiguration) { + PointerDownEvent event, + DataGridConfiguration dataGridConfiguration, + ) { // Need to reset the swiping and scrolling state to default when pointer // up and touch again dataGridConfiguration.isSwipingApplied = false; @@ -857,31 +1055,42 @@ class RenderVirtualizingCellsWidget extends RenderBox // If it's tapped on different [DataGridRow] or scrolled, we need to end // the swiping. final DataRowBase? swipedRow = dataGridConfiguration.rowGenerator.items - .firstWhereOrNull((DataRowBase row) => - row.isSwipingRow && dataGridConfiguration.swipingOffset.abs() > 0); + .firstWhereOrNull( + (DataRowBase row) => + row.isSwipingRow && dataGridConfiguration.swipingOffset.abs() > 0, + ); if (swipedRow != null && swipedRow.rowIndex != dataRow.rowIndex) { - dataGridConfiguration.container - .resetSwipeOffset(swipedRow: swipedRow, canUpdate: true); + dataGridConfiguration.container.resetSwipeOffset( + swipedRow: swipedRow, + canUpdate: true, + ); dataGridConfiguration.swipingOffset = event.localDelta.dx; dy = event.localDelta.dy; } } void _handleSwipeUpdate( - PointerMoveEvent event, DataGridConfiguration dataGridConfiguration) { + PointerMoveEvent event, + DataGridConfiguration dataGridConfiguration, + ) { bool canUpdateSwiping = true; void onSwipeStart(int rowIndex) { if (_swipeDirection != null && dataGridConfiguration.onSwipeStart != null) { final DataGridSwipeStartDetails swipeStartDetails = DataGridSwipeStartDetails( - rowIndex: rowIndex, swipeDirection: _swipeDirection!); + rowIndex: rowIndex, + swipeDirection: _swipeDirection!, + ); setSwipeOffsetInDataGridSwipeStartDetailsArgs( - dataGridConfiguration, swipeStartDetails); + dataGridConfiguration, + swipeStartDetails, + ); dataGridConfiguration.effectiveSwipeMaxOffset = null; - _canStartSwiping = - dataGridConfiguration.onSwipeStart!(swipeStartDetails); + _canStartSwiping = dataGridConfiguration.onSwipeStart!( + swipeStartDetails, + ); } } @@ -898,7 +1107,9 @@ class RenderVirtualizingCellsWidget extends RenderBox final ScrollController horizontalController = dataGridConfiguration.horizontalScrollController!; final int rowIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, dataRow.rowIndex); + dataGridConfiguration, + dataRow.rowIndex, + ); // Sets `swipeDirection` to null when the swipe offset is changed the // dragging direction to update the `swipeDirection` property. @@ -912,7 +1123,9 @@ class RenderVirtualizingCellsWidget extends RenderBox horizontalController.position.minScrollExtent && _swipeDirection == null) { _swipeDirection = grid_helper.getSwipeDirection( - dataGridConfiguration, currentSwipingDelta); + dataGridConfiguration, + currentSwipingDelta, + ); onSwipeStart(rowIndex); } } else if (currentSwipingDelta < -2) { @@ -920,24 +1133,31 @@ class RenderVirtualizingCellsWidget extends RenderBox horizontalController.position.maxScrollExtent && _swipeDirection == null) { _swipeDirection = grid_helper.getSwipeDirection( - dataGridConfiguration, currentSwipingDelta); + dataGridConfiguration, + currentSwipingDelta, + ); onSwipeStart(rowIndex); } } if (_swipeDirection != null && grid_helper.canSwipeRow( - dataGridConfiguration, _swipeDirection!, currentSwipingDelta)) { + dataGridConfiguration, + _swipeDirection!, + currentSwipingDelta, + )) { final double oldSwipingDelta = dataGridConfiguration.swipingOffset; if (_canStartSwiping) { dataGridConfiguration.isSwipingApplied = true; if (dataGridConfiguration.onSwipeUpdate != null) { final DataGridSwipeUpdateDetails swipeUpdateDetails = DataGridSwipeUpdateDetails( - rowIndex: rowIndex, - swipeDirection: _swipeDirection!, - swipeOffset: currentSwipingDelta); - canUpdateSwiping = - dataGridConfiguration.onSwipeUpdate!(swipeUpdateDetails); + rowIndex: rowIndex, + swipeDirection: _swipeDirection!, + swipeOffset: currentSwipingDelta, + ); + canUpdateSwiping = dataGridConfiguration.onSwipeUpdate!( + swipeUpdateDetails, + ); } if (!canUpdateSwiping || @@ -971,16 +1191,24 @@ class RenderVirtualizingCellsWidget extends RenderBox } void _handleSwipeEnd( - PointerUpEvent event, DataGridConfiguration dataGridConfiguration) { - void _onSwipeEnd() { + PointerUpEvent event, + DataGridConfiguration dataGridConfiguration, + ) { + void onSwipeEnd() { if (dataGridConfiguration.onSwipeEnd != null) { final int rowIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, dataRow.rowIndex); - final DataGridRowSwipeDirection swipeDirection = - grid_helper.getSwipeDirection( - dataGridConfiguration, dataGridConfiguration.swipingOffset); + dataGridConfiguration, + dataRow.rowIndex, + ); + final DataGridRowSwipeDirection swipeDirection = grid_helper + .getSwipeDirection( + dataGridConfiguration, + dataGridConfiguration.swipingOffset, + ); final DataGridSwipeEndDetails swipeEndDetails = DataGridSwipeEndDetails( - rowIndex: rowIndex, swipeDirection: swipeDirection); + rowIndex: rowIndex, + swipeDirection: swipeDirection, + ); dataGridConfiguration.onSwipeEnd!(swipeEndDetails); } } @@ -995,12 +1223,12 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataGridConfiguration.swipingOffset.abs() > maxOffset / 2) { dataGridConfiguration.swipingOffset = dataGridConfiguration.swipingOffset >= 0 ? maxOffset : -maxOffset; - dataGridConfiguration.swipingAnimationController! - .forward() - .then((_) => _onSwipeEnd()); + dataGridConfiguration.swipingAnimationController!.forward().then( + (_) => onSwipeEnd(), + ); } else { dataGridConfiguration.swipingAnimationController!.reverse().then((_) { - _onSwipeEnd(); + onSwipeEnd(); dataGridConfiguration.container.resetSwipeOffset(swipedRow: dataRow); }); } @@ -1030,51 +1258,105 @@ class RenderVirtualizingCellsWidget extends RenderBox @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { super.handleEvent(event, entry); + + if (!hasOwner()) { + return; + } _handleSwiping(event); _handleColumnResizing(event); + _handleColumnDragAndDrop(event); + // Handles the all the datagrid long press events here commonly. + _handleLongPress(event); + } + + void _handleLongPress(PointerEvent event) { if (event is PointerDownEvent) { _onLongPressGesture.addPointer(event); } } + // FLUT- 890745 Gestures need to be unhandled when the owner of the current widget is null. + // This means the widget is not attached to the current widget tree. + bool hasOwner() { + if (owner != null) { + return true; + } + return false; + } + + void _handleColumnDragAndDrop(PointerEvent event) { + final DataGridConfiguration configuration = _dataGridStateDetails(); + + if (configuration.allowColumnsDragging && + configuration.onColumnDragging != null && + !configuration.columnResizeController.canSwitchResizeColumnCursor && + !configuration.columnResizeController.isResizeIndicatorVisible) { + final ColumnDragAndDropController columnDragAndDropController = + configuration.columnDragAndDropController; + + if (event is PointerDownEvent) { + columnDragAndDropController.offset = event.localPosition; + columnDragAndDropController.dragDelta = event.position.dx; + } + + if (event is PointerMoveEvent) { + columnDragAndDropController.onPointerMove(event); + } + if (event is PointerUpEvent) { + columnDragAndDropController.onPointerUp(event); + } + } + } + void _handleColumnResizing(PointerEvent event) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); if (dataGridConfiguration.allowColumnsResizing) { if (event is PointerHoverEvent) { - dataGridConfiguration.columnResizeController - .onPointerHover(event, dataRow); + dataGridConfiguration.columnResizeController.onPointerHover( + event, + dataRow, + ); } if (event is PointerDownEvent) { - dataGridConfiguration.columnResizeController - .onPointerDown(event, dataRow); + dataGridConfiguration.columnResizeController.onPointerDown( + event, + dataRow, + ); } if (event is PointerMoveEvent) { - dataGridConfiguration.columnResizeController - .onPointerMove(event, dataRow); + dataGridConfiguration.columnResizeController.onPointerMove( + event, + dataRow, + ); } if (event is PointerUpEvent) { - dataGridConfiguration.columnResizeController - .onPointerUp(event, dataRow); + dataGridConfiguration.columnResizeController.onPointerUp( + event, + dataRow, + ); } } } @override void performLayout() { - void _layout( - {required RenderBox child, - required double width, - required double height}) { - child.layout(BoxConstraints.tightFor(width: width, height: height), - parentUsesSize: true); + void layout({ + required RenderBox child, + required double width, + required double height, + }) { + child.layout( + BoxConstraints.tightFor(width: width, height: height), + parentUsesSize: true, + ); } RenderBox? child = firstChild; while (child != null) { - final _VirtualizingCellWidgetParentData _parentData = + final _VirtualizingCellWidgetParentData parentData = child.parentData! as _VirtualizingCellWidgetParentData; if (dataRow.isVisible && child is RenderGridCell && @@ -1082,92 +1364,67 @@ class RenderVirtualizingCellsWidget extends RenderBox final Rect columnRect = child._measureColumnRect(constraints.maxHeight)!; size = constraints.constrain(Size(columnRect.width, columnRect.height)); - _parentData + parentData ..width = columnRect.width ..height = columnRect.height ..cellClipRect = child._cellClipRect; - _layout( - child: child, width: _parentData.width, height: _parentData.height); - _parentData.offset = Offset(columnRect.left, columnRect.top); + layout( + child: child, + width: parentData.width, + height: parentData.height, + ); + parentData.offset = Offset(columnRect.left, columnRect.top); } else { if (dataRow.rowType == RowType.footerRow) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); final Rect cellRect = Rect.fromLTWH( - dataGridConfiguration.container.horizontalOffset, - 0.0, - dataGridConfiguration.viewWidth, - dataGridConfiguration.footerHeight); + dataGridConfiguration.container.horizontalOffset, + 0.0, + dataGridConfiguration.viewWidth, + dataGridConfiguration.footerHeight, + ); size = constraints.constrain(Size(cellRect.width, cellRect.height)); - _parentData + parentData ..width = cellRect.width ..height = cellRect.height ..offset = Offset(cellRect.left, cellRect.top); - _layout( - child: child, - width: _parentData.width, - height: _parentData.height); + layout( + child: child, + width: parentData.width, + height: parentData.height, + ); } else { size = constraints.constrain(Size.zero); child.layout(const BoxConstraints.tightFor(width: 0, height: 0)); - _parentData.reset(); + parentData.reset(); } } - child = _parentData.nextSibling; + child = parentData.nextSibling; } } - void _drawRowHoverBackground(DataGridConfiguration dataGridConfiguration, - PaintingContext context, Offset offset) { + void _drawRowHoverBackground( + DataGridConfiguration dataGridConfiguration, + PaintingContext context, + Offset offset, + ) { if (dataGridConfiguration.isDesktop && dataGridConfiguration.highlightRowOnHover && dataRow.isHoveredRow) { dataGridConfiguration.gridPaint?.color = - dataGridConfiguration.dataGridThemeHelper!.rowHoverColor; - context.canvas.drawRect( - _getRowRect(dataGridConfiguration, offset, isHoveredLayer: true), - dataGridConfiguration.gridPaint!); - } - } - - void _drawTableSummaryRowBorder(DataGridConfiguration dataGridConfiguration, - PaintingContext context, Offset offset) { - Rect getRowRect() { - final double extentWidth = dataGridConfiguration.container.extentWidth; - final Rect rect = _getRowRect(dataGridConfiguration, offset); - - if (dataRow.rowType == RowType.tableSummaryRow) { - final double left = (extentWidth < dataGridConfiguration.viewWidth && - dataGridConfiguration.textDirection == TextDirection.rtl) - ? constraints.maxWidth - - min(extentWidth, dataGridConfiguration.viewWidth) - - offset.dx - : offset.dx; - return Rect.fromLTWH(left, rect.top, extentWidth, rect.height); - } else { - return Rect.fromLTWH(rect.left, rect.top, - min(extentWidth, dataGridConfiguration.viewWidth), rect.height); - } - } - - if (dataGridConfiguration.gridLinesVisibility == GridLinesVisibility.none || - dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.horizontal) { - return; - } - - if (dataRow.rowType == RowType.tableSummaryRow || - dataRow.rowType == RowType.tableSummaryCoveredRow) { - final BorderSide border = BorderSide( - width: dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth, - color: dataGridConfiguration.dataGridThemeHelper!.gridLineColor); - - if (dataGridConfiguration.textDirection == TextDirection.ltr) { - paintBorder(context.canvas, getRowRect(), right: border); - } else { - paintBorder(context.canvas, getRowRect(), left: border); + dataGridConfiguration.dataGridThemeHelper!.rowHoverColor!; + if (dataRow.rowType == RowType.captionSummaryCoveredRow) { + dataGridConfiguration.gridPaint?.color = + dataGridConfiguration + .dataGridThemeHelper! + .captionSummaryRowHoverColor; } + context.canvas.drawRect( + _getRowRect(dataGridConfiguration, offset, isHoveredLayer: true), + dataGridConfiguration.gridPaint!, + ); } } @@ -1204,9 +1461,6 @@ class RenderVirtualizingCellsWidget extends RenderBox child = childParentData.nextSibling; } - // To draw the right border to the table summary row. - _drawTableSummaryRowBorder(dataGridConfiguration, context, offset); - if (dataGridConfiguration.isDesktop) { _drawCurrentRowBorder(context, offset); } @@ -1216,7 +1470,8 @@ class RenderVirtualizingCellsWidget extends RenderBox MouseCursor get cursor { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); return dataGridConfiguration - .columnResizeController.canSwitchResizeColumnCursor + .columnResizeController + .canSwitchResizeColumnCursor ? SystemMouseCursors.resizeColumn : SystemMouseCursors.basic; } @@ -1234,13 +1489,11 @@ class RenderVirtualizingCellsWidget extends RenderBox dataRow.rowType == RowType.dataRow) || (dataGridConfiguration.allowColumnsResizing && (dataRow.rowType == RowType.stackedHeaderRow || - dataRow.rowType == RowType.headerRow)); + dataRow.rowType == RowType.headerRow)) || + dataRow.rowType == RowType.captionSummaryCoveredRow; } - DataCellBase? _getDataCellBase( - DataRowBase dataRow, - dynamic details, - ) { + DataCellBase? _getDataCellBase(DataRowBase dataRow, dynamic details) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); DataCellBase? dataCell; @@ -1256,17 +1509,22 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowType != RowType.footerRow) { final double position = dataGridConfiguration.columnResizeController .getXPosition(dataGridConfiguration, details.localPosition.dx); - final VisibleLineInfo? resizingLine = - dataGridConfiguration.container.scrollColumns.getVisibleLineAtPoint( - position, - false, - dataGridConfiguration.textDirection == TextDirection.rtl); + final VisibleLineInfo? resizingLine = dataGridConfiguration + .container + .scrollColumns + .getVisibleLineAtPoint( + position, + false, + dataGridConfiguration.textDirection == TextDirection.rtl, + ); if (dataRow.rowType == RowType.stackedHeaderRow || dataRow.rowType == RowType.tableSummaryRow || + dataRow.rowType == RowType.captionSummaryCoveredRow || dataRow.rowType == RowType.tableSummaryCoveredRow) { - dataCell = - dataRow.visibleColumns.firstWhereOrNull((DataCellBase dataCell) { + dataCell = dataRow.visibleColumns.firstWhereOrNull(( + DataCellBase dataCell, + ) { final int cellLeft = dataCell.columnIndex; final int cellRight = dataCell.columnIndex + dataCell.columnSpan; @@ -1277,8 +1535,9 @@ class RenderVirtualizingCellsWidget extends RenderBox }); } else { dataCell = dataRow.visibleColumns.firstWhereOrNull( - (DataCellBase element) => - element.columnIndex == resizingLine!.lineIndex); + (DataCellBase element) => + element.columnIndex == resizingLine!.lineIndex, + ); } } return dataCell; @@ -1289,19 +1548,37 @@ class RenderVirtualizingCellsWidget extends RenderBox if (_dataCellBase != null && _longPressStartDetails != null && dataGridConfiguration.onCellLongPress != null) { + // Issue: FLUT-865739-A null exception occurred when expanding the group alongside the onCellTap callback. + // Reason: The grid column is null when the row type is caption summary covered row. + // Fix: Check the grid column is null or not before invoking the onCellLongPress callback. + final GridColumn? column = grid_helper.getGridColumn( + dataGridConfiguration, + _dataCellBase!, + ); + + if (column == null) { + return; + } + final DataGridCellLongPressDetails longPressDetails = DataGridCellLongPressDetails( - rowColumnIndex: RowColumnIndex( - _dataCellBase!.rowIndex, _dataCellBase!.columnIndex), - column: _dataCellBase!.gridColumn!, - globalPosition: _longPressStartDetails!.globalPosition, - localPosition: _longPressStartDetails!.localPosition); + rowColumnIndex: RowColumnIndex( + _dataCellBase!.rowIndex, + _dataCellBase!.columnIndex, + ), + column: column, + globalPosition: _longPressStartDetails!.globalPosition, + localPosition: _longPressStartDetails!.localPosition, + ); dataGridConfiguration.onCellLongPress!(longPressDetails); } } // To handle long press start event. void _onLongPressStart(LongPressStartDetails details) { + if (!hasOwner()) { + return; + } final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); DataCellBase? dataCell; dataCell = _getDataCellBase(dataRow, details); @@ -1309,12 +1586,17 @@ class RenderVirtualizingCellsWidget extends RenderBox _dataCellBase = dataCell; _longPressStartDetails = details; - dataGridConfiguration.columnResizeController - .onLongPressStart(details, dataRow); + dataGridConfiguration.columnResizeController.onLongPressStart( + details, + dataRow, + ); } // To handle long press end event. - void _onLongPressEnd(LongPressEndDetails details) { + Future _onLongPressEnd(LongPressEndDetails details) async { + if (!hasOwner()) { + return; + } final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); DataCellBase? dataCell; dataCell = _getDataCellBase(dataRow, details); @@ -1325,21 +1607,29 @@ class RenderVirtualizingCellsWidget extends RenderBox dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); } else if (dataCell.cellType == CellType.gridCell) { // Clear editing when tap on the grid cell - if (dataGridConfiguration.currentCell - .canSubmitCell(dataGridConfiguration)) { - dataGridConfiguration.currentCell - .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + if (await dataGridConfiguration.currentCell.canSubmitCell( + dataGridConfiguration, + )) { + await dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true, + ); } } } } void _onPointerEnter(PointerEnterEvent event) { + if (!hasOwner()) { + return; + } final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); if (dataGridConfiguration.allowColumnsResizing) { - dataGridConfiguration.columnResizeController - .onPointerEnter(event, dataRow); + dataGridConfiguration.columnResizeController.onPointerEnter( + event, + dataRow, + ); } // Restricts the row hovering when resizing the column. @@ -1349,62 +1639,65 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataGridConfiguration.highlightRowOnHover && dataGridConfiguration.isDesktop && - dataRow.rowType == RowType.dataRow) { + (dataRow.rowType == RowType.dataRow || + dataRow.rowType == RowType.captionSummaryCoveredRow)) { dataRow.isHoveredRow = true; final TextStyle rowStyle = TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ); if (dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle != - rowStyle) { + rowStyle && + dataRow.rowType != RowType.captionSummaryCoveredRow) { dataRow.rowIndexChanged(); - notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, - propertyName: 'hoverOnCell'); - } - - /// FLUT-5777 Invoke markNeedsPaint only the widget is attached in the - /// current widget tree. The `owner` property will be null if it is unattached. - if (owner != null) { - markNeedsPaint(); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'hoverOnCell', + ); } + markNeedsPaint(); } } void _onPointerExit(PointerExitEvent event) { + if (!hasOwner()) { + return; + } final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); if (dataGridConfiguration.allowColumnsResizing) { - dataGridConfiguration.columnResizeController - .onPointerExit(event, dataRow); + dataGridConfiguration.columnResizeController.onPointerExit( + event, + dataRow, + ); } if (dataRow.isHoveredRow && dataGridConfiguration.highlightRowOnHover && dataGridConfiguration.isDesktop && - dataRow.rowType == RowType.dataRow) { + (dataRow.rowType == RowType.dataRow || + dataRow.rowType == RowType.captionSummaryCoveredRow)) { dataRow.isHoveredRow = false; final TextStyle rowStyle = TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.87)); + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: dataGridConfiguration.colorScheme!.onSurface[222], + ); if (dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle != - rowStyle) { + rowStyle && + dataRow.rowType != RowType.captionSummaryCoveredRow) { dataRow.rowIndexChanged(); - notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, - propertyName: 'hoverOnCell'); - } - - /// FLUT-5777 Invoke markNeedsPaint only the widget is attached in the - /// current widget tree. The `owner` property will be null if it is unattached. - if (owner != null) { - markNeedsPaint(); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'hoverOnCell', + ); } + markNeedsPaint(); } } } @@ -1436,9 +1729,10 @@ class GridCellRenderObjectWidget extends SingleChildRenderObjectWidget { @override RenderGridCell createRenderObject(BuildContext context) => RenderGridCell( - dataCell: dataCell, - isDirty: isDirty, - dataGridStateDetails: dataGridStateDetails); + dataCell: dataCell, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails, + ); @override void updateRenderObject(BuildContext context, RenderGridCell renderObject) { @@ -1455,14 +1749,14 @@ class RenderGridCell extends RenderBox with RenderObjectWithChildMixin implements MouseTrackerAnnotation { /// Creates a [RenderGridCell] for `SfDataGrid`. - RenderGridCell( - {RenderBox? child, - required DataCellBase dataCell, - required bool isDirty, - required DataGridStateDetails dataGridStateDetails}) - : _dataCell = dataCell, - _isDirty = isDirty, - _dataGridStateDetails = dataGridStateDetails { + RenderGridCell({ + RenderBox? child, + required DataCellBase dataCell, + required bool isDirty, + required DataGridStateDetails dataGridStateDetails, + }) : _dataCell = dataCell, + _isDirty = isDirty, + _dataGridStateDetails = dataGridStateDetails { this.child = child; } @@ -1510,24 +1804,41 @@ class RenderGridCell extends RenderBox final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); final double lineWidth = dataRow.getColumnWidth( - dataCell.columnIndex, dataCell.columnIndex + dataCell.columnSpan); + dataCell.columnIndex, + dataCell.columnIndex + dataCell.columnSpan, + ); final double lineHeight = dataRow.getRowHeight( - dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex); + dataCell.rowIndex - dataCell.rowSpan, + dataCell.rowIndex, + ); if (dataRow.rowType == RowType.stackedHeaderRow) { _columnRect = _getStackedHeaderCellRect( - dataGridConfiguration, lineWidth, lineHeight); - } else if (dataRow.tableSummaryRow != null && - (dataRow.rowType == RowType.tableSummaryRow || - dataRow.rowType == RowType.tableSummaryCoveredRow)) { + dataGridConfiguration, + lineWidth, + lineHeight, + ); + } else if ((dataRow.tableSummaryRow != null && + (dataRow.rowType == RowType.tableSummaryRow || + dataRow.rowType == RowType.tableSummaryCoveredRow)) || + dataRow.rowType == RowType.captionSummaryCoveredRow) { _columnRect = _getTableSummaryCellRect( - dataGridConfiguration, lineWidth, lineHeight); + dataGridConfiguration, + lineWidth, + lineHeight, + ); } else { - final VisibleLineInfo? lineInfo = - dataRow.getColumnVisibleLineInfo(dataCell.columnIndex); + final VisibleLineInfo? lineInfo = dataRow.getColumnVisibleLineInfo( + dataCell.columnIndex, + ); final double origin = lineInfo != null ? lineInfo.origin : 0.0; _columnRect = _getCellRect( - dataGridConfiguration, lineInfo, origin, lineWidth, lineHeight); + dataGridConfiguration, + lineInfo, + origin, + lineWidth, + lineHeight, + ); } } else { _columnRect = Rect.zero; @@ -1537,11 +1848,12 @@ class RenderGridCell extends RenderBox } Rect? _getCellRect( - DataGridConfiguration dataGridConfiguration, - VisibleLineInfo? lineInfo, - double origin, - double lineWidth, - double lineHeight) { + DataGridConfiguration dataGridConfiguration, + VisibleLineInfo? lineInfo, + double origin, + double lineWidth, + double lineHeight, + ) { final DataRowBase dataRow = dataCell.dataRow!; final int rowIndex = dataCell.rowIndex; final int rowSpan = dataCell.rowSpan; @@ -1561,27 +1873,36 @@ class RenderGridCell extends RenderBox if (dataCell.cellType != CellType.stackedHeaderCell) { // Clipping the column when frozen column applied - _cellClipRect = - _getCellClipRect(dataGridConfiguration, lineInfo, lineHeight); + _cellClipRect = _getCellClipRect( + dataGridConfiguration, + lineInfo, + lineHeight, + ); } - final double topPosition = (rowSpan > 0) - ? -dataRow.getRowHeight(rowIndex - rowSpan, rowIndex - 1) - : 0.0; + final double topPosition = + (rowSpan > 0) + ? -dataRow.getRowHeight(rowIndex - rowSpan, rowIndex - 1) + : 0.0; _columnRect = Rect.fromLTWH(origin, topPosition, lineWidth, lineHeight); return _columnRect; } - double _getCellClippedOrigin(DataGridConfiguration dataGridConfiguration, - int startIndex, int endIndex) { + double _getCellClippedOrigin( + DataGridConfiguration dataGridConfiguration, + int startIndex, + int endIndex, + ) { double origin = 0.0; final ScrollAxisBase scrollColumns = dataGridConfiguration.container.scrollColumns; bool updateOrigin(int index, bool isRTL) { - final VisibleLineInfo? newLine = - scrollColumns.getVisibleLineAtLineIndex(index, isRightToLeft: true); + final VisibleLineInfo? newLine = scrollColumns.getVisibleLineAtLineIndex( + index, + isRightToLeft: true, + ); if (newLine != null) { // Set origin to zero when scrolling is disabled in RTL. @@ -1592,14 +1913,21 @@ class RenderGridCell extends RenderBox } // Set origin only if the line is not footer. if (!newLine.isFooter) { - origin += isRTL && newLine.isClippedCorner - ? newLine.clippedSize - : newLine.size - newLine.clippedCorner; + // Adjust origin if the line is clipped. + if (newLine.isClipped) { + origin += + isRTL && newLine.isClippedCorner + ? newLine.clippedSize + : newLine.size - newLine.clippedCorner; + } return false; } } else { - origin += - dataCell.dataRow!.getColumnWidth(index, index, lineNull: true); + origin += dataCell.dataRow!.getColumnWidth( + index, + index, + lineNull: true, + ); } return true; } @@ -1620,20 +1948,25 @@ class RenderGridCell extends RenderBox return origin; } - double _getCellClippedSize(DataGridConfiguration dataGridConfiguration, - int startIndex, int endIndex) { + double _getCellClippedSize( + DataGridConfiguration dataGridConfiguration, + int startIndex, + int endIndex, + ) { double clippedSize = 0; final bool isRTL = dataGridConfiguration.textDirection == TextDirection.rtl; for (int index = startIndex; index <= endIndex; index++) { final VisibleLineInfo? newLine = dataGridConfiguration - .container.scrollColumns + .container + .scrollColumns .getVisibleLineAtLineIndex(index, isRightToLeft: isRTL); if (newLine != null) { - clippedSize += isRTL && newLine.isClippedCorner - ? newLine.clippedCornerExtent - : newLine.clippedSize; + clippedSize += + isRTL && newLine.isClippedCorner + ? newLine.clippedCornerExtent + : newLine.clippedSize; } } return clippedSize; @@ -1648,27 +1981,42 @@ class RenderGridCell extends RenderBox if (!line.isHeader && dataCell.columnSpan > 0) { final int endIndex = dataCell.columnIndex + dataCell.columnSpan; final double lineSize = _getCellClippedSize( - dataGridConfiguration, line.lineIndex, endIndex); + dataGridConfiguration, + line.lineIndex, + endIndex, + ); final double clipOrigin = _getCellClippedOrigin( - dataGridConfiguration, dataCell.columnIndex, endIndex); + dataGridConfiguration, + dataCell.columnIndex, + endIndex, + ); _cellClipRect = Rect.fromLTWH(clipOrigin, 0.0, lineSize, lineHeight); } else { - _cellClipRect = - _getCellClipRect(dataGridConfiguration, line, lineHeight); + _cellClipRect = _getCellClipRect( + dataGridConfiguration, + line, + lineHeight, + ); } } } - Rect _getTableSummaryCellRect(DataGridConfiguration dataGridConfiguration, - double lineWidth, double lineHeight) { + Rect _getTableSummaryCellRect( + DataGridConfiguration dataGridConfiguration, + double lineWidth, + double lineHeight, + ) { if (dataCell.dataRow!.rowType == RowType.tableSummaryCoveredRow) { - lineWidth = min(dataGridConfiguration.viewWidth, - dataGridConfiguration.container.extentWidth); + lineWidth = min( + dataGridConfiguration.viewWidth, + dataGridConfiguration.container.extentWidth, + ); double offset = dataGridConfiguration.container.horizontalOffset; if (dataGridConfiguration.textDirection == TextDirection.rtl && dataGridConfiguration.viewWidth > dataGridConfiguration.container.extentWidth) { - offset += dataGridConfiguration.viewWidth - + offset += + dataGridConfiguration.viewWidth - dataGridConfiguration.container.extentWidth; } return Rect.fromLTWH(offset, 0.0, lineWidth, lineHeight); @@ -1689,17 +2037,22 @@ class RenderGridCell extends RenderBox if (line == null) { // Gets the origin for the first visible line in the spanned cell and // calculates the clipped size. - for (int index = dataCell.columnIndex; - index <= dataCell.columnIndex + dataCell.columnSpan; - index++) { + for ( + int index = dataCell.columnIndex; + index <= dataCell.columnIndex + dataCell.columnSpan; + index++ + ) { final VisibleLineInfo? newLine = getVisibleLineInfo(index); if (newLine != null) { setOrigin(newLine); _setClipRect(newLine, lineHeight); break; } else { - clippedWidth += - dataCell.dataRow!.getColumnWidth(index, index, lineNull: true); + clippedWidth += dataCell.dataRow!.getColumnWidth( + index, + index, + lineNull: true, + ); } } } else { @@ -1709,16 +2062,20 @@ class RenderGridCell extends RenderBox origin += dataGridConfiguration.container.horizontalOffset; - origin = dataGridConfiguration.textDirection == TextDirection.rtl - ? (origin + clippedWidth) - lineWidth - : origin - clippedWidth; + origin = + dataGridConfiguration.textDirection == TextDirection.rtl + ? (origin + clippedWidth) - lineWidth + : origin - clippedWidth; return Rect.fromLTWH(origin, 0.0, lineWidth, lineHeight); } } - Rect? _getStackedHeaderCellRect(DataGridConfiguration dataGridConfiguration, - double lineWidth, double lineHeight) { + Rect? _getStackedHeaderCellRect( + DataGridConfiguration dataGridConfiguration, + double lineWidth, + double lineHeight, + ) { final DataRowBase dataRow = dataCell.dataRow!; final int cellStartIndex = dataCell.columnIndex; final int columnSpan = dataCell.columnSpan; @@ -1738,16 +2095,21 @@ class RenderGridCell extends RenderBox if (frozenColumns > cellStartIndex && frozenColumns <= cellEndIndex) { if (dataGridConfiguration.textDirection == TextDirection.ltr) { - for (int index = cellEndIndex; - index >= frozenColumnsCount - 1; - index--) { + for ( + int index = cellEndIndex; + index >= frozenColumnsCount - 1; + index-- + ) { lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); if (lineInfo != null) { - final VisibleLineInfo? startLineInfo = - scrollColumns.getVisibleLineAtLineIndex(cellStartIndex); + final VisibleLineInfo? startLineInfo = scrollColumns + .getVisibleLineAtLineIndex(cellStartIndex); origin = startLineInfo?.origin; lineWidth = _getClippedWidth( - dataGridConfiguration, cellStartIndex, cellEndIndex); + dataGridConfiguration, + cellStartIndex, + cellEndIndex, + ); break; } } @@ -1757,7 +2119,10 @@ class RenderGridCell extends RenderBox if (lineInfo != null) { origin = lineInfo.origin < 0 ? 0.0 : lineInfo.origin; lineWidth = _getClippedWidth( - dataGridConfiguration, cellStartIndex, cellEndIndex); + dataGridConfiguration, + cellStartIndex, + cellEndIndex, + ); if (lineInfo.origin < 0) { lineWidth += lineInfo.origin; } @@ -1774,13 +2139,18 @@ class RenderGridCell extends RenderBox if (lineInfo != null) { if (index == columnsLength - footerFrozenColumns) { origin = lineInfo.origin; - lineWidth = - dataRow.getColumnWidth(cellStartIndex + span, cellEndIndex); + lineWidth = dataRow.getColumnWidth( + cellStartIndex + span, + cellEndIndex, + ); break; } else { origin = lineInfo.clippedOrigin; lineWidth = _getClippedWidth( - dataGridConfiguration, cellStartIndex, cellEndIndex); + dataGridConfiguration, + cellStartIndex, + cellEndIndex, + ); break; } } @@ -1791,18 +2161,23 @@ class RenderGridCell extends RenderBox for (int index = cellStartIndex; index <= cellEndIndex; index++) { lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); if (lineInfo != null) { - final VisibleLineInfo? line = - scrollColumns.getVisibleLineAtLineIndex(cellEndIndex); + final VisibleLineInfo? line = scrollColumns + .getVisibleLineAtLineIndex(cellEndIndex); if (line != null) { if (index == columnsLength - footerFrozenColumnsCount) { origin = line.origin; - lineWidth = - dataRow.getColumnWidth(cellStartIndex + span, cellEndIndex); + lineWidth = dataRow.getColumnWidth( + cellStartIndex + span, + cellEndIndex, + ); break; } else { origin = line.clippedOrigin - lineInfo.scrollOffset; lineWidth = _getClippedWidth( - dataGridConfiguration, cellStartIndex, cellEndIndex); + dataGridConfiguration, + cellStartIndex, + cellEndIndex, + ); break; } } @@ -1816,12 +2191,18 @@ class RenderGridCell extends RenderBox for (int index = cellStartIndex; index <= cellEndIndex; index++) { lineInfo = dataRow.getColumnVisibleLineInfo(index); if (lineInfo != null) { - origin = lineInfo.origin + + origin = + lineInfo.origin + dataRow.getColumnWidth(index, index + span) - dataRow.getColumnWidth(cellStartIndex, cellEndIndex); - _cellClipRect = _getSpannedCellClipRect(dataGridConfiguration, - dataRow, dataCell, lineHeight, lineWidth); + _cellClipRect = _getSpannedCellClipRect( + dataGridConfiguration, + dataRow, + dataCell, + lineHeight, + lineWidth, + ); break; } span -= 1; @@ -1830,12 +2211,18 @@ class RenderGridCell extends RenderBox for (int index = cellEndIndex; index >= cellStartIndex; index--) { lineInfo = dataRow.getColumnVisibleLineInfo(index); if (lineInfo != null) { - origin = lineInfo.origin + + origin = + lineInfo.origin + dataRow.getColumnWidth(index - span, index) - dataRow.getColumnWidth(cellStartIndex, cellEndIndex); - _cellClipRect = _getSpannedCellClipRect(dataGridConfiguration, - dataRow, dataCell, lineHeight, lineWidth); + _cellClipRect = _getSpannedCellClipRect( + dataGridConfiguration, + dataRow, + dataCell, + lineHeight, + lineWidth, + ); break; } span -= 1; @@ -1846,28 +2233,37 @@ class RenderGridCell extends RenderBox if (lineInfo != null) { // If resizing a column to 0 width the origin value is return null // So We need to set origin value as 0.0 - columnRect = _getCellRect(dataGridConfiguration, lineInfo, origin ?? 0.0, - lineWidth, lineHeight); + columnRect = _getCellRect( + dataGridConfiguration, + lineInfo, + origin ?? 0.0, + lineWidth, + lineHeight, + ); } return columnRect; } - double _getClippedWidth(DataGridConfiguration dataGridConfiguration, - int startIndex, int endIndex) { + double _getClippedWidth( + DataGridConfiguration dataGridConfiguration, + int startIndex, + int endIndex, + ) { double clippedWidth = 0; for (int index = startIndex; index <= endIndex; index++) { - final VisibleLineInfo? newline = - dataCell.dataRow!.getColumnVisibleLineInfo(index); + final VisibleLineInfo? newline = dataCell.dataRow! + .getColumnVisibleLineInfo(index); if (newline != null) { if (dataGridConfiguration.textDirection == TextDirection.ltr) { clippedWidth += newline.isClipped ? newline.clippedSize : newline.size; } else { - clippedWidth += newline.isClipped - ? newline.clippedCornerExtent > 0 - ? newline.clippedCornerExtent - : newline.clippedSize - : newline.size; + clippedWidth += + newline.isClipped + ? newline.clippedCornerExtent > 0 + ? newline.clippedCornerExtent + : newline.clippedSize + : newline.size; } } } @@ -1903,10 +2299,12 @@ class RenderGridCell extends RenderBox final BoxParentData childParentData = child!.parentData! as BoxParentData; final bool isHit = result.addWithPaintOffset( - offset: childParentData.offset, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) => - child!.hitTest(result, position: transformed)); + offset: childParentData.offset, + position: position, + hitTest: + (BoxHitTestResult result, Offset transformed) => + child!.hitTest(result, position: transformed), + ); if (isHit) { return true; } else { @@ -1922,14 +2320,18 @@ class RenderGridCell extends RenderBox @override void performLayout() { - size = constraints - .constrain(Size(constraints.maxWidth, constraints.maxHeight)); + size = constraints.constrain( + Size(constraints.maxWidth, constraints.maxHeight), + ); if (child != null) { child!.layout( - BoxConstraints.tightFor( - width: constraints.maxWidth, height: constraints.maxHeight), - parentUsesSize: true); + BoxConstraints.tightFor( + width: constraints.maxWidth, + height: constraints.maxHeight, + ), + parentUsesSize: true, + ); } } @@ -1949,7 +2351,8 @@ class RenderGridCell extends RenderBox MouseCursor get cursor { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); return dataGridConfiguration - .columnResizeController.canSwitchResizeColumnCursor + .columnResizeController + .canSwitchResizeColumnCursor ? SystemMouseCursors.resizeColumn : SystemMouseCursors.basic; } @@ -1990,13 +2393,19 @@ class RenderGridCell extends RenderBox } void _paintHoverColor(PaintingContext context) { - if (dataCell.cellType == CellType.headerCell && _isHovered) { + if (dataCell.cellType == CellType.headerCell && + _isHovered && + !_dataGridStateDetails().columnDragAndDropController.isHoverDisabled) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); dataGridConfiguration.gridPaint!.color = - dataGridConfiguration.dataGridThemeHelper!.headerHoverColor; - final Rect cellRect = - Rect.fromLTRB(0, 0, constraints.maxWidth, constraints.maxHeight); + dataGridConfiguration.dataGridThemeHelper!.headerHoverColor!; + final Rect cellRect = Rect.fromLTRB( + 0, + 0, + constraints.maxWidth, + constraints.maxHeight, + ); context.canvas.drawRect(cellRect, dataGridConfiguration.gridPaint!); } @@ -2007,8 +2416,11 @@ class RenderGridCell extends RenderBox bool get validForMouseTracker => dataCell.cellType == CellType.headerCell; } -Rect? _getCellClipRect(DataGridConfiguration dataGridConfiguration, - VisibleLineInfo? lineInfo, double rowHeight) { +Rect? _getCellClipRect( + DataGridConfiguration dataGridConfiguration, + 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) { @@ -2017,9 +2429,12 @@ Rect? _getCellClipRect(DataGridConfiguration dataGridConfiguration, if (lineInfo.isClippedBody && lineInfo.isClippedOrigin && lineInfo.isClippedCorner) { - final double left = dataGridConfiguration.textDirection == TextDirection.ltr - ? lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent - : lineInfo.clippedSize; + final double left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.size - + lineInfo.clippedSize - + lineInfo.clippedCornerExtent + : lineInfo.clippedSize; final double right = dataGridConfiguration.textDirection == TextDirection.ltr ? lineInfo.clippedSize @@ -2027,9 +2442,12 @@ Rect? _getCellClipRect(DataGridConfiguration dataGridConfiguration, return Rect.fromLTWH(left, 0.0, right, rowHeight); } else if (lineInfo.isClippedBody && lineInfo.isClippedOrigin) { - final double left = dataGridConfiguration.textDirection == TextDirection.ltr - ? lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent - : 0.0; + final double left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.size - + lineInfo.clippedSize - + lineInfo.clippedCornerExtent + : 0.0; final double right = dataGridConfiguration.textDirection == TextDirection.ltr ? lineInfo.size @@ -2037,9 +2455,10 @@ Rect? _getCellClipRect(DataGridConfiguration dataGridConfiguration, return Rect.fromLTWH(left, 0.0, right, rowHeight); } else if (lineInfo.isClippedBody && lineInfo.isClippedCorner) { - final double left = dataGridConfiguration.textDirection == TextDirection.ltr - ? 0.0 - : lineInfo.size - (lineInfo.size - lineInfo.clippedSize); + final double left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? 0.0 + : lineInfo.size - (lineInfo.size - lineInfo.clippedSize); final double right = dataGridConfiguration.textDirection == TextDirection.ltr ? lineInfo.clippedSize @@ -2052,19 +2471,24 @@ Rect? _getCellClipRect(DataGridConfiguration dataGridConfiguration, } Rect? _getSpannedCellClipRect( - DataGridConfiguration dataGridConfiguration, - DataRowBase dataRow, - DataCellBase dataCell, - double cellHeight, - double cellWidth) { + DataGridConfiguration dataGridConfiguration, + DataRowBase dataRow, + DataCellBase dataCell, + double cellHeight, + double cellWidth, +) { Rect? clipRect; int firstVisibleStackedColumnIndex = dataCell.columnIndex; double lastCellClippedSize = 0.0; bool isLastCellClippedCorner = false; bool isLastCellClippedBody = false; - double getClippedWidth(DataCellBase dataCell, DataRowBase dataRow, - {bool columnsNotInViewWidth = false, bool allCellsClippedWidth = false}) { + double getClippedWidth( + DataCellBase dataCell, + DataRowBase dataRow, { + bool columnsNotInViewWidth = false, + bool allCellsClippedWidth = false, + }) { final int startIndex = dataCell.columnIndex; final int endIndex = dataCell.columnIndex + dataCell.columnSpan; double clippedWidth = 0; @@ -2072,8 +2496,8 @@ Rect? _getSpannedCellClipRect( final VisibleLineInfo? newline = dataRow.getColumnVisibleLineInfo(index); if (columnsNotInViewWidth) { if (newline == null) { - clippedWidth += - dataGridConfiguration.container.scrollColumns.getLineSize(index); + clippedWidth += dataGridConfiguration.container.scrollColumns + .getLineSize(index); } else { firstVisibleStackedColumnIndex = index; break; @@ -2085,11 +2509,12 @@ Rect? _getSpannedCellClipRect( clippedWidth += newline.isClipped ? newline.clippedSize : newline.size; } else { - clippedWidth += newline.isClipped - ? newline.clippedCornerExtent > 0 - ? newline.clippedCornerExtent - : newline.clippedSize - : newline.size; + clippedWidth += + newline.isClipped + ? newline.clippedCornerExtent > 0 + ? newline.clippedCornerExtent + : newline.clippedSize + : newline.size; } lastCellClippedSize = newline.clippedSize; isLastCellClippedCorner = newline.isClippedCorner; @@ -2106,16 +2531,24 @@ Rect? _getSpannedCellClipRect( } if (dataCell.renderer != null) { - final double columnsNotInViewWidth = - getClippedWidth(dataCell, dataRow, columnsNotInViewWidth: true); - final double clippedWidth = - getClippedWidth(dataCell, dataRow, allCellsClippedWidth: true); - final VisibleLineInfo? visibleLineInfo = - dataRow.getColumnVisibleLineInfo(firstVisibleStackedColumnIndex); + final double columnsNotInViewWidth = getClippedWidth( + dataCell, + dataRow, + columnsNotInViewWidth: true, + ); + final double clippedWidth = getClippedWidth( + dataCell, + dataRow, + allCellsClippedWidth: true, + ); + final VisibleLineInfo? visibleLineInfo = dataRow.getColumnVisibleLineInfo( + firstVisibleStackedColumnIndex, + ); if (visibleLineInfo != null) { if (visibleLineInfo.isClippedOrigin && visibleLineInfo.isClippedCorner) { - final double clippedOrigin = columnsNotInViewWidth + + final double clippedOrigin = + columnsNotInViewWidth + visibleLineInfo.size - (visibleLineInfo.clippedSize + visibleLineInfo.clippedCornerExtent); @@ -2130,7 +2563,8 @@ Rect? _getSpannedCellClipRect( clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); } else if (visibleLineInfo.isClippedOrigin) { - final double clippedOriginLTR = columnsNotInViewWidth + + final double clippedOriginLTR = + columnsNotInViewWidth + visibleLineInfo.size - visibleLineInfo.clippedSize; final double clippedOriginRTL = @@ -2154,8 +2588,8 @@ Rect? _getSpannedCellClipRect( dataGridConfiguration.textDirection == TextDirection.ltr ? columnsNotInViewWidth : dataCell.columnIndex < firstVisibleStackedColumnIndex - ? 0.0 - : cellWidth - clippedWidth; + ? 0.0 + : cellWidth - clippedWidth; final double right = dataGridConfiguration.textDirection == TextDirection.ltr ? clippedWidth @@ -2166,18 +2600,18 @@ Rect? _getSpannedCellClipRect( if (clippedWidth < cellWidth) { double left; if (dataCell.columnIndex < firstVisibleStackedColumnIndex) { - left = dataGridConfiguration.textDirection == TextDirection.ltr - ? cellWidth - clippedWidth - : 0.0; + left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? cellWidth - clippedWidth + : 0.0; } else { - left = dataGridConfiguration.textDirection == TextDirection.ltr - ? 0.0 - : cellWidth - clippedWidth; + left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? 0.0 + : cellWidth - clippedWidth; } clipRect = Rect.fromLTWH(left, 0.0, clippedWidth, cellHeight); - } else if (clipRect != null) { - clipRect = null; } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart index 35a55b2d4..43cd610aa 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart @@ -9,8 +9,10 @@ import '../../grid_common/line_size_host.dart'; import '../../grid_common/scroll_axis.dart'; import '../../grid_common/scrollbar.dart'; import '../../grid_common/visible_line_info.dart'; +import '../grouping/grouping.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/datagrid_helper.dart'; import '../helper/enums.dart'; import '../helper/selection_helper.dart' as selection_helper; import '../runtime/column.dart'; @@ -22,15 +24,17 @@ import 'rendering_widget.dart'; /// A [StatefulWidget] that build all the data grid views from the widget level. class ScrollViewWidget extends StatefulWidget { /// Creates a [ScrollViewWidget] for the [SfDataGrid]. - const ScrollViewWidget( - {required this.width, - required this.height, - required this.dataGridStateDetails}); - - /// The parent width of the datagid. + const ScrollViewWidget({ + super.key, + required this.width, + required this.height, + required this.dataGridStateDetails, + }); + + /// The parent width of the datagrid. final double width; - /// The parent height of the datagid. + /// The parent height of the datagrid. final double height; /// Holds the [DataGridStateDetails]. @@ -62,8 +66,9 @@ class _ScrollViewWidgetState extends State { _height = widget.height; _width = widget.width; - dataGridConfiguration.rowSelectionManager - .addListener(_handleSelectionController); + dataGridConfiguration.rowSelectionManager.addListener( + _handleSelectionController, + ); if (_dataGridFocusNode == null) { // [FocusNode.onKey] callback is not firing on key navigation after flutter @@ -92,35 +97,40 @@ class _ScrollViewWidgetState extends State { _dataGridConfiguration.rowSelectionManager; void _verticalListener() { - setState(() { - final double newValue = _verticalController!.offset; - _container.verticalOffset = newValue; - _container.setRowHeights(); - _container.resetSwipeOffset(); - _isScrolling = true; - _container.isDirty = true; - _dataGridConfiguration.scrollingState = ScrollDirection.forward; - _isLoadMoreViewLoaded = false; - }); + if (mounted) { + setState(() { + final double newValue = _verticalController!.offset; + _container.verticalOffset = newValue; + _container.setRowHeights(); + _container.resetSwipeOffset(); + _isScrolling = true; + _container.isDirty = true; + _dataGridConfiguration.scrollingState = ScrollDirection.forward; + _isLoadMoreViewLoaded = false; + }); + } } void _horizontalListener() { - setState(() { - final double newValue = _horizontalController!.offset; - _container.horizontalOffset = newValue; - // Updating the width of all columns initially and inside the - // `ScrollViewWidget` build method when setting the container_isDirty to - // `true`. Thus, Don't necessary to update column widths while horizontal - // scrolling. - // DataGridSettings.columnSizer._refresh(widget.width); - _container.resetSwipeOffset(); - _dataGridConfiguration.scrollingState = ScrollDirection.forward; - if (!_dataGridConfiguration - .columnResizeController.isResizeIndicatorVisible) { - _isScrolling = true; - } - _container.isDirty = true; - }); + if (mounted) { + setState(() { + final double newValue = _horizontalController!.offset; + _container.horizontalOffset = newValue; + // Updating the width of all columns initially and inside the + // `ScrollViewWidget` build method when setting the container_isDirty to + // `true`. Thus, Don't necessary to update column widths while horizontal + // scrolling. + // DataGridSettings.columnSizer._refresh(widget.width); + _container.resetSwipeOffset(); + _dataGridConfiguration.scrollingState = ScrollDirection.forward; + if (!_dataGridConfiguration + .columnResizeController + .isResizeIndicatorVisible) { + _isScrolling = true; + } + _container.isDirty = true; + }); + } } void _updateAxis() { @@ -131,12 +141,13 @@ class _ScrollViewWidgetState extends State { void _setHorizontalOffset() { if (_container.needToSetHorizontalOffset) { - _container.horizontalOffset = _horizontalController!.hasClients - ? _horizontalController!.offset - : 0.0; + if (_horizontalController!.hasClients) { + _container.horizontalOffset = _horizontalController!.offset; + } else if (_horizontalController!.initialScrollOffset <= 0) { + _container.horizontalOffset = 0.0; + } _container.scrollColumns.markDirty(); } - _container.needToSetHorizontalOffset = false; } @@ -166,8 +177,9 @@ class _ScrollViewWidgetState extends State { void _ensureItems(bool needToRefresh) { final VisibleLinesCollection visibleRows = _container.scrollRows.getVisibleLines(); - final VisibleLinesCollection visibleColumns = - grid_helper.getVisibleLines(widget.dataGridStateDetails()); + final VisibleLinesCollection visibleColumns = grid_helper.getVisibleLines( + widget.dataGridStateDetails(), + ); if (_container.isGridLoaded && visibleColumns.isNotEmpty) { rowGenerator.ensureRows(visibleRows, visibleColumns); @@ -180,8 +192,12 @@ class _ScrollViewWidgetState extends State { } } - Widget _buildScrollView(double extentWidth, double scrollViewHeight, - double extentHeight, Size containerSize) { + Widget _buildScrollView( + double extentWidth, + double scrollViewHeight, + double extentHeight, + Size containerSize, + ) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; // Issue: @@ -194,7 +210,9 @@ class _ScrollViewWidgetState extends State { // We have fixed it by handling only to the respective scroll direction // based on the scrollbar. bool handleNotificationPredicate( - ScrollNotification notification, Axis direction) { + ScrollNotification notification, + Axis direction, + ) { // Issue: // FLUT-6320 - Horizontal scrollbar is showing when typing the text in // TextField widget more than cell width in editing. @@ -222,38 +240,69 @@ class _ScrollViewWidgetState extends State { // both scroll views. // For more info: https://github.com/flutter/flutter/issues/70380#issuecomment-841502797 Widget scrollView = Scrollbar( + thickness: dataGridConfiguration.showVerticalScrollbar ? null : 0, controller: _verticalController, - isAlwaysShown: dataGridConfiguration.isScrollbarAlwaysShown, - notificationPredicate: (ScrollNotification notification) => - handleNotificationPredicate(notification, Axis.vertical), + thumbVisibility: dataGridConfiguration.isScrollbarAlwaysShown, + notificationPredicate: + (ScrollNotification notification) => + handleNotificationPredicate(notification, Axis.vertical), child: Scrollbar( + thickness: dataGridConfiguration.showHorizontalScrollbar ? null : 0, controller: _horizontalController, - isAlwaysShown: dataGridConfiguration.isScrollbarAlwaysShown, - notificationPredicate: (ScrollNotification notification) => - handleNotificationPredicate(notification, Axis.horizontal), + thumbVisibility: dataGridConfiguration.isScrollbarAlwaysShown, + notificationPredicate: + (ScrollNotification notification) => + handleNotificationPredicate(notification, Axis.horizontal), child: SingleChildScrollView( controller: _verticalController, - physics: dataGridConfiguration.isSwipingApplied - ? const NeverScrollableScrollPhysics() - : dataGridConfiguration.verticalScrollPhysics, + physics: + dataGridConfiguration.isSwipingApplied + ? const NeverScrollableScrollPhysics() + : dataGridConfiguration.verticalScrollPhysics, child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: min(scrollViewHeight, extentHeight)), + // FLUT-6553-BoxConstraints has a negative minimum height exception has been thrown. + // we need to set height as 0 if it's negative value + constraints: BoxConstraints( + minHeight: max(0, min(scrollViewHeight, extentHeight)), + ), child: SingleChildScrollView( controller: _horizontalController, scrollDirection: Axis.horizontal, - physics: dataGridConfiguration.isSwipingApplied - ? const NeverScrollableScrollPhysics() - : dataGridConfiguration.horizontalScrollPhysics, + physics: + dataGridConfiguration.isSwipingApplied + ? const NeverScrollableScrollPhysics() + : dataGridConfiguration.horizontalScrollPhysics, child: ConstrainedBox( - constraints: BoxConstraints(minWidth: min(_width, extentWidth)), - child: _VisualContainer( - key: const ValueKey('SfDataGrid-VisualContainer'), - isDirty: _container.isDirty, - rowGenerator: rowGenerator, - containerSize: containerSize, - dataGridStateDetails: widget.dataGridStateDetails, + // FLUT-6553-BoxConstraints has a negative minimum width exception has been thrown. + // we need to set width as 0 if it's negative value + constraints: BoxConstraints( + minWidth: max(0, min(_width, extentWidth)), ), + child: + _canShowPlaceHolder() + ? SizedBox( + width: extentWidth, + child: Transform.translate( + offset: Offset(_container.horizontalOffset, 0), + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: scrollViewHeight, + width: min(_width, extentWidth), + child: dataGridConfiguration.placeholder, + ), + ), + ), + ) + : _VisualContainer( + key: const ValueKey( + 'SfDataGrid-VisualContainer', + ), + isDirty: _container.isDirty, + rowGenerator: rowGenerator, + containerSize: containerSize, + dataGridStateDetails: widget.dataGridStateDetails, + ), ), ), ), @@ -263,11 +312,11 @@ class _ScrollViewWidgetState extends State { if (_dataGridConfiguration.allowPullToRefresh) { scrollView = RefreshIndicator( - child: scrollView, key: dataGridConfiguration.refreshIndicatorKey, onRefresh: () => handleRefresh(dataGridConfiguration.source), strokeWidth: dataGridConfiguration.refreshIndicatorStrokeWidth, displacement: dataGridConfiguration.refreshIndicatorDisplacement, + child: scrollView, ); } @@ -277,25 +326,32 @@ class _ScrollViewWidgetState extends State { void _addScrollView(List children) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; final double extentWidth = _container.extentWidth; - final double headerRowsHeight = _container.scrollRows - .rangeToRegionPoints( - 0, grid_helper.getHeaderIndex(dataGridConfiguration), true)[1] - .length; + final double headerRowsHeight = + _container.scrollRows + .rangeToRegionPoints( + 0, + grid_helper.getHeaderIndex(dataGridConfiguration), + true, + )[1] + .length; final double extentHeight = _container.extentHeight - headerRowsHeight; final double scrollViewHeight = _height - headerRowsHeight; final Size containerSize = Size( - _canDisableHorizontalScrolling(dataGridConfiguration) - ? _width - : max(_width, extentWidth), - _canDisableVerticalScrolling(dataGridConfiguration) - ? scrollViewHeight - : (extentHeight > scrollViewHeight - ? extentHeight - : scrollViewHeight)); + _canDisableHorizontalScrolling(dataGridConfiguration) + ? _width + : max(_width, extentWidth), + _canDisableVerticalScrolling(dataGridConfiguration) + ? scrollViewHeight + : (extentHeight > scrollViewHeight ? extentHeight : scrollViewHeight), + ); final Widget scrollView = _buildScrollView( - extentWidth, scrollViewHeight, extentHeight, containerSize); + extentWidth, + scrollViewHeight, + extentHeight, + containerSize, + ); final Positioned wrapScrollView = Positioned.fill( top: headerRowsHeight, @@ -305,6 +361,15 @@ class _ScrollViewWidgetState extends State { children.add(wrapScrollView); } + bool _canShowPlaceHolder() { + // Added a column header count manually with stacked header rows. + final int headerRowCount = + _dataGridConfiguration.stackedHeaderRows.length + 1; + + return _dataGridConfiguration.placeholder != null && + _dataGridConfiguration.container.rowCount == headerRowCount; + } + void _addHeaderRows(List children) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; @@ -313,86 +378,103 @@ class _ScrollViewWidgetState extends State { ? _width : max(_width, _container.extentWidth); - List _buildHeaderRows() { + List buildHeaderRows() { final List headerRows = []; // Adds stacked header rows if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { - headerRows.addAll(rowGenerator.items - .where((DataRowBase row) => - row.rowIndex >= 0 && - row.rowRegion == RowRegion.header && - row.rowType == RowType.stackedHeaderRow) - .map((DataRowBase dataRow) => _HeaderCellsWidget( + headerRows.addAll( + rowGenerator.items + .where( + (DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == RowRegion.header && + row.rowType == RowType.stackedHeaderRow, + ) + .map( + (DataRowBase dataRow) => _HeaderCellsWidget( key: dataRow.key!, dataRow: dataRow, isDirty: _container.isDirty || dataRow.isDirty, dataGridStateDetails: widget.dataGridStateDetails, - )) - .toList(growable: false)); + ), + ) + .toList(growable: false), + ); } // Adds column header row - headerRows.addAll(rowGenerator.items - .where((DataRowBase row) => - row.rowIndex >= 0 && - row.rowRegion == RowRegion.header && - row.rowType == RowType.headerRow) - .map((DataRowBase dataRow) => _HeaderCellsWidget( + headerRows.addAll( + rowGenerator.items + .where( + (DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == RowRegion.header && + row.rowType == RowType.headerRow, + ) + .map( + (DataRowBase dataRow) => _HeaderCellsWidget( key: dataRow.key!, dataRow: dataRow, isDirty: _container.isDirty || dataRow.isDirty, dataGridStateDetails: widget.dataGridStateDetails, - )) - .toList(growable: false)); + ), + ) + .toList(growable: false), + ); return headerRows; } double getStartX() { if (dataGridConfiguration.textDirection == TextDirection.ltr) { + if (_horizontalController!.hasClients) { + _container.horizontalOffset = _horizontalController!.offset; + } return -_container.horizontalOffset; } else { if (!_horizontalController!.hasClients || _horizontalController!.offset <= 0.0 || _horizontalController!.position.maxScrollExtent <= 0.0 || _container.extentWidth <= _width) { + // When the shrinkWrapColumns property is set to true, the extent width + // and view width of the container are the same. So, the scroll controller's + // offset has considered to arrange the header row in RTL mode. + if (_container.extentWidth <= _width && + _horizontalController!.hasClients && + dataGridConfiguration.shrinkWrapColumns) { + return -_horizontalController!.offset; + } else if (!_horizontalController!.hasClients && + _horizontalController!.initialScrollOffset > 0) { + final double maxScrollExtent = _container.extentWidth - _width; + return -(maxScrollExtent - _container.horizontalOffset); + } return 0.0; - } else if (_horizontalController!.position.maxScrollExtent == - _horizontalController!.offset) { - return -_horizontalController!.position.maxScrollExtent; - } - - late double maxScrollExtent; - if (dataGridConfiguration - .columnResizeController.isResizeIndicatorVisible) { - // In RTL, Resolves the glitching issue of header rows while resizing - // the column by calculating the maxScrollExtent manually. - maxScrollExtent = _container.extentWidth - - _horizontalController!.position.viewportDimension; } else { - maxScrollExtent = _horizontalController!.position.maxScrollExtent; + // When the scroll view's content dimension change at runtime, the scroll view's + // maxScrollExtent will not be changed. So calculates the maximum scroll extent manually. + final double maxScrollExtent = _container.extentWidth - _width; + return -(maxScrollExtent - _container.horizontalOffset); } - - return -(maxScrollExtent - _container.horizontalOffset); } } if (rowGenerator.items.isNotEmpty) { - final List headerRows = _buildHeaderRows(); + final List headerRows = buildHeaderRows(); for (int i = 0; i < headerRows.length; i++) { - final VisibleLineInfo? lineInfo = - _container.scrollRows.getVisibleLineAtLineIndex(i); + final VisibleLineInfo? lineInfo = _container.scrollRows + .getVisibleLineAtLineIndex(i); final Positioned header = Positioned.directional( - textDirection: dataGridConfiguration.textDirection, - start: getStartX(), - top: lineInfo?.origin, - height: lineInfo?.size, - // FLUT-1971 Changed the header row widget as extendwidth instead of - // device width to resloved the issue of apply sorting to the - // invisible columns. - width: containerWidth, - child: headerRows[i]); + textDirection: dataGridConfiguration.textDirection, + start: getStartX(), + top: lineInfo?.origin, + height: lineInfo?.size, + // FLUT-1971 Changed the header row widget as extendwidth instead of + // device width to resolved the issue of apply sorting to the + // invisible columns. + width: containerWidth, + child: headerRows[i], + ); children.add(header); } } @@ -414,8 +496,10 @@ class _ScrollViewWidgetState extends State { } if (rowIndex > 0) { - top = columnResizeController.resizingDataCell!.dataRow! - .getRowHeight(0, rowIndex - 1); + top = columnResizeController.resizingDataCell!.dataRow!.getRowHeight( + 0, + rowIndex - 1, + ); } final Widget indicator = Positioned( @@ -424,14 +508,20 @@ class _ScrollViewWidgetState extends State { // Ignores the `hitTest` of the indicator to resolved the update of the // cursor visibility when hovering the column resizing indicator. child: IgnorePointer( - child: _dataGridConfiguration.isDesktop - ? Container( + child: + _dataGridConfiguration.isDesktop + ? Container( width: dataGridThemeHelper!.columnResizeIndicatorStrokeWidth, height: _container.extentHeight - top, - color: dataGridThemeHelper.columnResizeIndicatorColor) - : _getResizingCursor( - _dataGridConfiguration, dataGridThemeHelper!, top)), + color: dataGridThemeHelper.columnResizeIndicatorColor, + ) + : _getResizingCursor( + _dataGridConfiguration, + dataGridThemeHelper!, + top, + ), + ), ); children.add(indicator); } @@ -452,8 +542,10 @@ class _ScrollViewWidgetState extends State { (_verticalController!.offset >= _verticalController!.position.maxScrollExtent) && !_isLoadMoreViewLoaded) { - final Widget? loadMoreView = - dataGridConfiguration.loadMoreViewBuilder!(context, loadMoreRows); + final Widget? loadMoreView = dataGridConfiguration.loadMoreViewBuilder!( + context, + loadMoreRows, + ); if (loadMoreView != null) { final Alignment loadMoreAlignment = @@ -461,14 +553,14 @@ class _ScrollViewWidgetState extends State { ? Alignment.bottomLeft : Alignment.bottomRight; - children.add(Positioned( + children.add( + Positioned( top: 0.0, width: _width, height: _height, - child: Align( - alignment: loadMoreAlignment, - child: loadMoreView, - ))); + child: Align(alignment: loadMoreAlignment, child: loadMoreView), + ), + ); } } } @@ -478,16 +570,18 @@ class _ScrollViewWidgetState extends State { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; final DataGridThemeHelper? dataGridThemeHelper = dataGridConfiguration.dataGridThemeHelper; - if (dataGridThemeHelper!.frozenPaneElevation <= 0.0 || + if (dataGridThemeHelper!.frozenPaneElevation! <= 0.0 || dataGridConfiguration.columns.isEmpty || effectiveRows(dataGridConfiguration.source).isEmpty) { return; } - final Color frozenLineColorWithOpacity = - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.38); final Color frozenLineColorWithoutOpacity = - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.14); + dataGridThemeHelper.frozenPaneLineColor!; + + final Color frozenLineColorWithOpacity = dataGridThemeHelper + .frozenPaneLineColor! + .withValues(alpha: 0.14); void drawElevation({ EdgeInsets? margin, @@ -501,44 +595,53 @@ class _ScrollViewWidgetState extends State { required double spreadRadiusValue, }) { final Widget elevationLine = ClipRect( - child: Container( - width: axis == Axis.vertical ? 1 : 0, - height: axis == Axis.horizontal ? 1 : 0, - margin: margin, - decoration: BoxDecoration( - color: const Color(0xFF000000), - boxShadow: [ - BoxShadow( - color: frozenLineColor, - offset: Offset.zero, - spreadRadius: spreadRadiusValue, - blurRadius: blurRadiusValue, - ) - ]))); - - children.add(Positioned.directional( - top: top, - end: end, - start: start, - bottom: bottom, - child: elevationLine, - textDirection: dataGridConfiguration.textDirection, - )); + child: Container( + width: axis == Axis.vertical ? 1 : 0, + height: axis == Axis.horizontal ? 1 : 0, + margin: margin, + decoration: BoxDecoration( + color: const Color(0xFF000000), + boxShadow: [ + BoxShadow( + color: frozenLineColor, + spreadRadius: spreadRadiusValue, + blurRadius: blurRadiusValue, + ), + ], + ), + ), + ); + + children.add( + Positioned.directional( + top: top, + end: end, + start: start, + bottom: bottom, + textDirection: dataGridConfiguration.textDirection, + child: elevationLine, + ), + ); } double getTopPosition(DataRowBase columnHeaderRow, int columnIndex) { double top = 0.0; if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { top = columnHeaderRow.getRowHeight( - 0, dataGridConfiguration.stackedHeaderRows.length - 1); + 0, + dataGridConfiguration.stackedHeaderRows.length - 1, + ); final DataCellBase? dataCell = columnHeaderRow.visibleColumns .firstWhereOrNull( - (DataCellBase cell) => cell.columnIndex == columnIndex); + (DataCellBase 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); + dataCell.rowIndex - dataCell.rowSpan, + dataCell.rowIndex - 1, + ); } } return top; @@ -556,62 +659,78 @@ class _ScrollViewWidgetState extends State { ? _width - dataGridConfiguration.container.extentWidth : 0.0; - final DataRowBase? columnHeaderRow = - dataGridConfiguration.container.rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => row.rowType == RowType.headerRow); + final DataRowBase? columnHeaderRow = dataGridConfiguration + .container + .rowGenerator + .items + .firstWhereOrNull( + (DataRowBase 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 = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation + 4.0; - final int frozenColumnIndex = - grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); - final int footerFrozenColumnIndex = - grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration); - final int frozenRowIndex = - grid_helper.getLastFrozenRowIndex(dataGridConfiguration); - final int footerFrozenRowIndex = - grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration); - + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation! + 4.0; + final int frozenColumnIndex = grid_helper.getLastFrozenColumnIndex( + dataGridConfiguration, + ); + final int footerFrozenColumnIndex = grid_helper + .getStartFooterFrozenColumnIndex(dataGridConfiguration); + final int frozenRowIndex = grid_helper.getLastFrozenRowIndex( + dataGridConfiguration, + ); + final int footerFrozenRowIndex = grid_helper.getStartFooterFrozenRowIndex( + dataGridConfiguration, + ); + final int indentColumnCount = + dataGridConfiguration.source.groupedColumns.length; if (columnHeaderRow != null && frozenColumnIndex >= 0 && !_canDisableHorizontalScrolling(dataGridConfiguration)) { double spreadRadiusValue = 1.5; double blurRadiusValue = 0.0; - Color frozenLineColor = frozenLineColorWithOpacity; + Color frozenLineColor = frozenLineColorWithoutOpacity; final double top = getTopPosition(columnHeaderRow, frozenColumnIndex); - final double left = columnHeaderRow.getColumnWidth( - 0, dataGridConfiguration.frozenColumnsCount - 1); + final double left = + (columnHeaderRow.getColumnWidth( + indentColumnCount, + dataGridConfiguration.frozenColumnsCount + indentColumnCount - 1, + )) + + (dataGridThemeHelper.indentColumnWidth * indentColumnCount); if (dataGridConfiguration.textDirection == TextDirection.ltr && dataGridConfiguration.container.horizontalOffset > 0) { spreadRadiusValue = 3.0; blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; - frozenLineColor = frozenLineColorWithoutOpacity; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; + frozenLineColor = frozenLineColorWithOpacity; } if (dataGridConfiguration.textDirection == TextDirection.rtl && dataGridConfiguration.horizontalScrollController!.hasClients && dataGridConfiguration - .horizontalScrollController!.position.maxScrollExtent > + .horizontalScrollController! + .position + .maxScrollExtent > dataGridConfiguration.container.horizontalOffset) { spreadRadiusValue = 3.0; blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; - frozenLineColor = frozenLineColorWithoutOpacity; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; + frozenLineColor = frozenLineColorWithOpacity; } drawElevation( - frozenLineColor: frozenLineColor, - spreadRadiusValue: spreadRadiusValue, - blurRadiusValue: blurRadiusValue, - top: top, - start: left, - bottom: remainingViewPortHeight, - axis: Axis.horizontal, - margin: dataGridConfiguration.textDirection == TextDirection.rtl - ? EdgeInsets.only(left: margin) - : EdgeInsets.only(right: margin)); + frozenLineColor: frozenLineColor, + spreadRadiusValue: spreadRadiusValue, + blurRadiusValue: blurRadiusValue, + top: top, + start: left, + bottom: remainingViewPortHeight, + axis: Axis.horizontal, + margin: + dataGridConfiguration.textDirection == TextDirection.rtl + ? EdgeInsets.only(left: margin) + : EdgeInsets.only(right: margin), + ); } if (columnHeaderRow != null && @@ -619,39 +738,47 @@ class _ScrollViewWidgetState extends State { !_canDisableHorizontalScrolling(dataGridConfiguration)) { double spreadRadiusValue = 3.0; double blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; - Color frozenLineColor = frozenLineColorWithoutOpacity; - final double top = - getTopPosition(columnHeaderRow, footerFrozenColumnIndex); + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; + Color frozenLineColor = frozenLineColorWithOpacity; + final double top = getTopPosition( + columnHeaderRow, + footerFrozenColumnIndex, + ); final double right = columnHeaderRow.getColumnWidth( - footerFrozenColumnIndex, dataGridConfiguration.container.columnCount); + footerFrozenColumnIndex, + dataGridConfiguration.container.columnCount, + ); if (dataGridConfiguration.textDirection == TextDirection.ltr && dataGridConfiguration.horizontalScrollController!.hasClients && dataGridConfiguration - .horizontalScrollController!.position.maxScrollExtent == + .horizontalScrollController! + .position + .maxScrollExtent == dataGridConfiguration.container.horizontalOffset) { spreadRadiusValue = 1.5; blurRadiusValue = 0.0; - frozenLineColor = frozenLineColorWithOpacity; + frozenLineColor = frozenLineColorWithoutOpacity; } if (dataGridConfiguration.textDirection == TextDirection.rtl && dataGridConfiguration.container.horizontalOffset == 0) { spreadRadiusValue = 1.5; blurRadiusValue = 0.0; - frozenLineColor = frozenLineColorWithOpacity; + frozenLineColor = frozenLineColorWithoutOpacity; } drawElevation( - frozenLineColor: frozenLineColor, - spreadRadiusValue: spreadRadiusValue, - blurRadiusValue: blurRadiusValue, - top: top, - bottom: remainingViewPortHeight, - end: right + remainingViewPortWidth, - axis: Axis.horizontal, - margin: dataGridConfiguration.textDirection == TextDirection.rtl - ? EdgeInsets.only(right: margin) - : EdgeInsets.only(left: margin)); + frozenLineColor: frozenLineColor, + spreadRadiusValue: spreadRadiusValue, + blurRadiusValue: blurRadiusValue, + top: top, + bottom: remainingViewPortHeight, + end: right + remainingViewPortWidth, + axis: Axis.horizontal, + margin: + dataGridConfiguration.textDirection == TextDirection.rtl + ? EdgeInsets.only(right: margin) + : EdgeInsets.only(left: margin), + ); } if (columnHeaderRow != null && @@ -659,25 +786,26 @@ class _ScrollViewWidgetState extends State { !_canDisableVerticalScrolling(dataGridConfiguration)) { double spreadRadiusValue = 1.5; double blurRadiusValue = 0.0; - Color frozenLineColor = frozenLineColorWithOpacity; + Color frozenLineColor = frozenLineColorWithoutOpacity; final double top = columnHeaderRow.getRowHeight(0, frozenRowIndex); if (dataGridConfiguration.container.verticalOffset > 0) { spreadRadiusValue = 3.0; blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; - frozenLineColor = frozenLineColorWithoutOpacity; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; + frozenLineColor = frozenLineColorWithOpacity; } drawElevation( - frozenLineColor: frozenLineColor, - spreadRadiusValue: spreadRadiusValue, - blurRadiusValue: blurRadiusValue, - top: top, - start: 0.0, - end: remainingViewPortWidth, - axis: Axis.vertical, - margin: EdgeInsets.only(bottom: margin)); + frozenLineColor: frozenLineColor, + spreadRadiusValue: spreadRadiusValue, + blurRadiusValue: blurRadiusValue, + top: top, + start: 0.0, + end: remainingViewPortWidth, + axis: Axis.vertical, + margin: EdgeInsets.only(bottom: margin), + ); } if (columnHeaderRow != null && @@ -685,36 +813,44 @@ class _ScrollViewWidgetState extends State { !_canDisableVerticalScrolling(dataGridConfiguration)) { double spreadRadiusValue = 3.0; double blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; - Color frozenLineColor = frozenLineColorWithoutOpacity; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; + Color frozenLineColor = frozenLineColorWithOpacity; final double bottom = columnHeaderRow.getRowHeight( - footerFrozenRowIndex, dataGridConfiguration.container.rowCount); + footerFrozenRowIndex, + dataGridConfiguration.container.rowCount, + ); if (dataGridConfiguration.verticalScrollController!.hasClients && dataGridConfiguration - .verticalScrollController!.position.maxScrollExtent == - dataGridConfiguration.container.verticalOffset) { + .verticalScrollController! + .position + .maxScrollExtent + .ceilToDouble() == + dataGridConfiguration.container.verticalOffset.ceilToDouble()) { spreadRadiusValue = 1.5; blurRadiusValue = 0.0; - frozenLineColor = frozenLineColorWithOpacity; + frozenLineColor = frozenLineColorWithoutOpacity; } drawElevation( - frozenLineColor: frozenLineColor, - spreadRadiusValue: spreadRadiusValue, - blurRadiusValue: blurRadiusValue, - start: 0.0, - end: remainingViewPortWidth, - axis: Axis.vertical, - bottom: bottom + remainingViewPortHeight, - margin: EdgeInsets.only(top: margin)); + frozenLineColor: frozenLineColor, + spreadRadiusValue: spreadRadiusValue, + blurRadiusValue: blurRadiusValue, + start: 0.0, + end: remainingViewPortWidth, + axis: Axis.vertical, + bottom: bottom + remainingViewPortHeight, + margin: EdgeInsets.only(top: margin), + ); } } void _handleSelectionController() { - setState(() { - /* Rebuild the DataGrid when the selection or currentcell is processed. */ - }); + if (mounted) { + setState(() { + /* Rebuild the DataGrid when the selection or currentcell is processed. */ + }); + } } // ------------------------------------------------------------------------- @@ -840,26 +976,81 @@ class _ScrollViewWidgetState extends State { // -------------------------------------------------------------------------- - KeyEventResult _handleKeyOperation( - FocusNode focusNode, RawKeyEvent keyEvent) { + KeyEventResult _handleKeyOperation(FocusNode focusNode, KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + // To handle group expansion and collapse the group. + void expandCollapseGroup() { + final DataRowBase? row = dataGridConfiguration + .container + .rowGenerator + .items + .firstWhereOrNull( + (DataRowBase element) => element.rowIndex == currentCell.rowIndex, + ); + if (row != null && + row.rowType == RowType.captionSummaryCoveredRow && + dataGridConfiguration.allowExpandCollapseGroup) { + final int rowIndex = resolveStartRecordIndex( + dataGridConfiguration, + currentCell.rowIndex, + ); + final dynamic group = getGroupElement(dataGridConfiguration, rowIndex); + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowRight) { + if (group is Group && !group.isExpanded) { + dataGridConfiguration.group!.expandGroups( + group, + dataGridConfiguration.group, + rowIndex, + ); + dataGridConfiguration.groupExpandCollapseRowIndex = + currentCell.rowIndex; + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + } else if (keyEvent.logicalKey == LogicalKeyboardKey.arrowLeft) { + if (group is Group && group.isExpanded) { + dataGridConfiguration.group!.collapseGroups( + group, + dataGridConfiguration.group, + rowIndex, + ); + dataGridConfiguration.groupExpandCollapseRowIndex = + currentCell.rowIndex; + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'grouping', + ); + } + } + } + } void processKeys() { - if (keyEvent.runtimeType == RawKeyDownEvent) { + if (keyEvent.runtimeType == KeyDownEvent || + keyEvent.runtimeType == KeyRepeatEvent) { _rowSelectionManager.handleKeyEvent(keyEvent); - if (keyEvent.isControlPressed) { + if (HardwareKeyboard.instance.isControlPressed) { dataGridConfiguration.isControlKeyPressed = true; } - if (keyEvent.isShiftPressed) { + if (HardwareKeyboard.instance.isMetaPressed) { + dataGridConfiguration.isCommandKeyPressed = true; + } + if (HardwareKeyboard.instance.isShiftPressed) { dataGridConfiguration.isShiftKeyPressed = true; } } - if (keyEvent.runtimeType == RawKeyUpEvent) { + if (keyEvent.runtimeType == KeyUpEvent) { if (keyEvent.logicalKey == LogicalKeyboardKey.controlLeft || keyEvent.logicalKey == LogicalKeyboardKey.controlRight) { dataGridConfiguration.isControlKeyPressed = false; } + if (keyEvent.logicalKey == LogicalKeyboardKey.metaLeft || + keyEvent.logicalKey == LogicalKeyboardKey.metaRight) { + dataGridConfiguration.isCommandKeyPressed = false; + } if (keyEvent.logicalKey == LogicalKeyboardKey.shiftLeft || keyEvent.logicalKey == LogicalKeyboardKey.shiftRight) { dataGridConfiguration.isShiftKeyPressed = false; @@ -878,11 +1069,13 @@ class _ScrollViewWidgetState extends State { currentCell.rowIndex == rowIndex) || (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); - if (keyEvent.isShiftPressed) { - final int firstRowIndex = - selection_helper.getFirstRowIndex(dataGridConfiguration); - final int firstCellIndex = - selection_helper.getFirstCellIndex(dataGridConfiguration); + if (HardwareKeyboard.instance.isShiftPressed) { + final int firstRowIndex = selection_helper.getFirstRowIndex( + dataGridConfiguration, + ); + final int firstCellIndex = selection_helper.getFirstCellIndex( + dataGridConfiguration, + ); if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { return KeyEventResult.ignored; @@ -890,10 +1083,12 @@ class _ScrollViewWidgetState extends State { return KeyEventResult.handled; } } else { - final int lastRowIndex = - selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); - final int lastCellIndex = - selection_helper.getLastCellIndex(dataGridConfiguration); + final int lastRowIndex = selection_helper.getLastNavigatingRowIndex( + dataGridConfiguration, + ); + final int lastCellIndex = selection_helper.getLastCellIndex( + dataGridConfiguration, + ); if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { return KeyEventResult.ignored; @@ -907,9 +1102,20 @@ class _ScrollViewWidgetState extends State { if (keyEvent.logicalKey == LogicalKeyboardKey.tab && needToMoveFocus() != KeyEventResult.handled) { return KeyEventResult.ignored; + } else if (keyEvent.logicalKey == LogicalKeyboardKey.goBack) { + return KeyEventResult.ignored; } processKeys(); + if (dataGridConfiguration.source.groupedColumns.isNotEmpty && + (keyEvent.logicalKey == LogicalKeyboardKey.arrowRight || + keyEvent.logicalKey == LogicalKeyboardKey.arrowLeft)) { + if (dataGridConfiguration.isMacPlatform + ? !HardwareKeyboard.instance.isMetaPressed + : !HardwareKeyboard.instance.isControlPressed) { + expandCollapseGroup(); + } + } return KeyEventResult.handled; } else { // On Editing, we have to handle below [LogicalKeyboardKey]'s. For, that @@ -944,14 +1150,18 @@ class _ScrollViewWidgetState extends State { /// Need not to change the height when onScreenKeyboard appears. /// Cause: If we change the height on editing, editable widget will not move /// above the onScreenKeyboard on mobile platforms. - final bool needToResizeHeight = !dataGridConfiguration.isDesktop && + final bool needToAvoidResizeHeight = + !dataGridConfiguration.isDesktop && dataGridConfiguration.currentCell.isEditing; - _width = widget.width; - _height = !needToResizeHeight ? widget.height : _height; - _container - ..needToSetHorizontalOffset = true - ..isDirty = true; + if (!needToAvoidResizeHeight) { + _width = widget.width; + _height = widget.height; + + _container + ..needToSetHorizontalOffset = true + ..isDirty = true; + } if (oldWidget.width != widget.width || oldWidget.height != widget.height) { _container.resetSwipeOffset(); @@ -976,7 +1186,7 @@ class _ScrollViewWidgetState extends State { _horizontalController!.removeListener(_horizontalListener); _horizontalController = dataGridConfiguration.horizontalScrollController ?? - ScrollController(); + ScrollController(); _horizontalController!.addListener(_horizontalListener); } } @@ -990,24 +1200,46 @@ class _ScrollViewWidgetState extends State { _updateColumnSizer(); } - _height = _dataGridConfiguration.viewHeight = - _dataGridConfiguration.shrinkWrapRows - ? (_container.scrollRows as PixelScrollAxis).totalExtent - : widget.height; - _width = _dataGridConfiguration.viewWidth = - _dataGridConfiguration.shrinkWrapColumns - ? (_container.scrollColumns as PixelScrollAxis).totalExtent - : widget.width; + _height = + _dataGridConfiguration.viewHeight = + _dataGridConfiguration.shrinkWrapRows + ? (_container.scrollRows as PixelScrollAxis).totalExtent + : widget.height; + _width = + _dataGridConfiguration.viewWidth = + _dataGridConfiguration.shrinkWrapColumns + ? (_container.scrollColumns as PixelScrollAxis).totalExtent + : widget.width; _updateAxis(); + if (_verticalController!.initialScrollOffset > 0 && + !_container.isPreGenerator) { + _container.verticalOffset = _verticalController!.initialScrollOffset; + } + //if shrink wrap width is not set we update the column size after updated the axis because we get the constraint max width if (!_dataGridConfiguration.shrinkWrapColumns) { _updateColumnSizer(); } + + if (_horizontalController!.initialScrollOffset > 0 && + !_container.isPreGenerator) { + _container.horizontalOffset = + _horizontalController!.initialScrollOffset; + } _container - ..setRowHeights() + ..setRowHeights(initialLoading: true) ..needToRefreshColumn = true; + + // FLUT-6545 if shrinkWrapRows is true, we need to the set the DataGrid maximum height + // based on the row value set in the onQueryRowHeight callback + if (_dataGridConfiguration.shrinkWrapRows) { + _height = + _dataGridConfiguration.viewHeight = + (_container.scrollRows as PixelScrollAxis).totalExtent; + _updateAxis(); + } } if (_container.needToSetHorizontalOffset) { @@ -1033,6 +1265,19 @@ class _ScrollViewWidgetState extends State { _container.isDirty = false; _isScrolling = false; + Widget addContainer() { + return Container( + height: _height, + width: _width, + decoration: const BoxDecoration(color: Colors.transparent), + clipBehavior: Clip.antiAlias, + child: Stack( + fit: StackFit.passthrough, + children: List.from(children), + ), + ); + } + // return RawKeyboardListener( // focusNode: _dataGridFocusNode!, // onKey: _handleKeyOperation, @@ -1053,43 +1298,41 @@ class _ScrollViewWidgetState extends State { // below mentioned issue is resolved on framework end. // [https://github.com/flutter/flutter/issues/83023] return Focus( + key: _dataGridConfiguration.dataGridKey, focusNode: _dataGridFocusNode, - onKey: _handleKeyOperation, - child: Container( - height: _height, - width: _width, - decoration: const BoxDecoration( - color: Colors.transparent, - ), - clipBehavior: Clip.antiAlias, - child: Stack( - fit: StackFit.passthrough, - children: List.from(children)), - ), + onKeyEvent: _handleKeyOperation, + child: addContainer(), ); } @override void dispose() { - if (_verticalController != null) { - _verticalController! - ..removeListener(_verticalListener) - ..dispose(); - } + // Need to dispose the vertical and horizontal scroll listeners when the widget disposed. + _verticalController?.removeListener(_verticalListener); + _horizontalController?.removeListener(_horizontalListener); + _dataGridConfiguration.rowSelectionManager.removeListener( + _handleSelectionController, + ); - if (_horizontalController != null) { - _horizontalController! - ..removeListener(_horizontalListener) - ..dispose(); + // Need to dispose the horizontal scroll controller when it's not set from the sample level. + if (_dataGridConfiguration.disposeVerticalScrollController) { + _verticalController?.dispose(); } + // Need to dispose the horizontal scroll controller when it's not set from the sample level. + if (_dataGridConfiguration.disposeHorizontalScrollController) { + _horizontalController?.dispose(); + } super.dispose(); } } /// Return the resizing indicator for mobile platform -Widget _getResizingCursor(DataGridConfiguration dataGridConfiguration, - DataGridThemeHelper themeData, double y) { +Widget _getResizingCursor( + DataGridConfiguration dataGridConfiguration, + DataGridThemeHelper themeData, + double y, +) { const double cursorContainerHeight = 16.0; const double cursorContainerWidth = 16.0; @@ -1098,14 +1341,16 @@ Widget _getResizingCursor(DataGridConfiguration dataGridConfiguration, final int rowSpan = columnResizeController.rowSpan; final int rowIndex = columnResizeController.rowIndex; - final Color indicatorColor = themeData.columnResizeIndicatorColor; - final double strokeWidth = themeData.columnResizeIndicatorStrokeWidth; + final Color indicatorColor = themeData.columnResizeIndicatorColor!; + final double strokeWidth = themeData.columnResizeIndicatorStrokeWidth!; double rowHeight = dataGridConfiguration.container.rowHeights[rowIndex]; // Consider the spanned row height to show the indicator at center if (rowSpan > 0) { - rowHeight = columnResizeController.resizingDataCell!.dataRow! - .getRowHeight(rowIndex - rowSpan, rowIndex); + rowHeight = columnResizeController.resizingDataCell!.dataRow!.getRowHeight( + rowIndex - rowSpan, + rowIndex, + ); } return Stack( @@ -1125,11 +1370,15 @@ Widget _getResizingCursor(DataGridConfiguration dataGridConfiguration, height: cursorContainerHeight, width: cursorContainerWidth, padding: const EdgeInsets.all(.2), - decoration: - BoxDecoration(color: indicatorColor, shape: BoxShape.circle), + decoration: BoxDecoration( + color: indicatorColor, + shape: BoxShape.circle, + ), child: CustomPaint( painter: _CustomResizingCursorPainter( - Colors.white, dataGridConfiguration.gridPaint!), + Colors.white, + dataGridConfiguration.gridPaint!, + ), ), ), ), @@ -1165,22 +1414,32 @@ class _CustomResizingCursorPainter extends CustomPainter { /// Drawing left arrow path.moveTo(leftAndRightPadding, verticalCenterPoint); path.lineTo( - horizontalCenterPoint - leftAndRightPadding, leftAndRightPadding); + horizontalCenterPoint - leftAndRightPadding, + leftAndRightPadding, + ); path.moveTo( - horizontalCenterPoint - leftAndRightPadding, topAndBottomPadding); - path.lineTo(horizontalCenterPoint - leftAndRightPadding, - size.height - topAndBottomPadding); + horizontalCenterPoint - leftAndRightPadding, + topAndBottomPadding, + ); + path.lineTo( + horizontalCenterPoint - leftAndRightPadding, + size.height - topAndBottomPadding, + ); path.lineTo(leftAndRightPadding, verticalCenterPoint); path.close(); canvas.drawPath(path, _arrowPaint); /// Drawing right arrow path.moveTo( - horizontalCenterPoint + topAndBottomPadding, leftAndRightPadding); + horizontalCenterPoint + topAndBottomPadding, + leftAndRightPadding, + ); path.lineTo(size.width - leftAndRightPadding, verticalCenterPoint); path.moveTo(size.width - leftAndRightPadding, verticalCenterPoint); - path.lineTo(horizontalCenterPoint + leftAndRightPadding, - size.height - topAndBottomPadding); + path.lineTo( + horizontalCenterPoint + leftAndRightPadding, + size.height - topAndBottomPadding, + ); path.lineTo(verticalCenterPoint + leftAndRightPadding, topAndBottomPadding); path.close(); canvas.drawPath(path, _arrowPaint); @@ -1201,7 +1460,8 @@ bool _canDisableVerticalScrolling(DataGridConfiguration dataGridConfiguration) { } bool _canDisableHorizontalScrolling( - DataGridConfiguration dataGridConfiguration) { + DataGridConfiguration dataGridConfiguration, +) { final VisualContainerHelper container = dataGridConfiguration.container; return (container.scrollColumns.headerExtent + container.scrollColumns.footerExtent) > @@ -1209,13 +1469,13 @@ bool _canDisableHorizontalScrolling( } class _VisualContainer extends StatefulWidget { - const _VisualContainer( - {required Key key, - required this.rowGenerator, - required this.containerSize, - required this.isDirty, - required this.dataGridStateDetails}) - : super(key: key); + const _VisualContainer({ + required Key key, + required this.rowGenerator, + required this.containerSize, + required this.isDirty, + required this.dataGridStateDetails, + }) : super(key: key); final Size containerSize; final RowGenerator rowGenerator; @@ -1233,32 +1493,51 @@ class _VisualContainerState extends State<_VisualContainer> { if (dataGridConfiguration.allowSwiping && dataGridConfiguration.swipingOffset.abs() > 0.0) { final DataRowBase? swipeRow = widget.rowGenerator.items - .where((DataRowBase row) => - (row.rowRegion == RowRegion.body || - row.rowType == RowType.dataRow) && - row.rowIndex >= 0) + .where( + (DataRowBase row) => + (row.rowRegion == RowRegion.body || + row.rowType == RowType.dataRow) && + row.rowIndex >= 0, + ) .firstWhereOrNull((DataRowBase row) => row.isSwipingRow); if (swipeRow != null) { - final DataGridRowSwipeDirection swipeDirection = - grid_helper.getSwipeDirection( - dataGridConfiguration, dataGridConfiguration.swipingOffset); - final int rowIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, swipeRow.rowIndex); + final DataGridRowSwipeDirection swipeDirection = grid_helper + .getSwipeDirection( + dataGridConfiguration, + dataGridConfiguration.swipingOffset, + ); + int rowIndex = 0; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + rowIndex = dataGridConfiguration.source.effectiveRows.indexOf( + swipeRow.dataGridRow!, + ); + } else { + rowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, + swipeRow.rowIndex, + ); + } switch (swipeDirection) { case DataGridRowSwipeDirection.startToEnd: if (dataGridConfiguration.startSwipeActionsBuilder != null) { - final Widget? startSwipeWidget = - dataGridConfiguration.startSwipeActionsBuilder!( - context, swipeRow.dataGridRow!, rowIndex); + final Widget? startSwipeWidget = dataGridConfiguration + .startSwipeActionsBuilder!( + context, + swipeRow.dataGridRow!, + rowIndex, + ); children.add(startSwipeWidget ?? Container()); } break; case DataGridRowSwipeDirection.endToStart: if (dataGridConfiguration.endSwipeActionsBuilder != null) { - final Widget? endSwipeWidget = - dataGridConfiguration.endSwipeActionsBuilder!( - context, swipeRow.dataGridRow!, rowIndex); + final Widget? endSwipeWidget = dataGridConfiguration + .endSwipeActionsBuilder!( + context, + swipeRow.dataGridRow!, + rowIndex, + ); children.add(endSwipeWidget ?? Container()); } break; @@ -1269,43 +1548,58 @@ class _VisualContainerState extends State<_VisualContainer> { void _addRows(List children, RowRegion rowRegion) { if (rowRegion == RowRegion.header) { - children.addAll(widget.rowGenerator.items - .where((DataRowBase row) => - row.rowIndex >= 0 && - row.rowRegion == rowRegion && - (row.rowType == RowType.tableSummaryRow || - row.rowType == RowType.tableSummaryCoveredRow || - row.rowType == RowType.dataRow)) - .map((DataRowBase dataRow) => addWidget(dataRow)) - .toList()); + children.addAll( + widget.rowGenerator.items + .where( + (DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == rowRegion && + (row.rowType == RowType.tableSummaryRow || + row.rowType == RowType.tableSummaryCoveredRow || + row.rowType == RowType.dataRow || + row.rowType == RowType.captionSummaryCoveredRow), + ) + .map((DataRowBase dataRow) => addWidget(dataRow)) + .toList(), + ); } else if (rowRegion == RowRegion.body) { // Need to restrict the layout of the currently unused rows that keep the // row index to -1 in the `rowGenerator.items` collection. - children.addAll(widget.rowGenerator.items - .where((DataRowBase row) => - row.rowIndex >= 0 && - row.rowRegion == rowRegion && - (row.rowType == RowType.dataRow || - row.rowType == RowType.footerRow)) - .map((DataRowBase dataRow) => addWidget(dataRow)) - .toList()); + children.addAll( + widget.rowGenerator.items + .where( + (DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == rowRegion && + (row.rowType == RowType.dataRow || + row.rowType == RowType.footerRow || + row.rowType == RowType.captionSummaryCoveredRow), + ) + .map((DataRowBase dataRow) => addWidget(dataRow)) + .toList(), + ); } else if (rowRegion == RowRegion.footer) { - children.addAll(widget.rowGenerator.items - .where((DataRowBase row) => - row.rowIndex >= 0 && - row.rowRegion == rowRegion && - (row.rowType == RowType.tableSummaryRow || - row.rowType == RowType.tableSummaryCoveredRow || - row.rowType == RowType.footerRow || - row.rowType == RowType.dataRow)) - .map((DataRowBase dataRow) => addWidget(dataRow)) - .toList()); + children.addAll( + widget.rowGenerator.items + .where( + (DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == rowRegion && + (row.rowType == RowType.tableSummaryRow || + row.rowType == RowType.tableSummaryCoveredRow || + row.rowType == RowType.footerRow || + row.rowType == RowType.dataRow || + row.rowType == RowType.captionSummaryCoveredRow), + ) + .map((DataRowBase dataRow) => addWidget(dataRow)) + .toList(), + ); } } Widget addWidget(DataRowBase dataRow) { return _VirtualizingCellsWidget( - key: dataRow.key!, + key: dataRow.key, dataRow: dataRow, isDirty: widget.isDirty || dataRow.isDirty, dataGridStateDetails: widget.dataGridStateDetails, @@ -1328,19 +1622,19 @@ class _VisualContainerState extends State<_VisualContainer> { key: widget.key, containerSize: widget.containerSize, isDirty: widget.isDirty, - children: children, dataGridStateDetails: widget.dataGridStateDetails, + children: children, ); } } class _VirtualizingCellsWidget extends StatefulWidget { - const _VirtualizingCellsWidget( - {required Key key, - required this.dataRow, - required this.isDirty, - required this.dataGridStateDetails}) - : super(key: key); + const _VirtualizingCellsWidget({ + required Key? key, + required this.dataRow, + required this.isDirty, + required this.dataGridStateDetails, + }) : super(key: key); final DataRowBase dataRow; final bool isDirty; @@ -1369,34 +1663,36 @@ class _VirtualizingCellsWidgetState extends State<_VirtualizingCellsWidget> { } return VirtualizingCellsRenderObjectWidget( - key: widget.key!, dataRow: widget.dataRow, isDirty: widget.isDirty, - children: List.from(children), dataGridStateDetails: widget.dataGridStateDetails, + children: List.from(children), ); } } class _HeaderCellsWidget extends _VirtualizingCellsWidget { - const _HeaderCellsWidget( - {required Key key, - required DataRowBase dataRow, - bool isDirty = false, - required DataGridStateDetails dataGridStateDetails}) - : super( - key: key, - dataRow: dataRow, - isDirty: isDirty, - dataGridStateDetails: dataGridStateDetails); + const _HeaderCellsWidget({ + required Key key, + required DataRowBase dataRow, + bool isDirty = false, + required DataGridStateDetails dataGridStateDetails, + }) : super( + key: key, + dataRow: dataRow, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails, + ); } /// A class [VisualContainerHelper] is used to provide all the information of the /// grid common to build a [SfDataGrid]. class VisualContainerHelper { /// Creates a [VisualContainerHelper] for the [SfDataGrid]. - VisualContainerHelper( - {required this.rowGenerator, required this.dataGridStateDetails}) { + VisualContainerHelper({ + required this.rowGenerator, + required this.dataGridStateDetails, + }) { isDirty = false; isGridLoaded = false; needToSetHorizontalOffset = false; @@ -1449,8 +1745,11 @@ class VisualContainerHelper { /// A class [ScrollAxisBase] that can be used to control the scrolling of rows /// in the [SfDataGrid]. ScrollAxisBase get scrollRows { - _scrollRows ??= - _createScrollAxis(true, verticalScrollBar, rowHeightsProvider); + _scrollRows ??= _createScrollAxis( + true, + verticalScrollBar, + rowHeightsProvider, + ); _scrollRows!.name = 'ScrollRows'; return _scrollRows!; @@ -1463,8 +1762,11 @@ class VisualContainerHelper { /// A class [ScrollAxisBase] that can be used to control the scrolling of columns /// in the [SfDataGrid]. ScrollAxisBase get scrollColumns { - _scrollColumns ??= - _createScrollAxis(true, horizontalScrollBar, columnWidthsProvider); + _scrollColumns ??= _createScrollAxis( + true, + horizontalScrollBar, + columnWidthsProvider, + ); _scrollColumns!.name = 'ScrollColumns'; return _scrollColumns!; } @@ -1562,12 +1864,17 @@ class VisualContainerHelper { if (dataGridConfiguration.textDirection == TextDirection.ltr) { horizontalScrollBar.value = newValue + horizontalScrollBar.minimum; } else { - horizontalScrollBar.value = max(horizontalScrollBar.minimum, - horizontalScrollBar.maximum - horizontalScrollBar.largeChange) - + horizontalScrollBar.value = + max( + horizontalScrollBar.minimum, + horizontalScrollBar.maximum - horizontalScrollBar.largeChange, + ) - newValue; } updateHorizontalOffset( - dataGridConfiguration.controller, horizontalScrollBar.value); + dataGridConfiguration.controller, + horizontalScrollBar.value, + ); needToRefreshColumn = true; } @@ -1582,24 +1889,26 @@ class VisualContainerHelper { dataGridStateDetails(); verticalScrollBar.value = newValue + verticalScrollBar.minimum; updateVerticalOffset( - dataGridConfiguration.controller, verticalScrollBar.value); + dataGridConfiguration.controller, + verticalScrollBar.value, + ); } } /// Returns total width of all the columns in the data grid. double get extentWidth { - final PixelScrollAxis _scrollColumns = scrollColumns as PixelScrollAxis; - return _scrollColumns.totalExtent; + final PixelScrollAxis pixelScrollColumns = scrollColumns as PixelScrollAxis; + return pixelScrollColumns.totalExtent; } /// Returns total height of all the rows in the data grid. double get extentHeight { - final PixelScrollAxis _scrollRows = scrollRows as PixelScrollAxis; - return _scrollRows.totalExtent; + final PixelScrollAxis pixelScrollRows = scrollRows as PixelScrollAxis; + return pixelScrollRows.totalExtent; } /// Sets the row height of all the data grid rows. - void setRowHeights() { + void setRowHeights({bool initialLoading = false}) { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); if (dataGridConfiguration.onQueryRowHeight == null) { return; @@ -1647,11 +1956,22 @@ class VisualContainerHelper { final LineSizeCollection lineSizeCollection = rowHeights as LineSizeCollection; - lineSizeCollection.suspendUpdates(); - for (int index = bodyStartLineIndex; - (current <= bodyEnd || (current <= dataGridConfiguration.viewHeight)) && - index < scrollRows.firstFooterLineIndex; - index++) { + + // We only need to suspend updates when shrinkWrap is set to true + // to recompute the size of all rows at initial loading, + // instead of suspending updates every time while scrolling. + if (dataGridConfiguration.shrinkWrapRows && + dataGridConfiguration.onQueryRowHeight != null && + initialLoading) { + lineSizeCollection.suspendUpdates(); + } + for ( + int index = bodyStartLineIndex; + ((current <= bodyEnd || (current <= dataGridConfiguration.viewHeight)) || + dataGridConfiguration.shrinkWrapRows) && + index < scrollRows.firstFooterLineIndex; + index++ + ) { double height = rowHeights[index]; if (!rowHeightManager.contains(index, RowRegion.body) && @@ -1668,7 +1988,10 @@ class VisualContainerHelper { } rowHeightManager.updateRegion( - bodyStartLineIndex, currentEnd, RowRegion.body); + bodyStartLineIndex, + currentEnd, + RowRegion.body, + ); if (rowHeightManager.dirtyRows.isNotEmpty) { for (final int index in rowHeightManager.dirtyRows) { @@ -1686,7 +2009,15 @@ class VisualContainerHelper { rowHeightManager.dirtyRows.clear(); } - lineSizeCollection.resumeUpdates(); + // We need to resume updates here, as we previously suspended them + // when the shrinkWrapRows property was enabled during the initial loading + // to determine the row height for all rows + if (dataGridConfiguration.shrinkWrapRows && + dataGridConfiguration.onQueryRowHeight != null && + initialLoading) { + initialLoading = false; + lineSizeCollection.resumeUpdates(); + } scrollRows.updateScrollBar(false); } @@ -1715,8 +2046,9 @@ class VisualContainerHelper { void _preGenerateItems() { final VisibleLinesCollection visibleRows = scrollRows.getVisibleLines(); - final VisibleLinesCollection visibleColumns = - grid_helper.getVisibleLines(dataGridStateDetails()); + final VisibleLinesCollection visibleColumns = grid_helper.getVisibleLines( + dataGridStateDetails(), + ); if (visibleRows.isNotEmpty && visibleColumns.isNotEmpty) { rowGenerator.preGenerateRows(visibleRows, visibleColumns); @@ -1767,12 +2099,18 @@ class VisualContainerHelper { } ScrollAxisBase _createScrollAxis( - bool isPixelScroll, ScrollBarBase scrollBar, LineSizeHostBase lineSizes) { + bool isPixelScroll, + ScrollBarBase scrollBar, + LineSizeHostBase lineSizes, + ) { if (isPixelScroll) { - final Object _lineSizes = lineSizes; + final Object lineSize = lineSizes; if (lineSizes is DistancesHostBase) { return PixelScrollAxis.fromPixelScrollAxis( - scrollBar, lineSizes, _lineSizes as DistancesHostBase); + scrollBar, + lineSizes, + lineSize as DistancesHostBase, + ); } else { return PixelScrollAxis.fromPixelScrollAxis(scrollBar, lineSizes, null); } @@ -1836,7 +2174,9 @@ class VisualContainerHelper { } if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { headerLineCount += grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.top); + dataGridConfiguration, + GridTableSummaryRowPosition.top, + ); } dataGridConfiguration.headerLineCount = headerLineCount; } @@ -1850,7 +2190,9 @@ class VisualContainerHelper { if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { // Add bottom summary rows count footerLineCount += grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); } return footerLineCount; } @@ -1863,25 +2205,61 @@ class VisualContainerHelper { final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); _updateColumnCount(dataGridConfiguration); _updateRowCount(dataGridConfiguration); - if (rowCount > 0) { - for (int i = 0; - i <= grid_helper.getHeaderIndex(dataGridConfiguration); - i++) { - rowHeights[i] = dataGridConfiguration.headerRowHeight; - } - } - + _refreshVerticalScrollbar(); //need to update the indent column width here lineSizeCollection.resumeUpdates(); updateScrollBars(); _updateFreezePaneColumns(dataGridConfiguration); - + updateIndentColumnWidths(dataGridConfiguration); rowHeights.lineCount = rowCount; columnWidths.lineCount = columnCount; } + void _refreshVerticalScrollbar() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.verticalScrollController != null && + dataGridConfiguration.verticalScrollController!.hasClients) { + final double verticalControllerOffset = + dataGridConfiguration.verticalScrollController!.offset + + dataGridConfiguration.container.scrollRows.headerExtent; + final double verticalContainerOffset = + dataGridConfiguration.container.verticalScrollBar.value; + if (verticalControllerOffset != verticalContainerOffset) { + dataGridConfiguration.container.verticalScrollBar.value = + verticalControllerOffset; + } + } + } + + void _updateRowHeights(DataGridConfiguration dataGridConfiguration) { + // Sets datagrid row height + rowHeights.setRange(0, rowCount - 1, dataGridConfiguration.rowHeight); + + // Sets header row height + if (rowCount > 0) { + for ( + int i = 0; + i <= grid_helper.getHeaderIndex(dataGridConfiguration); + i++ + ) { + rowHeights[i] = dataGridConfiguration.headerRowHeight; + } + } + + // Sets footer row height + if (dataGridConfiguration.footer != null) { + rowHeights[grid_helper.getFooterViewRowIndex(dataGridConfiguration)] = + dataGridConfiguration.footerHeight; + } + } + void _updateColumnCount(DataGridConfiguration dataGridConfiguration) { - final int columnCount = dataGridConfiguration.columns.length; + int columnCount = dataGridConfiguration.columns.length; + + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + columnCount += dataGridConfiguration.source.groupedColumns.length; + } + this.columnCount = columnCount; } @@ -1889,21 +2267,25 @@ class VisualContainerHelper { final LineSizeCollection lineSizeCollection = rowHeights as LineSizeCollection; lineSizeCollection.suspendUpdates(); - int _rowCount = 0; - _rowCount = effectiveRows(dataGridConfiguration.source).isNotEmpty - ? effectiveRows(dataGridConfiguration.source).length - : 0; - _rowCount += dataGridConfiguration.headerLineCount; + int rowsCount = 0; + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + rowsCount = + dataGridConfiguration.group?.displayElements?.grouped.length ?? 0; + } else { + rowsCount = + effectiveRows(dataGridConfiguration.source).isNotEmpty + ? effectiveRows(dataGridConfiguration.source).length + : 0; + } + rowsCount += dataGridConfiguration.headerLineCount; - _rowCount += _getFooterLineCount(dataGridConfiguration); + rowsCount += _getFooterLineCount(dataGridConfiguration); - rowCount = _rowCount; + rowCount = rowsCount; - // Sets footer row height - if (dataGridConfiguration.footer != null) { - rowHeights[grid_helper.getFooterViewRowIndex(dataGridConfiguration)] = - dataGridConfiguration.footerHeight; - } + // FLUT-958399 Reassign row height after footerIndex changes + // as the previous footerHeight remains in rowHeights[footerViewRowIndex] + _updateRowHeights(dataGridConfiguration); _updateFreezePaneRows(dataGridConfiguration); // FLUT-2047 Need to mark all visible rows height as dirty when @@ -1918,8 +2300,16 @@ class VisualContainerHelper { } void _updateFreezePaneColumns(DataGridConfiguration dataGridConfiguration) { - final int frozenColumnCount = grid_helper.resolveToScrollColumnIndex( - dataGridConfiguration, dataGridConfiguration.frozenColumnsCount); + // We need to consider the indent column along with the frozen columns count. + // So, we have added the 1 to the frozen columns count. + // We should consider the frozen columns count when the [SfDataGrid.frozenColumnsCount] is greater than 0. + final int frozenColumnCount = + dataGridConfiguration.frozenColumnsCount > 0 + ? grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, + dataGridConfiguration.frozenColumnsCount, + ) + : 0; if (frozenColumnCount > 0 && columnCount >= frozenColumnCount) { frozenColumns = frozenColumnCount; } else { @@ -1938,17 +2328,24 @@ class VisualContainerHelper { void _updateFreezePaneRows(DataGridConfiguration dataGridConfiguration) { final int frozenRowCount = grid_helper.resolveToRowIndex( - dataGridConfiguration, dataGridConfiguration.frozenRowsCount); - if (frozenRowCount > 0 && rowCount >= frozenRowCount) { - frozenRows = headerLineCount + dataGridConfiguration.frozenRowsCount; - } else { - frozenRows = headerLineCount; - } + dataGridConfiguration, + dataGridConfiguration.frozenRowsCount, + ); + + frozenRows = + (frozenRowCount > 0 && + rowCount >= frozenRowCount && + dataGridConfiguration.frozenRowsCount <= + resolveEffectiveRowCount(dataGridConfiguration)) + ? headerLineCount + dataGridConfiguration.frozenRowsCount + : headerLineCount; final int footerFrozenRowsCount = dataGridConfiguration.footerFrozenRowsCount; final int bottomTableSummariesCount = grid_helper.getTableSummaryCount( - dataGridConfiguration, GridTableSummaryRowPosition.bottom); + dataGridConfiguration, + GridTableSummaryRowPosition.bottom, + ); footerFrozenRows = 0; if (footerFrozenRowsCount > 0 && rowCount > frozenRows + footerFrozenRowsCount && @@ -1970,7 +2367,9 @@ class VisualContainerHelper { void resetRowIndex(DataRowBase dataRow) { if (dataRow.rowType == RowType.dataRow) { final int resolvedRowIndex = grid_helper.resolveToRecordIndex( - dataGridConfiguration, dataRow.rowIndex); + dataGridConfiguration, + dataRow.rowIndex, + ); if (resolvedRowIndex.isNegative) { return; } @@ -1978,7 +2377,9 @@ class VisualContainerHelper { dataRow.dataGridRow = effectiveRows(dataGridConfiguration.source)[resolvedRowIndex]; dataRow.dataGridRowAdapter = grid_helper.getDataGridRowAdapter( - dataGridConfiguration, dataRow.dataGridRow!); + dataGridConfiguration, + dataRow.dataGridRow!, + ); dataRow.rowIndexChanged(); } } @@ -1995,7 +2396,24 @@ class VisualContainerHelper { dataRow.rowIndex = -1; } - rowGenerator.items.forEach(resetRowIndex); + // Issue: + // FLUT-6349 - Checkbox animation is happening for checkbox column + // when setting rowCacheExtent count as available rows count. + // + // Fix: + // We have resolved the issue by removing old data rows and creating + // a new row for the corresponding index to restrict the checkbox state + // changing animation issue when reusing. + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.rowsCacheExtent != null && + dataGridConfiguration.rowsCacheExtent! > 0) { + rowGenerator.items.removeWhere( + (DataRowBase dataRow) => dataRow.rowRegion == RowRegion.body, + ); + rowGenerator.items.forEach(resetRowIndex); + } else { + rowGenerator.items.forEach(resetRowIndex); + } } /// Refreshes the data cell to update the current changes. @@ -2020,9 +2438,11 @@ class VisualContainerHelper { return; } - swipedRow = swipedRow ?? - dataGridConfiguration.rowGenerator.items - .firstWhereOrNull((DataRowBase row) => row.isSwipingRow); + swipedRow = + swipedRow ?? + dataGridConfiguration.rowGenerator.items.firstWhereOrNull( + (DataRowBase row) => row.isSwipingRow, + ); if (swipedRow != null) { swipedRow.isSwipingRow = false; @@ -2032,8 +2452,21 @@ class VisualContainerHelper { dataGridConfiguration.isSwipingApplied = false; if (canUpdate) { - notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, - propertyName: 'Swiping'); + notifyDataGridPropertyChangeListeners( + dataGridConfiguration.source, + propertyName: 'Swiping', + ); + } + } + + /// update the indent column width. + void updateIndentColumnWidths(DataGridConfiguration dataGridConfiguration) { + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + final int length = dataGridConfiguration.source.groupedColumns.length; + for (int i = 0; i < length; i++) { + dataGridConfiguration.container.columnWidths[i] = + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth; + } } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart index 5f88b63c2..4b6ece54a 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart @@ -1,3 +1,7 @@ +// ignore_for_file: avoid_setters_without_getters, avoid_redundant_argument_values + +import 'dart:math'; + import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -49,10 +53,10 @@ bool _suspendDataPagerUpdate = false; /// SfDataGrid( /// source: _ dataGridSource, /// columns: [ -/// GridNumericColumn(mappingName: 'id', headerText: 'ID'), -/// GridTextColumn(mappingName: 'name', headerText: 'Name'), -/// GridTextColumn(mappingName: 'designation', headerText: 'Designation'), -/// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), +/// GridColumn(columnName: 'id', label: Container(child: Text('ID'))), +/// GridColumn(columnName: 'name', label: Container(child: Text('Name'))), +/// GridColumn(columnName: 'designation', label: Container(child: Text('Designation'))), +/// GridColumn(columnName: 'salary', label: Container(child: Text('Salary'))), /// ], /// ), /// SfDataPager( @@ -172,26 +176,44 @@ class DataPagerController extends _DataPagerChangeNotifier { /// ```dart /// @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, -/// ), -/// ], -/// ), -/// ); +/// return Scaffold( +/// appBar: AppBar( +/// title: const Text('Syncfusion Flutter DataGrid'), +/// ), +/// body: Column( +/// children: [ +/// SfDataGrid(source: dataGridSource, columns: [ +/// GridColumn( +/// columnName: 'id', +/// label: Container( +/// alignment: Alignment.center, +/// child: Text('ID'))), +/// GridColumn( +/// columnName: 'name', +/// label: Container( +/// alignment: Alignment.center, +/// child: Text('Name'))), +/// GridColumn( +/// columnName: 'designation', +/// label: Container( +/// alignment: Alignment.center, +/// child: Text( +/// 'Designation', +/// ))), +/// GridColumn( +/// columnName: 'salary', +/// label: Container( +/// alignment: Alignment.center, +/// child: Text('Salary'))), +/// ]), +/// SfDataPager( +/// pageCount: paginatedDataTable.length / rowsPerPage, +/// visibleItemsCount: 5, +/// delegate: dataGridSource, +/// ), +/// ], +/// ), +/// ); /// } /// ``` /// @@ -238,37 +260,39 @@ class SfDataPager extends StatefulWidget { /// /// The [pageCount] and [delegate] argument must be defined and must not /// be null. - const SfDataPager( - {required this.pageCount, - required this.delegate, - Key? key, - this.direction = Axis.horizontal, - this.itemWidth = 50.0, - this.itemHeight = 50.0, - this.itemPadding = const EdgeInsets.all(5), - this.navigationItemHeight = 50.0, - this.navigationItemWidth = 50.0, - this.firstPageItemVisible = true, - this.lastPageItemVisible = true, - this.nextPageItemVisible = true, - this.previousPageItemVisible = true, - this.visibleItemsCount = 5, - this.initialPageIndex = 0, - this.pageItemBuilder, - this.onPageNavigationStart, - this.onPageNavigationEnd, - this.onRowsPerPageChanged, - this.availableRowsPerPage = const [10, 15, 20], - this.controller}) - : assert(pageCount > 0), - assert(itemHeight > 0 && itemWidth > 0), - assert(availableRowsPerPage.length != 0), - assert((firstPageItemVisible || - lastPageItemVisible || - nextPageItemVisible || - previousPageItemVisible) && - (navigationItemHeight > 0 && navigationItemWidth > 0)), - super(key: key); + const SfDataPager({ + required this.pageCount, + required this.delegate, + Key? key, + this.direction = Axis.horizontal, + this.itemWidth = 50.0, + this.itemHeight = 50.0, + this.itemPadding = const EdgeInsets.all(5), + this.navigationItemHeight = 50.0, + this.navigationItemWidth = 50.0, + this.firstPageItemVisible = true, + this.lastPageItemVisible = true, + this.nextPageItemVisible = true, + this.previousPageItemVisible = true, + this.visibleItemsCount = 5, + this.initialPageIndex = 0, + this.pageItemBuilder, + this.onPageNavigationStart, + this.onPageNavigationEnd, + this.onRowsPerPageChanged, + this.availableRowsPerPage = const [10, 15, 20], + this.controller, + }) : assert(pageCount > 0), + assert(itemHeight > 0 && itemWidth > 0), + assert(availableRowsPerPage.length != 0), + assert( + (firstPageItemVisible || + lastPageItemVisible || + nextPageItemVisible || + previousPageItemVisible) && + (navigationItemHeight > 0 && navigationItemWidth > 0), + ), + super(key: key); /// The number of pages required to display in [SfDataPager]. /// Calculate the number of pages by dividing the total number of items @@ -381,22 +405,45 @@ class SfDataPager extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - 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, - defaultValue: Axis.horizontal, showName: true)); - properties.add(ObjectFlagProperty('delegate', delegate, - showName: true, ifNull: 'null')); - properties.add(ObjectFlagProperty( - 'controller', controller, - showName: true, ifNull: 'null')); - properties.add(ObjectFlagProperty( - 'pageItemBuilder', pageItemBuilder, - showName: true, ifNull: 'null')); + IntProperty('visibleItemsCount', visibleItemsCount, defaultValue: 5), + ); + properties.add(DoubleProperty('pageCount', pageCount, ifNull: 'null')); + properties.add( + IntProperty('initialPageIndex', initialPageIndex, defaultValue: 0.0), + ); + properties.add( + DiagnosticsProperty( + 'direction', + direction, + defaultValue: Axis.horizontal, + ), + ); + properties.add( + ObjectFlagProperty( + 'delegate', + delegate, + showName: true, + ifNull: 'null', + ), + ); + properties.add( + ObjectFlagProperty( + 'controller', + controller, + showName: true, + ifNull: 'null', + ), + ); + // ignore: strict_raw_type + properties.add( + ObjectFlagProperty>( + 'pageItemBuilder', + pageItemBuilder, + showName: true, + ifNull: 'null', + ), + ); } } @@ -429,15 +476,26 @@ class SfDataPagerState extends State { TextDirection _textDirection = TextDirection.ltr; Orientation _deviceOrientation = Orientation.landscape; - DataPagerThemeHelper? _dataPagerThemeHelper; + late DataPagerThemeHelper? _dataPagerThemeHelper; bool _isDirty = false; bool _isOrientationChanged = false; bool _isPregenerateItems = false; bool _isDesktop = false; + // Checks whether the `SfDataPager` is loading initially or not. + // This flag is used to restrict calling the handlePageChange twice + // at the initial loading + bool _isInitialLoading = true; + + // Checks whether the `Rows Per Page` is changed or not. + bool _isRowsPerPageChanged = false; + int? _rowsPerPage; + // Holds the previous screen size. + Size? _previousScreenSize; + bool get _isRTL => _textDirection == TextDirection.rtl; int get _lastPageIndex => _pageCount - 1; @@ -479,38 +537,64 @@ class SfDataPagerState extends State { } } + void _adjustScrollPosition() { + if (_scrollController!.hasClients) { + final double maxScrollExtent = + _scrollController!.position.maxScrollExtent; + final double cumulativeSize = _getCumulativeSize(_currentPageIndex); + if ((min(cumulativeSize, maxScrollExtent) != 0.0) && + (_scrollController!.offset <= min(cumulativeSize, maxScrollExtent))) { + Future.delayed(Duration.zero, () { + final double distance = min( + cumulativeSize, + _scrollController!.position.maxScrollExtent, + ); + _scrollController!.animateTo( + distance, + duration: const Duration(milliseconds: 1), + curve: Curves.fastOutSlowIn, + ); + }); + } + } + } + @override void initState() { super.initState(); - _rowsPerPage = widget.availableRowsPerPage[0]; + // Issue: + // FLUT-835616 - SfDataPager's Rows per page dropdown value is not set + // based on the SfDataGrid's rowsPerPage property on initial loading. + // + // Fix: + // We have to check the `availableRowsPerPage` list contains the rows per page value + // from the SfDataGrid's rowsPerPage property. If it contains, + // then we have to set the rows per page value from the SfDataGrid's rowsPerPage property. + // Otherwise, we have to set the first value from the `availableRowsPerPage` list. + _rowsPerPage = _getAvailableRowsPerPage(getRowsPerPage(widget.delegate)); + _setRowsPerPageLabelSize(); /// Set page count in DataGridSource. _setPageCountInDataGridSource(widget.pageCount); _defaultPagerDimension = Size(300.0, _getDefaultDimensionHeight()); - _scrollController = ScrollController() - ..addListener(_handleScrollPositionChanged); + _scrollController = + ScrollController()..addListener(_handleScrollPositionChanged); _itemGenerator = _DataPagerItemGenerator(); - _controller = widget.controller ?? DataPagerController() - ..addListener(_handleDataPagerControlPropertyChanged); + _controller = + widget.controller ?? DataPagerController() + ..addListener(_handleDataPagerControlPropertyChanged); _addDelegateListener(); - _onInitialDataPagerLoaded(); } - void _onInitialDataPagerLoaded() { - WidgetsBinding.instance?.addPostFrameCallback((_) { - if (widget.initialPageIndex > 0) { - _handleDataPagerControlPropertyChanged(property: 'initialPageIndex'); - } else { - _currentPageIndex = widget.initialPageIndex; - _controller!._selectedPageIndex = _currentPageIndex; - _handlePageItemTapped(_currentPageIndex); - } - }); + int? _getAvailableRowsPerPage(int? count) { + if (count != null && widget.availableRowsPerPage.contains(count)) { + return count; + } else { + return widget.availableRowsPerPage[0]; + } } - // Delegate - void _addDelegateListener() { final Object delegate = widget.delegate; if (delegate is ChangeNotifier) { @@ -528,14 +612,52 @@ class SfDataPagerState extends State { // OnChanged void _handleScrollPositionChanged() { - setState(() { - _isDirty = true; - }); + if (mounted) { + setState(() { + _isDirty = true; + }); + } } void _handleDataPagerDelegatePropertyChanged() { - if (!_suspendDataPagerUpdate) { + // Issue: + // FLUT-6628 - `handlePageChange` method is called twice on initial loading + // + // Fix: + // We called the `handlePageChange` method twice in _addDelegateListener and `_onInitialDataPagerLoaded` methods. + // In the `_addDelegateListener` method with `_currentPageIndex` index 0 by default. + // In `_onInitialDataPagerLoaded` method, we called with `initialPageIndex` set in the API. + // Now, we have called both in the `_addDelegateListener` method. + // Hence, we removed the _onInitialDataPagerLoaded method. + // Also, introduced the flag _isInitialLoading to restrict unwanted calling. + if (!_suspendDataPagerUpdate && _isInitialLoading) { + if (widget.initialPageIndex > 0) { + final int index = _resolveToItemIndex(widget.initialPageIndex); + _handlePageItemTapped(index, _isInitialLoading); + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + final double distance = _getCumulativeSize(index); + _scrollTo(distance, canUpdate: true); + _setCurrentPageIndex(index); + }); + } else { + _handlePageItemTapped(_currentPageIndex, _isInitialLoading); + } + _isInitialLoading = false; + } else if (!_suspendDataPagerUpdate && _isRowsPerPageChanged) { + _isRowsPerPageChanged = false; + _handlePageItemTapped(_currentPageIndex); + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + final double distance = min( + _getCumulativeSize(_currentPageIndex), + _scrollController!.position.maxScrollExtent, + ); + _scrollTo(distance, canUpdate: true); + _setCurrentPageIndex(_currentPageIndex); + }); + } else if (!_suspendDataPagerUpdate) { _handlePageItemTapped(_currentPageIndex); + } else { + return; } } @@ -547,26 +669,50 @@ class SfDataPagerState extends State { } } - Future _handlePageItemTapped(int index) async { + Future _handlePageItemTapped( + int index, [ + bool isInitialLoading = false, + ]) async { + // Issue: + // FLUT-6759 - `handlePageChange` method is called infinite times when switch between pages so fast + // + // Fix: + // If the user changes the page so fast before the fetch data from API, it calls infinite times. + // We didn't wait in `_handlePageItemTapped` until the `handlePageChange` method completed. + // Now, we have called the _handlePageChange method again after completing the first process. + if (_suspendDataPagerUpdate) { + return; + } _suspendDataPagerUpdate = true; + + // When the index is greater than the page count, + // it is necessary to set the index to the page count index. if (index > widget.pageCount - 1) { - index -= 1; + index = (widget.pageCount - 1).toInt(); } - final bool canChange = await _canChangePage(index); + + final bool canRaiseNavigationEndCallback = + _controller!.selectedPageIndex != index || isInitialLoading; + final bool canChange = await _canChangePage(index, isInitialLoading); if (canChange) { - setState(() { - _setCurrentPageIndex(index); - _isDirty = true; - }); + if (mounted) { + setState(() { + _setCurrentPageIndex(index); + _isDirty = true; + }); + } + } + if (canRaiseNavigationEndCallback) { + _raisePageNavigationEnd(canChange ? index : _currentPageIndex); } - _raisePageNavigationEnd(canChange ? index : _currentPageIndex); _suspendDataPagerUpdate = false; } - Future _handleDataPagerControlPropertyChanged( - {String? property}) async { + Future _handleDataPagerControlPropertyChanged({ + String? property, + }) async { _suspendDataPagerUpdate = true; switch (property) { case 'first': @@ -592,7 +738,8 @@ class SfDataPagerState extends State { _setCurrentPageIndex(_lastPageIndex); } _raisePageNavigationEnd( - canChangePage ? _lastPageIndex : _currentPageIndex); + canChangePage ? _lastPageIndex : _currentPageIndex, + ); break; case 'previous': final int previousIndex = _getPreviousPageIndex(); @@ -607,7 +754,8 @@ class SfDataPagerState extends State { _setCurrentPageIndex(previousIndex); } _raisePageNavigationEnd( - canChangePage ? previousIndex : _currentPageIndex); + canChangePage ? previousIndex : _currentPageIndex, + ); break; case 'next': final int nextPageIndex = _getNextPageIndex(); @@ -623,7 +771,8 @@ class SfDataPagerState extends State { _setCurrentPageIndex(nextPageIndex); } _raisePageNavigationEnd( - canChangePage ? nextPageIndex : _currentPageIndex); + canChangePage ? nextPageIndex : _currentPageIndex, + ); break; case 'initialPageIndex': if (widget.initialPageIndex.isNegative) { @@ -631,13 +780,10 @@ class SfDataPagerState extends State { } final int index = _resolveToItemIndex(widget.initialPageIndex); - final bool canChangePage = await _canChangePage(index); - if (canChangePage) { - final double distance = _getCumulativeSize(index); - await _scrollTo(distance, canUpdate: true); - _setCurrentPageIndex(index); - } - _raisePageNavigationEnd(canChangePage ? index : _currentPageIndex); + final double distance = _getCumulativeSize(index); + await _scrollTo(distance, canUpdate: true); + _setCurrentPageIndex(index); + _raisePageNavigationEnd(index); break; case 'pageCount': _currentPageIndex = 0; @@ -649,9 +795,13 @@ class SfDataPagerState extends State { if (selectedPageIndex < 0 || selectedPageIndex > _lastPageIndex || selectedPageIndex == _currentPageIndex) { + _suspendDataPagerUpdate = false; return; } - final bool canChangePage = await _canChangePage(selectedPageIndex); + final bool canChangePage = await _canChangePage( + selectedPageIndex, + true, + ); if (canChangePage) { final double distance = _getScrollOffset(selectedPageIndex); @@ -659,7 +809,8 @@ class SfDataPagerState extends State { _setCurrentPageIndex(selectedPageIndex); } _raisePageNavigationEnd( - canChangePage ? selectedPageIndex : _currentPageIndex); + canChangePage ? selectedPageIndex : _currentPageIndex, + ); break; default: break; @@ -667,11 +818,19 @@ class SfDataPagerState extends State { _suspendDataPagerUpdate = false; } - Future _canChangePage(int index) async { - _raisePageNavigationStart(index); + Future _canChangePage( + int index, [ + bool canRaiseNavigationStartCallback = false, + ]) async { + if (_controller!.selectedPageIndex != index || + canRaiseNavigationStartCallback) { + _raisePageNavigationStart(_currentPageIndex); + } - final bool canHandle = - await widget.delegate.handlePageChange(_currentPageIndex, index); + final bool canHandle = await widget.delegate.handlePageChange( + _currentPageIndex, + index, + ); return canHandle; } @@ -739,16 +898,20 @@ class SfDataPagerState extends State { Future _scrollTo(double offset, {bool canUpdate = false}) async { if (offset == _scrollController!.offset && !canUpdate) { - setState(() { - _isDirty = true; - }); + if (mounted) { + setState(() { + _isDirty = true; + }); + } return; } Future.delayed(Duration.zero, () { - _scrollController!.animateTo(offset, - duration: const Duration(milliseconds: 250), - curve: Curves.fastOutSlowIn); + _scrollController!.animateTo( + offset, + duration: const Duration(milliseconds: 250), + curve: Curves.fastOutSlowIn, + ); }); } @@ -768,13 +931,9 @@ class SfDataPagerState extends State { Widget _getChildrenBasedOnDirection(List children) { if (widget.direction == Axis.vertical) { - return Column( - children: List.from(children), - ); + return Column(children: List.from(children)); } else { - return Row( - children: List.from(children), - ); + return Row(children: List.from(children)); } } @@ -805,8 +964,10 @@ class SfDataPagerState extends State { } Size _getScrollViewSize() { - final Size scrollViewSize = - Size(_scrollViewExtent, _defaultPagerDimension.height); + final Size scrollViewSize = Size( + _scrollViewExtent, + _defaultPagerDimension.height, + ); if (widget.direction == Axis.horizontal) { return scrollViewSize; } else { @@ -814,23 +975,34 @@ class SfDataPagerState extends State { } } - Widget _getIcon(String type, IconData iconData, bool visible) { + Widget _getIcon( + String type, + IconData iconData, + bool visible, + SfColorScheme colorScheme, + ) { Icon buildIcon() { - return Icon(iconData, - key: ValueKey(type), - size: 20, - color: visible - ? _dataPagerThemeHelper!.brightness == Brightness.light - ? _dataPagerThemeHelper!.disabledItemTextStyle.color - ?.withOpacity(0.54) - : _dataPagerThemeHelper!.disabledItemTextStyle.color - ?.withOpacity(0.65) - : _dataPagerThemeHelper!.disabledItemTextStyle.color); + return Icon( + iconData, + key: ValueKey(type), + size: 20, + color: + visible + ? colorScheme.brightness == Brightness.light + ? _dataPagerThemeHelper!.disabledItemTextStyle!.color + ?.withValues(alpha: 0.54) + : _dataPagerThemeHelper!.disabledItemTextStyle!.color + ?.withValues(alpha: 0.65) + : _dataPagerThemeHelper!.disabledItemTextStyle!.color, + ); } if (widget.direction == Axis.vertical) { return RotatedBox( - key: ValueKey(type), quarterTurns: 1, child: buildIcon()); + key: ValueKey(type), + quarterTurns: 1, + child: buildIcon(), + ); } else { return buildIcon(); } @@ -860,10 +1032,12 @@ class SfDataPagerState extends State { } } - double _getDataPagerWidth( - {bool canEnableDataPagerLable = false, bool isDropDown = false}) { + double _getDataPagerWidth({ + bool canEnableDataPagerLabel = false, + bool isDropDown = false, + }) { if (widget.direction == Axis.horizontal) { - if (canEnableDataPagerLable && isDropDown) { + if (canEnableDataPagerLabel && isDropDown) { return _headerExtent + _scrollViewPortSize + _footerExtent + @@ -927,7 +1101,9 @@ class SfDataPagerState extends State { void _preGenerateItem(double width) { _itemGenerator.preGenerateItems( - 0, width ~/ _getButtonSize(widget.itemHeight, widget.itemWidth)); + 0, + width ~/ _getButtonSize(widget.itemHeight, widget.itemWidth), + ); } void _ensureItems() { @@ -935,24 +1111,37 @@ class SfDataPagerState extends State { return; } - final double buttonSize = - _getButtonSize(widget.itemHeight, widget.itemWidth); + final double buttonSize = _getButtonSize( + widget.itemHeight, + widget.itemWidth, + ); final int startIndex = _scrollController!.offset <= _scrollController!.position.minScrollExtent ? 0 : _scrollController!.offset ~/ buttonSize; - final int endIndex = - _scrollController!.offset >= _scrollController!.position.maxScrollExtent - ? _lastPageIndex - : (_scrollController!.offset + _scrollViewPortSize) ~/ buttonSize; + // Issue: + // + // FLUT-6923-SfDataPager is not working properly when updating the pageCount dynamically + // + // Fix: + // We have gotten the page item `endIndex` based on the maxScrollExtent and offset. + // If the pager has below 5-page count then it's not scrollable at initial loading. + // Updating the page index dynamically, the offset and maxScrollExtent have zero as a value. + // So, it satisfied the first condition, takes the end index as the `_lastPageIndex` and generates all the pages. + // Now we removed that unwanted condition to get the last page index. Since we get the last page index + // from the current scroll offset + scrollViewPortSize ~/ buttonSize itself. + // It returns the last visible page index. + final int endIndex = + (_scrollController!.offset + _scrollViewPortSize) ~/ buttonSize; _itemGenerator.ensureItems(startIndex, endIndex); } void _arrangeScrollableItems() { _itemGenerator._items.sort( - (_ScrollableDataPagerItem first, _ScrollableDataPagerItem second) => - first.index.compareTo(second.index)); + (_ScrollableDataPagerItem first, _ScrollableDataPagerItem second) => + first.index.compareTo(second.index), + ); double getDifferenceInSize(double itemSize, double navigationItemSize) { if (itemSize > navigationItemSize) { return 0; @@ -963,27 +1152,35 @@ class SfDataPagerState extends State { for (final _ScrollableDataPagerItem element in _itemGenerator._items) { if (element.visible) { - final double buttonSize = - _getButtonSize(widget.itemHeight, widget.itemWidth); + final double buttonSize = _getButtonSize( + widget.itemHeight, + widget.itemWidth, + ); - double xPos = widget.direction == Axis.horizontal - ? _isRTL - ? _scrollViewExtent - (element.index * buttonSize) - buttonSize - : element.index * buttonSize - : 0.0; + double xPos = + widget.direction == Axis.horizontal + ? _isRTL + ? _scrollViewExtent - + (element.index * buttonSize) - + buttonSize + : element.index * buttonSize + : 0.0; // In vertical direction, If navigationItemWidth>itemWidth, // the items will align from starting position in order of aligning at the center // So we have to align the scrollable item at the center if (widget.direction == Axis.vertical && widget.itemWidth != widget.navigationItemWidth) { - xPos += - getDifferenceInSize(widget.itemWidth, widget.navigationItemWidth); + xPos += getDifferenceInSize( + widget.itemWidth, + widget.navigationItemWidth, + ); } - double yPos = widget.direction == Axis.vertical - ? element.index * buttonSize - : 0.0; + double yPos = + widget.direction == Axis.vertical + ? element.index * buttonSize + : 0.0; // In horizontal direction, If navigationItemHeight>itemHeight, // the items will align from starting position in order of aligning at the center @@ -991,11 +1188,17 @@ class SfDataPagerState extends State { if (widget.direction == Axis.horizontal && widget.itemHeight != widget.navigationItemHeight) { yPos += getDifferenceInSize( - widget.itemHeight, widget.navigationItemHeight); + widget.itemHeight, + widget.navigationItemHeight, + ); } - element.elementRect = - Rect.fromLTWH(xPos, yPos, widget.itemWidth, widget.itemHeight); + element.elementRect = Rect.fromLTWH( + xPos, + yPos, + widget.itemWidth, + widget.itemHeight, + ); } } } @@ -1003,17 +1206,18 @@ class SfDataPagerState extends State { // PagerItem builders // Item builder - Widget _buildDataPagerItem( - {_ScrollableDataPagerItem? element, - String? type, - IconData? iconData, - double? height, - double? width, - EdgeInsetsGeometry? padding}) { - final ThemeData _flutterTheme = Theme.of(context); + Widget _buildDataPagerItem({ + _ScrollableDataPagerItem? element, + String? type, + IconData? iconData, + double? height, + double? width, + EdgeInsetsGeometry? padding, + }) { + final SfColorScheme colorScheme = SfTheme.colorScheme(context); Widget? pagerItem; Key? pagerItemKey; - Color itemColor = _dataPagerThemeHelper!.itemColor; + Color itemColor = _dataPagerThemeHelper!.itemColor!; bool visible = true; late Border border; @@ -1022,47 +1226,79 @@ class SfDataPagerState extends State { pagerItem = widget.pageItemBuilder!(type ?? element!.index.toString()); } - void _setBorder() { - border = _dataPagerThemeHelper!.itemBorderWidth != null && - _dataPagerThemeHelper!.itemBorderWidth! > 0.0 - ? Border.all( - width: _dataPagerThemeHelper!.itemBorderWidth!, - color: _dataPagerThemeHelper!.itemBorderColor) - : Border.all(width: 0.0, color: Colors.transparent); + void setBorder() { + border = + _dataPagerThemeHelper!.itemBorderWidth != null && + _dataPagerThemeHelper!.itemBorderWidth! > 0.0 + ? Border.all( + width: _dataPagerThemeHelper!.itemBorderWidth!, + color: _dataPagerThemeHelper!.itemBorderColor!, + ) + : Border.all(width: 0.0, color: colorScheme.transparent); } if (pagerItem == null) { if (element == null) { visible = !_isNavigatorItemVisible(type!); - itemColor = visible - ? _dataPagerThemeHelper!.itemColor - : _dataPagerThemeHelper!.disabledItemColor; + itemColor = + visible + ? _dataPagerThemeHelper!.itemColor! + : _dataPagerThemeHelper!.disabledItemColor!; pagerItem = Semantics( label: '$type Page', - child: _getIcon(type, iconData!, visible), + child: _getIcon(type, iconData!, visible, colorScheme), ); pagerItemKey = ObjectKey(type); } else { final bool isSelected = _checkIsSelectedIndex(element.index); - itemColor = isSelected - ? _dataPagerThemeHelper!.selectedItemColor - : _dataPagerThemeHelper!.itemColor; + itemColor = + isSelected + ? _dataPagerThemeHelper!.selectedItemColor! + : _dataPagerThemeHelper!.itemColor!; final int index = _resolveToItemIndexInView(element.index); pagerItem = Text( index.toString(), key: element.key, - style: isSelected - ? _dataPagerThemeHelper!.selectedItemTextStyle - : _dataPagerThemeHelper!.itemTextStyle, + style: + isSelected + ? _dataPagerThemeHelper!.selectedItemTextStyle + : _dataPagerThemeHelper!.itemTextStyle, ); pagerItemKey = element.key; } + } else { + // Issue: + // + // FLUT-6687-The next and previous buttons are enabled even though + // the current page is the last and the first page respectively. + // + // Fix: + // + // We have applied the disabled and selected Items Color based on the visible and selected properties. + // But, we only update this bool properties when the pagerItem is null. + // So, the visible property is true by default hence the disabled color is not applied. + // Also, the selected item Color is applied only when the pagerItem is null. + // Now, we are updating those properties even though the pagerItem is not null. + + if (element == null) { + visible = !_isNavigatorItemVisible(type!); + itemColor = + visible + ? _dataPagerThemeHelper!.itemColor! + : _dataPagerThemeHelper!.disabledItemColor!; + } else { + final bool isSelected = _checkIsSelectedIndex(element.index); + itemColor = + isSelected + ? _dataPagerThemeHelper!.selectedItemColor! + : _dataPagerThemeHelper!.itemColor!; + } } - _setBorder(); + setBorder(); return SizedBox( key: pagerItemKey, @@ -1073,27 +1309,32 @@ class SfDataPagerState extends State { child: CustomPaint( key: pagerItemKey, painter: _DataPagerItemBoxPainter( - decoration: BoxDecoration( - color: itemColor, - border: border, - borderRadius: _dataPagerThemeHelper!.itemBorderRadius), - imageConfig: _imageConfiguration!), + decoration: BoxDecoration( + color: itemColor, + border: border, + borderRadius: _dataPagerThemeHelper!.itemBorderRadius, + ), + imageConfig: _imageConfiguration!, + ), child: Material( key: pagerItemKey, - color: Colors.transparent, + color: colorScheme.transparent, borderRadius: _dataPagerThemeHelper!.itemBorderRadius, clipBehavior: Clip.antiAlias, child: InkWell( key: pagerItemKey, - mouseCursor: visible - ? MaterialStateMouseCursor.clickable - : SystemMouseCursors.basic, + mouseCursor: + visible + ? WidgetStateMouseCursor.clickable + : SystemMouseCursors.basic, splashColor: - visible ? _flutterTheme.splashColor : Colors.transparent, + visible ? colorScheme.splashColor : colorScheme.transparent, hoverColor: - visible ? _flutterTheme.hoverColor : Colors.transparent, + visible ? colorScheme.hoverColor : colorScheme.transparent, highlightColor: - visible ? _flutterTheme.highlightColor : Colors.transparent, + visible + ? colorScheme.highlightColor + : colorScheme.transparent, onTap: () { if (element != null) { if (element.index == _currentPageIndex) { @@ -1106,13 +1347,11 @@ class SfDataPagerState extends State { } _handleDataPagerControlPropertyChanged( - property: type!.toLowerCase()); + property: type!.toLowerCase(), + ); } }, - child: Align( - key: pagerItemKey, - alignment: Alignment.center, - child: pagerItem), + child: Align(key: pagerItemKey, child: pagerItem), ), ), ), @@ -1143,31 +1382,40 @@ class SfDataPagerState extends State { //FirstIcon if (widget.firstPageItemVisible) { - children.add(_buildDataPagerItem( + children.add( + _buildDataPagerItem( type: 'First', iconData: getFirstIcon(), height: widget.navigationItemHeight, width: widget.navigationItemWidth, - padding: widget.itemPadding)); + padding: widget.itemPadding, + ), + ); } //PreviousIcon if (widget.previousPageItemVisible) { - children.add(_buildDataPagerItem( + children.add( + _buildDataPagerItem( type: 'Previous', iconData: getPreviousIcon(), height: widget.navigationItemHeight, width: widget.navigationItemWidth, - padding: widget.itemPadding)); + padding: widget.itemPadding, + ), + ); } //Set headerExtent - _headerExtent = children.isEmpty - ? 0.0 - : children.length * - _getButtonSize( - widget.navigationItemHeight, widget.navigationItemWidth); + _headerExtent = + children.isEmpty + ? 0.0 + : children.length * + _getButtonSize( + widget.navigationItemHeight, + widget.navigationItemWidth, + ); return children.isEmpty ? null : _getChildrenBasedOnDirection(children); } @@ -1175,36 +1423,41 @@ class SfDataPagerState extends State { // dropdown Widget? _buildDropDownWidget() { if (widget.onRowsPerPageChanged != null) { - final List _availableRowsPerPage = + final List availableRowsPerPage = widget.availableRowsPerPage.map>((int? value) { - return DropdownMenuItem( - value: value, - child: Text( - '$value', - style: _dataPagerThemeHelper!.itemTextStyle, - textAlign: _isRTL ? TextAlign.right : TextAlign.left, - )); - }).toList(); + return DropdownMenuItem( + value: value, + child: Text( + '$value', + style: _dataPagerThemeHelper!.itemTextStyle, + textAlign: _isRTL ? TextAlign.right : TextAlign.left, + ), + ); + }).toList(); return Padding( padding: const EdgeInsets.fromLTRB(0, 0, 16, 0), child: Container( width: _dropdownSize.width, height: _dropdownSize.height, - padding: !_isRTL - ? const EdgeInsets.fromLTRB(16, 8, 7, 8) - : const EdgeInsets.fromLTRB(7, 8, 16, 8), + padding: + !_isRTL + ? const EdgeInsets.fromLTRB(16, 8, 7, 8) + : const EdgeInsets.fromLTRB(7, 8, 16, 8), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(3.0), - border: Border.all( - color: _dataPagerThemeHelper!.dropdownButtonBorderColor)), + borderRadius: BorderRadius.circular(3.0), + border: Border.all( + color: _dataPagerThemeHelper!.dropdownButtonBorderColor!, + ), + ), child: DropdownButtonHideUnderline( child: DropdownButton( focusColor: Colors.transparent, itemHeight: 48, - items: _availableRowsPerPage.cast>(), + items: availableRowsPerPage.cast>(), value: _rowsPerPage, iconSize: 22.0, onChanged: (int? value) { + _isRowsPerPageChanged = true; _rowsPerPage = value; widget.onRowsPerPageChanged!(_rowsPerPage); }, @@ -1239,46 +1492,58 @@ class SfDataPagerState extends State { //NextIcon if (widget.nextPageItemVisible) { - children.add(_buildDataPagerItem( + children.add( + _buildDataPagerItem( type: 'Next', iconData: getNextIcon(), height: widget.navigationItemHeight, width: widget.navigationItemWidth, - padding: widget.itemPadding)); + padding: widget.itemPadding, + ), + ); } //LastIcon if (widget.lastPageItemVisible) { - children.add(_buildDataPagerItem( + children.add( + _buildDataPagerItem( type: 'Last', iconData: getLastIcon(), height: widget.navigationItemHeight, width: widget.navigationItemWidth, - padding: widget.itemPadding)); + padding: widget.itemPadding, + ), + ); } //Set footerExtent - _footerExtent = children.isEmpty - ? 0.0 - : children.length * - _getButtonSize( - widget.navigationItemHeight, widget.navigationItemWidth); + _footerExtent = + children.isEmpty + ? 0.0 + : children.length * + _getButtonSize( + widget.navigationItemHeight, + widget.navigationItemWidth, + ); return children.isEmpty ? null : _getChildrenBasedOnDirection(children); } // Body Widget _buildBody(BoxConstraints constraint) { - final int _oldPageCount = _pageCount; + final int oldPageCount = _pageCount; _pageCount = widget.pageCount.toInt(); - final double buttonSize = - _getButtonSize(widget.itemHeight, widget.itemWidth); + final double buttonSize = _getButtonSize( + widget.itemHeight, + widget.itemWidth, + ); _scrollViewExtent = buttonSize * _pageCount; final double size = _getSizeConstraint(constraint); - final double visibleItemsSize = size - + final double visibleItemsSize = + size - (_headerExtent + _footerExtent + widget.visibleItemsCount * buttonSize); // Reset the scroll offset @@ -1288,20 +1553,22 @@ class SfDataPagerState extends State { // Need to remove once it fixed final double preScrollViewPortSize = _scrollViewPortSize; - _scrollViewPortSize = visibleItemsSize <= 0 - ? size - _headerExtent - _footerExtent - : widget.visibleItemsCount * buttonSize; + _scrollViewPortSize = + visibleItemsSize <= 0 + ? size - _headerExtent - _footerExtent + : widget.visibleItemsCount * buttonSize; - _scrollViewPortSize = _scrollViewExtent <= _scrollViewPortSize - ? _scrollViewExtent - : _scrollViewPortSize; + _scrollViewPortSize = + _scrollViewExtent <= _scrollViewPortSize + ? _scrollViewExtent + : _scrollViewPortSize; // Reset the scroll offset // Case: VisibleItemsCount get fit into the view on orientation changed // After we scrolled some distance on previous orientation. // Issue: https://github.com/flutter/flutter/issues/60288 // Need to remove once it fixed - _resetScrollOffset(preScrollViewPortSize, _oldPageCount); + _resetScrollOffset(preScrollViewPortSize, oldPageCount); if (!_isPregenerateItems) { _preGenerateItem(_scrollViewPortSize); @@ -1315,15 +1582,18 @@ class SfDataPagerState extends State { final Widget child = _buildScrollView(); return Container( - width: widget.direction == Axis.horizontal - ? _scrollViewPortSize - : _defaultPagerDimension.height, - height: widget.direction == Axis.horizontal - ? _defaultPagerDimension.height - : _scrollViewPortSize, - clipBehavior: Clip.antiAlias, - decoration: const BoxDecoration(color: Colors.transparent), - child: child); + width: + widget.direction == Axis.horizontal + ? _scrollViewPortSize + : _defaultPagerDimension.height, + height: + widget.direction == Axis.horizontal + ? _defaultPagerDimension.height + : _scrollViewPortSize, + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration(color: Colors.transparent), + child: child, + ); } // ScrollView Builder @@ -1336,36 +1606,39 @@ class SfDataPagerState extends State { child: _DataPagerItemPanelRenderObject( isDirty: _isDirty, viewSize: _getScrollViewSize(), - children: List<_DataPagerItemRenderObject>.from(_itemGenerator._items - .map<_DataPagerItemRenderObject>( + children: List<_DataPagerItemRenderObject>.from( + _itemGenerator._items + .map<_DataPagerItemRenderObject>( (_ScrollableDataPagerItem element) => _DataPagerItemRenderObject( key: element.key, isDirty: _isDirty, element: element, child: _buildDataPagerItem( - element: element, - height: widget.itemHeight, - width: widget.itemWidth, - padding: widget.itemPadding), - )) - .toList(growable: false)), + element: element, + height: widget.itemHeight, + width: widget.itemWidth, + padding: widget.itemPadding, + ), + ), + ) + .toList(growable: false), + ), ), ); } - void _resetScrollOffset( - double previousScrollViewPortSize, int _oldPageCount) { + void _resetScrollOffset(double previousScrollViewPortSize, int oldPageCount) { if (_isPregenerateItems && _scrollController!.hasClients && _scrollController!.offset > 0.0 && ((_isOrientationChanged && _scrollViewPortSize > previousScrollViewPortSize) || - _oldPageCount != _pageCount)) { - WidgetsBinding.instance?.addPostFrameCallback((_) { + oldPageCount != _pageCount)) { + WidgetsBinding.instance.addPostFrameCallback((_) { _handleScrollPositionChanged(); - if (_oldPageCount != _pageCount) { - if (_currentPageIndex > _pageCount - 1) { + if (oldPageCount != _pageCount) { + if (_currentPageIndex >= _pageCount - 1) { _canChangePage(_pageCount - 1); } else { _canChangePage(0); @@ -1408,16 +1681,20 @@ class SfDataPagerState extends State { List? _buildRowsPerPageLabel() { final List children = []; - final Widget _dropDown = _buildDropDownWidget()!; - children.add(Container( - width: _rowsPerPageLabelWidth, - padding: __rowsPerPageLabelPadding, - child: Text(_localization.rowsPerPageDataPagerLabel, + final Widget dropDown = _buildDropDownWidget()!; + children.add( + Container( + width: _rowsPerPageLabelWidth, + padding: __rowsPerPageLabelPadding, + child: Text( + _localization.rowsPerPageDataPagerLabel, textDirection: _textDirection, style: _dataPagerThemeHelper!.itemTextStyle, - textAlign: _isRTL ? TextAlign.right : TextAlign.left), - )); - children.add(_dropDown); + textAlign: _isRTL ? TextAlign.right : TextAlign.left, + ), + ), + ); + children.add(dropDown); return children; } @@ -1425,39 +1702,44 @@ class SfDataPagerState extends State { // DataPager with Label builders Widget _buildDataPagerLabel() { final int index = _resolveToItemIndexInView(_currentPageIndex); - final String _labelInfo = + final String labelInfo = '$index ${_localization.ofDataPagerLabel} $_pageCount ' '${_localization.pagesDataPagerLabel}'; final Text label = Text( - _labelInfo, + labelInfo, textDirection: _textDirection, style: TextStyle( - fontSize: _dataPagerThemeHelper!.itemTextStyle.fontSize, - fontWeight: _dataPagerThemeHelper!.itemTextStyle.fontWeight, - fontFamily: _dataPagerThemeHelper!.itemTextStyle.fontFamily, - color: _dataPagerThemeHelper!.itemTextStyle.color), + fontSize: _dataPagerThemeHelper!.itemTextStyle!.fontSize, + fontWeight: _dataPagerThemeHelper!.itemTextStyle!.fontWeight, + fontFamily: _dataPagerThemeHelper!.itemTextStyle!.fontFamily, + color: _dataPagerThemeHelper!.itemTextStyle!.color, + ), ); final Widget dataPagerLabel = SizedBox( - width: _defaultPagerLabelDimension.width, - height: _defaultPagerLabelDimension.height, - child: Align( - alignment: _isRTL ? Alignment.centerLeft : Alignment.centerRight, - child: Center(child: label))); + width: _defaultPagerLabelDimension.width, + height: _defaultPagerLabelDimension.height, + child: Align( + alignment: _isRTL ? Alignment.centerLeft : Alignment.centerRight, + child: Center(child: label), + ), + ); return dataPagerLabel; } void _buildDataPagerWithLabel( - BoxConstraints constraint, List children) { - Widget? _dataPagerLabel; + BoxConstraints constraint, + List children, + ) { + Widget? dataPagerLabel; final bool canEnablePagerLabel = _canEnableDataPagerLabel(constraint); - final Widget? _dropDown = _buildDropDownWidget(); + final Widget? dropDown = _buildDropDownWidget(); - double _getRowsPerPageLabelWidth() { - if (_dropDown != null) { + double getRowsPerPageLabelWidth() { + if (dropDown != null) { return _rowsPerPageLabelWidth + _dropdownSize.width + 32; } else { return _defaultPagerLabelDimension.width; @@ -1465,54 +1747,58 @@ class SfDataPagerState extends State { } // DataPager - final BoxConstraints _dataPagerConstraint = BoxConstraints( - maxWidth: canEnablePagerLabel - ? _getTotalDataPagerWidth(constraint) - _getRowsPerPageLabelWidth() - : _getTotalDataPagerWidth(constraint), - maxHeight: _getTotalDataPagerHeight(constraint)); + final BoxConstraints dataPagerConstraint = BoxConstraints( + maxWidth: + canEnablePagerLabel + ? _getTotalDataPagerWidth(constraint) - getRowsPerPageLabelWidth() + : _getTotalDataPagerWidth(constraint), + maxHeight: _getTotalDataPagerHeight(constraint), + ); - final Widget _pager = _buildDataPager(_dataPagerConstraint); + final Widget pager = _buildDataPager(dataPagerConstraint); //DataPagerLabel if (canEnablePagerLabel && - _dataPagerConstraint.maxWidth >= _kMobileViewWidthOnWeb) { - _dataPagerLabel = _buildDataPagerLabel(); - } - bool _isDropDown = false; - if (_dropDown != null) { - _isDropDown = true; - } - final Widget _dataPager = SizedBox( - width: _dataPagerConstraint.maxWidth, - height: _dataPagerConstraint.maxHeight, - child: Align( - alignment: _isRTL + dataPagerConstraint.maxWidth >= _kMobileViewWidthOnWeb) { + dataPagerLabel = _buildDataPagerLabel(); + } + bool isDropDown = false; + if (dropDown != null) { + isDropDown = true; + } + final Widget dataPager = SizedBox( + width: dataPagerConstraint.maxWidth, + height: dataPagerConstraint.maxHeight, + child: Align( + alignment: + _isRTL ? canEnablePagerLabel ? Alignment.centerRight : Alignment.center : canEnablePagerLabel - ? Alignment.centerLeft - : Alignment.center, - child: SizedBox( - width: _getDataPagerWidth( - canEnableDataPagerLable: canEnablePagerLabel, - isDropDown: _isDropDown), - height: _getDataPagerHeight(), - child: _dataPagerLabel != null && _isDropDown - ? Row( - children: [_pager, _dataPagerLabel], - ) - : _pager))); + ? Alignment.centerLeft + : Alignment.center, + child: SizedBox( + width: _getDataPagerWidth( + canEnableDataPagerLabel: canEnablePagerLabel, + isDropDown: isDropDown, + ), + height: _getDataPagerHeight(), + child: + dataPagerLabel != null && isDropDown + ? Row(children: [pager, dataPagerLabel]) + : pager, + ), + ), + ); - children.add(_dataPager); + children.add(dataPager); - if (_isDropDown) { - children.add(Row( - children: _buildRowsPerPageLabel()!, - )); + if (isDropDown) { + children.add(Row(children: _buildRowsPerPageLabel()!)); } - if ((canEnablePagerLabel && _dataPagerLabel != null) && !_isDropDown) { - children.add(_dataPagerLabel); + if ((canEnablePagerLabel && dataPagerLabel != null) && !isDropDown) { + children.add(dataPagerLabel); } } @@ -1525,9 +1811,9 @@ class SfDataPagerState extends State { textDirection = Directionality.of(context); _localization = SfLocalizations.of(context); final ThemeData themeData = Theme.of(context); - _dataPagerThemeHelper = DataPagerThemeHelper( - _dataPagerThemeData, Theme.of(context).colorScheme, themeData); - _isDesktop = kIsWeb || + _dataPagerThemeHelper = DataPagerThemeHelper(context); + _isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -1564,19 +1850,29 @@ class SfDataPagerState extends State { } if (oldWidget.availableRowsPerPage != widget.availableRowsPerPage) { - _rowsPerPage = widget.availableRowsPerPage.contains(_rowsPerPage) - ? _rowsPerPage - : widget.availableRowsPerPage[0]; + _rowsPerPage = + widget.availableRowsPerPage.contains(_rowsPerPage) + ? _rowsPerPage + : widget.availableRowsPerPage[0]; } if (isDataPagerControllerChanged) { if (oldWidget.pageCount != widget.pageCount) { _currentPageIndex = 0; } - oldWidget.controller! - .removeListener(_handleDataPagerControlPropertyChanged); - _controller = widget.controller ?? _controller! - ..addListener(_handleDataPagerControlPropertyChanged); + oldWidget.controller!.removeListener( + _handleDataPagerControlPropertyChanged, + ); + _controller = + widget.controller ?? _controller! + ..addListener(_handleDataPagerControlPropertyChanged); + } + + if (_previousScreenSize != null) { + final Size currentScreenSize = MediaQuery.of(context).size; + if (_previousScreenSize != currentScreenSize) { + _adjustScrollPosition(); + } } _isDirty = true; @@ -1589,45 +1885,56 @@ class SfDataPagerState extends State { elevation: 0.0, color: _dataPagerThemeHelper!.backgroundColor, child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraint) { - _updateConstraintChanged(constraint); - if (_currentPageIndex > _pageCount) { - _currentPageIndex = _pageCount - 1; - } - if (_isDesktop && widget.direction == Axis.horizontal) { - final List children = []; + builder: (BuildContext context, BoxConstraints constraint) { + _previousScreenSize = MediaQuery.of(context).size; + _updateConstraintChanged(constraint); + // Issue: + // + // FLUT-868631-The DataPager did not function correctly when updating the rows per page at runtime + // + // Fix: We have page count value starts from 0. But the current page index starts from 1. + // So, we have to check the current page index is greater than or equal to the page count. + // If it is true, then we have to set the current page index as page count - 1. + if (_currentPageIndex >= _pageCount && _pageCount > 0) { + _currentPageIndex = _pageCount - 1; + } + if (_isDesktop && widget.direction == Axis.horizontal) { + final List children = []; - _buildDataPagerWithLabel(constraint, children); + _buildDataPagerWithLabel(constraint, children); - _isDirty = false; - return constraint.maxWidth >= _kMobileViewWidthOnWeb - ? SizedBox( + _isDirty = false; + return constraint.maxWidth >= _kMobileViewWidthOnWeb + ? SizedBox( width: _getTotalDataPagerWidth(constraint), height: _getTotalDataPagerHeight(constraint), child: _getChildrenBasedOnDirection(children), ) - : SizedBox( + : SizedBox( child: SingleChildScrollView( - scrollDirection: widget.direction, - child: _getChildrenBasedOnDirection(children)), + scrollDirection: widget.direction, + child: _getChildrenBasedOnDirection(children), + ), ); - } else { - final Widget _dataPager = _buildDataPager(constraint); - _isDirty = false; - if (widget.onRowsPerPageChanged != null && - widget.direction == Axis.horizontal) { - return SingleChildScrollView( - scrollDirection: widget.direction, - child: _dataPager, - ); } else { - return SizedBox( + final Widget dataPager = _buildDataPager(constraint); + _isDirty = false; + if (widget.onRowsPerPageChanged != null && + widget.direction == Axis.horizontal) { + return SingleChildScrollView( + scrollDirection: widget.direction, + child: dataPager, + ); + } else { + return SizedBox( width: _getDataPagerWidth(), height: _getDataPagerHeight(), - child: _dataPager); + child: dataPager, + ); + } } - } - }), + }, + ), ); } @@ -1676,24 +1983,27 @@ class _DataPagerItemGenerator { _ScrollableDataPagerItem? indexer(int index) { return _items.firstWhereOrNull( - (_ScrollableDataPagerItem element) => element.index == index); + (_ScrollableDataPagerItem element) => element.index == index, + ); } for (int index = startIndex; index <= endIndex; index++) { - _ScrollableDataPagerItem? _scrollableElement = indexer(index); + _ScrollableDataPagerItem? scrollableElement = indexer(index); - if (_scrollableElement == null) { - final _ScrollableDataPagerItem? reUseScrollableElement = - _items.firstWhereOrNull((_ScrollableDataPagerItem element) => - element.index == -1 || !element.isEnsured); + if (scrollableElement == null) { + final _ScrollableDataPagerItem? reUseScrollableElement = _items + .firstWhereOrNull( + (_ScrollableDataPagerItem element) => + element.index == -1 || !element.isEnsured, + ); updateScrollableItem(reUseScrollableElement, index); } - _scrollableElement ??= indexer(index); + scrollableElement ??= indexer(index); - if (_scrollableElement != null) { - _scrollableElement + if (scrollableElement != null) { + scrollableElement ..isEnsured = true ..visibility = true; } else { @@ -1739,12 +2049,12 @@ class _ScrollableDataPagerItem { } class _DataPagerItemRenderObject extends SingleChildRenderObjectWidget { - _DataPagerItemRenderObject( - {required Key? key, - required this.isDirty, - required this.element, - required this.child}) - : super(key: key, child: RepaintBoundary.wrap(child, 0)); + _DataPagerItemRenderObject({ + required Key? key, + required this.isDirty, + required this.element, + required this.child, + }) : super(key: key, child: RepaintBoundary.wrap(child, 0)); @override final Widget child; @@ -1755,15 +2065,14 @@ class _DataPagerItemRenderObject extends SingleChildRenderObjectWidget { @override _DataPagerItemRenderBox createRenderObject(BuildContext context) { - return _DataPagerItemRenderBox( - element: element, - isDirty: isDirty, - ); + return _DataPagerItemRenderBox(element: element, isDirty: isDirty); } @override void updateRenderObject( - BuildContext context, _DataPagerItemRenderBox renderObject) { + BuildContext context, + _DataPagerItemRenderBox renderObject, + ) { super.updateRenderObject(context, renderObject); renderObject ..element = element @@ -1773,7 +2082,7 @@ class _DataPagerItemRenderObject extends SingleChildRenderObjectWidget { class _DataPagerItemRenderBox extends RenderProxyBox { _DataPagerItemRenderBox({required this.element, required bool isDirty}) - : _isDirty = isDirty; + : _isDirty = isDirty; _ScrollableDataPagerItem element; @@ -1796,14 +2105,15 @@ class _DataPagerItemRenderBox extends RenderProxyBox { } class _DataPagerItemPanelRenderObject extends MultiChildRenderObjectWidget { - _DataPagerItemPanelRenderObject( - {Key? key, - required this.isDirty, - required this.viewSize, - required this.children}) - : super( - key: key, - children: RepaintBoundary.wrapAll(List.from(children))); + _DataPagerItemPanelRenderObject({ + Key? key, + required this.isDirty, + required this.viewSize, + required this.children, + }) : super( + key: key, + children: RepaintBoundary.wrapAll(List.from(children)), + ); @override final List<_DataPagerItemRenderObject> children; @@ -1819,7 +2129,9 @@ class _DataPagerItemPanelRenderObject extends MultiChildRenderObjectWidget { @override void updateRenderObject( - BuildContext context, _DataPagerItemPanelRenderBox renderObject) { + BuildContext context, + _DataPagerItemPanelRenderBox renderObject, + ) { super.updateRenderObject(context, renderObject); renderObject ..isDirty = isDirty @@ -1832,12 +2144,14 @@ class _DataPagerItemPanelParentData extends ContainerBoxParentData {} class _DataPagerItemPanelRenderBox extends RenderBox with ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin, + RenderBoxContainerDefaultsMixin< + RenderBox, + _DataPagerItemPanelParentData + >, DebugOverflowIndicatorMixin { _DataPagerItemPanelRenderBox({required Size viewSize, required bool isDirty}) - : _isDirty = isDirty, - _viewSize = viewSize; + : _isDirty = isDirty, + _viewSize = viewSize; bool get isDirty => _isDirty; bool _isDirty = false; @@ -1891,12 +2205,17 @@ class _DataPagerItemPanelRenderBox extends RenderBox final Rect childRect = child.pageItemRect; childParentData.offset = Offset(childRect.left, childRect.top); child.layout( - BoxConstraints.tightFor( - width: childRect.width, height: childRect.height), - parentUsesSize: true); + BoxConstraints.tightFor( + width: childRect.width, + height: childRect.height, + ), + parentUsesSize: true, + ); } else { - child.layout(const BoxConstraints.tightFor(width: 0.0, height: 0.0), - parentUsesSize: false); + child.layout( + const BoxConstraints.tightFor(width: 0.0, height: 0.0), + parentUsesSize: false, + ); } } child = childParentData.nextSibling; @@ -1923,8 +2242,10 @@ class _DataPagerItemPanelRenderBox extends RenderBox } class _DataPagerItemBoxPainter extends CustomPainter { - const _DataPagerItemBoxPainter( - {required this.decoration, required this.imageConfig}); + const _DataPagerItemBoxPainter({ + required this.decoration, + required this.imageConfig, + }); final ImageConfiguration imageConfig; final BoxDecoration decoration; @@ -1974,78 +2295,63 @@ class _DataPagerChangeNotifier { /// To Do class DataPagerThemeHelper { /// To Do - DataPagerThemeHelper(SfDataPagerThemeData? sfDataPagerThemeData, - ColorScheme? colorScheme, ThemeData themeData) { - brightness = sfDataPagerThemeData!.brightness ?? themeData.brightness; - backgroundColor = sfDataPagerThemeData.backgroundColor ?? - colorScheme!.surface.withOpacity(0.12); - itemColor = sfDataPagerThemeData.itemColor ?? Colors.transparent; - itemTextStyle = sfDataPagerThemeData.itemTextStyle ?? - TextStyle( - color: themeData.colorScheme.onSurface.withOpacity(0.6), - fontSize: 14, - fontFamily: 'Roboto', - fontWeight: FontWeight.w400); + DataPagerThemeHelper(BuildContext context) { + final SfDataPagerThemeData defaults = SfDataPagerTheme.of(context)!; + final SfDataPagerThemeData sfDataPagerThemeData = _SfDataPagerThemeData( + context, + ); + backgroundColor = + defaults.backgroundColor ?? sfDataPagerThemeData.backgroundColor; + itemColor = defaults.itemColor ?? sfDataPagerThemeData.itemColor; + itemTextStyle = + defaults.itemTextStyle ?? sfDataPagerThemeData.itemTextStyle; selectedItemColor = - sfDataPagerThemeData.selectedItemColor ?? colorScheme!.primary; - selectedItemTextStyle = sfDataPagerThemeData.selectedItemTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: colorScheme!.onPrimary); + defaults.selectedItemColor ?? sfDataPagerThemeData.selectedItemColor; + selectedItemTextStyle = + defaults.selectedItemTextStyle ?? + sfDataPagerThemeData.selectedItemTextStyle; disabledItemColor = - sfDataPagerThemeData.disabledItemColor ?? Colors.transparent; - disabledItemTextStyle = sfDataPagerThemeData.disabledItemTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: colorScheme!.onSurface.withOpacity(0.36)); + defaults.disabledItemColor ?? sfDataPagerThemeData.disabledItemColor; + disabledItemTextStyle = + defaults.disabledItemTextStyle ?? + sfDataPagerThemeData.disabledItemTextStyle; itemBorderColor = - sfDataPagerThemeData.itemBorderColor ?? Colors.transparent; - itemBorderWidth = sfDataPagerThemeData.itemBorderWidth; + defaults.itemBorderColor ?? sfDataPagerThemeData.itemBorderColor; + itemBorderWidth = + defaults.itemBorderWidth ?? sfDataPagerThemeData.itemBorderWidth; itemBorderRadius = - sfDataPagerThemeData.itemBorderRadius ?? BorderRadius.circular(50); + defaults.itemBorderRadius ?? sfDataPagerThemeData.itemBorderRadius; dropdownButtonBorderColor = - sfDataPagerThemeData.dropdownButtonBorderColor ?? - colorScheme!.onSurface.withOpacity(0.12); + defaults.dropdownButtonBorderColor ?? + sfDataPagerThemeData.dropdownButtonBorderColor; } - /// The brightness of the overall theme of the - /// application for the [SfDataPager] widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// datapager widgets will be applied. - late Brightness brightness; - /// The color of the page Items - late Color itemColor; + late final Color? itemColor; /// The color of the data pager background - late Color backgroundColor; + late final Color? backgroundColor; /// The style of the text of page Items - late TextStyle itemTextStyle; + late final TextStyle? itemTextStyle; /// The color of the page Items which are disabled. - late Color disabledItemColor; + late final Color? disabledItemColor; /// The style of the text of page items which are disabled. - late TextStyle disabledItemTextStyle; + late final TextStyle? disabledItemTextStyle; /// The color of the currently selected page item. - late Color selectedItemColor; + late final Color? selectedItemColor; /// The style of the text of currently selected page Item. - late TextStyle selectedItemTextStyle; + late final TextStyle? selectedItemTextStyle; /// The color of the border in page Item. - late Color itemBorderColor; + late final Color? itemBorderColor; /// The width of the border in page item. - double? itemBorderWidth; + late final double? itemBorderWidth; /// If non null, the corners of the page item are rounded by /// this [itemBorderRadius]. @@ -2054,8 +2360,70 @@ class DataPagerThemeHelper { /// see also: /// /// [BoxDecoration.borderRadius] - late BorderRadiusGeometry itemBorderRadius; + late final BorderRadiusGeometry? itemBorderRadius; ///The border color of the rowsPerPage dropdown button. - late Color dropdownButtonBorderColor; + late final Color? dropdownButtonBorderColor; +} + +/// +///Defines the theme data for the [SfDataPager] widget for Material 2 design. +/// +class _SfDataPagerThemeData extends SfDataPagerThemeData { + /// Constructs the [_SfDataPagerThemeData] + _SfDataPagerThemeData(this.context); + + /// The build context. + final BuildContext context; + + /// The color scheme derived from the current SfTheme context. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color get itemColor => colorScheme.transparent; + + @override + Color? get backgroundColor => colorScheme.surface[31]; + + @override + TextStyle get itemTextStyle => TextStyle( + color: colorScheme.onSurface[153], + fontSize: 14, + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + ); + + @override + Color get disabledItemColor => colorScheme.transparent; + + @override + TextStyle get disabledItemTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onSurface[92], + ); + + @override + Color? get selectedItemColor => colorScheme.primary[1]; + + @override + TextStyle get selectedItemTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onPrimary, + ); + + @override + Color get itemBorderColor => colorScheme.transparent; + + @override + double? get itemBorderWidth => null; + + @override + BorderRadiusGeometry get itemBorderRadius => BorderRadius.circular(50); + + @override + Color? get dropdownButtonBorderColor => colorScheme.onSurface[32]; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart index 79ca3c700..b04643027 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart @@ -14,11 +14,13 @@ import 'utility_helper.dart'; /// the scrollbars value and the rows or columns associated with that value. abstract class DistanceCounterCollectionBase { /// Create the DistanceCounterCollection Base - DistanceCounterCollectionBase( - {int count = 0, double defaultDistance = 0.0, double totalDistance = 0.0}) - : _count = count, - _defaultDistance = defaultDistance, - _totalDistance = totalDistance; + DistanceCounterCollectionBase({ + int count = 0, + double defaultDistance = 0.0, + double totalDistance = 0.0, + }) : _count = count, + _defaultDistance = defaultDistance, + _totalDistance = totalDistance; /// Gets the raw number of entities (lines, rows or columns). /// @@ -169,7 +171,9 @@ 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). /// @@ -206,7 +210,7 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase /// * trackedParentCollection - _required_ - The parent collection for which /// a subset is "tracked". DistanceCounterSubset(DistanceCounterCollectionBase trackedParentCollection) - : super(count: 0) { + : super(count: 0) { _trackDCC = trackedParentCollection; } @@ -241,7 +245,7 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase @override double get defaultDistance => _trackDCC.defaultDistance; - /// Sets the dafault distnace an entity spans. + /// Sets the default distance an entity spans. @override set defaultDistance(double value) { _trackDCC.defaultDistance = value; @@ -284,7 +288,8 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase /// * treeTableCounterSource - _required_ - The nested tree /// table visible counter source. void connectWithParentBase( - TreeTableCounterSourceBase treeTableCounterSource) { + TreeTableCounterSourceBase treeTableCounterSource, + ) { throw Exception('Do not use DistanceCounterSubset as nested collection!'); } @@ -299,7 +304,7 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase double getAlignedScrollValue(double point) { final double offset = _trackDCC.getPreviousScrollValue(start.toDouble()); final double d = _trackDCC.getAlignedScrollValue(point + offset); - if (d == double.nan || d < offset || d - offset > totalDistance) { + if (d.isNaN || d < offset || d - offset > totalDistance) { return double.nan; } @@ -358,7 +363,7 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase double getNextScrollValue(double point) { final double offset = _trackDCC.getCumulatedDistanceAt(start); final double d = _trackDCC.getNextScrollValue(point + offset); - if (d == double.nan || d < offset || d - offset > totalDistance) { + if (d.isNaN || d < offset || d - offset > totalDistance) { return double.nan; } @@ -395,7 +400,7 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase double getPreviousScrollValue(double point) { final double offset = _trackDCC.getCumulatedDistanceAt(start); final double d = _trackDCC.getPreviousScrollValue(point + offset); - if (d == double.nan || d < offset || d - offset > totalDistance) { + if (d.isNaN || d < offset || d - offset > totalDistance) { return double.nan; } @@ -429,7 +434,8 @@ class DistanceCounterSubset extends DistanceCounterCollectionBase } final int n = _trackDCC.indexOfCumulatedDistance( - cumulatedDistance + _trackDCC.getCumulatedDistanceAt(start)); + cumulatedDistance + _trackDCC.getCumulatedDistanceAt(start), + ); if (n > end || n < start) { return -1; } @@ -472,7 +478,9 @@ 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); } @@ -520,7 +528,7 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { /// /// * paddingDistance - _required_ - The padding distance. DistanceRangeCounterCollection.fromPaddingDistance(this.paddingDistance) - : super(defaultDistance: 1.0, count: 0) { + : super(defaultDistance: 1.0, count: 0) { final DistanceLineCounter startPos = DistanceLineCounter(0, 0); _rbTree = DistanceLineCounterTree(startPos, false); } @@ -578,16 +586,16 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { /// Check the range of line in between from and to. void checkRange(String paramName, int from, int to, int actualValue) { if (actualValue < from || actualValue > to) { - throw Exception( - '$paramName ,${actualValue.toString()} out of range ${from.toString()} to ${to.toString()}'); + throw Exception('$paramName ,$actualValue out of range $from to $to'); } } /// DistanceLineCounterEntry createTreeTableEntry(double distance, int count) { - final DistanceLineCounterEntry entry = DistanceLineCounterEntry() - ..value = DistanceLineCounterSource(distance, count) - ..tree = _rbTree; + final DistanceLineCounterEntry entry = + DistanceLineCounterEntry() + ..value = DistanceLineCounterSource(distance, count) + ..tree = _rbTree; return entry; } @@ -596,8 +604,10 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { final int treeCount = internalCount; final int insert = count - treeCount; if (insert > 0) { - final DistanceLineCounterEntry entry = - createTreeTableEntry(defaultDistance, insert); + final DistanceLineCounterEntry entry = createTreeTableEntry( + defaultDistance, + insert, + ); _rbTree.add(entry); } } @@ -647,11 +657,16 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { return internalCount + delta; } - final DistanceLineCounter searchPosition = - DistanceLineCounter(cumulatedDistance, 0); - final DistanceLineCounterEntry? rbEntry = - _rbTree.getEntryAtCounterPositionWithThreeArgs( - searchPosition, DistanceLineCounterKind.distance, false); + final DistanceLineCounter searchPosition = DistanceLineCounter( + cumulatedDistance, + 0, + ); + final DistanceLineCounterEntry? rbEntry = _rbTree + .getEntryAtCounterPositionWithThreeArgs( + searchPosition, + DistanceLineCounterKind.distance, + false, + ); final DistanceLineCounterSource? rbValue = rbEntry!.value; final DistanceLineCounter? rbEntryPosition = rbEntry.getCounterPosition; if (rbValue != null && @@ -659,7 +674,7 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { rbValue.singleLineDistance > 0) { final double totalDistance = (cumulatedDistance - rbEntryPosition.distance) / - rbValue.singleLineDistance; + rbValue.singleLineDistance; delta = totalDistance.isFinite ? totalDistance.floor() : 0; } @@ -668,17 +683,23 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { /// LineIndexEntryAt initDistanceLine( - int lineIndex, bool determineEntryPosition) { - final LineIndexEntryAt e = LineIndexEntryAt() - ..searchPosition = DistanceLineCounter(0, lineIndex); + int lineIndex, + bool determineEntryPosition, + ) { + final LineIndexEntryAt e = + LineIndexEntryAt()..searchPosition = DistanceLineCounter(0, lineIndex); e.rbEntry = _rbTree.getEntryAtCounterPositionWithThreeArgs( - e.searchPosition!, DistanceLineCounterKind.lines, false); + e.searchPosition!, + DistanceLineCounterKind.lines, + false, + ); e.rbValue = e.rbEntry!.value; e.rbEntryPosition = null; if (determineEntryPosition) { - e.rbEntryPosition = e.rbValue!.lineCount > 1 - ? e.rbEntry!.getCounterPosition - : e.searchPosition; + e.rbEntryPosition = + e.rbValue!.lineCount > 1 + ? e.rbEntry!.getCounterPosition + : e.searchPosition; } return e; @@ -704,8 +725,10 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { e.rbEntry!.invalidateCounterBottomUp(true); } else { final DistanceLineCounterEntry? rbEntry0 = split(insertAt); - final DistanceLineCounterEntry entry = - createTreeTableEntry(distance, count); + final DistanceLineCounterEntry entry = createTreeTableEntry( + distance, + count, + ); if (rbEntry0 == null) { _rbTree.add(entry); } else { @@ -804,8 +827,10 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { e.rbValue!.lineCount = count1; - final DistanceLineCounterEntry rbEntry2 = - createTreeTableEntry(e.rbValue!.singleLineDistance, count2); + final DistanceLineCounterEntry rbEntry2 = createTreeTableEntry( + e.rbValue!.singleLineDistance, + count2, + ); _rbTree.insert(_rbTree.indexOf(e.rbEntry!) + 1, rbEntry2); e.rbEntry!.invalidateCounterBottomUp(true); return rbEntry2; @@ -843,11 +868,12 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { final double delta = point - nestedStart; if (delta > 0) { - final DistanceCounterCollectionBase? nestedDcc = - getNestedDistances(index); + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances( + index, + ); if (nestedDcc != null) { final double r = nestedDcc.getAlignedScrollValue(delta); - if (!(r == double.nan) && r >= 0 && r < nestedDcc.totalDistance) { + if (!r.isNaN && r >= 0 && r < nestedDcc.totalDistance) { return nestedStart + r; } } @@ -903,7 +929,7 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { final DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getNextScrollValue(delta); - if (!(r == double.nan) && r >= 0 && r < nestedDcc.totalDistance) { + if (!r.isNaN && r >= 0 && r < nestedDcc.totalDistance) { return nestedStart + r; } } @@ -967,11 +993,12 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { double delta = point - nestedStart; if (delta > 0) { - final DistanceCounterCollectionBase? nestedDcc = - getNestedDistances(index); + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances( + index, + ); if (nestedDcc != null) { final double r = nestedDcc.getPreviousScrollValue(delta); - if (!(r == double.nan) && r >= 0 && r < nestedDcc.totalDistance) { + if (!r.isNaN && r >= 0 && r < nestedDcc.totalDistance) { return nestedStart + r; } } @@ -984,12 +1011,13 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { if (index >= 0 && index < count) { nestedStart = getCumulatedDistanceAt(index); - final DistanceCounterCollectionBase? nestedDcc = - getNestedDistances(index); + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances( + index, + ); if (nestedDcc != null) { delta = nestedDcc.totalDistance; final double r = nestedDcc.getPreviousScrollValue(delta); - if (!(r == double.nan) && r >= 0 && r < nestedDcc.totalDistance) { + if (!r.isNaN && r >= 0 && r < nestedDcc.totalDistance) { return nestedStart + r; } } @@ -1131,7 +1159,9 @@ 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) { @@ -1145,7 +1175,10 @@ class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { if (nestedCollection != null) { final NestedDistanceCounterCollectionSource vcs = NestedDistanceCounterCollectionSource( - this, nestedCollection, entry); + this, + nestedCollection, + entry, + ); entry.value = vcs; } else { entry.value = DistanceLineCounterSource(0, 1); @@ -1221,10 +1254,10 @@ class NestedDistanceCounterCollectionSource extends DistanceLineCounterSource { /// * nestedDistances - _required_ - The nested distances. /// * entry - _required_ - The entry. NestedDistanceCounterCollectionSource( - DistanceCounterCollectionBase parentDistances, - DistanceCounterCollectionBase nestedDistances, - this.entry) - : super(0, 1) { + DistanceCounterCollectionBase parentDistances, + DistanceCounterCollectionBase nestedDistances, + this.entry, + ) : super(0, 1) { _parentDistances = parentDistances; _nestedDistances = nestedDistances; @@ -1263,7 +1296,9 @@ class NestedDistanceCounterCollectionSource extends DistanceLineCounterSource { /// 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 @@ -1492,7 +1527,7 @@ class DistanceLineCounterTree extends TreeTableWithCounter { /// * startPos - _required_ - The start position. /// * sorted - _required_ - A boolean value indicating whether sorted. DistanceLineCounterTree(DistanceLineCounter startPos, bool sorted) - : super(startPos, sorted); + : super(startPos, sorted); /// Appends the given object. /// @@ -1531,7 +1566,9 @@ class DistanceLineCounterTree extends TreeTableWithCounter { /// Returns an entry at the specified counter position. @override DistanceLineCounterEntry? getEntryAtCounterPosition( - Object searchPosition, int cookie) { + Object searchPosition, + int cookie, + ) { if (searchPosition is TreeTableCounterBase && super.getEntryAtCounterPosition(searchPosition, cookie) is DistanceLineCounterEntry) { @@ -1553,13 +1590,23 @@ class DistanceLineCounterTree extends TreeTableWithCounter { /// /// Returns an entry at the specified counter position. DistanceLineCounterEntry? getEntryAtCounterPositionWithThreeArgs( - Object searchPosition, int cookie, bool preferLeftMost) { + Object searchPosition, + int cookie, + bool preferLeftMost, + ) { if (searchPosition is TreeTableCounterBase && super.getEntryAtCounterPositionwithThreeParameter( - searchPosition, cookie, preferLeftMost) + searchPosition, + cookie, + preferLeftMost, + ) is DistanceLineCounterEntry) { return super.getEntryAtCounterPositionwithThreeParameter( - searchPosition, cookie, preferLeftMost)! as DistanceLineCounterEntry; + searchPosition, + cookie, + preferLeftMost, + )! + as DistanceLineCounterEntry; } else { return null; } @@ -1590,7 +1637,9 @@ class DistanceLineCounterTree extends TreeTableWithCounter { /// the specific counter is not empty. @override DistanceLineCounterEntry? getNextNotEmptyCounterEntry( - Object current, int cookie) { + Object current, + int cookie, + ) { if (current is TreeTableEntryBase && super.getNextNotEmptyCounterEntry(current, cookie) is DistanceLineCounterEntry) { @@ -1643,7 +1692,9 @@ class DistanceLineCounterTree extends TreeTableWithCounter { /// specific counter is not empty. @override DistanceLineCounterEntry? getPreviousNotEmptyCounterEntry( - Object current, int cookie) { + Object current, + int cookie, + ) { if (current is TreeTableEntryBase && super.getPreviousNotEmptyCounterEntry(current, cookie) is DistanceLineCounterEntry) { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart index 69e871861..51f805e06 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart @@ -5,7 +5,7 @@ enum TreeTableNodeColor { red, /// TreeTableNodeColoe.black, will represented the Black color. - black + black, } /// Type of scroll axis regions @@ -22,7 +22,7 @@ enum ScrollAxisRegion { /// - ScrollAxisRegion.footer Specifies the footer region /// (at bottom or right side). - footer + footer, } /// Corner side enumeration. @@ -37,5 +37,5 @@ enum CornerSide { right, /// Bottom side alone. - bottom + bottom, } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart index 38b89750f..3ae3a79b2 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart @@ -16,7 +16,11 @@ class RangeChangedArgs { /// * oldSize - _required_ - The old size. /// * newSize - _required_ - The new size. RangeChangedArgs.fromRangeChangedArgs( - int from, int to, double oldSize, double newSize) { + int from, + int to, + double oldSize, + double newSize, + ) { _from = from; _to = to; _oldSize = oldSize; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart index 1ca60be66..50a81e38d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart @@ -7,8 +7,8 @@ import 'utility_helper.dart'; /// Returns the DefaultLineSizeChangedArgs used by the /// [onDefaultLineSizeChanged] event. -typedef DefaultLineSizeChangedCallback = void Function( - DefaultLineSizeChangedArgs _defaultLineSizeChangedArgs); +typedef DefaultLineSizeChangedCallback = + void Function(DefaultLineSizeChangedArgs defaultLineSizeChangedArgs); /// Returns the LineCountChangedArgs used by the /// [onLineCountChanged][onHeaderLineCountChanged][onFooderLineCountChanged] @@ -16,20 +16,19 @@ typedef DefaultLineSizeChangedCallback = void Function( typedef LineCountChangedCallback = void Function(); /// Returns the HiddenRangeChangedArgs used by the [onLineHiddenChanged] event. -typedef LineHiddenChangedCallback = void Function( - HiddenRangeChangedArgs _hiddenRangeChangedArgs); +typedef LineHiddenChangedCallback = + void Function(HiddenRangeChangedArgs hiddenRangeChangedArgs); /// Returns the LinesInsertedArgs used by the [onLinesInserted] event. -typedef LinesInsertedCallback = void Function( - LinesInsertedArgs _linesInsertedArgs); +typedef LinesInsertedCallback = + void Function(LinesInsertedArgs linesInsertedArgs); /// Returns the LinesRemovedArgs used by the [onLinesRemoved] event. -typedef LinesRemovedCallback = void Function( - LinesRemovedArgs _linesRemovedArgs); +typedef LinesRemovedCallback = void Function(LinesRemovedArgs linesRemovedArgs); /// Returns the RangeChangedArgs used by the [onLineSizeChanged] event. -typedef LineSizeChangedCallback = void Function( - RangeChangedArgs rangeChangedArgs); +typedef LineSizeChangedCallback = + void Function(RangeChangedArgs rangeChangedArgs); /// A collection that manages lines with varying height and hidden state. /// @@ -115,7 +114,7 @@ mixin DistancesHostBase { } /// An object that implements the `GetDistances` method. -abstract class NestedDistancesHostBase { +mixin NestedDistancesHostBase { /// Gets the nested distances, if a line contains a nested lines collection, /// otherwise null. /// @@ -136,21 +135,21 @@ abstract class NestedDistancesHostBase { /// size of line. abstract class EditableLineSizeHostBase extends LineSizeHostBase { /// - EditableLineSizeHostBase( - {double defaultLineSize = 0.0, - int footerLineCount = 0, - int headerLineCount = 0, - int lineCount = 0, - bool supportsInsertRemove = false, - bool supportsNestedLines = false, - double totalExtent = 0.0}) - : _defaultLineSize = defaultLineSize, - _footerLineCount = footerLineCount, - _headerLineCount = headerLineCount, - _lineCount = lineCount, - _supportsInsertRemove = supportsInsertRemove, - _supportsNestedLines = supportsNestedLines, - _totalExtent = totalExtent; + EditableLineSizeHostBase({ + double defaultLineSize = 0.0, + int footerLineCount = 0, + int headerLineCount = 0, + int lineCount = 0, + bool supportsInsertRemove = false, + bool supportsNestedLines = false, + double totalExtent = 0.0, + }) : _defaultLineSize = defaultLineSize, + _footerLineCount = footerLineCount, + _headerLineCount = headerLineCount, + _lineCount = lineCount, + _supportsInsertRemove = supportsInsertRemove, + _supportsNestedLines = supportsNestedLines, + _totalExtent = totalExtent; /// Gets the default size of lines. /// @@ -278,7 +277,10 @@ 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. /// @@ -287,7 +289,10 @@ 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. /// @@ -331,24 +336,25 @@ abstract class EditableLineSizeHostBase extends LineSizeHostBase { /// and `DeferRefresh` method. abstract class PaddedEditableLineSizeHostBase extends EditableLineSizeHostBase { /// - PaddedEditableLineSizeHostBase( - {double paddingDistance = 0.0, - double defaultLineSize = 0.0, - int footerLineCount = 0, - int headerLineCount = 0, - int lineCount = 0, - bool supportsInsertRemove = false, - bool supportsNestedLines = false, - double totalExtent = 0.0}) - : _paddingDistance = paddingDistance, - super( - defaultLineSize: defaultLineSize, - footerLineCount: footerLineCount, - headerLineCount: headerLineCount, - lineCount: lineCount, - supportsInsertRemove: supportsInsertRemove, - supportsNestedLines: supportsNestedLines, - totalExtent: totalExtent); + PaddedEditableLineSizeHostBase({ + double paddingDistance = 0.0, + double defaultLineSize = 0.0, + int footerLineCount = 0, + int headerLineCount = 0, + int lineCount = 0, + bool supportsInsertRemove = false, + bool supportsNestedLines = false, + double totalExtent = 0.0, + }) : _paddingDistance = paddingDistance, + super( + defaultLineSize: defaultLineSize, + footerLineCount: footerLineCount, + headerLineCount: headerLineCount, + lineCount: lineCount, + supportsInsertRemove: supportsInsertRemove, + supportsNestedLines: supportsNestedLines, + totalExtent: totalExtent, + ); /// Gets the padding distance for the line. /// @@ -377,19 +383,21 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase with DistancesHostBase, NestedDistancesHostBase { ///Initializes a new instance of the [LineSizeCollection] class. LineSizeCollection() - : super( - headerLineCount: 0, - footerLineCount: 0, - lineCount: 0, - defaultLineSize: 1.0, - paddingDistance: 0.0); + : super( + headerLineCount: 0, + footerLineCount: 0, + lineCount: 0, + defaultLineSize: 1.0, + paddingDistance: 0.0, + ); final SortedRangeValueList _lineSizes = SortedRangeValueList.from(-1); DistanceCounterCollectionBase? _distances; int _isSuspendUpdates = 0; - SortedRangeValueList _lineHidden = - SortedRangeValueList.from(false); + SortedRangeValueList _lineHidden = SortedRangeValueList.from( + false, + ); Map _lineNested = {}; set distances(DistanceCounterCollectionBase? newValue) { @@ -408,8 +416,9 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase @override DistanceCounterCollectionBase? get distances { if (_distances == null) { - _distances = - DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); + _distances = DistanceRangeCounterCollection.fromPaddingDistance( + paddingDistance, + ); initializeDistances(); } @@ -490,8 +499,9 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase return; } - _distances = - DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); + _distances = DistanceRangeCounterCollection.fromPaddingDistance( + paddingDistance, + ); initializeDistances(); } } @@ -621,8 +631,10 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase /// Returns the boolean value indicating the hidden state for a line. @override List getHidden(int index, int repeatValueCount) { - final List rangeValue = - _lineHidden.getRange(index, repeatValueCount); + final List rangeValue = _lineHidden.getRange( + index, + repeatValueCount, + ); return [rangeValue[0], rangeValue[1]]; } @@ -633,16 +645,20 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase if (_lineNested.containsKey(index)) { return [_lineNested[index]?.totalExtent, repeatValueCount]; } - final List hiddenValue = - _lineHidden.getRange(index, repeatValueCount); + final List hiddenValue = _lineHidden.getRange( + index, + repeatValueCount, + ); final bool hide = hiddenValue[0] as bool; repeatValueCount = hiddenValue[1] as int; if (hide) { return [0.0, repeatValueCount]; } - final List rangeValue = - _lineSizes.getRange(index, repeatValueCount); + final List rangeValue = _lineSizes.getRange( + index, + repeatValueCount, + ); final double size = rangeValue[0] as double; repeatValueCount = rangeValue[1] as int; if (size >= 0) { @@ -673,15 +689,18 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase for (final RangeValuePair entry in _lineSizes.rangeValues) { final double entryValue = entry.value as double; if (entryValue != -2) { - _distances!.setRange(entry.start, entry.end, - entryValue < 0.0 ? defaultLineSize : entryValue); + _distances!.setRange( + entry.start, + entry.end, + entryValue < 0.0 ? defaultLineSize : entryValue, + ); } } for (final RangeValuePair entry in _lineHidden.rangeValues) { final bool entryValue = entry.value as bool; if (entryValue) { - setRange(entry.start, entry.end, 0.0); + _distances!.setRange(entry.start, entry.end, 0.0); } } } @@ -705,12 +724,14 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase /// size are inserted. @override void insertLines( - int insertAtLine, int count, EditableLineSizeHostBase? moveLines) { - final LineSizeCollection? _moveLines = + int insertAtLine, + int count, + EditableLineSizeHostBase? moveLines, + ) { + final LineSizeCollection? moveLine = moveLines != null ? moveLines as LineSizeCollection : null; - _lineSizes.insertWithThreeArgs(insertAtLine, count, _moveLines?._lineSizes); - _lineHidden.insertWithThreeArgs( - insertAtLine, count, _moveLines?._lineHidden); + _lineSizes.insertWithThreeArgs(insertAtLine, count, moveLine?._lineSizes); + _lineHidden.insertWithThreeArgs(insertAtLine, count, moveLine?._lineHidden); _lineNested = {}; _lineNested.forEach((int key, LineSizeCollection value) { @@ -721,9 +742,9 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase } }); - if (_moveLines != null) { - for (int i = 0; i < _moveLines._lineNested.length; i++) { - _moveLines._lineNested.forEach((int key, LineSizeCollection value) { + if (moveLine != null) { + for (int i = 0; i < moveLine._lineNested.length; i++) { + moveLine._lineNested.forEach((int key, LineSizeCollection value) { _lineNested.putIfAbsent(key + insertAtLine, () => value); }); } @@ -736,12 +757,14 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase } if (_distances != null) { - DistancesUtil.onInserted(_distances!, this, insertAtLine, count); + DistancesUtil.instance.onInserted(_distances!, this, insertAtLine, count); } if (onLinesInserted != null) { - final LinesInsertedArgs linesInsertedArgs = - LinesInsertedArgs.fromArgs(insertAtLine, count); + final LinesInsertedArgs linesInsertedArgs = LinesInsertedArgs.fromArgs( + insertAtLine, + count, + ); onLinesInserted!(linesInsertedArgs); } } @@ -754,7 +777,8 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase final PixelScrollAxis pixelScrollAxis = scrollAxis as PixelScrollAxis; if (_lineNested.isNotEmpty) { throw Exception( - 'When you have nested line collections you need to use PixelScrolling!'); + 'When you have nested line collections you need to use PixelScrolling!', + ); } scrollAxis ..defaultLineSize = defaultLineSize @@ -763,8 +787,11 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase for (final RangeValuePair entry in _lineSizes) { if (entry.value != -2) { final double entryValue = entry.value as double; - scrollAxis.setLineSize(entry.start, entry.end, - entryValue < 0 ? defaultLineSize : entry.value); + scrollAxis.setLineSize( + entry.start, + entry.end, + entryValue < 0 ? defaultLineSize : entry.value, + ); } } @@ -796,12 +823,22 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase /// call when lines should be moved. @override void removeLines( - int removeAtLine, int count, EditableLineSizeHostBase? moveLines) { - final LineSizeCollection? _moveLines = + int removeAtLine, + int count, + EditableLineSizeHostBase? moveLines, + ) { + final LineSizeCollection? removeLines = moveLines != null ? moveLines as LineSizeCollection : null; - _lineSizes.removeWithThreeArgs(removeAtLine, count, _moveLines?._lineSizes); + _lineSizes.removeWithThreeArgs( + removeAtLine, + count, + removeLines?._lineSizes, + ); _lineHidden.removeWithThreeArgs( - removeAtLine, count, _moveLines?._lineHidden); + removeAtLine, + count, + removeLines?._lineHidden, + ); final Map lineNested = _lineNested; _lineNested = {}; @@ -811,8 +848,11 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase if (key >= removeAtLine) { if (key >= removeAtLine + count) { _lineNested.putIfAbsent(key - count, () => value); - } else if (_moveLines != null) { - _moveLines._lineNested.putIfAbsent(key - removeAtLine, () => value); + } else if (removeLines != null) { + removeLines._lineNested.putIfAbsent( + key - removeAtLine, + () => value, + ); } } else { _lineNested.putIfAbsent(key, () => value); @@ -831,8 +871,10 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase } if (onLinesRemoved != null) { - final LinesRemovedArgs linesRemovedArgs = - LinesRemovedArgs.fromArgs(removeAtLine, count); + final LinesRemovedArgs linesRemovedArgs = LinesRemovedArgs.fromArgs( + removeAtLine, + count, + ); onLinesRemoved!(linesRemovedArgs); } } @@ -891,14 +933,19 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase /// the lines. If set to true hide the lines. @override void setHidden(int from, int to, bool hide) { - _lineHidden.setRange(from, to - from + 1, hide); + _lineHidden.setRange(from, to - from + 1, hide, false); if (isSuspendUpdates) { return; } // DistancesLineHiddenChanged checks both hidden state and sizes together... if (_distances != null) { - DistancesUtil.distancesLineHiddenChanged(distances!, this, from, to); + DistancesUtil.instance.distancesLineHiddenChanged( + distances!, + this, + from, + to, + ); } HiddenRangeChangedArgs hiddenRangeChangedArgs; if (onLineHiddenChanged != null) { @@ -993,7 +1040,12 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase } if (_distances != null) { - DistancesUtil.distancesLineSizeChanged(distances!, this, index, index); + DistancesUtil.instance.distancesLineSizeChanged( + distances!, + this, + index, + index, + ); } if (onLineSizeChanged != null) { @@ -1021,12 +1073,21 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase } if (_distances != null) { - DistancesUtil.distancesLineHiddenChanged(_distances!, this, from, to); + DistancesUtil.instance.distancesLineHiddenChanged( + _distances!, + this, + from, + to, + ); } RangeChangedArgs rangeChangedArgs; if (onLineSizeChanged != null) { - rangeChangedArgs = - RangeChangedArgs.fromRangeChangedArgs(from, to, saveValue, size); + rangeChangedArgs = RangeChangedArgs.fromRangeChangedArgs( + from, + to, + saveValue, + size, + ); onLineSizeChanged!(rangeChangedArgs); } } @@ -1055,8 +1116,15 @@ class LineSizeCollection extends PaddedEditableLineSizeHostBase /// Initializes a new instance of the DistanceLineCounterTree class. class DistancesUtil { + // Private constructor + DistancesUtil._internal(); + /// Prevents a default instance of the DistancesUtil class from being created. - DistancesUtil(); + // Static instance of the class + static final DistancesUtil _instance = DistancesUtil._internal(); + + // Static method to access the instance + static DistancesUtil get instance => _instance; /// This method fires when distances line hidden changed. /// @@ -1064,11 +1132,12 @@ class DistancesUtil { /// * linesHost - _required_ - The line host. /// * from - _required_ - The start index of the line. /// * to - _required_ - The end index of the line. - static void distancesLineHiddenChanged( - DistanceCounterCollectionBase distances, - LineSizeHostBase linesHost, - int from, - int to) { + void distancesLineHiddenChanged( + DistanceCounterCollectionBase distances, + LineSizeHostBase linesHost, + int from, + int to, + ) { final Object ndh = linesHost; for (int n = from; n <= to; n++) { int repeatSizeCount = -1; @@ -1076,7 +1145,7 @@ class DistancesUtil { final bool hide = hiddenLine[0] as bool; repeatSizeCount = hiddenLine[1] as int; - void _setRange() { + void setRange() { final int rangeTo = getRangeToHelper(n, to, repeatSizeCount); if (hide) { distances.setRange(n, rangeTo, 0.0); @@ -1089,12 +1158,12 @@ class DistancesUtil { if (ndh is NestedDistancesHostBase) { if (ndh.getDistances(n) == null) { - _setRange(); + setRange(); } else { distances.setNestedDistances(n, hide ? null : ndh.getDistances(n)); } } else { - _setRange(); + setRange(); } } } @@ -1105,12 +1174,16 @@ class DistancesUtil { /// * linesHost - _required_ - The line host. /// * from - _required_ - The start index of the line. /// * to - _required_ - The end index of the line. - static void distancesLineSizeChanged(DistanceCounterCollectionBase distances, - LineSizeHostBase linesHost, int from, int to) { + void distancesLineSizeChanged( + DistanceCounterCollectionBase distances, + LineSizeHostBase linesHost, + int from, + int to, + ) { final Object ndh = linesHost; for (int n = from; n <= to; n++) { - void _setRange() { + void setRange() { int repeatSizeCount = -1; final List lineSize = linesHost.getSize(n, repeatSizeCount); final double size = lineSize[0] as double; @@ -1122,12 +1195,12 @@ class DistancesUtil { if (ndh is NestedDistancesHostBase) { if (ndh.getDistances(n) == null) { - _setRange(); + setRange(); } else { distances.setNestedDistances(n, ndh.getDistances(n)); } } else { - _setRange(); + setRange(); } } } @@ -1139,7 +1212,7 @@ class DistancesUtil { /// * repeatSizeCount - _required_ - The count of repeated sizes. /// /// Returns the minimum index value - static int getRangeToHelper(int n, int to, int repeatSizeCount) { + int getRangeToHelper(int n, int to, int repeatSizeCount) { if (repeatSizeCount == maxvalue) { return to; } @@ -1153,8 +1226,12 @@ class DistancesUtil { /// * linesHost - _required_ - The line host. /// * insertAt - _required_ - The index to insert. /// * count - _required_ - The count of the lines inserted. - static void onInserted(DistanceCounterCollectionBase distances, - LineSizeHostBase linesHost, int insertAt, int count) { + void onInserted( + DistanceCounterCollectionBase distances, + LineSizeHostBase linesHost, + int insertAt, + int count, + ) { distances.insert(insertAt, count); final int to = insertAt + count - 1; int repeatSizeCount = -1; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart index bab37af9e..750ec84b0 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart @@ -1,3 +1,5 @@ +// ignore_for_file: no_default_cases, avoid_setters_without_getters + import 'dart:math'; import 'distance_counter.dart'; @@ -342,7 +344,8 @@ abstract class ScrollAxisBase { /// void defaultLineSizeChangedCallback( - DefaultLineSizeChangedArgs defaultLineSizeChangedArgs) { + DefaultLineSizeChangedArgs defaultLineSizeChangedArgs, + ) { if (scrollLinesHost != null) { defaultLineSize = scrollLinesHost!.getDefaultLineSize(); markDirty(); @@ -376,8 +379,10 @@ 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) { - final List lineSize = - scrollLinesHost!.getSize(index, repeatSizeCount); + final List lineSize = scrollLinesHost!.getSize( + index, + repeatSizeCount, + ); double size = lineSize[0] as double; repeatSizeCount = lineSize[1] as int; @@ -408,7 +413,7 @@ abstract class ScrollAxisBase { return [ getScrollLinesHostSize(index, repeatSizeCount)[0], - getScrollLinesHostSize(index, repeatSizeCount)[1] + getScrollLinesHostSize(index, repeatSizeCount)[1], ]; } @@ -434,13 +439,17 @@ abstract class ScrollAxisBase { void getOriginAndCornerOfBodyRegion(double origin, double corner) { int scrollLineIndex = 0; double scrollOffset = 0.0; - final List lineInfo = - getScrollLineIndex(scrollLineIndex, scrollOffset); + final List lineInfo = getScrollLineIndex( + scrollLineIndex, + scrollOffset, + ); scrollLineIndex = lineInfo[0] as int; scrollOffset = lineInfo[1] as double; final double arrangeSize = renderSize; - final double adjustedFooterExtent = - adjustFooterExtentToAvoidGap(footerExtent, arrangeSize); + final double adjustedFooterExtent = adjustFooterExtentToAvoidGap( + footerExtent, + arrangeSize, + ); origin = headerExtent - scrollOffset; corner = arrangeSize - adjustedFooterExtent; } @@ -457,8 +466,11 @@ abstract class ScrollAxisBase { /// * scrollLineOffset The scroll line offset. /// * isRightToLeft The boolean value used to calculate visible columns /// in right to left mode. - List getScrollLineIndex(int scrollLineIndex, double scrollLineOffset, - [bool isRightToLeft = false]); + List getScrollLineIndex( + int scrollLineIndex, + double scrollLineOffset, [ + bool isRightToLeft = false, + ]); /// Gets the maximum range /// @@ -502,8 +514,10 @@ abstract class ScrollAxisBase { int scrollLineIndex = 0; double scrollOffset = 0.0; final int headerLineCount = this.headerLineCount; - final List scrollLineValues = - getScrollLineIndex(scrollLineIndex, scrollOffset); + final List scrollLineValues = getScrollLineIndex( + scrollLineIndex, + scrollOffset, + ); scrollLineIndex = scrollLineValues[0] as int; scrollOffset = scrollLineValues[1] as double; final int firstFooterLine = lineCount - footerLineCount; @@ -513,15 +527,24 @@ abstract class ScrollAxisBase { // Header double point = 0; int lastHeaderLineIndex = -1; - for (index = 0; - index != -1 && - strictlyLessThan(point, headerExtent) && - index < firstFooterLine && - index < headerLineCount; - index = getNextScrollLineIndex(index)) { + for ( + index = 0; + index != -1 && + strictlyLessThan(point, headerExtent) && + index < firstFooterLine && + index < headerLineCount; + index = getNextScrollLineIndex(index) + ) { final double size = getLineSize(index); final VisibleLineInfo line = VisibleLineInfo( - visibleIndex++, index, size, point, 0, true, false); + visibleIndex++, + index, + size, + point, + 0, + true, + false, + ); _visibleLines.add(line); point += size; lastHeaderLineIndex = index; @@ -532,22 +555,29 @@ abstract class ScrollAxisBase { VisibleLineInfo? lastScrollableLine; // Body point = headerExtent; - final int firstBodyLineIndex = - max(scrollLineIndex, lastHeaderLineIndex + 1); - for (index = firstBodyLineIndex; - index != -1 && - strictlyLessThan(point, footerStartPoint) && - index < firstFooterLine; - index = getNextScrollLineIndex(index)) { + final int firstBodyLineIndex = max( + scrollLineIndex, + lastHeaderLineIndex + 1, + ); + for ( + index = firstBodyLineIndex; + index != -1 && + strictlyLessThan(point, footerStartPoint) && + index < firstFooterLine; + index = getNextScrollLineIndex(index) + ) { final double size = getLineSize(index); - _visibleLines.add(lastScrollableLine = VisibleLineInfo( + _visibleLines.add( + lastScrollableLine = VisibleLineInfo( visibleIndex++, index, size, point, scrollOffset, false, - false)); + false, + ), + ); point += size - scrollOffset; scrollOffset = 0; // reset scrollOffset after first line. // Subsequent lines will start at given point. @@ -565,11 +595,13 @@ abstract class ScrollAxisBase { // Footer point = max(headerExtent, viewSize - footerExtent); - for (index = firstFooterLine; - index != -1 && - strictlyLessThan(point, renderSize) && - index < lineCount; - index = getNextScrollLineIndex(index)) { + for ( + index = firstFooterLine; + index != -1 && + strictlyLessThan(point, renderSize) && + index < lineCount; + index = getNextScrollLineIndex(index) + ) { if (lastScrollableLine != null) { lastScrollableLine.clippedCornerExtent = lastScrollableLine.corner - point; @@ -577,8 +609,17 @@ abstract class ScrollAxisBase { } final double size = getLineSize(index); - _visibleLines.add(VisibleLineInfo( - visibleIndex++, index, size, point, 0, false, true)); + _visibleLines.add( + VisibleLineInfo( + visibleIndex++, + index, + size, + point, + 0, + false, + true, + ), + ); point += size; } @@ -632,9 +673,12 @@ abstract class ScrollAxisBase { int visibleIndex = 0; int scrollLineIndex = 0; double scrollOffset = 0.0; - final int _headerLineCount = headerLineCount; - final List scrollLineValues = - getScrollLineIndex(scrollLineIndex, scrollOffset, true); + final int headerLineCounts = headerLineCount; + final List scrollLineValues = getScrollLineIndex( + scrollLineIndex, + scrollOffset, + true, + ); scrollLineIndex = scrollLineValues[0] as int; scrollOffset = scrollLineValues[1] as double; final int firstFooterLine = lineCount - footerLineCount; @@ -644,15 +688,26 @@ abstract class ScrollAxisBase { // Header double point = 0; int lastHeaderLineIndex = -1; - for (index = 0; - index != -1 && - strictlyLessThan(point, headerExtent) && - index < firstFooterLine && - index < _headerLineCount; - index = getNextScrollLineIndex(index)) { + for ( + index = 0; + index != -1 && + strictlyLessThan(point, headerExtent) && + index < firstFooterLine && + index < headerLineCounts; + index = getNextScrollLineIndex(index) + ) { final double size = getLineSize(index); - _visibleLines.add(VisibleLineInfo(visibleIndex++, index, size, - footerStartPoint - size - point, 0, true, false)); + _visibleLines.add( + VisibleLineInfo( + visibleIndex++, + index, + size, + footerStartPoint - size - point, + 0, + true, + false, + ), + ); footerStartPoint -= size; lastHeaderLineIndex = index; } @@ -662,8 +717,10 @@ abstract class ScrollAxisBase { // Body point = headerExtent; - int firstBodyLineIndex = - max(scrollLineIndex, lastHeaderLineIndex + 1); + int firstBodyLineIndex = max( + scrollLineIndex, + lastHeaderLineIndex + 1, + ); point = footerStartPoint - clip.start; @@ -675,22 +732,29 @@ abstract class ScrollAxisBase { } firstBodyLineIndex = max(scrollLineIndex, lastHeaderLineIndex + 1); - for (index = firstBodyLineIndex; - index != -1 && - strictlyLessThan( - point - 1, renderSize + clip.start - headerExtent) && - (point > footerExtent) && - (index < firstFooterLine && index >= headerLineCount); - index = getNextScrollLineIndex(index)) { + for ( + index = firstBodyLineIndex; + index != -1 && + strictlyLessThan( + point - 1, + renderSize + clip.start - headerExtent, + ) && + (point > footerExtent) && + (index < firstFooterLine && index >= headerLineCount); + index = getNextScrollLineIndex(index) + ) { final double size = getLineSize(index); - _visibleLines.add(lastScrollableLine = VisibleLineInfo( + _visibleLines.add( + lastScrollableLine = VisibleLineInfo( visibleIndex++, index, size, point - size + scrollOffset, scrollOffset, false, - false)); + false, + ), + ); point -= size - scrollOffset; scrollOffset = 0; } @@ -707,17 +771,22 @@ abstract class ScrollAxisBase { if (footerLineCount > 0) { 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 - ? clip.start + footerExtent - : renderSize - headerExtent) && - index < lineCount; - index = getNextScrollLineIndex(index)) { + point = min( + renderSize + clip.start - headerExtent, + clip.start + footerExtent, + ); + for ( + index = firstFooterLine; + index != -1 && + strictlyLessThan( + point - 1, + renderSize < scrollBar!.maximum + ? clip.start + footerExtent + : renderSize - headerExtent, + ) && + index < lineCount; + index = getNextScrollLineIndex(index) + ) { if (lastScrollableLine != null) { lastScrollableLine.clippedCornerExtent = lastScrollableLine.corner - point; @@ -725,20 +794,32 @@ abstract class ScrollAxisBase { } final double size = getLineSize(index); - _visibleLines.add(VisibleLineInfo( - visibleIndex++, index, size, point - size, 0, false, true)); + _visibleLines.add( + VisibleLineInfo( + visibleIndex++, + index, + size, + point - size, + 0, + false, + true, + ), + ); point -= size; } } else { - for (index = firstFooterLine; - index != -1 && - strictlyLessThan( - point - 1, - renderSize < scrollBar!.maximum - ? clip.start + footerExtent - : renderSize - headerExtent) && - index < lineCount; - index = getNextScrollLineIndex(index)) { + for ( + index = firstFooterLine; + index != -1 && + strictlyLessThan( + point - 1, + renderSize < scrollBar!.maximum + ? clip.start + footerExtent + : renderSize - headerExtent, + ) && + index < lineCount; + index = getNextScrollLineIndex(index) + ) { if (lastScrollableLine != null) { lastScrollableLine.clippedCornerExtent = lastScrollableLine.corner - point; @@ -746,8 +827,17 @@ abstract class ScrollAxisBase { } final double size = getLineSize(index); - _visibleLines.add(VisibleLineInfo( - visibleIndex++, index, size, point - size, 0, false, true)); + _visibleLines.add( + VisibleLineInfo( + visibleIndex++, + index, + size, + point - size, + 0, + false, + true, + ), + ); point -= size; } } @@ -794,15 +884,19 @@ 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, - [bool allowOutSideLines = false, bool isRightToLeft = false]) { + VisibleLineInfo? getVisibleLineAtPoint( + double point, [ + bool allowOutSideLines = false, + bool isRightToLeft = false, + ]) { if (!isRightToLeft) { if (allowOutSideLines) { point = max(point, 0); } - final VisibleLineInfo? lineInfo = - getVisibleLines().getVisibleLineAtPoint(point); + final VisibleLineInfo? lineInfo = getVisibleLines().getVisibleLineAtPoint( + point, + ); if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { return lineInfo; } @@ -813,8 +907,8 @@ abstract class ScrollAxisBase { final VisibleLinesCollection collection = getVisibleLines(true); final VisibleLinesCollection reversedCollection = collection.reversed; - final VisibleLineInfo? lineInfo = - reversedCollection.getVisibleLineAtPoint(point); + final VisibleLineInfo? lineInfo = reversedCollection + .getVisibleLineAtPoint(point); if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { return lineInfo; } @@ -830,9 +924,10 @@ abstract class ScrollAxisBase { /// The visible line that displays the line with the given /// absolute line index. - VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex, - {bool isRightToLeft = false}) => - getVisibleLines(isRightToLeft).getVisibleLineAtLineIndex(lineIndex); + VisibleLineInfo? getVisibleLineAtLineIndex( + int lineIndex, { + bool isRightToLeft = false, + }) => getVisibleLines(isRightToLeft).getVisibleLineAtLineIndex(lineIndex); /// Gets the visible line that displays the line with the given absolute /// line index. If the line is outside the view and you specify @@ -846,12 +941,21 @@ abstract class ScrollAxisBase { /// Returns the visible line that displays the line with the given /// absolute line index. VisibleLineInfo? getVisibleLineAtLineIndexWithTwoArgs( - int lineIndex, bool allowCreateEmptyLineIfNotVisible) { + int lineIndex, + bool allowCreateEmptyLineIfNotVisible, + ) { VisibleLineInfo? line = getVisibleLineAtLineIndex(lineIndex); if (line == null && allowCreateEmptyLineIfNotVisible) { final double size = getLineSize(lineIndex); line = VisibleLineInfo( - maxvalue, lineIndex, size, renderSize + 1, size, false, false); + maxvalue, + lineIndex, + size, + renderSize + 1, + size, + false, + false, + ); } return line; @@ -864,15 +968,25 @@ abstract class ScrollAxisBase { /// 1 - Body, 2 - Footer /// Returns the first and last VisibleLine.LineIndex for area identified /// by section. - Int32Span getVisibleLinesRange(ScrollAxisRegion section) { + Int32Span? getVisibleLinesRange(ScrollAxisRegion section) { final VisibleLinesCollection visibleLines = getVisibleLines(); int start = 0; int end = 0; final List visibleSection = getVisibleSection(section, start, end); start = visibleSection[0]; end = visibleSection[1]; - return Int32Span( - visibleLines[start].lineIndex, visibleLines[end].lineIndex); + if (start >= 0 && end >= 0) { + if (section == ScrollAxisRegion.footer && + visibleLines.firstFooterVisibleIndex == visibleLines.length) { + return null; + } + + return Int32Span( + visibleLines[start].lineIndex, + visibleLines[end].lineIndex, + ); + } + return null; } /// Return indexes for VisibleLinesCollection for area identified by section. @@ -882,7 +996,10 @@ abstract class ScrollAxisBase { /// * start The start index. /// * end The end index. void getVisibleSectionWithThreeArgs( - ScrollAxisRegion section, int start, int end) { + ScrollAxisRegion section, + int start, + int end, + ) { getVisibleSection(section, start, end); } @@ -907,9 +1024,6 @@ abstract class ScrollAxisBase { start = visibleLines.firstFooterVisibleIndex; end = visibleLines.length - 1; break; - default: - start = end = -1; - break; } return [start, end]; } @@ -924,7 +1038,9 @@ abstract class ScrollAxisBase { /// * lastLine The last visible line. /// Returns the clipping area for the specified visible lines. DoubleSpan getBorderRangeClipPoints( - VisibleLineInfo firstLine, VisibleLineInfo lastLine) { + VisibleLineInfo firstLine, + VisibleLineInfo lastLine, + ) { if (!firstLine.isClippedOrigin && !lastLine.isClippedCorner) { return DoubleSpan(0, renderSize); } @@ -951,10 +1067,16 @@ abstract class ScrollAxisBase { /// in right to left mode. /// Returns the visible line. VisibleLineInfo? getLineNearCorner( - double point, double hitTestPrecision, CornerSide side, - {bool isRightToLeft = false}) => - getLineNearCornerWithFourArgs( - point, hitTestPrecision, CornerSide.both, isRightToLeft); + double point, + double hitTestPrecision, + CornerSide side, { + bool isRightToLeft = false, + }) => getLineNearCornerWithFourArgs( + point, + hitTestPrecision, + CornerSide.both, + isRightToLeft, + ); /// Gets the line near the given corner point. Use this method /// for hit-testing row or column lines for resizing cells. @@ -965,8 +1087,11 @@ abstract class ScrollAxisBase { /// * isRightToLeft The boolean value indicates the right to left mode. /// Returns the visible line. VisibleLineInfo? getLineNearCornerWithFourArgs( - double point, double hitTestPrecision, CornerSide side, - [bool isRightToLeft = false]) { + double point, + double hitTestPrecision, + CornerSide side, [ + bool isRightToLeft = false, + ]) { if (!isRightToLeft) { final VisibleLinesCollection lines = getVisibleLines(); VisibleLineInfo? visibleLine = lines.getVisibleLineAtPoint(point); @@ -1003,8 +1128,11 @@ abstract class ScrollAxisBase { } } else { final VisibleLinesCollection lines = getVisibleLines(true); - VisibleLineInfo? visibleLine = - getVisibleLineAtPoint(point, false, isRightToLeft); + VisibleLineInfo? visibleLine = getVisibleLineAtPoint( + point, + false, + isRightToLeft, + ); if (visibleLine != null) { double d; @@ -1063,13 +1191,14 @@ abstract class ScrollAxisBase { /// * lastLine The last line or null if allowAdjust is false and line /// is not in viewable area. List getLinesAndVisibility( - int firstIndex, - int lastIndex, - bool allowAdjust, - bool firstVisible, - bool lastVisible, - VisibleLineInfo? firstLine, - VisibleLineInfo? lastLine) { + int firstIndex, + int lastIndex, + bool allowAdjust, + bool firstVisible, + bool lastVisible, + VisibleLineInfo? firstLine, + VisibleLineInfo? lastLine, + ) { final VisibleLinesCollection visibleLines = getVisibleLines(); if (firstIndex < 0) { @@ -1081,26 +1210,22 @@ abstract class ScrollAxisBase { firstVisible = false; firstLine = null; } - // Header else if (firstIndex < headerLineCount) { firstVisible = true; firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); } - // Footer else if (firstIndex >= firstFooterLineIndex) { firstVisible = true; firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); } - // After Header and Before Scroll Position else if (firstIndex < scrollLineIndex) { firstVisible = false; firstLine = allowAdjust ? getVisibleLineAtLineIndex(scrollLineIndex) : null; } - // After Scroll Position and Before Footer else if (firstIndex > lastBodyVisibleLineIndex) { firstVisible = false; @@ -1125,19 +1250,16 @@ abstract class ScrollAxisBase { lastVisible = false; lastLine = null; } - // Header else if (lastIndex < headerLineCount) { lastVisible = true; lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); } - // Footer else if (lastIndex >= firstFooterLineIndex) { lastVisible = true; lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); } - // After Header and Before Scroll Position else if (lastIndex < scrollLineIndex) { lastVisible = false; @@ -1155,7 +1277,6 @@ abstract class ScrollAxisBase { } } } - // After Scroll Position and Before Footer else if (lastIndex > lastBodyVisibleLineIndex) { lastVisible = false; @@ -1166,7 +1287,6 @@ abstract class ScrollAxisBase { lastLine = allowAdjust ? lastBodyVisibleLine : null; } } - // Regular line (Body) - Visible and not a Header or Footer. else { lastVisible = true; @@ -1191,8 +1311,18 @@ abstract class ScrollAxisBase { const bool lastVisible = false; VisibleLineInfo? firstLine, lastLine; - getLinesAndVisibility(firstIndex, lastIndex, true, firstVisible, - lastVisible, firstLine, lastLine); + final List values = getLinesAndVisibility( + firstIndex, + lastIndex, + true, + firstVisible, + lastVisible, + firstLine, + lastLine, + ); + firstLine = values[2]; + lastLine = values[3]; + if (firstLine == null || lastLine == null) { return DoubleSpan.empty(); } @@ -1206,8 +1336,10 @@ abstract class ScrollAxisBase { /// * isRightToLeft The boolean value used to calculate visible columns /// in right to left mode. /// Returns the clip points for a region. - DoubleSpan getClipPoints(ScrollAxisRegion region, - {bool isRightToLeft = false}) { + DoubleSpan getClipPoints( + ScrollAxisRegion region, { + bool isRightToLeft = false, + }) { final VisibleLinesCollection lines = getVisibleLines(); int start = -1; int end = -1; @@ -1276,17 +1408,25 @@ abstract class ScrollAxisBase { /// void hiddenRangeChangedCallback( - HiddenRangeChangedArgs hiddenRangeChangedArgs) { - for (int n = hiddenRangeChangedArgs.from; - n <= hiddenRangeChangedArgs.to; - n++) { + HiddenRangeChangedArgs hiddenRangeChangedArgs, + ) { + for ( + int n = hiddenRangeChangedArgs.from; + n <= hiddenRangeChangedArgs.to; + n++ + ) { int repeatSizeCount = 0; - final List hiddenValue = - scrollLinesHost!.getHidden(n, repeatSizeCount); + final List hiddenValue = scrollLinesHost!.getHidden( + n, + repeatSizeCount, + ); final bool hide = hiddenValue[0] as bool; repeatSizeCount = hiddenValue[1] as int; - final int rangeTo = - getRangeToHelper(n, hiddenRangeChangedArgs.to, repeatSizeCount); + final int rangeTo = getRangeToHelper( + n, + hiddenRangeChangedArgs.to, + repeatSizeCount, + ); setLineHiddenState(n, rangeTo, hide); n = rangeTo; } @@ -1354,8 +1494,11 @@ abstract class ScrollAxisBase { void resetLineResize() { const int repeatSizeCount = 0; if (_lineResizeIndex >= 0 && _scrollLinesHost != null) { - setLineSize(_lineResizeIndex, _lineResizeIndex, - getScrollLinesHostSize(_lineResizeIndex, repeatSizeCount)[0]); + setLineSize( + _lineResizeIndex, + _lineResizeIndex, + getScrollLinesHostSize(_lineResizeIndex, repeatSizeCount)[0], + ); } _lineResizeIndex = -1; @@ -1371,14 +1514,22 @@ abstract class ScrollAxisBase { final List lineSize = getScrollLinesHostSize(n, repeatSizeCount); final double size = lineSize[0] as double; repeatSizeCount = lineSize[1] as int; - final int rangeTo = - getRangeToHelper(n, rangeChangedArgs.to, repeatSizeCount); + final int rangeTo = getRangeToHelper( + n, + rangeChangedArgs.to, + repeatSizeCount, + ); setLineSize(n, rangeTo, size); n = rangeTo; } // Also check whether I need to re-hide any of the rows. - hiddenRangeChangedCallback(HiddenRangeChangedArgs.fromArgs( - rangeChangedArgs.from, rangeChangedArgs.to, false)); + hiddenRangeChangedCallback( + HiddenRangeChangedArgs.fromArgs( + rangeChangedArgs.from, + rangeChangedArgs.to, + false, + ), + ); markDirty(); raiseChanged(ScrollChangedAction.lineResized); } @@ -1398,7 +1549,10 @@ abstract class ScrollAxisBase { /// Returns An array with 3 ranges indicating the first and last point /// for the given lines in each region. List rangeToRegionPoints( - int first, int last, bool allowEstimatesForOutOfViewLines); + int first, + int last, + bool allowEstimatesForOutOfViewLines, + ); /// Gets the first and last point for the given lines in a region. /// @@ -1408,16 +1562,21 @@ abstract class ScrollAxisBase { /// * allowEstimatesForOutOfViewLines if set to true allow estimates /// for out of view lines. /// Returns the first and last point for the given lines in a region. - DoubleSpan rangeToPoints(ScrollAxisRegion region, int first, int last, - bool allowEstimatesForOutOfViewLines); + DoubleSpan rangeToPoints( + ScrollAxisRegion region, + int first, + int last, + bool allowEstimatesForOutOfViewLines, + ); /// Raises the `Changed` event. /// /// * action scroll action void raiseChanged(ScrollChangedAction action) { if (onChangedCallback != null) { - final ScrollChangedArgs scrollChangedArgs = - ScrollChangedArgs.fromArgs(action); + final ScrollChangedArgs scrollChangedArgs = ScrollChangedArgs.fromArgs( + action, + ); onChangedCallback!(scrollChangedArgs); } } @@ -1543,13 +1702,17 @@ abstract class ScrollAxisBase { double get viewCorner { int scrollLineIndex = 0; double scrollOffset = 0.0; - final List lineSize = - getScrollLineIndex(scrollLineIndex, scrollOffset); + final List lineSize = getScrollLineIndex( + scrollLineIndex, + scrollOffset, + ); scrollLineIndex = lineSize[0] as int; scrollOffset = lineSize[1] as double; final double arrangeSize = renderSize; - final double adjustedFooterExtent = - adjustFooterExtentToAvoidGap(footerExtent, arrangeSize); + final double adjustedFooterExtent = adjustFooterExtentToAvoidGap( + footerExtent, + arrangeSize, + ); return arrangeSize - adjustedFooterExtent; } @@ -1565,8 +1728,9 @@ abstract class ScrollAxisBase { point = max(point, 0); } - final VisibleLineInfo? line = - getVisibleLines().getVisibleLineAtPoint(point); + final VisibleLineInfo? line = getVisibleLines().getVisibleLineAtPoint( + point, + ); if (line != null && (allowOutsideLines || point <= line.corner)) { return line.lineIndex; } @@ -1618,10 +1782,12 @@ class PixelScrollAxis extends ScrollAxisBase { /// * scrollBar - _required_ - The scrollbar state. /// * scrollLinesHost - _required_ - The scroll lines host. /// * distancesHost - _required_ - The distances host. - PixelScrollAxis(ScrollAxisBase parentScrollAxis, ScrollBarBase scrollBar, - LineSizeHostBase scrollLinesHost, DistancesHostBase distancesHost) - : super(scrollBar, scrollLinesHost, - headerExtent: 0.0, footerExtent: 0.0) { + PixelScrollAxis( + ScrollAxisBase parentScrollAxis, + ScrollBarBase scrollBar, + LineSizeHostBase scrollLinesHost, + DistancesHostBase distancesHost, + ) : super(scrollBar, scrollLinesHost, headerExtent: 0.0, footerExtent: 0.0) { // GridCellGridRenderer passes in Distances. LineSizeCollection holds them. // This allows faster construction of grids when they were scrolled // out of view and unloaded. @@ -1637,14 +1803,17 @@ class PixelScrollAxis extends ScrollAxisBase { /// * scrollBar - _required_ - The scrollbar state. /// * scrollLinesHost - _required_ - The scroll lines host. /// * distancesHost - _required_ - The distances host. - PixelScrollAxis.fromPixelScrollAxis(ScrollBarBase scrollBar, - LineSizeHostBase? scrollLinesHost, DistancesHostBase? distancesHost) - : super(scrollBar, scrollLinesHost) { + PixelScrollAxis.fromPixelScrollAxis( + ScrollBarBase scrollBar, + LineSizeHostBase? scrollLinesHost, + DistancesHostBase? distancesHost, + ) : super(scrollBar, scrollLinesHost) { if (distancesHost != null) { _distancesHost = distancesHost; } else if (scrollLinesHost != null) { - _distances = DistanceRangeCounterCollection() - ..defaultDistance = scrollLinesHost.getDefaultLineSize(); + _distances = + DistanceRangeCounterCollection() + ..defaultDistance = scrollLinesHost.getDefaultLineSize(); scrollLinesHost.initializeScrollAxis(this); } @@ -1704,7 +1873,10 @@ class PixelScrollAxis extends ScrollAxisBase { if (_parentScrollAxis != null) { _parentScrollAxis!.setLineSize( - startLineIndex, startLineIndex, distances?.totalDistance ?? 0); + startLineIndex, + startLineIndex, + distances?.totalDistance ?? 0, + ); } } } @@ -1781,7 +1953,7 @@ class PixelScrollAxis extends ScrollAxisBase { void alignScrollLine() { final double d = distances?.getAlignedScrollValue(scrollBar?.value ?? 0.0) ?? 0.0; - if (!(d == double.nan)) { + if (!d.isNaN) { scrollBar!.value = d; } } @@ -1817,21 +1989,28 @@ class PixelScrollAxis extends ScrollAxisBase { /// * isRightToLeft - _optional_ - The boolean value used to calculate /// visible columns in right to left mode. @override - List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, - [bool isRightToLeft = false]) { + List getScrollLineIndex( + int scrollLineIndex, + double scrollLineDelta, [ + bool isRightToLeft = false, + ]) { if (!isRightToLeft && scrollBar != null && distances != null) { - scrollLineIndex = - max(0, distances!.indexOfCumulatedDistance(scrollBar!.value)); + scrollLineIndex = max( + 0, + distances!.indexOfCumulatedDistance(scrollBar!.value), + ); if (scrollLineIndex >= lineCount) { scrollLineDelta = 0.0; } else { - scrollLineDelta = scrollBar!.value - + scrollLineDelta = + scrollBar!.value - distances!.getCumulatedDistanceAt(scrollLineIndex); } } else { scrollLineIndex = max( - 0, - distances!.indexOfCumulatedDistance(scrollBar!.maximum - + 0, + distances!.indexOfCumulatedDistance( + scrollBar!.maximum - scrollBar!.largeChange - scrollBar!.value + (headerLineCount == 0 ? clip.start : clip.start + headerExtent) + @@ -1840,12 +2019,15 @@ class PixelScrollAxis extends ScrollAxisBase { scrollBar!.maximum - footerExtent - headerExtent - : 0))); + : 0), + ), + ); if (scrollLineIndex >= lineCount) { scrollLineDelta = 0.0; } else { - scrollLineDelta = scrollBar!.maximum - + scrollLineDelta = + scrollBar!.maximum - scrollBar!.largeChange - scrollBar!.value + (headerLineCount == 0 @@ -1934,7 +2116,12 @@ class PixelScrollAxis extends ScrollAxisBase { @override void onLinesInserted(int insertAt, int count) { if (distances != null) { - DistancesUtil.onInserted(distances!, scrollLinesHost!, insertAt, count); + DistancesUtil.instance.onInserted( + distances!, + scrollLinesHost!, + insertAt, + count, + ); } } @@ -1950,12 +2137,16 @@ class PixelScrollAxis extends ScrollAxisBase { /// the given lines in each region. @override List rangeToRegionPoints( - int first, int last, bool allowEstimatesForOutOfViewLines) { + int first, + int last, + bool allowEstimatesForOutOfViewLines, + ) { double p1, p2; p1 = distances!.getCumulatedDistanceAt(first); - p2 = last >= distances!.count - 1 - ? distances!.totalDistance - : distances!.getCumulatedDistanceAt(last + 1); + p2 = + last >= distances!.count - 1 + ? distances!.totalDistance + : distances!.getCumulatedDistanceAt(last + 1); final List result = []; for (int n = 0; n < 3; n++) { @@ -1994,8 +2185,12 @@ class PixelScrollAxis extends ScrollAxisBase { /// /// Returns the first and last point for the given lines in a region. @override - DoubleSpan rangeToPoints(ScrollAxisRegion region, int first, int last, - bool allowEstimatesForOutOfViewLines) { + DoubleSpan rangeToPoints( + ScrollAxisRegion region, + int first, + int last, + bool allowEstimatesForOutOfViewLines, + ) { final VisibleLinesCollection lines = getVisibleLines(); // If line is visible use already calculated values, @@ -2004,20 +2199,25 @@ class PixelScrollAxis extends ScrollAxisBase { final VisibleLineInfo? line2 = lines.getVisibleLineAtLineIndex(last); double p1, p2; - p1 = line1 == null - ? distances!.getCumulatedDistanceAt(first) - : getCumulatedOrigin(line1); + p1 = + line1 == null + ? distances!.getCumulatedDistanceAt(first) + : getCumulatedOrigin(line1); - p2 = line2 == null - ? distances!.getCumulatedDistanceAt(last + 1) - : getCumulatedCorner(line2); + p2 = + line2 == null + ? distances!.getCumulatedDistanceAt(last + 1) + : getCumulatedCorner(line2); return rangeToPointsHelper(region, p1, p2); } /// DoubleSpan rangeToPointsHelper( - ScrollAxisRegion region, double p1, double p2) { + ScrollAxisRegion region, + double p1, + double p2, + ) { final VisibleLinesCollection lines = getVisibleLines(); DoubleSpan doubleSpan = DoubleSpan.empty(); switch (region) { @@ -2040,9 +2240,6 @@ class PixelScrollAxis extends ScrollAxisBase { p2 += headerExtent - scrollBar!.value; doubleSpan = DoubleSpan(p1, p2); break; - default: - doubleSpan = DoubleSpan.empty(); - break; } return doubleSpan; @@ -2063,8 +2260,10 @@ class PixelScrollAxis extends ScrollAxisBase { /// 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(); } @@ -2072,10 +2271,11 @@ class PixelScrollAxis extends ScrollAxisBase { @override void scrollToNextLine() { final double d = distances!.getNextScrollValue(scrollBar!.value); - if (!(d == double.nan)) { - scrollBar!.value = d <= scrollBar!.value - ? distances!.getNextScrollValue(scrollBar!.value + 1) - : d; + if (!d.isNaN) { + scrollBar!.value = + d <= scrollBar!.value + ? distances!.getNextScrollValue(scrollBar!.value + 1) + : d; } else { scrollBar!.value += scrollBar!.smallChange; } @@ -2085,7 +2285,7 @@ class PixelScrollAxis extends ScrollAxisBase { @override void scrollToPreviousLine() { final double d = distances!.getPreviousScrollValue(scrollBar!.value); - if (!(d == double.nan)) { + if (!d.isNaN) { scrollBar!.value = d; } } @@ -2093,8 +2293,10 @@ class PixelScrollAxis extends ScrollAxisBase { /// 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(); } @@ -2166,10 +2368,11 @@ class PixelScrollAxis extends ScrollAxisBase { if (!isDistanceCounterSubset) { // Nested Grid cells in GridControl is not // DistanceRangeCounterCollection type. - final DistanceRangeCounterCollection _distances = + final DistanceRangeCounterCollection distance = distances! as DistanceRangeCounterCollection; - super.footerExtent = distances!.totalDistance - - _distances.paddingDistance - + super.footerExtent = + distances!.totalDistance - + distance.paddingDistance - distances!.getCumulatedDistanceAt(n); } else { super.footerExtent = @@ -2183,8 +2386,9 @@ class PixelScrollAxis extends ScrollAxisBase { /// * value - _required_ - The value. @override void setHeaderLineCount(int value) { - super.headerExtent = - distances!.getCumulatedDistanceAt(min(value, distances!.count)); + super.headerExtent = distances!.getCumulatedDistanceAt( + min(value, distances!.count), + ); } /// Sets the hidden state of the lines. @@ -2200,8 +2404,10 @@ class PixelScrollAxis extends ScrollAxisBase { } else { for (int n = from; n <= to; n++) { int repeatSizeCount = -1; - final List lineSize = - getLineSizeWithTwoArgs(n, repeatSizeCount); + final List lineSize = getLineSizeWithTwoArgs( + n, + repeatSizeCount, + ); final double size = lineSize[0] as double; repeatSizeCount = lineSize[1] as int; final int rangeTo = getRangeToHelper(n, to, repeatSizeCount); @@ -2222,7 +2428,6 @@ class PixelScrollAxis extends ScrollAxisBase { if (_distances != null) { _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. @@ -2247,8 +2452,10 @@ class PixelScrollAxis extends ScrollAxisBase { } if (_parentScrollAxis != null) { - _parentScrollAxis! - .setLineResize(startLineIndex, distances!.totalDistance); + _parentScrollAxis!.setLineResize( + startLineIndex, + distances!.totalDistance, + ); } inLineResize = false; @@ -2288,7 +2495,7 @@ class PixelScrollAxis extends ScrollAxisBase { // SH 6/22 - Commented out change to sb.Value and also modified // ScrollInfo.Value to return Math.Max(minimum, // Math.Min(maximum - largeChange, value)); instead. - if (proposeLargeChange >= 0 && delta != 0) { + if (proposeLargeChange >= 0 && delta != 0.0) { sb.value = oldValue + delta; } } @@ -2311,14 +2518,17 @@ class LineScrollAxis extends ScrollAxisBase { /// * scrollBar - _required_ - The state of the scrollbar. /// * scrollLinesHost - _required_ - The scroll lines host. LineScrollAxis(ScrollBarBase scrollBar, LineSizeHostBase scrollLinesHost) - : super(scrollBar, scrollLinesHost, - headerExtent: 0.0, - footerExtent: 0.0, - defaultLineSize: 0.0, - viewSize: 0.0) { - final Object _distancesHost = scrollLinesHost; - if (_distancesHost is DistancesHostBase) { - _distances = _distancesHost.distances; + : super( + scrollBar, + scrollLinesHost, + headerExtent: 0.0, + footerExtent: 0.0, + defaultLineSize: 0.0, + viewSize: 0.0, + ) { + final Object distancesHost = scrollLinesHost; + if (distancesHost is DistancesHostBase) { + _distances = distancesHost.distances; } else { _distances = DistanceRangeCounterCollection(); } @@ -2459,13 +2669,18 @@ class LineScrollAxis extends ScrollAxisBase { int repeatSizeCount = -1; for (int index = 0; index < lineCount; index++) { - final List hiddenValue = - scrollLinesHost!.getHidden(index, repeatSizeCount); + final List hiddenValue = scrollLinesHost!.getHidden( + index, + repeatSizeCount, + ); final bool hide = hiddenValue[0] as bool; repeatSizeCount = hiddenValue[1] as int; final double value = hide == true ? 0.0 : 1.0; - final int rangeTo = - getRangeToHelper(index, lineCount - 1, repeatSizeCount); + final int rangeTo = getRangeToHelper( + index, + lineCount - 1, + repeatSizeCount, + ); _distances!.setRange(index, rangeTo, value); index = rangeTo; } @@ -2508,8 +2723,11 @@ class LineScrollAxis extends ScrollAxisBase { /// * isRightToLeft - _required_ - The boolean value used to calculate visible /// columns in right to left mode. @override - List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, - [bool isRightToLeft = false]) { + List getScrollLineIndex( + int scrollLineIndex, + double scrollLineDelta, [ + bool isRightToLeft = false, + ]) { scrollLineIndex = scrollBarValueToLineIndex(scrollBar!.value); scrollLineDelta = 0.0; return [scrollLineIndex, scrollLineDelta]; @@ -2536,8 +2754,10 @@ class LineScrollAxis extends ScrollAxisBase { final int to = insertAt + count - 1; int repeatSizeCount = -1; for (int index = insertAt; index <= to; index++) { - final List hiddenValue = - scrollLinesHost!.getHidden(index, repeatSizeCount); + final List hiddenValue = scrollLinesHost!.getHidden( + index, + repeatSizeCount, + ); final bool hide = hiddenValue[0] as bool; repeatSizeCount = hiddenValue[1] as int; final double value = hide == true ? 0.0 : 1.0; @@ -2557,14 +2777,25 @@ class LineScrollAxis extends ScrollAxisBase { /// /// Returns the first and last point for the given lines in a region. @override - DoubleSpan rangeToPoints(ScrollAxisRegion region, int first, int last, - bool allowEstimatesForOutOfViewLines) { + DoubleSpan rangeToPoints( + ScrollAxisRegion region, + int first, + int last, + bool allowEstimatesForOutOfViewLines, + ) { bool firstVisible = false; bool lastVisible = false; VisibleLineInfo? firstLine, lastLine; final List lineValues = getLinesAndVisibility( - first, last, true, firstVisible, lastVisible, firstLine, lastLine); + first, + last, + true, + firstVisible, + lastVisible, + firstLine, + lastLine, + ); firstVisible = lineValues[0] as bool; lastVisible = lineValues[1] as bool; firstLine = lineValues[2] as VisibleLineInfo?; @@ -2674,7 +2905,10 @@ class LineScrollAxis extends ScrollAxisBase { /// for the given lines in each region. @override List rangeToRegionPoints( - int first, int last, bool allowEstimatesForOutOfViewLines) { + int first, + int last, + bool allowEstimatesForOutOfViewLines, + ) { final List result = []; for (int n = 0; n < 3; n++) { late ScrollAxisRegion region; @@ -2686,8 +2920,10 @@ class LineScrollAxis extends ScrollAxisBase { region = ScrollAxisRegion.footer; } - result.insert(n, - rangeToPoints(region, first, last, allowEstimatesForOutOfViewLines)); + result.insert( + n, + rangeToPoints(region, first, last, allowEstimatesForOutOfViewLines), + ); } return result; @@ -2726,7 +2962,9 @@ class LineScrollAxis extends ScrollAxisBase { if (visibleScrollIndex > scrollBar!.value) { final int scrollIndexLinex = intPreviousPageLineIndex( - scrollPageSize - getLineSize(lineIndex), lineIndex); + scrollPageSize - getLineSize(lineIndex), + lineIndex, + ); visibleScrollIndex = lineIndexToScrollBarValue(scrollIndexLinex); } @@ -2780,8 +3018,10 @@ class LineScrollAxis extends ScrollAxisBase { /// * scrollLineDelta - _required_ - The scroll line delta. @override void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { - scrollLineIndex = - min(max(_distances!.count - 1, 0), max(0, scrollLineIndex)); + scrollLineIndex = min( + max(_distances!.count - 1, 0), + max(0, scrollLineIndex), + ); scrollBar!.value = lineIndexToScrollBarValue(scrollLineIndex); resetVisibleLines(); } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart index 1e7b2a4f4..f3ef60523 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart @@ -8,29 +8,29 @@ import 'event_args.dart'; typedef ValueChangedCallback = void Function(); /// Returns the ValueChangingArgs by used the [onValueChanging] event. -typedef ValueChangingCallback = void Function( - ValueChangingArgs valueChangingArgs); +typedef ValueChangingCallback = + void Function(ValueChangingArgs valueChangingArgs); /// Returns the PropertyChangedArgs by used the [onPropertyChanged] event. -typedef PropertyChangedCallback = void Function( - PropertyChangedArgs propertyChangedArgs); +typedef PropertyChangedCallback = + void Function(PropertyChangedArgs propertyChangedArgs); /// Defines an interface that provides all properties to configure a scrollbar. class ScrollBarBase extends ChangeNotifier { /// - ScrollBarBase( - {required bool enabled, - required double maximum, - required double minimum, - required double largeChange, - required double smallChange, - required double value}) - : _enabled = enabled, - _maximum = maximum, - _minimum = minimum, - _largeChange = largeChange, - _smallChange = smallChange, - _value = value; + ScrollBarBase({ + required bool enabled, + required double maximum, + required double minimum, + required double largeChange, + required double smallChange, + required double value, + }) : _enabled = enabled, + _maximum = maximum, + _minimum = minimum, + _largeChange = largeChange, + _smallChange = smallChange, + _value = value; /// Gets a value indicating whether the scroll bar is enabled or not. /// @@ -123,13 +123,14 @@ class ScrollBarBase extends ChangeNotifier { class ScrollInfo extends ScrollBarBase { ///Initializes a new instance of the [ScrollInfo] class. ScrollInfo() - : super( - value: 0, - minimum: 0, - maximum: 100, - smallChange: 1, - enabled: true, - largeChange: 10) { + : super( + value: 0, + minimum: 0, + maximum: 100, + smallChange: 1, + enabled: true, + largeChange: 10, + ) { _proposedLargeChange = 10; } @@ -254,8 +255,9 @@ class ScrollInfo extends ScrollBarBase { /// * propertyName - _required_ - Name of the property. void onPropertyChanged(String propertyName) { if (onPropertyChangedEvent != null) { - final PropertyChangedArgs propertyChangedArgs = - PropertyChangedArgs(propertyName); + final PropertyChangedArgs propertyChangedArgs = PropertyChangedArgs( + propertyName, + ); onPropertyChangedEvent!(propertyChangedArgs); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart index 8d81bf136..1336a7c0b 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart @@ -565,7 +565,6 @@ class TreeTableEntry extends TreeTableNode with TreeTableEntryBase { /// * tree - _required_ - Tree table instance /// /// Returns the instance of newly created branch - @override TreeTableBranchBase? createBranch(TreeTable tree) => TreeTableBranch(tree); @@ -642,7 +641,6 @@ class TreeTableBase extends ListBase { bool _sorted = false; /// Gets the root node. - /// Gets a value indicating whether the tree was initialize or not. bool get isInitializing => _isInitializing; late bool _isInitializing; @@ -804,12 +802,12 @@ class TreeTable extends TreeTableBase { throw Exception(); } - final Object? _left = branch.parent?.left; + final Object? left = branch.parent?.left; if (!(branch.parent == null || (branch.parent != null && branch.parent!.left != null && branch.parent!.left!.isEntry()) || - (_left is TreeTableBranch && _left.right != branch))) { + (left is TreeTableBranch && left.right != branch))) { throw Exception(); } } @@ -840,7 +838,9 @@ class TreeTable extends TreeTableBase { /// /// Returns the instance for the tree TreeTableEntryBase? addIfNotExists( - Comparable? key, TreeTableEntryBase? value) { + Comparable? key, + TreeTableEntryBase? value, + ) { if (!sorted) { throw Exception('This tree is not sorted.'); } @@ -876,8 +876,9 @@ class TreeTable extends TreeTableBase { if (cmp == 0) { current = branch.right; while (current != null && !current.isEntry()) { - final TreeTableBranchBase _current = current as TreeTableBranchBase; - current = _current.left; + final TreeTableBranchBase tableBranchBase = + current as TreeTableBranchBase; + current = tableBranchBase.left; } return current! as TreeTableEntryBase; @@ -962,9 +963,9 @@ class TreeTable extends TreeTableBase { final dynamic right = branch.right!.getMinimum(); cmp = Comparable.compare(minimum, right); } else if (value.getMinimum() is Comparable) { - final Object? _minimum = value.getMinimum(); - if (_minimum != null && _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; } @@ -990,9 +991,9 @@ class TreeTable extends TreeTableBase { cmp = Comparable.compare(minimum, sortKey); } } else if (value.getMinimum() is Comparable) { - final Object? _minimum = value.getMinimum(); - if (_minimum != null && _minimum is Comparable) { - cmp = _minimum.compareTo(leaf.getSortKey()); + final Object? minimum = value.getMinimum(); + if (minimum != null && minimum is Comparable) { + cmp = minimum.compareTo(leaf.getSortKey()); } else { cmp = null; } @@ -1025,7 +1026,10 @@ class TreeTable extends TreeTableBase { /// TreeTableEntryBase? cacheLastFoundEntry( - TreeTableEntryBase? entry, Object? key, bool highestSmallerValue) { + TreeTableEntryBase? entry, + Object? key, + bool highestSmallerValue, + ) { lastIndex = -1; _lastFoundEntry = entry; _lastFoundEntryKey = key; @@ -1063,19 +1067,22 @@ class TreeTable extends TreeTableBase { } /// - void deleteFixup(TreeTableBranchBase? x, bool isLeft) { + void deleteFixup(TreeTableBranchBase? x) { const bool inAddMode = false; while (x != null && + x.parent != null && !referenceEquals(x, _root) && x._color == TreeTableNodeColor.black) { - if (isLeft) { + if (referenceEquals(x.parent!.left, x)) { TreeTableNodeBase? w = x.parent?.right; if (w != null && w.color == TreeTableNodeColor.red) { w.color = TreeTableNodeColor.black; - x.parent?.color = TreeTableNodeColor.black; + x.parent?.color = TreeTableNodeColor.red; leftRotate(x.parent, inAddMode); if (x.parent != null) { - w = x.parent!.right! as TreeTableBranchBase; + w = x.parent!.right; + } else { + return; } } @@ -1084,44 +1091,39 @@ class TreeTable extends TreeTableBase { } 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; + x = x.parent; + } else { + if (w is TreeTableBranchBase && + (w.right!.isEntry() || + w.getRightBranch()!.color == TreeTableNodeColor.black)) { + w.left?.color = TreeTableNodeColor.black; + w.color = TreeTableNodeColor.red; + rightRotate(w, inAddMode); + if (x.parent != null) { + w = x.parent!.right; + } else { + break; + } + } + if (w == null) { return; - } else { - isLeft = x.parent!.left == x; - x = x.parent; } - } 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 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.color = TreeTableNodeColor.red; - + if (w is TreeTableBranchBase) { + w.color = x.parent!.color; + w.right?.color = TreeTableNodeColor.black; + } + x.parent?.color = TreeTableNodeColor.black; leftRotate(x.parent, inAddMode); - w.color = x.parent!.color; - x.parent!.color = w.color; - return; - } else { - return; + if (_root is TreeTableBranchBase && _root != null) { + x = _root! as TreeTableBranchBase; + } else { + return; + } } } else { TreeTableNodeBase? w = x.parent?.left; @@ -1129,7 +1131,11 @@ class TreeTable extends TreeTableBase { w.color = TreeTableNodeColor.black; x.parent!.color = TreeTableNodeColor.red; rightRotate(x.parent, inAddMode); - w = x.parent!.left; + if (x.parent != null) { + w = x.parent!.left; + } else { + return; + } } if (w == null) { @@ -1137,53 +1143,44 @@ class TreeTable extends TreeTableBase { } 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.getRightBranch()!.color == TreeTableNodeColor.black) && + (w.left!.isEntry() || + w.getLeftBranch()!.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; - x = x.parent; - } + x = x.parent; } else { 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!; - w.color = xParent.color; - xParent.color = t; - return; - } 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.left!.isEntry() || + w.getLeftBranch()!.color == TreeTableNodeColor.black)) { + w.right?.color = TreeTableNodeColor.black; w.color = TreeTableNodeColor.red; - - leftRotate(x.parent, inAddMode); - w.color = xParent.color; - xParent.color = w.color; + leftRotate(w, inAddMode); + if (x.parent != null) { + w = x.parent!.left; + } else { + break; + } + } + if (w == null) { + return; + } + if (w is TreeTableBranchBase) { + w.color = x.parent!.color; + w.left?.color = TreeTableNodeColor.black; + } + x.parent?.color = TreeTableNodeColor.black; + rightRotate(x.parent, inAddMode); + if (_root is TreeTableBranchBase && _root != null) { + x = _root! as TreeTableBranchBase; + } else { return; } } } } - x!.color = TreeTableNodeColor.black; + x?.color = TreeTableNodeColor.black; } /// Finds the node in a sorted tree is just one entry ahead of the @@ -1264,7 +1261,10 @@ class TreeTable extends TreeTableBase { } return cacheLastFoundEntry( - current as TreeTableEntryBase, key, highestSmallerValue); + current as TreeTableEntryBase, + key, + highestSmallerValue, + ); } else if (cmp < 0) { current = branch.left!; lastLeft = branch.left; @@ -1295,12 +1295,16 @@ class TreeTable extends TreeTableBase { } else if (lastLeft != null) { current = lastLeft; while (!current.isEntry()) { - final TreeTableBranchBase _current = current as TreeTableBranchBase; - current = _current.right!; + final TreeTableBranchBase tableBranchBase = + current as TreeTableBranchBase; + current = tableBranchBase.right!; } return cacheLastFoundEntry( - current as TreeTableEntryBase, key, highestSmallerValue); + current as TreeTableEntryBase, + key, + highestSmallerValue, + ); } } @@ -1320,8 +1324,7 @@ class TreeTable extends TreeTableBase { final int treeCount = getCount(); if (index < 0 || index > treeCount) { - throw ArgumentError( - 'index ${index.toString()} must be between 0 and ${treeCount.toString()}'); + throw ArgumentError('index $index must be between 0 and $treeCount'); } if (index == treeCount) { @@ -1398,6 +1401,15 @@ class TreeTable extends TreeTableBase { /// void insertFixup(TreeTableBranchBase? x, bool inAddMode) { + // We set the color of all newly inserted nodes to red, + // except for the root node which is always black + if (x != null) { + if (x.parent == null) { + x.color = TreeTableNodeColor.black; + } else { + x.color = TreeTableNodeColor.red; + } + } // Check Red-Black properties while (x != null && x.parent != null && @@ -1405,46 +1417,89 @@ class TreeTable extends TreeTableBase { x.parent!.color == TreeTableNodeColor.red && x.parent!.parent != null) { // We have a violation - if (x.parent == x.parent!.parent!.left) { - final TreeTableNodeBase? y = x.parent!.parent?.right; - if (y != null && y.color == TreeTableNodeColor.red) { - // uncle is red + if (referenceEquals(x.parent, x.parent!.parent!.left)) { + // Uncle of x + final TreeTableNodeBase? uncle = x.parent!.parent!.right; + if (uncle != null && uncle.color == TreeTableNodeColor.red) { + // If uncle is red, we make the parent and uncle black, and the grandparent red, + // since all newly inserted nodes should be colored red except the root node which is always black. x.parent!.color = TreeTableNodeColor.black; - y.color = TreeTableNodeColor.black; - x.parent!.parent?.color = TreeTableNodeColor.red; + uncle.color = TreeTableNodeColor.black; + x.parent!.parent!.color = TreeTableNodeColor.red; x = x.parent!.parent; } else { // uncle is black - if (x == x.parent!.right) { - // Make x a left child - x = x.parent; - leftRotate(x, inAddMode); + if (referenceEquals(x, x.parent!.right)) { + // If the node is on the right side of the parent, + // we need to perform a rotation to convert it to the case + // where the node is on the left side of the parent. + final TreeTableBranchBase xParent = x.parent!; + final TreeTableBranchBase xGrandparent = xParent.parent!; + leftRotate(xParent, inAddMode); + // Now our node x should be the LEFT child of the grandparent. + assert(referenceEquals(xGrandparent.left, x)); + assert(referenceEquals(x.parent, xGrandparent)); + // Perform a right rotation, making the parent our left child and taking our place + assert(referenceEquals(x.left, xParent)); + assert(referenceEquals(xParent.parent, x)); + x = xParent; } - // Recolor and rotate - x!.parent!.color = TreeTableNodeColor.black; - x.parent!.parent!.color = TreeTableNodeColor.red; - rightRotate(x.parent!.parent, inAddMode); + // If the parent of the inserted node is red, we need to perform a left or right + // rotation followed by a color change to ensure that the tree maintains the red-black property. + // In this case, we color the parent black and the grandparent red, + // then perform a right rotation on the grandparent node. + final TreeTableBranchBase xParent = x.parent!; + final TreeTableBranchBase xGrandparent = xParent.parent!; + xParent.color = TreeTableNodeColor.black; + xGrandparent.color = TreeTableNodeColor.red; + rightRotate(xGrandparent, inAddMode); + assert(referenceEquals(x.parent, xParent)); + assert(referenceEquals(xParent.right, xGrandparent)); + assert(referenceEquals(xGrandparent.parent, xParent)); } } else { // Mirror image of above code - final TreeTableNodeBase? y = x.parent!.parent?.left; - if (y != null && y.color == TreeTableNodeColor.red) { - // uncle is red + // Uncle of x + final TreeTableNodeBase? uncle = x.parent!.parent!.left; + if (uncle != null && uncle.color == TreeTableNodeColor.red) { + // Since the uncle node is red, we need to make the parent and uncle nodes black, + // and the grandparent node red. x.parent!.color = TreeTableNodeColor.black; - y.color = TreeTableNodeColor.black; + uncle.color = TreeTableNodeColor.black; x.parent!.parent!.color = TreeTableNodeColor.red; x = x.parent!.parent; } else { // uncle is black - if (x == x.parent!.left) { - x = x.parent; - rightRotate(x, inAddMode); + if (referenceEquals(x, x.parent!.left)) { + // If the node is on the left of the parent, transform the tree to the case + // where the node is on the right of the parent by performing a right rotation. + final TreeTableBranchBase xParent = x.parent!; + final TreeTableBranchBase xGrandparent = xParent.parent!; + rightRotate(xParent, inAddMode); + // After rotating the parent node to the left, + // the node x is now in the correct position to become the right child of the grandparent node. + assert(referenceEquals(xGrandparent.right, x)); + assert(referenceEquals(x.parent, xGrandparent)); + // Since our parent has become our right child, + // we now take the place of our parent as the left child of the grandparent. + assert(referenceEquals(x.right, xParent)); + assert(referenceEquals(xParent.parent, x)); + x = xParent; } - x!.parent!.color = TreeTableNodeColor.black; - x.parent!.parent!.color = TreeTableNodeColor.red; - leftRotate(x.parent!.parent, inAddMode); + // To maintain the balance of the tree, we need to perform a left rotation on our grandparent + // after coloring our parent black and grandparent red. This ensures that the node we inserted + // (which is now the parent's left child) is properly positioned and does not violate any of + // the red-black tree properties. + final TreeTableBranchBase xParent = x.parent!; + final TreeTableBranchBase xGrandparent = xParent.parent!; + xParent.color = TreeTableNodeColor.black; + xGrandparent.color = TreeTableNodeColor.red; + leftRotate(xGrandparent, inAddMode); + assert(referenceEquals(x.parent, xParent)); + assert(referenceEquals(xParent.left, xGrandparent)); + assert(referenceEquals(xGrandparent.parent, xParent)); } } } @@ -1466,7 +1521,8 @@ class TreeTable extends TreeTableBase { final int treeCount = getCount(); if (index < 0 || index >= treeCount) { throw ArgumentError( - 'index ${index.toString()} must be between 0 and ${(treeCount - 1).toString()}'); + 'index $index must be between 0 and ${treeCount - 1}', + ); } if (_root == null) { @@ -1516,8 +1572,8 @@ class TreeTable extends TreeTableBase { } else { next = parent.left; while (!next!.isEntry()) { - final TreeTableBranchBase _next = next as TreeTableBranchBase; - next = _next.left; + final TreeTableBranchBase tableBranchBase = next as TreeTableBranchBase; + next = tableBranchBase.left; } } @@ -1525,7 +1581,9 @@ class TreeTable extends TreeTableBase { } TreeTableNodeBase _getSisterNode( - TreeTableBranchBase leafsParent, TreeTableNodeBase node) { + TreeTableBranchBase leafsParent, + TreeTableNodeBase node, + ) { final TreeTableNodeBase? sisterNode = referenceEquals(leafsParent.left, node) ? leafsParent.right @@ -1540,21 +1598,35 @@ class TreeTable extends TreeTableBase { return; } - final TreeTableBranchBase y = x.right! as TreeTableBranchBase; - - if (y.left is TreeTableNodeBase) { + // The goal is to replace the node x with its right child y as the new root of the subtree. + // Then, the left child of y becomes the right child of x, and the left child of y will become x. + final TreeTableNodeBase? y = x.right; + if (y != null && y is TreeTableBranchBase) { + final TreeTableNodeBase? yLeft = y.left; + final TreeTableBranchBase? xParent = x.parent; + // In order to perform a rotation on a node, we need to first place its right child y at the new head of the subtree. + // This is necessary to avoid infinite loops in computations that could occur during the setLeft/setRight operations. + // It's important to note that the order of operations is critical for this process to work correctly. + // Once y is in place, we can proceed with the rotation by making y the new root of the subtree, + // placing x as its left child, and making the left child of y the right child of x. y.setLeft(TreeTableEmpty.empty, inAddMode, sorted); - x.setRight(y.left, inAddMode); - if (x.parent != null) { - if (referenceEquals(x, x.parent!.left)) { - x.parent!.setLeft(y, inAddMode, sorted); + if (xParent != null) { + if (referenceEquals(x, xParent.left)) { + xParent.setLeft(y, inAddMode, sorted); } else { - x.parent!.setRight(y, inAddMode); + xParent.setRight(y, inAddMode); } } else { _root = y; + y._parent = null; } + // We want to replace x with y as the root of its subtree, and make x the left child of y. + // First, set y as the new root of the subtree. We must be careful with the order of operations + // to avoid creating a loop. + // Then, set x's right subtree to be y's old left subtree, and make x the left child of y. + x.setRight(TreeTableEmpty.empty, inAddMode); y.setLeft(x, inAddMode, sorted); + x.setRight(yLeft, inAddMode); } } @@ -1603,12 +1675,11 @@ class TreeTable extends TreeTableBase { _root = sisterNode..parent = null; } else { final TreeTableBranchBase leafsParentParent = leafsParent.parent!; - final bool isLeft = leafsParentParent.left == leafsParent; _replaceNode(leafsParentParent, leafsParent, sisterNode, false); if (leafsParent.color == TreeTableNodeColor.black) { leafsParent.parent = leafsParentParent; - deleteFixup(leafsParent, isLeft); + deleteFixup(leafsParent); } } @@ -1627,8 +1698,12 @@ 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 (referenceEquals(branch?.left, oldNode)) { branch?.setLeft(newNode, inAddMode, sorted); @@ -1643,22 +1718,35 @@ class TreeTable extends TreeTableBase { return; } - final TreeTableBranchBase y = x.left! as TreeTableBranchBase; - - final TreeTableNodeBase yRight = y.right!; - y.setRight( - TreeTableEmpty.empty, inAddMode); // make sure Parent is not reset later - x.setLeft(yRight, inAddMode, sorted); - if (x.parent != null) { - if (x == x.parent!.right) { - x.parent!.setRight(y, inAddMode); + // Goal is that y (left child of x) becomes the new root of the subtree + // in the place of x, and that the right child of y becomes the left + // child of x. The right child of y will become x. + + final TreeTableNodeBase? y = x.left; + if (y != null && y is TreeTableBranchBase) { + final TreeTableNodeBase? yRight = y.right; + final TreeTableBranchBase? xParent = x.parent; + // first place y at the new head of the subtree. + // the order is important to avoid infinite loops in computations + // in the setLeft/setRight operations. + y.setRight(TreeTableEmpty.empty, inAddMode); + if (xParent != null) { + if (referenceEquals(x, xParent.right)) { + xParent.setRight(y, inAddMode); + } else { + xParent.setLeft(y, inAddMode, sorted); + } } else { - x.parent!.setLeft(y, inAddMode, sorted); + _root = y; + y._parent = null; } - } else { - _root = y; + // now setup x's left with y's old right subtree, + // and place x as the right subtree of y. careful with the order + // to avoid creating a temporary loop. + x.setLeft(TreeTableEmpty.empty, inAddMode, sorted); + y.setRight(x, inAddMode); + x.setLeft(yRight, inAddMode, sorted); } - y.setRight(x, inAddMode); } /// Sets the node at the specified index. @@ -1957,22 +2045,22 @@ class TreeTableEnumerator implements EnumeratorBase { _cursor = _next; - TreeTableBranchBase? _parent = _cursor!.parent; + TreeTableBranchBase? parent = _cursor!.parent; - if (_parent == null) { + if (parent == null) { _next = null; return true; } else { - if (referenceEquals(_cursor, _parent.left)) { - _next = _parent.right; + if (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 (referenceEquals(parentParent!.right, _parent)) { - _parent = parentParent; + while (referenceEquals(parentParent!.right, parent)) { + parent = parentParent; parentParent = parentParent.parent; if (parentParent == null) { _next = null; @@ -2091,9 +2179,9 @@ class TreeTableEntrySourceCollection extends ListBase { void copyToBase(List? array, int index) { final int count = inner.count; for (int n = 0; n < count; n++) { - final Object _n = [n]; - if (_n is TreeTableEntryBaseSource && array != null) { - array[index + n] = _n; + final Object object = [n]; + if (object is TreeTableEntryBaseSource && array != null) { + array[index + n] = object; } } } @@ -2264,7 +2352,8 @@ class TreeTableEntrySourceCollectionEnumerator implements EnumeratorBase { /// /// * collection - _required_ - Collection value TreeTableEntrySourceCollectionEnumerator( - TreeTableEntrySourceCollection collection) { + TreeTableEntrySourceCollection collection, + ) { inner = TreeTableEnumerator(collection.inner); } @@ -2433,7 +2522,9 @@ class TreeTableWithCounterBranch extends TreeTableWithSummaryBranch if (referenceEquals(node, right)) { return pos.combine( - getLeftNode()?.getCounterTotal(), TreeTableCounterCookies.countAll); + getLeftNode()?.getCounterTotal(), + TreeTableCounterCookies.countAll, + ); } else if (referenceEquals(node, left)) { return pos; } @@ -2462,10 +2553,10 @@ class TreeTableWithCounterBranch extends TreeTableWithSummaryBranch if (tree!.isInitializing) { return null; } else if (counter == null) { - final TreeTableCounterBase? _left = getLeftNode()?.getCounterTotal(); - final TreeTableCounterBase? _right = getRightNode()?.getCounterTotal(); - if (_left != null && _right != null) { - counter = _left.combine(_right, TreeTableCounterCookies.countAll); + final TreeTableCounterBase? left = getLeftNode()?.getCounterTotal(); + final TreeTableCounterBase? right = getRightNode()?.getCounterTotal(); + if (left != null && right != null) { + counter = left.combine(right, TreeTableCounterCookies.countAll); } } @@ -2521,18 +2612,18 @@ class TreeTableWithCounterBranch extends TreeTableWithSummaryBranch if (parent != null) { parent!.invalidateCounterBottomUp(notifyCounterSource); } else if (notifyCounterSource) { - final Object _tree = tree!; - if (_tree is TreeTableWithCounter) { + final Object object = tree!; + if (object is TreeTableWithCounter) { TreeTableCounterSourceBase? tcs; - if (_tree.tag is TreeTableCounterSourceBase) { - tcs = _tree.tag! as TreeTableCounterSourceBase; + if (object.tag is TreeTableCounterSourceBase) { + tcs = object.tag! as TreeTableCounterSourceBase; } if (tcs != null) { tcs.invalidateCounterBottomUp(); } - tcs = _tree.parentCounterSource; + tcs = object.parentCounterSource; if (tcs != null) { tcs.invalidateCounterBottomUp(); } @@ -2656,19 +2747,19 @@ class TreeTableWithCounterEntry extends TreeTableWithSummaryEntryBase if (parent != null) { parent!.invalidateCounterBottomUp(notifyCounterSource); } else if (notifyCounterSource) { - final Object _tree = tree!; - if (_tree is TreeTableWithCounter) { + final Object object = tree!; + if (object is TreeTableWithCounter) { TreeTableCounterSourceBase? tcs; - if (_tree.tag is TreeTableCounterSourceBase) { - tcs = _tree.tag! as TreeTableCounterSourceBase; + if (object.tag is TreeTableCounterSourceBase) { + tcs = object.tag! as TreeTableCounterSourceBase; } if (tcs != null) { tcs.invalidateCounterBottomUp(); } - tcs = _tree.parentCounterSource; + tcs = object.parentCounterSource; if (tcs != null) { tcs.invalidateCounterBottomUp(); } @@ -2698,7 +2789,7 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// * startPosition - _required_ - Sorting position /// * sorted - _required_ - Boolean value TreeTableWithCounter(TreeTableCounterBase startPosition, bool sorted) - : super(sorted) { + : super(sorted) { _startPos = startPosition; } @@ -2727,9 +2818,9 @@ class TreeTableWithCounter extends TreeTableWithSummary { return _startPos; } - final Object? _root = root; - if (_root != null && _root is TreeTableCounterNodeBase) { - return _root.getCounterTotal(); + final Object? object = root; + if (object != null && object is TreeTableCounterNodeBase) { + return object.getCounterTotal(); } else { return null; } @@ -2743,9 +2834,14 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// /// Returns an entry at the specified counter position. TreeTableWithCounterEntry? getEntryAtCounterPosition( - TreeTableCounterBase searchPosition, int cookie) => - getEntryAtCounterPositionWithForParameter( - getStartCounterPosition(), searchPosition, cookie, false); + TreeTableCounterBase searchPosition, + int cookie, + ) => getEntryAtCounterPositionWithForParameter( + getStartCounterPosition(), + searchPosition, + cookie, + false, + ); /// Gets an entry at the specified counter position. A cookie defines the /// type of counter. @@ -2757,11 +2853,15 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// /// Returns an entry at the specified counter position. TreeTableWithCounterEntry? getEntryAtCounterPositionwithThreeParameter( - TreeTableCounterBase searchPosition, - int cookie, - bool preferLeftMost) => - getEntryAtCounterPositionWithForParameter( - getStartCounterPosition(), searchPosition, cookie, preferLeftMost); + TreeTableCounterBase searchPosition, + int cookie, + bool preferLeftMost, + ) => getEntryAtCounterPositionWithForParameter( + getStartCounterPosition(), + searchPosition, + cookie, + preferLeftMost, + ); /// Gets the entry at counter position. /// @@ -2772,10 +2872,11 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// /// Returns an entry at the specified counter position. TreeTableWithCounterEntry? getEntryAtCounterPositionWithForParameter( - TreeTableCounterBase start, - TreeTableCounterBase searchPosition, - int cookie, - bool preferLeftMost) { + TreeTableCounterBase start, + TreeTableCounterBase searchPosition, + int cookie, + bool preferLeftMost, + ) { if (searchPosition.compare(getStartCounterPosition(), cookie) < 0) { throw Exception('SearchPosition'); } @@ -2790,8 +2891,14 @@ class TreeTableWithCounter extends TreeTableWithSummary { // find node final TreeTableNodeBase? currentNode = root; final TreeTableCounterBase currentNodePosition = start; - return getEntryAtCounterPositionWithSixParameter(currentNode!, start, - searchPosition, cookie, preferLeftMost, currentNodePosition); + return getEntryAtCounterPositionWithSixParameter( + currentNode!, + start, + searchPosition, + cookie, + preferLeftMost, + currentNodePosition, + ); } } @@ -2808,12 +2915,13 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// /// Returns the current node. TreeTableWithCounterEntry getEntryAtCounterPositionWithSixParameter( - TreeTableNodeBase currentNode, - TreeTableCounterBase start, - TreeTableCounterBase searchPosition, - int cookie, - bool preferLeftMost, - TreeTableCounterBase? currentNodePosition) { + TreeTableNodeBase currentNode, + TreeTableCounterBase start, + TreeTableCounterBase searchPosition, + int cookie, + bool preferLeftMost, + TreeTableCounterBase? currentNodePosition, + ) { TreeTableWithCounterBranch? savedBranch; currentNodePosition = start; while (!currentNode.isEntry()) { @@ -2821,8 +2929,8 @@ class TreeTableWithCounter extends TreeTableWithSummary { currentNode as TreeTableWithCounterBranch; final TreeTableCounterNodeBase leftB = branch.left! as TreeTableCounterNodeBase; - final TreeTableCounterBase rightNodePosition = - currentNodePosition!.combine(leftB.getCounterTotal(), cookie); + final TreeTableCounterBase rightNodePosition = currentNodePosition! + .combine(leftB.getCounterTotal(), cookie); if (searchPosition.compare(rightNodePosition, cookie) < 0) { currentNode = branch.left!; @@ -2839,12 +2947,13 @@ class TreeTableWithCounter extends TreeTableWithSummary { TreeTableCounterBase? currentNode2Position; final TreeTableNodeBase currentNode2 = getEntryAtCounterPositionWithSixParameter( - branch.left!, - currentNodePosition, - searchPosition, - cookie, - preferLeftMost, - currentNode2Position); + branch.left!, + currentNodePosition, + searchPosition, + cookie, + preferLeftMost, + currentNode2Position, + ); if (rightNodePosition.compare(currentNode2Position, cookie) == 0) { currentNode = currentNode2; currentNodePosition = currentNode2Position; @@ -2872,7 +2981,9 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// Returns the subsequent entry in the collection for which the /// specific counter is not empty. TreeTableEntryBase? getNextNotEmptyCounterEntry( - TreeTableEntryBase current, int cookie) { + TreeTableEntryBase current, + int cookie, + ) { TreeTableBranchBase? parent = current.parent; TreeTableNodeBase? next; @@ -2916,11 +3027,12 @@ class TreeTableWithCounter extends TreeTableWithSummary { // walk down to most left leaf that has visible entries while (!next!.isEntry()) { final TreeTableBranchBase branch = next as TreeTableBranchBase; - final TreeTableCounterNodeBase _left = + final TreeTableCounterNodeBase left = branch.left! as TreeTableCounterNodeBase; - next = !_left.getCounterTotal()!.isEmpty(cookie) - ? branch.left - : branch.right; + next = + !left.getCounterTotal()!.isEmpty(cookie) + ? branch.left + : branch.right; } } @@ -2936,7 +3048,9 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// Returns the previous entry in the collection for which the specific /// counter is not empty. TreeTableEntryBase? getPreviousNotEmptyCounterEntry( - TreeTableEntryBase current, int cookie) { + TreeTableEntryBase current, + int cookie, + ) { TreeTableBranchBase? parent = current.parent; TreeTableNodeBase? next; @@ -2979,11 +3093,12 @@ class TreeTableWithCounter extends TreeTableWithSummary { // walk down to most left leaf that has visible entries while (!next!.isEntry()) { final TreeTableBranchBase branch = next as TreeTableBranchBase; - final TreeTableCounterNodeBase _right = + final TreeTableCounterNodeBase right = branch.right! as TreeTableCounterNodeBase; - next = !_right.getCounterTotal()!.isEmpty(cookie) - ? branch.right - : branch.left; + next = + !right.getCounterTotal()!.isEmpty(cookie) + ? branch.right + : branch.left; } } @@ -2998,9 +3113,12 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// Returns the next entry in the collection for which CountVisible counter /// is not empty. TreeTableWithCounterEntry? getNextVisibleEntry( - TreeTableWithCounterEntry current) { + TreeTableWithCounterEntry current, + ) { final TreeTableEntryBase? nextCounterEntry = getNextNotEmptyCounterEntry( - current, TreeTableCounterCookies.countVisible); + current, + TreeTableCounterCookies.countVisible, + ); if (nextCounterEntry != null) { return nextCounterEntry as TreeTableWithCounterEntry; } @@ -3016,10 +3134,13 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// Returns the previous entry in the collection for which CountVisible /// counter is not empty. TreeTableWithCounterEntry? getPreviousVisibleEntry( - TreeTableWithCounterEntry current) { + TreeTableWithCounterEntry current, + ) { final TreeTableEntryBase? previousCounterEntry = getPreviousNotEmptyCounterEntry( - current, TreeTableCounterCookies.countVisible); + current, + TreeTableCounterCookies.countVisible, + ); if (previousCounterEntry != null) { return previousCounterEntry as TreeTableWithCounterEntry; } @@ -3037,9 +3158,9 @@ class TreeTableWithCounter extends TreeTableWithSummary { /// * notifyCounterSource - _required_ - Boolean value void invalidateCounterTopDown(bool notifyCounterSource) { if (root != null) { - final Object _root = root!; - if (_root is TreeTableCounterNodeBase) { - _root.invalidateCounterTopDown(notifyCounterSource); + final Object object = root!; + if (object is TreeTableCounterNodeBase) { + object.invalidateCounterTopDown(notifyCounterSource); } } } @@ -3155,7 +3276,8 @@ mixin TreeTableSummaryNodeBase on TreeTableNodeBase { /// /// Returns an array of summary objects. List? getSummaries( - TreeTableEmptySummaryArraySourceBase emptySummaries); + TreeTableEmptySummaryArraySourceBase emptySummaries, + ); /// Marks all summaries dirty in this node and child nodes. /// @@ -3181,7 +3303,9 @@ abstract class TreeTableSummaryArraySourceBase { /// /// Returns An array of summary objects. List getSummaries( - TreeTableEmptySummaryArraySourceBase emptySummaries, bool changed); + TreeTableEmptySummaryArraySourceBase emptySummaries, + bool changed, + ); /// Marks all summaries dirty in this object and parent nodes. void invalidateSummariesBottomUp(); @@ -3252,14 +3376,17 @@ class TreeTableWithSummaryBranch extends TreeTableBranch /// Returns an array of summary objects. @override List? getSummaries( - TreeTableEmptySummaryArraySourceBase emptySummaries) { + TreeTableEmptySummaryArraySourceBase emptySummaries, + ) { if (tree!.isInitializing) { return null; } else if (_summaries == null) { - final List? left = - getLeftNode()?.getSummaries(emptySummaries); - final List? right = - getRightNode()?.getSummaries(emptySummaries); + final List? left = getLeftNode()?.getSummaries( + emptySummaries, + ); + final List? right = getRightNode()?.getSummaries( + emptySummaries, + ); if (left != null && right != null) { int reuseLeft = 0; int reuseRight = 0; @@ -3302,9 +3429,9 @@ class TreeTableWithSummaryBranch extends TreeTableBranch parent!.invalidateSummariesBottomUp(notifyParentRecordSource); } else if (notifyParentRecordSource) { if (tree != null && tree!.tag is TreeTableSummaryArraySourceBase) { - final TreeTableSummaryArraySourceBase _treeTag = + final TreeTableSummaryArraySourceBase treeTag = tree!.tag! as TreeTableSummaryArraySourceBase; - _treeTag.invalidateSummariesBottomUp(); + treeTag.invalidateSummariesBottomUp(); } } } @@ -3376,14 +3503,17 @@ class TreeTableWithSummaryEntryBase extends TreeTableEntry /// /// Returns an array of summary objects. List? onGetSummaries( - TreeTableEmptySummaryArraySourceBase emptySummaries) { + TreeTableEmptySummaryArraySourceBase emptySummaries, + ) { List? summaries; final TreeTableSummaryArraySourceBase? summaryArraySource = getSummaryArraySource(); if (summaryArraySource != null) { const bool summaryChanged = false; - summaries = - summaryArraySource.getSummaries(emptySummaries, summaryChanged); + summaries = summaryArraySource.getSummaries( + emptySummaries, + summaryChanged, + ); } return summaries; @@ -3397,9 +3527,9 @@ class TreeTableWithSummaryEntryBase extends TreeTableEntry /// Returns an instance for newly created TreeTable @override TreeTableBranchBase? createBranch(TreeTable tree) { - final Object _tree = tree; - if (_tree is TreeTableWithSummaryBranch) { - return _tree; + final Object object = tree; + if (object is TreeTableWithSummaryBranch) { + return object; } else { return null; } @@ -3412,8 +3542,8 @@ class TreeTableWithSummaryEntryBase extends TreeTableEntry /// Returns an array of summary objects. @override List getSummaries( - TreeTableEmptySummaryArraySourceBase emptySummaries) => - _summaries ??= onGetSummaries(emptySummaries) ?? emptySummaryArray; + TreeTableEmptySummaryArraySourceBase emptySummaries, + ) => _summaries ??= onGetSummaries(emptySummaries) ?? emptySummaryArray; /// Walks up parent branches and reset summaries. /// @@ -3422,18 +3552,18 @@ class TreeTableWithSummaryEntryBase extends TreeTableEntry void invalidateSummariesBottomUp(bool notifyParentRecordSource) { _summaries = null; if (value is TreeTableSummaryArraySourceBase && tree != null) { - final TreeTableSummaryArraySourceBase _tree = + final TreeTableSummaryArraySourceBase tableSummaryArraySourceBase = tree!.tag! as TreeTableSummaryArraySourceBase; - _tree.invalidateSummary(); + tableSummaryArraySourceBase.invalidateSummary(); } if (parent != null) { parent!.invalidateSummariesBottomUp(notifyParentRecordSource); } else if (notifyParentRecordSource) { if (tree != null && tree!.tag is TreeTableSummaryArraySourceBase) { - final TreeTableSummaryArraySourceBase _tree = + final TreeTableSummaryArraySourceBase tableSummaryArraySourceBase = tree!.tag! as TreeTableSummaryArraySourceBase; - _tree.invalidateSummariesBottomUp(); + tableSummaryArraySourceBase.invalidateSummariesBottomUp(); } } } @@ -3470,9 +3600,9 @@ class TreeTableWithSummary extends TreeTable { return false; } - final Object _root = root!; - if (_root is TreeTableSummaryNodeBase) { - return _root.hasSummaries; + final Object object = root!; + if (object is TreeTableSummaryNodeBase) { + return object.hasSummaries; } else { return false; } @@ -3484,14 +3614,15 @@ class TreeTableWithSummary extends TreeTable { /// /// Returns an array of summary objects. List? getSummaries( - TreeTableEmptySummaryArraySourceBase emptySummaries) { + TreeTableEmptySummaryArraySourceBase emptySummaries, + ) { if (root == null) { return emptySummaries.getEmptySummaries(); } - final Object _root = root!; - if (_root is TreeTableSummaryNodeBase) { - return _root.getSummaries(emptySummaries); + final Object object = root!; + if (object is TreeTableSummaryNodeBase) { + return object.getSummaries(emptySummaries); } else { return null; } @@ -3503,9 +3634,9 @@ class TreeTableWithSummary extends TreeTable { /// summaries source. void invalidateSummariesTopDown(bool notifySummariesSource) { if (root != null) { - final Object _root = root!; - if (_root is TreeTableSummaryNodeBase) { - _root.invalidateSummariesTopDown(notifySummariesSource); + final Object object = root!; + if (object is TreeTableSummaryNodeBase) { + object.invalidateSummariesTopDown(notifySummariesSource); } } } @@ -3596,7 +3727,6 @@ class TreeTableWithSummary extends TreeTable { } /// A strongly typed enumerator for the `TreeTableWithSummary` collection. - class TreeTableWithSummaryEnumerator extends TreeTableEnumerator { /// Initializes a new instance of the `TreeTableWithSummaryEnumerator` class. /// diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart index 1243ee8c9..40062f28e 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart @@ -3,7 +3,7 @@ import 'dart:collection'; /// Holds a range together with a value assigned to the range. /// /// * T - _required_ - The type of the parameter. -class RangeValuePair extends Comparable { +class RangeValuePair implements Comparable { /// Initializes a new instance of the RangeValuePair class. /// /// * start - _required_ - The start of the range. @@ -61,7 +61,8 @@ class RangeValuePair extends Comparable { /// /// Returns a `string` with state information about this `object`. @override - String toString() => 'RangeValuePair Start = $start ' + String toString() => + 'RangeValuePair Start = $start ' 'Count = $count End = $end Value = $value'; } @@ -139,8 +140,13 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// void ensureCount(num index) { if (index - count > 0) { - rangeValues.add(RangeValuePair.fromRangeValuePair( - count.toInt(), index.toInt() - count.toInt(), defaultValue)); + rangeValues.add( + RangeValuePair.fromRangeValuePair( + count.toInt(), + index.toInt() - count.toInt(), + defaultValue, + ), + ); } } @@ -165,8 +171,10 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// RangeValuePair getRangeValue(int index) { - final int n = - binarySearch>(rangeValues, RangeValuePair(index)); + final int n = binarySearch>( + rangeValues, + RangeValuePair(index), + ); return rangeValues[n]; } @@ -192,8 +200,12 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// * 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)) { @@ -201,14 +213,21 @@ class SortedRangeValueList extends EnumerableGenericBase> { } ensureCount(insertAt); - rangeValues.add(RangeValuePair.fromRangeValuePair( - insertAt.toInt(), count.toInt(), value)); + rangeValues.add( + RangeValuePair.fromRangeValuePair( + insertAt.toInt(), + count.toInt(), + value, + ), + ); if (rangeValues.length >= 2) { merge(rangeValues.length - 2); } } else { int n = binarySearch>( - rangeValues, RangeValuePair(insertAt.toInt())); + rangeValues, + RangeValuePair(insertAt.toInt()), + ); final RangeValuePair rv = rangeValues[n]; if (value == (rv.value)) { rv.count += count.toInt(); @@ -217,7 +236,10 @@ class SortedRangeValueList extends EnumerableGenericBase> { n = splitWithTwoArgs(insertAt, n); split(insertAt + 1); final RangeValuePair rv2 = RangeValuePair.fromRangeValuePair( - insertAt.toInt(), count.toInt(), value); + insertAt.toInt(), + count.toInt(), + value, + ); rangeValues.insert(n, rv2); adjustStart(n + 1, count); merge(n); @@ -246,7 +268,10 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// 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); } @@ -287,7 +312,10 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// 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; } @@ -301,7 +329,10 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// num removeHelper( - int removeAt, int count, SortedRangeValueList? moveRanges) { + int removeAt, + int count, + SortedRangeValueList? moveRanges, + ) { if (removeAt >= this.count) { return rangeValues.length; } @@ -315,8 +346,13 @@ class SortedRangeValueList extends EnumerableGenericBase> { total += rv.count; deleteCount++; if (moveRanges != null && !(rv.value == defaultValue)) { - moveRanges.rangeValues.add(RangeValuePair.fromRangeValuePair( - rv.start - removeAt, rv.count, rv.value)); + moveRanges.rangeValues.add( + RangeValuePair.fromRangeValuePair( + rv.start - removeAt, + rv.count, + rv.value, + ), + ); } } @@ -337,19 +373,41 @@ class SortedRangeValueList extends EnumerableGenericBase> { /// * index - _required_ - The index for the range to be changed. /// * count - _required_ - The count. /// * value - _required_ - The value. - void setRange(int index, int count, Object value) { + void setRange( + int index, + int count, + Object value, [ + bool canMergeLines = true, + ]) { if (index >= this.count && value == defaultValue) { return; } ensureCount(index); final num n = removeHelper(index, count, null); - final RangeValuePair rv = - RangeValuePair.fromRangeValuePair(index, count, value); + final RangeValuePair rv = RangeValuePair.fromRangeValuePair( + index, + count, + value, + ); rangeValues.insert(n.toInt(), rv); merge(n.toInt()); - if (n > 0) { - merge(n.toInt() - 1); + + /// Issue: + /// FLUT-6703 - The widths are not properly set to columns when hiding some + /// columns and using columnWidthMode. + /// + /// Temporary Fix: + /// An issue occurred due to improper internalCount and totalDistance value + /// return from the GridCommon. The below codes affect those properties while + /// hiding the continuous lines in the visible lines collection. The purpose + /// of the below code is to merge the continuous lines to a single range value + /// instead of creating a new range value for each hidden line. As of now, + /// We have fixed the issue by creating a new range value for each hidden line. + if (canMergeLines) { + if (n > 0) { + merge(n.toInt() - 1); + } } } @@ -359,7 +417,9 @@ class SortedRangeValueList extends EnumerableGenericBase> { return rangeValues.length; } final int n = binarySearch>( - rangeValues, RangeValuePair(index.toInt())); + rangeValues, + RangeValuePair(index.toInt()), + ); return splitWithTwoArgs(index, n); } @@ -374,8 +434,11 @@ class SortedRangeValueList extends EnumerableGenericBase> { final int count2 = rangeValues[n].count - count1; rv.count = count1; - final RangeValuePair rv2 = - RangeValuePair.fromRangeValuePair(index.toInt(), count2, rv.value); + final RangeValuePair rv2 = RangeValuePair.fromRangeValuePair( + index.toInt(), + count2, + rv.value, + ); rangeValues.insert(n + 1, rv2); return n + 1; } @@ -433,7 +496,8 @@ class SortedRangeValueList extends EnumerableGenericBase> { rangeValues[n.toInt() - 1].count++; } else { rangeValues.add( - RangeValuePair.fromRangeValuePair(index.toInt(), 1, value)); + RangeValuePair.fromRangeValuePair(index.toInt(), 1, value), + ); } } else { rangeValues[n.toInt()].value = value; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart index 861e77a01..9301ed54d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart @@ -7,7 +7,7 @@ import 'utility_helper.dart' hide ListBase; /// VisibleLines Information /// /// Contains information about a visible line (can also be a row or column). -class VisibleLineInfo extends Comparable { +class VisibleLineInfo implements Comparable { /// Initializes a new instance of the VisibleLineInfo class. /// /// * visibleIndex - _required_ - Visible index of the line. @@ -17,8 +17,15 @@ class VisibleLineInfo extends Comparable { /// * scrollOffset - _required_ - The scroll offset. /// * isHeader - _required_ - if set to true line is a header. /// * isFooter - _required_ - if set to true line is a footer. - VisibleLineInfo(int visibleIndex, this.lineIndex, double size, - double clippedOrigin, double scrollOffset, bool isHeader, bool isFooter) { + VisibleLineInfo( + int visibleIndex, + this.lineIndex, + double size, + double clippedOrigin, + double scrollOffset, + bool isHeader, + bool isFooter, + ) { _visibleIndex = visibleIndex; _size = size; _clippedOrigin = clippedOrigin; @@ -173,7 +180,7 @@ class VisibleLineInfo extends Comparable { /// * other - _required_ - An object to compare with this object. @override int compareTo(VisibleLineInfo other) => - ((clippedOrigin - other.clippedOrigin).sign).toInt(); + (clippedOrigin - other.clippedOrigin).sign.toInt(); /// A string describing the state of the object. /// @@ -182,13 +189,13 @@ class VisibleLineInfo extends Comparable { String toString() { final String sb = ''' VisibleLineInfo { - visibleIndex = ${_visibleIndex.toString()} - lineIndex = ${lineIndex.toString()} - size = ${_size.toString()} - origin = ${origin.toString()} - clippedOrigin = ${_clippedOrigin.toString()} - scrollOffset = ${_scrollOffset.toString()} - region = ${region.toString()} + visibleIndex = $_visibleIndex + lineIndex = $lineIndex + size = $_size + origin = $origin + clippedOrigin = $_clippedOrigin + scrollOffset = $_scrollOffset + region = $region }'''; return sb; @@ -200,8 +207,9 @@ class VisibleLineInfo extends Comparable { /// A strong-typed collection of `VisibleLineInfo` items. class VisibleLinesCollection extends ListBase { /// - List visibleLines = - List.empty(growable: true); + List visibleLines = List.empty( + growable: true, + ); /// VisibleLineInfoLineIndexComparer lineIndexComparer = @@ -283,10 +291,12 @@ class VisibleLinesCollection extends ListBase { /// /// Returns the first visible line for a line index that is not hidden. VisibleLineInfo? getVisibleLineNearLineIndex(int lineIndex) { - final List _visibleLine = + final List visibleLineInfo = visibleLines as List; int index = binarySearch( - _visibleLine, VisibleLineInfo.fromLineIndex(lineIndex)); + visibleLineInfo, + VisibleLineInfo.fromLineIndex(lineIndex), + ); index = (index < 0) ? (~index) - 1 : index; if (index >= 0) { return this[index]; @@ -301,9 +311,11 @@ class VisibleLinesCollection extends ListBase { /// /// Returns the visible line at point. VisibleLineInfo? getVisibleLineAtPoint(double point) { - final List _visibleLines = visibleLines.cast(); + final List visibleLineInfo = visibleLines.cast(); int index = binarySearch( - _visibleLines, VisibleLineInfo.fromClippedOrigin(point)); + visibleLineInfo, + VisibleLineInfo.fromClippedOrigin(point), + ); index = (index < 0) ? (~index) - 1 : index; if (index >= 0) { return this[index]; @@ -383,7 +395,7 @@ class VisibleLinesCollection extends ListBase { } /// Initializes a new instance of the `VisibleLineInfoLineIndexComparer` class. -class VisibleLineInfoLineIndexComparer extends Comparable { +class VisibleLineInfoLineIndexComparer implements Comparable { /// Compares the given visible lines. /// /// * x - _required_ - The visible line-1. diff --git a/packages/syncfusion_flutter_datagrid/pubspec.yaml b/packages/syncfusion_flutter_datagrid/pubspec.yaml index 3fe5fb097..208a759d1 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: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datagrid environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: @@ -13,7 +13,17 @@ dependencies: syncfusion_flutter_core: path: ../syncfusion_flutter_core - collection: ">=1.9.0 <=1.15.0" + + + collection: ">=1.9.0 <=2.0.0" +flutter: + fonts: + - family: UnsortIcon + fonts: + - asset: assets/font/UnsortIcon.ttf + - family: FilterIcon + fonts: + - asset: assets/font/FilterIcon.ttf diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_column_drag_and_drop.gif b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_column_drag_and_drop.gif new file mode 100644 index 000000000..9ae8fb829 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_column_drag_and_drop.gif differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_column_types.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_column_types.jpg new file mode 100644 index 000000000..e42cc9e23 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_column_types.jpg differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_editing.gif b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_editing.gif new file mode 100644 index 000000000..0f98bc86d Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_editing.gif differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_freeze_panes.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_freeze_panes.jpg new file mode 100644 index 000000000..2dd4b289f Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_freeze_panes.jpg differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_grouping.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_grouping.jpg new file mode 100644 index 000000000..e2c4f364c Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_grouping.jpg differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_paging.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_paging.jpg new file mode 100644 index 000000000..fa382c0d4 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_paging.jpg differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_selection.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_selection.jpg new file mode 100644 index 000000000..47b82391a Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_selection.jpg differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_sorting.gif b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_sorting.gif new file mode 100644 index 000000000..3edeb9a01 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_sorting.gif differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_stacked_headers.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_stacked_headers.jpg new file mode 100644 index 000000000..4d8f1bd52 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_stacked_headers.jpg differ diff --git a/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_summaries.jpg b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_summaries.jpg new file mode 100644 index 000000000..aa6adddd6 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid/screenshots/flutter_datagrid_summaries.jpg differ diff --git a/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md b/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md index d7e173de7..7f1491795 100644 --- a/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md +++ b/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md @@ -1,4 +1,10 @@ -## Unreleased +## [29.1.39] - 04/22/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [19.3.43-beta] - 09/30/2021 Intial release. diff --git a/packages/syncfusion_flutter_datagrid_export/README.md b/packages/syncfusion_flutter_datagrid_export/README.md index 53743cbaa..1b9a59e0d 100644 --- a/packages/syncfusion_flutter_datagrid_export/README.md +++ b/packages/syncfusion_flutter_datagrid_export/README.md @@ -22,15 +22,12 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

@@ -191,11 +188,11 @@ Use the `exportToPdfDocument` method from `_key.currentState!` and call this met ## Support and feedback -* If you have any questions, you can contact the [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post them to the [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug report through our [feedback portal](https://www.syncfusion.com/feedback/flutter). +* If you have any questions, you can contact the [Syncfusion support team](https://support.syncfusion.com/support/tickets/create) or post them to the [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug report 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 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 22,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. -Today, we provide 1,600+ 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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-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,600+ 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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)) , mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [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 ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_datagrid_export/analysis_options.yaml b/packages/syncfusion_flutter_datagrid_export/analysis_options.yaml index e68d44916..07fa312a6 100644 --- a/packages/syncfusion_flutter_datagrid_export/analysis_options.yaml +++ b/packages/syncfusion_flutter_datagrid_export/analysis_options.yaml @@ -2,7 +2,5 @@ include: package:syncfusion_flutter_core/analysis_options.yaml analyzer: errors: - lines_longer_than_80_chars: ignore include_file_not_found: ignore - invalid_dependency: ignore - avoid_as: ignore + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index 6ca23448e..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,59 +0,0 @@ -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 30 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion 16 - targetSdkVersion 30 - 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_datagrid_export/example/android/app/src/main/AndroidManifest.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/AndroidManifest.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index 34dd77efb..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/AndroidManifest.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index 449a9f930..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index d74aa35c2..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/build.gradle~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/android/build.gradle~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index 9b6ed06eb..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/android/build.gradle~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,29 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart index 7733b6118..d2edbbbda 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:open_file/open_file.dart' as open_file; import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart' as path_provider_interface; @@ -12,16 +13,25 @@ Future saveAndLaunchFile(List bytes, String fileName) async { Platform.isIOS || Platform.isLinux || Platform.isWindows) { - final Directory directory = - await path_provider.getApplicationSupportDirectory(); - path = directory.path; + if (Platform.isAndroid) { + final Directory? directory = await path_provider + .getExternalStorageDirectory(); + if (directory != null) { + path = directory.path; + } + } else { + final Directory directory = await path_provider + .getApplicationSupportDirectory(); + path = directory.path; + } } else { path = await path_provider_interface.PathProviderPlatform.instance .getApplicationSupportPath(); } - final String fileLocation = - Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'; + final String fileLocation = Platform.isWindows + ? '$path\\$fileName' + : '$path/$fileName'; final File file = File(fileLocation); await file.writeAsBytes(bytes, flush: true); diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_wasm.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_wasm.dart new file mode 100644 index 000000000..a996d8167 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_wasm.dart @@ -0,0 +1,19 @@ +import 'dart:convert'; +import 'package:web/web.dart' as web; + +Future saveAndLaunchFile(List bytes, String fileName) async { + final web.HTMLAnchorElement anchor = + web.document.createElement('a') as web.HTMLAnchorElement + ..href = 'data:application/octet-stream;base64,${base64Encode(bytes)}' + ..style.display = 'none' + ..download = fileName; + + // Insert new elements in the DOM: + web.document.body!.appendChild(anchor); + + // download + anchor.click(); + + // cleanup + web.document.body!.removeChild(anchor); +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart index 59f773f34..90d0e90d6 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart @@ -1,12 +1,20 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:html'; +import 'package:web/web.dart'; -///To save the excel sheet in the web platform. +// Function to save and launch a file for download in a web environment Future saveAndLaunchFile(List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); + final HTMLAnchorElement anchor = + document.createElement('a') as HTMLAnchorElement + ..href = 'data:application/octet-stream;base64,${base64Encode(bytes)}' + ..style.display = 'none' + ..download = fileName; + + // Insert the new element into the DOM + document.body!.appendChild(anchor); + + // Initiate the download + anchor.click(); + // Clean up the DOM by removing the anchor element + document.body!.removeChild(anchor); } diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart index 52e538c22..d8d540c62 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart @@ -1,22 +1,27 @@ +// ignore_for_file: depend_on_referenced_packages + import 'package:flutter/material.dart'; // External package imports import 'package:syncfusion_flutter_datagrid/datagrid.dart'; import 'package:syncfusion_flutter_datagrid_export/export.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; -import 'package:syncfusion_flutter_xlsio/xlsio.dart' - hide Alignment, Column, Row; +import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column, Row; // Local import import 'helper/save_file_mobile.dart' - if (dart.library.html) 'helper/save_file_web.dart' as helper; + if (dart.library.html) 'helper/save_file_web.dart' + if (dart.library.js_interop) 'helper/save_file_wasm.dart' + as helper; void main() { - runApp(MyApp()); + runApp(const MyApp()); } /// The application that contains datagrid on it. class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( @@ -60,10 +65,11 @@ class MyHomePageState extends State { } Future _exportDataGridToPdf() async { - final PdfDocument document = - _key.currentState!.exportToPdfDocument(fitAllColumnsInOnePage: true); + final PdfDocument document = _key.currentState!.exportToPdfDocument( + fitAllColumnsInOnePage: true, + ); - final List bytes = document.save(); + final List bytes = document.saveSync(); await helper.saveAndLaunchFile(bytes, 'DataGrid.pdf'); document.dispose(); } @@ -87,26 +93,30 @@ class MyHomePageState extends State { height: 40.0, width: 150.0, child: MaterialButton( - color: Colors.blue, - child: const Center( - child: Text( + color: Colors.blue, + onPressed: _exportDataGridToExcel, + child: const Center( + child: Text( 'Export to Excel', style: TextStyle(color: Colors.white), - )), - onPressed: _exportDataGridToExcel), + ), + ), + ), ), const Padding(padding: EdgeInsets.all(20)), SizedBox( height: 40.0, width: 150.0, child: MaterialButton( - color: Colors.blue, - child: const Center( - child: Text( + color: Colors.blue, + onPressed: _exportDataGridToPdf, + child: const Center( + child: Text( 'Export to PDF', style: TextStyle(color: Colors.white), - )), - onPressed: _exportDataGridToPdf), + ), + ), + ), ), ], ), @@ -117,34 +127,40 @@ class MyHomePageState extends State { source: _employeeDataSource, columns: [ GridColumn( - columnName: 'ID', - label: Container( - padding: const EdgeInsets.all(16.0), - alignment: Alignment.center, - child: const Text( - 'ID', - ))), + columnName: 'ID', + label: Container( + padding: const EdgeInsets.all(16.0), + alignment: Alignment.center, + child: const Text('ID'), + ), + ), GridColumn( - columnName: 'Name', - label: Container( - padding: const EdgeInsets.all(8.0), - alignment: Alignment.center, - child: const Text('Name'))), + columnName: 'Name', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text('Name'), + ), + ), GridColumn( - columnName: 'Designation', - label: Container( - padding: const EdgeInsets.all(8.0), - alignment: Alignment.center, - child: const Text( - 'Designation', - overflow: TextOverflow.ellipsis, - ))), + columnName: 'Designation', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text( + 'Designation', + overflow: TextOverflow.ellipsis, + ), + ), + ), GridColumn( - columnName: 'Salary', - label: Container( - padding: const EdgeInsets.all(8.0), - alignment: Alignment.center, - child: const Text('Salary'))), + columnName: 'Salary', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text('Salary'), + ), + ), ], ), ), @@ -164,7 +180,7 @@ class MyHomePageState extends State { Employee(10007, 'Balnc', 'Developer', 15000), Employee(10008, 'Perry', 'Developer', 15000), Employee(10009, 'Gable', 'Developer', 15000), - Employee(10010, 'Grimes', 'Developer', 15000) + Employee(10010, 'Grimes', 'Developer', 15000), ]; } } @@ -194,13 +210,19 @@ class EmployeeDataSource extends DataGridSource { /// Creates the employee data source class with required details. EmployeeDataSource({required List employeeData}) { _employeeData = employeeData - .map((Employee e) => DataGridRow(cells: [ + .map( + (Employee e) => DataGridRow( + cells: [ DataGridCell(columnName: 'ID', value: e.id), DataGridCell(columnName: 'Name', value: e.name), DataGridCell( - columnName: 'Designation', value: e.designation), + columnName: 'Designation', + value: e.designation, + ), DataGridCell(columnName: 'Salary', value: e.salary), - ])) + ], + ), + ) .toList(); } @@ -212,12 +234,13 @@ class EmployeeDataSource extends DataGridSource { @override DataGridRowAdapter buildRow(DataGridRow row) { return DataGridRowAdapter( - cells: row.getCells().map((DataGridCell cell) { - return Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(8.0), - child: Text(cell.value.toString()), - ); - }).toList()); + cells: row.getCells().map((DataGridCell cell) { + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(8.0), + child: Text(cell.value.toString()), + ); + }).toList(), + ); } } diff --git a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..eb768d3a6 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.cc @@ -2,8 +2,14 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); } diff --git a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugins.cmake index 51436ae8c..eb06039dc 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_file_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) @@ -13,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0d56f519c..4d6f3f9e6 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation -import path_provider_macos +import open_file_mac +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml b/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml index ba1f2390e..62035a0ce 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml @@ -6,7 +6,7 @@ publish_to: "none" version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: cupertino_icons: ^1.0.2 @@ -16,6 +16,7 @@ dependencies: path_provider: ^2.0.2 syncfusion_flutter_datagrid_export: path: ../ + web: ^1.1.0 dev_dependencies: flutter_test: diff --git a/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-maskable-192.png b/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-maskable-512.png b/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/syncfusion_flutter_datagrid_export/example/web/index.html b/packages/syncfusion_flutter_datagrid_export/example/web/index.html index 0081e1894..1aa025dd6 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/web/index.html +++ b/packages/syncfusion_flutter_datagrid_export/example/web/index.html @@ -10,8 +10,11 @@ For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + @@ -23,76 +26,13 @@ + + + example - - + diff --git a/packages/syncfusion_flutter_datagrid_export/example/web/manifest.json b/packages/syncfusion_flutter_datagrid_export/example/web/manifest.json index 8c012917d..096edf8fe 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/web/manifest.json +++ b/packages/syncfusion_flutter_datagrid_export/example/web/manifest.json @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.cc~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.cc~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index 4bfa0f3a3..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.cc~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,9 +0,0 @@ -// -// Generated file. Do not edit. -// - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.h~194afe6f24944c0d998bf2edc92e1a70e851e036 b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.h~194afe6f24944c0d998bf2edc92e1a70e851e036 deleted file mode 100644 index 9846246b4..000000000 --- a/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.h~194afe6f24944c0d998bf2edc92e1a70e851e036 +++ /dev/null @@ -1,13 +0,0 @@ -// -// Generated file. Do not edit. -// - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart index 783d0da4a..bd5554b78 100644 --- a/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart @@ -11,14 +11,18 @@ import 'helper.dart'; /// Signature for `cellExport` callback which is passed as an argument in /// `exportToExcelWorkbook` and `exportToExcelWorksheet` methods. -typedef DataGridCellExcelExportCallback = void Function( - DataGridCellExcelExportDetails details); +typedef DataGridCellExcelExportCallback = + void Function(DataGridCellExcelExportDetails details); /// Details for the callback that use [DataGridCellExcelExportDetails]. class DataGridCellExcelExportDetails { /// Creates the [DataGridCellExcelExportDetails]. const DataGridCellExcelExportDetails( - this.cellValue, this.columnName, this.excelRange, this.cellType); + this.cellValue, + this.columnName, + this.excelRange, + this.cellType, + ); /// The value of the cell. Typically, it is [DataGridCell.value]. final Object? cellValue; @@ -117,23 +121,32 @@ class DataGridToExcelConverter { /// Exports a column header to Excel. @protected - void exportColumnHeader(SfDataGrid dataGrid, GridColumn column, - String columnName, Worksheet worksheet) { + void exportColumnHeader( + SfDataGrid dataGrid, + GridColumn column, + String columnName, + Worksheet worksheet, + ) { Range range; int rowSpan = 0; if (exportStackedHeaders && dataGrid.stackedHeaderRows.isNotEmpty) { rowSpan = getRowSpan( - dataGrid: dataGrid, - isStackedHeader: false, - columnName: column.columnName, - rowIndex: excelRowIndex - startRowIndex - 1, - columnIndex: dataGrid.columns.indexOf(column)); + dataGrid: dataGrid, + isStackedHeader: false, + columnName: column.columnName, + rowIndex: excelRowIndex - startRowIndex - 1, + columnIndex: dataGrid.columns.indexOf(column), + ); } if (rowSpan > 0) { - range = worksheet.getRangeByIndex(excelRowIndex - rowSpan, - excelColumnIndex, excelRowIndex, excelColumnIndex); + range = worksheet.getRangeByIndex( + excelRowIndex - rowSpan, + excelColumnIndex, + excelRowIndex, + excelColumnIndex, + ); range.merge(); } else { range = worksheet.getRangeByIndex(excelRowIndex, excelColumnIndex); @@ -144,14 +157,22 @@ class DataGridToExcelConverter { ..hAlign = HAlignType.center ..vAlign = VAlignType.center; - _exportCellToExcel(worksheet, range, column, - DataGridExportCellType.columnHeader, columnName); + _exportCellToExcel( + worksheet, + range, + column, + DataGridExportCellType.columnHeader, + columnName, + ); } /// Exports all the data rows to Excel. @protected void exportRows( - SfDataGrid dataGrid, List rows, Worksheet worksheet) { + SfDataGrid dataGrid, + List rows, + Worksheet worksheet, + ) { if (rows.isEmpty) { return; } @@ -172,13 +193,24 @@ class DataGridToExcelConverter { /// Exports a [DataGridRow] to Excel. @protected - void exportRow(SfDataGrid dataGrid, DataGridRow row, GridColumn column, - Worksheet worksheet) { + void exportRow( + SfDataGrid dataGrid, + DataGridRow row, + GridColumn column, + Worksheet worksheet, + ) { final Object? cellValue = getCellValue(row, column); - final Range range = - worksheet.getRangeByIndex(excelRowIndex, excelColumnIndex); + final Range range = worksheet.getRangeByIndex( + excelRowIndex, + excelColumnIndex, + ); _exportCellToExcel( - worksheet, range, column, DataGridExportCellType.row, cellValue); + worksheet, + range, + column, + DataGridExportCellType.row, + cellValue, + ); } /// Exports all the stacked header rows to Excel. @@ -196,33 +228,48 @@ class DataGridToExcelConverter { /// Exports a stacked header row to Excel. @protected - void exportStackedHeaderRow(SfDataGrid dataGrid, - StackedHeaderRow stackedHeaderRow, Worksheet worksheet) { + void exportStackedHeaderRow( + SfDataGrid dataGrid, + StackedHeaderRow stackedHeaderRow, + Worksheet worksheet, + ) { _setRowHeight(dataGrid, DataGridExportCellType.stackedHeader, worksheet); for (final StackedHeaderCell column in stackedHeaderRow.cells) { - final List columnSequences = - getColumnSequences(dataGrid.columns, column); + final List columnSequences = getColumnSequences( + dataGrid.columns, + column, + ); for (final List indexes in getConsecutiveRanges(columnSequences)) { final int columnIndex = indexes.reduce(min); final int columnSpan = indexes.length - 1; final int rowSpan = getRowSpan( - dataGrid: dataGrid, - isStackedHeader: true, - columnIndex: columnIndex, - stackedHeaderCell: column, - rowIndex: excelRowIndex - startRowIndex - 1); + dataGrid: dataGrid, + isStackedHeader: true, + columnIndex: columnIndex, + stackedHeaderCell: column, + rowIndex: excelRowIndex - startRowIndex - 1, + ); _exportStackedHeaderCell( - dataGrid, column, columnIndex, columnSpan, rowSpan, worksheet); + dataGrid, + column, + columnIndex, + columnSpan, + rowSpan, + worksheet, + ); } } } /// Export table summary rows to the Excel. @protected - void exportTableSummaryRows(SfDataGrid dataGrid, - GridTableSummaryRowPosition position, Worksheet worksheet) { + void exportTableSummaryRows( + SfDataGrid dataGrid, + GridTableSummaryRowPosition position, + Worksheet worksheet, + ) { if (dataGrid.tableSummaryRows.isEmpty) { return; } @@ -243,8 +290,11 @@ class DataGridToExcelConverter { /// Export table summary row to the Excel. @protected - void exportTableSummaryRow(SfDataGrid dataGrid, - GridTableSummaryRow summaryRow, Worksheet worksheet) { + void exportTableSummaryRow( + SfDataGrid dataGrid, + GridTableSummaryRow summaryRow, + Worksheet worksheet, + ) { // Resets the column index when creating a new row. excelColumnIndex = startColumnIndex; @@ -252,32 +302,41 @@ class DataGridToExcelConverter { if (summaryRow.showSummaryInRow) { _exportTableSummaryCell( - sheet: worksheet, - dataGrid: dataGrid, - summaryRow: summaryRow, - columnSpan: columns.length); + sheet: worksheet, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: columns.length, + ); } else { int titleColumnCount = summaryRow.titleColumnSpan; if (titleColumnCount > 0) { // To consider the exclude columns in the `titleColumnCount`. - titleColumnCount = - getTitleColumnCount(summaryRow, dataGrid.columns, excludeColumns); + titleColumnCount = getTitleColumnCount( + summaryRow, + dataGrid.columns, + excludeColumns, + ); if (titleColumnCount > 0) { _exportTableSummaryCell( - sheet: worksheet, - dataGrid: dataGrid, - summaryRow: summaryRow, - columnSpan: titleColumnCount); + sheet: worksheet, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: titleColumnCount, + ); } } for (final GridSummaryColumn summaryColumn in summaryRow.columns) { final GridColumn? column = dataGrid.columns.firstWhereOrNull( - (GridColumn element) => - element.columnName == summaryColumn.columnName); + (GridColumn element) => + element.columnName == summaryColumn.columnName, + ); final int columnIndex = getSummaryColumnIndex( - dataGrid.columns, summaryColumn.columnName, excludeColumns); + dataGrid.columns, + summaryColumn.columnName, + excludeColumns, + ); // Restricts if the column doesn't exist or its column index is less // than the `titleColumnCount`. because the `titleColumn` summary cell @@ -289,12 +348,13 @@ class DataGridToExcelConverter { } _exportTableSummaryCell( - column: column, - sheet: worksheet, - dataGrid: dataGrid, - summaryRow: summaryRow, - columnIndex: columnIndex, - summaryColumn: summaryColumn); + column: column, + sheet: worksheet, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnIndex: columnIndex, + summaryColumn: summaryColumn, + ); } } } @@ -303,16 +363,21 @@ class DataGridToExcelConverter { @protected Object? getCellValue(DataGridRow row, GridColumn column) { final DataGridCell cellValue = row.getCells().firstWhereOrNull( - (DataGridCell cell) => cell.columnName == column.columnName)!; + (DataGridCell cell) => cell.columnName == column.columnName, + )!; return cellValue.value; } /// Exports the [SfDataGrid] to Excel [Worksheet]. void exportToExcelWorksheet( - SfDataGrid dataGrid, List? rows, Worksheet worksheet) { + SfDataGrid dataGrid, + List? rows, + Worksheet worksheet, + ) { _columns = dataGrid.columns .where( - (GridColumn column) => !excludeColumns.contains(column.columnName)) + (GridColumn column) => !excludeColumns.contains(column.columnName), + ) .toList(); // Return if all the columns are `excludeColumns`. @@ -335,7 +400,10 @@ class DataGridToExcelConverter { // Exports the top table summary rows. if (exportTableSummaries) { exportTableSummaryRows( - dataGrid, GridTableSummaryRowPosition.top, worksheet); + dataGrid, + GridTableSummaryRowPosition.top, + worksheet, + ); } // Exports the data rows. @@ -344,7 +412,10 @@ class DataGridToExcelConverter { // Exports the bottom table summary rows. if (exportTableSummaries) { exportTableSummaryRows( - dataGrid, GridTableSummaryRowPosition.bottom, worksheet); + dataGrid, + GridTableSummaryRowPosition.bottom, + worksheet, + ); } } @@ -356,30 +427,46 @@ class DataGridToExcelConverter { return workbook; } - void _exportCellToExcel(Worksheet sheet, Range range, GridColumn column, - DataGridExportCellType cellType, Object? cellValue) { + void _exportCellToExcel( + Worksheet sheet, + Range range, + GridColumn column, + DataGridExportCellType cellType, + Object? cellValue, + ) { range.setValue(cellValue); if (cellExport != null) { final DataGridCellExcelExportDetails details = DataGridCellExcelExportDetails( - cellValue, column.columnName, range, cellType); + cellValue, + column.columnName, + range, + cellType, + ); cellExport!(details); } } - void _exportStackedHeaderCell(SfDataGrid dataGrid, StackedHeaderCell column, - int columnIndex, int columnSpan, int rowSpan, Worksheet sheet) { + void _exportStackedHeaderCell( + SfDataGrid dataGrid, + StackedHeaderCell column, + int columnIndex, + int columnSpan, + int rowSpan, + Worksheet sheet, + ) { Range range; int firstColumnIndex = startColumnIndex + columnIndex; int lastColumnIndex = firstColumnIndex + columnSpan; if (excludeColumns.isNotEmpty) { final List startAndEndIndex = getSpannedCellStartAndEndIndex( - columnSpan: columnSpan, - columnIndex: columnIndex, - columns: dataGrid.columns, - excludeColumns: excludeColumns, - startColumnIndex: startColumnIndex); + columnSpan: columnSpan, + columnIndex: columnIndex, + columns: dataGrid.columns, + excludeColumns: excludeColumns, + startColumnIndex: startColumnIndex, + ); firstColumnIndex = startAndEndIndex[0]; lastColumnIndex = startAndEndIndex[1]; @@ -388,8 +475,12 @@ class DataGridToExcelConverter { if (firstColumnIndex <= lastColumnIndex) { excelColumnIndex = firstColumnIndex; if (rowSpan > 0 || lastColumnIndex > firstColumnIndex) { - range = sheet.getRangeByIndex(excelRowIndex - rowSpan, firstColumnIndex, - excelRowIndex, lastColumnIndex); + range = sheet.getRangeByIndex( + excelRowIndex - rowSpan, + firstColumnIndex, + excelRowIndex, + lastColumnIndex, + ); range.merge(); } else { range = sheet.getRangeByIndex(excelRowIndex, firstColumnIndex); @@ -400,45 +491,68 @@ class DataGridToExcelConverter { ..hAlign = HAlignType.center ..vAlign = VAlignType.center; - _exportCellToExcel(sheet, range, dataGrid.columns[columnIndex], - DataGridExportCellType.stackedHeader, column.text); + _exportCellToExcel( + sheet, + range, + dataGrid.columns[columnIndex], + DataGridExportCellType.stackedHeader, + column.text, + ); } } - void _exportTableSummaryCell( - {int columnSpan = 0, - int columnIndex = 0, - GridColumn? column, - required Worksheet sheet, - required SfDataGrid dataGrid, - GridSummaryColumn? summaryColumn, - required GridTableSummaryRow summaryRow}) { + void _exportTableSummaryCell({ + int columnSpan = 0, + int columnIndex = 0, + GridColumn? column, + required Worksheet sheet, + required SfDataGrid dataGrid, + GridSummaryColumn? summaryColumn, + required GridTableSummaryRow summaryRow, + }) { Range range; final GridColumn? column = dataGrid.columns.firstWhereOrNull( - (GridColumn column) => column.columnName == summaryColumn?.columnName); + (GridColumn column) => column.columnName == summaryColumn?.columnName, + ); final RowColumnIndex rowColumnIndex = RowColumnIndex( - excelRowIndex - startRowIndex, - column != null ? dataGrid.columns.indexOf(column) : 0); + excelRowIndex - startRowIndex, + column != null ? dataGrid.columns.indexOf(column) : 0, + ); final String summaryValue = dataGrid.source.calculateSummaryValue( - summaryRow, summaryColumn ?? summaryRow.columns.first, rowColumnIndex); + summaryRow, + summaryColumn ?? summaryRow.columns.first, + rowColumnIndex, + ); columnIndex += startColumnIndex; excelColumnIndex = columnIndex; if (columnSpan > 0) { - range = sheet.getRangeByIndex(excelRowIndex, excelColumnIndex, - excelRowIndex, excelColumnIndex + columnSpan - 1); + range = sheet.getRangeByIndex( + excelRowIndex, + excelColumnIndex, + excelRowIndex, + excelColumnIndex + columnSpan - 1, + ); range.merge(); } else { range = sheet.getRangeByIndex(excelRowIndex, excelColumnIndex); } - _exportCellToExcel(sheet, range, column ?? dataGrid.columns[0], - DataGridExportCellType.tableSummaryRow, summaryValue); + _exportCellToExcel( + sheet, + range, + column ?? dataGrid.columns[0], + DataGridExportCellType.tableSummaryRow, + summaryValue, + ); } void _setColumnWidths( - bool exportColumnWidth, Worksheet sheet, double columnWidth) { + bool exportColumnWidth, + Worksheet sheet, + double columnWidth, + ) { excelColumnIndex = startColumnIndex; for (final GridColumn column in columns) { if (exportColumnWidth && !column.actualWidth.isNaN) { @@ -451,19 +565,24 @@ class DataGridToExcelConverter { } void _setRowHeight( - SfDataGrid dataGrid, DataGridExportCellType cellType, Worksheet sheet) { + SfDataGrid dataGrid, + DataGridExportCellType cellType, + Worksheet sheet, + ) { if (exportRowHeight) { switch (cellType) { case DataGridExportCellType.columnHeader: case DataGridExportCellType.stackedHeader: - final double height = - dataGrid.headerRowHeight.isNaN ? 56.0 : dataGrid.headerRowHeight; + final double height = dataGrid.headerRowHeight.isNaN + ? 56.0 + : dataGrid.headerRowHeight; sheet.setRowHeightInPixels(excelRowIndex, height); break; case DataGridExportCellType.row: case DataGridExportCellType.tableSummaryRow: - final double height = - dataGrid.rowHeight.isNaN ? 49.0 : dataGrid.rowHeight; + final double height = dataGrid.rowHeight.isNaN + ? 49.0 + : dataGrid.rowHeight; sheet.setRowHeightInPixels(excelRowIndex, height); break; } @@ -477,17 +596,19 @@ class DataGridToExcelConverter { /// [SfDataGrid] to Excel. extension DataGridExcelExportExtensions on SfDataGridState { // Initializes the properties to the converter. - void _initializeProperties(DataGridToExcelConverter converter, - {required int excelStartRowIndex, - required int excelStartColumnIndex, - required bool canExportStackedHeaders, - required bool canExportTableSummaries, - required bool canExportColumnWidth, - required bool canExportRowHeight, - required int columnWidth, - required int rowHeight, - required List excludeColumns, - DataGridCellExcelExportCallback? cellExport}) { + void _initializeProperties( + DataGridToExcelConverter converter, { + required int excelStartRowIndex, + required int excelStartColumnIndex, + required bool canExportStackedHeaders, + required bool canExportTableSummaries, + required bool canExportColumnWidth, + required bool canExportRowHeight, + required int columnWidth, + required int rowHeight, + required List excludeColumns, + DataGridCellExcelExportCallback? cellExport, + }) { converter ..defaultColumnWidth = columnWidth.toDouble() ..defaultRowHeight = rowHeight.toDouble() @@ -593,19 +714,21 @@ extension DataGridExcelExportExtensions on SfDataGridState { /// ); /// } /// ``` - void exportToExcelWorksheet(Worksheet worksheet, - {List? rows, - bool exportStackedHeaders = true, - bool exportTableSummaries = true, - int defaultColumnWidth = 90, - int defaultRowHeight = 49, - bool exportColumnWidth = true, - bool exportRowHeight = true, - int startColumnIndex = 1, - int startRowIndex = 1, - List excludeColumns = const [], - DataGridToExcelConverter? converter, - DataGridCellExcelExportCallback? cellExport}) { + void exportToExcelWorksheet( + Worksheet worksheet, { + List? rows, + bool exportStackedHeaders = true, + bool exportTableSummaries = true, + int defaultColumnWidth = 90, + int defaultRowHeight = 49, + bool exportColumnWidth = true, + bool exportRowHeight = true, + int startColumnIndex = 1, + int startRowIndex = 1, + List excludeColumns = const [], + DataGridToExcelConverter? converter, + DataGridCellExcelExportCallback? cellExport, + }) { converter ??= DataGridToExcelConverter(); _initializeProperties( @@ -724,19 +847,20 @@ extension DataGridExcelExportExtensions on SfDataGridState { /// ); /// } /// ``` - Workbook exportToExcelWorkbook( - {List? rows, - bool exportStackedHeaders = true, - bool exportTableSummaries = true, - int defaultColumnWidth = 90, - int defaultRowHeight = 49, - bool exportRowHeight = true, - bool exportColumnWidth = true, - int startColumnIndex = 1, - int startRowIndex = 1, - List excludeColumns = const [], - DataGridToExcelConverter? converter, - DataGridCellExcelExportCallback? cellExport}) { + Workbook exportToExcelWorkbook({ + List? rows, + bool exportStackedHeaders = true, + bool exportTableSummaries = true, + int defaultColumnWidth = 90, + int defaultRowHeight = 49, + bool exportRowHeight = true, + bool exportColumnWidth = true, + int startColumnIndex = 1, + int startRowIndex = 1, + List excludeColumns = const [], + DataGridToExcelConverter? converter, + DataGridCellExcelExportCallback? cellExport, + }) { converter ??= DataGridToExcelConverter(); _initializeProperties( diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart index 56ad52c71..99d4cdfa6 100644 --- a/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart @@ -14,15 +14,18 @@ import 'helper.dart'; extension DataGridPdfExportExtensions on SfDataGridState { // Initializes the properties to the converter. - void _initializeProperties(DataGridToPdfConverter converter, - {required bool canRepeatHeaders, - required bool exportStackedHeaders, - required bool fitAllColumnsInOnePage, - required List excludeColumns, - required bool autoColumnWidth, - required bool exportTableSummaries, - DataGridCellPdfExportCallback? cellExport, - DataGridPdfHeaderFooterExportCallback? headerFooterExport}) { + void _initializeProperties( + DataGridToPdfConverter converter, { + required bool canRepeatHeaders, + required bool exportStackedHeaders, + required bool fitAllColumnsInOnePage, + required List excludeColumns, + required bool autoColumnWidth, + required bool exportTableSummaries, + int rowIndex = 0, + DataGridCellPdfExportCallback? cellExport, + DataGridPdfHeaderFooterExportCallback? headerFooterExport, + }) { converter ..canRepeatHeaders = canRepeatHeaders ..exportStackedHeaders = exportStackedHeaders @@ -31,7 +34,8 @@ extension DataGridPdfExportExtensions on SfDataGridState { ..cellExport = cellExport ..excludeColumns = excludeColumns ..headerFooterExport = headerFooterExport - ..exportTableSummaries = exportTableSummaries; + ..exportTableSummaries = exportTableSummaries + ..rowIndex = rowIndex; } /// Exports the `SfDataGrid` to `PdfDocument`. @@ -131,27 +135,30 @@ extension DataGridPdfExportExtensions on SfDataGridState { /// ); /// } /// ``` - PdfDocument exportToPdfDocument( - {List? rows, - bool exportStackedHeaders = true, - bool canRepeatHeaders = true, - bool fitAllColumnsInOnePage = false, - bool autoColumnWidth = true, - bool exportTableSummaries = true, - List excludeColumns = const [], - DataGridToPdfConverter? converter, - DataGridCellPdfExportCallback? cellExport, - DataGridPdfHeaderFooterExportCallback? headerFooterExport}) { + PdfDocument exportToPdfDocument({ + List? rows, + bool exportStackedHeaders = true, + bool canRepeatHeaders = true, + bool fitAllColumnsInOnePage = false, + bool autoColumnWidth = true, + bool exportTableSummaries = true, + List excludeColumns = const [], + DataGridToPdfConverter? converter, + DataGridCellPdfExportCallback? cellExport, + DataGridPdfHeaderFooterExportCallback? headerFooterExport, + }) { converter ??= DataGridToPdfConverter(); - _initializeProperties(converter, - autoColumnWidth: autoColumnWidth, - fitAllColumnsInOnePage: fitAllColumnsInOnePage, - canRepeatHeaders: canRepeatHeaders, - exportStackedHeaders: exportStackedHeaders, - cellExport: cellExport, - headerFooterExport: headerFooterExport, - excludeColumns: excludeColumns, - exportTableSummaries: exportTableSummaries); + _initializeProperties( + converter, + autoColumnWidth: autoColumnWidth, + fitAllColumnsInOnePage: fitAllColumnsInOnePage, + canRepeatHeaders: canRepeatHeaders, + exportStackedHeaders: exportStackedHeaders, + cellExport: cellExport, + headerFooterExport: headerFooterExport, + excludeColumns: excludeColumns, + exportTableSummaries: exportTableSummaries, + ); return converter.exportToPdfDocument(widget, rows); } @@ -254,25 +261,28 @@ extension DataGridPdfExportExtensions on SfDataGridState { /// ); /// } /// ``` - PdfGrid exportToPdfGrid( - {List? rows, - bool exportStackedHeaders = true, - bool canRepeatHeaders = true, - bool fitAllColumnsInOnePage = false, - bool autoColumnWidth = true, - bool exportTableSummaries = true, - List excludeColumns = const [], - DataGridToPdfConverter? converter, - DataGridCellPdfExportCallback? cellExport}) { + PdfGrid exportToPdfGrid({ + List? rows, + bool exportStackedHeaders = true, + bool canRepeatHeaders = true, + bool fitAllColumnsInOnePage = false, + bool autoColumnWidth = true, + bool exportTableSummaries = true, + List excludeColumns = const [], + DataGridToPdfConverter? converter, + DataGridCellPdfExportCallback? cellExport, + }) { converter ??= DataGridToPdfConverter(); - _initializeProperties(converter, - autoColumnWidth: autoColumnWidth, - fitAllColumnsInOnePage: fitAllColumnsInOnePage, - canRepeatHeaders: canRepeatHeaders, - cellExport: cellExport, - exportStackedHeaders: exportStackedHeaders, - excludeColumns: excludeColumns, - exportTableSummaries: exportTableSummaries); + _initializeProperties( + converter, + autoColumnWidth: autoColumnWidth, + fitAllColumnsInOnePage: fitAllColumnsInOnePage, + canRepeatHeaders: canRepeatHeaders, + cellExport: cellExport, + exportStackedHeaders: exportStackedHeaders, + excludeColumns: excludeColumns, + exportTableSummaries: exportTableSummaries, + ); return converter.exportToPdfGrid(widget, rows); } @@ -333,7 +343,10 @@ class DataGridToPdfConverter { /// Exports the column headers to Pdf. @protected void exportColumnHeaders( - SfDataGrid dataGrid, List columns, PdfGrid pdfGrid) { + SfDataGrid dataGrid, + List columns, + PdfGrid pdfGrid, + ) { if (columns.isEmpty) { return; } @@ -346,22 +359,29 @@ class DataGridToPdfConverter { /// Exports a column header to Pdf @protected - void exportColumnHeader(SfDataGrid dataGrid, GridColumn column, - String columnName, PdfGrid pdfGrid) { + void exportColumnHeader( + SfDataGrid dataGrid, + GridColumn column, + String columnName, + PdfGrid pdfGrid, + ) { int rowSpan = 0; PdfGridRow columnHeader = pdfGrid.headers[rowIndex]; PdfGridCell pdfCell = columnHeader.cells[_columnIndex]; if (dataGrid.stackedHeaderRows.isNotEmpty && exportStackedHeaders) { rowSpan = getRowSpan( - dataGrid: dataGrid, - isStackedHeader: false, - columnName: columnName, - rowIndex: rowIndex - 1, - columnIndex: dataGrid.columns.indexOf(column)); + dataGrid: dataGrid, + isStackedHeader: false, + columnName: column.columnName, + rowIndex: rowIndex - 1, + columnIndex: dataGrid.columns.indexOf(column), + ); } if (rowSpan > 0) { - columnHeader = pdfGrid.headers[rowIndex - 1]; + // Retrieve the header cell at the given index by subtracting the number of rows + // spanned by the previous cells in the same row. + columnHeader = pdfGrid.headers[rowIndex - rowSpan]; pdfCell = columnHeader.cells[_columnIndex]; pdfCell.rowSpan = rowSpan + 1; pdfCell.value = columnName; @@ -370,13 +390,20 @@ class DataGridToPdfConverter { } pdfCell.style.borders.all = _pdfPen; _exportCellToPdf( - DataGridExportCellType.columnHeader, pdfCell, columnName, column); + DataGridExportCellType.columnHeader, + pdfCell, + columnName, + column, + ); } /// Exports all the data rows to Pdf. @protected void exportRows( - List columns, List rows, PdfGrid pdfGrid) { + List columns, + List rows, + PdfGrid pdfGrid, + ) { for (final DataGridRow row in rows) { exportRow(columns, row, pdfGrid); rowIndex++; @@ -403,7 +430,9 @@ class DataGridToPdfConverter { /// /// If the `rows` is set, the given list of DataGridRow collection is exported. Typically, you can set this property to export PdfDocument exportToPdfDocument( - SfDataGrid dataGrid, List? rows) { + SfDataGrid dataGrid, + List? rows, + ) { final PdfDocument pdfDocument = PdfDocument(); //adding page into pdf document @@ -439,7 +468,8 @@ class DataGridToPdfConverter { //final List columns = dataGrid.columns; _columns = dataGrid.columns .where( - (GridColumn column) => !excludeColumns.contains(column.columnName)) + (GridColumn column) => !excludeColumns.contains(column.columnName), + ) .toList(); //if fit all columns in one page is false then horizontal overflow is true and type is next page @@ -476,7 +506,10 @@ class DataGridToPdfConverter { // Exports the top table summary rows. if (exportTableSummaries) { exportTableSummaryRows( - dataGrid, GridTableSummaryRowPosition.top, pdfGrid); + dataGrid, + GridTableSummaryRowPosition.top, + pdfGrid, + ); } //Export the rows @@ -485,7 +518,10 @@ class DataGridToPdfConverter { // Exports the bottom table summary rows. if (exportTableSummaries) { exportTableSummaryRows( - dataGrid, GridTableSummaryRowPosition.bottom, pdfGrid); + dataGrid, + GridTableSummaryRowPosition.bottom, + pdfGrid, + ); } //Can Repeate header for all pages @@ -493,10 +529,10 @@ class DataGridToPdfConverter { //if fit all columns in one page is false then auto column width is true if (!fitAllColumnsInOnePage && !autoColumnWidth) { - int _columnIndex = 0; + int columnIndex = 0; for (final GridColumn column in columns) { - pdfGrid.columns[_columnIndex].width = column.actualWidth; - _columnIndex++; + pdfGrid.columns[columnIndex].width = column.actualWidth; + columnIndex++; } } @@ -516,30 +552,48 @@ class DataGridToPdfConverter { /// Exports a stacked header row to Pdf. @protected void exportStackedHeaderRow( - SfDataGrid dataGrid, StackedHeaderRow stackedHeaderRow, PdfGrid pdfGrid) { + SfDataGrid dataGrid, + StackedHeaderRow stackedHeaderRow, + PdfGrid pdfGrid, + ) { for (final StackedHeaderCell column in stackedHeaderRow.cells) { int columnSpanValue = 0; - final List columnSequences = - getColumnSequences(dataGrid.columns, column); + final List columnSequences = getColumnSequences( + dataGrid.columns, + column, + ); for (final List indexes in getConsecutiveRanges(columnSequences)) { _columnIndex = indexes.reduce(min); columnSpanValue = indexes.length; final int rowSpan = getRowSpan( - dataGrid: dataGrid, - isStackedHeader: true, - columnIndex: _columnIndex, - stackedHeaderCell: column, - rowIndex: rowIndex - 1); + dataGrid: dataGrid, + isStackedHeader: true, + columnIndex: _columnIndex, + stackedHeaderCell: column, + rowIndex: rowIndex - 1, + ); _exportStackedHeaderCell( - dataGrid, column, _columnIndex, columnSpanValue, rowSpan, pdfGrid); + dataGrid, + column, + _columnIndex, + columnSpanValue, + rowSpan, + pdfGrid, + ); } } } /// Exports a stacked header cell to Pdf. - void _exportStackedHeaderCell(SfDataGrid dataGrid, StackedHeaderCell column, - int columnIndex, int columnSpan, int rowSpan, PdfGrid pdfGrid) { + void _exportStackedHeaderCell( + SfDataGrid dataGrid, + StackedHeaderCell column, + int columnIndex, + int columnSpan, + int rowSpan, + PdfGrid pdfGrid, + ) { PdfGridRow stakedHeaderRow = pdfGrid.headers[rowIndex]; int firstColumnIndex = columnIndex; @@ -547,11 +601,12 @@ class DataGridToPdfConverter { if (excludeColumns.isNotEmpty) { final List startAndEndIndex = getSpannedCellStartAndEndIndex( - columnSpan: columnSpan - 1, - columnIndex: columnIndex, - columns: dataGrid.columns, - excludeColumns: excludeColumns, - startColumnIndex: 0); + columnSpan: columnSpan - 1, + columnIndex: columnIndex, + columns: dataGrid.columns, + excludeColumns: excludeColumns, + startColumnIndex: 0, + ); firstColumnIndex = startAndEndIndex[0]; lastColumnIndex = startAndEndIndex[1]; @@ -560,7 +615,7 @@ class DataGridToPdfConverter { PdfGridCell pdfCell = stakedHeaderRow.cells[firstColumnIndex]; if (firstColumnIndex <= lastColumnIndex) { if (rowSpan > 0) { - stakedHeaderRow = pdfGrid.headers[rowIndex - 1]; + stakedHeaderRow = pdfGrid.headers[rowIndex - rowSpan]; pdfCell = stakedHeaderRow.cells[firstColumnIndex]; pdfCell.columnSpan = columnSpan; pdfCell.rowSpan = rowSpan + 1; @@ -572,15 +627,20 @@ class DataGridToPdfConverter { } pdfCell.style.borders.all = _pdfPen; - _exportCellToPdf(DataGridExportCellType.stackedHeader, pdfCell, - pdfCell.value, dataGrid.columns[firstColumnIndex]); + _exportCellToPdf( + DataGridExportCellType.stackedHeader, + pdfCell, + pdfCell.value, + dataGrid.columns[firstColumnIndex], + ); } /// Gets the cell value required for data rows. @protected Object? getCellValue(GridColumn column, DataGridRow row) { final DataGridCell cellValue = row.getCells().firstWhereOrNull( - (DataGridCell element) => element.columnName == column.columnName)!; + (DataGridCell element) => element.columnName == column.columnName, + )!; return cellValue.value; } @@ -592,13 +652,19 @@ class DataGridToPdfConverter { ) { if (cellExport != null) { final DataGridCellPdfExportDetails details = DataGridCellPdfExportDetails( - cellType, pdfCell, cellValue, column.columnName); + cellType, + pdfCell, + cellValue, + column.columnName, + ); cellExport!(details); } } void _exportHeaderFooter( - PdfPage pdfPage, PdfDocumentTemplate pdfDocumentTemplate) { + PdfPage pdfPage, + PdfDocumentTemplate pdfDocumentTemplate, + ) { if (headerFooterExport != null) { final DataGridPdfHeaderFooterExportDetails details = DataGridPdfHeaderFooterExportDetails(pdfPage, pdfDocumentTemplate); @@ -608,8 +674,11 @@ class DataGridToPdfConverter { /// Export table summary rows to the Excel. @protected - void exportTableSummaryRows(SfDataGrid dataGrid, - GridTableSummaryRowPosition position, PdfGrid pdfGrid) { + void exportTableSummaryRows( + SfDataGrid dataGrid, + GridTableSummaryRowPosition position, + PdfGrid pdfGrid, + ) { if (dataGrid.tableSummaryRows.isEmpty) { return; } @@ -632,40 +701,52 @@ class DataGridToPdfConverter { /// Export table summary row to the Excel. @protected void exportTableSummaryRow( - SfDataGrid dataGrid, GridTableSummaryRow summaryRow, PdfGrid pdfGrid) { + SfDataGrid dataGrid, + GridTableSummaryRow summaryRow, + PdfGrid pdfGrid, + ) { final PdfGridRow tableSummaryRow = pdfGrid.rows.add(); // Resets the column index when creating a new row. _columnIndex = 0; if (summaryRow.showSummaryInRow) { _exportTableSummaryCell( - pdfGrid: pdfGrid, - dataGrid: dataGrid, - summaryRow: summaryRow, - columnSpan: columns.length, - tableSummaryRow: tableSummaryRow); + pdfGrid: pdfGrid, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: columns.length, + tableSummaryRow: tableSummaryRow, + ); } else { int titleColumnCount = summaryRow.titleColumnSpan; if (titleColumnCount > 0) { // To consider the exclude columns in the `titleColumnCount`. - titleColumnCount = - getTitleColumnCount(summaryRow, dataGrid.columns, excludeColumns); + titleColumnCount = getTitleColumnCount( + summaryRow, + dataGrid.columns, + excludeColumns, + ); if (titleColumnCount > 0) { _exportTableSummaryCell( - pdfGrid: pdfGrid, - dataGrid: dataGrid, - summaryRow: summaryRow, - columnSpan: titleColumnCount, - tableSummaryRow: tableSummaryRow); + pdfGrid: pdfGrid, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: titleColumnCount, + tableSummaryRow: tableSummaryRow, + ); } } for (final GridSummaryColumn summaryColumn in summaryRow.columns) { final GridColumn? column = dataGrid.columns.firstWhereOrNull( - (GridColumn element) => - element.columnName == summaryColumn.columnName); + (GridColumn element) => + element.columnName == summaryColumn.columnName, + ); final int summaryColumnIndex = getSummaryColumnIndex( - dataGrid.columns, summaryColumn.columnName, excludeColumns); + dataGrid.columns, + summaryColumn.columnName, + excludeColumns, + ); // Restricts if the column doesn't exist or its column index is less // than the `titleColumnCount`. because the `titleColumn` summary cell @@ -675,33 +756,41 @@ class DataGridToPdfConverter { } _exportTableSummaryCell( - column: column, - pdfGrid: pdfGrid, - dataGrid: dataGrid, - summaryRow: summaryRow, - startColumnIndex: summaryColumnIndex, - summaryColumn: summaryColumn, - tableSummaryRow: tableSummaryRow); + column: column, + pdfGrid: pdfGrid, + dataGrid: dataGrid, + summaryRow: summaryRow, + startColumnIndex: summaryColumnIndex, + summaryColumn: summaryColumn, + tableSummaryRow: tableSummaryRow, + ); } } } - void _exportTableSummaryCell( - {int columnSpan = 0, - int startColumnIndex = 0, - GridColumn? column, - required PdfGrid pdfGrid, - required SfDataGrid dataGrid, - GridSummaryColumn? summaryColumn, - PdfGridRow? tableSummaryRow, - required GridTableSummaryRow summaryRow}) { + void _exportTableSummaryCell({ + int columnSpan = 0, + int startColumnIndex = 0, + GridColumn? column, + required PdfGrid pdfGrid, + required SfDataGrid dataGrid, + GridSummaryColumn? summaryColumn, + PdfGridRow? tableSummaryRow, + required GridTableSummaryRow summaryRow, + }) { final GridColumn? column = dataGrid.columns.firstWhereOrNull( - (GridColumn column) => column.columnName == summaryColumn?.columnName); + (GridColumn column) => column.columnName == summaryColumn?.columnName, + ); final RowColumnIndex rowColumnIndex = RowColumnIndex( - rowIndex, column != null ? dataGrid.columns.indexOf(column) : 0); + rowIndex, + column != null ? dataGrid.columns.indexOf(column) : 0, + ); final String summaryValue = dataGrid.source.calculateSummaryValue( - summaryRow, summaryColumn ?? summaryRow.columns.first, rowColumnIndex); + summaryRow, + summaryColumn ?? summaryRow.columns.first, + rowColumnIndex, + ); PdfGridCell pdfCell = tableSummaryRow!.cells[startColumnIndex]; if (columnSpan > 0) { @@ -716,8 +805,12 @@ class DataGridToPdfConverter { for (int i = 0; i < tableSummaryRow.cells.count; i++) { tableSummaryRow.cells[i].style.borders.all = _pdfPen; } - _exportCellToPdf(DataGridExportCellType.tableSummaryRow, pdfCell, - pdfCell.value, column ?? dataGrid.columns[0]); + _exportCellToPdf( + DataGridExportCellType.tableSummaryRow, + pdfCell, + pdfCell.value, + column ?? dataGrid.columns[0], + ); } } @@ -725,7 +818,11 @@ class DataGridToPdfConverter { class DataGridCellPdfExportDetails { ///the callback event [DataGridCellPdfExportDetails] const DataGridCellPdfExportDetails( - this.cellType, this.pdfCell, this.cellValue, this.columnName); + this.cellType, + this.pdfCell, + this.cellValue, + this.columnName, + ); /// The type of the cell. final DataGridExportCellType cellType; @@ -754,10 +851,10 @@ class DataGridPdfHeaderFooterExportDetails { /// Signature for `cellExport` callback which is passed as an argument in /// `exportToPdfGrid` and `exportToPdfDocument` methods. -typedef DataGridCellPdfExportCallback = void Function( - DataGridCellPdfExportDetails details); +typedef DataGridCellPdfExportCallback = + void Function(DataGridCellPdfExportDetails details); /// Signature for `headerFooterExport` callback which is passed as an argument /// `exportToPdfGrid` and `exportToPdfDocument` methods. -typedef DataGridPdfHeaderFooterExportCallback = void Function( - DataGridPdfHeaderFooterExportDetails details); +typedef DataGridPdfHeaderFooterExportCallback = + void Function(DataGridPdfHeaderFooterExportDetails details); diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart index ed214eb91..857a17b1b 100644 --- a/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart @@ -12,7 +12,8 @@ List getColumnSequences(List columns, StackedHeaderCell cell) { if (cell.columnNames.isNotEmpty) { for (final String columnName in cell.columnNames) { final GridColumn? column = columns.firstWhereOrNull( - (GridColumn column) => column.columnName == columnName); + (GridColumn column) => column.columnName == columnName, + ); if (column != null) { childSequences.add(columns.indexOf(column)); } @@ -49,19 +50,22 @@ List> getConsecutiveRanges(List columnIndexes) { } /// Returns the row span to the stacked header and column header row. -int getRowSpan( - {String? columnName, - required int rowIndex, - required int columnIndex, - required bool isStackedHeader, - required SfDataGrid dataGrid, - StackedHeaderCell? stackedHeaderCell}) { +int getRowSpan({ + String? columnName, + required int rowIndex, + required int columnIndex, + required bool isStackedHeader, + required SfDataGrid dataGrid, + StackedHeaderCell? stackedHeaderCell, +}) { int startIndex = 0, endIndex = 0, rowSpan = 0; if (isStackedHeader && stackedHeaderCell != null) { final List> columnIndexes = getConsecutiveRanges( - getColumnSequences(dataGrid.columns, stackedHeaderCell)); - final List? spannedColumn = columnIndexes - .singleWhereOrNull((List column) => column.first == columnIndex); + getColumnSequences(dataGrid.columns, stackedHeaderCell), + ); + final List? spannedColumn = columnIndexes.singleWhereOrNull( + (List column) => column.first == columnIndex, + ); if (spannedColumn != null) { startIndex = spannedColumn.reduce(min); endIndex = startIndex + spannedColumn.length - 1; @@ -78,7 +82,8 @@ int getRowSpan( for (final StackedHeaderCell stackedColumn in stackedHeaderRow.cells) { if (isStackedHeader) { final List> columnIndexes = getConsecutiveRanges( - getColumnSequences(dataGrid.columns, stackedColumn)); + getColumnSequences(dataGrid.columns, stackedColumn), + ); for (final List column in columnIndexes) { if ((startIndex >= column.first && startIndex <= column.last) || (endIndex >= column.first && endIndex <= column.last)) { @@ -104,12 +109,13 @@ int getRowSpan( /// Returns the start and end index of the given spanned columns when the /// exclude columns are applied. -List getSpannedCellStartAndEndIndex( - {required int columnSpan, - required int columnIndex, - required int startColumnIndex, - required List columns, - required List excludeColumns}) { +List getSpannedCellStartAndEndIndex({ + required int columnSpan, + required int columnIndex, + required int startColumnIndex, + required List columns, + required List excludeColumns, +}) { int excludeColumnsCount = 0; int firstColumnIndex = startColumnIndex + columnIndex; int lastColumnIndex = firstColumnIndex + columnSpan; @@ -117,7 +123,8 @@ List getSpannedCellStartAndEndIndex( for (final String columnName in excludeColumns) { final GridColumn? column = columns.firstWhereOrNull( - (GridColumn element) => element.columnName == columnName); + (GridColumn element) => element.columnName == columnName, + ); if (column != null) { excludeColumnIndexes.add(columns.indexOf(column)); } @@ -126,19 +133,26 @@ List getSpannedCellStartAndEndIndex( if (firstColumnIndex > startColumnIndex) { // Updates the first and last column index if any exclude column exists // before the `firstColumnIndex`. - excludeColumnsCount = - excludeColumnIndexes.where((int index) => index < columnIndex).length; - - firstColumnIndex = - max(startColumnIndex, firstColumnIndex - excludeColumnsCount); - lastColumnIndex = - max(startColumnIndex, lastColumnIndex - excludeColumnsCount); + excludeColumnsCount = excludeColumnIndexes + .where((int index) => index < columnIndex) + .length; + + firstColumnIndex = max( + startColumnIndex, + firstColumnIndex - excludeColumnsCount, + ); + lastColumnIndex = max( + startColumnIndex, + lastColumnIndex - excludeColumnsCount, + ); } // To remove the in-between excluded columns from the `lastColumnIndex`. excludeColumnsCount = excludeColumnIndexes - .where((int index) => - index >= columnIndex && index <= columnIndex + columnSpan) + .where( + (int index) => + index >= columnIndex && index <= columnIndex + columnSpan, + ) .length; lastColumnIndex -= excludeColumnsCount; @@ -148,8 +162,11 @@ List getSpannedCellStartAndEndIndex( /// ----------------- Table summary row helper methods -------------------// /// Gets title column count of the given summary column. -int getTitleColumnCount(GridTableSummaryRow summaryRow, - List columns, List excludeColumns) { +int getTitleColumnCount( + GridTableSummaryRow summaryRow, + List columns, + List excludeColumns, +) { int currentColumnSpan = 0; for (int i = 0; i < summaryRow.titleColumnSpan; i++) { if (i <= columns.length) { @@ -163,7 +180,10 @@ int getTitleColumnCount(GridTableSummaryRow summaryRow, /// Returns the column index to the summary column. int getSummaryColumnIndex( - List columns, String columnName, List excludeColumns) { + List columns, + String columnName, + List excludeColumns, +) { if (excludeColumns.contains(columnName)) { return -1; } @@ -172,6 +192,7 @@ int getSummaryColumnIndex( .where((GridColumn column) => !excludeColumns.contains(column.columnName)) .toList(); final GridColumn? column = visibleColumns.firstWhereOrNull( - (GridColumn element) => element.columnName == columnName); + (GridColumn element) => element.columnName == columnName, + ); return column != null ? visibleColumns.indexOf(column) : -1; } diff --git a/packages/syncfusion_flutter_datagrid_export/pubspec.yaml b/packages/syncfusion_flutter_datagrid_export/pubspec.yaml index 0ece5734a..79995a53f 100644 --- a/packages/syncfusion_flutter_datagrid_export/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid_export/pubspec.yaml @@ -1,10 +1,10 @@ name: syncfusion_flutter_datagrid_export description: The Syncfusion Flutter DataGrid Export library is used to export the DataGrid content to Excel and Pdf format with several customization options. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datagrid_export environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 flutter: ">=1.17.0" dependencies: @@ -13,11 +13,20 @@ dependencies: syncfusion_flutter_datagrid: path: ../syncfusion_flutter_datagrid + syncfusion_flutter_xlsio: path: ../syncfusion_flutter_xlsio + + syncfusion_officecore: + path: ../syncfusion_officecore + syncfusion_flutter_pdf: path: ../syncfusion_flutter_pdf + + + + collection: ">=1.9.0 <=2.0.0" + - collection: ">=1.9.0 <=1.15.0" flutter: null diff --git a/packages/syncfusion_flutter_datepicker/CHANGELOG.md b/packages/syncfusion_flutter_datepicker/CHANGELOG.md index f6ba2fa6b..8d3bb272c 100644 --- a/packages/syncfusion_flutter_datepicker/CHANGELOG.md +++ b/packages/syncfusion_flutter_datepicker/CHANGELOG.md @@ -1,3 +1,66 @@ +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter datepicker widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter datepicker example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter datepicker example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.11+1] - 12/03/2025 + +**Bug fixes** + +* \#BD693108 Now, the header text updates with the default Material theme color on the mobile web browser platform. + +## [28.2.7] - 25/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter datepicker widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.2.3] - 11/21/2024 + +**Bug fixes** +* \#GH2119 - The hover color will no longer be applied when swiping on the widget using touch on Windows and web platforms. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter datepicker widget has been updated to Flutter SDK 3.24.0. + +## 03/15/2024 + +**General** +* Provided th​e Material 3 themes support. + +**Bug fixes** +* \#FB50679 - Now, text size remains consistent when the app state or themes gets changed. + +## [24.1.46] - 01/17/2024 + +**General** +* Upgraded the `intl` package to the latest version 0.19.0. + ## [19.4.38] - 12/17/2021 **Features** * Provided support to extendable range selection direction in the date range picker. @@ -124,4 +187,4 @@ Initial release. * Theme support. * Right to left support. * Accessibility support. -* Selection style customization. \ No newline at end of file +* Selection style customization. diff --git a/packages/syncfusion_flutter_datepicker/LICENSE b/packages/syncfusion_flutter_datepicker/LICENSE index de7e30576..ba61343ef 100644 --- a/packages/syncfusion_flutter_datepicker/LICENSE +++ b/packages/syncfusion_flutter_datepicker/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Date Picker 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. +Syncfusion® Flutter Date Picker 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_datepicker/README.md b/packages/syncfusion_flutter_datepicker/README.md index 30291c7ec..e46709b90 100644 --- a/packages/syncfusion_flutter_datepicker/README.md +++ b/packages/syncfusion_flutter_datepicker/README.md @@ -4,7 +4,7 @@ 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 [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. +**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 @@ -19,7 +19,7 @@ The Flutter Date Range Picker is a lightweight widget that allows users to easil - [Date selection](#date-selection) - [Limit the date selection range](#limit-the-date-selection-range) - [Support and feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Date-range-picker features @@ -105,23 +105,20 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about the Syncfusion Flutter DateRangePicker. +Take a look at the following to learn more about the Syncfusion® Flutter DateRangePicker. -* [Syncfusion Flutter widgets product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter widgets product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation](https://help.syncfusion.com/flutter/introduction/overview) * [Knowledge base](https://www.syncfusion.com/kb) @@ -208,11 +205,11 @@ Widget build(BuildContext context) { ## 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +## 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_datepicker/analysis_options.yaml b/packages/syncfusion_flutter_datepicker/analysis_options.yaml index 7d8cc35a0..7bd9823d9 100644 --- a/packages/syncfusion_flutter_datepicker/analysis_options.yaml +++ b/packages/syncfusion_flutter_datepicker/analysis_options.yaml @@ -1,7 +1 @@ -include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore - invalid_dependency: ignore - avoid_as: ignore +include: package:syncfusion_flutter_core/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_datepicker/assets/fonts/Roboto-Medium.ttf b/packages/syncfusion_flutter_datepicker/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 000000000..d629e9848 Binary files /dev/null and b/packages/syncfusion_flutter_datepicker/assets/fonts/Roboto-Medium.ttf differ diff --git a/packages/syncfusion_flutter_datepicker/example/README.md b/packages/syncfusion_flutter_datepicker/example/README.md new file mode 100644 index 000000000..5bb6cbbed --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/README.md @@ -0,0 +1,16 @@ +# example + +Syncfusion date picker example project + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_flutter_datepicker/example/android/.gitignore b/packages/syncfusion_flutter_datepicker/example/android/.gitignore index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/.gitignore +++ b/packages/syncfusion_flutter_datepicker/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_datepicker/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_datepicker/example/android/app/build.gradle.kts new file mode 100644 index 000000000..21ecea973 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_flutter_datepicker/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_datepicker/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_datepicker/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_datepicker/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_datepicker/example/android/app/src/main/AndroidManifest.xml index 3f41384db..74a78b939 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_datepicker/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/packages/syncfusion_flutter_datepicker/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_datepicker/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000d..ac81bae64 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/syncfusion_flutter_datepicker/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_datepicker/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_datepicker/example/android/app/src/main/res/values-night/styles.xml index 449a9f930..06952be74 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/app/src/main/res/values-night/styles.xml +++ b/packages/syncfusion_flutter_datepicker/example/android/app/src/main/res/values-night/styles.xml @@ -3,14 +3,14 @@ diff --git a/packages/syncfusion_flutter_datepicker/example/android/build.gradle.kts b/packages/syncfusion_flutter_datepicker/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_datepicker/example/android/gradle.properties b/packages/syncfusion_flutter_datepicker/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/gradle.properties +++ b/packages/syncfusion_flutter_datepicker/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_datepicker/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_datepicker/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_flutter_datepicker/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_datepicker/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_datepicker/example/android/settings.gradle.kts b/packages/syncfusion_flutter_datepicker/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_datepicker/example/ios/.gitignore b/packages/syncfusion_flutter_datepicker/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/.gitignore +++ b/packages/syncfusion_flutter_datepicker/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_datepicker/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_datepicker/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Debug.xcconfig index e8efba114..592ceee85 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Release.xcconfig index 399e9340e..592ceee85 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_datepicker/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Podfile b/packages/syncfusion_flutter_datepicker/example/ios/Podfile new file mode 100644 index 000000000..6697f0a53 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/ios/Podfile @@ -0,0 +1,87 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + use_frameworks! + use_modular_headers! + + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.pbxproj index 5f1bf4c32..a6a988ee6 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -125,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -179,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -208,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -298,15 +371,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -316,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -432,15 +550,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -459,15 +572,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -480,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_datepicker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_datepicker/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_datepicker/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_datepicker/example/lib/main.dart b/packages/syncfusion_flutter_datepicker/example/lib/main.dart index 09d50a252..ec86cd975 100644 --- a/packages/syncfusion_flutter_datepicker/example/lib/main.dart +++ b/packages/syncfusion_flutter_datepicker/example/lib/main.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages import 'package:intl/intl.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; void main() { - return runApp(MyApp()); + return runApp(const MyApp()); } /// My app class to display the date range picker class MyApp extends StatefulWidget { + const MyApp({super.key}); + @override MyAppState createState() => MyAppState(); } @@ -36,7 +39,8 @@ class MyAppState extends State { /// multi range. setState(() { if (args.value is PickerDateRange) { - _range = '${DateFormat('dd/MM/yyyy').format(args.value.startDate)} -' + _range = + '${DateFormat('dd/MM/yyyy').format(args.value.startDate)} -' // ignore: lines_longer_than_80_chars ' ${DateFormat('dd/MM/yyyy').format(args.value.endDate ?? args.value.startDate)}'; } else if (args.value is DateTime) { @@ -52,43 +56,44 @@ class MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('DatePicker demo'), + home: Scaffold( + appBar: AppBar(title: const Text('DatePicker demo')), + body: Stack( + children: [ + Positioned( + left: 0, + right: 0, + top: 0, + height: 80, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Selected date: $_selectedDate'), + Text('Selected date count: $_dateCount'), + Text('Selected range: $_range'), + Text('Selected ranges count: $_rangeCount'), + ], + ), ), - body: Stack( - children: [ - Positioned( - left: 0, - right: 0, - top: 0, - height: 80, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Selected date: $_selectedDate'), - Text('Selected date count: $_dateCount'), - Text('Selected range: $_range'), - Text('Selected ranges count: $_rangeCount') - ], - ), + Positioned( + left: 0, + top: 80, + right: 0, + bottom: 0, + child: SfDateRangePicker( + onSelectionChanged: _onSelectionChanged, + selectionMode: DateRangePickerSelectionMode.range, + initialSelectedRange: PickerDateRange( + DateTime.now().subtract(const Duration(days: 4)), + DateTime.now().add(const Duration(days: 3)), ), - Positioned( - left: 0, - top: 80, - right: 0, - bottom: 0, - child: SfDateRangePicker( - onSelectionChanged: _onSelectionChanged, - selectionMode: DateRangePickerSelectionMode.range, - initialSelectedRange: PickerDateRange( - DateTime.now().subtract(const Duration(days: 4)), - DateTime.now().add(const Duration(days: 3))), - ), - ) - ], - ))); + ), + ), + ], + ), + ), + ); } } diff --git a/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_datepicker/example/macos/.gitignore b/packages/syncfusion_flutter_datepicker/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/.gitignore +++ b/packages/syncfusion_flutter_datepicker/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_datepicker/example/macos/Flutter/GeneratedPluginRegistrant.swift index cf9814600..cccf817a5 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_flutter_datepicker/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -2,8 +2,6 @@ // Generated file. Do not edit. // -// clang-format off - import FlutterMacOS import Foundation diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..fca5bdc5c 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,6 +61,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -106,6 +134,7 @@ isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,6 +218,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..9963aa361 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_datepicker/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - + + + + + + - - Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..dda9752f6 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_datepicker/example/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = example PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_datepicker/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_datepicker/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_datepicker/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_datepicker/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_datepicker/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_datepicker/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_datepicker/example/pubspec.yaml b/packages/syncfusion_flutter_datepicker/example/pubspec.yaml index bb4c76e54..f38896994 100644 --- a/packages/syncfusion_flutter_datepicker/example/pubspec.yaml +++ b/packages/syncfusion_flutter_datepicker/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Syncfusion date picker example project version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: diff --git a/packages/syncfusion_flutter_datepicker/example/web/index.html b/packages/syncfusion_flutter_datepicker/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_datepicker/example/web/index.html +++ b/packages/syncfusion_flutter_datepicker/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) 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 dec78dfce..13feb9021 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 @@ -9,15 +9,16 @@ 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 'package:syncfusion_flutter_datepicker/datepicker.dart'; +import '../../datepicker.dart'; import 'month_view.dart'; import 'picker_helper.dart'; +import 'theme.dart'; import 'year_view.dart'; /// Signature for callback that reports that the picker state value changed. -typedef UpdatePickerState = void Function( - PickerStateArgs updatePickerStateDetails); +typedef UpdatePickerState = + void Function(PickerStateArgs updatePickerStateDetails); /// Signature for callback that reports that a current view or current visible /// date range changes. @@ -30,8 +31,10 @@ typedef UpdatePickerState = void Function( /// See also: /// * [SfDateRangePicker.onViewChanged], which matches this signature. /// * [SfDateRangePicker], which uses this signature in one of it's callback. -typedef DateRangePickerViewChangedCallback = void Function( - DateRangePickerViewChangedArgs dateRangePickerViewChangedArgs); +typedef DateRangePickerViewChangedCallback = + void Function( + DateRangePickerViewChangedArgs dateRangePickerViewChangedArgs, + ); /// Signature for callback that reports that a current view or current visible /// date range changes. @@ -45,8 +48,10 @@ typedef DateRangePickerViewChangedCallback = void Function( /// * [SfHijriDateRangePicker.onViewChanged], which matches this signature. /// * [SfHijriDateRangePicker], which uses this signature in one of it's /// callback. -typedef HijriDatePickerViewChangedCallback = void Function( - HijriDatePickerViewChangedArgs hijriDatePickerViewChangedArgs); +typedef HijriDatePickerViewChangedCallback = + void Function( + HijriDatePickerViewChangedArgs hijriDatePickerViewChangedArgs, + ); /// Signature for callback that reports that a new dates or date ranges /// selected. @@ -63,30 +68,39 @@ typedef HijriDatePickerViewChangedCallback = void Function( /// * [SfDateRangePicker], which uses this signature in one of it's callback. /// * [SfHijriDateRangePicker], which uses this signature in one of it's /// callback. -typedef DateRangePickerSelectionChangedCallback = void Function( - DateRangePickerSelectionChangedArgs dateRangePickerSelectionChangedArgs); +typedef DateRangePickerSelectionChangedCallback = + void Function( + DateRangePickerSelectionChangedArgs dateRangePickerSelectionChangedArgs, + ); // method that raise the picker selection changed call back with the given // parameters. -void _raiseSelectionChangedCallback(_SfDateRangePicker picker, - {dynamic value}) { +void _raiseSelectionChangedCallback( + _SfDateRangePicker picker, { + dynamic value, +}) { picker.onSelectionChanged?.call(DateRangePickerSelectionChangedArgs(value)); } // method that raises the visible dates changed call back with the given // parameters. -void _raisePickerViewChangedCallback(_SfDateRangePicker picker, - {dynamic visibleDateRange, dynamic view}) { +void _raisePickerViewChangedCallback( + _SfDateRangePicker picker, { + dynamic visibleDateRange, + dynamic view, +}) { if (picker.onViewChanged == null) { return; } if (picker.isHijri) { - picker - .onViewChanged(HijriDatePickerViewChangedArgs(visibleDateRange, view)); + picker.onViewChanged( + HijriDatePickerViewChangedArgs(visibleDateRange, view), + ); } else { - picker - .onViewChanged(DateRangePickerViewChangedArgs(visibleDateRange, view)); + picker.onViewChanged( + DateRangePickerViewChangedArgs(visibleDateRange, view), + ); } } @@ -198,85 +212,90 @@ 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 = DateRangePickerView.month, - this.selectionMode = DateRangePickerSelectionMode.single, - this.headerHeight = 40, - this.todayHighlightColor, - this.backgroundColor, - 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, - 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, - this.rangeSelectionColor, - this.selectionTextStyle, - this.rangeTextStyle, - this.monthFormat, - this.cellBuilder, - this.showTodayButton = false, - this.selectableDayPredicate, - this.extendableRangeSelectionDirection = - ExtendableRangeSelectionDirection.both}) - : assert(headerHeight >= -1), - assert(minDate == null || maxDate == null || minDate.isBefore(maxDate)), - assert(minDate == null || maxDate == null || maxDate.isAfter(minDate)), - assert(viewSpacing >= 0), - initialSelectedDate = - controller != null && controller.selectedDate != null - ? controller.selectedDate - : initialSelectedDate, - initialSelectedDates = - controller != null && controller.selectedDates != null - ? controller.selectedDates - : initialSelectedDates, - initialSelectedRange = - controller != null && controller.selectedRange != null - ? controller.selectedRange - : initialSelectedRange, - initialSelectedRanges = - controller != null && controller.selectedRanges != null - ? controller.selectedRanges - : initialSelectedRanges, - view = controller != null && controller.view != null - ? controller.view! - : view, - initialDisplayDate = - controller != null && controller.displayDate != null - ? controller.displayDate! - : initialDisplayDate ?? DateTime.now(), - minDate = minDate ?? DateTime(1900, 01, 01), - maxDate = maxDate ?? DateTime(2100, 12, 31), - viewSpacing = enableMultiView ? viewSpacing : 0, - super(key: key); + SfDateRangePicker({ + 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, + 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, + 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, + this.rangeSelectionColor, + this.selectionTextStyle, + this.rangeTextStyle, + this.monthFormat, + this.cellBuilder, + this.showTodayButton = false, + this.selectableDayPredicate, + this.extendableRangeSelectionDirection = + ExtendableRangeSelectionDirection.both, + }) : assert(headerHeight >= -1), + assert( + minDate == null || + maxDate == null || + minDate.isBefore(maxDate) || + minDate == maxDate, + ), + assert(viewSpacing >= 0), + initialSelectedDate = + controller != null && controller.selectedDate != null + ? controller.selectedDate + : initialSelectedDate, + initialSelectedDates = + controller != null && controller.selectedDates != null + ? controller.selectedDates + : initialSelectedDates, + initialSelectedRange = + controller != null && controller.selectedRange != null + ? controller.selectedRange + : initialSelectedRange, + initialSelectedRanges = + controller != null && controller.selectedRanges != null + ? controller.selectedRanges + : initialSelectedRanges, + view = + controller != null && controller.view != null + ? controller.view! + : view, + initialDisplayDate = + controller != null && controller.displayDate != null + ? controller.displayDate! + : initialDisplayDate ?? DateTime.now(), + minDate = minDate ?? DateTime(1900), + maxDate = maxDate ?? DateTime(2100, 12, 31), + viewSpacing = enableMultiView ? viewSpacing : 0, + super(key: key); /// Defines the view for the [SfDateRangePicker]. /// @@ -1177,7 +1196,7 @@ class SfDateRangePicker extends StatelessWidget { /// controller: _pickerController, /// view: DateRangePickerView.month, /// selectionMode: DateRangePickerSelectionMode.range, - /// rangeSelectionColor: Colors.red.withOpacity(0.4), + /// rangeSelectionColor: Colors.red.withValues(alpha:0.4), /// ), /// ), /// ); @@ -1386,7 +1405,7 @@ class SfDateRangePicker extends StatelessWidget { /// width: 1), /// shape: BoxShape.circle), /// disabledDatesDecoration: BoxDecoration( - /// color: const Color(0xFFDFDFDF).withOpacity(0.2), + /// color: const Color(0xFFDFDFDF).withValues(alpha:0.2), /// border: Border.all(color: const Color(0xFFB6B6B6), /// width: 1), /// shape: BoxShape.circle), @@ -1474,7 +1493,7 @@ class SfDateRangePicker extends StatelessWidget { /// width: 1), /// shape: BoxShape.circle), /// disabledDatesDecoration: BoxDecoration( - /// color: const Color(0xFFDFDFDF).withOpacity(0.2), + /// color: const Color(0xFFDFDFDF).withValues(alpha:0.2), /// border: Border.all(color: const Color(0xFFB6B6B6), /// width: 1), /// shape: BoxShape.circle), @@ -2663,14 +2682,30 @@ class SfDateRangePicker extends StatelessWidget { 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( + 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)); @@ -2678,68 +2713,112 @@ class SfDateRangePicker extends StatelessWidget { properties.add(ColorProperty('backgroundColor', backgroundColor)); properties.add(ColorProperty('selectionColor', selectionColor)); properties.add( - ColorProperty('startRangeSelectionColor', startRangeSelectionColor)); - properties - .add(ColorProperty('endRangeSelectionColor', endRangeSelectionColor)); + 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('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)); + DiagnosticsProperty( + 'cellBuilder', + cellBuilder, + ), + ); + properties.add( + DiagnosticsProperty('allowViewNavigation', allowViewNavigation), + ); + properties.add( + DiagnosticsProperty('toggleDaySelection', toggleDaySelection), + ); + properties.add( + DiagnosticsProperty('enablePastDates', enablePastDates), + ); properties.add( - DiagnosticsProperty('toggleDaySelection', toggleDaySelection)); - properties - .add(DiagnosticsProperty('enablePastDates', enablePastDates)); + DiagnosticsProperty('showNavigationArrow', showNavigationArrow), + ); properties.add( - DiagnosticsProperty('showNavigationArrow', showNavigationArrow)); - properties - .add(DiagnosticsProperty('showActionButtons', showActionButtons)); + 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('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( + DiagnosticsProperty('controller', controller), + ); properties.add(headerStyle.toDiagnosticsNode(name: 'headerStyle')); properties.add(yearCellStyle.toDiagnosticsNode(name: 'yearCellStyle')); - properties - .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); + properties.add( + monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings'), + ); properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); - properties - .add(DiagnosticsProperty('showTodayButton', showTodayButton)); - properties.add(DiagnosticsProperty( - 'selectableDayPredicate', selectableDayPredicate)); - properties.add(EnumProperty( + properties.add( + DiagnosticsProperty('showTodayButton', showTodayButton), + ); + properties.add( + DiagnosticsProperty( + 'selectableDayPredicate', + selectableDayPredicate, + ), + ); + properties.add( + EnumProperty( 'extendableRangeSelectionDirection', - extendableRangeSelectionDirection)); + extendableRangeSelectionDirection, + ), + ); } } @@ -2896,33 +2975,34 @@ class SfHijriDateRangePicker extends StatelessWidget { this.selectableDayPredicate, this.extendableRangeSelectionDirection = ExtendableRangeSelectionDirection.both, - }) : initialSelectedDate = - controller != null && controller.selectedDate != null - ? controller.selectedDate - : initialSelectedDate, - initialSelectedDates = - controller != null && controller.selectedDates != null - ? controller.selectedDates - : initialSelectedDates, - initialSelectedRange = - controller != null && controller.selectedRange != null - ? controller.selectedRange - : initialSelectedRange, - initialSelectedRanges = - controller != null && controller.selectedRanges != null - ? controller.selectedRanges - : initialSelectedRanges, - view = controller != null && controller.view != null - ? controller.view! - : view, - initialDisplayDate = - controller != null && controller.displayDate != null - ? controller.displayDate! - : initialDisplayDate ?? HijriDateTime.now(), - minDate = minDate ?? HijriDateTime(1356, 01, 01), - maxDate = maxDate ?? HijriDateTime(1499, 12, 30), - viewSpacing = enableMultiView ? viewSpacing : 0, - super(key: key); + }) : initialSelectedDate = + controller != null && controller.selectedDate != null + ? controller.selectedDate + : initialSelectedDate, + initialSelectedDates = + controller != null && controller.selectedDates != null + ? controller.selectedDates + : initialSelectedDates, + initialSelectedRange = + controller != null && controller.selectedRange != null + ? controller.selectedRange + : initialSelectedRange, + initialSelectedRanges = + controller != null && controller.selectedRanges != null + ? controller.selectedRanges + : initialSelectedRanges, + view = + controller != null && controller.view != null + ? controller.view! + : view, + initialDisplayDate = + controller != null && controller.displayDate != null + ? controller.displayDate! + : initialDisplayDate ?? HijriDateTime.now(), + minDate = minDate ?? HijriDateTime(1356, 01, 01), + maxDate = maxDate ?? HijriDateTime(1499, 12, 30), + viewSpacing = enableMultiView ? viewSpacing : 0, + super(key: key); /// Defines the view for the [SfHijriDateRangePicker]. /// @@ -3828,7 +3908,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// controller: _pickerController, /// view: HijriDatePickerView.month, /// selectionMode: DateRangePickerSelectionMode.range, - /// rangeSelectionColor: Colors.red.withOpacity(0.4), + /// rangeSelectionColor: Colors.red.withValues(alpha:0.4), /// ), /// ), /// ); @@ -4023,7 +4103,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// fontWeight: FontWeight.w500, /// color: Colors.red), /// disabledDatesDecoration: BoxDecoration( - /// color: const Color(0xFFDFDFDF).withOpacity(0.2), + /// color: const Color(0xFFDFDFDF).withValues(alpha:0.2), /// border: Border.all(color: const Color(0xFFB6B6B6), /// width: 1), /// shape: BoxShape.circle), @@ -4101,7 +4181,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// fontWeight: FontWeight.w500, /// color: Colors.red), /// disabledDatesDecoration: BoxDecoration( - /// color: const Color(0xFFDFDFDF).withOpacity(0.2), + /// color: const Color(0xFFDFDFDF).withValues(alpha:0.2), /// border: Border.all(color: const Color(0xFFB6B6B6), /// width: 1), /// shape: BoxShape.circle), @@ -5304,14 +5384,30 @@ class SfHijriDateRangePicker extends StatelessWidget { 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( + 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)); @@ -5319,123 +5415,173 @@ class SfHijriDateRangePicker extends StatelessWidget { properties.add(ColorProperty('backgroundColor', backgroundColor)); properties.add(ColorProperty('selectionColor', selectionColor)); properties.add( - ColorProperty('startRangeSelectionColor', startRangeSelectionColor)); - properties - .add(ColorProperty('endRangeSelectionColor', endRangeSelectionColor)); + 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('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)); + DiagnosticsProperty( + 'cellBuilder', + cellBuilder, + ), + ); + properties.add( + DiagnosticsProperty('allowViewNavigation', allowViewNavigation), + ); + properties.add( + DiagnosticsProperty('toggleDaySelection', toggleDaySelection), + ); + properties.add( + DiagnosticsProperty('enablePastDates', enablePastDates), + ); properties.add( - DiagnosticsProperty('toggleDaySelection', toggleDaySelection)); - properties - .add(DiagnosticsProperty('enablePastDates', enablePastDates)); + DiagnosticsProperty('showNavigationArrow', showNavigationArrow), + ); properties.add( - DiagnosticsProperty('showNavigationArrow', showNavigationArrow)); - properties - .add(DiagnosticsProperty('showActionButtons', showActionButtons)); + 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('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( + DiagnosticsProperty('controller', controller), + ); properties.add(headerStyle.toDiagnosticsNode(name: 'headerStyle')); properties.add(yearCellStyle.toDiagnosticsNode(name: 'yearCellStyle')); - properties - .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); + properties.add( + monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings'), + ); properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); - properties - .add(DiagnosticsProperty('showTodayButton', showTodayButton)); - properties.add(DiagnosticsProperty( - 'selectableDayPredicate', selectableDayPredicate)); - properties.add(EnumProperty( + properties.add( + DiagnosticsProperty('showTodayButton', showTodayButton), + ); + properties.add( + DiagnosticsProperty( + 'selectableDayPredicate', + selectableDayPredicate, + ), + ); + properties.add( + EnumProperty( 'extendableRangeSelectionDirection', - extendableRangeSelectionDirection)); + extendableRangeSelectionDirection, + ), + ); } } @immutable class _SfDateRangePicker extends StatefulWidget { - const _SfDateRangePicker( - {Key? key, - required this.view, - required this.selectionMode, - this.isHijri = false, - required this.headerHeight, - this.todayHighlightColor, - this.backgroundColor, - this.initialSelectedDate, - this.initialSelectedDates, - this.initialSelectedRange, - this.initialSelectedRanges, - this.toggleDaySelection = false, - this.enablePastDates = true, - this.showNavigationArrow = false, - required this.selectionShape, - required this.navigationDirection, - this.controller, - this.onViewChanged, - this.onSelectionChanged, - 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, - this.rangeSelectionColor, - this.selectionTextStyle, - this.rangeTextStyle, - this.monthFormat, - this.cellBuilder, - this.showTodayButton = false, - this.selectableDayPredicate, - this.extendableRangeSelectionDirection = - ExtendableRangeSelectionDirection.both}) - : super(key: key); + const _SfDateRangePicker({ + Key? key, + required this.view, + required this.selectionMode, + this.isHijri = false, + required this.headerHeight, + this.todayHighlightColor, + this.backgroundColor, + this.initialSelectedDate, + this.initialSelectedDates, + this.initialSelectedRange, + this.initialSelectedRanges, + this.toggleDaySelection = false, + this.enablePastDates = true, + this.showNavigationArrow = false, + required this.selectionShape, + required this.navigationDirection, + this.controller, + this.onViewChanged, + this.onSelectionChanged, + 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, + this.rangeSelectionColor, + this.selectionTextStyle, + this.rangeTextStyle, + this.monthFormat, + this.cellBuilder, + this.showTodayButton = false, + this.selectableDayPredicate, + this.extendableRangeSelectionDirection = + ExtendableRangeSelectionDirection.both, + }) : super(key: key); final DateRangePickerView view; @@ -5554,11 +5700,11 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> late SfDateRangePickerThemeData _datePickerTheme; /// Holds the date collection after the display date. - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type final List _forwardDateCollection = []; /// Holds the date collection before the display date. - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type final List _backwardDateCollection = []; /// Holds the current scroll view key and it used to re initialize the @@ -5598,6 +5744,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> @override void initState() { + _textScaleFactor = 1; _isRtl = false; //// Update initial values to controller. _initPickerController(); @@ -5606,25 +5753,30 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _updateSelectionValues(); _view = DateRangePickerHelper.getPickerView(_controller.view); _updateCurrentVisibleDates(); - _headerVisibleDates = - ValueNotifier>(_currentViewVisibleDates); - _viewHeaderVisibleDates = - ValueNotifier>(_currentViewVisibleDates); + _headerVisibleDates = ValueNotifier>( + _currentViewVisibleDates, + ); + _viewHeaderVisibleDates = ValueNotifier>( + _currentViewVisibleDates, + ); _controller.addPropertyChangedListener(_pickerValueChangedListener); - _previousSelectedValue = PickerStateArgs() - ..selectedDate = _controller.selectedDate - ..selectedDates = - DateRangePickerHelper.cloneList(_controller.selectedDates) - ..selectedRange = _controller.selectedRange - ..selectedRanges = - DateRangePickerHelper.cloneList(_controller.selectedRanges); + _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; + _textScaleFactor = MediaQuery.textScalerOf(context).scale(1); final TextDirection direction = Directionality.of(context); // default width value will be device width when the widget placed inside a // infinity width widget @@ -5634,58 +5786,58 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _minPickerHeight = 300; _locale = Localizations.localeOf(context); _localizations = SfLocalizations.of(context); - _datePickerTheme = _getPickerThemeData( - SfDateRangePickerTheme.of(context), Theme.of(context).colorScheme); _isRtl = direction == TextDirection.rtl; - _isMobilePlatform = - DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); + _isMobilePlatform = DateRangePickerHelper.isMobileLayout( + Theme.of(context).platform, + ); _fadeInController ??= AnimationController( - duration: Duration(milliseconds: _isMobilePlatform ? 500 : 600), - vsync: this) - ..addListener(_updateFadeAnimation); - _fadeIn ??= Tween( - begin: 0.1, - end: 1, - ).animate(CurvedAnimation( - parent: _fadeInController!, - curve: Curves.easeIn, - )); + duration: Duration(milliseconds: _isMobilePlatform ? 500 : 600), + vsync: this, + )..addListener(_updateFadeAnimation); + _fadeIn ??= Tween(begin: 0.1, end: 1).animate( + CurvedAnimation(parent: _fadeInController!, curve: Curves.easeIn), + ); super.didChangeDependencies(); } @override void didUpdateWidget(_SfDateRangePicker oldWidget) { if (oldWidget.controller != widget.controller) { - oldWidget.controller - ?.removePropertyChangedListener(_pickerValueChangedListener); + oldWidget.controller?.removePropertyChangedListener( + _pickerValueChangedListener, + ); _controller.removePropertyChangedListener(_pickerValueChangedListener); if (widget.controller != null) { - _controller.selectedDate = widget.controller!.selectedDate; + _controller = widget.controller; _controller.selectedDates = _getSelectedDates( - DateRangePickerHelper.cloneList(widget.controller!.selectedDates)); - _controller.selectedRange = widget.controller!.selectedRange; + DateRangePickerHelper.cloneList(widget.controller!.selectedDates), + ); _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.displayDate ??= _currentDate; _currentDate = getValidDate( - widget.minDate, widget.maxDate, _controller.displayDate); + widget.minDate, + widget.maxDate, + _controller.displayDate, + ); } else { _initPickerController(); } - _controller.view ??= widget.isHijri - ? DateRangePickerHelper.getHijriPickerView(_view) - : DateRangePickerHelper.getPickerView(_view); + _controller.view ??= + widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(_view) + : DateRangePickerHelper.getPickerView(_view); _controller.addPropertyChangedListener(_pickerValueChangedListener); _initNavigation(); _updateSelectionValues(); _view = DateRangePickerHelper.getPickerView(_controller.view); } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(_controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + _controller.view, + ); if (view == DateRangePickerView.month && oldWidget.monthViewSettings.firstDayOfWeek != widget.monthViewSettings.firstDayOfWeek) { @@ -5734,13 +5886,16 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> /// 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); + _previousSelectedValue = + PickerStateArgs() + ..selectedDate = _controller.selectedDate + ..selectedDates = DateRangePickerHelper.cloneList( + _controller.selectedDates, + ) + ..selectedRange = _controller.selectedRange + ..selectedRanges = DateRangePickerHelper.cloneList( + _controller.selectedRanges, + ); } } @@ -5775,9 +5930,13 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> if (!widget.isHijri && DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri) != + widget.monthViewSettings, + widget.isHijri, + ) != DateRangePickerHelper.getNumberOfWeeksInView( - oldWidget.monthViewSettings, oldWidget.isHijri)) { + oldWidget.monthViewSettings, + oldWidget.isHijri, + )) { _currentDate = _updateCurrentDate(oldWidget); _controller.displayDate = _currentDate; } @@ -5794,8 +5953,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> if (oldWidget.controller?.selectedDates != widget.controller?.selectedDates) { - _selectedDates = - DateRangePickerHelper.cloneList(_controller.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _controller.selectedDates, + ); } if (oldWidget.controller?.selectedRange != @@ -5805,8 +5965,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> if (oldWidget.controller?.selectedRanges != widget.controller?.selectedRanges) { - _selectedRanges = - DateRangePickerHelper.cloneList(_controller.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _controller.selectedRanges, + ); } if (oldWidget.controller?.view != widget.controller?.view) { @@ -5817,8 +5978,11 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> if (oldWidget.controller?.displayDate != widget.controller?.displayDate && widget.controller?.displayDate != null) { - _currentDate = - getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); + _currentDate = getValidDate( + widget.minDate, + widget.maxDate, + _controller.displayDate, + ); _controller.displayDate = _currentDate; } @@ -5827,45 +5991,58 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> @override Widget build(BuildContext context) { + _datePickerTheme = _getPickerThemeData( + SfDateRangePickerTheme.of(context), + Theme.of(context), + ); double top = 0, height; return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final double? previousWidth = _minWidth; - final double? previousHeight = _minHeight; - _minWidth = constraints.maxWidth == double.infinity - ? _minPickerWidth - : constraints.maxWidth; - _minHeight = constraints.maxHeight == double.infinity - ? _minPickerHeight - : constraints.maxHeight; - - final double actionButtonsHeight = - (widget.showActionButtons || widget.showTodayButton) - ? _minHeight! * 0.1 < 50 - ? 50 - : _minHeight! * 0.1 - : 0; - _handleScrollViewSizeChanged(_minHeight!, _minWidth!, previousHeight, - previousWidth, actionButtonsHeight); + builder: (BuildContext context, BoxConstraints constraints) { + final double? previousWidth = _minWidth; + final double? previousHeight = _minHeight; + _minWidth = + constraints.maxWidth == double.infinity + ? _minPickerWidth + : constraints.maxWidth; + _minHeight = + constraints.maxHeight == double.infinity + ? _minPickerHeight + : constraints.maxHeight; + + final double actionButtonsHeight = + (widget.showActionButtons || widget.showTodayButton) + ? _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 == - DateRangePickerNavigationDirection.vertical) { - height -= widget.monthViewSettings.viewHeaderHeight; - top += widget.monthViewSettings.viewHeaderHeight; - } + height = _minHeight! - widget.headerHeight; + top = widget.headerHeight; + if (_view == DateRangePickerView.month && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + height -= widget.monthViewSettings.viewHeaderHeight; + top += widget.monthViewSettings.viewHeaderHeight; + } - return Container( - width: _minWidth, - height: _minHeight, - color: widget.backgroundColor ?? _datePickerTheme.backgroundColor, - child: widget.navigationMode == DateRangePickerNavigationMode.scroll - ? _addScrollView(_minWidth!, _minHeight!, actionButtonsHeight) - : _addChildren(top, height, _minWidth!, actionButtonsHeight), - ); - }); + return Container( + width: _minWidth, + height: _minHeight, + color: widget.backgroundColor ?? _datePickerTheme.backgroundColor, + child: + widget.navigationMode == DateRangePickerNavigationMode.scroll + ? _addScrollView(_minWidth!, _minHeight!, actionButtonsHeight) + : _addChildren(top, height, _minWidth!, actionButtonsHeight), + ); + }, + ); } @override @@ -5885,92 +6062,177 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } SfDateRangePickerThemeData _getPickerThemeData( - SfDateRangePickerThemeData pickerTheme, ColorScheme colorScheme) { + SfDateRangePickerThemeData pickerTheme, + ThemeData themeData, + ) { + final ColorScheme colorScheme = themeData.colorScheme; + final SfDateRangePickerThemeData effectiveThemeData = + SfDateRangePickerThemeKey(context); return pickerTheme.copyWith( - brightness: pickerTheme.brightness ?? colorScheme.brightness, - backgroundColor: pickerTheme.backgroundColor ?? Colors.transparent, - headerBackgroundColor: - pickerTheme.headerBackgroundColor ?? Colors.transparent, - viewHeaderBackgroundColor: - pickerTheme.viewHeaderBackgroundColor ?? Colors.transparent, - weekNumberBackgroundColor: pickerTheme.weekNumberBackgroundColor ?? - colorScheme.onSurface.withOpacity(0.08), - viewHeaderTextStyle: pickerTheme.viewHeaderTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 14, - fontFamily: 'Roboto'), - headerTextStyle: pickerTheme.headerTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 16, - fontFamily: 'Roboto'), - trailingDatesTextStyle: pickerTheme.trailingDatesTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, - fontFamily: 'Roboto'), - leadingCellTextStyle: pickerTheme.leadingCellTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, - fontFamily: 'Roboto'), - activeDatesTextStyle: pickerTheme.activeDatesTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 13, - fontFamily: 'Roboto'), - cellTextStyle: pickerTheme.cellTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 13, - fontFamily: 'Roboto'), - leadingDatesTextStyle: pickerTheme.leadingDatesTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, - fontFamily: 'Roboto'), - rangeSelectionTextStyle: pickerTheme.rangeSelectionTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 13, - fontFamily: 'Roboto'), - disabledDatesTextStyle: pickerTheme.disabledDatesTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.38), - fontSize: 13, - fontFamily: 'Roboto'), - disabledCellTextStyle: pickerTheme.disabledCellTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.38), - fontSize: 13, - fontFamily: 'Roboto'), - selectionTextStyle: pickerTheme.selectionTextStyle ?? - TextStyle( - color: colorScheme.onPrimary, - fontSize: 13, - fontFamily: 'Roboto'), - weekNumberTextStyle: pickerTheme.weekNumberTextStyle ?? - TextStyle( - color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 13, - fontFamily: 'Roboto'), - todayTextStyle: pickerTheme.todayTextStyle ?? - TextStyle( - color: colorScheme.primary, fontSize: 13, fontFamily: 'Roboto'), - todayCellTextStyle: pickerTheme.todayCellTextStyle ?? - // ignore: lines_longer_than_80_chars - TextStyle( - color: colorScheme.primary, fontSize: 13, fontFamily: 'Roboto'), - selectionColor: pickerTheme.selectionColor ?? colorScheme.primary, - startRangeSelectionColor: - pickerTheme.startRangeSelectionColor ?? colorScheme.primary, - rangeSelectionColor: pickerTheme.rangeSelectionColor ?? - colorScheme.primary.withOpacity(0.1), - endRangeSelectionColor: - pickerTheme.endRangeSelectionColor ?? colorScheme.primary, - todayHighlightColor: - pickerTheme.todayHighlightColor ?? colorScheme.primary); + brightness: themeData.brightness, + backgroundColor: + pickerTheme.backgroundColor ?? effectiveThemeData.backgroundColor, + headerBackgroundColor: + pickerTheme.headerBackgroundColor ?? + effectiveThemeData.headerBackgroundColor, + viewHeaderBackgroundColor: + pickerTheme.viewHeaderBackgroundColor ?? + effectiveThemeData.viewHeaderBackgroundColor, + weekNumberBackgroundColor: + pickerTheme.weekNumberBackgroundColor ?? + effectiveThemeData.weekNumberBackgroundColor, + viewHeaderTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 14, + ) + .merge(pickerTheme.viewHeaderTextStyle) + .merge(widget.monthViewSettings.viewHeaderStyle.textStyle), + headerTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 16, + fontWeight: FontWeight.w400, + ) + .merge(pickerTheme.headerTextStyle) + .merge(widget.headerStyle.textStyle), + trailingDatesTextStyle: + (widget.monthCellStyle is DateRangePickerMonthCellStyle) + ? themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: 13, + ) + .merge(pickerTheme.trailingDatesTextStyle) + .merge(widget.monthCellStyle.trailingDatesTextStyle) + : null, + leadingCellTextStyle: + (widget.yearCellStyle is DateRangePickerYearCellStyle) + ? themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: 13, + ) + .merge(pickerTheme.leadingCellTextStyle) + .merge(widget.yearCellStyle.leadingDatesTextStyle) + : null, + activeDatesTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.activeDatesTextStyle) + .merge(widget.monthCellStyle.textStyle), + cellTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.cellTextStyle) + .merge(widget.yearCellStyle.textStyle), + leadingDatesTextStyle: + (widget.monthCellStyle is DateRangePickerMonthCellStyle) + ? themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.54), + fontSize: 13, + ) + .merge(pickerTheme.leadingDatesTextStyle) + .merge(widget.monthCellStyle.leadingDatesTextStyle) + : null, + rangeSelectionTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.rangeSelectionTextStyle) + .merge(widget.rangeTextStyle), + disabledDatesTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.38), + fontSize: 13, + ) + .merge(pickerTheme.disabledDatesTextStyle) + .merge(widget.monthCellStyle.disabledDatesTextStyle), + disabledCellTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.38), + fontSize: 13, + ) + .merge(pickerTheme.disabledCellTextStyle) + .merge(widget.yearCellStyle.disabledDatesTextStyle), + selectionTextStyle: themeData.textTheme.bodyMedium! + .copyWith(color: colorScheme.onPrimary, fontSize: 13) + .merge(pickerTheme.selectionTextStyle) + .merge(widget.selectionTextStyle), + weekNumberTextStyle: themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.weekNumberTextStyle) + .merge(widget.monthViewSettings.weekNumberStyle.textStyle), + todayTextStyle: themeData.textTheme.bodyMedium! + .copyWith(color: colorScheme.primary, fontSize: 13) + .merge(pickerTheme.todayTextStyle) + .merge(widget.monthCellStyle.todayTextStyle), + todayCellTextStyle: themeData.textTheme.bodyMedium! + .copyWith(color: colorScheme.primary, fontSize: 13) + .merge(pickerTheme.todayCellTextStyle) + .merge(widget.yearCellStyle.todayTextStyle), + + /// Check the widget property and theme property styles are null. + /// If null assign the picker theme style or + /// If not null then assign the theme data text theme with + /// merge the styles from widget property or theme properties are given. + blackoutDatesTextStyle: + (widget.monthCellStyle.blackoutDateTextStyle == null && + pickerTheme.blackoutDatesTextStyle == null) + ? pickerTheme.blackoutDatesTextStyle + : themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.blackoutDatesTextStyle) + .merge(widget.monthCellStyle.blackoutDateTextStyle), + specialDatesTextStyle: + (widget.monthCellStyle.specialDatesTextStyle == null && + pickerTheme.specialDatesTextStyle == null) + ? pickerTheme.specialDatesTextStyle + : themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.specialDatesTextStyle) + .merge(widget.monthCellStyle.specialDatesTextStyle), + weekendDatesTextStyle: + (widget.monthCellStyle.weekendTextStyle == null && + pickerTheme.weekendDatesTextStyle == null) + ? pickerTheme.weekendDatesTextStyle + : themeData.textTheme.bodyMedium! + .copyWith( + color: colorScheme.onSurface.withValues(alpha: 0.87), + fontSize: 13, + ) + .merge(pickerTheme.weekendDatesTextStyle) + .merge(widget.monthCellStyle.weekendTextStyle), + selectionColor: + pickerTheme.selectionColor ?? effectiveThemeData.selectionColor, + startRangeSelectionColor: + pickerTheme.startRangeSelectionColor ?? + effectiveThemeData.startRangeSelectionColor, + rangeSelectionColor: + pickerTheme.rangeSelectionColor ?? + effectiveThemeData.rangeSelectionColor, + endRangeSelectionColor: + pickerTheme.endRangeSelectionColor ?? + effectiveThemeData.endRangeSelectionColor, + todayHighlightColor: + pickerTheme.todayHighlightColor ?? + effectiveThemeData.todayHighlightColor, + ); } void _updateFadeAnimation() { @@ -5987,21 +6249,28 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } void _initPickerController() { - _controller = widget.controller ?? + _controller = + widget.controller ?? (widget.isHijri ? HijriDatePickerController() : DateRangePickerController()); _controller.selectedDate = widget.initialSelectedDate; _controller.selectedDates = _getSelectedDates( - DateRangePickerHelper.cloneList(widget.initialSelectedDates)); + DateRangePickerHelper.cloneList(widget.initialSelectedDates), + ); _controller.selectedRange = widget.initialSelectedRange; - _controller.selectedRanges = - DateRangePickerHelper.cloneList(widget.initialSelectedRanges); - _controller.view = widget.isHijri - ? DateRangePickerHelper.getHijriPickerView(widget.view) - : DateRangePickerHelper.getPickerView(widget.view); - _currentDate = - getValidDate(widget.minDate, widget.maxDate, widget.initialDisplayDate); + _controller.selectedRanges = DateRangePickerHelper.cloneList( + widget.initialSelectedRanges, + ); + _controller.view = + widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(widget.view) + : DateRangePickerHelper.getPickerView(widget.view); + _currentDate = getValidDate( + widget.minDate, + widget.maxDate, + widget.initialDisplayDate, + ); _controller.displayDate = _currentDate; } @@ -6009,8 +6278,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _selectedDate = _controller.selectedDate; _selectedDates = DateRangePickerHelper.cloneList(_controller.selectedDates); _selectedRange = _controller.selectedRange; - _selectedRanges = - DateRangePickerHelper.cloneList(_controller.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _controller.selectedRanges, + ); } void _pickerValueChangedListener(String value) { @@ -6026,19 +6296,24 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } else if (value == 'selectedDates') { if (!mounted || DateRangePickerHelper.isDateCollectionEquals( - _selectedDates, _controller.selectedDates)) { + _selectedDates, + _controller.selectedDates, + )) { return; } _raiseSelectionChangedCallback(widget, value: _controller.selectedDates); setState(() { - _selectedDates = - DateRangePickerHelper.cloneList(_controller.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _controller.selectedDates, + ); }); } else if (value == 'selectedRange') { if (!mounted || DateRangePickerHelper.isRangeEquals( - _selectedRange, _controller.selectedRange)) { + _selectedRange, + _controller.selectedRange, + )) { return; } @@ -6049,14 +6324,17 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } else if (value == 'selectedRanges') { if (!mounted || DateRangePickerHelper.isDateRangesEquals( - _selectedRanges, _controller.selectedRanges)) { + _selectedRanges, + _controller.selectedRanges, + )) { return; } _raiseSelectionChangedCallback(widget, value: _controller.selectedRanges); setState(() { - _selectedRanges = - DateRangePickerHelper.cloneList(_controller.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _controller.selectedRanges, + ); }); } else if (value == 'view') { if (!mounted || @@ -6076,7 +6354,8 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _scrollViewKey.currentState!._children.clear(); _scrollViewKey.currentState!._updateVisibleDates(); _scrollViewKey.currentState!._triggerSelectableDayPredicates( - _scrollViewKey.currentState!._currentViewVisibleDates); + _scrollViewKey.currentState!._currentViewVisibleDates, + ); _scrollViewKey.currentState!._triggerViewChangedCallback(); } }); @@ -6114,22 +6393,26 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } bool _checkDateWithInVisibleDates(dynamic date) { - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(_controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + _controller.view, + ); switch (view) { case DateRangePickerView.month: { if (!widget.isHijri && DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri) != + widget.monthViewSettings, + widget.isHijri, + ) != 6) { return isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1], - date); + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + date, + ); } else { - final dynamic currentMonth = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ + final dynamic currentMonth = + _currentViewVisibleDates[_currentViewVisibleDates.length ~/ (_isMultiViewEnabled(widget) ? 4 : 2)]; return date.month == currentMonth.month && date.year == currentMonth.year; @@ -6169,11 +6452,14 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> null, widget.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - _view, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri), - widget.isHijri), - ); + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, + widget.isHijri, + ), + widget.isHijri, + ), + ); } break; case DateRangePickerView.year: @@ -6181,7 +6467,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> case DateRangePickerView.century: { _currentViewVisibleDates = DateRangePickerHelper.getVisibleYearDates( - _currentDate, _view, widget.isHijri); + _currentDate, + _view, + widget.isHijri, + ); } } } @@ -6213,15 +6502,19 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> if (_forwardDateCollection.isEmpty) { return true; } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(_controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + _controller.view, + ); final int numberOfWeekInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri); - // ignore: always_specify_types - final List startDates = _backwardDateCollection.isNotEmpty - ? _backwardDateCollection[_backwardDateCollection.length - 1] - : _forwardDateCollection[0]; - // ignore: always_specify_types + widget.monthViewSettings, + widget.isHijri, + ); + // ignore: always_specify_types, strict_raw_type + final List startDates = + _backwardDateCollection.isNotEmpty + ? _backwardDateCollection[_backwardDateCollection.length - 1] + : _forwardDateCollection[0]; + // ignore: always_specify_types, strict_raw_type final List endDates = _forwardDateCollection[_forwardDateCollection.length - 1]; switch (view) { @@ -6230,20 +6523,29 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> if (!widget.isHijri && numberOfWeekInView != 6) { final DateTime visibleStartDate = DateRangePickerHelper.getDateTimeValue( - startDates[startDates.length - 1]); + startDates[startDates.length - 1], + ); final DateTime visibleEndDate = DateRangePickerHelper.getDateTimeValue(endDates[0]); return isDateWithInDateRange( - widget.minDate, widget.maxDate, visibleStartDate) && + widget.minDate, + widget.maxDate, + visibleStartDate, + ) && isDateWithInDateRange( - widget.minDate, widget.maxDate, visibleEndDate); + widget.minDate, + widget.maxDate, + visibleEndDate, + ); } else { final DateTime visibleStartDate = DateRangePickerHelper.getDateTimeValue( - startDates[startDates.length ~/ 2]); + startDates[startDates.length ~/ 2], + ); final DateTime visibleEndDate = DateRangePickerHelper.getDateTimeValue( - endDates[endDates.length ~/ 2]); + endDates[endDates.length ~/ 2], + ); return (visibleStartDate.year > widget.minDate.year || (visibleStartDate.year == widget.minDate.year && visibleStartDate.month >= widget.minDate.month)) && @@ -6295,8 +6597,13 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> /// Handle the control size changed related view updates on scroll navigation /// mode. - void _handleScrollViewSizeChanged(double newHeight, double newWidth, - double? oldHeight, double? oldWidth, double actionButtonHeight) { + void _handleScrollViewSizeChanged( + double newHeight, + double newWidth, + double? oldHeight, + double? oldWidth, + double actionButtonHeight, + ) { if (widget.navigationMode != DateRangePickerNavigationMode.scroll || _pickerScrollController == null || !_pickerScrollController!.hasClients) { @@ -6312,16 +6619,17 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _pickerScrollController!.dispose(); _scrollKey = UniqueKey(); _pickerKey = UniqueKey(); - _pickerScrollController = - ScrollController(initialScrollOffset: index * newWidth) - ..addListener(_handleScrollChanged); + _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 as double - : 0; + final double viewHeaderHeight = + _view == DateRangePickerView.month + ? widget.monthViewSettings.viewHeaderHeight as double + : 0; final double viewSize = oldHeight - viewHeaderHeight - actionButtonHeight; final double index = _pickerScrollController!.position.pixels / viewSize; _pickerScrollController!.removeListener(_handleScrollChanged); @@ -6329,28 +6637,30 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _scrollKey = UniqueKey(); _pickerKey = UniqueKey(); _pickerScrollController = ScrollController( - initialScrollOffset: - index * (newHeight - viewHeaderHeight - actionButtonHeight)) - ..addListener(_handleScrollChanged); + 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; + 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; } @@ -6392,12 +6702,18 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> dynamic currentDate = visibleDates[0]; final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri); + 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); + date.year, + date.month, + 1, + widget.isHijri, + ); } _currentDate = getValidDate(widget.minDate, widget.maxDate, currentDate); @@ -6409,19 +6725,20 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> /// 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) { + 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: { - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type final List visibleDates = getVisibleDates( visibleDate, null, @@ -6466,18 +6783,20 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> dateCollection.add(visibleDates); if (isNextView) { visibleDate = DateRangePickerHelper.getNextViewStartDate( - currentView, - numberOfWeeksInView, - visibleDate, - false, - widget.isHijri); + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri, + ); } else { visibleDate = DateRangePickerHelper.getPreviousViewStartDate( - currentView, - numberOfWeeksInView, - visibleDate, - false, - widget.isHijri); + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri, + ); } count++; } @@ -6505,7 +6824,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> break; } } - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type final List visibleDates = DateRangePickerHelper.getVisibleYearDates( visibleDate, currentView, @@ -6515,18 +6834,20 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> dateCollection.add(visibleDates); if (isNextView) { visibleDate = DateRangePickerHelper.getNextViewStartDate( - currentView, - numberOfWeeksInView, - visibleDate, - false, - widget.isHijri); + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri, + ); } else { visibleDate = DateRangePickerHelper.getPreviousViewStartDate( - currentView, - numberOfWeeksInView, - visibleDate, - false, - widget.isHijri); + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri, + ); } count++; } @@ -6536,24 +6857,39 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } Widget _addScrollView( - double width, double height, double actionButtonsHeight) { - _pickerScrollController ??= ScrollController() - ..addListener(_handleScrollChanged); - final DateRangePickerView currentView = - DateRangePickerHelper.getPickerView(_view); + 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); + widget.monthViewSettings, + widget.isHijri, + ); final int visibleDatesCount = DateRangePickerHelper.getViewDatesCount( - currentView, numberOfWeeksInView, widget.isHijri); + currentView, + numberOfWeeksInView, + widget.isHijri, + ); final bool isInitialLoading = _forwardDateCollection.isEmpty; if (isInitialLoading) { - _addScrollViewDateCollection(_forwardDateCollection, true, _currentDate, - currentView, numberOfWeeksInView, visibleDatesCount); + _addScrollViewDateCollection( + _forwardDateCollection, + true, + _currentDate, + currentView, + numberOfWeeksInView, + visibleDatesCount, + ); } if (_backwardDateCollection.isEmpty) { - // ignore: always_specify_types, unnecessary_nullable_for_final_variable_declarations + // ignore: always_specify_types, unnecessary_nullable_for_final_variable_declarations, strict_raw_type final List? lastViewDates = _forwardDateCollection[0]; dynamic visibleDate = currentView == DateRangePickerView.month && numberOfWeeksInView != 6 @@ -6561,12 +6897,23 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> ? lastViewDates[0] : _currentDate : lastViewDates != null && lastViewDates.isNotEmpty - ? lastViewDates[lastViewDates.length ~/ 2] - : _currentDate; + ? lastViewDates[lastViewDates.length ~/ 2] + : _currentDate; visibleDate = DateRangePickerHelper.getPreviousViewStartDate( - currentView, numberOfWeeksInView, visibleDate, false, widget.isHijri); - _addScrollViewDateCollection(_backwardDateCollection, false, visibleDate, - currentView, numberOfWeeksInView, visibleDatesCount); + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri, + ); + _addScrollViewDateCollection( + _backwardDateCollection, + false, + visibleDate, + currentView, + numberOfWeeksInView, + visibleDatesCount, + ); } int forwardCollectionLength = _forwardDateCollection.length; @@ -6588,7 +6935,8 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _notifyCurrentVisibleDatesChanged(); } - final bool isHorizontal = widget.navigationDirection == + final bool isHorizontal = + widget.navigationDirection == DateRangePickerNavigationDirection.horizontal; final double topPosition = _view == DateRangePickerView.month && !isHorizontal @@ -6598,28 +6946,32 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> double scrollViewItemHeight = scrollViewHeight; double scrollViewItemWidth = width; if (isHorizontal) { - scrollViewItemWidth = widget.enableMultiView - ? scrollViewItemWidth / 2 - : scrollViewItemWidth; + scrollViewItemWidth = + widget.enableMultiView + ? scrollViewItemWidth / 2 + : scrollViewItemWidth; } else { - scrollViewItemHeight = widget.enableMultiView - ? scrollViewItemHeight / 2 - : scrollViewItemHeight; + 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())), + parent: ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics()), + ), controller: _pickerScrollController, center: _pickerKey, slivers: [ SliverFixedExtentList( itemExtent: isHorizontal ? scrollViewItemWidth : scrollViewItemHeight, - delegate: - SliverChildBuilderDelegate((BuildContext context, int index) { + delegate: SliverChildBuilderDelegate(( + BuildContext context, + int index, + ) { if (_backwardDateCollection.length <= index) { return null; } @@ -6627,27 +6979,31 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> /// Send negative index value to differentiate the /// backward view from forward view. return _getScrollViewItem( - -(index + 1), - scrollViewItemWidth, - scrollViewItemHeight, - _backwardDateCollection[index], - isHorizontal); + -(index + 1), + scrollViewItemWidth, + scrollViewItemHeight, + _backwardDateCollection[index], + isHorizontal, + ); }), ), SliverFixedExtentList( itemExtent: isHorizontal ? scrollViewItemWidth : scrollViewItemHeight, - delegate: - SliverChildBuilderDelegate((BuildContext context, int index) { + delegate: SliverChildBuilderDelegate(( + BuildContext context, + int index, + ) { if (_forwardDateCollection.length <= index) { return null; } return _getScrollViewItem( - index, - scrollViewItemWidth, - scrollViewItemHeight, - _forwardDateCollection[index], - isHorizontal); + index, + scrollViewItemWidth, + scrollViewItemHeight, + _forwardDateCollection[index], + isHorizontal, + ); }), key: _pickerKey, ), @@ -6659,72 +7015,115 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> children: [ scrollView, _getActionsButton( - topPosition + scrollViewHeight, actionButtonsHeight), + topPosition + scrollViewHeight, + actionButtonsHeight, + ), ], ); } else { _viewHeaderVisibleDates.value = _currentViewVisibleDates; - return Stack(children: [ - _getViewHeaderView(0), - Positioned( + return Stack( + children: [ + _getViewHeaderView(0), + Positioned( left: 0, top: topPosition, right: 0, height: scrollViewHeight, - child: scrollView), - _getActionsButton(topPosition + scrollViewHeight, actionButtonsHeight) - ]); + 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, - // ignore: always_specify_types - List dates, - bool isHorizontal) { - final DateRangePickerView currentView = - DateRangePickerHelper.getPickerView(_view); + int index, + double width, + double height, + // ignore: always_specify_types, strict_raw_type + List dates, + bool isHorizontal, + ) { + final DateRangePickerView currentView = DateRangePickerHelper.getPickerView( + _view, + ); final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri); + widget.monthViewSettings, + widget.isHijri, + ); final int visibleDatesCount = DateRangePickerHelper.getViewDatesCount( - currentView, numberOfWeeksInView, widget.isHijri); + currentView, + numberOfWeeksInView, + widget.isHijri, + ); if (index >= 0) { if (_forwardDateCollection.isNotEmpty && index > _forwardDateCollection.length - 2) { - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type 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]; + 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); + currentView, + numberOfWeeksInView, + date, + false, + widget.isHijri, + ); + _addScrollViewDateCollection( + _forwardDateCollection, + true, + date, + currentView, + numberOfWeeksInView, + visibleDatesCount, + ); } } else { if (_backwardDateCollection.isNotEmpty && -index > _backwardDateCollection.length - 2) { - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type 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]; + 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); + currentView, + numberOfWeeksInView, + date, + false, + widget.isHijri, + ); + _addScrollViewDateCollection( + _backwardDateCollection, + false, + date, + currentView, + numberOfWeeksInView, + visibleDatesCount, + ); } } @@ -6733,36 +7132,36 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> 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; + dates, + _view, + 0, + widget.isHijri, + numberOfWeeksInView, + widget.monthFormat, + false, + widget.headerStyle, + widget.navigationDirection, + _locale, + _localizations, + ); + headerWidth = + _getTextWidgetWidth( + headerText, + widget.headerHeight, + pickerWidth, + context, + style: _datePickerTheme.headerTextStyle!, + widthPadding: 20, + ).width; } if (headerWidth > pickerWidth) { headerWidth = pickerWidth; } - Color? backgroundColor = widget.headerStyle.backgroundColor ?? + final 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, @@ -6771,35 +7170,42 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> child: GestureDetector( child: Container( color: backgroundColor, + height: widget.headerHeight, 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, + ValueNotifier>(dates), + widget.headerStyle, + widget.selectionMode, + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri, - _localizations), - height: widget.headerHeight, + ), + 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, + kIsWeb && + (Theme.of(context).platform == TargetPlatform.android || + Theme.of(context).platform == TargetPlatform.iOS) + ? _datePickerTheme.selectionColor! + : widget.selectionColor ?? _datePickerTheme.selectionColor!, + _isRtl, + _textScaleFactor, + widget.isHijri, + _localizations, + ), ), onTapUp: (TapUpDetails details) { if (_view == DateRangePickerView.century || @@ -6811,16 +7217,25 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> dynamic currentDate = dates[0]; final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri); + 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); + date.year, + date.month, + 1, + widget.isHijri, + ); } - currentDate = - getValidDate(widget.minDate, widget.maxDate, currentDate); + currentDate = getValidDate( + widget.minDate, + widget.maxDate, + currentDate, + ); /// Check the moved view visible date not contains tapped /// header date @@ -6869,49 +7284,59 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> final List children = [pickerView]; if (isHorizontal) { - children.add(Positioned( - top: 0, - left: pickerWidth, - width: 1, - height: height, - child: const VerticalDivider( - thickness: 1, + children.add( + Positioned( + top: 0, + left: pickerWidth, + width: 1, + height: height, + child: const VerticalDivider(thickness: 1), ), - )); + ); } children.add(header); return SizedBox( - width: width, - height: height, - child: _StickyHeader( - isHorizontal: isHorizontal, - isRTL: _isRtl, - children: children, - )); + width: width, + height: height, + child: _StickyHeader( + isHorizontal: isHorizontal, + isRTL: _isRtl, + children: children, + ), + ); } Widget _addChildren( - double top, double height, double width, double actionButtonsHeight) { + 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( + return Stack( + children: [ + Positioned( + top: 0, + right: 0, + left: 0, + height: widget.headerHeight, + child: GestureDetector( + child: Container( + color: + widget.headerStyle.backgroundColor ?? + _datePickerTheme.headerBackgroundColor, + height: widget.headerHeight, + child: _PickerHeaderView( _headerVisibleDates, widget.headerStyle, widget.selectionMode, _view, DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri), + widget.monthViewSettings, + widget.isHijri, + ), widget.showNavigationArrow, widget.navigationDirection, widget.monthViewSettings.enableSwipeSelection, @@ -6928,47 +7353,52 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _controller.forward, _isMultiViewEnabled(widget), widget.viewSpacing, - widget.selectionColor ?? _datePickerTheme.selectionColor!, + kIsWeb && + (Theme.of(context).platform == TargetPlatform.android || + Theme.of(context).platform == TargetPlatform.iOS) + ? _datePickerTheme.selectionColor! + : widget.selectionColor ?? _datePickerTheme.selectionColor!, _isRtl, _textScaleFactor, widget.isHijri, - _localizations), - height: widget.headerHeight, + _localizations, + ), + ), + onTapUp: (TapUpDetails details) { + _updateCalendarTapCallbackForHeader(); + }, ), - onTapUp: (TapUpDetails details) { - _updateCalendarTapCallbackForHeader(); - }, ), - ), - _getViewHeaderView(widget.headerHeight), - Positioned( - top: top, - left: 0, - right: 0, - height: height, - child: _AnimatedOpacityWidget( - opacity: _opacity, - child: _PickerScrollView( - widget, - _controller, - width, - height, - _isRtl, - _datePickerTheme, - _locale, - _textScaleFactor, - getPickerStateValues: (PickerStateArgs details) { - _getPickerStateValues(details); - }, - updatePickerStateValues: (PickerStateArgs details) { - _updatePickerStateValues(details); - }, - key: _scrollViewKey, + _getViewHeaderView(widget.headerHeight), + Positioned( + top: top, + left: 0, + right: 0, + height: height, + child: _AnimatedOpacityWidget( + opacity: _opacity, + 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) - ]); + _getActionsButton(top + height, actionButtonsHeight), + ], + ); } Widget _getActionsButton(double top, double actionButtonsHeight) { @@ -6978,74 +7408,76 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> Color textColor = widget.todayHighlightColor ?? _datePickerTheme.todayHighlightColor!; if (textColor == Colors.transparent) { - final TextStyle style = - widget.monthCellStyle.todayTextStyle as TextStyle? ?? - _datePickerTheme.todayTextStyle!; + final TextStyle style = _datePickerTheme.todayTextStyle!; textColor = style.color != null ? style.color! : Colors.blue; } - final Widget actionButtons = widget.showActionButtons - ? 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), + final Widget actionButtons = + widget.showActionButtons + ? Container( + alignment: AlignmentDirectional.centerEnd, + constraints: const BoxConstraints(minHeight: 52.0), + padding: const EdgeInsets.symmetric(horizontal: 8), + child: OverflowBar( + spacing: 8, + children: [ + TextButton( + onPressed: _handleCancel, + child: Text( + widget.cancelText, + style: TextStyle(color: textColor), + ), ), - onPressed: _handleCancel, - ), - TextButton( - child: Text( - widget.confirmText, - style: TextStyle(color: textColor), + TextButton( + onPressed: _handleOk, + child: Text( + widget.confirmText, + style: TextStyle(color: textColor), + ), ), - onPressed: _handleOk, - ), - ], - ), - ) - : const SizedBox(width: 0, height: 0); - final Widget todayButton = widget.showTodayButton - ? Container( - alignment: AlignmentDirectional.centerStart, - constraints: const BoxConstraints(minHeight: 52.0), - padding: const EdgeInsets.symmetric(horizontal: 8), - child: OverflowBar( - spacing: 8, - children: [ - TextButton( - child: Text( - _localizations.todayLabel.toUpperCase(), - style: TextStyle(color: textColor), + ], + ), + ) + : const SizedBox(width: 0, height: 0); + final Widget todayButton = + widget.showTodayButton + ? Container( + alignment: AlignmentDirectional.centerStart, + constraints: const BoxConstraints(minHeight: 52.0), + padding: const EdgeInsets.symmetric(horizontal: 8), + child: OverflowBar( + spacing: 8, + children: [ + TextButton( + child: Text( + _localizations.todayLabel.toUpperCase(), + style: TextStyle(color: textColor), + ), + onPressed: () { + if (widget.allowViewNavigation) { + _controller.view = + widget.isHijri + ? HijriDatePickerView.month + : DateRangePickerView.month; + } + + _controller.displayDate = DateRangePickerHelper.getToday( + widget.isHijri, + ); + }, ), - onPressed: () { - if (widget.allowViewNavigation) { - _controller.view = widget.isHijri - ? HijriDatePickerView.month - : DateRangePickerView.month; - } - - _controller.displayDate = - DateRangePickerHelper.getToday(widget.isHijri); - }, - ), - ], - ), - ) - : const SizedBox(width: 0, height: 0); + ], + ), + ) + : const SizedBox(width: 0, height: 0); return Positioned( top: top, left: 0, right: 0, height: actionButtonsHeight, child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [todayButton, actionButtons]), + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [todayButton, actionButtons], + ), ); } @@ -7063,11 +7495,14 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> break; case DateRangePickerSelectionMode.multiple: { - _selectedDates = _previousSelectedValue.selectedDates != null - ? _getSelectedDates(_previousSelectedValue.selectedDates) - : null; + _selectedDates = + _previousSelectedValue.selectedDates != null + ? _getSelectedDates(_previousSelectedValue.selectedDates) + : null; if (!DateRangePickerHelper.isDateCollectionEquals( - _selectedDates, _controller.selectedDates)) { + _selectedDates, + _controller.selectedDates, + )) { setState(() { _controller.selectedDates = _previousSelectedValue.selectedDates != null @@ -7082,7 +7517,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _selectedRange = _previousSelectedValue.selectedRange; if (!DateRangePickerHelper.isRangeEquals( - _selectedRange, _controller.selectedRange)) { + _selectedRange, + _controller.selectedRange, + )) { setState(() { _controller.selectedRange = _selectedRange; }); @@ -7091,17 +7528,21 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> break; case DateRangePickerSelectionMode.multiRange: { - _selectedRanges = _previousSelectedValue.selectedRanges != null - ? _getSelectedRanges(_previousSelectedValue.selectedRanges) - : null; - if (!DateRangePickerHelper.isDateRangesEquals( - _selectedRanges, _controller.selectedRanges)) { - setState(() { - _controller.selectedRanges = _previousSelectedValue - .selectedRanges != - null + _selectedRanges = + _previousSelectedValue.selectedRanges != null ? _getSelectedRanges(_previousSelectedValue.selectedRanges) : null; + if (!DateRangePickerHelper.isDateRangesEquals( + _selectedRanges, + _controller.selectedRanges, + )) { + setState(() { + _controller.selectedRanges = + _previousSelectedValue.selectedRanges != null + ? _getSelectedRanges( + _previousSelectedValue.selectedRanges, + ) + : null; }); } } @@ -7122,8 +7563,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> case DateRangePickerSelectionMode.multiple: { value = _getSelectedDates(_selectedDates); - _previousSelectedValue.selectedDates = - _getSelectedDates(_selectedDates); + _previousSelectedValue.selectedDates = _getSelectedDates( + _selectedDates, + ); } break; case DateRangePickerSelectionMode.range: @@ -7136,8 +7578,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> case DateRangePickerSelectionMode.multiRange: { value = _getSelectedRanges(_selectedRanges); - _previousSelectedValue.selectedRanges = - _getSelectedRanges(_selectedRanges); + _previousSelectedValue.selectedRanges = _getSelectedRanges( + _selectedRanges, + ); } } @@ -7164,29 +7607,31 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> child: _AnimatedOpacityWidget( opacity: _opacity, child: Container( - color: widget.monthViewSettings.viewHeaderStyle.backgroundColor ?? + color: + widget.monthViewSettings.viewHeaderStyle.backgroundColor ?? _datePickerTheme.viewHeaderBackgroundColor, child: RepaintBoundary( child: CustomPaint( painter: _PickerViewHeaderPainter( - _currentViewVisibleDates, - widget.navigationMode, - widget.monthViewSettings.viewHeaderStyle, - widget.monthViewSettings.viewHeaderHeight, - widget.monthViewSettings, - _datePickerTheme, - _locale, - _isRtl, - widget.monthCellStyle, - _isMultiViewEnabled(widget), - widget.viewSpacing, - todayTextColor, - _textScaleFactor, - widget.isHijri, - widget.navigationDirection, - _viewHeaderVisibleDates, - widget.monthViewSettings.showWeekNumber, - _isMobilePlatform), + _currentViewVisibleDates, + widget.navigationMode, + widget.monthViewSettings.viewHeaderStyle, + widget.monthViewSettings.viewHeaderHeight, + widget.monthViewSettings, + _datePickerTheme, + _locale, + _isRtl, + widget.monthCellStyle, + _isMultiViewEnabled(widget), + widget.viewSpacing, + todayTextColor, + _textScaleFactor, + widget.isHijri, + widget.navigationDirection, + _viewHeaderVisibleDates, + widget.monthViewSettings.showWeekNumber, + _isMobilePlatform, + ), ), ), ), @@ -7202,13 +7647,16 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> return; } if (!DateRangePickerHelper.canMoveToNextView( - _view, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri), - widget.maxDate, - _currentViewVisibleDates, - _isMultiViewEnabled(widget), - widget.isHijri)) { + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, + widget.isHijri, + ), + widget.maxDate, + _currentViewVisibleDates, + _isMultiViewEnabled(widget), + widget.isHijri, + )) { return; } @@ -7222,13 +7670,16 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> return; } if (!DateRangePickerHelper.canMoveToPreviousView( - _view, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri), - widget.minDate, - _currentViewVisibleDates, - _isMultiViewEnabled(widget), - widget.isHijri)) { + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, + widget.isHijri, + ), + widget.minDate, + _currentViewVisibleDates, + _isMultiViewEnabled(widget), + widget.isHijri, + )) { return; } @@ -7268,9 +7719,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } if (_view != details.view) { - _controller.view = widget.isHijri - ? DateRangePickerHelper.getHijriPickerView(details.view) - : DateRangePickerHelper.getPickerView(details.view); + _controller.view = + widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(details.view) + : DateRangePickerHelper.getPickerView(details.view); if (_view == DateRangePickerView.month && widget.navigationDirection == DateRangePickerNavigationDirection.vertical) { @@ -7303,8 +7755,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> case DateRangePickerSelectionMode.single: { _selectedDate = details.selectedDate; - final bool isSameSelectedDate = - isSameDate(_controller.selectedDate, _selectedDate); + final bool isSameSelectedDate = isSameDate( + _controller.selectedDate, + _selectedDate, + ); if (widget.navigationMode == DateRangePickerNavigationMode.scroll && !isSameSelectedDate) { setState(() { @@ -7314,8 +7768,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _controller.selectedDate = _selectedDate; if (!isSameSelectedDate) { - _raiseSelectionChangedCallback(widget, - value: _controller.selectedDate); + _raiseSelectionChangedCallback( + widget, + value: _controller.selectedDate, + ); } } break; @@ -7324,7 +7780,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _selectedDates = details.selectedDates; final bool isSameSelectedDate = DateRangePickerHelper.isDateCollectionEquals( - _selectedDates, _controller.selectedDates); + _selectedDates, + _controller.selectedDates, + ); if (widget.navigationMode == DateRangePickerNavigationMode.scroll && !isSameSelectedDate) { setState(() { @@ -7333,9 +7791,12 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } _controller.selectedDates = _getSelectedDates(_selectedDates); - if (!isSameSelectedDate) - _raiseSelectionChangedCallback(widget, - value: _controller.selectedDates); + if (!isSameSelectedDate) { + _raiseSelectionChangedCallback( + widget, + value: _controller.selectedDates, + ); + } } break; case DateRangePickerSelectionMode.range: @@ -7343,7 +7804,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _selectedRange = details.selectedRange; final bool isSameSelectedDate = DateRangePickerHelper.isRangeEquals( - _selectedRange, _controller.selectedRange); + _selectedRange, + _controller.selectedRange, + ); if (widget.navigationMode == DateRangePickerNavigationMode.scroll && !isSameSelectedDate) { setState(() { @@ -7352,9 +7815,12 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } _controller.selectedRange = _selectedRange; - if (!isSameSelectedDate) - _raiseSelectionChangedCallback(widget, - value: _controller.selectedRange); + if (!isSameSelectedDate) { + _raiseSelectionChangedCallback( + widget, + value: _controller.selectedRange, + ); + } } break; case DateRangePickerSelectionMode.multiRange: @@ -7362,7 +7828,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> _selectedRanges = details.selectedRanges; final bool isSameSelectedDate = DateRangePickerHelper.isDateRangesEquals( - _selectedRanges, _controller.selectedRanges); + _selectedRanges, + _controller.selectedRanges, + ); if (widget.navigationMode == DateRangePickerNavigationMode.scroll && !isSameSelectedDate) { setState(() { @@ -7371,9 +7839,12 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } _controller.selectedRanges = _getSelectedRanges(_selectedRanges); - if (!isSameSelectedDate) - _raiseSelectionChangedCallback(widget, - value: _controller.selectedRanges); + if (!isSameSelectedDate) { + _raiseSelectionChangedCallback( + widget, + value: _controller.selectedRanges, + ); + } } } } @@ -7382,8 +7853,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> /// Used to call the view changed callback when [_currentViewVisibleDates] /// changed. void _notifyCurrentVisibleDatesChanged() { - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(_controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + _controller.view, + ); dynamic visibleDateRange; switch (view) { case DateRangePickerView.month: @@ -7391,57 +7863,76 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> final bool enableMultiView = _isMultiViewEnabled(widget); if (widget.isHijri || (!DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.monthViewSettings, widget.isHijri) && + widget.monthViewSettings, + widget.isHijri, + ) && DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri) == + widget.monthViewSettings, + widget.isHijri, + ) == 6)) { - final dynamic visibleDate = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ (enableMultiView ? 4 : 2)]; + 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)); + 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)); + 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); + _raisePickerViewChangedCallback( + widget, + visibleDateRange: visibleDateRange, + view: _controller.view, + ); } else { if (widget.isHijri) { visibleDateRange = HijriDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]); + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + ); } else { visibleDateRange = PickerDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]); + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + ); } - _raisePickerViewChangedCallback(widget, - visibleDateRange: visibleDateRange, view: _controller.view); + _raisePickerViewChangedCallback( + widget, + visibleDateRange: visibleDateRange, + view: _controller.view, + ); } } break; @@ -7450,26 +7941,33 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> case DateRangePickerView.century: { if (widget.isHijri) { - visibleDateRange = HijriDateRange(_currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1]); + visibleDateRange = HijriDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + ); } else { - visibleDateRange = PickerDateRange(_currentViewVisibleDates[0], - _currentViewVisibleDates[_currentViewVisibleDates.length - 1]); + visibleDateRange = PickerDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1], + ); } - _raisePickerViewChangedCallback(widget, - visibleDateRange: visibleDateRange, view: _controller.view); + _raisePickerViewChangedCallback( + widget, + visibleDateRange: visibleDateRange, + view: _controller.view, + ); } } } /// returns the selected ranges in the required type list. - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type List? _getSelectedRanges(List? ranges) { if (ranges == null) { return ranges; } - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type List selectedRanges; if (widget.isHijri) { selectedRanges = []; @@ -7485,13 +7983,13 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } /// returns the selected dates in the required type list - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type List? _getSelectedDates(List? dates) { if (dates == null) { return dates; } - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type List selectedDates; if (widget.isHijri) { selectedDates = []; @@ -7513,20 +8011,31 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } if (_view == DateRangePickerView.month) { - _controller.view = widget.isHijri - ? DateRangePickerHelper.getHijriPickerView(DateRangePickerView.year) - : DateRangePickerHelper.getPickerView(DateRangePickerView.year); + _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); + _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); + _controller.view = + widget.isHijri + ? DateRangePickerHelper.getHijriPickerView( + DateRangePickerView.century, + ) + : DateRangePickerHelper.getPickerView( + DateRangePickerView.century, + ); } } } @@ -7579,17 +8088,13 @@ class _AnimatedOpacityWidgetState extends State<_AnimatedOpacityWidget> { /// Holds content and header to show header like sticky based on content. class _StickyHeader extends Stack { - _StickyHeader({ + const _StickyHeader({ required List children, AlignmentDirectional alignment = AlignmentDirectional.topStart, this.isHorizontal = false, this.isRTL = false, Key? key, - }) : super( - key: key, - children: children, - alignment: alignment, - ); + }) : super(key: key, children: children, alignment: alignment); final bool isHorizontal; final bool isRTL; @@ -7597,7 +8102,7 @@ class _StickyHeader extends Stack { @override RenderStack createRenderObject(BuildContext context) => _StickyHeaderRenderObject( - scrollableState: Scrollable.of(context)!, + scrollableState: Scrollable.of(context), alignment: alignment, textDirection: textDirection ?? Directionality.of(context), fit: fit, @@ -7612,7 +8117,7 @@ class _StickyHeader extends Stack { if (renderObject is _StickyHeaderRenderObject) { renderObject - ..scrollableState = Scrollable.of(context)! + ..scrollableState = Scrollable.of(context) ..isRTL = isRTL ..isHorizontal = isHorizontal; } @@ -7627,14 +8132,10 @@ class _StickyHeaderRenderObject extends RenderStack { required StackFit fit, required bool isHorizontal, required bool isRTL, - }) : _scrollableState = scrollableState, - _isHorizontal = isHorizontal, - _isRTL = isRTL, - super( - alignment: alignment, - textDirection: textDirection, - fit: fit, - ); + }) : _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; @@ -7666,7 +8167,7 @@ class _StickyHeaderRenderObject extends RenderStack { } /// Current view port. - RenderAbstractViewport get _stackViewPort => RenderAbstractViewport.of(this)!; + RenderAbstractViewport get _stackViewPort => RenderAbstractViewport.of(this); ScrollableState get scrollableState => _scrollableState; @@ -7734,20 +8235,25 @@ class _StickyHeaderRenderObject extends RenderStack { /// 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); + 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); + headerParentData?.offset = Offset( + headerParentData.offset.dx, + headerYOffset, + ); } else if (_isHorizontal && headerYOffset != headerParentData?.offset.dx) { - headerParentData?.offset = - Offset(headerYOffset, headerParentData.offset.dy); + headerParentData?.offset = Offset( + headerYOffset, + headerParentData.offset.dy, + ); } } @@ -7801,34 +8307,34 @@ class _StickyHeaderRenderObject extends RenderStack { class _PickerHeaderView extends StatefulWidget { /// Constructor to create picker header view instance. const _PickerHeaderView( - this.visibleDates, - this.headerStyle, - this.selectionMode, - this.view, - this.numberOfWeeksInView, - this.showNavigationArrow, - this.navigationDirection, - this.enableSwipeSelection, - this.navigationMode, - this.minDate, - this.maxDate, - this.monthFormat, - this.datePickerTheme, - this.locale, - this.width, - this.height, - this.allowViewNavigation, - this.previousNavigationCallback, - this.nextNavigationCallback, - this.enableMultiView, - this.multiViewSpacing, - this.hoverColor, - this.isRtl, - this.textScaleFactor, - this.isHijri, - this.localizations, - {Key? key}) - : super(key: key); + this.visibleDates, + this.headerStyle, + this.selectionMode, + this.view, + this.numberOfWeeksInView, + this.showNavigationArrow, + this.navigationDirection, + this.enableSwipeSelection, + this.navigationMode, + this.minDate, + this.maxDate, + this.monthFormat, + this.datePickerTheme, + this.locale, + this.width, + this.height, + this.allowViewNavigation, + this.previousNavigationCallback, + this.nextNavigationCallback, + this.enableMultiView, + this.multiViewSpacing, + this.hoverColor, + this.isRtl, + this.textScaleFactor, + this.isHijri, + this.localizations, { + Key? key, + }) : super(key: key); /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; @@ -7930,21 +8436,26 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { @override Widget build(BuildContext context) { - final bool isMobilePlatform = - DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); + final bool isMobilePlatform = DateRangePickerHelper.isMobileLayout( + Theme.of(context).platform, + ); double arrowWidth = 0; double headerWidth = widget.width; - bool showNavigationArrow = widget.showNavigationArrow || + bool showNavigationArrow = + widget.showNavigationArrow || ((widget.view == DateRangePickerView.month || !widget.allowViewNavigation) && _isSwipeInteractionEnabled( - widget.enableSwipeSelection, widget.navigationMode) && + widget.enableSwipeSelection, + widget.navigationMode, + ) && (widget.selectionMode == DateRangePickerSelectionMode.range || widget.selectionMode == DateRangePickerSelectionMode.multiRange || widget.selectionMode == DateRangePickerSelectionMode.extendableRange)); - showNavigationArrow = showNavigationArrow && + showNavigationArrow = + showNavigationArrow && widget.navigationMode != DateRangePickerNavigationMode.scroll; if (showNavigationArrow) { arrowWidth = widget.width / 6; @@ -7952,34 +8463,33 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { headerWidth = widget.width - (arrowWidth * 2); } - Color arrowColor = widget.headerStyle.textStyle != null && - widget.headerStyle.textStyle!.color != null - ? widget.headerStyle.textStyle!.color! - : (widget.datePickerTheme.headerTextStyle!.color!); - arrowColor = arrowColor.withOpacity(arrowColor.opacity * 0.6); + Color arrowColor = widget.datePickerTheme.headerTextStyle!.color!; + arrowColor = arrowColor.withValues(alpha: arrowColor.a * 0.6); Color prevArrowColor = arrowColor; Color nextArrowColor = arrowColor; final List dates = widget.visibleDates.value; if (showNavigationArrow && !DateRangePickerHelper.canMoveToNextView( - widget.view, - widget.numberOfWeeksInView, - widget.maxDate, - dates, - widget.enableMultiView, - widget.isHijri)) { - nextArrowColor = nextArrowColor.withOpacity(arrowColor.opacity * 0.5); + widget.view, + widget.numberOfWeeksInView, + widget.maxDate, + dates, + widget.enableMultiView, + widget.isHijri, + )) { + nextArrowColor = nextArrowColor.withValues(alpha: arrowColor.a * 0.5); } if (showNavigationArrow && !DateRangePickerHelper.canMoveToPreviousView( - widget.view, - widget.numberOfWeeksInView, - widget.minDate, - dates, - widget.enableMultiView, - widget.isHijri)) { - prevArrowColor = prevArrowColor.withOpacity(arrowColor.opacity * 0.5); + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + dates, + widget.enableMultiView, + widget.isHijri, + )) { + prevArrowColor = prevArrowColor.withValues(alpha: arrowColor.a * 0.5); } final Widget headerText = _getHeaderText(headerWidth, isMobilePlatform); @@ -7992,43 +8502,33 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { double arrowSize = widget.height * 0.5; arrowSize = arrowSize > 25 ? 25 : arrowSize; arrowSize = arrowSize * widget.textScaleFactor; - final Container leftArrow = showNavigationArrow - ? _getLeftArrow(arrowWidth, arrowColor, prevArrowColor, arrowSize) - : Container(); + final Container leftArrow = + showNavigationArrow + ? _getLeftArrow(arrowWidth, arrowColor, prevArrowColor, arrowSize) + : Container(); - final Container rightArrow = showNavigationArrow - ? _getRightArrow(arrowWidth, arrowColor, nextArrowColor, arrowSize) - : Container(); + final Container rightArrow = + showNavigationArrow + ? _getRightArrow(arrowWidth, arrowColor, nextArrowColor, arrowSize) + : Container(); if (widget.headerStyle.textAlign == TextAlign.left || widget.headerStyle.textAlign == TextAlign.start) { return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - headerText, - leftArrow, - rightArrow, - ]); + mainAxisAlignment: MainAxisAlignment.center, + children: [headerText, leftArrow, rightArrow], + ); } else if (widget.headerStyle.textAlign == TextAlign.right || widget.headerStyle.textAlign == TextAlign.end) { return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - leftArrow, - rightArrow, - headerText, - ]); + mainAxisAlignment: MainAxisAlignment.center, + children: [leftArrow, rightArrow, headerText], + ); } else { return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - leftArrow, - headerText, - rightArrow, - ]); + mainAxisAlignment: MainAxisAlignment.center, + children: [leftArrow, headerText, rightArrow], + ); } } @@ -8047,7 +8547,9 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { ((widget.view == DateRangePickerView.month || !widget.allowViewNavigation) && _isSwipeInteractionEnabled( - widget.enableSwipeSelection, widget.navigationMode) && + widget.enableSwipeSelection, + widget.navigationMode, + ) && (widget.selectionMode == DateRangePickerSelectionMode.range || widget.selectionMode == DateRangePickerSelectionMode.multiRange || @@ -8060,69 +8562,77 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { } void _addListener() { - SchedulerBinding.instance?.addPostFrameCallback((_) { + SchedulerBinding.instance.addPostFrameCallback((_) { widget.visibleDates.addListener(_listener); }); } Widget _getHeaderText(double headerWidth, bool isMobilePlatform) { return MouseRegion( - onEnter: (PointerEnterEvent event) { - if (widget.view == DateRangePickerView.century || - (widget.isHijri && widget.view == DateRangePickerView.decade) || - isMobilePlatform) { - return; - } + onEnter: (PointerEnterEvent event) { + if (widget.view == DateRangePickerView.century || + (widget.isHijri && widget.view == DateRangePickerView.decade) || + isMobilePlatform) { + return; + } - setState(() { - _hovering = true; - }); - }, - onHover: (PointerHoverEvent event) { - if (widget.view == DateRangePickerView.century || - (widget.isHijri && widget.view == DateRangePickerView.decade) || - isMobilePlatform) { - return; - } + setState(() { + _hovering = true; + }); + }, + onHover: (PointerHoverEvent event) { + if (widget.view == DateRangePickerView.century || + (widget.isHijri && widget.view == DateRangePickerView.decade) || + isMobilePlatform) { + return; + } - setState(() { - _hovering = true; - }); - }, - onExit: (PointerExitEvent event) { - setState(() { - _hovering = false; - }); - }, - child: RepaintBoundary( - child: CustomPaint( + setState(() { + _hovering = true; + }); + }, + onExit: (PointerExitEvent event) { + setState(() { + _hovering = false; + }); + }, + child: RepaintBoundary( + child: CustomPaint( // Returns the header view as a child for the picker. painter: _PickerHeaderPainter( - widget.visibleDates, - widget.headerStyle, - widget.view, - widget.numberOfWeeksInView, - widget.monthFormat, - widget.datePickerTheme, - widget.isRtl, - widget.locale, - widget.enableMultiView, - widget.multiViewSpacing, - widget.hoverColor, - _hovering, - widget.textScaleFactor, - widget.isHijri, - widget.localizations, - widget.navigationDirection), + widget.visibleDates, + widget.headerStyle, + widget.view, + widget.numberOfWeeksInView, + widget.monthFormat, + widget.datePickerTheme, + widget.isRtl, + widget.locale, + widget.enableMultiView, + widget.multiViewSpacing, + widget.hoverColor, + _hovering, + widget.textScaleFactor, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + ), size: Size(headerWidth, widget.height), - ))); + ), + ), + ); } - Container _getLeftArrow(double arrowWidth, Color arrowColor, - Color prevArrowColor, double arrowSize) { + Container _getLeftArrow( + double arrowWidth, + Color arrowColor, + Color prevArrowColor, + double arrowSize, + ) { return Container( alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? + color: + widget.headerStyle.backgroundColor ?? widget.datePickerTheme.headerBackgroundColor, width: arrowWidth, padding: EdgeInsets.zero, @@ -8132,7 +8642,8 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { hoverColor: prevArrowColor != arrowColor ? Colors.transparent : null, highlightColor: prevArrowColor != arrowColor ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? + color: + widget.headerStyle.backgroundColor ?? widget.datePickerTheme.headerBackgroundColor, onPressed: widget.previousNavigationCallback, padding: EdgeInsets.zero, @@ -8156,11 +8667,16 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { ); } - Container _getRightArrow(double arrowWidth, Color arrowColor, - Color nextArrowColor, double arrowSize) { + Container _getRightArrow( + double arrowWidth, + Color arrowColor, + Color nextArrowColor, + double arrowSize, + ) { return Container( alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? + color: + widget.headerStyle.backgroundColor ?? widget.datePickerTheme.headerBackgroundColor, width: arrowWidth, padding: EdgeInsets.zero, @@ -8170,7 +8686,8 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { hoverColor: nextArrowColor != arrowColor ? Colors.transparent : null, highlightColor: nextArrowColor != arrowColor ? Colors.transparent : null, - color: widget.headerStyle.backgroundColor ?? + color: + widget.headerStyle.backgroundColor ?? widget.datePickerTheme.headerBackgroundColor, onPressed: widget.nextNavigationCallback, padding: EdgeInsets.zero, @@ -8197,23 +8714,23 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { class _PickerHeaderPainter extends CustomPainter { _PickerHeaderPainter( - this.visibleDates, - this.headerStyle, - this.view, - this.numberOfWeeksInView, - this.monthFormat, - this.datePickerTheme, - this.isRtl, - this.locale, - this.enableMultiView, - this.multiViewSpacing, - this.hoverColor, - this.hovering, - this.textScaleFactor, - this.isHijri, - this.localizations, - this.navigationDirection) - : super(repaint: visibleDates); + this.visibleDates, + this.headerStyle, + this.view, + this.numberOfWeeksInView, + this.monthFormat, + this.datePickerTheme, + this.isRtl, + this.locale, + this.enableMultiView, + this.multiViewSpacing, + this.hoverColor, + this.hovering, + this.textScaleFactor, + this.isHijri, + this.localizations, + this.navigationDirection, + ) : super(repaint: visibleDates); final DateRangePickerHeaderStyle headerStyle; final DateRangePickerView view; @@ -8240,42 +8757,44 @@ class _PickerHeaderPainter extends CustomPainter { double xPosition = 0; _textPainter.textDirection = TextDirection.ltr; _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); _textPainter.maxLines = 1; _headerText = ''; - final double width = (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.horizontal) && - headerStyle.textAlign == TextAlign.center - ? (size.width - multiViewSpacing) / 2 - : size.width; - final int count = (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.horizontal) && - headerStyle.textAlign == TextAlign.center - ? 2 - : 1; + final double width = + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal) && + headerStyle.textAlign == TextAlign.center + ? (size.width - multiViewSpacing) / 2 + : size.width; + final int count = + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal) && + headerStyle.textAlign == TextAlign.center + ? 2 + : 1; for (int j = 0; j < count; j++) { final int currentViewIndex = isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; xPosition = (currentViewIndex * width) + 10; final String text = _getHeaderText( - visibleDates.value, - view, - j, - isHijri, - numberOfWeeksInView, - monthFormat, - enableMultiView, - headerStyle, - navigationDirection, - locale, - localizations); + visibleDates.value, + view, + j, + isHijri, + numberOfWeeksInView, + monthFormat, + enableMultiView, + headerStyle, + navigationDirection, + locale, + localizations, + ); _headerText += j == 1 ? ' $text' : text; - TextStyle? style = - headerStyle.textStyle ?? datePickerTheme.headerTextStyle; + TextStyle? style = datePickerTheme.headerTextStyle; if (hovering) { style = style!.copyWith(color: hoverColor); } @@ -8289,10 +8808,11 @@ class _PickerHeaderPainter extends CustomPainter { double textWidth = ((currentViewIndex + 1) * width) - xPosition; textWidth = textWidth > 0 ? textWidth : 0; - _textPainter.layout(minWidth: textWidth, maxWidth: textWidth); + _textPainter.layout(maxWidth: textWidth); if (headerStyle.textAlign == TextAlign.center) { - xPosition = (currentViewIndex * width) + + xPosition = + (currentViewIndex * width) + (currentViewIndex * multiViewSpacing) + (width / 2) - (_textPainter.width / 2); @@ -8306,7 +8826,9 @@ class _PickerHeaderPainter extends CustomPainter { ((currentViewIndex + 1) * width) - _textPainter.width - xPosition; } _textPainter.paint( - canvas, Offset(xPosition, size.height / 2 - _textPainter.height / 2)); + canvas, + Offset(xPosition, size.height / 2 - _textPainter.height / 2), + ); } } @@ -8353,25 +8875,25 @@ class _PickerHeaderPainter extends CustomPainter { class _PickerViewHeaderPainter extends CustomPainter { /// Constructor to create picker view header view instance. _PickerViewHeaderPainter( - this.visibleDates, - this.navigationMode, - this.viewHeaderStyle, - this.viewHeaderHeight, - this.monthViewSettings, - this.datePickerTheme, - this.locale, - this.isRtl, - this.monthCellStyle, - this.enableMultiView, - this.multiViewSpacing, - this.todayHighlightColor, - this.textScaleFactor, - this.isHijri, - this.navigationDirection, - this.viewHeaderVisibleDates, - this.showWeekNumber, - this.isMobilePlatform) - : super(repaint: viewHeaderVisibleDates); + this.visibleDates, + this.navigationMode, + this.viewHeaderStyle, + this.viewHeaderHeight, + this.monthViewSettings, + this.datePickerTheme, + this.locale, + this.isRtl, + this.monthCellStyle, + this.enableMultiView, + this.multiViewSpacing, + this.todayHighlightColor, + this.textScaleFactor, + this.isHijri, + this.navigationDirection, + this.viewHeaderVisibleDates, + this.showWeekNumber, + this.isMobilePlatform, + ) : super(repaint: viewHeaderVisibleDates); /// Defines the view header style. final DateRangePickerViewHeaderStyle viewHeaderStyle; @@ -8418,9 +8940,10 @@ class _PickerViewHeaderPainter extends CustomPainter { /// Defines the navigation direction for [SfDateRangePicker]. final DateRangePickerNavigationDirection navigationDirection; final TextPainter _textPainter = TextPainter( - textDirection: TextDirection.ltr, - textAlign: TextAlign.left, - textWidthBasis: TextWidthBasis.longestLine); + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + ); final ValueNotifier>? viewHeaderVisibleDates; @@ -8435,42 +8958,49 @@ class _PickerViewHeaderPainter extends CustomPainter { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); final double weekNumberPanelWidth = DateRangePickerHelper.getWeekNumberPanelWidth( - showWeekNumber, size.width, isMobilePlatform); - double width = showWeekNumber - ? (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek - : size.width / DateTime.daysPerWeek; + showWeekNumber, + size.width, + isMobilePlatform, + ); + double width = + showWeekNumber + ? (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek + : size.width / DateTime.daysPerWeek; if (enableMultiView && navigationDirection == DateRangePickerNavigationDirection.horizontal) { - width = showWeekNumber - ? (size.width - multiViewSpacing - (weekNumberPanelWidth * 2)) / - (DateTime.daysPerWeek * 2) - : (size.width - multiViewSpacing) / (DateTime.daysPerWeek * 2); + width = + showWeekNumber + ? (size.width - multiViewSpacing - (weekNumberPanelWidth * 2)) / + (DateTime.daysPerWeek * 2) + : (size.width - multiViewSpacing) / (DateTime.daysPerWeek * 2); } /// Initializes the default text style for the texts in view header of /// picker. - final TextStyle? viewHeaderDayStyle = - viewHeaderStyle.textStyle ?? datePickerTheme.viewHeaderTextStyle; + final TextStyle? viewHeaderDayStyle = datePickerTheme.viewHeaderTextStyle; final dynamic today = DateRangePickerHelper.getToday(isHijri); TextStyle? dayTextStyle = viewHeaderDayStyle; double xPosition = isRtl ? 0 : weekNumberPanelWidth; double yPosition = 0; - final int count = (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.horizontal) - ? 2 - : 1; - final int datesCount = (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.horizontal) - ? visibleDates.length ~/ 2 - : visibleDates.length; + final int count = + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal) + ? 2 + : 1; + final int datesCount = + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.horizontal) + ? visibleDates.length ~/ 2 + : visibleDates.length; final bool isVerticalScroll = navigationDirection == DateRangePickerNavigationDirection.vertical && - navigationMode == DateRangePickerNavigationMode.scroll; - visibleDates = viewHeaderVisibleDates != null - ? viewHeaderVisibleDates!.value - : visibleDates; + navigationMode == DateRangePickerNavigationMode.scroll; + visibleDates = + viewHeaderVisibleDates != null + ? viewHeaderVisibleDates!.value + : visibleDates; for (int j = 0; j < count; j++) { final int currentViewIndex = @@ -8478,7 +9008,8 @@ class _PickerViewHeaderPainter extends CustomPainter { dynamic currentDate; final int month = visibleDates[(currentViewIndex * datesCount) + (datesCount ~/ 2)] - .month as int; + .month + as int; final int year = visibleDates[(currentViewIndex * datesCount) + (datesCount ~/ 2)].year as int; @@ -8487,18 +9018,23 @@ class _PickerViewHeaderPainter extends CustomPainter { final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - monthViewSettings, isHijri); + monthViewSettings, + isHijri, + ); final bool isTodayMonth = isDateWithInDateRange( - visibleDates[(currentViewIndex * datesCount)], - visibleDates[((currentViewIndex + 1) * datesCount) - 1], - today); - final bool hasToday = isVerticalScroll || + visibleDates[(currentViewIndex * datesCount)], + visibleDates[((currentViewIndex + 1) * datesCount) - 1], + today, + ); + final bool hasToday = + isVerticalScroll || (numberOfWeeksInView > 0 && numberOfWeeksInView < 6 || month == currentMonth && year == currentYear); for (int i = 0; i < DateTime.daysPerWeek; i++) { - int index = isRtl - ? DateRangePickerHelper.getRtlIndex(DateTime.daysPerWeek, i) - : i; + int index = + isRtl + ? DateRangePickerHelper.getRtlIndex(DateTime.daysPerWeek, i) + : i; index = index + (currentViewIndex * datesCount); currentDate = visibleDates[index]; String dayText = @@ -8510,10 +9046,11 @@ class _PickerViewHeaderPainter extends CustomPainter { if (hasToday && currentDate.weekday == today.weekday && (isTodayMonth || isVerticalScroll)) { - final Color textColor = monthCellStyle.todayTextStyle != null && - monthCellStyle.todayTextStyle.color != null - ? monthCellStyle.todayTextStyle.color! as Color - : todayHighlightColor ?? datePickerTheme.todayHighlightColor!; + final Color textColor = + monthCellStyle.todayTextStyle != null && + monthCellStyle.todayTextStyle.color != null + ? monthCellStyle.todayTextStyle.color! as Color + : todayHighlightColor ?? datePickerTheme.todayHighlightColor!; dayTextStyle = viewHeaderDayStyle!.copyWith(color: textColor); } else { dayTextStyle = viewHeaderDayStyle; @@ -8524,14 +9061,14 @@ class _PickerViewHeaderPainter extends CustomPainter { style: dayTextStyle, ); - _textPainter.textScaleFactor = textScaleFactor; + _textPainter.textScaler = TextScaler.linear(textScaleFactor); _textPainter.text = dayTextSpan; - _textPainter.layout(minWidth: width, maxWidth: width); + _textPainter.layout(maxWidth: width); yPosition = (viewHeaderHeight - _textPainter.height) / 2; _textPainter.paint( - canvas, - Offset( - xPosition + (width / 2 - _textPainter.width / 2), yPosition)); + canvas, + Offset(xPosition + (width / 2 - _textPainter.width / 2), yPosition), + ); xPosition += width; } @@ -8587,17 +9124,22 @@ class _PickerViewHeaderPainter extends CustomPainter { const double top = 0; for (int j = 0; j < count; j++) { 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(isHijri - ? visibleDates[(j * datesCount) + i].toDateTime() - : visibleDates[(j * datesCount) + i]) - .toUpperCase(), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, size.height), + properties: SemanticsProperties( + label: + DateFormat('EEEEE') + .format( + isHijri + ? visibleDates[(j * datesCount) + i].toDateTime() + : visibleDates[(j * datesCount) + i], + ) + .toUpperCase(), + textDirection: TextDirection.ltr, + ), ), - )); + ); if (isRtl) { left -= cellWidth; } else { @@ -8739,9 +9281,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _triggerSelectableDayPredicates(_currentViewVisibleDates); _triggerViewChangedCallback(); _animationController = AnimationController( - duration: const Duration(milliseconds: 250), - vsync: this, - animationBehavior: AnimationBehavior.normal); + duration: const Duration(milliseconds: 250), + vsync: this, + ); _tween = Tween(begin: 0.0, end: 0.1); _animation = _tween.animate(_animationController) ..addListener(_animationListener); @@ -8771,13 +9313,6 @@ class _PickerScrollViewState extends State<_PickerScrollView> _children.clear(); } - if (oldWidget.picker.controller != widget.controller) { - _position = 0; - _children.clear(); - _updateVisibleDates(); - _triggerViewChangedCallback(); - } - if (widget.isRtl != oldWidget.isRtl || widget.picker.enableMultiView != oldWidget.picker.enableMultiView) { _position = 0; @@ -8789,8 +9324,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _updateSettings(oldWidget); - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView == DateRangePickerView.year && widget.picker.monthFormat != oldWidget.picker.monthFormat) { @@ -8828,26 +9364,37 @@ class _PickerScrollViewState extends State<_PickerScrollView> oldWidget.picker.monthViewSettings.viewHeaderHeight != widget.picker.monthViewSettings.viewHeaderHeight || DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri) != + widget.picker.monthViewSettings, + widget.picker.isHijri, + ) != DateRangePickerHelper.canShowLeadingAndTrailingDates( - oldWidget.picker.monthViewSettings, - oldWidget.picker.isHijri))) { + oldWidget.picker.monthViewSettings, + oldWidget.picker.isHijri, + ))) { _children.clear(); _position = 0; if (DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri) != + widget.picker.monthViewSettings, + widget.picker.isHijri, + ) != DateRangePickerHelper.canShowLeadingAndTrailingDates( - oldWidget.picker.monthViewSettings, oldWidget.picker.isHijri)) { + oldWidget.picker.monthViewSettings, + oldWidget.picker.isHijri, + )) { _disabledDates?.clear(); _triggerSelectableDayPredicates(_currentViewVisibleDates); } } if (DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri) != + widget.picker.monthViewSettings, + widget.picker.isHijri, + ) != DateRangePickerHelper.getNumberOfWeeksInView( - oldWidget.picker.monthViewSettings, oldWidget.picker.isHijri) || + oldWidget.picker.monthViewSettings, + oldWidget.picker.isHijri, + ) || widget.picker.monthViewSettings.firstDayOfWeek != oldWidget.picker.monthViewSettings.firstDayOfWeek) { _updateVisibleDates(); @@ -8868,24 +9415,24 @@ class _PickerScrollViewState extends State<_PickerScrollView> _triggerSelectableDayPredicates(_currentViewVisibleDates); } - if (oldWidget.picker.controller != widget.controller || - widget.controller == null) { - widget.getPickerStateValues(_pickerStateDetails); - super.didUpdateWidget(oldWidget); - return; - } - - if (oldWidget.picker.controller?.displayDate != - widget.controller?.displayDate || - !isSameDate( - _pickerStateDetails.currentDate, widget.controller.displayDate)) { + if (!isSameDate( + _pickerStateDetails.currentDate, + widget.controller.displayDate, + )) { _pickerStateDetails.currentDate = widget.controller?.displayDate; _updateVisibleDates(); _triggerSelectableDayPredicates(_currentViewVisibleDates); _triggerViewChangedCallback(); } - _drawSelection(oldWidget.picker.controller, widget.controller); + if (_pickerStateDetails.view != pickerView) { + _position = 0; + _children.clear(); + _updateVisibleDates(); + _triggerViewChangedCallback(); + } + + _drawSelection(oldWidget.controller, widget.controller, pickerView); widget.getPickerStateValues(_pickerStateDetails); super.didUpdateWidget(oldWidget); } @@ -8918,56 +9465,63 @@ class _PickerScrollViewState extends State<_PickerScrollView> bottom: bottomPosition, top: topPosition, child: GestureDetector( + onHorizontalDragStart: + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none + ? _onHorizontalStart + : null, + onHorizontalDragUpdate: + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none + ? _onHorizontalUpdate + : null, + onHorizontalDragEnd: + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none + ? _onHorizontalEnd + : null, + onVerticalDragStart: + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none + ? _onVerticalStart + : null, + onVerticalDragUpdate: + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none + ? _onVerticalUpdate + : null, + onVerticalDragEnd: + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.vertical && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none + ? _onVerticalEnd + : null, child: FocusScope( node: _focusNode, - onKey: _onKeyDown, + onKeyEvent: _onKeyDown, child: CustomScrollViewerLayout( - _addViews(context), - widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? CustomScrollDirection.horizontal - : CustomScrollDirection.vertical, - _position, - _currentChildIndex), + _addViews(context), + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? CustomScrollDirection.horizontal + : CustomScrollDirection.vertical, + _position, + _currentChildIndex, + ), ), - onHorizontalDragStart: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal && - widget.picker.navigationMode != - DateRangePickerNavigationMode.none - ? _onHorizontalStart - : null, - onHorizontalDragUpdate: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal && - widget.picker.navigationMode != - DateRangePickerNavigationMode.none - ? _onHorizontalUpdate - : null, - onHorizontalDragEnd: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal && - widget.picker.navigationMode != - DateRangePickerNavigationMode.none - ? _onHorizontalEnd - : null, - onVerticalDragStart: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical && - widget.picker.navigationMode != - DateRangePickerNavigationMode.none - ? _onVerticalStart - : null, - onVerticalDragUpdate: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical && - widget.picker.navigationMode != - DateRangePickerNavigationMode.none - ? _onVerticalUpdate - : null, - onVerticalDragEnd: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical && - widget.picker.navigationMode != - DateRangePickerNavigationMode.none - ? _onVerticalEnd - : null, ), - ) + ), ], ); } @@ -8987,34 +9541,40 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.getPickerStateValues(_pickerStateDetails); final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); final dynamic currentDate = _pickerStateDetails.currentDate; final dynamic prevDate = DateRangePickerHelper.getPreviousViewStartDate( - DateRangePickerHelper.getPickerView(widget.controller.view), - numberOfWeeksInView, - _pickerStateDetails.currentDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerHelper.getPickerView(widget.controller.view), + numberOfWeeksInView, + _pickerStateDetails.currentDate, + widget.isRtl, + widget.picker.isHijri, + ); final dynamic nextDate = DateRangePickerHelper.getNextViewStartDate( - DateRangePickerHelper.getPickerView(widget.controller.view), - numberOfWeeksInView, - _pickerStateDetails.currentDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerHelper.getPickerView(widget.controller.view), + numberOfWeeksInView, + _pickerStateDetails.currentDate, + widget.isRtl, + widget.picker.isHijri, + ); dynamic afterNextViewDate; List? afterVisibleDates; if (widget.picker.enableMultiView) { afterNextViewDate = DateRangePickerHelper.getNextViewStartDate( - DateRangePickerHelper.getPickerView(widget.controller.view), - numberOfWeeksInView, - widget.isRtl ? prevDate : nextDate, - false, - widget.picker.isHijri); + DateRangePickerHelper.getPickerView(widget.controller.view), + numberOfWeeksInView, + widget.isRtl ? prevDate : nextDate, + false, + widget.picker.isHijri, + ); } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (view) { case DateRangePickerView.month: @@ -9024,21 +9584,30 @@ class _PickerScrollViewState extends State<_PickerScrollView> null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); _previousViewVisibleDates = getVisibleDates( prevDate, null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); _nextViewVisibleDates = getVisibleDates( nextDate, null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); if (widget.picker.enableMultiView) { afterVisibleDates = getVisibleDates( @@ -9046,7 +9615,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); } } @@ -9056,14 +9628,26 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerView.century: { _visibleDates = DateRangePickerHelper.getVisibleYearDates( - currentDate, view, widget.picker.isHijri); + currentDate, + view, + widget.picker.isHijri, + ); _previousViewVisibleDates = DateRangePickerHelper.getVisibleYearDates( - prevDate, view, widget.picker.isHijri); + prevDate, + view, + widget.picker.isHijri, + ); _nextViewVisibleDates = DateRangePickerHelper.getVisibleYearDates( - nextDate, view, widget.picker.isHijri); + nextDate, + view, + widget.picker.isHijri, + ); if (widget.picker.enableMultiView) { afterVisibleDates = DateRangePickerHelper.getVisibleYearDates( - afterNextViewDate, view, widget.picker.isHijri); + afterNextViewDate, + view, + widget.picker.isHijri, + ); } } } @@ -9119,9 +9703,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _triggerSelectableDayPredicates(_getCurrentVisibleDates(true)); _animationController.duration = const Duration(milliseconds: 500); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _animationController.forward().then( + (dynamic value) => _updateNextView(), + ); /// updates the current view visible dates when the view swiped _updateCurrentViewVisibleDates(isNextView: true); @@ -9152,9 +9736,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _triggerSelectableDayPredicates(_getCurrentVisibleDates(false)); _animationController.duration = const Duration(milliseconds: 500); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + _animationController.forward().then( + (dynamic value) => _updatePreviousView(), + ); /// updates the current view visible dates when the view swiped. _updateCurrentViewVisibleDates(); @@ -9185,41 +9769,47 @@ class _PickerScrollViewState extends State<_PickerScrollView> } void _updateNextViewVisibleDates() { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); dynamic currentViewDate = _currentViewVisibleDates[0]; if ((pickerView == DateRangePickerView.month && (numberOfWeeksInView == 6 || widget.picker.isHijri)) || pickerView == DateRangePickerView.year || pickerView == DateRangePickerView.decade || pickerView == DateRangePickerView.century) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / + currentViewDate = + _currentViewVisibleDates[(_currentViewVisibleDates.length / (widget.picker.enableMultiView ? 4 : 2)) .truncate()]; } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); currentViewDate = DateRangePickerHelper.getNextViewStartDate( - view, - numberOfWeeksInView, - currentViewDate, - widget.isRtl, - widget.picker.isHijri); + view, + numberOfWeeksInView, + currentViewDate, + widget.isRtl, + widget.picker.isHijri, + ); List? afterVisibleDates; dynamic afterNextViewDate; if (widget.picker.enableMultiView && !widget.isRtl) { afterNextViewDate = DateRangePickerHelper.getNextViewStartDate( - view, - numberOfWeeksInView, - currentViewDate, - widget.isRtl, - widget.picker.isHijri); + view, + numberOfWeeksInView, + currentViewDate, + widget.isRtl, + widget.picker.isHijri, + ); } List dates; switch (view) { @@ -9230,7 +9820,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); if (widget.picker.enableMultiView && !widget.isRtl) { afterVisibleDates = getVisibleDates( @@ -9238,7 +9831,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); } } @@ -9248,10 +9844,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerView.century: { dates = DateRangePickerHelper.getVisibleYearDates( - currentViewDate, view, widget.picker.isHijri); + currentViewDate, + view, + widget.picker.isHijri, + ); if (widget.picker.enableMultiView && !widget.isRtl) { afterVisibleDates = DateRangePickerHelper.getVisibleYearDates( - afterNextViewDate, view, widget.picker.isHijri); + afterNextViewDate, + view, + widget.picker.isHijri, + ); } } } @@ -9270,7 +9872,8 @@ class _PickerScrollViewState extends State<_PickerScrollView> } List _updateNextVisibleDateForMultiView( - List? afterVisibleDates) { + List? afterVisibleDates, + ) { List dates; if (widget.picker.isHijri) { dates = []; @@ -9291,42 +9894,48 @@ class _PickerScrollViewState extends State<_PickerScrollView> } void _updatePreviousViewVisibleDates() { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); dynamic currentViewDate = _currentViewVisibleDates[0]; if ((pickerView == DateRangePickerView.month && (numberOfWeeksInView == 6 || widget.picker.isHijri)) || pickerView == DateRangePickerView.year || pickerView == DateRangePickerView.decade || pickerView == DateRangePickerView.century) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / + currentViewDate = + _currentViewVisibleDates[(_currentViewVisibleDates.length / (widget.picker.enableMultiView ? 4 : 2)) .truncate()]; } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); currentViewDate = DateRangePickerHelper.getPreviousViewStartDate( - view, - numberOfWeeksInView, - currentViewDate, - widget.isRtl, - widget.picker.isHijri); + view, + numberOfWeeksInView, + currentViewDate, + widget.isRtl, + widget.picker.isHijri, + ); List dates; List? afterVisibleDates; dynamic afterNextViewDate; if (widget.picker.enableMultiView && widget.isRtl) { afterNextViewDate = DateRangePickerHelper.getPreviousViewStartDate( - view, - numberOfWeeksInView, - currentViewDate, - widget.isRtl, - widget.picker.isHijri); + view, + numberOfWeeksInView, + currentViewDate, + widget.isRtl, + widget.picker.isHijri, + ); } switch (view) { @@ -9337,7 +9946,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); if (widget.picker.enableMultiView && widget.isRtl) { afterVisibleDates = getVisibleDates( @@ -9345,7 +9957,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> null, widget.picker.monthViewSettings.firstDayOfWeek, DateRangePickerHelper.getViewDatesCount( - view, numberOfWeeksInView, widget.picker.isHijri), + view, + numberOfWeeksInView, + widget.picker.isHijri, + ), ); } } @@ -9355,10 +9970,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerView.century: { dates = DateRangePickerHelper.getVisibleYearDates( - currentViewDate, view, widget.picker.isHijri); + currentViewDate, + view, + widget.picker.isHijri, + ); if (widget.picker.enableMultiView && widget.isRtl) { afterVisibleDates = DateRangePickerHelper.getVisibleYearDates( - afterNextViewDate, view, widget.picker.isHijri); + afterNextViewDate, + view, + widget.picker.isHijri, + ); } } } @@ -9377,7 +9998,8 @@ class _PickerScrollViewState extends State<_PickerScrollView> } List _updatePreviousDatesForMultiView( - List? afterVisibleDates) { + List? afterVisibleDates, + ) { List dates; if (widget.picker.isHijri) { dates = []; @@ -9427,11 +10049,13 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.datePickerTheme, _focusNode, widget.textScaleFactor, - DateRangePickerHelper.cloneList((_disabledDates != null && - _disabledDates?.values != null && - _disabledDates!.values.isNotEmpty) - ? _disabledDates?.values.first - : null), + DateRangePickerHelper.cloneList( + (_disabledDates != null && + _disabledDates?.values != null && + _disabledDates!.values.isNotEmpty) + ? _disabledDates?.values.first + : null, + ), key: key, getPickerStateDetails: (PickerStateArgs details) { _getPickerViewStateDetails(details); @@ -9456,11 +10080,20 @@ class _PickerScrollViewState extends State<_PickerScrollView> } final _PickerView previousView = _updateViews( - _previousView!, _previousView!.visibleDates, _previousViewVisibleDates); - final _PickerView currentView = - _updateViews(_currentView!, _currentView!.visibleDates, _visibleDates); + _previousView!, + _previousView!.visibleDates, + _previousViewVisibleDates, + ); + final _PickerView currentView = _updateViews( + _currentView!, + _currentView!.visibleDates, + _visibleDates, + ); final _PickerView nextView = _updateViews( - _nextView!, _nextView!.visibleDates, _nextViewVisibleDates); + _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 @@ -9480,7 +10113,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> // method to check and update the views and appointments on the swiping end _PickerView _updateViews( - _PickerView view, List viewDates, List visibleDates) { + _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) { @@ -9493,7 +10129,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _disabledDates!.keys.isNotEmpty && _disabledDates!.keys.first == viewDates && !DateRangePickerHelper.isDateCollectionEquals( - view.disableDatePredicates, _disabledDates!.values.first)) { + view.disableDatePredicates, + _disabledDates!.values.first, + )) { view = _getView(viewDates, view.key!); _children[index] = view; } @@ -9538,15 +10176,19 @@ class _PickerScrollViewState extends State<_PickerScrollView> } } - void _drawSelection(dynamic oldValue, dynamic newValue) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + void _drawSelection( + dynamic oldValue, + dynamic newValue, + DateRangePickerView pickerView, + ) { switch (widget.picker.selectionMode) { case DateRangePickerSelectionMode.single: { if (oldValue.selectedDate != newValue.selectedDate || !isSameDate( - _pickerStateDetails.selectedDate, newValue.selectedDate)) { + _pickerStateDetails.selectedDate, + newValue.selectedDate, + )) { _pickerStateDetails.selectedDate = newValue.selectedDate; if (pickerView != DateRangePickerView.month && !widget.picker.allowViewNavigation) { @@ -9563,7 +10205,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> { if (oldValue.selectedDates != newValue.selectedDates || !DateRangePickerHelper.isDateCollectionEquals( - _pickerStateDetails.selectedDates, newValue.selectedDates)) { + _pickerStateDetails.selectedDates, + newValue.selectedDates, + )) { _pickerStateDetails.selectedDates = newValue.selectedDates as List?; if (pickerView != DateRangePickerView.month && @@ -9582,7 +10226,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> { if (oldValue.selectedRange != newValue.selectedRange || !DateRangePickerHelper.isRangeEquals( - _pickerStateDetails.selectedRange, newValue.selectedRange)) { + _pickerStateDetails.selectedRange, + newValue.selectedRange, + )) { _pickerStateDetails.selectedRange = newValue.selectedRange; if (pickerView != DateRangePickerView.month && !widget.picker.allowViewNavigation) { @@ -9599,8 +10245,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> { if (oldValue.selectedRanges != newValue.selectedRanges || !DateRangePickerHelper.isDateRangesEquals( - _pickerStateDetails.selectedRanges, - newValue.selectedRanges)) { + _pickerStateDetails.selectedRanges, + newValue.selectedRanges, + )) { _pickerStateDetails.selectedRanges = newValue.selectedRanges as List?; if (pickerView != DateRangePickerView.month && @@ -9619,8 +10266,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Update the selection details to scroll view children except current view /// while view navigation. void _updateSelection({dynamic selectedDate}) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); /// Update selection on month view and update selection on year view when /// [allowViewNavigation] property on [SfDateRangePicker] as false @@ -9635,8 +10283,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> continue; } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); final _PickerViewState viewState = _getCurrentViewState(i); switch (view) { @@ -9666,8 +10315,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Draw the selection on current month view when selected date value /// changed dynamically. void _drawMonthSelection() { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView != DateRangePickerView.month || _children.isEmpty) { return; } @@ -9691,8 +10341,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Draw the selection on current year, decade, century view when /// selected date value changed dynamically. void _drawYearSelection() { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView == DateRangePickerView.month || _children.isEmpty) { return; } @@ -9747,22 +10398,29 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Updates the current view visible dates for picker in the swiping end void _updateCurrentViewVisibleDates({bool isNextView = false}) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); _currentViewVisibleDates = _getCurrentVisibleDates(isNextView); _pickerStateDetails.currentViewVisibleDates = _currentViewVisibleDates; _pickerStateDetails.currentDate = _currentViewVisibleDates[0]; final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); if (pickerView == DateRangePickerView.month && (numberOfWeeksInView == 6 || widget.picker.isHijri)) { - final dynamic date = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ + final dynamic date = + _currentViewVisibleDates[_currentViewVisibleDates.length ~/ (widget.picker.enableMultiView ? 4 : 2)]; _pickerStateDetails.currentDate = DateRangePickerHelper.getDate( - date.year, date.month, 1, widget.picker.isHijri); + date.year, + date.month, + 1, + widget.picker.isHijri, + ); } widget.updatePickerStateValues(_pickerStateDetails); @@ -9829,7 +10487,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; } @@ -9838,15 +10496,23 @@ 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, LogicalKeyboardKey key, - _PickerView view, _PickerViewState state) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + dynamic _getYearSelectedDate( + dynamic selectedDate, + LogicalKeyboardKey key, + _PickerView view, + _PickerViewState state, + ) { + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); dynamic date; /// Calculate the index value for previous selected date. int index = DateRangePickerHelper.getDateCellIndex( - view.visibleDates, selectedDate, widget.controller.view); + view.visibleDates, + selectedDate, + widget.controller.view, + ); 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 @@ -9912,11 +10578,14 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Return the next date for year, decade and century view in keyboard /// navigation dynamic _updateNextYearSelectionDate(dynamic selectedDate) { - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); switch (view) { case DateRangePickerView.month: { @@ -9925,29 +10594,32 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerView.year: { return DateRangePickerHelper.getNextViewStartDate( - DateRangePickerView.month, - numberOfWeeksInView, - selectedDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerView.month, + numberOfWeeksInView, + selectedDate, + widget.isRtl, + widget.picker.isHijri, + ); } case DateRangePickerView.decade: { return DateRangePickerHelper.getNextViewStartDate( - DateRangePickerView.year, - numberOfWeeksInView, - selectedDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerView.year, + numberOfWeeksInView, + selectedDate, + widget.isRtl, + widget.picker.isHijri, + ); } case DateRangePickerView.century: { return DateRangePickerHelper.getNextViewStartDate( - DateRangePickerView.decade, - numberOfWeeksInView, - selectedDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerView.decade, + numberOfWeeksInView, + selectedDate, + widget.isRtl, + widget.picker.isHijri, + ); } } @@ -9959,9 +10631,12 @@ class _PickerScrollViewState extends State<_PickerScrollView> dynamic _updatePreviousYearSelectionDate(dynamic selectedDate) { final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (view) { case DateRangePickerView.month: { @@ -9970,40 +10645,43 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerView.year: { return DateRangePickerHelper.getPreviousViewStartDate( - DateRangePickerView.month, - numberOfWeeksInView, - selectedDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerView.month, + numberOfWeeksInView, + selectedDate, + widget.isRtl, + widget.picker.isHijri, + ); } case DateRangePickerView.decade: { return DateRangePickerHelper.getPreviousViewStartDate( - DateRangePickerView.year, - numberOfWeeksInView, - selectedDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerView.year, + numberOfWeeksInView, + selectedDate, + widget.isRtl, + widget.picker.isHijri, + ); } case DateRangePickerView.century: { return DateRangePickerHelper.getPreviousViewStartDate( - DateRangePickerView.decade, - numberOfWeeksInView, - selectedDate, - widget.isRtl, - widget.picker.isHijri); + DateRangePickerView.decade, + numberOfWeeksInView, + selectedDate, + widget.isRtl, + widget.picker.isHijri, + ); } } return selectedDate; } - KeyEventResult _switchViewsByKeyBoardEvent(RawKeyEvent event) { + KeyEventResult _switchViewsByKeyBoardEvent(KeyEvent event) { /// Ctrl + and Ctrl - used by browser to zoom the page, hence as referred /// EJ2 scheduler, we have used alt + numeric to switch between views in /// datepicker web - if (event.isAltPressed) { + if (HardwareKeyboard.instance.isAltPressed) { if (event.logicalKey == LogicalKeyboardKey.digit1) { _pickerStateDetails.view = DateRangePickerView.month; } else if (event.logicalKey == LogicalKeyboardKey.digit2) { @@ -10024,45 +10702,57 @@ class _PickerScrollViewState extends State<_PickerScrollView> } KeyEventResult _updateYearSelectionByKeyBoardNavigation( - _PickerViewState currentVisibleViewState, - _PickerView currentVisibleView, - DateRangePickerView pickerView, - RawKeyEvent event) { + _PickerViewState currentVisibleViewState, + _PickerView currentVisibleView, + DateRangePickerView pickerView, + KeyEvent event, + ) { dynamic selectedDate; if (_pickerStateDetails.selectedDate != null && widget.picker.selectionMode == DateRangePickerSelectionMode.single) { - selectedDate = _getYearSelectedDate(_pickerStateDetails.selectedDate, - event.logicalKey, currentVisibleView, currentVisibleViewState); + selectedDate = _getYearSelectedDate( + _pickerStateDetails.selectedDate, + event.logicalKey, + currentVisibleView, + currentVisibleViewState, + ); if (selectedDate != null && DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + )) { _pickerStateDetails.selectedDate = selectedDate; } } else if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple && _pickerStateDetails.selectedDates != null && _pickerStateDetails.selectedDates!.isNotEmpty && - event.isShiftPressed) { - final dynamic date = _pickerStateDetails - .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; + HardwareKeyboard.instance.isShiftPressed) { + final dynamic date = + _pickerStateDetails + .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; selectedDate = _getYearSelectedDate( - date, event.logicalKey, currentVisibleView, currentVisibleViewState); + date, + event.logicalKey, + currentVisibleView, + currentVisibleViewState, + ); if (selectedDate != null && DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { - _pickerStateDetails.selectedDates = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates) - ?..add(selectedDate); + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + )) { + _pickerStateDetails.selectedDates = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedDates, + )?..add(selectedDate); } } else if ((widget.picker.selectionMode == DateRangePickerSelectionMode.range || @@ -10070,22 +10760,28 @@ class _PickerScrollViewState extends State<_PickerScrollView> DateRangePickerSelectionMode.extendableRange) && _pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null && - event.isShiftPressed) { + HardwareKeyboard.instance.isShiftPressed) { final dynamic date = currentVisibleViewState._lastSelectedDate; selectedDate = _getYearSelectedDate( - date, event.logicalKey, currentVisibleView, currentVisibleViewState); + date, + event.logicalKey, + currentVisibleView, + currentVisibleViewState, + ); if (selectedDate == null || !DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + )) { return KeyEventResult.ignored; } - final bool isExtendableRange = widget.picker.selectionMode == + final bool isExtendableRange = + widget.picker.selectionMode == DateRangePickerSelectionMode.extendableRange; /// Ignore the selection date while the extendable range enabled and the @@ -10093,11 +10789,12 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// year view keyboard selection. if (isExtendableRange && DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return KeyEventResult.ignored; } @@ -10133,29 +10830,41 @@ class _PickerScrollViewState extends State<_PickerScrollView> } if (DateRangePickerHelper.isSameCellDates( - startDate, endDate, pickerView)) { + startDate, + endDate, + pickerView, + )) { return KeyEventResult.ignored; } endDate = DateRangePickerHelper.getLastDate( - endDate, widget.controller.view, widget.picker.isHijri); + endDate, + widget.controller.view, + widget.picker.isHijri, + ); if (widget.picker.maxDate != null) { - endDate = endDate.isAfter(widget.picker.maxDate) == true - ? widget.picker.maxDate - : endDate; + endDate = + endDate.isAfter(widget.picker.maxDate) == true + ? widget.picker.maxDate + : endDate; } startDate = DateRangePickerHelper.getFirstDate( - startDate, widget.picker.isHijri, pickerView); + startDate, + widget.picker.isHijri, + pickerView, + ); if (widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) == true - ? widget.picker.minDate - : startDate; + startDate = + startDate.isBefore(widget.picker.minDate) == true + ? widget.picker.minDate + : startDate; } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); currentVisibleViewState._lastSelectedDate = selectedDate; } else { return KeyEventResult.ignored; @@ -10171,8 +10880,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> if (_pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null && (_pickerStateDetails.selectedRange.endDate == null || - isSameDate(_pickerStateDetails.selectedRange.startDate, - _pickerStateDetails.selectedRange.endDate))) { + isSameDate( + _pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate, + ))) { dynamic startDate = _pickerStateDetails.selectedRange.startDate; dynamic endDate = selectedDate; if (startDate.isAfter(endDate) == true) { @@ -10181,13 +10892,15 @@ class _PickerScrollViewState extends State<_PickerScrollView> endDate = temp; } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); } else { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } } @@ -10240,9 +10953,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> } } } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); } else { _updateRangeSelectionByKeyboardNavigation(selectedDate); } @@ -10253,13 +10967,14 @@ class _PickerScrollViewState extends State<_PickerScrollView> } } - KeyEventResult _onKeyDown(FocusNode node, RawKeyEvent event) { + KeyEventResult _onKeyDown(FocusNode node, KeyEvent event) { KeyEventResult result = KeyEventResult.ignored; - if (event.runtimeType != RawKeyDownEvent) { + if (event.runtimeType != KeyDownEvent) { return result; } - if (event.isShiftPressed && event.logicalKey == LogicalKeyboardKey.tab) { + if (HardwareKeyboard.instance.isShiftPressed && + event.logicalKey == LogicalKeyboardKey.tab) { FocusScope.of(context).previousFocus(); return KeyEventResult.handled; } @@ -10268,33 +10983,40 @@ class _PickerScrollViewState extends State<_PickerScrollView> return KeyEventResult.handled; } - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); result = _switchViewsByKeyBoardEvent(event); - if (event.isControlPressed) { + if (HardwareKeyboard.instance.isControlPressed) { final bool canMoveToNextView = DateRangePickerHelper.canMoveToNextViewRtl( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView, - widget.picker.isHijri); + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + widget.picker.maxDate, + _currentViewVisibleDates, + widget.isRtl, + widget.picker.enableMultiView, + widget.picker.isHijri, + ); final bool canMoveToPreviousView = DateRangePickerHelper.canMoveToPreviousViewRtl( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView, - widget.picker.isHijri); + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + widget.picker.maxDate, + _currentViewVisibleDates, + widget.isRtl, + widget.picker.enableMultiView, + widget.picker.isHijri, + ); if (event.logicalKey == LogicalKeyboardKey.arrowRight && canMoveToNextView) { widget.isRtl @@ -10340,27 +11062,37 @@ class _PickerScrollViewState extends State<_PickerScrollView> if (pickerView != DateRangePickerView.month) { result = _updateYearSelectionByKeyBoardNavigation( - currentVisibleViewState, currentVisibleView, pickerView, event); + currentVisibleViewState, + currentVisibleView, + pickerView, + event, + ); return result; } - final dynamic selectedDate = - _updateSelectedDate(event, currentVisibleViewState, currentVisibleView); + final dynamic selectedDate = _updateSelectedDate( + event, + currentVisibleViewState, + currentVisibleView, + ); if (DateRangePickerHelper.isDateWithInVisibleDates( - currentVisibleView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate) || + currentVisibleView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - currentVisibleView.visibleDates, - currentVisibleView.disableDatePredicates, - selectedDate) || + currentVisibleView.visibleDates, + currentVisibleView.disableDatePredicates, + selectedDate, + ) || !DateRangePickerHelper.isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - selectedDate, - widget.picker.isHijri)) { + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri, + )) { return result; } @@ -10371,59 +11103,81 @@ class _PickerScrollViewState extends State<_PickerScrollView> DateRangePickerSelectionMode.extendableRange && _pickerStateDetails.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return result; } final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); final dynamic visibleStartDate = currentVisibleView.visibleDates[0]; - final dynamic visibleEndDate = currentVisibleView - .visibleDates[currentVisibleView.visibleDates.length - 1]; - final int datesCount = currentVisibleView.visibleDates.length ~/ + final dynamic visibleEndDate = + currentVisibleView.visibleDates[currentVisibleView.visibleDates.length - + 1]; + final int datesCount = + currentVisibleView.visibleDates.length ~/ (widget.picker.enableMultiView ? 2 : 1); - // ignore: avoid_bool_literals_in_conditional_expressions - final bool showLeadingTrailingDates = widget.picker.enableMultiView - ? false - : DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri); - final bool isCurrentMonthDate = widget.picker.enableMultiView - ? (DateRangePickerHelper.isDateAsCurrentMonthDate( - currentVisibleView.visibleDates[datesCount ~/ 2], - numberOfWeeksInView, - showLeadingTrailingDates, - selectedDate, - widget.picker.isHijri) || - DateRangePickerHelper.isDateAsCurrentMonthDate( - currentVisibleView.visibleDates[datesCount + (datesCount ~/ 2)], - numberOfWeeksInView, - showLeadingTrailingDates, - selectedDate, - widget.picker.isHijri)) - : DateRangePickerHelper.isDateAsCurrentMonthDate( - currentVisibleView.visibleDates[datesCount ~/ 2], - numberOfWeeksInView, - showLeadingTrailingDates, - selectedDate, - widget.picker.isHijri); + final bool showLeadingTrailingDates = + // ignore: avoid_bool_literals_in_conditional_expressions + widget.picker.enableMultiView + ? false + : DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); + final bool isCurrentMonthDate = + widget.picker.enableMultiView + ? (DateRangePickerHelper.isDateAsCurrentMonthDate( + currentVisibleView.visibleDates[datesCount ~/ 2], + numberOfWeeksInView, + showLeadingTrailingDates, + selectedDate, + widget.picker.isHijri, + ) || + DateRangePickerHelper.isDateAsCurrentMonthDate( + currentVisibleView.visibleDates[datesCount + + (datesCount ~/ 2)], + numberOfWeeksInView, + showLeadingTrailingDates, + selectedDate, + widget.picker.isHijri, + )) + : DateRangePickerHelper.isDateAsCurrentMonthDate( + currentVisibleView.visibleDates[datesCount ~/ 2], + numberOfWeeksInView, + showLeadingTrailingDates, + selectedDate, + widget.picker.isHijri, + ); if (!isCurrentMonthDate || !isDateWithInDateRange( - visibleStartDate, visibleEndDate, selectedDate)) { + visibleStartDate, + visibleEndDate, + selectedDate, + )) { final int month = selectedDate.month as int; final dynamic nextMonthDate = getNextMonthDate( - currentVisibleView.visibleDates[ - currentVisibleView.visibleDates.length ~/ - (widget.picker.enableMultiView ? 4 : 2)]); + currentVisibleView.visibleDates[currentVisibleView + .visibleDates + .length ~/ + (widget.picker.enableMultiView ? 4 : 2)], + ); int nextMonth = nextMonthDate.month as int; - final dynamic nextMonthEndDate = - DateRangePickerHelper.getMonthEndDate(nextMonthDate); + final dynamic nextMonthEndDate = DateRangePickerHelper.getMonthEndDate( + nextMonthDate, + ); if (isDateWithInDateRange( - visibleStartDate, visibleEndDate, nextMonthEndDate)) { + visibleStartDate, + visibleEndDate, + nextMonthEndDate, + )) { nextMonth = getNextMonthDate(nextMonthEndDate).month as int; } if (month == nextMonth) { @@ -10450,155 +11204,183 @@ class _PickerScrollViewState extends State<_PickerScrollView> } dynamic _updateSingleSelectionByKeyBoardKeys( - RawKeyEvent event, _PickerView currentView) { + KeyEvent event, + _PickerView currentView, + ) { dynamic selectedDate = _pickerStateDetails.selectedDate; if (event.logicalKey == LogicalKeyboardKey.arrowRight) { - if (isSameDate(_pickerStateDetails.selectedDate, - currentView.visibleDates[currentView.visibleDates.length - 1])) { + if (isSameDate( + _pickerStateDetails.selectedDate, + currentView.visibleDates[currentView.visibleDates.length - 1], + )) { _moveToNextViewWithAnimation(); } do { selectedDate = addDays(selectedDate, 1); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { if (isSameDate( - _pickerStateDetails.selectedDate, currentView.visibleDates[0])) { + _pickerStateDetails.selectedDate, + currentView.visibleDates[0], + )) { _moveToPreviousViewWithAnimation(); } do { selectedDate = addDays(selectedDate, -1); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { do { selectedDate = addDays(selectedDate, -DateTime.daysPerWeek); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { do { selectedDate = addDays(selectedDate, DateTime.daysPerWeek); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } return null; } - dynamic _updateMultiAndRangeSelectionByKeyBoard(RawKeyEvent event, - _PickerViewState currentState, _PickerView currentView) { + dynamic _updateMultiAndRangeSelectionByKeyBoard( + KeyEvent event, + _PickerViewState currentState, + _PickerView currentView, + ) { dynamic selectedDate; - if (event.isShiftPressed && + if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowRight) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - selectedDate = _pickerStateDetails - .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; + selectedDate = + _pickerStateDetails + .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; do { selectedDate = addDays(selectedDate, 1); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else { selectedDate = currentState._lastSelectedDate; do { selectedDate = addDays(selectedDate, 1); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } - } else if (event.isShiftPressed && + } else if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowLeft) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - selectedDate = _pickerStateDetails - .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; + selectedDate = + _pickerStateDetails + .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; do { selectedDate = addDays(selectedDate, -1); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else { selectedDate = currentState._lastSelectedDate; do { selectedDate = addDays(selectedDate, -1); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } - } else if (event.isShiftPressed && + } else if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowUp) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - selectedDate = _pickerStateDetails - .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; + selectedDate = + _pickerStateDetails + .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; do { selectedDate = addDays(selectedDate, -DateTime.daysPerWeek); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else { selectedDate = currentState._lastSelectedDate; do { selectedDate = addDays(selectedDate, -DateTime.daysPerWeek); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } - } else if (event.isShiftPressed && + } else if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowDown) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - selectedDate = _pickerStateDetails - .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; + selectedDate = + _pickerStateDetails + .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; do { selectedDate = addDays(selectedDate, DateTime.daysPerWeek); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } else { selectedDate = currentState._lastSelectedDate; do { selectedDate = addDays(selectedDate, DateTime.daysPerWeek); } while (DateRangePickerHelper.isDateWithInVisibleDates( - currentView.visibleDates, - widget.picker.monthViewSettings.blackoutDates, - selectedDate)); + currentView.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + )); return selectedDate; } } return null; } - dynamic _updateSelectedDate(RawKeyEvent event, _PickerViewState currentState, - _PickerView currentView) { + dynamic _updateSelectedDate( + KeyEvent event, + _PickerViewState currentState, + _PickerView currentView, + ) { switch (widget.picker.selectionMode) { case DateRangePickerSelectionMode.single: { @@ -10609,7 +11391,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerSelectionMode.extendableRange: { return _updateMultiAndRangeSelectionByKeyBoard( - event, currentState, currentView); + event, + currentState, + currentView, + ); } case DateRangePickerSelectionMode.multiRange: break; @@ -10632,8 +11417,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: { @@ -10641,27 +11427,33 @@ class _PickerScrollViewState extends State<_PickerScrollView> dragUpdateDetails.globalPosition.dx - _scrollStartPosition!; if (difference < 0 && !DateRangePickerHelper.canMoveToNextViewRtl( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + widget.picker.maxDate, + _currentViewVisibleDates, + widget.isRtl, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { return; } else if (difference > 0 && !DateRangePickerHelper.canMoveToPreviousViewRtl( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + widget.picker.maxDate, + _currentViewVisibleDates, + widget.isRtl, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { return; } @@ -10681,8 +11473,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } void _onHorizontalEnd(DragEndDetails dragEndDetails) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.vertical: break; @@ -10701,9 +11494,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _animationController.forward().then( + (dynamic value) => _updateNextView(), + ); /// updates the current view visible dates when the view swiped in /// right to left direction @@ -10712,15 +11505,18 @@ class _PickerScrollViewState extends State<_PickerScrollView> // fling the view from right to left else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { if (!DateRangePickerHelper.canMoveToNextViewRtl( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + widget.picker.maxDate, + _currentViewVisibleDates, + widget.isRtl, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { _position = 0; setState(() { /* Completes the swiping and rearrange the children position in @@ -10741,7 +11537,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _animationController.duration = const Duration(milliseconds: 250); _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updateNextView()); /// updates the current view visible dates when fling the view in @@ -10760,9 +11558,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + _animationController.forward().then( + (dynamic value) => _updatePreviousView(), + ); /// updates the current view visible dates when the view swiped in /// left to right direction @@ -10771,15 +11569,18 @@ class _PickerScrollViewState extends State<_PickerScrollView> // fling the view from left to right else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { if (!DateRangePickerHelper.canMoveToPreviousViewRtl( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - widget.picker.maxDate, - _currentViewVisibleDates, - widget.isRtl, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + widget.picker.maxDate, + _currentViewVisibleDates, + widget.isRtl, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { _position = 0; setState(() { /* Completes the swiping and rearrange the children position in @@ -10800,7 +11601,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _animationController.duration = const Duration(milliseconds: 250); _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updatePreviousView()); /// updates the current view visible dates when fling the view in @@ -10840,8 +11643,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: break; @@ -10851,23 +11655,29 @@ class _PickerScrollViewState extends State<_PickerScrollView> dragUpdateDetails.globalPosition.dy - _scrollStartPosition!; if (difference < 0 && !DateRangePickerHelper.canMoveToNextView( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.maxDate, - _currentViewVisibleDates, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.maxDate, + _currentViewVisibleDates, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { return; } else if (difference > 0 && !DateRangePickerHelper.canMoveToPreviousView( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - _currentViewVisibleDates, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + _currentViewVisibleDates, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { return; } @@ -10884,8 +11694,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } void _onVerticalEnd(DragEndDetails dragEndDetails) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: break; @@ -10904,9 +11715,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _animationController.forward().then( + (dynamic value) => _updateNextView(), + ); /// updates the current view visible dates when the view swiped in /// bottom to top direction @@ -10916,13 +11727,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> else if (-dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { if (!DateRangePickerHelper.canMoveToNextView( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.maxDate, - _currentViewVisibleDates, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.maxDate, + _currentViewVisibleDates, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { _position = 0; setState(() { /* Completes the swiping and rearrange the children position in @@ -10942,7 +11756,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _animationController.duration = const Duration(milliseconds: 250); _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updateNextView()); /// updates the current view visible dates when fling the view in @@ -10961,9 +11777,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> } _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + _animationController.forward().then( + (dynamic value) => _updatePreviousView(), + ); /// updates the current view visible dates when the view swiped in /// top to bottom direction @@ -10972,13 +11788,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> // fling the view to top to bottom else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { if (!DateRangePickerHelper.canMoveToPreviousView( - pickerView, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.minDate, - _currentViewVisibleDates, - widget.picker.enableMultiView, - widget.picker.isHijri)) { + pickerView, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + widget.picker.minDate, + _currentViewVisibleDates, + widget.picker.enableMultiView, + widget.picker.isHijri, + )) { _position = 0; setState(() { /* Completes the swiping and rearrange the children position in @@ -10999,7 +11818,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _animationController.duration = const Duration(milliseconds: 250); _animationController .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) + velocity: 5.0, + animationBehavior: AnimationBehavior.normal, + ) .then((dynamic value) => _updatePreviousView()); /// updates the current view visible dates when fling the view in @@ -11039,8 +11860,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> return; } - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); final int viewCount = _isMultiViewEnabled(widget.picker) ? 2 : 1; _disabledDates ??= , List>{}; @@ -11060,17 +11882,22 @@ class _PickerScrollViewState extends State<_PickerScrollView> for (int j = i * datesCount; j < ((i + 1) * datesCount); j++) { final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); final bool showLeadingTrailingDates = DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); final bool isCurrentMonthDate = DateRangePickerHelper.isDateAsCurrentMonthDate( - visibleDates[midDateIndex], - numberOfWeeksInView, - showLeadingTrailingDates, - visibleDates[j], - widget.picker.isHijri); + visibleDates[midDateIndex], + numberOfWeeksInView, + showLeadingTrailingDates, + visibleDates[j], + widget.picker.isHijri, + ); if (isCurrentMonthDate) { final bool isSelectedDayPredicate = widget.picker.selectableDayPredicate(visibleDates[j]) as bool; @@ -11202,31 +12029,37 @@ class _PickerViewState extends State<_PickerView> Widget build(BuildContext context) { final Locale locale = Localizations.localeOf(context); final SfLocalizations localizations = SfLocalizations.of(context); - _isMobilePlatform = - DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); + _isMobilePlatform = DateRangePickerHelper.isMobileLayout( + Theme.of(context).platform, + ); widget.getPickerStateDetails(_pickerStateDetails); - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (pickerView) { case DateRangePickerView.month: { return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: SizedBox( - width: widget.width, - height: widget.height, - child: _addMonthView( - locale, widget.datePickerTheme, localizations), - )), onTapUp: _updateTapCallback, onHorizontalDragStart: _getDragStartCallback(), onVerticalDragStart: _getDragStartCallback(), onHorizontalDragUpdate: _getDragUpdateCallback(), onVerticalDragUpdate: _getDragUpdateCallback(), + child: MouseRegion( + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: SizedBox( + width: widget.width, + height: widget.height, + child: _addMonthView( + locale, + widget.datePickerTheme, + localizations, + ), + ), + ), ); } case DateRangePickerView.year: @@ -11234,17 +12067,17 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerView.century: { return GestureDetector( + onTapUp: _updateTapCallback, + onHorizontalDragStart: _getDragStartCallback(), + onVerticalDragStart: _getDragStartCallback(), + onHorizontalDragUpdate: _getDragUpdateCallback(), + onVerticalDragUpdate: _getDragUpdateCallback(), child: MouseRegion( onEnter: _pointerEnterEvent, onHover: _pointerHoverEvent, onExit: _pointerExitEvent, child: _addYearView(locale, localizations), ), - onTapUp: _updateTapCallback, - onHorizontalDragStart: _getDragStartCallback(), - onVerticalDragStart: _getDragStartCallback(), - onHorizontalDragUpdate: _getDragUpdateCallback(), - onVerticalDragUpdate: _getDragUpdateCallback(), ); } } @@ -11272,11 +12105,13 @@ class _PickerViewState extends State<_PickerView> // Returns the month view as a child for the picker view. Widget _addMonthView( - Locale locale, - SfDateRangePickerThemeData datePickerTheme, - SfLocalizations localizations) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + Locale locale, + SfDateRangePickerThemeData datePickerTheme, + SfLocalizations localizations, + ) { + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); double viewHeaderHeight = widget.picker.monthViewSettings.viewHeaderHeight as double; if (pickerView == DateRangePickerView.month && @@ -11286,8 +12121,14 @@ class _PickerViewState extends State<_PickerView> } final double height = widget.height - viewHeaderHeight; - _monthView = _getMonthView(locale, widget.datePickerTheme, localizations, - widget.width, height, pickerView); + _monthView = _getMonthView( + locale, + widget.datePickerTheme, + localizations, + widget.width, + height, + pickerView, + ); return Stack( children: [ _getViewHeader(viewHeaderHeight, locale, datePickerTheme), @@ -11296,23 +12137,24 @@ class _PickerViewState extends State<_PickerView> top: viewHeaderHeight, right: 0, height: height, - child: RepaintBoundary( - child: _monthView, - ), + child: RepaintBoundary(child: _monthView), ), ], ); } MonthView _getMonthView( - Locale locale, - SfDateRangePickerThemeData datePickerTheme, - SfLocalizations localizations, - double width, - double height, - DateRangePickerView pickerView) { + Locale locale, + SfDateRangePickerThemeData datePickerTheme, + SfLocalizations localizations, + double width, + double height, + DateRangePickerView pickerView, + ) { final int rowCount = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); return MonthView( widget.visibleDates, rowCount, @@ -11330,7 +12172,9 @@ class _PickerViewState extends State<_PickerView> widget.picker.maxDate, widget.picker.enablePastDates, DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri), + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), widget.picker.monthViewSettings.blackoutDates, widget.picker.monthViewSettings.specialDates, widget.picker.monthViewSettings.weekendDays, @@ -11357,15 +12201,19 @@ class _PickerViewState extends State<_PickerView> ); } - Widget _getViewHeader(double viewHeaderHeight, Locale locale, - SfDateRangePickerThemeData datePickerTheme) { + Widget _getViewHeader( + double viewHeaderHeight, + Locale locale, + SfDateRangePickerThemeData datePickerTheme, + ) { if (viewHeaderHeight == 0) { return Positioned( - left: 0, - top: 0, - right: 0, - height: viewHeaderHeight, - child: Container()); + left: 0, + top: 0, + right: 0, + height: viewHeaderHeight, + child: Container(), + ); } final Color todayTextColor = @@ -11385,28 +12233,29 @@ class _PickerViewState extends State<_PickerView> child: Container( color: widget.picker.monthViewSettings.viewHeaderStyle.backgroundColor ?? - widget.datePickerTheme.viewHeaderBackgroundColor, + widget.datePickerTheme.viewHeaderBackgroundColor, child: RepaintBoundary( child: CustomPaint( painter: _PickerViewHeaderPainter( - widget.visibleDates, - widget.picker.navigationMode, - widget.picker.monthViewSettings.viewHeaderStyle, - viewHeaderHeight, - widget.picker.monthViewSettings, - widget.datePickerTheme, - locale, - widget.isRtl, - widget.picker.monthCellStyle, - widget.enableMultiView, - widget.picker.viewSpacing, - todayTextColor, - widget.textScaleFactor, - widget.picker.isHijri, - widget.picker.navigationDirection, - null, - widget.picker.monthViewSettings.showWeekNumber, - _isMobilePlatform), + widget.visibleDates, + widget.picker.navigationMode, + widget.picker.monthViewSettings.viewHeaderStyle, + viewHeaderHeight, + widget.picker.monthViewSettings, + widget.datePickerTheme, + locale, + widget.isRtl, + widget.picker.monthCellStyle, + widget.enableMultiView, + widget.picker.viewSpacing, + todayTextColor, + widget.textScaleFactor, + widget.picker.isHijri, + widget.picker.navigationDirection, + null, + widget.picker.monthViewSettings.showWeekNumber, + _isMobilePlatform, + ), ), ), ), @@ -11414,8 +12263,9 @@ class _PickerViewState extends State<_PickerView> } void _updateTapCallback(TapUpDetails details) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); switch (pickerView) { case DateRangePickerView.month: { @@ -11428,9 +12278,10 @@ class _PickerViewState extends State<_PickerView> final double weekNumberPanelWidth = DateRangePickerHelper.getWeekNumberPanelWidth( - widget.picker.monthViewSettings.showWeekNumber, - widget.width, - _isMobilePlatform); + widget.picker.monthViewSettings.showWeekNumber, + widget.width, + _isMobilePlatform, + ); if (details.localPosition.dy < viewHeaderHeight || ((!widget.isRtl && @@ -11443,9 +12294,12 @@ class _PickerViewState extends State<_PickerView> if (details.localPosition.dy > viewHeaderHeight) { _handleTouch( - Offset(details.localPosition.dx, - details.localPosition.dy - viewHeaderHeight), - details); + Offset( + details.localPosition.dx, + details.localPosition.dy - viewHeaderHeight, + ), + details, + ); } } break; @@ -11454,7 +12308,8 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerView.century: { _handleYearPanelSelection( - Offset(details.localPosition.dx, details.localPosition.dy)); + Offset(details.localPosition.dx, details.localPosition.dy), + ); } } @@ -11463,22 +12318,24 @@ class _PickerViewState extends State<_PickerView> } } - void _updateMouseHover(Offset globalPosition) { - if (_isMobilePlatform) { + void _updateMouseHover(Offset globalPosition, PointerDeviceKind kind) { + if (_isMobilePlatform || kind.name == 'touch') { return; } widget.getPickerStateDetails(_pickerStateDetails); - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); 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 - ? widget.picker.monthViewSettings.viewHeaderHeight as double - : 0; + final double viewHeaderHeight = + pickerView == DateRangePickerView.month && + widget.picker.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? widget.picker.monthViewSettings.viewHeaderHeight as double + : 0; final double xPosition = localPosition.dx; final double yPosition = localPosition.dy - viewHeaderHeight; @@ -11510,24 +12367,30 @@ class _PickerViewState extends State<_PickerView> date = widget.visibleDates[index]; if (!DateRangePickerHelper.isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - date, - widget.picker.isHijri)) { + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + date, + widget.picker.isHijri, + )) { _mouseHoverPosition.value = null; return; } final int currentMonthIndex = _getCurrentDateIndex(index); if (!DateRangePickerHelper.isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri), - date, - widget.picker.isHijri)) { + widget.visibleDates[currentMonthIndex], + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + date, + widget.picker.isHijri, + )) { _mouseHoverPosition.value = null; return; } @@ -11538,8 +12401,10 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerView.century: { if (widget.picker.allowViewNavigation) { - _mouseHoverPosition.value = - HoveringDetails(range, Offset(xPosition, yPosition)); + _mouseHoverPosition.value = HoveringDetails( + range, + Offset(xPosition, yPosition), + ); return; } @@ -11551,12 +12416,13 @@ class _PickerViewState extends State<_PickerView> date = widget.visibleDates[index]; if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - date, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { + date, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + )) { _mouseHoverPosition.value = null; return; } @@ -11567,11 +12433,12 @@ class _PickerViewState extends State<_PickerView> /// selection value is disabled based on extendable direction on /// mouse hovering. if (DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - date, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + date, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { _mouseHoverPosition.value = null; return; } @@ -11592,21 +12459,24 @@ class _PickerViewState extends State<_PickerView> rangeStartDate = date; } - range = widget.picker.isHijri - ? HijriDateRange(rangeStartDate, rangeEndDate) - : PickerDateRange(rangeStartDate, rangeEndDate); + range = + widget.picker.isHijri + ? HijriDateRange(rangeStartDate, rangeEndDate) + : PickerDateRange(rangeStartDate, rangeEndDate); } - _mouseHoverPosition.value = - HoveringDetails(range, Offset(xPosition, yPosition)); + _mouseHoverPosition.value = HoveringDetails( + range, + Offset(xPosition, yPosition), + ); } void _pointerEnterEvent(PointerEnterEvent event) { - _updateMouseHover(event.position); + _updateMouseHover(event.position, event.kind); } void _pointerHoverEvent(PointerHoverEvent event) { - _updateMouseHover(event.position); + _updateMouseHover(event.position, event.kind); } void _pointerExitEvent(PointerExitEvent event) { @@ -11615,59 +12485,60 @@ class _PickerViewState extends State<_PickerView> Widget _addYearView(Locale locale, SfLocalizations localizations) { _yearView = _getYearView(locale, localizations); - return RepaintBoundary( - child: _yearView, - ); + return RepaintBoundary(child: _yearView); } YearView _getYearView(Locale locale, SfLocalizations localizations) { return YearView( - widget.visibleDates, - widget.picker.yearCellStyle, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.picker.todayHighlightColor, - widget.picker.selectionShape, - widget.picker.monthFormat, - widget.isRtl, - widget.datePickerTheme, - locale, - _mouseHoverPosition, - widget.enableMultiView, - widget.picker.viewSpacing, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - widget.picker.selectionMode, - widget.picker.selectionRadius, - ValueNotifier(false), - widget.textScaleFactor, - widget.picker.allowViewNavigation, - widget.picker.cellBuilder, - widget.getPickerStateDetails, - DateRangePickerHelper.getPickerView(widget.controller.view), - widget.picker.isHijri, - localizations, - widget.picker.navigationDirection, - widget.width, - widget.height, - widget.disableDatePredicates, - widget.picker.extendableRangeSelectionDirection); + widget.visibleDates, + widget.picker.yearCellStyle, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.picker.todayHighlightColor, + widget.picker.selectionShape, + widget.picker.monthFormat, + widget.isRtl, + widget.datePickerTheme, + locale, + _mouseHoverPosition, + widget.enableMultiView, + widget.picker.viewSpacing, + widget.picker.selectionTextStyle, + widget.picker.rangeTextStyle, + widget.picker.selectionColor, + widget.picker.startRangeSelectionColor, + widget.picker.endRangeSelectionColor, + widget.picker.rangeSelectionColor, + widget.picker.selectionMode, + widget.picker.selectionRadius, + ValueNotifier(false), + widget.textScaleFactor, + widget.picker.allowViewNavigation, + widget.picker.cellBuilder, + widget.getPickerStateDetails, + DateRangePickerHelper.getPickerView(widget.controller.view), + widget.picker.isHijri, + localizations, + widget.picker.navigationDirection, + widget.width, + widget.height, + widget.disableDatePredicates, + widget.picker.extendableRangeSelectionDirection, + ); } GestureDragStartCallback? _getDragStartCallback() { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + 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) || !_isSwipeInteractionEnabled( - widget.picker.monthViewSettings.enableSwipeSelection, - widget.picker.navigationMode)) { + widget.picker.monthViewSettings.enableSwipeSelection, + widget.picker.navigationMode, + )) { return null; } @@ -11692,14 +12563,16 @@ class _PickerViewState extends State<_PickerView> } GestureDragUpdateCallback? _getDragUpdateCallback() { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + 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) || !_isSwipeInteractionEnabled( - widget.picker.monthViewSettings.enableSwipeSelection, - widget.picker.navigationMode)) { + widget.picker.monthViewSettings.enableSwipeSelection, + widget.picker.navigationMode, + )) { return null; } @@ -11794,15 +12667,17 @@ class _PickerViewState extends State<_PickerView> } const int totalDatesCount = YearView.maxRowCount * YearView.maxColumnCount; - index = (columnIndex * YearView.maxColumnCount) + + index = + (columnIndex * YearView.maxColumnCount) + ((rowIndex ~/ YearView.maxColumnCount) * totalDatesCount) + (rowIndex % YearView.maxColumnCount); return widget.enableMultiView && DateRangePickerHelper.isLeadingCellDate( - index, - (index ~/ totalDatesCount) * totalDatesCount, - widget.visibleDates, - widget.controller.view) + index, + (index ~/ totalDatesCount) * totalDatesCount, + widget.visibleDates, + widget.controller.view, + ) ? -1 : index; } @@ -11810,30 +12685,38 @@ class _PickerViewState extends State<_PickerView> int _getSelectedIndex(double xPosition, double yPosition) { final double weekNumberPanelWidth = DateRangePickerHelper.getWeekNumberPanelWidth( - widget.picker.monthViewSettings.showWeekNumber, - widget.width, - _isMobilePlatform); + widget.picker.monthViewSettings.showWeekNumber, + widget.width, + _isMobilePlatform, + ); int rowIndex, columnIndex; double width = widget.width - weekNumberPanelWidth; double height = widget.height; int index = -1; int totalColumnCount = DateTime.daysPerWeek; final int rowCount = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri); + widget.picker.monthViewSettings, + widget.picker.isHijri, + ); int totalRowCount = rowCount; if (widget.enableMultiView) { switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: { width = width - widget.picker.viewSpacing - weekNumberPanelWidth; + final double singleViewWidth = width / 2; totalColumnCount *= 2; - if (xPosition > width / 2 && + + /// return -1 while the position in between the view spacing. + if (xPosition > weekNumberPanelWidth + singleViewWidth && xPosition < - (width / 2) + + singleViewWidth + widget.picker.viewSpacing + - weekNumberPanelWidth) { + (2 * weekNumberPanelWidth)) { return index; - } else if (xPosition > width / 2) { + } else if (xPosition > singleViewWidth + weekNumberPanelWidth) { + /// Subtract the 2nd view week number panel width and in between + /// spacing while the position is after the 1st view. xPosition = xPosition - widget.picker.viewSpacing - weekNumberPanelWidth; } @@ -11843,6 +12726,8 @@ class _PickerViewState extends State<_PickerView> { height = (height - widget.picker.viewSpacing) / 2; totalRowCount *= 2; + + /// return -1 while the position in between the view spacing. if (yPosition > height && yPosition < height + widget.picker.viewSpacing) { return index; @@ -11865,8 +12750,9 @@ class _PickerViewState extends State<_PickerView> xPosition -= weekNumberPanelWidth; } - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); double viewHeaderHeight = widget.picker.monthViewSettings.viewHeaderHeight as double; @@ -11905,7 +12791,8 @@ class _PickerViewState extends State<_PickerView> } } - index = (columnIndex * DateTime.daysPerWeek) + + index = + (columnIndex * DateTime.daysPerWeek) + ((rowIndex ~/ DateTime.daysPerWeek) * (totalRowCount * DateTime.daysPerWeek)) + (rowIndex % DateTime.daysPerWeek); @@ -11917,13 +12804,15 @@ class _PickerViewState extends State<_PickerView> _isDragStart = false; widget.getPickerStateDetails(_pickerStateDetails); final double xPosition = details.localPosition.dx; - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); double yPosition = details.localPosition.dy; if (pickerView == DateRangePickerView.month && widget.picker.navigationDirection == DateRangePickerNavigationDirection.horizontal) { - yPosition = details.localPosition.dy - + yPosition = + details.localPosition.dy - widget.picker.monthViewSettings.viewHeaderHeight; } @@ -11934,30 +12823,42 @@ class _PickerViewState extends State<_PickerView> final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - selectedDate, - widget.picker.isHijri)) { + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri, + )) { return; } final int currentMonthIndex = _getCurrentDateIndex(index); if (!DateRangePickerHelper.isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri), - selectedDate, - widget.picker.isHijri)) { + widget.visibleDates[currentMonthIndex], + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + selectedDate, + widget.picker.isHijri, + )) { return; } - if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate) || + if (DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - widget.visibleDates, widget.disableDatePredicates, selectedDate)) { + widget.visibleDates, + widget.disableDatePredicates, + selectedDate, + )) { return; } @@ -11968,11 +12869,12 @@ class _PickerViewState extends State<_PickerView> DateRangePickerSelectionMode.extendableRange && _pickerStateDetails.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return; } @@ -11991,12 +12893,14 @@ class _PickerViewState extends State<_PickerView> widget.getPickerStateDetails(_pickerStateDetails); final double xPosition = details.localPosition.dx; double yPosition = details.localPosition.dy; - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView == DateRangePickerView.month && widget.picker.navigationDirection == DateRangePickerNavigationDirection.horizontal) { - yPosition = details.localPosition.dy - + yPosition = + details.localPosition.dy - widget.picker.monthViewSettings.viewHeaderHeight; } @@ -12007,30 +12911,42 @@ class _PickerViewState extends State<_PickerView> final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - selectedDate, - widget.picker.isHijri)) { + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri, + )) { return; } final int currentMonthIndex = _getCurrentDateIndex(index); if (!DateRangePickerHelper.isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri), - selectedDate, - widget.picker.isHijri)) { + widget.visibleDates[currentMonthIndex], + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + selectedDate, + widget.picker.isHijri, + )) { return; } - if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate) || + if (DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - widget.visibleDates, widget.disableDatePredicates, selectedDate)) { + widget.visibleDates, + widget.disableDatePredicates, + selectedDate, + )) { return; } @@ -12041,11 +12957,12 @@ class _PickerViewState extends State<_PickerView> DateRangePickerSelectionMode.extendableRange && _pickerStateDetails.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return; } @@ -12067,21 +12984,27 @@ class _PickerViewState extends State<_PickerView> break; case DateRangePickerSelectionMode.range: { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } break; case DateRangePickerSelectionMode.multiRange: { _pickerStateDetails.selectedRanges ??= []; - _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _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], + ); } break; case DateRangePickerSelectionMode.extendableRange: @@ -12098,24 +13021,30 @@ class _PickerViewState extends State<_PickerView> { //// Check the start date of the range updated or not, if not updated then create the new range. if (!_isDragStart) { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } else { if (_pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null) { final dynamic updatedRange = _getSelectedRangeOnDragUpdate( - _pickerStateDetails.selectedRange, selectedDate); + _pickerStateDetails.selectedRange, + selectedDate, + ); if (DateRangePickerHelper.isRangeEquals( - _pickerStateDetails.selectedRange, updatedRange)) { + _pickerStateDetails.selectedRange, + updatedRange, + )) { return; } _pickerStateDetails.selectedRange = updatedRange; } else { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } } } @@ -12124,37 +13053,48 @@ class _PickerViewState extends State<_PickerView> { _pickerStateDetails.selectedRanges ??= []; final int count = _pickerStateDetails.selectedRanges!.length; - dynamic _lastRange; + 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 - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _pickerStateDetails.selectedRanges!.add( + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null), + ); } else { - if (_lastRange != null && _lastRange.startDate != null) { - final dynamic updatedRange = - _getSelectedRangeOnDragUpdate(_lastRange, selectedDate); + if (lastRange != null && lastRange.startDate != null) { + final dynamic updatedRange = _getSelectedRangeOnDragUpdate( + lastRange, + selectedDate, + ); if (DateRangePickerHelper.isRangeEquals( - _lastRange, updatedRange)) { + lastRange, + updatedRange, + )) { return; } _pickerStateDetails.selectedRanges![count - 1] = updatedRange; } else { - _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _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], + ); } break; case DateRangePickerSelectionMode.extendableRange: @@ -12164,7 +13104,9 @@ class _PickerViewState extends State<_PickerView> /// Return the range that start date is before of end date in month view. dynamic _getSelectedRangeOnDragUpdate( - dynamic previousRange, dynamic selectedDate) { + dynamic previousRange, + dynamic selectedDate, + ) { final dynamic previousRangeStartDate = previousRange.startDate; final dynamic previousRangeEndDate = previousRange.endDate ?? previousRange.startDate; @@ -12197,14 +13139,19 @@ class _PickerViewState extends State<_PickerView> /// Return the range that start date is before of end date in year view. dynamic _getSelectedRangeOnDragUpdateYear( - dynamic previousRange, dynamic selectedDate) { + dynamic previousRange, + dynamic selectedDate, + ) { final dynamic previousRangeStartDate = previousRange.startDate; final dynamic previousRangeEndDate = previousRange.endDate ?? previousRange.startDate; dynamic rangeStartDate = previousRangeStartDate; dynamic rangeEndDate = selectedDate; - if (DateRangePickerHelper.isSameCellDates(previousRangeStartDate, - _previousSelectedDate, widget.controller.view)) { + if (DateRangePickerHelper.isSameCellDates( + previousRangeStartDate, + _previousSelectedDate, + widget.controller.view, + )) { if (_isSameOrBeforeDateCell(previousRangeEndDate, rangeEndDate)) { rangeStartDate = selectedDate; rangeEndDate = previousRangeEndDate; @@ -12213,7 +13160,10 @@ class _PickerViewState extends State<_PickerView> rangeEndDate = selectedDate; } } else if (DateRangePickerHelper.isSameCellDates( - previousRangeEndDate, _previousSelectedDate, widget.controller.view)) { + previousRangeEndDate, + _previousSelectedDate, + widget.controller.view, + )) { if (_isSameOrAfterDateCell(previousRangeStartDate, rangeEndDate)) { rangeStartDate = previousRangeStartDate; rangeEndDate = selectedDate; @@ -12224,21 +13174,30 @@ class _PickerViewState extends State<_PickerView> } rangeEndDate = DateRangePickerHelper.getLastDate( - rangeEndDate, widget.controller.view, widget.picker.isHijri); + rangeEndDate, + widget.controller.view, + widget.picker.isHijri, + ); if (widget.picker.maxDate != null) { - rangeEndDate = rangeEndDate.isAfter(widget.picker.maxDate) == true - ? widget.picker.maxDate - : rangeEndDate; + rangeEndDate = + rangeEndDate.isAfter(widget.picker.maxDate) == true + ? widget.picker.maxDate + : rangeEndDate; } - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); rangeStartDate = DateRangePickerHelper.getFirstDate( - rangeStartDate, widget.picker.isHijri, pickerView); + rangeStartDate, + widget.picker.isHijri, + pickerView, + ); if (widget.picker.minDate != null) { - rangeStartDate = rangeStartDate.isBefore(widget.picker.minDate) == true - ? widget.picker.minDate - : rangeStartDate; + rangeStartDate = + rangeStartDate.isBefore(widget.picker.minDate) == true + ? widget.picker.minDate + : rangeStartDate; } if (widget.picker.isHijri) { @@ -12254,8 +13213,9 @@ class _PickerViewState extends State<_PickerView> /// consider the month value(max month as 12). /// Note: This method not applicable for month view. bool _isSameOrBeforeDateCell(dynamic currentMaxDate, dynamic currentDate) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView == DateRangePickerView.year) { return (currentDate.month <= currentMaxDate.month == true && currentDate.year == currentMaxDate.year) || @@ -12275,8 +13235,9 @@ class _PickerViewState extends State<_PickerView> /// consider the month value(min month as 12). /// Note: This method not applicable for month view. bool _isSameOrAfterDateCell(dynamic currentMinDate, dynamic currentDate) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView == DateRangePickerView.year) { return (currentDate.month >= currentMinDate.month == true && currentDate.year == currentMinDate.year) || @@ -12299,24 +13260,30 @@ class _PickerViewState extends State<_PickerView> { //// Check the start date of the range updated or not, if not updated then create the new range. if (!_isDragStart) { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } else { if (_pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null) { final dynamic updatedRange = _getSelectedRangeOnDragUpdateYear( - _pickerStateDetails.selectedRange, selectedDate); + _pickerStateDetails.selectedRange, + selectedDate, + ); if (DateRangePickerHelper.isRangeEquals( - _pickerStateDetails.selectedRange, updatedRange)) { + _pickerStateDetails.selectedRange, + updatedRange, + )) { return; } _pickerStateDetails.selectedRange = updatedRange; } else { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } } } @@ -12325,37 +13292,48 @@ class _PickerViewState extends State<_PickerView> { _pickerStateDetails.selectedRanges ??= []; final int count = _pickerStateDetails.selectedRanges!.length; - dynamic _lastRange; + 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 - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _pickerStateDetails.selectedRanges!.add( + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null), + ); } else { - if (_lastRange != null && _lastRange.startDate != null) { - final dynamic updatedRange = - _getSelectedRangeOnDragUpdateYear(_lastRange, selectedDate); + if (lastRange != null && lastRange.startDate != null) { + final dynamic updatedRange = _getSelectedRangeOnDragUpdateYear( + lastRange, + selectedDate, + ); if (DateRangePickerHelper.isRangeEquals( - _lastRange, updatedRange)) { + lastRange, + updatedRange, + )) { return; } _pickerStateDetails.selectedRanges![count - 1] = updatedRange; } else { - _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _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], + ); } break; case DateRangePickerSelectionMode.extendableRange: @@ -12367,22 +13345,28 @@ class _PickerViewState extends State<_PickerView> //// Set drag start value as false, identifies the start date of the range not updated. _isDragStart = false; widget.getPickerStateDetails(_pickerStateDetails); - final int index = - _getYearViewIndex(details.localPosition.dx, details.localPosition.dy); + final int index = _getYearViewIndex( + details.localPosition.dx, + details.localPosition.dy, + ); if (index == -1) { return; } final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri) || + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - widget.visibleDates, widget.disableDatePredicates, selectedDate)) { + widget.visibleDates, + widget.disableDatePredicates, + selectedDate, + )) { return; } @@ -12395,11 +13379,12 @@ class _PickerViewState extends State<_PickerView> final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); if (DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return; } } @@ -12415,22 +13400,28 @@ class _PickerViewState extends State<_PickerView> void _dragUpdateOnYear(DragUpdateDetails details) { widget.getPickerStateDetails(_pickerStateDetails); - final int index = - _getYearViewIndex(details.localPosition.dx, details.localPosition.dy); + final int index = _getYearViewIndex( + details.localPosition.dx, + details.localPosition.dy, + ); if (index == -1) { return; } final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri) || + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - widget.visibleDates, widget.disableDatePredicates, selectedDate)) { + widget.visibleDates, + widget.disableDatePredicates, + selectedDate, + )) { return; } @@ -12443,11 +13434,12 @@ class _PickerViewState extends State<_PickerView> final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); if (DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return; } } @@ -12463,8 +13455,9 @@ class _PickerViewState extends State<_PickerView> void _handleTouch(Offset details, TapUpDetails tapUpDetails) { widget.getPickerStateDetails(_pickerStateDetails); - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (pickerView == DateRangePickerView.month) { final int index = _getSelectedIndex(details.dx, details.dy); if (index == -1) { @@ -12473,30 +13466,42 @@ class _PickerViewState extends State<_PickerView> final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isEnabledDate( - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - selectedDate, - widget.picker.isHijri)) { + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + selectedDate, + widget.picker.isHijri, + )) { return; } final int currentMonthIndex = _getCurrentDateIndex(index); if (!DateRangePickerHelper.isDateAsCurrentMonthDate( - widget.visibleDates[currentMonthIndex], - DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri), - DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri), - selectedDate, - widget.picker.isHijri)) { + widget.visibleDates[currentMonthIndex], + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ), + selectedDate, + widget.picker.isHijri, + )) { return; } - if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate) || - DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.disableDatePredicates, selectedDate)) { + if (DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, + widget.picker.monthViewSettings.blackoutDates, + selectedDate, + ) || + DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, + widget.disableDatePredicates, + selectedDate, + )) { return; } @@ -12507,11 +13512,12 @@ class _PickerViewState extends State<_PickerView> DateRangePickerSelectionMode.extendableRange && _pickerStateDetails.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - selectedDate, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + selectedDate, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return; } @@ -12523,8 +13529,11 @@ class _PickerViewState extends State<_PickerView> } int _getCurrentDateIndex(int index) { - final int datesCount = DateRangePickerHelper.getNumberOfWeeksInView( - widget.picker.monthViewSettings, widget.picker.isHijri) * + final int datesCount = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, + widget.picker.isHijri, + ) * DateTime.daysPerWeek; int currentMonthIndex = datesCount ~/ 2; if (widget.enableMultiView && index >= datesCount) { @@ -12536,8 +13545,11 @@ class _PickerViewState extends State<_PickerView> void _drawSingleSelectionForYear(dynamic selectedDate) { if (widget.picker.toggleDaySelection && - DateRangePickerHelper.isSameCellDates(selectedDate, - _pickerStateDetails.selectedDate, widget.controller.view)) { + DateRangePickerHelper.isSameCellDates( + selectedDate, + _pickerStateDetails.selectedDate, + widget.controller.view, + )) { selectedDate = null; } @@ -12549,9 +13561,10 @@ class _PickerViewState extends State<_PickerView> if (_pickerStateDetails.selectedDates != null && _pickerStateDetails.selectedDates!.isNotEmpty) { selectedIndex = DateRangePickerHelper.getDateCellIndex( - _pickerStateDetails.selectedDates!, - selectedDate, - widget.controller.view); + _pickerStateDetails.selectedDates!, + selectedDate, + widget.controller.view, + ); } if (selectedIndex == -1) { @@ -12567,9 +13580,10 @@ class _PickerViewState extends State<_PickerView> _pickerStateDetails.selectedRange.startDate != null && (_pickerStateDetails.selectedRange.endDate == null || DateRangePickerHelper.isSameCellDates( - _pickerStateDetails.selectedRange.startDate, - _pickerStateDetails.selectedRange.endDate, - widget.controller.view))) { + _pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate, + widget.controller.view, + ))) { dynamic startDate = _pickerStateDetails.selectedRange.startDate; dynamic endDate = selectedDate; if (startDate.isAfter(endDate) == true) { @@ -12579,26 +13593,33 @@ class _PickerViewState extends State<_PickerView> } endDate = DateRangePickerHelper.getLastDate( - endDate, widget.controller.view, widget.picker.isHijri); + endDate, + widget.controller.view, + widget.picker.isHijri, + ); if (widget.picker.maxDate != null) { - endDate = endDate.isAfter(widget.picker.maxDate) == true - ? widget.picker.maxDate - : endDate; + endDate = + endDate.isAfter(widget.picker.maxDate) == true + ? widget.picker.maxDate + : endDate; } if (widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) == true - ? widget.picker.minDate - : startDate; + startDate = + startDate.isBefore(widget.picker.minDate) == true + ? widget.picker.minDate + : startDate; } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); } else { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } _lastSelectedDate = selectedDate; @@ -12607,17 +13628,20 @@ class _PickerViewState extends State<_PickerView> void _drawRangesSelectionForYear(dynamic selectedDate) { _pickerStateDetails.selectedRanges ??= []; int count = _pickerStateDetails.selectedRanges!.length; - dynamic _lastRange; + dynamic lastRange; if (count > 0) { - _lastRange = _pickerStateDetails.selectedRanges![count - 1]; + lastRange = _pickerStateDetails.selectedRanges![count - 1]; } - if (_lastRange != null && - _lastRange.startDate != null && - (_lastRange.endDate == null || - DateRangePickerHelper.isSameCellDates(_lastRange.startDate, - _lastRange.endDate, widget.controller.view))) { - dynamic startDate = _lastRange.startDate; + if (lastRange != null && + lastRange.startDate != null && + (lastRange.endDate == null || + DateRangePickerHelper.isSameCellDates( + lastRange.startDate, + lastRange.endDate, + widget.controller.view, + ))) { + dynamic startDate = lastRange.startDate; dynamic endDate = selectedDate; if (startDate.isAfter(endDate) == true) { final dynamic temp = startDate; @@ -12626,40 +13650,53 @@ class _PickerViewState extends State<_PickerView> } endDate = DateRangePickerHelper.getLastDate( - endDate, widget.controller.view, widget.picker.isHijri); + endDate, + widget.controller.view, + widget.picker.isHijri, + ); if (widget.picker.maxDate != null) { - endDate = endDate.isAfter(widget.picker.maxDate) == true - ? widget.picker.maxDate - : endDate; + endDate = + endDate.isAfter(widget.picker.maxDate) == true + ? widget.picker.maxDate + : endDate; } if (widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) == true - ? widget.picker.minDate - : startDate; + startDate = + startDate.isBefore(widget.picker.minDate) == true + ? widget.picker.minDate + : startDate; } - final dynamic newRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + final dynamic newRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); _pickerStateDetails.selectedRanges![count - 1] = newRange; } else { - _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _pickerStateDetails.selectedRanges!.add( + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null), + ); } count = _pickerStateDetails.selectedRanges!.length; _removeInterceptRanges( - _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges, + _pickerStateDetails + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1], + ); + lastRange = _pickerStateDetails - .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]); - _lastRange = _pickerStateDetails - .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]; + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]; if (count != _pickerStateDetails.selectedRanges!.length && - (_lastRange.endDate == null || - DateRangePickerHelper.isSameCellDates(_lastRange.endDate, - _lastRange.startDate, widget.controller.view))) { + (lastRange.endDate == null || + DateRangePickerHelper.isSameCellDates( + lastRange.endDate, + lastRange.startDate, + widget.controller.view, + ))) { _pickerStateDetails.selectedRanges!.removeLast(); } } @@ -12684,26 +13721,31 @@ class _PickerViewState extends State<_PickerView> } void _handleYearPanelSelection(Offset details) { - final int _selectedIndex = _getYearViewIndex(details.dx, details.dy); + final int selectedIndex = _getYearViewIndex(details.dx, details.dy); final int viewCount = widget.enableMultiView ? 2 : 1; - if (_selectedIndex == -1 || _selectedIndex >= 12 * viewCount) { + if (selectedIndex == -1 || selectedIndex >= 12 * viewCount) { return; } - final dynamic date = widget.visibleDates[_selectedIndex]; + final dynamic date = widget.visibleDates[selectedIndex]; widget.getPickerStateDetails(_pickerStateDetails); - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (!widget.picker.allowViewNavigation) { if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - date, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri) || + date, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - widget.visibleDates, widget.disableDatePredicates, date)) { + widget.visibleDates, + widget.disableDatePredicates, + date, + )) { return; } @@ -12714,11 +13756,12 @@ class _PickerViewState extends State<_PickerView> DateRangePickerSelectionMode.extendableRange && _pickerStateDetails.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - _pickerStateDetails.selectedRange, - date, - widget.picker.extendableRangeSelectionDirection, - pickerView, - widget.picker.isHijri)) { + _pickerStateDetails.selectedRange, + date, + widget.picker.extendableRangeSelectionDirection, + pickerView, + widget.picker.isHijri, + )) { return; } @@ -12788,7 +13831,9 @@ 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); @@ -12800,20 +13845,23 @@ class _PickerViewState extends State<_PickerView> /// Draws the extendable selection, by extending the already available range /// selection. void _drawExtendableRangeSelection(dynamic selectedDate) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); if (_pickerStateDetails.selectedRange == null) { dynamic startDate = selectedDate; if (pickerView != DateRangePickerView.month && widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) == true - ? widget.picker.minDate - : startDate; + startDate = + startDate.isBefore(widget.picker.minDate) == true + ? widget.picker.minDate + : startDate; } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, null) - : PickerDateRange(startDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, null) + : PickerDateRange(startDate, null); _lastSelectedDate = selectedDate; return; } @@ -12857,25 +13905,34 @@ class _PickerViewState extends State<_PickerView> if (pickerView != DateRangePickerView.month) { endDate = DateRangePickerHelper.getLastDate( - endDate, widget.controller.view, widget.picker.isHijri); + endDate, + widget.controller.view, + widget.picker.isHijri, + ); if (widget.picker.maxDate != null) { - endDate = endDate.isAfter(widget.picker.maxDate) == true - ? widget.picker.maxDate - : endDate; + endDate = + endDate.isAfter(widget.picker.maxDate) == true + ? widget.picker.maxDate + : endDate; } startDate = DateRangePickerHelper.getFirstDate( - startDate, widget.picker.isHijri, pickerView); + startDate, + widget.picker.isHijri, + pickerView, + ); if (widget.picker.minDate != null) { - startDate = startDate.isBefore(widget.picker.minDate) == true - ? widget.picker.minDate - : startDate; + startDate = + startDate.isBefore(widget.picker.minDate) == true + ? widget.picker.minDate + : startDate; } } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); _lastSelectedDate = selectedDate; _mouseHoverPosition.value = HoveringDetails(null, null); @@ -12885,8 +13942,10 @@ class _PickerViewState extends State<_PickerView> if (_pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null && (_pickerStateDetails.selectedRange.endDate == null || - isSameDate(_pickerStateDetails.selectedRange.startDate, - _pickerStateDetails.selectedRange.endDate))) { + isSameDate( + _pickerStateDetails.selectedRange.startDate, + _pickerStateDetails.selectedRange.endDate, + ))) { dynamic startDate = _pickerStateDetails.selectedRange.startDate; dynamic endDate = selectedDate; if (startDate.isAfter(endDate) == true) { @@ -12895,13 +13954,15 @@ class _PickerViewState extends State<_PickerView> endDate = temp; } - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); } else { - _pickerStateDetails.selectedRange = widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } _lastSelectedDate = selectedDate; @@ -12927,23 +13988,28 @@ class _PickerViewState extends State<_PickerView> endDate = temp; } - final dynamic _newRange = widget.picker.isHijri - ? HijriDateRange(startDate, endDate) - : PickerDateRange(startDate, endDate); - _pickerStateDetails.selectedRanges![count - 1] = _newRange; + final dynamic newRange = + widget.picker.isHijri + ? HijriDateRange(startDate, endDate) + : PickerDateRange(startDate, endDate); + _pickerStateDetails.selectedRanges![count - 1] = newRange; } else { - _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri - ? HijriDateRange(selectedDate, null) - : PickerDateRange(selectedDate, null)); + _pickerStateDetails.selectedRanges!.add( + widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null), + ); } count = _pickerStateDetails.selectedRanges!.length; _removeInterceptRanges( - _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges, + _pickerStateDetails + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1], + ); + lastRange = _pickerStateDetails - .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]); - lastRange = _pickerStateDetails - .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]; + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]; if (count != _pickerStateDetails.selectedRanges!.length && (lastRange.endDate == null || isSameDate(lastRange.endDate, lastRange.startDate))) { @@ -12951,8 +14017,13 @@ class _PickerViewState extends State<_PickerView> } } - int? _removeInterceptRangesForMonth(dynamic range, dynamic startDate, - dynamic endDate, int i, dynamic selectedRangeValue) { + int? _removeInterceptRangesForMonth( + dynamic range, + dynamic startDate, + dynamic endDate, + int i, + dynamic selectedRangeValue, + ) { if (range != null && !DateRangePickerHelper.isRangeEquals(range, selectedRangeValue) && ((range.startDate != null && @@ -12967,18 +14038,30 @@ class _PickerViewState extends State<_PickerView> range.endDate != null && ((startDate != null && isDateWithInDateRange( - range.startDate, range.endDate, startDate)) || + range.startDate, + range.endDate, + startDate, + )) || (endDate != null && isDateWithInDateRange( - range.startDate, range.endDate, endDate)))) || + range.startDate, + range.endDate, + endDate, + )))) || (startDate != null && endDate != null && ((range.startDate != null && isDateWithInDateRange( - startDate, endDate, range.startDate)) || + startDate, + endDate, + range.startDate, + )) || (range.endDate != null && isDateWithInDateRange( - startDate, endDate, range.endDate)))) || + startDate, + endDate, + range.endDate, + )))) || (range.startDate != null && range.endDate != null && startDate != null && @@ -12993,8 +14076,13 @@ class _PickerViewState extends State<_PickerView> return null; } - int? _removeInterceptRangesForYear(dynamic range, dynamic startDate, - dynamic endDate, int i, dynamic selectedRangeValue) { + int? _removeInterceptRangesForYear( + dynamic range, + dynamic startDate, + dynamic endDate, + int i, + dynamic selectedRangeValue, + ) { if (range == null || DateRangePickerHelper.isRangeEquals(range, selectedRangeValue)) { return null; @@ -13004,10 +14092,16 @@ class _PickerViewState extends State<_PickerView> if (range.startDate != null && ((startDate != null && DateRangePickerHelper.isSameCellDates( - range.startDate, startDate, widget.controller.view)) || + range.startDate, + startDate, + widget.controller.view, + )) || (endDate != null && DateRangePickerHelper.isSameCellDates( - range.startDate, endDate, widget.controller.view)))) { + range.startDate, + endDate, + widget.controller.view, + )))) { return i; } @@ -13015,10 +14109,16 @@ class _PickerViewState extends State<_PickerView> if (range.endDate != null && ((startDate != null && DateRangePickerHelper.isSameCellDates( - range.endDate, startDate, widget.controller.view)) || + range.endDate, + startDate, + widget.controller.view, + )) || (endDate != null && DateRangePickerHelper.isSameCellDates( - range.endDate, endDate, widget.controller.view)))) { + range.endDate, + endDate, + widget.controller.view, + )))) { return i; } @@ -13027,10 +14127,16 @@ class _PickerViewState extends State<_PickerView> range.endDate != null && ((startDate != null && _isDateWithInYearRange( - range.startDate, range.endDate, startDate)) || + range.startDate, + range.endDate, + startDate, + )) || (endDate != null && _isDateWithInYearRange( - range.startDate, range.endDate, endDate)))) { + range.startDate, + range.endDate, + endDate, + )))) { return i; } @@ -13063,13 +14169,17 @@ class _PickerViewState extends State<_PickerView> /// Check whether the date is in between the start and end date value. bool _isDateWithInYearRange( - dynamic startDate, dynamic endDate, dynamic date) { + dynamic startDate, + dynamic endDate, + dynamic date, + ) { if (startDate == null || endDate == null || date == null) { return false; } - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + widget.controller.view, + ); /// Check the start date as before of end date, if not then swap /// the start and end date values. @@ -13092,7 +14202,9 @@ class _PickerViewState extends State<_PickerView> } void _removeInterceptRanges( - List? selectedRanges, dynamic selectedRangeValue) { + List? selectedRanges, + dynamic selectedRangeValue, + ) { if (selectedRanges == null || selectedRanges.isEmpty || selectedRangeValue == null) { @@ -13124,7 +14236,12 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerView.month: { index = _removeInterceptRangesForMonth( - range, startDate, endDate, i, selectedRangeValue); + range, + startDate, + endDate, + i, + selectedRangeValue, + ); } break; case DateRangePickerView.year: @@ -13132,7 +14249,12 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerView.century: { index = _removeInterceptRangesForYear( - range, startDate, endDate, i, selectedRangeValue); + range, + startDate, + endDate, + i, + selectedRangeValue, + ); } } if (index != null) { @@ -13148,19 +14270,20 @@ 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) { + 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 = @@ -13181,13 +14304,14 @@ String _getMonthHeaderText( return '$startText - $endText'; } else { - final String monthTextFormat = monthFormat == null || monthFormat.isEmpty - ? enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.vertical - ? 'MMM' - : 'MMMM' - : monthFormat; + final String monthTextFormat = + monthFormat == null || monthFormat.isEmpty + ? enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical + ? 'MMM' + : 'MMMM' + : monthFormat; String text; dynamic middleDate = dates[middleIndex]; if (isHijri) { @@ -13228,17 +14352,18 @@ String _getMonthHeaderText( } String _getHeaderText( - List dates, - DateRangePickerView view, - int index, - bool isHijri, - int numberOfWeeksInView, - String? monthFormat, - bool enableMultiView, - DateRangePickerHeaderStyle headerStyle, - DateRangePickerNavigationDirection navigationDirection, - Locale locale, - SfLocalizations localizations) { + 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; @@ -13248,19 +14373,20 @@ String _getHeaderText( case DateRangePickerView.month: { return _getMonthHeaderText( - startIndex, - endIndex, - dates, - middleIndex, - datesCount, - isHijri, - numberOfWeeksInView, - monthFormat, - enableMultiView, - headerStyle, - navigationDirection, - locale, - localizations); + startIndex, + endIndex, + dates, + middleIndex, + datesCount, + isHijri, + numberOfWeeksInView, + monthFormat, + enableMultiView, + headerStyle, + navigationDirection, + locale, + localizations, + ); } case DateRangePickerView.year: { @@ -13304,10 +14430,14 @@ String _getHeaderText( } Size _getTextWidgetWidth( - String text, double height, double width, BuildContext context, - {required TextStyle style, - double widthPadding = 10, - double heightPadding = 10}) { + 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, @@ -13322,18 +14452,22 @@ Size _getTextWidgetWidth( /// Create and layout the render object based on /// allocated width and height. - final RenderParagraph renderObject = - richTextWidget!.createRenderObject(context); - renderObject.layout(BoxConstraints( - minWidth: width, - maxWidth: width, - minHeight: height, - maxHeight: height, - )); + final RenderParagraph 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)); + TextSelection(baseOffset: 0, extentOffset: text.length), + ); double textWidth = 0; double textHeight = 0; for (final TextBox box in textBox) { @@ -13346,7 +14480,9 @@ Size _getTextWidgetWidth( } bool _isSwipeInteractionEnabled( - bool enableSwipeSelection, DateRangePickerNavigationMode navigationMode) { + bool enableSwipeSelection, + DateRangePickerNavigationMode navigationMode, +) { return enableSwipeSelection && 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 9a2d4ed0e..c4f225957 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,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_datepicker/datepicker.dart'; +import '../../datepicker.dart'; import 'picker_helper.dart'; /// Sets the style for customizing the [SfDateRangePicker] header view. @@ -48,8 +48,11 @@ import 'picker_helper.dart'; @immutable class DateRangePickerHeaderStyle with Diagnosticable { /// Creates a header style for date range picker. - const DateRangePickerHeaderStyle( - {this.textAlign = TextAlign.left, this.backgroundColor, this.textStyle}); + const DateRangePickerHeaderStyle({ + this.textAlign = TextAlign.left, + this.backgroundColor, + this.textStyle, + }); /// The text style for the text in the [SfDateRangePicker] header view. /// @@ -164,7 +167,7 @@ class DateRangePickerHeaderStyle with Diagnosticable { final Color? backgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -191,11 +194,7 @@ class DateRangePickerHeaderStyle with Diagnosticable { @override int get hashCode { - return hashValues( - textStyle, - textAlign, - backgroundColor, - ); + return Object.hash(textStyle, textAlign, backgroundColor); } } @@ -321,7 +320,7 @@ class DateRangePickerViewHeaderStyle with Diagnosticable { final TextStyle? textStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -346,10 +345,7 @@ class DateRangePickerViewHeaderStyle with Diagnosticable { @override int get hashCode { - return hashValues( - backgroundColor, - textStyle, - ); + return Object.hash(backgroundColor, textStyle); } } @@ -448,7 +444,7 @@ class DateRangePickerWeekNumberStyle with Diagnosticable { final TextStyle? textStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -473,10 +469,7 @@ class DateRangePickerWeekNumberStyle with Diagnosticable { @override int get hashCode { - return hashValues( - textStyle, - backgroundColor, - ); + return Object.hash(textStyle, backgroundColor); } } @@ -558,25 +551,25 @@ 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]. - const DateRangePickerMonthViewSettings( - {this.numberOfWeeksInView = 6, - this.firstDayOfWeek = 7, - this.dayFormat = 'EE', - this.viewHeaderHeight = 30, - @Deprecated('Use selectionRadius property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.selectionRadius = -1, - this.showTrailingAndLeadingDates = false, - this.viewHeaderStyle = const DateRangePickerViewHeaderStyle(), - this.enableSwipeSelection = true, - this.blackoutDates, - this.specialDates, - this.weekendDays = const [6, 7], - this.showWeekNumber = false, - this.weekNumberStyle = const DateRangePickerWeekNumberStyle()}) - : assert(numberOfWeeksInView >= 1 && numberOfWeeksInView <= 6), - assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), - assert(viewHeaderHeight >= -1); + const DateRangePickerMonthViewSettings({ + this.numberOfWeeksInView = 6, + this.firstDayOfWeek = 7, + this.dayFormat = 'EE', + this.viewHeaderHeight = 30, + @Deprecated('Use selectionRadius property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.selectionRadius = -1, + this.showTrailingAndLeadingDates = false, + this.viewHeaderStyle = const DateRangePickerViewHeaderStyle(), + this.enableSwipeSelection = true, + this.blackoutDates, + this.specialDates, + this.weekendDays = const [6, 7], + this.showWeekNumber = false, + this.weekNumberStyle = const DateRangePickerWeekNumberStyle(), + }) : assert(numberOfWeeksInView >= 1 && numberOfWeeksInView <= 6), + assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), + assert(viewHeaderHeight >= -1); /// Formats a text in the [SfDateRangePicker] month view view header. /// @@ -970,8 +963,8 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// The weekends for month view in [SfDateRangePicker]. /// - /// Defaults to `[6,7]` represents `[DateTime.saturday, - /// DateTime.sunday]`. + /// Defaults to `[6,7]` represents + /// `[DateTime.saturday,DateTime.sunday]`. /// /// _Note:_ The [weekendDays] will not be highlighted until it's customize by /// using the [DateRangePickerMonthCellStyle.weekendTextStyle] or @@ -1070,7 +1063,7 @@ class DateRangePickerMonthViewSettings with Diagnosticable { final DateRangePickerWeekNumberStyle weekNumberStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1099,18 +1092,29 @@ class DateRangePickerMonthViewSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(IterableDiagnostics(blackoutDates) - .toDiagnosticsNode(name: 'blackoutDates')); - properties.add(IterableDiagnostics(specialDates) - .toDiagnosticsNode(name: 'specialDates')); + 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( + DiagnosticsProperty( + 'showTrailingAndLeadingDates', + showTrailingAndLeadingDates, + ), + ); + properties.add( + DiagnosticsProperty('enableSwipeSelection', enableSwipeSelection), + ); properties.add(viewHeaderStyle.toDiagnosticsNode(name: 'viewHeaderStyle')); properties.add(IterableProperty('weekendDays', weekendDays)); properties.add(DiagnosticsProperty('showWeekNumber', showWeekNumber)); @@ -1119,19 +1123,23 @@ class DateRangePickerMonthViewSettings with Diagnosticable { @override int get hashCode { - return hashValues( - dayFormat, - firstDayOfWeek, - viewHeaderStyle, - enableSwipeSelection, - viewHeaderHeight, - showTrailingAndLeadingDates, - numberOfWeeksInView, - showWeekNumber, - weekNumberStyle, - hashList(specialDates), - hashList(blackoutDates), - hashList(weekendDays)); + return Object.hash( + dayFormat, + firstDayOfWeek, + viewHeaderStyle, + enableSwipeSelection, + viewHeaderHeight, + showTrailingAndLeadingDates, + numberOfWeeksInView, + showWeekNumber, + weekNumberStyle, + + /// Below condition is referred from text style class + /// https://api.flutter.dev/flutter/painting/TextStyle/hashCode.html + specialDates == null ? null : Object.hashAll(specialDates!), + blackoutDates == null ? null : Object.hashAll(blackoutDates!), + Object.hashAll(weekendDays), + ); } } @@ -1211,15 +1219,16 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// /// The properties allows to customize the year cells in year view of /// [SfDateRangePicker]. - const DateRangePickerYearCellStyle( - {this.textStyle, - this.todayTextStyle, - this.leadingDatesTextStyle, - this.disabledDatesTextStyle, - this.cellDecoration, - this.todayCellDecoration, - this.disabledDatesDecoration, - this.leadingDatesDecoration}); + const DateRangePickerYearCellStyle({ + this.textStyle, + this.todayTextStyle, + this.leadingDatesTextStyle, + this.disabledDatesTextStyle, + this.cellDecoration, + this.todayCellDecoration, + this.disabledDatesDecoration, + this.leadingDatesDecoration, + }); /// The text style for the text in the [SfDateRangePicker] year, decade and /// century view cells. @@ -1628,7 +1637,7 @@ class DateRangePickerYearCellStyle with Diagnosticable { final Decoration? leadingDatesDecoration; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1654,33 +1663,56 @@ class DateRangePickerYearCellStyle with Diagnosticable { 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( - textStyle, - todayTextStyle, + properties.add( + DiagnosticsProperty('todayTextStyle', todayTextStyle), + ); + properties.add( + DiagnosticsProperty( + 'leadingDatesTextStyle', leadingDatesTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'disabledDatesTextStyle', disabledDatesTextStyle, + ), + ); + properties.add( + DiagnosticsProperty( + 'disabledDatesDecoration', disabledDatesDecoration, - cellDecoration, + ), + ); + properties.add( + DiagnosticsProperty('cellDecoration', cellDecoration), + ); + properties.add( + DiagnosticsProperty( + 'todayCellDecoration', todayCellDecoration, - leadingDatesDecoration); + ), + ); + properties.add( + DiagnosticsProperty( + 'leadingDatesDecoration', + leadingDatesDecoration, + ), + ); + } + + @override + int get hashCode { + return Object.hash( + textStyle, + todayTextStyle, + leadingDatesTextStyle, + disabledDatesTextStyle, + disabledDatesDecoration, + cellDecoration, + todayCellDecoration, + leadingDatesDecoration, + ); } } @@ -1764,41 +1796,42 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// /// The properties allows to customize the month cells in month view of /// [SfDateRangePicker]. - const DateRangePickerMonthCellStyle( - {@Deprecated('Use selectionColor property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.selectionColor, - @Deprecated('Use startRangeSelectionColor property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.startRangeSelectionColor, - @Deprecated('Use endRangeSelectionColor property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.endRangeSelectionColor, - @Deprecated('Use rangeSelectionColor property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.rangeSelectionColor, - this.textStyle, - this.todayTextStyle, - this.trailingDatesTextStyle, - this.leadingDatesTextStyle, - @Deprecated('Use selectionTextStyle property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.selectionTextStyle, - this.disabledDatesTextStyle, - this.blackoutDateTextStyle, - this.weekendTextStyle, - this.specialDatesTextStyle, - this.specialDatesDecoration, - this.blackoutDatesDecoration, - this.cellDecoration, - this.todayCellDecoration, - this.disabledDatesDecoration, - this.trailingDatesDecoration, - this.leadingDatesDecoration, - @Deprecated('Use rangeTextStyle property in SfDateRangePicker') - // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.rangeTextStyle, - this.weekendDatesDecoration}); + const DateRangePickerMonthCellStyle({ + @Deprecated('Use selectionColor property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.selectionColor, + @Deprecated('Use startRangeSelectionColor property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.startRangeSelectionColor, + @Deprecated('Use endRangeSelectionColor property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.endRangeSelectionColor, + @Deprecated('Use rangeSelectionColor property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.rangeSelectionColor, + this.textStyle, + this.todayTextStyle, + this.trailingDatesTextStyle, + this.leadingDatesTextStyle, + @Deprecated('Use selectionTextStyle property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.selectionTextStyle, + this.disabledDatesTextStyle, + this.blackoutDateTextStyle, + this.weekendTextStyle, + this.specialDatesTextStyle, + this.specialDatesDecoration, + this.blackoutDatesDecoration, + this.cellDecoration, + this.todayCellDecoration, + this.disabledDatesDecoration, + this.trailingDatesDecoration, + this.leadingDatesDecoration, + @Deprecated('Use rangeTextStyle property in SfDateRangePicker') + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + this.rangeTextStyle, + this.weekendDatesDecoration, + }); /// The text style for the text in the [SfDateRangePicker] month cells. /// @@ -3052,7 +3085,7 @@ class DateRangePickerMonthCellStyle with Diagnosticable { final Color? endRangeSelectionColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -3086,41 +3119,92 @@ class DateRangePickerMonthCellStyle with Diagnosticable { 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)); + 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 Object.hashAll([ textStyle, todayTextStyle, trailingDatesTextStyle, @@ -3153,7 +3237,8 @@ class DateRangePickerValueChangeNotifier with Diagnosticable { /// /// Listeners can be removed with [removePropertyChangedListener]. void addPropertyChangedListener( - DateRangePickerValueChangedCallback listener) { + DateRangePickerValueChangedCallback listener, + ) { _listeners ??= []; _listeners?.add(listener); } @@ -3167,7 +3252,8 @@ class DateRangePickerValueChangeNotifier with Diagnosticable { /// /// Listeners can be added with [addPropertyChangedListener]. void removePropertyChangedListener( - DateRangePickerValueChangedCallback listener) { + DateRangePickerValueChangedCallback listener, + ) { if (_listeners == null) { return; } @@ -3922,12 +4008,19 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { 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')); + IterableDiagnostics( + selectedDates, + ).toDiagnosticsNode(name: 'selectedDates'), + ); + properties.add( + DiagnosticsProperty('selectedRange', selectedRange), + ); + properties.add( + IterableDiagnostics( + selectedRanges, + ).toDiagnosticsNode(name: 'selectedRanges'), + ); properties.add(EnumProperty('view', view)); } } @@ -4003,7 +4096,7 @@ enum DateRangePickerView { /// /// _Note:_ This property not applicable when the /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. - century + century, } /// The shape for the selection view in [SfDateRangePicker]. @@ -4018,7 +4111,7 @@ enum DateRangePickerSelectionShape { /// - DateRangePickerSelectionShape.rectangle, Draws the date selection in /// rectangle shape. - rectangle + rectangle, } /// A direction in which the [SfDateRangePicker] navigates. @@ -4033,7 +4126,7 @@ enum DateRangePickerNavigationDirection { /// - DateRangePickerNavigationDirection.horizontal, Navigates in right and /// left direction. - horizontal + horizontal, } /// A type specifies how the date picker navigation interaction works. @@ -4070,7 +4163,7 @@ enum DateRangePickerNavigationMode { /// 6. header view background color changed to white on light theme or /// grey[850] on dark theme when [backgroundColor] in /// [DateRangePickerHeaderStyle] is transparent. - scroll + scroll, } /// The direction for extendable range selection in [SfDateRangePicker]. @@ -4089,7 +4182,7 @@ enum ExtendableRangeSelectionDirection { /// Allows to extend the selection only in backward direction. /// The end date will not be changed here. - backward + backward, } /// The dates that visible on the view changes in [SfDateRangePicker]. @@ -4145,7 +4238,7 @@ class DateRangePickerSelectionChangedArgs { /// when the widget [DateRangePickerSelectionMode] set as range. /// /// The argument value will return the changed ranges as - /// [List] when the widget [DateRangePickerSelectionMode] set /// as multi range. final dynamic value; } @@ -4190,8 +4283,11 @@ class PickerDateRange with Diagnosticable { /// See also: /// * [SfDateRangePicker.cellBuilder], which matches this signature. /// * [SfDateRangePicker], which uses this signature in one of it's callback. -typedef DateRangePickerCellBuilder = Widget Function( - BuildContext context, DateRangePickerCellDetails cellDetails); +typedef DateRangePickerCellBuilder = + Widget Function( + BuildContext context, + DateRangePickerCellDetails cellDetails, + ); /// Signature for predicating dates for enabled date selections. /// @@ -4206,8 +4302,11 @@ typedef DateRangePickerSelectableDayPredicate = bool Function(DateTime date); /// * [SfDateRangePicker], which uses this signature in one of it's callback. class DateRangePickerCellDetails { /// Constructor to store the details that needed on calendar cell builder. - DateRangePickerCellDetails( - {required this.date, required this.bounds, required 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. @@ -4249,7 +4348,7 @@ class IterableDiagnostics extends DiagnosticableTree { return collection == null ? 'null' : collection!.isNotEmpty - ? '<$T>' - : ''; + ? '<$T>' + : ''; } } 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 bb115c8ae..5adba3d64 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,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_datepicker/datepicker.dart'; +import '../../datepicker.dart'; import 'picker_helper.dart'; /// Options to customize the month view of the [SfHijriDateRangePicker]. @@ -79,19 +79,19 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// /// The properties allows to customize the month view of /// [SfHijriDateRangePicker]. - const HijriDatePickerMonthViewSettings( - {this.firstDayOfWeek = 7, - this.dayFormat = 'EE', - this.viewHeaderHeight = 30, - this.viewHeaderStyle = const DateRangePickerViewHeaderStyle(), - this.enableSwipeSelection = true, - this.blackoutDates, - this.specialDates, - this.showWeekNumber = false, - this.weekNumberStyle = const DateRangePickerWeekNumberStyle(), - this.weekendDays = const [6, 7]}) - : assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), - assert(viewHeaderHeight >= -1); + const HijriDatePickerMonthViewSettings({ + this.firstDayOfWeek = 7, + this.dayFormat = 'EE', + this.viewHeaderHeight = 30, + this.viewHeaderStyle = const DateRangePickerViewHeaderStyle(), + this.enableSwipeSelection = true, + this.blackoutDates, + this.specialDates, + this.showWeekNumber = false, + this.weekNumberStyle = const DateRangePickerWeekNumberStyle(), + this.weekendDays = const [6, 7], + }) : assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), + assert(viewHeaderHeight >= -1); /// Formats a text in the [SfHijriDateRangePicker] month view view header. /// @@ -423,8 +423,8 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// The weekends for month view in [SfHijriDateRangePicker]. /// - /// Defaults to `[6,7]` represents `[DateTime.saturday, - /// DateTime.sunday]`. + /// Defaults to `[6,7]` represents + /// `[DateTime.saturday,DateTime.sunday]`. /// /// _Note:_ The [weekendDays] will not be highlighted until it's customize by /// using the [HijriDatePickerMonthCellStyle.weekendTextStyle] or @@ -463,7 +463,7 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { final List weekendDays; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -490,15 +490,22 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(IterableDiagnostics(blackoutDates) - .toDiagnosticsNode(name: 'blackoutDates')); - properties.add(IterableDiagnostics(specialDates) - .toDiagnosticsNode(name: 'specialDates')); + 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( + DiagnosticsProperty('enableSwipeSelection', enableSwipeSelection), + ); properties.add(viewHeaderStyle.toDiagnosticsNode(name: 'viewHeaderStyle')); properties.add(IterableProperty('weekendDays', weekendDays)); properties.add(DiagnosticsProperty('showWeekNumber', showWeekNumber)); @@ -507,17 +514,21 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { @override int get hashCode { - return hashValues( - dayFormat, - firstDayOfWeek, - viewHeaderStyle, - enableSwipeSelection, - viewHeaderHeight, - showWeekNumber, - weekNumberStyle, - hashList(specialDates), - hashList(blackoutDates), - hashList(weekendDays)); + return Object.hash( + dayFormat, + firstDayOfWeek, + viewHeaderStyle, + enableSwipeSelection, + viewHeaderHeight, + showWeekNumber, + weekNumberStyle, + + /// Below condition is referred from text style class + /// https://api.flutter.dev/flutter/painting/TextStyle/hashCode.html + specialDates == null ? null : Object.hashAll(specialDates!), + blackoutDates == null ? null : Object.hashAll(blackoutDates!), + Object.hashAll(weekendDays), + ); } } @@ -593,13 +604,14 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// /// The properties allows to customize the year cells in year view of /// [SfHijriDateRangePicker]. - const HijriDatePickerYearCellStyle( - {this.textStyle, - this.todayTextStyle, - this.disabledDatesTextStyle, - this.cellDecoration, - this.todayCellDecoration, - this.disabledDatesDecoration}); + const HijriDatePickerYearCellStyle({ + this.textStyle, + this.todayTextStyle, + this.disabledDatesTextStyle, + this.cellDecoration, + this.todayCellDecoration, + this.disabledDatesDecoration, + }); /// The text style for the text in the [SfHijriDateRangePicker] year and /// decade view cells. @@ -892,7 +904,7 @@ class HijriDatePickerYearCellStyle with Diagnosticable { final Decoration? todayCellDecoration; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -916,22 +928,42 @@ class HijriDatePickerYearCellStyle with Diagnosticable { 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)); + 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, - disabledDatesDecoration, cellDecoration, todayCellDecoration); + return Object.hash( + textStyle, + todayTextStyle, + disabledDatesTextStyle, + disabledDatesDecoration, + cellDecoration, + todayCellDecoration, + ); } } @@ -1009,19 +1041,20 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// /// The properties allows to customize the month cells in month view of /// [SfHijriDateRangePicker]. - const HijriDatePickerMonthCellStyle( - {this.textStyle, - this.todayTextStyle, - this.disabledDatesTextStyle, - this.blackoutDateTextStyle, - this.weekendTextStyle, - this.specialDatesTextStyle, - this.specialDatesDecoration, - this.blackoutDatesDecoration, - this.cellDecoration, - this.todayCellDecoration, - this.disabledDatesDecoration, - this.weekendDatesDecoration}); + const HijriDatePickerMonthCellStyle({ + this.textStyle, + this.todayTextStyle, + this.disabledDatesTextStyle, + this.blackoutDateTextStyle, + this.weekendTextStyle, + this.specialDatesTextStyle, + this.specialDatesDecoration, + this.blackoutDatesDecoration, + this.cellDecoration, + this.todayCellDecoration, + this.disabledDatesDecoration, + this.weekendDatesDecoration, + }); /// The text style for the text in the [SfHijriDateRangePicker] month cells. /// @@ -1729,7 +1762,7 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { final Decoration? todayCellDecoration; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1759,33 +1792,68 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { 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)); + 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 Object.hashAll([ textStyle, todayTextStyle, disabledDatesTextStyle, @@ -2518,16 +2586,25 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { @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')); + 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)); } } @@ -2617,15 +2694,18 @@ class HijriDateRange with Diagnosticable { /// * [SfHijriDateRangePicker.cellBuilder], which matches this signature. /// * [SfHijriDateRangePicker], which uses this signature in one of it's /// callback. -typedef HijriDateRangePickerCellBuilder = Widget Function( - BuildContext context, HijriDateRangePickerCellDetails cellDetails); +typedef HijriDateRangePickerCellBuilder = + Widget Function( + BuildContext context, + HijriDateRangePickerCellDetails cellDetails, + ); /// Signature for predicating dates for enabled date selections. /// /// [SelectableDayPredicate] parameter used to specify allowable days in the /// SfHijriDateRangePicker. -typedef HijriDatePickerSelectableDayPredicate = bool Function( - HijriDateTime date); +typedef HijriDatePickerSelectableDayPredicate = + bool Function(HijriDateTime date); /// Contains the details that needed on calendar cell builder. /// @@ -2635,8 +2715,11 @@ typedef HijriDatePickerSelectableDayPredicate = bool Function( /// callback. class HijriDateRangePickerCellDetails { /// Constructor to store the details that needed on calendar cell builder. - HijriDateRangePickerCellDetails( - {required this.date, required this.bounds, required this.visibleDates}); + HijriDateRangePickerCellDetails({ + required this.date, + required this.bounds, + required this.visibleDates, + }); /// Date value associated with the picker cell in month, year, decade and /// century views. 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 671c0c325..c0d28b95e 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 @@ -13,45 +13,47 @@ class MonthView extends StatefulWidget { /// Constructor for create the month view widget used to hold the month cell /// widgets. const MonthView( - this.visibleDates, - this.rowCount, - this.cellStyle, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.datePickerTheme, - this.isRtl, - this.todayHighlightColor, - this.minDate, - this.maxDate, - this.enablePastDates, - this.showLeadingAndTailingDates, - this.blackoutDates, - this.specialDates, - this.weekendDays, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor, - this.selectionMode, - this.isHijri, - this.localizations, - this.navigationDirection, - this.width, - this.height, - this.getPickerStateDetails, - this.cellBuilder, - this.showWeekNumber, - this.weekNumberStyle, - this.isMobilePlatform, - this.disableDatesCollection, - this.extendableRangeSelectionDirection); + this.visibleDates, + this.rowCount, + this.cellStyle, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.datePickerTheme, + this.isRtl, + this.todayHighlightColor, + this.minDate, + this.maxDate, + this.enablePastDates, + this.showLeadingAndTailingDates, + this.blackoutDates, + this.specialDates, + this.weekendDays, + this.selectionShape, + this.selectionRadius, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionNotifier, + this.textScaleFactor, + this.selectionMode, + this.isHijri, + this.localizations, + this.navigationDirection, + this.width, + this.height, + this.getPickerStateDetails, + this.cellBuilder, + this.showWeekNumber, + this.weekNumberStyle, + this.isMobilePlatform, + this.disableDatesCollection, + this.extendableRangeSelectionDirection, { + super.key, + }); /// Defines the month row count. final int rowCount; @@ -190,11 +192,13 @@ class _MonthViewState extends State { _pickerStateDetails = PickerStateArgs(); widget.getPickerStateDetails(_pickerStateDetails); _selectedDate = _pickerStateDetails.selectedDate; - _selectedDates = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedDates, + ); _selectedRange = _pickerStateDetails.selectedRange; - _selectedRanges = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedRanges, + ); widget.selectionNotifier.addListener(_updateSelection); super.initState(); } @@ -211,9 +215,13 @@ class _MonthViewState extends State { widget.multiViewSpacing != oldWidget.multiViewSpacing || widget.enableMultiView != oldWidget.enableMultiView || !DateRangePickerHelper.isDateCollectionEquals( - widget.blackoutDates, oldWidget.blackoutDates) || + widget.blackoutDates, + oldWidget.blackoutDates, + ) || !DateRangePickerHelper.isDateCollectionEquals( - widget.specialDates, oldWidget.specialDates) || + widget.specialDates, + oldWidget.specialDates, + ) || widget.showLeadingAndTailingDates != oldWidget.showLeadingAndTailingDates || widget.rowCount != oldWidget.rowCount || @@ -246,16 +254,21 @@ class _MonthViewState extends State { @override Widget build(BuildContext context) { weekNumberPanelWidth = DateRangePickerHelper.getWeekNumberPanelWidth( - widget.showWeekNumber, widget.width, widget.isMobilePlatform); + widget.showWeekNumber, + widget.width, + widget.isMobilePlatform, + ); if (widget.cellBuilder != null && _children.isEmpty) { double webUIPadding = 0; double width = widget.width - weekNumberPanelWidth; double height = widget.height; int viewCount = 1; - final bool isHorizontalMultiView = widget.enableMultiView && + final bool isHorizontalMultiView = + widget.enableMultiView && widget.navigationDirection == DateRangePickerNavigationDirection.horizontal; - final bool isVerticalMultiView = widget.enableMultiView && + final bool isVerticalMultiView = + widget.enableMultiView && widget.navigationDirection == DateRangePickerNavigationDirection.vertical; @@ -274,39 +287,48 @@ class _MonthViewState extends State { final double cellHeight = height / widget.rowCount; final bool hideLeadingAndTrailingDates = (widget.rowCount == 6 && !widget.showLeadingAndTailingDates) || - widget.isHijri; + widget.isHijri; for (int j = 0; j < viewCount; j++) { final int currentViewIndex = widget.isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; final int viewStartIndex = j * datesCount; - final int currentMonth = widget - .visibleDates[(viewStartIndex + (datesCount / 2)).truncate()] - .month as int; - - double viewStartPosition = isVerticalMultiView - ? weekNumberPanelWidth - : (currentViewIndex * width) + - (currentViewIndex * widget.multiViewSpacing) + - (weekNumberPanelWidth * (viewStartIndex + 1)); + final int currentMonth = + widget + .visibleDates[(viewStartIndex + (datesCount / 2)) + .truncate()] + .month + as int; + + double viewStartPosition = + isVerticalMultiView + ? weekNumberPanelWidth + : (currentViewIndex * width) + + (currentViewIndex * widget.multiViewSpacing) + + (weekNumberPanelWidth * (viewStartIndex + 1)); if (widget.isRtl) { - viewStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * currentViewIndex); + viewStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * currentViewIndex); } final double viewEndPosition = viewStartPosition + width; double xPosition = viewStartPosition; - double yPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + - (currentViewIndex * widget.multiViewSpacing); + double yPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + + (currentViewIndex * widget.multiViewSpacing); for (int i = 0; i < datesCount; i++) { int currentIndex = i; if (widget.isRtl) { final int rowIndex = i ~/ DateTime.daysPerWeek; - currentIndex = DateRangePickerHelper.getRtlIndex( - DateTime.daysPerWeek, i % DateTime.daysPerWeek) + + currentIndex = + DateRangePickerHelper.getRtlIndex( + DateTime.daysPerWeek, + i % DateTime.daysPerWeek, + ) + (rowIndex * DateTime.daysPerWeek); } @@ -329,19 +351,33 @@ class _MonthViewState extends State { continue; } - 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))) as Widget; + 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, + ), + ), + ) + as Widget; _children.add(child); xPosition += cellWidth; } @@ -359,11 +395,13 @@ class _MonthViewState extends State { _children.clear(); _selectedDate = _pickerStateDetails.selectedDate; - _selectedDates = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedDates, + ); _selectedRange = _pickerStateDetails.selectedRange; - _selectedRanges = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedRanges, + ); if (!isNeedSetState) { return; @@ -384,18 +422,24 @@ class _MonthViewState extends State { case DateRangePickerSelectionMode.multiple: { return DateRangePickerHelper.isDateCollectionEquals( - _selectedDates, _pickerStateDetails.selectedDates); + _selectedDates, + _pickerStateDetails.selectedDates, + ); } case DateRangePickerSelectionMode.range: case DateRangePickerSelectionMode.extendableRange: { return DateRangePickerHelper.isRangeEquals( - _selectedRange, _pickerStateDetails.selectedRange); + _selectedRange, + _pickerStateDetails.selectedRange, + ); } case DateRangePickerSelectionMode.multiRange: { return DateRangePickerHelper.isDateRangesEquals( - _selectedRanges, _pickerStateDetails.selectedRanges); + _selectedRanges, + _pickerStateDetails.selectedRanges, + ); } } } @@ -405,87 +449,130 @@ class _MonthViewState extends State { case DateRangePickerSelectionMode.single: { return _MonthViewSingleSelectionRenderWidget( - widget.visibleDates, - widget.rowCount, - widget.cellStyle, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.todayHighlightColor, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.showLeadingAndTailingDates, - widget.blackoutDates, - widget.specialDates, - widget.weekendDays, - widget.selectionShape, - widget.selectionRadius, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionNotifier, - widget.textScaleFactor, - widget.height, - widget.width, - _selectedDate, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.isMobilePlatform, - widget.weekNumberStyle, - weekNumberPanelWidth, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.rowCount, + widget.cellStyle, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.datePickerTheme, + widget.isRtl, + widget.todayHighlightColor, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.showLeadingAndTailingDates, + widget.blackoutDates, + widget.specialDates, + widget.weekendDays, + widget.selectionShape, + widget.selectionRadius, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionNotifier, + widget.textScaleFactor, + widget.height, + widget.width, + _selectedDate, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.isMobilePlatform, + widget.weekNumberStyle, + weekNumberPanelWidth, + widget.disableDatesCollection, + widgets: _children, + ); } case DateRangePickerSelectionMode.multiple: { return _MonthViewMultiSelectionRenderWidget( - widget.visibleDates, - widget.rowCount, - widget.cellStyle, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.todayHighlightColor, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.showLeadingAndTailingDates, - widget.blackoutDates, - widget.specialDates, - widget.weekendDays, - widget.selectionShape, - widget.selectionRadius, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionNotifier, - widget.textScaleFactor, - widget.height, - widget.width, - _selectedDates, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.isMobilePlatform, - widget.weekNumberStyle, - weekNumberPanelWidth, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.rowCount, + widget.cellStyle, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.datePickerTheme, + widget.isRtl, + widget.todayHighlightColor, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.showLeadingAndTailingDates, + widget.blackoutDates, + widget.specialDates, + widget.weekendDays, + widget.selectionShape, + widget.selectionRadius, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionNotifier, + widget.textScaleFactor, + widget.height, + widget.width, + _selectedDates, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.isMobilePlatform, + widget.weekNumberStyle, + weekNumberPanelWidth, + widget.disableDatesCollection, + widgets: _children, + ); } case DateRangePickerSelectionMode.range: return _MonthViewRangeSelectionRenderWidget( + widget.visibleDates, + widget.rowCount, + widget.cellStyle, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.datePickerTheme, + widget.isRtl, + widget.todayHighlightColor, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.showLeadingAndTailingDates, + widget.blackoutDates, + widget.specialDates, + widget.weekendDays, + widget.selectionShape, + widget.selectionRadius, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionNotifier, + widget.textScaleFactor, + widget.height, + widget.width, + _selectedRange, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.isMobilePlatform, + widget.weekNumberStyle, + weekNumberPanelWidth, + widget.disableDatesCollection, + widgets: _children, + ); + case DateRangePickerSelectionMode.extendableRange: + { + return _MonthViewExtendableRangeSelectionRenderWidget( widget.visibleDates, widget.rowCount, widget.cellStyle, @@ -522,89 +609,51 @@ class _MonthViewState extends State { widget.weekNumberStyle, weekNumberPanelWidth, widget.disableDatesCollection, - widgets: _children); - case DateRangePickerSelectionMode.extendableRange: - { - return _MonthViewExtendableRangeSelectionRenderWidget( - widget.visibleDates, - widget.rowCount, - widget.cellStyle, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.todayHighlightColor, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.showLeadingAndTailingDates, - widget.blackoutDates, - widget.specialDates, - widget.weekendDays, - widget.selectionShape, - widget.selectionRadius, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionNotifier, - widget.textScaleFactor, - widget.height, - widget.width, - _selectedRange, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.isMobilePlatform, - widget.weekNumberStyle, - weekNumberPanelWidth, - widget.disableDatesCollection, - widget.extendableRangeSelectionDirection, - widgets: _children); + widget.extendableRangeSelectionDirection, + widgets: _children, + ); } case DateRangePickerSelectionMode.multiRange: { return _MonthViewMultiRangeSelectionRenderWidget( - widget.visibleDates, - widget.rowCount, - widget.cellStyle, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.todayHighlightColor, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.showLeadingAndTailingDates, - widget.blackoutDates, - widget.specialDates, - widget.weekendDays, - widget.selectionShape, - widget.selectionRadius, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionNotifier, - widget.textScaleFactor, - widget.height, - widget.width, - _selectedRanges, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.isMobilePlatform, - widget.weekNumberStyle, - weekNumberPanelWidth, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.rowCount, + widget.cellStyle, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.datePickerTheme, + widget.isRtl, + widget.todayHighlightColor, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.showLeadingAndTailingDates, + widget.blackoutDates, + widget.specialDates, + widget.weekendDays, + widget.selectionShape, + widget.selectionRadius, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionNotifier, + widget.textScaleFactor, + widget.height, + widget.width, + _selectedRanges, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.isMobilePlatform, + widget.weekNumberStyle, + weekNumberPanelWidth, + widget.disableDatesCollection, + widgets: _children, + ); } } } @@ -612,45 +661,45 @@ class _MonthViewState extends State { class _MonthViewSingleSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MonthViewSingleSelectionRenderWidget( - this.visibleDates, - this.rowCount, - this.cellStyle, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.datePickerTheme, - this.isRtl, - this.todayHighlightColor, - this.minDate, - this.maxDate, - this.enablePastDates, - this.showLeadingAndTailingDates, - this.blackoutDates, - this.specialDates, - this.weekendDays, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor, - this.height, - this.width, - this.selectedDate, - this.isHijri, - this.localizations, - this.navigationDirection, - this.isMobilePlatform, - this.weekNumberStyle, - this.weekNumberPanelWidth, - this.disableDatesCollection, - {List widgets = const []}) - : super(children: widgets); + const _MonthViewSingleSelectionRenderWidget( + this.visibleDates, + this.rowCount, + this.cellStyle, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.datePickerTheme, + this.isRtl, + this.todayHighlightColor, + this.minDate, + this.maxDate, + this.enablePastDates, + this.showLeadingAndTailingDates, + this.blackoutDates, + this.specialDates, + this.weekendDays, + this.selectionShape, + this.selectionRadius, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionNotifier, + this.textScaleFactor, + this.height, + this.width, + this.selectedDate, + this.isHijri, + this.localizations, + this.navigationDirection, + this.isMobilePlatform, + this.weekNumberStyle, + this.weekNumberPanelWidth, + this.disableDatesCollection, { + List widgets = const [], + }) : super(children: widgets); final DateRangePickerWeekNumberStyle weekNumberStyle; @@ -726,49 +775,53 @@ class _MonthViewSingleSelectionRenderWidget @override _MonthViewSingleSelectionRenderObject createRenderObject( - BuildContext context) { + BuildContext context, + ) { return _MonthViewSingleSelectionRenderObject( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - selectedDate, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + selectedDate, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); } @override - void updateRenderObject(BuildContext context, - _MonthViewSingleSelectionRenderObject renderObject) { + void updateRenderObject( + BuildContext context, + _MonthViewSingleSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..rowCount = rowCount @@ -811,45 +864,45 @@ class _MonthViewSingleSelectionRenderWidget class _MonthViewMultiSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MonthViewMultiSelectionRenderWidget( - this.visibleDates, - this.rowCount, - this.cellStyle, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.datePickerTheme, - this.isRtl, - this.todayHighlightColor, - this.minDate, - this.maxDate, - this.enablePastDates, - this.showLeadingAndTailingDates, - this.blackoutDates, - this.specialDates, - this.weekendDays, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor, - this.height, - this.width, - this.selectedDates, - this.isHijri, - this.localizations, - this.navigationDirection, - this.isMobilePlatform, - this.weekNumberStyle, - this.weekNumberPanelWidth, - this.disableDatesCollection, - {List widgets = const []}) - : super(children: widgets); + const _MonthViewMultiSelectionRenderWidget( + this.visibleDates, + this.rowCount, + this.cellStyle, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.datePickerTheme, + this.isRtl, + this.todayHighlightColor, + this.minDate, + this.maxDate, + this.enablePastDates, + this.showLeadingAndTailingDates, + this.blackoutDates, + this.specialDates, + this.weekendDays, + this.selectionShape, + this.selectionRadius, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionNotifier, + this.textScaleFactor, + this.height, + this.width, + this.selectedDates, + this.isHijri, + this.localizations, + this.navigationDirection, + this.isMobilePlatform, + this.weekNumberStyle, + this.weekNumberPanelWidth, + this.disableDatesCollection, { + List widgets = const [], + }) : super(children: widgets); final DateRangePickerWeekNumberStyle weekNumberStyle; final int rowCount; @@ -924,49 +977,53 @@ class _MonthViewMultiSelectionRenderWidget @override _MonthViewMultiSelectionRenderObject createRenderObject( - BuildContext context) { + BuildContext context, + ) { return _MonthViewMultiSelectionRenderObject( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - selectedDates, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + selectedDates, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); } @override void updateRenderObject( - BuildContext context, _MonthViewMultiSelectionRenderObject renderObject) { + BuildContext context, + _MonthViewMultiSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..rowCount = rowCount @@ -1009,45 +1066,45 @@ class _MonthViewMultiSelectionRenderWidget class _MonthViewRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MonthViewRangeSelectionRenderWidget( - this.visibleDates, - this.rowCount, - this.cellStyle, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.datePickerTheme, - this.isRtl, - this.todayHighlightColor, - this.minDate, - this.maxDate, - this.enablePastDates, - this.showLeadingAndTailingDates, - this.blackoutDates, - this.specialDates, - this.weekendDays, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor, - this.height, - this.width, - this.selectedRange, - this.isHijri, - this.localizations, - this.navigationDirection, - this.isMobilePlatform, - this.weekNumberStyle, - this.weekNumberPanelWidth, - this.disableDatesCollection, - {required List widgets}) - : super(children: widgets); + const _MonthViewRangeSelectionRenderWidget( + this.visibleDates, + this.rowCount, + this.cellStyle, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.datePickerTheme, + this.isRtl, + this.todayHighlightColor, + this.minDate, + this.maxDate, + this.enablePastDates, + this.showLeadingAndTailingDates, + this.blackoutDates, + this.specialDates, + this.weekendDays, + this.selectionShape, + this.selectionRadius, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionNotifier, + this.textScaleFactor, + this.height, + this.width, + this.selectedRange, + this.isHijri, + this.localizations, + this.navigationDirection, + this.isMobilePlatform, + this.weekNumberStyle, + this.weekNumberPanelWidth, + this.disableDatesCollection, { + required List widgets, + }) : super(children: widgets); final DateRangePickerWeekNumberStyle weekNumberStyle; @@ -1123,49 +1180,53 @@ class _MonthViewRangeSelectionRenderWidget @override _MonthViewRangeSelectionRenderObject createRenderObject( - BuildContext context) { + BuildContext context, + ) { return _MonthViewRangeSelectionRenderObject( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - selectedRange, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + selectedRange, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); } @override void updateRenderObject( - BuildContext context, _MonthViewRangeSelectionRenderObject renderObject) { + BuildContext context, + _MonthViewRangeSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..rowCount = rowCount @@ -1208,46 +1269,46 @@ class _MonthViewRangeSelectionRenderWidget class _MonthViewExtendableRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MonthViewExtendableRangeSelectionRenderWidget( - this.visibleDates, - this.rowCount, - this.cellStyle, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.datePickerTheme, - this.isRtl, - this.todayHighlightColor, - this.minDate, - this.maxDate, - this.enablePastDates, - this.showLeadingAndTailingDates, - this.blackoutDates, - this.specialDates, - this.weekendDays, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor, - this.height, - this.width, - this.selectedRange, - this.isHijri, - this.localizations, - this.navigationDirection, - this.isMobilePlatform, - this.weekNumberStyle, - this.weekNumberPanelWidth, - this.disableDatesCollection, - this.extendableRangeSelectionDirection, - {required List widgets}) - : super(children: widgets); + const _MonthViewExtendableRangeSelectionRenderWidget( + this.visibleDates, + this.rowCount, + this.cellStyle, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.datePickerTheme, + this.isRtl, + this.todayHighlightColor, + this.minDate, + this.maxDate, + this.enablePastDates, + this.showLeadingAndTailingDates, + this.blackoutDates, + this.specialDates, + this.weekendDays, + this.selectionShape, + this.selectionRadius, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionNotifier, + this.textScaleFactor, + this.height, + this.width, + this.selectedRange, + this.isHijri, + this.localizations, + this.navigationDirection, + this.isMobilePlatform, + this.weekNumberStyle, + this.weekNumberPanelWidth, + this.disableDatesCollection, + this.extendableRangeSelectionDirection, { + required List widgets, + }) : super(children: widgets); final int rowCount; @@ -1325,50 +1386,54 @@ class _MonthViewExtendableRangeSelectionRenderWidget @override _MonthViewExtendableRangeSelectionRenderObject createRenderObject( - BuildContext context) { + BuildContext context, + ) { return _MonthViewExtendableRangeSelectionRenderObject( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - selectedRange, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection, - extendableRangeSelectionDirection); + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + selectedRange, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + extendableRangeSelectionDirection, + ); } @override - void updateRenderObject(BuildContext context, - _MonthViewExtendableRangeSelectionRenderObject renderObject) { + void updateRenderObject( + BuildContext context, + _MonthViewExtendableRangeSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..rowCount = rowCount @@ -1412,45 +1477,45 @@ class _MonthViewExtendableRangeSelectionRenderWidget class _MonthViewMultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MonthViewMultiRangeSelectionRenderWidget( - this.visibleDates, - this.rowCount, - this.cellStyle, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.datePickerTheme, - this.isRtl, - this.todayHighlightColor, - this.minDate, - this.maxDate, - this.enablePastDates, - this.showLeadingAndTailingDates, - this.blackoutDates, - this.specialDates, - this.weekendDays, - this.selectionShape, - this.selectionRadius, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionNotifier, - this.textScaleFactor, - this.height, - this.width, - this.selectedRanges, - this.isHijri, - this.localizations, - this.navigationDirection, - this.isMobilePlatform, - this.weekNumberStyle, - this.weekNumberPanelWidth, - this.disableDatesCollection, - {required List widgets}) - : super(children: widgets); + const _MonthViewMultiRangeSelectionRenderWidget( + this.visibleDates, + this.rowCount, + this.cellStyle, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.datePickerTheme, + this.isRtl, + this.todayHighlightColor, + this.minDate, + this.maxDate, + this.enablePastDates, + this.showLeadingAndTailingDates, + this.blackoutDates, + this.specialDates, + this.weekendDays, + this.selectionShape, + this.selectionRadius, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionNotifier, + this.textScaleFactor, + this.height, + this.width, + this.selectedRanges, + this.isHijri, + this.localizations, + this.navigationDirection, + this.isMobilePlatform, + this.weekNumberStyle, + this.weekNumberPanelWidth, + this.disableDatesCollection, { + required List widgets, + }) : super(children: widgets); final DateRangePickerWeekNumberStyle weekNumberStyle; @@ -1526,49 +1591,53 @@ class _MonthViewMultiRangeSelectionRenderWidget @override _MonthViewMultiRangeSelectionRenderObject createRenderObject( - BuildContext context) { - return _MonthViewMultiRangeSelectionRenderObject( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - selectedRanges, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + BuildContext context, + ) { + return _MonthViewMultiRangeSelectionRenderObject( + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + selectedRanges, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); } @override - void updateRenderObject(BuildContext context, - _MonthViewMultiRangeSelectionRenderObject renderObject) { + void updateRenderObject( + BuildContext context, + _MonthViewMultiRangeSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..rowCount = rowCount @@ -1614,41 +1683,42 @@ class _DatePickerParentData extends ContainerBoxParentData {} abstract class _IMonthView extends RenderBox with ContainerRenderObjectMixin { _IMonthView( - this._visibleDates, - this._rowCount, - this._cellStyle, - this._selectionTextStyle, - this._rangeTextStyle, - this._selectionColor, - this._startRangeSelectionColor, - this._endRangeSelectionColor, - this._rangeSelectionColor, - this._datePickerTheme, - this._isRtl, - this._todayHighlightColor, - this._minDate, - this._maxDate, - this._enablePastDates, - this._showLeadingAndTailingDates, - this._blackoutDates, - this._specialDates, - this._weekendDays, - this._selectionShape, - this._selectionRadius, - this._mouseHoverPosition, - this._enableMultiView, - this._multiViewSpacing, - this.selectionNotifier, - this._textScaleFactor, - this._height, - this._width, - this._isHijri, - this._navigationDirection, - this.localizations, - this.isMobilePlatform, - this._weekNumberStyle, - this._weekNumberPanelWidth, - this._disableDatesCollection); + this._visibleDates, + this._rowCount, + this._cellStyle, + this._selectionTextStyle, + this._rangeTextStyle, + this._selectionColor, + this._startRangeSelectionColor, + this._endRangeSelectionColor, + this._rangeSelectionColor, + this._datePickerTheme, + this._isRtl, + this._todayHighlightColor, + this._minDate, + this._maxDate, + this._enablePastDates, + this._showLeadingAndTailingDates, + this._blackoutDates, + this._specialDates, + this._weekendDays, + this._selectionShape, + this._selectionRadius, + this._mouseHoverPosition, + this._enableMultiView, + this._multiViewSpacing, + this.selectionNotifier, + this._textScaleFactor, + this._height, + this._width, + this._isHijri, + this._navigationDirection, + this.localizations, + this.isMobilePlatform, + this._weekNumberStyle, + this._weekNumberPanelWidth, + this._disableDatesCollection, + ); bool isMobilePlatform; @@ -2218,8 +2288,9 @@ abstract class _IMonthView extends RenderBox /// Used to draw month cell text in month view. final TextPainter _textPainter = TextPainter( - textDirection: TextDirection.ltr, - textWidthBasis: TextWidthBasis.longestLine); + textDirection: TextDirection.ltr, + textWidthBasis: TextWidthBasis.longestLine, + ); static const int _selectionPadding = 2; @@ -2258,8 +2329,10 @@ abstract class _IMonthView extends RenderBox @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; if (child == null) { return; @@ -2280,11 +2353,14 @@ abstract class _IMonthView extends RenderBox final double cellWidth = currentWidth / DateTime.daysPerWeek; final double cellHeight = currentHeight / rowCount; while (child != null) { - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: cellHeight, maxHeight: cellHeight, minWidth: cellWidth, - maxWidth: cellWidth)); + maxWidth: cellWidth, + ), + ); child = childAfter(child); } } @@ -2302,8 +2378,14 @@ abstract class _IMonthView extends RenderBox /// Draw the highlight for selected month cell based on it selection mode /// value(date, dates, range, ranges). - TextStyle drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle); + TextStyle drawSelection( + Canvas canvas, + double x, + double y, + int index, + TextStyle selectionTextStyle, + TextStyle selectionRangeTextStyle, + ); /// Draw the highlight for selected month cell based on it selection mode /// value(date, dates, range, ranges) when the month cell have custom widget. @@ -2329,9 +2411,10 @@ abstract class _IMonthView extends RenderBox 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 SemanticsNode newChild = + _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); @@ -2383,9 +2466,11 @@ abstract class _IMonthView extends RenderBox double width = size.width - weekNumberPanelWidth; double height = size.height; int viewCount = 1; - final bool isHorizontalMultiView = _enableMultiView && + final bool isHorizontalMultiView = + _enableMultiView && _navigationDirection == DateRangePickerNavigationDirection.horizontal; - final bool isVerticalMultiView = _enableMultiView && + final bool isVerticalMultiView = + _enableMultiView && _navigationDirection == DateRangePickerNavigationDirection.vertical; if (isHorizontalMultiView) { @@ -2405,28 +2490,32 @@ abstract class _IMonthView extends RenderBox for (int j = 0; j < viewCount; j++) { final int currentViewIndex = _isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; - left = _isRtl - ? width - cellWidth - weekNumberPanelWidth - : weekNumberPanelWidth; + left = + _isRtl + ? width - cellWidth - weekNumberPanelWidth + : weekNumberPanelWidth; top = 0; final dynamic middleDate = _visibleDates[(j * datesCount) + (datesCount ~/ 2)]; - double viewXStartPosition = isVerticalMultiView - ? weekNumberPanelWidth - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * (currentViewIndex + 1)); + double viewXStartPosition = + isVerticalMultiView + ? weekNumberPanelWidth + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * (currentViewIndex + 1)); if (isRtl) { - viewXStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * currentViewIndex); + viewXStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * currentViewIndex); } - final double viewYStartPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + - (currentViewIndex * _multiViewSpacing); + final double viewYStartPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + + (currentViewIndex * _multiViewSpacing); for (int i = 0; i < datesCount; i++) { final dynamic currentDate = _visibleDates[(j * datesCount) + i]; @@ -2436,71 +2525,139 @@ abstract class _IMonthView extends RenderBox double xPosition = _isRtl ? (size.width - left - cellWidth) : 0; if (isHorizontalMultiView || isVerticalMultiView) { xPosition = isRtl ? size.width - weekNumberPanelWidth : 0; - xPosition = isHorizontalMultiView - ? viewXStartPosition - weekNumberPanelWidth - : xPosition; + xPosition = + isHorizontalMultiView + ? viewXStartPosition - weekNumberPanelWidth + : xPosition; } - final int weekNumber = - DateRangePickerHelper.getWeekNumberOfYear(currentDate, isHijri); - semanticsBuilder.add(CustomPainterSemantics( + final int weekNumber = DateRangePickerHelper.getWeekNumberOfYear( + currentDate, + isHijri, + ); + semanticsBuilder.add( + CustomPainterSemantics( rect: Rect.fromLTWH( - xPosition, top, weekNumberPanelWidth, cellHeight), + xPosition, + top, + weekNumberPanelWidth, + cellHeight, + ), properties: SemanticsProperties( label: 'week$weekNumber', textDirection: TextDirection.ltr, - ))); + ), + ), + ); } - if (!DateRangePickerHelper.isDateAsCurrentMonthDate(middleDate, - _rowCount, _showLeadingAndTailingDates, currentDate, _isHijri)) { + if (!DateRangePickerHelper.isDateAsCurrentMonthDate( + middleDate, + _rowCount, + _showLeadingAndTailingDates, + currentDate, + _isHijri, + )) { leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - _isRtl, left, top, cellWidth, cellHeight, width); + _isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; continue; } else if (DateRangePickerHelper.isDateWithInVisibleDates( - _visibleDates, _blackoutDates, currentDate)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(viewXStartPosition + left, - viewYStartPosition + top, cellWidth, cellHeight), - properties: SemanticsProperties( - label: '${_getSemanticMonthLabel(currentDate)}, Blackout date', - textDirection: TextDirection.ltr, + _visibleDates, + _blackoutDates, + currentDate, + )) { + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + viewXStartPosition + left, + viewYStartPosition + top, + cellWidth, + cellHeight, + ), + properties: SemanticsProperties( + label: '${_getSemanticMonthLabel(currentDate)}, Blackout date', + textDirection: TextDirection.ltr, + ), ), - )); + ); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - _isRtl, left, top, cellWidth, cellHeight, width); + _isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; continue; } else if (!DateRangePickerHelper.isEnabledDate( - _minDate, _maxDate, _enablePastDates, currentDate, _isHijri) || + _minDate, + _maxDate, + _enablePastDates, + currentDate, + _isHijri, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - visibleDates, disableDatesCollection, currentDate)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(viewXStartPosition + left, - viewYStartPosition + top, cellWidth, cellHeight), - properties: SemanticsProperties( - label: '${_getSemanticMonthLabel(currentDate)}, Disabled date', - textDirection: TextDirection.ltr, + visibleDates, + disableDatesCollection, + currentDate, + )) { + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + viewXStartPosition + left, + viewYStartPosition + top, + cellWidth, + cellHeight, + ), + properties: SemanticsProperties( + label: '${_getSemanticMonthLabel(currentDate)}, Disabled date', + textDirection: TextDirection.ltr, + ), ), - )); + ); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - _isRtl, left, top, cellWidth, cellHeight, width); + _isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; continue; } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(viewXStartPosition + left, - viewYStartPosition + top, cellWidth, cellHeight), - properties: SemanticsProperties( - label: _getSemanticMonthLabel(currentDate), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + viewXStartPosition + left, + viewYStartPosition + top, + cellWidth, + cellHeight, + ), + properties: SemanticsProperties( + label: _getSemanticMonthLabel(currentDate), + textDirection: TextDirection.ltr, + ), ), - )); + ); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - _isRtl, left, top, cellWidth, cellHeight, width); + _isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; } @@ -2523,78 +2680,79 @@ abstract class _IMonthView extends RenderBox class _MonthViewSingleSelectionRenderObject extends _IMonthView { _MonthViewSingleSelectionRenderObject( - List visibleDates, - int rowCount, - dynamic cellStyle, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - SfDateRangePickerThemeData datePickerTheme, - bool isRtl, - Color? todayHighlightColor, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - bool showLeadingAndTailingDates, - List? blackoutDates, - List? specialDates, - List weekendDays, - DateRangePickerSelectionShape selectionShape, - double selectionRadius, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - ValueNotifier selectionNotifier, - double textScaleFactor, - double height, - double width, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedDate, - bool isMobilePlatform, - DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth, - List? disableDatesCollection) - : super( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + List visibleDates, + int rowCount, + dynamic cellStyle, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + SfDateRangePickerThemeData datePickerTheme, + bool isRtl, + Color? todayHighlightColor, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + bool showLeadingAndTailingDates, + List? blackoutDates, + List? specialDates, + List weekendDays, + DateRangePickerSelectionShape selectionShape, + double selectionRadius, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + ValueNotifier selectionNotifier, + double textScaleFactor, + double height, + double width, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedDate, + bool isMobilePlatform, + DateRangePickerWeekNumberStyle weekNumberStyle, + double weekNumberPanelWidth, + List? disableDatesCollection, + ) : super( + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); dynamic _selectedDate; @@ -2623,7 +2781,7 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { { _cellWidth = (size.width - _multiViewSpacing - (weekNumberPanelWidth * 2)) / - (DateTime.daysPerWeek * 2); + (DateTime.daysPerWeek * 2); } break; case DateRangePickerNavigationDirection.vertical: @@ -2639,22 +2797,42 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { } @override - TextStyle drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { + TextStyle drawSelection( + Canvas canvas, + double x, + double y, + int index, + TextStyle selectionTextStyle, + TextStyle selectionRangeTextStyle, + ) { _selectionPainter.isAntiAlias = true; switch (selectionShape) { case DateRangePickerSelectionShape.circle: { final double radius = _getCellRadius( - selectionRadius, _centerXPosition, _centerYPosition); - _drawCircleSelection(canvas, x + _centerXPosition, - y + _centerYPosition, radius, _selectionPainter); + selectionRadius, + _centerXPosition, + _centerYPosition, + ); + _drawCircleSelection( + canvas, + x + _centerXPosition, + y + _centerYPosition, + radius, + _selectionPainter, + ); } break; case DateRangePickerSelectionShape.rectangle: { _drawFillSelection( - canvas, x, y, _cellWidth, _cellHeight, _selectionPainter); + canvas, + x, + y, + _cellWidth, + _cellHeight, + _selectionPainter, + ); } } @@ -2667,18 +2845,26 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; - canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), - _selectionPainter); + canvas.drawRect( + Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter, + ); } @override List getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { final List selectedIndex = []; if (selectedDate != null) { - if (isDateWithInDateRange(visibleDates[viewStartIndex], - visibleDates[viewEndIndex], selectedDate)) { - final int index = _getSelectedIndex(selectedDate, visibleDates, - viewStartIndex: viewStartIndex); + if (isDateWithInDateRange( + visibleDates[viewStartIndex], + visibleDates[viewEndIndex], + selectedDate, + )) { + final int index = _getSelectedIndex( + selectedDate, + visibleDates, + viewStartIndex: viewStartIndex, + ); selectedIndex.add(index); } } @@ -2704,78 +2890,79 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { class _MonthViewMultiSelectionRenderObject extends _IMonthView { _MonthViewMultiSelectionRenderObject( - List visibleDates, - int rowCount, - dynamic cellStyle, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - SfDateRangePickerThemeData datePickerTheme, - bool isRtl, - Color? todayHighlightColor, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - bool showLeadingAndTailingDates, - List? blackoutDates, - List? specialDates, - List weekendDays, - DateRangePickerSelectionShape selectionShape, - double selectionRadius, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - ValueNotifier selectionNotifier, - double textScaleFactor, - double height, - double width, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedDates, - bool isMobilePlatform, - DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth, - List? disableDatesCollection) - : super( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + List visibleDates, + int rowCount, + dynamic cellStyle, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + SfDateRangePickerThemeData datePickerTheme, + bool isRtl, + Color? todayHighlightColor, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + bool showLeadingAndTailingDates, + List? blackoutDates, + List? specialDates, + List weekendDays, + DateRangePickerSelectionShape selectionShape, + double selectionRadius, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + ValueNotifier selectionNotifier, + double textScaleFactor, + double height, + double width, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedDates, + bool isMobilePlatform, + DateRangePickerWeekNumberStyle weekNumberStyle, + double weekNumberPanelWidth, + List? disableDatesCollection, + ) : super( + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); List? _selectedDates; @@ -2795,22 +2982,42 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { } @override - TextStyle drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { + TextStyle drawSelection( + Canvas canvas, + double x, + double y, + int index, + TextStyle selectionTextStyle, + TextStyle selectionRangeTextStyle, + ) { _selectionPainter.isAntiAlias = true; switch (selectionShape) { case DateRangePickerSelectionShape.circle: { final double radius = _getCellRadius( - selectionRadius, _centerXPosition, _centerYPosition); - _drawCircleSelection(canvas, x + _centerXPosition, - y + _centerYPosition, radius, _selectionPainter); + selectionRadius, + _centerXPosition, + _centerYPosition, + ); + _drawCircleSelection( + canvas, + x + _centerXPosition, + y + _centerYPosition, + radius, + _selectionPainter, + ); } break; case DateRangePickerSelectionShape.rectangle: { _drawFillSelection( - canvas, x, y, _cellWidth, _cellHeight, _selectionPainter); + canvas, + x, + y, + _cellWidth, + _cellHeight, + _selectionPainter, + ); } } @@ -2823,8 +3030,10 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; - canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), - _selectionPainter); + canvas.drawRect( + Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter, + ); } @override @@ -2834,12 +3043,16 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { for (int j = 0; j < selectedDates!.length; j++) { final dynamic date = selectedDates![j]; if (!isDateWithInDateRange( - visibleDates[viewStartIndex], visibleDates[viewEndIndex], date)) { + visibleDates[viewStartIndex], + visibleDates[viewEndIndex], + date, + )) { continue; } - selectedIndex.add(_getSelectedIndex(date, visibleDates, - viewStartIndex: viewStartIndex)); + selectedIndex.add( + _getSelectedIndex(date, visibleDates, viewStartIndex: viewStartIndex), + ); } } @@ -2856,7 +3069,7 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { { _cellWidth = (size.width - _multiViewSpacing - (weekNumberPanelWidth * 2)) / - (DateTime.daysPerWeek * 2); + (DateTime.daysPerWeek * 2); } break; case DateRangePickerNavigationDirection.vertical: @@ -2874,7 +3087,9 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { @override void updateSelection(PickerStateArgs details) { if (DateRangePickerHelper.isDateCollectionEquals( - details.selectedDates, selectedDates)) { + details.selectedDates, + selectedDates, + )) { return; } @@ -2890,78 +3105,79 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { class _MonthViewRangeSelectionRenderObject extends _IMonthView { _MonthViewRangeSelectionRenderObject( - List visibleDates, - int rowCount, - dynamic cellStyle, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - SfDateRangePickerThemeData datePickerTheme, - bool isRtl, - Color? todayHighlightColor, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - bool showLeadingAndTailingDates, - List? blackoutDates, - List? specialDates, - List weekendDays, - DateRangePickerSelectionShape selectionShape, - double selectionRadius, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - ValueNotifier selectionNotifier, - double textScaleFactor, - double height, - double width, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedRange, - bool isMobilePlatform, - DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth, - List? disableDatesCollection) - : super( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + List visibleDates, + int rowCount, + dynamic cellStyle, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + SfDateRangePickerThemeData datePickerTheme, + bool isRtl, + Color? todayHighlightColor, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + bool showLeadingAndTailingDates, + List? blackoutDates, + List? specialDates, + List weekendDays, + DateRangePickerSelectionShape selectionShape, + double selectionRadius, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + ValueNotifier selectionNotifier, + double textScaleFactor, + double height, + double width, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedRange, + bool isMobilePlatform, + DateRangePickerWeekNumberStyle weekNumberStyle, + double weekNumberPanelWidth, + List? disableDatesCollection, + ) : super( + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); dynamic _selectedRange; @@ -2983,55 +3199,84 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { List _selectedIndex = []; @override - TextStyle drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { + TextStyle drawSelection( + Canvas canvas, + double x, + double y, + int index, + TextStyle selectionTextStyle, + TextStyle selectionRangeTextStyle, + ) { final List selectionDetails = _getSelectedRangePosition(index); final bool isSelectedDate = selectionDetails[0]; final bool isStartRange = selectionDetails[1]; final bool isEndRange = selectionDetails[2]; final bool isBetweenRange = selectionDetails[3]; - final double radius = - _getCellRadius(selectionRadius, _centerXPosition, _centerYPosition); + final double radius = _getCellRadius( + selectionRadius, + _centerXPosition, + _centerYPosition, + ); final double heightDifference = _cellHeight / 2 - radius; if (isSelectedDate) { - _drawSelectedDate(canvas, radius, _centerXPosition, _cellWidth, - _cellHeight, x, y, this, _centerYPosition); + _drawSelectedDate( + canvas, + radius, + _centerXPosition, + _cellWidth, + _cellHeight, + x, + y, + this, + _centerYPosition, + ); } else if (isStartRange) { _selectionPainter.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( - canvas, - this, - _cellHeight, - _cellWidth, - radius, - _centerXPosition, - _centerYPosition, - x, - y, - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, - heightDifference, - isStartRange); + canvas, + this, + _cellHeight, + _cellWidth, + radius, + _centerXPosition, + _centerYPosition, + x, + y, + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, + heightDifference, + isStartRange, + ); } else if (isEndRange) { _selectionPainter.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( - canvas, - this, - _cellHeight, - _cellWidth, - radius, - _centerXPosition, - _centerYPosition, - x, - y, - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, - heightDifference, - isStartRange); + canvas, + this, + _cellHeight, + _cellWidth, + radius, + _centerXPosition, + _centerYPosition, + x, + y, + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, + heightDifference, + isStartRange, + ); } else if (isBetweenRange) { - return _drawBetweenSelection(canvas, this, _cellWidth, _cellHeight, - radius, x, y, heightDifference, selectionRangeTextStyle); + return _drawBetweenSelection( + canvas, + this, + _cellWidth, + _cellHeight, + radius, + x, + y, + heightDifference, + selectionRangeTextStyle, + ); } return selectionTextStyle; @@ -3061,8 +3306,10 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } - canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), - _selectionPainter); + canvas.drawRect( + Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter, + ); } List _getSelectedRangePosition(int index) { @@ -3112,8 +3359,13 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { final dynamic startDate = selectedRange.startDate; final dynamic endDate = selectedRange.endDate ?? selectedRange.startDate; _selectedIndex = _getSelectedRangeIndex( - startDate, endDate, visibleDates, isHijri, - monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); + startDate, + endDate, + visibleDates, + isHijri, + monthStartIndex: viewStartIndex, + monthEndIndex: viewEndIndex, + ); } return _selectedIndex; @@ -3129,7 +3381,7 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { { _cellWidth = (size.width - _multiViewSpacing - (weekNumberPanelWidth * 2)) / - (DateTime.daysPerWeek * 2); + (DateTime.daysPerWeek * 2); } break; case DateRangePickerNavigationDirection.vertical: @@ -3147,7 +3399,9 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { @override void updateSelection(PickerStateArgs details) { if (DateRangePickerHelper.isRangeEquals( - details.selectedRange, selectedRange)) { + details.selectedRange, + selectedRange, + )) { return; } @@ -3168,79 +3422,80 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { _MonthViewExtendableRangeSelectionRenderObject( - List visibleDates, - int rowCount, - dynamic cellStyle, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - SfDateRangePickerThemeData datePickerTheme, - bool isRtl, - Color? todayHighlightColor, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - bool showLeadingAndTailingDates, - List? blackoutDates, - List? specialDates, - List weekendDays, - DateRangePickerSelectionShape selectionShape, - double selectionRadius, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - ValueNotifier selectionNotifier, - double textScaleFactor, - double height, - double width, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedRange, - bool isMobilePlatform, - DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth, - List? disableDatesCollection, - this._extendableRangeSelectionDirection) - : super( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + List visibleDates, + int rowCount, + dynamic cellStyle, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + SfDateRangePickerThemeData datePickerTheme, + bool isRtl, + Color? todayHighlightColor, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + bool showLeadingAndTailingDates, + List? blackoutDates, + List? specialDates, + List weekendDays, + DateRangePickerSelectionShape selectionShape, + double selectionRadius, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + ValueNotifier selectionNotifier, + double textScaleFactor, + double height, + double width, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedRange, + bool isMobilePlatform, + DateRangePickerWeekNumberStyle weekNumberStyle, + double weekNumberPanelWidth, + List? disableDatesCollection, + this._extendableRangeSelectionDirection, + ) : super( + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); dynamic _selectedRange; @@ -3265,7 +3520,8 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { _extendableRangeSelectionDirection; set extendableRangeSelectionDirection( - ExtendableRangeSelectionDirection value) { + ExtendableRangeSelectionDirection value, + ) { if (_extendableRangeSelectionDirection == value) { return; } @@ -3281,56 +3537,87 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { List _selectedIndex = []; @override - TextStyle drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { - final List selectionDetails = - _getSelectedRangePosition(index, _selectedIndex); + TextStyle drawSelection( + Canvas canvas, + double x, + double y, + int index, + TextStyle selectionTextStyle, + TextStyle selectionRangeTextStyle, + ) { + final List selectionDetails = _getSelectedRangePosition( + index, + _selectedIndex, + ); final bool isSelectedDate = selectionDetails[0]; final bool isStartRange = selectionDetails[1]; final bool isEndRange = selectionDetails[2]; final bool isBetweenRange = selectionDetails[3]; - final double radius = - _getCellRadius(selectionRadius, _centerXPosition, _centerYPosition); + final double radius = _getCellRadius( + selectionRadius, + _centerXPosition, + _centerYPosition, + ); final double heightDifference = _cellHeight / 2 - radius; if (isSelectedDate) { - _drawSelectedDate(canvas, radius, _centerXPosition, _cellWidth, - _cellHeight, x, y, this, _centerYPosition); + _drawSelectedDate( + canvas, + radius, + _centerXPosition, + _cellWidth, + _cellHeight, + x, + y, + this, + _centerYPosition, + ); } else if (isStartRange) { - _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; - _drawStartAndEndRange( - canvas, - this, - _cellHeight, - _cellWidth, - radius, - _centerXPosition, - _centerYPosition, - x, - y, - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, - heightDifference, - isStartRange); + _selectionPainter.color = + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; + _drawStartAndEndRange( + canvas, + this, + _cellHeight, + _cellWidth, + radius, + _centerXPosition, + _centerYPosition, + x, + y, + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, + heightDifference, + isStartRange, + ); } else if (isEndRange) { _selectionPainter.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( - canvas, - this, - _cellHeight, - _cellWidth, - radius, - _centerXPosition, - _centerYPosition, - x, - y, - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, - heightDifference, - isStartRange); + canvas, + this, + _cellHeight, + _cellWidth, + radius, + _centerXPosition, + _centerYPosition, + x, + y, + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, + heightDifference, + isStartRange, + ); } else if (isBetweenRange) { - return _drawBetweenSelection(canvas, this, _cellWidth, _cellHeight, - radius, x, y, heightDifference, selectionRangeTextStyle); + return _drawBetweenSelection( + canvas, + this, + _cellWidth, + _cellHeight, + radius, + x, + y, + heightDifference, + selectionRangeTextStyle, + ); } return selectionTextStyle; @@ -3341,8 +3628,10 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; - final List selectionDetails = - _getSelectedRangePosition(index, _selectedIndex); + final List selectionDetails = _getSelectedRangePosition( + index, + _selectedIndex, + ); final bool isSelectedDate = selectionDetails[0]; final bool isStartRange = selectionDetails[1]; final bool isEndRange = selectionDetails[2]; @@ -3361,8 +3650,10 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } - canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), - _selectionPainter); + canvas.drawRect( + Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter, + ); } List _getSelectedRangePosition(int index, List indexList) { @@ -3412,8 +3703,13 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { final dynamic startDate = selectedRange.startDate; final dynamic endDate = selectedRange.endDate ?? selectedRange.startDate; _selectedIndex = _getSelectedRangeIndex( - startDate, endDate, visibleDates, isHijri, - monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); + startDate, + endDate, + visibleDates, + isHijri, + monthStartIndex: viewStartIndex, + monthEndIndex: viewEndIndex, + ); } return _selectedIndex; @@ -3429,7 +3725,7 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { { _cellWidth = (size.width - _multiViewSpacing - (weekNumberPanelWidth * 2)) / - (DateTime.daysPerWeek * 2); + (DateTime.daysPerWeek * 2); } break; case DateRangePickerNavigationDirection.vertical: @@ -3447,7 +3743,9 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { @override void updateSelection(PickerStateArgs details) { if (DateRangePickerHelper.isRangeEquals( - details.selectedRange, selectedRange)) { + details.selectedRange, + selectedRange, + )) { return; } @@ -3461,86 +3759,89 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { /// index is either it is start or end range or selected date and is between /// range as a list, in this list the isBetweenRange boolean index is 3, /// hence we used 3 here. - final List selectionDetails = - _getSelectedRangePosition(index, _selectedIndex); + final List selectionDetails = _getSelectedRangePosition( + index, + _selectedIndex, + ); return selectionDetails[3]; } } class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { _MonthViewMultiRangeSelectionRenderObject( - List visibleDates, - int rowCount, - dynamic cellStyle, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - SfDateRangePickerThemeData datePickerTheme, - bool isRtl, - Color? todayHighlightColor, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - bool showLeadingAndTailingDates, - List? blackoutDates, - List? specialDates, - List weekendDays, - DateRangePickerSelectionShape selectionShape, - double selectionRadius, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - ValueNotifier selectionNotifier, - double textScaleFactor, - double height, - double width, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedRanges, - bool isMobilePlatform, - DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth, - List? disableDatesCollection) - : super( - visibleDates, - rowCount, - cellStyle, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - datePickerTheme, - isRtl, - todayHighlightColor, - minDate, - maxDate, - enablePastDates, - showLeadingAndTailingDates, - blackoutDates, - specialDates, - weekendDays, - selectionShape, - selectionRadius, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionNotifier, - textScaleFactor, - height, - width, - isHijri, - navigationDirection, - localizations, - isMobilePlatform, - weekNumberStyle, - weekNumberPanelWidth, - disableDatesCollection); + List visibleDates, + int rowCount, + dynamic cellStyle, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + SfDateRangePickerThemeData datePickerTheme, + bool isRtl, + Color? todayHighlightColor, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + bool showLeadingAndTailingDates, + List? blackoutDates, + List? specialDates, + List weekendDays, + DateRangePickerSelectionShape selectionShape, + double selectionRadius, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + ValueNotifier selectionNotifier, + double textScaleFactor, + double height, + double width, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedRanges, + bool isMobilePlatform, + DateRangePickerWeekNumberStyle weekNumberStyle, + double weekNumberPanelWidth, + List? disableDatesCollection, + ) : super( + visibleDates, + rowCount, + cellStyle, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + datePickerTheme, + isRtl, + todayHighlightColor, + minDate, + maxDate, + enablePastDates, + showLeadingAndTailingDates, + blackoutDates, + specialDates, + weekendDays, + selectionShape, + selectionRadius, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionNotifier, + textScaleFactor, + height, + width, + isHijri, + navigationDirection, + localizations, + isMobilePlatform, + weekNumberStyle, + weekNumberPanelWidth, + disableDatesCollection, + ); List? _selectedRanges; @@ -3562,55 +3863,84 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { List> _selectedRangesIndex = >[]; @override - TextStyle drawSelection(Canvas canvas, double x, double y, int index, - TextStyle selectionTextStyle, TextStyle selectionRangeTextStyle) { + TextStyle drawSelection( + Canvas canvas, + double x, + double y, + int index, + TextStyle selectionTextStyle, + TextStyle selectionRangeTextStyle, + ) { final List selectionDetails = _getSelectedRangePosition(index); final bool isSelectedDate = selectionDetails[0]; final bool isStartRange = selectionDetails[1]; final bool isEndRange = selectionDetails[2]; final bool isBetweenRange = selectionDetails[3]; - final double radius = - _getCellRadius(selectionRadius, _centerXPosition, _centerYPosition); + final double radius = _getCellRadius( + selectionRadius, + _centerXPosition, + _centerYPosition, + ); final double heightDifference = _cellHeight / 2 - radius; if (isSelectedDate) { - _drawSelectedDate(canvas, radius, _centerXPosition, _cellWidth, - _cellHeight, x, y, this, _centerYPosition); + _drawSelectedDate( + canvas, + radius, + _centerXPosition, + _cellWidth, + _cellHeight, + x, + y, + this, + _centerYPosition, + ); } else if (isStartRange) { _selectionPainter.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( - canvas, - this, - _cellHeight, - _cellWidth, - radius, - _centerXPosition, - _centerYPosition, - x, - y, - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, - heightDifference, - isStartRange); + canvas, + this, + _cellHeight, + _cellWidth, + radius, + _centerXPosition, + _centerYPosition, + x, + y, + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, + heightDifference, + isStartRange, + ); } else if (isEndRange) { _selectionPainter.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( - canvas, - this, - _cellHeight, - _cellWidth, - radius, - _centerXPosition, - _centerYPosition, - x, - y, - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, - heightDifference, - isStartRange); + canvas, + this, + _cellHeight, + _cellWidth, + radius, + _centerXPosition, + _centerYPosition, + x, + y, + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, + heightDifference, + isStartRange, + ); } else if (isBetweenRange) { - return _drawBetweenSelection(canvas, this, _cellWidth, _cellHeight, - radius, x, y, heightDifference, selectionRangeTextStyle); + return _drawBetweenSelection( + canvas, + this, + _cellWidth, + _cellHeight, + radius, + x, + y, + heightDifference, + selectionRangeTextStyle, + ); } return selectionTextStyle; @@ -3640,8 +3970,10 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } - canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), - _selectionPainter); + canvas.drawRect( + Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), + _selectionPainter, + ); } List _getSelectedRangePosition(int index) { @@ -3703,8 +4035,13 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { final dynamic startDate = range.startDate; final dynamic endDate = range.endDate ?? range.startDate; final List rangeIndex = _getSelectedRangeIndex( - startDate, endDate, visibleDates, isHijri, - monthStartIndex: viewStartIndex, monthEndIndex: viewEndIndex); + startDate, + endDate, + visibleDates, + isHijri, + monthStartIndex: viewStartIndex, + monthEndIndex: viewEndIndex, + ); for (int i = 0; i < rangeIndex.length; i++) { selectedIndex.add(rangeIndex[i]); } @@ -3726,7 +4063,7 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { { _cellWidth = (size.width - _multiViewSpacing - (weekNumberPanelWidth * 2)) / - (DateTime.daysPerWeek * 2); + (DateTime.daysPerWeek * 2); } break; case DateRangePickerNavigationDirection.vertical: @@ -3744,7 +4081,9 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { @override void updateSelection(PickerStateArgs details) { if (DateRangePickerHelper.isDateRangesEquals( - details.selectedRanges, selectedRanges)) { + details.selectedRanges, + selectedRanges, + )) { return; } @@ -3764,60 +4103,89 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { } void _drawSelectedDate( - Canvas canvas, - double radius, - double centerXPosition, - double cellWidth, - double cellHeight, - double x, - double y, - _IMonthView view, - double centerYPosition) { + Canvas canvas, + double radius, + double centerXPosition, + double cellWidth, + double cellHeight, + double x, + double y, + _IMonthView view, + double centerYPosition, +) { view._selectionPainter.isAntiAlias = true; - view._selectionPainter.color = view.startRangeSelectionColor ?? + view._selectionPainter.color = + view.startRangeSelectionColor ?? view.datePickerTheme.startRangeSelectionColor!; switch (view.selectionShape) { case DateRangePickerSelectionShape.circle: { - _drawCircleSelection(canvas, x + centerXPosition, y + centerYPosition, - radius, view._selectionPainter); + _drawCircleSelection( + canvas, + x + centerXPosition, + y + centerYPosition, + radius, + view._selectionPainter, + ); } break; case DateRangePickerSelectionShape.rectangle: { _drawFillSelection( - canvas, x, y, cellWidth, cellHeight, view._selectionPainter); + canvas, + x, + y, + cellWidth, + cellHeight, + view._selectionPainter, + ); } } } void _drawStartAndEndRange( - Canvas canvas, - _IMonthView view, - double cellHeight, - double cellWidth, - double radius, - double centerXPosition, - double centerYPosition, - double x, - double y, - Color color, - double heightDifference, - bool isStartRange) { + Canvas canvas, + _IMonthView view, + double cellHeight, + double cellWidth, + double radius, + double centerXPosition, + double centerYPosition, + double x, + double y, + Color color, + double heightDifference, + bool isStartRange, +) { switch (view.selectionShape) { case DateRangePickerSelectionShape.circle: { Rect rect; if (isStartRange) { - rect = Rect.fromLTRB(x + centerXPosition, y + heightDifference, - x + cellWidth, y + cellHeight - heightDifference); + rect = Rect.fromLTRB( + x + centerXPosition, + y + heightDifference, + x + cellWidth, + y + cellHeight - heightDifference, + ); } else { - rect = Rect.fromLTRB(x, y + heightDifference, x + centerXPosition, - y + cellHeight - heightDifference); + rect = Rect.fromLTRB( + x, + y + heightDifference, + x + centerXPosition, + y + cellHeight - heightDifference, + ); } - _drawStartEndRangeCircleSelection(canvas, x + centerXPosition, - y + centerYPosition, radius, rect, view._selectionPainter, color); + _drawStartEndRangeCircleSelection( + canvas, + x + centerXPosition, + y + centerYPosition, + radius, + rect, + view._selectionPainter, + color, + ); } break; case DateRangePickerSelectionShape.rectangle: @@ -3826,25 +4194,38 @@ void _drawStartAndEndRange( view._selectionPainter.color = color; if (isStartRange) { _drawStartRangeFillSelection( - canvas, x, y, cellWidth, cellHeight, view._selectionPainter); + canvas, + x, + y, + cellWidth, + cellHeight, + view._selectionPainter, + ); } else { _drawEndRangeFillSelection( - canvas, x, y, cellWidth, cellHeight, view._selectionPainter); + canvas, + x, + y, + cellWidth, + cellHeight, + view._selectionPainter, + ); } } } } TextStyle _drawBetweenSelection( - Canvas canvas, - _IMonthView view, - double cellWidth, - double cellHeight, - double radius, - double x, - double y, - double heightDifference, - TextStyle selectionRangeTextStyle) { + Canvas canvas, + _IMonthView view, + double cellWidth, + double cellHeight, + double radius, + double x, + double y, + double heightDifference, + TextStyle selectionRangeTextStyle, +) { switch (view.selectionShape) { case DateRangePickerSelectionShape.rectangle: heightDifference = 1; @@ -3855,16 +4236,26 @@ TextStyle _drawBetweenSelection( view._selectionPainter.color = view.rangeSelectionColor ?? view.datePickerTheme.rangeSelectionColor!; - _drawRectRangeSelection(canvas, x, y + heightDifference, x + cellWidth, - y + cellHeight - heightDifference, view._selectionPainter); + _drawRectRangeSelection( + canvas, + x, + y + heightDifference, + x + cellWidth, + y + cellHeight - heightDifference, + view._selectionPainter, + ); return selectionRangeTextStyle; } double _getCellRadius( - double selectionRadius, double maxXRadius, double maxYRadius) { - final double radius = maxXRadius > maxYRadius - ? maxYRadius - _IMonthView._selectionPadding - : maxXRadius - _IMonthView._selectionPadding; + double selectionRadius, + double maxXRadius, + double maxYRadius, +) { + final double radius = + maxXRadius > maxYRadius + ? maxYRadius - _IMonthView._selectionPadding + : maxXRadius - _IMonthView._selectionPadding; if (selectionRadius == -1) { return radius; @@ -3873,9 +4264,14 @@ double _getCellRadius( return radius > selectionRadius ? selectionRadius : radius; } -List _getSelectedRangeIndex(dynamic startDate, dynamic endDate, - List visibleDates, bool isHijri, - {int monthStartIndex = -1, int monthEndIndex = -1}) { +List _getSelectedRangeIndex( + dynamic startDate, + dynamic endDate, + List visibleDates, + bool isHijri, { + int monthStartIndex = -1, + int monthEndIndex = -1, +}) { int startIndex = -1; int endIndex = -1; final List selectedIndex = []; @@ -3886,7 +4282,11 @@ List _getSelectedRangeIndex(dynamic startDate, dynamic endDate, dynamic endRangeDate; if (endDate != null) { endRangeDate = DateRangePickerHelper.getDate( - endDate.year, endDate.month, endDate.day, isHijri); + endDate.year, + endDate.month, + endDate.day, + isHijri, + ); } if (startDate != null && startDate.isAfter(endRangeDate) == true) { final dynamic temp = startDate; @@ -3896,16 +4296,20 @@ List _getSelectedRangeIndex(dynamic startDate, dynamic endDate, final dynamic viewStartDate = monthStartIndex != -1 ? visibleDates[monthStartIndex] : visibleDates[0]; - final dynamic viewEndDate = monthEndIndex != -1 - ? visibleDates[monthEndIndex] - : visibleDates[visibleDates.length - 1]; + final dynamic viewEndDate = + monthEndIndex != -1 + ? visibleDates[monthEndIndex] + : visibleDates[visibleDates.length - 1]; if (startDate != null) { if (viewStartDate.isAfter(startDate) == true && viewStartDate.isBefore(endRangeDate) == true) { startIndex = -1; } else { - startIndex = _getSelectedIndex(startDate, visibleDates, - viewStartIndex: monthStartIndex); + startIndex = _getSelectedIndex( + startDate, + visibleDates, + viewStartIndex: monthStartIndex, + ); } } @@ -3914,8 +4318,11 @@ List _getSelectedRangeIndex(dynamic startDate, dynamic endDate, viewEndDate.isBefore(endRangeDate) == true) { endIndex = visibleDates.length; } else { - endIndex = _getSelectedIndex(endRangeDate, visibleDates, - viewStartIndex: monthStartIndex); + endIndex = _getSelectedIndex( + endRangeDate, + visibleDates, + viewStartIndex: monthStartIndex, + ); } } @@ -3937,8 +4344,11 @@ List _getSelectedRangeIndex(dynamic startDate, dynamic endDate, return selectedIndex; } -int _getSelectedIndex(dynamic date, List visibleDates, - {int viewStartIndex = 0}) { +int _getSelectedIndex( + dynamic date, + List visibleDates, { + int viewStartIndex = 0, +}) { if (viewStartIndex == -1) { viewStartIndex = 0; } @@ -3953,62 +4363,111 @@ int _getSelectedIndex(dynamic date, List visibleDates, } void _drawCircleSelection( - Canvas canvas, double x, double y, double radius, Paint selectionPainter) { + Canvas canvas, + double x, + double y, + double radius, + Paint selectionPainter, +) { canvas.drawCircle(Offset(x, y), radius, selectionPainter); } -void _drawFillSelection(Canvas canvas, double x, double y, double width, - double height, Paint selectionPainter) { +void _drawFillSelection( + Canvas canvas, + double x, + double y, + double width, + double height, + Paint selectionPainter, +) { const double padding = 1; canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB(x + padding, y + padding, x + width - padding, - y + height - padding), - Radius.circular(height / 4 > 10 ? 10 : height / 4)), - selectionPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + x + padding, + y + padding, + x + width - padding, + y + height - padding, + ), + Radius.circular(height / 4 > 10 ? 10 : height / 4), + ), + selectionPainter, + ); } -void _drawStartRangeFillSelection(Canvas canvas, double x, double y, - double width, double height, Paint selectionPainter) { +void _drawStartRangeFillSelection( + Canvas canvas, + double x, + double y, + double width, + double height, + Paint selectionPainter, +) { const double padding = 1; final double cornerRadius = height / 4 > 10 ? 10 : height / 4; canvas.drawRRect( - RRect.fromRectAndCorners( - Rect.fromLTRB( - x + padding, y + padding, x + width, y + height - padding), - bottomLeft: Radius.circular(cornerRadius), - topLeft: Radius.circular(cornerRadius)), - selectionPainter); + RRect.fromRectAndCorners( + Rect.fromLTRB(x + padding, y + padding, x + width, y + height - padding), + bottomLeft: Radius.circular(cornerRadius), + topLeft: Radius.circular(cornerRadius), + ), + selectionPainter, + ); } -void _drawEndRangeFillSelection(Canvas canvas, double x, double y, double width, - double height, Paint selectionPainter) { +void _drawEndRangeFillSelection( + Canvas canvas, + double x, + double y, + double width, + double height, + Paint selectionPainter, +) { const double padding = 1; final double cornerRadius = height / 4 > 10 ? 10 : height / 4; canvas.drawRRect( - RRect.fromRectAndCorners( - Rect.fromLTRB( - x, y + padding, x + width - padding, y + height - padding), - bottomRight: Radius.circular(cornerRadius), - topRight: Radius.circular(cornerRadius)), - selectionPainter); + RRect.fromRectAndCorners( + Rect.fromLTRB(x, y + padding, x + width - padding, y + height - padding), + bottomRight: Radius.circular(cornerRadius), + topRight: Radius.circular(cornerRadius), + ), + selectionPainter, + ); } -void _drawStartEndRangeCircleSelection(Canvas canvas, double x, double y, - double radius, Rect rect, Paint selectionPainter, Color color) { +void _drawStartEndRangeCircleSelection( + Canvas canvas, + double x, + double y, + double radius, + Rect rect, + Paint selectionPainter, + Color color, +) { canvas.drawRect(rect, selectionPainter); selectionPainter.isAntiAlias = true; selectionPainter.color = color; canvas.drawCircle(Offset(x, y), radius, selectionPainter); } -void _drawRectRangeSelection(Canvas canvas, double left, double top, - double right, double bottom, Paint selectionPainter) { +void _drawRectRangeSelection( + Canvas canvas, + double left, + double top, + double right, + double bottom, + Paint selectionPainter, +) { canvas.drawRect(Rect.fromLTRB(left, top, right, bottom), selectionPainter); } -void _drawMonthCellsAndSelection(PaintingContext context, Size size, - _IMonthView monthView, double cellWidth, double cellHeight) { +void _drawMonthCellsAndSelection( + PaintingContext context, + Size size, + _IMonthView monthView, + double cellWidth, + double cellHeight, +) { final double weekNumberPanelWidth = monthView.weekNumberPanelWidth; final Canvas canvas = context.canvas; double xPosition = monthView._isRtl ? 0 : weekNumberPanelWidth, yPosition; @@ -4016,10 +4475,12 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, double width = size.width - weekNumberPanelWidth; double height = size.height; int viewCount = 1; - final bool isHorizontalMultiView = monthView.enableMultiView && + final bool isHorizontalMultiView = + monthView.enableMultiView && monthView.navigationDirection == DateRangePickerNavigationDirection.horizontal; - final bool isVerticalMultiView = monthView.enableMultiView && + final bool isVerticalMultiView = + monthView.enableMultiView && monthView.navigationDirection == DateRangePickerNavigationDirection.vertical; @@ -4033,48 +4494,62 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, height = (height - webUIPadding) / viewCount; } - monthView._textPainter.textScaleFactor = monthView.textScaleFactor; - TextStyle textStyle = monthView.cellStyle.textStyle as TextStyle? ?? - monthView.datePickerTheme.activeDatesTextStyle!; + monthView._textPainter.textScaler = TextScaler.linear( + monthView.textScaleFactor, + ); + TextStyle 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; + monthView.isHijri; if (isNeedWidgetPaint) { RenderBox? child = monthView.firstChild; if (!isHorizontalMultiView) { _drawWeekNumberPanel( - canvas, size, weekNumberPanelWidth, monthView, isHorizontalMultiView); + canvas, + size, + weekNumberPanelWidth, + monthView, + isHorizontalMultiView, + ); } for (int j = 0; j < viewCount; j++) { final int currentViewIndex = monthView.isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; - final int currentMonth = monthView - .visibleDates[((j * datesCount) + (datesCount / 2)).truncate()] - .month as int; + final int currentMonth = + monthView + .visibleDates[((j * datesCount) + (datesCount / 2)) + .truncate()] + .month + as int; final int viewStartIndex = j * datesCount; final int viewEndIndex = ((j + 1) * datesCount) - 1; - final List selectedIndex = - monthView.getSelectedIndexValues(viewStartIndex, viewEndIndex); - double viewStartPosition = isVerticalMultiView - ? weekNumberPanelWidth - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * (currentViewIndex + 1)); + final List selectedIndex = monthView.getSelectedIndexValues( + viewStartIndex, + viewEndIndex, + ); + double viewStartPosition = + isVerticalMultiView + ? weekNumberPanelWidth + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * (currentViewIndex + 1)); if (monthView.isRtl) { - viewStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * currentViewIndex); + viewStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * currentViewIndex); } final double viewEndPosition = viewStartPosition + width; xPosition = viewStartPosition; - yPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + yPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); /// In horizontal multi view we need to add two week number panel in /// different position hence added condition here to add week number @@ -4082,19 +4557,30 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, /// position and if we add here the color applied for two times when /// multi view enabled. if (isHorizontalMultiView) { - _drawWeekNumberPanel(canvas, size, weekNumberPanelWidth, monthView, - isHorizontalMultiView, - viewStartPosition: monthView.isRtl - ? (viewEndPosition + weekNumberPanelWidth + (j * webUIPadding)) - : viewStartPosition); + _drawWeekNumberPanel( + canvas, + size, + weekNumberPanelWidth, + monthView, + isHorizontalMultiView, + viewStartPosition: + monthView.isRtl + ? (viewEndPosition + + weekNumberPanelWidth + + (j * webUIPadding)) + : viewStartPosition, + ); } for (int i = 0; i < datesCount; i++) { int currentIndex = i; if (monthView.isRtl) { final int rowIndex = i ~/ DateTime.daysPerWeek; - currentIndex = DateRangePickerHelper.getRtlIndex( - DateTime.daysPerWeek, i % DateTime.daysPerWeek) + + currentIndex = + DateRangePickerHelper.getRtlIndex( + DateTime.daysPerWeek, + i % DateTime.daysPerWeek, + ) + (rowIndex * DateTime.daysPerWeek); } @@ -4123,34 +4609,35 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, (i > DateTime.daysPerWeek && i <= (DateTime.daysPerWeek * 2) && monthView - .visibleDates[ - viewStartIndex + (DateTime.daysPerWeek * 2)] + .visibleDates[viewStartIndex + + (DateTime.daysPerWeek * 2)] .month == currentMonth) || (i >= datesCount - (DateTime.daysPerWeek * 2) && i < datesCount - DateTime.daysPerWeek && monthView - .visibleDates[ - viewEndIndex - ((DateTime.daysPerWeek * 2) - 1)] + .visibleDates[viewEndIndex - + ((DateTime.daysPerWeek * 2) - 1)] .month == currentMonth) || (i >= datesCount - DateTime.daysPerWeek && (monthView - .visibleDates[ - viewEndIndex - (DateTime.daysPerWeek - 1)] + .visibleDates[viewEndIndex - + (DateTime.daysPerWeek - 1)] .month == currentMonth))) && date.weekday == DateTime.monday) { _drawWeekNumber( - canvas, - size, - date, - cellHeight, - yPosition, - weekNumberPanelWidth, - monthView, - viewStartPosition, - viewEndPosition); + canvas, + size, + date, + cellHeight, + yPosition, + weekNumberPanelWidth, + monthView, + viewStartPosition, + viewEndPosition, + ); } if (hideLeadingAndTrailingDates && currentDateMonth != currentMonth) { @@ -4159,28 +4646,36 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, } final bool isEnableDate = DateRangePickerHelper.isEnabledDate( - monthView.minDate, - monthView.maxDate, - monthView.enablePastDates, - date, - monthView.isHijri); + monthView.minDate, + monthView.maxDate, + monthView.enablePastDates, + date, + monthView.isHijri, + ); final bool isBlackedDate = DateRangePickerHelper.isDateWithInVisibleDates( - monthView.visibleDates, monthView.blackoutDates, date); + monthView.visibleDates, + monthView.blackoutDates, + date, + ); final bool isSelectedDate = selectedIndex.contains(currentIndex); bool isDisabledDate = DateRangePickerHelper.isDateWithInVisibleDates( - monthView.visibleDates, monthView.disableDatesCollection, date); + monthView.visibleDates, + monthView.disableDatesCollection, + date, + ); if (!isDisabledDate && monthView is _MonthViewExtendableRangeSelectionRenderObject && monthView.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - monthView.selectedRange, - date, - monthView.extendableRangeSelectionDirection, - DateRangePickerView.month, - monthView.isHijri, - isInBetweenEnabled: true)) { + monthView.selectedRange, + date, + monthView.extendableRangeSelectionDirection, + DateRangePickerView.month, + monthView.isHijri, + isInBetweenEnabled: true, + )) { isDisabledDate = true; } @@ -4192,7 +4687,11 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, (monthView.rowCount != 6 || (currentMonth == currentDateMonth)))) { monthView.drawCustomCellSelection( - canvas, xPosition, yPosition, currentIndex); + canvas, + xPosition, + yPosition, + currentIndex, + ); } child!.paint(context, Offset(xPosition, yPosition)); @@ -4205,15 +4704,16 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, (i > (DateTime.daysPerWeek * 2)) && (i < datesCount - (DateTime.daysPerWeek * 2))))) { _drawWeekNumber( - canvas, - size, - date, - cellHeight, - yPosition, - weekNumberPanelWidth, - monthView, - viewStartPosition, - viewEndPosition); + canvas, + size, + date, + cellHeight, + yPosition, + weekNumberPanelWidth, + monthView, + viewStartPosition, + viewEndPosition, + ); } if (monthView.mouseHoverPosition.value != null && @@ -4234,14 +4734,19 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, monthView.mouseHoverPosition.value!.offset!.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._selectionPainter.color = + monthView.selectionColor != null + ? monthView.selectionColor!.withValues(alpha: 0.4) + : monthView.datePickerTheme.selectionColor!.withValues( + alpha: 0.4, + ); canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(xPosition, yPosition, cellWidth, cellHeight), - const Radius.circular(2)), - monthView._selectionPainter); + RRect.fromRectAndRadius( + Rect.fromLTWH(xPosition, yPosition, cellWidth, cellHeight), + const Radius.circular(2), + ), + monthView._selectionPainter, + ); } } @@ -4255,20 +4760,26 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, final dynamic today = DateRangePickerHelper.getToday(monthView.isHijri); if (!isHorizontalMultiView) { _drawWeekNumberPanel( - canvas, size, weekNumberPanelWidth, monthView, isHorizontalMultiView); + canvas, + size, + weekNumberPanelWidth, + monthView, + isHorizontalMultiView, + ); } for (int j = 0; j < viewCount; j++) { final int currentViewIndex = monthView.isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; - final dynamic currentMonthDate = monthView - .visibleDates[((j * datesCount) + (datesCount / 2)).truncate()]; + final dynamic currentMonthDate = + monthView.visibleDates[((j * datesCount) + (datesCount / 2)) + .truncate()]; final int nextMonth = getNextMonthDate(currentMonthDate).month as int; final int previousMonth = getPreviousMonthDate(currentMonthDate).month as int; bool isCurrentDate; - final TextStyle selectionTextStyle = monthView.selectionTextStyle ?? + final TextStyle selectionTextStyle = monthView.datePickerTheme.selectionTextStyle!; - final TextStyle selectedRangeTextStyle = monthView.rangeTextStyle ?? + final TextStyle selectedRangeTextStyle = monthView.datePickerTheme.rangeSelectionTextStyle!; Decoration? dateDecoration; @@ -4276,51 +4787,66 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, final int viewStartIndex = j * datesCount; final int viewEndIndex = ((j + 1) * datesCount) - 1; - final List selectedIndex = - monthView.getSelectedIndexValues(viewStartIndex, viewEndIndex); - double viewStartPosition = isVerticalMultiView - ? weekNumberPanelWidth - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * (currentViewIndex + 1)); + final List selectedIndex = monthView.getSelectedIndexValues( + viewStartIndex, + viewEndIndex, + ); + double viewStartPosition = + isVerticalMultiView + ? weekNumberPanelWidth + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * (currentViewIndex + 1)); if (monthView.isRtl) { - viewStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + - (currentViewIndex * webUIPadding) + - (weekNumberPanelWidth * currentViewIndex); + viewStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + + (currentViewIndex * webUIPadding) + + (weekNumberPanelWidth * currentViewIndex); } List? hoveringIndex; if (monthView.mouseHoverPosition.value != null && monthView.mouseHoverPosition.value!.hoveringRange != null) { hoveringIndex = _getSelectedRangeIndex( - monthView.mouseHoverPosition.value!.hoveringRange.startDate, - monthView.mouseHoverPosition.value!.hoveringRange.endDate, - monthView.visibleDates, - monthView.isHijri, - monthStartIndex: viewStartIndex, - monthEndIndex: viewEndIndex); + monthView.mouseHoverPosition.value!.hoveringRange.startDate, + monthView.mouseHoverPosition.value!.hoveringRange.endDate, + monthView.visibleDates, + monthView.isHijri, + monthStartIndex: viewStartIndex, + monthEndIndex: viewEndIndex, + ); } final double viewEndPosition = viewStartPosition + width; xPosition = viewStartPosition; - yPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + yPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); if (isHorizontalMultiView) { _drawWeekNumberPanel( - canvas, size, weekNumberPanelWidth, monthView, isHorizontalMultiView, - viewStartPosition: monthView.isRtl - ? (viewEndPosition + weekNumberPanelWidth + (j * webUIPadding)) - : viewStartPosition); + canvas, + size, + weekNumberPanelWidth, + monthView, + isHorizontalMultiView, + viewStartPosition: + monthView.isRtl + ? (viewEndPosition + weekNumberPanelWidth + (j * webUIPadding)) + : viewStartPosition, + ); } for (int i = 0; i < datesCount; i++) { int currentIndex = i; if (monthView.isRtl) { final int rowIndex = i ~/ DateTime.daysPerWeek; - currentIndex = DateRangePickerHelper.getRtlIndex( - DateTime.daysPerWeek, i % DateTime.daysPerWeek) + + currentIndex = + DateRangePickerHelper.getRtlIndex( + DateTime.daysPerWeek, + i % DateTime.daysPerWeek, + ) + (rowIndex * DateTime.daysPerWeek); } @@ -4343,40 +4869,42 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, if (weekNumberPanelWidth != 0 && hideLeadingAndTrailingDates && ((i <= DateTime.daysPerWeek && - monthView.visibleDates[viewStartIndex + DateTime.daysPerWeek] + monthView + .visibleDates[viewStartIndex + DateTime.daysPerWeek] .month == currentMonthDate.month) || (i > DateTime.daysPerWeek && i <= (DateTime.daysPerWeek * 2) && monthView - .visibleDates[ - viewStartIndex + (DateTime.daysPerWeek * 2)] + .visibleDates[viewStartIndex + + (DateTime.daysPerWeek * 2)] .month == currentMonthDate.month) || (i >= datesCount - (DateTime.daysPerWeek * 2) && i < datesCount - DateTime.daysPerWeek && monthView - .visibleDates[ - viewEndIndex - ((DateTime.daysPerWeek * 2) - 1)] + .visibleDates[viewEndIndex - + ((DateTime.daysPerWeek * 2) - 1)] .month == currentMonthDate.month) || (i >= datesCount - DateTime.daysPerWeek && (monthView - .visibleDates[ - viewEndIndex - (DateTime.daysPerWeek - 1)] + .visibleDates[viewEndIndex - + (DateTime.daysPerWeek - 1)] .month == currentMonthDate.month))) && date.weekday == DateTime.monday) { _drawWeekNumber( - canvas, - size, - date, - cellHeight, - yPosition, - weekNumberPanelWidth, - monthView, - viewStartPosition, - viewEndPosition); + canvas, + size, + date, + cellHeight, + yPosition, + weekNumberPanelWidth, + monthView, + viewStartPosition, + viewEndPosition, + ); } bool isNextMonth = false; @@ -4404,67 +4932,83 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, (i > (DateTime.daysPerWeek * 2)) && (i < datesCount - (DateTime.daysPerWeek * 2))))) { _drawWeekNumber( - canvas, - size, - date, - cellHeight, - yPosition, - weekNumberPanelWidth, - monthView, - viewStartPosition, - viewEndPosition); + canvas, + size, + date, + cellHeight, + yPosition, + weekNumberPanelWidth, + monthView, + viewStartPosition, + viewEndPosition, + ); } isCurrentDate = isSameDate(date, today); final bool isEnableDate = DateRangePickerHelper.isEnabledDate( - monthView.minDate, - monthView.maxDate, - monthView.enablePastDates, - date, - monthView.isHijri); + monthView.minDate, + monthView.maxDate, + monthView.enablePastDates, + date, + monthView.isHijri, + ); final bool isBlackedDate = DateRangePickerHelper.isDateWithInVisibleDates( - monthView.visibleDates, monthView.blackoutDates, date); - final bool isWeekEnd = - DateRangePickerHelper.isWeekend(monthView.weekendDays, date); + monthView.visibleDates, + monthView.blackoutDates, + date, + ); + final bool isWeekEnd = DateRangePickerHelper.isWeekend( + monthView.weekendDays, + date, + ); final bool isSpecialDate = DateRangePickerHelper.isDateWithInVisibleDates( - monthView.visibleDates, monthView.specialDates, date); + monthView.visibleDates, + monthView.specialDates, + date, + ); bool isDisabledDate = DateRangePickerHelper.isDateWithInVisibleDates( - monthView.visibleDates, monthView.disableDatesCollection, date); + monthView.visibleDates, + monthView.disableDatesCollection, + date, + ); if (!isDisabledDate && monthView is _MonthViewExtendableRangeSelectionRenderObject && monthView.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - monthView.selectedRange, - date, - monthView.extendableRangeSelectionDirection, - DateRangePickerView.month, - monthView.isHijri, - isInBetweenEnabled: true)) { + monthView.selectedRange, + date, + monthView.extendableRangeSelectionDirection, + DateRangePickerView.month, + monthView.isHijri, + isInBetweenEnabled: true, + )) { isDisabledDate = true; } textStyle = _updateTextStyle( - monthView, - isNextMonth, - isPreviousMonth, - isCurrentDate, - isEnableDate, - isBlackedDate, - isWeekEnd, - isSpecialDate, - isDisabledDate); + monthView, + isNextMonth, + isPreviousMonth, + isCurrentDate, + isEnableDate, + isBlackedDate, + isWeekEnd, + isSpecialDate, + isDisabledDate, + ); dateDecoration = _updateDecoration( - isNextMonth, - isPreviousMonth, - monthView, - isEnableDate, - isCurrentDate, - isBlackedDate, - date, - isWeekEnd, - isSpecialDate, - isDisabledDate); + isNextMonth, + isPreviousMonth, + monthView, + isEnableDate, + isCurrentDate, + isBlackedDate, + date, + isWeekEnd, + isSpecialDate, + isDisabledDate, + ); final bool isSelectedDate = selectedIndex.contains(currentIndex); if (isSelectedDate && @@ -4475,19 +5019,35 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, (monthView.rowCount != 6 || (currentMonthDate.month == currentDateMonth)))) { textStyle = _drawCellAndSelection( - canvas, - xPosition, - yPosition, - selectionTextStyle, - selectedRangeTextStyle, - monthView, - currentIndex); + canvas, + xPosition, + yPosition, + selectionTextStyle, + selectedRangeTextStyle, + monthView, + currentIndex, + ); } else if (dateDecoration != null) { - _drawDecoration(canvas, xPosition, yPosition, padding, cellWidth, - cellHeight, dateDecoration, monthView); + _drawDecoration( + canvas, + xPosition, + yPosition, + padding, + cellWidth, + cellHeight, + dateDecoration, + monthView, + ); } else if (isCurrentDate) { - _drawCurrentDate(canvas, monthView, xPosition, yPosition, padding, - cellWidth, cellHeight); + _drawCurrentDate( + canvas, + monthView, + xPosition, + yPosition, + padding, + cellWidth, + cellHeight, + ); } final TextSpan dateText = TextSpan( @@ -4496,19 +5056,28 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, ); monthView._textPainter.text = dateText; - monthView._textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + monthView._textPainter.layout(maxWidth: cellWidth); monthView._textPainter.paint( - canvas, - Offset(xPosition + (cellWidth / 2 - monthView._textPainter.width / 2), - yPosition + ((cellHeight - monthView._textPainter.height) / 2))); + canvas, + Offset( + xPosition + (cellWidth / 2 - monthView._textPainter.width / 2), + yPosition + ((cellHeight - monthView._textPainter.height) / 2), + ), + ); if (hoveringIndex != null && hoveringIndex.isNotEmpty && hoveringIndex.contains(currentIndex) && !isBlackedDate && isEnableDate) { - _addRangeHoverEffect(canvas, xPosition, yPosition, monthView, - currentIndex, hoveringIndex); + _addRangeHoverEffect( + canvas, + xPosition, + yPosition, + monthView, + currentIndex, + hoveringIndex, + ); } if (monthView.mouseHoverPosition.value != null) { @@ -4521,8 +5090,15 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, } if (monthView.mouseHoverPosition.value!.offset != null) { - _addHoveringEffect(canvas, monthView, xPosition, yPosition, cellWidth, - cellHeight, size); + _addHoveringEffect( + canvas, + monthView, + xPosition, + yPosition, + cellWidth, + cellHeight, + size, + ); } } @@ -4532,47 +5108,61 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, } void _drawWeekNumber( - Canvas canvas, - Size size, - dynamic date, - double cellHeight, - double yPosition, - double weekNumberPanelWidth, - _IMonthView monthView, - double viewStartPosition, - double viewEndPosition) { + Canvas canvas, + Size size, + dynamic date, + double cellHeight, + double yPosition, + double weekNumberPanelWidth, + _IMonthView monthView, + double viewStartPosition, + double viewEndPosition, +) { final String weekNumber = - DateRangePickerHelper.getWeekNumberOfYear(date, monthView.isHijri) - .toString(); - final TextStyle weekNumberTextStyle = monthView.weekNumberStyle.textStyle ?? + DateRangePickerHelper.getWeekNumberOfYear( + date, + monthView.isHijri, + ).toString(); + final TextStyle weekNumberTextStyle = monthView.datePickerTheme.weekNumberTextStyle!; - final TextSpan textSpan = - TextSpan(text: weekNumber, style: weekNumberTextStyle); + final TextSpan textSpan = TextSpan( + text: weekNumber, + style: weekNumberTextStyle, + ); monthView._textPainter.text = textSpan; monthView._textPainter.textAlign = TextAlign.left; monthView._textPainter.textDirection = TextDirection.ltr; monthView._textPainter.textWidthBasis = TextWidthBasis.longestLine; - monthView._textPainter.layout(minWidth: 0, maxWidth: weekNumberPanelWidth); + monthView._textPainter.layout(maxWidth: weekNumberPanelWidth); double weekNumberPosition = (weekNumberPanelWidth - monthView._textPainter.width) / 2; - weekNumberPosition += monthView.isRtl - ? viewStartPosition == 0 && - monthView.navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? viewEndPosition + monthView.multiViewSpacing - : viewEndPosition - : viewStartPosition - weekNumberPanelWidth; + weekNumberPosition += + monthView.isRtl + ? viewStartPosition == 0 && + monthView.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? viewEndPosition + monthView.multiViewSpacing + : viewEndPosition + : viewStartPosition - weekNumberPanelWidth; monthView._textPainter.paint( - canvas, - Offset(weekNumberPosition, - yPosition + ((cellHeight - monthView._textPainter.height) / 2))); + canvas, + Offset( + weekNumberPosition, + yPosition + ((cellHeight - monthView._textPainter.height) / 2), + ), + ); } -void _drawWeekNumberPanel(Canvas canvas, Size size, double weekNumberPanelWidth, - _IMonthView monthView, bool isHorizontalMultiView, - {double viewStartPosition = 0}) { +void _drawWeekNumberPanel( + Canvas canvas, + Size size, + double weekNumberPanelWidth, + _IMonthView monthView, + bool isHorizontalMultiView, { + double viewStartPosition = 0, +}) { if (weekNumberPanelWidth == 0) { return; } @@ -4583,21 +5173,34 @@ void _drawWeekNumberPanel(Canvas canvas, Size size, double weekNumberPanelWidth, } final double padding = monthView.isMobilePlatform ? 5 : 0; - final Rect rect = Rect.fromLTRB(xPosition + padding, padding, - (xPosition + weekNumberPanelWidth) - padding, size.height - padding); - final Paint _linePainter = Paint(); - _linePainter.style = PaintingStyle.fill; - _linePainter.color = monthView.weekNumberStyle.backgroundColor ?? + final Rect rect = Rect.fromLTRB( + xPosition + padding, + padding, + (xPosition + weekNumberPanelWidth) - padding, + size.height - padding, + ); + final Paint linePainter = Paint(); + linePainter.style = PaintingStyle.fill; + linePainter.color = + monthView.weekNumberStyle.backgroundColor ?? monthView.datePickerTheme.weekNumberBackgroundColor!; - final RRect roundedRect = - RRect.fromRectAndRadius(rect, Radius.circular(padding)); - canvas.drawRRect(roundedRect, _linePainter); + final RRect roundedRect = RRect.fromRectAndRadius( + rect, + Radius.circular(padding), + ); + canvas.drawRRect(roundedRect, linePainter); } /// Add the hovering effect when the selection mode set as extendable and the /// hovering date extends the range. -void _addRangeHoverEffect(Canvas canvas, double xPosition, double yPosition, - _IMonthView monthView, int currentIndex, List? hoveringIndex) { +void _addRangeHoverEffect( + Canvas canvas, + double xPosition, + double yPosition, + _IMonthView monthView, + int currentIndex, + List? hoveringIndex, +) { if (hoveringIndex == null || hoveringIndex.isEmpty) { return; } @@ -4613,9 +5216,10 @@ void _addRangeHoverEffect(Canvas canvas, double xPosition, double yPosition, final bool isBetweenRange = hoveringDetails[3]; final double radius = _getCellRadius( - rangeSelectionMonthView.selectionRadius, - rangeSelectionMonthView._centerXPosition, - rangeSelectionMonthView._centerYPosition); + rangeSelectionMonthView.selectionRadius, + rangeSelectionMonthView._centerXPosition, + rangeSelectionMonthView._centerYPosition, + ); final double cornerRadius = monthView._cellHeight / 4 > 10 ? 10 : monthView._cellHeight / 4; double heightDifference = rangeSelectionMonthView._cellHeight / 2 - radius; @@ -4632,61 +5236,82 @@ void _addRangeHoverEffect(Canvas canvas, double xPosition, double yPosition, switch (monthView.selectionShape) { case DateRangePickerSelectionShape.circle: rect = Rect.fromLTRB( - xPosition + rangeSelectionMonthView._centerXPosition, - yPosition + heightDifference, - xPosition + rangeSelectionMonthView._cellWidth, - yPosition + rangeSelectionMonthView._cellHeight - heightDifference); + xPosition + rangeSelectionMonthView._centerXPosition, + yPosition + heightDifference, + xPosition + rangeSelectionMonthView._cellWidth, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference, + ); break; case DateRangePickerSelectionShape.rectangle: final double rightPosition = xPosition + rangeSelectionMonthView._cellWidth; rect = Rect.fromLTRB( - rightPosition - cornerRadius, - yPosition + heightDifference, - rightPosition, - yPosition + rangeSelectionMonthView._cellHeight - heightDifference); + rightPosition - cornerRadius, + yPosition + heightDifference, + rightPosition, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference, + ); } } else if (isEndRange) { switch (monthView.selectionShape) { case DateRangePickerSelectionShape.circle: rect = Rect.fromLTRB( - xPosition, - yPosition + heightDifference, - xPosition + rangeSelectionMonthView._centerXPosition, - yPosition + rangeSelectionMonthView._cellHeight - heightDifference); + xPosition, + yPosition + heightDifference, + xPosition + rangeSelectionMonthView._centerXPosition, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference, + ); break; case DateRangePickerSelectionShape.rectangle: { rect = Rect.fromLTRB( - xPosition, - yPosition + heightDifference, - xPosition + cornerRadius, - yPosition + - rangeSelectionMonthView._cellHeight - - heightDifference); + xPosition, + yPosition + heightDifference, + xPosition + cornerRadius, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference, + ); } } } else if (isBetweenRange) { rect = Rect.fromLTRB( - xPosition, - yPosition + heightDifference, - xPosition + rangeSelectionMonthView._cellWidth, - yPosition + rangeSelectionMonthView._cellHeight - heightDifference); + xPosition, + yPosition + heightDifference, + xPosition + rangeSelectionMonthView._cellWidth, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference, + ); } monthView._selectionPainter.style = PaintingStyle.stroke; monthView._selectionPainter.strokeWidth = 1.0; - monthView._selectionPainter.color = monthView.selectionColor != null - ? monthView.selectionColor!.withOpacity(0.4) - : monthView.datePickerTheme.selectionColor!.withOpacity(0.4); + monthView._selectionPainter.color = + monthView.selectionColor != null + ? monthView.selectionColor!.withValues(alpha: 0.4) + : monthView.datePickerTheme.selectionColor!.withValues(alpha: 0.4); DateRangePickerHelper.drawDashedLine( - rect.left, rect.top, rect.right, canvas, monthView._selectionPainter); + rect.left, + rect.top, + rect.right, + canvas, + monthView._selectionPainter, + ); DateRangePickerHelper.drawDashedLine( - rect.left, rect.bottom, rect.right, canvas, monthView._selectionPainter); + rect.left, + rect.bottom, + rect.right, + canvas, + monthView._selectionPainter, + ); } -void _addHoveringEffect(Canvas canvas, _IMonthView monthView, double xPosition, - double yPosition, double cellWidth, double cellHeight, Size size) { +void _addHoveringEffect( + Canvas canvas, + _IMonthView monthView, + double xPosition, + double yPosition, + double cellWidth, + double cellHeight, + Size size, +) { if (xPosition <= monthView.mouseHoverPosition.value!.offset!.dx && xPosition + cellWidth >= monthView.mouseHoverPosition.value!.offset!.dx && yPosition <= monthView.mouseHoverPosition.value!.offset!.dy && @@ -4694,74 +5319,104 @@ void _addHoveringEffect(Canvas canvas, _IMonthView monthView, double xPosition, monthView.mouseHoverPosition.value!.offset!.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._selectionPainter.color = + monthView.selectionColor != null + ? monthView.selectionColor!.withValues(alpha: 0.4) + : monthView.datePickerTheme.selectionColor!.withValues(alpha: 0.4); switch (monthView.selectionShape) { case DateRangePickerSelectionShape.circle: { final double centerXPosition = cellWidth / 2; final double centerYPosition = cellHeight / 2; final double radius = _getCellRadius( - monthView.selectionRadius, centerXPosition, centerYPosition); + monthView.selectionRadius, + centerXPosition, + centerYPosition, + ); canvas.drawCircle( - Offset(xPosition + centerXPosition, yPosition + centerYPosition), - radius, - monthView._selectionPainter); + Offset(xPosition + centerXPosition, yPosition + centerYPosition), + radius, + monthView._selectionPainter, + ); } break; case DateRangePickerSelectionShape.rectangle: { canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(xPosition + 1, yPosition + 1, cellWidth - 1, - cellHeight - 1), - Radius.circular(cellHeight / 4 > 10 ? 10 : cellHeight / 4)), - monthView._selectionPainter); + RRect.fromRectAndRadius( + Rect.fromLTWH( + xPosition + 1, + yPosition + 1, + cellWidth - 1, + cellHeight - 1, + ), + Radius.circular(cellHeight / 4 > 10 ? 10 : cellHeight / 4), + ), + monthView._selectionPainter, + ); } } } } TextStyle _drawCellAndSelection( - Canvas canvas, - double xPosition, - double yPosition, - TextStyle selectionTextStyle, - TextStyle selectedRangeTextStyle, - _IMonthView monthView, - int currentIndex) { + Canvas canvas, + double xPosition, + double yPosition, + TextStyle selectionTextStyle, + TextStyle selectedRangeTextStyle, + _IMonthView monthView, + int currentIndex, +) { monthView._selectionPainter.color = 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; monthView._selectionPainter.style = PaintingStyle.fill; - return monthView.drawSelection(canvas, xPosition, yPosition, currentIndex, - selectionTextStyle, selectedRangeTextStyle); + return monthView.drawSelection( + canvas, + xPosition, + yPosition, + currentIndex, + selectionTextStyle, + selectedRangeTextStyle, + ); } void _drawDecoration( - Canvas canvas, - double xPosition, - double yPosition, - double padding, - double cellWidth, - double cellHeight, - Decoration dateDecoration, - _IMonthView monthView) { - final BoxPainter boxPainter = - dateDecoration.createBoxPainter(monthView.markNeedsPaint); + Canvas canvas, + double xPosition, + double yPosition, + double padding, + double cellWidth, + double cellHeight, + Decoration dateDecoration, + _IMonthView monthView, +) { + final BoxPainter boxPainter = dateDecoration.createBoxPainter( + monthView.markNeedsPaint, + ); boxPainter.paint( - canvas, - Offset(xPosition + padding, yPosition + padding), - ImageConfiguration( - size: Size(cellWidth - (2 * padding), cellHeight - (2 * padding)))); + canvas, + Offset(xPosition + padding, yPosition + padding), + ImageConfiguration( + size: Size(cellWidth - (2 * padding), cellHeight - (2 * padding)), + ), + ); } -void _drawCurrentDate(Canvas canvas, _IMonthView monthView, double xPosition, - double yPosition, double padding, double cellWidth, double cellHeight) { - monthView._selectionPainter.color = monthView.todayHighlightColor ?? +void _drawCurrentDate( + Canvas canvas, + _IMonthView monthView, + double xPosition, + double yPosition, + double padding, + double cellWidth, + double cellHeight, +) { + monthView._selectionPainter.color = + monthView.todayHighlightColor ?? monthView.datePickerTheme.todayHighlightColor!; monthView._selectionPainter.isAntiAlias = true; monthView._selectionPainter.strokeWidth = 1.0; @@ -4773,96 +5428,94 @@ void _drawCurrentDate(Canvas canvas, _IMonthView monthView, double xPosition, final double centerXPosition = cellWidth / 2; final double centerYPosition = cellHeight / 2; final double radius = _getCellRadius( - monthView.selectionRadius, centerXPosition, centerYPosition); + monthView.selectionRadius, + centerXPosition, + centerYPosition, + ); canvas.drawCircle( - Offset(xPosition + centerXPosition, yPosition + centerYPosition), - radius, - monthView._selectionPainter); + Offset(xPosition + centerXPosition, yPosition + centerYPosition), + radius, + monthView._selectionPainter, + ); } break; case DateRangePickerSelectionShape.rectangle: { canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTRB( - xPosition + padding, - yPosition + padding, - xPosition + cellWidth - padding, - yPosition + cellHeight - padding), - Radius.circular(cellHeight / 4 > 10 ? 10 : cellHeight / 4)), - monthView._selectionPainter); + RRect.fromRectAndRadius( + Rect.fromLTRB( + xPosition + padding, + yPosition + padding, + xPosition + cellWidth - padding, + yPosition + cellHeight - padding, + ), + Radius.circular(cellHeight / 4 > 10 ? 10 : cellHeight / 4), + ), + monthView._selectionPainter, + ); } } } TextStyle _updateTextStyle( - _IMonthView monthView, - bool isNextMonth, - bool isPreviousMonth, - bool isCurrentDate, - bool isEnableDate, - bool isBlackedDate, - bool isWeekEnd, - bool isSpecialDate, - bool isDisabledDate) { + _IMonthView monthView, + bool isNextMonth, + bool isPreviousMonth, + bool isCurrentDate, + bool isEnableDate, + bool isBlackedDate, + bool isWeekEnd, + bool isSpecialDate, + bool isDisabledDate, +) { final TextStyle currentDatesTextStyle = - monthView.cellStyle.textStyle as TextStyle? ?? - monthView.datePickerTheme.activeDatesTextStyle!; + monthView.datePickerTheme.activeDatesTextStyle!; if (isBlackedDate) { - return monthView.cellStyle.blackoutDateTextStyle as TextStyle? ?? - (monthView.datePickerTheme.blackoutDatesTextStyle ?? - currentDatesTextStyle.copyWith( - decoration: TextDecoration.lineThrough)); + return monthView.datePickerTheme.blackoutDatesTextStyle ?? + currentDatesTextStyle.copyWith(decoration: TextDecoration.lineThrough); } if (isSpecialDate) { final TextStyle? specialDateTextStyle = - monthView.cellStyle.specialDatesTextStyle as TextStyle? ?? - monthView.datePickerTheme.specialDatesTextStyle; + monthView.datePickerTheme.specialDatesTextStyle; if (specialDateTextStyle != null) { return specialDateTextStyle; } } if (!isEnableDate || isDisabledDate) { - return monthView.cellStyle.disabledDatesTextStyle as TextStyle? ?? - monthView.datePickerTheme.disabledDatesTextStyle!; + return monthView.datePickerTheme.disabledDatesTextStyle!; } if (isCurrentDate) { - return monthView.cellStyle.todayTextStyle as TextStyle? ?? - monthView.datePickerTheme.todayTextStyle!; + return monthView.datePickerTheme.todayTextStyle!; } - if (isWeekEnd && monthView.cellStyle.weekendTextStyle != null) { - return monthView.cellStyle.weekendTextStyle as TextStyle; - } else if (isWeekEnd && - monthView.datePickerTheme.weekendDatesTextStyle != null) { + if (isWeekEnd && monthView.datePickerTheme.weekendDatesTextStyle != null) { return monthView.datePickerTheme.weekendDatesTextStyle!; } if (isNextMonth && !monthView.isHijri) { - return monthView.cellStyle.leadingDatesTextStyle as TextStyle? ?? - monthView.datePickerTheme.leadingDatesTextStyle!; + return monthView.datePickerTheme.leadingDatesTextStyle!; } else if (isPreviousMonth && !monthView.isHijri) { - return monthView.cellStyle.trailingDatesTextStyle as TextStyle? ?? - monthView.datePickerTheme.trailingDatesTextStyle!; + return monthView.datePickerTheme.trailingDatesTextStyle!; } return currentDatesTextStyle; } Decoration? _updateDecoration( - bool isNextMonth, - bool isPreviousMonth, - _IMonthView monthView, - bool isEnableDate, - bool isCurrentDate, - bool isBlackedDate, - dynamic date, - bool isWeekEnd, - bool isSpecialDate, - bool isDisabledDate) { + bool isNextMonth, + bool isPreviousMonth, + _IMonthView monthView, + bool isEnableDate, + bool isCurrentDate, + bool isBlackedDate, + dynamic date, + bool isWeekEnd, + bool isSpecialDate, + bool isDisabledDate, +) { final Decoration? dateDecoration = monthView.cellStyle.cellDecoration as Decoration?; 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 39e935a62..f4899fd82 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 @@ -16,7 +16,10 @@ class DateRangePickerHelper { /// Calculate the visible dates count based on picker view static int getViewDatesCount( - DateRangePickerView pickerView, int numberOfWeeks, bool isHijri) { + DateRangePickerView pickerView, + int numberOfWeeks, + bool isHijri, + ) { if (pickerView == DateRangePickerView.month) { if (isHijri) { /// 6 used to render the default number of weeks, since, Hijri type @@ -48,7 +51,9 @@ 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; } @@ -78,11 +83,21 @@ 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) { + static dynamic getNextViewStartDate( + DateRangePickerView pickerView, + int numberOfWeeksInView, + dynamic date, + bool isRtl, + bool isHijri, + ) { if (isRtl) { return getPreviousViewStartDate( - pickerView, numberOfWeeksInView, date, false, isHijri); + pickerView, + numberOfWeeksInView, + date, + false, + isHijri, + ); } switch (pickerView) { @@ -108,11 +123,21 @@ class DateRangePickerHelper { } /// Calculate the previous view visible start date based on calendar view. - static dynamic getPreviousViewStartDate(DateRangePickerView pickerView, - int numberOfWeeksInView, dynamic date, bool isRtl, bool isHijri) { + static dynamic getPreviousViewStartDate( + DateRangePickerView pickerView, + int numberOfWeeksInView, + dynamic date, + bool isRtl, + bool isHijri, + ) { if (isRtl) { return getNextViewStartDate( - pickerView, numberOfWeeksInView, date, false, isHijri); + pickerView, + numberOfWeeksInView, + date, + false, + isHijri, + ); } switch (pickerView) { @@ -179,7 +204,9 @@ 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,8 +237,13 @@ 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 && @@ -221,7 +253,10 @@ class DateRangePickerHelper { /// Return the first date of the month, year and decade based on view. /// Note: This method not applicable for month view. static dynamic getFirstDate( - dynamic date, bool isHijri, DateRangePickerView pickerView) { + dynamic date, + bool isHijri, + DateRangePickerView pickerView, + ) { if (pickerView == DateRangePickerView.month) { return date; } @@ -232,7 +267,11 @@ class DateRangePickerHelper { return DateRangePickerHelper.getDate(date.year, 1, 1, isHijri); } else if (pickerView == DateRangePickerView.century) { return DateRangePickerHelper.getDate( - (date.year ~/ 10) * 10, 1, 1, isHijri); + (date.year ~/ 10) * 10, + 1, + 1, + isHijri, + ); } return date; @@ -243,12 +282,13 @@ class DateRangePickerHelper { /// is in between enabled used to enable the in between dates on selected /// range while draw the cell. static bool isDisableDirectionDate( - dynamic range, - dynamic currentDate, - ExtendableRangeSelectionDirection extendableRangeSelectionDirection, - DateRangePickerView view, - bool isHijri, - {bool isInBetweenEnabled = false}) { + dynamic range, + dynamic currentDate, + ExtendableRangeSelectionDirection extendableRangeSelectionDirection, + DateRangePickerView view, + bool isHijri, { + bool isInBetweenEnabled = false, + }) { if (range == null) { return false; } @@ -258,9 +298,10 @@ class DateRangePickerHelper { } final dynamic startDate = getFirstDate(range.startDate, isHijri, view); - final dynamic endDate = range.endDate != null - ? getFirstDate(range.endDate, isHijri, view) - : startDate; + final dynamic endDate = + range.endDate != null + ? getFirstDate(range.endDate, isHijri, view) + : startDate; final dynamic currentDateValue = getFirstDate(currentDate, isHijri, view); switch (extendableRangeSelectionDirection) { case ExtendableRangeSelectionDirection.none: @@ -269,10 +310,16 @@ class DateRangePickerHelper { (isInBetweenEnabled && ((startDate.isAfter(currentDateValue) == true && !isSameCellDates( - startDate, currentDateValue, view)) || + startDate, + currentDateValue, + view, + )) || (endDate.isBefore(currentDateValue) == true && !isSameCellDates( - endDate, currentDateValue, view))))); + endDate, + currentDateValue, + view, + ))))); case ExtendableRangeSelectionDirection.forward: return startDate.isAfter(currentDateValue) == true && !isSameCellDates(startDate, currentDateValue, view); @@ -285,8 +332,13 @@ class DateRangePickerHelper { } /// Check the date as current month date. - static bool isDateAsCurrentMonthDate(dynamic visibleDate, int rowCount, - bool showLeadingAndTrialingDates, dynamic date, bool isHijri) { + static bool isDateAsCurrentMonthDate( + dynamic visibleDate, + int rowCount, + bool showLeadingAndTrialingDates, + dynamic date, + bool isHijri, + ) { if ((rowCount == 6 && !showLeadingAndTrialingDates || isHijri) && date.month != visibleDate.month) { return false; @@ -297,11 +349,17 @@ class DateRangePickerHelper { /// Return the updated left and top value based cell width and height and /// total width and height. - static Map getTopAndLeftValues(bool isRtl, double left, - double top, double cellWidth, double cellHeight, double width) { + static Map getTopAndLeftValues( + bool isRtl, + double left, + double top, + double cellWidth, + double cellHeight, + double width, + ) { final Map topAndLeft = { 'left': left, - 'top': top + 'top': top, }; if (isRtl) { if (left.round() == cellWidth.round()) { @@ -328,7 +386,10 @@ 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; } @@ -337,7 +398,10 @@ class DateRangePickerHelper { final dynamic visibleEndDate = visibleDates[visibleDates.length - 1]; for (final dynamic currentDate in dates) { if (!isDateWithInDateRange( - visibleStartDate, visibleEndDate, currentDate)) { + visibleStartDate, + visibleEndDate, + currentDate, + )) { continue; } @@ -360,58 +424,87 @@ class DateRangePickerHelper { /// Check the left side view have valid dates based on widget direction. static bool canMoveToPreviousViewRtl( - DateRangePickerView view, - int numberOfWeeksInView, - dynamic minDate, - dynamic maxDate, - List visibleDates, - bool isRtl, - bool enableMultiView, - bool isHijri) { + DateRangePickerView view, + int numberOfWeeksInView, + dynamic minDate, + dynamic maxDate, + List visibleDates, + bool isRtl, + bool enableMultiView, + bool isHijri, + ) { if (isRtl) { - return canMoveToNextView(view, numberOfWeeksInView, maxDate, visibleDates, - enableMultiView, isHijri); + return canMoveToNextView( + view, + numberOfWeeksInView, + maxDate, + visibleDates, + enableMultiView, + isHijri, + ); } else { - return canMoveToPreviousView(view, numberOfWeeksInView, minDate, - visibleDates, enableMultiView, isHijri); + return canMoveToPreviousView( + view, + numberOfWeeksInView, + minDate, + visibleDates, + enableMultiView, + isHijri, + ); } } /// Check the right side view have valid dates based on widget direction. static bool canMoveToNextViewRtl( - DateRangePickerView view, - int numberOfWeeksInView, - dynamic minDate, - dynamic maxDate, - List visibleDates, - bool isRtl, - bool enableMultiView, - bool isHijri) { + DateRangePickerView view, + int numberOfWeeksInView, + dynamic minDate, + dynamic maxDate, + List visibleDates, + bool isRtl, + bool enableMultiView, + bool isHijri, + ) { if (isRtl) { - return canMoveToPreviousView(view, numberOfWeeksInView, minDate, - visibleDates, enableMultiView, isHijri); + return canMoveToPreviousView( + view, + numberOfWeeksInView, + minDate, + visibleDates, + enableMultiView, + isHijri, + ); } else { - return canMoveToNextView(view, numberOfWeeksInView, maxDate, visibleDates, - enableMultiView, isHijri); + return canMoveToNextView( + view, + numberOfWeeksInView, + maxDate, + visibleDates, + enableMultiView, + isHijri, + ); } } /// Check the previous view have enabled dates or not. static bool canMoveToPreviousView( - DateRangePickerView view, - int numberOfWeeksInView, - dynamic minDate, - List visibleDates, - bool enableMultiView, - bool isHijri) { + DateRangePickerView view, + int numberOfWeeksInView, + dynamic minDate, + List visibleDates, + bool enableMultiView, + bool isHijri, + ) { switch (view) { case DateRangePickerView.month: { if (numberOfWeeksInView != 6 && !isHijri) { - DateTime prevViewDate = - DateRangePickerHelper.getDateTimeValue(visibleDates[0]); + DateTime prevViewDate = DateRangePickerHelper.getDateTimeValue( + visibleDates[0], + ); prevViewDate = DateRangePickerHelper.getDateTimeValue( - addDays(prevViewDate, -1)); + addDays(prevViewDate, -1), + ); if (!isSameOrAfterDate(minDate, prevViewDate)) { return false; } @@ -433,7 +526,8 @@ class DateRangePickerHelper { { final int currentYear = visibleDates[visibleDates.length ~/ (enableMultiView ? 4 : 2)] - .year as int; + .year + as int; final int minYear = minDate.year as int; final int offset = getOffset(view); @@ -464,10 +558,13 @@ class DateRangePickerHelper { } /// Get the visible dates based on the date value and visible dates count. - // ignore: always_specify_types + // ignore: always_specify_types, strict_raw_type static List getVisibleYearDates( - dynamic date, DateRangePickerView view, bool isHijri) { - // ignore: always_specify_types + dynamic date, + DateRangePickerView view, + bool isHijri, + ) { + // ignore: always_specify_types, strict_raw_type List datesCollection; if (isHijri) { datesCollection = []; @@ -514,20 +611,23 @@ class DateRangePickerHelper { /// Check the next view have enabled dates or not. static bool canMoveToNextView( - DateRangePickerView view, - int numberOfWeeksInView, - dynamic maxDate, - List visibleDates, - bool enableMultiView, - bool isHijri) { + DateRangePickerView view, + int numberOfWeeksInView, + dynamic maxDate, + List visibleDates, + bool enableMultiView, + bool isHijri, + ) { switch (view) { case DateRangePickerView.month: { if (!isHijri && numberOfWeeksInView != 6) { DateTime nextViewDate = DateRangePickerHelper.getDateTimeValue( - visibleDates[visibleDates.length - 1]); + visibleDates[visibleDates.length - 1], + ); nextViewDate = DateRangePickerHelper.getDateTimeValue( - addDays(nextViewDate, 1)); + addDays(nextViewDate, 1), + ); if (!isSameOrBeforeDate(maxDate, nextViewDate)) { return false; } @@ -549,7 +649,8 @@ class DateRangePickerHelper { { final int currentYear = visibleDates[visibleDates.length ~/ (enableMultiView ? 4 : 2)] - .year as int; + .year + as int; final int maxYear = maxDate.year as int; final int offset = getOffset(view); if (((currentYear ~/ offset) * offset) + offset > @@ -582,7 +683,10 @@ class DateRangePickerHelper { /// Returns the corresponding hijri month date, for the date passed with the /// given month format and localization. static String getHijriMonthText( - dynamic date, SfLocalizations localizations, String format) { + dynamic date, + SfLocalizations localizations, + String format, + ) { if (date.month == 1) { if (format == 'M' || format == 'MM') { return date.month.toString(); @@ -735,7 +839,9 @@ class DateRangePickerHelper { /// Determines whether the leading and trailing dates can be shown or not. static bool canShowLeadingAndTrailingDates( - dynamic monthViewSettings, bool isHijri) { + dynamic monthViewSettings, + bool isHijri, + ) { if (isHijri) { return false; } @@ -792,7 +898,11 @@ class DateRangePickerHelper { /// as leading date or not. /// Note: This method not applicable for month view. static bool isLeadingCellDate( - int index, int viewStartIndex, List visibleDates, dynamic view) { + int index, + int viewStartIndex, + List visibleDates, + dynamic view, + ) { final DateRangePickerView pickerView = getPickerView(view); if (pickerView == DateRangePickerView.month || pickerView == DateRangePickerView.year) { @@ -816,8 +926,14 @@ 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; } @@ -866,15 +982,23 @@ class DateRangePickerHelper { } if (pickerView == DateRangePickerView.year) { - final dynamic currentDate = - getDate(date.year, date.month + 1, 1, isHijri); + final dynamic currentDate = getDate( + date.year, + date.month + 1, + 1, + isHijri, + ); return addDays(currentDate, -1); } else if (pickerView == DateRangePickerView.decade) { final dynamic currentDate = getDate(date.year + 1, 1, 1, isHijri); return addDays(currentDate, -1); } else if (pickerView == DateRangePickerView.century) { - final dynamic currentDate = - getDate(((date.year ~/ 10) * 10) + 10, 1, 1, isHijri); + final dynamic currentDate = getDate( + ((date.year ~/ 10) * 10) + 10, + 1, + 1, + isHijri, + ); return addDays(currentDate, -1); } @@ -883,8 +1007,13 @@ 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, - {int viewStartIndex = -1, int viewEndIndex = -1}) { + static int getDateCellIndex( + List dates, + dynamic date, + dynamic view, { + int viewStartIndex = -1, + int viewEndIndex = -1, + }) { if (date == null) { return -1; } @@ -914,7 +1043,10 @@ class DateRangePickerHelper { /// Check the showWeekNumber is true or not and returns the position. static double getWeekNumberPanelWidth( - bool showWeekNumber, double width, bool isMobilePlatform) { + bool showWeekNumber, + double width, + bool isMobilePlatform, + ) { return showWeekNumber ? (width / (DateTime.daysPerWeek + 1)) / (isMobilePlatform ? 1.3 : 4) : 0; @@ -922,9 +1054,10 @@ class DateRangePickerHelper { /// Returns week number for the given date. static int getWeekNumberOfYear(dynamic date, bool isHijri) { - final dynamic yearEndDate = isHijri - ? HijriDateTime(date.year - 1, 12, 31) - : DateTime(date.year - 1, 12, 31); + final dynamic yearEndDate = + isHijri + ? HijriDateTime(date.year - 1, 12, 31) + : DateTime(date.year - 1, 12, 31); final int dayOfYear = date.difference(yearEndDate).inDays as int; int weekNumber = (dayOfYear - date.weekday + 10) ~/ 7; if (weekNumber < 1) { @@ -945,28 +1078,41 @@ class DateRangePickerHelper { } /// Draws the dashed line between given x positions. - static void drawDashedLine(double xPosition, double yPosition, - double endXPosition, Canvas canvas, Paint painter) { + static void drawDashedLine( + double xPosition, + double yPosition, + double endXPosition, + Canvas canvas, + Paint painter, + ) { const double dashWidth = 4, dashSpace = 5; while (xPosition < endXPosition) { - canvas.drawLine(Offset(xPosition, yPosition), - Offset(xPosition + dashWidth, yPosition), painter); + canvas.drawLine( + Offset(xPosition, yPosition), + Offset(xPosition + dashWidth, yPosition), + painter, + ); xPosition += dashWidth + dashSpace; } } /// Returns the dashed path for teh given path - static Path getDashedPath(Path sourcePath, bool isStartRange, bool isEndRange, - bool isRectSelection) { + static Path getDashedPath( + Path sourcePath, + bool isStartRange, + bool isEndRange, + bool isRectSelection, + ) { final Path dashedPath = Path(); final List array = [6, 5]; final List sourceMetrics = sourcePath.computeMetrics().toList(); final Rect sourceRect = sourcePath.getBounds(); - final double restrictXPosition = isRectSelection - ? isStartRange - ? sourceRect.right - : sourceRect.left - : -1; + final double restrictXPosition = + isRectSelection + ? isStartRange + ? sourceRect.right + : sourceRect.left + : -1; for (int i = 0; i < sourceMetrics.length; i++) { final PathMetric metric = sourceMetrics[i]; double distance = 0; @@ -975,8 +1121,10 @@ class DateRangePickerHelper { while (distance < metric.length) { final double length = array[j]; if (canDrawPath) { - final Path extractedPath = - metric.extractPath(distance, distance + length); + final Path extractedPath = metric.extractPath( + distance, + distance + length, + ); final Rect extractedRect = extractedPath.getBounds(); if ((isStartRange && extractedRect.right != restrictXPosition) || (isEndRange && extractedRect.left != restrictXPosition)) { diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart new file mode 100644 index 000000000..bdc18a303 --- /dev/null +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/theme.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [SfDateRangePickerThemeDataKey] this class provides material2 and material3 +/// themeData. +/// SfDateRangePickerThemeDataKey class extends the 'SfDateRangePickerThemeData' +/// class and customize the appearance of a mapping component based on th color +/// scheme obtained from the provided [BuildContext]. +class SfDateRangePickerThemeKey extends SfDateRangePickerThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the color scheme of the current theme. + SfDateRangePickerThemeKey(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the color scheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get backgroundColor => + Theme.of(context).useMaterial3 + ? (Theme.of(context).brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1)) + : Colors.transparent; + + @override + Color? get headerBackgroundColor => + Theme.of(context).useMaterial3 + ? (Theme.of(context).brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1)) + : Theme.of(context).brightness == Brightness.light + ? Colors.white + : Colors.grey[850]; + + @override + Color? get viewHeaderBackgroundColor => colorScheme.transparent; + + @override + Color? get weekNumberBackgroundColor => colorScheme.onSurface[22]; + + @override + Color? get selectionColor => colorScheme.primary; + + @override + Color? get startRangeSelectionColor => colorScheme.primary; + + @override + Color? get rangeSelectionColor => colorScheme.primary[30]; + + @override + Color? get endRangeSelectionColor => colorScheme.primary; + + @override + Color? get todayHighlightColor => colorScheme.primary; +} 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 d92a45646..2f0a4a2d6 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 @@ -12,41 +12,43 @@ class YearView extends StatefulWidget { /// Constructor for create the year view widget used to hold the year cell /// widgets. const YearView( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - this.monthFormat, - this.isRtl, - this.datePickerTheme, - this.locale, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectionMode, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor, - this.allowViewNavigation, - this.cellBuilder, - this.getPickerStateDetails, - this.view, - this.isHijri, - this.localizations, - this.navigationDirection, - this.width, - this.height, - this.disableDatesCollection, - this.extendableRangeSelectionDirection); + this.visibleDates, + this.cellStyle, + this.minDate, + this.maxDate, + this.enablePastDates, + this.todayHighlightColor, + this.selectionShape, + this.monthFormat, + this.isRtl, + this.datePickerTheme, + this.locale, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectionMode, + this.selectionRadius, + this.selectionNotifier, + this.textScaleFactor, + this.allowViewNavigation, + this.cellBuilder, + this.getPickerStateDetails, + this.view, + this.isHijri, + this.localizations, + this.navigationDirection, + this.width, + this.height, + this.disableDatesCollection, + this.extendableRangeSelectionDirection, { + super.key, + }); /// Defines the year cell style. final dynamic cellStyle; @@ -179,11 +181,13 @@ class _YearViewState extends State { _children = []; widget.getPickerStateDetails(_pickerStateDetails); _selectedDate = _pickerStateDetails.selectedDate; - _selectedDates = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedDates, + ); _selectedRange = _pickerStateDetails.selectedRange; - _selectedRanges = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedRanges, + ); widget.selectionNotifier.addListener(_updateSelection); super.initState(); } @@ -230,10 +234,12 @@ class _YearViewState extends State { double width = widget.width; double height = widget.height; int viewCount = 1; - final bool isHorizontalMultiView = widget.enableMultiView && + final bool isHorizontalMultiView = + widget.enableMultiView && widget.navigationDirection == DateRangePickerNavigationDirection.horizontal; - final bool isVerticalMultiView = widget.enableMultiView && + final bool isVerticalMultiView = + widget.enableMultiView && widget.navigationDirection == DateRangePickerNavigationDirection.vertical; @@ -256,22 +262,27 @@ class _YearViewState extends State { final int viewStartIndex = j * visibleDatesCount; - final double viewStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + - (currentViewIndex * widget.multiViewSpacing); + final double viewStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + + (currentViewIndex * widget.multiViewSpacing); final double viewEndPosition = viewStartPosition + width; double xPosition = viewStartPosition; - double yPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + - (currentViewIndex * widget.multiViewSpacing); + double yPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + + (currentViewIndex * widget.multiViewSpacing); for (int i = 0; i < visibleDatesCount; i++) { int currentIndex = i; if (widget.isRtl) { final int rowIndex = i ~/ YearView.maxColumnCount; - currentIndex = DateRangePickerHelper.getRtlIndex( - YearView.maxColumnCount, i % YearView.maxColumnCount) + + currentIndex = + DateRangePickerHelper.getRtlIndex( + YearView.maxColumnCount, + i % YearView.maxColumnCount, + ) + (rowIndex * YearView.maxColumnCount); } @@ -282,26 +293,44 @@ class _YearViewState extends State { } if ((widget.enableMultiView || widget.isHijri) && - DateRangePickerHelper.isLeadingCellDate(currentIndex, - viewStartIndex, widget.visibleDates, widget.view)) { + DateRangePickerHelper.isLeadingCellDate( + currentIndex, + viewStartIndex, + widget.visibleDates, + widget.view, + )) { xPosition += cellWidth; continue; } final dynamic date = widget.visibleDates[currentIndex]; - 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))) as Widget; + 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, + ), + ), + ) + as Widget; _children.add(child); xPosition += cellWidth; } @@ -316,183 +345,188 @@ class _YearViewState extends State { case DateRangePickerSelectionMode.single: { return _SingleSelectionRenderWidget( - widget.visibleDates, - widget.cellStyle, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.todayHighlightColor, - widget.selectionShape, - widget.monthFormat, - widget.isRtl, - widget.datePickerTheme, - widget.locale, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.allowViewNavigation ? null : _getSelectedDateValue(), - widget.selectionRadius, - widget.selectionNotifier, - widget.textScaleFactor, - widget.width, - widget.height, - widget.view, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.cellStyle, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.todayHighlightColor, + widget.selectionShape, + widget.monthFormat, + widget.isRtl, + widget.datePickerTheme, + widget.locale, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.allowViewNavigation ? null : _getSelectedDateValue(), + widget.selectionRadius, + widget.selectionNotifier, + widget.textScaleFactor, + widget.width, + widget.height, + widget.view, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.disableDatesCollection, + widgets: _children, + ); } case DateRangePickerSelectionMode.multiple: { return _MultiSelectionRenderWidget( - widget.visibleDates, - widget.cellStyle, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.todayHighlightColor, - widget.selectionShape, - widget.monthFormat, - widget.isRtl, - widget.datePickerTheme, - widget.locale, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.allowViewNavigation ? null : _getSelectedDateValue(), - widget.selectionRadius, - widget.selectionNotifier, - widget.textScaleFactor, - widget.width, - widget.height, - widget.view, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.cellStyle, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.todayHighlightColor, + widget.selectionShape, + widget.monthFormat, + widget.isRtl, + widget.datePickerTheme, + widget.locale, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.allowViewNavigation ? null : _getSelectedDateValue(), + widget.selectionRadius, + widget.selectionNotifier, + widget.textScaleFactor, + widget.width, + widget.height, + widget.view, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.disableDatesCollection, + widgets: _children, + ); } case DateRangePickerSelectionMode.range: { return _RangeSelectionRenderWidget( - widget.visibleDates, - widget.cellStyle, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.todayHighlightColor, - widget.selectionShape, - widget.monthFormat, - widget.isRtl, - widget.datePickerTheme, - widget.locale, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.allowViewNavigation ? null : _getSelectedDateValue(), - widget.selectionRadius, - widget.selectionNotifier, - widget.textScaleFactor, - widget.width, - widget.height, - widget.view, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.cellStyle, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.todayHighlightColor, + widget.selectionShape, + widget.monthFormat, + widget.isRtl, + widget.datePickerTheme, + widget.locale, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.allowViewNavigation ? null : _getSelectedDateValue(), + widget.selectionRadius, + widget.selectionNotifier, + widget.textScaleFactor, + widget.width, + widget.height, + widget.view, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.disableDatesCollection, + widgets: _children, + ); } case DateRangePickerSelectionMode.extendableRange: { return _ExtendableRangeSelectionRenderWidget( - widget.visibleDates, - widget.cellStyle, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.todayHighlightColor, - widget.selectionShape, - widget.monthFormat, - widget.isRtl, - widget.datePickerTheme, - widget.locale, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.allowViewNavigation ? null : _getSelectedDateValue(), - widget.selectionRadius, - widget.selectionNotifier, - widget.textScaleFactor, - widget.width, - widget.height, - widget.view, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.disableDatesCollection, - widget.extendableRangeSelectionDirection, - widgets: _children); + widget.visibleDates, + widget.cellStyle, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.todayHighlightColor, + widget.selectionShape, + widget.monthFormat, + widget.isRtl, + widget.datePickerTheme, + widget.locale, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.allowViewNavigation ? null : _getSelectedDateValue(), + widget.selectionRadius, + widget.selectionNotifier, + widget.textScaleFactor, + widget.width, + widget.height, + widget.view, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.disableDatesCollection, + widget.extendableRangeSelectionDirection, + widgets: _children, + ); } case DateRangePickerSelectionMode.multiRange: { return _MultiRangeSelectionRenderWidget( - widget.visibleDates, - widget.cellStyle, - widget.minDate, - widget.maxDate, - widget.enablePastDates, - widget.todayHighlightColor, - widget.selectionShape, - widget.monthFormat, - widget.isRtl, - widget.datePickerTheme, - widget.locale, - widget.mouseHoverPosition, - widget.enableMultiView, - widget.multiViewSpacing, - widget.selectionTextStyle, - widget.rangeTextStyle, - widget.selectionColor, - widget.startRangeSelectionColor, - widget.endRangeSelectionColor, - widget.rangeSelectionColor, - widget.allowViewNavigation ? null : _getSelectedDateValue(), - widget.selectionRadius, - widget.selectionNotifier, - widget.textScaleFactor, - widget.width, - widget.height, - widget.view, - widget.isHijri, - widget.localizations, - widget.navigationDirection, - widget.disableDatesCollection, - widgets: _children); + widget.visibleDates, + widget.cellStyle, + widget.minDate, + widget.maxDate, + widget.enablePastDates, + widget.todayHighlightColor, + widget.selectionShape, + widget.monthFormat, + widget.isRtl, + widget.datePickerTheme, + widget.locale, + widget.mouseHoverPosition, + widget.enableMultiView, + widget.multiViewSpacing, + widget.selectionTextStyle, + widget.rangeTextStyle, + widget.selectionColor, + widget.startRangeSelectionColor, + widget.endRangeSelectionColor, + widget.rangeSelectionColor, + widget.allowViewNavigation ? null : _getSelectedDateValue(), + widget.selectionRadius, + widget.selectionNotifier, + widget.textScaleFactor, + widget.width, + widget.height, + widget.view, + widget.isHijri, + widget.localizations, + widget.navigationDirection, + widget.disableDatesCollection, + widgets: _children, + ); } } } @@ -501,11 +535,13 @@ class _YearViewState extends State { widget.getPickerStateDetails(_pickerStateDetails); if (widget.allowViewNavigation) { _selectedDate = _pickerStateDetails.selectedDate; - _selectedDates = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedDates, + ); _selectedRange = _pickerStateDetails.selectedRange; - _selectedRanges = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedRanges, + ); return; } @@ -515,11 +551,13 @@ class _YearViewState extends State { _children.clear(); _selectedDate = _pickerStateDetails.selectedDate; - _selectedDates = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates); + _selectedDates = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedDates, + ); _selectedRange = _pickerStateDetails.selectedRange; - _selectedRanges = - DateRangePickerHelper.cloneList(_pickerStateDetails.selectedRanges); + _selectedRanges = DateRangePickerHelper.cloneList( + _pickerStateDetails.selectedRanges, + ); if (!isNeedSetState) { return; @@ -562,58 +600,64 @@ class _YearViewState extends State { case DateRangePickerSelectionMode.multiple: { return DateRangePickerHelper.isDateCollectionEquals( - _selectedDates, _pickerStateDetails.selectedDates); + _selectedDates, + _pickerStateDetails.selectedDates, + ); } case DateRangePickerSelectionMode.range: case DateRangePickerSelectionMode.extendableRange: { return DateRangePickerHelper.isRangeEquals( - _selectedRange, _pickerStateDetails.selectedRange); + _selectedRange, + _pickerStateDetails.selectedRange, + ); } case DateRangePickerSelectionMode.multiRange: { return DateRangePickerHelper.isDateRangesEquals( - _selectedRanges, _pickerStateDetails.selectedRanges); + _selectedRanges, + _pickerStateDetails.selectedRanges, + ); } } } } class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { - _SingleSelectionRenderWidget( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - this.monthFormat, - this.isRtl, - this.datePickerTheme, - this.locale, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectedDate, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor, - this.width, - this.height, - this.view, - this.isHijri, - this.localizations, - this.navigationDirection, - this.disableDatesCollection, - {required List widgets}) - : super(children: widgets); + const _SingleSelectionRenderWidget( + this.visibleDates, + this.cellStyle, + this.minDate, + this.maxDate, + this.enablePastDates, + this.todayHighlightColor, + this.selectionShape, + this.monthFormat, + this.isRtl, + this.datePickerTheme, + this.locale, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectedDate, + this.selectionRadius, + this.selectionNotifier, + this.textScaleFactor, + this.width, + this.height, + this.view, + this.isHijri, + this.localizations, + this.navigationDirection, + this.disableDatesCollection, { + required List widgets, + }) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; @@ -704,41 +748,44 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { @override _SingleSelectionRenderObject createRenderObject(BuildContext context) { return _SingleSelectionRenderObject( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - selectedDate, - disableDatesCollection); + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + selectedDate, + disableDatesCollection, + ); } @override void updateRenderObject( - BuildContext context, _SingleSelectionRenderObject renderObject) { + BuildContext context, + _SingleSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..cellStyle = cellStyle @@ -774,40 +821,40 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { } class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MultiSelectionRenderWidget( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - this.monthFormat, - this.isRtl, - this.datePickerTheme, - this.locale, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectedDates, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor, - this.width, - this.height, - this.view, - this.isHijri, - this.localizations, - this.navigationDirection, - this.disableDatesCollection, - {required List widgets}) - : super(children: widgets); + const _MultiSelectionRenderWidget( + this.visibleDates, + this.cellStyle, + this.minDate, + this.maxDate, + this.enablePastDates, + this.todayHighlightColor, + this.selectionShape, + this.monthFormat, + this.isRtl, + this.datePickerTheme, + this.locale, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectedDates, + this.selectionRadius, + this.selectionNotifier, + this.textScaleFactor, + this.width, + this.height, + this.view, + this.isHijri, + this.localizations, + this.navigationDirection, + this.disableDatesCollection, { + required List widgets, + }) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; @@ -898,41 +945,44 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { @override _MultipleSelectionRenderObject createRenderObject(BuildContext context) { return _MultipleSelectionRenderObject( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - selectedDates, - disableDatesCollection); + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + selectedDates, + disableDatesCollection, + ); } @override void updateRenderObject( - BuildContext context, _MultipleSelectionRenderObject renderObject) { + BuildContext context, + _MultipleSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..cellStyle = cellStyle @@ -968,40 +1018,40 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { } class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { - _RangeSelectionRenderWidget( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - this.monthFormat, - this.isRtl, - this.datePickerTheme, - this.locale, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectedRange, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor, - this.width, - this.height, - this.view, - this.isHijri, - this.localizations, - this.navigationDirection, - this.disableDatesCollection, - {required List widgets}) - : super(children: widgets); + const _RangeSelectionRenderWidget( + this.visibleDates, + this.cellStyle, + this.minDate, + this.maxDate, + this.enablePastDates, + this.todayHighlightColor, + this.selectionShape, + this.monthFormat, + this.isRtl, + this.datePickerTheme, + this.locale, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectedRange, + this.selectionRadius, + this.selectionNotifier, + this.textScaleFactor, + this.width, + this.height, + this.view, + this.isHijri, + this.localizations, + this.navigationDirection, + this.disableDatesCollection, { + required List widgets, + }) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; @@ -1092,41 +1142,44 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { @override _RangeSelectionRenderObject createRenderObject(BuildContext context) { return _RangeSelectionRenderObject( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - selectedRange, - disableDatesCollection); + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + selectedRange, + disableDatesCollection, + ); } @override void updateRenderObject( - BuildContext context, _RangeSelectionRenderObject renderObject) { + BuildContext context, + _RangeSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..cellStyle = cellStyle @@ -1163,41 +1216,41 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { class _ExtendableRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { - _ExtendableRangeSelectionRenderWidget( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - this.monthFormat, - this.isRtl, - this.datePickerTheme, - this.locale, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectedRange, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor, - this.width, - this.height, - this.view, - this.isHijri, - this.localizations, - this.navigationDirection, - this.disableDatesCollection, - this.extendableRangeSelectionDirection, - {required List widgets}) - : super(children: widgets); + const _ExtendableRangeSelectionRenderWidget( + this.visibleDates, + this.cellStyle, + this.minDate, + this.maxDate, + this.enablePastDates, + this.todayHighlightColor, + this.selectionShape, + this.monthFormat, + this.isRtl, + this.datePickerTheme, + this.locale, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectedRange, + this.selectionRadius, + this.selectionNotifier, + this.textScaleFactor, + this.width, + this.height, + this.view, + this.isHijri, + this.localizations, + this.navigationDirection, + this.disableDatesCollection, + this.extendableRangeSelectionDirection, { + required List widgets, + }) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; @@ -1289,44 +1342,48 @@ class _ExtendableRangeSelectionRenderWidget @override _ExtendableRangeSelectionRenderObject createRenderObject( - BuildContext context) { + BuildContext context, + ) { return _ExtendableRangeSelectionRenderObject( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - selectedRange, - disableDatesCollection, - extendableRangeSelectionDirection); + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + selectedRange, + disableDatesCollection, + extendableRangeSelectionDirection, + ); } @override - void updateRenderObject(BuildContext context, - _ExtendableRangeSelectionRenderObject renderObject) { + void updateRenderObject( + BuildContext context, + _ExtendableRangeSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..cellStyle = cellStyle @@ -1363,40 +1420,40 @@ class _ExtendableRangeSelectionRenderWidget } class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { - _MultiRangeSelectionRenderWidget( - this.visibleDates, - this.cellStyle, - this.minDate, - this.maxDate, - this.enablePastDates, - this.todayHighlightColor, - this.selectionShape, - this.monthFormat, - this.isRtl, - this.datePickerTheme, - this.locale, - this.mouseHoverPosition, - this.enableMultiView, - this.multiViewSpacing, - this.selectionTextStyle, - this.rangeTextStyle, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectedRanges, - this.selectionRadius, - this.selectionNotifier, - this.textScaleFactor, - this.width, - this.height, - this.view, - this.isHijri, - this.localizations, - this.navigationDirection, - this.disableDatesCollection, - {required List widgets}) - : super(children: widgets); + const _MultiRangeSelectionRenderWidget( + this.visibleDates, + this.cellStyle, + this.minDate, + this.maxDate, + this.enablePastDates, + this.todayHighlightColor, + this.selectionShape, + this.monthFormat, + this.isRtl, + this.datePickerTheme, + this.locale, + this.mouseHoverPosition, + this.enableMultiView, + this.multiViewSpacing, + this.selectionTextStyle, + this.rangeTextStyle, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectedRanges, + this.selectionRadius, + this.selectionNotifier, + this.textScaleFactor, + this.width, + this.height, + this.view, + this.isHijri, + this.localizations, + this.navigationDirection, + this.disableDatesCollection, { + required List widgets, + }) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; @@ -1488,41 +1545,44 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { @override _MultiRangeSelectionRenderObject createRenderObject(BuildContext context) { return _MultiRangeSelectionRenderObject( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - selectedRanges, - disableDatesCollection); + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + selectedRanges, + disableDatesCollection, + ); } @override void updateRenderObject( - BuildContext context, _MultiRangeSelectionRenderObject renderObject) { + BuildContext context, + _MultiRangeSelectionRenderObject renderObject, + ) { renderObject ..visibleDates = visibleDates ..cellStyle = cellStyle @@ -1562,35 +1622,36 @@ class _DatePickerParentData extends ContainerBoxParentData {} abstract class _IYearViewRenderObject extends RenderBox with ContainerRenderObjectMixin { _IYearViewRenderObject( - this._visibleDates, - this._cellStyle, - this._minDate, - this._maxDate, - this._enablePastDates, - this._todayHighlightColor, - this._selectionShape, - this._isRtl, - this._datePickerTheme, - this._mouseHoverPosition, - this._enableMultiView, - this._multiViewSpacing, - this._selectionTextStyle, - this._rangeTextStyle, - this._selectionColor, - this._startRangeSelectionColor, - this._endRangeSelectionColor, - this._rangeSelectionColor, - this._selectionRadius, - this._textScaleFactor, - this._width, - this._height, - this._monthFormat, - this._locale, - this._view, - this._isHijri, - this._navigationDirection, - this.localizations, - this._disableDatesCollection); + this._visibleDates, + this._cellStyle, + this._minDate, + this._maxDate, + this._enablePastDates, + this._todayHighlightColor, + this._selectionShape, + this._isRtl, + this._datePickerTheme, + this._mouseHoverPosition, + this._enableMultiView, + this._multiViewSpacing, + this._selectionTextStyle, + this._rangeTextStyle, + this._selectionColor, + this._startRangeSelectionColor, + this._endRangeSelectionColor, + this._rangeSelectionColor, + this._selectionRadius, + this._textScaleFactor, + this._width, + this._height, + this._monthFormat, + this._locale, + this._view, + this._isHijri, + this._navigationDirection, + this.localizations, + this._disableDatesCollection, + ); DateRangePickerNavigationDirection _navigationDirection; @@ -2061,10 +2122,10 @@ abstract class _IYearViewRenderObject extends RenderBox /// Used to draw year cell text in month view. final TextPainter _textPainter = TextPainter( - textAlign: TextAlign.start, - textDirection: TextDirection.ltr, - maxLines: 2, - textWidthBasis: TextWidthBasis.longestLine); + textDirection: TextDirection.ltr, + maxLines: 2, + textWidthBasis: TextWidthBasis.longestLine, + ); /// Used to paint the selection of year cell and today highlight on all /// the selection mode. @@ -2102,8 +2163,10 @@ abstract class _IYearViewRenderObject extends RenderBox @override void performLayout() { final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); + size = Size( + widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height, + ); RenderBox? child = firstChild; if (child == null) { return; @@ -2123,11 +2186,14 @@ abstract class _IYearViewRenderObject extends RenderBox final double cellWidth = currentWidth / YearView.maxColumnCount; final double cellHeight = currentHeight / YearView.maxRowCount; while (child != null) { - child.layout(constraints.copyWith( + child.layout( + constraints.copyWith( minHeight: cellHeight, maxHeight: cellHeight, minWidth: cellWidth, - maxWidth: cellWidth)); + maxWidth: cellWidth, + ), + ); child = childAfter(child); } } @@ -2152,9 +2218,10 @@ abstract class _IYearViewRenderObject extends RenderBox 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 SemanticsNode newChild = + _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); @@ -2200,16 +2267,17 @@ abstract class _IYearViewRenderObject extends RenderBox List getSelectedIndex(int viewStartIndex, int viewEndIndex); void drawSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double centerYPosition, - double xPosition, - double yPosition, - TextSpan yearText); + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText, + ); /// draw selection when the cell have custom widget. void drawCustomCellSelection(Canvas canvas, Rect rect, int index); @@ -2226,9 +2294,11 @@ abstract class _IYearViewRenderObject extends RenderBox double width = size.width; double height = size.height; double webUIPadding = 0; - final bool isHorizontalMultiView = _enableMultiView && + final bool isHorizontalMultiView = + _enableMultiView && _navigationDirection == DateRangePickerNavigationDirection.horizontal; - final bool isVerticalMultiView = _enableMultiView && + final bool isVerticalMultiView = + _enableMultiView && _navigationDirection == DateRangePickerNavigationDirection.vertical; if (isHorizontalMultiView) { webUIPadding = _multiViewSpacing; @@ -2248,54 +2318,99 @@ abstract class _IYearViewRenderObject extends RenderBox isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; left = isRtl ? width - cellWidth : 0; top = 0; - final double startXPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + (currentViewIndex * webUIPadding); - final double startYPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + final double startXPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double startYPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); final int startIndex = j * datesCount; for (int i = 0; i < datesCount; i++) { final dynamic date = visibleDates[startIndex + i]; if (DateRangePickerHelper.isLeadingCellDate( - startIndex + i, startIndex, _visibleDates, _view)) { + startIndex + i, + startIndex, + _visibleDates, + _view, + )) { leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - isRtl, left, top, cellWidth, cellHeight, width); + isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; continue; } if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - date, _minDate, _maxDate, _enablePastDates, _view, _isHijri) || + date, + _minDate, + _maxDate, + _enablePastDates, + _view, + _isHijri, + ) || DateRangePickerHelper.isDateWithInVisibleDates( - visibleDates, disableDatesCollection, date)) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(startXPosition + left, startYPosition + top, - cellWidth, cellHeight), - properties: SemanticsProperties( - label: '${getCellSemanticsText(date)}Disabled cell', - textDirection: TextDirection.ltr, + visibleDates, + disableDatesCollection, + date, + )) { + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + startXPosition + left, + startYPosition + top, + cellWidth, + cellHeight, + ), + properties: SemanticsProperties( + label: '${getCellSemanticsText(date)}Disabled cell', + textDirection: TextDirection.ltr, + ), ), - )); + ); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - isRtl, left, top, cellWidth, cellHeight, width); + isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; continue; } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(startXPosition + left, startYPosition + top, - cellWidth, cellHeight), - properties: SemanticsProperties( - label: getCellSemanticsText(date), - textDirection: TextDirection.ltr, + semanticsBuilder.add( + CustomPainterSemantics( + rect: Rect.fromLTWH( + startXPosition + left, + startYPosition + top, + cellWidth, + cellHeight, + ), + properties: SemanticsProperties( + label: getCellSemanticsText(date), + textDirection: TextDirection.ltr, + ), ), - )); + ); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( - isRtl, left, top, cellWidth, cellHeight, width); + isRtl, + left, + top, + cellWidth, + cellHeight, + width, + ); left = leftAndTopValue['left']!; top = leftAndTopValue['top']!; } @@ -2305,8 +2420,13 @@ abstract class _IYearViewRenderObject extends RenderBox } /// Return list of int value in between start and end date index value. - List _getRangeIndex(dynamic startDate, dynamic endDate, - DateRangePickerView pickerView, int viewStartIndex, int viewEndIndex) { + List _getRangeIndex( + dynamic startDate, + dynamic endDate, + DateRangePickerView pickerView, + int viewStartIndex, + int viewEndIndex, + ) { int startIndex = -1; int endIndex = -1; final List selectedIndex = []; @@ -2317,7 +2437,11 @@ abstract class _IYearViewRenderObject extends RenderBox dynamic endRangeDate; if (endDate != null) { endRangeDate = DateRangePickerHelper.getDate( - endDate.year, endDate.month, endDate.day, _isHijri); + endDate.year, + endDate.month, + endDate.day, + _isHijri, + ); } /// Check the start date as before of end date, if not then swap @@ -2330,7 +2454,10 @@ abstract class _IYearViewRenderObject extends RenderBox final dynamic viewStartDate = visibleDates[viewStartIndex]; final dynamic viewEndDate = DateRangePickerHelper.getLastDate( - visibleDates[viewEndIndex], pickerView, _isHijri); + visibleDates[viewEndIndex], + pickerView, + _isHijri, + ); if (startDate != null) { /// Assign start index as -1 when the start date before view start date. if (viewStartDate.isAfter(startDate) == true && @@ -2338,8 +2465,12 @@ abstract class _IYearViewRenderObject extends RenderBox startIndex = -1; } else { startIndex = DateRangePickerHelper.getDateCellIndex( - visibleDates, startDate, pickerView, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + visibleDates, + startDate, + pickerView, + viewStartIndex: viewStartIndex, + viewEndIndex: viewEndIndex, + ); } } @@ -2351,8 +2482,12 @@ abstract class _IYearViewRenderObject extends RenderBox endIndex = viewEndIndex + 1; } else { endIndex = DateRangePickerHelper.getDateCellIndex( - visibleDates, endRangeDate, _view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + visibleDates, + endRangeDate, + _view, + viewStartIndex: viewStartIndex, + viewEndIndex: viewEndIndex, + ); } } @@ -2383,7 +2518,10 @@ abstract class _IYearViewRenderObject extends RenderBox monthFormat == null || monthFormat!.isEmpty ? 'MMM' : monthFormat!; if (isHijri) { return DateRangePickerHelper.getHijriMonthText( - date, localizations, format); + date, + localizations, + format, + ); } else { return DateFormat(format, locale.toString()).format(date); } @@ -2400,7 +2538,10 @@ abstract class _IYearViewRenderObject extends RenderBox if (_view == DateRangePickerView.year) { if (isHijri) { return DateRangePickerHelper.getHijriMonthText( - date, localizations, 'MMMM') + + date, + localizations, + 'MMMM', + ) + date.year.toString(); } else { return DateFormat('MMMM yyyy').format(date); @@ -2415,40 +2556,43 @@ abstract class _IYearViewRenderObject extends RenderBox } void _addMouseHovering( - Canvas canvas, - double cellWidth, - double cellHeight, - double centerYPosition, - int currentViewIndex, - double width, - double highlightPadding, - dynamic date, - double selectionPadding, - double textHalfHeight, - double webUIPadding, - double xOffset, - double xPosition, - double yOffset, - double yPosition) { + Canvas canvas, + double cellWidth, + double cellHeight, + double centerYPosition, + int currentViewIndex, + double width, + double highlightPadding, + dynamic date, + double selectionPadding, + double textHalfHeight, + double webUIPadding, + double xOffset, + double xPosition, + double yOffset, + double yPosition, + ) { if (xPosition <= _mouseHoverPosition.value!.offset!.dx && xPosition + cellWidth >= _mouseHoverPosition.value!.offset!.dx && yPosition <= _mouseHoverPosition.value!.offset!.dy && yPosition + cellHeight >= _mouseHoverPosition.value!.offset!.dy) { _todayHighlightPaint.style = PaintingStyle.fill; _todayHighlightPaint.strokeWidth = 2; - _todayHighlightPaint.color = selectionColor != null - ? selectionColor!.withOpacity(0.4) - : datePickerTheme.selectionColor!.withOpacity(0.4); + _todayHighlightPaint.color = + selectionColor != null + ? selectionColor!.withValues(alpha: 0.4) + : datePickerTheme.selectionColor!.withValues(alpha: 0.4); if (centerYPosition - textHalfHeight < highlightPadding / 2) { highlightPadding = (centerYPosition - textHalfHeight / 2) - 1; } final Rect rect = Rect.fromLTRB( - xPosition + selectionPadding, - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + cellWidth - selectionPadding, - yPosition + centerYPosition + highlightPadding + textHalfHeight); + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); double cornerRadius = rect.height / 2; switch (selectionShape) { case DateRangePickerSelectionShape.rectangle: @@ -2461,26 +2605,28 @@ abstract class _IYearViewRenderObject extends RenderBox } canvas.drawRRect( - RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), - _todayHighlightPaint); + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint, + ); } } /// Add the hovering effect when the selection mode set as extendable and the /// hovering date extends the range. void _addRangeHoverEffect( - Canvas canvas, - double xPosition, - double yPosition, - int currentIndex, - List? hoveringIndex, - _IYearViewRenderObject yearView, - double centerYPosition, - double textHalfHeight, - double selectionPadding, - double highlightPadding, - double cellWidth, - double cellHeight) { + Canvas canvas, + double xPosition, + double yPosition, + int currentIndex, + List? hoveringIndex, + _IYearViewRenderObject yearView, + double centerYPosition, + double textHalfHeight, + double selectionPadding, + double highlightPadding, + double cellWidth, + double cellHeight, + ) { if (hoveringIndex == null || hoveringIndex.isEmpty) { return; } @@ -2507,44 +2653,69 @@ abstract class _IYearViewRenderObject extends RenderBox final double endYPosition = yPosition + centerYPosition + highlightPadding + textHalfHeight; final double endXPosition = xPosition + cellWidth; - final double cornerRadius = isBetweenRange - ? 0 - : (selectionShape == DateRangePickerSelectionShape.circle - ? (endYPosition - startYPosition) / 2 - : 3); + final double cornerRadius = + isBetweenRange + ? 0 + : (selectionShape == DateRangePickerSelectionShape.circle + ? (endYPosition - startYPosition) / 2 + : 3); _todayHighlightPaint.style = PaintingStyle.stroke; _todayHighlightPaint.strokeWidth = 1.0; - _todayHighlightPaint.color = selectionColor != null - ? selectionColor!.withOpacity(0.4) - : datePickerTheme.selectionColor!.withOpacity(0.4); + _todayHighlightPaint.color = + selectionColor != null + ? selectionColor!.withValues(alpha: 0.4) + : datePickerTheme.selectionColor!.withValues(alpha: 0.4); if (isStartRange) { - rect = Rect.fromLTRB(endXPosition - cornerRadius, startYPosition, - endXPosition, endYPosition); + rect = Rect.fromLTRB( + endXPosition - cornerRadius, + startYPosition, + endXPosition, + endYPosition, + ); } else if (isEndRange) { rect = Rect.fromLTRB( - xPosition, startYPosition, xPosition + cornerRadius, endYPosition); + xPosition, + startYPosition, + xPosition + cornerRadius, + endYPosition, + ); } else if (isBetweenRange) { rect = Rect.fromLTRB( - xPosition, startYPosition, xPosition + cellWidth, endYPosition); + xPosition, + startYPosition, + xPosition + cellWidth, + endYPosition, + ); } DateRangePickerHelper.drawDashedLine( - rect.left, rect.top, rect.right, canvas, _todayHighlightPaint); + rect.left, + rect.top, + rect.right, + canvas, + _todayHighlightPaint, + ); DateRangePickerHelper.drawDashedLine( - rect.left, rect.bottom, rect.right, canvas, _todayHighlightPaint); + rect.left, + rect.bottom, + rect.right, + canvas, + _todayHighlightPaint, + ); } void _drawTodayHighlight( - Canvas canvas, - double cellWidth, - double cellHeight, - double centerYPosition, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double xPosition, - double yPosition) { + Canvas canvas, + double cellWidth, + double cellHeight, + double centerYPosition, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double xPosition, + double yPosition, + ) { _todayHighlightPaint.color = todayHighlightColor ?? datePickerTheme.todayHighlightColor!; _todayHighlightPaint.isAntiAlias = true; @@ -2557,10 +2728,11 @@ abstract class _IYearViewRenderObject extends RenderBox } final Rect rect = Rect.fromLTRB( - xPosition + selectionPadding, - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + cellWidth - selectionPadding, - yPosition + centerYPosition + highlightPadding + textHalfHeight); + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); double cornerRadius = rect.height / 2; switch (selectionShape) { case DateRangePickerSelectionShape.rectangle: @@ -2573,54 +2745,69 @@ abstract class _IYearViewRenderObject extends RenderBox } canvas.drawRRect( - RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), - _todayHighlightPaint); + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint, + ); } void _drawYearDecoration( - Canvas canvas, - Decoration yearDecoration, - double xPosition, - double yPosition, - double decorationPadding, - double cellWidth, - double cellHeight) { - final BoxPainter boxPainter = - yearDecoration.createBoxPainter(markNeedsPaint); + Canvas canvas, + Decoration yearDecoration, + double xPosition, + double yPosition, + double decorationPadding, + double cellWidth, + double cellHeight, + ) { + final BoxPainter boxPainter = yearDecoration.createBoxPainter( + markNeedsPaint, + ); boxPainter.paint( - canvas, - Offset(xPosition + decorationPadding, yPosition + decorationPadding), - ImageConfiguration( - size: Size(cellWidth - (2 * decorationPadding), - cellHeight - (2 * decorationPadding)))); + canvas, + Offset(xPosition + decorationPadding, yPosition + decorationPadding), + ImageConfiguration( + size: Size( + cellWidth - (2 * decorationPadding), + cellHeight - (2 * decorationPadding), + ), + ), + ); } - TextStyle _updateCellTextStyle(int j, bool isCurrentDate, bool isSelected, - bool isEnableDate, bool isActiveDate, bool isDisabledDate) { + TextStyle _updateCellTextStyle( + int j, + bool isCurrentDate, + bool isSelected, + bool isEnableDate, + bool isActiveDate, + bool isDisabledDate, + ) { if (!isEnableDate || isDisabledDate) { - return cellStyle.disabledDatesTextStyle as TextStyle? ?? - datePickerTheme.disabledCellTextStyle!; + return datePickerTheme.disabledCellTextStyle!; } if (isSelected) { - return selectionTextStyle ?? datePickerTheme.selectionTextStyle!; + return datePickerTheme.selectionTextStyle!; } if (isCurrentDate) { - return cellStyle.todayTextStyle as TextStyle? ?? - datePickerTheme.todayCellTextStyle!; + return datePickerTheme.todayCellTextStyle!; } if (!isActiveDate && !_isHijri) { - return cellStyle.leadingDatesTextStyle as TextStyle? ?? - datePickerTheme.leadingCellTextStyle!; + return datePickerTheme.leadingCellTextStyle!; } - return cellStyle.textStyle as TextStyle? ?? datePickerTheme.cellTextStyle!; + return datePickerTheme.cellTextStyle!; } - Decoration? _updateCellDecoration(int j, bool isCurrentDate, - bool isEnableDate, bool isActiveDate, bool isDisabledDate) { + Decoration? _updateCellDecoration( + int j, + bool isCurrentDate, + bool isEnableDate, + bool isActiveDate, + bool isDisabledDate, + ) { if ((!isEnableDate || isDisabledDate) && cellStyle.disabledDatesDecoration != null) { return cellStyle.disabledDatesDecoration as Decoration?; @@ -2642,66 +2829,67 @@ abstract class _IYearViewRenderObject extends RenderBox class _SingleSelectionRenderObject extends _IYearViewRenderObject { _SingleSelectionRenderObject( - List visibleDates, - dynamic cellStyle, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - Color? todayHighlightColor, - DateRangePickerSelectionShape selectionShape, - bool isRtl, - SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - double selectionRadius, - double textScaleFactor, - double width, - double height, - String? monthFormat, - Locale locale, - DateRangePickerView view, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedDate, - List? disableDatesCollection) - : super( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - disableDatesCollection); + List visibleDates, + dynamic cellStyle, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + Color? todayHighlightColor, + DateRangePickerSelectionShape selectionShape, + bool isRtl, + SfDateRangePickerThemeData datePickerTheme, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + double selectionRadius, + double textScaleFactor, + double width, + double height, + String? monthFormat, + Locale locale, + DateRangePickerView view, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedDate, + List? disableDatesCollection, + ) : super( + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + disableDatesCollection, + ); dynamic _selectedDate; @@ -2727,16 +2915,17 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { @override void drawSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double centerYPosition, - double xPosition, - double yPosition, - TextSpan yearText) { + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText, + ) { _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -2746,10 +2935,11 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { } final Rect rect = Rect.fromLTRB( - xPosition + selectionPadding, - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + cellWidth - selectionPadding, - yPosition + centerYPosition + highlightPadding + textHalfHeight); + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); final double cornerRadius = selectionShape == DateRangePickerSelectionShape.circle ? rect.height / 2 @@ -2758,8 +2948,9 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { selectionColor ?? datePickerTheme.selectionColor!; canvas.drawRRect( - RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), - _todayHighlightPaint); + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint, + ); } @override @@ -2779,8 +2970,12 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { } final int index = DateRangePickerHelper.getDateCellIndex( - visibleDates, _selectedDate, _view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + visibleDates, + _selectedDate, + _view, + viewStartIndex: viewStartIndex, + viewEndIndex: viewEndIndex, + ); if (index != -1) { selectedIndex.add(index); } @@ -2796,66 +2991,67 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { class _MultipleSelectionRenderObject extends _IYearViewRenderObject { _MultipleSelectionRenderObject( - List visibleDates, - dynamic cellStyle, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - Color? todayHighlightColor, - DateRangePickerSelectionShape selectionShape, - bool isRtl, - SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - double selectionRadius, - double textScaleFactor, - double width, - double height, - String? monthFormat, - Locale locale, - DateRangePickerView view, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedDates, - List? disableDatesCollection) - : super( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - disableDatesCollection); + List visibleDates, + dynamic cellStyle, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + Color? todayHighlightColor, + DateRangePickerSelectionShape selectionShape, + bool isRtl, + SfDateRangePickerThemeData datePickerTheme, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + double selectionRadius, + double textScaleFactor, + double width, + double height, + String? monthFormat, + Locale locale, + DateRangePickerView view, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedDates, + List? disableDatesCollection, + ) : super( + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + disableDatesCollection, + ); List? _selectedDates; @@ -2881,16 +3077,17 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { @override void drawSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double centerYPosition, - double xPosition, - double yPosition, - TextSpan yearText) { + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText, + ) { _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -2900,10 +3097,11 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { } final Rect rect = Rect.fromLTRB( - xPosition + selectionPadding, - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + cellWidth - selectionPadding, - yPosition + centerYPosition + highlightPadding + textHalfHeight); + xPosition + selectionPadding, + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + cellWidth - selectionPadding, + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); final double cornerRadius = selectionShape == DateRangePickerSelectionShape.circle ? rect.height / 2 @@ -2912,8 +3110,9 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { selectionColor ?? datePickerTheme.selectionColor!; canvas.drawRRect( - RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), - _todayHighlightPaint); + RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), + _todayHighlightPaint, + ); } @override @@ -2933,8 +3132,12 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { } for (int i = 0; i < _selectedDates!.length; i++) { final int index = DateRangePickerHelper.getDateCellIndex( - visibleDates, _selectedDates![i], _view, - viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); + visibleDates, + _selectedDates![i], + _view, + viewStartIndex: viewStartIndex, + viewEndIndex: viewEndIndex, + ); if (index != -1) { selectedIndex.add(index); } @@ -2951,66 +3154,67 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { class _RangeSelectionRenderObject extends _IYearViewRenderObject { _RangeSelectionRenderObject( - List visibleDates, - dynamic cellStyle, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - Color? todayHighlightColor, - DateRangePickerSelectionShape selectionShape, - bool isRtl, - SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - double selectionRadius, - double textScaleFactor, - double width, - double height, - String? monthFormat, - Locale locale, - DateRangePickerView view, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedRange, - List? disableDatesCollection) - : super( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - disableDatesCollection); + List visibleDates, + dynamic cellStyle, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + Color? todayHighlightColor, + DateRangePickerSelectionShape selectionShape, + bool isRtl, + SfDateRangePickerThemeData datePickerTheme, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + double selectionRadius, + double textScaleFactor, + double width, + double height, + String? monthFormat, + Locale locale, + DateRangePickerView view, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedRange, + List? disableDatesCollection, + ) : super( + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + disableDatesCollection, + ); dynamic _selectedRange; @@ -3039,16 +3243,17 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { @override void drawSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double centerYPosition, - double xPosition, - double yPosition, - TextSpan yearText) { + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText, + ) { _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -3064,17 +3269,19 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { final bool isBetweenRange = selectionDetails[3]; final Rect rect = Rect.fromLTRB( - xPosition + (isBetweenRange || isEndRange ? 0 : selectionPadding), - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + - cellWidth - - (isBetweenRange || isStartRange ? 0 : selectionPadding), - yPosition + centerYPosition + highlightPadding + textHalfHeight); - final double cornerRadius = isBetweenRange - ? 0 - : (selectionShape == DateRangePickerSelectionShape.circle - ? rect.height / 2 - : 3); + xPosition + (isBetweenRange || isEndRange ? 0 : selectionPadding), + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + + cellWidth - + (isBetweenRange || isStartRange ? 0 : selectionPadding), + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); + final double cornerRadius = + isBetweenRange + ? 0 + : (selectionShape == DateRangePickerSelectionShape.circle + ? rect.height / 2 + : 3); final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; if (isSelectedDate) { @@ -3086,25 +3293,28 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { } else if (isBetweenRange) { yearText = TextSpan( text: yearText.text, - style: rangeTextStyle ?? datePickerTheme.rangeSelectionTextStyle, + style: datePickerTheme.rangeSelectionTextStyle, ); _todayHighlightPaint.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _textPainter.text = yearText; - _textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + _textPainter.layout(maxWidth: cellWidth); } else if (isEndRange) { _todayHighlightPaint.color = endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRRect( - RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(leftRadius), - bottomLeft: Radius.circular(leftRadius), - bottomRight: Radius.circular(rightRadius), - topRight: Radius.circular(rightRadius)), - _todayHighlightPaint); + RRect.fromRectAndCorners( + rect, + topLeft: Radius.circular(leftRadius), + bottomLeft: Radius.circular(leftRadius), + bottomRight: Radius.circular(rightRadius), + topRight: Radius.circular(rightRadius), + ), + _todayHighlightPaint, + ); } @override @@ -3182,7 +3392,8 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { final dynamic startDate = _selectedRange.startDate; final dynamic endDate = _selectedRange.endDate ?? _selectedRange.startDate; _selectedIndex.addAll( - _getRangeIndex(startDate, endDate, view, viewStartIndex, viewEndIndex)); + _getRangeIndex(startDate, endDate, view, viewStartIndex, viewEndIndex), + ); return _selectedIndex; } @@ -3200,67 +3411,68 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { _ExtendableRangeSelectionRenderObject( - List visibleDates, - dynamic cellStyle, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - Color? todayHighlightColor, - DateRangePickerSelectionShape selectionShape, - bool isRtl, - SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - double selectionRadius, - double textScaleFactor, - double width, - double height, - String? monthFormat, - Locale locale, - DateRangePickerView view, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedRange, - List? disableDatesCollection, - this._extendableRangeSelectionDirection) - : super( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - disableDatesCollection); + List visibleDates, + dynamic cellStyle, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + Color? todayHighlightColor, + DateRangePickerSelectionShape selectionShape, + bool isRtl, + SfDateRangePickerThemeData datePickerTheme, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + double selectionRadius, + double textScaleFactor, + double width, + double height, + String? monthFormat, + Locale locale, + DateRangePickerView view, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedRange, + List? disableDatesCollection, + this._extendableRangeSelectionDirection, + ) : super( + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + disableDatesCollection, + ); dynamic _selectedRange; @@ -3285,7 +3497,8 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { _extendableRangeSelectionDirection; set extendableRangeSelectionDirection( - ExtendableRangeSelectionDirection value) { + ExtendableRangeSelectionDirection value, + ) { if (_extendableRangeSelectionDirection == value) { return; } @@ -3308,16 +3521,17 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { @override void drawSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double centerYPosition, - double xPosition, - double yPosition, - TextSpan yearText) { + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText, + ) { _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -3326,25 +3540,29 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { highlightPadding = maximumHighlight; } - final List selectionDetails = - _getSelectedRangePosition(currentIndex, _selectedIndex); + final List selectionDetails = _getSelectedRangePosition( + currentIndex, + _selectedIndex, + ); final bool isSelectedDate = selectionDetails[0]; final bool isStartRange = selectionDetails[1]; final bool isEndRange = selectionDetails[2]; final bool isBetweenRange = selectionDetails[3]; final Rect rect = Rect.fromLTRB( - xPosition + (isBetweenRange || isEndRange ? 0 : selectionPadding), - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + - cellWidth - - (isBetweenRange || isStartRange ? 0 : selectionPadding), - yPosition + centerYPosition + highlightPadding + textHalfHeight); - final double cornerRadius = isBetweenRange - ? 0 - : (selectionShape == DateRangePickerSelectionShape.circle - ? rect.height / 2 - : 3); + xPosition + (isBetweenRange || isEndRange ? 0 : selectionPadding), + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + + cellWidth - + (isBetweenRange || isStartRange ? 0 : selectionPadding), + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); + final double cornerRadius = + isBetweenRange + ? 0 + : (selectionShape == DateRangePickerSelectionShape.circle + ? rect.height / 2 + : 3); final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; if (isSelectedDate) { @@ -3356,33 +3574,38 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { } else if (isBetweenRange) { yearText = TextSpan( text: yearText.text, - style: rangeTextStyle ?? datePickerTheme.rangeSelectionTextStyle, + style: datePickerTheme.rangeSelectionTextStyle, ); _todayHighlightPaint.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _textPainter.text = yearText; - _textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + _textPainter.layout(maxWidth: cellWidth); } else if (isEndRange) { _todayHighlightPaint.color = endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRRect( - RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(leftRadius), - bottomLeft: Radius.circular(leftRadius), - bottomRight: Radius.circular(rightRadius), - topRight: Radius.circular(rightRadius)), - _todayHighlightPaint); + RRect.fromRectAndCorners( + rect, + topLeft: Radius.circular(leftRadius), + bottomLeft: Radius.circular(leftRadius), + bottomRight: Radius.circular(rightRadius), + topRight: Radius.circular(rightRadius), + ), + _todayHighlightPaint, + ); } @override void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; - final List selectionDetails = - _getSelectedRangePosition(index, _selectedIndex); + final List selectionDetails = _getSelectedRangePosition( + index, + _selectedIndex, + ); final bool isSelectedDate = selectionDetails[0]; final bool isStartRange = selectionDetails[1]; final bool isEndRange = selectionDetails[2]; @@ -3453,7 +3676,8 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { final dynamic startDate = _selectedRange.startDate; final dynamic endDate = _selectedRange.endDate ?? _selectedRange.startDate; _selectedIndex.addAll( - _getRangeIndex(startDate, endDate, view, viewStartIndex, viewEndIndex)); + _getRangeIndex(startDate, endDate, view, viewStartIndex, viewEndIndex), + ); return _selectedIndex; } @@ -3464,74 +3688,77 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { /// index is either it is start or end range or selected date and is between /// range as a list, in this list the isBetweenRange boolean index is 3, /// hence we used 3 here. - final List selectionDetails = - _getSelectedRangePosition(index, _selectedIndex); + final List selectionDetails = _getSelectedRangePosition( + index, + _selectedIndex, + ); return selectionDetails[3]; } } class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { _MultiRangeSelectionRenderObject( - List visibleDates, - dynamic cellStyle, - dynamic minDate, - dynamic maxDate, - bool enablePastDates, - Color? todayHighlightColor, - DateRangePickerSelectionShape selectionShape, - bool isRtl, - SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, - bool enableMultiView, - double multiViewSpacing, - TextStyle? selectionTextStyle, - TextStyle? rangeTextStyle, - Color? selectionColor, - Color? startRangeSelectionColor, - Color? endRangeSelectionColor, - Color? rangeSelectionColor, - double selectionRadius, - double textScaleFactor, - double width, - double height, - String? monthFormat, - Locale locale, - DateRangePickerView view, - bool isHijri, - DateRangePickerNavigationDirection navigationDirection, - SfLocalizations localizations, - this._selectedRanges, - List? disableDatesCollection) - : super( - visibleDates, - cellStyle, - minDate, - maxDate, - enablePastDates, - todayHighlightColor, - selectionShape, - isRtl, - datePickerTheme, - mouseHoverPosition, - enableMultiView, - multiViewSpacing, - selectionTextStyle, - rangeTextStyle, - selectionColor, - startRangeSelectionColor, - endRangeSelectionColor, - rangeSelectionColor, - selectionRadius, - textScaleFactor, - width, - height, - monthFormat, - locale, - view, - isHijri, - navigationDirection, - localizations, - disableDatesCollection); + List visibleDates, + dynamic cellStyle, + dynamic minDate, + dynamic maxDate, + bool enablePastDates, + Color? todayHighlightColor, + DateRangePickerSelectionShape selectionShape, + bool isRtl, + SfDateRangePickerThemeData datePickerTheme, + ValueNotifier mouseHoverPosition, + bool enableMultiView, + double multiViewSpacing, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, + double selectionRadius, + double textScaleFactor, + double width, + double height, + String? monthFormat, + Locale locale, + DateRangePickerView view, + bool isHijri, + DateRangePickerNavigationDirection navigationDirection, + SfLocalizations localizations, + this._selectedRanges, + List? disableDatesCollection, + ) : super( + visibleDates, + cellStyle, + minDate, + maxDate, + enablePastDates, + todayHighlightColor, + selectionShape, + isRtl, + datePickerTheme, + mouseHoverPosition, + enableMultiView, + multiViewSpacing, + selectionTextStyle, + rangeTextStyle, + selectionColor, + startRangeSelectionColor, + endRangeSelectionColor, + rangeSelectionColor, + selectionRadius, + textScaleFactor, + width, + height, + monthFormat, + locale, + view, + isHijri, + navigationDirection, + localizations, + disableDatesCollection, + ); List? _selectedRanges; @@ -3560,16 +3787,17 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { @override void drawSelection( - Canvas canvas, - double cellWidth, - int currentIndex, - double highlightPadding, - double selectionPadding, - double textHalfHeight, - double centerYPosition, - double xPosition, - double yPosition, - TextSpan yearText) { + Canvas canvas, + double cellWidth, + int currentIndex, + double highlightPadding, + double selectionPadding, + double textHalfHeight, + double centerYPosition, + double xPosition, + double yPosition, + TextSpan yearText, + ) { _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -3585,17 +3813,19 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { final bool isBetweenRange = selectionDetails[3]; final Rect rect = Rect.fromLTRB( - xPosition + (isBetweenRange || isEndRange ? 0 : selectionPadding), - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + - cellWidth - - (isBetweenRange || isStartRange ? 0 : selectionPadding), - yPosition + centerYPosition + highlightPadding + textHalfHeight); - final double cornerRadius = isBetweenRange - ? 0 - : (selectionShape == DateRangePickerSelectionShape.circle - ? rect.height / 2 - : 3); + xPosition + (isBetweenRange || isEndRange ? 0 : selectionPadding), + yPosition + centerYPosition - highlightPadding - textHalfHeight, + xPosition + + cellWidth - + (isBetweenRange || isStartRange ? 0 : selectionPadding), + yPosition + centerYPosition + highlightPadding + textHalfHeight, + ); + final double cornerRadius = + isBetweenRange + ? 0 + : (selectionShape == DateRangePickerSelectionShape.circle + ? rect.height / 2 + : 3); final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; if (isSelectedDate) { @@ -3607,25 +3837,28 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { } else if (isBetweenRange) { yearText = TextSpan( text: yearText.text, - style: rangeTextStyle ?? datePickerTheme.rangeSelectionTextStyle, + style: datePickerTheme.rangeSelectionTextStyle, ); _todayHighlightPaint.color = rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _textPainter.text = yearText; - _textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + _textPainter.layout(maxWidth: cellWidth); } else if (isEndRange) { _todayHighlightPaint.color = endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRRect( - RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(leftRadius), - bottomLeft: Radius.circular(leftRadius), - bottomRight: Radius.circular(rightRadius), - topRight: Radius.circular(rightRadius)), - _todayHighlightPaint); + RRect.fromRectAndCorners( + rect, + topLeft: Radius.circular(leftRadius), + bottomLeft: Radius.circular(leftRadius), + bottomRight: Radius.circular(rightRadius), + topRight: Radius.circular(rightRadius), + ), + _todayHighlightPaint, + ); } @override @@ -3714,7 +3947,12 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { final dynamic startDate = range.startDate; final dynamic endDate = range.endDate ?? range.startDate; final List index = _getRangeIndex( - startDate, endDate, view, viewStartIndex, viewEndIndex); + startDate, + endDate, + view, + viewStartIndex, + viewEndIndex, + ); _rangesIndex.add(index); selectedIndex.addAll(index); } @@ -3734,10 +3972,16 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { } /// Check the date cell placed in current view or not. -bool _isCurrentViewDateCell(dynamic date, int index, List visibleDates, - bool enableMultiView, dynamic view) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(view); +bool _isCurrentViewDateCell( + dynamic date, + int index, + List visibleDates, + bool enableMultiView, + dynamic view, +) { + final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView( + view, + ); if (pickerView == DateRangePickerView.year) { return true; @@ -3758,16 +4002,21 @@ bool _isCurrentViewDateCell(dynamic date, int index, List visibleDates, /// Draws the year cell on canvas based on selection mode. void _drawYearCells( - PaintingContext context, Size size, _IYearViewRenderObject yearView) { + PaintingContext context, + Size size, + _IYearViewRenderObject yearView, +) { final Canvas canvas = context.canvas; double webUIPadding = 0; int count = 1; double width = size.width; double height = size.height; - final bool isHorizontalMultiView = yearView.enableMultiView && + final bool isHorizontalMultiView = + yearView.enableMultiView && yearView.navigationDirection == DateRangePickerNavigationDirection.horizontal; - final bool isVerticalMultiView = yearView.enableMultiView && + final bool isVerticalMultiView = + yearView.enableMultiView && yearView.navigationDirection == DateRangePickerNavigationDirection.vertical; if (isHorizontalMultiView) { @@ -3786,8 +4035,9 @@ void _drawYearCells( double xPosition = 0, yPosition; final bool isNeedWidgetPaint = yearView.childCount != 0; - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(yearView.view); + final DateRangePickerView view = DateRangePickerHelper.getPickerView( + yearView.view, + ); if (isNeedWidgetPaint) { RenderBox? child = yearView.firstChild; @@ -3799,23 +4049,30 @@ void _drawYearCells( final int viewEndIndex = ((j + 1) * visibleDatesCount) - 1; /// Calculate the selected index values based on selected date property. - final List selectedIndex = - yearView.getSelectedIndex(viewStartIndex, viewEndIndex); + final List selectedIndex = yearView.getSelectedIndex( + viewStartIndex, + viewEndIndex, + ); - final double viewStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double viewStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); final double viewEndPosition = viewStartPosition + width; xPosition = viewStartPosition; - yPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + yPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); for (int i = 0; i < visibleDatesCount; i++) { int currentIndex = i; if (yearView.isRtl) { final int rowIndex = i ~/ YearView.maxColumnCount; - currentIndex = DateRangePickerHelper.getRtlIndex( - YearView.maxColumnCount, i % YearView.maxColumnCount) + + currentIndex = + DateRangePickerHelper.getRtlIndex( + YearView.maxColumnCount, + i % YearView.maxColumnCount, + ) + (rowIndex * YearView.maxColumnCount); } @@ -3827,7 +4084,11 @@ void _drawYearCells( if ((yearView.enableMultiView || yearView.isHijri) && DateRangePickerHelper.isLeadingCellDate( - currentIndex, viewStartIndex, yearView.visibleDates, view)) { + currentIndex, + viewStartIndex, + yearView.visibleDates, + view, + )) { xPosition += cellWidth; continue; } @@ -3835,33 +4096,43 @@ void _drawYearCells( final dynamic date = yearView.visibleDates[currentIndex]; final bool isSelected = selectedIndex.contains(currentIndex); final bool isEnableDate = DateRangePickerHelper.isBetweenMinMaxDateCell( - date, - yearView.minDate, - yearView.maxDate, - yearView.enablePastDates, - view, - yearView.isHijri); + date, + yearView.minDate, + yearView.maxDate, + yearView.enablePastDates, + view, + yearView.isHijri, + ); bool isDisabledDate = DateRangePickerHelper.isDateWithInVisibleDates( - yearView.visibleDates, yearView.disableDatesCollection, date); + yearView.visibleDates, + yearView.disableDatesCollection, + date, + ); if (!isDisabledDate && yearView is _ExtendableRangeSelectionRenderObject && yearView.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - yearView.selectedRange, - date, - yearView.extendableRangeSelectionDirection, - view, - yearView.isHijri, - isInBetweenEnabled: true)) { + yearView.selectedRange, + date, + yearView.extendableRangeSelectionDirection, + view, + yearView.isHijri, + isInBetweenEnabled: true, + )) { isDisabledDate = true; } if (isSelected && isEnableDate && !isDisabledDate) { yearView.drawCustomCellSelection( - canvas, - Rect.fromLTRB(xPosition, yPosition, xPosition + cellWidth, - yPosition + cellHeight), - currentIndex); + canvas, + Rect.fromLTRB( + xPosition, + yPosition, + xPosition + cellWidth, + yPosition + cellHeight, + ), + currentIndex, + ); } child!.paint(context, Offset(xPosition, yPosition)); @@ -3881,14 +4152,21 @@ void _drawYearCells( yearView._todayHighlightPaint.strokeWidth = 2; yearView._todayHighlightPaint.color = yearView.selectionColor != null - ? yearView.selectionColor!.withOpacity(0.4) - : yearView.datePickerTheme.selectionColor!.withOpacity(0.4); - - final Rect rect = Rect.fromLTRB(xPosition, yPosition, - xPosition + cellWidth, yPosition + cellHeight); + ? yearView.selectionColor!.withValues(alpha: 0.4) + : yearView.datePickerTheme.selectionColor!.withValues( + alpha: 0.4, + ); + + final Rect rect = Rect.fromLTRB( + xPosition, + yPosition, + xPosition + cellWidth, + yPosition + cellHeight, + ); canvas.drawRRect( - RRect.fromRectAndRadius(rect, const Radius.circular(2)), - yearView._todayHighlightPaint); + RRect.fromRectAndRadius(rect, const Radius.circular(2)), + yearView._todayHighlightPaint, + ); } } @@ -3900,7 +4178,9 @@ void _drawYearCells( } final dynamic today = DateRangePickerHelper.getToday(yearView.isHijri); - yearView._textPainter.textScaleFactor = yearView.textScaleFactor; + yearView._textPainter.textScaler = TextScaler.linear( + yearView.textScaleFactor, + ); const double decorationPadding = 1; const double selectionPadding = 3; @@ -3914,34 +4194,42 @@ void _drawYearCells( final int viewEndIndex = ((j + 1) * visibleDatesCount) - 1; /// Calculate the selected index values based on selected date property. - final List selectedIndex = - yearView.getSelectedIndex(viewStartIndex, viewEndIndex); + final List selectedIndex = yearView.getSelectedIndex( + viewStartIndex, + viewEndIndex, + ); List? hoveringIndex; if (yearView.mouseHoverPosition.value != null && yearView.mouseHoverPosition.value!.hoveringRange != null) { hoveringIndex = yearView._getRangeIndex( - yearView.mouseHoverPosition.value!.hoveringRange.startDate, - yearView.mouseHoverPosition.value!.hoveringRange.endDate, - yearView.view, - viewStartIndex, - viewEndIndex); + yearView.mouseHoverPosition.value!.hoveringRange.startDate, + yearView.mouseHoverPosition.value!.hoveringRange.endDate, + yearView.view, + viewStartIndex, + viewEndIndex, + ); } - final double viewStartPosition = isVerticalMultiView - ? 0 - : (currentViewIndex * width) + (currentViewIndex * webUIPadding); + final double viewStartPosition = + isVerticalMultiView + ? 0 + : (currentViewIndex * width) + (currentViewIndex * webUIPadding); final double viewEndPosition = viewStartPosition + width; xPosition = viewStartPosition; - yPosition = isHorizontalMultiView - ? 0 - : (currentViewIndex * height) + (currentViewIndex * webUIPadding); + yPosition = + isHorizontalMultiView + ? 0 + : (currentViewIndex * height) + (currentViewIndex * webUIPadding); for (int i = 0; i < visibleDatesCount; i++) { int currentIndex = i; if (yearView.isRtl) { final int rowIndex = i ~/ YearView.maxColumnCount; - currentIndex = DateRangePickerHelper.getRtlIndex( - YearView.maxColumnCount, i % YearView.maxColumnCount) + + currentIndex = + DateRangePickerHelper.getRtlIndex( + YearView.maxColumnCount, + i % YearView.maxColumnCount, + ) + (rowIndex * YearView.maxColumnCount); } @@ -3953,43 +4241,71 @@ void _drawYearCells( if ((yearView.enableMultiView || yearView.isHijri) && DateRangePickerHelper.isLeadingCellDate( - currentIndex, viewStartIndex, yearView.visibleDates, view)) { + currentIndex, + viewStartIndex, + yearView.visibleDates, + view, + )) { xPosition += cellWidth; continue; } final dynamic date = yearView.visibleDates[currentIndex]; - final bool isCurrentDate = - DateRangePickerHelper.isSameCellDates(date, today, view); + final bool isCurrentDate = DateRangePickerHelper.isSameCellDates( + date, + today, + view, + ); final bool isSelected = selectedIndex.contains(currentIndex); final bool isEnableDate = DateRangePickerHelper.isBetweenMinMaxDateCell( - date, - yearView.minDate, - yearView.maxDate, - yearView.enablePastDates, - view, - yearView.isHijri); + date, + yearView.minDate, + yearView.maxDate, + yearView.enablePastDates, + view, + yearView.isHijri, + ); final bool isActiveDate = _isCurrentViewDateCell( - date, j, yearView.visibleDates, yearView.enableMultiView, view); + date, + j, + yearView.visibleDates, + yearView.enableMultiView, + view, + ); bool isDisabledDate = DateRangePickerHelper.isDateWithInVisibleDates( - yearView.visibleDates, yearView.disableDatesCollection, date); + yearView.visibleDates, + yearView.disableDatesCollection, + date, + ); if (!isDisabledDate && yearView is _ExtendableRangeSelectionRenderObject && yearView.selectedRange != null && DateRangePickerHelper.isDisableDirectionDate( - yearView.selectedRange, - date, - yearView.extendableRangeSelectionDirection, - view, - yearView.isHijri, - isInBetweenEnabled: true)) { + yearView.selectedRange, + date, + yearView.extendableRangeSelectionDirection, + view, + yearView.isHijri, + isInBetweenEnabled: true, + )) { isDisabledDate = true; } - final TextStyle style = yearView._updateCellTextStyle(j, isCurrentDate, - isSelected, isEnableDate, isActiveDate, isDisabledDate); + final TextStyle style = yearView._updateCellTextStyle( + j, + isCurrentDate, + isSelected, + isEnableDate, + isActiveDate, + isDisabledDate, + ); final Decoration? yearDecoration = yearView._updateCellDecoration( - j, isCurrentDate, isEnableDate, isActiveDate, isDisabledDate); + j, + isCurrentDate, + isEnableDate, + isActiveDate, + isDisabledDate, + ); final TextSpan yearText = TextSpan( text: yearView._getCellText(date), @@ -3997,37 +4313,46 @@ void _drawYearCells( ); yearView._textPainter.text = yearText; - yearView._textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); + yearView._textPainter.layout(maxWidth: cellWidth); final double highlightPadding = yearView.selectionRadius == -1 ? 10 : yearView.selectionRadius; final double textHalfHeight = yearView._textPainter.height / 2; if (isSelected && isEnableDate && !isDisabledDate) { yearView.drawSelection( - canvas, - cellWidth, - currentIndex, - highlightPadding, - selectionPadding, - textHalfHeight, - centerYPosition, - xPosition, - yPosition, - yearText); + canvas, + cellWidth, + currentIndex, + highlightPadding, + selectionPadding, + textHalfHeight, + centerYPosition, + xPosition, + yPosition, + yearText, + ); } else if (yearDecoration != null) { - yearView._drawYearDecoration(canvas, yearDecoration, xPosition, - yPosition, decorationPadding, cellWidth, cellHeight); + yearView._drawYearDecoration( + canvas, + yearDecoration, + xPosition, + yPosition, + decorationPadding, + cellWidth, + cellHeight, + ); } else if (isCurrentDate) { yearView._drawTodayHighlight( - canvas, - cellWidth, - cellHeight, - centerYPosition, - highlightPadding, - selectionPadding, - textHalfHeight, - xPosition, - yPosition); + canvas, + cellWidth, + cellHeight, + centerYPosition, + highlightPadding, + selectionPadding, + textHalfHeight, + xPosition, + yPosition, + ); } double xOffset = @@ -4042,18 +4367,19 @@ void _drawYearCells( hoveringIndex.contains(currentIndex) && isEnableDate) { yearView._addRangeHoverEffect( - canvas, - xPosition, - yPosition, - currentIndex, - hoveringIndex, - yearView, - centerYPosition, - textHalfHeight, - selectionPadding, - highlightPadding, - cellWidth, - cellHeight); + canvas, + xPosition, + yPosition, + currentIndex, + hoveringIndex, + yearView, + centerYPosition, + textHalfHeight, + selectionPadding, + highlightPadding, + cellWidth, + cellHeight, + ); } if ((!isSelected || (yearView.isBetweenRange(currentIndex))) && @@ -4062,21 +4388,22 @@ void _drawYearCells( yearView.mouseHoverPosition.value != null && yearView.mouseHoverPosition.value!.offset != null) { yearView._addMouseHovering( - canvas, - cellWidth, - cellHeight, - centerYPosition, - currentViewIndex, - width, - highlightPadding, - date, - selectionPadding, - textHalfHeight, - webUIPadding, - xOffset, - xPosition, - yOffset, - yPosition); + canvas, + cellWidth, + cellHeight, + centerYPosition, + currentViewIndex, + width, + highlightPadding, + date, + selectionPadding, + textHalfHeight, + webUIPadding, + xOffset, + xPosition, + yOffset, + yPosition, + ); } yearView._textPainter.paint(canvas, Offset(xOffset, yOffset)); diff --git a/packages/syncfusion_flutter_datepicker/pubspec.yaml b/packages/syncfusion_flutter_datepicker/pubspec.yaml index b56a21fda..4f003c84d 100644 --- a/packages/syncfusion_flutter_datepicker/pubspec.yaml +++ b/packages/syncfusion_flutter_datepicker/pubspec.yaml @@ -1,18 +1,27 @@ name: 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: 20.1.47 -homepage: https://github.com/syncfusion/flutter-examples +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datepicker + +screenshots: + - description: 'This screenshot shows different features of date range picker.' + path: screenshots/flutter-daterangepicker-screenshot.png environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.20.0" + intl: '>=0.18.1 <0.21.0' syncfusion_flutter_core: path: ../syncfusion_flutter_core + +flutter: + uses-material-design: true + assets: + - assets/fonts/Roboto-Medium.ttf \ No newline at end of file diff --git a/packages/syncfusion_flutter_datepicker/screenshots/flutter-daterangepicker-screenshot.png b/packages/syncfusion_flutter_datepicker/screenshots/flutter-daterangepicker-screenshot.png new file mode 100644 index 000000000..607b7c387 Binary files /dev/null and b/packages/syncfusion_flutter_datepicker/screenshots/flutter-daterangepicker-screenshot.png differ diff --git a/packages/syncfusion_flutter_gauges/CHANGELOG.md b/packages/syncfusion_flutter_gauges/CHANGELOG.md index b55f0cf9d..da55f3fd2 100644 --- a/packages/syncfusion_flutter_gauges/CHANGELOG.md +++ b/packages/syncfusion_flutter_gauges/CHANGELOG.md @@ -1,3 +1,109 @@ +## Unreleased + +**General** + +* The compatible version of our Flutter gauges widget has been updated to Flutter SDK 3.32.0. + +# [29.2.6] - 05/27/2025 +## Radial Gauge +**Bugs** +* #FR67597 - Now, the [WidgetPointer](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/WidgetPointer-class.html) position will update properly, when we override the valueToFactor method in the custom axis renderer to update the widget pointer position. + +## [29.1.39] - 04/22/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 03/25/2025 + +**General** + +* The compatible version of our Flutter gauges widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter gauges example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter gauges example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.7] - 02/25/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter gauges widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter gauges widget has been updated to Flutter SDK 3.24.0. + +## [26.2.4] - 07/23/2024 + +## Linear Gauge + +**Bugs** +* Now, the [`SfLinearGauge``](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/SfLinearGauge-class.html) properly adjusts its constraints when resizing the screen. + +# [26.1.41] - 07/09/2024 + +## Linear Gauge + +**Bugs** +* Now the bar painter updates properly while dynamically update [`isAxisInversed`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/SfLinearGauge/isAxisInversed.html) property. + +## [25.1.35] - 03/15/2024 + +**General** +* Provided th​e Material 3 themes support. + +## [24.1.46] - 01/17/2024 + +**General** +* Upgraded the `intl` package to the latest version 0.19.0. + +# [23.1.43] - 10/31/2023 + +## Radial Gauge + +**Bugs** +* #FB47938 - Now, the disposed exception is no longer thrown when the radial gauge is displayed in a button click along with the marker pointer or needle pointer. + +## [21.1.39] - 04/11/2023 + +## Radial Gauge + +**Bugs** +* #FB42660 - Now, the disposed exception is no longer thrown in the radial gauge when a pointer is updated dynamically. + +## [20.4.52] - 02/28/2023 + +## Radial Gauge + +**Bugs** +* #FB40920 - Now, the axis tapped callback returns the proper value while enabling the [`canScaleToFit`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/RadialAxis/canScaleToFit.html) property in gauges. + +## [20.2.36] - 07/01/2022 + +## Radial Gauge + +**Features** +* Now, the pointer focus will not get lost until the user interaction ends. + +## [20.1.55] - 05/12/2022 +**Bugs** +* Now, on setting the [`showLastLabel`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/RadialAxis/showLastLabel.html) property as `false`, the last label will not be visible in the [`SfRadialGauge`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/SfRadialGauge-class.html) widget. To make it visible, set the [`showLastLabel`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/RadialAxis/showLastLabel.html) property as `true`. + ## [19.3.43] - 09/30/2021 ## Linear Gauge diff --git a/packages/syncfusion_flutter_gauges/LICENSE b/packages/syncfusion_flutter_gauges/LICENSE index 4934fbe6d..f64be67b9 100644 --- a/packages/syncfusion_flutter_gauges/LICENSE +++ b/packages/syncfusion_flutter_gauges/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Gauge 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. +Syncfusion® Flutter Gauge 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_gauges/README.md b/packages/syncfusion_flutter_gauges/README.md index 8df515284..2d7722dbd 100644 --- a/packages/syncfusion_flutter_gauges/README.md +++ b/packages/syncfusion_flutter_gauges/README.md @@ -7,7 +7,7 @@ The Flutter Gauges library includes the data visualization widgets Linear Gauge ## 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. -**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. +**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 @@ -22,7 +22,7 @@ The Linear Gauge is used to display data on a linear scale, while the Radial Gau - [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) +- [About Syncfusion®](#about-syncfusion) ## Linear gauge features @@ -78,22 +78,19 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about Syncfusion Flutter guages: +Take a look at the following to learn more about Syncfusion® Flutter guages: -* [Syncfusion Flutter product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation](https://help.syncfusion.com/flutter/radial-gauge/overview) * [Video tutorials](https://www.syncfusion.com/tutorial-videos/flutter/radial-gauge) * [Knowledge base](https://www.syncfusion.com/kb) @@ -216,11 +213,11 @@ The following screenshot illustrates the result of the above code sample. ## 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +## 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_gauges/analysis_options.yaml b/packages/syncfusion_flutter_gauges/analysis_options.yaml index 1c37f8e35..7bd9823d9 100644 --- a/packages/syncfusion_flutter_gauges/analysis_options.yaml +++ b/packages/syncfusion_flutter_gauges/analysis_options.yaml @@ -1,7 +1 @@ -include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore - lines_longer_than_80_chars: ignore - avoid_as: false \ No newline at end of file +include: package:syncfusion_flutter_core/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_gauges/example/README.md b/packages/syncfusion_flutter_gauges/example/README.md new file mode 100644 index 000000000..72e0a0acb --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/README.md @@ -0,0 +1,3 @@ +# Gauge example + +How to render Syncfusion Flutter Radial Gauge widget? diff --git a/packages/syncfusion_flutter_gauges/example/android/.gitignore b/packages/syncfusion_flutter_gauges/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_gauges/example/android/.gitignore +++ b/packages/syncfusion_flutter_gauges/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_gauges/example/android/app/build.gradle b/packages/syncfusion_flutter_gauges/example/android/app/build.gradle index 5fe3c929f..b5511a9a3 100644 --- a/packages/syncfusion_flutter_gauges/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_gauges/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_gauges/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_gauges/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_gauges/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_gauges/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_gauges/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_gauges/example/android/app/src/main/AndroidManifest.xml index 3f41384db..74a78b939 100644 --- a/packages/syncfusion_flutter_gauges/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_gauges/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/packages/syncfusion_flutter_gauges/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_gauges/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt index e793a000d..70f8f08f2 100644 --- a/packages/syncfusion_flutter_gauges/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/packages/syncfusion_flutter_gauges/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -2,5 +2,4 @@ package com.example.example import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { -} +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_gauges/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_gauges/example/android/build.gradle b/packages/syncfusion_flutter_gauges/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_gauges/example/android/build.gradle +++ b/packages/syncfusion_flutter_gauges/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_gauges/example/android/gradle.properties b/packages/syncfusion_flutter_gauges/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_gauges/example/android/gradle.properties +++ b/packages/syncfusion_flutter_gauges/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_gauges/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_gauges/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_gauges/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_gauges/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_gauges/example/android/settings.gradle b/packages/syncfusion_flutter_gauges/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_gauges/example/android/settings.gradle +++ b/packages/syncfusion_flutter_gauges/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_gauges/example/ios/.gitignore b/packages/syncfusion_flutter_gauges/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/.gitignore +++ b/packages/syncfusion_flutter_gauges/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_gauges/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_gauges/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.pbxproj index 0d45b982d..a6a988ee6 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,22 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -26,8 +34,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -37,14 +43,14 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; 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 = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; 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 = ""; }; @@ -57,20 +63,25 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -84,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -91,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -102,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -111,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -137,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -147,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -157,7 +187,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -165,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -191,20 +232,23 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -220,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -231,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -253,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -285,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -293,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -310,15 +371,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -328,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -362,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -376,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -386,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -418,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -426,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -444,15 +550,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -471,15 +572,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -492,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -513,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_gauges/example/ios/Runner/AppDelegate.swift index 70693e4a8..626664468 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_gauges/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/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_gauges/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_gauges/example/ios/Runner/Info.plist index a060db61e..5458fc418 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_gauges/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_gauges/example/ios/Runner/Runner-Bridging-Header.h index 7335fdf90..308a2a560 100644 --- a/packages/syncfusion_flutter_gauges/example/ios/Runner/Runner-Bridging-Header.h +++ b/packages/syncfusion_flutter_gauges/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_gauges/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_gauges/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_gauges/example/lib/main.dart b/packages/syncfusion_flutter_gauges/example/lib/main.dart index 08ea5b7e0..02fba2493 100644 --- a/packages/syncfusion_flutter_gauges/example/lib/main.dart +++ b/packages/syncfusion_flutter_gauges/example/lib/main.dart @@ -37,57 +37,70 @@ class _MyHomePageState extends State { Widget _getRadialGauge() { return SfRadialGauge( - title: GaugeTitle( - text: 'Speedometer', - textStyle: - const TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), - axes: [ - RadialAxis(minimum: 0, maximum: 150, ranges: [ + 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), + startValue: 0, + endValue: 50, + color: Colors.green, + startWidth: 10, + endWidth: 10, + ), GaugeRange( - startValue: 50, - endValue: 100, - color: Colors.orange, - startWidth: 10, - endWidth: 10), + 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: [ + 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: 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)), + 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), ); } @@ -95,7 +108,8 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Syncfusion Flutter Gauge')), - body: _getGauge()); + appBar: AppBar(title: const Text('Syncfusion Flutter Gauge')), + body: _getGauge(), + ); } } diff --git a/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_gauges/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_gauges/example/macos/.gitignore b/packages/syncfusion_flutter_gauges/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_gauges/example/macos/.gitignore +++ b/packages/syncfusion_flutter_gauges/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..fca5bdc5c 100644 --- a/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,6 +61,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -106,6 +134,7 @@ isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,6 +218,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..9963aa361 100644 --- a/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_gauges/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - + + + + + + - - Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_gauges/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_gauges/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_gauges/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_gauges/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_gauges/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..dda9752f6 100644 --- a/packages/syncfusion_flutter_gauges/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_gauges/example/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = example PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_gauges/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_gauges/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_gauges/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_gauges/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_gauges/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_gauges/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_gauges/example/pubspec.yaml b/packages/syncfusion_flutter_gauges/example/pubspec.yaml index 0ddcd7f31..df2c89ae3 100644 --- a/packages/syncfusion_flutter_gauges/example/pubspec.yaml +++ b/packages/syncfusion_flutter_gauges/example/pubspec.yaml @@ -1,20 +1,10 @@ name: example 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' + sdk: ^3.7.0-0 dependencies: flutter: @@ -22,53 +12,12 @@ dependencies: syncfusion_flutter_gauges: 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 + cupertino_icons: '>=0.1.2 <1.0.7' 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_gauges/example/web/index.html b/packages/syncfusion_flutter_gauges/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_gauges/example/web/index.html +++ b/packages/syncfusion_flutter_gauges/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_gauges/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_gauges/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_gauges/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_gauges/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) 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 index a54401f2f..3fe231482 100644 --- 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 @@ -59,6 +59,6 @@ class LinearAxisLabel { @override int get hashCode { - return hashValues(text, value); + return Object.hash(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 index d69b68dc3..bf2f30395 100644 --- 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 @@ -4,6 +4,7 @@ import 'dart:ui' as dart_ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:intl/intl.dart' show NumberFormat; +import 'package:syncfusion_flutter_core/theme.dart'; import '../../linear_gauge/axis/linear_axis_label.dart'; import '../../linear_gauge/axis/linear_axis_track_style.dart'; @@ -18,9 +19,11 @@ 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. - const LinearAxisRenderObjectWidget( - {Key? key, required this.linearGauge, this.fadeAnimation}) - : super(key: key); + const LinearAxisRenderObjectWidget({ + Key? key, + required this.linearGauge, + this.fadeAnimation, + }) : super(key: key); /// Holds the linear axis. final SfLinearGauge linearGauge; @@ -34,20 +37,19 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { 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; + final SfGaugeThemeData gaugeThemeData = SfGaugeTheme.of(context)!; + final SfColorScheme colorScheme = SfTheme.colorScheme(context); + final Color axisLineColor = colorScheme.onSurface[35]!; + final Color axisBorderColor = colorScheme.onSurface[76]!; + final Color majorTick = colorScheme.onSurface[61]!; + final Color minorTick = colorScheme.onSurface[61]!; return RenderLinearAxis( orientation: linearGauge.orientation, showAxisTrack: linearGauge.showAxisTrack, thickness: style.thickness, - color: style.color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.24) - : theme.colorScheme.onSurface.withOpacity(0.12)), + color: style.color ?? axisLineColor, borderWidth: style.borderWidth, - borderColor: style.borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)), + borderColor: style.borderColor ?? axisBorderColor, gradient: style.gradient, edgeStyle: style.edgeStyle, minimum: linearGauge.minimum, @@ -60,30 +62,22 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { tickPosition: linearGauge.tickPosition, tickOffset: linearGauge.tickOffset, isMirrored: linearGauge.isMirrored, - textStyle: linearGauge.axisLabelStyle ?? - TextStyle( - fontSize: 12.0, - fontStyle: FontStyle.normal, - color: isDarkTheme - ? theme.colorScheme.onSurface - : theme.colorScheme.onSurface.withOpacity(0.87), - fontWeight: FontWeight.normal), + textStyle: theme.textTheme.bodySmall! + .copyWith( + color: gaugeThemeData.axisLabelColor ?? colorScheme.onSurface[223], + ) + .merge(gaugeThemeData.axisLabelTextStyle) + .merge(linearGauge.axisLabelStyle), showLabels: linearGauge.showLabels, showTicks: linearGauge.showTicks, isAxisInversed: linearGauge.isAxisInversed, useRangeColorForAxis: linearGauge.useRangeColorForAxis, majorTickLength: linearGauge.majorTickStyle.length, majorTickThickness: linearGauge.majorTickStyle.thickness, - majorTickColor: majorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)), + majorTickColor: majorTickColor ?? majorTick, minorTickLength: linearGauge.minorTickStyle.length, minorTickThickness: linearGauge.minorTickStyle.thickness, - minorTickColor: minorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)), + minorTickColor: minorTickColor ?? minorTick, maximumLabels: linearGauge.maximumLabels, axisTrackExtent: linearGauge.axisTrackExtent, fadeAnimation: fadeAnimation, @@ -102,19 +96,19 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { 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; + final SfGaugeThemeData gaugeThemeData = SfGaugeTheme.of(context)!; + final SfColorScheme colorScheme = SfTheme.colorScheme(context); + final Color axisLabelColor = colorScheme.onSurface[223]!; + final Color axisLineColor = colorScheme.onSurface[35]!; + final Color axisBorderColor = colorScheme.onSurface[76]!; + final Color majorTick = colorScheme.onSurface[61]!; + final Color minorTick = colorScheme.onSurface[61]!; renderObject ..orientation = linearGauge.orientation ..showAxisTrack = linearGauge.showAxisTrack ..thickness = style.thickness - ..color = style.color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.24) - : theme.colorScheme.onSurface.withOpacity(0.12)) - ..borderColor = style.borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)) + ..color = style.color ?? axisLineColor + ..borderColor = style.borderColor ?? axisBorderColor ..borderWidth = style.borderWidth ..gradient = style.gradient ..edgeStyle = style.edgeStyle @@ -128,26 +122,16 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { ..tickPosition = linearGauge.tickPosition ..majorTickLength = linearGauge.majorTickStyle.length ..majorTickThickness = linearGauge.majorTickStyle.thickness - ..majorTickColor = majorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)) + ..majorTickColor = majorTickColor ?? majorTick ..minorTickLength = linearGauge.minorTickStyle.length ..minorTickThickness = linearGauge.minorTickStyle.thickness - ..minorTickColor = minorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)) + ..minorTickColor = minorTickColor ?? minorTick ..tickOffset = linearGauge.tickOffset ..isMirrored = linearGauge.isMirrored - ..textStyle = linearGauge.axisLabelStyle ?? - TextStyle( - fontSize: 12.0, - fontStyle: FontStyle.normal, - color: isDarkTheme - ? theme.colorScheme.onSurface - : theme.colorScheme.onSurface.withOpacity(0.87), - fontWeight: FontWeight.normal) + ..textStyle = theme.textTheme.bodySmall! + .copyWith(color: gaugeThemeData.axisLabelColor ?? axisLabelColor) + .merge(gaugeThemeData.axisLabelTextStyle) + .merge(linearGauge.axisLabelStyle) ..showLabels = linearGauge.showLabels ..showTicks = linearGauge.showTicks ..useRangeColorForAxis = linearGauge.useRangeColorForAxis @@ -208,45 +192,48 @@ class RenderLinearAxis extends RenderBox { FactorToValueCallback? factorToValueCallback, List? ranges, required BuildContext context, - }) : _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 = Theme.of(context).colorScheme.onSurface.withOpacity(0.12); + }) : _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 = Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.12); _textPainter = TextPainter(textDirection: TextDirection.ltr); _visibleLabels = []; _isHorizontalOrientation = orientation == LinearGaugeOrientation.horizontal; @@ -828,10 +815,7 @@ class RenderLinearAxis extends RenderBox { /// Returns the measured the label size. Size _measureLabelSize(double? value) { final String? label = _labelMap[value]; - final TextSpan textSpan = TextSpan( - text: label, - style: textStyle, - ); + final TextSpan textSpan = TextSpan(text: label, style: textStyle); _textPainter.text = textSpan; _textPainter.layout(); @@ -867,15 +851,20 @@ class RenderLinearAxis extends RenderBox { /// Measures the axis element size. double _measureAxisSize() { + // ignore: no_leading_underscores_for_local_identifiers 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 = - getEffectiveElementPosition(tickPosition, isMirrored); - final LinearLabelPosition labelPlacement = - getEffectiveLabelPosition(labelPosition, isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + tickPosition, + isMirrored, + ); + final LinearLabelPosition labelPlacement = getEffectiveLabelPosition( + labelPosition, + isMirrored, + ); switch (position) { case LinearElementPosition.inside: @@ -903,12 +892,11 @@ class RenderLinearAxis extends RenderBox { } break; case LinearElementPosition.cross: - _axisWidgetThickness = axisSize + + _axisWidgetThickness = + axisSize + ((axisSize < tickSize) ? tickSize - axisSize : 0.0) + labelSize; break; - default: - break; } return _axisWidgetThickness; @@ -921,10 +909,14 @@ class RenderLinearAxis extends RenderBox { final double tickSize = getTickSize() - tickMarginSize; final double labelSize = getEffectiveLabelSize() - labelMarginSize; final double axisSize = getAxisLineThickness(); - final LinearElementPosition position = - getEffectiveElementPosition(tickPosition, isMirrored); - final LinearLabelPosition labelPlacement = - getEffectiveLabelPosition(labelPosition, isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + tickPosition, + isMirrored, + ); + final LinearLabelPosition labelPlacement = getEffectiveLabelPosition( + labelPosition, + isMirrored, + ); switch (position) { case LinearElementPosition.inside: @@ -938,11 +930,10 @@ class RenderLinearAxis extends RenderBox { case LinearLabelPosition.outside: _labelTop = 0; axisOffset = showAxisTrack ? labelSize + labelMarginSize : 0; - _tickTop = showTicks - ? labelSize + labelMarginSize + axisSize + tickMarginSize - : 0; - break; - default: + _tickTop = + showTicks + ? labelSize + labelMarginSize + axisSize + tickMarginSize + : 0; break; } break; @@ -951,9 +942,10 @@ class RenderLinearAxis extends RenderBox { case LinearLabelPosition.inside: _tickTop = 0; axisOffset = showAxisTrack ? tickSize + tickMarginSize : 0; - _labelTop = showLabels - ? axisSize + tickSize + labelMarginSize + tickMarginSize - : 0; + _labelTop = + showLabels + ? axisSize + tickSize + labelMarginSize + tickMarginSize + : 0; break; case LinearLabelPosition.outside: _labelTop = 0; @@ -968,8 +960,6 @@ class RenderLinearAxis extends RenderBox { axisOffset += tickMarginSize - (labelSize + labelMarginSize); } break; - default: - break; } break; case LinearElementPosition.cross: @@ -978,9 +968,10 @@ class RenderLinearAxis extends RenderBox { if (axisSize < tickSize && tickSize > 0) { _tickTop = 0; axisOffset = showAxisTrack ? (tickSize - axisSize) / 2 : 0; - _labelTop = showLabels - ? (axisSize + (tickSize - axisSize) + labelMarginSize) - : 0; + _labelTop = + showLabels + ? (axisSize + (tickSize - axisSize) + labelMarginSize) + : 0; } else if (axisSize > tickSize && tickSize > 0) { axisOffset = 0; _tickTop = showTicks ? (axisSize - tickSize) / 2 : 0; @@ -995,27 +986,25 @@ class RenderLinearAxis extends RenderBox { if (axisSize < tickSize && tickSize > 0) { _labelTop = 0; _tickTop = showTicks ? labelSize + labelMarginSize : 0; - axisOffset = showAxisTrack - ? labelSize + labelMarginSize + (tickSize - axisSize) / 2 - : 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; + _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; } } @@ -1053,52 +1042,52 @@ class RenderLinearAxis extends RenderBox { /// Return the axis layout padding. double getAxisLayoutPadding() { - double _layoutStartPadding = 0; - double _layoutEndPadding = 0; + double layoutStartPadding = 0; + double layoutEndPadding = 0; final double labelStartPadding = _getStartLabelPadding(); final double labelEndPadding = _getEndLabelPadding(); if (axisTrackExtent == 0) { if (pointerStartPadding! > labelStartPadding) { - _layoutStartPadding = pointerStartPadding! - labelStartPadding; + layoutStartPadding = pointerStartPadding! - labelStartPadding; } if (pointerEndPadding! > labelEndPadding) { - _layoutEndPadding = pointerEndPadding! - labelEndPadding; + layoutEndPadding = pointerEndPadding! - labelEndPadding; } } else { - _layoutStartPadding = 0; - _layoutEndPadding = 0; + layoutStartPadding = 0; + layoutEndPadding = 0; /// Measure the layout start padding based on axis track extent. if (pointerStartPadding! > axisTrackExtent) { - _layoutStartPadding = pointerStartPadding! - axisTrackExtent; + layoutStartPadding = pointerStartPadding! - axisTrackExtent; if (labelStartPadding > axisTrackExtent) { - _layoutStartPadding -= labelStartPadding - axisTrackExtent; + layoutStartPadding -= labelStartPadding - axisTrackExtent; } } if (pointerStartPadding! < labelStartPadding) { - _layoutStartPadding = 0; + layoutStartPadding = 0; } /// Measure the layout end padding based on axis track extent. if (pointerEndPadding! > axisTrackExtent) { - _layoutEndPadding = pointerEndPadding! - axisTrackExtent; + layoutEndPadding = pointerEndPadding! - axisTrackExtent; if (labelEndPadding > axisTrackExtent) { - _layoutEndPadding -= labelEndPadding - axisTrackExtent; + layoutEndPadding -= labelEndPadding - axisTrackExtent; } } if (pointerEndPadding! < labelEndPadding) { - _layoutEndPadding = 0; + layoutEndPadding = 0; } } - return _layoutStartPadding + _layoutEndPadding; + return layoutStartPadding + layoutEndPadding; } /// Return the axis position padding. @@ -1132,15 +1121,17 @@ class RenderLinearAxis extends RenderBox { /// Get the child padding for positioning. double getChildPadding({dynamic child}) { double paddingSize = math.max( - math.max(axisTrackExtent, pointerStartPadding!), - _getStartLabelPadding()); + math.max(axisTrackExtent, pointerStartPadding!), + _getStartLabelPadding(), + ); if (child != null && (child is RenderLinearWidgetPointer || child is RenderLinearShapePointer)) { - final double childSize = _isHorizontalOrientation - ? child.size.width as double - : child.size.height as double; + final double childSize = + _isHorizontalOrientation + ? child.size.width as double + : child.size.height as double; if (child.markerAlignment == LinearMarkerAlignment.start) { return paddingSize; @@ -1210,15 +1201,15 @@ class RenderLinearAxis extends RenderBox { @override void performLayout() { - double _parentWidgetSize; + double parentWidgetSize; final double actualParentWidth = constraints.maxWidth; final double actualParentHeight = constraints.maxHeight; if (_isHorizontalOrientation) { - _parentWidgetSize = actualParentWidth; + parentWidgetSize = actualParentWidth; } else { - _parentWidgetSize = actualParentHeight; + parentWidgetSize = actualParentHeight; } final double axisWidgetThickness = _measureAxisSize(); @@ -1230,23 +1221,30 @@ class RenderLinearAxis extends RenderBox { _calculateAxisElementPositions(); if (_isHorizontalOrientation) { - _axisActualSize = Size(_parentWidgetSize, axisWidgetThickness); + _axisActualSize = Size(parentWidgetSize, axisWidgetThickness); } else { - _axisActualSize = Size(axisWidgetThickness, _parentWidgetSize); + _axisActualSize = Size(axisWidgetThickness, parentWidgetSize); } - size = _axisActualSize; + size = Size( + math.min(_axisActualSize.width, constraints.maxWidth), + math.min(_axisActualSize.height, constraints.maxHeight), + ); } /// To calculate the axis interval based on the maximum axis label count. double _calculateAxisInterval(Size size) { final double delta = (maximum - minimum).abs(); final double area = _isHorizontalOrientation ? size.width : size.height; - final double actualDesiredIntervalsCount = - math.max((area * maximumLabels) / 100, 1.0); + final double actualDesiredIntervalsCount = math.max( + (area * maximumLabels) / 100, + 1.0, + ); double niceInterval = delta / actualDesiredIntervalsCount; - final num minInterval = - math.pow(10, (math.log(niceInterval) / math.log(10)).floor()); + final num minInterval = math.pow( + 10, + (math.log(niceInterval) / math.log(10)).floor(), + ); final List intervalDivisions = [10, 5, 2, 1]; for (final double intervalDivision in intervalDivisions) { final double currentInterval = minInterval * intervalDivision; @@ -1301,8 +1299,10 @@ class RenderLinearAxis extends RenderBox { /// Returns the pixel position based on value. double valueToPixel(double value, {bool isTickPositionCalculation = false}) { - final double factor = valueToFactor(value, - isTickPositionCalculation: isTickPositionCalculation); + final double factor = valueToFactor( + value, + isTickPositionCalculation: isTickPositionCalculation, + ); double? labelStartPadding = _getStartLabelPadding(); double? labelEndPadding = _getEndLabelPadding(); @@ -1324,8 +1324,14 @@ class RenderLinearAxis extends RenderBox { (labelStartPadding + labelEndPadding)); } - void _drawTickLine(double x1, double y1, double x2, double y2, Canvas canvas, - bool isMajorTick) { + 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); @@ -1336,13 +1342,18 @@ class RenderLinearAxis extends RenderBox { if (_visibleLabels.length - 1 == index) { return null; } else { - endValuePosition = valueToPixel(_visibleLabels[index + 1].value, - isTickPositionCalculation: true); - } - - final double width = (endValuePosition - - valueToPixel(_visibleLabels[index].value, - isTickPositionCalculation: true)) + endValuePosition = valueToPixel( + _visibleLabels[index + 1].value, + isTickPositionCalculation: true, + ); + } + + final double width = + (endValuePosition - + valueToPixel( + _visibleLabels[index].value, + isTickPositionCalculation: true, + )) .abs() / (minorTicksPerInterval + 1); @@ -1368,15 +1379,22 @@ class RenderLinearAxis extends RenderBox { animationValue = _fadeAnimation!.value; } - _axisPaint.color = - paintColor.withOpacity(animationValue * paintColor.opacity); + _axisPaint.color = paintColor.withValues( + alpha: animationValue * paintColor.a, + ); } ///Draws minor tick elements. - void _drawMinorTicks(double minorTickLeftPosition, double top, - int majorTickIndex, Canvas canvas) { - final LinearElementPosition position = - getEffectiveElementPosition(tickPosition, isMirrored); + void _drawMinorTicks( + double minorTickLeftPosition, + double top, + int majorTickIndex, + Canvas canvas, + ) { + final LinearElementPosition position = getEffectiveElementPosition( + tickPosition, + isMirrored, + ); if (_isHorizontalOrientation) { if (position == LinearElementPosition.outside) { @@ -1398,38 +1416,48 @@ class RenderLinearAxis extends RenderBox { final double? valueGap = _getMinorTickValueGap(majorTickIndex); if (minorTickGap != null) { - for (int minorTickIndex = 1; - minorTickIndex <= minorTicksPerInterval; - minorTickIndex++) { + for ( + int minorTickIndex = 1; + minorTickIndex <= minorTicksPerInterval; + minorTickIndex++ + ) { if (_isHorizontalOrientation) { minorTickLeftPosition += minorTickGap; } else { top += minorTickGap; } - _setPaintColor(useRangeColorForAxis - ? _getRangeColor(_visibleLabels[majorTickIndex].value + - (valueGap! * minorTickIndex)) ?? - minorTickColor - : minorTickColor); + _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); + 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}) { + dart_ui.TextStyle _getTextStyle({ + double textScaleFactor = 1.0, + required TextStyle style, + Color? color, + }) { double animationValue = 1; if (_fadeAnimation != null) { @@ -1437,7 +1465,7 @@ class RenderLinearAxis extends RenderBox { } if (color != null) { - color = color.withOpacity(animationValue * color.opacity); + color = color.withValues(alpha: animationValue * color.a); } return dart_ui.TextStyle( @@ -1458,7 +1486,8 @@ class RenderLinearAxis extends RenderBox { height: style.height, locale: style.locale, foreground: style.foreground, - background: style.background ?? + background: + style.background ?? (style.backgroundColor != null ? (Paint()..color = style.backgroundColor!) : null), @@ -1468,18 +1497,26 @@ class RenderLinearAxis extends RenderBox { } ///Draws axis label elements. - void _drawLabels(Canvas canvas, int majorTickIndex, - double majorTickLeftPosition, double top) { + void _drawLabels( + Canvas canvas, + int majorTickIndex, + double majorTickLeftPosition, + double top, + ) { final dart_ui.ParagraphStyle paragraphStyle = dart_ui.ParagraphStyle( - textDirection: TextDirection.ltr, textAlign: TextAlign.left); + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + ); final String labelText = _labelMap[_visibleLabels[majorTickIndex].value]!; final double value = _visibleLabels[majorTickIndex].value; final dart_ui.TextStyle labelTextStyle = _getTextStyle( - style: textStyle, - color: useRangeColorForAxis - ? _getRangeColor(_visibleLabels[majorTickIndex].value) ?? - textStyle.color - : textStyle.color); + style: textStyle, + color: + useRangeColorForAxis + ? _getRangeColor(_visibleLabels[majorTickIndex].value) ?? + textStyle.color + : textStyle.color, + ); final dart_ui.ParagraphBuilder paragraphBuilder = dart_ui.ParagraphBuilder(paragraphStyle) ..pushStyle(labelTextStyle) @@ -1491,17 +1528,19 @@ class RenderLinearAxis extends RenderBox { Offset labelOffset; if (_isHorizontalOrientation) { - final double _labelLeftPosition = + final double labelLeftPosition = majorTickLeftPosition - (labelSize.width / 2); - labelOffset = Offset(_labelLeftPosition, _labelTop); + labelOffset = Offset(labelLeftPosition, _labelTop); } else { - final double _labelLeftPosition = top - (labelSize.height / 2); + final double labelLeftPosition = top - (labelSize.height / 2); if (_labelTop == 0 && _maxLabelWidth > labelSize.width) { - labelOffset = - Offset(_maxLabelWidth - labelSize.width, _labelLeftPosition); + labelOffset = Offset( + _maxLabelWidth - labelSize.width, + labelLeftPosition, + ); } else { - labelOffset = Offset(_labelTop, _labelLeftPosition); + labelOffset = Offset(_labelTop, labelLeftPosition); } } @@ -1527,23 +1566,26 @@ class RenderLinearAxis extends RenderBox { if (_isHorizontalOrientation) { _axisLineRect = Rect.fromLTWH( - offset.dx + startLabelPadding, - offset.dy + axisOffset, - size.width - (startLabelPadding + endLabelPadding), - thickness); + 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)); + 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); + _axisLineRect.left + borderWidth / 2, + _axisLineRect.top + borderWidth / 2, + _axisLineRect.width - borderWidth, + _axisLineRect.height - borderWidth, + ); if (showAxisTrack) { _axisPaint.style = PaintingStyle.fill; @@ -1559,24 +1601,29 @@ class RenderLinearAxis extends RenderBox { _path.addRect(_axisLineRect); break; case LinearEdgeStyle.bothCurve: - _path.addRRect(RRect.fromRectAndRadius( - _axisLineRect, Radius.circular(thickness))); + _path.addRRect( + RRect.fromRectAndRadius(_axisLineRect, Radius.circular(thickness)), + ); break; case LinearEdgeStyle.startCurve: - _path.addRRect(getStartCurve( + _path.addRRect( + getStartCurve( isHorizontal: _isHorizontalOrientation, isAxisInversed: isAxisInversed, rect: _axisLineRect, - radius: thickness / 2)); + radius: thickness / 2, + ), + ); break; case LinearEdgeStyle.endCurve: - _path.addRRect(getEndCurve( + _path.addRRect( + getEndCurve( isHorizontal: _isHorizontalOrientation, isAxisInversed: isAxisInversed, rect: _axisLineRect, - radius: thickness / 2)); - break; - default: + radius: thickness / 2, + ), + ); break; } @@ -1593,20 +1640,26 @@ class RenderLinearAxis extends RenderBox { } void _drawTicksAndLabels(Canvas canvas, Offset offset) { - final double majorTickLeftPosition = - math.max(_getStartLabelPadding(), axisTrackExtent); + final double 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); + _setPaintColor( + useRangeColorForAxis + ? _getRangeColor(_visibleLabels[index].value) ?? majorTickColor + : majorTickColor, + ); _axisPaint.strokeWidth = majorTickThickness; - final double calculatedPosition = valueToPixel( - _visibleLabels[index].value, - isTickPositionCalculation: true) + + final double calculatedPosition = + valueToPixel( + _visibleLabels[index].value, + isTickPositionCalculation: true, + ) + majorTickLeftPosition; tickStartPoint = Offset(calculatedPosition, _tickTop); @@ -1619,8 +1672,14 @@ class RenderLinearAxis extends RenderBox { if (showTicks) { /// Drawing the major ticks. - _drawTickLine(tickStartPoint.dx, tickStartPoint.dy, tickEndPoint.dx, - tickEndPoint.dy, canvas, true); + _drawTickLine( + tickStartPoint.dx, + tickStartPoint.dy, + tickEndPoint.dx, + tickEndPoint.dy, + canvas, + true, + ); _drawMinorTicks(tickStartPoint.dx, tickStartPoint.dy, index, canvas); } 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 index c7c572ca4..011e0328d 100644 --- 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 @@ -6,13 +6,14 @@ import '../../linear_gauge/utils/enum.dart'; @immutable class LinearAxisTrackStyle { /// Creates a style for axis line. - const LinearAxisTrackStyle( - {this.thickness = 5.0, - this.edgeStyle = LinearEdgeStyle.bothFlat, - this.color, - this.gradient, - this.borderColor, - this.borderWidth = 0}); + const 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. /// @@ -143,7 +144,7 @@ class LinearAxisTrackStyle { @override int get hashCode { - return hashValues( + return Object.hash( thickness, color, borderColor, 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 index e14e0fa7f..eb2ee84c3 100644 --- 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 @@ -82,10 +82,6 @@ class LinearTickStyle { @override int get hashCode { - return hashValues( - length, - color, - thickness, - ); + return Object.hash(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 index 0c21a2851..0205f2f43 100644 --- 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 @@ -26,48 +26,48 @@ import '../../linear_gauge/utils/linear_gauge_typedef.dart'; /// ``` 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, - this.axisLabelStyle, - LinearAxisTrackStyle? axisTrackStyle, - LinearTickStyle? majorTickStyle, - LinearTickStyle? minorTickStyle}) - : assert(minimum <= maximum, 'Maximum should be greater than minimum.'), - axisTrackStyle = axisTrackStyle ?? const LinearAxisTrackStyle(), - tickOffset = tickOffset > 0 ? tickOffset : 0, - labelOffset = labelOffset > 0 ? labelOffset : 4, - majorTickStyle = majorTickStyle ?? const LinearTickStyle(length: 8.0), - minorTickStyle = minorTickStyle ?? const LinearTickStyle(length: 4.0), - numberFormat = numberFormat ?? NumberFormat('#.##'), - super(key: key); + 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, + this.axisLabelStyle, + LinearAxisTrackStyle? axisTrackStyle, + LinearTickStyle? majorTickStyle, + LinearTickStyle? minorTickStyle, + }) : assert(minimum <= maximum, 'Maximum should be greater than minimum.'), + axisTrackStyle = axisTrackStyle ?? const LinearAxisTrackStyle(), + tickOffset = tickOffset > 0 ? tickOffset : 0, + labelOffset = labelOffset > 0 ? labelOffset : 4, + majorTickStyle = majorTickStyle ?? const LinearTickStyle(length: 8.0), + minorTickStyle = minorTickStyle ?? const LinearTickStyle(), + numberFormat = numberFormat ?? NumberFormat('#.##'), + super(key: key); ///Customizes each range by adding it to the [ranges] collection. /// @@ -537,12 +537,14 @@ class _SfLinearGaugeState extends State } void _updateOldList() { - _oldBarPointerList = (widget.barPointers != null) - ? List.from(widget.barPointers!) - : null; - _oldMarkerPointerList = (widget.markerPointers != null) - ? List.from(widget.markerPointers!) - : null; + _oldBarPointerList = + (widget.barPointers != null) + ? List.from(widget.barPointers!) + : null; + _oldMarkerPointerList = + (widget.markerPointers != null) + ? List.from(widget.markerPointers!) + : null; } bool _isEqualLists(List? a, List? b) { @@ -572,13 +574,17 @@ class _SfLinearGaugeState extends State if (widget.animateAxis || widget.animateRange) { _animationController = AnimationController( - vsync: this, - duration: Duration(milliseconds: widget.animationDuration)); + vsync: this, + duration: Duration(milliseconds: widget.animationDuration), + ); _animationController!.addListener(_axisAnimationListener); - _fadeAnimation = Tween(begin: 0, end: 1).animate(CurvedAnimation( + _fadeAnimation = Tween(begin: 0, end: 1).animate( + CurvedAnimation( parent: _animationController!, - curve: const Interval(0.05, 1.0, curve: Curves.ease))); + curve: const Interval(0.05, 1.0, curve: Curves.ease), + ), + ); } else { if (_animationController != null) { _animationController!.removeListener(_axisAnimationListener); @@ -594,12 +600,16 @@ class _SfLinearGaugeState extends State void _addPointerAnimation(int duration, LinearAnimationType animationType) { final AnimationController pointerController = AnimationController( - vsync: this, duration: Duration(milliseconds: duration)); + vsync: this, + duration: Duration(milliseconds: duration), + ); final Animation animation = Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: pointerController, - curve: Interval(0, 1, curve: getCurveAnimation(animationType)))); + CurvedAnimation( + parent: pointerController, + curve: Interval(0, 1, curve: getCurveAnimation(animationType)), + ), + ); _pointerAnimations.add(animation); _pointerAnimationControllers.add(pointerController); @@ -617,7 +627,9 @@ class _SfLinearGaugeState extends State barPointer.position == LinearElementPosition.inside) { if (barPointer.enableAnimation && barPointer.animationDuration > 0) { _addPointerAnimation( - barPointer.animationDuration, barPointer.animationType); + barPointer.animationDuration, + barPointer.animationType, + ); } } } @@ -630,7 +642,9 @@ class _SfLinearGaugeState extends State barPointer.enableAnimation && barPointer.animationDuration > 0) { _addPointerAnimation( - barPointer.animationDuration, barPointer.animationType); + barPointer.animationDuration, + barPointer.animationType, + ); } } } @@ -641,7 +655,9 @@ class _SfLinearGaugeState extends State if (shapePointer.enableAnimation && shapePointer.animationDuration > 0) { _addPointerAnimation( - shapePointer.animationDuration, shapePointer.animationType); + shapePointer.animationDuration, + shapePointer.animationType, + ); } } } @@ -672,15 +688,21 @@ class _SfLinearGaugeState extends State } } - void _addChild(Widget child, Animation? animation, - AnimationController? controller) { - _linearGaugeWidgets.add(LinearGaugeScope( - child: child, + void _addChild( + Widget child, + Animation? animation, + AnimationController? controller, + ) { + _linearGaugeWidgets.add( + LinearGaugeScope( animation: animation, orientation: widget.orientation, isAxisInversed: widget.isAxisInversed, isMirrored: widget.isMirrored, - animationController: controller)); + animationController: controller, + child: child, + ), + ); } List _buildChildWidgets(BuildContext context) { @@ -703,8 +725,11 @@ class _SfLinearGaugeState extends State if (barPointer.position == LinearElementPosition.outside || barPointer.position == LinearElementPosition.inside) { if (barPointer.enableAnimation && barPointer.animationDuration > 0) { - _addChild(barPointer, _pointerAnimations[index], - _pointerAnimationControllers[index]); + _addChild( + barPointer, + _pointerAnimations[index], + _pointerAnimationControllers[index], + ); index++; } else { _addChild(barPointer, null, null); @@ -714,9 +739,12 @@ class _SfLinearGaugeState extends State } /// Adding linear gauge axis widget. - _linearGaugeWidgets.add(LinearAxisRenderObjectWidget( + _linearGaugeWidgets.add( + LinearAxisRenderObjectWidget( linearGauge: widget, - fadeAnimation: widget.animateAxis ? _fadeAnimation : null)); + fadeAnimation: widget.animateAxis ? _fadeAnimation : null, + ), + ); if (widget.ranges != null && widget.ranges!.isNotEmpty) { /// Adding linear gauge range widgets. @@ -732,8 +760,11 @@ class _SfLinearGaugeState extends State for (final LinearBarPointer barPointer in widget.barPointers!) { if (barPointer.position == LinearElementPosition.cross) { if (barPointer.enableAnimation && barPointer.animationDuration > 0) { - _addChild(barPointer, _pointerAnimations[index], - _pointerAnimationControllers[index]); + _addChild( + barPointer, + _pointerAnimations[index], + _pointerAnimationControllers[index], + ); index++; } else { _addChild(barPointer, null, null); @@ -746,8 +777,11 @@ class _SfLinearGaugeState extends State /// Adding linear gauge widget bar pointer element. for (final LinearMarkerPointer pointer in widget.markerPointers!) { if (pointer.enableAnimation && pointer.animationDuration > 0) { - _addChild(pointer as Widget, _pointerAnimations[index], - _pointerAnimationControllers[index]); + _addChild( + pointer as Widget, + _pointerAnimations[index], + _pointerAnimationControllers[index], + ); index++; } else { _addChild(pointer as Widget, null, null); @@ -761,8 +795,9 @@ class _SfLinearGaugeState extends State @override Widget build(BuildContext context) { return LinearGaugeRenderWidget( - children: _buildChildWidgets(context), - pointerAnimations: _pointerAnimations); + pointerAnimations: _pointerAnimations, + children: _buildChildWidgets(context), + ); } void _disposeAnimationControllers() { 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 index c22b512e7..81bb3b374 100644 --- 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 @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:flutter/gestures.dart' show + DeviceGestureSettings, DragStartBehavior, GestureArenaTeam, HitTestTarget, @@ -30,22 +31,26 @@ 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); + const LinearGaugeRenderWidget({ + Key? key, + required this.pointerAnimations, + required List children, + }) : super(key: key, children: children); /// Linear gauge pointer animations. final List> pointerAnimations; @override - RenderObject createRenderObject(BuildContext context) => - RenderLinearGauge(pointerAnimations: pointerAnimations); + RenderObject createRenderObject(BuildContext context) => RenderLinearGauge( + pointerAnimations: pointerAnimations, + gestureSettings: MediaQuery.of(context).gestureSettings, + ); @override void updateRenderObject( - BuildContext context, RenderLinearGauge renderObject) { + BuildContext context, + RenderLinearGauge renderObject, + ) { renderObject.pointerAnimations = pointerAnimations; super.updateRenderObject(context, renderObject); } @@ -106,27 +111,33 @@ class RenderLinearGauge extends RenderBox /// /// By default, the non-positioned children of the stack are aligned by their /// top left corners. - RenderLinearGauge({required List> pointerAnimations}) - : _gestureArenaTeam = GestureArenaTeam(), - _pointerAnimations = pointerAnimations { + RenderLinearGauge({ + required List> pointerAnimations, + required DeviceGestureSettings gestureSettings, + }) : _gestureArenaTeam = GestureArenaTeam(), + _pointerAnimations = pointerAnimations { _ranges = []; _barPointers = []; _shapePointers = []; _widgetPointers = []; _markerPointers = []; - _verticalDragGestureRecognizer = VerticalDragGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..dragStartBehavior = DragStartBehavior.start; - - _horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..dragStartBehavior = DragStartBehavior.start; + _verticalDragGestureRecognizer = + VerticalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..gestureSettings = gestureSettings + ..dragStartBehavior = DragStartBehavior.start; + + _horizontalDragGestureRecognizer = + HorizontalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..gestureSettings = gestureSettings + ..dragStartBehavior = DragStartBehavior.start; } final GestureArenaTeam _gestureArenaTeam; @@ -270,55 +281,64 @@ class RenderLinearGauge extends RenderBox final double labelSize = axis!.getEffectiveLabelSize(); final double tickSize = axis!.getTickSize(); final double axisLineSize = axis!.getAxisLineThickness(); - final LinearElementPosition position = - getEffectiveElementPosition(axis!.tickPosition, axis!.isMirrored); - final LinearLabelPosition labelPosition = - getEffectiveLabelPosition(axis!.labelPosition, axis!.isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + axis!.tickPosition, + axis!.isMirrored, + ); + final LinearLabelPosition labelPosition = getEffectiveLabelPosition( + axis!.labelPosition, + axis!.isMirrored, + ); final bool isInsideLabel = labelPosition == LinearLabelPosition.inside; - late double _insideElementSize; + late double insideElementSize; switch (position) { case LinearElementPosition.inside: if (isInsideLabel) { - _insideElementSize = (_axisWidgetThickness - axisLineSize) > thickness - ? 0 - : thickness - (_axisWidgetThickness - axisLineSize); + insideElementSize = + (_axisWidgetThickness - axisLineSize) > thickness + ? 0 + : thickness - (_axisWidgetThickness - axisLineSize); } else { - _insideElementSize = thickness - tickSize; + insideElementSize = thickness - tickSize; } break; case LinearElementPosition.outside: if (isInsideLabel) { - _insideElementSize = thickness - labelSize; + insideElementSize = thickness - labelSize; } else { - _insideElementSize = thickness; + insideElementSize = thickness; } break; case LinearElementPosition.cross: if (isInsideLabel) { - _insideElementSize = (_axisWidgetThickness - axisLineSize) > thickness - ? 0 - : thickness - (_axisWidgetThickness - axisLineSize); + insideElementSize = + (_axisWidgetThickness - axisLineSize) > thickness + ? 0 + : thickness - (_axisWidgetThickness - axisLineSize); } else { - _insideElementSize = (axis!.showLabels ? thickness : 0) - + insideElementSize = + (axis!.showLabels ? thickness : 0) - (axisLineSize < tickSize ? (tickSize - axisLineSize) / 2 : 0); } break; - default: - break; } - _insideWidgetElementSize = - math.max(_insideWidgetElementSize, _insideElementSize); + _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); + _insideWidgetElementSize = math.max( + thickness - _axisWidgetThickness, + _insideWidgetElementSize, + ); } } @@ -327,10 +347,14 @@ class RenderLinearGauge extends RenderBox final double labelSize = axis!.getEffectiveLabelSize(); final double tickSize = axis!.getTickSize(); final double axisSize = axis!.getAxisLineThickness(); - final LinearElementPosition position = - getEffectiveElementPosition(axis!.tickPosition, axis!.isMirrored); - final LinearLabelPosition labelPlacement = - getEffectiveLabelPosition(axis!.labelPosition, axis!.isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + axis!.tickPosition, + axis!.isMirrored, + ); + final LinearLabelPosition labelPlacement = getEffectiveLabelPosition( + axis!.labelPosition, + axis!.isMirrored, + ); final bool isInsideLabel = labelPlacement == LinearLabelPosition.inside; switch (position) { @@ -340,15 +364,20 @@ class RenderLinearGauge extends RenderBox } else if (axisSize < thickness) { final double sizeDifference = (thickness - axisSize) / 2 - (isInsideLabel ? 0 : labelSize); - _outsideWidgetElementSize = - math.max(sizeDifference, _outsideWidgetElementSize); + _outsideWidgetElementSize = math.max( + sizeDifference, + _outsideWidgetElementSize, + ); if (_axisWidgetThickness < thickness) { double axisSizeDifference = (thickness - axisSize) / 2; - axisSizeDifference = axisSizeDifference - + axisSizeDifference = + axisSizeDifference - (tickSize + (isInsideLabel ? labelSize : 0)); - _insideWidgetElementSize = - math.max(axisSizeDifference, _insideWidgetElementSize); + _insideWidgetElementSize = math.max( + axisSizeDifference, + _insideWidgetElementSize, + ); } } break; @@ -356,85 +385,102 @@ class RenderLinearGauge extends RenderBox if (axisSize == 0) { _updateInsideElementSize(thickness); } else if (axisSize < thickness) { - final double sizeDifference = (thickness - axisSize) / 2 - + final double sizeDifference = + (thickness - axisSize) / 2 - (tickSize + (isInsideLabel ? 0 : labelSize)); - _outsideWidgetElementSize = - math.max(sizeDifference, _outsideWidgetElementSize); + _outsideWidgetElementSize = math.max( + sizeDifference, + _outsideWidgetElementSize, + ); double axisSizeDifference = (thickness - axisSize) / 2; axisSizeDifference = axisSizeDifference - (isInsideLabel ? labelSize : 0); - _insideWidgetElementSize = - math.max(axisSizeDifference, _insideWidgetElementSize); + _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) - + final double sizeDifference = + ((thickness - axisSize) / 2) - ((tickSize - axisSize) / 2) - (isInsideLabel ? 0 : labelSize); - _outsideWidgetElementSize = - math.max(sizeDifference, _outsideWidgetElementSize); + _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); + _insideWidgetElementSize = math.max( + axisSizeDifference, + _insideWidgetElementSize, + ); } } else if (axisSize < thickness) { final double sizeDifference = ((thickness - axisSize) / 2) - (isInsideLabel ? 0 : labelSize); - _outsideWidgetElementSize = - math.max(sizeDifference, _outsideWidgetElementSize); + _outsideWidgetElementSize = math.max( + sizeDifference, + _outsideWidgetElementSize, + ); if (_axisWidgetThickness < thickness) { double axisSizeDifference = (thickness - axisSize) / 2; axisSizeDifference = axisSizeDifference - (isInsideLabel ? labelSize : 0); - _insideWidgetElementSize = - math.max(axisSizeDifference, _insideWidgetElementSize); + _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 LinearElementPosition pointerPosition = - getEffectiveElementPosition(position, axis!.isMirrored); + void _layoutPointerChild({ + required RenderObject renderObject, + required LinearElementPosition position, + required double thickness, + required double offset, + }) { + final LinearElementPosition pointerPosition = getEffectiveElementPosition( + position, + axis!.isMirrored, + ); switch (pointerPosition) { case LinearElementPosition.inside: _measureInsideElementSize(thickness + offset); break; case LinearElementPosition.outside: - _outsideWidgetElementSize = - math.max(_outsideWidgetElementSize, thickness + offset - _axisTop); + _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}) { + void _positionChildElement( + RenderObject linearGaugeChild, { + double thickness = 0, + }) { final MultiChildLayoutParentData? childParentData = linearGaugeChild.parentData as MultiChildLayoutParentData?; final double xPoint = @@ -442,23 +488,30 @@ class RenderLinearGauge extends RenderBox final double yPoint = _pointY; if (_isHorizontalOrientation) { - childParentData!.offset = - Offset(xPoint - (_isAxisInversed ? thickness : 0), yPoint); + childParentData!.offset = Offset( + xPoint - (_isAxisInversed ? thickness : 0), + yPoint, + ); } else { - childParentData!.offset = - Offset(yPoint, xPoint - (!_isAxisInversed ? thickness : 0)); + childParentData!.offset = Offset( + yPoint, + xPoint - (!_isAxisInversed ? thickness : 0), + ); } } /// Calculates the marker pointer offset. - double? _calculateMarkerOffset( - {required LinearElementPosition elementPosition, - required double offset, - required Size size}) { + double? _calculateMarkerOffset({ + required LinearElementPosition elementPosition, + required double offset, + required Size size, + }) { final double markerSize = _isHorizontalOrientation ? size.height : size.width; - final LinearElementPosition pointerPosition = - getEffectiveElementPosition(elementPosition, axis!.isMirrored); + final LinearElementPosition pointerPosition = getEffectiveElementPosition( + elementPosition, + axis!.isMirrored, + ); switch (pointerPosition) { case LinearElementPosition.inside: return _outsideWidgetElementSize + @@ -476,11 +529,7 @@ class RenderLinearGauge extends RenderBox return _outsideWidgetElementSize + _getCrossElementPosition(markerSize) + (_actualSizeDelta! / 2); - default: - break; } - - return null; } double _getCrossElementPosition(double width) { @@ -498,8 +547,10 @@ class RenderLinearGauge extends RenderBox } } - void _updatePointerPositionOnDrag(RenderLinearPointerBase pointer, - {bool isDragCall = false}) { + void _updatePointerPositionOnDrag( + RenderLinearPointerBase pointer, { + bool isDragCall = false, + }) { double animationValue = 1; if (!isDragCall) { @@ -511,15 +562,18 @@ class RenderLinearGauge extends RenderBox } } - final double startPosition = - axis!.valueToPixel(pointer.oldValue ?? axis!.minimum); - final double endPosition = (axis!.valueToPixel(pointer.value)).abs(); + final double startPosition = axis!.valueToPixel( + pointer.oldValue ?? axis!.minimum, + ); + final double 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))!; + _pointY = + _calculateMarkerOffset( + elementPosition: pointer.position, + offset: pointer.offset, + size: Size(pointer.size.width, pointer.size.height), + )!; /// _pointX calculation is depends on animation, so the constrained marker /// goes beyond the reference marker even though it's constrained. To avoid @@ -569,17 +623,20 @@ class RenderLinearGauge extends RenderBox _pointerEndPadding = 0; _markerPointers.clear(); - _markerPointers = >[ - _shapePointers, - _widgetPointers - ].expand((List x) => x).toList(); - - final double width = constraints.hasBoundedWidth - ? constraints.maxWidth - : kDefaultLinearGaugeWidth; - final double height = constraints.hasBoundedHeight - ? constraints.maxHeight - : kDefaultLinearGaugeHeight; + _markerPointers = + >[ + _shapePointers, + _widgetPointers, + ].expand((List x) => x).toList(); + + final double width = + constraints.hasBoundedWidth + ? constraints.maxWidth + : kDefaultLinearGaugeWidth; + final double height = + constraints.hasBoundedHeight + ? constraints.maxHeight + : kDefaultLinearGaugeHeight; _parentConstraints = BoxConstraints(maxWidth: width, maxHeight: height); } @@ -589,9 +646,10 @@ class RenderLinearGauge extends RenderBox for (final dynamic pointer in _markerPointers) { pointer.layout(_parentConstraints, parentUsesSize: true); - final double thickness = _isHorizontalOrientation - ? pointer.size.width as double - : pointer.size.height as double; + final double thickness = + _isHorizontalOrientation + ? pointer.size.width as double + : pointer.size.height as double; if (pointer.markerAlignment == LinearMarkerAlignment.start) { _pointerEndPadding = math.max(_pointerEndPadding, thickness); @@ -618,12 +676,14 @@ class RenderLinearGauge extends RenderBox final double padding = axis!.getAxisLayoutPadding(); if (_isHorizontalOrientation) { return BoxConstraints( - maxWidth: _parentConstraints.maxWidth - padding, - maxHeight: _parentConstraints.maxHeight); + maxWidth: _parentConstraints.maxWidth - padding, + maxHeight: _parentConstraints.maxHeight, + ); } else { return BoxConstraints( - maxWidth: _parentConstraints.maxWidth, - maxHeight: _parentConstraints.maxHeight - padding); + maxWidth: _parentConstraints.maxWidth, + maxHeight: _parentConstraints.maxHeight - padding, + ); } } @@ -650,22 +710,24 @@ class RenderLinearGauge extends RenderBox range.layout(_childConstraints, parentUsesSize: true); final double rangeThickness = _isHorizontalOrientation ? range.size.height : range.size.width; - final LinearElementPosition position = - getEffectiveElementPosition(range.position, range.isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + range.position, + range.isMirrored, + ); switch (position) { case LinearElementPosition.inside: _measureInsideElementSize(rangeThickness); break; case LinearElementPosition.outside: - _outsideWidgetElementSize = - math.max(_outsideWidgetElementSize, rangeThickness - _axisTop); + _outsideWidgetElementSize = math.max( + _outsideWidgetElementSize, + rangeThickness - _axisTop, + ); break; case LinearElementPosition.cross: _measureCrossElementSize(rangeThickness); break; - default: - break; } } } @@ -678,10 +740,11 @@ class RenderLinearGauge extends RenderBox barPointer.layout(_childConstraints, parentUsesSize: true); _layoutPointerChild( - renderObject: barPointer, - position: barPointer.position, - thickness: barPointer.thickness, - offset: barPointer.offset); + renderObject: barPointer, + position: barPointer.position, + thickness: barPointer.thickness, + offset: barPointer.offset, + ); } } } @@ -689,15 +752,17 @@ class RenderLinearGauge extends RenderBox void _measureMarkerPointersSize() { if (_markerPointers.isNotEmpty) { for (final dynamic markerPointer in _markerPointers) { - final double thickness = _isHorizontalOrientation - ? markerPointer.size.height as double - : markerPointer.size.width as double; + final double thickness = + _isHorizontalOrientation + ? markerPointer.size.height as double + : markerPointer.size.width as double; _layoutPointerChild( - renderObject: markerPointer, - position: markerPointer.position, - thickness: thickness, - offset: markerPointer.offset); + renderObject: markerPointer, + position: markerPointer.position, + thickness: thickness, + offset: markerPointer.offset, + ); } } } @@ -707,22 +772,25 @@ class RenderLinearGauge extends RenderBox double actualHeight, actualWidth; if (_isHorizontalOrientation) { - actualHeight = constraints.hasBoundedHeight - ? constraints.maxHeight - : _axisWidgetThickness + - _outsideWidgetElementSize + - _insideWidgetElementSize; + actualHeight = + constraints.hasBoundedHeight + ? constraints.maxHeight + : _axisWidgetThickness + + _outsideWidgetElementSize + + _insideWidgetElementSize; actualWidth = _parentConstraints.maxWidth; } else { actualHeight = _parentConstraints.maxHeight; - actualWidth = constraints.hasBoundedWidth - ? constraints.maxWidth - : _axisWidgetThickness + - _outsideWidgetElementSize + - _insideWidgetElementSize; + actualWidth = + constraints.hasBoundedWidth + ? constraints.maxWidth + : _axisWidgetThickness + + _outsideWidgetElementSize + + _insideWidgetElementSize; } - _actualSizeDelta = (_isHorizontalOrientation ? actualHeight : actualWidth) - + _actualSizeDelta = + (_isHorizontalOrientation ? actualHeight : actualWidth) - (_axisWidgetThickness + _outsideWidgetElementSize + _insideWidgetElementSize); @@ -758,13 +826,16 @@ class RenderLinearGauge extends RenderBox _pointX = axis!.valueToPixel(range.startValue).abs(); - final LinearElementPosition position = - getEffectiveElementPosition(range.position, range.isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + range.position, + range.isMirrored, + ); final double axisSize = axis!.showAxisTrack ? axis!.thickness : 0.0; switch (position) { case LinearElementPosition.inside: - _pointY = _outsideWidgetElementSize + + _pointY = + _outsideWidgetElementSize + _axisTop + axisSize + (_actualSizeDelta! / 2); @@ -777,12 +848,11 @@ class RenderLinearGauge extends RenderBox _pointY = positionY + (_actualSizeDelta! / 2); break; case LinearElementPosition.cross: - _pointY = _outsideWidgetElementSize + + _pointY = + _outsideWidgetElementSize + (_actualSizeDelta! / 2) + _getCrossElementPosition(thickness); break; - default: - break; } _positionChildElement(range, thickness: rangeWidth); @@ -796,23 +866,28 @@ class RenderLinearGauge extends RenderBox for (final RenderLinearBarPointer barPointer in _barPointers) { _pointX = axis!.valueToPixel(axis!.minimum).abs(); - final double barWidth = _isHorizontalOrientation - ? barPointer.size.width - : barPointer.size.height; + final double barWidth = + _isHorizontalOrientation + ? barPointer.size.width + : barPointer.size.height; - final LinearElementPosition position = - getEffectiveElementPosition(barPointer.position, axis!.isMirrored); + final LinearElementPosition position = getEffectiveElementPosition( + barPointer.position, + axis!.isMirrored, + ); switch (position) { case LinearElementPosition.inside: - _pointY = _outsideWidgetElementSize + + _pointY = + _outsideWidgetElementSize + _axisTop + axis!.getAxisLineThickness() + barPointer.offset + (_actualSizeDelta! / 2); break; case LinearElementPosition.outside: - _pointY = (_actualSizeDelta! / 2) + + _pointY = + (_actualSizeDelta! / 2) + (barPointer.offset * -1) + (_outsideWidgetElementSize + _axisTop > barPointer.thickness ? _outsideWidgetElementSize + @@ -821,12 +896,11 @@ class RenderLinearGauge extends RenderBox : 0); break; case LinearElementPosition.cross: - _pointY = _outsideWidgetElementSize + + _pointY = + _outsideWidgetElementSize + (_actualSizeDelta! / 2) + _getCrossElementPosition(barPointer.thickness); break; - default: - break; } _positionChildElement(barPointer, thickness: barWidth); @@ -925,7 +999,9 @@ class RenderLinearGauge extends RenderBox } void _applyConstraintBehavior( - RenderLinearPointerBase markerRenderObject, double currentValue) { + RenderLinearPointerBase markerRenderObject, + double currentValue, + ) { if (currentValue > _markerRenderObject.dragRangeMin! && currentValue < _markerRenderObject.dragRangeMax!) { _markerRenderObject.constrainedBy = ConstrainedBy.none; @@ -944,7 +1020,9 @@ class RenderLinearGauge extends RenderBox // This method for pull drag behavior for markers. // ignore: unused_element void _applyPullBehavior( - RenderLinearPointerBase markerRenderObject, double currentValue) { + RenderLinearPointerBase markerRenderObject, + double currentValue, + ) { for (final RenderLinearPointerBase markerPointer in _markerPointers) { if (markerPointer != markerRenderObject) { if (currentValue < _dragStartValue!) { @@ -982,12 +1060,14 @@ class RenderLinearGauge extends RenderBox } void _findDraggableRange(RenderLinearPointerBase pointer) { - pointer.dragRangeMin = pointer.constrainedBy == ConstrainedBy.min - ? pointer.value - : axis!.minimum; - pointer.dragRangeMax = pointer.constrainedBy == ConstrainedBy.max - ? pointer.value - : axis!.maximum; + pointer.dragRangeMin = + pointer.constrainedBy == ConstrainedBy.min + ? pointer.value + : axis!.minimum; + pointer.dragRangeMax = + pointer.constrainedBy == ConstrainedBy.max + ? pointer.value + : axis!.maximum; for (int i = 0; i < _markerPointers.length; i++) { final double currentValue = _markerPointers[i].value; if (pointer.constrainedBy != ConstrainedBy.min && @@ -1049,42 +1129,58 @@ class RenderLinearGauge extends RenderBox @override void paint(PaintingContext context, Offset offset) { context.pushClipRect( - needsCompositing, offset, Rect.fromLTWH(0, 0, size.width, size.height), - (PaintingContext context, Offset offset) { - defaultPaint(context, offset); - // There's no point in drawing the children if we're empty. - if (size.isEmpty) { - return; - } + needsCompositing, + offset, + Rect.fromLTWH(0, 0, size.width, size.height), + (PaintingContext context, Offset 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( + 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); - } + '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; - }()); - }); + 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 index 9f11cf5f8..49478b53d 100644 --- 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 @@ -1,20 +1,20 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_gauges/src/linear_gauge/utils/enum.dart'; import '../../linear_gauge/gauge/linear_gauge.dart'; +import '../utils/enum.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); + 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; @@ -35,9 +35,11 @@ class LinearGaugeScope extends InheritedWidget { static LinearGaugeScope of(BuildContext context) { late LinearGaugeScope scope; - final InheritedWidget widget = context - .getElementForInheritedWidgetOfExactType()! - .widget; + final InheritedWidget widget = + context + .getElementForInheritedWidgetOfExactType()! + .widget + as InheritedWidget; if (widget is LinearGaugeScope) { scope = widget; 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 index 601a0694b..a2cdab80e 100644 --- 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 @@ -7,24 +7,24 @@ 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]. - const 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); + const 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 [SfLinearGauge.barPointers]. /// This value must be between the min and max value of an axis track. @@ -270,34 +270,49 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { RenderObject createRenderObject(BuildContext context) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData themeData = Theme.of(context); + final bool isMaterial3 = themeData.useMaterial3; + final bool isDarkTheme = themeData.brightness == Brightness.dark; + final Color barPointerColor = + isMaterial3 + ? (isDarkTheme ? const Color(0XFFFFF500) : const Color(0XFF06AEE0)) + : themeData.colorScheme.primary; return RenderLinearBarPointer( - value: value, - edgeStyle: edgeStyle, - shaderCallback: shaderCallback, - color: color ?? themeData.colorScheme.primary, - borderColor: borderColor ?? themeData.colorScheme.primary, - borderWidth: borderWidth, - thickness: thickness, - offset: offset, - position: position, - orientation: linearGaugeScope.orientation, - isAxisInversed: linearGaugeScope.isAxisInversed, - onAnimationCompleted: onAnimationCompleted, - animationController: linearGaugeScope.animationController, - pointerAnimation: linearGaugeScope.animation); + value: value, + edgeStyle: edgeStyle, + shaderCallback: shaderCallback, + color: color ?? barPointerColor, + borderColor: borderColor ?? barPointerColor, + 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) { + BuildContext context, + RenderLinearBarPointer renderObject, + ) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData themeData = Theme.of(context); + final bool isMaterial3 = themeData.useMaterial3; + final bool isDarkTheme = themeData.brightness == Brightness.dark; + final Color barPointerColor = + isMaterial3 + ? (isDarkTheme ? const Color(0XFFFFF500) : const Color(0XFF06AEE0)) + : themeData.colorScheme.primary; renderObject ..value = value ..edgeStyle = edgeStyle ..shaderCallback = shaderCallback - ..color = color ?? themeData.colorScheme.primary - ..borderColor = borderColor ?? themeData.colorScheme.primary + ..color = color ?? barPointerColor + ..borderColor = borderColor ?? barPointerColor ..borderWidth = borderWidth ..thickness = thickness ..offset = offset 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 index a0f632f93..98c1df401 100644 --- 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 @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -8,34 +10,34 @@ 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, - this.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, - _isAxisInversed = isAxisInversed { + 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, + this.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, + _isAxisInversed = isAxisInversed { _barPaint = Paint(); _isHorizontal = orientation == LinearGaugeOrientation.horizontal; _path = Path(); @@ -68,7 +70,11 @@ class RenderLinearBarPointer extends RenderOpacity { if (animationController != null && animationController!.isAnimating) { animationController!.stop(); _oldBarRect = Rect.fromLTWH( - _barPointerOffset.dx, _barPointerOffset.dy, size.width, size.height); + _barPointerOffset.dx, + _barPointerOffset.dy, + size.width, + size.height, + ); } _value = value; @@ -201,7 +207,7 @@ class RenderLinearBarPointer extends RenderOpacity { } _isAxisInversed = value; - markNeedsPaint(); + markNeedsLayout(); } /// Gets the orientation assigned to [RenderLinearBarPointer]. @@ -316,7 +322,10 @@ class RenderLinearBarPointer extends RenderOpacity { child!.layout(BoxConstraints.tight(controlSize)); } - size = controlSize; + size = Size( + min(controlSize.width, constraints.maxWidth), + min(controlSize.height, constraints.maxHeight), + ); } ///Measures the bar rect. @@ -333,15 +342,15 @@ class RenderLinearBarPointer extends RenderOpacity { 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); + 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, @@ -358,10 +367,11 @@ class RenderLinearBarPointer extends RenderOpacity { if (borderWidth > 0) { _barRect = Rect.fromLTWH( - _barRect.left + borderWidth / 2, - _barRect.top + borderWidth / 2, - _barRect.width - borderWidth, - _barRect.height - borderWidth); + _barRect.left + borderWidth / 2, + _barRect.top + borderWidth / 2, + _barRect.width - borderWidth, + _barRect.height - borderWidth, + ); } } @@ -373,23 +383,28 @@ class RenderLinearBarPointer extends RenderOpacity { break; case LinearEdgeStyle.bothCurve: _path.addRRect( - RRect.fromRectAndRadius(_barRect, Radius.circular(thickness / 2))); + RRect.fromRectAndRadius(_barRect, Radius.circular(thickness / 2)), + ); break; case LinearEdgeStyle.startCurve: - _path.addRRect(getStartCurve( + _path.addRRect( + getStartCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: _barRect, - radius: thickness / 2)); + radius: thickness / 2, + ), + ); break; case LinearEdgeStyle.endCurve: - _path.addRRect(getEndCurve( + _path.addRRect( + getEndCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: _barRect, - radius: thickness / 2)); - break; - default: + radius: thickness / 2, + ), + ); break; } 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 index 5884d7f7c..3f316b50c 100644 --- 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 @@ -5,19 +5,20 @@ import '../../linear_gauge/utils/enum.dart'; /// [LinearMarkerPointer] 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.onChanged, - this.onChangeStart, - this.onChangeEnd, - this.enableAnimation = false, - this.animationDuration = 1000, - this.animationType = LinearAnimationType.ease, - this.offset = 0.0, - this.markerAlignment = LinearMarkerAlignment.center, - this.position = LinearElementPosition.cross, - this.dragBehavior = LinearMarkerDragBehavior.free, - this.onAnimationCompleted}); + LinearMarkerPointer({ + required this.value, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.enableAnimation = false, + this.animationDuration = 1000, + this.animationType = LinearAnimationType.ease, + this.offset = 0.0, + this.markerAlignment = LinearMarkerAlignment.center, + this.position = LinearElementPosition.cross, + this.dragBehavior = LinearMarkerDragBehavior.free, + this.onAnimationCompleted, + }); /// Specifies the linear axis value to place the pointer. /// @@ -114,30 +115,30 @@ abstract class LinearMarkerPointer { /// Represents the render object base class for shape and widget pointer. class RenderLinearPointerBase extends RenderProxyBox { /// Creates a instance for [RenderLinearPointerBase] - RenderLinearPointerBase( - {required double value, - ValueChanged? onChanged, - this.onChangeStart, - this.onChangeEnd, - required double offset, - required LinearElementPosition position, - required LinearMarkerAlignment markerAlignment, - required bool isAxisInversed, - required bool isMirrored, - Animation? pointerAnimation, - VoidCallback? onAnimationCompleted, - required LinearMarkerDragBehavior dragBehavior, - this.animationController}) - : _value = value, - _onChanged = onChanged, - _offset = offset, - _position = position, - _dragBehavior = dragBehavior, - _markerAlignment = markerAlignment, - _pointerAnimation = pointerAnimation, - _isAxisInversed = isAxisInversed, - _isMirrored = isMirrored, - _onAnimationCompleted = onAnimationCompleted; + RenderLinearPointerBase({ + required double value, + ValueChanged? onChanged, + this.onChangeStart, + this.onChangeEnd, + required double offset, + required LinearElementPosition position, + required LinearMarkerAlignment markerAlignment, + required bool isAxisInversed, + required bool isMirrored, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + required LinearMarkerDragBehavior dragBehavior, + this.animationController, + }) : _value = value, + _onChanged = onChanged, + _offset = offset, + _position = position, + _dragBehavior = dragBehavior, + _markerAlignment = markerAlignment, + _pointerAnimation = pointerAnimation, + _isAxisInversed = isAxisInversed, + _isMirrored = isMirrored, + _onAnimationCompleted = onAnimationCompleted; /// Gets or sets the shape pointer old value. double? oldValue; 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 index daf132e45..e4f3dd9bd 100644 --- 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 @@ -8,30 +8,30 @@ import '../../linear_gauge/utils/enum.dart'; class LinearShapePointer extends LeafRenderObjectWidget implements LinearMarkerPointer { /// Creates a shape marker pointer for linear axis. - const LinearShapePointer( - {Key? key, - required this.value, - this.onChanged, - this.onChangeStart, - this.onChangeEnd, - 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.dragBehavior = LinearMarkerDragBehavior.free, - this.color, - this.borderColor, - this.borderWidth = 0.0, - this.elevation = 0, - this.elevationColor = Colors.black}) - : offset = offset > 0 ? offset : 0, - super(key: key); + const LinearShapePointer({ + Key? key, + required this.value, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, + 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.dragBehavior = LinearMarkerDragBehavior.free, + 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 [LinearShapePointer]. /// This value must be between the min and max value of an axis track. @@ -442,58 +442,63 @@ class LinearShapePointer extends LeafRenderObjectWidget final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; + final bool isMaterial3 = theme.useMaterial3; + final Color shapePointerColor = + isMaterial3 + ? theme.colorScheme.onSurfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withValues(alpha: 0.70) + : theme.colorScheme.onSurface.withValues(alpha: 0.54); return RenderLinearShapePointer( - value: value, - onChanged: onChanged, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - color: color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)), - borderColor: borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)), - 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, - isAxisInversed: linearGaugeScope.isAxisInversed, - markerAlignment: markerAlignment, - animationController: linearGaugeScope.animationController, - dragBehavior: dragBehavior, - onAnimationCompleted: onAnimationCompleted, - pointerAnimation: linearGaugeScope.animation); + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + color: color ?? shapePointerColor, + borderColor: borderColor ?? shapePointerColor, + 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, + isAxisInversed: linearGaugeScope.isAxisInversed, + markerAlignment: markerAlignment, + animationController: linearGaugeScope.animationController, + dragBehavior: dragBehavior, + onAnimationCompleted: onAnimationCompleted, + pointerAnimation: linearGaugeScope.animation, + ); } @override void updateRenderObject( - BuildContext context, RenderLinearShapePointer renderObject) { + BuildContext context, + RenderLinearShapePointer renderObject, + ) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; + final bool isMaterial3 = theme.useMaterial3; + final Color shapePointerColor = + isMaterial3 + ? theme.colorScheme.onSurfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withValues(alpha: 0.70) + : theme.colorScheme.onSurface.withValues(alpha: 0.54); renderObject ..value = value ..onChanged = onChanged ..onChangeStart = onChangeStart ..onChangeEnd = onChangeEnd - ..color = color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)) - ..borderColor = borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)) + ..color = color ?? shapePointerColor + ..borderColor = borderColor ?? shapePointerColor ..borderWidth = borderWidth ..width = width ?? (shapeType == LinearShapePointerType.diamond ? 12 : 16) ..height = 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 index 34f44f4e0..939a4eae2 100644 --- 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 @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/core.dart' as core; @@ -31,29 +33,30 @@ class RenderLinearShapePointer extends RenderLinearPointerBase { VoidCallback? onAnimationCompleted, required LinearMarkerDragBehavior dragBehavior, AnimationController? animationController, - }) : _color = color, - _borderColor = borderColor, - _borderWidth = borderWidth, - _shapeType = shapeType, - _elevation = elevation, - _orientation = orientation, - _elevationColor = elevationColor, - _width = width, - _height = height, - super( - value: value, - onChanged: onChanged, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - offset: offset, - position: position, - dragBehavior: dragBehavior, - markerAlignment: markerAlignment, - isMirrored: isMirrored, - isAxisInversed: isAxisInversed, - pointerAnimation: pointerAnimation, - animationController: animationController, - onAnimationCompleted: onAnimationCompleted) { + }) : _color = color, + _borderColor = borderColor, + _borderWidth = borderWidth, + _shapeType = shapeType, + _elevation = elevation, + _orientation = orientation, + _elevationColor = elevationColor, + _width = width, + _height = height, + super( + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + offset: offset, + position: position, + dragBehavior: dragBehavior, + markerAlignment: markerAlignment, + isMirrored: isMirrored, + isAxisInversed: isAxisInversed, + pointerAnimation: pointerAnimation, + animationController: animationController, + onAnimationCompleted: onAnimationCompleted, + ) { _shapePaint = Paint(); _borderPaint = Paint(); } @@ -230,7 +233,10 @@ class RenderLinearShapePointer extends RenderLinearPointerBase { @override void performLayout() { - size = Size(width, height); + size = Size( + min(width, constraints.maxWidth), + min(height, constraints.maxHeight), + ); } @override @@ -243,10 +249,11 @@ class RenderLinearShapePointer extends RenderLinearPointerBase { 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); + offset.dx + borderWidth / 2, + offset.dy + borderWidth / 2, + size.width - borderWidth, + size.height - borderWidth, + ); _shapePaint.style = PaintingStyle.fill; _shapePaint.color = color; @@ -294,18 +301,17 @@ class RenderLinearShapePointer extends RenderLinearPointerBase { case LinearShapePointerType.diamond: markerType = core.ShapeMarkerType.diamond; break; - default: - break; } core.paint( - canvas: canvas, - rect: _shapeRect, - elevation: elevation, - shapeType: markerType, - elevationColor: elevationColor, - paint: _shapePaint, - borderPaint: _borderPaint); + canvas: canvas, + rect: _shapeRect, + elevation: elevation, + shapeType: markerType, + elevationColor: elevationColor, + paint: _shapePaint, + borderPaint: _borderPaint, + ); } } } 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 index 955948520..d060452d8 100644 --- 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 @@ -10,23 +10,23 @@ import '../../linear_gauge/utils/enum.dart'; class LinearWidgetPointer extends SingleChildRenderObjectWidget implements LinearMarkerPointer { /// Creates a widget marker pointer. - const LinearWidgetPointer( - {Key? key, - required this.value, - this.onChanged, - this.onChangeStart, - this.onChangeEnd, - this.enableAnimation = true, - this.animationDuration = 1000, - this.animationType = LinearAnimationType.ease, - this.onAnimationCompleted, - double offset = 0.0, - this.position = LinearElementPosition.cross, - this.markerAlignment = LinearMarkerAlignment.center, - this.dragBehavior = LinearMarkerDragBehavior.free, - required Widget child}) - : offset = offset > 0 ? offset : 0, - super(key: key, child: child); + const LinearWidgetPointer({ + Key? key, + required this.value, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.enableAnimation = true, + this.animationDuration = 1000, + this.animationType = LinearAnimationType.ease, + this.onAnimationCompleted, + double offset = 0.0, + this.position = LinearElementPosition.cross, + this.markerAlignment = LinearMarkerAlignment.center, + this.dragBehavior = LinearMarkerDragBehavior.free, + required Widget child, + }) : offset = offset > 0 ? offset : 0, + super(key: key, child: child); /// Specifies the pointer value for [LinearWidgetPointer]. /// This value must be between the min and max value of an axis track. @@ -320,24 +320,27 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget RenderObject createRenderObject(BuildContext context) { final LinearGaugeScope scope = LinearGaugeScope.of(context); return RenderLinearWidgetPointer( - value: value, - onChanged: onChanged, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - offset: offset, - position: position, - markerAlignment: markerAlignment, - animationController: scope.animationController, - onAnimationCompleted: onAnimationCompleted, - pointerAnimation: scope.animation, - isAxisInversed: scope.isAxisInversed, - isMirrored: scope.isMirrored, - dragBehavior: dragBehavior); + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + offset: offset, + position: position, + markerAlignment: markerAlignment, + animationController: scope.animationController, + onAnimationCompleted: onAnimationCompleted, + pointerAnimation: scope.animation, + isAxisInversed: scope.isAxisInversed, + isMirrored: scope.isMirrored, + dragBehavior: dragBehavior, + ); } @override void updateRenderObject( - BuildContext context, RenderLinearWidgetPointer renderObject) { + BuildContext context, + RenderLinearWidgetPointer renderObject, + ) { final LinearGaugeScope scope = LinearGaugeScope.of(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 index 5130519e2..9d25eb5b7 100644 --- 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 @@ -1,40 +1,40 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_gauges/src/linear_gauge/pointers/linear_marker_pointer.dart'; import '../../linear_gauge/utils/enum.dart'; +import 'linear_marker_pointer.dart'; /// Represents the render object of shape pointer. class RenderLinearWidgetPointer extends RenderLinearPointerBase { /// Creates a instance for [RenderLinearWidgetPointer]. - RenderLinearWidgetPointer( - {required double value, - ValueChanged? onChanged, - ValueChanged? onChangeStart, - ValueChanged? onChangeEnd, - required double offset, - required LinearElementPosition position, - required LinearMarkerAlignment markerAlignment, - required bool isAxisInversed, - required bool isMirrored, - Animation? pointerAnimation, - VoidCallback? onAnimationCompleted, - required LinearMarkerDragBehavior dragBehavior, - AnimationController? animationController}) - : super( - value: value, - onChanged: onChanged, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - offset: offset, - position: position, - dragBehavior: dragBehavior, - markerAlignment: markerAlignment, - isAxisInversed: isAxisInversed, - isMirrored: isMirrored, - pointerAnimation: pointerAnimation, - animationController: animationController, - onAnimationCompleted: onAnimationCompleted, - ); + RenderLinearWidgetPointer({ + required double value, + ValueChanged? onChanged, + ValueChanged? onChangeStart, + ValueChanged? onChangeEnd, + required double offset, + required LinearElementPosition position, + required LinearMarkerAlignment markerAlignment, + required bool isAxisInversed, + required bool isMirrored, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + required LinearMarkerDragBehavior dragBehavior, + AnimationController? animationController, + }) : super( + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + offset: offset, + position: position, + dragBehavior: dragBehavior, + markerAlignment: markerAlignment, + isAxisInversed: isAxisInversed, + isMirrored: isMirrored, + pointerAnimation: pointerAnimation, + animationController: animationController, + onAnimationCompleted: onAnimationCompleted, + ); @override bool hitTestSelf(Offset position) { 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 index 1a715bfba..b1e40a4fe 100644 --- 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 @@ -8,24 +8,24 @@ import '../../linear_gauge/utils/enum.dart'; class LinearGaugeRange extends SingleChildRenderObjectWidget { /// Creates a new range in linear gauge. /// - const 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); + const 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. /// @@ -205,32 +205,37 @@ class LinearGaugeRange extends SingleChildRenderObjectWidget { final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; return RenderLinearRange( - color: color ?? - (isDarkTheme ? const Color(0xffFF7B7B) : const 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); + color: + color ?? + (isDarkTheme ? const Color(0xffFF7B7B) : const 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) { + BuildContext context, + RenderLinearRange renderObject, + ) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; renderObject - ..color = color ?? + ..color = + color ?? (isDarkTheme ? const Color(0xffFF7B7B) : const Color(0xffF45656)) ..position = position ..startValue = startValue 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 index 38dddf0ad..18f853ec8 100644 --- 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 @@ -10,39 +10,39 @@ 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}) - : _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 { + 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, + }) : _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.zero, growable: false); + _rangeOffsets = List.filled(5, Offset.zero); _isHorizontal = orientation == LinearGaugeOrientation.horizontal; _path = Path(); } @@ -299,8 +299,10 @@ class RenderLinearRange extends RenderOpacity { @override void performLayout() { - final double thickness = - max(max(startThickness, midThickness), endThickness); + final double thickness = max( + max(startThickness, midThickness), + endThickness, + ); double rangeWidth = 0; if (axis != null) { @@ -319,7 +321,10 @@ class RenderLinearRange extends RenderOpacity { child!.layout(BoxConstraints.tight(controlSize)); } - size = controlSize; + size = Size( + min(controlSize.width, constraints.maxWidth), + min(controlSize.height, constraints.maxHeight), + ); } ///Calculation Position based on value. @@ -341,31 +346,31 @@ class RenderLinearRange extends RenderOpacity { void _getRangeOffsets() { final LinearElementPosition rangeElementPosition = getEffectiveElementPosition(position, isMirrored); - double _bottom = _rangeOffset.dy + _rangeRect.height; + double bottom = _rangeOffset.dy + _rangeRect.height; if (orientation == LinearGaugeOrientation.vertical) { - _bottom = _rangeOffset.dx + _rangeRect.width; + bottom = _rangeOffset.dx + _rangeRect.width; } - final double _leftStart = _getPosition(startValue); - final double _leftMid = _getPosition(midValue.clamp(startValue, endValue)); - final double _leftEnd = _getPosition(endValue); - double _topStart = _bottom - startThickness; - double _topMid = _bottom - midThickness; - double _topEnd = _bottom - endThickness; + final double leftStart = _getPosition(startValue); + final double leftMid = _getPosition(midValue.clamp(startValue, endValue)); + final double 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(); + 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); + _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++) { @@ -381,8 +386,12 @@ class RenderLinearRange extends RenderOpacity { 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.quadraticBezierTo( + _rangeOffsets[1].dx, + _rangeOffsets[1].dy, + _rangeOffsets[2].dx, + _rangeOffsets[2].dy, + ); } path.lineTo(_rangeOffsets[3].dx, _rangeOffsets[3].dy); @@ -391,8 +400,12 @@ class RenderLinearRange extends RenderOpacity { void _getRangePath() { if (startThickness == endThickness && startThickness == midThickness) { - final Rect rangeRect = Rect.fromLTRB(_rangeOffsets[0].dx, - _rangeOffsets[0].dy, _rangeOffsets[3].dx, _rangeOffsets[3].dy); + final Rect rangeRect = Rect.fromLTRB( + _rangeOffsets[0].dx, + _rangeOffsets[0].dy, + _rangeOffsets[3].dx, + _rangeOffsets[3].dy, + ); if (rangeRect.hasNaN) { return; @@ -403,24 +416,32 @@ class RenderLinearRange extends RenderOpacity { _path.addRect(rangeRect); break; case LinearEdgeStyle.bothCurve: - _path.addRRect(RRect.fromRectAndRadius( - rangeRect, Radius.circular(startThickness / 2))); + _path.addRRect( + RRect.fromRectAndRadius( + rangeRect, + Radius.circular(startThickness / 2), + ), + ); break; case LinearEdgeStyle.startCurve: - _path.addRRect(getStartCurve( + _path.addRRect( + getStartCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: rangeRect, - radius: startThickness / 2)); + radius: startThickness / 2, + ), + ); break; case LinearEdgeStyle.endCurve: - _path.addRRect(getEndCurve( + _path.addRRect( + getEndCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: rangeRect, - radius: startThickness / 2)); - break; - default: + radius: startThickness / 2, + ), + ); break; } } else { @@ -437,7 +458,7 @@ class RenderLinearRange extends RenderOpacity { animationValue = _rangeAnimation!.value; } - _rangePaint.color = color.withOpacity(animationValue * color.opacity); + _rangePaint.color = color.withValues(alpha: animationValue * color.a); _path.reset(); _getRangePath(); canvas.drawPath(_path, _rangePaint); 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 index c77520321..f07f101a9 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart @@ -4,7 +4,7 @@ enum LinearGaugeOrientation { vertical, ///LinearGaugeOrientation.horizontal will align the linear gauge in horizontal orientation. - horizontal + horizontal, } /// Apply the shape style for range element. @@ -13,7 +13,7 @@ enum LinearRangeShapeType { flat, /// LinearRangeShapeType.curve apply the curve shape between start and end value. - curve + curve, } /// Apply the edge style for range pointer. @@ -28,7 +28,7 @@ enum LinearEdgeStyle { startCurve, /// LinearEdgeStyle.endCurve apply the rounded corner on end(right) side. - endCurve + endCurve, } /// Apply the different marker pointer. @@ -70,7 +70,7 @@ enum LinearAnimationType { linear, /// LinearAnimationType.slowMiddle animates the pointers with Curves.slowMiddle. - slowMiddle + slowMiddle, } /// Apply the different label position based on Axis. @@ -79,7 +79,7 @@ enum LinearLabelPosition { inside, /// LinearAnimationType.outside places the elements inside the axis. - outside + outside, } /// Apply the different element position based on Axis. @@ -91,7 +91,7 @@ enum LinearElementPosition { outside, /// LinearElementPosition.cross places the elements cross the axis. - cross + cross, } /// Apply the different pointer alignment based on Axis. @@ -103,7 +103,7 @@ enum LinearMarkerAlignment { start, /// LinearMarkerAlignment.end points the axis from outside position. - end + end, } /// Apply the different drag behavior for marker pointers. 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 index 70828ab43..d2ac5a0fa 100644 --- 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 @@ -3,32 +3,44 @@ import 'package:flutter/material.dart'; import '../../linear_gauge/utils/enum.dart'; RRect _getHorizontalStartCurve(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(radius), bottomLeft: Radius.circular(radius)); + return RRect.fromRectAndCorners( + rect, + topLeft: Radius.circular(radius), + bottomLeft: Radius.circular(radius), + ); } RRect _getHorizontalEndCurvePath(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - topRight: Radius.circular(radius), bottomRight: Radius.circular(radius)); + return RRect.fromRectAndCorners( + rect, + topRight: Radius.circular(radius), + bottomRight: Radius.circular(radius), + ); } RRect _getVerticalStartCurve(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(radius), topRight: Radius.circular(radius)); + return RRect.fromRectAndCorners( + rect, + topLeft: Radius.circular(radius), + topRight: Radius.circular(radius), + ); } RRect _getVerticalEndCurvePath(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - bottomLeft: Radius.circular(radius), - bottomRight: Radius.circular(radius)); + return RRect.fromRectAndCorners( + rect, + bottomLeft: Radius.circular(radius), + bottomRight: Radius.circular(radius), + ); } /// Returns the start curve path. -RRect getStartCurve( - {required bool isHorizontal, - required bool isAxisInversed, - required Rect rect, - required double radius}) { +RRect getStartCurve({ + required bool isHorizontal, + required bool isAxisInversed, + required Rect rect, + required double radius, +}) { if (isHorizontal) { return !isAxisInversed ? _getHorizontalStartCurve(rect, radius) @@ -41,11 +53,12 @@ RRect getStartCurve( } /// Returns the end curve path. -RRect getEndCurve( - {required bool isHorizontal, - required bool isAxisInversed, - required Rect rect, - required double radius}) { +RRect getEndCurve({ + required bool isHorizontal, + required bool isAxisInversed, + required Rect rect, + required double radius, +}) { if (isHorizontal) { return !isAxisInversed ? _getHorizontalEndCurvePath(rect, radius) @@ -59,13 +72,15 @@ RRect getEndCurve( /// Returns the effective element position. LinearElementPosition getEffectiveElementPosition( - LinearElementPosition position, bool isMirrored) { + LinearElementPosition position, + bool isMirrored, +) { if (isMirrored) { return (position == LinearElementPosition.inside) ? LinearElementPosition.outside : (position == LinearElementPosition.outside) - ? LinearElementPosition.inside - : LinearElementPosition.cross; + ? LinearElementPosition.inside + : LinearElementPosition.cross; } return position; @@ -73,11 +88,14 @@ LinearElementPosition getEffectiveElementPosition( /// Returns the effective label position. LinearLabelPosition getEffectiveLabelPosition( - LinearLabelPosition labelPlacement, bool isMirrored) { + LinearLabelPosition labelPlacement, + bool isMirrored, +) { if (isMirrored) { - labelPlacement = (labelPlacement == LinearLabelPosition.inside) - ? LinearLabelPosition.outside - : LinearLabelPosition.inside; + labelPlacement = + (labelPlacement == LinearLabelPosition.inside) + ? LinearLabelPosition.outside + : LinearLabelPosition.inside; } return labelPlacement; @@ -108,8 +126,6 @@ Curve getCurveAnimation(LinearAnimationType type) { case LinearAnimationType.slowMiddle: curve = Curves.slowMiddle; break; - default: - break; } return curve; } 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 aff4b16a9..540e0e13f 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 @@ -29,17 +29,19 @@ class GaugeAnnotation extends SingleChildRenderObjectWidget { /// /// The arguments [positionFactor] must not be null and [positionFactor] must /// be non-negative. - const GaugeAnnotation( - {Key? key, - this.axisValue, - this.horizontalAlignment = GaugeAlignment.center, - this.angle, - this.verticalAlignment = GaugeAlignment.center, - this.positionFactor = 0, - required this.widget}) - : assert( - positionFactor >= 0, 'Position factor must be greater than zero.'), - super(key: key, child: widget); + const GaugeAnnotation({ + Key? key, + this.axisValue, + this.horizontalAlignment = GaugeAlignment.center, + this.angle, + this.verticalAlignment = GaugeAlignment.center, + this.positionFactor = 0, + required this.widget, + }) : assert( + positionFactor >= 0, + 'Position factor must be greater than zero.', + ), + super(key: key, child: widget); /// Specifies the axis value for positioning annotation. /// @@ -188,7 +190,9 @@ class GaugeAnnotation extends SingleChildRenderObjectWidget { @override void updateRenderObject( - BuildContext context, RenderGaugeAnnotation renderObject) { + BuildContext context, + RenderGaugeAnnotation renderObject, + ) { final RadialAxisScope radialAxis = RadialAxisScope.of(context); renderObject ..axisValue = axisValue diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation_renderer.dart index 75eaf9158..1ddbbc34f 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation_renderer.dart @@ -10,23 +10,23 @@ import '../../radial_gauge/utils/helper.dart'; /// Represents the renderer of radial gauge annotation. class RenderGaugeAnnotation extends RenderOpacity { /// Creates a instance for [RenderGaugeAnnotation]. - RenderGaugeAnnotation( - {double? axisValue, - required GaugeAlignment horizontalAlignment, - double? angle, - required GaugeAlignment verticalAlignment, - required double positionFactor, - Animation? annotationAnimation, - required ValueNotifier repaintNotifier, - RenderBox? child}) - : _axisValue = axisValue, - _horizontalAlignment = horizontalAlignment, - _angle = angle, - _verticalAlignment = verticalAlignment, - _positionFactor = positionFactor, - _repaintNotifier = repaintNotifier, - _annotationAnimation = annotationAnimation, - super(child: child); + RenderGaugeAnnotation({ + double? axisValue, + required GaugeAlignment horizontalAlignment, + double? angle, + required GaugeAlignment verticalAlignment, + required double positionFactor, + Animation? annotationAnimation, + required ValueNotifier repaintNotifier, + RenderBox? child, + }) : _axisValue = axisValue, + _horizontalAlignment = horizontalAlignment, + _angle = angle, + _verticalAlignment = verticalAlignment, + _positionFactor = positionFactor, + _repaintNotifier = repaintNotifier, + _annotationAnimation = annotationAnimation, + super(child: child); /// Specifies the offset of positioning the annotation. late Offset _annotationPosition; @@ -136,7 +136,8 @@ class RenderGaugeAnnotation extends RenderOpacity { if (angle != null) { actualValue = angle!; } else if (axisValue != null) { - actualValue = (axisRenderer!.valueToFactor(axisValue!) * _sweepAngle) + + actualValue = + (axisRenderer!.valueToFactor(axisValue!) * _sweepAngle) + axisRenderer!.startAngle; } @@ -165,19 +166,22 @@ class RenderGaugeAnnotation extends RenderOpacity { final double offset = value * _radius; final double angle = _calculateActualAngle(); final double radian = getDegreeToRadian(angle); + final double axisHalfWidth = positionFactor == 1 ? _actualAxisWidth / 2 : 0; if (!axisRenderer!.canScaleToFit) { - final double x = (_axisSize.width / 2) + - (offset - (_actualAxisWidth / 2)) * math.cos(radian) - + final double x = + (_axisSize.width / 2) + + (offset - axisHalfWidth) * math.cos(radian) - _centerXPoint; - final double y = (_axisSize.height / 2) + - (offset - (_actualAxisWidth / 2)) * math.sin(radian) - + final double y = + (_axisSize.height / 2) + + (offset - axisHalfWidth) * math.sin(radian) - _centerYPoint; _annotationPosition = Offset(x, y); } else { final double x = - _axisCenter.dx + (offset - (_actualAxisWidth / 2)) * math.cos(radian); + _axisCenter.dx + (offset - axisHalfWidth) * math.cos(radian); final double y = - _axisCenter.dy + (offset - (_actualAxisWidth / 2)) * math.sin(radian); + _axisCenter.dy + (offset - axisHalfWidth) * math.sin(radian); _annotationPosition = Offset(x, y); } } @@ -195,7 +199,10 @@ class RenderGaugeAnnotation extends RenderOpacity { _axisCenter = axisRenderer!.getAxisCenter(); _radius = _axisRenderer!.getRadius(); _actualAxisWidth = _axisRenderer!.getActualValue( - _axisRenderer!.thickness, _axisRenderer!.thicknessUnit, false); + _axisRenderer!.thickness, + _axisRenderer!.thicknessUnit, + false, + ); } void _addListeners() { @@ -245,18 +252,20 @@ class RenderGaugeAnnotation extends RenderOpacity { if (child!.parentData is BoxParentData) { final BoxParentData? childParentData = child!.parentData as BoxParentData?; - final double dx = _annotationPosition.dx - + final double dx = + _annotationPosition.dx - (horizontalAlignment == GaugeAlignment.near ? 0 : horizontalAlignment == GaugeAlignment.center - ? child!.size.width / 2 - : child!.size.width); - final double dy = _annotationPosition.dy - + ? child!.size.width / 2 + : child!.size.width); + final double dy = + _annotationPosition.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/axis/radial_axis.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis.dart index 17f0bde0d..137eff200 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 @@ -39,64 +39,55 @@ class RadialAxis extends StatefulWidget { /// [tickOffset] and [labelOffset] must not be null. /// Additionally [centerX], [centerY] must be non-negative /// and [maximum] must be greater than [minimum]. - RadialAxis( - {Key? key, - this.startAngle = 130, - this.endAngle = 50, - this.radiusFactor = 0.95, - this.centerX = 0.5, - this.centerY = 0.5, - this.onLabelCreated, - this.onAxisTapped, - this.canRotateLabels = false, - this.showFirstLabel = true, - this.showLastLabel = false, - this.canScaleToFit = false, - this.backgroundImage, - this.ranges, - this.pointers, - this.annotations, - this.minimum = 0, - this.maximum = 100, - this.interval, - this.minorTicksPerInterval = 1, - this.showLabels = true, - this.showAxisLine = true, - this.showTicks = true, - this.tickOffset = 0, - this.labelOffset = 15, - this.isInversed = false, - this.maximumLabels = 3, - this.useRangeColorForAxis = false, - this.labelFormat, - NumberFormat? numberFormat, - this.onCreateAxisRenderer, - this.ticksPosition = ElementsPosition.inside, - this.labelsPosition = ElementsPosition.inside, - this.offsetUnit = GaugeSizeUnit.logicalPixel, - GaugeTextStyle? axisLabelStyle, - AxisLineStyle? axisLineStyle, - MajorTickStyle? majorTickStyle, - MinorTickStyle? minorTickStyle}) - : 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.'), - axisLabelStyle = axisLabelStyle ?? - const GaugeTextStyle( - fontSize: 12.0, - fontFamily: 'Segoe UI', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal), - axisLineStyle = axisLineStyle ?? - const AxisLineStyle( - thickness: 10, - ), - numberFormat = numberFormat ?? NumberFormat('#.##'), - majorTickStyle = majorTickStyle ?? const MajorTickStyle(), - minorTickStyle = minorTickStyle ?? const MinorTickStyle(), - super(key: key); + RadialAxis({ + Key? key, + this.startAngle = 130, + this.endAngle = 50, + this.radiusFactor = 0.95, + this.centerX = 0.5, + this.centerY = 0.5, + this.onLabelCreated, + this.onAxisTapped, + this.canRotateLabels = false, + this.showFirstLabel = true, + this.showLastLabel = false, + this.canScaleToFit = false, + this.backgroundImage, + this.ranges, + this.pointers, + this.annotations, + this.minimum = 0, + this.maximum = 100, + this.interval, + this.minorTicksPerInterval = 1, + this.showLabels = true, + this.showAxisLine = true, + this.showTicks = true, + this.tickOffset = 0, + this.labelOffset = 15, + this.isInversed = false, + this.maximumLabels = 3, + this.useRangeColorForAxis = false, + this.labelFormat, + NumberFormat? numberFormat, + this.onCreateAxisRenderer, + this.ticksPosition = ElementsPosition.inside, + this.labelsPosition = ElementsPosition.inside, + this.offsetUnit = GaugeSizeUnit.logicalPixel, + GaugeTextStyle? axisLabelStyle, + AxisLineStyle? axisLineStyle, + MajorTickStyle? majorTickStyle, + MinorTickStyle? minorTickStyle, + }) : 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.'), + axisLabelStyle = axisLabelStyle ?? const GaugeTextStyle(), + axisLineStyle = axisLineStyle ?? const AxisLineStyle(), + numberFormat = numberFormat ?? NumberFormat('#.##'), + majorTickStyle = majorTickStyle ?? const MajorTickStyle(), + minorTickStyle = minorTickStyle ?? const MinorTickStyle(), + super(key: key); /// Specifies the start angle of axis. /// @@ -835,6 +826,7 @@ class RadialAxis extends StatefulWidget { /// } /// /// ``` + // ignore: strict_raw_type final GaugeAxisRendererFactory? onCreateAxisRenderer; @override @@ -849,16 +841,11 @@ class _RadialAxisState extends State with TickerProviderStateMixin { _hasAnnotations = false; /// Specifies the axis line interval for animation - List _axisLineInterval = - List.filled(5, null, growable: false); - List _axisElementsInterval = - List.filled(5, null, growable: false); - List _rangesInterval = - List.filled(5, null, growable: false); - List _pointersInterval = - List.filled(5, null, growable: false); - List _annotationInterval = - List.filled(5, null, growable: false); + List _axisLineInterval = List.filled(5, null); + List _axisElementsInterval = List.filled(5, null); + List _rangesInterval = List.filled(5, null); + List _pointersInterval = List.filled(5, null); + List _annotationInterval = List.filled(5, null); AnimationController? _animationController; @@ -901,9 +888,10 @@ class _RadialAxisState extends State with TickerProviderStateMixin { } void _updateOldList() { - _oldPointerList = (widget.pointers != null) - ? List.from(widget.pointers!) - : null; + _oldPointerList = + (widget.pointers != null) + ? List.from(widget.pointers!) + : null; } bool _isEqualLists(List? a, List? b) { @@ -935,41 +923,61 @@ class _RadialAxisState extends State with TickerProviderStateMixin { _disposeAnimationControllers(); if (_enableAnimation) { _animationController = AnimationController( - vsync: this, duration: Duration(milliseconds: animationDuration)); + vsync: this, + duration: Duration(milliseconds: animationDuration), + ); _animationController!.addListener(_axisAnimationListener); if (_hasAxisLine) { _axisAnimation = Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _animationController!, - curve: Interval(_axisLineInterval[0]!, _axisLineInterval[1]!, - curve: Curves.easeIn))); + CurvedAnimation( + parent: _animationController!, + curve: Interval( + _axisLineInterval[0]!, + _axisLineInterval[1]!, + curve: Curves.easeIn, + ), + ), + ); } // Includes animation duration for axis ticks and labels if (_hasAxisElements) { _axisElementAnimation = Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _animationController!, - curve: Interval( - _axisElementsInterval[0]!, _axisElementsInterval[1]!, - curve: Curves.easeIn))); + CurvedAnimation( + parent: _animationController!, + curve: Interval( + _axisElementsInterval[0]!, + _axisElementsInterval[1]!, + curve: Curves.easeIn, + ), + ), + ); } if (_hasRanges) { _rangeAnimation = Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _animationController!, - curve: Interval(_rangesInterval[0]!, _rangesInterval[1]!, - curve: Curves.easeIn))); + CurvedAnimation( + parent: _animationController!, + curve: Interval( + _rangesInterval[0]!, + _rangesInterval[1]!, + curve: Curves.easeIn, + ), + ), + ); } if (_hasAnnotations) { _annotationAnimation = Tween(begin: 0, end: 1).animate( - CurvedAnimation( - parent: _animationController!, - curve: Interval( - _annotationInterval[0]!, _annotationInterval[1]!, - curve: Curves.easeIn))); + CurvedAnimation( + parent: _animationController!, + curve: Interval( + _annotationInterval[0]!, + _annotationInterval[1]!, + curve: Curves.easeIn, + ), + ), + ); } } @@ -980,9 +988,11 @@ class _RadialAxisState extends State with TickerProviderStateMixin { AnimationController? pointerAnimationController; if (widget.pointers![i].enableAnimation) { pointerAnimationController = AnimationController( - vsync: this, - duration: Duration( - milliseconds: widget.pointers![i].animationDuration.toInt())); + vsync: this, + duration: Duration( + milliseconds: widget.pointers![i].animationDuration.toInt(), + ), + ); _pointerAnimationControllers.add(pointerAnimationController); } } @@ -1026,20 +1036,26 @@ class _RadialAxisState extends State with TickerProviderStateMixin { _radialAxisWidgets.clear(); /// Adding the axis widget. - _radialAxisWidgets.add(RadialAxisScope( - child: RadialAxisRenderObjectWidget(axis: widget), + _radialAxisWidgets.add( + RadialAxisScope( animation1: _axisElementAnimation, isRadialGaugeAnimationEnabled: _enableAnimation, repaintNotifier: _repaintNotifier, - animation: _axisAnimation)); + animation: _axisAnimation, + child: RadialAxisRenderObjectWidget(axis: widget), + ), + ); if (widget.ranges != null) { for (int i = 0; i < widget.ranges!.length; i++) { - _radialAxisWidgets.add(RadialAxisScope( - child: widget.ranges![i], + _radialAxisWidgets.add( + RadialAxisScope( isRadialGaugeAnimationEnabled: _enableAnimation, repaintNotifier: _repaintNotifier, - animation: _rangeAnimation)); + animation: _rangeAnimation, + child: widget.ranges![i], + ), + ); } } @@ -1056,22 +1072,28 @@ class _RadialAxisState extends State with TickerProviderStateMixin { pointerAnimationController = _animationController; } - _radialAxisWidgets.add(RadialAxisScope( - child: widget.pointers![i] as Widget, + _radialAxisWidgets.add( + RadialAxisScope( animationController: pointerAnimationController, isRadialGaugeAnimationEnabled: _enableAnimation, repaintNotifier: _repaintNotifier, - pointerInterval: _pointersInterval)); + pointerInterval: _pointersInterval, + child: widget.pointers![i] as Widget, + ), + ); } } if (widget.annotations != null) { for (int i = 0; i < widget.annotations!.length; i++) { - _radialAxisWidgets.add(RadialAxisScope( - child: widget.annotations![i], + _radialAxisWidgets.add( + RadialAxisScope( isRadialGaugeAnimationEnabled: _enableAnimation, repaintNotifier: _repaintNotifier, - animation: _annotationAnimation)); + animation: _annotationAnimation, + child: widget.annotations![i], + ), + ); } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_label.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_label.dart index 2b400a84a..2cdc9ac31 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_label.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_label.dart @@ -8,7 +8,11 @@ import '../../radial_gauge/styles/radial_text_style.dart'; class CircularAxisLabel { /// Creates the axis label with default or required properties. CircularAxisLabel( - this.labelStyle, this.text, this.index, this.needsRotateLabel); + this.labelStyle, + this.text, + this.index, + this.needsRotateLabel, + ); /// Style for axis label text. late GaugeTextStyle labelStyle; diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart index 50a84f5e9..3884e3f65 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:flutter/gestures.dart' show + DeviceGestureSettings, DragStartBehavior, GestureArenaTeam, HitTestTarget, @@ -31,16 +32,20 @@ const double kDefaultRadialGaugeSize = 350.0; /// Represents the renderer of radial gauge axis. class RadialAxisParentWidget extends MultiChildRenderObjectWidget { /// Creates instance for [RadialGaugeRenderWidget]. - RadialAxisParentWidget({Key? key, required List children}) - : super(key: key, children: children); + const RadialAxisParentWidget({Key? key, required List children}) + : super(key: key, children: children); @override RenderObject createRenderObject(BuildContext context) => - RenderRadialAxisParent(); + RenderRadialAxisParent( + gestureSettings: MediaQuery.of(context).gestureSettings, + ); @override void updateRenderObject( - BuildContext context, RenderRadialAxisParent renderObject) { + BuildContext context, + RenderRadialAxisParent renderObject, + ) { super.updateRenderObject(context, renderObject); } @@ -90,7 +95,7 @@ class RadialAxisParentElement extends MultiChildRenderObjectElement { } else if (child is RenderRangePointer) { renderObject.removeRangePointer(child); } else if (child is RenderMarkerPointer) { - renderObject.addMarkerPointer(child); + renderObject.removeMarkerPointer(child); } else if (child is RenderGaugeRange) { renderObject.removeRange(child); } else if (child is RenderGaugeAnnotation) { @@ -109,19 +114,25 @@ class RenderRadialAxisParent extends RenderBox /// /// By default, the non-positioned children of the stack are aligned by their /// top left corners. - RenderRadialAxisParent() : _gestureArenaTeam = GestureArenaTeam() { - _verticalDragGestureRecognizer = VerticalDragGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..dragStartBehavior = DragStartBehavior.start; - - _horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..dragStartBehavior = DragStartBehavior.start; + RenderRadialAxisParent({required DeviceGestureSettings gestureSettings}) + : _gestureArenaTeam = GestureArenaTeam() { + _verticalDragGestureRecognizer = + VerticalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..gestureSettings = gestureSettings + ..dragStartBehavior = DragStartBehavior.start; + + _horizontalDragGestureRecognizer = + HorizontalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..gestureSettings = gestureSettings + ..dragStartBehavior = DragStartBehavior.start; _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUP; @@ -262,14 +273,15 @@ class RenderRadialAxisParent extends RenderBox void _updateElements() { // ignore: always_specify_types - _axisElements = [ - _ranges, - _annotations, - _markerPointers, - _widgetPointers, - _rangePointers, - _needlePointers - ].expand((List x) => x).toList(); + _axisElements = + [ + _ranges, + _annotations, + _markerPointers, + _widgetPointers, + _rangePointers, + _needlePointers, + ].expand((List x) => x).toList(); for (int i = 0; i < _axisElements.length; i++) { _axisElements[i].axisRenderer = axis; @@ -300,25 +312,29 @@ class RenderRadialAxisParent extends RenderBox @override void performLayout() { - final double actualHeight = constraints.hasBoundedHeight - ? constraints.maxHeight - : kDefaultRadialGaugeSize; - final double actualWidth = constraints.hasBoundedWidth - ? constraints.maxWidth - : kDefaultRadialGaugeSize; + final double actualHeight = + constraints.hasBoundedHeight + ? constraints.maxHeight + : kDefaultRadialGaugeSize; + final double actualWidth = + constraints.hasBoundedWidth + ? constraints.maxWidth + : kDefaultRadialGaugeSize; if (axis != null) { axis!.layout( - BoxConstraints(maxHeight: actualHeight, maxWidth: actualWidth), - parentUsesSize: true); + BoxConstraints(maxHeight: actualHeight, maxWidth: actualWidth), + parentUsesSize: true, + ); _updateElements(); } if (_axisElements.isNotEmpty) { for (int i = 0; i < _axisElements.length; i++) { _axisElements[i].layout( - BoxConstraints(maxHeight: actualHeight, maxWidth: actualWidth), - parentUsesSize: true); + BoxConstraints(maxHeight: actualHeight, maxWidth: actualWidth), + parentUsesSize: true, + ); } } @@ -483,17 +499,19 @@ class RenderRadialAxisParent extends RenderBox _pointerRenderObject = null; } - /// Method to update the drag value + /// Method to update the drag value. void _updateDragValue(double x, double y, dynamic pointer) { - final double actualCenterX = axis!.canScaleToFit - ? axis!.getAxisCenter().dx - : size.width * axis!.centerX; - final double actualCenterY = axis!.canScaleToFit - ? axis!.getAxisCenter().dy - : size.height * axis!.centerY; + final double actualCenterX = + axis!.canScaleToFit + ? axis!.getAxisCenter().dx + : size.width * axis!.centerX; + final double actualCenterY = + axis!.canScaleToFit + ? axis!.getAxisCenter().dy + : size.height * axis!.centerY; double angle = math.atan2(y - actualCenterY, x - actualCenterX) * (180 / math.pi) + - 360; + 360; final double endAngle = axis!.startAngle + axis!.getAxisSweepAngle(); if (angle < 360 && angle > 180) { @@ -504,34 +522,18 @@ class RenderRadialAxisParent extends RenderBox angle %= 360; } - // Restricts the dragging of pointer once the maximum or minimum - // value of axis is reached, if it is not a full circle - if (axis!.startAngle != axis!.endAngle) { - final double pointerAngle = angle > 360 ? angle % 360 : angle; - final double startAngle = - axis!.startAngle > 360 ? axis!.startAngle % 360 : axis!.startAngle; - final double endAngle = - axis!.endAngle > 360 ? axis!.endAngle % 360 : axis!.endAngle; - final bool isPointerInsideRange = startAngle < endAngle - ? pointerAngle > (startAngle == 360 ? 0 : startAngle) && - pointerAngle < (endAngle == 0 ? 360 : endAngle) - : pointerAngle > (startAngle == 360 ? 0 : startAngle) || - pointerAngle < (endAngle == 0 ? 360 : endAngle); - if (!isPointerInsideRange) { - _checkPointerIsDragged(); - } - } - if (angle >= axis!.startAngle && angle <= endAngle) { double dragValue = 0; /// The current pointer value is calculated from the angle if (!axis!.isInversed) { - dragValue = axis!.minimum + + dragValue = + axis!.minimum + (angle - axis!.startAngle) * ((axis!.maximum - axis!.minimum) / axis!.getAxisSweepAngle()); } else { - dragValue = axis!.maximum - + dragValue = + axis!.maximum - (angle - axis!.startAngle) * ((axis!.maximum - axis!.minimum) / axis!.getAxisSweepAngle()); } @@ -571,12 +573,16 @@ class RenderRadialAxisParent extends RenderBox void _enableOverlayForMarkerPointer(PointerHoverEvent event) { final Offset hoverPosition = globalToLocal(event.position); final dynamic pointer = _pointerRenderObject; - if (pointer != null && pointer is RenderMarkerPointer) { + if (pointer != null && + pointer is RenderMarkerPointer && + pointer.pointerRect != null) { pointer.isHovered = false; - if (pointer.pointerRect.contains(hoverPosition) && + if (pointer.pointerRect!.contains(hoverPosition) && _markerPointers.isNotEmpty && - !_markerPointers.any((RenderMarkerPointer element) => - element.isHovered != null && element.isHovered!)) { + !_markerPointers.any( + (RenderMarkerPointer element) => + element.isHovered != null && element.isHovered!, + )) { pointer.isHovered = true; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_scope.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_scope.dart index 2ff25469b..11e38f1d9 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_scope.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_scope.dart @@ -36,9 +36,10 @@ class RadialAxisScope extends InheritedWidget { static RadialAxisScope of(BuildContext context) { late RadialAxisScope scope; - final Widget widget = context - .getElementForInheritedWidgetOfExactType()! - .widget; + final Widget widget = + context + .getElementForInheritedWidgetOfExactType()! + .widget; if (widget is RadialAxisScope) { scope = widget; diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart index e33733758..9a18fd772 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart @@ -20,7 +20,7 @@ import '../styles/radial_tick_style.dart'; /// Radial Axis widget. class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { ///Creates a object for [RadialAxisWidget]. - const RadialAxisRenderObjectWidget({required this.axis}); + const RadialAxisRenderObjectWidget({super.key, required this.axis}); /// Radial axis widget. final RadialAxis axis; @@ -32,6 +32,9 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { final MajorTickStyle majorTickStyle = axis.majorTickStyle; final MinorTickStyle minorTickStyle = axis.minorTickStyle; final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); + final SfColorScheme colorScheme = SfTheme.colorScheme(context); + RadialAxisRenderer? renderer; if (axis.onCreateAxisRenderer != null) { @@ -40,73 +43,79 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { } return RenderRadialAxisWidget( - startAngle: axis.startAngle, - endAngle: axis.endAngle, - radiusFactor: axis.radiusFactor, - centerX: axis.centerX, - centerY: axis.centerY, - canRotateLabels: axis.canRotateLabels, - canScaleToFit: axis.canScaleToFit, - showFirstLabel: axis.showFirstLabel, - showLastLabel: axis.showLastLabel, - onLabelCreated: axis.onLabelCreated, - onAxisTapped: axis.onAxisTapped, - minimum: axis.minimum, - maximum: axis.maximum, - interval: axis.interval, - isInversed: axis.isInversed, - minorTicksPerInterval: axis.minorTicksPerInterval, - showAxisLine: axis.showAxisLine, - showLabels: axis.showLabels, - showTicks: axis.showTicks, - tickOffset: axis.tickOffset, - numberFormat: axis.numberFormat, - labelFormat: axis.labelFormat, - maximumLabels: axis.maximumLabels, - labelOffset: axis.labelOffset, - useRangeColorForAxis: axis.useRangeColorForAxis, - labelPosition: axis.labelsPosition, - tickPosition: axis.ticksPosition, - offsetUnit: axis.offsetUnit, - thickness: axisLineStyle.thickness, - thicknessUnit: axisLineStyle.thicknessUnit, - axisLineColor: axisLineStyle.color, - axisLineGradient: axisLineStyle.gradient, - axisLineCornerStyle: axisLineStyle.cornerStyle, - axisLineDashArray: axisLineStyle.dashArray, - gaugeTextStyle: axis.axisLabelStyle, - majorTickLength: majorTickStyle.length, - majorTickThickness: majorTickStyle.thickness, - majorTickLengthUnit: majorTickStyle.lengthUnit, - majorTickColor: majorTickStyle.color, - majorTickDashArray: majorTickStyle.dashArray, - minorTickLength: minorTickStyle.length, - minorTickThickness: minorTickStyle.thickness, - minorTickLengthUnit: minorTickStyle.lengthUnit, - minorTickColor: minorTickStyle.color, - minorTickDashArray: minorTickStyle.dashArray, - axisLineAnimation: radialGaugeScope.animation, - axisElementsAnimation: radialGaugeScope.animation1, - repaintNotifier: radialGaugeScope.repaintNotifier, - gaugeThemeData: gaugeTheme, - context: context, - ranges: axis.ranges, - renderer: renderer, - backgroundImage: axis.backgroundImage, - imageStream: axis.backgroundImage != null - ? axis.backgroundImage! - .resolve(createLocalImageConfiguration(context)) - : null); + startAngle: axis.startAngle, + endAngle: axis.endAngle, + radiusFactor: axis.radiusFactor, + centerX: axis.centerX, + centerY: axis.centerY, + canRotateLabels: axis.canRotateLabels, + canScaleToFit: axis.canScaleToFit, + showFirstLabel: axis.showFirstLabel, + showLastLabel: axis.showLastLabel, + onLabelCreated: axis.onLabelCreated, + onAxisTapped: axis.onAxisTapped, + minimum: axis.minimum, + maximum: axis.maximum, + interval: axis.interval, + isInversed: axis.isInversed, + minorTicksPerInterval: axis.minorTicksPerInterval, + showAxisLine: axis.showAxisLine, + showLabels: axis.showLabels, + showTicks: axis.showTicks, + tickOffset: axis.tickOffset, + numberFormat: axis.numberFormat, + labelFormat: axis.labelFormat, + maximumLabels: axis.maximumLabels, + labelOffset: axis.labelOffset, + useRangeColorForAxis: axis.useRangeColorForAxis, + labelPosition: axis.labelsPosition, + tickPosition: axis.ticksPosition, + offsetUnit: axis.offsetUnit, + thickness: axisLineStyle.thickness, + thicknessUnit: axisLineStyle.thicknessUnit, + axisLineColor: axisLineStyle.color, + axisLineGradient: axisLineStyle.gradient, + axisLineCornerStyle: axisLineStyle.cornerStyle, + axisLineDashArray: axisLineStyle.dashArray, + gaugeTextStyle: axis.axisLabelStyle, + majorTickLength: majorTickStyle.length, + majorTickThickness: majorTickStyle.thickness, + majorTickLengthUnit: majorTickStyle.lengthUnit, + majorTickColor: majorTickStyle.color, + majorTickDashArray: majorTickStyle.dashArray, + minorTickLength: minorTickStyle.length, + minorTickThickness: minorTickStyle.thickness, + minorTickLengthUnit: minorTickStyle.lengthUnit, + minorTickColor: minorTickStyle.color, + minorTickDashArray: minorTickStyle.dashArray, + axisLineAnimation: radialGaugeScope.animation, + axisElementsAnimation: radialGaugeScope.animation1, + repaintNotifier: radialGaugeScope.repaintNotifier, + gaugeThemeData: gaugeTheme, + themeData: themeData, + colorScheme: colorScheme, + ranges: axis.ranges, + renderer: renderer, + backgroundImage: axis.backgroundImage, + imageStream: axis.backgroundImage?.resolve( + createLocalImageConfiguration(context), + ), + ); } @override void updateRenderObject( - BuildContext context, RenderRadialAxisWidget renderObject) { + BuildContext context, + RenderRadialAxisWidget renderObject, + ) { final RadialAxisScope radialGaugeScope = RadialAxisScope.of(context); final AxisLineStyle axisLineStyle = axis.axisLineStyle; final MajorTickStyle majorTickStyle = axis.majorTickStyle; final MinorTickStyle minorTickStyle = axis.minorTickStyle; final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); + final SfColorScheme colorScheme = SfTheme.colorScheme(context); + RadialAxisRenderer? renderer; if (axis.onCreateAxisRenderer != null) { @@ -164,11 +173,12 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { ..axisLineAnimation = radialGaugeScope.animation ..axisElementsAnimation = radialGaugeScope.animation1 ..gaugeThemeData = gaugeTheme + ..themeData = themeData + ..colorScheme = colorScheme ..renderer = renderer - ..imageStream = axis.backgroundImage != null - ? axis.backgroundImage! - .resolve(createLocalImageConfiguration(context)) - : null + ..imageStream = axis.backgroundImage?.resolve( + createLocalImageConfiguration(context), + ) ..backgroundImage = axis.backgroundImage; super.updateRenderObject(context, renderObject); } @@ -177,116 +187,117 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { //// Represents the renderer of radial gauge axis element. class RenderRadialAxisWidget extends RenderBox { /// Creates a object for [RenderRadialAxis]. - RenderRadialAxisWidget( - {required double startAngle, - required double endAngle, - required double radiusFactor, - required double centerX, - required double centerY, - required bool canRotateLabels, - required bool canScaleToFit, - required bool showFirstLabel, - required bool showLastLabel, - ValueChanged? onLabelCreated, - ValueChanged? onAxisTapped, - required double minimum, - required double maximum, - double? interval, - required bool isInversed, - required double minorTicksPerInterval, - required bool showAxisLine, - required bool showLabels, - required bool showTicks, - required double tickOffset, - init.NumberFormat? numberFormat, - String? labelFormat, - required int maximumLabels, - required double labelOffset, - required bool useRangeColorForAxis, - required ElementsPosition labelPosition, - required ElementsPosition tickPosition, - required GaugeSizeUnit offsetUnit, - required double thickness, - required GaugeSizeUnit thicknessUnit, - Color? axisLineColor, - Gradient? axisLineGradient, - required CornerStyle axisLineCornerStyle, - List? axisLineDashArray, - required GaugeTextStyle gaugeTextStyle, - required double majorTickLength, - required double majorTickThickness, - required GaugeSizeUnit majorTickLengthUnit, - Color? majorTickColor, - List? majorTickDashArray, - required double minorTickLength, - required double minorTickThickness, - required GaugeSizeUnit minorTickLengthUnit, - Color? minorTickColor, - List? minorTickDashArray, - required SfGaugeThemeData gaugeThemeData, - required BuildContext context, - RadialAxisRenderer? renderer, - List? ranges, - Animation? axisElementsAnimation, - Animation? axisLineAnimation, - ImageStream? imageStream, - required ValueNotifier repaintNotifier, - ImageProvider? backgroundImage}) - : _startAngle = startAngle, - _endAngle = endAngle, - _radiusFactor = radiusFactor, - _centerX = centerX, - _centerY = centerY, - _canRotateLabels = canRotateLabels, - _canScaleToFit = canScaleToFit, - _showFirstLabel = showFirstLabel, - _showLastLabel = showLastLabel, - _onLabelCreated = onLabelCreated, - _onAxisTapped = onAxisTapped, - _minimum = minimum, - _maximum = maximum, - _interval = interval, - _isInversed = isInversed, - _minorTicksPerInterval = minorTicksPerInterval, - _showAxisLine = showAxisLine, - _showLabels = showLabels, - _showTicks = showTicks, - _tickOffset = tickOffset, - _numberFormat = numberFormat, - _labelFormat = labelFormat, - _maximumLabels = maximumLabels, - _labelOffset = labelOffset, - _useRangeColorForAxis = useRangeColorForAxis, - _labelPosition = labelPosition, - _tickPosition = tickPosition, - _offsetUnit = offsetUnit, - _thickness = thickness, - _thicknessUnit = thicknessUnit, - _axisLineColor = axisLineColor, - _axisLineGradient = axisLineGradient, - _axisLineCornerStyle = axisLineCornerStyle, - _axisLineDashArray = axisLineDashArray, - _gaugeTextStyle = gaugeTextStyle, - _majorTickLength = majorTickLength, - _majorTickThickness = majorTickThickness, - _majorTickLengthUnit = majorTickLengthUnit, - _majorTickColor = majorTickColor, - _majorTickDashArray = majorTickDashArray, - _minorTickLength = minorTickLength, - _minorTickThickness = minorTickThickness, - _minorTickLengthUnit = minorTickLengthUnit, - _minorTickColor = minorTickColor, - _minorTickDashArray = minorTickDashArray, - _axisLineAnimation = axisLineAnimation, - _axisElementsAnimation = axisElementsAnimation, - _gaugeThemeData = gaugeThemeData, - _imageStream = imageStream, - _ranges = ranges, - _renderer = renderer, - _repaintNotifier = repaintNotifier, - _themeData = Theme.of(context), - _isDarkTheme = Theme.of(context).brightness == Brightness.dark, - _backgroundImage = backgroundImage { + RenderRadialAxisWidget({ + required double startAngle, + required double endAngle, + required double radiusFactor, + required double centerX, + required double centerY, + required bool canRotateLabels, + required bool canScaleToFit, + required bool showFirstLabel, + required bool showLastLabel, + ValueChanged? onLabelCreated, + ValueChanged? onAxisTapped, + required double minimum, + required double maximum, + double? interval, + required bool isInversed, + required double minorTicksPerInterval, + required bool showAxisLine, + required bool showLabels, + required bool showTicks, + required double tickOffset, + init.NumberFormat? numberFormat, + String? labelFormat, + required int maximumLabels, + required double labelOffset, + required bool useRangeColorForAxis, + required ElementsPosition labelPosition, + required ElementsPosition tickPosition, + required GaugeSizeUnit offsetUnit, + required double thickness, + required GaugeSizeUnit thicknessUnit, + Color? axisLineColor, + Gradient? axisLineGradient, + required CornerStyle axisLineCornerStyle, + List? axisLineDashArray, + required GaugeTextStyle gaugeTextStyle, + required double majorTickLength, + required double majorTickThickness, + required GaugeSizeUnit majorTickLengthUnit, + Color? majorTickColor, + List? majorTickDashArray, + required double minorTickLength, + required double minorTickThickness, + required GaugeSizeUnit minorTickLengthUnit, + Color? minorTickColor, + List? minorTickDashArray, + required SfGaugeThemeData gaugeThemeData, + required ThemeData themeData, + required SfColorScheme colorScheme, + RadialAxisRenderer? renderer, + List? ranges, + Animation? axisElementsAnimation, + Animation? axisLineAnimation, + ImageStream? imageStream, + required ValueNotifier repaintNotifier, + ImageProvider? backgroundImage, + }) : _startAngle = startAngle, + _endAngle = endAngle, + _radiusFactor = radiusFactor, + _centerX = centerX, + _centerY = centerY, + _canRotateLabels = canRotateLabels, + _canScaleToFit = canScaleToFit, + _showFirstLabel = showFirstLabel, + _showLastLabel = showLastLabel, + _onLabelCreated = onLabelCreated, + _onAxisTapped = onAxisTapped, + _minimum = minimum, + _maximum = maximum, + _interval = interval, + _isInversed = isInversed, + _minorTicksPerInterval = minorTicksPerInterval, + _showAxisLine = showAxisLine, + _showLabels = showLabels, + _showTicks = showTicks, + _tickOffset = tickOffset, + _numberFormat = numberFormat, + _labelFormat = labelFormat, + _maximumLabels = maximumLabels, + _labelOffset = labelOffset, + _useRangeColorForAxis = useRangeColorForAxis, + _labelPosition = labelPosition, + _tickPosition = tickPosition, + _offsetUnit = offsetUnit, + _thickness = thickness, + _thicknessUnit = thicknessUnit, + _axisLineColor = axisLineColor, + _axisLineGradient = axisLineGradient, + _axisLineCornerStyle = axisLineCornerStyle, + _axisLineDashArray = axisLineDashArray, + _gaugeTextStyle = gaugeTextStyle, + _majorTickLength = majorTickLength, + _majorTickThickness = majorTickThickness, + _majorTickLengthUnit = majorTickLengthUnit, + _majorTickColor = majorTickColor, + _majorTickDashArray = majorTickDashArray, + _minorTickLength = minorTickLength, + _minorTickThickness = minorTickThickness, + _minorTickLengthUnit = minorTickLengthUnit, + _minorTickColor = minorTickColor, + _minorTickDashArray = minorTickDashArray, + _axisLineAnimation = axisLineAnimation, + _axisElementsAnimation = axisElementsAnimation, + _gaugeThemeData = gaugeThemeData, + _imageStream = imageStream, + _ranges = ranges, + _renderer = renderer, + _repaintNotifier = repaintNotifier, + _themeData = themeData, + _colorScheme = colorScheme, + _backgroundImage = backgroundImage { _isLabelsOutside = labelPosition == ElementsPosition.outside; _isTicksOutside = tickPosition == ElementsPosition.outside; _imageStreamListener = ImageStreamListener(_updateBackgroundImage); @@ -317,8 +328,6 @@ class RenderRadialAxisWidget extends RenderBox { late double _cornerAngle; ImageInfo? _backgroundImageInfo; late ImageStreamListener _imageStreamListener; - final ThemeData _themeData; - final bool _isDarkTheme; late double _radius; late double _actualAxisWidth; @@ -408,6 +417,31 @@ class RenderRadialAxisWidget extends RenderBox { markNeedsPaint(); } + /// Gets the themeData assigned to [RenderRadialAxisWidget]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the themeData for [RenderRadialAxisWidget]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + /// Gets the colors of SfColorScheme + SfColorScheme get colorScheme => _colorScheme; + SfColorScheme _colorScheme; + + set colorScheme(SfColorScheme value) { + if (value == _colorScheme) { + return; + } + _colorScheme = value; + markNeedsPaint(); + } + /// Gets the startAngle assigned to [RenderRadialAxisWidget]. double get startAngle => _startAngle; double _startAngle; @@ -1138,7 +1172,11 @@ class RenderRadialAxisWidget extends RenderBox { } bounds = Rect.fromLTRB( - x - diff / 2, y, x + minScale + (diff / 2), y + minScale + diff); + x - diff / 2, + y, + x + minScale + (diff / 2), + y + minScale + diff, + ); } else { diff = centerXDiff / 2; final double angleRadius = _axisSize.width / 2 + diff; @@ -1148,14 +1186,20 @@ class RenderRadialAxisWidget extends RenderBox { diff = actualDiff * 0.7; } - bounds = Rect.fromLTRB(x - diff / 2, y - diff / 2, - x + minScale + (diff / 2), y + minScale + (diff / 2)); + 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)); + bounds.left + (bounds.width / 2), + bounds.top + (bounds.height / 2), + ); } /// Get the RadialAxis radius. @@ -1201,8 +1245,10 @@ class RenderRadialAxisWidget extends RenderBox { getCenterY(); if (!canScaleToFit) { - _axisCenter = Offset(_axisSize.width / 2 - _centerXPoint, - _axisSize.height / 2 - _centerYPoint); + _axisCenter = Offset( + _axisSize.width / 2 - _centerXPoint, + _axisSize.height / 2 - _centerYPoint, + ); } else { _axisCenter = Offset(_centerXPoint, _centerYPoint); } @@ -1224,9 +1270,10 @@ class RenderRadialAxisWidget extends RenderBox { _actualMajorTickLength = _getTickLength(true); _actualMinorTickLength = _getTickLength(false); - _maximumTickLength = _actualMajorTickLength > _actualMinorTickLength - ? _actualMajorTickLength - : _actualMinorTickLength; + _maximumTickLength = + _actualMajorTickLength > _actualMinorTickLength + ? _actualMajorTickLength + : _actualMinorTickLength; _actualLabelOffset = getActualValue(labelOffset, offsetUnit, true); _actualTickOffset = getActualValue(tickOffset, offsetUnit, true); } @@ -1267,8 +1314,11 @@ class RenderRadialAxisWidget extends RenderBox { _radius = _center; Offset actualCenter = Offset(x, y); final double actualStartAngle = _getWrapAngle(startAngle, -630, 630); - final double actualEndAngle = - _getWrapAngle(startAngle + _sweepAngle.abs(), -630, 630); + final double actualEndAngle = _getWrapAngle( + startAngle + _sweepAngle.abs(), + -630, + 630, + ); final List regions = [ -630, -540, @@ -1284,52 +1334,84 @@ class RenderRadialAxisWidget extends RenderBox { 360, 450, 540, - 630 + 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()); + 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()); + 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))); + 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); + startPoint, + endPoint, + x, + y, + _radius, + region, + ); break; case 1: - actualCenter = - _getCenterLengthOne(startPoint, endPoint, x, y, _radius, region); + actualCenter = _getCenterLengthOne( + startPoint, + endPoint, + x, + y, + _radius, + region, + ); break; case 2: - actualCenter = - _getCenterForLengthTwo(startPoint, endPoint, x, y, _radius, region); + actualCenter = _getCenterForLengthTwo( + startPoint, + endPoint, + x, + y, + _radius, + region, + ); break; case 3: actualCenter = _getCenterForLengthThree( - startPoint, endPoint, x, y, _radius, region); + startPoint, + endPoint, + x, + y, + _radius, + region, + ); break; } @@ -1345,9 +1427,10 @@ class RenderRadialAxisWidget extends RenderBox { late double begin, end; if (pointer is RenderRangePointer) { animationStartValue = pointer.animationStartValue ?? 0; - animationEndValue = isInversed - ? _getSweepAngle(pointer.getSweepAngle()) - : pointer.getSweepAngle(); + animationEndValue = + isInversed + ? _getSweepAngle(pointer.getSweepAngle()) + : pointer.getSweepAngle(); pointer.animationStartValue = animationEndValue; } else { animationStartValue = _getSweepAngle(pointer.oldValue ?? minimum); @@ -1357,40 +1440,66 @@ class RenderRadialAxisWidget extends RenderBox { begin = enableAnimation ? 0 : pointer.pointerInterval![0]! as double; end = enableAnimation ? 1 : pointer.pointerInterval![1]! as double; - return Tween(begin: animationStartValue, end: animationEndValue) - .animate(CurvedAnimation( - parent: pointer.pointerAnimationController!, - curve: Interval(begin, end, - curve: getCurveAnimation(pointer.animationType)))); + return Tween( + begin: animationStartValue, + end: animationEndValue, + ).animate( + CurvedAnimation( + parent: pointer.pointerAnimationController!, + curve: Interval( + begin, + end, + curve: getCurveAnimation(pointer.animationType), + ), + ), + ); } else { return null; } } /// 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); + 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 _getCenterLengthOne( + Offset startPoint, + Offset endPoint, + double x, + double y, + double radius, + List region, + ) { Offset point1 = Offset.zero; Offset point2 = Offset.zero; final double maxRadian = 2 * math.pi * region[0] / 360; final Offset maxPoint = Offset( - x + (radius * math.cos(maxRadian)), y + (radius * math.sin(maxRadian))); + x + (radius * math.cos(maxRadian)), + y + (radius * math.sin(maxRadian)), + ); switch (region[0]) { case 270: @@ -1412,8 +1521,10 @@ class RenderRadialAxisWidget extends RenderBox { break; } - final Offset midPoint = - Offset((point1.dx + point2.dx) / 2, (point1.dy + point2.dy) / 2); + 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 = @@ -1422,16 +1533,26 @@ class RenderRadialAxisWidget extends RenderBox { } ///Calculates the center when the region length is two. - Offset _getCenterForLengthTwo(Offset startPoint, Offset endPoint, double x, - double y, double radius, List region) { + 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))); + x + (radius * math.cos(maxRadian)), + y + (radius * math.sin(maxRadian)), + ); final Offset minPoint = Offset( - x + (radius * math.cos(minRadian)), y + (radius * math.sin(minRadian))); + x + (radius * math.cos(minRadian)), + y + (radius * math.sin(minRadian)), + ); if ((region[0] == 0 && region[1] == 90) || (region[0] == 180 && region[1] == 270)) { @@ -1441,92 +1562,122 @@ class RenderRadialAxisWidget extends RenderBox { } if (region[0] == 0 || region[0] == 180) { - point2 = Offset(_getMinMaxValue(startPoint, endPoint, region[0]), - _getMinMaxValue(startPoint, endPoint, region[1])); + 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])); + 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 + + (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 + + ? 0 + : (x - midPoint.dx)); + final double yValue = + y + (midPoint.dy == 0 ? 0 : (y - midPoint.dy) >= radius - ? 0 - : (y - midPoint.dy)); + ? 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) { + 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))); + 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.zero; Offset regionEndPoint = Offset.zero; switch (region[2]) { case 0: case 360: regionStartPoint = Offset(region0Point.dx, region1Point.dy); - regionEndPoint = - Offset(region2Point.dx, math.max(startPoint.dy, endPoint.dy)); + regionEndPoint = Offset( + region2Point.dx, + math.max(startPoint.dy, endPoint.dy), + ); break; case 90: - regionStartPoint = - Offset(math.min(startPoint.dx, endPoint.dx), region0Point.dy); + 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)); + 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); + 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 + + (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 + + ? 0 + : (x - midRegionPoint.dx)); + final double yValue = + y + (midRegionPoint.dy == 0 ? 0 : (y - midRegionPoint.dy) >= radius - ? 0 - : (y - midRegionPoint.dy)); + ? 0 + : (y - midRegionPoint.dy)); return Offset(xValue, yValue); } @@ -1573,56 +1724,62 @@ class RenderRadialAxisWidget extends RenderBox { switch (axisLineCornerStyle) { case CornerStyle.startCurve: { - _startCornerRadian = isInversed - ? getDegreeToRadian(-_cornerAngle) - : getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = isInversed - ? getDegreeToRadian((-_sweepAngle) + _cornerAngle) - : getDegreeToRadian(_sweepAngle - _cornerAngle); + _startCornerRadian = + isInversed + ? getDegreeToRadian(-_cornerAngle) + : getDegreeToRadian(_cornerAngle); + _sweepCornerRadian = + isInversed + ? getDegreeToRadian((-_sweepAngle) + _cornerAngle) + : getDegreeToRadian(_sweepAngle - _cornerAngle); } break; case CornerStyle.endCurve: { _startCornerRadian = getDegreeToRadian(0); - _sweepCornerRadian = isInversed - ? getDegreeToRadian((-_sweepAngle) + _cornerAngle) - : getDegreeToRadian(_sweepAngle - _cornerAngle); + _sweepCornerRadian = + isInversed + ? getDegreeToRadian((-_sweepAngle) + _cornerAngle) + : getDegreeToRadian(_sweepAngle - _cornerAngle); } break; case CornerStyle.bothCurve: { - _startCornerRadian = isInversed - ? getDegreeToRadian(-_cornerAngle) - : getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = isInversed - ? getDegreeToRadian((-_sweepAngle) + (2 * _cornerAngle)) - : getDegreeToRadian(_sweepAngle - (2 * _cornerAngle)); + _startCornerRadian = + isInversed + ? getDegreeToRadian(-_cornerAngle) + : getDegreeToRadian(_cornerAngle); + _sweepCornerRadian = + isInversed + ? getDegreeToRadian((-_sweepAngle) + (2 * _cornerAngle)) + : getDegreeToRadian(_sweepAngle - (2 * _cornerAngle)); } break; case CornerStyle.bothFlat: - _startCornerRadian = !isInversed - ? getDegreeToRadian(0) - : getDegreeToRadian(startAngle + _sweepAngle); - final double _value = isInversed ? -1 : 1; - _sweepCornerRadian = getDegreeToRadian(_sweepAngle * _value); + _startCornerRadian = + !isInversed + ? getDegreeToRadian(0) + : getDegreeToRadian(startAngle + _sweepAngle); + _sweepCornerRadian = getDegreeToRadian( + _sweepAngle * (isInversed ? -1 : 1), + ); 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)); + final double axisOffset = _radius - (_actualAxisWidth / 2 + _axisOffset); + _axisRect = Rect.fromLTRB(-axisOffset, -axisOffset, axisOffset, axisOffset); axisPath.reset(); + final double centerX = canScaleToFit ? _axisCenter.dx : size.width / 2; + final double centerY = canScaleToFit ? _axisCenter.dy : size.height / 2; final Rect rect = Rect.fromLTRB( - _axisRect.left + size.width / 2, - _axisRect.top + size.height / 2, - _axisRect.right + size.width / 2, - _axisRect.bottom + size.height / 2, + _axisRect.left + centerX, + _axisRect.top + centerY, + _axisRect.right + centerX, + _axisRect.bottom + centerY, ); axisPath.arcTo(rect, _startRadian, _endRadian, false); @@ -1630,12 +1787,14 @@ class RenderRadialAxisWidget extends RenderBox { /// Method to calculate the angle from the tapped point. void calculateAngleFromOffset(Offset offset) { - final double actualCenterX = size.width * centerX; - final double actualCenterY = size.height * centerY; + final double actualCenterX = + canScaleToFit ? _axisCenter.dx : size.width * centerX; + final double actualCenterY = + canScaleToFit ? _axisCenter.dy : size.height * centerY; double angle = math.atan2(offset.dy - actualCenterY, offset.dx - actualCenterX) * - (180 / math.pi) + - 360; + (180 / math.pi) + + 360; final double actualEndAngle = startAngle + _sweepAngle; if (angle < 360 && angle > 180) { angle += 360; @@ -1648,13 +1807,14 @@ class RenderRadialAxisWidget extends RenderBox { if (angle >= startAngle && angle <= actualEndAngle) { final double angleFactor = (angle - startAngle) / _sweepAngle; - final double value = (renderer != null && - renderer!.factorToValue(angleFactor) != null) - ? renderer!.factorToValue(angleFactor) ?? factorToValue(angleFactor) - : factorToValue(angleFactor); + final double value = + (renderer != null && renderer!.factorToValue(angleFactor) != null) + ? renderer!.factorToValue(angleFactor) ?? + factorToValue(angleFactor) + : factorToValue(angleFactor); if (value >= minimum && value <= maximum) { - final double _tappedValue = _angleToValue(angle); - onAxisTapped!(_tappedValue); + final double tappedValue = _angleToValue(angle); + onAxisTapped!(tappedValue); } } } @@ -1662,17 +1822,20 @@ class RenderRadialAxisWidget extends RenderBox { /// Calculate the offset for axis line based on ticks and labels double getAxisOffset() { double offset = 0; - offset = _isTicksOutside - ? showTicks - ? (_maximumTickLength + _actualTickOffset) - : 0 - : 0; - offset += _isLabelsOutside - ? showLabels - ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / 2 + - _actualLabelOffset) - : 0 - : 0; + offset = + _isTicksOutside + ? showTicks + ? (_maximumTickLength + _actualTickOffset) + : 0 + : 0; + offset += + _isLabelsOutside + ? showLabels + ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / + 2 + + _actualLabelOffset) + : 0 + : 0; return offset; } @@ -1684,7 +1847,8 @@ class RenderRadialAxisWidget extends RenderBox { angle = (_sweepAngle / (maximum - minimum).abs()) * (minimum - value).abs(); } else { - angle = _sweepAngle - + angle = + _sweepAngle - ((_sweepAngle / (maximum - minimum).abs()) * (minimum - value).abs()); } @@ -1695,10 +1859,12 @@ class RenderRadialAxisWidget extends RenderBox { double _angleToValue(double angle) { double value = 0; if (!isInversed) { - value = (((angle - startAngle) / _sweepAngle) * (maximum - minimum)) + + value = + (((angle - startAngle) / _sweepAngle) * (maximum - minimum)) + minimum; } else { - value = maximum - + value = + maximum - (((angle - startAngle) / _sweepAngle) * (maximum - minimum)); } @@ -1711,12 +1877,14 @@ class RenderRadialAxisWidget extends RenderBox { double angularSpaceForTicks; if (_actualInterval != null) { _majorTicksCount = (maximum - minimum) / _actualInterval!; - angularSpaceForTicks = - getDegreeToRadian(_sweepAngle / _majorTicksCount); + angularSpaceForTicks = getDegreeToRadian( + _sweepAngle / _majorTicksCount, + ); } else { _majorTicksCount = _axisLabels!.length; - angularSpaceForTicks = - getDegreeToRadian(_sweepAngle / (_majorTicksCount - 1)); + angularSpaceForTicks = getDegreeToRadian( + _sweepAngle / (_majorTicksCount - 1), + ); } final double axisLineWidth = showAxisLine ? _actualAxisWidth : 0; @@ -1724,20 +1892,26 @@ class RenderRadialAxisWidget extends RenderBox { double tickStartOffset = 0; double tickEndOffset = 0; _majorTickOffsets = []; - angleForTicks = isInversed - ? getDegreeToRadian(startAngle + _sweepAngle - 90) - : getDegreeToRadian(startAngle - 90); - final double offset = _isLabelsOutside - ? showLabels - ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / - 2 + - _actualLabelOffset) - : 0 - : 0; + angleForTicks = + isInversed + ? getDegreeToRadian(startAngle + _sweepAngle - 90) + : getDegreeToRadian(startAngle - 90); + final double offset = + _isLabelsOutside + ? showLabels + ? (math.max( + _maximumLabelSize.height, + _maximumLabelSize.width, + ) / + 2 + + _actualLabelOffset) + : 0 + : 0; if (!_isTicksOutside) { tickStartOffset = _radius - (axisLineWidth + _actualTickOffset + offset); - tickEndOffset = _radius - + tickEndOffset = + _radius - (axisLineWidth + _actualMajorTickLength + _actualTickOffset + @@ -1751,22 +1925,31 @@ class RenderRadialAxisWidget extends RenderBox { tickStartOffset = _radius + _actualTickOffset; tickEndOffset = _radius + _actualMajorTickLength + _actualTickOffset; } else { - tickStartOffset = isGreater - ? _radius - offset - : _radius - - (_maximumTickLength - _actualMajorTickLength + offset); + tickStartOffset = + isGreater + ? _radius - offset + : _radius - + (_maximumTickLength - _actualMajorTickLength + offset); tickEndOffset = _radius - (offset + _maximumTickLength); } } _calculateOffsetForMajorTicks( - tickStartOffset, tickEndOffset, angularSpaceForTicks, angleForTicks); + tickStartOffset, + tickEndOffset, + angularSpaceForTicks, + angleForTicks, + ); } } /// Calculates the offset for major ticks - void _calculateOffsetForMajorTicks(double tickStartOffset, - double tickEndOffset, double angularSpaceForTicks, double angleForTicks) { + 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++) { @@ -1774,17 +1957,25 @@ class RenderRadialAxisWidget extends RenderBox { final num count = _actualInterval != null ? _majorTicksCount : _majorTicksCount - 1; if (i == 0 || i == count) { - tickAngle = - _getTickPositionInCorner(i, angleForTicks, tickStartOffset, true); + tickAngle = _getTickPositionInCorner( + i, + angleForTicks, + tickStartOffset, + true, + ); } else { tickAngle = angleForTicks; } - final List tickPosition = - _getTickPosition(tickStartOffset, tickEndOffset, tickAngle); + final List tickPosition = _getTickPosition( + tickStartOffset, + tickEndOffset, + tickAngle, + ); final TickOffset tickOffset = TickOffset(); tickOffset.startPoint = tickPosition[0]; tickOffset.endPoint = tickPosition[1]; - final double degree = (isInversed + final double degree = + (isInversed ? getRadianToDegree(tickAngle) + 90 - (startAngle + _sweepAngle) : (getRadianToDegree(tickAngle) + 90 - startAngle)) / _sweepAngle; @@ -1794,10 +1985,14 @@ class RenderRadialAxisWidget extends RenderBox { : factorToValue(degree); final Offset centerPoint = !canScaleToFit ? Offset(_centerXPoint, _centerYPoint) : Offset.zero; - 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); + 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 (isInversed) { angleForTicks -= angularSpaceForTicks; @@ -1809,19 +2004,27 @@ class RenderRadialAxisWidget extends RenderBox { /// Calculates the angle to adjust the start and end tick double _getTickPositionInCorner( - num num, double angleForTicks, double startOffset, bool isMajor) { + num num, + double angleForTicks, + double startOffset, + bool isMajor, + ) { final double thickness = isMajor ? majorTickThickness : minorTickThickness; - final double angle = - cornerRadiusAngle(startOffset + _actualAxisWidth / 2, thickness / 2); + final double angle = cornerRadiusAngle( + startOffset + _actualAxisWidth / 2, + thickness / 2, + ); if (num == 0) { - final double ticksAngle = !isInversed - ? getRadianToDegree(angleForTicks) + angle - : getRadianToDegree(angleForTicks) - angle; + final double ticksAngle = + !isInversed + ? getRadianToDegree(angleForTicks) + angle + : getRadianToDegree(angleForTicks) - angle; return getDegreeToRadian(ticksAngle); } else { - final double ticksAngle = !isInversed - ? getRadianToDegree(angleForTicks) - angle - : getRadianToDegree(angleForTicks) + angle; + final double ticksAngle = + !isInversed + ? getRadianToDegree(angleForTicks) - angle + : getRadianToDegree(angleForTicks) + angle; return getDegreeToRadian(ticksAngle); } } @@ -1832,17 +2035,22 @@ class RenderRadialAxisWidget extends RenderBox { final double axisLineWidth = showAxisLine ? _actualAxisWidth : 0; double tickStartOffset = 0; double tickEndOffset = 0; - final double offset = _isLabelsOutside - ? showLabels - ? (_actualLabelOffset + - math.max(_maximumLabelSize.height, _maximumLabelSize.width) / - 2) - : 0 - : 0; + final double offset = + _isLabelsOutside + ? showLabels + ? (_actualLabelOffset + + math.max( + _maximumLabelSize.height, + _maximumLabelSize.width, + ) / + 2) + : 0 + : 0; if (!_isTicksOutside) { tickStartOffset = _radius - (axisLineWidth + _actualTickOffset + offset); - tickEndOffset = _radius - + tickEndOffset = + _radius - (axisLineWidth + _actualMinorTickLength + _actualTickOffset + @@ -1853,10 +2061,11 @@ class RenderRadialAxisWidget extends RenderBox { tickStartOffset = _radius + _actualTickOffset; tickEndOffset = _radius + _actualMinorTickLength + _actualTickOffset; } else { - tickStartOffset = isGreater - ? _radius - offset - : _radius - - (_maximumTickLength - _actualMinorTickLength + offset); + tickStartOffset = + isGreater + ? _radius - offset + : _radius - + (_maximumTickLength - _actualMinorTickLength + offset); tickEndOffset = _radius - (_maximumTickLength + offset); } } @@ -1871,14 +2080,17 @@ class RenderRadialAxisWidget extends RenderBox { /// the smaller method but it leads to passing more number of parameter and /// which degrades the performance void _calculateOffsetForMinorTicks( - double tickStartOffset, double tickEndOffset) { + double tickStartOffset, + double tickEndOffset, + ) { _minorTickOffsets = []; double angularSpaceForTicks; double totalMinorTicks; if (_actualInterval != null) { final double majorTicksInterval = (maximum - minimum) / _actualInterval!; - angularSpaceForTicks = - getDegreeToRadian(_sweepAngle / majorTicksInterval); + angularSpaceForTicks = getDegreeToRadian( + _sweepAngle / majorTicksInterval, + ); final double maximumLabelValue = _axisLabels![_axisLabels!.length - 2].value.toDouble(); int remainingTicks; @@ -1891,19 +2103,22 @@ class RenderRadialAxisWidget extends RenderBox { remainingTicks = difference ~/ minorTickInterval; } - final int labelLength = difference == _actualInterval - ? _axisLabels!.length - 1 - : _axisLabels!.length - 2; + final int labelLength = + difference == _actualInterval + ? _axisLabels!.length - 1 + : _axisLabels!.length - 2; totalMinorTicks = (labelLength * minorTicksPerInterval) + remainingTicks; } else { - angularSpaceForTicks = - getDegreeToRadian(_sweepAngle / (_majorTicksCount - 1)); + angularSpaceForTicks = getDegreeToRadian( + _sweepAngle / (_majorTicksCount - 1), + ); totalMinorTicks = (_axisLabels!.length - 1) * minorTicksPerInterval; } - double angleForTicks = isInversed - ? getDegreeToRadian(startAngle + _sweepAngle - 90) - : getDegreeToRadian(startAngle - 90); + double angleForTicks = + isInversed + ? getDegreeToRadian(startAngle + _sweepAngle - 90) + : getDegreeToRadian(startAngle - 90); const num minorTickIndex = 1; // Since the minor tick rendering // needs to be start in the index one @@ -1917,24 +2132,33 @@ class RenderRadialAxisWidget extends RenderBox { angleForTicks += minorTickAngle; } - final double factor = (isInversed + final double factor = + (isInversed ? getRadianToDegree(angleForTicks) + 90 - (startAngle + _sweepAngle) : (getRadianToDegree(angleForTicks) + 90 - startAngle)) / _sweepAngle; - final double tickFactor = (renderer != null) - ? renderer!.factorToValue(factor) ?? factorToValue(factor) - : factorToValue(factor); + final double tickFactor = + (renderer != null) + ? renderer!.factorToValue(factor) ?? factorToValue(factor) + : factorToValue(factor); final double tickValue = double.parse(tickFactor.toStringAsFixed(5)); if (tickValue <= maximum && tickValue >= minimum) { if (tickValue == maximum) { angleForTicks = _getTickPositionInCorner( - i, angleForTicks, tickStartOffset, false); + i, + angleForTicks, + tickStartOffset, + false, + ); } - final List tickPosition = - _getTickPosition(tickStartOffset, tickEndOffset, angleForTicks); + final List tickPosition = _getTickPosition( + tickStartOffset, + tickEndOffset, + angleForTicks, + ); final TickOffset tickOffset = TickOffset(); tickOffset.startPoint = tickPosition[0]; tickOffset.endPoint = tickPosition[1]; @@ -1943,10 +2167,13 @@ class RenderRadialAxisWidget extends RenderBox { final Offset centerPoint = !canScaleToFit ? Offset(_centerXPoint, _centerYPoint) : Offset.zero; 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); + 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 % minorTicksPerInterval == 0) { if (isInversed) { @@ -1971,9 +2198,10 @@ class RenderRadialAxisWidget extends RenderBox { } final double labelSpaceInAngle = _sweepAngle / labelsInterval; final double labelSpaceInRadian = getDegreeToRadian(labelSpaceInAngle); - final double tickLength = _actualMajorTickLength > _actualMinorTickLength - ? _actualMajorTickLength - : _actualMinorTickLength; + final double tickLength = + _actualMajorTickLength > _actualMinorTickLength + ? _actualMajorTickLength + : _actualMinorTickLength; final double tickPadding = showTicks ? tickLength + _actualTickOffset : 0; double labelRadian = 0; double labelAngle = 0; @@ -1990,22 +2218,33 @@ class RenderRadialAxisWidget extends RenderBox { math.max(_maximumLabelSize.height, _maximumLabelSize.width) / 2; if (_isLabelsOutside) { final double featureOffset = labelSize; - labelPosition = _useAxisElementsInsideRadius - ? _radius - featureOffset - : _radius + tickPadding + _actualLabelOffset; + labelPosition = + _useAxisElementsInsideRadius + ? _radius - featureOffset + : _radius + tickPadding + _actualLabelOffset; } else { labelPosition = _radius - (_actualAxisWidth + tickPadding + _actualLabelOffset); } - _calculateLabelPosition(labelPosition, labelRadian, labelAngle, - labelSpaceInRadian, labelSpaceInAngle); + _calculateLabelPosition( + labelPosition, + labelRadian, + labelAngle, + labelSpaceInRadian, + labelSpaceInAngle, + ); } } // Method to calculate label position - void _calculateLabelPosition(double labelPosition, double labelRadian, - double labelAngle, double labelSpaceInRadian, double labelSpaceInAngle) { + 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; @@ -2016,24 +2255,26 @@ class RenderRadialAxisWidget extends RenderBox { label.angle = labelAngle; labelRadian = getDegreeToRadian(labelAngle); } else { - final double coordinateValue = (isInversed + final double coordinateValue = + (isInversed ? labelAngle + 90 - (startAngle + _sweepAngle) : (labelAngle + 90 - startAngle)) / _sweepAngle; - label.value = (renderer != null && - renderer!.factorToValue(coordinateValue) != null) - ? renderer!.factorToValue(coordinateValue) ?? - factorToValue(coordinateValue) - : factorToValue(coordinateValue); + label.value = + (renderer != null && + renderer!.factorToValue(coordinateValue) != null) + ? renderer!.factorToValue(coordinateValue) ?? + factorToValue(coordinateValue) + : factorToValue(coordinateValue); } if (!canScaleToFit) { final double x = ((size.width / 2) - (labelPosition * math.sin(labelRadian))) - - _centerXPoint; + _centerXPoint; final double y = ((size.height / 2) + (labelPosition * math.cos(labelRadian))) - - _centerYPoint; + _centerYPoint; label.position = Offset(x, y); } else { final double x = @@ -2059,14 +2300,16 @@ class RenderRadialAxisWidget extends RenderBox { 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 + 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 - : label.labelSize.width - : _maximumLabelSize.width; - final double maxHeight = _maximumLabelSize.height < label.labelSize.height - ? label.labelSize.height - : _maximumLabelSize.height; + : _maximumLabelSize.height; _maximumLabelSize = Size(maxWidth, maxHeight); } @@ -2074,7 +2317,10 @@ class RenderRadialAxisWidget extends RenderBox { /// Gets the start and end offset of tick List _getTickPosition( - double tickStartOffset, double tickEndOffset, double angleForTicks) { + double tickStartOffset, + double tickEndOffset, + double angleForTicks, + ) { final Offset centerPoint = !canScaleToFit ? Offset(size.width / 2, size.height / 2) : _axisCenter; final double tickStartX = @@ -2149,11 +2395,15 @@ class RenderRadialAxisWidget extends RenderBox { 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); + 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 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]; @@ -2208,12 +2458,13 @@ class RenderRadialAxisWidget extends RenderBox { final GaugeTextStyle labelStyle = argsLabelStyle ?? gaugeTextStyle; final CircularAxisLabel label = CircularAxisLabel( - labelStyle, - labelText, - i, - labelCreatedArgs != null && - labelCreatedArgs.canRotate != null && - labelCreatedArgs.canRotate!); + labelStyle, + labelText, + i, + labelCreatedArgs != null && + labelCreatedArgs.canRotate != null && + labelCreatedArgs.canRotate!, + ); label.value = value; return label; } @@ -2251,9 +2502,10 @@ class RenderRadialAxisWidget extends RenderBox { /// Converts the factor value to axis value. double factorToValue(double factor) { - final double angle = isInversed - ? (factor * _sweepAngle) + startAngle + _sweepAngle - : (factor * _sweepAngle) + startAngle; + final double angle = + isInversed + ? (factor * _sweepAngle) + startAngle + _sweepAngle + : (factor * _sweepAngle) + startAngle; return _angleToValue(angle); } @@ -2265,18 +2517,33 @@ class RenderRadialAxisWidget extends RenderBox { SweepGradient? gradient; if (axisLineGradient != null && axisLineGradient!.colors.isNotEmpty) { gradient = SweepGradient( - stops: calculateGradientStops( - _getGradientOffset(), isInversed, _sweepAngle), - colors: isInversed - ? axisLineGradient!.colors.reversed.toList() - : axisLineGradient!.colors); + stops: calculateGradientStops( + _getGradientOffset(), + isInversed, + _sweepAngle, + ), + colors: + isInversed + ? axisLineGradient!.colors.reversed.toList() + : axisLineGradient!.colors, + ); } if (axisLineCornerStyle == CornerStyle.bothFlat || isDashedAxisLine) { _drawAxisPath( - canvas, _startRadian, _endRadian, gradient, isDashedAxisLine); + canvas, + _startRadian, + _endRadian, + gradient, + isDashedAxisLine, + ); } else { - _drawAxisPath(canvas, _startCornerRadian, _sweepCornerRadian, gradient, - isDashedAxisLine); + _drawAxisPath( + canvas, + _startCornerRadian, + _sweepCornerRadian, + gradient, + isDashedAxisLine, + ); } } @@ -2288,8 +2555,10 @@ class RenderRadialAxisWidget extends RenderBox { } else { // Calculates the gradient stop values based on the provided color final double difference = 1 / axisLineGradient!.colors.length; - final List offsets = - List.filled(axisLineGradient!.colors.length, null); + final List offsets = List.filled( + axisLineGradient!.colors.length, + null, + ); for (int i = 0; i < axisLineGradient!.colors.length; i++) { offsets[i] = i * difference; } @@ -2299,17 +2568,24 @@ class RenderRadialAxisWidget extends RenderBox { } /// Method to draw axis line. - void _drawAxisPath(Canvas canvas, double startRadian, double endRadian, - SweepGradient? gradient, bool isDashedAxisLine) { + void _drawAxisPath( + Canvas canvas, + double startRadian, + double endRadian, + SweepGradient? gradient, + bool isDashedAxisLine, + ) { if (_axisLineAnimation != null) { endRadian = endRadian * _axisLineAnimation!.value; } canvas.save(); canvas.translate(_axisCenter.dx, _axisCenter.dy); - canvas.rotate(isInversed - ? getDegreeToRadian(startAngle + _sweepAngle) - : getDegreeToRadian(startAngle)); + canvas.rotate( + isInversed + ? getDegreeToRadian(startAngle + _sweepAngle) + : getDegreeToRadian(startAngle), + ); Path path = Path(); //whether the style of paint is fill. @@ -2328,16 +2604,23 @@ class RenderRadialAxisWidget extends RenderBox { _drawStartCurve(path, endRadian, innerRadius, outerRadius); } - path.addArc(Rect.fromCircle(center: Offset.zero, radius: outerRadius), - _startCornerRadian, endRadian); + path.addArc( + Rect.fromCircle(center: Offset.zero, radius: outerRadius), + _startCornerRadian, + endRadian, + ); // Adds the rounded corner at end of axis line. if (axisLineCornerStyle == CornerStyle.endCurve || axisLineCornerStyle == CornerStyle.bothCurve) { _drawEndCurve(path, endRadian, innerRadius, outerRadius); } - path.arcTo(Rect.fromCircle(center: Offset.zero, radius: innerRadius), - endRadian + _startCornerRadian, -endRadian, false); + path.arcTo( + Rect.fromCircle(center: Offset.zero, radius: innerRadius), + endRadian + _startCornerRadian, + -endRadian, + false, + ); } } else { path = _getPath(endRadian, isFill); @@ -2347,17 +2630,25 @@ class RenderRadialAxisWidget extends RenderBox { } // Method to render the path. - void _renderPath(bool isDashedAxisLine, Path path, Canvas canvas, - SweepGradient? gradient, bool isFill) { + void _renderPath( + bool isDashedAxisLine, + Path path, + Canvas canvas, + SweepGradient? gradient, + bool isFill, + ) { final Paint paint = _getPaint(gradient, isFill); if (!isDashedAxisLine) { canvas.drawPath(path, paint); } else { if (axisLineDashArray != null) { canvas.drawPath( - dashPath(path, - dashArray: CircularIntervalList(axisLineDashArray!)), - paint); + dashPath( + path, + dashArray: CircularIntervalList(axisLineDashArray!), + ), + paint, + ); } } @@ -2377,14 +2668,14 @@ class RenderRadialAxisWidget extends RenderBox { } Paint _getPaint(SweepGradient? gradient, bool isFill) { - final Paint paint = Paint() - ..color = axisLineColor ?? - _gaugeThemeData.axisLineColor ?? - (_isDarkTheme - ? _themeData.colorScheme.onSurface.withOpacity(0.24) - : _themeData.colorScheme.onSurface.withOpacity(0.12)) - ..style = !isFill ? PaintingStyle.stroke : PaintingStyle.fill - ..strokeWidth = _actualAxisWidth; + final Paint paint = + Paint() + ..color = + axisLineColor ?? + _gaugeThemeData.axisLineColor ?? + colorScheme.onSurface[35]! + ..style = !isFill ? PaintingStyle.stroke : PaintingStyle.fill + ..strokeWidth = _actualAxisWidth; if (gradient != null) { paint.shader = gradient.createShader(_axisRect); } @@ -2394,45 +2685,65 @@ class RenderRadialAxisWidget extends RenderBox { /// Draws the start corner style void _drawStartCurve( - Path path, double endRadian, double innerRadius, double outerRadius) { + Path path, + double endRadian, + double innerRadius, + double outerRadius, + ) { final Offset midPoint = getDegreeToPoint( - isInversed ? -_cornerAngle : _cornerAngle, - (innerRadius + outerRadius) / 2, - Offset.zero); + isInversed ? -_cornerAngle : _cornerAngle, + (innerRadius + outerRadius) / 2, + Offset.zero, + ); final double midStartAngle = getDegreeToRadian(180); double midEndAngle = midStartAngle + getDegreeToRadian(180); midEndAngle = isInversed ? -midEndAngle : midEndAngle; path.addArc( - Rect.fromCircle( - center: midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle, - midEndAngle); + Rect.fromCircle( + center: midPoint, + radius: (innerRadius - outerRadius).abs() / 2, + ), + midStartAngle, + midEndAngle, + ); } ///Draws the end corner curve void _drawEndCurve( - Path path, double sweepRadian, double innerRadius, double outerRadius) { + Path path, + double sweepRadian, + double innerRadius, + double outerRadius, + ) { final double curveCornerAngle = axisLineCornerStyle == CornerStyle.bothCurve ? _cornerAngle : 0; - final double angle = isInversed - ? getRadianToDegree(sweepRadian) - curveCornerAngle - : getRadianToDegree(sweepRadian) + curveCornerAngle; - final Offset midPoint = - getDegreeToPoint(angle, (innerRadius + outerRadius) / 2, Offset.zero); + final double angle = + isInversed + ? getRadianToDegree(sweepRadian) - curveCornerAngle + : getRadianToDegree(sweepRadian) + curveCornerAngle; + final Offset midPoint = getDegreeToPoint( + angle, + (innerRadius + outerRadius) / 2, + Offset.zero, + ); final double midStartAngle = sweepRadian / 2; - final double midEndAngle = isInversed - ? midStartAngle - getDegreeToRadian(180) - : midStartAngle + getDegreeToRadian(180); + final double midEndAngle = + isInversed + ? midStartAngle - getDegreeToRadian(180) + : midStartAngle + getDegreeToRadian(180); path.arcTo( - Rect.fromCircle( - center: midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle, - midEndAngle, - false); + Rect.fromCircle( + center: midPoint, + radius: (innerRadius - outerRadius).abs() / 2, + ), + midStartAngle, + midEndAngle, + false, + ); } /// Checks whether the axis line is dashed line @@ -2451,7 +2762,8 @@ class RenderRadialAxisWidget extends RenderBox { for (int i = 0; i < ranges!.length; i++) { if (ranges![i].startValue <= value.roundToDouble() && ranges![i].endValue >= value.roundToDouble()) { - color = ranges![i].color ?? + color = + ranges![i].color ?? gaugeThemeData.rangeColor ?? const Color(0xFFF67280); break; @@ -2462,59 +2774,69 @@ class RenderRadialAxisWidget extends RenderBox { } /// Method to draw the major ticks - void _drawMajorTicks(Canvas canvas) { + void _drawMajorTicks(Canvas canvas, bool isDarkTheme) { double length = _majorTickOffsets.length.toDouble(); - final Color colorSchemeMajorTickColor = _isDarkTheme - ? _themeData.colorScheme.onSurface.withOpacity(0.27) - : _themeData.colorScheme.onSurface.withOpacity(0.18); + final Color colorSchemeMajorTickColor = colorScheme.onSurface[46]!; if (_axisElementsAnimation != null) { length = _majorTickOffsets.length * _axisElementsAnimation!.value; } if (_actualMajorTickLength > 0 && majorTickThickness > 0) { - final Paint tickPaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = majorTickThickness; + final Paint tickPaint = + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = majorTickThickness; for (int i = 0; i < length; i++) { final TickOffset tickOffset = _majorTickOffsets[i]; if (!(i == 0 && _sweepAngle == 360)) { - tickPaint.color = useRangeColorForAxis - ? _getRangeColor(tickOffset.value, _gaugeThemeData) ?? - majorTickColor ?? - _gaugeThemeData.majorTickColor ?? - colorSchemeMajorTickColor - : majorTickColor ?? - _gaugeThemeData.majorTickColor ?? - colorSchemeMajorTickColor; + tickPaint.color = + useRangeColorForAxis + ? _getRangeColor(tickOffset.value, _gaugeThemeData) ?? + majorTickColor ?? + _gaugeThemeData.majorTickColor ?? + colorSchemeMajorTickColor + : majorTickColor ?? + _gaugeThemeData.majorTickColor ?? + colorSchemeMajorTickColor; if (majorTickDashArray != null && majorTickDashArray!.isNotEmpty) { - final Path path = Path() - ..moveTo(tickOffset.startPoint.dx, tickOffset.startPoint.dy) - ..lineTo(tickOffset.endPoint.dx, tickOffset.endPoint.dy); + final Path path = + Path() + ..moveTo(tickOffset.startPoint.dx, tickOffset.startPoint.dy) + ..lineTo(tickOffset.endPoint.dx, tickOffset.endPoint.dy); canvas.drawPath( - dashPath(path, - dashArray: - CircularIntervalList(majorTickDashArray!)), - tickPaint); + dashPath( + path, + dashArray: CircularIntervalList(majorTickDashArray!), + ), + tickPaint, + ); } else { if ((i == _majorTickOffsets.length - 1) && _sweepAngle == 360) { // Reposition the last tick when its sweep angle is 360 - final double x1 = (_majorTickOffsets[0].startPoint.dx + + final double x1 = + (_majorTickOffsets[0].startPoint.dx + _majorTickOffsets[i].startPoint.dx) / 2; - final double y1 = (_majorTickOffsets[0].startPoint.dy + + final double y1 = + (_majorTickOffsets[0].startPoint.dy + _majorTickOffsets[i].startPoint.dy) / 2; - final double x2 = (_majorTickOffsets[0].endPoint.dx + + final double x2 = + (_majorTickOffsets[0].endPoint.dx + _majorTickOffsets[i].endPoint.dx) / 2; - final double y2 = (_majorTickOffsets[0].endPoint.dy + + final double y2 = + (_majorTickOffsets[0].endPoint.dy + _majorTickOffsets[i].endPoint.dy) / 2; canvas.drawLine(Offset(x1, y1), Offset(x2, y2), tickPaint); } else { canvas.drawLine( - tickOffset.startPoint, tickOffset.endPoint, tickPaint); + tickOffset.startPoint, + tickOffset.endPoint, + tickPaint, + ); } } } @@ -2523,78 +2845,97 @@ class RenderRadialAxisWidget extends RenderBox { } /// Method to draw the minor ticks. - void _drawMinorTicks(Canvas canvas) { + void _drawMinorTicks(Canvas canvas, bool isDarkTheme) { double length = _minorTickOffsets.length.toDouble(); - final Color colorSchemeMinorTickColor = _isDarkTheme - ? _themeData.colorScheme.onSurface.withOpacity(0.33) - : _themeData.colorScheme.onSurface.withOpacity(0.28); + final Color colorSchemeMinorTickColor = colorScheme.onSurface[71]!; if (_axisElementsAnimation != null) { length = _minorTickOffsets.length * _axisElementsAnimation!.value; } if (_actualMinorTickLength > 0 && minorTickThickness > 0) { - final Paint tickPaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = minorTickThickness; + final Paint tickPaint = + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = minorTickThickness; for (int i = 0; i < length; i++) { final TickOffset tickOffset = _minorTickOffsets[i]; - tickPaint.color = useRangeColorForAxis - ? _getRangeColor(tickOffset.value, _gaugeThemeData) ?? - minorTickColor ?? - _gaugeThemeData.minorTickColor ?? - colorSchemeMinorTickColor - : minorTickColor ?? - _gaugeThemeData.minorTickColor ?? - colorSchemeMinorTickColor; + tickPaint.color = + useRangeColorForAxis + ? _getRangeColor(tickOffset.value, _gaugeThemeData) ?? + minorTickColor ?? + _gaugeThemeData.minorTickColor ?? + colorSchemeMinorTickColor + : minorTickColor ?? + _gaugeThemeData.minorTickColor ?? + colorSchemeMinorTickColor; if (minorTickDashArray != null && minorTickDashArray!.isNotEmpty) { - final Path path = Path() - ..moveTo(tickOffset.startPoint.dx, tickOffset.startPoint.dy) - ..lineTo(tickOffset.endPoint.dx, tickOffset.endPoint.dy); + final Path path = + Path() + ..moveTo(tickOffset.startPoint.dx, tickOffset.startPoint.dy) + ..lineTo(tickOffset.endPoint.dx, tickOffset.endPoint.dy); canvas.drawPath( - dashPath(path, - dashArray: CircularIntervalList(minorTickDashArray!)), - tickPaint); + dashPath( + path, + dashArray: CircularIntervalList(minorTickDashArray!), + ), + tickPaint, + ); } else { canvas.drawLine( - tickOffset.startPoint, tickOffset.endPoint, tickPaint); + tickOffset.startPoint, + tickOffset.endPoint, + tickPaint, + ); } } } } /// Method to draw the axis labels. - void _drawAxisLabels(Canvas canvas) { + void _drawAxisLabels(Canvas canvas, bool isDarkTheme) { double length = _axisLabels!.length.toDouble(); if (_axisElementsAnimation != null) { length = _axisLabels!.length * _axisElementsAnimation!.value; } for (int i = 0; i < length; i++) { if (!((i == 0 && !showFirstLabel) || - (i == _axisLabels!.length - 1 && - !showLastLabel && - _isMaxiumValueIncluded))) { + (i == _axisLabels!.length - 1 && !showLastLabel))) { final CircularAxisLabel label = _axisLabels![i]; - final Color labelColor = label.labelStyle.color ?? + final Color labelColor = + label.labelStyle.color ?? + _gaugeThemeData.axisLabelTextStyle?.color ?? _gaugeThemeData.axisLabelColor ?? - (_isDarkTheme - ? _themeData.colorScheme.onSurface - : _themeData.colorScheme.onSurface.withOpacity(0.72)); + colorScheme.onSurface[184]!; + final TextStyle axisLabelTextStyle = _themeData.textTheme.bodySmall! + .copyWith( + color: + ranges != null && ranges!.isNotEmpty && useRangeColorForAxis + ? _getRangeColor(label.value, _gaugeThemeData) ?? + labelColor + : labelColor, + fontSize: + label.labelStyle.fontSize ?? + _gaugeThemeData.axisLabelTextStyle?.fontSize, + fontFamily: + label.labelStyle.fontFamily ?? + _gaugeThemeData.axisLabelTextStyle?.fontFamily, + fontStyle: + label.labelStyle.fontStyle ?? + _gaugeThemeData.axisLabelTextStyle?.fontStyle, + fontWeight: + label.labelStyle.fontWeight ?? + _gaugeThemeData.axisLabelTextStyle?.fontWeight, + ); + final TextSpan span = TextSpan( - text: label.text, - style: TextStyle( - color: ranges != null && - ranges!.isNotEmpty && - useRangeColorForAxis - ? _getRangeColor(label.value, _gaugeThemeData) ?? labelColor - : labelColor, - fontSize: label.labelStyle.fontSize, - fontFamily: label.labelStyle.fontFamily, - fontStyle: label.labelStyle.fontStyle, - fontWeight: label.labelStyle.fontWeight)); + text: label.text, + style: axisLabelTextStyle, + ); final TextPainter textPainter = TextPainter( - text: span, - textDirection: TextDirection.ltr, - textAlign: TextAlign.center); + text: span, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); textPainter.layout(); _renderText(canvas, textPainter, label); @@ -2604,40 +2945,54 @@ class RenderRadialAxisWidget extends RenderBox { // Methods to render the range label. void _renderText( - Canvas canvas, TextPainter textPainter, CircularAxisLabel label) { + Canvas canvas, + TextPainter textPainter, + CircularAxisLabel label, + ) { if (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.scale(-1); - textPainter.paint(canvas, - Offset(-label.labelSize.width / 2, -label.labelSize.height / 2)); + textPainter.paint( + canvas, + Offset(-label.labelSize.width / 2, -label.labelSize.height / 2), + ); canvas.restore(); } else { textPainter.paint( - canvas, - Offset(label.position.dx - label.labelSize.width / 2, - label.position.dy - label.labelSize.height / 2)); + canvas, + Offset( + label.position.dx - label.labelSize.width / 2, + label.position.dy - label.labelSize.height / 2, + ), + ); } } @override void paint(PaintingContext context, Offset offset) { final Canvas canvas = context.canvas; + final bool isDarkTheme = _themeData.brightness == Brightness.dark; _calculateAxisElementsPosition(); if (backgroundImage != null && _backgroundImageInfo != null) { Rect rect; if (!canScaleToFit) { final double radius = math.min(size.width, size.height) / 2; rect = Rect.fromLTRB( - size.width / 2 - radius - _centerXPoint, - size.height / 2 - radius - _centerYPoint, - size.width / 2 + radius - _centerXPoint, - size.height / 2 + radius - _centerYPoint); + size.width / 2 - radius - _centerXPoint, + size.height / 2 - radius - _centerYPoint, + size.width / 2 + radius - _centerXPoint, + size.height / 2 + radius - _centerYPoint, + ); } else { - rect = Rect.fromLTRB(_axisCenter.dx - _radius, _axisCenter.dy - _radius, - _axisCenter.dx + _radius, _axisCenter.dx + _radius); + rect = Rect.fromLTRB( + _axisCenter.dx - _radius, + _axisCenter.dy - _radius, + _axisCenter.dx + _radius, + _axisCenter.dx + _radius, + ); } // Draws the background image of axis @@ -2655,12 +3010,12 @@ class RenderRadialAxisWidget extends RenderBox { } if (showTicks) { - _drawMajorTicks(canvas); - _drawMinorTicks(canvas); + _drawMajorTicks(canvas, isDarkTheme); + _drawMinorTicks(canvas, isDarkTheme); } if (showLabels) { - _drawAxisLabels(canvas); + _drawAxisLabels(canvas, isDarkTheme); } } } 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 69f7b7cba..d1420e893 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 @@ -38,15 +38,15 @@ class SfRadialGauge extends StatefulWidget { /// /// To enable the loading animation set [enableLoadingAnimation] is true. // ignore: prefer_const_constructors_in_immutables - SfRadialGauge( - {Key? key, - List? axes, - this.enableLoadingAnimation = false, - this.animationDuration = 2000, - this.backgroundColor = Colors.transparent, - this.title}) - : axes = axes ?? [RadialAxis()], - super(key: key); + SfRadialGauge({ + Key? key, + List? axes, + this.enableLoadingAnimation = false, + this.animationDuration = 2000, + this.backgroundColor = Colors.transparent, + this.title, + }) : axes = axes ?? [RadialAxis()], + super(key: key); /// Add a list of radial axis to the gauge and customize each axis by /// adding it to the [axes] collection. @@ -155,35 +155,54 @@ class SfRadialGaugeState extends State @override void didChangeDependencies() { - _gaugeTheme = SfGaugeTheme.of(context)!; + _gaugeTheme = _updateThemeData(context); super.didChangeDependencies(); } + SfGaugeThemeData _updateThemeData(BuildContext context) { + SfGaugeThemeData gaugeThemeData = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); + final bool isMaterial3 = themeData.useMaterial3; + gaugeThemeData = gaugeThemeData.copyWith( + titleTextStyle: Theme.of(context).textTheme.bodySmall! + .copyWith( + color: gaugeThemeData.titleColor ?? themeData.colorScheme.onSurface, + fontSize: isMaterial3 ? 16 : 15, + ) + .merge(gaugeThemeData.titleTextStyle) + .merge(widget.title?.textStyle), + ); + return gaugeThemeData; + } + /// Methods to add the title of circular gauge Widget _addGaugeTitle() { if (widget.title != null && widget.title!.text.isNotEmpty) { final Widget titleWidget = Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), - decoration: BoxDecoration( - color: widget.title!.backgroundColor ?? - _gaugeTheme.titleBackgroundColor, - border: Border.all( - color: - widget.title!.borderColor ?? _gaugeTheme.titleBorderColor, - width: widget.title!.borderWidth)), - child: Text( - widget.title!.text, - style: widget.title!.textStyle, - textAlign: TextAlign.center, - overflow: TextOverflow.clip, + padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), + decoration: BoxDecoration( + color: + widget.title!.backgroundColor ?? _gaugeTheme.titleBackgroundColor, + border: Border.all( + color: widget.title!.borderColor ?? _gaugeTheme.titleBorderColor, + width: widget.title!.borderWidth, ), - alignment: (widget.title!.alignment == GaugeAlignment.near) - ? Alignment.topLeft - : (widget.title!.alignment == GaugeAlignment.far) - ? Alignment.topRight - : (widget.title!.alignment == GaugeAlignment.center) - ? Alignment.topCenter - : Alignment.topCenter); + ), + alignment: + (widget.title!.alignment == GaugeAlignment.near) + ? Alignment.topLeft + : (widget.title!.alignment == GaugeAlignment.far) + ? Alignment.topRight + : (widget.title!.alignment == GaugeAlignment.center) + ? Alignment.topCenter + : Alignment.topCenter, + child: Text( + widget.title!.text, + style: _gaugeTheme.titleTextStyle, + textAlign: TextAlign.center, + overflow: TextOverflow.clip, + ), + ); return titleWidget; } else { @@ -193,33 +212,38 @@ class SfRadialGaugeState extends State @override Widget build(BuildContext context) { - final List _radialAxes = []; + final List radialAxes = []; for (int i = 0; i < widget.axes.length; i++) { - _radialAxes.add(RadialGaugeScope( + radialAxes.add( + RadialGaugeScope( enableLoadingAnimation: widget.enableLoadingAnimation, animationDuration: widget.animationDuration.toInt(), - child: widget.axes[i])); + child: widget.axes[i], + ), + ); } return RepaintBoundary( - child: LimitedBox( - maxHeight: 350, - maxWidth: 350, - child: Container( - color: widget.backgroundColor, - child: Column(children: [ - _addGaugeTitle(), - Expanded( - child: Stack( - textDirection: TextDirection.ltr, - children: _radialAxes)) - ])))); - } - - @override - void dispose() { - super.dispose(); + child: LimitedBox( + maxHeight: 350, + maxWidth: 350, + child: Container( + color: widget.backgroundColor, + child: Column( + children: [ + _addGaugeTitle(), + Expanded( + child: Stack( + textDirection: TextDirection.ltr, + children: radialAxes, + ), + ), + ], + ), + ), + ), + ); } /// Method to convert the [SfRadialGauge] as an image. diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge_scope.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge_scope.dart index 3050a51f2..439c06dc0 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge_scope.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge_scope.dart @@ -21,9 +21,10 @@ class RadialGaugeScope extends InheritedWidget { static RadialGaugeScope of(BuildContext context) { late RadialGaugeScope scope; - final Widget widget = context - .getElementForInheritedWidgetOfExactType()! - .widget; + final Widget widget = + context + .getElementForInheritedWidgetOfExactType()! + .widget; if (widget is RadialGaugeScope) { scope = widget; 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 cff1e5751..1d308330c 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 @@ -61,20 +61,16 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { this.overlayColor, this.overlayRadius, this.elevation = 0, - }) : textStyle = textStyle ?? - const GaugeTextStyle( - fontSize: 12.0, - fontFamily: 'Segoe UI', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal), - assert(animationDuration > 0, - 'Animation duration must be a non-negative value.'), - 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( - elevation >= 0, 'Shadow elevation must be a non-negative value.'), - super(key: key); + }) : textStyle = textStyle ?? const GaugeTextStyle(), + assert( + animationDuration > 0, + 'Animation duration must be a non-negative value.', + ), + 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(elevation >= 0, 'Shadow elevation must be a non-negative value.'), + super(key: key); /// Specifies the built-in shape type for pointer. /// @@ -350,7 +346,7 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { /// } ///``` final MarkerPointerRendererFactory? - onCreatePointerRenderer; + onCreatePointerRenderer; /// Elevation of the pointer. /// @@ -659,9 +655,12 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { @override RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); + final SfColorScheme colorScheme = SfTheme.colorScheme(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; MarkerPointerRenderer? markerPointerRenderer; if (onCreatePointerRenderer != null) { @@ -670,45 +669,51 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { } return RenderMarkerPointer( - value: value.clamp(ancestor.minimum, ancestor.maximum), - enableDragging: enableDragging, - onValueChanged: onValueChanged, - onValueChangeStart: onValueChangeStart, - onValueChangeEnd: onValueChangeEnd, - onValueChanging: onValueChanging, - markerType: markerType, - color: color, - markerWidth: markerWidth, - markerHeight: markerHeight, - borderWidth: borderWidth, - markerOffset: markerOffset, - text: text, - borderColor: borderColor, - offsetUnit: offsetUnit, - imageUrl: imageUrl, - markerPointerRenderer: markerPointerRenderer, - textStyle: textStyle, - overlayColor: overlayColor, - overlayRadius: overlayRadius, - elevation: elevation, - animationType: animationType, - pointerInterval: radialAxisScope.pointerInterval, - enableAnimation: enableAnimation, - isRadialGaugeAnimationEnabled: - radialAxisScope.isRadialGaugeAnimationEnabled, - pointerAnimationController: radialAxisScope.animationController, - repaintNotifier: radialAxisScope.repaintNotifier, - gaugeThemeData: gaugeTheme, - context: context); + value: value.clamp(ancestor.minimum, ancestor.maximum), + enableDragging: enableDragging, + onValueChanged: onValueChanged, + onValueChangeStart: onValueChangeStart, + onValueChangeEnd: onValueChangeEnd, + onValueChanging: onValueChanging, + markerType: markerType, + color: color, + markerWidth: markerWidth, + markerHeight: markerHeight, + borderWidth: borderWidth, + markerOffset: markerOffset, + text: text, + borderColor: borderColor, + offsetUnit: offsetUnit, + imageUrl: imageUrl, + markerPointerRenderer: markerPointerRenderer, + textStyle: textStyle, + overlayColor: overlayColor, + overlayRadius: overlayRadius, + elevation: elevation, + animationType: animationType, + pointerInterval: radialAxisScope.pointerInterval, + enableAnimation: enableAnimation, + isRadialGaugeAnimationEnabled: + radialAxisScope.isRadialGaugeAnimationEnabled, + pointerAnimationController: radialAxisScope.animationController, + repaintNotifier: radialAxisScope.repaintNotifier, + gaugeThemeData: gaugeTheme, + themeData: themeData, + colorScheme: colorScheme, + ); } @override void updateRenderObject( - BuildContext context, RenderMarkerPointer renderObject) { + BuildContext context, + RenderMarkerPointer renderObject, + ) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; MarkerPointerRenderer? markerPointerRenderer; if (onCreatePointerRenderer != null) { markerPointerRenderer = onCreatePointerRenderer!(); @@ -743,6 +748,7 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { ..isRadialGaugeAnimationEnabled = radialAxisScope.isRadialGaugeAnimationEnabled ..gaugeThemeData = gaugeTheme + ..themeData = themeData ..value = value.clamp(ancestor.minimum, ancestor.maximum); super.updateRenderObject(context, renderObject); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart index 9fbc5d907..bb7a60115 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart @@ -18,62 +18,63 @@ import '../../radial_gauge/utils/radial_callback_args.dart'; /// Represents the renderer of radial gauge marker pointer. class RenderMarkerPointer extends RenderBox { /// Creates a object for [RenderMarkerPointer]. - RenderMarkerPointer( - {required double value, - required this.enableDragging, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - required MarkerType markerType, - Color? color, - required double markerWidth, - required double markerHeight, - required double borderWidth, - required double markerOffset, - String? text, - Color? borderColor, - required GaugeSizeUnit offsetUnit, - String? imageUrl, - MarkerPointerRenderer? markerPointerRenderer, - required GaugeTextStyle textStyle, - required BuildContext context, - Color? overlayColor, - double? overlayRadius, - double elevation = 0, - AnimationController? pointerAnimationController, - this.pointerInterval, - required this.animationType, - required this.enableAnimation, - required this.isRadialGaugeAnimationEnabled, - required ValueNotifier repaintNotifier, - required SfGaugeThemeData gaugeThemeData}) - : _value = value, - _markerType = markerType, - _color = color, - _markerWidth = markerWidth, - _markerHeight = markerHeight, - _borderWidth = borderWidth, - _markerOffset = markerOffset, - _text = text, - _borderColor = borderColor, - _offsetUnit = offsetUnit, - _imageUrl = imageUrl, - _textStyle = textStyle, - _overlayColor = overlayColor, - _overlayRadius = overlayRadius, - _elevation = elevation, - _markerPointerRenderer = markerPointerRenderer, - _pointerAnimationController = pointerAnimationController, - _repaintNotifier = repaintNotifier, - _gaugeThemeData = gaugeThemeData, - _themeData = Theme.of(context), - _isDarkTheme = Theme.of(context).brightness == Brightness.dark; + RenderMarkerPointer({ + required double value, + required this.enableDragging, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + required MarkerType markerType, + Color? color, + required double markerWidth, + required double markerHeight, + required double borderWidth, + required double markerOffset, + String? text, + Color? borderColor, + required GaugeSizeUnit offsetUnit, + String? imageUrl, + MarkerPointerRenderer? markerPointerRenderer, + required GaugeTextStyle textStyle, + Color? overlayColor, + double? overlayRadius, + double elevation = 0, + AnimationController? pointerAnimationController, + this.pointerInterval, + required this.animationType, + required this.enableAnimation, + required this.isRadialGaugeAnimationEnabled, + required ValueNotifier repaintNotifier, + required SfGaugeThemeData gaugeThemeData, + required ThemeData themeData, + required SfColorScheme colorScheme, + }) : _value = value, + _markerType = markerType, + _color = color, + _markerWidth = markerWidth, + _markerHeight = markerHeight, + _borderWidth = borderWidth, + _markerOffset = markerOffset, + _text = text, + _borderColor = borderColor, + _offsetUnit = offsetUnit, + _imageUrl = imageUrl, + _textStyle = textStyle, + _overlayColor = overlayColor, + _overlayRadius = overlayRadius, + _elevation = elevation, + _markerPointerRenderer = markerPointerRenderer, + _pointerAnimationController = pointerAnimationController, + _repaintNotifier = repaintNotifier, + _gaugeThemeData = gaugeThemeData, + _themeData = themeData, + _colorScheme = colorScheme; final double _margin = 15; dart_ui.Image? _image; late double _radian; - Size? _textSize; + late Size? _textSize; late double _totalOffset; late double _actualMarkerOffset; late double _angle; @@ -88,14 +89,12 @@ class RenderMarkerPointer extends RenderBox { late double _centerXPoint; late double _centerYPoint; late Offset _axisCenter; - final bool _isDarkTheme; - final ThemeData _themeData; /// Marker pointer old value. double? oldValue; /// Pointer rect. - late Rect pointerRect; + Rect? pointerRect; /// Specifies the value whether the pointer is dragged bool? isDragStarted; @@ -209,6 +208,30 @@ class RenderMarkerPointer extends RenderBox { markNeedsPaint(); } + /// Gets the themeData assigned to [RenderRadialAxisWidget]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the themeData for [RenderRadialAxisWidget]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + /// Gets the colors of SfColorScheme + SfColorScheme get colorScheme => _colorScheme; + SfColorScheme _colorScheme; + set colorScheme(SfColorScheme value) { + if (value == _colorScheme) { + return; + } + _colorScheme = value; + markNeedsPaint(); + } + /// Gets the value assigned to [RenderMarkerPointer]. double get value => _value; double _value; @@ -482,7 +505,10 @@ class RenderMarkerPointer extends RenderBox { _axisCenter = axisRenderer!.getAxisCenter(); _radius = _axisRenderer!.getRadius(); _actualAxisWidth = _axisRenderer!.getActualValue( - _axisRenderer!.thickness, _axisRenderer!.thicknessUnit, false); + _axisRenderer!.thickness, + _axisRenderer!.thicknessUnit, + false, + ); } void _animationStatusListener(AnimationStatus status) { @@ -537,10 +563,11 @@ class RenderMarkerPointer extends RenderBox { } pointerRect = Rect.fromLTRB( - offset.dx - markerWidth / 2 - _margin, - offset.dy - markerHeight / 2 - _margin, - offset.dx + markerWidth / 2 + _margin, - offset.dy + markerHeight / 2 + _margin); + offset.dx - markerWidth / 2 - _margin, + offset.dy - markerHeight / 2 - _margin, + offset.dx + markerWidth / 2 + _margin, + offset.dy + markerHeight / 2 + _margin, + ); } @override @@ -553,8 +580,8 @@ class RenderMarkerPointer extends RenderBox { @override bool hitTestSelf(Offset position) { - if (enableDragging) { - return pointerRect.contains(position); + if (enableDragging && pointerRect != null) { + return pointerRect!.contains(position); } else { return false; } @@ -562,36 +589,45 @@ class RenderMarkerPointer extends RenderBox { /// Method returns the angle of current pointer value double _getPointerAngle() { - final double currentFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer?.valueToFactor(value) != null) - ? axisRenderer!.renderer?.valueToFactor(value) ?? - axisRenderer!.valueToFactor(value) - : axisRenderer!.valueToFactor(value); + final double currentFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer?.valueToFactor(value) != null) + ? axisRenderer!.renderer?.valueToFactor(value) ?? + axisRenderer!.valueToFactor(value) + : axisRenderer!.valueToFactor(value); return (currentFactor * _sweepAngle) + axisRenderer!.startAngle; } /// Calculates the marker offset position Offset _getMarkerOffset(double markerRadian) { - _actualMarkerOffset = - axisRenderer!.getActualValue(markerOffset, offsetUnit, true); - _totalOffset = _actualMarkerOffset < 0 - ? axisRenderer!.getAxisOffset() + _actualMarkerOffset - : (_actualMarkerOffset + axisRenderer!.getAxisOffset()); + _actualMarkerOffset = axisRenderer!.getActualValue( + markerOffset, + offsetUnit, + true, + ); + _totalOffset = + _actualMarkerOffset < 0 + ? axisRenderer!.getAxisOffset() + _actualMarkerOffset + : (_actualMarkerOffset + axisRenderer!.getAxisOffset()); if (!axisRenderer!.canScaleToFit) { - final double x = (size.width / 2) + + final double x = + (size.width / 2) + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.cos(markerRadian) - _centerXPoint; - final double y = (size.height / 2) + + final double y = + (size.height / 2) + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.sin(markerRadian) - _centerYPoint; _offset = Offset(x, y); } else { - final double x = _axisCenter.dx + + final double x = + _axisCenter.dx + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.cos(markerRadian); - final double y = _axisCenter.dy + + final double y = + _axisCenter.dy + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.sin(markerRadian); _offset = Offset(x, y); @@ -604,16 +640,18 @@ class RenderMarkerPointer extends RenderBox { // ignore: avoid_void_async void _loadImage() async { await _renderImage().then((void value) { - WidgetsBinding.instance! - .addPostFrameCallback((Duration duration) => markNeedsPaint()); + WidgetsBinding.instance.addPostFrameCallback( + (Duration duration) => markNeedsPaint(), + ); }); } /// Renders the image from the image url Future _renderImage() async { final ByteData imageData = await rootBundle.load(imageUrl!); - final dart_ui.Codec imageCodec = - await dart_ui.instantiateImageCodec(imageData.buffer.asUint8List()); + final dart_ui.Codec imageCodec = await dart_ui.instantiateImageCodec( + imageData.buffer.asUint8List(), + ); final dart_ui.FrameInfo frameInfo = await imageCodec.getNextFrame(); _image = frameInfo.image; } @@ -623,80 +661,108 @@ class RenderMarkerPointer extends RenderBox { /// By overriding this method, you can draw the customized marker /// pointer using required values. /// - void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, - SfGaugeThemeData gaugeThemeData) { - final Paint paint = Paint() - ..color = color ?? - gaugeThemeData.markerColor ?? - _themeData.colorScheme.secondaryContainer.withOpacity(0.8) - ..style = PaintingStyle.fill; + void drawPointer( + Canvas canvas, + PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData, + ) { + final Paint paint = + Paint() + ..color = + color ?? + gaugeThemeData.markerColor ?? + colorScheme.secondaryContainer[205]! + ..style = PaintingStyle.fill; const Color shadowColor = Colors.black; Paint? overlayPaint; if ((isHovered != null && isHovered!) && - overlayColor != Colors.transparent) { - overlayPaint = Paint() - ..color = overlayColor ?? - color?.withOpacity(0.12) ?? - gaugeThemeData.markerColor?.withOpacity(0.12) ?? - _themeData.colorScheme.secondaryContainer.withOpacity(0.12) - ..style = PaintingStyle.fill; + overlayColor != colorScheme.transparent) { + overlayPaint = + Paint() + ..color = + overlayColor ?? + color?.withValues(alpha: 0.12) ?? + gaugeThemeData.markerColor?.withValues(alpha: 0.12) ?? + _themeData.colorScheme.secondaryContainer.withValues( + alpha: 0.12, + ) + ..style = PaintingStyle.fill; } Paint? borderPaint; if (borderWidth > 0) { - borderPaint = Paint() - ..color = borderColor ?? gaugeThemeData.markerBorderColor - ..strokeWidth = borderWidth - ..style = PaintingStyle.stroke; + borderPaint = + Paint() + ..color = borderColor ?? gaugeThemeData.markerBorderColor + ..strokeWidth = borderWidth + ..style = PaintingStyle.stroke; } canvas.save(); switch (markerType) { case MarkerType.circle: - _drawCircle(canvas, paint, pointerPaintingDetails.startOffset, - borderPaint, overlayPaint, shadowColor); + _drawCircle( + canvas, + paint, + pointerPaintingDetails.startOffset, + borderPaint, + overlayPaint, + shadowColor, + ); break; case MarkerType.rectangle: _drawRectangle( - canvas, - paint, - pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, - borderPaint, - overlayPaint, - shadowColor); + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + borderPaint, + overlayPaint, + shadowColor, + ); break; case MarkerType.image: - _drawMarkerImage(canvas, paint, pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle); + _drawMarkerImage( + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + ); canvas.restore(); break; case MarkerType.triangle: case MarkerType.invertedTriangle: _drawTriangle( - canvas, - paint, - pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, - borderPaint, - overlayPaint, - shadowColor); + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + borderPaint, + overlayPaint, + shadowColor, + ); break; case MarkerType.diamond: _drawDiamond( + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + borderPaint, + overlayPaint, + shadowColor, + ); + break; + case MarkerType.text: + if (text != null) { + _drawText( canvas, paint, pointerPaintingDetails.startOffset, pointerPaintingDetails.pointerAngle, - borderPaint, - overlayPaint, - shadowColor); - break; - case MarkerType.text: - if (text != null) { - _drawText(canvas, paint, pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, gaugeThemeData); + gaugeThemeData, + ); canvas.restore(); } break; @@ -704,71 +770,90 @@ class RenderMarkerPointer extends RenderBox { if (markerType != MarkerType.text && markerType != MarkerType.image) { core.paint( - canvas: canvas, - rect: _markerRect, - paint: paint, - elevation: elevation, - elevationColor: shadowColor, - shapeType: _shapeMarkerType, - borderPaint: borderPaint); + canvas: canvas, + rect: _markerRect, + paint: paint, + elevation: elevation, + elevationColor: shadowColor, + shapeType: _shapeMarkerType, + borderPaint: borderPaint, + ); canvas.restore(); } } /// To render the MarkerShape.Text - void _drawText(Canvas canvas, Paint paint, Offset startPosition, - double pointerAngle, SfGaugeThemeData gaugeThemeData) { - final Color labelColor = textStyle.color ?? - gaugeThemeData.axisLabelColor ?? - (_isDarkTheme - ? _themeData.colorScheme.onSurface - : _themeData.colorScheme.onSurface.withOpacity(0.72)); - - final TextSpan span = TextSpan( - text: text, - style: TextStyle( - color: labelColor, - fontSize: textStyle.fontSize, - fontFamily: textStyle.fontFamily, - fontStyle: textStyle.fontStyle, - fontWeight: textStyle.fontWeight)); + void _drawText( + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + SfGaugeThemeData gaugeThemeData, + ) { + final TextStyle markerTextStyle = _themeData.textTheme.bodySmall!.copyWith( + color: + textStyle.color ?? + _gaugeThemeData.markerTextStyle?.color ?? + _gaugeThemeData.axisLabelColor ?? + colorScheme.onSurface[184], + fontSize: textStyle.fontSize ?? _gaugeThemeData.markerTextStyle?.fontSize, + fontFamily: + textStyle.fontFamily ?? _gaugeThemeData.markerTextStyle?.fontFamily, + fontStyle: + textStyle.fontStyle ?? _gaugeThemeData.markerTextStyle?.fontStyle, + fontWeight: + textStyle.fontWeight ?? _gaugeThemeData.markerTextStyle?.fontWeight, + ); + final TextSpan span = TextSpan(text: text, style: markerTextStyle); final TextPainter textPainter = TextPainter( - text: span, - textDirection: TextDirection.ltr, - textAlign: TextAlign.center); + 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, + 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, Color shadowColor) { + void _drawCircle( + Canvas canvas, + Paint paint, + Offset startPosition, + Paint? borderPaint, + Paint? overlayPaint, + Color shadowColor, + ) { final double pointerOverlayRadius = overlayRadius ?? 15; _markerRect = Rect.fromLTRB( - startPosition.dx - markerWidth / 2, - startPosition.dy - markerHeight / 2, - startPosition.dx + markerWidth / 2, - startPosition.dy + markerHeight / 2); + startPosition.dx - markerWidth / 2, + startPosition.dy - markerHeight / 2, + startPosition.dx + markerWidth / 2, + startPosition.dy + markerHeight / 2, + ); Rect overlayRect; if (overlayRadius != null) { overlayRect = Rect.fromLTRB( - startPosition.dx - pointerOverlayRadius, - startPosition.dy - pointerOverlayRadius, - startPosition.dx + pointerOverlayRadius, - startPosition.dy + pointerOverlayRadius); + startPosition.dx - pointerOverlayRadius, + startPosition.dy - pointerOverlayRadius, + startPosition.dx + pointerOverlayRadius, + startPosition.dy + pointerOverlayRadius, + ); } else { overlayRect = Rect.fromLTRB( - _markerRect.left - pointerOverlayRadius, - _markerRect.top - pointerOverlayRadius, - _markerRect.right + pointerOverlayRadius, - _markerRect.bottom + pointerOverlayRadius); + _markerRect.left - pointerOverlayRadius, + _markerRect.top - pointerOverlayRadius, + _markerRect.right + pointerOverlayRadius, + _markerRect.bottom + pointerOverlayRadius, + ); } if (overlayPaint != null) { @@ -780,24 +865,30 @@ class RenderMarkerPointer extends RenderBox { /// Renders the MarkerShape.rectangle void _drawRectangle( - Canvas canvas, - Paint paint, - Offset startPosition, - double pointerAngle, - Paint? borderPaint, - Paint? overlayPaint, - Color shadowColor) { + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + Paint? borderPaint, + Paint? overlayPaint, + Color shadowColor, + ) { final double pointerOverlayRadius = overlayRadius ?? 15; Rect overlayRect; if (overlayRadius != null) { - overlayRect = Rect.fromLTRB(-pointerOverlayRadius, -pointerOverlayRadius, - pointerOverlayRadius, pointerOverlayRadius); + overlayRect = Rect.fromLTRB( + -pointerOverlayRadius, + -pointerOverlayRadius, + pointerOverlayRadius, + pointerOverlayRadius, + ); } else { overlayRect = Rect.fromLTRB( - _markerRect.left - pointerOverlayRadius, - _markerRect.top - pointerOverlayRadius, - _markerRect.right + pointerOverlayRadius, - _markerRect.bottom + pointerOverlayRadius); + _markerRect.left - pointerOverlayRadius, + _markerRect.top - pointerOverlayRadius, + _markerRect.right + pointerOverlayRadius, + _markerRect.bottom + pointerOverlayRadius, + ); } canvas.translate(startPosition.dx, startPosition.dy); @@ -811,11 +902,19 @@ class RenderMarkerPointer extends RenderBox { /// Renders the MarkerShape.image void _drawMarkerImage( - Canvas canvas, Paint paint, Offset startPosition, double pointerAngle) { + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + ) { canvas.translate(startPosition.dx, startPosition.dy); canvas.rotate(getDegreeToRadian(pointerAngle + 90)); final Rect rect = Rect.fromLTRB( - -markerWidth / 2, -markerHeight / 2, markerWidth / 2, markerHeight / 2); + -markerWidth / 2, + -markerHeight / 2, + markerWidth / 2, + markerHeight / 2, + ); if (_image != null) { canvas.drawImageNine(_image!, rect, rect, paint); } @@ -823,13 +922,14 @@ class RenderMarkerPointer extends RenderBox { /// Renders the MarkerShape.diamond void _drawDiamond( - Canvas canvas, - Paint paint, - Offset startPosition, - double pointerAngle, - Paint? borderPaint, - Paint? overlayPaint, - Color shadowColor) { + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + Paint? borderPaint, + Paint? overlayPaint, + Color shadowColor, + ) { canvas.translate(startPosition.dx, startPosition.dy); canvas.rotate(getDegreeToRadian(pointerAngle - 90)); @@ -859,17 +959,19 @@ class RenderMarkerPointer extends RenderBox { /// Renders the triangle and the inverted triangle void _drawTriangle( - Canvas canvas, - Paint paint, - Offset startPosition, - double pointerAngle, - Paint? borderPaint, - Paint? overlayPaint, - Color shadowColor) { + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + Paint? borderPaint, + Paint? overlayPaint, + Color shadowColor, + ) { canvas.translate(startPosition.dx, startPosition.dy); - final double triangleAngle = markerType == MarkerType.triangle - ? pointerAngle + 90 - : pointerAngle - 90; + final double triangleAngle = + markerType == MarkerType.triangle + ? pointerAngle + 90 + : pointerAngle - 90; canvas.rotate(getDegreeToRadian(triangleAngle)); if (overlayPaint != null) { @@ -881,13 +983,19 @@ class RenderMarkerPointer extends RenderBox { overlayPath.lineTo(0, -pointerOverlayRadius); overlayPath.lineTo(-pointerOverlayRadius, pointerOverlayRadius); } else { - overlayPath.moveTo(-((markerWidth + pointerOverlayRadius) / 2), - (markerHeight + pointerOverlayRadius) / 2); - overlayPath.lineTo((markerWidth + pointerOverlayRadius) / 2, - (markerHeight + pointerOverlayRadius) / 2); + overlayPath.moveTo( + -((markerWidth + pointerOverlayRadius) / 2), + (markerHeight + pointerOverlayRadius) / 2, + ); + overlayPath.lineTo( + (markerWidth + pointerOverlayRadius) / 2, + (markerHeight + pointerOverlayRadius) / 2, + ); overlayPath.lineTo(0, -((markerHeight + pointerOverlayRadius) / 2)); - overlayPath.lineTo(-((markerWidth + pointerOverlayRadius) / 2), - (markerHeight + pointerOverlayRadius) / 2); + overlayPath.lineTo( + -((markerWidth + pointerOverlayRadius) / 2), + (markerHeight + pointerOverlayRadius) / 2, + ); } overlayPath.close(); @@ -916,9 +1024,10 @@ class RenderMarkerPointer extends RenderBox { if (isRadialGaugeAnimationEnabled) { if (_pointerAnimation != null && _isInitialLoading) { - needsShowPointer = axisRenderer!.isInversed - ? _pointerAnimation!.value < 1 - : _pointerAnimation!.value > 0; + needsShowPointer = + axisRenderer!.isInversed + ? _pointerAnimation!.value < 1 + : _pointerAnimation!.value > 0; } else { needsShowPointer = true; } @@ -929,17 +1038,25 @@ class RenderMarkerPointer extends RenderBox { if (needsShowPointer) { final PointerPaintingDetails pointerPaintingDetails = PointerPaintingDetails( - startOffset: markerOffset, - endOffset: markerOffset, - pointerAngle: markerAngle, - axisRadius: _radius, - axisCenter: _axisCenter); - - _markerRect = Rect.fromLTRB(-markerWidth / 2, -markerHeight / 2, - markerWidth / 2, markerHeight / 2); + startOffset: markerOffset, + endOffset: markerOffset, + pointerAngle: markerAngle, + axisRadius: _radius, + axisCenter: _axisCenter, + ); + + _markerRect = Rect.fromLTRB( + -markerWidth / 2, + -markerHeight / 2, + markerWidth / 2, + markerHeight / 2, + ); if (markerPointerRenderer != null) { - markerPointerRenderer! - .drawPointer(canvas, pointerPaintingDetails, _gaugeThemeData); + markerPointerRenderer!.drawPointer( + canvas, + pointerPaintingDetails, + _gaugeThemeData, + ); } else { drawPointer(canvas, pointerPaintingDetails, _gaugeThemeData); } 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 ee91a9f98..d55fdf4b3 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 @@ -33,35 +33,41 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { /// /// The arguments [value], must not be null and [animationDuration], /// [needleLength], [needleStartWidth], [needleEndWidth] must be non-negative. - const NeedlePointer( - {Key? key, - this.value = 0, - this.enableDragging = false, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - KnobStyle? knobStyle, - this.tailStyle, - this.gradient, - this.needleLength = 0.6, - this.lengthUnit = GaugeSizeUnit.factor, - this.needleStartWidth = 1, - this.needleEndWidth = 10, - this.onCreatePointerRenderer, - this.enableAnimation = false, - this.animationDuration = 1000, - this.animationType = AnimationType.ease, - this.needleColor}) - : knobStyle = knobStyle ?? const KnobStyle(knobRadius: 0.08), - assert(animationDuration > 0, - 'Animation duration must be a non-negative value'), - assert(needleLength >= 0, 'Needle length must be greater than zero.'), - assert(needleStartWidth >= 0, - 'Needle start width must be greater than zero.'), - assert( - needleEndWidth >= 0, 'Needle end width must be greater than zero.'), - super(key: key); + const NeedlePointer({ + Key? key, + this.value = 0, + this.enableDragging = false, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + KnobStyle? knobStyle, + this.tailStyle, + this.gradient, + this.needleLength = 0.6, + this.lengthUnit = GaugeSizeUnit.factor, + this.needleStartWidth = 1, + this.needleEndWidth = 10, + this.onCreatePointerRenderer, + this.enableAnimation = false, + this.animationDuration = 1000, + this.animationType = AnimationType.ease, + this.needleColor, + }) : knobStyle = knobStyle ?? const KnobStyle(), + assert( + animationDuration > 0, + 'Animation duration must be a non-negative value', + ), + assert(needleLength >= 0, 'Needle length must be greater than zero.'), + assert( + needleStartWidth >= 0, + 'Needle start width must be greater than zero.', + ), + assert( + needleEndWidth >= 0, + 'Needle end width must be greater than zero.', + ), + super(key: key); /// The style to use for the needle knob. /// @@ -270,7 +276,7 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { /// } ///``` final NeedlePointerRendererFactory? - onCreatePointerRenderer; + onCreatePointerRenderer; /// Specifies the duration of the pointer animation. /// @@ -505,9 +511,12 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { @override RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); + final SfColorScheme colorScheme = SfTheme.colorScheme(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; NeedlePointerRenderer? needlePointerRenderer; if (onCreatePointerRenderer != null) { @@ -516,39 +525,46 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { } return RenderNeedlePointer( - value: value.clamp(ancestor.minimum, ancestor.maximum), - enableDragging: enableDragging, - onValueChanged: onValueChanged, - onValueChangeStart: onValueChangeStart, - onValueChangeEnd: onValueChangeEnd, - onValueChanging: onValueChanging, - knobStyle: knobStyle, - tailStyle: tailStyle, - gradient: gradient, - needleLength: needleLength, - lengthUnit: lengthUnit, - needleStartWidth: needleStartWidth, - needleEndWidth: needleEndWidth, - needlePointerRenderer: needlePointerRenderer, - needleColor: needleColor, - pointerAnimationController: radialAxisScope.animationController, - pointerInterval: radialAxisScope.pointerInterval, - isRadialGaugeAnimationEnabled: - radialAxisScope.isRadialGaugeAnimationEnabled, - enableAnimation: enableAnimation, - animationType: animationType, - repaintNotifier: radialAxisScope.repaintNotifier, - gaugeThemeData: gaugeTheme, - context: context); + value: value.clamp(ancestor.minimum, ancestor.maximum), + enableDragging: enableDragging, + onValueChanged: onValueChanged, + onValueChangeStart: onValueChangeStart, + onValueChangeEnd: onValueChangeEnd, + onValueChanging: onValueChanging, + knobStyle: knobStyle, + tailStyle: tailStyle, + gradient: gradient, + needleLength: needleLength, + lengthUnit: lengthUnit, + needleStartWidth: needleStartWidth, + needleEndWidth: needleEndWidth, + needlePointerRenderer: needlePointerRenderer, + needleColor: needleColor, + pointerAnimationController: radialAxisScope.animationController, + pointerInterval: radialAxisScope.pointerInterval, + isRadialGaugeAnimationEnabled: + radialAxisScope.isRadialGaugeAnimationEnabled, + enableAnimation: enableAnimation, + animationType: animationType, + repaintNotifier: radialAxisScope.repaintNotifier, + gaugeThemeData: gaugeTheme, + themeData: themeData, + colorScheme: colorScheme, + ); } @override void updateRenderObject( - BuildContext context, RenderNeedlePointer renderObject) { + BuildContext context, + RenderNeedlePointer renderObject, + ) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final SfColorScheme colorScheme = SfTheme.colorScheme(context); + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; NeedlePointerRenderer? needlePointerRenderer; if (onCreatePointerRenderer != null) { needlePointerRenderer = onCreatePointerRenderer!(); @@ -577,6 +593,8 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { ..isRadialGaugeAnimationEnabled = radialAxisScope.isRadialGaugeAnimationEnabled ..gaugeThemeData = gaugeTheme + ..themeData = themeData + ..colorScheme = colorScheme ..value = value.clamp(ancestor.minimum, ancestor.maximum); super.updateRenderObject(context, renderObject); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart index 2c6518542..eb4992f90 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart @@ -16,44 +16,46 @@ import '../../radial_gauge/utils/radial_callback_args.dart'; /// Represents the renderer of radial gauge needle pointer. class RenderNeedlePointer extends RenderBox { /// Creates a object for [RenderNeedlePointer]. - RenderNeedlePointer( - {required double value, - required this.enableDragging, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - required KnobStyle knobStyle, - TailStyle? tailStyle, - Gradient? gradient, - required double needleLength, - required GaugeSizeUnit lengthUnit, - required double needleStartWidth, - required double needleEndWidth, - NeedlePointerRenderer? needlePointerRenderer, - Color? needleColor, - AnimationController? pointerAnimationController, - this.pointerInterval, - required this.animationType, - required this.enableAnimation, - required this.isRadialGaugeAnimationEnabled, - required ValueNotifier repaintNotifier, - required BuildContext context, - required SfGaugeThemeData gaugeThemeData}) - : _value = value, - _knobStyle = knobStyle, - _tailStyle = tailStyle, - _gradient = gradient, - _needleLength = needleLength, - _lengthUnit = lengthUnit, - _needleStartWidth = needleStartWidth, - _needleEndWidth = needleEndWidth, - _needleColor = needleColor, - _repaintNotifier = repaintNotifier, - _pointerAnimationController = pointerAnimationController, - _needlePointerRenderer = needlePointerRenderer, - _themeData = Theme.of(context), - _gaugeThemeData = gaugeThemeData; + RenderNeedlePointer({ + required double value, + required this.enableDragging, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + required KnobStyle knobStyle, + TailStyle? tailStyle, + Gradient? gradient, + required double needleLength, + required GaugeSizeUnit lengthUnit, + required double needleStartWidth, + required double needleEndWidth, + NeedlePointerRenderer? needlePointerRenderer, + Color? needleColor, + AnimationController? pointerAnimationController, + this.pointerInterval, + required this.animationType, + required this.enableAnimation, + required this.isRadialGaugeAnimationEnabled, + required ValueNotifier repaintNotifier, + required ThemeData themeData, + required SfColorScheme colorScheme, + required SfGaugeThemeData gaugeThemeData, + }) : _value = value, + _knobStyle = knobStyle, + _tailStyle = tailStyle, + _gradient = gradient, + _needleLength = needleLength, + _lengthUnit = lengthUnit, + _needleStartWidth = needleStartWidth, + _needleEndWidth = needleEndWidth, + _needleColor = needleColor, + _repaintNotifier = repaintNotifier, + _pointerAnimationController = pointerAnimationController, + _needlePointerRenderer = needlePointerRenderer, + _themeData = themeData, + _colorScheme = colorScheme, + _gaugeThemeData = gaugeThemeData; double _actualTailLength = 0; late double _actualNeedleLength; @@ -86,13 +88,12 @@ class RenderNeedlePointer extends RenderBox { late double _radius; late double _sweepAngle; late Offset _axisCenter; - final ThemeData _themeData; /// Marker pointer old value. double? oldValue; /// Pointer rect. - late Rect pointerRect; + Rect? pointerRect; /// Specifies the value whether the pointer is dragged bool? isDragStarted; @@ -181,6 +182,32 @@ class RenderNeedlePointer extends RenderBox { markNeedsPaint(); } + /// Gets the themeData assigned to [RenderRadialAxisWidget]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the themeData for [RenderRadialAxisWidget]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + + /// Gets the colors assigned to [RenderNeedlePointer] + SfColorScheme get colorScheme => _colorScheme; + SfColorScheme _colorScheme; + + /// Sets the colors for [RenderNeedlePointer] + set colorScheme(SfColorScheme value) { + if (value == _colorScheme) { + return; + } + _colorScheme = value; + markNeedsPaint(); + } + /// Gets the animation assigned to [RenderNeedlePointer]. Animation? get pointerAnimation => _pointerAnimation; Animation? _pointerAnimation; @@ -431,8 +458,8 @@ class RenderNeedlePointer extends RenderBox { @override bool hitTestSelf(Offset position) { - if (enableDragging) { - return pointerRect.contains(position); + if (enableDragging && pointerRect != null) { + return pointerRect!.contains(position); } else { return false; } @@ -447,15 +474,22 @@ class RenderNeedlePointer extends RenderBox { /// Calculates the default value void _calculateDefaultValue() { - _actualNeedleLength = - axisRenderer!.getActualValue(needleLength, lengthUnit, false); - _actualCapRadius = axisRenderer! - .getActualValue(knobStyle.knobRadius, knobStyle.sizeUnit, false); - final double currentFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer!.valueToFactor(value) != null) - ? axisRenderer!.renderer!.valueToFactor(value) ?? - axisRenderer!.valueToFactor(value) - : axisRenderer!.valueToFactor(value); + _actualNeedleLength = axisRenderer!.getActualValue( + needleLength, + lengthUnit, + false, + ); + _actualCapRadius = axisRenderer!.getActualValue( + knobStyle.knobRadius, + knobStyle.sizeUnit, + false, + ); + final double currentFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor(value) != null) + ? axisRenderer!.renderer!.valueToFactor(value) ?? + axisRenderer!.valueToFactor(value) + : axisRenderer!.valueToFactor(value); _angle = (currentFactor * _sweepAngle) + axisRenderer!.startAngle; _radian = getDegreeToRadian(_angle); _centerPoint = _axisCenter; @@ -525,8 +559,11 @@ class RenderNeedlePointer extends RenderBox { /// Calculates the values to render the needle tail void _calculateTailPosition(double needleRadian) { final double pointerWidth = tailStyle!.width; - _actualTailLength = axisRenderer! - .getActualValue(tailStyle!.length, tailStyle!.lengthUnit, false); + _actualTailLength = axisRenderer!.getActualValue( + tailStyle!.length, + tailStyle!.lengthUnit, + false, + ); if (_actualTailLength > 0) { final double tailEndX = _startX - _actualTailLength * math.cos(needleRadian); @@ -549,10 +586,14 @@ class RenderNeedlePointer extends RenderBox { /// By overriding this method, you can draw the customized needled pointer /// using required values. /// - void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, - SfGaugeThemeData gaugeThemeData) { - final double pointerRadian = - getDegreeToRadian(pointerPaintingDetails.pointerAngle); + void drawPointer( + Canvas canvas, + PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData, + ) { + final double pointerRadian = getDegreeToRadian( + pointerPaintingDetails.pointerAngle, + ); if (_actualNeedleLength > 0) { _renderNeedle(canvas, pointerRadian); } @@ -564,11 +605,13 @@ class RenderNeedlePointer extends RenderBox { /// To render the needle of the pointer void _renderNeedle(Canvas canvas, double pointerRadian) { - final Paint paint = Paint() - ..color = needleColor ?? - _gaugeThemeData.needleColor ?? - _themeData.colorScheme.onSurface - ..style = PaintingStyle.fill; + final Paint paint = + Paint() + ..color = + needleColor ?? + _gaugeThemeData.needleColor ?? + colorScheme.onSurface[255]! + ..style = PaintingStyle.fill; final Path path = Path(); path.moveTo(_startLeftX, _startLeftY); @@ -601,23 +644,27 @@ class RenderNeedlePointer extends RenderBox { canvas.translate(_centerPoint.dx, _centerPoint.dy); canvas.rotate(pointerRadian); - final Paint tailPaint = Paint() - ..color = tailStyle!.color ?? - _gaugeThemeData.tailColor ?? - _themeData.colorScheme.onSurface; + final Paint tailPaint = + Paint() + ..color = + tailStyle!.color ?? + _gaugeThemeData.tailColor ?? + colorScheme.onSurface[255]!; if (tailStyle!.gradient != null) { - tailPaint.shader = - tailStyle!.gradient!.createShader(tailPath.getBounds()); + tailPaint.shader = tailStyle!.gradient!.createShader( + tailPath.getBounds(), + ); } canvas.drawPath(tailPath, tailPaint); if (tailStyle!.borderWidth > 0) { - final Paint tailStrokePaint = Paint() - ..color = tailStyle!.borderColor ?? _gaugeThemeData.tailBorderColor - ..style = PaintingStyle.stroke - ..strokeWidth = tailStyle!.borderWidth; + final Paint tailStrokePaint = + Paint() + ..color = tailStyle!.borderColor ?? _gaugeThemeData.tailBorderColor + ..style = PaintingStyle.stroke + ..strokeWidth = tailStyle!.borderWidth; canvas.drawPath(tailPath, tailStrokePaint); } @@ -628,20 +675,26 @@ class RenderNeedlePointer extends RenderBox { /// To render the cap of needle void _renderCap(Canvas canvas, SfGaugeThemeData gaugeThemeData) { if (_actualCapRadius > 0) { - final Paint knobPaint = Paint() - ..color = knobStyle.color ?? - gaugeThemeData.knobColor ?? - _themeData.colorScheme.onSurface; + final Paint knobPaint = + Paint() + ..color = + knobStyle.color ?? + gaugeThemeData.knobColor ?? + colorScheme.onSurface[255]!; canvas.drawCircle(_axisCenter, _actualCapRadius, knobPaint); if (knobStyle.borderWidth > 0) { - final double actualBorderWidth = axisRenderer! - .getActualValue(knobStyle.borderWidth, knobStyle.sizeUnit, false); - final Paint strokePaint = Paint() - ..color = knobStyle.borderColor ?? gaugeThemeData.knobBorderColor - ..style = PaintingStyle.stroke - ..strokeWidth = actualBorderWidth; + final double actualBorderWidth = axisRenderer!.getActualValue( + knobStyle.borderWidth, + knobStyle.sizeUnit, + false, + ); + final Paint strokePaint = + Paint() + ..color = knobStyle.borderColor ?? gaugeThemeData.knobBorderColor + ..style = PaintingStyle.stroke + ..strokeWidth = actualBorderWidth; canvas.drawCircle(_centerPoint, _actualCapRadius, strokePaint); } @@ -655,7 +708,8 @@ class RenderNeedlePointer extends RenderBox { bool needsShowPointer = false; if (_pointerAnimation != null && _isAnimating) { - angle = (_sweepAngle * _pointerAnimation!.value) + + angle = + (_sweepAngle * _pointerAnimation!.value) + axisRenderer!.startAngle + 90; } else { @@ -664,9 +718,10 @@ class RenderNeedlePointer extends RenderBox { if (isRadialGaugeAnimationEnabled) { if (_pointerAnimation != null && _isInitialLoading) { - needsShowPointer = axisRenderer!.isInversed - ? _pointerAnimation!.value < 1 - : _pointerAnimation!.value > 0; + needsShowPointer = + axisRenderer!.isInversed + ? _pointerAnimation!.value < 1 + : _pointerAnimation!.value > 0; } else { needsShowPointer = true; } @@ -680,14 +735,18 @@ class RenderNeedlePointer extends RenderBox { final PointerPaintingDetails pointerPaintingDetails = PointerPaintingDetails( - startOffset: startPosition, - endOffset: endPosition, - pointerAngle: angle, - axisRadius: _radius, - axisCenter: _axisCenter); + startOffset: startPosition, + endOffset: endPosition, + pointerAngle: angle, + axisRadius: _radius, + axisCenter: _axisCenter, + ); if (needlePointerRenderer != null) { needlePointerRenderer!.drawPointer( - context.canvas, pointerPaintingDetails, _gaugeThemeData); + context.canvas, + pointerPaintingDetails, + _gaugeThemeData, + ); } else { drawPointer(context.canvas, pointerPaintingDetails, _gaugeThemeData); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/pointer_painting_details.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/pointer_painting_details.dart index 9ae69f7aa..99d0b3e0a 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/pointer_painting_details.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/pointer_painting_details.dart @@ -5,12 +5,13 @@ import 'package:flutter/material.dart'; @immutable class PointerPaintingDetails { /// Creates the details which are required to paint the pointer - const PointerPaintingDetails( - {required this.startOffset, - required this.endOffset, - required this.pointerAngle, - required this.axisRadius, - required this.axisCenter}); + const PointerPaintingDetails({ + 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/pointers/range_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart index 36a9d0d0c..ea54171c7 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 @@ -28,32 +28,32 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { /// /// The arguments [value], [pointerOffset], must not be null and /// [animationDuration], [width], must be non-negative. - const RangePointer( - {Key? key, - this.value = 0, - this.enableDragging = false, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - this.animationType = AnimationType.ease, - this.enableAnimation = false, - this.animationDuration = 1000, - this.cornerStyle = CornerStyle.bothFlat, - this.gradient, - this.pointerOffset = 0, - this.sizeUnit = GaugeSizeUnit.logicalPixel, - this.width = 10, - this.dashArray, - this.color}) - : assert( - animationDuration > 0, 'Animation duration must be non-negative'), - assert(width >= 0, 'Width must be a non-negative value.'), - assert( - (gradient != null && gradient is SweepGradient) || gradient == null, - 'The gradient must be null or else the gradient must be equal to ' - 'sweep gradient.'), - super(key: key); + const RangePointer({ + Key? key, + this.value = 0, + this.enableDragging = false, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + this.animationType = AnimationType.ease, + this.enableAnimation = false, + this.animationDuration = 1000, + this.cornerStyle = CornerStyle.bothFlat, + this.gradient, + this.pointerOffset = 0, + this.sizeUnit = GaugeSizeUnit.logicalPixel, + this.width = 10, + this.dashArray, + this.color, + }) : assert(animationDuration > 0, 'Animation duration must be non-negative'), + assert(width >= 0, 'Width must be a non-negative value.'), + assert( + (gradient != null && gradient is SweepGradient) || gradient == null, + 'The gradient must be null or else the gradient must be equal to ' + 'sweep gradient.', + ), + super(key: key); /// Adjusts the range pointer position. /// @@ -449,42 +449,49 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { @override RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; return RenderRangePointer( - value: value.clamp(ancestor.minimum, ancestor.maximum), - enableDragging: enableDragging, - onValueChanged: onValueChanged, - onValueChangeStart: onValueChangeStart, - onValueChangeEnd: onValueChangeEnd, - onValueChanging: onValueChanging, - cornerStyle: cornerStyle, - gradient: gradient, - pointerOffset: pointerOffset, - sizeUnit: sizeUnit, - width: width, - dashArray: dashArray, - color: color, - pointerAnimationController: radialAxisScope.animationController, - pointerInterval: radialAxisScope.pointerInterval, - enableAnimation: enableAnimation, - isRadialGaugeAnimationEnabled: - radialAxisScope.isRadialGaugeAnimationEnabled, - repaintNotifier: radialAxisScope.repaintNotifier, - animationType: animationType, - context: context, - gaugeThemeData: gaugeTheme); + value: value.clamp(ancestor.minimum, ancestor.maximum), + enableDragging: enableDragging, + onValueChanged: onValueChanged, + onValueChangeStart: onValueChangeStart, + onValueChangeEnd: onValueChangeEnd, + onValueChanging: onValueChanging, + cornerStyle: cornerStyle, + gradient: gradient, + pointerOffset: pointerOffset, + sizeUnit: sizeUnit, + width: width, + dashArray: dashArray, + color: color, + pointerAnimationController: radialAxisScope.animationController, + pointerInterval: radialAxisScope.pointerInterval, + enableAnimation: enableAnimation, + isRadialGaugeAnimationEnabled: + radialAxisScope.isRadialGaugeAnimationEnabled, + repaintNotifier: radialAxisScope.repaintNotifier, + animationType: animationType, + themeData: themeData, + gaugeThemeData: gaugeTheme, + ); } @override void updateRenderObject( - BuildContext context, RenderRangePointer renderObject) { + BuildContext context, + RenderRangePointer renderObject, + ) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; renderObject ..enableDragging = enableDragging @@ -506,6 +513,7 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { ..isRadialGaugeAnimationEnabled = radialAxisScope.isRadialGaugeAnimationEnabled ..gaugeThemeData = gaugeTheme + ..themeData = themeData ..value = value.clamp(ancestor.minimum, ancestor.maximum); super.updateRenderObject(context, renderObject); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart index e283fe2c6..71e56cec4 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart @@ -12,40 +12,40 @@ import '../utils/radial_callback_args.dart'; /// Represents the renderer of radial gauge range pointer. class RenderRangePointer extends RenderBox { /// Creates a object for [RenderRangePointer]. - RenderRangePointer( - {required double value, - required this.enableDragging, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - required CornerStyle cornerStyle, - Gradient? gradient, - required double pointerOffset, - required GaugeSizeUnit sizeUnit, - required double width, - List? dashArray, - Color? color, - AnimationController? pointerAnimationController, - this.pointerInterval, - required this.animationType, - required this.enableAnimation, - required this.isRadialGaugeAnimationEnabled, - required ValueNotifier repaintNotifier, - required BuildContext context, - required SfGaugeThemeData gaugeThemeData}) - : _value = value, - _cornerStyle = cornerStyle, - _gradient = gradient, - _pointerOffset = pointerOffset, - _sizeUnit = sizeUnit, - _width = width, - _dashArray = dashArray, - _color = color, - _pointerAnimationController = pointerAnimationController, - _repaintNotifier = repaintNotifier, - _themeData = Theme.of(context), - _gaugeThemeData = gaugeThemeData; + RenderRangePointer({ + required double value, + required this.enableDragging, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + required CornerStyle cornerStyle, + Gradient? gradient, + required double pointerOffset, + required GaugeSizeUnit sizeUnit, + required double width, + List? dashArray, + Color? color, + AnimationController? pointerAnimationController, + this.pointerInterval, + required this.animationType, + required this.enableAnimation, + required this.isRadialGaugeAnimationEnabled, + required ValueNotifier repaintNotifier, + required ThemeData themeData, + required SfGaugeThemeData gaugeThemeData, + }) : _value = value, + _cornerStyle = cornerStyle, + _gradient = gradient, + _pointerOffset = pointerOffset, + _sizeUnit = sizeUnit, + _width = width, + _dashArray = dashArray, + _color = color, + _pointerAnimationController = pointerAnimationController, + _repaintNotifier = repaintNotifier, + _themeData = themeData, + _gaugeThemeData = gaugeThemeData; late double _rangeArcTop; late double _rangeArcBottom; @@ -67,7 +67,6 @@ class RenderRangePointer extends RenderBox { late Offset _axisCenter; bool _isAnimating = true; bool _isInitialLoading = true; - final ThemeData _themeData; /// Range pointer animation start value. double? animationStartValue; @@ -167,6 +166,19 @@ class RenderRangePointer extends RenderBox { markNeedsPaint(); } + /// Gets the gaugeThemeData assigned to [RenderRangePointer]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the gaugeThemeData for [RenderMarkerPointer]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + /// Gets the animation assigned to [RenderRangePointer]. Animation? get pointerAnimation => _pointerAnimation; Animation? _pointerAnimation; @@ -392,10 +404,11 @@ class RenderRangePointer extends RenderBox { bool hitTestSelf(Offset position) { if (enableDragging) { final Rect pathRect = Rect.fromLTRB( - arcPath.getBounds().left + _axisCenter.dx - 5, - arcPath.getBounds().top + _axisCenter.dy - 5, - arcPath.getBounds().right + _axisCenter.dx + 5, - arcPath.getBounds().bottom + _axisCenter.dy + 5); + arcPath.getBounds().left + _axisCenter.dx - 5, + arcPath.getBounds().top + _axisCenter.dy - 5, + arcPath.getBounds().right + _axisCenter.dx + 5, + arcPath.getBounds().bottom + _axisCenter.dy + 5, + ); return pathRect.contains(position); } else { return false; @@ -405,25 +418,34 @@ class RenderRangePointer extends RenderBox { /// Method to calculate pointer position. void _calculatePosition() { _updateAxisValues(); - _actualRangeThickness = - axisRenderer!.getActualValue(width, sizeUnit, false); - _actualPointerOffset = - axisRenderer!.getActualValue(pointerOffset, sizeUnit, true); - _totalOffset = _actualPointerOffset < 0 - ? axisRenderer!.getAxisOffset() + _actualPointerOffset - : (_actualPointerOffset + axisRenderer!.getAxisOffset()); - final double minFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer!.valueToFactor(axisRenderer!.minimum) != - null) - ? axisRenderer!.renderer!.valueToFactor(axisRenderer!.minimum) ?? - axisRenderer!.valueToFactor(axisRenderer!.minimum) - : axisRenderer!.valueToFactor(axisRenderer!.minimum); + _actualRangeThickness = axisRenderer!.getActualValue( + width, + sizeUnit, + false, + ); + _actualPointerOffset = axisRenderer!.getActualValue( + pointerOffset, + sizeUnit, + true, + ); + _totalOffset = + _actualPointerOffset < 0 + ? axisRenderer!.getAxisOffset() + _actualPointerOffset + : (_actualPointerOffset + axisRenderer!.getAxisOffset()); + final double minFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor(axisRenderer!.minimum) != + null) + ? axisRenderer!.renderer!.valueToFactor(axisRenderer!.minimum) ?? + axisRenderer!.valueToFactor(axisRenderer!.minimum) + : axisRenderer!.valueToFactor(axisRenderer!.minimum); _startArc = (minFactor * _sweepAngle) + axisRenderer!.startAngle; - final double maxFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer!.valueToFactor(value) != null) - ? axisRenderer!.renderer!.valueToFactor(value) ?? - axisRenderer!.valueToFactor(value) - : axisRenderer!.valueToFactor(value); + final double maxFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor(value) != null) + ? axisRenderer!.renderer!.valueToFactor(value) ?? + axisRenderer!.valueToFactor(value) + : axisRenderer!.valueToFactor(value); final double rangeEndAngle = (maxFactor * _sweepAngle) + axisRenderer!.startAngle; _endArc = rangeEndAngle - _startArc; @@ -439,12 +461,24 @@ class RenderRangePointer extends RenderBox { /// To creates the arc rect for range pointer void _createRangeRect() { _arcRect = Rect.fromLTRB( - _rangeArcLeft, _rangeArcTop, _rangeArcRight, _rangeArcBottom); + _rangeArcLeft, + _rangeArcTop, + _rangeArcRight, + _rangeArcBottom, + ); pointerRect = Rect.fromLTRB( - _rangeArcLeft, _rangeArcTop, _rangeArcRight, _rangeArcBottom); + _rangeArcLeft, + _rangeArcTop, + _rangeArcRight, + _rangeArcBottom, + ); arcPath = Path(); - arcPath.arcTo(_arcRect, getDegreeToRadian(_startArc), - getDegreeToRadian(_endArc), true); + arcPath.arcTo( + _arcRect, + getDegreeToRadian(_startArc), + getDegreeToRadian(_endArc), + true, + ); _calculateCornerStylePosition(); } @@ -456,30 +490,35 @@ class RenderRangePointer extends RenderBox { switch (cornerStyle) { case CornerStyle.startCurve: { - _startCornerRadian = axisRenderer!.isInversed - ? getDegreeToRadian(-_cornerAngle) - : getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = axisRenderer!.isInversed - ? getDegreeToRadian(_endArc + _cornerAngle) - : getDegreeToRadian(_endArc - _cornerAngle); + _startCornerRadian = + axisRenderer!.isInversed + ? getDegreeToRadian(-_cornerAngle) + : getDegreeToRadian(_cornerAngle); + _sweepCornerRadian = + axisRenderer!.isInversed + ? getDegreeToRadian(_endArc + _cornerAngle) + : getDegreeToRadian(_endArc - _cornerAngle); } break; case CornerStyle.endCurve: { _startCornerRadian = getDegreeToRadian(0); - _sweepCornerRadian = axisRenderer!.isInversed - ? getDegreeToRadian(_endArc + _cornerAngle) - : getDegreeToRadian(_endArc - _cornerAngle); + _sweepCornerRadian = + axisRenderer!.isInversed + ? getDegreeToRadian(_endArc + _cornerAngle) + : getDegreeToRadian(_endArc - _cornerAngle); } break; case CornerStyle.bothCurve: { - _startCornerRadian = axisRenderer!.isInversed - ? getDegreeToRadian(-_cornerAngle) - : getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = axisRenderer!.isInversed - ? getDegreeToRadian(_endArc + 2 * _cornerAngle) - : getDegreeToRadian(_endArc - 2 * _cornerAngle); + _startCornerRadian = + axisRenderer!.isInversed + ? getDegreeToRadian(-_cornerAngle) + : getDegreeToRadian(_cornerAngle); + _sweepCornerRadian = + axisRenderer!.isInversed + ? getDegreeToRadian(_endArc + 2 * _cornerAngle) + : getDegreeToRadian(_endArc - 2 * _cornerAngle); } break; case CornerStyle.bothFlat: @@ -500,42 +539,58 @@ class RenderRangePointer extends RenderBox { /// Draws the start corner style. void _drawStartCurve(Path path, double innerRadius, double outerRadius) { final Offset midPoint = getDegreeToPoint( - axisRenderer!.isInversed ? -_cornerAngle : _cornerAngle, - (innerRadius + outerRadius) / 2, - Offset.zero); + axisRenderer!.isInversed ? -_cornerAngle : _cornerAngle, + (innerRadius + outerRadius) / 2, + Offset.zero, + ); final double midStartAngle = getDegreeToRadian(180); double midEndAngle = midStartAngle + getDegreeToRadian(180); midEndAngle = axisRenderer!.isInversed ? -midEndAngle : midEndAngle; path.addArc( - Rect.fromCircle( - center: midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle, - midEndAngle); + Rect.fromCircle( + center: midPoint, + radius: (innerRadius - outerRadius).abs() / 2, + ), + midStartAngle, + midEndAngle, + ); } ///Draws the end corner curve. void _drawEndCurve( - Path path, double sweepRadian, double innerRadius, double outerRadius) { + Path path, + double sweepRadian, + double innerRadius, + double outerRadius, + ) { final double cornerAngle = cornerStyle == CornerStyle.bothCurve ? _cornerAngle : 0; - final double angle = axisRenderer!.isInversed - ? getRadianToDegree(sweepRadian) - cornerAngle - : getRadianToDegree(sweepRadian) + cornerAngle; - final Offset midPoint = - getDegreeToPoint(angle, (innerRadius + outerRadius) / 2, Offset.zero); + final double angle = + axisRenderer!.isInversed + ? getRadianToDegree(sweepRadian) - cornerAngle + : getRadianToDegree(sweepRadian) + cornerAngle; + final Offset midPoint = getDegreeToPoint( + angle, + (innerRadius + outerRadius) / 2, + Offset.zero, + ); final double midStartAngle = sweepRadian / 2; - final double midEndAngle = axisRenderer!.isInversed - ? midStartAngle - getDegreeToRadian(180) - : midStartAngle + getDegreeToRadian(180); + final double midEndAngle = + axisRenderer!.isInversed + ? midStartAngle - getDegreeToRadian(180) + : midStartAngle + getDegreeToRadian(180); path.arcTo( - Rect.fromCircle( - center: midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle, - midEndAngle, - false); + Rect.fromCircle( + center: midPoint, + radius: (innerRadius - outerRadius).abs() / 2, + ), + midStartAngle, + midEndAngle, + false, + ); } /// Checks whether the axis line is dashed line. @@ -562,12 +617,17 @@ class RenderRangePointer extends RenderBox { /// Returns the paint for the pointer. Paint _getPointerPaint(Rect rect, bool isFill) { - final Paint paint = Paint() - ..color = color ?? - gaugeThemeData.rangePointerColor ?? - _themeData.colorScheme.secondaryContainer.withOpacity(0.8) - ..strokeWidth = _actualRangeThickness - ..style = isFill ? PaintingStyle.fill : PaintingStyle.stroke; + final Paint paint = + Paint() + ..color = + color ?? + gaugeThemeData.rangePointerColor ?? + (_themeData.useMaterial3 + ? _themeData.colorScheme.primary + : _themeData.colorScheme.secondaryContainer) + .withValues(alpha: 0.8) + ..strokeWidth = _actualRangeThickness + ..style = isFill ? PaintingStyle.fill : PaintingStyle.stroke; if (gradient != null && gradient!.colors.isNotEmpty) { // Holds the color for gradient. @@ -580,9 +640,13 @@ class RenderRangePointer extends RenderBox { } // gradient for the range pointer. final SweepGradient sweepGradient = SweepGradient( - colors: gradientColors, - stops: calculateGradientStops( - offsets, axisRenderer!.isInversed, sweepAngle)); + colors: gradientColors, + stops: calculateGradientStops( + offsets, + axisRenderer!.isInversed, + sweepAngle, + ), + ); paint.shader = sweepGradient.createShader(rect); } @@ -597,8 +661,10 @@ class RenderRangePointer extends RenderBox { // Calculates the gradient stop values based on the number of // provided color. final double difference = 1 / gradient!.colors.length; - final List offsets = - List.filled(gradient!.colors.length, null); + final List offsets = List.filled( + gradient!.colors.length, + null, + ); for (int i = 0; i < gradient!.colors.length; i++) { offsets[i] = i * difference; } @@ -623,13 +689,14 @@ class RenderRangePointer extends RenderBox { final double outerRadius = _radius - _totalOffset; final double innerRadius = outerRadius - _actualRangeThickness; final double cornerRadius = (innerRadius - outerRadius).abs() / 2; - final double refCurveRadius = (2 * - math.pi * - (innerRadius + outerRadius) / - 2 * - getRadianToDegree(_sweepCornerRadian) / - 360) - .abs(); + final double refCurveRadius = + (2 * + math.pi * + (innerRadius + outerRadius) / + 2 * + getRadianToDegree(_sweepCornerRadian) / + 360) + .abs(); final Path path = Path(); final bool isDashedPointerLine = _getIsDashedLine(); @@ -639,11 +706,12 @@ class RenderRangePointer extends RenderBox { canvas.save(); canvas.translate(_axisCenter.dx, _axisCenter.dy); canvas.rotate(getDegreeToRadian(_startArc)); - final double curveRadius = cornerStyle != CornerStyle.bothFlat - ? cornerStyle == CornerStyle.startCurve - ? cornerRadius - : cornerRadius * 2 - : 0; + final double curveRadius = + cornerStyle != CornerStyle.bothFlat + ? cornerStyle == CornerStyle.startCurve + ? cornerRadius + : cornerRadius * 2 + : 0; if (cornerStyle != CornerStyle.bothFlat && !isDashedPointerLine && (refCurveRadius.floorToDouble() > curveRadius)) { @@ -656,8 +724,11 @@ class RenderRangePointer extends RenderBox { } if (needsToAnimatePointer) { - path.addArc(Rect.fromCircle(center: Offset.zero, radius: outerRadius), - _startCornerRadian, sweepRadian); + path.addArc( + Rect.fromCircle(center: Offset.zero, radius: outerRadius), + _startCornerRadian, + sweepRadian, + ); } if (cornerStyle == CornerStyle.endCurve || @@ -668,14 +739,19 @@ class RenderRangePointer extends RenderBox { } if (needsToAnimatePointer) { - path.arcTo(Rect.fromCircle(center: Offset.zero, radius: innerRadius), - sweepRadian + _startCornerRadian, -sweepRadian, false); + path.arcTo( + Rect.fromCircle(center: Offset.zero, radius: innerRadius), + sweepRadian + _startCornerRadian, + -sweepRadian, + false, + ); } } else { isFill = false; - sweepRadian = cornerStyle == CornerStyle.bothFlat - ? sweepRadian - : getDegreeToRadian(_endArc); + sweepRadian = + cornerStyle == CornerStyle.bothFlat + ? sweepRadian + : getDegreeToRadian(_endArc); path.addArc(_arcRect, 0, sweepRadian); } @@ -686,9 +762,9 @@ class RenderRangePointer extends RenderBox { } else { if (dashArray != null) { canvas.drawPath( - dashPath(path, - dashArray: CircularIntervalList(dashArray!)), - paint); + dashPath(path, dashArray: CircularIntervalList(dashArray!)), + paint, + ); } } 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 index e9713ce07..61a2b2d1d 100644 --- 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 @@ -34,23 +34,25 @@ class WidgetPointer extends SingleChildRenderObjectWidget /// The arguments [child], [value], [offset], must not be null and /// [animationDuration] must be non-negative. /// - const WidgetPointer( - {Key? key, - this.value = 0, - this.enableDragging = false, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - this.animationType = AnimationType.ease, - this.enableAnimation = false, - this.animationDuration = 1000, - this.offsetUnit = GaugeSizeUnit.logicalPixel, - this.offset = 0, - required this.child}) - : assert(animationDuration > 0, - 'Animation duration must be a non-negative value.'), - super(key: key, child: child); + const WidgetPointer({ + Key? key, + this.value = 0, + this.enableDragging = false, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + this.animationType = AnimationType.ease, + this.enableAnimation = false, + this.animationDuration = 1000, + this.offsetUnit = GaugeSizeUnit.logicalPixel, + this.offset = 0, + required this.child, + }) : assert( + animationDuration > 0, + 'Animation duration must be a non-negative value.', + ), + super(key: key, child: child); /// A widget, which is to be used as the pointer. /// @@ -375,32 +377,37 @@ class WidgetPointer extends SingleChildRenderObjectWidget @override RenderObject createRenderObject(BuildContext context) { final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; return RenderWidgetPointer( - value: value.clamp(ancestor.minimum, ancestor.maximum), - enableDragging: enableDragging, - onValueChanged: onValueChanged, - onValueChangeStart: onValueChangeStart, - onValueChangeEnd: onValueChangeEnd, - onValueChanging: onValueChanging, - offsetUnit: offsetUnit, - pointerAnimationController: radialAxisScope.animationController, - pointerInterval: radialAxisScope.pointerInterval, - enableAnimation: enableAnimation, - repaintNotifier: radialAxisScope.repaintNotifier, - isRadialGaugeAnimationEnabled: - radialAxisScope.isRadialGaugeAnimationEnabled, - animationType: animationType, - offset: offset); + value: value.clamp(ancestor.minimum, ancestor.maximum), + enableDragging: enableDragging, + onValueChanged: onValueChanged, + onValueChangeStart: onValueChangeStart, + onValueChangeEnd: onValueChangeEnd, + onValueChanging: onValueChanging, + offsetUnit: offsetUnit, + pointerAnimationController: radialAxisScope.animationController, + pointerInterval: radialAxisScope.pointerInterval, + enableAnimation: enableAnimation, + repaintNotifier: radialAxisScope.repaintNotifier, + isRadialGaugeAnimationEnabled: + radialAxisScope.isRadialGaugeAnimationEnabled, + animationType: animationType, + offset: offset, + ); } @override void updateRenderObject( - BuildContext context, RenderWidgetPointer renderObject) { + BuildContext context, + RenderWidgetPointer renderObject, + ) { final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; renderObject ..enableDragging = enableDragging diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer_renderer.dart index bac4930ea..b7ccf165c 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer_renderer.dart @@ -11,28 +11,28 @@ import '../../radial_gauge/utils/radial_callback_args.dart'; /// Represents the renderer of radial gauge widget pointer. class RenderWidgetPointer extends RenderShiftedBox { /// Creates a object for [RenderWidgetPointer]. - RenderWidgetPointer( - {required double value, - required this.enableDragging, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - required GaugeSizeUnit offsetUnit, - required double offset, - AnimationController? pointerAnimationController, - this.pointerInterval, - required this.animationType, - required this.enableAnimation, - required this.isRadialGaugeAnimationEnabled, - required ValueNotifier repaintNotifier, - RenderBox? child}) - : _value = value, - _offsetUnit = offsetUnit, - _offset = offset, - _pointerAnimationController = pointerAnimationController, - _repaintNotifier = repaintNotifier, - super(child); + RenderWidgetPointer({ + required double value, + required this.enableDragging, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + required GaugeSizeUnit offsetUnit, + required double offset, + AnimationController? pointerAnimationController, + this.pointerInterval, + required this.animationType, + required this.enableAnimation, + required this.isRadialGaugeAnimationEnabled, + required ValueNotifier repaintNotifier, + RenderBox? child, + }) : _value = value, + _offsetUnit = offsetUnit, + _offset = offset, + _pointerAnimationController = pointerAnimationController, + _repaintNotifier = repaintNotifier, + super(child); late double _radian; late double _totalOffset; @@ -233,32 +233,45 @@ class RenderWidgetPointer extends RenderShiftedBox { /// Method returns the angle of current pointer value. double getPointerAngle() { - return (axisRenderer!.valueToFactor(value) * _sweepAngle) + - axisRenderer!.startAngle; + final double currentFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer?.valueToFactor(value) != null) + ? axisRenderer!.renderer?.valueToFactor(value) ?? + axisRenderer!.valueToFactor(value) + : axisRenderer!.valueToFactor(value); + return (currentFactor * _sweepAngle) + axisRenderer!.startAngle; } /// Calculates the marker offset position. Offset _getPointerOffset(double pointerRadian) { - _actualWidgetOffset = - axisRenderer!.getActualValue(offset, offsetUnit, true); - _totalOffset = _actualWidgetOffset < 0 - ? axisRenderer!.getAxisOffset() + _actualWidgetOffset - : (_actualWidgetOffset + axisRenderer!.getAxisOffset()); + _actualWidgetOffset = axisRenderer!.getActualValue( + offset, + offsetUnit, + true, + ); + _totalOffset = + _actualWidgetOffset < 0 + ? axisRenderer!.getAxisOffset() + _actualWidgetOffset + : (_actualWidgetOffset + axisRenderer!.getAxisOffset()); if (!axisRenderer!.canScaleToFit) { - final double x = (size.width / 2) + + final double x = + (size.width / 2) + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.cos(pointerRadian) - _centerXPoint; - final double y = (size.height / 2) + + final double y = + (size.height / 2) + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.sin(pointerRadian) - _centerYPoint; _pointerOffset = Offset(x, y); } else { - final double x = _axisCenter.dx + + final double x = + _axisCenter.dx + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.cos(pointerRadian); - final double y = _axisCenter.dy + + final double y = + _axisCenter.dy + (_radius - _totalOffset - (_actualAxisWidth / 2)) * math.sin(pointerRadian); _pointerOffset = Offset(x, y); @@ -274,7 +287,10 @@ class RenderWidgetPointer extends RenderShiftedBox { _axisCenter = axisRenderer!.getAxisCenter(); _radius = _axisRenderer!.getRadius(); _actualAxisWidth = _axisRenderer!.getActualValue( - _axisRenderer!.thickness, _axisRenderer!.thicknessUnit, false); + _axisRenderer!.thickness, + _axisRenderer!.thicknessUnit, + false, + ); } void _animationStatusListener(AnimationStatus status) { @@ -343,9 +359,10 @@ class RenderWidgetPointer extends RenderShiftedBox { if (isRadialGaugeAnimationEnabled) { if (_pointerAnimation != null && _isInitialLoading) { - _needsShowPointer = axisRenderer!.isInversed - ? _pointerAnimation!.value < 1 - : _pointerAnimation!.value > 0; + _needsShowPointer = + axisRenderer!.isInversed + ? _pointerAnimation!.value < 1 + : _pointerAnimation!.value > 0; } else { _needsShowPointer = true; } @@ -364,8 +381,12 @@ class RenderWidgetPointer extends RenderShiftedBox { size = Size.zero; } - _childRect = Rect.fromLTWH(_pointerOffset.dx, _pointerOffset.dy, - child!.size.width, child!.size.height); + _childRect = Rect.fromLTWH( + _pointerOffset.dx, + _pointerOffset.dy, + child!.size.width, + child!.size.height, + ); } } 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 49373f5eb..ae0cf4a0c 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 @@ -28,32 +28,28 @@ class GaugeRange extends LeafRenderObjectWidget { /// Create a range with the default or required properties. /// /// The arguments [startValue], [endValue], must not be null. - GaugeRange( - {Key? key, - 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}) - : startWidth = - startWidth = startWidth ?? (label != null ? startWidth : 10), - endWidth = endWidth = endWidth ?? (label != null ? endWidth : 10), - labelStyle = labelStyle ?? - const GaugeTextStyle( - fontSize: 12.0, - fontFamily: 'Segoe UI', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal), - assert( - (gradient != null && gradient is SweepGradient) || gradient == null, - 'The gradient must be null or else the gradient must be equal' - ' to sweep gradient.'), - super(key: key); + GaugeRange({ + Key? key, + 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, + }) : startWidth = + startWidth = startWidth ?? (label != null ? startWidth : 10), + endWidth = endWidth = endWidth ?? (label != null ? endWidth : 10), + labelStyle = labelStyle ?? const GaugeTextStyle(), + assert( + (gradient != null && gradient is SweepGradient) || gradient == null, + 'The gradient must be null or else the gradient must be equal' + ' to sweep gradient.', + ), + super(key: key); /// Specifies the range start value. /// @@ -292,31 +288,34 @@ class GaugeRange extends LeafRenderObjectWidget { RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; return RenderGaugeRange( - startValue: startValue.clamp(ancestor.minimum, ancestor.maximum), - endValue: endValue.clamp(ancestor.minimum, ancestor.maximum), - startWidth: startWidth, - endWidth: endWidth, - sizeUnit: sizeUnit, - color: color, - gradient: gradient, - rangeOffset: rangeOffset, - label: label, - rangeAnimation: radialAxisScope.animation, - labelStyle: labelStyle, - repaintNotifier: radialAxisScope.repaintNotifier, - gaugeThemeData: gaugeTheme); + startValue: startValue.clamp(ancestor.minimum, ancestor.maximum), + endValue: endValue.clamp(ancestor.minimum, ancestor.maximum), + startWidth: startWidth, + endWidth: endWidth, + sizeUnit: sizeUnit, + color: color, + gradient: gradient, + rangeOffset: rangeOffset, + label: label, + rangeAnimation: radialAxisScope.animation, + labelStyle: labelStyle, + repaintNotifier: radialAxisScope.repaintNotifier, + gaugeThemeData: gaugeTheme, + ); } @override void updateRenderObject(BuildContext context, RenderGaugeRange renderObject) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); - final RadialAxisInheritedWidget ancestor = context - .dependOnInheritedWidgetOfExactType()!; + final RadialAxisInheritedWidget ancestor = + context + .dependOnInheritedWidgetOfExactType()!; renderObject ..startValue = startValue.clamp(ancestor.minimum, ancestor.maximum) diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart index 7f0726a6e..898f24314 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart @@ -12,33 +12,33 @@ import '../../radial_gauge/utils/helper.dart'; /// Represents the renderer of radial gauge range. class RenderGaugeRange extends RenderBox { ///Creates a object for [RenderGaugeRange]. - RenderGaugeRange( - {required double startValue, - required double endValue, - double? startWidth, - double? endWidth, - required GaugeSizeUnit sizeUnit, - Color? color, - Gradient? gradient, - required double rangeOffset, - String? label, - required GaugeTextStyle labelStyle, - Animation? rangeAnimation, - required ValueNotifier repaintNotifier, - required SfGaugeThemeData gaugeThemeData}) - : _startValue = startValue, - _endValue = endValue, - _startWidth = startWidth, - _endWidth = endWidth, - _sizeUnit = sizeUnit, - _color = color, - _gradient = gradient, - _rangeOffset = rangeOffset, - _label = label, - _labelStyle = labelStyle, - _repaintNotifier = repaintNotifier, - _rangeAnimation = rangeAnimation, - _gaugeThemeData = gaugeThemeData; + RenderGaugeRange({ + required double startValue, + required double endValue, + double? startWidth, + double? endWidth, + required GaugeSizeUnit sizeUnit, + Color? color, + Gradient? gradient, + required double rangeOffset, + String? label, + required GaugeTextStyle labelStyle, + Animation? rangeAnimation, + required ValueNotifier repaintNotifier, + required SfGaugeThemeData gaugeThemeData, + }) : _startValue = startValue, + _endValue = endValue, + _startWidth = startWidth, + _endWidth = endWidth, + _sizeUnit = sizeUnit, + _color = color, + _gradient = gradient, + _rangeOffset = rangeOffset, + _label = label, + _labelStyle = labelStyle, + _repaintNotifier = repaintNotifier, + _rangeAnimation = rangeAnimation, + _gaugeThemeData = gaugeThemeData; late double _rangeMidRadian; late double _maxAngle; @@ -323,14 +323,19 @@ class RenderGaugeRange extends RenderBox { void _calculateRangePosition() { _updateAxisValues(); _calculateActualWidth(); - _actualRangeOffset = - axisRenderer!.getActualValue(rangeOffset, sizeUnit, true); - _center = !axisRenderer!.canScaleToFit - ? Offset(size.width / 2, size.height / 2) - : _axisCenter; - _totalOffset = _actualRangeOffset < 0 - ? axisRenderer!.getAxisOffset() + _actualRangeOffset - : (_actualRangeOffset + axisRenderer!.getAxisOffset()); + _actualRangeOffset = axisRenderer!.getActualValue( + rangeOffset, + sizeUnit, + true, + ); + _center = + !axisRenderer!.canScaleToFit + ? Offset(size.width / 2, size.height / 2) + : _axisCenter; + _totalOffset = + _actualRangeOffset < 0 + ? axisRenderer!.getAxisOffset() + _actualRangeOffset + : (_actualRangeOffset + axisRenderer!.getAxisOffset()); _maxAngle = _sweepAngle; _calculateRangeAngle(); if (_actualStartWidth != _actualEndWidth) { @@ -356,54 +361,66 @@ class RenderGaugeRange extends RenderBox { _outerArc = _getRadiusToAngleConversion(_outerStartOffset, _outerEndOffset); _innerArc = _getRadiusToAngleConversion(_innerStartOffset, _innerEndOffset); - _outerArcSweepAngle = - _getSweepAngle(_outerArc.endAngle - _outerArc.startAngle); - _innerArcSweepAngle = - _getSweepAngle(_innerArc.endAngle - _innerArc.startAngle); + _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; + 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 (!axisRenderer!.isInversed) { - _rangeStartValue = axisRenderer!.startAngle + + _rangeStartValue = + axisRenderer!.startAngle + (_maxAngle / ((axisRenderer!.maximum - axisRenderer!.minimum) / (startValue - axisRenderer!.minimum))); - _rangeEndValue = axisRenderer!.startAngle + + _rangeEndValue = + axisRenderer!.startAngle + (_maxAngle / ((axisRenderer!.maximum - axisRenderer!.minimum) / (endValue - axisRenderer!.minimum))); - _rangeMidValue = axisRenderer!.startAngle + + _rangeMidValue = + axisRenderer!.startAngle + (_maxAngle / ((axisRenderer!.maximum - axisRenderer!.minimum) / ((endValue - startValue) / 2 + startValue))); } else { - _rangeStartValue = axisRenderer!.startAngle + + _rangeStartValue = + axisRenderer!.startAngle + _maxAngle - (_maxAngle / ((axisRenderer!.maximum - axisRenderer!.minimum) / (startValue - axisRenderer!.minimum))); - _rangeEndValue = axisRenderer!.startAngle + + _rangeEndValue = + axisRenderer!.startAngle + _maxAngle - (_maxAngle / ((axisRenderer!.maximum - axisRenderer!.minimum) / (endValue - axisRenderer!.minimum))); - _rangeMidValue = axisRenderer!.startAngle + + _rangeMidValue = + axisRenderer!.startAngle + _maxAngle - (_maxAngle / ((axisRenderer!.maximum - axisRenderer!.minimum) / @@ -418,44 +435,53 @@ class RenderGaugeRange extends RenderBox { /// Method to calculate the rect for range with equal start and end width void _calculateEqualWidthArc() { _thickness = _actualStartWidth; - final double startFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer!.valueToFactor(startValue) != null) - ? axisRenderer!.renderer!.valueToFactor(startValue) ?? - axisRenderer!.valueToFactor(startValue) - : axisRenderer!.valueToFactor(startValue); + final double startFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor(startValue) != null) + ? axisRenderer!.renderer!.valueToFactor(startValue) ?? + axisRenderer!.valueToFactor(startValue) + : axisRenderer!.valueToFactor(startValue); _rangeStartRadian = getDegreeToRadian( - (startFactor * _sweepAngle) + axisRenderer!.startAngle); - final double endFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer!.valueToFactor(endValue) != null) - ? axisRenderer!.renderer!.valueToFactor(endValue) ?? - axisRenderer!.valueToFactor(endValue) - : axisRenderer!.valueToFactor(endValue); - final double endRadian = - getDegreeToRadian((endFactor * _sweepAngle) + axisRenderer!.startAngle); + (startFactor * _sweepAngle) + axisRenderer!.startAngle, + ); + final double endFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor(endValue) != null) + ? axisRenderer!.renderer!.valueToFactor(endValue) ?? + axisRenderer!.valueToFactor(endValue) + : axisRenderer!.valueToFactor(endValue); + final double endRadian = getDegreeToRadian( + (endFactor * _sweepAngle) + axisRenderer!.startAngle, + ); _rangeEndRadian = endRadian - _rangeStartRadian; // To render the range in clock wise if the start value is greater than the end value // for full circle axis track. if (axisRenderer!.startAngle == axisRenderer!.endAngle && startValue > endValue) { - final double midFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer!.valueToFactor(axisRenderer!.maximum) != - null) - ? axisRenderer!.renderer!.valueToFactor(axisRenderer!.maximum) ?? - axisRenderer!.valueToFactor(axisRenderer!.maximum) - : axisRenderer!.valueToFactor(axisRenderer!.maximum); + final double midFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor( + axisRenderer!.maximum, + ) != + null) + ? axisRenderer!.renderer!.valueToFactor(axisRenderer!.maximum) ?? + axisRenderer!.valueToFactor(axisRenderer!.maximum) + : axisRenderer!.valueToFactor(axisRenderer!.maximum); final double midRadian = getDegreeToRadian( - (midFactor * _sweepAngle) + axisRenderer!.startAngle); + (midFactor * _sweepAngle) + axisRenderer!.startAngle, + ); final double startRadian = getDegreeToRadian(axisRenderer!.startAngle); _rangeEndRadian = (midRadian - _rangeStartRadian) + (endRadian - startRadian); } _rangeRect = Rect.fromLTRB( - -(_radius - (_actualStartWidth / 2 + _totalOffset)), - -(_radius - (_actualStartWidth / 2 + _totalOffset)), - _radius - (_actualStartWidth / 2 + _totalOffset), - _radius - (_actualStartWidth / 2 + _totalOffset)); + -(_radius - (_actualStartWidth / 2 + _totalOffset)), + -(_radius - (_actualStartWidth / 2 + _totalOffset)), + _radius - (_actualStartWidth / 2 + _totalOffset), + _radius - (_actualStartWidth / 2 + _totalOffset), + ); } /// Method to calculate the sweep angle @@ -496,22 +522,33 @@ class RenderGaugeRange extends RenderBox { /// Method to create the arc data ArcData _getArcData( - Offset rangeStartOffset, Offset rangeEndOffset, Offset rangeMidOffset) { - final Offset controlPoint = - _getPointConversion(rangeStartOffset, rangeEndOffset, rangeMidOffset); + 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, - )); + 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; @@ -531,29 +568,34 @@ class RenderGaugeRange extends RenderBox { 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); + 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; + 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) + + 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)) + + double y = + -diff * (x - ((offset1.dx + offset2.dx) / 2)) + ((offset1.dy + offset2.dy) / 2); x = x.isNaN ? 0 : x; y = y.isNaN ? 0 : y; @@ -591,32 +633,41 @@ class RenderGaugeRange extends RenderBox { /// Calculates the label position void _calculateLabelPosition() { - final double midValueFactor = (axisRenderer!.renderer != null && - axisRenderer!.renderer! - .valueToFactor((endValue + startValue) / 2) != - null) - ? axisRenderer!.renderer!.valueToFactor((endValue + startValue) / 2) ?? - axisRenderer!.valueToFactor((endValue + startValue) / 2) - : axisRenderer!.valueToFactor((endValue + startValue) / 2); + final double midValueFactor = + (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor( + (endValue + startValue) / 2, + ) != + null) + ? axisRenderer!.renderer!.valueToFactor( + (endValue + startValue) / 2, + ) ?? + axisRenderer!.valueToFactor((endValue + startValue) / 2) + : axisRenderer!.valueToFactor((endValue + startValue) / 2); final double midAngle = (midValueFactor * _sweepAngle) + axisRenderer!.startAngle; final double labelRadian = getDegreeToRadian(midAngle); _labelAngle = midAngle - 90; - final double height = _actualStartWidth != _actualEndWidth - ? (_actualEndWidth - _actualStartWidth).abs() / 2 - : _actualEndWidth / 2; + final double height = + _actualStartWidth != _actualEndWidth + ? (_actualEndWidth - _actualStartWidth).abs() / 2 + : _actualEndWidth / 2; if (!axisRenderer!.canScaleToFit) { - final double x = size.width / 2 + + final double x = + size.width / 2 + ((_radius - (_totalOffset + height)) * math.cos(labelRadian)) - _centerXPoint; - final double y = size.height / 2 + + final double y = + size.height / 2 + ((_radius - (_totalOffset + height)) * math.sin(labelRadian)) - _centerYPoint; _labelPosition = Offset(x, y); } else { - final double x = _axisCenter.dx + + final double x = + _axisCenter.dx + ((_radius - (_totalOffset + height)) * math.cos(labelRadian)); - final double y = _axisCenter.dy + + final double y = + _axisCenter.dy + ((_radius - (_totalOffset + height)) * math.sin(labelRadian)); _labelPosition = Offset(x, y); } @@ -629,12 +680,14 @@ class RenderGaugeRange extends RenderBox { opacity = _rangeAnimation!.value; } - final Paint paint = Paint() - ..style = isFill ? PaintingStyle.fill : PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..color = color ?? _gaugeThemeData.rangeColor ?? const Color(0xFFF67280); - final double actualOpacity = paint.color.opacity; - paint.color = paint.color.withOpacity(opacity * actualOpacity); + final Paint paint = + Paint() + ..style = isFill ? PaintingStyle.fill : PaintingStyle.stroke + ..strokeWidth = strokeWidth + ..color = + color ?? _gaugeThemeData.rangeColor ?? const Color(0xFFF67280); + final double actualOpacity = paint.color.a; + paint.color = paint.color.withValues(alpha: opacity * actualOpacity); if (gradient != null && gradient!.colors.isNotEmpty) { List colors = gradient!.colors; if (axisRenderer!.isInversed) { @@ -642,20 +695,22 @@ class RenderGaugeRange extends RenderBox { } paint.shader = SweepGradient( - colors: colors, stops: _getGradientStops() as List) - .createShader(rect); + colors: colors, + stops: _getGradientStops() as List, + ).createShader(rect); } return paint; } /// To calculate the gradient stop based on the sweep angle List _getGradientStops() { - final double sweepRadian = _actualStartWidth != _actualEndWidth - ? _rangeEndRadian - _rangeStartRadian - : _rangeEndRadian; + final double sweepRadian = + _actualStartWidth != _actualEndWidth + ? _rangeEndRadian - _rangeStartRadian + : _rangeEndRadian; double rangeStartAngle = axisRenderer!.valueToFactor(startValue) * _sweepAngle + - axisRenderer!.startAngle; + axisRenderer!.startAngle; if (rangeStartAngle < 0) { rangeStartAngle += 360; } @@ -666,7 +721,10 @@ class RenderGaugeRange extends RenderBox { final double sweepAngle = getRadianToDegree(sweepRadian).abs(); return calculateGradientStops( - _getGradientOffset(), axisRenderer!.isInversed, sweepAngle); + _getGradientOffset(), + axisRenderer!.isInversed, + sweepAngle, + ); } /// Returns the gradient stop of axis line gradient @@ -677,8 +735,10 @@ class RenderGaugeRange extends RenderBox { // Calculates the gradient stop values based on the number of provided // color final double difference = 1 / gradient!.colors.length; - final List offsets = - List.filled(gradient!.colors.length, null); + final List offsets = List.filled( + gradient!.colors.length, + null, + ); for (int i = 0; i < gradient!.colors.length; i++) { offsets[i] = i * difference; } @@ -697,26 +757,31 @@ class RenderGaugeRange extends RenderBox { final Color rangeColor = color ?? _gaugeThemeData.rangeColor ?? const Color(0xFFF67280); final Color labelColor = labelStyle.color ?? getSaturationColor(rangeColor); - final double actualOpacity = labelColor.opacity; + final double actualOpacity = labelColor.a; final TextSpan span = TextSpan( - text: label, - style: TextStyle( - color: labelColor.withOpacity(actualOpacity * opacity), - fontSize: labelStyle.fontSize, - fontFamily: labelStyle.fontFamily, - fontStyle: labelStyle.fontStyle, - fontWeight: labelStyle.fontWeight)); + text: label, + style: TextStyle( + color: labelColor.withValues(alpha: actualOpacity * opacity), + fontSize: labelStyle.fontSize, + fontFamily: labelStyle.fontFamily, + fontStyle: labelStyle.fontStyle, + fontWeight: labelStyle.fontWeight, + ), + ); final TextPainter textPainter = TextPainter( - text: span, - textDirection: TextDirection.ltr, - textAlign: TextAlign.center); + text: span, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center, + ); textPainter.layout(); canvas.save(); canvas.translate(_labelPosition.dx, _labelPosition.dy); canvas.rotate(getDegreeToRadian(_labelAngle)); canvas.scale(-1); textPainter.paint( - canvas, Offset(-_labelSize.width / 2, -_labelSize.height / 2)); + canvas, + Offset(-_labelSize.width / 2, -_labelSize.height / 2), + ); canvas.restore(); } @@ -730,7 +795,9 @@ class RenderGaugeRange extends RenderBox { canvas.save(); if (!axisRenderer!.canScaleToFit) { canvas.translate( - _center.dx - _centerXPoint, _center.dy - _centerYPoint); + _center.dx - _centerXPoint, + _center.dy - _centerYPoint, + ); } else { canvas.translate(_axisCenter.dx, _axisCenter.dy); } @@ -738,10 +805,18 @@ class RenderGaugeRange extends RenderBox { canvas.rotate(_rangeStartRadian); if (_rangeRect == null) { - path.arcTo(_outerArc.arcRect, getDegreeToRadian(_outerArc.startAngle), - getDegreeToRadian(_outerArcSweepAngle), false); - path.arcTo(_innerArc.arcRect, getDegreeToRadian(_innerArc.endAngle), - getDegreeToRadian(_innerArcSweepAngle), false); + path.arcTo( + _outerArc.arcRect, + getDegreeToRadian(_outerArc.startAngle), + getDegreeToRadian(_outerArcSweepAngle), + false, + ); + path.arcTo( + _innerArc.arcRect, + getDegreeToRadian(_innerArc.endAngle), + getDegreeToRadian(_innerArcSweepAngle), + false, + ); paint = _getRangePaint(true, _pathRect, 0); canvas.drawPath(path, paint); 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 index 2a245f8ef..20f562fba 100644 --- 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 @@ -21,6 +21,9 @@ class MarkerPointerRenderer { /// By overriding this method, you can draw the customized marker /// pointer using required values . /// - void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, - SfGaugeThemeData gaugeThemeData) {} + void drawPointer( + Canvas canvas, + PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData, + ) {} } 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 index 6a90dd988..e15585020 100644 --- 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 @@ -21,6 +21,9 @@ class NeedlePointerRenderer { /// By overriding this method, you can draw the customized marker /// pointer using required values. /// - void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, - SfGaugeThemeData gaugeThemeData) {} + void drawPointer( + Canvas canvas, + PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData, + ) {} } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_knob_style.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_knob_style.dart index 4dafecf2b..ba13e923b 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_knob_style.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_knob_style.dart @@ -21,17 +21,18 @@ class KnobStyle { /// Creates the knob style with default or required properties. /// /// The arguments [knobRadius], [borderWidth] must be non-negative. - const KnobStyle( - {this.knobRadius = 0.08, - this.borderWidth = 0, - this.sizeUnit = GaugeSizeUnit.factor, - this.borderColor, - this.color}) - : assert(knobRadius >= 0, 'Knob radius must be a non-negative value.'), - assert( - borderWidth >= 0, - 'Knob border width must be a ' - 'non-negative value.'); + const KnobStyle({ + this.knobRadius = 0.08, + this.borderWidth = 0, + this.sizeUnit = GaugeSizeUnit.factor, + this.borderColor, + this.color, + }) : assert(knobRadius >= 0, 'Knob radius must be a non-negative value.'), + assert( + borderWidth >= 0, + 'Knob border width must be a ' + 'non-negative value.', + ); /// Adjusts the knob radius in needle pointer. /// @@ -159,8 +160,8 @@ class KnobStyle { borderWidth, sizeUnit, borderColor, - color + color, ]; - return hashList(values); + return Object.hashAll(values); } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tail_style.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tail_style.dart index 9152fc7dc..2c3550908 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tail_style.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tail_style.dart @@ -19,20 +19,21 @@ import '../../radial_gauge/utils/enum.dart'; @immutable class TailStyle { /// Creates the tail style with default or required properties. - const TailStyle( - {this.color, - this.width = 0, - this.length = 0, - this.borderWidth = 0, - this.gradient, - this.lengthUnit = GaugeSizeUnit.factor, - this.borderColor}) - : assert(width >= 0, 'Tail width must be a non-negative value.'), - assert(length >= 0, 'Tail length must be a non-negative value.'), - assert( - borderWidth >= 0, - 'Tail border width must be a ' - 'non-negative value.'); + const TailStyle({ + this.color, + this.width = 0, + this.length = 0, + this.borderWidth = 0, + this.gradient, + this.lengthUnit = GaugeSizeUnit.factor, + this.borderColor, + }) : assert(width >= 0, 'Tail width must be a non-negative value.'), + assert(length >= 0, 'Tail length must be a non-negative value.'), + assert( + borderWidth >= 0, + 'Tail border width must be a ' + 'non-negative value.', + ); /// Specifies the color of the tail. /// @@ -214,8 +215,8 @@ class TailStyle { length, gradient, lengthUnit, - borderColor + borderColor, ]; - return hashList(values); + return Object.hashAll(values); } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_text_style.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_text_style.dart index d659d5d26..106982e8a 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_text_style.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_text_style.dart @@ -7,12 +7,13 @@ import 'package:flutter/material.dart'; @immutable class GaugeTextStyle { /// Creates a gauge text style with default or required properties. - const GaugeTextStyle( - {this.color, - this.fontFamily = 'Segoe UI', - this.fontStyle = FontStyle.normal, - this.fontWeight = FontWeight.normal, - this.fontSize = 12}); + const GaugeTextStyle({ + this.color, + this.fontFamily, + this.fontStyle, + this.fontWeight, + this.fontSize, + }); /// To set the color of guage text. final Color? color; @@ -20,20 +21,20 @@ class GaugeTextStyle { /// To set the font family to guage text. /// ///Defaults to `Roboto`. - final String fontFamily; + final String? fontFamily; /// To set the font style to guage text. - final FontStyle fontStyle; + final FontStyle? fontStyle; /// To set the font weight to gauge text. /// /// Defaults to FontWeight.normal - final FontWeight fontWeight; + final FontWeight? fontWeight; /// To set the font size to gauge text /// /// Defaults to `12`. - final double fontSize; + final double? fontSize; @override bool operator ==(Object other) { @@ -58,8 +59,8 @@ class GaugeTextStyle { fontFamily, fontStyle, fontWeight, - fontSize + fontSize, ]; - return hashList(values); + return Object.hashAll(values); } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tick_style.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tick_style.dart index c1eccd0ca..6a76c514d 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tick_style.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/styles/radial_tick_style.dart @@ -22,14 +22,14 @@ class MajorTickStyle { /// Creates a major tick style with default or required properties. /// /// The arguments [length], [thickness], must be non-negative. - const MajorTickStyle( - {this.length = 7, - this.thickness = 1.5, - this.lengthUnit = GaugeSizeUnit.logicalPixel, - this.color, - this.dashArray}) - : assert(length >= 0, 'Tick length must be a non-negative value.'), - assert(thickness >= 0, 'Tick thickness must be a non-negative value.'); + const MajorTickStyle({ + this.length = 7, + this.thickness = 1.5, + this.lengthUnit = GaugeSizeUnit.logicalPixel, + this.color, + this.dashArray, + }) : assert(length >= 0, 'Tick length must be a non-negative value.'), + assert(thickness >= 0, 'Tick thickness must be a non-negative value.'); /// Specifies the length of the tick. /// @@ -147,9 +147,9 @@ class MajorTickStyle { thickness, lengthUnit, color, - dashArray + dashArray, ]; - return hashList(values); + return Object.hashAll(values); } } @@ -172,21 +172,21 @@ class MinorTickStyle extends MajorTickStyle { /// Creates a minor tick style with default or required properties. /// /// The arguments [length], [thickness], must be non-negative. - const MinorTickStyle( - {double length = 5, - GaugeSizeUnit lengthUnit = GaugeSizeUnit.logicalPixel, - Color? color, - double thickness = 1.5, - 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, - thickness: thickness, - dashArray: dashArray, - color: color, - ); + const MinorTickStyle({ + double length = 5, + GaugeSizeUnit lengthUnit = GaugeSizeUnit.logicalPixel, + Color? color, + double thickness = 1.5, + 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, + thickness: thickness, + dashArray: dashArray, + color: color, + ); } /// Create the style of axis line. @@ -207,19 +207,22 @@ class AxisLineStyle { /// Creates a minor tick style with default or required properties. /// /// The arguments [thickness], must be non-negative. - const AxisLineStyle( - {this.thickness = 10, - this.thicknessUnit = GaugeSizeUnit.logicalPixel, - this.color, - this.gradient, - this.cornerStyle = CornerStyle.bothFlat, - this.dashArray}) - : assert(thickness >= 0, - 'Axis line thickness must be a non-negative value.'), - assert( - (gradient != null && gradient is SweepGradient) || gradient == null, - 'The gradient must be null or else ' - 'the gradient must be equal to sweep gradient.'); + const AxisLineStyle({ + this.thickness = 10, + this.thicknessUnit = GaugeSizeUnit.logicalPixel, + this.color, + this.gradient, + this.cornerStyle = CornerStyle.bothFlat, + this.dashArray, + }) : assert( + thickness >= 0, + 'Axis line thickness must be a non-negative value.', + ), + assert( + (gradient != null && gradient is SweepGradient) || gradient == null, + 'The gradient must be null or else ' + 'the gradient must be equal to sweep gradient.', + ); /// Calculates the axis line thickness either in logical pixel /// or radius factor. @@ -373,8 +376,8 @@ class AxisLineStyle { color, gradient, cornerStyle, - dashArray + dashArray, ]; - return hashList(values); + return Object.hashAll(values); } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/title/radial_title.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/title/radial_title.dart index 962e35b1f..2f3d85d6d 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/title/radial_title.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/title/radial_title.dart @@ -26,17 +26,14 @@ import '../../radial_gauge/utils/enum.dart'; @immutable class GaugeTitle { /// Creates the gauge title with default or required properties. - const GaugeTitle( - {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}); + const GaugeTitle({ + required this.text, + this.textStyle, + this.alignment = GaugeAlignment.center, + this.borderColor, + this.borderWidth = 0, + this.backgroundColor, + }); /// Text to be displayed as gauge title. /// @@ -80,7 +77,7 @@ class GaugeTitle { /// )); ///} ///``` - final TextStyle textStyle; + final TextStyle? textStyle; /// The color that fills the title of the gauge. /// @@ -187,8 +184,8 @@ class GaugeTitle { textStyle, borderWidth, borderColor, - backgroundColor + backgroundColor, ]; - return hashList(values); + return Object.hashAll(values); } } 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 dd6d98de2..6e47f375d 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 @@ -10,7 +10,7 @@ enum GaugeAlignment { /// GaugeAlignment.far aligns the gauge element to far either /// the horizontal or vertical. - far + far, } /// Position the gauge element either inside or outside the axis. @@ -19,7 +19,7 @@ enum ElementsPosition { inside, /// ElementPosition.outside places the axis elements outside the axis. - outside + outside, } /// Apply the corner style for range pointer. @@ -34,7 +34,7 @@ enum CornerStyle { startCurve, /// CornerStyle.endCurce apply the rounded corner on end(right) side. - endCurve + endCurve, } /// Apply the different marker type for pointer. @@ -58,7 +58,7 @@ enum MarkerType { image, /// MarkerText.text points the value with text. - text + text, } /// Apply the different types of animation to pointer. @@ -82,7 +82,7 @@ enum AnimationType { linear, /// AnimationType.slowMiddle animates the pointers with Curves.slowMiddle. - slowMiddle + slowMiddle, } /// Size determined either the logical pixel or the radius factor. @@ -91,5 +91,5 @@ enum GaugeSizeUnit { factor, /// GaugeSizeUnit.logicalPixel specifies the value in logical pixel. - logicalPixel + logicalPixel, } 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 7beaa8313..28bbf9803 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,5 +1,6 @@ import 'dart:math' as math; import 'dart:ui'; + import 'package:flutter/material.dart'; import '../../radial_gauge/styles/radial_text_style.dart'; @@ -42,14 +43,18 @@ double getRadianToDegree(double radian) { /// To get the degree from the point 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); + return Offset( + center.dx + math.cos(degree) * radius, + center.dy + math.sin(degree) * radius, + ); } /// Methods to get the saturation color Color getSaturationColor(Color color) { final num contrast = - ((color.red * 299 + color.green * 587 + color.blue * 114) / 1000).round(); + (((color.r * 255) * 299 + (color.g * 255) * 587 + (color.b * 255) * 114) / + 1000) + .round(); final Color saturationColor = contrast >= 128 ? const Color(0xFF333333) : const Color(0xFFF5F5F5); return saturationColor; @@ -63,13 +68,15 @@ Size getTextSize(String textValue, GaugeTextStyle textStyle) { textAlign: TextAlign.center, textDirection: TextDirection.ltr, text: TextSpan( - text: textValue, - style: TextStyle( - color: textStyle.color, - fontSize: textStyle.fontSize, - fontFamily: textStyle.fontFamily, - fontStyle: textStyle.fontStyle, - fontWeight: textStyle.fontWeight)), + text: textValue, + style: TextStyle( + color: textStyle.color, + fontSize: textStyle.fontSize, + fontFamily: textStyle.fontFamily, + fontStyle: textStyle.fontStyle, + fontWeight: textStyle.fontWeight, + ), + ), ); textPainter.layout(); size = Size(textPainter.width, textPainter.height); @@ -78,17 +85,20 @@ Size getTextSize(String textValue, GaugeTextStyle textStyle) { /// Returns the revised gradient stop List calculateGradientStops( - List offsets, bool isInversed, double sweepAngle) { + 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 (int i = 0; i < offsets.length; i++) { final double offset = offsets[i]!; - double _stop = ((sweepAngle / 360) * offset).abs(); + double stop = ((sweepAngle / 360) * offset).abs(); if (isInversed) { - _stop = 1 - _stop; + stop = 1 - stop; } - gradientStops[i] = _stop; + gradientStops[i] = stop; } return isInversed ? gradientStops.reversed.toList() : gradientStops; @@ -155,8 +165,10 @@ Path dashPath(Path source, {CircularIntervalList? dashArray}) { while (distance < measurePath.length) { final double length = dashArray.next; if (draw) { - path.addPath(measurePath.extractPath(distance, distance + length), - Offset.zero); + path.addPath( + measurePath.extractPath(distance, distance + length), + Offset.zero, + ); } distance += length; draw = !draw; @@ -169,10 +181,12 @@ Path dashPath(Path source, {CircularIntervalList? dashArray}) { /// Calculates the corner radius angle double cornerRadiusAngle(double totalRadius, double circleRadius) { final double perimeter = (totalRadius + totalRadius + circleRadius) / 2; - final double area = math.sqrt(perimeter * - (perimeter - totalRadius) * - (perimeter - totalRadius) * - (perimeter - circleRadius)); + final double area = math.sqrt( + perimeter * + (perimeter - totalRadius) * + (perimeter - totalRadius) * + (perimeter - circleRadius), + ); final double cornerRadiusAngle = math.asin((2 * area) / (totalRadius * totalRadius)) * (180 / math.pi); return cornerRadiusAngle; @@ -182,6 +196,7 @@ double cornerRadiusAngle(double totalRadius, double circleRadius) { class RadialAxisInheritedWidget extends InheritedWidget { /// Creates [RadialAxisInheritedWidget]. const RadialAxisInheritedWidget({ + super.key, required this.minimum, required this.maximum, required Widget child, diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/radial_gauge_typedef.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/radial_gauge_typedef.dart index 99a6b9f62..a382697f2 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/radial_gauge_typedef.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/radial_gauge_typedef.dart @@ -1,14 +1,14 @@ /// Signature for the callback that reports the custom renderer has been extended /// and set to the gauge axis -typedef GaugeAxisRendererFactory = GaugeAxisRenderer - Function(); +typedef GaugeAxisRendererFactory = + GaugeAxisRenderer Function(); /// Signature for the callback that reports the custom renderer has been extended /// and set to the marker pointer -typedef MarkerPointerRendererFactory - = MarkerPointerRenderer Function(); +typedef MarkerPointerRendererFactory = + MarkerPointerRenderer Function(); /// Signature for the callback that report the custom renderer has been extended /// and set to the needle pointer -typedef NeedlePointerRendererFactory - = NeedlePointerRenderer Function(); +typedef NeedlePointerRendererFactory = + NeedlePointerRenderer Function(); diff --git a/packages/syncfusion_flutter_gauges/pubspec.yaml b/packages/syncfusion_flutter_gauges/pubspec.yaml index dcd716b7d..084354001 100644 --- a/packages/syncfusion_flutter_gauges/pubspec.yaml +++ b/packages/syncfusion_flutter_gauges/pubspec.yaml @@ -1,17 +1,21 @@ name: syncfusion_flutter_gauges 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: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_gauges environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.7.0 dependencies: flutter: sdk: flutter syncfusion_flutter_core: path: ../syncfusion_flutter_core - intl: ^0.17.0 + + + intl: '>=0.18.1 <0.21.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 a58a9d109..51b29bc68 100644 --- a/packages/syncfusion_flutter_maps/CHANGELOG.md +++ b/packages/syncfusion_flutter_maps/CHANGELOG.md @@ -1,5 +1,104 @@ ## Unreleased +**General** + +* The compatible version of our Flutter maps widget has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter maps widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter maps example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter maps example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter maps widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter maps widget has been updated to Flutter SDK 3.24.0. + +## [25.1.35] - 03/15/2024 + +**General** + +* Provided th​e Material 3 themes support. + +## [22.2.8] - 08/08/2023 + +**Bugs** + +* #FB45437 - Resolved the late error exception when assigning any value other than -1 to the selectedIndex property. + +## [22.2.7] - 08/02/2023 + +**General** + +* Upgraded the http package to the latest version 1.0.0. + +## [21.2.3] - 05/03/2023 + +**Bugs** + +* #FB42695 - Now, the map tile layer no longer jumps to distant locations when panning during fling zooming. + +## [20.4.50] - 02/14/2023 + +**Bugs** + +* #FB40437 - The issue with panning after the pinch zooming on the iPhone browser has been resolved. + +* The issue with the custom button zooming on the MapZoomPanBehavior not working when the enablePinching is set to false has been resolved. + +## [20.4.43] - 01/10/2023 + +**Feature** +* #FB39509 - The issue with the previous and new zoom levels of onWillZoom not updating properly while zooming through the toolbar has been resolved. + +## [20.3.47] - 09/29/2022 + +**Bug** +* #FB37232 – Now, the data labels get rendered when enabling it programmatically. + +## [20.2.36] - 07/01/2022 + +**Bug** +* The null exception will not be thrown when simultaneously calling the setstate and zooming the map. + +**Features** +* Provided an option to enable or disable the mouse wheel zoom functionality. + +## [20.1.56] - 05/17/2022 +**Bugs** +* Now, while performing mouse wheel zooming, the current segment color will be applied properly. + +## [19.4.38-beta] - 12/17/2021 + **Features** * Legend pointer - Provided an option to show a pointer on the solid bar legend while hover over the shape or bubble. diff --git a/packages/syncfusion_flutter_maps/LICENSE b/packages/syncfusion_flutter_maps/LICENSE index f9405acb6..b602eb8f5 100644 --- a/packages/syncfusion_flutter_maps/LICENSE +++ b/packages/syncfusion_flutter_maps/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Maps 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. +Syncfusion® Flutter Maps 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +The Syncfusion® license that contains the terms and conditions can be found at https://www.syncfusion.com/content/downloads/syncfusion_license.pdf diff --git a/packages/syncfusion_flutter_maps/README.md b/packages/syncfusion_flutter_maps/README.md index 1b815732c..e6d9b5bc1 100644 --- a/packages/syncfusion_flutter_maps/README.md +++ b/packages/syncfusion_flutter_maps/README.md @@ -8,7 +8,7 @@ The Flutter Maps package is a data visualization library for creating beautiful, 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 [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. +**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) @@ -21,7 +21,7 @@ Create a highly interactive and customizable Flutter Maps that has features set - [Mapping the data source](#mapping-the-data-source) - [Add maps elements](#add-maps-elements) - [Support and Feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Maps features @@ -109,23 +109,20 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

## Useful links -Take a look at the following to learn more about Syncfusion Flutter Maps: +Take a look at the following to learn more about Syncfusion® Flutter Maps: -* [Syncfusion Flutter Maps product page](https://www.syncfusion.com/flutter-widgets/maps) +* [Syncfusion® Flutter Maps product page](https://www.syncfusion.com/flutter-widgets/maps) * [User guide documentation for Maps](https://help.syncfusion.com/flutter/maps) * [Knowledge base](https://www.syncfusion.com/kb) @@ -340,11 +337,11 @@ The following screenshot illustrates the result of the above code sample. ## 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). +* For questions, reach out to our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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 +## 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. +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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_maps/analysis_options.yaml b/packages/syncfusion_flutter_maps/analysis_options.yaml index 2f547af5d..7bd9823d9 100644 --- a/packages/syncfusion_flutter_maps/analysis_options.yaml +++ b/packages/syncfusion_flutter_maps/analysis_options.yaml @@ -1,5 +1 @@ -include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore \ No newline at end of file +include: package:syncfusion_flutter_core/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_maps/example/README.md b/packages/syncfusion_flutter_maps/example/README.md new file mode 100644 index 000000000..c60140bd4 --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/README.md @@ -0,0 +1,16 @@ +# maps_sample + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_flutter_maps/example/android/.gitignore b/packages/syncfusion_flutter_maps/example/android/.gitignore index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_flutter_maps/example/android/.gitignore +++ b/packages/syncfusion_flutter_maps/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_maps/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_maps/example/android/app/build.gradle.kts new file mode 100644 index 000000000..5bdd15b63 --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.maps_sample" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.maps_sample" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_flutter_maps/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_maps/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_maps/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_maps/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_maps/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_maps/example/android/app/src/main/AndroidManifest.xml index 3f41384db..3c932e267 100644 --- a/packages/syncfusion_flutter_maps/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_maps/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_maps/example/android/app/src/main/kotlin/com/example/maps_sample/MainActivity.kt b/packages/syncfusion_flutter_maps/example/android/app/src/main/kotlin/com/example/maps_sample/MainActivity.kt new file mode 100644 index 000000000..5f785896c --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/android/app/src/main/kotlin/com/example/maps_sample/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.maps_sample + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_maps/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_maps/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_maps/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_maps/example/android/build.gradle.kts b/packages/syncfusion_flutter_maps/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_maps/example/android/gradle.properties b/packages/syncfusion_flutter_maps/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_flutter_maps/example/android/gradle.properties +++ b/packages/syncfusion_flutter_maps/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_maps/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_maps/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_flutter_maps/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_maps/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_maps/example/android/settings.gradle.kts b/packages/syncfusion_flutter_maps/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_maps/example/ios/.gitignore b/packages/syncfusion_flutter_maps/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_maps/example/ios/.gitignore +++ b/packages/syncfusion_flutter_maps/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_maps/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_maps/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf47..7c5696400 100644 --- a/packages/syncfusion_flutter_maps/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_maps/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 12.0 diff --git a/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.pbxproj index 1aec2aa07..f8d115234 100644 --- a/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -101,6 +126,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -117,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -127,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -145,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -171,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -185,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -200,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -211,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -235,6 +315,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -264,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -272,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -289,17 +371,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -307,10 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -340,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -354,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -366,6 +492,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -395,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -403,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -421,17 +550,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -448,17 +572,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -469,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -490,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_maps/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Maps Sample CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - example + maps_sample CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_maps/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_maps/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_maps/example/lib/main.dart b/packages/syncfusion_flutter_maps/example/lib/main.dart index 57c45af64..354e9c187 100644 --- a/packages/syncfusion_flutter_maps/example/lib/main.dart +++ b/packages/syncfusion_flutter_maps/example/lib/main.dart @@ -2,17 +2,16 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_maps/maps.dart'; void main() { - return runApp(MapsApp()); + return runApp(const MapsApp()); } /// This widget will be the root of application. class MapsApp extends StatelessWidget { + const MapsApp({super.key}); + @override Widget build(BuildContext context) { - return const MaterialApp( - title: 'Maps Demo', - home: MyHomePage(), - ); + return const MaterialApp(title: 'Maps Demo', home: MyHomePage()); } } @@ -34,18 +33,30 @@ class _MyHomePageState extends State { @override void initState() { _data = const [ - Model('New South Wales', Color.fromRGBO(255, 215, 0, 1.0), - ' New\nSouth Wales'), + Model( + 'New South Wales', + Color.fromRGBO(255, 215, 0, 1.0), + ' New\nSouth Wales', + ), Model('Queensland', Color.fromRGBO(72, 209, 204, 1.0), 'Queensland'), - Model('Northern Territory', Color.fromRGBO(255, 78, 66, 1.0), - 'Northern\nTerritory'), + Model( + 'Northern Territory', + Color.fromRGBO(255, 78, 66, 1.0), + 'Northern\nTerritory', + ), Model('Victoria', Color.fromRGBO(171, 56, 224, 0.75), 'Victoria'), - Model('South Australia', Color.fromRGBO(126, 247, 74, 0.75), - 'South Australia'), - Model('Western Australia', Color.fromRGBO(79, 60, 201, 0.7), - 'Western Australia'), + Model( + 'South Australia', + Color.fromRGBO(126, 247, 74, 0.75), + 'South Australia', + ), + Model( + 'Western Australia', + Color.fromRGBO(79, 60, 201, 0.7), + 'Western Australia', + ), Model('Tasmania', Color.fromRGBO(99, 164, 230, 1), 'Tasmania'), - Model('Australian Capital Territory', Colors.teal, 'ACT') + Model('Australian Capital Territory', Colors.teal, 'ACT'), ]; _mapSource = MapShapeSource.asset( @@ -72,9 +83,10 @@ class _MyHomePageState extends State { showDataLabels: true, legend: const MapLegend(MapElement.shape), tooltipSettings: MapTooltipSettings( - color: Colors.grey[700], - strokeColor: Colors.white, - strokeWidth: 2), + color: Colors.grey[700], + strokeColor: Colors.white, + strokeWidth: 2, + ), strokeColor: Colors.white, strokeWidth: 0.5, shapeTooltipBuilder: (BuildContext context, int index) { @@ -87,11 +99,12 @@ class _MyHomePageState extends State { ); }, dataLabelSettings: MapDataLabelSettings( - textStyle: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: - Theme.of(context).textTheme.caption!.fontSize)), + textStyle: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: Theme.of(context).textTheme.bodySmall!.fontSize, + ), + ), ), ], ), diff --git a/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_maps/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_maps/example/macos/.gitignore b/packages/syncfusion_flutter_maps/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_maps/example/macos/.gitignore +++ b/packages/syncfusion_flutter_maps/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..031a2438a 100644 --- a/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* maps_sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "maps_sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* maps_sample.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* maps_sample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/maps_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/maps_sample"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/maps_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/maps_sample"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/maps_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/maps_sample"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..94bd9bb35 100644 --- a/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_maps/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_maps/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_maps/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_maps/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_maps/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_maps/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_maps/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_maps/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_maps/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..248b64e02 100644 --- a/packages/syncfusion_flutter_maps/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_maps/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = maps_sample // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.mapsSample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_maps/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_maps/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_maps/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_maps/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_maps/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_maps/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_maps/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_maps/example/pubspec.yaml b/packages/syncfusion_flutter_maps/example/pubspec.yaml index ed578d7ed..e0089bb94 100644 --- a/packages/syncfusion_flutter_maps/example/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/example/pubspec.yaml @@ -1,80 +1,28 @@ name: maps_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" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.17.0" + intl: ^0.18.0 syncfusion_flutter_maps: - path: ../../syncfusion_flutter_maps/ + 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 + cupertino_icons: '>=0.1.3 <1.0.7' 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 assets: - - world_map.json - - australia.json - - # 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 + - assets/world_map.json + - assets/australia.json diff --git a/packages/syncfusion_flutter_maps/example/web/index.html b/packages/syncfusion_flutter_maps/example/web/index.html index 0081e1894..3d7a2f6da 100644 --- a/packages/syncfusion_flutter_maps/example/web/index.html +++ b/packages/syncfusion_flutter_maps/example/web/index.html @@ -27,72 +27,6 @@ - - + diff --git a/packages/syncfusion_flutter_maps/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_maps/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_maps/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_maps/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_maps/lib/maps.dart b/packages/syncfusion_flutter_maps/lib/maps.dart index 8b922002e..14692113b 100644 --- a/packages/syncfusion_flutter_maps/lib/maps.dart +++ b/packages/syncfusion_flutter_maps/lib/maps.dart @@ -101,7 +101,7 @@ Future getBingUrlTemplate(String url) async { if (key == 'resources') { final List resources = // ignore: avoid_as - (resourceSets[0])[key] as List; + resourceSets[0][key] as List; final Map resourcesMap = // ignore: avoid_as resources[0] as Map; @@ -267,10 +267,7 @@ Future _fetchResponse(String url) { /// instance of [MapZoomPanBehavior]. class SfMaps extends StatefulWidget { /// Creates a [SfMaps]. - const SfMaps({ - Key? key, - required this.layers, - }) : super(key: key); + const SfMaps({Key? key, required this.layers}) : super(key: key); /// The collection of map shape layer in which geographical rendering is done. /// @@ -326,8 +323,9 @@ class _SfMapsState extends State { /// Otherwise, returns the last layer. List _buildLayers(List layers) { if (layers.length > 1) { - final bool loadMultiTileLayer = - layers.every((MapLayer layer) => layer is MapTileLayer); + final bool loadMultiTileLayer = layers.every( + (MapLayer layer) => layer is MapTileLayer, + ); return loadMultiTileLayer ? layers : [layers.last]; } else { return layers; @@ -337,7 +335,7 @@ class _SfMapsState extends State { class _MapsRenderObjectWidget extends SingleChildRenderObjectWidget { const _MapsRenderObjectWidget({Key? key, required Widget child}) - : super(key: key, child: child); + : super(key: key, child: child); @override _RenderMaps createRenderObject(BuildContext context) { @@ -353,8 +351,10 @@ class _RenderMaps extends RenderProxyBox { final double height = constraints.hasBoundedHeight ? constraints.maxHeight : 300; if (child != null) { - child!.layout(BoxConstraints.loose(Size(width, height)), - parentUsesSize: true); + child!.layout( + BoxConstraints.loose(Size(width, height)), + parentUsesSize: true, + ); size = child!.size; } } @@ -923,19 +923,35 @@ class MapShapeSublayer extends MapSublayer { 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', + 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)); } @@ -949,11 +965,13 @@ class MapShapeSublayer extends MapSublayer { } properties.add(IntProperty('selectedIndex', selectedIndex)); - properties - .add(dataLabelSettings.toDiagnosticsNode(name: 'dataLabelSettings')); + properties.add( + dataLabelSettings.toDiagnosticsNode(name: 'dataLabelSettings'), + ); properties.add(bubbleSettings.toDiagnosticsNode(name: 'bubbleSettings')); - properties - .add(selectionSettings.toDiagnosticsNode(name: 'selectionSettings')); + properties.add( + selectionSettings.toDiagnosticsNode(name: 'selectionSettings'), + ); } } @@ -1041,17 +1059,17 @@ class MapShapeLayer extends MapLayer { WillZoomCallback? onWillZoom, WillPanCallback? onWillPan, }) : super( - key: key, - initialLatLngBounds: initialLatLngBounds, - sublayers: sublayers, - initialMarkersCount: initialMarkersCount, - markerBuilder: markerBuilder, - markerTooltipBuilder: markerTooltipBuilder, - tooltipSettings: tooltipSettings, - zoomPanBehavior: zoomPanBehavior, - onWillZoom: onWillZoom, - onWillPan: onWillPan, - ); + key: key, + initialLatLngBounds: initialLatLngBounds, + sublayers: sublayers, + initialMarkersCount: initialMarkersCount, + markerBuilder: markerBuilder, + markerTooltipBuilder: markerTooltipBuilder, + tooltipSettings: tooltipSettings, + zoomPanBehavior: zoomPanBehavior, + onWillZoom: onWillZoom, + onWillPan: onWillPan, + ); /// The source that maps the data source with the shape file and provides /// data for the elements of the this layer like data labels, bubbles, @@ -1645,11 +1663,19 @@ class MapShapeLayer extends MapLayer { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(source.toDiagnosticsNode(name: 'source')); - properties.add(ObjectFlagProperty.has( - 'loadingBuilder', loadingBuilder)); + properties.add( + ObjectFlagProperty.has( + 'loadingBuilder', + loadingBuilder, + ), + ); if (initialLatLngBounds != null) { - properties.add(DiagnosticsProperty( - 'initialLatLngBounds', initialLatLngBounds)); + properties.add( + DiagnosticsProperty( + 'initialLatLngBounds', + initialLatLngBounds, + ), + ); } if (controller != null) { properties.add(IntProperty('markersCount', controller!.markersCount)); @@ -1660,19 +1686,35 @@ class MapShapeLayer extends MapLayer { 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', + 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)); } @@ -1686,23 +1728,28 @@ class MapShapeLayer extends MapLayer { } properties.add(IntProperty('selectedIndex', selectedIndex)); - properties - .add(dataLabelSettings.toDiagnosticsNode(name: 'dataLabelSettings')); + properties.add( + dataLabelSettings.toDiagnosticsNode(name: 'dataLabelSettings'), + ); if (legend != null) { properties.add(legend!.toDiagnosticsNode(name: 'legend')); } properties.add(bubbleSettings.toDiagnosticsNode(name: 'bubbleSettings')); - properties - .add(selectionSettings.toDiagnosticsNode(name: 'selectionSettings')); + properties.add( + selectionSettings.toDiagnosticsNode(name: 'selectionSettings'), + ); properties.add(tooltipSettings.toDiagnosticsNode(name: 'tooltipSettings')); if (zoomPanBehavior != null) { - properties - .add(zoomPanBehavior!.toDiagnosticsNode(name: 'zoomPanBehavior')); + properties.add( + zoomPanBehavior!.toDiagnosticsNode(name: 'zoomPanBehavior'), + ); } properties.add( - ObjectFlagProperty.has('onWillZoom', onWillZoom)); - properties - .add(ObjectFlagProperty.has('onWillPan', onWillPan)); + ObjectFlagProperty.has('onWillZoom', onWillZoom), + ); + properties.add( + ObjectFlagProperty.has('onWillPan', onWillPan), + ); } } @@ -1768,9 +1815,10 @@ class _ShapeLayerState extends State<_ShapeLayer> { @override void initState() { - _controller = MapController() - ..tooltipKey = GlobalKey() - ..layerType = LayerType.shape; + _controller = + MapController() + ..tooltipKey = GlobalKey() + ..layerType = LayerType.shape; super.initState(); } @@ -1884,21 +1932,23 @@ class MapTileLayer extends MapLayer { MapZoomPanBehavior? zoomPanBehavior, WillZoomCallback? onWillZoom, WillPanCallback? onWillPan, - }) : assert(initialZoomLevel >= 1 && initialZoomLevel <= 15), - assert(initialMarkersCount == 0 || - initialMarkersCount != 0 && markerBuilder != null), - super( - key: key, - initialLatLngBounds: initialLatLngBounds, - sublayers: sublayers, - initialMarkersCount: initialMarkersCount, - markerBuilder: markerBuilder, - markerTooltipBuilder: markerTooltipBuilder, - tooltipSettings: tooltipSettings, - zoomPanBehavior: zoomPanBehavior, - onWillZoom: onWillZoom, - onWillPan: onWillPan, - ); + }) : assert(initialZoomLevel >= 1 && initialZoomLevel <= 15), + assert( + initialMarkersCount == 0 || + initialMarkersCount != 0 && markerBuilder != null, + ), + super( + key: key, + initialLatLngBounds: initialLatLngBounds, + sublayers: sublayers, + initialMarkersCount: initialMarkersCount, + markerBuilder: markerBuilder, + markerTooltipBuilder: markerTooltipBuilder, + tooltipSettings: tooltipSettings, + zoomPanBehavior: zoomPanBehavior, + onWillZoom: onWillZoom, + onWillPan: onWillPan, + ); /// URL template to request the tiles from the providers. /// @@ -2127,32 +2177,41 @@ class MapTileLayer extends MapLayer { super.debugFillProperties(properties); properties.add(StringProperty('urlTemplate', urlTemplate)); - properties.add(DiagnosticsProperty( - 'initialFocalLatLng', initialFocalLatLng)); + properties.add( + DiagnosticsProperty('initialFocalLatLng', initialFocalLatLng), + ); properties.add(IntProperty('initialZoomLevel', initialZoomLevel)); if (initialLatLngBounds != null) { - properties.add(DiagnosticsProperty( - 'initialLatLngBounds', initialLatLngBounds)); + properties.add( + DiagnosticsProperty( + 'initialLatLngBounds', + initialLatLngBounds, + ), + ); } if (sublayers != null && sublayers!.isNotEmpty) { final DebugSublayerTree pointerTreeNode = DebugSublayerTree(sublayers!); properties.add(pointerTreeNode.toDiagnosticsNode()); } if (zoomPanBehavior != null) { - properties - .add(zoomPanBehavior!.toDiagnosticsNode(name: 'zoomPanBehavior')); + properties.add( + zoomPanBehavior!.toDiagnosticsNode(name: 'zoomPanBehavior'), + ); } - properties.add(ObjectFlagProperty.has( - 'markerBuilder', markerBuilder)); + properties.add( + ObjectFlagProperty.has('markerBuilder', markerBuilder), + ); if (controller != null) { properties.add(IntProperty('markersCount', controller!.markersCount)); } else { properties.add(IntProperty('markersCount', initialMarkersCount)); } properties.add( - ObjectFlagProperty.has('onWillZoom', onWillZoom)); - properties - .add(ObjectFlagProperty.has('onWillPan', onWillPan)); + ObjectFlagProperty.has('onWillZoom', onWillZoom), + ); + properties.add( + ObjectFlagProperty.has('onWillPan', onWillPan), + ); } } @@ -2198,10 +2257,11 @@ class _TileLayerState extends State<_TileLayer> { @override void initState() { - _controller = MapController() - ..tooltipKey = GlobalKey() - ..layerType = LayerType.tile - ..tileCurrentLevelDetails = TileZoomLevelDetails(); + _controller = + MapController() + ..tooltipKey = GlobalKey() + ..layerType = LayerType.tile + ..tileCurrentLevelDetails = TileZoomLevelDetails(); if (widget.zoomPanBehavior != null && widget.zoomPanBehavior!._zoomController == null) { widget.zoomPanBehavior!._zoomController = ZoomableController(); @@ -2341,19 +2401,21 @@ class MapZoomPanBehavior extends MapBehavior { bool enablePinching = true, bool enablePanning = true, bool enableDoubleTapZooming = false, + bool enableMouseWheelZooming = false, bool showToolbar = true, MapToolbarSettings toolbarSettings = const MapToolbarSettings(), - }) : _zoomLevel = zoomLevel.clamp(minZoomLevel, maxZoomLevel), - _focalLatLng = focalLatLng, - _latLngBounds = latLngBounds, - _minZoomLevel = minZoomLevel, - _maxZoomLevel = maxZoomLevel, - _enablePinching = enablePinching, - _enablePanning = enablePanning, - _enableDoubleTapZooming = enableDoubleTapZooming, - _showToolbar = showToolbar, - _toolbarSettings = toolbarSettings, - _zoomController = ZoomableController(); + }) : _zoomLevel = zoomLevel.clamp(minZoomLevel, maxZoomLevel), + _focalLatLng = focalLatLng, + _latLngBounds = latLngBounds, + _minZoomLevel = minZoomLevel, + _maxZoomLevel = maxZoomLevel, + _enablePinching = enablePinching, + _enablePanning = enablePanning, + _enableDoubleTapZooming = enableDoubleTapZooming, + _enableMouseWheelZooming = enableMouseWheelZooming, + _showToolbar = showToolbar, + _toolbarSettings = toolbarSettings, + _zoomController = ZoomableController(); /// Current zoom level of the map layer. /// @@ -2432,9 +2494,12 @@ class MapZoomPanBehavior extends MapBehavior { _latLngBounds = value; if (_controller != null) { - zoomLevel = getZoomLevel(_latLngBounds!, _controller!.layerType!, - renderBox.size, _controller!.shapeLayerSizeFactor / zoomLevel) - .clamp(minZoomLevel, maxZoomLevel); + zoomLevel = getZoomLevel( + _latLngBounds!, + _controller!.layerType!, + renderBox.size, + _controller!.shapeLayerSizeFactor / zoomLevel, + ).clamp(minZoomLevel, maxZoomLevel); focalLatLng = getFocalLatLng(_latLngBounds!); } } @@ -2509,6 +2574,18 @@ class MapZoomPanBehavior extends MapBehavior { _enableDoubleTapZooming = value; } + /// Enables mouse wheel zooming in map + /// + /// Defaults to `false`. + bool get enableMouseWheelZooming => _enableMouseWheelZooming; + bool _enableMouseWheelZooming; + set enableMouseWheelZooming(bool value) { + if (_enableMouseWheelZooming == value) { + return; + } + _enableMouseWheelZooming = value; + } + /// Shows zooming toolbar in the web platform. /// /// Defaults to `true` in web platform. @@ -2636,16 +2713,20 @@ class MapZoomPanBehavior extends MapBehavior { // Converts the current focalLatLng into a offset and validates it with // zoom controller actual rect to perform animation. void _updateZoomControllerActualRect() { - final Offset center = pixelFromLatLng(_focalLatLng!.latitude, - _focalLatLng!.longitude, Size.square(getTotalTileWidth(_zoomLevel))); + final Offset center = pixelFromLatLng( + _focalLatLng!.latitude, + _focalLatLng!.longitude, + Size.square(getTotalTileWidth(_zoomLevel)), + ); final Offset newOffset = Offset( - _zoomController!.parentRect!.width / 2 - center.dx, - _zoomController!.parentRect!.height / 2 - center.dy); + _zoomController!.parentRect!.width / 2 - center.dx, + _zoomController!.parentRect!.height / 2 - center.dy, + ); final Offset delta = newOffset - _zoomController!.actualRect.topLeft; if (delta != Offset.zero) { _zoomController!.actualRect = (_zoomController!.actualRect.topLeft + delta) & - _zoomController!.actualRect.size; + _zoomController!.actualRect.size; return; } } @@ -2657,32 +2738,51 @@ class MapZoomPanBehavior extends MapBehavior { properties.add(DoubleProperty('zoomLevel', zoomLevel)); properties.add(DoubleProperty('minZoomLevel', minZoomLevel)); properties.add(DoubleProperty('maxZoomLevel', maxZoomLevel)); - properties.add(FlagProperty('enablePanning', + properties.add( + FlagProperty( + 'enablePanning', value: enablePanning, ifTrue: 'Panning is enabled', ifFalse: 'Panning is disabled', - showName: false)); - properties.add(FlagProperty('enablePinching', + ), + ); + properties.add( + FlagProperty( + 'enablePinching', value: enablePinching, ifTrue: 'Pinching is enabled', ifFalse: 'Pinching is disabled', - showName: false)); - properties.add(FlagProperty('enableDoubleTapZooming', + ), + ); + properties.add( + FlagProperty( + 'enableDoubleTapZooming', value: enableDoubleTapZooming, ifTrue: 'Double tap is enabled', ifFalse: 'Double tap is disabled', - showName: false)); + ), + ); + properties.add( + FlagProperty( + 'enableMouseWheelZooming', + value: enableMouseWheelZooming, + ifTrue: 'Mouse wheel is enabled', + ifFalse: 'Mouse wheel is disabled', + ), + ); properties.add(DiagnosticsProperty('focalLatLng', focalLatLng)); properties.add( - DiagnosticsProperty('latLngBounds', latLngBounds)); - properties.add(FlagProperty('showToolbar', + DiagnosticsProperty('latLngBounds', latLngBounds), + ); + properties.add( + FlagProperty( + 'showToolbar', value: showToolbar, ifTrue: 'Toolbar is enabled', ifFalse: 'Toolbar is disabled', - showName: false)); - properties.add( - toolbarSettings.toDiagnosticsNode(name: 'toolbarSettings'), + ), ); + properties.add(toolbarSettings.toDiagnosticsNode(name: 'toolbarSettings')); } } @@ -2690,10 +2790,7 @@ class MapZoomPanBehavior extends MapBehavior { @immutable class MapLatLng { /// Creates a [MapLatLng]. - const MapLatLng( - this.latitude, - this.longitude, - ); + const MapLatLng(this.latitude, this.longitude); /// The latitude in the maps. final double latitude; @@ -2723,16 +2820,17 @@ class MapLatLng { return null; } - return MapLatLng(a.latitude + (b.latitude - a.latitude) * t, - a.longitude + (b.longitude - a.longitude) * t); + return MapLatLng( + a.latitude + (b.latitude - a.latitude) * t, + a.longitude + (b.longitude - a.longitude) * t, + ); } @override - int get hashCode => hashValues(latitude, longitude); + int get hashCode => Object.hash(latitude, longitude); @override - String toString() => - 'MapLatLng(${latitude.toString()}, ${longitude.toString()})'; + String toString() => 'MapLatLng($latitude, $longitude)'; } /// Bounds of the maps. @@ -2762,11 +2860,10 @@ class MapLatLngBounds { } @override - int get hashCode => hashValues(northeast, southwest); + int get hashCode => Object.hash(northeast, southwest); @override - String toString() => - 'MapLatLngBounds(${northeast.toString()}, ${southwest.toString()})'; + String toString() => 'MapLatLngBounds($northeast, $southwest)'; } /// Contains details about the current zoom position. @@ -2861,6 +2958,7 @@ class BehaviorView extends StatefulWidget { required this.controller, required this.behavior, required this.onWillZoom, + this.enableMouseWheelZooming, required this.onWillPan, }) : super(key: key); @@ -2882,6 +2980,22 @@ class BehaviorView extends StatefulWidget { /// Called whenever panning is happening. final WillPanCallback? onWillPan; + /// Enables mouse wheel zooming in map + /// + /// Defaults to `false` in web platform. + /// + /// ```dart + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// zoomPanBehavior: MapZoomPanBehavior( + /// enableMouseWheelZooming : true ), + /// ), + /// ] + /// ), + ///``` + final bool? enableMouseWheelZooming; + @override State createState() => _BehaviorViewState(); } @@ -2890,22 +3004,29 @@ class _BehaviorViewState extends State { Size? _size; late double _currentZoomLevel; late MapLatLng _currentFocalLatLng; - Rect? _initialRect; + late Rect? _initialRect; Offset _pixelFromLatLng(MapLatLng latLng, double scale) { - final double latitude = - latLng.latitude.clamp(minimumLatitude, maximumLatitude); - final double longitude = - latLng.longitude.clamp(minimumLongitude, maximumLongitude); + final double latitude = latLng.latitude.clamp( + minimumLatitude, + maximumLatitude, + ); + final double longitude = latLng.longitude.clamp( + minimumLongitude, + maximumLongitude, + ); return pixelFromLatLng( - latitude, longitude, Size.square(getTotalTileWidth(scale))); + latitude, + longitude, + Size.square(getTotalTileWidth(scale)), + ); } MapLatLng _pixelToLatLng(Offset point, double scale) { return pixelToLatLng(point, Size.square(getTotalTileWidth(scale))); } - void _invokeOnZooming({ + bool _invokeOnZooming({ required Offset localFocalPoint, required Offset globalFocalPoint, required double newZoomLevel, @@ -2914,17 +3035,23 @@ class _BehaviorViewState extends State { required Offset? pinchCenter, }) { newZoomLevel = newZoomLevel.clamp( - widget.behavior.minZoomLevel, widget.behavior.maxZoomLevel); + widget.behavior.minZoomLevel, + widget.behavior.maxZoomLevel, + ); final Rect previousVisibleBounds = Rect.fromCenter( center: _pixelFromLatLng( - widget.behavior.focalLatLng!, widget.behavior.zoomLevel), + widget.behavior.focalLatLng!, + widget.behavior.zoomLevel, + ), width: _size!.width, height: _size!.height, ); final MapLatLngBounds previousVisibleLatLngBounds = MapLatLngBounds( _pixelToLatLng(previousVisibleBounds.topRight, widget.behavior.zoomLevel), _pixelToLatLng( - previousVisibleBounds.bottomLeft, widget.behavior.zoomLevel), + previousVisibleBounds.bottomLeft, + widget.behavior.zoomLevel, + ), ); widget.controller ..localScale = scale @@ -2949,12 +3076,67 @@ class _BehaviorViewState extends State { newVisibleBounds: newVisibleLatLngBounds, focalLatLng: newFocalLatLng, ); + if (widget.onWillZoom == null || widget.onWillZoom!(zoomDetails)) { - widget.behavior.onZooming(zoomDetails); + if (widget.behavior._zoomController!.actionType != + ActionType.pinchFling || + widget.behavior._zoomController!.actionType != ActionType.panFling) { + widget.behavior.onZooming(zoomDetails); + } + return true; } + + return false; } - void _invokeOnPanning({ + void _handleToolbarZooming(double newZoomLevel) { + newZoomLevel = newZoomLevel.clamp( + widget.behavior.minZoomLevel, + widget.behavior.maxZoomLevel, + ); + final Rect previousVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng( + widget.behavior.focalLatLng!, + widget.behavior.zoomLevel, + ), + width: _size!.width, + height: _size!.height, + ); + final MapLatLngBounds previousVisibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(previousVisibleBounds.topRight, widget.behavior.zoomLevel), + _pixelToLatLng( + previousVisibleBounds.bottomLeft, + widget.behavior.zoomLevel, + ), + ); + + final Rect newVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng(widget.behavior.focalLatLng!, newZoomLevel), + width: _size!.width, + height: _size!.height, + ); + final MapLatLngBounds newVisibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel), + ); + final Offset localFocalPoint = newVisibleBounds.center; + final RenderBox renderBox = context.findRenderObject()! as RenderBox; + final Offset globalFocalPoint = renderBox.localToGlobal(localFocalPoint); + final MapZoomDetails zoomDetails = MapZoomDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: widget.behavior.zoomLevel, + newZoomLevel: newZoomLevel, + previousVisibleBounds: previousVisibleLatLngBounds, + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: widget.behavior.focalLatLng, + ); + if (widget.onWillZoom == null || widget.onWillZoom!(zoomDetails)) { + widget.behavior.zoomLevel = zoomDetails.newZoomLevel!; + } + } + + bool _invokeOnPanning({ required Offset localFocalPoint, required Offset globalFocalPoint, required double newZoomLevel, @@ -2963,14 +3145,18 @@ class _BehaviorViewState extends State { }) { final Rect previousVisibleBounds = Rect.fromCenter( center: _pixelFromLatLng( - widget.behavior.focalLatLng!, widget.behavior.zoomLevel), + widget.behavior.focalLatLng!, + widget.behavior.zoomLevel, + ), width: _size!.width, height: _size!.height, ); final MapLatLngBounds previousVisibleLatLngBounds = MapLatLngBounds( _pixelToLatLng(previousVisibleBounds.topRight, widget.behavior.zoomLevel), _pixelToLatLng( - previousVisibleBounds.bottomLeft, widget.behavior.zoomLevel), + previousVisibleBounds.bottomLeft, + widget.behavior.zoomLevel, + ), ); final Rect newVisibleBounds = Rect.fromCenter( @@ -2994,16 +3180,26 @@ class _BehaviorViewState extends State { ); widget.controller.visibleLatLngBounds = panDetails.newVisibleBounds; if (widget.onWillPan == null || widget.onWillPan!(panDetails)) { - widget.behavior.onPanning(panDetails); + if (widget.behavior._zoomController!.actionType != + ActionType.pinchFling || + widget.behavior._zoomController!.actionType != ActionType.panFling) { + widget.behavior.onPanning(panDetails); + } + return true; } + + return false; } void _handleZoomableChange(ZoomPanDetails details) { final Offset focalPoint = Offset( - (_size!.width / 2) - details.actualRect.left, - (_size!.height / 2) - details.actualRect.top); + (_size!.width / 2) - details.actualRect.left, + (_size!.height / 2) - details.actualRect.top, + ); final MapLatLng newFocalLatLng = pixelToLatLng( - focalPoint, Size.square(getTotalTileWidth(details.newZoomLevel))); + focalPoint, + Size.square(getTotalTileWidth(details.newZoomLevel)), + ); _currentZoomLevel = details.newZoomLevel; _currentFocalLatLng = newFocalLatLng; @@ -3038,14 +3234,55 @@ class _BehaviorViewState extends State { ..notifyRefreshListeners(); } + bool _handleZoomableFling(ZoomPanDetails details) { + final Offset focalPoint = Offset( + (_size!.width / 2) - details.actualRect.left, + (_size!.height / 2) - details.actualRect.top, + ); + final MapLatLng newFocalLatLng = pixelToLatLng( + focalPoint, + Size.square(getTotalTileWidth(details.newZoomLevel)), + ); + _currentZoomLevel = details.newZoomLevel; + _currentFocalLatLng = newFocalLatLng; + bool canFling = false; + + if ((widget.behavior._zoomController!.actionType == + ActionType.pinchFling) && + _currentZoomLevel != details.previousZoomLevel) { + canFling = _invokeOnZooming( + localFocalPoint: details.localFocalPoint, + globalFocalPoint: details.globalFocalPoint, + newZoomLevel: _currentZoomLevel, + newFocalLatLng: newFocalLatLng, + scale: details.scale, + pinchCenter: details.pinchCenter, + ); + } else if (widget.behavior._zoomController!.actionType == + ActionType.panFling && + details.previousRect != details.actualRect) { + canFling = _invokeOnPanning( + localFocalPoint: details.localFocalPoint, + globalFocalPoint: details.globalFocalPoint, + newZoomLevel: _currentZoomLevel, + newFocalLatLng: newFocalLatLng, + delta: details.actualRect.topLeft - details.previousRect.topLeft, + ); + } + + return canFling; + } + Rect _getInitialRect() { widget.behavior._zoomController!.parentRect = Offset.zero & _size!; final double tileSize = getTotalTileWidth(_currentZoomLevel); - final Offset center = - _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); + final Offset center = _pixelFromLatLng( + _currentFocalLatLng, + _currentZoomLevel, + ); final Rect initialRect = Offset(_size!.width / 2 - center.dx, _size!.height / 2 - center.dy) & - Size.square(tileSize); + Size.square(tileSize); return initialRect; } @@ -3055,11 +3292,13 @@ class _BehaviorViewState extends State { _currentZoomLevel = widget.zoomLevel; widget.behavior._zoomLevel = _currentZoomLevel; widget.behavior._focalLatLng = _currentFocalLatLng; + widget.controller.addToolbarZoomedListener(_handleToolbarZooming); super.initState(); } @override void dispose() { + widget.controller.removeToolbarZoomedListener(_handleToolbarZooming); widget.behavior .._zoomController?.dispose() .._zoomController = null; @@ -3090,8 +3329,10 @@ class _BehaviorViewState extends State { enablePinching: widget.behavior.enablePinching, enablePanning: widget.behavior.enablePanning, enableDoubleTapZooming: widget.behavior.enableDoubleTapZooming, + enableMouseWheelZooming: widget.behavior.enableMouseWheelZooming, onUpdate: _handleZoomableChange, onComplete: _handleZoomableEnd, + onFling: _handleZoomableFling, frictionCoefficient: 0.009, child: BehaviorViewRenderObjectWidget( controller: widget.controller, @@ -3129,9 +3370,10 @@ class BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderBehaviorView renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderBehaviorView renderObject, + ) { renderObject ..controller = controller ..zoomPanBehavior = zoomPanBehavior; @@ -3142,8 +3384,8 @@ class _RenderBehaviorView extends RenderBox implements MouseTrackerAnnotation { _RenderBehaviorView({ required MapController listener, required MapZoomPanBehavior zoomPanBehavior, - }) : controller = listener, - _zoomPanBehavior = zoomPanBehavior { + }) : controller = listener, + _zoomPanBehavior = zoomPanBehavior { _zoomPanBehavior._renderBox = this; _zoomPanBehavior._controller = controller; } @@ -3189,9 +3431,10 @@ class _RenderBehaviorView extends RenderBox implements MouseTrackerAnnotation { } @override - MouseCursor get cursor => controller.gesture == Gesture.pan - ? SystemMouseCursors.grabbing - : SystemMouseCursors.basic; + MouseCursor get cursor => + controller.gesture == Gesture.pan + ? SystemMouseCursors.grabbing + : SystemMouseCursors.basic; @override PointerEnterEventListener? get onEnter => _handleEnter; diff --git a/packages/syncfusion_flutter_maps/lib/src/common.dart b/packages/syncfusion_flutter_maps/lib/src/common.dart index 9407aa109..2c3bb84f8 100644 --- a/packages/syncfusion_flutter_maps/lib/src/common.dart +++ b/packages/syncfusion_flutter_maps/lib/src/common.dart @@ -38,7 +38,7 @@ enum PointerKind { touch, /// Indicates the pointer kind as hover. - hover + hover, } /// Provides the information about the current and previous zoom level. @@ -165,12 +165,13 @@ class MapModel { /// 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.zoomController, - this.sublayers}) - : super(child: child); + const MapLayerInheritedWidget({ + super.key, + required Widget child, + required this.controller, + this.zoomController, + this.sublayers, + }) : super(child: child); /// Creates [MapController]. final MapController controller; @@ -191,7 +192,8 @@ class MapLayerInheritedWidget extends InheritedWidget { /// Adds [MapSublayer] into a stack widget. class SublayerContainer extends Stack { /// Creates a [SublayerContainer]. - SublayerContainer({ + const SublayerContainer({ + super.key, required this.ancestor, required List children, }) : super(children: children); @@ -207,10 +209,8 @@ class SublayerContainer extends Stack { /// 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)); + RenderSublayerContainer({required this.container, required this.context}) + : super(textDirection: Directionality.of(context)); /// The build context. final BuildContext context; @@ -236,9 +236,14 @@ class ShapeLayerChildRenderBoxBase extends RenderProxyBox { // Handle hover interaction here. } - void paintTooltip(int? elementIndex, Rect? elementRect, - MapLayerElement? element, PointerKind kind, - [int? sublayerIndex, Offset? position]) {} + void paintTooltip( + int? elementIndex, + Rect? elementRect, + MapLayerElement? element, + PointerKind kind, [ + int? sublayerIndex, + Offset? position, + ]) {} void onExit() { // Handle exit interaction here. @@ -276,18 +281,24 @@ class MapIconShape { iconSize = getPreferredSize(iconSize, themeData); final double halfIconWidth = iconSize.width / 2; final double halfIconHeight = iconSize.height / 2; - final bool hasStroke = strokeWidth > 0 && + final bool hasStroke = + strokeWidth > 0 && strokeColor != null && strokeColor != Colors.transparent; - final Paint paint = Paint() - ..isAntiAlias = true - ..color = color; + 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); + offset.dx, + offset.dy, + iconSize.width, + iconSize.height, + ); context.canvas.drawOval(rect, paint); if (hasStroke) { paint @@ -299,7 +310,11 @@ class MapIconShape { break; case MapIconType.rectangle: final Rect rect = Rect.fromLTWH( - offset.dx, offset.dy, iconSize.width, iconSize.height); + offset.dx, + offset.dy, + iconSize.width, + iconSize.height, + ); context.canvas.drawRect(rect, paint); if (hasStroke) { paint @@ -310,11 +325,12 @@ class MapIconShape { } 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(); + 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 @@ -325,12 +341,13 @@ class MapIconShape { } 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(); + 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 diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart index d13e2db58..2d6ffbb41 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart @@ -2,8 +2,8 @@ import 'package:flutter/foundation.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 { diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart index 83a855f3b..0c1ecc605 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart @@ -28,6 +28,8 @@ class MapController { ObserverList? _resetListeners = ObserverList(); ObserverList? _toggledListeners = ObserverList(); ObserverList? _zoomPanListeners = ObserverList(); + ObserverList? _toolbarZoomedListeners = + ObserverList(); double shapeLayerSizeFactor = 1.0; double localScale = 1.0; @@ -110,6 +112,14 @@ class MapController { _zoomingListeners?.remove(listener); } + void addToolbarZoomedListener(ZoomToCallback listener) { + _toolbarZoomedListeners?.add(listener); + } + + void removeToolbarZoomedListener(ZoomToCallback listener) { + _toolbarZoomedListeners?.remove(listener); + } + void addPanningListener(PanningCallback listener) { _panningListeners?.add(listener); } @@ -148,6 +158,12 @@ class MapController { } } + void notifyToolbarZoomedListeners(double newZoomLevel) { + for (final ZoomToCallback listener in _toolbarZoomedListeners!) { + listener(newZoomLevel); + } + } + void notifyPanningListeners(MapPanDetails details) { for (final PanningCallback listener in _panningListeners!) { listener(details); @@ -177,47 +193,65 @@ class MapController { 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]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; return pixelToLatLng( - Offset(shapeLayerBoxSize.width / 2, shapeLayerBoxSize.height / 2), - shapeLayerBoxSize, - translation, - factor); + Offset(shapeLayerBoxSize.width / 2, shapeLayerBoxSize.height / 2), + shapeLayerBoxSize, + translation, + factor, + ); } - Rect getVisibleBounds( - [Offset? translation, double? factor, MapLatLng? focalLatLng]) { + Rect getVisibleBounds([ + Offset? translation, + double? factor, + MapLatLng? focalLatLng, + ]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; focalLatLng ??= getVisibleFocalLatLng(translation, factor); return Rect.fromCenter( - center: pixelFromLatLng( - visibleFocalLatLng!.latitude, - visibleFocalLatLng!.longitude, - shapeLayerBoxSize, - translation, - factor), - width: shapeLayerBoxSize.width, - height: shapeLayerBoxSize.height); - } - - MapLatLngBounds getVisibleLatLngBounds(Offset topRight, Offset bottomLeft, - [Offset? translation, double? factor]) { + center: pixelFromLatLng( + visibleFocalLatLng!.latitude, + visibleFocalLatLng!.longitude, + shapeLayerBoxSize, + translation, + factor, + ), + width: shapeLayerBoxSize.width, + height: shapeLayerBoxSize.height, + ); + } + + MapLatLngBounds getVisibleLatLngBounds( + Offset topRight, + Offset bottomLeft, [ + Offset? translation, + double? factor, + ]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; return MapLatLngBounds( - pixelToLatLng(topRight, shapeLayerBoxSize, translation, factor), - pixelToLatLng(bottomLeft, shapeLayerBoxSize, translation, factor)); + pixelToLatLng(topRight, shapeLayerBoxSize, translation, factor), + pixelToLatLng(bottomLeft, shapeLayerBoxSize, translation, factor), + ); } - Offset getZoomingTranslation( - {Offset? origin, double? scale, Offset? previousOrigin}) { + Offset getZoomingTranslation({ + Offset? origin, + double? scale, + Offset? previousOrigin, + }) { origin ??= pinchCenter; scale ??= localScale; previousOrigin ??= shapeLayerOffset; @@ -235,12 +269,17 @@ class MapController { return Size.square(256 * pow(2, zoomLevel).toDouble()); } - void applyTransform(PaintingContext context, Offset offset, - [bool isVectorLayer = false]) { + void applyTransform( + PaintingContext context, + Offset offset, [ + bool isVectorLayer = false, + ]) { if (isVectorLayer && layerType == LayerType.tile) { context.canvas - ..translate(offset.dx + tileCurrentLevelDetails.translatePoint.dx, - offset.dy + tileCurrentLevelDetails.translatePoint.dy) + ..translate( + offset.dx + tileCurrentLevelDetails.translatePoint.dx, + offset.dy + tileCurrentLevelDetails.translatePoint.dy, + ) ..scale(tileCurrentLevelDetails.scale); } else { switch (gesture) { @@ -248,19 +287,26 @@ class MapController { // Translating to the focal point // which we got from [ScaleUpdateDetails.localFocalPoint]. context.canvas - ..translate(offset.dx + pinchCenter.dx + normalize.dx, - offset.dy + pinchCenter.dy + normalize.dy) + ..translate( + offset.dx + pinchCenter.dx + normalize.dx, + offset.dy + pinchCenter.dy + normalize.dy, + ) ..scale(localScale) // Moving back to the original position to draw shapes. ..translate(-pinchCenter.dx, -pinchCenter.dy); break; case Gesture.pan: context.canvas.translate( - offset.dx + panDistance.dx, offset.dy + panDistance.dy); + offset.dx + panDistance.dx, + offset.dy + panDistance.dy, + ); break; + // ignore: no_default_cases default: - context.canvas - .translate(offset.dx + normalize.dx, offset.dy + normalize.dy); + context.canvas.translate( + offset.dx + normalize.dx, + offset.dy + normalize.dy, + ); } } } @@ -273,5 +319,6 @@ class MapController { _panningListeners = null; _resetListeners = null; _refreshListeners = null; + _toolbarZoomedListeners = null; } } diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart b/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart index 5703c8e6f..bddf46c3d 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart @@ -62,7 +62,7 @@ class AssetMapProvider extends MapProvider { } @override - int get hashCode => hashValues(shapePath, bytes); + int get hashCode => Object.hash(shapePath, bytes); } // Decodes the given map URL from the network. @@ -114,7 +114,7 @@ class NetworkMapProvider extends MapProvider { } @override - int get hashCode => hashValues(shapePath, bytes); + int get hashCode => Object.hash(shapePath, bytes); } /// Decodes the given [Uint8List] buffer as an map. @@ -161,5 +161,5 @@ class MemoryMapProvider extends MapProvider { } @override - int get hashCode => hashValues(shapePath, bytes); + int get hashCode => Object.hash(shapePath, bytes); } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart index de5ed6da4..31d15da0c 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart @@ -82,16 +82,18 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { required this.dataLabelAnimationController, required this.toggleAnimationController, required this.hoverBubbleAnimationController, - }) : _source = source, - _bubbleSettings = bubbleSettings, - _legend = legend, - _themeData = themeData { + }) : _source = source, + _bubbleSettings = bubbleSettings, + _legend = legend, + _themeData = themeData { _bubbleAnimation = CurvedAnimation( parent: bubbleAnimationController, curve: const Interval(0.2, 1.0, curve: Curves.easeInOut), ); _toggleBubbleAnimation = CurvedAnimation( - parent: toggleAnimationController, curve: Curves.easeInOut); + parent: toggleAnimationController, + curve: Curves.easeInOut, + ); _forwardToggledBubbleColorTween = ColorTween(); _forwardToggledBubbleStrokeColorTween = ColorTween(); @@ -99,7 +101,9 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { _reverseToggledBubbleStrokeColorTween = ColorTween(); _toggleBubbleAnimation = CurvedAnimation( - parent: toggleAnimationController, curve: Curves.easeInOut); + parent: toggleAnimationController, + curve: Curves.easeInOut, + ); _forwardBubbleHoverColorTween = ColorTween(); _forwardBubbleHoverStrokeColorTween = ColorTween(); @@ -107,7 +111,9 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { _reverseBubbleHoverStrokeColorTween = ColorTween(); _hoverBubbleAnimation = CurvedAnimation( - parent: hoverBubbleAnimationController, curve: Curves.easeInOut); + parent: hoverBubbleAnimationController, + curve: Curves.easeInOut, + ); if (_legend != null && _legend!.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); @@ -269,19 +275,24 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } void _updateHoverItemTween() { - final double opacity = - _bubbleAnimation.value * _bubbleSettings.color!.opacity; - final Color defaultColor = bubbleSettings.color!.withOpacity(opacity); + final double opacity = _bubbleAnimation.value * _bubbleSettings.color!.a; + final Color defaultColor = bubbleSettings.color!.withValues(alpha: opacity); if (_currentHoverItem != null) { _forwardBubbleHoverColorTween.begin = _currentHoverItem!.bubbleColor ?? defaultColor; - _forwardBubbleHoverColorTween.end = - _getHoverFillColor(opacity, defaultColor, _currentHoverItem!); + _forwardBubbleHoverColorTween.end = _getHoverFillColor( + opacity, + defaultColor, + _currentHoverItem!, + ); } if (_previousHoverItem != null) { - _reverseBubbleHoverColorTween.begin = - _getHoverFillColor(opacity, defaultColor, _previousHoverItem!); + _reverseBubbleHoverColorTween.begin = _getHoverFillColor( + opacity, + defaultColor, + _previousHoverItem!, + ); _reverseBubbleHoverColorTween.end = _previousHoverItem!.bubbleColor ?? defaultColor; } @@ -354,10 +365,12 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } void _initializeToggledBubbleTweenColors() { - final Color? toggledBubbleColor = _themeData.toggledItemColor != - Colors.transparent - ? _themeData.toggledItemColor!.withOpacity(_legend!.toggledItemOpacity) - : null; + final Color? toggledBubbleColor = + _themeData.toggledItemColor != Colors.transparent + ? _themeData.toggledItemColor!.withValues( + alpha: _legend!.toggledItemOpacity, + ) + : null; _forwardToggledBubbleColorTween.end = toggledBubbleColor; _forwardToggledBubbleStrokeColorTween.begin = _themeData.bubbleStrokeColor; @@ -389,8 +402,9 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { void _updateToggledBubbleTweenColor() { late MapModel model; if (source.bubbleColorMappers == null) { - model = - mapDataSource.values.elementAt(controller!.currentToggledItemIndex); + model = mapDataSource.values.elementAt( + controller!.currentToggledItemIndex, + ); } else { for (final MapModel mapModel in mapDataSource.values) { if (mapModel.dataIndex != null && @@ -408,21 +422,25 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { @override void paint(PaintingContext context, Offset offset) { - final Rect bounds = - Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); + 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 double opacity = _bubbleAnimation.value * _bubbleSettings.color!.a; + final Color defaultColor = bubbleSettings.color!.withValues(alpha: opacity); final bool hasToggledIndices = controller!.toggledIndices.isNotEmpty; final Paint fillPaint = Paint()..isAntiAlias = true; - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; mapDataSource.forEach((String key, MapModel model) { if (model.bubbleSizeValue == null || @@ -431,16 +449,25 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { return; } - final double bubbleRadius = - _getDesiredValue(_bubbleAnimation.value * model.bubbleRadius!); + 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); + context.canvas.drawCircle( + model.shapePathCenter!, + bubbleRadius, + fillPaint, + ); } _drawBubbleStroke( - context, model, strokePaint, bubbleRadius, hasToggledIndices); + context, + model, + strokePaint, + bubbleRadius, + hasToggledIndices, + ); }); _drawHoveredBubble(context, opacity, defaultColor); @@ -451,16 +478,22 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { (controller!.gesture == Gesture.scale ? controller!.localScale : 1); } - void _drawBubbleStroke(PaintingContext context, MapModel model, - Paint strokePaint, double bubbleRadius, bool hasToggledIndices) { + void _drawBubbleStroke( + PaintingContext context, + MapModel model, + Paint strokePaint, + double bubbleRadius, + bool hasToggledIndices, + ) { if (hasToggledStroke || hasDefaultStroke) { _updateStrokePaint(model, strokePaint, hasToggledIndices, bubbleRadius); strokePaint.strokeWidth /= controller!.gesture == Gesture.scale ? controller!.localScale : 1; context.canvas.drawCircle( - model.shapePathCenter!, - _getDesiredRadius(bubbleRadius, strokePaint.strokeWidth), - strokePaint); + model.shapePathCenter!, + _getDesiredRadius(bubbleRadius, strokePaint.strokeWidth), + strokePaint, + ); } } @@ -472,8 +505,12 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { // Set color to the toggled and un-toggled bubbles based on // the [legendController.toggledIndices] collection. - void _updateFillColor(MapModel model, Paint fillPaint, bool hasToggledIndices, - Color defaultColor) { + 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) { @@ -481,9 +518,14 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { // 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) - ? _forwardToggledBubbleColorTween.evaluate(_toggleBubbleAnimation) - : _reverseToggledBubbleColorTween.evaluate(_toggleBubbleAnimation); + final Color? bubbleColor = + controller!.wasToggled(model) + ? _forwardToggledBubbleColorTween.evaluate( + _toggleBubbleAnimation, + ) + : _reverseToggledBubbleColorTween.evaluate( + _toggleBubbleAnimation, + ); fillPaint.color = bubbleColor ?? Colors.transparent; return; } else if (hasToggledIndices && controller!.wasToggled(model)) { @@ -496,9 +538,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { if (_previousHoverItem != null && _previousHoverItem!.primaryKey == model.primaryKey) { - fillPaint.color = _themeData.bubbleHoverColor != Colors.transparent - ? _reverseBubbleHoverColorTween.evaluate(_hoverBubbleAnimation)! - : (_previousHoverItem!.bubbleColor ?? defaultColor); + fillPaint.color = + _themeData.bubbleHoverColor != Colors.transparent + ? _reverseBubbleHoverColorTween.evaluate(_hoverBubbleAnimation)! + : (_previousHoverItem!.bubbleColor ?? defaultColor); return; } fillPaint.color = model.bubbleColor ?? defaultColor; @@ -506,8 +549,12 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { // Set stroke paint to the toggled and un-toggled bubbles based on // the [legendController.toggledIndices] collection. - void _updateStrokePaint(MapModel model, Paint strokePaint, - bool hasToggledIndices, double bubbleRadius) { + 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) { @@ -517,14 +564,18 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { // If the legend item is un-toggled, then the // [_reverseToggledBubbleStrokeColorTween] return. strokePaint - ..color = controller!.wasToggled(model) - ? _forwardToggledBubbleStrokeColorTween - .evaluate(_toggleBubbleAnimation)! - : _reverseToggledBubbleStrokeColorTween - .evaluate(_toggleBubbleAnimation)! - ..strokeWidth = controller!.wasToggled(model) - ? _legend!.toggledItemStrokeWidth - : _bubbleSettings.strokeWidth!; + ..color = + controller!.wasToggled(model) + ? _forwardToggledBubbleStrokeColorTween.evaluate( + _toggleBubbleAnimation, + )! + : _reverseToggledBubbleStrokeColorTween.evaluate( + _toggleBubbleAnimation, + )! + ..strokeWidth = + controller!.wasToggled(model) + ? _legend!.toggledItemStrokeWidth + : _bubbleSettings.strokeWidth!; return; } else if (hasToggledIndices && controller!.wasToggled(model)) { // Set toggled stroke color to the previously toggled bubbles. @@ -541,8 +592,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { _themeData.bubbleHoverStrokeColor != Colors.transparent) { strokePaint ..style = PaintingStyle.stroke - ..color = _reverseBubbleHoverStrokeColorTween - .evaluate(_hoverBubbleAnimation)! + ..color = + _reverseBubbleHoverStrokeColorTween.evaluate( + _hoverBubbleAnimation, + )! ..strokeWidth = _themeData.bubbleStrokeWidth; return; } else if (hasDefaultStroke) { @@ -555,25 +608,37 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } strokePaint ..color = _bubbleSettings.strokeColor! - ..strokeWidth = _bubbleSettings.strokeWidth! > bubbleRadius - ? bubbleRadius - : _bubbleSettings.strokeWidth!; + ..strokeWidth = + _bubbleSettings.strokeWidth! > bubbleRadius + ? bubbleRadius + : _bubbleSettings.strokeWidth!; } void _drawHoveredBubble( - PaintingContext context, double opacity, Color defaultColor) { + PaintingContext context, + double opacity, + Color defaultColor, + ) { if (_currentHoverItem != null) { - final double bubbleRadius = - _getDesiredValue(_currentHoverItem!.bubbleRadius!); + final double bubbleRadius = _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); + final Paint paint = + Paint() + ..style = PaintingStyle.fill + ..color = + _themeData.bubbleHoverColor != Colors.transparent + ? _forwardBubbleHoverColorTween.evaluate( + _hoverBubbleAnimation, + )! + : (_currentHoverItem!.bubbleColor ?? defaultColor); if (paint.color != Colors.transparent) { context.canvas.drawCircle( - _currentHoverItem!.shapePathCenter!, bubbleRadius, paint); + _currentHoverItem!.shapePathCenter!, + bubbleRadius, + paint, + ); } _drawHoveredBubbleStroke(context, bubbleRadius); @@ -581,9 +646,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } void _drawHoveredBubbleStroke(PaintingContext context, double bubbleRadius) { - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; if (_themeData.bubbleHoverStrokeWidth! > 0.0 && _themeData.bubbleHoverStrokeColor != Colors.transparent) { strokePaint @@ -599,11 +665,12 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { if (strokePaint.strokeWidth > 0.0 && strokePaint.color != Colors.transparent) { context.canvas.drawCircle( - _currentHoverItem!.shapePathCenter!, - strokePaint.strokeWidth > bubbleRadius - ? bubbleRadius / 2 - : bubbleRadius - strokePaint.strokeWidth / 2, - strokePaint); + _currentHoverItem!.shapePathCenter!, + strokePaint.strokeWidth > bubbleRadius + ? bubbleRadius / 2 + : bubbleRadius - strokePaint.strokeWidth / 2, + strokePaint, + ); } } } 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 ffe37ccce..e36848ad4 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart @@ -10,6 +10,7 @@ import '../utils.dart'; // ignore_for_file: public_member_api_docs class MapDataLabel extends LeafRenderObjectWidget { const MapDataLabel({ + super.key, required this.source, required this.mapDataSource, required this.settings, @@ -44,9 +45,10 @@ class MapDataLabel extends LeafRenderObjectWidget { @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderMapDataLabel renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderMapDataLabel renderObject, + ) { renderObject ..source = source ..mapDataSource = mapDataSource @@ -68,14 +70,14 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { required this.controller, required this.dataLabelAnimationController, required MediaQueryData mediaQueryData, - }) : _settings = settings, - _effectiveTextStyle = effectiveTextStyle, - _themeData = themeData, - _mediaQueryData = mediaQueryData { - _effectiveTextScaleFactor = _mediaQueryData.textScaleFactor; + }) : _settings = settings, + _effectiveTextStyle = effectiveTextStyle, + _themeData = themeData, + _mediaQueryData = mediaQueryData { + _effectiveTextScaleFactor = _mediaQueryData.textScaler; _textPainter = TextPainter(textDirection: TextDirection.ltr) - ..textScaleFactor = _effectiveTextScaleFactor; + ..textScaler = _effectiveTextScaleFactor; _opacityTween = Tween(begin: 0.0, end: 1.0); _dataLabelAnimation = CurvedAnimation( @@ -85,7 +87,7 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { _checkDataLabelColor(); } - late double _effectiveTextScaleFactor; + late TextScaler _effectiveTextScaleFactor; late Animation _dataLabelAnimation; late Tween _opacityTween; late bool _isCustomTextColor; @@ -135,13 +137,14 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { return; } _mediaQueryData = value; - _effectiveTextScaleFactor = _mediaQueryData.textScaleFactor; - _textPainter.textScaleFactor = _effectiveTextScaleFactor; + _effectiveTextScaleFactor = _mediaQueryData.textScaler; + _textPainter.textScaler = _effectiveTextScaleFactor; markNeedsPaint(); } void _checkDataLabelColor() { - _isCustomTextColor = _settings.textStyle?.color != null || + _isCustomTextColor = + _settings.textStyle?.color != null || _themeData.dataLabelTextStyle?.color != null; } @@ -210,22 +213,29 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { String? dataLabelText; final TextStyle textStyle = _effectiveTextStyle.copyWith( - color: _getAnimatedColor(_effectiveTextStyle.color!)); + color: _getAnimatedColor(_effectiveTextStyle.color), + ); final bool hasMapper = source.dataLabelMapper != null; mapDataSource.forEach((String key, MapModel model) { - dataLabelText = controller!.isInInteractive - ? model.visibleDataLabelText - : hasMapper + 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); + final TextStyle desiredTextStyle = _updateLuminanceColor( + textStyle, + _isCustomTextColor, + model, + ); + _textPainter.text = TextSpan( + style: desiredTextStyle, + text: dataLabelText, + ); _textPainter.layout(); if (!controller!.isInInteractive) { if (_settings.overflowMode == MapLabelOverflow.hide) { @@ -235,13 +245,16 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { } } else if (_settings.overflowMode == MapLabelOverflow.ellipsis) { final String trimmedText = getTrimText( - dataLabelText!, - desiredTextStyle, - model.shapeWidth!, - _textPainter, - _textPainter.width); - _textPainter.text = - TextSpan(style: desiredTextStyle, text: trimmedText); + dataLabelText!, + desiredTextStyle, + model.shapeWidth!, + _textPainter, + _textPainter.width, + ); + _textPainter.text = TextSpan( + style: desiredTextStyle, + text: trimmedText, + ); _textPainter.layout(); } @@ -253,18 +266,24 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { ..save() ..translate(model.shapePathCenter!.dx, model.shapePathCenter!.dy) ..scale(1 / controller!.localScale); - _textPainter.paint(context.canvas, - Offset(-_textPainter.width / 2, -_textPainter.height / 2)); + _textPainter.paint( + context.canvas, + Offset(-_textPainter.width / 2, -_textPainter.height / 2), + ); context.canvas.restore(); }); context.canvas.restore(); } TextStyle _updateLuminanceColor( - TextStyle style, bool isCustomTextStyle, MapModel model) { + TextStyle style, + bool isCustomTextStyle, + MapModel model, + ) { if (!isCustomTextStyle) { - final Brightness brightness = - ThemeData.estimateBrightnessForColor(_getActualShapeColor(model)); + final Brightness brightness = ThemeData.estimateBrightnessForColor( + _getActualShapeColor(model), + ); final Color color = brightness == Brightness.dark ? Colors.white : Colors.black; style = style.copyWith(color: _getAnimatedColor(color)); @@ -277,7 +296,10 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { return model.shapeColor ?? _themeData.layerColor!; } - Color _getAnimatedColor(Color color) { - return color.withOpacity(_opacityTween.evaluate(_dataLabelAnimation)); + Color? _getAnimatedColor(Color? color) { + if (color == null) { + return null; + } + return color.withValues(alpha: _opacityTween.evaluate(_dataLabelAnimation)); } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart index 1d8e20eab..df6230fc2 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart @@ -4,10 +4,10 @@ import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/legend_internal.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_maps/maps.dart'; -import 'package:syncfusion_flutter_maps/src/controller/map_controller.dart'; +import '../../maps.dart'; import '../common.dart'; +import '../controller/map_controller.dart'; /// Specifies the legend type. enum _LegendType { @@ -19,8 +19,8 @@ enum _LegendType { } /// Signature to return a [Widget] for the given value. -typedef MapLegendPointerBuilder = Widget Function( - BuildContext context, dynamic value); +typedef MapLegendPointerBuilder = + Widget Function(BuildContext context, dynamic value); /// Shows legend for the bubbles or shapes. /// @@ -303,6 +303,7 @@ class MapLegend extends DiagnosticableTree { /// * `MapLegend.bar()` named constructor, for bar legend type. const MapLegend( this.source, { + this.shouldAlwaysShowScrollbar = false, this.title, this.position = MapLegendPosition.top, this.offset, @@ -318,19 +319,19 @@ class MapLegend extends DiagnosticableTree { this.toggledItemStrokeColor, this.toggledItemStrokeWidth = 1.0, this.toggledItemOpacity = 0.5, - }) : _type = _LegendType.vector, - segmentSize = null, - labelsPlacement = null, - edgeLabelsPlacement = MapLegendEdgeLabelsPlacement.inside, - labelOverflow = MapLabelOverflow.visible, - segmentPaintingStyle = MapLegendPaintingStyle.solid, - showPointerOnHover = false, - pointerBuilder = null, - pointerColor = null, - pointerSize = Size.zero, - assert(spacing >= 0), - assert(toggledItemStrokeWidth >= 0), - assert(toggledItemOpacity >= 0 && toggledItemOpacity <= 1); + }) : _type = _LegendType.vector, + segmentSize = null, + labelsPlacement = null, + edgeLabelsPlacement = MapLegendEdgeLabelsPlacement.inside, + labelOverflow = MapLabelOverflow.visible, + segmentPaintingStyle = MapLegendPaintingStyle.solid, + showPointerOnHover = false, + pointerBuilder = null, + pointerColor = null, + pointerSize = Size.zero, + assert(spacing >= 0), + assert(toggledItemStrokeWidth >= 0), + assert(toggledItemOpacity >= 0 && toggledItemOpacity <= 1); /// Creates a bar type legend for the bubbles or shapes. /// @@ -462,6 +463,7 @@ class MapLegend extends DiagnosticableTree { /// diamond, rectangle and triangle. const MapLegend.bar( this.source, { + this.shouldAlwaysShowScrollbar = false, this.title, this.overflowMode = MapLegendOverflowMode.scroll, this.padding = const EdgeInsets.all(10.0), @@ -479,15 +481,15 @@ class MapLegend extends DiagnosticableTree { this.pointerBuilder, this.pointerColor, this.pointerSize = const Size(16, 12), - }) : _type = _LegendType.bar, - enableToggleInteraction = false, - toggledItemColor = null, - toggledItemStrokeColor = null, - toggledItemStrokeWidth = 0.0, - toggledItemOpacity = 0.0, - iconType = MapIconType.circle, - iconSize = const Size(8.0, 8.0), - assert(spacing >= 0); + }) : _type = _LegendType.bar, + enableToggleInteraction = false, + toggledItemColor = null, + toggledItemStrokeColor = null, + toggledItemStrokeWidth = 0.0, + toggledItemOpacity = 0.0, + iconType = MapIconType.circle, + iconSize = const Size(8.0, 8.0), + assert(spacing >= 0); /// Shows legend for the bubbles or shapes. /// @@ -584,6 +586,25 @@ class MapLegend extends DiagnosticableTree { /// * `MapLegend()`, to know about the legend in maps. final Widget? title; + ///Toggles the scrollbar visibility. + /// + /// When set to false, the scrollbar appears only when scrolling else the + /// scrollbar fades out. When true, the scrollbar will never fade out and + /// will always be visible when the items are overflown. + /// + ///Defaults to `false`. + /// + ///```dart + /// MapShapeLayer( + /// legend:MapLegend( + /// isVisible: true, + /// shouldAlwaysShowScrollbar: true, + /// overflowMode: MapLegendOverflowMode.scroll, + /// ) + /// ) + ///``` + final bool shouldAlwaysShowScrollbar; + /// Sets the padding around the legend. /// /// Defaults to EdgeInsets.all(10.0). @@ -2005,25 +2026,25 @@ class MapLegend extends DiagnosticableTree { } @override - int get hashCode => hashValues( - padding, - offset, - spacing, - direction, - overflowMode, - position, - textStyle, - enableToggleInteraction, - toggledItemColor, - toggledItemStrokeColor, - toggledItemStrokeWidth, - toggledItemOpacity, - source, - showPointerOnHover, - pointerBuilder, - pointerColor, - pointerSize, - ); + int get hashCode => Object.hash( + padding, + offset, + spacing, + direction, + overflowMode, + position, + textStyle, + enableToggleInteraction, + toggledItemColor, + toggledItemStrokeColor, + toggledItemStrokeWidth, + toggledItemOpacity, + source, + showPointerOnHover, + pointerBuilder, + pointerColor, + pointerSize, + ); /// Creates a copy of this class but with the given fields /// replaced with the new values. @@ -2113,8 +2134,9 @@ class MapLegend extends DiagnosticableTree { properties.add(EnumProperty('direction', direction)); } - properties - .add(EnumProperty('overflowMode', overflowMode)); + properties.add( + EnumProperty('overflowMode', overflowMode), + ); properties.add(EnumProperty('position', position)); if (textStyle != null) { properties.add(textStyle!.toDiagnosticsNode(name: 'textStyle')); @@ -2123,33 +2145,51 @@ class MapLegend extends DiagnosticableTree { if (_type == _LegendType.vector) { properties.add(DiagnosticsProperty('iconSize', iconSize)); properties.add(EnumProperty('iconType', iconType)); - properties.add(FlagProperty('enableToggleInteraction', + properties.add( + FlagProperty( + 'enableToggleInteraction', value: enableToggleInteraction, ifTrue: 'Toggle is enabled', ifFalse: 'Toggle is disabled', - showName: false)); + ), + ); if (toggledItemColor != null) { properties.add(ColorProperty('toggledItemColor', toggledItemColor)); } if (toggledItemStrokeColor != null) { properties.add( - ColorProperty('toggledItemStrokeColor', toggledItemStrokeColor)); + ColorProperty('toggledItemStrokeColor', toggledItemStrokeColor), + ); } properties.add( - DoubleProperty('toggledItemStrokeWidth', toggledItemStrokeWidth)); + DoubleProperty('toggledItemStrokeWidth', toggledItemStrokeWidth), + ); properties.add(DoubleProperty('toggledItemOpacity', toggledItemOpacity)); } 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)); + EnumProperty( + 'labelsPlacement', + labelsPlacement, + ), + ); + properties.add( + EnumProperty( + 'edgeLabelsPlacement', + edgeLabelsPlacement, + ), + ); + properties.add( + EnumProperty('labelOverflowMode', labelOverflow), + ); + properties.add( + EnumProperty( + 'segmentPaintingStyle', + segmentPaintingStyle, + ), + ); } } } @@ -2166,8 +2206,8 @@ class Legend extends StatefulWidget { required this.child, required this.themeData, this.controller, - }) : assert(colorMappers != null || dataSource != null), - super(key: key); + }) : assert(colorMappers != null || dataSource != null), + super(key: key); // Collection of [MapColorMapper] which specifies map color based /// on the data. @@ -2200,7 +2240,7 @@ class _LegendState extends State { /// Generates the list of legend items based on the data source. List _getLegendItems() { - final List _legendItems = []; + final List legendItems = []; final bool isLegendForBubbles = widget.legend.source == MapElement.bubble; if (widget.colorMappers != null && widget.colorMappers!.isNotEmpty) { @@ -2214,35 +2254,39 @@ class _LegendState extends State { MapLegendLabelsPlacement.betweenItems) { if (index == 0) { final String startValue = _getStartSegmentLabel(colorMapper); - _legendItems - .add(LegendItem(text: startValue, color: colorMapper.color)); + legendItems.add( + LegendItem(text: startValue, color: colorMapper.color), + ); } else { - final String text = colorMapper.text ?? + final String text = + colorMapper.text ?? colorMapper.value ?? colorMapper.to.toString(); - _legendItems.add(LegendItem(text: text, color: colorMapper.color)); + legendItems.add(LegendItem(text: text, color: colorMapper.color)); } } else { - final String text = colorMapper.text ?? + final String text = + colorMapper.text ?? colorMapper.value ?? '${colorMapper.from} - ${colorMapper.to}'; - _legendItems.add(LegendItem(text: text, color: colorMapper.color)); + legendItems.add(LegendItem(text: text, color: colorMapper.color)); } } } else if (widget.dataSource != null && widget.dataSource!.isNotEmpty) { widget.dataSource!.forEach((String key, MapModel mapModel) { - _legendItems.add( + legendItems.add( LegendItem( text: mapModel.primaryKey, - color: isLegendForBubbles - ? mapModel.bubbleColor ?? widget.themeData.bubbleColor - : mapModel.shapeColor ?? widget.themeData.layerColor, + color: + isLegendForBubbles + ? mapModel.bubbleColor ?? widget.themeData.bubbleColor + : mapModel.shapeColor ?? widget.themeData.layerColor, ), ); }); } - return _legendItems; + return legendItems; } String _getStartSegmentLabel(MapColorMapper colorMapper) { @@ -2281,7 +2325,8 @@ class _LegendState extends State { } LegendPaintingStyle _getEffectiveSegmentPaintingStyle( - MapLegendPaintingStyle paintingStyle) { + MapLegendPaintingStyle paintingStyle, + ) { switch (paintingStyle) { case MapLegendPaintingStyle.solid: return LegendPaintingStyle.solid; @@ -2291,7 +2336,8 @@ class _LegendState extends State { } LegendLabelOverflow _getEffectiveLabelOverflow( - MapLabelOverflow labelOverflow) { + MapLabelOverflow labelOverflow, + ) { switch (labelOverflow) { case MapLabelOverflow.visible: return LegendLabelOverflow.visible; @@ -2303,7 +2349,8 @@ class _LegendState extends State { } LegendEdgeLabelsPlacement _getEffectiveEdgeLabelsPlacement( - MapLegendEdgeLabelsPlacement edgeLabelsPlacement) { + MapLegendEdgeLabelsPlacement edgeLabelsPlacement, + ) { switch (edgeLabelsPlacement) { case MapLegendEdgeLabelsPlacement.center: return LegendEdgeLabelsPlacement.center; @@ -2313,7 +2360,8 @@ class _LegendState extends State { } LegendLabelsPlacement _getEffectiveLabelPlacement( - MapLegendLabelsPlacement labelsPlacement) { + MapLegendLabelsPlacement labelsPlacement, + ) { switch (labelsPlacement) { case MapLegendLabelsPlacement.betweenItems: return LegendLabelsPlacement.betweenItems; @@ -2336,7 +2384,8 @@ class _LegendState extends State { } LegendOverflowMode _getEffectiveOverflowMode( - MapLegendOverflowMode overflowMode) { + MapLegendOverflowMode overflowMode, + ) { switch (overflowMode) { case MapLegendOverflowMode.scroll: return LegendOverflowMode.scroll; @@ -2386,8 +2435,9 @@ class _LegendState extends State { Color? _getEffectiveToggledItemColor() { if (widget.themeData.toggledItemColor != Colors.transparent) { - return widget.themeData.toggledItemColor! - .withOpacity(widget.legend.toggledItemOpacity); + return widget.themeData.toggledItemColor!.withValues( + alpha: widget.legend.toggledItemOpacity, + ); } return widget.themeData.toggledItemColor; } @@ -2397,6 +2447,7 @@ class _LegendState extends State { switch (widget.legend._type) { case _LegendType.vector: return SfLegend( + shouldAlwaysShowScrollbar: widget.legend.shouldAlwaysShowScrollbar, items: _getLegendItems(), title: widget.legend.title, direction: widget.legend.direction, @@ -2405,20 +2456,21 @@ class _LegendState extends State { position: _getEffectivePosition(widget.legend.position), overflowMode: _getEffectiveOverflowMode(widget.legend.overflowMode), itemSpacing: widget.legend.spacing, - textStyle: widget.legend.textStyle, + textStyle: widget.themeData.legendTextStyle, iconType: _getEffectiveLegendIconType(widget.legend.iconType), iconSize: widget.legend.iconSize, toggledIndices: _toggledIndices, - onToggledIndicesChanged: widget.legend.enableToggleInteraction - ? (List indices, int currentIndex) { - setState(() { - widget.controller!.toggledIndices.clear(); - widget.controller!.toggledIndices.addAll(indices); - widget.controller!.currentToggledItemIndex = currentIndex; - _toggledIndices = indices; - }); - } - : null, + onToggledIndicesChanged: + widget.legend.enableToggleInteraction + ? (List indices, int currentIndex) { + setState(() { + widget.controller!.toggledIndices.clear(); + widget.controller!.toggledIndices.addAll(indices); + widget.controller!.currentToggledItemIndex = currentIndex; + _toggledIndices = indices; + }); + } + : null, toggledIconColor: _getEffectiveToggledItemColor(), toggledTextOpacity: widget.legend.toggledItemOpacity, child: widget.child, @@ -2427,27 +2479,33 @@ class _LegendState extends State { return SfLegend.bar( items: _getLegendItems(), title: widget.legend.title, + shouldAlwaysShowScrollbar: widget.legend.shouldAlwaysShowScrollbar, position: _getEffectivePosition(widget.legend.position), overflowMode: _getEffectiveOverflowMode(widget.legend.overflowMode), itemSpacing: widget.legend.spacing, direction: widget.legend.direction, offset: widget.legend.offset, padding: widget.legend.padding, - textStyle: widget.legend.textStyle, - labelsPlacement: - _getEffectiveLabelPlacement(_getActualLabelsPlacement()), + textStyle: widget.themeData.legendTextStyle, + labelsPlacement: _getEffectiveLabelPlacement( + _getActualLabelsPlacement(), + ), edgeLabelsPlacement: _getEffectiveEdgeLabelsPlacement( - widget.legend.edgeLabelsPlacement), - labelOverflow: - _getEffectiveLabelOverflow(widget.legend.labelOverflow), + widget.legend.edgeLabelsPlacement, + ), + labelOverflow: _getEffectiveLabelOverflow( + widget.legend.labelOverflow, + ), segmentSize: widget.legend.segmentSize, segmentPaintingStyle: _getEffectiveSegmentPaintingStyle( - widget.legend.segmentPaintingStyle), + widget.legend.segmentPaintingStyle, + ), pointerBuilder: widget.legend.pointerBuilder, pointerColor: widget.legend.pointerColor, - pointerSize: widget.legend.showPointerOnHover - ? widget.legend.pointerSize - : Size.zero, + pointerSize: + widget.legend.showPointerOnHover + ? widget.legend.pointerSize + : Size.zero, pointerController: widget.pointerController, child: widget.child, ); diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart index f26e1fa42..98b386ab4 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart @@ -13,8 +13,10 @@ import '../utils.dart'; // ignore_for_file: public_member_api_docs class MarkerContainer extends Stack { MarkerContainer({ + super.key, required this.markerTooltipBuilder, required this.controller, + required this.themeData, this.sublayer, this.ancestor, List? children, @@ -22,6 +24,7 @@ class MarkerContainer extends Stack { final IndexedWidgetBuilder? markerTooltipBuilder; final MapController controller; + final SfMapsThemeData themeData; final MapShapeSublayer? sublayer; final MapLayerInheritedWidget? ancestor; @@ -30,17 +33,20 @@ class MarkerContainer extends Stack { return _RenderMarkerContainer() ..markerTooltipBuilder = markerTooltipBuilder ..controller = controller + ..themeData = themeData ..sublayer = sublayer ..container = this; } @override void updateRenderObject( - BuildContext context, - // ignore: library_private_types_in_public_api - _RenderMarkerContainer renderObject) { + BuildContext context, + // ignore: library_private_types_in_public_api + _RenderMarkerContainer renderObject, + ) { renderObject ..markerTooltipBuilder = markerTooltipBuilder + ..themeData = themeData ..sublayer = sublayer ..container = this; } @@ -49,6 +55,7 @@ class MarkerContainer extends Stack { class _RenderMarkerContainer extends RenderStack { late MarkerContainer container; late MapController controller; + late SfMapsThemeData themeData; GlobalKey? tooltipKey; IndexedWidgetBuilder? markerTooltipBuilder; MapShapeSublayer? sublayer; @@ -57,9 +64,10 @@ class _RenderMarkerContainer extends RenderStack { return container.children.indexOf(marker); } - Size get containerSize => controller.layerType == LayerType.tile - ? controller.getTileSize(controller.tileCurrentLevelDetails.zoomLevel) - : controller.shapeLayerBoxSize; + Size get containerSize => + controller.layerType == LayerType.tile + ? controller.getTileSize(controller.tileCurrentLevelDetails.zoomLevel) + : controller.shapeLayerBoxSize; void _handleZooming(MapZoomDetails details) { markNeedsLayout(); @@ -108,9 +116,10 @@ class _RenderMarkerContainer extends RenderStack { @override void performLayout() { - size = controller.layerType == LayerType.shape - ? controller.shapeLayerBoxSize - : controller.tileLayerBoxSize!; + size = + controller.layerType == LayerType.shape + ? controller.shapeLayerBoxSize + : controller.tileLayerBoxSize!; final double factor = getLayerSizeFactor(controller); final Offset translation = getTranslationOffset(controller); RenderBox? child = firstChild; @@ -121,22 +130,31 @@ class _RenderMarkerContainer extends RenderStack { // ignore: avoid_as child.parentData! as StackParentData; child.layout(constraints, parentUsesSize: true); - childParentData.offset = pixelFromLatLng(marker.latitude, - marker.longitude, containerSize, translation, factor); + childParentData.offset = pixelFromLatLng( + marker.latitude, + 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; + level.translatePoint; } - childParentData.offset -= - Offset(marker.size.width / 2, marker.size.height / 2); + childParentData.offset -= Offset( + marker.size.width / 2, + marker.size.height / 2, + ); if (marker.alignment != Alignment.center) { - final Alignment effectiveAlignment = - marker.alignment.resolve(textDirection); + final Alignment effectiveAlignment = marker.alignment.resolve( + textDirection, + ); childParentData.offset -= Offset( - effectiveAlignment.x * marker.size.width / 2, - effectiveAlignment.y * marker.size.height / 2); + effectiveAlignment.x * marker.size.width / 2, + effectiveAlignment.y * marker.size.height / 2, + ); } if (marker.offset != Offset.zero) { @@ -154,8 +172,12 @@ class _RenderMarkerContainer extends RenderStack { 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); + final Rect childRect = Rect.fromLTWH( + childParentData.offset.dx, + childParentData.offset.dy, + child.size.width, + child.size.height, + ); if (sublayer != null || controller.visibleBounds!.overlaps(childRect)) { context.paintChild(child, childParentData.offset); } @@ -880,9 +902,7 @@ class MapMarker extends SingleChildRenderObjectWidget { iconStrokeColor: iconStrokeColor, iconStrokeWidth: iconStrokeWidth, iconType: iconType, - themeData: SfMapsTheme.of(context)!, marker: this, - context: context, ); } @@ -899,7 +919,6 @@ class MapMarker extends SingleChildRenderObjectWidget { ..iconStrokeColor = iconStrokeColor ..iconStrokeWidth = iconStrokeWidth ..iconType = iconType - ..themeData = SfMapsTheme.of(context)! ..marker = this; } } @@ -916,24 +935,19 @@ class _RenderMapMarker extends RenderProxyBox required Color? iconStrokeColor, required double? iconStrokeWidth, required MapIconType iconType, - required SfMapsThemeData themeData, - required BuildContext context, required this.marker, - }) : _longitude = longitude, - _latitude = latitude, - _markerSize = markerSize, - _alignment = alignment, - _offset = offset, - _iconColor = iconColor, - _iconStrokeColor = iconStrokeColor, - _iconStrokeWidth = iconStrokeWidth, - _iconType = iconType, - _themeData = themeData, - _theme = Theme.of(context) { + }) : _longitude = longitude, + _latitude = latitude, + _markerSize = markerSize, + _alignment = alignment, + _offset = offset, + _iconColor = iconColor, + _iconStrokeColor = iconStrokeColor, + _iconStrokeWidth = iconStrokeWidth, + _iconType = iconType { _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - final ThemeData _theme; final Size _defaultMarkerSize = const Size(14.0, 14.0); late TapGestureRecognizer _tapGestureRecognizer; @@ -1037,18 +1051,6 @@ class _RenderMapMarker extends RenderProxyBox } } - SfMapsThemeData get themeData => _themeData; - SfMapsThemeData _themeData; - set themeData(SfMapsThemeData value) { - if (_themeData == value) { - return; - } - _themeData = value; - if (child == null) { - markNeedsPaint(); - } - } - void _handleTapUp(TapUpDetails details) { _handleInteraction(); } @@ -1058,43 +1060,53 @@ class _RenderMapMarker extends RenderProxyBox } void _handlePointerExit(PointerExitEvent event) { - 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(); + if (owner != null) { + 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([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!); - } + if (owner != null) { + 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 tooltipRenderBox = - markerContainerRenderBox.controller.tooltipKey!.currentContext! - // ignore: avoid_as - .findRenderObject()! as ShapeLayerChildRenderBoxBase; - // ignore: avoid_as - final StackParentData childParentData = parentData! as StackParentData; - tooltipRenderBox.paintTooltip( + 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); + sublayerIndex, + ); + } } } @@ -1160,11 +1172,14 @@ class _RenderMapMarker extends RenderProxyBox } Paint? _getBorderPaint() { + final _RenderMarkerContainer markerContainer = + parent! as _RenderMarkerContainer; + final SfMapsThemeData themeData = markerContainer.themeData; if (_iconStrokeWidth != null && _iconStrokeWidth! > 0) { return Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _iconStrokeWidth ?? _themeData.markerIconStrokeWidth - ..color = _iconStrokeColor ?? _themeData.markerIconStrokeColor!; + ..strokeWidth = _iconStrokeWidth ?? themeData.markerIconStrokeWidth + ..color = _iconStrokeColor ?? themeData.markerIconStrokeColor!; } return null; @@ -1172,18 +1187,19 @@ class _RenderMapMarker extends RenderProxyBox @override void paint(PaintingContext context, Offset offset) { + final _RenderMarkerContainer markerContainer = + parent! as _RenderMarkerContainer; if (child == null) { core.paint( - canvas: context.canvas, - rect: paintBounds, - shapeType: _getEffectiveShapeType(), - paint: Paint() - ..color = _iconColor ?? - _themeData.markerIconColor ?? - (_theme.brightness == Brightness.light - ? const Color.fromRGBO(98, 0, 238, 1) - : const Color.fromRGBO(187, 134, 252, 1)), - borderPaint: _getBorderPaint()); + canvas: context.canvas, + rect: paintBounds, + shapeType: _getEffectiveShapeType(), + paint: + Paint() + ..color = + _iconColor ?? markerContainer.themeData.markerIconColor!, + borderPaint: _getBorderPaint(), + ); } else { context.paintChild(child!, offset); } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart index cee7ebbc5..8b0ed766f 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart @@ -9,12 +9,11 @@ enum _ToolbarIcon { zoomIn, zoomOut, reset } class MapToolbar extends StatelessWidget { const MapToolbar({ - required this.onWillZoom, + super.key, required this.zoomPanBehavior, required this.controller, }); - final WillZoomCallback? onWillZoom; final MapZoomPanBehavior zoomPanBehavior; final MapController? controller; @@ -31,7 +30,6 @@ class MapToolbar extends StatelessWidget { _ToolbarItem( controller: controller, zoomPanBehavior: zoomPanBehavior, - onWillZoom: onWillZoom, iconData: Icons.add_circle_outline, icon: _ToolbarIcon.zoomIn, tooltipText: 'Zoom In', @@ -39,7 +37,6 @@ class MapToolbar extends StatelessWidget { _ToolbarItem( controller: controller, zoomPanBehavior: zoomPanBehavior, - onWillZoom: onWillZoom, iconData: Icons.remove_circle_outline, icon: _ToolbarIcon.zoomOut, tooltipText: 'Zoom Out', @@ -47,7 +44,6 @@ class MapToolbar extends StatelessWidget { _ToolbarItem( controller: controller, zoomPanBehavior: zoomPanBehavior, - onWillZoom: onWillZoom, iconData: Icons.autorenew, icon: _ToolbarIcon.reset, tooltipText: 'Reset', @@ -77,7 +73,6 @@ class _ToolbarItem extends StatefulWidget { const _ToolbarItem({ required this.controller, required this.zoomPanBehavior, - required this.onWillZoom, required this.iconData, required this.icon, required this.tooltipText, @@ -87,8 +82,6 @@ class _ToolbarItem extends StatefulWidget { final MapZoomPanBehavior zoomPanBehavior; - final WillZoomCallback? onWillZoom; - final IconData iconData; final _ToolbarIcon icon; @@ -130,15 +123,18 @@ class _ToolbarItemState extends State<_ToolbarItem> { bool enabled; switch (widget.icon) { case _ToolbarIcon.zoomIn: - enabled = widget.zoomPanBehavior.zoomLevel != + enabled = + widget.zoomPanBehavior.zoomLevel != widget.zoomPanBehavior.maxZoomLevel; break; case _ToolbarIcon.zoomOut: - enabled = widget.zoomPanBehavior.zoomLevel != + enabled = + widget.zoomPanBehavior.zoomLevel != widget.zoomPanBehavior.minZoomLevel; break; case _ToolbarIcon.reset: - enabled = widget.zoomPanBehavior.zoomLevel != + enabled = + widget.zoomPanBehavior.zoomLevel != widget.zoomPanBehavior.minZoomLevel; break; } @@ -205,9 +201,10 @@ class _ToolbarItemState extends State<_ToolbarItem> { offset: const Offset(0.0, 2.0), ), ], - color: _isLightTheme - ? const Color.fromARGB(255, 250, 250, 250) - : const Color.fromARGB(255, 66, 66, 66), + color: + _isLightTheme + ? const Color.fromARGB(255, 250, 250, 250) + : const Color.fromARGB(255, 66, 66, 66), ), child: Container( height: _toolbarItemSize.height, @@ -222,19 +219,22 @@ class _ToolbarItemState extends State<_ToolbarItem> { highlightColor: Colors.transparent, hoverColor: Colors.transparent, splashColor: Colors.transparent, - disabledColor: _isLightTheme - ? const Color.fromRGBO(0, 0, 0, 0.24) - : const Color.fromRGBO(255, 255, 255, 0.24), + disabledColor: + _isLightTheme + ? const Color.fromRGBO(0, 0, 0, 0.24) + : const Color.fromRGBO(255, 255, 255, 0.24), icon: Icon(widget.iconData), - color: widget.zoomPanBehavior.toolbarSettings.iconColor ?? + color: + widget.zoomPanBehavior.toolbarSettings.iconColor ?? (_isLightTheme ? const Color.fromRGBO(0, 0, 0, 0.54) : const Color.fromRGBO(255, 255, 255, 0.54)), - onPressed: _enabled - ? () { - _handlePointerUp(); - } - : null, + onPressed: + _enabled + ? () { + _handlePointerUp(); + } + : null, mouseCursor: _enabled ? SystemMouseCursors.click : SystemMouseCursors.basic, tooltip: widget.tooltipText, @@ -270,8 +270,13 @@ class _ToolbarItemState extends State<_ToolbarItem> { break; } - newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior.minZoomLevel, - widget.zoomPanBehavior.maxZoomLevel); - widget.zoomPanBehavior.zoomLevel = newZoomLevel; + newZoomLevel = newZoomLevel.clamp( + widget.zoomPanBehavior.minZoomLevel, + widget.zoomPanBehavior.maxZoomLevel, + ); + if (widget.controller != null && + newZoomLevel != widget.zoomPanBehavior.zoomLevel) { + widget.controller!.notifyToolbarZoomedListeners(newZoomLevel); + } } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart index 425d3d0c9..e4af2baea 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_maps/maps.dart'; +import '../../maps.dart'; import '../common.dart'; import '../controller/map_controller.dart'; @@ -108,7 +108,9 @@ class _MapTooltipState extends State void initState() { super.initState(); controller = AnimationController( - vsync: this, duration: const Duration(milliseconds: 200)); + vsync: this, + duration: const Duration(milliseconds: 200), + ); } @override @@ -120,7 +122,8 @@ class _MapTooltipState extends State @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - isDesktop = kIsWeb || + isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -163,7 +166,9 @@ class _MapTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { @override void updateRenderObject( - BuildContext context, _RenderMapTooltip renderObject) { + BuildContext context, + _RenderMapTooltip renderObject, + ) { renderObject ..source = source ..tooltipSettings = tooltipSettings @@ -181,13 +186,15 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { required MediaQueryData mediaQueryData, required this.context, required _MapTooltipState state, - }) : _source = source, - _tooltipSettings = tooltipSettings, - _themeData = themeData, - _mediaQueryData = mediaQueryData, - _state = state { - _scaleAnimation = - CurvedAnimation(parent: _state.controller, curve: Curves.easeOutBack); + }) : _source = source, + _tooltipSettings = tooltipSettings, + _themeData = themeData, + _mediaQueryData = mediaQueryData, + _state = state { + _scaleAnimation = CurvedAnimation( + parent: _state.controller, + curve: Curves.easeOutBack, + ); } static const double tooltipTriangleHeight = 7; @@ -247,8 +254,12 @@ 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); _currentPosition = position; @@ -287,7 +298,9 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _showTimer?.cancel(); if (_pointerKind == PointerKind.touch) { _showTimer = Timer( - Duration(seconds: _tooltipSettings.hideDelay.toInt()), hideTooltip); + Duration(seconds: _tooltipSettings.hideDelay.toInt()), + hideTooltip, + ); } } @@ -301,8 +314,13 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { 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 && @@ -338,12 +356,22 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { } @override - void paintTooltip(int? elementIndex, Rect? elementRect, - MapLayerElement? element, PointerKind kind, - [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); + position, + elementIndex, + elementRect, + element, + sublayerIndex, + ); } @override @@ -411,14 +439,15 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { } _tooltipShape.paint( - context, - offset, - _currentPosition!, - _preferTooltipOnTop, - this, - _scaleAnimation, - _themeData, - _tooltipSettings); + context, + offset, + _currentPosition!, + _preferTooltipOnTop, + this, + _scaleAnimation, + _themeData, + _tooltipSettings, + ); } } @@ -433,23 +462,29 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _updateElementRect(bounds); } final double halfRectWidth = _elementRect!.width / 2; - Offset tooltipPosition = _elementRect!.topLeft + + Offset tooltipPosition = + _elementRect!.topLeft + Offset(halfRectWidth, _elementRect!.height * tooltipHeightFactor); _preferTooltipOnTop = bounds.contains( - tooltipPosition - Offset(0.0, tooltipHeight + tooltipTriangleHeight)); + 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! - - Offset(0.0, tooltipHeight + tooltipTriangleHeight)); + _preferTooltipOnTop = bounds.contains( + _currentPosition! - Offset(0.0, tooltipHeight + tooltipTriangleHeight), + ); } } @@ -483,21 +518,23 @@ class _TooltipShape { /// 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, - SfMapsThemeData themeData, - MapTooltipSettings tooltipSettings) { + PaintingContext context, + Offset offset, + Offset center, + bool preferTooltipOnTop, + RenderProxyBox parentBox, + Animation tooltipAnimation, + SfMapsThemeData themeData, + MapTooltipSettings tooltipSettings, + ) { const double tooltipTriangleWidth = 12.0; const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; const double elevation = 0.0; Path path = Path(); - BorderRadius borderRadius = - themeData.tooltipBorderRadius.resolve(TextDirection.ltr); + 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; @@ -508,29 +545,36 @@ class _TooltipShape { 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; + 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) { @@ -545,44 +589,56 @@ class _TooltipShape { tooltipHeight *= -1; borderRadius = BorderRadius.only( topRight: Radius.elliptical( - borderRadius.bottomRight.x, -borderRadius.bottomRight.y), + borderRadius.bottomRight.x, + -borderRadius.bottomRight.y, + ), bottomRight: Radius.elliptical( - borderRadius.topRight.x, -borderRadius.topRight.y), + borderRadius.topRight.x, + -borderRadius.topRight.y, + ), topLeft: Radius.elliptical( - borderRadius.bottomLeft.x, -borderRadius.bottomLeft.y), - bottomLeft: - Radius.elliptical(borderRadius.topLeft.x, -borderRadius.topLeft.y), + 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); + path, + triangleHeight, + halfTooltipHeight, + halfTooltipTriangleWidth, + tooltipTriangleOffsetY, + moveNosePoint, + rightLineWidth, + leftLineWidth, + borderRadius, + tooltipHeight, + ); context.canvas.save(); - context.canvas - .translate(center.dx, center.dy - triangleHeight - halfTooltipHeight); + 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.strokeColor ?? themeData.tooltipStrokeColor; - 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; + 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. @@ -592,22 +648,25 @@ class _TooltipShape { 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(); } Path _getTooltipPath( - Path path, - double tooltipTriangleHeight, - double halfTooltipHeight, - double halfTooltipTriangleWidth, - double tooltipTriangleOffsetY, - double moveNosePoint, - double rightLineWidth, - double leftLineWidth, - BorderRadius borderRadius, - double tooltipHeight) { + 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); @@ -617,7 +676,9 @@ class _TooltipShape { // preferTooltipOnTop is false, // \ path.lineTo( - halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + halfTooltipTriangleWidth + moveNosePoint, + tooltipTriangleOffsetY, + ); // preferTooltipOnTop is true, // ___ // / @@ -625,7 +686,9 @@ class _TooltipShape { // preferTooltipOnTop is false, // \___ path.lineTo( - rightLineWidth - borderRadius.bottomRight.x, tooltipTriangleOffsetY); + rightLineWidth - borderRadius.bottomRight.x, + tooltipTriangleOffsetY, + ); // preferTooltipOnTop is true, // ___| // / @@ -633,10 +696,16 @@ class _TooltipShape { // preferTooltipOnTop is false, // \___ // | - path.quadraticBezierTo(rightLineWidth, tooltipTriangleOffsetY, - rightLineWidth, tooltipTriangleOffsetY - borderRadius.bottomRight.y); - path.lineTo(rightLineWidth, - tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y); + path.quadraticBezierTo( + rightLineWidth, + tooltipTriangleOffsetY, + rightLineWidth, + tooltipTriangleOffsetY - borderRadius.bottomRight.y, + ); + path.lineTo( + rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y, + ); // preferTooltipOnTop is true, // _______ // ___| @@ -646,12 +715,15 @@ class _TooltipShape { // \___ // ________| path.quadraticBezierTo( - rightLineWidth, - tooltipTriangleOffsetY - tooltipHeight, - rightLineWidth - borderRadius.topRight.x, - tooltipTriangleOffsetY - tooltipHeight); - path.lineTo(-leftLineWidth + borderRadius.topLeft.x, - tooltipTriangleOffsetY - tooltipHeight); + rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + rightLineWidth - borderRadius.topRight.x, + tooltipTriangleOffsetY - tooltipHeight, + ); + path.lineTo( + -leftLineWidth + borderRadius.topLeft.x, + tooltipTriangleOffsetY - tooltipHeight, + ); // preferTooltipOnTop is true, // _______ // | ___| @@ -661,12 +733,15 @@ class _TooltipShape { // \___ // |________| path.quadraticBezierTo( - -leftLineWidth, - tooltipTriangleOffsetY - tooltipHeight, - -leftLineWidth, - tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y); + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y, + ); path.lineTo( - -leftLineWidth, tooltipTriangleOffsetY - borderRadius.bottomLeft.y); + -leftLineWidth, + tooltipTriangleOffsetY - borderRadius.bottomLeft.y, + ); // preferTooltipOnTop is true, // ________ // |___ ___| @@ -675,10 +750,16 @@ class _TooltipShape { // preferTooltipOnTop is false, // ___ \___ // |________| - path.quadraticBezierTo(-leftLineWidth, tooltipTriangleOffsetY, - -leftLineWidth + borderRadius.bottomLeft.x, tooltipTriangleOffsetY); + path.quadraticBezierTo( + -leftLineWidth, + tooltipTriangleOffsetY, + -leftLineWidth + borderRadius.bottomLeft.x, + tooltipTriangleOffsetY, + ); path.lineTo( - -halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + -halfTooltipTriangleWidth + moveNosePoint, + tooltipTriangleOffsetY, + ); // preferTooltipOnTop is true, // ________ // |___ ___| @@ -693,7 +774,10 @@ class _TooltipShape { } Offset _getShiftPosition( - Offset offset, Offset center, RenderProxyBox parent) { + Offset offset, + Offset center, + RenderProxyBox parent, + ) { final Size childSize = parent.child!.size; final double halfChildWidth = childSize.width / 2; final double halfChildHeight = childSize.height / 2; @@ -702,8 +786,9 @@ class _TooltipShape { // edge goes out of the map's right edge. if (center.dx + halfChildWidth + marginSpace > parent.size.width) { return Offset( - childSize.width + center.dx - parent.size.width + marginSpace, - halfChildHeight); + childSize.width + center.dx - parent.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. diff --git a/packages/syncfusion_flutter_maps/lib/src/enum.dart b/packages/syncfusion_flutter_maps/lib/src/enum.dart index b0d4055c7..ef78a6657 100644 --- a/packages/syncfusion_flutter_maps/lib/src/enum.dart +++ b/packages/syncfusion_flutter_maps/lib/src/enum.dart @@ -47,7 +47,7 @@ enum MapLabelOverflow { /// It trims the labels based on the available space in their respective /// shape. - ellipsis + ellipsis, } /// Shows legend for the shapes or the bubbles. @@ -71,7 +71,7 @@ enum MapToolbarPosition { bottomLeft, /// Positions the toolbar on right bottom. - bottomRight + bottomRight, } /// Option to place the labels either between the bars or on the bar in bar @@ -81,7 +81,7 @@ enum MapLegendLabelsPlacement { onItem, /// [MapLegendLabelsPlacement.betweenItems] places labels in-between two bars. - betweenItems + betweenItems, } /// Placement of edge labels in the bar legend. @@ -91,7 +91,7 @@ enum MapLegendEdgeLabelsPlacement { /// Place the edge labels in the center of the starting position of the /// legend bars. - center + center, } /// Applies gradient or solid color for the bar segments. @@ -100,5 +100,5 @@ enum MapLegendPaintingStyle { solid, /// Applies gradient color for bar segments. - gradient + gradient, } 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 6e2af0443..007897fc7 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; +// ignore: unnecessary_import import 'dart:typed_data' show Uint8List; import 'dart:ui'; @@ -13,7 +14,7 @@ import 'package:flutter/scheduler.dart' show SchedulerBinding; import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_core/legend_internal.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_maps/maps.dart'; +import '../../maps.dart'; import '../common.dart'; import '../controller/map_controller.dart'; @@ -25,6 +26,7 @@ import '../elements/marker.dart'; import '../elements/toolbar.dart'; import '../elements/tooltip.dart'; import '../layer/vector_layers.dart'; +import '../theme.dart'; import '../utils.dart'; /// The source that maps the data source with the shape file and provides @@ -162,18 +164,24 @@ class MapShapeSource extends DiagnosticableTree { this.bubbleSizeMapper, this.shapeColorValueMapper, this.bubbleColorValueMapper, - }) : _path = name, - _type = GeoJSONSourceType.asset, - assert((primaryValueMapper != null && dataCount > 0) || - primaryValueMapper == null), - assert((shapeColorMappers != null && - primaryValueMapper != null && - shapeColorValueMapper != null) || - shapeColorMappers == null), - assert((bubbleColorMappers != null && - primaryValueMapper != null && - bubbleColorValueMapper != null) || - bubbleColorMappers == null); + }) : _path = name, + _type = GeoJSONSourceType.asset, + assert( + (primaryValueMapper != null && dataCount > 0) || + primaryValueMapper == null, + ), + assert( + (shapeColorMappers != null && + primaryValueMapper != null && + shapeColorValueMapper != null) || + shapeColorMappers == null, + ), + assert( + (bubbleColorMappers != null && + primaryValueMapper != null && + bubbleColorValueMapper != null) || + bubbleColorMappers == null, + ); /// Creates a layer using the .json file from the network. /// @@ -216,18 +224,24 @@ class MapShapeSource extends DiagnosticableTree { this.bubbleSizeMapper, this.shapeColorValueMapper, this.bubbleColorValueMapper, - }) : _path = src, - _type = GeoJSONSourceType.network, - assert((primaryValueMapper != null && dataCount > 0) || - primaryValueMapper == null), - assert((shapeColorMappers != null && - primaryValueMapper != null && - shapeColorValueMapper != null) || - shapeColorMappers == null), - assert((bubbleColorMappers != null && - primaryValueMapper != null && - bubbleColorValueMapper != null) || - bubbleColorMappers == null); + }) : _path = src, + _type = GeoJSONSourceType.network, + assert( + (primaryValueMapper != null && dataCount > 0) || + primaryValueMapper == null, + ), + assert( + (shapeColorMappers != null && + primaryValueMapper != null && + shapeColorValueMapper != null) || + shapeColorMappers == null, + ), + assert( + (bubbleColorMappers != null && + primaryValueMapper != null && + bubbleColorValueMapper != null) || + bubbleColorMappers == null, + ); /// Creates a layer using the GeoJSON source as bytes from [Uint8List]. /// @@ -272,18 +286,24 @@ class MapShapeSource extends DiagnosticableTree { this.bubbleSizeMapper, this.shapeColorValueMapper, this.bubbleColorValueMapper, - }) : _path = bytes, - _type = GeoJSONSourceType.memory, - assert((primaryValueMapper != null && dataCount > 0) || - primaryValueMapper == null), - assert((shapeColorMappers != null && - primaryValueMapper != null && - shapeColorValueMapper != null) || - shapeColorMappers == null), - assert((bubbleColorMappers != null && - primaryValueMapper != null && - bubbleColorValueMapper != null) || - bubbleColorMappers == null); + }) : _path = bytes, + _type = GeoJSONSourceType.memory, + assert( + (primaryValueMapper != null && dataCount > 0) || + primaryValueMapper == null, + ), + assert( + (shapeColorMappers != null && + primaryValueMapper != null && + shapeColorValueMapper != null) || + shapeColorMappers == null, + ), + assert( + (bubbleColorMappers != null && + primaryValueMapper != null && + bubbleColorValueMapper != null) || + bubbleColorMappers == null, + ); /// Field name in the .json file to identify each shape. /// @@ -833,20 +853,48 @@ class MapShapeSource extends DiagnosticableTree { } properties.add(StringProperty('shapeDataField', shapeDataField)); properties.add(IntProperty('dataCount', dataCount)); - properties.add(ObjectFlagProperty.has( - 'primaryValueMapper', primaryValueMapper)); - properties.add(ObjectFlagProperty>.has( - 'shapeColorMappers', shapeColorMappers)); - properties.add(ObjectFlagProperty>.has( - 'bubbleColorMappers', bubbleColorMappers)); - properties.add(ObjectFlagProperty.has( - 'dataLabelMapper', dataLabelMapper)); - properties.add(ObjectFlagProperty.has( - 'bubbleSizeMapper', bubbleSizeMapper)); - properties.add(ObjectFlagProperty.has( - 'shapeColorValueMapper', shapeColorValueMapper)); - properties.add(ObjectFlagProperty.has( - 'bubbleColorValueMapper', bubbleColorValueMapper)); + properties.add( + ObjectFlagProperty.has( + 'primaryValueMapper', + primaryValueMapper, + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'shapeColorMappers', + shapeColorMappers, + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'bubbleColorMappers', + bubbleColorMappers, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'dataLabelMapper', + dataLabelMapper, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'bubbleSizeMapper', + bubbleSizeMapper, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'shapeColorValueMapper', + shapeColorValueMapper, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'bubbleColorValueMapper', + bubbleColorValueMapper, + ), + ); } } @@ -863,18 +911,14 @@ class _ShapeBounds { num? maxLongitude; num? maxLatitude; - _ShapeBounds get empty => _ShapeBounds( - minLongitude: null, - minLatitude: null, - maxLongitude: null, - maxLatitude: null); + _ShapeBounds get empty => _ShapeBounds(); } class _ShapeFileData { Map? decodedJsonData; late Map mapDataSource; late _ShapeBounds bounds; - late MapModel initialSelectedModel; + MapModel? initialSelectedModel; void reset() { decodedJsonData?.clear(); @@ -884,10 +928,11 @@ class _ShapeFileData { } Future<_ShapeFileData> _retrieveDataFromShapeFile( - MapProvider provider, - String? shapeDataField, - _ShapeFileData shapeFileData, - bool isSublayer) async { + MapProvider provider, + String? shapeDataField, + _ShapeFileData shapeFileData, + bool isSublayer, +) async { if (shapeFileData.mapDataSource.isNotEmpty) { return shapeFileData; } @@ -896,7 +941,7 @@ Future<_ShapeFileData> _retrieveDataFromShapeFile( 'AssertBundleData': assertBundleData, 'ShapeDataField': shapeDataField, 'ShapeFileData': shapeFileData, - 'IsSublayer': isSublayer + 'IsSublayer': isSublayer, }; return compute(_decodeJsonData, data); } @@ -920,25 +965,30 @@ void _readJsonFile(Map data) { final String? shapeDataField = data['ShapeDataField'] as String?; // ignore: avoid_as final bool isSublayer = data['IsSublayer'] as bool; - final bool hasFeatures = - shapeFileData.decodedJsonData!.containsKey('features'); - final bool hasGeometries = - shapeFileData.decodedJsonData!.containsKey('geometries'); - final String? key = hasFeatures - ? 'features' - : hasGeometries + 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 - // ignore: avoid_as - : shapeFileData.decodedJsonData![key].length as int; + final int jsonLength = + key == null || key.isEmpty + ? 0 + // ignore: avoid_as + : shapeFileData.decodedJsonData![key].length as int; if (isSublayer) { shapeFileData.bounds = _ShapeBounds( - minLatitude: minimumLatitude, - maxLatitude: maximumLatitude, - minLongitude: minimumLongitude, - maxLongitude: maximumLongitude); + minLatitude: minimumLatitude, + maxLatitude: maximumLatitude, + minLongitude: minimumLongitude, + maxLongitude: maximumLongitude, + ); } for (int i = 0; i < jsonLength; i++) { @@ -956,23 +1006,38 @@ void _readJsonFile(Map data) { if (geometry['type'] == 'Polygon') { // ignore: avoid_as polygonGeometryData = geometry['coordinates'][0] as List; - _updateMapDataSource(shapeFileData, shapeDataField, properties, - polygonGeometryData, isSublayer); + _updateMapDataSource( + shapeFileData, + shapeDataField, + properties, + polygonGeometryData, + isSublayer, + ); } else { // ignore: avoid_as multipolygonGeometryLength = geometry['coordinates'].length as int; for (int j = 0; j < multipolygonGeometryLength; j++) { // ignore: avoid_as polygonGeometryData = geometry['coordinates'][j][0] as List; - _updateMapDataSource(shapeFileData, shapeDataField, properties, - polygonGeometryData, isSublayer); + _updateMapDataSource( + shapeFileData, + shapeDataField, + properties, + polygonGeometryData, + isSublayer, + ); } } } } -void _updateMapDataSource(_ShapeFileData shapeFileData, String? shapeDataField, - Map? properties, List points, bool isSublayer) { +void _updateMapDataSource( + _ShapeFileData shapeFileData, + String? shapeDataField, + Map? properties, + List points, + bool isSublayer, +) { final String dataPath = // ignore: avoid_as (properties != null ? (properties[shapeDataField] ?? '') : '') as String; @@ -998,7 +1063,9 @@ void _updateMapDataSource(_ShapeFileData shapeFileData, String? shapeDataField, } void _updateShapeBounds( - _ShapeFileData shapeFileData, List coordinates) { + _ShapeFileData shapeFileData, + List coordinates, +) { List data; num longitude, latitude; final int length = coordinates.length; @@ -1015,14 +1082,22 @@ void _updateShapeBounds( 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!); + 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!, + ); } } } @@ -1120,16 +1195,18 @@ class MapShapeLayerController extends MapLayerController { /// Converts pixel point to [MapLatLng]. MapLatLng pixelToLatLng(Offset position) { return getPixelToLatLng( - position, - _parentBox.size, - _parentBox._controller.shapeLayerOffset, - _parentBox._controller.shapeLayerSizeFactor); + position, + _parentBox.size, + _parentBox._controller.shapeLayerOffset, + _parentBox._controller.shapeLayerSizeFactor, + ); } } // ignore_for_file: public_member_api_docs class GeoJSONLayer extends StatefulWidget { const GeoJSONLayer({ + super.key, required this.source, required this.controller, this.initialLatLngBounds, @@ -1218,6 +1295,7 @@ class _GeoJSONLayerState extends State MapController? _controller; Widget _buildGeoJSONLayer(SfMapsThemeData themeData, bool isDesktop) { + final ThemeData theme = Theme.of(context); Widget current = ClipRect( child: Stack( children: [ @@ -1231,23 +1309,28 @@ class _GeoJSONLayerState extends State selectionSettings: widget.selectionSettings, zoomPanBehavior: widget.zoomPanBehavior, bubbleSettings: widget.bubbleSettings.copyWith( - color: themeData.bubbleColor, - strokeColor: themeData.bubbleStrokeColor, - strokeWidth: themeData.bubbleStrokeWidth), + color: themeData.bubbleColor, + strokeColor: themeData.bubbleStrokeColor, + strokeWidth: themeData.bubbleStrokeWidth, + ), themeData: themeData, isDesktop: isDesktop, state: this, children: [ if (_hasSublayer) SublayerContainer( - ancestor: ancestor, children: widget.sublayers!), + ancestor: ancestor, + children: widget.sublayers!, + ), if (_markers != null && _markers!.isNotEmpty) MarkerContainer( - controller: _controller!, - markerTooltipBuilder: widget.markerTooltipBuilder, - sublayer: widget.sublayerAncestor, - ancestor: ancestor, - children: _markers) + controller: _controller!, + markerTooltipBuilder: widget.markerTooltipBuilder, + themeData: themeData, + sublayer: widget.sublayerAncestor, + ancestor: ancestor, + children: _markers, + ), ], ), if (widget.source.bubbleSizeMapper != null) @@ -1257,9 +1340,10 @@ class _GeoJSONLayerState extends State source: widget.source, mapDataSource: shapeFileData.mapDataSource, bubbleSettings: widget.bubbleSettings.copyWith( - color: themeData.bubbleColor, - strokeColor: themeData.bubbleStrokeColor, - strokeWidth: themeData.bubbleStrokeWidth), + color: themeData.bubbleColor, + strokeColor: themeData.bubbleStrokeColor, + strokeWidth: themeData.bubbleStrokeWidth, + ), legend: widget.legend, showDataLabels: widget.showDataLabels, themeData: themeData, @@ -1274,9 +1358,9 @@ class _GeoJSONLayerState extends State source: widget.source, mapDataSource: shapeFileData.mapDataSource, settings: widget.dataLabelSettings, - effectiveTextStyle: Theme.of(context).textTheme.caption!.merge( - widget.dataLabelSettings.textStyle ?? - themeData.dataLabelTextStyle), + effectiveTextStyle: theme.textTheme.bodySmall! + .merge(themeData.dataLabelTextStyle) + .merge(widget.dataLabelSettings.textStyle), themeData: themeData, dataLabelAnimationController: dataLabelAnimationController, ), @@ -1289,9 +1373,9 @@ class _GeoJSONLayerState extends State widget.zoomPanBehavior!.showToolbar && isDesktop) MapToolbar( - controller: _controller, - onWillZoom: widget.onWillZoom, - zoomPanBehavior: widget.zoomPanBehavior!), + controller: _controller, + zoomPanBehavior: widget.zoomPanBehavior!, + ), if (_hasTooltipBuilder()) MapTooltip( key: _controller!.tooltipKey, @@ -1357,88 +1441,89 @@ class _GeoJSONLayerState extends State return false; } - SfMapsThemeData _updateThemeData(BuildContext context, ThemeData themeData, - SfMapsThemeData mapsThemeData) { - final bool isLightTheme = mapsThemeData.brightness == Brightness.light; + SfMapsThemeData _updateThemeData( + BuildContext context, + ThemeData themeData, + SfMapsThemeData mapsThemeData, + ) { + final MapsThemeData effectiveThemeData = MapsThemeData(context); return mapsThemeData.copyWith( - layerColor: widget.color ?? + layerColor: + widget.color ?? (isSublayer - ? (isLightTheme - ? const Color.fromRGBO(198, 198, 198, 1) - : const Color.fromRGBO(71, 71, 71, 1)) - : mapsThemeData.layerColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.11) - : themeData.colorScheme.onSurface.withOpacity(0.24))), - layerStrokeColor: widget.strokeColor ?? + ? effectiveThemeData.subLayerColor + : mapsThemeData.layerColor ?? effectiveThemeData.layerColor), + layerStrokeColor: + widget.strokeColor ?? (isSublayer - ? (isLightTheme - ? const Color.fromRGBO(145, 145, 145, 1) - : const Color.fromRGBO(133, 133, 133, 1)) + ? effectiveThemeData.subLayerStrokeColor : mapsThemeData.layerStrokeColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.18) - : themeData.colorScheme.onSurface.withOpacity(0.43))), - layerStrokeWidth: widget.strokeWidth ?? + effectiveThemeData.layerStrokeColor), + layerStrokeWidth: + widget.strokeWidth ?? (isSublayer - ? (isLightTheme ? 0.5 : 0.25) + ? effectiveThemeData.subLayerStrokeWidth : mapsThemeData.layerStrokeWidth), shapeHoverStrokeWidth: mapsThemeData.shapeHoverStrokeWidth ?? mapsThemeData.layerStrokeWidth, - legendTextStyle: themeData.textTheme.caption! + legendTextStyle: themeData.textTheme.bodySmall! .copyWith( - color: themeData.textTheme.caption!.color!.withOpacity(0.87)) - .merge(widget.legend?.textStyle ?? mapsThemeData.legendTextStyle), - markerIconColor: mapsThemeData.markerIconColor ?? - (isLightTheme - ? const Color.fromRGBO(98, 0, 238, 1) - : const Color.fromRGBO(187, 134, 252, 1)), - bubbleColor: widget.bubbleSettings.color ?? + color: themeData.textTheme.bodySmall!.color!.withValues( + alpha: 0.87, + ), + ) + .merge(mapsThemeData.legendTextStyle) + .merge(widget.legend?.textStyle), + markerIconColor: + mapsThemeData.markerIconColor ?? effectiveThemeData.markerIconColor, + bubbleColor: + widget.bubbleSettings.color ?? mapsThemeData.bubbleColor ?? - (isLightTheme - ? const Color.fromRGBO(98, 0, 238, 0.5) - : const Color.fromRGBO(187, 134, 252, 0.8)), - bubbleStrokeColor: widget.bubbleSettings.strokeColor ?? + effectiveThemeData.bubbleColor, + bubbleStrokeColor: + widget.bubbleSettings.strokeColor ?? mapsThemeData.bubbleStrokeColor ?? - Colors.transparent, + effectiveThemeData.bubbleStrokeColor, bubbleStrokeWidth: widget.bubbleSettings.strokeWidth ?? mapsThemeData.bubbleStrokeWidth, - bubbleHoverStrokeWidth: mapsThemeData.bubbleHoverStrokeWidth ?? + bubbleHoverStrokeWidth: + mapsThemeData.bubbleHoverStrokeWidth ?? mapsThemeData.bubbleStrokeWidth, - selectionColor: widget.selectionSettings.color ?? + selectionColor: + widget.selectionSettings.color ?? mapsThemeData.selectionColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.53) - : themeData.colorScheme.onSurface.withOpacity(0.85)), - selectionStrokeColor: widget.selectionSettings.strokeColor ?? + effectiveThemeData.selectionColor, + selectionStrokeColor: + widget.selectionSettings.strokeColor ?? mapsThemeData.selectionStrokeColor ?? - (isLightTheme - ? themeData.colorScheme.onPrimary.withOpacity(0.29) - : themeData.colorScheme.surface.withOpacity(0.56)), - selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? + effectiveThemeData.selectionStrokeColor, + selectionStrokeWidth: + widget.selectionSettings.strokeWidth ?? mapsThemeData.selectionStrokeWidth, - tooltipColor: widget.tooltipSettings.color ?? + tooltipColor: + widget.tooltipSettings.color ?? mapsThemeData.tooltipColor ?? - (isLightTheme - ? const Color.fromRGBO(117, 117, 117, 1) - : const Color.fromRGBO(245, 245, 245, 1)), - tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? - mapsThemeData.tooltipStrokeColor, - tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? + effectiveThemeData.tooltipColor, + tooltipStrokeColor: + widget.tooltipSettings.strokeColor ?? + mapsThemeData.tooltipStrokeColor ?? + effectiveThemeData.tooltipStrokeColor, + tooltipStrokeWidth: + widget.tooltipSettings.strokeWidth ?? mapsThemeData.tooltipStrokeWidth, - tooltipBorderRadius: - mapsThemeData.tooltipBorderRadius.resolve(Directionality.of(context)), - toggledItemColor: widget.legend?.toggledItemColor ?? + tooltipBorderRadius: mapsThemeData.tooltipBorderRadius.resolve( + Directionality.of(context), + ), + toggledItemColor: + widget.legend?.toggledItemColor ?? mapsThemeData.toggledItemColor ?? - (isLightTheme - ? themeData.colorScheme.onPrimary - : themeData.colorScheme.onSurface.withOpacity(0.09)), - toggledItemStrokeColor: widget.legend?.toggledItemStrokeColor ?? + effectiveThemeData.toggledItemColor, + toggledItemStrokeColor: + widget.legend?.toggledItemStrokeColor ?? mapsThemeData.toggledItemStrokeColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.37) - : themeData.colorScheme.onSurface.withOpacity(0.17)), - toggledItemStrokeWidth: widget.legend?.toggledItemStrokeWidth ?? + effectiveThemeData.toggledItemStrokeColor, + toggledItemStrokeWidth: + widget.legend?.toggledItemStrokeWidth ?? mapsThemeData.toggledItemStrokeWidth, ); } @@ -1477,27 +1562,38 @@ class _GeoJSONLayerState extends State } void _updateShapeColor( - bool hasShapeColorValueMapper, int index, MapModel mapModel) { + bool hasShapeColorValueMapper, + int index, + MapModel mapModel, + ) { if (hasShapeColorValueMapper) { mapModel.shapeColor = _getActualColor( - widget.source.shapeColorValueMapper!(index), - widget.source.shapeColorMappers, - mapModel); + widget.source.shapeColorValueMapper!(index), + widget.source.shapeColorMappers, + mapModel, + ); } } void _updateBubbleColor( - bool hasBubbleColorValueMapper, int index, MapModel mapModel) { + bool hasBubbleColorValueMapper, + int index, + MapModel mapModel, + ) { if (hasBubbleColorValueMapper) { mapModel.bubbleColor = _getActualColor( - widget.source.bubbleColorValueMapper!(index), - widget.source.bubbleColorMappers, - mapModel); + widget.source.bubbleColorValueMapper!(index), + widget.source.bubbleColorMappers, + mapModel, + ); } } void _validateBubbleSize( - bool hasBubbleSizeMapper, int index, MapModel mapModel) { + bool hasBubbleSizeMapper, + int index, + MapModel mapModel, + ) { if (hasBubbleSizeMapper) { mapModel.bubbleSizeValue = widget.source.bubbleSizeMapper!(index); if (minBubbleValue == null) { @@ -1564,8 +1660,11 @@ class _GeoJSONLayerState extends State } /// 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; @@ -1594,10 +1693,13 @@ class _GeoJSONLayerState extends State mapModel.legendMapperIndex = i; mapModel.colorValue = colorValue; if (mapper.minOpacity != null && mapper.maxOpacity != null) { - return mapper.color.withOpacity(lerpDouble( + return mapper.color.withValues( + alpha: lerpDouble( mapper.minOpacity, mapper.maxOpacity, - (colorValue - mapper.from!) / (mapper.to! - mapper.from!))!); + (colorValue - mapper.from!) / (mapper.to! - mapper.from!), + ), + ); } return mapper.color; } @@ -1649,14 +1751,52 @@ class _GeoJSONLayerState extends State }); } + void _needPathCenterAndWidthCalculation(Map mapDataSource) { + 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; + for (int j = 0; j < rawPointsLength; j++) { + rawPoints = mapModel.rawPoints[j]; + pointsLength = rawPoints.length; + pixelPoints = mapModel.pixelPoints![j]; + for (int k = 0; k < pointsLength; k++) { + if (k > 0) { + final int l = k - 1; + if (widget.showDataLabels && 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); + } + } + } + } + if (widget.showDataLabels) { + findPathCenterAndWidth(signedArea, centerX, centerY, mapModel); + } + }); + } + void _obtainDataSource() { - _computeDataSource = _obtainDataSourceAndBindDataSource() - .then((_ShapeFileData data) => data); + _computeDataSource = _obtainDataSourceAndBindDataSource().then( + (_ShapeFileData data) => data, + ); } Future<_ShapeFileData> _obtainDataSourceAndBindDataSource() async { shapeFileData = await _retrieveDataFromShapeFile( - _provider, widget.source.shapeDataField, shapeFileData, isSublayer); + _provider, + widget.source.shapeDataField, + shapeFileData, + isSublayer, + ); if (_shouldUpdateMapDataSource) { minBubbleValue = null; maxBubbleValue = null; @@ -1673,25 +1813,43 @@ class _GeoJSONLayerState extends State void initState() { _pointerController = PointerController(); bubbleKey = GlobalKey(); - shapeFileData = _ShapeFileData() - ..mapDataSource = {} - ..bounds = _ShapeBounds(); + shapeFileData = + _ShapeFileData() + ..mapDataSource = {} + ..bounds = _ShapeBounds(); dataLabelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 750)); + vsync: this, + duration: const Duration(milliseconds: 750), + ); bubbleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 500)); + vsync: this, + duration: const Duration(milliseconds: 500), + ); toggleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); _hoverBubbleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); hoverShapeAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); selectionAnimationController = AnimationController( - vsync: this, value: 1.0, duration: const Duration(milliseconds: 150)); + vsync: this, + value: 1.0, + duration: const Duration(milliseconds: 150), + ); zoomLevelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)); + vsync: this, + duration: const Duration(milliseconds: 650), + ); focalLatLngAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)); + vsync: this, + duration: const Duration(milliseconds: 650), + ); if (widget.controller != null) { widget.controller!._markersCount = widget.initialMarkersCount; @@ -1715,8 +1873,9 @@ class _GeoJSONLayerState extends State @override void didChangeDependencies() { if (_controller == null) { - ancestor = context - .dependOnInheritedWidgetOfExactType()!; + ancestor = + context + .dependOnInheritedWidgetOfExactType()!; _controller = ancestor.controller; } super.didChangeDependencies(); @@ -1728,8 +1887,10 @@ class _GeoJSONLayerState extends State _hasSublayer = widget.sublayers != null && widget.sublayers!.isNotEmpty; isSublayer = widget.sublayerAncestor != null; - final MapProvider currentProvider = - getSourceProvider(widget.source._path, widget.source._type); + final MapProvider currentProvider = getSourceProvider( + widget.source._path, + widget.source._type, + ); if (_provider != currentProvider) { _provider = currentProvider; shapeFileData.reset(); @@ -1744,6 +1905,18 @@ class _GeoJSONLayerState extends State _controller!.visibleFocalLatLng = null; } + if (oldWidget.showDataLabels != widget.showDataLabels && + widget.showDataLabels) { + if (shapeFileData.mapDataSource.values.first.shapePathCenter == null || + shapeFileData.mapDataSource.values.first.shapeWidth == null) { + _needPathCenterAndWidthCalculation(shapeFileData.mapDataSource); + dataLabelAnimationController.value = 0.0; + if (mounted) { + dataLabelAnimationController.forward(from: 0); + } + } + } + _obtainDataSource(); super.didUpdateWidget(oldWidget); } @@ -1773,23 +1946,35 @@ class _GeoJSONLayerState extends State @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.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); - final bool isDesktop = kIsWeb || + final bool isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; - final SfMapsThemeData mapsThemeData = - _updateThemeData(context, themeData, SfMapsTheme.of(context)!); + final SfMapsThemeData mapsThemeData = _updateThemeData( + context, + themeData, + SfMapsTheme.of(context)!, + ); return FutureBuilder<_ShapeFileData>( future: _computeDataSource, @@ -1866,7 +2051,9 @@ class _GeoJSONLayerRenderObjectWidget extends Stack { @override void updateRenderObject( - BuildContext context, _RenderGeoJSONLayer renderObject) { + BuildContext context, + _RenderGeoJSONLayer renderObject, + ) { renderObject ..mapDataSource = mapDataSource ..mapSource = mapSource @@ -1898,23 +2085,24 @@ class _RenderGeoJSONLayer extends RenderStack required this.isDesktop, required this.context, required _GeoJSONLayerState state, - }) : _controller = controller, - _initialLatLngBounds = initialLatLngBounds, - _currentZoomLevel = initialZoomLevel, - _mapDataSource = mapDataSource, - _mapSource = mapSource, - _selectedIndex = selectedIndex, - _legend = legend, - _bubbleSettings = bubbleSettings, - _selectionSettings = selectionSettings, - _zoomPanBehavior = zoomPanBehavior, - _themeData = themeData, - _state = state, - super(textDirection: Directionality.of(state.context)) { - _scaleGestureRecognizer = ScaleGestureRecognizer() - ..onStart = _handleScaleStart - ..onUpdate = _handleScaleUpdate - ..onEnd = _handleScaleEnd; + }) : _controller = controller, + _initialLatLngBounds = initialLatLngBounds, + _currentZoomLevel = initialZoomLevel, + _mapDataSource = mapDataSource, + _mapSource = mapSource, + _selectedIndex = selectedIndex, + _legend = legend, + _bubbleSettings = bubbleSettings, + _selectionSettings = selectionSettings, + _zoomPanBehavior = zoomPanBehavior, + _themeData = themeData, + _state = state, + super(textDirection: Directionality.of(state.context)) { + _scaleGestureRecognizer = + ScaleGestureRecognizer() + ..onStart = _handleScaleStart + ..onUpdate = _handleScaleUpdate + ..onEnd = _handleScaleEnd; _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; @@ -1930,14 +2118,18 @@ class _RenderGeoJSONLayer extends RenderStack _reverseToggledShapeStrokeColorTween = ColorTween(); _hoverColorAnimation = CurvedAnimation( - parent: _state.hoverShapeAnimationController, curve: Curves.easeInOut); + parent: _state.hoverShapeAnimationController, + curve: Curves.easeInOut, + ); _forwardHoverColorTween = ColorTween(); _forwardHoverStrokeColorTween = ColorTween(); _reverseHoverColorTween = ColorTween(); _reverseHoverStrokeColorTween = ColorTween(); _toggleShapeAnimation = CurvedAnimation( - parent: _state.toggleAnimationController, curve: Curves.easeInOut); + parent: _state.toggleAnimationController, + curve: Curves.easeInOut, + ); if (_zoomPanBehavior != null) { _initializeZoomPanAnimations(); @@ -1968,8 +2160,8 @@ class _RenderGeoJSONLayer extends RenderStack double _actualFactor = 1.0; double _maximumReachedScaleOnInteraction = 1.0; int _pointerCount = 0; + bool _initailPinchZooming = false; Offset _panDistanceBeforeFlinging = Offset.zero; - bool _isZoomedUsingToolbar = false; bool _avoidPanUpdate = false; bool _isFlingAnimationActive = false; bool _doubleTapEnabled = false; @@ -2054,8 +2246,9 @@ class _RenderGeoJSONLayer extends RenderStack _state.bubbleAnimationController.value = 0.0; _refresh(); markNeedsPaint(); - SchedulerBinding.instance - ?.addPostFrameCallback(_initiateInitialAnimations); + SchedulerBinding.instance.addPostFrameCallback( + _initiateInitialAnimations, + ); } } @@ -2138,7 +2331,9 @@ class _RenderGeoJSONLayer extends RenderStack _initializeSelectionTween(); } - _handleShapeLayerSelection(); + if (_mapSource!.primaryValueMapper != null) { + _handleShapeLayerSelection(); + } } SfMapsThemeData get themeData => _themeData; @@ -2167,9 +2362,10 @@ class _RenderGeoJSONLayer extends RenderStack Rect get paintBounds => Offset.zero & _size; @override - MouseCursor get cursor => _controller.gesture == Gesture.pan - ? SystemMouseCursors.grabbing - : SystemMouseCursors.basic; + MouseCursor get cursor => + _controller.gesture == Gesture.pan + ? SystemMouseCursors.grabbing + : SystemMouseCursors.basic; @override PointerEnterEventListener? get onEnter => null; @@ -2188,21 +2384,30 @@ class _RenderGeoJSONLayer extends RenderStack void _initializeZoomPanAnimations() { _flingZoomLevelCurvedAnimation = CurvedAnimation( - parent: _state.zoomLevelAnimationController, curve: Curves.decelerate); + parent: _state.zoomLevelAnimationController, + curve: Curves.decelerate, + ); _flingFocalLatLngCurvedAnimation = CurvedAnimation( - parent: _state.focalLatLngAnimationController, - curve: Curves.decelerate); + parent: _state.focalLatLngAnimationController, + curve: Curves.decelerate, + ); _zoomLevelCurvedAnimation = CurvedAnimation( - parent: _state.zoomLevelAnimationController, curve: Curves.easeInOut); + parent: _state.zoomLevelAnimationController, + curve: Curves.easeInOut, + ); _focalLatLngCurvedAnimation = CurvedAnimation( - parent: _state.focalLatLngAnimationController, curve: Curves.easeInOut); + parent: _state.focalLatLngAnimationController, + curve: Curves.easeInOut, + ); _focalLatLngTween = MapLatLngTween(); _zoomLevelTween = Tween(); } void _initializeSelectionTween() { _selectionColorAnimation = CurvedAnimation( - parent: _state.selectionAnimationController, curve: Curves.easeInOut); + parent: _state.selectionAnimationController, + curve: Curves.easeInOut, + ); _forwardSelectionColorTween = ColorTween(); _forwardSelectionStrokeColorTween = ColorTween(); _reverseSelectionColorTween = ColorTween(); @@ -2225,10 +2430,11 @@ class _RenderGeoJSONLayer extends RenderStack void _updateCurrentSelectedItemTween() { if (_currentSelectedItem != null && (_state.isSublayer || !_controller.wasToggled(_currentSelectedItem!))) { - _forwardSelectionColorTween!.begin = (_currentHoverItem != null && - _currentInteractedElement == MapLayerElement.shape) - ? _forwardHoverColorTween.end - : getActualShapeColor(_currentSelectedItem!); + _forwardSelectionColorTween!.begin = + (_currentHoverItem != null && + _currentInteractedElement == MapLayerElement.shape) + ? _forwardHoverColorTween.end + : getActualShapeColor(_currentSelectedItem!); } if (_prevSelectedItem != null) { @@ -2339,28 +2545,39 @@ class _RenderGeoJSONLayer extends RenderStack if (latlng == null && initialBounds != null) { initialBounds = _getMaxVisibleBounds(initialBounds); zoomLevel = getZoomLevel( - initialBounds, _controller.layerType!, _size, _actualFactor); + initialBounds, + _controller.layerType!, + _size, + _actualFactor, + ); latlng = getFocalLatLng(initialBounds); } if (_zoomPanBehavior != null) { _currentZoomLevel = zoomLevel.clamp( - _zoomPanBehavior!.minZoomLevel, _zoomPanBehavior!.maxZoomLevel); + _zoomPanBehavior!.minZoomLevel, + _zoomPanBehavior!.maxZoomLevel, + ); _zoomPanBehavior!.zoomLevel = _currentZoomLevel; final double inflateWidth = _size.width * _currentZoomLevel / 2 - _size.width / 2; final double inflateHeight = _size.height * _currentZoomLevel / 2 - _size.height / 2; _controller.shapeLayerOrigin = Offset( - paintBounds.left - inflateWidth, paintBounds.top - inflateHeight); + paintBounds.left - inflateWidth, + paintBounds.top - inflateHeight, + ); } else { - _currentZoomLevel = - zoomLevel.clamp(kDefaultMinZoomLevel, kDefaultMaxZoomLevel); + _currentZoomLevel = zoomLevel.clamp( + kDefaultMinZoomLevel, + kDefaultMaxZoomLevel, + ); } _controller.shapeLayerSizeFactor = _actualFactor * _currentZoomLevel; - _controller.shapeLayerOffset = - _getTranslationPoint(_controller.shapeLayerSizeFactor); + _controller.shapeLayerOffset = _getTranslationPoint( + _controller.shapeLayerSizeFactor, + ); final Offset offsetBeforeAdjust = _controller.shapeLayerOffset; _adjustLayerOffsetTo(latlng); if (!_state.isSublayer) { @@ -2376,17 +2593,23 @@ class _RenderGeoJSONLayer extends RenderStack void _computeActualFactor() { final Offset minPoint = pixelFromLatLng( - _state.shapeFileData.bounds.minLatitude!, - _state.shapeFileData.bounds.minLongitude!, - _size); + _state.shapeFileData.bounds.minLatitude!, + _state.shapeFileData.bounds.minLongitude!, + _size, + ); final Offset maxPoint = pixelFromLatLng( - _state.shapeFileData.bounds.maxLatitude!, - _state.shapeFileData.bounds.maxLongitude!, - _size); + _state.shapeFileData.bounds.maxLatitude!, + _state.shapeFileData.bounds.maxLongitude!, + _size, + ); _actualShapeSize = Size( - (maxPoint.dx - minPoint.dx).abs(), (maxPoint.dy - minPoint.dy).abs()); - _actualFactor = min(_size.height / _actualShapeSize.height, - _size.width / _actualShapeSize.width); + (maxPoint.dx - minPoint.dx).abs(), + (maxPoint.dy - minPoint.dy).abs(), + ); + _actualFactor = min( + _size.height / _actualShapeSize.height, + _size.width / _actualShapeSize.width, + ); } Offset _getTranslationPoint(double factor, [Rect? bounds]) { @@ -2395,30 +2618,37 @@ class _RenderGeoJSONLayer extends RenderStack // 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 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, - widgetSize.height - _actualShapeSize.height * factor); + widgetSize.width - _actualShapeSize.width * factor, + widgetSize.height - _actualShapeSize.height * factor, + ); return Offset(dx + shift.dx / 2, dy + shift.dy / 2); } Rect _getShapeBounds(double factor, [Offset translation = Offset.zero]) { final Offset minPoint = pixelFromLatLng( - _state.shapeFileData.bounds.minLatitude!, - _state.shapeFileData.bounds.minLongitude!, - _size, - translation, - factor); + _state.shapeFileData.bounds.minLatitude!, + _state.shapeFileData.bounds.minLongitude!, + _size, + translation, + factor, + ); final Offset maxPoint = pixelFromLatLng( - _state.shapeFileData.bounds.maxLatitude!, - _state.shapeFileData.bounds.maxLongitude!, - _size, - translation, - factor); + _state.shapeFileData.bounds.maxLatitude!, + _state.shapeFileData.bounds.maxLongitude!, + _size, + translation, + factor, + ); return Rect.fromPoints(minPoint, maxPoint); } @@ -2449,22 +2679,29 @@ class _RenderGeoJSONLayer extends RenderStack _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, []); + 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); + 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); + point = + pixelPoints[k] = pixelFromLatLng( + coordinate[1], + coordinate[0], + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor, + ); if (k == 0) { shapePath.moveTo(point.dx, point.dy); } else { @@ -2488,58 +2725,14 @@ class _RenderGeoJSONLayer extends RenderStack } mapModel.shapePath = shapePath; - _findPathCenterAndWidth(signedArea, centerX, centerY, mapModel); + if (_state.widget.showDataLabels || + _state.widget.source.bubbleSizeMapper != null) { + findPathCenterAndWidth(signedArea, centerX, centerY, mapModel); + } _updateBubbleRadiusAndPath(mapModel); }); } - void _findPathCenterAndWidth( - double signedArea, double centerX, double centerY, MapModel mapModel) { - if (_state.widget.showDataLabels || - _state.widget.source.bubbleSizeMapper != null) { - // Used mathematical formula to find the center of polygon points. - signedArea /= 2; - centerX = centerX / (6 * signedArea); - centerY = centerY / (6 * signedArea); - mapModel.shapePathCenter = Offset(centerX, centerY); - 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 Offset point in points) { - distance = (centerY - point.dy).abs(); - if (point.dx < centerX) { - // Collected all points which is less 10 pixels distance from - // 'center y' to position the labels more smartly. - if (minX != null && distance < 10) { - minDistances.add(point.dx); - } - if (distance < minDistance) { - minX = point.dx; - minDistance = distance; - } - } else if (point.dx > centerX) { - if (maxX != null && distance < 10) { - maxDistances.add(point.dx); - } - - if (distance > maxDistance) { - maxX = point.dx; - maxDistance = distance; - } - } - } - } - - mapModel.shapeWidth = max(maxX!, maxDistances.reduce(max)) - - min(minX!, minDistances.reduce(min)); - } - } - void _updateBubbleRadiusAndPath(MapModel mapModel) { final double? bubbleSizeValue = mapModel.bubbleSizeValue; if (bubbleSizeValue != null) { @@ -2548,10 +2741,12 @@ class _RenderGeoJSONLayer extends RenderStack } else if (bubbleSizeValue == _state.maxBubbleValue) { mapModel.bubbleRadius = _bubbleSettings.maxRadius; } else { - final double percentage = ((bubbleSizeValue - _state.minBubbleValue!) / + final double percentage = + ((bubbleSizeValue - _state.minBubbleValue!) / (_state.maxBubbleValue! - _state.minBubbleValue!)) * 100; - mapModel.bubbleRadius = bubbleSettings.minRadius + + mapModel.bubbleRadius = + bubbleSettings.minRadius + (bubbleSettings.maxRadius - bubbleSettings.minRadius) * (percentage / 100); } @@ -2559,13 +2754,13 @@ class _RenderGeoJSONLayer extends RenderStack if ((_state.widget.bubbleTooltipBuilder != null || hasBubbleHoverColor) && mapModel.bubbleRadius != null) { - mapModel.bubblePath = Path() - ..addOval( - Rect.fromCircle( - center: mapModel.shapePathCenter!, - radius: mapModel.bubbleRadius!, - ), - ); + mapModel.bubblePath = + Path()..addOval( + Rect.fromCircle( + center: mapModel.shapePathCenter!, + radius: mapModel.bubbleRadius!, + ), + ); } } @@ -2598,10 +2793,13 @@ class _RenderGeoJSONLayer extends RenderStack _maximumReachedScaleOnInteraction = 1.0; _downGlobalPoint = globalFocalPoint; _downLocalPoint = localFocalPoint; - _referenceVisibleBounds = - _controller.getVisibleBounds(_controller.shapeLayerOffset); + _referenceVisibleBounds = _controller.getVisibleBounds( + _controller.shapeLayerOffset, + ); _referenceShapeBounds = _getShapeBounds( - _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset); + _controller.shapeLayerSizeFactor, + _controller.shapeLayerOffset, + ); _zoomDetails = MapZoomDetails( newVisibleBounds: _controller.getVisibleLatLngBounds( _referenceVisibleBounds!.topRight, @@ -2650,9 +2848,11 @@ class _RenderGeoJSONLayer extends RenderStack if (_zoomPanBehavior!.enablePinching && !_state.zoomLevelAnimationController.isAnimating && !_state.focalLatLngAnimationController.isAnimating) { - _invokeOnZooming(scale, - localFocalPoint: _downLocalPoint, - globalFocalPoint: _downGlobalPoint); + _invokeOnZooming( + scale, + localFocalPoint: _downLocalPoint, + globalFocalPoint: _downGlobalPoint, + ); } return; case Gesture.pan: @@ -2660,7 +2860,10 @@ class _RenderGeoJSONLayer extends RenderStack !_state.focalLatLngAnimationController.isAnimating && !_state.zoomLevelAnimationController.isAnimating) { _invokeOnPanning( - details.localFocalPoint, _downLocalPoint!, details.focalPoint); + details.localFocalPoint, + _downLocalPoint!, + details.focalPoint, + ); } return; } @@ -2700,16 +2903,28 @@ class _RenderGeoJSONLayer extends RenderStack _controller.gesture = null; } + // Hack: If the initial pinch zooming is performed then reinitialize + // the ScaleGestureRecognizer for the web platform alone + if (!_initailPinchZooming && kIsWeb) { + _scaleGestureRecognizer.dispose(); + _scaleGestureRecognizer = + ScaleGestureRecognizer() + ..onStart = _handleScaleStart + ..onUpdate = _handleScaleUpdate + ..onEnd = _handleScaleEnd; + _initailPinchZooming = true; + } } void _startFlingAnimationForPanning(ScaleEndDetails details) { _isFlingAnimationActive = true; final Offset currentPixelPoint = pixelFromLatLng( - _controller.visibleFocalLatLng!.latitude, - _controller.visibleFocalLatLng!.longitude, - _size, - _controller.shapeLayerOffset, - _controller.shapeLayerSizeFactor); + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor, + ); final FrictionSimulation frictionX = FrictionSimulation( _frictionCoefficient, currentPixelPoint.dx, @@ -2723,37 +2938,47 @@ class _RenderGeoJSONLayer extends RenderStack ); final MapLatLng latLng = getPixelToLatLng( - Offset(frictionX.finalX, frictionY.finalX), - _size, - _controller.shapeLayerOffset, - _controller.shapeLayerSizeFactor); + Offset(frictionX.finalX, frictionY.finalX), + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor, + ); _state.focalLatLngAnimationController.duration = _getFlingAnimationDuration( - details.velocity.pixelsPerSecond.distance, _frictionCoefficient); + details.velocity.pixelsPerSecond.distance, + _frictionCoefficient, + ); _controller.isInInteractive = false; _panDistanceBeforeFlinging = _controller.panDistance; - _zoomPanBehavior!.focalLatLng = latLng; + _handlePanningCallback(latLng); } void _startFlingAnimationForPinching(ScaleEndDetails details) { _isFlingAnimationActive = true; final int direction = _controller.localScale >= _maximumReachedScaleOnInteraction ? 1 : -1; - double newZoomLevel = _currentZoomLevel + + double newZoomLevel = + _currentZoomLevel + (direction * (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * _zoomPanBehavior!.maxZoomLevel); newZoomLevel = newZoomLevel.clamp( - _zoomPanBehavior!.minZoomLevel, _zoomPanBehavior!.maxZoomLevel); + _zoomPanBehavior!.minZoomLevel, + _zoomPanBehavior!.maxZoomLevel, + ); _state.zoomLevelAnimationController.duration = _getFlingAnimationDuration( - details.velocity.pixelsPerSecond.distance, _frictionCoefficient); + details.velocity.pixelsPerSecond.distance, + _frictionCoefficient, + ); _controller.isInInteractive = false; - _zoomPanBehavior!.zoomLevel = newZoomLevel; + _handleZoomingCallback(newZoomLevel, downPoint: _downLocalPoint); } // Returns the animation duration for the given distance and // friction co-efficient. Duration _getFlingAnimationDuration( - double distance, double frictionCoefficient) { + double distance, + double frictionCoefficient, + ) { final int duration = (log(10.0 / distance) / log(frictionCoefficient / 100)).round(); final int durationInMs = duration * 650; @@ -2762,7 +2987,7 @@ class _RenderGeoJSONLayer extends RenderStack /// Handling zooming using mouse wheel scrolling. void _handleScrollEvent(PointerScrollEvent event) { - if (_zoomPanBehavior != null && _zoomPanBehavior!.enablePinching) { + if (_zoomPanBehavior != null && _zoomPanBehavior!.enableMouseWheelZooming) { _controller.isInInteractive = true; _controller.gesture ??= Gesture.scale; if (_controller.gesture != Gesture.scale) { @@ -2770,7 +2995,6 @@ class _RenderGeoJSONLayer extends RenderStack } if (_currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; _currentHoverItem = null; } _downGlobalPoint ??= event.position; @@ -2783,8 +3007,11 @@ class _RenderGeoJSONLayer extends RenderStack scale = _actualFactor / _controller.shapeLayerSizeFactor; } - _invokeOnZooming(scale, - localFocalPoint: _downLocalPoint, globalFocalPoint: _downGlobalPoint); + _invokeOnZooming( + scale, + localFocalPoint: _downLocalPoint, + globalFocalPoint: _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(); @@ -2794,37 +3021,139 @@ class _RenderGeoJSONLayer extends RenderStack } } - void _invokeOnZooming(double scale, - {MapLatLng? focalLatLng, - Offset? localFocalPoint, - Offset? globalFocalPoint}) { + void _invokeOnZooming( + double scale, { + MapLatLng? focalLatLng, + Offset? localFocalPoint, + Offset? globalFocalPoint, + }) { final double newZoomLevel = _getZoomLevel(scale); - final double newShapeLayerSizeFactor = _getScale(newZoomLevel); - final Offset newShapeLayerOffset = - _controller.getZoomingTranslation(origin: localFocalPoint); + final double newShapeLayerSizeFactor = + _getScale(newZoomLevel) * _controller.shapeLayerSizeFactor; + final Offset newShapeLayerOffset = _controller.getZoomingTranslation( + origin: localFocalPoint, + ); + _controller.visibleFocalLatLng = _controller.getVisibleFocalLatLng( + newShapeLayerOffset, + newShapeLayerSizeFactor, + ); final Rect newVisibleBounds = _controller.getVisibleBounds( - newShapeLayerOffset, newShapeLayerSizeFactor); - final MapLatLngBounds newVisibleLatLngBounds = - _controller.getVisibleLatLngBounds( - newVisibleBounds.topRight, - newVisibleBounds.bottomLeft, newShapeLayerOffset, newShapeLayerSizeFactor, ); - _zoomDetails = MapZoomDetails( + final MapLatLngBounds newVisibleLatLngBounds = _controller + .getVisibleLatLngBounds( + newVisibleBounds.topRight, + newVisibleBounds.bottomLeft, + newShapeLayerOffset, + newShapeLayerSizeFactor, + ); + if (_currentZoomLevel != newZoomLevel) { + _zoomDetails = MapZoomDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: _currentZoomLevel, + newZoomLevel: newZoomLevel, + previousVisibleBounds: + _zoomDetails != null + ? _zoomDetails!.newVisibleBounds + : _controller.visibleLatLngBounds, + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: focalLatLng ?? getFocalLatLng(newVisibleLatLngBounds), + ); + if (_state.widget.onWillZoom == null || + _state.widget.onWillZoom!(_zoomDetails!)) { + _zoomPanBehavior?.onZooming(_zoomDetails!); + } + } + } + + void _handleZoomingCallback(double newZoomLevel, {Offset? downPoint}) { + downPoint ??= pixelFromLatLng( + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor, + ); + final double newShapeLayerSizeFactor = + _getScale(newZoomLevel) * _controller.shapeLayerSizeFactor; + final Offset newShapeLayerOffset = _controller.getZoomingTranslation( + origin: downPoint, + ); + final Rect newVisibleBounds = _controller.getVisibleBounds( + newShapeLayerOffset, + newShapeLayerSizeFactor, + ); + final MapLatLngBounds newVisibleLatLngBounds = _controller + .getVisibleLatLngBounds( + newVisibleBounds.topRight, + newVisibleBounds.bottomLeft, + newShapeLayerOffset, + newShapeLayerSizeFactor, + ); + if (_currentZoomLevel != newZoomLevel) { + _zoomDetails = MapZoomDetails( + localFocalPoint: downPoint, + globalFocalPoint: localToGlobal(downPoint), + previousZoomLevel: _currentZoomLevel, + newZoomLevel: newZoomLevel, + previousVisibleBounds: + _zoomDetails != null + ? _zoomDetails!.newVisibleBounds + : _controller.visibleLatLngBounds, + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: getFocalLatLng(newVisibleLatLngBounds), + ); + if (_state.widget.onWillZoom == null || + _state.widget.onWillZoom!(_zoomDetails!)) { + _zoomPanBehavior?.zoomLevel = _zoomDetails!.newZoomLevel!; + } + } + } + + void _handlePanningCallback(MapLatLng newLatLng) { + final Offset localFocalPoint = pixelFromLatLng( + newLatLng.latitude, + newLatLng.longitude, + _size, + _controller.shapeLayerOffset + _panDistanceBeforeFlinging, + _controller.shapeLayerSizeFactor, + ); + final Offset previousFocalPoint = pixelFromLatLng( + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset + _panDistanceBeforeFlinging, + _controller.shapeLayerSizeFactor, + ); + final Offset delta = _getValidPanDelta( + localFocalPoint - previousFocalPoint, + ); + final Rect visibleBounds = _controller.getVisibleBounds( + _controller.shapeLayerOffset + delta, + ); + final MapLatLngBounds newVisibleLatLngBounds = _controller + .getVisibleLatLngBounds( + visibleBounds.topRight, + visibleBounds.bottomLeft, + _controller.shapeLayerOffset + delta, + ); + _panDetails = MapPanDetails( + globalFocalPoint: localToGlobal(localFocalPoint), localFocalPoint: localFocalPoint, - globalFocalPoint: globalFocalPoint, - previousZoomLevel: _zoomPanBehavior!.zoomLevel, - newZoomLevel: newZoomLevel, - previousVisibleBounds: _zoomDetails != null - ? _zoomDetails!.newVisibleBounds - : _controller.visibleLatLngBounds, + zoomLevel: _zoomPanBehavior!.zoomLevel, + delta: delta, + previousVisibleBounds: + _panDetails != null + ? _panDetails!.newVisibleBounds + : _controller.visibleLatLngBounds, newVisibleBounds: newVisibleLatLngBounds, - focalLatLng: focalLatLng ?? getFocalLatLng(newVisibleLatLngBounds), + focalLatLng: newLatLng, ); - if (_state.widget.onWillZoom == null || - _state.widget.onWillZoom!(_zoomDetails!)) { - _zoomPanBehavior?.onZooming(_zoomDetails!); + if (_state.widget.onWillPan == null || + _state.widget.onWillPan!(_panDetails!)) { + _zoomPanBehavior?.focalLatLng = _panDetails!.focalLatLng; } } @@ -2839,14 +3168,14 @@ class _RenderGeoJSONLayer extends RenderStack _controller.localScale = _getScale(details.newZoomLevel!); _controller.pinchCenter = details.localFocalPoint!; _controller.updateVisibleBounds( - _controller.getZoomingTranslation() + _controller.normalize, - _controller.shapeLayerSizeFactor * _controller.localScale); + _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! @@ -2865,18 +3194,20 @@ class _RenderGeoJSONLayer extends RenderStack return; } if (!_isFlingAnimationActive && !_doubleTapEnabled) { - _state.zoomLevelAnimationController.duration = - const Duration(milliseconds: 650); + _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.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor, + ); } _controller.isInInteractive = true; _controller.gesture = Gesture.scale; @@ -2887,14 +3218,17 @@ class _RenderGeoJSONLayer extends RenderStack void _handleZoomLevelAnimation() { if (_zoomLevelTween.end != null) { - _currentZoomLevel = _zoomLevelTween.evaluate(_isFlingAnimationActive - ? _flingZoomLevelCurvedAnimation - : _zoomLevelCurvedAnimation!); + _currentZoomLevel = _zoomLevelTween.evaluate( + _isFlingAnimationActive + ? _flingZoomLevelCurvedAnimation + : _zoomLevelCurvedAnimation!, + ); } _controller.localScale = _getScale(_currentZoomLevel); _controller.updateVisibleBounds( - _controller.getZoomingTranslation() + _controller.normalize, - _controller.shapeLayerSizeFactor * _controller.localScale); + _controller.getZoomingTranslation() + _controller.normalize, + _controller.shapeLayerSizeFactor * _controller.localScale, + ); _validateEdges(_currentZoomLevel); _controller.notifyRefreshListeners(); markNeedsPaint(); @@ -2915,10 +3249,6 @@ class _RenderGeoJSONLayer extends RenderStack void _handleZoomingAnimationEnd([MapLatLng? latLng]) { _isFlingAnimationActive = false; _zoomEnd(); - if (!_isZoomedUsingToolbar && !_doubleTapEnabled) { - _invokeOnZooming(_getScale(_currentZoomLevel), focalLatLng: latLng); - } - _isZoomedUsingToolbar = false; _doubleTapEnabled = false; } @@ -2929,13 +3259,13 @@ class _RenderGeoJSONLayer extends RenderStack _zoomingDelayTimer = null; _zoomDetails = null; _panDetails = null; - if (_zoomPanBehavior != null && - _zoomPanBehavior!.enablePinching && - !_state.isSublayer) { + if (_zoomPanBehavior != null && !_state.isSublayer) { _controller.shapeLayerOffset = _controller.getZoomingTranslation() + _controller.normalize; - _controller.shapeLayerOrigin = _controller.getZoomingTranslation( - previousOrigin: _controller.shapeLayerOrigin) + + _controller.shapeLayerOrigin = + _controller.getZoomingTranslation( + previousOrigin: _controller.shapeLayerOrigin, + ) + _controller.normalize; _controller.shapeLayerSizeFactor *= _controller.localScale; _updateMapDataSourceForVisual(); @@ -2950,28 +3280,34 @@ class _RenderGeoJSONLayer extends RenderStack } void _invokeOnPanning( - Offset localFocalPoint, Offset previousFocalPoint, Offset focalPoint, - [bool canAvoidPanUpdate = false]) { + Offset localFocalPoint, + Offset previousFocalPoint, + Offset focalPoint, [ + bool canAvoidPanUpdate = false, + ]) { _avoidPanUpdate = canAvoidPanUpdate; - final Offset delta = - _getValidPanDelta(localFocalPoint - previousFocalPoint); + final Offset delta = _getValidPanDelta( + localFocalPoint - previousFocalPoint, + ); final Rect visibleBounds = _controller.getVisibleBounds( - _controller.shapeLayerOffset + - (canAvoidPanUpdate ? Offset.zero : delta)); - final MapLatLngBounds newVisibleLatLngBounds = - _controller.getVisibleLatLngBounds( - visibleBounds.topRight, - visibleBounds.bottomLeft, _controller.shapeLayerOffset + (canAvoidPanUpdate ? Offset.zero : delta), ); + final MapLatLngBounds newVisibleLatLngBounds = _controller + .getVisibleLatLngBounds( + visibleBounds.topRight, + visibleBounds.bottomLeft, + _controller.shapeLayerOffset + + (canAvoidPanUpdate ? Offset.zero : delta), + ); _panDetails = MapPanDetails( globalFocalPoint: focalPoint, localFocalPoint: localFocalPoint, zoomLevel: _zoomPanBehavior!.zoomLevel, delta: delta, - previousVisibleBounds: _panDetails != null - ? _panDetails!.newVisibleBounds - : _controller.visibleLatLngBounds, + previousVisibleBounds: + _panDetails != null + ? _panDetails!.newVisibleBounds + : _controller.visibleLatLngBounds, newVisibleBounds: newVisibleLatLngBounds, focalLatLng: getFocalLatLng(newVisibleLatLngBounds), ); @@ -2988,14 +3324,14 @@ class _RenderGeoJSONLayer extends RenderStack } if (_currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; _currentHoverItem = null; } if (!_state.isSublayer) { _controller.panDistance = details.delta!; - _controller - .updateVisibleBounds(_controller.shapeLayerOffset + details.delta!); + _controller.updateVisibleBounds( + _controller.shapeLayerOffset + details.delta!, + ); } markNeedsPaint(); @@ -3014,8 +3350,9 @@ class _RenderGeoJSONLayer extends RenderStack return; } if (!_isFlingAnimationActive) { - _state.focalLatLngAnimationController.duration = - const Duration(milliseconds: 650); + _state.focalLatLngAnimationController.duration = const Duration( + milliseconds: 650, + ); } if (_state.focalLatLngAnimationController.isAnimating) { @@ -3025,15 +3362,19 @@ class _RenderGeoJSONLayer extends RenderStack _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); + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset + _panDistanceBeforeFlinging, + _controller.shapeLayerSizeFactor, + ); + _referenceVisibleBounds = _controller.getVisibleBounds( + _controller.shapeLayerOffset, + ); _referenceShapeBounds = _getShapeBounds( - _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset); + _controller.shapeLayerSizeFactor, + _controller.shapeLayerOffset, + ); _controller.isInInteractive = true; _controller.gesture = Gesture.pan; _state.focalLatLngAnimationController.forward(from: 0.0); @@ -3041,20 +3382,24 @@ class _RenderGeoJSONLayer extends RenderStack } void _handleFocalLatLngAnimation() { - final MapLatLng latLng = _focalLatLngTween.evaluate(_isFlingAnimationActive - ? _flingFocalLatLngCurvedAnimation - : _focalLatLngCurvedAnimation); + final MapLatLng latLng = _focalLatLngTween.evaluate( + _isFlingAnimationActive + ? _flingFocalLatLngCurvedAnimation + : _focalLatLngCurvedAnimation, + ); final Offset localFocalPoint = pixelFromLatLng( - latLng.latitude, - latLng.longitude, - _size, - _controller.shapeLayerOffset + _panDistanceBeforeFlinging, - _controller.shapeLayerSizeFactor); + 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.shapeLayerOffset + _panDistanceBeforeFlinging + delta, + ); _controller.notifyRefreshListeners(); markNeedsPaint(); } @@ -3072,24 +3417,13 @@ class _RenderGeoJSONLayer extends RenderStack void _handleFocalLatLngAnimationEnd() { _isFlingAnimationActive = false; _panEnd(); - _referenceVisibleBounds = - _controller.getVisibleBounds(_controller.shapeLayerOffset); + _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); + _controller.shapeLayerSizeFactor, + _controller.shapeLayerOffset, + ); } void _panEnd() { @@ -3097,7 +3431,7 @@ class _RenderGeoJSONLayer extends RenderStack _zoomDetails = null; _panDetails = null; _panDistanceBeforeFlinging = Offset.zero; - if (_zoomPanBehavior!.enablePanning && !_state.isSublayer) { + if (!_state.isSublayer) { _controller.shapeLayerOffset += _controller.panDistance; _controller.shapeLayerOrigin += _controller.panDistance; _updateMapDataSourceForVisual(); @@ -3124,7 +3458,6 @@ class _RenderGeoJSONLayer extends RenderStack void _cancelZoomingAnimation() { _state.zoomLevelAnimationController.stop(); - _isZoomedUsingToolbar = false; _handleZoomingAnimationEnd(); if (_isAnimationOnQueue) { _isAnimationOnQueue = false; @@ -3171,9 +3504,10 @@ class _RenderGeoJSONLayer extends RenderStack if (_currentInteractedItem != null && deviceKind != PointerDeviceKind.mouse) { _invokeTooltip( - position: position, - model: _currentInteractedItem, - element: _currentInteractedElement); + position: position, + model: _currentInteractedItem, + element: _currentInteractedElement, + ); } _downLocalPoint = null; @@ -3187,22 +3521,27 @@ class _RenderGeoJSONLayer extends RenderStack if (_controller.gesture == null && _zoomPanBehavior != null) { double newZoomLevel = _currentZoomLevel + 1; newZoomLevel = newZoomLevel.clamp( - _zoomPanBehavior!.minZoomLevel, _zoomPanBehavior!.maxZoomLevel); + _zoomPanBehavior!.minZoomLevel, + _zoomPanBehavior!.maxZoomLevel, + ); if (newZoomLevel == _currentZoomLevel) { return; } - _state.zoomLevelAnimationController.duration = - const Duration(milliseconds: 200); + _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), - localFocalPoint: _downLocalPoint, - globalFocalPoint: _downGlobalPoint, - focalLatLng: _controller.visibleFocalLatLng); + _invokeOnZooming( + _getScale(newZoomLevel), + localFocalPoint: _downLocalPoint, + globalFocalPoint: _downGlobalPoint, + focalLatLng: _controller.visibleFocalLatLng, + ); } } @@ -3235,7 +3574,8 @@ class _RenderGeoJSONLayer extends RenderStack _state._updateLegendPointer(null); } _state._updateLegendPointer( - _currentInteractedItem!.colorValue?.toDouble()); + _currentInteractedItem!.colorValue?.toDouble(), + ); } else { _state._updateLegendPointer(null); } @@ -3298,7 +3638,9 @@ class _RenderGeoJSONLayer extends RenderStack Offset _getValidPanDelta(Offset delta) { final Rect currentShapeBounds = _getShapeBounds( - _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset + delta); + _controller.shapeLayerSizeFactor, + _controller.shapeLayerOffset + delta, + ); double dx = 0.0, dy = 0.0; if (_referenceVisibleBounds!.width < _referenceShapeBounds!.width) { dx = delta.dx; @@ -3327,11 +3669,16 @@ class _RenderGeoJSONLayer extends RenderStack 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); + 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); } @@ -3386,7 +3733,10 @@ class _RenderGeoJSONLayer extends RenderStack _currentInteractedItem = mapModel; } } else if (_isShapeContains( - position, mapModel, _currentInteractedElement) && + position, + mapModel, + _currentInteractedElement, + ) && !(wasToggled && _legend!.source == MapElement.shape)) { _currentInteractedItem = mapModel; _currentInteractedElement = MapLayerElement.shape; @@ -3408,21 +3758,26 @@ class _RenderGeoJSONLayer extends RenderStack } 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 != null && mapModel.shapePath!.contains(position); } void _performChildHover(Offset position) { final MapModel? currentInteractedItem = _currentInteractedItem; _invokeTooltip( - position: position, - model: _currentInteractedItem, - element: _currentInteractedElement, - kind: PointerKind.hover); + position: position, + model: _currentInteractedItem, + element: _currentInteractedElement, + kind: PointerKind.hover, + ); if (_state.widget.source.bubbleSizeMapper != null && _state.bubbleKey.currentContext != null) { final ShapeLayerChildRenderBoxBase bubbleRenderObject = @@ -3430,7 +3785,9 @@ class _RenderGeoJSONLayer extends RenderStack // ignore: avoid_as as ShapeLayerChildRenderBoxBase; bubbleRenderObject.onHover( - currentInteractedItem, _currentInteractedElement); + currentInteractedItem, + _currentInteractedElement, + ); } if (hasShapeHoverColor && @@ -3460,11 +3817,12 @@ class _RenderGeoJSONLayer extends RenderStack } } - void _invokeTooltip( - {MapModel? model, - Offset? position, - MapLayerElement? element, - PointerKind kind = PointerKind.touch}) { + void _invokeTooltip({ + MapModel? model, + Offset? position, + MapLayerElement? element, + PointerKind kind = PointerKind.touch, + }) { if ((_state.widget.shapeTooltipBuilder != null || _state.widget.bubbleTooltipBuilder != null) && _controller.tooltipKey != null && @@ -3476,18 +3834,27 @@ class _RenderGeoJSONLayer extends RenderStack 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; if (_state.isSublayer) { - sublayerIndex = - _state.ancestor.sublayers!.indexOf(_state.widget.sublayerAncestor!); + sublayerIndex = _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, - kind, sublayerIndex, position); + tooltipRenderObject.paintTooltip( + model?.dataIndex, + elementRect, + element, + kind, + sublayerIndex, + position, + ); } } @@ -3502,7 +3869,8 @@ class _RenderGeoJSONLayer extends RenderStack _currentSelectedItem = null; } else { _currentSelectedItem = _mapDataSource.values.firstWhere( - (MapModel element) => element.dataIndex == _selectedIndex); + (MapModel element) => element.dataIndex == _selectedIndex, + ); _currentSelectedItem!.isSelected = !_currentSelectedItem!.isSelected; if (_prevSelectedItem != null) { _prevSelectedItem!.isSelected = false; @@ -3518,8 +3886,9 @@ class _RenderGeoJSONLayer extends RenderStack if (_legend != null && _legend!.source == MapElement.shape) { late MapModel model; if (_state.widget.source.shapeColorMappers == null) { - model = - mapDataSource.values.elementAt(_controller.currentToggledItemIndex); + model = mapDataSource.values.elementAt( + _controller.currentToggledItemIndex, + ); } else { for (final MapModel mapModel in _mapDataSource.values) { if (mapModel.dataIndex != null && @@ -3531,10 +3900,11 @@ class _RenderGeoJSONLayer extends RenderStack } } - final Color shapeColor = (_currentSelectedItem != null && - _currentSelectedItem!.actualIndex == model.actualIndex) - ? _themeData.selectionColor! - : getActualShapeColor(model); + final Color shapeColor = + (_currentSelectedItem != null && + _currentSelectedItem!.actualIndex == model.actualIndex) + ? _themeData.selectionColor! + : getActualShapeColor(model); _forwardToggledShapeColorTween.begin = shapeColor; _reverseToggledShapeColorTween.end = shapeColor; _state.toggleAnimationController.forward(from: 0); @@ -3552,7 +3922,8 @@ class _RenderGeoJSONLayer extends RenderStack _controller ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset); + ..addResetListener(_handleReset) + ..addToolbarZoomedListener(_handleZoomingCallback); if (_state.isSublayer) { _controller ..addRefreshListener(_handleRefresh) @@ -3568,7 +3939,7 @@ class _RenderGeoJSONLayer extends RenderStack ..addListener(_handleFocalLatLngAnimation) ..addStatusListener(_handleFocalLatLngAnimationStatusChange); } - SchedulerBinding.instance?.addPostFrameCallback(_initiateInitialAnimations); + SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); } @override @@ -3582,7 +3953,8 @@ class _RenderGeoJSONLayer extends RenderStack _controller ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset); + ..removeResetListener(_handleReset) + ..removeToolbarZoomedListener(_handleZoomingCallback); if (_state.isSublayer) { _controller ..removeRefreshListener(_handleRefresh) @@ -3676,12 +4048,15 @@ class _RenderGeoJSONLayer extends RenderStack _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 && + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + final bool hasPrevSelectedItem = + _prevSelectedItem != null && !_controller.wasToggled(_prevSelectedItem!); - final bool hasCurrentSelectedItem = _currentSelectedItem != null && + final bool hasCurrentSelectedItem = + _currentSelectedItem != null && !_controller.wasToggled(_currentSelectedItem!); _mapDataSource.forEach((String key, MapModel model) { @@ -3698,17 +4073,20 @@ class _RenderGeoJSONLayer extends RenderStack fillPaint.color = _reverseSelectionColorTween.evaluate(_selectionColorAnimation)!; strokePaint - ..color = _reverseSelectionStrokeColorTween - .evaluate(_selectionColorAnimation)! + ..color = + _reverseSelectionStrokeColorTween.evaluate( + _selectionColorAnimation, + )! ..strokeWidth = _themeData.selectionStrokeWidth; } else if (_previousHoverItem != null && _previousHoverItem!.primaryKey == key && !_controller.wasToggled(_previousHoverItem!) && _previousHoverItem != _currentHoverItem) { - fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? (_reverseHoverColorTween.evaluate(_hoverColorAnimation) ?? - getActualShapeColor(model)) - : getActualShapeColor(model); + fillPaint.color = + _themeData.shapeHoverColor != Colors.transparent + ? (_reverseHoverColorTween.evaluate(_hoverColorAnimation) ?? + getActualShapeColor(model)) + : getActualShapeColor(model); if (_themeData.shapeHoverStrokeWidth! > 0.0 && _themeData.shapeHoverStrokeColor != Colors.transparent) { @@ -3729,8 +4107,9 @@ class _RenderGeoJSONLayer extends RenderStack context.canvas.drawPath(model.shapePath!, fillPaint); if (strokePaint.strokeWidth > 0.0 && strokePaint.color != Colors.transparent) { - strokePaint.strokeWidth = - _getIntrinsicStrokeWidth(strokePaint.strokeWidth); + strokePaint.strokeWidth = _getIntrinsicStrokeWidth( + strokePaint.strokeWidth, + ); context.canvas.drawPath(model.shapePath!, strokePaint); } }); @@ -3745,13 +4124,19 @@ class _RenderGeoJSONLayer extends RenderStack // Set the color to the toggled and un-toggled shapes based on // the [legendController.toggledIndices] collection. void _updateFillColor( - MapModel model, Paint fillPaint, bool hasToggledIndices) { + MapModel model, + Paint fillPaint, + bool hasToggledIndices, + ) { fillPaint.style = PaintingStyle.fill; if (_legend != null && _legend!.source == MapElement.shape) { if (_controller.currentToggledItemIndex == model.legendMapperIndex) { - final Color? shapeColor = _controller.wasToggled(model) - ? _forwardToggledShapeColorTween.evaluate(_toggleShapeAnimation) - : _reverseToggledShapeColorTween.evaluate(_toggleShapeAnimation); + final Color? shapeColor = + _controller.wasToggled(model) + ? _forwardToggledShapeColorTween.evaluate(_toggleShapeAnimation) + : _reverseToggledShapeColorTween.evaluate( + _toggleShapeAnimation, + ); // Set tween color to the shape based on the currently tapped // legend item. If the legend item is toggled, then the // [_forwardToggledShapeColorTween] return. If the legend item is @@ -3772,23 +4157,30 @@ class _RenderGeoJSONLayer extends RenderStack // Set the stroke paint to the toggled and un-toggled shapes based on // the [legendController.toggledIndices] collection. void _updateStrokePaint( - MapModel model, Paint strokePaint, bool hasToggledIndices) { + MapModel model, + Paint strokePaint, + bool hasToggledIndices, + ) { if (_legend != null && _legend!.source == MapElement.shape) { if (_controller.currentToggledItemIndex == model.legendMapperIndex) { - final Color? shapeStrokeColor = _controller.wasToggled(model) - ? _forwardToggledShapeStrokeColorTween - .evaluate(_toggleShapeAnimation) - : _reverseToggledShapeStrokeColorTween - .evaluate(_toggleShapeAnimation); + final Color? shapeStrokeColor = + _controller.wasToggled(model) + ? _forwardToggledShapeStrokeColorTween.evaluate( + _toggleShapeAnimation, + ) + : _reverseToggledShapeStrokeColorTween.evaluate( + _toggleShapeAnimation, + ); // Set tween color to the shape stroke based on the currently // tapped legend item. If the legend item is toggled, then the // [_forwardToggledShapeStrokeColorTween] return. If the legend item is // un-toggled, then the [_reverseToggledShapeStrokeColorTween] return. strokePaint ..color = shapeStrokeColor ?? Colors.transparent - ..strokeWidth = _controller.wasToggled(model) - ? _themeData.toggledItemStrokeWidth! - : _themeData.layerStrokeWidth; + ..strokeWidth = + _controller.wasToggled(model) + ? _themeData.toggledItemStrokeWidth! + : _themeData.layerStrokeWidth; return; } else if (hasToggledIndices && _controller.wasToggled(model)) { // Set toggled stroke color to the previously toggled shapes. @@ -3817,7 +4209,10 @@ class _RenderGeoJSONLayer extends RenderStack } void _drawSelectedShape( - PaintingContext context, Paint fillPaint, Paint strokePaint) { + PaintingContext context, + Paint fillPaint, + Paint strokePaint, + ) { if (_currentSelectedItem != null && !_controller.wasToggled(_currentSelectedItem!)) { fillPaint.color = @@ -3825,29 +4220,37 @@ class _RenderGeoJSONLayer extends RenderStack context.canvas.drawPath(_currentSelectedItem!.shapePath!, fillPaint); if (_themeData.selectionStrokeWidth > 0.0) { strokePaint - ..color = _forwardSelectionStrokeColorTween - .evaluate(_selectionColorAnimation)! - ..strokeWidth = - _getIntrinsicStrokeWidth(_themeData.selectionStrokeWidth); + ..color = + _forwardSelectionStrokeColorTween.evaluate( + _selectionColorAnimation, + )! + ..strokeWidth = _getIntrinsicStrokeWidth( + _themeData.selectionStrokeWidth, + ); context.canvas.drawPath(_currentSelectedItem!.shapePath!, strokePaint); } } } void _drawHoverShape( - PaintingContext context, Paint fillPaint, Paint strokePaint) { + PaintingContext context, + Paint fillPaint, + Paint strokePaint, + ) { if (_currentHoverItem != null) { - fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColorTween.evaluate(_hoverColorAnimation)! - : getActualShapeColor(_currentHoverItem!); + fillPaint.color = + _themeData.shapeHoverColor != Colors.transparent + ? _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)! - ..strokeWidth = - _getIntrinsicStrokeWidth(_themeData.shapeHoverStrokeWidth!); + ..strokeWidth = _getIntrinsicStrokeWidth( + _themeData.shapeHoverStrokeWidth!, + ); } else { strokePaint ..color = _themeData.layerStrokeColor! 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 6575688cd..86528e509 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart @@ -13,15 +13,23 @@ import '../elements/toolbar.dart'; import '../elements/tooltip.dart'; import '../layer/vector_layers.dart'; import '../layer/zoomable.dart'; +import '../theme.dart'; import '../utils.dart'; Offset _pixelFromLatLng(MapLatLng latLng, double scale) { - final double latitude = - latLng.latitude.clamp(minimumLatitude, maximumLatitude); - final double longitude = - latLng.longitude.clamp(minimumLongitude, maximumLongitude); + final double latitude = latLng.latitude.clamp( + minimumLatitude, + maximumLatitude, + ); + final double longitude = latLng.longitude.clamp( + minimumLongitude, + maximumLongitude, + ); return pixelFromLatLng( - latitude, longitude, Size.square(getTotalTileWidth(scale))); + latitude, + longitude, + Size.square(getTotalTileWidth(scale)), + ); } MapLatLng _pixelToLatLng(Offset point, double scale) { @@ -53,7 +61,7 @@ class _MapTileCoordinate { String toString() => '_MapTileCoordinate($x, $y, $z)'; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (other is _MapTileCoordinate) { return x == other.x && y == other.y && z == other.z; } @@ -61,7 +69,7 @@ class _MapTileCoordinate { } @override - int get hashCode => hashValues(x.hashCode, y.hashCode, z.hashCode); + int get hashCode => Object.hash(x.hashCode, y.hashCode, z.hashCode); } /// Represents the information about the tile. @@ -114,10 +122,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); - final Offset actualCenterPixelPosition = - _pixelFromLatLng(_state._currentFocalLatLng, _state._currentZoomLevel); + (_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 _pixelToLatLng(newCenterPoint, _state._currentZoomLevel); @@ -127,6 +138,7 @@ class MapTileLayerController extends MapLayerController { // ignore_for_file: public_member_api_docs class TileLayer extends StatefulWidget { const TileLayer({ + super.key, required this.urlTemplate, required this.initialFocalLatLng, required this.initialZoomLevel, @@ -226,15 +238,19 @@ class _TileLayerState extends State with TickerProviderStateMixin { void _requestAndPopulateNewTiles() { _updateZoomLevel(); final double tileCount = pow(2, _nextZoomLevel).toDouble(); - final Offset actualCenterPixelPosition = - _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); + final Offset actualCenterPixelPosition = _pixelFromLatLng( + _currentFocalLatLng, + _currentZoomLevel, + ); final Rect newVisibleBounds = Rect.fromCenter( - center: actualCenterPixelPosition, - width: _size!.width, - height: _size!.height); + center: actualCenterPixelPosition, + width: _size!.width, + height: _size!.height, + ); final MapLatLngBounds visibleLatLngBounds = MapLatLngBounds( - _pixelToLatLng(newVisibleBounds.topRight, _currentZoomLevel), - _pixelToLatLng(newVisibleBounds.bottomLeft, _currentZoomLevel)); + _pixelToLatLng(newVisibleBounds.topRight, _currentZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, _currentZoomLevel), + ); _updateVisibleBounds(visibleLatLngBounds); final Offset halfSize = Offset(_size!.width, _size!.height) / 2; @@ -259,8 +275,11 @@ class _TileLayerState extends State with TickerProviderStateMixin { for (int i = startX; i <= endX; i++) { for (int j = startY; j <= endY; j++) { - final _MapTileCoordinate tileCoordinate = - _MapTileCoordinate(i, j, _nextZoomLevel); + final _MapTileCoordinate tileCoordinate = _MapTileCoordinate( + i, + j, + _nextZoomLevel, + ); if ((tileCoordinate.x < _globalTileStart.dx || tileCoordinate.x > globalTileEnd.dx) || @@ -279,11 +298,15 @@ class _TileLayerState extends State with TickerProviderStateMixin { } final Offset tileCenter = Offset((startX + endX) / 2, (startY + endY) / 2); - tileCoordinates.sort((_MapTileCoordinate a, _MapTileCoordinate b) => - (a.distanceTo(Offset(a.x.toDouble(), a.y.toDouble()), tileCenter) - - b.distanceTo( - Offset(b.x.toDouble(), b.y.toDouble()), tileCenter)) - .toInt()); + tileCoordinates.sort( + (_MapTileCoordinate a, _MapTileCoordinate b) => + (a.distanceTo(Offset(a.x.toDouble(), a.y.toDouble()), tileCenter) - + b.distanceTo( + Offset(b.x.toDouble(), b.y.toDouble()), + tileCenter, + )) + .toInt(), + ); for (int i = 0; i < tileCoordinates.length; i++) { _addTile(tileCoordinates[i]); @@ -294,17 +317,18 @@ class _TileLayerState extends State with TickerProviderStateMixin { final TileZoomLevelDetails level = _levels[_nextZoomLevel]!; Offset topRight = _pixelFromLatLng(latLngBounds.northeast, level.zoomLevel) - - level.origin!; + level.origin!; // Adjust the topRight value based on the current scale value. - topRight = Offset(topRight.dx * level.scale, topRight.dy * level.scale) + + topRight = + Offset(topRight.dx * level.scale, topRight.dy * level.scale) + level.translatePoint; Offset bottomLeft = _pixelFromLatLng(latLngBounds.southwest, level.zoomLevel) - - level.origin!; + level.origin!; // Adjust the bottomLeft value based on the current scale value. bottomLeft = Offset(bottomLeft.dx * level.scale, bottomLeft.dy * level.scale) + - level.translatePoint; + level.translatePoint; _controller!.visibleBounds = Rect.fromPoints(bottomLeft, topRight); } @@ -350,7 +374,10 @@ class _TileLayerState extends State with TickerProviderStateMixin { (_zoomController != null && _zoomController!.actionType == ActionType.pinch)) { _updateZoomLevelTransform( - level, _currentFocalLatLng, _currentZoomLevel); + level, + _currentFocalLatLng, + _currentZoomLevel, + ); } } @@ -389,7 +416,10 @@ class _TileLayerState extends State with TickerProviderStateMixin { // Calculate amount of scale and translation for each zoom level while // scaling. void _updateZoomLevelTransform( - TileZoomLevelDetails level, MapLatLng center, double zoom) { + TileZoomLevelDetails level, + MapLatLng center, + double zoom, + ) { if (level.origin == null) { return; } @@ -397,8 +427,9 @@ class _TileLayerState extends State with TickerProviderStateMixin { 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.origin!.dx * currentScale) - scaledPixelOrigin.dx, + (level.origin!.dy * currentScale) - scaledPixelOrigin.dy, + ); level.scale = currentScale; if (level.zoomLevel == _nextZoomLevel) { @@ -426,10 +457,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { xyzKey: tileFactorToKey, tilePos: Offset(tileLeftPos, tileTopPos), level: _levels[tileFactor.z]!, - image: Image.network( - url, - fit: BoxFit.fill, - ), + image: Image.network(url, fit: BoxFit.fill), ); } @@ -572,46 +600,56 @@ class _TileLayerState extends State with TickerProviderStateMixin { final List children = []; Widget current; - current = Stack(children: [ - _buildTiles(), - if (_hasSublayer) - SublayerContainer(ancestor: _ancestor, children: widget.sublayers!), - if (_markers != null && _markers!.isNotEmpty) - MarkerContainer( - markerTooltipBuilder: widget.markerTooltipBuilder, - controller: _controller!, - children: _markers, - ), - ]); + current = Stack( + children: [ + _buildTiles(), + if (_hasSublayer) + SublayerContainer(ancestor: _ancestor, children: widget.sublayers!), + if (_markers != null && _markers!.isNotEmpty) + MarkerContainer( + markerTooltipBuilder: widget.markerTooltipBuilder, + controller: _controller!, + themeData: _mapsThemeData, + children: _markers, + ), + ], + ); children.add(current); if (widget.zoomPanBehavior != null) { - children.add(BehaviorView( - zoomLevel: _currentZoomLevel, - focalLatLng: _currentFocalLatLng, - controller: _controller!, - behavior: widget.zoomPanBehavior!, - onWillZoom: widget.onWillZoom, - onWillPan: widget.onWillPan, - )); - if (_isDesktop && widget.zoomPanBehavior!.showToolbar) { - children.add(MapToolbar( + children.add( + BehaviorView( + zoomLevel: _currentZoomLevel, + focalLatLng: _currentFocalLatLng, + controller: _controller!, + behavior: widget.zoomPanBehavior!, onWillZoom: widget.onWillZoom, - zoomPanBehavior: widget.zoomPanBehavior!, - controller: _controller, - )); + onWillPan: widget.onWillPan, + enableMouseWheelZooming: + widget.zoomPanBehavior!.enableMouseWheelZooming, + ), + ); + if (_isDesktop && widget.zoomPanBehavior!.showToolbar) { + children.add( + MapToolbar( + zoomPanBehavior: widget.zoomPanBehavior!, + controller: _controller, + ), + ); } } if (_hasTooltipBuilder()) { - children.add(MapTooltip( - key: _controller!.tooltipKey, - controller: _controller, - sublayers: widget.sublayers, - markerTooltipBuilder: widget.markerTooltipBuilder, - tooltipSettings: widget.tooltipSettings, - themeData: _mapsThemeData, - )); + children.add( + MapTooltip( + key: _controller!.tooltipKey, + controller: _controller, + sublayers: widget.sublayers, + markerTooltipBuilder: widget.markerTooltipBuilder, + tooltipSettings: widget.tooltipSettings, + themeData: _mapsThemeData, + ), + ); } return SizedBox( @@ -622,9 +660,9 @@ class _TileLayerState extends State with TickerProviderStateMixin { } void _handledZoomPanChange() { - if (SchedulerBinding.instance!.schedulerPhase == + if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) { - SchedulerBinding.instance!.addPostFrameCallback((Duration duration) { + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { _handledZoomPanChange(); }); return; @@ -633,17 +671,21 @@ class _TileLayerState extends State with TickerProviderStateMixin { setState(() { _currentZoomLevel = _zoomController!.zoomLevel; final Offset point = Offset( - (_size!.width / 2) - _zoomController!.actualRect.left, - (_size!.height / 2) - _zoomController!.actualRect.top); + (_size!.width / 2) - _zoomController!.actualRect.left, + (_size!.height / 2) - _zoomController!.actualRect.top, + ); final MapLatLng newFocalLatLng = pixelToLatLng( - point, Size.square(getTotalTileWidth(_currentZoomLevel))); + point, + Size.square(getTotalTileWidth(_currentZoomLevel)), + ); _currentFocalLatLng = newFocalLatLng; _handleTransform(); if (_hasSublayer) { _controller! ..visibleFocalLatLng = _currentFocalLatLng ..tileZoomLevel = _currentZoomLevel; - if (_zoomController!.actionType == ActionType.fling) { + if (_zoomController!.actionType == ActionType.pinchFling || + _zoomController!.actionType == ActionType.panFling) { _invalidateSublayer(); } } @@ -663,12 +705,13 @@ class _TileLayerState extends State with TickerProviderStateMixin { void _initializeController() { _ancestor = context.dependOnInheritedWidgetOfExactType()!; - _controller ??= _ancestor.controller - ..tileZoomLevel = _currentZoomLevel - ..visibleFocalLatLng = _currentFocalLatLng; + _controller ??= + _ancestor.controller + ..tileZoomLevel = _currentZoomLevel + ..visibleFocalLatLng = _currentFocalLatLng; if (_ancestor.zoomController != null) { - _zoomController ??= _ancestor.zoomController! - ..addListener(_handledZoomPanChange); + _zoomController ??= + _ancestor.zoomController!..addListener(_handledZoomPanChange); } } @@ -676,16 +719,22 @@ class _TileLayerState extends State with TickerProviderStateMixin { final MapLatLngBounds? bounds = widget.zoomPanBehavior?.latLngBounds ?? widget.initialLatLngBounds; if (bounds != null) { - final double zoomLevel = - getZoomLevel(bounds, _controller!.layerType!, _size!); + final double zoomLevel = getZoomLevel( + bounds, + _controller!.layerType!, + _size!, + ); _currentFocalLatLng = getFocalLatLng(bounds); if (widget.zoomPanBehavior != null) { _currentZoomLevel = zoomLevel.clamp( - widget.zoomPanBehavior!.minZoomLevel, - widget.zoomPanBehavior!.maxZoomLevel); + widget.zoomPanBehavior!.minZoomLevel, + widget.zoomPanBehavior!.maxZoomLevel, + ); } else { - _currentZoomLevel = - zoomLevel.clamp(kDefaultMinZoomLevel, kDefaultMaxZoomLevel); + _currentZoomLevel = zoomLevel.clamp( + kDefaultMinZoomLevel, + kDefaultMaxZoomLevel, + ); } _nextZoomLevel = _currentZoomLevel.floor(); _controller! @@ -698,10 +747,11 @@ class _TileLayerState extends State with TickerProviderStateMixin { void initState() { _currentFocalLatLng = widget.zoomPanBehavior?.focalLatLng ?? widget.initialFocalLatLng; - _currentZoomLevel = (widget.zoomPanBehavior != null && - widget.zoomPanBehavior!.zoomLevel != 1) - ? widget.zoomPanBehavior!.zoomLevel - : widget.initialZoomLevel.toDouble(); + _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) { @@ -747,19 +797,32 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - _isDesktop = kIsWeb || + final MapsThemeData effectiveMapsThemeData = MapsThemeData(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 ?? + tooltipColor: + widget.tooltipSettings.color ?? + _mapsThemeData.tooltipColor ?? + effectiveMapsThemeData.tooltipColor, + tooltipStrokeColor: + widget.tooltipSettings.strokeColor ?? + _mapsThemeData.tooltipStrokeColor ?? + effectiveMapsThemeData.tooltipStrokeColor, + tooltipStrokeWidth: + widget.tooltipSettings.strokeWidth ?? _mapsThemeData.tooltipStrokeWidth, - tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius - .resolve(Directionality.of(context)), + tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius.resolve( + Directionality.of(context), + ), + markerIconColor: + _mapsThemeData.markerIconColor ?? + effectiveMapsThemeData.markerIconColor, ); return LayoutBuilder( 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 87f626277..606613059 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart @@ -31,8 +31,10 @@ Offset _getTranslation(MapController controller) { Offset _getScaledOffset(Offset offset, MapController controller) { if (controller.layerType == LayerType.tile) { - return Offset(offset.dx * controller.tileCurrentLevelDetails.scale, - offset.dy * controller.tileCurrentLevelDetails.scale) + + return Offset( + offset.dx * controller.tileCurrentLevelDetails.scale, + offset.dy * controller.tileCurrentLevelDetails.scale, + ) + controller.tileCurrentLevelDetails.translatePoint; } @@ -41,13 +43,19 @@ Offset _getScaledOffset(Offset offset, MapController controller) { // To calculate a point along a line with a radius away from another point. Offset _getPointAlongLineWithCap( - Offset start, Offset end, double capRadius, bool canUpdateStartPoint) { + Offset start, + Offset end, + double capRadius, + bool canUpdateStartPoint, +) { // Calculate distance between two points. // i.e, d = sqrt[(x1 - x2)^2 + (y1 - y2)^2]. // Here, x1 = end point's dx value, x2 = start point's dx value, // y1 = end point's dy value and y2 = start point's dy value. - final double lineLength = sqrt((end.dx - start.dx) * (end.dx - start.dx) + - (end.dy - start.dy) * (end.dy - start.dy)); + final double lineLength = sqrt( + (end.dx - start.dx) * (end.dx - start.dx) + + (end.dy - start.dy) * (end.dy - start.dy), + ); if (canUpdateStartPoint) { // Calculated the center point, if the distance between the two points // is less than the cap diameter. @@ -62,8 +70,9 @@ Offset _getPointAlongLineWithCap( // y1 = start point's dy value, y2 = end point's dy value // and d = startPointDifference. return Offset( - (1 - startPointDifference) * start.dx + startPointDifference * end.dx, - (1 - startPointDifference) * start.dy + startPointDifference * end.dy); + (1 - startPointDifference) * start.dx + startPointDifference * end.dx, + (1 - startPointDifference) * start.dy + startPointDifference * end.dy, + ); } else { // Returned the start point, if the distance between the two points // is less than the cap radius. @@ -78,44 +87,51 @@ Offset _getPointAlongLineWithCap( // y1 = start point's dy value, y2 = end point's dy value // and d = endPointDifference. return Offset( - (1 - endPointDifference) * start.dx + endPointDifference * end.dx, - (1 - endPointDifference) * start.dy + endPointDifference * end.dy); + (1 - endPointDifference) * start.dx + endPointDifference * end.dx, + (1 - endPointDifference) * start.dy + endPointDifference * end.dy, + ); } } void _drawInvertedPath( - PaintingContext context, - Path path, - MapController controller, - Paint fillPaint, - Paint strokePaint, - Offset offset) { + 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); + 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) + Path.combine( + PathOperation.difference, + Path()..addRect(controller.visibleBounds!), + path, + ), + fillPaint, + ) ..drawPath(path, strokePaint); } } Color _getHoverColor( - Color? elementColor, Color layerColor, SfMapsThemeData themeData) { + Color? elementColor, + Color layerColor, + SfMapsThemeData themeData, +) { final Color color = elementColor ?? layerColor; return themeData.shapeHoverColor != null && themeData.shapeHoverColor != Colors.transparent @@ -126,10 +142,8 @@ Color _getHoverColor( /// Base class for all vector layers. abstract class MapVectorLayer extends MapSublayer { /// Creates a [MapVectorLayer]. - const MapVectorLayer({ - Key? key, - IndexedWidgetBuilder? tooltipBuilder, - }) : super(key: key, tooltipBuilder: tooltipBuilder); + const MapVectorLayer({Key? key, IndexedWidgetBuilder? tooltipBuilder}) + : super(key: key, tooltipBuilder: tooltipBuilder); } /// A sublayer which renders group of [MapLine] on [MapShapeLayer] and @@ -675,14 +689,17 @@ class MapLineLayer extends MapVectorLayer { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); if (lines.isNotEmpty) { - final _DebugVectorShapeTree pointerTreeNode = - _DebugVectorShapeTree(lines); + final _DebugVectorShapeTree pointerTreeNode = _DebugVectorShapeTree( + lines, + ); properties.add(pointerTreeNode.toDiagnosticsNode()); } - properties - .add(ObjectFlagProperty>.has('animation', animation)); - properties.add(ObjectFlagProperty.has( - 'tooltip', tooltipBuilder)); + properties.add( + ObjectFlagProperty>.has('animation', animation), + ); + properties.add( + ObjectFlagProperty.has('tooltip', tooltipBuilder), + ); properties.add(DiagnosticsProperty>('dashArray', dashArray)); if (color != null) { @@ -729,14 +746,17 @@ class _MapLineLayerState extends State<_MapLineLayer> void initState() { super.initState(); _hoverAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); } @override void didChangeDependencies() { if (_controller == null) { - ancestor = context - .dependOnInheritedWidgetOfExactType()!; + ancestor = + context + .dependOnInheritedWidgetOfExactType()!; _controller = ancestor.controller; } super.didChangeDependencies(); @@ -752,7 +772,8 @@ class _MapLineLayerState extends State<_MapLineLayer> @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - final bool isDesktop = kIsWeb || + final bool isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -761,10 +782,11 @@ class _MapLineLayerState extends State<_MapLineLayer> controller: _controller, lines: widget.lines, animation: widget.animation, - color: widget.color ?? - (_mapsThemeData.brightness == Brightness.light - ? themeData.colorScheme.onSurface.withOpacity(0.44) - : themeData.colorScheme.onSurface.withOpacity(0.78)), + color: + widget.color ?? + (themeData.brightness == Brightness.light + ? themeData.colorScheme.onSurface.withValues(alpha: 0.44) + : themeData.colorScheme.onSurface.withValues(alpha: 0.78)), width: widget.width, strokeCap: widget.strokeCap, dashArray: widget.dashArray, @@ -861,20 +883,22 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { required this.isDesktop, required this.hoverAnimationController, required this.state, - }) : _controller = controller, - _lines = lines, - _animation = animation, - _color = color, - _width = width, - _strokeCap = strokeCap, - _dashArray = dashArray, - _tooltipBuilder = tooltipBuilder, - _themeData = themeData { + }) : _controller = controller, + _lines = lines, + _animation = animation, + _color = color, + _width = width, + _strokeCap = strokeCap, + _dashArray = dashArray, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData { selectedLinePoints = []; _forwardHoverColor = ColorTween(); _reverseHoverColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( - parent: hoverAnimationController, curve: Curves.easeInOut); + parent: hoverAnimationController, + curve: Curves.easeInOut, + ); linesInList = _lines.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } @@ -987,8 +1011,11 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { void _updateHoverItemTween() { if (isDesktop) { - final Color hoverStrokeColor = - _getHoverColor(selectedLine?.color, _color, _themeData); + final Color hoverStrokeColor = _getHoverColor( + selectedLine?.color, + _color, + _themeData, + ); final Color beginColor = selectedLine?.color ?? _color; if (_previousHoverItem != null) { @@ -1034,8 +1061,10 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleInteraction(Offset position, - [PointerKind kind = PointerKind.touch]) { + void _handleInteraction( + Offset position, [ + PointerKind kind = PointerKind.touch, + ]) { if (_controller != null && _controller!.tooltipKey != null && _controller!.tooltipKey!.currentContext != null) { @@ -1047,10 +1076,11 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { 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), - min(startPoint.dy, endPoint.dy) + - ((startPoint.dy - endPoint.dy).abs() / 2)); + min(startPoint.dx, endPoint.dx) + + ((startPoint.dx - endPoint.dx).abs() / 2), + min(startPoint.dy, endPoint.dy) + + ((startPoint.dy - endPoint.dy).abs() / 2), + ); position = !paintBounds.contains(lineMidPosition) ? position : lineMidPosition; tooltipRenderer.paintTooltip( @@ -1086,13 +1116,16 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation!.isCompleted) { + if ((_animation != null && !_animation!.isCompleted) || + linesInList == null || + linesInList!.isEmpty) { return false; } - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + 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) { @@ -1118,7 +1151,11 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { endPoint = _getScaledOffset(endPoint, _controller!); if (_liesPointOnLine( - startPoint, endPoint, actualTouchTolerance, position)) { + startPoint, + endPoint, + actualTouchTolerance, + position, + )) { selectedLine = line; selectedIndex = index; selectedLinePoints! @@ -1148,7 +1185,9 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { // ignore: avoid_as final RenderBox renderBox = context.findRenderObject()! as RenderBox; _handleInteraction( - renderBox.globalToLocal(event.position), PointerKind.hover); + renderBox.globalToLocal(event.position), + PointerKind.hover, + ); } } @@ -1200,13 +1239,15 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { context.canvas.save(); Offset startPoint; Offset endPoint; - final Paint paint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; + final Paint paint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; Path path = Path(); - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Size boxSize = + _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; final Offset translationOffset = _getTranslation(_controller!); _controller!.applyTransform(context, offset, true); @@ -1225,15 +1266,25 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { translationOffset, _controller!.shapeLayerSizeFactor, ); - final double strokeWidth = - _getCurrentWidth(line.width ?? _width, _controller!); + final double strokeWidth = _getCurrentWidth( + line.width ?? _width, + _controller!, + ); final StrokeCap strokeCap = line.strokeCap ?? _strokeCap; final bool hasCap = strokeCap != StrokeCap.butt; if (hasCap) { startPoint = _getPointAlongLineWithCap( - startPoint, endPoint, strokeWidth / 2, true); + startPoint, + endPoint, + strokeWidth / 2, + true, + ); endPoint = _getPointAlongLineWithCap( - startPoint, endPoint, strokeWidth / 2, false); + startPoint, + endPoint, + strokeWidth / 2, + false, + ); } if (_previousHoverItem != null && @@ -1261,8 +1312,13 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { if (line.dashArray != null) { assert(line.dashArray!.length >= 2 && line.dashArray!.length.isEven); - _drawDashedLine(context.canvas, line.dashArray!, paint, path, - hasCap ? strokeWidth / 2 : 0); + _drawDashedLine( + context.canvas, + line.dashArray!, + paint, + path, + hasCap ? strokeWidth / 2 : 0, + ); } else { _drawDashedLine(context.canvas, _dashArray, paint, path); } @@ -1744,10 +1800,12 @@ class MapArcLayer extends MapVectorLayer { final _DebugVectorShapeTree pointerTreeNode = _DebugVectorShapeTree(arcs); properties.add(pointerTreeNode.toDiagnosticsNode()); } - properties - .add(ObjectFlagProperty>.has('animation', animation)); - properties.add(ObjectFlagProperty.has( - 'tooltip', tooltipBuilder)); + properties.add( + ObjectFlagProperty>.has('animation', animation), + ); + properties.add( + ObjectFlagProperty.has('tooltip', tooltipBuilder), + ); properties.add(DiagnosticsProperty>('dashArray', dashArray)); if (color != null) { properties.add(ColorProperty('color', color)); @@ -1790,14 +1848,17 @@ class _MapArcLayerState extends State<_MapArcLayer> void initState() { super.initState(); _hoverAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); } @override void didChangeDependencies() { if (_controller == null) { - ancestor = context - .dependOnInheritedWidgetOfExactType()!; + ancestor = + context + .dependOnInheritedWidgetOfExactType()!; _controller = ancestor.controller; } super.didChangeDependencies(); @@ -1813,7 +1874,8 @@ class _MapArcLayerState extends State<_MapArcLayer> @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - final bool isDesktop = kIsWeb || + final bool isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -1822,10 +1884,11 @@ class _MapArcLayerState extends State<_MapArcLayer> controller: _controller, arcs: widget.arcs, animation: widget.animation, - color: widget.color ?? - (_mapsThemeData.brightness == Brightness.light - ? themeData.colorScheme.onSurface.withOpacity(0.44) - : themeData.colorScheme.onSurface.withOpacity(0.78)), + color: + widget.color ?? + (themeData.brightness == Brightness.light + ? themeData.colorScheme.onSurface.withValues(alpha: 0.44) + : themeData.colorScheme.onSurface.withValues(alpha: 0.78)), width: widget.width, dashArray: widget.dashArray, tooltipBuilder: widget.tooltipBuilder, @@ -1916,18 +1979,20 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { required this.isDesktop, required this.hoverAnimationController, required this.state, - }) : _controller = controller, - _arcs = arcs, - _color = color, - _width = width, - _dashArray = dashArray, - _animation = animation, - _tooltipBuilder = tooltipBuilder, - _themeData = themeData { + }) : _controller = controller, + _arcs = arcs, + _color = color, + _width = width, + _dashArray = dashArray, + _animation = animation, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData { _forwardHoverColor = ColorTween(); _reverseHoverColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( - parent: hoverAnimationController, curve: Curves.easeInOut); + parent: hoverAnimationController, + curve: Curves.easeInOut, + ); arcsInList = _arcs.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } @@ -2029,8 +2094,11 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { void _updateHoverItemTween() { if (isDesktop) { - final Color hoverStrokeColor = - _getHoverColor(selectedArc.color, _color, _themeData); + final Color hoverStrokeColor = _getHoverColor( + selectedArc.color, + _color, + _themeData, + ); final Color beginColor = selectedArc.color ?? _color; if (_previousHoverItem != null) { @@ -2096,8 +2164,10 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { // ignore: override_on_non_overriding_member bool get validForMouseTracker => true; - void _handleInteraction(Offset position, - [PointerKind kind = PointerKind.touch]) { + void _handleInteraction( + Offset position, [ + PointerKind kind = PointerKind.touch, + ]) { if (_controller != null && _controller!.tooltipKey != null && _controller!.tooltipKey!.currentContext != null) { @@ -2118,13 +2188,16 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation!.isCompleted) { + if ((_animation != null && !_animation!.isCompleted) || + arcsInList == null || + arcsInList!.isEmpty) { return false; } - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + 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) { @@ -2149,10 +2222,19 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { startPoint = _getScaledOffset(startPoint, _controller!); endPoint = _getScaledOffset(endPoint, _controller!); final Offset controlPoint = _calculateControlPoint( - startPoint, endPoint, arc.heightFactor, arc.controlPointFactor); + startPoint, + endPoint, + arc.heightFactor, + arc.controlPointFactor, + ); - if (_liesPointOnArc(startPoint, endPoint, controlPoint, - actualTouchTolerance, position)) { + if (_liesPointOnArc( + startPoint, + endPoint, + controlPoint, + actualTouchTolerance, + position, + )) { selectedArc = arc; selectedIndex = index; return true; @@ -2178,7 +2260,9 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { // ignore: avoid_as final RenderBox renderBox = context.findRenderObject()! as RenderBox; _handleInteraction( - renderBox.globalToLocal(event.position), PointerKind.hover); + renderBox.globalToLocal(event.position), + PointerKind.hover, + ); } } @@ -2230,13 +2314,15 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { context.canvas.save(); Offset startPoint; Offset endPoint; - final Paint paint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; + final Paint paint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; Path path = Path(); - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Size boxSize = + _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; final Offset translationOffset = _getTranslation(_controller!); _controller!.applyTransform(context, offset, true); @@ -2256,7 +2342,11 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { _controller!.shapeLayerSizeFactor, ); final Offset controlPoint = _calculateControlPoint( - startPoint, endPoint, arc.heightFactor, arc.controlPointFactor); + startPoint, + endPoint, + arc.heightFactor, + arc.controlPointFactor, + ); if (_previousHoverItem != null && _previousHoverItem == arc && @@ -2275,7 +2365,11 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { ..reset() ..moveTo(startPoint.dx, startPoint.dy) ..quadraticBezierTo( - controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy); + controlPoint.dx, + controlPoint.dy, + endPoint.dx, + endPoint.dy, + ); if (_animation != null) { path = _getAnimatedPath(path, _animation!); } @@ -2290,8 +2384,12 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { context.canvas.restore(); } - Offset _calculateControlPoint(Offset startPoint, Offset endPoint, - double heightFactor, double controlPointFactor) { + Offset _calculateControlPoint( + Offset startPoint, + Offset endPoint, + double heightFactor, + double controlPointFactor, + ) { final double width = endPoint.dx - startPoint.dx; final double height = endPoint.dy - startPoint.dy; // Calculating curve height from base line based on the value of @@ -2303,17 +2401,22 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { // value. Converting factor value into pixel value using this formula // (((1 − factor)𝑥0 + factor * 𝑥1),((1 − factor)𝑦0 + factor * 𝑦1)) Offset controlPoint = Offset( - (1 - controlPointFactor) * startPoint.dx + - controlPointFactor * endPoint.dx, - (1 - controlPointFactor) * startPoint.dy + - controlPointFactor * endPoint.dy); + (1 - controlPointFactor) * startPoint.dx + + controlPointFactor * endPoint.dx, + (1 - controlPointFactor) * startPoint.dy + + controlPointFactor * endPoint.dy, + ); if (startPoint.dx < endPoint.dx) { - controlPoint = Offset(controlPoint.dx + horizontalDistance, - controlPoint.dy - verticalDistance); + controlPoint = Offset( + controlPoint.dx + horizontalDistance, + controlPoint.dy - verticalDistance, + ); } else { - controlPoint = Offset(controlPoint.dx - horizontalDistance, - controlPoint.dy + verticalDistance); + controlPoint = Offset( + controlPoint.dx - horizontalDistance, + controlPoint.dy + verticalDistance, + ); } return controlPoint; } @@ -2816,14 +2919,17 @@ class MapPolylineLayer extends MapVectorLayer { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); if (polylines.isNotEmpty) { - final _DebugVectorShapeTree pointerTreeNode = - _DebugVectorShapeTree(polylines); + final _DebugVectorShapeTree pointerTreeNode = _DebugVectorShapeTree( + polylines, + ); properties.add(pointerTreeNode.toDiagnosticsNode()); } - properties - .add(ObjectFlagProperty>.has('animation', animation)); - properties.add(ObjectFlagProperty.has( - 'tooltip', tooltipBuilder)); + properties.add( + ObjectFlagProperty>.has('animation', animation), + ); + properties.add( + ObjectFlagProperty.has('tooltip', tooltipBuilder), + ); properties.add(DiagnosticsProperty>('dashArray', dashArray)); if (color != null) { properties.add(ColorProperty('color', color)); @@ -2869,14 +2975,17 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> void initState() { super.initState(); _hoverAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); } @override void didChangeDependencies() { if (_controller == null) { - ancestor = context - .dependOnInheritedWidgetOfExactType()!; + ancestor = + context + .dependOnInheritedWidgetOfExactType()!; _controller = ancestor.controller; } super.didChangeDependencies(); @@ -2892,7 +3001,8 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - final bool isDesktop = kIsWeb || + final bool isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -2901,10 +3011,11 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> controller: _controller, polylines: widget.polylines, animation: widget.animation, - color: widget.color ?? - (_mapsThemeData.brightness == Brightness.light - ? themeData.colorScheme.onSurface.withOpacity(0.44) - : themeData.colorScheme.onSurface.withOpacity(0.78)), + color: + widget.color ?? + (themeData.brightness == Brightness.light + ? themeData.colorScheme.onSurface.withValues(alpha: 0.44) + : themeData.colorScheme.onSurface.withValues(alpha: 0.78)), width: widget.width, strokeCap: widget.strokeCap, dashArray: widget.dashArray, @@ -2971,7 +3082,9 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { @override void updateRenderObject( - BuildContext context, _RenderMapPolyline renderObject) { + BuildContext context, + _RenderMapPolyline renderObject, + ) { renderObject ..polylines = polylines ..animation = animation @@ -3002,19 +3115,21 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { required this.isDesktop, required this.hoverAnimationController, required this.state, - }) : _controller = controller, - _polylines = polylines, - _color = color, - _width = width, - _strokeCap = strokeCap, - _dashArray = dashArray, - _animation = animation, - _tooltipBuilder = tooltipBuilder, - _themeData = themeData { + }) : _controller = controller, + _polylines = polylines, + _color = color, + _width = width, + _strokeCap = strokeCap, + _dashArray = dashArray, + _animation = animation, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData { _forwardHoverColor = ColorTween(); _reverseHoverColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( - parent: hoverAnimationController, curve: Curves.easeInOut); + parent: hoverAnimationController, + curve: Curves.easeInOut, + ); polylinesInList = _polylines.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } @@ -3125,8 +3240,11 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { void _updateHoverItemTween() { if (isDesktop) { - final Color hoverStrokeColor = - _getHoverColor(selectedPolyline.color, _color, _themeData); + final Color hoverStrokeColor = _getHoverColor( + selectedPolyline.color, + _color, + _themeData, + ); final Color beginColor = selectedPolyline.color ?? _color; if (_previousHoverItem != null) { @@ -3173,8 +3291,10 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleInteraction(Offset position, - [PointerKind kind = PointerKind.touch]) { + void _handleInteraction( + Offset position, [ + PointerKind kind = PointerKind.touch, + ]) { if (_controller != null && _controller!.tooltipKey != null && _controller!.tooltipKey!.currentContext != null) { @@ -3214,13 +3334,16 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation!.isCompleted) { + if ((_animation != null && !_animation!.isCompleted) || + polylinesInList == null || + polylinesInList!.isEmpty) { return false; } - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Size boxSize = + _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; final Offset translationOffset = _getTranslation(_controller!); bool tappedOnLine = false; int index = polylinesInList!.length - 1; @@ -3254,7 +3377,11 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { endPoint = _getScaledOffset(endPoint, _controller!); if (_liesPointOnLine( - startPoint, endPoint, actualTouchTolerance, position)) { + startPoint, + endPoint, + actualTouchTolerance, + position, + )) { tappedOnLine = true; selectedPolyline = polyline; selectedIndex = index; @@ -3314,7 +3441,9 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { // ignore: avoid_as final RenderBox renderBox = context.findRenderObject()! as RenderBox; _handleInteraction( - renderBox.globalToLocal(event.position), PointerKind.hover); + renderBox.globalToLocal(event.position), + PointerKind.hover, + ); } } @@ -3332,62 +3461,79 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { return; } context.canvas.save(); - final Paint paint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; + final Paint paint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; Path path = Path(); - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + 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) { final int polylinePointsLength = polyline.points.length; final MapLatLng startCoordinate = polyline.points[0]; final MapLatLng endCoordinate = polyline.points[polylinePointsLength - 1]; - final double strokeWidth = - _getCurrentWidth(polyline.width ?? _width, _controller!); + final double strokeWidth = _getCurrentWidth( + polyline.width ?? _width, + _controller!, + ); final StrokeCap strokeCap = polyline.strokeCap ?? _strokeCap; final bool hasCap = strokeCap != StrokeCap.butt; Offset startPoint = pixelFromLatLng( - startCoordinate.latitude, - startCoordinate.longitude, - boxSize, - translationOffset, - _controller!.shapeLayerSizeFactor); + startCoordinate.latitude, + startCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor, + ); Offset endPoint = pixelFromLatLng( - endCoordinate.latitude, - endCoordinate.longitude, - boxSize, - translationOffset, - _controller!.shapeLayerSizeFactor); + endCoordinate.latitude, + endCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor, + ); if (hasCap) { final MapLatLng? secondCoordinate = polylinePointsLength > 1 ? polyline.points[1] : null; - final MapLatLng? beforeEndCoordinate = polylinePointsLength > 1 - ? polyline.points[polylinePointsLength - 2] - : null; + final MapLatLng? beforeEndCoordinate = + polylinePointsLength > 1 + ? polyline.points[polylinePointsLength - 2] + : null; if (secondCoordinate != null) { final Offset secondPoint = pixelFromLatLng( - secondCoordinate.latitude, - secondCoordinate.longitude, - boxSize, - translationOffset, - _controller!.shapeLayerSizeFactor); + secondCoordinate.latitude, + secondCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor, + ); startPoint = _getPointAlongLineWithCap( - startPoint, secondPoint, strokeWidth / 2, true); + startPoint, + secondPoint, + strokeWidth / 2, + true, + ); } if (beforeEndCoordinate != null) { final Offset beforeEndPoint = pixelFromLatLng( - beforeEndCoordinate.latitude, - beforeEndCoordinate.longitude, - boxSize, - translationOffset, - _controller!.shapeLayerSizeFactor); + beforeEndCoordinate.latitude, + beforeEndCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor, + ); endPoint = _getPointAlongLineWithCap( - beforeEndPoint, endPoint, strokeWidth / 2, false); + beforeEndPoint, + endPoint, + strokeWidth / 2, + false, + ); } } @@ -3403,11 +3549,12 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { for (int j = 1; j < polylinePointsLength; j++) { final MapLatLng nextCoordinate = polyline.points[j]; final Offset nextPoint = pixelFromLatLng( - nextCoordinate.latitude, - nextCoordinate.longitude, - boxSize, - translationOffset, - _controller!.shapeLayerSizeFactor); + nextCoordinate.latitude, + nextCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor, + ); if (j < polylinePointsLength - 1) { path.lineTo(nextPoint.dx, nextPoint.dy); } else { @@ -3432,10 +3579,16 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { } if (polyline.dashArray != null) { - assert(polyline.dashArray!.length >= 2 && - polyline.dashArray!.length.isEven); - _drawDashedLine(context.canvas, polyline.dashArray!, paint, path, - hasCap ? strokeWidth / 2 : 0); + assert( + polyline.dashArray!.length >= 2 && polyline.dashArray!.length.isEven, + ); + _drawDashedLine( + context.canvas, + polyline.dashArray!, + paint, + path, + hasCap ? strokeWidth / 2 : 0, + ); } else { _drawDashedLine(context.canvas, _dashArray, paint, path); } @@ -3507,8 +3660,8 @@ class MapPolygonLayer extends MapVectorLayer { this.strokeWidth = 1, this.strokeColor, IndexedWidgetBuilder? tooltipBuilder, - }) : _fillType = _VectorFillType.inner, - super(key: key, tooltipBuilder: tooltipBuilder); + }) : _fillType = _VectorFillType.inner, + super(key: key, tooltipBuilder: tooltipBuilder); /// Creates the inverted color polygon shape. /// @@ -3570,8 +3723,8 @@ class MapPolygonLayer extends MapVectorLayer { this.color, this.strokeColor, IndexedWidgetBuilder? tooltipBuilder, - }) : _fillType = _VectorFillType.outer, - super(key: key, tooltipBuilder: tooltipBuilder); + }) : _fillType = _VectorFillType.outer, + super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapPolygon]. /// @@ -3819,12 +3972,14 @@ class MapPolygonLayer extends MapVectorLayer { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); if (polygons.isNotEmpty) { - final _DebugVectorShapeTree pointerTreeNode = - _DebugVectorShapeTree(polygons); + final _DebugVectorShapeTree pointerTreeNode = _DebugVectorShapeTree( + polygons, + ); properties.add(pointerTreeNode.toDiagnosticsNode()); } - properties.add(ObjectFlagProperty.has( - 'tooltip', tooltipBuilder)); + properties.add( + ObjectFlagProperty.has('tooltip', tooltipBuilder), + ); if (color != null) { properties.add(ColorProperty('color', color)); } @@ -3871,14 +4026,17 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> void initState() { super.initState(); _hoverAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); } @override void didChangeDependencies() { if (_controller == null) { - ancestor = context - .dependOnInheritedWidgetOfExactType()!; + ancestor = + context + .dependOnInheritedWidgetOfExactType()!; _controller = ancestor.controller; } super.didChangeDependencies(); @@ -3897,39 +4055,52 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> 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') - ])); + 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') - ])); + 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') - ])); + 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 || + final bool isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -3937,17 +4108,19 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> 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 + : (themeData.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 + : (themeData.brightness == Brightness.light ? const Color.fromRGBO(98, 0, 238, 1) : const Color.fromRGBO(187, 134, 252, 0.5))), tooltipBuilder: widget.tooltipBuilder, @@ -4011,7 +4184,9 @@ class _MapPolygonLayerRenderObject extends LeafRenderObjectWidget { @override void updateRenderObject( - BuildContext context, _RenderMapPolygon renderObject) { + BuildContext context, + _RenderMapPolygon renderObject, + ) { renderObject ..polygons = polygons ..color = color @@ -4040,19 +4215,21 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { required this.isDesktop, required this.fillType, required this.state, - }) : _controller = controller, - _polygons = polygons, - _color = color, - _strokeWidth = strokeWidth, - _strokeColor = strokeColor, - _tooltipBuilder = tooltipBuilder, - _themeData = themeData { + }) : _controller = controller, + _polygons = polygons, + _color = color, + _strokeWidth = strokeWidth, + _strokeColor = strokeColor, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData { _forwardHoverColor = ColorTween(); _reverseHoverColor = ColorTween(); _forwardHoverStrokeColor = ColorTween(); _reverseHoverStrokeColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( - parent: hoverAnimationController, curve: Curves.easeInOut); + parent: hoverAnimationController, + curve: Curves.easeInOut, + ); _polygonsInList = _polygons.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } @@ -4229,8 +4406,10 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleInteraction(Offset position, - [PointerKind kind = PointerKind.touch]) { + void _handleInteraction( + Offset position, [ + PointerKind kind = PointerKind.touch, + ]) { if (_controller != null && _controller!.tooltipKey != null && _controller!.tooltipKey!.currentContext != null) { @@ -4238,8 +4417,14 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { _controller!.tooltipKey!.currentContext!.findRenderObject()! // ignore: avoid_as as ShapeLayerChildRenderBoxBase; - tooltipRenderer.paintTooltip(_selectedIndex, null, MapLayerElement.vector, - kind, state.ancestor.sublayers?.indexOf(polygonLayer), position); + tooltipRenderer.paintTooltip( + _selectedIndex, + null, + MapLayerElement.vector, + kind, + state.ancestor.sublayers?.indexOf(polygonLayer), + position, + ); } } @@ -4269,10 +4454,14 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { + if (_polygonsInList == null || _polygonsInList!.isEmpty) { + return false; + } int index = _polygonsInList!.length - 1; - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + 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 || isDesktop) { @@ -4280,22 +4469,24 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { final MapLatLng startCoordinate = polygon.points[0]; Offset startPoint = pixelFromLatLng( - startCoordinate.latitude, - startCoordinate.longitude, - boxSize, - translationOffset, - getLayerSizeFactor(_controller!)); + 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, - getLayerSizeFactor(_controller!)); + nextCoordinate.latitude, + nextCoordinate.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!), + ); nextPoint = _getScaledOffset(nextPoint, _controller!); path.lineTo(nextPoint.dx, nextPoint.dy); } @@ -4326,7 +4517,9 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { // ignore: avoid_as final RenderBox renderBox = context.findRenderObject()! as RenderBox; _handleInteraction( - renderBox.globalToLocal(event.position), PointerKind.hover); + renderBox.globalToLocal(event.position), + PointerKind.hover, + ); } } @@ -4372,12 +4565,14 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { void paint(PaintingContext context, Offset offset) { context.canvas.save(); final Paint fillPaint = Paint()..isAntiAlias = true; - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + 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. @@ -4395,11 +4590,12 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { final MapLatLng startLatLng = polygon.points[0]; Offset startPoint = pixelFromLatLng( - startLatLng.latitude, - startLatLng.longitude, - boxSize, - translationOffset, - getLayerSizeFactor(_controller!)); + startLatLng.latitude, + startLatLng.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!), + ); startPoint = _getScaledOffset(startPoint, _controller!); path.moveTo(startPoint.dx, startPoint.dy); @@ -4407,11 +4603,12 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { for (int j = 1; j < polygon.points.length; j++) { final MapLatLng nextCoordinate = polygon.points[j]; Offset nextPoint = pixelFromLatLng( - nextCoordinate.latitude, - nextCoordinate.longitude, - boxSize, - translationOffset, - getLayerSizeFactor(_controller!)); + nextCoordinate.latitude, + nextCoordinate.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!), + ); nextPoint = _getScaledOffset(nextPoint, _controller!); path.lineTo(nextPoint.dx, nextPoint.dy); } @@ -4432,7 +4629,13 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { ..strokeWidth = _strokeWidth ..color = _strokeColor; _drawInvertedPath( - context, path, _controller!, fillPaint, strokePaint, offset); + context, + path, + _controller!, + fillPaint, + strokePaint, + offset, + ); } context.canvas.restore(); } @@ -4444,13 +4647,15 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { } if (_previousHoverItem != null && _previousHoverItem == polygon) { - paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColor.evaluate(_hoverColorAnimation)! - : (polygon.color ?? _color); + paint.color = + _themeData.shapeHoverColor != Colors.transparent + ? _reverseHoverColor.evaluate(_hoverColorAnimation)! + : (polygon.color ?? _color); } else if (_currentHoverItem != null && _currentHoverItem == polygon) { - paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColor.evaluate(_hoverColorAnimation)! - : (polygon.color ?? _color); + paint.color = + _themeData.shapeHoverColor != Colors.transparent + ? _forwardHoverColor.evaluate(_hoverColorAnimation)! + : (polygon.color ?? _color); } else { paint.color = polygon.color ?? _color; } @@ -4485,7 +4690,10 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { } void _updateHoverStrokeColor( - Paint paint, MapPolygon polygon, ColorTween tween) { + Paint paint, + MapPolygon polygon, + ColorTween tween, + ) { if (_themeData.shapeHoverStrokeColor != Colors.transparent) { paint.color = tween.evaluate(_hoverColorAnimation)!; } else { @@ -4563,8 +4771,8 @@ class MapCircleLayer extends MapVectorLayer { this.strokeWidth = 1, this.strokeColor, IndexedWidgetBuilder? tooltipBuilder, - }) : _fillType = _VectorFillType.inner, - super(key: key, tooltipBuilder: 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 @@ -4630,8 +4838,8 @@ class MapCircleLayer extends MapVectorLayer { this.color, this.strokeColor, IndexedWidgetBuilder? tooltipBuilder, - }) : _fillType = _VectorFillType.outer, - super(key: key, tooltipBuilder: tooltipBuilder); + }) : _fillType = _VectorFillType.outer, + super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapCircle]. /// @@ -4984,14 +5192,17 @@ class MapCircleLayer extends MapVectorLayer { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); if (circles.isNotEmpty) { - final _DebugVectorShapeTree pointerTreeNode = - _DebugVectorShapeTree(circles); + final _DebugVectorShapeTree pointerTreeNode = _DebugVectorShapeTree( + circles, + ); properties.add(pointerTreeNode.toDiagnosticsNode()); } - properties - .add(ObjectFlagProperty>.has('animation', animation)); - properties.add(ObjectFlagProperty.has( - 'tooltip', tooltipBuilder)); + properties.add( + ObjectFlagProperty>.has('animation', animation), + ); + properties.add( + ObjectFlagProperty.has('tooltip', tooltipBuilder), + ); if (color != null) { properties.add(ColorProperty('color', color)); @@ -5041,14 +5252,17 @@ class _MapCircleLayerState extends State<_MapCircleLayer> void initState() { super.initState(); _hoverAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); + vsync: this, + duration: const Duration(milliseconds: 250), + ); } @override void didChangeDependencies() { if (_controller == null) { - ancestor = context - .dependOnInheritedWidgetOfExactType()!; + ancestor = + context + .dependOnInheritedWidgetOfExactType()!; _controller = ancestor.controller; } super.didChangeDependencies(); @@ -5067,39 +5281,52 @@ class _MapCircleLayerState extends State<_MapCircleLayer> 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') - ])); + 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') - ])); + 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') - ])); + 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 || + final bool isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -5108,17 +5335,19 @@ class _MapCircleLayerState extends State<_MapCircleLayer> 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 + : (themeData.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 + : (themeData.brightness == Brightness.light ? const Color.fromRGBO(98, 0, 238, 1) : const Color.fromRGBO(187, 134, 252, 0.5))), tooltipBuilder: widget.tooltipBuilder, @@ -5215,20 +5444,22 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { required this.isDesktop, required this.fillType, required this.state, - }) : _controller = controller, - _circles = circles, - _animation = animation, - _color = color, - _strokeColor = strokeColor, - _strokeWidth = strokeWidth, - _tooltipBuilder = tooltipBuilder, - _themeData = themeData { + }) : _controller = controller, + _circles = circles, + _animation = animation, + _color = color, + _strokeColor = strokeColor, + _strokeWidth = strokeWidth, + _tooltipBuilder = tooltipBuilder, + _themeData = themeData { _forwardHoverColor = ColorTween(); _reverseHoverColor = ColorTween(); _forwardHoverStrokeColor = ColorTween(); _reverseHoverStrokeColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( - parent: hoverAnimationController, curve: Curves.easeInOut); + parent: hoverAnimationController, + curve: Curves.easeInOut, + ); _circlesInList = _circles.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } @@ -5414,8 +5645,10 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleInteraction(Offset position, - [PointerKind kind = PointerKind.touch]) { + void _handleInteraction( + Offset position, [ + PointerKind kind = PointerKind.touch, + ]) { if (_controller != null && _controller!.tooltipKey != null && _controller!.tooltipKey!.currentContext != null) { @@ -5423,9 +5656,10 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { _controller!.tooltipKey!.currentContext!.findRenderObject()! // ignore: avoid_as as ShapeLayerChildRenderBoxBase; - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Size boxSize = + _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; final Offset translationOffset = _getTranslation(_controller!); if (_selectedIndex != -1) { @@ -5438,8 +5672,10 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { ); center = _getScaledOffset(center, _controller!); - final Rect circleRect = - Rect.fromCircle(center: center, radius: _selectedCircle.radius); + final Rect circleRect = Rect.fromCircle( + center: center, + radius: _selectedCircle.radius, + ); tooltipRenderer.paintTooltip( _selectedIndex, circleRect, @@ -5478,14 +5714,17 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation!.isCompleted) { + if ((_animation != null && !_animation!.isCompleted) || + _circlesInList == null || + _circlesInList!.isEmpty) { return false; } int index = _circlesInList!.length - 1; - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Size boxSize = + _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; final Offset translationOffset = getTranslationOffset(_controller!); for (final MapCircle circle in _circlesInList!.reversed) { @@ -5500,10 +5739,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { ); center = _getScaledOffset(center, _controller!); - path.addOval(Rect.fromCircle( - center: center, - radius: circle.radius, - )); + path.addOval(Rect.fromCircle(center: center, radius: circle.radius)); if (path.contains(position)) { _selectedCircle = circle; _selectedIndex = index; @@ -5561,7 +5797,9 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { // ignore: avoid_as final RenderBox renderBox = context.findRenderObject()! as RenderBox; _handleInteraction( - renderBox.globalToLocal(event.position), PointerKind.hover); + renderBox.globalToLocal(event.position), + PointerKind.hover, + ); } } @@ -5581,12 +5819,14 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { context.canvas.save(); final Paint fillPaint = Paint()..isAntiAlias = true; - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - final Size boxSize = _controller?.layerType == LayerType.tile - ? _controller!.totalTileSize! - : size; + final Paint strokePaint = + Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; + 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. @@ -5631,7 +5871,13 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { ..strokeWidth = _strokeWidth ..color = _strokeColor; _drawInvertedPath( - context, path, _controller!, fillPaint, strokePaint, offset); + context, + path, + _controller!, + fillPaint, + strokePaint, + offset, + ); } context.canvas.restore(); } @@ -5643,13 +5889,15 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { } if (_previousHoverItem != null && _previousHoverItem == circle) { - paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColor.evaluate(_hoverColorAnimation)! - : (circle.color ?? _color); + paint.color = + _themeData.shapeHoverColor != Colors.transparent + ? _reverseHoverColor.evaluate(_hoverColorAnimation)! + : (circle.color ?? _color); } else if (_currentHoverItem != null && _currentHoverItem == circle) { - paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColor.evaluate(_hoverColorAnimation)! - : (circle.color ?? _color); + paint.color = + _themeData.shapeHoverColor != Colors.transparent + ? _forwardHoverColor.evaluate(_hoverColorAnimation)! + : (circle.color ?? _color); } else { paint.color = circle.color ?? _color; } @@ -5684,7 +5932,10 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { } void _updateHoverStrokeColor( - Paint paint, MapCircle circle, ColorTween tween) { + Paint paint, + MapCircle circle, + ColorTween tween, + ) { if (_themeData.shapeHoverStrokeColor != Colors.transparent) { paint.color = tween.evaluate(_hoverColorAnimation)!; } else { @@ -8207,12 +8458,20 @@ Path? _dashPath( double startPoint = distance; double endPoint = distance + length; if (capRadius != 0) { - startPoint = _getPointAlongLineWithCap( - Offset(startPoint, 0), Offset(endPoint, 0), capRadius, true) - .dx; - endPoint = _getPointAlongLineWithCap( - Offset(startPoint, 0), Offset(endPoint, 0), capRadius, false) - .dx; + startPoint = + _getPointAlongLineWithCap( + Offset(startPoint, 0), + Offset(endPoint, 0), + capRadius, + true, + ).dx; + endPoint = + _getPointAlongLineWithCap( + Offset(startPoint, 0), + Offset(endPoint, 0), + capRadius, + false, + ).dx; } path.addPath(matric.extractPath(startPoint, endPoint), Offset.zero); } @@ -8224,8 +8483,12 @@ Path? _dashPath( } void _drawDashedLine( - Canvas canvas, List dashArray, Paint paint, Path path, - [double capRadius = 0]) { + Canvas canvas, + List dashArray, + Paint paint, + Path path, [ + double capRadius = 0, +]) { bool even = false; for (int i = 1; i < dashArray.length; i = i + 2) { if (dashArray[i] == 0) { @@ -8235,12 +8498,13 @@ void _drawDashedLine( if (!even) { paint.isAntiAlias = false; canvas.drawPath( - _dashPath( - path, - dashArray: _IntervalList(dashArray), - capRadius: capRadius, - )!, - paint); + _dashPath( + path, + dashArray: _IntervalList(dashArray), + capRadius: capRadius, + )!, + paint, + ); } else { canvas.drawPath(path, paint); } @@ -8261,8 +8525,12 @@ class _IntervalList { } } -bool _liesPointOnLine(Offset startPoint, Offset endPoint, double touchTolerance, - Offset touchPosition) { +bool _liesPointOnLine( + Offset startPoint, + Offset endPoint, + double touchTolerance, + Offset touchPosition, +) { final Path path = Path(); // Calculate distance between two points i.e, d = sqrt[(x1-x2)^2+(y1-y2)^2]. final double width = endPoint.dx - startPoint.dx; @@ -8271,14 +8539,22 @@ bool _liesPointOnLine(Offset startPoint, Offset endPoint, double touchTolerance, final double horizontalTouchLength = touchTolerance * height / lineLength; final double verticalTouchLength = touchTolerance * width / lineLength; - final Offset lineTopLeft = Offset(startPoint.dx - horizontalTouchLength, - startPoint.dy + verticalTouchLength); - final Offset lineTopRight = Offset(startPoint.dx + horizontalTouchLength, - startPoint.dy - verticalTouchLength); + final Offset lineTopLeft = Offset( + startPoint.dx - horizontalTouchLength, + startPoint.dy + verticalTouchLength, + ); + final Offset lineTopRight = Offset( + startPoint.dx + horizontalTouchLength, + startPoint.dy - verticalTouchLength, + ); final Offset lineBottomRight = Offset( - endPoint.dx + horizontalTouchLength, endPoint.dy - verticalTouchLength); + endPoint.dx + horizontalTouchLength, + endPoint.dy - verticalTouchLength, + ); final Offset lineBottomLeft = Offset( - endPoint.dx - horizontalTouchLength, endPoint.dy + verticalTouchLength); + endPoint.dx - horizontalTouchLength, + endPoint.dy + verticalTouchLength, + ); path ..moveTo(lineTopLeft.dx, lineTopLeft.dy) ..lineTo(lineTopRight.dx, lineTopRight.dy) @@ -8293,9 +8569,10 @@ Path _getAnimatedPath(Path originalPath, Animation animation) { double currentLength = 0.0; final PathMetrics pathMetrics = originalPath.computeMetrics(); final double pathLength = pathMetrics.fold( - 0.0, - (double previousValue, PathMetric pathMetric) => - previousValue + pathMetric.length); + 0.0, + (double previousValue, PathMetric pathMetric) => + previousValue + pathMetric.length, + ); final double requiredPathLength = pathLength * animation.value; final Iterator metricsIterator = originalPath.computeMetrics().iterator; @@ -8305,8 +8582,9 @@ Path _getAnimatedPath(Path originalPath, Animation animation) { final double nextLength = currentLength + metric.length; if (nextLength > requiredPathLength) { extractedPath.addPath( - metric.extractPath(0.0, requiredPathLength - currentLength), - Offset.zero); + metric.extractPath(0.0, requiredPathLength - currentLength), + Offset.zero, + ); break; } extractedPath.addPath(metric.extractPath(0.0, metric.length), Offset.zero); @@ -8316,8 +8594,13 @@ Path _getAnimatedPath(Path originalPath, Animation animation) { return extractedPath; } -bool _liesPointOnArc(Offset startPoint, Offset endPoint, Offset controlPoint, - double touchTolerance, Offset touchPosition) { +bool _liesPointOnArc( + Offset startPoint, + Offset endPoint, + Offset controlPoint, + double touchTolerance, + Offset touchPosition, +) { final Path path = Path(); final double width = endPoint.dx - startPoint.dx; final double height = endPoint.dy - startPoint.dy; @@ -8326,26 +8609,45 @@ bool _liesPointOnArc(Offset startPoint, Offset endPoint, Offset controlPoint, final double horizontalTouchLength = touchTolerance * height / lineLength; final double verticalTouchLength = touchTolerance * width / lineLength; - final Offset lineBottomLeft = Offset(startPoint.dx - horizontalTouchLength, - startPoint.dy + verticalTouchLength); - final Offset lineTopLeft = Offset(startPoint.dx + horizontalTouchLength, - startPoint.dy - verticalTouchLength); + final Offset lineBottomLeft = Offset( + startPoint.dx - horizontalTouchLength, + startPoint.dy + verticalTouchLength, + ); + final Offset lineTopLeft = Offset( + startPoint.dx + horizontalTouchLength, + startPoint.dy - verticalTouchLength, + ); final Offset lineTopRight = Offset( - endPoint.dx + horizontalTouchLength, endPoint.dy - verticalTouchLength); + endPoint.dx + horizontalTouchLength, + endPoint.dy - verticalTouchLength, + ); final Offset lineBottomRight = Offset( - endPoint.dx - horizontalTouchLength, endPoint.dy + verticalTouchLength); - final Offset controlPointTop = Offset(controlPoint.dx + horizontalTouchLength, - controlPoint.dy - verticalTouchLength); + endPoint.dx - horizontalTouchLength, + endPoint.dy + verticalTouchLength, + ); + final Offset controlPointTop = Offset( + controlPoint.dx + horizontalTouchLength, + controlPoint.dy - verticalTouchLength, + ); final Offset controlPointBottom = Offset( - controlPoint.dx - horizontalTouchLength, - controlPoint.dy + verticalTouchLength); + controlPoint.dx - horizontalTouchLength, + controlPoint.dy + verticalTouchLength, + ); path ..moveTo(lineTopLeft.dx, lineTopLeft.dy) - ..quadraticBezierTo(controlPointTop.dx, controlPointTop.dy, lineTopRight.dx, - lineTopRight.dy) + ..quadraticBezierTo( + controlPointTop.dx, + controlPointTop.dy, + lineTopRight.dx, + lineTopRight.dy, + ) ..lineTo(lineBottomRight.dx, lineBottomRight.dy) - ..quadraticBezierTo(controlPointBottom.dx, controlPointBottom.dy, - lineBottomLeft.dx, lineBottomLeft.dy) + ..quadraticBezierTo( + controlPointBottom.dx, + controlPointBottom.dy, + lineBottomLeft.dx, + lineBottomLeft.dy, + ) ..close(); return path.contains(touchPosition); } diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart b/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart index b8728007b..f55951281 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart @@ -14,6 +14,9 @@ typedef ZoomableUpdateCallback = void Function(ZoomPanDetails); /// Signature for when the zooming and panning action has been completed. typedef ZoomableCompleteCallback = void Function(ZoomPanDetails); +/// Signature for when the zooming and panning action has been flinging. +typedef ZoomableFlingCallback = bool Function(ZoomPanDetails); + /// Signature for when zoom level change callback. typedef ZoomCallback = void Function(double); @@ -31,11 +34,14 @@ enum ActionType { /// Denotes the current action as double tap. tap, - /// Denotes the current action as flinging. - fling, + /// Denotes the current action as pinch flinging. + pinchFling, + + /// Denotes the current action as pan flinging. + panFling, /// Denotes there is no action currently. - none + none, } /// Contains details about the current zooming and panning action. @@ -88,6 +94,7 @@ class Zoomable extends StatefulWidget { required this.minZoomLevel, required this.maxZoomLevel, required this.zoomController, + this.enableMouseWheelZooming = false, this.enablePinching = true, this.enablePanning = true, this.enableDoubleTapZooming = false, @@ -95,12 +102,14 @@ class Zoomable extends StatefulWidget { this.frictionCoefficient = 0.005, required this.onUpdate, required this.onComplete, + required this.onFling, this.child, - }) : assert(minZoomLevel >= 1 && minZoomLevel <= maxZoomLevel), - assert(initialZoomLevel >= minZoomLevel && - initialZoomLevel <= maxZoomLevel), - assert(frictionCoefficient > 0.0), - super(key: key); + }) : assert(minZoomLevel >= 1 && minZoomLevel <= maxZoomLevel), + assert( + initialZoomLevel >= minZoomLevel && initialZoomLevel <= maxZoomLevel, + ), + assert(frictionCoefficient > 0.0), + super(key: key); /// Specifies the initial zoomLevel of the widget. /// @@ -116,6 +125,11 @@ class Zoomable extends StatefulWidget { /// [maxZoomLevel]. final double minZoomLevel; + /// Option to enable mouse wheel in web. + /// + /// By default it will be `false`. + final bool enableMouseWheelZooming; + /// Specifies the maximum zoomLevel of the widget. /// /// It can't be null and must be greater than or equal to `1` and @@ -160,6 +174,9 @@ class Zoomable extends StatefulWidget { /// Called when the zoom level or actual rect updating has been completed. final ZoomableCompleteCallback onComplete; + /// Called when the zoom level or actual rect updating has been completed. + final ZoomableFlingCallback onFling; + /// Specifies the child of the zoomable widget. It didn't get updated based on /// the zoomable widget interaction or changes. /// @@ -180,7 +197,7 @@ class _ZoomableState extends State with TickerProviderStateMixin { late Matrix4 _newMatrix; late Offset _startLocalPoint; - Rect? _boundaryRect; + late Rect? _boundaryRect; Timer? _doubleTapTimer; Size? _size; Offset? _matrixStartPoint; @@ -220,14 +237,19 @@ class _ZoomableState extends State with TickerProviderStateMixin { assert(scale != 0.0); final double currentScale = matrix.getMaxScaleOnAxis(); - final double clampedTotalScale = (currentScale * scale) - .clamp(_getScale(widget.minZoomLevel), _getScale(widget.maxZoomLevel)); + final double clampedTotalScale = (currentScale * scale).clamp( + _getScale(widget.minZoomLevel), + _getScale(widget.maxZoomLevel), + ); final double clampedScale = clampedTotalScale / currentScale; return matrix.clone()..scale(clampedScale); } ActionType _getActionTypes( - double scale, Offset focalPoint, Offset startFocalPoint) { + double scale, + Offset focalPoint, + Offset startFocalPoint, + ) { // The minimum distance required to start scale or pan gesture. const int minScaleDistance = 3; final Offset distance = focalPoint - startFocalPoint; @@ -264,12 +286,17 @@ class _ZoomableState extends State with TickerProviderStateMixin { if (!_zoomLevelAnimationController.isAnimating && !_actualRectAnimationController.isAnimating) { final double scale = _newMatrix.getMaxScaleOnAxis(); - final Offset matrixFocalPoint = - _getActualPointInMatrix(_newMatrix, details.localFocalPoint); + final Offset matrixFocalPoint = _getActualPointInMatrix( + _newMatrix, + details.localFocalPoint, + ); if (widget.zoomController.actionType == ActionType.none) { widget.zoomController.actionType = _getActionTypes( - details.scale, details.localFocalPoint, _startLocalPoint); + details.scale, + details.localFocalPoint, + _startLocalPoint, + ); } if (widget.zoomController.actionType == ActionType.none) { @@ -293,14 +320,23 @@ class _ZoomableState extends State with TickerProviderStateMixin { final double newScale = desiredScale / scale; _newMatrix = _matrixScale(_newMatrix, newScale); - final Offset matrixFocalPointScaled = - _getActualPointInMatrix(_newMatrix, _startLocalPoint); + final Offset matrixFocalPointScaled = _getActualPointInMatrix( + _newMatrix, + _startLocalPoint, + ); _newMatrix = _translateMatrix( - _newMatrix, matrixFocalPointScaled - _matrixStartPoint!); + _newMatrix, + matrixFocalPointScaled - _matrixStartPoint!, + ); - _invokeZoomableUpdate(_newMatrix, details.localFocalPoint, - details.focalPoint, details.scale, _startLocalPoint); + _invokeZoomableUpdate( + _newMatrix, + details.localFocalPoint, + details.focalPoint, + details.scale, + _startLocalPoint, + ); break; case ActionType.pan: @@ -312,13 +348,19 @@ class _ZoomableState extends State with TickerProviderStateMixin { final Offset newTranslation = matrixFocalPoint - _matrixStartPoint!; _newMatrix = _translateMatrix(_newMatrix, newTranslation); - _matrixStartPoint = - _getActualPointInMatrix(_newMatrix, details.localFocalPoint); + _matrixStartPoint = _getActualPointInMatrix( + _newMatrix, + details.localFocalPoint, + ); _invokeZoomableUpdate( - _newMatrix, details.localFocalPoint, details.focalPoint); + _newMatrix, + details.localFocalPoint, + details.focalPoint, + ); break; case ActionType.tap: - case ActionType.fling: + case ActionType.pinchFling: + case ActionType.panFling: case ActionType.none: break; } @@ -349,23 +391,40 @@ class _ZoomableState extends State with TickerProviderStateMixin { void _startFlingAnimationForPinching(ScaleEndDetails details) { _isFlingAnimationActive = true; _newMatrix = widget.zoomController.controllerMatrix.clone(); + _zoomLevelTween.begin = _newMatrix.getMaxScaleOnAxis(); final double zoomLevel = _getZoomLevel(_newMatrix.getMaxScaleOnAxis()); final int direction = _lastScaleValueOnInteraction >= _maximumReachedScaleOnInteraction ? 1 : -1; - double newZoomLevel = zoomLevel + + double newZoomLevel = + zoomLevel + (direction * (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * widget.maxZoomLevel); newZoomLevel = newZoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); - _zoomLevelTween - ..begin = _newMatrix.getMaxScaleOnAxis() - ..end = _getScale(newZoomLevel); - widget.zoomController.actionType = ActionType.fling; - _zoomLevelAnimationController.duration = - _getFlingAnimationDuration(details.velocity.pixelsPerSecond.distance); - _zoomLevelAnimationController.forward(from: 0.0); + widget.zoomController.actionType = ActionType.pinchFling; + final double scale = _getScale(newZoomLevel) / _zoomLevelTween.begin!; + Matrix4 matrix = _matrixScale(_newMatrix, scale); + final Offset matrixFocalPointScaled = _getActualPointInMatrix( + matrix, + _startLocalPoint, + ); + matrix = _translateMatrix( + matrix, + matrixFocalPointScaled - _matrixStartPoint!, + ); + if (_invokeZoomableUpdate(matrix, null, null, scale, _startLocalPoint)) { + _zoomLevelTween.end = _getScale(newZoomLevel); + _zoomLevelAnimationController.duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, + ); + _zoomLevelAnimationController.forward(from: 0.0); + } else { + _scaleStart = null; + widget.zoomController.actionType = ActionType.none; + _invokeZoomableComplete(_newMatrix); + } } // This methods performs fling animation for panning. @@ -374,6 +433,7 @@ class _ZoomableState extends State with TickerProviderStateMixin { _newMatrix = widget.zoomController.controllerMatrix.clone(); final Vector3 translationVector = _newMatrix.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); + _actualRectTween.begin = translation; final FrictionSimulation frictionSimulationX = FrictionSimulation( widget.frictionCoefficient, translation.dx, @@ -384,13 +444,24 @@ class _ZoomableState extends State with TickerProviderStateMixin { translation.dy, details.velocity.pixelsPerSecond.dy, ); - _actualRectTween.begin = translation; - _actualRectTween.end = - Offset(frictionSimulationX.finalX, frictionSimulationY.finalX); - widget.zoomController.actionType = ActionType.fling; - _actualRectAnimationController.duration = - _getFlingAnimationDuration(details.velocity.pixelsPerSecond.distance); - _actualRectAnimationController.forward(from: 0.0); + _actualRectTween.end = Offset( + frictionSimulationX.finalX, + frictionSimulationY.finalX, + ); + widget.zoomController.actionType = ActionType.panFling; + final Offset newTranslation = + _actualRectTween.end! - _actualRectTween.begin!; + final Matrix4 matrix = _translateMatrix(_newMatrix, newTranslation); + if (_invokeZoomableUpdate(matrix)) { + _actualRectAnimationController.duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, + ); + _actualRectAnimationController.forward(from: 0.0); + } else { + _scaleStart = null; + widget.zoomController.actionType = ActionType.none; + _invokeZoomableComplete(_newMatrix); + } } // Returns the animation duration for the given distance and @@ -409,19 +480,26 @@ class _ZoomableState extends State with TickerProviderStateMixin { final double desiredScale = _zoomLevelTween.evaluate(_zoomLevelAnimation); final double scaleChange = desiredScale / scale; _newMatrix = _matrixScale(_newMatrix, scaleChange); - final Offset matrixFocalPointScaled = - _getActualPointInMatrix(_newMatrix, _startLocalPoint); + final Offset matrixFocalPointScaled = _getActualPointInMatrix( + _newMatrix, + _startLocalPoint, + ); _newMatrix = _translateMatrix( - _newMatrix, matrixFocalPointScaled - _matrixStartPoint!); + _newMatrix, + matrixFocalPointScaled - _matrixStartPoint!, + ); if (widget.zoomController.actionType == ActionType.none || - widget.zoomController.actionType == ActionType.fling) { + widget.zoomController.actionType == ActionType.pinchFling) { final double zoomLevel = _getZoomLevel(_newMatrix.getMaxScaleOnAxis()); final Offset translatedPoint = _getTranslationOffset(_newMatrix); - final Rect actualRect = translatedPoint & - Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), - _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + final Rect actualRect = + translatedPoint & + Size( + _getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2), + ); widget.zoomController._internalSetValues(zoomLevel, actualRect); } else { _invokeZoomableUpdate(_newMatrix, _startLocalPoint); @@ -433,23 +511,31 @@ class _ZoomableState extends State with TickerProviderStateMixin { _isFlingAnimationActive ? Curves.decelerate : Curves.easeInOut; final Vector3 translationVector = _newMatrix.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); - final Offset matrixActualPoint = - _getActualPointInMatrix(_newMatrix, translation); - final Offset newTranslation = - _actualRectTween.evaluate(_actualRectAnimation); - final Offset animationMatrixPoint = - _getActualPointInMatrix(_newMatrix, newTranslation); + final Offset matrixActualPoint = _getActualPointInMatrix( + _newMatrix, + translation, + ); + final Offset newTranslation = _actualRectTween.evaluate( + _actualRectAnimation, + ); + final Offset animationMatrixPoint = _getActualPointInMatrix( + _newMatrix, + newTranslation, + ); final Offset matrixTranslationChange = animationMatrixPoint - matrixActualPoint; _newMatrix = _translateMatrix(_newMatrix, matrixTranslationChange); if (widget.zoomController.actionType == ActionType.none || - widget.zoomController.actionType == ActionType.fling) { + widget.zoomController.actionType == ActionType.panFling) { final double zoomLevel = _getZoomLevel(_newMatrix.getMaxScaleOnAxis()); final Offset translatedPoint = _getTranslationOffset(_newMatrix); - final Rect actualRect = translatedPoint & - Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), - _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + final Rect actualRect = + translatedPoint & + Size( + _getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2), + ); widget.zoomController._internalSetValues(zoomLevel, actualRect); } else { _invokeZoomableUpdate(_newMatrix); @@ -476,8 +562,10 @@ class _ZoomableState extends State with TickerProviderStateMixin { _startLocalPoint = widget.zoomController.parentRect!.center; _newMatrix = widget.zoomController.controllerMatrix.clone(); final Vector3 translationVector = _newMatrix.getTranslation(); - final Offset translation = - Offset(translationVector.x, translationVector.y); + final Offset translation = Offset( + translationVector.x, + translationVector.y, + ); _actualRectTween.begin = translation; _actualRectTween.end = actualRect.topLeft; _actualRectAnimationController.duration = widget.animationDuration; @@ -491,12 +579,12 @@ class _ZoomableState extends State with TickerProviderStateMixin { } if (_zoomLevelAnimationController.isAnimating && - widget.zoomController.actionType == ActionType.fling) { + widget.zoomController.actionType == ActionType.pinchFling) { _zoomLevelAnimationController.stop(); _handleZoomLevelAnimationEnd(); } if (_actualRectAnimationController.isAnimating && - widget.zoomController.actionType == ActionType.fling) { + widget.zoomController.actionType == ActionType.panFling) { _actualRectAnimationController.stop(); _handleActualRectAnimationEnd(); } @@ -515,13 +603,18 @@ class _ZoomableState extends State with TickerProviderStateMixin { _resetDoubleTapTimer(); // By default, we have increased the zoom level by 1 while double tapping. final double lastZoomLevel = _getZoomLevel( - widget.zoomController.controllerMatrix.getMaxScaleOnAxis()); + widget.zoomController.controllerMatrix.getMaxScaleOnAxis(), + ); double newZoomLevel = lastZoomLevel + 1; - newZoomLevel = - newZoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); + newZoomLevel = newZoomLevel.clamp( + widget.minZoomLevel, + widget.maxZoomLevel, + ); if (newZoomLevel != lastZoomLevel) { _handleDoubleTap( - event.localPosition, pow(2, newZoomLevel - 1).toDouble()); + event.localPosition, + pow(2, newZoomLevel - 1).toDouble(), + ); } } } @@ -568,7 +661,9 @@ class _ZoomableState extends State with TickerProviderStateMixin { _doubleTapEnabled = false; widget.zoomController.actionType = ActionType.pinch; _invokeZoomableUpdate( - widget.zoomController.controllerMatrix, _startLocalPoint); + widget.zoomController.controllerMatrix, + _startLocalPoint, + ); widget.zoomController.actionType = ActionType.none; _invokeZoomableComplete(widget.zoomController.controllerMatrix); } @@ -577,7 +672,9 @@ class _ZoomableState extends State with TickerProviderStateMixin { _isFlingAnimationActive = false; widget.zoomController.actionType = ActionType.pan; _invokeZoomableUpdate( - widget.zoomController.controllerMatrix, _startLocalPoint); + widget.zoomController.controllerMatrix, + _startLocalPoint, + ); widget.zoomController.actionType = ActionType.none; _invokeZoomableComplete(widget.zoomController.controllerMatrix); } @@ -593,15 +690,21 @@ class _ZoomableState extends State with TickerProviderStateMixin { final double scaleChange = exp(-event.scrollDelta.dy / 200); _newMatrix = widget.zoomController.controllerMatrix.clone(); - final Offset matrixFocalPoint = - _getActualPointInMatrix(_newMatrix, event.localPosition); + final Offset matrixFocalPoint = _getActualPointInMatrix( + _newMatrix, + event.localPosition, + ); _newMatrix = _matrixScale(_newMatrix, scaleChange); - final Offset matrixFocalPointScaled = - _getActualPointInMatrix(_newMatrix, event.localPosition); + final Offset matrixFocalPointScaled = _getActualPointInMatrix( + _newMatrix, + event.localPosition, + ); _newMatrix = _translateMatrix( - _newMatrix, matrixFocalPointScaled - matrixFocalPoint); + _newMatrix, + matrixFocalPointScaled - matrixFocalPoint, + ); widget.zoomController.actionType = ActionType.pinch; _invokeZoomableUpdate(_newMatrix, event.localPosition, event.position); widget.zoomController.actionType = ActionType.none; @@ -609,11 +712,13 @@ class _ZoomableState extends State with TickerProviderStateMixin { } } - void _invokeZoomableUpdate(Matrix4 matrix, - [Offset? localFocalPoint, - Offset? globalFocalPoint, - double scale = 1.0, - Offset? pinchCenter]) { + bool _invokeZoomableUpdate( + Matrix4 matrix, [ + Offset? localFocalPoint, + Offset? globalFocalPoint, + double scale = 1.0, + Offset? pinchCenter, + ]) { final double zoomLevel = _getZoomLevel(matrix.getMaxScaleOnAxis()); localFocalPoint ??= widget.zoomController.parentRect!.center; if (globalFocalPoint == null) { @@ -621,9 +726,12 @@ class _ZoomableState extends State with TickerProviderStateMixin { globalFocalPoint = renderBox.localToGlobal(localFocalPoint); } final Offset translatedPoint = _getTranslationOffset(matrix); - final Rect actualRect = translatedPoint & - Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), - _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + final Rect actualRect = + translatedPoint & + Size( + _getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2), + ); final ZoomPanDetails details = ZoomPanDetails( localFocalPoint: localFocalPoint, globalFocalPoint: globalFocalPoint, @@ -634,14 +742,22 @@ class _ZoomableState extends State with TickerProviderStateMixin { scale: scale, pinchCenter: pinchCenter, ); - widget.onUpdate(details); + if (widget.zoomController.actionType == ActionType.pinchFling || + widget.zoomController.actionType == ActionType.panFling) { + return widget.onFling(details); + } else { + widget.onUpdate(details); + } + return true; } - void _invokeZoomableComplete(Matrix4 matrix, - [Offset? localFocalPoint, - Offset? globalFocalPoint, - double scale = 1.0, - Offset? pinchCenter]) { + void _invokeZoomableComplete( + Matrix4 matrix, [ + Offset? localFocalPoint, + Offset? globalFocalPoint, + double scale = 1.0, + Offset? pinchCenter, + ]) { final double zoomLevel = _getZoomLevel(matrix.getMaxScaleOnAxis()); localFocalPoint ??= widget.zoomController.parentRect!.center; if (globalFocalPoint == null) { @@ -649,9 +765,12 @@ class _ZoomableState extends State with TickerProviderStateMixin { globalFocalPoint = renderBox.localToGlobal(localFocalPoint); } final Offset translatedPoint = _getTranslationOffset(matrix); - final Rect actualRect = translatedPoint & - Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), - _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + final Rect actualRect = + translatedPoint & + Size( + _getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2), + ); final ZoomPanDetails details = ZoomPanDetails( localFocalPoint: localFocalPoint, globalFocalPoint: globalFocalPoint, @@ -675,9 +794,13 @@ class _ZoomableState extends State with TickerProviderStateMixin { ..addListener(_handleActualRectAnimation) ..addStatusListener(_handleActualRectAnimationStatusChange); _zoomLevelAnimation = CurvedAnimation( - parent: _zoomLevelAnimationController, curve: Curves.easeInOut); + parent: _zoomLevelAnimationController, + curve: Curves.easeInOut, + ); _actualRectAnimation = CurvedAnimation( - parent: _actualRectAnimationController, curve: Curves.easeInOut); + parent: _actualRectAnimationController, + curve: Curves.easeInOut, + ); _actualRectTween = Tween(); _zoomLevelTween = Tween(); } @@ -708,36 +831,40 @@ class _ZoomableState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final Size newSize = Size(constraints.maxWidth, constraints.maxHeight); - if (_size == null || _size != newSize) { - _size = newSize; - widget.zoomController - ..parentRect = Offset.zero & _size! - .._actualRect = widget.initialRect - .._zoomLevel = widget.initialZoomLevel; - _boundaryRect = _getBoundaryRect(); - widget.zoomController.controllerMatrix = Matrix4.identity() - ..scale(_getScale(widget.initialZoomLevel)) - ..setTranslation( - Vector3(widget.initialRect.left, widget.initialRect.top, 0.0)); - } - - return Listener( - onPointerDown: _handlePointerDown, - onPointerUp: _handlePointerUp, - onPointerCancel: _handlePointerCancel, - onPointerSignal: _handleMouseWheelZooming, - behavior: HitTestBehavior.translucent, - child: GestureDetector( - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - onScaleEnd: _handleScaleEnd, + builder: (BuildContext context, BoxConstraints constraints) { + final Size newSize = Size(constraints.maxWidth, constraints.maxHeight); + if (_size == null || _size != newSize) { + _size = newSize; + widget.zoomController + ..parentRect = Offset.zero & _size! + .._actualRect = widget.initialRect + .._zoomLevel = widget.initialZoomLevel; + _boundaryRect = _getBoundaryRect(); + widget.zoomController.controllerMatrix = + Matrix4.identity() + ..scale(_getScale(widget.initialZoomLevel)) + ..setTranslation( + Vector3(widget.initialRect.left, widget.initialRect.top, 0.0), + ); + } + + return Listener( + onPointerDown: _handlePointerDown, + onPointerUp: _handlePointerUp, + onPointerCancel: _handlePointerCancel, + onPointerSignal: + widget.enableMouseWheelZooming ? _handleMouseWheelZooming : null, behavior: HitTestBehavior.translucent, - child: widget.child, - ), - ); - }); + child: GestureDetector( + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onScaleEnd: _handleScaleEnd, + behavior: HitTestBehavior.translucent, + child: widget.child, + ), + ); + }, + ); } } @@ -766,8 +893,9 @@ double _getScale(double zoomLevel) { /// Return the exact pixel point of the given matrix. Offset _getActualPointInMatrix(Matrix4 matrix, Offset localPoint) { final Matrix4 inverseMatrix = Matrix4.inverted(matrix); - final Vector3 untransformed = - inverseMatrix.transform3(Vector3(localPoint.dx, localPoint.dy, 0)); + final Vector3 untransformed = inverseMatrix.transform3( + Vector3(localPoint.dx, localPoint.dy, 0), + ); return Offset(untransformed.x, untransformed.y); } @@ -817,9 +945,10 @@ class ZoomableController { if (actionType != ActionType.none) { _actualRect = value; final double newScale = pow(2, _zoomLevel - 1).toDouble(); - controllerMatrix = Matrix4.identity() - ..scale(newScale) - ..setTranslation(Vector3(_actualRect.left, _actualRect.top, 0.0)); + controllerMatrix = + Matrix4.identity() + ..scale(newScale) + ..setTranslation(Vector3(_actualRect.left, _actualRect.top, 0.0)); notifyListeners(); return; } @@ -830,9 +959,10 @@ class ZoomableController { _zoomLevel = zoomLevel; _actualRect = actualRect; final double newScale = pow(2, _zoomLevel - 1).toDouble(); - controllerMatrix = Matrix4.identity() - ..scale(newScale) - ..setTranslation(Vector3(_actualRect.left, _actualRect.top, 0.0)); + controllerMatrix = + Matrix4.identity() + ..scale(newScale) + ..setTranslation(Vector3(_actualRect.left, _actualRect.top, 0.0)); notifyListeners(); } diff --git a/packages/syncfusion_flutter_maps/lib/src/settings.dart b/packages/syncfusion_flutter_maps/lib/src/settings.dart index a7991cbc7..d5ec172be 100644 --- a/packages/syncfusion_flutter_maps/lib/src/settings.dart +++ b/packages/syncfusion_flutter_maps/lib/src/settings.dart @@ -183,10 +183,12 @@ class MapColorMapper { this.minOpacity, this.maxOpacity, this.text, - }) : assert((from == null && to == null) || - (from != null && to != null && from < to && to > from)), - assert(minOpacity == null || minOpacity != 0), - assert(maxOpacity == null || maxOpacity != 0); + }) : assert( + (from == null && to == null) || + (from != null && to != null && from < to && to > from), + ), + assert(minOpacity == null || minOpacity != 0), + assert(maxOpacity == null || maxOpacity != 0); /// Sets the range start for the color mapping. /// @@ -683,7 +685,7 @@ class MapColorMapper { @override int get hashCode => - hashValues(from, to, value, color, minOpacity, maxOpacity, text); + Object.hash(from, to, value, color, minOpacity, maxOpacity, text); } /// Customizes the appearance of the data labels. @@ -886,7 +888,7 @@ class MapDataLabelSettings extends DiagnosticableTree { } @override - int get hashCode => hashValues(textStyle, overflowMode); + int get hashCode => Object.hash(textStyle, overflowMode); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -895,8 +897,9 @@ class MapDataLabelSettings extends DiagnosticableTree { if (textStyle != null) { properties.add(textStyle!.toDiagnosticsNode(name: 'textStyle')); } - properties - .add(EnumProperty('overflowMode', overflowMode)); + properties.add( + EnumProperty('overflowMode', overflowMode), + ); } } @@ -1264,7 +1267,7 @@ class MapBubbleSettings extends DiagnosticableTree { @override int get hashCode => - hashValues(color, strokeWidth, strokeColor, maxRadius, minRadius); + Object.hash(color, strokeWidth, strokeColor, maxRadius, minRadius); /// Creates a copy of this class but with the given fields /// replaced with the new values. @@ -1634,7 +1637,7 @@ class MapSelectionSettings extends DiagnosticableTree { } @override - int get hashCode => hashValues(color, strokeWidth, strokeColor); + int get hashCode => Object.hash(color, strokeWidth, strokeColor); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -2037,7 +2040,7 @@ class MapTooltipSettings extends DiagnosticableTree { } @override - int get hashCode => hashValues(hideDelay, color, strokeWidth, strokeColor); + int get hashCode => Object.hash(hideDelay, color, strokeWidth, strokeColor); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/packages/syncfusion_flutter_maps/lib/src/theme.dart b/packages/syncfusion_flutter_maps/lib/src/theme.dart new file mode 100644 index 000000000..1dc589aab --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/theme.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// [MapsThemeData] this class provides themeData. +/// MapsThemeData class extends the 'SfMapsThemeData' class and customize +/// the appearance of a mapping component based on th colorScheme obtained from +/// the provided [BuildContext]. +class MapsThemeData extends SfMapsThemeData { + /// This a constructor that takes a [BuildContext] as a parameter.This context + /// is used for obtaining the colorScheme of the current theme. + MapsThemeData(this.context); + + /// Property that stores the provided [BuildContext] + /// context is later used to obtain the colorScheme. + final BuildContext context; + + /// A late-initialized property representing the color scheme obtained from + /// the current theme using the provided [BuildContext] + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + /// Specifies the sub layer color of the maps widgets. + Color get subLayerColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(198, 198, 198, 1) + : const Color.fromRGBO(71, 71, 71, 1); + + /// Specifies the sub layer stroke color of the maps widgets. + Color get subLayerStrokeColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(145, 145, 145, 1) + : const Color.fromRGBO(133, 133, 133, 1); + + /// Specifies the sub layer width of the maps widgets. + double get subLayerStrokeWidth => + colorScheme.brightness == Brightness.light ? 0.5 : 0.25; + + // TODO(Aswini): Dark color not appiled properly. + @override + Color? get layerColor => colorScheme.onSurface[29]; + + @override + Color? get layerStrokeColor => colorScheme.onSurface[47]; + + @override + Color? get markerIconColor => colorScheme.primary[98]; + + @override + Color? get bubbleColor => + colorScheme.useMaterial3 + ? colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(255, 180, 0, 0.4) + : const Color.fromRGBO(201, 88, 142, 0.4) + : colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(98, 0, 238, 0.5) + : const Color.fromRGBO(187, 134, 252, 0.8); + + // TODO(Aswini): Dark color not appiled properly. + @override + Color? get bubbleStrokeColor => colorScheme.transparent[255]; + + @override + Color? get selectionColor => colorScheme.outlineVariant[255]; + + @override + Color? get selectionStrokeColor => colorScheme.onPrimary[75]; + + @override + Color? get tooltipColor => colorScheme.onSurface[256]; + + @override + Color? get tooltipStrokeColor => colorScheme.inverseSurface[257]; + + @override + Color? get toggledItemColor => colorScheme.onSurface[24]; + + @override + Color? get toggledItemStrokeColor => colorScheme.onSurface[95]; +} diff --git a/packages/syncfusion_flutter_maps/lib/src/utils.dart b/packages/syncfusion_flutter_maps/lib/src/utils.dart index 65408e03a..3d8e792b8 100644 --- a/packages/syncfusion_flutter_maps/lib/src/utils.dart +++ b/packages/syncfusion_flutter_maps/lib/src/utils.dart @@ -29,10 +29,11 @@ Color getSaturatedColor(Color color, [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); + ((1 - factor) * (color.r * 255) + factor * (mix.r * 255)).toInt(), + ((1 - factor) * (color.g * 255) + factor * (mix.g * 255)).toInt(), + ((1 - factor) * (color.b * 255) + factor * (mix.b * 255)).toInt(), + 1, + ); } Size getBoxSize(BoxConstraints constraints) { @@ -42,8 +43,13 @@ Size getBoxSize(BoxConstraints constraints) { return Size(width, height); } -Offset pixelFromLatLng(num latitude, num longitude, Size size, - [Offset offset = Offset.zero, double scale = 1.0]) { +Offset pixelFromLatLng( + num latitude, + num longitude, + Size size, [ + Offset offset = Offset.zero, + double scale = 1.0, +]) { final double x = (longitude + 180.0) / 360.0; final double sinLatitude = sin(latitude * pi / 180.0); final double y = @@ -55,12 +61,20 @@ Offset pixelFromLatLng(num latitude, num longitude, Size size, } MapLatLng getPixelToLatLng( - Offset offset, Size size, Offset translation, double scale) { + Offset offset, + Size size, + Offset translation, + double scale, +) { return pixelToLatLng(offset, size, translation, scale); } -MapLatLng pixelToLatLng(Offset offset, Size size, - [Offset translation = Offset.zero, double scale = 1.0]) { +MapLatLng pixelToLatLng( + Offset offset, + Size size, [ + Offset translation = Offset.zero, + double scale = 1.0, +]) { final double mapSize = size.longestSide * scale; final double x = ((offset.dx - translation.dx).clamp(0, mapSize - 1) / mapSize) - 0.5; @@ -79,14 +93,24 @@ MapLatLng getFocalLatLng(MapLatLngBounds bounds) { return MapLatLng(latitude, longitude); } -double getZoomLevel(MapLatLngBounds bounds, LayerType layerType, Size size, - [double actualShapeSizeFactor = 1.0]) { +double getZoomLevel( + MapLatLngBounds bounds, + LayerType layerType, + Size size, [ + double actualShapeSizeFactor = 1.0, +]) { switch (layerType) { case LayerType.shape: final Offset northEast = pixelFromLatLng( - bounds.northeast.latitude, bounds.northeast.longitude, size); + bounds.northeast.latitude, + bounds.northeast.longitude, + size, + ); final Offset southWest = pixelFromLatLng( - bounds.southwest.latitude, bounds.southwest.longitude, size); + bounds.southwest.latitude, + bounds.southwest.longitude, + size, + ); final Rect boundsRect = Rect.fromPoints(northEast, southWest); final double latZoom = size.height / boundsRect.height; final double lngZoom = size.width / boundsRect.width; @@ -94,12 +118,19 @@ double getZoomLevel(MapLatLngBounds bounds, LayerType layerType, Size size, case LayerType.tile: // Calculating the scale value for the given bounds using the // default tile layer size with default minimum zoom level. - final Size tileLayerSize = - Size.square(getTotalTileWidth(kDefaultMinZoomLevel)); + final Size tileLayerSize = Size.square( + getTotalTileWidth(kDefaultMinZoomLevel), + ); final Offset northEast = pixelFromLatLng( - bounds.northeast.latitude, bounds.northeast.longitude, tileLayerSize); + bounds.northeast.latitude, + bounds.northeast.longitude, + tileLayerSize, + ); final Offset southWest = pixelFromLatLng( - bounds.southwest.latitude, bounds.southwest.longitude, tileLayerSize); + bounds.southwest.latitude, + bounds.southwest.longitude, + tileLayerSize, + ); final Rect boundsRect = Rect.fromPoints(northEast, southWest); // Converting scale into zoom level. final double latZoomLevel = log(boundsRect.height / size.height) / log(2); @@ -108,9 +139,15 @@ double getZoomLevel(MapLatLngBounds bounds, LayerType layerType, Size size, } } -String getTrimText(String text, TextStyle style, double maxWidth, - TextPainter painter, double width, - [double? nextTextHalfWidth, bool isInsideLastLabel = false]) { +String getTrimText( + String text, + TextStyle style, + double maxWidth, + TextPainter painter, + double width, [ + double? nextTextHalfWidth, + bool isInsideLastLabel = false, +]) { final int actualTextLength = text.length; String trimmedText = text; int trimLength = 3; // 3 dots @@ -122,7 +159,10 @@ String getTrimText(String text, TextStyle style, double maxWidth, break; } else { trimmedText = text.replaceRange( - actualTextLength - trimLength, actualTextLength, '...'); + actualTextLength - trimLength, + actualTextLength, + '...', + ); painter.text = TextSpan(style: style, text: trimmedText); painter.layout(); trimLength++; @@ -131,9 +171,10 @@ String getTrimText(String text, TextStyle style, double maxWidth, if (isInsideLastLabel && nextTextHalfWidth != null) { width = painter.width + nextTextHalfWidth; } else { - width = nextTextHalfWidth != null - ? painter.width / 2 + nextTextHalfWidth - : painter.width; + width = + nextTextHalfWidth != null + ? painter.width / 2 + nextTextHalfWidth + : painter.width; } } @@ -168,7 +209,9 @@ double getLayerSizeFactor(MapController controller) { } MapProvider getSourceProvider( - Object geoJsonSource, GeoJSONSourceType geoJSONSourceType) { + Object geoJsonSource, + GeoJSONSourceType geoJSONSourceType, +) { switch (geoJSONSourceType) { case GeoJSONSourceType.asset: return AssetMapProvider(geoJsonSource.toString()); @@ -180,6 +223,56 @@ MapProvider getSourceProvider( } } +// Calculates the shape's path center and width for data label rendering. +void findPathCenterAndWidth( + double signedArea, + double centerX, + double centerY, + MapModel mapModel, +) { + // Used mathematical formula to find the center of polygon points. + signedArea /= 2; + centerX = centerX / (6 * signedArea); + centerY = centerY / (6 * signedArea); + mapModel.shapePathCenter = Offset(centerX, centerY); + double minX = double.infinity; + double maxX = double.negativeInfinity; + 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 Offset point in points) { + distance = (centerY - point.dy).abs(); + if (point.dx < centerX) { + // Collected all points which is less 10 pixels distance from + // 'center y' to position the labels more smartly. + if (distance < 10) { + minDistances.add(point.dx); + } + if (distance < minDistance) { + minX = point.dx; + minDistance = distance; + } + } else if (point.dx > centerX) { + if (distance < 10) { + maxDistances.add(point.dx); + } + + if (distance > maxDistance) { + maxX = point.dx; + maxDistance = distance; + } + } + } + } + + mapModel.shapeWidth = + max(maxX, maxDistances.reduce(max)) - min(minX, minDistances.reduce(min)); +} + /// An interpolation between two latlng. /// /// This class specializes the interpolation of [Tween] to use @@ -187,7 +280,7 @@ MapProvider getSourceProvider( class MapLatLngTween extends Tween { /// Creates an [MapLatLng] tween. MapLatLngTween({MapLatLng? begin, MapLatLng? end}) - : super(begin: begin, end: end); + : super(begin: begin, end: end); @override 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 5a3b528cb..db1bb7366 100644 --- a/packages/syncfusion_flutter_maps/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/pubspec.yaml @@ -1,10 +1,16 @@ name: syncfusion_flutter_maps description: A Flutter Maps library for creating beautiful, interactive, and customizable maps from shape files or WMTS services to visualize the geographical area. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_maps +screenshots: + - description: 'This screenshot shows the different types of shape layer feature.' + path: screenshots/shape_layer_maps.png + - description: 'This screenshot shows the different types of tile layer feature.' + path: screenshots/tile_layer_maps.png + environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: @@ -12,10 +18,14 @@ dependencies: syncfusion_flutter_core: path: ../syncfusion_flutter_core - - http: ">=0.12.0 <=0.13.4" + + + + http: ^1.0.0 vector_math: ">=2.1.0 <=3.0.0" + + flutter: - \ No newline at end of file + diff --git a/packages/syncfusion_flutter_maps/screenshots/shape_layer_maps.png b/packages/syncfusion_flutter_maps/screenshots/shape_layer_maps.png new file mode 100644 index 000000000..2beb554e3 Binary files /dev/null and b/packages/syncfusion_flutter_maps/screenshots/shape_layer_maps.png differ diff --git a/packages/syncfusion_flutter_maps/screenshots/tile_layer_maps.png b/packages/syncfusion_flutter_maps/screenshots/tile_layer_maps.png new file mode 100644 index 000000000..b8406e231 Binary files /dev/null and b/packages/syncfusion_flutter_maps/screenshots/tile_layer_maps.png differ diff --git a/packages/syncfusion_flutter_pdf/.pubignore b/packages/syncfusion_flutter_pdf/.pubignore new file mode 100644 index 000000000..805607a57 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/.pubignore @@ -0,0 +1,4 @@ +# See https://dart.dev/tools/pub/publishing#what-files-are-published + +lib/src/test/* + diff --git a/packages/syncfusion_flutter_pdf/CHANGELOG.md b/packages/syncfusion_flutter_pdf/CHANGELOG.md index 22cb8cd29..bf01dfbf3 100644 --- a/packages/syncfusion_flutter_pdf/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdf/CHANGELOG.md @@ -1,55 +1,485 @@ -## [19.3.55-beta] - 11/23/2021 +## Unreleased **Bugs** -* Exception will no longer been thrown while extracting text from particular PDF document. +* The compatible version of our Flutter PDF library has been updated to Flutter SDK 3.32.0. -## [19.3.48-beta] - 11/02/2021 +## [29.2.11] - 17/06/2025 **Bugs** -* Emoji characters preservation issue has been resolved now. +* Resolved an exception that occurred when hiding document-level layers in a specific PDF document. -* Null check exception will no longer been thrown while removing password from PDF document. +## [29.2.9] - 05/06/2025 -## [19.3.47-beta] - 10/26/2021 +**Bugs** + +* Resolved a mismatch error in the text word length and text glyph length from a specific PDF document. + +## [29.2.7] - 27/05/2025 + +**Bugs** + +* Resolved a range error exception while extracting text lines from a specific PDF document. + +## [29.2.4] - 13/05/2025 + +**Bugs** + +* Resolved an Out-Of-Memory crash that occurred during the decryption of PDF document. +* Resolved a performance issue when decrypting password-protected PDF documents. + +## [29.1.41] - 06/05/2025 + +**Bugs** + +* Resolved an issue where an exception occurred when flattening a signature field in a PDF document. + +## [29.1.40] - 29/04/2025 + +**Bugs** + +* Resolved a content preservation issue that occurred when drawing multiple PDF grids on the same page of a PDF document. +* Resolved an issue where PDF form is returned as null when acro form has no fields. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.38] - 04/15/2025 + +**Bugs** + +* Resolved an issue where the null check operator was incorrectly used on a nullable value. +* Resolved an issue where the PDF was shifting towards the top right corner when drawing existing page templates on PDFs. +* Exception occurs when getting the bounds of the text box form field item in a document has been resolved. + +## [29.1.37] - 04/08/2025 + +**Bugs** + +* Resolved the invalid cross reference table issue occurs while loading the specific PDF document. +* Resolved the Layer name not preserved properly while removing layers in the PDF document + +## [29.1.35] - 04/01/2025 + +**Bugs** + +* Resolved the exception that occurred while decrypting PDF files on the Flutter web platform. + +## [29.1.33] - 03/25/2025 + +**General** + +* The compatible version of our Flutter PDF library has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter PDF example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter PDF example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +**Bugs** + +* Fixed incorrect TextWord bounds when extracting text from a cropped PDF document. + +## [28.2.9] - 03/04/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +**Bugs** + +* Resolved the TypeError occurs when getting the bounds of the form field in a specific document + +## [28.2.7] - 02/25/2025 + +**Bugs** + +* Resolved the Performance issue occurs while decrypting the pdf + +## [28.2.5] - 02/11/2025 + +**Bugs** + +* Resolved the Format exception occurs while extracting the text from the PDF document + +## [28.2.4] - 02/04/2025 + +**Bugs** + +* Resolved the preservation issue when filling the multiline text box field. +* Typecasting issue occurs while loading the checkbox fields in the PDF document has been resolved. + +## [28.1.39] - 01/14/2025 + +**Bugs** + +* Resolved an issue where Unicode characters were not correctly recognized during text extraction from PDF documents. + +## [28.1.38] - 01/07/2025 + +**Bugs** + +* The preservation issue encountered when adding annotations to a document has been resolved. + +## [27.2.3] - 11/22/2024 + +**Bugs** + +* The issue of the signature appearance being duplicated when viewing the resultant PDF in the Syncfusion® viewer has been resolved. + +## [27.2.2] - 11/15/2024 + +**Bugs** + +* The format exception that occurred while signing the document with a specific certificate using an external signer has been resolved. + +## [27.1.58] - 11/5/2024 + +**Bugs** + +* The issue of an exception occurring when drawing emoji symbols in PDF form fields has been resolved. + +## [27.1.55] - 10/22/2024 + +**Bugs** + +* Type casting issue occurs while extracting text lines from a specific PDF document has been resolved. + +## [27.1.52] - 10/08/2024 + +**Bugs** + +* The issue of the file being corrupted while inserting a page into an existing PDF document has been resolved. + +## [27.1.50] - 09/24/2024 + +**Bugs** + +* Type casting issue occurs while loading annotations from the PDF document has been resolved. +* The issue where file size did not decrease after removing pages from the PDF document has been resolved. + +## [26.2.14] - 09/10/2024 + +**General** + +* The compatible version of our Flutter PDF library has been updated to Flutter SDK 3.24.0. + +**Bugs** + +* The issue of incorrect bounds being retrieved from text markup annotations created with the Firefox PDF viewer has been resolved. + +## [26.2.10] - 08/20/2024 + +**Bugs** + +* Exception will no longer be thrown while extracting text from a particular PDF document. + +## [26.2.9] - 08/13/2024 + +**Bugs** + +* Text will now be properly extracted in the Safari browser on macOS. + +## [26.2.7] - 07/30/2024 + +**Bugs** + +* The text color now remains consistent after decrypting and saving the PDF document. + +## [26.2.4] - 07/24/2024 + +**Bugs** + +* Incorrect bounds are no longer being retrieved when extracting text from a specific PDF document. + +## [26.1.42] - 07/16/2024 + +**Bugs** + +* The issue causing null check errors when retrieving items from a radio button field in the PDF document has been resolved. + +* The issue with type casting no longer occurs when retrieving annotations from a PDF document. + +## [26.1.41] - 07/09/2024 + +**Bugs** + +* The performance issue no longer occurs when reading the signature field from the PDF document. + +* The type cast issue no longer occurs when flattening the signature in a specific PDF document. + +## [26.1.40] - 07/02/2024 + +**Bugs** + +* High memory consumption will no longer occur while extracting text from large PDF documents. + +## [26.1.38] - 06/19/2024 + +**Bugs** + +* Extracting text lines now returns the proper bounds for the CID font type in the PDF document. + +* Performance issue no longer occurs while extracting text lines from the PDF document. + +## [26.1.35] - 06/11/2024 **Features** -* Provided the support to identify whether the TextGlyph is rotated or not. +* Provided support for adding timestamp and LTV in PDF signature. + +## [25.2.6] - 05/28/2024 -## [19.3.46-beta] - 10/19/2021 **Bugs** -* The text search bounds related issue has been resolved now. +* Resolved the issue where flattening some form fields was not working in a specific PDF document. -## [19.3.45-beta] - 10/12/2021 +## [25.2.5] - 05/21/2024 **Bugs** -* PDF destination retrieval related issue has been resolved now. +* Preservation issues no longer occur when drawing on a page with negative crop box x and y coordinates. -## [19.2.56-beta.1] - 08/17/2021 +## [25.1.42] - 04/30/2024 **Bugs** -* Resolved the RangeError while extracting text lines from the PDF document. -* The TextLine extraction bounds related issue has been resolved now. +* Extracting text from an encrypted PDF document will no longer significantly increase file size and corrupt the document. -## [19.2.56-beta] - 08/17/2021 +## [25.1.41] - 04/23/2024 + +**Bugs** + +* The existing signature no longer becomes invalid when adding a second signature to the PDF document. + +## [25.1.39] - 04/09/2024 + +**Bugs** + +* RangeError will no longer occur while extracting text lines from the PDF document. + +## [25.1.37] - 03/26/2024 + +**Bugs** + +* Preservation issue will no longer occur while flattening the signature field in the PDF document. + +* The issue with the invalid signature has been resolved when signing the PDF document. + +## [25.1.35] - 03/15/2024 + +**Breaking changes** + +* The `sign` method in the `IPdfExternalSigner` class has been changed to an asynchronous type, and the `signSync` method has been added for synchronous signing. **Features** -* Provided the support to get or set rotation in an existing PDF page. +* Provided support for importing and exporting annotations in the PDF document. -## [19.2.48-beta] - 07/20/2021 +* Provided support for asynchronous external signing in the PDF document. + +## [24.2.8] - 02/27/2024 **Bugs** -* The white space missing issue while extracting text has been resolved now. -* The unhandled exception when encrypting PDF document is resolved now. +* Resolved the issue where extracting text returns incorrect results for a specific PDF document. -## [19.2.44-beta] - 06/30/2021 +* Invalid font name error will no longer occur when extracting text from specific PDF document. + +## [24.2.5] - 02/13/2024 + +**Bugs** + +* Extracting text lines will no longer return incorrect results for specific PDF documents. + +## [24.2.4] - 02/06/2024 + +**Bugs** + +* Find text related issues are now resolved in PDF documents. + +* Extract text is now working properly in specific PDF document. + +## [24.2.3] - 01/31/2024 + +**Features** + +* Provided support to check and uncheck the check box field items. + +## [24.1.46] - 01/17/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## [24.1.45] - 01/09/2024 + +**Bugs** + +* Preservation failure no longer occurs in Mac PDF viewer while saving specific encrypted PDF document. + +## [24.1.44] - 01/03/2023 + +**Bugs** + +* Null check error will no longer occur while removing pages from the PDF document. + +## [24.1.43] - 12/27/2023 + +**Bugs** + +* Preservation issue will no longer occur while flattening text box field in specific PDF document. + +## [24.1.41] - 12/18/2023 + +**Features** + +* Provided support for text markup and popup annotations. + +* Provided support for adding annotation flags to PDF annotations. + +**Bugs** + +* The issue of retrieving incorrect values from the text box field after saving an encrypted PDF document has been resolved. + +## [23.1.44] - 11/07/2023 + +**Bugs** + +* The null reference exception that occurred while retrieving the page from the form fields in the PDF has been resolved. + +## [23.1.40] - 10/10/2023 + +**Bugs** + +* Resolved the incorrect image dimension recording in PdfBitmap. + +## [23.1.39] - 10/04/2023 + +**Features** + +* Support has been provided for back color, border color, and border style in the PDF signature field. + +## [22.2.11] - 08/29/2023 + +**Bugs** + +* High memory consumption will no longer occurs while extracting text from large PDF documents. + +* Null check exceptions will no longer occur while drawing PDF grid in a loaded page. + +## [22.2.9] - 08/15/2023 + +**Bugs** + +* Null reference exceptions will no longer occur when retrieving form fields from the PDF document. + +## [22.2.5] - 07/27/2023 + +**Bugs** + +* Resolved white background preservation while flattening loaded empty signature field. + +* Type casting issue no longer occurs while flattening specific PDF document. + +## [22.1.36] - 06/28/2023 + +**Bugs** + +* Resolved the space between text missing issue in the extract text layout and lines. + +* PDF signature added with signed name is now valid in Adobe viewer. + +## [21.2.4] - 05/09/2023 + +**Bugs** + +* Null reference exceptions will no longer occur while getting form fields from the PDF document. + +* Now, spaces are preserved properly when extracting text with the layout. + +* The preservation issue no longer occurs after modifying the values in the fields of the PDF document. + +## [21.1.41] - 04/18/2023 + +**Bugs** + +* The unhandled exception that occurred during the extraction of text and flattening of form fields in the PDF document has been resolved. + +## [21.1.39] - 04/11/2023 + +**Bugs** + +* RTL bookmark title is now properly retrieved from Encrypted PDF documents. + +## [21.1.37] - 03/29/2023 + +**Bugs** + +* Text words are now properly split while extracting text from PDF documents. + +## [20.4.54] - 03/15/2023 + +**Bugs** + +* The issue of PDF document size increasing after removing pages has been resolved. + +## [20.4.50] - 02/14/2023 + +**Bugs** + +* Resolved the document corruption exception when signing existing signed PDF documents. + +* Text bounds are now retrieved properly when finding text from cropped PDF documents. + +## [20.3.57] - 11/15/2022 + +**Bugs** + +* Alpha channel is not initialized properly in transparent brush is now resolved. + +## [20.3.56] - 11/08/2022 + +**Features** + +* Provided support to set signed date while signing the pdf document. + +## [20.2.48] - 09/06/2022 + +**Bugs** + +* The type casting issue when trying to get annotation is now resolved. + +## [20.2.45] - 08/23/2022 + +**Bugs** + +* The font is not updated properly for loaded form fields is now resolved. + +## [20.2.36] - 06/30/2022 + +**Breaking changes** + +* The `save` method has been changed to an asynchronous type in the `PdfDocument` and the `saveSync` method has been added for synchronous. + +**Features** + +* Provided asynchronous save support for PDF documents. + +**Bugs** + +* The preservation issue when flattening the PDF text box field is now resolved. + +* Resolved the document corruption issue while modifying encrypted PDF document. + +## [20.1.48-beta] - 04/12/2022 + +**Bugs** + +* The layout issue when extracting text from the PDF document is now resolved. + +## [20.1.47-beta] - 04/04/2022 **Features** @@ -59,7 +489,7 @@ **Known Limitation** -* RTL text with LTR text is not working in find text, Ex. .(PDF فلم يف +* Combination of RTL and LTR text in find operations will not work. For example "80٪ خصم في المحدد jeans". ## [19.4.41-beta] - 01/04/2022 @@ -258,7 +688,7 @@ Initial release -**Features** +**Features** * Provided the support for creating a PDF document with pages and sections. * Provided the support for adding text, images, shapes, and more. @@ -272,6 +702,6 @@ Initial release * Provided the support for creating bookmarks to the PDF. * Provided the support for drawing images (JPEG and PNG only) to the PDF document. * Provided the support for adding hyperlinks and internal document navigations. -* Provided the support for color, pen, and brushes. +* Provided the support for color, pen, and brushes. * Provided the support for adding Chinese, Japanese, and Korean text with the standard CJK fonts. * Provided the support for creating and drawing PdfTemplates. diff --git a/packages/syncfusion_flutter_pdf/README.md b/packages/syncfusion_flutter_pdf/README.md index 95e33975a..248d5abd3 100644 --- a/packages/syncfusion_flutter_pdf/README.md +++ b/packages/syncfusion_flutter_pdf/README.md @@ -63,15 +63,12 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

@@ -108,7 +105,7 @@ document.pages.add().graphics.drawString( brush: PdfSolidBrush(PdfColor(0, 0, 0)), bounds: const Rect.fromLTWH(0, 0, 150, 20)); // Save the document. -File('HelloWorld.pdf').writeAsBytes(document.save()); +File('HelloWorld.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -128,7 +125,7 @@ final PdfFont font = PdfTrueTypeFont(fontData, 12); document.pages.add().graphics.drawString('Hello World!!!', font, bounds: const Rect.fromLTWH(0, 0, 200, 50)); // Save the document. -File('TrueType.pdf').writeAsBytes(document.save()); +File('TrueType.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -150,7 +147,7 @@ document.pages .graphics .drawImage(image, const Rect.fromLTWH(0, 0, 500, 200)); // Save the document. -File('ImageToPDF.pdf').writeAsBytes(document.save()); +File('ImageToPDF.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -187,7 +184,7 @@ page.graphics.drawLine( Offset(0, layoutResult.bounds.bottom + 10), Offset(page.getClientSize().width, layoutResult.bounds.bottom + 10)); // Save the document. -File('TextFlow.pdf').writeAsBytes(document.save()); +File('TextFlow.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -236,7 +233,7 @@ orderedList.draw( bounds: Rect.fromLTWH( 0, 0, page.getClientSize().width, page.getClientSize().height)); // Save the document. -File('BulletandList.pdf').writeAsBytes(document.save()); +File('BulletandList.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -285,7 +282,7 @@ grid.draw( bounds: Rect.fromLTWH( 0, 0, page.getClientSize().width, page.getClientSize().height)); // Save the document. -File('PDFTable.pdf').writeAsBytes(document.save()); +File('PDFTable.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -319,7 +316,7 @@ document.template.bottom = footerTemplate; document.pages.add(); document.pages.add(); // Save the document. -File('HeaderandFooter.pdf').writeAsBytes(document.save()); +File('HeaderandFooter.pdf').writeAsBytes(await document.save()); // Dispose the document. document.dispose(); ``` @@ -340,7 +337,7 @@ page.graphics.drawString( brush: PdfSolidBrush(PdfColor(0, 0, 0)), bounds: const Rect.fromLTWH(0, 0, 150, 20)); //Save the document. -File('output.pdf').writeAsBytes(document.save()); +File('output.pdf').writeAsBytes(await document.save()); //Dispose the document. document.dispose(); ``` @@ -359,7 +356,7 @@ document.pages.add().graphics.drawString( brush: PdfSolidBrush(PdfColor(0, 0, 0)), bounds: const Rect.fromLTWH(0, 0, 150, 20)); //Save the document. -File('output.pdf').writeAsBytes(document.save()); +File('output.pdf').writeAsBytes(await document.save()); //Dispose the document. document.dispose(); ``` @@ -379,7 +376,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('annotations.pdf').writeAsBytes(document.save()); +File('annotations.pdf').writeAsBytes(await document.save()); //Dispose the document. document.dispose(); ``` @@ -411,7 +408,7 @@ bookmark.destination = PdfDestination(document.pages[1], Offset(20, 20)); //Set the bookmark color. bookmark.color = PdfColor(255, 0, 0); //Save the document. -File('bookmark.pdf').writeAsBytes(document.save()); +File('bookmark.pdf').writeAsBytes(await document.save()); //Dispose the document. document.dispose(); ``` @@ -497,7 +494,7 @@ security.ownerPassword = 'ownerpassword@123'; security.algorithm = PdfEncryptionAlgorithm.aesx256Bit; //Save the document. -File('secured.pdf').writeAsBytes(document.save()); +File('secured.pdf').writeAsBytes(await document.save()); //Dispose the document. document.dispose(); @@ -522,7 +519,7 @@ final PdfDocument document = PdfDocument(conformanceLevel: PdfConformanceLevel.a 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()); +File('conformance.pdf').writeAsBytesSync(await document.save()); document.dispose(); ``` @@ -552,7 +549,7 @@ document.form.fields.add(PdfCheckBoxField( isChecked: true)); //Save and dispose the document. -File('form.pdf').writeAsBytesSync(document.save()); +File('form.pdf').writeAsBytesSync(await document.save()); document.dispose(); ``` @@ -575,7 +572,7 @@ PdfRadioButtonListField gender = form.fields[1] as PdfRadioButtonListField; gender.selectedIndex = 1; //Save and dispose the document. -File('output.pdf').writeAsBytesSync(document.save()); +File('output.pdf').writeAsBytesSync(await document.save()); document.dispose(); ``` @@ -593,7 +590,7 @@ PdfForm form = document.form; form.flattenAllFields(); //Save and dispose the document. -File('output.pdf').writeAsBytesSync(document.save()); +File('output.pdf').writeAsBytesSync(await document.save()); document.dispose(); ``` @@ -624,7 +621,7 @@ PdfSignatureField signatureField = PdfSignatureField(page, 'Signature', document.form.fields.add(signatureField); //Save and dispose the PDF document -File('signed.pdf').writeAsBytes(document.save()); +File('signed.pdf').writeAsBytes(await document.save()); document.dispose(); ``` Add the following code to sign the existing PDF document. @@ -645,7 +642,7 @@ signatureField.signature = PdfSignature( ); //Save and dispose the document. -File('output.pdf').writeAsBytesSync(document.save()); +File('output.pdf').writeAsBytesSync(await document.save()); document.dispose(); ``` @@ -654,14 +651,11 @@ Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-wit ## Support and feedback -* 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). +* For any questions, please post them in our [community forums](https://www.syncfusion.com/forums) or contact our [Syncfusion support team](https://support.syncfusion.com/support/tickets/create). 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 Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 22,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. -Today we provide 1,600+ 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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-controls)). We provide ready-to deploy enterprise software in our Bold line of products for dashboarding and reporting. Many customers have saved millions in licensing fees by deploying our software. - - - +Today we provide 1,600+ 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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-controls)). We provide ready-to deploy enterprise software in our Bold line of products for dashboarding and reporting. Many customers have saved millions in licensing fees by deploying our software. \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdf/analysis_options.yaml b/packages/syncfusion_flutter_pdf/analysis_options.yaml index 84c92bd60..03286a00b 100644 --- a/packages/syncfusion_flutter_pdf/analysis_options.yaml +++ b/packages/syncfusion_flutter_pdf/analysis_options.yaml @@ -1,8 +1 @@ include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore - library_private_types_in_public_api: ignore - lines_longer_than_80_chars: ignore - avoid_as: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdf/example/README.md b/packages/syncfusion_flutter_pdf/example/README.md new file mode 100644 index 000000000..1649922e1 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/example/README.md @@ -0,0 +1,3 @@ +# pdf_example + +Demo for creating a PDF file using syncfusion_flutter_pdf package. diff --git a/packages/syncfusion_flutter_pdf/example/android/.gitignore b/packages/syncfusion_flutter_pdf/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_pdf/example/android/.gitignore +++ b/packages/syncfusion_flutter_pdf/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_pdf/example/android/app/build.gradle b/packages/syncfusion_flutter_pdf/example/android/app/build.gradle index 5fe3c929f..e6af82920 100644 --- a/packages/syncfusion_flutter_pdf/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_pdf/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.pdf_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.pdf_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_pdf/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_pdf/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_pdf/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_pdf/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_pdf/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_pdf/example/android/app/src/main/AndroidManifest.xml index 3f41384db..8c6372c09 100644 --- a/packages/syncfusion_flutter_pdf/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_pdf/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_pdf/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_pdf/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_pdf/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_pdf/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_pdf/example/android/build.gradle b/packages/syncfusion_flutter_pdf/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_pdf/example/android/build.gradle +++ b/packages/syncfusion_flutter_pdf/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_pdf/example/android/gradle.properties b/packages/syncfusion_flutter_pdf/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_pdf/example/android/gradle.properties +++ b/packages/syncfusion_flutter_pdf/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_pdf/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_pdf/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_pdf/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_pdf/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_pdf/example/android/settings.gradle b/packages/syncfusion_flutter_pdf/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_pdf/example/android/settings.gradle +++ b/packages/syncfusion_flutter_pdf/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_pdf/example/ios/.gitignore b/packages/syncfusion_flutter_pdf/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/.gitignore +++ b/packages/syncfusion_flutter_pdf/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_pdf/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_pdf/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_pdf/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_pdf/example/ios/Flutter/Debug.xcconfig index 592ceee85..ec97fc6f3 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_pdf/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdf/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_pdf/example/ios/Flutter/Release.xcconfig index 592ceee85..c4855bfe2 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_pdf/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.pbxproj index 510d8449c..b8112bec9 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,32 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 7F15601F119AE6C7562CC4FD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DE71D83B295040DBCD81AA8 /* Pods_RunnerTests.framework */; }; 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 */; }; + BC2FA9C62106F8FF4EDDAF3D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 590AA98BC407193C250FF4B1 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,10 +45,18 @@ /* 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 = ""; }; + 23F7C81AAFCD6382EFD03CAD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 2CAA9EF46737D3F8CE72F53B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 590AA98BC407193C250FF4B1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6B0873AA0771EE76F8993BC3 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; 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 = ""; }; + 7DE71D83B295040DBCD81AA8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 898AAF3537B003F29F8841CB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.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; }; @@ -42,19 +64,53 @@ 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 = ""; }; + B8C7602BF9696ECA39F77E64 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + E9F16AE549CDEA8D0B92FD68 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 8DF9B5731F7CB029C7C1A695 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7F15601F119AE6C7562CC4FD /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + BC2FA9C62106F8FF4EDDAF3D /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 28ED1629C965C88D3E0CDD9F /* Pods */ = { + isa = PBXGroup; + children = ( + E9F16AE549CDEA8D0B92FD68 /* Pods-Runner.debug.xcconfig */, + 23F7C81AAFCD6382EFD03CAD /* Pods-Runner.release.xcconfig */, + 898AAF3537B003F29F8841CB /* Pods-Runner.profile.xcconfig */, + B8C7602BF9696ECA39F77E64 /* Pods-RunnerTests.debug.xcconfig */, + 2CAA9EF46737D3F8CE72F53B /* Pods-RunnerTests.release.xcconfig */, + 6B0873AA0771EE76F8993BC3 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +128,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 28ED1629C965C88D3E0CDD9F /* Pods */, + F4BF9BBB9F2DF1C7061F3852 /* Frameworks */, ); sourceTree = ""; }; @@ -79,6 +138,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +150,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,32 +158,58 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { + F4BF9BBB9F2DF1C7061F3852 /* Frameworks */ = { isa = PBXGroup; children = ( + 590AA98BC407193C250FF4B1 /* Pods_Runner.framework */, + 7DE71D83B295040DBCD81AA8 /* Pods_RunnerTests.framework */, ); - name = "Supporting Files"; + name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + E1D99DA7F810369CA60E2DA5 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 8DF9B5731F7CB029C7C1A695 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 0FEE208F0E4B5EA6CF753A5A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + DE75AE7C47A1A2B33DA66CE7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +220,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +243,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -177,12 +278,36 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0FEE208F0E4B5EA6CF753A5A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +318,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -205,9 +331,56 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + DE75AE7C47A1A2B33DA66CE7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E1D99DA7F810369CA60E2DA5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +392,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +422,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +454,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +463,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -297,16 +479,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Z38D26686T; 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -316,11 +494,61 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B8C7602BF9696ECA39F77E64 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2CAA9EF46737D3F8CE72F53B /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6B0873AA0771EE76F8993BC3 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +578,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +593,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +603,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +635,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +644,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -431,16 +662,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Z38D26686T; 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -458,16 +685,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Z38D26686T; 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -480,6 +703,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +734,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..d795332e1 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_pdf/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + @@ -61,8 +89,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - + + diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_pdf/example/ios/Runner/AppDelegate.swift index 70693e4a8..626664468 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_pdf/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_pdf/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/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_pdf/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_pdf/example/ios/Runner/Info.plist index 8fc192e52..0cc49ee18 100644 --- a/packages/syncfusion_flutter_pdf/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_pdf/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Pdf Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_pdf/example/lib/main.dart b/packages/syncfusion_flutter_pdf/example/lib/main.dart index 3a395b1f8..09dcdf665 100644 --- a/packages/syncfusion_flutter_pdf/example/lib/main.dart +++ b/packages/syncfusion_flutter_pdf/example/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +// ignore: depend_on_referenced_packages import 'package:intl/intl.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; @@ -6,16 +7,16 @@ import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'save_file_mobile.dart' if (dart.library.html) 'save_file_web.dart'; void main() { - runApp(CreatePdfWidget()); + runApp(const CreatePdfWidget()); } /// Represents the PDF widget class. class CreatePdfWidget extends StatelessWidget { + const CreatePdfWidget({super.key}); + @override Widget build(BuildContext context) { - return const MaterialApp( - home: CreatePdfStatefulWidget(), - ); + return const MaterialApp(home: CreatePdfStatefulWidget()); } } @@ -32,22 +33,20 @@ class _CreatePdfState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Create PDF document'), - ), + appBar: AppBar(title: const Text('Create PDF document')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( - child: const Text('Generate PDF'), style: TextButton.styleFrom( - primary: Colors.white, + foregroundColor: Colors.white, backgroundColor: Colors.lightBlue, - onSurface: Colors.grey, + disabledForegroundColor: Colors.grey, ), onPressed: generateInvoice, - ) + child: const Text('Generate PDF'), + ), ], ), ), @@ -63,8 +62,9 @@ class _CreatePdfState extends State { final Size pageSize = page.getClientSize(); //Draw rectangle page.graphics.drawRectangle( - bounds: Rect.fromLTWH(0, 0, pageSize.width, pageSize.height), - pen: PdfPen(PdfColor(142, 170, 219, 255))); + bounds: Rect.fromLTWH(0, 0, pageSize.width, pageSize.height), + pen: PdfPen(PdfColor(142, 170, 219)), + ); //Generate PDF grid. final PdfGrid grid = getGrid(); //Draw the header section by creating text element @@ -74,7 +74,7 @@ class _CreatePdfState extends State { //Add invoice footer drawFooter(page, pageSize); //Save the PDF document - final List bytes = document.save(); + final List bytes = document.saveSync(); //Dispose the document. document.dispose(); //Save and launch the file. @@ -85,35 +85,46 @@ class _CreatePdfState extends State { PdfLayoutResult drawHeader(PdfPage page, Size pageSize, PdfGrid grid) { //Draw rectangle page.graphics.drawRectangle( - brush: PdfSolidBrush(PdfColor(91, 126, 215, 255)), - bounds: Rect.fromLTWH(0, 0, pageSize.width - 115, 90)); + brush: PdfSolidBrush(PdfColor(91, 126, 215)), + bounds: Rect.fromLTWH(0, 0, pageSize.width - 115, 90), + ); //Draw string page.graphics.drawString( - 'INVOICE', PdfStandardFont(PdfFontFamily.helvetica, 30), - brush: PdfBrushes.white, - bounds: Rect.fromLTWH(25, 0, pageSize.width - 115, 90), - format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.middle)); + 'INVOICE', + PdfStandardFont(PdfFontFamily.helvetica, 30), + brush: PdfBrushes.white, + bounds: Rect.fromLTWH(25, 0, pageSize.width - 115, 90), + format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.middle), + ); page.graphics.drawRectangle( - bounds: Rect.fromLTWH(400, 0, pageSize.width - 400, 90), - brush: PdfSolidBrush(PdfColor(65, 104, 205))); + bounds: Rect.fromLTWH(400, 0, pageSize.width - 400, 90), + brush: PdfSolidBrush(PdfColor(65, 104, 205)), + ); - page.graphics.drawString(r'$' + getTotalAmount(grid).toString(), - PdfStandardFont(PdfFontFamily.helvetica, 18), - bounds: Rect.fromLTWH(400, 0, pageSize.width - 400, 100), - brush: PdfBrushes.white, - format: PdfStringFormat( - alignment: PdfTextAlignment.center, - lineAlignment: PdfVerticalAlignment.middle)); + page.graphics.drawString( + r'$' + getTotalAmount(grid).toString(), + PdfStandardFont(PdfFontFamily.helvetica, 18), + bounds: Rect.fromLTWH(400, 0, pageSize.width - 400, 100), + brush: PdfBrushes.white, + format: PdfStringFormat( + alignment: PdfTextAlignment.center, + lineAlignment: PdfVerticalAlignment.middle, + ), + ); final PdfFont contentFont = PdfStandardFont(PdfFontFamily.helvetica, 9); //Draw string - page.graphics.drawString('Amount', contentFont, - brush: PdfBrushes.white, - bounds: Rect.fromLTWH(400, 0, pageSize.width - 400, 33), - format: PdfStringFormat( - alignment: PdfTextAlignment.center, - lineAlignment: PdfVerticalAlignment.bottom)); + page.graphics.drawString( + 'Amount', + contentFont, + brush: PdfBrushes.white, + bounds: Rect.fromLTWH(400, 0, pageSize.width - 400, 33), + format: PdfStringFormat( + alignment: PdfTextAlignment.center, + lineAlignment: PdfVerticalAlignment.bottom, + ), + ); //Create data foramt and convert it to text. final DateFormat format = DateFormat.yMMMMd('en_US'); final String invoiceNumber = @@ -125,14 +136,24 @@ class _CreatePdfState extends State { \r\n\r\n9920 BridgePointe Parkway, \r\n\r\n9365550136'''; PdfTextElement(text: invoiceNumber, font: contentFont).draw( - page: page, - bounds: Rect.fromLTWH(pageSize.width - (contentSize.width + 30), 120, - contentSize.width + 30, pageSize.height - 120)); + page: page, + bounds: Rect.fromLTWH( + pageSize.width - (contentSize.width + 30), + 120, + contentSize.width + 30, + pageSize.height - 120, + ), + ); return PdfTextElement(text: address, font: contentFont).draw( - page: page, - bounds: Rect.fromLTWH(30, 120, - pageSize.width - (contentSize.width + 30), pageSize.height - 120))!; + page: page, + bounds: Rect.fromLTWH( + 30, + 120, + pageSize.width - (contentSize.width + 30), + pageSize.height - 120, + ), + )!; } //Draws the grid @@ -149,45 +170,61 @@ 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))!; + result = + grid.draw( + 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, - result.bounds.bottom + 10, - quantityCellBounds!.width, - quantityCellBounds!.height)); - page.graphics.drawString(getTotalAmount(grid).toString(), - PdfStandardFont(PdfFontFamily.helvetica, 9, style: PdfFontStyle.bold), - bounds: Rect.fromLTWH( - totalPriceCellBounds!.left, - result.bounds.bottom + 10, - totalPriceCellBounds!.width, - totalPriceCellBounds!.height)); + page.graphics.drawString( + 'Grand Total', + PdfStandardFont(PdfFontFamily.helvetica, 9, style: PdfFontStyle.bold), + bounds: Rect.fromLTWH( + quantityCellBounds!.left, + result.bounds.bottom + 10, + quantityCellBounds!.width, + quantityCellBounds!.height, + ), + ); + page.graphics.drawString( + getTotalAmount(grid).toString(), + PdfStandardFont(PdfFontFamily.helvetica, 9, style: PdfFontStyle.bold), + bounds: Rect.fromLTWH( + totalPriceCellBounds!.left, + result.bounds.bottom + 10, + totalPriceCellBounds!.width, + totalPriceCellBounds!.height, + ), + ); } //Draw the invoice footer data. void drawFooter(PdfPage page, Size pageSize) { - final PdfPen linePen = - PdfPen(PdfColor(142, 170, 219, 255), dashStyle: PdfDashStyle.custom); + final PdfPen linePen = PdfPen( + PdfColor(142, 170, 219), + dashStyle: PdfDashStyle.custom, + ); linePen.dashPattern = [3, 3]; //Draw line - page.graphics.drawLine(linePen, Offset(0, pageSize.height - 100), - Offset(pageSize.width, pageSize.height - 100)); + page.graphics.drawLine( + linePen, + Offset(0, pageSize.height - 100), + Offset(pageSize.width, pageSize.height - 100), + ); const String footerContent = - // ignore: leading_newlines_in_multiline_strings - '''800 Interchange Blvd.\r\n\r\nSuite 2501, Austin, + // ignore: leading_newlines_in_multiline_strings + '''800 Interchange Blvd.\r\n\r\nSuite 2501, Austin, TX 78721\r\n\r\nAny Questions? support@adventure-works.com'''; //Added 30 as a margin for the layout page.graphics.drawString( - footerContent, PdfStandardFont(PdfFontFamily.helvetica, 9), - format: PdfStringFormat(alignment: PdfTextAlignment.right), - bounds: Rect.fromLTWH(pageSize.width - 30, pageSize.height - 70, 0, 0)); + footerContent, + PdfStandardFont(PdfFontFamily.helvetica, 9), + format: PdfStringFormat(alignment: PdfTextAlignment.right), + bounds: Rect.fromLTWH(pageSize.width - 30, pageSize.height - 70, 0, 0), + ); } //Create PDF grid and return @@ -219,8 +256,12 @@ class _CreatePdfState extends State { //Set gird columns width grid.columns[1].width = 200; for (int i = 0; i < headerRow.cells.count; i++) { - headerRow.cells[i].style.cellPadding = - PdfPaddings(bottom: 5, left: 5, right: 5, top: 5); + headerRow.cells[i].style.cellPadding = PdfPaddings( + bottom: 5, + left: 5, + right: 5, + top: 5, + ); } for (int i = 0; i < grid.rows.count; i++) { final PdfGridRow row = grid.rows[i]; @@ -229,16 +270,26 @@ class _CreatePdfState extends State { if (j == 0) { cell.stringFormat.alignment = PdfTextAlignment.center; } - cell.style.cellPadding = - PdfPaddings(bottom: 5, left: 5, right: 5, top: 5); + cell.style.cellPadding = PdfPaddings( + bottom: 5, + left: 5, + right: 5, + top: 5, + ); } } return grid; } //Create and row for the grid. - void addProducts(String productId, String productName, double price, - int quantity, double total, PdfGrid grid) { + void addProducts( + String productId, + String productName, + double price, + int quantity, + double total, + PdfGrid grid, + ) { final PdfGridRow row = grid.rows.add(); row.cells[0].value = productId; row.cells[1].value = productName; diff --git a/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart b/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart index df50f4f62..eb186a86d 100644 --- a/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart +++ b/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:open_file/open_file.dart' as open_file; import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; ///To save the pdf file in the device @@ -18,8 +19,9 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else { path = await PathProviderPlatform.instance.getApplicationSupportPath(); } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + final File file = File( + Platform.isWindows ? '$path\\$fileName' : '$path/$fileName', + ); await file.writeAsBytes(bytes, flush: true); if (Platform.isAndroid || Platform.isIOS) { //Launch the file (used open_file package) @@ -29,7 +31,8 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else if (Platform.isMacOS) { await Process.run('open', ['$path/$fileName'], runInShell: true); } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); + await Process.run('xdg-open', [ + '$path/$fileName', + ], runInShell: true); } } diff --git a/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart b/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart index 99d117c52..82d87615c 100644 --- a/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart +++ b/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart @@ -1,14 +1,22 @@ -///Dart imports +/// Dart imports import 'dart:async'; import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; -///To save the pdf file in the device +import 'package:web/web.dart'; + +// Function to save and launch a file for download in a web environment Future saveAndLaunchFile(List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); + final HTMLAnchorElement anchor = + document.createElement('a') as HTMLAnchorElement + ..href = 'data:application/octet-stream;base64,${base64Encode(bytes)}' + ..style.display = 'none' + ..download = fileName; + + // Insert the new element into the DOM + document.body!.appendChild(anchor); + + // Initiate the download + anchor.click(); + // Clean up the DOM by removing the anchor element + document.body!.removeChild(anchor); } diff --git a/packages/syncfusion_flutter_pdf/example/linux/CMakeLists.txt b/packages/syncfusion_flutter_pdf/example/linux/CMakeLists.txt index a558bc458..5d04ce358 100644 --- a/packages/syncfusion_flutter_pdf/example/linux/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdf/example/linux/CMakeLists.txt @@ -1,11 +1,19 @@ -cmake_minimum_required(VERSION 3.10) +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) -set(BINARY_NAME "example") -set(APPLICATION_ID "com.example.example") +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "pdf_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.pdf_example") +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. cmake_policy(SET CMP0063 NEW) +# Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. @@ -18,7 +26,7 @@ if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() -# Configure build options. +# Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) @@ -27,6 +35,10 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() # Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) @@ -34,27 +46,20 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") - # Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") -# Application build -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) -apply_standard_settings(${BINARY_NAME}) -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +# Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) + # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of @@ -64,6 +69,7 @@ set_target_properties(${BINARY_NAME} RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) + # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) @@ -94,11 +100,17 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR} install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -endif() +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. diff --git a/packages/syncfusion_flutter_pdf/example/linux/flutter/CMakeLists.txt b/packages/syncfusion_flutter_pdf/example/linux/flutter/CMakeLists.txt index 6dc970558..d5bd01648 100644 --- a/packages/syncfusion_flutter_pdf/example/linux/flutter/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdf/example/linux/flutter/CMakeLists.txt @@ -1,3 +1,4 @@ +# This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") @@ -24,8 +25,6 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) -pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) -pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") @@ -67,8 +66,6 @@ target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO - PkgConfig::BLKID - PkgConfig::LZMA ) add_dependencies(flutter flutter_assemble) diff --git a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc index e71a16d23..eb768d3a6 100644 --- a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); } diff --git a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugins.cmake index 51436ae8c..eb06039dc 100644 --- a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_file_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) @@ -13,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_pdf/example/macos/.gitignore b/packages/syncfusion_flutter_pdf/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/.gitignore +++ b/packages/syncfusion_flutter_pdf/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b60..4b81f9b2d 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Release.xcconfig index c2efd0b60..5caa9d157 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Release.xcconfig +++ b/packages/syncfusion_flutter_pdf/example/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdf/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_pdf/example/macos/Flutter/GeneratedPluginRegistrant.swift index 0d56f519c..4d6f3f9e6 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_flutter_pdf/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation -import path_provider_macos +import open_file_mac +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..a331b347a 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,25 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 2ED92DA3D9D3A3ED79AD7C75 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E986FC827C8E061EBFCEC12 /* Pods_Runner.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 3F152AB12F4BB1001C0554B7 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D380DCDE8813C5944FE430E /* Pods_RunnerTests.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +63,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* pdf_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = pdf_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,21 +79,47 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 58F59E5C0AEB6BEBB111AD0F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 5B3A2E269DB5970578E39075 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7E4201B2BCAB0B259C6B97AB /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 7E986FC827C8E061EBFCEC12 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 991FD2FD7E9716C1F3A6147D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9D380DCDE8813C5944FE430E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BEC548A05A7A0EB322E15DE3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + C0D3138784DDCDC5BBB475DD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3F152AB12F4BB1001C0554B7 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 2ED92DA3D9D3A3ED79AD7C75 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,15 +136,18 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + C23F74D46DF053D8BEF98D7F /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* pdf_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -145,9 +187,25 @@ path = Runner; sourceTree = ""; }; + C23F74D46DF053D8BEF98D7F /* Pods */ = { + isa = PBXGroup; + children = ( + C0D3138784DDCDC5BBB475DD /* Pods-Runner.debug.xcconfig */, + 991FD2FD7E9716C1F3A6147D /* Pods-Runner.release.xcconfig */, + 58F59E5C0AEB6BEBB111AD0F /* Pods-Runner.profile.xcconfig */, + BEC548A05A7A0EB322E15DE3 /* Pods-RunnerTests.debug.xcconfig */, + 5B3A2E269DB5970578E39075 /* Pods-RunnerTests.release.xcconfig */, + 7E4201B2BCAB0B259C6B97AB /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 7E986FC827C8E061EBFCEC12 /* Pods_Runner.framework */, + 9D380DCDE8813C5944FE430E /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -155,15 +213,36 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 362177E064935B4FDFD58D4F /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + CC49A7046848B15EF8D1C77B /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 7555243112C7AC1B43210A4E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -171,8 +250,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* pdf_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +263,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +297,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +333,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -270,9 +369,78 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 362177E064935B4FDFD58D4F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 7555243112C7AC1B43210A4E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + CC49A7046848B15EF8D1C77B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +454,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +479,57 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BEC548A05A7A0EB322E15DE3 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pdf_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pdf_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B3A2E269DB5970578E39075 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pdf_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pdf_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7E4201B2BCAB0B259C6B97AB /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pdf_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pdf_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +553,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +565,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +605,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +629,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +647,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +661,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +685,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +697,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +763,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +804,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..1ddaf3330 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_pdf/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16e..21a3cc14c 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_pdf/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_pdf/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_pdf/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_pdf/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..d01f00242 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = pdf_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.pdfExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_pdf/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_pdf/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_pdf/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_pdf/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_pdf/example/pubspec.yaml b/packages/syncfusion_flutter_pdf/example/pubspec.yaml index c3a9b6f3a..a9e4e8e02 100644 --- a/packages/syncfusion_flutter_pdf/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/example/pubspec.yaml @@ -3,15 +3,16 @@ 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" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - path_provider: ^2.0.1 - open_file: ^3.1.0 + path_provider: ^2.1.2 + open_file: ^3.3.2 syncfusion_flutter_pdf: path: ../ + web: ^1.1.0 # The following section is specific to Flutter. flutter: diff --git a/packages/syncfusion_flutter_pdf/example/web/index.html b/packages/syncfusion_flutter_pdf/example/web/index.html index 9058a56f7..5d1087923 100644 --- a/packages/syncfusion_flutter_pdf/example/web/index.html +++ b/packages/syncfusion_flutter_pdf/example/web/index.html @@ -8,91 +8,31 @@ The path provided below has to start and end with a slash "/" in order for it to work correctly. - Fore more details: + For more details: * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. --> - + - + - + - example + + + + pdf_example - - + diff --git a/packages/syncfusion_flutter_pdf/example/web/manifest.json b/packages/syncfusion_flutter_pdf/example/web/manifest.json index 8c012917d..aa2e1f5aa 100644 --- a/packages/syncfusion_flutter_pdf/example/web/manifest.json +++ b/packages/syncfusion_flutter_pdf/example/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "example", - "short_name": "example", + "name": "pdf_example", + "short_name": "pdf_example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/packages/syncfusion_flutter_pdf/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_pdf/example/windows/CMakeLists.txt index abf90408e..54ef6a137 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdf/example/windows/CMakeLists.txt @@ -1,13 +1,16 @@ -cmake_minimum_required(VERSION 3.15) -project(example LANGUAGES CXX) +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(pdf_example LANGUAGES CXX) -set(BINARY_NAME "example") +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "pdf_example") -cmake_policy(SET CMP0063 NEW) +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Configure build options. +# Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" @@ -20,7 +23,7 @@ else() "Debug" "Profile" "Release") endif() endif() - +# Define settings for the Profile build mode. set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") @@ -30,6 +33,10 @@ set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") @@ -38,14 +45,14 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") - # Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) -# Application build +# Application build; see runner/CMakeLists.txt. add_subdirectory("runner") + # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) @@ -80,6 +87,12 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/packages/syncfusion_flutter_pdf/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_pdf/example/windows/flutter/CMakeLists.txt index b02c5485c..903f4899d 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/flutter/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdf/example/windows/flutter/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.15) +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") @@ -9,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -91,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_pdf/example/windows/runner/CMakeLists.txt index 977e38b5d..394917c05 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/CMakeLists.txt @@ -1,18 +1,40 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" - "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_pdf/example/windows/runner/Runner.rc index 51812dcd4..a159e413f 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/Runner.rc +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif @@ -90,12 +90,12 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "A new Flutter project." "\0" + VALUE "FileDescription", "pdf_example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "example.exe" "\0" - VALUE "ProductName", "example" "\0" + VALUE "InternalName", "pdf_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "pdf_example.exe" "\0" + VALUE "ProductName", "pdf_example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.cpp index c42272304..955ee3038 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.cpp +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.cpp @@ -4,9 +4,8 @@ #include "flutter/generated_plugin_registrant.h" -FlutterWindow::FlutterWindow(RunLoop* run_loop, - const flutter::DartProject& project) - : run_loop_(run_loop), project_(project) {} +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} FlutterWindow::~FlutterWindow() {} @@ -26,14 +25,22 @@ bool FlutterWindow::OnCreate() { return false; } RegisterPlugins(flutter_controller_->engine()); - run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { - run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } @@ -44,7 +51,7 @@ LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opporutunity to handle window messages. + // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.h b/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.h index b663ddd50..6da0652f0 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.h +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/flutter_window.h @@ -6,16 +6,13 @@ #include -#include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: - // Creates a new FlutterWindow driven by the |run_loop|, hosting a - // Flutter view running |project|. - explicit FlutterWindow(RunLoop* run_loop, - const flutter::DartProject& project); + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: @@ -26,9 +23,6 @@ class FlutterWindow : public Win32Window { LPARAM const lparam) noexcept override; private: - // The run loop driving events for this window. - RunLoop* run_loop_; - // The project to run. flutter::DartProject project_; diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/main.cpp b/packages/syncfusion_flutter_pdf/example/windows/runner/main.cpp index b637809bd..a76006b34 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/main.cpp +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/main.cpp @@ -3,7 +3,6 @@ #include #include "flutter_window.h" -#include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, @@ -18,8 +17,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - RunLoop run_loop; - flutter::DartProject project(L"data"); std::vector command_line_arguments = @@ -27,15 +24,19 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - FlutterWindow window(&run_loop, project); + FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"example", origin, size)) { + if (!window.Create(L"pdf_example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); - run_loop.Run(); + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } ::CoUninitialize(); return EXIT_SUCCESS; diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_pdf/example/windows/runner/runner.exe.manifest index c977c4a42..153653e8d 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/runner.exe.manifest +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/runner.exe.manifest @@ -7,14 +7,8 @@ - + - - - - - - diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_pdf/example/windows/runner/utils.cpp index d19bdbbcc..3a0b46511 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/utils.cpp +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/utils.cpp @@ -45,18 +45,19 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } - int target_length = ::WideCharToMultiByte( + unsigned int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr); - if (target_length == 0) { - return std::string(); - } + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, utf8_string.data(), - target_length, nullptr, nullptr); + input_length, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.cpp index c10f08dc7..60608d0fe 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.cpp +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.cpp @@ -1,13 +1,31 @@ #include "win32_window.h" +#include #include #include "resource.h" namespace { +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; @@ -31,8 +49,8 @@ void EnableFullDpiSupportIfAvailable(HWND hwnd) { GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); } + FreeLibrary(user32_module); } } // namespace @@ -42,7 +60,7 @@ class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; - // Returns the singleton registar instance. + // Returns the singleton registrar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); @@ -102,9 +120,9 @@ Win32Window::~Win32Window() { Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size) { +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { Destroy(); const wchar_t* window_class = @@ -117,7 +135,7 @@ bool Win32Window::CreateAndShow(const std::wstring& title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); @@ -126,9 +144,15 @@ bool Win32Window::CreateAndShow(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, @@ -188,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -243,3 +271,18 @@ bool Win32Window::OnCreate() { void Win32Window::OnDestroy() { // No-op; provided for subclasses. } + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.h index 17ba43112..e901dde68 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.h +++ b/packages/syncfusion_flutter_pdf/example/windows/runner/win32_window.h @@ -28,15 +28,16 @@ class Win32Window { Win32Window(); virtual ~Win32Window(); - // Creates and shows a win32 window with |title| and position and size using + // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size); + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); // Release OS resources associated with window. void Destroy(); @@ -76,7 +77,7 @@ class Win32Window { // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by + // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, @@ -86,6 +87,9 @@ class Win32Window { // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + bool quit_on_close_ = false; // window handle for top level window. diff --git a/packages/syncfusion_flutter_pdf/lib/pdf.dart b/packages/syncfusion_flutter_pdf/lib/pdf.dart index eb5cdb8cd..baaa4af70 100644 --- a/packages/syncfusion_flutter_pdf/lib/pdf.dart +++ b/packages/syncfusion_flutter_pdf/lib/pdf.dart @@ -21,7 +21,12 @@ export 'src/pdf/implementation/annotations/enum.dart' PdfLineEndingStyle, PdfSubmitFormFlags, SubmitDataFormat, - HttpMethod; + HttpMethod, + PdfTextMarkupAnnotationType, + PdfPopupIcon, + PdfAnnotationFlags, + PdfAnnotationDataFormat, + PdfAnnotationExportType; export 'src/pdf/implementation/annotations/pdf_action_annotation.dart' show PdfLinkAnnotation, PdfActionLinkAnnotation, PdfActionAnnotation; export 'src/pdf/implementation/annotations/pdf_annotation.dart' @@ -40,8 +45,12 @@ export 'src/pdf/implementation/annotations/pdf_line_annotation.dart' show PdfLineAnnotation; export 'src/pdf/implementation/annotations/pdf_polygon_annotation.dart' show PdfPolygonAnnotation; +export 'src/pdf/implementation/annotations/pdf_popup_annotation.dart' + show PdfPopupAnnotation; export 'src/pdf/implementation/annotations/pdf_rectangle_annotation.dart' show PdfRectangleAnnotation; +export 'src/pdf/implementation/annotations/pdf_text_markup_annotation.dart' + show PdfTextMarkupAnnotation; export 'src/pdf/implementation/annotations/pdf_text_web_link.dart' show PdfTextWebLink; export 'src/pdf/implementation/annotations/pdf_uri_annotation.dart' @@ -221,7 +230,9 @@ export 'src/pdf/implementation/security/digital_signature/pdf_certificate.dart' show PdfCertificate; export 'src/pdf/implementation/security/digital_signature/pdf_external_signer.dart'; export 'src/pdf/implementation/security/digital_signature/pdf_signature.dart' - show PdfSignature; + show PdfSignature, RevocationType; +export 'src/pdf/implementation/security/digital_signature/time_stamp_server/time_stamp_server.dart' + show TimestampServer; export 'src/pdf/implementation/security/enum.dart'; export 'src/pdf/implementation/security/pdf_security.dart' show PdfPermissions, PdfSecurity; 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 0944b52f7..daa31aa06 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 @@ -19,8 +19,9 @@ abstract class PdfAction implements IPdfWrapper { set next(PdfAction? value) { if (value != null && _helper.action != value) { _helper.action = value; - _helper.dictionary.setArray(PdfDictionaryProperties.next, - [PdfReferenceHolder(_helper.action)]); + _helper.dictionary.setArray(PdfDictionaryProperties.next, [ + PdfReferenceHolder(_helper.action), + ]); } } } @@ -43,8 +44,10 @@ class PdfActionHelper { /// internal method void initialize() { - dictionary.setProperty(PdfName(PdfDictionaryProperties.type), - PdfName(PdfDictionaryProperties.action)); + dictionary.setProperty( + PdfName(PdfDictionaryProperties.type), + PdfName(PdfDictionaryProperties.action), + ); element = dictionary; } 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 index 08abd1d9d..0280d03b0 100644 --- 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 @@ -7,16 +7,23 @@ import 'pdf_action.dart'; 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}) { + PdfAnnotationActions({ + PdfAction? mouseEnter, + PdfAction? mouseLeave, + PdfAction? mouseUp, + PdfAction? mouseDown, + PdfAction? gotFocus, + PdfAction? lostFocus, + }) { _helper = PdfAnnotationActionsHelper(); _initValues( - mouseEnter, mouseLeave, mouseUp, mouseDown, gotFocus, lostFocus); + mouseEnter, + mouseLeave, + mouseUp, + mouseDown, + gotFocus, + lostFocus, + ); } PdfAnnotationActions._loaded(PdfDictionary? dictionary) { @@ -93,8 +100,14 @@ class PdfAnnotationActions implements IPdfWrapper { } // Implementation - void _initValues(PdfAction? mEnter, PdfAction? mLeave, PdfAction? mUp, - PdfAction? mDown, PdfAction? gotF, PdfAction? lostF) { + void _initValues( + PdfAction? mEnter, + PdfAction? mLeave, + PdfAction? mUp, + PdfAction? mDown, + PdfAction? gotF, + PdfAction? lostF, + ) { if (mEnter != null) { mouseEnter = mEnter; } @@ -137,7 +150,8 @@ class PdfAnnotationActionsHelper { /// internal method static PdfAnnotationActionsHelper getHelper( - PdfAnnotationActions annotationActions) { + PdfAnnotationActions annotationActions, + ) { return annotationActions._helper; } 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 index 28557ccfb..e29276a38 100644 --- 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 @@ -11,11 +11,13 @@ 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}) { + PdfFieldActions( + PdfAnnotationActions annotationActions, { + PdfJavaScriptAction? keyPressed, + PdfJavaScriptAction? format, + PdfJavaScriptAction? validate, + PdfJavaScriptAction? calculate, + }) { _helper.annotationActions = annotationActions; _initValues(keyPressed, format, validate, calculate); } @@ -198,10 +200,13 @@ class PdfJavaScriptAction extends PdfAction { PdfJavaScriptAction(String javaScript) : super() { _initValue(javaScript); PdfActionHelper.getHelper(this).dictionary.setProperty( - PdfDictionaryProperties.s, PdfName(PdfDictionaryProperties.javaScript)); - PdfActionHelper.getHelper(this) - .dictionary - .setProperty(PdfDictionaryProperties.js, PdfString(_javaScript)); + PdfDictionaryProperties.s, + PdfName(PdfDictionaryProperties.javaScript), + ); + PdfActionHelper.getHelper(this).dictionary.setProperty( + PdfDictionaryProperties.js, + PdfString(_javaScript), + ); } //Fields @@ -214,12 +219,13 @@ class PdfJavaScriptAction extends PdfAction { set javaScript(String value) { if (_javaScript != value) { _javaScript = value; - PdfActionHelper.getHelper(this) - .dictionary - .setString(PdfDictionaryProperties.js, _javaScript); + PdfActionHelper.getHelper( + this, + ).dictionary.setString(PdfDictionaryProperties.js, _javaScript); } } + // ignore: use_setters_to_change_properties void _initValue(String js) { javaScript = js; } 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 index f037f451c..7552d0a49 100644 --- 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 @@ -14,40 +14,44 @@ 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._() { + 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._() { final PdfActionHelper helper = PdfActionHelper.getHelper(this); helper.dictionary.beginSave = _dictionaryBeginSave; helper.dictionary.setProperty( - PdfDictionaryProperties.s, PdfName(PdfDictionaryProperties.submitForm)); + PdfDictionaryProperties.s, + PdfName(PdfDictionaryProperties.submitForm), + ); if (url.isEmpty) { ArgumentError.value("The URL can't be an empty string."); } _url = url; helper.dictionary.setProperty(PdfDictionaryProperties.f, PdfString(_url)); _initValues( - httpMethod = HttpMethod.post, - dataFormat, - canonicalDateTimeFormat, - submitCoordinates, - includeNoValueFields, - includeIncrementalUpdates, - includeAnnotations, - excludeNonUserAnnotations, - embedForm, - include, - fields); + httpMethod = HttpMethod.post, + dataFormat, + canonicalDateTimeFormat, + submitCoordinates, + includeNoValueFields, + includeIncrementalUpdates, + includeAnnotations, + excludeNonUserAnnotations, + embedForm, + include, + fields, + ); } //Fields @@ -216,21 +220,24 @@ class PdfSubmitAction extends PdfFormAction { void _dictionaryBeginSave(Object sender, SavePdfPrimitiveArgs? ars) { PdfActionHelper.getHelper(this).dictionary.setProperty( - PdfDictionaryProperties.flags, PdfNumber(_getFlagValue(_flags))); + PdfDictionaryProperties.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, + 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; @@ -321,9 +328,9 @@ class PdfFormAction extends PdfAction { PdfFormFieldCollection get fields { if (_fields == null) { _fields = PdfFormFieldCollectionHelper.getCollection(); - PdfActionHelper.getHelper(this) - .dictionary - .setProperty(PdfDictionaryProperties.fields, _fields); + PdfActionHelper.getHelper( + this, + ).dictionary.setProperty(PdfDictionaryProperties.fields, _fields); } PdfFormFieldCollectionHelper.getHelper(_fields!).isAction = true; return _fields!; @@ -337,7 +344,9 @@ class PdfResetAction extends PdfFormAction { /// Initializes a new instance of the [PdfResetAction] class. PdfResetAction({bool? include, List? fields}) : super._() { PdfActionHelper.getHelper(this).dictionary.setProperty( - PdfDictionaryProperties.s, PdfName(PdfDictionaryProperties.resetForm)); + PdfDictionaryProperties.s, + PdfName(PdfDictionaryProperties.resetForm), + ); _initValues(include, fields); } @@ -346,9 +355,10 @@ class PdfResetAction extends PdfFormAction { set include(bool value) { if (super.include != value) { super.include = value; - PdfActionHelper.getHelper(this) - .dictionary - .setNumber(PdfDictionaryProperties.flags, super.include ? 0 : 1); + PdfActionHelper.getHelper(this).dictionary.setNumber( + PdfDictionaryProperties.flags, + super.include ? 0 : 1, + ); } } 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 dfc7889c0..f71dc1c50 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 @@ -13,8 +13,9 @@ class PdfUriAction extends PdfAction { this.uri = uri; } PdfActionHelper.getHelper(this).dictionary.setProperty( - PdfName(PdfDictionaryProperties.s), - PdfName(PdfDictionaryProperties.uri)); + PdfName(PdfDictionaryProperties.s), + PdfName(PdfDictionaryProperties.uri), + ); } // fields @@ -27,8 +28,8 @@ class PdfUriAction extends PdfAction { /// Sets the unique resource identifier. set uri(String value) { _uri = value; - PdfActionHelper.getHelper(this) - .dictionary - .setString(PdfDictionaryProperties.uri, _uri); + PdfActionHelper.getHelper( + this, + ).dictionary.setString(PdfDictionaryProperties.uri, _uri); } } 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 index 682c45a65..73d402c69 100644 --- 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 @@ -21,8 +21,10 @@ class PdfExtendedAppearance implements IPdfWrapper { PdfAppearanceState get normal { if (_normal == null) { _normal = PdfAppearanceState(); - _dictionary! - .setProperty(PdfDictionaryProperties.n, PdfReferenceHolder(_normal)); + _dictionary!.setProperty( + PdfDictionaryProperties.n, + PdfReferenceHolder(_normal), + ); } return _normal!; } @@ -32,7 +34,9 @@ class PdfExtendedAppearance implements IPdfWrapper { if (_mouseHover == null) { _mouseHover = PdfAppearanceState(); _dictionary!.setProperty( - PdfDictionaryProperties.r, PdfReferenceHolder(_mouseHover)); + PdfDictionaryProperties.r, + PdfReferenceHolder(_mouseHover), + ); } return _mouseHover!; } @@ -41,8 +45,10 @@ class PdfExtendedAppearance implements IPdfWrapper { PdfAppearanceState get pressed { if (_pressed == null) { _pressed = PdfAppearanceState(); - _dictionary! - .setProperty(PdfDictionaryProperties.d, PdfReferenceHolder(_pressed)); + _dictionary!.setProperty( + PdfDictionaryProperties.d, + PdfReferenceHolder(_pressed), + ); } return _pressed!; } 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 60b08a967..12ade3fce 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 @@ -10,7 +10,7 @@ enum PdfHighlightMode { outline, /// Pushed highlighting. - push + push, } /// Specifies the file path type. @@ -19,7 +19,7 @@ enum PdfFilePathType { relative, /// Specifies the location, including the domain name. - absolute + absolute, } /// Specifies the annotation types. @@ -48,8 +48,65 @@ enum PdfAnnotationTypes { /// WidgetAnnotation type widgetAnnotation, + /// Highlight type annotation. + highlight, + + /// Underline type annotation. + underline, + + /// StrikeOut type annotation. + strikeOut, + + /// Squiggly type annotation. + squiggly, + + /// PopupAnnotation type. + popupAnnotation, + /// No annotation. - noAnnotation + noAnnotation, +} + +/// Specifies the annotation export types. +enum PdfAnnotationExportType { + /// Line annotation type. + lineAnnotation, + + /// Circle annotation type. + circleAnnotation, + + /// Rectangle annotation type. + rectangleAnnotation, + + /// Polygon annotation type. + polygonAnnotation, + + /// Highlight annotation type. + highlightAnnotation, + + /// Underline annotation type. + underlineAnnotation, + + /// StrikeOut annotation type. + strikeOutAnnotation, + + /// Squiggly annotation type. + squigglyAnnotation, + + /// Popup annotation type. + popupAnnotation, +} + +/// Specifies the format of Export or Import data. +enum PdfAnnotationDataFormat { + /// Specifies FDF file format. + fdf, + + /// Specifies XFDF file format. + xfdf, + + /// Specifies JSON file format. + json, } /// Specifies the available styles for a field border. @@ -72,7 +129,7 @@ enum PdfBorderStyle { underline, /// A dotted rectangle surrounding the annotation. - dot + dot, } /// Gets or sets the line intent of the annotation. @@ -123,7 +180,7 @@ enum PdfLineEndingStyle { square, /// Indicates Slash - slash + slash, } /// Specifies the available data formats for submitting the form data. @@ -140,7 +197,6 @@ enum PdfSubmitFormFlags { /// 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, @@ -208,7 +264,7 @@ enum PdfSubmitFormFlags { /// 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 + embedForm, } /// Specifies the enumeration of submit data formats. @@ -223,7 +279,7 @@ enum SubmitDataFormat { fdf, /// Data should be transmitted as XML Forms Data Format. - xfdf + xfdf, } /// Specifies Http request method. @@ -232,5 +288,77 @@ enum HttpMethod { getHttp, /// Data submitted using Http Post method. - post + post, +} + +/// Specifies the Style of the Text Markup Annotation +enum PdfTextMarkupAnnotationType { + /// Represents highlight text markup annotation type. + highlight, + + /// Represents underline text markup annotation type. + underline, + + /// Represents squiggly text markup annotation type. + squiggly, + + /// Represents strikethrough text markup annotation type. + strikethrough, +} + +/// Specifies the enumeration of popup annotation icons. +enum PdfPopupIcon { + /// Indicates note popup annotation. + note, + + /// Indicates comment popup annotation. + comment, + + /// Indicates help popup annotation. + help, + + /// Indicates insert popup annotation. + insert, + + /// Indicates key popup annotation. + key, + + /// Indicates new paragraph popup annotation. + newParagraph, + + /// Indicates paragraph popup annotation. + paragraph, +} + +/// Specifies the enumeration of the annotation flags. +enum PdfAnnotationFlags { + /// Default value. + defaultFlag, + + /// Represents invisible annotation flag's key. + invisible, + + /// Represents hidden annotation flag's key. + hidden, + + /// Represents print annotation flag's key. + print, + + /// Represents annotation flag's key with no zooming. + noZoom, + + /// Represents annotation flag's key with no rotation. + noRotate, + + /// Represents annotation flag's key with no view. + noView, + + /// Represents read only annotation flag's key. + readOnly, + + /// Represents locked annotation flag's key. + locked, + + /// Annotation flag's key with no toggle view. + toggleNoView, } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart new file mode 100644 index 000000000..fa06b04a1 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_document.dart @@ -0,0 +1,359 @@ +import 'dart:convert'; + +import '../../interfaces/pdf_interface.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/pdf_page.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; + +/// Internal class +class FdfDocument { + /// Internal Consturctor + FdfDocument(this.dictionary, this.page); + + /// Internal field + late PdfDictionary dictionary; + + /// Internal field + late PdfPage page; + + /// Internal field + String _annotationID = ''; + + /// Internal method + Map exportAnnotations( + int currentID, + List annotID, + int pageIndex, + bool hasAppearance, + ) { + const String startObject = + '${PdfOperators.whiteSpace}0${PdfOperators.whiteSpace}${PdfOperators.obj}${PdfOperators.newLine}'; + const String endObject = + PdfOperators.newLine + PdfOperators.endobj + PdfOperators.newLine; + PdfDictionary dictionary = this.dictionary; + _annotationID = currentID.toString(); + final List exportData = []; + exportData.addAll(utf8.encode('$currentID$startObject<<')); + final Map subDictionaries = {}; + final List streamReferences = []; + annotID.add(_annotationID); + dictionary.items![PdfName('Page')] = PdfNumber(pageIndex); + Map exportDataDictionary = _getEntriesInDictionary( + subDictionaries, + streamReferences, + currentID, + dictionary, + hasAppearance, + ); + exportData.addAll(exportDataDictionary['exportData'] as List); + currentID = exportDataDictionary['currentID'] as int; + dictionary.remove('Page'); + exportData.addAll(utf8.encode('>>$endObject')); + while (subDictionaries.isNotEmpty) { + final List keys = subDictionaries.keys.toList(); + for (final int key in keys) { + if (subDictionaries[key] is PdfDictionary) { + dictionary = subDictionaries[key]! as PdfDictionary; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final IPdfPrimitive? name = + dictionary[PdfDictionaryProperties.type]; + if (name != null && + name is PdfName && + name.name == PdfDictionaryProperties.annot) { + annotID.add(key.toString()); + dictionary.items![PdfName('Page')] = PdfNumber(pageIndex); + } + } + exportData.addAll(utf8.encode('$key$startObject<<')); + exportDataDictionary = _getEntriesInDictionary( + subDictionaries, + streamReferences, + currentID, + dictionary, + hasAppearance, + ); + exportData.addAll(exportDataDictionary['exportData'] as List); + currentID = exportDataDictionary['currentID'] as int; + if (dictionary.containsKey('Page')) { + dictionary.remove('Page'); + } + exportData.addAll(utf8.encode('>>')); + if (streamReferences.contains(key)) { + exportData.addAll(_appendStream(subDictionaries[key]!)); + } + exportData.addAll(utf8.encode(endObject)); + } else if (subDictionaries[key] is PdfName) { + final PdfName name = subDictionaries[key]! as PdfName; + exportData.addAll(utf8.encode('$key$startObject$name$endObject')); + } else if (subDictionaries[key] is PdfArray) { + final PdfArray array = subDictionaries[key]! as PdfArray; + exportData.addAll(utf8.encode('$key$startObject')); + final Map result = _appendArrayElements( + array, + currentID, + hasAppearance, + subDictionaries, + streamReferences, + ); + exportData.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + exportData.addAll(utf8.encode(endObject)); + } else if (subDictionaries[key] is PdfBoolean) { + final PdfBoolean boolean = subDictionaries[key]! as PdfBoolean; + exportData.addAll( + utf8.encode( + '$key$startObject${boolean.value! ? 'true' : 'false'}$endObject', + ), + ); + } else if (subDictionaries[key] is PdfString) { + final PdfString data = subDictionaries[key]! as PdfString; + if (data.value != null) { + exportData.addAll( + utf8.encode( + '$key$startObject(${_getFormattedStringFDF(data.value!)})$endObject', + ), + ); + } + } + subDictionaries.remove(key); + } + } + currentID++; + return {'exportData': exportData, 'currentID': currentID}; + } + + /// Internal method + List _appendStream(IPdfPrimitive stream) { + final List streamData = []; + if (stream is PdfStream && + stream.dataStream != null && + stream.dataStream!.isNotEmpty) { + streamData.addAll(utf8.encode('stream${PdfOperators.newLine}')); + streamData.addAll(stream.dataStream!); + streamData.addAll(utf8.encode('${PdfOperators.newLine}endstream')); + } + return streamData; + } + + Map _getEntriesInDictionary( + Map dictionaries, + List streamReferences, + int currentID, + PdfDictionary dictionary, + bool hasAppearance, + ) { + final List annotationData = []; + bool isStream = false; + final List keys = dictionary.items!.keys.toList(); + for (final PdfName? key in keys) { + if (!hasAppearance && key!.name == PdfDictionaryProperties.ap) { + continue; + } + if (key!.name != PdfDictionaryProperties.p) { + annotationData.addAll(utf8.encode(key.toString())); + } + if (key.name == 'Sound' || + key.name == PdfDictionaryProperties.f || + hasAppearance) { + isStream = true; + } + final IPdfPrimitive? primitive = dictionary[key]; + if (primitive is PdfString) { + if (primitive.value != null) { + annotationData.addAll( + utf8.encode('(${_getFormattedStringFDF(primitive.value!)})'), + ); + } + } else if (primitive is PdfName) { + annotationData.addAll(utf8.encode(primitive.toString())); + } else if (primitive is PdfArray) { + final Map result = _appendArrayElements( + primitive, + currentID, + isStream, + dictionaries, + streamReferences, + ); + annotationData.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } else if (primitive is PdfNumber) { + annotationData.addAll(utf8.encode(' ${primitive.value!}')); + } else if (primitive is PdfBoolean) { + annotationData.addAll(utf8.encode(' ${primitive.value!}')); + } else if (primitive is PdfDictionary) { + annotationData.addAll(utf8.encode('<<')); + final Map data = _getEntriesInDictionary( + dictionaries, + streamReferences, + currentID, + primitive, + hasAppearance, + ); + annotationData.addAll(data['exportData'] as List); + currentID = data['currentID'] as int; + annotationData.addAll(utf8.encode('>>')); + } else if (primitive is PdfReferenceHolder) { + if (PdfPageHelper.getHelper(page).document != null) { + final int pageNumber = PdfPageHelper.getHelper( + page, + ).document!.pages.indexOf(page); + if (key.name == PdfDictionaryProperties.parent) { + annotationData.addAll(utf8.encode(' $_annotationID 0 R')); + annotationData.addAll(utf8.encode('/Page $pageNumber')); + } else if (key.name == PdfDictionaryProperties.irt) { + if (primitive.object != null && primitive.object is PdfDictionary) { + final IPdfPrimitive? inReplyTo = primitive.object; + if (inReplyTo != null && + inReplyTo is PdfDictionary && + inReplyTo.containsKey('NM')) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + inReplyTo['NM'], + ); + if (name != null && name is PdfString) { + if (name.value != null) { + annotationData.addAll( + utf8.encode('(${_getFormattedStringFDF(name.value!)})'), + ); + } + } + } + } + } else if (key.name != PdfDictionaryProperties.p) { + currentID++; + annotationData.addAll(utf8.encode(' $currentID 0 R')); + if (isStream) { + streamReferences.add(currentID); + } + if (primitive.object != null) { + dictionaries[currentID] = primitive.object!; + } + } + } + } + isStream = false; + } + return { + 'exportData': annotationData, + 'currentID': currentID, + }; + } + + String _getFormattedStringFDF(String value) { + String result = ''; + for (int i = 0; i < value.length; i++) { + final int c = value.codeUnitAt(i); + if (c == 40 || c == 41) { + result += r'\'; + } + if (c == 13 || c == 10) { + if (c == 13) { + result += r'\r'; + } + if (c == 10) { + result += r'\n'; + } + continue; + } + result += String.fromCharCode(c); + } + return result; + } + + Map _appendArrayElements( + PdfArray array, + int currentID, + bool isStream, + Map dictionaries, + List streamReferences, + ) { + final List arrayData = []; + arrayData.addAll(utf8.encode('[')); + if (array.elements.isNotEmpty) { + final int count = array.elements.length; + for (int i = 0; i < count; i++) { + final IPdfPrimitive? element = array.elements[i]; + if (i != 0 && + element != null && + (element is PdfNumber || + element is PdfReferenceHolder || + element is PdfBoolean)) { + arrayData.addAll(utf8.encode(' ')); + } + final Map result = _appendElement( + element!, + currentID, + isStream, + dictionaries, + streamReferences, + ); + arrayData.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } + } + arrayData.addAll(utf8.encode(']')); + return {'exportData': arrayData, 'currentID': currentID}; + } + + Map _appendElement( + IPdfPrimitive element, + int currentID, + bool isStream, + Map dictionaries, + List streamReferences, + ) { + final List exportData = []; + if (element is PdfNumber) { + exportData.addAll(utf8.encode(element.value!.toString())); + } else if (element is PdfName) { + exportData.addAll(utf8.encode(element.toString())); + } else if (element is PdfString) { + if (element.value != null) { + exportData.addAll( + utf8.encode('(${_getFormattedStringFDF(element.value!)})'), + ); + } + } else if (element is PdfBoolean) { + exportData.addAll(utf8.encode(element.value!.toString())); + } else if (element is PdfReferenceHolder) { + currentID++; + if (isStream) { + streamReferences.add(currentID); + } + if (element.object != null) { + dictionaries[currentID] = element.object!; + } + exportData.addAll(utf8.encode('$currentID 0 R')); + } else if (element is PdfArray) { + final Map result = _appendArrayElements( + element, + currentID, + isStream, + dictionaries, + streamReferences, + ); + currentID = result['currentID'] as int; + exportData.addAll(result['exportData'] as List); + } else if (element is PdfDictionary) { + exportData.addAll(utf8.encode('<<')); + final Map data = _getEntriesInDictionary( + dictionaries, + streamReferences, + currentID, + element, + isStream, + ); + exportData.addAll(data['exportData'] as List); + currentID = data['currentID'] as int; + exportData.addAll(utf8.encode('>>')); + } + return {'exportData': exportData, 'currentID': currentID}; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart new file mode 100644 index 000000000..293982667 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/fdf_parser.dart @@ -0,0 +1,402 @@ +import 'dart:convert'; + +import '../../interfaces/pdf_interface.dart'; +import '../io/enums.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../io/pdf_parser.dart'; +import '../io/pdf_reader.dart'; +import '../pages/pdf_page.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'json_parser.dart'; + +/// The class provides methods and properties to handle the loaded annotations from the existing PDF document for Fdf export and import. +class FdfParser { + //Constructor. + /// Initializes a new instance of the [FdfParser] class with the specified data. + FdfParser(List data) { + _data = data; + _reader = PdfReader(data); + _fdfObjects = {}; + } + + //Fields + late List _data; + PdfReader? _reader; + late Map _fdfObjects; + PdfParser? _parser; + Map? _annotationObjects; + Map? _groupObjects; + + //Properties + /// Gets the grouped objects. + Map get groupedObjects { + _groupObjects ??= {}; + return _groupObjects!; + } + + //Implementations. + /// Parse the annotation data. + void parseAnnotationData() { + final PdfCrossTable table = PdfCrossTable.fromFdf(_data); + if (table.crossTable != null) { + _parser = table.crossTable!.parser; + final int startPos = _checkFdf(); + if (startPos != -1) { + final int? endPos = _reader!.seekEnd(); + _parser!.setOffset(startPos); + _parser!.advance(); + while (_parser!.next != null && _parser!.next! == PdfTokenType.number) { + _parseObject(_fdfObjects); + } + if (_parser!.lexer != null && + _parser!.lexer!.text == PdfOperators.trailer) { + final IPdfPrimitive trailer = _parser!.trailer(); + _fdfObjects[PdfOperators.trailer] = trailer; + } + _parser!.advance(); + while (_parser!.lexer != null && + _parser!.lexer!.position != endPos && + _parser!.next == PdfTokenType.number) { + _parseObject(_fdfObjects); + } + } + } + } + + /// Imports the annotation data from FDF stream + void importAnnotations(PdfDocument document) { + _annotationObjects = _getAnnotationObjects(); + final bool hasAnnotations = _groupAnnotations(); + if (hasAnnotations && _annotationObjects != null) { + for (final PdfReferenceHolder holder in _annotationObjects!.values) { + final IPdfPrimitive? dictionary = holder.object; + if (dictionary != null && + dictionary is PdfDictionary && + dictionary.items != null && + dictionary.items!.isNotEmpty) { + _parseDictionary(dictionary); + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + final IPdfPrimitive? inReplyTo = + dictionary[PdfDictionaryProperties.irt]; + if (inReplyTo != null && + inReplyTo is PdfString && + !isNullOrEmpty(inReplyTo.value)) { + if (groupedObjects.containsKey(inReplyTo.value)) { + final String referenceString = groupedObjects[inReplyTo.value]!; + dictionary[PdfDictionaryProperties.irt] = + _annotationObjects![referenceString]; + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? content = + dictionary[PdfDictionaryProperties.contents]; + if (content != null && + content is PdfString && + !isNullOrEmpty(content.value)) { + String contentText = content.value!; + if (RegExp(r'[\u0085-\u00FF]').hasMatch(contentText)) { + final List bytes = content.pdfEncode(document); + contentText = utf8.decode(bytes); + if (contentText.startsWith('(') && contentText.endsWith(')')) { + while (contentText.startsWith('(')) { + contentText = contentText.substring(1); + } + while (contentText.endsWith(')')) { + contentText = contentText.substring( + 0, + contentText.length - 1, + ); + } + } + dictionary[PdfDictionaryProperties.contents] = PdfString( + contentText, + ); + if (dictionary.containsKey('RC')) { + dictionary.setString( + 'RC', + '

$contentText

', + ); + } + dictionary.modify(); + } + } + } + if (dictionary.containsKey('Page')) { + final IPdfPrimitive? pageNumber = dictionary['Page']; + if (pageNumber != null && pageNumber is PdfNumber) { + final int pageIndex = pageNumber.value!.toInt(); + if (pageIndex < document.pages.count) { + final PdfPage loadedPage = document.pages[pageIndex]; + PdfPageHelper.getHelper(loadedPage).importAnnotation = true; + final PdfDictionary? pageDictionary = + PdfPageHelper.getHelper(loadedPage).dictionary; + if (pageDictionary != null) { + if (!pageDictionary.containsKey( + PdfDictionaryProperties.annots, + )) { + pageDictionary[PdfDictionaryProperties.annots] = PdfArray(); + } + final IPdfPrimitive? annots = PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.annots], + ); + if (annots != null && annots is PdfArray) { + annots.elements.add(holder); + annots.changed = true; + pageDictionary.modify(); + } + } + } + dictionary.remove('Page'); + } + } + } + } + } + } + + /// internal method + void dispose() { + _fdfObjects.clear(); + _reader = null; + _reader = null; + _parser = null; + if (_annotationObjects != null) { + _annotationObjects!.clear(); + } + _annotationObjects = null; + } + + Map _getAnnotationObjects() { + final Map mappedObjects = + {}; + final Map objects = _fdfObjects; + if (objects.isNotEmpty && objects.containsKey(PdfOperators.trailer)) { + final IPdfPrimitive trailer = objects[PdfOperators.trailer]!; + if (trailer is PdfDictionary && + trailer.containsKey(PdfDictionaryProperties.root)) { + final IPdfPrimitive? holder = trailer[PdfDictionaryProperties.root]; + if (holder != null && holder is PdfReferenceHolder) { + PdfReference? reference = holder.reference; + if (reference != null) { + final String rootKey = '${reference.objNum} ${reference.genNum}'; + if (objects.containsKey(rootKey)) { + final IPdfPrimitive root = objects[rootKey]!; + if (root is PdfDictionary && root.containsKey('FDF')) { + final IPdfPrimitive? fdf = root['FDF']; + if (fdf != null && + fdf is PdfDictionary && + fdf.containsKey(PdfDictionaryProperties.annots)) { + final IPdfPrimitive? annots = + fdf[PdfDictionaryProperties.annots]; + if (annots != null && + annots is PdfArray && + annots.count != 0) { + for (final IPdfPrimitive? holder in annots.elements) { + if (holder != null && holder is PdfReferenceHolder) { + reference = holder.reference; + if (reference != null) { + final String key = + '${reference.objNum} ${reference.genNum}'; + if (objects.containsKey(key)) { + mappedObjects[key] = PdfReferenceHolder( + objects[key], + ); + objects.remove(key); + } + } + } + } + } + } + objects.remove(rootKey); + } + } + } + } + } + objects.remove(PdfOperators.trailer); + } + return mappedObjects; + } + + bool _groupAnnotations() { + if (_annotationObjects != null && _annotationObjects!.isNotEmpty) { + _annotationObjects!.forEach((String key, PdfReferenceHolder value) { + final IPdfPrimitive? dictionary = value.object; + if (dictionary != null && + dictionary is PdfDictionary && + dictionary.containsKey('NM')) { + final IPdfPrimitive? name = dictionary['NM']; + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + groupedObjects[name.value!] = key; + } + } + }); + return true; + } + return false; + } + + int _checkFdf() { + const int headerLength = 8; + final String header = utf8.decode(_data, allowMalformed: true); + final int index = header.indexOf('%FDF-'); + if (index < 0) { + throw ArgumentError( + 'The source is not a valid FDF file because it does not start with"%FDF-"', + ); + } + return index + headerLength; + } + + void _parseObject(Map objects) { + final FdfObject? obj = _parser!.parseObject(); + if (obj != null && obj.objectNumber > 0 && obj.generationNumber >= 0) { + final String key = '${obj.objectNumber} ${obj.generationNumber}'; + objects[key] = obj.object; + } + _parser!.advance(); + } + + void _parseDictionary(PdfDictionary dictionary, [PdfName? key]) { + if (key != null) { + final IPdfPrimitive? primitive = dictionary[key]; + if (primitive != null) { + if (primitive is PdfDictionary) { + _parseDictionary(primitive); + } else if (primitive is PdfArray) { + _parseArray(primitive); + } else if (primitive is PdfReferenceHolder) { + final PdfReference? reference = primitive.reference; + if (reference != null) { + final String objectKey = '${reference.objNum} ${reference.genNum}'; + if (_annotationObjects != null && + _annotationObjects!.containsKey(objectKey)) { + dictionary[key] = _annotationObjects![objectKey]; + dictionary.modify(); + } else if (_fdfObjects.containsKey(objectKey)) { + final Map objects = _fdfObjects; + if (objects[objectKey] is PdfReferenceHolder) { + dictionary[key] = objects[objectKey]; + dictionary.modify(); + } else if (objects[objectKey] is PdfName) { + final PdfName obj = objects[objectKey]! as PdfName; + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } else if (objects[objectKey] is PdfArray) { + final PdfArray obj = objects[objectKey]! as PdfArray; + _parseArray(obj); + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } else if (objects[objectKey] is PdfStream) { + final PdfStream obj = objects[objectKey]! as PdfStream; + _parseDictionary(obj); + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } else if (objects[objectKey] is PdfDictionary) { + final PdfDictionary obj = objects[objectKey]! as PdfDictionary; + _parseDictionary(obj); + final PdfReferenceHolder holder = PdfReferenceHolder(obj); + dictionary[key] = holder; + objects[objectKey] = holder; + dictionary.modify(); + } + } else { + dictionary.remove(key); + } + } + } + } + } else { + final List names = _getKeys(dictionary); + for (int i = 0; i < names.length; i++) { + _parseDictionary(dictionary, names[i]); + } + } + } + + List _getKeys(PdfDictionary dictionary) { + final List names = []; + for (final PdfName? name in dictionary.items!.keys) { + if (name != null) { + names.add(name); + } + } + return names; + } + + void _parseArray(PdfArray array) { + final int count = array.elements.length; + for (int i = 0; i < count; i++) { + final IPdfPrimitive? element = array[i]; + if (element != null && element is PdfReferenceHolder) { + final PdfReference? reference = element.reference; + if (reference != null) { + final String objectKey = '${reference.objNum} ${reference.genNum}'; + if (_annotationObjects != null && + _annotationObjects!.containsKey(objectKey)) { + array.elements[i] = _annotationObjects![objectKey]; + array.changed = true; + } else if (_fdfObjects.containsKey(objectKey)) { + final Map objects = _fdfObjects; + if (objects[objectKey] is PdfReferenceHolder) { + array.elements[i] = objects[objectKey]; + array.changed = true; + } else if (objects[objectKey] != null && + objects[objectKey] is PdfDictionary) { + _parseDictionary(objects[objectKey]! as PdfDictionary); + final PdfReferenceHolder holder = PdfReferenceHolder( + objects[objectKey], + ); + array.elements[i] = holder; + objects[objectKey] = holder; + array.changed = true; + } + } + } + } + } + } +} + +/// The class provides fields and properties to handle objects in Fdf stream. +class FdfObject { + // Constructor + /// Initializes a new instance of the [FdfObject] class with the specified object number, generation number and object. + FdfObject(PdfNumber objNum, PdfNumber genNum, IPdfPrimitive obj) { + _objNumber = objNum.value!.toInt(); + _genNumber = genNum.value!.toInt(); + _object = obj; + } + + // Fields + late int? _objNumber; + late int? _genNumber; + late IPdfPrimitive? _object; + + // Properties + /// Gets the object number. + int get objectNumber => _objNumber!; + + /// Gets the generation number. + int get generationNumber => _genNumber!; + + /// Gets the object. + IPdfPrimitive get object => _object!; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart new file mode 100644 index 000000000..bb6c50283 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_document.dart @@ -0,0 +1,1217 @@ +import 'dart:convert'; + +import 'package:intl/intl.dart'; + +import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_xfdf_document.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'json_parser.dart'; +import 'pdf_annotation.dart'; + +/// Represents a class which contains the methods and properties to export annotations in JSON format. +class JsonDocument { + //Consturctor. + /// Initializes a new instance of the [JsonDocument] class. + JsonDocument(this._document); + + //Fields + late final PdfDocument _document; + // late String _fileName; + bool _skipBorderStyle = false; + + //Implementation + /// Internal method. + void exportAnnotationData( + Map table, + bool exportAppearance, + int pageIndex, + PdfDictionary dictionary, + ) { + bool hasAppearance = exportAppearance; + _skipBorderStyle = false; + final String? annotationType = _getAnnotationType(dictionary); + if (!isNullOrEmpty(annotationType)) { + table['type'] = annotationType!; + table['page'] = pageIndex.toString(); + switch (annotationType) { + case PdfDictionaryProperties.line: + if (dictionary.containsKey(PdfDictionaryProperties.l)) { + final IPdfPrimitive? linePoints = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.l], + ); + if (linePoints != null && + linePoints is PdfArray && + linePoints.count == 4 && + linePoints[0] != null && + linePoints[0] is PdfNumber && + linePoints[1] != null && + linePoints[1] is PdfNumber && + linePoints[2] != null && + linePoints[2] is PdfNumber && + linePoints[3] != null && + linePoints[3] is PdfNumber) { + table['start'] = + '${(linePoints[0]! as PdfNumber).value},${(linePoints[1]! as PdfNumber).value}'; + table['end'] = + '${(linePoints[2]! as PdfNumber).value},${(linePoints[3]! as PdfNumber).value}'; + } + } + break; + case 'Stamp': + if (!hasAppearance) { + hasAppearance = true; + } + break; + case 'Square': + if (!hasAppearance) { + hasAppearance = true; + } + break; + } + if (dictionary.containsKey(PdfDictionaryProperties.be) && + dictionary.containsKey(PdfDictionaryProperties.bs)) { + final IPdfPrimitive? borderEffect = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.be], + ); + if (borderEffect != null && + borderEffect is PdfDictionary && + borderEffect.containsKey(PdfDictionaryProperties.s)) { + _skipBorderStyle = true; + } + } + _writeDictionary(pageIndex, dictionary, hasAppearance, table); + } + } + + /// Internal method. + String convertToJson(Map value) { + int j = 0; + String json = '{'; + value.forEach((String fieldKey, String fieldValue) { + if (fieldValue.startsWith('{') || fieldValue.startsWith('[')) { + json = '$json"${_replaceJsonDelimiters(fieldKey)}":$fieldValue'; + } else { + if (fieldValue.startsWith(' ') && + fieldValue.length > 1 && + (fieldValue[1] == '[' || fieldValue[1] == '{')) { + fieldValue = fieldValue.trim(); + } + json = '$json"${_replaceJsonDelimiters(fieldKey)}":"$fieldValue"'; + } + if (j < value.length - 1) { + json = '$json,'; + } + j++; + }); + json = '$json}'; + return json; + } + + void _writeDictionary( + int pageIndex, + PdfDictionary dictionary, + bool hasAppearance, + Map table, + ) { + bool isBSdictionary = false; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.type], + ); + if (name != null && + name is PdfName && + name.name != null && + name.name! == PdfDictionaryProperties.border && + _skipBorderStyle) { + isBSdictionary = true; + } + } + dictionary.items!.forEach((PdfName? name, IPdfPrimitive? value) { + final String? key = name!.name; + if (key != null && + !(key == PdfDictionaryProperties.p || + key == PdfDictionaryProperties.parent)) { + final IPdfPrimitive? primitive = value; + if (primitive != null) { + if (primitive is PdfReferenceHolder) { + final IPdfPrimitive? obj = primitive.object; + if (obj != null && obj is PdfDictionary) { + switch (key) { + case PdfDictionaryProperties.bs: + _writeDictionary(pageIndex, obj, false, table); + break; + case PdfDictionaryProperties.be: + _writeDictionary(pageIndex, obj, false, table); + break; + case PdfDictionaryProperties.irt: + if (obj.containsKey('NM')) { + final String? value = _getValue(obj['NM']); + if (value != null) { + table['inreplyto'] = value; + } + } + break; + } + } + } else if (primitive is PdfDictionary) { + _writeDictionary(pageIndex, primitive, false, table); + } else if (value != null && + (!isBSdictionary || + (isBSdictionary && key != PdfDictionaryProperties.s))) { + _writeAttribute(key, value, pageIndex, dictionary, table); + } + } + } + }); + if (dictionary.containsKey(PdfDictionaryProperties.measure)) { + _exportMeasureDictionary(dictionary, table); + } + if (hasAppearance && dictionary.containsKey(PdfDictionaryProperties.ap)) { + List? bytes = _getAppearanceString( + dictionary[PdfDictionaryProperties.ap]!, + ); + if (bytes.isNotEmpty) { + table['appearance'] = base64.encode(bytes); + } + bytes = null; + } + if (dictionary.containsKey('Sound')) { + final IPdfPrimitive? sound = PdfCrossTable.dereference( + dictionary['Sound'], + ); + if (sound != null && sound is PdfStream) { + if (sound.containsKey('B')) { + final String? bits = _getValue(sound['B']); + if (!isNullOrEmpty(bits)) { + table['bits'] = bits!; + } + } + if (sound.containsKey(PdfDictionaryProperties.c)) { + final String? channels = _getValue(sound[PdfDictionaryProperties.c]); + if (!isNullOrEmpty(channels)) { + table['channels'] = channels!; + } + } + if (sound.containsKey(PdfDictionaryProperties.e)) { + final String? encoding = _getValue(sound[PdfDictionaryProperties.e]); + if (!isNullOrEmpty(encoding)) { + table['encoding'] = encoding!; + } + } + if (sound.containsKey(PdfDictionaryProperties.r)) { + final String? rate = _getValue(sound[PdfDictionaryProperties.r]); + if (!isNullOrEmpty(rate)) { + table['rate'] = rate!; + } + } + if (sound.dataStream != null && sound.dataStream!.isNotEmpty) { + final String data = PdfString.bytesToHex(sound.dataStream!); + if (!isNullOrEmpty(data)) { + table[XfdfProperties.mode.toLowerCase()] = 'raw'; + table['encodings'] = 'hex'; + if (sound.containsKey(PdfDictionaryProperties.length)) { + final String? length = _getValue( + sound[PdfDictionaryProperties.length], + ); + if (!isNullOrEmpty(length)) { + table[PdfDictionaryProperties.length.toLowerCase()] = length!; + } + } + if (sound.containsKey(PdfDictionaryProperties.filter)) { + final String? filter = _getValue( + sound[PdfDictionaryProperties.filter], + ); + if (!isNullOrEmpty(filter)) { + table[PdfDictionaryProperties.filter.toLowerCase()] = filter!; + } + } + table['data'] = data; + } + } + } + } else if (dictionary.containsKey(PdfDictionaryProperties.fs)) { + final IPdfPrimitive? fsDictionary = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.fs], + ); + if (fsDictionary != null && fsDictionary is PdfDictionary) { + if (fsDictionary.containsKey(PdfDictionaryProperties.f)) { + final String? file = _getValue( + fsDictionary[PdfDictionaryProperties.f], + ); + if (!isNullOrEmpty(file)) { + table['file'] = file!; + } + } + if (fsDictionary.containsKey(PdfDictionaryProperties.ef)) { + final IPdfPrimitive? efDictionary = PdfCrossTable.dereference( + fsDictionary[PdfDictionaryProperties.ef], + ); + if (efDictionary != null && + efDictionary is PdfDictionary && + efDictionary.containsKey(PdfDictionaryProperties.f)) { + final IPdfPrimitive? fStream = PdfCrossTable.dereference( + efDictionary[PdfDictionaryProperties.f], + ); + if (fStream != null && fStream is PdfStream) { + if (fStream.containsKey(PdfDictionaryProperties.params)) { + final IPdfPrimitive? paramsDictionary = + PdfCrossTable.dereference( + fStream[PdfDictionaryProperties.params], + ); + if (paramsDictionary != null && + paramsDictionary is PdfDictionary) { + if (paramsDictionary.containsKey( + PdfDictionaryProperties.creationDate, + )) { + final IPdfPrimitive? creationDate = + PdfCrossTable.dereference( + paramsDictionary[PdfDictionaryProperties + .creationDate], + ); + if (creationDate != null && creationDate is PdfString) { + final DateTime dateTime = dictionary.getDateTime( + creationDate, + ); + table['creation'] = DateFormat( + 'M/d/yyyy h:mm:ss a', + ).format(dateTime); + } + } + if (paramsDictionary.containsKey( + PdfDictionaryProperties.modificationDate, + )) { + final IPdfPrimitive? modifyDate = PdfCrossTable.dereference( + paramsDictionary[PdfDictionaryProperties + .modificationDate], + ); + if (modifyDate != null && modifyDate is PdfString) { + final DateTime dateTime = dictionary.getDateTime( + modifyDate, + ); + table['modification'] = DateFormat( + 'M/d/yyyy h:mm:ss a', + ).format(dateTime); + } + } + if (paramsDictionary.containsKey( + PdfDictionaryProperties.size, + )) { + final String? size = _getValue( + paramsDictionary[PdfDictionaryProperties.size], + ); + if (!isNullOrEmpty(size)) { + table[PdfDictionaryProperties.size.toLowerCase()] = size!; + } + } + if (paramsDictionary.containsKey('CheckSum')) { + final String? checksumValue = _getValue( + paramsDictionary['CheckSum'], + ); + if (!isNullOrEmpty(checksumValue)) { + final List checksum = utf8.encode(checksumValue!); + final String hexString = PdfString.bytesToHex(checksum); + table['checksum'] = hexString; + } + } + } + } + if (fStream.dataStream != null && + fStream.dataStream!.isNotEmpty) { + final String data = PdfString.bytesToHex(fStream.dataStream!); + if (!isNullOrEmpty(data)) { + table[XfdfProperties.mode.toLowerCase()] = + XfdfProperties.raw.toLowerCase(); + table[PdfDictionaryProperties.encoding.toLowerCase()] = + XfdfProperties.hex.toLowerCase(); + if (fStream.containsKey(PdfDictionaryProperties.length)) { + final String? length = _getValue( + fStream[PdfDictionaryProperties.length], + ); + if (!isNullOrEmpty(length)) { + table[PdfDictionaryProperties.length.toLowerCase()] = + length!; + } + } + if (fStream.containsKey(PdfDictionaryProperties.filter)) { + final String? filter = _getValue( + fStream[PdfDictionaryProperties.filter], + ); + if (!isNullOrEmpty(filter)) { + table[PdfDictionaryProperties.filter.toLowerCase()] = + filter!; + } + } + table[XfdfProperties.data.toLowerCase()] = data; + } + } + } + } + } + } + } + } + + List _getAppearanceString(IPdfPrimitive primitive) { + final Map appearanceTable = {}; + final Map parentTable = {}; + final IPdfPrimitive? appearance = PdfCrossTable.dereference(primitive); + if (appearance != null && appearance is PdfDictionary) { + _writeAppearanceDictionary(appearanceTable, appearance); + } + parentTable['ap'] = convertToJson(appearanceTable); + final String jsonData = convertToJson(parentTable); + return utf8.encode(jsonData); + } + + void _writeAppearanceDictionary( + Map textWriter, + PdfDictionary dictionary, + ) { + if (dictionary.count > 0) { + dictionary.items!.forEach((PdfName? name, IPdfPrimitive? value) { + _writeObject(textWriter, name!.name, value, null); + }); + } + } + + void _writeObject( + Map? textWriter, + String? key, + IPdfPrimitive? primitive, + List>? arrayWriter, + ) { + if (primitive != null) { + final String type = primitive.runtimeType.toString(); + switch (type) { + case 'PdfReferenceHolder': + final PdfReferenceHolder holder = primitive as PdfReferenceHolder; + if (holder.object != null) { + _writeObject(textWriter, key, holder.object, arrayWriter); + } + break; + case 'PdfDictionary': + final PdfDictionary dictionaryElement = primitive as PdfDictionary; + final Map mainTable = {}; + final Map subtable = {}; + _writeAppearanceDictionary(subtable, dictionaryElement); + mainTable['dict'] = convertToJson(subtable); + if (key != null) { + textWriter![key] = convertToJson(mainTable); + } else { + arrayWriter!.add(mainTable); + } + break; + case 'PdfStream': + final IPdfPrimitive? streamElement = (primitive as PdfStream) + .cloneObject(PdfDocumentHelper.getHelper(_document).crossTable); + if (streamElement != null && streamElement is PdfStream) { + if (streamElement.dataStream != null && + streamElement.dataStream!.isNotEmpty) { + final Map streamTable = {}; + _writeAppearanceDictionary(streamTable, streamElement); + final Map dataTable = {}; + final String? type = _getValue( + streamElement[PdfDictionaryProperties.subtype], + ); + if ((streamElement.containsKey(PdfDictionaryProperties.subtype) && + !isNullOrEmpty(type) && + PdfDictionaryProperties.image == type!) || + (!streamElement.containsKey(PdfDictionaryProperties.type) && + !streamElement.containsKey( + PdfDictionaryProperties.subtype, + ))) { + dataTable['mode'] = 'raw'; + dataTable['encoding'] = 'hex'; + final String data = PdfString.bytesToHex( + streamElement.dataStream!, + ); + if (!isNullOrEmpty(data)) { + dataTable['bytes'] = data; + } + } else if (streamElement.containsKey( + PdfDictionaryProperties.subtype, + ) && + !isNullOrEmpty(type) && + (PdfDictionaryProperties.form == type || + 'CIDFontType0C' == type || + 'OpenType' == type)) { + dataTable['mode'] = 'raw'; + dataTable['encoding'] = 'hex'; + streamElement.decompress(); + final String data = PdfString.bytesToHex( + streamElement.dataStream!, + ); + if (!isNullOrEmpty(data)) { + dataTable['bytes'] = data; + } + } else { + dataTable['mode'] = 'filtered'; + dataTable['encoding'] = 'ascii'; + streamElement.decompress(); + final String ascii = PdfString.bytesToHex( + streamElement.dataStream!, + ); + if (!isNullOrEmpty(ascii)) { + dataTable['bytes'] = ascii; + } + } + streamTable['data'] = convertToJson(dataTable); + final Map keyValuePairs = {}; + keyValuePairs['stream'] = convertToJson(streamTable); + if (key != null) { + textWriter![key] = convertToJson(keyValuePairs); + } else { + arrayWriter!.add(keyValuePairs); + } + } + } + break; + case 'PdfBoolean': + final PdfBoolean booleanElement = primitive as PdfBoolean; + final Map boolean = {}; + boolean['boolean'] = booleanElement.value.toString(); + if (key != null) { + textWriter![key] = convertToJson(boolean); + } else { + arrayWriter!.add(boolean); + } + break; + case 'PdfName': + if (primitive is PdfName && primitive.name != null) { + final Map name = {}; + name['name'] = primitive.name!; + if (key != null) { + textWriter![key] = convertToJson(name); + } else { + arrayWriter!.add(name); + } + } + break; + case 'PdfString': + if (primitive is PdfString && primitive.value != null) { + final Map stringValue = {}; + stringValue['string'] = primitive.value!; + if (key != null) { + textWriter![key] = convertToJson(stringValue); + } else { + arrayWriter!.add(stringValue); + } + } + break; + case 'PdfNumber': + if (primitive is PdfNumber && primitive.value != null) { + if (primitive.value is int) { + final Map integer = {}; + integer['int'] = primitive.value!.toString(); + if (key != null) { + textWriter![key] = convertToJson(integer); + } else { + arrayWriter!.add(integer); + } + } else { + final String value = primitive.value!.toDouble().toStringAsFixed( + 6, + ); + final Map integer = {}; + integer['fixed'] = value; + if (key != null) { + textWriter![key] = convertToJson(integer); + } else { + arrayWriter!.add(integer); + } + } + } + break; + case 'PdfNull': + final Map nullValue = {}; + nullValue['null'] = 'null'; + if (key != null) { + textWriter![key] = convertToJson(nullValue); + } else { + arrayWriter!.add(nullValue); + } + break; + case 'PdfArray': + final List> arrayDict = >[]; + _writeArray(arrayDict, primitive as PdfArray); + final Map tempDict = {}; + tempDict['array'] = _convertListToJson(arrayDict); + if (key != null) { + textWriter![key] = convertToJson(tempDict); + } else { + arrayWriter!.add(tempDict); + } + break; + } + } + } + + void _writeArray(List> textWriter, PdfArray array) { + for (final IPdfPrimitive? element in array.elements) { + if (element != null) { + _writeObject(null, null, element, textWriter); + } + } + } + + String _convertListToJson(List> value) { + String json = '['; + for (int i = 0; i < value.length; i++) { + json += convertToJson(value[i]); + if (i < value.length - 1) { + json = '$json,'; + } + } + json += ']'; + return json; + } + + String? _getValue(IPdfPrimitive? primitive) { + String? value; + if (primitive != null) { + if (primitive is PdfName) { + value = primitive.name; + } else if (primitive is PdfBoolean) { + value = primitive.value.toString(); + } else if (primitive is PdfString) { + value = primitive.value; + if (value != null && (value.startsWith('[') || value.startsWith('{'))) { + value = ' $value'; + } + value = _getValidString(value); + } else if (primitive is PdfArray) { + if (primitive.elements.isNotEmpty) { + for (int i = 0; i < primitive.elements.length; i++) { + final String? colorValue = _getValue(primitive.elements[i]); + if (colorValue != null) { + value = i == 0 ? colorValue : '$value,$colorValue'; + } + } + } + } else if (primitive is PdfNumber) { + if (primitive.value != null) { + value = primitive.value!.toString(); + } + } + } + return value; + } + + String? _getValidString(String? value) { + if (value != null) { + if (value.contains('"')) { + final RegExp regExp = RegExp(r'(? table, + ) { + switch (key) { + case PdfDictionaryProperties.c: + final String color = _getColor(primitive); + if (!isNullOrEmpty(color)) { + table['color'] = color; + } + break; + case PdfDictionaryProperties.da: + final IPdfPrimitive? defaultAppearance = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.da], + ); + if (defaultAppearance != null && + defaultAppearance is PdfString && + !isNullOrEmpty(defaultAppearance.value)) { + table['defaultappearance'] = defaultAppearance.value!; + } + break; + case PdfDictionaryProperties.ic: + final String interiorColor = _getColor(primitive); + if (!isNullOrEmpty(interiorColor)) { + table['interior-color'] = interiorColor; + } + break; + case PdfDictionaryProperties.m: + final IPdfPrimitive? modifiedDate = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.m], + ); + if (modifiedDate != null && + modifiedDate is PdfString && + !isNullOrEmpty(modifiedDate.value)) { + final DateTime dateTime = dictionary.getDateTime(modifiedDate); + table['date'] = DateFormat('M/d/yyyy h:mm:ss a').format(dateTime); + } + break; + case 'NM': + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[PdfDictionaryProperties.name.toLowerCase()] = value!; + } + break; + case PdfDictionaryProperties.name: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table['icon'] = value!; + } + break; + case PdfDictionaryProperties.subj: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[PdfDictionaryProperties.subject.toLowerCase()] = value!; + } + break; + case PdfDictionaryProperties.t: + final String? value = _getValue(primitive); + if (!table.containsKey(PdfDictionaryProperties.title.toLowerCase())) { + table[PdfDictionaryProperties.title.toLowerCase()] = value!; + } + break; + case PdfDictionaryProperties.rect: + final String? rect = _getValue(primitive); + if (!isNullOrEmpty(rect)) { + final List styleArray = rect!.split(','); + final Map subTable = {}; + subTable['x'] = styleArray[0]; + subTable['y'] = styleArray[1]; + subTable['width'] = styleArray[2]; + subTable['height'] = styleArray[3]; + table[key.toLowerCase()] = convertToJson(subTable); + } + break; + case PdfDictionaryProperties.creationDate: + final IPdfPrimitive? createDate = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.creationDate], + ); + if (createDate != null && + createDate is PdfString && + !isNullOrEmpty(createDate.value)) { + final DateTime creationDate = dictionary.getDateTime(createDate); + table[key.toLowerCase()] = DateFormat( + 'M/d/yyyy h:mm:ss a', + ).format(creationDate); + } + break; + case PdfDictionaryProperties.rotate: + final String? rotation = _getValue(primitive); + if (!isNullOrEmpty(rotation)) { + table['rotation'] = rotation!; + } + break; + case PdfDictionaryProperties.w: + final String? width = _getValue(primitive); + if (!isNullOrEmpty(width)) { + table[PdfDictionaryProperties.width.toLowerCase()] = width!; + } + break; + case PdfDictionaryProperties.le: + if (primitive is PdfArray) { + if (primitive.count == 2) { + table['head'] = _getValue(primitive.elements[0])!; + table['tail'] = _getValue(primitive.elements[1])!; + } + } else if (primitive is PdfName) { + final String? head = _getValue(primitive); + if (!isNullOrEmpty(head)) { + table['head'] = head!; + } + } + break; + case 'S': + final String? style = _getValue(primitive); + if (!isNullOrEmpty(style)) { + switch (style) { + case PdfDictionaryProperties.d: + table['style'] = 'dash'; + break; + case PdfDictionaryProperties.c: + table['style'] = 'cloudy'; + break; + case PdfDictionaryProperties.s: + table['style'] = 'solid'; + break; + case 'B': + table['style'] = 'bevelled'; + break; + case PdfDictionaryProperties.i: + table['style'] = 'inset'; + break; + case PdfDictionaryProperties.u: + table['style'] = 'underline'; + break; + } + } + break; + case PdfDictionaryProperties.d: + if (!table.containsKey('dashes')) { + final String? dashes = _getValue(primitive); + if (!isNullOrEmpty(dashes)) { + table['dashes'] = dashes!; + } + } + break; + case PdfDictionaryProperties.i: + final String? intensity = _getValue(primitive); + if (!isNullOrEmpty(intensity)) { + table['intensity'] = intensity!; + } + break; + case PdfDictionaryProperties.rd: + final String? fringe = _getValue(primitive); + if (!isNullOrEmpty(fringe)) { + table['fringe'] = fringe!; + } + break; + case PdfDictionaryProperties.it: + final String? it = _getValue(primitive); + if (!isNullOrEmpty(it)) { + table[key] = it!; + } + break; + case 'RT': + final String? replyType = _getValue(primitive); + if (!isNullOrEmpty(replyType)) { + table['replyType'] = replyType!.toLowerCase(); + } + break; + case PdfDictionaryProperties.ll: + final String? leaderLength = _getValue(primitive); + if (!isNullOrEmpty(leaderLength)) { + table['leaderLength'] = leaderLength!; + } + break; + case PdfDictionaryProperties.lle: + final String? leaderExtend = _getValue(primitive); + if (!isNullOrEmpty(leaderExtend)) { + table['leaderExtend'] = leaderExtend!; + } + break; + case PdfDictionaryProperties.cap: + final String? caption = _getValue(primitive); + if (!isNullOrEmpty(caption)) { + table['caption'] = caption!; + } + break; + case PdfDictionaryProperties.cp: + final String? captionStyle = _getValue(primitive); + if (!isNullOrEmpty(captionStyle)) { + table['caption-style'] = captionStyle!; + } + break; + case 'CL': + final String? callout = _getValue(primitive); + if (!isNullOrEmpty(callout)) { + table['callout'] = callout!; + } + break; + case PdfDictionaryProperties.quadPoints: + final String? coords = _getValue(primitive); + if (!isNullOrEmpty(coords)) { + table['coords'] = coords!; + } + break; + case PdfDictionaryProperties.ca: + final String? opacity = _getValue(primitive); + if (!isNullOrEmpty(opacity)) { + table['opacity'] = opacity!; + } + break; + case PdfDictionaryProperties.f: + if (primitive is PdfNumber) { + final List annotationFlags = + PdfAnnotationHelper.obtainAnnotationFlags( + primitive.value!.toInt(), + ); + final String flag = + annotationFlags + .map((PdfAnnotationFlags flag) => getEnumName(flag)) + .toString() + .replaceAll(RegExp('[ ()]'), '') + .toLowerCase(); + table[PdfDictionaryProperties.flags.toLowerCase()] = flag; + } + break; + case PdfDictionaryProperties.contents: + final IPdfPrimitive? contents = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.contents], + ); + if (contents != null && + contents is PdfString && + !isNullOrEmpty(contents.value)) { + table['contents'] = _getValidString(contents.value)!; + } + break; + case 'InkList': + final Map points = {}; + final IPdfPrimitive? inkList = PdfCrossTable.dereference( + dictionary['InkList'], + ); + if (inkList != null && inkList is PdfArray && inkList.count > 0) { + final List element = []; + for (int j = 0; j < inkList.count; j++) { + if (inkList[j]! is PdfArray) { + element.add(inkList[j]! as PdfArray); + } + } + points['gesture'] = _convertToJsonArray(element); + table['inklist'] = convertToJson(points); + } + break; + case PdfDictionaryProperties.vertices: + final IPdfPrimitive? vertices = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.vertices], + ); + if (vertices != null && vertices is PdfArray && vertices.count > 0) { + if (vertices.count.isEven) { + String value = ''; + IPdfPrimitive? numberElement; + for (int i = 0; i < vertices.count - 1; i++) { + numberElement = vertices.elements[i]; + if (numberElement != null && numberElement is PdfNumber) { + final String? number = _getValue(numberElement); + if (!isNullOrEmpty(number)) { + value += number! + (i % 2 != 0 ? ';' : ','); + } + } + } + numberElement = vertices.elements[vertices.count - 1]; + if (numberElement != null && numberElement is PdfNumber) { + final String? number = _getValue(numberElement); + if (!isNullOrEmpty(number)) { + value += number!; + } + } + if (!isNullOrEmpty(value)) { + table['vertices'] = value; + } + } + } + break; + case 'DS': + if (dictionary.containsKey('DS')) { + final IPdfPrimitive? defaultStyle = PdfCrossTable.dereference( + dictionary['DS'], + ); + final Map styleTable = {}; + if (defaultStyle != null && + defaultStyle is PdfString && + !isNullOrEmpty(defaultStyle.value)) { + final List textStyle = defaultStyle.value!.split(';'); + for (int i = 0; i < textStyle.length; i++) { + if (!isNullOrEmpty(textStyle[i]) && textStyle[i].contains(',')) { + textStyle[i] = _replaceJsonDelimiters(textStyle[i]); + } + final List text = textStyle[i].split(':'); + styleTable[text[0]] = text[1]; + } + } + table['defaultStyle'] = convertToJson(styleTable); + } + break; + case 'RC': + if (dictionary.containsKey('RC')) { + final IPdfPrimitive? contentStyle = PdfCrossTable.dereference( + dictionary['RC'], + ); + if (contentStyle != null && + contentStyle is PdfString && + !isNullOrEmpty(contentStyle.value)) { + String value = contentStyle.value!; + final int index = value.indexOf(' 0) { + value = value.substring(index); + } + table['contents-richtext'] = _getValidString(value)!; + } + } + break; + case PdfDictionaryProperties.type: + case PdfDictionaryProperties.subtype: + case PdfDictionaryProperties.p: + case PdfDictionaryProperties.parent: + case PdfDictionaryProperties.l: + case PdfDictionaryProperties.fs: + case 'MeasurementTypes': + case 'GroupNesting': + case 'ITEx': + case 'Sound': + break; + case PdfDictionaryProperties.border: + case PdfDictionaryProperties.a: + case PdfDictionaryProperties.r: + case PdfDictionaryProperties.x: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[key.toLowerCase()] = value!; + } + break; + default: + final String? value = _getValue(primitive); + if (!isNullOrEmpty(value)) { + table[key] = value!; + } + break; + } + } + + String _convertToJsonArray(List value) { + String json = '['; + for (int i = 0; i < value.length; i++) { + final String? point = _getValue(value[i]); + if (point != null) { + json = '$json[$point]'; + } + if (i < value.length - 1) { + json = '$json,'; + } + } + json = '$json]'; + return json; + } + + String _getColor(IPdfPrimitive primitive) { + String color = ''; + if (primitive is PdfArray && primitive.count >= 3) { + final String r = + PdfString.bytesToHex([ + ((primitive.elements[0]! as PdfNumber).value! * 255).round(), + ]).toUpperCase(); + final String g = + PdfString.bytesToHex([ + ((primitive.elements[1]! as PdfNumber).value! * 255).round(), + ]).toUpperCase(); + final String b = + PdfString.bytesToHex([ + ((primitive.elements[2]! as PdfNumber).value! * 255).round(), + ]).toUpperCase(); + color = '#$r$g$b'; + } + return color; + } + + void _exportMeasureDictionary( + PdfDictionary dictionary, + Map table, + ) { + final IPdfPrimitive? mdictionary = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.measure], + ); + if (mdictionary != null && mdictionary is PdfDictionary) { + if (mdictionary.containsKey(PdfDictionaryProperties.type)) { + table['type1'] = 'Measure'; + } + if (mdictionary.containsKey(PdfDictionaryProperties.r)) { + final String? value = _getValue(mdictionary[PdfDictionaryProperties.r]); + if (!isNullOrEmpty(value)) { + table['ratevalue'] = value!; + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.subtype)) { + final String? value = _getValue( + mdictionary[PdfDictionaryProperties.subtype], + ); + if (!isNullOrEmpty(value)) { + table[PdfDictionaryProperties.subtype] = value!; + } + } + if (mdictionary.containsKey('TargetUnitConversion')) { + final String? value = _getValue(mdictionary['TargetUnitConversion']); + if (!isNullOrEmpty(value)) { + table['TargetUnitConversion'] = value!; + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.a)) { + final IPdfPrimitive? aArray = mdictionary[PdfDictionaryProperties.a]; + if (aArray != null && + aArray is PdfArray && + aArray.elements.isNotEmpty) { + final IPdfPrimitive? adictionary = PdfCrossTable.dereference( + aArray.elements[0], + ); + if (adictionary != null && adictionary is PdfDictionary) { + _exportMeasureFormatDetails('area', adictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.d)) { + final IPdfPrimitive? dArray = mdictionary[PdfDictionaryProperties.d]; + if (dArray != null && + dArray is PdfArray && + dArray.elements.isNotEmpty) { + final IPdfPrimitive? ddictionary = PdfCrossTable.dereference( + dArray.elements[0], + ); + if (ddictionary != null && ddictionary is PdfDictionary) { + _exportMeasureFormatDetails('distance', ddictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.x)) { + final IPdfPrimitive? xArray = mdictionary[PdfDictionaryProperties.x]; + if (xArray != null && + xArray is PdfArray && + xArray.elements.isNotEmpty) { + final IPdfPrimitive? xdictionary = PdfCrossTable.dereference( + xArray.elements[0], + ); + if (xdictionary != null && xdictionary is PdfDictionary) { + _exportMeasureFormatDetails('xformat', xdictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.t)) { + final IPdfPrimitive? tArray = mdictionary[PdfDictionaryProperties.t]; + if (tArray != null && + tArray is PdfArray && + tArray.elements.isNotEmpty) { + final IPdfPrimitive? tdictionary = PdfCrossTable.dereference( + tArray.elements[0], + ); + if (tdictionary != null && tdictionary is PdfDictionary) { + _exportMeasureFormatDetails('tformat', tdictionary, table); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.v)) { + final IPdfPrimitive? vArray = mdictionary[PdfDictionaryProperties.v]; + if (vArray != null && + vArray is PdfArray && + vArray.elements.isNotEmpty) { + final IPdfPrimitive? vdictionary = PdfCrossTable.dereference( + vArray.elements[0], + ); + if (vdictionary != null && vdictionary is PdfDictionary) { + _exportMeasureFormatDetails('vformat', vdictionary, table); + } + } + } + } + } + + void _exportMeasureFormatDetails( + String key, + PdfDictionary measurementDetails, + Map table, + ) { + final Map subTable = {}; + if (measurementDetails.containsKey(PdfDictionaryProperties.c)) { + final String? value = _getValue( + measurementDetails[PdfDictionaryProperties.c], + ); + if (!isNullOrEmpty(value)) { + subTable['c'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.f)) { + final String? value = _getValue( + measurementDetails[PdfDictionaryProperties.f], + ); + if (!isNullOrEmpty(value)) { + subTable['f'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.d)) { + final String? value = _getValue( + measurementDetails[PdfDictionaryProperties.d], + ); + if (!isNullOrEmpty(value)) { + subTable['d'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.rd)) { + final String? value = _getValue( + measurementDetails[PdfDictionaryProperties.rd], + ); + if (!isNullOrEmpty(value)) { + subTable['rd'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.u)) { + final String? value = _getValue( + measurementDetails[PdfDictionaryProperties.u], + ); + if (!isNullOrEmpty(value)) { + subTable['u'] = value!; + } + } + if (measurementDetails.containsKey('RT')) { + final String? value = _getValue(measurementDetails['RT']); + if (!isNullOrEmpty(value)) { + subTable['rt'] = value!; + } + } + if (measurementDetails.containsKey('SS')) { + final String? value = _getValue(measurementDetails['SS']); + if (!isNullOrEmpty(value)) { + subTable['ss'] = value!; + } + } + if (measurementDetails.containsKey('FD')) { + final String? value = _getValue(measurementDetails['FD']); + if (!isNullOrEmpty(value)) { + subTable['fd'] = value!; + } + } + if (measurementDetails.containsKey(PdfDictionaryProperties.type)) { + final String? value = _getValue( + measurementDetails[PdfDictionaryProperties.type], + ); + if (!isNullOrEmpty(value)) { + subTable[PdfDictionaryProperties.type] = value!; + } + } + table[key] = convertToJson(subTable); + } + + String _replaceJsonDelimiters(String value) { + // ignore: unnecessary_string_escapes + return value.contains(RegExp('[":,{}]|[\[]|]')) + ? value + .replaceAll(',', '_x002C_') + .replaceAll('"', '_x0022_') + .replaceAll(':', '_x003A_') + .replaceAll('{', '_x007B_') + .replaceAll('}', '_x007D_') + .replaceAll('[', '_x005B_') + .replaceAll(']', '_x005D_') + : value; + } + + String? _getAnnotationType(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype], + ); + if (subtype != null && subtype is PdfName && subtype.name != null) { + return subtype.name!; + } + } + return null; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart new file mode 100644 index 000000000..915d65ea4 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/json_parser.dart @@ -0,0 +1,1418 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; + +import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_form.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/pdf_page.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'pdf_annotation.dart'; +import 'xfdf_parser.dart'; + +/// Internal class. +class JsonParser { + /// Internal Constructor. + JsonParser(this.document); + + /// Internal Field. + late PdfDocument document; + + /// Internal Field. + String? annotation; + + /// Internal Field. + String? style; + + /// Internal Field. + bool isBasicStyle = true; + + /// Internal Field. + String? beginLineStyle; + + /// Internal Field. + String? endLineStyle; + + /// Internal Field. + Map? dataStream; + + /// Internal Field. + String? values; + + /// Internal Field. + Map? groupReferences; + + /// Internal Field. + List? groupHolders; + + bool _isNormalAppearanceAdded = false; + + /// Internal Method. + void importAnnotationData(List? data) { + if (data != null) { + final Map jsonData = json.decode(utf8.decode(data)); + parseJsonData(jsonData); + jsonData.clear(); + if (groupHolders != null && groupHolders!.isNotEmpty) { + for (final PdfDictionary dictionary in groupHolders!) { + final IPdfPrimitive? inReplyTo = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.irt], + ); + if (inReplyTo != null && + inReplyTo is PdfString && + !isNullOrEmpty(inReplyTo.value)) { + if (groupReferences != null && + groupReferences!.containsKey(inReplyTo.value)) { + dictionary[PdfDictionaryProperties.irt] = + groupReferences![inReplyTo.value]; + } else { + dictionary.remove(PdfDictionaryProperties.irt); + } + } + } + } + if (groupReferences != null) { + groupReferences!.clear(); + } + if (groupHolders != null) { + groupHolders!.clear(); + } + groupReferences = null; + groupHolders = null; + } + } + + /// Internal Method. + void parseJsonData(Map data) { + if (data.containsKey('type')) { + parseAnnotationData(data); + } + for (final dynamic value in data.values) { + if (value is Map) { + parseJsonData(value); + } else if (value is List) { + // ignore: avoid_function_literals_in_foreach_calls + value.forEach((dynamic element) { + if (element is Map) { + parseJsonData(element); + } + }); + } + } + } + + /// Internal Method. + void parseAnnotationData(Map annotData) { + final String page = annotData.containsKey('page') ? annotData['page'] : ''; + final String type = annotData.containsKey('type') ? annotData['type'] : ''; + final int pageIndex = int.tryParse(page) ?? -1; + if (pageIndex >= 0 && pageIndex < document.pages.count) { + final PdfPage loadedPage = document.pages[pageIndex]; + PdfPageHelper.getHelper(loadedPage).importAnnotation = true; + final PdfDictionary annotDictionary = getAnnotationData( + type, + pageIndex, + annotData, + ); + if (annotDictionary.count > 0) { + final PdfReferenceHolder holder = PdfReferenceHolder(annotDictionary); + if (annotDictionary.containsKey(PdfDictionaryProperties.nm) || + annotDictionary.containsKey(PdfDictionaryProperties.irt)) { + addReferenceToGroup(holder, annotDictionary); + } + final PdfDictionary? pageDictionary = + PdfPageHelper.getHelper(document.pages[pageIndex]).dictionary; + if (pageDictionary != null) { + if (!pageDictionary.containsKey(PdfDictionaryProperties.annots)) { + pageDictionary[PdfDictionaryProperties.annots] = PdfArray(); + } + final IPdfPrimitive? annots = PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.annots], + ); + if (annots != null && annots is PdfArray) { + annots.elements.add(holder); + annots.changed = true; + } + } + } + } + beginLineStyle = null; + endLineStyle = null; + } + + /// Internal Method. + void addReferenceToGroup( + PdfReferenceHolder holder, + PdfDictionary dictionary, + ) { + IPdfPrimitive? name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.nm], + ); + groupReferences ??= {}; + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + groupReferences![name.value!] = holder; + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + groupHolders ??= []; + groupHolders!.add(dictionary); + } + } else if (name == null) { + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.irt], + ); + } + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + if (groupReferences!.containsKey(name.value)) { + final PdfReferenceHolder referenceHolder = + groupReferences![name.value]!; + dictionary[PdfDictionaryProperties.irt] = referenceHolder; + } + } + } + } + + /// Internal Method. + PdfDictionary getAnnotationData( + String type, + int pageindex, + Map annotData, + ) { + final PdfDictionary annotDictionary = PdfDictionary(); + annotDictionary.setName( + PdfDictionaryProperties.type, + PdfDictionaryProperties.annot, + ); + bool isValidType = true; + switch (type.toLowerCase()) { + case 'line': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.line, + ); + break; + case 'circle': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.circle, + ); + break; + case 'square': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.square, + ); + break; + case 'polyline': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'PolyLine'); + break; + case 'polygon': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.polygon, + ); + break; + case 'ink': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Ink'); + break; + case 'popup': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.popup, + ); + break; + case 'text': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.text, + ); + break; + case 'freetext': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'FreeText'); + break; + case 'stamp': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Stamp'); + break; + case 'highlight': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.highlight, + ); + break; + case 'squiggly': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.squiggly, + ); + break; + case 'underline': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.underline, + ); + break; + case 'strikeout': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.strikeOut, + ); + break; + case 'fileattachment': + annotDictionary.setName( + PdfDictionaryProperties.subtype, + 'FileAttachment', + ); + break; + case 'sound': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Sound'); + break; + case 'redact': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Redact'); + annotation = 'redact'; + break; + case 'caret': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Caret'); + break; + case 'watermark': + annotDictionary.setName(PdfDictionaryProperties.subtype, 'Watermark'); + break; + default: + isValidType = false; + break; + } + if (isValidType) { + addAnnotationData(annotDictionary, annotData, pageindex); + } + return annotDictionary; + } + + /// Internal method. + void addAnnotationData( + PdfDictionary annotDictionary, + Map annotData, + int index, + ) { + List? linePoints = []; + final PdfDictionary borderEffectDictionary = PdfDictionary(); + final PdfDictionary borderStyleDictionary = PdfDictionary(); + annotData.forEach((String key, dynamic value) { + if (value is String) { + value = PdfFormHelper.decodeXMLConversion(value); + } + switch (key.toLowerCase()) { + case 'start': + case 'end': + linePoints!.addAll(_obtainFloatPoints(value)); + if (linePoints!.length == 4) { + annotDictionary.setProperty( + PdfDictionaryProperties.l, + PdfArray(linePoints), + ); + linePoints!.clear(); + linePoints = null; + } + break; + case 'itex': + break; + case 'state': + addString(annotDictionary, 'State', value.toString()); + break; + case 'statemodel': + addString(annotDictionary, 'StateModel', value.toString()); + break; + case 'replytype': + if (value == 'group') { + annotDictionary.setName('RT', 'Group'); + } + break; + case 'inreplyto': + addString( + annotDictionary, + PdfDictionaryProperties.irt, + value.toString(), + ); + break; + case 'dashes': + case 'width': + case 'intensity': + case 'style': + addBorderStyle( + key, + value, + borderEffectDictionary, + borderStyleDictionary, + ); + break; + case 'rect': + final List points = _obtainFloatPoints(value.values.toList()); + if (points.length == 4) { + annotDictionary.setProperty( + PdfDictionaryProperties.rect, + PdfArray(points), + ); + } + break; + case 'color': + if (value is String && !isNullOrEmpty(value)) { + final PdfArray? colorArray = getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.c, + colorArray, + ); + } + } + break; + case 'oc': + if (annotation == 'redact') { + if (value is String && !isNullOrEmpty(value)) { + final PdfArray? colorArray = getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.ic, + colorArray, + ); + } + } + } + break; + case 'interior-color': + if (value is String && !isNullOrEmpty(value)) { + final PdfArray? colorArray = getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.ic, + colorArray, + ); + } + } + break; + case 'date': + addString( + annotDictionary, + PdfDictionaryProperties.m, + value.toString(), + ); + break; + case 'creationdate': + addString( + annotDictionary, + PdfDictionaryProperties.creationDate, + value.toString(), + ); + break; + case 'name': + addString( + annotDictionary, + PdfDictionaryProperties.nm, + value.toString(), + ); + break; + case 'icon': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.name, value); + } + break; + case 'subject': + addString( + annotDictionary, + PdfDictionaryProperties.subj, + value.toString(), + ); + break; + case 'title': + addString(annotDictionary, PdfDictionaryProperties.t, value); + break; + case 'rotation': + addNumber(annotDictionary, PdfDictionaryProperties.rotate, value); + break; + case 'fringe': + addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.rd, + ); + break; + case 'it': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.it, value); + } + break; + case 'leaderlength': + addNumber( + annotDictionary, + PdfDictionaryProperties.ll, + value.toString(), + ); + break; + case 'leaderextend': + addNumber( + annotDictionary, + PdfDictionaryProperties.lle, + value.toString(), + ); + break; + case 'caption': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setBoolean( + PdfDictionaryProperties.cap, + value.toLowerCase() == 'yes' || value.toLowerCase() == 'true', + ); + } + break; + case 'caption-style': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.cp, value); + } + break; + case 'callout': + addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.cl, + ); + break; + case 'coords': + addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.quadPoints, + ); + break; + case 'border': + addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.border, + ); + break; + case 'opacity': + addNumber(annotDictionary, PdfDictionaryProperties.ds, value); + break; + case 'defaultstyle': + addString( + annotDictionary, + PdfDictionaryProperties.ds, + value + .toString() + .replaceAll(RegExp(r'[{}]'), '') + .replaceAll(',', ';'), + ); + break; + case 'defaultappearance': + addString( + annotDictionary, + PdfDictionaryProperties.da, + value.toString().replaceAll(RegExp(r'[{},]'), ''), + ); + break; + case 'contents-richtext': + final String richtext = trimEscapeCharacters(value); + annotDictionary.setString(PdfDictionaryProperties.rc, richtext); + break; + case 'flags': + if (value is String && !isNullOrEmpty(value)) { + final List annotFlag = []; + if (value.contains(',')) { + final List values = value.split(','); + for (final String flag in values) { + final PdfAnnotationFlags flagType = + XfdfParser.mapAnnotationFlags(flag); + if (!annotFlag.contains(flagType)) { + annotFlag.add(flagType); + } + } + } else { + annotFlag.add(XfdfParser.mapAnnotationFlags(value)); + } + int flagValue = 0; + for (int i = 0; i < annotFlag.length; i++) { + flagValue |= PdfAnnotationHelper.getAnnotationFlagsValue( + annotFlag[i], + ); + } + if (flagValue > 0) { + annotDictionary.setNumber(PdfDictionaryProperties.f, flagValue); + } + } + break; + case 'open': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setBoolean( + PdfDictionaryProperties.open, + value == 'true' || value == 'yes', + ); + } + break; + case 'repeat': + if (value is String && !isNullOrEmpty(value)) { + annotDictionary.setBoolean( + PdfDictionaryProperties.repeat, + value == 'true' || value == 'yes', + ); + } + break; + case 'overlaytext': + annotDictionary.setString(PdfDictionaryProperties.overlayText, value); + break; + case 'contents': + final String contents = trimEscapeCharacters(value); + if (!isNullOrEmpty(contents)) { + annotDictionary.setString( + PdfDictionaryProperties.contents, + contents, + ); + } + break; + case 'q': + final int? alignment = int.tryParse(value.toString()); + if (alignment != null) { + annotDictionary.setNumber(PdfDictionaryProperties.q, alignment); + } + break; + case 'inklist': + final PdfArray inkListCollection = PdfArray(); + final String inklist = value + .toString() + .replaceAll('gesture', '') + .replaceAll(RegExp(r'[\[\]{}:]'), ''); + final List pointsArray = inklist.split(','); + if (pointsArray.isNotEmpty) { + final List pointsList = []; + for (final String point in pointsArray) { + final num? result = num.tryParse(point); + if (result != null) { + pointsList.add(result); + } + } + if (pointsList.isNotEmpty && pointsList.length.isEven) { + inkListCollection.add(PdfArray(pointsList)); + } + pointsList.clear(); + } + annotDictionary.setProperty( + PdfDictionaryProperties.inkList, + inkListCollection, + ); + break; + case 'head': + beginLineStyle = getEnumName( + XfdfParser.mapLineEndingStyle(value.toString()), + ); + break; + case 'tail': + endLineStyle = getEnumName( + XfdfParser.mapLineEndingStyle(value.toString()), + ); + break; + case 'creation': + case 'modification': + case 'file': + case 'bits': + case 'channels': + case 'encoding': + case 'rate': + case 'length': + case 'filter': + case 'mode': + case 'size': + dataStream ??= {}; + dataStream![key] = value.toString(); + break; + case 'data': + values = value; + break; + case 'vertices': + if (value is String && !isNullOrEmpty(value)) { + final List vertices = value.split(RegExp('[;,]')); + if (vertices.isNotEmpty) { + final List verticesList = []; + for (final String vertice in vertices) { + addFloatPointsToCollection(verticesList, vertice); + } + if (verticesList.isNotEmpty && verticesList.length.isEven) { + annotDictionary.setProperty( + PdfDictionaryProperties.vertices, + PdfArray(verticesList), + ); + } + } + } + break; + case 'customdata': + addString( + annotDictionary, + PdfDictionaryProperties.customData, + trimEscapeCharacters(value.toString()), + ); + break; + case 'appearance': + _addAppearanceData(annotDictionary, value.toString()); + break; + default: + break; + } + }); + _addMeasureDictionary(annotDictionary, annotData); + if (!isNullOrEmpty(beginLineStyle)) { + if (!isNullOrEmpty(endLineStyle)) { + final PdfArray lineEndingStyles = PdfArray(); + lineEndingStyles.add(PdfName(beginLineStyle)); + lineEndingStyles.add(PdfName(endLineStyle)); + annotDictionary.setProperty( + PdfDictionaryProperties.le, + lineEndingStyles, + ); + } else { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + } else if (!isNullOrEmpty(endLineStyle)) { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + if (borderStyleDictionary.count > 0) { + borderStyleDictionary.setProperty( + PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.border), + ); + annotDictionary.setProperty( + PdfDictionaryProperties.bs, + PdfReferenceHolder(borderStyleDictionary), + ); + } + if (borderEffectDictionary.count > 0) { + annotDictionary.setProperty( + PdfDictionaryProperties.be, + PdfReferenceHolder(borderEffectDictionary), + ); + } + if (dataStream != null && values != null) { + addStreamData(dataStream!, annotDictionary, values!); + } + } + + void _addMeasureDictionary( + PdfDictionary annotDictionary, + Map element, + ) { + Map? area; + Map? distance; + Map? xformat; + Map? tformat; + Map? vformat; + final PdfDictionary measureDictionary = PdfDictionary(); + final PdfArray dArray = PdfArray(); + final PdfArray aArray = PdfArray(); + final PdfArray xArray = PdfArray(); + final PdfArray tArray = PdfArray(); + final PdfArray vArray = PdfArray(); + final PdfDictionary dDict = PdfDictionary(); + final PdfDictionary aDict = PdfDictionary(); + final PdfDictionary xDict = PdfDictionary(); + final PdfDictionary tDict = PdfDictionary(); + final PdfDictionary vDict = PdfDictionary(); + measureDictionary.items![PdfName(PdfDictionaryProperties.a)] = aArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.d)] = dArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.x)] = xArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.t)] = tArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.v)] = vArray; + if (element.containsKey(PdfDictionaryProperties.type1.toLowerCase())) { + measureDictionary.setName( + PdfDictionaryProperties.type, + PdfDictionaryProperties.measure, + ); + } + element.forEach((String key, dynamic value) { + if (value is String) { + switch (key.toLowerCase()) { + case 'ratevalue': + measureDictionary.setString(PdfDictionaryProperties.r, value); + break; + case 'subtype': + measureDictionary.setString(PdfDictionaryProperties.subtype, value); + break; + case 'targetunitconversion': + measureDictionary.setString( + PdfDictionaryProperties.targetUnitConversion, + value, + ); + break; + case 'area': + area = {}; + area = _addDictionaryData(area!, value); + break; + case 'distance': + distance = {}; + distance = _addDictionaryData(distance!, value); + break; + case 'xformat': + xformat = {}; + xformat = _addDictionaryData(xformat!, value); + break; + case 'tformat': + tformat = {}; + tformat = _addDictionaryData(tformat!, value); + break; + case 'vformat': + vformat = {}; + vformat = _addDictionaryData(vformat!, value); + break; + } + } + }); + if (xformat != null) { + _addElements(xformat!, xDict); + xArray.add(xDict); + } + if (area != null) { + _addElements(area!, aDict); + aArray.add(aDict); + } + if (distance != null) { + _addElements(distance!, dDict); + dArray.add(dDict); + } + if (vformat != null) { + _addElements(vformat!, vDict); + vArray.add(vDict); + } + if (tformat != null) { + _addElements(tformat!, tDict); + tArray.add(tDict); + } + if (measureDictionary.count > 0 && + measureDictionary.containsKey(PdfDictionaryProperties.type)) { + annotDictionary.items![PdfName( + PdfDictionaryProperties.measure, + )] = PdfReferenceHolder(measureDictionary); + } + } + + void _addElements(Map element, PdfDictionary dictionary) { + element.forEach((String key, String value) { + final num? elementValue = num.tryParse(value); + if (elementValue != null) { + switch (key.toLowerCase()) { + case 'd': + dictionary.items![PdfName(PdfDictionaryProperties.d)] = PdfNumber( + elementValue, + ); + break; + case 'c': + dictionary.items![PdfName(PdfDictionaryProperties.c)] = PdfNumber( + elementValue, + ); + break; + case 'rt': + dictionary.items![PdfName(PdfDictionaryProperties.rt)] = PdfNumber( + elementValue, + ); + break; + case 'rd': + dictionary.items![PdfName(PdfDictionaryProperties.rd)] = PdfNumber( + elementValue, + ); + break; + case 'ss': + dictionary.items![PdfName(PdfDictionaryProperties.ss)] = PdfNumber( + elementValue, + ); + break; + case 'u': + dictionary.items![PdfName(PdfDictionaryProperties.u)] = PdfNumber( + elementValue, + ); + break; + case 'f': + dictionary.items![PdfName(PdfDictionaryProperties.f)] = PdfNumber( + elementValue, + ); + break; + case 'fd': + dictionary.items![PdfName(PdfDictionaryProperties.fd)] = PdfNumber( + elementValue, + ); + break; + case 'type': + dictionary.items![PdfName( + PdfDictionaryProperties.type, + )] = PdfNumber(elementValue); + break; + } + } + }); + } + + Map _addDictionaryData( + Map data, + String value, + ) { + String addValue = ''; + for (int k = 0; k < value.length; k++) { + addValue += (value[k] == ':' || value[k] == ';') ? '#' : value[k]; + } + final List valueSplit = addValue.split('#'); + for (int i = 0; i < valueSplit.length - 1; i += 2) { + data[valueSplit[i]] = valueSplit[i + 1]; + } + return data; + } + + void _addAppearanceData(PdfDictionary dictionary, String value) { + if (!isNullOrEmpty(value)) { + final List appearanceData = base64.decode(value); + if (appearanceData.isNotEmpty) { + final Map dict = json.decode( + utf8.decode(appearanceData), + ); + final PdfDictionary appearance = PdfDictionary(); + if (dict.isNotEmpty) { + for (final dynamic dictValue in dict.values) { + dictionary[PdfDictionaryProperties.ap] = PdfReferenceHolder( + _parseDictionaryItems(dictValue, appearance), + ); + } + } + } + } + _isNormalAppearanceAdded = false; + } + + IPdfPrimitive _parseDictionaryItems( + dynamic elementValue, + IPdfPrimitive primitive, + ) { + if (elementValue != null) { + if (elementValue is Map) { + for (String token in elementValue.keys) { + final dynamic value = elementValue[token]; + token = PdfFormHelper.decodeXMLConversion(token); + switch (token) { + case 'stream': + PdfStream stream = PdfStream(); + stream = _parseDictionaryItems(value, stream) as PdfStream; + return stream; + case 'array': + PdfArray array = PdfArray(); + array = _parseDictionaryItems(value, array) as PdfArray; + return array; + case 'name': + return PdfName(value.toString()); + case 'string': + return PdfString(value.toString()); + case 'boolean': + final bool test = value.toString().toLowerCase() == 'true'; + return PdfBoolean(test); + case 'dict': + PdfDictionary pdfDictionary = PdfDictionary(); + pdfDictionary = + _parseDictionaryItems(value, pdfDictionary) as PdfDictionary; + return pdfDictionary; + case 'int': + final int? result = int.tryParse(value.toString()); + if (result != null) { + return PdfNumber(result); + } + break; + case 'fixed': + final num? result = num.tryParse(value.toString()); + if (result != null) { + return PdfNumber(result); + } + break; + case 'data': + if (primitive is PdfStream && + value != null && + value is Map) { + primitive.data = _getStreamData(value); + if (!primitive.containsKey(PdfDictionaryProperties.type) && + !primitive.containsKey(PdfDictionaryProperties.subtype)) { + primitive.decompress(); + } + bool isImage = false; + if (primitive.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + primitive[PdfDictionaryProperties.subtype], + ); + if (subtype != null && + subtype is PdfName && + subtype.name == PdfDictionaryProperties.image) { + isImage = true; + } + } + if (isImage) { + primitive.compress = false; + } else { + if (primitive.containsKey(PdfDictionaryProperties.length)) { + primitive.remove(PdfDictionaryProperties.length); + } + if (primitive.containsKey(PdfDictionaryProperties.filter)) { + primitive.remove(PdfDictionaryProperties.filter); + } + } + } + break; + case 'N': + if (_isNormalAppearanceAdded && primitive is PdfDictionary) { + primitive[token] = _parseDictionaryItems(value, primitive); + } else if (primitive is PdfDictionary) { + _isNormalAppearanceAdded = true; + final PdfDictionary dic = PdfDictionary(); + primitive[token] = PdfReferenceHolder( + _parseDictionaryItems(value, dic), + ); + } else { + _isNormalAppearanceAdded = true; + final PdfDictionary dic = PdfDictionary(); + dic[token] = PdfReferenceHolder( + _parseDictionaryItems(value, dic), + ); + return dic; + } + break; + case 'BBox': + case 'Type': + case 'Subtype': + case 'Resources': + case 'BaseFont': + case 'ProcSet': + case 'Font': + case 'Encoding': + case 'Matrix': + case 'Length': + case 'CIDToGIDMap': + case 'DW': + case 'FontName': + case 'Flags': + case 'FontBBox': + case 'MissingWidth': + case 'StemV': + case 'ItalicAngle': + case 'CapHeight': + case 'Ascent': + case 'Descent': + case 'Leading': + case 'AvgWidth': + case 'MaxWidth': + case 'StemH': + case 'CIDSystemInfo': + case 'Registry': + case 'Ordering': + case 'Supplement': + case 'W': + case 'XObject': + case 'Filter': + case 'BitsPerComponent': + case 'ColorSpace': + case 'FormType': + case 'Name': + case 'Height': + case 'Width': + case 'Decode': + case 'DecodeParms': + case 'BlackIs1': + case 'Columns': + case 'K': + case 'Rows': + case 'ImageMask': + case 'Interpolate': + case 'ca': + case 'CA': + case 'AIS': + case 'BM': + case 'ExtGState': + case 'Pattern': + case 'PatternType': + case 'CS': + case 'I': + case 'S': + case 'Coords': + case 'Extend': + case 'ShadingType': + case 'Bounds': + case 'Domain': + case 'Encode': + case 'FunctionType': + case 'Widths': + case 'FirstChar': + case 'LastChar': + if (primitive is PdfDictionary) { + primitive[token] = _parseDictionaryItems(value, primitive); + } + break; + default: + if (primitive is PdfDictionary) { + final PdfDictionary temp = PdfDictionary(); + primitive[token] = PdfReferenceHolder( + _parseDictionaryItems(value, temp), + ); + } + break; + } + } + } else if (elementValue is List && primitive is PdfArray) { + final List list = elementValue; + for (int i = 0; i < list.length; i++) { + final dynamic listObject = list[i]; + if (listObject is Map) { + listObject.forEach((String token, dynamic value) { + token = PdfFormHelper.decodeXMLConversion(token); + switch (token) { + case 'int': + final int? result = int.tryParse(value.toString()); + if (result != null) { + primitive.add(PdfNumber(result)); + } + break; + case 'fixed': + final num? result = num.tryParse(value.toString()); + if (result != null) { + primitive.add(PdfNumber(result)); + } + break; + case 'name': + primitive.add(PdfName(value.toString())); + break; + case 'string': + primitive.add(PdfString(value.toString())); + break; + case 'dict': + PdfDictionary pdfDictionary = PdfDictionary(); + pdfDictionary = + _parseDictionaryItems(value, pdfDictionary) + as PdfDictionary; + primitive.add(PdfReferenceHolder(pdfDictionary)); + break; + case 'array': + PdfArray array = PdfArray(); + array = _parseDictionaryItems(value, array) as PdfArray; + primitive.add(array); + break; + case 'boolean': + final bool test = value.toString().toLowerCase() == 'true'; + primitive.add(PdfBoolean(test)); + break; + case 'stream': + PdfStream stream = PdfStream(); + stream = _parseDictionaryItems(value, stream) as PdfStream; + primitive.add(stream); + break; + } + }); + } + } + } + } + return primitive; + } + + List? _getStreamData(Map element) { + List? rawData; + String encoding = ''; + for (final String token in element.keys) { + final dynamic value = element[token]; + switch (token) { + case 'encoding': + encoding = value.toString(); + break; + case 'bytes': + if (value != null) { + if (encoding == 'hex') { + rawData = getBytes(value.toString()); + } else if (encoding == 'ascii') { + rawData = utf8.encode(value.toString()); + } + } + return rawData; + } + } + return rawData; + } + + /// Internal Methods. + void addString(PdfDictionary dictionary, String key, String value) { + value = PdfFormHelper.decodeXMLConversion(value); + if (!isNullOrEmpty(value)) { + dictionary.setString(key, value); + } + } + + /// Internal Methods. + void addNumber(PdfDictionary dictionary, String key, String value) { + final num? number = num.tryParse(value); + if (number != null) { + dictionary.setNumber(key, number); + } + } + + /// Internal Methods. + void addBorderStyle( + String key, + dynamic value, + PdfDictionary borderEffectDictionary, + PdfDictionary borderStyleDictionary, + ) { + if (value is String) { + switch (value) { + case 'dash': + style = PdfDictionaryProperties.d; + break; + case 'solid': + style = PdfDictionaryProperties.s; + break; + case 'bevelled': + style = PdfDictionaryProperties.b; + break; + case 'inset': + style = PdfDictionaryProperties.i; + break; + case 'underline': + style = PdfDictionaryProperties.u; + break; + case 'cloudy': + style = PdfDictionaryProperties.c; + isBasicStyle = false; + break; + } + } + if (key == 'width') { + final double? width = double.tryParse(value); + if (width != null) { + borderStyleDictionary.setNumber(PdfDictionaryProperties.w, width); + } + } + if (key == 'intensity') { + final double? intensity = double.tryParse(value); + if (intensity != null) { + borderEffectDictionary.setNumber(PdfDictionaryProperties.i, intensity); + } + } + if (!isNullOrEmpty(style)) { + (isBasicStyle ? borderStyleDictionary : borderEffectDictionary).setName( + PdfDictionaryProperties.s, + style, + ); + } + if (key == 'dashes') { + final List dashPoints = _obtainFloatPoints(value.toString()); + if (dashPoints.isNotEmpty) { + borderStyleDictionary.setProperty( + PdfDictionaryProperties.d, + PdfArray(dashPoints), + ); + } + } + } + + /// Internal Methods. + PdfArray? getColorArray(String value) { + if (!value.contains(',')) { + final String hex = value.replaceAll('#', ''); + final int r = int.parse(hex.substring(0, 2), radix: 16); + final int g = int.parse(hex.substring(2, 4), radix: 16); + final int b = int.parse(hex.substring(4, hex.length), radix: 16); + if (r >= 0 && g >= 0 && b >= 0) { + final PdfArray colorArray = PdfArray(); + colorArray.add(PdfNumber(r / 255)); + colorArray.add(PdfNumber(g / 255)); + colorArray.add(PdfNumber(b / 255)); + return colorArray; + } + } else { + final List colorValues = value.split(','); + final num? r = num.tryParse(colorValues[0]); + final num? g = num.tryParse(colorValues[1]); + final num? b = num.tryParse(colorValues[2]); + if (r != null && g != null && b != null) { + final PdfArray colorArray = PdfArray(); + colorArray.add(PdfNumber(r)); + colorArray.add(PdfNumber(g)); + colorArray.add(PdfNumber(b)); + return colorArray; + } + } + return null; + } + + /// Internal Methods. + void addFloatPoints(PdfDictionary dictionary, List? points, String key) { + if (points != null && points.isNotEmpty) { + dictionary.setProperty(key, PdfArray(points)); + } + } + + /// Internal Methods. + String trimEscapeCharacters(String value) { + if (value.contains(r'\\r')) { + value = value.replaceAll(r'\\r', '\r'); + } + if (value.contains(r'\\n')) { + value = value.replaceAll(r'\\n', '\n'); + } + if (value.contains(r'\\\"')) { + value = value.replaceAll(r'\\\"', '"'); + } + return value; + } + + /// Internal Methods. + void addFloatPointsToCollection(List collection, String value) { + final num? number = num.tryParse(value); + if (number != null) { + collection.add(number); + } + } + + /// Internal Methods. + void addStreamData( + Map dataValues, + PdfDictionary annotDictionary, + String values, + ) { + if (annotDictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.subtype], + ); + if (primitive != null && primitive is PdfName && primitive.name != null) { + final String subtype = primitive.name!; + final List raw = List.from(hex.decode(values)); + if (raw.isNotEmpty) { + if (subtype.toLowerCase() == 'sound') { + final PdfStream soundStream = PdfStream(); + soundStream.setName( + PdfDictionaryProperties.type, + PdfDictionaryProperties.sound, + ); + dataValues.forEach((String key, String value) { + switch (key) { + case 'bits': + if (!isNullOrEmpty(value)) { + addNumber(soundStream, PdfDictionaryProperties.b, value); + } + break; + case 'rate': + if (!isNullOrEmpty(value)) { + addNumber(soundStream, PdfDictionaryProperties.r, value); + } + break; + case 'channels': + if (!isNullOrEmpty(value)) { + addNumber(soundStream, PdfDictionaryProperties.c, value); + } + break; + case 'encoding': + if (!isNullOrEmpty(value)) { + soundStream.setName(PdfDictionaryProperties.e, value); + } + break; + case 'filter': + soundStream.addFilter(PdfDictionaryProperties.flateDecode); + break; + } + }); + soundStream.data = raw; + annotDictionary.setProperty( + PdfDictionaryProperties.sound, + PdfReferenceHolder(soundStream), + ); + } else if (subtype.toLowerCase() == 'fileattachment') { + final PdfDictionary fileDictionary = PdfDictionary(); + final PdfStream fileStream = PdfStream(); + final PdfDictionary param = PdfDictionary(); + fileDictionary.setName( + PdfDictionaryProperties.type, + PdfDictionaryProperties.filespec, + ); + dataValues.forEach((String key, String value) { + switch (key) { + case 'file': + addString(fileDictionary, PdfDictionaryProperties.f, value); + addString(fileDictionary, PdfDictionaryProperties.uf, value); + break; + case 'size': + final int? size = int.tryParse(value); + if (size != null) { + param.setNumber(PdfDictionaryProperties.size, size); + fileStream.setNumber('DL', size); + } + break; + case 'creation': + addString( + param, + 'creation', + PdfDictionaryProperties.creationDate, + ); + break; + case 'modification': + addString( + param, + 'modification', + PdfDictionaryProperties.modificationDate, + ); + break; + } + }); + fileStream.setProperty(PdfDictionaryProperties.params, param); + fileStream.data = raw; + final PdfDictionary embeddedFile = PdfDictionary(); + embeddedFile.setProperty( + PdfDictionaryProperties.f, + PdfReferenceHolder(fileStream), + ); + fileDictionary.setProperty( + PdfDictionaryProperties.ef, + embeddedFile, + ); + annotDictionary.setProperty( + PdfDictionaryProperties.fs, + PdfReferenceHolder(fileDictionary), + ); + } + } + } + } + } + + /// Internal Methods. + List getBytes(String hex) { + final PdfString pdfString = PdfString(''); + return pdfString.hexToBytes(hex); + } + + List _obtainFloatPoints(dynamic points) { + final List pointsValue = points + .toString() + .replaceAll(RegExp(r'[\[\]{}:]'), '') + .split(','); + final List linePoints = []; + for (final dynamic value in pointsValue) { + if (value is String && !isNullOrEmpty(value)) { + final num? number = num.tryParse(value); + if (number != null) { + linePoints.add(number); + } + } + } + return linePoints; + } +} + +/// Internal method. +bool isNullOrEmpty(String? value) { + return value == null || value.isEmpty; +} + +/// Internal method. +String getEnumName(dynamic text) { + final int index = text.toString().indexOf('.'); + final String name = text.toString().substring(index + 1); + return name[0].toUpperCase() + name.substring(1); +} 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 6f613c3c7..27dd4d36d 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 @@ -2,12 +2,14 @@ import 'dart:ui'; import '../../interfaces/pdf_interface.dart'; import '../actions/pdf_action.dart'; +import '../graphics/pdf_color.dart'; import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../primitives/pdf_dictionary.dart'; import '../primitives/pdf_name.dart'; import 'enum.dart'; import 'pdf_annotation.dart'; +import 'pdf_annotation_border.dart'; /// Represents the base class for the link annotations. abstract class PdfLinkAnnotation extends PdfAnnotation implements IPdfWrapper { @@ -26,7 +28,7 @@ abstract class PdfLinkAnnotation extends PdfAnnotation implements IPdfWrapper { /// PdfDestination(document.pages.add(), Offset(10, 0))) /// ..highlightMode = PdfHighlightMode.outline); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -37,9 +39,9 @@ abstract class PdfLinkAnnotation extends PdfAnnotation implements IPdfWrapper { set highlightMode(PdfHighlightMode value) { _highlightMode = value; final String mode = _getHighlightMode(_highlightMode); - PdfAnnotationHelper.getHelper(this) - .dictionary! - .setName(PdfName(PdfDictionaryProperties.h), mode); + PdfAnnotationHelper.getHelper( + this, + ).dictionary!.setName(PdfName(PdfDictionaryProperties.h), mode); } String _getHighlightMode(PdfHighlightMode mode) { @@ -63,11 +65,14 @@ abstract class PdfLinkAnnotation extends PdfAnnotation implements IPdfWrapper { PdfHighlightMode _obtainHighlightMode() { PdfHighlightMode mode = PdfHighlightMode.noHighlighting; - if (PdfAnnotationHelper.getHelper(this) - .dictionary! - .containsKey(PdfDictionaryProperties.h)) { - final PdfName name = PdfAnnotationHelper.getHelper(this) - .dictionary![PdfDictionaryProperties.h]! as PdfName; + if (PdfAnnotationHelper.getHelper( + this, + ).dictionary!.containsKey(PdfDictionaryProperties.h)) { + final PdfName name = + PdfAnnotationHelper.getHelper( + this, + ).dictionary![PdfDictionaryProperties.h]! + as PdfName; switch (name.name) { case 'I': mode = PdfHighlightMode.invert; @@ -90,17 +95,23 @@ abstract class PdfLinkAnnotation extends PdfAnnotation implements IPdfWrapper { /// [PdfLinkAnnotation] helper class PdfLinkAnnotationHelper extends PdfAnnotationHelper { /// internal constructor - PdfLinkAnnotationHelper(PdfLinkAnnotation linkAnnotation, Rect? bounds) - : super(linkAnnotation) { + PdfLinkAnnotationHelper( + PdfLinkAnnotation super.linkAnnotation, + Rect? bounds, + ) { initializeAnnotation(bounds: bounds); - dictionary!.setProperty(PdfName(PdfDictionaryProperties.subtype), - PdfName(PdfDictionaryProperties.link)); + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.subtype), + PdfName(PdfDictionaryProperties.link), + ); } /// internal constructor - PdfLinkAnnotationHelper.load(PdfLinkAnnotation linkAnnotation, - PdfDictionary dictionary, PdfCrossTable crossTable) - : super(linkAnnotation) { + PdfLinkAnnotationHelper.load( + PdfLinkAnnotation super.linkAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { initializeExistingAnnotation(dictionary, crossTable); } } @@ -121,9 +132,10 @@ abstract class PdfActionLinkAnnotation extends PdfLinkAnnotation { class PdfActionLinkAnnotationHelper extends PdfLinkAnnotationHelper { /// internal constructor PdfActionLinkAnnotationHelper( - PdfActionLinkAnnotation actionLinkAnnotation, Rect bounds, - [PdfAction? action]) - : super(actionLinkAnnotation, bounds) { + PdfActionLinkAnnotation actionLinkAnnotation, + Rect bounds, [ + PdfAction? action, + ]) : super(actionLinkAnnotation, bounds) { if (action != null) { actionLinkAnnotation._action = action; } @@ -131,10 +143,10 @@ class PdfActionLinkAnnotationHelper extends PdfLinkAnnotationHelper { /// internal constructor PdfActionLinkAnnotationHelper.load( - PdfActionLinkAnnotation actionLinkAnnotation, - PdfDictionary dictionary, - PdfCrossTable crossTable) - : super.load(actionLinkAnnotation, dictionary, crossTable); + PdfActionLinkAnnotation super.actionLinkAnnotation, + super.dictionary, + super.crossTable, + ) : super.load(); } /// Represents the annotation with associated action. @@ -146,14 +158,34 @@ class PdfActionAnnotation extends PdfActionLinkAnnotation { _helper = PdfActionAnnotationHelper(this, bounds, action); } late PdfActionAnnotationHelper _helper; + + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } } /// [PdfActionAnnotation] helper class PdfActionAnnotationHelper extends PdfActionLinkAnnotationHelper { /// internal method PdfActionAnnotationHelper( - this.actionAnnotation, Rect bounds, PdfAction action) - : super(actionAnnotation, bounds, action); + this.actionAnnotation, + Rect bounds, + PdfAction action, + ) : super(actionAnnotation, bounds, action); /// internal method PdfActionAnnotation actionAnnotation; @@ -165,8 +197,10 @@ class PdfActionAnnotationHelper extends PdfActionLinkAnnotationHelper { /// internal method void save() { - dictionary!.setProperty(PdfName(PdfDictionaryProperties.a), - IPdfWrapper.getElement(actionAnnotation.action!)); + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.a), + IPdfWrapper.getElement(actionAnnotation.action!), + ); } /// internal method 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 1e4f68843..05a0f77df 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 @@ -39,7 +39,10 @@ import 'pdf_document_link_annotation.dart'; import 'pdf_ellipse_annotation.dart'; import 'pdf_line_annotation.dart'; import 'pdf_polygon_annotation.dart'; +import 'pdf_popup_annotation.dart'; import 'pdf_rectangle_annotation.dart'; +import 'pdf_text_markup_annotation.dart'; +import 'pdf_text_web_link.dart'; import 'widget_annotation.dart'; /// Represents the base class for annotation objects. @@ -47,7 +50,7 @@ abstract class PdfAnnotation implements IPdfWrapper { // fields late PdfAnnotationHelper _helper; - /// Gets or sets whether the annotation needs appearance. + /// Set whether the annotation requires an appearance. bool get setAppearance => _helper.setAppearance; set setAppearance(bool value) { _helper.setAppearance = value; @@ -67,16 +70,6 @@ abstract class PdfAnnotation implements IPdfWrapper { _helper.bounds = value; } - /// Gets annotation's border properties like width, horizontal radius etc. - PdfAnnotationBorder get border { - return _helper.border; - } - - /// Sets annotation's border properties like width, horizontal radius etc. - set border(PdfAnnotationBorder value) { - _helper.border = value; - } - /// Gets content of the annotation. /// The string value specifies the text of the annotation. String get text { @@ -89,22 +82,6 @@ abstract class PdfAnnotation implements IPdfWrapper { _helper.text = value; } - /// Gets the annotation color. - PdfColor get color => _helper.color; - - /// Sets the annotation color. - set color(PdfColor value) { - _helper.color = value; - } - - /// Gets the inner color of the annotation. - PdfColor get innerColor => _helper.innerColor; - - /// Sets the inner color of the annotation. - set innerColor(PdfColor value) { - _helper.innerColor = value; - } - /// Gets the author of the annotation. String get author => _helper.author; @@ -150,6 +127,12 @@ abstract class PdfAnnotation implements IPdfWrapper { _helper.appearance = value; } + /// Gets or sets the annotation flags. + List get annotationFlags => _helper.annotationFlags; + set annotationFlags(List value) { + _helper.annotationFlags = value; + } + //Public methods /// Flatten the annotation. /// @@ -174,41 +157,63 @@ class PdfAnnotationHelper { /// internal method /// Initialize [PdfAnnotation] object - void initializeAnnotation( - {PdfPage? page, - String? text, - Rect? bounds, - PdfAnnotationBorder? border, - PdfColor? color, - PdfColor? innerColor, - String? author, - double? opacity, - String? subject, - DateTime? modifiedDate, - bool? setAppearance}) { + void initializeAnnotation({ + PdfPage? page, + String? text, + Rect? bounds, + PdfAnnotationBorder? border, + PdfColor? color, + PdfColor? innerColor, + String? author, + double? opacity, + String? subject, + DateTime? modifiedDate, + List? flags, + bool? setAppearance, + }) { base._helper = this; - initializeAnnotationProperties(page, text, bounds, border, color, - innerColor, author, opacity, subject, modifiedDate, setAppearance); + initializeAnnotationProperties( + page, + text, + bounds, + border, + color, + innerColor, + author, + opacity, + subject, + modifiedDate, + flags, + setAppearance, + ); } /// internal method /// Initialize [PdfAnnotation] object void initializeExistingAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { base._helper = this; this.dictionary = dictionary; this.crossTable = crossTable; isLoadedAnnotation = true; PdfName? name; if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { - name = dictionary.items![PdfName(PdfDictionaryProperties.subtype)] - as PdfName?; + name = + dictionary.items![PdfName(PdfDictionaryProperties.subtype)] + as PdfName?; } if (name != null) { if (name.name == PdfDictionaryProperties.circle || name.name == PdfDictionaryProperties.square || name.name == PdfDictionaryProperties.line || - name.name == PdfDictionaryProperties.polygon) { + name.name == PdfDictionaryProperties.polygon || + name.name == PdfDictionaryProperties.highlight || + name.name == PdfDictionaryProperties.underline || + name.name == PdfDictionaryProperties.squiggly || + name.name == PdfDictionaryProperties.strikeOut || + name.name == PdfDictionaryProperties.text) { PdfDocumentHelper.getHelper(crossTable.document!).catalog.beginSave = dictionaryBeginSave; PdfDocumentHelper.getHelper(crossTable.document!).catalog.modify(); @@ -270,6 +275,12 @@ class PdfAnnotationHelper { /// internal field bool setAppearance = false; + /// internal field + bool isOldAnnotation = false; + + /// internal field + List? flag; + ///Gets or sets the boolean flag to flatten the annotation, ///by default, its become false. bool flatten = false; @@ -298,11 +309,12 @@ class PdfAnnotationHelper { return rectangle.rect; } else { final PdfRectangle 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) - : rect.y - rect.height; + rect.y = + page != null + ? rect.y == 0 && rect.height == 0 + ? rect.y + rect.height + : page!.size.height - (rect.y + rect.height) + : rect.y - rect.height; return rect.rect; } } @@ -313,12 +325,14 @@ class PdfAnnotationHelper { final PdfRectangle rect = PdfRectangle.fromRect(value); if (rectangle != rect) { rectangle = rect; - dictionary!.setProperty(PdfName(PdfDictionaryProperties.rect), - PdfArray.fromRectangle(rect)); + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.rect), + PdfArray.fromRectangle(rect), + ); } } else { isBounds = true; - if (value.isEmpty) { + if (value == Rect.zero) { throw ArgumentError('rectangle'); } final double height = page!.size.height; @@ -326,7 +340,7 @@ class PdfAnnotationHelper { PdfNumber(value.left), PdfNumber(height - (value.top + value.height)), PdfNumber(value.left + value.width), - PdfNumber(height - value.top) + PdfNumber(height - value.top), ]; final PdfDictionary dic = dictionary!; dic.setArray(PdfDictionaryProperties.rect, values); @@ -340,8 +354,10 @@ class PdfAnnotationHelper { } else { annotationBorder ??= _obtainBorder(); if (!isLineBorder()) { - dictionary! - .setProperty(PdfDictionaryProperties.border, annotationBorder); + dictionary!.setProperty( + PdfDictionaryProperties.border, + annotationBorder, + ); } } PdfAnnotationBorderHelper.getHelper(annotationBorder!).isLineBorder = @@ -356,7 +372,9 @@ class PdfAnnotationHelper { dictionary!.setProperty(PdfName(PdfDictionaryProperties.bs), border); } else { dictionary!.setProperty( - PdfName(PdfDictionaryProperties.border), annotationBorder); + PdfName(PdfDictionaryProperties.border), + annotationBorder, + ); } } @@ -394,11 +412,12 @@ class PdfAnnotationHelper { annotationColor = value; PdfColorSpace? cs = PdfColorSpace.rgb; if (page != null && !PdfPageHelper.getHelper(page!).isLoadedPage) { - cs = PdfSectionCollectionHelper.getHelper(PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(page!).section!) - .parent!) - .document! - .colorSpace; + cs = + PdfSectionCollectionHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page!).section!, + ).parent!, + ).document!.colorSpace; } final PdfArray colours = PdfColorHelper.toArray(annotationColor, cs); dictionary!.setProperty(PdfDictionaryProperties.c, colours); @@ -420,8 +439,10 @@ class PdfAnnotationHelper { annotationInnerColor = value; if (isLoadedAnnotation) { if (PdfColorHelper.getHelper(annotationInnerColor!).alpha != 0) { - dictionary!.setProperty(PdfDictionaryProperties.iC, - PdfColorHelper.toArray(annotationInnerColor!, PdfColorSpace.rgb)); + dictionary!.setProperty( + PdfDictionaryProperties.iC, + PdfColorHelper.toArray(annotationInnerColor!), + ); } else if (dictionary!.containsKey(PdfDictionaryProperties.iC)) { dictionary!.remove(PdfDictionaryProperties.iC); } @@ -484,8 +505,10 @@ class PdfAnnotationHelper { set modifiedDate(DateTime? value) { if (annotationModifiedDate != value) { annotationModifiedDate = value; - dictionary! - .setDateTime(PdfDictionaryProperties.m, annotationModifiedDate!); + dictionary!.setDateTime( + PdfDictionaryProperties.m, + annotationModifiedDate!, + ); } } @@ -511,10 +534,25 @@ class PdfAnnotationHelper { if (annotationOpacity != value) { annotationOpacity = value; dictionary!.setProperty( - PdfDictionaryProperties.ca, PdfNumber(annotationOpacity)); + PdfDictionaryProperties.ca, + PdfNumber(annotationOpacity), + ); } } + /// Sets the annotation flags. + set annotationFlags(List value) { + flag = value; + } + + /// Gets the annotation flags. + List get annotationFlags { + if (isLoadedAnnotation && flag == null) { + flag ??= obtainAnnotationFlags(getFlagValue()); + } + return flag ??= []; + } + /// internal method bool isLineBorder() { if (base is PdfRectangleAnnotation || @@ -529,20 +567,24 @@ class PdfAnnotationHelper { /// internal method void initializeAnnotationProperties( - PdfPage? page, - String? annotText, - Rect? bounds, - PdfAnnotationBorder? border, - PdfColor? color, - PdfColor? innerColor, - String? author, - double? opacity, - String? subject, - DateTime? modifiedDate, - bool? setAppearance) { + PdfPage? page, + String? annotText, + Rect? bounds, + PdfAnnotationBorder? border, + PdfColor? color, + PdfColor? innerColor, + String? author, + double? opacity, + String? subject, + DateTime? modifiedDate, + List? flags, + bool? setAppearance, + ) { dictionary!.beginSave = dictionaryBeginSave; - dictionary!.setProperty(PdfName(PdfDictionaryProperties.type), - PdfName(PdfDictionaryProperties.annot)); + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.type), + PdfName(PdfDictionaryProperties.annot), + ); if (page != null) { this.page = page; } @@ -552,7 +594,9 @@ class PdfAnnotationHelper { if (annotText != null) { text = annotText; dictionary!.setProperty( - PdfName(PdfDictionaryProperties.contents), PdfString(text)); + PdfName(PdfDictionaryProperties.contents), + PdfString(text), + ); } if (border != null) { this.border = border; @@ -578,6 +622,9 @@ class PdfAnnotationHelper { if (setAppearance != null) { this.setAppearance = setAppearance; } + if (flags != null) { + annotationFlags = flags; + } } /// internal method @@ -594,11 +641,16 @@ class PdfAnnotationHelper { bool contains = false; PdfArray? annotation; if (page != null && - PdfPageHelper.getHelper(page!) - .dictionary! - .containsKey(PdfDictionaryProperties.annots)) { - annotation = PdfCrossTable.dereference(PdfPageHelper.getHelper(page!) - .dictionary![PdfDictionaryProperties.annots]) as PdfArray?; + PdfPageHelper.getHelper( + page!, + ).dictionary!.containsKey(PdfDictionaryProperties.annots)) { + annotation = + PdfCrossTable.dereference( + PdfPageHelper.getHelper( + page!, + ).dictionary![PdfDictionaryProperties.annots], + ) + as PdfArray?; if (annotation != null && annotation.elements.isNotEmpty && annotation.contains(annotation.elements[0]!)) { @@ -618,7 +670,8 @@ class PdfAnnotationHelper { PdfDocumentHelper.getHelper(document).conformanceLevel == PdfConformanceLevel.a1b) { throw ArgumentError( - 'The specified annotation type is not supported by PDF/A1-B or PDF/A1-A standard documents.'); + '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 (base is! PdfLinkAnnotation && @@ -628,7 +681,8 @@ class PdfAnnotationHelper { PdfDocumentHelper.getHelper(document).conformanceLevel == PdfConformanceLevel.a3b)) { throw ArgumentError( - "The appearance dictionary doesn't contain an entry. Enable setAppearance in PdfAnnotation class to overcome this error."); + "The appearance dictionary doesn't contain an entry. Enable setAppearance in PdfAnnotation class to overcome this error.", + ); } dictionary!.setNumber(PdfDictionaryProperties.f, 4); } @@ -636,31 +690,46 @@ class PdfAnnotationHelper { if (isLineBorder()) { dictionary!.setProperty(PdfDictionaryProperties.bs, border); } else { - dictionary! - .setProperty(PdfName(PdfDictionaryProperties.border), border); + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.border), + border, + ); } } - final PdfRectangle nativeRectangle = _obtainNativeRectangle(); - if (annotationInnerColor != null && - !annotationInnerColor!.isEmpty && - PdfColorHelper.getHelper(annotationInnerColor!).alpha != 0.0) { - dictionary!.setProperty(PdfName(PdfDictionaryProperties.ic), - PdfColorHelper.toArray(annotationInnerColor!, PdfColorSpace.rgb)); + if ((base is! PdfLinkAnnotation && base is! PdfTextWebLink) || + ((base is PdfLinkAnnotation || base is PdfTextWebLink) && + !isLoadedAnnotation)) { + final PdfRectangle nativeRectangle = _obtainNativeRectangle(); + if (annotationInnerColor != null && + !annotationInnerColor!.isEmpty && + PdfColorHelper.getHelper(annotationInnerColor!).alpha != 0.0) { + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.ic), + PdfColorHelper.toArray(annotationInnerColor!), + ); + } + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.rect), + PdfArray.fromRectangle(nativeRectangle), + ); } - dictionary!.setProperty(PdfName(PdfDictionaryProperties.rect), - PdfArray.fromRectangle(nativeRectangle)); } PdfRectangle _obtainNativeRectangle() { - final PdfRectangle nativeRectangle = - PdfRectangle(bounds.left, bounds.bottom, bounds.width, bounds.height); + final PdfRectangle nativeRectangle = PdfRectangle( + bounds.left, + bounds.bottom, + bounds.width, + bounds.height, + ); Size? size; PdfArray? cropOrMediaBox; if (page != null) { if (!PdfPageHelper.getHelper(page!).isLoadedPage) { final PdfSection section = PdfPageHelper.getHelper(page!).section!; - nativeRectangle.location = PdfSectionHelper.getHelper(section) - .pointToNativePdf(page!, nativeRectangle.location); + nativeRectangle.location = PdfSectionHelper.getHelper( + section, + ).pointToNativePdf(page!, nativeRectangle.location); } else { size = page!.size; nativeRectangle.y = size.height - rectangle.bottom; @@ -671,9 +740,11 @@ class PdfAnnotationHelper { if (cropOrMediaBox.count > 2) { if ((cropOrMediaBox[0]! as PdfNumber).value != 0 || (cropOrMediaBox[1]! as PdfNumber).value != 0) { - nativeRectangle.x = nativeRectangle.x + + nativeRectangle.x = + nativeRectangle.x + (cropOrMediaBox[0]! as PdfNumber).value!.toDouble(); - nativeRectangle.y = nativeRectangle.y + + nativeRectangle.y = + nativeRectangle.y + (cropOrMediaBox[1]! as PdfNumber).value!.toDouble(); } } @@ -688,8 +759,11 @@ class PdfAnnotationHelper { PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.cropBox]) as PdfArray?; } else if (dictionary.containsKey(PdfDictionaryProperties.mediaBox)) { - cropOrMediaBox = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.mediaBox]) as PdfArray?; + cropOrMediaBox = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.mediaBox], + ) + as PdfArray?; } return cropOrMediaBox; } @@ -707,11 +781,16 @@ class PdfAnnotationHelper { pdfPage.graphics; //Accessed for creating page content. ArgumentError.checkNotNull(graphics); if (dictionary!.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName? name = dictionary! - .items![PdfName(PdfDictionaryProperties.subtype)] as PdfName?; + final PdfName? name = + dictionary!.items![PdfName(PdfDictionaryProperties.subtype)] + as PdfName?; if (name != null) { if (name.name == PdfDictionaryProperties.text || name.name == PdfDictionaryProperties.square || + name.name == PdfDictionaryProperties.highlight || + name.name == PdfDictionaryProperties.squiggly || + name.name == PdfDictionaryProperties.underline || + name.name == PdfDictionaryProperties.strikeOut || flatten) { catalog.beginSaveList!.add(dictionaryBeginSave); catalog.modify(); @@ -727,14 +806,21 @@ class PdfAnnotationHelper { final PdfCatalog catalog = PdfDocumentHelper.getHelper(document).catalog; if (dictionary!.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName? name = dictionary! - .items![PdfName(PdfDictionaryProperties.subtype)] as PdfName?; + final PdfName? name = + dictionary!.items![PdfName(PdfDictionaryProperties.subtype)] + as PdfName?; catalog.beginSaveList ??= []; if (name != null) { if (name.name == PdfDictionaryProperties.circle || name.name == PdfDictionaryProperties.square || name.name == PdfDictionaryProperties.line || - name.name == PdfDictionaryProperties.polygon) { + name.name == PdfDictionaryProperties.polygon || + name.name == PdfDictionaryProperties.highlight || + name.name == PdfDictionaryProperties.squiggly || + name.name == PdfDictionaryProperties.underline || + name.name == PdfDictionaryProperties.strikeOut || + name.name == PdfDictionaryProperties.text || + name.name == PdfDictionaryProperties.link) { catalog.beginSaveList!.add(dictionaryBeginSave); catalog.modify(); } @@ -747,7 +833,9 @@ class PdfAnnotationHelper { } if (page != null && !PdfPageHelper.getHelper(page!).isLoadedPage) { dictionary!.setProperty( - PdfName(PdfDictionaryProperties.p), PdfReferenceHolder(page)); + PdfName(PdfDictionaryProperties.p), + PdfReferenceHolder(page), + ); } } @@ -755,8 +843,9 @@ class PdfAnnotationHelper { PdfRectangle _getBounds(PdfDictionary dictionary, PdfCrossTable crossTable) { PdfArray? array; if (dictionary.containsKey(PdfDictionaryProperties.rect)) { - array = crossTable.getObject(dictionary[PdfDictionaryProperties.rect]) - as PdfArray?; + array = + crossTable.getObject(dictionary[PdfDictionaryProperties.rect]) + as PdfArray?; } return array!.toRectangle(); } @@ -783,8 +872,9 @@ class PdfAnnotationHelper { } } } else if (dictionary!.containsKey(PdfDictionaryProperties.bs)) { - final PdfDictionary lbDic = crossTable - .getObject(dictionary![PdfDictionaryProperties.bs])! as PdfDictionary; + final PdfDictionary lbDic = + crossTable.getObject(dictionary![PdfDictionaryProperties.bs])! + as PdfDictionary; if (lbDic.containsKey(PdfDictionaryProperties.w)) { final PdfNumber? value = lbDic[PdfDictionaryProperties.w] as PdfNumber?; if (value != null) { @@ -798,16 +888,16 @@ class PdfAnnotationHelper { border.borderStyle = _getBorderStyle(bstr.name.toString()); } if (lbDic.containsKey(PdfDictionaryProperties.d)) { - final PdfArray? _dasharray = + final PdfArray? dasharray = PdfCrossTable.dereference(lbDic[PdfDictionaryProperties.d]) as PdfArray?; - if (_dasharray != null) { - final PdfNumber dashArray = _dasharray[0]! as PdfNumber; - final int _dashArray = dashArray.value!.toInt(); - _dasharray.clear(); - _dasharray.insert(0, PdfNumber(_dashArray)); - _dasharray.insert(1, PdfNumber(_dashArray)); - border.dashArray = _dashArray; + if (dasharray != null) { + final PdfNumber dashArray = dasharray[0]! as PdfNumber; + final int dashArrayValue = dashArray.value!.toInt(); + dasharray.clear(); + dasharray.insert(0, PdfNumber(dashArrayValue)); + dasharray.insert(1, PdfNumber(dashArrayValue)); + border.dashArray = dashArrayValue; } } } @@ -818,8 +908,11 @@ class PdfAnnotationHelper { String? _obtainText() { String tempText; if (dictionary!.containsKey(PdfDictionaryProperties.contents)) { - final PdfString? mText = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.contents]) as PdfString?; + final PdfString? mText = + PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.contents], + ) + as PdfString?; if (mText != null) { textValue = mText.value.toString(); } @@ -891,41 +984,50 @@ class PdfAnnotationHelper { } // Gets the Author. - String? _obtainAuthor() { - String? author; + String _obtainAuthor() { + String author = ''; if (dictionary!.containsKey(PdfDictionaryProperties.author)) { - final PdfString? tempAuthor = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.author]) - as PdfString?; - if (tempAuthor != null) { - author = tempAuthor.value; + final IPdfPrimitive? tempAuthor = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.author], + ); + if (tempAuthor != null && + tempAuthor is PdfString && + tempAuthor.value != null) { + author = tempAuthor.value!; } } else if (dictionary!.containsKey(PdfDictionaryProperties.t)) { - final PdfString? tempAuthor = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.t]) - as PdfString?; - if (tempAuthor != null) { - author = tempAuthor.value; + final IPdfPrimitive? tempAuthor = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.t], + ); + if (tempAuthor != null && + tempAuthor is PdfString && + tempAuthor.value != null) { + author = tempAuthor.value!; } } return author; } // Gets the Subject. - String? _obtainSubject() { - String? subject; + String _obtainSubject() { + String subject = ''; if (dictionary!.containsKey(PdfDictionaryProperties.subject)) { - final PdfString? tempSubject = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.subject]) as PdfString?; - if (tempSubject != null) { - subject = tempSubject.value; + final IPdfPrimitive? tempSubject = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.subject], + ); + if (tempSubject != null && + tempSubject is PdfString && + tempSubject.value != null) { + subject = tempSubject.value!; } } else if (dictionary!.containsKey(PdfDictionaryProperties.subj)) { - final PdfString? tempSubject = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.subj]) - as PdfString?; - if (tempSubject != null) { - subject = tempSubject.value; + final IPdfPrimitive? tempSubject = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.subj], + ); + if (tempSubject != null && + tempSubject is PdfString && + tempSubject.value != null) { + subject = tempSubject.value!; } } return subject; @@ -959,12 +1061,15 @@ class PdfAnnotationHelper { PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.iC]) as PdfArray?; if (colours != null && colours.count > 0) { - final int red = - ((colours[0]! as PdfNumber).value! * 255).round().toUnsigned(8); - final int green = - ((colours[1]! as PdfNumber).value! * 255).round().toUnsigned(8); - final int blue = - ((colours[2]! as PdfNumber).value! * 255).round().toUnsigned(8); + final int red = ((colours[0]! as PdfNumber).value! * 255) + .round() + .toUnsigned(8); + final int green = ((colours[1]! as PdfNumber).value! * 255) + .round() + .toUnsigned(8); + final int blue = ((colours[2]! as PdfNumber).value! * 255) + .round() + .toUnsigned(8); color = PdfColor(red, green, blue); } } @@ -983,19 +1088,35 @@ class PdfAnnotationHelper { void flattenPopup() { if (page != null && !isLoadedAnnotation) { _flattenAnnotationPopups( - page!, color, bounds, border, author, subject, text); - } - } - - void _flattenAnnotationPopups(PdfPage page, PdfColor color, Rect annotBounds, - PdfAnnotationBorder border, String author, String subject, String text) { - final Size clientSize = PdfPageHelper.getHelper(page).isLoadedPage - ? page.size - : page.getClientSize(); + page!, + color, + bounds, + border, + author, + subject, + text, + ); + } + } + + void _flattenAnnotationPopups( + PdfPage page, + PdfColor color, + Rect annotBounds, + PdfAnnotationBorder border, + String author, + String subject, + String text, + ) { + final Size clientSize = + PdfPageHelper.getHelper(page).isLoadedPage + ? page.size + : page.getClientSize(); final double x = clientSize.width - 180; - final double y = (annotBounds.top + 142) < clientSize.height - ? annotBounds.top - : clientSize.height - 142; + final double y = + (annotBounds.top + 142) < clientSize.height + ? annotBounds.top + : clientSize.height - 142; Rect bounds = Rect.fromLTWH(x, y, 180, 142); // Draw annotation based on bounds if (dictionary![PdfDictionaryProperties.popup] != null) { @@ -1003,8 +1124,11 @@ class PdfAnnotationHelper { final PdfDictionary? tempDictionary = PdfCrossTable.dereference(obj) as PdfDictionary?; if (tempDictionary != null) { - final PdfArray? rectValue = PdfCrossTable.dereference( - tempDictionary[PdfDictionaryProperties.rect]) as PdfArray?; + final PdfArray? rectValue = + PdfCrossTable.dereference( + tempDictionary[PdfDictionaryProperties.rect], + ) + as PdfArray?; final PdfCrossTable? crosstable = PdfPageHelper.getHelper(page).crossTable; if (rectValue != null) { @@ -1017,10 +1141,11 @@ class PdfAnnotationHelper { final PdfNumber height = crosstable.getReference(rectValue[3]) as PdfNumber; bounds = Rect.fromLTWH( - left.value! as double, - top.value! as double, - width.value! - (left.value! as double), - height.value! - (top.value! as double)); + left.value! as double, + top.value! as double, + width.value! - (left.value! as double), + height.value! - (top.value! as double), + ); } } } @@ -1029,134 +1154,192 @@ class PdfAnnotationHelper { double? trackingHeight = 0; final PdfBrush aBrush = PdfSolidBrush(_getForeColor(color)); if (author != '') { - final Map returnedValue = _drawAuthor(author, subject, - bounds, backBrush, aBrush, page, trackingHeight, border); + final Map returnedValue = _drawAuthor( + author, + subject, + bounds, + backBrush, + aBrush, + page, + trackingHeight, + border, + ); trackingHeight = returnedValue['height']; } else if (subject != '') { - final Rect titleRect = Rect.fromLTWH(bounds.left + borderWidth, - bounds.top + borderWidth, bounds.width - border.width, 40); + final Rect titleRect = Rect.fromLTWH( + bounds.left + borderWidth, + bounds.top + borderWidth, + bounds.width - border.width, + 40, + ); _saveGraphics(page, PdfBlendMode.hardLight); page.graphics.drawRectangle( - pen: PdfPens.black, brush: backBrush, bounds: titleRect); + pen: PdfPens.black, + brush: backBrush, + bounds: titleRect, + ); page.graphics.restore(); - Rect contentRect = Rect.fromLTWH(titleRect.left + 11, titleRect.top, - titleRect.width, titleRect.height / 2); + Rect contentRect = Rect.fromLTWH( + titleRect.left + 11, + titleRect.top, + titleRect.width, + titleRect.height / 2, + ); contentRect = Rect.fromLTWH( - contentRect.left, - contentRect.top + contentRect.height - 2, - contentRect.width, - titleRect.height / 2); + contentRect.left, + contentRect.top + contentRect.height - 2, + contentRect.width, + titleRect.height / 2, + ); _saveGraphics(page, PdfBlendMode.normal); _drawSubject(subject, contentRect, page); page.graphics.restore(); trackingHeight = 40; } else { _saveGraphics(page, PdfBlendMode.hardLight); - final Rect titleRect = Rect.fromLTWH(bounds.left + borderWidth, - bounds.top + borderWidth, bounds.width - border.width, 20); + final Rect titleRect = Rect.fromLTWH( + bounds.left + borderWidth, + bounds.top + borderWidth, + bounds.width - border.width, + 20, + ); page.graphics.drawRectangle( - pen: PdfPens.black, brush: backBrush, bounds: titleRect); + pen: PdfPens.black, + brush: backBrush, + bounds: titleRect, + ); trackingHeight = 20; page.graphics.restore(); } Rect cRect = Rect.fromLTWH( - bounds.left + borderWidth, - bounds.top + borderWidth + trackingHeight!, - bounds.width - border.width, - bounds.height - (trackingHeight + border.width)); + bounds.left + borderWidth, + bounds.top + borderWidth + trackingHeight!, + bounds.width - border.width, + bounds.height - (trackingHeight + border.width), + ); _saveGraphics(page, PdfBlendMode.hardLight); page.graphics.drawRectangle( - pen: PdfPens.black, brush: PdfBrushes.white, bounds: cRect); + pen: PdfPens.black, + brush: PdfBrushes.white, + bounds: cRect, + ); cRect = Rect.fromLTWH( - cRect.left + 11, cRect.top + 5, cRect.width - 22, cRect.height); + cRect.left + 11, + cRect.top + 5, + cRect.width - 22, + cRect.height, + ); page.graphics.restore(); _saveGraphics(page, PdfBlendMode.normal); page.graphics.drawString( - text, PdfStandardFont(PdfFontFamily.helvetica, 10.5), - brush: PdfBrushes.black, bounds: cRect); + text, + PdfStandardFont(PdfFontFamily.helvetica, 10.5), + brush: PdfBrushes.black, + bounds: cRect, + ); page.graphics.restore(); } void _drawSubject(String subject, Rect bounds, PdfPage page) { page.graphics.drawString( - subject, - PdfStandardFont(PdfFontFamily.helvetica, 10.5, - style: PdfFontStyle.bold), - brush: PdfBrushes.black, - bounds: bounds, - format: PdfStringFormat( - alignment: PdfTextAlignment.left, - lineAlignment: PdfVerticalAlignment.middle)); + subject, + PdfStandardFont(PdfFontFamily.helvetica, 10.5, style: PdfFontStyle.bold), + brush: PdfBrushes.black, + bounds: bounds, + format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.middle), + ); } void _saveGraphics(PdfPage page, PdfBlendMode mode) { page.graphics.save(); - PdfGraphicsHelper.getHelper(page.graphics) - .applyTransparency(0.8, 8.0, mode); + PdfGraphicsHelper.getHelper( + page.graphics, + ).applyTransparency(0.8, 8.0, mode); } PdfColor _getForeColor(PdfColor c) { return (((c.r + c.b + c.g) / 3) > 128) - ? PdfColor(0, 0, 0, 255) - : PdfColor(255, 255, 255, 255); + ? PdfColor(0, 0, 0) + : PdfColor(255, 255, 255); } Map _drawAuthor( - String author, - String subject, - Rect bounds, - PdfBrush backBrush, - PdfBrush aBrush, - PdfPage page, - double? trackingHeight, - PdfAnnotationBorder border) { + String author, + String subject, + Rect bounds, + PdfBrush backBrush, + PdfBrush aBrush, + PdfPage page, + double? trackingHeight, + PdfAnnotationBorder border, + ) { final double borderWidth = border.width / 2; - final PdfRectangle titleRect = PdfRectangle.fromRect(Rect.fromLTWH( + final PdfRectangle titleRect = PdfRectangle.fromRect( + Rect.fromLTWH( bounds.left + borderWidth, bounds.top + borderWidth, bounds.width - border.width, - 20)); + 20, + ), + ); if (subject != '') { titleRect.height += 20; trackingHeight = titleRect.height; _saveGraphics(page, PdfBlendMode.hardLight); page.graphics.drawRectangle( - pen: PdfPens.black, brush: backBrush, bounds: titleRect.rect); + pen: PdfPens.black, + brush: backBrush, + bounds: titleRect.rect, + ); page.graphics.restore(); Rect contentRect = Rect.fromLTWH( - titleRect.x + 11, titleRect.y, titleRect.width, titleRect.height / 2); + titleRect.x + 11, + titleRect.y, + titleRect.width, + titleRect.height / 2, + ); _saveGraphics(page, PdfBlendMode.normal); page.graphics.drawString( - author, - PdfStandardFont(PdfFontFamily.helvetica, 10.5, - style: PdfFontStyle.bold), - brush: aBrush, - bounds: contentRect, - format: PdfStringFormat( - alignment: PdfTextAlignment.left, - lineAlignment: PdfVerticalAlignment.middle)); + author, + PdfStandardFont( + PdfFontFamily.helvetica, + 10.5, + style: PdfFontStyle.bold, + ), + brush: aBrush, + bounds: contentRect, + format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.middle), + ); contentRect = Rect.fromLTWH( - contentRect.left, - contentRect.top + contentRect.height - 2, - contentRect.width, - titleRect.height / 2); + contentRect.left, + contentRect.top + contentRect.height - 2, + contentRect.width, + titleRect.height / 2, + ); _drawSubject(subject, contentRect, page); page.graphics.restore(); } else { _saveGraphics(page, PdfBlendMode.hardLight); page.graphics.drawRectangle( - pen: PdfPens.black, brush: backBrush, bounds: titleRect.rect); + pen: PdfPens.black, + brush: backBrush, + bounds: titleRect.rect, + ); page.graphics.restore(); final Rect contentRect = Rect.fromLTWH( - titleRect.x + 11, titleRect.y, titleRect.width, titleRect.height); + titleRect.x + 11, + titleRect.y, + titleRect.width, + titleRect.height, + ); _saveGraphics(page, PdfBlendMode.normal); page.graphics.drawString( - author, PdfStandardFont(PdfFontFamily.helvetica, 10.5), - brush: aBrush, - bounds: contentRect, - format: PdfStringFormat( - alignment: PdfTextAlignment.left, - lineAlignment: PdfVerticalAlignment.middle)); + author, + PdfStandardFont(PdfFontFamily.helvetica, 10.5), + brush: aBrush, + bounds: contentRect, + format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.middle), + ); trackingHeight = titleRect.height; page.graphics.restore(); } @@ -1165,14 +1348,19 @@ class PdfAnnotationHelper { /// internal method 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, height = bounds.height; if (page != null) { final int graphicsRotation = _obtainGraphicsRotation( - PdfGraphicsHelper.getHelper(page.graphics).matrix); + PdfGraphicsHelper.getHelper(page.graphics).matrix, + ); if (graphicsRotation == 0 && !isNormalMatrix) { x = bounds.left; y = bounds.top + bounds.height - bounds.width; @@ -1185,8 +1373,10 @@ class PdfAnnotationHelper { int _obtainGraphicsRotation(PdfTransformationMatrix matrix) { int angle = 0; - final double radians = - atan2(matrix.matrix.elements[2], matrix.matrix.elements[0]); + final double radians = atan2( + matrix.matrix.elements[2], + matrix.matrix.elements[0], + ); angle = (radians * 180 / pi).round(); switch (angle) { case -90: @@ -1203,7 +1393,7 @@ class PdfAnnotationHelper { } /// internal method - void setMatrix(PdfDictionary template) { + static void setMatrixToZeroRotation(PdfDictionary template) { final PdfArray? bbox = template[PdfDictionaryProperties.bBox] as PdfArray?; if (bbox != null) { final List elements = [ @@ -1212,7 +1402,7 @@ class PdfAnnotationHelper { 0, 1, -(bbox[0]! as PdfNumber).value! as double, - -(bbox[1]! as PdfNumber).value! as double + -(bbox[1]! as PdfNumber).value! as double, ]; template[PdfDictionaryProperties.matrix] = PdfArray(elements); } @@ -1242,12 +1432,13 @@ class PdfAnnotationHelper { /// internal method PdfRectangle calculateLineBounds( - List linePoints, - int _leaderLineExt, - int _leaderLine, - int leaderOffset, - PdfArray lineStyle, - double borderLength) { + List linePoints, + int leaderLineExt, + int leaderLineValue, + int leaderOffset, + PdfArray lineStyle, + double borderLength, + ) { PdfRectangle tempBounds = PdfRectangle.fromRect(bounds); final PdfPath path = PdfPath(); if (linePoints.length == 4) { @@ -1267,20 +1458,26 @@ class PdfAnnotationHelper { } int leaderLine = 0; double lineAngle = 0; - if (_leaderLine < 0) { - leaderLine = _leaderLine * -1; + if (leaderLineValue < 0) { + leaderLine = leaderLineValue * -1; lineAngle = angle + 180; } else { - leaderLine = _leaderLine; + leaderLine = leaderLineValue; lineAngle = angle; } final List x1y1 = [x1, y1]; final List x2y2 = [x2, y2]; if (leaderOffset != 0) { - final List offsetPoint1 = - getAxisValue(x1y1, lineAngle + 90, leaderOffset.toDouble()); - final List offsetPoint2 = - getAxisValue(x2y2, lineAngle + 90, leaderOffset.toDouble()); + final List offsetPoint1 = getAxisValue( + x1y1, + lineAngle + 90, + leaderOffset.toDouble(), + ); + final List offsetPoint2 = getAxisValue( + x2y2, + lineAngle + 90, + leaderOffset.toDouble(), + ); linePoints[0] = offsetPoint1[0].toInt(); linePoints[1] = offsetPoint1[1].toInt(); linePoints[2] = offsetPoint2[0].toInt(); @@ -1288,15 +1485,27 @@ class PdfAnnotationHelper { } final List startingPoint = getAxisValue( - x1y1, lineAngle + 90, (leaderLine + leaderOffset).toDouble()); + x1y1, + lineAngle + 90, + (leaderLine + leaderOffset).toDouble(), + ); final List endingPoint = getAxisValue( - x2y2, lineAngle + 90, (leaderLine + leaderOffset).toDouble()); - - final List beginLineLeader = getAxisValue(x1y1, lineAngle + 90, - (_leaderLineExt + leaderLine + leaderOffset).toDouble()); - - final List endLineLeader = getAxisValue(x2y2, lineAngle + 90, - (_leaderLineExt + leaderLine + leaderOffset).toDouble()); + x2y2, + lineAngle + 90, + (leaderLine + leaderOffset).toDouble(), + ); + + final List beginLineLeader = getAxisValue( + x1y1, + lineAngle + 90, + (leaderLineExt + leaderLine + leaderOffset).toDouble(), + ); + + final List endLineLeader = getAxisValue( + x2y2, + lineAngle + 90, + (leaderLineExt + leaderLine + leaderOffset).toDouble(), + ); final List stylePoint = []; @@ -1368,8 +1577,10 @@ class PdfAnnotationHelper { [startingPoint[0], endingPoint[0]].reduce(min)) { startingPoint[0] -= widthX[0] * borderLength; endingPoint[0] += widthX[1] * borderLength; - startingPoint[0] = - [startingPoint[0], linePoints[0].toDouble()].reduce(min); + startingPoint[0] = [ + startingPoint[0], + linePoints[0].toDouble(), + ].reduce(min); startingPoint[0] = min(startingPoint[0], beginLineLeader[0]); endingPoint[0] = max(endingPoint[0], linePoints[2].toDouble()); endingPoint[0] = max(endingPoint[0], endLineLeader[0]); @@ -1396,8 +1607,10 @@ class PdfAnnotationHelper { endingPoint[1] = min(endingPoint[1], linePoints[3].toDouble()); endingPoint[1] = min(endingPoint[1], endLineLeader[1]); } - path.addLine(Offset(startingPoint[0], startingPoint[1]), - Offset(endingPoint[0], endingPoint[1])); + path.addLine( + Offset(startingPoint[0], startingPoint[1]), + Offset(endingPoint[0], endingPoint[1]), + ); tempBounds = PdfPathHelper.getHelper(path).getBoundsInternal(); } return tempBounds; @@ -1435,14 +1648,15 @@ class PdfAnnotationHelper { /// internal method void setLineEndingStyles( - List startingPoint, - List endingPoint, - PdfGraphics? graphics, - double angle, - PdfPen? borderPen, - PdfBrush? backBrush, - PdfArray lineStyle, - double borderLength) { + List startingPoint, + List endingPoint, + PdfGraphics? graphics, + double angle, + PdfPen? borderPen, + PdfBrush? backBrush, + PdfArray lineStyle, + double borderLength, + ) { List axisPoint; if (borderLength == 0) { borderLength = 1; @@ -1464,21 +1678,26 @@ class PdfAnnotationHelper { 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); + 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); + axisPoint[0] - (3 * borderLength), + -(axisPoint[1] + (3 * borderLength)), + 6 * borderLength, + 6 * borderLength, + ); graphics!.drawEllipse(rect, pen: borderPen, brush: backBrush); } break; @@ -1497,16 +1716,26 @@ class PdfAnnotationHelper { } else { startPoint = getAxisValue(axisPoint, angle, -borderLength); } - final List point1 = - getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - getAxisValue(startPoint, angle - arraowAngle, length); + 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])); + 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; @@ -1525,14 +1754,20 @@ class PdfAnnotationHelper { } else { startPoint = getAxisValue(axisPoint, angle, -borderLength); } - final List point1 = - getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - getAxisValue(startPoint, angle - arraowAngle, length); + 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]) + Offset(point2[0], -point2[1]), ]; graphics!.drawPolygon(points, pen: borderPen, brush: backBrush); } @@ -1552,16 +1787,26 @@ class PdfAnnotationHelper { } else { startPoint = getAxisValue(axisPoint, angle, borderLength); } - final List point1 = - getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - getAxisValue(startPoint, angle - arraowAngle, length); + 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])); + 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; @@ -1581,14 +1826,20 @@ class PdfAnnotationHelper { startPoint = getAxisValue(axisPoint, angle, borderLength); } - final List point1 = - getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - getAxisValue(startPoint, angle - arraowAngle, length); + 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]) + Offset(point2[0], -point2[1]), ]; graphics!.drawPolygon(points, pen: borderPen, brush: backBrush); } @@ -1596,14 +1847,26 @@ class PdfAnnotationHelper { 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 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': @@ -1617,7 +1880,7 @@ class PdfAnnotationHelper { Offset(point1[0], -point1[1]), Offset(point2[0], -point2[1]), Offset(point3[0], -point3[1]), - Offset(point4[0], -point4[1]) + Offset(point4[0], -point4[1]), ]; graphics!.drawPolygon(points, pen: borderPen, brush: backBrush); } @@ -1625,13 +1888,22 @@ class PdfAnnotationHelper { 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 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; } @@ -1659,25 +1931,100 @@ class PdfAnnotationHelper { return isRotatedMatrix; } + /// Returns the boolean if the template matrix is valid or not + bool isValidTemplateMatrix( + PdfDictionary dictionary, + Offset bounds, + PdfTemplate template, + ) { + bool isValidMatrix = true; + Offset point = bounds; + if (dictionary.containsKey(PdfDictionaryProperties.matrix)) { + final IPdfPrimitive? bbox = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.bBox], + ); + final IPdfPrimitive? matrix = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.matrix], + ); + if (matrix != null && + bbox != null && + matrix is PdfArray && + bbox is PdfArray && + matrix.count > 3 && + bbox.count > 2) { + 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) { + if ((((bbox[0]! as PdfNumber).value!.toDouble() != + -(matrix[4]! as PdfNumber).value!.toDouble()) && + ((bbox[1]! as PdfNumber).value!.toDouble() != + -(matrix[5]! as PdfNumber).value!.toDouble())) || + ((bbox[0]! as PdfNumber).value!.toDouble() == 0 && + -(matrix[4]! as PdfNumber).value!.toDouble() == 0)) { + final PdfGraphics pageGraphics = page!.graphics; + final PdfGraphicsState state = pageGraphics.save(); + if (opacity < 1) { + pageGraphics.setTransparency(opacity); + } + point = Offset( + point.dx - (bbox[0]! as PdfNumber).value!.toDouble(), + point.dy + (bbox[1]! as PdfNumber).value!.toDouble(), + ); + pageGraphics.drawPdfTemplate(template, point); + pageGraphics.restore(state); + page!.annotations.remove(base); + isValidMatrix = false; + } + } else if ((matrix[0]! as PdfNumber).value == 0 && + (matrix[1]! as PdfNumber).value == -1 && + (matrix[2]! as PdfNumber).value == 1 && + (matrix[3]! as PdfNumber).value == 0) { + if ((bbox[0]! as PdfNumber).value! > 0) { + isValidMatrix = false; + } + } else { + if ((bbox[0]! as PdfNumber).value! > 0) { + isValidMatrix = false; + } + } + } + } + return isValidMatrix; + } + /// Flatten annotation template void flattenAnnotationTemplate(PdfTemplate appearance, bool isNormalMatrix) { final PdfGraphicsState state = page!.graphics.save(); if (opacity < 1) { page!.graphics.setTransparency(opacity); } - final Rect bound = - calculateTemplateBounds(bounds, page, appearance, isNormalMatrix); + final Rect bound = calculateTemplateBounds( + bounds, + page, + appearance, + isNormalMatrix, + ); page!.graphics.drawPdfTemplate(appearance, bound.topLeft, bounds.size); page!.graphics.restore(state); page!.annotations.remove(base); } /// Draw CloudStye to the Shapes - void drawCloudStyle(PdfGraphics graphics, PdfBrush? brush, PdfPen? pen, - double radius, double overlap, List points, bool isAppearance) { + void drawCloudStyle( + PdfGraphics graphics, + PdfBrush? brush, + PdfPen? pen, + double radius, + double overlap, + List points, + bool isAppearance, + ) { if (_isClockWise(points)) { points = List.generate( - points.length, (int i) => points[points.length - (i + 1)]); + points.length, + (int i) => points[points.length - (i + 1)], + ); } // Create a list of circles @@ -1694,8 +2041,10 @@ class PdfAnnotationHelper { final double d = circleOverlap; for (double a = 0; a + 0.1 * d < len; a += d) { final _CloudStyleArc cur = _CloudStyleArc(); - cur.point = - Offset(previousPoint.dx + a * dx, previousPoint.dy + a * dy); + cur.point = Offset( + previousPoint.dx + a * dx, + previousPoint.dy + a * dy, + ); circles.add(cur); } previousPoint = currentPoint; @@ -1708,7 +2057,10 @@ class PdfAnnotationHelper { for (int i = 0; i < circles.length; i++) { final _CloudStyleArc currentCurvedStyleArc = circles[i]; final Offset angle = _getIntersectionDegrees( - previousCurvedStyleArc.point, currentCurvedStyleArc.point, radius); + previousCurvedStyleArc.point, + currentCurvedStyleArc.point, + radius, + ); previousCurvedStyleArc.endAngle = angle.dx; currentCurvedStyleArc.startAngle = angle.dy; previousCurvedStyleArc = currentCurvedStyleArc; @@ -1718,12 +2070,14 @@ class PdfAnnotationHelper { PdfPath path = PdfPath(); for (int i = 0; i < circles.length; i++) { final _CloudStyleArc curr = circles[i]; - final double angle = curr.startAngle < 0 - ? ((curr.startAngle * -1) % 360) * -1 - : curr.startAngle % 360; - final double angle1 = curr.endAngle < 0 - ? ((curr.endAngle * -1) % 360) * -1 - : curr.endAngle % 360; + final double angle = + curr.startAngle < 0 + ? ((curr.startAngle * -1) % 360) * -1 + : curr.startAngle % 360; + final double angle1 = + curr.endAngle < 0 + ? ((curr.endAngle * -1) % 360) * -1 + : curr.endAngle % 360; double sweepAngel = 0; if (angle > 0 && angle1 < 0) { sweepAngel = (180 - angle) + (180 - (angle1 < 0 ? -angle1 : angle1)); @@ -1751,27 +2105,35 @@ class PdfAnnotationHelper { } curr.endAngle = sweepAngel; path.addArc( - Rect.fromLTWH(curr.point.dx - radius, curr.point.dy - radius, - 2 * radius, 2 * radius), - angle, - sweepAngel); + Rect.fromLTWH( + curr.point.dx - radius, + curr.point.dy - radius, + 2 * radius, + 2 * radius, + ), + angle, + sweepAngel, + ); } path.closeFigure(); PdfPath pdfPath = PdfPath(); if (isAppearance) { for (int i = 0; i < PdfPathHelper.getHelper(path).points.length; i++) { - PdfPathHelper.getHelper(pdfPath).points.add(Offset( + PdfPathHelper.getHelper(pdfPath).points.add( + Offset( PdfPathHelper.getHelper(path).points[i].dx, - -PdfPathHelper.getHelper(path).points[i].dy)); + -PdfPathHelper.getHelper(path).points[i].dy, + ), + ); } } else { - PdfPathHelper.getHelper(pdfPath) - .points - .addAll(PdfPathHelper.getHelper(path).points); + PdfPathHelper.getHelper( + pdfPath, + ).points.addAll(PdfPathHelper.getHelper(path).points); } - PdfPathHelper.getHelper(pdfPath) - .pathTypes - .addAll(PdfPathHelper.getHelper(path).pathTypes); + PdfPathHelper.getHelper( + pdfPath, + ).pathTypes.addAll(PdfPathHelper.getHelper(path).pathTypes); if (brush != null) { graphics.drawPath(pdfPath, brush: brush); } @@ -1780,27 +2142,35 @@ class PdfAnnotationHelper { for (int i = 0; i < circles.length; i++) { final _CloudStyleArc curr = circles[i]; path.addArc( - Rect.fromLTWH(curr.point.dx - radius, curr.point.dy - radius, - 2 * radius, 2 * radius), - curr.startAngle, - curr.endAngle + incise); + Rect.fromLTWH( + curr.point.dx - radius, + curr.point.dy - radius, + 2 * radius, + 2 * radius, + ), + curr.startAngle, + curr.endAngle + incise, + ); } path.closeFigure(); pdfPath = PdfPath(); if (isAppearance) { for (int i = 0; i < PdfPathHelper.getHelper(path).points.length; i++) { - PdfPathHelper.getHelper(pdfPath).points.add(Offset( + PdfPathHelper.getHelper(pdfPath).points.add( + Offset( PdfPathHelper.getHelper(path).points[i].dx, - -PdfPathHelper.getHelper(path).points[i].dy)); + -PdfPathHelper.getHelper(path).points[i].dy, + ), + ); } } else { - PdfPathHelper.getHelper(pdfPath) - .points - .addAll(PdfPathHelper.getHelper(path).points); + PdfPathHelper.getHelper( + pdfPath, + ).points.addAll(PdfPathHelper.getHelper(path).points); } - PdfPathHelper.getHelper(pdfPath) - .pathTypes - .addAll(PdfPathHelper.getHelper(path).pathTypes); + PdfPathHelper.getHelper( + pdfPath, + ).pathTypes.addAll(PdfPathHelper.getHelper(path).pathTypes); graphics.drawPath(pdfPath, pen: pen); } @@ -1827,13 +2197,18 @@ class PdfAnnotationHelper { } final double radian = atan2(dy, dx); final double cosvalue = acos(a); - return Offset((radian - cosvalue) * (180 / pi), - (pi + radian + cosvalue) * (180 / pi)); + return Offset( + (radian - cosvalue) * (180 / pi), + (pi + radian + cosvalue) * (180 / pi), + ); } // Searches the in parents. static IPdfPrimitive? _searchInParents( - PdfDictionary dictionary, PdfCrossTable? crossTable, String value) { + PdfDictionary dictionary, + PdfCrossTable? crossTable, + String value, + ) { IPdfPrimitive? primitive; PdfDictionary? dic = dictionary; while ((primitive == null) && (dic != null)) { @@ -1841,8 +2216,9 @@ class PdfAnnotationHelper { primitive = crossTable!.getObject(dic[value]); } else { if (dic.containsKey(PdfDictionaryProperties.parent)) { - dic = crossTable!.getObject(dic[PdfDictionaryProperties.parent]) - as PdfDictionary?; + dic = + crossTable!.getObject(dic[PdfDictionaryProperties.parent]) + as PdfDictionary?; } else { dic = null; } @@ -1852,8 +2228,12 @@ class PdfAnnotationHelper { } /// internal method - static IPdfPrimitive? getValue(PdfDictionary dictionary, - PdfCrossTable? crossTable, String value, bool inheritable) { + static IPdfPrimitive? getValue( + PdfDictionary dictionary, + PdfCrossTable? crossTable, + String value, + bool inheritable, + ) { IPdfPrimitive? primitive; if (dictionary.containsKey(value)) { primitive = crossTable!.getObject(dictionary[value]); @@ -1887,11 +2267,89 @@ class PdfAnnotationHelper { } else if (annotation is WidgetAnnotation) { WidgetAnnotationHelper.getHelper(annotation).save(); isSaveComplete = true; + } else if (annotation is PdfTextMarkupAnnotation) { + PdfTextMarkupAnnotationHelper.getHelper(annotation).save(); + isSaveComplete = true; + } else if (annotation is PdfPopupAnnotation) { + PdfPopupAnnotationHelper.getHelper(annotation).save(); + isSaveComplete = true; + } + if (!PdfAnnotationHelper.getHelper(annotation).flatten) { + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); + if (helper.flag != null) { + int flagValue = 0; + for (int i = 0; i < helper.flag!.length; i++) { + flagValue |= getAnnotationFlagsValue(helper.flag![i]); + } + helper.dictionary!.setNumber(PdfDictionaryProperties.f, flagValue); + } } if (!isSaveComplete) { PdfAnnotationHelper.getHelper(annotation).saveAnnotation(); } } + + /// Internal method. + int? getFlagValue() { + if (dictionary!.containsKey(PdfDictionaryProperties.f)) { + final IPdfPrimitive? annotFlags = getValue( + dictionary!, + crossTable, + PdfDictionaryProperties.f, + false, + ); + if (annotFlags != null && + annotFlags is PdfNumber && + annotFlags.value != null) { + return annotFlags.value!.toInt(); + } + } + return null; + } + + /// Internal method. + static List obtainAnnotationFlags(int? flagValue) { + final List flags = []; + if (flagValue != null) { + for (final PdfAnnotationFlags flag in PdfAnnotationFlags.values) { + if (flagValue == 0) { + return flags..add(flag); + } + if (getAnnotationFlagsValue(flag) & flagValue != 0) { + flags.add(flag); + } + } + } + return flags; + } + + /// internal method + static int getAnnotationFlagsValue(PdfAnnotationFlags value) { + switch (value) { + case PdfAnnotationFlags.defaultFlag: + return 0; + case PdfAnnotationFlags.invisible: + return 1; + case PdfAnnotationFlags.hidden: + return 2; + case PdfAnnotationFlags.print: + return 4; + case PdfAnnotationFlags.noZoom: + return 8; + case PdfAnnotationFlags.noRotate: + return 16; + case PdfAnnotationFlags.noView: + return 32; + case PdfAnnotationFlags.readOnly: + return 64; + case PdfAnnotationFlags.locked: + return 128; + case PdfAnnotationFlags.toggleNoView: + return 256; + } + } } class _CloudStyleArc { 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 fddd52922..963a61508 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 @@ -15,12 +15,13 @@ class PdfAnnotationBorder implements IPdfWrapper { /// horizontal and vertical radius. /// /// The borderStyle and dashArray only used for shape annotations. - PdfAnnotationBorder( - [double? borderWidth, - double? horizontalRadius, - double? verticalRadius, - PdfBorderStyle? borderStyle, - int? dashArray]) { + PdfAnnotationBorder([ + double? borderWidth, + double? horizontalRadius, + double? verticalRadius, + PdfBorderStyle? borderStyle, + int? dashArray, + ]) { _helper.array.add(PdfNumber(0)); _helper.array.add(PdfNumber(0)); _helper.array.add(PdfNumber(1)); @@ -29,7 +30,9 @@ class PdfAnnotationBorder implements IPdfWrapper { this.verticalRadius = verticalRadius ??= 0; _borderStyle = borderStyle ??= PdfBorderStyle.solid; _helper.dictionary.setName( - PdfName(PdfDictionaryProperties.s), _styleToString(_borderStyle)); + PdfName(PdfDictionaryProperties.s), + _styleToString(_borderStyle), + ); if (dashArray != null) { this.dashArray = dashArray; } @@ -37,10 +40,14 @@ class PdfAnnotationBorder implements IPdfWrapper { PdfAnnotationBorder._asWidgetBorder() { _helper.dictionary.setProperty( - PdfDictionaryProperties.type, PdfName(PdfDictionaryProperties.border)); + PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.border), + ); _borderStyle = PdfBorderStyle.solid; _helper.dictionary.setName( - PdfName(PdfDictionaryProperties.s), _styleToString(_borderStyle)); + PdfName(PdfDictionaryProperties.s), + _styleToString(_borderStyle), + ); _helper.isWidgetBorder = true; } @@ -82,8 +89,10 @@ class PdfAnnotationBorder implements IPdfWrapper { if (!_helper.isWidgetBorder) { _setNumber(2, value); } - _helper.dictionary - .setNumber(PdfDictionaryProperties.w, _borderWidth.toInt()); + _helper.dictionary.setNumber( + PdfDictionaryProperties.w, + _borderWidth.toInt(), + ); } } @@ -94,7 +103,9 @@ class PdfAnnotationBorder implements IPdfWrapper { if (value != _borderStyle) { _borderStyle = value; _helper.dictionary.setName( - PdfName(PdfDictionaryProperties.s), _styleToString(_borderStyle)); + PdfName(PdfDictionaryProperties.s), + _styleToString(_borderStyle), + ); } } @@ -128,6 +139,7 @@ class PdfAnnotationBorder implements IPdfWrapper { return 'I'; case PdfBorderStyle.underline: return 'U'; + // ignore: no_default_cases default: return 'S'; } @@ -171,7 +183,8 @@ class PdfAnnotationBorderHelper { /// internal method static PdfAnnotationBorderHelper getHelper( - PdfAnnotationBorder annotationBorder) { + PdfAnnotationBorder annotationBorder, + ) { return annotationBorder._helper; } } 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 ac20f9cb7..020e09c75 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 @@ -17,7 +17,9 @@ import 'pdf_document_link_annotation.dart'; import 'pdf_ellipse_annotation.dart'; import 'pdf_line_annotation.dart'; import 'pdf_polygon_annotation.dart'; +import 'pdf_popup_annotation.dart'; import 'pdf_rectangle_annotation.dart'; +import 'pdf_text_markup_annotation.dart'; import 'pdf_text_web_link.dart'; import 'pdf_uri_annotation.dart'; import 'widget_annotation.dart'; @@ -71,12 +73,17 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfAnnotationCollectionHelper(this.base, this.page) : super(base); PdfAnnotationCollectionHelper._(this.base, this.page) : super(base) { - for (int i = 0; - i < PdfPageHelper.getHelper(page).terminalAnnotation.length; - ++i) { + for ( + int i = 0; + i < PdfPageHelper.getHelper(page).terminalAnnotation.length; + ++i + ) { final PdfAnnotation? annot = _getAnnotation(i); if (annot != null) { + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper(annot); + helper.isOldAnnotation = true; _doAdd(annot); + helper.isOldAnnotation = false; } } } @@ -106,17 +113,20 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { /// internal method PdfArray? rearrange(PdfReference reference, int tabIndex, int index) { - final PdfArray? annots = PdfPageHelper.getHelper(page) - .crossTable! - .getObject(PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.annots]) as PdfArray?; + final PdfArray? annots = + PdfPageHelper.getHelper(page).crossTable!.getObject( + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .annots], + ) + as PdfArray?; if (annots != null) { if (tabIndex > annots.count) { tabIndex = 0; } if (index >= annots.count) { - index = - PdfPageHelper.getHelper(page).annotsReference.indexOf(reference); + index = PdfPageHelper.getHelper( + page, + ).annotsReference.indexOf(reference); } final IPdfPrimitive? annotReference = annots.elements[index]; if (annotReference != null && annotReference is PdfReferenceHolder) { @@ -148,11 +158,16 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { if (flatten && PdfPageHelper.getHelper(page).document != null) { final PdfCrossTable? cross = PdfPageHelper.getHelper(page).crossTable; if (cross != null && - PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.annots)) { - final PdfArray? annots = cross.getObject(PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.annots]) as PdfArray?; + PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.annots)) { + final PdfArray? annots = + cross.getObject( + PdfPageHelper.getHelper( + page, + ).dictionary![PdfDictionaryProperties.annots], + ) + as PdfArray?; if (annots != null) { for (int count = 0; count < annots.count; ++count) { final PdfDictionary? annotDicrionary = @@ -193,18 +208,27 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { PdfAnnotationHelper.getHelper(annot).flatten = true; } PdfAnnotationHelper.getHelper(annot).setPage(page); + if (!PdfAnnotationHelper.getHelper(annot).isLoadedAnnotation && + annot is PdfTextMarkupAnnotation) { + PdfTextMarkupAnnotationHelper.getHelper(annot).setQuadPoints(page.size); + } if (PdfPageHelper.getHelper(page).isLoadedPage) { PdfArray? array; final PdfDictionary dictionary = PdfPageHelper.getHelper(page).dictionary!; if (dictionary.containsKey(PdfDictionaryProperties.annots)) { - array = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.annots]) as PdfArray?; + array = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.annots], + ) + as PdfArray?; } array ??= PdfArray(); - final PdfReferenceHolder reference = - PdfReferenceHolder(PdfAnnotationHelper.getHelper(annot).dictionary); - if (!_checkPresence(array, reference)) { + final PdfReferenceHolder reference = PdfReferenceHolder( + PdfAnnotationHelper.getHelper(annot).dictionary, + ); + if (!PdfAnnotationHelper.getHelper(annot).isOldAnnotation && + !_checkPresence(array, reference)) { array.add(reference); dictionary.setProperty(PdfDictionaryProperties.annots, array); } @@ -212,7 +236,9 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { final IPdfPrimitive? tempElement = IPdfWrapper.getElement(annot); if (tempElement == null) { IPdfWrapper.setElement( - annot, PdfAnnotationHelper.getHelper(annot).dictionary); + annot, + PdfAnnotationHelper.getHelper(annot).dictionary, + ); } annotations.add(PdfReferenceHolder(annot)); list.add(annot); @@ -250,25 +276,30 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { final PdfDictionary pageDic = PdfPageHelper.getHelper(page).dictionary!; PdfArray? annots; if (pageDic.containsKey(PdfDictionaryProperties.annots)) { - annots = PdfPageHelper.getHelper(page) - .crossTable! - .getObject(pageDic[PdfDictionaryProperties.annots]) as PdfArray?; + annots = + PdfPageHelper.getHelper( + page, + ).crossTable!.getObject(pageDic[PdfDictionaryProperties.annots]) + as PdfArray?; } else { annots = PdfArray(); } - if (PdfAnnotationHelper.getHelper(annot) - .dictionary! - .containsKey(PdfDictionaryProperties.popup)) { + if (PdfAnnotationHelper.getHelper( + annot, + ).dictionary!.containsKey(PdfDictionaryProperties.popup)) { final IPdfPrimitive? popUpDictionary = - (PdfAnnotationHelper.getHelper(annot) - .dictionary![PdfName(PdfDictionaryProperties.popup)] + (PdfAnnotationHelper.getHelper(annot).dictionary![PdfName( + PdfDictionaryProperties.popup, + )] is PdfReferenceHolder) - ? (PdfAnnotationHelper.getHelper(annot) - .dictionary![PdfName(PdfDictionaryProperties.popup)]! + ? (PdfAnnotationHelper.getHelper(annot).dictionary![PdfName( + PdfDictionaryProperties.popup, + )]! as PdfReferenceHolder) .object - : PdfAnnotationHelper.getHelper(annot) - .dictionary![PdfName(PdfDictionaryProperties.popup)]; + : PdfAnnotationHelper.getHelper(annot).dictionary![PdfName( + PdfDictionaryProperties.popup, + )]; if (popUpDictionary is PdfDictionary) { for (int i = 0; i < annots!.count; i++) { if (popUpDictionary == @@ -279,22 +310,22 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { break; } } - final IPdfPrimitive popUpObj = PdfPageHelper.getHelper(page) - .crossTable! - .getObject(popUpDictionary)!; - final int? popUpIndex = - PdfPageHelper.getHelper(page).crossTable!.items!.lookFor(popUpObj); + final IPdfPrimitive popUpObj = + PdfPageHelper.getHelper( + page, + ).crossTable!.getObject(popUpDictionary)!; + final int? popUpIndex = PdfPageHelper.getHelper( + page, + ).crossTable!.items!.lookFor(popUpObj); if (popUpIndex != null && popUpIndex != -1) { - PdfPageHelper.getHelper(page) - .crossTable! - .items! - .objectCollection! - .removeAt(popUpIndex); + PdfPageHelper.getHelper( + page, + ).crossTable!.items!.objectCollection!.removeAt(popUpIndex); } _removeAllReference(popUpObj); - PdfPageHelper.getHelper(page) - .terminalAnnotation - .remove(popUpDictionary); + PdfPageHelper.getHelper( + page, + ).terminalAnnotation.remove(popUpDictionary); } } for (int i = 0; i < annots!.count; i++) { @@ -307,9 +338,9 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { } } PdfAnnotationHelper.getHelper(annot).dictionary!.changed = false; - PdfPageHelper.getHelper(page) - .dictionary! - .setProperty(PdfDictionaryProperties.annots, annots); + PdfPageHelper.getHelper( + page, + ).dictionary!.setProperty(PdfDictionaryProperties.annots, annots); } void _removeAllReference(IPdfPrimitive obj) { @@ -322,14 +353,13 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { k.name != PdfDictionaryProperties.parent) { final IPdfPrimitive newobj = PdfPageHelper.getHelper(page).crossTable!.getObject(v)!; - final int? index = - PdfPageHelper.getHelper(page).crossTable!.items!.lookFor(newobj); + final int? index = PdfPageHelper.getHelper( + page, + ).crossTable!.items!.lookFor(newobj); if (index != null && index != -1) { - PdfPageHelper.getHelper(page) - .crossTable! - .items! - .objectCollection! - .removeAt(index); + PdfPageHelper.getHelper( + page, + ).crossTable!.items!.objectCollection!.removeAt(index); } _removeAllReference(v!); (PdfCrossTable.dereference(v)! as PdfStream) @@ -347,20 +377,29 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { final PdfCrossTable? crossTable = PdfPageHelper.getHelper(page).crossTable; PdfAnnotation? annot; if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName name = PdfAnnotationHelper.getValue( - dictionary, crossTable, PdfDictionaryProperties.subtype, true)! - as PdfName; - final PdfAnnotationTypes type = - _getAnnotationType(name, dictionary, crossTable); + final PdfName name = + PdfAnnotationHelper.getValue( + dictionary, + crossTable, + PdfDictionaryProperties.subtype, + true, + )! + as PdfName; + final PdfAnnotationTypes type = getAnnotationType( + name, + dictionary, + crossTable, + ); final PdfArray? rectValue = PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.rect]) as PdfArray?; if (rectValue != null) { String text = ''; if (dictionary.containsKey(PdfDictionaryProperties.contents)) { - final PdfString? str = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.contents]) as PdfString?; - if (str != null) { + final IPdfPrimitive? str = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.contents], + ); + if (str != null && str is PdfString) { text = str.value.toString(); } } @@ -370,13 +409,19 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { break; case PdfAnnotationTypes.linkAnnotation: if (dictionary.containsKey(PdfDictionaryProperties.a)) { - final PdfDictionary? remoteLinkDic = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.a]) as PdfDictionary?; + final PdfDictionary? remoteLinkDic = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.a], + ) + as PdfDictionary?; if (remoteLinkDic != null && remoteLinkDic.containsKey(PdfDictionaryProperties.s)) { PdfName? gotor; - gotor = PdfCrossTable.dereference( - remoteLinkDic[PdfDictionaryProperties.s]) as PdfName?; + gotor = + PdfCrossTable.dereference( + remoteLinkDic[PdfDictionaryProperties.s], + ) + as PdfName?; if (gotor != null && gotor.name == 'URI') { annot = _createLinkAnnotation(dictionary, crossTable!, text); } @@ -403,6 +448,16 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { case PdfAnnotationTypes.widgetAnnotation: annot = _createWidgetAnnotation(dictionary, crossTable!); break; + case PdfAnnotationTypes.highlight: + case PdfAnnotationTypes.squiggly: + case PdfAnnotationTypes.strikeOut: + case PdfAnnotationTypes.underline: + annot = _createMarkupAnnotation(dictionary, crossTable!); + break; + case PdfAnnotationTypes.popupAnnotation: + annot = _createPopupAnnotation(dictionary, crossTable!, text); + break; + // ignore: no_default_cases default: break; } @@ -415,8 +470,11 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { } /// Gets the type of the annotation. - PdfAnnotationTypes _getAnnotationType( - PdfName name, PdfDictionary dictionary, PdfCrossTable? crossTable) { + static PdfAnnotationTypes getAnnotationType( + PdfName name, + PdfDictionary dictionary, + PdfCrossTable? crossTable, + ) { final String str = name.name!; PdfAnnotationTypes type = PdfAnnotationTypes.noAnnotation; switch (str.toLowerCase()) { @@ -428,13 +486,19 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { as PdfDictionary?; } if (linkDic != null && linkDic.containsKey(PdfDictionaryProperties.s)) { - name = PdfCrossTable.dereference(linkDic[PdfDictionaryProperties.s])! - as PdfName; - final PdfArray? border = (PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.border]) is PdfArray) - ? PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.border]) as PdfArray? - : null; + name = + PdfCrossTable.dereference(linkDic[PdfDictionaryProperties.s])! + as PdfName; + final PdfArray? border = + (PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.border], + ) + is PdfArray) + ? PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.border], + ) + as PdfArray? + : null; final bool mType = _findAnnotation(border); if (name.name == 'URI') { type = PdfAnnotationTypes.linkAnnotation; @@ -449,8 +513,11 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { type = PdfAnnotationTypes.documentLinkAnnotation; } } else if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName? strText = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.subtype]) as PdfName?; + final PdfName? strText = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype], + ) + as PdfName?; if (strText != null) { switch (strText.name) { case 'Link': @@ -475,6 +542,23 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { case 'widget': type = PdfAnnotationTypes.widgetAnnotation; break; + case 'highlight': + type = PdfAnnotationTypes.highlight; + break; + case 'underline': + type = PdfAnnotationTypes.underline; + break; + case 'strikeout': + type = PdfAnnotationTypes.strikeOut; + break; + case 'squiggly': + type = PdfAnnotationTypes.squiggly; + break; + case 'text': + if (!dictionary.containsKey(PdfDictionaryProperties.irt)) { + type = PdfAnnotationTypes.popupAnnotation; + } + break; default: break; } @@ -483,27 +567,43 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { // Creates the file link annotation. PdfAnnotation _createDocumentLinkAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfAnnotation annot = - PdfDocumentLinkAnnotationHelper.load(dictionary, crossTable); + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfAnnotation annot = PdfDocumentLinkAnnotationHelper.load( + dictionary, + crossTable, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); PdfAnnotationHelper.getHelper(annot).page = page; return annot; } PdfAnnotation _createLinkAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { - final PdfAnnotation annot = - PdfUriAnnotationHelper.load(dictionary, crossTable, text); + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfUriAnnotationHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); return annot; } // Creates the Line Annotation. PdfAnnotation _createLineAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { - final PdfAnnotation annot = - PdfLineAnnotationHelper.load(dictionary, crossTable, text); + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfLineAnnotationHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); PdfAnnotationHelper.getHelper(annot).page = page; return annot; @@ -511,9 +611,15 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { // Creates the Ellipse Annotation. PdfAnnotation _createEllipseAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { - final PdfAnnotation annot = - PdfEllipseAnnotationHelper.load(dictionary, crossTable, text); + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfEllipseAnnotationHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); PdfAnnotationHelper.getHelper(annot).page = page; return annot; @@ -521,9 +627,15 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { // Creates the Rectangle Annotation. PdfAnnotation _createRectangleAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { - final PdfAnnotation annot = - PdfRectangleAnnotationHelper.load(dictionary, crossTable, text); + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfRectangleAnnotationHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); PdfAnnotationHelper.getHelper(annot).page = page; return annot; @@ -531,32 +643,75 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { // Creates the Polygon Annotation. PdfAnnotation _createPolygonAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { - final PdfAnnotation annot = - PdfPolygonAnnotationHelper.load(dictionary, crossTable, text); + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfPolygonAnnotationHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); PdfAnnotationHelper.getHelper(annot).page = page; return annot; } PdfAnnotation _createTextWebLinkAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { - final PdfAnnotation annot = - PdfTextWebLinkHelper.load(dictionary, crossTable, text); + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfTextWebLinkHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); return annot; } //Creates the widget annotation. PdfAnnotation _createWidgetAnnotation( - PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfAnnotation annot = - WidgetAnnotationHelper.load(dictionary, crossTable); + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfAnnotation annot = WidgetAnnotationHelper.load( + dictionary, + crossTable, + ); + PdfAnnotationHelper.getHelper(annot).setPage(page); + return annot; + } + + /// Creates the Markup Annotation. + PdfAnnotation _createMarkupAnnotation( + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfAnnotation annot = PdfTextMarkupAnnotationHelper.load( + dictionary, + crossTable, + ); + PdfAnnotationHelper.getHelper(annot).setPage(page); + return annot; + } + + PdfAnnotation _createPopupAnnotation( + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + final PdfAnnotation annot = PdfPopupAnnotationHelper.load( + dictionary, + crossTable, + text, + ); PdfAnnotationHelper.getHelper(annot).setPage(page); return annot; } - bool _findAnnotation(PdfArray? arr) { + static bool _findAnnotation(PdfArray? arr) { if (arr == null) { return false; } @@ -568,7 +723,7 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { (temp[j] is PdfNumber) ? temp[j] as PdfNumber? : null; int? val = 0; if (value != null) { - val = value.value as int?; + val = value.value?.toInt(); } if (val! > 0) { return false; @@ -596,7 +751,8 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { /// internal method static PdfAnnotationCollectionHelper getHelper( - PdfAnnotationCollection annotationCollection) { + PdfAnnotationCollection annotationCollection, + ) { return annotationCollection._helper; } 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 1600cb3d0..7bd34408c 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 @@ -22,9 +22,13 @@ class PdfAppearance implements IPdfWrapper { PdfTemplate get normal { if (_helper.templateNormal == null) { _helper.templateNormal = PdfTemplate( - _annotation.bounds.size.width, _annotation.bounds.size.height); - _helper.dictionary!.setProperty(PdfDictionaryProperties.n, - PdfReferenceHolder(_helper.templateNormal)); + _annotation.bounds.size.width, + _annotation.bounds.size.height, + ); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.n, + PdfReferenceHolder(_helper.templateNormal), + ); } return _helper.templateNormal!; } @@ -33,18 +37,24 @@ class PdfAppearance implements IPdfWrapper { set normal(PdfTemplate value) { if (_helper.templateNormal != value) { _helper.templateNormal = value; - _helper.dictionary!.setProperty(PdfDictionaryProperties.n, - PdfReferenceHolder(_helper.templateNormal)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.n, + PdfReferenceHolder(_helper.templateNormal), + ); } } /// Gets or sets [PdfTemplate] object which applied to an annotation when mouse button is pressed. PdfTemplate get pressed { if (_helper.templatePressed == null) { - _helper.templatePressed = - PdfTemplate(_annotation.bounds.width, _annotation.bounds.height); - _helper.dictionary!.setProperty(PdfDictionaryProperties.d, - PdfReferenceHolder(_helper.templatePressed)); + _helper.templatePressed = PdfTemplate( + _annotation.bounds.width, + _annotation.bounds.height, + ); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.d, + PdfReferenceHolder(_helper.templatePressed), + ); } return _helper.templatePressed!; } @@ -52,8 +62,10 @@ class PdfAppearance implements IPdfWrapper { set pressed(PdfTemplate value) { if (value != _helper.templatePressed) { _helper.templatePressed = value; - _helper.dictionary!.setProperty(PdfDictionaryProperties.d, - PdfReferenceHolder(_helper.templatePressed)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.d, + PdfReferenceHolder(_helper.templatePressed), + ); } } } 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 38d43dfc4..79f1c428b 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 @@ -4,6 +4,7 @@ import '../../interfaces/pdf_interface.dart'; import '../drawing/drawing.dart'; import '../general/enum.dart'; import '../general/pdf_destination.dart'; +import '../graphics/pdf_color.dart'; import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../pages/pdf_page.dart'; @@ -17,6 +18,7 @@ import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_string.dart'; import 'pdf_action_annotation.dart'; import 'pdf_annotation.dart'; +import 'pdf_annotation_border.dart'; /// Represents an annotation object with holds link on /// another location within a document. @@ -28,7 +30,7 @@ import 'pdf_annotation.dart'; /// Rect.fromLTWH(10, 40, 30, 30), /// PdfDestination(document.pages.add(), Offset(10, 0)))); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -44,7 +46,7 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { /// Rect.fromLTWH(10, 40, 30, 30), /// PdfDestination(document.pages.add(), Offset(10, 0)))); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -54,7 +56,9 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { } PdfDocumentLinkAnnotation._( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { _helper = PdfDocumentLinkAnnotationHelper._(this, dictionary, crossTable); } @@ -78,7 +82,7 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { /// //Add the document link to the page /// page.annotations.add(documentLinkAnnotation); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -92,39 +96,61 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { _helper.destination = value; } if (PdfAnnotationHelper.getHelper(this).isLoadedAnnotation) { - PdfAnnotationHelper.getHelper(this) - .dictionary! - .setProperty(PdfDictionaryProperties.dest, value); + PdfAnnotationHelper.getHelper( + this, + ).dictionary!.setProperty(PdfDictionaryProperties.dest, value); } } } + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } + // Gets the destination of the document link annotation PdfDestination? _obtainDestination() { - PdfDestination? _destination; + PdfDestination? dest; final PdfDictionary dictionary = PdfAnnotationHelper.getHelper(this).dictionary!; final PdfCrossTable crossTable = PdfAnnotationHelper.getHelper(this).crossTable; if (dictionary.containsKey(PdfDictionaryProperties.dest)) { - final IPdfPrimitive? obj = - crossTable.getObject(dictionary[PdfDictionaryProperties.dest]); + final IPdfPrimitive? obj = crossTable.getObject( + dictionary[PdfDictionaryProperties.dest], + ); PdfArray? array; if (obj is PdfArray) { array = obj; } else if (crossTable.document != null && PdfDocumentHelper.getHelper(crossTable.document!).isLoadedDocument) { if (obj is PdfName || obj is PdfString) { - array = PdfDocumentHelper.getHelper(crossTable.document!) - .getNamedDestination(obj!); + array = PdfDocumentHelper.getHelper( + 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 = PdfPageCollectionHelper.getHelper(crossTable.document!.pages) - .getPage(dic); + final PdfDictionary? dic = + crossTable.getObject(array[0]! as PdfReferenceHolder) + as PdfDictionary?; + page = PdfPageCollectionHelper.getHelper( + crossTable.document!.pages, + ).getPage(dic); final PdfName? mode = array[1] as PdfName?; if (mode != null) { if (mode.name == 'XYZ') { @@ -144,37 +170,38 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { (top == null) ? 0 : page.size.height - (top.value!.toDouble()); final double leftValue = (left == null) ? 0 : left.value!.toDouble(); - _destination = PdfDestination(page, Offset(leftValue, topValue)); + dest = PdfDestination(page, Offset(leftValue, topValue)); if (zoom != null) { - _destination.zoom = zoom.value!.toDouble(); + dest.zoom = zoom.value!.toDouble(); } - _destination.mode = PdfDestinationMode.location; + dest.mode = PdfDestinationMode.location; } else if (mode.name == 'Fit' || mode.name == 'FitV') { - _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + dest = PdfDestination(page); + dest.mode = PdfDestinationMode.fitToPage; } else if (mode.name == 'FitH') { late PdfNumber top; if (array[2] is PdfNumber) { top = array[2]! as PdfNumber; } final double topValue = page.size.height - top.value!; - _destination = PdfDestination(page, Offset(0, topValue)); - _destination.mode = PdfDestinationMode.fitH; + dest = PdfDestination(page, Offset(0, topValue)); + dest.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(); - _destination = PdfDestinationHelper.getDestination( - page, PdfRectangle(left, top, width, height)); - _destination.mode = PdfDestinationMode.fitR; + dest = PdfDestinationHelper.getDestination( + page, + PdfRectangle(left, top, width, height), + ); + dest.mode = PdfDestinationMode.fitR; } } } } - } else if (dictionary.containsKey(PdfDictionaryProperties.a) && - (_destination == null)) { + } else if (dictionary.containsKey(PdfDictionaryProperties.a)) { IPdfPrimitive obj = crossTable.getObject(dictionary[PdfDictionaryProperties.a])!; final PdfDictionary destDic = obj as PdfDictionary; @@ -188,8 +215,9 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { } else if (crossTable.document != null && PdfDocumentHelper.getHelper(crossTable.document!).isLoadedDocument) { if (obj is PdfName || obj is PdfString) { - array = PdfDocumentHelper.getHelper(crossTable.document!) - .getNamedDestination(obj); + array = PdfDocumentHelper.getHelper( + crossTable.document!, + ).getNamedDestination(obj); } } if (array != null && array[0] is PdfReferenceHolder) { @@ -198,8 +226,9 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { final IPdfPrimitive? primitiveObj = PdfCrossTable.dereference(holder); final PdfDictionary? dic = primitiveObj as PdfDictionary?; if (dic != null) { - page = PdfPageCollectionHelper.getHelper(crossTable.document!.pages) - .getPage(dic); + page = PdfPageCollectionHelper.getHelper( + crossTable.document!.pages, + ).getPage(dic); } if (page != null) { final PdfName mode = array[1]! as PdfName; @@ -210,8 +239,8 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { } final double topValue = (top == null) ? 0 : page.size.height - (top.value!.toDouble()); - _destination = PdfDestination(page, Offset(0, topValue)); - _destination.mode = PdfDestinationMode.fitH; + dest = PdfDestination(page, Offset(0, topValue)); + dest.mode = PdfDestinationMode.fitH; } else if (mode.name == 'XYZ') { PdfNumber? left; PdfNumber? top; @@ -229,33 +258,38 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { (top == null) ? 0 : page.size.height - (top.value!.toDouble()); final double leftValue = (left == null) ? 0 : left.value!.toDouble(); - _destination = PdfDestination(page, Offset(leftValue, topValue)); + dest = PdfDestination(page, Offset(leftValue, topValue)); if (zoom != null) { - _destination.zoom = zoom.value!.toDouble(); + dest.zoom = zoom.value!.toDouble(); } - _destination.mode = PdfDestinationMode.location; + dest.mode = PdfDestinationMode.location; } else if (mode.name == 'FitR') { if (array.count == 6) { final PdfNumber left = array[2]! as PdfNumber; final PdfNumber bottom = array[3]! as PdfNumber; final PdfNumber right = array[4]! as PdfNumber; final PdfNumber top = array[5]! as PdfNumber; - _destination = PdfDestinationHelper.getDestination( - page, - PdfRectangle(left.value!.toDouble(), bottom.value!.toDouble(), - right.value!.toDouble(), top.value!.toDouble())); - _destination.mode = PdfDestinationMode.fitR; + dest = PdfDestinationHelper.getDestination( + page, + PdfRectangle( + left.value!.toDouble(), + bottom.value!.toDouble(), + right.value!.toDouble(), + top.value!.toDouble(), + ), + ); + dest.mode = PdfDestinationMode.fitR; } } else { if (mode.name == 'Fit' || mode.name == 'FitV') { - _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + dest = PdfDestination(page); + dest.mode = PdfDestinationMode.fitToPage; } } } } } - return _destination; + return dest; } } @@ -263,10 +297,12 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { class PdfDocumentLinkAnnotationHelper extends PdfLinkAnnotationHelper { /// internal constructor PdfDocumentLinkAnnotationHelper(this.documentLinkHelper, Rect bounds) - : super(documentLinkHelper, bounds); - PdfDocumentLinkAnnotationHelper._(this.documentLinkHelper, - PdfDictionary dictionary, PdfCrossTable crossTable) - : super.load(documentLinkHelper, dictionary, crossTable); + : super(documentLinkHelper, bounds); + PdfDocumentLinkAnnotationHelper._( + this.documentLinkHelper, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super.load(documentLinkHelper, dictionary, crossTable); /// internal field PdfDocumentLinkAnnotation documentLinkHelper; @@ -282,20 +318,24 @@ class PdfDocumentLinkAnnotationHelper extends PdfLinkAnnotationHelper { void save() { if (destination != null) { PdfAnnotationHelper.getHelper(base).dictionary!.setProperty( - PdfName(PdfDictionaryProperties.dest), - IPdfWrapper.getElement(destination!)); + PdfName(PdfDictionaryProperties.dest), + IPdfWrapper.getElement(destination!), + ); } } /// internal method static PdfDocumentLinkAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfDocumentLinkAnnotation._(dictionary, crossTable); } /// internal method static PdfDocumentLinkAnnotationHelper getHelper( - PdfDocumentLinkAnnotation annotation) { + PdfDocumentLinkAnnotation annotation, + ) { return annotation._helper; } } 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 5d460a5eb..fc78ded91 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 @@ -19,6 +19,7 @@ import '../primitives/pdf_name.dart'; import '../primitives/pdf_number.dart'; import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_stream.dart'; +import 'enum.dart'; import 'pdf_annotation.dart'; import 'pdf_annotation_collection.dart'; import 'pdf_appearance.dart'; @@ -40,31 +41,43 @@ class PdfEllipseAnnotation extends PdfAnnotation { /// //Add annotation to the page. /// page.annotations.add(ellipseAnnotation); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` - PdfEllipseAnnotation(Rect bounds, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - DateTime? modifiedDate, - double? opacity, - bool? setAppearance}) { - _helper = PdfEllipseAnnotationHelper(this, bounds, text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); + PdfEllipseAnnotation( + Rect bounds, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + List? flags, + bool? setAppearance, + }) { + _helper = PdfEllipseAnnotationHelper( + this, + bounds, + text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); } PdfEllipseAnnotation._( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { _helper = PdfEllipseAnnotationHelper._(this, dictionary, crossTable); this.text = text; } @@ -73,6 +86,32 @@ class PdfEllipseAnnotation extends PdfAnnotation { late PdfEllipseAnnotationHelper _helper; // properties + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } + + /// Gets the inner color of the annotation. + PdfColor get innerColor => _helper.innerColor; + + /// Sets the inner color of the annotation. + set innerColor(PdfColor value) { + _helper.innerColor = value; + } + IPdfPrimitive? get _element => PdfAnnotationHelper.getHelper(this).dictionary; set _element(IPdfPrimitive? value) { if (value != null && value is PdfDictionary) { @@ -84,34 +123,44 @@ class PdfEllipseAnnotation extends PdfAnnotation { /// [PdfEllipseAnnotation] helper class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { /// internal constructor - PdfEllipseAnnotationHelper(this.annotation, Rect bounds, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - DateTime? modifiedDate, - double? opacity, - bool? setAppearance}) - : super(annotation) { + PdfEllipseAnnotationHelper( + this.annotation, + Rect bounds, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + List? flags, + bool? setAppearance, + }) : super(annotation) { initializeAnnotation( - bounds: bounds, - text: text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); - dictionary!.setProperty(PdfDictionaryProperties.subtype, - PdfName(PdfDictionaryProperties.circle)); + bounds: bounds, + text: text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(PdfDictionaryProperties.circle), + ); } PdfEllipseAnnotationHelper._( - this.annotation, PdfDictionary dictionary, PdfCrossTable crossTable) - : super(annotation) { + this.annotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(annotation) { initializeExistingAnnotation(dictionary, crossTable); } @@ -120,10 +169,12 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { /// internal method void save() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); - if (PdfAnnotationCollectionHelper.getHelper(annotation.page!.annotations) - .flatten) { + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); + if (PdfAnnotationCollectionHelper.getHelper( + annotation.page!.annotations, + ).flatten) { helper.flatten = true; } final PdfAppearance? pdfAppearance = helper.appearance; @@ -144,15 +195,19 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { } else { if (appearance != null) { annotation.appearance.normal = appearance; - helper.dictionary!.setProperty(PdfDictionaryProperties.ap, - PdfReferenceHolder(annotation.appearance)); + helper.dictionary!.setProperty( + PdfDictionaryProperties.ap, + PdfReferenceHolder(annotation.appearance), + ); } } } if (!helper.flatten && !isLoadedAnnotation) { helper.saveAnnotation(); - helper.dictionary! - .setProperty(PdfDictionaryProperties.bs, annotation.border); + helper.dictionary!.setProperty( + PdfDictionaryProperties.bs, + annotation.border, + ); } if (helper.flattenPopups) { helper.flattenPopup(); @@ -160,32 +215,41 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { } void _flattenAnnotation(PdfPage? page, PdfTemplate? appearance) { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); if (helper.isLoadedAnnotation) { - final bool isContainsAP = - helper.dictionary!.containsKey(PdfDictionaryProperties.ap); + final bool isContainsAP = helper.dictionary!.containsKey( + PdfDictionaryProperties.ap, + ); if (appearance == null) { if (isContainsAP) { - PdfDictionary? appearanceDictionary = PdfCrossTable.dereference( - helper.dictionary![PdfDictionaryProperties.ap]) as PdfDictionary?; + PdfDictionary? appearanceDictionary = + PdfCrossTable.dereference( + helper.dictionary![PdfDictionaryProperties.ap], + ) + as PdfDictionary?; if (appearanceDictionary != null) { - appearanceDictionary = PdfCrossTable.dereference( - appearanceDictionary[PdfDictionaryProperties.n]) - as PdfDictionary?; + appearanceDictionary = + PdfCrossTable.dereference( + appearanceDictionary[PdfDictionaryProperties.n], + ) + as PdfDictionary?; if (appearanceDictionary != null) { final PdfStream appearanceStream = appearanceDictionary as PdfStream; appearance = PdfTemplateHelper.fromPdfStream(appearanceStream); - final bool isNormalMatrix = - helper.validateTemplateMatrix(appearanceDictionary); + final bool isNormalMatrix = helper.validateTemplateMatrix( + appearanceDictionary, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } else { annotation.setAppearance = true; appearance = _createAppearance(); if (appearance != null) { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } @@ -195,50 +259,68 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { appearance = _createAppearance(); if (appearance != null) { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } } else { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { page!.graphics.save(); final Rect rectangle = helper.calculateTemplateBounds( - annotation.bounds, page, appearance, true); + annotation.bounds, + page, + appearance, + true, + ); if (annotation.opacity < 1) { - page.graphics - .setTransparency(annotation.opacity, mode: PdfBlendMode.normal); + page.graphics.setTransparency(annotation.opacity); } page.graphics.drawPdfTemplate( - appearance!, Offset(rectangle.left, rectangle.top), rectangle.size); + appearance!, + Offset(rectangle.left, rectangle.top), + rectangle.size, + ); page.annotations.remove(annotation); page.graphics.restore(); } } PdfTemplate? _createAppearance() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); final bool isLoadedAnnotation = helper.isLoadedAnnotation; if (isLoadedAnnotation && !annotation.setAppearance) { return null; } - final PdfRectangle nativeRectangle = - PdfRectangle(0, 0, annotation.bounds.width, annotation.bounds.height); - final PdfTemplate template = - PdfTemplateHelper.fromRect(nativeRectangle.rect); - helper.setMatrix(PdfTemplateHelper.getHelper(template).content); + final PdfRectangle nativeRectangle = PdfRectangle( + 0, + 0, + annotation.bounds.width, + annotation.bounds.height, + ); + final PdfTemplate template = PdfTemplateHelper.fromRect( + nativeRectangle.rect, + ); + PdfAnnotationHelper.setMatrixToZeroRotation( + PdfTemplateHelper.getHelper(template).content, + ); if (isLoadedAnnotation && helper.dictionary!.containsKey(PdfDictionaryProperties.be)) { PdfTemplateHelper.getHelper(template).writeTransformation = false; } final PaintParams paintParams = PaintParams(); final double borderWidth = annotation.border.width / 2; - final PdfPen mBorderPen = - PdfPen(annotation.color, width: annotation.border.width); + final PdfPen mBorderPen = PdfPen( + annotation.color, + width: annotation.border.width, + ); if (annotation.border.width > 0 && PdfColorHelper.getHelper(annotation.color).alpha != 0) { paintParams.borderPen = mBorderPen; @@ -255,28 +337,42 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { graphics.setTransparency(annotation.opacity); } if (isLoadedAnnotation) { - final PdfRectangle rectangle = - _obtainStyle(mBorderPen, nativeRectangle, borderWidth); + final PdfRectangle rectangle = _obtainStyle( + mBorderPen, + nativeRectangle, + borderWidth, + ); if (helper.dictionary!.containsKey(PdfDictionaryProperties.be)) { _drawAppearance(rectangle, borderWidth, graphics, paintParams); } else { graphics!.drawEllipse( - Rect.fromLTWH(rectangle.x + borderWidth, rectangle.y, - rectangle.width - annotation.border.width, rectangle.height), - pen: paintParams.borderPen, - brush: paintParams.backBrush); + Rect.fromLTWH( + rectangle.x + borderWidth, + rectangle.y, + rectangle.width - annotation.border.width, + rectangle.height, + ), + pen: paintParams.borderPen, + brush: paintParams.backBrush, + ); } } else { - final Rect rect = Rect.fromLTWH(nativeRectangle.left, nativeRectangle.top, - nativeRectangle.width, nativeRectangle.height); + final Rect rect = Rect.fromLTWH( + nativeRectangle.left, + nativeRectangle.top, + nativeRectangle.width, + nativeRectangle.height, + ); graphics!.drawEllipse( - Rect.fromLTWH( - rect.left + borderWidth, - rect.top + borderWidth, - rect.width - annotation.border.width, - rect.height - annotation.border.width), - pen: paintParams.borderPen, - brush: paintParams.backBrush); + Rect.fromLTWH( + rect.left + borderWidth, + rect.top + borderWidth, + rect.width - annotation.border.width, + rect.height - annotation.border.width, + ), + pen: paintParams.borderPen, + brush: paintParams.backBrush, + ); } if (annotation.opacity < 1) { graphics!.restore(); @@ -286,7 +382,10 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { // Obtain Style for annotation PdfRectangle _obtainStyle( - PdfPen mBorderPen, PdfRectangle rectangle, double borderWidth) { + PdfPen mBorderPen, + PdfRectangle rectangle, + double borderWidth, + ) { final PdfDictionary dictionary = PdfAnnotationHelper.getHelper(annotation).dictionary!; if (dictionary.containsKey(PdfDictionaryProperties.bs)) { @@ -302,7 +401,8 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { final List dashPattern = []; for (int i = 0; i < dashPatternArray.count; i++) { dashPattern.add( - (dashPatternArray.elements[i]! as PdfNumber).value!.toDouble()); + (dashPatternArray.elements[i]! as PdfNumber).value!.toDouble(), + ); } mBorderPen.dashStyle = PdfDashStyle.dash; mBorderPen.dashPattern = dashPattern; @@ -332,32 +432,43 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { } // Draw appearance for annotation - void _drawAppearance(PdfRectangle rectangle, double borderWidth, - PdfGraphics? graphics, PaintParams paintParams) { + void _drawAppearance( + PdfRectangle rectangle, + double borderWidth, + PdfGraphics? graphics, + PaintParams paintParams, + ) { final PdfPath graphicsPath = PdfPath(); - graphicsPath.addEllipse(Rect.fromLTWH( + graphicsPath.addEllipse( + Rect.fromLTWH( rectangle.x + borderWidth, -rectangle.y - rectangle.height, rectangle.width - annotation.border.width, - rectangle.height)); + rectangle.height, + ), + ); double? radius = 0; - if (PdfAnnotationHelper.getHelper(annotation) - .dictionary! - .containsKey(PdfDictionaryProperties.rd)) { - final PdfArray? rdArray = PdfCrossTable.dereference( - PdfAnnotationHelper.getHelper(annotation) - .dictionary! - .items![PdfName(PdfDictionaryProperties.rd)]) as PdfArray?; + if (PdfAnnotationHelper.getHelper( + annotation, + ).dictionary!.containsKey(PdfDictionaryProperties.rd)) { + final PdfArray? rdArray = + PdfCrossTable.dereference( + PdfAnnotationHelper.getHelper( + annotation, + ).dictionary!.items![PdfName(PdfDictionaryProperties.rd)], + ) + as PdfArray?; if (rdArray != null) { radius = (rdArray.elements[0]! as PdfNumber).value as double?; } } if (radius! > 0) { final PdfRectangle rect = PdfRectangle( - rectangle.x + borderWidth, - -rectangle.y - rectangle.height, - rectangle.width - annotation.border.width, - rectangle.height); + rectangle.x + borderWidth, + -rectangle.y - rectangle.height, + rectangle.width - annotation.border.width, + rectangle.height, + ); final List startPointList = []; final List controlPointList = []; final List endPointList = []; @@ -380,56 +491,91 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { for (int i = 0; i < controlPointList.length; i++) { _createBezier( - startPointList[i], controlPointList[i], endPointList[i], points); + startPointList[i], + controlPointList[i], + endPointList[i], + points, + ); } PdfAnnotationHelper.getHelper(annotation).drawCloudStyle( - graphics!, - paintParams.backBrush, - paintParams.borderPen, - radius, - 0.833, - points, - false); + graphics!, + paintParams.backBrush, + paintParams.borderPen, + radius, + 0.833, + points, + false, + ); startPointList.clear(); controlPointList.clear(); endPointList.clear(); points.clear(); } else { graphics!.drawEllipse( - Rect.fromLTWH(rectangle.x + borderWidth, -rectangle.y, - rectangle.width - annotation.border.width, -rectangle.height), - pen: paintParams.borderPen, - brush: paintParams.backBrush); + Rect.fromLTWH( + rectangle.x + borderWidth, + -rectangle.y, + rectangle.width - annotation.border.width, + -rectangle.height, + ), + pen: paintParams.borderPen, + brush: paintParams.backBrush, + ); } } // Create bezier curve void _createBezier( - Offset ctrl1, Offset ctrl2, Offset ctrl3, List bezierPoints) { + Offset ctrl1, + Offset ctrl2, + Offset ctrl3, + List bezierPoints, + ) { bezierPoints.add(ctrl1); // add the first control point _populateBezierPoints(ctrl1, ctrl2, ctrl3, 0, bezierPoints); bezierPoints.add(ctrl3); // add the last control point } // calculate bezier points - void _populateBezierPoints(Offset ctrl1, Offset ctrl2, Offset ctrl3, - int currentIteration, List bezierPoints) { + void _populateBezierPoints( + Offset ctrl1, + Offset ctrl2, + Offset ctrl3, + int currentIteration, + List bezierPoints, + ) { if (currentIteration < 2) { //calculate next mid points - final Offset midPoint1 = - Offset((ctrl1.dx + ctrl2.dx) / 2, (ctrl1.dy + ctrl2.dy) / 2); - final Offset midPoint2 = - Offset((ctrl2.dx + ctrl3.dx) / 2, (ctrl2.dy + ctrl3.dy) / 2); + final Offset midPoint1 = Offset( + (ctrl1.dx + ctrl2.dx) / 2, + (ctrl1.dy + ctrl2.dy) / 2, + ); + final Offset midPoint2 = Offset( + (ctrl2.dx + ctrl3.dx) / 2, + (ctrl2.dy + ctrl3.dy) / 2, + ); final Offset midPoint3 = Offset( - (midPoint1.dx + midPoint2.dx) / 2, (midPoint1.dy + midPoint2.dy) / 2); + (midPoint1.dx + midPoint2.dx) / 2, + (midPoint1.dy + midPoint2.dy) / 2, + ); //the next control point currentIteration++; - _populateBezierPoints(ctrl1, midPoint1, midPoint3, currentIteration, - bezierPoints); //left branch + _populateBezierPoints( + ctrl1, + midPoint1, + midPoint3, + currentIteration, + bezierPoints, + ); //left branch bezierPoints.add(midPoint3); //add the next control point - _populateBezierPoints(midPoint3, midPoint2, ctrl3, currentIteration, - bezierPoints); //right branch + _populateBezierPoints( + midPoint3, + midPoint2, + ctrl3, + currentIteration, + bezierPoints, + ); //right branch } } @@ -446,7 +592,10 @@ class PdfEllipseAnnotationHelper extends PdfAnnotationHelper { /// internal method static PdfEllipseAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { return PdfEllipseAnnotation._(dictionary, crossTable, text); } 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 7d92cd75b..fc0bf5321 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 @@ -52,34 +52,43 @@ class PdfLineAnnotation extends PdfAnnotation { /// lineCaption: true, /// captionType: PdfLineCaptionType.top); /// page.annotations.add(lineAnnotation); - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` - PdfLineAnnotation(List linePoints, String text, - {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}) { - _helper = PdfLineAnnotationHelper(this, linePoints, text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); + PdfLineAnnotation( + List linePoints, + String text, { + 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, + List? flags, + }) { + _helper = PdfLineAnnotationHelper( + this, + linePoints, + text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); this.beginLineStyle = beginLineStyle; this.endLineStyle = endLineStyle; this.captionType = captionType; @@ -94,7 +103,10 @@ class PdfLineAnnotation extends PdfAnnotation { } PdfLineAnnotation._( - PdfDictionary dictionary, PdfCrossTable crossTable, String annotText) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String annotText, + ) { _helper = PdfLineAnnotationHelper._(this, dictionary, crossTable); text = annotText; } @@ -110,9 +122,10 @@ class PdfLineAnnotation extends PdfAnnotation { // Properties /// Gets the leader line. - int get leaderLine => PdfAnnotationHelper.getHelper(this).isLoadedAnnotation - ? _helper._obtainLeaderLine() - : _helper.leaderLine; + int get leaderLine => + PdfAnnotationHelper.getHelper(this).isLoadedAnnotation + ? _helper._obtainLeaderLine() + : _helper.leaderLine; /// Sets the leader line. set leaderLine(int value) { @@ -137,8 +150,10 @@ class PdfLineAnnotation extends PdfAnnotation { _helper.points = value; _helper.linePoints = PdfArray(_helper.points); if (helper.isLoadedAnnotation) { - helper.dictionary! - .setProperty(PdfDictionaryProperties.l, _helper.linePoints); + helper.dictionary!.setProperty( + PdfDictionaryProperties.l, + _helper.linePoints, + ); } } @@ -155,14 +170,17 @@ class PdfLineAnnotation extends PdfAnnotation { _lineIntent = value; } else { helper.dictionary!.setName( - PdfName(PdfDictionaryProperties.it), helper.getEnumName(value)); + PdfName(PdfDictionaryProperties.it), + helper.getEnumName(value), + ); } } /// Gets the line caption of the annotation. - bool get lineCaption => PdfAnnotationHelper.getHelper(this).isLoadedAnnotation - ? _helper._obtainLineCaption() - : _lineCaption; + bool get lineCaption => + PdfAnnotationHelper.getHelper(this).isLoadedAnnotation + ? _helper._obtainLineCaption() + : _lineCaption; /// Sets the line caption of the annotation. set lineCaption(bool value) { @@ -202,17 +220,21 @@ class PdfLineAnnotation extends PdfAnnotation { if (!helper.isLoadedAnnotation) { _beginLineStyle = value; } else { - PdfArray? _lineStyle = _helper._obtainLineStyle(); - if (_lineStyle == null) { - _lineStyle = PdfArray(); + PdfArray? lineStyle = _helper._obtainLineStyle(); + if (lineStyle == null) { + lineStyle = PdfArray(); } else { - _lineStyle.elements.removeAt(0); + lineStyle.elements.removeAt(0); } - _lineStyle.insert( - 0, - PdfName(helper.getEnumName( - _helper._getLineStyle(helper.getEnumName(value.toString()))))); - helper.dictionary!.setProperty(PdfDictionaryProperties.le, _lineStyle); + lineStyle.insert( + 0, + PdfName( + helper.getEnumName( + _helper._getLineStyle(helper.getEnumName(value.toString())), + ), + ), + ); + helper.dictionary!.setProperty(PdfDictionaryProperties.le, lineStyle); } } @@ -228,17 +250,21 @@ class PdfLineAnnotation extends PdfAnnotation { if (!helper.isLoadedAnnotation) { _endLineStyle = value; } else { - PdfArray? _lineStyle = _helper._obtainLineStyle(); - if (_lineStyle == null) { - _lineStyle = PdfArray(); + PdfArray? lineStyle = _helper._obtainLineStyle(); + if (lineStyle == null) { + lineStyle = PdfArray(); } else { - _lineStyle.elements.removeAt(1); + lineStyle.elements.removeAt(1); } - _lineStyle.insert( - 1, - PdfName(helper.getEnumName( - _helper._getLineStyle(helper.getEnumName(value.toString()))))); - helper.dictionary!.setProperty(PdfDictionaryProperties.le, _lineStyle); + lineStyle.insert( + 1, + PdfName( + helper.getEnumName( + _helper._getLineStyle(helper.getEnumName(value.toString())), + ), + ), + ); + helper.dictionary!.setProperty(PdfDictionaryProperties.le, lineStyle); } } @@ -255,9 +281,13 @@ class PdfLineAnnotation extends PdfAnnotation { _captionType = value; } else { helper.dictionary!.setProperty( - PdfDictionaryProperties.cp, - PdfName(helper.getEnumName( - _helper._getCaptionType(helper.getEnumName(value.toString()))))); + PdfDictionaryProperties.cp, + PdfName( + helper.getEnumName( + _helper._getCaptionType(helper.getEnumName(value.toString())), + ), + ), + ); } } @@ -271,11 +301,39 @@ class PdfLineAnnotation extends PdfAnnotation { final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper(this); if (helper.textValue != value) { helper.textValue = value; - helper.dictionary! - .setString(PdfDictionaryProperties.contents, helper.textValue); + helper.dictionary!.setString( + PdfDictionaryProperties.contents, + helper.textValue, + ); } } + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } + + /// Gets the inner color of the annotation. + PdfColor get innerColor => _helper.innerColor; + + /// Sets the inner color of the annotation. + set innerColor(PdfColor value) { + _helper.innerColor = value; + } + IPdfPrimitive? get _element => PdfAnnotationHelper.getHelper(this).dictionary; set _element(IPdfPrimitive? value) { @@ -289,34 +347,43 @@ class PdfLineAnnotation extends PdfAnnotation { class PdfLineAnnotationHelper extends PdfAnnotationHelper { /// internal constructor PdfLineAnnotationHelper( - this.lineAnnotation, List linePoints, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - DateTime? modifiedDate, - double? opacity, - bool? setAppearance}) - : super(lineAnnotation) { + this.lineAnnotation, + List linePoints, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + List? flags, + bool? setAppearance, + }) : super(lineAnnotation) { initializeAnnotation( - text: text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); + text: text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); this.linePoints = PdfArray(linePoints); points = linePoints; dictionary!.setProperty( - PdfDictionaryProperties.subtype, PdfName(PdfDictionaryProperties.line)); + PdfDictionaryProperties.subtype, + PdfName(PdfDictionaryProperties.line), + ); } PdfLineAnnotationHelper._( - this.lineAnnotation, PdfDictionary dictionary, PdfCrossTable crossTable) - : super(lineAnnotation) { + this.lineAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(lineAnnotation) { initializeExistingAnnotation(dictionary, crossTable); } @@ -346,11 +413,12 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { /// internal method void save() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); if (PdfAnnotationCollectionHelper.getHelper( - lineAnnotation.page!.annotations) - .flatten) { + lineAnnotation.page!.annotations, + ).flatten) { helper.flatten = true; } if (helper.flatten || lineAnnotation.setAppearance) { @@ -364,8 +432,10 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { } else { if (appearance != null) { lineAnnotation.appearance.normal = appearance; - helper.dictionary!.setProperty(PdfDictionaryProperties.ap, - PdfReferenceHolder(lineAnnotation.appearance)); + helper.dictionary!.setProperty( + PdfDictionaryProperties.ap, + PdfReferenceHolder(lineAnnotation.appearance), + ); } } } @@ -379,18 +449,24 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { } void _savePdfLineDictionary() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); helper.saveAnnotation(); lineStyle = PdfArray(); if (lineStyle!.elements.isNotEmpty) { lineStyle!.insert( - 0, PdfName(helper.getEnumName(lineAnnotation.beginLineStyle))); - lineStyle! - .insert(1, PdfName(helper.getEnumName(lineAnnotation.endLineStyle))); + 0, + PdfName(helper.getEnumName(lineAnnotation.beginLineStyle)), + ); + lineStyle!.insert( + 1, + PdfName(helper.getEnumName(lineAnnotation.endLineStyle)), + ); } else { - lineStyle! - .add(PdfName(helper.getEnumName(lineAnnotation.beginLineStyle))); + lineStyle!.add( + PdfName(helper.getEnumName(lineAnnotation.beginLineStyle)), + ); lineStyle!.add(PdfName(helper.getEnumName(lineAnnotation.endLineStyle))); } final PdfDictionary dictionary = helper.dictionary!; @@ -410,58 +486,77 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { dictionary.setProperty(PdfDictionaryProperties.bs, lineAnnotation.border); if (!lineAnnotation.innerColor.isEmpty && PdfColorHelper.getHelper(lineAnnotation.innerColor).alpha != 0) { - dictionary.setProperty(PdfDictionaryProperties.ic, - PdfColorHelper.toArray(lineAnnotation.innerColor, PdfColorSpace.rgb)); + dictionary.setProperty( + PdfDictionaryProperties.ic, + PdfColorHelper.toArray(lineAnnotation.innerColor), + ); } - dictionary[PdfDictionaryProperties.c] = - PdfColorHelper.toArray(lineAnnotation.color, PdfColorSpace.rgb); + dictionary[PdfDictionaryProperties.c] = PdfColorHelper.toArray( + lineAnnotation.color, + ); - dictionary.setProperty(PdfDictionaryProperties.it, - PdfName(helper.getEnumName(lineAnnotation.lineIntent))); + dictionary.setProperty( + PdfDictionaryProperties.it, + PdfName(helper.getEnumName(lineAnnotation.lineIntent)), + ); dictionary.setProperty( - PdfDictionaryProperties.lle, PdfNumber(lineAnnotation.leaderLineExt)); + PdfDictionaryProperties.lle, + PdfNumber(lineAnnotation.leaderLineExt), + ); dictionary.setProperty(PdfDictionaryProperties.ll, PdfNumber(leaderLine)); - dictionary.setProperty(PdfDictionaryProperties.cp, - PdfName(helper.getEnumName(lineAnnotation.captionType))); + dictionary.setProperty( + PdfDictionaryProperties.cp, + PdfName(helper.getEnumName(lineAnnotation.captionType)), + ); dictionary.setProperty( - PdfDictionaryProperties.cap, PdfBoolean(lineAnnotation.lineCaption)); + PdfDictionaryProperties.cap, + PdfBoolean(lineAnnotation.lineCaption), + ); - dictionary.setProperty(PdfDictionaryProperties.rect, - PdfArray.fromRectangle(_obtainLineBounds())); + dictionary.setProperty( + PdfDictionaryProperties.rect, + PdfArray.fromRectangle(_obtainLineBounds()), + ); } void _flattenAnnotation(PdfPage? page, PdfTemplate? appearance) { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); final PdfDictionary dictionary = helper.dictionary!; if (helper.isLoadedAnnotation) { - final bool isContainsAP = - dictionary.containsKey(PdfDictionaryProperties.ap); + final bool isContainsAP = dictionary.containsKey( + PdfDictionaryProperties.ap, + ); if (isContainsAP && appearance == null) { PdfDictionary? appearanceDictionary = PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.ap]) as PdfDictionary?; if (appearanceDictionary != null) { - appearanceDictionary = PdfCrossTable.dereference( - appearanceDictionary[PdfDictionaryProperties.n]) - as PdfDictionary?; + appearanceDictionary = + PdfCrossTable.dereference( + appearanceDictionary[PdfDictionaryProperties.n], + ) + as PdfDictionary?; if (appearanceDictionary != null) { final PdfStream appearanceStream = appearanceDictionary as PdfStream; appearance = PdfTemplateHelper.fromPdfStream(appearanceStream); - final bool isNormalMatrix = - helper.validateTemplateMatrix(appearanceDictionary); + final bool isNormalMatrix = helper.validateTemplateMatrix( + appearanceDictionary, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } else { lineAnnotation.setAppearance = true; appearance = _createAppearance(); if (appearance != null) { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } @@ -471,43 +566,57 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { appearance = _createAppearance(); if (appearance != null) { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance!).content); + PdfTemplateHelper.getHelper(appearance!).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { page!.graphics.save(); final Rect rectangle = helper.calculateTemplateBounds( - lineAnnotation.bounds, page, appearance, true); + lineAnnotation.bounds, + page, + appearance, + true, + ); if (lineAnnotation.opacity < 1) { page.graphics.setTransparency(lineAnnotation.opacity); } page.graphics.drawPdfTemplate( - appearance!, Offset(rectangle.left, rectangle.top), rectangle.size); + appearance!, + Offset(rectangle.left, rectangle.top), + rectangle.size, + ); page.annotations.remove(lineAnnotation); page.graphics.restore(); } } PdfTemplate? _createAppearance() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); final bool isLoadedAnnotation = helper.isLoadedAnnotation; if (isLoadedAnnotation && !lineAnnotation.setAppearance) { return null; } final Rect nativeRectangle = _obtainLineBounds().rect; final PdfTemplate template = PdfTemplateHelper.fromRect(nativeRectangle); - helper.setMatrix(PdfTemplateHelper.getHelper(template).content); + PdfAnnotationHelper.setMatrixToZeroRotation( + PdfTemplateHelper.getHelper(template).content, + ); PdfTemplateHelper.getHelper(template).writeTransformation = false; final PaintParams paintParams = PaintParams(); final PdfGraphics? graphics = template.graphics; - final PdfPen mBorderPen = - PdfPen(lineAnnotation.color, width: lineAnnotation.border.width); + final PdfPen mBorderPen = PdfPen( + lineAnnotation.color, + width: lineAnnotation.border.width, + ); if (lineAnnotation.border.borderStyle == PdfBorderStyle.dashed) { mBorderPen.dashStyle = PdfDashStyle.dash; } else if (lineAnnotation.border.borderStyle == PdfBorderStyle.dot) { @@ -516,8 +625,10 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { paintParams.borderPen = mBorderPen; paintParams.foreBrush = PdfSolidBrush(lineAnnotation.color); final PdfFont mFont = PdfStandardFont( - PdfFontFamily.helvetica, isLoadedAnnotation ? 10 : 9, - style: PdfFontStyle.regular); + PdfFontFamily.helvetica, + isLoadedAnnotation ? 10 : 9, + style: PdfFontStyle.regular, + ); final PdfStringFormat format = PdfStringFormat(); format.alignment = PdfTextAlignment.center; format.lineAlignment = PdfVerticalAlignment.middle; @@ -550,34 +661,54 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { } final List x1y1 = [x1, y1]; final List x2y2 = [x2, y2]; - final double line = leadLine + + final double line = + leadLine + (isLoadedAnnotation ? _obtainLeaderOffset().toDouble() : 0); - final List startingPoint = - helper.getAxisValue(x1y1, lineAngle + 90, line); - final List endingPoint = - helper.getAxisValue(x2y2, lineAngle + 90, line); + final List startingPoint = helper.getAxisValue( + x1y1, + lineAngle + 90, + line, + ); + final List endingPoint = helper.getAxisValue( + x2y2, + lineAngle + 90, + line, + ); final double lineDistance = sqrt( - pow(endingPoint[0] - startingPoint[0], 2) + - pow(endingPoint[1] - startingPoint[1], 2)); + pow(endingPoint[0] - startingPoint[0], 2) + + pow(endingPoint[1] - startingPoint[1], 2), + ); final double centerWidth = lineDistance / 2 - ((lineWidth / 2) + lineAnnotation.border.width); - final List middlePoint1 = - helper.getAxisValue(startingPoint, angle, centerWidth); - final List middlePoint2 = - helper.getAxisValue(endingPoint, angle + 180, centerWidth); + final List middlePoint1 = helper.getAxisValue( + startingPoint, + angle, + centerWidth, + ); + final List middlePoint2 = helper.getAxisValue( + endingPoint, + angle + 180, + centerWidth, + ); List lineStartingPoint; List lineEndingPoint; if (lineAnnotation.beginLineStyle == PdfLineEndingStyle.openArrow || lineAnnotation.beginLineStyle == PdfLineEndingStyle.closedArrow) { lineStartingPoint = helper.getAxisValue( - startingPoint, angle, lineAnnotation.border.width); + startingPoint, + angle, + lineAnnotation.border.width, + ); } else { lineStartingPoint = startingPoint; } if (lineAnnotation.endLineStyle == PdfLineEndingStyle.openArrow || lineAnnotation.endLineStyle == PdfLineEndingStyle.closedArrow) { lineEndingPoint = helper.getAxisValue( - endingPoint, angle, -lineAnnotation.border.width); + endingPoint, + angle, + -lineAnnotation.border.width, + ); } else { lineEndingPoint = endingPoint; } @@ -590,18 +721,21 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { caption == 'Top' || !lineAnnotation.lineCaption) { graphics!.drawLine( - mBorderPen, - Offset(lineStartingPoint[0], -lineStartingPoint[1]), - Offset(lineEndingPoint[0], -lineEndingPoint[1])); + mBorderPen, + Offset(lineStartingPoint[0], -lineStartingPoint[1]), + Offset(lineEndingPoint[0], -lineEndingPoint[1]), + ); } else { graphics!.drawLine( - mBorderPen, - Offset(lineStartingPoint[0], -lineStartingPoint[1]), - Offset(middlePoint1[0], -middlePoint1[1])); + mBorderPen, + Offset(lineStartingPoint[0], -lineStartingPoint[1]), + Offset(middlePoint1[0], -middlePoint1[1]), + ); graphics.drawLine( - mBorderPen, - Offset(lineEndingPoint[0], -lineEndingPoint[1]), - Offset(middlePoint2[0], -middlePoint2[1])); + mBorderPen, + Offset(lineEndingPoint[0], -lineEndingPoint[1]), + Offset(middlePoint2[0], -middlePoint2[1]), + ); } if (lineAnnotation.opacity < 1) { graphics.restore(); @@ -610,88 +744,149 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { final PdfBrush backBrush = PdfSolidBrush(lineAnnotation.innerColor); final PdfArray lineStyle = PdfArray(); lineStyle.insert( - 0, PdfName(helper.getEnumName(lineAnnotation.beginLineStyle))); + 0, + PdfName(helper.getEnumName(lineAnnotation.beginLineStyle)), + ); lineStyle.insert( - 1, PdfName(helper.getEnumName(lineAnnotation.endLineStyle))); + 1, + PdfName(helper.getEnumName(lineAnnotation.endLineStyle)), + ); final double borderLength = lineAnnotation.border.width; - helper.setLineEndingStyles(startingPoint, endingPoint, graphics, angle, - mBorderPen, backBrush, lineStyle, borderLength); + helper.setLineEndingStyles( + startingPoint, + endingPoint, + graphics, + angle, + mBorderPen, + backBrush, + lineStyle, + borderLength, + ); //Set leader extension. - final List beginLineExt = helper.getAxisValue(startingPoint, - lineAngle + 90, lineAnnotation.leaderLineExt.toDouble()); - graphics.drawLine(mBorderPen, Offset(startingPoint[0], -startingPoint[1]), - Offset(beginLineExt[0], -beginLineExt[1])); + final List beginLineExt = helper.getAxisValue( + startingPoint, + lineAngle + 90, + lineAnnotation.leaderLineExt.toDouble(), + ); + graphics.drawLine( + mBorderPen, + Offset(startingPoint[0], -startingPoint[1]), + Offset(beginLineExt[0], -beginLineExt[1]), + ); final List endLineExt = helper.getAxisValue( - endingPoint, lineAngle + 90, lineAnnotation.leaderLineExt.toDouble()); - graphics.drawLine(mBorderPen, Offset(endingPoint[0], -endingPoint[1]), - Offset(endLineExt[0], -endLineExt[1])); + endingPoint, + lineAngle + 90, + lineAnnotation.leaderLineExt.toDouble(), + ); + graphics.drawLine( + mBorderPen, + Offset(endingPoint[0], -endingPoint[1]), + Offset(endLineExt[0], -endLineExt[1]), + ); //Set leader line - final List beginLeaderLine = - helper.getAxisValue(startingPoint, lineAngle - 90, leadLine); - graphics.drawLine(mBorderPen, Offset(startingPoint[0], -startingPoint[1]), - Offset(beginLeaderLine[0], -beginLeaderLine[1])); - final List endLeaderLine = - helper.getAxisValue(endingPoint, lineAngle - 90, leadLine); - graphics.drawLine(mBorderPen, Offset(endingPoint[0], -endingPoint[1]), - Offset(endLeaderLine[0], -endLeaderLine[1])); + final List beginLeaderLine = helper.getAxisValue( + startingPoint, + lineAngle - 90, + leadLine, + ); + graphics.drawLine( + mBorderPen, + Offset(startingPoint[0], -startingPoint[1]), + Offset(beginLeaderLine[0], -beginLeaderLine[1]), + ); + final List endLeaderLine = helper.getAxisValue( + endingPoint, + lineAngle - 90, + leadLine, + ); + graphics.drawLine( + mBorderPen, + Offset(endingPoint[0], -endingPoint[1]), + Offset(endLeaderLine[0], -endLeaderLine[1]), + ); //Set caption Type. if (lineAnnotation.lineCaption) { final double midpoint = lineDistance / 2; - final List centerPoint = - helper.getAxisValue(startingPoint, angle, midpoint); - final List captionPosition = - _getCaptionPosition(caption, centerPoint, angle, mFont); + final List centerPoint = helper.getAxisValue( + startingPoint, + angle, + midpoint, + ); + final List captionPosition = _getCaptionPosition( + caption, + centerPoint, + angle, + mFont, + ); graphics.translateTransform(captionPosition[0], -captionPosition[1]); graphics.rotateTransform(-angle); - graphics.drawString(lineAnnotation.text, mFont, - brush: backBrush, bounds: Rect.fromLTWH(-lineWidth / 2, 0, 0, 0)); + graphics.drawString( + lineAnnotation.text, + mFont, + brush: backBrush, + bounds: Rect.fromLTWH(-lineWidth / 2, 0, 0, 0), + ); } } if (isLoadedAnnotation) { - helper.dictionary!.setProperty(PdfDictionaryProperties.rect, - PdfArray.fromRectangle(_obtainLineBounds())); + helper.dictionary!.setProperty( + PdfDictionaryProperties.rect, + PdfArray.fromRectangle(_obtainLineBounds()), + ); } if (!isLoadedAnnotation && helper.flatten) { final double pageHeight = lineAnnotation.page!.size.height; final PdfMargins? margins = helper.obtainMargin(); if (lineAnnotation.page != null) { lineAnnotation.bounds = Rect.fromLTWH( - nativeRectangle.left - margins!.left, - pageHeight - - (nativeRectangle.top + nativeRectangle.height) - - margins.top, - nativeRectangle.width, - nativeRectangle.height); + nativeRectangle.left - margins!.left, + pageHeight - + (nativeRectangle.top + nativeRectangle.height) - + margins.top, + nativeRectangle.width, + nativeRectangle.height, + ); } else { lineAnnotation.bounds = Rect.fromLTWH( - nativeRectangle.left, - pageHeight - (nativeRectangle.top + nativeRectangle.height), - nativeRectangle.width, - nativeRectangle.height); + nativeRectangle.left, + pageHeight - (nativeRectangle.top + nativeRectangle.height), + nativeRectangle.width, + nativeRectangle.height, + ); } } return template; } List _getCaptionPosition( - String caption, List centerPoint, double angle, PdfFont font) { + String caption, + List centerPoint, + double angle, + PdfFont font, + ) { List captionPosition = List.filled(2, 0); - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); if (helper.isLoadedAnnotation) { - final bool isContainsMeasure = helper.dictionary!.items! - .containsKey(PdfName(PdfDictionaryProperties.measure)); - final double length = caption == 'Top' - ? isContainsMeasure - ? 2 * font.height - : font.height - : isContainsMeasure + final bool isContainsMeasure = helper.dictionary!.items!.containsKey( + PdfName(PdfDictionaryProperties.measure), + ); + final double length = + caption == 'Top' + ? isContainsMeasure + ? 2 * font.height + : font.height + : isContainsMeasure ? 3 * (font.height / 2) : font.height / 2; captionPosition = helper.getAxisValue(centerPoint, angle + 90, length); } else { - captionPosition = helper.getAxisValue(centerPoint, angle + 90, - caption == 'Top' ? font.height : font.height / 2); + captionPosition = helper.getAxisValue( + centerPoint, + angle + 90, + caption == 'Top' ? font.height : font.height / 2, + ); } return captionPosition; } @@ -699,8 +894,9 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { // Gets leader line of the lineAnnotation. int _obtainLeaderLine() { int lLine = 0; - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); if (helper.dictionary!.containsKey(PdfDictionaryProperties.ll)) { final PdfNumber ll = helper.dictionary![PdfDictionaryProperties.ll]! as PdfNumber; @@ -711,16 +907,19 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { // Gets the line intent of the annotation. PdfLineIntent _obtainLineIntent() { - PdfLineIntent _lineintent = PdfLineIntent.lineArrow; - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + PdfLineIntent lineintentValue = PdfLineIntent.lineArrow; + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); if (helper.dictionary!.containsKey(PdfDictionaryProperties.it)) { - final PdfName lineintent = helper.crossTable - .getObject(helper.dictionary![PdfDictionaryProperties.it])! - as PdfName; - _lineintent = _getLineIntentText(lineintent.name.toString()); + final PdfName lineintent = + helper.crossTable.getObject( + helper.dictionary![PdfDictionaryProperties.it], + )! + as PdfName; + lineintentValue = _getLineIntentText(lineintent.name.toString()); } - return _lineintent; + return lineintentValue; } // Get the Line Intent Text. @@ -749,12 +948,16 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { } } } else { - if (PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary! - .containsKey(PdfDictionaryProperties.l)) { - linePoints = PdfCrossTable.dereference( - PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary![PdfDictionaryProperties.l]) as PdfArray?; + if (PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary!.containsKey(PdfDictionaryProperties.l)) { + linePoints = + PdfCrossTable.dereference( + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary![PdfDictionaryProperties.l], + ) + as PdfArray?; if (linePoints != null) { points = []; // ignore: prefer_final_in_for_each @@ -772,11 +975,14 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { // Gets line caption of the annotation. bool _obtainLineCaption() { bool lCaption = false; - if (PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary! - .containsKey(PdfDictionaryProperties.cap)) { - final PdfBoolean lCap = PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary![PdfDictionaryProperties.cap]! as PdfBoolean; + if (PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary!.containsKey(PdfDictionaryProperties.cap)) { + final PdfBoolean lCap = + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary![PdfDictionaryProperties.cap]! + as PdfBoolean; lCaption = lCap.value!; } return lCaption; @@ -785,11 +991,14 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { // Gets leader ext of the annotation. int _obtainLeaderExt() { int lLineExt = 0; - if (PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary! - .containsKey(PdfDictionaryProperties.lle)) { - final PdfNumber lExt = PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary![PdfDictionaryProperties.lle]! as PdfNumber; + if (PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary!.containsKey(PdfDictionaryProperties.lle)) { + final PdfNumber lExt = + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary![PdfDictionaryProperties.lle]! + as PdfNumber; lLineExt = lExt.value!.toInt(); } return lLineExt; @@ -850,35 +1059,53 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { final PdfArray lineStyle = PdfArray(); if (lineStyle.elements.isNotEmpty) { lineStyle.insert( - 0, - PdfName(PdfAnnotationHelper.getHelper(lineAnnotation) - .getEnumName(lineAnnotation.beginLineStyle))); + 0, + PdfName( + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).getEnumName(lineAnnotation.beginLineStyle), + ), + ); lineStyle.insert( - 1, - PdfName(PdfAnnotationHelper.getHelper(lineAnnotation) - .getEnumName(lineAnnotation.endLineStyle))); + 1, + PdfName( + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).getEnumName(lineAnnotation.endLineStyle), + ), + ); } else { lineStyle.add(PdfName(lineAnnotation.beginLineStyle.toString())); lineStyle.add(PdfName(lineAnnotation.endLineStyle.toString())); } - bounds = PdfAnnotationHelper.getHelper(lineAnnotation) - .isLoadedAnnotation - ? PdfAnnotationHelper.getHelper(lineAnnotation).calculateLineBounds( - lPoints, - lineAnnotation.leaderLineExt, - lineAnnotation.leaderLine, - _obtainLeaderOffset(), - lineStyle, - lineAnnotation.border.width) - : PdfAnnotationHelper.getHelper(lineAnnotation).calculateLineBounds( - lPoints, - lineAnnotation.leaderLineExt, - leaderLine, - 0, - lineStyle, - lineAnnotation.border.width); - bounds = PdfRectangle(bounds.left - 8, bounds.top - 8, - bounds.width + 2 * 8, bounds.height + 2 * 8); + bounds = + PdfAnnotationHelper.getHelper(lineAnnotation).isLoadedAnnotation + ? PdfAnnotationHelper.getHelper( + lineAnnotation, + ).calculateLineBounds( + lPoints, + lineAnnotation.leaderLineExt, + lineAnnotation.leaderLine, + _obtainLeaderOffset(), + lineStyle, + lineAnnotation.border.width, + ) + : PdfAnnotationHelper.getHelper( + lineAnnotation, + ).calculateLineBounds( + lPoints, + lineAnnotation.leaderLineExt, + leaderLine, + 0, + lineStyle, + lineAnnotation.border.width, + ); + bounds = PdfRectangle( + bounds.left - 8, + bounds.top - 8, + bounds.width + 2 * 8, + bounds.height + 2 * 8, + ); } } return bounds; @@ -887,11 +1114,14 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { // Gets leader offset of the annotation. int _obtainLeaderOffset() { int lLineOffset = 0; - if (PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary! - .containsKey(PdfDictionaryProperties.llo)) { - final PdfNumber lOffset = PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary![PdfDictionaryProperties.llo]! as PdfNumber; + if (PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary!.containsKey(PdfDictionaryProperties.llo)) { + final PdfNumber lOffset = + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary![PdfDictionaryProperties.llo]! + as PdfNumber; lLineOffset = lOffset.value!.toInt(); } return lLineOffset; @@ -900,27 +1130,33 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { // Gets line style of the annotation. PdfArray? _obtainLineStyle() { PdfArray? array; - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(lineAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + lineAnnotation, + ); if (helper.dictionary!.containsKey(PdfDictionaryProperties.le)) { - array = helper.crossTable - .getObject(helper.dictionary![PdfDictionaryProperties.le]) - as PdfArray?; + array = + helper.crossTable.getObject( + helper.dictionary![PdfDictionaryProperties.le], + ) + as PdfArray?; } return array; } // Gets caption type of the annotation. PdfLineCaptionType _obtainCaptionType() { - PdfLineCaptionType _captiontype = PdfLineCaptionType.inline; - if (PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary! - .containsKey(PdfDictionaryProperties.cp)) { - final PdfName cType = PdfAnnotationHelper.getHelper(lineAnnotation) - .dictionary![PdfDictionaryProperties.cp]! as PdfName; - _captiontype = _getCaptionType(cType.name.toString()); + PdfLineCaptionType captiontypeValue = PdfLineCaptionType.inline; + if (PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary!.containsKey(PdfDictionaryProperties.cp)) { + final PdfName cType = + PdfAnnotationHelper.getHelper( + lineAnnotation, + ).dictionary![PdfDictionaryProperties.cp]! + as PdfName; + captiontypeValue = _getCaptionType(cType.name.toString()); } - return _captiontype; + return captiontypeValue; } // Gets caption type of the annotation. @@ -936,7 +1172,10 @@ class PdfLineAnnotationHelper extends PdfAnnotationHelper { /// internal method static PdfLineAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { return PdfLineAnnotation._(dictionary, crossTable, text); } 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 2d5332c93..b6ba3862e 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 @@ -7,14 +7,15 @@ import 'enum.dart'; /// internal class class PaintParams { /// internal constructor - PaintParams( - {this.backBrush, - this.foreBrush, - this.borderPen, - this.bounds, - this.style, - this.borderWidth, - this.shadowBrush}); + PaintParams({ + this.backBrush, + this.foreBrush, + this.borderPen, + this.bounds, + this.style, + this.borderWidth, + this.shadowBrush, + }); /// internal field PdfBrush? backBrush; 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 a87404d69..c581177ab 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 @@ -18,6 +18,7 @@ import '../primitives/pdf_name.dart'; import '../primitives/pdf_number.dart'; import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_stream.dart'; +import 'enum.dart'; import 'pdf_annotation.dart'; import 'pdf_annotation_collection.dart'; @@ -46,31 +47,43 @@ class PdfPolygonAnnotation extends PdfAnnotation { /// polygonAnnotation.color = PdfColor(255, 0, 0); /// polygonAnnotation.innerColor = PdfColor(255, 0, 255); /// page.annotations.add(polygonAnnotation); - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` - PdfPolygonAnnotation(List points, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - DateTime? modifiedDate, - double? opacity, - bool? setAppearance}) { - _helper = PdfPolygonAnnotationHelper(this, points, text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); + PdfPolygonAnnotation( + List points, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + List? flags, + bool? setAppearance, + }) { + _helper = PdfPolygonAnnotationHelper( + this, + points, + text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); } PdfPolygonAnnotation._( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { _helper = PdfPolygonAnnotationHelper._(this, dictionary, crossTable); this.text = text; } @@ -102,6 +115,32 @@ class PdfPolygonAnnotation extends PdfAnnotation { } } + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } + + /// Gets the inner color of the annotation. + PdfColor get innerColor => _helper.innerColor; + + /// Sets the inner color of the annotation. + set innerColor(PdfColor value) { + _helper.innerColor = value; + } + IPdfPrimitive? get _element => PdfAnnotationHelper.getHelper(this).dictionary; set _element(IPdfPrimitive? value) { @@ -114,34 +153,44 @@ class PdfPolygonAnnotation extends PdfAnnotation { /// [PdfPolygonAnnotation] helper class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { /// internal constructor - PdfPolygonAnnotationHelper(this.annotation, List points, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - DateTime? modifiedDate, - double? opacity, - bool? setAppearance}) - : super(annotation) { + PdfPolygonAnnotationHelper( + this.annotation, + List points, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + List? flags, + bool? setAppearance, + }) : super(annotation) { initializeAnnotation( - text: text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); - dictionary!.setProperty(PdfDictionaryProperties.subtype, - PdfName(PdfDictionaryProperties.polygon)); + text: text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(PdfDictionaryProperties.polygon), + ); linePoints = PdfArray(points); _polygonPoints = points; } PdfPolygonAnnotationHelper._( - this.annotation, PdfDictionary dictionary, PdfCrossTable crossTable) - : super(annotation) { + this.annotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(annotation) { initializeExistingAnnotation(dictionary, crossTable); } @@ -154,7 +203,10 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { /// internal method static PdfPolygonAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { return PdfPolygonAnnotation._(dictionary, crossTable, text); } @@ -174,8 +226,9 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { /// internal method void save() { - if (PdfAnnotationCollectionHelper.getHelper(annotation.page!.annotations) - .flatten) { + if (PdfAnnotationCollectionHelper.getHelper( + annotation.page!.annotations, + ).flatten) { PdfAnnotationHelper.getHelper(annotation).flatten = true; } if (PdfAnnotationHelper.getHelper(annotation).isLoadedAnnotation) { @@ -186,89 +239,118 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { } void _saveNewPolygonAnnotation() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); final PdfDictionary dictionary = helper.dictionary!; Rect nativeRectangle = Rect.zero; if (annotation.setAppearance) { _getBoundsValue(); nativeRectangle = Rect.fromLTWH( - annotation.bounds.left - annotation.border.width, - annotation.bounds.top - (annotation.border.width), - annotation.bounds.width + (2 * annotation.border.width), - annotation.bounds.height + (2 * annotation.border.width)); + annotation.bounds.left - annotation.border.width, + annotation.bounds.top - (annotation.border.width), + annotation.bounds.width + (2 * annotation.border.width), + annotation.bounds.height + (2 * annotation.border.width), + ); dictionary.setProperty(PdfDictionaryProperties.ap, annotation.appearance); if (dictionary[PdfDictionaryProperties.ap] != null) { - annotation.appearance.normal = - PdfTemplateHelper.fromRect(nativeRectangle); + annotation.appearance.normal = PdfTemplateHelper.fromRect( + nativeRectangle, + ); final PdfTemplate template = annotation.appearance.normal; PdfTemplateHelper.getHelper(template).writeTransformation = false; final PdfGraphics? graphics = template.graphics; - final PdfBrush? _backBrush = annotation.innerColor.isEmpty - ? null - : PdfSolidBrush(annotation.innerColor); - PdfPen? _borderPen; + final PdfBrush? backBrushColor = + annotation.innerColor.isEmpty + ? null + : PdfSolidBrush(annotation.innerColor); + PdfPen? borderPenColor; if (annotation.border.width > 0 && PdfColorHelper.getHelper(annotation.color).alpha != 0) { - _borderPen = PdfPen(annotation.color, width: annotation.border.width); + borderPenColor = PdfPen( + annotation.color, + width: annotation.border.width, + ); } if (helper.flatten) { annotation.page!.annotations.remove(annotation); - annotation.page!.graphics.drawPolygon(_getLinePoints()!, - pen: _borderPen, brush: _backBrush); + annotation.page!.graphics.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backBrushColor, + ); } else { - graphics!.drawPolygon(_getLinePoints()!, - pen: _borderPen, brush: _backBrush); + graphics!.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backBrushColor, + ); } } } if (helper.flatten && !annotation.setAppearance) { annotation.page!.annotations.remove(annotation); - PdfPen? _borderPen; + PdfPen? borderPenColor; if (annotation.border.width > 0 && PdfColorHelper.getHelper(annotation.color).alpha != 0) { - _borderPen = PdfPen(annotation.color, width: annotation.border.width); + borderPenColor = PdfPen( + annotation.color, + width: annotation.border.width, + ); } - final PdfBrush? _backBrush = annotation.innerColor.isEmpty - ? null - : PdfSolidBrush(annotation.innerColor); - annotation.page!.graphics - .drawPolygon(_getLinePoints()!, pen: _borderPen, brush: _backBrush); + final PdfBrush? backBrushColor = + annotation.innerColor.isEmpty + ? null + : PdfSolidBrush(annotation.innerColor); + annotation.page!.graphics.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backBrushColor, + ); } else if (!helper.flatten) { helper.saveAnnotation(); dictionary.setProperty( - PdfDictionaryProperties.vertices, PdfArray(linePoints)); + PdfDictionaryProperties.vertices, + PdfArray(linePoints), + ); dictionary.setProperty(PdfDictionaryProperties.bs, annotation.border); _getBoundsValue(); - dictionary.setProperty(PdfDictionaryProperties.rect, - PdfArray.fromRectangle(PdfRectangle.fromRect(annotation.bounds))); + dictionary.setProperty( + PdfDictionaryProperties.rect, + PdfArray.fromRectangle(PdfRectangle.fromRect(annotation.bounds)), + ); if (annotation.setAppearance) { - dictionary.setProperty(PdfDictionaryProperties.rect, - PdfArray.fromRectangle(PdfRectangle.fromRect(nativeRectangle))); + dictionary.setProperty( + PdfDictionaryProperties.rect, + PdfArray.fromRectangle(PdfRectangle.fromRect(nativeRectangle)), + ); } } } void _saveOldPolygonAnnotation() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); PdfGraphicsState? state; PdfRectangle nativeRectangle = PdfRectangle.empty; final PdfDictionary dictionary = helper.dictionary!; if (annotation.setAppearance) { _getBoundsValue(); nativeRectangle = PdfRectangle( - annotation.bounds.left - annotation.border.width, - annotation.page!.size.height - - annotation.bounds.top - - (annotation.border.width) - - annotation.bounds.height, - annotation.bounds.width + (2 * annotation.border.width), - annotation.bounds.height + (2 * annotation.border.width)); + annotation.bounds.left - annotation.border.width, + annotation.page!.size.height - + annotation.bounds.top - + (annotation.border.width) - + annotation.bounds.height, + annotation.bounds.width + (2 * annotation.border.width), + annotation.bounds.height + (2 * annotation.border.width), + ); dictionary.setProperty(PdfDictionaryProperties.ap, annotation.appearance); if (dictionary[PdfDictionaryProperties.ap] != null) { - annotation.appearance.normal = - PdfTemplateHelper.fromRect(nativeRectangle.rect); + annotation.appearance.normal = PdfTemplateHelper.fromRect( + nativeRectangle.rect, + ); final PdfTemplate template = annotation.appearance.normal; PdfTemplateHelper.getHelper(template).writeTransformation = false; final PdfGraphics? graphics = annotation.appearance.normal.graphics; @@ -276,9 +358,12 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { if (PdfColorHelper.getHelper(annotation.innerColor).alpha != 0) { backgroundBrush = PdfSolidBrush(annotation.innerColor); } - PdfPen? _borderPen; + PdfPen? borderPenColor; if (annotation.border.width > 0) { - _borderPen = PdfPen(annotation.color, width: annotation.border.width); + borderPenColor = PdfPen( + annotation.color, + width: annotation.border.width, + ); } if (dictionary.containsKey(PdfDictionaryProperties.bs)) { PdfDictionary? bSDictionary; @@ -286,20 +371,26 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { is PdfReferenceHolder) { bSDictionary = (dictionary.items![PdfName(PdfDictionaryProperties.bs)]! - as PdfReferenceHolder) - .object as PdfDictionary?; + as PdfReferenceHolder) + .object + as PdfDictionary?; } else { - bSDictionary = dictionary - .items![PdfName(PdfDictionaryProperties.bs)] as PdfDictionary?; + bSDictionary = + dictionary.items![PdfName(PdfDictionaryProperties.bs)] + as PdfDictionary?; } if (bSDictionary!.containsKey(PdfDictionaryProperties.d)) { - final PdfArray? dashPatternArray = PdfCrossTable.dereference( - bSDictionary.items![PdfName(PdfDictionaryProperties.d)]) - as PdfArray?; + final PdfArray? dashPatternArray = + PdfCrossTable.dereference( + bSDictionary.items![PdfName(PdfDictionaryProperties.d)], + ) + as PdfArray?; if (dashPatternArray != null) { final List dashPattern = List.filled( - dashPatternArray.count, 0, - growable: true); + dashPatternArray.count, + 0, + growable: true, + ); for (int i = 0; i < dashPatternArray.count; i++) { final IPdfPrimitive? pdfPrimitive = dashPatternArray.elements[i]; @@ -307,9 +398,9 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { dashPattern[i] = pdfPrimitive.value!.toDouble(); } } - _borderPen!.dashStyle = PdfDashStyle.dash; - PdfPenHelper.getHelper(_borderPen).isSkipPatternWidth = true; - _borderPen.dashPattern = dashPattern; + borderPenColor!.dashStyle = PdfDashStyle.dash; + PdfPenHelper.getHelper(borderPenColor).isSkipPatternWidth = true; + borderPenColor.dashPattern = dashPattern; } } } @@ -323,25 +414,47 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { final PdfDictionary beDictionary = dictionary[PdfName(PdfDictionaryProperties.be)]! as PdfDictionary; - final double? iNumber = (beDictionary - .items![PdfName(PdfDictionaryProperties.i)]! as PdfNumber) - .value as double?; + final double? iNumber = + (beDictionary.items![PdfName(PdfDictionaryProperties.i)]! + as PdfNumber) + .value + as double?; final double radius = iNumber == 1 ? 5 : 10; if (radius > 0) { final List points = _getLinePoints()!; if (points[0].dy > points[points.length - 1].dy) { - helper.drawCloudStyle(graphics!, backgroundBrush, _borderPen, - radius, 0.833, _getLinePoints()!, false); + helper.drawCloudStyle( + graphics!, + backgroundBrush, + borderPenColor, + radius, + 0.833, + _getLinePoints()!, + false, + ); } - helper.drawCloudStyle(annotation.page!.graphics, backgroundBrush, - _borderPen, radius, 0.833, _getLinePoints()!, false); + helper.drawCloudStyle( + annotation.page!.graphics, + backgroundBrush, + borderPenColor, + radius, + 0.833, + _getLinePoints()!, + false, + ); } else { - annotation.page!.graphics.drawPolygon(_getLinePoints()!, - pen: _borderPen, brush: backgroundBrush); + annotation.page!.graphics.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backgroundBrush, + ); } } else { - annotation.page!.graphics.drawPolygon(_getLinePoints()!, - pen: _borderPen, brush: backgroundBrush); + annotation.page!.graphics.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backgroundBrush, + ); } if (annotation.opacity < 1) { annotation.page!.graphics.restore(state); @@ -355,9 +468,11 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { final PdfDictionary beDictionary = dictionary[PdfName(PdfDictionaryProperties.be)]! as PdfDictionary; - final double? iNumber = (beDictionary - .items![PdfName(PdfDictionaryProperties.i)]! as PdfNumber) - .value as double?; + final double? iNumber = + (beDictionary.items![PdfName(PdfDictionaryProperties.i)]! + as PdfNumber) + .value + as double?; final double radius = iNumber == 1 ? 5 : 10; List points = _getLinePoints()!; if (points[0].dy > points[points.length - 1].dy) { @@ -366,22 +481,41 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { point.add(Offset(points[i].dx, -points[i].dy)); } points = point; - helper.drawCloudStyle(graphics!, backgroundBrush, _borderPen, - radius, 0.833, points, true); + helper.drawCloudStyle( + graphics!, + backgroundBrush, + borderPenColor, + radius, + 0.833, + points, + true, + ); } else { - helper.drawCloudStyle(graphics!, backgroundBrush, _borderPen, - radius, 0.833, points, false); + helper.drawCloudStyle( + graphics!, + backgroundBrush, + borderPenColor, + radius, + 0.833, + points, + false, + ); } } else { - graphics!.drawPolygon(_getLinePoints()!, - pen: _borderPen, brush: backgroundBrush); + graphics!.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backgroundBrush, + ); } if (annotation.opacity < 1) { graphics.restore(state); } } - dictionary.setProperty(PdfDictionaryProperties.rect, - PdfArray.fromRectangle(nativeRectangle)); + dictionary.setProperty( + PdfDictionaryProperties.rect, + PdfArray.fromRectangle(nativeRectangle), + ); } } if (helper.flatten && !annotation.setAppearance) { @@ -401,20 +535,30 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { } final bool isNormalMatrix = helper.validateTemplateMatrix(dic); final Rect rect = helper.calculateTemplateBounds( - annotation.bounds, annotation.page, template, isNormalMatrix); - annotation.page!.graphics - .drawPdfTemplate(template, rect.topLeft, rect.size); + annotation.bounds, + annotation.page, + template, + isNormalMatrix, + ); + annotation.page!.graphics.drawPdfTemplate( + template, + rect.topLeft, + rect.size, + ); annotation.page!.graphics.restore(state); annotation.page!.annotations.remove(annotation); } } } else { annotation.page!.annotations.remove(annotation); - final PdfPen _borderPen = - PdfPen(annotation.color, width: annotation.border.width); - final PdfBrush? backgroundBrush = annotation.innerColor.isEmpty - ? null - : PdfSolidBrush(annotation.innerColor); + final PdfPen borderPenColor = PdfPen( + annotation.color, + width: annotation.border.width, + ); + final PdfBrush? backgroundBrush = + annotation.innerColor.isEmpty + ? null + : PdfSolidBrush(annotation.innerColor); if (annotation.opacity < 1) { state = annotation.page!.graphics.save(); annotation.page!.graphics.setTransparency(annotation.opacity); @@ -422,18 +566,30 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { if (dictionary.containsKey(PdfDictionaryProperties.be)) { final IPdfPrimitive? primitive = dictionary[PdfName(PdfDictionaryProperties.be)]; - final PdfDictionary beDictionary = (primitive is PdfReferenceHolder - ? primitive.object - : primitive)! as PdfDictionary; - final double? iNumber = (beDictionary - .items![PdfName(PdfDictionaryProperties.i)]! as PdfNumber) - .value as double?; + final PdfDictionary beDictionary = + (primitive is PdfReferenceHolder ? primitive.object : primitive)! + as PdfDictionary; + final double? iNumber = + (beDictionary.items![PdfName(PdfDictionaryProperties.i)]! + as PdfNumber) + .value + as double?; final double radius = iNumber == 1 ? 5 : 10; - helper.drawCloudStyle(annotation.page!.graphics, backgroundBrush, - _borderPen, radius, 0.833, _getLinePoints()!, false); + helper.drawCloudStyle( + annotation.page!.graphics, + backgroundBrush, + borderPenColor, + radius, + 0.833, + _getLinePoints()!, + false, + ); } else { - annotation.page!.graphics.drawPolygon(_getLinePoints()!, - pen: _borderPen, brush: backgroundBrush); + annotation.page!.graphics.drawPolygon( + _getLinePoints()!, + pen: borderPenColor, + brush: backgroundBrush, + ); } if (annotation.opacity < 1) { annotation.page!.graphics.restore(state); @@ -446,8 +602,9 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { } List? _getLinePoints() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); if (helper.isLoadedAnnotation) { List? points; if (helper.dictionary!.containsKey(PdfDictionaryProperties.vertices)) { @@ -462,8 +619,9 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { points = []; for (int j = 0; j < point.length; j = j + 2) { if (helper.flatten) { - points.add(Offset( - point[j], annotation.page!.size.height - point[j + 1])); + points.add( + Offset(point[j], annotation.page!.size.height - point[j + 1]), + ); } else { points.add(Offset(point[j], -point[j + 1])); } @@ -486,21 +644,21 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { if (helper.flatten) { PdfPageHelper.getHelper(annotation.page!).isLoadedPage ? points.add( - Offset(pointsValue[j], pageHeight - pointsValue[j + 1])) - : points.add(Offset( + Offset(pointsValue[j], pageHeight - pointsValue[j + 1]), + ) + : points.add( + Offset( pointsValue[j] - - PdfPageHelper.getHelper(annotation.page!) - .section! - .pageSettings - .margins - .left, + PdfPageHelper.getHelper( + annotation.page!, + ).section!.pageSettings.margins.left, pageHeight - pointsValue[j + 1] - - PdfPageHelper.getHelper(annotation.page!) - .section! - .pageSettings - .margins - .right)); + PdfPageHelper.getHelper( + annotation.page!, + ).section!.pageSettings.margins.right, + ), + ); } else { points.add(Offset(pointsValue[j], -pointsValue[j + 1])); } @@ -511,8 +669,9 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { } void _getBoundsValue() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(annotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); if (helper.isLoadedAnnotation) { final PdfArray rect = helper.dictionary![PdfDictionaryProperties.rect]! as PdfArray; @@ -520,11 +679,17 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { final List xval = []; final List yval = []; if (helper.dictionary!.containsKey(PdfDictionaryProperties.vertices)) { - final PdfArray linePoints = PdfCrossTable.dereference( - helper.dictionary![PdfDictionaryProperties.vertices])! as PdfArray; + final PdfArray linePoints = + PdfCrossTable.dereference( + helper.dictionary![PdfDictionaryProperties.vertices], + )! + as PdfArray; if (linePoints.count > 0) { - final List points = - List.filled(linePoints.count, 0, growable: true); + final List points = List.filled( + linePoints.count, + 0, + growable: true, + ); for (int j = 0; j < linePoints.count; j++) { final PdfNumber number = linePoints[j]! as PdfNumber; points[j] = number.value!.toDouble(); @@ -540,8 +705,12 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { } xval.sort(); yval.sort(); - annotation.bounds = Rect.fromLTWH(xval[0], yval[0], - xval[xval.length - 1] - xval[0], yval[yval.length - 1] - yval[0]); + annotation.bounds = Rect.fromLTWH( + xval[0], + yval[0], + xval[xval.length - 1] - xval[0], + yval[yval.length - 1] - yval[0], + ); } else { final List xval = []; final List yval = []; @@ -563,8 +732,12 @@ class PdfPolygonAnnotationHelper extends PdfAnnotationHelper { } xval.sort(); yval.sort(); - annotation.bounds = Rect.fromLTWH(xval[0], yval[0], - xval[xval.length - 1] - xval[0], yval[yval.length - 1] - yval[0]); + annotation.bounds = Rect.fromLTWH( + xval[0], + yval[0], + xval[xval.length - 1] - xval[0], + yval[yval.length - 1] - yval[0], + ); } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart new file mode 100644 index 000000000..cc00b2785 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart @@ -0,0 +1,396 @@ +import 'dart:ui'; + +import '../../interfaces/pdf_interface.dart'; +import '../graphics/brushes/pdf_brush.dart'; +import '../graphics/brushes/pdf_solid_brush.dart'; +import '../graphics/figures/pdf_path.dart'; +import '../graphics/figures/pdf_template.dart'; +import '../graphics/pdf_color.dart'; +import '../graphics/pdf_graphics.dart'; +import '../graphics/pdf_pen.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'pdf_annotation.dart'; +import 'pdf_annotation_collection.dart'; + +/// Represents a base class for popup annotation which can be either in open or closed state. +class PdfPopupAnnotation extends PdfAnnotation { + // Constructor + /// Initializes a instance of the [PdfPopupAnnotation] class specified with bounds, text, open, color, author, subject, opacity, icon, modifiedDate, and flags. + /// ``` dart + /// final PdfDocument document = PdfDocument(); + /// final PdfPage page = document.pages.add(); + /// final PdfPopupAnnotation popup = + /// PdfPopupAnnotation(Rect.fromLTWH(10, 10, 30, 30), 'Popup Annotation'); + /// page.annotations.add(popup); + /// final List bytes = await document.save(); + /// document.dispose(); + /// ``` + PdfPopupAnnotation( + Rect bounds, + String text, { + bool? open, + String? author, + PdfColor? color, + String? subject, + double? opacity, + DateTime? modifiedDate, + PdfPopupIcon? icon, + List? flags, + bool? setAppearance, + }) { + _helper = PdfPopupAnnotationHelper( + this, + bounds, + text, + open, + author, + color, + subject, + opacity, + modifiedDate, + icon, + flags, + setAppearance, + ); + } + + PdfPopupAnnotation._( + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + _helper = PdfPopupAnnotationHelper._(this, dictionary, crossTable, text); + } + + // Fields + late PdfPopupAnnotationHelper _helper; + + // Properties + /// Gets value whether annotation is initially open or closed. + bool get open => _helper.open; + + /// Sets value whether annotation is initially open or closed. + set open(bool value) { + _helper.open = value; + } + + /// Gets the icon style of the annotation. + PdfPopupIcon get icon => _helper.icon; + + /// Sets the icon style of the annotation. + set icon(PdfPopupIcon value) { + _helper.icon = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } +} + +/// [PdfPopupAnnotationHelper] helper +class PdfPopupAnnotationHelper extends PdfAnnotationHelper { + //Constructor + /// internal constructor + PdfPopupAnnotationHelper( + this.popupAnnotation, + Rect bounds, + String text, + bool? open, + String? author, + PdfColor? color, + String? subject, + double? opacity, + DateTime? modifiedDate, + PdfPopupIcon? icon, + List? flags, + bool? setAppearance, + ) : super(popupAnnotation) { + initializeAnnotation( + bounds: bounds, + text: text, + color: color, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); + this.open = open ??= false; + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(PdfDictionaryProperties.text), + ); + this.icon = icon ?? PdfPopupIcon.note; + } + + PdfPopupAnnotationHelper._( + this.popupAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) : super(popupAnnotation) { + this.text = text; + initializeExistingAnnotation(dictionary, crossTable); + } + + // Fields + /// Internal fields + late PdfPopupAnnotation popupAnnotation; + bool _open = false; + PdfPopupIcon? _icon; + + /// internal method + static PdfPopupAnnotationHelper getHelper(PdfPopupAnnotation annotation) { + return annotation._helper; + } + + /// internal method + static PdfPopupAnnotation load( + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { + return PdfPopupAnnotation._(dictionary, crossTable, text); + } + + // Properties + /// Gets value whether annotation is initially open or closed. + bool get open => isLoadedAnnotation ? obtainOpen() : _open; + + /// Sets value whether annotation is initially open or closed. + set open(bool value) { + if (_open != value) { + _open = value; + dictionary!.setBoolean(PdfDictionaryProperties.open, _open); + } + } + + /// Gets the icon style of the annotation. + PdfPopupIcon get icon => + isLoadedAnnotation ? obtainIcon() : _icon ?? PdfPopupIcon.note; + + /// Sets the icon style of the annotation. + set icon(PdfPopupIcon value) { + if (_icon != value) { + _icon = value; + dictionary!.setName( + PdfName(PdfDictionaryProperties.name), + getEnumName(_icon), + ); + } + } + + /// Internal method. + bool obtainOpen() { + if (dictionary!.containsKey(PdfDictionaryProperties.open)) { + final IPdfPrimitive? open = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.open], + ); + if (open != null && open is PdfBoolean) { + return open.value ?? false; + } + } + return false; + } + + /// Internal method. + PdfPopupIcon obtainIcon() { + if (dictionary!.containsKey(PdfDictionaryProperties.name)) { + final IPdfPrimitive? icon = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.name], + ); + if (icon != null && icon is PdfName) { + return getIconName(icon.name.toString()); + } else if (icon != null && icon is PdfString) { + return getIconName(icon.value.toString()); + } + } + return PdfPopupIcon.note; + } + + /// Internal method. + PdfPopupIcon getIconName(String name) { + switch (name.toLowerCase()) { + case 'note': + return PdfPopupIcon.note; + case 'comment': + return PdfPopupIcon.comment; + case 'help': + return PdfPopupIcon.help; + case 'insert': + return PdfPopupIcon.insert; + case 'key': + return PdfPopupIcon.key; + case 'newparagraph': + return PdfPopupIcon.newParagraph; + case 'paragraph': + return PdfPopupIcon.paragraph; + } + return PdfPopupIcon.note; + } + + /// Internal method. + void save() { + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + popupAnnotation, + ); + if (PdfAnnotationCollectionHelper.getHelper( + popupAnnotation.page!.annotations, + ).flatten) { + helper.flatten = true; + } + if (helper.isLoadedAnnotation) { + if (helper.setAppearance) { + popupAnnotation.appearance.normal = PdfTemplate( + bounds.width, + bounds.height, + ); + drawIcon(popupAnnotation.appearance.normal.graphics!); + dictionary!.setProperty( + PdfDictionaryProperties.ap, + popupAnnotation.appearance, + ); + } + if (helper.flatten) { + bool isFlattenPopup = true; + if (dictionary!.containsKey(PdfDictionaryProperties.f)) { + final IPdfPrimitive? flag = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.f], + ); + if (flag != null && flag is PdfNumber && flag.value == 30) { + if (!helper.flattenPopups) { + isFlattenPopup = false; + } + } + } + if (dictionary![PdfDictionaryProperties.ap] != null && isFlattenPopup) { + IPdfPrimitive? dic = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.ap], + ); + PdfTemplate template; + if (dic != null && dic is PdfDictionary) { + dic = PdfCrossTable.dereference(dic[PdfDictionaryProperties.n]); + if (dic != null && dic is PdfStream) { + final PdfStream stream = dic; + template = PdfTemplateHelper.fromPdfStream(stream); + if (!setAppearance && opacity < 1) { + page!.graphics.save(); + page!.graphics.setTransparency(opacity); + } + page!.graphics.drawPdfTemplate( + template, + bounds.topLeft, + bounds.size, + ); + if (!setAppearance && opacity < 1) { + page!.graphics.restore(); + } + } + } + } + page!.annotations.remove(popupAnnotation); + } + } else { + helper.saveAnnotation(); + if (helper.setAppearance || helper.flatten) { + drawIcon(popupAnnotation.appearance.normal.graphics!); + if (helper.flatten) { + page!.graphics.drawPdfTemplate( + popupAnnotation.appearance.normal, + bounds.topLeft, + popupAnnotation.appearance.normal.size, + ); + page!.annotations.remove(popupAnnotation); + } else { + dictionary!.setProperty( + PdfDictionaryProperties.ap, + popupAnnotation.appearance, + ); + } + } + } + if (helper.flattenPopups) { + helper.flattenPopup(); + } + } + + /// Internal method. + void drawIcon(PdfGraphics graphics) { + if (dictionary!.containsKey(PdfDictionaryProperties.name)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.name], + ); + if (name != null && name is PdfName && name.name == 'Comment') { + if (flatten) { + popupAnnotation.appearance.normal = PdfTemplate( + bounds.width > 24 ? bounds.width : 24, + bounds.height > 22 ? bounds.height : 22, + ); + graphics = popupAnnotation.appearance.normal.graphics!; + } + final PdfPen pen = PdfPen(PdfColor(0, 0, 0), width: 0.3); + PdfBrush brush = PdfSolidBrush(color); + final PdfPen pen1 = PdfPen(PdfColor(255, 255, 255), width: 0.35); + final List points = [ + const Offset(7, 15.45), + const Offset(9, 16.15), + const Offset(4, 19), + ]; + final PdfPath path = PdfPath(); + if (color.isEmpty == true) { + brush = PdfBrushes.gold; + } + final PdfTemplate template = PdfTemplate(24, 22); + if (opacity < 1) { + template.graphics!.save(); + template.graphics!.setTransparency(opacity); + } + template.graphics!.drawRectangle( + bounds: const Rect.fromLTWH(0, 0, 24, 22), + pen: pen, + brush: brush, + ); + template.graphics!.drawPolygon( + points, + pen: pen, + brush: PdfBrushes.white, + ); + path.addEllipse(const Rect.fromLTWH(2.5, 2.5, 19, 14)); + template.graphics!.drawPath(pen: pen, brush: PdfBrushes.white, path); + template.graphics!.drawArc( + const Rect.fromLTWH(2.5, 2.5, 19, 14), + 110.7, + 10.3, + pen: pen1, + ); + if (opacity < 1) { + template.graphics!.restore(); + } + graphics.drawPdfTemplate(template, Offset.zero, const Size(17, 17)); + } + } + } + + /// internal method + @override + IPdfPrimitive? get element => dictionary; + @override + set element(IPdfPrimitive? value) { + if (value != null && value is PdfDictionary) { + dictionary = 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 d6b83b4e1..e17202bff 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 @@ -19,6 +19,7 @@ import '../primitives/pdf_name.dart'; import '../primitives/pdf_number.dart'; import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_stream.dart'; +import 'enum.dart'; import 'pdf_annotation.dart'; import 'pdf_annotation_collection.dart'; import 'pdf_paintparams.dart'; @@ -26,7 +27,7 @@ import 'pdf_paintparams.dart'; /// Represents a PDF rectangle annotation class PdfRectangleAnnotation extends PdfAnnotation { // Constructor - /// Initializes new instance of the [PdfRectangleAnnotation] with bounds, text, border, color, innerColor, author, rotate, subject and modifiedDate. + /// Initializes new instance of the [PdfRectangleAnnotation] with bounds, text, border, color, innerColor, author, rotate, subject, modifiedDate, and flags. /// ``` dart /// PdfDocument document = PdfDocument(); /// PdfPage page = document.pages.add(); @@ -34,31 +35,43 @@ class PdfRectangleAnnotation extends PdfAnnotation { /// const Rect.fromLTWH(0, 30, 80, 80), 'SquareAnnotation', /// innerColor: PdfColor(255, 0, 0), color: PdfColor(255, 255, 0)); /// page.annotations.add(rectangleAnnotation); - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` - PdfRectangleAnnotation(Rect bounds, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - double? opacity, - DateTime? modifiedDate, - bool? setAppearance}) { - _helper = PdfRectangleAnnotationHelper(this, bounds, text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); + PdfRectangleAnnotation( + Rect bounds, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + double? opacity, + DateTime? modifiedDate, + List? flags, + bool? setAppearance, + }) { + _helper = PdfRectangleAnnotationHelper( + this, + bounds, + text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); } PdfRectangleAnnotation._( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { _helper = PdfRectangleAnnotationHelper._(this, dictionary, crossTable); this.text = text; } @@ -67,6 +80,32 @@ class PdfRectangleAnnotation extends PdfAnnotation { late PdfRectangleAnnotationHelper _helper; // Properites + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } + + /// Gets the inner color of the annotation. + PdfColor get innerColor => _helper.innerColor; + + /// Sets the inner color of the annotation. + set innerColor(PdfColor value) { + _helper.innerColor = value; + } + IPdfPrimitive? get _element => PdfAnnotationHelper.getHelper(this).dictionary; set _element(IPdfPrimitive? value) { @@ -80,33 +119,42 @@ class PdfRectangleAnnotation extends PdfAnnotation { class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { /// internal constructor PdfRectangleAnnotationHelper( - this.rectangleAnnotation, Rect bounds, String text, - {PdfColor? color, - PdfColor? innerColor, - PdfAnnotationBorder? border, - String? author, - String? subject, - double? opacity, - DateTime? modifiedDate, - bool? setAppearance}) - : super(rectangleAnnotation) { + this.rectangleAnnotation, + Rect bounds, + String text, { + PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + double? opacity, + DateTime? modifiedDate, + List? flags, + bool? setAppearance, + }) : super(rectangleAnnotation) { initializeAnnotation( - bounds: bounds, - text: text, - color: color, - innerColor: innerColor, - border: border, - author: author, - subject: subject, - modifiedDate: modifiedDate, - opacity: opacity, - setAppearance: setAppearance); - dictionary!.setProperty(PdfDictionaryProperties.subtype, - PdfName(PdfDictionaryProperties.square)); + bounds: bounds, + text: text, + color: color, + innerColor: innerColor, + border: border, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(PdfDictionaryProperties.square), + ); } - PdfRectangleAnnotationHelper._(this.rectangleAnnotation, - PdfDictionary dictionary, PdfCrossTable crossTable) - : super(rectangleAnnotation) { + PdfRectangleAnnotationHelper._( + this.rectangleAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(rectangleAnnotation) { initializeExistingAnnotation(dictionary, crossTable); } @@ -115,13 +163,17 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { /// internal method static PdfRectangleAnnotationHelper getHelper( - PdfRectangleAnnotation annotation) { + PdfRectangleAnnotation annotation, + ) { return annotation._helper; } /// internal method static PdfRectangleAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { return PdfRectangleAnnotation._(dictionary, crossTable, text); } @@ -135,11 +187,12 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { /// internal method void save() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(rectangleAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + rectangleAnnotation, + ); if (PdfAnnotationCollectionHelper.getHelper( - rectangleAnnotation.page!.annotations) - .flatten) { + rectangleAnnotation.page!.annotations, + ).flatten) { helper.flatten = true; } if (helper.flatten || @@ -160,15 +213,19 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } else { if (appearance != null) { rectangleAnnotation.appearance.normal = appearance; - helper.dictionary!.setProperty(PdfDictionaryProperties.ap, - PdfReferenceHolder(rectangleAnnotation.appearance)); + helper.dictionary!.setProperty( + PdfDictionaryProperties.ap, + PdfReferenceHolder(rectangleAnnotation.appearance), + ); } } } if (!helper.flatten && !helper.isLoadedAnnotation) { helper.saveAnnotation(); - helper.dictionary! - .setProperty(PdfDictionaryProperties.bs, rectangleAnnotation.border); + helper.dictionary!.setProperty( + PdfDictionaryProperties.bs, + rectangleAnnotation.border, + ); } if (helper.flattenPopups) { helper.flattenPopup(); @@ -176,26 +233,33 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } PdfTemplate? _createAppearance() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(rectangleAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + rectangleAnnotation, + ); if (helper.isLoadedAnnotation) { if (rectangleAnnotation.setAppearance) { final PaintParams paintParams = PaintParams(); final double borderWidth = rectangleAnnotation.border.width / 2; - final PdfPen mBorderPen = PdfPen(rectangleAnnotation.color, - width: rectangleAnnotation.border.width); + final PdfPen mBorderPen = PdfPen( + rectangleAnnotation.color, + width: rectangleAnnotation.border.width, + ); PdfBrush? mBackBrush; final Map result = _calculateCloudBorderBounds(); final double borderIntensity = result['borderIntensity'] as double; final String? borderStyle = result['borderStyle'] as String?; final PdfRectangle nativeRectangle = PdfRectangle( - 0, - 0, - rectangleAnnotation.bounds.width, - rectangleAnnotation.bounds.height); - final PdfTemplate template = - PdfTemplateHelper.fromRect(nativeRectangle.rect); - helper.setMatrix(PdfTemplateHelper.getHelper(template).content); + 0, + 0, + rectangleAnnotation.bounds.width, + rectangleAnnotation.bounds.height, + ); + final PdfTemplate template = PdfTemplateHelper.fromRect( + nativeRectangle.rect, + ); + PdfAnnotationHelper.setMatrixToZeroRotation( + PdfTemplateHelper.getHelper(template).content, + ); if (borderIntensity > 0 && borderStyle == 'C') { PdfTemplateHelper.getHelper(template).writeTransformation = false; } @@ -210,21 +274,35 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { paintParams.foreBrush = PdfSolidBrush(rectangleAnnotation.color); paintParams.backBrush = mBackBrush; final PdfRectangle rectangle = _obtainStyle( - mBorderPen, nativeRectangle, borderWidth, - borderIntensity: borderIntensity, borderStyle: borderStyle); + mBorderPen, + nativeRectangle, + borderWidth, + borderIntensity: borderIntensity, + borderStyle: borderStyle, + ); if (rectangleAnnotation.opacity < 1) { graphics!.save(); graphics.setTransparency(rectangleAnnotation.opacity); } if (borderIntensity > 0 && borderStyle == 'C') { _drawAppearance( - rectangle, borderWidth, graphics, paintParams, borderIntensity); + rectangle, + borderWidth, + graphics, + paintParams, + borderIntensity, + ); } else { graphics!.drawRectangle( - pen: paintParams.borderPen, - brush: paintParams.backBrush, - bounds: Rect.fromLTWH( - rectangle.x, rectangle.y, rectangle.width, rectangle.height)); + pen: paintParams.borderPen, + brush: paintParams.backBrush, + bounds: Rect.fromLTWH( + rectangle.x, + rectangle.y, + rectangle.width, + rectangle.height, + ), + ); } if (rectangleAnnotation.opacity < 1) { graphics!.restore(); @@ -233,17 +311,27 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } return null; } else { - final Rect nativeRectangle = Rect.fromLTWH(0, 0, - rectangleAnnotation.bounds.width, rectangleAnnotation.bounds.height); + final Rect nativeRectangle = Rect.fromLTWH( + 0, + 0, + rectangleAnnotation.bounds.width, + rectangleAnnotation.bounds.height, + ); final PdfTemplate template = PdfTemplate( - rectangleAnnotation.bounds.width, rectangleAnnotation.bounds.height); - helper.setMatrix(PdfTemplateHelper.getHelper(template).content); + rectangleAnnotation.bounds.width, + rectangleAnnotation.bounds.height, + ); + PdfAnnotationHelper.setMatrixToZeroRotation( + PdfTemplateHelper.getHelper(template).content, + ); final PaintParams paintParams = PaintParams(); final PdfGraphics graphics = template.graphics!; if (rectangleAnnotation.border.width > 0 && PdfColorHelper.getHelper(rectangleAnnotation.color).alpha != 0) { - final PdfPen mBorderPen = PdfPen(rectangleAnnotation.color, - width: rectangleAnnotation.border.width); + final PdfPen mBorderPen = PdfPen( + rectangleAnnotation.color, + width: rectangleAnnotation.border.width, + ); paintParams.borderPen = mBorderPen; } PdfBrush? mBackBrush; @@ -256,21 +344,26 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { if (paintParams.foreBrush != PdfSolidBrush(rectangleAnnotation.color)) { paintParams.foreBrush = PdfSolidBrush(rectangleAnnotation.color); } - final Rect rect = Rect.fromLTWH(nativeRectangle.left, nativeRectangle.top, - nativeRectangle.width, nativeRectangle.height); + final Rect rect = Rect.fromLTWH( + nativeRectangle.left, + nativeRectangle.top, + nativeRectangle.width, + nativeRectangle.height, + ); if (rectangleAnnotation.opacity < 1) { graphics.save(); - graphics.setTransparency(rectangleAnnotation.opacity, - mode: PdfBlendMode.normal); + graphics.setTransparency(rectangleAnnotation.opacity); } graphics.drawRectangle( - bounds: Rect.fromLTWH( - rect.left + width, - rect.top + width, - rect.width - rectangleAnnotation.border.width, - rect.height - rectangleAnnotation.border.width), - pen: paintParams.borderPen, - brush: paintParams.backBrush); + bounds: Rect.fromLTWH( + rect.left + width, + rect.top + width, + rect.width - rectangleAnnotation.border.width, + rect.height - rectangleAnnotation.border.width, + ), + pen: paintParams.borderPen, + brush: paintParams.backBrush, + ); if (rectangleAnnotation.opacity < 1) { graphics.restore(); } @@ -279,8 +372,9 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } Map _calculateCloudBorderBounds() { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(rectangleAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + rectangleAnnotation, + ); double borderIntensity = 0; String borderStyle = ''; final PdfDictionary dictionary = helper.dictionary!; @@ -290,8 +384,9 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.be])! as PdfDictionary; if (dict.containsKey(PdfDictionaryProperties.s)) { - borderStyle = helper - .getEnumName((dict[PdfDictionaryProperties.s]! as PdfName).name); + borderStyle = helper.getEnumName( + (dict[PdfDictionaryProperties.s]! as PdfName).name, + ); } if (dict.containsKey(PdfDictionaryProperties.i)) { borderIntensity = @@ -299,24 +394,25 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } if (borderIntensity != 0 && borderStyle == 'C') { final Rect cloudRectangle = Rect.fromLTWH( - rectangleAnnotation.bounds.left - - borderIntensity * 5 - - rectangleAnnotation.border.width / 2, - rectangleAnnotation.bounds.top - - borderIntensity * 5 - - rectangleAnnotation.border.width / 2, - rectangleAnnotation.bounds.width + - borderIntensity * 10 + - rectangleAnnotation.border.width, - rectangleAnnotation.bounds.height + - borderIntensity * 10 + - rectangleAnnotation.border.width); + rectangleAnnotation.bounds.left - + borderIntensity * 5 - + rectangleAnnotation.border.width / 2, + rectangleAnnotation.bounds.top - + borderIntensity * 5 - + rectangleAnnotation.border.width / 2, + rectangleAnnotation.bounds.width + + borderIntensity * 10 + + rectangleAnnotation.border.width, + rectangleAnnotation.bounds.height + + borderIntensity * 10 + + rectangleAnnotation.border.width, + ); final double radius = borderIntensity * 5; final List arr = [ radius + rectangleAnnotation.border.width / 2, radius + rectangleAnnotation.border.width / 2, radius + rectangleAnnotation.border.width / 2, - radius + rectangleAnnotation.border.width / 2 + radius + rectangleAnnotation.border.width / 2, ]; dictionary.setProperty(PdfDictionaryProperties.rd, PdfArray(arr)); rectangleAnnotation.bounds = cloudRectangle; @@ -330,30 +426,32 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { final PdfNumber num3 = mRdArray.elements[2]! as PdfNumber; final PdfNumber num4 = mRdArray.elements[3]! as PdfNumber; Rect cloudRectangle = Rect.fromLTWH( - rectangleAnnotation.bounds.left + num1.value!.toDouble(), - rectangleAnnotation.bounds.top + num2.value!.toDouble(), - rectangleAnnotation.bounds.width - num3.value!.toDouble() * 2, - rectangleAnnotation.bounds.height - num4.value!.toDouble() * 2); + rectangleAnnotation.bounds.left + num1.value!.toDouble(), + rectangleAnnotation.bounds.top + num2.value!.toDouble(), + rectangleAnnotation.bounds.width - num3.value!.toDouble() * 2, + rectangleAnnotation.bounds.height - num4.value!.toDouble() * 2, + ); if (borderIntensity != 0 && borderStyle == 'C') { cloudRectangle = Rect.fromLTWH( - cloudRectangle.left - - borderIntensity * 5 - - rectangleAnnotation.border.width / 2, - cloudRectangle.top - - borderIntensity * 5 - - rectangleAnnotation.border.width / 2, - cloudRectangle.width + - borderIntensity * 10 + - rectangleAnnotation.border.width, - cloudRectangle.height + - borderIntensity * 10 + - rectangleAnnotation.border.width); + cloudRectangle.left - + borderIntensity * 5 - + rectangleAnnotation.border.width / 2, + cloudRectangle.top - + borderIntensity * 5 - + rectangleAnnotation.border.width / 2, + cloudRectangle.width + + borderIntensity * 10 + + rectangleAnnotation.border.width, + cloudRectangle.height + + borderIntensity * 10 + + rectangleAnnotation.border.width, + ); final double radius = borderIntensity * 5; final List arr = [ radius + rectangleAnnotation.border.width / 2, radius + rectangleAnnotation.border.width / 2, radius + rectangleAnnotation.border.width / 2, - radius + rectangleAnnotation.border.width / 2 + radius + rectangleAnnotation.border.width / 2, ]; dictionary.setProperty(PdfDictionaryProperties.rd, PdfArray(arr)); } else { @@ -363,38 +461,44 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } return { 'borderIntensity': borderIntensity, - 'borderStyle': borderStyle + 'borderStyle': borderStyle, }; } void _flattenAnnotation(PdfPage? page, PdfTemplate? appearance) { - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(rectangleAnnotation); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + rectangleAnnotation, + ); final PdfDictionary dictionary = helper.dictionary!; if (helper.isLoadedAnnotation) { - final bool isContainsAP = - dictionary.containsKey(PdfDictionaryProperties.ap); + final bool isContainsAP = dictionary.containsKey( + PdfDictionaryProperties.ap, + ); if (isContainsAP && appearance == null) { PdfDictionary? appearanceDictionary = PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.ap]) as PdfDictionary?; if (appearanceDictionary != null) { - appearanceDictionary = PdfCrossTable.dereference( - appearanceDictionary[PdfDictionaryProperties.n]) - as PdfDictionary?; + appearanceDictionary = + PdfCrossTable.dereference( + appearanceDictionary[PdfDictionaryProperties.n], + ) + as PdfDictionary?; if (appearanceDictionary != null) { final PdfStream appearanceStream = appearanceDictionary as PdfStream; appearance = PdfTemplateHelper.fromPdfStream(appearanceStream); - final bool isNormalMatrix = - helper.validateTemplateMatrix(appearanceDictionary); + final bool isNormalMatrix = helper.validateTemplateMatrix( + appearanceDictionary, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } else { rectangleAnnotation.setAppearance = true; appearance = _createAppearance(); if (appearance != null) { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } @@ -404,25 +508,33 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { appearance = _createAppearance(); if (appearance != null) { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance).content); + PdfTemplateHelper.getHelper(appearance).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { final bool isNormalMatrix = helper.validateTemplateMatrix( - PdfTemplateHelper.getHelper(appearance!).content); + PdfTemplateHelper.getHelper(appearance!).content, + ); helper.flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { page!.graphics.save(); final Rect rectangle = helper.calculateTemplateBounds( - rectangleAnnotation.bounds, page, appearance, true); + rectangleAnnotation.bounds, + page, + appearance, + true, + ); if (rectangleAnnotation.opacity < 1) { - page.graphics.setTransparency(rectangleAnnotation.opacity, - mode: PdfBlendMode.normal); + page.graphics.setTransparency(rectangleAnnotation.opacity); } page.graphics.drawPdfTemplate( - appearance!, Offset(rectangle.left, rectangle.top), rectangle.size); + appearance!, + Offset(rectangle.left, rectangle.top), + rectangle.size, + ); page.annotations.remove(rectangleAnnotation); page.graphics.restore(); } @@ -430,14 +542,22 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { // Obtain Style from annotation PdfRectangle _obtainStyle( - PdfPen mBorderPen, PdfRectangle rectangle, double borderWidth, - {double? borderIntensity, String? borderStyle}) { - if (PdfAnnotationHelper.getHelper(rectangleAnnotation) - .dictionary! - .containsKey(PdfDictionaryProperties.bs)) { - final PdfDictionary? bSDictionary = PdfCrossTable.dereference( - PdfAnnotationHelper.getHelper(rectangleAnnotation) - .dictionary![PdfDictionaryProperties.bs]) as PdfDictionary?; + PdfPen mBorderPen, + PdfRectangle rectangle, + double borderWidth, { + double? borderIntensity, + String? borderStyle, + }) { + if (PdfAnnotationHelper.getHelper( + rectangleAnnotation, + ).dictionary!.containsKey(PdfDictionaryProperties.bs)) { + final PdfDictionary? bSDictionary = + PdfCrossTable.dereference( + PdfAnnotationHelper.getHelper( + rectangleAnnotation, + ).dictionary![PdfDictionaryProperties.bs], + ) + as PdfDictionary?; if (bSDictionary != null && bSDictionary.containsKey(PdfDictionaryProperties.d)) { final PdfArray dashPatternArray = @@ -446,7 +566,8 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { final List dashPattern = []; for (int i = 0; i < dashPatternArray.count; i++) { dashPattern.add( - (dashPatternArray.elements[i]! as PdfNumber).value!.toDouble()); + (dashPatternArray.elements[i]! as PdfNumber).value!.toDouble(), + ); } mBorderPen.dashStyle = PdfDashStyle.dash; mBorderPen.dashPattern = dashPattern; @@ -471,8 +592,13 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { } // Draw appearance for annotation - void _drawAppearance(PdfRectangle rectangle, double borderWidth, - PdfGraphics? graphics, PaintParams paintParams, double borderIntensity) { + void _drawAppearance( + PdfRectangle rectangle, + double borderWidth, + PdfGraphics? graphics, + PaintParams paintParams, + double borderIntensity, + ) { final PdfPath graphicsPath = PdfPath(); graphicsPath.addRectangle(rectangle.rect); final double radius = borderIntensity * 4.25; @@ -482,29 +608,38 @@ class PdfRectangleAnnotationHelper extends PdfAnnotationHelper { PdfPathHelper.getHelper(graphicsPath).points.removeAt(4); } final List points = []; - for (int i = 0; - i < PdfPathHelper.getHelper(graphicsPath).points.length; - i++) { - points.add(Offset(PdfPathHelper.getHelper(graphicsPath).points[i].dx, - -PdfPathHelper.getHelper(graphicsPath).points[i].dy)); + for ( + int i = 0; + i < PdfPathHelper.getHelper(graphicsPath).points.length; + i++ + ) { + points.add( + Offset( + PdfPathHelper.getHelper(graphicsPath).points[i].dx, + -PdfPathHelper.getHelper(graphicsPath).points[i].dy, + ), + ); } PdfAnnotationHelper.getHelper(rectangleAnnotation).drawCloudStyle( - graphics!, - paintParams.backBrush, - paintParams.borderPen, - radius, - 0.833, - points, - false); + graphics!, + paintParams.backBrush, + paintParams.borderPen, + radius, + 0.833, + points, + false, + ); } else { graphics!.drawRectangle( - pen: paintParams.borderPen, - brush: paintParams.backBrush, - bounds: Rect.fromLTWH( - rectangle.x + borderWidth, - rectangle.y, - rectangle.width - rectangleAnnotation.border.width, - rectangle.height)); + pen: paintParams.borderPen, + brush: paintParams.backBrush, + bounds: Rect.fromLTWH( + rectangle.x + borderWidth, + rectangle.y, + rectangle.width - rectangleAnnotation.border.width, + rectangle.height, + ), + ); } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart new file mode 100644 index 000000000..cad794afe --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart @@ -0,0 +1,756 @@ +import 'dart:math'; +import 'dart:ui'; + +import '../../interfaces/pdf_interface.dart'; +import '../drawing/drawing.dart'; +import '../graphics/brushes/pdf_solid_brush.dart'; +import '../graphics/enums.dart'; +import '../graphics/figures/pdf_path.dart'; +import '../graphics/figures/pdf_template.dart'; +import '../graphics/pdf_color.dart'; +import '../graphics/pdf_graphics.dart'; +import '../graphics/pdf_margins.dart'; +import '../graphics/pdf_pen.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/enum.dart'; +import '../pages/pdf_page.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import 'enum.dart'; +import 'pdf_annotation.dart'; +import 'pdf_annotation_collection.dart'; + +/// Represents the text markup annotation. +class PdfTextMarkupAnnotation extends PdfAnnotation { + // Constructor + /// Initializes new instance of [PdfTextMarkupAnnotation] class with bounds, text, color, author, subject, modifiedDate, boundsCollection, flags and textMarkupAnnotationType. + /// ``` dart + /// //Create a new PDF document. + /// final PdfDocument document = PdfDocument(); + /// //Create a new page. + /// final PdfPage page = document.pages.add(); + /// //Create PDF font with font style. + /// final PdfFont font = + /// PdfStandardFont(PdfFontFamily.courier, 10, style: PdfFontStyle.bold); + /// const String markupText = 'Text Markup'; + /// final Size textSize = font.measureString(markupText); + /// page.graphics.drawString(markupText, font, + /// brush: PdfBrushes.black, bounds: const Rect.fromLTWH(175, 40, 0, 0)); + /// //Create a text markup annotation. + /// final PdfTextMarkupAnnotation markupAnnotation = PdfTextMarkupAnnotation( + /// Rect.fromLTWH(175, 40, textSize.width, textSize.height), + /// 'Markup annotation', + /// PdfColor(128, 43, 226)); + /// markupAnnotation.author = 'MarkUp'; + /// markupAnnotation.textMarkupAnnotationType = + /// PdfTextMarkupAnnotationType.highlight; + /// //Adds the annotation to the page + /// page.annotations.add(markupAnnotation); + /// final List bytes = document.saveSync(); + /// document.dispose(); + /// ``` + PdfTextMarkupAnnotation( + Rect bounds, + String text, + PdfColor color, { + String? author, + String? subject, + double? opacity, + DateTime? modifiedDate, + bool? setAppearance, + List? boundsCollection, + List? flags, + PdfTextMarkupAnnotationType? textMarkupAnnotationType, + }) { + _helper = PdfTextMarkupAnnotationHelper( + this, + bounds, + text, + color, + author, + subject, + opacity, + modifiedDate, + setAppearance, + flags, + textMarkupAnnotationType, + ); + if (boundsCollection != null) { + this.boundsCollection = boundsCollection; + } + } + + PdfTextMarkupAnnotation._( + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + _helper = PdfTextMarkupAnnotationHelper._(this, dictionary, crossTable); + } + + // Fields + late PdfTextMarkupAnnotationHelper _helper; + + // Properties + /// Gets the text markup annotation bounds collection. + List get boundsCollection => _helper.boundsCollection; + + /// Sets the text markup annotation bounds collection. + set boundsCollection(List value) { + _helper.boundsCollection = value; + } + + /// Gets text markup annotation Type. + PdfTextMarkupAnnotationType get textMarkupAnnotationType => + _helper.textMarkupAnnotationType; + + /// Sets text markup annotation Type. + set textMarkupAnnotationType(PdfTextMarkupAnnotationType value) { + _helper.textMarkupAnnotationType = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } +} + +/// [PdfTextMarkupAnnotation] helper +class PdfTextMarkupAnnotationHelper extends PdfAnnotationHelper { + //Constructor + /// internal constructor + PdfTextMarkupAnnotationHelper( + this.textMarkupAnnotation, + Rect bounds, + String text, + PdfColor color, + String? author, + String? subject, + double? opacity, + DateTime? modifiedDate, + bool? setAppearance, + List? flags, + PdfTextMarkupAnnotationType? textMarkupAnnotationType, + ) : super(textMarkupAnnotation) { + initializeAnnotation( + bounds: bounds, + text: text, + color: color, + author: author, + subject: subject, + modifiedDate: modifiedDate, + opacity: opacity, + flags: flags, + setAppearance: setAppearance, + ); + this.textMarkupAnnotationType = + textMarkupAnnotationType ?? PdfTextMarkupAnnotationType.highlight; + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(getEnumName(this.textMarkupAnnotationType.toString())), + ); + } + + PdfTextMarkupAnnotationHelper._( + this.textMarkupAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(textMarkupAnnotation) { + initializeExistingAnnotation(dictionary, crossTable); + } + + //Fields + /// internal field + late PdfTextMarkupAnnotation textMarkupAnnotation; + List _boundsCollection = []; + + /// internal field + PdfArray? points; + + /// internal field + PdfTextMarkupAnnotationType? _textMarkupAnnotationType; + + /// internal method + static PdfTextMarkupAnnotationHelper getHelper( + PdfTextMarkupAnnotation annotation, + ) { + return annotation._helper; + } + + /// internal method + static PdfTextMarkupAnnotation load( + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + return PdfTextMarkupAnnotation._(dictionary, crossTable); + } + + //Properties + /// Gets the text markup annotation bounds collection. + List get boundsCollection { + if (isLoadedAnnotation) { + _boundsCollection = _obtainBoundsValue(); + } + return _boundsCollection; + } + + /// Sets the text markup annotation bounds collection. + set boundsCollection(List value) { + if (!isLoadedAnnotation) { + if (_boundsCollection.isNotEmpty) { + for (int i = 0; i < value.length; i++) { + _boundsCollection.add(value[i]); + } + } else { + _boundsCollection = value; + } + } else { + _boundsCollection = value; + setQuadPoints(page!.size); + } + } + + /// Gets text markup annotation Type. + PdfTextMarkupAnnotationType get textMarkupAnnotationType => + isLoadedAnnotation + ? _obtainTextMarkupAnnotationType() + : _textMarkupAnnotationType!; + + /// Sets text markup annotation Type. + set textMarkupAnnotationType(PdfTextMarkupAnnotationType value) { + _textMarkupAnnotationType = value; + if (isLoadedAnnotation) { + dictionary![PdfDictionaryProperties.subtype] = PdfName( + getEnumName(_textMarkupAnnotationType), + ); + } + } + + //Implementation + /// Internal method. + void setQuadPoints(Size pageSize) { + if (!isLoadedAnnotation) { + final double pageHeight = pageSize.height; + final PdfMargins margins = obtainMargin()!; + if (bounds.width == 0 && bounds.height == 0) { + bounds = Rect.fromLTWH( + bounds.left, + bounds.top, + bounds.width, + bounds.height, + ); + } + if (_boundsCollection.isEmpty && !_boundsCollection.contains(bounds)) { + _boundsCollection.add(bounds); + } + final int length = _boundsCollection.length * 8; + final List textQuadLocation = List.filled(length, 0); + final int noofRect = length ~/ 8; + for (int i = 0; i < noofRect; i++) { + final double locationX = _boundsCollection[i].left, + locationY = _boundsCollection[i].top; + textQuadLocation[0 + (i * 8)] = locationX + margins.left; + textQuadLocation[1 + (i * 8)] = (pageHeight - locationY) - margins.top; + textQuadLocation[2 + (i * 8)] = + (locationX + _boundsCollection[i].width) + margins.left; + textQuadLocation[3 + (i * 8)] = (pageHeight - locationY) - margins.top; + textQuadLocation[4 + (i * 8)] = locationX + margins.left; + textQuadLocation[5 + (i * 8)] = + textQuadLocation[1 + (i * 8)] - _boundsCollection[i].height; + textQuadLocation[6 + (i * 8)] = + (locationX + _boundsCollection[i].width) + margins.left; + textQuadLocation[7 + (i * 8)] = textQuadLocation[5 + (i * 8)]; + } + points = PdfArray(textQuadLocation); + dictionary!.setProperty(PdfDictionaryProperties.quadPoints, points); + } else { + final List textQuadLocation = List.filled( + _boundsCollection.length * 8, + 0, + ); + final double pageHeight = pageSize.height; + for (int i = 0; i < _boundsCollection.length; i++) { + final double locationX = _boundsCollection[i].left, + locationY = _boundsCollection[i].top; + textQuadLocation[0 + (i * 8)] = locationX; + textQuadLocation[1 + (i * 8)] = pageHeight - locationY; + textQuadLocation[2 + (i * 8)] = locationX + _boundsCollection[i].width; + textQuadLocation[3 + (i * 8)] = pageHeight - locationY; + textQuadLocation[4 + (i * 8)] = locationX; + textQuadLocation[5 + (i * 8)] = + textQuadLocation[1 + (i * 8)] - _boundsCollection[i].height; + textQuadLocation[6 + (i * 8)] = locationX + _boundsCollection[i].width; + textQuadLocation[7 + (i * 8)] = textQuadLocation[5 + (i * 8)]; + } + dictionary!.setProperty( + PdfDictionaryProperties.quadPoints, + PdfArray(textQuadLocation), + ); + } + } + + List _obtainBoundsValue() { + final List collection = []; + if (dictionary!.containsKey(PdfDictionaryProperties.quadPoints)) { + final IPdfPrimitive? points = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.quadPoints], + ); + double x, y, width, height; + if (points != null && points is PdfArray) { + for (int i = 0; i < (points.count / 8).round(); i++) { + final double q1 = + (points[0 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q2 = + (points[1 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q3 = + (points[2 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q4 = + (points[3 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q5 = + (points[4 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q6 = + (points[5 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q7 = + (points[6 + (i * 8)]! as PdfNumber).value!.toDouble(); + final double q8 = + (points[7 + (i * 8)]! as PdfNumber).value!.toDouble(); + x = q5 - q1; + y = q6 - q2; + height = sqrt((x * x) + (y * y)); + x = q7 - q5; + y = q8 - q6; + width = sqrt((x * x) + (y * y)); + final double m = [q1, q3, q5, q7].reduce(min); + final double n = page!.size.height - [q2, q4, q6, q8].reduce(max); + final Rect rect = Rect.fromLTWH(m, n, width, height); + collection.add(rect); + } + } + } + return collection; + } + + PdfTextMarkupAnnotationType _getTextMarkupAnnotation(String aType) { + PdfTextMarkupAnnotationType annotType = + PdfTextMarkupAnnotationType.highlight; + switch (aType) { + case 'Highlight': + annotType = PdfTextMarkupAnnotationType.highlight; + break; + case 'Squiggly': + annotType = PdfTextMarkupAnnotationType.squiggly; + break; + case 'StrikeOut': + annotType = PdfTextMarkupAnnotationType.strikethrough; + break; + case 'Underline': + annotType = PdfTextMarkupAnnotationType.underline; + break; + } + return annotType; + } + + /// internal method + void save() { + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + textMarkupAnnotation, + ); + if (PdfAnnotationCollectionHelper.getHelper( + textMarkupAnnotation.page!.annotations, + ).flatten) { + helper.flatten = true; + } + if (!helper.isLoadedAnnotation) { + final PdfArray textMarkupColor = PdfArray(); + if (!color.isEmpty) { + final double red = color.r / 255; + final double green = color.g / 255; + final double blue = color.b / 255; + if (textMarkupColor.elements.isEmpty) { + textMarkupColor.add(PdfNumber(red)); + textMarkupColor.add(PdfNumber(green)); + textMarkupColor.add(PdfNumber(blue)); + } else { + textMarkupColor.insert(0, PdfNumber(red)); + textMarkupColor.insert(1, PdfNumber(green)); + textMarkupColor.insert(2, PdfNumber(blue)); + } + dictionary!.setProperty(PdfDictionaryProperties.c, textMarkupColor); + } else { + throw ArgumentError.value('TextMarkupColor is not null'); + } + } + if (helper.flatten || helper.setAppearance) { + final PdfTemplate? appearance = _createAppearance(); + if (helper.flatten) { + if (textMarkupAnnotation.page != null) { + _flattenAnnotation(textMarkupAnnotation.page!, appearance); + } + } else { + if (appearance != null) { + textMarkupAnnotation.appearance.normal = appearance; + dictionary!.setProperty( + PdfDictionaryProperties.ap, + PdfReferenceHolder(textMarkupAnnotation.appearance), + ); + } + } + } + if (!helper.flatten && !helper.isLoadedAnnotation) { + super.saveAnnotation(); + _saveTextMarkUpDictionary(); + } + if (helper.flattenPopups) { + helper.flattenPopup(); + } + } + + String _getMarkupAnnotationType() { + switch (textMarkupAnnotationType) { + case PdfTextMarkupAnnotationType.highlight: + return 'Highlight'; + case PdfTextMarkupAnnotationType.underline: + return 'Underline'; + case PdfTextMarkupAnnotationType.squiggly: + return 'Squiggly'; + case PdfTextMarkupAnnotationType.strikethrough: + return 'StrikeOut'; + } + } + + void _saveTextMarkUpDictionary() { + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(_getMarkupAnnotationType()), + ); + } + + PdfTemplate? _createAppearance() { + if (!isLoadedAnnotation || (isLoadedAnnotation && setAppearance)) { + double x, y; + double width = 0, height = 0; + PdfRectangle? rectangle; + if (boundsCollection.length > 1) { + final PdfPath pdfPath = PdfPath(); + for (int i = 0; i < boundsCollection.length; i++) { + pdfPath.addRectangle(boundsCollection[i]); + } + rectangle = PdfPathHelper.getHelper(pdfPath).getBoundsInternal(); + bounds = rectangle.rect; + width = rectangle.width; + height = rectangle.height; + } else { + if (dictionary!.containsKey(PdfDictionaryProperties.quadPoints)) { + final IPdfPrimitive? mQuadPoints = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.quadPoints], + ); + if (mQuadPoints != null && mQuadPoints is PdfArray) { + for (int i = 0; i < (mQuadPoints.count / 8); i++) { + if (isLoadedAnnotation) { + final List quadPoints = List.filled( + mQuadPoints.count ~/ 2, + Offset.zero, + ); + int j = 0; + for (int k = 0; k < mQuadPoints.count;) { + final double x1 = + (mQuadPoints[k]! as PdfNumber).value!.toDouble(); + final double y1 = + (mQuadPoints[k + 1]! as PdfNumber).value!.toDouble(); + quadPoints[j] = Offset(x1, y1); + k = k + 2; + j++; + } + final PdfPath path = PdfPath(); + PdfPathHelper.getHelper(path).addLines(quadPoints); + rectangle = PdfPathHelper.getHelper(path).getBoundsInternal(); + height = rectangle.height; + width = rectangle.width; + } else { + x = + ((mQuadPoints[4 + (i * 8)]! as PdfNumber).value! - + (mQuadPoints[0 + (i * 8)]! as PdfNumber).value!) + .toDouble(); + y = + ((mQuadPoints[5 + (i * 8)]! as PdfNumber).value! - + (mQuadPoints[1 + (i * 8)]! as PdfNumber).value!) + .toDouble(); + height = sqrt((x * x) + (y * y)); + x = + ((mQuadPoints[6 + (i * 8)]! as PdfNumber).value! - + (mQuadPoints[4 + (i * 8)]! as PdfNumber).value!) + .toDouble(); + y = + ((mQuadPoints[7 + (i * 8)]! as PdfNumber).value! - + (mQuadPoints[5 + (i * 8)]! as PdfNumber).value!) + .toDouble(); + width = sqrt((x * x) + (y * y)); + bounds = Rect.fromLTWH(bounds.left, bounds.top, width, height); + } + } + } + } + } + final PdfTemplate template = PdfTemplate(width, height); + PdfAnnotationHelper.setMatrixToZeroRotation( + PdfTemplateHelper.getHelper(template).content, + ); + final PdfGraphics graphics = template.graphics!; + graphics.setTransparency(opacity, mode: PdfBlendMode.multiply); + if (boundsCollection.length > 1) { + for (int i = 0; i < boundsCollection.length; i++) { + if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.highlight) { + graphics.drawRectangle( + brush: PdfSolidBrush(color), + bounds: Rect.fromLTWH( + boundsCollection[i].left - rectangle!.x, + boundsCollection[i].top - rectangle.y, + boundsCollection[i].width, + boundsCollection[i].height, + ), + ); + } else if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.underline) { + graphics.drawLine( + PdfPen(color, width: boundsCollection[i].height * 0.05), + Offset( + boundsCollection[i].left - rectangle!.x, + (boundsCollection[i].top - rectangle.y) + + (boundsCollection[i].height - + ((boundsCollection[i].height / 2) / 3)), + ), + Offset( + boundsCollection[i].width + + (boundsCollection[i].left - rectangle.x), + (boundsCollection[i].top - rectangle.y) + + (boundsCollection[i].height - + ((boundsCollection[i].height / 2) / 3)), + ), + ); + } else if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.strikethrough) { + graphics.drawLine( + PdfPen(color, width: boundsCollection[i].height * 0.05), + Offset( + boundsCollection[i].left - rectangle!.x, + (boundsCollection[i].top - rectangle.y) + + (boundsCollection[i].height - + (boundsCollection[i].height / 2)), + ), + Offset( + boundsCollection[i].width + + (boundsCollection[i].left - rectangle.x), + (boundsCollection[i].top - rectangle.y) + + (boundsCollection[i].height - + (boundsCollection[i].height / 2)), + ), + ); + } else if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.squiggly) { + final PdfPen pdfPen = PdfPen( + color, + width: boundsCollection[i].height * 0.02, + ); + graphics.save(); + graphics.translateTransform( + boundsCollection[i].left - rectangle!.x, + boundsCollection[i].top - rectangle.y, + ); + graphics.setClip( + bounds: Rect.fromLTWH( + 0, + 0, + boundsCollection[i].width, + boundsCollection[i].height, + ), + ); + graphics.drawPath( + _drawSquiggly( + boundsCollection[i].width, + boundsCollection[i].height, + ), + pen: pdfPen, + ); + graphics.restore(); + } + } + } else { + if (textMarkupAnnotationType == PdfTextMarkupAnnotationType.highlight) { + graphics.drawRectangle( + brush: PdfSolidBrush(color), + bounds: Rect.fromLTWH(0, 0, width, height), + ); + } else if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.underline) { + graphics.drawLine( + PdfPen(color, width: height * 0.05), + Offset(0, height - ((height / 2) / 3)), + Offset(width, height - ((height / 2) / 3)), + ); + } else if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.strikethrough) { + graphics.drawLine( + PdfPen(color, width: height * 0.05), + Offset(0, height / 2), + Offset(width, height / 2), + ); + } else if (textMarkupAnnotationType == + PdfTextMarkupAnnotationType.squiggly) { + final PdfPen pdfPen = PdfPen(color, width: height * 0.02); + graphics.drawPath(_drawSquiggly(width, height), pen: pdfPen); + } + if (isLoadedAnnotation) { + dictionary![PdfDictionaryProperties.rect] = PdfArray.fromRectangle( + rectangle!, + ); + } + } + return template; + } + return null; + } + + PdfPath _drawSquiggly(double width, double height) { + if (width.toInt() % 2 != 0 || width.round() > width) { + width = width + 1; + } + final PdfPath path = PdfPath(); + final List mPathPoints = List.filled( + ((width / height) * 16).ceil(), + Offset.zero, + ); + final double length = width / (mPathPoints.length / 2); + final double location = (length + length) * 0.6; + double zigZag = location; + double x = 0; + for (int i = 0; i < mPathPoints.length; i++, x += length) { + mPathPoints[i] = Offset( + x, + ((height - location) + zigZag) - (height * 0.02), + ); + if (zigZag == 0) { + zigZag = location; + } else { + zigZag = 0; + } + } + PdfPathHelper.getHelper(path).addLines(mPathPoints); + return path; + } + + void _flattenAnnotation(PdfPage page, PdfTemplate? appearance) { + if (!isLoadedAnnotation) { + if (appearance != null) { + page.graphics.save(); + final Rect rectangle = calculateTemplateBounds( + bounds, + page, + appearance, + true, + ); + if (opacity < 1) { + page.graphics.setTransparency(opacity); + } + page.graphics.drawPdfTemplate( + appearance, + Offset(rectangle.left, rectangle.top), + rectangle.size, + ); + page.annotations.remove(textMarkupAnnotation); + page.graphics.restore(); + } + } else { + if (dictionary != null && + dictionary!.containsKey(PdfDictionaryProperties.ap) && + appearance == null) { + IPdfPrimitive? appearanceDictionary = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.ap], + ); + if (appearanceDictionary != null && + appearanceDictionary is PdfDictionary) { + appearanceDictionary = PdfCrossTable.dereference( + appearanceDictionary[PdfDictionaryProperties.n], + ); + if (appearanceDictionary != null && + appearanceDictionary is PdfStream) { + appearance = PdfTemplateHelper.fromPdfStream(appearanceDictionary); + final bool isNormalMatrix = validateTemplateMatrix( + appearanceDictionary, + ); + if (isNormalMatrix && + page.rotation != PdfPageRotateAngle.rotateAngle0) { + flattenAnnotationTemplate(appearance, isNormalMatrix); + } else if (isNormalMatrix && + isValidTemplateMatrix( + appearanceDictionary, + bounds.topLeft, + appearance, + )) { + flattenAnnotationTemplate(appearance, isNormalMatrix); + } + } else { + setAppearance = true; + appearance = _createAppearance(); + if (appearance != null) { + final bool isNormalMatrix = validateTemplateMatrix( + PdfTemplateHelper.getHelper(appearance).content, + ); + flattenAnnotationTemplate(appearance, isNormalMatrix); + } + } + } + } else if (!dictionary!.containsKey(PdfDictionaryProperties.ap) && + appearance == null) { + setAppearance = true; + appearance = _createAppearance(); + if (appearance != null) { + final bool isNormalMatrix = validateTemplateMatrix( + PdfTemplateHelper.getHelper(appearance).content, + ); + flattenAnnotationTemplate(appearance, isNormalMatrix); + } + } else if (!dictionary!.containsKey(PdfDictionaryProperties.ap) && + appearance != null) { + final bool isNormalMatrix = validateTemplateMatrix( + PdfTemplateHelper.getHelper(appearance).content, + ); + flattenAnnotationTemplate(appearance, isNormalMatrix); + } else if (dictionary!.containsKey(PdfDictionaryProperties.ap) && + appearance != null) { + final bool isNormalMatrix = validateTemplateMatrix( + PdfTemplateHelper.getHelper(appearance).content, + ); + flattenAnnotationTemplate(appearance, isNormalMatrix); + } + } + } + + PdfTextMarkupAnnotationType _obtainTextMarkupAnnotationType() { + final IPdfPrimitive? annotType = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.subtype], + ); + if (annotType != null && annotType is PdfName) { + final String aType = annotType.name.toString(); + return _getTextMarkupAnnotation(aType); + } + return PdfTextMarkupAnnotationType.highlight; + } + + /// internal method + @override + IPdfPrimitive? get element => dictionary; + @override + set element(IPdfPrimitive? value) { + if (value != null && value is PdfDictionary) { + dictionary = 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 4763790ba..978ab2008 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 @@ -33,7 +33,7 @@ import 'pdf_uri_annotation.dart'; /// lineAlignment: PdfVerticalAlignment.middle)) /// .draw(document.pages.add(), Offset(50, 40)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -55,24 +55,28 @@ class PdfTextWebLink extends PdfAnnotation { /// lineAlignment: PdfVerticalAlignment.middle)) /// .draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfTextWebLink( - {required String url, - String? text, - PdfBrush? brush, - PdfFont? font, - PdfPen? pen, - PdfStringFormat? format}) { + PdfTextWebLink({ + required String url, + String? text, + PdfBrush? brush, + PdfFont? font, + PdfPen? pen, + PdfStringFormat? format, + }) { _helper = PdfTextWebLinkHelper(this); _initializeWebLink(text, font, pen, brush, format); this.url = url; } PdfTextWebLink._( - PdfDictionary dictionary, PdfCrossTable crossTable, String? annotText) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String? annotText, + ) { _helper = PdfTextWebLinkHelper._(this, dictionary, crossTable); text = annotText != null && annotText.isNotEmpty ? annotText : ''; } @@ -100,7 +104,7 @@ class PdfTextWebLink extends PdfAnnotation { /// //Draw the web link in the PDF page /// textWebLink.draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -124,7 +128,7 @@ class PdfTextWebLink extends PdfAnnotation { /// //Draw the web link in the PDF page /// textWebLink.draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -148,7 +152,7 @@ class PdfTextWebLink extends PdfAnnotation { /// //Draw the web link in the PDF page /// textWebLink.draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -172,7 +176,7 @@ class PdfTextWebLink extends PdfAnnotation { /// //Draw the web link in the PDF page /// textWebLink.draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -198,7 +202,7 @@ class PdfTextWebLink extends PdfAnnotation { /// //Draw the web link in the PDF page /// textWebLink.draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -231,8 +235,13 @@ class PdfTextWebLink extends PdfAnnotation { } // implementation - void _initializeWebLink(String? annotText, PdfFont? font, PdfPen? pen, - PdfBrush? brush, PdfStringFormat? format) { + void _initializeWebLink( + String? annotText, + PdfFont? font, + PdfPen? pen, + PdfBrush? brush, + PdfStringFormat? format, + ) { text = annotText != null && annotText.isNotEmpty ? annotText : ''; if (font != null) { this.font = font; @@ -265,7 +274,7 @@ class PdfTextWebLink extends PdfAnnotation { /// //Draw the web link in the PDF page /// textWebLink.draw(document.pages.add(), Offset(50, 40)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -275,7 +284,11 @@ class PdfTextWebLink extends PdfAnnotation { 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); + location.dx, + location.dy, + textSize.width, + textSize.height, + ); _uriAnnotation = PdfUriAnnotation(bounds: rect, uri: url); _uriAnnotation.border = PdfAnnotationBorder(0, 0, 0); page.annotations.add(_uriAnnotation); @@ -284,8 +297,14 @@ class PdfTextWebLink extends PdfAnnotation { } void _drawInternal(PdfGraphics graphics, Rect bounds, PdfFont pdfFont) { - graphics.drawString(text, pdfFont, - pen: pen, brush: brush, bounds: bounds, format: stringFormat); + graphics.drawString( + text, + pdfFont, + pen: pen, + brush: brush, + bounds: bounds, + format: stringFormat, + ); } String? _obtainUrl() { @@ -319,8 +338,10 @@ class PdfTextWebLinkHelper extends PdfAnnotationHelper { /// internal constructor PdfTextWebLinkHelper._( - this.webLinkHelper, PdfDictionary dictionary, PdfCrossTable crossTable) - : super(webLinkHelper) { + this.webLinkHelper, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(webLinkHelper) { initializeExistingAnnotation(dictionary, crossTable); } @@ -333,7 +354,10 @@ class PdfTextWebLinkHelper extends PdfAnnotationHelper { /// internal method static PdfTextWebLink load( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { return PdfTextWebLink._(dictionary, crossTable, text); } 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 7611c88cb..9b8d80576 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 @@ -3,6 +3,7 @@ import 'dart:ui'; import '../../interfaces/pdf_interface.dart'; import '../actions/pdf_action.dart'; import '../actions/pdf_uri_action.dart'; +import '../graphics/pdf_color.dart'; import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../primitives/pdf_dictionary.dart'; @@ -10,6 +11,7 @@ import '../primitives/pdf_name.dart'; import '../primitives/pdf_string.dart'; import 'pdf_action_annotation.dart'; import 'pdf_annotation.dart'; +import 'pdf_annotation_border.dart'; /// Represents the Uri annotation. class PdfUriAnnotation extends PdfActionLinkAnnotation { @@ -22,7 +24,10 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { } PdfUriAnnotation._( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { _helper = PdfUriAnnotationHelper._(this, dictionary, crossTable); super.text = text; } @@ -49,8 +54,11 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { if (_uri != value) { _uri = value; if (tempDictionary.containsKey(PdfDictionaryProperties.a)) { - final PdfDictionary? dictionary = PdfCrossTable.dereference( - tempDictionary[PdfDictionaryProperties.a]) as PdfDictionary?; + final PdfDictionary? dictionary = + PdfCrossTable.dereference( + tempDictionary[PdfDictionaryProperties.a], + ) + as PdfDictionary?; if (dictionary != null) { dictionary.setString(PdfDictionaryProperties.uri, _uri); dictionary.modify(); @@ -64,8 +72,11 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { if (helper.isLoadedAnnotation) { PdfDictionary? dictionary = tempDictionary; if (tempDictionary.containsKey(PdfDictionaryProperties.a)) { - dictionary = PdfCrossTable.dereference( - tempDictionary[PdfDictionaryProperties.a]) as PdfDictionary?; + dictionary = + PdfCrossTable.dereference( + tempDictionary[PdfDictionaryProperties.a], + ) + as PdfDictionary?; if (dictionary != null) { dictionary.setString(PdfDictionaryProperties.uri, _uri); } @@ -76,6 +87,24 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { } } + /// Gets annotation's border properties like width, horizontal radius etc. + PdfAnnotationBorder get border { + return _helper.border; + } + + /// Sets annotation's border properties like width, horizontal radius etc. + set border(PdfAnnotationBorder value) { + _helper.border = value; + } + + /// Gets the annotation color. + PdfColor get color => _helper.color; + + /// Sets the annotation color. + set color(PdfColor value) { + _helper.color = value; + } + /// Gets the action. @override PdfAction? get action => super.action; @@ -115,20 +144,26 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { class PdfUriAnnotationHelper extends PdfActionLinkAnnotationHelper { /// internal constructor PdfUriAnnotationHelper(this.uriAnnotation, Rect bounds) - : super(uriAnnotation, bounds) { + : super(uriAnnotation, bounds) { _uriAction ??= PdfUriAction(); - dictionary!.setProperty(PdfName(PdfDictionaryProperties.subtype), - PdfName(PdfDictionaryProperties.link)); + dictionary!.setProperty( + PdfName(PdfDictionaryProperties.subtype), + PdfName(PdfDictionaryProperties.link), + ); final IPdfPrimitive? element = IPdfWrapper.getElement(_uriAction!); if (element == null) { IPdfWrapper.setElement( - _uriAction!, PdfActionHelper.getHelper(_uriAction!).dictionary); + _uriAction!, + PdfActionHelper.getHelper(_uriAction!).dictionary, + ); } dictionary!.setProperty(PdfName(PdfDictionaryProperties.a), element); } PdfUriAnnotationHelper._( - this.uriAnnotation, PdfDictionary dictionary, PdfCrossTable crossTable) - : super.load(uriAnnotation, dictionary, crossTable); + this.uriAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super.load(uriAnnotation, dictionary, crossTable); /// internal field late PdfUriAnnotation uriAnnotation; @@ -140,7 +175,10 @@ class PdfUriAnnotationHelper extends PdfActionLinkAnnotationHelper { /// internal method static PdfUriAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable, String text) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + String text, + ) { return PdfUriAnnotation._(dictionary, crossTable, text); } 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 index 3e0a2a3d2..063f425ba 100644 --- 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 @@ -1,4 +1,3 @@ -import '../../interfaces/pdf_interface.dart'; import '../actions/pdf_annotation_action.dart'; import '../forms/pdf_field.dart'; import '../general/pdf_default_appearance.dart'; @@ -52,15 +51,18 @@ class WidgetAnnotation extends PdfAnnotation { } /// Sets the parent. + // ignore: avoid_setters_without_getters set parent(PdfField? value) { if (_parent != value) { _parent = value; _parent != null ? PdfAnnotationHelper.getHelper(this).dictionary!.setProperty( - PdfDictionaryProperties.parent, PdfReferenceHolder(_parent)) - : PdfAnnotationHelper.getHelper(this) - .dictionary! - .remove(PdfDictionaryProperties.parent); + PdfDictionaryProperties.parent, + PdfReferenceHolder(_parent), + ) + : PdfAnnotationHelper.getHelper( + this, + ).dictionary!.remove(PdfDictionaryProperties.parent); } } @@ -69,9 +71,10 @@ class WidgetAnnotation extends PdfAnnotation { set textAlignment(PdfTextAlignment? value) { if (alignment != value) { alignment = value; - PdfAnnotationHelper.getHelper(this) - .dictionary! - .setProperty(PdfDictionaryProperties.q, PdfNumber(alignment!.index)); + PdfAnnotationHelper.getHelper(this).dictionary!.setProperty( + PdfDictionaryProperties.q, + PdfNumber(alignment!.index), + ); } } @@ -83,8 +86,9 @@ class WidgetAnnotation extends PdfAnnotation { set highlightMode(PdfHighlightMode? value) { _highlightMode = value; PdfAnnotationHelper.getHelper(this).dictionary!.setName( - PdfName(PdfDictionaryProperties.h), - _helper.highlightModeToString(_highlightMode)); + PdfName(PdfDictionaryProperties.h), + _helper.highlightModeToString(_highlightMode), + ); } /// internal property @@ -98,11 +102,14 @@ class WidgetAnnotation extends PdfAnnotation { } /// internal property + // ignore: avoid_setters_without_getters set appearanceState(String value) { if (_appearState != value) { _appearState = value; PdfAnnotationHelper.getHelper(this).dictionary!.setName( - PdfName(PdfDictionaryProperties.usageApplication), _appearState); + PdfName(PdfDictionaryProperties.usageApplication), + _appearState, + ); } } @@ -110,9 +117,9 @@ class WidgetAnnotation extends PdfAnnotation { PdfAnnotationActions? get actions { if (_actions == null) { _actions = PdfAnnotationActions(); - PdfAnnotationHelper.getHelper(this) - .dictionary! - .setProperty(PdfDictionaryProperties.aa, _actions); + PdfAnnotationHelper.getHelper( + this, + ).dictionary!.setProperty(PdfDictionaryProperties.aa, _actions); } return _actions; } @@ -120,9 +127,9 @@ class WidgetAnnotation extends PdfAnnotation { set actions(PdfAnnotationActions? value) { if (value != null) { _actions = value; - PdfAnnotationHelper.getHelper(this) - .dictionary! - .setProperty(PdfDictionaryProperties.aa, _actions); + PdfAnnotationHelper.getHelper( + this, + ).dictionary!.setProperty(PdfDictionaryProperties.aa, _actions); } } } @@ -133,17 +140,23 @@ class WidgetAnnotationHelper extends PdfAnnotationHelper { WidgetAnnotationHelper(this.widgetAnnotation) : super(widgetAnnotation) { initializeAnnotation(); dictionary!.setNumber(PdfDictionaryProperties.f, 4); //Sets print. - dictionary!.setProperty(PdfDictionaryProperties.subtype, - PdfName(PdfDictionaryProperties.widget)); + dictionary!.setProperty( + PdfDictionaryProperties.subtype, + PdfName(PdfDictionaryProperties.widget), + ); widgetAnnotation.widgetBorder ??= PdfAnnotationBorderHelper.getWidgetBorder(); - dictionary! - .setProperty(PdfDictionaryProperties.bs, widgetAnnotation.widgetBorder); + dictionary!.setProperty( + PdfDictionaryProperties.bs, + widgetAnnotation.widgetBorder, + ); widgetAppearance = WidgetAppearance(); } WidgetAnnotationHelper._( - this.widgetAnnotation, PdfDictionary dictionary, PdfCrossTable crossTable) - : super(widgetAnnotation) { + this.widgetAnnotation, + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) : super(widgetAnnotation) { initializeExistingAnnotation(dictionary, crossTable); } @@ -164,7 +177,9 @@ class WidgetAnnotationHelper extends PdfAnnotationHelper { /// internal method static WidgetAnnotation load( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return WidgetAnnotation._(dictionary, crossTable); } @@ -187,7 +202,8 @@ class WidgetAnnotationHelper extends PdfAnnotationHelper { PdfDocumentHelper.getHelper(document).conformanceLevel == PdfConformanceLevel.a3b)) { throw ArgumentError( - "The appearance dictionary doesn't contain an entry in the conformance PDF."); + "The appearance dictionary doesn't contain an entry in the conformance PDF.", + ); } helper.saveAnnotation(); _onBeginSave(); @@ -197,32 +213,25 @@ class WidgetAnnotationHelper extends PdfAnnotationHelper { dictionary.setProperty(PdfDictionaryProperties.mk, widgetAppearance); } else { dictionary.setProperty(PdfDictionaryProperties.ap, null); - bool isSignatureField = false; final PdfAppearance? tempAppearance = helper.appearance; dictionary.setProperty( - PdfDictionaryProperties.ap, - tempAppearance != null && - PdfAppearanceHelper.getHelper(tempAppearance) - .templateNormal != - null - ? tempAppearance - : null); - if (dictionary.containsKey(PdfDictionaryProperties.ft)) { - final IPdfPrimitive? signatureName = PdfCrossTable.dereference( - helper.dictionary![PdfDictionaryProperties.ft]); - if (signatureName is PdfName && - signatureName.name == PdfDictionaryProperties.sig) { - isSignatureField = true; - } - } - if (!isSignatureField) { + PdfDictionaryProperties.ap, + tempAppearance != null && + PdfAppearanceHelper.getHelper(tempAppearance).templateNormal != + null + ? tempAppearance + : null, + ); + if (widgetAppearance != null && widgetAppearance!.dictionary!.count > 0) { dictionary.setProperty(PdfDictionaryProperties.mk, widgetAppearance); } dictionary.setProperty(PdfDictionaryProperties.usageApplication, null); } if (pdfDefaultAppearance != null) { - dictionary.setProperty(PdfDictionaryProperties.da, - PdfString(pdfDefaultAppearance!.getString())); + dictionary.setProperty( + PdfDictionaryProperties.da, + PdfString(pdfDefaultAppearance!.getString()), + ); } } @@ -235,6 +244,7 @@ class WidgetAnnotationHelper extends PdfAnnotationHelper { return 'O'; case PdfHighlightMode.push: return 'P'; + // ignore: no_default_cases default: return 'I'; } @@ -248,11 +258,14 @@ class WidgetAnnotationHelper extends PdfAnnotationHelper { PdfHighlightMode _obtainHighlightMode() { PdfHighlightMode mode = PdfHighlightMode.noHighlighting; - if (PdfAnnotationHelper.getHelper(base) - .dictionary! - .containsKey(PdfDictionaryProperties.h)) { - final PdfName name = PdfAnnotationHelper.getHelper(base) - .dictionary![PdfDictionaryProperties.h]! as PdfName; + if (PdfAnnotationHelper.getHelper( + base, + ).dictionary!.containsKey(PdfDictionaryProperties.h)) { + final PdfName name = + PdfAnnotationHelper.getHelper( + base, + ).dictionary![PdfDictionaryProperties.h]! + as PdfName; switch (name.name) { case 'I': mode = PdfHighlightMode.invert; 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 index f09f030b8..6c0bb474e 100644 --- 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 @@ -1,5 +1,4 @@ import '../../interfaces/pdf_interface.dart'; -import '../graphics/enums.dart'; import '../graphics/pdf_color.dart'; import '../io/pdf_constants.dart'; import '../primitives/pdf_array.dart'; @@ -9,16 +8,16 @@ import '../primitives/pdf_dictionary.dart'; class WidgetAppearance implements IPdfWrapper { /// internal Constructor WidgetAppearance() : super() { - dictionary!.setProperty(PdfDictionaryProperties.bc, - PdfColorHelper.toArray(_borderColor, PdfColorSpace.rgb)); - dictionary!.setProperty(PdfDictionaryProperties.bg, - PdfColorHelper.toArray(_backColor, PdfColorSpace.rgb)); + dictionary!.setProperty( + PdfDictionaryProperties.bc, + PdfColorHelper.toArray(_borderColor), + ); } /// internal field PdfDictionary? dictionary = PdfDictionary(); PdfColor _borderColor = PdfColor(0, 0, 0); - PdfColor _backColor = PdfColor(255, 255, 255); + PdfColor _backColor = PdfColor.empty; String? _normalCaption = ''; //Overrides @@ -35,10 +34,14 @@ class WidgetAppearance implements IPdfWrapper { if (_borderColor != value) { _borderColor = value; PdfColorHelper.getHelper(value).alpha == 0 - ? dictionary! - .setProperty(PdfDictionaryProperties.bc, PdfArray([])) - : dictionary!.setProperty(PdfDictionaryProperties.bc, - PdfColorHelper.toArray(_borderColor, PdfColorSpace.rgb)); + ? dictionary!.setProperty( + PdfDictionaryProperties.bc, + PdfArray([]), + ) + : dictionary!.setProperty( + PdfDictionaryProperties.bc, + PdfColorHelper.toArray(_borderColor), + ); } } @@ -48,12 +51,16 @@ class WidgetAppearance implements IPdfWrapper { if (_backColor != value) { _backColor = value; if (PdfColorHelper.getHelper(_backColor).alpha == 0) { - dictionary! - .setProperty(PdfDictionaryProperties.bc, PdfArray([0, 0, 0])); + dictionary!.setProperty( + PdfDictionaryProperties.bc, + PdfArray([0, 0, 0]), + ); dictionary!.remove(PdfDictionaryProperties.bg); } else { - dictionary!.setProperty(PdfDictionaryProperties.bg, - PdfColorHelper.toArray(_backColor, PdfColorSpace.rgb)); + dictionary!.setProperty( + PdfDictionaryProperties.bg, + PdfColorHelper.toArray(_backColor), + ); } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart new file mode 100644 index 000000000..b5201aaf1 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/xfdf_parser.dart @@ -0,0 +1,1570 @@ +import 'dart:convert'; +import 'dart:ui'; + +import 'package:convert/convert.dart'; +import 'package:xml/xml.dart'; + +import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_xfdf_document.dart'; +import '../graphics/figures/pdf_template.dart'; +import '../graphics/images/pdf_bitmap.dart'; +import '../graphics/pdf_transformation_matrix.dart'; +import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pages/pdf_page.dart'; +import '../pdf_document/pdf_document.dart'; +import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; +import 'enum.dart'; +import 'json_parser.dart'; +import 'pdf_annotation.dart'; + +/// The class provides methods and properties to handle the loaded annotations from the existing PDF document for Xfdf export and import. +class XfdfParser { + //Consructor + /// Initializes a new instance of the [XfdfParser] class. + XfdfParser(List data, PdfDocument document) { + _document = document; + final String xmlString = utf8.decode(data); + _xmlDocument = XmlDocument.parse(xmlString); + if (_xmlDocument.rootElement.localName.toLowerCase() != 'xfdf') { + throw ArgumentError.value( + _xmlDocument.rootElement.localName, + 'xmlString', + 'Invalid XFDF data', + ); + } + } + + //Fields + late PdfDocument _document; + late XmlDocument _xmlDocument; + Map? _groupReferences; + List? _groupHolders; + + //Implementation. + /// internal method. + void parseAndImportAnnotationData() { + for (final XmlNode node + in _xmlDocument.rootElement.firstElementChild!.children) { + if (node is XmlElement) { + _parseAnnotationData(node); + } + } + } + + void _parseAnnotationData(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? pageIndexString = element.getAttribute('page'); + if (pageIndexString != null && !isNullOrEmpty(pageIndexString)) { + final int pageIndex = int.parse(pageIndexString); + if (pageIndex >= 0 && pageIndex < _document.pages.count) { + PdfPageHelper.getHelper(_document.pages[pageIndex]).importAnnotation = + true; + final PdfDictionary annotationDictionary = _getAnnotation( + element, + pageIndex, + ); + if (annotationDictionary.count > 0) { + final PdfReferenceHolder holder = PdfReferenceHolder( + annotationDictionary, + ); + if (annotationDictionary.containsKey('NM') || + annotationDictionary.containsKey(PdfDictionaryProperties.irt)) { + _addReferenceToGroup(holder, annotationDictionary); + } + final PdfDictionary? pageDictionary = + PdfPageHelper.getHelper(_document.pages[pageIndex]).dictionary; + if (pageDictionary != null) { + if (!pageDictionary.containsKey(PdfDictionaryProperties.annots)) { + pageDictionary[PdfDictionaryProperties.annots] = PdfArray(); + } + final IPdfPrimitive? annots = PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.annots], + ); + if (annots != null && annots is PdfArray) { + annots.elements.add(holder); + _handlePopUp(annots, holder, annotationDictionary); + annots.changed = true; + } + } + } + } + } + } + } + + void _handlePopUp( + PdfArray annots, + PdfReferenceHolder holder, + PdfDictionary annotDictionary, + ) { + if (annotDictionary.containsKey(PdfDictionaryProperties.popup)) { + final IPdfPrimitive? popup = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.popup], + ); + if (popup != null && popup is PdfDictionary) { + popup.setProperty(PdfDictionaryProperties.parent, holder); + annots.add(popup); + } + } + } + + PdfDictionary _getAnnotation(XmlElement element, int pageIndex) { + final PdfDictionary annotationDictionary = PdfDictionary(); + annotationDictionary.setName( + PdfName(PdfDictionaryProperties.name), + PdfDictionaryProperties.annot, + ); + bool isValidType = true; + if (!isNullOrEmpty(element.localName)) { + switch (element.localName.toLowerCase()) { + case 'line': + annotationDictionary.setName( + PdfName(PdfDictionaryProperties.subtype), + PdfDictionaryProperties.line, + ); + final String? start = element.getAttribute('start'); + final String? end = element.getAttribute('end'); + if (start != null && end != null) { + final List linePoints = []; + _addLinePoints(linePoints, start); + _addLinePoints(linePoints, end); + if (linePoints.length == 4) { + annotationDictionary.setProperty( + PdfDictionaryProperties.l, + PdfArray(linePoints), + ); + } + linePoints.clear(); + } + _addLineEndStyle(element, annotationDictionary); + break; + case 'circle': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.circle, + ); + break; + case 'square': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.square, + ); + break; + case 'polyline': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'PolyLine', + ); + _addLineEndStyle(element, annotationDictionary); + break; + case 'polygon': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.polygon, + ); + _addLineEndStyle(element, annotationDictionary); + break; + case 'ink': + annotationDictionary.setName(PdfDictionaryProperties.subtype, 'Ink'); + break; + case 'popup': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.popup, + ); + break; + case 'text': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.text, + ); + break; + case 'freetext': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'FreeText', + ); + _addLineEndStyle(element, annotationDictionary); + break; + case 'stamp': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'Stamp', + ); + break; + case 'highlight': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.highlight, + ); + break; + case 'squiggly': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.squiggly, + ); + break; + case 'underline': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.underline, + ); + break; + case 'strikeout': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + PdfDictionaryProperties.strikeOut, + ); + break; + case 'fileattachment': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'FileAttachment', + ); + break; + case 'sound': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'Sound', + ); + break; + case 'caret': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'Caret', + ); + break; + case 'redact': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'Redact', + ); + break; + case 'watermark': + annotationDictionary.setName( + PdfDictionaryProperties.subtype, + 'Watermark', + ); + break; + default: + isValidType = false; + break; + } + if (isValidType) { + _addAnnotationData(annotationDictionary, element, pageIndex); + } + } + return annotationDictionary; + } + + void _addAnnotationData( + PdfDictionary annotDictionary, + XmlElement element, + int pageIndex, + ) { + _addBorderStyle(annotDictionary, element); + _applyAttributeValues(annotDictionary, element); + _parseInnerElements(annotDictionary, element, pageIndex); + _addMeasureDictionary(annotDictionary, element); + } + + void _addBorderStyle(PdfDictionary annotDictionary, XmlElement element) { + if (element.attributes.isNotEmpty) { + final PdfDictionary borderEffectDictionary = PdfDictionary(); + final PdfDictionary borderStyleDictionary = PdfDictionary(); + String? attribute = element.getAttribute('width'); + if (!isNullOrEmpty(attribute)) { + borderStyleDictionary.setNumber( + PdfDictionaryProperties.w, + double.parse(attribute!), + ); + } + bool isBasicStyle = true; + attribute = element.getAttribute('style'); + if (!isNullOrEmpty(attribute)) { + String style = ''; + switch (attribute) { + case 'dash': + style = PdfDictionaryProperties.d; + break; + case 'solid': + style = PdfDictionaryProperties.s; + break; + case 'bevelled': + style = 'B'; + break; + case 'inset': + style = PdfDictionaryProperties.i; + break; + case 'underline': + style = PdfDictionaryProperties.u; + break; + case 'cloudy': + style = PdfDictionaryProperties.c; + isBasicStyle = false; + break; + } + if (!isNullOrEmpty(style)) { + (isBasicStyle ? borderStyleDictionary : borderEffectDictionary) + .setName(PdfDictionaryProperties.s, style); + attribute = element.getAttribute('intensity'); + if (!isBasicStyle && !isNullOrEmpty(attribute)) { + borderEffectDictionary.setNumber( + PdfDictionaryProperties.i, + double.parse(attribute!), + ); + } else { + attribute = element.getAttribute('dashes'); + if (!isNullOrEmpty(attribute)) { + final List dashPoints = _obtainFloatPoints(attribute!); + if (dashPoints.isNotEmpty) { + borderStyleDictionary.setProperty( + PdfDictionaryProperties.d, + PdfArray(dashPoints), + ); + } + } + } + } + } + if (borderEffectDictionary.count > 0) { + annotDictionary.setProperty( + PdfDictionaryProperties.be, + PdfReferenceHolder(borderEffectDictionary), + ); + } else { + borderEffectDictionary.clear(); + borderEffectDictionary.isSaving = false; + } + if (borderStyleDictionary.count > 0) { + borderStyleDictionary.setProperty( + PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.border), + ); + annotDictionary.setProperty( + PdfDictionaryProperties.bs, + PdfReferenceHolder(borderStyleDictionary), + ); + } else { + borderStyleDictionary.clear(); + borderStyleDictionary.isSaving = false; + } + } + } + + List _obtainFloatPoints(String value) { + final List result = []; + if (!isNullOrEmpty(value)) { + final List pointsArray = value.split(','); + for (final String pointString in pointsArray) { + final double? point = double.tryParse(pointString); + if (point != null) { + result.add(point); + } + } + } + return result; + } + + void _addLinePoints(List linePoints, String value) { + if (!isNullOrEmpty(value) && value.contains(',')) { + final List pointsArray = value.split(','); + for (final String pointString in pointsArray) { + final double? point = double.tryParse(pointString); + if (point != null) { + linePoints.add(point); + } + } + } + } + + void _applyAttributeValues( + PdfDictionary annotDictionary, + XmlElement element, + ) { + for (final XmlAttribute attribute in element.attributes) { + final String value = attribute.value; + final XmlName name = attribute.name; + switch (name.local.toLowerCase()) { + case 'page': + case 'start': + case 'end': + case 'width': + case 'head': + case 'tail': + case 'style': + case 'intensity': + case 'itex': + break; + case 'state': + _addString(annotDictionary, 'State', value); + break; + case 'statemodel': + _addString(annotDictionary, 'StateModel', value); + break; + case 'replytype': + if (value == 'group') { + annotDictionary.setName('RT', 'Group'); + } + break; + case 'inreplyto': + _addString(annotDictionary, PdfDictionaryProperties.irt, value); + break; + case 'rect': + final List points = _obtainFloatPoints(value); + if (points.isNotEmpty && points.length == 4) { + annotDictionary.setProperty( + PdfDictionaryProperties.rect, + PdfArray(points), + ); + } + break; + case 'color': + if (!isNullOrEmpty(value)) { + final PdfArray? colorArray = _getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.c, + colorArray, + ); + } + } + break; + case 'interior-color': + if (!isNullOrEmpty(value)) { + final PdfArray? colorArray = _getColorArray(value); + if (colorArray != null) { + annotDictionary.setProperty( + PdfDictionaryProperties.ic, + colorArray, + ); + } + } + break; + case 'date': + _addString(annotDictionary, PdfDictionaryProperties.m, value); + break; + case 'creationdate': + _addString( + annotDictionary, + PdfDictionaryProperties.creationDate, + value, + ); + break; + case 'name': + _addString(annotDictionary, 'NM', value); + break; + case 'icon': + if (!isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.name, value); + } + break; + case 'subject': + _addString( + annotDictionary, + PdfDictionaryProperties.subj, + _getFormattedString(value), + ); + break; + case 'title': + _addString( + annotDictionary, + PdfDictionaryProperties.t, + _getFormattedString(value), + ); + break; + case 'rotation': + _addInt(annotDictionary, PdfDictionaryProperties.rotate, value); + break; + case 'justification': + _addInt(annotDictionary, PdfDictionaryProperties.q, value); + break; + case 'fringe': + _addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.rd, + ); + break; + case 'it': + if (!isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.it, value); + } + break; + case 'leaderlength': + _addFloat(annotDictionary, PdfDictionaryProperties.ll, value); + break; + case 'leaderextend': + if (!isNullOrEmpty(value)) { + final double? leaderExtend = double.tryParse(value); + if (leaderExtend != null) { + annotDictionary.setNumber( + PdfDictionaryProperties.lle, + leaderExtend, + ); + } + } + break; + case 'caption': + if (!isNullOrEmpty(value)) { + annotDictionary.setBoolean( + PdfDictionaryProperties.cap, + value.toLowerCase() == 'yes', + ); + } + break; + case 'caption-style': + if (!isNullOrEmpty(value)) { + annotDictionary.setName(PdfDictionaryProperties.cp, value); + } + break; + case 'callout': + _addFloatPoints(annotDictionary, _obtainFloatPoints(value), 'CL'); + break; + case 'coords': + _addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.quadPoints, + ); + break; + case 'border': + _addFloatPoints( + annotDictionary, + _obtainFloatPoints(value), + PdfDictionaryProperties.border, + ); + break; + case 'opacity': + if (!isNullOrEmpty(value)) { + final double? opacity = double.tryParse(value); + if (opacity != null) { + annotDictionary.setNumber(PdfDictionaryProperties.ca, opacity); + } + } + break; + case 'flags': + if (!isNullOrEmpty(value)) { + final List annotationFlags = + []; + final List flags = value.split(','); + for (int i = 0; i < flags.length; i++) { + final PdfAnnotationFlags flagType = mapAnnotationFlags(flags[i]); + if (!annotationFlags.contains(flagType)) { + annotationFlags.add(flagType); + } + } + int flagValue = 0; + for (int i = 0; i < annotationFlags.length; i++) { + flagValue |= PdfAnnotationHelper.getAnnotationFlagsValue( + annotationFlags[i], + ); + } + if (flagValue > 0) { + annotDictionary.setNumber(PdfDictionaryProperties.f, flagValue); + } + } + break; + case 'open': + if (!isNullOrEmpty(value)) { + annotDictionary.setBoolean( + PdfDictionaryProperties.open, + value == 'true' || value == 'yes', + ); + } + break; + case 'calibrate': + _addString(annotDictionary, 'Calibrate', value); + break; + case 'customdata': + _addString(annotDictionary, 'CustomData', value); + break; + case 'overlaytext': + _addString(annotDictionary, 'OverlayText', value); + break; + case 'repeat': + annotDictionary.setBoolean( + 'Repeat', + value == 'true' || value == 'yes', + ); + break; + default: + break; + } + } + } + + void _parseInnerElements( + PdfDictionary annotDictionary, + XmlElement element, + int pageIndex, + ) { + if (element.attributes.isNotEmpty) { + for (final XmlNode childNode in element.children) { + if (childNode is XmlElement) { + final XmlName childName = childNode.name; + switch (childName.local.toLowerCase()) { + case 'popup': + if (childNode.attributes.isNotEmpty) { + final PdfDictionary popupDictionary = _getAnnotation( + childNode, + pageIndex, + ); + if (popupDictionary.count > 0) { + final PdfReferenceHolder holder = PdfReferenceHolder( + popupDictionary, + ); + annotDictionary.setProperty( + PdfDictionaryProperties.popup, + holder, + ); + if (popupDictionary.containsKey('NM')) { + _addReferenceToGroup(holder, popupDictionary); + } + } + } + break; + case 'contents': + String? contents = childNode.innerText; + if (!isNullOrEmpty(contents)) { + contents = contents.replaceAll('<', '<'); + contents = contents.replaceAll('>', '>'); + annotDictionary.setString( + PdfDictionaryProperties.contents, + contents, + ); + } + break; + case 'contents-richtext': + final XmlNode? richText = childNode.firstChild; + if (richText != null) { + final String richTextContents = richText.innerText; + final String contentText = childNode.innerText; + if (!isNullOrEmpty(richTextContents) && + !isNullOrEmpty(contentText) && + !annotDictionary.containsKey( + PdfDictionaryProperties.contents, + )) { + annotDictionary.setString( + 'RC', + '$richTextContents', + ); + annotDictionary.setString( + PdfDictionaryProperties.contents, + contentText, + ); + } else if (!isNullOrEmpty(richTextContents)) { + annotDictionary.setString( + 'RC', + '$richTextContents', + ); + } + } + break; + case 'defaultstyle': + _addString(annotDictionary, 'DS', childNode.innerText); + break; + case 'defaultappearance': + _addString( + annotDictionary, + PdfDictionaryProperties.da, + childNode.innerText, + ); + break; + case 'vertices': + if (!isNullOrEmpty(childNode.innerText)) { + final String verticesValue = childNode.innerText.replaceAll( + ';', + ',', + ); + if (verticesValue != '') { + final List verticesList = []; + _addLinePoints(verticesList, verticesValue); + if (verticesList.isNotEmpty && verticesList.length.isEven) { + annotDictionary.setProperty( + PdfDictionaryProperties.vertices, + PdfArray(verticesList), + ); + } + } + } + break; + case 'appearance': + if (!isNullOrEmpty(childNode.innerText)) { + final List appearanceArray = base64.decode( + childNode.innerText, + ); + if (appearanceArray.isNotEmpty) { + final XmlDocument appearanceDoc = XmlDocument.parse( + utf8.decode(appearanceArray), + ); + final List childNodes = appearanceDoc.children; + for (final XmlNode rootElement in childNodes) { + if (rootElement is XmlElement) { + if (rootElement.childElements.isNotEmpty) { + final XmlName rootName = rootElement.name; + if (rootName.local == XfdfProperties.dict) { + final String? rootAttribute = rootElement + .getAttribute(XfdfProperties.key); + if (rootAttribute != null && + !isNullOrEmpty(rootAttribute)) { + if (rootAttribute == 'AP') { + final PdfDictionary appearance = PdfDictionary(); + final List childs = rootElement.children; + for (final XmlNode child in childs) { + if (child is XmlElement) { + _getAppearance(appearance, child); + } + } + if (appearance.count > 0) { + annotDictionary.setProperty( + PdfDictionaryProperties.ap, + appearance, + ); + } + } + } + } + } + } + } + } + } + break; + case 'imagedata': + if (!isNullOrEmpty(childNode.innerText)) { + _addImageToAppearance(annotDictionary, childNode.innerText); + } + break; + case 'inklist': + if (childNode.children.isNotEmpty) { + for (final XmlNode child in childNode.children) { + if (child is XmlElement) { + final XmlName childNodeName = child.name; + if (childNodeName.local.toLowerCase() == 'gesture' && + !isNullOrEmpty(child.innerText)) { + final String pointsArrayValue = child.innerText + .replaceAll(';', ','); + if (pointsArrayValue != '') { + final PdfArray inkListCollection = PdfArray(); + final List pointsList = []; + _addLinePoints(pointsList, pointsArrayValue); + if (pointsList.isNotEmpty && pointsList.length.isEven) { + inkListCollection.add(PdfArray(pointsList)); + } + pointsList.clear(); + if (inkListCollection.count > 0) { + annotDictionary.setProperty( + 'InkList', + inkListCollection, + ); + } + } + } + } + } + } + break; + case 'data': + if (!isNullOrEmpty(childNode.innerText)) { + final List raw = List.from( + hex.decode(childNode.innerText), + ); + if (raw.isNotEmpty) { + if (annotDictionary.containsKey( + PdfDictionaryProperties.subtype, + )) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.subtype], + ); + if (subtype != null && subtype is PdfName) { + if (subtype.name == 'FileAttachment') { + final PdfDictionary fileDictionary = PdfDictionary(); + fileDictionary.setName( + PdfDictionaryProperties.type, + PdfDictionaryProperties.filespec, + ); + _addElementStrings( + fileDictionary, + element, + 'file', + PdfDictionaryProperties.f, + ); + _addElementStrings( + fileDictionary, + element, + 'file', + PdfDictionaryProperties.uf, + ); + final PdfStream fileStream = PdfStream(); + final PdfDictionary param = PdfDictionary(); + final String? sizeAttributes = element.getAttribute( + 'size', + ); + if (!isNullOrEmpty(sizeAttributes)) { + final int? size = int.tryParse(sizeAttributes!); + if (size != null) { + param.setNumber(PdfDictionaryProperties.size, size); + fileStream.setNumber('DL', size); + } + } + _addElementStrings( + param, + element, + 'modification', + PdfDictionaryProperties.modificationDate, + ); + _addElementStrings( + param, + element, + 'creation', + PdfDictionaryProperties.creationDate, + ); + fileStream.setProperty( + PdfDictionaryProperties.params, + param, + ); + _addElementStrings( + fileStream, + element, + 'mimetype', + PdfDictionaryProperties.subtype, + ); + fileStream.data = raw; + final PdfDictionary embeddedFile = PdfDictionary(); + embeddedFile.setProperty( + PdfDictionaryProperties.f, + PdfReferenceHolder(fileStream), + ); + fileDictionary.setProperty( + PdfDictionaryProperties.ef, + embeddedFile, + ); + annotDictionary.setProperty( + PdfDictionaryProperties.fs, + PdfReferenceHolder(fileDictionary), + ); + } else if (subtype.name == 'Sound') { + final PdfStream soundStream = PdfStream(); + soundStream.setName( + PdfDictionaryProperties.type, + 'Sound', + ); + _addNumber(soundStream, element, 'bits', 'B'); + _addNumber( + soundStream, + element, + 'rate', + PdfDictionaryProperties.r, + ); + _addNumber( + soundStream, + element, + 'channels', + PdfDictionaryProperties.c, + ); + String? attribute = element.getAttribute('encoding'); + if (!isNullOrEmpty(attribute)) { + soundStream.setName( + PdfDictionaryProperties.e, + attribute, + ); + } + soundStream.data = raw; + attribute = element.getAttribute('filter'); + if (!isNullOrEmpty(attribute)) { + soundStream.addFilter( + PdfDictionaryProperties.flateDecode, + ); + } + annotDictionary.setProperty( + 'Sound', + PdfReferenceHolder(soundStream), + ); + } + } + } + } + } + break; + } + } + } + } + } + + void _addElementStrings( + PdfDictionary dictionary, + XmlElement element, + String attributeName, + String key, + ) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(attributeName); + if (!isNullOrEmpty(attribute)) { + _addString(dictionary, key, attribute); + } + } + } + + void _addNumber( + PdfDictionary dictionary, + XmlElement element, + String attributeName, + String key, + ) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(attributeName); + if (!isNullOrEmpty(attribute)) { + _addInt(dictionary, key, attribute); + } + } + } + + void _addImageToAppearance(PdfDictionary annotDictionary, String value) { + final String convert = value + .replaceAll('data:image/png;base64,', '') + .replaceAll('data:image/jpg;base64,', '') + .replaceAll('data:image/bmp;base64,', ''); + final List appearanceArray = base64.decode(convert); + final PdfBitmap image = PdfBitmap(appearanceArray); + final IPdfPrimitive? array = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.rect], + ); + if (array != null && array is PdfArray) { + const double x = 0; + const double y = 0; + final double width = (array[2]! as PdfNumber).value!.toDouble(); + final double height = (array[3]! as PdfNumber).value!.toDouble(); + final Rect rect = Rect.fromLTWH(x, y, width, height); + final PdfTemplate template = PdfTemplate(width, height); + _setMatrix( + PdfTemplateHelper.getHelper(template).content, + annotDictionary, + ); + template.graphics!.drawImage(image, rect); + final PdfReferenceHolder refHolder = PdfReferenceHolder(template); + final PdfDictionary appearanceDictionary = PdfDictionary(); + appearanceDictionary.setProperty(PdfDictionaryProperties.n, refHolder); + annotDictionary.setProperty( + PdfDictionaryProperties.ap, + appearanceDictionary, + ); + } + } + + void _setMatrix(PdfDictionary template, PdfDictionary annotDictionary) { + final IPdfPrimitive? bbox = PdfCrossTable.dereference( + template[PdfDictionaryProperties.bBox], + ); + if (bbox != null && bbox is PdfArray) { + if (annotDictionary.containsKey(PdfDictionaryProperties.rotate)) { + final IPdfPrimitive? rotate = PdfCrossTable.dereference( + annotDictionary[PdfDictionaryProperties.rotate], + ); + if (rotate is PdfNumber && rotate.value != 0) { + int rotateAngle = rotate.value!.toInt(); + if (rotateAngle == 0) { + rotateAngle = rotateAngle * 90; + } + final PdfTransformationMatrix matrix = PdfTransformationMatrix(); + matrix.rotate(rotateAngle.toDouble()); + final PdfArray mMatrix = PdfArray(matrix.matrix.elements); + template[PdfDictionaryProperties.matrix] = mMatrix; + } + } else { + final List elements = [ + 1, + 0, + 0, + 1, + -(bbox[0]! as PdfNumber).value!, + -(bbox[1]! as PdfNumber).value!, + ]; + template[PdfDictionaryProperties.matrix] = PdfArray(elements); + } + } + } + + PdfDictionary _getAppearance(PdfDictionary appearance, XmlElement element) { + final XmlName elementName = element.name; + switch (elementName.local) { + case XfdfProperties.stream: + final PdfStream stream = _getStream(element); + final PdfReferenceHolder holder = PdfReferenceHolder(stream); + _addKey(holder, appearance, element); + break; + case XfdfProperties.dict: + final PdfDictionary dictionary = _getDictionary(element); + final PdfReferenceHolder holder = PdfReferenceHolder(dictionary); + _addKey(holder, appearance, element); + break; + case XfdfProperties.array: + final PdfArray array = _getArray(element); + _addKey(array, appearance, element); + break; + case XfdfProperties.fixed: + final PdfNumber? floatNumber = _getFixed(element); + _addKey(floatNumber, appearance, element); + break; + case XfdfProperties.int: + final PdfNumber? intNumber = _getInt(element); + _addKey(intNumber, appearance, element); + break; + case XfdfProperties.string: + final PdfString? mstring = _getString(element); + _addKey(mstring, appearance, element); + break; + case XfdfProperties.name: + final PdfName? name = _getName(element); + _addKey(name, appearance, element); + break; + case XfdfProperties.bool: + final PdfBoolean? boolean = _getBoolean(element); + _addKey(boolean, appearance, element); + break; + case XfdfProperties.data: + final List? data = _getData(element); + if (data != null && data.isNotEmpty) { + if (appearance is PdfStream) { + appearance.data = data; + if (!appearance.containsKey(PdfDictionaryProperties.type) && + !appearance.containsKey(PdfDictionaryProperties.subtype)) { + appearance.decompress(); + } + bool isImage = false; + if (appearance.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + appearance[PdfDictionaryProperties.subtype], + ); + if (subtype != null && + subtype is PdfName && + subtype.name == PdfDictionaryProperties.image) { + isImage = true; + } + } + if (isImage) { + appearance.compress = false; + } else { + if (appearance.containsKey(PdfDictionaryProperties.length)) { + appearance.remove(PdfDictionaryProperties.length); + } + if (appearance.containsKey(PdfDictionaryProperties.filter)) { + appearance.remove(PdfDictionaryProperties.filter); + } + } + } + } + break; + } + + return appearance; + } + + void _addMeasureDictionary( + PdfDictionary annotDictionary, + XmlElement element, + ) { + XmlElement? measurement; + XmlElement? area; + XmlElement? distance; + XmlElement? xformat; + for (final XmlNode childNode in element.children) { + if (childNode is XmlElement) { + final XmlName childName = childNode.name; + if (childName.local.toLowerCase() == 'measure') { + measurement = childNode; + break; + } + } + } + final PdfDictionary measureDictionary = PdfDictionary(); + final PdfArray dArray = PdfArray(); + final PdfArray aArray = PdfArray(); + final PdfArray xArray = PdfArray(); + final PdfDictionary dDict = PdfDictionary(); + final PdfDictionary aDict = PdfDictionary(); + final PdfDictionary xDict = PdfDictionary(); + measureDictionary.items![PdfName(PdfDictionaryProperties.a)] = aArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.d)] = dArray; + measureDictionary.items![PdfName(PdfDictionaryProperties.x)] = xArray; + if (measurement != null) { + measureDictionary.setName( + PdfDictionaryProperties.type, + PdfDictionaryProperties.measure, + ); + if (measurement.attributes.isNotEmpty) { + final String? attribute = measurement.getAttribute('rateValue'); + if (!isNullOrEmpty(attribute)) { + measureDictionary.setString(PdfDictionaryProperties.r, attribute); + } + } + for (final XmlNode childNode in measurement.children) { + if (childNode is XmlElement) { + final XmlName newElement = childNode.name; + if (newElement.local.toLowerCase() == 'area') { + area = childNode; + } + if (newElement.local.toLowerCase() == 'distance') { + distance = childNode; + } + if (newElement.local.toLowerCase() == 'xformat') { + xformat = childNode; + } + } + } + } + if (xformat != null) { + _addElements(xformat, xDict); + xArray.add(xDict); + } + if (area != null) { + _addElements(area, aDict); + aArray.add(aDict); + } + if (distance != null) { + _addElements(distance, dDict); + dArray.add(dDict); + } + if (measureDictionary.items!.isNotEmpty && + measureDictionary.containsKey(PdfDictionaryProperties.type)) { + annotDictionary.items![PdfName( + PdfDictionaryProperties.measure, + )] = PdfReferenceHolder(measureDictionary); + } + } + + void _addElements(XmlElement element, PdfDictionary dictionary) { + if (element.attributes.isNotEmpty) { + String? attribute = element.getAttribute('d'); + if (attribute != null) { + final double? d = double.tryParse(attribute); + if (d != null) { + dictionary.setProperty( + PdfName(PdfDictionaryProperties.d), + PdfNumber(d), + ); + } + } + attribute = element.getAttribute(PdfDictionaryProperties.c.toLowerCase()); + if (attribute != null) { + final double? c = double.tryParse(attribute); + if (c != null) { + dictionary.setProperty( + PdfName(PdfDictionaryProperties.c), + PdfNumber(c), + ); + } + } + attribute = element.getAttribute('rt'); + if (attribute != null) { + dictionary.items![PdfName('RT')] = PdfString(attribute); + } + attribute = element.getAttribute('rd'); + if (attribute != null) { + dictionary.items![PdfName(PdfDictionaryProperties.rd)] = PdfString( + attribute, + ); + } + attribute = element.getAttribute('ss'); + if (attribute != null) { + dictionary.items![PdfName('SS')] = PdfString(attribute); + } + attribute = element.getAttribute(PdfDictionaryProperties.u.toLowerCase()); + if (attribute != null) { + dictionary.items![PdfName(PdfDictionaryProperties.u)] = PdfString( + attribute, + ); + } + attribute = element.getAttribute(PdfDictionaryProperties.f.toLowerCase()); + if (attribute != null) { + dictionary.items![PdfName(PdfDictionaryProperties.f)] = PdfName( + attribute, + ); + } + attribute = element.getAttribute('fd'); + if (attribute != null) { + dictionary.items![PdfName('FD')] = PdfBoolean(attribute == 'yes'); + } + } + } + + PdfStream _getStream(XmlElement element) { + final PdfStream stream = PdfStream(); + if (element.children.isNotEmpty) { + for (final XmlNode child in element.children) { + if (child is XmlElement) { + _getAppearance(stream, child); + } + } + } + return stream; + } + + void _addKey( + IPdfPrimitive? primitive, + PdfDictionary dictionary, + XmlElement element, + ) { + if (primitive != null && element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.key); + if (!isNullOrEmpty(attribute)) { + dictionary.setProperty(attribute, primitive); + } + } + } + + PdfDictionary _getDictionary(XmlElement element) { + final PdfDictionary dictionary = PdfDictionary(); + if (element.children.isNotEmpty) { + for (final XmlNode child in element.children) { + if (child is XmlElement) { + _getAppearance(dictionary, child); + } + } + } + return dictionary; + } + + PdfArray _getArray(XmlElement element) { + final PdfArray array = PdfArray(); + if (element.children.isNotEmpty) { + for (final XmlNode child in element.children) { + if (child is XmlElement) { + _addArrayElements(array, child); + } + } + } + return array; + } + + PdfNumber? _getFixed(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + final double? value = double.tryParse(attribute!); + if (value != null) { + return PdfNumber(value); + } + } + } + return null; + } + + PdfNumber? _getInt(XmlElement element) { + // int value; + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + final int? value = int.tryParse(attribute!); + if (value != null) { + return PdfNumber(value); + } + } + } + return null; + } + + void _addArrayElements(PdfArray array, XmlElement element) { + final XmlName elementName = element.name; + switch (elementName.local) { + case XfdfProperties.stream: + final PdfStream stream = _getStream(element); + _addArrayElement(array, PdfReferenceHolder(stream)); + break; + case XfdfProperties.dict: + final PdfDictionary dictionary = _getDictionary(element); + _addArrayElement(array, PdfReferenceHolder(dictionary)); + break; + case XfdfProperties.array: + final PdfArray pdfArray = _getArray(element); + _addArrayElement(array, pdfArray); + break; + case XfdfProperties.fixed: + final PdfNumber? floatValue = _getFixed(element); + _addArrayElement(array, floatValue); + break; + case XfdfProperties.int: + final PdfNumber? intValue = _getInt(element); + _addArrayElement(array, intValue); + break; + case XfdfProperties.name: + final PdfName? name = _getName(element); + _addArrayElement(array, name); + break; + case XfdfProperties.bool: + final PdfBoolean? boolean = _getBoolean(element); + _addArrayElement(array, boolean); + break; + case XfdfProperties.string: + final PdfString? mstring = _getString(element); + _addArrayElement(array, mstring); + break; + } + } + + void _addArrayElement(PdfArray array, IPdfPrimitive? primitive) { + if (primitive != null) { + array.add(primitive); + } + } + + PdfName? _getName(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + return PdfName(attribute); + } + } + return null; + } + + PdfBoolean? _getBoolean(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + return PdfBoolean(attribute!.toLowerCase() == 'true'); + } + } + return null; + } + + PdfString? _getString(XmlElement element) { + if (element.attributes.isNotEmpty) { + final String? attribute = element.getAttribute(XfdfProperties.val); + if (!isNullOrEmpty(attribute)) { + return PdfString(attribute!); + } else if (element.name.toXmlString().contains(XfdfProperties.string)) { + final List? data = _getData(element); + if (data != null && data.isNotEmpty) { + return PdfString.fromBytes(data); + } + } + } + return null; + } + + List? _getData(XmlElement element) { + if (!isNullOrEmpty(element.innerText) && element.attributes.isNotEmpty) { + final String? mode = element.getAttribute(XfdfProperties.mode); + final String? encoding = element.getAttribute('ENCODING'); + if (!isNullOrEmpty(mode) && !isNullOrEmpty(encoding)) { + if (mode == XfdfProperties.filtered && + encoding == XfdfProperties.ascii) { + return [...utf8.encode(element.innerText)]; + } else if (mode == XfdfProperties.raw && + encoding == XfdfProperties.hex) { + return _hexToBytes(element.innerText); + } + } else if (!isNullOrEmpty(encoding) && encoding == XfdfProperties.hex) { + return _hexToBytes(element.innerText); + } + } + return null; + } + + List _hexToBytes(String hexString) { + final List bytes = []; + for (int i = 0; i < hexString.length; i += 2) { + final String hex = hexString.substring(i, i + 2); + bytes.add(int.parse(hex, radix: 16)); + } + return bytes; + } + + void _addReferenceToGroup( + PdfReferenceHolder holder, + PdfDictionary dictionary, + ) { + IPdfPrimitive? name = PdfCrossTable.dereference(dictionary['NM']); + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + _groupReferences ??= {}; + _groupReferences![name.value!] = holder; + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + _groupHolders ??= []; + _groupHolders!.add(dictionary); + } + } else if (name == null) { + if (dictionary.containsKey(PdfDictionaryProperties.irt)) { + name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.irt], + ); + } + if (name != null && name is PdfString && !isNullOrEmpty(name.value)) { + if (_groupReferences != null && + _groupReferences!.containsKey(name.value)) { + final PdfReferenceHolder referenceHolder = + _groupReferences![name.value]!; + dictionary[PdfDictionaryProperties.irt] = referenceHolder; + } + } + } + } + + String? _getFormattedString(String? value) { + if (!isNullOrEmpty(value)) { + value = value!.replaceAll('<', '<'); + value = value.replaceAll('>', '>'); + } + return value; + } + + void _addString(PdfDictionary dictionary, String key, String? value) { + if (!isNullOrEmpty(value)) { + dictionary.setString(key, value); + } + } + + void _addInt(PdfDictionary dictionary, String key, String? value) { + if (!isNullOrEmpty(value)) { + final double? number = double.tryParse(value!); + if (number != null) { + dictionary.setNumber(key, number.toInt()); + } + } + } + + void _addFloatPoints(PdfDictionary dictionary, List points, String key) { + if (points.isNotEmpty) { + dictionary.setProperty(key, PdfArray(points)); + } + } + + void _addFloat(PdfDictionary dictionary, String key, String value) { + if (!isNullOrEmpty(value)) { + final int? number = int.tryParse(value); + if (number != null) { + dictionary.setNumber(key, number); + } + } + } + + PdfArray? _getColorArray(String value) { + final String hex = value.replaceAll('#', ''); + final int r = int.parse(hex.substring(0, 2), radix: 16); + final int g = int.parse(hex.substring(2, 4), radix: 16); + final int b = int.parse(hex.substring(4, hex.length), radix: 16); + if (r >= 0 && g >= 0 && b >= 0) { + final PdfArray colorArray = PdfArray(); + colorArray.add(PdfNumber(r / 255)); + colorArray.add(PdfNumber(g / 255)); + colorArray.add(PdfNumber(b / 255)); + return colorArray; + } + return null; + } + + ///Internal method + static PdfAnnotationFlags mapAnnotationFlags(String flag) { + PdfAnnotationFlags type; + switch (flag.toLowerCase()) { + case 'hidden': + type = PdfAnnotationFlags.hidden; + break; + case 'invisible': + type = PdfAnnotationFlags.invisible; + break; + case 'locked': + type = PdfAnnotationFlags.locked; + break; + case 'norotate': + type = PdfAnnotationFlags.noRotate; + break; + case 'noview': + type = PdfAnnotationFlags.noView; + break; + case 'nozoom': + type = PdfAnnotationFlags.noZoom; + break; + case 'print': + type = PdfAnnotationFlags.print; + break; + case 'readonly': + type = PdfAnnotationFlags.readOnly; + break; + case 'togglenoview': + type = PdfAnnotationFlags.toggleNoView; + break; + default: + type = PdfAnnotationFlags.defaultFlag; + break; + } + return type; + } + + void _addLineEndStyle(XmlElement element, PdfDictionary annotDictionary) { + if (element.attributes.isNotEmpty) { + String beginLineStyle = ''; + final String? headAttribute = element.getAttribute('head'); + if (!isNullOrEmpty(headAttribute)) { + beginLineStyle = getEnumName(mapLineEndingStyle(headAttribute!)); + } + String endLineStyle = ''; + final String? tailAttribute = element.getAttribute('tail'); + if (!isNullOrEmpty(tailAttribute)) { + endLineStyle = getEnumName(mapLineEndingStyle(tailAttribute!)); + } + if (!isNullOrEmpty(beginLineStyle)) { + if (!isNullOrEmpty(endLineStyle)) { + final PdfArray lineEndingStyles = PdfArray(); + lineEndingStyles.add(PdfName(beginLineStyle)); + lineEndingStyles.add(PdfName(endLineStyle)); + annotDictionary.setProperty( + PdfDictionaryProperties.le, + lineEndingStyles, + ); + } else { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + } else if (!isNullOrEmpty(endLineStyle)) { + annotDictionary.setName(PdfDictionaryProperties.le, beginLineStyle); + } + } + } + + /// Internal Field. + static PdfLineEndingStyle mapLineEndingStyle(String style) { + PdfLineEndingStyle lineStyle; + switch (style.toLowerCase()) { + case 'butt': + lineStyle = PdfLineEndingStyle.butt; + break; + case 'circle': + lineStyle = PdfLineEndingStyle.circle; + break; + case 'closedarrow': + lineStyle = PdfLineEndingStyle.closedArrow; + break; + case 'diamond': + lineStyle = PdfLineEndingStyle.diamond; + break; + case 'openarrow': + lineStyle = PdfLineEndingStyle.openArrow; + break; + case 'rclosedarrow': + lineStyle = PdfLineEndingStyle.rClosedArrow; + break; + case 'ropenarrow': + lineStyle = PdfLineEndingStyle.rOpenArrow; + break; + case 'slash': + lineStyle = PdfLineEndingStyle.slash; + break; + case 'square': + lineStyle = PdfLineEndingStyle.square; + break; + default: + lineStyle = PdfLineEndingStyle.none; + break; + } + return lineStyle; + } +} 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 ba8103e82..e4322d613 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 @@ -13,8 +13,10 @@ class PdfICCColorProfile implements IPdfWrapper { /// Initializes a new instance of the [PdfICCColorProfile] class. PdfICCColorProfile() : super() { stream.compress = true; - stream.setProperty(PdfDictionaryProperties.filter, - PdfName(PdfDictionaryProperties.flateDecode)); + stream.setProperty( + PdfDictionaryProperties.filter, + PdfName(PdfDictionaryProperties.flateDecode), + ); stream.setProperty(PdfDictionaryProperties.n, PdfNumber(3)); stream.beginSave = _beginSaveStream; } @@ -30,7 +32,7 @@ class PdfICCColorProfile implements IPdfWrapper { //Handles the BeginSave event of the Stream control. void _beginSaveStream(Object sender, SavePdfPrimitiveArgs? args) { stream.clearStream(); - stream.dataStream = base64.decode(profileData).toList(); + stream.data = base64.decode(profileData).toList(); } /// internal property 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 9a2f0fad9..48c9f5b55 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 @@ -37,13 +37,13 @@ class CompressedStreamReader { DecompressorHuffmanTree? _currentDistanceTree; /// internal field - static const List def_huffman_dyntree_repeat_bits = [2, 3, 7]; + static const List defHuffmanDyntreeRepeatBits = [2, 3, 7]; /// internal field - static const List def_huffman_dyntree_repeat_minimums = [3, 3, 11]; + static const List defHuffmanDyntreeRepeatMinimums = [3, 3, 11]; /// internal field - static const List def_huffman_repeat_length_base = [ + static const List defHuffmanRepeatLengthBase = [ 3, 4, 5, @@ -72,11 +72,11 @@ class CompressedStreamReader { 163, 195, 227, - 258 + 258, ]; /// internal field - static const List def_huffman_repeat_length_extension = [ + static const List defHuffmanRepeatLengthExtension = [ 0, 0, 0, @@ -105,11 +105,11 @@ class CompressedStreamReader { 5, 5, 5, - 0 + 0, ]; /// internal field - static const List def_huffman_repeat_distanse_base = [ + static const List defHuffmanRepeatDistanseBase = [ 1, 2, 3, @@ -139,11 +139,11 @@ class CompressedStreamReader { 8193, 12289, 16385, - 24577 + 24577, ]; /// internal field - static const List def_huffman_repeat_distanse_extension = [ + static const List defHuffmanRepeatDistanseExtension = [ 0, 0, 0, @@ -173,20 +173,20 @@ class CompressedStreamReader { 12, 12, 13, - 13 + 13, ]; /// internal field - static const int def_huffman_repeat_max = 258; + static const int defHuffmanRepeatMax = 258; /// internal field - static const int def_huffman_end_block = 256; + static const int defHuffmanEndBlock = 256; /// internal field - static const int def_huffman_length_minimum_code = 257; + static const int defHuffmanLengthMinimumCode = 257; /// internal field - static const int def_huffman_length_maximum_code = 285; + static const int defHuffmanLengthMaximumCode = 285; /// internal field late int bufferedBits; @@ -218,7 +218,9 @@ class CompressedStreamReader { final int header = _readInt16(); if (header == -1) { throw ArgumentError.value( - header, 'Header of the stream can not be read.'); + header, + 'Header of the stream can not be read.', + ); } if (header % 31 != 0) { throw ArgumentError.value(header, 'Header checksum illegal'); @@ -229,11 +231,15 @@ class CompressedStreamReader { _windowSize = pow(2, ((header & _defHeaderInfoMask) >> 12) + 8) as int; if (_windowSize > _maxValue) { throw ArgumentError.value( - header, 'Unsupported window size for deflate compression method.'); + header, + 'Unsupported window size for deflate compression method.', + ); } if ((header & _defHeaderFlagsFdict) >> 5 == 1) { throw ArgumentError.value( - header, 'Custom dictionary is not supported at the moment.'); + header, + 'Custom dictionary is not supported at the moment.', + ); } } @@ -263,7 +269,9 @@ class CompressedStreamReader { if (length > _maxValue) { throw ArgumentError.value( - length, 'Uncompressed block length can not be more than 65535.'); + length, + 'Uncompressed block length can not be more than 65535.', + ); } _uncompressedDataLength = length; @@ -279,8 +287,10 @@ class CompressedStreamReader { case 2: _readingUncompressed = false; _uncompressedDataLength = -1; - final Map result = - _decodeDynamicHeader(_currentLengthTree, _currentDistanceTree); + final Map result = _decodeDynamicHeader( + _currentLengthTree, + _currentDistanceTree, + ); _currentLengthTree = result['lengthTree'] as DecompressorHuffmanTree; _currentDistanceTree = result['distanceTree'] as DecompressorHuffmanTree; @@ -291,8 +301,10 @@ class CompressedStreamReader { return true; } - Map _decodeDynamicHeader(DecompressorHuffmanTree? lengthTree, - DecompressorHuffmanTree? distanceTree) { + Map _decodeDynamicHeader( + DecompressorHuffmanTree? lengthTree, + DecompressorHuffmanTree? distanceTree, + ) { List arrDecoderCodeLengths; List arrResultingCodeLengths; @@ -309,8 +321,11 @@ class CompressedStreamReader { iDistancesCount += 1; final int iResultingCodeLengthsCount = iLengthsCount + iDistancesCount; - arrResultingCodeLengths = - List.filled(iResultingCodeLengthsCount, 0, growable: true); + arrResultingCodeLengths = List.filled( + iResultingCodeLengthsCount, + 0, + growable: true, + ); arrDecoderCodeLengths = List.filled(19, 0, growable: true); iCodeLengthsCount += 4; int iCurrentCode = 0; @@ -323,11 +338,13 @@ class CompressedStreamReader { } arrDecoderCodeLengths[CompressedStreamWriter - .def_huffman_dyntree_codelengths_order[iCurrentCode++]] = - len.toUnsigned(8); + .defHuffmanDyntreeCodeLengthsOrder[iCurrentCode++]] = len.toUnsigned( + 8, + ); } - final DecompressorHuffmanTree treeInternalDecoder = - DecompressorHuffmanTree(arrDecoderCodeLengths); + final DecompressorHuffmanTree treeInternalDecoder = DecompressorHuffmanTree( + arrDecoderCodeLengths, + ); iCurrentCode = 0; @@ -359,8 +376,8 @@ class CompressedStreamReader { throw ArgumentError.value(iCurrentCode, 'Wrong dynamic huffman codes.'); } - final int _iRepSymbol = symbol - 16; - final int bits = def_huffman_dyntree_repeat_bits[_iRepSymbol]; + final int iRepSymbol = symbol - 16; + final int bits = defHuffmanDyntreeRepeatBits[iRepSymbol]; int count = _readBits(bits); @@ -368,7 +385,7 @@ class CompressedStreamReader { throw ArgumentError.value(count, 'Wrong dynamic huffman codes.'); } - count += def_huffman_dyntree_repeat_minimums[_iRepSymbol]; + count += defHuffmanDyntreeRepeatMinimums[iRepSymbol]; if (iCurrentCode + count > iResultingCodeLengthsCount) { throw ArgumentError.value(iCurrentCode, 'Wrong dynamic huffman codes.'); @@ -383,21 +400,37 @@ class CompressedStreamReader { } } - final List tempLengthArray = - List.filled(iLengthsCount, 0, growable: true); + final List tempLengthArray = List.filled( + iLengthsCount, + 0, + growable: true, + ); List.copyRange( - tempLengthArray, 0, arrResultingCodeLengths, 0, iLengthsCount); + tempLengthArray, + 0, + arrResultingCodeLengths, + 0, + iLengthsCount, + ); lengthTree = DecompressorHuffmanTree(tempLengthArray); - final List tempDistanceArray = - List.filled(iDistancesCount, 0, growable: true); - List.copyRange(tempDistanceArray, 0, arrResultingCodeLengths, iLengthsCount, - iLengthsCount + iDistancesCount); + final List tempDistanceArray = List.filled( + iDistancesCount, + 0, + growable: true, + ); + List.copyRange( + tempDistanceArray, + 0, + arrResultingCodeLengths, + iLengthsCount, + iLengthsCount + iDistancesCount, + ); distanceTree = DecompressorHuffmanTree(tempDistanceArray); return { 'lengthTree': lengthTree, - 'distanceTree': distanceTree + 'distanceTree': distanceTree, }; } @@ -528,7 +561,9 @@ class CompressedStreamReader { Map read(List buffer, int offset, int length) { if (offset < 0 || offset > buffer.length - 1) { throw ArgumentError.value( - offset, 'Offset does not belong to specified buffer.'); + offset, + 'Offset does not belong to specified buffer.', + ); } if (length < 0 || length > buffer.length - offset) { throw ArgumentError.value(length, 'Length is illegal.'); @@ -537,13 +572,20 @@ class CompressedStreamReader { while (length > 0) { if (_currentPosition < _dataLength) { final int inBlockPosition = _currentPosition % _maxValue; - int dataToCopy = - min(_maxValue - inBlockPosition, _dataLength - _currentPosition); + int dataToCopy = min( + _maxValue - inBlockPosition, + _dataLength - _currentPosition, + ); dataToCopy = min(dataToCopy, length); // Copy data. - List.copyRange(buffer, offset, _blockBuffer!, inBlockPosition, - inBlockPosition + dataToCopy); + List.copyRange( + buffer, + offset, + _blockBuffer!, + inBlockPosition, + inBlockPosition + dataToCopy, + ); _currentPosition += dataToCopy; offset += dataToCopy; length -= dataToCopy; @@ -567,13 +609,20 @@ class CompressedStreamReader { } else { // Position of the data end in block buffer. final int inBlockPosition = _dataLength % _maxValue; - final int dataToRead = - min(_uncompressedDataLength, _maxValue - inBlockPosition); - final int dataRead = - _readPackedBytes(_blockBuffer!, inBlockPosition, dataToRead); + final int dataToRead = min( + _uncompressedDataLength, + _maxValue - inBlockPosition, + ); + final int dataRead = _readPackedBytes( + _blockBuffer!, + inBlockPosition, + dataToRead, + ); if (dataToRead != dataRead) { throw ArgumentError.value( - dataToRead, 'Not enough data in stream.'); + dataToRead, + 'Not enough data in stream.', + ); } _uncompressedDataLength -= dataRead; _dataLength += dataRead; @@ -605,19 +654,25 @@ class CompressedStreamReader { return { 'length': initialLength - length, - 'buffer': buffer + 'buffer': buffer, }; } void _checksumUpdate(List? buffer, int offset, int length) { _checkSum = CompressedStreamWriter.checksumUpdate( - _checkSum, buffer, offset, length); + _checkSum, + buffer, + offset, + length, + ); } int _readPackedBytes(List buffer, int offset, int length) { if (offset < 0 || offset > buffer.length - 1) { - throw ArgumentError.value(offset, - 'Offset cannot be less than zero or greater than buffer length - 1.'); + throw ArgumentError.value( + offset, + 'Offset cannot be less than zero or greater than buffer length - 1.', + ); } if (length < 0) { @@ -630,7 +685,9 @@ class CompressedStreamReader { if ((bufferedBits & 7) != 0) { throw ArgumentError.value( - buffer, 'Reading of unalligned data is not supported.'); + buffer, + 'Reading of unalligned data is not supported.', + ); } if (length == 0) { @@ -660,32 +717,32 @@ class CompressedStreamReader { int free = _maxValue - (_dataLength - _currentPosition); bool dataRead = false; int symbol = 0; - while (free >= def_huffman_repeat_max) { + while (free >= defHuffmanRepeatMax) { while (((symbol = _currentLengthTree!.unpackSymbol(this)) & ~0xff) == 0) { _blockBuffer![_dataLength++ % _maxValue] = symbol.toUnsigned(8); dataRead = true; - if (--free < def_huffman_repeat_max) { + if (--free < defHuffmanRepeatMax) { return true; } } - if (symbol < def_huffman_length_minimum_code) { - if (symbol < def_huffman_end_block) { + if (symbol < defHuffmanLengthMinimumCode) { + if (symbol < defHuffmanEndBlock) { throw ArgumentError.value(symbol, 'Illegal code.'); } _canReadMoreData = _decodeBlockHeader(); return dataRead | _canReadMoreData; } - if (symbol > def_huffman_length_maximum_code) { + if (symbol > defHuffmanLengthMaximumCode) { throw ArgumentError.value(symbol, 'Illegal repeat code length.'); } - int iRepeatLength = def_huffman_repeat_length_base[ - symbol - def_huffman_length_minimum_code]; + int iRepeatLength = + defHuffmanRepeatLengthBase[symbol - defHuffmanLengthMinimumCode]; - int iRepeatExtraBits = def_huffman_repeat_length_extension[ - symbol - def_huffman_length_minimum_code]; + int iRepeatExtraBits = + defHuffmanRepeatLengthExtension[symbol - defHuffmanLengthMinimumCode]; if (iRepeatExtraBits > 0) { final int extra = _readBits(iRepeatExtraBits); @@ -698,11 +755,11 @@ class CompressedStreamReader { // Unpack repeat distance. symbol = _currentDistanceTree!.unpackSymbol(this); - if (symbol < 0 || symbol > def_huffman_repeat_distanse_base.length) { + if (symbol < 0 || symbol > defHuffmanRepeatDistanseBase.length) { throw ArgumentError.value(symbol, 'Wrong distance code.'); } - int iRepeatDistance = def_huffman_repeat_distanse_base[symbol]; - iRepeatExtraBits = def_huffman_repeat_distanse_extension[symbol]; + int iRepeatDistance = defHuffmanRepeatDistanseBase[symbol]; + iRepeatExtraBits = defHuffmanRepeatDistanseExtension[symbol]; if (iRepeatExtraBits > 0) { final int extra = _readBits(iRepeatExtraBits); if (extra < 0) { 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 6e7d5acc6..0ea19e403 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 @@ -6,28 +6,44 @@ import 'compressor_huffman_tree.dart'; /// internal class class CompressedStreamWriter { /// internal constructor - CompressedStreamWriter(List outputStream, bool bNoWrap, - PdfCompressionLevel? level, bool bCloseStream) { + CompressedStreamWriter( + List outputStream, + bool bNoWrap, + PdfCompressionLevel? level, + bool bCloseStream, + ) { _treeLiteral = CompressorHuffmanTree( - this, def_huffman_literal_alphabet_length, 257, 15); + this, + defHuffmanLiteralAlphabetLength, + 257, + 15, + ); _treeDistances = CompressorHuffmanTree( - this, def_huffman_distances_alphabet_length, 1, 15); - _treeCodeLengths = - CompressorHuffmanTree(this, def_huffman_bitlen_tree_length, 4, 7); - _arrDistancesBuffer = List.filled(def_huffman_buffer_size, 0); - _arrLiteralsBuffer = List.filled(def_huffman_buffer_size, 0); + this, + defHuffmanDistancesAlphabetLength, + 1, + 15, + ); + _treeCodeLengths = CompressorHuffmanTree( + this, + defHuffmanBitlenTreeLength, + 4, + 7, + ); + _arrDistancesBuffer = List.filled(defHuffmanBufferSize, 0); + _arrLiteralsBuffer = List.filled(defHuffmanBufferSize, 0); _stream = outputStream; _level = level; _bNoWrap = bNoWrap; _bCloseStream = bCloseStream; _dataWindow = List.filled(2 * wsize, 0); - _hashHead = List.filled(hash_size, 0); + _hashHead = List.filled(hashSize, 0); _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 = goodLength[_getCompressionLevel(level)!]; + _niceLength = niceLength[_getCompressionLevel(level)!]; + _maximumChainLength = maxChain[_getCompressionLevel(level)!]; + _maximumLazySearch = maxLazy[_getCompressionLevel(level)!]; if (!bNoWrap) { _writeZLIBHeader(); } @@ -35,31 +51,31 @@ class CompressedStreamWriter { } /// Start template of the zlib header. - static const int def_zlib_header_template = (8 + (7 << 4)) << 8; + static const int defZlibHeaderTemplate = (8 + (7 << 4)) << 8; /// Memory usage level. - static const int default_mem_level = 8; + static const int defaultMemLevel = 8; /// Size of the pending buffer. - static const int def_pending_buffer_size = 1 << (default_mem_level + 8); + static const int defPendingBufferSize = 1 << (defaultMemLevel + 8); /// Size of the buffer for the huffman encoding. - static const int def_huffman_buffer_size = 1 << (default_mem_level + 6); + static const int defHuffmanBufferSize = 1 << (defaultMemLevel + 6); /// Length of the literal alphabet(literal+lengths). - static const int def_huffman_literal_alphabet_length = 286; + static const int defHuffmanLiteralAlphabetLength = 286; /// Distances alphabet length. - static const int def_huffman_distances_alphabet_length = 30; + static const int defHuffmanDistancesAlphabetLength = 30; /// Length of the code-lengths tree. - static const int def_huffman_bitlen_tree_length = 19; + static const int defHuffmanBitlenTreeLength = 19; /// Code of the symbol, than means the end of the block. - static const int def_huffman_endblock_symbol = 256; + static const int defHuffmanEndblockSymbol = 256; /// internal field - static const int too_far = 4096; + static const int tooFar = 4096; /// Maximum window size. static const int wsize = 1 << 15; @@ -68,31 +84,31 @@ class CompressedStreamWriter { static const int wmask = wsize - 1; /// Internal compression engine constant - static const int hash_bits = default_mem_level + 7; + static const int hashBits = defaultMemLevel + 7; /// Internal compression engine constant - static const int hash_size = 1 << hash_bits; + static const int hashSize = 1 << hashBits; /// Internal compression engine constant - static const int hash_mask = hash_size - 1; + static const int hashMask = hashSize - 1; /// Internal compression engine constant - static const int max_match = 258; + static const int maxMatch = 258; /// Internal compression engine constant - static const int min_match = 3; + static const int minMatch = 3; /// Internal compression engine constant - static const int hash_shift = 5; + static const int hashShift = 5; /// Internal compression engine constant - static const int min_lookahead = max_match + min_match + 1; + static const int minLookahead = maxMatch + minMatch + 1; /// Internal compression engine constant - static const int max_dist = wsize - min_lookahead; + static const int maxDist = wsize - minLookahead; /// Internal compression engine constant - static const List good_length = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32]; + static const List goodLength = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32]; /// internal field static const int checkSumBitOffset = 16; @@ -104,7 +120,7 @@ class CompressedStreamWriter { static const int checksumIterationCount = 3800; /// Internal compression engine constant - static const List nice_length = [ + static const List niceLength = [ 0, 8, 16, @@ -114,11 +130,11 @@ class CompressedStreamWriter { 128, 128, 258, - 258 + 258, ]; /// Internal compression engine constant - static const List max_chain = [ + static const List maxChain = [ 0, 4, 8, @@ -128,11 +144,11 @@ class CompressedStreamWriter { 128, 256, 1024, - 4096 + 4096, ]; /// internal field - static const List def_reverse_bits = [ + static const List defReverseBits = [ 0, 8, 4, @@ -148,11 +164,11 @@ class CompressedStreamWriter { 3, 11, 7, - 15 + 15, ]; /// internal field - static const List def_huffman_dyntree_codelengths_order = [ + static const List defHuffmanDyntreeCodeLengthsOrder = [ 16, 17, 18, @@ -171,13 +187,13 @@ class CompressedStreamWriter { 2, 14, 1, - 15 + 15, ]; /// internal field - static const List max_lazy = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258]; + static const List maxLazy = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258]; - final List _pendingBuffer = List.filled(def_pending_buffer_size, 0); + final List _pendingBuffer = List.filled(defPendingBufferSize, 0); int _pendingBufferLength = 0; late List _arrDistancesBuffer; late List _arrLiteralsBuffer; @@ -216,7 +232,7 @@ class CompressedStreamWriter { static late List _arrDistanceLengths; bool get _needsInput => _inputEnd == _inputOffset; bool get _pendingBufferIsFlushed => _pendingBufferLength == 0; - bool get _huffmanIsFull => _iBufferPosition >= def_huffman_buffer_size; + bool get _huffmanIsFull => _iBufferPosition >= defHuffmanBufferSize; int _maximumLazySearch = 0; int? _getCompressionLevel(PdfCompressionLevel? level) { @@ -233,6 +249,7 @@ class CompressedStreamWriter { return 7; case PdfCompressionLevel.best: return 9; + // ignore: no_default_cases default: return null; } @@ -241,10 +258,8 @@ class CompressedStreamWriter { /// internal method void initializeStaticLiterals() { if (_arrLiteralCodes == null) { - _arrLiteralCodes = - List.filled(def_huffman_literal_alphabet_length, 0); - _arrLiteralLengths = - List.filled(def_huffman_literal_alphabet_length, 0); + _arrLiteralCodes = List.filled(defHuffmanLiteralAlphabetLength, 0); + _arrLiteralLengths = List.filled(defHuffmanLiteralAlphabetLength, 0); int i = 0; while (i < 144) { _arrLiteralCodes![i] = bitReverse((0x030 + i) << 8); @@ -258,16 +273,20 @@ class CompressedStreamWriter { _arrLiteralCodes![i] = bitReverse(((0x000 - 256) + i) << 9); _arrLiteralLengths[i++] = 7; } - while (i < def_huffman_literal_alphabet_length) { + while (i < defHuffmanLiteralAlphabetLength) { _arrLiteralCodes![i] = bitReverse(((0x0c0 - 280) + i) << 8); _arrLiteralLengths[i++] = 8; } - _arrDistanceCodes = - List.filled(def_huffman_distances_alphabet_length, 0); - _arrDistanceLengths = - List.filled(def_huffman_distances_alphabet_length, 0); - - for (i = 0; i < def_huffman_distances_alphabet_length; i++) { + _arrDistanceCodes = List.filled( + defHuffmanDistancesAlphabetLength, + 0, + ); + _arrDistanceLengths = List.filled( + defHuffmanDistancesAlphabetLength, + 0, + ); + + for (i = 0; i < defHuffmanDistancesAlphabetLength; i++) { _arrDistanceCodes[i] = bitReverse(i << 11); _arrDistanceLengths[i] = 5; } @@ -276,7 +295,7 @@ class CompressedStreamWriter { void _writeZLIBHeader() { // Initialize header. - int iHeaderData = def_zlib_header_template; + int iHeaderData = defZlibHeaderTemplate; // Save compression level. iHeaderData |= ((_getCompressionLevel(_level)! >> 2) & 3) << 6; // Align header. @@ -338,6 +357,7 @@ class CompressedStreamWriter { case PdfCompressionLevel.belowNormal: success = _compressFast(canFlush, finish); break; + // ignore: no_default_cases default: success = _compressSlow(canFlush, finish); break; @@ -349,11 +369,11 @@ class CompressedStreamWriter { // Compress, using maximum compression level. bool _compressSlow(bool flush, bool finish) { - if (_lookAhead < min_lookahead && !flush) { + if (_lookAhead < minLookahead && !flush) { return false; } - while (_lookAhead >= min_lookahead || flush) { + while (_lookAhead >= minLookahead || flush) { if (_lookAhead == 0) { if (_matchPreviousAvailable) { _huffmanTallyLit(_dataWindow![_stringStart - 1] & 0xff); @@ -362,37 +382,41 @@ class CompressedStreamWriter { _matchPreviousAvailable = false; _huffmanFlushBlock( - _dataWindow, _blockStart, _stringStart - _blockStart, finish); + _dataWindow, + _blockStart, + _stringStart - _blockStart, + finish, + ); _blockStart = _stringStart; return false; } - if (_stringStart >= 2 * wsize - min_lookahead) { + if (_stringStart >= 2 * wsize - minLookahead) { _slideWindow(); } final int prevMatch = _matchStart; int prevLen = _matchLength; - if (_lookAhead >= min_match) { + if (_lookAhead >= minMatch) { final int hashHead = _insertString(); if (hashHead != 0 && - _stringStart - hashHead <= max_dist && + _stringStart - hashHead <= maxDist && _findLongestMatch(hashHead)) { /// Discard match if too small and too far away. if (_matchLength <= 5 && - (_matchLength == min_match) && - _stringStart - _matchStart > too_far) { - _matchLength = min_match - 1; + (_matchLength == minMatch) && + _stringStart - _matchStart > tooFar) { + _matchLength = minMatch - 1; } } } /// Previous match was better. - if (prevLen >= min_match && _matchLength <= prevLen) { + if (prevLen >= minMatch && _matchLength <= prevLen) { _huffmanTallyDist(_stringStart - 1 - prevMatch, prevLen); prevLen -= 2; @@ -400,7 +424,7 @@ class CompressedStreamWriter { _stringStart++; _lookAhead--; - if (_lookAhead >= min_match) { + if (_lookAhead >= minMatch) { _insertString(); } } while (--prevLen > 0); @@ -408,7 +432,7 @@ class CompressedStreamWriter { _stringStart++; _lookAhead--; _matchPreviousAvailable = false; - _matchLength = min_match - 1; + _matchLength = minMatch - 1; } else { if (_matchPreviousAvailable) { _huffmanTallyLit(_dataWindow![_stringStart - 1] & 0xff); @@ -439,34 +463,42 @@ class CompressedStreamWriter { // Compress with a maximum speed. bool _compressFast(bool flush, bool finish) { - if (_lookAhead < min_lookahead && !flush) { + if (_lookAhead < minLookahead && !flush) { return false; } - while (_lookAhead >= min_lookahead || flush) { + while (_lookAhead >= minLookahead || flush) { if (_lookAhead == 0) { _huffmanFlushBlock( - _dataWindow, _blockStart, _stringStart - _blockStart, finish); + _dataWindow, + _blockStart, + _stringStart - _blockStart, + finish, + ); _blockStart = _stringStart; return false; } - if (_stringStart > 2 * wsize - min_lookahead) { + if (_stringStart > 2 * wsize - minLookahead) { _slideWindow(); } int hashHead; - if (_lookAhead >= min_match && + if (_lookAhead >= minMatch && (hashHead = _insertString()) != 0 && - _stringStart - hashHead <= max_dist && + _stringStart - hashHead <= maxDist && _findLongestMatch(hashHead)) { if (_huffmanTallyDist(_stringStart - _matchStart, _matchLength)) { final bool lastBlock = finish && _lookAhead == 0; _huffmanFlushBlock( - _dataWindow, _blockStart, _stringStart - _blockStart, lastBlock); + _dataWindow, + _blockStart, + _stringStart - _blockStart, + lastBlock, + ); _blockStart = _stringStart; } _lookAhead -= _matchLength; - if (_matchLength <= _maximumLazySearch && _lookAhead >= min_match) { + if (_matchLength <= _maximumLazySearch && _lookAhead >= minMatch) { while (--_matchLength > 0) { ++_stringStart; _insertString(); @@ -476,11 +508,11 @@ class CompressedStreamWriter { } else { _stringStart += _matchLength; - if (_lookAhead >= min_match - 1) { + if (_lookAhead >= minMatch - 1) { _updateHash(); } } - _matchLength = min_match - 1; + _matchLength = minMatch - 1; continue; } else { @@ -491,7 +523,11 @@ class CompressedStreamWriter { if (_huffmanIsFull) { final bool lastBlock = finish && _lookAhead == 0; _huffmanFlushBlock( - _dataWindow, _blockStart, _stringStart - _blockStart, lastBlock); + _dataWindow, + _blockStart, + _stringStart - _blockStart, + lastBlock, + ); _blockStart = _stringStart; return !lastBlock; @@ -505,11 +541,11 @@ class CompressedStreamWriter { int scan = _stringStart; int match; int bestEnd = _stringStart + _matchLength; - int bestLen = max(_matchLength, min_match - 1); + int bestLen = max(_matchLength, minMatch - 1); - final int limit = max(_stringStart - max_dist, 0); + final int limit = max(_stringStart - maxDist, 0); - final int strend = _stringStart + max_match - 1; + final int strend = _stringStart + maxMatch - 1; int scanEnd1 = _dataWindow![bestEnd - 1]; int scanEnd = _dataWindow![bestEnd]; @@ -567,7 +603,7 @@ class CompressedStreamWriter { _matchLength = min(bestLen, _lookAhead); - return _matchLength >= min_match; + return _matchLength >= minMatch; } bool _huffmanTallyDist(int dist, int len) { @@ -589,8 +625,12 @@ class CompressedStreamWriter { } void _huffmanFlushBlock( - List? stored, int storedOffset, int storedLength, bool lastBlock) { - _treeLiteral.codeFrequences[def_huffman_endblock_symbol]++; + List? stored, + int storedOffset, + int storedLength, + bool lastBlock, + ) { + _treeLiteral.codeFrequences[defHuffmanEndblockSymbol]++; // Build trees. _treeLiteral.buildTree(); @@ -605,13 +645,13 @@ class CompressedStreamWriter { int blTreeCodes = 4; for (int i = 18; i > blTreeCodes; i--) { - if (_treeCodeLengths! - .codeLengths![def_huffman_dyntree_codelengths_order[i]] > + if (_treeCodeLengths!.codeLengths![defHuffmanDyntreeCodeLengthsOrder[i]] > 0) { blTreeCodes = i + 1; } } - int optLen = 14 + + int optLen = + 14 + blTreeCodes * 3 + _treeCodeLengths!.getEncodedLength() + _treeLiteral.getEncodedLength() + @@ -619,10 +659,10 @@ class CompressedStreamWriter { _iExtraBits; int staticLen = _iExtraBits; - for (int i = 0; i < def_huffman_literal_alphabet_length; i++) { + for (int i = 0; i < defHuffmanLiteralAlphabetLength; i++) { staticLen += _treeLiteral.codeFrequences[i] * _arrLiteralLengths[i]; } - for (int i = 0; i < def_huffman_distances_alphabet_length; i++) { + for (int i = 0; i < defHuffmanDistancesAlphabetLength; i++) { staticLen += _treeDistances.codeFrequences[i] * _arrDistanceLengths[i]; } if (optLen >= staticLen) { @@ -658,9 +698,9 @@ class CompressedStreamWriter { for (int rank = 0; rank < blTreeCodes; rank++) { pendingBufferWriteBits( - _treeCodeLengths! - .codeLengths![def_huffman_dyntree_codelengths_order[rank]], - 3); + _treeCodeLengths!.codeLengths![defHuffmanDyntreeCodeLengthsOrder[rank]], + 3, + ); } _treeLiteral.writeTree(_treeCodeLengths); @@ -669,9 +709,10 @@ class CompressedStreamWriter { int _insertString() { int match; - final int hash = ((_currentHash << hash_shift) ^ - _dataWindow![_stringStart + (min_match - 1)]) & - hash_mask; + final int hash = + ((_currentHash << hashShift) ^ + _dataWindow![_stringStart + (minMatch - 1)]) & + hashMask; _hashPrevious[_stringStart & wmask] = match = _hashHead[hash]; _hashHead[hash] = _stringStart.toSigned(16); @@ -705,7 +746,7 @@ class CompressedStreamWriter { } } - _treeLiteral.writeCodeToStream(def_huffman_endblock_symbol); + _treeLiteral.writeCodeToStream(defHuffmanEndblockSymbol); } int _huffmanDistanceCode(int distance) { @@ -734,7 +775,11 @@ class CompressedStreamWriter { } void _huffmanFlushStoredBlock( - List stored, int storedOffset, int storedLength, bool lastBlock) { + List stored, + int storedOffset, + int storedLength, + bool lastBlock, + ) { pendingBufferWriteBits((0 << 1) + (lastBlock ? 1 : 0), 3); _pendingBufferAlignToByte(); _pendingBufferWriteShort(storedLength); @@ -758,18 +803,23 @@ class CompressedStreamWriter { void _pendingBufferWriteByteBlock(List data, int offset, int length) { List.copyRange( - _pendingBuffer, _pendingBufferLength, data, offset, offset + length); + _pendingBuffer, + _pendingBufferLength, + data, + offset, + offset + length, + ); _pendingBufferLength += length; } void _pendingBufferAlignToByte() { if (_pendingBufferBitsInCache > 0) { - _pendingBuffer[_pendingBufferLength++] = - _pendingBufferBitsCache.toUnsigned(8); + _pendingBuffer[_pendingBufferLength++] = _pendingBufferBitsCache + .toUnsigned(8); if (_pendingBufferBitsInCache > 8) { - _pendingBuffer[_pendingBufferLength++] = - (_pendingBufferBitsCache >> 8).toUnsigned(8); + _pendingBuffer[_pendingBufferLength++] = (_pendingBufferBitsCache >> 8) + .toUnsigned(8); } } @@ -793,23 +843,28 @@ class CompressedStreamWriter { } void _fillWindow() { - if (_stringStart >= wsize + max_dist) { + if (_stringStart >= wsize + maxDist) { _slideWindow(); } - while (_lookAhead < min_lookahead && _inputOffset < _inputEnd) { + while (_lookAhead < minLookahead && _inputOffset < _inputEnd) { int more = 2 * wsize - _lookAhead - _stringStart; if (more > _inputEnd - _inputOffset) { more = _inputEnd - _inputOffset; } - List.copyRange(_dataWindow!, _stringStart + _lookAhead, _inputBuffer!, - _inputOffset, _inputOffset + more); + List.copyRange( + _dataWindow!, + _stringStart + _lookAhead, + _inputBuffer!, + _inputOffset, + _inputOffset + more, + ); _inputOffset += more; _lookAhead += more; } - if (_lookAhead >= min_match) { + if (_lookAhead >= minMatch) { _updateHash(); } } @@ -820,7 +875,7 @@ class CompressedStreamWriter { _stringStart -= wsize; _blockStart -= wsize; - for (int i = 0; i < hash_size; ++i) { + for (int i = 0; i < hashSize; ++i) { final int m = _hashHead[i] & 0xffff; _hashHead[i] = ((m >= wsize) ? (m - wsize) : 0).toSigned(16); } @@ -832,7 +887,8 @@ class CompressedStreamWriter { } void _updateHash() { - _currentHash = (_dataWindow![_stringStart] << hash_shift) ^ + _currentHash = + (_dataWindow![_stringStart] << hashShift) ^ _dataWindow![_stringStart + 1]; } @@ -850,9 +906,9 @@ class CompressedStreamWriter { int _pendingBufferFlushBits() { int result = 0; while (_pendingBufferBitsInCache >= 8 && - _pendingBufferLength < def_pending_buffer_size) { - _pendingBuffer[_pendingBufferLength++] = - _pendingBufferBitsCache.toUnsigned(8); + _pendingBufferLength < defPendingBufferSize) { + _pendingBuffer[_pendingBufferLength++] = _pendingBufferBitsCache + .toUnsigned(8); _pendingBufferBitsCache >>= 8; _pendingBufferBitsInCache -= 8; result++; @@ -890,16 +946,20 @@ class CompressedStreamWriter { /// internal field static int bitReverse(int value) { - return (def_reverse_bits[(value & 15)] << 12 | - def_reverse_bits[((value >> 4) & 15)] << 8 | - def_reverse_bits[((value >> 8) & 15)] << 4 | - def_reverse_bits[(value >> 12)]) + return (defReverseBits[(value & 15)] << 12 | + defReverseBits[((value >> 4) & 15)] << 8 | + defReverseBits[((value >> 8) & 15)] << 4 | + defReverseBits[(value >> 12)]) .toSigned(16); } /// internal method 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; 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 a9832e26a..00ef61169 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 @@ -5,8 +5,12 @@ import 'compressed_stream_writer.dart'; /// Represents the Huffman Tree. class CompressorHuffmanTree { /// Create a new Huffman tree. - CompressorHuffmanTree(CompressedStreamWriter writer, int iElementsCount, - int iMinimumCodes, int iMaximumLength) { + CompressorHuffmanTree( + CompressedStreamWriter writer, + int iElementsCount, + int iMinimumCodes, + int iMaximumLength, + ) { _writer = writer; _codeMinimumCount = iMinimumCodes; _maximumLength = iMaximumLength; @@ -15,7 +19,7 @@ class CompressorHuffmanTree { } /// internal field - static const List def_reverse_bits = [ + static const List defReverseBits = [ 0, 8, 4, @@ -31,7 +35,7 @@ class CompressorHuffmanTree { 3, 11, 7, - 15 + 15, ]; List? _codes; late List _lengthCounts; @@ -62,8 +66,8 @@ class CompressorHuffmanTree { int pos = iTreeLength++; int ppos; - while ( - pos > 0 && codeFrequences[arrTree[ppos = (pos - 1) ~/ 2]] > freq) { + while (pos > 0 && + codeFrequences[arrTree[ppos = (pos - 1) ~/ 2]] > freq) { arrTree[pos] = arrTree[ppos]; pos = ppos; } @@ -364,10 +368,10 @@ class CompressorHuffmanTree { } int _bitReverse(int value) { - return (def_reverse_bits[value & 15] << 12 | - def_reverse_bits[(value >> 4) & 15] << 8 | - def_reverse_bits[(value >> 8) & 15] << 4 | - def_reverse_bits[value >> 12]) + return (defReverseBits[value & 15] << 12 | + defReverseBits[(value >> 4) & 15] << 8 | + defReverseBits[(value >> 8) & 15] << 4 | + defReverseBits[value >> 12]) .toSigned(16); } 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 9f1db50e7..66762afea 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 @@ -60,26 +60,42 @@ class DecompressorHuffmanTree { _distanceTree = DecompressorHuffmanTree(lengths); } catch (e) { throw ArgumentError.value( - e, 'DecompressorHuffmanTree: fixed trees generation failed'); + e, + 'DecompressorHuffmanTree: fixed trees generation failed', + ); } } void _buildTree(List lengths) { - final List blCount = - List.filled(_maxBitLength + 1, 0, growable: true); - final List nextCode = - List.filled(_maxBitLength + 1, 0, growable: true); + final List blCount = List.filled( + _maxBitLength + 1, + 0, + growable: true, + ); + final List nextCode = List.filled( + _maxBitLength + 1, + 0, + growable: true, + ); int? treeSize; int? code = 0; - final Map result = - _prepareData(blCount, nextCode, lengths, treeSize); + final Map result = _prepareData( + blCount, + nextCode, + lengths, + treeSize, + ); treeSize = result['treeSize'] as int?; code = result['code'] as int; _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++) { @@ -101,8 +117,13 @@ class DecompressorHuffmanTree { return {'treeSize': treeSize, 'code': code}; } - List _treeFromData(List blCount, List nextCode, - List lengths, int? code, int treeSize) { + List _treeFromData( + List blCount, + List nextCode, + List lengths, + int? code, + int treeSize, + ) { final List tree = List.filled(treeSize, 0, growable: true); int pointer = 512; const int increment = 1 << 7; @@ -111,8 +132,8 @@ class DecompressorHuffmanTree { code -= blCount[bits] << (16 - bits); final int start = code & 0x1ff80; for (int i = start; i < end; i += increment) { - tree[CompressedStreamWriter.bitReverse(i)] = - ((-pointer << 4) | bits).toSigned(16); + tree[CompressedStreamWriter.bitReverse(i)] = ((-pointer << 4) | bits) + .toSigned(16); pointer += 1 << (bits - 9); } } 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 c139fda76..d7b4a9070 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 @@ -41,7 +41,12 @@ class DecompressedOutput { if (copyStart <= border && _end < border) { if (length <= distance) { List.copyRange( - _dOutput!, _end, _dOutput!, copyStart, copyStart + length); + _dOutput!, + _end, + _dOutput!, + copyStart, + copyStart + length, + ); _end += length; } else { while (length-- > 0) { @@ -88,16 +93,23 @@ class DecompressedOutput { final int tailLen = length - end; int sourceStart = _dOutSize - tailLen; if (tailLen > 0) { - for (int i = 0; - i < tailLen && - i + sourceStart < _dOutput!.length && - i + offset < output.length; - i++) { + for ( + int i = 0; + i < tailLen && + i + sourceStart < _dOutput!.length && + i + offset < output.length; + i++ + ) { output[offset + i] = _dOutput![sourceStart + i]; } final int sourceStartIndex = _dOutSize - tailLen; - List.copyRange(output, offset, _dOutput!, sourceStartIndex, - sourceStartIndex + tailLen); + List.copyRange( + output, + offset, + _dOutput!, + sourceStartIndex, + sourceStartIndex + tailLen, + ); offset += tailLen; length = end; } 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 4d6565f09..55f58bf4a 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 @@ -14,7 +14,7 @@ class DeflateStream { //Fields late List _data; // ignore: unused_field - bool? _leaveOpen; + late bool? _leaveOpen; late int _offset; List? _buffer; late Inflater _inflater; @@ -25,8 +25,11 @@ class DeflateStream { int cOffset = offset; int rCount = count; while (true) { - final Map inflateResult = - _inflater.inflate(array!, cOffset, rCount); + final Map inflateResult = _inflater.inflate( + array!, + cOffset, + rCount, + ); length = inflateResult['count'] as int?; array = inflateResult['data'] as List; cOffset += length!; 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 d7b273e5a..1e31f91e7 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 @@ -33,8 +33,8 @@ class HuffmanTree { // Fields late int _tBits; late List _table; - List? _left; - List? _right; + late List? _left; + late List? _right; late List _clArray; late int _tMask; 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 85da61953..1556447df 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 @@ -94,11 +94,11 @@ class InBuffer { if (length > avail) { length = avail; } - for (int i = 0; - i < length && - i + _begin < _buffer!.length && - i + offset < output!.length; - i++) { + for ( + int i = 0; + i < length && i + _begin < _buffer!.length && i + offset < output!.length; + i++ + ) { output[offset + i] = _buffer![_begin + i]; } _begin += 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 bce99f4b9..db8549628 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 @@ -20,8 +20,11 @@ class Inflater { _output = DecompressedOutput(); _input = InBuffer(); _loopCounter = 0; - _codeList = List.filled(HuffmanTree.maxLTree + HuffmanTree.maxDTree, 0, - growable: true); + _codeList = List.filled( + HuffmanTree.maxLTree + HuffmanTree.maxDTree, + 0, + growable: true, + ); _cltcl = List.filled(HuffmanTree.nCLength, 0, growable: true); _inflaterstate = InflaterState.readingBFinal; } @@ -147,7 +150,7 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } break; @@ -159,7 +162,7 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } break; @@ -172,21 +175,22 @@ class Inflater { return { 'result': true, 'eob': endblock, - 'output': _output + 'output': _output, }; } if (_output.unusedBytes == 0) { return { 'result': true, 'eob': endblock, - 'output': _output + 'output': _output, }; } return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; + // ignore: no_default_cases default: break; } @@ -199,8 +203,8 @@ class Inflater { return false; } _blBuffer[_getInflaterStateValue(_inflaterstate) - - _getInflaterStateValue(InflaterState.unCompressedByte1)] = - bits.toUnsigned(8); + _getInflaterStateValue(InflaterState.unCompressedByte1)] = bits + .toUnsigned(8); if (_inflaterstate == InflaterState.unCompressedByte4) { _bLength = _blBuffer[0] + (_blBuffer[1]) * 256; if (_bLength.toUnsigned(16) != @@ -208,8 +212,9 @@ class Inflater { throw ArgumentError.value('Ivalid block length.'); } } - _inflaterstate = - _getInflaterState(_getInflaterStateValue(_inflaterstate) + 1); + _inflaterstate = _getInflaterState( + _getInflaterStateValue(_inflaterstate) + 1, + ); return true; } @@ -225,7 +230,7 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } if (symbol < 256) { @@ -237,7 +242,7 @@ class Inflater { return { 'result': true, 'eob': endblock, - 'output': _output + 'output': _output, }; } else { symbol -= 257; @@ -260,7 +265,7 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } } @@ -272,7 +277,7 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } break; @@ -283,7 +288,7 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } break; @@ -294,10 +299,11 @@ class Inflater { return { 'result': false, 'eob': endblock, - 'output': _output + 'output': _output, }; } break; + // ignore: no_default_cases default: break; } @@ -305,7 +311,7 @@ class Inflater { return { 'result': true, 'eob': endblock, - 'output': _output + 'output': _output, }; } @@ -403,17 +409,27 @@ class Inflater { return false; } break; + // ignore: no_default_cases default: break; } - final List literalTreeCodeLength = - List.filled(HuffmanTree.maxLTree, 0); + final List literalTreeCodeLength = List.filled( + HuffmanTree.maxLTree, + 0, + ); List.copyRange(literalTreeCodeLength, 0, _codeList, 0, _llCodeCount); - final List distanceTreeCodeLength = - List.filled(HuffmanTree.maxDTree, 0); - List.copyRange(distanceTreeCodeLength, 0, _codeList, _llCodeCount, - _llCodeCount + _dCodeCount); + final List distanceTreeCodeLength = List.filled( + HuffmanTree.maxDTree, + 0, + ); + List.copyRange( + distanceTreeCodeLength, + 0, + _codeList, + _llCodeCount, + _llCodeCount + _dCodeCount, + ); _llTree = HuffmanTree(code: literalTreeCodeLength); _distanceTree = HuffmanTree(code: distanceTreeCodeLength); _inflaterstate = InflaterState.decodeTop; @@ -489,7 +505,7 @@ class Inflater { if (_loopCounter == 0) { throw ArgumentError.value('Invalid data.'); } - final int previousCode = (_codeList[_loopCounter - 1]).toUnsigned(8); + final int previousCode = _codeList[_loopCounter - 1].toUnsigned(8); repeatCount = _input.getBits(2) + 3; if (_loopCounter + repeatCount > _caSize) { throw ArgumentError.value('Invalid data.'); @@ -631,6 +647,7 @@ class Inflater { return 23; case InflaterState.done: return 24; + // ignore: no_default_cases default: return 0; } @@ -666,7 +683,7 @@ class Inflater { 163, 195, 227, - 258 + 258, ]; static const List _distanceBasePosition = [ 1, @@ -700,7 +717,7 @@ class Inflater { 16385, 24577, 0, - 0 + 0, ]; static const List _codeOrder = [ 16, @@ -721,7 +738,7 @@ class Inflater { 2, 14, 1, - 15 + 15, ]; static const List _staticDistanceTreeTable = [ 0x00, @@ -755,7 +772,7 @@ class Inflater { 0x07, 0x17, 0x0f, - 0x1f + 0x1f, ]; static const List _extraLengthBits = [ 0, @@ -786,6 +803,6 @@ class Inflater { 5, 5, 5, - 0 + 0, ]; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/enums.dart index 05a6e2f00..5ffc60e11 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/enums.dart @@ -67,7 +67,7 @@ enum InflaterState { vFooter, /// internal enumerator - done + done, } /// internal enumerator @@ -79,5 +79,5 @@ enum BlockType { staticType, /// internal enumerator - dynamicType + dynamicType, } 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 4550cc1c4..e1a5ebe4b 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 @@ -16,8 +16,10 @@ class PdfPngFilter { /// internal method List decompress(List data, int bytesPerRow) { if (bytesPerRow <= 0) { - throw ArgumentError.value(bytesPerRow, - 'There cannot be less or equal to zero bytes in a line.'); + throw ArgumentError.value( + bytesPerRow, + 'There cannot be less or equal to zero bytes in a line.', + ); } return _modify(data, bytesPerRow + 1, _decompressFilter, false); } @@ -40,38 +42,78 @@ class PdfPngFilter { return result; } - List _decompressData(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR) { + List _decompressData( + List data, + int inIndex, + int inBPR, + List result, + int resIndex, + int resBPR, + ) { final _Type type = _getType(data[inIndex]); switch (type) { case _Type.none: - result = - _decompressNone(data, inIndex + 1, inBPR, result, resIndex, resBPR); + result = _decompressNone( + data, + inIndex + 1, + inBPR, + result, + resIndex, + resBPR, + ); break; case _Type.sub: - result = - _deompressSub(data, inIndex + 1, inBPR, result, resIndex, resBPR); + result = _deompressSub( + data, + inIndex + 1, + inBPR, + result, + resIndex, + resBPR, + ); break; case _Type.up: - result = - _decompressUp(data, inIndex + 1, inBPR, result, resIndex, resBPR); + result = _decompressUp( + data, + inIndex + 1, + inBPR, + result, + resIndex, + resBPR, + ); break; case _Type.average: result = _decompressAverage( - data, inIndex + 1, inBPR, result, resIndex, resBPR); + data, + inIndex + 1, + inBPR, + result, + resIndex, + resBPR, + ); break; case _Type.paeth: result = _decompressPaeth( - data, inIndex + 1, inBPR, result, resIndex, resBPR); + data, + inIndex + 1, + inBPR, + result, + resIndex, + resBPR, + ); break; - default: - throw ArgumentError.value(type, 'Unsupported PNG filter'); } return result; } - List _decompressNone(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR) { + List _decompressNone( + List data, + int inIndex, + int inBPR, + List result, + int resIndex, + int resBPR, + ) { for (int i = 1; i < inBPR; ++i) { result[resIndex] = data[inIndex]; ++resIndex; @@ -80,24 +122,36 @@ class PdfPngFilter { return result; } - List _deompressSub(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR) { + List _deompressSub( + List data, + int inIndex, + int inBPR, + 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); + result[resIndex] = (data[inIndex] + ((i > 0) ? result[resIndex - 1] : 0)) + .toUnsigned(8); ++resIndex; ++inIndex; } return result; } - List _decompressUp(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR) { + List _decompressUp( + List data, + int inIndex, + int inBPR, + List result, + int resIndex, + int resBPR, + ) { int prevIndex = resIndex - resBPR; for (int i = 0; i < resBPR; ++i) { - result[resIndex] = - (data[inIndex] + ((prevIndex < 0) ? 0 : result[prevIndex])) - .toUnsigned(8); + result[resIndex] = (data[inIndex] + + ((prevIndex < 0) ? 0 : result[prevIndex])) + .toUnsigned(8); ++resIndex; ++inIndex; ++prevIndex; @@ -105,8 +159,14 @@ class PdfPngFilter { return result; } - List _decompressAverage(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR) { + List _decompressAverage( + List data, + int inIndex, + int inBPR, + List result, + int resIndex, + int resBPR, + ) { int prevIndex = resIndex - resBPR; final List previous = List.filled(resBPR, 0, growable: true); for (int i = 0; i < resBPR; i++) { @@ -116,25 +176,26 @@ class PdfPngFilter { if (prevIndex < 0) { result[resIndex] = (data[inIndex] + previous[resIndex]).toUnsigned(8); } else { - result[resIndex] = - (data[inIndex] + (result[prevIndex] / 2)).toInt().toUnsigned(8); + result[resIndex] = (data[inIndex] + (result[prevIndex] / 2)) + .toInt() + .toUnsigned(8); } ++prevIndex; ++resIndex; } for (int i = bytesPerPixel; i < resBPR; i++) { if (prevIndex < 0) { - result[resIndex] = result[resIndex] + - (((result[resIndex - bytesPerPixel] & 0xff) + + result[resIndex] = (result[resIndex] + + (((result[resIndex - bytesPerPixel] & 0xff) + (previous[resIndex] & 0xff)) ~/ - 2) - .toUnsigned(8); + 2)) + .toUnsigned(8); } else { - result[resIndex] = result[resIndex] + - (((result[resIndex - bytesPerPixel] & 0xff) + + result[resIndex] = (result[resIndex] + + (((result[resIndex - bytesPerPixel] & 0xff) + (result[prevIndex] & 0xff)) ~/ - 2) - .toUnsigned(8); + 2)) + .toUnsigned(8); } ++resIndex; ++inIndex; @@ -143,14 +204,20 @@ class PdfPngFilter { return result; } - List _decompressPaeth(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR) { + List _decompressPaeth( + List data, + int inIndex, + int inBPR, + List result, + int resIndex, + int resBPR, + ) { int prevIndex = resIndex - resBPR; for (int i = 0; i < resBPR; i++) { result[resIndex + i] = data[inIndex + i]; } for (int i = 0; i < bytesPerPixel; i++) { - result[resIndex] = result[resIndex] + result[prevIndex]; + result[resIndex] = (result[resIndex] + result[prevIndex]).toUnsigned(8); resIndex++; prevIndex++; } @@ -158,7 +225,8 @@ class PdfPngFilter { final int a = result[resIndex - bytesPerPixel] & 0xff; final int b = result[prevIndex] & 0xff; final int c = result[prevIndex - bytesPerPixel] & 0xff; - result[resIndex] = result[resIndex] + _paethPredictor(a, b, c); + result[resIndex] = (result[resIndex] + _paethPredictor(a, b, c)) + .toUnsigned(8); ++resIndex; ++inIndex; ++prevIndex; @@ -200,8 +268,15 @@ class PdfPngFilter { } //Delegates -typedef _RowFilter = List Function(List data, int inIndex, int inBPR, - List result, int resIndex, int resBPR); +typedef _RowFilter = + List Function( + List data, + int inIndex, + int inBPR, + List result, + int resIndex, + int resBPR, + ); //Enum enum _Type { none, sub, up, average, paeth } 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 3bc27fdf5..ab2dc822c 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 @@ -40,8 +40,11 @@ class PdfZlibCompressor { int? numRead = 0; outputStream = []; do { - final Map result = - deflateStream.read(buffer, 0, buffer.length); + final Map result = deflateStream.read( + buffer, + 0, + buffer.length, + ); numRead = result['count'] as int?; buffer = result['data'] as List; for (int i = 0; i < numRead!; i++) { @@ -84,8 +87,11 @@ class PdfAscii85Compressor { switch (c) { case 'z': if (count != 0) { - throw ArgumentError.value(c, 'c', - 'The character "z" is invalid inside an ASCII85 block.'); + throw ArgumentError.value( + c, + 'c', + 'The character "z" is invalid inside an ASCII85 block.', + ); } _decodedBlock = List.filled(4, 0, growable: true); for (int i = 0; i < _decodedBlock.length; i++) { 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 4fa5778cb..8e8872b8e 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 @@ -560,8 +560,6 @@ class KnownColorTable { return 0xa6; case KnownColor.yellowGreen: return 0xa7; - default: - return 0x23; } } } @@ -1089,5 +1087,5 @@ enum KnownColor { yellow, /// internal enumerator - yellowGreen + yellowGreen, } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/enums.dart index 96dee65bc..ca9af76f0 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/enums.dart @@ -7,5 +7,5 @@ enum TextSearchOption { caseSensitive, /// Searches words with both the case sensitive and whole word. - both + both, } 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 000282298..2bfdd0086 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 @@ -1,6 +1,5 @@ -/// internal constructor // ignore_for_file: text_direction_code_point_in_literal - +/// internal constructor class AdobeGlyphList { // Constructor /// internal class 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 fb3ecdaca..87d753c28 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 @@ -7,6 +7,7 @@ import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/fonts/pdf_standard_font.dart'; import '../../io/decode_big_endian.dart'; import '../../io/pdf_constants.dart'; +import '../../io/pdf_cross_table.dart'; import '../../primitives/pdf_array.dart'; import '../../primitives/pdf_dictionary.dart'; import '../../primitives/pdf_name.dart'; @@ -38,8 +39,9 @@ class FontStructure { if (fontDictionary['CharProcs'] is PdfDictionary) { charProcs = fontDictionary['CharProcs'] as PdfDictionary?; } else { - charProcs = (fontDictionary['CharProcs']! as PdfReferenceHolder) - .object as PdfDictionary?; + charProcs = + (fontDictionary['CharProcs']! as PdfReferenceHolder).object + as PdfDictionary?; } final List names = charProcs!.items!.keys.toList(); int i = 0; @@ -61,7 +63,7 @@ class FontStructure { } } _fontStyle = [PdfFontStyle.regular]; - defaultGlyphWidth = 0; + defaultGlyphWidth = isCid ? 1000 : 0; _containsCmap = true; differenceEncoding = {}; } @@ -175,7 +177,13 @@ class FontStructure { late List cjkEncoding; late List _windows1252MapTable; -//Properties + /// internal field + bool isLayout = false; + + /// internal field + bool macRomanEncoded = false; + + //Properties /// internal property String? get fontEncoding => _fontEncoding ??= getFontEncoding(); @@ -253,10 +261,12 @@ class FontStructure { /// internal property Map? get fontGlyphWidths { - if (fontEncoding == 'Identity-H' || fontEncoding == 'Identity#2DH') { - _getGlyphWidths(); - } else { - _getGlyphWidthsNonIdH(); + if (_fontGlyphWidth == null) { + if (fontEncoding == 'Identity-H' || fontEncoding == 'Identity#2DH') { + _getGlyphWidths(); + } else { + _getGlyphWidthsNonIdH(); + } } return _fontGlyphWidth; } @@ -310,13 +320,13 @@ class FontStructure { 'HYSMyeongJo-Medium,BoldItalic', 'HYSMyeongJo-Medium,Bold', 'HYSMyeongJo-Medium,Italic', - 'HYSMyeongJo-Medium' + 'HYSMyeongJo-Medium', ]; cjkEncoding = [ 'UniKS-UCS2-H', 'UniJIS-UCS2-H', 'UniCNS-UCS2-H', - 'UniGB-UCS2-H' + 'UniGB-UCS2-H', ]; standardFontNames = [ 'Helvetica', @@ -343,7 +353,7 @@ class FontStructure { 'Times-Italic', 'Times-BoldItalic', 'Symbol', - 'ZapfDingbats' + 'ZapfDingbats', ]; _windows1252MapTable = [ '\u0000', @@ -600,7 +610,7 @@ class FontStructure { 'ü', 'ý', 'þ', - 'ÿ' + 'ÿ', ]; } @@ -639,8 +649,9 @@ class FontStructure { final IPdfPrimitive? primitiveObject = referenceholder.object; if (primitiveObject != null && primitiveObject is PdfDictionary) { final PdfDictionary dictionary = primitiveObject; - if (dictionary - .containsKey(PdfDictionaryProperties.fontDescriptor)) { + if (dictionary.containsKey( + PdfDictionaryProperties.fontDescriptor, + )) { IPdfPrimitive? fontDescriptor = dictionary[PdfDictionaryProperties.fontDescriptor]; PdfDictionary? descriptorDictionary; @@ -654,8 +665,9 @@ class FontStructure { descriptorDictionary = fontDescriptor; } if (descriptorDictionary != null && - descriptorDictionary - .containsKey(PdfDictionaryProperties.flags)) { + descriptorDictionary.containsKey( + PdfDictionaryProperties.flags, + )) { primitive = descriptorDictionary[PdfDictionaryProperties.flags]; if (primitive is PdfNumber) { @@ -779,14 +791,14 @@ class FontStructure { int firstChar = 0; final PdfDictionary dictionary = fontDictionary; if (dictionary.containsKey(PdfDictionaryProperties.dw)) { - defaultGlyphWidth = (dictionary[PdfDictionaryProperties.dw]! as PdfNumber) - .value! - .toDouble(); + defaultGlyphWidth = + (dictionary[PdfDictionaryProperties.dw]! as PdfNumber).value! + .toDouble(); } if (dictionary.containsKey(PdfDictionaryProperties.firstChar)) { - firstChar = (dictionary[PdfDictionaryProperties.firstChar]! as PdfNumber) - .value! - .toInt(); + firstChar = + (dictionary[PdfDictionaryProperties.firstChar]! as PdfNumber).value! + .toInt(); } _fontGlyphWidth = {}; PdfArray? w; @@ -853,8 +865,9 @@ class FontStructure { _fontGlyphWidth![entryValue] = (tempW[j]! as PdfNumber).value!.toInt(); } - } else if (differencesDictionary - .containsKey(index.toString())) { + } else if (differencesDictionary.containsKey( + index.toString(), + )) { if (!_fontGlyphWidth!.containsKey(index)) { _fontGlyphWidth![index] = (tempW[j]! as PdfNumber).value!.toInt(); @@ -900,8 +913,9 @@ class FontStructure { } if (baseFont != null) { _standardFontName = baseFont.name; - result &= - standardFontNames.contains(_resolveFontName(_standardFontName)); + result &= standardFontNames.contains( + _resolveFontName(_standardFontName), + ); } else { result = false; } @@ -941,7 +955,8 @@ class FontStructure { } } result &= encoding != null && cjkEncoding.contains(encoding.name); - result &= _standardCJKFontName != '' && + result &= + _standardCJKFontName != '' && standardCJKFontNames.contains(_standardCJKFontName); } else { result = false; @@ -972,13 +987,19 @@ class FontStructure { /// internal method PdfFont? createStandardFont(double size) { - if (_standardFontName != '') { + if (_standardFontName != null && _standardFontName != '') { + if (_standardFontName!.contains('#')) { + _standardFontName = decodeHexFontName(_standardFontName!); + } final PdfFontFamily fontFamily = _getFontFamily(_standardFontName!); final List styles = _getFontStyle(_standardFontName!); if (styles.contains(PdfFontStyle.bold) && styles.contains(PdfFontStyle.italic)) { - font = PdfStandardFont(fontFamily, size, - multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + font = PdfStandardFont( + fontFamily, + size, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic], + ); } else if (styles.contains(PdfFontStyle.bold)) { font = PdfStandardFont(fontFamily, size, style: PdfFontStyle.bold); } else if (styles.contains(PdfFontStyle.italic)) { @@ -993,13 +1014,17 @@ class FontStructure { /// internal method PdfFont? createStandardCJKFont(double size) { if (_standardCJKFontName != '') { - final PdfCjkFontFamily fontFamily = - _getCJKFontFamily(_standardCJKFontName!); + final PdfCjkFontFamily fontFamily = _getCJKFontFamily( + _standardCJKFontName!, + ); final List styles = _getCJKFontStyle(_standardCJKFontName!); if (styles.contains(PdfFontStyle.bold) && styles.contains(PdfFontStyle.italic)) { - font = PdfCjkStandardFont(fontFamily, size, - multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + font = PdfCjkStandardFont( + fontFamily, + size, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic], + ); } else if (styles.contains(PdfFontStyle.bold)) { font = PdfCjkStandardFont(fontFamily, size, style: PdfFontStyle.bold); } else if (styles.contains(PdfFontStyle.italic)) { @@ -1083,19 +1108,28 @@ class FontStructure { } else { PdfDictionary? baseFontDict = PdfDictionary(); if (fontDictionary[PdfDictionaryProperties.encoding] is PdfDictionary) { - baseFontDict = fontDictionary[PdfDictionaryProperties.encoding] - as PdfDictionary?; + baseFontDict = + fontDictionary[PdfDictionaryProperties.encoding] + as PdfDictionary?; if (baseFontDict == null) { - baseFont = (fontDictionary[PdfDictionaryProperties.encoding]! - as PdfReferenceHolder) - .object as PdfName?; + baseFont = + (fontDictionary[PdfDictionaryProperties.encoding]! + as PdfReferenceHolder) + .object + as PdfName?; fontEncoding = baseFont!.name; } } else if (fontDictionary[PdfDictionaryProperties.encoding] is PdfReferenceHolder) { - baseFontDict = (fontDictionary[PdfDictionaryProperties.encoding]! - as PdfReferenceHolder) - .object as PdfDictionary?; + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + fontDictionary[PdfDictionaryProperties.encoding], + ); + if (primitive != null && primitive is PdfName) { + baseFont = primitive; + fontEncoding = baseFont.name; + } else if (primitive != null && primitive is PdfDictionary) { + baseFontDict = primitive; + } } if (baseFontDict != null && baseFontDict.containsKey(PdfDictionaryProperties.type)) { @@ -1126,11 +1160,17 @@ class FontStructure { String? fontName = ''; isSystemFontExist = false; if (fontDictionary.containsKey(PdfDictionaryProperties.baseFont)) { - PdfName? baseFont = - fontDictionary[PdfDictionaryProperties.baseFont] as PdfName?; - baseFont ??= (fontDictionary[PdfDictionaryProperties.baseFont]! - as PdfReferenceHolder) - .object as PdfName?; + PdfName? baseFont; + if (fontDictionary[PdfDictionaryProperties.baseFont] is PdfName) { + baseFont = fontDictionary[PdfDictionaryProperties.baseFont] as PdfName?; + } else if (fontDictionary[PdfDictionaryProperties.baseFont] + is PdfReferenceHolder) { + baseFont = + (fontDictionary[PdfDictionaryProperties.baseFont]! + as PdfReferenceHolder) + .object + as PdfName?; + } String font = baseFont!.name!; if (font.contains('#20') && !font.contains('+')) { final int startIndex = font.lastIndexOf('#20'); @@ -1360,27 +1400,40 @@ class FontStructure { final int numberOfCharacters = tableValue.length ~/ 4; for (int j2 = 0; j2 < numberOfCharacters; j2++) { final String mapChar = String.fromCharCode( - int.parse(tableValue.substring(0, 4), radix: 16) - .toSigned(64)); + int.parse( + tableValue.substring(0, 4), + radix: 16, + ).toSigned(64), + ); tableValue = tableValue.substring(4); mapValue += mapChar; } mapValue = checkContainInvalidChar(mapValue); if (!mapTable.containsKey( - int.parse(tmpList[0], radix: 16).toSigned(64))) { - mapTable[int.parse(tmpList[0], radix: 16) - .toSigned(64) - .toDouble()] = mapValue; + int.parse(tmpList[0], radix: 16).toSigned(64), + )) { + mapTable[int.parse( + tmpList[0], + radix: 16, + ).toSigned(64).toDouble()] = + mapValue; } continue; } - if (!mapTable.containsKey( - int.parse(tmpList[0], radix: 16).toSigned(64))) { + if (tmpList.length > 1 && + tmpList[0] != '' && + tmpList[1] != '' && + !mapTable.containsKey( + int.parse(tmpList[0], radix: 16).toSigned(64), + )) { final String mapValue = String.fromCharCode( - int.parse(tmpList[1], radix: 16).toSigned(64)); - mapTable[int.parse(tmpList[0], radix: 16) - .toSigned(64) - .toDouble()] = mapValue; + int.parse(tmpList[1], radix: 16).toSigned(64), + ); + mapTable[int.parse( + tmpList[0], + radix: 16, + ).toSigned(64).toDouble()] = + mapValue; } } } @@ -1397,28 +1450,37 @@ class FontStructure { final int numberOfCharacters = tableValue.length ~/ 4; for (int j = 0; j < numberOfCharacters; j++) { final String mapChar = String.fromCharCode( - int.parse(tableValue.substring(0, 4), radix: 16) - .toSigned(64)); + int.parse( + tableValue.substring(0, 4), + radix: 16, + ).toSigned(64), + ); tableValue = tableValue.substring(4); mapValue += mapChar; } mapValue = checkContainInvalidChar(mapValue); if (!mapTable.containsKey( - int.parse(tempStringList[0], radix: 16) - .toSigned(64))) { - mapTable[int.parse(tempStringList[0], radix: 16) - .toSigned(64) - .toDouble()] = mapValue; + int.parse(tempStringList[0], radix: 16).toSigned(64), + )) { + mapTable[int.parse( + tempStringList[0], + radix: 16, + ).toSigned(64).toDouble()] = + mapValue; } continue; } if (!mapTable.containsKey( - int.parse(tempStringList[0]).toSigned(64))) { + int.parse(tempStringList[0]).toSigned(64), + )) { final String mapValue = String.fromCharCode( - int.parse(tempStringList[1], radix: 16).toSigned(64)); - mapTable[int.parse(tempStringList[0], radix: 16) - .toSigned(64) - .toDouble()] = mapValue; + int.parse(tempStringList[1], radix: 16).toSigned(64), + ); + mapTable[int.parse( + tempStringList[0], + radix: 16, + ).toSigned(64).toDouble()] = + mapValue; } } } @@ -1432,13 +1494,17 @@ class FontStructure { final int subArrayStatIndex = tableEntry[i].indexOf('['); final int subArrayEndIndex = tableEntry[i].indexOf(']'); if (subArrayEndIndex == -1) { - str = tableEntry[i] - .substring(subArrayStatIndex, tableEntry[i].length); + str = tableEntry[i].substring( + subArrayStatIndex, + tableEntry[i].length, + ); i++; while (true) { if (tableEntry[i].contains(']')) { - str += tableEntry[i] - .substring(0, tableEntry[i].indexOf(']')); + str += tableEntry[i].substring( + 0, + tableEntry[i].indexOf(']'), + ); break; } else { str += tableEntry[i]; @@ -1446,8 +1512,10 @@ class FontStructure { } } } else { - str = tableEntry[i] - .substring(subArrayStatIndex, subArrayEndIndex); + str = tableEntry[i].substring( + subArrayStatIndex, + subArrayEndIndex, + ); } List subArray = []; subArray = getHexCode(str); @@ -1462,27 +1530,39 @@ class FontStructure { } endOfTable = i; if (tempStringList.length > 1) { - startRange = int.parse(tempStringList[0], radix: 16) - .toSigned(64) - .toDouble(); - endRange = int.parse(tempStringList[1], radix: 16) - .toSigned(64) - .toDouble(); + startRange = + int.parse( + tempStringList[0], + radix: 16, + ).toSigned(64).toDouble(); + endRange = + int.parse( + tempStringList[1], + radix: 16, + ).toSigned(64).toDouble(); int t = 0; - for (double j = startRange, k = 0; - j <= endRange; - j++, k++, t++) { + for ( + double j = startRange, k = 0; + j <= endRange; + j++, k++, t++ + ) { String mapString = ''; int l = 0; while (l < subArray[t].length) { - final String mapValueHex = - subArray[t].substring(l, l + 4); - final int hexEquivalent = - int.parse(mapValueHex, radix: 16).toSigned(64); + final String mapValueHex = subArray[t].substring( + l, + l + 4, + ); + final int hexEquivalent = int.parse( + mapValueHex, + radix: 16, + ).toSigned(64); final int hex = hexEquivalent; final String hexString = hex.toRadixString(16); - final int mapValue = - int.parse(hexString, radix: 16).toSigned(64); + final int mapValue = int.parse( + hexString, + radix: 16, + ).toSigned(64); final String mapChar = String.fromCharCode(mapValue); mapString += mapChar; l += 4; @@ -1495,33 +1575,46 @@ class FontStructure { } else { tempStringList = getHexCode(tableEntry[i]); if (tempStringList.length == 3) { - startRange = int.parse(tempStringList[0], radix: 16) - .toSigned(64) - .toDouble(); - endRange = int.parse(tempStringList[1], radix: 16) - .toSigned(64) - .toDouble(); + startRange = + int.parse( + tempStringList[0], + radix: 16, + ).toSigned(64).toDouble(); + endRange = + int.parse( + tempStringList[1], + radix: 16, + ).toSigned(64).toDouble(); String mapValueHex = tempStringList[2]; if (tempStringList[2].length > 4) { final String mapFirstHex = mapValueHex.substring(0, 4); - final int hexFirstEquivalent = - int.parse(mapFirstHex, radix: 16).toSigned(64); + final int hexFirstEquivalent = int.parse( + mapFirstHex, + radix: 16, + ).toSigned(64); final int mapFirstValue = int.parse( - hexFirstEquivalent.toRadixString(16), - radix: 16) - .toSigned(64); - final String mapFirstChar = - String.fromCharCode(mapFirstValue); + hexFirstEquivalent.toRadixString(16), + radix: 16, + ).toSigned(64); + final String mapFirstChar = String.fromCharCode( + mapFirstValue, + ); mapValueHex = mapValueHex.substring(5, 8); - final int hexEquivalent = - int.parse(mapValueHex, radix: 16).toSigned(64); - for (double j = startRange, k = 0; - j <= endRange; - j++, k++) { + final int hexEquivalent = int.parse( + mapValueHex, + radix: 16, + ).toSigned(64); + for ( + double j = startRange, k = 0; + j <= endRange; + j++, k++ + ) { final int hex = hexEquivalent + k.toInt(); final String hexString = hex.toRadixString(16); - final int mapValue = - int.parse(hexString, radix: 16).toSigned(64); + final int mapValue = int.parse( + hexString, + radix: 16, + ).toSigned(64); String mapChar = mapFirstChar + String.fromCharCode(mapValue); mapChar = checkContainInvalidChar(mapChar); @@ -1530,15 +1623,21 @@ class FontStructure { } } } else { - final int hexEquivalent = - int.parse(mapValueHex, radix: 16).toSigned(64); - for (double j = startRange, k = 0; - j <= endRange; - j++, k++) { + final int hexEquivalent = int.parse( + mapValueHex, + radix: 16, + ).toSigned(64); + for ( + double j = startRange, k = 0; + j <= endRange; + j++, k++ + ) { final int hex = hexEquivalent + k.toInt(); final String hexString = hex.toRadixString(16); - final int mapValue = - int.parse(hexString, radix: 16).toSigned(64); + final int mapValue = int.parse( + hexString, + radix: 16, + ).toSigned(64); final String mapChar = String.fromCharCode(mapValue); if (!mapTable.containsKey(j)) { mapTable[j] = mapChar; @@ -1550,11 +1649,16 @@ class FontStructure { semiCount = tempStringList.length; for (int k = 0; k < semiCount;) { final String mapValue = String.fromCharCode( - int.parse(tempStringList[k + 2], radix: 16) - .toSigned(64)); - mapTable[int.parse(tempStringList[k], radix: 16) - .toSigned(64) - .toDouble()] = mapValue; + int.parse( + tempStringList[k + 2], + radix: 16, + ).toSigned(64), + ); + mapTable[int.parse( + tempStringList[k], + radix: 16, + ).toSigned(64).toDouble()] = + mapValue; k = k + 3; } } @@ -1590,9 +1694,12 @@ class FontStructure { if (fontDictionary.containsKey(PdfDictionaryProperties.encoding)) { if (fontDictionary[PdfDictionaryProperties.encoding] is PdfReferenceHolder) { - encodingDictionary = (fontDictionary[PdfDictionaryProperties.encoding]! - as PdfReferenceHolder) - .object as PdfDictionary?; + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + fontDictionary[PdfDictionaryProperties.encoding], + ); + if (primitive != null && primitive is PdfDictionary) { + encodingDictionary = primitive; + } } else if (fontDictionary[PdfDictionaryProperties.encoding] is PdfDictionary) { encodingDictionary = @@ -1600,8 +1707,9 @@ class FontStructure { } if (encodingDictionary != null) { - if (encodingDictionary - .containsKey(PdfDictionaryProperties.differences)) { + if (encodingDictionary.containsKey( + PdfDictionaryProperties.differences, + )) { int differenceCount = 0; final IPdfPrimitive? obj = encodingDictionary[PdfDictionaryProperties.differences]; @@ -1622,16 +1730,17 @@ class FontStructure { text = (differences[i]! as PdfName).name; if ((fontType!.name == 'Type1') && (text == '.notdef')) { text = ' '; - differencesDictionary[differenceCount.toString()] = - getLatinCharacter(text); + differencesDictionary[differenceCount + .toString()] = getLatinCharacter(text); differenceCount++; } else { text = getLatinCharacter(text); text = getSpecialCharacter(text); - if (!differencesDictionary - .containsKey(differenceCount.toString())) { - differencesDictionary[differenceCount.toString()] = - getLatinCharacter(text); + if (!differencesDictionary.containsKey( + differenceCount.toString(), + )) { + differencesDictionary[differenceCount + .toString()] = getLatinCharacter(text); } differenceCount++; } @@ -2447,17 +2556,17 @@ class FontStructure { /// hexCode - Mapping string in the map table of the document List getHexCode(String hexCode) { final List tmp = []; - String _tmp = hexCode; - int _start = 0; - int _stop = 0; - String _txt; - for (int j1 = 0; _start >= 0; j1++) { - _start = _tmp.indexOf('<'); - _stop = _tmp.indexOf('>'); - if (_start >= 0 && _stop >= 0) { - _txt = _tmp.substring(_start + 1, _stop); - tmp.add(_txt); - _tmp = _tmp.substring(_stop + 1, _tmp.length); + String tmpvalue = hexCode; + int startValue = 0; + int stopValue = 0; + String txtValue; + for (int j1 = 0; startValue >= 0; j1++) { + startValue = tmpvalue.indexOf('<'); + stopValue = tmpvalue.indexOf('>'); + if (startValue >= 0 && stopValue >= 0) { + txtValue = tmpvalue.substring(startValue + 1, stopValue); + tmp.add(txtValue); + tmpvalue = tmpvalue.substring(stopValue + 1, tmpvalue.length); } } return tmp; @@ -2483,12 +2592,16 @@ class FontStructure { } /// internal method - String decodeTextExtraction(String textToDecode, bool isSameFont, - [List? charcodes]) { + String decodeTextExtraction( + String textToDecode, + bool isSameFont, [ + List? charcodes, + ]) { String decodedText = ''; String encodedText = textToDecode; this.isSameFont = isSameFont; bool hasEscapeChar = false; + bool isHex = false; switch (encodedText[0]) { case '(': { @@ -2512,10 +2625,11 @@ class FontStructure { 'UniCNS-UCS2-H', 'UniKS-UCS2-H', 'UniJIS-UCS2-H', - 'UniGB-UCS2-H' + 'UniGB-UCS2-H', ].contains( - (fontDictionary[PdfDictionaryProperties.encoding]! as PdfName) - .name)) { + (fontDictionary[PdfDictionaryProperties.encoding]! as PdfName) + .name, + )) { String text = decodedText; if (!hasEscapeChar) { do { @@ -2542,7 +2656,7 @@ class FontStructure { } encodedText = encodedText.substring(1, encodedText.length - 1); while (encodedText.isNotEmpty) { - bool isHex = false; + isHex = false; int textStart = encodedText.indexOf('('); int textEnd = encodedText.indexOf(')'); final int textHexStart = encodedText.indexOf('<'); @@ -2571,24 +2685,31 @@ class FontStructure { } } - final String tempString = - encodedText.substring(textStart + 1, textEnd); + final String tempString = encodedText.substring( + textStart + 1, + textEnd, + ); if (isHex) { decodedText += getHexaDecimalString(tempString, charcodes); } else { decodedText += getLiteralString(tempString, charcodes); } - encodedText = - encodedText.substring(textEnd + 1, encodedText.length); + encodedText = encodedText.substring( + textEnd + 1, + encodedText.length, + ); } } break; case '<': { - final String hexEncodedText = - encodedText.substring(1, encodedText.length - 1); + final String hexEncodedText = encodedText.substring( + 1, + encodedText.length - 1, + ); decodedText = getHexaDecimalString(hexEncodedText, charcodes); + isHex = true; } break; default: @@ -2600,7 +2721,7 @@ class FontStructure { (fontEncoding == 'Identity-H' && containsCmap)) { isMappingDone = true; if (characterMapTable.isNotEmpty) { - decodedText = mapCharactersFromTable(decodedText); + decodedText = mapCharactersFromTable(decodedText, isHex); } else if (differencesDictionary.isNotEmpty) { decodedText = mapDifferences(decodedText); } else if (fontEncoding != '') { @@ -2615,6 +2736,7 @@ class FontStructure { decodedText = mapZapf(decodedText); } if (fontEncoding == 'MacRomanEncoding') { + macRomanEncoded = true; String tempstring = ''; for (int i = 0; i < decodedText.length; i++) { final int b = decodedText[i].codeUnitAt(0).toUnsigned(8); @@ -2705,9 +2827,10 @@ class FontStructure { decodedList.add(listElement); } final String tempString = encodedText.substring(textStart + 1, textEnd); - listElement = isHex - ? getHexaDecimalCJKString(tempString) - : getLiteralString(tempString); + listElement = + isHex + ? getHexaDecimalCJKString(tempString) + : getLiteralString(tempString); decodedText += listElement; listElement = skipEscapeSequence(listElement); if (listElement.isNotEmpty) { @@ -2715,8 +2838,11 @@ class FontStructure { listElement[0].codeUnitAt(0) <= 3711 && decodedList.isNotEmpty) { String previous = decodedList[0]; - previous = - previous.replaceRange(previous.length - 1, previous.length, ''); + previous = previous.replaceRange( + previous.length - 1, + previous.length, + '', + ); previous += listElement; listElement = previous; decodedList[0] = '${fromUnicodeText(listElement)}s'; @@ -2728,7 +2854,10 @@ class FontStructure { decodedList.isNotEmpty) { String previous = decodedList[0]; previous = previous.replaceRange( - previous.length - 1, previous.length, ''); + previous.length - 1, + previous.length, + '', + ); previous += listElement; listElement = previous; decodedList[0] = '${fromUnicodeText(listElement)}s'; @@ -2767,7 +2896,9 @@ class FontStructure { /// internal method Map, String> decodeTextExtractionTJ( - String textToDecode, bool isSameFont) { + String textToDecode, + bool isSameFont, + ) { String decodedText = ''; String encodedText = textToDecode; String listElement; @@ -2870,8 +3001,10 @@ class FontStructure { charCodes = []; } } - final String tempString = - encodedText.substring(textStart + 1, textEnd); + final String tempString = encodedText.substring( + textStart + 1, + textEnd, + ); if (isHex) { listElement = getHexaDecimalString(tempString, charCodes); if (listElement.contains(r'\')) { @@ -2886,7 +3019,9 @@ class FontStructure { if (listElement.contains('\u0000') && !characterMapTable.containsKey(0)) { listElement = listElement.replaceAll( - '\u0000', ''); //replace empty character. + '\u0000', + '', + ); //replace empty character. } listElement = skipEscapeSequence(listElement); if (fontEncoding != 'Identity-H' || @@ -2894,7 +3029,7 @@ class FontStructure { (fontEncoding == 'Identity-H' && containsCmap)) { isMappingDone = true; if (characterMapTable.isNotEmpty) { - listElement = mapCharactersFromTable(listElement); + listElement = mapCharactersFromTable(listElement, isHex); } else if (differencesDictionary.isNotEmpty) { listElement = mapDifferences(listElement); } else if (fontEncoding != '') { @@ -2904,10 +3039,11 @@ class FontStructure { if (cidToGidTable != null && !isTextExtraction) { listElement = mapCidToGid(listElement); } - if (encodedText - .substring(textEnd + 1, encodedText.length) - .trim() - .isEmpty) { + if (!isLayout && + encodedText + .substring(textEnd + 1, encodedText.length) + .trim() + .isEmpty) { listElement = listElement.trimRight(); } if (listElement.contains(RegExp('[\n-\r]'))) { @@ -2919,7 +3055,10 @@ class FontStructure { decodedList.isNotEmpty) { String previous = decodedList.values.toList()[0]; previous = previous.replaceRange( - previous.length - 1, previous.length, ''); + previous.length - 1, + previous.length, + '', + ); previous += listElement; listElement = previous; decodedList[decodedList.keys.toList()[0]] = '${listElement}s'; @@ -2931,7 +3070,10 @@ class FontStructure { decodedList.isNotEmpty) { String previous = decodedList.values.toList()[0]; previous = previous.replaceRange( - previous.length - 1, previous.length, ''); + previous.length - 1, + previous.length, + '', + ); previous += listElement; listElement = previous; decodedList[decodedList.keys.toList()[0]] = '${listElement}s'; @@ -2950,15 +3092,19 @@ class FontStructure { decodedList[charCodes] = listElement; charCodes = []; } - encodedText = - encodedText.substring(textEnd + 1, encodedText.length); + encodedText = encodedText.substring( + textEnd + 1, + encodedText.length, + ); } } break; case '<': { - final String hexEncodedText = - encodedText.substring(1, encodedText.length - 1); + final String hexEncodedText = encodedText.substring( + 1, + encodedText.length - 1, + ); decodedText = getHexaDecimalString(hexEncodedText, charCodes); } break; @@ -2966,7 +3112,10 @@ class FontStructure { break; } decodedText = skipEscapeSequence(decodedText); - isWhiteSpace = decodedText.isEmpty || decodedText.trimRight() == ''; + isWhiteSpace = + decodedText.isEmpty || + (!decodedText.contains(RegExp(r'[\f\r]')) && + decodedText.trimRight().isEmpty); return decodedList; } @@ -2977,7 +3126,8 @@ class FontStructure { for (int i = 0; i < value.length; i++) { if (i + 1 < value.length) { result += String.fromCharCode( - (value.codeUnitAt(i) * 256) + value.codeUnitAt(i + 1)); + (value.codeUnitAt(i) * 256) + value.codeUnitAt(i + 1), + ); i++; } else { result += String.fromCharCode(value.codeUnitAt(i)); @@ -2986,7 +3136,8 @@ class FontStructure { } else { for (int i = 0; i < value.length; i++) { result += String.fromCharCode( - (value.codeUnitAt(i) * 256) + value.codeUnitAt(i + 1)); + (value.codeUnitAt(i) * 256) + value.codeUnitAt(i + 1), + ); i++; } } @@ -3111,8 +3262,10 @@ class FontStructure { } } } - final String tempString = - textToEncode.substring(textStart + 1, textEnd); + final String tempString = textToEncode.substring( + textStart + 1, + textEnd, + ); if (isHex) { encodedText += getHexaDecimalString(tempString); } else if (!isHex && fontEncoding == 'Identity-H') { @@ -3120,15 +3273,19 @@ class FontStructure { } else { encodedText += getLiteralString(tempString); } - textToEncode = - textToEncode.substring(textEnd + 1, textToEncode.length); + textToEncode = textToEncode.substring( + textEnd + 1, + textToEncode.length, + ); } } break; case '<': { - final String hexEncodedText = - textToEncode.substring(1, textToEncode.length - 1); + final String hexEncodedText = textToEncode.substring( + 1, + textToEncode.length - 1, + ); encodedText = getHexaDecimalString(hexEncodedText); if (encodedText.contains(r'\')) { // ignore: use_raw_strings @@ -3147,7 +3304,10 @@ class FontStructure { if (!isTextExtraction) { encodedText = skipEscapeSequence(encodedText); } - isWhiteSpace = encodedText.isEmpty || encodedText.trimRight() == ''; + isWhiteSpace = + encodedText.isEmpty || + (!encodedText.contains(RegExp(r'[\f\r]')) && + encodedText.trimRight().isEmpty); return encodedText; } @@ -3167,8 +3327,9 @@ class FontStructure { primitive = (descendantFontArray[0]! as PdfReferenceHolder).object; if (primitive is PdfDictionary) { final PdfDictionary descendantDictionary = primitive; - if (descendantDictionary - .containsKey(PdfDictionaryProperties.subtype)) { + if (descendantDictionary.containsKey( + PdfDictionaryProperties.subtype, + )) { primitive = descendantDictionary[PdfDictionaryProperties.subtype]; if (primitive is PdfName) { @@ -3191,8 +3352,9 @@ class FontStructure { primitive = (descendantFontArray[0]! as PdfReferenceHolder).object; if (primitive is PdfDictionary) { final PdfDictionary descendantDictionary = primitive; - if (descendantDictionary - .containsKey(PdfDictionaryProperties.subtype)) { + if (descendantDictionary.containsKey( + PdfDictionaryProperties.subtype, + )) { primitive = descendantDictionary[PdfDictionaryProperties.subtype]; if (primitive is PdfName) { final PdfName subtype = @@ -3295,9 +3457,9 @@ class FontStructure { encodedText = unescape(encodedText!); } catch (e) { if (encodedText != null && encodedText.isNotEmpty) { - encodedText = RegExp.escape(encodedText) - .replaceAll(r"\'''", r"'''") - .replaceAll(r'\\', r'\'); + encodedText = RegExp.escape( + encodedText, + ).replaceAll(r"\'''", r"'''").replaceAll(r'\\', r'\'); } else { throw ArgumentError(e.toString()); } @@ -3319,7 +3481,8 @@ class FontStructure { if (textDecoded.length == 7 && textDecoded.toLowerCase().startsWith('uni')) { textDecoded = String.fromCharCode( - int.parse(textDecoded.substring(3), radix: 16)); + int.parse(textDecoded.substring(3), radix: 16), + ); } decodedText += textDecoded; } else { @@ -3334,8 +3497,9 @@ class FontStructure { } } } - if (!reverseDictMapping - .containsKey(differencesDictionary[character.toString()])) { + if (!reverseDictMapping.containsKey( + differencesDictionary[character.toString()], + )) { reverseDictMapping[differencesDictionary[character.toString()]] = character; } @@ -3352,15 +3516,17 @@ class FontStructure { if (skip) { switch (encodedText[i]) { case 'n': - if (differencesDictionary - .containsKey('\n'.codeUnitAt(0).toString())) { + if (differencesDictionary.containsKey( + '\n'.codeUnitAt(0).toString(), + )) { decodedText += differencesDictionary['\n'.codeUnitAt(0).toString()]!; } break; case 'r': - if (differencesDictionary - .containsKey('\r'.codeUnitAt(0).toString())) { + if (differencesDictionary.containsKey( + '\r'.codeUnitAt(0).toString(), + )) { decodedText += differencesDictionary['\r'.codeUnitAt(0).toString()]!; } @@ -4199,7 +4365,10 @@ class FontStructure { if (reverseMapTable!.containsKey(encodedText)) { decodedtext = encodedText; final int charPosition = reverseMapTable![decodedtext]!.toInt(); - zapfPostScript = differenceTable[charPosition]!; + if (differenceTable.isNotEmpty && + differenceTable.containsKey(charPosition)) { + zapfPostScript = differenceTable[charPosition]!; + } } else { decodedtext = '\u2708'; zapfPostScript = 'a118'; @@ -4241,8 +4410,9 @@ class FontStructure { mappingString = mappingString.replaceAll(mappingString[index], ''); } if (mappingString.isNotEmpty) { - if (!cidToGidReverseMapTable - .containsKey(mappingString.codeUnitAt(0))) { + if (!cidToGidReverseMapTable.containsKey( + mappingString.codeUnitAt(0), + )) { cidToGidReverseMapTable[mappingString.codeUnitAt(0)] = character; } } @@ -4351,9 +4521,11 @@ class FontStructure { } limit = 2; } - for (int i = octalIndex + 1; - i <= octalIndex + limit; - i++) //check for octal characters + for ( + int i = octalIndex + 1; + i <= octalIndex + limit; + i++ + ) //check for octal characters { if (i < decodedText.length) { int val = 0; @@ -4386,7 +4558,7 @@ class FontStructure { temp = _getWindows1252DecodedText(charbytes); List tempchar; tempchar = [ - _getWindows1252DecodedText([decimalValue.toUnsigned(8)]) + _getWindows1252DecodedText([decimalValue.toUnsigned(8)]), ]; int charvalue = 0; for (final String tempchar1 in tempchar) { @@ -4400,7 +4572,7 @@ class FontStructure { final List charbytes = [decimalValue.toUnsigned(8)]; temp = String.fromCharCodes(charbytes); final List tempchar = [ - String.fromCharCodes([decimalValue.toUnsigned(8)]) + String.fromCharCodes([decimalValue.toUnsigned(8)]), ]; int charvalue = 0; for (final String tempchar1 in tempchar) { @@ -4413,8 +4585,11 @@ class FontStructure { } } (charCodes ??= []).add(decimalValue); - decodedText = - decodedText.replaceRange(octalIndex, octalIndex + limit + 1, ''); + decodedText = decodedText.replaceRange( + octalIndex, + octalIndex + limit + 1, + '', + ); final List str = decodedText.split(''); str.insert(octalIndex, temp); octalIndexCollection.add(octalIndex); @@ -4432,7 +4607,7 @@ class FontStructure { 'r': '\r', 't': '\t', 'v': '\v', - "'": "'" + "'": "'", }; if (decodedText.contains(r'\')) { for (int i = count - 2; i >= 0; i--) { @@ -4450,13 +4625,15 @@ class FontStructure { //Update the octal index collection and char codes, //if character map table contains the escape sequence. for (int j = 0; j < octalIndexCollection.length; j++) { - if (characterMapTable != null && - characterMapTable - .containsKey(escapeSequence[sequence]!.codeUnitAt(0))) { + if (characterMapTable.containsKey( + escapeSequence[sequence]!.codeUnitAt(0), + )) { if (i < octalIndexCollection[j]) { octalIndexCollection.insert(j, i); - charCodes! - .insert(j, escapeSequence[sequence]!.codeUnitAt(0)); + charCodes!.insert( + j, + escapeSequence[sequence]!.codeUnitAt(0), + ); break; } else if (j == octalIndexCollection.length - 1 && i > octalIndexCollection[j]) { @@ -4478,15 +4655,15 @@ class FontStructure { int combinedGlyphDiff = 0; for (int i = 0; i < count; i++) { if (!octalIndexCollection.contains(i)) { - if (characterMapTable != null && - characterMapTable.containsKey(str[i].codeUnitAt(0))) { - (charCodes ??= []) - .insert(i + combinedGlyphDiff, str[i].codeUnitAt(0)); + if (characterMapTable.containsKey(str[i].codeUnitAt(0))) { + (charCodes ??= []).insert( + i + combinedGlyphDiff, + str[i].codeUnitAt(0), + ); } else { (charCodes ??= []).insert(i + combinedGlyphDiff, 0); } - } else if (characterMapTable != null && - characterMapTable.containsKey(str[i].codeUnitAt(0))) { + } else if (characterMapTable.containsKey(str[i].codeUnitAt(0))) { final String mappingString = characterMapTable[str[i].codeUnitAt(0)]!; final int mappingStringLength = mappingString.length; if (mappingStringLength > 1) { @@ -4563,8 +4740,10 @@ class FontStructure { } /// Decodes the HEX encoded string and returns Decoded string. - String getHexaDecimalString(String hexEncodedText, - [List? charCodes]) { + String getHexaDecimalString( + String hexEncodedText, [ + List? charCodes, + ]) { String decodedText = ''; // IsHexaDecimalString = true; if (hexEncodedText.isNotEmpty) { @@ -4586,25 +4765,29 @@ class FontStructure { } String hexChar = hexEncodedText.substring(0, limit); - if (fontDictionary - .containsKey(PdfDictionaryProperties.descendantFonts) && + if (fontDictionary.containsKey( + PdfDictionaryProperties.descendantFonts, + ) && !fontDictionary.containsKey(PdfDictionaryProperties.toUnicode)) { final IPdfPrimitive? descendantArray = fontDictionary[PdfDictionaryProperties.descendantFonts]; if (descendantArray != null && descendantArray is PdfArray) { PdfDictionary? descendantDictionary; if (descendantArray[0] is PdfReferenceHolder) { - descendantDictionary = (descendantArray[0]! as PdfReferenceHolder) - .object as PdfDictionary?; + descendantDictionary = + (descendantArray[0]! as PdfReferenceHolder).object + as PdfDictionary?; } else if (descendantArray[0] is PdfDictionary) { descendantDictionary = descendantArray[0]! as PdfDictionary; } if (descendantDictionary != null) { PdfDictionary? descriptorDictionary; - if (descendantDictionary - .containsKey(PdfDictionaryProperties.fontDescriptor)) { - IPdfPrimitive? primitive = descendantDictionary[ - PdfDictionaryProperties.fontDescriptor]; + if (descendantDictionary.containsKey( + PdfDictionaryProperties.fontDescriptor, + )) { + IPdfPrimitive? primitive = + descendantDictionary[PdfDictionaryProperties + .fontDescriptor]; if (primitive is PdfReferenceHolder) { primitive = primitive.object; if (primitive != null && primitive is PdfDictionary) { @@ -4615,10 +4798,12 @@ class FontStructure { } } if (descriptorDictionary != null) { - if (descendantDictionary - .containsKey(PdfDictionaryProperties.subtype) && - !descriptorDictionary - .containsKey(PdfDictionaryProperties.fontFile2)) { + if (descendantDictionary.containsKey( + PdfDictionaryProperties.subtype, + ) && + !descriptorDictionary.containsKey( + PdfDictionaryProperties.fontFile2, + )) { final PdfName subtype = descendantDictionary[PdfDictionaryProperties.subtype]! as PdfName; @@ -4628,11 +4813,13 @@ class FontStructure { } } } - } else if (fontDictionary.items! - .containsKey(PdfName(PdfDictionaryProperties.descendantFonts))) { + } else if (fontDictionary.items!.containsKey( + PdfName(PdfDictionaryProperties.descendantFonts), + )) { final PdfReferenceHolder? descendantFontArrayReference = - fontDictionary.items![ - PdfName(PdfDictionaryProperties.descendantFonts)] + fontDictionary.items![PdfName( + PdfDictionaryProperties.descendantFonts, + )] as PdfReferenceHolder?; if (descendantFontArrayReference != null) { PdfName? subtype; @@ -4643,24 +4830,31 @@ class FontStructure { (descendantFontArray[0]! as PdfReferenceHolder).object as PdfDictionary?; if (descendantDictionary != null && - descendantDictionary - .containsKey(PdfDictionaryProperties.cidSystemInfo) && - descendantDictionary - .containsKey(PdfDictionaryProperties.subtype)) { + descendantDictionary.containsKey( + PdfDictionaryProperties.cidSystemInfo, + ) && + descendantDictionary.containsKey( + PdfDictionaryProperties.subtype, + )) { subtype = descendantDictionary[PdfDictionaryProperties.subtype] as PdfName?; - final PdfDictionary? cidSystemInfo = (descendantDictionary[ - PdfDictionaryProperties.cidSystemInfo]! - as PdfReferenceHolder) - .object as PdfDictionary?; + final PdfDictionary? cidSystemInfo = + (descendantDictionary[PdfDictionaryProperties + .cidSystemInfo]! + as PdfReferenceHolder) + .object + as PdfDictionary?; if (cidSystemInfo != null && - cidSystemInfo - .containsKey(PdfDictionaryProperties.registry) && - cidSystemInfo - .containsKey(PdfDictionaryProperties.ordering) && - cidSystemInfo - .containsKey(PdfDictionaryProperties.supplement)) { + cidSystemInfo.containsKey( + PdfDictionaryProperties.registry, + ) && + cidSystemInfo.containsKey( + PdfDictionaryProperties.ordering, + ) && + cidSystemInfo.containsKey( + PdfDictionaryProperties.supplement, + )) { final PdfString pdfRegistry = cidSystemInfo[PdfDictionaryProperties.registry]! as PdfString; @@ -4700,16 +4894,17 @@ class FontStructure { decodedTxt.contains('’')) && tempHexEncodedText.length < limit) { decodedText = tempDecodedText; - final int hexNum = - int.parse(tempHexEncodedText, radix: 16).toSigned(32); + final int hexNum = int.parse( + tempHexEncodedText, + radix: 16, + ).toSigned(32); (charCodes ??= []).add(hexNum); hexEncodedText = String.fromCharCode(hexNum); decodedText += hexEncodedText; } int combinedGlyphDiff = 0; for (int i = 0; i < decodedText.length; i++) { - if (characterMapTable != null && - characterMapTable.containsKey(decodedText[i].codeUnitAt(0))) { + if (characterMapTable.containsKey(decodedText[i].codeUnitAt(0))) { final String mappingString = characterMapTable[decodedText[i].codeUnitAt(0)]!; final int mappingStringLength = mappingString.length; @@ -4771,7 +4966,7 @@ class FontStructure { /// Takes in the decoded text and maps it with its /// corresponding entry in the CharacterMapTable - String mapCharactersFromTable(String decodedText) { + String mapCharactersFromTable(String decodedText, [bool isHex = false]) { String finalText = ''; bool skip = false; for (int i = 0; i < decodedText.length; i++) { @@ -4789,7 +4984,8 @@ class FontStructure { characterMapTable.length != reverseMapTable!.length) { if (isCancel(mappingString) || isNonPrintableCharacter( - character)) //Contains 'CANCEL' of ASCII value 24 + character, + )) //Contains 'CANCEL' of ASCII value 24 { mappingString = character; } @@ -4797,7 +4993,8 @@ class FontStructure { finalText += mappingString; skip = false; } else if (!characterMapTable.containsKey(character.codeUnitAt(0)) && - !skip) { + !skip && + !isHex) { final List bytes = encodeBigEndian(character); if (bytes[0] != 92) { if (characterMapTable.containsKey(bytes[0])) { @@ -4902,4 +5099,80 @@ class FontStructure { } return isNonPrintable; } + + /// Disposes the instance. + void dispose() { + _differencesDictionary = null; + if (_characterMapTable != null && _characterMapTable!.isNotEmpty) { + _characterMapTable!.clear(); + } + _characterMapTable = null; + if (_reverseMapTable != null && _reverseMapTable!.isNotEmpty) { + _reverseMapTable!.clear(); + } + _reverseMapTable = null; + if (reverseDictMapping.isNotEmpty) { + reverseDictMapping.clear(); + } + if (cidToGidTable != null && cidToGidTable!.isNotEmpty) { + cidToGidTable!.clear(); + } + cidToGidTable = null; + if (_differencesDictionary != null && _differencesDictionary!.isNotEmpty) { + _differencesDictionary!.clear(); + } + _differencesDictionary = null; + if (_fontGlyphWidth != null && _fontGlyphWidth!.isNotEmpty) { + _fontGlyphWidth!.clear(); + } + _fontGlyphWidth = null; + if (tempMapTable.isNotEmpty) { + tempMapTable.clear(); + } + if (_octDecMapTable != null && _octDecMapTable!.isNotEmpty) { + _octDecMapTable!.clear(); + } + _octDecMapTable = null; + if (_cidToGidReverseMapTable != null && + _cidToGidReverseMapTable!.isNotEmpty) { + _cidToGidReverseMapTable!.clear(); + } + _cidToGidReverseMapTable = null; + if (differenceTable.isNotEmpty) { + differenceTable.clear(); + } + if (differenceEncoding != null && differenceEncoding!.isNotEmpty) { + differenceEncoding!.clear(); + } + differenceEncoding = null; + if (type3FontCharProcsDict.isNotEmpty) { + type3FontCharProcsDict.clear(); + } + if (tempStringList.isNotEmpty) { + tempStringList.clear(); + } + if (_unicodeCharMapTable != null && _unicodeCharMapTable!.isNotEmpty) { + _unicodeCharMapTable!.clear(); + } + _unicodeCharMapTable = null; + if (_macEncodeTable != null && _macEncodeTable!.isNotEmpty) { + _macEncodeTable!.clear(); + } + _macEncodeTable = null; + if (_winansiMapTable.isNotEmpty) { + _winansiMapTable.clear(); + } + if (_windows1252MapTable.isNotEmpty) { + _windows1252MapTable.clear(); + } + if (_macRomanMapTable.isNotEmpty) { + _macRomanMapTable.clear(); + } + if (standardCJKFontNames.isNotEmpty) { + standardCJKFontNames.clear(); + } + if (standardFontNames.isNotEmpty) { + standardFontNames.clear(); + } + } } 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 973306643..aca16d722 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 @@ -14,30 +14,44 @@ import 'xobject_element.dart'; /// internal class class ImageRenderer { /// internal constructor - ImageRenderer(PdfRecordCollection? contentElements, - PdfPageResources resources, this.currentPageHeight, - [GraphicsObject? g]) { + ImageRenderer( + PdfRecordCollection? contentElements, + PdfPageResources resources, + this.currentPageHeight, [ + GraphicsObject? g, + ]) { const int dpiX = 96; _graphicsObject = GraphicsObject(); _graphicsState = GraphicStateCollection(); graphicsObjects = GraphicObjectDataCollection(); final GraphicObjectData newObject = GraphicObjectData(); - newObject.currentTransformationMatrix = - MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); + 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( - transformMatrix.offsetX / 1.333, transformMatrix.offsetY / 1.333); + transformMatrix.offsetX / 1.333, + transformMatrix.offsetY / 1.333, + ); newObject.drawing2dMatrixCTM = MatrixHelper(1, 0, 0, 1, 0, 0); newObject.drawing2dMatrixCTM!.translate( - transformMatrix.offsetX / 1.333, transformMatrix.offsetY / 1.333); + transformMatrix.offsetX / 1.333, + transformMatrix.offsetY / 1.333, + ); newObject.documentMatrix = MatrixHelper( - 1.33333333333333 * (dpiX / 96) * transformMatrix.m11, - 0, - 0, - -1.33333333333333 * (dpiX / 96) * transformMatrix.m22, - 0, - currentPageHeight! * transformMatrix.m22); + 1.33333333333333 * (dpiX / 96) * transformMatrix.m11, + 0, + 0, + -1.33333333333333 * (dpiX / 96) * transformMatrix.m22, + 0, + currentPageHeight! * transformMatrix.m22, + ); graphicsObjects!.push(newObject); graphicsObjects!.push(newObject); _contentElements = contentElements; @@ -53,6 +67,7 @@ class ImageRenderer { GraphicsObject? _graphicsObject; PdfRecordCollection? _contentElements; PdfPageResources? _resources; + String? _actualText; /// internal field double? currentPageHeight; @@ -209,10 +224,10 @@ class ImageRenderer { /// internal method void renderAsImage() { - final PdfRecordCollection? _recordCollection = + final PdfRecordCollection? pdfRecordCollection = _isType3Font ? _type3RecordCollection : _contentElements; - if (_recordCollection != null) { - final List records = _recordCollection.recordCollection; + if (pdfRecordCollection != null) { + final List records = pdfRecordCollection.recordCollection; for (int i = 0; i < records.length; i++) { final PdfRecord record = records[i]; final String token = record.operatorName!; @@ -234,6 +249,18 @@ class ImageRenderer { if (_skipRendering) { _inlayersCount++; } + if (elements[1].contains('ActualText') && + elements[1].contains('(')) { + _actualText = elements[1].substring( + elements[1].indexOf('(') + 1, + elements[1].lastIndexOf(')'), + ); + const String bigEndianPreambleString = 'þÿ'; + if (_actualText != null && + _actualText!.startsWith(bigEndianPreambleString)) { + _actualText = null; + } + } } } break; @@ -245,6 +272,7 @@ class ImageRenderer { if (_inlayersCount <= 0) { _skipRendering = false; } + _actualText = null; } break; case 'q': @@ -305,8 +333,9 @@ class ImageRenderer { } final GraphicsState? state = _graphicsObject!.save(); _graphicsState!.push(state); - _graphicsObject! - .multiplyTransform(MatrixHelper(a, -b, -c, d, e, -f)); + _graphicsObject!.multiplyTransform( + MatrixHelper(a, -b, -c, d, e, -f), + ); currentLocation = Offset.zero; _textMatrix = true; break; @@ -341,8 +370,8 @@ class ImageRenderer { _textMatrix = false; } if (_renderingMode == 2 && - _recordCollection.recordCollection.length > i + 1 && - _recordCollection.recordCollection[i + 1].operatorName != + pdfRecordCollection.recordCollection.length > i + 1 && + pdfRecordCollection.recordCollection[i + 1].operatorName != 'q') { _renderingMode = 0; } @@ -377,8 +406,9 @@ class ImageRenderer { case "'": { _moveToNextLineWithCurrentTextLeading(); - final MatrixHelper transformMatrix = - _getTextRenderingMatrix(false); + final MatrixHelper transformMatrix = _getTextRenderingMatrix( + false, + ); objects.textMatrixUpdate = transformMatrix; if (_textScaling != 100) { final GraphicsState? state = _graphicsObject!.save(); @@ -386,8 +416,9 @@ class ImageRenderer { _graphicsObject!.scaleTransform(_textScaling! / 100, 1); isScaledText = true; currentLocation = Offset( - currentLocation!.dx / (_textScaling! / 100), - currentLocation!.dy); + currentLocation!.dx / (_textScaling! / 100), + currentLocation!.dy, + ); } _renderTextElementWithLeading(elements!, token); break; @@ -401,8 +432,9 @@ class ImageRenderer { 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; } @@ -410,8 +442,10 @@ class ImageRenderer { { final double dx = double.tryParse(elements![0])!; final double dy = double.tryParse(elements[1])!; - currentLocation = - Offset(currentLocation!.dx + dx, currentLocation!.dy - dy); + currentLocation = Offset( + currentLocation!.dx + dx, + currentLocation!.dy - dy, + ); _moveToNextLine(dx, dy); break; } @@ -448,8 +482,8 @@ class ImageRenderer { if (_skipRendering) { break; } - if (i < _recordCollection.count && - _recordCollection.recordCollection[i + 1].operatorName == + if (i < pdfRecordCollection.count && + pdfRecordCollection.recordCollection[i + 1].operatorName == 'f') { _isNextFill = true; } @@ -468,22 +502,30 @@ class ImageRenderer { } 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, - result.m12, - result.m21, - result.m22, - result.offsetX, - result.offsetY); - MatrixHelper graphicsTransformMatrix = - MatrixHelper(1, 0, 0, 1, 0, 0); + result.m11, + result.m12, + result.m21, + result.m22, + result.offsetX, + result.offsetY, + ); + MatrixHelper graphicsTransformMatrix = MatrixHelper( + 1, + 0, + 0, + 1, + 0, + 0, + ); graphicsTransformMatrix = graphicsTransformMatrix * transformMatrix; _graphicsObject!.transformMatrix = @@ -503,12 +545,13 @@ class ImageRenderer { MatrixHelper _getTextRenderingMatrix(bool isPath) { MatrixHelper mat = MatrixHelper( - fontSize! * (objects.horizontalScaling! / 100), - 0, - 0, - isPath ? fontSize! : (-fontSize!), - 0, - (isPath ? objects.rise! : (fontSize! + objects.rise!)) as double); + fontSize! * (objects.horizontalScaling! / 100), + 0, + 0, + isPath ? fontSize! : (-fontSize!), + 0, + (isPath ? objects.rise! : (fontSize! + objects.rise!)) as double, + ); mat *= textLineMatrix! * currentTransformationMatrix!; return mat; } @@ -559,13 +602,16 @@ class ImageRenderer { _isCurrentPositionChanged = true; 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!); + _currentLocation = Offset( + _currentLocation!.dx, + _currentLocation!.dy + fontSize!, + ); } } @@ -587,12 +633,13 @@ class ImageRenderer { xObjectElement.pageHeight = pageHeight; } final Map result = xObjectElement.renderTextElement( - _graphicsObject, - _resources, - _graphicsState, - graphicsObjects, - currentPageHeight, - xObjectGlyphs); + _graphicsObject, + _resources, + _graphicsState, + graphicsObjects, + currentPageHeight, + xObjectGlyphs, + ); _graphicsState = result['graphicStates'] as GraphicStateCollection?; graphicsObjects = result['objects'] as GraphicObjectDataCollection?; xObjectGlyphs = result['glyphList'] as List?; @@ -609,7 +656,9 @@ class ImageRenderer { } void _renderTextElementWithLeading( - List textElements, String tokenType) { + List textElements, + String tokenType, + ) { String text = textElements.join(); final List retrievedCharCodes = []; if (_resources!.containsKey(currentFont)) { @@ -617,7 +666,6 @@ class ImageRenderer { _resources![currentFont!] as FontStructure; structure.isSameFont = _resources!.isSameFont(); structure.fontSize = fontSize; - if (!structure.isEmbedded && structure.font != null && structure.isStandardCJKFont) { @@ -628,7 +676,14 @@ class ImageRenderer { text = structure.getEncodedText(text, true); } else { text = structure.decodeTextExtraction( - text, _resources!.isSameFont(), retrievedCharCodes); + text, + _resources!.isSameFont(), + retrievedCharCodes, + ); + } + if (_actualText != null && _actualText!.isNotEmpty) { + text = _actualText!; + _actualText = null; } final TextElement element = TextElement(text, documentMatrix); element.fontStyle = structure.fontStyle!; @@ -661,39 +716,48 @@ class ImageRenderer { element.substitutedFontsList = _substitutedFontsList; element.wordSpacing = objects.wordSpacing; element.characterSpacing = objects.characterSpacing; + element.isExtractTextData = isExtractLineCollection; final MatrixHelper tempTextMatrix = MatrixHelper(0, 0, 0, 0, 0, 0); tempTextMatrix.type = MatrixTypes.identity; if (_isCurrentPositionChanged) { _isCurrentPositionChanged = false; _endTextPosition = currentLocation!; final Map renderedResult = element.renderTextElement( - _graphicsObject, - Offset(_endTextPosition.dx, - _endTextPosition.dy + ((-textLeading!) / 4)), - _textScaling, - glyphWidths, - structure.type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix, - retrievedCharCodes); + _graphicsObject, + Offset( + _endTextPosition.dx, + _endTextPosition.dy + ((-textLeading!) / 4), + ), + _textScaling, + glyphWidths, + structure.type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix, + retrievedCharCodes, + ); _textElementWidth = renderedResult['textElementWidth'] as double; textMatrix = renderedResult['tempTextMatrix'] as MatrixHelper; } else { _endTextPosition = Offset( - _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + _endTextPosition.dx + _textElementWidth, + _endTextPosition.dy, + ); final Map renderedResult = element.renderTextElement( - _graphicsObject, - Offset( - _endTextPosition.dx, _endTextPosition.dy + (-textLeading! / 4)), - _textScaling, - glyphWidths, - structure.type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); + _graphicsObject, + Offset( + _endTextPosition.dx, + _endTextPosition.dy + (-textLeading! / 4), + ), + _textScaling, + glyphWidths, + structure.type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix, + ); _textElementWidth = renderedResult['textElementWidth'] as double; textMatrix = renderedResult['tempTextMatrix'] as MatrixHelper; } @@ -708,8 +772,10 @@ class ImageRenderer { _whiteSpace[0].textLineMatrix!.offsetY == element.textLineMatrix!.offsetY) { if (_whiteSpace[0].text.isNotEmpty) { - element.textElementGlyphList - .insert(0, _whiteSpace[0].textElementGlyphList[0]); + element.textElementGlyphList.insert( + 0, + _whiteSpace[0].textElementGlyphList[0], + ); } extractTextElement.add(_whiteSpace[0]); } @@ -727,7 +793,9 @@ class ImageRenderer { } void _renderTextElementWithSpacing( - List textElements, String tokenType) { + List textElements, + String tokenType, + ) { List decodedList = []; Map, String> decodedListCollection = , String>{}; @@ -741,17 +809,22 @@ class ImageRenderer { if (!structure.isEmbedded && structure.isStandardCJKFont && structure.font != null) { - decodedList = - structure.decodeCjkTextExtractionTJ(text, _resources!.isSameFont()); + decodedList = structure.decodeCjkTextExtractionTJ( + text, + _resources!.isSameFont(), + ); for (final String decodedString in decodedList) { decodedListCollection[[]] = decodedString; } } else { - decodedListCollection = - structure.decodeTextExtractionTJ(text, _resources!.isSameFont()); + decodedListCollection = structure.decodeTextExtractionTJ( + text, + _resources!.isSameFont(), + ); } - final List bytes = - utf8.encode(structure.getEncodedText(text, _resources!.isSameFont())); + 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) { @@ -790,6 +863,7 @@ class ImageRenderer { element.pageRotation = pageRotation; element.zoomFactor = zoomFactor; element.substitutedFontsList = _substitutedFontsList; + element.isExtractTextData = isExtractLineCollection; if (structure.flags != null) { element.fontFlag = structure.flags!.value!.toInt(); } @@ -802,20 +876,23 @@ class ImageRenderer { _endTextPosition = currentLocation!; } else { _endTextPosition = Offset( - _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + _endTextPosition.dx + _textElementWidth, + _endTextPosition.dy, + ); } final Map renderedResult = element.renderWithSpacing( - _graphicsObject, - Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize!), - decodedListCollection, - characterSpacings, - _textScaling, - glyphWidths, - structure.type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); + _graphicsObject, + Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize!), + decodedListCollection, + characterSpacings, + _textScaling, + glyphWidths, + structure.type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix, + ); _textElementWidth = renderedResult['textElementWidth'] as double; textMatrix = renderedResult['tempTextMatrix'] as MatrixHelper; if (!structure.isWhiteSpace) { @@ -829,8 +906,10 @@ class ImageRenderer { _whiteSpace[0].textLineMatrix!.offsetY == element.textLineMatrix!.offsetY && _whiteSpace[0].textElementGlyphList.isNotEmpty) { - element.textElementGlyphList - .insert(0, _whiteSpace[0].textElementGlyphList[0]); + element.textElementGlyphList.insert( + 0, + _whiteSpace[0].textElementGlyphList[0], + ); extractTextElement.add(_whiteSpace[0]); } _whiteSpace = []; @@ -863,21 +942,35 @@ class ImageRenderer { } void _setTextMatrix( - double a, double b, double c, double d, double e, double f) { + double a, + double b, + double c, + double d, + double e, + double f, + ) { textMatrix = MatrixHelper(a, b, c, d, e, f); textLineMatrix = textMatrix!.clone(); } MatrixHelper _setMatrix( - double a, double b, double c, double d, double e, double f) { - currentTransformationMatrix = MatrixHelper(a, b, c, d, e, f) * + double a, + double b, + double c, + double d, + double e, + double f, + ) { + currentTransformationMatrix = + MatrixHelper(a, b, c, d, e, f) * graphicsObjects!.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/matrix_helper.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matrix_helper.dart index 1b79b69bd..00a3816c1 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 @@ -5,7 +5,13 @@ import 'dart:ui'; class MatrixHelper { /// internal constructor MatrixHelper( - this.m11, this.m12, this.m21, this.m22, this.offsetX, this.offsetY) { + this.m11, + this.m12, + this.m21, + this.m22, + this.offsetX, + this.offsetY, + ) { type = MatrixTypes.unknown; _checkMatrixType(); } @@ -40,12 +46,13 @@ class MatrixHelper { /// internal property MatrixHelper operator *(MatrixHelper matrix) { return MatrixHelper( - m11 * matrix.m11 + m12 * matrix.m21, - m11 * matrix.m12 + m12 * matrix.m22, - m21 * matrix.m11 + m22 * matrix.m21, - m21 * matrix.m12 + m22 * matrix.m22, - offsetX * matrix.m11 + offsetY * matrix.m21 + matrix.offsetX, - offsetX * matrix.m12 + offsetY * matrix.m22 + matrix.offsetY); + m11 * matrix.m11 + m12 * matrix.m21, + m11 * matrix.m12 + m12 * matrix.m22, + m21 * matrix.m11 + m22 * matrix.m21, + m21 * matrix.m12 + m22 * matrix.m22, + offsetX * matrix.m11 + offsetY * matrix.m21 + matrix.offsetX, + offsetX * matrix.m12 + offsetY * matrix.m22 + matrix.offsetY, + ); } /// internal method @@ -60,15 +67,23 @@ class MatrixHelper { this.offsetX += offsetX; this.offsetY += offsetY; type = _getType( - _getTypeIndex(type) | _getTypeIndex(MatrixTypes.translation)); + _getTypeIndex(type) | _getTypeIndex(MatrixTypes.translation), + ); } } return this; } /// internal method - void setMatrix(double m11, double m12, double m21, double m22, double offsetX, - double offsetY, MatrixTypes type) { + void setMatrix( + double m11, + double m12, + double m21, + double m22, + double offsetX, + double offsetY, + MatrixTypes type, + ) { this.m11 = m11; this.m12 = m12; this.m21 = m21; @@ -89,7 +104,8 @@ class MatrixHelper { } if (offsetX != 0.0 || offsetY != 0.0) { type = _getType( - _getTypeIndex(type) | _getTypeIndex(MatrixTypes.translation)); + _getTypeIndex(type) | _getTypeIndex(MatrixTypes.translation), + ); } if (_getTypeIndex(type) & 3 == _getTypeIndex(MatrixTypes.identity)) { type = MatrixTypes.identity; @@ -122,18 +138,29 @@ class MatrixHelper { return 2; case MatrixTypes.scalingAndTranslation: return 3; - default: + case MatrixTypes.unknown: return 4; } } /// internal method MatrixHelper scale( - double scaleX, double scaleY, double centerX, double centerY) { + double scaleX, + double scaleY, + double centerX, + double centerY, + ) { final MatrixHelper newMatrix = MatrixHelper(scaleX, 0.0, 0.0, scaleY, centerX, centerY) * this; - setMatrix(newMatrix.m11, newMatrix.m12, newMatrix.m21, newMatrix.m22, - newMatrix.offsetX, newMatrix.offsetY, newMatrix.type); + setMatrix( + newMatrix.m11, + newMatrix.m12, + newMatrix.m21, + newMatrix.m22, + newMatrix.offsetX, + newMatrix.offsetY, + newMatrix.type, + ); return this; } @@ -143,29 +170,45 @@ class MatrixHelper { final double num1 = sin(angle); final double num2 = cos(angle); MatrixHelper matrix = MatrixHelper( - num2, - num1, - -num1, - num2, - centerX * (1.0 - num2) + centerY * num1, - centerY * (1.0 - num2) - centerX * num1); + num2, + num1, + -num1, + num2, + centerX * (1.0 - num2) + centerY * num1, + centerY * (1.0 - num2) - centerX * num1, + ); matrix.type = MatrixTypes.unknown; matrix *= this; - setMatrix(matrix.m11, matrix.m12, matrix.m21, matrix.m22, matrix.offsetX, - matrix.offsetY, matrix.type); + setMatrix( + matrix.m11, + matrix.m12, + matrix.m21, + matrix.m22, + matrix.offsetX, + matrix.offsetY, + matrix.type, + ); return this; } /// internal method Offset transform(Offset point) { - return Offset(point.dx * m11 + point.dy * m21 + offsetX, - point.dx * m12 + point.dy * m22 + offsetY); + return Offset( + point.dx * m11 + point.dy * m21 + offsetX, + point.dx * m12 + point.dy * m22 + offsetY, + ); } /// internal method MatrixHelper clone() { - final MatrixHelper result = - MatrixHelper(m11, m12, m21, m22, offsetX, offsetY); + final MatrixHelper result = MatrixHelper( + m11, + m12, + m21, + m22, + offsetX, + offsetY, + ); result.type = type; return result; } @@ -186,5 +229,5 @@ enum MatrixTypes { scalingAndTranslation, /// internal enumerator - unknown + unknown, } 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 c6cc20214..fdb118a08 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 @@ -1,11 +1,14 @@ import '../../../interfaces/pdf_interface.dart'; import '../../graphics/pdf_resources.dart'; +import '../../io/cross_table.dart'; import '../../io/pdf_constants.dart'; +import '../../io/pdf_cross_table.dart'; import '../../pages/enum.dart'; import '../../pages/pdf_page.dart'; import '../../primitives/pdf_dictionary.dart'; import '../../primitives/pdf_name.dart'; import '../../primitives/pdf_reference_holder.dart'; +import '../../primitives/pdf_stream.dart'; import 'font_structure.dart'; import 'xobject_element.dart'; @@ -20,10 +23,14 @@ class PageResourceLoader { PdfPageResources pageResources = PdfPageResources(); double resourceNumber = 0; PdfDictionary? resources = PdfPageHelper.getHelper(page).getResources(); - pageResources = - updatePageResources(pageResources, getFontResources(resources, page)); - pageResources = - updatePageResources(pageResources, getFormResources(resources)); + pageResources = updatePageResources( + pageResources, + getFontResources(resources, page), + ); + pageResources = updatePageResources( + pageResources, + getFormResources(resources, PdfPageHelper.getHelper(page).crossTable), + ); while (resources != null && resources.containsKey(PdfDictionaryProperties.xObject)) { PdfDictionary? tempResources = resources; @@ -31,18 +38,25 @@ class PageResourceLoader { if (resources[PdfDictionaryProperties.xObject] is PdfReferenceHolder) { xobjects = (resources[PdfDictionaryProperties.xObject]! as PdfReferenceHolder) - .object as PdfDictionary?; + .object + as PdfDictionary?; } else { xobjects = resources[PdfDictionaryProperties.xObject] as PdfDictionary?; } resources = xobjects![PdfDictionaryProperties.resources] as PdfDictionary?; - for (final dynamic objValue in xobjects.items!.values) { + final PdfCrossTable? crosstable = + PdfPageHelper.getHelper(page).crossTable; + for (final PdfName? objKey in xobjects.items!.keys) { + final IPdfPrimitive? objValue = xobjects[objKey]; PdfDictionary? xobjectDictionary; - if (objValue is PdfReferenceHolder) { - xobjectDictionary = objValue.object as PdfDictionary?; - } else { - xobjectDictionary = objValue as PdfDictionary?; + PdfReferenceHolder? referenceHolder; + if (objValue is PdfReferenceHolder && + objValue.object is PdfDictionary) { + referenceHolder = objValue; + xobjectDictionary = referenceHolder.object as PdfDictionary?; + } else if (objValue is PdfDictionary) { + xobjectDictionary = objValue; } if (xobjectDictionary != null && xobjectDictionary.containsKey(PdfDictionaryProperties.resources)) { @@ -58,15 +72,35 @@ class PageResourceLoader { continue; } } else { - resources = xobjectDictionary[PdfDictionaryProperties.resources] - as PdfDictionary?; + resources = + xobjectDictionary[PdfDictionaryProperties.resources] + as PdfDictionary?; } + xobjectDictionary = null; if (resources == tempResources) { resources = null; tempResources = null; } pageResources = updatePageResources( - pageResources, getFontResources(resources, page)); + pageResources, + getFontResources(resources, page), + ); + } else { + if (objKey != null && + !pageResources.resources.containsKey(objKey.name) && + crosstable != null && + referenceHolder != null && + referenceHolder.reference != null && + referenceHolder.reference!.objNum != null && + referenceHolder.reference!.objNum! > 0) { + crosstable.items!.remove(referenceHolder.reference!.objNum!); + crosstable.objNumbers.remove(referenceHolder.reference); + if (crosstable.crossTable != null) { + final ObjectInformation? oi = + crosstable.crossTable![referenceHolder.reference!.objNum!]; + oi!.obj = null; + } + } } } } @@ -82,7 +116,10 @@ class PageResourceLoader { } /// internal method - Map getFormResources(PdfDictionary? resourceDictionary) { + Map getFormResources( + PdfDictionary? resourceDictionary, [ + PdfCrossTable? crosstable, + ]) { final Map pageResources = {}; if (resourceDictionary != null && resourceDictionary.containsKey(PdfDictionaryProperties.xObject)) { @@ -100,8 +137,10 @@ class PageResourceLoader { if (xObjects != null) { xObjects.items!.forEach((PdfName? key, IPdfPrimitive? value) { PdfDictionary? xObjectDictionary; + PdfReferenceHolder? referenceHolder; if (value is PdfReferenceHolder && value.object is PdfDictionary) { - xObjectDictionary = value.object as PdfDictionary?; + referenceHolder = value; + xObjectDictionary = referenceHolder.object as PdfDictionary?; } else if (value is PdfDictionary) { xObjectDictionary = value; } @@ -113,12 +152,32 @@ class PageResourceLoader { (subType.name == PdfDictionaryProperties.form || (subType.name != PdfDictionaryProperties.image && !pageResources.containsKey(key!.name)))) { - pageResources[key!.name] = - XObjectElement(xObjectDictionary, key.name); + pageResources[key!.name] = XObjectElement( + xObjectDictionary, + key.name, + ); + } else if (xObjectDictionary is PdfStream) { + if (crosstable != null && + referenceHolder != null && + referenceHolder.reference != null && + referenceHolder.reference!.objNum != null && + referenceHolder.reference!.objNum! > 0) { + crosstable.items!.remove(referenceHolder.reference!.objNum!); + crosstable.objNumbers.remove(referenceHolder.reference); + if (crosstable.crossTable != null) { + final ObjectInformation? oi = + crosstable.crossTable![referenceHolder + .reference! + .objNum!]; + oi!.obj = null; + } + } } } + xObjectDictionary = null; }); } + xObjects = null; } return pageResources; } @@ -126,8 +185,10 @@ class PageResourceLoader { // Collects all the fonts in the page in a dictionary. // Returns dictionary containing font name and the font. /// internal method - Map getFontResources(PdfDictionary? resourceDictionary, - [PdfPage? page]) { + Map getFontResources( + PdfDictionary? resourceDictionary, [ + PdfPage? page, + ]) { final Map pageResources = {}; if (resourceDictionary != null) { IPdfPrimitive? fonts = resourceDictionary[PdfDictionaryProperties.font]; @@ -143,8 +204,10 @@ class PageResourceLoader { fontsDictionary.items!.forEach((PdfName? k, IPdfPrimitive? v) { if (v is PdfReferenceHolder) { if (v.reference != null) { - pageResources[k!.name] = - FontStructure(v.object, v.reference.toString()); + pageResources[k!.name] = FontStructure( + v.object, + v.reference.toString(), + ); } else { pageResources[k!.name] = FontStructure(v.object); } @@ -153,20 +216,24 @@ class PageResourceLoader { pageResources[k!.name] = FontStructure(v); } else { pageResources[k!.name] = FontStructure( - v, (v! as PdfReferenceHolder).reference.toString()); + v, + (v! as PdfReferenceHolder).reference.toString(), + ); } } }); } } if (page != null) { - final IPdfPrimitive? parentPage = PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.parent]; + final IPdfPrimitive? parentPage = + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .parent]; if (parentPage != null) { final IPdfPrimitive? parentRef = (parentPage as PdfReferenceHolder).object; - final PdfResources parentResources = - PdfResources(parentRef as PdfDictionary?); + final PdfResources parentResources = PdfResources( + parentRef as PdfDictionary?, + ); fonts = parentResources[PdfDictionaryProperties.font]; if (fonts != null && fonts is PdfDictionary) { final PdfDictionary fontsDictionary = fonts; @@ -175,7 +242,9 @@ class PageResourceLoader { pageResources[k!.name] = (v as PdfReferenceHolder).object; } pageResources[k!.name] = FontStructure( - v, (v! as PdfReferenceHolder).reference.toString()); + v, + (v! as PdfReferenceHolder).reference.toString(), + ); }); } } @@ -186,7 +255,9 @@ class PageResourceLoader { /// Updates the resources in the page PdfPageResources updatePageResources( - PdfPageResources pageResources, Map objects) { + PdfPageResources pageResources, + Map objects, + ) { objects.forEach((String? k, Object? v) { pageResources.add(k, v); }); 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 732187a79..c2b6448b8 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 @@ -14,6 +14,7 @@ class ContentLexer { int _charPointer = 0; bool _isArtifactContentEnds = false; bool _isContentEnded = false; + String? _tempContent; /// internal field bool isContainsArtifacts = false; @@ -38,7 +39,6 @@ class ContentLexer { return _getNumber(); case '[': - case '(': return _getLiteralString(); @@ -147,7 +147,8 @@ class ContentLexer { _resetToken(); while (true) { final String ch = _consumeValue(); - if (_isWhiteSpace(ch) || _isDelimiter(ch)) { + final String data = String.fromCharCode(int.parse(ch)); + if (_isWhiteSpace(ch) || _isDelimiter(data)) { break; } } @@ -174,8 +175,8 @@ class ContentLexer { } String _consumeValue() { - final String data = String.fromCharCode(int.parse(_currentChar)); - operatorParams!.write(data); + _tempContent = String.fromCharCode(int.parse(_currentChar)); + operatorParams!.write(_tempContent); if (isContainsArtifacts && operatorParams.toString().contains('/Contents') && !_isContentEnded) { @@ -261,10 +262,13 @@ class ContentLexer { PdfToken _getHexadecimalString() { int parentLevel = 0; + bool skipParentLevel = false; String ch = String.fromCharCode(int.parse(_consumeValue())); while (true) { if (ch == '<') { - parentLevel++; + if (!skipParentLevel) { + parentLevel++; + } ch = String.fromCharCode(int.parse(_consumeValue())); } else if (ch == '>' && !_isArtifactContentEnds) { if (parentLevel == 0) { @@ -285,6 +289,16 @@ class ContentLexer { } ch = String.fromCharCode(int.parse(_consumeValue())); } + } else if (operatorParams != null && + operatorParams.toString().contains('ActualText') && + ch == '(') { + skipParentLevel = true; + ch = String.fromCharCode(int.parse(_consumeValue())); + } else if (operatorParams != null && + operatorParams.toString().contains('ActualText') && + ch == ')') { + skipParentLevel = false; + ch = String.fromCharCode(int.parse(_consumeValue())); } else { ch = String.fromCharCode(int.parse(_consumeValue())); } 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 f65892ab9..324f1468a 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 @@ -99,7 +99,7 @@ class ContentParser { 'B*', "'", '"', - 'true' + 'true', ]; // Implementation @@ -114,7 +114,7 @@ class ContentParser { void _parseObject(PdfToken stop) { PdfToken symbol; - final List _operands = []; + final List operands = []; while ((symbol = _getNextToken()) != PdfToken.eof) { if (symbol == stop || symbol == PdfToken.nullType) { @@ -125,37 +125,37 @@ class ContentParser { break; case PdfToken.integer: - _operands.add(_operatorParams.toString()); + operands.add(_operatorParams.toString()); break; case PdfToken.real: - _operands.add(_operatorParams.toString()); + operands.add(_operatorParams.toString()); break; case PdfToken.string: case PdfToken.hexString: case PdfToken.unicodeString: case PdfToken.unicodeHexString: - _operands.add(_operatorParams.toString()); + operands.add(_operatorParams.toString()); break; case PdfToken.name: if (_operatorParams.toString() == '/Artifact') { _lexer.isContainsArtifacts = true; } - _operands.add(_operatorParams.toString()); + operands.add(_operatorParams.toString()); break; case PdfToken.operators: if (_operatorParams.toString() == 'true') { - _operands.add(_operatorParams.toString()); + operands.add(_operatorParams.toString()); } else if (_operatorParams.toString() == 'ID') { - _createRecord(_operands); - _operands.clear(); - _consumeValue(_operands); + _createRecord(operands); + operands.clear(); + _consumeValue(operands); } else { - _createRecord(_operands); - _operands.clear(); + _createRecord(operands); + operands.clear(); } break; @@ -166,6 +166,7 @@ class ContentParser { ArgumentError.value('Error while parsing content'); break; + // ignore: no_default_cases default: break; } @@ -183,18 +184,20 @@ class ContentParser { ArgumentError.value('Invalid operand'); } PdfRecord record; - final List op = operands.isNotEmpty - ? List.filled(operands.length, '', growable: true) - : []; + final List op = + operands.isNotEmpty + ? List.filled(operands.length, '', growable: true) + : []; if (operands.isNotEmpty) { List.copyRange(op, 0, operands); } if (!_isByteOperands) { record = PdfRecord(operand, op); } else { - final List imBytes = inlineImageBytes!.isNotEmpty - ? List.filled(inlineImageBytes.length, 0, growable: true) - : []; + final List imBytes = + inlineImageBytes!.isNotEmpty + ? List.filled(inlineImageBytes.length, 0, growable: true) + : []; if (inlineImageBytes.isNotEmpty) { List.copyRange(imBytes, 0, inlineImageBytes); } @@ -207,7 +210,7 @@ class ContentParser { String currentChar = '0'; String nextChar = '0'; String secondNextChar = '0'; - final List _inlineImageBytes = []; + final List inlineImageBytes = []; if (_conformanceEnabled) { List thirdChar; while (true) { @@ -241,53 +244,57 @@ class ContentParser { snc == '\r') && thirdChar.isNotEmpty) { if (String.fromCharCode( - int.parse(thirdChar[thirdChar.length - 1])) == + int.parse(thirdChar[thirdChar.length - 1]), + ) == 'Q' || thirdChar[thirdChar.length - 1] == '65535' || String.fromCharCode( - int.parse(thirdChar[thirdChar.length - 1])) == + int.parse(thirdChar[thirdChar.length - 1]), + ) == 'S') { _operatorParams!.clear(); - _operatorParams! - .write(String.fromCharCode(int.parse(currentChar))); - _operatorParams! - .write(String.fromCharCode(int.parse(nextChar))); + _operatorParams!.write( + String.fromCharCode(int.parse(currentChar)), + ); + _operatorParams!.write( + String.fromCharCode(int.parse(nextChar)), + ); _isByteOperands = true; - _createRecord(operands, _inlineImageBytes); + _createRecord(operands, inlineImageBytes); _isByteOperands = false; - _inlineImageBytes.clear(); + inlineImageBytes.clear(); nextChar = _lexer.getNextInlineChar(); break; } else { - _inlineImageBytes.add(int.parse(currentChar)); - _inlineImageBytes.add(int.parse(nextChar)); - _inlineImageBytes.add(int.parse(secondNextChar)); + inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(nextChar)); + inlineImageBytes.add(int.parse(secondNextChar)); if (thirdChar.length > 1) { thirdChar.removeAt(0); thirdChar.removeAt(thirdChar.length - 1); } for (final String c in thirdChar) { - _inlineImageBytes.add(int.parse(c)); + inlineImageBytes.add(int.parse(c)); } currentChar = _lexer.getNextCharforInlineStream(); } } else { - _inlineImageBytes.add(int.parse(currentChar)); - _inlineImageBytes.add(int.parse(nextChar)); - _inlineImageBytes.add(int.parse(secondNextChar)); + inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(nextChar)); + inlineImageBytes.add(int.parse(secondNextChar)); if (thirdChar.isNotEmpty) { for (final String tc in thirdChar) { - _inlineImageBytes.add(int.parse(tc)); + inlineImageBytes.add(int.parse(tc)); } } currentChar = _lexer.getNextCharforInlineStream(); } } else { - _inlineImageBytes.add(int.parse(currentChar)); - _inlineImageBytes.add(int.parse(nextChar)); + inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(nextChar)); } } else { - _inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(currentChar)); } thirdChar.clear(); } @@ -319,30 +326,32 @@ class ContentParser { thirdChar == '65535' || String.fromCharCode(int.parse(thirdChar)) == 'S') { _operatorParams!.clear(); - _operatorParams! - .write(String.fromCharCode(int.parse(currentChar))); - _operatorParams! - .write(String.fromCharCode(int.parse(nextChar))); + _operatorParams!.write( + String.fromCharCode(int.parse(currentChar)), + ); + _operatorParams!.write( + String.fromCharCode(int.parse(nextChar)), + ); _isByteOperands = true; - _createRecord(operands, _inlineImageBytes); + _createRecord(operands, inlineImageBytes); _isByteOperands = false; - _inlineImageBytes.clear(); + inlineImageBytes.clear(); nextChar = _lexer.getNextInlineChar(); break; } } else { - _inlineImageBytes.add(int.parse(currentChar)); - _inlineImageBytes.add(int.parse(nextChar)); - _inlineImageBytes.add(int.parse(secondNextChar)); - _inlineImageBytes.add(int.parse(thirdChar)); + inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(nextChar)); + inlineImageBytes.add(int.parse(secondNextChar)); + inlineImageBytes.add(int.parse(thirdChar)); currentChar = _lexer.getNextCharforInlineStream(); } } else { - _inlineImageBytes.add(int.parse(currentChar)); - _inlineImageBytes.add(int.parse(nextChar)); + inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(nextChar)); } } else { - _inlineImageBytes.add(int.parse(currentChar)); + inlineImageBytes.add(int.parse(currentChar)); } } } 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 33a14455e..7472e22f1 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 @@ -48,8 +48,11 @@ class PdfTextExtractor { /// ``` PdfTextExtractor(PdfDocument document) { if (!PdfDocumentHelper.getHelper(document).isLoadedDocument) { - ArgumentError.value(document, 'document', - 'document instance is not a loaded PDF document'); + ArgumentError.value( + document, + 'document', + 'document instance is not a loaded PDF document', + ); } _document = document; _initialize(); @@ -67,13 +70,18 @@ class PdfTextExtractor { double _characterSpacing = 0; double _wordSpacing = 0; MatrixHelper? _textLineMatrix; - MatrixHelper? _textMatrix; - MatrixHelper? _currentTextMatrix; + late MatrixHelper? _textMatrix; + late MatrixHelper? _currentTextMatrix; Rect? _tempBoundingRectangle; bool _hasLeading = false; + bool _hasET = false; late MatrixHelper _currentTransformationMatrix; bool _hasBDC = false; Bidi? _bidiInstance; + MatrixHelper? _initialTransform; + String? _actualText; + + //Properties Bidi get _bidi { _bidiInstance ??= Bidi(); return _bidiInstance!; @@ -98,8 +106,11 @@ 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); } @@ -150,10 +161,12 @@ class PdfTextExtractor { /// //Dispose the document. /// document.dispose(); /// ``` - List findText(List searchString, - {int? startPageIndex, - int? endPageIndex, - TextSearchOption? searchOption}) { + List findText( + List searchString, { + int? startPageIndex, + int? endPageIndex, + TextSearchOption? searchOption, + }) { return _findText(searchString, startPageIndex, endPageIndex, searchOption); } @@ -177,8 +190,11 @@ class PdfTextExtractor { String _extractText(int? startPageIndex, int? endPageIndex) { if (startPageIndex == null) { if (endPageIndex != null) { - throw ArgumentError.value(endPageIndex, '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); } @@ -196,8 +212,11 @@ class PdfTextExtractor { List _extractTextLines(int? startPageIndex, int? endPageIndex) { if (startPageIndex == null) { if (endPageIndex != null) { - throw ArgumentError.value(endPageIndex, '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); } @@ -212,26 +231,44 @@ 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, '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); + 0, + _document.pages.count - 1, + searchString, + searchOption, + ); } } else if (endPageIndex == null) { _checkPageNumber(startPageIndex); _currentPageIndex = startPageIndex; return _searchInBackground( - _document.pages[startPageIndex], searchString, searchOption); + _document.pages[startPageIndex], + searchString, + searchOption, + ); } else { _checkPageNumber(startPageIndex); _checkPageNumber(endPageIndex); return _findTextFromRange( - startPageIndex, endPageIndex, searchString, searchOption); + startPageIndex, + endPageIndex, + searchString, + searchOption, + ); } } @@ -239,13 +276,21 @@ 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 && + (!_isLayout || (_isLayout && !resultText.endsWith('\n'))) + ? '\r\n' + : '') + + text; } return resultText; } List _extractTextLineFromRange( - int startPageIndex, int endPageIndex) { + int startPageIndex, + int endPageIndex, + ) { final List result = []; for (int i = startPageIndex; i <= endPageIndex; i++) { _currentPageIndex = i; @@ -257,13 +302,20 @@ class PdfTextExtractor { return result; } - List _findTextFromRange(int startPageIndex, int endPageIndex, - List searchString, TextSearchOption? searchOption) { + List _findTextFromRange( + int startPageIndex, + int endPageIndex, + List searchString, + TextSearchOption? searchOption, + ) { final List result = []; for (int i = startPageIndex; i <= endPageIndex; i++) { _currentPageIndex = i; - final List textLines = - _searchInBackground(_document.pages[i], searchString, searchOption); + final List textLines = _searchInBackground( + _document.pages[i], + searchString, + searchOption, + ); if (textLines.isNotEmpty) { result.addAll(textLines); } @@ -278,18 +330,43 @@ class PdfTextExtractor { final bool isChanged = _checkPageDictionary(page); final bool isContentChanged = _checkContentArray(page); final PdfRecordCollection? recordCollection = _getRecordCollection(page); - final PdfPageResources pageResources = - _resourceLoader.getPageResources(page); - String resultantText = _isLayout - ? _renderTextAsLayout(recordCollection, pageResources) - : _renderText(recordCollection, pageResources); + PdfPageResources? pageResources = _resourceLoader.getPageResources(page); + String resultantText; + if (_isLayout) { + try { + _initialTransform = MatrixHelper( + 1.3333333333333333, + 0, + 0, + -1.3333333333333333, + 0, + page.size.height * 1.3333333333333333, + ); + } catch (e) { + _initialTransform = MatrixHelper.identity; + } + resultantText = _renderTextAsLayout(recordCollection, pageResources); + } else { + resultantText = _renderText(recordCollection, pageResources); + } if (recordCollection != null) { recordCollection.recordCollection.clear(); } - pageResources.resources.clear(); + if (pageResources.resources.isNotEmpty) { + pageResources.resources.forEach((String? key, Object? value) { + if (value != null && value is FontStructure) { + value.dispose(); + } + }); + pageResources.resources.clear(); + } if (pageResources.fontCollection.isNotEmpty) { + pageResources.fontCollection.forEach((String? key, FontStructure value) { + value.dispose(); + }); pageResources.fontCollection.clear(); } + pageResources = null; if (resultantText != '') { resultantText = _skipEscapeSequence(resultantText); } @@ -307,10 +384,14 @@ class PdfTextExtractor { final bool isChanged = _checkPageDictionary(pdfPage); final bool isContentChanged = _checkContentArray(pdfPage); final PdfRecordCollection? recordCollection = _getRecordCollection(pdfPage); - final PdfPageResources pageResources = - _resourceLoader.getPageResources(pdfPage); - final ImageRenderer renderer = ImageRenderer(recordCollection, - pageResources, pdfPage.size.height * 1.3333333333333333); + final PdfPageResources pageResources = _resourceLoader.getPageResources( + pdfPage, + ); + final ImageRenderer renderer = ImageRenderer( + recordCollection, + pageResources, + pdfPage.size.height * 1.3333333333333333, + ); renderer.isExtractLineCollection = true; final double rotation = _getPageRotation(pdfPage); renderer.pageRotation = rotation; @@ -342,105 +423,163 @@ class PdfTextExtractor { } else { yPos = tempGlyph.boundingRect.top; } + if (k == 0) { + offsetY = yPos.toInt(); + } if ((i != 0 && yPos.toInt() != offsetY && renderer.extractTextElement[k].renderedText != '') || (i == renderer.imageRenderGlyphList.length - 1)) { offsetY = yPos.toInt(); if (textLine.wordCollection.isNotEmpty) { - result.add(_prepareTextLine( - textLine, renderer, lineStartIndex, i, rotation)); + result.add( + _prepareTextLine( + textLine, + renderer, + lineStartIndex, + i, + rotation, + ), + ); } lineStartIndex = i; textLine = TextLineHelper.initialize(); } final TextElement textElement = renderer.extractTextElement[k]; - final List words = textElement.renderedText.split(' '); + final List words = _splitRenderedText( + textElement.renderedText, + renderer.imageRenderGlyphList, + i, + ); textElement.text = ' '; TextWord? textwords; List glyphs = []; for (int x = 0; x < words.length; x++) { if (pagestring.contains(words[x]) && words[x].isNotEmpty) { glyphs = []; - String tempText = ''; int lastIndex = i; - for (int m = i; m < i + words[x].length; m++) { + for ( + int m = i; + m < i + words[x].length && + m < renderer.imageRenderGlyphList.length; + m++ + ) { final Glyph tempGlyph = renderer.imageRenderGlyphList[m]; final Rect tempBounds = tempGlyph.boundingRect; - final Rect glyphBounds = Rect.fromLTRB(tempBounds.left, - tempBounds.top, tempBounds.right, tempBounds.bottom); + final Rect glyphBounds = Rect.fromLTRB( + tempBounds.left, + tempBounds.top, + tempBounds.right, + tempBounds.bottom, + ); final TextGlyph textGlyph = TextGlyphHelper.initialize( - tempGlyph.toUnicode, - textElement.fontName, - textElement.fontStyle, - _calculateBounds(glyphBounds), - textElement.fontSize, - tempGlyph.isRotated); - tempText += textGlyph.text; + tempGlyph.toUnicode, + textElement.fontName, + textElement.fontStyle, + _calculateBounds(glyphBounds), + textElement.fontSize, + tempGlyph.isRotated, + ); glyphs.add(textGlyph); lastIndex = m; - if (words[x] == tempText) { - break; - } } Rect? wordBound; dx = renderer.imageRenderGlyphList[i].boundingRect.left; dy = renderer.imageRenderGlyphList[i].boundingRect.top; if (hasRotation && rotation == 270) { width = renderer.imageRenderGlyphList[i].boundingRect.width; - height = renderer - .imageRenderGlyphList[lastIndex].boundingRect.bottom; + height = + renderer + .imageRenderGlyphList[lastIndex] + .boundingRect + .bottom; wordBound = Rect.fromLTWH(dx, dy, width, height - dy); } else { height = renderer.imageRenderGlyphList[i].boundingRect.height; if (dx > renderer - .imageRenderGlyphList[lastIndex].boundingRect.left) { - width = (dx - - renderer.imageRenderGlyphList[lastIndex].boundingRect + .imageRenderGlyphList[lastIndex] + .boundingRect + .left) { + width = + (dx - + renderer + .imageRenderGlyphList[lastIndex] + .boundingRect .left) + renderer - .imageRenderGlyphList[lastIndex].boundingRect.width; + .imageRenderGlyphList[lastIndex] + .boundingRect + .width; } else { - width = (renderer - .imageRenderGlyphList[lastIndex].boundingRect.left) + + width = + (renderer + .imageRenderGlyphList[lastIndex] + .boundingRect + .left) + renderer - .imageRenderGlyphList[lastIndex].boundingRect.width; + .imageRenderGlyphList[lastIndex] + .boundingRect + .width; } wordBound = Rect.fromLTWH(dx, dy, width - dx, height); } i = lastIndex + 1; String word = words[x]; if (word.isNotEmpty && bidi.Bidi.hasAnyRtl(word)) { - word = _bidi.getLogicalToVisualString(word, true)['rtlText'] - as String; + word = + _bidi.getLogicalToVisualString(word, true)['rtlText'] + as String; } textwords = TextWordHelper.initialize( - word, - textElement.fontName, - textElement.fontStyle, - glyphs, - wordBound, - textElement.fontSize); + word, + textElement.fontName, + textElement.fontStyle, + glyphs, + _calculateBounds(wordBound), + textElement.fontSize, + ); textLine.wordCollection.add(textwords); } textElement.text = words[x]; if (textElement.text != '') { - if (x < words.length - 1) { + if (x < words.length - 1 && + i <= renderer.imageRenderGlyphList.length - 1 && + renderer.imageRenderGlyphList[i].toUnicode == ' ') { if (i != 0) { - final Map tempResult = _addSpace(textwords, - renderer, textElement, i, dx, dy, width, height); + final Map tempResult = _addSpace( + textwords, + renderer, + textElement, + i, + dx, + dy, + width, + height, + ); dx = tempResult['dx'] as double?; dy = tempResult['dy'] as double?; width = tempResult['width'] as double?; height = tempResult['height'] as double?; + final Rect wordBound = Rect.fromLTWH( + dx!, + dy!, + width!, + height!, + ); + tempResult['word'].bounds = _calculateBounds(wordBound); textLine.wordCollection.add(tempResult['word']); } i = i + 1; } if (x < words.length - 1 && i <= renderer.imageRenderGlyphList.length - 1 && - renderer.imageRenderGlyphList[i].toUnicode == ' ') { + renderer.imageRenderGlyphList[i].toUnicode == ' ' && + ((renderer.imageRenderGlyphList[i].isRotated && + (rotation == 270 || rotation == 90)) || + renderer.imageRenderGlyphList[i].boundingRect.top + .toInt() == + offsetY)) { i = i + 1; } } else { @@ -448,12 +587,27 @@ class PdfTextExtractor { if (x != words.length - 1 && renderer.imageRenderGlyphList[i].toUnicode == ' ') { if (i != 0) { - final Map tempResult = _addSpace(textwords, - renderer, textElement, i, dx, dy, width, height); + final Map tempResult = _addSpace( + textwords, + renderer, + textElement, + i, + dx, + dy, + width, + height, + ); dx = tempResult['dx'] as double?; dy = tempResult['dy'] as double?; width = tempResult['width'] as double?; height = tempResult['height'] as double?; + final Rect wordBound = Rect.fromLTWH( + dx!, + dy!, + width!, + height!, + ); + tempResult['word'].bounds = _calculateBounds(wordBound); textLine.wordCollection.add(tempResult['word']); } i = i + 1; @@ -469,8 +623,15 @@ class PdfTextExtractor { if (renderer.extractTextElement.isNotEmpty && k == 0) { offsetY = yPos.toInt(); if (textLine.wordCollection.isNotEmpty) { - result.add(_prepareTextLine( - textLine, renderer, lineStartIndex, i, rotation)); + result.add( + _prepareTextLine( + textLine, + renderer, + lineStartIndex, + i, + rotation, + ), + ); } lineStartIndex = i; textLine = TextLineHelper.initialize(); @@ -485,13 +646,15 @@ class PdfTextExtractor { element.renderedText != '' && element.renderedText != ' ') { result.add( - _prepareTextLine(textLine, renderer, lineStartIndex, i, rotation)); + _prepareTextLine(textLine, renderer, lineStartIndex, i, rotation), + ); textLine = TextLineHelper.initialize(); } } if (textLine.wordCollection.isNotEmpty && !result.contains(textLine)) { result.add( - _prepareTextLine(textLine, renderer, lineStartIndex, i, rotation)); + _prepareTextLine(textLine, renderer, lineStartIndex, i, rotation), + ); textLine = TextLineHelper.initialize(); } PdfPageHelper.getHelper(pdfPage).contents.changed = isContentChanged; @@ -500,28 +663,102 @@ class PdfTextExtractor { return result; } + //Splits the words in a rendered text and returns the list of words. + List _splitRenderedText(String text, List glyphs, int index) { + List words = []; + String tempString = ''; + Rect? previousRect; + bool isSplit = false; + if (text.isNotEmpty && + !text.codeUnits.any((int element) => element > 255) && + text[0] == glyphs[index].toUnicode) { + for (int i = index, j = 0; j < text.length; i++, j++) { + try { + final Glyph textGlyph = glyphs[i]; + if (text[j] == ' ') { + if (tempString.isNotEmpty) { + words.add(tempString); + } + if (j == 0 || (j != text.length - 1 && text[j - 1] == ' ')) { + words.add(''); + } + if (j + 1 >= text.length) { + words.add(''); + } + previousRect = null; + tempString = ''; + continue; + } + final Rect currentRect = textGlyph.boundingRect; + if (previousRect != null) { + if ((previousRect.left + previousRect.width - currentRect.left) + .abs() > + 1.5) { + isSplit = true; + } else { + tempString += text[j]; + } + } else { + tempString += text[j]; + } + if (isSplit) { + words.add(tempString); + isSplit = false; + previousRect = null; + tempString = ''; + i--; + j--; + } else { + previousRect = currentRect; + } + } catch (e) { + words = text.split(' '); + return words; + } + } + if (tempString.isNotEmpty) { + words.add(tempString); + } + } else { + words = text.split(' '); + } + return words; + } + List _searchInBackground( - PdfPage page, List searchString, TextSearchOption? searchOption) { + PdfPage page, + List searchString, + TextSearchOption? searchOption, + ) { final List result = []; _isLayout = true; final String pageText = _getText(page); _isLayout = false; final bool containsRtl = bidi.Bidi.hasAnyRtl(pageText); if (pageText != '') { - bool isMatched = - _isMatchFound(searchString, searchOption, pageText, containsRtl); + bool isMatched = _isMatchFound( + searchString, + searchOption, + pageText, + containsRtl, + ); if (isMatched || containsRtl) { _currentPage = page; _fontSize = 0; PdfPageHelper.getHelper(page).isTextExtraction = true; final bool isChanged = _checkPageDictionary(page); final bool isContentChanged = _checkContentArray(page); - final PdfRecordCollection? recordCollection = - _getRecordCollection(page); - final PdfPageResources pageResources = - _resourceLoader.getPageResources(page); - final ImageRenderer renderer = ImageRenderer(recordCollection, - pageResources, page.size.height * 1.3333333333333333); + final PdfRecordCollection? recordCollection = _getRecordCollection( + page, + ); + final PdfPageResources pageResources = _resourceLoader.getPageResources( + page, + ); + final ImageRenderer renderer = ImageRenderer( + recordCollection, + pageResources, + page.size.height * 1.3333333333333333, + ); renderer.pageRotation = _getPageRotation(page); renderer.renderAsImage(); String renderedString = ''; @@ -536,8 +773,8 @@ class PdfTextExtractor { } List? visualOrderedTextGlyph; if (containsRtl) { - final Map visualOrderResult = - _bidi.getLogicalToVisualString(renderedString, true); + final Map visualOrderResult = _bidi + .getLogicalToVisualString(renderedString, true); renderedString = visualOrderResult['rtlText'] as String; final List visualOrderIndexes = visualOrderResult['orderedIndexes'] as List; @@ -549,11 +786,15 @@ class PdfTextExtractor { if (newIndex + 1 < visualOrderIndexes.length && visualOrderIndexes[newIndex + 1] == key + 1) { visualOrderIndexes.removeRange( - newIndex + 1, newIndex + value); + newIndex + 1, + newIndex + value, + ); } else if (newIndex - 1 > -1 && visualOrderIndexes[newIndex - 1] == key + 1) { visualOrderIndexes.removeRange( - newIndex - value + 1, newIndex); + newIndex - value + 1, + newIndex, + ); } } }); @@ -576,16 +817,17 @@ class PdfTextExtractor { } } if (index < glyphListLength) { - visualOrderedTextGlyph - .add(renderer.imageRenderGlyphList[index]); + visualOrderedTextGlyph.add( + renderer.imageRenderGlyphList[index], + ); } } } final List renderedStringCollection = [ - renderedString + renderedString, ]; final List?> textGlyphCollection = ?>[ - visualOrderedTextGlyph ?? renderer.imageRenderGlyphList + visualOrderedTextGlyph ?? renderer.imageRenderGlyphList, ]; if (renderedString.contains(' ') || renderedString.contains('\t\t')) { @@ -600,18 +842,21 @@ class PdfTextExtractor { spaceStartIndex = innerSpaceTrimmedString.indexOf('\t\t'); } if (spaceStartIndex >= 0) { - final List stringList = - innerSpaceTrimmedString.split(''); + final List stringList = innerSpaceTrimmedString.split( + '', + ); int i = spaceStartIndex + 2; for (i = spaceStartIndex + 2; i < stringList.length; i++) { if (stringList[i] == ' ' || stringList[i] == '\t') { continue; - } else if (i == stringList.length - 1) { + } else if (i >= stringList.length) { break; } else { stringList.removeRange(spaceStartIndex + 1, i); innerSpaceTrimmedTextGlyph.removeRange( - spaceStartIndex + 1, i); + spaceStartIndex + 1, + i, + ); break; } } @@ -627,7 +872,11 @@ class PdfTextExtractor { if (!isMatched) { for (final String renderedText in renderedStringCollection) { isMatched = _isMatchFound( - searchString, searchOption, renderedText, containsRtl); + searchString, + searchOption, + renderedText, + containsRtl, + ); if (isMatched) { break; } @@ -635,13 +884,14 @@ class PdfTextExtractor { } if (isMatched) { _getMatchedItems( - page, - searchString, - searchOption, - renderedStringCollection, - combinedGlyphLength, - textGlyphCollection, - result); + page, + searchString, + searchOption, + renderedStringCollection, + combinedGlyphLength, + textGlyphCollection, + result, + ); } } PdfPageHelper.getHelper(page).contents.changed = isContentChanged; @@ -652,8 +902,12 @@ class PdfTextExtractor { return result; } - bool _isMatchFound(List searchString, TextSearchOption? searchOption, - String pageText, bool containsRtl) { + bool _isMatchFound( + List searchString, + TextSearchOption? searchOption, + String pageText, + bool containsRtl, + ) { ArabicShapeRenderer? shapeRenderer; if (containsRtl) { shapeRenderer = ArabicShapeRenderer(); @@ -700,9 +954,9 @@ class PdfTextExtractor { if (!tempTermCollection.contains(tempRenderedTerm)) { tempTermCollection.add(tempRenderedTerm); if (!searchString.contains(tempRenderedTerm) && - pageText - .toLowerCase() - .contains(tempRenderedTerm.toLowerCase())) { + pageText.toLowerCase().contains( + tempRenderedTerm.toLowerCase(), + )) { searchString.add(tempRenderedTerm); isMatched = true; } @@ -723,13 +977,14 @@ class PdfTextExtractor { } void _getMatchedItems( - PdfPage page, - List searchString, - TextSearchOption? searchOption, - List renderedStringCollection, - Map combinedGlyphLength, - List?> glyphListCollection, - List result) { + PdfPage page, + List searchString, + TextSearchOption? searchOption, + List renderedStringCollection, + Map combinedGlyphLength, + List?> glyphListCollection, + List result, + ) { if (renderedStringCollection.isNotEmpty && renderedStringCollection[0] != '') { final Map> mappedIndexes = >{}; @@ -746,36 +1001,45 @@ class PdfTextExtractor { for (final String term in searchString) { if (term != '' && !mappedIndexes.containsKey(term)) { final List indexes = []; - final String currentText = (searchOption != null && - (searchOption == TextSearchOption.caseSensitive || - searchOption == TextSearchOption.both)) - ? term - : term.toLowerCase(); + final String currentText = + (searchOption != null && + (searchOption == TextSearchOption.caseSensitive || + searchOption == TextSearchOption.both)) + ? term + : term.toLowerCase(); int startIndex = 0; final int length = currentText.length; for (int i = 0; i < renderedStringCollection.length; i++) { while (startIndex <= textLengthCollection[i] && renderedStringCollection[i].contains(currentText, startIndex)) { - int index = - renderedStringCollection[i].indexOf(currentText, startIndex); + int index = renderedStringCollection[i].indexOf( + currentText, + startIndex, + ); final int tempIndex = index; if (searchOption != null && (searchOption == TextSearchOption.wholeWords || searchOption == TextSearchOption.both)) { if (index == 0 || _hasEscapeCharacter( - renderedStringCollection[i][index - 1])) { + renderedStringCollection[i][index - 1], + )) { if (index + length == textLengthCollection[i]) { if (combinedGlyphLength.isNotEmpty) { - index = - _checkCombinedTextIndex(index, combinedGlyphLength); + index = _checkCombinedTextIndex( + index, + combinedGlyphLength, + ); } indexes.add(index); } else if (_hasEscapeCharacter( - renderedStringCollection[i][index + length])) { + renderedStringCollection[i][index + length], + )) { if (combinedGlyphLength.isNotEmpty) { - index = - _checkCombinedTextIndex(index, combinedGlyphLength); + index = _checkCombinedTextIndex( + index, + combinedGlyphLength, + ); } indexes.add(index); } @@ -793,13 +1057,15 @@ class PdfTextExtractor { if (glyphListCollection[i] != null && index < glyphListCollection[i]!.length) { final List rect = _calculatedTextounds( - glyphListCollection[i]!, - term, - index, - page, - bidi.Bidi.hasAnyRtl(term)); - result.add(MatchedItemHelper.initialize( - term, rect, _currentPageIndex)); + glyphListCollection[i]!, + term, + index, + page, + bidi.Bidi.hasAnyRtl(term), + ); + result.add( + MatchedItemHelper.initialize(term, rect, _currentPageIndex), + ); } } break; @@ -811,7 +1077,9 @@ class PdfTextExtractor { } int _checkCombinedTextIndex( - int textIndex, Map combinedGlyphLength) { + int textIndex, + Map combinedGlyphLength, + ) { int adjustableLength = 0; combinedGlyphLength.forEach((int index, int length) { if (index < textIndex) { @@ -823,15 +1091,17 @@ class PdfTextExtractor { PdfRecordCollection? _getRecordCollection(PdfPage page) { PdfRecordCollection? recordCollection; - final List? combinedData = - PdfPageLayerCollectionHelper.getHelper(page.layers) - .combineContent(true); + List? combinedData = PdfPageLayerCollectionHelper.getHelper( + page.layers, + ).combineContent(true); if (combinedData != null) { final ContentParser parser = ContentParser(combinedData); parser.isTextExtractionProcess = true; recordCollection = parser.readContent(); parser.isTextExtractionProcess = false; + combinedData.clear(); } + combinedData = null; return recordCollection; } @@ -842,11 +1112,12 @@ class PdfTextExtractor { bool _checkContentArray(PdfPage page) { bool isContentChanged = false; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.contents)) { - final IPdfPrimitive? contents = PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.contents]; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? contents = + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .contents]; if (contents is PdfReferenceHolder) { final PdfReferenceHolder holder = contents; final IPdfPrimitive? primitive = holder.object; @@ -863,51 +1134,65 @@ class PdfTextExtractor { } Map _addSpace( - TextWord? textwords, - ImageRenderer renderer, - TextElement textElement, - int i, - double? dx, - double? dy, - double? width, - double? height) { + TextWord? textwords, + ImageRenderer renderer, + TextElement textElement, + int i, + 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); + tempBounds.left, + tempBounds.top, + tempBounds.width, + tempBounds.height, + ); final TextGlyph textGlyph = TextGlyphHelper.initialize( - renderer.imageRenderGlyphList[i].toUnicode, - textElement.fontName, - textElement.fontStyle, - _calculateBounds(glyphBounds), - textElement.fontSize, - renderer.imageRenderGlyphList[i].isRotated); + renderer.imageRenderGlyphList[i].toUnicode, + textElement.fontName, + textElement.fontStyle, + _calculateBounds(glyphBounds), + textElement.fontSize, + renderer.imageRenderGlyphList[i].isRotated, + ); dx = renderer.imageRenderGlyphList[i].boundingRect.left; dy = renderer.imageRenderGlyphList[i].boundingRect.top; height = renderer.imageRenderGlyphList[i].boundingRect.height; if (dx > renderer.imageRenderGlyphList[i].boundingRect.left) { - width = (dx - renderer.imageRenderGlyphList[i].boundingRect.left) + + width = + (dx - renderer.imageRenderGlyphList[i].boundingRect.left) + renderer.imageRenderGlyphList[i].boundingRect.width; } else { - width = (renderer.imageRenderGlyphList[i].boundingRect.left - dx) + + width = + (renderer.imageRenderGlyphList[i].boundingRect.left - dx) + renderer.imageRenderGlyphList[i].boundingRect.width; } return { 'word': TextWordHelper.initialize( - ' ', - textElement.fontName, - textElement.fontStyle, - [textGlyph], - Rect.fromLTWH(dx, dy, width, height), - textElement.fontSize), + ' ', + textElement.fontName, + textElement.fontStyle, + [textGlyph], + Rect.fromLTWH(dx, dy, width, height), + textElement.fontSize, + ), 'dx': dx, 'dy': dy, 'width': width, - 'height': height + 'height': height, }; } List _calculatedTextounds( - List glyphs, String text, int index, PdfPage page, bool isRTL) { + List glyphs, + String text, + int index, + PdfPage page, + bool isRTL, + ) { Rect textBounds = Rect.zero; if (!isRTL) { textBounds = _getLTRBounds(glyphs, text, index, page); @@ -942,11 +1227,13 @@ class PdfTextExtractor { } textCollection.forEach((int key, String value) { if (bidi.Bidi.hasAnyRtl(value)) { - boundsCollection - .add(_calculateBounds(_getRTLBounds(glyphs, value, key, page))); + boundsCollection.add( + _calculateBounds(_getRTLBounds(glyphs, value, key, page)), + ); } else { - boundsCollection - .add(_calculateBounds(_getLTRBounds(glyphs, value, key, page))); + boundsCollection.add( + _calculateBounds(_getLTRBounds(glyphs, value, key, page)), + ); } }); return boundsCollection; @@ -1081,7 +1368,7 @@ class PdfTextExtractor { Rect _calculateBounds(Rect bounds) { if (_currentPage != null) { - if (PdfPageHelper.getHelper(_currentPage!).cropBox != Rect.zero && + if (!PdfPageHelper.getHelper(_currentPage!).cropBox.isEmpty && PdfPageHelper.getHelper(_currentPage!).cropBox != PdfPageHelper.getHelper(_currentPage!).mediaBox) { final double x = @@ -1089,13 +1376,27 @@ class PdfTextExtractor { final double y = bounds.top + PdfPageHelper.getHelper(_currentPage!).cropBox.top; return Rect.fromLTWH(x, y, bounds.width, bounds.height); + } else if (!PdfPageHelper.getHelper(_currentPage!).mediaBox.isEmpty && + (PdfPageHelper.getHelper(_currentPage!).mediaBox.left != 0 || + PdfPageHelper.getHelper(_currentPage!).mediaBox.top != 0)) { + final double cLeft = + PdfPageHelper.getHelper(_currentPage!).mediaBox.left; + final double ctop = PdfPageHelper.getHelper(_currentPage!).mediaBox.top; + final double x = bounds.left - cLeft; + final double y = bounds.top + ctop; + return Rect.fromLTWH(x, y, bounds.width, bounds.height); } } return bounds; } - TextLine _prepareTextLine(TextLine textLine, ImageRenderer renderer, - int lineStartIndex, int glyphIndex, double rotation) { + TextLine _prepareTextLine( + TextLine textLine, + ImageRenderer renderer, + int lineStartIndex, + int glyphIndex, + double rotation, + ) { bool isSameFontName = true; bool isSameFontSize = true; bool isSameFontStyle = true; @@ -1106,42 +1407,52 @@ class PdfTextExtractor { if (rotation == 270 && renderer.imageRenderGlyphList[lineStartIndex].isRotated) { textLine.bounds = Rect.fromLTWH( - renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, - renderer.imageRenderGlyphList[lineStartIndex].boundingRect.top, - renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.width, - renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.bottom - - renderer.imageRenderGlyphList[lineStartIndex].boundingRect.top); + renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, + renderer.imageRenderGlyphList[lineStartIndex].boundingRect.top, + renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.width, + renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.bottom - + renderer.imageRenderGlyphList[lineStartIndex].boundingRect.top, + ); } else { textLine.bounds = Rect.fromLTWH( - renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, - renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.top, - renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.right - - renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, - renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.height); + renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, + renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.top, + renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.right - + renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, + renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.height, + ); } textLine.bounds = _calculateBounds(textLine.bounds); - for (int i = lineStartIndex; i < glyphIndex; i++) { - final Glyph glyph = renderer.imageRenderGlyphList[i]; + Rect? prevBounds; + for (int i = 0; i < textLine.wordCollection.length; i++) { + final TextWord word = textLine.wordCollection[i]; if (i == 0) { - fontName = glyph.fontFamily; - fontSize = glyph.fontSize; - fontStyle = glyph.fontStyle; + fontName = word.fontName; + fontSize = word.fontSize; + fontStyle = word.fontStyle; + } + if (prevBounds != null && + (prevBounds.left + prevBounds.width - word.bounds.left).abs() > 1 && + !textLine.text.endsWith(' ') && + !bidi.Bidi.hasAnyRtl(textLine.text)) { + textLine.text += ' '; } - textLine.text = textLine.text + glyph.toUnicode; - if (fontName == glyph.fontFamily && isSameFontName) { + textLine.text += word.text; + prevBounds = word.bounds; + if (fontName == word.fontName && isSameFontName) { textLine.fontName = fontName!; } else { isSameFontName = false; textLine.fontName = ''; } - if (fontSize == glyph.fontSize && isSameFontSize) { + if (fontSize == word.fontSize && isSameFontSize) { textLine.fontSize = fontSize!; } else { isSameFontSize = false; textLine.fontSize = 0; } - if (fontStyle == glyph.fontStyle && isSameFontStyle) { + if (fontStyle == word.fontStyle && isSameFontStyle) { textLine.fontStyle = fontStyle!; } else { isSameFontStyle = false; @@ -1158,14 +1469,17 @@ class PdfTextExtractor { } } if (textLine.text.isNotEmpty && bidi.Bidi.hasAnyRtl(textLine.text)) { - textLine.text = _bidi.getLogicalToVisualString( - textLine.text, true)['rtlText'] as String; + textLine.text = + _bidi.getLogicalToVisualString(textLine.text, true)['rtlText'] + as String; } return textLine; } String _renderText( - PdfRecordCollection? recordCollection, PdfPageResources pageResources) { + PdfRecordCollection? recordCollection, + PdfPageResources pageResources, + ) { String resultantText = ''; if (recordCollection != null && recordCollection.recordCollection.isNotEmpty) { @@ -1195,12 +1509,32 @@ class PdfTextExtractor { resultantText += '\r\n'; break; } + case 'BDC': + { + if (elements != null && + elements.length > 1 && + elements[1].contains('ActualText') && + elements[1].contains('(')) { + _initializeActualText(elements[1]); + } + break; + } + case 'EMC': + _actualText = null; + break; case 'Tj': case 'TJ': case "'": { final String? resultText = - _renderTextElement(elements!, token, pageResources); + (_actualText != null && _actualText!.isNotEmpty) + ? _actualText + : _renderTextElement( + elements!, + token, + pageResources, + null, + ); if (resultText != null) { resultantText += resultText; } @@ -1211,8 +1545,11 @@ class PdfTextExtractor { } case 'Do': { - final String? result = - _getXObject(resultantText, elements!, pageResources); + final String? result = _getXObject( + resultantText, + elements!, + pageResources, + ); if (result != null && result != '') { resultantText += result; } @@ -1227,7 +1564,9 @@ class PdfTextExtractor { } String _renderTextAsLayout( - PdfRecordCollection? recordCollection, PdfPageResources pageResources) { + PdfRecordCollection? recordCollection, + PdfPageResources pageResources, + ) { double? currentMatrixY = 0; double? prevMatrixY = 0; double? currentY = 0; @@ -1242,6 +1581,7 @@ class PdfTextExtractor { double? horizontalScaling = 100; bool hasNoSpacing = false; bool spaceBetweenWord = false; + bool isSpaceAdded = false; _tempBoundingRectangle = Rect.zero; if (recordCollection != null && recordCollection.recordCollection.isNotEmpty) { @@ -1256,6 +1596,11 @@ class PdfTextExtractor { } } switch (token.trim()) { + case 'q': + { + _hasET = false; + break; + } case 'Tw': { _wordSpacing = double.tryParse(elements![0])!; @@ -1268,6 +1613,7 @@ class PdfTextExtractor { } case 'Tm': { + hasTm = true; final double a = double.tryParse(elements![0])!; final double b = double.tryParse(elements[1])!; final double c = double.tryParse(elements[2])!; @@ -1296,13 +1642,16 @@ class PdfTextExtractor { } case 'cm': { + _hasET = false; 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 && - (locationY < 0 || locationY >= 1)) { + (locationY < 0 || locationY >= 1) && + resultantText.isNotEmpty && + !resultantText.endsWith('\n')) { resultantText += '\r\n'; hasTm = false; } @@ -1312,18 +1661,31 @@ class PdfTextExtractor { case 'BDC': { _hasBDC = true; + if (elements != null && + elements.length > 1 && + elements[1].contains('ActualText') && + elements[1].contains('(')) { + _initializeActualText(elements[1]); + } else { + _hasET = true; + } break; } + case 'EMC': + _actualText = null; + break; case 'TD': { textLeading = double.tryParse(elements![1]); - _textMatrix = MatrixHelper( - 1, - 0, - 0, - 1, - double.tryParse(elements[0])!, - double.tryParse(elements[1])!) * + _textMatrix = + MatrixHelper( + 1, + 0, + 0, + 1, + double.tryParse(elements[0])!, + double.tryParse(elements[1])!, + ) * _textLineMatrix!; _textLineMatrix = _textMatrix!.clone(); if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || @@ -1337,13 +1699,15 @@ class PdfTextExtractor { } case 'Td': { - _textMatrix = MatrixHelper( - 1, - 0, - 0, - 1, - double.tryParse(elements![0])!, - double.tryParse(elements[1])!) * + _textMatrix = + MatrixHelper( + 1, + 0, + 0, + 1, + double.tryParse(elements![0])!, + double.tryParse(elements[1])!, + ) * _textLineMatrix!; _textLineMatrix = _textMatrix!.clone(); if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || @@ -1353,7 +1717,8 @@ class PdfTextExtractor { _tempBoundingRectangle = Rect.zero; _hasBDC = false; } - if ((_textLineMatrix!.offsetX - _currentTextMatrix!.offsetX) > + if ((_textLineMatrix!.offsetX - _currentTextMatrix!.offsetX) + .abs() > 0 && !spaceBetweenWord && hasTj) { @@ -1370,8 +1735,8 @@ class PdfTextExtractor { } case 'BT': { - _textMatrix = MatrixHelper(0, 0, 0, 0, 0, 0); - _textLineMatrix = MatrixHelper(0, 0, 0, 0, 0, 0); + _textMatrix = MatrixHelper(1, 0, 0, 1, 0, 0); + _textLineMatrix = MatrixHelper(1, 0, 0, 1, 0, 0); break; } case 'T*': @@ -1388,13 +1753,15 @@ class PdfTextExtractor { } case 'ET': { + _hasET = true; final double endTextPosition = (_textLineMatrix!.offsetX - _tempBoundingRectangle!.right) / - 10; + 10; if (_hasLeading && endTextPosition == 0 && hasNoSpacing) { resultantText += ' '; _tempBoundingRectangle = Rect.zero; _hasLeading = false; + isSpaceAdded = true; } break; } @@ -1413,23 +1780,59 @@ class PdfTextExtractor { if (difference < 0) { difference = -difference; } - if (spaceBetweenWord) { + if (prevY != 0 && difference >= 1) { + if (resultantText.isNotEmpty && !resultantText.endsWith('\n')) { + resultantText += '\r\n'; + } + } else if (spaceBetweenWord) { if (differenceX > _fontSize!) { differenceX = 0; } - spaceBetweenWord = false; + bool isEncoded = true; + if (elements != null) { + final String text = elements.join(); + isEncoded = + text[0] != '(' || + (text[0] == '(' && _hasOctalEscape(text)); + } + if (currentToken == 'Tj' && + (_hasET || !isEncoded) && + resultantText.isNotEmpty && + !resultantText.endsWith(' ')) { + resultantText += ' '; + } } + spaceBetweenWord = false; hasTj = true; - if (prevY != 0 && difference >= 1) { - resultantText += '\r\n'; + currentText = + currentToken == 'TJ' + ? _renderTextElementTJ( + elements!, + token, + pageResources, + horizontalScaling, + ) + : _renderTextElement( + elements!, + token, + pageResources, + horizontalScaling, + ); + if (_actualText != null && _actualText!.isNotEmpty) { + currentText = _actualText; + _actualText = null; } - currentText = currentToken == 'TJ' - ? _renderTextElementTJ( - elements!, token, pageResources, horizontalScaling) - : _renderTextElement(elements!, token, pageResources); _currentTextMatrix = _textLineMatrix!.clone(); prevY = currentY; resultantText += currentText!; + if (currentToken == 'TJ' && + _textLineMatrix!.m11 != 1 && + _textLineMatrix!.m22 != 1 && + _hasET && + currentText.isNotEmpty) { + resultantText += ' '; + } + _hasET = false; _textMatrix = _textLineMatrix!.clone(); if (currentToken == 'TJ') { _hasBDC = false; @@ -1451,29 +1854,52 @@ class PdfTextExtractor { difference = -difference; } _hasLeading = true; - if (prevY != 0 && difference >= 1) { + if (prevY != 0 && + (difference >= 1 || + (i > 0 && records[i - 1].operatorName! == "'")) && + resultantText.isNotEmpty && + !resultantText.endsWith('\n')) { + if (isSpaceAdded && + resultantText.isNotEmpty && + resultantText.endsWith(' ')) { + resultantText = resultantText.substring( + 0, + resultantText.length - 1, + ); + } resultantText += '\r\n'; } + isSpaceAdded = false; prevY = currentY; - final int currentXPosition = - _textLineMatrix!.offsetX.toInt().toSigned(64); - final int prevXPosition = - _currentTextMatrix!.offsetX.toInt().toSigned(64); + final int currentXPosition = _textLineMatrix!.offsetX + .toInt() + .toSigned(64); + final int prevXPosition = _currentTextMatrix!.offsetX + .toInt() + .toSigned(64); if ((prevXPosition - currentXPosition) > 0) { hasNoSpacing = true; } _textMatrix = MatrixHelper(1, 0, 0, 1, 0, textLeading!) * _textLineMatrix!; _textLineMatrix = _textMatrix!.clone(); - currentText = _renderTextElement(elements!, token, pageResources); + currentText = _renderTextElement( + elements!, + token, + pageResources, + horizontalScaling, + ); _currentTextMatrix = _textLineMatrix!.clone(); 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; } @@ -1484,7 +1910,28 @@ class PdfTextExtractor { } } } - return resultantText; + if (isSpaceAdded && + resultantText.isNotEmpty && + resultantText.endsWith(' ')) { + resultantText = resultantText.substring(0, resultantText.length - 1); + } + return resultantText + .replaceAll(RegExp(r' {2,}'), ' ') + .replaceAll(' \r\n', '\r\n'); + } + + bool _hasOctalEscape(String input) { + final RegExp octalPattern = RegExp(r'\\[0-7]{1,3}'); + return octalPattern.hasMatch(input); + } + + void _initializeActualText(String text) { + _actualText = text.substring(text.indexOf('(') + 1, text.lastIndexOf(')')); + const String bigEndianPreambleString = 'þÿ'; + if (_actualText != null && + _actualText!.startsWith(bigEndianPreambleString)) { + _actualText = null; + } } String _skipEscapeSequence(String text) { @@ -1524,8 +1971,12 @@ class PdfTextExtractor { } } - String _renderTextElementTJ(List elements, String tokenType, - PdfPageResources pageResources, double? horizontalScaling) { + String _renderTextElementTJ( + List elements, + String tokenType, + PdfPageResources pageResources, + double? horizontalScaling, + ) { List decodedList = []; Map?, String> decodedListCollection = ?, String>{}; final String text = elements.join(); @@ -1537,20 +1988,28 @@ class PdfTextExtractor { fontStructure = returnValue; } fontStructure!.isTextExtraction = true; + fontStructure.isLayout = _isLayout; fontStructure.fontSize = _fontSize; if (!fontStructure.isEmbedded && fontStructure.isStandardCJKFont && fontStructure.font != null) { decodedList = fontStructure.decodeCjkTextExtractionTJ( - text, pageResources.isSameFont()); + text, + pageResources.isSameFont(), + ); } else { decodedListCollection = fontStructure.decodeTextExtractionTJ( - text, pageResources.isSameFont()); + text, + pageResources.isSameFont(), + ); decodedList = decodedListCollection.values.toList(); } fontStructure.isTextExtraction = false; - tempText = - _renderTextFromTJ(decodedList, horizontalScaling, fontStructure); + tempText = _renderTextFromTJ( + decodedList, + horizontalScaling, + fontStructure, + ); if (bidi.Bidi.hasAnyRtl(tempText)) { tempText = _bidi.getLogicalToVisualString(tempText, true)['rtlText'] as String; @@ -1559,23 +2018,44 @@ class PdfTextExtractor { return tempText; } - String _renderTextFromTJ(List decodedList, double? horizontalScaling, - FontStructure? fontStructure) { + String _renderTextFromTJ( + List decodedList, + double? horizontalScaling, + FontStructure? fontStructure, + ) { String extractedText = ''; for (String word in decodedList) { final double? space = double.tryParse(word); if (space != null) { - _textLineMatrix = - _updateTextMatrixWithSpacing(space, horizontalScaling!); + _textLineMatrix = _updateTextMatrixWithSpacing( + space, + horizontalScaling!, + ); if ((_textLineMatrix!.offsetX - _textMatrix!.offsetX).toInt() > 1 && !_hasBDC) { extractedText += ' '; } } else { - double _characterWidth = 1.0; + double characterWidth = 1.0; if (word != '' && word[word.length - 1] == 's') { word = word.substring(0, word.length - 1); } + if (fontStructure != null && + fontStructure.fontEncoding == 'MacRomanEncoding') { + String tempstring = ''; + for (int i = 0; i < word.length; i++) { + final int b = word[i].codeUnitAt(0).toUnsigned(8); + if (b > 126) { + final String x = fontStructure.macEncodeTable![b]!; + tempstring += x; + } else { + tempstring += word[i]; + } + } + if (tempstring != '') { + word = tempstring; + } + } for (int i = 0; i < word.length; i++) { final String renderedCharacter = word[i]; MatrixHelper transform = MatrixHelper(1, 0, 0, 1, 0, 0); @@ -1583,20 +2063,26 @@ class PdfTextExtractor { fontStructure.isStandardFont && fontStructure.font != null) { final PdfStandardFont font = fontStructure.font! as PdfStandardFont; - _characterWidth = PdfStandardFontHelper.getHelper(font) - .getCharWidthInternal(renderedCharacter) * + characterWidth = + PdfStandardFontHelper.getHelper( + font, + ).getCharWidthInternal(renderedCharacter) * PdfFontHelper.characterSizeMultiplier; } else if (!fontStructure.isEmbedded && fontStructure.isStandardCJKFont && fontStructure.font != null) { final PdfCjkStandardFont font = fontStructure.font! as PdfCjkStandardFont; - _characterWidth = PdfCjkStandardFontHelper.getHelper(font) - .getCharWidthInternal(renderedCharacter) * + characterWidth = + PdfCjkStandardFontHelper.getHelper( + font, + ).getCharWidthInternal(renderedCharacter) * PdfFontHelper.characterSizeMultiplier; } else { - _characterWidth = - _getCharacterWidth(renderedCharacter, fontStructure); + characterWidth = _getCharacterWidth( + renderedCharacter, + fontStructure, + ); } _textMatrix = _getTextRenderingMatrix(horizontalScaling!); final MatrixHelper identity = MatrixHelper.identity.clone(); @@ -1617,10 +2103,11 @@ class PdfTextExtractor { tempFontSize = _fontSize; } final Rect boundingRect = Rect.fromLTWH( - matrix.offsetX / 1.3333333333333333, - (matrix.offsetY - tempFontSize!) / 1.3333333333333333, - _characterWidth * tempFontSize, - tempFontSize); + matrix.offsetX / 1.3333333333333333, + (matrix.offsetY - tempFontSize!) / 1.3333333333333333, + characterWidth * tempFontSize, + tempFontSize, + ); if (_tempBoundingRectangle != null) { final double boundingDifference = ((boundingRect.left - _tempBoundingRectangle!.right) / 10) @@ -1634,8 +2121,10 @@ class PdfTextExtractor { } } extractedText += renderedCharacter; - _textLineMatrix = - _updateTextMatrix(_characterWidth, horizontalScaling); + _textLineMatrix = _updateTextMatrix( + characterWidth, + horizontalScaling, + ); _tempBoundingRectangle = boundingRect; _textMatrix = _textLineMatrix!.clone(); } @@ -1645,7 +2134,11 @@ class PdfTextExtractor { } String? _renderTextElement( - List elements, String tokenType, PdfPageResources pageResources) { + List elements, + String tokenType, + PdfPageResources pageResources, + double? horizontalScaling, + ) { try { String text = elements.join(); if (!pageResources.containsKey(_currentFont)) { @@ -1662,6 +2155,14 @@ class PdfTextExtractor { fontStructure!.isTextExtraction = true; fontStructure.fontSize = _fontSize; text = fontStructure.decodeTextExtraction(text, true); + if (_isLayout) { + text = _renderTextFromLeading( + text, + _textLineMatrix, + fontStructure, + horizontalScaling, + ); + } fontStructure.isTextExtraction = false; } if (bidi.Bidi.hasAnyRtl(text)) { @@ -1673,8 +2174,80 @@ class PdfTextExtractor { } } - String? _getXObject(String resultantText, List xobjectElement, - PdfPageResources pageResources) { + String _renderTextFromLeading( + String decodedText, + MatrixHelper? textLineMatrix, + FontStructure structure, [ + double? horizontalScaling = 100, + ]) { + String extractedText = ''; + for (int i = 0; i < decodedText.length; i++) { + final String ch = decodedText[i]; + double characterWidth; + if (structure.isStandardFont) { + final PdfStandardFont font = structure.font! as PdfStandardFont; + characterWidth = + PdfStandardFontHelper.getHelper(font).getCharWidthInternal(ch) * + PdfFontHelper.characterSizeMultiplier; + } else if (structure.isStandardCJKFont) { + final PdfCjkStandardFont font = structure.font! as PdfCjkStandardFont; + characterWidth = + PdfCjkStandardFontHelper.getHelper(font).getCharWidthInternal(ch) * + PdfFontHelper.characterSizeMultiplier; + } else { + characterWidth = _getCharacterWidth(ch, structure); + } + _textMatrix = _getTextRenderingMatrix(horizontalScaling!); + final MatrixHelper identity = MatrixHelper.identity; + identity.scale(0.01, 0.01, 0.0, 0.0); + identity.translate(0.0, 1.0); + MatrixHelper tranformation = identity * _textMatrix!; + if (_initialTransform != null) { + tranformation = tranformation * _initialTransform!; + } + MatrixHelper matrix = MatrixHelper(1, 0, 0, 1, 0, 0); + matrix = matrix * tranformation; + 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; + } + } else { + tempFontSize = structure.fontSize; + } + final Rect boundingRect = Rect.fromLTWH( + matrix.offsetX / 1.3333333333333333, + (matrix.offsetY - tempFontSize!) / 1.3333333333333333, + characterWidth * tempFontSize, + tempFontSize, + ); + if (_tempBoundingRectangle != null) { + final double boundingDifference = + ((boundingRect.left - _tempBoundingRectangle!.right) / 10) + .round() + .toDouble(); + if ((_tempBoundingRectangle!.right != 0 && boundingRect.left != 0) && + boundingDifference >= 1 && + _hasLeading) { + extractedText += ' '; + } + } + extractedText += ch; + _textLineMatrix = _updateTextMatrix(characterWidth, 100); + _tempBoundingRectangle = boundingRect; + } + return extractedText; + } + + String? _getXObject( + String resultantText, + List xobjectElement, + PdfPageResources pageResources, + ) { String? result; final String key = xobjectElement[0].replaceAll('/', ''); if (pageResources.containsKey(key)) { @@ -1694,9 +2267,13 @@ class PdfTextExtractor { pageDictionary = resource; } childResource = _resourceLoader.updatePageResources( - childResource, _resourceLoader.getFormResources(pageDictionary)); - childResource = _resourceLoader.updatePageResources(childResource, - _resourceLoader.getFontResources(pageDictionary, _currentPage)); + childResource, + _resourceLoader.getFormResources(pageDictionary), + ); + childResource = _resourceLoader.updatePageResources( + childResource, + _resourceLoader.getFontResources(pageDictionary, _currentPage), + ); } else { childResource = _updateFontResources(pageResources); } @@ -1723,7 +2300,9 @@ class PdfTextExtractor { } MatrixHelper? _updateTextMatrixWithSpacing( - double space, double horizontalScaling) { + double space, + double horizontalScaling, + ) { final double x = -(space * 0.001 * _fontSize! * horizontalScaling / 100); final Offset point = _textLineMatrix!.transform(Offset.zero); final Offset point2 = _textLineMatrix!.transform(Offset(x, 0.0)); @@ -1736,26 +2315,34 @@ class PdfTextExtractor { } MatrixHelper _getTextRenderingMatrix(double textHorizontalScaling) { - return MatrixHelper(_fontSize! * (textHorizontalScaling / 100), 0, 0, - -_fontSize!, 0, _fontSize!) * + return MatrixHelper( + _fontSize! * (textHorizontalScaling / 100), + 0, + 0, + -_fontSize!, + 0, + _fontSize!, + ) * _textLineMatrix! * _currentTransformationMatrix; } double _getCharacterWidth(String character, FontStructure structure) { - final int _charID = character.codeUnitAt(0); + final int charID = character.codeUnitAt(0); return (structure.fontGlyphWidths != null && structure.fontType!.name == 'TrueType' && - structure.fontGlyphWidths!.containsKey(_charID)) - ? structure.fontGlyphWidths![_charID]! * 0.001 + structure.fontGlyphWidths!.containsKey(charID)) + ? structure.fontGlyphWidths![charID]! * 0.001 : 1.0; } MatrixHelper _updateTextMatrix( - double characterWidth, double horizontalScaling) { + double characterWidth, + double horizontalScaling, + ) { final double offsetX = (characterWidth * _fontSize! + _characterSpacing + _wordSpacing) * - (horizontalScaling / 100); + (horizontalScaling / 100); 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 02cbefa09..e39c82118 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 @@ -17,7 +17,7 @@ class TextElement { //constructor /// internal constructor TextElement(this.text, MatrixHelper? transformMatrix) { - transformations = _TransformationStack(transformMatrix); + transformations = TransformationStack(transformMatrix); _initialize(); } @@ -26,13 +26,13 @@ class TextElement { late String text; /// internal field - late _TransformationStack transformations; + late TransformationStack transformations; /// internal field late List textElementGlyphList; /// internal field - bool? isExtractTextData; + late bool isExtractTextData; /// internal field late String fontName; @@ -297,29 +297,36 @@ class TextElement { 184, 733, 731, - 711 + 711, ]; } MatrixHelper _getTextRenderingMatrix() { - return MatrixHelper(fontSize * (textHorizontalScaling! / 100), 0, 0, - -fontSize, 0, fontSize + rise!) * + return MatrixHelper( + fontSize * (textHorizontalScaling! / 100), + 0, + 0, + -fontSize, + 0, + fontSize + rise!, + ) * textLineMatrix! * currentTransformationMatrix!; } /// internal method Map renderTextElement( - GraphicsObject? g, - Offset currentLocation, - double? textScaling, - Map? glyphWidths, - double? type1Height, - Map differenceTable, - Map differenceMappedTable, - Map? differenceEncoding, - MatrixHelper? txtMatrix, - [List? retrievedCharCodes]) { + GraphicsObject? g, + Offset currentLocation, + double? textScaling, + Map? glyphWidths, + double? type1Height, + Map differenceTable, + Map differenceMappedTable, + Map? differenceEncoding, + MatrixHelper? txtMatrix, [ + List? retrievedCharCodes, + ]) { txtMatrix = MatrixHelper(0, 0, 0, 0, 0, 0); txtMatrix.type = MatrixTypes.identity; double changeInX = currentLocation.dx; @@ -343,13 +350,17 @@ class TextElement { glyph.charSpacing = characterSpacing!; if (structure.isStandardFont) { final PdfStandardFont font = structure.font! as PdfStandardFont; - glyph.width = PdfStandardFontHelper.getHelper(font) - .getCharWidthInternal(character) * + glyph.width = + PdfStandardFontHelper.getHelper( + font, + ).getCharWidthInternal(character) * PdfFontHelper.characterSizeMultiplier; } else if (structure.isStandardCJKFont) { final PdfCjkStandardFont font = structure.font! as PdfCjkStandardFont; - glyph.width = PdfCjkStandardFontHelper.getHelper(font) - .getCharWidthInternal(character) * + glyph.width = + PdfCjkStandardFontHelper.getHelper( + font, + ).getCharWidthInternal(character) * PdfFontHelper.characterSizeMultiplier; } final MatrixHelper identity = MatrixHelper.identity.clone(); @@ -377,12 +388,13 @@ class TextElement { tempFontSize = glyph.fontSize; } glyph.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor!, - ((matrix.offsetY - (tempFontSize * zoomFactor!)) / - 1.3333333333333333) / - zoomFactor!, - glyph.width * tempFontSize, - tempFontSize); + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / + 1.3333333333333333) / + zoomFactor!, + glyph.width * tempFontSize, + tempFontSize, + ); textElementGlyphList.add(glyph); _updateTextMatrix(glyph); transformations._popTransform(); @@ -399,12 +411,13 @@ class TextElement { } for (int i = 0; i < text.length; i++) { final String letter = text[i]; - final dynamic retrievedCharCode = (retrievedCharCodes != null && - i < retrievedCharCodes.length && - retrievedCharCodes[i] != null && - retrievedCharCodes[i] != 0) - ? retrievedCharCodes[i] - : null; + final dynamic retrievedCharCode = + (retrievedCharCodes != null && + i < retrievedCharCodes.length && + retrievedCharCodes[i] != null && + retrievedCharCodes[i] != 0) + ? retrievedCharCodes[i] + : null; letterCount += 1; final int charCode = letter.codeUnitAt(0); isTextGlyphAdded = false; @@ -413,7 +426,11 @@ class TextElement { !isEmbeddedFont) { isTextGlyphAdded = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - letter, g!, txtMatrix, retrievedCharCode); + letter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -423,7 +440,11 @@ class TextElement { if (renderingMode == 1) { isTextGlyphAdded = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - letter, g!, txtMatrix, retrievedCharCode); + letter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -433,16 +454,23 @@ class TextElement { reverseMapTable!.containsKey(letter)) { final int tempCharCode = reverseMapTable![letter]!.toInt(); if (fontGlyphWidths != null) { - currentGlyphWidth = (fontGlyphWidths! - .containsKey(retrievedCharCode ?? tempCharCode) + currentGlyphWidth = + (fontGlyphWidths!.containsKey( + retrievedCharCode ?? tempCharCode, + ) ? fontGlyphWidths![retrievedCharCode ?? tempCharCode] : defaultGlyphWidth)! * charSizeMultiplier; } else { currentGlyphWidth = defaultGlyphWidth! * charSizeMultiplier; } - txtMatrix = - drawGlyphs(currentGlyphWidth, g!, txtMatrix, letter, false); + txtMatrix = drawGlyphs( + currentGlyphWidth, + g!, + txtMatrix, + letter, + false, + ); isTextGlyphAdded = true; } else { if (characterMapTable.isNotEmpty && @@ -450,7 +478,11 @@ class TextElement { final String tempLetter = characterMapTable[charCode]![0]; isTextGlyphAdded = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - tempLetter, g!, txtMatrix, retrievedCharCode); + tempLetter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -471,14 +503,15 @@ class TextElement { !structure.isMappingDone) { currentGlyphWidth = fontGlyphWidths![cidToGidReverseMapTable![charCode]!]! * - charSizeMultiplier; + charSizeMultiplier; } else if (fontGlyphWidths!.containsKey(charCode)) { currentGlyphWidth = fontGlyphWidths![charCode]! * charSizeMultiplier; } else { if (reverseMapTable!.containsKey(unicode) && - !fontGlyphWidths! - .containsKey(reverseMapTable![unicode]!.toInt())) { + !fontGlyphWidths!.containsKey( + reverseMapTable![unicode]!.toInt(), + )) { currentGlyphWidth = defaultGlyphWidth! * charSizeMultiplier; } @@ -500,7 +533,8 @@ class TextElement { } } } else if (fontGlyphWidths != null) { - currentGlyphWidth = (fontGlyphWidths!.containsKey(charCode) + currentGlyphWidth = + (fontGlyphWidths!.containsKey(charCode) ? fontGlyphWidths![charCode] : defaultGlyphWidth)! * charSizeMultiplier; @@ -511,31 +545,37 @@ class TextElement { location = Offset(location.dx + characterSpacing!, location.dy); } if (!isTextGlyphAdded) { - txtMatrix = - drawGlyphs(currentGlyphWidth, g!, txtMatrix, letter, false); + txtMatrix = drawGlyphs( + currentGlyphWidth, + g!, + txtMatrix, + letter, + false, + ); } } } changeInX = location.dx - changeInX; return { 'textElementWidth': changeInX, - 'tempTextMatrix': txtMatrix + 'tempTextMatrix': txtMatrix, }; } /// internal method Map renderWithSpacing( - GraphicsObject? g, - Offset currentLocation, - Map?, String> decodedList, - List? characterSpacing, - double? textScaling, - Map? glyphWidths, - double? type1Height, - Map differenceTable, - Map differenceMappedTable, - Map? differenceEncoding, - MatrixHelper? txtMatrix) { + GraphicsObject? g, + Offset currentLocation, + Map?, String> decodedList, + List? characterSpacing, + double? textScaling, + Map? glyphWidths, + double? type1Height, + Map differenceTable, + Map differenceMappedTable, + Map? differenceEncoding, + MatrixHelper? txtMatrix, + ) { txtMatrix = MatrixHelper(0, 0, 0, 0, 0, 0); txtMatrix.type = MatrixTypes.identity; double changeInX = currentLocation.dx; @@ -569,14 +609,18 @@ class TextElement { glyph.charSpacing = this.characterSpacing!; if (structure.isStandardFont) { final PdfStandardFont font = structure.font! as PdfStandardFont; - glyph.width = PdfStandardFontHelper.getHelper(font) - .getCharWidthInternal(character) * + glyph.width = + PdfStandardFontHelper.getHelper( + font, + ).getCharWidthInternal(character) * PdfFontHelper.characterSizeMultiplier; } else if (structure.isStandardCJKFont) { final PdfCjkStandardFont font = structure.font! as PdfCjkStandardFont; - glyph.width = PdfCjkStandardFontHelper.getHelper(font) - .getCharWidthInternal(character) * + glyph.width = + PdfCjkStandardFontHelper.getHelper( + font, + ).getCharWidthInternal(character) * PdfFontHelper.characterSizeMultiplier; } final MatrixHelper identity = MatrixHelper.identity.clone(); @@ -601,12 +645,13 @@ class TextElement { tempFontSize = glyph.fontSize; } glyph.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor!, - ((matrix.offsetY - (tempFontSize * zoomFactor!)) / - 1.3333333333333333) / - zoomFactor!, - glyph.width * tempFontSize, - tempFontSize); + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / + 1.3333333333333333) / + zoomFactor!, + glyph.width * tempFontSize, + tempFontSize, + ); textElementGlyphList.add(glyph); _updateTextMatrix(glyph); transformations._popTransform(); @@ -632,19 +677,24 @@ class TextElement { reverseMapTable!.isNotEmpty && reverseMapTable!.containsKey(word)) { final int charCode = reverseMapTable![word]!.toInt(); - final dynamic retrievedCharCode = (keys != null && - keys.isNotEmpty && - keys[0] != null && - keys[0] != 0) - ? keys[0] - : null; + final dynamic retrievedCharCode = + (keys != null && + keys.isNotEmpty && + keys[0] != null && + keys[0] != 0) + ? keys[0] + : null; if (characterMapTable.isNotEmpty && characterMapTable.containsKey(charCode)) { final String tempLetter = characterMapTable[charCode]!; isTextGlyphAdded = true; isComplexScript = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - tempLetter, g!, txtMatrix, retrievedCharCode); + tempLetter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -658,19 +708,24 @@ class TextElement { final String letter = word[i]; letterCount += 1; int charCode = letter.codeUnitAt(0); - final dynamic retrievedCharCode = (keys != null && - i < keys.length && - keys[i] != null && - keys[i] != 0) - ? keys[i] - : null; + final dynamic retrievedCharCode = + (keys != null && + i < keys.length && + keys[i] != null && + keys[i] != 0) + ? keys[i] + : null; isTextGlyphAdded = false; if (charCode.toUnsigned(8) > 126 && fontEncoding == 'MacRomanEncoding' && !isEmbeddedFont) { isTextGlyphAdded = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - letter, g!, txtMatrix, retrievedCharCode); + letter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -680,7 +735,11 @@ class TextElement { if (renderingMode == 1) { isTextGlyphAdded = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - letter, g!, txtMatrix, retrievedCharCode); + letter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -696,7 +755,11 @@ class TextElement { final String tempLetter = characterMapTable[charCode]![0]; isTextGlyphAdded = true; final MatrixHelper? tempMatrix = drawSystemFontGlyphShape( - tempLetter, g!, txtMatrix, retrievedCharCode); + tempLetter, + g!, + txtMatrix, + retrievedCharCode, + ); if (tempMatrix != null) { txtMatrix = tempMatrix; } else { @@ -715,8 +778,8 @@ class TextElement { if (cidToGidReverseMapTable != null && cidToGidReverseMapTable!.containsKey(charCode) && !structure.isMappingDone) { - currentGlyphWidth = fontGlyphWidths![ - cidToGidReverseMapTable![charCode]!]! * + currentGlyphWidth = + fontGlyphWidths![cidToGidReverseMapTable![charCode]!]! * charSizeMultiplier; } else if (fontGlyphWidths!.containsKey(charCode)) { currentGlyphWidth = @@ -724,7 +787,8 @@ class TextElement { } else { if (reverseMapTable!.containsKey(unicode) && !fontGlyphWidths!.containsKey( - reverseMapTable![unicode]!.toInt())) { + reverseMapTable![unicode]!.toInt(), + )) { currentGlyphWidth = defaultGlyphWidth! * charSizeMultiplier; } @@ -746,22 +810,30 @@ class TextElement { } } } else if (fontGlyphWidths != null) { - currentGlyphWidth = (fontGlyphWidths!.containsKey(charCode) + currentGlyphWidth = + (fontGlyphWidths!.containsKey(charCode) ? fontGlyphWidths![charCode] : defaultGlyphWidth)! * charSizeMultiplier; } } if (letterCount < word.length) { - location = - Offset(location.dx + this.characterSpacing!, location.dy); + location = Offset( + location.dx + this.characterSpacing!, + location.dy, + ); } if (!isTextGlyphAdded && (retrievedCharCode == null || (retrievedCharCode != null && retrievedCharCode is! String))) { txtMatrix = drawGlyphs( - currentGlyphWidth, g!, txtMatrix, letter, i == 0); + currentGlyphWidth, + g!, + txtMatrix, + letter, + i == 0, + ); } } } @@ -772,13 +844,18 @@ class TextElement { changeInX = location.dx - changeInX; return { 'textElementWidth': changeInX, - 'tempTextMatrix': txtMatrix + 'tempTextMatrix': txtMatrix, }; } /// internal method - MatrixHelper? drawGlyphs(double? glyphwidth, GraphicsObject g, - MatrixHelper? temptextmatrix, String? glyphChar, bool renderWithSpace) { + MatrixHelper? drawGlyphs( + double? glyphwidth, + GraphicsObject g, + MatrixHelper? temptextmatrix, + String? glyphChar, + bool renderWithSpace, + ) { final MatrixHelper defaultTransformations = g.transformMatrix!.clone(); g.transformMatrix = MatrixHelper(1, 0, 0, 1, 0, 0); final Glyph glyph = Glyph(); @@ -804,16 +881,20 @@ class TextElement { if (cidToGidReverseMapTable != null && cidToGidReverseMapTable!.containsKey(glyphChar!.codeUnitAt(0)) && (structure.characterMapTable.isNotEmpty)) { - glyphChar = characterMapTable[ - cidToGidReverseMapTable![glyphChar.codeUnitAt(0)]]; + 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 - .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')) { glyphChar = glyphChar.replaceAll('\u0092', '’'); @@ -824,13 +905,33 @@ class TextElement { tempFontSize = glyph.transformMatrix.m11; } else if (glyph.transformMatrix.m12 != 0 && glyph.transformMatrix.m21 != 0) { - tempFontSize = glyph.transformMatrix.m12 < 0 - ? -glyph.transformMatrix.m12 - : glyph.transformMatrix.m12; + tempFontSize = + glyph.transformMatrix.m12 < 0 + ? -glyph.transformMatrix.m12 + : glyph.transformMatrix.m12; } else { tempFontSize = glyph.fontSize; } - glyph.toUnicode = glyphChar!; + final String glyphText = glyphChar!; + if (!structure.macRomanEncoded && + structure.fontEncoding == 'MacRomanEncoding') { + String tempstring = ''; + for (int i = 0; i < glyphText.length; i++) { + final int b = glyphText[i].codeUnitAt(0).toUnsigned(8); + if (b > 126) { + final String x = structure.macEncodeTable![b]!; + tempstring += x; + } else { + tempstring += glyphText[i]; + } + } + if (tempstring != '') { + glyphChar = tempstring; + } + } else { + structure.macRomanEncoded = false; + } + glyph.toUnicode = glyphChar; if (matrix.m12 != 0 && matrix.m21 != 0) { glyph.isRotated = true; if (matrix.m12 < 0 && matrix.m21 > 0) { @@ -840,11 +941,13 @@ class TextElement { } else if (matrix.m12 < 0 && matrix.m21 < 0) { glyph.rotationAngle = 180; } - final double x = ((matrix.offsetX + + final double x = + ((matrix.offsetX + ((tempFontSize + (glyph.ascent / 1000.0)) * matrix.m21)) / 1.3333333333333333) / zoomFactor!; - double y = ((matrix.offsetY - + double y = + ((matrix.offsetY - ((pageRotation == 270 ? tempFontSize : (glyph.width * tempFontSize)) * @@ -871,12 +974,12 @@ class TextElement { glyph.boundingRect = Rect.fromLTWH(x, y, width, height); } else { glyph.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor!, - ((matrix.offsetY - (tempFontSize * zoomFactor!)) / - 1.3333333333333333) / - zoomFactor!, - glyph.width * tempFontSize, - tempFontSize); + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / + zoomFactor!, + glyph.width * tempFontSize, + tempFontSize, + ); } if (glyph.toUnicode.length != 1) { textElementGlyphList.add(glyph); @@ -897,8 +1000,11 @@ class TextElement { /// internal method MatrixHelper? drawSystemFontGlyphShape( - String letter, GraphicsObject g, MatrixHelper? temptextmatrix, - [dynamic charCode]) { + String letter, + GraphicsObject g, + MatrixHelper? temptextmatrix, [ + dynamic charCode, + ]) { final MatrixHelper? defaultTransformations = g.transformMatrix; g.transformMatrix = MatrixHelper(1, 0, 0, 1, 0, 0); final Glyph gly = Glyph(); @@ -928,6 +1034,8 @@ class TextElement { systemFontGlyph = fontGlyphWidths![letter.codeUnitAt(0)]! * charSizeMultiplier; } + } else if (defaultGlyphWidth != null && defaultGlyphWidth! > 0) { + systemFontGlyph = defaultGlyphWidth! * charSizeMultiplier; } gly.width = (systemFontGlyph == null) ? 0 : systemFontGlyph; final MatrixHelper identity = MatrixHelper(1, 0, 0, 1, 0, 0); @@ -942,9 +1050,10 @@ class TextElement { if (gly.transformMatrix.m11 > 0) { tempFontSize = gly.transformMatrix.m11; } else if (gly.transformMatrix.m12 != 0 && gly.transformMatrix.m21 != 0) { - tempFontSize = gly.transformMatrix.m12 < 0 - ? -gly.transformMatrix.m12 - : gly.transformMatrix.m12; + tempFontSize = + gly.transformMatrix.m12 < 0 + ? -gly.transformMatrix.m12 + : gly.transformMatrix.m12; } else { tempFontSize = gly.fontSize; } @@ -953,29 +1062,64 @@ class TextElement { if (cidToGidReverseMapTable != null && cidToGidReverseMapTable!.containsKey(glyphName.codeUnitAt(0)) && (structure.characterMapTable.isNotEmpty)) { - glyphName = characterMapTable[ - cidToGidReverseMapTable![glyphName.codeUnitAt(0)]]; + glyphName = + characterMapTable[cidToGidReverseMapTable![glyphName.codeUnitAt( + 0, + )]]; } else if (structure.characterMapTable.isNotEmpty) { glyphName = structure.mapCharactersFromTable(glyphName); } else if (structure.differencesDictionary.isNotEmpty) { glyphName = structure.mapDifferences(glyphName); - } else if (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')) { glyphName = glyphName.replaceAll('\u0092', '’'); } } + if (!structure.macRomanEncoded && + structure.fontEncoding == 'MacRomanEncoding') { + String tempstring = ''; + for (int i = 0; i < glyphName.length; i++) { + final int b = glyphName[i].codeUnitAt(0).toUnsigned(8); + if (b > 126) { + final String x = structure.macEncodeTable![b]!; + tempstring += x; + } else { + tempstring += glyphName[i]; + } + } + if (tempstring != '') { + glyphName = tempstring; + } + } else { + structure.macRomanEncoded = false; + } gly.toUnicode = glyphName; gly.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor!, - ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / - zoomFactor!, - gly.width * tempFontSize, - tempFontSize); + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / + zoomFactor!, + gly.width * tempFontSize, + tempFontSize, + ); textElementGlyphList.add(gly); + if (isExtractTextData && gly.toUnicode.length != 1) { + for (int i = 0; i < gly.toUnicode.length - 1; i++) { + final Glyph emptyGlyph = Glyph(); + emptyGlyph.boundingRect = Rect.fromLTWH( + gly.boundingRect.right, + gly.boundingRect.top, + 0, + 0, + ); + textElementGlyphList.add(emptyGlyph); + } + } _updateTextMatrix(gly); transformations._popTransform(); g.transformMatrix = defaultTransformations; @@ -1006,16 +1150,17 @@ class TextElement { final double width = glyph.width; final double offsetX = (width * glyph.fontSize + glyph.charSpacing + glyph.wordSpacing) * - (glyph.horizontalScaling / 100); + (glyph.horizontalScaling / 100); return MatrixHelper(1.0, 0.0, 0.0, 1.0, offsetX, 0.0) * m; } } -class _TransformationStack { - _TransformationStack([MatrixHelper? transformMatrix]) { - _initialTransform = (transformMatrix != null) - ? transformMatrix - : MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); +class TransformationStack { + TransformationStack([MatrixHelper? transformMatrix]) { + _initialTransform = + (transformMatrix != null) + ? transformMatrix + : MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); transformStack = Queue(); } 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 ff98b656b..e7631c63e 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 @@ -3,8 +3,14 @@ import '../../graphics/fonts/enums.dart'; /// Gets the details of character in the word. class TextGlyph { - TextGlyph._(this.text, this.fontName, this.fontStyle, this.bounds, - this.fontSize, this.isRotated); + TextGlyph._( + this.text, + this.fontName, + this.fontStyle, + this.bounds, + this.fontSize, + this.isRotated, + ); //Fields /// Gets the bounds of glyph. @@ -31,8 +37,13 @@ class TextGlyph { class TextGlyphHelper { /// internal method static TextGlyph initialize( - String text, String fontName, List fontStyle, - [Rect bounds = Rect.zero, double fontSize = 0, bool isRotated = false]) { + String text, + String fontName, + List fontStyle, [ + Rect bounds = Rect.zero, + double fontSize = 0, + bool isRotated = false, + ]) { return TextGlyph._(text, fontName, fontStyle, bounds, fontSize, isRotated); } } 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 dbd183972..0f6555edd 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 @@ -6,8 +6,14 @@ import 'text_glyph.dart'; /// Details of a word present in the line. class TextWord { //constructor - TextWord._(this.text, this.fontName, this.fontStyle, List glyphs, - this.bounds, this.fontSize) { + TextWord._( + this.text, + this.fontName, + this.fontStyle, + List glyphs, + this.bounds, + this.fontSize, + ) { _glyphs = glyphs; } @@ -37,9 +43,14 @@ class TextWord { /// [TextWord] helper class TextWordHelper { /// internal method - static TextWord initialize(String text, String fontName, - List fontStyle, List glyphs, - [Rect bounds = Rect.zero, double fontSize = 0]) { + static TextWord initialize( + String text, + String fontName, + List fontStyle, + List glyphs, [ + Rect bounds = Rect.zero, + double fontSize = 0, + ]) { return TextWord._(text, fontName, fontStyle, glyphs, bounds, fontSize); } } 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 304ac1a6b..cd9eb22be 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 @@ -71,12 +71,13 @@ class XObjectElement { /// internal method Map renderTextElement( - GraphicsObject? g, - PdfPageResources? resources, - GraphicStateCollection? graphicsStates, - GraphicObjectDataCollection? objects, - double? currentPageHeight, - List? glyphList) { + GraphicsObject? g, + PdfPageResources? resources, + GraphicStateCollection? graphicsStates, + GraphicObjectDataCollection? objects, + double? currentPageHeight, + List? glyphList, + ) { glyphList = []; List? extractTextElement; if (_objectType != null && @@ -102,9 +103,13 @@ class XObjectElement { pageDictionary = primitive; } childResource = resourceLoader.updatePageResources( - childResource, resourceLoader.getFontResources(pageDictionary)); + childResource, + resourceLoader.getFontResources(pageDictionary), + ); childResource = resourceLoader.updatePageResources( - childResource, resourceLoader.getFormResources(pageDictionary)); + childResource, + resourceLoader.getFormResources(pageDictionary), + ); } MatrixHelper xFormsMatrix = MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); if (xobjects.containsKey(PdfDictionaryProperties.matrix)) { @@ -142,20 +147,25 @@ class XObjectElement { } } } - final ImageRenderer renderer = - ImageRenderer(contentTree, childResource, currentPageHeight, g); + final ImageRenderer renderer = ImageRenderer( + contentTree, + childResource, + currentPageHeight, + g, + ); renderer.isExtractLineCollection = isExtractTextLine!; renderer.graphicsObjects = 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); + newMatrix.m11, + newMatrix.m12, + newMatrix.m21, + newMatrix.m22, + newMatrix.offsetX, + newMatrix.offsetY, + ); objects.last.currentTransformationMatrix = newMatrix; renderer.selectablePrintDocument = isPrintSelected; renderer.pageHeight = pageHeight; @@ -174,7 +184,7 @@ class XObjectElement { 'graphicStates': graphicsStates, 'glyphList': glyphList, 'objects': objects, - 'extractTextElement': extractTextElement + 'extractTextElement': extractTextElement, }; } } 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 index 2294c1aba..4eed9f30a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/enum.dart @@ -88,7 +88,7 @@ enum FieldFlags { /// 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 + commitOnSelChange, } /// Specifies the style for a check box field. @@ -109,7 +109,7 @@ enum PdfCheckBoxStyle { square, /// A star is used for the checked state. - star + star, } /// internal enumerator @@ -124,7 +124,7 @@ enum PdfCheckFieldState { pressedUnchecked, /// Indicated pressed checked state. - pressedChecked + pressedChecked, } /// internal enumerator @@ -151,7 +151,7 @@ enum PdfFieldTypes { comboBox, /// Identify that field has no type. - none + none, } /// internal enumerator @@ -171,7 +171,7 @@ enum SignatureFlags { /// 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 + appendOnly, } /// Specifies the format of Export or Import data. @@ -186,5 +186,5 @@ enum DataFormat { json, /// Specifies XML file format - xml + xml, } 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 index 84c2c3ffb..efa853ada 100644 --- 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 @@ -1,11 +1,10 @@ import 'dart:ui'; -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/annotations/pdf_annotation_collection.dart'; - import '../../interfaces/pdf_interface.dart'; import '../actions/pdf_field_actions.dart'; import '../annotations/enum.dart'; import '../annotations/pdf_annotation.dart'; +import '../annotations/pdf_annotation_collection.dart'; import '../annotations/pdf_appearance.dart'; import '../annotations/pdf_paintparams.dart'; import '../annotations/widget_annotation.dart'; @@ -39,31 +38,41 @@ 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}) { + 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, + }) { _helper = PdfButtonFieldHelper(this); - _helper.internal(page, name, bounds, - tooltip: tooltip, - font: font, - borderColor: borderColor, - backColor: backColor, - foreColor: foreColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle); + _helper.internal( + page, + name, + bounds, + tooltip: tooltip, + font: font, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + ); _helper.dictionary!.setProperty( - PdfDictionaryProperties.ft, PdfName(PdfDictionaryProperties.btn)); + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.btn), + ); if (backColor == null) { - _helper.backColor = PdfColor(211, 211, 211, 255); + _helper.backColor = PdfColor(211, 211, 211); } _helper.flags.add(FieldFlags.pushButton); _helper.initValues(text, actions); @@ -154,23 +163,29 @@ class PdfButtonField extends PdfField { PdfFieldActions get actions { if (_helper.isLoadedField && _helper.actions == null) { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.aa)) { - final PdfDictionary actionDict = _helper.crossTable! - .getObject(_helper.dictionary![PdfDictionaryProperties.aa])! - as PdfDictionary; + final PdfDictionary actionDict = + _helper.crossTable!.getObject( + _helper.dictionary![PdfDictionaryProperties.aa], + )! + as PdfDictionary; _helper.actions = PdfFieldActionsHelper.load(actionDict); _helper.widget!.actions = PdfFieldActionsHelper.getHelper(_helper.actions!).annotationActions; } else { _helper.actions = PdfFieldActionsHelper.load(PdfDictionary()); - _helper.dictionary! - .setProperty(PdfDictionaryProperties.aa, _helper.actions); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.aa, + _helper.actions, + ); } _helper.changed = true; } else { if (_helper.actions == null) { _helper.actions = PdfFieldActions(_helper.widget!.actions!); - _helper.dictionary! - .setProperty(PdfDictionaryProperties.aa, _helper.actions); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.aa, + _helper.actions, + ); } } return _helper.actions!; @@ -215,18 +230,23 @@ class PdfButtonFieldHelper extends PdfFieldHelper { void addPrintAction() { final PdfDictionary actionDictionary = PdfDictionary(); actionDictionary.setProperty( - PdfDictionaryProperties.n, PdfName(PdfDictionaryProperties.print)); + PdfDictionaryProperties.n, + PdfName(PdfDictionaryProperties.print), + ); actionDictionary.setProperty(PdfDictionaryProperties.s, PdfName('Named')); if (isLoadedField) { - final PdfArray? kidsArray = crossTable! - .getObject(dictionary![PdfDictionaryProperties.kids]) as PdfArray?; + final PdfArray? kidsArray = + crossTable!.getObject(dictionary![PdfDictionaryProperties.kids]) + as PdfArray?; if (kidsArray != null) { final PdfReferenceHolder buttonObject = kidsArray[0]! as PdfReferenceHolder; final PdfDictionary buttonDictionary = buttonObject.object! as PdfDictionary; buttonDictionary.setProperty( - PdfDictionaryProperties.a, actionDictionary); + PdfDictionaryProperties.a, + actionDictionary, + ); } else { dictionary!.setProperty(PdfDictionaryProperties.a, actionDictionary); } @@ -251,12 +271,12 @@ class PdfButtonFieldHelper extends PdfFieldHelper { final PdfPage? page = buttonField.page; if (widget != null) { page!.annotations.remove(widget!); - PdfAnnotationCollectionHelper.getHelper(page.annotations) - .annotations - .insert(buttonField.tabIndex, PdfReferenceHolder(widget)); - PdfObjectCollectionHelper.getHelper(page.annotations) - .list - .insert(buttonField.tabIndex, widget!); + PdfAnnotationCollectionHelper.getHelper( + page.annotations, + ).annotations.insert(buttonField.tabIndex, PdfReferenceHolder(widget)); + PdfObjectCollectionHelper.getHelper( + page.annotations, + ).list.insert(buttonField.tabIndex, widget!); } } if (buttonField.form != null && @@ -282,20 +302,26 @@ class PdfButtonFieldHelper extends PdfFieldHelper { buttonField.text = buttonField.name!; } final PaintParams paintParams = PaintParams( - bounds: Rect.fromLTWH( - 0, 0, widget!.bounds.size.width, widget!.bounds.size.height), - backBrush: PdfSolidBrush(buttonField.backColor), - foreBrush: PdfSolidBrush(buttonField.foreColor), - borderPen: borderPen, - style: buttonField.borderStyle, - borderWidth: borderWidth, - shadowBrush: PdfSolidBrush(buttonField.backColor)); + bounds: Rect.fromLTWH( + 0, + 0, + widget!.bounds.size.width, + widget!.bounds.size.height, + ), + backBrush: PdfSolidBrush(buttonField.backColor), + foreBrush: PdfSolidBrush(buttonField.foreColor), + borderPen: borderPen, + style: buttonField.borderStyle, + borderWidth: borderWidth, + shadowBrush: PdfSolidBrush(buttonField.backColor), + ); FieldPainter().drawButton( - template.graphics!, - paintParams, - buttonField.text, - (font == null) ? PdfStandardFont(PdfFontFamily.helvetica, 8) : font!, - format); + template.graphics!, + paintParams, + buttonField.text, + (font == null) ? PdfStandardFont(PdfFontFamily.helvetica, 8) : font!, + format, + ); } void _drawPressedAppearance(PdfTemplate template) { @@ -303,40 +329,54 @@ class PdfButtonFieldHelper extends PdfFieldHelper { buttonField.text = buttonField.name!; } final PaintParams paintParams = PaintParams( - bounds: Rect.fromLTWH( - 0, 0, widget!.bounds.size.width, widget!.bounds.size.height), - backBrush: PdfSolidBrush(buttonField.backColor), - foreBrush: PdfSolidBrush(buttonField.foreColor), - borderPen: borderPen, - style: buttonField.borderStyle, - borderWidth: borderWidth, - shadowBrush: PdfSolidBrush(buttonField.backColor)); + bounds: Rect.fromLTWH( + 0, + 0, + widget!.bounds.size.width, + widget!.bounds.size.height, + ), + backBrush: PdfSolidBrush(buttonField.backColor), + foreBrush: PdfSolidBrush(buttonField.foreColor), + borderPen: borderPen, + style: buttonField.borderStyle, + borderWidth: borderWidth, + shadowBrush: PdfSolidBrush(buttonField.backColor), + ); FieldPainter().drawPressedButton( - template.graphics!, - paintParams, - buttonField.text, - (font == null) ? PdfStandardFont(PdfFontFamily.helvetica, 8) : font!, - format); + template.graphics!, + paintParams, + buttonField.text, + (font == null) ? PdfStandardFont(PdfFontFamily.helvetica, 8) : font!, + format, + ); } String _obtainText() { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); String? str; if (widget.containsKey(PdfDictionaryProperties.mk)) { - final PdfDictionary appearance = crossTable! - .getObject(widget[PdfDictionaryProperties.mk])! as PdfDictionary; + final PdfDictionary appearance = + crossTable!.getObject(widget[PdfDictionaryProperties.mk])! + as PdfDictionary; if (appearance.containsKey(PdfDictionaryProperties.ca)) { - final PdfString text = crossTable! - .getObject(appearance[PdfDictionaryProperties.ca])! as PdfString; + final PdfString text = + crossTable!.getObject(appearance[PdfDictionaryProperties.ca])! + as PdfString; str = text.value; } } if (str == null) { - PdfString? val = crossTable! - .getObject(dictionary![PdfDictionaryProperties.v]) as PdfString?; - val ??= PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true) - as PdfString?; + PdfString? val = + crossTable!.getObject(dictionary![PdfDictionaryProperties.v]) + as PdfString?; + val ??= + PdfFieldHelper.getValue( + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ) + as PdfString?; if (val != null) { str = val.value; } else { @@ -350,16 +390,21 @@ class PdfButtonFieldHelper extends PdfFieldHelper { final String text = value; final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); if (widget.containsKey(PdfDictionaryProperties.mk)) { - final PdfDictionary appearance = crossTable! - .getObject(widget[PdfDictionaryProperties.mk])! as PdfDictionary; + final PdfDictionary appearance = + crossTable!.getObject(widget[PdfDictionaryProperties.mk])! + as PdfDictionary; appearance.setString(PdfDictionaryProperties.ca, text); widget.setProperty( - PdfDictionaryProperties.mk, PdfReferenceHolder(appearance)); + PdfDictionaryProperties.mk, + PdfReferenceHolder(appearance), + ); } else { final PdfDictionary appearance = PdfDictionary(); appearance.setString(PdfDictionaryProperties.ca, text); widget.setProperty( - PdfDictionaryProperties.mk, PdfReferenceHolder(appearance)); + PdfDictionaryProperties.mk, + PdfReferenceHolder(appearance), + ); } if (widget.containsKey(PdfDictionaryProperties.ap)) { _applyAppearance(widget, null); @@ -403,32 +448,48 @@ class PdfButtonFieldHelper extends PdfFieldHelper { PdfAnnotationHelper.getHelper(widget!).appearance; if (appearance != null) { buttonField.page!.graphics.drawPdfTemplate( - appearance.normal, Offset(widget!.bounds.left, widget!.bounds.top)); + appearance.normal, + Offset(widget!.bounds.left, widget!.bounds.top), + ); } else { Rect rect = buttonField.bounds; rect = Rect.fromLTWH( - 0, 0, buttonField.bounds.width, buttonField.bounds.height); + 0, + 0, + buttonField.bounds.width, + buttonField.bounds.height, + ); final PdfFont tempFont = font ?? PdfStandardFont(PdfFontFamily.helvetica, 8); final PaintParams params = PaintParams( - bounds: rect, - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: buttonField.borderStyle, - borderWidth: buttonField.borderWidth, - shadowBrush: shadowBrush); + bounds: rect, + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: buttonField.borderStyle, + borderWidth: buttonField.borderWidth, + shadowBrush: shadowBrush, + ); final PdfTemplate template = PdfTemplate(rect.width, rect.height); - FieldPainter().drawButton(template.graphics!, params, buttonField.text, - tempFont, stringFormat); - buttonField.page!.graphics.drawPdfTemplate(template, - Offset(buttonField.bounds.left, buttonField.bounds.top), rect.size); + FieldPainter().drawButton( + template.graphics!, + params, + buttonField.text, + tempFont, + stringFormat, + ); + buttonField.page!.graphics.drawPdfTemplate( + template, + Offset(buttonField.bounds.left, buttonField.bounds.top), + rect.size, + ); buttonField.page!.graphics.drawString( - (buttonField.text.isEmpty) ? buttonField.name! : buttonField.text, - tempFont, - brush: params.foreBrush, - bounds: buttonField.bounds, - format: stringFormat); + (buttonField.text.isEmpty) ? buttonField.name! : buttonField.text, + tempFont, + brush: params.foreBrush, + bounds: buttonField.bounds, + format: stringFormat, + ); } } } @@ -439,19 +500,23 @@ class PdfButtonFieldHelper extends PdfFieldHelper { widget!.setProperty(PdfDictionaryProperties.aa, actions); } if ((widget != null) && (widget.containsKey(PdfDictionaryProperties.ap))) { - final PdfDictionary? appearance = crossTable! - .getObject(widget[PdfDictionaryProperties.ap]) as PdfDictionary?; + final PdfDictionary? appearance = + crossTable!.getObject(widget[PdfDictionaryProperties.ap]) + as PdfDictionary?; if ((appearance != null) && (appearance.containsKey(PdfDictionaryProperties.n))) { final Rect bounds = (item == null) ? buttonField.bounds : item.bounds; PdfTemplate template = PdfTemplate(bounds.width, bounds.height); - final PdfTemplate pressedTemplate = - PdfTemplate(bounds.width, bounds.height); + final PdfTemplate pressedTemplate = PdfTemplate( + bounds.width, + bounds.height, + ); if (widget.containsKey(PdfDictionaryProperties.mk)) { PdfDictionary? mkDic; if (widget[PdfDictionaryProperties.mk] is PdfReferenceHolder) { - mkDic = crossTable!.getObject(widget[PdfDictionaryProperties.mk]) - as PdfDictionary?; + mkDic = + crossTable!.getObject(widget[PdfDictionaryProperties.mk]) + as PdfDictionary?; } else { mkDic = widget[PdfDictionaryProperties.mk] as PdfDictionary?; } @@ -463,30 +528,44 @@ class PdfButtonFieldHelper extends PdfFieldHelper { template = PdfTemplate(bounds.size.height, bounds.size.width); PdfTemplateHelper.getHelper(template).writeTransformation = false; - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = - PdfArray([0, 1, -1, 0, bounds.size.width, 0]); + PdfTemplateHelper.getHelper( + template, + ).content[PdfDictionaryProperties.matrix] = PdfArray([ + 0, + 1, + -1, + 0, + bounds.size.width, + 0, + ]); } else if (angle.value == 180) { template = PdfTemplate(bounds.size.width, bounds.size.height); PdfTemplateHelper.getHelper(template).writeTransformation = false; - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = - PdfArray([ + PdfTemplateHelper.getHelper( + template, + ).content[PdfDictionaryProperties.matrix] = PdfArray([ -1, 0, 0, -1, bounds.size.width, - bounds.size.height + bounds.size.height, ]); } else if (angle.value == 270) { template = PdfTemplate(bounds.size.height, bounds.size.width); PdfTemplateHelper.getHelper(template).writeTransformation = false; - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = - PdfArray([0, -1, 1, 0, 0, bounds.size.height]); + PdfTemplateHelper.getHelper( + template, + ).content[PdfDictionaryProperties.matrix] = PdfArray([ + 0, + -1, + 1, + 0, + 0, + bounds.size.height, + ]); } } } @@ -494,31 +573,40 @@ class PdfButtonFieldHelper extends PdfFieldHelper { _drawButton(template.graphics, item, widget); _drawButton(pressedTemplate.graphics, item, widget); appearance.setProperty( - PdfDictionaryProperties.n, PdfReferenceHolder(template)); + PdfDictionaryProperties.n, + PdfReferenceHolder(template), + ); appearance.setProperty( - PdfDictionaryProperties.d, PdfReferenceHolder(pressedTemplate)); + PdfDictionaryProperties.d, + PdfReferenceHolder(pressedTemplate), + ); widget.setProperty(PdfDictionaryProperties.ap, appearance); } - } else if (PdfFormHelper.getHelper(buttonField.form!) - .setAppearanceDictionary) { + } else if (PdfFormHelper.getHelper( + buttonField.form!, + ).setAppearanceDictionary) { PdfFormHelper.getHelper(buttonField.form!).needAppearances = true; } } - void _drawButton(PdfGraphics? graphics, PdfButtonField? item, - [PdfDictionary? widget]) { + void _drawButton( + PdfGraphics? graphics, + PdfButtonField? item, [ + PdfDictionary? widget, + ]) { final GraphicsProperties gp = GraphicsProperties(buttonField); 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); + bounds: gp.bounds, + backBrush: gp.backBrush, + foreBrush: gp.foreBrush, + borderPen: gp.borderPen, + style: gp.style, + borderWidth: gp.borderWidth, + shadowBrush: gp.shadowBrush, + ); if (dictionary!.containsKey(PdfDictionaryProperties.ap) && !(PdfGraphicsHelper.getHelper(graphics!).layer != null && PdfGraphicsHelper.getHelper(graphics).page!.rotation != @@ -534,10 +622,13 @@ class PdfButtonFieldHelper extends PdfFieldHelper { if (buttonResource != null) { final PdfStream? stream = buttonResource as PdfStream?; if (stream != null) { - final PdfTemplate buttonShape = - PdfTemplateHelper.fromPdfStream(stream); - buttonField.page!.graphics.drawPdfTemplate(buttonShape, - Offset(buttonField.bounds.left, buttonField.bounds.top)); + final PdfTemplate buttonShape = PdfTemplateHelper.fromPdfStream( + stream, + ); + buttonField.page!.graphics.drawPdfTemplate( + buttonShape, + Offset(buttonField.bounds.left, buttonField.bounds.top), + ); } } } @@ -558,22 +649,32 @@ class PdfButtonFieldHelper extends PdfFieldHelper { if (buttonResource != null) { final PdfStream? stream = buttonResource as PdfStream?; if (stream != null) { - final PdfTemplate buttonShape = - PdfTemplateHelper.fromPdfStream(stream); - buttonField.page!.graphics.drawPdfTemplate(buttonShape, - Offset(buttonField.bounds.left, buttonField.bounds.top)); + final PdfTemplate buttonShape = PdfTemplateHelper.fromPdfStream( + stream, + ); + buttonField.page!.graphics.drawPdfTemplate( + buttonShape, + Offset(buttonField.bounds.left, buttonField.bounds.top), + ); } } } } else { FieldPainter().drawButton( - graphics!, prms, buttonField.text, gp.font!, gp.stringFormat); + graphics!, + prms, + buttonField.text, + gp.font!, + gp.stringFormat, + ); } } /// internal method static PdfButtonField loadButtonField( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfButtonField._loaded(dictionary, crossTable); } } 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 index e24ed102a..33383b456 100644 --- 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 @@ -30,26 +30,34 @@ 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}) { + 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, + }) { _helper = PdfCheckBoxFieldHelper(this); - _helper.initialize(page, name, bounds, - style: style, - borderColor: borderColor, - backColor: backColor, - foreColor: foreColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + _helper.initialize( + page, + name, + bounds, + style: style, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + ); _setCheckBoxValue(isChecked); } @@ -62,8 +70,9 @@ class PdfCheckBoxField extends PdfCheckFieldBase { for (int i = 0; i < kids.count; ++i) { final PdfDictionary? itemDictionary = crossTable.getObject(kids[i]) as PdfDictionary?; - PdfFieldItemCollectionHelper.getHelper(_items!) - .add(PdfCheckBoxItemHelper.getItem(this, i, itemDictionary)); + PdfFieldItemCollectionHelper.getHelper( + _items!, + ).add(PdfCheckBoxItemHelper.getItem(this, i, itemDictionary)); } _helper.array = kids; } @@ -82,20 +91,26 @@ class PdfCheckBoxField extends PdfCheckFieldBase { if (_helper.isLoadedField) { if (items != null && items!.count > 0) { final IPdfPrimitive? state = PdfCrossTable.dereference( - PdfFieldItemHelper.getHelper(items![_helper.defaultIndex]) - .dictionary![PdfDictionaryProperties.usageApplication]); + PdfFieldItemHelper.getHelper( + items![_helper.defaultIndex], + ).dictionary![PdfDictionaryProperties.usageApplication], + ); if (state == null) { final IPdfPrimitive? name = PdfFieldHelper.getValue( - _helper.dictionary!, - _helper.crossTable, - PdfDictionaryProperties.v, - false); + _helper.dictionary!, + _helper.crossTable, + PdfDictionaryProperties.v, + false, + ); if (name != null && name is PdfName) { - _checked = name.name == + _checked = + name.name == _helper.getItemValue( - PdfFieldItemHelper.getHelper(items![_helper.defaultIndex]) - .dictionary!, - _helper.crossTable); + PdfFieldItemHelper.getHelper( + items![_helper.defaultIndex], + ).dictionary!, + _helper.crossTable, + ); } } else if (state is PdfName) { _checked = state.name != PdfDictionaryProperties.off; @@ -103,9 +118,16 @@ class PdfCheckBoxField extends PdfCheckFieldBase { return _checked; } if (_helper.dictionary!.containsKey(PdfDictionaryProperties.v)) { - final PdfName chk = - _helper.dictionary![PdfDictionaryProperties.v]! as PdfName; - _checked = chk.name != 'Off'; + if (_helper.dictionary![PdfDictionaryProperties.v]! is PdfName) { + final PdfName chk = + _helper.dictionary![PdfDictionaryProperties.v]! as PdfName; + _checked = chk.name != 'Off'; + } else if (_helper.dictionary![PdfDictionaryProperties.v]! + is PdfString) { + final PdfString chk = + _helper.dictionary![PdfDictionaryProperties.v]! as PdfString; + _checked = chk.value != 'Off'; + } } } return _checked; @@ -114,12 +136,23 @@ class PdfCheckBoxField extends PdfCheckFieldBase { set isChecked(bool value) { if (_helper.isLoadedField) { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.v)) { - final PdfName chk = - _helper.dictionary![PdfDictionaryProperties.v]! as PdfName; - if (chk.name!.isNotEmpty) { - _checked = chk.name != 'Off'; - } else { - _helper.dictionary!.remove(PdfDictionaryProperties.v); + if (_helper.dictionary![PdfDictionaryProperties.v]! is PdfName) { + final PdfName chk = + _helper.dictionary![PdfDictionaryProperties.v]! as PdfName; + if (chk.name!.isNotEmpty) { + _checked = chk.name != 'Off'; + } else { + _helper.dictionary!.remove(PdfDictionaryProperties.v); + } + } else if (_helper.dictionary![PdfDictionaryProperties.v]! + is PdfString) { + final PdfString chk = + _helper.dictionary![PdfDictionaryProperties.v]! as PdfString; + if (chk.value!.isNotEmpty) { + _checked = chk.value != 'Off'; + } else { + _helper.dictionary!.remove(PdfDictionaryProperties.v); + } } } PdfFormHelper.getHelper(form!).setAppearanceDictionary = true; @@ -132,18 +165,26 @@ class PdfCheckBoxField extends PdfCheckFieldBase { String? val; if (_helper.isLoadedField) { val = _helper._enableCheckBox(value); - _helper._enableItems(value, val); + val = _helper._enableItems(value, val); } if (_checked) { - _helper.dictionary!.setName(PdfName(PdfDictionaryProperties.v), - val ?? PdfDictionaryProperties.yes); + _helper.dictionary!.setName( + PdfName(PdfDictionaryProperties.v), + val ?? PdfDictionaryProperties.yes, + ); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.usageApplication, + PdfName(val ?? PdfDictionaryProperties.yes), + ); } else { _helper.dictionary!.remove(PdfDictionaryProperties.v); - if (_helper.dictionary! - .containsKey(PdfDictionaryProperties.usageApplication)) { + if (_helper.dictionary!.containsKey( + PdfDictionaryProperties.usageApplication, + )) { _helper.dictionary!.setName( - PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); + PdfName(PdfDictionaryProperties.usageApplication), + PdfDictionaryProperties.off, + ); } } } @@ -152,6 +193,7 @@ class PdfCheckBoxField extends PdfCheckFieldBase { /// Gets the collection of check box field items. PdfFieldItemCollection? get items => _items; + // ignore: use_setters_to_change_properties void _setCheckBoxValue(bool isChecked) { this.isChecked = isChecked; } @@ -165,9 +207,17 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { /// internal field PdfCheckBoxField checkBoxField; + /// internal field + // ignore: avoid_setters_without_getters + set items(PdfFieldItemCollection? value) { + checkBoxField._items = value; + } + /// internal method static PdfCheckBoxField loadCheckBoxField( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfCheckBoxField._loaded(dictionary, crossTable); } @@ -200,9 +250,10 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { bool isChecked = false; String? val; if (dictionary!.containsKey(PdfDictionaryProperties.usageApplication)) { - final PdfName? state = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.usageApplication]) as PdfName?; - if (state != null) { + final IPdfPrimitive? state = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.usageApplication], + ); + if (state != null && state is PdfName) { isChecked = state.name != PdfDictionaryProperties.off; } } @@ -212,69 +263,74 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { if (val == null || val == '') { val = PdfDictionaryProperties.yes; } - dictionary!.setProperty( - PdfDictionaryProperties.usageApplication, PdfName(val)); changed = true; } } return val; } - void _enableItems(bool check, String? value) { + String? _enableItems(bool check, String? value) { if (checkBoxField.items != null && checkBoxField.items!.count > 0) { + (checkBoxField.items![defaultIndex] as PdfCheckBoxItem).checked = check; final PdfDictionary? dic = - PdfFieldItemHelper.getHelper(checkBoxField.items![defaultIndex]) - .dictionary; + PdfFieldItemHelper.getHelper( + checkBoxField.items![defaultIndex], + ).dictionary; if (dic != null) { - if (value == null || value.isEmpty) { - value = getItemValue(dic, crossTable); - } - if (value == null || value.isEmpty) { - value = PdfDictionaryProperties.yes; - } - if (check) { - dic.setProperty( - PdfDictionaryProperties.usageApplication, PdfName(value)); - dic.setProperty(PdfDictionaryProperties.v, PdfName(value)); - } else { - dic.setName(PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); - } + value = getItemValue(dic, crossTable); } } + return value; } /// internal method void 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: checkBoxField.borderStyle, - borderWidth: checkBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: Rect.fromLTWH( + 0, + 0, + widget!.bounds.size.width, + widget!.bounds.size.height, + ), + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: checkBoxField.borderStyle, + borderWidth: checkBoxField.borderWidth, + shadowBrush: shadowBrush, + ); PdfTemplate template = widget!.extendedAppearance!.normal.activate!; - FieldPainter().drawCheckBox(template.graphics!, paintParams, - styleToString(checkBoxField.style), PdfCheckFieldState.checked, font); + FieldPainter().drawCheckBox( + template.graphics!, + paintParams, + styleToString(checkBoxField.style), + PdfCheckFieldState.checked, + font, + ); template = widget!.extendedAppearance!.normal.off!; - FieldPainter().drawCheckBox(template.graphics!, paintParams, - styleToString(checkBoxField.style), PdfCheckFieldState.unchecked, font); + FieldPainter().drawCheckBox( + template.graphics!, + paintParams, + styleToString(checkBoxField.style), + PdfCheckFieldState.unchecked, + font, + ); template = widget!.extendedAppearance!.pressed.activate!; FieldPainter().drawCheckBox( - template.graphics!, - paintParams, - styleToString(checkBoxField.style), - PdfCheckFieldState.pressedChecked, - font); + template.graphics!, + paintParams, + styleToString(checkBoxField.style), + PdfCheckFieldState.pressedChecked, + font, + ); template = widget!.extendedAppearance!.pressed.off!; FieldPainter().drawCheckBox( - template.graphics!, - paintParams, - styleToString(checkBoxField.style), - PdfCheckFieldState.pressedUnchecked, - font); + template.graphics!, + paintParams, + styleToString(checkBoxField.style), + PdfCheckFieldState.pressedUnchecked, + font, + ); } /// internal method @@ -296,18 +352,20 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { @override void draw() { super.draw(); - final PdfCheckFieldState state = checkBoxField.isChecked - ? PdfCheckFieldState.checked - : PdfCheckFieldState.unchecked; + PdfCheckFieldState state = + checkBoxField.isChecked + ? PdfCheckFieldState.checked + : PdfCheckFieldState.unchecked; if (!isLoadedField) { final PaintParams params = PaintParams( - bounds: checkBoxField.bounds, - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: checkBoxField.borderStyle, - borderWidth: checkBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: checkBoxField.bounds, + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: checkBoxField.borderStyle, + borderWidth: checkBoxField.borderWidth, + shadowBrush: shadowBrush, + ); if (fieldItems != null && fieldItems!.isNotEmpty) { for (int i = 0; i < array.count; i++) { final PdfCheckBoxField item = fieldItems![i] as PdfCheckBoxField; @@ -319,17 +377,29 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { params.borderWidth = item.borderWidth; params.shadowBrush = item._helper.shadowBrush; FieldPainter().drawCheckBox( - item.page!.graphics, params, styleToString(item.style), state); + item.page!.graphics, + params, + styleToString(item.style), + state, + ); } } else { - FieldPainter().drawCheckBox(checkBoxField.page!.graphics, params, - styleToString(checkBoxField.style), state); + FieldPainter().drawCheckBox( + checkBoxField.page!.graphics, + params, + styleToString(checkBoxField.style), + state, + ); } } else { if (kids != null) { for (int i = 0; i < kids!.count; ++i) { final PdfCheckBoxItem item = checkBoxField._items![i] as PdfCheckBoxItem; + state = + item.checked + ? PdfCheckFieldState.checked + : PdfCheckFieldState.unchecked; if (item.page != null) { drawStateItem(item.page!.graphics, state, null, item); } @@ -440,26 +510,36 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { /// Initializes a instance of the [PdfCheckFieldBase] class with /// the specific page, name and bounds. - void initialize(PdfPage? page, String? name, Rect bounds, - {PdfCheckBoxStyle? style, - PdfColor? borderColor, - PdfColor? backColor, - PdfColor? foreColor, - int? borderWidth, - PdfHighlightMode? highlightMode, - PdfBorderStyle? borderStyle, - String? tooltip}) { + void initialize( + PdfPage? page, + String? name, + Rect bounds, { + PdfCheckBoxStyle? style, + PdfColor? borderColor, + PdfColor? backColor, + PdfColor? foreColor, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfBorderStyle? borderStyle, + String? tooltip, + }) { checkField._checkBaseHelper = this; - internal(page, name, bounds, - borderColor: borderColor, - backColor: backColor, - foreColor: foreColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + internal( + page, + name, + bounds, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + ); dictionary!.setProperty( - PdfDictionaryProperties.ft, PdfName(PdfDictionaryProperties.btn)); + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.btn), + ); checkField._initValues(style); } @@ -487,17 +567,21 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { void save() { super.save(); if (checkField.form != null) { - final Map checkValue = - _createTemplate(_checkedTemplate); + final Map checkValue = _createTemplate( + _checkedTemplate, + ); _checkedTemplate = checkValue['template']; - final Map unCheckValue = - _createTemplate(_uncheckedTemplate); + final Map unCheckValue = _createTemplate( + _uncheckedTemplate, + ); _uncheckedTemplate = unCheckValue['template']; - final Map pressedValue = - _createTemplate(_pressedCheckedTemplate); + final Map pressedValue = _createTemplate( + _pressedCheckedTemplate, + ); _pressedCheckedTemplate = pressedValue['template']; - final Map unPressedValue = - _createTemplate(_pressedUncheckedTemplate); + final Map unPressedValue = _createTemplate( + _pressedUncheckedTemplate, + ); _pressedUncheckedTemplate = unPressedValue['template']; widget!.extendedAppearance!.normal.activate = _checkedTemplate; widget!.extendedAppearance!.normal.off = _uncheckedTemplate; @@ -514,8 +598,10 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { Map _createTemplate(PdfTemplate? template) { if (template == null) { - template = - PdfTemplate(widget!.bounds.size.width, widget!.bounds.size.height); + template = PdfTemplate( + widget!.bounds.size.width, + widget!.bounds.size.height, + ); } else { template.reset(widget!.bounds.size.width, widget!.bounds.size.height); } @@ -534,28 +620,39 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { (checkField as PdfCheckBoxField)._helper.drawCheckAppearance(); } else if (checkField is PdfRadioButtonListItem) { PdfRadioButtonListItemHelper.getHelper( - checkField as PdfRadioButtonListItem) - .drawCheckAppearance(); + checkField as PdfRadioButtonListItem, + ).drawCheckAppearance(); } } /// internal method - void applyAppearance(PdfDictionary? widget, PdfCheckFieldBase? item, - [PdfFieldItem? fieldItem]) { + void applyAppearance( + PdfDictionary? widget, + PdfCheckFieldBase? item, [ + PdfFieldItem? fieldItem, + ]) { if (widget != null && item != null) { - if (item._checkBaseHelper.dictionary! - .containsKey(PdfDictionaryProperties.v) && + if (item._checkBaseHelper.dictionary!.containsKey( + PdfDictionaryProperties.v, + ) && item is! PdfRadioButtonListItem) { widget.setName( - PdfName(PdfDictionaryProperties.v), PdfDictionaryProperties.yes); - widget.setName(PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.yes); - } else if (!item._checkBaseHelper.dictionary! - .containsKey(PdfDictionaryProperties.v) && + PdfName(PdfDictionaryProperties.v), + PdfDictionaryProperties.yes, + ); + widget.setName( + PdfName(PdfDictionaryProperties.usageApplication), + PdfDictionaryProperties.yes, + ); + } else if (!item._checkBaseHelper.dictionary!.containsKey( + PdfDictionaryProperties.v, + ) && item is! PdfRadioButtonListItem) { widget.remove(PdfDictionaryProperties.v); - widget.setName(PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); + widget.setName( + PdfName(PdfDictionaryProperties.usageApplication), + PdfDictionaryProperties.off, + ); } } else if (widget != null && fieldItem != null) { widget = PdfFieldItemHelper.getHelper(fieldItem).dictionary; @@ -563,8 +660,9 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { widget = item!._checkBaseHelper.dictionary; } if ((widget != null) && (widget.containsKey(PdfDictionaryProperties.ap))) { - final PdfDictionary? appearance = crossTable! - .getObject(widget[PdfDictionaryProperties.ap]) as PdfDictionary?; + final PdfDictionary? appearance = + crossTable!.getObject(widget[PdfDictionaryProperties.ap]) + as PdfDictionary?; if ((appearance != null) && (appearance.containsKey(PdfDictionaryProperties.n))) { String? value = ''; @@ -574,63 +672,97 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { rect = item.bounds; } else if (fieldItem != null) { value = getItemValue( - widget, - PdfFieldHelper.getHelper( - PdfFieldItemHelper.getHelper(fieldItem).field) - .crossTable); + widget, + PdfFieldHelper.getHelper( + PdfFieldItemHelper.getHelper(fieldItem).field, + ).crossTable, + ); rect = fieldItem.bounds; } else { value = getItemValue(widget, crossTable); rect = checkField.bounds; } - IPdfPrimitive? holder = - PdfCrossTable.dereference(appearance[PdfDictionaryProperties.n]); + IPdfPrimitive? holder = PdfCrossTable.dereference( + appearance[PdfDictionaryProperties.n], + ); PdfDictionary? normal = holder as PdfDictionary?; if (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); + 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(PdfDictionaryProperties.off, - PdfReferenceHolder(unchekedTemplate)); + normal.setProperty( + PdfDictionaryProperties.off, + PdfReferenceHolder(unchekedTemplate), + ); appearance.remove(PdfDictionaryProperties.n); appearance[PdfDictionaryProperties.n] = PdfReferenceHolder(normal); } - holder = - PdfCrossTable.dereference(appearance[PdfDictionaryProperties.d]); + holder = PdfCrossTable.dereference( + appearance[PdfDictionaryProperties.d], + ); PdfDictionary? pressed = holder as PdfDictionary?; if (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(PdfDictionaryProperties.off, - PdfReferenceHolder(unchekedTemplate)); + 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( + PdfDictionaryProperties.off, + PdfReferenceHolder(unchekedTemplate), + ); pressed.setProperty(value, PdfReferenceHolder(checkedTemplate)); appearance.remove(PdfDictionaryProperties.d); appearance[PdfDictionaryProperties.d] = PdfReferenceHolder(pressed); } } widget.setProperty(PdfDictionaryProperties.ap, appearance); - } else if (PdfFormHelper.getHelper(checkField.form!) - .setAppearanceDictionary) { + } else if (PdfFormHelper.getHelper( + checkField.form!, + ).setAppearanceDictionary) { PdfFormHelper.getHelper(checkField.form!).needAppearances = true; } else if (PdfFormHelper.getHelper(form!).setAppearanceDictionary && !PdfFormHelper.getHelper(form!).needAppearances!) { final PdfDictionary dic = PdfDictionary(); - final PdfTemplate template = - PdfTemplate(checkField.bounds.width, checkField.bounds.height); + final PdfTemplate template = PdfTemplate( + checkField.bounds.width, + checkField.bounds.height, + ); drawAppearance(template); dic.setProperty(PdfDictionaryProperties.n, PdfReferenceHolder(template)); widget!.setProperty(PdfDictionaryProperties.ap, dic); @@ -641,8 +773,9 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); PdfCheckBoxStyle style = PdfCheckBoxStyle.check; if (widget.containsKey(PdfDictionaryProperties.mk)) { - final PdfDictionary bs = crossTable! - .getObject(widget[PdfDictionaryProperties.mk])! as PdfDictionary; + final PdfDictionary bs = + crossTable!.getObject(widget[PdfDictionaryProperties.mk])! + as PdfDictionary; style = _createStyle(bs); } return style; @@ -706,8 +839,9 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { break; } if (widget[PdfDictionaryProperties.mk] is PdfReferenceHolder) { - final PdfDictionary widgetDict = crossTable! - .getObject(widget[PdfDictionaryProperties.mk])! as PdfDictionary; + final PdfDictionary widgetDict = + crossTable!.getObject(widget[PdfDictionaryProperties.mk])! + as PdfDictionary; if (widgetDict.containsKey(PdfDictionaryProperties.ca)) { widgetDict[PdfDictionaryProperties.ca] = PdfString(style); } else { @@ -737,7 +871,6 @@ class PdfCheckFieldBaseHelper extends PdfFieldHelper { case PdfCheckBoxStyle.star: return 'H'; case PdfCheckBoxStyle.check: - default: return '4'; } } @@ -748,25 +881,32 @@ 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}) { + 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, + }) { _radioButtonListItemHelper = PdfRadioButtonListItemHelper(this); - _radioButtonListItemHelper.initialize(null, null, bounds, - style: style, - borderColor: borderColor, - backColor: backColor, - foreColor: foreColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + _radioButtonListItemHelper.initialize( + null, + null, + bounds, + style: style, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + ); this.value = value; _radioButtonListItemHelper.dictionary!.beginSave = _radioButtonListItemHelper.dictionaryBeginSave; @@ -775,8 +915,11 @@ class PdfRadioButtonListItem extends PdfCheckFieldBase { style = PdfCheckBoxStyle.circle; } - PdfRadioButtonListItem._loaded(PdfDictionary dictionary, - PdfCrossTable crossTable, PdfRadioButtonListField field) { + PdfRadioButtonListItem._loaded( + PdfDictionary dictionary, + PdfCrossTable crossTable, + PdfRadioButtonListField field, + ) { _radioButtonListItemHelper = PdfRadioButtonListItemHelper(this); _radioButtonListItemHelper.load(dictionary, crossTable); _radioButtonListItemHelper._field = field; @@ -787,10 +930,11 @@ class PdfRadioButtonListItem extends PdfCheckFieldBase { ///Gets or sets the value. String get value { if (_radioButtonListItemHelper.isLoadedField) { - _radioButtonListItemHelper._value = - _radioButtonListItemHelper.getItemValue( - _radioButtonListItemHelper.dictionary!, - _radioButtonListItemHelper.crossTable); + _radioButtonListItemHelper._value = _radioButtonListItemHelper + .getItemValue( + _radioButtonListItemHelper.dictionary!, + _radioButtonListItemHelper.crossTable, + ); } return _radioButtonListItemHelper._value!; } @@ -807,9 +951,10 @@ class PdfRadioButtonListItem extends PdfCheckFieldBase { /// Gets the form of the field.{Read-Only} @override - PdfForm? get form => (_radioButtonListItemHelper._field != null) - ? _radioButtonListItemHelper._field!.form - : null; + PdfForm? get form => + (_radioButtonListItemHelper._field != null) + ? _radioButtonListItemHelper._field!.form + : null; @override set style(PdfCheckBoxStyle value) { @@ -824,10 +969,10 @@ class PdfRadioButtonListItem extends PdfCheckFieldBase { } else { if (super.style != value) { super.style = value; - WidgetAnnotationHelper.getHelper(_radioButtonListItemHelper.widget!) - .widgetAppearance! - .normalCaption = - _radioButtonListItemHelper.styleToString(super.style); + WidgetAnnotationHelper.getHelper( + _radioButtonListItemHelper.widget!, + ).widgetAppearance!.normalCaption = _radioButtonListItemHelper + .styleToString(super.style); } } } @@ -847,8 +992,11 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { String? optionValue; /// internal method - static PdfRadioButtonListItem loaded(PdfDictionary dictionary, - PdfCrossTable crossTable, PdfRadioButtonListField field) { + static PdfRadioButtonListItem loaded( + PdfDictionary dictionary, + PdfCrossTable crossTable, + PdfRadioButtonListField field, + ) { return PdfRadioButtonListItem._loaded(dictionary, crossTable, field); } @@ -885,7 +1033,7 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { } else { returnValue = _value; } - return returnValue!.replaceAll(RegExp(r'\s+'), '#20'); + return PdfName.normalizeValue(returnValue)!; } /// internal method @@ -902,9 +1050,11 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { final PdfDictionary pageDic = PdfPageHelper.getHelper(page).dictionary!; PdfArray? annots; if (pageDic.containsKey(PdfDictionaryProperties.annots)) { - annots = PdfPageHelper.getHelper(page) - .crossTable! - .getObject(pageDic[PdfDictionaryProperties.annots]) as PdfArray?; + annots = + PdfPageHelper.getHelper( + page, + ).crossTable!.getObject(pageDic[PdfDictionaryProperties.annots]) + as PdfArray?; } else { annots = PdfArray(); } @@ -919,9 +1069,9 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { if (!field.page!.annotations.contains(widget!)) { field.page!.annotations.add(widget!); } - PdfPageHelper.getHelper(field.page!) - .dictionary! - .setProperty(PdfDictionaryProperties.annots, annots); + PdfPageHelper.getHelper( + field.page!, + ).dictionary!.setProperty(PdfDictionaryProperties.annots, annots); } } if (field != null) { @@ -932,27 +1082,35 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { void _setItemValue(String value) { final String str = value; if (dictionary!.containsKey(PdfDictionaryProperties.ap)) { - PdfDictionary dic = crossTable! - .getObject(dictionary![PdfDictionaryProperties.ap])! as PdfDictionary; + PdfDictionary dic = + crossTable!.getObject(dictionary![PdfDictionaryProperties.ap])! + as PdfDictionary; if (dic.containsKey(PdfDictionaryProperties.n)) { - final PdfReference normal = - crossTable!.getReference(dic[PdfDictionaryProperties.n]); + final PdfReference normal = crossTable!.getReference( + dic[PdfDictionaryProperties.n], + ); dic = crossTable!.getObject(normal)! as PdfDictionary; final String? dicValue = getItemValue(dictionary!, crossTable); if (dic.containsKey(dicValue)) { final PdfReference valRef = crossTable!.getReference(dic[dicValue]); dic.remove(base.value); dic.setProperty( - str, PdfReferenceHolder.fromReference(valRef, crossTable)); + str, + PdfReferenceHolder.fromReference(valRef, crossTable), + ); } } } if (str == _field!.selectedValue) { - dictionary! - .setName(PdfName(PdfDictionaryProperties.usageApplication), str); + dictionary!.setName( + PdfName(PdfDictionaryProperties.usageApplication), + str, + ); } else { - dictionary!.setName(PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); + dictionary!.setName( + PdfName(PdfDictionaryProperties.usageApplication), + PdfDictionaryProperties.off, + ); } } @@ -961,13 +1119,14 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { void draw() { removeAnnotationFromPage(_field!.page); final PaintParams params = PaintParams( - bounds: base.bounds, - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: base.borderStyle, - borderWidth: base.borderWidth, - shadowBrush: shadowBrush); + bounds: base.bounds, + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: base.borderStyle, + borderWidth: base.borderWidth, + shadowBrush: shadowBrush, + ); if (params.borderPen != null && params.borderWidth == 0) { params.borderWidth = 1; } @@ -976,15 +1135,18 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { state = PdfCheckFieldState.checked; } FieldPainter().drawRadioButton( - _field!.page!.graphics, params, styleToString(base.style), state); + _field!.page!.graphics, + params, + styleToString(base.style), + state, + ); } /// internal method @override Rect getBounds() { IPdfPrimitive? array; - if (array == null && - dictionary!.containsKey(PdfDictionaryProperties.rect)) { + if (dictionary!.containsKey(PdfDictionaryProperties.rect)) { array = crossTable!.getObject(dictionary![PdfDictionaryProperties.rect]); } Rect bounds; @@ -992,15 +1154,20 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { 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?; + 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); + bounds.left, + y! <= 0 ? bounds.top : y, + bounds.width, + bounds.height, + ); } else { bounds = Rect.zero; } @@ -1010,29 +1177,50 @@ class PdfRadioButtonListItemHelper extends PdfCheckFieldBaseHelper { /// internal method void drawCheckAppearance() { final PaintParams paintParams = PaintParams( - bounds: Rect.fromLTWH( - 0, 0, widget!.bounds.size.width, widget!.bounds.size.height), - backBrush: PdfSolidBrush(base.backColor), - foreBrush: PdfSolidBrush(base.foreColor), - borderPen: borderPen, - style: base.borderStyle, - borderWidth: base.borderWidth, - shadowBrush: PdfSolidBrush(base.backColor)); + bounds: Rect.fromLTWH( + 0, + 0, + widget!.bounds.size.width, + widget!.bounds.size.height, + ), + backBrush: PdfSolidBrush(base.backColor), + foreBrush: PdfSolidBrush(base.foreColor), + borderPen: borderPen, + style: base.borderStyle, + borderWidth: base.borderWidth, + shadowBrush: PdfSolidBrush(base.backColor), + ); PdfTemplate template = widget!.extendedAppearance!.normal.activate!; - FieldPainter().drawRadioButton(template.graphics, paintParams, - styleToString(base.style), PdfCheckFieldState.checked); + FieldPainter().drawRadioButton( + template.graphics, + paintParams, + styleToString(base.style), + PdfCheckFieldState.checked, + ); template = widget!.extendedAppearance!.normal.off!; - FieldPainter().drawRadioButton(template.graphics, paintParams, - styleToString(base.style), PdfCheckFieldState.unchecked); + FieldPainter().drawRadioButton( + template.graphics, + paintParams, + styleToString(base.style), + PdfCheckFieldState.unchecked, + ); template = widget!.extendedAppearance!.pressed.activate!; - FieldPainter().drawRadioButton(template.graphics, paintParams, - styleToString(base.style), PdfCheckFieldState.pressedChecked); + FieldPainter().drawRadioButton( + template.graphics, + paintParams, + styleToString(base.style), + PdfCheckFieldState.pressedChecked, + ); template = widget!.extendedAppearance!.pressed.off!; - FieldPainter().drawRadioButton(template.graphics, paintParams, - styleToString(base.style), PdfCheckFieldState.pressedUnchecked); + FieldPainter().drawRadioButton( + template.graphics, + paintParams, + styleToString(base.style), + PdfCheckFieldState.pressedUnchecked, + ); } } 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 index b4ba4b572..183ce06bf 100644 --- 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 @@ -30,32 +30,40 @@ import 'pdf_list_field_item.dart'; 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}) { + 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, + }) { _helper = PdfComboBoxFieldHelper(this); - _helper.initializeInternal(page, name, bounds, - font: font, - alignment: alignment, - items: items, - borderColor: borderColor, - foreColor: foreColor, - backColor: backColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + _helper.initializeInternal( + page, + name, + bounds, + font: font, + alignment: alignment, + items: items, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + ); _helper.flags.add(FieldFlags.combo); this.editable = editable; if (selectedIndex != null) { @@ -82,7 +90,8 @@ class PdfComboBoxField extends PdfListField { /// The default value is false. bool get editable { if (_helper.isLoadedField) { - _editable = _helper.isFlagPresent(FieldFlags.edit) || + _editable = + _helper.isFlagPresent(FieldFlags.edit) || _helper.flags.contains(FieldFlags.edit); } return _editable; @@ -94,25 +103,28 @@ class PdfComboBoxField extends PdfListField { _editable ? _helper.flags.add(FieldFlags.edit) : _helper.isLoadedField - ? _helper.removeFlag(FieldFlags.edit) - : _helper.flags.remove(FieldFlags.edit); + ? _helper.removeFlag(FieldFlags.edit) + : _helper.flags.remove(FieldFlags.edit); } } /// Gets or sets the selected index in the list. - int get selectedIndex => _helper.selectedIndexes[0]; + int get selectedIndex => + _helper.selectedIndexes.isEmpty ? -1 : _helper.selectedIndexes[0]; set selectedIndex(int value) { _helper.selectedIndexes = [value]; } /// Gets or sets the selected value in the list. - String get selectedValue => _helper.selectedValues[0]; + String get selectedValue => + _helper.selectedValues.isEmpty ? '' : _helper.selectedValues[0]; set selectedValue(String value) { _helper.selectedValues = [value]; } /// Gets the selected item in the list. - PdfListFieldItem? get selectedItem => _helper.selectedItems[0]; + PdfListFieldItem? get selectedItem => + _helper.selectedItems.count > 0 ? _helper.selectedItems[0] : null; } /// [PdfComboBoxField] helper @@ -125,7 +137,9 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { /// internal method static PdfComboBoxField loadComboBox( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfComboBoxField._load(dictionary, crossTable); } @@ -139,36 +153,44 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { void drawAppearance(PdfTemplate template) { super.drawAppearance(template); final PaintParams params = PaintParams( - bounds: Rect.fromLTWH( - 0, 0, comboBoxField.bounds.width, comboBoxField.bounds.height), - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: comboBoxField.borderStyle, - borderWidth: comboBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: Rect.fromLTWH( + 0, + 0, + comboBoxField.bounds.width, + comboBoxField.bounds.height, + ), + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: comboBoxField.borderStyle, + borderWidth: comboBoxField.borderWidth, + shadowBrush: shadowBrush, + ); FieldPainter().drawRectangularControl(template.graphics!, params); if (comboBoxField.selectedIndex != -1 && comboBoxField.items[comboBoxField.selectedIndex].text != '' && PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(comboBoxField.page!).document!) - .conformanceLevel == + PdfPageHelper.getHelper(comboBoxField.page!).document!, + ).conformanceLevel == PdfConformanceLevel.none) { - final int multiplier = params.style == PdfBorderStyle.beveled || - params.style == PdfBorderStyle.inset - ? 2 - : 1; + 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!); + 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( - comboBoxField.items[comboBoxField.selectedIndex].text, - comboBoxField.font ?? PdfStandardFont(PdfFontFamily.timesRoman, 12), - brush: params.foreBrush, - bounds: rectangle, - format: format); + comboBoxField.items[comboBoxField.selectedIndex].text, + comboBoxField.font ?? PdfStandardFont(PdfFontFamily.timesRoman, 12), + brush: params.foreBrush, + bounds: rectangle, + format: format, + ); } } @@ -182,29 +204,37 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { void _applyAppearance(PdfDictionary widget) { if (widget.containsKey(PdfDictionaryProperties.ap) && !PdfFormHelper.getHelper(comboBoxField.form!).needAppearances!) { - final IPdfPrimitive? appearance = - crossTable!.getObject(widget[PdfDictionaryProperties.ap]); + final IPdfPrimitive? appearance = crossTable!.getObject( + widget[PdfDictionaryProperties.ap], + ); if ((appearance != null) && appearance is PdfDictionary && (appearance.containsKey(PdfDictionaryProperties.n))) { final PdfTemplate template = PdfTemplate( - comboBoxField.bounds.width, comboBoxField.bounds.height); + comboBoxField.bounds.width, + comboBoxField.bounds.height, + ); _drawComboBox(template.graphics); appearance.remove(PdfDictionaryProperties.n); appearance.setProperty( - PdfDictionaryProperties.n, PdfReferenceHolder(template)); + PdfDictionaryProperties.n, + PdfReferenceHolder(template), + ); widget.setProperty(PdfDictionaryProperties.ap, appearance); } } else if (comboBoxField.form!.readOnly == true || comboBoxField.readOnly == true) { PdfFormHelper.getHelper(comboBoxField.form!).setAppearanceDictionary = true; - } else if (PdfFormHelper.getHelper(comboBoxField.form!) - .setAppearanceDictionary && + } else if (PdfFormHelper.getHelper( + comboBoxField.form!, + ).setAppearanceDictionary && !PdfFormHelper.getHelper(comboBoxField.form!).needAppearances!) { final PdfDictionary dic = PdfDictionary(); - final PdfTemplate template = - PdfTemplate(comboBoxField.bounds.width, comboBoxField.bounds.height); + final PdfTemplate template = PdfTemplate( + comboBoxField.bounds.width, + comboBoxField.bounds.height, + ); drawAppearance(template); dic.setProperty(PdfDictionaryProperties.n, PdfReferenceHolder(template)); widget.setProperty(PdfDictionaryProperties.ap, dic); @@ -218,21 +248,31 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { if (!isLoadedField && PdfAnnotationHelper.getHelper(widget!).appearance != null) { comboBoxField.page!.graphics.drawPdfTemplate( - widget!.appearance.normal, comboBoxField.bounds.topLeft); + widget!.appearance.normal, + comboBoxField.bounds.topLeft, + ); } else { final Rect rect = Rect.fromLTWH( - 0, 0, comboBoxField.bounds.width, comboBoxField.bounds.height); - final PdfFont font = comboBoxField.font ?? + 0, + 0, + comboBoxField.bounds.width, + comboBoxField.bounds.height, + ); + final PdfFont font = + comboBoxField.font ?? PdfStandardFont( - PdfFontFamily.helvetica, getFontHeight(PdfFontFamily.helvetica)); + PdfFontFamily.helvetica, + getFontHeight(PdfFontFamily.helvetica), + ); final PaintParams parameters = PaintParams( - bounds: rect, - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: comboBoxField.borderStyle, - borderWidth: comboBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: rect, + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: comboBoxField.borderStyle, + borderWidth: comboBoxField.borderWidth, + shadowBrush: shadowBrush, + ); final PdfTemplate template = PdfTemplate(rect.width, rect.height); String? text = ''; if (comboBoxField.selectedIndex != -1) { @@ -242,8 +282,9 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { dictionary!.containsKey(PdfDictionaryProperties.v) && dictionary!.containsKey(PdfDictionaryProperties.ap) && !dictionary!.containsKey(PdfDictionaryProperties.parent)) { - final IPdfPrimitive? value = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.v]); + final IPdfPrimitive? value = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.v], + ); if (value != null && value is PdfString) { text = value.value; } @@ -253,7 +294,8 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { (dictionary![PdfDictionaryProperties.dv]! as PdfString).value; } else { final IPdfPrimitive? str = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.dv]); + dictionary![PdfDictionaryProperties.dv], + ); if (str != null && str is PdfString) { text = str.value; } @@ -264,37 +306,54 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { FieldPainter().drawRectangularControl(template.graphics!, parameters); final double borderWidth = parameters.borderWidth!.toDouble(); final double doubleBorderWidth = 2 * borderWidth; - final bool padding = parameters.style == PdfBorderStyle.inset || + final bool padding = + parameters.style == PdfBorderStyle.inset || parameters.style == PdfBorderStyle.beveled; - final Offset point = padding - ? Offset(2 * doubleBorderWidth, 2 * borderWidth) - : Offset(doubleBorderWidth, borderWidth); + 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); - comboBoxField.page!.graphics - .drawPdfTemplate(template, comboBoxField.bounds.topLeft, rect.size); + point.dx, + point.dy, + width - point.dx, + parameters.bounds!.height - + (padding ? doubleBorderWidth : borderWidth), + ); + template.graphics!.drawString( + text!, + font, + brush: foreBrush, + bounds: itemTextBound, + format: format, + ); + comboBoxField.page!.graphics.drawPdfTemplate( + template, + comboBoxField.bounds.topLeft, + rect.size, + ); } else { final GraphicsProperties gp = GraphicsProperties(comboBoxField); 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); + 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 > comboBoxField.bounds.height) { setFittingFontSize(gp, prms, text!); } FieldPainter().drawComboBox( - comboBoxField.page!.graphics, prms, text, gp.font, gp.stringFormat); + comboBoxField.page!.graphics, + prms, + text, + gp.font, + gp.stringFormat, + ); } } } @@ -302,15 +361,20 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { void _drawComboBox(PdfGraphics? graphics) { final GraphicsProperties gp = GraphicsProperties(comboBoxField); gp.bounds = Rect.fromLTWH( - 0, 0, comboBoxField.bounds.width, comboBoxField.bounds.height); + 0, + 0, + comboBoxField.bounds.width, + comboBoxField.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); + 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 && comboBoxField.selectedIndex != -1 && @@ -318,18 +382,29 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { text = selectedItems[0].text; } else if (dictionary!.containsKey(PdfDictionaryProperties.dv) && !flattenField) { - final IPdfPrimitive? defaultValue = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.dv]); + final IPdfPrimitive? defaultValue = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.dv], + ); if (defaultValue != null && defaultValue is PdfString) { text = defaultValue.value; } } if (selectedItems.count == 0) { - FieldPainter().drawComboBox(graphics!, prms, comboBoxField.selectedValue, - gp.font, gp.stringFormat); + FieldPainter().drawComboBox( + graphics!, + prms, + comboBoxField.selectedValue, + gp.font, + gp.stringFormat, + ); } else if (text != null && !flattenField) { - FieldPainter() - .drawComboBox(graphics!, prms, text, gp.font, gp.stringFormat); + FieldPainter().drawComboBox( + graphics!, + prms, + text, + gp.font, + gp.stringFormat, + ); } else { FieldPainter().drawRectangularControl(graphics!, prms); } @@ -342,8 +417,9 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { final List widths = []; if (comboBoxField.selectedIndex != -1) { final PdfFont itemFont = PdfStandardFont(family, 12); - widths - .add(itemFont.measureString(comboBoxField.selectedItem!.text).width); + widths.add( + itemFont.measureString(comboBoxField.selectedItem!.text).width, + ); } else { final PdfFont sfont = PdfStandardFont(family, 12); double max = sfont.measureString(comboBoxField.items[0].text).width; @@ -355,12 +431,13 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { } } widths.sort(); - double s = widths.isNotEmpty - ? ((12 * - (comboBoxField.bounds.size.width - - 4 * comboBoxField.borderWidth)) / - widths[widths.length - 1]) - : 12; + double s = + widths.isNotEmpty + ? ((12 * + (comboBoxField.bounds.size.width - + 4 * comboBoxField.borderWidth)) / + widths[widths.length - 1]) + : 12; if (comboBoxField.selectedIndex != -1) { final PdfFont font = PdfStandardFont(family, s); final String text = comboBoxField.selectedValue; @@ -381,8 +458,11 @@ class PdfComboBoxFieldHelper extends PdfListFieldHelper { do { fontSize = fontSize - 0.001; PdfFontHelper.getHelper(font).setSize(fontSize); - final double textWidth = - PdfFontHelper.getLineWidth(font, text, format); + final double textWidth = PdfFontHelper.getLineWidth( + font, + text, + format, + ); if (fontSize < minimumFontSize) { PdfFontHelper.getHelper(font).setSize(minimumFontSize); break; 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 index 35f3fb79d..2a1a8b9a6 100644 --- 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 @@ -4,6 +4,7 @@ import 'package:xml/xml.dart'; import '../../interfaces/pdf_interface.dart'; import '../annotations/enum.dart'; +import '../annotations/json_parser.dart'; import '../annotations/pdf_annotation.dart'; import '../annotations/pdf_annotation_border.dart'; import '../annotations/pdf_annotation_collection.dart'; @@ -59,17 +60,21 @@ import 'pdf_text_box_field.dart'; abstract class PdfField implements IPdfWrapper { //Constructor /// Initializes a new instance of the [PdfField] class with the specific page and name. - void _internal(PdfPage? page, String? name, Rect bounds, - {PdfFont? font, - PdfTextAlignment? alignment, - PdfColor? borderColor, - PdfColor? foreColor, - PdfColor? backColor, - int? borderWidth, - PdfHighlightMode? highlightMode, - PdfBorderStyle? borderStyle, - String? tooltip, - PdfFieldHelper? helper}) { + void _internal( + PdfPage? page, + String? name, + Rect bounds, { + PdfFont? font, + PdfTextAlignment? alignment, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfBorderStyle? borderStyle, + String? tooltip, + PdfFieldHelper? helper, + }) { _fieldHelper = helper!; if (this is PdfSignatureField) { if (page != null && PdfPageHelper.getHelper(page).document != null) { @@ -83,8 +88,10 @@ abstract class PdfField implements IPdfWrapper { this.bounds = bounds; if (name != null) { _fieldHelper.name = name; - _fieldHelper.dictionary! - .setProperty(PdfDictionaryProperties.t, PdfString(name)); + _fieldHelper.dictionary!.setProperty( + PdfDictionaryProperties.t, + PdfString(name), + ); } if (font != null) { _fieldHelper.font = font; @@ -119,8 +126,11 @@ abstract class PdfField implements IPdfWrapper { } /// internal constructor - void _load(PdfDictionary dictionary, PdfCrossTable crossTable, - PdfFieldHelper helper) { + void _load( + PdfDictionary dictionary, + PdfCrossTable crossTable, + PdfFieldHelper helper, + ) { _fieldHelper = helper; _fieldHelper.dictionary = dictionary; _fieldHelper.crossTable = crossTable; @@ -191,10 +201,11 @@ abstract class PdfField implements IPdfWrapper { if (_fieldHelper.isLoadedField && (_mappingName == null || _mappingName!.isEmpty)) { final IPdfPrimitive? str = PdfFieldHelper.getValue( - _fieldHelper.dictionary!, - _fieldHelper.crossTable, - PdfDictionaryProperties.tm, - false); + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + PdfDictionaryProperties.tm, + false, + ); if (str != null && str is PdfString) { _mappingName = str.value; } @@ -205,8 +216,10 @@ abstract class PdfField implements IPdfWrapper { set mappingName(String value) { if (_mappingName != value) { _mappingName = value; - _fieldHelper.dictionary! - .setString(PdfDictionaryProperties.tm, _mappingName); + _fieldHelper.dictionary!.setString( + PdfDictionaryProperties.tm, + _mappingName, + ); } if (_fieldHelper.isLoadedField) { _fieldHelper.changed = true; @@ -217,10 +230,11 @@ abstract class PdfField implements IPdfWrapper { String get tooltip { if (_fieldHelper.isLoadedField && (_tooltip == null || _tooltip!.isEmpty)) { final IPdfPrimitive? str = PdfFieldHelper.getValue( - _fieldHelper.dictionary!, - _fieldHelper.crossTable, - PdfDictionaryProperties.tu, - false); + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + PdfDictionaryProperties.tu, + false, + ); if (str != null && str is PdfString) { _tooltip = str.value; } @@ -243,8 +257,9 @@ abstract class PdfField implements IPdfWrapper { /// The default value is true. bool get canExport { if (_fieldHelper.isLoadedField) { - _export = !(_fieldHelper.isFlagPresent(FieldFlags.noExport) || - _fieldHelper.flags.contains(FieldFlags.noExport)); + _export = + !(_fieldHelper.isFlagPresent(FieldFlags.noExport) || + _fieldHelper.flags.contains(FieldFlags.noExport)); } return _export; } @@ -291,9 +306,14 @@ abstract class PdfField implements IPdfWrapper { } else if (dictionary.containsKey(PdfDictionaryProperties.mediaBox)) { PdfArray? mediaBox; if (PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.mediaBox]) is PdfArray) { - mediaBox = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.mediaBox]) as PdfArray?; + dictionary[PdfDictionaryProperties.mediaBox], + ) + is PdfArray) { + mediaBox = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.mediaBox], + ) + as PdfArray?; } if ((mediaBox![0]! as PdfNumber).value! > 0 || (mediaBox[1]! as PdfNumber).value! > 0 || @@ -310,8 +330,12 @@ abstract class PdfField implements IPdfWrapper { } else { y = rect.top + rect.height; } - return Rect.fromLTWH(x == 0 ? rect.left : x, y == 0 ? rect.top : y, - rect.width, rect.height); + return Rect.fromLTWH( + x == 0 ? rect.left : x, + y == 0 ? rect.top : y, + rect.width, + rect.height, + ); } else { return _fieldHelper.widget!.bounds; } @@ -328,12 +352,14 @@ abstract class PdfField implements IPdfWrapper { PdfNumber(rect.left), PdfNumber(height - (rect.top + rect.height)), PdfNumber(rect.left + rect.width), - PdfNumber(height - rect.top) + PdfNumber(height - rect.top), ]; PdfDictionary dic = _fieldHelper.dictionary!; if (!dic.containsKey(PdfDictionaryProperties.rect)) { dic = _fieldHelper.getWidgetAnnotation( - _fieldHelper.dictionary!, _fieldHelper.crossTable); + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + ); } dic.setArray(PdfDictionaryProperties.rect, values); _fieldHelper.changed = true; @@ -349,11 +375,15 @@ abstract class PdfField implements IPdfWrapper { if (_fieldHelper.isLoadedField) { if (page != null) { final PdfDictionary annotDic = _fieldHelper.getWidgetAnnotation( - _fieldHelper.dictionary!, _fieldHelper.crossTable); - final PdfReference reference = - PdfPageHelper.getHelper(page!).crossTable!.getReference(annotDic); - _tabIndex = - PdfPageHelper.getHelper(page!).annotsReference.indexOf(reference); + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + ); + final PdfReference reference = PdfPageHelper.getHelper( + page!, + ).crossTable!.getReference(annotDic); + _tabIndex = PdfPageHelper.getHelper( + page!, + ).annotsReference.indexOf(reference); } } return _tabIndex; @@ -365,21 +395,24 @@ abstract class PdfField implements IPdfWrapper { page != null && page!.formFieldsTabOrder == PdfFormFieldsTabOrder.manual) { final PdfAnnotation annotationReference = WidgetAnnotationHelper.load( - _fieldHelper.dictionary!, _fieldHelper.crossTable!); - final PdfReference reference = PdfPageHelper.getHelper(page!) - .crossTable! - .getReference(IPdfWrapper.getElement(annotationReference)); - int index = - PdfPageHelper.getHelper(page!).annotsReference.indexOf(reference); + _fieldHelper.dictionary!, + _fieldHelper.crossTable!, + ); + final PdfReference reference = PdfPageHelper.getHelper( + page!, + ).crossTable!.getReference(IPdfWrapper.getElement(annotationReference)); + int index = PdfPageHelper.getHelper( + page!, + ).annotsReference.indexOf(reference); if (index < 0) { index = _fieldHelper.annotationIndex; } - final PdfArray? annots = - PdfAnnotationCollectionHelper.getHelper(page!.annotations) - .rearrange(reference, _tabIndex, index); - PdfPageHelper.getHelper(page!) - .dictionary! - .setProperty(PdfDictionaryProperties.annots, annots); + final PdfArray? annots = PdfAnnotationCollectionHelper.getHelper( + page!.annotations, + ).rearrange(reference, _tabIndex, index); + PdfPageHelper.getHelper( + page!, + ).dictionary!.setProperty(PdfDictionaryProperties.annots, annots); } } @@ -391,9 +424,10 @@ abstract class PdfField implements IPdfWrapper { //Implementations void _initialize() { - _fieldHelper.dictionary!.beginSave = this is PdfSignatureField - ? _fieldHelper.dictionaryBeginSave - : _dictBeginSave; + _fieldHelper.dictionary!.beginSave = + this is PdfSignatureField + ? _fieldHelper.dictionaryBeginSave + : _dictBeginSave; _fieldHelper.widget = WidgetAnnotation(); if (this is PdfSignatureField && form!.fieldAutoNaming) { _fieldHelper._createBorderPen(); @@ -404,15 +438,18 @@ abstract class PdfField implements IPdfWrapper { _fieldHelper.widget!.parent = this; if (this is! PdfSignatureField) { _fieldHelper.format = PdfStringFormat( - alignment: _fieldHelper.widget!.alignment!, - lineAlignment: PdfVerticalAlignment.middle); + alignment: _fieldHelper.widget!.alignment!, + lineAlignment: PdfVerticalAlignment.middle, + ); _fieldHelper._createBorderPen(); _fieldHelper._createBackBrush(); } final PdfArray array = PdfArray(); array.add(PdfReferenceHolder(_fieldHelper.widget)); - _fieldHelper.dictionary! - .setProperty(PdfDictionaryProperties.kids, PdfArray(array)); + _fieldHelper.dictionary!.setProperty( + PdfDictionaryProperties.kids, + PdfArray(array), + ); } _fieldHelper.widget!.defaultAppearance.fontName = 'TiRo'; } @@ -425,50 +462,74 @@ abstract class PdfField implements IPdfWrapper { String? name; PdfString? str; if (!_fieldHelper.dictionary!.containsKey(PdfDictionaryProperties.parent)) { - str = PdfFieldHelper.getValue( - _fieldHelper.dictionary!, - _fieldHelper.crossTable, - PdfDictionaryProperties.t, - false) as PdfString?; + str = + PdfFieldHelper.getValue( + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + PdfDictionaryProperties.t, + false, + ) + as PdfString?; } else { - IPdfPrimitive? dic = _fieldHelper.crossTable! - .getObject(_fieldHelper.dictionary![PdfDictionaryProperties.parent]); + IPdfPrimitive? dic = _fieldHelper.crossTable!.getObject( + _fieldHelper.dictionary![PdfDictionaryProperties.parent], + ); while (dic != null && dic is PdfDictionary && dic.containsKey(PdfDictionaryProperties.parent)) { if (dic.containsKey(PdfDictionaryProperties.t)) { - name = name == null - ? (PdfFieldHelper.getValue(dic, _fieldHelper.crossTable, - PdfDictionaryProperties.t, false)! as PdfString) - .value - : '${(PdfFieldHelper.getValue(dic, _fieldHelper.crossTable, PdfDictionaryProperties.t, false)! as PdfString).value!}.$name'; + name = + name == null + ? (PdfFieldHelper.getValue( + dic, + _fieldHelper.crossTable, + PdfDictionaryProperties.t, + false, + )! + as PdfString) + .value + : '${(PdfFieldHelper.getValue(dic, _fieldHelper.crossTable, PdfDictionaryProperties.t, false)! as PdfString).value!}.$name'; } - dic = _fieldHelper.crossTable! - .getObject(dic[PdfDictionaryProperties.parent]) as PdfDictionary?; + dic = + _fieldHelper.crossTable!.getObject( + dic[PdfDictionaryProperties.parent], + ) + as PdfDictionary?; } if (dic != null && dic is PdfDictionary && dic.containsKey(PdfDictionaryProperties.t)) { - name = name == null - ? (PdfFieldHelper.getValue(dic, _fieldHelper.crossTable, - PdfDictionaryProperties.t, false)! as PdfString) - .value - : '${(PdfFieldHelper.getValue(dic, _fieldHelper.crossTable, PdfDictionaryProperties.t, false)! as PdfString).value!}.$name'; + name = + name == null + ? (PdfFieldHelper.getValue( + dic, + _fieldHelper.crossTable, + PdfDictionaryProperties.t, + false, + )! + as PdfString) + .value + : '${(PdfFieldHelper.getValue(dic, _fieldHelper.crossTable, PdfDictionaryProperties.t, false)! as PdfString).value!}.$name'; final IPdfPrimitive? strName = PdfFieldHelper.getValue( - _fieldHelper.dictionary!, - _fieldHelper.crossTable, - PdfDictionaryProperties.t, - false); + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + PdfDictionaryProperties.t, + false, + ); if (strName != null && strName is PdfString) { name = '${name!}.${strName.value!}'; } - } else if (_fieldHelper.dictionary! - .containsKey(PdfDictionaryProperties.t)) { - str = PdfFieldHelper.getValue( - _fieldHelper.dictionary!, - _fieldHelper.crossTable, - PdfDictionaryProperties.t, - false) as PdfString?; + } else if (_fieldHelper.dictionary!.containsKey( + PdfDictionaryProperties.t, + )) { + str = + PdfFieldHelper.getValue( + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + PdfDictionaryProperties.t, + false, + ) + as PdfString?; } } if (str != null) { @@ -484,51 +545,62 @@ abstract class PdfField implements IPdfWrapper { _fieldHelper.crossTable != null) { final PdfDocument? doc = _fieldHelper.crossTable!.document; final PdfDictionary widget = _fieldHelper.getWidgetAnnotation( - _fieldHelper.dictionary!, _fieldHelper.crossTable); + _fieldHelper.dictionary!, + _fieldHelper.crossTable, + ); if (widget.containsKey(PdfDictionaryProperties.p) && PdfCrossTable.dereference(widget[PdfDictionaryProperties.p]) is! PdfNull) { - final IPdfPrimitive? pageRef = _fieldHelper.crossTable! - .getObject(widget[PdfDictionaryProperties.p]); + final IPdfPrimitive? pageRef = _fieldHelper.crossTable!.getObject( + widget[PdfDictionaryProperties.p], + ); if (pageRef != null && pageRef is PdfDictionary) { page = PdfPageCollectionHelper.getHelper(doc!.pages).getPage(pageRef); } } else { - final PdfReference widgetReference = - _fieldHelper.crossTable!.getReference(widget); + final PdfReference widgetReference = _fieldHelper.crossTable! + .getReference(widget); for (int j = 0; j < doc!.pages.count; j++) { final PdfPage loadedPage = doc.pages[j]; final PdfArray? lAnnots = PdfPageHelper.getHelper(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 (_fieldHelper.requiredReference != null && - _fieldHelper.requiredReference!.reference!.objNum == - holder.reference!.objNum && - _fieldHelper.requiredReference!.reference!.genNum == - holder.reference!.genNum) { - page = loadedPage; - return page; + final IPdfPrimitive? holder = lAnnots[i]; + if (holder != null && + holder is PdfReferenceHolder && + holder.reference != null) { + if (holder.reference!.objNum == widgetReference.objNum && + holder.reference!.genNum == widgetReference.genNum) { + page = loadedPage; + return page; + } else if (_fieldHelper.requiredReference != null && + _fieldHelper.requiredReference!.reference != null && + _fieldHelper.requiredReference!.reference!.objNum == + holder.reference!.objNum && + _fieldHelper.requiredReference!.reference!.genNum == + holder.reference!.genNum) { + page = loadedPage; + return page; + } } } } } } } - if (PdfPageHelper.getHelper(page!) - .dictionary! - .containsKey(PdfDictionaryProperties.tabs)) { - final PdfName tabName = PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.tabs]! as PdfName; - if (tabName.name == '') { - PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.tabs] = PdfName(' '); + if (page != null && + PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.tabs)) { + final IPdfPrimitive? tabName = PdfCrossTable.dereference( + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties.tabs], + ); + if (tabName != null && + ((tabName is PdfName && tabName.name == '') || + (tabName is PdfString && tabName.value == ''))) { + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .tabs] = PdfName(' '); } } return page; @@ -537,40 +609,50 @@ abstract class PdfField implements IPdfWrapper { void _addAnnotationToPage(PdfPage? page, PdfAnnotation? widget) { if (page != null && !PdfPageHelper.getHelper(page).isLoadedPage) { PdfAnnotationHelper.getHelper(widget!).dictionary!.setProperty( - PdfDictionaryProperties.t, PdfString(_fieldHelper.name!)); + PdfDictionaryProperties.t, + PdfString(_fieldHelper.name!), + ); } else { final PdfDictionary pageDic = PdfPageHelper.getHelper(page!).dictionary!; PdfArray? annots; if (pageDic.containsKey(PdfDictionaryProperties.annots)) { - final IPdfPrimitive? obj = PdfPageHelper.getHelper(page) - .crossTable! - .getObject(pageDic[PdfDictionaryProperties.annots]); + final IPdfPrimitive? obj = PdfPageHelper.getHelper( + page, + ).crossTable!.getObject(pageDic[PdfDictionaryProperties.annots]); if (obj != null && obj is PdfArray) { annots = obj; } } annots ??= PdfArray(); - PdfAnnotationHelper.getHelper(widget!) - .dictionary! - .setProperty(PdfDictionaryProperties.p, PdfReferenceHolder(page)); + PdfAnnotationHelper.getHelper(widget!).dictionary!.setProperty( + PdfDictionaryProperties.p, + PdfReferenceHolder(page), + ); form!.fieldAutoNaming ? PdfAnnotationHelper.getHelper(widget).dictionary!.setProperty( - PdfDictionaryProperties.t, PdfString(_fieldHelper.name!)) + PdfDictionaryProperties.t, + PdfString(_fieldHelper.name!), + ) : _fieldHelper.dictionary!.setProperty( - PdfDictionaryProperties.t, PdfString(_fieldHelper.name!)); + PdfDictionaryProperties.t, + PdfString(_fieldHelper.name!), + ); annots.add(PdfReferenceHolder(widget)); - PdfPageHelper.getHelper(page) - .dictionary! - .setProperty(PdfDictionaryProperties.annots, annots); + PdfPageHelper.getHelper( + page, + ).dictionary!.setProperty(PdfDictionaryProperties.annots, annots); } } List _getWidgetAnnotations( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { final List widgetAnnotationCollection = []; if (dictionary.containsKey(PdfDictionaryProperties.kids)) { - final IPdfPrimitive? array = - crossTable.getObject(dictionary[PdfDictionaryProperties.kids]); + final IPdfPrimitive? array = crossTable.getObject( + dictionary[PdfDictionaryProperties.kids], + ); if (array != null && array is PdfArray && array.count > 0) { for (int i = 0; i < array.count; i++) { final IPdfPrimitive item = array[i]!; @@ -582,8 +664,9 @@ abstract class PdfField implements IPdfWrapper { } } } else if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { - final IPdfPrimitive? type = - crossTable.getObject(dictionary[PdfDictionaryProperties.subtype]); + final IPdfPrimitive? type = crossTable.getObject( + dictionary[PdfDictionaryProperties.subtype], + ); if (type != null && type is PdfName && type.name == PdfDictionaryProperties.widget) { @@ -611,27 +694,35 @@ class PdfFieldHelper { } /// internal method - void internal(PdfPage? page, String? name, Rect bounds, - {PdfFont? font, - PdfTextAlignment? alignment, - PdfColor? borderColor, - PdfColor? foreColor, - PdfColor? backColor, - int? borderWidth, - PdfHighlightMode? highlightMode, - PdfBorderStyle? borderStyle, - String? tooltip}) { - field._internal(page, name, bounds, - font: font, - alignment: alignment, - borderColor: borderColor, - foreColor: foreColor, - backColor: backColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip, - helper: this); + void internal( + PdfPage? page, + String? name, + Rect bounds, { + PdfFont? font, + PdfTextAlignment? alignment, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfBorderStyle? borderStyle, + String? tooltip, + }) { + field._internal( + page, + name, + bounds, + font: font, + alignment: alignment, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + helper: this, + ); } /// internal field @@ -726,7 +817,7 @@ class PdfFieldHelper { } /// internal field - late _BeforeNameChangesEventHandler beforeNameChanges; + late BeforeNameChangesEventHandler beforeNameChanges; /// Gets or sets a value indicating whether to flatten this [PdfField]. bool get flattenField { @@ -745,10 +836,11 @@ class PdfFieldHelper { PdfColor get borderColor { if (isLoadedField) { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); - PdfColor bc = PdfColor(0, 0, 0); + PdfColor bc = PdfColor.empty; if (widget.containsKey(PdfDictionaryProperties.mk)) { - final IPdfPrimitive? getObject = - crossTable!.getObject(widget[PdfDictionaryProperties.mk]); + final IPdfPrimitive? getObject = crossTable!.getObject( + widget[PdfDictionaryProperties.mk], + ); if (getObject != null && getObject is PdfDictionary && getObject.containsKey(PdfDictionaryProperties.bc)) { @@ -759,9 +851,9 @@ class PdfFieldHelper { } return bc; } else { - return WidgetAnnotationHelper.getHelper(widget!) - .widgetAppearance! - .borderColor; + return WidgetAnnotationHelper.getHelper( + widget!, + ).widgetAppearance!.borderColor; } } @@ -770,7 +862,7 @@ class PdfFieldHelper { value; if (isLoadedField) { PdfFormHelper.getHelper(field.form!).setAppearanceDictionary = true; - _assignBorderColor(value); + assignBorderColor(value); if (PdfFormHelper.getHelper(field.form!).needAppearances == false) { changed = true; fieldChanged = true; @@ -780,13 +872,16 @@ class PdfFieldHelper { } /// Gets or sets the color of the background. - PdfColor get backColor => isLoadedField - ? getBackColor(false) - : WidgetAnnotationHelper.getHelper(widget!).widgetAppearance!.backColor; + PdfColor get backColor => + isLoadedField + ? getBackColor() + : WidgetAnnotationHelper.getHelper( + widget!, + ).widgetAppearance!.backColor; set backColor(PdfColor value) { if (isLoadedField) { - _assignBackColor(value); + assignBackColor(value); if (PdfFormHelper.getHelper(field.form!).needAppearances == false) { changed = true; fieldChanged = true; @@ -816,12 +911,15 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); PdfColor color = PdfColor(0, 0, 0); if (widget.containsKey(PdfDictionaryProperties.da)) { - final PdfString defaultAppearance = crossTable! - .getObject(widget[PdfDictionaryProperties.da])! as PdfString; + final PdfString defaultAppearance = + crossTable!.getObject(widget[PdfDictionaryProperties.da])! + as PdfString; color = _getForeColor(defaultAppearance.value); } else { final IPdfPrimitive? defaultAppearance = widget.getValue( - PdfDictionaryProperties.da, PdfDictionaryProperties.parent); + PdfDictionaryProperties.da, + PdfDictionaryProperties.parent, + ); if (defaultAppearance != null && defaultAppearance is PdfString) { color = _getForeColor(defaultAppearance.value); } @@ -854,15 +952,17 @@ class PdfFieldHelper { defaultAppearance.fontName = name; defaultAppearance.fontSize = height; defaultAppearance.foreColor = value; - widget[PdfDictionaryProperties.da] = - PdfString(defaultAppearance.getString()); + widget[PdfDictionaryProperties.da] = PdfString( + defaultAppearance.getString(), + ); } else if (font != null) { final PdfDefaultAppearance defaultAppearance = PdfDefaultAppearance(); defaultAppearance.fontName = font!.name; defaultAppearance.fontSize = font!.size; defaultAppearance.foreColor = value; - widget[PdfDictionaryProperties.da] = - PdfString(defaultAppearance.getString()); + widget[PdfDictionaryProperties.da] = PdfString( + defaultAppearance.getString(), + ); } PdfFormHelper.getHelper(field.form!).setAppearanceDictionary = true; } else { @@ -874,9 +974,10 @@ class PdfFieldHelper { } /// Gets or sets the width of the border. - int get borderWidth => isLoadedField - ? _obtainBorderWidth() - : widget!.widgetBorder!.width.toInt(); + int get borderWidth => + isLoadedField + ? _obtainBorderWidth() + : widget!.widgetBorder!.width.toInt(); set borderWidth(int value) { if (widget!.widgetBorder!.width != value) { widget!.widgetBorder!.width = value.toDouble(); @@ -884,9 +985,9 @@ class PdfFieldHelper { _assignBorderWidth(value); } else { value == 0 - ? WidgetAnnotationHelper.getHelper(widget!) - .widgetAppearance! - .borderColor = PdfColor(255, 255, 255) + ? WidgetAnnotationHelper.getHelper( + widget!, + ).widgetAppearance!.borderColor = PdfColor(255, 255, 255) : _createBorderPen(); } } @@ -895,9 +996,10 @@ class PdfFieldHelper { /// Gets or sets the highlighting mode. PdfHighlightMode get highlightMode => isLoadedField ? _obtainHighlightMode() : widget!.highlightMode!; - set highlightMode(PdfHighlightMode value) => isLoadedField - ? _assignHighlightMode(value) - : widget!.highlightMode = value; + set highlightMode(PdfHighlightMode value) => + isLoadedField + ? _assignHighlightMode(value) + : widget!.highlightMode = value; /// Gets or sets the border style. PdfBorderStyle get borderStyle => @@ -919,8 +1021,9 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); PdfBorderStyle style = PdfBorderStyle.solid; if (widget.containsKey(PdfDictionaryProperties.bs)) { - final PdfDictionary bs = crossTable! - .getObject(widget[PdfDictionaryProperties.bs])! as PdfDictionary; + final PdfDictionary bs = + crossTable!.getObject(widget[PdfDictionaryProperties.bs])! + as PdfDictionary; style = _createBorderStyle(bs); } return style; @@ -929,8 +1032,9 @@ class PdfFieldHelper { PdfBorderStyle _createBorderStyle(PdfDictionary bs) { PdfBorderStyle style = PdfBorderStyle.solid; if (bs.containsKey(PdfDictionaryProperties.s)) { - final IPdfPrimitive? name = - crossTable!.getObject(bs[PdfDictionaryProperties.s]); + final IPdfPrimitive? name = crossTable!.getObject( + bs[PdfDictionaryProperties.s], + ); if (name != null && name is PdfName) { switch (name.name!.toLowerCase()) { case 'd': @@ -954,8 +1058,7 @@ class PdfFieldHelper { /// Gets or sets the font. PdfFont? get font { if (isLoadedField) { - if (_internalFont != null && - !dictionary!.containsKey(PdfDictionaryProperties.kids)) { + if (_internalFont != null) { return _internalFont!; } bool? isCorrectFont = false; @@ -963,10 +1066,12 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); if (widget.containsKey(PdfDictionaryProperties.da) || dictionary!.containsKey(PdfDictionaryProperties.da)) { - IPdfPrimitive? defaultAppearance = - crossTable!.getObject(widget[PdfDictionaryProperties.da]); - defaultAppearance ??= - crossTable!.getObject(dictionary![PdfDictionaryProperties.da]); + IPdfPrimitive? defaultAppearance = crossTable!.getObject( + widget[PdfDictionaryProperties.da], + ); + defaultAppearance ??= crossTable!.getObject( + dictionary![PdfDictionaryProperties.da], + ); String? fontName; if (defaultAppearance != null && defaultAppearance is PdfString) { final Map value = _getFont(defaultAppearance.value!); @@ -975,9 +1080,9 @@ class PdfFieldHelper { fontName = value['fontName'] as String?; if (!isCorrectFont! && fontName != null) { widget.setProperty( - PdfDictionaryProperties.da, - PdfString( - defaultAppearance.value!.replaceAll(fontName, '/Helv'))); + PdfDictionaryProperties.da, + PdfString(defaultAppearance.value!.replaceAll(fontName, '/Helv')), + ); } } } @@ -998,21 +1103,26 @@ class PdfFieldHelper { defaultAppearance.fontSize = _internalFont!.size; defaultAppearance.foreColor = foreColor; final IPdfPrimitive? fontDictionary = PdfCrossTable.dereference( - PdfFormHelper.getHelper(form!) - .resources[PdfDictionaryProperties.font]); + PdfFormHelper.getHelper(form!).resources[PdfDictionaryProperties + .font], + ); if (fontDictionary != null && fontDictionary is PdfDictionary && !fontDictionary.containsKey(defaultAppearance.fontName)) { final IPdfWrapper fontWrapper = _internalFont!; final PdfDictionary? fontElement = IPdfWrapper.getElement(fontWrapper) as PdfDictionary?; - fontDictionary.items![PdfName(defaultAppearance.fontName)] = - PdfReferenceHolder(fontElement); + fontDictionary.items![PdfName( + defaultAppearance.fontName, + )] = PdfReferenceHolder(fontElement); } - final PdfDictionary widget = - getWidgetAnnotation(dictionary!, crossTable); - widget[PdfDictionaryProperties.da] = - PdfString(defaultAppearance.getString()); + final PdfDictionary widget = getWidgetAnnotation( + dictionary!, + crossTable, + ); + widget[PdfDictionaryProperties.da] = PdfString( + defaultAppearance.getString(), + ); } else { _defineDefaultAppearance(); } @@ -1030,7 +1140,9 @@ class PdfFieldHelper { } else if (widget!.textAlignment != value) { widget!.textAlignment = value; format = PdfStringFormat( - alignment: value, lineAlignment: PdfVerticalAlignment.middle); + alignment: value, + lineAlignment: PdfVerticalAlignment.middle, + ); } } @@ -1082,7 +1194,7 @@ class PdfFieldHelper { return 1 << 21; case FieldFlags.commitOnSelChange: return 1 << 26; - default: + case FieldFlags.defaultFieldFlag: return 0; } } @@ -1098,8 +1210,9 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); PdfColor c = PdfColor.empty; if (widget.containsKey(PdfDictionaryProperties.mk)) { - final IPdfPrimitive? bs = - crossTable!.getObject(widget[PdfDictionaryProperties.mk]); + final IPdfPrimitive? bs = crossTable!.getObject( + widget[PdfDictionaryProperties.mk], + ); if (bs is PdfDictionary) { IPdfPrimitive? array; if (bs.containsKey(PdfDictionaryProperties.bg)) { @@ -1120,7 +1233,7 @@ class PdfFieldHelper { set backBrush(PdfBrush? value) { if (isLoadedField && value is PdfSolidBrush) { - _assignBackColor(value.color); + assignBackColor(value.color); } else { bBrush = value; } @@ -1162,7 +1275,8 @@ class PdfFieldHelper { int flagValue = isLoadedField ? this.flagValue : 0; // ignore: avoid_function_literals_in_foreach_calls value.forEach( - (FieldFlags element) => flagValue |= _getFieldFlagsValue(element)); + (FieldFlags element) => flagValue |= _getFieldFlagsValue(element), + ); flagValues = flagValue; dictionary!.setNumber(PdfDictionaryProperties.fieldFlags, flagValues); } @@ -1175,43 +1289,54 @@ class PdfFieldHelper { /// internal method int getFlagValue() { final IPdfPrimitive? number = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.fieldFlags, true); + dictionary!, + crossTable, + PdfDictionaryProperties.fieldFlags, + true, + ); return number != null && number is PdfNumber ? number.value!.toInt() : 0; } void _defineDefaultAppearance() { if (field.form != null && _internalFont != null) { if (isLoadedField) { - final PdfDictionary widget = - getWidgetAnnotation(dictionary!, crossTable); - final PdfName name = - PdfFormHelper.getHelper(form!).resources.getName(font!); + final PdfDictionary widget = getWidgetAnnotation( + dictionary!, + crossTable, + ); + final PdfName name = PdfFormHelper.getHelper( + form!, + ).resources.getName(font!); PdfFormHelper.getHelper(form!).resources.add(_internalFont, name); PdfFormHelper.getHelper(form!).needAppearances = true; final PdfDefaultAppearance defaultAppearance = PdfDefaultAppearance(); defaultAppearance.fontName = name.name; defaultAppearance.fontSize = _internalFont!.size; defaultAppearance.foreColor = foreColor; - widget[PdfDictionaryProperties.da] = - PdfString(defaultAppearance.getString()); + widget[PdfDictionaryProperties.da] = PdfString( + defaultAppearance.getString(), + ); if (field is PdfRadioButtonListField) { final PdfRadioButtonListField radioButtonListField = field as PdfRadioButtonListField; for (int i = 0; i < radioButtonListField.items.count; i++) { final PdfRadioButtonListItem item = radioButtonListField.items[i]; - if (PdfFieldHelper.getHelper(item).font != null) + if (PdfFieldHelper.getHelper(item).font != null) { PdfFormHelper.getHelper(field.form!).resources.add( - PdfFieldHelper.getHelper(radioButtonListField.items[i]).font, - PdfName(WidgetAnnotationHelper.getHelper( - PdfFieldHelper.getHelper(item).widget!) - .pdfDefaultAppearance! - .fontName)); + PdfFieldHelper.getHelper(radioButtonListField.items[i]).font, + PdfName( + WidgetAnnotationHelper.getHelper( + PdfFieldHelper.getHelper(item).widget!, + ).pdfDefaultAppearance!.fontName, + ), + ); + } } } } else { - final PdfName name = PdfFormHelper.getHelper(field.form!) - .resources - .getName(_internalFont!); + final PdfName name = PdfFormHelper.getHelper( + field.form!, + ).resources.getName(_internalFont!); widget!.defaultAppearance.fontName = name.name; widget!.defaultAppearance.fontSize = _internalFont!.size; } @@ -1257,14 +1382,18 @@ class PdfFieldHelper { /// Gets the widget annotation. PdfDictionary getWidgetAnnotation( - PdfDictionary dictionary, PdfCrossTable? crossTable) { + PdfDictionary dictionary, + PdfCrossTable? crossTable, + ) { PdfDictionary? dic; if (dictionary.containsKey(PdfDictionaryProperties.kids)) { - final IPdfPrimitive? array = - crossTable!.getObject(dictionary[PdfDictionaryProperties.kids]); + final IPdfPrimitive? array = crossTable!.getObject( + dictionary[PdfDictionaryProperties.kids], + ); if (array is PdfArray && array.count > 0) { - final IPdfPrimitive reference = - crossTable.getReference(array[defaultIndex]); + final IPdfPrimitive reference = crossTable.getReference( + array[defaultIndex], + ); if (reference is PdfReference) { dic = crossTable.getObject(reference) as PdfDictionary?; } @@ -1279,19 +1408,22 @@ class PdfFieldHelper { if ((font is PdfStandardFont || font is PdfCjkStandardFont) && page != null && PdfPageHelper.getHelper(page!).document != null && - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!) - .conformanceLevel != + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page!).document!, + ).conformanceLevel != PdfConformanceLevel.none) { throw ArgumentError( - 'All the fonts must be embedded in ${PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!).conformanceLevel.toString()} document.'); + 'All the fonts must be embedded in ${PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!).conformanceLevel} document.', + ); } else if (font is PdfTrueTypeFont && PdfPageHelper.getHelper(page!).document != null && - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!) - .conformanceLevel == + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page!).document!, + ).conformanceLevel == PdfConformanceLevel.a1b) { - PdfTrueTypeFontHelper.getHelper(font! as PdfTrueTypeFont) - .fontInternal - .initializeCidSet(); + PdfTrueTypeFontHelper.getHelper( + font! as PdfTrueTypeFont, + ).fontInternal.initializeCidSet(); } } } @@ -1308,8 +1440,18 @@ class PdfFieldHelper { double height = fontNameDic['height'] as double; PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, height); IPdfPrimitive? fontDictionary = crossTable!.getObject( - PdfFormHelper.getHelper(field.form!) - .resources[PdfDictionaryProperties.font]); + PdfFormHelper.getHelper(field.form!).resources[PdfDictionaryProperties + .font], + ); + if (height == 0) { + if (font is PdfStandardFont) { + height = getFontHeight(font.fontFamily); + if (height == 0) { + height = 12; + } + PdfFontHelper.getHelper(font).setSize(height); + } + } if (fontDictionary != null && name != null && fontDictionary is PdfDictionary && @@ -1318,15 +1460,23 @@ class PdfFieldHelper { if (fontDictionary != null && fontDictionary is PdfDictionary && fontDictionary.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName fontSubtype = crossTable! - .getObject(fontDictionary[PdfDictionaryProperties.subtype])! - as PdfName; + final PdfName fontSubtype = + crossTable!.getObject( + fontDictionary[PdfDictionaryProperties.subtype], + )! + as PdfName; if (fontSubtype.name == PdfDictionaryProperties.type1) { - final PdfName baseFont = crossTable! - .getObject(fontDictionary[PdfDictionaryProperties.baseFont])! - as PdfName; - final List fontStyle = _getFontStyle(baseFont.name!); - final dynamic fontFamilyDic = _getFontFamily(baseFont.name!); + final PdfName baseFont = + crossTable!.getObject( + fontDictionary[PdfDictionaryProperties.baseFont], + )! + as PdfName; + final List fontStyle = _getFontStyle( + PdfName.decodeName(baseFont.name)!, + ); + final dynamic fontFamilyDic = _getFontFamily( + PdfName.decodeName(baseFont.name)!, + ); final PdfFontFamily? fontFamily = fontFamilyDic['fontFamily'] as PdfFontFamily?; final String? standardName = fontFamilyDic['standardName'] as String?; @@ -1336,7 +1486,7 @@ class PdfFieldHelper { font = _updateFontEncoding(font, fontDictionary); } } else { - if (height == 0 && standardName != _getEnumName(fontFamily)) { + if (height == 0 && standardName != getEnumName(fontFamily)) { PdfDictionary? appearanceDictionary = PdfDictionary(); if (dictionary!.containsKey(PdfDictionaryProperties.ap)) { appearanceDictionary = @@ -1351,15 +1501,17 @@ class PdfFieldHelper { final PdfReferenceHolder kids = kidsArray[i]! as PdfReferenceHolder; final IPdfPrimitive? dictionary = kids.object; - appearanceDictionary = dictionary != null && - dictionary is PdfDictionary && - dictionary - .containsKey(PdfDictionaryProperties.ap) && - dictionary[PdfDictionaryProperties.ap] - is PdfDictionary - ? dictionary[PdfDictionaryProperties.ap] - as PdfDictionary? - : null; + appearanceDictionary = + dictionary != null && + dictionary is PdfDictionary && + dictionary.containsKey( + PdfDictionaryProperties.ap, + ) && + dictionary[PdfDictionaryProperties.ap] + is PdfDictionary + ? dictionary[PdfDictionaryProperties.ap] + as PdfDictionary? + : null; break; } } @@ -1368,56 +1520,84 @@ class PdfFieldHelper { if (appearanceDictionary != null && appearanceDictionary.containsKey(PdfDictionaryProperties.n)) { final IPdfPrimitive? dic = PdfCrossTable.dereference( - appearanceDictionary[PdfDictionaryProperties.n]); + appearanceDictionary[PdfDictionaryProperties.n], + ); if (dic != null && dic is PdfStream) { final PdfStream stream = dic; stream.decompress(); - final dynamic fontNameDict = - _fontName(utf8.decode(stream.dataStream!.toList())); + final dynamic fontNameDict = _fontName( + utf8.decode(stream.dataStream!.toList()), + ); height = fontNameDict['height'] as double; } } } - if (height == 0 && standardName != _getEnumName(fontFamily)) { + 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); + font = PdfStandardFont( + PdfFontFamily.helvetica, + height, + multiStyle: fontStyle, + ); } - if (standardName != _getEnumName(fontFamily)) { + if (standardName != getEnumName(fontFamily)) { font = _updateFontEncoding(font, fontDictionary); } - PdfFontHelper.getHelper(font).metrics = - _createFont(fontDictionary, height, baseFont); + PdfFontHelper.getHelper(font).metrics = _createFont( + fontDictionary, + height, + baseFont, + ); PdfFontHelper.getHelper(font).fontInternals = fontDictionary; } } else if (fontSubtype.name == 'TrueType') { - final PdfName baseFont = crossTable! - .getObject(fontDictionary[PdfDictionaryProperties.baseFont])! - as PdfName; + final PdfName baseFont = + crossTable!.getObject( + fontDictionary[PdfDictionaryProperties.baseFont], + )! + as PdfName; final List fontStyle = _getFontStyle(baseFont.name!); font = PdfStandardFont.prototype( - PdfStandardFont(PdfFontFamily.helvetica, 8), height, - multiStyle: fontStyle); + PdfStandardFont(PdfFontFamily.helvetica, 8), + height, + multiStyle: fontStyle, + ); + font = _createFontFromFontStream( + font, + fontDictionary, + height, + fontStyle, + ); final IPdfPrimitive? tempName = fontDictionary[PdfDictionaryProperties.name]; if (tempName != null && tempName is PdfName) { - if (font.name != tempName.name) { - PdfFontHelper.getHelper(font).metrics = - _createFont(fontDictionary, height, baseFont); + if (font is PdfStandardFont && font.name != tempName.name) { + final PdfFontHelper fontHelper = PdfFontHelper.getHelper(font); + final WidthTable? widthTable = fontHelper.metrics!.widthTable; + fontHelper.metrics = _createFont( + fontDictionary, + height, + baseFont, + ); + fontHelper.metrics!.widthTable = widthTable; } } } else if (fontSubtype.name == PdfDictionaryProperties.type0) { - final IPdfPrimitive? baseFont = crossTable! - .getObject(fontDictionary[PdfDictionaryProperties.baseFont]); + final IPdfPrimitive? baseFont = crossTable!.getObject( + fontDictionary[PdfDictionaryProperties.baseFont], + ); if (baseFont != null && baseFont is PdfName && _isCjkFont(baseFont.name)) { - font = PdfCjkStandardFont(_getCjkFontFamily(baseFont.name)!, height, - multiStyle: _getFontStyle(baseFont.name!)); + font = PdfCjkStandardFont( + _getCjkFontFamily(baseFont.name)!, + height, + multiStyle: _getFontStyle(baseFont.name!), + ); } else { IPdfPrimitive? descendantFontsArray; IPdfPrimitive? descendantFontsDic; @@ -1425,13 +1605,15 @@ class PdfFieldHelper { IPdfPrimitive? fontDescriptorDic; IPdfPrimitive? fontName; descendantFontsArray = crossTable!.getObject( - fontDictionary[PdfDictionaryProperties.descendantFonts]); + fontDictionary[PdfDictionaryProperties.descendantFonts], + ); if (descendantFontsArray != null && descendantFontsArray is PdfArray && descendantFontsArray.count > 0) { - descendantFontsDic = descendantFontsArray[0] is PdfDictionary - ? descendantFontsArray[0] - : (descendantFontsArray[0]! as PdfReferenceHolder).object; + descendantFontsDic = + descendantFontsArray[0] is PdfDictionary + ? descendantFontsArray[0] + : (descendantFontsArray[0]! as PdfReferenceHolder).object; } if (descendantFontsDic != null && descendantFontsDic is PdfDictionary) { @@ -1442,15 +1624,19 @@ class PdfFieldHelper { fontDescriptor is PdfReferenceHolder) { fontDescriptorDic = fontDescriptor.object; } - if (fontDescriptorDic != null && fontDescriptorDic is PdfDictionary) + if (fontDescriptorDic != null && + fontDescriptorDic is PdfDictionary) { fontName = fontDescriptorDic[PdfDictionaryProperties.fontName]; + } if (fontName != null && fontName is PdfName) { - String fontNameStr = - fontName.name!.substring(fontName.name!.indexOf('+') + 1); + String fontNameStr = fontName.name!.substring( + fontName.name!.indexOf('+') + 1, + ); final PdfFontMetrics fontMetrics = _createFont( - descendantFontsDic! as PdfDictionary, - height, - PdfName(fontNameStr)); + descendantFontsDic! as PdfDictionary, + height, + PdfName(fontNameStr), + ); if (fontNameStr.contains('PSMT')) { fontNameStr = fontNameStr.replaceAll('PSMT', ''); } @@ -1474,24 +1660,78 @@ class PdfFieldHelper { 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; - } - PdfFontHelper.getHelper(font).setSize(height); - } - } return { 'font': font, 'isCorrectFont': isCorrectFont, - 'FontName': name + 'FontName': name, }; } + PdfFont _createFontFromFontStream( + PdfFont font, + PdfDictionary fontDictionary, + double height, + List fontStyle, + ) { + if (fontDictionary.containsKey(PdfDictionaryProperties.fontDescriptor)) { + IPdfPrimitive? fontDescriptor = PdfCrossTable.dereference( + fontDictionary[PdfDictionaryProperties.fontDescriptor], + ); + if (fontDescriptor == null && + fontDescriptor is PdfDictionary && + fontDictionary.containsKey(PdfDictionaryProperties.descendantFonts)) { + final IPdfPrimitive? descendFonts = PdfCrossTable.dereference( + fontDictionary[PdfDictionaryProperties.descendantFonts], + ); + if (descendFonts != null && descendFonts is PdfArray) { + final IPdfPrimitive? descendDict = PdfCrossTable.dereference( + descendFonts[0], + ); + if (descendDict != null && + descendDict is PdfDictionary && + descendDict.containsKey(PdfDictionaryProperties.fontDescriptor)) { + fontDescriptor = PdfCrossTable.dereference( + descendDict[PdfDictionaryProperties.fontDescriptor], + ); + } + } + } + IPdfPrimitive? fontFile; + if (fontDescriptor != null && fontDescriptor is PdfDictionary) { + if (fontDescriptor.containsKey(PdfDictionaryProperties.fontFile2)) { + fontFile = PdfCrossTable.dereference( + fontDescriptor[PdfDictionaryProperties.fontFile2], + ); + } + if (fontDescriptor.containsKey('FontFile3')) { + fontFile = PdfCrossTable.dereference(fontDescriptor['FontFile3']); + } + } + if (fontFile != null && fontFile is PdfStream) { + fontFile.decompress(); + fontFile.freezeChanges(fontFile); + try { + fontFile.position = 0; + if (fontFile.dataStream != null) { + font = PdfTrueTypeFont( + fontFile.dataStream!, + height, + multiStyle: fontStyle, + ); + } + } catch (e) { + return font; + } + } + } + return font; + } + PdfFontMetrics _createFont( - PdfDictionary fontDictionary, double? height, PdfName baseFont) { + PdfDictionary fontDictionary, + double? height, + PdfName baseFont, + ) { final PdfFontMetrics fontMetrics = PdfFontMetrics(); if (fontDictionary.containsKey(PdfDictionaryProperties.fontDescriptor)) { IPdfPrimitive? createFontDictionary; @@ -1508,11 +1748,13 @@ class PdfFieldHelper { createFontDictionary is PdfDictionary) { fontMetrics.ascent = (createFontDictionary[PdfDictionaryProperties.ascent]! as PdfNumber) - .value! as double; + .value! + as double; fontMetrics.descent = (createFontDictionary[PdfDictionaryProperties.descent]! - as PdfNumber) - .value! as double; + as PdfNumber) + .value! + as double; fontMetrics.size = height!; fontMetrics.height = fontMetrics.ascent - fontMetrics.descent; fontMetrics.postScriptName = baseFont.name; @@ -1522,8 +1764,9 @@ class PdfFieldHelper { if (fontDictionary.containsKey(PdfDictionaryProperties.widths)) { if (fontDictionary[PdfDictionaryProperties.widths] is PdfReferenceHolder) { - final PdfReferenceHolder tableReference = - PdfReferenceHolder(fontDictionary[PdfDictionaryProperties.widths]); + final PdfReferenceHolder tableReference = PdfReferenceHolder( + fontDictionary[PdfDictionaryProperties.widths], + ); final PdfReferenceHolder tableArray = tableReference.object! as PdfReferenceHolder; array = tableArray.object as PdfArray?; @@ -1549,56 +1792,92 @@ class PdfFieldHelper { PdfFont? font; switch (name) { case 'CoBO': //"Courier-BoldOblique" - font = PdfStandardFont(PdfFontFamily.courier, height, - multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + font = PdfStandardFont( + PdfFontFamily.courier, + height, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic], + ); break; case 'CoBo': //"Courier-Bold" - font = PdfStandardFont(PdfFontFamily.courier, height, - style: PdfFontStyle.bold); + font = PdfStandardFont( + PdfFontFamily.courier, + height, + style: PdfFontStyle.bold, + ); break; case 'CoOb': //"Courier-Oblique" - font = PdfStandardFont(PdfFontFamily.courier, height, - style: PdfFontStyle.italic); + font = PdfStandardFont( + PdfFontFamily.courier, + height, + style: PdfFontStyle.italic, + ); break; case 'Courier': case 'Cour': //"Courier" - font = PdfStandardFont(PdfFontFamily.courier, height, - style: PdfFontStyle.regular); + font = PdfStandardFont( + PdfFontFamily.courier, + height, + style: PdfFontStyle.regular, + ); break; case 'HeBO': //"Helvetica-BoldOblique" - font = PdfStandardFont(PdfFontFamily.helvetica, height, - multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + font = PdfStandardFont( + PdfFontFamily.helvetica, + height, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic], + ); break; case 'HeBo': //"Helvetica-Bold" - font = PdfStandardFont(PdfFontFamily.helvetica, height, - style: PdfFontStyle.bold); + font = PdfStandardFont( + PdfFontFamily.helvetica, + height, + style: PdfFontStyle.bold, + ); break; case 'HeOb': //"Helvetica-Oblique" - font = PdfStandardFont(PdfFontFamily.helvetica, height, - style: PdfFontStyle.italic); + font = PdfStandardFont( + PdfFontFamily.helvetica, + height, + style: PdfFontStyle.italic, + ); break; case 'Helv': //"Helvetica" - font = PdfStandardFont(PdfFontFamily.helvetica, height, - style: PdfFontStyle.regular); + 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]); + font = PdfStandardFont( + PdfFontFamily.timesRoman, + height, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic], + ); break; case 'TiBo': // "Times-Bold" - font = PdfStandardFont(PdfFontFamily.timesRoman, height, - style: PdfFontStyle.bold); + font = PdfStandardFont( + PdfFontFamily.timesRoman, + height, + style: PdfFontStyle.bold, + ); break; case 'TiIt': // "Times-Italic" - font = PdfStandardFont(PdfFontFamily.timesRoman, height, - style: PdfFontStyle.italic); + font = PdfStandardFont( + PdfFontFamily.timesRoman, + height, + style: PdfFontStyle.italic, + ); break; case 'TiRo': // "Times-Roman" - font = PdfStandardFont(PdfFontFamily.timesRoman, height, - style: PdfFontStyle.regular); + font = PdfStandardFont( + PdfFontFamily.timesRoman, + height, + style: PdfFontStyle.regular, + ); break; case 'ZaDb': // "ZapfDingbats" font = PdfStandardFont(PdfFontFamily.zapfDingbats, height); @@ -1630,22 +1909,22 @@ class PdfFieldHelper { colour = PdfColorHelper.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)); + (_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!)); + _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); @@ -1709,7 +1988,7 @@ class PdfFieldHelper { 'HYSMyeongJo-Medium', 'MSung-Light', 'MHei-Medium', - 'HYGoThic-Medium' + 'HYGoThic-Medium', ]; for (int i = 0; i < 7; i++) { if (fontName!.contains(fontString[i])) { @@ -1727,7 +2006,7 @@ class PdfFieldHelper { 'HYSMyeongJo-Medium', 'MSung-Light', 'MHei-Medium', - 'HYGoThic-Medium' + 'HYGoThic-Medium', ]; String? value; for (int i = 0; i < 7; i++) { @@ -1832,7 +2111,7 @@ class PdfFieldHelper { 'Courier', 'TimesRoman', 'Symbol', - 'ZapfDingbats' + 'ZapfDingbats', ]; if (standardName != null && fontFamilyList.contains(standardName)) { fontFamily = PdfFontFamily.values[fontFamilyList.indexOf(standardName)]; @@ -1840,15 +2119,16 @@ class PdfFieldHelper { } return { 'fontFamily': fontFamily, - 'standardName': standardName + 'standardName': standardName, }; } PdfFont _updateFontEncoding(PdfFont font, PdfDictionary fontDictionary) { final PdfDictionary? fontInternalDictionary = PdfFontHelper.getHelper(font).fontInternals as PdfDictionary?; - if (fontDictionary.items! - .containsKey(PdfName(PdfDictionaryProperties.encoding))) { + if (fontDictionary.items!.containsKey( + PdfName(PdfDictionaryProperties.encoding), + )) { final PdfName encodingName = PdfName(PdfDictionaryProperties.encoding); final IPdfPrimitive? encodingReferenceHolder = fontDictionary.items![PdfName(PdfDictionaryProperties.encoding)]; @@ -1856,10 +2136,12 @@ class PdfFieldHelper { encodingReferenceHolder is PdfReferenceHolder) { final IPdfPrimitive? dictionary = encodingReferenceHolder.object; if (dictionary != null && dictionary is PdfDictionary) { - if (fontInternalDictionary!.items! - .containsKey(PdfName(PdfDictionaryProperties.encoding))) { - fontInternalDictionary.items! - .remove(PdfName(PdfDictionaryProperties.encoding)); + if (fontInternalDictionary!.items!.containsKey( + PdfName(PdfDictionaryProperties.encoding), + )) { + fontInternalDictionary.items!.remove( + PdfName(PdfDictionaryProperties.encoding), + ); } fontInternalDictionary.items![encodingName] = dictionary; } @@ -1867,10 +2149,12 @@ class PdfFieldHelper { final IPdfPrimitive? encodingDictionary = fontDictionary.items![PdfName(PdfDictionaryProperties.encoding)]; if (encodingDictionary != null && encodingDictionary is PdfDictionary) { - if (fontInternalDictionary!.items! - .containsKey(PdfName(PdfDictionaryProperties.encoding))) { - fontInternalDictionary.items! - .remove(PdfName(PdfDictionaryProperties.encoding)); + if (fontInternalDictionary!.items!.containsKey( + PdfName(PdfDictionaryProperties.encoding), + )) { + fontInternalDictionary.items!.remove( + PdfName(PdfDictionaryProperties.encoding), + ); } fontInternalDictionary.items![encodingName] = encodingDictionary; } @@ -1879,20 +2163,18 @@ class PdfFieldHelper { return font; } - String _getEnumName(dynamic annotText) { - final int index = annotText.toString().indexOf('.'); - final String name = annotText.toString().substring(index + 1); - return name[0].toUpperCase() + name.substring(1); - } - /// internal method void setFittingFontSize( - GraphicsProperties gp, PaintParams prms, String text) { + 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 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!; const double minimumFontSize = 0.248; if (text.endsWith(' ')) { @@ -1906,8 +2188,11 @@ class PdfFieldHelper { do { fontSize = fontSize - 0.001; PdfFontHelper.getHelper(gp.font!).setSize(fontSize); - final double textWidth = - PdfFontHelper.getLineWidth(gp.font!, text, gp.stringFormat); + final double textWidth = PdfFontHelper.getLineWidth( + gp.font!, + text, + gp.stringFormat, + ); if (fontSize < minimumFontSize) { PdfFontHelper.getHelper(gp.font!).setSize(minimumFontSize); break; @@ -1945,11 +2230,15 @@ class PdfFieldHelper { /// internal method void drawStateItem( - PdfGraphics graphics, PdfCheckFieldState state, PdfCheckFieldBase? item, - [PdfFieldItem? fieldItem]) { - final GraphicsProperties gp = item != null - ? GraphicsProperties(item) - : GraphicsProperties.fromFieldItem(fieldItem!); + 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); } @@ -1958,44 +2247,49 @@ class PdfFieldHelper { } 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); + 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 { - PdfGraphicsHelper.getHelper(graphics) - .streamWriter! - .setTextRenderingMode(0); + PdfGraphicsHelper.getHelper( + graphics, + ).streamWriter!.setTextRenderingMode(0); final PdfTemplate? stateTemplate = _getStateTemplate( - state, - item != null - ? PdfFieldHelper.getHelper(item).dictionary! - : PdfFieldItemHelper.getHelper(fieldItem!).dictionary!); + state, + item != null + ? PdfFieldHelper.getHelper(item).dictionary! + : PdfFieldItemHelper.getHelper(fieldItem!).dictionary!, + ); if (stateTemplate != null) { - final Rect bounds = item == null && fieldItem == null - ? field.bounds - : item != null + final Rect bounds = + item == null && fieldItem == null + ? field.bounds + : item != null ? item.bounds : fieldItem!.bounds; bool encryptedContent = false; if (crossTable != null && crossTable!.document != null && - PdfDocumentHelper.getHelper(crossTable!.document!) - .isLoadedDocument) { + PdfDocumentHelper.getHelper( + crossTable!.document!, + ).isLoadedDocument) { final PdfDocument? loadedDocument = crossTable!.document; if (loadedDocument != null && PdfDocumentHelper.getHelper(loadedDocument).isEncrypted) { - if (PdfSecurityHelper.getHelper(loadedDocument.security) - .encryptor - .isEncrypt! && + if (PdfSecurityHelper.getHelper( + loadedDocument.security, + ).encryptor.isEncrypt! && loadedDocument.security.encryptionOptions == - PdfEncryptionOptions.encryptAllContents) + PdfEncryptionOptions.encryptAllContents) { encryptedContent = true; + } } } final PdfStream pdfStream = @@ -2006,12 +2300,14 @@ class PdfFieldHelper { field is PdfCheckBoxField) { gp.font = null; FieldPainter().drawCheckBox( - graphics, - prms, - PdfCheckFieldBaseHelper.getHelper(field as PdfCheckBoxField) - .styleToString((field as PdfCheckBoxField).style), - state, - gp.font); + graphics, + prms, + PdfCheckFieldBaseHelper.getHelper( + field as PdfCheckBoxField, + ).styleToString((field as PdfCheckBoxField).style), + state, + gp.font, + ); } else { graphics.drawPdfTemplate(stateTemplate, bounds.topLeft, bounds.size); } @@ -2023,18 +2319,23 @@ class PdfFieldHelper { } PdfTemplate? _getStateTemplate( - PdfCheckFieldState state, PdfDictionary? itemDictionary) { + PdfCheckFieldState state, + PdfDictionary? itemDictionary, + ) { final PdfDictionary dic = itemDictionary ?? dictionary!; - final String? value = state == PdfCheckFieldState.checked - ? getItemValue(dic, crossTable) - : PdfDictionaryProperties.off; + final String? value = + state == PdfCheckFieldState.checked + ? getItemValue(dic, crossTable) + : PdfDictionaryProperties.off; PdfTemplate? template; if (dic.containsKey(PdfDictionaryProperties.ap)) { - final IPdfPrimitive? appearance = - PdfCrossTable.dereference(dic[PdfDictionaryProperties.ap]); + final IPdfPrimitive? appearance = PdfCrossTable.dereference( + dic[PdfDictionaryProperties.ap], + ); if (appearance != null && appearance is PdfDictionary) { - final IPdfPrimitive? norm = - PdfCrossTable.dereference(appearance[PdfDictionaryProperties.n]); + final IPdfPrimitive? norm = PdfCrossTable.dereference( + appearance[PdfDictionaryProperties.n], + ); if (value != null && value.isNotEmpty && norm != null && @@ -2055,34 +2356,45 @@ class PdfFieldHelper { return template; } - void _drawFields(PdfGraphics graphics, GraphicsProperties gp, - PaintParams params, PdfCheckFieldState state) { + void _drawFields( + PdfGraphics graphics, + GraphicsProperties gp, + PaintParams params, + PdfCheckFieldState state, + ) { if (gp.font!.size >= 0) { gp.font = null; } if (field is PdfCheckBoxField) { FieldPainter().drawCheckBox( - graphics, - params, - PdfCheckBoxFieldHelper.getHelper(field as PdfCheckBoxField) - .styleToString((field as PdfCheckBoxField).style), - state, - gp.font); + graphics, + params, + PdfCheckBoxFieldHelper.getHelper( + field as PdfCheckBoxField, + ).styleToString((field as PdfCheckBoxField).style), + state, + gp.font, + ); } else if (field is PdfRadioButtonListItem) { FieldPainter().drawRadioButton( - graphics, - params, - PdfRadioButtonListItemHelper.getHelper( - field as PdfRadioButtonListItem) - .styleToString((field as PdfRadioButtonListItem).style), - state); + graphics, + params, + PdfRadioButtonListItemHelper.getHelper( + field as PdfRadioButtonListItem, + ).styleToString((field as PdfRadioButtonListItem).style), + state, + ); } } /// internal method void importFieldValue(Object fieldValue) { final IPdfPrimitive? primitive = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.ft, true); + dictionary!, + crossTable, + PdfDictionaryProperties.ft, + true, + ); String? value; if (fieldValue is String) { value = fieldValue; @@ -2112,11 +2424,31 @@ class PdfFieldHelper { final PdfCheckBoxField field1 = field as PdfCheckBoxField; if (value.toUpperCase() == 'OFF' || value.toUpperCase() == 'NO') { field1.isChecked = false; + } else if (value.toUpperCase() == 'ON' || + value.toUpperCase() == 'YES') { + field1.isChecked = true; } else if (_containsExportValue( - value, field1._fieldHelper.dictionary!)) { + value, + field1._fieldHelper.dictionary!, + )) { field1.isChecked = true; - } else + } else if (field1.items != null && field1.items!.count > 0) { + bool isChecked = false; + for (int i = 0; i < field1.items!.count; i++) { + if (_containsExportValue( + value, + PdfFieldItemHelper.getHelper(field1.items![i]).dictionary!, + )) { + (field1.items![i] as PdfCheckBoxItem).checked = true; + isChecked = true; + } + } + if (!isChecked) { + field1.isChecked = false; + } + } else { field1.isChecked = false; + } } else if (field is PdfRadioButtonListField) { (field as PdfRadioButtonListField).selectedValue = value; } @@ -2143,13 +2475,15 @@ class PdfFieldHelper { case PdfBorderStyle.underline: style = 'U'; break; + // ignore: no_default_cases default: style = 'S'; break; } if (widget[PdfDictionaryProperties.bs] is PdfReferenceHolder) { - final PdfDictionary widgetDict = crossTable! - .getObject(widget[PdfDictionaryProperties.bs])! as PdfDictionary; + final PdfDictionary widgetDict = + crossTable!.getObject(widget[PdfDictionaryProperties.bs])! + as PdfDictionary; if (widgetDict.containsKey(PdfDictionaryProperties.s)) { widgetDict[PdfDictionaryProperties.s] = PdfName(style); } else { @@ -2169,9 +2503,11 @@ class PdfFieldHelper { if (!widget.containsKey(PdfDictionaryProperties.bs)) { this.widget!.widgetBorder!.borderStyle = borderStyle!; widget.setProperty( - PdfDictionaryProperties.bs, - PdfAnnotationBorderHelper.getHelper(this.widget!.widgetBorder!) - .dictionary); + PdfDictionaryProperties.bs, + PdfAnnotationBorderHelper.getHelper( + this.widget!.widgetBorder!, + ).dictionary, + ); } } if (widget.containsKey(PdfDictionaryProperties.mk) && @@ -2185,12 +2521,14 @@ class PdfFieldHelper { .dictionary! .items! .forEach((PdfName? key, IPdfPrimitive? value) { - mkDict.setProperty(key, value); - }); + mkDict.setProperty(key, value); + }); } } else { - widget.setProperty(PdfDictionaryProperties.mk, - WidgetAnnotationHelper.getHelper(this.widget!).widgetAppearance); + widget.setProperty( + PdfDictionaryProperties.mk, + WidgetAnnotationHelper.getHelper(this.widget!).widgetAppearance, + ); } } @@ -2219,16 +2557,20 @@ class PdfFieldHelper { bool _containsExportValue(String value, PdfDictionary dictionary) { bool result = false; - final PdfDictionary widgetDictionary = - getWidgetAnnotation(dictionary, crossTable); + final PdfDictionary widgetDictionary = getWidgetAnnotation( + dictionary, + crossTable, + ); if (widgetDictionary.containsKey(PdfDictionaryProperties.ap)) { - final IPdfPrimitive? appearance = - crossTable!.getObject(widgetDictionary[PdfDictionaryProperties.ap]); + final IPdfPrimitive? appearance = crossTable!.getObject( + widgetDictionary[PdfDictionaryProperties.ap], + ); if (appearance != null && appearance is PdfDictionary && appearance.containsKey(PdfDictionaryProperties.n)) { - final IPdfPrimitive? normalTemplate = - PdfCrossTable.dereference(appearance[PdfDictionaryProperties.n]); + final IPdfPrimitive? normalTemplate = PdfCrossTable.dereference( + appearance[PdfDictionaryProperties.n], + ); if (normalTemplate != null && normalTemplate is PdfDictionary && normalTemplate.containsKey(value)) { @@ -2247,27 +2589,40 @@ class PdfFieldHelper { kids = crossTable!.getObject(dictionary![PdfDictionaryProperties.kids]); if (kids != null && kids is PdfArray) { for (int i = 0; i < kids.count; i++) { - flag = flag || + flag = + flag || (kids[i] is PdfField && (kids[i]! as PdfField)._fieldHelper.isLoadedField); } } } final IPdfPrimitive? name = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.ft, true); + dictionary!, + crossTable, + PdfDictionaryProperties.ft, + true, + ); String? strValue = ''; if (name != null && name is PdfName) { switch (name.name) { case 'Tx': final IPdfPrimitive? tempName = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true); + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ); if (tempName != null && tempName is PdfString) { strValue = tempName.value; } break; case 'Ch': final IPdfPrimitive? checkBoxPrimitive = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true); + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ); if (checkBoxPrimitive != null) { final String? value = getExportValue(field, checkBoxPrimitive); if (value != null && value.isNotEmpty) { @@ -2277,7 +2632,11 @@ class PdfFieldHelper { break; case 'Btn': final IPdfPrimitive? buttonFieldPrimitive = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true); + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ); if (buttonFieldPrimitive != null) { final String? value = getExportValue(field, buttonFieldPrimitive); if (value != null && value.isNotEmpty) { @@ -2292,8 +2651,10 @@ class PdfFieldHelper { if (field is PdfRadioButtonListField) { strValue = getAppearanceStateValue(field); } else { - final PdfDictionary holder = - getWidgetAnnotation(dictionary!, crossTable); + final PdfDictionary holder = getWidgetAnnotation( + dictionary!, + crossTable, + ); final IPdfPrimitive? holderName = holder[PdfDictionaryProperties.usageApplication]; if (holderName != null && holderName is PdfName) { @@ -2313,8 +2674,8 @@ class PdfFieldHelper { field is PdfField && (field as PdfField)._fieldHelper.isLoadedField && (field as PdfField).canExport) { - final Map out = - (field as PdfField)._fieldHelper.exportField(bytes, objectID); + final Map out = (field as PdfField)._fieldHelper + .exportField(bytes, objectID); bytes = out['bytes'] as List; objectID = out['objectID'] as int; } @@ -2325,7 +2686,8 @@ class PdfFieldHelper { ..encode = ForceEncoding.ascii; final StringBuffer buffer = StringBuffer(); buffer.write( - '$objID 0 obj<
/Kids ['); + '$objID 0 obj<
/Kids [', + ); for (int i = 0; i < kids.count; i++) { final PdfField field = kids[i]! as PdfField; @@ -2353,8 +2715,8 @@ class PdfFieldHelper { final PdfString stringFieldName = PdfString(this.name!) ..encode = ForceEncoding.ascii; final PdfString buildString = PdfString( - '$objID 0 obj<
/V $strValue >>endobj\n') - ..encode = ForceEncoding.ascii; + '$objID 0 obj<
/V $strValue >>endobj\n', + )..encode = ForceEncoding.ascii; bytes.addAll(buildString.value!.codeUnits); } } @@ -2404,7 +2766,9 @@ class PdfFieldHelper { /// internal method String getAppearanceStateValue(PdfField field) { final List holders = field._getWidgetAnnotations( - field._fieldHelper.dictionary!, field._fieldHelper.crossTable!); + field._fieldHelper.dictionary!, + field._fieldHelper.crossTable!, + ); String? value; for (int i = 0; i < holders.length; i++) { final IPdfPrimitive? pdfName = @@ -2424,7 +2788,11 @@ class PdfFieldHelper { /// internal method XmlElement? exportFieldForXml() { final IPdfPrimitive? name = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.ft, true); + dictionary!, + crossTable, + PdfDictionaryProperties.ft, + true, + ); String fieldName = this.name!.replaceAll(' ', '_x0020_'); fieldName = fieldName .replaceAll(r'\', '_x005C_') @@ -2442,7 +2810,11 @@ class PdfFieldHelper { switch (name.name) { case 'Tx': final IPdfPrimitive? str = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true); + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ); if ((str != null && str is PdfString) || exportEmptyField) { element = XmlElement(XmlName(fieldName)); if (str != null && str is PdfString) { @@ -2454,7 +2826,11 @@ class PdfFieldHelper { break; case 'Ch': final IPdfPrimitive? str = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true); + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ); if (str != null && str is PdfName) { final XmlElement element = XmlElement(XmlName(fieldName)); element.innerText = str.name!; @@ -2469,7 +2845,11 @@ class PdfFieldHelper { break; case 'Btn': final IPdfPrimitive? buttonFieldPrimitive = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.v, true); + dictionary!, + crossTable, + PdfDictionaryProperties.v, + true, + ); if (buttonFieldPrimitive != null) { final String? value = getExportValue(field, buttonFieldPrimitive); if ((value != null && value.isNotEmpty) || exportEmptyField) { @@ -2491,11 +2871,14 @@ class PdfFieldHelper { } else { if (field is PdfRadioButtonListField) { element = XmlElement(XmlName(fieldName)); - element.innerText = - getAppearanceStateValue(field as PdfRadioButtonListField); + element.innerText = getAppearanceStateValue( + field as PdfRadioButtonListField, + ); } else { - final PdfDictionary holder = - getWidgetAnnotation(dictionary!, crossTable); + final PdfDictionary holder = getWidgetAnnotation( + dictionary!, + crossTable, + ); if ((holder[PdfDictionaryProperties.usageApplication] is PdfName) || exportEmptyField) { @@ -2534,12 +2917,12 @@ class PdfFieldHelper { if (page != null && page!.formFieldsTabOrder == PdfFormFieldsTabOrder.manual) { page!.annotations.remove(widget!); - PdfAnnotationCollectionHelper.getHelper(page!.annotations) - .annotations - .insert(field.tabIndex, PdfReferenceHolder(widget)); - PdfObjectCollectionHelper.getHelper(page!.annotations) - .list - .insert(field.tabIndex, widget!); + PdfAnnotationCollectionHelper.getHelper( + page!.annotations, + ).annotations.insert(field.tabIndex, PdfReferenceHolder(widget)); + PdfObjectCollectionHelper.getHelper( + page!.annotations, + ).list.insert(field.tabIndex, widget!); } if (form != null && !PdfFormHelper.getHelper(form!).needAppearances! && @@ -2554,11 +2937,13 @@ class PdfFieldHelper { String? value = ''; PdfName? name; if (dictionary.containsKey(PdfDictionaryProperties.usageApplication)) { - name = crossTable! - .getObject(dictionary[PdfDictionaryProperties.usageApplication]) - as PdfName?; + name = + crossTable!.getObject( + dictionary[PdfDictionaryProperties.usageApplication], + ) + as PdfName?; if (name != null && name.name != PdfDictionaryProperties.off) { - value = name.name; + value = PdfName.decodeName(name.name); } } if (value!.isEmpty) { @@ -2567,8 +2952,9 @@ class PdfFieldHelper { crossTable!.getObject(dictionary[PdfDictionaryProperties.ap])! as PdfDictionary; if (dic.containsKey(PdfDictionaryProperties.n)) { - final PdfReference reference = - crossTable.getReference(dic[PdfDictionaryProperties.n]); + final PdfReference reference = crossTable.getReference( + dic[PdfDictionaryProperties.n], + ); final PdfDictionary normalAppearance = crossTable.getObject(reference)! as PdfDictionary; final List list = []; @@ -2578,7 +2964,7 @@ class PdfFieldHelper { for (int i = 0; i < list.length; ++i) { name = list[i] as PdfName?; if (name!.name != PdfDictionaryProperties.off) { - value = name.name; + value = PdfName.decodeName(name.name); break; } } @@ -2596,16 +2982,20 @@ class PdfFieldHelper { page.annotations.remove(widget!); } else { final PdfDictionary pageDic = PdfPageHelper.getHelper(page).dictionary!; - final PdfArray annots = pageDic - .containsKey(PdfDictionaryProperties.annots) - ? PdfPageHelper.getHelper(page) - .crossTable! - .getObject(pageDic[PdfDictionaryProperties.annots])! as PdfArray - : PdfArray(); - final PdfAnnotationHelper helper = - PdfAnnotationHelper.getHelper(widget!); - helper.dictionary! - .setProperty(PdfDictionaryProperties.p, PdfReferenceHolder(page)); + final PdfArray annots = + pageDic.containsKey(PdfDictionaryProperties.annots) + ? PdfPageHelper.getHelper(page).crossTable!.getObject( + pageDic[PdfDictionaryProperties.annots], + )! + as PdfArray + : PdfArray(); + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + widget!, + ); + helper.dictionary!.setProperty( + PdfDictionaryProperties.p, + PdfReferenceHolder(page), + ); for (int i = 0; i < annots.count; i++) { final IPdfPrimitive? obj = annots[i]; if (obj != null && @@ -2616,9 +3006,9 @@ class PdfFieldHelper { break; } } - PdfPageHelper.getHelper(page) - .dictionary! - .setProperty(PdfDictionaryProperties.annots, annots); + PdfPageHelper.getHelper( + page, + ).dictionary!.setProperty(PdfDictionaryProperties.annots, annots); } } } @@ -2627,8 +3017,9 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); PdfPen? pen; if (widget.containsKey(PdfDictionaryProperties.mk)) { - final IPdfPrimitive? mk = - crossTable!.getObject(widget[PdfDictionaryProperties.mk]); + final IPdfPrimitive? mk = crossTable!.getObject( + widget[PdfDictionaryProperties.mk], + ); if (mk is PdfDictionary && mk.containsKey(PdfDictionaryProperties.bc)) { final PdfArray array = crossTable!.getObject(mk[PdfDictionaryProperties.bc])! as PdfArray; @@ -2655,8 +3046,9 @@ class PdfFieldHelper { if (borderStyle == PdfBorderStyle.dashed) { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); if (widget.containsKey(PdfDictionaryProperties.d)) { - final IPdfPrimitive? dashes = - crossTable!.getObject(widget[PdfDictionaryProperties.d]); + final IPdfPrimitive? dashes = crossTable!.getObject( + widget[PdfDictionaryProperties.d], + ); if (dashes != null && dashes is PdfArray) { if (dashes.count == 2) { array = [0, 0]; @@ -2685,9 +3077,11 @@ class PdfFieldHelper { void _assignHighlightMode(PdfHighlightMode? highlightMode) { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); widget.setName( - PdfName(PdfDictionaryProperties.h), - WidgetAnnotationHelper.getHelper(this.widget!) - .highlightModeToString(highlightMode)); + PdfName(PdfDictionaryProperties.h), + WidgetAnnotationHelper.getHelper( + this.widget!, + ).highlightModeToString(highlightMode), + ); changed = true; } @@ -2710,35 +3104,49 @@ class PdfFieldHelper { if (parentDictionary.containsKey(PdfDictionaryProperties.ft) && (parentDictionary[PdfDictionaryProperties.ft]! as PdfName).name == PdfDictionaryProperties.btn) { - final PdfDictionary widget = - getWidgetAnnotation(parentDictionary, crossTable); + final PdfDictionary widget = getWidgetAnnotation( + parentDictionary, + crossTable, + ); if (widget.containsKey(PdfDictionaryProperties.rect)) { - array = - crossTable!.getObject(widget[PdfDictionaryProperties.rect]); + array = crossTable!.getObject( + widget[PdfDictionaryProperties.rect], + ); } } } } if (array == null && dictionary!.containsKey(PdfDictionaryProperties.rect)) { - array = - crossTable!.getObject(dictionary![PdfDictionaryProperties.rect]); + array = crossTable!.getObject( + dictionary![PdfDictionaryProperties.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; + double y = 0.0; + + final value1 = (PdfCrossTable.dereference(array[1])! as PdfNumber).value!; + final value3 = (PdfCrossTable.dereference(array[3])! as PdfNumber).value!; + + final value1Double = value1.toDouble(); + final value3Double = value3.toDouble(); + + if (value1Double < 0) { + y = value1Double; + + if (value1Double > value3Double) { + y = y - bounds.height; } } bounds = Rect.fromLTWH( - bounds.left, y! <= 0 ? bounds.top : y, bounds.width, bounds.height); + bounds.left, + y <= 0 ? bounds.top : y, + bounds.width, + bounds.height, + ); } else { bounds = Rect.zero; } @@ -2746,12 +3154,13 @@ class PdfFieldHelper { } /// internal method - PdfColor getBackColor(bool isBrush) { + PdfColor getBackColor() { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); - PdfColor c = isBrush ? PdfColor(255, 255, 255, 255) : PdfColor(0, 0, 0); + PdfColor c = PdfColor.empty; if (widget.containsKey(PdfDictionaryProperties.mk)) { - final IPdfPrimitive? bs = - crossTable!.getObject(widget[PdfDictionaryProperties.mk]); + final IPdfPrimitive? bs = crossTable!.getObject( + widget[PdfDictionaryProperties.mk], + ); if (bs is PdfDictionary) { IPdfPrimitive? array; if (bs.containsKey(PdfDictionaryProperties.bg)) { @@ -2777,45 +3186,58 @@ class PdfFieldHelper { } switch (dim) { case 1: - color = (colors[0] > 0.0) && (colors[0] <= 1.0) - ? PdfColorHelper.fromGray(colors[0]) - : PdfColorHelper.fromGray( - colors[0].toInt().toUnsigned(8).toDouble()); + color = + (colors[0] > 0.0) && (colors[0] <= 1.0) + ? PdfColorHelper.fromGray(colors[0]) + : PdfColorHelper.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)); + 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()); + 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) { + /// internal method + void assignBackColor(PdfColor? value) { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); if (widget.containsKey(PdfDictionaryProperties.mk)) { - final PdfDictionary mk = crossTable! - .getObject(widget[PdfDictionaryProperties.mk])! as PdfDictionary; + final PdfDictionary mk = + (crossTable != null + ? crossTable!.getObject(widget[PdfDictionaryProperties.mk]) + : PdfCrossTable.dereference( + widget[PdfDictionaryProperties.mk], + ))! + as PdfDictionary; final PdfArray array = PdfColorHelper.toArray(value!); mk[PdfDictionaryProperties.bg] = array; } else { @@ -2827,17 +3249,25 @@ class PdfFieldHelper { PdfFormHelper.getHelper(field.form!).setAppearanceDictionary = true; } - void _assignBorderColor(PdfColor borderColor) { + /// internal method + void assignBorderColor(PdfColor borderColor) { if (dictionary!.containsKey(PdfDictionaryProperties.kids)) { - final IPdfPrimitive? kids = - crossTable!.getObject(dictionary![PdfDictionaryProperties.kids]); + final IPdfPrimitive? kids = crossTable!.getObject( + dictionary![PdfDictionaryProperties.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(PdfDictionaryProperties.mk)) { final IPdfPrimitive? mk = - crossTable!.getObject(widget[PdfDictionaryProperties.mk]); + crossTable != null + ? crossTable!.getObject( + widget[PdfDictionaryProperties.mk], + ) + : PdfCrossTable.dereference( + widget[PdfDictionaryProperties.mk], + ); if (mk != null && mk is PdfDictionary) { final PdfArray array = PdfColorHelper.toArray(borderColor); if (PdfColorHelper.getHelper(borderColor).alpha == 0) { @@ -2863,7 +3293,9 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); if (widget.containsKey(PdfDictionaryProperties.mk)) { final IPdfPrimitive? mk = - crossTable!.getObject(widget[PdfDictionaryProperties.mk]); + crossTable != null + ? crossTable!.getObject(widget[PdfDictionaryProperties.mk]) + : PdfCrossTable.dereference(widget[PdfDictionaryProperties.mk]); if (mk != null && mk is PdfDictionary) { final PdfArray array = PdfColorHelper.toArray(borderColor); if (PdfColorHelper.getHelper(borderColor).alpha == 0) { @@ -2888,14 +3320,17 @@ class PdfFieldHelper { int _obtainBorderWidth() { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); int width = 0; - final IPdfPrimitive? name = - crossTable!.getObject(widget[PdfDictionaryProperties.ft]); + final IPdfPrimitive? name = crossTable!.getObject( + widget[PdfDictionaryProperties.ft], + ); if (widget.containsKey(PdfDictionaryProperties.bs)) { width = 1; - final PdfDictionary bs = crossTable! - .getObject(widget[PdfDictionaryProperties.bs])! as PdfDictionary; - final IPdfPrimitive? number = - crossTable!.getObject(bs[PdfDictionaryProperties.w]); + final PdfDictionary bs = + crossTable!.getObject(widget[PdfDictionaryProperties.bs])! + as PdfDictionary; + final IPdfPrimitive? number = crossTable!.getObject( + bs[PdfDictionaryProperties.w], + ); if (number != null && number is PdfNumber) { width = number.value!.toInt(); } @@ -2909,8 +3344,9 @@ class PdfFieldHelper { final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); if (widget.containsKey(PdfDictionaryProperties.bs)) { if (widget[PdfDictionaryProperties.bs] is PdfReferenceHolder) { - final PdfDictionary widgetDict = crossTable! - .getObject(widget[PdfDictionaryProperties.bs])! as PdfDictionary; + final PdfDictionary widgetDict = + crossTable!.getObject(widget[PdfDictionaryProperties.bs])! + as PdfDictionary; if (widgetDict.containsKey(PdfDictionaryProperties.w)) { widgetDict[PdfDictionaryProperties.w] = PdfNumber(width); } else { @@ -2924,11 +3360,15 @@ class PdfFieldHelper { } else { if (!widget.containsKey(PdfDictionaryProperties.bs)) { widget.setProperty( - PdfDictionaryProperties.bs, - PdfAnnotationBorderHelper.getHelper(this.widget!.widgetBorder!) - .dictionary); - (widget[PdfDictionaryProperties.bs]! as PdfDictionary) - .setProperty(PdfDictionaryProperties.w, PdfNumber(width)); + PdfDictionaryProperties.bs, + PdfAnnotationBorderHelper.getHelper( + this.widget!.widgetBorder!, + ).dictionary, + ); + (widget[PdfDictionaryProperties.bs]! as PdfDictionary).setProperty( + PdfDictionaryProperties.w, + PdfNumber(width), + ); _createBorderPen(); } } @@ -2938,8 +3378,9 @@ class PdfFieldHelper { void _createBorderPen() { final double width = widget!.widgetBorder!.width; borderPen = PdfPen( - WidgetAnnotationHelper.getHelper(widget!).widgetAppearance!.borderColor, - width: width); + WidgetAnnotationHelper.getHelper(widget!).widgetAppearance!.borderColor, + width: width, + ); if (widget!.widgetBorder!.borderStyle == PdfBorderStyle.dashed || widget!.widgetBorder!.borderStyle == PdfBorderStyle.dot) { borderPen!.dashStyle = PdfDashStyle.custom; @@ -2970,7 +3411,9 @@ class PdfFieldHelper { dictionary![PdfDictionaryProperties.tu]; if (toolTip != null && toolTip is PdfString) { widgetAnnot.setString( - PdfDictionaryProperties.tu, toolTip.value); + PdfDictionaryProperties.tu, + toolTip.value, + ); } } } @@ -2980,8 +3423,12 @@ class PdfFieldHelper { } /// Gets the value. - static IPdfPrimitive? getValue(PdfDictionary dictionary, - PdfCrossTable? crossTable, String value, bool inheritable) { + static IPdfPrimitive? getValue( + PdfDictionary dictionary, + PdfCrossTable? crossTable, + String value, + bool inheritable, + ) { IPdfPrimitive? primitive; if (dictionary.containsKey(value)) { primitive = crossTable!.getObject(dictionary[value]); @@ -2993,17 +3440,21 @@ class PdfFieldHelper { /// Searches the in parents. static IPdfPrimitive? searchInParents( - PdfDictionary dictionary, PdfCrossTable? crossTable, String value) { + 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(PdfDictionaryProperties.parent) - ? (crossTable!.getObject(dic[PdfDictionaryProperties.parent]) - as PdfDictionary?)! - : null; + dic = + dic.containsKey(PdfDictionaryProperties.parent) + ? (crossTable!.getObject(dic[PdfDictionaryProperties.parent]) + as PdfDictionary?)! + : null; } } return primitive; @@ -3026,8 +3477,11 @@ class GraphicsProperties { if ((!field._fieldHelper.isLoadedField) && field.page != null && field.page!.rotation != PdfPageRotateAngle.rotateAngle0) { - bounds = - _rotateTextbox(field.bounds, field.page!.size, field.page!.rotation); + bounds = _rotateTextbox( + field.bounds, + field.page!.size, + field.page!.rotation, + ); } } @@ -3046,8 +3500,11 @@ class GraphicsProperties { if ((!helper.field._fieldHelper.isLoadedField) && item.page != null && item.page!.rotation != PdfPageRotateAngle.rotateAngle0) { - bounds = - _rotateTextbox(item.bounds, item.page!.size, item.page!.rotation); + bounds = _rotateTextbox( + item.bounds, + item.page!.size, + item.page!.rotation, + ); } } @@ -3083,20 +3540,32 @@ class GraphicsProperties { 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); + 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); + 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); + 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); +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 index 01711d861..a5bc5f56a 100644 --- 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 @@ -18,7 +18,10 @@ import '../primitives/pdf_reference.dart'; import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_string.dart'; import 'enum.dart'; +import 'pdf_check_box_field.dart'; import 'pdf_field.dart'; +import 'pdf_field_item_collection.dart'; +import 'pdf_form.dart'; /// Represents base class for field's group items. class PdfFieldItem { @@ -70,11 +73,13 @@ class PdfFieldItem { final PdfDocument? doc = crosstable.document; if (doc != null && PdfDocumentHelper.getHelper(doc).isLoadedDocument) { if (_helper.dictionary!.containsKey(pName)) { - final IPdfPrimitive? pageRef = crosstable - .getObject(_helper.dictionary![PdfDictionaryProperties.p]); + final IPdfPrimitive? pageRef = crosstable.getObject( + _helper.dictionary![PdfDictionaryProperties.p], + ); if (pageRef != null && pageRef is PdfDictionary) { - final PdfReference widgetReference = - crosstable.getReference(_helper.dictionary); + final PdfReference widgetReference = crosstable.getReference( + _helper.dictionary, + ); for (int i = 0; i < doc.pages.count; i++) { final PdfPage loadedPage = doc.pages[i]; final PdfArray? lAnnots = @@ -84,22 +89,40 @@ class PdfFieldItem { final IPdfPrimitive? holder = lAnnots[i]; if (holder != null && holder is PdfReferenceHolder && + holder.reference != null && holder.reference!.objNum == widgetReference.objNum && holder.reference!.genNum == widgetReference.genNum) { - _page = PdfPageCollectionHelper.getHelper(doc.pages) - .getPage(pageRef); + _page = PdfPageCollectionHelper.getHelper( + doc.pages, + ).getPage(pageRef); helper.defaultIndex = backUpIndex; return _page; } } } + if (_helper.dictionary!.containsKey( + PdfDictionaryProperties.p, + )) { + final IPdfPrimitive? itemPageDict = PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.p], + ); + final PdfDictionary pageDict = + PdfPageHelper.getHelper(loadedPage).dictionary!; + if (itemPageDict is PdfDictionary && + itemPageDict == pageDict) { + _page = loadedPage; + helper.defaultIndex = backUpIndex; + return _page; + } + } } helper.defaultIndex = backUpIndex; _page = null; } } else { - final PdfReference widgetReference = - crosstable.getReference(_helper.dictionary); + final PdfReference widgetReference = crosstable.getReference( + _helper.dictionary, + ); for (int i = 0; i < doc.pages.count; i++) { final PdfPage loadedPage = doc.pages[i]; final PdfArray? lAnnots = @@ -227,12 +250,116 @@ class PdfFieldItemHelper { static PdfFieldItemHelper getHelper(PdfFieldItem item) { return item._helper; } + + /// internal method + bool obtainCheckedStatus() { + bool check = false; + IPdfPrimitive? state; + if (dictionary!.containsKey(PdfDictionaryProperties.usageApplication)) { + state = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.usageApplication], + ); + } + if (state == null) { + final PdfFieldHelper fieldHelper = PdfFieldHelper.getHelper(field); + final IPdfPrimitive? name = PdfFieldHelper.getValue( + fieldHelper.dictionary!, + fieldHelper.crossTable, + PdfDictionaryProperties.v, + false, + ); + if (name != null && name is PdfName) { + check = + (name.name == + fieldHelper.getItemValue(dictionary!, fieldHelper.crossTable)); + } + } else if (state is PdfName) { + check = (state.name != PdfDictionaryProperties.off); + } + return check; + } + + /// internal method + void setCheckedStatus(bool check) { + final PdfFieldHelper fieldHelper = PdfFieldHelper.getHelper(field); + String? val = fieldHelper.getItemValue(dictionary!, fieldHelper.crossTable); + if (val != null) { + _uncheckOthers(val, check, fieldHelper); + } + if (check) { + if (val == null || val.isEmpty) { + val = PdfDictionaryProperties.yes; + } + fieldHelper.dictionary!.setName(PdfName(PdfDictionaryProperties.v), val); + dictionary!.setProperty( + PdfDictionaryProperties.usageApplication, + PdfName(val), + ); + dictionary!.setProperty(PdfDictionaryProperties.v, PdfName(val)); + } else { + IPdfPrimitive? v; + if (fieldHelper.dictionary!.containsKey(PdfDictionaryProperties.v)) { + v = PdfCrossTable.dereference( + fieldHelper.dictionary![PdfDictionaryProperties.v], + ); + } + if (v != null && v is PdfName && val == v.name) { + fieldHelper.dictionary!.remove(PdfDictionaryProperties.v); + } + dictionary!.setProperty( + PdfDictionaryProperties.usageApplication, + PdfName(PdfDictionaryProperties.off), + ); + } + fieldHelper.changed = true; + } + + /// internal method + void _uncheckOthers(String value, bool check, PdfFieldHelper fieldHelper) { + final PdfFieldItemCollection? items = (field as PdfCheckBoxField).items; + if (items != null && items.count > 0) { + final PdfFieldItemCollectionHelper fieldItemCollectionHelper = + PdfFieldItemCollectionHelper.getHelper(items); + if (fieldItemCollectionHelper.allowUncheck) { + fieldItemCollectionHelper.allowUncheck = false; + for (int i = 0; i < items.count; i++) { + final PdfFieldItem item = items[i]; + if (item != fieldItem && item is PdfCheckBoxItem) { + final String? val = fieldHelper.getItemValue( + PdfFieldItemHelper.getHelper(item).dictionary!, + fieldHelper.crossTable, + ); + final bool v = val != null && val == value; + if (v && check) { + item.checked = true; + } else { + item.checked = false; + } + } + } + } + fieldItemCollectionHelper.allowUncheck = true; + } + } } /// Represents loaded check box item. class PdfCheckBoxItem extends PdfFieldItem { - PdfCheckBoxItem._(PdfField field, int index, PdfDictionary? dictionary) - : super._(field, index, dictionary); + PdfCheckBoxItem._(super.field, super.index, super.dictionary) : super._(); + + //Properties + /// Gets or sets a value indicating whether the [PdfCheckBoxItem] is checked. + bool get checked => _helper.obtainCheckedStatus(); + + set checked(bool value) { + if (!_helper.field.readOnly) { + if (value != checked) { + _helper.setCheckedStatus(value); + PdfFormHelper.getHelper(_helper.field.form!).setAppearanceDictionary = + true; + } + } + } //Implementation void _setStyle(PdfCheckBoxStyle value) { @@ -266,7 +393,9 @@ class PdfCheckBoxItem extends PdfFieldItem { widgetDict[PdfDictionaryProperties.ca] = PdfString(style); } else { widgetDict.setProperty( - PdfDictionaryProperties.ca, PdfString(style)); + PdfDictionaryProperties.ca, + PdfString(style), + ); } } } else if (mk is PdfDictionary) { @@ -286,15 +415,17 @@ class PdfCheckBoxItemHelper { /// internal method static PdfCheckBoxItem getItem( - PdfField field, int index, PdfDictionary? dictionary) { + PdfField field, + int index, + PdfDictionary? dictionary, + ) { return PdfCheckBoxItem._(field, index, dictionary); } } /// Represents an item in a text box field collection. class PdfTextBoxItem extends PdfFieldItem { - PdfTextBoxItem._(PdfField field, int index, PdfDictionary? dictionary) - : super._(field, index, dictionary); + PdfTextBoxItem._(super.field, super.index, super.dictionary) : super._(); } // ignore: avoid_classes_with_only_static_members @@ -302,7 +433,10 @@ class PdfTextBoxItem extends PdfFieldItem { class PdfTextBoxItemHelper { /// internal method static PdfTextBoxItem getItem( - PdfField field, int index, PdfDictionary? dictionary) { + PdfField field, + int index, + PdfDictionary? dictionary, + ) { return PdfTextBoxItem._(field, index, dictionary); } } 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 index fafe1d46d..43e5678e6 100644 --- 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 @@ -33,7 +33,7 @@ class PdfFieldItemCollection extends PdfObjectCollection { class PdfFieldItemCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfFieldItemCollectionHelper(this.fieldItemCollection, this.field) - : super(fieldItemCollection); + : super(fieldItemCollection); /// internal field late PdfFieldItemCollection fieldItemCollection; @@ -41,6 +41,9 @@ class PdfFieldItemCollectionHelper extends PdfObjectCollectionHelper { /// internal field late PdfField field; + /// internal field + bool allowUncheck = true; + /// internal method static PdfFieldItemCollection load(PdfField field) { return PdfFieldItemCollection._(field); @@ -48,7 +51,8 @@ class PdfFieldItemCollectionHelper extends PdfObjectCollectionHelper { /// internal method static PdfFieldItemCollectionHelper getHelper( - PdfFieldItemCollection collection) { + PdfFieldItemCollection collection, + ) { return collection._helper; } @@ -61,8 +65,8 @@ class PdfFieldItemCollectionHelper extends PdfObjectCollectionHelper { void clear() { PdfFieldHelper.getHelper(field).array.clear(); list.clear(); - PdfFieldHelper.getHelper(field) - .dictionary! - .remove(PdfDictionaryProperties.kids); + PdfFieldHelper.getHelper( + field, + ).dictionary!.remove(PdfDictionaryProperties.kids); } } 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 index 056375b0c..9492eee52 100644 --- 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 @@ -28,38 +28,68 @@ class FieldPainter { /// Draws a rectangular control. void drawRectangularControl(PdfGraphics graphics, PaintParams params) { graphics.drawRectangle( - bounds: params.bounds ?? Rect.zero, brush: params.backBrush); - drawBorder(graphics, params.bounds, params.borderPen, params.style, - params.borderWidth); + bounds: params.bounds ?? Rect.zero, + 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); + graphics, + params.bounds!, + params.borderWidth!, + PdfBrushes.gray, + ); drawRightBottomShadow( - graphics, params.bounds!, params.borderWidth!, PdfBrushes.silver); + graphics, + params.bounds!, + params.borderWidth!, + PdfBrushes.silver, + ); break; case PdfBorderStyle.beveled: drawLeftTopShadow( - graphics, params.bounds!, params.borderWidth!, PdfBrushes.white); + graphics, + params.bounds!, + params.borderWidth!, + PdfBrushes.white, + ); drawRightBottomShadow( - graphics, params.bounds!, params.borderWidth!, params.shadowBrush); + graphics, + params.bounds!, + params.borderWidth!, + params.shadowBrush, + ); break; + // ignore: no_default_cases default: break; } } /// internal method - void drawCheckBox(PdfGraphics g, PaintParams paintParams, String checkSymbol, - PdfCheckFieldState state, - [PdfFont? font]) { + void drawCheckBox( + PdfGraphics g, + PaintParams paintParams, + String checkSymbol, + PdfCheckFieldState state, [ + PdfFont? font, + ]) { switch (state) { case PdfCheckFieldState.unchecked: case PdfCheckFieldState.checked: if (paintParams.borderPen != null && PdfColorHelper.getHelper(paintParams.borderPen!.color).alpha != 0) { g.drawRectangle( - brush: paintParams.backBrush, bounds: paintParams.bounds!); + brush: paintParams.backBrush, + bounds: paintParams.bounds!, + ); } break; @@ -71,55 +101,98 @@ class FieldPainter { PdfColorHelper.getHelper(paintParams.borderPen!.color).alpha != 0) { g.drawRectangle( - brush: paintParams.backBrush, bounds: paintParams.bounds!); + brush: paintParams.backBrush, + bounds: paintParams.bounds!, + ); } } else { if (paintParams.borderPen != null && PdfColorHelper.getHelper(paintParams.borderPen!.color).alpha != 0) { g.drawRectangle( - brush: paintParams.shadowBrush, bounds: paintParams.bounds!); + brush: paintParams.shadowBrush, + bounds: paintParams.bounds!, + ); } } break; } - drawBorder(g, paintParams.bounds, paintParams.borderPen, paintParams.style, - paintParams.borderWidth); + 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); + 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); + drawLeftTopShadow( + g, + paintParams.bounds!, + paintParams.borderWidth!, + paintParams.shadowBrush, + ); + drawRightBottomShadow( + g, + paintParams.bounds!, + paintParams.borderWidth!, + PdfBrushes.white, + ); break; + // ignore: no_default_cases default: } } else { switch (paintParams.style) { case PdfBorderStyle.inset: - drawLeftTopShadow(g, paintParams.bounds!, paintParams.borderWidth!, - PdfBrushes.gray); - drawRightBottomShadow(g, paintParams.bounds!, - paintParams.borderWidth!, PdfBrushes.silver); + 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); + drawLeftTopShadow( + g, + paintParams.bounds!, + paintParams.borderWidth!, + PdfBrushes.white, + ); + drawRightBottomShadow( + g, + paintParams.bounds!, + paintParams.borderWidth!, + paintParams.shadowBrush, + ); break; + // ignore: no_default_cases default: } } @@ -131,21 +204,23 @@ class FieldPainter { if (font == null) { final bool extraBorder = paintParams.style == PdfBorderStyle.beveled || - paintParams.style == PdfBorderStyle.inset; + paintParams.style == PdfBorderStyle.inset; double borderWidth = paintParams.borderWidth!.toDouble(); if (extraBorder) { borderWidth *= 2; } - double xPosition = extraBorder - ? 2.0 * paintParams.borderWidth! - : paintParams.borderWidth!.toDouble(); + 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; + size = + (paintParams.bounds!.width > paintParams.bounds!.height) + ? paintParams.bounds!.height + : paintParams.bounds!.width; final double fontSize = size - 2 * xOffset; @@ -162,54 +237,74 @@ class FieldPainter { if (size < font.size) { ArgumentError.value( - 'Font size cannot be greater than CheckBox height'); + '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)); + 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; + // ignore: no_default_cases default: } } /// Draws a border. - void drawBorder(PdfGraphics graphics, Rect? bounds, PdfPen? borderPen, - PdfBorderStyle? style, int? borderWidth) { + 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)); + 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)); + pen: borderPen, + bounds: Rect.fromLTWH( + bounds!.left + borderWidth / 2, + bounds.top + borderWidth / 2, + bounds.width - borderWidth, + bounds.height - borderWidth, + ), + ); } } } } /// internal method - void drawRadioButton(PdfGraphics? g, PaintParams paintParams, - String checkSymbol, PdfCheckFieldState state) { + 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); + drawCheckBox(g!, paintParams, checkSymbol, state); } else { switch (state) { case PdfCheckFieldState.unchecked: @@ -228,26 +323,35 @@ class FieldPainter { break; } - drawRoundBorder(g, paintParams.bounds, paintParams.borderPen, - paintParams.borderWidth); + 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!); + 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); + 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; + // ignore: no_default_cases default: break; } @@ -256,105 +360,173 @@ class FieldPainter { /// Draws the left top shadow. void drawLeftTopShadow( - PdfGraphics graphics, Rect bounds, int width, PdfBrush? brush) { + 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) + 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) { + 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) + Offset(bounds.right - width, bounds.bottom - width), ]; graphics.drawPath(PdfPath()..addPolygon(points), brush: brush); } /// internal method - void drawButton(PdfGraphics g, PaintParams paintParams, String text, - PdfFont font, PdfStringFormat? format) { + void drawButton( + PdfGraphics g, + PaintParams paintParams, + String text, + PdfFont font, + PdfStringFormat? format, + ) { drawRectangularControl(g, paintParams); final Rect? rectangle = paintParams.bounds; - g.drawString(text, font, - brush: paintParams.foreBrush, bounds: rectangle, format: format); + g.drawString( + text, + font, + brush: paintParams.foreBrush, + bounds: rectangle, + format: format, + ); } /// internal method - void drawPressedButton(PdfGraphics g, PaintParams paintParams, String text, - PdfFont font, PdfStringFormat? 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!); + brush: paintParams.shadowBrush, + bounds: paintParams.bounds!, + ); break; + // ignore: no_default_cases default: g.drawRectangle( - brush: paintParams.backBrush, bounds: paintParams.bounds!); + brush: paintParams.backBrush, + bounds: paintParams.bounds!, + ); break; } - drawBorder(g, paintParams.bounds, paintParams.borderPen, paintParams.style, - paintParams.borderWidth); + 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); + 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); + 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); + drawLeftTopShadow( + g, + paintParams.bounds!, + paintParams.borderWidth!, + paintParams.shadowBrush, + ); drawRightBottomShadow( - g, paintParams.bounds!, paintParams.borderWidth!, PdfBrushes.white); + g, + paintParams.bounds!, + paintParams.borderWidth!, + PdfBrushes.white, + ); break; + // ignore: no_default_cases default: - drawLeftTopShadow(g, paintParams.bounds!, paintParams.borderWidth!, - paintParams.shadowBrush); + drawLeftTopShadow( + g, + paintParams.bounds!, + paintParams.borderWidth!, + paintParams.shadowBrush, + ); break; } } /// internal method void drawRoundBorder( - PdfGraphics? g, Rect? bounds, PdfPen? borderPen, int? borderWidth) { + 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); + bounds!.left + borderWidth! / 2.0, + bounds.top + borderWidth / 2.0, + bounds.width - borderWidth, + bounds.height - borderWidth, + ); g!.drawEllipse(outward, pen: borderPen); } } /// internal method void drawRoundShadow( - PdfGraphics? g, PaintParams paintParams, PdfCheckFieldState state) { + PdfGraphics? g, + PaintParams paintParams, + PdfCheckFieldState state, + ) { final double borderWidth = paintParams.borderWidth!.toDouble(); final Rect rectangle = paintParams.bounds!; rectangle.inflate(-1.5 * borderWidth); @@ -369,8 +541,10 @@ class FieldPainter { case PdfCheckFieldState.pressedChecked: case PdfCheckFieldState.pressedUnchecked: leftTopPen = PdfPen(shadowColor, width: borderWidth); - rightBottomPen = - PdfPen(PdfColor(255, 255, 255), width: borderWidth); + rightBottomPen = PdfPen( + PdfColor(255, 255, 255), + width: borderWidth, + ); break; case PdfCheckFieldState.checked: @@ -378,7 +552,6 @@ class FieldPainter { leftTopPen = PdfPen(PdfColor(255, 255, 255), width: borderWidth); rightBottomPen = PdfPen(shadowColor, width: borderWidth); break; - default: } break; @@ -392,13 +565,18 @@ class FieldPainter { case PdfCheckFieldState.checked: case PdfCheckFieldState.unchecked: - leftTopPen = - PdfPen(PdfColor(255, 128, 128, 128), width: borderWidth); - rightBottomPen = - PdfPen(PdfColor(255, 192, 192, 192), width: borderWidth); + leftTopPen = PdfPen( + PdfColor(255, 128, 128, 128), + width: borderWidth, + ); + rightBottomPen = PdfPen( + PdfColor(255, 192, 192, 192), + width: borderWidth, + ); break; } break; + // ignore: no_default_cases default: } if (leftTopPen != null && rightBottomPen != null) { @@ -408,41 +586,59 @@ class FieldPainter { } /// Draws the combo box - void drawComboBox(PdfGraphics graphics, PaintParams paintParams, String? text, - PdfFont? font, PdfStringFormat? format) { + void drawComboBox( + PdfGraphics graphics, + PaintParams paintParams, + String? text, + PdfFont? font, + PdfStringFormat? format, + ) { drawRectangularControl(graphics, paintParams); final Rect? rectangle = paintParams.bounds; - graphics.drawString(text!, font!, - brush: paintParams.foreBrush, bounds: rectangle, format: format); + 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) { + 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 || + 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); + 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)); + 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 int selectedIndex in selectedItem) { @@ -456,61 +652,100 @@ class FieldPainter { x += borderWidth; width -= doubleBorderWidth; } - brush = PdfSolidBrush(PdfColor(51, 153, 255, 255)); + brush = PdfSolidBrush(PdfColor(51, 153, 255)); graphics.drawRectangle( - brush: brush, - bounds: Rect.fromLTWH(x, point.dy, width, font.height)); - brush = PdfSolidBrush(PdfColor(255, 255, 255, 255)); + brush: brush, + bounds: Rect.fromLTWH(x, point.dy, width, font.height), + ); + brush = PdfSolidBrush(PdfColor(255, 255, 255)); } final String value = item.text; - final PdfRectangle itemTextBound = - PdfRectangle(point.dx, point.dy, width - point.dx, font.height); - PdfGraphicsHelper.getHelper(graphics).layoutString(value, font, - brush: brush ?? PdfSolidBrush(PdfColor(0, 0, 0)), - layoutRectangle: itemTextBound, - format: stringFormat); + final PdfRectangle itemTextBound = PdfRectangle( + point.dx, + point.dy, + width - point.dx, + font.height, + ); + PdfGraphicsHelper.getHelper(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) { + 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; + 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!); + 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 = - PdfFontHelper.getHelper(font).metrics!.getAscent(format); - final double descent = - PdfFontHelper.getHelper(font).metrics!.getDescent(format); - final double shift = subScript - ? tempHeight - (font.height + descent) - : tempHeight - ascent; + final double ascent = PdfFontHelper.getHelper( + font, + ).metrics!.getAscent(format); + final double descent = PdfFontHelper.getHelper( + 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); + rectangle = Rect.fromLTWH( + rectangle.left, + -(rectangle.top - shift), + rectangle.width, + rectangle.height, + ); } - } - graphics.drawString(text, font, + graphics.drawString( + text, + font, brush: params.foreBrush, bounds: rectangle, format: PdfStringFormat( - alignment: format.alignment, - lineAlignment: PdfVerticalAlignment.middle)); + alignment: format.alignment, + lineAlignment: format.lineAlignment, + )..lineLimit = false, + ); + } else { + graphics.drawString( + text, + font, + brush: params.foreBrush, + bounds: rectangle, + format: PdfStringFormat( + alignment: format.alignment, + lineAlignment: PdfVerticalAlignment.middle, + )..lineLimit = false, + ); + } } /// internal method 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 index b21e6910b..2e99b3d42 100644 --- 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 @@ -39,34 +39,40 @@ class PdfForm implements IPdfWrapper { _helper = PdfFormHelper(this); _helper._fields = PdfFormFieldCollectionHelper.getCollection(); PdfFormFieldCollectionHelper.getHelper(_helper._fields!).form = this; - _helper.dictionary! - .setProperty(PdfDictionaryProperties.fields, _helper._fields); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.fields, + _helper._fields, + ); if (!_helper.isLoadedForm) { _helper.dictionary!.beginSave = _helper.beginSave; } _helper.setAppearanceDictionary = true; } - PdfForm._internal(PdfCrossTable? crossTable, - [PdfDictionary? formDictionary]) { + PdfForm._internal( + PdfCrossTable? crossTable, [ + PdfDictionary? formDictionary, + ]) { _helper = PdfFormHelper(this); _helper.isLoadedForm = true; _helper.crossTable = crossTable; _helper.dictionary!.setBoolean( - PdfDictionaryProperties.needAppearances, _helper.needAppearances); + PdfDictionaryProperties.needAppearances, + _helper.needAppearances, + ); PdfDocumentHelper.getHelper(_helper.crossTable!.document!) .catalog .beginSaveList ??= []; - PdfDocumentHelper.getHelper(_helper.crossTable!.document!) - .catalog - .beginSaveList! - .add(_helper.beginSave); + PdfDocumentHelper.getHelper( + _helper.crossTable!.document!, + ).catalog.beginSaveList!.add(_helper.beginSave); PdfDocumentHelper.getHelper(_helper.crossTable!.document!).catalog.modify(); - if (PdfDocumentHelper.getHelper(_helper.crossTable!.document!) - .catalog - .containsKey(PdfDictionaryProperties.perms)) { + if (PdfDocumentHelper.getHelper( + _helper.crossTable!.document!, + ).catalog.containsKey(PdfDictionaryProperties.perms)) { _checkPerms( - PdfDocumentHelper.getHelper(_helper.crossTable!.document!).catalog); + PdfDocumentHelper.getHelper(_helper.crossTable!.document!).catalog, + ); } if (formDictionary != null) { _initialize(formDictionary, crossTable!); @@ -115,8 +121,8 @@ class PdfForm implements IPdfWrapper { /// Specifies whether to set the default appearance for the form or not. void setDefaultAppearance(bool value) { _helper.needAppearances = value; - _helper.setAppearanceDictionary = true; - _helper._isDefaultAppearance = true; + _helper.setAppearanceDictionary = !value; + _helper._isDefaultAppearance = value; } /// Flatten all the fields available in the form. @@ -127,15 +133,24 @@ class PdfForm implements IPdfWrapper { } /// Imports form value from base 64 string to the file with the specific [DataFormat]. - void importDataFromBase64String(String base64String, DataFormat dataFormat, - [bool continueImportOnError = false]) { - importData(base64.decode(base64String).toList(), dataFormat, - continueImportOnError); + void importDataFromBase64String( + String base64String, + DataFormat dataFormat, [ + bool continueImportOnError = false, + ]) { + importData( + base64.decode(base64String).toList(), + dataFormat, + continueImportOnError, + ); } /// Imports form value to the file with the specific [DataFormat]. - void importData(List inputBytes, DataFormat dataFormat, - [bool continueImportOnError = false]) { + void importData( + List inputBytes, + DataFormat dataFormat, [ + bool continueImportOnError = false, + ]) { if (dataFormat == DataFormat.fdf) { _importDataFDF(inputBytes, continueImportOnError); } else if (dataFormat == DataFormat.json) { @@ -164,17 +179,36 @@ class PdfForm implements IPdfWrapper { /// Imports XFDF Data from the given data. void _importDataXFDF(List bytes) { - final String data = String.fromCharCodes(bytes); + final String data = utf8.decode(bytes); final XmlDocument xmlDoc = XmlDocument.parse(data); PdfField formField; for (final XmlNode node in xmlDoc.rootElement.firstElementChild!.children) { if (node is XmlElement) { final String fieldName = node.attributes.first.value; - final int index = PdfFormFieldCollectionHelper.getHelper(fields) - .getFieldIndex(fieldName); - formField = fields[index]; - final String fieldInnerValue = node.firstElementChild!.innerText; - PdfFieldHelper.getHelper(formField).importFieldValue(fieldInnerValue); + final int index = PdfFormFieldCollectionHelper.getHelper( + fields, + ).getFieldIndex(fieldName); + if (index >= 0 && index < fields.count) { + formField = fields[index]; + String? fieldInnerValue; + final List fieldInnerValues = []; + if (node.childElements.length > 1) { + for (int i = 0; i < node.childElements.length; i++) { + fieldInnerValues.add(node.childElements.elementAt(i).innerText); + } + } else { + fieldInnerValue = node.firstElementChild!.innerText; + } + if (fieldInnerValues.isNotEmpty) { + PdfFieldHelper.getHelper( + formField, + ).importFieldValue(fieldInnerValues); + } else if (fieldInnerValue != null) { + PdfFieldHelper.getHelper( + formField, + ).importFieldValue(fieldInnerValue); + } + } } } } @@ -186,11 +220,14 @@ class PdfForm implements IPdfWrapper { //Get terminal fields. _createFields(); //Gets NeedAppearance - if (_helper.dictionary! - .containsKey(PdfDictionaryProperties.needAppearances)) { - final PdfBoolean needAppearance = crossTable.getObject( - _helper.dictionary![PdfDictionaryProperties.needAppearances])! - as PdfBoolean; + if (_helper.dictionary!.containsKey( + PdfDictionaryProperties.needAppearances, + )) { + final PdfBoolean needAppearance = + crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.needAppearances], + )! + as PdfBoolean; _helper.needAppearances = needAppearance.value; _helper.setAppearanceDictionary = true; } else { @@ -199,7 +236,8 @@ class PdfForm implements IPdfWrapper { //Gets resource dictionary if (_helper.dictionary!.containsKey(PdfDictionaryProperties.dr)) { final IPdfPrimitive? resourceDictionary = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.dr]); + _helper.dictionary![PdfDictionaryProperties.dr], + ); if (resourceDictionary != null && resourceDictionary is PdfDictionary) { _helper.resources = PdfResources(resourceDictionary); } @@ -210,8 +248,9 @@ class PdfForm implements IPdfWrapper { void _createFields() { PdfArray? fields; if (_helper.dictionary!.containsKey(PdfDictionaryProperties.fields)) { - final IPdfPrimitive? obj = _helper.crossTable! - .getObject(_helper.dictionary![PdfDictionaryProperties.fields]); + final IPdfPrimitive? obj = _helper.crossTable!.getObject( + _helper.dictionary![PdfDictionaryProperties.fields], + ); if (obj != null) { fields = obj as PdfArray; } @@ -220,24 +259,28 @@ class PdfForm implements IPdfWrapper { final Queue<_NodeInfo> nodes = Queue<_NodeInfo>(); while (true && fields != null) { for (; count < fields!.count; ++count) { - final IPdfPrimitive? fieldDictionary = - _helper.crossTable!.getObject(fields[count]); + final IPdfPrimitive? fieldDictionary = _helper.crossTable!.getObject( + fields[count], + ); PdfArray? fieldKids; if (fieldDictionary != null && fieldDictionary is PdfDictionary && fieldDictionary.containsKey(PdfDictionaryProperties.kids)) { - final IPdfPrimitive? fieldKid = _helper.crossTable! - .getObject(fieldDictionary[PdfDictionaryProperties.kids]); + final IPdfPrimitive? fieldKid = _helper.crossTable!.getObject( + fieldDictionary[PdfDictionaryProperties.kids], + ); if (fieldKid != null && fieldKid is PdfArray) { fieldKids = fieldKid; for (int i = 0; i < fieldKids.count; i++) { - final IPdfPrimitive? kidsDict = - PdfCrossTable.dereference(fieldKids[i]); + final IPdfPrimitive? kidsDict = PdfCrossTable.dereference( + fieldKids[i], + ); if (kidsDict != null && kidsDict is PdfDictionary && !kidsDict.containsKey(PdfDictionaryProperties.parent)) { - kidsDict[PdfDictionaryProperties.parent] = - PdfReferenceHolder(fieldDictionary); + kidsDict[PdfDictionaryProperties.parent] = PdfReferenceHolder( + fieldDictionary, + ); } } } @@ -249,8 +292,9 @@ class PdfForm implements IPdfWrapper { } } } else { - if (!(fieldDictionary! as PdfDictionary) - .containsKey(PdfDictionaryProperties.ft) || + if (!(fieldDictionary! as PdfDictionary).containsKey( + PdfDictionaryProperties.ft, + ) || _isNode(fieldKids)) { nodes.addFirst(_NodeInfo(fields, count)); _helper.formHasKids = true; @@ -278,8 +322,11 @@ class PdfForm implements IPdfWrapper { final PdfDictionary dictionary = _helper.crossTable!.getObject(kids[0])! as PdfDictionary; if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName name = _helper.crossTable! - .getObject(dictionary[PdfDictionaryProperties.subtype])! as PdfName; + final PdfName name = + _helper.crossTable!.getObject( + dictionary[PdfDictionaryProperties.subtype], + )! + as PdfName; if (name.name != PdfDictionaryProperties.widget) { isNode = true; } @@ -311,7 +358,8 @@ class PdfForm implements IPdfWrapper { token = reader.getNextToken(); if (token != null && !token.startsWith('FDF-')) { throw ArgumentError( - 'The source is not a valid FDF file because it does not start with"%FDF-"'); + 'The source is not a valid FDF file because it does not start with"%FDF-"', + ); } } final Map> table = >{}; @@ -331,8 +379,9 @@ class PdfForm implements IPdfWrapper { PdfField? field; table.forEach((String k, List v) { try { - final int index = - PdfFormFieldCollectionHelper.getHelper(fields).getFieldIndex(k); + final int index = PdfFormFieldCollectionHelper.getHelper( + fields, + ).getFieldIndex(k); if (index == -1) { throw ArgumentError('Incorrect field name.'); } @@ -369,7 +418,8 @@ class PdfForm implements IPdfWrapper { ..encode = ForceEncoding.ascii; final StringBuffer buffer = StringBuffer(); buffer.write( - '$count 0 obj< /Fields ['); + '$count 0 obj< /Fields [', + ); for (int i = 0; i < fields.count; i++) { final PdfField field = fields[i]; final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); @@ -416,8 +466,12 @@ class PdfForm implements IPdfWrapper { return [fieldname, token]; } - void _getFieldValue(PdfReader reader, String? token, String fieldName, - Map> table) { + void _getFieldValue( + PdfReader reader, + String? token, + String fieldName, + Map> table, + ) { token = reader.getNextToken(); if (token != null && token.isNotEmpty) { if (token == '[') { @@ -425,8 +479,14 @@ class PdfForm implements IPdfWrapper { if (token != null && token.isNotEmpty) { final List fieldValues = []; while (token != ']') { - token = - _fieldValue(reader, token, true, table, fieldName, fieldValues); + token = _fieldValue( + reader, + token, + true, + table, + fieldName, + fieldValues, + ); } if (!table.containsKey(fieldName) && fieldValues.isNotEmpty) { table[fieldName] = fieldValues; @@ -439,12 +499,13 @@ class PdfForm implements IPdfWrapper { } String? _fieldValue( - PdfReader reader, - String? token, - bool isMultiSelect, - Map> table, - String fieldName, - List? fieldValues) { + PdfReader reader, + String? token, + bool isMultiSelect, + Map> table, + String fieldName, + List? fieldValues, + ) { if (token == '<') { token = reader.getNextToken(); if (token != null && token.isNotEmpty && token != '>') { @@ -524,16 +585,21 @@ class PdfForm implements IPdfWrapper { final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); if (field.canExport) { helper.exportEmptyField = exportEmptyFields; - final IPdfPrimitive? name = PdfFieldHelper.getValue(helper.dictionary!, - helper.crossTable, PdfDictionaryProperties.ft, true); + final IPdfPrimitive? name = PdfFieldHelper.getValue( + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.ft, + true, + ); if (name != null && name is PdfName) { switch (name.name) { case 'Tx': final IPdfPrimitive? fieldValue = PdfFieldHelper.getValue( - helper.dictionary!, - helper.crossTable, - PdfDictionaryProperties.v, - true); + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (fieldValue is PdfString) { xfdf.setFields(field.name!, fieldValue.value!); } else if (exportEmptyFields) { @@ -543,10 +609,11 @@ class PdfForm implements IPdfWrapper { case 'Ch': if (field is PdfListBoxField) { final IPdfPrimitive? primitive = PdfFieldHelper.getValue( - helper.dictionary!, - helper.crossTable, - PdfDictionaryProperties.v, - true); + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (primitive is PdfArray) { xfdf.setFields(field.name!, primitive); } else if (primitive is PdfString) { @@ -556,18 +623,20 @@ class PdfForm implements IPdfWrapper { } } else { final IPdfPrimitive? listField = PdfFieldHelper.getValue( - helper.dictionary!, - helper.crossTable, - PdfDictionaryProperties.v, - true); + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (listField is PdfName) { xfdf.setFields(field.name!, listField.name!); } else { final IPdfPrimitive? comboValue = PdfFieldHelper.getValue( - helper.dictionary!, - helper.crossTable, - PdfDictionaryProperties.v, - true); + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (comboValue is PdfString) { xfdf.setFields(field.name!, comboValue.value!); } else if (exportEmptyFields) { @@ -578,11 +647,17 @@ class PdfForm implements IPdfWrapper { break; case 'Btn': final IPdfPrimitive? buttonFieldPrimitive = - PdfFieldHelper.getValue(helper.dictionary!, helper.crossTable, - PdfDictionaryProperties.v, true); + PdfFieldHelper.getValue( + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (buttonFieldPrimitive != null) { - final String? value = - helper.getExportValue(field, buttonFieldPrimitive); + final String? value = helper.getExportValue( + field, + buttonFieldPrimitive, + ); if (value != null && value.isNotEmpty) { xfdf.setFields(field.name!, value); } else if (field is PdfRadioButtonListField || @@ -596,7 +671,9 @@ class PdfForm implements IPdfWrapper { } else { if (field is PdfRadioButtonListField) { xfdf.setFields( - field.name!, helper.getAppearanceStateValue(field)); + field.name!, + helper.getAppearanceStateValue(field), + ); } else { final PdfDictionary holder = helper.getWidgetAnnotation( helper.dictionary!, @@ -640,8 +717,9 @@ class PdfForm implements IPdfWrapper { } } if (fieldKey != null && fieldValue != null) { - table[_decodeXMLConversion(fieldKey)] = - _decodeXMLConversion(fieldValue); + table[PdfFormHelper.decodeXMLConversion( + fieldKey, + )] = PdfFormHelper.decodeXMLConversion(fieldValue); fieldKey = fieldValue = null; } token = reader.getNextJsonToken(); @@ -649,8 +727,9 @@ class PdfForm implements IPdfWrapper { PdfField? field; table.forEach((String k, String v) { try { - final int index = - PdfFormFieldCollectionHelper.getHelper(fields).getFieldIndex(k); + final int index = PdfFormFieldCollectionHelper.getHelper( + fields, + ).getFieldIndex(k); if (index == -1) { throw ArgumentError('Incorrect field name.'); } @@ -673,16 +752,21 @@ class PdfForm implements IPdfWrapper { final PdfField field = fields[i]; final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); if (helper.isLoadedField && field.canExport) { - final IPdfPrimitive? name = PdfFieldHelper.getValue(helper.dictionary!, - helper.crossTable, PdfDictionaryProperties.ft, true); - if (name != null && name is PdfName) + final IPdfPrimitive? name = PdfFieldHelper.getValue( + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.ft, + true, + ); + if (name != null && name is PdfName) { switch (name.name) { case 'Tx': final IPdfPrimitive? textField = PdfFieldHelper.getValue( - helper.dictionary!, - helper.crossTable, - PdfDictionaryProperties.v, - true); + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (textField != null && textField is PdfString) { table[field.name!] = textField.value!; } @@ -690,10 +774,11 @@ class PdfForm implements IPdfWrapper { case 'Ch': if (field is PdfListBoxField || field is PdfComboBoxField) { final IPdfPrimitive? listValue = PdfFieldHelper.getValue( - helper.dictionary!, - helper.crossTable, - PdfDictionaryProperties.v, - true); + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (listValue != null) { final String? value = helper.getExportValue(field, listValue); if (value != null && value.isNotEmpty) { @@ -704,11 +789,17 @@ class PdfForm implements IPdfWrapper { break; case 'Btn': final IPdfPrimitive? buttonFieldPrimitive = - PdfFieldHelper.getValue(helper.dictionary!, helper.crossTable, - PdfDictionaryProperties.v, true); + PdfFieldHelper.getValue( + helper.dictionary!, + helper.crossTable, + PdfDictionaryProperties.v, + true, + ); if (buttonFieldPrimitive != null) { - final String? value = - helper.getExportValue(field, buttonFieldPrimitive); + final String? value = helper.getExportValue( + field, + buttonFieldPrimitive, + ); if (value != null && value.isNotEmpty) { table[field.name!] = value; } else if (field is PdfRadioButtonListField || @@ -720,7 +811,9 @@ class PdfForm implements IPdfWrapper { table[field.name!] = helper.getAppearanceStateValue(field); } else { final PdfDictionary holder = helper.getWidgetAnnotation( - helper.dictionary!, helper.crossTable); + helper.dictionary!, + helper.crossTable, + ); final IPdfPrimitive? holderName = holder[PdfDictionaryProperties.usageApplication]; if (holderName != null && holderName is PdfName) { @@ -730,6 +823,7 @@ class PdfForm implements IPdfWrapper { } break; } + } } } bytes.addAll(utf8.encode('{')); @@ -758,30 +852,9 @@ class PdfForm implements IPdfWrapper { : value; } - String _decodeXMLConversion(String value) { - String newString = value; - while (newString.contains('_x')) { - final int index = newString.indexOf('_x'); - final String tempString = newString.substring(index); - if (tempString.length >= 7 && tempString[6] == '_') { - newString = newString.replaceRange(index, index + 2, '--'); - final int? charCode = - int.tryParse(value.substring(index + 2, index + 6), radix: 16); - if (charCode != null && charCode >= 0) { - value = value.replaceRange( - index, index + 7, String.fromCharCode(charCode)); - newString = newString.replaceRange(index, index + 7, '-'); - } - } else { - break; - } - } - return value; - } - /// Imports XML Data from the given data. void _importDataXml(List bytes, bool continueImportOnError) { - final String data = String.fromCharCodes(bytes); + final String data = utf8.decode(bytes); final XmlDocument document = XmlDocument.parse(data); if (document.rootElement.name.local != 'Fields') { ArgumentError.value('The XML form data stream is not valid'); @@ -807,14 +880,16 @@ class PdfForm implements IPdfWrapper { .replaceAll('_x007D_', '}') .replaceAll('_x0024_', r'$'); try { - final int index = PdfFormFieldCollectionHelper.getHelper(fields) - .getFieldIndex(fieldName); + final int index = PdfFormFieldCollectionHelper.getHelper( + fields, + ).getFieldIndex(fieldName); if (index == -1) { throw ArgumentError('Incorrect field name.'); } final PdfField formField = fields[index]; - PdfFieldHelper.getHelper(formField) - .importFieldValue(childNode.innerText); + PdfFieldHelper.getHelper( + formField, + ).importFieldValue(childNode.innerText); } catch (e) { if (!continueImportOnError) { rethrow; @@ -863,7 +938,7 @@ class PdfFormHelper { bool? needAppearances = false; /// internal field - bool setAppearanceDictionary = true; + bool setAppearanceDictionary = false; /// internal field final List fieldNames = []; @@ -893,7 +968,7 @@ class PdfFormHelper { /// internal field bool flatten = false; - bool _isDefaultAppearance = true; + bool _isDefaultAppearance = false; PdfFormFieldCollection? _fields; /// internal property @@ -908,8 +983,10 @@ class PdfFormHelper { } /// internal method - static PdfForm internal(PdfCrossTable? crossTable, - [PdfDictionary? formDictionary]) { + static PdfForm internal( + PdfCrossTable? crossTable, [ + PdfDictionary? formDictionary, + ]) { return PdfForm._internal(crossTable, formDictionary); } @@ -942,7 +1019,9 @@ class PdfFormHelper { _checkFlatten(); if (form.fields.count > 0 && setAppearanceDictionary) { dictionary!.setBoolean( - PdfDictionaryProperties.needAppearances, needAppearances); + PdfDictionaryProperties.needAppearances, + needAppearances, + ); } } else { int i = 0; @@ -954,9 +1033,12 @@ class PdfFormHelper { if (!_isDefaultAppearance) { needAppearances = false; } - if (dictionary!.containsKey(PdfDictionaryProperties.needAppearances)) + if (dictionary!.containsKey(PdfDictionaryProperties.needAppearances)) { dictionary!.setBoolean( - PdfDictionaryProperties.needAppearances, needAppearances); + PdfDictionaryProperties.needAppearances, + needAppearances, + ); + } } while (i < form.fields.count) { final PdfField field = form.fields[i]; @@ -966,6 +1048,17 @@ class PdfFormHelper { final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); if (helper.isLoadedField) { final PdfDictionary dic = helper.dictionary!; + bool isSigned = false; + if (field is PdfSignatureField) { + if (dic.containsKey(PdfDictionaryProperties.v)) { + final IPdfPrimitive? value = PdfCrossTable.dereference( + dic[PdfDictionaryProperties.v], + ); + if (value != null) { + isSigned = true; + } + } + } bool isNeedAppearance = false; if (!dic.containsKey(PdfDictionaryProperties.ap) && _isDefaultAppearance && @@ -979,16 +1072,20 @@ class PdfFormHelper { } int fieldFlag = 0; if (dic.containsKey(PdfDictionaryProperties.f)) { - final IPdfPrimitive? flag = - PdfCrossTable.dereference(dic[PdfDictionaryProperties.f]); + final IPdfPrimitive? flag = PdfCrossTable.dereference( + dic[PdfDictionaryProperties.f], + ); if (flag != null && flag is PdfNumber) { fieldFlag = flag.value!.toInt(); } } PdfArray? kids; if (helper.dictionary!.containsKey(PdfDictionaryProperties.kids)) { - kids = PdfCrossTable.dereference( - helper.dictionary![PdfDictionaryProperties.kids]) as PdfArray?; + kids = + PdfCrossTable.dereference( + helper.dictionary![PdfDictionaryProperties.kids], + ) + as PdfArray?; } if (helper.flattenField && fieldFlag != 6) { if (field.page != null || kids != null) { @@ -1000,7 +1097,9 @@ class PdfFormHelper { crossTable!.items!.objectCollection!.removeAt(index!); } --i; - } else if (helper.changed || isNeedAppearance) { + } else if (helper.changed || + isNeedAppearance || + (setAppearanceDictionary && !isSigned)) { helper.beginSave(); } } else { @@ -1012,11 +1111,27 @@ class PdfFormHelper { helper.save(); } } + if (_fields?.count == 0) { + final int? index = crossTable!.items!.lookFor(helper.dictionary!); + dictionary?.clear(); + (sender as PdfDictionary).remove(PdfDictionaryProperties.acroForm); + if (index != -1) { + crossTable!.items!.objectCollection!.removeAt(index!); + } + } ++i; } - if (setAppearanceDictionary) { + if (_isDefaultAppearance) { + dictionary!.setBoolean( + PdfDictionaryProperties.needAppearances, + _isDefaultAppearance, + ); + } else if (!_isDefaultAppearance && + dictionary!.containsKey(PdfDictionaryProperties.needAppearances)) { dictionary!.setBoolean( - PdfDictionaryProperties.needAppearances, needAppearances); + PdfDictionaryProperties.needAppearances, + _isDefaultAppearance, + ); } dictionary!.remove('XFA'); } @@ -1045,8 +1160,9 @@ class PdfFormHelper { if (pageFontResource == null || (pageFontResource is PdfDictionary && !pageFontResource.containsKey(key))) { - final PdfReferenceHolder? fieldFontReference = (fieldFontResource! - as PdfDictionary)[key] as PdfReferenceHolder?; + final PdfReferenceHolder? fieldFontReference = + (fieldFontResource! as PdfDictionary)[key] + as PdfReferenceHolder?; if (pageFontResource == null) { final PdfDictionary fontDictionary = PdfDictionary(); fontDictionary.items![key] = fieldFontReference; @@ -1070,8 +1186,9 @@ class PdfFormHelper { int? fieldFlag = 0; final PdfDictionary fieldDictionary = helper.dictionary!; if (fieldDictionary.containsKey(PdfDictionaryProperties.f)) { - fieldFlag = (fieldDictionary[PdfDictionaryProperties.f]! as PdfNumber) - .value as int?; + fieldFlag = + (fieldDictionary[PdfDictionaryProperties.f]! as PdfNumber).value + as int?; } if (fieldFlag != 6) { _addFieldResourcesToPage(field); @@ -1119,7 +1236,8 @@ class PdfFormHelper { pageRef = crossTable!.getReference(dic[pName]); } else if (field.page != null) { pageRef = crossTable!.getReference( - PdfPageHelper.getHelper(field.page!).dictionary); + PdfPageHelper.getHelper(field.page!).dictionary, + ); } page = crossTable!.getObject(pageRef) as PdfDictionary?; } @@ -1161,31 +1279,29 @@ class PdfFormHelper { final PdfAnnotationCollection annotCollection = helper.page!.annotations; if (annotCollection.count > 0) { - final int index = - PdfAnnotationCollectionHelper.getHelper(annotCollection) - .annotations - .indexOf(holder); + final int index = PdfAnnotationCollectionHelper.getHelper( + 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(PdfDictionaryProperties.annots, annots); - } } } } else if (isLoaded) { helper.requiredReference = holder; if (field.page != null && - PdfPageHelper.getHelper(field.page!) - .dictionary! - .containsKey(annotsName)) { - final PdfArray annots = crossTable!.getObject( - PdfPageHelper.getHelper(field.page!) - .dictionary![annotsName])! as PdfArray; + PdfPageHelper.getHelper( + field.page!, + ).dictionary!.containsKey(annotsName)) { + final PdfArray annots = + crossTable!.getObject( + PdfPageHelper.getHelper( + field.page!, + ).dictionary![annotsName], + )! + as PdfArray; for (int i = 0; i < annots.count; i++) { final IPdfPrimitive? obj = annots.elements[i]; if (obj != null && @@ -1200,35 +1316,42 @@ class PdfFormHelper { } if (crossTable!.items != null && crossTable!.items!.contains(widget)) { - crossTable!.items!.objectCollection! - .removeAt(crossTable!.items!.lookFor(widget)!); + crossTable!.items!.objectCollection!.removeAt( + crossTable!.items!.lookFor(widget)!, + ); } helper.requiredReference = null; } } } else { - PdfDictionary? page; + IPdfPrimitive? page; if (!isLoaded) { - final PdfReferenceHolder pageRef = dic.containsKey(pName) - ? (dic[pName] as PdfReferenceHolder?)! - : PdfReferenceHolder( - PdfPageHelper.getHelper(field.page!).dictionary); - page = pageRef.object as PdfDictionary?; + final PdfReferenceHolder pageRef = + dic.containsKey(pName) + ? (dic[pName] as PdfReferenceHolder?)! + : PdfReferenceHolder( + PdfPageHelper.getHelper(field.page!).dictionary, + ); + page = pageRef.object; } else { PdfReference? pageRef; if (dic.containsKey(pName) && dic[PdfDictionaryProperties.p] is! PdfNull) { pageRef = crossTable!.getReference(dic[pName]); } else if (field.page != null) { - pageRef = crossTable! - .getReference(PdfPageHelper.getHelper(field.page!).dictionary); + pageRef = crossTable!.getReference( + PdfPageHelper.getHelper(field.page!).dictionary, + ); } - page = crossTable!.getObject(pageRef) as PdfDictionary?; + page = crossTable!.getObject(pageRef); } - if (page != null && page.containsKey(PdfDictionaryProperties.annots)) { - final IPdfPrimitive? annots = isLoaded - ? crossTable!.getObject(page[annotsName]) - : page[PdfDictionaryProperties.annots]; + if (page != null && + page is PdfDictionary && + page.containsKey(PdfDictionaryProperties.annots)) { + final IPdfPrimitive? annots = + isLoaded + ? crossTable!.getObject(page[annotsName]) + : page[PdfDictionaryProperties.annots]; if (annots != null && annots is PdfArray) { for (int i = 0; i < annots.count; i++) { final IPdfPrimitive? obj = annots.elements[i]; @@ -1245,12 +1368,16 @@ class PdfFormHelper { } } else if (isLoaded && field.page != null && - PdfPageHelper.getHelper(field.page!) - .dictionary! - .containsKey(annotsName)) { - final PdfArray annots = crossTable!.getObject( - PdfPageHelper.getHelper(field.page!).dictionary![annotsName])! - as PdfArray; + PdfPageHelper.getHelper( + field.page!, + ).dictionary!.containsKey(annotsName)) { + final PdfArray annots = + crossTable!.getObject( + PdfPageHelper.getHelper( + field.page!, + ).dictionary![annotsName], + )! + as PdfArray; for (int i = 0; i < annots.count; i++) { final IPdfPrimitive? obj = annots.elements[i]; if (obj != null && @@ -1282,10 +1409,11 @@ class PdfFormHelper { if (dic.items != null) { if (dic.containsKey(PdfDictionaryProperties.kids)) { PdfArray? array; - array = !helper.isLoadedField - ? dic[PdfDictionaryProperties.kids] as PdfArray? - : crossTable!.getObject(dic[PdfDictionaryProperties.kids]) - as PdfArray?; + array = + !helper.isLoadedField + ? dic[PdfDictionaryProperties.kids] as PdfArray? + : crossTable!.getObject(dic[PdfDictionaryProperties.kids]) + as PdfArray?; array!.clear(); dic.setProperty(PdfDictionaryProperties.kids, array); } @@ -1309,72 +1437,86 @@ class PdfFormHelper { /// internal method //Removes field and kids annotation from dictionaries. - void removeFromDictionaries(PdfField field) { + void removeFromDictionaries( + PdfField field, [ + bool removeFieldFromAcroForm = false, + ]) { final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); - if (_fields != null && _fields!.count > 0) { + if ((_fields != null && _fields!.count > 0) || removeFieldFromAcroForm) { final PdfName fieldsDict = PdfName(PdfDictionaryProperties.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 == helper.dictionary) { - holder = obj; + fields.remove(obj); break; } } - fields.remove(holder); + helper.dictionary!.isSkip = true; fields.changed = true; - if (!formHasKids || - !helper.dictionary!.items! - .containsKey(PdfName(PdfDictionaryProperties.parent))) { - for (int i = 0; i < fields.count; i++) { - final IPdfPrimitive? fieldDictionary = - PdfCrossTable.dereference(crossTable!.getObject(fields[i])); - final PdfName kidsName = PdfName(PdfDictionaryProperties.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 (helper.dictionary!.items! - .containsKey(PdfName(PdfDictionaryProperties.parent))) { + if (!(!formHasKids || + !helper.dictionary!.items!.containsKey( + PdfName(PdfDictionaryProperties.parent), + ))) { + if (helper.dictionary!.items!.containsKey( + PdfName(PdfDictionaryProperties.parent), + )) { final PdfDictionary dic = (helper.dictionary![PdfDictionaryProperties.parent]! - as PdfReferenceHolder) - .object! as PdfDictionary; + as PdfReferenceHolder) + .object! + as PdfDictionary; final PdfArray kids = dic.items![PdfName(PdfDictionaryProperties.kids)]! as PdfArray; for (int k = 0; k < kids.count; k++) { final PdfReferenceHolder kidsReference = kids[k]! as PdfReferenceHolder; - if (kidsReference.object == holder.object) { + if (kidsReference.object == helper.dictionary) { kids.remove(kidsReference); + dic.modify(); + break; } } } } dictionary!.setProperty(fieldsDict, fields); } - if (helper.isLoadedField) { + if (helper.isLoadedField && !removeFieldFromAcroForm) { deleteFromPages(field); deleteAnnotation(field); } } + + /// internal method + static String decodeXMLConversion(String value) { + String newString = value; + while (newString.contains('_x')) { + final int index = newString.indexOf('_x'); + final String tempString = newString.substring(index); + if (tempString.length >= 7 && tempString[6] == '_') { + newString = newString.replaceRange(index, index + 2, '--'); + final int? charCode = int.tryParse( + value.substring(index + 2, index + 6), + radix: 16, + ); + if (charCode != null && charCode >= 0) { + value = value.replaceRange( + index, + index + 7, + String.fromCharCode(charCode), + ); + newString = newString.replaceRange(index, index + 7, '-'); + } + } else { + break; + } + } + return value; + } } class _NodeInfo { 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 index d5e0d59b8..62abf43b4 100644 --- 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 @@ -14,6 +14,8 @@ import 'pdf_button_field.dart'; import 'pdf_check_box_field.dart'; import 'pdf_combo_box_field.dart'; import 'pdf_field.dart'; +import 'pdf_field_item.dart'; +import 'pdf_field_item_collection.dart'; import 'pdf_form.dart'; import 'pdf_list_box_field.dart'; import 'pdf_radio_button_list_field.dart'; @@ -86,15 +88,18 @@ class PdfFormFieldCollection extends PdfObjectCollection class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfFormFieldCollectionHelper(this.formFieldCollection, PdfForm? form) - : super(formFieldCollection) { + : super(formFieldCollection) { if (form != null) { this.form = form; - for (int i = 0; - i < PdfFormHelper.getHelper(form).terminalFields.length; - ++i) { + final PdfFormHelper formHelper = PdfFormHelper.getHelper(form); + for (int i = 0; i < formHelper.terminalFields.length; ++i) { final PdfField? field = _getField(index: i); if (field != null) { _doAdd(field); + if (removeTerminalField) { + formHelper.terminalFields.removeAt(i); + i--; + } } } } @@ -112,13 +117,17 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { /// internal field bool isAction = false; + /// internal field + bool removeTerminalField = false; + /// internal field // ignore: prefer_final_fields final List addedFieldNames = []; /// internal method static PdfFormFieldCollectionHelper getHelper( - PdfFormFieldCollection collection) { + PdfFormFieldCollection collection, + ) { return collection._helper; } @@ -136,9 +145,11 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { /// internal method void createFormFieldsFromWidgets(int startFormFieldIndex) { - for (int i = startFormFieldIndex; - i < PdfFormHelper.getHelper(form!).terminalFields.length; - ++i) { + for ( + int i = startFormFieldIndex; + i < PdfFormHelper.getHelper(form!).terminalFields.length; + ++i + ) { final PdfField? field = _getField(index: i); if (field != null) { _doAdd(field); @@ -152,9 +163,9 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { if (dictValue.isNotEmpty) { final PdfField? field = _getField(dictionary: dictValue[0]); if (field != null) { - PdfFormHelper.getHelper(form!) - .terminalFields - .add(PdfFieldHelper.getHelper(field).dictionary!); + PdfFormHelper.getHelper( + form!, + ).terminalFields.add(PdfFieldHelper.getHelper(field).dictionary!); _doAdd(field); } } @@ -165,23 +176,27 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { int _doAdd(PdfField field) { final bool isLoaded = form != null && PdfFormHelper.getHelper(form!).isLoadedForm; + removeTerminalField = false; if (!isAction) { PdfFieldHelper.getHelper(field).setForm(form); String? name = field.name; PdfArray? array; bool skipField = false; if (isLoaded) { - array = PdfFormHelper.getHelper(form!) - .dictionary! - .containsKey(PdfDictionaryProperties.fields) - ? PdfFormHelper.getHelper(form!).crossTable!.getObject( - PdfFormHelper.getHelper(form!) - .dictionary![PdfDictionaryProperties.fields]) as PdfArray? - : PdfArray(); - if (PdfFieldHelper.getHelper(field) - .dictionary! - .items! - .containsKey(PdfName(PdfDictionaryProperties.parent))) { + array = + PdfFormHelper.getHelper( + form!, + ).dictionary!.containsKey(PdfDictionaryProperties.fields) + ? PdfFormHelper.getHelper(form!).crossTable!.getObject( + PdfFormHelper.getHelper( + form!, + ).dictionary![PdfDictionaryProperties.fields], + ) + as PdfArray? + : PdfArray(); + if (PdfFieldHelper.getHelper(field).dictionary!.items!.containsKey( + PdfName(PdfDictionaryProperties.parent), + )) { skipField = true; } } else { @@ -193,150 +208,49 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { if (!isLoaded || !PdfFieldHelper.getHelper(field).isLoadedField) { if (form!.fieldAutoNaming && !skipField) { if (!isLoaded) { - PdfFieldHelper.getHelper(field) - .applyName(PdfFormHelper.getHelper(form!).getCorrectName(name)); + PdfFieldHelper.getHelper( + field, + ).applyName(PdfFormHelper.getHelper(form!).getCorrectName(name)); } else { PdfFieldHelper.getHelper(field).applyName(getCorrectName(name)); array!.add(PdfReferenceHolder(field)); - PdfFormHelper.getHelper(form!) - .dictionary! - .setProperty(PdfDictionaryProperties.fields, array); + PdfFormHelper.getHelper( + form!, + ).dictionary!.setProperty(PdfDictionaryProperties.fields, array); } } else if (isLoaded && !addedFieldNames.contains(name) && !skipField) { array!.add(PdfReferenceHolder(field)); - PdfFormHelper.getHelper(form!) - .dictionary! - .setProperty(PdfDictionaryProperties.fields, array); + PdfFormHelper.getHelper( + form!, + ).dictionary!.setProperty(PdfDictionaryProperties.fields, array); } else if (isLoaded && (!addedFieldNames.contains(name) && skipField) || (form!.fieldAutoNaming && skipField)) { addedFieldNames.add(field.name); } else if (formFieldCollection.count > 0 && !isLoaded) { - for (int i = 0; i < formFieldCollection.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 = PdfAnnotationHelper.getHelper( - PdfFieldHelper.getHelper(field).widget!) - .dictionary!; - dic.remove(PdfDictionaryProperties.parent); - PdfFieldHelper.getHelper(field).widget!.parent = oldField; - if (PdfFieldHelper.getHelper(field).page != null) { - PdfFieldHelper.getHelper(field) - .page! - .annotations - .add(PdfFieldHelper.getHelper(field).widget!); - } - bool isPresent = false; - for (int i = 0; - i < PdfFieldHelper.getHelper(oldField).array.count; - i++) { - final IPdfPrimitive? obj = - PdfFieldHelper.getHelper(oldField).array.elements[i]; - if (obj != null && - obj is PdfReferenceHolder && - obj.object != null && - obj.object is PdfDictionary && - obj.object == - PdfAnnotationHelper.getHelper( - PdfFieldHelper.getHelper(oldField).widget!) - .dictionary) { - isPresent = true; - break; - } - } - if (!isPresent) { - PdfFieldHelper.getHelper(oldField).array.add( - PdfReferenceHolder( - PdfFieldHelper.getHelper(oldField).widget)); - if (PdfFieldHelper.getHelper(oldField).fieldItems == null) { - PdfFieldHelper.getHelper(oldField).fieldItems = - []; - } - PdfFieldHelper.getHelper(oldField) - .fieldItems! - .add(oldField); - } - PdfFieldHelper.getHelper(oldField).array.add( - PdfReferenceHolder( - PdfFieldHelper.getHelper(field).widget)); - if (PdfFieldHelper.getHelper(oldField).fieldItems == null) { - PdfFieldHelper.getHelper(oldField).fieldItems = - []; - } - PdfFieldHelper.getHelper(oldField).fieldItems!.add(field); - PdfFieldHelper.getHelper(oldField).dictionary!.setProperty( - PdfDictionaryProperties.kids, - PdfFieldHelper.getHelper(oldField).array); - return formFieldCollection.count - 1; - } else if (field is PdfSignatureField) { - final PdfSignatureField currentField = field; - final PdfDictionary dictionary = - PdfAnnotationHelper.getHelper( - PdfFieldHelper.getHelper(currentField).widget!) - .dictionary!; - if (dictionary.containsKey(PdfDictionaryProperties.parent)) { - dictionary.remove(PdfDictionaryProperties.parent); - } - PdfFieldHelper.getHelper(currentField).widget!.parent = - oldField; - IPdfPrimitive? oldKids; - IPdfPrimitive? newKids; - if (PdfFieldHelper.getHelper(oldField) - .dictionary! - .containsKey(PdfDictionaryProperties.kids)) { - oldKids = PdfFieldHelper.getHelper(oldField) - .dictionary! - .items![PdfName(PdfDictionaryProperties.kids)]; - } - if (PdfFieldHelper.getHelper(field) - .dictionary! - .containsKey(PdfDictionaryProperties.kids)) { - newKids = PdfFieldHelper.getHelper(field) - .dictionary! - .items![PdfName(PdfDictionaryProperties.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); - } - } - } - PdfFieldHelper.getHelper(oldField) - .dictionary! - .setProperty(PdfDictionaryProperties.kids, oldKids); - PdfSignatureFieldHelper.getHelper(currentField) - .skipKidsCertificate = true; - if (!field.page!.annotations.contains( - PdfFieldHelper.getHelper(currentField).widget!)) { - field.page!.annotations - .add(PdfFieldHelper.getHelper(currentField).widget!); - } - return formFieldCollection.count - 1; - } - } - } + final int index = _addFieldItem(field, isLoaded); + if (index >= 0) { + removeTerminalField = true; + return index; } } } } - if (isLoaded && !addedFieldNames.contains(field.name)) { - addedFieldNames.add(field.name); + if (isLoaded) { + if (!addedFieldNames.contains(field.name)) { + addedFieldNames.add(field.name); + } else if (PdfFieldHelper.getHelper(field).isLoadedField) { + final int index = _addFieldItem(field, isLoaded); + if (index >= 0) { + removeTerminalField = true; + return index; + } + } } if (field is! PdfRadioButtonListField && PdfFieldHelper.getHelper(field).page != null) { - PdfFieldHelper.getHelper(field) - .page! - .annotations - .add(PdfFieldHelper.getHelper(field).widget!); + PdfFieldHelper.getHelper( + field, + ).page!.annotations.add(PdfFieldHelper.getHelper(field).widget!); } array.add(PdfReferenceHolder(field)); list.add(field); @@ -344,16 +258,245 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { return list.length - 1; } + int _addFieldItem(PdfField field, bool isLoaded) { + for (int i = 0; i < formFieldCollection.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 PdfFieldHelper fieldHelper = PdfFieldHelper.getHelper(field); + final PdfFieldHelper oldFieldHelper = PdfFieldHelper.getHelper( + oldField, + ); + PdfDictionary? dic; + PdfDictionary? oldFieldDic; + if (isLoaded) { + dic = _getWidgetAnnotation(fieldHelper.dictionary!); + oldFieldDic = _getWidgetAnnotation(oldFieldHelper.dictionary!); + PdfAnnotationHelper.getHelper(fieldHelper.widget!).dictionary = + dic; + PdfAnnotationHelper.getHelper(oldFieldHelper.widget!).dictionary = + oldFieldDic; + } else { + dic = + PdfAnnotationHelper.getHelper(fieldHelper.widget!).dictionary; + oldFieldDic = + PdfAnnotationHelper.getHelper( + oldFieldHelper.widget!, + ).dictionary; + } + if (isLoaded) { + final PdfCrossTable? crossTable = + PdfFormHelper.getHelper(form!).crossTable; + final PdfDictionary? cloneIprimitive = + dic?.cloneObject(crossTable!) as PdfDictionary?; + dic = cloneIprimitive; + dic?.setProperty( + PdfDictionaryProperties.p, + PdfReferenceHolder(field.page), + ); + } + dic!.remove(PdfDictionaryProperties.parent); + if (isLoaded) { + dic.setProperty( + PdfDictionaryProperties.parent, + PdfReferenceHolder(oldFieldDic), + ); + } + fieldHelper.widget!.parent = oldField; + if (!isLoaded && fieldHelper.page != null) { + fieldHelper.page!.annotations.add(fieldHelper.widget!); + } + final bool isOldFieldPresent = _checkCollection( + oldFieldHelper.array, + oldFieldDic!, + ); + bool isNewFieldPresent = false; + if (isLoaded) { + isNewFieldPresent = _checkCollection(oldFieldHelper.array, dic); + } + PdfReferenceHolder? oldFieldReferenceHolder; + PdfReferenceHolder? newFieldReferenceHolder; + if (!isOldFieldPresent) { + oldFieldHelper.array.clear(); + oldFieldReferenceHolder = PdfReferenceHolder(oldFieldDic); + oldFieldHelper.array.add(oldFieldReferenceHolder); + oldFieldHelper.fieldItems ??= []; + oldFieldHelper.fieldItems!.add(oldField); + } + if (!isNewFieldPresent) { + newFieldReferenceHolder = PdfReferenceHolder(dic); + oldFieldHelper.array.add(newFieldReferenceHolder); + oldFieldHelper.fieldItems ??= []; + oldFieldHelper.fieldItems!.add(field); + oldFieldHelper.dictionary!.setProperty( + PdfDictionaryProperties.kids, + oldFieldHelper.array, + ); + } + if (isLoaded) { + if (oldFieldReferenceHolder != null) { + _addItem(oldField, oldFieldReferenceHolder, 0, true); + } + if (newFieldReferenceHolder != null) { + _addItem( + oldField, + newFieldReferenceHolder, + oldFieldHelper.array.count - 1, + false, + ); + } + if (PdfFieldHelper.getHelper(field).isLoadedField) { + PdfFormHelper.getHelper( + form!, + ).removeFromDictionaries(field, true); + } + } + return formFieldCollection.count - 1; + } else if (!isLoaded && field is PdfSignatureField) { + final PdfSignatureField currentField = field; + final PdfDictionary dictionary = + PdfAnnotationHelper.getHelper( + PdfFieldHelper.getHelper(currentField).widget!, + ).dictionary!; + if (dictionary.containsKey(PdfDictionaryProperties.parent)) { + dictionary.remove(PdfDictionaryProperties.parent); + } + PdfFieldHelper.getHelper(currentField).widget!.parent = oldField; + IPdfPrimitive? oldKids; + IPdfPrimitive? newKids; + if (PdfFieldHelper.getHelper( + oldField, + ).dictionary!.containsKey(PdfDictionaryProperties.kids)) { + oldKids = + PdfFieldHelper.getHelper(oldField).dictionary!.items![PdfName( + PdfDictionaryProperties.kids, + )]; + } + if (PdfFieldHelper.getHelper( + field, + ).dictionary!.containsKey(PdfDictionaryProperties.kids)) { + newKids = + PdfFieldHelper.getHelper(field).dictionary!.items![PdfName( + PdfDictionaryProperties.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); + } + } + } + PdfFieldHelper.getHelper( + oldField, + ).dictionary!.setProperty(PdfDictionaryProperties.kids, oldKids); + PdfSignatureFieldHelper.getHelper(currentField) + .skipKidsCertificate = true; + if (!field.page!.annotations.contains( + PdfFieldHelper.getHelper(currentField).widget!, + )) { + field.page!.annotations.add( + PdfFieldHelper.getHelper(currentField).widget!, + ); + } + return formFieldCollection.count - 1; + } + } + } + } + return -1; + } + + PdfDictionary _getWidgetAnnotation(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.kids)) { + final IPdfPrimitive? array = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.kids], + ); + if (array is PdfArray && array.count > 0) { + final IPdfPrimitive? dic = PdfCrossTable.dereference(array[0]); + if (dic is PdfDictionary) { + return dic; + } + } + } + return dictionary; + } + + void _addItem( + PdfField field, + PdfReferenceHolder referenceHolder, + int index, + bool initializeNew, + ) { + PdfFieldItemCollection? items; + if (initializeNew) { + items = PdfFieldItemCollectionHelper.load(field); + if (field is PdfCheckBoxField) { + PdfCheckBoxFieldHelper.getHelper(field).items = items; + } else if (field is PdfTextBoxField) { + PdfTextBoxFieldHelper.getHelper(field).items = items; + } + } else { + if (field is PdfCheckBoxField) { + items = field.items; + } else if (field is PdfTextBoxField) { + items = field.items; + } + } + if (items != null) { + final PdfDictionary? itemDictionary = + PdfCrossTable.dereference(referenceHolder) as PdfDictionary?; + if (field is PdfCheckBoxField) { + PdfFieldItemCollectionHelper.getHelper( + items, + ).add(PdfCheckBoxItemHelper.getItem(field, index, itemDictionary)); + } else if (field is PdfTextBoxField) { + PdfFieldItemCollectionHelper.getHelper( + items, + ).add(PdfTextBoxItemHelper.getItem(field, index, itemDictionary)); + } + } + } + + bool _checkCollection(PdfArray array, PdfDictionary dictionary) { + for (int i = 0; i < array.count; i++) { + final IPdfPrimitive? obj = array.elements[i]; + if (obj != null && + obj is PdfReferenceHolder && + obj.object != null && + obj.object is PdfDictionary && + obj.object == dictionary) { + return true; + } + } + return false; + } + // Gets the field. PdfField? _getField({int? index, PdfDictionary? dictionary}) { index != null ? dictionary = PdfFormHelper.getHelper(form!).terminalFields[index] : ArgumentError.checkNotNull( - dictionary, 'method cannot be initialized without parameters'); + dictionary, + 'method cannot be initialized without parameters', + ); final PdfCrossTable? crossTable = PdfFormHelper.getHelper(form!).crossTable; PdfField? field; - final PdfName? name = PdfFieldHelper.getValue( - dictionary!, crossTable, PdfDictionaryProperties.ft, true) as PdfName?; + final PdfName? name = + PdfFieldHelper.getValue( + dictionary!, + crossTable, + PdfDictionaryProperties.ft, + true, + ) + as PdfName?; PdfFieldTypes type = PdfFieldTypes.none; if (name != null) { type = _getFieldType(name, dictionary, crossTable); @@ -380,7 +523,7 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { case PdfFieldTypes.signatureField: field = _createSignatureField(dictionary, crossTable!); break; - default: + case PdfFieldTypes.none: break; } if (field != null) { @@ -396,12 +539,20 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { //Gets the type of the field. PdfFieldTypes _getFieldType( - PdfName name, PdfDictionary dictionary, PdfCrossTable? crossTable) { + PdfName name, + PdfDictionary dictionary, + PdfCrossTable? crossTable, + ) { final String str = name.name!; PdfFieldTypes type = PdfFieldTypes.none; - final PdfNumber? number = PdfFieldHelper.getValue( - dictionary, crossTable, PdfDictionaryProperties.fieldFlags, true) - as PdfNumber?; + final PdfNumber? number = + PdfFieldHelper.getValue( + dictionary, + crossTable, + PdfDictionaryProperties.fieldFlags, + true, + ) + as PdfNumber?; int fieldFlags = 0; if (number != null) { fieldFlags = number.value!.toInt(); @@ -436,56 +587,78 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { //Creates the combo box. PdfField _createComboBox(PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfComboBoxFieldHelper.loadComboBox(dictionary, crossTable); + final PdfField field = PdfComboBoxFieldHelper.loadComboBox( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } //Creates the list box. PdfField _createListBox(PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfListBoxFieldHelper.loadListBox(dictionary, crossTable); + final PdfField field = PdfListBoxFieldHelper.loadListBox( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } //Creates the text field. PdfField _createTextField( - PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfTextBoxFieldHelper.loadTextBox(dictionary, crossTable); + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfField field = PdfTextBoxFieldHelper.loadTextBox( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } PdfField _createCheckBox(PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfCheckBoxFieldHelper.loadCheckBoxField(dictionary, crossTable); + final PdfField field = PdfCheckBoxFieldHelper.loadCheckBoxField( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } PdfField _createRadioButton( - PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfRadioButtonListFieldHelper.loaded(dictionary, crossTable); + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfField field = PdfRadioButtonListFieldHelper.loaded( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } PdfField _createPushButton( - PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfButtonFieldHelper.loadButtonField(dictionary, crossTable); + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfField field = PdfButtonFieldHelper.loadButtonField( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } PdfField _createSignatureField( - PdfDictionary dictionary, PdfCrossTable crossTable) { - final PdfField field = - PdfSignatureFieldHelper.loadSignatureField(dictionary, crossTable); + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { + final PdfField field = PdfSignatureFieldHelper.loadSignatureField( + dictionary, + crossTable, + ); PdfFieldHelper.getHelper(field).setForm(form); return field; } @@ -512,28 +685,56 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { /// internal method int getFieldIndex(String name) { int i = -1; - final List _fieldNames = []; - final List _indexedFieldNames = []; + final List fieldNames = []; + final List indexedFieldNames = []; for (int j = 0; j < formFieldCollection.count; j++) { if (PdfObjectCollectionHelper.getHelper(formFieldCollection).list[j] is PdfField) { final PdfField field = PdfObjectCollectionHelper.getHelper(formFieldCollection).list[j] as PdfField; - _fieldNames.add(field.name!); + fieldNames.add(field.name!); if (field.name != null) { - _indexedFieldNames.add(field.name!.split('[')[0]); + indexedFieldNames.add(field.name!.split('[')[0]); } } } - if (_fieldNames.contains(name)) { - i = _fieldNames.indexOf(name); - } else if (_indexedFieldNames.contains(name)) { - i = _indexedFieldNames.indexOf(name); + if (fieldNames.contains(name)) { + i = fieldNames.indexOf(name); + } else if (indexedFieldNames.contains(name)) { + i = indexedFieldNames.indexOf(name); } return i; } + /// internal method + void removeContainingField(PdfReferenceHolder pageReferenceHolder) { + for (int i = array.count - 1; i >= 0; --i) { + final IPdfPrimitive? fieldDictionary = PdfCrossTable.dereference( + array[i], + ); + if (fieldDictionary != null && fieldDictionary is PdfDictionary) { + if (fieldDictionary.containsKey(PdfDictionaryProperties.p)) { + final IPdfPrimitive? holder = + fieldDictionary[PdfDictionaryProperties.p]; + if (holder != null && + holder is PdfReferenceHolder && + holder.object == pageReferenceHolder.object) { + _doRemoveAt(i); + } + } else if (fieldDictionary.containsKey(PdfDictionaryProperties.kids)) { + final bool removed = _removeContainingFieldItems( + fieldDictionary, + pageReferenceHolder, + ); + if (removed) { + _doRemoveAt(i); + } + } + } + } + } + void _removeFromDictionary(PdfField field) { if (PdfFieldHelper.getHelper(field).isLoadedField) { PdfFormHelper.getHelper(form!).removeFromDictionaries(field); @@ -586,4 +787,38 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { array.clear(); list.clear(); } + + bool _removeContainingFieldItems( + PdfDictionary fieldDictionary, + PdfReferenceHolder pageReferenceHolder, + ) { + bool isAllKidsRemoved = false; + if (fieldDictionary.containsKey(PdfDictionaryProperties.kids)) { + final IPdfPrimitive? array = PdfCrossTable.dereference( + fieldDictionary[PdfDictionaryProperties.kids], + ); + if (array != null && array is PdfArray) { + for (int i = array.count - 1; i >= 0; --i) { + IPdfPrimitive? holder; + final IPdfPrimitive? kidObject = PdfCrossTable.dereference(array[i]); + if (kidObject != null && + kidObject is PdfDictionary && + kidObject.containsKey(PdfDictionaryProperties.p)) { + holder = kidObject[PdfDictionaryProperties.p]; + } + if (holder != null && + holder is PdfReferenceHolder && + holder.object == pageReferenceHolder.object) { + (kidObject as PdfDictionary?)!.isSkip = true; + array.removeAt(i); + array.changed = true; + } + } + if (array.count == 0) { + isAllKidsRemoved = true; + } + } + } + return isAllKidsRemoved; + } } 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 index 797fe46cf..d86918d4a 100644 --- 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 @@ -30,32 +30,40 @@ import 'pdf_list_field_item_collection.dart'; 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}) { + 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, + }) { _helper = PdfListBoxFieldHelper(this); - _helper.initializeInternal(page, name, bounds, - font: font, - alignment: alignment, - items: items, - borderColor: borderColor, - foreColor: foreColor, - backColor: backColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + _helper.initializeInternal( + 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; @@ -81,7 +89,8 @@ class PdfListBoxField extends PdfListField { /// The default value is false. bool get multiSelect { if (_helper.isLoadedField) { - _multiSelect = _helper.isFlagPresent(FieldFlags.multiSelect) || + _multiSelect = + _helper.isFlagPresent(FieldFlags.multiSelect) || _helper.flags.contains(FieldFlags.multiSelect); } return _multiSelect; @@ -93,8 +102,8 @@ class PdfListBoxField extends PdfListField { _multiSelect ? _helper.flags.add(FieldFlags.multiSelect) : _helper.isLoadedField - ? _helper.removeFlag(FieldFlags.multiSelect) - : _helper.flags.remove(FieldFlags.multiSelect); + ? _helper.removeFlag(FieldFlags.multiSelect) + : _helper.flags.remove(FieldFlags.multiSelect); } } @@ -104,9 +113,8 @@ class PdfListBoxField extends PdfListField { /// Otherwise only the first index in the collection will be selected. List get selectedIndexes => _helper.selectedIndexes; set selectedIndexes(List value) { - if (value.isNotEmpty) { - _helper.selectedIndexes = multiSelect ? value : [value[0]]; - } + _helper.selectedIndexes = + multiSelect || value.isEmpty ? value : [value[0]]; } /// Gets or sets the selected values in the list. @@ -115,9 +123,8 @@ class PdfListBoxField extends PdfListField { /// Otherwise only the first value in the collection will be selected. List get selectedValues => _helper.selectedValues; set selectedValues(List value) { - if (value.isNotEmpty) { - _helper.selectedValues = multiSelect ? value : [value[0]]; - } + _helper.selectedValues = + multiSelect || value.isEmpty ? value : [value[0]]; } /// Gets the selected items in the list. @@ -142,31 +149,45 @@ class PdfListBoxFieldHelper extends PdfListFieldHelper { void drawAppearance(PdfTemplate template) { super.drawAppearance(template); final PaintParams params = PaintParams( - bounds: Rect.fromLTWH( - 0, 0, listBoxField.bounds.width, listBoxField.bounds.height), - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: listBoxField.borderStyle, - borderWidth: listBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: Rect.fromLTWH( + 0, + 0, + listBoxField.bounds.width, + listBoxField.bounds.height, + ), + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: listBoxField.borderStyle, + borderWidth: listBoxField.borderWidth, + shadowBrush: shadowBrush, + ); PdfFont font; if (listBoxField.font == null) { if (PdfPageHelper.getHelper(listBoxField.page!).document != null && PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(listBoxField.page!).document!) - .conformanceLevel != + PdfPageHelper.getHelper(listBoxField.page!).document!, + ).conformanceLevel != PdfConformanceLevel.none) { throw ArgumentError( - 'Font data is not embedded to the conformance PDF.'); + 'Font data is not embedded to the conformance PDF.', + ); } font = PdfStandardFont( - PdfFontFamily.timesRoman, getFontHeight(PdfFontFamily.timesRoman)); + PdfFontFamily.timesRoman, + getFontHeight(PdfFontFamily.timesRoman), + ); } else { font = listBoxField.font!; } - FieldPainter().drawListBox(template.graphics!, params, listBoxField.items, - selectedIndexes, font, format); + FieldPainter().drawListBox( + template.graphics!, + params, + listBoxField.items, + selectedIndexes, + font, + format, + ); } /// internal method @@ -176,33 +197,56 @@ class PdfListBoxFieldHelper extends PdfListFieldHelper { if (!isLoadedField) { if (PdfAnnotationHelper.getHelper(widget!).appearance != null) { listBoxField.page!.graphics.drawPdfTemplate( - widget!.appearance.normal, listBoxField.bounds.topLeft); + widget!.appearance.normal, + listBoxField.bounds.topLeft, + ); } else { final Rect rect = Rect.fromLTWH( - 0, 0, listBoxField.bounds.width, listBoxField.bounds.height); - final PdfFont font = listBoxField.font ?? - PdfStandardFont(PdfFontFamily.helvetica, - getFontHeight(PdfFontFamily.helvetica)); + 0, + 0, + listBoxField.bounds.width, + listBoxField.bounds.height, + ); + final PdfFont font = + listBoxField.font ?? + PdfStandardFont( + PdfFontFamily.helvetica, + getFontHeight(PdfFontFamily.helvetica), + ); final PaintParams parameters = PaintParams( - bounds: rect, - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: listBoxField.borderStyle, - borderWidth: listBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: rect, + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: listBoxField.borderStyle, + borderWidth: listBoxField.borderWidth, + shadowBrush: shadowBrush, + ); final PdfTemplate template = PdfTemplate(rect.width, rect.height); - FieldPainter().drawListBox(template.graphics!, parameters, - listBoxField.items, listBoxField.selectedIndexes, font, format); - listBoxField.page!.graphics - .drawPdfTemplate(template, listBoxField.bounds.topLeft, rect.size); + FieldPainter().drawListBox( + template.graphics!, + parameters, + listBoxField.items, + listBoxField.selectedIndexes, + font, + format, + ); + listBoxField.page!.graphics.drawPdfTemplate( + template, + listBoxField.bounds.topLeft, + rect.size, + ); } } else { - final PdfTemplate template = - PdfTemplate(listBoxField.bounds.width, listBoxField.bounds.height); + final PdfTemplate template = PdfTemplate( + listBoxField.bounds.width, + listBoxField.bounds.height, + ); _drawListBox(template.graphics!); - listBoxField.page!.graphics - .drawPdfTemplate(template, listBoxField.bounds.topLeft); + listBoxField.page!.graphics.drawPdfTemplate( + template, + listBoxField.bounds.topLeft, + ); } } @@ -216,33 +260,41 @@ class PdfListBoxFieldHelper extends PdfListFieldHelper { void _applyAppearance(PdfDictionary widget) { if (widget.containsKey(PdfDictionaryProperties.ap) && !PdfFormHelper.getHelper(listBoxField.form!).needAppearances!) { - final IPdfPrimitive? appearance = - crossTable!.getObject(widget[PdfDictionaryProperties.ap]); + final IPdfPrimitive? appearance = crossTable!.getObject( + widget[PdfDictionaryProperties.ap], + ); if (appearance != null && appearance is PdfDictionary && appearance.containsKey(PdfDictionaryProperties.n)) { - final PdfTemplate template = - PdfTemplate(listBoxField.bounds.width, listBoxField.bounds.height); + final PdfTemplate template = PdfTemplate( + listBoxField.bounds.width, + listBoxField.bounds.height, + ); PdfTemplateHelper.getHelper(template).writeTransformation = false; - beginMarkupSequence(PdfGraphicsHelper.getHelper(template.graphics!) - .streamWriter! - .stream!); + beginMarkupSequence( + PdfGraphicsHelper.getHelper(template.graphics!).streamWriter!.stream!, + ); PdfGraphicsHelper.getHelper(template.graphics!).initializeCoordinates(); _drawListBox(template.graphics!); - endMarkupSequence(PdfGraphicsHelper.getHelper(template.graphics!) - .streamWriter! - .stream!); + endMarkupSequence( + PdfGraphicsHelper.getHelper(template.graphics!).streamWriter!.stream!, + ); appearance.remove(PdfDictionaryProperties.n); appearance.setProperty( - PdfDictionaryProperties.n, PdfReferenceHolder(template)); + PdfDictionaryProperties.n, + PdfReferenceHolder(template), + ); widget.setProperty(PdfDictionaryProperties.ap, appearance); } - } else if (PdfFormHelper.getHelper(listBoxField.form!) - .setAppearanceDictionary && + } else if (PdfFormHelper.getHelper( + listBoxField.form!, + ).setAppearanceDictionary && !PdfFormHelper.getHelper(listBoxField.form!).needAppearances!) { final PdfDictionary dic = PdfDictionary(); - final PdfTemplate template = - PdfTemplate(listBoxField.bounds.width, listBoxField.bounds.height); + final PdfTemplate template = PdfTemplate( + listBoxField.bounds.width, + listBoxField.bounds.height, + ); drawAppearance(template); dic.setProperty(PdfDictionaryProperties.n, PdfReferenceHolder(template)); widget.setProperty(PdfDictionaryProperties.ap, dic); @@ -252,21 +304,32 @@ class PdfListBoxFieldHelper extends PdfListFieldHelper { void _drawListBox(PdfGraphics graphics) { final GraphicsProperties gp = GraphicsProperties(listBoxField); gp.bounds = Rect.fromLTWH( - 0, 0, listBoxField.bounds.width, listBoxField.bounds.height); + 0, + 0, + listBoxField.bounds.width, + listBoxField.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); + bounds: gp.bounds, + backBrush: gp.backBrush, + foreBrush: gp.foreBrush, + borderPen: gp.borderPen, + style: gp.style, + borderWidth: gp.borderWidth, + shadowBrush: gp.shadowBrush, + ); if (!PdfFormHelper.getHelper(listBoxField.form!).setAppearanceDictionary && !PdfFormHelper.getHelper(listBoxField.form!).flatten) { prms.backBrush = null; } - FieldPainter().drawListBox(graphics, prms, listBoxField.items, - selectedIndexes, gp.font!, gp.stringFormat); + FieldPainter().drawListBox( + graphics, + prms, + listBoxField.items, + selectedIndexes, + gp.font!, + gp.stringFormat, + ); } /// internal method @@ -289,7 +352,9 @@ class PdfListBoxFieldHelper extends PdfListFieldHelper { /// internal method static PdfListBoxField loadListBox( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfListBoxField._load(dictionary, crossTable); } } 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 index 5d1c70f6a..657f6ca76 100644 --- 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 @@ -22,41 +22,54 @@ import 'pdf_list_field_item_collection.dart'; /// Represents base class for form's list fields. abstract class PdfListField extends PdfField { /// internal constructor - void _internal(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, - PdfListFieldHelper? helper}) { + void _internal( + 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, + PdfListFieldHelper? helper, + }) { _helper = helper!; - _helper.internal(page, name, bounds, - font: font, - alignment: alignment, - borderColor: borderColor, - foreColor: foreColor, - backColor: backColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + _helper.internal( + page, + name, + bounds, + font: font, + alignment: alignment, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + ); _helper.dictionary!.setProperty( - PdfDictionaryProperties.ft, PdfName(PdfDictionaryProperties.ch)); + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.ch), + ); if (items != null && items.isNotEmpty) { - items - .toList() - .forEach((PdfListFieldItem element) => this.items.add(element)); + items.toList().forEach( + (PdfListFieldItem element) => this.items.add(element), + ); } } /// internal constructor - void _load(PdfDictionary dictionary, PdfCrossTable crossTable, - PdfListFieldHelper helper) { + void _load( + PdfDictionary dictionary, + PdfCrossTable crossTable, + PdfListFieldHelper helper, + ) { _helper = helper; _helper.load(dictionary, crossTable); } @@ -70,8 +83,10 @@ abstract class PdfListField extends PdfField { if (_helper._items == null) { if (!_helper.isLoadedField) { _helper._items = PdfListFieldItemCollectionHelper.itemCollection(); - _helper.dictionary! - .setProperty(PdfDictionaryProperties.opt, _helper._items); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.opt, + _helper._items, + ); } else { _helper._items = _getListItemCollection(); } @@ -145,17 +160,26 @@ abstract class PdfListField extends PdfField { PdfListFieldItemCollection _getListItemCollection() { final PdfListFieldItemCollection items = PdfListFieldItemCollectionHelper.itemCollection(this); - final IPdfPrimitive? array = PdfFieldHelper.getValue(_helper.dictionary!, - _helper.crossTable, PdfDictionaryProperties.opt, true); + final IPdfPrimitive? array = PdfFieldHelper.getValue( + _helper.dictionary!, + _helper.crossTable, + PdfDictionaryProperties.opt, + true, + ); if (array != null && array is PdfArray) { for (int i = 0; i < array.count; i++) { - final IPdfPrimitive? primitive = - _helper.crossTable!.getObject(array[i]); + final IPdfPrimitive? primitive = _helper.crossTable!.getObject( + array[i], + ); PdfListFieldItem item; if (primitive is PdfString) { final PdfString str = primitive; item = PdfListFieldItemHelper.load( - str.value, null, this, _helper.crossTable); + str.value, + null, + this, + _helper.crossTable, + ); } else { final PdfArray arr = primitive! as PdfArray; final PdfString value = @@ -163,7 +187,11 @@ abstract class PdfListField extends PdfField { final PdfString text = _helper.crossTable!.getObject(arr[1])! as PdfString; item = PdfListFieldItemHelper.load( - text.value, value.value, this, _helper.crossTable); + text.value, + value.value, + this, + _helper.crossTable, + ); } PdfListFieldItemCollectionHelper.getHelper(items).addItem(item); } @@ -181,33 +209,41 @@ class PdfListFieldHelper extends PdfFieldHelper { PdfListField listField; /// internal field - List selectedIndex = [-1]; + List selectedIndex = []; PdfListFieldItemCollection? _items; /// internal method - void initializeInternal(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}) { - listField._internal(page, name, bounds, - items: items, - font: font, - alignment: alignment, - borderColor: borderColor, - foreColor: foreColor, - backColor: backColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip, - helper: this); + void initializeInternal( + 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, + }) { + listField._internal( + page, + name, + bounds, + items: items, + font: font, + alignment: alignment, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + helper: this, + ); } /// internal method @@ -232,10 +268,12 @@ class PdfListFieldHelper extends PdfFieldHelper { if (isLoadedField) { _assignSelectedIndex(value); } else { - if (selectedIndex != value && selectedIndex.isNotEmpty) { + if (selectedIndex != value) { selectedIndex = value; - dictionary! - .setProperty(PdfDictionaryProperties.i, PdfArray(selectedIndex)); + dictionary!.setProperty( + PdfDictionaryProperties.i, + PdfArray(selectedIndex), + ); } } } @@ -245,9 +283,6 @@ class PdfListFieldHelper extends PdfFieldHelper { if (isLoadedField) { return _obtainSelectedValue(); } else { - if (selectedIndex == [-1]) { - throw ArgumentError('No value is selected.'); - } final List values = []; for (final int index in selectedIndex) { values.add(_items![index].value); @@ -257,9 +292,6 @@ class PdfListFieldHelper extends PdfFieldHelper { } set selectedValues(List value) { - if (value.isEmpty) { - throw ArgumentError("selected value can't be null/Empty"); - } if (isLoadedField) { bool isText = false; if (listField.items[0].value.isEmpty) { @@ -276,8 +308,10 @@ class PdfListFieldHelper extends PdfFieldHelper { break; } } - dictionary! - .setProperty(PdfDictionaryProperties.i, PdfArray(selectedIndex)); + dictionary!.setProperty( + PdfDictionaryProperties.i, + PdfArray(selectedIndex), + ); } } @@ -288,13 +322,15 @@ class PdfListFieldHelper extends PdfFieldHelper { } final PdfListFieldItemCollection item = PdfListFieldItemCollectionHelper.itemCollection( - isLoadedField ? listField : null); + isLoadedField ? listField : null, + ); for (final int index in selectedIndexes) { if (index > -1 && listField.items.count > 0 && listField.items.count > index) { - PdfListFieldItemCollectionHelper.getHelper(item) - .addItem(listField.items[index]); + PdfListFieldItemCollectionHelper.getHelper( + item, + ).addItem(listField.items[index]); } } return item; @@ -303,8 +339,9 @@ class PdfListFieldHelper extends PdfFieldHelper { List _obtainSelectedIndex() { final List selectedIndex = []; if (dictionary!.containsKey(PdfDictionaryProperties.i)) { - final IPdfPrimitive? array = - crossTable!.getObject(dictionary![PdfDictionaryProperties.i]); + final IPdfPrimitive? array = crossTable!.getObject( + dictionary![PdfDictionaryProperties.i], + ); if (array != null && array is PdfArray) { if (array.count > 0) { for (int i = 0; i < array.count; i++) { @@ -315,16 +352,14 @@ class PdfListFieldHelper extends PdfFieldHelper { } } } else { - final IPdfPrimitive? number = - crossTable!.getObject(dictionary![PdfDictionaryProperties.i]); + final IPdfPrimitive? number = crossTable!.getObject( + dictionary![PdfDictionaryProperties.i], + ); if (number != null && number is PdfNumber) { selectedIndex.add(number.value!.toInt()); } } } - if (selectedIndex.isEmpty) { - selectedIndex.add(-1); - } return selectedIndex; } @@ -332,8 +367,9 @@ class PdfListFieldHelper extends PdfFieldHelper { List _obtainSelectedValue() { final List value = []; if (dictionary!.containsKey(PdfDictionaryProperties.v)) { - final IPdfPrimitive? primitive = - crossTable!.getObject(dictionary![PdfDictionaryProperties.v]); + final IPdfPrimitive? primitive = crossTable!.getObject( + dictionary![PdfDictionaryProperties.v], + ); if (primitive is PdfString) { value.add(primitive.value!); } else { @@ -354,9 +390,6 @@ class PdfListFieldHelper extends PdfFieldHelper { } void _assignSelectedIndex(List value) { - if ((value.isEmpty) || (value.length > listField.items.count)) { - throw RangeError('selectedIndex'); - } // ignore: avoid_function_literals_in_foreach_calls value.forEach((int element) { if (element >= listField.items.count) { @@ -382,9 +415,7 @@ class PdfListFieldHelper extends PdfFieldHelper { selectedValues.add(listField.items[element].text); }); } - if (selectedValues.isNotEmpty) { - _assignSelectedValue(selectedValues, isText); - } + _assignSelectedValue(selectedValues, isText); changed = true; } } @@ -417,14 +448,18 @@ class PdfListFieldHelper extends PdfFieldHelper { } if (selectedIndexes.isNotEmpty) { selectedIndexes.sort(); - dictionary! - .setProperty(PdfDictionaryProperties.i, PdfArray(selectedIndexes)); - } else + dictionary!.setProperty( + PdfDictionaryProperties.i, + PdfArray(selectedIndexes), + ); + } else { dictionary!.remove(PdfDictionaryProperties.i); + } } if (dictionary!.containsKey(PdfDictionaryProperties.v)) { - final IPdfPrimitive? primitive = - crossTable!.getObject(dictionary![PdfDictionaryProperties.v]); + final IPdfPrimitive? primitive = crossTable!.getObject( + dictionary![PdfDictionaryProperties.v], + ); if ((primitive == null) || (primitive is PdfString)) { if (listField is PdfListBoxField) { final PdfArray array = PdfArray(); 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 index ad8e450ec..53e8307e5 100644 --- 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 @@ -18,7 +18,11 @@ class PdfListFieldItem implements IPdfWrapper { /// Initializes a new instance of the [PdfListFieldItem] class. PdfListFieldItem._load( - String? text, String? value, PdfListField field, PdfCrossTable? cTable) { + String? text, + String? value, + PdfListField field, + PdfCrossTable? cTable, + ) { _helper = PdfListFieldItemHelper(this); _field = field; _crossTable = cTable; @@ -73,15 +77,17 @@ class PdfListFieldItem implements IPdfWrapper { final PdfDictionary fieldDic = PdfFieldHelper.getHelper(_field!).dictionary!; if (fieldDic.containsKey(PdfDictionaryProperties.opt)) { - final PdfArray array = _crossTable! - .getObject(fieldDic[PdfDictionaryProperties.opt])! as PdfArray; - final PdfArray item = isText - ? (PdfArray() - ..add(PdfString(_value!)) - ..add(PdfString(value))) - : (PdfArray() - ..add(PdfString(value)) - ..add(PdfString(_text!))); + final PdfArray array = + _crossTable!.getObject(fieldDic[PdfDictionaryProperties.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; @@ -123,7 +129,11 @@ class PdfListFieldItemHelper { /// internal method static PdfListFieldItem load( - String? text, String? value, PdfListField field, PdfCrossTable? cTable) { + String? text, + String? value, + PdfListField field, + PdfCrossTable? cTable, + ) { return PdfListFieldItem._load(text, value, field, cTable); } } 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 index b180fe38e..db90064f1 100644 --- 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 @@ -74,9 +74,9 @@ class PdfListFieldItemCollection extends PdfObjectCollection void clear() { if (_field != null && PdfFieldHelper.getHelper(_field!).isLoadedField) { final PdfArray list = _getItems()..clear(); - PdfFieldHelper.getHelper(_field!) - .dictionary! - .setProperty(PdfDictionaryProperties.opt, list); + PdfFieldHelper.getHelper( + _field!, + ).dictionary!.setProperty(PdfDictionaryProperties.opt, list); } else { _helper._items.clear(); } @@ -89,9 +89,9 @@ class PdfListFieldItemCollection extends PdfObjectCollection final PdfArray list = _getItems(); final PdfArray itemArray = _getArray(item); list.add(itemArray); - PdfFieldHelper.getHelper(_field!) - .dictionary! - .setProperty(PdfDictionaryProperties.opt, list); + PdfFieldHelper.getHelper( + _field!, + ).dictionary!.setProperty(PdfDictionaryProperties.opt, list); } else { _helper._items.add(IPdfWrapper.getElement(item)!); } @@ -104,9 +104,9 @@ class PdfListFieldItemCollection extends PdfObjectCollection final PdfArray list = _getItems(); final PdfArray itemArray = _getArray(item); list.insert(index, itemArray); - PdfFieldHelper.getHelper(_field!) - .dictionary! - .setProperty(PdfDictionaryProperties.opt, list); + PdfFieldHelper.getHelper( + _field!, + ).dictionary!.setProperty(PdfDictionaryProperties.opt, list); } else { _helper._items.insert(index, IPdfWrapper.getElement(item)!); } @@ -119,9 +119,9 @@ class PdfListFieldItemCollection extends PdfObjectCollection } if (_field != null && PdfFieldHelper.getHelper(_field!).isLoadedField) { final PdfArray list = _getItems()..removeAt(index!); - PdfFieldHelper.getHelper(_field!) - .dictionary! - .setProperty(PdfDictionaryProperties.opt, list); + PdfFieldHelper.getHelper( + _field!, + ).dictionary!.setProperty(PdfDictionaryProperties.opt, list); } else { _helper._items.removeAt(index!); } @@ -130,13 +130,15 @@ class PdfListFieldItemCollection extends PdfObjectCollection PdfArray _getItems() { PdfArray? items; - if (PdfFieldHelper.getHelper(_field!) - .dictionary! - .containsKey(PdfDictionaryProperties.opt)) { - final IPdfPrimitive? obj = PdfFieldHelper.getHelper(_field!) - .crossTable! - .getObject(PdfFieldHelper.getHelper(_field!) - .dictionary![PdfDictionaryProperties.opt]); + if (PdfFieldHelper.getHelper( + _field!, + ).dictionary!.containsKey(PdfDictionaryProperties.opt)) { + final IPdfPrimitive? obj = PdfFieldHelper.getHelper( + _field!, + ).crossTable!.getObject( + PdfFieldHelper.getHelper(_field!).dictionary![PdfDictionaryProperties + .opt], + ); if (obj != null && obj is PdfArray) { items = obj; } @@ -160,7 +162,7 @@ class PdfListFieldItemCollection extends PdfObjectCollection class PdfListFieldItemCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfListFieldItemCollectionHelper(this.listFieldItemCollection) - : super(listFieldItemCollection); + : super(listFieldItemCollection); /// internal field PdfListFieldItemCollection listFieldItemCollection; @@ -175,7 +177,8 @@ class PdfListFieldItemCollectionHelper extends PdfObjectCollectionHelper { /// internal method static PdfListFieldItemCollectionHelper getHelper( - PdfListFieldItemCollection itemCollection) { + PdfListFieldItemCollection itemCollection, + ) { return itemCollection._helper; } 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 index 9f8ad2c8f..b1801a89d 100644 --- 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 @@ -59,7 +59,7 @@ class PdfRadioButtonItemCollection extends PdfObjectCollection class PdfRadioButtonItemCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfRadioButtonItemCollectionHelper(this.radioButtonItemCollection, this.field) - : super(radioButtonItemCollection); + : super(radioButtonItemCollection); /// internal field PdfRadioButtonItemCollection radioButtonItemCollection; @@ -72,13 +72,15 @@ class PdfRadioButtonItemCollectionHelper extends PdfObjectCollectionHelper { /// internal method static PdfRadioButtonItemCollectionHelper getHelper( - PdfRadioButtonItemCollection collection) { + PdfRadioButtonItemCollection collection, + ) { return collection._helper; } /// internal method static PdfRadioButtonItemCollection getCollection( - PdfRadioButtonListField field) { + PdfRadioButtonListField field, + ) { return PdfRadioButtonItemCollection._(field); } 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 index af6b62088..3830fe39b 100644 --- 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 @@ -20,20 +20,27 @@ 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}) { + PdfRadioButtonListField( + PdfPage page, + String name, { + List? items, + int? selectedIndex, + String? selectedValue, + }) { _helper = PdfRadioButtonListFieldHelper(this); _helper.internal(page, name, Rect.zero); _initValues(items, selectedIndex, selectedValue); _helper.flags.add(FieldFlags.radio); _helper.dictionary!.setProperty( - PdfDictionaryProperties.ft, PdfName(PdfDictionaryProperties.btn)); + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.btn), + ); } PdfRadioButtonListField._loaded( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { _helper = PdfRadioButtonListFieldHelper(this); _helper.load(dictionary, crossTable); _retrieveOptionValue(); @@ -48,7 +55,8 @@ class PdfRadioButtonListField extends PdfField { PdfRadioButtonItemCollection get items { if (_helper.isLoadedField) { _items ??= _getRadioButtonListItems( - PdfRadioButtonItemCollectionHelper.getCollection(this)); + PdfRadioButtonItemCollectionHelper.getCollection(this), + ); return _items!; } else { if (_items == null) { @@ -135,11 +143,14 @@ class PdfRadioButtonListField extends PdfField { //Implementation void _initValues( - List? radioItems, int? index, String? value) { + List? radioItems, + int? index, + String? value, + ) { if (radioItems != null) { - radioItems - .toList() - .forEach((PdfRadioButtonListItem item) => items.add(item)); + radioItems.toList().forEach( + (PdfRadioButtonListItem item) => items.add(item), + ); } if (index != null) { selectedIndex = index; @@ -150,15 +161,23 @@ class PdfRadioButtonListField extends PdfField { } PdfRadioButtonItemCollection _getRadioButtonListItems( - PdfRadioButtonItemCollection listItems) { - final PdfArray fieldKids = _helper.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 = PdfRadioButtonListItemHelper.loaded( - kidsDict, _helper.crossTable!, this); - PdfRadioButtonItemCollectionHelper.getHelper(listItems) - .doAdd(item, true); + PdfRadioButtonItemCollection listItems, + ) { + final PdfArray? fieldKids = _helper.obtainKids(); + if (fieldKids != null) { + for (int i = 0; i < fieldKids.count; i++) { + final IPdfPrimitive? kidsDict = PdfCrossTable.dereference(fieldKids[i]); + if (kidsDict != null && kidsDict is PdfDictionary) { + final PdfRadioButtonListItem item = + PdfRadioButtonListItemHelper.loaded( + kidsDict, + _helper.crossTable!, + this, + ); + PdfRadioButtonItemCollectionHelper.getHelper( + listItems, + ).doAdd(item, true); + } } } return listItems; @@ -170,11 +189,15 @@ class PdfRadioButtonListField extends PdfField { final PdfRadioButtonListItem item = items[i]; final PdfDictionary dic = PdfFieldHelper.getHelper(item).dictionary!; final IPdfPrimitive? checkNamePrimitive = PdfFieldHelper.searchInParents( - dic, _helper.crossTable, PdfDictionaryProperties.v); + dic, + _helper.crossTable, + PdfDictionaryProperties.v, + ); if (dic.containsKey(PdfDictionaryProperties.usageApplication) && (checkNamePrimitive is PdfName || checkNamePrimitive is PdfString)) { - final IPdfPrimitive? name = _helper.crossTable! - .getObject(dic[PdfDictionaryProperties.usageApplication]); + final IPdfPrimitive? name = _helper.crossTable!.getObject( + dic[PdfDictionaryProperties.usageApplication], + ); if (name is PdfName && name.name!.toLowerCase() != 'off') { if (checkNamePrimitive is PdfName && checkNamePrimitive.name!.toLowerCase() != 'off') { @@ -209,19 +232,22 @@ class PdfRadioButtonListField extends PdfField { final PdfRadioButtonListItem item = items[i]; if (item.value == name.name) { PdfFieldHelper.getHelper(item).dictionary!.setName( - PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); + PdfName(PdfDictionaryProperties.usageApplication), + PdfDictionaryProperties.off, + ); } } } PdfFieldHelper.getHelper(items[value]).dictionary!.setName( - PdfName(PdfDictionaryProperties.usageApplication), - items[value].value); + PdfName(PdfDictionaryProperties.usageApplication), + items[value].value, + ); } } void _assignSelectedValue(String value) { PdfName? name; + value = PdfName.decodeName(value)!; if (_helper.dictionary!.containsKey(PdfDictionaryProperties.v)) { name = _helper.dictionary![PdfDictionaryProperties.v] as PdfName?; _helper.dictionary!.remove(PdfDictionaryProperties.v); @@ -230,10 +256,11 @@ class PdfRadioButtonListField extends PdfField { if (name != null) { for (int i = 0; i < items.count; i++) { final PdfRadioButtonListItem item = items[i]; - if (item.value == name.name) { - _helper.dictionary!.setName( - PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); + if (item.value == PdfName.decodeName(name.name)) { + PdfFieldHelper.getHelper(item).dictionary!.setName( + PdfName(PdfDictionaryProperties.usageApplication), + PdfDictionaryProperties.off, + ); } } } @@ -245,15 +272,23 @@ class PdfRadioButtonListField extends PdfField { PdfRadioButtonListItemHelper.getHelper(item).optionValue == value)) { _helper.selectedIndex = items.indexOf(item); - _helper.dictionary! - .setName(PdfName(PdfDictionaryProperties.v), item.value); - _helper.dictionary! - .setName(PdfName(PdfDictionaryProperties.dv), item.value); + _helper.dictionary!.setName( + PdfName(PdfDictionaryProperties.v), + item.value, + ); + _helper.dictionary!.setName( + PdfName(PdfDictionaryProperties.dv), + item.value, + ); final PdfFieldHelper helper = PdfFieldHelper.getHelper(item); helper.dictionary!.setName( - PdfName(PdfDictionaryProperties.usageApplication), item.value); - helper.dictionary! - .setName(PdfName(PdfDictionaryProperties.v), item.value); + PdfName(PdfDictionaryProperties.usageApplication), + item.value, + ); + helper.dictionary!.setName( + PdfName(PdfDictionaryProperties.v), + item.value, + ); break; } } @@ -269,9 +304,10 @@ class PdfRadioButtonListField extends PdfField { final int count = (options.count <= items.count) ? options.count : items.count; for (int i = 0; i < count; i++) { - final IPdfPrimitive? option = options[i] is PdfReferenceHolder - ? (options[i]! as PdfReferenceHolder).object - : options[i]; + final IPdfPrimitive? option = + options[i] is PdfReferenceHolder + ? (options[i]! as PdfReferenceHolder).object + : options[i]; if (option != null && option is PdfString) { PdfRadioButtonListItemHelper.getHelper(items[i]).optionValue = option.value; @@ -295,13 +331,16 @@ class PdfRadioButtonListFieldHelper extends PdfFieldHelper { /// internal method static PdfRadioButtonListFieldHelper getHelper( - PdfRadioButtonListField radioButtonList) { + PdfRadioButtonListField radioButtonList, + ) { return radioButtonList._helper; } /// internal method static PdfRadioButtonListField loaded( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfRadioButtonListField._loaded(dictionary, crossTable); } 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 index 722dd93ae..fdf4ccd96 100644 --- 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 @@ -1,3 +1,4 @@ +import 'dart:math'; import 'dart:ui'; import '../../interfaces/pdf_interface.dart'; @@ -5,8 +6,8 @@ import '../annotations/enum.dart'; import '../annotations/pdf_annotation.dart'; import '../annotations/pdf_appearance.dart'; import '../annotations/pdf_paintparams.dart'; -import '../graphics/brushes/pdf_solid_brush.dart'; import '../graphics/figures/pdf_template.dart'; +import '../graphics/pdf_color.dart'; import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../io/pdf_main_object_collection.dart'; @@ -31,38 +32,64 @@ import 'pdf_form.dart'; 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}) { + PdfSignatureField( + PdfPage page, + String name, { + Rect bounds = Rect.zero, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfSignature? signature, + String? tooltip, + PdfColor? backColor, + PdfColor? borderColor, + PdfBorderStyle? borderStyle, + }) { _helper = PdfSignatureFieldHelper(this); - _helper.internal(page, name, bounds, - borderWidth: borderWidth, - highlightMode: highlightMode, - tooltip: tooltip); + _helper.internal( + page, + name, + bounds, + borderWidth: borderWidth, + highlightMode: highlightMode, + tooltip: tooltip, + backColor: backColor, + borderColor: borderColor, + borderStyle: borderStyle, + ); form!.fieldAutoNaming - ? PdfAnnotationHelper.getHelper(_helper.widget!) - .dictionary! - .setProperty(PdfDictionaryProperties.ft, - PdfName(PdfDictionaryProperties.sig)) + ? PdfAnnotationHelper.getHelper( + _helper.widget!, + ).dictionary!.setProperty( + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.sig), + ) : _helper.dictionary!.setProperty( - PdfDictionaryProperties.ft, PdfName(PdfDictionaryProperties.sig)); + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.sig), + ); if (PdfPageHelper.getHelper(page).document != null) { PdfFormHelper.getHelper(form!).signatureFlags = [ SignatureFlags.signaturesExists, - SignatureFlags.appendOnly + SignatureFlags.appendOnly, ]; } if (signature != null) { this.signature = signature; } + if (borderWidth != null || + borderColor != null || + backColor != null || + borderStyle != null) { + _helper.appearance = true; + } } PdfSignatureField._(PdfDictionary dictionary, PdfCrossTable crossTable) { _helper = PdfSignatureFieldHelper(this); _helper.load(dictionary, crossTable); + if (dictionary.containsKey(PdfDictionaryProperties.v)) { + _helper.isLoadedSign = true; + } } //Fields @@ -75,6 +102,7 @@ class PdfSignatureField extends PdfField { /// The default value is 1. int get borderWidth => _helper.borderWidth; set borderWidth(int value) { + _helper.appearance = true; _helper.borderWidth = value; } @@ -94,6 +122,7 @@ class PdfSignatureField extends PdfField { if (_helper.isLoadedField && _signature == null) { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.v)) { _setSignature(_helper.dictionary![PdfDictionaryProperties.v]); + PdfSignatureHelper.getHelper(_signature!).field = this; } } return _signature; @@ -103,6 +132,50 @@ class PdfSignatureField extends PdfField { _initializeSignature(value); } + /// Gets or sets the color of the background. + /// + /// The default color is empty. + PdfColor get backColor => _helper.backColor; + set backColor(PdfColor value) { + _helper.appearance = true; + _helper.backColor = value; + _helper.assignBackColor(value); + } + + /// Gets or sets the color of the border. + /// + /// The default color is black. + PdfColor get borderColor => _helper.borderColor; + set borderColor(PdfColor value) { + _helper.appearance = true; + _helper.borderColor = value; + _helper.assignBorderColor(value); + } + + /// Gets or sets the border style. + /// + /// The default style is solid. + PdfBorderStyle get borderStyle => _helper.borderStyle; + set borderStyle(PdfBorderStyle value) { + _helper.appearance = true; + _helper.borderStyle = value; + } + + /// Checks whether the signature field is signed or not. + /// + /// ``` dart + /// // Load the existing PDF document. + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + /// // Create a new PDF document. + /// PdfSignatureField field = document.form.fields[0] as PdfSignatureField; + /// // Check if field is signed. + /// bool isSigned = field.isSigned; + /// // Dispose the document. + /// document.dispose(); + /// ``` + bool get isSigned => _helper.isLoadedSign; + //Implementations void _initializeSignature(PdfSignature? value) { if (value != null) { @@ -110,31 +183,34 @@ class PdfSignatureField extends PdfField { PdfSignatureHelper.getHelper(_signature!).page = page; PdfSignatureHelper.getHelper(_signature!).document = PdfPageHelper.getHelper( - PdfSignatureHelper.getHelper(_signature!).page!) - .document; - PdfSignatureHelper.getHelper(_signature!) - .checkAnnotationElementsContainsSignature(page!, name); + PdfSignatureHelper.getHelper(_signature!).page!, + ).document; + PdfSignatureHelper.getHelper( + _signature!, + ).checkAnnotationElementsContainsSignature(page!, name); PdfSignatureHelper.getHelper(_signature!).field = this; PdfDocumentHelper.getHelper( - PdfSignatureHelper.getHelper(_signature!).document!) - .catalog - .beginSave = + PdfSignatureHelper.getHelper(_signature!).document!, + ).catalog.beginSave = PdfSignatureHelper.getHelper(_signature!).catalogBeginSave; _helper.dictionary!.beginSaveList ??= []; - _helper.dictionary!.beginSaveList! - .add(PdfSignatureHelper.getHelper(_signature!).dictionaryBeginSave); + _helper.dictionary!.beginSaveList!.add( + PdfSignatureHelper.getHelper(_signature!).dictionaryBeginSave, + ); if (!_helper.skipKidsCertificate) { final PdfDocument document = PdfSignatureHelper.getHelper(_signature!).document!; - PdfSignatureHelper.getHelper(_signature!).signatureDictionary = - PdfSignatureDictionary(document, _signature!); + PdfSignatureHelper.getHelper( + _signature!, + ).signatureDictionary = PdfSignatureDictionary(document, _signature!); final PdfSignatureDictionary signatureDictionary = PdfSignatureHelper.getHelper(_signature!).signatureDictionary!; if (!PdfDocumentHelper.getHelper(document).isLoadedDocument || document.fileStructure.incrementalUpdate != false) { - PdfDocumentHelper.getHelper(document) - .objects - .add(signatureDictionary.element); + signatureDictionary.dictionary!.archive = false; + PdfDocumentHelper.getHelper( + document, + ).objects.add(signatureDictionary.element); PdfDocumentHelper.getHelper(document) .objects[PdfDocumentHelper.getHelper(document).objects.count - 1] .isModified = true; @@ -143,12 +219,15 @@ class PdfSignatureField extends PdfField { if (_helper.isLoadedField) { PdfFormHelper.getHelper(form!).signatureFlags = [ SignatureFlags.signaturesExists, - SignatureFlags.appendOnly + SignatureFlags.appendOnly, ]; final PdfDictionary widget = _helper.getWidgetAnnotation( - _helper.dictionary!, _helper.crossTable); - widget[PdfDictionaryProperties.v] = - PdfReferenceHolder(signatureDictionary); + _helper.dictionary!, + _helper.crossTable, + ); + widget[PdfDictionaryProperties.v] = PdfReferenceHolder( + signatureDictionary, + ); widget.modify(); _helper.changed = true; widget.setProperty(PdfDictionaryProperties.fieldFlags, PdfNumber(0)); @@ -156,14 +235,17 @@ class PdfSignatureField extends PdfField { } else { final PdfDictionary widget = PdfAnnotationHelper.getHelper(_helper.widget!).dictionary!; - widget.setProperty(PdfDictionaryProperties.v, - PdfReferenceHolder(signatureDictionary)); + widget.setProperty( + PdfDictionaryProperties.v, + PdfReferenceHolder(signatureDictionary), + ); widget.setProperty(PdfDictionaryProperties.fieldFlags, PdfNumber(0)); } } else { - PdfAnnotationHelper.getHelper(_helper.widget!) - .dictionary! - .setProperty(PdfDictionaryProperties.fieldFlags, PdfNumber(0)); + PdfAnnotationHelper.getHelper(_helper.widget!).dictionary!.setProperty( + PdfDictionaryProperties.fieldFlags, + PdfNumber(0), + ); } _helper.widget!.bounds = bounds; } @@ -181,7 +263,8 @@ class PdfSignatureField extends PdfField { String? subFilterType = ''; if (signatureDictionary.containsKey(PdfDictionaryProperties.subFilter)) { final IPdfPrimitive? filter = PdfCrossTable.dereference( - signatureDictionary[PdfDictionaryProperties.subFilter]); + signatureDictionary[PdfDictionaryProperties.subFilter], + ); if (filter != null && filter is PdfName) { subFilterType = filter.name; } @@ -191,8 +274,9 @@ class PdfSignatureField extends PdfField { } if (crossTable.document != null && !PdfDocumentHelper.getHelper(crossTable.document!).isLoadedDocument) { - if (signatureDictionary - .containsKey(PdfDictionaryProperties.reference)) { + if (signatureDictionary.containsKey( + PdfDictionaryProperties.reference, + )) { final IPdfPrimitive? tempArray = signatureDictionary[PdfDictionaryProperties.reference]; if (tempArray != null && tempArray is PdfArray) { @@ -205,15 +289,20 @@ class PdfSignatureField extends PdfField { tempDictionary[PdfDictionaryProperties.data]; if (tempReferenceHolder != null && tempReferenceHolder is PdfReferenceHolder && - !mainObjectCollection - .containsReference(tempReferenceHolder.reference!)) { - final IPdfPrimitive? tempObject = mainObjectCollection - .objectCollection![ - tempReferenceHolder.reference!.objectCollectionIndex!] - .object; + !mainObjectCollection.containsReference( + tempReferenceHolder.reference!, + )) { + final IPdfPrimitive? tempObject = + mainObjectCollection + .objectCollection![tempReferenceHolder + .reference! + .objectCollectionIndex!] + .object; tempReferenceHolder = PdfReferenceHolder(tempObject); tempDictionary.setProperty( - PdfDictionaryProperties.data, tempReferenceHolder); + PdfDictionaryProperties.data, + tempReferenceHolder, + ); } } } @@ -221,15 +310,19 @@ class PdfSignatureField extends PdfField { } signatureDictionary.remove(PdfDictionaryProperties.byteRange); PdfSignatureDictionary.fromDictionary( - crossTable.document!, signatureDictionary); + crossTable.document!, + signatureDictionary, + ); _helper.dictionary!.remove(PdfDictionaryProperties.contents); _helper.dictionary!.remove(PdfDictionaryProperties.byteRange); } if (signatureDictionary.containsKey(PdfDictionaryProperties.m) && signatureDictionary[PdfDictionaryProperties.m] is PdfString) { - PdfSignatureHelper.getHelper(_signature!).dateOfSign = - _helper.dictionary!.getDateTime( - signatureDictionary[PdfDictionaryProperties.m]! as PdfString); + PdfSignatureHelper.getHelper(_signature!).dateOfSign = _helper + .dictionary! + .getDateTime( + signatureDictionary[PdfDictionaryProperties.m]! as PdfString, + ); } if (signatureDictionary.containsKey(PdfDictionaryProperties.name) && signatureDictionary[PdfDictionaryProperties.name] is PdfString) { @@ -239,22 +332,26 @@ class PdfSignatureField extends PdfField { } if (signatureDictionary.containsKey(PdfDictionaryProperties.reason)) { final IPdfPrimitive? reason = PdfCrossTable.dereference( - signatureDictionary[PdfDictionaryProperties.reason]); + signatureDictionary[PdfDictionaryProperties.reason], + ); if (reason != null && reason is PdfString) { _signature!.reason = reason.value; } } if (signatureDictionary.containsKey(PdfDictionaryProperties.location)) { final IPdfPrimitive? location = PdfCrossTable.dereference( - signatureDictionary[PdfDictionaryProperties.location]); + signatureDictionary[PdfDictionaryProperties.location], + ); if (location != null && location is PdfString) { _signature!.locationInfo = location.value; } } - if (signatureDictionary - .containsKey(PdfDictionaryProperties.contactInfo)) { + if (signatureDictionary.containsKey( + PdfDictionaryProperties.contactInfo, + )) { final IPdfPrimitive? contactInfo = PdfCrossTable.dereference( - signatureDictionary[PdfDictionaryProperties.contactInfo]); + signatureDictionary[PdfDictionaryProperties.contactInfo], + ); if (contactInfo != null && contactInfo is PdfString) { _signature!.contactInfo = contactInfo.value; } @@ -283,10 +380,12 @@ class PdfSignatureField extends PdfField { : docPermission; if (permissionDictionary != null && permissionDictionary is PdfDictionary && - permissionDictionary - .containsKey(PdfDictionaryProperties.byteRange)) { + permissionDictionary.containsKey( + PdfDictionaryProperties.byteRange, + )) { final IPdfPrimitive? byteRange = PdfCrossTable.dereference( - permissionDictionary[PdfDictionaryProperties.byteRange]); + permissionDictionary[PdfDictionaryProperties.byteRange], + ); bool isValid = true; if (byteRange != null && byteRange is PdfArray && @@ -312,16 +411,18 @@ class PdfSignatureField extends PdfField { } } if (hasPermission && - signatureDictionary - .containsKey(PdfDictionaryProperties.reference)) { + signatureDictionary.containsKey( + PdfDictionaryProperties.reference, + )) { IPdfPrimitive? primitive = signatureDictionary[PdfDictionaryProperties.reference]; if (primitive is PdfArray) { primitive = primitive.elements[0]; } - IPdfPrimitive? reference = (primitive is PdfReferenceHolder) - ? primitive.object - : primitive; + IPdfPrimitive? reference = + (primitive is PdfReferenceHolder) + ? primitive.object + : primitive; if (reference != null && reference is PdfDictionary && reference.containsKey('TransformParams')) { @@ -335,11 +436,13 @@ class PdfSignatureField extends PdfField { reference.containsKey(PdfDictionaryProperties.p)) { final IPdfPrimitive? permissionNumber = PdfCrossTable.dereference( - reference[PdfDictionaryProperties.p]); + reference[PdfDictionaryProperties.p], + ); if (permissionNumber != null && permissionNumber is PdfNumber) { - _signature!.documentPermissions = - PdfSignatureHelper.getHelper(_signature!) - .getCertificateFlags(permissionNumber.value! as int); + _signature! + .documentPermissions = PdfSignatureHelper.getHelper( + _signature!, + ).getCertificateFlags(permissionNumber.value!.toInt()); } } } @@ -358,6 +461,12 @@ class PdfSignatureFieldHelper extends PdfFieldHelper { /// internal field PdfSignatureField signatureField; + /// internal field + bool appearance = false; + + /// internal field + bool isLoadedSign = false; + /// internal method static PdfSignatureFieldHelper getHelper(PdfSignatureField signatureField) { return signatureField._helper; @@ -365,7 +474,9 @@ class PdfSignatureFieldHelper extends PdfFieldHelper { /// internal method static PdfSignatureField loadSignatureField( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfSignatureField._(dictionary, crossTable); } @@ -377,56 +488,224 @@ class PdfSignatureFieldHelper extends PdfFieldHelper { @override void draw() { if (!isLoadedField) { + if (PdfAnnotationHelper.getHelper(widget!).appearance == null && + appearance) { + PdfAnnotationHelper.getHelper(widget!).appearance = PdfAppearance( + widget!, + ); + PdfAnnotationHelper.getHelper(widget!).appearance!.normal = PdfTemplate( + signatureField.bounds.width, + signatureField.bounds.height, + ); + drawAppearance( + PdfAnnotationHelper.getHelper(widget!).appearance!.normal, + ); + } super.draw(); if (PdfAnnotationHelper.getHelper(widget!).appearance != null) { signatureField.page!.graphics.drawPdfTemplate( - widget!.appearance.normal, signatureField.bounds.topLeft); + widget!.appearance.normal, + signatureField.bounds.topLeft, + ); } } else if (flattenField) { + if (!isLoadedSign && + PdfAppearanceHelper.getHelper( + signatureField.appearance, + ).templateNormal != + null) { + final PdfDictionary widget = getWidgetAnnotation( + dictionary!, + crossTable, + ); + widget.setProperty( + PdfDictionaryProperties.ap, + signatureField.appearance, + ); + } if (dictionary![PdfDictionaryProperties.ap] != null) { final IPdfPrimitive? tempDictionary = dictionary![PdfDictionaryProperties.ap]; - final IPdfPrimitive? appearanceDictionary = - PdfCrossTable.dereference(tempDictionary); + final IPdfPrimitive? appearanceDictionary = PdfCrossTable.dereference( + tempDictionary, + ); PdfTemplate template; if (appearanceDictionary != null && appearanceDictionary is PdfDictionary) { final IPdfPrimitive? appearanceRefHolder = appearanceDictionary[PdfDictionaryProperties.n]; - final IPdfPrimitive? objectDictionary = - PdfCrossTable.dereference(appearanceRefHolder); + final IPdfPrimitive? objectDictionary = PdfCrossTable.dereference( + appearanceRefHolder, + ); if (objectDictionary != null && objectDictionary is PdfDictionary) { - if (objectDictionary is PdfStream) { + if (objectDictionary is PdfStream && + objectDictionary.dataStream != null && + objectDictionary.dataStream!.isNotEmpty && + (isLoadedSign || (!isLoadedSign && !appearance))) { final PdfStream stream = objectDictionary; - template = PdfTemplateHelper.fromPdfStream(stream); - signatureField.page!.graphics - .drawPdfTemplate(template, signatureField.bounds.topLeft); + template = _drawRotatedTemplate( + PdfTemplateHelper.fromPdfStream(stream), + ); + signatureField.page!.graphics.drawPdfTemplate( + template, + signatureField.bounds.topLeft, + ); + } else { + drawRectangularControl(); } } } } else { - //signature field without appearance dictionary - final PdfBrush brush = PdfSolidBrush(getBackColor(true)); - final GraphicsProperties graphicsProperties = - GraphicsProperties(signatureField); - 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(signatureField.page!.graphics, paintingParameters); + drawRectangularControl(); + } + } + } + + PdfTemplate _drawRotatedTemplate(PdfTemplate template) { + final PdfStream content = PdfTemplateHelper.getHelper(template).content; + if (content.containsKey(PdfDictionaryProperties.matrix)) { + final IPdfPrimitive? matrix = PdfCrossTable.dereference( + content[PdfDictionaryProperties.matrix], + ); + if (matrix != null && matrix is PdfArray) { + final int angle = _obtainGraphicsRotation( + (matrix.elements[2]! as PdfNumber).value!, + (matrix.elements[0]! as PdfNumber).value!, + ); + if (angle != 0) { + PdfAnnotationHelper.setMatrixToZeroRotation(content); + final PdfTemplate rotatedTemplate = PdfTemplate( + template.size.width, + template.size.height, + ); + rotatedTemplate.graphics!.save(); + if (angle == 90) { + rotatedTemplate.graphics!.translateTransform( + 0, + template.size.height, + ); + rotatedTemplate.graphics!.rotateTransform(-90); + rotatedTemplate.graphics!.drawPdfTemplate( + template, + Offset.zero, + Size(template.size.height, template.size.width), + ); + } else if (angle == 180) { + rotatedTemplate.graphics!.translateTransform( + template.size.width, + template.size.height, + ); + rotatedTemplate.graphics!.rotateTransform(-180); + rotatedTemplate.graphics!.drawPdfTemplate( + template, + Offset.zero, + template.size, + ); + } else if (angle == 270) { + rotatedTemplate.graphics!.translateTransform( + template.size.width, + 0, + ); + rotatedTemplate.graphics!.rotateTransform(-270); + rotatedTemplate.graphics!.drawPdfTemplate( + template, + Offset.zero, + Size(template.size.height, template.size.width), + ); + } + rotatedTemplate.graphics!.restore(); + return rotatedTemplate; + } } } + return template; + } + + int _obtainGraphicsRotation(num a, num b) { + int angle = 0; + final double radians = atan2(a, b); + angle = (radians * 180 / pi).round(); + switch (angle) { + case -90: + angle = 90; + break; + case -180: + angle = 180; + break; + case 90: + angle = 270; + break; + } + return angle; + } + + /// internal method + void drawRectangularControl() { + if (!isLoadedSign && appearance) { + final PaintParams params = PaintParams( + bounds: signatureField.bounds, + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: signatureField.borderStyle, + borderWidth: signatureField.borderWidth, + shadowBrush: shadowBrush, + ); + FieldPainter().drawSignature(signatureField.page!.graphics, params); + } } /// internal method @override void drawAppearance(PdfTemplate template) { super.drawAppearance(template); - FieldPainter().drawSignature(template.graphics!, PaintParams()); + if (appearance) { + final PaintParams params = PaintParams( + bounds: Rect.fromLTWH( + 0, + 0, + signatureField.bounds.width, + signatureField.bounds.height, + ), + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: signatureField.borderStyle, + borderWidth: signatureField.borderWidth, + shadowBrush: shadowBrush, + ); + FieldPainter().drawSignature(template.graphics!, params); + } else { + FieldPainter().drawSignature( + template.graphics!, + PaintParams( + bounds: Rect.fromLTWH( + 0, + 0, + signatureField.bounds.width, + signatureField.bounds.height, + ), + ), + ); + } + } + + /// internal method + @override + void beginSave() { + if (!isLoadedSign && + appearance && + PdfAppearanceHelper.getHelper( + signatureField.appearance, + ).templateNormal == + null) { + signatureField.appearance.normal = PdfTemplate( + signatureField.bounds.width, + signatureField.bounds.height, + ); + drawAppearance(signatureField.appearance.normal); + final PdfDictionary widget = getWidgetAnnotation(dictionary!, crossTable); + widget.setProperty(PdfDictionaryProperties.ap, signatureField.appearance); + } } } 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 index 23af4a6ab..a3616f65d 100644 --- 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 @@ -34,41 +34,59 @@ import 'pdf_form.dart'; 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}) { + 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, + }) { _helper = PdfTextBoxFieldHelper(this); - _helper.internal(page, name, bounds, - font: font, - alignment: alignment, - borderColor: borderColor, - foreColor: foreColor, - backColor: backColor, - borderWidth: borderWidth, - highlightMode: highlightMode, - borderStyle: borderStyle, - tooltip: tooltip); + _helper.internal( + page, + name, + bounds, + font: font, + alignment: alignment, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip, + ); this.font = font ?? PdfStandardFont(PdfFontFamily.helvetica, 8); - _init(text, defaultValue, maxLength, spellCheck, insertSpaces, multiline, - isPassword, scrollable); + _init( + text, + defaultValue, + maxLength, + spellCheck, + insertSpaces, + multiline, + isPassword, + scrollable, + ); _helper.flags.add(FieldFlags.doNotSpellCheck); _helper.dictionary!.setProperty( - PdfDictionaryProperties.ft, PdfName(PdfDictionaryProperties.tx)); + PdfDictionaryProperties.ft, + PdfName(PdfDictionaryProperties.tx), + ); } /// Initializes a new instance of the [PdfTextBoxField] class. @@ -81,8 +99,9 @@ class PdfTextBoxField extends PdfField { for (int i = 0; i < kids.count; ++i) { final PdfDictionary? itemDictionary = crossTable.getObject(kids[i]) as PdfDictionary?; - PdfFieldItemCollectionHelper.getHelper(_items!) - .add(PdfTextBoxItemHelper.getItem(this, i, itemDictionary)); + PdfFieldItemCollectionHelper.getHelper( + _items!, + ).add(PdfTextBoxItemHelper.getItem(this, i, itemDictionary)); } _helper.array = kids; } @@ -108,8 +127,9 @@ class PdfTextBoxField extends PdfField { final IPdfPrimitive? referenceHolder = _helper.dictionary![PdfDictionaryProperties.v]; if (referenceHolder != null && referenceHolder is PdfReferenceHolder) { - final IPdfPrimitive? textObject = - PdfCrossTable.dereference(referenceHolder); + final IPdfPrimitive? textObject = PdfCrossTable.dereference( + referenceHolder, + ); if (textObject is PdfStream) { final PdfStream stream = referenceHolder.object! as PdfStream; stream.decompress(); @@ -117,14 +137,22 @@ class PdfTextBoxField extends PdfField { final String data = utf8.decode(bytes); str = PdfString(data); } else if (textObject is PdfString) { - str = PdfFieldHelper.getValue(_helper.dictionary!, _helper.crossTable, - PdfDictionaryProperties.v, true); + str = PdfFieldHelper.getValue( + _helper.dictionary!, + _helper.crossTable, + PdfDictionaryProperties.v, + true, + ); } else { str = PdfString(''); } } else { - str = PdfFieldHelper.getValue(_helper.dictionary!, _helper.crossTable, - PdfDictionaryProperties.v, true); + str = PdfFieldHelper.getValue( + _helper.dictionary!, + _helper.crossTable, + PdfDictionaryProperties.v, + true, + ); } _text = str != null && str is PdfString ? str.value : ''; return _text!; @@ -145,18 +173,23 @@ class PdfTextBoxField extends PdfField { if (dicRef != null && dicRef is PdfReferenceHolder) { final IPdfPrimitive? dict = dicRef.object; if (dict != null && dict is PdfDictionary) { - final IPdfPrimitive? str = - PdfCrossTable.dereference(dict['JS']); + final IPdfPrimitive? str = PdfCrossTable.dereference( + dict['JS'], + ); if (str != null && str is PdfString) { _helper.dictionary!.setProperty( - PdfDictionaryProperties.v, PdfString(str.value!)); + PdfDictionaryProperties.v, + PdfString(str.value!), + ); } } } } } - _helper.dictionary! - .setProperty(PdfDictionaryProperties.v, PdfString(value)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.v, + PdfString(value), + ); _helper.changed = true; PdfFormHelper.getHelper(super.form!).setAppearanceDictionary = true; if (PdfFormHelper.getHelper(super.form!).isUR3) { @@ -183,8 +216,12 @@ class PdfTextBoxField extends PdfField { /// Gets or sets the default value. String get defaultValue { if (_helper.isLoadedField) { - final IPdfPrimitive? str = PdfFieldHelper.getValue(_helper.dictionary!, - _helper.crossTable, PdfDictionaryProperties.dv, true); + final IPdfPrimitive? str = PdfFieldHelper.getValue( + _helper.dictionary!, + _helper.crossTable, + PdfDictionaryProperties.dv, + true, + ); if (str != null && str is PdfString) { _defaultValue = str.value; } @@ -207,8 +244,12 @@ class PdfTextBoxField extends PdfField { /// The default value is 0. int get maxLength { if (_helper.isLoadedField) { - final IPdfPrimitive? number = PdfFieldHelper.getValue(_helper.dictionary!, - _helper.crossTable, PdfDictionaryProperties.maxLen, true); + final IPdfPrimitive? number = PdfFieldHelper.getValue( + _helper.dictionary!, + _helper.crossTable, + PdfDictionaryProperties.maxLen, + true, + ); if (number != null && number is PdfNumber) { _maxLength = number.value!.toInt(); } @@ -231,8 +272,9 @@ class PdfTextBoxField extends PdfField { /// The default value is false. bool get spellCheck { if (_helper.isLoadedField) { - _spellCheck = !(_helper.isFlagPresent(FieldFlags.doNotSpellCheck) || - _helper.flags.contains(FieldFlags.doNotSpellCheck)); + _spellCheck = + !(_helper.isFlagPresent(FieldFlags.doNotSpellCheck) || + _helper.flags.contains(FieldFlags.doNotSpellCheck)); } return _spellCheck; } @@ -256,12 +298,14 @@ class PdfTextBoxField extends PdfField { /// The default value is false. bool get insertSpaces { final List flags = _helper.flags; - _insertSpaces = flags.contains(FieldFlags.comb) && + _insertSpaces = + flags.contains(FieldFlags.comb) && !flags.contains(FieldFlags.multiline) && !flags.contains(FieldFlags.password) && !flags.contains(FieldFlags.fileSelect); if (_helper.isLoadedField) { - _insertSpaces = _insertSpaces || + _insertSpaces = + _insertSpaces || (_helper.isFlagPresent(FieldFlags.comb) && !_helper.isFlagPresent(FieldFlags.multiline) && !_helper.isFlagPresent(FieldFlags.password) && @@ -276,8 +320,8 @@ class PdfTextBoxField extends PdfField { _insertSpaces ? _helper.flags.add(FieldFlags.comb) : _helper.isLoadedField - ? _helper.removeFlag(FieldFlags.comb) - : _helper.flags.remove(FieldFlags.comb); + ? _helper.removeFlag(FieldFlags.comb) + : _helper.flags.remove(FieldFlags.comb); } } @@ -286,7 +330,8 @@ class PdfTextBoxField extends PdfField { /// The default value is false. bool get multiline { if (_helper.isLoadedField) { - _multiline = _helper.isFlagPresent(FieldFlags.multiline) || + _multiline = + _helper.isFlagPresent(FieldFlags.multiline) || _helper.flags.contains(FieldFlags.multiline); } return _multiline; @@ -312,7 +357,8 @@ class PdfTextBoxField extends PdfField { /// The default value is false. bool get isPassword { if (_helper.isLoadedField) { - _password = _helper.isFlagPresent(FieldFlags.password) || + _password = + _helper.isFlagPresent(FieldFlags.password) || _helper.flags.contains(FieldFlags.password); } return _password; @@ -324,8 +370,8 @@ class PdfTextBoxField extends PdfField { _password ? _helper.flags.add(FieldFlags.password) : _helper.isLoadedField - ? _helper.removeFlag(FieldFlags.password) - : _helper.flags.remove(FieldFlags.password); + ? _helper.removeFlag(FieldFlags.password) + : _helper.flags.remove(FieldFlags.password); } } @@ -334,8 +380,9 @@ class PdfTextBoxField extends PdfField { /// The default value is true. bool get scrollable { if (_helper.isLoadedField) { - _scrollable = !(_helper.isFlagPresent(FieldFlags.doNotScroll) || - _helper.flags.contains(FieldFlags.doNotScroll)); + _scrollable = + !(_helper.isFlagPresent(FieldFlags.doNotScroll) || + _helper.flags.contains(FieldFlags.doNotScroll)); } return _scrollable; } @@ -416,8 +463,16 @@ class PdfTextBoxField extends PdfField { _helper.beginSave(); } - void _init(String? text, String? defaultValue, int maxLength, bool spellCheck, - bool insertSpaces, bool multiline, bool password, bool scrollable) { + void _init( + String? text, + String? defaultValue, + int maxLength, + bool spellCheck, + bool insertSpaces, + bool multiline, + bool password, + bool scrollable, + ) { if (text != null) { this.text = text; } @@ -432,8 +487,11 @@ class PdfTextBoxField extends PdfField { this.scrollable = scrollable; } - void _drawTextBox(PdfGraphics? graphics, - {PaintParams? params, PdfFieldItem? item}) { + void _drawTextBox( + PdfGraphics? graphics, { + PaintParams? params, + PdfFieldItem? item, + }) { if (params != null) { String newText = text; if (isPassword && text.isNotEmpty) { @@ -448,70 +506,117 @@ class PdfTextBoxField extends PdfField { final List ch = text.split(''); if (maxLength > 0) { width = params.bounds!.width / maxLength; - } - graphics.drawRectangle(bounds: params.bounds!, pen: _helper.borderPen); - for (int i = 0; i < maxLength; i++) { - if (_helper.format!.alignment != PdfTextAlignment.right) { - if (_helper.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] - : ''; + graphics.drawRectangle( + bounds: params.bounds!, + pen: _helper.borderPen, + ); + for (int i = 0; i < maxLength; i++) { + if (_helper.format!.alignment != PdfTextAlignment.right) { + if (_helper.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 = ch.length > i ? ch[i] : ''; + newText = + maxLength - ch.length <= i + ? ch[i - (maxLength - ch.length)] + : ''; } - } 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( + params.bounds = Rect.fromLTWH( + params.bounds!.left, + params.bounds!.top, + width, + params.bounds!.height, + ); + final PdfStringFormat format = PdfStringFormat( alignment: PdfTextAlignment.center, - lineAlignment: _helper.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( + lineAlignment: _helper.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)); + Offset( + params.bounds!.left, + params.bounds!.top + params.bounds!.height, + ), + ); + } } + } else { + FieldPainter().drawTextBox( + graphics, + params, + newText, + font, + _helper.format!, + insertSpaces, + multiline, + ); } } else { - FieldPainter().drawTextBox(graphics, params, newText, font, - _helper.format!, insertSpaces, multiline); + FieldPainter().drawTextBox( + graphics, + params, + newText, + font, + _helper.format!, + insertSpaces, + multiline, + ); } graphics.restore(); } else { - final GraphicsProperties gp = item != null - ? GraphicsProperties.fromFieldItem(item) - : GraphicsProperties(this); + 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 (PdfGraphicsHelper.getHelper(graphics!).layer == null) { - gp.bounds = Rect.fromLTWH(gp.bounds!.left, gp.bounds!.top, - graphics.size.width, graphics.size.height); + gp.bounds = Rect.fromLTWH( + gp.bounds!.left, + gp.bounds!.top, + graphics.size.width, + graphics.size.height, + ); } if (!_helper.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); + 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); } } @@ -532,25 +637,40 @@ class PdfTextBoxField extends PdfField { if (angle != null && angle is PdfNumber) { if (angle.value == 90) { template = PdfTemplate(bounds.size.height, bounds.size.width); - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = - PdfArray([0, 1, -1, 0, bounds.size.width, 0]); + PdfTemplateHelper.getHelper( + template, + ).content[PdfDictionaryProperties.matrix] = PdfArray([ + 0, + 1, + -1, + 0, + bounds.size.width, + 0, + ]); } else if (angle.value == 180) { template = PdfTemplate(bounds.size.width, bounds.size.height); - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = PdfArray([ + PdfTemplateHelper.getHelper( + template, + ).content[PdfDictionaryProperties.matrix] = PdfArray([ -1, 0, 0, -1, bounds.size.width, - bounds.size.height + bounds.size.height, ]); } else if (angle.value == 270) { template = PdfTemplate(bounds.size.height, bounds.size.width); - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = - PdfArray([0, -1, 1, 0, 0, bounds.size.height]); + PdfTemplateHelper.getHelper( + template, + ).content[PdfDictionaryProperties.matrix] = PdfArray([ + 0, + -1, + 1, + 0, + 0, + bounds.size.height, + ]); } if (template != null) { PdfTemplateHelper.getHelper(template).writeTransformation = @@ -562,27 +682,31 @@ class PdfTextBoxField extends PdfField { if (template == null) { template = PdfTemplate(bounds.size.width, bounds.size.height); PdfTemplateHelper.getHelper(template).writeTransformation = false; - PdfTemplateHelper.getHelper(template) - .content[PdfDictionaryProperties.matrix] = - PdfArray([1, 0, 0, 1, 0, 0]); + PdfTemplateHelper.getHelper(template).content[PdfDictionaryProperties + .matrix] = PdfArray([1, 0, 0, 1, 0, 0]); } if (item != null) { _helper.beginMarkupSequence( - PdfGraphicsHelper.getHelper(template.graphics!) - .streamWriter! - .stream!); - PdfGraphicsHelper.getHelper(template.graphics!) - .initializeCoordinates(); + PdfGraphicsHelper.getHelper( + template.graphics!, + ).streamWriter!.stream!, + ); + PdfGraphicsHelper.getHelper( + template.graphics!, + ).initializeCoordinates(); _drawTextBox(template.graphics, item: item); _helper.endMarkupSequence( - PdfGraphicsHelper.getHelper(template.graphics!) - .streamWriter! - .stream!); + PdfGraphicsHelper.getHelper( + template.graphics!, + ).streamWriter!.stream!, + ); } else { _helper.drawAppearance(template); } appearance.setProperty( - PdfDictionaryProperties.n, PdfReferenceHolder(template)); + PdfDictionaryProperties.n, + PdfReferenceHolder(template), + ); widget.setProperty(PdfDictionaryProperties.ap, appearance); } else { PdfFormHelper.getHelper(super.form!).needAppearances = true; @@ -599,6 +723,12 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { /// internal field PdfTextBoxField textBoxField; + /// internal field + // ignore: avoid_setters_without_getters + set items(PdfFieldItemCollection? value) { + textBoxField._items = value; + } + /// internal method static PdfTextBoxFieldHelper getHelper(PdfTextBoxField textBoxField) { return textBoxField._helper; @@ -606,7 +736,9 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { /// internal method static PdfTextBoxField loadTextBox( - PdfDictionary dictionary, PdfCrossTable crossTable) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + ) { return PdfTextBoxField._load(dictionary, crossTable); } @@ -628,18 +760,24 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { void drawAppearance(PdfTemplate template) { super.drawAppearance(template); final PaintParams params = PaintParams( - bounds: Rect.fromLTWH( - 0, 0, textBoxField.bounds.width, textBoxField.bounds.height), - backBrush: backBrush, - foreBrush: foreBrush, - borderPen: borderPen, - style: textBoxField.borderStyle, - borderWidth: textBoxField.borderWidth, - shadowBrush: shadowBrush); + bounds: Rect.fromLTWH( + 0, + 0, + textBoxField.bounds.width, + textBoxField.bounds.height, + ), + backBrush: backBrush, + foreBrush: foreBrush, + borderPen: borderPen, + style: textBoxField.borderStyle, + borderWidth: textBoxField.borderWidth, + shadowBrush: shadowBrush, + ); PdfTemplateHelper.getHelper(template).writeTransformation = false; final PdfGraphics graphics = template.graphics!; beginMarkupSequence( - PdfGraphicsHelper.getHelper(graphics).streamWriter!.stream!); + PdfGraphicsHelper.getHelper(graphics).streamWriter!.stream!, + ); PdfGraphicsHelper.getHelper(graphics).initializeCoordinates(); if (params.borderWidth == 0 && params.borderPen != null) { params.borderWidth = 1; @@ -647,7 +785,8 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { } textBoxField._drawTextBox(graphics, params: params); endMarkupSequence( - PdfGraphicsHelper.getHelper(graphics).streamWriter!.stream!); + PdfGraphicsHelper.getHelper(graphics).streamWriter!.stream!, + ); } /// internal method @@ -661,8 +800,9 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { textBoxField._applyAppearance(widget, textBoxField.items![i]); } } else { - textBoxField - ._applyAppearance(getWidgetAnnotation(dictionary!, crossTable)); + textBoxField._applyAppearance( + getWidgetAnnotation(dictionary!, crossTable), + ); } } @@ -673,7 +813,8 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { if (!textBoxField.multiline) { final PdfStandardFont font = PdfStandardFont(family, 12); final Size fontSize = font.measureString(textBoxField.text); - s = (8 * + s = + (8 * (textBoxField.bounds.size.width - 4 * textBoxField.borderWidth)) / fontSize.width; s = (s > 8) ? 8 : s; @@ -690,17 +831,19 @@ class PdfTextBoxFieldHelper extends PdfFieldHelper { if (!isLoadedField && PdfAnnotationHelper.getHelper(widget!).appearance != null) { textBoxField.page!.graphics.drawPdfTemplate( - PdfAnnotationHelper.getHelper(widget!).appearance!.normal, - Offset(textBoxField.bounds.width, textBoxField.bounds.height)); + PdfAnnotationHelper.getHelper(widget!).appearance!.normal, + Offset(textBoxField.bounds.width, textBoxField.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 = textBoxField.text; field.page!.graphics.drawPdfTemplate( - PdfAnnotationHelper.getHelper(field._helper.widget!) - .appearance! - .normal, - Offset(field.bounds.width, field.bounds.height)); + PdfAnnotationHelper.getHelper( + field._helper.widget!, + ).appearance!.normal, + Offset(field.bounds.width, field.bounds.height), + ); } } } else { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart index a384b96a7..ef26ebe75 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart @@ -3,8 +3,19 @@ import 'dart:convert'; import 'package:xml/xml.dart'; import '../../interfaces/pdf_interface.dart'; +import '../annotations/enum.dart'; +import '../annotations/json_parser.dart'; +import '../annotations/pdf_annotation.dart'; import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pdf_document/pdf_document.dart'; import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; import '../primitives/pdf_string.dart'; /// internal class @@ -15,8 +26,12 @@ class XFdfDocument { } //Fields - String? _pdfFilePath; + late String? _pdfFilePath; final Map _table = {}; + List? _annotationAttributes; + bool _skipBorderStyle = false; + bool _isStampAnnotation = false; + PdfDocument? _document; //Implementation /// internal method @@ -25,19 +40,35 @@ class XFdfDocument { } /// internal method - List save() { + List save([List? annotationData]) { List xmlData; final XmlBuilder builder = XmlBuilder(); builder.processing('xml', 'version="1.0" encoding="utf-8"'); - builder.element(PdfDictionaryProperties.xfdf.toLowerCase(), nest: () { - builder.attribute('xmlns', 'http://ns.adobe.com/xfdf/'); - builder.attribute('xml:space', 'preserve'); - builder.element(PdfDictionaryProperties.fields.toLowerCase(), - nest: _writeFormData()); - builder.element('f', nest: () { - builder.attribute('href', _pdfFilePath!); - }); - }); + builder.element( + PdfDictionaryProperties.xfdf.toLowerCase(), + nest: () { + builder.attribute('xmlns', 'http://ns.adobe.com/xfdf/'); + builder.attribute('xml:space', 'preserve'); + if (annotationData != null && annotationData.isNotEmpty) { + builder.element( + PdfDictionaryProperties.annots.toLowerCase(), + nest: annotationData, + ); + } else { + builder.element( + PdfDictionaryProperties.fields.toLowerCase(), + nest: _writeFormData(), + ); + } + builder.element( + 'f', + nest: () { + // ignore: unnecessary_null_checks + builder.attribute('href', _pdfFilePath!); + }, + ); + }, + ); xmlData = utf8.encode(builder.buildDocument().toXmlString(pretty: true)); return xmlData; } @@ -45,27 +76,1519 @@ class XFdfDocument { List _writeFormData() { final List elements = []; _table.forEach((Object key, Object value) { - final XmlElement xmlElement = - XmlElement(XmlName(PdfDictionaryProperties.field.toLowerCase())); - xmlElement.attributes.add(XmlAttribute( - XmlName(PdfDictionaryProperties.name.toLowerCase()), key.toString())); + final XmlElement xmlElement = XmlElement( + XmlName(PdfDictionaryProperties.field.toLowerCase()), + ); + xmlElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.name.toLowerCase()), + key.toString(), + ), + ); if (value is PdfArray) { for (final IPdfPrimitive? str in value.elements) { if (str is PdfString) { - xmlElement.children.add(XmlElement( + xmlElement.children.add( + XmlElement( XmlName(PdfDictionaryProperties.value.toLowerCase()), [], - [XmlText(str.value.toString())])); + [XmlText(str.value.toString())], + ), + ); } } } else { - xmlElement.children.add(XmlElement( + xmlElement.children.add( + XmlElement( XmlName(PdfDictionaryProperties.value.toLowerCase()), [], - [XmlText(value.toString())])); + [XmlText(value.toString())], + ), + ); } elements.add(xmlElement); }); return elements; } + + /// internal method + XmlElement? exportAnnotationData( + PdfDictionary annotationDictionary, + int pageIndex, + bool exportAppearance, + PdfDocument document, + ) { + _document = document; + if (!PdfDocumentHelper.isLinkAnnotation(annotationDictionary)) { + return _writeAnnotationData( + pageIndex, + exportAppearance, + annotationDictionary, + ); + } + return null; + } + + XmlElement? _writeAnnotationData( + int pageIndex, + bool exportAppearance, + PdfDictionary annotationDictionary, + ) { + XmlElement? element; + final String? type = _getAnnotationType(annotationDictionary); + _skipBorderStyle = false; + if (type != null && type.isNotEmpty) { + _annotationAttributes ??= []; + element = XmlElement(XmlName(type.toLowerCase())); + element.attributes.add( + XmlAttribute(XmlName('page'), pageIndex.toString()), + ); + switch (type) { + case PdfDictionaryProperties.line: + if (annotationDictionary.containsKey(PdfDictionaryProperties.l)) { + final IPdfPrimitive? linePoints = PdfCrossTable.dereference( + annotationDictionary[PdfDictionaryProperties.l], + ); + if (linePoints != null && + linePoints is PdfArray && + linePoints.count == 4 && + linePoints[0] != null && + linePoints[0] is PdfNumber && + linePoints[1] != null && + linePoints[1] is PdfNumber && + linePoints[2] != null && + linePoints[2] is PdfNumber && + linePoints[3] != null && + linePoints[3] is PdfNumber) { + element.attributes.add( + XmlAttribute( + XmlName('start'), + '${(linePoints[0]! as PdfNumber).value},${(linePoints[1]! as PdfNumber).value}', + ), + ); + element.attributes.add( + XmlAttribute( + XmlName('end'), + '${(linePoints[2]! as PdfNumber).value},${(linePoints[3]! as PdfNumber).value}', + ), + ); + } + } + break; + case 'Stamp': + exportAppearance = true; + _isStampAnnotation = true; + break; + case PdfDictionaryProperties.square: + if (!exportAppearance && + annotationDictionary.containsKey(PdfDictionaryProperties.it)) { + final IPdfPrimitive? name = + annotationDictionary[PdfDictionaryProperties.it]; + if (name != null && name is PdfName && name.name == 'SquareImage') { + exportAppearance = true; + } + } + break; + } + if (annotationDictionary.containsKey(PdfDictionaryProperties.be) && + annotationDictionary.containsKey(PdfDictionaryProperties.bs)) { + final IPdfPrimitive? borderEffect = PdfCrossTable.dereference( + annotationDictionary[PdfDictionaryProperties.be], + ); + if (borderEffect != null && + borderEffect is PdfDictionary && + borderEffect.containsKey(PdfDictionaryProperties.s)) { + _skipBorderStyle = true; + } + } + _writeDictionary( + annotationDictionary, + pageIndex, + element, + exportAppearance, + ); + _annotationAttributes!.clear(); + if (_isStampAnnotation) { + _isStampAnnotation = false; + } + } + return element; + } + + void _writeDictionary( + PdfDictionary dictionary, + int pageIndex, + XmlElement element, + bool exportAppearance, + ) { + bool isBSdictionary = false; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.type], + ); + if (name != null && + name is PdfName && + name.name == PdfDictionaryProperties.border && + _skipBorderStyle) { + isBSdictionary = true; + } + } + dictionary.items!.forEach((PdfName? name, IPdfPrimitive? value) { + final String key = name!.name!; + if (!((!exportAppearance && key == PdfDictionaryProperties.ap) || + key == PdfDictionaryProperties.p || + key == PdfDictionaryProperties.parent)) { + final IPdfPrimitive? primitive = value; + if (primitive is PdfReferenceHolder) { + final IPdfPrimitive? obj = primitive.object; + if (obj != null && obj is PdfDictionary) { + switch (key) { + case PdfDictionaryProperties.bs: + _writeDictionary(obj, pageIndex, element, false); + break; + case PdfDictionaryProperties.be: + _writeDictionary(obj, pageIndex, element, false); + break; + case PdfDictionaryProperties.irt: + if (obj.containsKey('NM')) { + element.attributes.add( + XmlAttribute(XmlName('inreplyto'), _getValue(obj['NM'])), + ); + } + break; + } + } + } else if (primitive is PdfDictionary) { + _writeDictionary(primitive, pageIndex, element, false); + } else if (primitive != null && (!isBSdictionary) || + (isBSdictionary && key != PdfDictionaryProperties.s)) { + _writeAttribute(element, key, primitive!); + } + } + }); + if (exportAppearance && + dictionary.containsKey(PdfDictionaryProperties.ap)) { + final IPdfPrimitive? appearance = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.ap], + ); + if (appearance != null && appearance is PdfDictionary) { + final List elements = _getAppearanceString(appearance); + if (elements.isNotEmpty) { + element.children.add( + XmlElement(XmlName('appearance'), [], [ + XmlText(base64.encode(elements)), + ]), + ); + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.measure)) { + element.children.add(_exportMeasureDictionary(dictionary)); + } + if (dictionary.containsKey('Sound')) { + final IPdfPrimitive? sound = PdfCrossTable.dereference( + dictionary['Sound'], + ); + if (sound != null && sound is PdfStream) { + if (sound.containsKey('B')) { + element.attributes.add( + XmlAttribute(XmlName('bits'), _getValue(sound['B'])), + ); + } + if (sound.containsKey(PdfDictionaryProperties.c)) { + element.attributes.add( + XmlAttribute( + XmlName('channels'), + _getValue(sound[PdfDictionaryProperties.c]), + ), + ); + } + if (sound.containsKey(PdfDictionaryProperties.e)) { + element.attributes.add( + XmlAttribute( + XmlName('encoding'), + _getValue(sound[PdfDictionaryProperties.e]), + ), + ); + } + if (sound.containsKey(PdfDictionaryProperties.r)) { + element.attributes.add( + XmlAttribute( + XmlName('rate'), + _getValue(sound[PdfDictionaryProperties.r]), + ), + ); + } + if (sound.dataStream != null && sound.dataStream!.isNotEmpty) { + final String data = PdfString.bytesToHex(sound.dataStream!); + if (!isNullOrEmpty(data)) { + element.children.add( + XmlElement( + XmlName(XfdfProperties.data.toLowerCase()), + [ + XmlAttribute(XmlName(XfdfProperties.mode), 'raw'), + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toLowerCase()), + 'hex', + ), + if (sound.containsKey(PdfDictionaryProperties.length)) + XmlAttribute( + XmlName(PdfDictionaryProperties.length.toLowerCase()), + _getValue(sound[PdfDictionaryProperties.length]), + ), + if (sound.containsKey(PdfDictionaryProperties.filter)) + XmlAttribute( + XmlName(PdfDictionaryProperties.filter), + _getValue(sound[PdfDictionaryProperties.filter]), + ), + ], + [XmlText(data)], + ), + ); + } + } + } + } else if (dictionary.containsKey(PdfDictionaryProperties.fs)) { + final IPdfPrimitive? fsDictionary = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.fs], + ); + if (fsDictionary != null && fsDictionary is PdfDictionary) { + if (fsDictionary.containsKey(PdfDictionaryProperties.f)) { + element.attributes.add( + XmlAttribute( + XmlName('file'), + _getValue(fsDictionary[PdfDictionaryProperties.f]), + ), + ); + } + if (fsDictionary.containsKey(PdfDictionaryProperties.ef)) { + final IPdfPrimitive? efDictionary = PdfCrossTable.dereference( + fsDictionary[PdfDictionaryProperties.ef], + ); + if (efDictionary != null && + efDictionary is PdfDictionary && + efDictionary.containsKey(PdfDictionaryProperties.f)) { + final IPdfPrimitive? fStream = PdfCrossTable.dereference( + efDictionary[PdfDictionaryProperties.f], + ); + if (fStream != null && fStream is PdfStream) { + if (fStream.containsKey(PdfDictionaryProperties.params)) { + final IPdfPrimitive? paramsDictionary = + PdfCrossTable.dereference( + fStream[PdfDictionaryProperties.params], + ); + if (paramsDictionary != null && + paramsDictionary is PdfDictionary) { + if (paramsDictionary.containsKey( + PdfDictionaryProperties.creationDate, + )) { + element.attributes.add( + XmlAttribute( + XmlName('creation'), + _getValue( + paramsDictionary[PdfDictionaryProperties + .creationDate], + ), + ), + ); + } + if (paramsDictionary.containsKey( + PdfDictionaryProperties.modificationDate, + )) { + element.attributes.add( + XmlAttribute( + XmlName('modification'), + _getValue( + paramsDictionary[PdfDictionaryProperties + .modificationDate], + ), + ), + ); + } + if (paramsDictionary.containsKey( + PdfDictionaryProperties.size, + )) { + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.size.toLowerCase()), + _getValue( + paramsDictionary[PdfDictionaryProperties.size], + ), + ), + ); + } + if (paramsDictionary.containsKey('CheckSum')) { + final List checksum = utf8.encode( + _getValue(paramsDictionary['CheckSum']), + ); + final String hexString = PdfString.bytesToHex(checksum); + element.attributes.add( + XmlAttribute(XmlName('checksum'), hexString), + ); + } + } + } + final String data = PdfString.bytesToHex(fStream.dataStream!); + if (!isNullOrEmpty(data)) { + element.children.add( + XmlElement( + XmlName(XfdfProperties.data.toLowerCase()), + [ + XmlAttribute( + XmlName(XfdfProperties.mode), + XfdfProperties.raw.toLowerCase(), + ), + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toLowerCase()), + XfdfProperties.hex.toLowerCase(), + ), + if (fStream.containsKey(PdfDictionaryProperties.length)) + XmlAttribute( + XmlName(PdfDictionaryProperties.length.toLowerCase()), + _getValue(fStream[PdfDictionaryProperties.length]), + ), + if (fStream.containsKey(PdfDictionaryProperties.filter)) + XmlAttribute( + XmlName(PdfDictionaryProperties.filter), + _getValue(fStream[PdfDictionaryProperties.filter]), + ), + ], + [XmlText(data)], + ), + ); + } + } + } + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.vertices)) { + final XmlElement verticesElement = XmlElement( + XmlName(PdfDictionaryProperties.vertices.toLowerCase()), + ); + final IPdfPrimitive? vertices = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.vertices], + ); + if (vertices != null && vertices is PdfArray && vertices.count > 0) { + final int elementCount = vertices.count; + if (elementCount.isEven) { + String value = ''; + IPdfPrimitive? numberElement; + for (int i = 0; i < elementCount - 1; i++) { + numberElement = vertices.elements[i]; + if (numberElement != null && numberElement is PdfNumber) { + value += _getValue(numberElement) + (i % 2 != 0 ? ';' : ','); + } + } + numberElement = vertices.elements[elementCount - 1]; + if (numberElement != null && numberElement is PdfNumber) { + value += _getValue(numberElement); + } + if (!isNullOrEmpty(value)) { + verticesElement.children.add(XmlText(value)); + } + } + } + element.children.add(verticesElement); + } + if (dictionary.containsKey(PdfDictionaryProperties.popup)) { + final IPdfPrimitive? popup = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.popup], + ); + if (popup != null && popup is PdfDictionary) { + final XmlElement? popupElement = _writeAnnotationData( + pageIndex, + exportAppearance, + popup, + ); + if (popupElement != null) { + element.children.add(popupElement); + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.da)) { + final IPdfPrimitive? defaultAppearance = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.da], + ); + if (defaultAppearance != null && defaultAppearance is PdfString) { + if (!isNullOrEmpty(defaultAppearance.value)) { + element.children.add( + XmlElement( + XmlName('defaultappearance'), + [], + [XmlText(defaultAppearance.value!)], + ), + ); + } + } + } + if (dictionary.containsKey('DS')) { + final IPdfPrimitive? defaultStyle = PdfCrossTable.dereference( + dictionary['DS'], + ); + if (defaultStyle != null && defaultStyle is PdfString) { + if (!isNullOrEmpty(defaultStyle.value)) { + element.children.add( + XmlElement(XmlName('defaultstyle'), [], [ + XmlText(defaultStyle.value!), + ]), + ); + } + } + } + if (dictionary.containsKey('InkList')) { + final IPdfPrimitive? inkList = PdfCrossTable.dereference( + dictionary['InkList'], + ); + if (inkList != null && inkList is PdfArray && inkList.count > 0) { + final XmlElement inkListElement = XmlElement(XmlName('inkList')); + for (int j = 0; j < inkList.count; j++) { + final IPdfPrimitive? list = PdfCrossTable.dereference(inkList[j]); + if (list != null && list is PdfArray) { + inkListElement.children.add( + XmlElement(XmlName('gesture'), [], [ + XmlText(_getValue(list)), + ]), + ); + } + } + element.children.add(inkListElement); + } + } + if (dictionary.containsKey('RC')) { + final IPdfPrimitive? contents = PdfCrossTable.dereference( + dictionary['RC'], + ); + if (contents != null && contents is PdfString) { + String? value = contents.value; + if (!isNullOrEmpty(value)) { + final int index = value!.indexOf(' 0) { + value = value.substring(index); + } + element.children.add( + XmlElement( + XmlName('contents-richtext'), + [], + [XmlText(value)], + ), + ); + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? contents = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.contents], + ); + if (contents != null && contents is PdfString) { + if (!isNullOrEmpty(contents.value)) { + element.children.add( + XmlElement(XmlName('contents'), [], [ + XmlText(contents.value!), + ]), + ); + } + } + } + } + + String _getColor(IPdfPrimitive primitive) { + String color = ''; + final PdfArray colorArray = primitive as PdfArray; + if (colorArray.count >= 3) { + final String r = + PdfString.bytesToHex([ + ((colorArray.elements[0]! as PdfNumber).value! * 255).round(), + ]).toUpperCase(); + final String g = + PdfString.bytesToHex([ + ((colorArray.elements[1]! as PdfNumber).value! * 255).round(), + ]).toUpperCase(); + final String b = + PdfString.bytesToHex([ + ((colorArray.elements[2]! as PdfNumber).value! * 255).round(), + ]).toUpperCase(); + color = '#$r$g$b'; + } + return color; + } + + void _writeAttribute( + XmlElement element, + String key, + IPdfPrimitive primitive, + ) { + if (_annotationAttributes != null && + !_annotationAttributes!.contains(key)) { + switch (key) { + case PdfDictionaryProperties.c: + final String color = _getColor(primitive); + if (primitive is PdfNumber) { + final String c = _getValue(primitive); + if (!isNullOrEmpty(c) && !_annotationAttributes!.contains('c')) { + element.attributes.add(XmlAttribute(XmlName('c'), c)); + _annotationAttributes!.add('c'); + } + } + if (!isNullOrEmpty(color) && + !_annotationAttributes!.contains('color')) { + element.attributes.add(XmlAttribute(XmlName('color'), color)); + _annotationAttributes!.add('color'); + } + break; + case PdfDictionaryProperties.ic: + final String interiorColor = _getColor(primitive); + if (!isNullOrEmpty(interiorColor) && + !_annotationAttributes!.contains('interior-color')) { + element.attributes.add( + XmlAttribute(XmlName('interior-color'), interiorColor), + ); + _annotationAttributes!.add('interior-color'); + } + break; + case PdfDictionaryProperties.m: + if (!_annotationAttributes!.contains('date')) { + element.attributes.add( + XmlAttribute(XmlName('date'), _getValue(primitive)), + ); + _annotationAttributes!.add('date'); + } + break; + case 'NM': + if (!_annotationAttributes!.contains( + PdfDictionaryProperties.name.toLowerCase(), + )) { + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.name.toLowerCase()), + _getValue(primitive), + ), + ); + _annotationAttributes!.add( + PdfDictionaryProperties.name.toLowerCase(), + ); + } + break; + case PdfDictionaryProperties.name: + if (!_annotationAttributes!.contains('icon')) { + element.attributes.add( + XmlAttribute(XmlName('icon'), _getValue(primitive)), + ); + _annotationAttributes!.add('icon'); + } + break; + case PdfDictionaryProperties.subj: + if (!_annotationAttributes!.contains( + PdfDictionaryProperties.subject.toLowerCase(), + )) { + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.subject.toLowerCase()), + _getValue(primitive), + ), + ); + _annotationAttributes!.add( + PdfDictionaryProperties.subject.toLowerCase(), + ); + } + break; + case PdfDictionaryProperties.t: + if (!_annotationAttributes!.contains( + PdfDictionaryProperties.title.toLowerCase(), + )) { + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.title.toLowerCase()), + _getValue(primitive), + ), + ); + _annotationAttributes!.add( + PdfDictionaryProperties.title.toLowerCase(), + ); + } + break; + case PdfDictionaryProperties.rect: + case PdfDictionaryProperties.creationDate: + if (!_annotationAttributes!.contains(key.toLowerCase())) { + element.attributes.add( + XmlAttribute(XmlName(key.toLowerCase()), _getValue(primitive)), + ); + _annotationAttributes!.add(key.toLowerCase()); + } + break; + case PdfDictionaryProperties.rotate: + if (!_annotationAttributes!.contains('rotation')) { + element.attributes.add( + XmlAttribute(XmlName('rotation'), _getValue(primitive)), + ); + _annotationAttributes!.add('rotation'); + } + break; + case PdfDictionaryProperties.w: + if (!_annotationAttributes!.contains( + PdfDictionaryProperties.width.toLowerCase(), + )) { + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.width.toLowerCase()), + _getValue(primitive), + ), + ); + _annotationAttributes!.add( + PdfDictionaryProperties.width.toLowerCase(), + ); + } + break; + case PdfDictionaryProperties.le: + if (primitive is PdfArray) { + if (primitive.count == 2) { + element.attributes.add( + XmlAttribute(XmlName('head'), _getValue(primitive.elements[0])), + ); + element.attributes.add( + XmlAttribute(XmlName('tail'), _getValue(primitive.elements[1])), + ); + } + } else if (primitive is PdfName && + !_annotationAttributes!.contains('head')) { + element.attributes.add( + XmlAttribute(XmlName('head'), _getValue(primitive)), + ); + _annotationAttributes!.add('head'); + } + break; + case PdfDictionaryProperties.s: + if (!_annotationAttributes!.contains('style')) { + switch (_getValue(primitive)) { + case PdfDictionaryProperties.d: + element.attributes.add(XmlAttribute(XmlName('style'), 'dash')); + break; + case PdfDictionaryProperties.c: + element.attributes.add( + XmlAttribute(XmlName('style'), 'cloudy'), + ); + break; + case PdfDictionaryProperties.s: + element.attributes.add(XmlAttribute(XmlName('style'), 'solid')); + break; + case 'B': + element.attributes.add( + XmlAttribute(XmlName('style'), 'bevelled'), + ); + break; + case PdfDictionaryProperties.i: + element.attributes.add(XmlAttribute(XmlName('style'), 'inset')); + break; + case PdfDictionaryProperties.u: + element.attributes.add( + XmlAttribute(XmlName('style'), 'underline'), + ); + break; + } + _annotationAttributes!.add('style'); + } + break; + case PdfDictionaryProperties.d: + if (!_annotationAttributes!.contains('dashes')) { + element.attributes.add( + XmlAttribute(XmlName('dashes'), _getValue(primitive)), + ); + _annotationAttributes!.add('dashes'); + } + break; + case PdfDictionaryProperties.i: + if (!_annotationAttributes!.contains('intensity')) { + element.attributes.add( + XmlAttribute(XmlName('intensity'), _getValue(primitive)), + ); + _annotationAttributes!.add('intensity'); + } + break; + case PdfDictionaryProperties.rd: + if (!_annotationAttributes!.contains('fringe')) { + element.attributes.add( + XmlAttribute(XmlName('fringe'), _getValue(primitive)), + ); + _annotationAttributes!.add('fringe'); + } + break; + case PdfDictionaryProperties.it: + if (!_annotationAttributes!.contains(key)) { + element.attributes.add( + XmlAttribute(XmlName(key), _getValue(primitive)), + ); + _annotationAttributes!.add(key); + } + break; + case 'RT': + if (!_annotationAttributes!.contains('replyType')) { + element.attributes.add( + XmlAttribute( + XmlName('replyType'), + _getValue(primitive).toLowerCase(), + ), + ); + _annotationAttributes!.add('replyType'); + } + break; + case PdfDictionaryProperties.ll: + if (!_annotationAttributes!.contains('leaderLength')) { + element.attributes.add( + XmlAttribute(XmlName('leaderLength'), _getValue(primitive)), + ); + _annotationAttributes!.add('leaderLength'); + } + break; + case PdfDictionaryProperties.lle: + if (!_annotationAttributes!.contains('leaderExtend')) { + element.attributes.add( + XmlAttribute(XmlName('leaderExtend'), _getValue(primitive)), + ); + _annotationAttributes!.add('leaderExtend'); + } + break; + case PdfDictionaryProperties.cap: + if (!_annotationAttributes!.contains('caption')) { + element.attributes.add( + XmlAttribute(XmlName('caption'), _getValue(primitive)), + ); + _annotationAttributes!.add('caption'); + } + break; + case PdfDictionaryProperties.q: + if (!_annotationAttributes!.contains('justification')) { + element.attributes.add( + XmlAttribute(XmlName('justification'), _getValue(primitive)), + ); + _annotationAttributes!.add('justification'); + } + break; + case PdfDictionaryProperties.cp: + if (!_annotationAttributes!.contains('caption-style')) { + element.attributes.add( + XmlAttribute(XmlName('caption-style'), _getValue(primitive)), + ); + _annotationAttributes!.add('caption-style'); + } + break; + case 'CL': + if (!_annotationAttributes!.contains('callout')) { + element.attributes.add( + XmlAttribute(XmlName('callout'), _getValue(primitive)), + ); + _annotationAttributes!.add('callout'); + } + break; + case 'FD': + if (!_annotationAttributes!.contains(key.toLowerCase())) { + element.attributes.add( + XmlAttribute(XmlName(key.toLowerCase()), _getValue(primitive)), + ); + _annotationAttributes!.add(key.toLowerCase()); + } + break; + case PdfDictionaryProperties.quadPoints: + if (!_annotationAttributes!.contains('Coords')) { + element.attributes.add( + XmlAttribute(XmlName('coords'), _getValue(primitive)), + ); + _annotationAttributes!.add('coords'); + } + break; + case PdfDictionaryProperties.ca: + if (!_annotationAttributes!.contains('opacity')) { + element.attributes.add( + XmlAttribute(XmlName('opacity'), _getValue(primitive)), + ); + _annotationAttributes!.add('opacity'); + } + break; + case PdfDictionaryProperties.f: + if (primitive is PdfNumber && + !_annotationAttributes!.contains( + PdfDictionaryProperties.flags.toLowerCase(), + )) { + final List annotationFlags = + PdfAnnotationHelper.obtainAnnotationFlags( + primitive.value!.toInt(), + ); + final String flag = annotationFlags + .map( + (PdfAnnotationFlags flag) => getEnumName(flag).toLowerCase(), + ) + .toString() + .replaceAll(' ', ''); + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.flags.toLowerCase()), + flag.substring(1, flag.length - 1), + ), + ); + _annotationAttributes!.add( + PdfDictionaryProperties.flags.toLowerCase(), + ); + } + break; + case 'InkList': + case PdfDictionaryProperties.type: + case PdfDictionaryProperties.subtype: + case PdfDictionaryProperties.p: + case PdfDictionaryProperties.parent: + case PdfDictionaryProperties.l: + case PdfDictionaryProperties.contents: + case 'RC': + case PdfDictionaryProperties.da: + case 'DS': + case PdfDictionaryProperties.fs: + case 'MeasurementTypes': + case PdfDictionaryProperties.vertices: + case 'GroupNesting': + case 'ITEx': + break; + case PdfDictionaryProperties.open: + case 'State': + case 'StateModel': + case 'OverlayText': + case 'OC': + case PdfDictionaryProperties.llo: + case 'Repeat': + if (!_annotationAttributes!.contains(key)) { + element.attributes.add( + XmlAttribute(XmlName(key.toLowerCase()), _getValue(primitive)), + ); + _annotationAttributes!.add(key.toLowerCase()); + } + break; + case PdfDictionaryProperties.border: + if (!_annotationAttributes!.contains( + PdfDictionaryProperties.width.toLowerCase(), + ) && + !_annotationAttributes!.contains( + PdfDictionaryProperties.border.toLowerCase(), + ) && + primitive is PdfArray && + primitive.count >= 3 && + primitive.elements[2] is PdfNumber) { + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.width.toLowerCase()), + _getValue(primitive.elements[2]), + ), + ); + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.border.toLowerCase()), + _getValue(primitive), + ), + ); + _annotationAttributes!.add(PdfDictionaryProperties.border); + _annotationAttributes!.add(PdfDictionaryProperties.width); + } + break; + default: + if (!_annotationAttributes!.contains(key)) { + element.attributes.add( + XmlAttribute(XmlName(key), _getValue(primitive)), + ); + _annotationAttributes!.add(key); + } + break; + } + } + } + + /// internal method + String _getValue(IPdfPrimitive? primitive) { + String value = ''; + if (primitive != null) { + if (primitive is PdfName) { + value = primitive.name!; + } else if (primitive is PdfBoolean) { + value = primitive.value! ? 'yes' : 'no'; + } else if (primitive is PdfString) { + value = primitive.value!; + } else if (primitive is PdfArray) { + if (primitive.elements.isNotEmpty) { + value = _getValue(primitive.elements[0]); + } + for (int i = 1; i < primitive.elements.length; i++) { + value += ',${_getValue(primitive.elements[i])}'; + } + } else if (primitive is PdfNumber) { + value = primitive.value.toString(); + } + if (value.contains('\u0002')) { + value = value.replaceAll('\u0002', '\u2010'); + } + } + return value; + } + + String? _getAnnotationType(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype], + ); + if (subtype != null && subtype is PdfName && subtype.name != null) { + return subtype.name!; + } + } + return null; + } + + List _getAppearanceString(PdfDictionary appearanceDictionary) { + final XmlBuilder builder = XmlBuilder(); + builder.processing('xml', 'version="1.0" encoding="utf-8"'); + builder.element( + 'DICT', + attributes: { + XfdfProperties.key: PdfDictionaryProperties.ap, + }, + nest: _writeAppearanceDictionary(appearanceDictionary), + ); + final String xmlString = builder.buildDocument().toXmlString(); + return utf8.encode(xmlString); + } + + List _writeAppearanceDictionary(PdfDictionary dictionary) { + final List elements = []; + if (dictionary.count > 0) { + dictionary.items!.forEach((PdfName? key, IPdfPrimitive? value) { + final XmlElement? element = _writeObject(key!.name!, value); + if (element != null) { + elements.add(element); + } + }); + } + return elements; + } + + XmlElement? _writeObject(String key, IPdfPrimitive? primitive) { + XmlElement? element; + if (primitive != null) { + final String type = primitive.runtimeType.toString(); + switch (type) { + case 'PdfReferenceHolder': + element = _writeObject(key, (primitive as PdfReferenceHolder).object); + break; + case 'PdfDictionary': + element = XmlElement(XmlName(XfdfProperties.dict)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + element.children.addAll( + _writeAppearanceDictionary(primitive as PdfDictionary), + ); + break; + case 'PdfStream': + final PdfStream streamElement = primitive as PdfStream; + if (streamElement.dataStream != null && + streamElement.dataStream!.isNotEmpty) { + element = XmlElement(XmlName(XfdfProperties.stream)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.define), ''), + ); + element.children.addAll(_writeAppearanceDictionary(streamElement)); + final String type = _getValue( + streamElement[PdfDictionaryProperties.subtype], + ); + final XmlElement dataElement = XmlElement( + XmlName(XfdfProperties.data), + ); + if ((streamElement.containsKey(PdfDictionaryProperties.subtype) && + PdfDictionaryProperties.image == type) || + (!streamElement.containsKey(PdfDictionaryProperties.type) && + !streamElement.containsKey( + PdfDictionaryProperties.subtype, + ))) { + dataElement.attributes.add( + XmlAttribute(XmlName(XfdfProperties.mode), XfdfProperties.raw), + ); + dataElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex, + ), + ); + String data = ''; + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } else if (streamElement.containsKey( + PdfDictionaryProperties.subtype, + ) && + PdfDictionaryProperties.form == type && + !_isStampAnnotation) { + dataElement.attributes.add( + XmlAttribute(XmlName(XfdfProperties.mode), XfdfProperties.raw), + ); + dataElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex, + ), + ); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } else { + streamElement.decompress(); + if (streamElement.dataStream != null) { + final String ascii = utf8.decode(streamElement.dataStream!); + if (_isStampAnnotation && !ascii.contains('TJ')) { + dataElement.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.mode), + XfdfProperties.filtered, + ), + ); + dataElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.ascii, + ), + ); + if (!isNullOrEmpty(ascii)) { + dataElement.children.add( + XmlText(_getFormatedString(ascii)), + ); + } + } + } else { + dataElement.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.mode), + XfdfProperties.raw, + ), + ); + dataElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex, + ), + ); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } + } + element.children.add(dataElement); + } + break; + case 'PdfBoolean': + element = XmlElement(XmlName(XfdfProperties.bool)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + element.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.val), + primitive is PdfBoolean && + primitive.value != null && + primitive.value! + ? 'true' + : 'false', + ), + ); + break; + case 'PdfName': + element = XmlElement(XmlName(XfdfProperties.name)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + element.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.val), + (primitive as PdfName).name ?? '', + ), + ); + break; + case 'PdfString': + element = XmlElement(XmlName(XfdfProperties.string)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + element.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.val), + (primitive as PdfString).value ?? '', + ), + ); + break; + case 'PdfNumber': + element = XmlElement(XmlName(XfdfProperties.fixed)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + final String value = (primitive as PdfNumber).value! + .toDouble() + .toStringAsFixed(6); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.val), value), + ); + break; + case 'PdfNull': + element = XmlElement(XmlName(XfdfProperties.nullVal)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + break; + case 'PdfArray': + element = XmlElement(XmlName(XfdfProperties.array)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.key), key), + ); + element.children.addAll(_writeArray(primitive as PdfArray)); + break; + } + } + return element; + } + + List _writeArray(PdfArray array) { + final List elements = []; + for (final IPdfPrimitive? element in array.elements) { + final XmlElement? xmlElement = _writeArrayElement(element!); + if (xmlElement != null) { + elements.add(xmlElement); + } + } + return elements; + } + + XmlElement? _writeArrayElement(IPdfPrimitive primitive) { + XmlElement? element; + final String type = primitive.runtimeType.toString(); + switch (type) { + case 'PdfArray': + element = XmlElement(XmlName(XfdfProperties.array)); + element.children.addAll(_writeArray(primitive as PdfArray)); + break; + case 'PdfName': + element = XmlElement(XmlName(XfdfProperties.name)); + element.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.val), + (primitive as PdfName).name ?? '', + ), + ); + break; + case 'PdfString': + element = XmlElement(XmlName(XfdfProperties.string)); + final RegExp regex = RegExp(r'[\u0085-\u00FF]'); + if ((primitive as PdfString).value != null && + regex.hasMatch(primitive.value!) && + primitive.isHex != null && + primitive.isHex!) { + final List bytes = primitive.pdfEncode(_document); + primitive.value = PdfString.byteToString(bytes); + element.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex, + ), + ); + if (!isNullOrEmpty(primitive.value)) { + element.children.add(XmlText(_getFormatedString(primitive.value!))); + } + } else { + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.val), primitive.value ?? ''), + ); + } + break; + case 'PdfNumber': + element = XmlElement(XmlName(XfdfProperties.fixed)); + final String value = (primitive as PdfNumber).value! + .toDouble() + .toStringAsFixed(6); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.val), value), + ); + break; + case 'PdfBoolean': + element = XmlElement(XmlName(XfdfProperties.bool)); + element.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.val), + (primitive as PdfBoolean).value != null && primitive.value! + ? 'true' + : 'false', + ), + ); + break; + case 'PdfDictionary': + element = XmlElement(XmlName(XfdfProperties.dict)); + element.children.addAll( + _writeAppearanceDictionary(primitive as PdfDictionary), + ); + break; + case 'PdfStream': + final PdfStream streamElement = primitive as PdfStream; + if (streamElement.dataStream != null && + streamElement.dataStream!.isNotEmpty) { + element = XmlElement(XmlName(XfdfProperties.stream)); + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.define), ''), + ); + element.children.addAll(_writeAppearanceDictionary(streamElement)); + final XmlElement dataElement = XmlElement( + XmlName(XfdfProperties.data), + ); + final String type = _getValue( + streamElement[PdfDictionaryProperties.subtype], + ); + if (streamElement.containsKey(PdfDictionaryProperties.subtype) && + PdfDictionaryProperties.image == type) { + dataElement.attributes.add( + XmlAttribute(XmlName(XfdfProperties.mode), XfdfProperties.raw), + ); + dataElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex, + ), + ); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } else { + dataElement.attributes.add( + XmlAttribute( + XmlName(XfdfProperties.mode), + XfdfProperties.filtered, + ), + ); + dataElement.attributes.add( + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.ascii, + ), + ); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = utf8.decode(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(_getFormatedString(data))); + } + } + element.children.add(dataElement); + } + break; + case 'PdfReferenceHolder': + if ((primitive as PdfReferenceHolder).object != null) { + element = _writeArrayElement(primitive.object!); + } + break; + } + return element; + } + + XmlElement _exportMeasureDictionary(PdfDictionary dictionary) { + final XmlElement measureXmlElement = XmlElement(XmlName('measure')); + final IPdfPrimitive? mdictionary = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.measure], + ); + if (mdictionary != null && mdictionary is PdfDictionary) { + if (mdictionary.containsKey(PdfDictionaryProperties.r)) { + measureXmlElement.attributes.add( + XmlAttribute( + XmlName('rateValue'), + _getValue(mdictionary[PdfDictionaryProperties.r]), + ), + ); + } + if (mdictionary.containsKey(PdfDictionaryProperties.a)) { + IPdfPrimitive? aprimitive = PdfCrossTable.dereference( + mdictionary[PdfDictionaryProperties.a], + ); + if (aprimitive != null && aprimitive is PdfArray) { + aprimitive = PdfCrossTable.dereference(aprimitive.elements[0]); + if (aprimitive != null && aprimitive is PdfDictionary) { + final XmlElement areaXmlElement = XmlElement(XmlName('area')); + _exportMeasureFormatDetails(aprimitive, areaXmlElement); + measureXmlElement.children.add(areaXmlElement); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.d)) { + IPdfPrimitive? dprimitive = PdfCrossTable.dereference( + mdictionary[PdfDictionaryProperties.d], + ); + if (dprimitive != null && dprimitive is PdfArray) { + dprimitive = PdfCrossTable.dereference(dprimitive.elements[0]); + if (dprimitive != null && dprimitive is PdfDictionary) { + final XmlElement distanceXmlElement = XmlElement( + XmlName('distance'), + ); + _exportMeasureFormatDetails(dprimitive, distanceXmlElement); + measureXmlElement.children.add(distanceXmlElement); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.x)) { + IPdfPrimitive? xprimitive = PdfCrossTable.dereference( + mdictionary[PdfDictionaryProperties.x], + ); + if (xprimitive != null && xprimitive is PdfArray) { + xprimitive = PdfCrossTable.dereference(xprimitive.elements[0]); + if (xprimitive != null && xprimitive is PdfDictionary) { + final XmlElement xformatXmlElement = XmlElement(XmlName('xformat')); + _exportMeasureFormatDetails(xprimitive, xformatXmlElement); + measureXmlElement.children.add(xformatXmlElement); + } + } + } + } + return measureXmlElement; + } + + void _exportMeasureFormatDetails( + PdfDictionary measurementDetails, + XmlElement element, + ) { + if (measurementDetails.containsKey(PdfDictionaryProperties.c)) { + element.attributes.add( + XmlAttribute( + XmlName('c'), + _getValue(measurementDetails[PdfDictionaryProperties.c]), + ), + ); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.f)) { + element.attributes.add( + XmlAttribute( + XmlName('f'), + _getValue(measurementDetails[PdfDictionaryProperties.f]), + ), + ); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.d)) { + element.attributes.add( + XmlAttribute( + XmlName('d'), + _getValue(measurementDetails[PdfDictionaryProperties.d]), + ), + ); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.rd)) { + element.attributes.add( + XmlAttribute( + XmlName('rd'), + _getValue(measurementDetails[PdfDictionaryProperties.rd]), + ), + ); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.u)) { + element.attributes.add( + XmlAttribute( + XmlName('u'), + _getValue(measurementDetails[PdfDictionaryProperties.u]), + ), + ); + } + if (measurementDetails.containsKey('RT')) { + element.attributes.add( + XmlAttribute(XmlName('rt'), _getValue(measurementDetails['RT'])), + ); + } + if (measurementDetails.containsKey('SS')) { + element.attributes.add( + XmlAttribute(XmlName('ss'), _getValue(measurementDetails['SS'])), + ); + } + if (measurementDetails.containsKey('FD')) { + element.attributes.add( + XmlAttribute(XmlName('fd'), _getValue(measurementDetails['FD'])), + ); + } + } + + String _getFormatedString(String value) { + value = value.replaceAll('&', '&'); + value = value.replaceAll('<', '<'); + value = value.replaceAll('>', '>'); + return value; + } +} + +/// XFDF dictionary properties. +class XfdfProperties { + //Constants + /// internal field + static const String dict = 'DICT'; + + /// internal field + static const String key = 'KEY'; + + /// internal field + static const String stream = 'STREAM'; + + /// internal field + static const String define = 'DEFINE'; + + /// internal field + static const String data = 'DATA'; + + /// internal field + static const String mode = 'MODE'; + + /// internal field + static const String raw = 'RAW'; + + /// internal field + static const String hex = 'HEX'; + + /// internal field + static const String filtered = 'FILTERED'; + + /// internal field + static const String ascii = 'ASCII'; + + /// internal field + static const String bool = 'BOOL'; + + /// internal field + static const String val = 'VAL'; + + /// internal field + static const String name = 'NAME'; + + /// internal field + static const String string = 'STRING'; + + /// internal field + static const String int = 'INT'; + + /// internal field + static const String fixed = 'FIXED'; + + /// internal field + static const String nullVal = 'NULL'; + + /// internal field + static const String array = 'ARRAY'; } 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 4d9fb0173..19f1e4776 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 @@ -50,8 +50,10 @@ class EmbeddedFile implements IPdfWrapper { //Implementations. void _initialize() { - _stream.setProperty(PdfDictionaryProperties.type, - PdfName(PdfDictionaryProperties.embeddedFile)); + _stream.setProperty( + PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.embeddedFile), + ); _stream.setProperty(PdfDictionaryProperties.params, params); _stream.beginSave = _streamBeginSave; } @@ -64,7 +66,7 @@ class EmbeddedFile implements IPdfWrapper { void _streamBeginSave(Object sender, SavePdfPrimitiveArgs? ars) { _stream.clearStream(); _stream.compress = false; - _stream.dataStream = data; + _stream.data = data; params.size = data.length; } @@ -110,6 +112,7 @@ class EmbeddedFileParams implements IPdfWrapper { // int get _size => _fileSize; /// internal property + // ignore: avoid_setters_without_getters set size(int value) { if (_fileSize != value) { _fileSize = value; 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 89b358002..07bd6fa4b 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 @@ -12,9 +12,9 @@ class PdfEmbeddedFileSpecification extends PdfFileSpecificationBase { /// Initializes a new instance of the [PdfEmbeddedFileSpecification] class PdfEmbeddedFileSpecification(String fileName, List data) : super() { _helper = PdfEmbeddedFileSpecificationHelper(this); - PdfFileSpecificationBaseHelper.getHelper(this) - .dictionary! - .setProperty(PdfDictionaryProperties.ef, _helper._dict); + PdfFileSpecificationBaseHelper.getHelper( + this, + ).dictionary!.setProperty(PdfDictionaryProperties.ef, _helper._dict); _helper.embeddedFile = EmbeddedFile(fileName, data); _helper.description = fileName; } @@ -33,7 +33,8 @@ class PdfEmbeddedFileSpecificationHelper { /// internal method static PdfEmbeddedFileSpecificationHelper getHelper( - PdfEmbeddedFileSpecification base) { + PdfEmbeddedFileSpecification base, + ) { return base._helper; } @@ -53,9 +54,9 @@ class PdfEmbeddedFileSpecificationHelper { set description(String value) { if (_desc != value) { _desc = value; - PdfFileSpecificationBaseHelper.getHelper(base) - .dictionary! - .setString(PdfDictionaryProperties.description, _desc); + PdfFileSpecificationBaseHelper.getHelper( + base, + ).dictionary!.setString(PdfDictionaryProperties.description, _desc); } } @@ -70,13 +71,15 @@ class PdfEmbeddedFileSpecificationHelper { void save() { _dict[PdfDictionaryProperties.f] = PdfReferenceHolder(embeddedFile); final PdfString str = PdfString( - PdfFileSpecificationBaseHelper.getHelper(base) - .formatFileName(embeddedFile.fileName, false)); - PdfFileSpecificationBaseHelper.getHelper(base) - .dictionary! - .setProperty(PdfDictionaryProperties.f, str); - PdfFileSpecificationBaseHelper.getHelper(base) - .dictionary! - .setProperty(PdfDictionaryProperties.uf, str); + PdfFileSpecificationBaseHelper.getHelper( + base, + ).formatFileName(embeddedFile.fileName, false), + ); + PdfFileSpecificationBaseHelper.getHelper( + base, + ).dictionary!.setProperty(PdfDictionaryProperties.f, str); + PdfFileSpecificationBaseHelper.getHelper( + base, + ).dictionary!.setProperty(PdfDictionaryProperties.uf, str); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/enum.dart index 962689afd..72ac59380 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/enum.dart @@ -25,5 +25,5 @@ enum PdfDestinationMode { /// top positioned at the top edge of the window and the contents of the page /// magnified just enough to fit the entire width of the page /// within the window. - fitH + fitH, } 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 d28495aa8..2d2520a29 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 @@ -30,7 +30,8 @@ class PdfFileSpecificationBaseHelper { /// internal method static PdfFileSpecificationBaseHelper getHelper( - PdfFileSpecificationBase base) { + PdfFileSpecificationBase base, + ) { return base._helper; } @@ -44,8 +45,10 @@ class PdfFileSpecificationBaseHelper { void initialize() { dictionary = PdfDictionary(); - dictionary!.setProperty(PdfDictionaryProperties.type, - PdfName(PdfDictionaryProperties.filespec)); + dictionary!.setProperty( + PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.filespec), + ); dictionary!.beginSave = _dictionaryBeginSave; } 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 888839b2c..f0d2fd111 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 @@ -140,15 +140,14 @@ class PdfDestination implements IPdfWrapper { _array.add(PdfName(PdfDictionaryProperties.fitH)); _array.add(PdfNumber(value)); break; - default: - break; } _helper.element = _array; } PdfPoint _pointToNativePdf(PdfPage page, PdfPoint point) { - return PdfSectionHelper.getHelper(PdfPageHelper.getHelper(page).section!) - .pointToNativePdf(page, point); + return PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).pointToNativePdf(page, point); } } 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 5e64e4e8e..69333f7da 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 @@ -1,11 +1,10 @@ import 'dart:ui'; -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/pages/pdf_page_collection.dart'; - import '../../interfaces/pdf_interface.dart'; import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../pages/pdf_page.dart'; +import '../pages/pdf_page_collection.dart'; import '../primitives/pdf_array.dart'; import '../primitives/pdf_dictionary.dart'; import '../primitives/pdf_name.dart'; @@ -27,8 +26,10 @@ class PdfNamedDestination implements IPdfWrapper { } PdfNamedDestination._( - PdfDictionary dictionary, PdfCrossTable crossTable, bool isLoaded) - : super() { + PdfDictionary dictionary, + PdfCrossTable crossTable, + bool isLoaded, + ) : super() { _helper = PdfNamedDestinationHelper(this); _helper.dictionary = dictionary; _crossTable = crossTable; @@ -66,8 +67,11 @@ class PdfNamedDestination implements IPdfWrapper { if (_isLoaded) { String? title = ''; if (_helper.dictionary!.containsKey(PdfDictionaryProperties.title)) { - final PdfString str = _crossTable.getObject( - _helper.dictionary![PdfDictionaryProperties.title])! as PdfString; + final PdfString str = + _crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.title], + )! + as PdfString; title = str.value; } return title!; @@ -93,14 +97,17 @@ class PdfNamedDestination implements IPdfWrapper { _helper.dictionary!.setProperty(PdfDictionaryProperties.d, _destination); }; _helper.dictionary!.setProperty( - PdfDictionaryProperties.s, PdfName(PdfDictionaryProperties.goTo)); + PdfDictionaryProperties.s, + PdfName(PdfDictionaryProperties.goTo), + ); } PdfDestination? _obtainDestination() { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.d) && (_destination == null)) { - final IPdfPrimitive? obj = - _crossTable.getObject(_helper.dictionary![PdfDictionaryProperties.d]); + final IPdfPrimitive? obj = _crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.d], + ); final PdfArray? destination = obj as PdfArray?; if (destination != null && destination.count > 1) { final PdfReferenceHolder? referenceHolder = @@ -110,9 +117,9 @@ class PdfNamedDestination implements IPdfWrapper { final PdfDictionary? dictionary = _crossTable.getObject(referenceHolder) as PdfDictionary?; if (dictionary != null) { - page = - PdfPageCollectionHelper.getHelper(_crossTable.document!.pages) - .getPage(dictionary); + page = PdfPageCollectionHelper.getHelper( + _crossTable.document!.pages, + ).getPage(dictionary); } } @@ -182,7 +189,10 @@ class PdfNamedDestinationHelper { /// internal method static PdfNamedDestination load( - PdfDictionary dictionary, PdfCrossTable crossTable, bool isLoaded) { + PdfDictionary dictionary, + PdfCrossTable crossTable, + bool isLoaded, + ) { return PdfNamedDestination._(dictionary, crossTable, isLoaded); } } 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 b3e04c74d..d87f1fd46 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 @@ -17,7 +17,9 @@ class PdfNamedDestinationCollection implements IPdfWrapper { } PdfNamedDestinationCollection._( - PdfDictionary? dictionary, PdfCrossTable? crossTable) { + PdfDictionary? dictionary, + PdfCrossTable? crossTable, + ) { _helper = PdfNamedDestinationCollectionHelper(this); _helper.dictionary = dictionary; if (crossTable != null) { @@ -25,8 +27,11 @@ class PdfNamedDestinationCollection implements IPdfWrapper { } if (_helper.dictionary != null && _helper.dictionary!.containsKey(PdfDictionaryProperties.dests)) { - final PdfDictionary? destination = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.dests]) as PdfDictionary?; + final PdfDictionary? destination = + PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.dests], + ) + as PdfDictionary?; if (destination != null && destination.containsKey(PdfDictionaryProperties.names)) { _addCollection(destination); @@ -38,32 +43,43 @@ class PdfNamedDestinationCollection implements IPdfWrapper { if (kids != null) { for (int i = 0; i < kids.count; i++) { _findDestination( - PdfCrossTable.dereference(kids[i]) as PdfDictionary?); + PdfCrossTable.dereference(kids[i]) as PdfDictionary?, + ); } } } } - PdfDocumentHelper.getHelper(_crossTable.document!).catalog.beginSave = - (Object sender, SavePdfPrimitiveArgs? ars) { + PdfDocumentHelper.getHelper(_crossTable.document!).catalog.beginSave = ( + Object sender, + SavePdfPrimitiveArgs? ars, + ) { for (final PdfNamedDestination values in _namedCollections) { _namedDestination.add(PdfString(values.title)); _namedDestination.add(PdfReferenceHolder(values)); } _helper.dictionary!.setProperty( - PdfDictionaryProperties.names, PdfReferenceHolder(_namedDestination)); + PdfDictionaryProperties.names, + PdfReferenceHolder(_namedDestination), + ); if (_helper.dictionary!.containsKey(PdfDictionaryProperties.dests)) { - final PdfDictionary? destsDictionary = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.dests]) - as PdfDictionary?; + final PdfDictionary? destsDictionary = + PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.dests], + ) + as PdfDictionary?; if (destsDictionary != null && !destsDictionary.containsKey(PdfDictionaryProperties.kids)) { - destsDictionary.setProperty(PdfDictionaryProperties.names, - PdfReferenceHolder(_namedDestination)); + destsDictionary.setProperty( + PdfDictionaryProperties.names, + PdfReferenceHolder(_namedDestination), + ); } } else { - _helper.dictionary!.setProperty(PdfDictionaryProperties.names, - PdfReferenceHolder(_namedDestination)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.names, + PdfReferenceHolder(_namedDestination), + ); } }; PdfDocumentHelper.getHelper(_crossTable.document!).catalog.modify(); @@ -114,7 +130,8 @@ class PdfNamedDestinationCollection implements IPdfWrapper { void removeAt(int index) { if (index >= _namedCollections.length) { throw RangeError( - 'The index value should not be greater than or equal to the count.'); + 'The index value should not be greater than or equal to the count.', + ); } _namedCollections.removeAt(index); } @@ -128,7 +145,8 @@ class PdfNamedDestinationCollection implements IPdfWrapper { void insert(int index, PdfNamedDestination namedDestination) { if (index < 0 || index > count) { throw RangeError( - "The index can't be less then zero or greater then Count."); + "The index can't be less then zero or greater then Count.", + ); } _namedCollections.insert(index, namedDestination); } @@ -141,13 +159,18 @@ class PdfNamedDestinationCollection implements IPdfWrapper { _namedDestination.add(PdfReferenceHolder(values)); } _helper.dictionary!.setProperty( - PdfDictionaryProperties.names, PdfReferenceHolder(_namedDestination)); + PdfDictionaryProperties.names, + PdfReferenceHolder(_namedDestination), + ); }; } void _addCollection(PdfDictionary namedDictionary) { - final PdfArray? elements = PdfCrossTable.dereference( - namedDictionary[PdfDictionaryProperties.names]) as PdfArray?; + final PdfArray? elements = + PdfCrossTable.dereference( + namedDictionary[PdfDictionaryProperties.names], + ) + as PdfArray?; if (elements != null) { for (int i = 1; i <= elements.count; i = i + 2) { PdfReferenceHolder? reference; @@ -157,13 +180,17 @@ class PdfNamedDestinationCollection implements IPdfWrapper { PdfDictionary? dictionary; if (reference != null && reference.object is PdfArray) { dictionary = PdfDictionary(); - dictionary.setProperty(PdfDictionaryProperties.d, - PdfArray(reference.object as PdfArray?)); + dictionary.setProperty( + PdfDictionaryProperties.d, + PdfArray(reference.object as PdfArray?), + ); } else if (reference == null && elements[i] is PdfArray) { dictionary = PdfDictionary(); final PdfArray referenceArray = elements[i]! as PdfArray; dictionary.setProperty( - PdfDictionaryProperties.d, PdfArray(referenceArray)); + PdfDictionaryProperties.d, + PdfArray(referenceArray), + ); } else { dictionary = reference!.object as PdfDictionary?; } @@ -192,7 +219,8 @@ class PdfNamedDestinationCollection implements IPdfWrapper { if (kids != null) { for (int i = 0; i < kids.count; i++) { _findDestination( - PdfCrossTable.dereference(kids[i]) as PdfDictionary?); + PdfCrossTable.dereference(kids[i]) as PdfDictionary?, + ); } } } @@ -212,13 +240,16 @@ class PdfNamedDestinationCollectionHelper { /// internal method static PdfNamedDestinationCollectionHelper getHelper( - PdfNamedDestinationCollection destination) { + PdfNamedDestinationCollection destination, + ) { return destination._helper; } /// internal method static PdfNamedDestinationCollection load( - PdfDictionary? dictionary, PdfCrossTable? crossTable) { + PdfDictionary? dictionary, + PdfCrossTable? crossTable, + ) { return PdfNamedDestinationCollection._(dictionary, crossTable); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/windows1252encoding.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/windows1252encoding.dart new file mode 100644 index 000000000..85fae6576 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/windows1252encoding.dart @@ -0,0 +1,271 @@ +class Windows1252Encoding { + static const List _charCodeTable = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 8364, + 65533, + 8218, + 402, + 8222, + 8230, + 8224, + 8225, + 710, + 8240, + 352, + 8249, + 338, + 65533, + 381, + 65533, + 65533, + 8216, + 8217, + 8220, + 8221, + 8226, + 8211, + 8212, + 732, + 8482, + 353, + 8250, + 339, + 65533, + 382, + 376, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + ]; + + List getBytes(String s) { + final List bytes = []; + for (int i = 0; i < s.length; i++) { + final int index = s.codeUnitAt(i); + if (index < _charCodeTable.length) { + bytes.add(_charCodeTable[index]); + } + } + return bytes; + } +} 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 442480b91..412e2c7bb 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 @@ -3010,7 +3010,9 @@ class PdfBrushes { static PdfBrush _getBrush(KnownColor kColor) { final ColorHelper color = ColorHelper(kColor); - final PdfBrush brush = PdfSolidBrush(PdfColor(color.r, color.g, color.b)); + final PdfBrush brush = PdfSolidBrush( + PdfColor(color.r, color.g, color.b, color.a), + ); _brushes[kColor] = brush; return brush; } 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 d2d46c4a6..3b5140717 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 @@ -26,11 +26,12 @@ class PdfSolidBrush implements PdfBrush { @override bool _monitorChanges( - PdfBrush? brush, - PdfStreamWriter? streamWriter, - Function? getResources, - bool saveChanges, - PdfColorSpace? currentColorSpace) { + PdfBrush? brush, + PdfStreamWriter? streamWriter, + Function? getResources, + bool saveChanges, + PdfColorSpace? currentColorSpace, + ) { bool diff = false; if (getResources != null && streamWriter != null) { if (brush == null) { @@ -72,11 +73,12 @@ class PdfSolidBrush implements PdfBrush { /// ``` abstract class PdfBrush { bool _monitorChanges( - PdfBrush? brush, - PdfStreamWriter? streamWriter, - Function? getResources, - bool saveChanges, - PdfColorSpace? currentColorSpace); + PdfBrush? brush, + PdfStreamWriter? streamWriter, + Function? getResources, + bool saveChanges, + PdfColorSpace? currentColorSpace, + ); } // ignore: avoid_classes_with_only_static_members @@ -84,13 +86,19 @@ abstract class PdfBrush { class PdfBrushHelper { /// internal method static bool monitorChanges( - PdfBrush base, - PdfBrush? brush, - PdfStreamWriter? streamWriter, - Function? getResources, - bool saveChanges, - PdfColorSpace? currentColorSpace) { + PdfBrush base, + PdfBrush? brush, + PdfStreamWriter? streamWriter, + Function? getResources, + bool saveChanges, + PdfColorSpace? currentColorSpace, + ) { return base._monitorChanges( - brush, streamWriter, getResources, saveChanges, currentColorSpace); + brush, + streamWriter, + getResources, + saveChanges, + currentColorSpace, + ); } } 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 bb00fc3f5..55f5d8252 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 @@ -7,7 +7,7 @@ /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(alignment: PdfTextAlignment.left)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -22,7 +22,7 @@ enum PdfTextAlignment { right, /// Specifies the text as Justified text. - justify + justify, } /// Specifies the type of Vertical alignment. @@ -34,7 +34,7 @@ enum PdfTextAlignment { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.top)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -58,7 +58,7 @@ enum PdfVerticalAlignment { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(textDirection: PdfTextDirection.none)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -70,7 +70,7 @@ enum PdfTextDirection { leftToRight, /// Specifies the right to left direction. - rightToLeft + rightToLeft, } /// Defines set of color spaces. @@ -99,7 +99,7 @@ enum PdfColorSpace { grayScale, /// Indexed color space. - indexed + indexed, } /// Possible dash styles of the pen. @@ -113,7 +113,7 @@ enum PdfColorSpace { /// ..dashPattern = [4, 2, 1, 3], /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -134,7 +134,7 @@ enum PdfDashStyle { dashDotDot, /// User defined dash style. - custom + custom, } /// Specifies the corner style of the shapes. @@ -148,7 +148,7 @@ enum PdfDashStyle { /// ..dashPattern = [4, 2, 1, 3], /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -164,7 +164,7 @@ enum PdfLineJoin { /// The two segments are finished with caps and the resulting notch beyond /// the ends of the segments is filled with a triangle. - bevel + bevel, } /// Specifies the line cap style to be used at the ends of the lines. @@ -178,7 +178,7 @@ enum PdfLineJoin { /// ..dashPattern = [4, 2, 1, 3], /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -193,7 +193,7 @@ enum PdfLineCap { /// The stroke continues beyond the endpoint of the path for a distance /// equal to half the line width and is squared off. - square + square, } /// Specifies how the shapes are filled. @@ -217,7 +217,7 @@ enum PdfFillMode { winding, /// Even odd rule of determining "insideness" of point. - alternate + alternate, } /// Specifies the blend mode for transparency. @@ -321,5 +321,5 @@ enum PdfBlendMode { /// Creates a color with the luminosity of the source color /// and the hue and saturation of the backdrop color. This /// produces an inverse effect to that of the Color mode. - luminosity + luminosity, } 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 d7c53e67e..dc5afb420 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 @@ -50,7 +50,11 @@ abstract class ElementLayouter { return param.format!._boundsSet ? PdfRectangle.fromRect(param.format!.paginateBounds) : PdfRectangle( - param.bounds!.x, 0, param.bounds!.width, param.bounds!.height); + param.bounds!.x, + 0, + param.bounds!.width, + param.bounds!.height, + ); } } @@ -58,10 +62,11 @@ abstract class ElementLayouter { class PdfLayoutFormat { //Constructor /// Initializes a new instance of the [PdfLayoutFormat] class. - PdfLayoutFormat( - {PdfLayoutType? layoutType, - PdfLayoutBreakType? breakType, - Rect? paginateBounds}) { + PdfLayoutFormat({ + PdfLayoutType? layoutType, + PdfLayoutBreakType? breakType, + Rect? paginateBounds, + }) { this.breakType = breakType ?? PdfLayoutBreakType.fitPage; this.layoutType = layoutType ?? PdfLayoutType.paginate; if (paginateBounds != null) { 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 4fc3eaba3..292665532 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 @@ -30,18 +30,23 @@ 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? _draw(PdfGraphics? graphics, PdfPage? page, Rect? bounds, - PdfLayoutFormat? format) { + PdfLayoutResult? _draw( + PdfGraphics? graphics, + PdfPage? page, + Rect? bounds, + PdfLayoutFormat? format, + ) { if (page != null) { final PdfLayoutParams param = PdfLayoutParams(); param.page = page; @@ -100,8 +105,9 @@ class PdfLayoutElementHelper { if (base is PdfShapeElement) { return PdfShapeElementHelper.layout(base as PdfShapeElement, param); } else if (base is PdfTextElement) { - return PdfTextElementHelper.getHelper(base as PdfTextElement) - .layout(param); + return PdfTextElementHelper.getHelper( + base as PdfTextElement, + ).layout(param); } else if (base is PdfList) { return PdfListHelper.getHelper(base as PdfList).layout(param); } else if (base is PdfGrid) { @@ -127,16 +133,19 @@ class PdfLayoutElementHelper { /// internal method void drawInternal(PdfGraphics graphics, PdfRectangle bounds) { if (base is PdfBezierCurve) { - PdfBezierCurveHelper.getHelper(base as PdfBezierCurve) - .drawInternal(graphics, bounds); + PdfBezierCurveHelper.getHelper( + base as PdfBezierCurve, + ).drawInternal(graphics, bounds); } else if (base is PdfPath) { PdfPathHelper.getHelper(base as PdfPath).drawInternal(graphics, bounds); } else if (base is PdfTextElement) { - PdfTextElementHelper.getHelper(base as PdfTextElement) - .drawInternal(graphics, bounds); + PdfTextElementHelper.getHelper( + base as PdfTextElement, + ).drawInternal(graphics, bounds); } else if (base is PdfImage) { - PdfBitmapHelper.getHelper(base as PdfBitmap) - .drawInternal(graphics, bounds); + PdfBitmapHelper.getHelper( + base as PdfBitmap, + ).drawInternal(graphics, bounds); } else if (base is PdfList) { PdfListHelper.getHelper(base as PdfList).drawInternal(graphics, bounds); } else if (base is PdfGrid) { @@ -150,7 +159,7 @@ class PdfLayoutElementHelper { class EndTextPageLayoutArgs extends EndPageLayoutArgs { /// Initializes a new instance of the [EndTextPageLayoutArgs] class /// with the specified [PdfTextLayoutResult]. - EndTextPageLayoutArgs(PdfTextLayoutResult result) : super(result); + EndTextPageLayoutArgs(PdfTextLayoutResult super.result); } /// Provides data for event once lay outing completed on the new page. @@ -206,10 +215,10 @@ class PdfCancelArgs { /// Represents the method that will handle an event /// that before lay outing on the page. -typedef BeginPageLayoutCallback = void Function( - Object sender, BeginPageLayoutArgs args); +typedef BeginPageLayoutCallback = + void Function(Object sender, BeginPageLayoutArgs args); /// Represents the method that will handle an event, /// once completed the lay outing on the page. -typedef EndPageLayoutCallback = void Function( - Object sender, EndPageLayoutArgs args); +typedef EndPageLayoutCallback = + void Function(Object sender, EndPageLayoutArgs args); 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 7354aa7e8..5bf1097ed 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 @@ -41,8 +41,9 @@ abstract class PdfShapeElement extends PdfLayoutElement { PdfRectangle? _getBoundsInternal() { if (this is PdfBezierCurve) { - return PdfBezierCurveHelper.getHelper(this as PdfBezierCurve) - .getBoundsInternal(); + return PdfBezierCurveHelper.getHelper( + this as PdfBezierCurve, + ).getBoundsInternal(); } else if (this is PdfPath) { return PdfPathHelper.getHelper(this as PdfPath).getBoundsInternal(); } else if (this is PdfImage) { @@ -75,7 +76,9 @@ class PdfShapeElementHelper { /// internal method static PdfLayoutResult? layout( - PdfShapeElement element, PdfLayoutParams param) { + PdfShapeElement element, + PdfLayoutParams param, + ) { return element._layout(param); } 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 12e2ed10c..f737a6730 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 @@ -14,7 +14,7 @@ import 'text_layouter.dart'; class ShapeLayouter extends ElementLayouter { // constructor /// internal constructor - ShapeLayouter(PdfShapeElement element) : super(element); + ShapeLayouter(PdfShapeElement super.element); // properties /// Gets shape element. @@ -35,23 +35,30 @@ class ShapeLayouter extends ElementLayouter { pageResult._page = currentPage; while (true) { // ref is there so implement in proper way - final Map returnedValue = - _raiseBeforePageLayout(currentPage, currentBounds!.rect); + final Map returnedValue = _raiseBeforePageLayout( + currentPage, + currentBounds!.rect, + ); bool cancel = returnedValue['cancel'] as bool; currentBounds = PdfRectangle.fromRect(returnedValue['bounds']); EndPageLayoutArgs? endArgs; if (!cancel) { pageResult = _layoutOnPage( - currentPage!, currentBounds, shapeLayoutBounds!, param); + currentPage!, + currentBounds, + shapeLayoutBounds!, + param, + ); endArgs = _raiseEndPageLayout(pageResult); cancel = endArgs != null && endArgs.cancel; } if (!pageResult._end && !cancel) { currentBounds = getPaginateBounds(param); shapeLayoutBounds = _getNextShapeBounds(shapeLayoutBounds!, pageResult); - currentPage = (endArgs == null || endArgs.nextPage == null) - ? getNextPage(currentPage!) - : endArgs.nextPage; + currentPage = + (endArgs == null || endArgs.nextPage == null) + ? getNextPage(currentPage!) + : endArgs.nextPage; } else { result = _getLayoutResult(pageResult); break; @@ -61,11 +68,15 @@ class ShapeLayouter extends ElementLayouter { } Map _raiseBeforePageLayout( - PdfPage? currentPage, Rect currentBounds) { + PdfPage? currentPage, + Rect currentBounds, + ) { bool cancel = false; if (PdfLayoutElementHelper.getHelper(element!).raiseBeginPageLayout) { - final BeginPageLayoutArgs args = - BeginPageLayoutArgs(currentBounds, currentPage!); + final BeginPageLayoutArgs args = BeginPageLayoutArgs( + currentBounds, + currentPage!, + ); PdfLayoutElementHelper.getHelper(element!).onBeginPageLayout(args); cancel = args.cancel; currentBounds = args.bounds; @@ -74,13 +85,18 @@ class ShapeLayouter extends ElementLayouter { } _ShapeLayoutResult _layoutOnPage( - PdfPage currentPage, - PdfRectangle currentBounds, - PdfRectangle shapeLayoutBounds, - PdfLayoutParams param) { + PdfPage currentPage, + PdfRectangle currentBounds, + PdfRectangle shapeLayoutBounds, + PdfLayoutParams param, + ) { final _ShapeLayoutResult result = _ShapeLayoutResult(); currentBounds = _checkCorrectCurrentBounds( - currentPage, currentBounds, shapeLayoutBounds, param); + currentPage, + currentBounds, + shapeLayoutBounds, + param, + ); final bool fitToPage = _fitsToBounds(currentBounds, shapeLayoutBounds); final bool canDraw = !(param.format!.breakType == PdfLayoutBreakType.fitElement && @@ -88,8 +104,10 @@ class ShapeLayouter extends ElementLayouter { currentPage == param.page); bool shapeFinished = false; if (canDraw) { - final PdfRectangle drawRectangle = - _getDrawBounds(currentBounds, shapeLayoutBounds); + final PdfRectangle drawRectangle = _getDrawBounds( + currentBounds, + shapeLayoutBounds, + ); _drawShape(currentPage.graphics, currentBounds.rect, drawRectangle); result._bounds = _getPageResultBounds(currentBounds, shapeLayoutBounds).rect; @@ -103,59 +121,79 @@ class ShapeLayouter extends ElementLayouter { } PdfRectangle _checkCorrectCurrentBounds( - PdfPage currentPage, - PdfRectangle currentBounds, - PdfRectangle shapeLayoutBounds, - PdfLayoutParams param) { + PdfPage currentPage, + PdfRectangle currentBounds, + PdfRectangle shapeLayoutBounds, + PdfLayoutParams param, + ) { final Size pageSize = currentPage.graphics.clientSize; - currentBounds.width = (currentBounds.width > 0) - ? currentBounds.width - : pageSize.width - currentBounds.x; - currentBounds.height = (currentBounds.height > 0) - ? currentBounds.height - : pageSize.height - currentBounds.y; + currentBounds.width = + (currentBounds.width > 0) + ? currentBounds.width + : pageSize.width - currentBounds.x; + currentBounds.height = + (currentBounds.height > 0) + ? currentBounds.height + : pageSize.height - currentBounds.y; return currentBounds; } bool _fitsToBounds( - PdfRectangle currentBounds, PdfRectangle shapeLayoutBounds) { + PdfRectangle currentBounds, + PdfRectangle shapeLayoutBounds, + ) { final bool fits = shapeLayoutBounds.height <= currentBounds.height; return fits; } PdfRectangle _getDrawBounds( - PdfRectangle currentBounds, PdfRectangle shapeLayoutBounds) { - final PdfRectangle result = PdfRectangle(currentBounds.x, currentBounds.y, - currentBounds.width, currentBounds.height); + PdfRectangle currentBounds, + PdfRectangle shapeLayoutBounds, + ) { + final PdfRectangle result = PdfRectangle( + currentBounds.x, + currentBounds.y, + currentBounds.width, + currentBounds.height, + ); result.y -= shapeLayoutBounds.y; result.height += shapeLayoutBounds.y; return result; } void _drawShape( - PdfGraphics g, Rect currentBounds, PdfRectangle drawRectangle) { + PdfGraphics g, + Rect currentBounds, + PdfRectangle drawRectangle, + ) { final PdfGraphicsState gState = g.save(); try { g.setClip(bounds: currentBounds); element!.draw( - graphics: g, - bounds: Rect.fromLTWH(drawRectangle.x, drawRectangle.y, 0, 0)); + graphics: g, + bounds: Rect.fromLTWH(drawRectangle.x, drawRectangle.y, 0, 0), + ); } finally { g.restore(gState); } } PdfRectangle _getPageResultBounds( - PdfRectangle currentBounds, PdfRectangle shapeLayoutBounds) { + PdfRectangle currentBounds, + PdfRectangle shapeLayoutBounds, + ) { final PdfRectangle result = currentBounds; - result.height = (result.height > shapeLayoutBounds.height) - ? shapeLayoutBounds.height - : result.height; + result.height = + (result.height > shapeLayoutBounds.height) + ? shapeLayoutBounds.height + : result.height; return result; } PdfRectangle _getNextShapeBounds( - PdfRectangle shapeLayoutBounds, _ShapeLayoutResult pageResult) { + PdfRectangle shapeLayoutBounds, + _ShapeLayoutResult pageResult, + ) { shapeLayoutBounds.y += pageResult._bounds!.height; shapeLayoutBounds.height -= pageResult._bounds!.height; return shapeLayoutBounds; @@ -172,8 +210,10 @@ class ShapeLayouter extends ElementLayouter { } PdfLayoutResult _getLayoutResult(_ShapeLayoutResult pageResult) { - final PdfLayoutResult result = - PdfLayoutResultHelper.load(pageResult._page!, pageResult._bounds!); + final PdfLayoutResult result = PdfLayoutResultHelper.load( + pageResult._page!, + pageResult._bounds!, + ); return result; } } 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 ae4ecda59..8bfbf7fbe 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 @@ -16,7 +16,7 @@ import 'layout_element.dart'; class TextLayouter extends ElementLayouter { //Constructor /// internal constructor - TextLayouter(PdfTextElement element) : super(element); + TextLayouter(PdfTextElement super.element); //Fields PdfStringFormat? _format; @@ -27,8 +27,12 @@ class TextLayouter extends ElementLayouter { super.element is PdfTextElement ? super.element as PdfTextElement? : null; //Implementation - _TextPageLayoutResult _layoutOnPage(String text, PdfPage currentPage, - PdfRectangle currentBounds, PdfLayoutParams param) { + _TextPageLayoutResult _layoutOnPage( + String text, + PdfPage currentPage, + PdfRectangle currentBounds, + PdfLayoutParams param, + ) { final _TextPageLayoutResult result = _TextPageLayoutResult(); result.remainder = text; result.page = currentPage; @@ -37,109 +41,150 @@ class TextLayouter extends ElementLayouter { currentPage = getNextPage(currentPage)!; result.page = currentPage; currentBounds = PdfRectangle( - currentBounds.x, 0, currentBounds.width, currentBounds.height); + currentBounds.x, + 0, + currentBounds.width, + currentBounds.height, + ); } final PdfStringLayouter layouter = PdfStringLayouter(); final PdfStringLayoutResult stringResult = layouter.layout( - text, element!.font, _format, - bounds: currentBounds, pageHeight: currentPage.getClientSize().height); + text, + element!.font, + _format, + bounds: currentBounds, + pageHeight: currentPage.getClientSize().height, + ); final bool textFinished = stringResult.remainder == null || stringResult.remainder!.isEmpty; final bool doesntFit = param.format!.breakType == PdfLayoutBreakType.fitElement && - currentPage == param.page && - !textFinished; + currentPage == param.page && + !textFinished; final bool canDraw = !(doesntFit || stringResult.isEmpty); if (canDraw) { final PdfGraphics graphics = currentPage.graphics; PdfGraphicsHelper.getHelper(graphics).drawStringLayoutResult( - stringResult, - element!.font, - element!.pen, - PdfTextElementHelper.getHelper(element!).obtainBrush(), - currentBounds, - _format); + stringResult, + element!.font, + element!.pen, + PdfTextElementHelper.getHelper(element!).obtainBrush(), + currentBounds, + _format, + ); final LineInfo lineInfo = stringResult.lines![stringResult.lineCount - 1]; - result.lastLineBounds = PdfGraphicsHelper.getHelper(graphics) - .getLineBounds(stringResult.lineCount - 1, stringResult, - element!.font, currentBounds, _format); - result.bounds = - _getTextPageBounds(currentPage, currentBounds, stringResult); + result.lastLineBounds = PdfGraphicsHelper.getHelper( + graphics, + ).getLineBounds( + stringResult.lineCount - 1, + stringResult, + element!.font, + currentBounds, + _format, + ); + result.bounds = _getTextPageBounds( + currentPage, + currentBounds, + stringResult, + ); result.remainder = stringResult.remainder; _checkCorectStringFormat(lineInfo); } else { - result.bounds = - _getTextPageBounds(currentPage, currentBounds, stringResult); + result.bounds = _getTextPageBounds( + currentPage, + currentBounds, + stringResult, + ); } - final bool stopLayouting = stringResult.isEmpty && + final bool stopLayouting = + stringResult.isEmpty && ((param.format!.breakType != PdfLayoutBreakType.fitElement) && (param.format!.layoutType != PdfLayoutType.paginate) && canDraw) || (param.format!.breakType == PdfLayoutBreakType.fitElement && currentPage != param.page); - result.end = textFinished || + result.end = + textFinished || stopLayouting || param.format!.layoutType == PdfLayoutType.onePage; return result; } PdfRectangle _checkCorrectBounds( - PdfPage currentPage, PdfRectangle currentBounds) { + PdfPage currentPage, + PdfRectangle currentBounds, + ) { final Size pageSize = currentPage.graphics.clientSize; - currentBounds.height = (currentBounds.height > 0) - ? currentBounds.height - : pageSize.height - currentBounds.y; + currentBounds.height = + (currentBounds.height > 0) + ? currentBounds.height + : pageSize.height - currentBounds.y; return currentBounds; } - Rect _getTextPageBounds(PdfPage currentPage, PdfRectangle currentBounds, - PdfStringLayoutResult stringResult) { + Rect _getTextPageBounds( + PdfPage currentPage, + PdfRectangle currentBounds, + PdfStringLayoutResult stringResult, + ) { final PdfSize textSize = stringResult.size; double? x = currentBounds.x; double? y = currentBounds.y; final double width = (currentBounds.width > 0) ? currentBounds.width : textSize.width; final double height = textSize.height; - final PdfRectangle shiftedRect = - PdfGraphicsHelper.getHelper(currentPage.graphics) - .checkCorrectLayoutRectangle( - textSize, currentBounds.x, currentBounds.y, _format); + final PdfRectangle shiftedRect = PdfGraphicsHelper.getHelper( + currentPage.graphics, + ).checkCorrectLayoutRectangle( + textSize, + currentBounds.x, + currentBounds.y, + _format, + ); if (currentBounds.width <= 0) { x = shiftedRect.x; } if (currentBounds.height <= 0) { y = shiftedRect.y; } - final double verticalShift = - PdfGraphicsHelper.getHelper(currentPage.graphics) - .getTextVerticalAlignShift( - textSize.height, currentBounds.height, _format); + final double verticalShift = PdfGraphicsHelper.getHelper( + currentPage.graphics, + ).getTextVerticalAlignShift(textSize.height, currentBounds.height, _format); y += verticalShift; return Rect.fromLTWH(x, y, width, height); } void _checkCorectStringFormat(LineInfo lineInfo) { if (_format != null) { - PdfStringFormatHelper.getHelper(_format!).firstLineIndent = (lineInfo - .lineType & - PdfStringLayouter.getLineTypeValue(LineType.newLineBreak)! > - 0) - ? PdfStringFormatHelper.getHelper(element!.stringFormat!) - .firstLineIndent - : 0; + PdfStringFormatHelper.getHelper(_format!).firstLineIndent = + (lineInfo.lineType & + PdfStringLayouter.getLineTypeValue( + LineType.newLineBreak, + )! > + 0) + ? PdfStringFormatHelper.getHelper( + element!.stringFormat!, + ).firstLineIndent + : 0; } } PdfTextLayoutResult _getLayoutResult(_TextPageLayoutResult pageResult) { - return PdfTextLayoutResult._(pageResult.page, pageResult.bounds, - pageResult.remainder, pageResult.lastLineBounds); + return PdfTextLayoutResult._( + pageResult.page, + pageResult.bounds, + pageResult.remainder, + pageResult.lastLineBounds, + ); } bool _raiseBeforePageLayout(PdfPage? currentPage, Rect currentBounds) { bool cancel = false; if (PdfLayoutElementHelper.getHelper(element!).raiseBeginPageLayout) { - final BeginPageLayoutArgs args = - BeginPageLayoutArgs(currentBounds, currentPage!); + final BeginPageLayoutArgs args = BeginPageLayoutArgs( + currentBounds, + currentPage!, + ); PdfLayoutElementHelper.getHelper(element!).onBeginPageLayout(args); cancel = args.cancel; currentBounds = args.bounds; @@ -183,9 +228,10 @@ class TextLayouter extends ElementLayouter { } currentBounds = getPaginateBounds(param); text = pageResult.remainder; - currentPage = endArgs == null || endArgs.nextPage == null - ? getNextPage(currentPage!) - : endArgs.nextPage; + currentPage = + endArgs == null || endArgs.nextPage == null + ? getNextPage(currentPage!) + : endArgs.nextPage; } else { result = _getLayoutResult(pageResult); break; @@ -202,8 +248,11 @@ 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; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/enums.dart index 8a33398e1..3bc9a90b9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/enums.dart @@ -4,7 +4,7 @@ enum PdfLayoutType { paginate, /// Draw the element on the one page only. - onePage + onePage, } /// Specifies how the element should be contained on the page. @@ -16,7 +16,7 @@ enum PdfLayoutBreakType { fitElement, /// Fit the columns withtin the page. - fitColumnsToPage + fitColumnsToPage, } /// Specifies path point type @@ -31,5 +31,5 @@ enum PathPointType { bezier3, /// closeSubpath type - closeSubpath + closeSubpath, } 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 973ba731a..3a305ec6f 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 @@ -10,9 +10,13 @@ class PdfBezierCurve extends PdfShapeElement { // constructor /// Initializes a new instance of the [PdfBezierCurve] class /// with the specified [PdfPen] and [Offset] structure. - PdfBezierCurve(Offset startPoint, Offset firstControlPoint, - Offset secondControlPoint, Offset endPoint, - {PdfPen? pen}) { + PdfBezierCurve( + Offset startPoint, + Offset firstControlPoint, + Offset secondControlPoint, + Offset endPoint, { + PdfPen? pen, + }) { _helper = PdfBezierCurveHelper(this); if (pen != null) { super.pen = pen; @@ -79,9 +83,13 @@ class PdfBezierCurveHelper { /// internal method void drawInternal(PdfGraphics graphics, PdfRectangle bounds) { - graphics.drawBezier(base.startPoint, base.firstControlPoint, - base.secondControlPoint, base.endPoint, - pen: PdfShapeElementHelper.obtainPen(base)); + graphics.drawBezier( + base.startPoint, + base.firstControlPoint, + base.secondControlPoint, + base.endPoint, + pen: PdfShapeElementHelper.obtainPen(base), + ); } /// internal method 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 a0952d72f..2b6303bfe 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 @@ -15,12 +15,13 @@ class PdfPath extends PdfShapeElement { // constructor /// Initializes a new instance of the [PdfPath] class /// with pen, brush, fillMode, points and pathTypes - PdfPath( - {PdfPen? pen, - PdfBrush? brush, - PdfFillMode fillMode = PdfFillMode.winding, - List points = const [], - List? pathTypes}) { + PdfPath({ + PdfPen? pen, + PdfBrush? brush, + PdfFillMode fillMode = PdfFillMode.winding, + List points = const [], + List? pathTypes, + }) { _helper = PdfPathHelper(this); if (pen != null) { super.pen = pen; @@ -79,7 +80,8 @@ class PdfPath extends PdfShapeElement { final int count = pathPoints.length; if (count != pathTypes.length) { throw ArgumentError.value( - 'The argument arrays should be of equal length.'); + 'The argument arrays should be of equal length.', + ); } _helper.points.addAll(pathPoints); _helper.pathTypes.addAll(_assignPathtype(pathTypes)); @@ -87,8 +89,12 @@ class PdfPath extends PdfShapeElement { /// Adds a line void addLine(Offset point1, Offset point2) { - _addPoints([point1.dx, point1.dy, point2.dx, point2.dy], - PathPointType.line); + _addPoints([ + point1.dx, + point1.dy, + point2.dx, + point2.dy, + ], PathPointType.line); } /// Adds a rectangle @@ -102,7 +108,7 @@ class PdfPath extends PdfShapeElement { bounds.left + bounds.width, bounds.top + bounds.height, bounds.left, - bounds.top + bounds.height + bounds.top + bounds.height, ], PathPointType.line); closeFigure(); } @@ -112,8 +118,9 @@ class PdfPath extends PdfShapeElement { startFigure(); addArc(bounds, startAngle, sweepAngle); _addPoint( - Offset(bounds.left + bounds.width / 2, bounds.top + bounds.height / 2), - PathPointType.line); + Offset(bounds.left + bounds.width / 2, bounds.top + bounds.height / 2), + PathPointType.line, + ); closeFigure(); } @@ -125,8 +132,12 @@ class PdfPath extends PdfShapeElement { } /// Adds an bezier curve - void addBezier(Offset startPoint, Offset firstControlPoint, - Offset secondControlPoint, Offset endPoint) { + void addBezier( + Offset startPoint, + Offset firstControlPoint, + Offset secondControlPoint, + Offset endPoint, + ) { final List points = []; points.add(startPoint.dx); points.add(startPoint.dy); @@ -142,12 +153,13 @@ class PdfPath extends PdfShapeElement { /// Adds an arc void addArc(Rect bounds, double startAngle, double sweepAngle) { final List> points = PdfGraphicsHelper.getBezierArcPoints( - bounds.left, - bounds.top, - bounds.left + bounds.width, - bounds.top + bounds.height, - startAngle, - sweepAngle); + bounds.left, + bounds.top, + bounds.left + bounds.width, + bounds.top + bounds.height, + startAngle, + sweepAngle, + ); final List list = []; for (int i = 0; i < points.length; ++i) { final List pt = points[i]; @@ -264,4 +276,18 @@ class PdfPathHelper { void drawInternal(PdfGraphics graphics, PdfRectangle bounds) { graphics.drawPath(path, pen: path.pen, brush: path.brush); } + + /// internal method + void addLines(List linePoints) { + Offset start = linePoints[0]; + if (linePoints.length == 1) { + path._addPoint(linePoints[0], PathPointType.line); + } else { + for (int i = 1; i < linePoints.length; i++) { + final Offset last = linePoints[i]; + path.addLine(start, last); + start = last; + } + } + } } 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 d23fd5140..bd9359d67 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 @@ -4,6 +4,7 @@ import '../../../interfaces/pdf_interface.dart'; import '../../drawing/drawing.dart'; import '../../io/pdf_constants.dart'; import '../../io/pdf_cross_table.dart'; +import '../../pages/pdf_page.dart'; import '../../primitives/pdf_array.dart'; import '../../primitives/pdf_dictionary.dart'; import '../../primitives/pdf_name.dart'; @@ -23,7 +24,7 @@ import '../pdf_resources.dart'; /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)), /// Offset(0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -39,7 +40,7 @@ class PdfTemplate implements IPdfWrapper { /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)), /// Offset(0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -47,50 +48,71 @@ class PdfTemplate implements IPdfWrapper { _helper = PdfTemplateHelper(this); _helper.content = PdfStream(); _setSize(width, height); - _helper.content[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.xObject); - _helper.content[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.form); + _helper.content[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.xObject, + ); + _helper.content[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.form, + ); } PdfTemplate._fromRect(Rect bounds) { _helper = PdfTemplateHelper(this); _helper.content = PdfStream(); _setBounds(bounds); - _helper.content[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.xObject); - _helper.content[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.form); + _helper.content[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.xObject, + ); + _helper.content[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.form, + ); } PdfTemplate._fromPdfStream(PdfStream template) { _helper = PdfTemplateHelper(this); _helper.content = template; - final IPdfPrimitive obj = PdfCrossTable.dereference( - _helper.content[PdfDictionaryProperties.bBox])!; + final IPdfPrimitive obj = + PdfCrossTable.dereference( + _helper.content[PdfDictionaryProperties.bBox], + )!; final PdfRectangle rect = (obj as PdfArray).toRectangle(); _size = rect.size; _helper.isReadonly = true; } - PdfTemplate._(Offset origin, Size size, List stream, - PdfDictionary resources, bool isLoadedPage) - : super() { + PdfTemplate._( + Offset origin, + Size size, + List stream, + PdfDictionary resources, + bool isLoadedPage, + PdfPageHelper page, + ) : super() { _helper = PdfTemplateHelper(this); if (size == Size.zero) { throw ArgumentError.value( - size, 'size', 'The size of the new PdfTemplate cannot be empty.'); + size, + 'size', + 'The size of the new PdfTemplate cannot be empty.', + ); } _helper.content = PdfStream(); - if (origin.dx < 0 || origin.dy < 0) { - _setSize(size.width, size.height, origin); + if (page.cropBox.left > 0 && page.cropBox.top > 0) { + _setBounds(page.cropBox); + _helper._origin = Offset(page.cropBox.left, page.cropBox.top); + _setSize(page.cropBox.right, page.cropBox.bottom, _helper._origin); } else { - _setSize(size.width, size.height); + if (origin.dx < 0 || origin.dy < 0) { + _setSize(size.width, size.height, origin); + } else { + _setSize(size.width, size.height); + } } _initialize(); _helper.content.dataStream!.addAll(stream); - _helper.content[PdfDictionaryProperties.resources] = - PdfDictionary(resources); + _helper.content[PdfDictionaryProperties.resources] = PdfDictionary( + resources, + ); _helper._resources = PdfResources(resources); _helper.isLoadedPageTemplate = isLoadedPage; _helper.isReadonly = true; @@ -117,7 +139,7 @@ class PdfTemplate implements IPdfWrapper { /// //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(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -139,7 +161,7 @@ class PdfTemplate implements IPdfWrapper { /// //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(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -177,7 +199,7 @@ class PdfTemplate implements IPdfWrapper { /// //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(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -207,14 +229,19 @@ class PdfTemplate implements IPdfWrapper { void _setSize(double width, double height, [Offset? origin]) { if (origin != null) { - final PdfArray array = - PdfArray([origin.dx, origin.dy, width, height]); + final PdfArray array = PdfArray([ + origin.dx, + origin.dy, + width, + height, + ]); _helper.content[PdfDictionaryProperties.bBox] = array; _size = PdfSize(width, height); } else { final PdfRectangle rectangle = PdfRectangle(0, 0, width, height); - _helper.content[PdfDictionaryProperties.bBox] = - PdfArray.fromRectangle(rectangle); + _helper.content[PdfDictionaryProperties.bBox] = PdfArray.fromRectangle( + rectangle, + ); _size = PdfSize(width, height); } } @@ -227,10 +254,12 @@ class PdfTemplate implements IPdfWrapper { } void _initialize() { - _helper.content[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.xObject); - _helper.content[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.form); + _helper.content[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.xObject, + ); + _helper.content[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.form, + ); } } @@ -254,6 +283,7 @@ class PdfTemplateHelper { /// internal field bool isLoadedPageTemplate = false; PdfResources? _resources; + Offset _origin = Offset.zero; /// internal method static PdfTemplateHelper getHelper(PdfTemplate template) { @@ -269,6 +299,8 @@ class PdfTemplateHelper { } } + Offset get origin => _origin; + /// internal method static PdfTemplate fromRect(Rect bounds) { return PdfTemplate._fromRect(bounds); @@ -280,9 +312,15 @@ class PdfTemplateHelper { } /// internal method - static PdfTemplate internal(Offset origin, Size size, List stream, - PdfDictionary resources, bool isLoadedPage) { - return PdfTemplate._(origin, size, stream, resources, isLoadedPage); + static PdfTemplate internal( + Offset origin, + Size size, + List stream, + PdfDictionary resources, + bool isLoadedPage, + PdfPageHelper page, + ) { + return PdfTemplate._(origin, size, stream, resources, isLoadedPage, page); } /// internal method 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 398ac1669..25f7c3836 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 @@ -16,12 +16,13 @@ import 'base/text_layouter.dart'; class PdfTextElement extends PdfLayoutElement { //Constructors /// Initializes a new instance of the [PdfTextElement] class. - PdfTextElement( - {String text = '', - PdfFont? font, - PdfPen? pen, - PdfBrush? brush, - PdfStringFormat? format}) { + PdfTextElement({ + String text = '', + PdfFont? font, + PdfPen? pen, + PdfBrush? brush, + PdfStringFormat? format, + }) { _helper = PdfTextElementHelper(this); _initialize(text, font, pen, brush, format); } @@ -48,8 +49,13 @@ class PdfTextElement extends PdfLayoutElement { PdfStringFormat? stringFormat; //Implementation - void _initialize(String text, PdfFont? font, PdfPen? pen, PdfBrush? brush, - PdfStringFormat? format) { + void _initialize( + String text, + PdfFont? font, + PdfPen? pen, + PdfBrush? brush, + PdfStringFormat? format, + ) { this.text = text; if (font != null) { this.font = font; @@ -101,10 +107,13 @@ class PdfTextElementHelper { /// internal method void drawInternal(PdfGraphics graphics, PdfRectangle bounds) { - graphics.drawString(base.text, base.font, - pen: base.pen, - brush: obtainBrush(), - bounds: bounds.rect, - format: base.stringFormat); + graphics.drawString( + base.text, + base.font, + pen: base.pen, + brush: obtainBrush(), + bounds: bounds.rect, + format: base.stringFormat, + ); } } 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 52d59d204..3ef4c90fe 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 @@ -6,7 +6,7 @@ /// ..pages.add().graphics.drawString('Hello World!', /// PdfStandardFont(PdfFontFamily.helvetica, 12, style: PdfFontStyle.bold)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -24,7 +24,7 @@ enum PdfFontStyle { underline, ///Represents Strikeout text - strikethrough + strikethrough, } /// Indicates type of standard PDF fonts. @@ -36,7 +36,7 @@ enum PdfFontStyle { /// 'Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -68,7 +68,7 @@ enum PdfFontFamily { /// alignment: PdfTextAlignment.left, /// subSuperscript: PdfSubSuperscript.subscript)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -80,7 +80,7 @@ enum PdfSubSuperscript { superscript, /// Specifies subscript format. - subscript + subscript, } /// Specifies the type of CJK font. @@ -92,7 +92,7 @@ enum PdfSubSuperscript { /// 'こんにちは世界', PdfCjkStandardFont(PdfCjkFontFamily.heiseiMinchoW3, 20), /// brush: PdfBrushes.black); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -116,7 +116,7 @@ enum PdfCjkFontFamily { monotypeSungLight, /// Represents the sinotype song light font. - sinoTypeSongLight + sinoTypeSongLight, } /// Specifies the types of text wrapping. @@ -129,7 +129,7 @@ enum PdfCjkFontFamily { /// format: PdfStringFormat( /// alignment: PdfTextAlignment.left, wordWrap: PdfWordWrapType.word)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -148,7 +148,7 @@ enum PdfWordWrapType { /// Text is wrapped by characters. In this case the word /// at the end of the text line can be split. - character + character, } /// Break type of the line. @@ -166,5 +166,5 @@ enum LineType { firstParagraphLine, /// The line is the last in the paragraph. - lastParagraphLine + lastParagraphLine, } 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 f4001ec8e..615b0a69b 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 @@ -12,18 +12,27 @@ import 'pdf_font_metrics.dart'; /// internal class 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[PdfDictionaryProperties.type] = PdfName(PdfDictionaryProperties.font); - this[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.cidFontType2); - this[PdfDictionaryProperties.baseFont] = - PdfName(fontMetrics.postScriptName); - this[PdfDictionaryProperties.dw] = - PdfNumber((fontMetrics.widthTable! as CjkWidthTable).defaultWidth); + this[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.cidFontType2, + ); + this[PdfDictionaryProperties.baseFont] = PdfName( + fontMetrics.postScriptName, + ); + this[PdfDictionaryProperties.dw] = PdfNumber( + (fontMetrics.widthTable! as CjkWidthTable).defaultWidth, + ); this[PdfDictionaryProperties.w] = fontMetrics.widthTable!.toArray(); - this[PdfDictionaryProperties.fontDescriptor] = - _getFontDescryptor(fontFamily, fontStyle, fontMetrics); + this[PdfDictionaryProperties.fontDescriptor] = _getFontDescryptor( + fontFamily, + fontStyle, + fontMetrics, + ); this[PdfDictionaryProperties.cidSystemInfo] = _getSystemInfo(fontFamily); } @@ -51,6 +60,7 @@ class PdfCidFont extends PdfDictionary { sysInfo[PdfDictionaryProperties.ordering] = PdfString('GB1'); sysInfo[PdfDictionaryProperties.supplement] = PdfNumber(2); break; + // ignore: no_default_cases default: break; } @@ -58,21 +68,34 @@ class PdfCidFont extends PdfDictionary { } /// internal method - PdfDictionary _getFontDescryptor(PdfCjkFontFamily? fontFamily, int? fontStyle, - PdfFontMetrics fontMetrics) { + PdfDictionary _getFontDescryptor( + PdfCjkFontFamily? fontFamily, + int? fontStyle, + PdfFontMetrics fontMetrics, + ) { final PdfDictionary fontDescryptor = PdfDictionary(); switch (fontFamily) { case PdfCjkFontFamily.hanyangSystemsGothicMedium: _fillHanyangSystemsGothicMedium( - fontDescryptor, fontFamily, fontMetrics); + fontDescryptor, + fontFamily, + fontMetrics, + ); break; case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: _fillHanyangSystemsShinMyeongJoMedium( - fontDescryptor, fontFamily, fontMetrics); + fontDescryptor, + fontFamily, + fontMetrics, + ); break; case PdfCjkFontFamily.heiseiKakuGothicW5: _fillHanyangSystemsGothicMediumWithStyle( - fontDescryptor, fontStyle!, fontFamily, fontMetrics); + fontDescryptor, + fontStyle!, + fontFamily, + fontMetrics, + ); break; case PdfCjkFontFamily.heiseiMinchoW3: _fillHeiseiMinchoW3(fontDescryptor, fontFamily, fontMetrics); @@ -86,6 +109,7 @@ class PdfCidFont extends PdfDictionary { case PdfCjkFontFamily.sinoTypeSongLight: _fillSinoTypeSongLight(fontDescryptor, fontFamily, fontMetrics); break; + // ignore: no_default_cases default: break; } @@ -93,8 +117,11 @@ class PdfCidFont extends PdfDictionary { } /// Fills the monotype sung light font descryptor. - void _fillMonotypeSungLight(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillMonotypeSungLight( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(-160, -249, 1175, 1137); _fillFontBBox(fontDescryptor, fontBBox); _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); @@ -110,8 +137,11 @@ class PdfCidFont extends PdfDictionary { } /// Fills the hanyang systems shin myeong jo medium font descryptor. - void _fillHanyangSystemsShinMyeongJoMedium(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillHanyangSystemsShinMyeongJoMedium( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(0, -148, 1001, 1028); _fillFontBBox(fontDescryptor, fontBBox); _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); @@ -127,8 +157,11 @@ class PdfCidFont extends PdfDictionary { } /// Fills the heisei mincho w3 font descryptor. - void _fillHeiseiMinchoW3(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillHeiseiMinchoW3( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(-123, -257, 1124, 1167); _fillFontBBox(fontDescryptor, fontBBox); _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); @@ -144,8 +177,11 @@ class PdfCidFont extends PdfDictionary { } /// Fills the sino type song light font descryptor. - void _fillSinoTypeSongLight(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillSinoTypeSongLight( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(-25, -254, 1025, 1134); _fillFontBBox(fontDescryptor, fontBBox); _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); @@ -161,8 +197,11 @@ class PdfCidFont extends PdfDictionary { } /// Fills the monotype hei medium font descryptor. - void _fillMonotypeHeiMedium(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillMonotypeHeiMedium( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(-45, -250, 1060, 1137); _fillFontBBox(fontDescryptor, fontBBox); _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); @@ -178,8 +217,11 @@ class PdfCidFont extends PdfDictionary { } /// Fills the hanyang systems gothic medium font descryptor. - void _fillHanyangSystemsGothicMedium(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillHanyangSystemsGothicMedium( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(-6, -145, 1009, 1025); _fillFontBBox(fontDescryptor, fontBBox); _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); @@ -196,8 +238,12 @@ class PdfCidFont extends PdfDictionary { } /// Fills the heisei kaku gothic w5 font descryptor. - void _fillHanyangSystemsGothicMediumWithStyle(PdfDictionary fontDescryptor, - int fontStyle, PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { + void _fillHanyangSystemsGothicMediumWithStyle( + PdfDictionary fontDescryptor, + int fontStyle, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { final PdfRectangle fontBBox = PdfRectangle(-92, -250, 1102, 1175); final PdfRectangle fontBBoxI = PdfRectangle(-92, -250, 1102, 1932); if ((fontStyle & @@ -221,19 +267,27 @@ class PdfCidFont extends PdfDictionary { } /// Fills the known info. - void _fillKnownInfo(PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, PdfFontMetrics fontMetrics) { - fontDescryptor[PdfDictionaryProperties.fontName] = - PdfName(fontMetrics.postScriptName); - fontDescryptor[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.fontDescriptor); + void _fillKnownInfo( + PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, + PdfFontMetrics fontMetrics, + ) { + fontDescryptor[PdfDictionaryProperties.fontName] = PdfName( + fontMetrics.postScriptName, + ); + fontDescryptor[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.fontDescriptor, + ); fontDescryptor[PdfDictionaryProperties.italicAngle] = PdfNumber(0); - fontDescryptor[PdfDictionaryProperties.missingWidth] = - PdfNumber((fontMetrics.widthTable! as CjkWidthTable).defaultWidth); - fontDescryptor[PdfDictionaryProperties.ascent] = - PdfNumber(fontMetrics.ascent); - fontDescryptor[PdfDictionaryProperties.descent] = - PdfNumber(fontMetrics.descent); + fontDescryptor[PdfDictionaryProperties.missingWidth] = PdfNumber( + (fontMetrics.widthTable! as CjkWidthTable).defaultWidth, + ); + fontDescryptor[PdfDictionaryProperties.ascent] = PdfNumber( + fontMetrics.ascent, + ); + fontDescryptor[PdfDictionaryProperties.descent] = PdfNumber( + fontMetrics.descent, + ); _fillFlags(fontDescryptor, fontFamily); } @@ -251,6 +305,7 @@ class PdfCidFont extends PdfDictionary { case PdfCjkFontFamily.heiseiMinchoW3: fontDescryptor[PdfDictionaryProperties.flags] = PdfNumber(6); break; + // ignore: no_default_cases default: break; } @@ -258,7 +313,8 @@ class PdfCidFont extends PdfDictionary { /// Fills the font BBox. void _fillFontBBox(PdfDictionary fontDescryptor, PdfRectangle fontBBox) { - fontDescryptor[PdfDictionaryProperties.fontBBox] = - PdfArray.fromRectangle(fontBBox); + fontDescryptor[PdfDictionaryProperties.fontBBox] = PdfArray.fromRectangle( + fontBBox, + ); } } 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 ab32fd5ba..527070d5e 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 @@ -20,7 +20,7 @@ import 'pdf_string_format.dart'; /// PdfCjkFontFamily.heiseiMinchoW3, 20), /// brush: PdfBrushes.black); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -38,15 +38,20 @@ class PdfCjkStandardFont extends PdfFont { /// PdfCjkFontFamily.heiseiMinchoW3, 20), /// brush: PdfBrushes.black); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` - PdfCjkStandardFont(PdfCjkFontFamily fontFamily, double size, - {PdfFontStyle? style, List? multiStyle}) { + PdfCjkStandardFont( + PdfCjkFontFamily fontFamily, + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { _helper = PdfCjkStandardFontHelper(this); - PdfFontHelper.getHelper(this) - .initialize(size, style: style, multiStyle: multiStyle); + PdfFontHelper.getHelper( + this, + ).initialize(size, style: style, multiStyle: multiStyle); _fontFamily = fontFamily; _initializeInternals(); } @@ -64,15 +69,20 @@ class PdfCjkStandardFont extends PdfFont { /// PdfCjkStandardFont(PdfCjkFontFamily.heiseiMinchoW3, 12), 12), /// brush: PdfBrushes.black); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` - PdfCjkStandardFont.protoType(PdfCjkStandardFont prototype, double size, - {PdfFontStyle? style, List? multiStyle}) { + PdfCjkStandardFont.protoType( + PdfCjkStandardFont prototype, + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { _helper = PdfCjkStandardFontHelper(this); - PdfFontHelper.getHelper(this) - .initialize(size, style: style, multiStyle: multiStyle); + PdfFontHelper.getHelper( + this, + ).initialize(size, style: style, multiStyle: multiStyle); _fontFamily = prototype.fontFamily; if (style == null && (multiStyle == null || multiStyle.isEmpty)) { PdfFontHelper.getHelper(this).setStyle(prototype.style, null); @@ -100,16 +110,20 @@ class PdfCjkStandardFont extends PdfFont { /// '"The CJK font family name is ${font.fontFamily}', font, /// brush: PdfBrushes.black); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` PdfCjkFontFamily get fontFamily => _fontFamily; void _initializeInternals() { - PdfFontHelper.getHelper(this).metrics = - PdfCjkStandardFontMetricsFactory.getMetrics( - _fontFamily, PdfFontHelper.getHelper(this).fontStyle, size); + PdfFontHelper.getHelper( + this, + ).metrics = PdfCjkStandardFontMetricsFactory.getMetrics( + _fontFamily, + PdfFontHelper.getHelper(this).fontStyle, + size, + ); PdfFontHelper.getHelper(this).fontInternals = _createInternals(); } @@ -117,12 +131,15 @@ class PdfCjkStandardFont extends PdfFont { PdfDictionary _createInternals() { final PdfDictionary dictionary = PdfDictionary(); - dictionary[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.font); - dictionary[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.type0); - dictionary[PdfDictionaryProperties.baseFont] = - PdfName(PdfFontHelper.getHelper(this).metrics!.postScriptName); + dictionary[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.font, + ); + dictionary[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.type0, + ); + dictionary[PdfDictionaryProperties.baseFont] = PdfName( + PdfFontHelper.getHelper(this).metrics!.postScriptName, + ); dictionary[PdfDictionaryProperties.encoding] = _getEncoding(_fontFamily); dictionary[PdfDictionaryProperties.descendantFonts] = _getDescendantFont(); @@ -150,6 +167,7 @@ class PdfCjkStandardFont extends PdfFont { case PdfCjkFontFamily.sinoTypeSongLight: encoding = 'UniGB-UCS2-H'; break; + // ignore: no_default_cases default: break; } @@ -161,9 +179,10 @@ class PdfCjkStandardFont extends PdfFont { PdfArray _getDescendantFont() { final PdfArray df = PdfArray(); final PdfCidFont cidFont = PdfCidFont( - _fontFamily, - PdfFontHelper.getHelper(this).fontStyle, - PdfFontHelper.getHelper(this).metrics!); + _fontFamily, + PdfFontHelper.getHelper(this).fontStyle, + PdfFontHelper.getHelper(this).metrics!, + ); df.add(cidFont); return df; } 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 2cfbbce07..2e679d630 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 @@ -13,18 +13,28 @@ 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; - + final PdfCjkStandardFontMetricsFactory fontfactory = + PdfCjkStandardFontMetricsFactory(); switch (fontFamily) { case PdfCjkFontFamily.hanyangSystemsGothicMedium: - metrics = - _getHanyangSystemsGothicMediumMetrix(fontFamily, fontStyle!, size); + metrics = _getHanyangSystemsGothicMediumMetrix( + fontFamily, + fontStyle!, + size, + ); break; case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: metrics = _getHanyangSystemsShinMyeongJoMediumMetrix( - fontFamily, fontStyle!, size); + fontFamily, + fontStyle!, + size, + ); break; case PdfCjkFontFamily.heiseiKakuGothicW5: @@ -44,9 +54,14 @@ class PdfCjkStandardFontMetricsFactory { break; case PdfCjkFontFamily.sinoTypeSongLight: - metrics = _getSinoTypeSongLight(fontFamily, fontStyle!, size); + metrics = fontfactory._getSinoTypeSongLight( + fontFamily, + fontStyle!, + size, + ); break; + // ignore: no_default_cases default: throw Exception('Unsupported font family, $fontFamily'); } @@ -60,7 +75,10 @@ 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; @@ -91,7 +109,10 @@ 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; @@ -122,7 +143,10 @@ 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; @@ -152,8 +176,11 @@ class PdfCjkStandardFontMetricsFactory { } /// Gets the sino type song light font metrics. - static PdfFontMetrics _getSinoTypeSongLight( - PdfCjkFontFamily? fontFamily, int fontStyle, double size) { + PdfFontMetrics _getSinoTypeSongLight( + PdfCjkFontFamily? fontFamily, + int fontStyle, + double size, + ) { final PdfFontMetrics metrics = PdfFontMetrics(); final CjkWidthTable widthTable = CjkWidthTable(1000); metrics.widthTable = widthTable; @@ -186,7 +213,10 @@ 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; @@ -217,7 +247,10 @@ 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; @@ -248,7 +281,10 @@ 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 100bd6ac3..0ef21dca3 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 @@ -21,7 +21,7 @@ import 'string_tokenizer.dart'; /// PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -41,7 +41,7 @@ abstract class PdfFont implements IPdfWrapper { /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', /// font); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -59,7 +59,7 @@ abstract class PdfFont implements IPdfWrapper { /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', /// font); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -77,7 +77,7 @@ abstract class PdfFont implements IPdfWrapper { /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', /// font); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -95,7 +95,7 @@ abstract class PdfFont implements IPdfWrapper { /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', /// font); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -121,15 +121,20 @@ abstract class PdfFont implements IPdfWrapper { /// brush: PdfBrushes.black, /// bounds: Rect.fromLTWH(0, 0, size.width, size.height)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` Size measureString(String text, {Size? layoutArea, PdfStringFormat? format}) { layoutArea ??= Size.zero; final PdfStringLayouter layouter = PdfStringLayouter(); - final PdfStringLayoutResult result = layouter.layout(text, this, format, - width: layoutArea.width, height: layoutArea.height); + final PdfStringLayoutResult result = layouter.layout( + text, + this, + format, + width: layoutArea.width, + height: layoutArea.height, + ); return result.size.size; } @@ -140,15 +145,20 @@ abstract class PdfFont implements IPdfWrapper { /// 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) { realWidth += (line.length - 1) * format.characterSpacing; } if (format.wordSpacing != 0) { - final int whitespacesCount = - StringTokenizer.getCharacterCount(line, StringTokenizer.spaces); + final int whitespacesCount = StringTokenizer.getCharacterCount( + line, + StringTokenizer.spaces, + ); realWidth += whitespacesCount * format.wordSpacing; } } @@ -173,7 +183,7 @@ class PdfFontHelper { 'courier', 'TimesRoman', 'Symbol', - 'ZapfDingbats' + 'ZapfDingbats', ]; /// internal field @@ -184,7 +194,7 @@ class PdfFontHelper { 'HeiseiMinchoW3', 'MonotypeHeiMedium', 'MonotypeSungLight', - 'SinoTypeSongLight' + 'SinoTypeSongLight', ]; /// internal field @@ -202,6 +212,12 @@ class PdfFontHelper { /// internal field PdfFontStyle style = PdfFontStyle.regular; + /// internal field + bool isBold = false; + + /// internal field + bool isItalic = false; + /// internal field //ignore:unused_element bool get isUnderline => @@ -214,8 +230,11 @@ class PdfFontHelper { /// Initializes a new instance of the [PdfFont] class /// with font size and style. - void initialize(double size, - {PdfFontStyle? style, List? multiStyle}) { + void initialize( + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { setSize(size); setStyle(style, multiStyle); } @@ -233,10 +252,20 @@ class PdfFontHelper { if (style != null) { this.style = style; fontStyle = getPdfFontStyle(style); + if (style == PdfFontStyle.bold) { + isBold = true; + } else if (style == PdfFontStyle.italic) { + isItalic = true; + } } if (multiStyle != null && multiStyle.isNotEmpty) { for (int i = 0; i < multiStyle.length; i++) { fontStyle = fontStyle | getPdfFontStyle(multiStyle[i]); + if (multiStyle[i] == PdfFontStyle.bold) { + isBold = true; + } else if (multiStyle[i] == PdfFontStyle.italic) { + isItalic = true; + } } } else if (style == null) { this.style = PdfFontStyle.regular; @@ -255,17 +284,21 @@ class PdfFontHelper { return 4; case PdfFontStyle.strikethrough: return 8; - default: + case PdfFontStyle.regular: return 0; } } /// internal method static double getLineWidth( - PdfFont font, String line, PdfStringFormat? format) { + PdfFont font, + String line, + PdfStringFormat? format, + ) { if (font is PdfCjkStandardFont) { - return PdfCjkStandardFontHelper.getHelper(font) - .getLineWidth(line, format); + return PdfCjkStandardFontHelper.getHelper( + font, + ).getLineWidth(line, format); } else if (font is PdfStandardFont) { return PdfStandardFontHelper.getHelper(font).getLineWidth(line, format); } else if (font is PdfTrueTypeFont) { @@ -276,7 +309,11 @@ class PdfFontHelper { /// internal method static double applyFormatSettings( - PdfFont font, String line, PdfStringFormat? format, double width) { + PdfFont font, + String line, + PdfStringFormat? format, + double width, + ) { return font._applyFormatSettings(line, format, width); } } 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 c06b37d24..9af8c2c3e 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 @@ -49,20 +49,21 @@ class PdfFontMetrics { //Implementation /// Calculates size of the font depending on the subscript/superscript value. double? getSize([PdfStringFormat? format]) { - double? _size = size; + double? sizeValue = size; if (format != null) { switch (format.subSuperscript) { case PdfSubSuperscript.subscript: - _size = _size / 1.5; + sizeValue = sizeValue / 1.5; break; case PdfSubSuperscript.superscript: - _size = _size / 1.5; + sizeValue = sizeValue / 1.5; break; + // ignore: no_default_cases default: break; } } - return _size; + return sizeValue; } /// Returns height taking into consideration font's size. @@ -112,7 +113,9 @@ class StandardWidthTable extends WidthTable { int? _returnValue(int index) { if (index < 0 || index >= _widths!.length) { throw ArgumentError.value( - index, 'The character is not supported by the font.'); + index, + 'The character is not supported by the font.', + ); } return _widths![index]; } @@ -128,11 +131,11 @@ class StandardWidthTable extends WidthTable { class CjkWidthTable extends WidthTable { /// Initializes a new instance of the [CjkWidthTable] class. CjkWidthTable(this.defaultWidth) { - width = <_CjkWidth>[]; + width = []; } /// Local variable to store the width. - late List<_CjkWidth> width; + late List width; /// Local variable to store the default width. int defaultWidth; @@ -140,7 +143,7 @@ class CjkWidthTable extends WidthTable { @override int operator [](int index) { int newWidth = defaultWidth; - for (final _CjkWidth widths in width) { + for (final CjkWidth widths in width) { if (index >= widths.from && index <= widths.to) { newWidth = widths[index]; } @@ -149,14 +152,14 @@ class CjkWidthTable extends WidthTable { } /// internal method - void add(_CjkWidth widths) { + void add(CjkWidth widths) { width.add(widths); } @override PdfArray toArray() { final PdfArray arr = PdfArray(); - for (final _CjkWidth widths in width) { + for (final CjkWidth widths in width) { widths.appendToArray(arr); } return arr; @@ -164,7 +167,7 @@ class CjkWidthTable extends WidthTable { } /// The base class of CJK widths types. -abstract class _CjkWidth { +abstract class CjkWidth { /// Gets the starting character. int get from; @@ -179,7 +182,7 @@ abstract class _CjkWidth { } /// Implements capabilities to control a range of character with the same width. -class CjkSameWidth extends _CjkWidth { +class CjkSameWidth extends CjkWidth { /// internal constructor CjkSameWidth(this.from, this.to, this.width) { if (from > to) { @@ -216,7 +219,7 @@ class CjkSameWidth extends _CjkWidth { /// Implements capabilities to control a sequent range of characters /// with different width. -class CjkDifferentWidth extends _CjkWidth { +class CjkDifferentWidth extends CjkWidth { /// internal constructor CjkDifferentWidth(this.from, this.width); 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 23e81ae2b..345a92bb2 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 @@ -18,7 +18,7 @@ import 'pdf_string_format.dart'; /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -34,15 +34,20 @@ class PdfStandardFont extends PdfFont { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` - PdfStandardFont(PdfFontFamily fontFamily, double size, - {PdfFontStyle? style, List? multiStyle}) { + PdfStandardFont( + PdfFontFamily fontFamily, + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { _helper = PdfStandardFontHelper(this); - PdfFontHelper.getHelper(this) - .initialize(size, style: style, multiStyle: multiStyle); + PdfFontHelper.getHelper( + this, + ).initialize(size, style: style, multiStyle: multiStyle); _fontFamily = fontFamily; _checkStyle(); _initializeInternals(); @@ -62,15 +67,20 @@ class PdfStandardFont extends PdfFont { /// 'The font family name is ${font.fontFamily}', font, /// brush: PdfBrushes.black); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` - PdfStandardFont.prototype(PdfStandardFont prototype, double size, - {PdfFontStyle? style, List? multiStyle}) { + PdfStandardFont.prototype( + PdfStandardFont prototype, + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { _helper = PdfStandardFontHelper(this); - PdfFontHelper.getHelper(this) - .initialize(size, style: style, multiStyle: multiStyle); + PdfFontHelper.getHelper( + this, + ).initialize(size, style: style, multiStyle: multiStyle); _fontFamily = prototype.fontFamily; if (style == null && (multiStyle == null || multiStyle.isEmpty)) { PdfFontHelper.getHelper(this).setStyle(prototype.style, null); @@ -101,7 +111,7 @@ class PdfStandardFont extends PdfFont { /// 'The font family name is ${font.fontFamily}', font, /// brush: PdfBrushes.black); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -114,29 +124,36 @@ class PdfStandardFont extends PdfFont { fontFamily == PdfFontFamily.zapfDingbats) { PdfFontHelper.getHelper(this).fontStyle = PdfFontHelper.getHelper(this).fontStyle & - ~(PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) | - PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic)); + ~(PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) | + PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic)); PdfFontHelper.getHelper(this).style = PdfFontStyle.regular; } } /// Initializes font internals. void _initializeInternals() { - PdfFontHelper.getHelper(this).metrics = - PdfStandardFontMetricsFactory.getMetrics( - _fontFamily, PdfFontHelper.getHelper(this).fontStyle, size); + PdfFontHelper.getHelper( + this, + ).metrics = PdfStandardFontMetricsFactory.getMetrics( + _fontFamily, + PdfFontHelper.getHelper(this).fontStyle, + size, + ); PdfFontHelper.getHelper(this).fontInternals = _createInternals(); } /// Creates font's dictionary. PdfDictionary _createInternals() { final PdfDictionary dictionary = PdfDictionary(); - dictionary[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.font); - dictionary[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.type1); - dictionary[PdfDictionaryProperties.baseFont] = - PdfName(PdfFontHelper.getHelper(this).metrics!.postScriptName); + dictionary[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.font, + ); + dictionary[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.type1, + ); + dictionary[PdfDictionaryProperties.baseFont] = PdfName( + PdfFontHelper.getHelper(this).metrics!.postScriptName, + ); if (fontFamily != PdfFontFamily.symbol && fontFamily != PdfFontFamily.zapfDingbats) { dictionary[PdfDictionaryProperties.encoding] = PdfName('WinAnsiEncoding'); @@ -410,7 +427,7 @@ class PdfStandardFontHelper { 'ü', 'ý', 'þ', - 'ÿ' + 'ÿ', ]; return _windows1252MapTable; } 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 c158bf078..e4bccec6c 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 @@ -23,7 +23,7 @@ class PdfStandardFontMetricsFactory { static const double _helveticaBoldAscent = 962; /// Ascender value for the font. - static const double _helveticaBoldDescent = -228; + final double _helveticaBoldDescent = -228; /// Font type static const String _helveticaBoldName = 'Helvetica-Bold'; @@ -138,7 +138,10 @@ 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: @@ -156,9 +159,13 @@ class PdfStandardFontMetricsFactory { case PdfFontFamily.zapfDingbats: metrics = _getZapfDingbatsMetrics(fontFamily, size); break; + // ignore: no_default_cases default: - metrics = - _getHelveticaMetrics(PdfFontFamily.helvetica, fontStyle!, size); + metrics = _getHelveticaMetrics( + PdfFontFamily.helvetica, + fontStyle!, + size, + ); break; } metrics.name = PdfFontHelper.standardFontNames[fontFamily!.index]; @@ -169,25 +176,32 @@ 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(); + final PdfStandardFontMetricsFactory standardFontMetricsFactory = + PdfStandardFontMetricsFactory(); if (fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) > 0 && fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic) > 0) { metrics.ascent = _helveticaBoldItalicAscent; metrics.descent = _helveticaBoldItalicDescent; metrics.postScriptName = _helveticaBoldItalicName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._arialBoldWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._arialBoldWidth, + ); metrics.height = metrics.ascent - metrics.descent; } else if (fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) > 0) { metrics.ascent = _helveticaBoldAscent; - metrics.descent = _helveticaBoldDescent; + metrics.descent = standardFontMetricsFactory._helveticaBoldDescent; metrics.postScriptName = _helveticaBoldName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._arialBoldWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._arialBoldWidth, + ); metrics.height = metrics.ascent - metrics.descent; } else if (fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -210,7 +224,10 @@ 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 & PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) > 0 && fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -249,7 +266,10 @@ 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 & PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) > 0 && fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -257,8 +277,9 @@ class PdfStandardFontMetricsFactory { metrics.descent = _timesBoldItalicDescent; metrics.postScriptName = _timesBoldItalicName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._timesRomanBoldItalicWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._timesRomanBoldItalicWidth, + ); metrics.height = metrics.ascent - metrics.descent; } else if (fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.bold) > 0) { @@ -266,8 +287,9 @@ class PdfStandardFontMetricsFactory { metrics.descent = _timesBoldDescent; metrics.postScriptName = _timesBoldName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._timesRomanBoldWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._timesRomanBoldWidth, + ); metrics.height = metrics.ascent - metrics.descent; } else if (fontStyle & PdfFontHelper.getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -275,16 +297,18 @@ class PdfStandardFontMetricsFactory { metrics.descent = _timesItalicDescent; metrics.postScriptName = _timesItalicName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._timesRomanItalicWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._timesRomanItalicWidth, + ); metrics.height = metrics.ascent - metrics.descent; } else { metrics.ascent = _timesAscent; metrics.descent = _timesDescent; metrics.postScriptName = _timesName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._timesRomanWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._timesRomanWidth, + ); metrics.height = metrics.ascent - metrics.descent; } return metrics; @@ -292,7 +316,9 @@ class PdfStandardFontMetricsFactory { /// Creates Symbol font metrics. static PdfFontMetrics _getSymbolMetrics( - PdfFontFamily? fontFamily, double size) { + PdfFontFamily? fontFamily, + double size, + ) { final PdfFontMetrics metrics = PdfFontMetrics(); metrics.ascent = _symbolAscent; @@ -306,14 +332,17 @@ 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; metrics.postScriptName = _zapfDingbatsName; metrics.size = size; - metrics.widthTable = - StandardWidthTable(_StandardFontWidth._zapfDingbatsWidth); + metrics.widthTable = StandardWidthTable( + _StandardFontWidth._zapfDingbatsWidth, + ); metrics.height = metrics.ascent - metrics.descent; return metrics; } @@ -544,7 +573,7 @@ class _StandardFontWidth { 556, 500, 556, - 500 + 500, ]; static const List _arialBoldWidth = [ 278, @@ -770,7 +799,7 @@ class _StandardFontWidth { 611, 556, 611, - 556 + 556, ]; static const List _fixedWidth = [ 600, @@ -996,7 +1025,7 @@ class _StandardFontWidth { 600, 600, 600, - 600 + 600, ]; static const List _timesRomanWidth = [ 250, @@ -1222,7 +1251,7 @@ class _StandardFontWidth { 500, 500, 500, - 500 + 500, ]; static const List _timesRomanBoldWidth = [ 250, @@ -1448,7 +1477,7 @@ class _StandardFontWidth { 556, 500, 556, - 500 + 500, ]; static const List _timesRomanItalicWidth = [ 250, @@ -1674,7 +1703,7 @@ class _StandardFontWidth { 500, 444, 500, - 444 + 444, ]; static const List _timesRomanBoldItalicWidth = [ 250, @@ -1900,7 +1929,7 @@ class _StandardFontWidth { 556, 444, 500, - 444 + 444, ]; static const List _symbolWidth = [ 250, @@ -2092,7 +2121,7 @@ class _StandardFontWidth { 494, 494, 494, - -1 + -1, ]; static const List _zapfDingbatsWidth = [ 278, @@ -2296,6 +2325,6 @@ class _StandardFontWidth { 873, 927, 970, - 918 + 918, ]; } 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 e8d0175a6..8a729da0f 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 @@ -21,7 +21,7 @@ import 'enums.dart'; /// measureTrailingSpaces: true, /// wordWrap: PdfWordWrapType.word)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -48,33 +48,35 @@ class PdfStringFormat { /// measureTrailingSpaces: true, /// wordWrap: PdfWordWrapType.word)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` - PdfStringFormat( - {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}) { + PdfStringFormat({ + 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, + }) { _helper = PdfStringFormatHelper(); _initialize( - alignment, - lineAlignment, - textDirection, - characterSpacing, - wordSpacing, - lineSpacing, - subSuperscript, - paragraphIndent, - measureTrailingSpaces, - wordWrap); + alignment, + lineAlignment, + textDirection, + characterSpacing, + wordSpacing, + lineSpacing, + subSuperscript, + paragraphIndent, + measureTrailingSpaces, + wordWrap, + ); } //Fields @@ -89,7 +91,7 @@ class PdfStringFormat { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(alignment: PdfTextAlignment.left)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -104,7 +106,7 @@ class PdfStringFormat { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.top)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -119,7 +121,7 @@ class PdfStringFormat { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(textDirection: PdfTextDirection.rightToLeft)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -137,7 +139,7 @@ class PdfStringFormat { /// .drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(characterSpacing: 0.5)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -155,7 +157,7 @@ class PdfStringFormat { /// .drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(wordSpacing: 0.5)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -173,7 +175,7 @@ class PdfStringFormat { /// .drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(lineSpacing: 0.5)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -194,7 +196,7 @@ class PdfStringFormat { /// //Set the clip path. /// ..clipPath = true); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -214,7 +216,7 @@ class PdfStringFormat { /// characterSpacing: 1, /// subSuperscript: PdfSubSuperscript.subscript)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -244,7 +246,7 @@ class PdfStringFormat { /// //Set line limit. /// ..lineLimit = true); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -260,7 +262,7 @@ class PdfStringFormat { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(measureTrailingSpaces: true)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -289,7 +291,7 @@ class PdfStringFormat { /// ..noClip = true /// ..lineLimit = true); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -304,7 +306,7 @@ class PdfStringFormat { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(wordWrap: PdfWordWrapType.word)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -323,7 +325,7 @@ class PdfStringFormat { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// format: PdfStringFormat(paragraphIndent: 2.1)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -335,16 +337,17 @@ class PdfStringFormat { //Implementation void _initialize( - PdfTextAlignment textAlignment, - PdfVerticalAlignment verticalAlignment, - PdfTextDirection textDirection, - double characterSpacing, - double wordSpacing, - double lineSpacing, - PdfSubSuperscript subSuperscript, - double paragraphIndent, - bool measureTrailingSpaces, - PdfWordWrapType wordWrap) { + PdfTextAlignment textAlignment, + PdfVerticalAlignment verticalAlignment, + PdfTextDirection textDirection, + double characterSpacing, + double wordSpacing, + double lineSpacing, + PdfSubSuperscript subSuperscript, + double paragraphIndent, + bool measureTrailingSpaces, + PdfWordWrapType wordWrap, + ) { alignment = textAlignment; lineAlignment = verticalAlignment; this.characterSpacing = characterSpacing; 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 cc52c06aa..8c787352e 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 @@ -30,11 +30,14 @@ class PdfStringLayouter { //Implementation /// internal method PdfStringLayoutResult layout( - String text, PdfFont font, PdfStringFormat? format, - {double? width, - double? height, - PdfRectangle? bounds, - double? pageHeight}) { + String text, + PdfFont font, + PdfStringFormat? format, { + double? width, + double? height, + PdfRectangle? bounds, + double? pageHeight, + }) { _text = text; _font = font; _format = format; @@ -85,12 +88,14 @@ class PdfStringLayouter { double? _getLineIndent(bool firstLine) { double? lineIndent = 0; if (_format != null) { - lineIndent = firstLine - ? PdfStringFormatHelper.getHelper(_format!).firstLineIndent - : _format!.paragraphIndent; - lineIndent = (_size.width > 0) - ? (_size.width <= lineIndent ? _size.width : lineIndent) - : lineIndent; + lineIndent = + firstLine + ? PdfStringFormatHelper.getHelper(_format!).firstLineIndent + : _format!.paragraphIndent; + lineIndent = + (_size.width > 0) + ? (_size.width <= lineIndent ? _size.width : lineIndent) + : lineIndent; } return lineIndent; } @@ -117,12 +122,12 @@ class PdfStringLayouter { if (maxWidth <= 0 || lineWidth.roundToDouble() <= maxWidth.roundToDouble()) { _addToLineResult( - lineResult, - lines, - line, - lineWidth, - getLineTypeValue(LineType.newLineBreak)! | - getLineTypeValue(lineType)!); + lineResult, + lines, + line, + lineWidth, + getLineTypeValue(LineType.newLineBreak)! | getLineTypeValue(lineType)!, + ); } else { String builder = ''; String curLine = ''; @@ -167,12 +172,13 @@ class PdfStringLayouter { final String ln = builder; if (ln != ' ') { _addToLineResult( - lineResult, - lines, - ln, - lineWidth, - getLineTypeValue(LineType.layoutBreak)! | - getLineTypeValue(lineType)!); + lineResult, + lines, + ln, + lineWidth, + getLineTypeValue(LineType.layoutBreak)! | + getLineTypeValue(lineType)!, + ); } curLine = ''; builder = ''; @@ -203,12 +209,13 @@ class PdfStringLayouter { if (builder.isNotEmpty) { final String ln = builder; _addToLineResult( - lineResult, - lines, - ln, - lineWidth, - getLineTypeValue(LineType.newLineBreak)! | - getLineTypeValue(LineType.lastParagraphLine)!); + lineResult, + lines, + ln, + lineWidth, + getLineTypeValue(LineType.newLineBreak)! | + getLineTypeValue(LineType.lastParagraphLine)!, + ); } reader.close(); } @@ -217,8 +224,13 @@ class PdfStringLayouter { return lineResult; } - void _addToLineResult(PdfStringLayoutResult lineResult, List lines, - String line, double lineWidth, int breakType) { + void _addToLineResult( + PdfStringLayoutResult lineResult, + List lines, + String line, + double lineWidth, + int breakType, + ) { final LineInfo info = LineInfo(); info.text = line; info.width = lineWidth; @@ -266,10 +278,11 @@ class PdfStringLayouter { } dynamic _copyToResult( - PdfStringLayoutResult result, - PdfStringLayoutResult lineResult, - List lines, - int? numInserted) { + PdfStringLayoutResult result, + PdfStringLayoutResult lineResult, + List lines, + int? numInserted, + ) { bool success = true; final bool allowPartialLines = _format != null && !_format!.lineLimit; double? height = result.size.height; @@ -317,7 +330,8 @@ class PdfStringLayouter { LineInfo _trimLine(LineInfo info, bool firstLine) { String line = info.text.toString(); double? lineWidth = info.width; - final bool start = _format == null || + final bool start = + _format == null || _format!.textDirection != PdfTextDirection.rightToLeft; if (!info.lineTypeList.contains(LineType.firstParagraphLine)) { line = start ? line.trimLeft() : line.trimRight(); 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 1a07b7b28..2dccc099a 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 @@ -20,7 +20,7 @@ import 'unicode_true_type_font.dart'; /// brush: PdfBrushes.black, /// bounds: Rect.fromLTWH(0, 0, 100, 50)); /// //Saves the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -41,12 +41,16 @@ class PdfTrueTypeFont extends PdfFont { /// brush: PdfBrushes.black, /// bounds: Rect.fromLTWH(0, 0, 100, 50)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfTrueTypeFont(List fontData, double size, - {PdfFontStyle? style, List? multiStyle}) { + PdfTrueTypeFont( + List fontData, + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { _helper = PdfTrueTypeFontHelper(this); _initializeFont(fontData, size, style, multiStyle); } @@ -75,8 +79,12 @@ class PdfTrueTypeFont extends PdfFont { /// //Dispose the document. /// doc.dispose(); /// ``` - PdfTrueTypeFont.fromBase64String(String fontData, double size, - {PdfFontStyle? style, List? multiStyle}) { + PdfTrueTypeFont.fromBase64String( + String fontData, + double size, { + PdfFontStyle? style, + List? multiStyle, + }) { _helper = PdfTrueTypeFontHelper(this); if (fontData.isEmpty) { throw ArgumentError.value(fontData, 'fontData', 'Invalid font data'); @@ -88,10 +96,15 @@ class PdfTrueTypeFont extends PdfFont { late PdfTrueTypeFontHelper _helper; //Implementation - void _initializeFont(List fontData, double size, PdfFontStyle? style, - List? multiStyle) { - PdfFontHelper.getHelper(this) - .initialize(size, style: style, multiStyle: multiStyle); + void _initializeFont( + List fontData, + double size, + PdfFontStyle? style, + List? multiStyle, + ) { + PdfFontHelper.getHelper( + this, + ).initialize(size, style: style, multiStyle: multiStyle); _helper.unicode = true; _createFontInternals(fontData); } 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 a084416eb..b282ee41d 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 @@ -86,7 +86,7 @@ class ArabicShapeRenderer { ['\u06CC', '\uFBFC', '\uFBFD', '\uFBFE', '\uFBFF'], ['\u06D0', '\uFBE4', '\uFBE5', '\uFBE6', '\uFBE7'], ['\u06D2', '\uFBAE', '\uFBAF'], - ['\u06D3', '\uFBB0', '\uFBB1'] + ['\u06D3', '\uFBB0', '\uFBB1'], ]; /// internal field @@ -197,8 +197,8 @@ class ArabicShapeRenderer { final StringBuffer str = StringBuffer(); int ligature, len, i = 0; String next; - _ArabicShape previous = _ArabicShape(); - _ArabicShape present = _ArabicShape(); + ArabicShape previous = ArabicShape(); + ArabicShape present = ArabicShape(); while (i < input.length) { next = input[i++]; ligature = this.ligature(next, present); @@ -212,7 +212,7 @@ class ArabicShapeRenderer { present.value = getCharacterShape(present.value, len); append(str, previous, level); previous = present; - present = _ArabicShape(); + present = ArabicShape(); present.value = next; present.shapes = shapeCount; present.ligature++; @@ -227,7 +227,7 @@ class ArabicShapeRenderer { } /// internal method - void append(StringBuffer buffer, _ArabicShape shape, int level) { + void append(StringBuffer buffer, ArabicShape shape, int level) { if (shape.value != '') { buffer.write(shape.value); shape.ligature -= 1; @@ -251,7 +251,7 @@ class ArabicShapeRenderer { } /// internal method - int ligature(String value, _ArabicShape shape) { + int ligature(String value, ArabicShape shape) { if (shape.value != '') { int result = 0; if ((value.codeUnitAt(0) >= fathatan.codeUnitAt(0) && @@ -374,7 +374,7 @@ class ArabicShapeRenderer { } } -class _ArabicShape { +class ArabicShape { String value = ''; String type = ''; String vowel = ''; 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 691a113f0..4f79df994 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 @@ -95,7 +95,7 @@ class Bidi { } return { 'rtlText': renderedString, - 'orderedIndexes': indexes + 'orderedIndexes': indexes, }; } @@ -155,8 +155,9 @@ class Bidi { for (int i = 0; i < text.length; i++) { if (((indexLevels[i] & 1) == 1) && mirroringShapeCharacters.containsKey(text[i].codeUnitAt(0))) { - result.write(String.fromCharCode( - mirroringShapeCharacters[text[i].codeUnitAt(0)]!)); + result.write( + String.fromCharCode(mirroringShapeCharacters[text[i].codeUnitAt(0)]!), + ); } else { result.write(text[i]); } @@ -580,8 +581,11 @@ class _RTLCharacters { // Specifies the resultant levels. late List levels; //sbyte // Specifies the RTL character types. - List rtlCharacterTypes = - List.filled(65536, 0, growable: true); //sbyte + List rtlCharacterTypes = List.filled( + 65536, + 0, + growable: true, + ); //sbyte // Left-to-Right (Non-European or non-Arabic digits). static const int l = 0; @@ -2352,7 +2356,7 @@ class _RTLCharacters { otn, 65534, 65535, - l + l, ]; // Implementations. @@ -2360,8 +2364,11 @@ class _RTLCharacters { types = getCharacterCode(inputText); textOrder = isRTL ? lre : l; doVisualOrder(); - final List result = - List.filled(this.result.length, 0, growable: true); + 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); } @@ -2369,8 +2376,11 @@ class _RTLCharacters { } List getCharacterCode(String text) { - final List characterCodes = - List.filled(text.length, 0, growable: true); + 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)]; } @@ -2497,7 +2507,12 @@ class _RTLCharacters { } void checkEuropeanDigits( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index; i < length; ++i) { if (result[i] == en) { for (int j = i - 1; j >= index; --j) { @@ -2514,7 +2529,12 @@ class _RTLCharacters { } void checkArabicCharacters( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index; i < length; ++i) { if (result[i] == al) { result[i] = r; @@ -2524,7 +2544,12 @@ class _RTLCharacters { } void checkEuropeanNumberSeparator( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index + 1; i < length - 1; ++i) { if (result[i] == es || result[i] == cs) { final int preview = result[i - 1]; @@ -2540,7 +2565,12 @@ class _RTLCharacters { } void checkEuropeanNumberTerminator( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index; i < length; ++i) { if (result[i] == et) { final int s = i; @@ -2575,7 +2605,12 @@ class _RTLCharacters { } void checkOtherNeutrals( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index; i < length; ++i) { if (result[i] == es || result[i] == et || result[i] == cs) { result[i] = otn; @@ -2585,7 +2620,12 @@ class _RTLCharacters { } void checkOtherCharacters( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index; i < length; ++i) { if (result[i] == en) { int pst = startType; @@ -2604,7 +2644,12 @@ class _RTLCharacters { } void checkCommanCharacters( - int index, int length, int level, int startType, int endType) { + int index, + int length, + int level, + int startType, + int endType, + ) { for (int i = index; i < length; ++i) { if (result[i] == ws || result[i] == otn || 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 8b4a1a6e6..07c26cfef 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 @@ -23,7 +23,7 @@ class TtfReader { 'hmtx', 'loca', 'maxp', - 'prep' + 'prep', ]; final List _entrySelectors = [ 0, @@ -46,7 +46,7 @@ class TtfReader { 4, 4, 4, - 4 + 4, ]; //Fields @@ -345,18 +345,21 @@ class TtfReader { } void _initializeMetrics( - _TtfNameTable nameTable, - _TtfHeadTable headTable, - _TtfHorizontalHeaderTable horizontalHeadTable, - _TtfOS2Table os2Table, - _TtfPostTable postTable, - List<_TtfCmapSubTable> cmapTables) { + _TtfNameTable nameTable, + _TtfHeadTable headTable, + _TtfHorizontalHeaderTable horizontalHeadTable, + _TtfOS2Table os2Table, + _TtfPostTable postTable, + List<_TtfCmapSubTable> cmapTables, + ) { _initializeFontName(nameTable); bool bSymbol = false; for (int i = 0; i < cmapTables.length; i++) { final _TtfCmapSubTable subTable = cmapTables[i]; - final _TtfCmapEncoding encoding = - _getCmapEncoding(subTable.platformID, subTable.encodingID); + final _TtfCmapEncoding encoding = _getCmapEncoding( + subTable.platformID, + subTable.encodingID, + ); if (encoding == _TtfCmapEncoding.symbol) { bSymbol = true; break; @@ -369,12 +372,14 @@ class TtfReader { 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!.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!.leading = + (os2Table.sTypoAscender - os2Table.sTypoDescender + os2Table.sTypoLineGap) * factor; @@ -418,8 +423,10 @@ class TtfReader { final _TtfTableInfo tableInfo = _getTable('cmap')!; currentOffset = tableInfo.offset! + subTable.offset; final _TtfCmapFormat format = _getCmapFormat(_readUInt16(currentOffset!)); - final _TtfCmapEncoding encoding = - _getCmapEncoding(subTable.platformID, subTable.encodingID); + final _TtfCmapEncoding encoding = _getCmapEncoding( + subTable.platformID, + subTable.encodingID, + ); if (encoding != _TtfCmapEncoding.unknown) { switch (format) { case _TtfCmapFormat.apple: @@ -439,7 +446,9 @@ class TtfReader { } void _readUCS4CmapTable( - _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { + _TtfCmapSubTable subTable, + _TtfCmapEncoding encoding, + ) { final _TtfTableInfo tableInfo = _getTable('cmap')!; currentOffset = tableInfo.offset! + subTable.offset + 12; final int count = _readInt32(currentOffset!); @@ -459,7 +468,9 @@ class TtfReader { } void _readAppleCmapTable( - _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { + _TtfCmapSubTable subTable, + _TtfCmapEncoding encoding, + ) { final _TtfTableInfo tableInfo = _getTable('cmap')!; currentOffset = tableInfo.offset! + subTable.offset; final _TtfAppleCmapSubTable table = _TtfAppleCmapSubTable(); @@ -480,7 +491,9 @@ class TtfReader { } void _readMicrosoftCmapTable( - _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { + _TtfCmapSubTable subTable, + _TtfCmapEncoding encoding, + ) { final _TtfTableInfo tableInfo = _getTable('cmap')!; currentOffset = tableInfo.offset! + subTable.offset; final Map? collection = @@ -505,18 +518,21 @@ class TtfReader { int codeOffset = 0; int index = 0; for (int j = 0; j < segCount; j++) { - for (int k = table.startCount[j]; - k <= table.endCount[j] && k != 65535; - k++) { + for ( + int k = table.startCount[j]; + k <= table.endCount[j] && k != 65535; + k++ + ) { if (table.idRangeOffset[j] == 0) { codeOffset = (k + table.idDelta[j]) & 65535; } else { - index = (j + - table.idRangeOffset[j] / 2 - - segCount + - k - - table.startCount[j]) - .toInt(); + index = + (j + + table.idRangeOffset[j] / 2 - + segCount + + k - + table.startCount[j]) + .toInt(); if (index >= table.glyphID.length) { continue; } @@ -525,9 +541,10 @@ class TtfReader { final TtfGlyphInfo glyph = TtfGlyphInfo(); glyph.index = codeOffset; glyph.width = _getWidth(glyph.index); - final int id = (encoding == _TtfCmapEncoding.symbol) - ? ((k & 0xff00) == 0xf000 ? k & 0xff : k) - : k; + final int id = + (encoding == _TtfCmapEncoding.symbol) + ? ((k & 0xff00) == 0xf000 ? k & 0xff : k) + : k; glyph.charCode = id; collection![id] = glyph; _addGlyph(glyph, encoding); @@ -536,7 +553,9 @@ class TtfReader { } void _readTrimmedCmapTable( - _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { + _TtfCmapSubTable subTable, + _TtfCmapEncoding encoding, + ) { final _TtfTableInfo tableInfo = _getTable('cmap')!; currentOffset = tableInfo.offset! + subTable.offset; final _TtfTrimmedCmapSubTable table = _TtfTrimmedCmapSubTable(); @@ -605,15 +624,19 @@ class TtfReader { /// internal method double getCharWidth(String code) { TtfGlyphInfo? glyphInfo = getGlyph(char: code); - glyphInfo = (glyphInfo != null && !glyphInfo.empty) - ? glyphInfo - : _getDefaultGlyph(); + glyphInfo = + (glyphInfo != null && !glyphInfo.empty) + ? glyphInfo + : _getDefaultGlyph(); return (!glyphInfo!.empty) ? glyphInfo.width.toDouble() : 0; } /// internal method - TtfGlyphInfo? getGlyph( - {int? charCode, String? char, bool? isSetSymbol = false}) { + TtfGlyphInfo? getGlyph({ + int? charCode, + String? char, + bool? isSetSymbol = false, + }) { if (charCode != null) { TtfGlyphInfo? glyphInfo; if (!metrics!.isSymbol && _microsoftGlyphs != null) { @@ -647,7 +670,8 @@ class TtfReader { } if (_isLowSurrogate(code)) { if (_isSurrogatePair(_surrogateHigh, code)) { - code = (((_surrogateHigh >> 6) & ((1 << 5) - 1)) + 1) << 16 | + code = + (((_surrogateHigh >> 6) & ((1 << 5) - 1)) + 1) << 16 | ((_surrogateHigh & ((1 << 6) - 1)) << 10 | code & ((1 << 10) - 1)); if (_unicodeUCS4GlyphCollection!.containsKey(code)) { @@ -726,10 +750,18 @@ class TtfReader { final _TtfOS2Table os2Table = _readOS2Table(); final _TtfPostTable postTable = _readPostTable(); _width = _readWidthTable( - horizontalHeadTable.numberOfHMetrics, headTable.unitsPerEm); + horizontalHeadTable.numberOfHMetrics, + headTable.unitsPerEm, + ); final List<_TtfCmapSubTable> subTables = _readCmapTable(); - _initializeMetrics(nameTable, headTable, horizontalHeadTable, os2Table, - postTable, subTables); + _initializeMetrics( + nameTable, + headTable, + horizontalHeadTable, + os2Table, + postTable, + subTables, + ); } _TtfCmapFormat _getCmapFormat(int format) { @@ -813,7 +845,7 @@ class TtfReader { case _TtfCmapEncoding.unicodeUCS4: collection = _unicodeUcs4Glyph; break; - default: + case _TtfCmapEncoding.unknown: break; } collection![glyph.charCode] = glyph; @@ -829,7 +861,8 @@ class TtfReader { case _TtfCmapEncoding.symbol: collection = _macintoshGlyphs; break; - default: + case _TtfCmapEncoding.unknown: + case _TtfCmapEncoding.unicodeUCS4: break; } collection![glyph.index] = glyph; @@ -865,7 +898,11 @@ class TtfReader { final int? newLocaSize = result['newLocaSize'] as int?; final List newLocaUpdated = result['newLocaUpdated'] as List; final List? fontProgram = _getFontProgram( - newLocaUpdated, newGlyphTable, glyphTableSize, newLocaSize); + newLocaUpdated, + newGlyphTable, + glyphTableSize, + newLocaSize, + ); return fontProgram; } @@ -890,7 +927,10 @@ class TtfReader { } dynamic _updateLocaTable( - List newLocaTable, bool isLocaShort, List? newLocaTableOut) { + List newLocaTable, + bool isLocaShort, + List? newLocaTableOut, + ) { final int size = isLocaShort ? newLocaTable.length * 2 : newLocaTable.length * 4; final int count = _align(size); @@ -906,7 +946,7 @@ class TtfReader { } return { 'newLocaUpdated': writer.data, - 'newLocaSize': size + 'newLocaSize': size, }; } @@ -925,7 +965,10 @@ class TtfReader { } void _processCompositeGlyph( - Map glyphChars, int glyph, List offsets) { + Map glyphChars, + int glyph, + List offsets, + ) { if (glyph < offsets.length - 1) { final int glyphOffset = offsets[glyph]; if (glyphOffset != offsets[glyph + 1]) { @@ -963,8 +1006,12 @@ class TtfReader { } } - dynamic _generateGlyphTable(Map glyphChars, List offsets, - List? newLocaTable, List? newGlyphTable) { + dynamic _generateGlyphTable( + Map glyphChars, + List offsets, + List? newLocaTable, + List? newGlyphTable, + ) { newLocaTable = []; final List activeGlyphs = glyphChars.keys.toList(); activeGlyphs.sort((int a, int b) => a - b); @@ -1003,7 +1050,7 @@ class TtfReader { return { 'glyphTableSize': glyphSize, 'newLocaTable': newLocaTable, - 'newGlyphTable': newGlyphTable + 'newGlyphTable': newGlyphTable, }; } @@ -1011,10 +1058,17 @@ class TtfReader { return (value + 3) & (~3); } - List? _getFontProgram(List newLocaTableOut, List newGlyphTable, - int? glyphTableSize, int? locaTableSize) { - final dynamic result = - _getFontProgramLength(newLocaTableOut, newGlyphTable, 0); + List? _getFontProgram( + List newLocaTableOut, + List newGlyphTable, + int? glyphTableSize, + int? locaTableSize, + ) { + final dynamic result = _getFontProgramLength( + newLocaTableOut, + newGlyphTable, + 0, + ); final int fontProgramLength = result['fontProgramLength'] as int; final int numTables = result['numTables'] as int; final BigEndianWriter writer = BigEndianWriter(fontProgramLength); @@ -1024,14 +1078,23 @@ class TtfReader { writer.writeShort((1 << (entrySelector & 31)) * 16); writer.writeShort(entrySelector); writer.writeShort((numTables - (1 << (entrySelector & 31))) * 16); - _writeCheckSums(writer, numTables, newLocaTableOut, newGlyphTable, - glyphTableSize, locaTableSize); + _writeCheckSums( + writer, + numTables, + newLocaTableOut, + newGlyphTable, + glyphTableSize, + locaTableSize, + ); _writeGlyphs(writer, newLocaTableOut, newGlyphTable); return writer.data; } dynamic _getFontProgramLength( - List newLocaTableOut, List newGlyphTable, int numTables) { + List newLocaTableOut, + List newGlyphTable, + int numTables, + ) { numTables = 2; final List tableNames = _tableNames; int fontProgramLength = 0; @@ -1051,17 +1114,18 @@ class TtfReader { fontProgramLength += usedTablesSize; return { 'fontProgramLength': fontProgramLength, - 'numTables': numTables + 'numTables': numTables, }; } void _writeCheckSums( - BigEndianWriter writer, - int numTables, - List newLocaTableOut, - List newGlyphTable, - int? glyphTableSize, - int? locaTableSize) { + BigEndianWriter writer, + int numTables, + List newLocaTableOut, + List newGlyphTable, + int? glyphTableSize, + int? locaTableSize, + ) { final List tableNames = _tableNames; int usedTablesSize = numTables * 16 + (3 * 4); int? nextTableSize = 0; @@ -1106,7 +1170,10 @@ class TtfReader { } void _writeGlyphs( - BigEndianWriter writer, List newLocaTable, List newGlyphTable) { + BigEndianWriter writer, + List newLocaTable, + List newGlyphTable, + ) { final List tableNames = _tableNames; for (int i = 0; i < tableNames.length; i++) { final String tableName = tableNames[i]; @@ -1229,9 +1296,11 @@ class TtfReader { int written = 0; int read = 0; do { - for (int i = 0; - i < count - written && currentOffset! + i < _fontData.length; - i++) { + for ( + int i = 0; + i < count - written && currentOffset! + i < _fontData.length; + i++ + ) { buffer[index + i] = _fontData[currentOffset! + i]; } read = count - written; @@ -1689,7 +1758,7 @@ enum _TtfCmapFormat { trimmed, /// This is the Microsoft standard character-to-glyph-index mapping table for fonts supporting Unicode supplementary-plane characters (U+10000 to U+10FFFF). - microsoftExt + microsoftExt, } /// Enumerator that implements CMAP encodings. @@ -1707,7 +1776,7 @@ enum _TtfCmapEncoding { macintosh, /// When building a Unicode font for Windows (plane characters). - unicodeUCS4 + unicodeUCS4, } /// Ttf platform ID. @@ -1722,7 +1791,7 @@ enum _TtfPlatformID { iso, /// Microsoft platform. - microsoft + microsoft, } /// Microsoft encoding ID. @@ -1734,7 +1803,7 @@ enum _TtfMicrosoftEncodingID { unicode, /// Unicode UCS4. - unicodeUCS4 + unicodeUCS4, } /// Macintosh encoding ID. @@ -1746,5 +1815,5 @@ enum _TtfMacintoshEncodingID { japanese, /// Chinese encoding. - chinese + chinese, } 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 46c7a834d..11eb4c6f7 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 @@ -158,17 +158,20 @@ class UnicodeTrueTypeFont { void _createDescendantFont() { _descendantFont!.beginSave = _descendantFontBeginSave; - _descendantFont![PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.font); - _descendantFont![PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.cidFontType2); + _descendantFont![PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.font, + ); + _descendantFont![PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.cidFontType2, + ); _descendantFont![PdfDictionaryProperties.baseFont] = PdfName(_subsetName); - _descendantFont![PdfDictionaryProperties.cidToGIDMap] = - PdfName(PdfDictionaryProperties.identity); + _descendantFont![PdfDictionaryProperties.cidToGIDMap] = PdfName( + PdfDictionaryProperties.identity, + ); _descendantFont![PdfDictionaryProperties.dw] = PdfNumber(1000); _fontDescriptor = _createFontDescriptor(); - _descendantFont![PdfDictionaryProperties.fontDescriptor] = - PdfReferenceHolder(_fontDescriptor); + _descendantFont![PdfDictionaryProperties + .fontDescriptor] = PdfReferenceHolder(_fontDescriptor); _descendantFont![PdfDictionaryProperties.cidSystemInfo] = _createSystemInfo(); } @@ -176,30 +179,42 @@ class UnicodeTrueTypeFont { PdfDictionary _createFontDescriptor() { final PdfDictionary descriptor = PdfDictionary(); final TtfMetrics metrics = reader.metrics!; - descriptor[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.fontDescriptor); + descriptor[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.fontDescriptor, + ); descriptor[PdfDictionaryProperties.fontName] = PdfName(_subsetName); - descriptor[PdfDictionaryProperties.flags] = - PdfNumber(_getDescriptorFlags()); + descriptor[PdfDictionaryProperties.flags] = PdfNumber( + _getDescriptorFlags(), + ); final PdfRectangle rect = reader.metrics!.fontBox; - descriptor[PdfDictionaryProperties.fontBBox] = PdfArray( - [rect.x, rect.y + rect.height, rect.width, -rect.height]); - descriptor[PdfDictionaryProperties.missingWidth] = - PdfNumber(metrics.widthTable[32]); + descriptor[PdfDictionaryProperties.fontBBox] = PdfArray([ + rect.x, + rect.y + rect.height, + rect.width, + -rect.height, + ]); + descriptor[PdfDictionaryProperties.missingWidth] = PdfNumber( + metrics.widthTable[32], + ); descriptor[PdfDictionaryProperties.stemV] = PdfNumber(metrics.stemV); - descriptor[PdfDictionaryProperties.italicAngle] = - PdfNumber(metrics.italicAngle!); - descriptor[PdfDictionaryProperties.capHeight] = - PdfNumber(metrics.capHeight); + descriptor[PdfDictionaryProperties.italicAngle] = PdfNumber( + metrics.italicAngle!, + ); + descriptor[PdfDictionaryProperties.capHeight] = PdfNumber( + metrics.capHeight, + ); descriptor[PdfDictionaryProperties.ascent] = PdfNumber(metrics.winAscent); descriptor[PdfDictionaryProperties.descent] = PdfNumber(metrics.winDescent); descriptor[PdfDictionaryProperties.leading] = PdfNumber(metrics.leading); - descriptor[PdfDictionaryProperties.avgWidth] = - PdfNumber(metrics.widthTable[32]); - descriptor[PdfDictionaryProperties.fontFile2] = - PdfReferenceHolder(_fontProgram); - descriptor[PdfDictionaryProperties.maxWidth] = - PdfNumber(metrics.widthTable[32]); + descriptor[PdfDictionaryProperties.avgWidth] = PdfNumber( + metrics.widthTable[32], + ); + descriptor[PdfDictionaryProperties.fontFile2] = PdfReferenceHolder( + _fontProgram, + ); + descriptor[PdfDictionaryProperties.maxWidth] = PdfNumber( + metrics.widthTable[32], + ); descriptor[PdfDictionaryProperties.xHeight] = PdfNumber(0); descriptor[PdfDictionaryProperties.stemH] = PdfNumber(0); return descriptor; @@ -228,8 +243,9 @@ class UnicodeTrueTypeFont { IPdfPrimitive _createSystemInfo() { final PdfDictionary systemInfo = PdfDictionary(); systemInfo[PdfDictionaryProperties.registry] = PdfString('Adobe'); - systemInfo[PdfDictionaryProperties.ordering] = - PdfString(PdfDictionaryProperties.identity); + systemInfo[PdfDictionaryProperties.ordering] = PdfString( + PdfDictionaryProperties.identity, + ); systemInfo[PdfDictionaryProperties.supplement] = PdfNumber(0); return systemInfo; } @@ -251,13 +267,16 @@ class UnicodeTrueTypeFont { void _createFontDictionary() { fontDictionary!.beginSave = _fontDictionaryBeginSave; - fontDictionary![PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.font); + fontDictionary![PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.font, + ); fontDictionary![PdfDictionaryProperties.baseFont] = PdfName(_subsetName); - fontDictionary![PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.type0); - fontDictionary![PdfDictionaryProperties.encoding] = - PdfName(PdfDictionaryProperties.identityH); + fontDictionary![PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.type0, + ); + fontDictionary![PdfDictionaryProperties.encoding] = PdfName( + PdfDictionaryProperties.identityH, + ); final PdfArray descFonts = PdfArray(); final PdfReferenceHolder reference = PdfReferenceHolder(_descendantFont); descFonts.add(reference); @@ -268,8 +287,9 @@ class UnicodeTrueTypeFont { if (_usedChars != null && _usedChars!.isNotEmpty && !fontDictionary!.containsKey(PdfDictionaryProperties.toUnicode)) { - fontDictionary![PdfDictionaryProperties.toUnicode] = - PdfReferenceHolder(_cmap); + fontDictionary![PdfDictionaryProperties.toUnicode] = PdfReferenceHolder( + _cmap, + ); } } @@ -285,8 +305,9 @@ class UnicodeTrueTypeFont { } glyphInfo.add(glyph); } - glyphInfo - .sort((TtfGlyphInfo a, TtfGlyphInfo b) => a.index.compareTo(b.index)); + glyphInfo.sort( + (TtfGlyphInfo a, TtfGlyphInfo b) => a.index.compareTo(b.index), + ); int firstGlyphIndex = 0; int lastGlyphIndex = 0; bool firstGlyphIndexWasSet = false; @@ -327,7 +348,8 @@ class UnicodeTrueTypeFont { keys.sort(); final int first = keys[0]; final int last = keys[keys.length - 1]; - final String middlePart = _getHexString(first, false) + + final String middlePart = + _getHexString(first, false) + _getHexString(last, false) + PdfOperators.newLine; String builder = ''; @@ -433,8 +455,9 @@ class UnicodeTrueTypeFont { void _fontDescriptorBeginSave(Object sender, SavePdfPrimitiveArgs? ars) { if ((_usedChars != null && _usedChars!.isNotEmpty) && !_fontDescriptor!.containsKey(PdfDictionaryProperties.cidSet)) { - _fontDescriptor![PdfDictionaryProperties.cidSet] = - PdfReferenceHolder(_cidStream); + _fontDescriptor![PdfDictionaryProperties.cidSet] = PdfReferenceHolder( + _cidStream, + ); } } @@ -448,7 +471,7 @@ class UnicodeTrueTypeFont { 0x08, 0x04, 0x02, - 0x01 + 0x01, ]; if (_usedChars != null && _usedChars!.isNotEmpty) { final Map glyphChars = reader.getGlyphChars(_usedChars!); 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 5c96e9ee9..8d9044d54 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 @@ -97,7 +97,11 @@ abstract class ImageDecoder { /// internal method 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) { for (int i = 0; i < length; i++) { @@ -109,7 +113,7 @@ abstract class ImageDecoder { return { 'offset': streamOffset, 'outputBuffer': buffer, - 'length': result + 'length': result, }; } 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 2d7b29c21..c03d15622 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 @@ -51,7 +51,7 @@ class JpegDecoder extends ImageDecoder { 46, 48, 47, - 0 + 0, ]; //Fields @@ -119,21 +119,26 @@ class JpegDecoder extends ImageDecoder { @override PdfStream? getImageDictionary() { _imageStream = PdfStream(); - _imageStream!.dataStream = imageData; + _imageStream!.data = imageData; _imageStream!.compress = false; - _imageStream![PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.xObject); - _imageStream![PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.image); + _imageStream![PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.xObject, + ); + _imageStream![PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.image, + ); _imageStream![PdfDictionaryProperties.width] = PdfNumber(width); _imageStream![PdfDictionaryProperties.height] = PdfNumber(height); - _imageStream![PdfDictionaryProperties.bitsPerComponent] = - PdfNumber(bitsPerComponent!); - _imageStream![PdfDictionaryProperties.filter] = - PdfName(PdfDictionaryProperties.dctDecode); - _imageStream![PdfDictionaryProperties.colorSpace] = - PdfName(_getColorSpace()); + _imageStream![PdfDictionaryProperties.bitsPerComponent] = PdfNumber( + bitsPerComponent!, + ); + _imageStream![PdfDictionaryProperties.filter] = PdfName( + PdfDictionaryProperties.dctDecode, + ); + _imageStream![PdfDictionaryProperties.colorSpace] = PdfName( + _getColorSpace(), + ); _imageStream![PdfDictionaryProperties.decodeParms] = _getDecodeParams(); return _imageStream; @@ -145,8 +150,9 @@ class JpegDecoder extends ImageDecoder { decodeParams[PdfDictionaryProperties.blackIs1] = PdfBoolean(true); decodeParams[PdfDictionaryProperties.k] = PdfNumber(-1); decodeParams[PdfDictionaryProperties.predictor] = PdfNumber(15); - decodeParams[PdfDictionaryProperties.bitsPerComponent] = - PdfNumber(bitsPerComponent!); + decodeParams[PdfDictionaryProperties.bitsPerComponent] = PdfNumber( + bitsPerComponent!, + ); return decodeParams; } @@ -156,7 +162,7 @@ class JpegDecoder extends ImageDecoder { if (_convertToUShort(_readJpegBytes(2)) != 0xFFD8) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } int? jpegMarkerStart; @@ -170,14 +176,14 @@ class JpegDecoder extends ImageDecoder { if (offset != jpegPosition || offset > imageData.length) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } } if (jpegMarkerStart != 0xFF || jpegMarkerNum != 0xE1) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } else { seek(2); @@ -185,7 +191,7 @@ class JpegDecoder extends ImageDecoder { _convertToUShort(_readJpegBytes(2)) != 0) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } final int tiffTypeHeaderStart = offset; @@ -194,15 +200,17 @@ class JpegDecoder extends ImageDecoder { if (_convertToUShort(_readJpegBytes(2)) != 0x002A) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } offset = _convertToUInt(_readJpegBytes(4)) + tiffTypeHeaderStart; final int ifdEntryCount = _convertToUShort(_readJpegBytes(2)); int orientationPosition = 0; - for (int currentIfdEntry = 0; - currentIfdEntry < ifdEntryCount; - currentIfdEntry++) { + for ( + int currentIfdEntry = 0; + currentIfdEntry < ifdEntryCount; + currentIfdEntry++ + ) { if (_convertToUShort(_readJpegBytes(2)) == 274) { orientationPosition = offset - 2; } @@ -213,7 +221,7 @@ class JpegDecoder extends ImageDecoder { if (_convertToUShort(_readJpegBytes(2)) != 274) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } seek(6); @@ -228,7 +236,7 @@ class JpegDecoder extends ImageDecoder { if (orientationAngle == 0) { return { 'hasOrientation': false, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } else { imageOrientation = orientationAngle; @@ -237,7 +245,7 @@ class JpegDecoder extends ImageDecoder { } return { 'hasOrientation': true, - 'imageOrientation': imageOrientation + 'imageOrientation': imageOrientation, }; } @@ -263,7 +271,7 @@ class JpegDecoder extends ImageDecoder { height = imageData[offset] << 8 | imageData[offset + 1]; seek(2); width = imageData[offset] << 8 | imageData[offset + 1]; - _noOfComponents = imageData[offset]; + _noOfComponents = imageData[offset + 2]; seek(1); isContinueReading = false; break; @@ -344,13 +352,16 @@ class JpegDecoder extends ImageDecoder { if (jfif == 0xE0FF) { while (true) { i += step; - int markerLength = - _convertToUShort([imageData[i + 1], imageData[i]]) - .toSigned(32); + int markerLength = _convertToUShort([ + imageData[i + 1], + imageData[i], + ]).toSigned(32); markerLength = (markerLength >> 8) | (markerLength << 8) & 0xFFFF; i += markerLength; - final int marker = - _convertToUShort([imageData[i + 1], imageData[i]]); + final int marker = _convertToUShort([ + imageData[i + 1], + imageData[i], + ]); if (marker == 0xDAFF) { i += step + 2; switch (imageData[i]) { 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 4a4c48de7..f29f5aba0 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 @@ -93,6 +93,7 @@ class PngDecoder extends ImageDecoder { case _PngChunkTypes.unknown: _ignoreChunk(); break; + // ignore: no_default_cases default: break; } @@ -230,7 +231,8 @@ class PngDecoder extends ImageDecoder { } void _decodeImageData() { - isDecode = (_header.interlace == 1) || + isDecode = + (_header.interlace == 1) || (_header.bitDepth == 16) || ((_header.colorType & 4) != 0) || _shades; @@ -266,8 +268,11 @@ class PngDecoder extends ImageDecoder { int? numRead = 0; final List outputData = []; do { - final Map result = - deflateStream.read(buffer, 0, buffer.length); + final Map result = deflateStream.read( + buffer, + 0, + buffer.length, + ); numRead = result['count'] as int?; buffer = result['data'] as List; for (int i = 0; i < numRead!; i++) { @@ -292,7 +297,13 @@ 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 { @@ -300,13 +311,19 @@ class PngDecoder extends ImageDecoder { (_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++, destinationY += yStep) { + for ( + int sourceY = 0, destinationY = yOffset; + sourceY < height!; + sourceY++, destinationY += yStep + ) { final int filter = _dataStream[_dataStreamOffset!]; _dataStreamOffset = _dataStreamOffset! + 1; - _dataStreamOffset = - _readStream(_dataStream, _dataStreamOffset, current, bytesPerRow); + _dataStreamOffset = _readStream( + _dataStream, + _dataStreamOffset, + current, + bytesPerRow, + ); switch (_getFilterType(filter)) { case _PngFilterTypes.none: break; @@ -322,8 +339,6 @@ class PngDecoder extends ImageDecoder { case _PngFilterTypes.paeth: _decompressPaeth(current, prior, bytesPerRow, _bitsPerPixel); break; - default: - throw Exception('Unknown PNG filter'); } _processPixels(current, xOffset, xStep, destinationY, width); final List tmp = prior; @@ -334,7 +349,11 @@ class PngDecoder extends ImageDecoder { } int? _readStream( - List stream, int? streamOffset, List? data, int count) { + List stream, + int? streamOffset, + List? data, + int count, + ) { final dynamic result = read(stream, streamOffset, data, count); data = result['outputBuffer'] as List?; streamOffset = result['offset'] as int?; @@ -360,8 +379,16 @@ class PngDecoder extends ImageDecoder { final int depth = (_header.bitDepth == 16) ? 8 : _header.bitDepth; 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); + _decodedImageData = _setPixel( + _decodedImageData, + pixel, + _inputBands * sourceX, + size, + destX, + y, + _header.bitDepth, + yStep, + ); destX += step; } } @@ -371,14 +398,22 @@ class PngDecoder extends ImageDecoder { if (_header.bitDepth == 16) { for (int i = 0; i < width!; ++i) { final int temp = i * _inputBands + size; - pixel[temp] = ((pixel[temp]).toUnsigned(32) >> 8).toSigned(32); + pixel[temp] = (pixel[temp].toUnsigned(32) >> 8).toSigned(32); } } final int? yStep = width; destX = x; for (sourceX = 0; sourceX < width!; sourceX++) { - _maskData = _setPixel(_maskData, pixel, _inputBands * sourceX + size, - 1, destX, y, 8, yStep); + _maskData = _setPixel( + _maskData, + pixel, + _inputBands * sourceX + size, + 1, + destX, + y, + 8, + yStep, + ); destX += step; } } else { @@ -407,16 +442,21 @@ class PngDecoder extends ImageDecoder { } return pixel; } else if (_header.bitDepth == 16) { - final List pixel = - List.filled(data.length ~/ 2, 0, growable: true); + 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.filled( - data.length * 8 ~/ _header.bitDepth, 0, - growable: true); + data.length * 8 ~/ _header.bitDepth, + 0, + growable: true, + ); int index = 0; final int p = 8 ~/ _header.bitDepth; final int mask = (1 << _header.bitDepth) - 1; @@ -432,8 +472,16 @@ class PngDecoder extends ImageDecoder { } } - 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; for (int i = 0; i < size; ++i) { @@ -446,7 +494,8 @@ class PngDecoder extends ImageDecoder { } } else { final int position = bpr! * y + x ~/ (8 / bitDepth!); - final int t = data[offset] << + final int t = + data[offset] << (8 - bitDepth * (x % (8 / bitDepth)) - bitDepth).toInt(); imageData![position] = imageData[position] | t.toUnsigned(8); } @@ -569,21 +618,25 @@ class PngDecoder extends ImageDecoder { const double greenY = 0.6; const double bX = 0.15; const double bY = 0.06; - const double t = wpY * + const double t = + wpY * ((greenX - bX) * redY - (redX - bX) * greenY + (redX - greenX) * bY); - const double alphaY = redY * + const double alphaY = + redY * ((greenX - bX) * wpY - (wpX - bX) * greenY + (wpX - greenX) * bY) / t; const double alphaX = alphaY * redX / redY; const double alphaZ = alphaY * ((1 - redX) / redY - 1); - const double blueY = -greenY * + const double blueY = + -greenY * ((redX - bX) * wpY - (wpX - bX) * redY + (wpX - redX) * bY) / t; const double blueX = blueY * greenX / greenY; const double blueZ = blueY * ((1 - greenX) / greenY - 1); - const double colorY = bY * + const double colorY = + bY * ((redX - greenX) * wpY - (wpX - greenX) * wpY + (wpX - redX) * greenY) / @@ -611,7 +664,9 @@ class PngDecoder extends ImageDecoder { calRGB.setProperty(PdfName(PdfDictionaryProperties.matrix), matrix); } calRGB.setProperty( - PdfName(PdfDictionaryProperties.whitePoint), whitePoint); + PdfName(PdfDictionaryProperties.whitePoint), + whitePoint, + ); colorspace.add(PdfName(PdfDictionaryProperties.calRGB)); colorspace.add(calRGB); return colorspace; @@ -621,23 +676,29 @@ class PngDecoder extends ImageDecoder { void _setMask(PdfStream imageStream) { if (_maskData != null && _maskData!.isNotEmpty) { final PdfStream stream = PdfStream(); - stream.dataStream = _maskData; - stream[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.xObject); - stream[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.image); + stream.data = _maskData; + stream[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.xObject, + ); + stream[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.image, + ); stream[PdfDictionaryProperties.width] = PdfNumber(width); stream[PdfDictionaryProperties.height] = PdfNumber(height); if (bitsPerComponent == 16) { stream[PdfDictionaryProperties.bitsPerComponent] = PdfNumber(8); } else { - stream[PdfDictionaryProperties.bitsPerComponent] = - PdfNumber(bitsPerComponent!); + stream[PdfDictionaryProperties.bitsPerComponent] = PdfNumber( + bitsPerComponent!, + ); } - stream[PdfDictionaryProperties.colorSpace] = - PdfName(PdfDictionaryProperties.deviceGray); + stream[PdfDictionaryProperties.colorSpace] = PdfName( + PdfDictionaryProperties.deviceGray, + ); imageStream.setProperty( - PdfName(PdfDictionaryProperties.sMask), PdfReferenceHolder(stream)); + PdfName(PdfDictionaryProperties.sMask), + PdfReferenceHolder(stream), + ); } } @@ -646,8 +707,9 @@ class PngDecoder extends ImageDecoder { decodeParams[PdfDictionaryProperties.columns] = PdfNumber(width); decodeParams[PdfDictionaryProperties.colors] = PdfNumber(_colors); decodeParams[PdfDictionaryProperties.predictor] = PdfNumber(15); - decodeParams[PdfDictionaryProperties.bitsPerComponent] = - PdfNumber(bitsPerComponent!); + decodeParams[PdfDictionaryProperties.bitsPerComponent] = PdfNumber( + bitsPerComponent!, + ); return decodeParams; } @@ -672,34 +734,40 @@ class PngDecoder extends ImageDecoder { @override PdfStream getImageDictionary() { final PdfStream imageStream = PdfStream(); - imageStream.dataStream = _decodedImageData; + imageStream.data = _decodedImageData; if (isDecode && _ideateDecode) { imageStream.compress = true; } else { imageStream.compress = false; } - imageStream[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.xObject); - imageStream[PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.image); + imageStream[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.xObject, + ); + imageStream[PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.image, + ); imageStream[PdfDictionaryProperties.width] = PdfNumber(width); imageStream[PdfDictionaryProperties.height] = PdfNumber(height); if (bitsPerComponent == 16) { imageStream[PdfDictionaryProperties.bitsPerComponent] = PdfNumber(8); } else { - imageStream[PdfDictionaryProperties.bitsPerComponent] = - PdfNumber(bitsPerComponent!); + imageStream[PdfDictionaryProperties.bitsPerComponent] = PdfNumber( + bitsPerComponent!, + ); } if (!isDecode || !_ideateDecode) { - imageStream[PdfDictionaryProperties.filter] = - PdfName(PdfDictionaryProperties.flateDecode); + imageStream[PdfDictionaryProperties.filter] = PdfName( + PdfDictionaryProperties.flateDecode, + ); } if ((_header.colorType & 2) == 0) { - imageStream[PdfDictionaryProperties.colorSpace] = - PdfName(PdfDictionaryProperties.deviceGray); + imageStream[PdfDictionaryProperties.colorSpace] = PdfName( + PdfDictionaryProperties.deviceGray, + ); } else { - imageStream[PdfDictionaryProperties.colorSpace] = - PdfName(PdfDictionaryProperties.deviceRGB); + imageStream[PdfDictionaryProperties.colorSpace] = PdfName( + PdfDictionaryProperties.deviceRGB, + ); } if (!isDecode || _shades && !_ideateDecode) { imageStream[PdfDictionaryProperties.decodeParms] = _getDecodeParams(); @@ -746,7 +814,7 @@ enum _PngChunkTypes { sRGB, iCCP, iTXt, - unknown + unknown, } enum _PngFilterTypes { none, sub, up, average, paeth } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/enum.dart index a381550eb..9c06b0129 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/enum.dart @@ -4,5 +4,5 @@ enum ImageType { jpeg, /// PNG image format - png + png, } 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 2279bb7e4..8743c97f1 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 @@ -178,10 +178,18 @@ class PdfBitmap extends PdfImage { final ImageDecoder? decoder = ImageDecoder.getDecoder(imageData); if (decoder != null) { _decoder = decoder; - _height = _decoder!.height; - _width = _decoder!.width; + if (_decoder!.jpegDecoderOrientationAngle == 90 || + _decoder!.jpegDecoderOrientationAngle == 270) { + _height = _decoder!.width; + _width = _decoder!.height; + } else { + _height = _decoder!.height; + _width = _decoder!.width; + } PdfImageHelper.setJpegOrientationAngle( - this, _decoder!.jpegDecoderOrientationAngle); + this, + _decoder!.jpegDecoderOrientationAngle, + ); _imageStatus = false; } else { throw UnsupportedError('Invalid/Unsupported image stream'); @@ -203,26 +211,44 @@ class PdfBitmap extends PdfImage { } switch (_helper.colorSpace) { case PdfColorSpace.cmyk: - stream[PdfDictionaryProperties.decode] = - PdfArray([1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0]); - stream[PdfDictionaryProperties.colorSpace] = - PdfName(PdfDictionaryProperties.deviceCMYK); + stream[PdfDictionaryProperties.decode] = PdfArray([ + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + ]); + stream[PdfDictionaryProperties.colorSpace] = PdfName( + PdfDictionaryProperties.deviceCMYK, + ); break; case PdfColorSpace.grayScale: stream[PdfDictionaryProperties.decode] = PdfArray([0.0, 1.0]); - stream[PdfDictionaryProperties.colorSpace] = - PdfName(PdfDictionaryProperties.deviceGray); + stream[PdfDictionaryProperties.colorSpace] = PdfName( + PdfDictionaryProperties.deviceGray, + ); break; case PdfColorSpace.rgb: - stream[PdfDictionaryProperties.decode] = - PdfArray([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]); - stream[PdfDictionaryProperties.colorSpace] = - PdfName(PdfDictionaryProperties.deviceRGB); + stream[PdfDictionaryProperties.decode] = PdfArray([ + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + ]); + stream[PdfDictionaryProperties.colorSpace] = PdfName( + PdfDictionaryProperties.deviceRGB, + ); break; case PdfColorSpace.indexed: stream[PdfDictionaryProperties.colorSpace] = (_decoder! as PngDecoder).colorSpace; break; + // ignore: no_default_cases default: break; } @@ -250,7 +276,9 @@ class PdfBitmapHelper { if (!bitmap._imageStatus) { bitmap._imageStatus = true; PdfImageHelper.setImageStream( - bitmap, bitmap._decoder!.getImageDictionary()); + bitmap, + bitmap._decoder!.getImageDictionary(), + ); if (bitmap._decoder!.format == ImageType.png) { final PngDecoder? decoder = bitmap._decoder as PngDecoder?; if (decoder != null && decoder.isDecode) { @@ -268,8 +296,10 @@ class PdfBitmapHelper { /// internal method void drawInternal(PdfGraphics graphics, PdfRectangle bounds) { - graphics.drawImage(bitmap, - Rect.fromLTWH(0, 0, bitmap._width * 0.75, bitmap._height * 0.75)); + graphics.drawImage( + bitmap, + Rect.fromLTWH(0, 0, bitmap._width * 0.75, bitmap._height * 0.75), + ); } /// internal method 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 cf644436f..1a3316352 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 @@ -12,7 +12,7 @@ import 'enums.dart'; /// //Using PDF color set pen. /// pen: PdfPen(PdfColor(200, 120, 80))); /// //Saves the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -28,7 +28,7 @@ class PdfColor { /// //Using PDF color set pen. /// pen: PdfPen(PdfColor(200, 120, 80))); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -58,7 +58,7 @@ class PdfColor { /// //Using PDF color set pen. /// pen: PdfPen(PdfColor.fromCMYK(200, 120, 80, 40))); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -141,7 +141,7 @@ class PdfColor { /// //Create PDF color. /// pen: PdfPen(PdfColor.empty)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -193,7 +193,7 @@ class PdfColor { /// pen: PdfPen(PdfColor(0, 0, 0)..r = 255), /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -214,7 +214,7 @@ class PdfColor { /// pen: PdfPen(PdfColor(0, 0, 0)..g = 255), /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -235,7 +235,7 @@ class PdfColor { /// pen: PdfPen(PdfColor(0, 0, 0)..b = 255), /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -258,7 +258,7 @@ class PdfColor { /// PdfStandardFont(PdfFontFamily.helvetica, 12), /// pen: PdfPen(color)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -323,6 +323,7 @@ class PdfColor { array.add(PdfNumber(_green / _maxColourChannelValue)); array.add(PdfNumber(_blue / _maxColourChannelValue)); break; + // ignore: no_default_cases default: throw ArgumentError.value('Unsupported colour space.'); } @@ -349,8 +350,10 @@ class PdfColorHelper { } /// internal method - static PdfArray toArray(PdfColor color, - [PdfColorSpace colorSpace = PdfColorSpace.rgb]) { + static PdfArray toArray( + PdfColor color, [ + PdfColorSpace colorSpace = PdfColorSpace.rgb, + ]) { return color._toArray(colorSpace); } 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 426752e71..a9e2d520a 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 @@ -3,6 +3,7 @@ import 'dart:ui'; import '../drawing/drawing.dart'; import '../general/pdf_collection.dart'; +import '../general/windows1252encoding.dart'; import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../io/pdf_stream_writer.dart'; @@ -265,7 +266,9 @@ class PdfGraphics { } else { if (state._graphics != this) { throw ArgumentError.value( - this, 'The graphics state belongs to another graphics object'); + this, + 'The graphics state belongs to another graphics object', + ); } if (_graphicsState.contains(state)) { while (true) { @@ -303,8 +306,14 @@ class PdfGraphics { /// //Dispose the document. /// doc.dispose(); /// ``` - void drawString(String s, PdfFont font, - {PdfPen? pen, PdfBrush? brush, Rect? bounds, PdfStringFormat? format}) { + void drawString( + String s, + PdfFont font, { + PdfPen? pen, + PdfBrush? brush, + Rect? bounds, + PdfStringFormat? format, + }) { PdfRectangle layoutRectangle; if (bounds != null) { layoutRectangle = PdfRectangle.fromRect(bounds); @@ -314,11 +323,17 @@ class PdfGraphics { if (pen == null && brush == null) { brush = PdfSolidBrush(PdfColor(0, 0, 0)); } - _helper.layoutString(s, font, - pen: pen, - brush: brush, - layoutRectangle: layoutRectangle, - format: format); + + s = _normalizeText(font, s); + + _helper.layoutString( + s, + font, + pen: pen, + brush: brush, + layoutRectangle: layoutRectangle, + format: format, + ); } /// Draws a line connecting the two points specified by the coordinate pairs. @@ -343,8 +358,9 @@ class PdfGraphics { _helper.streamWriter!.appendLineSegment(point2.dx, point2.dy); _helper.streamWriter!.strokePath(); _helper.endMarkContent(); - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.pdf); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.pdf, + ); } /// Draws a rectangle specified by a pen, a brush and a Rect structure. @@ -366,12 +382,17 @@ class PdfGraphics { void drawRectangle({PdfPen? pen, PdfBrush? brush, required Rect bounds}) { _helper._beginMarkContent(); _helper._stateControl(pen, brush, null, null); - _helper.streamWriter! - .appendRectangle(bounds.left, bounds.top, bounds.width, bounds.height); + _helper.streamWriter!.appendRectangle( + bounds.left, + bounds.top, + bounds.width, + bounds.height, + ); _drawPath(pen, brush, PdfFillMode.winding, false); _helper.endMarkContent(); - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.pdf); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.pdf, + ); } /// Draws a template at the specified location and size. @@ -434,8 +455,11 @@ class PdfGraphics { /// //Dispose the document. /// doc.dispose(); /// ``` - void setTransparency(double alpha, - {double? alphaBrush, PdfBlendMode mode = PdfBlendMode.normal}) { + void setTransparency( + double alpha, { + double? alphaBrush, + PdfBlendMode mode = PdfBlendMode.normal, + }) { if (alpha < 0 || alpha > 1) { ArgumentError.value(alpha, 'alpha', 'invalid alpha value'); } @@ -465,12 +489,18 @@ class PdfGraphics { if (bounds != null) { mode ??= PdfFillMode.winding; _helper.streamWriter!.appendRectangle( - bounds.left, bounds.top, bounds.width, bounds.height); + bounds.left, + bounds.top, + bounds.width, + bounds.height, + ); _helper.streamWriter!.clipPath(mode == PdfFillMode.alternate); } else if (path != null) { mode ??= PdfPathHelper.getHelper(path).fillMode; - _buildUpPath(PdfPathHelper.getHelper(path).points, - PdfPathHelper.getHelper(path).pathTypes); + _buildUpPath( + PdfPathHelper.getHelper(path).points, + PdfPathHelper.getHelper(path).pathTypes, + ); _helper.streamWriter!.clipPath(mode == PdfFillMode.alternate); } } @@ -492,15 +522,25 @@ class PdfGraphics { /// //Dispose the document. /// doc.dispose(); /// ``` - void drawBezier(Offset startPoint, Offset firstControlPoint, - Offset secondControlPoint, Offset endPoint, - {PdfPen? pen}) { + void drawBezier( + Offset startPoint, + Offset firstControlPoint, + Offset secondControlPoint, + Offset endPoint, { + PdfPen? pen, + }) { _helper._beginMarkContent(); _helper._stateControl(pen, null, null, null); final PdfStreamWriter sw = _helper.streamWriter!; sw.beginPath(startPoint.dx, startPoint.dy); - sw.appendBezierSegment(firstControlPoint.dx, firstControlPoint.dy, - secondControlPoint.dx, secondControlPoint.dy, endPoint.dx, endPoint.dy); + sw.appendBezierSegment( + firstControlPoint.dx, + firstControlPoint.dy, + secondControlPoint.dx, + secondControlPoint.dy, + endPoint.dx, + endPoint.dy, + ); sw.strokePath(); _helper.endMarkContent(); } @@ -528,8 +568,10 @@ class PdfGraphics { void drawPath(PdfPath path, {PdfPen? pen, PdfBrush? brush}) { _helper._beginMarkContent(); _helper._stateControl(pen, brush, null, null); - _buildUpPath(PdfPathHelper.getHelper(path).points, - PdfPathHelper.getHelper(path).pathTypes); + _buildUpPath( + PdfPathHelper.getHelper(path).points, + PdfPathHelper.getHelper(path).pathTypes, + ); _drawPath(pen, brush, PdfPathHelper.getHelper(path).fillMode, false); _helper.endMarkContent(); } @@ -551,15 +593,28 @@ class PdfGraphics { /// //Dispose the document. /// doc.dispose(); /// ``` - void drawPie(Rect bounds, double startAngle, double sweepAngle, - {PdfPen? pen, PdfBrush? brush}) { + void drawPie( + Rect bounds, + double startAngle, + double sweepAngle, { + PdfPen? pen, + PdfBrush? brush, + }) { if (sweepAngle != 0) { _helper._beginMarkContent(); _helper._stateControl(pen, brush, null, null); - _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, - bounds.top + bounds.height, startAngle, sweepAngle); + _constructArcPath( + bounds.left, + bounds.top, + bounds.left + bounds.width, + bounds.top + bounds.height, + startAngle, + sweepAngle, + ); _helper.streamWriter!.appendLineSegment( - bounds.left + bounds.width / 2, bounds.top + bounds.height / 2); + bounds.left + bounds.width / 2, + bounds.top + bounds.height / 2, + ); _drawPath(pen, brush, PdfFillMode.winding, true); _helper.endMarkContent(); } @@ -585,7 +640,13 @@ class PdfGraphics { _helper._beginMarkContent(); _helper._stateControl(pen, brush, null, null); _constructArcPath( - bounds.left, bounds.top, bounds.right, bounds.bottom, 0, 360); + bounds.left, + bounds.top, + bounds.right, + bounds.bottom, + 0, + 360, + ); _drawPath(pen, brush, PdfFillMode.winding, true); _helper.endMarkContent(); } @@ -606,13 +667,23 @@ class PdfGraphics { /// //Dispose the document. /// doc.dispose(); /// ``` - void drawArc(Rect bounds, double startAngle, double sweepAngle, - {PdfPen? pen}) { + void drawArc( + Rect bounds, + double startAngle, + double sweepAngle, { + PdfPen? pen, + }) { if (sweepAngle != 0) { _helper._beginMarkContent(); _helper._stateControl(pen, null, null, null); - _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, - bounds.top + bounds.height, startAngle, sweepAngle); + _constructArcPath( + bounds.left, + bounds.top, + bounds.left + bounds.width, + bounds.top + bounds.height, + startAngle, + sweepAngle, + ); _drawPath(pen, null, PdfFillMode.winding, false); _helper.endMarkContent(); } @@ -641,12 +712,16 @@ class PdfGraphics { return; } _helper._stateControl(pen, brush, null, null); - _helper.streamWriter! - .beginPath(points.elementAt(0).dx, points.elementAt(0).dy); + _helper.streamWriter!.beginPath( + points.elementAt(0).dx, + points.elementAt(0).dy, + ); for (int i = 1; i < points.length; ++i) { - _helper.streamWriter! - .appendLineSegment(points.elementAt(i).dx, points.elementAt(i).dy); + _helper.streamWriter!.appendLineSegment( + points.elementAt(i).dx, + points.elementAt(i).dy, + ); } _drawPath(pen, brush, PdfFillMode.winding, true); _helper.endMarkContent(); @@ -665,7 +740,7 @@ class PdfGraphics { /// pen: PdfPens.red) /// ..restore(); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -690,17 +765,23 @@ class PdfGraphics { _helper._previousTextScaling = -100.0; _helper.clipBounds = PdfRectangle(0, 0, size.width, size.height); _graphicsState = []; - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.pdf); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.pdf, + ); } void _drawImage(PdfImage image, Rect rectangle) { _helper._beginMarkContent(); final PdfRectangle bounds = PdfRectangle.fromRect( - (rectangle.width <= 0 && rectangle.height <= 0) - ? Rect.fromLTWH(rectangle.left, rectangle.top, image.width * 0.75, - image.height * 0.75) - : rectangle); + (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 = PdfImageHelper.getJpegOrientationAngle(image)!.toInt(); if (angle > 0) { @@ -756,21 +837,30 @@ class PdfGraphics { restore(beforeOrientation); } _helper.endMarkContent(); - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.grayScaleImage); - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.colorImage); - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.indexedImage); - (_helper._getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.text); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.grayScaleImage, + ); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.colorImage, + ); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.indexedImage, + ); + (_helper._getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.text, + ); } void _drawPath( - PdfPen? pen, PdfBrush? brush, PdfFillMode fillMode, bool needClosing) { + PdfPen? pen, + PdfBrush? brush, + PdfFillMode fillMode, + bool needClosing, + ) { final bool isPen = pen != null && PdfColorHelper.getHelper(pen.color).isFilled; - final bool isBrush = brush != null && + final bool isBrush = + brush != null && PdfColorHelper.getHelper((brush as PdfSolidBrush).color).isFilled; final bool isEvenOdd = fillMode == PdfFillMode.alternate; if (isPen && isBrush) { @@ -803,8 +893,8 @@ class PdfGraphics { if (_helper.layer != null && PdfPageHelper.getHelper(_helper.page!).document != null && PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).document!) - .conformanceLevel != + PdfPageHelper.getHelper(_helper.page!).document!, + ).conformanceLevel != PdfConformanceLevel.none && PdfGraphicsHelper.getHelper(template.graphics!)._currentFont != null && (PdfGraphicsHelper.getHelper(template.graphics!)._currentFont @@ -812,54 +902,61 @@ class PdfGraphics { PdfGraphicsHelper.getHelper(template.graphics!)._currentFont is PdfCjkStandardFont)) { throw ArgumentError( - 'All the fonts must be embedded in ${PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_helper.page!).document!).conformanceLevel.toString()} document.'); + 'All the fonts must be embedded in ${PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_helper.page!).document!).conformanceLevel} document.', + ); } else if (_helper.layer != null && PdfPageHelper.getHelper(_helper.page!).document != null && PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).document!) - .conformanceLevel == + PdfPageHelper.getHelper(_helper.page!).document!, + ).conformanceLevel == PdfConformanceLevel.a1b && PdfGraphicsHelper.getHelper(template.graphics!)._currentFont != null && PdfGraphicsHelper.getHelper(template.graphics!)._currentFont is PdfTrueTypeFont) { PdfTrueTypeFontHelper.getHelper( - PdfGraphicsHelper.getHelper(template.graphics!)._currentFont! - as PdfTrueTypeFont) - .fontInternal - .initializeCidSet(); + PdfGraphicsHelper.getHelper(template.graphics!)._currentFont! + as PdfTrueTypeFont, + ).fontInternal.initializeCidSet(); } if ((_helper.layer != null || _helper._documentLayer != null) && PdfTemplateHelper.getHelper(template).isLoadedPageTemplate) { PdfCrossTable? crossTable; if (PdfPageHelper.getHelper(_helper.page!).isLoadedPage) { if (PdfPageHelper.getHelper(_helper.page!).section != null) { - crossTable = PdfDocumentHelper.getHelper(PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).section!) - .document!) - .crossTable; + crossTable = + PdfDocumentHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(_helper.page!).section!, + ).document!, + ).crossTable; } else { - crossTable = PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).document!) - .crossTable; + crossTable = + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_helper.page!).document!, + ).crossTable; } } else { if (PdfPageHelper.getHelper(_helper.page!).section != null) { - crossTable = (PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).section!) - .document != - null) - ? PdfDocumentHelper.getHelper(PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).section!) - .document!) - .crossTable - : PdfDocumentHelper.getHelper(PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).section!) - .pdfDocument!) - .crossTable; + crossTable = + (PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(_helper.page!).section!, + ).document != + null) + ? PdfDocumentHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(_helper.page!).section!, + ).document!, + ).crossTable + : PdfDocumentHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(_helper.page!).section!, + ).pdfDocument!, + ).crossTable; } else { - crossTable = PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(_helper.page!).document!) - .crossTable; + crossTable = + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_helper.page!).document!, + ).crossTable; } } if ((PdfTemplateHelper.getHelper(template).isReadonly) || @@ -867,6 +964,11 @@ class PdfGraphics { PdfTemplateHelper.getHelper(template).cloneResources(crossTable); } } + if (PdfTemplateHelper.getHelper(template).origin.dx > 0 && + PdfTemplateHelper.getHelper(template).origin.dy > 0 && + template.size > size) { + size = template.size; + } final double scaleX = (template.size.width > 0) ? size.width / template.size.width : 1; final double scaleY = @@ -886,17 +988,21 @@ class PdfGraphics { PdfArray? cropBox; PdfArray? mediaBox; if (dictionary[PdfDictionaryProperties.cropBox] is PdfReferenceHolder) { - cropBox = (dictionary[PdfDictionaryProperties.cropBox]! - as PdfReferenceHolder) - .object as PdfArray?; + cropBox = + (dictionary[PdfDictionaryProperties.cropBox]! + as PdfReferenceHolder) + .object + as PdfArray?; } else { cropBox = dictionary[PdfDictionaryProperties.cropBox] as PdfArray?; } if (dictionary[PdfDictionaryProperties.mediaBox] is PdfReferenceHolder) { - mediaBox = (dictionary[PdfDictionaryProperties.mediaBox]! - as PdfReferenceHolder) - .object as PdfArray?; + mediaBox = + (dictionary[PdfDictionaryProperties.mediaBox]! + as PdfReferenceHolder) + .object + as PdfArray?; } else { mediaBox = dictionary[PdfDictionaryProperties.mediaBox] as PdfArray?; } @@ -910,9 +1016,11 @@ class PdfGraphics { PdfArray? mBox; if (dictionary[PdfDictionaryProperties.mediaBox] is PdfReferenceHolder) { - mBox = (dictionary[PdfDictionaryProperties.mediaBox]! - as PdfReferenceHolder) - .object as PdfArray?; + mBox = + (dictionary[PdfDictionaryProperties.mediaBox]! + as PdfReferenceHolder) + .object + as PdfArray?; } else { mBox = dictionary[PdfDictionaryProperties.mediaBox] as PdfArray?; } @@ -933,7 +1041,17 @@ class PdfGraphics { matrix.translate(location.dx, -(location.dy + 0)); } } else { - matrix.translate(location.dx, -(location.dy + size.height)); + if (PdfTemplateHelper.getHelper(template).origin.dx > 0 && + PdfTemplateHelper.getHelper(template).origin.dy > 0 && + location.dx == 0 && + location.dy == 0) { + matrix.translate( + location.dx - PdfTemplateHelper.getHelper(template).origin.dx, + -(location.dy + size.height), + ); + } else { + matrix.translate(location.dx, -(location.dy + size.height)); + } } if (hasScale) { matrix.scale(scaleX, scaleY); @@ -948,20 +1066,29 @@ class PdfGraphics { final PdfGraphics? g = template.graphics; if (g != null) { - for (final Object? fieldInfo in PdfObjectCollectionHelper.getHelper( - PdfGraphicsHelper.getHelper(g).autoFields!) - .list) { + for (final Object? fieldInfo + in PdfObjectCollectionHelper.getHelper( + PdfGraphicsHelper.getHelper(g).autoFields!, + ).list) { if (fieldInfo is PdfAutomaticFieldInfo) { final PdfPoint newLocation = PdfPoint( - fieldInfo.location.x + location.dx, - fieldInfo.location.y + location.dy); + 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; - _helper.autoFields!.add(PdfAutomaticFieldInfo( - fieldInfo.field, newLocation, scalingX, scalingY)); + final double scalingY = + template.size.height == 0 + ? 0 + : size.height / template.size.height; + _helper.autoFields!.add( + PdfAutomaticFieldInfo( + fieldInfo.field, + newLocation, + scalingX, + scalingY, + ), + ); PdfPageHelper.getHelper(_helper.page!).dictionary!.modify(); } } @@ -999,14 +1126,25 @@ class PdfGraphics { case PathPointType.bezier3: Offset? p2, p3; - final Map returnValue = - _getBezierPoints(points, types, i, p2, p3); + final Map returnValue = _getBezierPoints( + points, + types, + i, + p2, + p3, + ); i = returnValue['i'] as int; final List p = returnValue['points'] as List; p2 = p.first; p3 = p.last; _helper.streamWriter!.appendBezierSegment( - point.dx, point.dy, p2.dx, p2.dy, p3.dx, p3.dy); + point.dx, + point.dy, + p2.dx, + p2.dy, + p3.dx, + p3.dy, + ); break; case PathPointType.line: @@ -1016,15 +1154,17 @@ class PdfGraphics { case PathPointType.closeSubpath: _helper.streamWriter!.closePath(); break; - - default: - throw ArgumentError('Incorrect path formation.'); } } } - Map _getBezierPoints(List points, - List types, int i, Offset? p2, Offset? p3) { + Map _getBezierPoints( + List points, + List types, + int i, + Offset? p2, + Offset? p3, + ) { const String errorMsg = 'Malforming path.'; ++i; if (types[i] == PathPointType.bezier3) { @@ -1036,18 +1176,30 @@ class PdfGraphics { throw ArgumentError(errorMsg); } } else { - throw throw ArgumentError(errorMsg); + throw ArgumentError(errorMsg); } return { 'i': i, - 'points': [p2, p3] + 'points': [p2, p3], }; } - void _constructArcPath(double x1, double y1, double x2, double y2, - double startAng, double sweepAngle) { - final List> points = - _getBezierArcPoints(x1, y1, x2, y2, startAng, sweepAngle); + void _constructArcPath( + double x1, + double y1, + double x2, + double y2, + double startAng, + double sweepAngle, + ) { + final List> points = _getBezierArcPoints( + x1, + y1, + x2, + y2, + startAng, + sweepAngle, + ); if (points.isEmpty) { return; } @@ -1055,13 +1207,25 @@ class PdfGraphics { _helper.streamWriter!.beginPath(pt[0], pt[1]); for (int i = 0; i < points.length; ++i) { pt = points.elementAt(i); - _helper.streamWriter! - .appendBezierSegment(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]); - } - } - - static List> _getBezierArcPoints(double x1, double y1, double x2, - double y2, double startAng, double extent) { + _helper.streamWriter!.appendBezierSegment( + pt[2], + pt[3], + pt[4], + pt[5], + pt[6], + pt[7], + ); + } + } + + static List> _getBezierArcPoints( + double x1, + double y1, + double x2, + double y2, + double startAng, + double extent, + ) { if (x1 > x2) { double tmp; tmp = x1; @@ -1108,7 +1272,7 @@ class PdfGraphics { xCen + rx * (cos1 + kappa * sin1), yCen - ry * (sin1 - kappa * cos1), xCen + rx * cos1, - yCen - ry * sin1 + yCen - ry * sin1, ]); } else { pointList.add([ @@ -1119,7 +1283,7 @@ class PdfGraphics { xCen + rx * (cos1 - kappa * sin1), yCen - ry * (sin1 + kappa * cos1), xCen + rx * cos1, - yCen - ry * sin1 + yCen - ry * sin1, ]); } } @@ -1127,10 +1291,26 @@ class PdfGraphics { } PdfTransformationMatrix _getSkewTransform( - double angleX, double angleY, PdfTransformationMatrix input) { + double angleX, + double angleY, + PdfTransformationMatrix input, + ) { input.skew(-angleX, -angleY); return input; } + + String _normalizeText(PdfFont font, String text) { + if (font is PdfStandardFont) { + text = _convert(text); + } + return text; + } + + String _convert(String text) { + final Windows1252Encoding encoding = Windows1252Encoding(); + final List encodedBytes = encoding.getBytes(text); + return String.fromCharCodes(encodedBytes); + } } /// Represents the state of a Graphics object. \ @@ -1207,13 +1387,13 @@ class _TextRenderingMode { static const int clipFlag = 4; } -class _PdfAutomaticFieldInfoCollection extends PdfObjectCollection { +class PdfAutomaticFieldInfoCollection extends PdfObjectCollection { // constructor - _PdfAutomaticFieldInfoCollection() : super() { - _helper = _PdfAutomaticFieldInfoCollectionHelper(this); + PdfAutomaticFieldInfoCollection() : super() { + _helper = PdfAutomaticFieldInfoCollectionHelper(this); } - late _PdfAutomaticFieldInfoCollectionHelper _helper; + late PdfAutomaticFieldInfoCollectionHelper _helper; // implementaion int add(PdfAutomaticFieldInfo fieldInfo) { @@ -1221,10 +1401,10 @@ class _PdfAutomaticFieldInfoCollection extends PdfObjectCollection { } } -class _PdfAutomaticFieldInfoCollectionHelper extends PdfObjectCollectionHelper { +class PdfAutomaticFieldInfoCollectionHelper extends PdfObjectCollectionHelper { // constructor - _PdfAutomaticFieldInfoCollectionHelper(this.base) : super(base); - _PdfAutomaticFieldInfoCollection base; + PdfAutomaticFieldInfoCollectionHelper(this.base) : super(base); + PdfAutomaticFieldInfoCollection base; // implementaion int add(PdfAutomaticFieldInfo fieldInfo) { @@ -1282,7 +1462,7 @@ class PdfGraphicsHelper { PdfBrush? _currentBrush; PdfTransformationMatrix? _transformationMatrix; PdfLayer? _documentLayer; - _PdfAutomaticFieldInfoCollection? _automaticFields; + PdfAutomaticFieldInfoCollection? _automaticFields; Map<_TransparencyData, PdfTransparency>? _trasparencies; Function? _getResources; double? _previousTextScaling; @@ -1290,9 +1470,10 @@ class PdfGraphicsHelper { PdfColorSpace.rgb: 'RGB', PdfColorSpace.cmyk: 'CMYK', PdfColorSpace.grayScale: 'GrayScale', - PdfColorSpace.indexed: 'Indexed' + PdfColorSpace.indexed: 'Indexed', }; PdfPen? _currentPen; + bool _isItalic = false; /// internal property PdfTransformationMatrix get matrix { @@ -1310,8 +1491,8 @@ class PdfGraphicsHelper { } /// Gets the automatic fields. - _PdfAutomaticFieldInfoCollection? get autoFields { - _automaticFields ??= _PdfAutomaticFieldInfoCollection(); + PdfAutomaticFieldInfoCollection? get autoFields { + _automaticFields ??= PdfAutomaticFieldInfoCollection(); return _automaticFields; } @@ -1319,19 +1500,27 @@ class PdfGraphicsHelper { void applyTransparency(double alpha, double alphaBrush, PdfBlendMode mode) { _trasparencies ??= <_TransparencyData, PdfTransparency>{}; PdfTransparency? transparency; - final _TransparencyData transparencyData = - _TransparencyData(alpha, alphaBrush, mode); + final _TransparencyData transparencyData = _TransparencyData( + alpha, + alphaBrush, + mode, + ); if (_trasparencies!.containsKey(transparencyData)) { transparency = _trasparencies![transparencyData]; } if (transparency == null) { - transparency = PdfTransparency(alpha, alphaBrush, mode, - conformance: layer != null && - PdfPageHelper.getHelper(page!).document != null && - PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(page!).document!) - .conformanceLevel == - PdfConformanceLevel.a1b); + transparency = PdfTransparency( + alpha, + alphaBrush, + mode, + conformance: + layer != null && + PdfPageHelper.getHelper(page!).document != null && + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page!).document!, + ).conformanceLevel == + PdfConformanceLevel.a1b, + ); _trasparencies![transparencyData] = transparency; } final PdfResources resources = _getResources!() as PdfResources; @@ -1343,18 +1532,30 @@ class PdfGraphicsHelper { } /// internal method - void layoutString(String s, PdfFont font, - {PdfPen? pen, - PdfBrush? brush, - required PdfRectangle layoutRectangle, - PdfStringFormat? format}) { + void layoutString( + String s, + PdfFont font, { + PdfPen? pen, + PdfBrush? brush, + required PdfRectangle layoutRectangle, + PdfStringFormat? format, + }) { final PdfStringLayouter layouter = PdfStringLayouter(); PdfStringLayoutResult result; - result = layouter.layout(s, font, format, - width: layoutRectangle.width, height: layoutRectangle.height); + result = layouter.layout( + s, + font, + format, + width: layoutRectangle.width, + height: layoutRectangle.height, + ); if (!result.isEmpty) { final PdfRectangle rectangle = checkCorrectLayoutRectangle( - result.size, layoutRectangle.x, layoutRectangle.y, format); + result.size, + layoutRectangle.x, + layoutRectangle.y, + format, + ); if (layoutRectangle.width <= 0) { layoutRectangle.x = rectangle.x; layoutRectangle.width = rectangle.width; @@ -1368,44 +1569,95 @@ class PdfGraphicsHelper { } drawStringLayoutResult(result, font, pen, brush, layoutRectangle, format); stringLayoutResult = result; - (_getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.text); + (_getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.text, + ); } } /// internal method void drawStringLayoutResult( - PdfStringLayoutResult result, - PdfFont font, - PdfPen? pen, - PdfBrush? brush, - PdfRectangle layoutRectangle, - PdfStringFormat? format) { + PdfStringLayoutResult result, + PdfFont font, + PdfPen? pen, + PdfBrush? brush, + PdfRectangle layoutRectangle, + PdfStringFormat? format, + ) { if (!result.isEmpty) { _beginMarkContent(); + PdfGraphicsState? gState; + if (font is PdfTrueTypeFont && + PdfTrueTypeFontHelper.getHelper(font).fontInternal.ttfMetrics != + null && + !PdfTrueTypeFontHelper.getHelper( + font, + ).fontInternal.ttfMetrics!.isItalic && + PdfFontHelper.getHelper(font).isItalic) { + gState = base.save(); + _isItalic = true; + } _applyStringSettings(font, pen, brush, format, layoutRectangle); - final double textScaling = format != null - ? PdfStringFormatHelper.getHelper(format).scalingFactor - : 100.0; + final double textScaling = + format != null + ? PdfStringFormatHelper.getHelper(format).scalingFactor + : 100.0; if (textScaling != _previousTextScaling) { streamWriter!.setTextScaling(textScaling); _previousTextScaling = textScaling; } double verticalAlignShift = getTextVerticalAlignShift( - result.size.height, layoutRectangle.height, format); - final PdfTransformationMatrix matrix = PdfTransformationMatrix(); - matrix.translate( + result.size.height, + layoutRectangle.height, + format, + ); + double? height; + if (_isItalic) { + height = + (format == null || format.lineSpacing == 0) + ? font.height + : format.lineSpacing + font.height; + final bool subScript = + format != null && + format.subSuperscript == PdfSubSuperscript.subscript; + final double shift = + subScript + ? height - + (font.height + + PdfFontHelper.getHelper( + font, + ).metrics!.getDescent(format)) + : (height - + PdfFontHelper.getHelper(font).metrics!.getAscent(format)); + base.translateTransform( + layoutRectangle.left + font.size / 5, + layoutRectangle.top - shift + verticalAlignShift, + ); + base.skewTransform(0, -11); + } + if (!_isItalic) { + final PdfTransformationMatrix matrix = PdfTransformationMatrix(); + matrix.translate( layoutRectangle.x, (-(layoutRectangle.y + font.height) - (PdfFontHelper.getHelper(font).metrics!.getDescent(format) > 0 - ? -PdfFontHelper.getHelper(font) - .metrics! - .getDescent(format) - : PdfFontHelper.getHelper(font) - .metrics! - .getDescent(format))) - - verticalAlignShift); - streamWriter!.modifyTransformationMatrix(matrix); + ? -PdfFontHelper.getHelper( + font, + ).metrics!.getDescent(format) + : PdfFontHelper.getHelper( + font, + ).metrics!.getDescent(format))) - + verticalAlignShift, + ); + streamWriter!.modifyTransformationMatrix(matrix); + } else { + streamWriter!.startNextLine(0, 0); + } + if (_isItalic && height != null && height >= font.size) { + streamWriter!.stream!.write(height.toString()); + streamWriter!.stream!.write(PdfOperators.whiteSpace); + streamWriter!.writeOperator(PdfOperators.setTextLeading); + } if (layoutRectangle.height < font.size) { if ((result.size.height - layoutRectangle.height) < (font.size / 2) - 1) { @@ -1414,45 +1666,73 @@ class PdfGraphicsHelper { } _drawLayoutResult(result, font, format, layoutRectangle); if (verticalAlignShift != 0) { - streamWriter! - .startNextLine(0, -(verticalAlignShift - result.lineHeight)); + streamWriter!.startNextLine( + 0, + -(verticalAlignShift - result.lineHeight), + ); } streamWriter!.endText(); + if (gState != null) { + base.restore(gState); + _isItalic = false; + } _underlineStrikeoutText( - pen, brush, result, font, layoutRectangle, format); + pen, + brush, + result, + font, + layoutRectangle, + format, + ); endMarkContent(); } } - void _drawLayoutResult(PdfStringLayoutResult result, PdfFont font, - PdfStringFormat? format, PdfRectangle layoutRectangle) { + void _drawLayoutResult( + PdfStringLayoutResult result, + PdfFont font, + PdfStringFormat? format, + PdfRectangle layoutRectangle, + ) { bool? unicode = false; if (font is PdfTrueTypeFont) { unicode = PdfTrueTypeFontHelper.getHelper(font).unicode; } final List lines = result.lines!; - final double height = (format == null || format.lineSpacing == 0) - ? font.height - : format.lineSpacing + font.height; + 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; - if (line == null || line.isEmpty) { + if ((line == null || line.isEmpty) && !_isItalic) { final double verticalAlignShift = getTextVerticalAlignShift( - result.size.height, layoutRectangle.height, format); + result.size.height, + layoutRectangle.height, + format, + ); final PdfTransformationMatrix matrix = PdfTransformationMatrix(); - double baseline = (-(layoutRectangle.y + font.height) - + double baseline = + (-(layoutRectangle.y + font.height) - PdfFontHelper.getHelper(font).metrics!.getDescent(format)) - verticalAlignShift; baseline -= height * (i + 1); matrix.translate(layoutRectangle.x, baseline); streamWriter!.modifyTransformationMatrix(matrix); } else { - double horizontalAlignShift = - _getHorizontalAlignShift(lineWidth, layoutRectangle.width, format); - final double? lineIndent = - _getLineIndent(lineInfo, format, layoutRectangle, i == 0); + double horizontalAlignShift = _getHorizontalAlignShift( + lineWidth, + layoutRectangle.width, + format, + ); + final double? lineIndent = _getLineIndent( + lineInfo, + format, + layoutRectangle, + i == 0, + ); horizontalAlignShift += (!_rightToLeft(format)) ? lineIndent! : 0; if (horizontalAlignShift != 0) { @@ -1467,20 +1747,33 @@ class PdfGraphicsHelper { } if (i + 1 != lines.length) { - final double verticalAlignShift = getTextVerticalAlignShift( - result.size.height, layoutRectangle.height, format); - final PdfTransformationMatrix matrix = PdfTransformationMatrix(); - double baseline = (-(layoutRectangle.y + font.height) - - PdfFontHelper.getHelper(font).metrics!.getDescent(format)) - - verticalAlignShift; - baseline -= height * (i + 1); - matrix.translate(layoutRectangle.x, baseline); - streamWriter!.modifyTransformationMatrix(matrix); + if (!_isItalic) { + final double verticalAlignShift = getTextVerticalAlignShift( + result.size.height, + layoutRectangle.height, + format, + ); + final PdfTransformationMatrix matrix = PdfTransformationMatrix(); + double baseline = + (-(layoutRectangle.y + font.height) - + PdfFontHelper.getHelper(font).metrics!.getDescent(format)) - + verticalAlignShift; + baseline -= height * (i + 1); + matrix.translate(layoutRectangle.x, baseline); + streamWriter!.modifyTransformationMatrix(matrix); + } else { + //tan(11) = 0.19486, theta value for italic skewAngle (11 degree). + streamWriter!.startNextLine( + font.height * 0.19486 - horizontalAlignShift, + 0, + ); + } } } } - (_getResources!() as PdfResources) - .requireProcset(PdfDictionaryProperties.text); + (_getResources!() as PdfResources).requireProcset( + PdfDictionaryProperties.text, + ); } bool _rightToLeft(PdfStringFormat? format) { @@ -1493,7 +1786,10 @@ class PdfGraphicsHelper { } double _getHorizontalAlignShift( - double? lineWidth, double boundsWidth, PdfStringFormat? format) { + double? lineWidth, + double boundsWidth, + PdfStringFormat? format, + ) { double shift = 0; if (boundsWidth >= 0 && format != null && @@ -1505,23 +1801,32 @@ class PdfGraphicsHelper { case PdfTextAlignment.right: shift = boundsWidth - lineWidth!; break; - default: + case PdfTextAlignment.left: + case PdfTextAlignment.justify: break; } } return shift; } - void _drawAsciiLine(LineInfo lineInfo, PdfRectangle layoutRectangle, - PdfFont font, PdfStringFormat? format) { + void _drawAsciiLine( + LineInfo lineInfo, + PdfRectangle layoutRectangle, + PdfFont font, + PdfStringFormat? format, + ) { _justifyLine(lineInfo, layoutRectangle.width, format); final PdfString str = PdfString(lineInfo.text!); str.isAsciiEncode = true; streamWriter!.showNextLineText(str); } - void _drawCjkString(LineInfo lineInfo, PdfRectangle layoutRectangle, - PdfFont font, PdfStringFormat? format) { + void _drawCjkString( + LineInfo lineInfo, + PdfRectangle layoutRectangle, + PdfFont font, + PdfStringFormat? format, + ) { _justifyLine(lineInfo, layoutRectangle.width, format); final String line = lineInfo.text!; final List str = _getCjkString(line); @@ -1538,8 +1843,9 @@ class PdfGraphicsHelper { if (format != null) { internalFont.setSymbols(line, ttfReader.internalUsedChars); } else { - PdfTrueTypeFontHelper.getHelper(font) - .setSymbols(text, ttfReader.internalUsedChars); + PdfTrueTypeFontHelper.getHelper( + font, + ).setSymbols(text, ttfReader.internalUsedChars); } ttfReader.internalUsedChars = null; final List bytes = PdfString.toUnicodeArray(text); @@ -1547,23 +1853,34 @@ class PdfGraphicsHelper { return text; } - void _drawUnicodeLine(LineInfo lineInfo, PdfRectangle layoutRectangle, - PdfFont font, PdfStringFormat? format) { + void _drawUnicodeLine( + LineInfo lineInfo, + PdfRectangle layoutRectangle, + PdfFont font, + PdfStringFormat? format, + ) { final String? line = lineInfo.text; - final bool useWordSpace = format != null && + final bool useWordSpace = + format != null && (format.wordSpacing != 0 || format.alignment == PdfTextAlignment.justify); final PdfTrueTypeFont ttfFont = font as PdfTrueTypeFont; - final double wordSpacing = - _justifyLine(lineInfo, layoutRectangle.width, format); + final double wordSpacing = _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 Bidi bidi = Bidi(); bidi.isVisualOrder = false; - final String result = bidi.getLogicalToVisualString(txt, - format.textDirection == PdfTextDirection.rightToLeft)['rtlText'] - as String; + final String result = + bidi.getLogicalToVisualString( + txt, + format.textDirection == PdfTextDirection.rightToLeft, + )['rtlText'] + as String; bidi.isVisualOrder = true; final List blocks = []; if (useWordSpace) { @@ -1593,8 +1910,13 @@ class PdfGraphicsHelper { } } - void _drawUnicodeBlocks(List blocks, List words, - PdfTrueTypeFont font, PdfStringFormat? format, double wordSpacing) { + void _drawUnicodeBlocks( + List blocks, + List words, + PdfTrueTypeFont font, + PdfStringFormat? format, + double wordSpacing, + ) { streamWriter!.startNextLine(); double x = 0; double xShift = 0; @@ -1610,7 +1932,7 @@ class PdfGraphicsHelper { } double spaceWidth = PdfTrueTypeFontHelper.getHelper(font).getCharWidth(' ', format) + - wordSpacing; + wordSpacing; final double characterSpacing = format != null ? format.characterSpacing : 0; final double wordSpace = @@ -1648,7 +1970,10 @@ class PdfGraphicsHelper { } dynamic _breakUnicodeLine( - String line, PdfTrueTypeFont ttfFont, List? words) { + String line, + PdfTrueTypeFont ttfFont, + List? words, + ) { words = line.split(' '); final List tokens = []; for (int i = 0; i < words.length; i++) { @@ -1668,7 +1993,10 @@ class PdfGraphicsHelper { } 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; @@ -1683,11 +2011,32 @@ class PdfGraphicsHelper { return tm; } - void _applyStringSettings(PdfFont font, PdfPen? pen, PdfBrush? brush, - PdfStringFormat? format, PdfRectangle bounds) { - final int renderingMode = _getTextRenderingMode(pen, brush, format); + void _applyStringSettings( + PdfFont font, + PdfPen? pen, + PdfBrush? brush, + PdfStringFormat? format, + PdfRectangle bounds, + ) { + int renderingMode = _getTextRenderingMode(pen, brush, format); + bool setLineWidth = false; + if (font is PdfTrueTypeFont && + PdfTrueTypeFontHelper.getHelper(font).fontInternal.ttfMetrics != null && + !PdfTrueTypeFontHelper.getHelper( + font, + ).fontInternal.ttfMetrics!.isBold && + PdfFontHelper.getHelper(font).isBold) { + if (pen == null && brush != null && brush is PdfSolidBrush) { + pen = PdfPen(brush.color); + } + renderingMode = 2; + setLineWidth = true; + } streamWriter!.writeOperator(PdfOperators.beginText); _stateControl(pen, brush, font, format); + if (setLineWidth) { + streamWriter!.setLineWidth(font.size / 30); + } if (renderingMode != base._previousTextRenderingMode) { streamWriter!.setTextRenderingMode(renderingMode); base._previousTextRenderingMode = renderingMode; @@ -1706,14 +2055,19 @@ class PdfGraphicsHelper { } 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 (!PdfPageHelper.getHelper(page!).isLoadedPage && - !PdfDocumentHelper.getHelper(PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(page!).section!) - .pdfDocument!) - .isLoadedDocument) { + !PdfDocumentHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page!).section!, + ).pdfDocument!, + ).isLoadedDocument) { if (_colorSpaceChanged == false) { if (page != null) { base.colorSpace = @@ -1727,10 +2081,11 @@ class PdfGraphicsHelper { } else if (pen != null) { if (layer != null) { if (!PdfPageHelper.getHelper(page!).isLoadedPage && - !PdfDocumentHelper.getHelper(PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(page!).section!) - .pdfDocument!) - .isLoadedDocument) { + !PdfDocumentHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page!).section!, + ).pdfDocument!, + ).isLoadedDocument) { base.colorSpace = PdfPageHelper.getHelper(page!).document!.colorSpace; } } @@ -1744,9 +2099,13 @@ class PdfGraphicsHelper { void _initCurrentColorSpace(PdfColorSpace? colorspace) { if (!_isColorSpaceInitialized) { streamWriter!.setColorSpace( - PdfName('Device${_colorSpaces[base.colorSpace]!}'), true); + PdfName('Device${_colorSpaces[base.colorSpace]!}'), + true, + ); streamWriter!.setColorSpace( - PdfName('Device${_colorSpaces[base.colorSpace]!}'), false); + PdfName('Device${_colorSpaces[base.colorSpace]!}'), + false, + ); _isColorSpaceInitialized = true; } } @@ -1755,16 +2114,28 @@ class PdfGraphicsHelper { if (pen != null) { _currentPen = pen; base.colorSpace = PdfColorSpace.rgb; - PdfPenHelper.getHelper(pen).monitorChanges(_currentPen, streamWriter!, - _getResources, saveState, base.colorSpace, matrix); + PdfPenHelper.getHelper(pen).monitorChanges( + _currentPen, + streamWriter!, + _getResources, + saveState, + base.colorSpace, + matrix, + ); _currentPen = pen; } } void _brushControl(PdfBrush? brush, bool saveState) { if (brush != null) { - PdfBrushHelper.monitorChanges(brush as PdfSolidBrush, _currentBrush, - streamWriter, _getResources, saveState, base.colorSpace); + PdfBrushHelper.monitorChanges( + brush as PdfSolidBrush, + _currentBrush, + streamWriter, + _getResources, + saveState, + base.colorSpace, + ); _currentBrush = brush; brush = null; } @@ -1775,30 +2146,37 @@ class PdfGraphicsHelper { if ((font is PdfStandardFont || font is PdfCjkStandardFont) && layer != null && PdfPageHelper.getHelper(page!).document != null && - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!) - .conformanceLevel != + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page!).document!, + ).conformanceLevel != PdfConformanceLevel.none) { throw ArgumentError( - 'All the fonts must be embedded in ${PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!).conformanceLevel.toString()} document.'); + 'All the fonts must be embedded in ${PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!).conformanceLevel} document.', + ); } else if (font is PdfTrueTypeFont && layer != null && PdfPageHelper.getHelper(page!).document != null && - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page!).document!) - .conformanceLevel == + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page!).document!, + ).conformanceLevel == PdfConformanceLevel.a1b) { PdfTrueTypeFontHelper.getHelper(font).fontInternal.initializeCidSet(); } final PdfSubSuperscript current = format != null ? format.subSuperscript : PdfSubSuperscript.none; - final PdfSubSuperscript privious = currentStringFormat != null - ? currentStringFormat!.subSuperscript - : PdfSubSuperscript.none; + final PdfSubSuperscript privious = + currentStringFormat != null + ? currentStringFormat!.subSuperscript + : PdfSubSuperscript.none; if (saveState || font != _currentFont || current != privious) { final PdfResources resources = _getResources!() as PdfResources; _currentFont = font; currentStringFormat = format; - streamWriter!.setFont(font, resources.getName(font), - PdfFontHelper.getHelper(font).metrics!.getSize(format)!); + streamWriter!.setFont( + font, + resources.getName(font), + PdfFontHelper.getHelper(font).metrics!.getSize(format)!, + ); } } } @@ -1814,9 +2192,11 @@ class PdfGraphicsHelper { if (_documentLayer != null) { if (PdfLayerHelper.getHelper(_documentLayer!).isEndState && PdfLayerHelper.getHelper(_documentLayer!).parentLayer.isNotEmpty) { - for (int i = 0; - i < PdfLayerHelper.getHelper(_documentLayer!).parentLayer.length; - i++) { + for ( + int i = 0; + i < PdfLayerHelper.getHelper(_documentLayer!).parentLayer.length; + i++ + ) { streamWriter!.write('EMC\n'); } } @@ -1828,9 +2208,17 @@ class PdfGraphicsHelper { /// internal method PdfRectangle checkCorrectLayoutRectangle( - PdfSize textSize, double? x, double? y, PdfStringFormat? format) { - final PdfRectangle layoutedRectangle = - PdfRectangle(x!, y!, textSize.width, textSize.width); + PdfSize textSize, + double? x, + double? y, + PdfStringFormat? format, + ) { + final PdfRectangle layoutedRectangle = PdfRectangle( + x!, + y!, + textSize.width, + textSize.width, + ); if (format != null) { switch (format.alignment) { case PdfTextAlignment.center: @@ -1839,7 +2227,8 @@ class PdfGraphicsHelper { case PdfTextAlignment.right: layoutedRectangle.x -= layoutedRectangle.width; break; - default: + case PdfTextAlignment.left: + case PdfTextAlignment.justify: break; } switch (format.lineAlignment) { @@ -1849,7 +2238,7 @@ class PdfGraphicsHelper { case PdfVerticalAlignment.bottom: layoutedRectangle.y -= layoutedRectangle.height; break; - default: + case PdfVerticalAlignment.top: break; } } @@ -1857,24 +2246,34 @@ class PdfGraphicsHelper { } void _underlineStrikeoutText( - PdfPen? pen, - PdfBrush? brush, - PdfStringLayoutResult result, - PdfFont font, - PdfRectangle layoutRectangle, - PdfStringFormat? format) { + PdfPen? pen, + PdfBrush? brush, + PdfStringLayoutResult result, + PdfFont font, + PdfRectangle layoutRectangle, + PdfStringFormat? format, + ) { if (PdfFontHelper.getHelper(font).isUnderline | PdfFontHelper.getHelper(font).isStrikeout) { - final PdfPen? linePen = - _createUnderlineStikeoutPen(pen, brush, font, format); + 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 + + result.size.height, + layoutRectangle.height, + format, + ); + double underlineYOffset = + layoutRectangle.y + verticalShift + PdfFontHelper.getHelper(font).metrics!.getAscent(format) + 1.5 * linePen.width; - double strikeoutYOffset = layoutRectangle.y + + double strikeoutYOffset = + layoutRectangle.y + verticalShift + PdfFontHelper.getHelper(font).metrics!.getHeight(format) / 2 + 1.5 * linePen.width; @@ -1883,9 +2282,16 @@ class PdfGraphicsHelper { final LineInfo lineInfo = lines![i]; final double? lineWidth = lineInfo.width; double horizontalShift = _getHorizontalAlignShift( - lineWidth, layoutRectangle.width, format); - final double? lineIndent = - _getLineIndent(lineInfo, format, layoutRectangle, i == 0); + lineWidth, + layoutRectangle.width, + format, + ); + final double? lineIndent = _getLineIndent( + lineInfo, + format, + layoutRectangle, + i == 0, + ); horizontalShift += (!_rightToLeft(format)) ? lineIndent! : 0; final double x1 = layoutRectangle.x + horizontalShift; final double x2 = @@ -1893,13 +2299,19 @@ class PdfGraphicsHelper { ? x1 + lineWidth! - lineIndent! : x1 + layoutRectangle.width - lineIndent!; if (PdfFontHelper.getHelper(font).isUnderline) { - base.drawLine(linePen, Offset(x1, underlineYOffset), - Offset(x2, underlineYOffset)); + base.drawLine( + linePen, + Offset(x1, underlineYOffset), + Offset(x2, underlineYOffset), + ); underlineYOffset += result.lineHeight; } if (PdfFontHelper.getHelper(font).isStrikeout) { - base.drawLine(linePen, Offset(x1, strikeoutYOffset), - Offset(x2, strikeoutYOffset)); + base.drawLine( + linePen, + Offset(x1, strikeoutYOffset), + Offset(x2, strikeoutYOffset), + ); strikeoutYOffset += result.lineHeight; } } @@ -1908,7 +2320,11 @@ class PdfGraphicsHelper { } PdfPen? _createUnderlineStikeoutPen( - PdfPen? pen, PdfBrush? brush, PdfFont font, PdfStringFormat? format) { + PdfPen? pen, + PdfBrush? brush, + PdfFont font, + PdfStringFormat? format, + ) { final double lineWidth = PdfFontHelper.getHelper(font).metrics!.getSize(format)! / 20; PdfPen? linePen; @@ -1931,8 +2347,8 @@ class PdfGraphicsHelper { 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 || + if (cropX != 0 || + cropY != 0 || base.size.width == cropW || base.size.height == cropH) { base.translateTransform(cropX, -cropH); @@ -1957,7 +2373,11 @@ class PdfGraphicsHelper { this.clipBounds = clipBounds; streamWriter!.writeComment('Clip margins.'); streamWriter!.appendRectangle( - clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height); + clipBounds.x, + clipBounds.y, + clipBounds.width, + clipBounds.height, + ); streamWriter!.closePath(); streamWriter!.clipPath(false); streamWriter!.writeComment('Translate co-ordinate system.'); @@ -1965,14 +2385,28 @@ class PdfGraphicsHelper { } /// internal method - void clipTranslateMargins(double x, double y, double left, double top, - double right, double bottom) { - final PdfRectangle clipArea = PdfRectangle(left, top, - base.size.width - left - right, base.size.height - top - bottom); + void clipTranslateMargins( + double x, + double y, + double left, + double top, + double right, + double bottom, + ) { + final PdfRectangle clipArea = PdfRectangle( + left, + top, + base.size.width - left - right, + base.size.height - top - bottom, + ); clipBounds = clipArea; streamWriter!.writeComment('Clip margins.'); streamWriter!.appendRectangle( - clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height); + clipBounds.x, + clipBounds.y, + clipBounds.width, + clipBounds.height, + ); streamWriter!.closePath(); streamWriter!.clipPath(false); streamWriter!.writeComment('Translate co-ordinate system.'); @@ -1996,10 +2430,11 @@ class PdfGraphicsHelper { in PdfObjectCollectionHelper.getHelper(_automaticFields!).list) { if (fieldInfo is PdfAutomaticFieldInfo) { PdfAutomaticFieldHelper.getHelper(fieldInfo.field).performDraw( - base, - fieldInfo.location, - fieldInfo.scalingX, - fieldInfo.scalingY); + base, + fieldInfo.location, + fieldInfo.scalingX, + fieldInfo.scalingY, + ); } } } @@ -2028,7 +2463,10 @@ class PdfGraphicsHelper { /// internal method double getTextVerticalAlignShift( - double? textHeight, double boundsHeight, PdfStringFormat? format) { + double? textHeight, + double boundsHeight, + PdfStringFormat? format, + ) { double shift = 0; if (boundsHeight >= 0 && format != null && @@ -2037,11 +2475,10 @@ class PdfGraphicsHelper { case PdfVerticalAlignment.middle: shift = (boundsHeight - textHeight!) / 2; break; - case PdfVerticalAlignment.bottom: shift = boundsHeight - textHeight!; break; - default: + case PdfVerticalAlignment.top: break; } } @@ -2049,20 +2486,35 @@ class PdfGraphicsHelper { } /// internal method - Rect getLineBounds(int lineIndex, PdfStringLayoutResult result, PdfFont font, - PdfRectangle layoutRectangle, PdfStringFormat? format) { + Rect getLineBounds( + int lineIndex, + PdfStringLayoutResult result, + PdfFont font, + PdfRectangle layoutRectangle, + PdfStringFormat? format, + ) { PdfRectangle bounds = PdfRectangle.empty; if (!result.isEmpty && lineIndex < result.lineCount && lineIndex >= 0) { final LineInfo line = result.lines![lineIndex]; final double verticalShift = getTextVerticalAlignShift( - result.size.height, layoutRectangle.height, format); + result.size.height, + layoutRectangle.height, + format, + ); final double y = verticalShift + layoutRectangle.y + (result.lineHeight * lineIndex); final double? lineWidth = line.width; - double horizontalShift = - _getHorizontalAlignShift(lineWidth, layoutRectangle.width, format); - final double? lineIndent = - _getLineIndent(line, format, layoutRectangle, lineIndex == 0); + double horizontalShift = _getHorizontalAlignShift( + lineWidth, + layoutRectangle.width, + format, + ); + final double? lineIndent = _getLineIndent( + line, + format, + layoutRectangle, + lineIndex == 0, + ); horizontalShift += (!_rightToLeft(format)) ? lineIndent! : 0; final double x = layoutRectangle.x + horizontalShift; final double width = @@ -2075,19 +2527,28 @@ class PdfGraphicsHelper { return bounds.rect; } - double? _getLineIndent(LineInfo lineInfo, PdfStringFormat? format, - PdfRectangle layoutBounds, bool firstLine) { + double? _getLineIndent( + LineInfo lineInfo, + PdfStringFormat? format, + PdfRectangle layoutBounds, + bool firstLine, + ) { double? lineIndent = 0; - final bool firstParagraphLine = (lineInfo.lineType & + final bool firstParagraphLine = + (lineInfo.lineType & PdfStringLayouter.getLineTypeValue(LineType.firstParagraphLine)!) > 0; if (format != null && firstParagraphLine) { - lineIndent = firstLine - ? PdfStringFormatHelper.getHelper(format).firstLineIndent - : format.paragraphIndent; - lineIndent = (layoutBounds.width > 0) - ? (layoutBounds.width <= lineIndent ? layoutBounds.width : lineIndent) - : lineIndent; + lineIndent = + firstLine + ? PdfStringFormatHelper.getHelper(format).firstLineIndent + : format.paragraphIndent; + lineIndent = + (layoutBounds.width > 0) + ? (layoutBounds.width <= lineIndent + ? layoutBounds.width + : lineIndent) + : lineIndent; } return lineIndent; } @@ -2097,10 +2558,11 @@ class PdfGraphicsHelper { final TtfReader ttfReader = PdfTrueTypeFontHelper.getHelper(font).fontInternal.reader; token = ttfReader.convertString(text); - PdfTrueTypeFontHelper.getHelper(font) - .setSymbols(text, ttfReader.internalUsedChars); + PdfTrueTypeFontHelper.getHelper( + font, + ).setSymbols(text, ttfReader.internalUsedChars); ttfReader.internalUsedChars = null; - final List bytes = PdfString.toUnicodeArray(token, false); + final List bytes = PdfString.toUnicodeArray(token); token = PdfString.byteToString(bytes); return token; } @@ -2112,13 +2574,18 @@ class PdfGraphicsHelper { } double _justifyLine( - LineInfo lineInfo, double boundsWidth, PdfStringFormat? format) { + 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 = - StringTokenizer.getCharacterCount(line, StringTokenizer.spaces); + final int whitespacesCount = StringTokenizer.getCharacterCount( + line, + StringTokenizer.spaces, + ); double wordSpace = 0; if (shouldJustify) { if (hasWordSpacing) { @@ -2134,17 +2601,23 @@ class PdfGraphicsHelper { } bool _shouldJustify( - LineInfo lineInfo, double boundsWidth, PdfStringFormat? format) { + 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 int whitespacesCount = - StringTokenizer.getCharacterCount(line, StringTokenizer.spaces); + final int whitespacesCount = StringTokenizer.getCharacterCount( + line, + StringTokenizer.spaces, + ); final bool hasSpaces = whitespacesCount > 0 && line[0] != StringTokenizer.whiteSpace; - final bool goodLineBreakStyle = (lineInfo.lineType & + final bool goodLineBreakStyle = + (lineInfo.lineType & PdfStringLayouter.getLineTypeValue(LineType.layoutBreak)!) > 0; final bool shouldJustify = @@ -2153,8 +2626,14 @@ class PdfGraphicsHelper { } /// internal method - static List> getBezierArcPoints(double x1, double y1, double x2, - double y2, double startAng, double extent) { + static List> getBezierArcPoints( + double x1, + double y1, + double x2, + double y2, + double startAng, + double extent, + ) { return PdfGraphics._getBezierArcPoints(x1, y1, x2, y2, startAng, extent); } } 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 758f54857..4abe1028f 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 @@ -8,7 +8,7 @@ /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -24,7 +24,7 @@ class PdfMargins { /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -54,7 +54,7 @@ class PdfMargins { /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -79,7 +79,7 @@ class PdfMargins { /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -104,7 +104,7 @@ class PdfMargins { /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -129,7 +129,7 @@ class PdfMargins { /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -150,10 +150,11 @@ class PdfMargins { /// ..pages.add().graphics.drawString( /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); /// //Saves the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` + // ignore: avoid_setters_without_getters set all(double value) { if (!_helper.isPageAdded) { _helper.setMargins(value); 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 893b8516c..2d22693ea 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 @@ -35,11 +35,13 @@ class PdfPen { /// //Close the document. /// doc.dispose(); /// ``` - PdfPen(PdfColor pdfColor, - {double width = 1.0, - PdfDashStyle dashStyle = PdfDashStyle.solid, - PdfLineCap lineCap = PdfLineCap.flat, - PdfLineJoin lineJoin = PdfLineJoin.miter}) { + PdfPen( + PdfColor pdfColor, { + double width = 1.0, + PdfDashStyle dashStyle = PdfDashStyle.solid, + PdfLineCap lineCap = PdfLineCap.flat, + PdfLineJoin lineJoin = PdfLineJoin.miter, + }) { _helper = PdfPenHelper(this); _color = pdfColor; _initialize(width, dashStyle, lineCap, lineJoin); @@ -59,11 +61,13 @@ class PdfPen { /// //Close the document. /// doc.dispose(); /// ``` - PdfPen.fromBrush(PdfBrush brush, - {double width = 1.0, - PdfDashStyle dashStyle = PdfDashStyle.solid, - PdfLineCap lineCap = PdfLineCap.flat, - PdfLineJoin lineJoin = PdfLineJoin.miter}) { + PdfPen.fromBrush( + PdfBrush brush, { + double width = 1.0, + PdfDashStyle dashStyle = PdfDashStyle.solid, + PdfLineCap lineCap = PdfLineCap.flat, + PdfLineJoin lineJoin = PdfLineJoin.miter, + }) { _helper = PdfPenHelper(this); _setBrush(brush); _width = width; @@ -111,7 +115,7 @@ class PdfPen { /// pen: PdfPen(PdfColor(255, 0, 0)), /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -131,7 +135,7 @@ class PdfPen { /// pen: PdfPen(PdfColor(255, 0, 0))..brush = PdfBrushes.green, /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -151,7 +155,7 @@ class PdfPen { /// pen: PdfPen(PdfColor(255, 0, 0))..dashOffset = 0.5, /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -171,7 +175,7 @@ class PdfPen { /// 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(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -179,7 +183,8 @@ class PdfPen { set dashPattern(List value) { if (dashStyle == PdfDashStyle.solid) { UnsupportedError( - 'This operation is not allowed. Set Custom dash style to change the pattern.'); + 'This operation is not allowed. Set Custom dash style to change the pattern.', + ); } _checkImmutability(); _dashPattern = value; @@ -196,7 +201,7 @@ class PdfPen { /// ..dashPattern = [4, 2, 1, 3], /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -217,7 +222,7 @@ class PdfPen { /// ..dashPattern = [4, 2, 1, 3], /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -237,7 +242,7 @@ class PdfPen { /// pen: PdfPen(PdfColor(255, 0, 0), width: 4), /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -258,7 +263,7 @@ class PdfPen { /// ..miterLimit = 2, /// bounds: Rect.fromLTWH(10, 10, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -279,7 +284,7 @@ class PdfPen { /// ..dashPattern = [4, 2, 1, 3], /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Close the document. /// document.dispose(); /// ``` @@ -340,16 +345,16 @@ class PdfPen { case PdfDashStyle.solid: _dashPattern = []; break; - default: - _dashStyle = PdfDashStyle.solid; - _dashPattern = []; - break; } } } - void _initialize(double width, PdfDashStyle dashStyle, PdfLineCap lineCap, - PdfLineJoin lineJoin) { + void _initialize( + double width, + PdfDashStyle dashStyle, + PdfLineCap lineCap, + PdfLineJoin lineJoin, + ) { _width = width; _colorSpace = PdfColorSpace.rgb; _dashOffset = 0; @@ -394,12 +399,13 @@ class PdfPenHelper { /// internal method bool monitorChanges( - PdfPen? currentPen, - PdfStreamWriter streamWriter, - Function? getResources, - bool saveState, - PdfColorSpace? currentColorSpace, - PdfTransformationMatrix? matrix) { + PdfPen? currentPen, + PdfStreamWriter streamWriter, + Function? getResources, + bool saveState, + PdfColorSpace? currentColorSpace, + PdfTransformationMatrix? matrix, + ) { bool diff = false; saveState = true; if (currentPen == null) { 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 2c1345a82..04bf34579 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 @@ -3208,8 +3208,9 @@ class PdfPens { static PdfPen _getPen(KnownColor kColor) { final ColorHelper color = ColorHelper(kColor); - final PdfPen pen = - PdfPenHelper.immutable(PdfColor(color.r, color.g, color.b)); + final PdfPen pen = PdfPenHelper.immutable( + PdfColor(color.r, color.g, color.b, color.a), + ); _pens[kColor] = pen; return pen; } 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 c8ec3f4b1..9054a6838 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 @@ -16,7 +16,7 @@ import 'pdf_transparency.dart'; class PdfResources extends PdfDictionary { //Constructor /// Initializes a new instance of the [PdfResources] class. - PdfResources([PdfDictionary? baseDictionary]) : super(baseDictionary); + PdfResources([super.baseDictionary]); //Fields Map? _resourceNames; @@ -111,11 +111,17 @@ class PdfResources extends PdfDictionary { } } else { PdfDictionary? xObjects; - xObjects = - this[PdfName(PdfDictionaryProperties.xObject)] as PdfDictionary?; + final PdfName xobjectName = PdfName(PdfDictionaryProperties.xObject); + if (this[xobjectName] is PdfReferenceHolder) { + xObjects = + (this[xobjectName] as PdfReferenceHolder?)?.object + as PdfDictionary?; + } else if (this[xobjectName] is PdfDictionary) { + xObjects = this[xobjectName] as PdfDictionary?; + } if (xObjects == null) { xObjects = PdfDictionary(); - this[PdfName(PdfDictionaryProperties.xObject)] = xObjects; + this[xobjectName] = xObjects; } xObjects[name] = PdfReferenceHolder(IPdfWrapper.getElement(resource!)); } @@ -134,7 +140,8 @@ class PdfResources extends PdfDictionary { } if (dictionary != null) { dictionary.items!.forEach( - (PdfName? name, IPdfPrimitive? value) => _addName(name, value)); + (PdfName? name, IPdfPrimitive? value) => _addName(name, value), + ); } } return _resourceNames; 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 6477f5f93..ab629a1d3 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 @@ -32,7 +32,8 @@ class PdfTransformationMatrix { /// internal method void rotate(double angle) { angle = double.parse( - (angle * 3.1415926535897931 / 180).toStringAsExponential(9)); + (angle * 3.1415926535897931 / 180).toStringAsExponential(9), + ); matrix.elements[0] = cos(angle); matrix.elements[1] = sin(angle); matrix.elements[2] = -sin(angle); @@ -41,14 +42,16 @@ class PdfTransformationMatrix { /// internal method void skew(double angleX, double angleY) { - matrix.multiply(Matrix([ - 1, - tan((pi / 180) * angleX), - tan((pi / 180) * angleY), - 1, - 0, - 0 - ])); + matrix.multiply( + Matrix([ + 1, + tan((pi / 180) * angleX), + tan((pi / 180) * angleY), + 1, + 0, + 0, + ]), + ); } /// internal method @@ -118,9 +121,11 @@ class Matrix { (elements[2] * matrix.elements[0]) + (elements[3] * matrix.elements[2]); tempMatrix[3] = (elements[2] * matrix.elements[1]) + (elements[3] * matrix.elements[3]); - tempMatrix[4] = (_offsetX! * matrix.elements[0]) + + tempMatrix[4] = + (_offsetX! * matrix.elements[0]) + (_offsetY! * matrix.elements[2] + matrix._offsetX!); - tempMatrix[5] = (_offsetX! * matrix.elements[1]) + + 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 0defac1a8..087cec332 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 @@ -9,16 +9,26 @@ import 'enums.dart'; class PdfTransparency implements IPdfWrapper { //Constructor /// internal constructor - PdfTransparency(double stroke, double fill, PdfBlendMode mode, - {bool conformance = false}) { + PdfTransparency( + double stroke, + double fill, + PdfBlendMode mode, { + bool conformance = false, + }) { dictionary = PdfDictionary(); if (stroke < 0) { throw ArgumentError.value( - stroke, 'stroke', 'The value cannot be less then zero.'); + stroke, + 'stroke', + 'The value cannot be less then zero.', + ); } if (fill < 0) { throw ArgumentError.value( - fill, 'fill', 'The value cannot be less then zero.'); + fill, + 'fill', + 'The value cannot be less then zero.', + ); } //NOTE : This is needed to attain PDF/A conformance. Since PDF/A1B //does not support transparency key. @@ -85,7 +95,7 @@ class PdfTransparency implements IPdfWrapper { return 'Color'; case PdfBlendMode.luminosity: return 'Luminosity'; - default: + case PdfBlendMode.normal: return 'Normal'; } } 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 843e04a38..b3397f55c 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 @@ -37,7 +37,7 @@ class BigEndianWriter { void writeShort(int value) { final List bytes = [ (value & 0x0000ff00) >> 8, - value & 0x000000ff + value & 0x000000ff, ]; _flush(bytes); } @@ -56,7 +56,7 @@ class BigEndianWriter { (value & 0xff000000) >> 24, (value & 0x00ff0000) >> 16, (value & 0x0000ff00) >> 8, - value & 0x000000ff + value & 0x000000ff, ]; _flush(bytes); } @@ -67,7 +67,7 @@ class BigEndianWriter { (value & 0xff000000) >> 24, (value & 0x00ff0000) >> 16, (value & 0x0000ff00) >> 8, - value & 0x000000ff + value & 0x000000ff, ]; _flush(buff); } 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 75ba388fa..08324b35a 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 @@ -7,6 +7,7 @@ import '../primitives/pdf_number.dart'; import '../primitives/pdf_reference.dart'; import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_stream.dart'; +import '../primitives/pdf_string.dart'; import '../security/pdf_encryptor.dart'; import 'enums.dart'; import 'pdf_cross_table.dart'; @@ -26,6 +27,13 @@ class CrossTable { _initialize(); } + /// internal constructor + CrossTable.fromFdf(List docStream, PdfCrossTable crossTable) { + _data = docStream; + _crossTable = crossTable; + objects = {}; + } + //Fields late List _data; late PdfCrossTable _crossTable; @@ -111,7 +119,9 @@ class CrossTable { final int startingOffset = _checkJunk(); if (startingOffset < 0) { ArgumentError.value( - startingOffset, 'Could not find valid signature (%PDF-)'); + startingOffset, + 'Could not find valid signature (%PDF-)', + ); } objects = {}; PdfReader reader = this.reader; @@ -148,8 +158,9 @@ class CrossTable { startCrossReference = position; _parser!.setOffset(position); if (_whiteSpace != 0) { - final int crossReferencePosition = - reader.searchForward(PdfOperators.crossReference); + final int crossReferencePosition = reader.searchForward( + PdfOperators.crossReference, + ); if (crossReferencePosition == -1) { isForwardSearch = false; position += _whiteSpace; @@ -190,8 +201,10 @@ class CrossTable { } reader.position = position; try { - final Map tempResult = - parser.parseCrossReferenceTable(objects, this); + final Map tempResult = parser.parseCrossReferenceTable( + objects, + this, + ); trailer = tempResult['object'] as PdfDictionary?; objects = tempResult['objects'] as Map; } catch (e) { @@ -205,9 +218,9 @@ class CrossTable { number.value = number.value! + _whiteSpace; _isStructureAltered = true; } - position = (trailerObj[PdfDictionaryProperties.prev]! as PdfNumber) - .value! - .toInt(); + position = + (trailerObj[PdfDictionaryProperties.prev]! as PdfNumber).value! + .toInt(); final PdfReader tokenReader = PdfReader(_reader!.streamReader.data); tokenReader.position = position; String? token = tokenReader.getNextToken(); @@ -215,12 +228,12 @@ class CrossTable { token = tokenReader.getNextToken(); //check the coditon for valid object number final int? number = int.tryParse(token!); - if (number != null && number >= 0 && number <= 9) { + if (number != null && number >= 0) { token = tokenReader.getNextToken(); if (token == PdfDictionaryProperties.obj) { parser.setOffset(position); - final Map tempResults = - parser.parseCrossReferenceTable(objects, this); + final Map tempResults = parser + .parseCrossReferenceTable(objects, this); trailerObj = tempResults['object'] as PdfDictionary; objects = tempResults['objects'] as Map; parser.setOffset(position); @@ -231,8 +244,8 @@ class CrossTable { break; } else { parser.setOffset(position); - final Map tempResults = - parser.parseCrossReferenceTable(objects, this); + final Map tempResults = parser + .parseCrossReferenceTable(objects, this); trailerObj = tempResults['object'] as PdfDictionary; objects = tempResults['objects'] as Map; if (trailerObj.containsKey(PdfDictionaryProperties.size) && @@ -251,8 +264,11 @@ class CrossTable { for (int i = 0; i < objKey.length; i++) { final int key = objKey[i]; final ObjectInformation info = objects[key]!; - objects[key] = - ObjectInformation(info._offset! + _whiteSpace, null, this); + objects[key] = ObjectInformation( + info._offset! + _whiteSpace, + null, + this, + ); } _isStructureAltered = true; } else if (_whiteSpace != 0 && _whiteSpace > 0 && !_isStructureAltered) { @@ -280,14 +296,24 @@ class CrossTable { } final PdfParser? parser = oi.parser; final int? position = oi.offset; - if (oi._obj != null) { - obj = oi._obj; + if (oi.obj != null) { + obj = oi.obj; } else if (oi._archive == null) { obj = parser!.parseOffset(position!); } else { obj = _getObjectFromPosition(parser!, position!); + if (encryptor != null) { + if (obj is PdfDictionary) { + obj.decrypted = true; + for (final dynamic element in obj.items!.values) { + if (element is PdfString) { + element.isParentDecrypted = true; + } + } + } + } } - oi._obj = obj; + oi.obj = obj; return obj; } else { return pointer; @@ -301,7 +327,9 @@ class CrossTable { /// internal method Map? parseNewTable( - PdfStream? stream, Map? objects) { + PdfStream? stream, + Map? objects, + ) { if (stream == null) { throw ArgumentError.value(stream, 'Invalid format'); } @@ -310,8 +338,12 @@ class CrossTable { int? ssIndex = 0; for (int i = 0; i < subSections.length; i++) { final _SubSection ss = subSections[i]; - final Map result = - _parseWithHashTable(stream, ss, objects, ssIndex); + final Map result = _parseWithHashTable( + stream, + ss, + objects, + ssIndex, + ); ssIndex = result['index'] as int?; objects = result['objects'] as Map?; } @@ -319,10 +351,11 @@ class CrossTable { } Map _parseWithHashTable( - PdfStream stream, - _SubSection subsection, - Map? table, - int? startIndex) { + PdfStream stream, + _SubSection subsection, + Map? table, + int? startIndex, + ) { int? index = startIndex; final IPdfPrimitive? entry = getObject(stream[PdfDictionaryProperties.w]); if (entry is PdfArray) { @@ -352,7 +385,7 @@ class CrossTable { reference[j] = field; } int offset = 0; - _ArchiveInformation? ai; + ArchiveInformation? ai; if (reference[0] == PdfObjectType.normal.index) { if (_whiteSpace != 0) { offset = reference[1] + _whiteSpace; @@ -360,8 +393,7 @@ class CrossTable { offset = reference[1]; } } else if (reference[0] == PdfObjectType.packed.index) { - ai = - _ArchiveInformation(reference[1], reference[2], _retrieveArchive); + ai = ArchiveInformation(reference[1], reference[2], _retrieveArchive); } ObjectInformation? oi; // NOTE: do not store removed objects. @@ -488,8 +520,9 @@ class CrossTable { do { final int length = _data.length - position < 1024 ? (_data.length - position) : 1024; - final String header = - String.fromCharCodes(_data.sublist(position, length)); + final String header = String.fromCharCodes( + _data.sublist(position, length), + ); index = header.indexOf('%PDF-'); position += length; } while (index < 0 && position != _data.length); @@ -519,7 +552,7 @@ class CrossTable { } /// internal method - PdfParser? retrieveParser(_ArchiveInformation? archive) { + PdfParser? retrieveParser(ArchiveInformation? archive) { if (archive == null) { return _parser; } else { @@ -543,17 +576,22 @@ class ObjectInformation { //Constructor /// internal constructor ObjectInformation( - int offset, _ArchiveInformation? arciveInfo, CrossTable? crossTable) { + int offset, + ArchiveInformation? arciveInfo, + CrossTable? crossTable, + ) { _offset = offset; _archive = arciveInfo; _crossTable = crossTable; } //Fields - _ArchiveInformation? _archive; + ArchiveInformation? _archive; PdfParser? _parser; int? _offset; CrossTable? _crossTable; - IPdfPrimitive? _obj; + + /// internal Fields + IPdfPrimitive? obj; //Properties /// internal property @@ -575,8 +613,11 @@ class ObjectInformation { if (archieveNumber != null) { pairs = archieveNumber.value!.toInt(); } - final List indices = - List.filled(pairs * 2, 0, growable: true); + final List indices = List.filled( + pairs * 2, + 0, + growable: true, + ); for (int i = 0; i < pairs; ++i) { PdfNumber? obj = parser.simple() as PdfNumber?; if (obj != null) { @@ -590,7 +631,9 @@ class ObjectInformation { 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 = @@ -604,9 +647,9 @@ class ObjectInformation { } } -class _ArchiveInformation { +class ArchiveInformation { //Constructor - _ArchiveInformation(int archiveNumber, int index, _GetArchive getArchive) { + ArchiveInformation(int archiveNumber, int index, GetArchive getArchive) { _archiveNumber = archiveNumber; _index = index; _getArchive = getArchive; @@ -616,7 +659,7 @@ class _ArchiveInformation { late int _archiveNumber; late int _index; PdfStream? _archive; - late _GetArchive _getArchive; + late GetArchive _getArchive; //Properties PdfStream get archive { @@ -625,7 +668,7 @@ class _ArchiveInformation { } } -typedef _GetArchive = PdfStream Function(int archiveNumber); +typedef GetArchive = PdfStream Function(int archiveNumber); class _SubSection { //constructor 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 7e68d754c..724d63a98 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 @@ -6,12 +6,19 @@ String decodeBigEndian(List? bytes, [int offset = 0, int? length]) { return String.fromCharCodes(result); } -List _convertCodeUnitsToCodePoints(List codeUnits, - [int offset = 0, int? length]) { - final UtfCodeUnitDecoder decoder = - UtfCodeUnitDecoder(ByteRange(codeUnits, offset, length)); - final List codePoints = - List.filled(decoder.byteRange.remaining, 0, growable: true); +List _convertCodeUnitsToCodePoints( + List codeUnits, [ + int offset = 0, + int? length, +]) { + final UtfCodeUnitDecoder decoder = UtfCodeUnitDecoder( + ByteRange(codeUnits, offset, length), + ); + final List codePoints = List.filled( + decoder.byteRange.remaining, + 0, + growable: true, + ); int i = 0; while (decoder.moveNext) { codePoints[i++] = decoder._current!; @@ -177,8 +184,11 @@ class UtfCodeUnitDecoder { /// internal method List encodeBigEndian(String content) { final List codeUnits = _codePointsToCodeUnits(content.codeUnits); - final List encodedBytes = - List.filled(2 * codeUnits.length, 0, growable: true); + 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; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/enums.dart index ba480f31f..e83a88334 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/enums.dart @@ -5,7 +5,7 @@ enum PdfObjectStatus { none, /// internal enumerator - registered + registered, } /// Represents a type of an object. @@ -15,9 +15,9 @@ enum PdfObjectType { /// internal enumerator normal, -// ignore: unused_field + // ignore: unused_field /// internal enumerator - packed + packed, } /// internal enumerator @@ -104,5 +104,5 @@ enum PdfTokenType { hexStringWeirdEscape, /// internal enumerator - whiteSpace + whiteSpace, } 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 8f6197a0e..4f18ef9a1 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 @@ -41,7 +41,20 @@ class PdfObjectInfo { void setReference(PdfReference reference) { if (this.reference != null) { throw ArgumentError.value( - this.reference, 'The object has the reference bound to it.'); + this.reference, + 'The object has the reference bound to it.', + ); + } + this.reference = reference; + } + + /// internal method + Future setReferenceAsync(PdfReference reference) async { + if (this.reference != null) { + throw ArgumentError.value( + this.reference, + 'The object has the reference bound to it.', + ); } this.reference = reference; } 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 9d30fb0e5..366c39dbe 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 @@ -6,6 +6,8 @@ import '../primitives/pdf_name.dart'; import '../primitives/pdf_number.dart'; import '../primitives/pdf_reference.dart'; import '../primitives/pdf_stream.dart'; +import '../security/pdf_encryptor.dart'; +import '../security/pdf_security.dart'; import 'pdf_constants.dart'; import 'pdf_writer.dart'; @@ -16,16 +18,16 @@ class PdfArchiveStream extends PdfStream { PdfArchiveStream(PdfDocument? document) : super() { ArgumentError.notNull('document'); _document = document; - _objects = []; - _objectWriter = PdfWriter(_objects); - _objectWriter!.document = _document; + _objects = PdfBytesBuilder(); _indices = {}; + _objectWriter = PdfWriter(null, _objects); + _objectWriter!.document = _document; } // Fields PdfDocument? _document; IPdfWriter? _objectWriter; - late List _objects; + late PdfBytesBuilder _objects; Map? _indices; late PdfArchiveStreamWriter _writer; @@ -47,12 +49,11 @@ class PdfArchiveStream extends PdfStream { // Implementation @override void save(IPdfWriter? writer) { - final List data = []; - _writer = PdfArchiveStreamWriter(data); + _writer = PdfArchiveStreamWriter([]); _saveIndices(); this[PdfDictionaryProperties.first] = PdfNumber(_writer.position!); _saveObjects(); - dataStream = _writer._buffer; + super.data = _writer._builder!.takeBytes(); this[PdfDictionaryProperties.n] = PdfNumber(_indices!.length); this[PdfDictionaryProperties.type] = PdfName('ObjStm'); super.save(writer); @@ -61,7 +62,7 @@ class PdfArchiveStream extends PdfStream { void _saveIndices() { for (final Object? position in _indices!.keys) { if (position is int) { - _writer = PdfArchiveStreamWriter(_writer._buffer!); + _writer = PdfArchiveStreamWriter(_writer._builder!.takeBytes()); _writer.write(_indices![position]); _writer.write(PdfOperators.whiteSpace); _writer.write(position); @@ -71,14 +72,35 @@ class PdfArchiveStream extends PdfStream { } void _saveObjects() { - _writer._buffer!.addAll(_objects); + _writer._builder!.add(_objects.takeBytes()); } /// internal method void saveObject(IPdfPrimitive obj, PdfReference reference) { final int? position = _objectWriter!.position; _indices![position] = reference.objNum; + final PdfEncryptor encryptor = + PdfSecurityHelper.getHelper(_document!.security).encryptor; + final bool state = encryptor.encrypt; + encryptor.encrypt = false; obj.save(_objectWriter); + encryptor.encrypt = state; + _objectWriter!.write(PdfOperators.newLine); + } + + /// internal method + Future saveObjectAsync( + IPdfPrimitive obj, + PdfReference reference, + ) async { + final int? position = _objectWriter!.position; + _indices![position] = reference.objNum; + final PdfEncryptor encryptor = + PdfSecurityHelper.getHelper(_document!.security).encryptor; + final bool state = encryptor.encrypt; + encryptor.encrypt = false; + obj.save(_objectWriter); + encryptor.encrypt = state; _objectWriter!.write(PdfOperators.newLine); } } @@ -87,24 +109,25 @@ class PdfArchiveStream extends PdfStream { class PdfArchiveStreamWriter extends IPdfWriter { // Constructor /// internal constructor - PdfArchiveStreamWriter(List buffer) { - _buffer = buffer; - length = buffer.length; - position = buffer.length; + PdfArchiveStreamWriter(List? data) { + _builder = PdfBytesBuilder(); + if (data != null) { + _builder!.add(data); + } + length = _builder!.length; + position = _builder!.length; } //Fields - List? _buffer; + PdfBytesBuilder? _builder; @override // ignore: avoid_renaming_method_parameters void write(dynamic data) { if (data is List) { - for (int i = 0; i < data.length; i++) { - _buffer!.add(data[i]); - } - length = _buffer!.length; - position = _buffer!.length; + _builder!.add(data); + length = _builder!.length; + position = _builder!.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_constants.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart index 7325f7016..19a832977 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart @@ -793,6 +793,90 @@ class PdfDictionaryProperties { /// internal field static const String value = 'value'; + + /// internal field + static const String quadPoints = 'QuadPoints'; + + /// internal field + static const String highlight = 'Highlight'; + + /// internal field + static const String underline = 'Underline'; + + /// internal field + static const String strikeOut = 'StrikeOut'; + + /// internal field + static const String squiggly = 'Squiggly'; + + /// internal field + static const String open = 'Open'; + + /// internal field + static const String irt = 'IRT'; + + /// internal field + static const String b = 'B'; + + /// internal field + static const String nm = 'NM'; + + /// internal field + static const String cl = 'CL'; + + /// internal field + static const String ds = 'DS'; + + /// internal field + static const String rc = 'RC'; + + /// internal field + static const String repeat = 'Repeat'; + + /// internal field + static const String overlayText = 'OverlayText'; + + /// internal field + static const String inkList = 'InkList'; + + /// internal field + static const String customData = 'CustomData'; + + /// internal field + static const String sound = 'Sound'; + + /// internal field + static const String rt = 'RT'; + + /// internal field + static const String ss = 'SS'; + + /// internal field + static const String fd = 'FD'; + + /// internal field + static const String targetUnitConversion = 'TargetUnitConversion'; + + /// internal field + static const String dss = 'DSS'; + + /// internal field + static const String ocsps = 'OCSPs'; + + /// internal field + static const String crls = 'CRLs'; + + /// internal field + static const String vri = 'VRI'; + + /// internal field + static const String ocsp = 'OCSP'; + + /// internal field + static const String crl = 'CRL'; + + /// internal field + static const String certs = 'Certs'; } /// Class of string PDF common operators. @@ -903,6 +987,9 @@ class PdfOperators { /// internal field static const String setTextScaling = 'Tz'; + /// internal field + static const String setTextLeading = 'TL'; + /// internal field static const String beginPath = 'm'; 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 9f146c0dc..444bc483c 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 @@ -35,7 +35,7 @@ class PdfCrossTable { PdfCrossTable([PdfDocument? document, List? data]) { if (document != null) { this.document = document; - _objNumbers = Queue(); + objNumbers = Queue(); if (data != null) { _data = data; _initializeCrossTable(); @@ -46,13 +46,23 @@ class PdfCrossTable { } /// internal constructor - PdfCrossTable.fromCatalog(int tableCount, PdfDictionary? encryptionDictionary, - this.pdfDocumentCatalog) { + PdfCrossTable.fromCatalog( + int tableCount, + PdfDictionary? encryptionDictionary, + this.pdfDocumentCatalog, + ) { _storedCount = tableCount; _encryptorDictionary = encryptionDictionary; _bForceNew = true; _isColorSpace = false; } + + /// internal constructor + PdfCrossTable.fromFdf(List docStream) { + _data = docStream; + crossTable = CrossTable.fromFdf(docStream, this); + } + //Fields PdfDocument? _pdfDocument; int _count = 0; @@ -62,8 +72,12 @@ class PdfCrossTable { PdfDictionary? _trailer; Map? _objects = {}; List? _data; - CrossTable? _crossTable; - late Queue _objNumbers; + + /// internal field + CrossTable? crossTable; + + /// internal field + late Queue objNumbers; /// internal field PdfDictionary? pdfDocumentCatalog; @@ -77,6 +91,7 @@ class PdfCrossTable { Map? _mappedReferences; List? _prevRef; late bool _isColorSpace; + bool _isOutlineOrDestination = false; //Properties /// internal property @@ -89,27 +104,30 @@ class PdfCrossTable { /// internal property PdfEncryptor? get encryptor { - return _crossTable == null ? null : _crossTable!.encryptor; + return crossTable?.encryptor; } set encryptor(PdfEncryptor? value) { if (value != null) { - _crossTable!.encryptor = value.clone(); + crossTable!.encryptor = value.clone(); } } /// internal property PdfDictionary? get documentCatalog { - if (pdfDocumentCatalog == null && _crossTable != null) { + if (pdfDocumentCatalog == null && crossTable != null) { pdfDocumentCatalog = - dereference(_crossTable!.documentCatalog) as PdfDictionary?; + dereference(crossTable!.documentCatalog) as PdfDictionary?; } return pdfDocumentCatalog; } /// internal property PdfDictionary? get trailer { - _trailer ??= _crossTable == null ? PdfStream() : _crossTable!.trailer; + _trailer ??= crossTable == null ? PdfStream() : crossTable!.trailer; + if (_trailer!.containsKey('XRefStm')) { + _trailer!.remove('XRefStm'); + } return _trailer; } @@ -117,8 +135,9 @@ class PdfCrossTable { PdfDictionary? get encryptorDictionary { if (_encryptorDictionary == null && trailer!.containsKey(PdfDictionaryProperties.encrypt)) { - final IPdfPrimitive? primitive = - dereference(trailer![PdfDictionaryProperties.encrypt]); + final IPdfPrimitive? primitive = dereference( + trailer![PdfDictionaryProperties.encrypt], + ); if (primitive is PdfDictionary) { _encryptorDictionary = primitive; } @@ -131,8 +150,8 @@ class PdfCrossTable { if (_count == 0) { IPdfPrimitive? obj; PdfNumber? tempCount; - if (_crossTable != null) { - obj = _crossTable!.trailer![PdfDictionaryProperties.size]; + if (crossTable != null) { + obj = crossTable!.trailer![PdfDictionaryProperties.size]; } if (obj != null) { tempCount = dereference(obj) as PdfNumber?; @@ -174,16 +193,29 @@ class PdfCrossTable { //Implementation void _initializeCrossTable() { - _crossTable = CrossTable(_data, this); + crossTable = CrossTable(_data, this); } void _markTrailerReferences() { trailer!.items!.forEach((PdfName? name, IPdfPrimitive? element) { if (element is PdfReferenceHolder) { final PdfReferenceHolder rh = element; - if (!PdfDocumentHelper.getHelper(document!) - .objects - .contains(rh.object!)) { + if (!PdfDocumentHelper.getHelper( + document!, + ).objects.contains(rh.object!)) { + PdfDocumentHelper.getHelper(document!).objects.add(rh.object); + } + } + }); + } + + Future _markTrailerReferencesAsync() async { + trailer!.items!.forEach((PdfName? name, IPdfPrimitive? element) { + if (element is PdfReferenceHolder) { + final PdfReferenceHolder rh = element; + if (!PdfDocumentHelper.getHelper( + document!, + ).objects.contains(rh.object!)) { PdfDocumentHelper.getHelper(document!).objects.add(rh.object); } } @@ -210,11 +242,14 @@ class PdfCrossTable { _registerObject(PdfReference(0, -1), position: 0, isFreeType: true); final int? referencePosition = writer.position; final int prevXRef = - _crossTable == null ? 0 : _crossTable!.startCrossReference; + crossTable == null ? 0 : crossTable!.startCrossReference; if (_isCrossReferenceStream(writer.document)) { PdfReference? xRefReference; final Map returnedValue = _prepareXRefStream( - prevXRef.toDouble(), referencePosition!.toDouble(), xRefReference); + prevXRef.toDouble(), + referencePosition!.toDouble(), + xRefReference, + ); final PdfStream xRefStream = returnedValue['xRefStream']! as PdfStream; xRefReference = returnedValue['reference'] as PdfReference?; xRefStream.blockEncryption = true; @@ -233,6 +268,61 @@ class PdfCrossTable { } } + /// internal method + Future saveAsync(PdfWriter writer) async { + await _saveHeadAsync(writer); + _objects!.clear(); + if (_archives != null) { + _archives!.clear(); + } + _archive = null; + await _markTrailerReferencesAsync(); + await _saveObjectsAsync(writer); + final int saveCount = count; + if (PdfDocumentHelper.getHelper(document!).isLoadedDocument) { + writer.position = writer.length; + } + if (await _isCrossReferenceStreamAsync(writer.document)) { + await _saveArchivesAsync(writer); + } + await _registerObjectAsync( + PdfReference(0, -1), + position: 0, + isFreeType: true, + ); + final int? referencePosition = writer.position; + final int prevXRef = + crossTable == null ? 0 : crossTable!.startCrossReference; + if (await _isCrossReferenceStreamAsync(writer.document)) { + PdfReference? xRefReference; + PdfStream? xRefStream; + await _prepareXRefStreamAsync( + prevXRef.toDouble(), + referencePosition!.toDouble(), + xRefReference, + ).then((Map returnedValue) { + xRefStream = returnedValue['xRefStream']! as PdfStream; + xRefReference = returnedValue['reference'] as PdfReference?; + }); + xRefStream!.blockEncryption = true; + await _doSaveObjectAsync(xRefStream!, xRefReference!, writer); + } else { + await writer.writeAsync(PdfOperators.crossReference).then((_) async { + await writer.writeAsync(PdfOperators.newLine).then((_) async { + await _saveSectionsAsync(writer).then((_) async { + await _saveTrailerAsync(writer, count, prevXRef); + }); + }); + }); + } + await _saveEndAsync(writer, referencePosition); + count = saveCount; + for (int i = 0; i < objectCollection!.count; i++) { + final PdfObjectInfo objectInfo = objectCollection![i]; + objectInfo.object!.isSaving = false; + } + } + void _saveHead(PdfWriter writer) { writer.write('%PDF-'); final String version = _generateFileVersion(writer.document!); @@ -242,6 +332,21 @@ class PdfCrossTable { writer.write(PdfOperators.newLine); } + Future _saveHeadAsync(PdfWriter writer) async { + await writer.writeAsync('%PDF-').then((_) async { + final String version = await _generateFileVersionAsync(writer.document!); + await writer.writeAsync(version).then((_) async { + await writer.writeAsync(PdfOperators.newLine).then((_) async { + await writer.writeAsync([0x25, 0x83, 0x92, 0xfa, 0xfe]).then(( + _, + ) async { + await writer.writeAsync(PdfOperators.newLine); + }); + }); + }); + }); + } + String _generateFileVersion(PdfDocument document) { if (document.fileStructure.version == PdfVersion.version2_0) { const String version = '2.0'; @@ -251,6 +356,15 @@ class PdfCrossTable { } } + Future _generateFileVersionAsync(PdfDocument document) async { + if (document.fileStructure.version == PdfVersion.version2_0) { + const String version = '2.0'; + return version; + } else { + return '1.${document.fileStructure.version.index}'; + } + } + void _saveObjects(PdfWriter writer) { final PdfMainObjectCollection objects = objectCollection!; if (_bForceNew) { @@ -267,7 +381,40 @@ class PdfCrossTable { final PdfReference ref = getReference(obj); objInfo.reference = ref; } - _saveIndirectObject(obj, writer); + bool skip = false; + if (obj is PdfDictionary && obj.isSkip) { + skip = true; + } + if (!skip) { + _saveIndirectObject(obj, writer); + } + } + } + } + + Future _saveObjectsAsync(PdfWriter writer) async { + final PdfMainObjectCollection objects = objectCollection!; + if (_bForceNew) { + count = 1; + _mappedReferences = null; + } + await _setSecurityAsync(); + for (int i = 0; i < objects.count; i++) { + final PdfObjectInfo objInfo = objects[i]; + if (objInfo.modified! || _bForceNew) { + final IPdfPrimitive obj = objInfo.object!; + final IPdfPrimitive? reference = objInfo.reference; + if (reference == null) { + final PdfReference ref = await getReferenceAsync(obj); + objInfo.reference = ref; + } + bool skip = false; + if (obj is PdfDictionary && obj.isSkip) { + skip = true; + } + if (!skip) { + await _saveIndirectObjectAsync(obj, writer); + } } } } @@ -283,16 +430,49 @@ class PdfCrossTable { PdfDocumentHelper.getHelper(document!).objects.add(securityDictionary); securityDictionary.position = -1; } - securityDictionary = PdfSecurityHelper.getHelper(security) - .encryptor - .saveToDictionary(securityDictionary); + securityDictionary = PdfSecurityHelper.getHelper( + security, + ).encryptor.saveToDictionary(securityDictionary); + trailer![PdfDictionaryProperties.id] = + PdfSecurityHelper.getHelper(security).encryptor.fileID; + trailer![PdfDictionaryProperties.encrypt] = PdfReferenceHolder( + securityDictionary, + ); + } else if (!PdfSecurityHelper.getHelper( + security, + ).encryptor.encryptOnlyAttachment) { + if (trailer!.containsKey(PdfDictionaryProperties.encrypt)) { + trailer!.remove(PdfDictionaryProperties.encrypt); + } + if (trailer!.containsKey(PdfDictionaryProperties.id) && + !PdfFileStructureHelper.fileID(document!.fileStructure)) { + trailer!.remove(PdfDictionaryProperties.id); + } + } + } + + Future _setSecurityAsync() async { + final PdfSecurity security = document!.security; + trailer!.encrypt = false; + if (PdfSecurityHelper.getHelper(security).encryptor.encrypt) { + PdfDictionary? securityDictionary = encryptorDictionary; + if (securityDictionary == null) { + securityDictionary = PdfDictionary(); + securityDictionary.encrypt = false; + PdfDocumentHelper.getHelper(document!).objects.add(securityDictionary); + securityDictionary.position = -1; + } + securityDictionary = await PdfSecurityHelper.getHelper( + security, + ).encryptor.saveToDictionaryAsync(securityDictionary); trailer![PdfDictionaryProperties.id] = PdfSecurityHelper.getHelper(security).encryptor.fileID; - trailer![PdfDictionaryProperties.encrypt] = - PdfReferenceHolder(securityDictionary); - } else if (!PdfSecurityHelper.getHelper(security) - .encryptor - .encryptOnlyAttachment) { + trailer![PdfDictionaryProperties.encrypt] = PdfReferenceHolder( + securityDictionary, + ); + } else if (!PdfSecurityHelper.getHelper( + security, + ).encryptor.encryptOnlyAttachment) { if (trailer!.containsKey(PdfDictionaryProperties.encrypt)) { trailer!.remove(PdfDictionaryProperties.encrypt); } @@ -347,6 +527,58 @@ class PdfCrossTable { } } + Future _saveIndirectObjectAsync( + IPdfPrimitive object, + PdfWriter writer, + ) async { + await getReferenceAsync(object).then((PdfReference reference) async { + if (object is PdfCatalog) { + trailer![PdfDictionaryProperties.root] = reference; + //NOTE: This is needed to get PDF/A Conformance. + if (document != null && + PdfDocumentHelper.getHelper(document!).conformanceLevel != + PdfConformanceLevel.none) { + trailer![PdfDictionaryProperties.id] = + PdfSecurityHelper.getHelper(document!.security).encryptor.fileID; + } + } + PdfDocumentHelper.getHelper(document!).currentSavingObject = reference; + bool archive = false; + archive = object is! PdfDictionary || object.archive; + final bool allowedType = + !((object is PdfStream) || !archive || (object is PdfCatalog)); + bool sigFlag = false; + if (object is PdfDictionary && + document!.fileStructure.crossReferenceType == + PdfCrossReferenceType.crossReferenceStream) { + final PdfDictionary dictionary = object; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final PdfName? name = + dictionary[PdfDictionaryProperties.type] as PdfName?; + if (name != null && name.name! == 'Sig') { + sigFlag = true; + } + } + } + if (allowedType && + await _isCrossReferenceStreamAsync(writer.document) && + reference.genNum == 0 && + !sigFlag) { + await _doArchiveObjectAsync(object, reference, writer); + } else { + await _registerObjectAsync(reference, position: writer.position).then(( + _, + ) async { + await _doSaveObjectAsync(object, reference, writer).then((_) { + if (object == _archive) { + _archive = null; + } + }); + }); + } + }); + } + /// internal method PdfReference getReference(IPdfPrimitive? object) { if (object is PdfArchiveStream) { @@ -369,17 +601,16 @@ class PdfCrossTable { if (items!.count > 0 && object.objectCollectionIndex! > 0 && items!.count > object.objectCollectionIndex! - 1) { - final Map result = - PdfDocumentHelper.getHelper(document!) - .objects - .getReference(object, wasNew); + final Map result = PdfDocumentHelper.getHelper( + document!, + ).objects.getReference(object, wasNew); wasNew = result['isNew'] as bool?; reference = result['reference']; } } else { - final Map result = PdfDocumentHelper.getHelper(document!) - .objects - .getReference(object, wasNew); + final Map result = PdfDocumentHelper.getHelper( + document!, + ).objects.getReference(object, wasNew); wasNew = result['isNew'] as bool?; reference = result['reference']; } @@ -394,17 +625,17 @@ class PdfCrossTable { } if (_bForceNew) { if (reference == null) { - int maxObj = (_storedCount > 0) - ? _storedCount++ - : PdfDocumentHelper.getHelper(document!).objects.count; + int maxObj = + (_storedCount > 0) + ? _storedCount++ + : PdfDocumentHelper.getHelper(document!).objects.count; if (maxObj <= 0) { maxObj = -1; _storedCount = 2; } - while (PdfDocumentHelper.getHelper(document!) - .objects - .mainObjectCollection! - .containsKey(maxObj)) { + while (PdfDocumentHelper.getHelper( + document!, + ).objects.mainObjectCollection!.containsKey(maxObj)) { maxObj++; } reference = PdfReference(maxObj, 0); @@ -416,21 +647,20 @@ class PdfCrossTable { } if (reference == null) { int objectNumber = nextObjectNumber; - if (_crossTable != null) { - while (_crossTable!.objects.containsKey(objectNumber)) { + if (crossTable != null) { + while (crossTable!.objects.containsKey(objectNumber)) { objectNumber = nextObjectNumber; } } - if (PdfDocumentHelper.getHelper(document!) - .objects - .mainObjectCollection! - .containsKey(objectNumber)) { + if (PdfDocumentHelper.getHelper( + document!, + ).objects.mainObjectCollection!.containsKey(objectNumber)) { reference = PdfReference(nextObjectNumber, 0); } else { PdfNumber? trailerCount; - if (_crossTable != null) { + if (crossTable != null) { trailerCount = - _crossTable!.trailer![PdfDictionaryProperties.size] as PdfNumber?; + crossTable!.trailer![PdfDictionaryProperties.size] as PdfNumber?; } if (trailerCount != null && objectNumber == trailerCount.value) { reference = PdfReference(nextObjectNumber, 0); @@ -443,27 +673,160 @@ class PdfCrossTable { (object as IPdfChangable).changed = true; } PdfDocumentHelper.getHelper(document!).objects.add(object); - PdfDocumentHelper.getHelper(document!) - .objects - .trySetReference(object, reference); + PdfDocumentHelper.getHelper( + document!, + ).objects.trySetReference(object, reference); + final int tempIndex = + PdfDocumentHelper.getHelper(document!).objects.count - 1; + final int? tempkey = + PdfDocumentHelper.getHelper( + document!, + ).objects.objectCollection![tempIndex].reference!.objNum; + final PdfObjectInfo tempvalue = + PdfDocumentHelper.getHelper( + document!, + ).objects.objectCollection![PdfDocumentHelper.getHelper( + document!, + ).objects.count - + 1]; + PdfDocumentHelper.getHelper( + document!, + ).objects.mainObjectCollection![tempkey] = + tempvalue; + object.position = -1; + } else { + PdfDocumentHelper.getHelper( + document!, + ).objects.trySetReference(object, reference); + } + object.objectCollectionIndex = reference.objNum as int?; + object.status = PdfObjectStatus.none; + } + return reference as PdfReference; + } + + /// internal method + Future getReferenceAsync(IPdfPrimitive? object) async { + if (object is PdfArchiveStream) { + await _findArchiveReferenceAsync(object).then((PdfReference r) { + return r; + }); + } + if (object is PdfReferenceHolder) { + object = object.object; + if (document != null && + !PdfDocumentHelper.getHelper(document!).isLoadedDocument) { + object!.isSaving = true; + } + } + if (object is IPdfWrapper) { + object = IPdfWrapper.getElement(object! as IPdfWrapper); + } + dynamic reference; + bool? wasNew = false; + if (object!.isSaving!) { + if (items!.count > 0 && + object.objectCollectionIndex! > 0 && + items!.count > object.objectCollectionIndex! - 1) { + await PdfDocumentHelper.getHelper(document!).objects + .getReferenceAsync(object, wasNew) + .then((Map result) { + wasNew = result['isNew'] as bool?; + reference = result['reference']; + }); + } + } else { + await PdfDocumentHelper.getHelper(document!).objects + .getReferenceAsync(object, wasNew) + .then((Map result) { + wasNew = result['isNew'] as bool?; + reference = result['reference']; + }); + } + if (reference == null) { + if (object.status == PdfObjectStatus.registered) { + wasNew = false; + } else { + wasNew = true; + } + } else { + wasNew = false; + } + if (_bForceNew) { + if (reference == null) { + int maxObj = + (_storedCount > 0) + ? _storedCount++ + : PdfDocumentHelper.getHelper(document!).objects.count; + if (maxObj <= 0) { + maxObj = -1; + _storedCount = 2; + } + while (PdfDocumentHelper.getHelper( + document!, + ).objects.mainObjectCollection!.containsKey(maxObj)) { + maxObj++; + } + reference = PdfReference(maxObj, 0); + if (wasNew!) { + PdfDocumentHelper.getHelper(document!).objects.add(object, reference); + } + } + reference = await getMappedReferenceAsync(reference); + } + if (reference == null) { + int objectNumber = nextObjectNumber; + if (crossTable != null) { + while (crossTable!.objects.containsKey(objectNumber)) { + objectNumber = nextObjectNumber; + } + } + if (PdfDocumentHelper.getHelper( + document!, + ).objects.mainObjectCollection!.containsKey(objectNumber)) { + reference = PdfReference(nextObjectNumber, 0); + } else { + PdfNumber? trailerCount; + if (crossTable != null) { + trailerCount = + crossTable!.trailer![PdfDictionaryProperties.size] as PdfNumber?; + } + if (trailerCount != null && objectNumber == trailerCount.value) { + reference = PdfReference(nextObjectNumber, 0); + } else { + reference = PdfReference(objectNumber, 0); + } + } + if (wasNew!) { + if (object is IPdfChangable) { + (object as IPdfChangable).changed = true; + } + await PdfDocumentHelper.getHelper(document!).objects.addAsync(object); + await PdfDocumentHelper.getHelper( + document!, + ).objects.trySetReferenceAsync(object, reference); final int tempIndex = PdfDocumentHelper.getHelper(document!).objects.count - 1; - final int? tempkey = PdfDocumentHelper.getHelper(document!) - .objects - .objectCollection![tempIndex] - .reference! - .objNum; + final int? tempkey = + PdfDocumentHelper.getHelper( + document!, + ).objects.objectCollection![tempIndex].reference!.objNum; final PdfObjectInfo tempvalue = - PdfDocumentHelper.getHelper(document!).objects.objectCollection![ - PdfDocumentHelper.getHelper(document!).objects.count - 1]; - PdfDocumentHelper.getHelper(document!) - .objects - .mainObjectCollection![tempkey] = tempvalue; + PdfDocumentHelper.getHelper( + document!, + ).objects.objectCollection![PdfDocumentHelper.getHelper( + document!, + ).objects.count - + 1]; + PdfDocumentHelper.getHelper( + document!, + ).objects.mainObjectCollection![tempkey] = + tempvalue; object.position = -1; } else { - PdfDocumentHelper.getHelper(document!) - .objects - .trySetReference(object, reference); + await PdfDocumentHelper.getHelper( + document!, + ).objects.trySetReferenceAsync(object, reference); } object.objectCollectionIndex = reference.objNum as int?; object.status = PdfObjectStatus.none; @@ -474,9 +837,24 @@ class PdfCrossTable { /// internal method PdfReference getMappedReference(PdfReference reference) { _mappedReferences ??= {}; - PdfReference? mref = _mappedReferences!.containsKey(reference) - ? _mappedReferences![reference] - : null; + PdfReference? mref = + _mappedReferences!.containsKey(reference) + ? _mappedReferences![reference] + : null; + if (mref == null) { + mref = PdfReference(nextObjectNumber, 0); + _mappedReferences![reference] = mref; + } + return mref; + } + + /// internal method + Future getMappedReferenceAsync(PdfReference reference) async { + _mappedReferences ??= {}; + PdfReference? mref = + _mappedReferences!.containsKey(reference) + ? _mappedReferences![reference] + : null; if (mref == null) { mref = PdfReference(nextObjectNumber, 0); _mappedReferences![reference] = mref; @@ -484,26 +862,67 @@ class PdfCrossTable { return mref; } - void _registerObject(PdfReference reference, - {int? position, PdfArchiveStream? archive, bool? isFreeType}) { + void _registerObject( + PdfReference 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()); + } else { + _objects![reference.objNum] = _RegisteredObject.fromArchive( + this, + archive, + reference, + ); + _maxGenNumIndex = max(_maxGenNumIndex, archive!.count.toDouble()); + } + } else { + _objects![reference.objNum] = _RegisteredObject( + position, + reference, + isFreeType, + ); + _maxGenNumIndex = max(_maxGenNumIndex, reference.genNum!.toDouble()); + } + } + + Future _registerObjectAsync( + PdfReference reference, { + int? position, + PdfArchiveStream? archive, + bool? isFreeType, + }) async { if (isFreeType == null) { if (position != null) { _objects![reference.objNum] = _RegisteredObject(position, reference); _maxGenNumIndex = max(_maxGenNumIndex, reference.genNum!.toDouble()); } else { - _objects![reference.objNum] = - _RegisteredObject.fromArchive(this, archive, reference); + _objects![reference.objNum] = _RegisteredObject.fromArchive( + this, + archive, + reference, + ); _maxGenNumIndex = max(_maxGenNumIndex, archive!.count.toDouble()); } } else { - _objects![reference.objNum] = - _RegisteredObject(position, reference, isFreeType); + _objects![reference.objNum] = _RegisteredObject( + position, + reference, + isFreeType, + ); _maxGenNumIndex = max(_maxGenNumIndex, reference.genNum!.toDouble()); } } void _doSaveObject( - IPdfPrimitive object, PdfReference reference, PdfWriter writer) { + IPdfPrimitive object, + PdfReference reference, + PdfWriter writer, + ) { writer.write(reference.objNum); writer.write(PdfOperators.whiteSpace); writer.write(reference.genNum); @@ -518,6 +937,34 @@ class PdfCrossTable { writer.write(PdfOperators.newLine); } + Future _doSaveObjectAsync( + IPdfPrimitive object, + PdfReference reference, + PdfWriter writer, + ) async { + await writer.writeAsync(reference.objNum).then((_) async { + await writer.writeAsync(PdfOperators.whiteSpace).then((_) async { + await writer.writeAsync(reference.genNum).then((_) async { + await writer.writeAsync(PdfOperators.whiteSpace).then((_) async { + await writer.writeAsync(PdfOperators.obj).then((_) async { + await writer.writeAsync(PdfOperators.newLine).then((_) async { + object.save(writer); + if (object is PdfName || + object is PdfNumber || + object is PdfNull) { + await writer.writeAsync(PdfOperators.newLine); + } + await writer.writeAsync(PdfOperators.endobj).then((_) async { + await writer.writeAsync(PdfOperators.newLine); + }); + }); + }); + }); + }); + }); + }); + } + void _saveSections(IPdfWriter writer) { int objectNumber = 0; int? count = 0; @@ -530,6 +977,22 @@ class PdfCrossTable { } while (count != 0); } + Future _saveSectionsAsync(IPdfWriter writer) async { + int objectNumber = 0; + int? count = 0; + do { + await _prepareSubsectionAsync(objectNumber).then(( + Map result, + ) { + count = result['count']; + objectNumber = result['objectNumber']!; + }); + await _saveSubsectionAsync(writer, objectNumber, count!).then((_) { + objectNumber += count!; + }); + } while (count != 0); + } + Map _prepareSubsection(int objectNumber) { int tempCount = 0; int i; @@ -538,12 +1001,48 @@ class PdfCrossTable { total = PdfDocumentHelper.getHelper(document!).objects.count + 1; } if (total < - PdfDocumentHelper.getHelper(document!) - .objects - .maximumReferenceObjectNumber!) { - total = PdfDocumentHelper.getHelper(document!) - .objects - .maximumReferenceObjectNumber!; + PdfDocumentHelper.getHelper( + document!, + ).objects.maximumReferenceObjectNumber!) { + total = + PdfDocumentHelper.getHelper( + document!, + ).objects.maximumReferenceObjectNumber!; + _isIndexOutOfRange = true; + } + if (objectNumber >= total) { + return {'count': tempCount, 'objectNumber': objectNumber}; + } + for (i = objectNumber; i < total; ++i) { + if (_objects!.containsKey(i)) { + break; + } + } + objectNumber = i; + for (; i < total; ++i) { + if (!_objects!.containsKey(i)) { + break; + } + ++tempCount; + } + return {'count': tempCount, 'objectNumber': objectNumber}; + } + + Future> _prepareSubsectionAsync(int objectNumber) async { + int tempCount = 0; + int i; + int total = count; + if (total <= 0) { + total = PdfDocumentHelper.getHelper(document!).objects.count + 1; + } + if (total < + PdfDocumentHelper.getHelper( + document!, + ).objects.maximumReferenceObjectNumber!) { + total = + PdfDocumentHelper.getHelper( + document!, + ).objects.maximumReferenceObjectNumber!; _isIndexOutOfRange = true; } if (objectNumber >= total) { @@ -582,6 +1081,28 @@ class PdfCrossTable { } } + Future _saveSubsectionAsync( + IPdfWriter writer, + int? objectNumber, + int tempCount, + ) async { + if (tempCount <= 0 || objectNumber! >= count && !_isIndexOutOfRange) { + return; + } + + writer.write('$objectNumber $tempCount${PdfOperators.newLine}'); + for (int i = objectNumber; i < objectNumber + tempCount; ++i) { + final _RegisteredObject obj = _objects![i]!; + String value = ''; + if (obj._type == PdfObjectType.free) { + value = await _getItemAsync(obj._offset, 65535, true); + } else { + value = await _getItemAsync(obj._offset, obj._generationNumber!, false); + } + writer.write(value); + } + } + String _getItem(int? offset, int generationNumber, bool isFreeType) { String result = ''; final int offsetLength = 10 - offset.toString().length; @@ -600,7 +1121,37 @@ class PdfCrossTable { result = '${result}0'; } result = '$result$generationNumber '; - result = result + + result = + result + + (isFreeType ? PdfOperators.f : PdfOperators.n) + + PdfOperators.newLine; + return result; + } + + Future _getItemAsync( + int? offset, + int generationNumber, + bool isFreeType, + ) async { + String result = ''; + final int offsetLength = 10 - offset.toString().length; + if (generationNumber <= 0) { + generationNumber = 0; + } + final int generationNumberLength = + (5 - generationNumber.toString().length) <= 0 + ? 0 + : (5 - generationNumber.toString().length); + for (int index = 0; index < offsetLength; index++) { + result = '${result}0'; + } + result = '$result$offset '; + for (int index = 0; index < generationNumberLength; index++) { + result = '${result}0'; + } + result = '$result$generationNumber '; + result = + result + (isFreeType ? PdfOperators.f : PdfOperators.n) + PdfOperators.newLine; return result; @@ -619,10 +1170,40 @@ class PdfCrossTable { trailerDictionary.save(writer); } + Future _saveTrailerAsync( + IPdfWriter writer, + int count, + int prevCrossReference, + ) async { + writer.write(PdfOperators.trailer); + writer.write(PdfOperators.newLine); + PdfDictionary trailerDictionary = trailer!; + if (prevCrossReference != 0) { + trailer![PdfDictionaryProperties.prev] = PdfNumber(prevCrossReference); + } + trailerDictionary[PdfDictionaryProperties.size] = PdfNumber(_count); + trailerDictionary = PdfDictionary(trailerDictionary); + trailerDictionary.encrypt = false; + trailerDictionary.save(writer); + } + void _saveEnd(IPdfWriter writer, int? position) { - writer.write(PdfOperators.newLine + - PdfOperators.startCrossReference + - PdfOperators.newLine); + writer.write( + PdfOperators.newLine + + PdfOperators.startCrossReference + + PdfOperators.newLine, + ); + writer.write(position.toString() + PdfOperators.newLine); + writer.write(PdfOperators.endOfFileMarker); + writer.write(PdfOperators.newLine); + } + + Future _saveEndAsync(IPdfWriter writer, int? position) async { + writer.write( + PdfOperators.newLine + + PdfOperators.startCrossReference + + PdfOperators.newLine, + ); writer.write(position.toString() + PdfOperators.newLine); writer.write(PdfOperators.endOfFileMarker); writer.write(PdfOperators.newLine); @@ -657,10 +1238,10 @@ class PdfCrossTable { result = pointer.object; } else if (pointer is PdfReference) { final PdfReference reference = pointer; - _objNumbers.addLast(pointer); + objNumbers.addLast(pointer); IPdfPrimitive? obj; - if (_crossTable != null) { - obj = _crossTable!.getObject(pointer); + if (crossTable != null) { + obj = crossTable!.getObject(pointer); } else { final int? index = items!.getObjectIndex(reference); if (index == 0) { @@ -700,7 +1281,7 @@ class PdfCrossTable { } } if (pointer is PdfReference) { - _objNumbers.removeLast(); + objNumbers.removeLast(); } return result; } @@ -718,11 +1299,12 @@ class PdfCrossTable { final PdfName type = getObject(objType)! as PdfName; if (type.name == 'Page') { if (!dic.containsKey(PdfDictionaryProperties.kids)) { - if (PdfDocumentHelper.getHelper(_pdfDocument!) - .isLoadedDocument) { - final PdfPage lPage = - PdfPageCollectionHelper.getHelper(_pdfDocument!.pages) - .getPage(dic); + if (PdfDocumentHelper.getHelper( + _pdfDocument!, + ).isLoadedDocument) { + final PdfPage lPage = PdfPageCollectionHelper.getHelper( + _pdfDocument!.pages, + ).getPage(dic); obj = IPdfWrapper.getElement(lPage); final PdfMainObjectCollection items = PdfDocumentHelper.getHelper(_pdfDocument!).objects; @@ -744,12 +1326,28 @@ class PdfCrossTable { bool _isCrossReferenceStream(PdfDocument? document) { ArgumentError.notNull('document'); bool result = false; - if (_crossTable != null) { - if (_crossTable!.trailer is PdfStream) { + if (crossTable != null) { + if (crossTable!.trailer is PdfStream) { + result = true; + } + } else { + result = + document!.fileStructure.crossReferenceType == + PdfCrossReferenceType.crossReferenceStream; + } + return result; + } + + Future _isCrossReferenceStreamAsync(PdfDocument? document) async { + ArgumentError.notNull('document'); + bool result = false; + if (crossTable != null) { + if (crossTable!.trailer is PdfStream) { result = true; } } else { - result = document!.fileStructure.crossReferenceType == + result = + document!.fileStructure.crossReferenceType == PdfCrossReferenceType.crossReferenceStream; } return result; @@ -770,8 +1368,29 @@ class PdfCrossTable { } } + Future _saveArchivesAsync(PdfWriter writer) async { + if (_archives != null) { + for (final _ArchiveInfo ai in _archives!) { + PdfReference? reference = ai._reference; + if (reference == null) { + reference = PdfReference(nextObjectNumber, 0); + ai._reference = reference; + } + PdfDocumentHelper.getHelper(document!).currentSavingObject = reference; + await _registerObjectAsync(reference, position: writer.position).then(( + _, + ) async { + await _doSaveObjectAsync(ai._archive!, reference!, writer); + }); + } + } + } + void _doArchiveObject( - IPdfPrimitive obj, PdfReference reference, PdfWriter writer) { + IPdfPrimitive obj, + PdfReference reference, + PdfWriter writer, + ) { if (_archive == null) { _archive = PdfArchiveStream(document); _saveArchive(writer); @@ -783,14 +1402,40 @@ class PdfCrossTable { } } + Future _doArchiveObjectAsync( + IPdfPrimitive obj, + PdfReference reference, + PdfWriter writer, + ) async { + if (_archive == null) { + _archive = PdfArchiveStream(document); + await _saveArchiveAsync(writer); + } + await _registerObjectAsync(reference, archive: _archive); + await _archive!.saveObjectAsync(obj, reference).then((_) { + if (_archive!.objCount >= 100) { + _archive = null; + } + }); + } + void _saveArchive(PdfWriter writer) { final _ArchiveInfo ai = _ArchiveInfo(null, _archive); _archives ??= <_ArchiveInfo>[]; _archives!.add(ai); } + Future _saveArchiveAsync(PdfWriter writer) async { + final _ArchiveInfo ai = _ArchiveInfo(null, _archive); + _archives ??= <_ArchiveInfo>[]; + _archives!.add(ai); + } + Map _prepareXRefStream( - double prevXRef, double position, PdfReference? reference) { + double prevXRef, + double position, + PdfReference? reference, + ) { PdfStream? xRefStream; xRefStream = _trailer as PdfStream?; if (xRefStream == null) { @@ -823,14 +1468,87 @@ class PdfCrossTable { } } //iw.Flush(); - xRefStream.dataStream = ms; + xRefStream.data = ms; + xRefStream[PdfDictionaryProperties.index] = sectionIndeces; + xRefStream[PdfDictionaryProperties.size] = PdfNumber(this.count); + xRefStream[PdfDictionaryProperties.prev] = PdfNumber(prevXRef); + xRefStream[PdfDictionaryProperties.type] = PdfName('XRef'); + xRefStream[PdfDictionaryProperties.w] = PdfArray(paramsFormat); + if (crossTable != null) { + final PdfDictionary trailer = crossTable!.trailer!; + for (final PdfName? key in trailer.items!.keys) { + final bool contains = xRefStream.containsKey(key); + if (!contains && + key!.name != PdfDictionaryProperties.decodeParms && + key.name != PdfDictionaryProperties.filter) { + xRefStream[key] = trailer[key]; + } + } + } + if (prevXRef == 0 && xRefStream.containsKey(PdfDictionaryProperties.prev)) { + xRefStream.remove(PdfDictionaryProperties.prev); + } + xRefStream.encrypt = false; + return { + 'xRefStream': xRefStream, + 'reference': reference, + }; + } + + Future> _prepareXRefStreamAsync( + double prevXRef, + double position, + PdfReference? reference, + ) async { + PdfStream? xRefStream; + xRefStream = _trailer as PdfStream?; + if (xRefStream == null) { + xRefStream = PdfStream(); + } else { + xRefStream.remove(PdfDictionaryProperties.filter); + xRefStream.remove(PdfDictionaryProperties.decodeParms); + } + final PdfArray sectionIndeces = PdfArray(); + reference = PdfReference(nextObjectNumber, 0); + await _registerObjectAsync(reference, position: position.toInt()); + + double objectNum = 0; + double count = 0; + final List paramsFormat = [1, 8, 1]; + paramsFormat[1] = max( + await _getSizeAsync(position), + await _getSizeAsync(this.count.toDouble()), + ); + paramsFormat[2] = await _getSizeAsync(_maxGenNumIndex); + final List ms = []; + while (true) { + await _prepareSubsectionAsync(objectNum.toInt()).then(( + Map result, + ) { + count = result['count']!.toDouble(); + objectNum = result['objectNumber']!.toDouble(); + }); + if (count <= 0) { + break; + } else { + sectionIndeces.add(PdfNumber(objectNum)); + sectionIndeces.add(PdfNumber(count)); + await _saveSubSectionAsync(ms, objectNum, count, paramsFormat).then(( + _, + ) { + objectNum += count; + }); + } + } + //iw.Flush(); + xRefStream.data = ms; xRefStream[PdfDictionaryProperties.index] = sectionIndeces; xRefStream[PdfDictionaryProperties.size] = PdfNumber(this.count); xRefStream[PdfDictionaryProperties.prev] = PdfNumber(prevXRef); xRefStream[PdfDictionaryProperties.type] = PdfName('XRef'); xRefStream[PdfDictionaryProperties.w] = PdfArray(paramsFormat); - if (_crossTable != null) { - final PdfDictionary trailer = _crossTable!.trailer!; + if (crossTable != null) { + final PdfDictionary trailer = crossTable!.trailer!; for (final PdfName? key in trailer.items!.keys) { final bool contains = xRefStream.containsKey(key); if (!contains && @@ -846,7 +1564,7 @@ class PdfCrossTable { xRefStream.encrypt = false; return { 'xRefStream': xRefStream, - 'reference': reference + 'reference': reference, }; } @@ -873,8 +1591,35 @@ class PdfCrossTable { return size; } + Future _getSizeAsync(double number) async { + int size = 0; + + if (number < 4294967295) { + if (number < 65535) { + if (number < 255) { + size = 1; + } else { + size = 2; + } + } else { + if (number < (65535 | 65535 << 8)) { + size = 3; + } else { + size = 4; + } + } + } else { + size = 8; + } + return size; + } + void _saveSubSection( - List xRefStream, double objectNum, double count, List format) { + List xRefStream, + double objectNum, + double count, + List format, + ) { for (int i = objectNum.toInt(); i < objectNum + count; ++i) { final _RegisteredObject obj = _objects![i]!; xRefStream.add(obj._type!.index.toUnsigned(8)); @@ -894,6 +1639,47 @@ class PdfCrossTable { _saveLong(xRefStream, obj.offset, format[2]); break; + // ignore: no_default_cases + default: + throw ArgumentError('Internal error: Undefined object type.'); + } + } + } + + Future _saveSubSectionAsync( + List xRefStream, + double objectNum, + double count, + List format, + ) async { + 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 PdfObjectType.free: + await _saveLongAsync( + xRefStream, + obj._objectNumber!.toInt(), + format[1], + ); + await _saveLongAsync(xRefStream, obj._generationNumber, format[2]); + break; + + case PdfObjectType.normal: + await _saveLongAsync(xRefStream, obj.offset, format[1]); + await _saveLongAsync(xRefStream, obj._generationNumber, format[2]); + break; + + case PdfObjectType.packed: + await _saveLongAsync( + xRefStream, + obj._objectNumber!.toInt(), + format[1], + ); + await _saveLongAsync(xRefStream, obj.offset, format[2]); + break; + + // ignore: no_default_cases default: throw ArgumentError('Internal error: Undefined object type.'); } @@ -907,6 +1693,17 @@ class PdfCrossTable { } } + Future _saveLongAsync( + List xRefStream, + int? number, + int count, + ) async { + for (int i = count - 1; i >= 0; --i) { + final int b = (number! >> (i << 3) & 255).toUnsigned(8); + xRefStream.add(b); + } + } + PdfReference _findArchiveReference(PdfArchiveStream archive) { int i = 0; late _ArchiveInfo ai; @@ -922,25 +1719,47 @@ class PdfCrossTable { return reference; } + Future _findArchiveReferenceAsync( + PdfArchiveStream archive, + ) async { + int i = 0; + late _ArchiveInfo ai; + for (final int count = _archives!.length; i < count; ++i) { + ai = _archives![i]; + if (ai._archive == archive) { + break; + } + } + PdfReference? reference = ai._reference; + reference ??= PdfReference(nextObjectNumber, 0); + ai._reference = reference; + return reference; + } + void _decrypt(IPdfPrimitive? obj) { if (obj != null) { if (obj is PdfDictionary || obj is PdfStream) { final PdfDictionary dic = obj as PdfDictionary; + _isOutlineOrDestination = _checkForOutlinesAndDestination(dic); if (!dic.decrypted!) { dic.items!.forEach((PdfName? key, IPdfPrimitive? element) { _decrypt(element); }); if (obj is PdfStream) { final PdfStream stream = obj; - if (PdfDocumentHelper.getHelper(document!).isEncrypted && + if (objNumbers.isNotEmpty) { + stream.crossTable = this; + stream.objNumber = objNumbers.last.objNum; + } else if (PdfDocumentHelper.getHelper(document!).isEncrypted && !stream.decrypted! && - _objNumbers.isNotEmpty && + objNumbers.isNotEmpty && encryptor != null && !encryptor!.encryptAttachmentOnly!) { - stream.decrypt(encryptor!, _objNumbers.last.objNum); + stream.decrypt(encryptor!, objNumbers.last.objNum); } } } + _isOutlineOrDestination = false; } else if (obj is PdfArray) { final PdfArray array = obj; for (final IPdfPrimitive? element in array.elements) { @@ -955,14 +1774,40 @@ class PdfCrossTable { _isColorSpace = false; } else if (obj is PdfString) { final PdfString str = obj; - if (!str.decrypted && (!str.isHex! || _isColorSpace)) { + if (!str.decrypted && + (!str.isHex! || _isColorSpace || _isOutlineOrDestination)) { if (PdfDocumentHelper.getHelper(document!).isEncrypted && - _objNumbers.isNotEmpty) { - obj.decrypt(encryptor!, _objNumbers.last.objNum); + objNumbers.isNotEmpty) { + obj.decrypt(encryptor!, objNumbers.last.objNum); + } + } + } + } + } + + /// Checks the dictionary if it is an outline type. + bool _checkForOutlinesAndDestination(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.parent)) { + final IPdfPrimitive? outline = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.parent], + ); + if (outline != null && outline is PdfDictionary) { + if (documentCatalog != null && + documentCatalog!.containsKey(PdfDictionaryProperties.outlines)) { + final IPdfPrimitive? dict = PdfCrossTable.dereference( + documentCatalog![PdfDictionaryProperties.outlines], + ); + if (dict != null && dict is PdfDictionary && dict == outline) { + return true; + } else { + return _checkForOutlinesAndDestination(outline); } } } + } else if (dictionary.containsKey(PdfDictionaryProperties.limits)) { + return true; } + return false; } } @@ -980,8 +1825,11 @@ class _RegisteredObject { } } - _RegisteredObject.fromArchive(PdfCrossTable xrefTable, - PdfArchiveStream? archive, PdfReference reference) { + _RegisteredObject.fromArchive( + PdfCrossTable xrefTable, + PdfArchiveStream? archive, + PdfReference reference, + ) { _xrefTable = xrefTable; _archive = archive; _offset = reference.objNum; 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 50bd547c1..967f7e0f5 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 @@ -68,7 +68,7 @@ class PdfLexer { late bool atBool; /// internal field - late _State lexicalState; + late State lexicalState; /// internal field List stateTrans = [0, 81, 83]; @@ -119,7 +119,7 @@ class PdfLexer { bufferEnd = 0; line = 0; atBool = true; - lexicalState = _State.initial; + lexicalState = State.initial; accept = [ notAccept, noAnchor, @@ -208,14 +208,25 @@ class PdfLexer { notAccept, notAccept, notAccept, - notAccept + notAccept, ]; - cMap = _unpackFromString(1, 258, - '3,17:8,3,11,17,3,4,17:18,3,17:4,1,17:2,7,2,17,26,17,26,28,16,27:10,17:2,5,17,6,17:2,13:6,17:11,35,17:8,14,12,15,17:3,23,30,13,33,21,22,17:2,36,31,17,24,34,32,29,17:2,19,25,18,20,17:2,37,17:2,10,17,10,17:128,8,9,0:2')[0]; - rMap = _unpackFromString(1, 88, - '0,1,2,1:2,3,4,1:2,5,6,7,1:3,8,1:18,9,1,10,11,12,13,14,15,16,17,18,19,20,21,7,8:2,22,23,24,25,13,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57')[0]; - next = _unpackFromString(58, 38, - '1,2,3,4:2,5,37,6,3:3,4,3:2,7,8,9,3,42,3:2,44,10,3:2,46,48,11,50,52,3:2,38,3:2,12,3,54,-1:39,2:3,-1,2:6,-1,2:26,-1:5,13,-1:40,36,-1:37,9:2,-1:2,9:2,-1:3,9:21,-1:23,45,-1:41,11,49,-1:36,15,-1:11,35:3,84,35:33,-1:9,55,-1:34,14,-1:51,85,-1:18,63,17,63:8,64,63:26,-1,30:3,82,30:33,-1:20,56,-1:2,57,-1:33,41,-1:51,58,-1:36,43,-1:29,59,-1:31,47,-1:38,86,-1:3,60,-1:45,16,-1:36,51,-1:28,62,-1:35,53,-1:39,18,-1:52,65,-1:26,66,-1:3,67,-1:33,56,-1:31,87,-1:42,19,-1:35,20,-1:16,55:3,-1,55:6,-1,-1:26,-1,64,39,64,63,64:33,-1:24,69,-1:31,70,-1:49,71,-1:30,72,-1:35,74,-1:35,75,-1:49,21,-1:40,22,-1:40,76,-1:19,23,-1:39,77,-1:35,78,-1:41,79,-1:35,80,-1:50,24,-1:25,25,-1:15,1,26:2,27:2,26,28,26:4,27,40,29,26:7,29:3,26:3,29,26:2,29,26:2,29,26:4,-1:11,30,-1:26,1,31,32,31:4,33,31:4,34,31:25,-1:11,35,-1:50,61,-1:34,68,-1:34,73,-1:19'); + cMap = + _unpackFromString( + 1, + 258, + '3,17:8,3,11,17,3,4,17:18,3,17:4,1,17:2,7,2,17,26,17,26,28,16,27:10,17:2,5,17,6,17:2,13:6,17:11,35,17:8,14,12,15,17:3,23,30,13,33,21,22,17:2,36,31,17,24,34,32,29,17:2,19,25,18,20,17:2,37,17:2,10,17,10,17:128,8,9,0:2', + )[0]; + rMap = + _unpackFromString( + 1, + 88, + '0,1,2,1:2,3,4,1:2,5,6,7,1:3,8,1:18,9,1,10,11,12,13,14,15,16,17,18,19,20,21,7,8:2,22,23,24,25,13,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57', + )[0]; + next = _unpackFromString( + 58, + 38, + '1,2,3,4:2,5,37,6,3:3,4,3:2,7,8,9,3,42,3:2,44,10,3:2,46,48,11,50,52,3:2,38,3:2,12,3,54,-1:39,2:3,-1,2:6,-1,2:26,-1:5,13,-1:40,36,-1:37,9:2,-1:2,9:2,-1:3,9:21,-1:23,45,-1:41,11,49,-1:36,15,-1:11,35:3,84,35:33,-1:9,55,-1:34,14,-1:51,85,-1:18,63,17,63:8,64,63:26,-1,30:3,82,30:33,-1:20,56,-1:2,57,-1:33,41,-1:51,58,-1:36,43,-1:29,59,-1:31,47,-1:38,86,-1:3,60,-1:45,16,-1:36,51,-1:28,62,-1:35,53,-1:39,18,-1:52,65,-1:26,66,-1:3,67,-1:33,56,-1:31,87,-1:42,19,-1:35,20,-1:16,55:3,-1,55:6,-1,-1:26,-1,64,39,64,63,64:33,-1:24,69,-1:31,70,-1:49,71,-1:30,72,-1:35,74,-1:35,75,-1:49,21,-1:40,22,-1:40,76,-1:19,23,-1:39,77,-1:35,78,-1:41,79,-1:35,80,-1:50,24,-1:25,25,-1:15,1,26:2,27:2,26,28,26:4,27,40,29,26:7,29:3,26:3,29,26:2,29,26:2,29,26:4,-1:11,30,-1:26,1,31,32,31:4,33,31:4,34,31:25,-1:11,35,-1:50,61,-1:34,68,-1:34,73,-1:19', + ); } List> _unpackFromString(int size1, int size2, String text) { @@ -226,7 +237,9 @@ class PdfLexer { int commaIndex; String workString; final List> res = List>.generate( - size1, (int i) => List.generate(size2, (int j) => 0)); + size1, + (int i) => List.generate(size2, (int j) => 0), + ); for (int i = 0; i < size1; ++i) { for (int j = 0; j < size2; ++j) { if (sequenceLength != 0) { @@ -269,7 +282,7 @@ class PdfLexer { bufferEnd = 0; line = 0; atBool = true; - lexicalState = _State.initial; + lexicalState = State.initial; } void _markStart() { @@ -363,8 +376,11 @@ class PdfLexer { } int _read() { - final int nextRead = - _reader.readData(buffer, bufferRead, buffer.length - bufferRead); + final int nextRead = _reader.readData( + buffer, + bufferRead, + buffer.length - bufferRead, + ); if (nextRead > 0) { bufferRead += nextRead; } @@ -406,7 +422,8 @@ class PdfLexer { void _toMark() { bufferIndex = bufferEnd; - atBool = (bufferEnd > bufferStart) && + atBool = + (bufferEnd > bufferStart) && (13 == buffer[bufferEnd - 1] || 10 == buffer[bufferEnd - 1] || 2028 == buffer[bufferEnd - 1] || @@ -485,14 +502,14 @@ class PdfLexer { break; case 5: { - _begin(_State.hexString); + _begin(State.hexString); return PdfTokenType.hexStringStart; } case -6: break; case 6: { - _begin(_State.string); + _begin(State.string); stringText = ''; break; } @@ -626,7 +643,7 @@ class PdfLexer { break; case 28: { - _begin(_State.initial); + _begin(State.initial); return PdfTokenType.hexStringEnd; } case -29: @@ -656,7 +673,7 @@ class PdfLexer { stringText += _text(); --paren; } else { - _begin(_State.initial); + _begin(State.initial); return PdfTokenType.string; } break; @@ -865,15 +882,18 @@ class PdfLexer { return String.fromCharCodes(buffer.sublist(bufferStart, bufferEnd)); } - void _begin(_State state) { + // ignore: use_setters_to_change_properties + void _begin(State state) { lexicalState = state; } void _error(_Error code, bool fatal) { if (fatal) { if (objectName != null) { - throw ArgumentError.value(code, - 'Fatal Error occurred at $position.\n When reading object type of ${objectName!}'); + throw ArgumentError.value( + code, + 'Fatal Error occurred at $position.\n When reading object type of ${objectName!}', + ); } else { throw ArgumentError.value(code, 'Fatal Error occurred at $position'); } @@ -881,6 +901,6 @@ class PdfLexer { } } -enum _State { initial, hexString, string } +enum State { initial, hexString, string } enum _Error { internal, match } 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 1d982a93a..8ac908d88 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 @@ -87,6 +87,38 @@ class PdfMainObjectCollection { } } + /// Adds the specified element. + Future addAsync(dynamic element, [PdfReference? reference]) async { + if (element == null) { + throw ArgumentError.value(element, 'element', 'value cannot be null'); + } + if (element is IPdfWrapper) { + element = IPdfWrapper.getElement(element); + } + if (reference == null) { + final PdfObjectInfo info = PdfObjectInfo(element); + objectCollection!.add(info); + if (!_primitiveObjectCollection!.containsKey(element)) { + _primitiveObjectCollection![element] = objectCollection!.length - 1; + } + element.position = objectCollection!.length - 1; + _index = objectCollection!.length - 1; + element.status = PdfObjectStatus.registered; + } else { + final PdfObjectInfo info = PdfObjectInfo(element, reference); + if (maximumReferenceObjectNumber! < reference.objNum!) { + maximumReferenceObjectNumber = reference.objNum; + } + 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; + } + } + /// internal property Map getReference(IPdfPrimitive object, bool? isNew) { _index = lookFor(object); @@ -101,6 +133,23 @@ class PdfMainObjectCollection { return {'isNew': isNew, 'reference': reference}; } + /// internal method + Future> getReferenceAsync( + IPdfPrimitive object, + bool? isNew, + ) async { + _index = await lookForAsync(object); + PdfReference? reference; + if (_index! < 0 || _index! > count) { + isNew = true; + } else { + isNew = false; + final PdfObjectInfo objectInfo = objectCollection![_index!]; + reference = objectInfo.reference; + } + return {'isNew': isNew, 'reference': reference}; + } + /// internal property bool contains(IPdfPrimitive element) { return lookFor(element)! >= 0; @@ -119,8 +168,34 @@ class PdfMainObjectCollection { for (int i = count - 1; i >= 0; i--) { final PdfObjectInfo objectInfo = objectCollection![i]; final IPdfPrimitive? primitive = objectInfo.object; - final bool isValidType = !((primitive is PdfName && obj is! PdfName) || - (primitive is! PdfName && obj is PdfName)); + final bool isValidType = + !((primitive is PdfName && obj is! PdfName) || + (primitive is! PdfName && obj is PdfName)); + if (isValidType && primitive == obj) { + index = i; + break; + } + } + } + return index; + } + + /// internal method + Future lookForAsync(IPdfPrimitive obj) async { + int? index = -1; + if (obj.position != -1) { + return obj.position; + } + if (_primitiveObjectCollection!.containsKey(obj) && + count == _primitiveObjectCollection!.length) { + index = _primitiveObjectCollection![obj]; + } else { + for (int i = count - 1; i >= 0; i--) { + final PdfObjectInfo objectInfo = objectCollection![i]; + final IPdfPrimitive? primitive = objectInfo.object; + final bool isValidType = + !((primitive is PdfName && obj is! PdfName) || + (primitive is! PdfName && obj is PdfName)); if (isValidType && primitive == obj) { index = i; break; @@ -184,6 +259,26 @@ class PdfMainObjectCollection { return result; } + /// internal method + Future trySetReferenceAsync( + IPdfPrimitive object, + PdfReference reference, + ) async { + bool result = true; + _index = await lookForAsync(object); + if (_index! < 0 || _index! >= objectCollection!.length) { + result = false; + } else { + final PdfObjectInfo objectInfo = objectCollection![_index!]; + if (objectInfo.reference != null) { + result = false; + } else { + await objectInfo.setReferenceAsync(reference); + } + } + return result; + } + /// internal property IPdfPrimitive? getObjectFromReference(PdfReference reference) { try { @@ -197,7 +292,31 @@ class PdfMainObjectCollection { void reregisterReference(int oldObjIndex, IPdfPrimitive newObj) { if (oldObjIndex < 0 || oldObjIndex > count) { throw ArgumentError.value( - oldObjIndex, 'oldObjIndex', 'index out of range'); + oldObjIndex, + 'oldObjIndex', + 'index out of range', + ); + } + final PdfObjectInfo oi = objectCollection![oldObjIndex]; + if (oi.object != newObj) { + _primitiveObjectCollection!.remove(oi.object); + _primitiveObjectCollection![newObj] = oldObjIndex; + } + oi.object = newObj; + newObj.position = oldObjIndex; + } + + /// internal method + Future reregisterReferenceAsync( + int oldObjIndex, + IPdfPrimitive newObj, + ) async { + if (oldObjIndex < 0 || oldObjIndex > count) { + throw ArgumentError.value( + oldObjIndex, + 'oldObjIndex', + 'index out of range', + ); } final PdfObjectInfo oi = objectCollection![oldObjIndex]; if (oi.object != newObj) { @@ -208,6 +327,18 @@ class PdfMainObjectCollection { newObj.position = oldObjIndex; } + /// internal method + void remove(int index) { + if (mainObjectCollection != null && + mainObjectCollection!.containsKey(index)) { + if (objectCollection != null && + objectCollection!.contains(mainObjectCollection![index])) { + objectCollection!.remove(mainObjectCollection![index]); + } + mainObjectCollection!.remove(index); + } + } + /// internal property void dispose() { if (mainObjectCollection != null) { 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 2ad56a6ea..b62208810 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 @@ -1,6 +1,8 @@ import 'dart:collection'; import '../../interfaces/pdf_interface.dart'; +import '../annotations/fdf_parser.dart'; +import '../pdf_document/pdf_document.dart'; import '../primitives/pdf_array.dart'; import '../primitives/pdf_boolean.dart'; import '../primitives/pdf_dictionary.dart'; @@ -85,11 +87,17 @@ class PdfParser { ' ', '¡', '¢', - '£' + '£', ]; return _windows1252MapTable; } + /// internal property + PdfTokenType? get next => _next; + + /// internal property + PdfLexer? get lexer => _lexer; + //Implementation /// internal method void setOffset(int offset) { @@ -100,6 +108,41 @@ class PdfParser { _lexer!.reset(); } + /// internal method + void advance() { + if (_cTable != null && _cTable!.validateSyntax) { + _lexer!.getNextToken(); + } + _next = _lexer!.getNextToken(); + } + + /// Read fdf object + FdfObject? parseObject() { + final IPdfPrimitive? num1 = simple(); + final IPdfPrimitive? num2 = simple(); + _match(_next, PdfTokenType.objectStart); + advance(); + final IPdfPrimitive? obj = simple(); + if (_next != PdfTokenType.objectEnd) { + _next = PdfTokenType.objectEnd; + } + _match(_next, PdfTokenType.objectEnd); + return num1 != null && + num2 != null && + obj != null && + num1 is PdfNumber && + num2 is PdfNumber + ? FdfObject(num1, num2, obj) + : null; + } + + /// internal method + IPdfPrimitive trailer() { + _match(_next, PdfTokenType.trailer); + advance(); + return _dictionary(); + } + PdfNumber? _parseInteger() { final double? value = double.tryParse(_lexer!.text); PdfNumber? integer; @@ -108,7 +151,7 @@ class PdfParser { } else { _error(_ErrorType.badlyFormedInteger, _lexer!.text); } - _advance(); + advance(); return integer; } @@ -125,10 +168,12 @@ class PdfParser { if (_next == PdfTokenType.number) { final PdfNumber? integer2 = _parseInteger(); if (_next == PdfTokenType.reference) { - final PdfReference reference = - PdfReference(integer!.value!.toInt(), integer2!.value!.toInt()); + final PdfReference reference = PdfReference( + integer!.value!.toInt(), + integer2!.value!.toInt(), + ); obj = PdfReferenceHolder.fromReference(reference, _crossTable); - _advance(); + advance(); } else { integerQueue.addLast(integer2!.value!.toInt()); } @@ -137,7 +182,7 @@ class PdfParser { } void _parseOldXRef(CrossTable cTable, Map? objects) { - _advance(); + advance(); while (_isSubsection()) { cTable.parseSubsection(this, objects); } @@ -157,12 +202,14 @@ class PdfParser { /// internal method Map parseCrossReferenceTable( - Map? objects, CrossTable cTable) { + Map? objects, + CrossTable cTable, + ) { IPdfPrimitive? obj; - _advance(); + advance(); if (_next == PdfTokenType.xRef) { _parseOldXRef(cTable, objects); - obj = _trailer(); + obj = trailer(); final PdfDictionary trailerDic = obj as PdfDictionary; if (trailerDic.containsKey('Size')) { final int size = (trailerDic['Size']! as PdfNumber).value!.toInt(); @@ -192,19 +239,36 @@ class PdfParser { obj = _parse(); objects = cTable.parseNewTable(obj as PdfStream?, objects); } + if (obj is PdfDictionary && + _crossTable != null && + obj.containsKey('XRefStm')) { + try { + int xrefStreamPosition = 0; + final PdfDictionary trailerDictionary = obj; + final IPdfPrimitive? pdfNumber = PdfCrossTable.dereference( + trailerDictionary['XRefStm'], + ); + if (pdfNumber != null && pdfNumber is PdfNumber) { + xrefStreamPosition = pdfNumber.value!.toInt(); + } + cTable.parser.setOffset(xrefStreamPosition); + final IPdfPrimitive? xrefStream = cTable.parser.parseOffset( + xrefStreamPosition, + ); + if (xrefStream != null && xrefStream is PdfStream) { + objects = cTable.parseNewTable(xrefStream, objects); + } + } catch (e) { + //exception may occurs if offest is not correct/crosstable is corrupted. + } + } return {'object': obj, 'objects': objects}; } - IPdfPrimitive _trailer() { - _match(_next, PdfTokenType.trailer); - _advance(); - return _dictionary(); - } - /// internal method IPdfPrimitive? parseOffset(int offset) { setOffset(offset); - _advance(); + advance(); return _parse(); } @@ -213,14 +277,14 @@ class PdfParser { simple(); simple(); _match(_next, PdfTokenType.objectStart); - _advance(); + advance(); final IPdfPrimitive? obj = simple(); if (_next != PdfTokenType.objectEnd) { _next = PdfTokenType.objectEnd; } _match(_next, PdfTokenType.objectEnd); if (!_lexer!.skip) { - _advance(); + advance(); } else { _lexer!.skip = false; } @@ -263,8 +327,9 @@ class PdfParser { break; case PdfTokenType.nullType: obj = PdfNull(); - _advance(); + advance(); break; + // ignore: no_default_cases default: obj = null; break; @@ -282,7 +347,7 @@ class PdfParser { } else { _error(_ErrorType.badlyFormedReal, _lexer!.text); } - _advance(); + advance(); return real; } @@ -290,7 +355,7 @@ class PdfParser { _match(_next, PdfTokenType.boolean); final bool value = _lexer!.text == 'true'; final PdfBoolean result = PdfBoolean(value); - _advance(); + advance(); return result; } @@ -298,19 +363,21 @@ class PdfParser { _match(_next, PdfTokenType.name); final String name = _lexer!.text.substring(1); final PdfName result = PdfName(name); - _advance(); + advance(); return result; } /// internal method void startFrom(int offset) { setOffset(offset); - _advance(); + advance(); } /// internal method void rebuildXrefTable( - Map newObjects, CrossTable? crosstable) { + Map newObjects, + CrossTable? crosstable, + ) { final PdfReader reader = PdfReader(_reader.streamReader.data); reader.position = 0; newObjects.clear(); @@ -330,10 +397,10 @@ class PdfParser { final List tokens = str.split(''); final bool previousObject = previoursToken[0].codeUnitAt(0) >= '0'.codeUnitAt(0) && - previoursToken[0].codeUnitAt(0) <= '9'.codeUnitAt(0) && - tokens.length > 1 && - tokens[1].codeUnitAt(0) >= '0'.codeUnitAt(0) && - tokens[1].codeUnitAt(0) <= '9'.codeUnitAt(0); + previoursToken[0].codeUnitAt(0) <= '9'.codeUnitAt(0) && + tokens.length > 1 && + tokens[1].codeUnitAt(0) >= '0'.codeUnitAt(0) && + tokens[1].codeUnitAt(0) <= '9'.codeUnitAt(0); if (tokens[0].codeUnitAt(0) >= '0'.codeUnitAt(0) && tokens[0].codeUnitAt(0) <= '9'.codeUnitAt(0) || previousObject) { @@ -350,8 +417,11 @@ class PdfParser { marker = int.tryParse(words[1]); if (marker != null) { if (marker == 0 && words[2] == PdfDictionaryProperties.obj) { - final ObjectInformation objectInfo = - ObjectInformation(previousPosition, null, crosstable); + final ObjectInformation objectInfo = ObjectInformation( + previousPosition, + null, + crosstable, + ); if (!newObjects.containsKey(objNumber)) { newObjects[objNumber] = objectInfo; } @@ -386,19 +456,22 @@ class PdfParser { text = String.fromCharCodes(_processEscapes(text)); } } - } else { - if (_isColorSpace) { - text = String.fromCharCodes(_processEscapes(text)); - } - text = 'ColorFound$text'; + } else if ((_crossTable != null && + _crossTable!.document != null && + PdfDocumentHelper.getHelper(_crossTable!.document!).isEncrypted) || + _isColorSpace) { + text = String.fromCharCodes(_processEscapes(text)); } final PdfString str = PdfString(text); + if (_isColorSpace) { + str.isColorSpace = true; + } if (!unicode) { str.encode = ForceEncoding.ascii; } else { str.encode = ForceEncoding.unicode; } - _advance(); + advance(); return str; } @@ -427,7 +500,7 @@ class PdfParser { str.encode = ForceEncoding.unicode; } if (!_lexer!.skip) { - _advance(); + advance(); } else { _next = PdfTokenType.dictionaryEnd; } @@ -482,26 +555,20 @@ class PdfParser { result.add(next); break; default: - if (next < 48 || next > 55) { + if (next >= 48 && next <= 55) { + int octal = next - 48; + for (int j = 0; j < 2 && i + 1 < data.length; j++) { + next = data[i + 1]; + if (next < 48 || next > 55) { + break; + } + i++; + octal = (octal << 3) + (next - 48); + } + result.add(octal & 0xFF); + } else { result.add(next); - break; } - 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; } } else { @@ -532,8 +599,8 @@ class PdfParser { IPdfPrimitive _hexString() { _match(_next, PdfTokenType.hexStringStart); - _advance(); - String sb = ''; + advance(); + final StringBuffer sb = StringBuffer(); bool isHex = true; while (_next != PdfTokenType.hexStringEnd) { String text = _lexer!.text; @@ -543,18 +610,21 @@ class PdfParser { isHex = false; text = text.substring(1); } - sb += text; - _advance(); + sb.write(text); + advance(); } _match(_next, PdfTokenType.hexStringEnd); - _advance(); - final PdfString result = PdfString(sb, !isHex); + advance(); + final PdfString result = PdfString(sb.toString(), !isHex); + if (_isColorSpace) { + result.isColorSpace = true; + } return result; } IPdfPrimitive _array() { _match(_next, PdfTokenType.arrayStart); - _advance(); + advance(); IPdfPrimitive? obj; final PdfArray array = PdfArray(); _lexer!.isArray = true; @@ -566,11 +636,11 @@ class PdfParser { _isColorSpace = false; } if (_next == PdfTokenType.unknown) { - _advance(); + advance(); } } _match(_next, PdfTokenType.arrayEnd); - _advance(); + advance(); _lexer!.isArray = false; array.freezeChanges(this); return array; @@ -578,7 +648,7 @@ class PdfParser { IPdfPrimitive _dictionary() { _match(_next, PdfTokenType.dictionaryStart); - _advance(); + advance(); final PdfDictionary dic = PdfDictionary(); _Pair pair = _readPair(); while (pair.name != null && pair._value != null) { @@ -592,7 +662,7 @@ class PdfParser { } _match(_next, PdfTokenType.dictionaryEnd); if (!_lexer!.skip) { - _advance(); + advance(); } else { _next = PdfTokenType.objectEnd; _lexer!.skip = false; @@ -635,12 +705,12 @@ class PdfParser { _reader.position = position; final List buffer = _lexer!.readBytes(streamLength); final PdfStream innerStream = PdfStream(dic, buffer); - _advance(); + advance(); if (_next != PdfTokenType.streamEnd) { _next = PdfTokenType.streamEnd; } _match(_next, PdfTokenType.streamEnd); - _advance(); + advance(); if (_next != PdfTokenType.objectEnd) {} return innerStream; } else if (reference != null) { @@ -674,12 +744,12 @@ class PdfParser { final List buf = _lexer!.readBytes(streamLength); stream = PdfStream(dic, buf); } - _advance(); + advance(); if (_next != PdfTokenType.streamEnd) { _next = PdfTokenType.streamEnd; } _match(_next, PdfTokenType.streamEnd); - _advance(); + advance(); if (_next != PdfTokenType.objectEnd) { _next = PdfTokenType.objectEnd; } @@ -690,7 +760,7 @@ class PdfParser { String getObjectFlag() { _match(_next, PdfTokenType.objectType); final String type = _lexer!.text[0]; - _advance(); + advance(); return type; } @@ -731,7 +801,8 @@ class PdfParser { _error(_ErrorType.badlyFormedDictionary, 'next should be a name.'); } if (name!.name == PdfDictionaryProperties.u || - name.name == PdfDictionaryProperties.o) { + name.name == PdfDictionaryProperties.o || + name.name == PdfDictionaryProperties.id) { _isPassword = true; } obj = simple(); @@ -741,9 +812,9 @@ class PdfParser { /// internal method int startCrossReference() { - _advance(); + advance(); _match(_next, PdfTokenType.startXRef); - _advance(); + advance(); final PdfNumber? number = _number() as PdfNumber?; if (number != null) { return number.value!.toInt(); @@ -784,7 +855,6 @@ class PdfParser { case _ErrorType.none: case _ErrorType.badlyFormedHexString: - default: message = 'Internal error.'; break; } @@ -795,13 +865,6 @@ class PdfParser { throw ArgumentError.value(error, message); } - - void _advance() { - if (_cTable != null && _cTable!.validateSyntax) { - _lexer!.getNextToken(); - } - _next = _lexer!.getNextToken(); - } } enum _ErrorType { 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 3c4796e6c..091cced9e 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 @@ -43,7 +43,7 @@ class PdfReader { '\u000b', '\u000c', '\u000d', - '\u0085' + '\u0085', ]; late int _peekedByte; late bool _bytePeeked; @@ -65,8 +65,8 @@ class PdfReader { int value; do { value = _read(); - } while ( - value != -1 && _spaceCharacters.contains(String.fromCharCode(value))); + } while (value != -1 && + _spaceCharacters.contains(String.fromCharCode(value))); position = value == -1 ? streamReader.length! : (position - 1); } } @@ -185,9 +185,11 @@ class PdfReader { 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++) { + for ( + int i = pos; + i < length! && i < (pos + count) && index < buffer.length; + i++ + ) { buffer[index] = _read(); index++; } @@ -263,8 +265,11 @@ class PdfReader { if (buf[0] == token.codeUnitAt(0)) { if (!isStartXref) { pos = position - 1; - final Map result = - copyBytes(buf, 1, token.length - 1); + final Map result = copyBytes( + buf, + 1, + token.length - 1, + ); final int length = result['next'] as int; buf = result['buffer'] as List?; position = pos; @@ -281,10 +286,15 @@ class PdfReader { pos = position - 1; position = pos; int newPosition = pos; - List? buff = - List.filled(PdfOperators.startCrossReference.length, 0); - final Map result = - copyBytes(buff, 1, PdfOperators.startCrossReference.length); + List? buff = List.filled( + PdfOperators.startCrossReference.length, + 0, + ); + final Map result = copyBytes( + buff, + 1, + PdfOperators.startCrossReference.length, + ); buff = result['buffer'] as List?; if (PdfOperators.startCrossReference == String.fromCharCodes(buff!)) { isStartXref = true; 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 193a718aa..678093434 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 @@ -125,10 +125,14 @@ class PdfStreamWriter implements IPdfWriter { /// internal method void setColorAndSpace( - PdfColor color, PdfColorSpace? colorSpace, bool forStroking) { + PdfColor color, + PdfColorSpace? colorSpace, + bool forStroking, + ) { if (!color.isEmpty) { stream!.write( - PdfColorHelper.getHelper(color).getString(colorSpace, forStroking)); + PdfColorHelper.getHelper(color).getString(colorSpace, forStroking), + ); stream!.write(PdfOperators.newLine); } } @@ -137,9 +141,11 @@ class PdfStreamWriter implements IPdfWriter { void setColorSpace(PdfName name, bool forStroking) { stream!.write(name.toString()); stream!.write(PdfOperators.whiteSpace); - stream!.write(forStroking - ? PdfOperators.selectColorSpaceForStroking - : PdfOperators.selectColorSpaceForNonStroking); + stream!.write( + forStroking + ? PdfOperators.selectColorSpaceForStroking + : PdfOperators.selectColorSpaceForNonStroking, + ); stream!.write(PdfOperators.newLine); } @@ -225,7 +231,10 @@ class PdfStreamWriter implements IPdfWriter { void setGraphicsState(PdfName name) { if (name.name!.isEmpty) { throw ArgumentError.value( - name, 'name', 'dictionary name cannot be empty'); + name, + 'name', + 'dictionary name cannot be empty', + ); } stream!.write(name.toString()); stream!.write(PdfOperators.whiteSpace); @@ -305,7 +314,13 @@ class PdfStreamWriter implements IPdfWriter { /// internal method void appendBezierSegment( - double x1, double y1, double x2, double y2, double x3, double y3) { + double x1, + double y1, + double x2, + double y2, + double x3, + double y3, + ) { writePoint(x1, y1); writePoint(x2, y2); writePoint(x3, y3); @@ -325,7 +340,9 @@ class PdfStreamWriter implements IPdfWriter { //ignore:unused_element set document(PdfDocument? value) { throw ArgumentError.value( - value, 'The method or operation is not implemented'); + value, + 'The method or operation is not implemented', + ); } @override @@ -336,7 +353,9 @@ class PdfStreamWriter implements IPdfWriter { //ignore:unused_element set length(int? value) { throw ArgumentError.value( - value, 'The method or operation is not implemented'); + value, + 'The method or operation is not implemented', + ); } @override @@ -346,7 +365,9 @@ class PdfStreamWriter implements IPdfWriter { //ignore:unused_element set position(int? value) { throw ArgumentError.value( - value, 'The method or operation is not implemented'); + value, + 'The method or operation is not implemented', + ); } @override @@ -371,7 +392,9 @@ class PdfStreamWriter implements IPdfWriter { stream!.write(pdfObject); } else { throw ArgumentError.value( - pdfObject, 'The method or operation is not implemented'); + 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 3c56816ff..f185a55a0 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 @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:typed_data'; import '../../interfaces/pdf_interface.dart'; import '../pdf_document/pdf_document.dart'; @@ -7,15 +8,23 @@ import '../pdf_document/pdf_document.dart'; class PdfWriter implements IPdfWriter { //Constructor /// internal constructor - PdfWriter(this.buffer) { - length = buffer!.length; - position = buffer!.length; + PdfWriter(this.buffer, [PdfBytesBuilder? bytesBuilder]) { + if (bytesBuilder != null) { + this.bytesBuilder = bytesBuilder; + length = this.bytesBuilder!.length; + position = this.bytesBuilder!.length; + isBytesBuilder = true; + } else { + length = buffer!.length; + position = buffer!.length; + } } //Fields /// internal field List? buffer; - + PdfBytesBuilder? bytesBuilder; + bool isBytesBuilder = false; //IPdfWriter members @override PdfDocument? document; @@ -55,10 +64,114 @@ class PdfWriter implements IPdfWriter { length = length! + tempLength; position = position! + tempLength; if (end == null) { - buffer!.addAll(data); + if (isBytesBuilder) { + bytesBuilder!.add(data); + } else { + _addDataInChunks(data); + //buffer!.addAll(data); + } } else { - buffer!.addAll(data.take(end)); + if (isBytesBuilder) { + bytesBuilder!.add(data.take(end).toList()); + } else { + _addDataInChunks(data.take(end).toList()); + //buffer!.addAll(data.take(end)); + } } } } + + void _addDataInChunks(List data) { + const int chunkSize = 8190; + for (int i = 0; i < data.length; i += chunkSize) { + final int end = + (i + chunkSize < data.length) ? i + chunkSize : data.length; + buffer!.addAll(data.sublist(i, end)); + } + } + + /// Internal method + Future writeAsync(dynamic data, [int? end]) async { + if (data == null) { + throw ArgumentError.value(data, 'data', 'value cannot be null'); + } + if (data is int) { + writeAsync(data.toString()); + } else if (data is double) { + String value = data.toStringAsFixed(2); + if (value.endsWith('.00')) { + if (value.length == 3) { + value = '0'; + } else { + value = value.substring(0, value.length - 3); + } + } + writeAsync(value); + } else if (data is String) { + writeAsync(utf8.encode(data)); + } else if (data is IPdfPrimitive) { + data.save(this); + } else if (data is List) { + int tempLength; + if (end == null) { + tempLength = data.length; + } else { + tempLength = end; + } + length = length! + tempLength; + position = position! + tempLength; + if (end == null) { + if (isBytesBuilder) { + bytesBuilder!.add(data); + } else { + _addDataInChunks(data); + //buffer!.addAll(data); + } + } else { + if (isBytesBuilder) { + bytesBuilder!.add(data.take(end).toList()); + } else { + _addDataInChunks(data.take(end).toList()); + //buffer!.addAll(data.take(end)); + } + } + } + } +} + +///Helper class to write the PDF document data. +class PdfBytesBuilder { + /// internal fields + final List _chunks = []; + int _length = 0; + + /// get the length of the data + int get length => _length; + + /// add the data + void add(List data) { + if (data.isEmpty) { + return; + } + final Uint8List chunk = Uint8List.fromList(data); + _chunks.add(chunk); + _length += chunk.length; + } + + /// get the bytes + Uint8List takeBytes() { + final Uint8List result = Uint8List(_length); + int offset = 0; + for (final Uint8List chunk in _chunks) { + result.setRange(offset, offset + chunk.length, chunk); + offset += chunk.length; + } + return result; + } + + /// clear the data + void clear() { + _chunks.clear(); + _length = 0; + } } 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 784c62ef5..4e96f022a 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 @@ -20,7 +20,7 @@ enum PdfPageRotateAngle { rotateAngle180, /// The page is rotated as 270 angle. - rotateAngle270 + rotateAngle270, } /// Specifies the docking style of the page template. @@ -41,7 +41,7 @@ enum PdfDockStyle { right, /// The page template stretch on full page. - fill + fill, } /// Specifies how the page template is aligned relative to the template area. @@ -95,7 +95,7 @@ enum PdfNumberStyle { upperLatin, /// Uppercase roman numerals. - upperRoman + upperRoman, } /// Specifies tab order types for form fields @@ -131,5 +131,5 @@ enum TemplateType { left, /// Page template is used as Right. - right + right, } 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 87fe5a9c6..620cb5fc6 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 @@ -54,8 +54,10 @@ class PdfLayer implements IPdfWrapper { set name(String? value) { _name = value; if (_helper.dictionary != null && _name != null && _name != '') { - _helper.dictionary! - .setProperty(PdfDictionaryProperties.name, PdfString(_name!)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.name, + PdfString(_name!), + ); } } @@ -76,12 +78,15 @@ class PdfLayer implements IPdfWrapper { if (_helper.dictionary != null) { _helper.dictionary![PdfDictionaryProperties.visible] = PdfBoolean(value); } + _helper._setVisibility(_visible); } /// Gets the collection of child [PdfLayer] PdfLayerCollection get layers { - _layerCollection ??= - PdfLayerCollectionHelper.withLayer(_helper.document, _helper.layer); + _layerCollection ??= PdfLayerCollectionHelper.withLayer( + _helper.document, + _helper.layer, + ); return _layerCollection!; } @@ -90,9 +95,9 @@ class PdfLayer implements IPdfWrapper { PdfGraphics createGraphics(PdfPage page) { _helper.page = page; if (_graphics == null) { - PdfPageHelper.getHelper(page) - .contents - .add(PdfReferenceHolder(_helper.layer)); + PdfPageHelper.getHelper( + page, + ).contents.add(PdfReferenceHolder(_helper.layer)); } final PdfResources? resource = PdfPageHelper.getHelper(page).getResources(); if (PdfLayerHelper.getHelper(_helper.layer!).layerId == null || @@ -125,19 +130,21 @@ class PdfLayer implements IPdfWrapper { if (_graphics == null) { final Function resources = PdfPageHelper.getHelper(page).getResources; bool isPageHasMediaBox = false; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfName(PdfDictionaryProperties.mediaBox))) { + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfName(PdfDictionaryProperties.mediaBox))) { isPageHasMediaBox = true; } double llx = 0; double lly = 0; double urx = 0; double ury = 0; - final PdfArray? mediaBox = PdfPageHelper.getHelper(page) - .dictionary! - .getValue(PdfDictionaryProperties.mediaBox, - PdfDictionaryProperties.parent) as PdfArray?; + final PdfArray? mediaBox = + PdfPageHelper.getHelper(page).dictionary!.getValue( + PdfDictionaryProperties.mediaBox, + PdfDictionaryProperties.parent, + ) + as PdfArray?; if (mediaBox != null) { // Lower Left X co-ordinate Value. llx = (mediaBox[0]! as PdfNumber).value!.toDouble(); @@ -149,12 +156,15 @@ class PdfLayer implements IPdfWrapper { ury = (mediaBox[3]! as PdfNumber).value!.toDouble(); } PdfArray? cropBox; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.cropBox)) { - cropBox = PdfPageHelper.getHelper(page).dictionary!.getValue( - PdfDictionaryProperties.cropBox, PdfDictionaryProperties.parent) - as PdfArray?; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.cropBox)) { + cropBox = + PdfPageHelper.getHelper(page).dictionary!.getValue( + PdfDictionaryProperties.cropBox, + PdfDictionaryProperties.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(); @@ -162,40 +172,57 @@ class PdfLayer implements IPdfWrapper { 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 = - PdfGraphicsHelper.load(pageSize, resources, _helper._content!); + final Size pageSize = Size( + [cropX, cropRX].reduce(max), + [cropY, cropRY].reduce(max), + ); + _graphics = PdfGraphicsHelper.load( + pageSize, + resources, + _helper._content!, + ); } else { - _graphics = - PdfGraphicsHelper.load(page.size, resources, _helper._content!); + _graphics = PdfGraphicsHelper.load( + page.size, + resources, + _helper._content!, + ); PdfGraphicsHelper.getHelper(_graphics!).cropBox = cropBox; } } else if ((llx < 0 || lly < 0 || urx < 0 || ury < 0) && (lly.abs().floor() == page.size.height.abs().floor()) && (urx.abs().floor() == page.size.width.abs().floor())) { final Size pageSize = Size( - [llx, urx].reduce(max), [lly, ury].reduce(max)); + [llx, urx].reduce(max), + [lly, ury].reduce(max), + ); if (pageSize.width <= 0 || pageSize.height <= 0) { - _graphics = - PdfGraphicsHelper.load(pageSize, resources, _helper._content!); + _graphics = PdfGraphicsHelper.load( + pageSize, + resources, + _helper._content!, + ); } } else { - _graphics = - PdfGraphicsHelper.load(page.size, resources, _helper._content!); + _graphics = PdfGraphicsHelper.load( + page.size, + resources, + _helper._content!, + ); } if (isPageHasMediaBox) { PdfGraphicsHelper.getHelper(_graphics!).mediaBoxUpperRightBound = ury; } if (!PdfPageHelper.getHelper(page).isLoadedPage) { final PdfSectionCollection? sectionCollection = - PdfSectionHelper.getHelper(PdfPageHelper.getHelper(page).section!) - .parent; + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).parent; if (sectionCollection != null) { _graphics!.colorSpace = - PdfSectionCollectionHelper.getHelper(sectionCollection) - .document! - .colorSpace; + PdfSectionCollectionHelper.getHelper( + sectionCollection, + ).document!.colorSpace; } } if (!_graphicsMap.containsKey(_graphics)) { @@ -213,9 +240,9 @@ class PdfLayer implements IPdfWrapper { return _graphics!; } } - PdfGraphicsHelper.getHelper(_graphics!) - .streamWriter! - .write(PdfOperators.newLine); + PdfGraphicsHelper.getHelper( + _graphics!, + ).streamWriter!.write(PdfOperators.newLine); _graphics!.save(); PdfGraphicsHelper.getHelper(_graphics!).initializeCoordinates(); if (PdfGraphicsHelper.getHelper(_graphics!).hasTransparencyBrush) { @@ -223,39 +250,44 @@ class PdfLayer implements IPdfWrapper { } if (PdfPageHelper.getHelper(page).isLoadedPage && (page.rotation != PdfPageRotateAngle.rotateAngle0 || - PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.rotate))) { + PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.rotate))) { PdfArray? cropBox; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.cropBox)) { - cropBox = PdfPageHelper.getHelper(page).dictionary!.getValue( - PdfDictionaryProperties.cropBox, PdfDictionaryProperties.parent) - as PdfArray?; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.cropBox)) { + cropBox = + PdfPageHelper.getHelper(page).dictionary!.getValue( + PdfDictionaryProperties.cropBox, + PdfDictionaryProperties.parent, + ) + as PdfArray?; } _updatePageRotation(page, _graphics, cropBox); } if (!PdfPageHelper.getHelper(page).isLoadedPage) { - final PdfRectangle clipRect = - PdfSectionHelper.getHelper(PdfPageHelper.getHelper(page).section!) - .getActualBounds(page, true); + final PdfRectangle clipRect = PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).getActualBounds(page, true); if (_clipPageTemplates) { if (PdfPageHelper.getHelper(page).origin.dx >= 0 && PdfPageHelper.getHelper(page).origin.dy >= 0) { - PdfGraphicsHelper.getHelper(_graphics!) - .clipTranslateMarginsWithBounds(clipRect); + PdfGraphicsHelper.getHelper( + _graphics!, + ).clipTranslateMarginsWithBounds(clipRect); } } else { final PdfMargins margins = PdfPageHelper.getHelper(page).section!.pageSettings.margins; PdfGraphicsHelper.getHelper(_graphics!).clipTranslateMargins( - clipRect.x, - clipRect.y, - margins.left, - margins.top, - margins.right, - margins.bottom); + clipRect.x, + clipRect.y, + margins.left, + margins.top, + margins.right, + margins.bottom, + ); } } if (!_helper.pages.contains(page)) { @@ -266,16 +298,26 @@ class PdfLayer implements IPdfWrapper { } void _updatePageRotation( - PdfPage page, PdfGraphics? graphics, PdfArray? cropBox) { + PdfPage page, + PdfGraphics? graphics, + PdfArray? cropBox, + ) { PdfNumber? rotation; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.rotate)) { - rotation = PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.rotate] as PdfNumber?; - rotation ??= rotation = PdfCrossTable.dereference( - PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.rotate]) as PdfNumber?; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.rotate)) { + rotation = + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .rotate] + as PdfNumber?; + rotation ??= + rotation = + PdfCrossTable.dereference( + PdfPageHelper.getHelper( + page, + ).dictionary![PdfDictionaryProperties.rotate], + ) + as PdfNumber?; } else if (page.rotation != PdfPageRotateAngle.rotateAngle0) { if (page.rotation == PdfPageRotateAngle.rotateAngle90) { rotation = PdfNumber(90); @@ -291,25 +333,29 @@ class PdfLayer implements IPdfWrapper { if (cropBox != null) { 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) { PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( - page.size.height - cropBoxOffset.dy, - cropBoxSize.width - cropBoxOffset.dx); + page.size.height - cropBoxOffset.dy, + cropBoxSize.width - cropBoxOffset.dx, + ); } else { PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( - cropBoxSize.height - cropBoxOffset.dy, - cropBoxSize.width - cropBoxOffset.dx); + cropBoxSize.height - cropBoxOffset.dy, + cropBoxSize.width - cropBoxOffset.dx, + ); } } else { - PdfGraphicsHelper.getHelper(graphics).clipBounds.size = - PdfSize(page.size.height, page.size.width); + PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( + page.size.height, + page.size.width, + ); } } else if (rotation.value == 180) { graphics!.translateTransform(page.size.width, page.size.height); @@ -317,15 +363,20 @@ class PdfLayer implements IPdfWrapper { } else if (rotation.value == 270) { graphics!.translateTransform(page.size.width, 0); graphics.rotateTransform(-270); - PdfGraphicsHelper.getHelper(graphics).clipBounds.size = - PdfSize(page.size.height, page.size.width); + PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( + page.size.height, + page.size.width, + ); } } void _graphicsContent(PdfPage page) { final PdfStream stream = PdfStream(); _graphics = PdfGraphicsHelper.load( - page.size, PdfPageHelper.getHelper(page).getResources, stream); + page.size, + PdfPageHelper.getHelper(page).getResources, + stream, + ); PdfPageHelper.getHelper(page).contents.add(PdfReferenceHolder(stream)); stream.beginSave = _beginSaveContent; if (!_graphicsMap.containsKey(_graphics)) { @@ -346,9 +397,9 @@ class PdfLayer implements IPdfWrapper { _helper.beginLayer(_graphics); PdfGraphicsHelper.getHelper(_graphics!).endMarkContent(); } - PdfGraphicsHelper.getHelper(_graphics!) - .streamWriter! - .write(PdfOperators.restoreState + PdfOperators.newLine); + PdfGraphicsHelper.getHelper( + _graphics!, + ).streamWriter!.write(PdfOperators.restoreState + PdfOperators.newLine); keyValue = key; flag = true; } @@ -439,13 +490,14 @@ class PdfLayerHelper { if (parentLayer.isNotEmpty) { for (int i = 0; i < parentLayer.length; i++) { PdfGraphicsHelper.getHelper(base._graphics!).streamWriter!.write( - '/OC /${PdfLayerHelper.getHelper(parentLayer[i]).layerId!} BDC\n'); + '/OC /${PdfLayerHelper.getHelper(parentLayer[i]).layerId!} BDC\n', + ); } } if (base.name != null && base.name != '') { - PdfGraphicsHelper.getHelper(base._graphics!) - .streamWriter! - .write('/OC /${layerId!} BDC\n'); + PdfGraphicsHelper.getHelper( + base._graphics!, + ).streamWriter!.write('/OC /${layerId!} BDC\n'); isEndState = true; } else { _content!.write('/OC /${layerId!} BDC\n'); @@ -463,17 +515,24 @@ class PdfLayerHelper { PdfPageHelper.getHelper(document!.pages[i]).dictionary!; final PdfPage page = document!.pages[i]; if (pageDictionary.containsKey(PdfDictionaryProperties.resources)) { - final PdfDictionary? resources = PdfCrossTable.dereference( - pageDictionary[PdfDictionaryProperties.resources]) - as PdfDictionary?; + final PdfDictionary? resources = + PdfCrossTable.dereference( + pageDictionary[PdfDictionaryProperties.resources], + ) + as PdfDictionary?; if (resources != null && (resources.containsKey(PdfDictionaryProperties.properties)) || (resources!.containsKey(PdfDictionaryProperties.xObject))) { - final PdfDictionary? properties = PdfCrossTable.dereference( - resources[PdfDictionaryProperties.properties]) - as PdfDictionary?; - final PdfDictionary? xObject = PdfCrossTable.dereference( - resources[PdfDictionaryProperties.xObject]) as PdfDictionary?; + final PdfDictionary? properties = + PdfCrossTable.dereference( + resources[PdfDictionaryProperties.properties], + ) + as PdfDictionary?; + final PdfDictionary? xObject = + PdfCrossTable.dereference( + resources[PdfDictionaryProperties.xObject], + ) + as PdfDictionary?; if (properties != null) { properties.items!.forEach((PdfName? key, IPdfPrimitive? value) { if (value is PdfReferenceHolder) { @@ -493,14 +552,15 @@ class PdfLayerHelper { if (dictionary.containsKey('OC')) { final PdfName? layerID = key; reference = dictionary['OC']! as PdfReferenceHolder; - dictionary = PdfCrossTable.dereference(dictionary['OC'])! - as PdfDictionary; + dictionary = + PdfCrossTable.dereference(dictionary['OC'])! + as PdfDictionary; final bool isPresent = _parsingDictionary(dictionary, reference, page, layerID)!; if (isPresent) { - PdfLayerHelper.getHelper(layer!) - .xobject - .add(layerID!.name!); + PdfLayerHelper.getHelper( + layer!, + ).xobject.add(layerID!.name!); } } }); @@ -514,8 +574,12 @@ class PdfLayerHelper { } } - bool? _parsingDictionary(PdfDictionary? dictionary, - PdfReferenceHolder? reference, PdfPage? page, PdfName? layerID) { + bool? _parsingDictionary( + PdfDictionary? dictionary, + PdfReferenceHolder? reference, + PdfPage? page, + PdfName? layerID, + ) { if (base._isPresent == null || !base._isPresent!) { base._isPresent = false; if (!dictionary!.containsKey(PdfDictionaryProperties.name) && @@ -527,8 +591,11 @@ class PdfLayerHelper { if (pdfArray == null) { reference = dictionary[PdfDictionaryProperties.ocg] as PdfReferenceHolder?; - dictionary = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.ocg]) as PdfDictionary?; + dictionary = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.ocg], + ) + as PdfDictionary?; if (dictionary != null && dictionary.containsKey(PdfDictionaryProperties.name)) { base._isPresent = _setLayerPage(reference, page, layerID); @@ -553,13 +620,20 @@ class PdfLayerHelper { } bool _setLayerPage( - PdfReferenceHolder? reference, PdfPage? page, PdfName? layerID) { + PdfReferenceHolder? reference, + PdfPage? page, + PdfName? layerID, + ) { bool isPresent = false; if (PdfLayerHelper.getHelper(layer!).referenceHolder != null) { if (identical( - PdfLayerHelper.getHelper(layer!).referenceHolder, reference) || - identical(PdfLayerHelper.getHelper(layer!).referenceHolder!.object, - reference!.object) || + PdfLayerHelper.getHelper(layer!).referenceHolder, + reference, + ) || + identical( + PdfLayerHelper.getHelper(layer!).referenceHolder!.object, + reference!.object, + ) || PdfLayerHelper.getHelper(layer!).referenceHolder?.reference?.objNum == reference.reference?.objNum) { PdfLayerHelper.getHelper(layer!).pageParsed = true; @@ -574,6 +648,81 @@ class PdfLayerHelper { return isPresent; } + void _setVisibility(bool? value) { + PdfDictionary? oCProperties; + if (PdfDocumentHelper.getHelper( + document!, + ).catalog.containsKey(PdfDictionaryProperties.ocProperties)) { + oCProperties = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; + } + if (oCProperties != null) { + PdfDictionary? defaultView; + if (oCProperties.containsKey(PdfDictionaryProperties.defaultView)) { + final value = oCProperties[PdfDictionaryProperties.defaultView]; + if (value is PdfReferenceHolder) { + defaultView = PdfCrossTable.dereference(value) as PdfDictionary?; + } else if (value is PdfDictionary) { + defaultView = value; + } + } + if (defaultView != null) { + PdfArray? ocgON = + defaultView[PdfDictionaryProperties.ocgOn] as PdfArray?; + PdfArray? ocgOFF = + defaultView[PdfDictionaryProperties.ocgOff] as PdfArray?; + if (referenceHolder != null) { + if (value == false) { + if (ocgON != null) { + _removeContent(ocgON, referenceHolder); + } + if (ocgOFF == null) { + ocgOFF = PdfArray(); + defaultView.items![PdfName(PdfDictionaryProperties.ocgOff)] = + ocgOFF; + } + ocgOFF.insert(ocgOFF.count, referenceHolder!); + } else if (value ?? true) { + if (ocgOFF != null) { + _removeContent(ocgOFF, referenceHolder); + } + if (ocgON == null) { + ocgON = PdfArray(); + defaultView.items![PdfName(PdfDictionaryProperties.ocgOn)] = + ocgON; + } + ocgON.insert(ocgON.count, referenceHolder!); + } + } + } + } + } + + void _removeContent(PdfArray content, PdfReferenceHolder? referenceHolder) { + bool flag = false; + for (int i = 0; i < content.count; 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) { + content.elements.removeAt(i); + flag = true; + i--; + } + } + } + } + if (flag) { + content.changed = true; + } + } + /// internal property IPdfPrimitive? get element => _content; //ignore: unused_element 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 cc30c313b..50d1a3e11 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 @@ -45,8 +45,11 @@ 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}) { + void remove({ + PdfLayer? layer, + String? name, + bool isRemoveGraphicalContent = false, + }) { _helper.remove(layer, name, isRemoveGraphicalContent); } @@ -65,7 +68,7 @@ class PdfLayerCollection extends PdfObjectCollection { class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfLayerCollectionHelper(this.layerCollection, PdfDocument document) - : super(layerCollection) { + : super(layerCollection) { _document = document; _sublayer = false; if (PdfDocumentHelper.getHelper(document).isLoadedDocument) { @@ -75,8 +78,10 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfLayerCollectionHelper._( - this.layerCollection, PdfDocument? document, PdfLayer? layer) - : super(layerCollection) { + this.layerCollection, + PdfDocument? document, + PdfLayer? layer, + ) : super(layerCollection) { _document = document; _parent = layer; _sublayer = true; @@ -148,7 +153,8 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { void removeAt(int index, bool isRemoveGraphicalContent) { if (index < 0 || index > list.length - 1) { ArgumentError.value( - '$index Value can not be less 0 and greater List.Count - 1'); + '$index Value can not be less 0 and greater List.Count - 1', + ); } final PdfLayer layer = layerCollection[index]; list.removeAt(index); @@ -176,31 +182,34 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { void _createLayer(PdfLayer layer) { final PdfDictionary ocProperties = PdfDictionary(); - ocProperties[PdfDictionaryProperties.ocg] = - _createOptionalContentDictionary(layer); - ocProperties[PdfDictionaryProperties.defaultView] = - _createOptionalContentViews(layer); - PdfDocumentHelper.getHelper(_document!) - .catalog[PdfDictionaryProperties.ocProperties] = ocProperties; - PdfDocumentHelper.getHelper(_document!) - .catalog - .setProperty(PdfDictionaryProperties.ocProperties, ocProperties); + ocProperties[PdfDictionaryProperties + .ocg] = _createOptionalContentDictionary(layer); + ocProperties[PdfDictionaryProperties + .defaultView] = _createOptionalContentViews(layer); + PdfDocumentHelper.getHelper(_document!).catalog[PdfDictionaryProperties + .ocProperties] = + ocProperties; + PdfDocumentHelper.getHelper( + _document!, + ).catalog.setProperty(PdfDictionaryProperties.ocProperties, ocProperties); } IPdfPrimitive? _createOptionalContentDictionary(PdfLayer layer) { final PdfDictionary dictionary = PdfDictionary(); dictionary[PdfDictionaryProperties.name] = PdfString(layer.name!); dictionary[PdfDictionaryProperties.type] = PdfName('OCG'); - dictionary[PdfDictionaryProperties.layerID] = - PdfName(PdfLayerHelper.getHelper(layer).layerId); + dictionary[PdfDictionaryProperties.layerID] = PdfName( + PdfLayerHelper.getHelper(layer).layerId, + ); dictionary[PdfDictionaryProperties.visible] = PdfBoolean(layer.visible); PdfLayerHelper.getHelper(layer).usage = _setPrintOption(layer); - dictionary[PdfDictionaryProperties.usage] = - PdfReferenceHolder(PdfLayerHelper.getHelper(layer).usage); - PdfDocumentHelper.getHelper(_document!) - .printLayer! - .add(PdfReferenceHolder(dictionary)); + dictionary[PdfDictionaryProperties.usage] = PdfReferenceHolder( + PdfLayerHelper.getHelper(layer).usage, + ); + PdfDocumentHelper.getHelper( + _document!, + ).printLayer!.add(PdfReferenceHolder(dictionary)); final PdfReferenceHolder reference = PdfReferenceHolder(dictionary); PdfDocumentHelper.getHelper(_document!).pdfPrimitive!.add(reference); @@ -208,9 +217,13 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { PdfLayerHelper.getHelper(layer).dictionary = dictionary; // Order of the layers - final PdfDictionary? ocProperties = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(_document!) - .catalog[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + final PdfDictionary? ocProperties = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + _document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; _createSublayer(ocProperties, reference, layer); if (layer.visible) { PdfDocumentHelper.getHelper(_document!).on!.add(reference); @@ -223,24 +236,33 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { PdfDictionary _setPrintOption(PdfLayer layer) { final PdfDictionary usage = PdfDictionary(); PdfLayerHelper.getHelper(layer).printOption = PdfDictionary(); - PdfLayerHelper.getHelper(layer) - .printOption![PdfDictionaryProperties.subtype] = PdfName('Print'); - usage[PdfDictionaryProperties.print] = - PdfReferenceHolder(PdfLayerHelper.getHelper(layer).printOption); + PdfLayerHelper.getHelper(layer).printOption![PdfDictionaryProperties + .subtype] = PdfName('Print'); + usage[PdfDictionaryProperties.print] = PdfReferenceHolder( + PdfLayerHelper.getHelper(layer).printOption, + ); return usage; } - void _createSublayer(PdfDictionary? ocProperties, - PdfReferenceHolder reference, PdfLayer layer) { + void _createSublayer( + PdfDictionary? ocProperties, + PdfReferenceHolder reference, + PdfLayer layer, + ) { if (_sublayer == false) { if (ocProperties != null) { PdfArray? order; - final PdfDictionary? defaultview = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) - as PdfDictionary?; + final PdfDictionary? defaultview = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultview != null) { - order = PdfCrossTable.dereference( - defaultview[PdfDictionaryProperties.ocgOrder]) as PdfArray?; + order = + PdfCrossTable.dereference( + defaultview[PdfDictionaryProperties.ocgOrder], + ) + as PdfArray?; } if (PdfDocumentHelper.getHelper(_document!).order != null && order != null) { @@ -254,12 +276,17 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { PdfLayerHelper.getHelper(layer).parent = _parent; if (ocProperties != null) { PdfArray? order; - final PdfDictionary? defaultview = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) - as PdfDictionary?; + final PdfDictionary? defaultview = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultview != null) { - order = PdfCrossTable.dereference( - defaultview[PdfDictionaryProperties.ocgOrder]) as PdfArray?; + order = + PdfCrossTable.dereference( + defaultview[PdfDictionaryProperties.ocgOrder], + ) + as PdfArray?; } if (PdfDocumentHelper.getHelper(_document!).order != null && order != null) { @@ -268,59 +295,66 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } if (PdfLayerHelper.getHelper(_parent!).child.isEmpty) { PdfLayerHelper.getHelper(_parent!).sublayer.add(reference); - } else if (PdfDocumentHelper.getHelper(_document!) - .order! - .contains(PdfLayerHelper.getHelper(_parent!).referenceHolder!)) { - final int position = PdfDocumentHelper.getHelper(_document!) - .order! - .indexOf(PdfLayerHelper.getHelper(_parent!).referenceHolder!); + } else if (PdfDocumentHelper.getHelper( + _document!, + ).order!.contains(PdfLayerHelper.getHelper(_parent!).referenceHolder!)) { + final int position = PdfDocumentHelper.getHelper( + _document!, + ).order!.indexOf(PdfLayerHelper.getHelper(_parent!).referenceHolder!); PdfDocumentHelper.getHelper(_document!).order!.removeAt(position + 1); PdfLayerHelper.getHelper(_parent!).sublayer.add(reference); } else { PdfLayerHelper.getHelper(_parent!).sublayer.add(reference); } - if (PdfDocumentHelper.getHelper(_document!) - .order! - .contains(PdfLayerHelper.getHelper(_parent!).referenceHolder!)) { - final int position = PdfDocumentHelper.getHelper(_document!) - .order! - .indexOf(PdfLayerHelper.getHelper(_parent!).referenceHolder!); - PdfDocumentHelper.getHelper(_document!) - .order! - .insert(position + 1, PdfLayerHelper.getHelper(_parent!).sublayer); + if (PdfDocumentHelper.getHelper( + _document!, + ).order!.contains(PdfLayerHelper.getHelper(_parent!).referenceHolder!)) { + final int position = PdfDocumentHelper.getHelper( + _document!, + ).order!.indexOf(PdfLayerHelper.getHelper(_parent!).referenceHolder!); + PdfDocumentHelper.getHelper(_document!).order!.insert( + position + 1, + PdfLayerHelper.getHelper(_parent!).sublayer, + ); } else { if (PdfLayerHelper.getHelper(_parent!).parent != null) { if (PdfLayerHelper.getHelper( - PdfLayerHelper.getHelper(_parent!).parent!) - .sublayer - .contains(PdfLayerHelper.getHelper(_parent!).referenceHolder!)) { + PdfLayerHelper.getHelper(_parent!).parent!, + ).sublayer.contains( + PdfLayerHelper.getHelper(_parent!).referenceHolder!, + )) { int position = PdfLayerHelper.getHelper( - PdfLayerHelper.getHelper(_parent!).parent!) - .sublayer - .indexOf(PdfLayerHelper.getHelper(_parent!).referenceHolder!); + PdfLayerHelper.getHelper(_parent!).parent!, + ).sublayer.indexOf( + PdfLayerHelper.getHelper(_parent!).referenceHolder!, + ); if (PdfLayerHelper.getHelper(_parent!).sublayer.count == 1) { PdfLayerHelper.getHelper( - PdfLayerHelper.getHelper(_parent!).parent!) - .sublayer - .insert(position + 1, - PdfLayerHelper.getHelper(_parent!).sublayer); + PdfLayerHelper.getHelper(_parent!).parent!, + ).sublayer.insert( + position + 1, + PdfLayerHelper.getHelper(_parent!).sublayer, + ); } if (PdfDocumentHelper.getHelper(_document!).order!.contains( - PdfLayerHelper.getHelper( - PdfLayerHelper.getHelper(_parent!).parent!) - .referenceHolder!)) { + PdfLayerHelper.getHelper( + PdfLayerHelper.getHelper(_parent!).parent!, + ).referenceHolder!, + )) { position = PdfDocumentHelper.getHelper(_document!).order!.indexOf( - PdfLayerHelper.getHelper( - PdfLayerHelper.getHelper(_parent!).parent!) - .referenceHolder!); - PdfDocumentHelper.getHelper(_document!) - .order! - .removeAt(position + 1); + PdfLayerHelper.getHelper( + PdfLayerHelper.getHelper(_parent!).parent!, + ).referenceHolder!, + ); + PdfDocumentHelper.getHelper( + _document!, + ).order!.removeAt(position + 1); PdfDocumentHelper.getHelper(_document!).order!.insert( - position + 1, - PdfLayerHelper.getHelper( - PdfLayerHelper.getHelper(_parent!).parent!) - .sublayer); + position + 1, + PdfLayerHelper.getHelper( + PdfLayerHelper.getHelper(_parent!).parent!, + ).sublayer, + ); } } } @@ -331,15 +365,17 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { if (PdfLayerHelper.getHelper(_parent!).parentLayer.isEmpty) { PdfLayerHelper.getHelper(layer).parentLayer.add(_parent!); } else { - for (int i = 0; - i < PdfLayerHelper.getHelper(_parent!).parentLayer.length; - i++) { - if (!PdfLayerHelper.getHelper(layer) - .parentLayer - .contains(PdfLayerHelper.getHelper(_parent!).parentLayer[i])) { - PdfLayerHelper.getHelper(layer) - .parentLayer - .add(PdfLayerHelper.getHelper(_parent!).parentLayer[i]); + for ( + int i = 0; + i < PdfLayerHelper.getHelper(_parent!).parentLayer.length; + i++ + ) { + if (!PdfLayerHelper.getHelper(layer).parentLayer.contains( + PdfLayerHelper.getHelper(_parent!).parentLayer[i], + )) { + PdfLayerHelper.getHelper(layer).parentLayer.add( + PdfLayerHelper.getHelper(_parent!).parentLayer[i], + ); } } PdfLayerHelper.getHelper(layer).parentLayer.add(_parent!); @@ -372,16 +408,23 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { void _getDocumentLayer(PdfDocument document) { PdfDictionary? layerDictionary; PdfReferenceHolder layerReference; - if (PdfDocumentHelper.getHelper(_document!) - .catalog - .containsKey(PdfDictionaryProperties.ocProperties)) { - final PdfDictionary? ocProperties = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(_document!) - .catalog[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + if (PdfDocumentHelper.getHelper( + _document!, + ).catalog.containsKey(PdfDictionaryProperties.ocProperties)) { + final PdfDictionary? ocProperties = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + _document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; if (ocProperties != null) { if (ocProperties.containsKey(PdfDictionaryProperties.ocg)) { - final PdfArray ocGroup = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.ocg])! as PdfArray; + final PdfArray ocGroup = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.ocg], + )! + as PdfArray; for (int i = 0; i < ocGroup.count; i++) { if (ocGroup[i] is PdfReferenceHolder) { layerReference = ocGroup[i]! as PdfReferenceHolder; @@ -389,25 +432,33 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { final PdfLayer layer = PdfLayerHelper.internal(); if (layerDictionary != null && layerDictionary.containsKey(PdfDictionaryProperties.name)) { - final PdfString layerName = PdfCrossTable.dereference( - layerDictionary[PdfDictionaryProperties.name])! - as PdfString; + final PdfString layerName = + PdfCrossTable.dereference( + layerDictionary[PdfDictionaryProperties.name], + )! + as PdfString; layer.name = layerName.value; PdfLayerHelper.getHelper(layer).dictionary = layerDictionary; PdfLayerHelper.getHelper(layer).referenceHolder = layerReference; final IPdfPrimitive? layerId = PdfCrossTable.dereference( - layerDictionary[PdfDictionaryProperties.layerID]); + layerDictionary[PdfDictionaryProperties.layerID], + ); if (layerId != null) { PdfLayerHelper.getHelper(layer).layerId = layerId.toString(); } - final PdfDictionary? usage = PdfCrossTable.dereference( - layerDictionary[PdfDictionaryProperties.usage]) - as PdfDictionary?; + final PdfDictionary? usage = + PdfCrossTable.dereference( + layerDictionary[PdfDictionaryProperties.usage], + ) + as PdfDictionary?; if (usage != null) { - final PdfDictionary? printOption = PdfCrossTable.dereference( - usage[PdfDictionaryProperties.print]) as PdfDictionary?; + final PdfDictionary? printOption = + PdfCrossTable.dereference( + usage[PdfDictionaryProperties.print], + ) + as PdfDictionary?; if (printOption != null) { PdfLayerHelper.getHelper(layer).printOption = printOption; @@ -432,16 +483,22 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { void _checkLayerVisible(PdfDictionary ocProperties) { PdfArray? visible; - if (PdfDocumentHelper.getHelper(_document!) - .catalog - .containsKey(PdfDictionaryProperties.ocProperties)) { - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) as PdfDictionary?; + if (PdfDocumentHelper.getHelper( + _document!, + ).catalog.containsKey(PdfDictionaryProperties.ocProperties)) { + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null && defaultView.containsKey(PdfDictionaryProperties.ocgOff)) { - visible = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOff]) as PdfArray?; + visible = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOff], + ) + as PdfArray?; } if (visible != null) { for (int i = 0; i < visible.count; i++) { @@ -450,11 +507,13 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { if (pdfLayer != null) { pdfLayer.visible = false; if (PdfLayerHelper.getHelper(pdfLayer).dictionary != null && - PdfLayerHelper.getHelper(pdfLayer) - .dictionary! - .containsKey(PdfDictionaryProperties.visible)) { + PdfLayerHelper.getHelper( + pdfLayer, + ).dictionary!.containsKey(PdfDictionaryProperties.visible)) { PdfLayerHelper.getHelper(pdfLayer).dictionary!.setProperty( - PdfDictionaryProperties.visible, PdfBoolean(false)); + PdfDictionaryProperties.visible, + PdfBoolean(false), + ); } } } @@ -463,11 +522,17 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } void _checkParentLayer(PdfDictionary ocProperties) { - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) as PdfDictionary?; + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null) { - final PdfArray? array = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOrder]) as PdfArray?; + final PdfArray? array = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOrder], + ) + as PdfArray?; if (array != null) { _parsingLayerOrder(array, _layerDictionary); } @@ -476,12 +541,18 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { void _checkLayerLock(PdfDictionary ocProperties) { PdfArray? locked; - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) as PdfDictionary?; + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null && defaultView.containsKey(PdfDictionaryProperties.ocgLock)) { - locked = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgLock]) as PdfArray?; + locked = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgLock], + ) + as PdfArray?; } if (locked != null) { for (int i = 0; i < locked.count; i++) { @@ -495,8 +566,11 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } void _createLayerHierarchical(PdfDictionary ocProperties) { - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) as PdfDictionary?; + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null && defaultView.containsKey(PdfDictionaryProperties.ocgOrder)) { if (_layerDictionary.isNotEmpty) { @@ -510,17 +584,12 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { _addChildlayer(PdfLayerHelper.getHelper(pdfLayer).parent!); } else if (PdfLayerHelper.getHelper(pdfLayer).parent != null && PdfLayerHelper.getHelper(pdfLayer).child.isEmpty && - !PdfLayerHelper.getHelper(pdfLayer) - .parent! - .layers - ._helper - .list - .contains(pdfLayer)) { - PdfLayerHelper.getHelper(pdfLayer) - .parent! - .layers - ._helper - ._addNestedLayer(pdfLayer); + !PdfLayerHelper.getHelper( + pdfLayer, + ).parent!.layers._helper.list.contains(pdfLayer)) { + PdfLayerHelper.getHelper( + pdfLayer, + ).parent!.layers._helper._addNestedLayer(pdfLayer); } }); } @@ -543,8 +612,10 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } void _parsingLayerOrder( - PdfArray array, Map layerDictionary, - [PdfLayer? parent]) { + PdfArray array, + Map layerDictionary, [ + PdfLayer? parent, + ]) { PdfReferenceHolder reference; PdfLayer? layer; for (int i = 0; i < array.count; i++) { @@ -563,14 +634,17 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { PdfLayerHelper.getHelper(parent).parentLayer.add(parent); PdfLayerHelper.getHelper(layer!).parent = parent; } else { - for (int j = 0; - j < PdfLayerHelper.getHelper(parent).parentLayer.length; - j++) { + for ( + int j = 0; + j < PdfLayerHelper.getHelper(parent).parentLayer.length; + j++ + ) { if (!PdfLayerHelper.getHelper(layer!).parentLayer.contains( - PdfLayerHelper.getHelper(parent).parentLayer[j])) { - PdfLayerHelper.getHelper(layer!) - .parentLayer - .add(PdfLayerHelper.getHelper(parent).parentLayer[j]); + PdfLayerHelper.getHelper(parent).parentLayer[j], + )) { + PdfLayerHelper.getHelper(layer!).parentLayer.add( + PdfLayerHelper.getHelper(parent).parentLayer[j], + ); } } PdfLayerHelper.getHelper(layer!).parentLayer.add(parent); @@ -597,8 +671,11 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { _parsingLayerOrder(subarray, layerDictionary, parent); } else { parent = null; - _parsingLayerOrder(PdfCrossTable.dereference(array[i])! as PdfArray, - layerDictionary, parent); + _parsingLayerOrder( + PdfCrossTable.dereference(array[i])! as PdfArray, + layerDictionary, + parent, + ); } } } @@ -609,58 +686,87 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { if (_document != null) { dictionary = PdfDocumentHelper.getHelper(_document!).catalog; if (dictionary.containsKey(PdfDictionaryProperties.ocProperties)) { - final PdfDictionary? ocPropertie = PdfCrossTable.dereference( - dictionary[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + final PdfDictionary? ocPropertie = + PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; if (ocPropertie != null) { - final PdfArray? ocGroup = PdfCrossTable.dereference( - ocPropertie[PdfDictionaryProperties.ocg]) as PdfArray?; + final PdfArray? ocGroup = + PdfCrossTable.dereference( + ocPropertie[PdfDictionaryProperties.ocg], + ) + as PdfArray?; if (ocGroup != null) { _removeOCProperties( - ocGroup, PdfLayerHelper.getHelper(layer).referenceHolder); + ocGroup, + PdfLayerHelper.getHelper(layer).referenceHolder, + ); } if (ocPropertie.containsKey(PdfDictionaryProperties.defaultView)) { - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocPropertie[PdfDictionaryProperties.defaultView]) - as PdfDictionary?; + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocPropertie[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null) { - PdfArray? _on, off; + PdfArray? on, off; if (defaultView.containsKey(PdfDictionaryProperties.ocgOrder)) { - final PdfArray? order = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOrder]) as PdfArray?; + final PdfArray? order = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOrder], + ) + as PdfArray?; if (order != null) { _removeOrder(layer, order, []); // _removeOCProperties(order, layer.referenceHolder); } } if (defaultView.containsKey(PdfDictionaryProperties.ocgLock)) { - final PdfArray? locked = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgLock]) as PdfArray?; + final PdfArray? locked = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgLock], + ) + as PdfArray?; if (locked != null) { _removeOCProperties( - locked, PdfLayerHelper.getHelper(layer).referenceHolder); + locked, + PdfLayerHelper.getHelper(layer).referenceHolder, + ); } } if (defaultView.containsKey(PdfDictionaryProperties.ocgOff)) { - off = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOff]) as PdfArray?; + off = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOff], + ) + as PdfArray?; } if (defaultView.containsKey(PdfDictionaryProperties.ocgOn)) { - _on = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOn]) as PdfArray?; - } else if (_on == null && defaultView.containsKey('ON')) { - _on = PdfCrossTable.dereference(defaultView['ON']) as PdfArray?; + on = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOn], + ) + as PdfArray?; + } else if (defaultView.containsKey('ON')) { + on = PdfCrossTable.dereference(defaultView['ON']) as PdfArray?; } - if (defaultView - .containsKey(PdfDictionaryProperties.usageApplication)) { - final PdfArray? usage = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.usageApplication]) - as PdfArray?; + if (defaultView.containsKey( + PdfDictionaryProperties.usageApplication, + )) { + final PdfArray? usage = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.usageApplication], + ) + as PdfArray?; if (usage != null) { _removeOCProperties( - usage, PdfLayerHelper.getHelper(layer).referenceHolder); + usage, + PdfLayerHelper.getHelper(layer).referenceHolder, + ); } } - _removeVisible(layer, _on, off); + _removeVisible(layer, on, off); } } } @@ -671,16 +777,20 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } } - void _removeVisible(PdfLayer layer, PdfArray? _on, PdfArray? off) { + void _removeVisible(PdfLayer layer, PdfArray? on, PdfArray? off) { if (layer.visible) { - if (_on != null) { + if (on != null) { _removeOCProperties( - _on, PdfLayerHelper.getHelper(layer).referenceHolder); + on, + PdfLayerHelper.getHelper(layer).referenceHolder, + ); } } else { if (off != null) { _removeOCProperties( - off, PdfLayerHelper.getHelper(layer).referenceHolder); + off, + PdfLayerHelper.getHelper(layer).referenceHolder, + ); } } } @@ -690,10 +800,14 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { for (int i = 0; i < order.count; i++) { if (order[i] is PdfReferenceHolder) { final PdfReferenceHolder holder = order[i]! as PdfReferenceHolder; - if (identical(holder.object, - PdfLayerHelper.getHelper(layer).referenceHolder!.object) || - identical(holder.reference, - PdfLayerHelper.getHelper(layer).referenceHolder!.reference)) { + if (identical( + holder.object, + PdfLayerHelper.getHelper(layer).referenceHolder!.object, + ) || + identical( + holder.reference, + PdfLayerHelper.getHelper(layer).referenceHolder!.reference, + )) { if (i != order.count - 1) { if (order[i + 1] is PdfArray) { order.removeAt(i); @@ -726,7 +840,9 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } void _removeOCProperties( - PdfArray content, PdfReferenceHolder? referenceHolder) { + PdfArray content, + PdfReferenceHolder? referenceHolder, + ) { bool isChange = false; for (int i = 0; i < content.count; i++) { final IPdfPrimitive? primitive = content.elements[i]; @@ -767,17 +883,28 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } if (PdfLayerHelper.getHelper(layer).pages.isNotEmpty) { for (int i = 0; i < PdfLayerHelper.getHelper(layer).pages.length; i++) { - final PdfDictionary? resource = PdfCrossTable.dereference(PdfPageHelper - .getHelper(PdfLayerHelper.getHelper(layer).pages[i]) - .dictionary![PdfDictionaryProperties.resources]) as PdfDictionary?; + final PdfDictionary? resource = + PdfCrossTable.dereference( + PdfPageHelper.getHelper( + PdfLayerHelper.getHelper(layer).pages[i], + ).dictionary![PdfDictionaryProperties.resources], + ) + as PdfDictionary?; if (resource != null) { - properties = PdfCrossTable.dereference( - resource[PdfDictionaryProperties.properties]) as PdfDictionary?; - xObject = PdfCrossTable.dereference( - resource[PdfDictionaryProperties.xObject]) as PdfDictionary?; + properties = + PdfCrossTable.dereference( + resource[PdfDictionaryProperties.properties], + ) + as PdfDictionary?; + xObject = + PdfCrossTable.dereference( + resource[PdfDictionaryProperties.xObject], + ) + as PdfDictionary?; if (properties != null) { - if (properties - .containsKey(PdfLayerHelper.getHelper(layer).layerId)) { + if (properties.containsKey( + PdfLayerHelper.getHelper(layer).layerId, + )) { properties.remove(PdfLayerHelper.getHelper(layer).layerId); } } @@ -793,16 +920,17 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } } final PdfArray content = - PdfPageHelper.getHelper(PdfLayerHelper.getHelper(layer).pages[i]) - .contents; + PdfPageHelper.getHelper( + PdfLayerHelper.getHelper(layer).pages[i], + ).contents; for (int m = 0; m < content.count; m++) { List? stream = []; final PdfStream data = PdfStream(); final PdfStream pageContent = PdfCrossTable.dereference(content[m])! as PdfStream; if (PdfPageHelper.getHelper( - PdfLayerHelper.getHelper(layer).pages[i]) - .isLoadedPage) { + PdfLayerHelper.getHelper(layer).pages[i], + ).isLoadedPage) { pageContent.decompress(); } stream = pageContent.dataStream; @@ -814,13 +942,18 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { if (mOperator == 'BMC' || mOperator == 'EMC' || mOperator == 'BDC') { - _processBeginMarkContent(layer, mOperator, - recordCollection.recordCollection[j].operands, data); + _processBeginMarkContent( + layer, + mOperator, + recordCollection.recordCollection[j].operands, + data, + ); isSkip = true; } if (mOperator == PdfOperators.paintXObject) { if (PdfLayerHelper.getHelper(layer).xobject.contains( - recordCollection.recordCollection[j].operands![0])) { + recordCollection.recordCollection[j].operands![0], + )) { isSkip = true; } } @@ -837,12 +970,20 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { mOperator == PdfOperators.selectColorSpaceForNonStroking || mOperator == PdfOperators.selectColorSpaceForStroking) { if (!isSkip) { - _streamWrite(recordCollection.recordCollection[j].operands, - mOperator, false, data); + _streamWrite( + recordCollection.recordCollection[j].operands, + mOperator, + false, + data, + ); } } else if (!isSkip) { - _streamWrite(recordCollection.recordCollection[j].operands, - mOperator, true, data); + _streamWrite( + recordCollection.recordCollection[j].operands, + mOperator, + true, + data, + ); } isSkip = false; } @@ -859,8 +1000,12 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } } - 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')) { @@ -882,7 +1027,11 @@ class PdfLayerCollectionHelper extends PdfObjectCollectionHelper { } 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; 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 36ba22f90..5f48e3f82 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 @@ -1,7 +1,5 @@ import 'dart:ui'; -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/pages/pdf_page_settings.dart'; - import '../../interfaces/pdf_interface.dart'; import '../annotations/pdf_annotation_collection.dart'; import '../forms/pdf_field.dart'; @@ -24,6 +22,7 @@ import '../primitives/pdf_string.dart'; import 'enum.dart'; import 'pdf_page_layer.dart'; import 'pdf_page_layer_collection.dart'; +import 'pdf_page_settings.dart'; import 'pdf_section.dart'; import 'pdf_section_collection.dart'; @@ -39,7 +38,7 @@ import 'pdf_section_collection.dart'; /// brush: PdfBrushes.darkBlue, /// bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -58,7 +57,7 @@ class PdfPage implements IPdfWrapper { /// brush: PdfBrushes.darkBlue, /// bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -67,8 +66,11 @@ class PdfPage implements IPdfWrapper { _initialize(); } - PdfPage._fromDictionary(PdfDocument document, PdfCrossTable crossTable, - PdfDictionary dictionary) { + PdfPage._fromDictionary( + PdfDocument document, + PdfCrossTable crossTable, + PdfDictionary dictionary, + ) { _helper = PdfPageHelper(this); _helper._pdfDocument = document; _helper.dictionary = dictionary; @@ -96,7 +98,7 @@ class PdfPage implements IPdfWrapper { /// //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(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -106,9 +108,13 @@ class PdfPage implements IPdfWrapper { double width = 0; double height = 0; final IPdfPrimitive? mBox = _helper.dictionary!.getValue( - PdfDictionaryProperties.mediaBox, PdfDictionaryProperties.parent); + PdfDictionaryProperties.mediaBox, + PdfDictionaryProperties.parent, + ); final IPdfPrimitive? cBox = _helper.dictionary!.getValue( - PdfDictionaryProperties.cropBox, PdfDictionaryProperties.parent); + PdfDictionaryProperties.cropBox, + PdfDictionaryProperties.parent, + ); if (cBox != null && cBox is PdfArray) { final num c0 = (cBox[0]! as PdfNumber).value!; final num? c1 = (cBox[1]! as PdfNumber).value; @@ -145,7 +151,7 @@ class PdfPage implements IPdfWrapper { /// //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(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -155,22 +161,24 @@ class PdfPage implements IPdfWrapper { _helper._annotations = PdfAnnotationCollection(this); if (!_helper.dictionary!.containsKey(PdfDictionaryProperties.annots)) { _helper.dictionary![PdfDictionaryProperties.annots] = - PdfAnnotationCollectionHelper.getHelper(_helper._annotations!) - .internalAnnotations; + PdfAnnotationCollectionHelper.getHelper( + _helper._annotations!, + ).internalAnnotations; } - PdfAnnotationCollectionHelper.getHelper(_helper._annotations!) - .internalAnnotations = + PdfAnnotationCollectionHelper.getHelper( + _helper._annotations!, + ).internalAnnotations = _helper.dictionary![PdfDictionaryProperties.annots] as PdfArray?; } } else { - if (_helper._annotations == null) { + if (_helper._annotations == null || _helper.importAnnotation) { // Create the annotations. _helper.createAnnotations(_helper.getWidgetReferences()); } if (_helper._annotations == null || - (PdfAnnotationCollectionHelper.getHelper(_helper._annotations!) - .internalAnnotations - .count == + (PdfAnnotationCollectionHelper.getHelper( + _helper._annotations!, + ).internalAnnotations.count == 0 && _helper._annotations!.count != 0)) { _helper._annotations = PdfAnnotationCollectionHelper.load(this); @@ -190,7 +198,7 @@ class PdfPage implements IPdfWrapper { /// brush: PdfBrushes.darkBlue, /// bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -220,7 +228,7 @@ class PdfPage implements IPdfWrapper { /// 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(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -241,7 +249,7 @@ class PdfPage implements IPdfWrapper { /// //Create a new PDF page and gets the default layer /// PdfPageLayer defaultLayer = document.pages.add().defaultLayer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -254,7 +262,7 @@ class PdfPage implements IPdfWrapper { /// //Create a new PDF page and gets the default layer index /// int layerIndex = document.pages.add().defaultLayerIndex; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -295,7 +303,7 @@ class PdfPage implements IPdfWrapper { /// //Rotation of the PDF page /// PdfPageRotateAngle rotation = document.pages[0].rotation; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -307,8 +315,9 @@ class PdfPage implements IPdfWrapper { set rotation(PdfPageRotateAngle angle) { if (_helper.isLoadedPage && rotation != angle) { _rotation = angle; - _helper.dictionary![PdfDictionaryProperties.rotate] = - PdfNumber(PdfSectionCollectionHelper.rotateFactor * angle.index); + _helper.dictionary![PdfDictionaryProperties.rotate] = PdfNumber( + PdfSectionCollectionHelper.rotateFactor * angle.index, + ); } } @@ -328,17 +337,16 @@ class PdfPage implements IPdfWrapper { /// brush: PdfBrushes.darkBlue, /// bounds: Rect.fromLTWH(400, 600, clientSize.width, clientSize.height)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` Size getClientSize() { return _helper.isLoadedPage ? size - : PdfSectionHelper.getHelper(_helper.section!) - .getActualBounds(this, true) - .size - .size; + : PdfSectionHelper.getHelper( + _helper.section!, + ).getActualBounds(this, true).size.size; } /// Creates a template from the page content. @@ -358,7 +366,7 @@ class PdfPage implements IPdfWrapper { /// page.graphics.drawPdfTemplate( /// template, Offset(20, 0), Size(page.size.width / 2, page.size.height)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -379,30 +387,38 @@ class PdfPage implements IPdfWrapper { void _drawPageTemplates(PdfDocument doc) { // Draw Background templates. - final bool hasBackTemplates = PdfSectionHelper.getHelper(_helper.section!) - .containsTemplates(doc, this, false); + final bool hasBackTemplates = PdfSectionHelper.getHelper( + _helper.section!, + ).containsTemplates(doc, this, false); if (hasBackTemplates) { - final PdfPageLayer backLayer = - PdfPageLayerHelper.fromClipPageTemplate(this, false); - final PdfPageLayerCollection _layer = PdfPageLayerCollection(this); - _layers = _layer; + final PdfPageLayer backLayer = PdfPageLayerHelper.fromClipPageTemplate( + this, + false, + ); + final PdfPageLayerCollection layer = PdfPageLayerCollection(this); + _layers = layer; _layers!.addLayer(backLayer); - PdfSectionHelper.getHelper(_helper.section!) - .drawTemplates(this, backLayer, doc, false); + PdfSectionHelper.getHelper( + _helper.section!, + ).drawTemplates(this, backLayer, doc, false); } // Draw Foreground templates. - final bool hasFrontTemplates = PdfSectionHelper.getHelper(_helper.section!) - .containsTemplates(doc, this, true); + final bool hasFrontTemplates = PdfSectionHelper.getHelper( + _helper.section!, + ).containsTemplates(doc, this, true); if (hasFrontTemplates) { - final PdfPageLayer frontLayer = - PdfPageLayerHelper.fromClipPageTemplate(this, false); - final PdfPageLayerCollection _layer = PdfPageLayerCollection(this); - _layers = _layer; + final PdfPageLayer frontLayer = PdfPageLayerHelper.fromClipPageTemplate( + this, + false, + ); + final PdfPageLayerCollection layer = PdfPageLayerCollection(this); + _layers = layer; _layers!.addLayer(frontLayer); - PdfSectionHelper.getHelper(_helper.section!) - .drawTemplates(this, frontLayer, doc, true); + PdfSectionHelper.getHelper( + _helper.section!, + ).drawTemplates(this, frontLayer, doc, true); } } @@ -414,7 +430,8 @@ class PdfPage implements IPdfWrapper { if (parent[PdfDictionaryProperties.rotate] is PdfReferenceHolder) { angle = (parent[PdfDictionaryProperties.rotate]! as PdfReferenceHolder) - .object as PdfNumber?; + .object + as PdfNumber?; } else { angle = parent[PdfDictionaryProperties.rotate] as PdfNumber?; } @@ -423,9 +440,10 @@ class PdfPage implements IPdfWrapper { IPdfPrimitive? parentPrimitive = parent[PdfDictionaryProperties.parent]; if (parentPrimitive != null) { parentPrimitive = PdfCrossTable.dereference(parentPrimitive); - parent = parentPrimitive != null && parentPrimitive is PdfDictionary - ? parentPrimitive - : null; + parent = + parentPrimitive != null && parentPrimitive is PdfDictionary + ? parentPrimitive + : null; } else { parent = null; } @@ -437,8 +455,9 @@ class PdfPage implements IPdfWrapper { if (angle.value!.toInt() < 0) { angle.value = 360 + angle.value!.toInt(); } - final PdfPageRotateAngle rotateAngle = - _getRotationFromAngle(angle.value! ~/ 90); + final PdfPageRotateAngle rotateAngle = _getRotationFromAngle( + angle.value! ~/ 90, + ); return rotateAngle; } @@ -473,14 +492,20 @@ class PdfPageHelper { /// internal field late PdfPage base; + /// internal field + bool importAnnotation = false; + /// internal method static PdfPageHelper getHelper(PdfPage base) { return base._helper; } /// internal method - static PdfPage fromDictionary(PdfDocument document, PdfCrossTable crossTable, - PdfDictionary dictionary) { + static PdfPage fromDictionary( + PdfDocument document, + PdfCrossTable crossTable, + PdfDictionary dictionary, + ) { return PdfPage._fromDictionary(document, crossTable, dictionary); } @@ -533,9 +558,9 @@ class PdfPageHelper { /// internal method Offset get origin { if (section != null) { - return PdfPageSettingsHelper.getHelper(section!.pageSettings) - .origin - .offset; + return PdfPageSettingsHelper.getHelper( + section!.pageSettings, + ).origin.offset; } else { return Offset.zero; } @@ -549,8 +574,8 @@ class PdfPageHelper { if (section != null) { if (PdfSectionHelper.getHelper(section!).parent != null) { return PdfSectionCollectionHelper.getHelper( - PdfSectionHelper.getHelper(section!).parent!) - .document; + PdfSectionHelper.getHelper(section!).parent!, + ).document; } else if (PdfSectionHelper.getHelper(section!).document != null) { return PdfSectionHelper.getHelper(section!).document; } else { @@ -570,12 +595,15 @@ class PdfPageHelper { Rect get cropBox { if (_cBox.isEmpty) { final IPdfPrimitive? cBox = dictionary!.getValue( - PdfDictionaryProperties.cropBox, PdfDictionaryProperties.parent); + PdfDictionaryProperties.cropBox, + PdfDictionaryProperties.parent, + ); if (cBox != null && cBox is PdfArray) { final double width = (cBox[2]! as PdfNumber).value!.toDouble(); - final double height = (cBox[3]! as PdfNumber).value != 0 - ? (cBox[3]! as PdfNumber).value!.toDouble() - : (cBox[1]! as PdfNumber).value!.toDouble(); + final double height = + (cBox[3]! as PdfNumber).value != 0 + ? (cBox[3]! as PdfNumber).value!.toDouble() + : (cBox[1]! as PdfNumber).value!.toDouble(); final double x = (cBox[0]! as PdfNumber).value!.toDouble(); final double y = (cBox[1]! as PdfNumber).value!.toDouble(); _cBox = _calculateBounds(x, y, width, height); @@ -588,12 +616,15 @@ class PdfPageHelper { Rect get mediaBox { if (_mBox.isEmpty) { final IPdfPrimitive? mBox = dictionary!.getValue( - PdfDictionaryProperties.mediaBox, PdfDictionaryProperties.parent); + PdfDictionaryProperties.mediaBox, + PdfDictionaryProperties.parent, + ); if (mBox != null && mBox is PdfArray) { final double width = (mBox[2]! as PdfNumber).value!.toDouble(); - final double height = (mBox[3]! as PdfNumber).value != 0 - ? (mBox[3]! as PdfNumber).value!.toDouble() - : (mBox[1]! as PdfNumber).value!.toDouble(); + final double height = + (mBox[3]! as PdfNumber).value != 0 + ? (mBox[3]! as PdfNumber).value!.toDouble() + : (mBox[1]! as PdfNumber).value!.toDouble(); final double x = (mBox[0]! as PdfNumber).value!.toDouble(); final double y = (mBox[1]! as PdfNumber).value!.toDouble(); _mBox = _calculateBounds(x, y, width, height); @@ -681,15 +712,17 @@ class PdfPageHelper { dictionary![PdfDictionaryProperties.resources] = obj; _resources = PdfResources(obj); final PdfDictionary xobjects = PdfDictionary(); - if (_resources! - .containsKey(PdfDictionaryProperties.xObject)) { + if (_resources!.containsKey( + PdfDictionaryProperties.xObject, + )) { final PdfDictionary? xObject = _resources![PdfDictionaryProperties.xObject] as PdfDictionary?; if (xObject != null) { final IPdfPrimitive? content = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.contents]); + dictionary![PdfDictionaryProperties.contents], + ); if (content != null) { if (content is PdfArray) { for (int i = 0; i < content.count; i++) { @@ -703,7 +736,9 @@ class PdfPageHelper { } } _resources!.setProperty( - PdfDictionaryProperties.xObject, xobjects); + PdfDictionaryProperties.xObject, + xobjects, + ); setResources(_resources); } } @@ -716,8 +751,9 @@ class PdfPageHelper { _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!.containsValue( + _resources![key], + )) { isValueEqual = true; } } else { @@ -743,8 +779,11 @@ class PdfPageHelper { _resources = PdfResources(dic); dictionary![PdfDictionaryProperties.resources] = _resources; if (dictionary!.containsKey(PdfDictionaryProperties.parent)) { - final PdfDictionary? parentDic = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.parent]) as PdfDictionary?; + final PdfDictionary? parentDic = + PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.parent], + ) + as PdfDictionary?; if (parentDic != null && parentDic.containsKey(PdfDictionaryProperties.resources)) { final IPdfPrimitive? resource = @@ -776,38 +815,47 @@ class PdfPageHelper { /// internal method void createAnnotations(List widgetReferences) { - PdfArray? annots; + IPdfPrimitive? annots; if (dictionary!.containsKey(PdfDictionaryProperties.annots)) { - annots = crossTable! - .getObject(dictionary![PdfDictionaryProperties.annots]) as PdfArray?; - if (annots != null) { + annots = crossTable!.getObject( + dictionary![PdfDictionaryProperties.annots], + ); + if (annots != null && annots is PdfArray) { for (int count = 0; count < annots.count; ++count) { - final PdfDictionary? annotDicrionary = - crossTable!.getObject(annots[count]) as PdfDictionary?; - final PdfReferenceHolder annotReference = - annots[count]! as PdfReferenceHolder; + PdfDictionary? annotDictionary; + if (crossTable!.getObject(annots[count]) is PdfDictionary) { + annotDictionary = + crossTable!.getObject(annots[count]) as PdfDictionary?; + } + PdfReferenceHolder? annotReference; + if (crossTable!.getObject(annots[count]) is PdfReferenceHolder) { + annotReference = + crossTable!.getObject(annots[count]) as PdfReferenceHolder?; + } if (document != null && PdfDocumentHelper.getHelper(document!).crossTable.encryptor != null && - PdfDocumentHelper.getHelper(document!) - .crossTable - .encryptor! - .encryptAttachmentOnly!) { - if (annotDicrionary != null && - annotDicrionary.containsKey(PdfDictionaryProperties.subtype)) { - final IPdfPrimitive? primitive = annotDicrionary - .items![PdfName(PdfDictionaryProperties.subtype)]; + PdfDocumentHelper.getHelper( + document!, + ).crossTable.encryptor!.encryptAttachmentOnly!) { + if (annotDictionary != null && + annotDictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? primitive = + annotDictionary.items![PdfName( + PdfDictionaryProperties.subtype, + )]; if (primitive is PdfName && primitive.name == 'FileAttachment' && - annotDicrionary.containsKey(PdfDictionaryProperties.fs)) { + annotDictionary.containsKey(PdfDictionaryProperties.fs)) { final IPdfPrimitive? file = - annotDicrionary[PdfDictionaryProperties.fs]; + annotDictionary[PdfDictionaryProperties.fs]; if (file != null && file is PdfReferenceHolder) { final IPdfPrimitive? streamDictionary = file.object; if (streamDictionary != null && streamDictionary is PdfDictionary && - streamDictionary - .containsKey(PdfDictionaryProperties.ef)) { + streamDictionary.containsKey( + PdfDictionaryProperties.ef, + )) { PdfDictionary? attachmentStream; IPdfPrimitive? holder = streamDictionary[PdfDictionaryProperties.ef]; @@ -820,38 +868,44 @@ class PdfPageHelper { attachmentStream = holder; } if (attachmentStream != null && - attachmentStream - .containsKey(PdfDictionaryProperties.f)) { + attachmentStream.containsKey( + PdfDictionaryProperties.f, + )) { holder = attachmentStream[PdfDictionaryProperties.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 (PdfDocumentHelper.getHelper(document!) - .isLoadedDocument) { + if (PdfDocumentHelper.getHelper( + document!, + ).isLoadedDocument) { if (document!.onPdfPassword != null && - PdfDocumentHelper.getHelper(document!) - .password == + PdfDocumentHelper.getHelper( + document!, + ).password == '') { final PdfPasswordArgs args = PdfPasswordArgsHelper.load(); - PdfDocumentHelper.getHelper(document!) - .setUserPassword(args); + PdfDocumentHelper.getHelper( + document!, + ).setUserPassword(args); PdfDocumentHelper.getHelper(document!).password = args.attachmentOpenPassword; } - PdfDocumentHelper.getHelper(document!) - .checkEncryption( - PdfDocumentHelper.getHelper(document!) - .crossTable - .encryptor! - .encryptAttachmentOnly); + PdfDocumentHelper.getHelper( + document!, + ).checkEncryption( + PdfDocumentHelper.getHelper( + document!, + ).crossTable.encryptor!.encryptAttachmentOnly, + ); encryptedObj.decrypt( - PdfDocumentHelper.getHelper(document!) - .crossTable - .encryptor!, - reference!.objNum); + PdfDocumentHelper.getHelper( + document!, + ).crossTable.encryptor!, + reference!.objNum, + ); } } } @@ -861,42 +915,55 @@ class PdfPageHelper { } } } - if (annotDicrionary != null && - annotDicrionary.containsKey(PdfDictionaryProperties.subtype)) { - final PdfName? name = annotDicrionary - .items![PdfName(PdfDictionaryProperties.subtype)] as PdfName?; - if (name != null && name.name.toString() == 'Widget') { - if (annotDicrionary.containsKey(PdfDictionaryProperties.parent)) { - final PdfDictionary? annotParentDictionary = (annotDicrionary - .items![PdfName(PdfDictionaryProperties.parent)]! - as PdfReferenceHolder) - .object as PdfDictionary?; + if (annotDictionary != null && + annotDictionary.containsKey(PdfDictionaryProperties.subtype)) { + final PdfName? name = + annotDictionary.items![PdfName(PdfDictionaryProperties.subtype)] + as PdfName?; + if (name != null && name.name.toString() != 'Widget') { + if (!terminalAnnotation.contains(annotDictionary)) { + terminalAnnotation.add(annotDictionary); + } + } else if (name != null && name.name.toString() == 'Widget') { + if (annotDictionary.containsKey(PdfDictionaryProperties.parent)) { + final PdfDictionary? annotParentDictionary = + (annotDictionary.items![PdfName( + PdfDictionaryProperties.parent, + )]! + as PdfReferenceHolder) + .object + as PdfDictionary?; if (annotParentDictionary != null) { - if (!annotParentDictionary - .containsKey(PdfDictionaryProperties.fields)) { - if (annotReference.reference != null && - !widgetReferences - .contains(annotReference.reference!.objNum)) { - if (!PdfFormHelper.getHelper(document!.form) - .terminalFields - .contains(annotParentDictionary)) { - PdfFormHelper.getHelper(document!.form) - .terminalFields - .add(annotParentDictionary); + if (!annotParentDictionary.containsKey( + PdfDictionaryProperties.fields, + )) { + if (annotReference != null && + annotReference.reference != null && + !widgetReferences.contains( + annotReference.reference!.objNum, + )) { + if (!PdfFormHelper.getHelper( + document!.form, + ).terminalFields.contains(annotParentDictionary)) { + PdfFormHelper.getHelper( + document!.form, + ).terminalFields.add(annotParentDictionary); } - } else if (annotParentDictionary - .containsKey(PdfDictionaryProperties.kids) && + } else if (annotParentDictionary.containsKey( + PdfDictionaryProperties.kids, + ) && annotParentDictionary.count == 1) { - annotDicrionary.remove(PdfDictionaryProperties.parent); + annotDictionary.remove(PdfDictionaryProperties.parent); } - } else if (!annotParentDictionary - .containsKey(PdfDictionaryProperties.kids)) { - annotDicrionary.remove(PdfDictionaryProperties.parent); + } else if (!annotParentDictionary.containsKey( + PdfDictionaryProperties.kids, + )) { + annotDictionary.remove(PdfDictionaryProperties.parent); } } - } else if (!PdfFormHelper.getHelper(document!.form) - .terminalFields - .contains(annotDicrionary)) { + } else if (!PdfFormHelper.getHelper( + document!.form, + ).terminalFields.contains(annotDictionary)) { Map>? widgetDictionary = PdfFormHelper.getHelper(document!.form).widgetDictionary; if (widgetDictionary == null) { @@ -905,22 +972,23 @@ class PdfPageHelper { widgetDictionary = PdfFormHelper.getHelper(document!.form).widgetDictionary; } - if (annotDicrionary.containsKey(PdfDictionaryProperties.t)) { - final String? fieldName = (annotDicrionary - .items![PdfName(PdfDictionaryProperties.t)]! - as PdfString) - .value; + if (annotDictionary.containsKey(PdfDictionaryProperties.t)) { + final String? fieldName = + (annotDictionary.items![PdfName( + PdfDictionaryProperties.t, + )]! + as PdfString) + .value; if (widgetDictionary!.containsKey(fieldName)) { final List dict = widgetDictionary[fieldName]!; - dict.add(annotDicrionary); + dict.add(annotDictionary); } else { if (!PdfFormFieldCollectionHelper.getHelper( - document!.form.fields) - .addedFieldNames - .contains(fieldName)) { + document!.form.fields, + ).addedFieldNames.contains(fieldName)) { widgetDictionary[fieldName] = [ - annotDicrionary + annotDictionary, ]; } } @@ -928,7 +996,7 @@ class PdfPageHelper { } } } - if (annotReference.reference != null) { + if (annotReference != null && annotReference.reference != null) { if (!annotsReference.contains(annotReference.reference!)) { annotsReference.add(annotReference.reference!); } @@ -941,9 +1009,12 @@ class PdfPageHelper { final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); if (helper.isLoadedField) { final IPdfPrimitive widget = helper.getWidgetAnnotation( - helper.dictionary!, helper.crossTable); - final PdfReference widgetReference = - crossTable!.getReference(widget); + helper.dictionary!, + helper.crossTable, + ); + final PdfReference widgetReference = crossTable!.getReference( + widget, + ); if (annotReference.reference!.objNum == widgetReference.objNum && annotReference.reference!.genNum == @@ -953,12 +1024,13 @@ class PdfPageHelper { } } } - if (annotDicrionary != null && annotReference.reference != null) { - if (!widgetReferences - .contains(annotReference.reference!.objNum) && + if (annotDictionary != null && annotReference.reference != null) { + if (!widgetReferences.contains( + annotReference.reference!.objNum, + ) && !skip) { - if (!terminalAnnotation.contains(annotDicrionary)) { - terminalAnnotation.add(annotDicrionary); + if (!terminalAnnotation.contains(annotDictionary)) { + terminalAnnotation.add(annotDictionary); } } } @@ -966,48 +1038,67 @@ class PdfPageHelper { } } } + if (importAnnotation) { + importAnnotation = false; + } _annotations = PdfAnnotationCollectionHelper.load(base); } PdfTemplate _getContent() { final List combinedData = - PdfPageLayerCollectionHelper.getHelper(base.layers) - .combineContent(false)!; - final PdfDictionary? resources = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.resources]) as PdfDictionary?; + PdfPageLayerCollectionHelper.getHelper( + base.layers, + ).combineContent(false)!; + final PdfDictionary? resources = + PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.resources], + ) + as PdfDictionary?; final PdfTemplate template = PdfTemplateHelper.internal( - origin, base.size, combinedData, resources!, isLoadedPage); + origin, + base.size, + combinedData, + resources!, + isLoadedPage, + this, + ); return template; } /// internal method List getWidgetReferences() { - final List _widgetReferences = []; + final List widgetReferences = []; final PdfFormFieldCollection collection = document!.form.fields; for (int i = 0; i < collection.count; i++) { final PdfField field = collection[i]; final PdfFieldHelper helper = PdfFieldHelper.getHelper(field); if (helper.isLoadedField) { - final IPdfPrimitive widget = - helper.getWidgetAnnotation(helper.dictionary!, helper.crossTable); + final IPdfPrimitive widget = helper.getWidgetAnnotation( + helper.dictionary!, + helper.crossTable, + ); final Map widgetReference = - PdfDocumentHelper.getHelper(document!) - .objects - .getReference(widget, false); - _widgetReferences.add(((widgetReference['isNew'] as bool) - ? crossTable!.getReference(widget).objNum - : (widgetReference['reference'] as PdfReference).objNum)! - .toSigned(64)); + PdfDocumentHelper.getHelper( + 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; + return widgetReferences; } /// internal method PdfArray? obtainAnnotations() { final IPdfPrimitive? obj = dictionary!.getValue( - PdfDictionaryProperties.annots, PdfDictionaryProperties.parent); + PdfDictionaryProperties.annots, + PdfDictionaryProperties.parent, + ); return (obj != null && obj is PdfReferenceHolder ? obj.object : obj) as PdfArray?; } 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 11497247c..88023c38f 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 @@ -1,6 +1,7 @@ import 'dart:ui'; import '../../interfaces/pdf_interface.dart'; +import '../forms/pdf_form_field_collection.dart'; import '../graphics/pdf_graphics.dart'; import '../graphics/pdf_margins.dart'; import '../io/pdf_constants.dart'; @@ -36,7 +37,9 @@ class PdfPageCollection { } PdfPageCollection._fromCrossTable( - PdfDocument document, PdfCrossTable crossTable) { + PdfDocument document, + PdfCrossTable crossTable, + ) { _helper = PdfPageCollectionHelper(this); _helper.document = document; _helper._crossTable = crossTable; @@ -64,8 +67,10 @@ class PdfPageCollection { int get count { if (PdfDocumentHelper.getHelper(_helper.document!).isLoadedDocument) { int tempCount = 0; - final IPdfPrimitive? obj = PdfDocumentHelper.getHelper(_helper.document!) - .catalog[PdfDictionaryProperties.pages]; + final IPdfPrimitive? obj = + PdfDocumentHelper.getHelper( + _helper.document!, + ).catalog[PdfDictionaryProperties.pages]; final PdfDictionary? node = PdfCrossTable.dereference(obj) as PdfDictionary?; if (node != null) { @@ -136,18 +141,21 @@ class PdfPageCollection { /// [margins] - The page margin. /// [rotation] - The PDF page rotation angle. /// [orientation] - The PDF page orientation. - PdfPage insert(int index, - [Size? size, - PdfMargins? margins, - PdfPageRotateAngle? rotation, - PdfPageOrientation? orientation]) { + PdfPage insert( + int index, [ + Size? size, + PdfMargins? margins, + PdfPageRotateAngle? rotation, + PdfPageOrientation? orientation, + ]) { if (size == null || size.isEmpty) { size = PdfPageSize.a4; } rotation ??= PdfPageRotateAngle.rotateAngle0; - orientation ??= (size.width > size.height) - ? PdfPageOrientation.landscape - : PdfPageOrientation.portrait; + orientation ??= + (size.width > size.height) + ? PdfPageOrientation.landscape + : PdfPageOrientation.portrait; final PdfPage page = PdfPage(); final PdfPageSettings settings = PdfPageSettings(size, orientation); if (margins == null) { @@ -161,8 +169,11 @@ class PdfPageCollection { PdfSectionHelper.getHelper(sec).add(page); PdfDictionary dic = IPdfWrapper.getElement(sec)! as PdfDictionary; int? localIndex = 0; - final Map result = - _getValidParent(index, localIndex, false); + final Map result = _getValidParent( + index, + localIndex, + false, + ); final PdfDictionary parent = result['node'] as PdfDictionary; localIndex = result['index'] as int?; if (parent.containsKey(PdfDictionaryProperties.rotate)) { @@ -171,9 +182,8 @@ class PdfPageCollection { parent[PdfDictionaryProperties.rotate]! as PdfNumber; if (parentRotation.value!.toInt() != rotationValue && (!dic.containsKey(PdfDictionaryProperties.rotate))) { - PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.rotate] = - PdfNumber(rotationValue); + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .rotate] = PdfNumber(rotationValue); } } dic[PdfDictionaryProperties.parent] = PdfReferenceHolder(parent); @@ -184,8 +194,9 @@ class PdfPageCollection { _helper._pageCache![dic] = page; page.graphics.colorSpace = _helper.document!.colorSpace; PdfPageLayerHelper.getHelper( - PdfGraphicsHelper.getHelper(page.graphics).layer!) - .colorSpace = _helper.document!.colorSpace; + PdfGraphicsHelper.getHelper(page.graphics).layer!, + ).colorSpace = + _helper.document!.colorSpace; return page; } @@ -199,7 +210,7 @@ class PdfPageCollection { /// //Remove the first page. /// document.pages.remove(page); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` void remove(PdfPage page) { @@ -214,7 +225,7 @@ class PdfPageCollection { /// //Remove the page at index 0. /// document.pages.removeAt(0); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` void removeAt(int index) { @@ -226,8 +237,9 @@ class PdfPageCollection { //Implementation int _getNodeCount(PdfDictionary node) { - final PdfNumber? number = _helper._crossTable! - .getObject(node[PdfDictionaryProperties.count]) as PdfNumber?; + final PdfNumber? number = + _helper._crossTable!.getObject(node[PdfDictionaryProperties.count]) + as PdfNumber?; return (number == null) ? 0 : number.value!.toInt(); } @@ -263,8 +275,9 @@ class PdfPageCollection { return _helper.getPage(node); } else { if (_helper._section != null) { - return PdfSectionHelper.getHelper(_helper._section!) - .getPageByIndex(index); + return PdfSectionHelper.getHelper( + _helper._section!, + ).getPageByIndex(index); } else { if (index < 0 || index >= count) { throw ArgumentError.value('index', 'out of range'); @@ -278,8 +291,9 @@ class PdfPageCollection { sectionCount = PdfSectionHelper.getHelper(section).count; pageIndex = index - sectionStartIndex; if (index >= sectionStartIndex && pageIndex < sectionCount) { - page = - PdfSectionHelper.getHelper(section).getPageByIndex(pageIndex); + page = PdfSectionHelper.getHelper( + section, + ).getPageByIndex(pageIndex); break; } sectionStartIndex += sectionCount; @@ -293,18 +307,24 @@ class PdfPageCollection { while (parent != null) { final int count = _getNodeCount(parent) + 1; parent[PdfDictionaryProperties.count] = PdfNumber(count); - parent = PdfCrossTable.dereference(parent[PdfDictionaryProperties.parent]) - as PdfDictionary?; + parent = + PdfCrossTable.dereference(parent[PdfDictionaryProperties.parent]) + 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 = PdfDocumentHelper.getHelper(_helper.document!) - .catalog[PdfDictionaryProperties.pages]; + final IPdfPrimitive? obj = + PdfDocumentHelper.getHelper( + _helper.document!, + ).catalog[PdfDictionaryProperties.pages]; PdfDictionary node = _helper._crossTable!.getObject(obj)! as PdfDictionary; int lowIndex = 0; localIndex = _getNodeCount(node); @@ -383,8 +403,10 @@ class PdfPageCollection { if (index < 0 && index > count) { throw ArgumentError.value(index, 'page index is not within range'); } - _pageCatalog ??= PdfDocumentHelper.getHelper(_helper.document!) - .catalog[PdfDictionaryProperties.pages]; + _pageCatalog ??= + PdfDocumentHelper.getHelper( + _helper.document!, + ).catalog[PdfDictionaryProperties.pages]; bool isNodeChanged = false; PdfDictionary? node; if (_nodeDictionary == null) { @@ -447,21 +469,42 @@ class PdfPageCollection { int? tempLocalIndex = 0; if (kids!.count == count) { - Map returnValue = _getParentNode(kidStartIndex, kids, - 0, index, tempNode, tempLocalIndex, isParentNodeFetched); + Map returnValue = _getParentNode( + kidStartIndex, + kids, + 0, + index, + tempNode, + tempLocalIndex, + isParentNodeFetched, + ); tempNode = returnValue['tempNode'] as PdfDictionary?; tempLocalIndex = returnValue['tempLocalIndex'] as int?; isParentNodeFetched = returnValue['isParentNodeFetched'] as bool?; if (!isParentNodeFetched!) { returnValue = _getParentNode( - 0, kids, 0, index, tempNode, tempLocalIndex, isParentNodeFetched); + 0, + kids, + 0, + index, + tempNode, + tempLocalIndex, + isParentNodeFetched, + ); tempNode = returnValue['tempNode'] as PdfDictionary?; tempLocalIndex = returnValue['tempLocalIndex'] as int?; isParentNodeFetched = returnValue['isParentNodeFetched'] as bool?; } } else { final Map returnValue = _getParentNode( - 0, kids, 0, index, tempNode, tempLocalIndex, isParentNodeFetched); + 0, + kids, + 0, + index, + tempNode, + tempLocalIndex, + isParentNodeFetched, + ); tempNode = returnValue['tempNode'] as PdfDictionary?; tempLocalIndex = returnValue['tempLocalIndex'] as int?; isParentNodeFetched = returnValue['isParentNodeFetched'] as bool?; @@ -487,13 +530,14 @@ class PdfPageCollection { } Map _getParentNode( - int kidStartIndex, - PdfArray kids, - int lowIndex, - int pageIndex, - PdfDictionary? node, - int? localIndex, - bool? isParentFetched) { + int kidStartIndex, + PdfArray kids, + int lowIndex, + int pageIndex, + PdfDictionary? node, + int? localIndex, + bool? isParentFetched, + ) { isParentFetched = false; node = null; localIndex = -1; @@ -535,7 +579,7 @@ class PdfPageCollection { return { 'tempNode': node, 'tempLocalIndex': localIndex, - 'isParentNodeFetched': isParentFetched + 'isParentNodeFetched': isParentFetched, }; } @@ -546,12 +590,14 @@ class PdfPageCollection { int _countPages() { final PdfSectionCollection sectionCollection = _helper.document!.sections!; int count = 0; - for (int i = 0; - i < - PdfSectionCollectionHelper.getHelper(sectionCollection) - .sections - .length; - i++) { + for ( + int i = 0; + i < + PdfSectionCollectionHelper.getHelper( + sectionCollection, + ).sections.length; + i++ + ) { final PdfSection section = sectionCollection[i]; count += PdfSectionHelper.getHelper(section).count; } @@ -562,8 +608,9 @@ class PdfPageCollection { if (PdfDocumentHelper.getHelper(_helper.document!).isLoadedDocument && index > -1) { final Map? pageToBookmarkDic = - PdfDocumentHelper.getHelper(_helper.document!) - .createBookmarkDestinationDictionary(); + PdfDocumentHelper.getHelper( + _helper.document!, + ).createBookmarkDestinationDictionary(); if (pageToBookmarkDic != null) { List? bookmarks; if (pageToBookmarkDic.containsKey(page)) { @@ -573,19 +620,19 @@ class PdfPageCollection { for (int i = 0; i < bookmarks.length; i++) { if (bookmarks[i] is PdfBookmarkBase) { final PdfBookmarkBase current = bookmarks[i] as PdfBookmarkBase; - if (PdfBookmarkBaseHelper.getHelper(current) - .dictionary! - .containsKey(PdfDictionaryProperties.a)) { - PdfBookmarkBaseHelper.getHelper(current) - .dictionary! - .remove(PdfDictionaryProperties.a); + if (PdfBookmarkBaseHelper.getHelper( + current, + ).dictionary!.containsKey(PdfDictionaryProperties.a)) { + PdfBookmarkBaseHelper.getHelper( + current, + ).dictionary!.remove(PdfDictionaryProperties.a); } - if (PdfBookmarkBaseHelper.getHelper(current) - .dictionary! - .containsKey(PdfDictionaryProperties.dest)) { - PdfBookmarkBaseHelper.getHelper(current) - .dictionary! - .remove(PdfDictionaryProperties.dest); + if (PdfBookmarkBaseHelper.getHelper( + current, + ).dictionary!.containsKey(PdfDictionaryProperties.dest)) { + PdfBookmarkBaseHelper.getHelper( + current, + ).dictionary!.remove(PdfDictionaryProperties.dest); } } } @@ -644,6 +691,7 @@ class PdfPageCollection { } } if (remove != null) { + _removeFormFields(remove); kids.remove(remove); if (kids.count == 0 && parent.containsKey(PdfDictionaryProperties.parent)) { @@ -685,6 +733,15 @@ class PdfPageCollection { } } _updateCountDecrement(parent); + dic.isSkip = true; + } + } + + void _removeFormFields(PdfReferenceHolder pageHolder) { + if (PdfDocumentHelper.getHelper(_helper.document!).isLoadedDocument) { + PdfFormFieldCollectionHelper.getHelper( + PdfPageCollectionHelper.getHelper(this).document!.form.fields, + ).removeContainingField(pageHolder); } } @@ -693,8 +750,9 @@ class PdfPageCollection { int count = _getNodeCount(parent) - 1; if (count == 0) { final PdfDictionary node = parent; - final IPdfPrimitive? result = - PdfCrossTable.dereference(parent[PdfDictionaryProperties.parent]); + final IPdfPrimitive? result = PdfCrossTable.dereference( + parent[PdfDictionaryProperties.parent], + ); if (result != null && result is PdfDictionary) { final IPdfPrimitive? kids = result[PdfDictionaryProperties.kids]; if (kids != null && kids is PdfArray) { @@ -704,8 +762,9 @@ class PdfPageCollection { } count = _getNodeCount(parent) - 1; parent[PdfDictionaryProperties.count] = PdfNumber(count); - final IPdfPrimitive? primitive = - PdfCrossTable.dereference(parent[PdfDictionaryProperties.parent]); + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + parent[PdfDictionaryProperties.parent], + ); parent = (primitive != null && primitive is PdfDictionary) ? primitive : null; } @@ -732,7 +791,9 @@ class PdfPageCollectionHelper { /// internal method static PdfPageCollection fromCrossTable( - PdfDocument document, PdfCrossTable crossTable) { + PdfDocument document, + PdfCrossTable crossTable, + ) { return PdfPageCollection._fromCrossTable(document, crossTable); } @@ -764,16 +825,15 @@ class PdfPageCollectionHelper { PdfSection _getLastSection() { final PdfSectionCollection sectionCollection = document!.sections!; - if (PdfSectionCollectionHelper.getHelper(sectionCollection) - .sections - .isEmpty) { + if (PdfSectionCollectionHelper.getHelper( + sectionCollection, + ).sections.isEmpty) { sectionCollection.add(); } - return sectionCollection[ - PdfSectionCollectionHelper.getHelper(sectionCollection) - .sections - .length - - 1]; + return sectionCollection[PdfSectionCollectionHelper.getHelper( + sectionCollection, + ).sections.length - + 1]; } bool _checkPageSettings(PdfPageSettings sectionSettings) { @@ -793,8 +853,9 @@ class PdfPageCollectionHelper { } else { bool value = false; for (int i = 0; i < document!.sections!.count; i++) { - value |= PdfPageCollectionHelper.getHelper(document!.sections![i].pages) - .contains(page); + value |= PdfPageCollectionHelper.getHelper( + document!.sections![i].pages, + ).contains(page); } return value; } @@ -813,10 +874,12 @@ class PdfPageCollectionHelper { PdfSectionHelper.getHelper(_section!).remove(page); } else { for (int i = 0; i < document!.sections!.count; i++) { - if (PdfPageCollectionHelper.getHelper(document!.sections![i].pages) - .contains(page)) { - PdfPageCollectionHelper.getHelper(document!.sections![i].pages) - .remove(page); + if (PdfPageCollectionHelper.getHelper( + document!.sections![i].pages, + ).contains(page)) { + PdfPageCollectionHelper.getHelper( + document!.sections![i].pages, + ).remove(page); break; } } 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 e1a2c5b12..6595a777a 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 @@ -34,8 +34,10 @@ class PdfPageLayer implements IPdfWrapper { _initialize(pdfPage, true); } - PdfPageLayer._fromClipPageTemplate(PdfPage pdfPage, - [bool? clipPageTemplates]) { + PdfPageLayer._fromClipPageTemplate( + PdfPage pdfPage, [ + bool? clipPageTemplates, + ]) { _helper = PdfPageLayerHelper(this); _initialize(pdfPage, clipPageTemplates); } @@ -113,19 +115,21 @@ class PdfPageLayer implements IPdfWrapper { final Function resources = PdfPageHelper.getHelper(page!).getResources; bool isPageHasMediaBox = false; bool isInvalidSize = false; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfName(PdfDictionaryProperties.mediaBox))) { + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfName(PdfDictionaryProperties.mediaBox))) { isPageHasMediaBox = true; } double llx = 0; double lly = 0; double urx = 0; double ury = 0; - final PdfArray? mediaBox = PdfPageHelper.getHelper(page) - .dictionary! - .getValue(PdfDictionaryProperties.mediaBox, - PdfDictionaryProperties.parent) as PdfArray?; + final PdfArray? mediaBox = + PdfPageHelper.getHelper(page).dictionary!.getValue( + PdfDictionaryProperties.mediaBox, + PdfDictionaryProperties.parent, + ) + as PdfArray?; final PdfReferenceHolder referenceHolder = PdfReferenceHolder(this); if (mediaBox != null) { // Lower Left X co-ordinate Value. @@ -138,12 +142,15 @@ class PdfPageLayer implements IPdfWrapper { ury = (mediaBox[3]! as PdfNumber).value!.toDouble(); } PdfArray? cropBox; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.cropBox)) { - cropBox = PdfPageHelper.getHelper(page).dictionary!.getValue( - PdfDictionaryProperties.cropBox, PdfDictionaryProperties.parent) - as PdfArray?; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.cropBox)) { + cropBox = + PdfPageHelper.getHelper(page).dictionary!.getValue( + PdfDictionaryProperties.cropBox, + PdfDictionaryProperties.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(); @@ -151,28 +158,40 @@ class PdfPageLayer implements IPdfWrapper { 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)); - _helper.graphics = - PdfGraphicsHelper.load(pageSize, resources, _helper._content!); - if (!PdfPageHelper.getHelper(page) - .contents - .contains(referenceHolder) && + final Size pageSize = Size( + [cropX, cropRX].reduce(max), + [cropY, cropRY].reduce(max), + ); + _helper.graphics = PdfGraphicsHelper.load( + pageSize, + resources, + _helper._content!, + ); + if (!PdfPageHelper.getHelper( + page, + ).contents.contains(referenceHolder) && !PdfPageHelper.getHelper(page).isDefaultGraphics && !_isContainsPageContent( - PdfPageHelper.getHelper(page).contents, referenceHolder)) { + PdfPageHelper.getHelper(page).contents, + referenceHolder, + )) { PdfPageHelper.getHelper(page).contents.add(referenceHolder); } } else { - _helper.graphics = - PdfGraphicsHelper.load(page.size, resources, _helper._content!); + _helper.graphics = PdfGraphicsHelper.load( + page.size, + resources, + _helper._content!, + ); PdfGraphicsHelper.getHelper(_helper.graphics!).cropBox = cropBox; - if (!PdfPageHelper.getHelper(page) - .contents - .contains(referenceHolder) && + if (!PdfPageHelper.getHelper( + page, + ).contents.contains(referenceHolder) && !PdfPageHelper.getHelper(page).isDefaultGraphics && !_isContainsPageContent( - PdfPageHelper.getHelper(page).contents, referenceHolder)) { + PdfPageHelper.getHelper(page).contents, + referenceHolder, + )) { PdfPageHelper.getHelper(page).contents.add(referenceHolder); } } @@ -180,7 +199,9 @@ class PdfPageLayer implements IPdfWrapper { (lly.abs().floor() == page.size.height.abs().floor()) && (urx.abs().floor() == page.size.width.abs().floor())) { Size pageSize = Size( - [llx, urx].reduce(max), [lly, ury].reduce(max)); + [llx, urx].reduce(max), + [lly, ury].reduce(max), + ); if (pageSize.width <= 0 || pageSize.height <= 0) { isInvalidSize = true; if (llx < 0) { @@ -194,25 +215,37 @@ class PdfPageLayer implements IPdfWrapper { ury = -ury; } pageSize = Size( - [llx, urx].reduce(max), [lly, ury].reduce(max)); - _helper.graphics = - PdfGraphicsHelper.load(pageSize, resources, _helper._content!); - if (!PdfPageHelper.getHelper(page) - .contents - .contains(referenceHolder) && + [llx, urx].reduce(max), + [lly, ury].reduce(max), + ); + _helper.graphics = PdfGraphicsHelper.load( + pageSize, + resources, + _helper._content!, + ); + if (!PdfPageHelper.getHelper( + page, + ).contents.contains(referenceHolder) && !PdfPageHelper.getHelper(page).isDefaultGraphics && !_isContainsPageContent( - PdfPageHelper.getHelper(page).contents, referenceHolder)) { + PdfPageHelper.getHelper(page).contents, + referenceHolder, + )) { PdfPageHelper.getHelper(page).contents.add(referenceHolder); } } } else { - _helper.graphics = - PdfGraphicsHelper.load(page.size, resources, _helper._content!); + _helper.graphics = PdfGraphicsHelper.load( + page.size, + resources, + _helper._content!, + ); if (!PdfPageHelper.getHelper(page).contents.contains(referenceHolder) && !PdfPageHelper.getHelper(page).isDefaultGraphics && !_isContainsPageContent( - PdfPageHelper.getHelper(page).contents, referenceHolder)) { + PdfPageHelper.getHelper(page).contents, + referenceHolder, + )) { PdfPageHelper.getHelper(page).contents.add(referenceHolder); } } @@ -223,69 +256,76 @@ class PdfPageLayer implements IPdfWrapper { } if (!PdfPageHelper.getHelper(page).isLoadedPage) { final PdfSectionCollection? sectionCollection = - PdfSectionHelper.getHelper(PdfPageHelper.getHelper(page).section!) - .parent; + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).parent; if (sectionCollection != null) { _helper.graphics!.colorSpace = - PdfSectionCollectionHelper.getHelper(sectionCollection) - .document! - .colorSpace; + PdfSectionCollectionHelper.getHelper( + sectionCollection, + ).document!.colorSpace; _helper.colorSpace = - PdfSectionCollectionHelper.getHelper(sectionCollection) - .document! - .colorSpace; + PdfSectionCollectionHelper.getHelper( + sectionCollection, + ).document!.colorSpace; } } _helper._content!.beginSave = _beginSaveContent; } _graphicsState = _helper.graphics!.save(); if (name != null && name!.isNotEmpty) { - PdfGraphicsHelper.getHelper(_helper.graphics!) - .streamWriter! - .write('/OC /${_helper.layerID!} BDC\n'); + PdfGraphicsHelper.getHelper( + _helper.graphics!, + ).streamWriter!.write('/OC /${_helper.layerID!} BDC\n'); _isEndState = true; } PdfGraphicsHelper.getHelper(_helper.graphics!).initializeCoordinates(); if (PdfGraphicsHelper.getHelper(_helper.graphics!).hasTransparencyBrush) { - PdfGraphicsHelper.getHelper(_helper.graphics!) - .setTransparencyGroup(page!); + PdfGraphicsHelper.getHelper( + _helper.graphics!, + ).setTransparencyGroup(page!); } if (page != null && (!PdfPageHelper.getHelper(page).isLoadedPage) && (page.rotation != PdfPageRotateAngle.rotateAngle0 || - PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.rotate))) { + PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.rotate))) { PdfArray? cropBox; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.cropBox)) { - cropBox = PdfPageHelper.getHelper(page).dictionary!.getValue( - PdfDictionaryProperties.cropBox, PdfDictionaryProperties.parent) - as PdfArray?; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.cropBox)) { + cropBox = + PdfPageHelper.getHelper(page).dictionary!.getValue( + PdfDictionaryProperties.cropBox, + PdfDictionaryProperties.parent, + ) + as PdfArray?; } _updatePageRotation(page, _helper.graphics, cropBox); } if (page != null && !PdfPageHelper.getHelper(page).isLoadedPage) { - final PdfRectangle clipRect = - PdfSectionHelper.getHelper(PdfPageHelper.getHelper(page).section!) - .getActualBounds(page, true); + final PdfRectangle clipRect = PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).getActualBounds(page, true); if (_clipPageTemplates!) { if (PdfPageHelper.getHelper(page).origin.dx >= 0 && PdfPageHelper.getHelper(page).origin.dy >= 0) { - PdfGraphicsHelper.getHelper(_helper.graphics!) - .clipTranslateMarginsWithBounds(clipRect); + PdfGraphicsHelper.getHelper( + _helper.graphics!, + ).clipTranslateMarginsWithBounds(clipRect); } } else { final PdfMargins margins = PdfPageHelper.getHelper(page).section!.pageSettings.margins; PdfGraphicsHelper.getHelper(_helper.graphics!).clipTranslateMargins( - clipRect.x, - clipRect.y, - margins.left, - margins.top, - margins.right, - margins.bottom); + clipRect.x, + clipRect.y, + margins.left, + margins.top, + margins.right, + margins.bottom, + ); } } PdfGraphicsHelper.getHelper(_helper.graphics!).setLayer(this); @@ -293,16 +333,26 @@ class PdfPageLayer implements IPdfWrapper { } void _updatePageRotation( - PdfPage page, PdfGraphics? graphics, PdfArray? cropBox) { + PdfPage page, + PdfGraphics? graphics, + PdfArray? cropBox, + ) { PdfNumber? rotation; - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.rotate)) { - rotation = PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.rotate] as PdfNumber?; - rotation ??= rotation = PdfCrossTable.dereference( - PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.rotate]) as PdfNumber?; + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.rotate)) { + rotation = + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .rotate] + as PdfNumber?; + rotation ??= + rotation = + PdfCrossTable.dereference( + PdfPageHelper.getHelper( + page, + ).dictionary![PdfDictionaryProperties.rotate], + ) + as PdfNumber?; } else if (page.rotation != PdfPageRotateAngle.rotateAngle0) { if (page.rotation == PdfPageRotateAngle.rotateAngle90) { rotation = PdfNumber(90); @@ -318,25 +368,29 @@ class PdfPageLayer implements IPdfWrapper { if (cropBox != null) { 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) { PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( - page.size.height - cropBoxOffset.dy, - cropBoxSize.width - cropBoxOffset.dx); + page.size.height - cropBoxOffset.dy, + cropBoxSize.width - cropBoxOffset.dx, + ); } else { PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( - cropBoxSize.height - cropBoxOffset.dy, - cropBoxSize.width - cropBoxOffset.dx); + cropBoxSize.height - cropBoxOffset.dy, + cropBoxSize.width - cropBoxOffset.dx, + ); } } else { - PdfGraphicsHelper.getHelper(graphics).clipBounds.size = - PdfSize(page.size.height, page.size.width); + PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( + page.size.height, + page.size.width, + ); } } else if (rotation.value == 180) { graphics!.translateTransform(page.size.width, page.size.height); @@ -344,17 +398,19 @@ class PdfPageLayer implements IPdfWrapper { } else if (rotation.value == 270) { graphics!.translateTransform(page.size.width, 0); graphics.rotateTransform(-270); - PdfGraphicsHelper.getHelper(graphics).clipBounds.size = - PdfSize(page.size.height, page.size.width); + PdfGraphicsHelper.getHelper(graphics).clipBounds.size = PdfSize( + page.size.height, + page.size.width, + ); } } void _beginSaveContent(Object sender, SavePdfPrimitiveArgs? args) { if (_graphicsState != null) { if (_isEndState) { - PdfGraphicsHelper.getHelper(_helper.graphics!) - .streamWriter! - .write('EMC\n'); + PdfGraphicsHelper.getHelper( + _helper.graphics!, + ).streamWriter!.write('EMC\n'); _isEndState = false; } graphics.restore(_graphicsState); @@ -365,12 +421,16 @@ class PdfPageLayer implements IPdfWrapper { void _setVisibility(bool? value) { PdfDictionary? oCProperties; - if (PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_page).document!) - .catalog - .containsKey(PdfDictionaryProperties.ocProperties)) { - oCProperties = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_page).document!) - .catalog[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + if (PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_page).document!, + ).catalog.containsKey(PdfDictionaryProperties.ocProperties)) { + oCProperties = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_page).document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; } if (oCProperties != null) { final PdfDictionary? defaultView = @@ -391,7 +451,7 @@ class PdfPageLayer implements IPdfWrapper { ocgOFF; } ocgOFF.insert(ocgOFF.count, _helper.referenceHolder!); - } else if (value == true) { + } else if (value ?? true) { if (ocgOFF != null) { _removeContent(ocgOFF, _helper.referenceHolder); } @@ -408,7 +468,9 @@ class PdfPageLayer implements IPdfWrapper { } bool _isContainsPageContent( - PdfArray content, PdfReferenceHolder referenceHolder) { + PdfArray content, + PdfReferenceHolder referenceHolder, + ) { for (int i = 0; i < content.count; i++) { final IPdfPrimitive? primitive = content.elements[i]; if (primitive != null && primitive is PdfReferenceHolder) { @@ -465,8 +527,10 @@ class PdfPageLayerHelper { } /// internal method - static PdfPageLayer fromClipPageTemplate(PdfPage pdfPage, - [bool? clipPageTemplates]) { + static PdfPageLayer fromClipPageTemplate( + PdfPage pdfPage, [ + bool? clipPageTemplates, + ]) { return PdfPageLayer._fromClipPageTemplate(pdfPage, clipPageTemplates); } 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 b4d702fbb..382d1ecec 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 @@ -73,7 +73,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { /// internal constructor PdfPageLayerCollectionHelper(this.pageLayerCollection, PdfPage page) - : super(pageLayerCollection) { + : super(pageLayerCollection) { _optionalContent = PdfDictionary(); _subLayer = false; _page = page; @@ -95,7 +95,8 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { /// internal method static PdfPageLayerCollectionHelper getHelper( - PdfPageLayerCollection pageLayerCollection) { + PdfPageLayerCollection pageLayerCollection, + ) { return pageLayerCollection._helper; } @@ -118,12 +119,19 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { pdfLoaded = page; } if (pdfLoaded != null) { - propertie = PdfCrossTable.dereference( - resource![PdfDictionaryProperties.properties]) as PdfDictionary?; + propertie = + PdfCrossTable.dereference( + resource![PdfDictionaryProperties.properties], + ) + as PdfDictionary?; if (PdfPageHelper.getHelper(pdfLoaded).document != null) { - ocProperties = PdfCrossTable.dereference(PdfDocumentHelper.getHelper( - PdfPageHelper.getHelper(pdfLoaded).document!) - .catalog[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + ocProperties = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(pdfLoaded).document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; } } @@ -135,8 +143,14 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { PdfCrossTable.dereference(value) as PdfDictionary?; if ((layerDictionary != null && layerReferenceHolder != null) || layerDictionary!.containsKey(PdfDictionaryProperties.ocg)) { - _addLayer(page, layerDictionary, layerReferenceHolder, key!.name, - pageLayerCollection, false); + _addLayer( + page, + layerDictionary, + layerReferenceHolder, + key!.name, + pageLayerCollection, + false, + ); } }); } @@ -148,13 +162,13 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { final PdfStream restoreStream = PdfStream(); const int saveState = 113; const int restoreState = 81; - saveStream.dataStream = [saveState]; + saveStream.data = [saveState]; if (contents.count > 0) { contents.insert(0, PdfReferenceHolder(saveStream)); } else { contents.add(PdfReferenceHolder(saveStream)); } - restoreStream.dataStream = [restoreState]; + restoreStream.data = [restoreState]; contents.add(PdfReferenceHolder(restoreStream)); } } @@ -170,7 +184,11 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } List _combineProcess( - PdfPage page, bool decompress, List end, bool isTextExtraction) { + PdfPage page, + bool decompress, + List end, + bool isTextExtraction, + ) { final List data = []; for (int i = 0; i < PdfPageHelper.getHelper(page).contents.count; i++) { PdfStream? layerStream; @@ -186,12 +204,10 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } if (layerStream != null) { if (decompress) { - final bool isChanged = - layerStream.changed != null && layerStream.changed!; - layerStream.decompress(); - layerStream.changed = isChanged || !isTextExtraction; + data.addAll(layerStream.getDecompressedData(false)!); + } else { + data.addAll(layerStream.dataStream!); } - data.addAll(layerStream.dataStream!); data.addAll(end); } } @@ -203,12 +219,16 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { final IPdfPrimitive? ocgroups = _createOptionContentDictionary(layer); bool isPresent = false; if (PdfPageHelper.getHelper(_page).document != null && - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_page).document!) - .catalog - .containsKey(PdfDictionaryProperties.ocProperties)) { - final PdfDictionary? ocDictionary = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_page).document!) - .catalog[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_page).document!, + ).catalog.containsKey(PdfDictionaryProperties.ocProperties)) { + final PdfDictionary? ocDictionary = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_page).document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; if (ocDictionary != null && ocDictionary.containsKey(PdfDictionaryProperties.ocg)) { final PdfArray? ocgsList = @@ -216,10 +236,13 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { as PdfArray?; if (ocgsList != null) { isPresent = true; - if (!ocgsList - .contains(PdfPageLayerHelper.getHelper(layer).referenceHolder!)) { - ocgsList.insert(ocgsList.count, - PdfPageLayerHelper.getHelper(layer).referenceHolder!); + if (!ocgsList.contains( + PdfPageLayerHelper.getHelper(layer).referenceHolder!, + )) { + ocgsList.insert( + ocgsList.count, + PdfPageLayerHelper.getHelper(layer).referenceHolder!, + ); } } if (ocDictionary.containsKey(PdfDictionaryProperties.defaultView)) { @@ -227,19 +250,30 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { ocDictionary[PdfDictionaryProperties.defaultView] as PdfDictionary?; if (defaultView != null) { - PdfArray? _on = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOn]) as PdfArray?; - final PdfArray? order = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOrder]) as PdfArray?; - PdfArray? off = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOff]) as PdfArray?; - final PdfArray? usage = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.usageApplication]) - as PdfArray?; - - if (_on == null) { - _on = PdfArray(); - defaultView[PdfDictionaryProperties.ocgOn] = _on; + PdfArray? on = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOn], + ) + as PdfArray?; + final PdfArray? order = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOrder], + ) + as PdfArray?; + PdfArray? off = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOff], + ) + as PdfArray?; + final PdfArray? usage = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.usageApplication], + ) + as PdfArray?; + + if (on == null) { + on = PdfArray(); + defaultView[PdfDictionaryProperties.ocgOn] = on; } if (!layer.visible && off == null) { @@ -253,8 +287,8 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { order.insert(order.count, referenceHolder); } - if (layer.visible && !_on.contains(referenceHolder)) { - _on.insert(_on.count, referenceHolder); + if (layer.visible && !on.contains(referenceHolder)) { + on.insert(on.count, referenceHolder); } if (!layer.visible && off != null && @@ -267,8 +301,11 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { PdfCrossTable.dereference(usage[0]) as PdfDictionary?; if (asDictionary != null && asDictionary.containsKey(PdfDictionaryProperties.ocg)) { - final PdfArray? usageOcGroup = PdfCrossTable.dereference( - asDictionary[PdfDictionaryProperties.ocg]) as PdfArray?; + final PdfArray? usageOcGroup = + PdfCrossTable.dereference( + asDictionary[PdfDictionaryProperties.ocg], + ) + as PdfArray?; if (usageOcGroup != null && !usageOcGroup.contains(referenceHolder)) { usageOcGroup.insert(usageOcGroup.count, referenceHolder); @@ -281,11 +318,11 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } if (!isPresent && PdfPageHelper.getHelper(_page).document != null) { ocProperties[PdfDictionaryProperties.ocg] = ocgroups; - ocProperties[PdfDictionaryProperties.defaultView] = - _createOptionalContentViews(layer); - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_page).document!) - .catalog - .setProperty(PdfDictionaryProperties.ocProperties, ocProperties); + ocProperties[PdfDictionaryProperties + .defaultView] = _createOptionalContentViews(layer); + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_page).document!, + ).catalog.setProperty(PdfDictionaryProperties.ocProperties, ocProperties); } } @@ -293,17 +330,20 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { final PdfDictionary optionalContent = PdfDictionary(); optionalContent[PdfDictionaryProperties.name] = PdfString(layer.name!); optionalContent[PdfDictionaryProperties.type] = PdfName('OCG'); - optionalContent[PdfDictionaryProperties.layerID] = - PdfName(PdfPageLayerHelper.getHelper(layer).layerID); - optionalContent[PdfDictionaryProperties.visible] = - PdfBoolean(layer.visible); + optionalContent[PdfDictionaryProperties.layerID] = PdfName( + PdfPageLayerHelper.getHelper(layer).layerID, + ); + optionalContent[PdfDictionaryProperties.visible] = PdfBoolean( + layer.visible, + ); PdfPageLayerHelper.getHelper(layer).usage = _setPrintOption(layer); - optionalContent[PdfDictionaryProperties.usage] = - PdfReferenceHolder(PdfPageLayerHelper.getHelper(layer).usage); + optionalContent[PdfDictionaryProperties.usage] = PdfReferenceHolder( + PdfPageLayerHelper.getHelper(layer).usage, + ); final PdfDocument document = PdfPageHelper.getHelper(_page).document!; - PdfDocumentHelper.getHelper(document) - .printLayer! - .add(PdfReferenceHolder(optionalContent)); + PdfDocumentHelper.getHelper( + document, + ).printLayer!.add(PdfReferenceHolder(optionalContent)); final PdfReferenceHolder reference = PdfReferenceHolder(optionalContent); PdfDocumentHelper.getHelper(document).pdfPrimitive!.add(reference); PdfPageLayerHelper.getHelper(layer).dictionary = optionalContent; @@ -349,10 +389,11 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { PdfDictionary _setPrintOption(PdfPageLayer layer) { final PdfDictionary usage = PdfDictionary(); PdfPageLayerHelper.getHelper(layer).printOption = PdfDictionary(); - PdfPageLayerHelper.getHelper(layer) - .printOption![PdfDictionaryProperties.subtype] = PdfName('Print'); - usage[PdfDictionaryProperties.print] = - PdfReferenceHolder(PdfPageLayerHelper.getHelper(layer).printOption); + PdfPageLayerHelper.getHelper(layer).printOption![PdfDictionaryProperties + .subtype] = PdfName('Print'); + usage[PdfDictionaryProperties.print] = PdfReferenceHolder( + PdfPageLayerHelper.getHelper(layer).printOption, + ); return usage; } @@ -385,12 +426,13 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } void _addLayer( - PdfPage page, - PdfDictionary dictionary, - PdfReferenceHolder? reference, - String? key, - Map pageLayerCollection, - bool isResourceLayer) { + PdfPage page, + PdfDictionary dictionary, + PdfReferenceHolder? reference, + String? key, + Map pageLayerCollection, + bool isResourceLayer, + ) { final PdfPageLayer layer = PdfPageLayer(page); list.add(layer); if (!pageLayerCollection.containsKey(reference)) { @@ -410,10 +452,15 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } } - void _checkVisible(PdfDictionary ocproperties, - Map layerDictionary) { - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocproperties[PdfDictionaryProperties.defaultView]) as PdfDictionary?; + void _checkVisible( + PdfDictionary ocproperties, + Map layerDictionary, + ) { + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocproperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null) { final PdfArray? visible = PdfCrossTable.dereference(defaultView[PdfDictionaryProperties.ocgOff]) @@ -426,11 +473,13 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { if (pdfLayer != null) { pdfLayer.visible = false; if (PdfPageLayerHelper.getHelper(pdfLayer).dictionary != null && - PdfPageLayerHelper.getHelper(pdfLayer) - .dictionary! - .containsKey(PdfDictionaryProperties.visible)) { + PdfPageLayerHelper.getHelper( + pdfLayer, + ).dictionary!.containsKey(PdfDictionaryProperties.visible)) { PdfPageLayerHelper.getHelper(pdfLayer).dictionary!.setProperty( - PdfDictionaryProperties.visible, PdfBoolean(false)); + PdfDictionaryProperties.visible, + PdfBoolean(false), + ); } } } @@ -442,12 +491,18 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { void _removeLayer(PdfPageLayer layer) { PdfDictionary? ocProperties; _removeLayerContent(layer); - final PdfDictionary? resource = PdfCrossTable.dereference( - PdfPageHelper.getHelper(_page) - .dictionary![PdfDictionaryProperties.resources]) as PdfDictionary?; + final PdfDictionary? resource = + PdfCrossTable.dereference( + PdfPageHelper.getHelper(_page).dictionary![PdfDictionaryProperties + .resources], + ) + as PdfDictionary?; if (resource != null) { - final PdfDictionary? properties = PdfCrossTable.dereference( - resource[PdfDictionaryProperties.properties]) as PdfDictionary?; + final PdfDictionary? properties = + PdfCrossTable.dereference( + resource[PdfDictionaryProperties.properties], + ) + as PdfDictionary?; if (properties != null && PdfPageLayerHelper.getHelper(layer).layerID != null && properties.containsKey(PdfPageLayerHelper.getHelper(layer).layerID)) { @@ -456,12 +511,16 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } final PdfPage page = _page; if (PdfPageHelper.getHelper(page).document != null && - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page).document!) - .catalog - .containsKey(PdfDictionaryProperties.ocProperties)) { - ocProperties = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(page).document!) - .catalog[PdfDictionaryProperties.ocProperties]) as PdfDictionary?; + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page).document!, + ).catalog.containsKey(PdfDictionaryProperties.ocProperties)) { + ocProperties = + PdfCrossTable.dereference( + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(page).document!, + ).catalog[PdfDictionaryProperties.ocProperties], + ) + as PdfDictionary?; } if (ocProperties != null) { final PdfArray? ocGroup = @@ -469,19 +528,36 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { as PdfArray?; if (ocGroup != null) { _removeContent( - ocGroup, PdfPageLayerHelper.getHelper(layer).referenceHolder); + ocGroup, + PdfPageLayerHelper.getHelper(layer).referenceHolder, + ); } - final PdfDictionary? defaultView = PdfCrossTable.dereference( - ocProperties[PdfDictionaryProperties.defaultView]) as PdfDictionary?; + final PdfDictionary? defaultView = + PdfCrossTable.dereference( + ocProperties[PdfDictionaryProperties.defaultView], + ) + as PdfDictionary?; if (defaultView != null) { - final PdfArray? _on = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOn]) as PdfArray?; - final PdfArray? order = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOrder]) as PdfArray?; - final PdfArray? off = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.ocgOff]) as PdfArray?; - final PdfArray? usage = PdfCrossTable.dereference( - defaultView[PdfDictionaryProperties.usageApplication]) as PdfArray?; + final PdfArray? on = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOn], + ) + as PdfArray?; + final PdfArray? order = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOrder], + ) + as PdfArray?; + final PdfArray? off = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.ocgOff], + ) + as PdfArray?; + final PdfArray? usage = + PdfCrossTable.dereference( + defaultView[PdfDictionaryProperties.usageApplication], + ) + as PdfArray?; if (usage != null && usage.count > 0) { for (int i = 0; i < usage.count; i++) { @@ -492,22 +568,30 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { final PdfArray? usageOcGroup = usageDictionary[PdfDictionaryProperties.ocg] as PdfArray?; if (usageOcGroup != null) { - _removeContent(usageOcGroup, - PdfPageLayerHelper.getHelper(layer).referenceHolder); + _removeContent( + usageOcGroup, + PdfPageLayerHelper.getHelper(layer).referenceHolder, + ); } } } } if (order != null) { _removeContent( - order, PdfPageLayerHelper.getHelper(layer).referenceHolder); + order, + PdfPageLayerHelper.getHelper(layer).referenceHolder, + ); } - if (layer.visible && _on != null) { + if (layer.visible && on != null) { _removeContent( - _on, PdfPageLayerHelper.getHelper(layer).referenceHolder); + on, + PdfPageLayerHelper.getHelper(layer).referenceHolder, + ); } else if (off != null) { _removeContent( - off, PdfPageLayerHelper.getHelper(layer).referenceHolder); + off, + PdfPageLayerHelper.getHelper(layer).referenceHolder, + ); } } } @@ -531,8 +615,9 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { 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--; @@ -575,12 +660,13 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { recordCollection.recordCollection[j].operatorName; if (mOperator == 'BMC' || mOperator == 'EMC' || mOperator == 'BDC') { final Map returnedValue = _processBeginMarkContent( - layer, - mOperator, - recordCollection.recordCollection[j].operands, - data, - isNewContentStream, - removePageContent); + layer, + mOperator, + recordCollection.recordCollection[j].operands, + data, + isNewContentStream, + removePageContent, + ); removePageContent = returnedValue['removePageContent']; isSkip = true; if (removePageContent!) { @@ -609,12 +695,20 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { mOperator == PdfOperators.selectColorSpaceForNonStroking || mOperator == PdfOperators.selectColorSpaceForStroking) { if (!isSkip) { - _streamWrite(recordCollection.recordCollection[j].operands, - mOperator, false, data); + _streamWrite( + recordCollection.recordCollection[j].operands, + mOperator, + false, + data, + ); } } else if (!isSkip) { - _streamWrite(recordCollection.recordCollection[j].operands, mOperator, - true, data); + _streamWrite( + recordCollection.recordCollection[j].operands, + mOperator, + true, + data, + ); } isSkip = false; } @@ -626,20 +720,24 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { pageContent.clear(); } if (removePageContent!) { - _removeContent(PdfPageHelper.getHelper(_page).contents, - PdfPageLayerHelper.getHelper(layer).referenceHolder); + _removeContent( + PdfPageHelper.getHelper(_page).contents, + PdfPageLayerHelper.getHelper(layer).referenceHolder, + ); if (PdfPageLayerHelper.getHelper(layer).graphics != null && PdfGraphicsHelper.getHelper( - PdfPageLayerHelper.getHelper(layer).graphics!) - .streamWriter != + PdfPageLayerHelper.getHelper(layer).graphics!, + ).streamWriter != null) { - final PdfStream? lcontent = PdfGraphicsHelper.getHelper( - PdfPageLayerHelper.getHelper(layer).graphics!) - .streamWriter! - .stream; + final PdfStream? lcontent = + PdfGraphicsHelper.getHelper( + PdfPageLayerHelper.getHelper(layer).graphics!, + ).streamWriter!.stream; if (lcontent != null) { - _removeContent(PdfPageHelper.getHelper(_page).contents, - PdfReferenceHolder(lcontent)); + _removeContent( + PdfPageHelper.getHelper(_page).contents, + PdfReferenceHolder(lcontent), + ); } } } @@ -647,12 +745,13 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } Map _processBeginMarkContent( - PdfPageLayer parser, - String? mOperator, - List? operands, - PdfStream data, - bool isNewContentStream, - bool? removePageContent) { + PdfPageLayer parser, + String? mOperator, + List? operands, + PdfStream data, + bool isNewContentStream, + bool? removePageContent, + ) { removePageContent = false; if ('BDC' == mOperator) { String? operand; @@ -682,7 +781,11 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { } 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; @@ -730,12 +833,14 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { _createLayerLoadedPage(layer); } else { final PdfDictionary ocProperties = PdfDictionary(); - ocProperties[PdfDictionaryProperties.ocg] = - _createOptionContentDictionary(layer); - ocProperties[PdfDictionaryProperties.defaultView] = - _createOptionalContentViews(layer); - PdfDocumentHelper.getHelper(PdfPageHelper.getHelper(_page).document!) - .catalog[PdfDictionaryProperties.ocProperties] = ocProperties; + ocProperties[PdfDictionaryProperties + .ocg] = _createOptionContentDictionary(layer); + ocProperties[PdfDictionaryProperties + .defaultView] = _createOptionalContentViews(layer); + PdfDocumentHelper.getHelper( + PdfPageHelper.getHelper(_page).document!, + ).catalog[PdfDictionaryProperties.ocProperties] = + ocProperties; } } return listIndex; @@ -773,7 +878,8 @@ class PdfPageLayerCollectionHelper extends PdfObjectCollectionHelper { void removeAt(int index) { if (index < 0 || index > list.length - 1) { ArgumentError.value( - '$index Value can not be less 0 and greater List.Count - 1'); + '$index Value can not be less 0 and greater List.Count - 1', + ); } final PdfPageLayer layer = pageLayerCollection[index]; _removeLayer(layer); 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 8eaa9ed17..11bf127dd 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 @@ -22,7 +22,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfPageSettings([Size? size, PdfPageOrientation? orientation]) { @@ -58,7 +58,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfMargins get margins => _margins; @@ -83,7 +83,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, width, height)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` double get width => _size.width; @@ -102,7 +102,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, width, height)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` double get height => _size.height; @@ -119,7 +119,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH.(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfPageOrientation get orientation => _orientation; @@ -136,7 +136,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` set orientation(PdfPageOrientation value) { @@ -158,7 +158,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` Size get size => _size.size; @@ -175,7 +175,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` set size(Size value) { @@ -199,7 +199,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfPageRotateAngle get rotate => _rotateAngle; @@ -219,7 +219,7 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` set rotate(PdfPageRotateAngle value) { @@ -242,14 +242,15 @@ class PdfPageSettings { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` void setMargins(double all, [double? top, double? right, double? bottom]) { if (!_helper.isPageAdded) { if (top != null && right != null && bottom != null) { - PdfMarginsHelper.getHelper(margins) - .setMarginsAll(all, top, right, bottom); + PdfMarginsHelper.getHelper( + margins, + ).setMarginsAll(all, top, right, bottom); } else if (top != null && right == null) { PdfMarginsHelper.getHelper(margins).setMarginsLT(all, top); } else if (top == null && bottom != null) { @@ -308,8 +309,10 @@ class PdfPageSettingsHelper { /// internal method Size getActualSize() { - return Size(base.width - (base.margins.left + base.margins.right), - base.height - (base.margins.top + base.margins.bottom)); + return Size( + base.width - (base.margins.left + base.margins.right), + base.height - (base.margins.top + base.margins.bottom), + ); } /// internal method @@ -326,7 +329,7 @@ class PdfPageSettingsHelper { case PdfPageRotateAngle.rotateAngle270: result.rotate = PdfPageRotateAngle.rotateAngle270; break; - default: + case PdfPageRotateAngle.rotateAngle0: result.rotate = PdfPageRotateAngle.rotateAngle0; break; } 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 ef2598700..3b07de8cb 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 @@ -32,7 +32,7 @@ import 'pdf_section.dart'; /// bounds: Rect.fromLTWH(250, 0, 515, 100)); /// } /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -65,7 +65,7 @@ class PdfPageTemplateElement { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -106,7 +106,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -140,7 +140,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -170,7 +170,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -197,7 +197,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -226,7 +226,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -255,7 +255,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -284,7 +284,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -313,7 +313,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -343,7 +343,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -371,7 +371,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -395,7 +395,7 @@ class PdfPageTemplateElement { /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), /// bounds: Rect.fromLTWH(0, 0, 100, 100)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -415,34 +415,39 @@ class PdfPageTemplateElement { bool canBeSet = false; switch (dock) { case PdfDockStyle.left: - canBeSet = alignment == PdfAlignmentStyle.topLeft || + canBeSet = + alignment == PdfAlignmentStyle.topLeft || alignment == PdfAlignmentStyle.middleLeft || alignment == PdfAlignmentStyle.bottomLeft || alignment == PdfAlignmentStyle.none; break; case PdfDockStyle.top: - canBeSet = alignment == PdfAlignmentStyle.topLeft || + canBeSet = + alignment == PdfAlignmentStyle.topLeft || alignment == PdfAlignmentStyle.topCenter || alignment == PdfAlignmentStyle.topRight || alignment == PdfAlignmentStyle.none; break; case PdfDockStyle.right: - canBeSet = alignment == PdfAlignmentStyle.topRight || + canBeSet = + alignment == PdfAlignmentStyle.topRight || alignment == PdfAlignmentStyle.middleRight || alignment == PdfAlignmentStyle.bottomRight || alignment == PdfAlignmentStyle.none; break; case PdfDockStyle.bottom: - canBeSet = alignment == PdfAlignmentStyle.bottomLeft || + canBeSet = + alignment == PdfAlignmentStyle.bottomLeft || alignment == PdfAlignmentStyle.bottomCenter || alignment == PdfAlignmentStyle.bottomRight || alignment == PdfAlignmentStyle.none; break; case PdfDockStyle.fill: - canBeSet = alignment == PdfAlignmentStyle.middleCenter || + canBeSet = + alignment == PdfAlignmentStyle.middleCenter || alignment == PdfAlignmentStyle.none; break; - default: + case PdfDockStyle.none: break; } if (canBeSet) { @@ -494,7 +499,7 @@ class PdfPageTemplateElementHelper { case TemplateType.right: base.dock = PdfDockStyle.right; break; - default: + case TemplateType.none: break; } _resetAlignment(); @@ -537,8 +542,9 @@ class PdfPageTemplateElementHelper { Rect _getSimpleAlignmentBounds(PdfPage page, PdfDocument document) { final Rect result = base.bounds; final PdfSection section = PdfPageHelper.getHelper(page).section!; - final PdfRectangle actualBounds = PdfSectionHelper.getHelper(section) - .getActualBounds(page, false, document); + final PdfRectangle actualBounds = PdfSectionHelper.getHelper( + section, + ).getActualBounds(page, false, document); double x = base.location.dx; double y = base.location.dy; @@ -607,8 +613,6 @@ class PdfPageTemplateElementHelper { break; case PdfAlignmentStyle.none: break; - default: - break; } return Offset(x, y) & result.size; @@ -628,83 +632,87 @@ class PdfPageTemplateElementHelper { Rect _getSimpleDockBounds(PdfPage page, PdfDocument document) { Rect result = base.bounds; final PdfSection section = PdfPageHelper.getHelper(page).section!; - final PdfRectangle actualBounds = PdfSectionHelper.getHelper(section) - .getActualBounds(page, false, document); + final PdfRectangle actualBounds = PdfSectionHelper.getHelper( + section, + ).getActualBounds(page, false, document); double x = base.location.dx; double y = base.location.dy; - double? _width = base.width; - double? _height = base.height; + double? width = base.width; + double? height = base.height; switch (_dockStyle) { case PdfDockStyle.left: x = 0.0; y = 0.0; - _width = base.width; - _height = actualBounds.height; + width = base.width; + height = actualBounds.height; break; case PdfDockStyle.top: x = 0.0; y = 0.0; - _width = actualBounds.width; - _height = base.height; + width = actualBounds.width; + height = base.height; break; case PdfDockStyle.right: x = actualBounds.width - base.width; y = 0.0; - _width = base.width; - _height = actualBounds.height; + width = base.width; + height = actualBounds.height; break; case PdfDockStyle.bottom: x = 0.0; y = actualBounds.height - base.height; - _width = actualBounds.width; - _height = base.height; + width = actualBounds.width; + height = base.height; break; case PdfDockStyle.fill: x = 0.0; x = 0.0; - _width = actualBounds.width; - _height = actualBounds.height; + width = actualBounds.width; + height = actualBounds.height; break; case PdfDockStyle.none: break; + // ignore: no_default_cases default: break; } - result = Rect.fromLTWH(x, y, _width, _height); + result = Rect.fromLTWH(x, y, width, height); return result; } Rect _getTemplateDockBounds(PdfPage page, PdfDocument document) { final PdfSection section = PdfPageHelper.getHelper(page).section!; - final PdfRectangle actualBounds = PdfSectionHelper.getHelper(section) - .getActualBounds(page, false, document); + final PdfRectangle actualBounds = PdfSectionHelper.getHelper( + section, + ).getActualBounds(page, false, document); final PdfSize actualSize = PdfSize.fromSize( - PdfPageSettingsHelper.getHelper(section.pageSettings).getActualSize()); + PdfPageSettingsHelper.getHelper(section.pageSettings).getActualSize(), + ); double x = base.location.dx; double y = base.location.dy; - double? _width = base.width; - double? _height = base.height; + double? width = base.width; + double? height = base.height; switch (_dockStyle) { case PdfDockStyle.left: x = -actualBounds.x; y = 0.0; - _width = base.width; - _height = actualBounds.height; + width = base.width; + height = actualBounds.height; break; case PdfDockStyle.top: x = -actualBounds.x; y = -actualBounds.y; - _width = actualSize.width; - _height = base.height; + width = actualSize.width; + height = base.height; if (actualBounds.height < 0) { y = -actualBounds.y + actualSize.height; @@ -712,23 +720,27 @@ class PdfPageTemplateElementHelper { break; case PdfDockStyle.right: - x = actualBounds.width + - PdfSectionHelper.getHelper(section) - .getRightIndentWidth(document, page, false) - + x = + actualBounds.width + + PdfSectionHelper.getHelper( + section, + ).getRightIndentWidth(document, page, false) - base.width; y = 0.0; - _width = base.width; - _height = actualBounds.height; + width = base.width; + height = actualBounds.height; break; case PdfDockStyle.bottom: x = -actualBounds.x; - y = actualBounds.height + - PdfSectionHelper.getHelper(section) - .getBottomIndentHeight(document, page, false) - + y = + actualBounds.height + + PdfSectionHelper.getHelper( + section, + ).getBottomIndentHeight(document, page, false) - base.height; - _width = actualSize.width; - _height = base.height; + width = actualSize.width; + height = base.height; if (actualBounds.height < 0) { y -= actualSize.height; } @@ -737,14 +749,15 @@ class PdfPageTemplateElementHelper { case PdfDockStyle.fill: x = 0.0; x = 0.0; - _width = actualBounds.width; - _height = actualBounds.height; + width = actualBounds.width; + height = actualBounds.height; break; case PdfDockStyle.none: break; + // ignore: no_default_cases default: break; } - return Rect.fromLTWH(x, y, _width, _height); + 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 9dca0f677..ac18a2623 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 @@ -1,7 +1,5 @@ import 'dart:ui'; -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/pdf_document/pdf_document_template.dart'; - import '../../interfaces/pdf_interface.dart'; import '../drawing/drawing.dart'; import '../general/pdf_collection.dart'; @@ -35,7 +33,7 @@ import 'pdf_section_template.dart'; /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -49,13 +47,15 @@ class PdfSection implements IPdfWrapper { _helper._section!.beginSave = _helper.beginSave; _helper._pageCount = PdfNumber(0); _helper._section![PdfDictionaryProperties.count] = _helper._pageCount; - _helper._section![PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.pages); + _helper._section![PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.pages, + ); _helper._section![PdfDictionaryProperties.kids] = _helper.pageReferences; _helper.pdfDocument = document; - _helper.settings = settings != null - ? PdfPageSettingsHelper.getHelper(settings).clone() - : PdfPageSettingsHelper.getHelper(document!.pageSettings).clone(); + _helper.settings = + settings != null + ? PdfPageSettingsHelper.getHelper(settings).clone() + : PdfPageSettingsHelper.getHelper(document!.pageSettings).clone(); } /// Event rises when the new page has been added @@ -81,7 +81,7 @@ class PdfSection implements IPdfWrapper { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -105,7 +105,7 @@ class PdfSection implements IPdfWrapper { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -130,7 +130,7 @@ class PdfSection implements IPdfWrapper { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -236,10 +236,8 @@ class PdfSectionHelper { final PdfSectionCollection? sectionCollection = parent; if (sectionCollection != null) { PdfPageCollectionHelper.getHelper( - PdfSectionCollectionHelper.getHelper(sectionCollection) - .document! - .pages) - .onPageAdded(args); + PdfSectionCollectionHelper.getHelper(sectionCollection).document!.pages, + ).onPageAdded(args); } _pageCount!.value = count; } @@ -273,35 +271,47 @@ class PdfSectionHelper { } /// internal method - PdfRectangle getActualBounds(PdfPage page, bool includeMargins, - [PdfDocument? document]) { + PdfRectangle getActualBounds( + PdfPage page, + bool includeMargins, [ + PdfDocument? document, + ]) { if (document == null) { if (parent != null) { final PdfDocument? pdfDocument = PdfSectionCollectionHelper.getHelper(parent!).document; return getActualBounds(page, includeMargins, pdfDocument); } else { - final PdfSize size = includeMargins - ? PdfSize.fromSize( - PdfPageSettingsHelper.getHelper(base.pageSettings) - .getActualSize()) - : base.pageSettings.size as PdfSize; + final PdfSize size = + includeMargins + ? PdfSize.fromSize( + PdfPageSettingsHelper.getHelper( + base.pageSettings, + ).getActualSize(), + ) + : base.pageSettings.size as PdfSize; final double left = includeMargins ? base.pageSettings.margins.left : 0; final double top = includeMargins ? base.pageSettings.margins.top : 0; return PdfRectangle(left, top, size.width, size.height); } } else { final PdfRectangle bounds = PdfRectangle.empty; - final Size size = includeMargins - ? base.pageSettings.size - : PdfPageSettingsHelper.getHelper(base.pageSettings).getActualSize(); + final Size size = + includeMargins + ? base.pageSettings.size + : PdfPageSettingsHelper.getHelper( + base.pageSettings, + ).getActualSize(); bounds.width = size.width; bounds.height = size.height; final double left = getLeftIndentWidth(document, page, includeMargins); final double top = getTopIndentHeight(document, page, includeMargins); final double right = getRightIndentWidth(document, page, includeMargins); - final double bottom = - getBottomIndentHeight(document, page, includeMargins); + final double bottom = getBottomIndentHeight( + document, + page, + includeMargins, + ); bounds.x += left; bounds.y += top; bounds.width -= left + right; @@ -312,100 +322,121 @@ class PdfSectionHelper { /// internal method double getLeftIndentWidth( - PdfDocument document, PdfPage page, bool includeMargins) { + PdfDocument document, + PdfPage page, + bool includeMargins, + ) { double value = includeMargins ? base.pageSettings.margins.left : 0; final double templateWidth = PdfDocumentTemplateHelper.getHelper(base.template).getLeft(page) != null - ? PdfDocumentTemplateHelper.getHelper(base.template) - .getLeft(page)! - .width + ? PdfDocumentTemplateHelper.getHelper( + base.template, + ).getLeft(page)!.width : 0; final double docTemplateWidth = PdfDocumentTemplateHelper.getHelper(document.template).getLeft(page) != null - ? PdfDocumentTemplateHelper.getHelper(document.template) - .getLeft(page)! - .width + ? PdfDocumentTemplateHelper.getHelper( + document.template, + ).getLeft(page)!.width : 0; - value += base.template.leftTemplate - ? (templateWidth >= docTemplateWidth ? templateWidth : docTemplateWidth) - : templateWidth; + value += + base.template.leftTemplate + ? (templateWidth >= docTemplateWidth + ? templateWidth + : docTemplateWidth) + : templateWidth; return value; } /// internal method double getTopIndentHeight( - PdfDocument document, PdfPage page, bool includeMargins) { + PdfDocument document, + PdfPage page, + bool includeMargins, + ) { double value = includeMargins ? base.pageSettings.margins.top : 0; final double templateHeight = PdfDocumentTemplateHelper.getHelper(base.template).getTop(page) != null - ? PdfDocumentTemplateHelper.getHelper(base.template) - .getTop(page)! - .height + ? PdfDocumentTemplateHelper.getHelper( + base.template, + ).getTop(page)!.height : 0; final double docTemplateHeight = PdfDocumentTemplateHelper.getHelper(document.template).getTop(page) != null - ? PdfDocumentTemplateHelper.getHelper(document.template) - .getTop(page)! - .height + ? PdfDocumentTemplateHelper.getHelper( + document.template, + ).getTop(page)!.height : 0; - value += base.template.topTemplate - ? (templateHeight >= docTemplateHeight - ? templateHeight - : docTemplateHeight) - : templateHeight; + value += + base.template.topTemplate + ? (templateHeight >= docTemplateHeight + ? templateHeight + : docTemplateHeight) + : templateHeight; return value; } /// internal method double getRightIndentWidth( - PdfDocument document, PdfPage page, bool includeMargins) { + PdfDocument document, + PdfPage page, + bool includeMargins, + ) { double value = includeMargins ? base.pageSettings.margins.right : 0; final double templateWidth = PdfDocumentTemplateHelper.getHelper(base.template).getRight(page) != null - ? PdfDocumentTemplateHelper.getHelper(base.template) - .getRight(page)! - .width + ? PdfDocumentTemplateHelper.getHelper( + base.template, + ).getRight(page)!.width : 0; final double docTemplateWidth = PdfDocumentTemplateHelper.getHelper(document.template).getRight(page) != null - ? PdfDocumentTemplateHelper.getHelper(document.template) - .getRight(page)! - .width + ? PdfDocumentTemplateHelper.getHelper( + document.template, + ).getRight(page)!.width : 0; - value += base.template.rightTemplate - ? (templateWidth >= docTemplateWidth ? templateWidth : docTemplateWidth) - : templateWidth; + value += + base.template.rightTemplate + ? (templateWidth >= docTemplateWidth + ? templateWidth + : docTemplateWidth) + : templateWidth; return value; } /// internal method double getBottomIndentHeight( - PdfDocument document, PdfPage page, bool includeMargins) { + PdfDocument document, + PdfPage page, + bool includeMargins, + ) { double value = includeMargins ? base.pageSettings.margins.bottom : 0; final double templateHeight = PdfDocumentTemplateHelper.getHelper(base.template).getBottom(page) != null - ? PdfDocumentTemplateHelper.getHelper(base.template) - .getBottom(page)! - .height + ? PdfDocumentTemplateHelper.getHelper( + base.template, + ).getBottom(page)!.height : 0; final double docTemplateHeight = - PdfDocumentTemplateHelper.getHelper(document.template) - .getBottom(page) != + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getBottom(page) != null - ? PdfDocumentTemplateHelper.getHelper(document.template) - .getBottom(page)! - .height + ? PdfDocumentTemplateHelper.getHelper( + document.template, + ).getBottom(page)!.height : 0; - value += base.template.bottomTemplate - ? (templateHeight >= docTemplateHeight - ? templateHeight - : docTemplateHeight) - : templateHeight; + value += + base.template.bottomTemplate + ? (templateHeight >= docTemplateHeight + ? templateHeight + : docTemplateHeight) + : templateHeight; return value; } @@ -420,45 +451,56 @@ class PdfSectionHelper { } } - void _setPageSettings(PdfDictionary container, - [PdfPageSettings? parentSettings]) { + void _setPageSettings( + PdfDictionary container, [ + PdfPageSettings? parentSettings, + ]) { if (parentSettings == null || base.pageSettings.size != parentSettings.size) { final PdfRectangle bounds = PdfRectangle( - PdfPageSettingsHelper.getHelper(settings).origin.x, - PdfPageSettingsHelper.getHelper(settings).origin.y, - settings.size.width, - settings.size.height); - container[PdfDictionaryProperties.mediaBox] = - PdfArray.fromRectangle(bounds); + PdfPageSettingsHelper.getHelper(settings).origin.x, + PdfPageSettingsHelper.getHelper(settings).origin.y, + settings.size.width, + settings.size.height, + ); + container[PdfDictionaryProperties.mediaBox] = PdfArray.fromRectangle( + bounds, + ); } if (parentSettings == null || base.pageSettings.rotate != parentSettings.rotate) { int rotate = 0; if (_sectionCollection != null) { if (PdfPageSettingsHelper.getHelper(base.pageSettings).isRotation && - !PdfPageSettingsHelper.getHelper(document!.pageSettings) - .isRotation) { - rotate = PdfSectionCollectionHelper.rotateFactor * + !PdfPageSettingsHelper.getHelper( + document!.pageSettings, + ).isRotation) { + rotate = + PdfSectionCollectionHelper.rotateFactor * base.pageSettings.rotate.index; } else { - if (!PdfPageSettingsHelper.getHelper(document!.pageSettings) - .isRotation && + if (!PdfPageSettingsHelper.getHelper( + document!.pageSettings, + ).isRotation && base.pageSettings.rotate != PdfPageRotateAngle.rotateAngle0) { - rotate = PdfSectionCollectionHelper.rotateFactor * + rotate = + PdfSectionCollectionHelper.rotateFactor * base.pageSettings.rotate.index; } else { if (PdfPageSettingsHelper.getHelper(base.pageSettings).isRotation) { - rotate = PdfSectionCollectionHelper.rotateFactor * + rotate = + PdfSectionCollectionHelper.rotateFactor * base.pageSettings.rotate.index; } else if (parentSettings != null) { - rotate = PdfSectionCollectionHelper.rotateFactor * + rotate = + PdfSectionCollectionHelper.rotateFactor * parentSettings.rotate.index; } } } } else { - rotate = PdfSectionCollectionHelper.rotateFactor * + rotate = + PdfSectionCollectionHelper.rotateFactor * base.pageSettings.rotate.index; } final PdfNumber angle = PdfNumber(rotate); @@ -479,15 +521,26 @@ class PdfSectionHelper { /// internal method bool containsTemplates(PdfDocument document, PdfPage page, bool foreground) { - final List documentHeaders = - _getDocumentTemplates(document, page, true, foreground); + final List documentHeaders = _getDocumentTemplates( + document, + page, + true, + foreground, + ); final List documentTemplates = _getDocumentTemplates(document, page, false, foreground); - final List sectionHeaders = - _getSectionTemplates(page, true, foreground); - final List sectionTemplates = - _getSectionTemplates(page, false, foreground); - final bool contains = documentHeaders.isNotEmpty || + final List sectionHeaders = _getSectionTemplates( + page, + true, + foreground, + ); + final List sectionTemplates = _getSectionTemplates( + page, + false, + foreground, + ); + final bool contains = + documentHeaders.isNotEmpty || documentTemplates.isNotEmpty || sectionHeaders.isNotEmpty || sectionTemplates.isNotEmpty; @@ -495,51 +548,66 @@ class PdfSectionHelper { } List _getDocumentTemplates( - PdfDocument document, PdfPage page, bool headers, bool foreground) { + PdfDocument document, + PdfPage page, + bool headers, + bool foreground, + ) { final List templates = []; if (headers) { if (base.template.topTemplate && PdfDocumentTemplateHelper.getHelper(document.template).getTop(page) != null && - PdfDocumentTemplateHelper.getHelper(document.template) - .getTop(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getTop(page)!.foreground == foreground) { - templates.add(PdfDocumentTemplateHelper.getHelper(document.template) - .getTop(page)!); + templates.add( + PdfDocumentTemplateHelper.getHelper(document.template).getTop(page)!, + ); } if (base.template.bottomTemplate && - PdfDocumentTemplateHelper.getHelper(document.template) - .getBottom(page) != + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getBottom(page) != null && - PdfDocumentTemplateHelper.getHelper(document.template) - .getBottom(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getBottom(page)!.foreground == foreground) { - templates.add(PdfDocumentTemplateHelper.getHelper(document.template) - .getBottom(page)!); + templates.add( + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getBottom(page)!, + ); } if (base.template.leftTemplate && - PdfDocumentTemplateHelper.getHelper(document.template) - .getLeft(page) != + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getLeft(page) != null && - PdfDocumentTemplateHelper.getHelper(document.template) - .getLeft(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getLeft(page)!.foreground == foreground) { - templates.add(PdfDocumentTemplateHelper.getHelper(document.template) - .getLeft(page)!); + templates.add( + PdfDocumentTemplateHelper.getHelper(document.template).getLeft(page)!, + ); } if (base.template.rightTemplate && - PdfDocumentTemplateHelper.getHelper(document.template) - .getRight(page) != + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getRight(page) != null && - PdfDocumentTemplateHelper.getHelper(document.template) - .getRight(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getRight(page)!.foreground == foreground) { - templates.add(PdfDocumentTemplateHelper.getHelper(document.template) - .getRight(page)!); + templates.add( + PdfDocumentTemplateHelper.getHelper( + document.template, + ).getRight(page)!, + ); } } else if (base.template.stamp) { final List list = @@ -556,44 +624,51 @@ class PdfSectionHelper { } List _getSectionTemplates( - PdfPage page, bool headers, bool foreground) { + PdfPage page, + bool headers, + bool foreground, + ) { final List templates = []; if (headers) { if (PdfDocumentTemplateHelper.getHelper(base.template).getTop(page) != null && - PdfDocumentTemplateHelper.getHelper(base.template) - .getTop(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + base.template, + ).getTop(page)!.foreground == foreground) { templates.add( - PdfDocumentTemplateHelper.getHelper(base.template).getTop(page)!); + PdfDocumentTemplateHelper.getHelper(base.template).getTop(page)!, + ); } if (PdfDocumentTemplateHelper.getHelper(base.template).getBottom(page) != null && - PdfDocumentTemplateHelper.getHelper(base.template) - .getBottom(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + base.template, + ).getBottom(page)!.foreground == foreground) { - templates.add(PdfDocumentTemplateHelper.getHelper(base.template) - .getBottom(page)!); + templates.add( + PdfDocumentTemplateHelper.getHelper(base.template).getBottom(page)!, + ); } if (PdfDocumentTemplateHelper.getHelper(base.template).getLeft(page) != null && - PdfDocumentTemplateHelper.getHelper(base.template) - .getLeft(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + base.template, + ).getLeft(page)!.foreground == foreground) { templates.add( - PdfDocumentTemplateHelper.getHelper(base.template).getLeft(page)!); + PdfDocumentTemplateHelper.getHelper(base.template).getLeft(page)!, + ); } if (PdfDocumentTemplateHelper.getHelper(base.template).getRight(page) != null && - PdfDocumentTemplateHelper.getHelper(base.template) - .getRight(page)! - .foreground == + PdfDocumentTemplateHelper.getHelper( + base.template, + ).getRight(page)!.foreground == foreground) { templates.add( - PdfDocumentTemplateHelper.getHelper(base.template).getRight(page)!); + PdfDocumentTemplateHelper.getHelper(base.template).getRight(page)!, + ); } } else { final List list = @@ -610,15 +685,29 @@ class PdfSectionHelper { /// internal method void drawTemplates( - PdfPage page, PdfPageLayer layer, PdfDocument document, bool foreground) { - final List documentHeaders = - _getDocumentTemplates(document, page, true, foreground); + PdfPage page, + PdfPageLayer layer, + PdfDocument document, + bool foreground, + ) { + final List documentHeaders = _getDocumentTemplates( + document, + page, + true, + foreground, + ); final List documentTemplates = _getDocumentTemplates(document, page, false, foreground); - final List sectionHeaders = - _getSectionTemplates(page, true, foreground); - final List sectionTemplates = - _getSectionTemplates(page, false, foreground); + final List sectionHeaders = _getSectionTemplates( + page, + true, + foreground, + ); + final List sectionTemplates = _getSectionTemplates( + page, + false, + foreground, + ); if (foreground) { _internaldrawTemplates(layer, document, sectionHeaders); _internaldrawTemplates(layer, document, sectionTemplates); @@ -632,8 +721,11 @@ class PdfSectionHelper { } } - void _internaldrawTemplates(PdfPageLayer layer, PdfDocument document, - List templates) { + void _internaldrawTemplates( + PdfPageLayer layer, + PdfDocument document, + List templates, + ) { if (templates.isNotEmpty) { for (final PdfPageTemplateElement template in templates) { PdfPageTemplateElementHelper.getHelper(template).draw(layer, document); @@ -651,7 +743,7 @@ class PdfSectionHelper { /// internal method void dropCropBox() { - _setPageSettings(_section!, null); + _setPageSettings(_section!); _section![PdfDictionaryProperties.cropBox] = _section![PdfDictionaryProperties.mediaBox]; } 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 46d540307..9e7757142 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 @@ -113,17 +113,20 @@ class PdfSectionCollectionHelper { } void _setPageSettings( - PdfDictionary dictionary, PdfPageSettings pageSettings) { + PdfDictionary dictionary, + PdfPageSettings pageSettings, + ) { final List list = [ 0, 0, pageSettings.size.width, - pageSettings.size.height + pageSettings.size.height, ]; dictionary[PdfDictionaryProperties.mediaBox] = PdfArray(list); if (pageSettings.rotate != PdfPageRotateAngle.rotateAngle0) { - dictionary[PdfDictionaryProperties.rotate] = - PdfNumber(rotateFactor * pageSettings.rotate.index); + dictionary[PdfDictionaryProperties.rotate] = PdfNumber( + rotateFactor * pageSettings.rotate.index, + ); } } 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 3c3b736c6..eac7f6dd7 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 @@ -15,7 +15,7 @@ import '../pdf_document/pdf_document_template.dart'; /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -35,7 +35,7 @@ class PdfSectionTemplate extends PdfDocumentTemplate { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -59,7 +59,7 @@ class PdfSectionTemplate extends PdfDocumentTemplate { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -81,7 +81,7 @@ class PdfSectionTemplate extends PdfDocumentTemplate { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -103,7 +103,7 @@ class PdfSectionTemplate extends PdfDocumentTemplate { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -125,7 +125,7 @@ class PdfSectionTemplate extends PdfDocumentTemplate { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -147,7 +147,7 @@ class PdfSectionTemplate extends PdfDocumentTemplate { /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 578176ba9..7a67400ce 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 @@ -12,9 +12,12 @@ class PdfAttachment extends PdfEmbeddedFileSpecification { //Constructor. /// 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}) - : super(fileName, data) { + PdfAttachment( + super.fileName, + super.data, { + String? description, + String? mimeType, + }) { _embeddedFile = PdfEmbeddedFileSpecificationHelper.getHelper(this).embeddedFile; _updateValues(description, mimeType); @@ -22,9 +25,12 @@ 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}) - : super(fileName, base64.decode(base64String)) { + PdfAttachment.fromBase64String( + String fileName, + String base64String, { + String? description, + String? mimeType, + }) : super(fileName, base64.decode(base64String)) { _embeddedFile = PdfEmbeddedFileSpecificationHelper.getHelper(this).embeddedFile; _updateValues(description, mimeType); @@ -81,9 +87,13 @@ class PdfAttachment extends PdfEmbeddedFileSpecification { set relationship(PdfAttachmentRelationship value) { PdfEmbeddedFileSpecificationHelper.getHelper(this).relationship = value; PdfFileSpecificationBaseHelper.getHelper(this).dictionary!.setProperty( - PdfDictionaryProperties.afRelationship, - PdfName(PdfEmbeddedFileSpecificationHelper.getHelper(this).getEnumName( - PdfEmbeddedFileSpecificationHelper.getHelper(this).relationship))); + PdfDictionaryProperties.afRelationship, + PdfName( + PdfEmbeddedFileSpecificationHelper.getHelper(this).getEnumName( + PdfEmbeddedFileSpecificationHelper.getHelper(this).relationship, + ), + ), + ); } void _updateValues(String? desc, String? 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 5d4910695..685254b8b 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 @@ -26,7 +26,9 @@ class PdfAttachmentCollection extends PdfObjectCollection } PdfAttachmentCollection._( - PdfDictionary dictionary, PdfCrossTable? crossTable) { + PdfDictionary dictionary, + PdfCrossTable? crossTable, + ) { _helper = PdfAttachmentCollectionHelper._(this, dictionary, crossTable); } @@ -81,7 +83,7 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { dictionary.setProperty(PdfDictionaryProperties.names, _array); } PdfAttachmentCollectionHelper._(this.base, this.dictionary, this.crossTable) - : super(base) { + : super(base) { _initializeAttachmentCollection(); } @@ -110,7 +112,9 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { /// internal method static PdfAttachmentCollection load( - PdfDictionary attachmentDictionary, PdfCrossTable? crossTable) { + PdfDictionary attachmentDictionary, + PdfCrossTable? crossTable, + ) { return PdfAttachmentCollection._(attachmentDictionary, crossTable); } @@ -139,7 +143,8 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { int add(PdfAttachment attachment) { if (conformance) { throw ArgumentError( - 'Attachment is not allowed for this conformance level.'); + 'Attachment is not allowed for this conformance level.', + ); } final int position = _doAdd(attachment); dictionary.modify(); @@ -172,9 +177,10 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { 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 && + 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 @@ -197,14 +203,14 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { } //Internal method to get attachement information. - void _attachmentInformation(PdfArray _array) { - if (_array.count != 0) { + void _attachmentInformation(PdfArray array) { + if (array.count != 0) { 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]; - if (_array[k] is PdfReferenceHolder) { - streamDictionary = (_array[k]! as PdfReferenceHolder).object; + for (int i = 0; i < (array.count ~/ 2); i++) { + if (array[k] is PdfReferenceHolder || array[k] is PdfDictionary) { + IPdfPrimitive? streamDictionary = array[k]; + if (array[k] is PdfReferenceHolder) { + streamDictionary = (array[k]! as PdfReferenceHolder).object; } if (streamDictionary is PdfDictionary) { PdfStream? stream = PdfStream(); @@ -212,8 +218,9 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { if (streamDictionary.containsKey(PdfDictionaryProperties.ef)) { if (streamDictionary[PdfDictionaryProperties.ef] is PdfDictionary) { - attachmentStream = streamDictionary[PdfDictionaryProperties.ef] - as PdfDictionary?; + attachmentStream = + streamDictionary[PdfDictionaryProperties.ef] + as PdfDictionary?; } else if (streamDictionary[PdfDictionaryProperties.ef] is PdfReferenceHolder) { final PdfReferenceHolder streamHolder = @@ -243,12 +250,14 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { 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( - fileStream[PdfDictionaryProperties.subtype]); + fileStream[PdfDictionaryProperties.subtype], + ); if (subtype is PdfName) { attachment.mimeType = subtype.name! .replaceAll('#23', '#') @@ -258,33 +267,42 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { } if (fileStream is PdfDictionary && fileStream.containsKey(PdfDictionaryProperties.params)) { - final IPdfPrimitive? mParams = PdfCrossTable.dereference( - fileStream[PdfDictionaryProperties.params]) - as PdfDictionary?; + final IPdfPrimitive? mParams = + PdfCrossTable.dereference( + fileStream[PdfDictionaryProperties.params], + ) + as PdfDictionary?; if (mParams is PdfDictionary) { final IPdfPrimitive? creationDate = PdfCrossTable.dereference( - mParams[PdfDictionaryProperties.creationDate]); + mParams[PdfDictionaryProperties.creationDate], + ); final IPdfPrimitive? modifiedDate = PdfCrossTable.dereference( - mParams[PdfDictionaryProperties.modificationDate]); + mParams[PdfDictionaryProperties.modificationDate], + ); if (creationDate is PdfString) { - attachment.creationDate = - mParams.getDateTime(creationDate); + attachment.creationDate = mParams.getDateTime( + creationDate, + ); } if (modifiedDate is PdfString) { - attachment.modificationDate = - mParams.getDateTime(modifiedDate); + attachment.modificationDate = mParams.getDateTime( + modifiedDate, + ); } } } - if (streamDictionary - .containsKey(PdfDictionaryProperties.afRelationship)) { + if (streamDictionary.containsKey( + PdfDictionaryProperties.afRelationship, + )) { final IPdfPrimitive? relationShip = PdfCrossTable.dereference( - streamDictionary[PdfDictionaryProperties.afRelationship]); + streamDictionary[PdfDictionaryProperties.afRelationship], + ); if (relationShip is PdfName) { - attachment.relationship = - _obtainRelationShip(relationShip.name); + attachment.relationship = _obtainRelationShip( + relationShip.name, + ); } } if (streamDictionary.containsKey('Desc')) { @@ -293,16 +311,21 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { } } 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); @@ -342,9 +365,10 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { //Adds the attachment. int _doAdd(PdfAttachment attachment) { final String fileName = attachment.fileName; - final String converted = utf8.encode(fileName).length != fileName.length - ? 'Attachment ${_count++}' - : fileName; + 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)) { @@ -376,8 +400,9 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { index = list.indexOf(attachment); } _array!.removeAt(2 * index!); - IPdfPrimitive? attachmentDictionay = - PdfCrossTable.dereference(_array![2 * index]); + IPdfPrimitive? attachmentDictionay = PdfCrossTable.dereference( + _array![2 * index], + ); if (attachmentDictionay is PdfDictionary) { _removeAttachementObjects(attachmentDictionay); attachmentDictionay = null; @@ -388,34 +413,36 @@ class PdfAttachmentCollectionHelper extends PdfObjectCollectionHelper { //Removing attachment dictionary and stream from main object collection. void _removeAttachementObjects(PdfDictionary attachmentDictionary) { - PdfMainObjectCollection? _objectCollection; + PdfMainObjectCollection? objectCollection; if (crossTable != null && crossTable!.document != null) { - _objectCollection = + objectCollection = PdfDocumentHelper.getHelper(crossTable!.document!).objects; } - if (_objectCollection != null) { + if (objectCollection != null) { if (attachmentDictionary.containsKey(PdfDictionaryProperties.ef)) { final IPdfPrimitive? embedded = PdfCrossTable.dereference( - attachmentDictionary[PdfDictionaryProperties.ef]); + attachmentDictionary[PdfDictionaryProperties.ef], + ); if (embedded is PdfDictionary) { if (embedded.containsKey(PdfDictionaryProperties.f)) { - final IPdfPrimitive? stream = - PdfCrossTable.dereference(embedded[PdfDictionaryProperties.f]); + final IPdfPrimitive? stream = PdfCrossTable.dereference( + embedded[PdfDictionaryProperties.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(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) { 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 6e5f0ddee..a87977db4 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 @@ -23,8 +23,12 @@ import 'pdf_static_field.dart'; /// Represents a fields which is calculated before the document saves. abstract class PdfAutomaticField { /// Initialize [PdfAutomaticField] object - void _internal(PdfFont? font, - {Rect? bounds, PdfBrush? brush, PdfAutomaticFieldHelper? helper}) { + void _internal( + PdfFont? font, { + Rect? bounds, + PdfBrush? brush, + PdfAutomaticFieldHelper? helper, + }) { _helper = helper!; this.font = font ?? PdfStandardFont(PdfFontFamily.helvetica, 8); if (bounds != null) { @@ -75,7 +79,7 @@ abstract class PdfAutomaticField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); ///``` @@ -116,7 +120,7 @@ abstract class PdfAutomaticField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); ///``` @@ -155,7 +159,7 @@ abstract class PdfAutomaticField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -199,7 +203,7 @@ abstract class PdfAutomaticField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); ///``` @@ -246,7 +250,7 @@ abstract class PdfAutomaticField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); ///``` @@ -290,15 +294,15 @@ abstract class PdfAutomaticField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); ///``` void draw(PdfGraphics graphics, [Offset? location]) { location ??= Offset.zero; - PdfGraphicsHelper.getHelper(graphics) - .autoFields! - .add(PdfAutomaticFieldInfo(this, PdfPoint.fromOffset(location))); + PdfGraphicsHelper.getHelper(graphics).autoFields!.add( + PdfAutomaticFieldInfo(this, PdfPoint.fromOffset(location)), + ); } } @@ -433,40 +437,71 @@ class PdfAutomaticFieldHelper { String? getValue(PdfGraphics graphics) { if (base is PdfCompositeField) { return PdfCompositeFieldHelper.getValue( - base as PdfCompositeField, graphics); + base as PdfCompositeField, + graphics, + ); } else if (base is PdfDateTimeField) { return PdfDateTimeFieldHelper.getValue( - base as PdfDateTimeField, graphics); + base as PdfDateTimeField, + graphics, + ); } else if (base is PdfDestinationPageNumberField) { return PdfDestinationPageNumberFieldHelper.getValue( - base as PdfDestinationPageNumberField, graphics); + base as PdfDestinationPageNumberField, + graphics, + ); } else if (base is PdfPageNumberField) { - return PdfPageNumberFieldHelper.getHelper(base as PdfPageNumberField) - .getValue(graphics); + return PdfPageNumberFieldHelper.getHelper( + base as PdfPageNumberField, + ).getValue(graphics); } else if (base is PdfPageCountField) { return PdfPageCountFieldHelper.getValue( - base as PdfPageCountField, graphics); + base as PdfPageCountField, + graphics, + ); } return graphics as String; } /// internal method - void performDraw(PdfGraphics graphics, PdfPoint? location, double scalingX, - double scalingY) { + void performDraw( + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { if (base.bounds.height == 0 || base.bounds.width == 0) { final String text = getValue(graphics)!; - _templateSize = base.font.measureString(text, - layoutArea: base.bounds.size, format: base.stringFormat); + _templateSize = base.font.measureString( + text, + layoutArea: base.bounds.size, + format: base.stringFormat, + ); } if (base is PdfStaticField) { PdfStaticFieldHelper.performDraw( - base as PdfStaticField, graphics, location, scalingX, scalingY); + base as PdfStaticField, + graphics, + location, + scalingX, + scalingY, + ); } else if (base is PdfSingleValueField) { PdfSingleValueFieldHelper.performDraw( - base as PdfSingleValueField, graphics, location, scalingX, scalingY); + base as PdfSingleValueField, + graphics, + location, + scalingX, + scalingY, + ); } else if (base is PdfMultipleValueField) { - PdfMultipleValueFieldHelper.performDraw(base as PdfMultipleValueField, - graphics, location, scalingX, scalingY); + PdfMultipleValueFieldHelper.performDraw( + base as PdfMultipleValueField, + graphics, + location, + scalingX, + scalingY, + ); } } 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 f3af96f1d..4df42076a 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 @@ -5,8 +5,12 @@ import 'pdf_automatic_field.dart'; class PdfAutomaticFieldInfo { // constructor /// internal constructor - PdfAutomaticFieldInfo(this.field, - [PdfPoint? location, double scalingX = 1, double scalingY = 1]) { + PdfAutomaticFieldInfo( + this.field, [ + PdfPoint? location, + double scalingX = 1, + double scalingY = 1, + ]) { this.location = location ?? PdfPoint.empty; scalingX = scalingX; scalingY = scalingY; 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 f02ac4785..b3c9d1c76 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 @@ -1,4 +1,3 @@ -import '../../graphics/brushes/pdf_solid_brush.dart'; import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/pdf_graphics.dart'; import 'pdf_automatic_field.dart'; @@ -34,7 +33,7 @@ import 'pdf_multiple_value_field.dart'; /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -74,16 +73,16 @@ class PdfCompositeField extends PdfMultipleValueField { /// //Add the header at top of the document /// document.template.top = header; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfCompositeField( - {PdfFont? font, - PdfBrush? brush, - String? text, - List? fields}) - : super(font: font, brush: brush) { + PdfCompositeField({ + super.font, + super.brush, + String? text, + List? fields, + }) { this.text = (text == null) ? '' : text; if (fields != null) { this.fields = fields; @@ -131,7 +130,7 @@ class PdfCompositeField extends PdfMultipleValueField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -172,7 +171,7 @@ class PdfCompositeField extends PdfMultipleValueField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -191,8 +190,10 @@ class PdfCompositeField extends PdfMultipleValueField { if (fields.isNotEmpty) { copyText = text; for (int i = 0; i < fields.length; i++) { - copyText = copyText!.replaceAll('{$i}', - PdfAutomaticFieldHelper.getHelper(fields[i]).getValue(graphics)!); + copyText = copyText!.replaceAll( + '{$i}', + PdfAutomaticFieldHelper.getHelper(fields[i]).getValue(graphics)!, + ); } } 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 5ff1a8478..139999c08 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,9 +1,6 @@ -import 'dart:ui'; - import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; -import '../../graphics/brushes/pdf_solid_brush.dart'; import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/pdf_graphics.dart'; import 'pdf_static_field.dart'; @@ -40,7 +37,7 @@ import 'pdf_static_field.dart'; /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -82,12 +79,11 @@ class PdfDateTimeField extends PdfStaticField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfDateTimeField({PdfFont? font, PdfBrush? brush, Rect? bounds}) - : super(font: font, brush: brush, bounds: bounds); + PdfDateTimeField({super.font, super.brush, super.bounds}); /// Get the current date and set the required date. /// ```dart @@ -123,7 +119,7 @@ class PdfDateTimeField extends PdfStaticField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -164,7 +160,7 @@ class PdfDateTimeField extends PdfStaticField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -204,7 +200,7 @@ class PdfDateTimeField extends PdfStaticField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 3834c3a91..025eab1fb 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 @@ -11,9 +11,12 @@ class PdfDestinationPageNumberField extends PdfPageNumberField { // constructor /// Initializes a new instance of the [PdfDestinationPageNumberField] class /// may include with [PdfFont], [PdfBrush] and [Rect]. - PdfDestinationPageNumberField( - {PdfPage? page, PdfFont? font, PdfBrush? brush, Rect? bounds}) - : super(font: font, brush: brush, bounds: bounds) { + PdfDestinationPageNumberField({ + PdfPage? page, + super.font, + super.brush, + super.bounds, + }) { if (page != null) { this.page = page; } @@ -33,7 +36,9 @@ class PdfDestinationPageNumberField extends PdfPageNumberField { class PdfDestinationPageNumberFieldHelper { /// internal method static String getValue( - PdfDestinationPageNumberField field, PdfGraphics graphics) { + PdfDestinationPageNumberField field, + PdfGraphics graphics, + ) { return field._getValue(graphics); } } 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 f4a759d21..a10d94b0e 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 @@ -1,9 +1,7 @@ import 'dart:ui'; import '../../drawing/drawing.dart'; -import '../../graphics/brushes/pdf_solid_brush.dart'; import '../../graphics/figures/pdf_template.dart'; -import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/pdf_graphics.dart'; import 'pdf_automatic_field.dart'; import 'pdf_dynamic_field.dart'; @@ -13,50 +11,64 @@ import 'pdf_template_value_pair.dart'; abstract class PdfMultipleValueField extends PdfDynamicField { // constructor /// internal constructor - PdfMultipleValueField({PdfFont? font, PdfBrush? brush, Rect? bounds}) - : super(font: font, bounds: bounds, brush: brush); + PdfMultipleValueField({super.font, super.brush, super.bounds}); // fields final Map _list = {}; // implementation - void _performDraw(PdfGraphics graphics, PdfPoint? _location, double scalingX, - double scalingY) { - final String? value = - PdfAutomaticFieldHelper.getHelper(this).getValue(graphics); + void _performDraw( + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { + final String? value = PdfAutomaticFieldHelper.getHelper( + this, + ).getValue(graphics); if (_list.containsKey(graphics)) { final PdfTemplateValuePair pair = _list[graphics]!; if (pair.value != value) { final Size size = PdfAutomaticFieldHelper.getHelper(this).obtainSize(); pair.template.reset(size.width, size.height); - pair.template.graphics!.drawString(value!, font, - pen: pen, - brush: brush, - bounds: Rect.fromLTWH(0, 0, size.width, size.height), - format: stringFormat); + pair.template.graphics!.drawString( + value!, + font, + pen: pen, + brush: brush, + bounds: Rect.fromLTWH(0, 0, size.width, size.height), + format: stringFormat, + ); } } else { final PdfTemplate template = PdfTemplate( - PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().height); + PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, + PdfAutomaticFieldHelper.getHelper(this).obtainSize().height, + ); _list[graphics] = PdfTemplateValuePair(template, value!); - template.graphics!.drawString(value, font, - pen: pen, - brush: brush, - bounds: Rect.fromLTWH( - 0, - 0, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().height), - format: stringFormat); - final Offset drawLocation = - Offset(_location!.x + bounds.left, _location.y + bounds.top); + template.graphics!.drawString( + value, + font, + pen: pen, + brush: brush, + bounds: Rect.fromLTWH( + 0, + 0, + PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, + PdfAutomaticFieldHelper.getHelper(this).obtainSize().height, + ), + format: stringFormat, + ); + final Offset drawLocation = Offset( + location!.x + bounds.left, + location.y + bounds.top, + ); graphics.drawPdfTemplate( - template, - drawLocation, - Size( - template.size.width * scalingX, template.size.height * scalingY)); + template, + drawLocation, + Size(template.size.width * scalingX, template.size.height * scalingY), + ); } } } @@ -65,8 +77,13 @@ abstract class PdfMultipleValueField extends PdfDynamicField { /// [PdfMultipleValueField] helper class PdfMultipleValueFieldHelper { /// internal method - static void performDraw(PdfMultipleValueField field, PdfGraphics graphics, - PdfPoint? _location, double scalingX, double scalingY) { - field._performDraw(graphics, _location, scalingX, scalingY); + static void performDraw( + PdfMultipleValueField field, + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { + field._performDraw(graphics, location, scalingX, scalingY); } } 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 7f9202613..467af91ca 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 @@ -48,7 +48,7 @@ import 'pdf_single_value_field.dart'; /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -89,13 +89,16 @@ class PdfPageCountField extends PdfSingleValueField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfPageCountField( - {PdfFont? font, PdfBrush? brush, Rect? bounds, bool? isSectionPageCount}) - : super(font, brush, bounds) { + PdfPageCountField({ + PdfFont? font, + PdfBrush? brush, + Rect? bounds, + bool? isSectionPageCount, + }) : super(font, brush, bounds) { _isSectionPageCount = isSectionPageCount != null && isSectionPageCount; } @@ -134,7 +137,7 @@ class PdfPageCountField extends PdfSingleValueField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -153,11 +156,12 @@ class PdfPageCountField extends PdfSingleValueField { final int count = PdfSectionHelper.getHelper(section).count; return PdfAutomaticFieldHelper.convert(count, numberStyle); } else { - final PdfDocument document = PdfSectionCollectionHelper.getHelper( - PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(page).section!) - .parent!) - .document!; + final PdfDocument document = + PdfSectionCollectionHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).parent!, + ).document!; final int number = document.pages.count; return PdfAutomaticFieldHelper.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 b67d12ab5..244c536b4 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 @@ -47,7 +47,7 @@ import 'pdf_multiple_value_field.dart'; /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -88,13 +88,16 @@ class PdfPageNumberField extends PdfMultipleValueField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfPageNumberField( - {PdfFont? font, PdfBrush? brush, Rect? bounds, bool? isSectionPageNumber}) - : super(font: font, brush: brush, bounds: bounds) { + PdfPageNumberField({ + super.font, + super.brush, + super.bounds, + bool? isSectionPageNumber, + }) { _helper = PdfPageNumberFieldHelper(this); _helper._isSectionPageNumber = isSectionPageNumber != null && isSectionPageNumber; @@ -135,7 +138,7 @@ class PdfPageNumberField extends PdfMultipleValueField { /// //Add the footer at the bottom of the document /// document.template.bottom = footer; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -176,11 +179,12 @@ class PdfPageNumberFieldHelper { final int index = PdfSectionHelper.getHelper(section).indexOf(page) + 1; return PdfAutomaticFieldHelper.convert(index, base.numberStyle); } else { - final PdfDocument document = PdfSectionCollectionHelper.getHelper( - PdfSectionHelper.getHelper( - PdfPageHelper.getHelper(page!).section!) - .parent!) - .document!; + final PdfDocument document = + PdfSectionCollectionHelper.getHelper( + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page!).section!, + ).parent!, + ).document!; final int pageIndex = document.pages.indexOf(page) + 1; return PdfAutomaticFieldHelper.convert(pageIndex, base.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 d7eb43002..35baa71d9 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 @@ -16,7 +16,7 @@ abstract class PdfSingleValueField extends PdfDynamicField { // constructor /// internal constructor PdfSingleValueField([PdfFont? font, PdfBrush? brush, Rect? bounds]) - : super(font: font, bounds: bounds, brush: brush); + : super(font: font, bounds: bounds, brush: brush); // field final Map _list = @@ -24,13 +24,18 @@ abstract class PdfSingleValueField extends PdfDynamicField { final List _painterGraphics = []; // implementation - void _performDraw(PdfGraphics graphics, PdfPoint? _location, double scalingX, - double scalingY) { + void _performDraw( + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { if (PdfGraphicsHelper.getHelper(graphics).page is PdfPage) { final PdfPage page = PdfDynamicField.getPageFromGraphics(graphics); final PdfDocument? document = PdfPageHelper.getHelper(page).document; - final String? value = - PdfAutomaticFieldHelper.getHelper(this).getValue(graphics); + final String? value = PdfAutomaticFieldHelper.getHelper( + this, + ).getValue(graphics); if (_list.containsKey(document)) { final PdfTemplateValuePair pair = _list[document]!; @@ -38,42 +43,63 @@ abstract class PdfSingleValueField extends PdfDynamicField { final Size size = PdfAutomaticFieldHelper.getHelper(this).obtainSize(); pair.template.reset(size.width, size.height); - pair.template.graphics!.drawString(value!, font, - pen: pen, - brush: brush, - bounds: bounds.topLeft & size, - format: stringFormat); + pair.template.graphics!.drawString( + value!, + font, + pen: pen, + brush: brush, + bounds: bounds.topLeft & size, + format: stringFormat, + ); } if (!_painterGraphics.contains(graphics)) { - final Offset drawLocation = - Offset(_location!.x + bounds.left, _location.y + bounds.top); + final Offset drawLocation = Offset( + location!.x + bounds.left, + location.y + bounds.top, + ); graphics.drawPdfTemplate( - pair.template, - drawLocation, - Size(pair.template.size.width * scalingX, - pair.template.size.height * scalingY)); + pair.template, + drawLocation, + Size( + pair.template.size.width * scalingX, + pair.template.size.height * scalingY, + ), + ); _painterGraphics.add(graphics); } } else { final PdfTemplate template = PdfTemplate( - PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().height); + PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, + PdfAutomaticFieldHelper.getHelper(this).obtainSize().height, + ); _list[document] = PdfTemplateValuePair(template, value!); final PdfTemplateValuePair pair = _list[document]!; - template.graphics!.drawString(value, font, - pen: pen, - 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); + template.graphics!.drawString( + value, + font, + pen: pen, + 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, + ); graphics.drawPdfTemplate( - pair.template, - drawLocation, - Size(pair.template.size.width * scalingX, - pair.template.size.height * scalingY)); + pair.template, + drawLocation, + Size( + pair.template.size.width * scalingX, + pair.template.size.height * scalingY, + ), + ); _painterGraphics.add(graphics); } } @@ -84,8 +110,13 @@ abstract class PdfSingleValueField extends PdfDynamicField { /// [PdfSingleValueField] value class PdfSingleValueFieldHelper { /// internal method - static void performDraw(PdfSingleValueField field, PdfGraphics graphics, - PdfPoint? _location, double scalingX, double scalingY) { - field._performDraw(graphics, _location, scalingX, scalingY); + static void performDraw( + PdfSingleValueField field, + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { + field._performDraw(graphics, location, scalingX, scalingY); } } 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 7033a69af..709ac225b 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 @@ -21,39 +21,57 @@ abstract class PdfStaticField extends PdfAutomaticField { final List _graphicsList = []; // implementation - void _performDraw(PdfGraphics graphics, PdfPoint? location, double scalingX, - double scalingY) { - final String? value = - PdfAutomaticFieldHelper.getHelper(this).getValue(graphics); - final Offset drawLocation = - Offset(location!.x + bounds.left, location.y + bounds.top); + void _performDraw( + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { + final String? value = PdfAutomaticFieldHelper.getHelper( + this, + ).getValue(graphics); + final Offset drawLocation = Offset( + location!.x + bounds.left, + location.y + bounds.top, + ); if (_template == null) { _template = PdfTemplate( + PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, + PdfAutomaticFieldHelper.getHelper(this).obtainSize().height, + ); + _template!.graphics!.drawString( + value!, + font, + pen: pen, + brush: brush, + bounds: Rect.fromLTWH( + 0, + 0, PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().height); - _template!.graphics!.drawString(value!, font, - pen: pen, - brush: brush, - bounds: Rect.fromLTWH( - 0, - 0, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().width, - PdfAutomaticFieldHelper.getHelper(this).obtainSize().height), - format: stringFormat); + PdfAutomaticFieldHelper.getHelper(this).obtainSize().height, + ), + format: stringFormat, + ); graphics.drawPdfTemplate( - _template!, - drawLocation, - Size(_template!.size.width * scalingX, - _template!.size.height * scalingY)); + _template!, + drawLocation, + Size( + _template!.size.width * scalingX, + _template!.size.height * scalingY, + ), + ); _graphicsList.add(graphics); } else { if (!_graphicsList.contains(graphics)) { graphics.drawPdfTemplate( - _template!, - drawLocation, - Size(_template!.size.width * scalingX, - _template!.size.height * scalingY)); + _template!, + drawLocation, + Size( + _template!.size.width * scalingX, + _template!.size.height * scalingY, + ), + ); _graphicsList.add(graphics); } } @@ -64,8 +82,13 @@ abstract class PdfStaticField extends PdfAutomaticField { /// [PdfStaticField] helper class PdfStaticFieldHelper { /// internal method - static void performDraw(PdfStaticField staticField, PdfGraphics graphics, - PdfPoint? location, double scalingX, double scalingY) { + static void performDraw( + PdfStaticField staticField, + PdfGraphics graphics, + PdfPoint? location, + double scalingX, + double scalingY, + ) { staticField._performDraw(graphics, location, scalingX, scalingY); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/enums.dart index 05689556d..e20616b36 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/enums.dart @@ -25,7 +25,7 @@ enum PdfVersion { version1_7, /// PDF version 2.0. - version2_0 + version2_0, } /// Specifies the type of the PDF cross-reference. @@ -46,7 +46,7 @@ enum PdfCrossReferenceType { /// and a data stream. This leads to more compact representation of the file /// data especially along with the compression enabled. /// This format is supported by PDF 1.5 version and higher only. - crossReferenceStream + crossReferenceStream, } /// Specifies the PDF document's conformance-level. @@ -85,7 +85,7 @@ enum PdfAttachmentRelationship { supplement, ///Relationship is not known or cannot be described using one of the other values - unspecified + unspecified, } /// Compression level. @@ -106,5 +106,5 @@ enum PdfCompressionLevel { aboveNormal, /// Use best compression, slow enough - best + best, } 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 c452a7561..5ba384b3e 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 @@ -8,7 +8,7 @@ /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) /// ..textStyle = [PdfTextStyle.bold]; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 f4ae5141b..0fa9c5b2a 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 @@ -7,7 +7,6 @@ import '../../general/enum.dart'; import '../../general/pdf_destination.dart'; import '../../general/pdf_named_destination.dart'; import '../../general/pdf_named_destination_collection.dart'; -import '../../graphics/enums.dart'; import '../../graphics/pdf_color.dart'; import '../../io/pdf_constants.dart'; import '../../io/pdf_cross_table.dart'; @@ -34,25 +33,30 @@ import 'enums.dart'; /// ..textStyle = [PdfTextStyle.bold] /// ..color = PdfColor(255, 0, 0); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await 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, - {bool isExpanded = false, - PdfColor? color, - PdfDestination? destination, - PdfNamedDestination? namedDestination, - PdfAction? action, - List? textStyle}) - : super._internal() { + PdfBookmark._internal( + String title, + PdfBookmarkBase parent, + PdfBookmark? previous, + PdfBookmark? next, { + bool isExpanded = false, + PdfColor? color, + PdfDestination? destination, + PdfNamedDestination? namedDestination, + PdfAction? action, + List? textStyle, + }) : super._internal() { _parent = parent; _helper.dictionary!.setProperty( - PdfDictionaryProperties.parent, PdfReferenceHolder(parent)); + PdfDictionaryProperties.parent, + PdfReferenceHolder(parent), + ); _previous = previous; _next = next; this.title = title; @@ -74,8 +78,8 @@ class PdfBookmark extends PdfBookmarkBase { } } - PdfBookmark._load(PdfDictionary? dictionary, PdfCrossTable crossTable) - : super._load(dictionary, crossTable); + PdfBookmark._load(super.dictionary, PdfCrossTable super.crossTable) + : super._load(); //Fields /// Internal variable to store destination. @@ -99,6 +103,269 @@ class PdfBookmark extends PdfBookmarkBase { /// Internal variable to store action. PdfAction? _action; + /// Internal variable to store RegExp. + final RegExp _regex = RegExp(r'[\u0080-\u00FF]'); + + /// Internal variable to store byte value. + final List _pdfEncodingByteToChar = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 0x2022, + 0x2020, + 0x2021, + 0x2026, + 0x2014, + 0x2013, + 0x0192, + 0x2044, + 0x2039, + 0x203a, + 0x2212, + 0x2030, + 0x201e, + 0x201c, + 0x201d, + 0x2018, + 0x2019, + 0x201a, + 0x2122, + 0xfb01, + 0xfb02, + 0x0141, + 0x0152, + 0x0160, + 0x0178, + 0x017d, + 0x0131, + 0x0142, + 0x0153, + 0x0161, + 0x017e, + 65533, + 0x20ac, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + ]; + //Properties /// Gets or sets the outline destination page. /// @@ -112,7 +379,7 @@ class PdfBookmark extends PdfBookmarkBase { /// ..textStyle = [PdfTextStyle.bold] /// ..color = PdfColor(255, 0, 0); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -151,7 +418,7 @@ class PdfBookmark extends PdfBookmarkBase { /// ..namedDestination = namedDestination /// ..color = PdfColor(255, 0, 0); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -169,11 +436,17 @@ class PdfBookmark extends PdfBookmarkBase { _namedDestination = value; final PdfDictionary dictionary = PdfDictionary(); dictionary.setProperty( - PdfDictionaryProperties.d, PdfString(_namedDestination!.title)); + PdfDictionaryProperties.d, + PdfString(_namedDestination!.title), + ); dictionary.setProperty( - PdfDictionaryProperties.s, PdfName(PdfDictionaryProperties.goTo)); + PdfDictionaryProperties.s, + PdfName(PdfDictionaryProperties.goTo), + ); dictionary.setProperty( - PdfDictionaryProperties.a, PdfReferenceHolder(dictionary)); + PdfDictionaryProperties.a, + PdfReferenceHolder(dictionary), + ); } } @@ -190,7 +463,7 @@ class PdfBookmark extends PdfBookmarkBase { /// //Set the bookmark title. /// ..title = 'Bookmark'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -224,7 +497,7 @@ class PdfBookmark extends PdfBookmarkBase { /// ..color = PdfColor(255, 0, 0) /// ..title = 'Bookmark'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -259,7 +532,7 @@ class PdfBookmark extends PdfBookmarkBase { /// ..color = PdfColor(255, 0, 0) /// ..title = 'Bookmark'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -295,7 +568,7 @@ class PdfBookmark extends PdfBookmarkBase { /// //Set the bookmark action. /// ..action = PdfUriAction('http://www.google.com'); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -303,8 +576,10 @@ class PdfBookmark extends PdfBookmarkBase { set action(PdfAction? value) { if (value != null && _action != value) { _action = value; - _helper.dictionary!.setProperty(PdfDictionaryProperties.a, - PdfReferenceHolder(PdfActionHelper.getHelper(_action!).dictionary)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.a, + PdfReferenceHolder(PdfActionHelper.getHelper(_action!).dictionary), + ); } } @@ -321,7 +596,7 @@ class PdfBookmark extends PdfBookmarkBase { /// //Get if is expanded. /// bool expand = bookmark.isExpanded; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -341,8 +616,10 @@ class PdfBookmark extends PdfBookmarkBase { set _previous(PdfBookmark? value) { if (_previousBookmark != value) { _previousBookmark = value; - _helper.dictionary! - .setProperty(PdfDictionaryProperties.prev, PdfReferenceHolder(value)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.prev, + PdfReferenceHolder(value), + ); } } @@ -359,8 +636,10 @@ class PdfBookmark extends PdfBookmarkBase { set _next(PdfBookmark? value) { if (_nextBookmark != value) { _nextBookmark = value; - _helper.dictionary! - .setProperty(PdfDictionaryProperties.next, PdfReferenceHolder(value)); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.next, + PdfReferenceHolder(value), + ); } } @@ -384,8 +663,9 @@ class PdfBookmark extends PdfBookmarkBase { if (array != null && _color.isEmpty) { _helper.dictionary!.remove(PdfDictionaryProperties.c); } else { - _helper.dictionary![PdfDictionaryProperties.c] = - PdfColorHelper.toArray(_color, PdfColorSpace.rgb); + _helper.dictionary![PdfDictionaryProperties.c] = PdfColorHelper.toArray( + _color, + ); } } @@ -405,10 +685,25 @@ class PdfBookmark extends PdfBookmarkBase { String _obtainTitle() { String title = ''; if (_helper.dictionary!.containsKey(PdfDictionaryProperties.title)) { - final PdfString? str = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.title]) as PdfString?; + final PdfString? str = + PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.title], + ) + as PdfString?; if (str != null && str.value != null) { title = str.value!; + if (_regex.hasMatch(title)) { + for (int i = 0; i < title.length; i++) { + if (_regex.hasMatch(title[i])) { + title = title.replaceAll( + title[i], + String.fromCharCode( + _pdfEncodingByteToChar[title.codeUnitAt(i) & 0xff], + ), + ); + } + } + } } } return title; @@ -417,8 +712,11 @@ class PdfBookmark extends PdfBookmarkBase { PdfColor _obtainColor() { PdfColor color = PdfColor(0, 0, 0); if (_helper.dictionary!.containsKey(PdfDictionaryProperties.c)) { - final PdfArray? colours = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.c]) as PdfArray?; + final PdfArray? colours = + PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.c], + ) + as PdfArray?; if (colours != null && colours.count > 2) { double? red = 0; double green = 0; @@ -445,8 +743,11 @@ class PdfBookmark extends PdfBookmarkBase { PdfTextStyle _obtainTextStyle() { PdfTextStyle style = PdfTextStyle.regular; if (_helper.dictionary!.containsKey(PdfDictionaryProperties.f)) { - final PdfNumber? flag = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.f]) as PdfNumber?; + final PdfNumber? flag = + PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.f], + ) + as PdfNumber?; int flagValue = 0; if (flag != null) { flagValue = flag.value!.toInt() - 1; @@ -464,9 +765,11 @@ class PdfBookmark extends PdfBookmarkBase { nextBookmark = _parent!._list[index] as PdfBookmark?; } else { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.next)) { - final PdfDictionary? next = _helper._crossTable - .getObject(_helper.dictionary![PdfDictionaryProperties.next]) - as PdfDictionary?; + final PdfDictionary? next = + _helper._crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.next], + ) + as PdfDictionary?; nextBookmark = PdfBookmark._load(next, _helper._crossTable); } } @@ -481,9 +784,11 @@ class PdfBookmark extends PdfBookmarkBase { prevBookmark = _helper._bookmarkList[index] as PdfBookmark?; } else { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.prev)) { - final PdfDictionary? prev = _helper._crossTable - .getObject(_helper.dictionary![PdfDictionaryProperties.prev]) - as PdfDictionary?; + final PdfDictionary? prev = + _helper._crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.prev], + ) + as PdfDictionary?; prevBookmark = PdfBookmark._load(prev, _helper._crossTable); } } @@ -494,7 +799,7 @@ class PdfBookmark extends PdfBookmarkBase { final List rgb = [ color.r.toDouble(), color.g.toDouble(), - color.b.toDouble() + color.b.toDouble(), ]; final PdfArray colors = PdfArray(rgb); _helper.dictionary!.setProperty(PdfDictionaryProperties.c, colors); @@ -518,16 +823,22 @@ class PdfBookmark extends PdfBookmarkBase { IPdfPrimitive? destination; if (namedCollection != null) { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.a)) { - final PdfDictionary? action = PdfCrossTable.dereference( - _helper.dictionary![PdfDictionaryProperties.a]) as PdfDictionary?; + final PdfDictionary? action = + PdfCrossTable.dereference( + _helper.dictionary![PdfDictionaryProperties.a], + ) + as PdfDictionary?; if (action != null && action.containsKey(PdfDictionaryProperties.d)) { - destination = - PdfCrossTable.dereference(action[PdfDictionaryProperties.d]); + destination = PdfCrossTable.dereference( + action[PdfDictionaryProperties.d], + ); } - } else if (_helper.dictionary! - .containsKey(PdfDictionaryProperties.dest)) { - destination = _helper._crossTable - .getObject(_helper.dictionary![PdfDictionaryProperties.dest]); + } else if (_helper.dictionary!.containsKey( + PdfDictionaryProperties.dest, + )) { + destination = _helper._crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.dest], + ); } if (destination != null) { @@ -556,8 +867,9 @@ class PdfBookmark extends PdfBookmarkBase { PdfDestination? _obtainDestination() { if (_helper.dictionary!.containsKey(PdfDictionaryProperties.dest) && (_destination == null)) { - final IPdfPrimitive? obj = _helper._crossTable - .getObject(_helper.dictionary![PdfDictionaryProperties.dest]); + final IPdfPrimitive? obj = _helper._crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.dest], + ); PdfArray? array = obj as PdfArray?; final PdfName? name = (obj is PdfName) ? obj as PdfName? : null; final PdfDocument? ldDoc = _helper._crossTable.document; @@ -569,87 +881,98 @@ class PdfBookmark extends PdfBookmarkBase { } if (array != null) { - final PdfReferenceHolder? holder = array[0] as PdfReferenceHolder?; + final IPdfPrimitive? holder = array[0]; PdfPage? page; - if (holder != null) { - final PdfDictionary? dic = - _helper._crossTable.getObject(holder) as PdfDictionary?; - if (ldDoc != null && dic != null) { + if (holder != null && holder is PdfReferenceHolder) { + final IPdfPrimitive? dic = _helper._crossTable.getObject(holder); + if (ldDoc != null && dic != null && dic is PdfDictionary) { page = PdfPageCollectionHelper.getHelper(ldDoc.pages).getPage(dic); } - PdfName? mode; + IPdfPrimitive? mode; if (array.count > 1) { - mode = array[1]! as PdfName; + mode = array[1]; } - if (mode != null) { + if (mode != null && mode is PdfName) { if (mode.name == PdfDictionaryProperties.xyz) { - PdfNumber? left; - PdfNumber? top; + IPdfPrimitive? left; + IPdfPrimitive? top; if (array.count > 2) { - left = array[2]! as PdfNumber; + left = array[2]; } if (array.count > 3) { - top = array[3]! as PdfNumber; + top = array[3]; } - PdfNumber? zoom; + IPdfPrimitive? zoom; if (array.count > 4) { - zoom = array[4]! as PdfNumber; + zoom = array[4]; } - if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value!; + (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; final double leftValue = - (left == null) ? 0 : left.value! as double; - _destination = - PdfDestination(page, Offset(leftValue, topValue)); - if (zoom != null) { - _destination!.zoom = zoom.value!.toDouble(); - } + (left != null && left is PdfNumber) + ? left.value! as double + : 0; + _destination = PdfDestination( + page, + Offset(leftValue, topValue), + ); + _destination!.zoom = + (zoom != null && zoom is PdfNumber) + ? zoom.value!.toDouble() + : 0; } } else { if (mode.name == PdfDictionaryProperties.fitR) { - PdfNumber? left; - PdfNumber? bottom; - PdfNumber? right; - PdfNumber? top; + IPdfPrimitive? left; + IPdfPrimitive? bottom; + IPdfPrimitive? right; + IPdfPrimitive? top; if (array.count > 2) { - left = array[2]! as PdfNumber; + left = array[2]; } if (array.count > 3) { - bottom = array[3]! as PdfNumber; + bottom = array[3]; } if (array.count > 4) { - right = array[4]! as PdfNumber; + right = array[4]; } if (array.count > 5) { - top = array[5]! as PdfNumber; + top = array[5]; } - if (page != null) { - left = (left == null) ? PdfNumber(0) : left; - bottom = (bottom == null) ? PdfNumber(0) : bottom; - right = (right == null) ? PdfNumber(0) : right; - top = (top == null) ? PdfNumber(0) : top; - _destination = PdfDestinationHelper.getDestination( - page, - PdfRectangle( - left.value!.toDouble(), - bottom.value!.toDouble(), - right.value!.toDouble(), - top.value!.toDouble())); + page, + PdfRectangle( + (left != null && left is PdfNumber) + ? left.value!.toDouble() + : 0, + (bottom != null && bottom is PdfNumber) + ? bottom.value!.toDouble() + : 0, + (right != null && right is PdfNumber) + ? right.value!.toDouble() + : 0, + (top != null && top is PdfNumber) + ? top.value!.toDouble() + : 0, + ), + ); _destination!.mode = PdfDestinationMode.fitR; } } else if (mode.name == PdfDictionaryProperties.fitBH || mode.name == PdfDictionaryProperties.fitH) { - PdfNumber? top; + IPdfPrimitive? top; if (array.count >= 3) { - top = array[2]! as PdfNumber; + top = array[2]; } if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value!; + (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; _destination = PdfDestination(page, Offset(0, topValue)); _destination!.mode = PdfDestinationMode.fitH; } @@ -670,8 +993,9 @@ class PdfBookmark extends PdfBookmarkBase { } } else if (_helper.dictionary!.containsKey(PdfDictionaryProperties.a) && (_destination == null)) { - IPdfPrimitive? obj = _helper._crossTable - .getObject(_helper.dictionary![PdfDictionaryProperties.a]); + IPdfPrimitive? obj = _helper._crossTable.getObject( + _helper.dictionary![PdfDictionaryProperties.a], + ); PdfDictionary? destDic; if (obj is PdfDictionary) { destDic = obj; @@ -691,56 +1015,60 @@ class PdfBookmark extends PdfBookmarkBase { } } if (array != null) { - final PdfReferenceHolder? holder = array[0] as PdfReferenceHolder?; + final IPdfPrimitive? holder = array[0]; PdfPage? page; - if (holder != null) { - final PdfDictionary? dic = - _helper._crossTable.getObject(holder) as PdfDictionary?; - if (dic != null && ldDoc != null) { + if (holder != null && holder is PdfReferenceHolder) { + final IPdfPrimitive? dic = _helper._crossTable.getObject(holder); + if (dic != null && ldDoc != null && dic is PdfDictionary) { page = PdfPageCollectionHelper.getHelper(ldDoc.pages).getPage(dic); } } - - PdfName? mode; + IPdfPrimitive? mode; if (array.count > 1) { - mode = array[1]! as PdfName; + mode = array[1]; } - if (mode != null) { + if (mode != null && mode is PdfName) { if (mode.name == PdfDictionaryProperties.fitBH || mode.name == PdfDictionaryProperties.fitH) { - PdfNumber? top; + IPdfPrimitive? top; if (array.count >= 3) { - top = array[2]! as PdfNumber; + top = array[2]; } if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value!; + (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; _destination = PdfDestination(page, Offset(0, topValue)); _destination!.mode = PdfDestinationMode.fitH; } } else if (mode.name == PdfDictionaryProperties.xyz) { - PdfNumber? left; - PdfNumber? top; + IPdfPrimitive? left; + IPdfPrimitive? top; if (array.count > 2) { - left = array[2]! as PdfNumber; + left = array[2]; } if (array.count > 3) { - top = array[3]! as PdfNumber; + top = array[3]; } - PdfNumber? zoom; - if (array.count > 4 && (array[4] is PdfNumber)) { - zoom = array[4]! as PdfNumber; + IPdfPrimitive? zoom; + if (array.count > 4) { + zoom = array[4]; } - if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value!; + (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; final double leftValue = - (left == null) ? 0 : left.value! as double; + (left != null && left is PdfNumber) + ? left.value! as double + : 0; _destination = PdfDestination(page, Offset(leftValue, topValue)); - if (zoom != null) { - _destination!.zoom = zoom.value!.toDouble(); - } + _destination!.zoom = + (zoom != null && zoom is PdfNumber) + ? zoom.value!.toDouble() + : 0; } } else { if (page != null && mode.name == PdfDictionaryProperties.fit) { @@ -773,7 +1101,7 @@ class PdfBookmark extends PdfBookmarkBase { /// ..textStyle = [PdfTextStyle.bold] /// ..title = 'Changed title'; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -816,7 +1144,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// //get the bookmark count. /// int count = document.bookmarks.count; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -847,7 +1175,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// ..textStyle = [PdfTextStyle.bold] /// ..title = 'Changed title'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -890,20 +1218,26 @@ class PdfBookmarkBase implements IPdfWrapper { /// ..textStyle = [PdfTextStyle.bold] /// ..color = PdfColor(255, 0, 0); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfBookmark add(String title, - {bool isExpanded = false, - PdfColor? color, - PdfDestination? destination, - PdfNamedDestination? namedDestination, - PdfAction? action, - List? textStyle}) { + 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); + final PdfBookmark outline = PdfBookmark._internal( + title, + this, + previous, + null, + ); if (previous != null) { previous._next = outline; } @@ -922,7 +1256,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// //check whether the specified bookmark present in the collection /// bool contains = document.bookmarks.contains(bookmark); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -940,7 +1274,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// //Remove specified bookmark. /// bookmarks.remove('bookmark'); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -972,7 +1306,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// //Remove specified bookmark using index. /// document.bookmarks.removeAt(0); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -990,10 +1324,13 @@ class PdfBookmarkBase implements IPdfWrapper { } final PdfBookmark current = _bookmark![index]; if (index == 0) { - if (current._helper.dictionary! - .containsKey(PdfDictionaryProperties.next)) { - _helper.dictionary!.setProperty(PdfDictionaryProperties.first, - current._helper.dictionary![PdfDictionaryProperties.next]); + if (current._helper.dictionary!.containsKey( + PdfDictionaryProperties.next, + )) { + _helper.dictionary!.setProperty( + PdfDictionaryProperties.first, + current._helper.dictionary![PdfDictionaryProperties.next], + ); } else { _helper.dictionary!.remove(PdfDictionaryProperties.first); _helper.dictionary!.remove(PdfDictionaryProperties.last); @@ -1002,19 +1339,23 @@ class PdfBookmarkBase implements IPdfWrapper { (current._previous != null) && (current._next != null)) { current._previous!._helper.dictionary!.setProperty( - PdfDictionaryProperties.next, - current._helper.dictionary![PdfDictionaryProperties.next]); + PdfDictionaryProperties.next, + current._helper.dictionary![PdfDictionaryProperties.next], + ); current._next!._helper.dictionary!.setProperty( - PdfDictionaryProperties.prev, - current._helper.dictionary![PdfDictionaryProperties.prev]); + PdfDictionaryProperties.prev, + current._helper.dictionary![PdfDictionaryProperties.prev], + ); } else if ((current._parent != null) && (current._previous != null) && (current._next == null)) { - current._previous!._helper.dictionary! - .remove(PdfDictionaryProperties.next); + current._previous!._helper.dictionary!.remove( + PdfDictionaryProperties.next, + ); current._parent!._helper.dictionary!.setProperty( - PdfDictionaryProperties.last, - current._helper.dictionary![PdfDictionaryProperties.prev]); + PdfDictionaryProperties.last, + current._helper.dictionary![PdfDictionaryProperties.prev], + ); } if (current._parent != null) { current._parent!._list.remove(current); @@ -1031,7 +1372,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// //Clear all the bookmarks. /// document.bookmarks.clear(); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1055,7 +1396,7 @@ class PdfBookmarkBase implements IPdfWrapper { /// document.bookmarks.insert(0, 'bookmark') /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1087,9 +1428,13 @@ class PdfBookmarkBase implements IPdfWrapper { final int newCount = _isExpanded ? _list.length : -_list.length; _helper.dictionary![PdfDictionaryProperties.count] = PdfNumber(newCount); _helper.dictionary!.setProperty( - PdfDictionaryProperties.first, PdfReferenceHolder(this[0])); + PdfDictionaryProperties.first, + PdfReferenceHolder(this[0]), + ); _helper.dictionary!.setProperty( - PdfDictionaryProperties.last, PdfReferenceHolder(this[count - 1])); + PdfDictionaryProperties.last, + PdfReferenceHolder(this[count - 1]), + ); } else { _helper.dictionary!.clear(); } @@ -1105,21 +1450,30 @@ class PdfBookmarkBase implements IPdfWrapper { /// [PdfBookmark] helper class PdfBookmarkHelper { /// internal method - static PdfBookmark internal(String title, PdfBookmarkBase parent, - PdfBookmark? previous, PdfBookmark? next, - {bool isExpanded = false, - PdfColor? color, - PdfDestination? destination, - PdfNamedDestination? namedDestination, - PdfAction? action, - List? textStyle}) { - return PdfBookmark._internal(title, parent, previous, next, - isExpanded: isExpanded, - color: color, - destination: destination, - namedDestination: namedDestination, - action: action, - textStyle: textStyle); + static PdfBookmark internal( + String title, + PdfBookmarkBase parent, + PdfBookmark? previous, + PdfBookmark? next, { + bool isExpanded = false, + PdfColor? color, + PdfDestination? destination, + PdfNamedDestination? namedDestination, + PdfAction? action, + List? textStyle, + }) { + return PdfBookmark._internal( + title, + parent, + previous, + next, + isExpanded: isExpanded, + color: color, + destination: destination, + namedDestination: namedDestination, + action: action, + textStyle: textStyle, + ); } /// internal method @@ -1164,7 +1518,9 @@ class PdfBookmarkBaseHelper { /// internal method static PdfBookmarkBase loaded( - PdfDictionary? dictionary, PdfCrossTable crossTable) { + PdfDictionary? dictionary, + PdfCrossTable crossTable, + ) { return PdfBookmarkBase._load(dictionary, crossTable); } 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 e13acf475..a92930328 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 @@ -25,10 +25,11 @@ class PdfCatalog extends PdfDictionary { /// internal constructor PdfCatalog.fromDocument(PdfDocument this.document, PdfDictionary? catalog) - : super(catalog) { + : super(catalog) { if (containsKey(PdfDictionaryProperties.names)) { - final IPdfPrimitive? obj = - PdfCrossTable.dereference(this[PdfDictionaryProperties.names]); + final IPdfPrimitive? obj = PdfCrossTable.dereference( + this[PdfDictionaryProperties.names], + ); if (obj is PdfDictionary) { _catalogNames = PdfCatalogNames(obj); } @@ -60,8 +61,9 @@ class PdfCatalog extends PdfDictionary { PdfDictionary? get destinations { PdfDictionary? dests; if (containsKey(PdfDictionaryProperties.dests)) { - dests = PdfCrossTable.dereference(this[PdfDictionaryProperties.dests]) - as PdfDictionary?; + dests = + PdfCrossTable.dereference(this[PdfDictionaryProperties.dests]) + as PdfDictionary?; } return dests; } 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 e82cad27b..009da509e 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 @@ -34,18 +34,22 @@ class PdfCatalogNames implements IPdfWrapper { //Gets the destinations. /// internal property PdfDictionary? get destinations { - final IPdfPrimitive? obj = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.dests]); + final IPdfPrimitive? obj = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.dests], + ); final PdfDictionary? dests = obj as PdfDictionary?; return dests; } /// internal property + // ignore: avoid_setters_without_getters set embeddedFiles(PdfAttachmentCollection value) { if (_attachments != value) { _attachments = value; - dictionary!.setProperty(PdfDictionaryProperties.embeddedFiles, - PdfReferenceHolder(_attachments)); + dictionary!.setProperty( + PdfDictionaryProperties.embeddedFiles, + PdfReferenceHolder(_attachments), + ); } } 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 a813c64d1..d620f3d3a 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 @@ -1,11 +1,24 @@ import 'dart:collection'; import 'dart:convert'; +import 'dart:typed_data'; -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/forms/pdf_form_field_collection.dart'; +import 'package:xml/xml.dart'; import '../../interfaces/pdf_interface.dart'; +import '../annotations/enum.dart'; +import '../annotations/fdf_document.dart'; +import '../annotations/fdf_parser.dart'; +import '../annotations/json_document.dart'; +import '../annotations/json_parser.dart'; +import '../annotations/pdf_action_annotation.dart'; +import '../annotations/pdf_annotation.dart'; +import '../annotations/pdf_annotation_collection.dart'; +import '../annotations/pdf_text_web_link.dart'; +import '../annotations/xfdf_parser.dart'; import '../color_space/pdf_icc_color_profile.dart'; import '../forms/pdf_form.dart'; +import '../forms/pdf_form_field_collection.dart'; +import '../forms/pdf_xfdf_document.dart'; import '../general/file_specification_base.dart'; import '../general/pdf_destination.dart'; import '../general/pdf_named_destination.dart'; @@ -65,14 +78,15 @@ class PdfDocument { /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), /// bounds: Rect.fromLTWH(0, 0, 150, 20)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfDocument( - {List? inputBytes, - PdfConformanceLevel? conformanceLevel, - String? password}) { + PdfDocument({ + List? inputBytes, + PdfConformanceLevel? conformanceLevel, + String? password, + }) { _helper = PdfDocumentHelper(this); _helper.isLoadedDocument = inputBytes != null; _helper.password = password; @@ -98,7 +112,7 @@ class PdfDocument { /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), /// bounds: Rect.fromLTWH(0, 0, 150, 20)); /// //Save the updated PDF document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -172,7 +186,7 @@ class PdfDocument { /// security.userPassword = 'password'; /// security.ownerPassword = 'syncfusion'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -196,7 +210,7 @@ class PdfDocument { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfSectionCollection? get sections => _sections; @@ -213,7 +227,7 @@ class PdfDocument { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfPageSettings get pageSettings { @@ -239,7 +253,7 @@ class PdfDocument { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` set pageSettings(PdfPageSettings settings) { @@ -307,7 +321,7 @@ class PdfDocument { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfPageCollection get pages => _getPageCollection(); @@ -323,7 +337,7 @@ class PdfDocument { /// ..textStyle = [PdfTextStyle.bold] /// ..color = PdfColor(255, 0, 0); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -332,10 +346,13 @@ class PdfDocument { if (_bookmark == null) { if (_helper.catalog.containsKey(PdfDictionaryProperties.outlines)) { final IPdfPrimitive? outlines = PdfCrossTable.dereference( - _helper.catalog[PdfDictionaryProperties.outlines]); + _helper.catalog[PdfDictionaryProperties.outlines], + ); if (outlines != null && outlines is PdfDictionary) { - _bookmark = - PdfBookmarkBaseHelper.loaded(outlines, _helper.crossTable); + _bookmark = PdfBookmarkBaseHelper.loaded( + outlines, + _helper.crossTable, + ); PdfBookmarkBaseHelper.getHelper(_bookmark!).reproduceTree(); } else { _bookmark = _createBookmarkRoot(); @@ -348,8 +365,9 @@ class PdfDocument { } else { if (_outlines == null) { _outlines = PdfBookmarkBaseHelper.loadInternal(); - _helper.catalog[PdfDictionaryProperties.outlines] = - PdfReferenceHolder(_outlines); + _helper.catalog[PdfDictionaryProperties.outlines] = PdfReferenceHolder( + _outlines, + ); } return _outlines!; } @@ -361,10 +379,15 @@ class PdfDocument { if (_namedDestinations == null) { if (_helper.catalog.containsKey(PdfDictionaryProperties.names) && (_namedDestinations == null)) { - final PdfDictionary? namedDestinations = PdfCrossTable.dereference( - _helper.catalog[PdfDictionaryProperties.names]) as PdfDictionary?; + final PdfDictionary? namedDestinations = + PdfCrossTable.dereference( + _helper.catalog[PdfDictionaryProperties.names], + ) + as PdfDictionary?; _namedDestinations = PdfNamedDestinationCollectionHelper.load( - namedDestinations, _helper.crossTable); + namedDestinations, + _helper.crossTable, + ); } _namedDestinations ??= _createNamedDestinations(); } @@ -372,15 +395,18 @@ class PdfDocument { } else { if (_namedDestinations == null) { _namedDestinations = PdfNamedDestinationCollection(); - _helper.catalog[PdfDictionaryProperties.names] = - PdfReferenceHolder(_namedDestinations); - final PdfReferenceHolder? names = _helper - .catalog[PdfDictionaryProperties.names] as PdfReferenceHolder?; + _helper.catalog[PdfDictionaryProperties.names] = PdfReferenceHolder( + _namedDestinations, + ); + final PdfReferenceHolder? names = + _helper.catalog[PdfDictionaryProperties.names] + as PdfReferenceHolder?; if (names != null) { final PdfDictionary? dic = names.object as PdfDictionary?; if (dic != null) { - dic[PdfDictionaryProperties.dests] = - PdfReferenceHolder(_namedDestinations); + dic[PdfDictionaryProperties.dests] = PdfReferenceHolder( + _namedDestinations, + ); } } } @@ -398,23 +424,29 @@ class PdfDocument { final PdfDictionary? entry = PdfCrossTable.dereference(trailer[PdfDictionaryProperties.info]) as PdfDictionary?; - _documentInfo = PdfDocumentInformationHelper.load(_helper.catalog, - dictionary: entry, - isLoaded: true, - conformance: _helper.conformanceLevel); + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + dictionary: entry, + isLoaded: true, + conformance: _helper.conformanceLevel, + ); } else { - _documentInfo = PdfDocumentInformationHelper.load(_helper.catalog, - conformance: _helper.conformanceLevel); - _helper.crossTable.trailer![PdfDictionaryProperties.info] = - PdfReferenceHolder(_documentInfo); + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + conformance: _helper.conformanceLevel, + ); + _helper.crossTable.trailer![PdfDictionaryProperties + .info] = PdfReferenceHolder(_documentInfo); } // Read document's info dictionary if present. _readDocumentInfo(); } else { - _documentInfo = PdfDocumentInformationHelper.load(_helper.catalog, - conformance: _helper.conformanceLevel); - _helper.crossTable.trailer![PdfDictionaryProperties.info] = - PdfReferenceHolder(_documentInfo); + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + conformance: _helper.conformanceLevel, + ); + _helper.crossTable.trailer![PdfDictionaryProperties + .info] = PdfReferenceHolder(_documentInfo); } } return _documentInfo!; @@ -447,13 +479,17 @@ class PdfDocument { _helper.checkEncryption(_helper._isAttachOnlyEncryption); } final IPdfPrimitive? attachmentDictionary = PdfCrossTable.dereference( - _helper.catalog[PdfDictionaryProperties.names]); + _helper.catalog[PdfDictionaryProperties.names], + ); if (attachmentDictionary != null && attachmentDictionary is PdfDictionary && - attachmentDictionary - .containsKey(PdfDictionaryProperties.embeddedFiles)) { + attachmentDictionary.containsKey( + PdfDictionaryProperties.embeddedFiles, + )) { _attachments = PdfAttachmentCollectionHelper.load( - attachmentDictionary, _helper.crossTable); + attachmentDictionary, + _helper.crossTable, + ); } else { _attachments = PdfAttachmentCollection(); _helper.catalog.names!.embeddedFiles = _attachments!; @@ -469,7 +505,8 @@ class PdfDocument { if (_form == null) { if (_helper.catalog.containsKey(PdfDictionaryProperties.acroForm)) { final IPdfPrimitive? formDictionary = PdfCrossTable.dereference( - _helper.catalog[PdfDictionaryProperties.acroForm]); + _helper.catalog[PdfDictionaryProperties.acroForm], + ); if (formDictionary is PdfDictionary) { _form = PdfFormHelper.internal(_helper.crossTable, formDictionary); if (_form != null && _form!.fields.count != 0) { @@ -477,52 +514,76 @@ class PdfDocument { List? widgetReference; if (PdfFormHelper.getHelper(_form!).crossTable!.document != null) { - for (int i = 0; - i < - PdfFormHelper.getHelper(_form!) - .crossTable! - .document! - .pages - .count; - i++) { - final PdfPage page = PdfFormHelper.getHelper(_form!) - .crossTable! - .document! - .pages[i]; + for ( + int i = 0; + i < + PdfFormHelper.getHelper( + _form!, + ).crossTable!.document!.pages.count; + i++ + ) { + final PdfPage page = + PdfFormHelper.getHelper( + _form!, + ).crossTable!.document!.pages[i]; widgetReference ??= PdfPageHelper.getHelper(page).getWidgetReferences(); if (widgetReference.isNotEmpty) { - PdfPageHelper.getHelper(page) - .createAnnotations(widgetReference); + PdfPageHelper.getHelper( + page, + ).createAnnotations(widgetReference); } } if (widgetReference != null) { widgetReference.clear(); } if (!PdfFormHelper.getHelper(_form!).formHasKids) { - PdfFormFieldCollectionHelper.getHelper(_form!.fields) - .createFormFieldsFromWidgets(_form!.fields.count); + PdfFormFieldCollectionHelper.getHelper( + _form!.fields, + ).createFormFieldsFromWidgets(_form!.fields.count); } } } + return _form!; } - } else { - _form = PdfFormHelper.internal(_helper.crossTable); - _helper.catalog.setProperty( - PdfDictionaryProperties.acroForm, PdfReferenceHolder(_form)); - _helper.catalog.form = _form; - return _form!; } + _form = PdfFormHelper.internal(_helper.crossTable); + _helper.catalog.setProperty( + PdfDictionaryProperties.acroForm, + PdfReferenceHolder(_form), + ); + _helper.catalog.form = _form; + return _form!; } else { return _form!; } } else { return _helper.catalog.form ??= PdfForm(); } - return _form!; } //Public methods + /// Saves the document and return the saved bytes as Uint8List. + /// ```dart + /// //Create a PDF document instance. + /// PdfDocument document = PdfDocument(); + /// //Get the page and draw text. + /// document.pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); + /// //Save and dispose document. + /// Uint8List bytes = document.saveAsBytesSync(); + /// document.dispose(); + /// ``` + Uint8List saveAsBytesSync() { + final PdfBytesBuilder buffer = PdfBytesBuilder(); + final PdfWriter writer = PdfWriter(null, buffer); + _saveDocument(writer); + final Uint8List bytes = buffer.takeBytes(); + buffer.clear(); + return bytes; + } + /// Saves the document and return the saved bytes as list of int. /// /// ```dart @@ -533,28 +594,51 @@ class PdfDocument { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = document.saveSync(); /// document.dispose(); /// ``` - List save() { + List saveSync() { final List buffer = []; final PdfWriter writer = PdfWriter(buffer); + _saveDocument(writer); + return writer.buffer!; + } + + /// Internal method to save the PDF document. + void _saveDocument(PdfWriter writer) { writer.document = this; _checkPages(); + if (_helper.isLoadedDocument && + _bookmark != null && + _bookmark!.count > 0 && + _helper.crossTable.documentCatalog != null && + !_helper.crossTable.documentCatalog!.containsKey( + PdfDictionaryProperties.outlines, + )) { + _helper.catalog.setProperty( + PdfDictionaryProperties.outlines, + PdfReferenceHolder(_bookmark), + ); + } else if (_bookmark != null && + _bookmark!.count < 1 && + _helper.catalog.containsKey(PdfDictionaryProperties.outlines)) { + _helper.catalog.remove(PdfDictionaryProperties.outlines); + } if (PdfSecurityHelper.getHelper(security).encryptAttachments && security.userPassword == '') { throw ArgumentError.value( - 'User password cannot be empty for encrypt only attachment.'); + 'User password cannot be empty for encrypt only attachment.', + ); } if (_helper.isLoadedDocument) { if (_helper._security != null) { - if (PdfSecurityHelper.getHelper(_helper._security!) - .encryptAttachments) { + if (PdfSecurityHelper.getHelper( + _helper._security!, + ).encryptAttachments) { fileStructure.incrementalUpdate = false; - if (PdfSecurityHelper.getHelper(_helper._security!) - .encryptor - .userPassword - .isEmpty) { + if (PdfSecurityHelper.getHelper( + _helper._security!, + ).encryptor.userPassword.isEmpty) { PdfSecurityHelper.getHelper(_helper._security!) .encryptor .encryptOnlyAttachment = false; @@ -564,22 +648,25 @@ class PdfDocument { if (fileStructure.incrementalUpdate && (_helper._security == null || (_helper._security != null && - !PdfSecurityHelper.getHelper(_helper._security!) - .modifiedSecurity && + !PdfSecurityHelper.getHelper( + _helper._security!, + ).modifiedSecurity && !PdfPermissionsHelper.isModifiedPermissions( - _helper._security!.permissions)))) { + _helper._security!.permissions, + )))) { _copyOldStream(writer); if (_helper.catalog.changed!) { _readDocumentInfo(); } } else { _helper.crossTable = PdfCrossTable.fromCatalog( - _helper.crossTable.count, - _helper.crossTable.encryptorDictionary, - _helper.crossTable.pdfDocumentCatalog); + _helper.crossTable.count, + _helper.crossTable.encryptorDictionary, + _helper.crossTable.pdfDocumentCatalog, + ); _helper.crossTable.document = this; - _helper.crossTable.trailer![PdfDictionaryProperties.info] = - PdfReferenceHolder(documentInformation); + _helper.crossTable.trailer![PdfDictionaryProperties + .info] = PdfReferenceHolder(documentInformation); } _appendDocument(writer); } else { @@ -590,21 +677,25 @@ class PdfDocument { if (_helper.conformanceLevel == PdfConformanceLevel.a3b && _helper.catalog.names != null && attachments.count > 0) { - final PdfName fileRelationShip = - PdfName(PdfDictionaryProperties.afRelationship); + final PdfName fileRelationShip = PdfName( + PdfDictionaryProperties.afRelationship, + ); final PdfArray fileAttachmentAssociationArray = PdfArray(); for (int i = 0; i < attachments.count; i++) { - if (!PdfFileSpecificationBaseHelper.getHelper(attachments[i]) - .dictionary! - .items! - .containsKey(fileRelationShip)) { - PdfFileSpecificationBaseHelper.getHelper(attachments[i]) - .dictionary! - .items![fileRelationShip] = PdfName('Alternative'); + if (!PdfFileSpecificationBaseHelper.getHelper( + attachments[i], + ).dictionary!.items!.containsKey(fileRelationShip)) { + PdfFileSpecificationBaseHelper.getHelper( + attachments[i], + ).dictionary!.items![fileRelationShip] = PdfName('Alternative'); } - fileAttachmentAssociationArray.add(PdfReferenceHolder( - PdfFileSpecificationBaseHelper.getHelper(attachments[i]) - .dictionary)); + fileAttachmentAssociationArray.add( + PdfReferenceHolder( + PdfFileSpecificationBaseHelper.getHelper( + attachments[i], + ).dictionary, + ), + ); } _helper.catalog.items![PdfName(PdfDictionaryProperties.af)] = fileAttachmentAssociationArray; @@ -614,9 +705,152 @@ class PdfDocument { final DocumentSavedArgs argsSaved = DocumentSavedArgs(writer); _onDocumentSaved(argsSaved); } + } + + /// Saves the document and return the saved bytes as feature Uint8List. + /// ```dart + /// //Create a PDF document instance. + /// PdfDocument document = PdfDocument(); + /// //Get the page and draw text. + /// document.pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); + /// //Save and dispose document. + /// Uint8List bytes = await document.saveAsBytes(); + /// document.dispose(); + /// ``` + Future saveAsBytes() async { + final PdfBytesBuilder buffer = PdfBytesBuilder(); + final PdfWriter writer = PdfWriter(null, buffer); + await _saveDocumentAsync(writer); + final Uint8List bytes = buffer.takeBytes(); + buffer.clear(); + return bytes; + } + + /// Saves the document and return the saved bytes as future list of int. + /// ```dart + /// //Create a PDF document instance. + /// PdfDocument document = PdfDocument(); + /// //Get the page and draw text. + /// document.pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); + /// //Save and dispose document. + /// List bytes = await document.save(); + /// document.dispose(); + /// ``` + Future> save() async { + final List buffer = []; + final PdfWriter writer = PdfWriter(buffer); + await _saveDocumentAsync(writer); return writer.buffer!; } + /// Internal method to save the PDF document + Future _saveDocumentAsync(PdfWriter writer) async { + writer.document = this; + await _checkPagesAsync(); + if (_helper.isLoadedDocument && + _bookmark != null && + _bookmark!.count > 0 && + _helper.crossTable.documentCatalog != null && + !_helper.crossTable.documentCatalog!.containsKey( + PdfDictionaryProperties.outlines, + )) { + _helper.catalog.setProperty( + PdfDictionaryProperties.outlines, + PdfReferenceHolder(_bookmark), + ); + } else if (_outlines != null && + _outlines!.count < 1 && + _helper.catalog.containsKey(PdfDictionaryProperties.outlines)) { + _helper.catalog.remove(PdfDictionaryProperties.outlines); + } + if (PdfSecurityHelper.getHelper(security).encryptAttachments && + security.userPassword == '') { + throw ArgumentError.value( + 'User password cannot be empty for encrypt only attachment.', + ); + } + if (_helper.isLoadedDocument) { + if (_helper._security != null) { + if (PdfSecurityHelper.getHelper( + _helper._security!, + ).encryptAttachments) { + fileStructure.incrementalUpdate = false; + if (PdfSecurityHelper.getHelper( + _helper._security!, + ).encryptor.userPassword.isEmpty) { + PdfSecurityHelper.getHelper(_helper._security!) + .encryptor + .encryptOnlyAttachment = false; + } + } + } + if (fileStructure.incrementalUpdate && + (_helper._security == null || + (_helper._security != null && + !PdfSecurityHelper.getHelper( + _helper._security!, + ).modifiedSecurity && + !PdfPermissionsHelper.isModifiedPermissions( + _helper._security!.permissions, + )))) { + await _copyOldStreamAsync(writer).then((_) { + if (_helper.catalog.changed!) { + _readDocumentInfoAsync(); + } + }); + } else { + _helper.crossTable = PdfCrossTable.fromCatalog( + _helper.crossTable.count, + _helper.crossTable.encryptorDictionary, + _helper.crossTable.pdfDocumentCatalog, + ); + _helper.crossTable.document = this; + _helper.crossTable.trailer![PdfDictionaryProperties + .info] = PdfReferenceHolder(documentInformation); + } + await _appendDocumentAsync(writer); + } else { + if (_helper.conformanceLevel == PdfConformanceLevel.a1b || + _helper.conformanceLevel == PdfConformanceLevel.a2b || + _helper.conformanceLevel == PdfConformanceLevel.a3b) { + PdfDocumentInformationHelper.getHelper(documentInformation).xmpMetadata; + if (_helper.conformanceLevel == PdfConformanceLevel.a3b && + _helper.catalog.names != null && + attachments.count > 0) { + final PdfName fileRelationShip = PdfName( + PdfDictionaryProperties.afRelationship, + ); + final PdfArray fileAttachmentAssociationArray = PdfArray(); + for (int i = 0; i < attachments.count; i++) { + if (!PdfFileSpecificationBaseHelper.getHelper( + attachments[i], + ).dictionary!.items!.containsKey(fileRelationShip)) { + PdfFileSpecificationBaseHelper.getHelper( + attachments[i], + ).dictionary!.items![fileRelationShip] = PdfName('Alternative'); + } + fileAttachmentAssociationArray.add( + PdfReferenceHolder( + PdfFileSpecificationBaseHelper.getHelper( + attachments[i], + ).dictionary, + ), + ); + } + _helper.catalog.items![PdfName(PdfDictionaryProperties.af)] = + fileAttachmentAssociationArray; + } + } + await _helper.crossTable.saveAsync(writer); + final DocumentSavedArgs argsSaved = DocumentSavedArgs(writer); + await _onDocumentSavedAsync(argsSaved); + } + } + void _checkPages() { if (!_helper.isLoadedDocument) { if (pages.count == 0) { @@ -625,6 +859,14 @@ class PdfDocument { } } + Future _checkPagesAsync() async { + if (!_helper.isLoadedDocument) { + if (pages.count == 0) { + pages.add(); + } + } + } + /// Releases all the resources used by document instances. /// /// ```dart @@ -635,7 +877,7 @@ class PdfDocument { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` void dispose() { @@ -646,6 +888,81 @@ class PdfDocument { _helper.currentSavingObject = null; } + /// Export the annotation data to UTF8 bytes with the specific [PdfAnnotationDataFormat]. + /// + /// To export specific annotations, annotation types, appearances, and + /// add a file name to the export format, we can use the named parameters + /// exportList, exportTypes, exportAppearance, and fileName respectively. + /// + /// ```dart + /// //Load an existing PDF document. + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + /// //Export annotations in specific PdfAnnotationDataFormat format. + /// List bytes = document.exportAnnotation(PdfAnnotationDataFormat.fdf, + /// fileName: 'PDFExportDocument'); + /// //Save the exported data. + /// File('export.fdf').writeAsBytesSync(bytes); + /// //Dispose the document. + /// document.dispose(); + /// ``` + List exportAnnotation( + PdfAnnotationDataFormat format, { + String? fileName, + List? exportList, + List? exportTypes, + bool exportAppearance = false, + }) { + List bytes = []; + if (format == PdfAnnotationDataFormat.xfdf) { + bytes = _helper.exportXfdf( + fileName, + exportList, + exportTypes, + exportAppearance, + ); + } else if (format == PdfAnnotationDataFormat.fdf) { + bytes = _helper.exportFdf( + fileName, + exportList, + exportTypes, + exportAppearance, + ); + } else if (format == PdfAnnotationDataFormat.json) { + bytes = _helper.exportJson( + fileName, + exportList, + exportTypes, + exportAppearance, + ); + } + return bytes; + } + + /// Import the annotation data from UTF8 bytes with the specific [PdfAnnotationDataFormat]. + /// + /// ```dart + /// //Load an existing PDF document. + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + /// //Import annotations in specific PdfAnnotationDataFormat format. + /// document.importAnnotation( + /// File('input.fdf').readAsBytesSync(), PdfAnnotationDataFormat.fdf); + /// //Save the document. + /// File('output.pdf').writeAsBytesSync(await document.save()); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void importAnnotation(List data, PdfAnnotationDataFormat format) { + if (format == PdfAnnotationDataFormat.xfdf) { + _helper.importXfdf(data); + } else if (format == PdfAnnotationDataFormat.fdf) { + _helper.importFdf(data); + } else if (format == PdfAnnotationDataFormat.json) { + _helper.importJson(data); + } + } + //Implementation void _initialize(List? pdfData) { _helper._isAttachOnlyEncryption = false; @@ -658,20 +975,25 @@ class PdfDocument { final PdfCatalog catalog = _getCatalogValue(); if (catalog.containsKey(PdfDictionaryProperties.pages) && !catalog.containsKey(PdfDictionaryProperties.type)) { - catalog.addItems(PdfDictionaryProperties.type, - PdfName(PdfDictionaryProperties.catalog)); + catalog.addItems( + PdfDictionaryProperties.type, + PdfName(PdfDictionaryProperties.catalog), + ); } if (catalog.containsKey(PdfDictionaryProperties.type)) { - if (!(catalog[PdfDictionaryProperties.type]! as PdfName) - .name! - .contains(PdfDictionaryProperties.catalog)) { - catalog[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.catalog); + if (!(catalog[PdfDictionaryProperties.type]! as PdfName).name!.contains( + PdfDictionaryProperties.catalog, + )) { + catalog[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.catalog, + ); } _setCatalog(catalog); } else { throw ArgumentError.value( - catalog, 'Cannot find the PDF catalog information'); + catalog, + 'Cannot find the PDF catalog information', + ); } bool hasVersion = false; if (catalog.containsKey(PdfDictionaryProperties.version)) { @@ -712,6 +1034,11 @@ class PdfDocument { _helper.isStreamCopied = true; } + Future _copyOldStreamAsync(PdfWriter writer) async { + writer.writeAsync(_data); + _helper.isStreamCopied = true; + } + void _appendDocument(PdfWriter writer) { writer.document = this; _helper.crossTable.save(writer); @@ -720,12 +1047,21 @@ class PdfDocument { _onDocumentSaved(argsSaved); } + Future _appendDocumentAsync(PdfWriter writer) async { + writer.document = this; + await _helper.crossTable.saveAsync(writer).then((_) async { + PdfSecurityHelper.getHelper(security).encryptor.encrypt = true; + final DocumentSavedArgs argsSaved = DocumentSavedArgs(writer); + await _onDocumentSavedAsync(argsSaved); + }); + } + void _readFileVersion() { - final PdfReader _reader = PdfReader(_data); - _reader.position = 0; - String token = _reader.getNextToken()!; + final PdfReader reader = PdfReader(_data); + reader.position = 0; + String token = reader.getNextToken()!; if (token.startsWith('%')) { - token = _reader.getNextToken()!; + token = reader.getNextToken()!; _setFileVersion(token); } } @@ -767,8 +1103,10 @@ class PdfDocument { } PdfCatalog _getCatalogValue() { - final PdfCatalog catalog = - PdfCatalog.fromDocument(this, _helper.crossTable.documentCatalog); + final PdfCatalog catalog = PdfCatalog.fromDocument( + this, + _helper.crossTable.documentCatalog, + ); final int index = _helper.objects.lookFor(_helper.crossTable.documentCatalog!)!; _helper.objects.reregisterReference(index, catalog); @@ -779,8 +1117,9 @@ class PdfDocument { void _setCatalog(PdfCatalog catalog) { _helper.catalog = catalog; if (_helper.catalog.containsKey(PdfDictionaryProperties.outlines)) { - final PdfReferenceHolder? outlines = _helper - .catalog[PdfDictionaryProperties.outlines] as PdfReferenceHolder?; + final PdfReferenceHolder? outlines = + _helper.catalog[PdfDictionaryProperties.outlines] + as PdfReferenceHolder?; PdfDictionary? dic; if (outlines == null) { dic = @@ -802,17 +1141,16 @@ class PdfDocument { } PdfPageCollection _getPageCollection() { - _pages ??= _helper.isLoadedDocument - ? PdfPageCollectionHelper.fromCrossTable(this, _helper.crossTable) - : PdfPageCollectionHelper.load(this); + _pages ??= + _helper.isLoadedDocument + ? PdfPageCollectionHelper.fromCrossTable(this, _helper.crossTable) + : PdfPageCollectionHelper.load(this); return _pages!; } /// Creates a bookmarks collection to the document. PdfBookmarkBase? _createBookmarkRoot() { _bookmark = PdfBookmarkBaseHelper.loadInternal(); - _helper.catalog.setProperty( - PdfDictionaryProperties.outlines, PdfReferenceHolder(_bookmark)); return _bookmark; } @@ -825,12 +1163,16 @@ class PdfDocument { final PdfDictionary? catalogNames = catalogReference.object as PdfDictionary?; if (catalogNames != null) { - catalogNames.setProperty(PdfDictionaryProperties.dests, - PdfReferenceHolder(_namedDestinations)); + catalogNames.setProperty( + PdfDictionaryProperties.dests, + PdfReferenceHolder(_namedDestinations), + ); } } else { - _helper.catalog.setProperty(PdfDictionaryProperties.names, - PdfReferenceHolder(_namedDestinations)); + _helper.catalog.setProperty( + PdfDictionaryProperties.names, + PdfReferenceHolder(_namedDestinations), + ); } _helper.catalog.modify(); return _namedDestinations; @@ -838,30 +1180,79 @@ class PdfDocument { void _readDocumentInfo() { // Read document's info if present. - final PdfDictionary? info = PdfCrossTable.dereference( - _helper.crossTable.trailer![PdfDictionaryProperties.info]) - as PdfDictionary?; + final PdfDictionary? info = + PdfCrossTable.dereference( + _helper.crossTable.trailer![PdfDictionaryProperties.info], + ) + as PdfDictionary?; if (info != null && _documentInfo == null) { - _documentInfo = PdfDocumentInformationHelper.load(_helper.catalog, - dictionary: info, - isLoaded: true, - conformance: _helper.conformanceLevel); + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + dictionary: info, + isLoaded: true, + conformance: _helper.conformanceLevel, + ); } if (info != null && !info.changed! && _helper.crossTable.trailer![PdfDictionaryProperties.info] is PdfReferenceHolder) { - _documentInfo = PdfDocumentInformationHelper.load(_helper.catalog, - dictionary: info, - isLoaded: true, - conformance: _helper.conformanceLevel); + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + dictionary: info, + isLoaded: true, + conformance: _helper.conformanceLevel, + ); if (_helper.catalog.changed!) { _documentInfo!.modificationDate = DateTime.now(); } if (_helper.objects.lookFor(IPdfWrapper.getElement(_documentInfo!)!)! > -1) { - _helper.objects.reregisterReference(_helper.objects.lookFor(info)!, - IPdfWrapper.getElement(_documentInfo!)!); + _helper.objects.reregisterReference( + _helper.objects.lookFor(info)!, + IPdfWrapper.getElement(_documentInfo!)!, + ); + IPdfWrapper.getElement(_documentInfo!)!.position = -1; + } + } + } + + Future _readDocumentInfoAsync() async { + // Read document's info if present. + final PdfDictionary? info = + PdfCrossTable.dereference( + _helper.crossTable.trailer![PdfDictionaryProperties.info], + ) + as PdfDictionary?; + if (info != null && _documentInfo == null) { + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + dictionary: info, + isLoaded: true, + conformance: _helper.conformanceLevel, + ); + } + if (info != null && + !info.changed! && + _helper.crossTable.trailer![PdfDictionaryProperties.info] + is PdfReferenceHolder) { + _documentInfo = PdfDocumentInformationHelper.load( + _helper.catalog, + dictionary: info, + isLoaded: true, + conformance: _helper.conformanceLevel, + ); + if (_helper.catalog.changed!) { + _documentInfo!.modificationDate = DateTime.now(); + } + if ((await _helper.objects.lookForAsync( + IPdfWrapper.getElement(_documentInfo!)!, + ))! > + -1) { + await _helper.objects.reregisterReferenceAsync( + (await _helper.objects.lookForAsync(info))!, + IPdfWrapper.getElement(_documentInfo!)!, + ); IPdfWrapper.getElement(_documentInfo!)!.position = -1; } } @@ -906,6 +1297,15 @@ class PdfDocument { } } } + + Future _onDocumentSavedAsync(DocumentSavedArgs args) async { + if (_helper.documentSavedListAsync != null && + _helper.documentSavedListAsync!.isNotEmpty) { + for (int i = 0; i < _helper.documentSavedListAsync!.length; i++) { + await _helper.documentSavedListAsync![i](this, args); + } + } + } } /// Delegate for handling the PDF password @@ -932,8 +1332,8 @@ class PdfDocument { /// args.attachmentOpenPassword = 'syncfusion'; /// } /// ``` -typedef PdfPasswordCallback = void Function( - PdfDocument sender, PdfPasswordArgs args); +typedef PdfPasswordCallback = + void Function(PdfDocument sender, PdfPasswordArgs args); /// Arguments of Pdf Password. /// @@ -1019,6 +1419,9 @@ class PdfDocumentHelper { /// internal field List? documentSavedList; + /// internal field + List? documentSavedListAsync; + /// internal field late PdfMainObjectCollection objects; @@ -1117,8 +1520,11 @@ class PdfDocumentHelper { encryption = false; } if (!encryptor.checkPassword(password!, key, encryption)) { - throw ArgumentError.value(password, 'password', - 'Cannot open an encrypted document. The password is invalid.'); + throw ArgumentError.value( + password, + 'password', + 'Cannot open an encrypted document. The password is invalid.', + ); } encryptionDict.encrypt = false; final PdfSecurity security = PdfSecurityHelper.getSecurity(); @@ -1151,8 +1557,10 @@ class PdfDocumentHelper { final PdfCatalogNames? names = catalog.names; if (names != null) { destinations = names.destinations; - final IPdfPrimitive? name = - names.getNamedObjectFromTree(destinations, obj); + final IPdfPrimitive? name = names.getNamedObjectFromTree( + destinations, + obj, + ); destination = _extractDestination(name); } } @@ -1188,8 +1596,9 @@ class PdfDocumentHelper { if (_bookmarkHashTable == null) { _bookmarkHashTable = {}; final Queue stack = Queue(); - CurrentNodeInfo ni = - CurrentNodeInfo(PdfBookmarkBaseHelper.getList(current)); + CurrentNodeInfo ni = CurrentNodeInfo( + PdfBookmarkBaseHelper.getList(current), + ); do { for (; ni.index < ni.kids.length;) { current = ni.kids[ni.index]; @@ -1210,15 +1619,18 @@ class PdfDocumentHelper { } } else { final PdfDestination? dest = (current as PdfBookmark).destination; - final PdfPage page = dest!.page; - List? list = _bookmarkHashTable!.containsKey(page) - ? _bookmarkHashTable![page] as List? - : null; - if (list == null) { - list = []; - _bookmarkHashTable![page] = list; + 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); } - list.add(current); } ni.index = ni.index + 1; if (current.count > 0) { @@ -1242,4 +1654,336 @@ class PdfDocumentHelper { void setUserPassword(PdfPasswordArgs args) { base.onPdfPassword!(base, args); } + + /// Imports the FDF file bytes. + void importFdf(List inputBytes) { + final FdfParser parser = FdfParser(inputBytes); + parser.parseAnnotationData(); + parser.importAnnotations(base); + parser.dispose(); + } + + /// Imports the FDF file bytes. + void importJson(List inputBytes) { + final JsonParser parser = JsonParser(base); + parser.importAnnotationData(inputBytes); + } + + /// Imports the XFDF file bytes. + void importXfdf(List data) { + final XfdfParser parser = XfdfParser(data, base); + parser.parseAndImportAnnotationData(); + } + + /// Exports annotation to FDF file bytes. + List exportFdf( + String? fileName, + List? exportAnnotation, + List? exportTypes, + bool exportAppearance, + ) { + const String genNumber = + '${PdfOperators.whiteSpace}0${PdfOperators.whiteSpace}'; + const String startDictionary = '<<${PdfOperators.slash}'; + final List fdfBytes = []; + fdfBytes.addAll(utf8.encode('%FDF-1.2${PdfOperators.newLine}')); + int currentID = 2; + final List annotID = []; + final List annotType = []; + _getExportTypes(exportTypes, annotType); + if (exportAnnotation != null && exportAnnotation.isNotEmpty) { + for (int i = 0; i < exportAnnotation.length; i++) { + final PdfAnnotation annotation = exportAnnotation[i]; + final PdfAnnotationHelper helper = PdfAnnotationHelper.getHelper( + annotation, + ); + if (helper.isLoadedAnnotation && + (annotType.isEmpty || + annotType.contains(_getAnnotationType(helper.dictionary!))) && + !(annotation is PdfLinkAnnotation || + annotation is PdfTextWebLink) && + annotation.page != null) { + final FdfDocument fdfDocument = FdfDocument( + helper.dictionary!, + annotation.page!, + ); + final Map result = fdfDocument.exportAnnotations( + currentID, + annotID, + base.pages.indexOf(annotation.page!), + _checkForStamp(helper.dictionary!) == 'Stamp' || exportAppearance, + ); + fdfBytes.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } + } + } else { + for (int i = 0; i < base.pages.count; i++) { + final PdfPage page = base.pages[i]; + final PdfPageHelper pageHelper = PdfPageHelper.getHelper(page); + pageHelper.createAnnotations(pageHelper.getWidgetReferences()); + for (int j = 0; j < pageHelper.terminalAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + pageHelper.terminalAnnotation[j]; + if ((annotType.isEmpty || + annotType.contains( + _getAnnotationType(annotationDictionary), + )) && + !isLinkAnnotation(annotationDictionary)) { + final FdfDocument fdfDocument = FdfDocument( + annotationDictionary, + page, + ); + final Map result = fdfDocument.exportAnnotations( + currentID, + annotID, + i, + _checkForStamp(annotationDictionary) == 'Stamp' || + exportAppearance, + ); + fdfBytes.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } + } + } + } + fileName ??= ''; + if (currentID != 2) { + const String root = '1$genNumber'; + fdfBytes.addAll( + utf8.encode( + '${'$root${PdfOperators.obj}${PdfOperators.newLine}${startDictionary}FDF$startDictionary${PdfDictionaryProperties.annots}'}[', + ), + ); + for (int i = 0; i < annotID.length - 1; i++) { + fdfBytes.addAll( + utf8.encode( + '${annotID[i]}$genNumber${PdfDictionaryProperties.r}${PdfOperators.whiteSpace}', + ), + ); + } + fdfBytes.addAll( + utf8.encode( + '${annotID[annotID.length - 1]}$genNumber${PdfDictionaryProperties.r}]${PdfOperators.slash}${PdfDictionaryProperties.f}($fileName)${PdfOperators.slash}${PdfDictionaryProperties.uf}($fileName)>>${PdfOperators.slash}${PdfDictionaryProperties.type}${PdfOperators.slash}${PdfDictionaryProperties.catalog}>>${PdfOperators.newLine}${PdfOperators.endobj}${PdfOperators.newLine}', + ), + ); + fdfBytes.addAll( + utf8.encode( + '${PdfOperators.trailer}${PdfOperators.newLine}$startDictionary${PdfDictionaryProperties.root}${PdfOperators.whiteSpace}$root${PdfDictionaryProperties.r}>>${PdfOperators.newLine}${PdfOperators.endOfFileMarker}${PdfOperators.newLine}', + ), + ); + } + return fdfBytes; + } + + /// Exports annotation to JSON file bytes. + List exportJson( + String? fileName, + List? exportAnnotation, + List? exportTypes, + bool exportAppearance, + ) { + String json = '{"pdfAnnotation":{'; + bool isAnnotationAdded = false; + JsonDocument? jsonDocument = JsonDocument(base); + final Map table = {}; + final List annotType = []; + _getExportTypes(exportTypes, annotType); + if (exportAnnotation != null && exportAnnotation.isNotEmpty) { + final Map table1 = {}; + String? tempJson = ''; + for (int j = 0; j < exportAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + PdfAnnotationHelper.getHelper(exportAnnotation[j]).dictionary!; + if ((annotType.isEmpty || + annotType.contains(_getAnnotationType(annotationDictionary))) && + exportAnnotation[j].page != null) { + final int pageIndex = base.pages.indexOf(exportAnnotation[j].page!); + if (pageIndex >= 0) { + if (table1.containsKey(pageIndex)) { + tempJson = '${table1[pageIndex]!},'; + } else { + tempJson = '"$pageIndex":{ "shapeAnnotation":['; + } + jsonDocument.exportAnnotationData( + table, + exportAppearance, + base.pages.indexOf(exportAnnotation[j].page!), + annotationDictionary, + ); + tempJson += jsonDocument.convertToJson(table); + table1[pageIndex] = tempJson; + table.clear(); + } + } + } + final List values = table1.values.toList(); + for (int i = 0; i < values.length; i++) { + json += table1[i]! + ((i < values.length - 1) ? ']},' : ']}'); + } + table1.clear(); + values.clear(); + } else { + for (int i = 0; i < base.pages.count; i++) { + final PdfPageHelper pageHelper = PdfPageHelper.getHelper(base.pages[i]); + pageHelper.createAnnotations(pageHelper.getWidgetReferences()); + if (pageHelper.terminalAnnotation.isNotEmpty) { + json += (i != 0 && isAnnotationAdded) ? ',' : ' '; + json += '"$i":{ "shapeAnnotation":['; + isAnnotationAdded = true; + } + for (int j = 0; j < pageHelper.terminalAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + pageHelper.terminalAnnotation[j]; + if (annotType.isEmpty || + annotType.contains(_getAnnotationType(annotationDictionary))) { + jsonDocument.exportAnnotationData( + table, + exportAppearance, + i, + annotationDictionary, + ); + json += jsonDocument.convertToJson(table); + if (j < pageHelper.terminalAnnotation.length - 1) { + json += ','; + } + table.clear(); + } + } + if (pageHelper.terminalAnnotation.isNotEmpty) { + json += ']}'; + } + } + } + jsonDocument = null; + json += '}}'; + return utf8.encode(json); + } + + /// Exports annotation to XFDF file bytes. + List exportXfdf( + String? fileName, + List? exportAnnotation, + List? exportTypes, + bool exportAppearance, + ) { + final XFdfDocument xfdf = XFdfDocument(fileName ?? ''); + final List elements = []; + final List annotType = []; + _getExportTypes(exportTypes, annotType); + if (exportAnnotation != null && exportAnnotation.isNotEmpty) { + for (int j = 0; j < exportAnnotation.length; j++) { + if (annotType.isEmpty || + annotType.contains( + _getAnnotationType( + PdfAnnotationHelper.getHelper(exportAnnotation[j]).dictionary!, + ), + )) { + final XmlElement? element = xfdf.exportAnnotationData( + PdfAnnotationHelper.getHelper(exportAnnotation[j]).dictionary!, + base.pages.indexOf(exportAnnotation[j].page!), + exportAppearance, + base, + ); + if (element != null) { + elements.add(element); + } + } + } + } else { + for (int i = 0; i < base.pages.count; i++) { + final PdfPageHelper pageHelper = PdfPageHelper.getHelper(base.pages[i]); + pageHelper.createAnnotations(pageHelper.getWidgetReferences()); + for (int j = 0; j < pageHelper.terminalAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + pageHelper.terminalAnnotation[j]; + if (annotType.isEmpty || + annotType.contains(_getAnnotationType(annotationDictionary))) { + final XmlElement? element = xfdf.exportAnnotationData( + annotationDictionary, + i, + exportAppearance, + base, + ); + if (element != null) { + elements.add(element); + } + } + } + } + } + return xfdf.save(elements); + } + + void _getExportTypes( + List? types, + List annotType, + ) { + if (types != null && types.isNotEmpty) { + for (int i = 0; i < types.length; i++) { + String annotationType = getEnumName(types[i]); + switch (annotationType) { + case 'HighlightAnnotation': + annotationType = 'Highlight'; + break; + case 'UnderlineAnnotation': + annotationType = 'Underline'; + break; + case 'StrikeOutAnnotation': + annotationType = 'StrikeOut'; + break; + case 'SquigglyAnnotation': + annotationType = 'Squiggly'; + break; + } + annotType.add(annotationType); + } + } + } + + String _getAnnotationType(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final PdfName name = + PdfAnnotationHelper.getValue( + dictionary, + crossTable, + PdfDictionaryProperties.subtype, + true, + )! + as PdfName; + final PdfAnnotationTypes type = + PdfAnnotationCollectionHelper.getAnnotationType( + name, + dictionary, + crossTable, + ); + return getEnumName(type); + } + return ''; + } + + String _checkForStamp(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype], + ); + if (name != null && name is PdfName) { + return name.name ?? ''; + } + } + return ''; + } + + /// Internal method. + static bool isLinkAnnotation(PdfDictionary annotationDictionary) { + if (annotationDictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + annotationDictionary[PdfDictionaryProperties.subtype], + ); + if (name != null && name is PdfName) { + return name.name == 'Link'; + } + } + return false; + } } 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 e27bec1c7..f9c05d66b 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 @@ -11,10 +11,12 @@ import 'pdf_catalog.dart'; /// A class containing the information about the document. class PdfDocumentInformation implements IPdfWrapper { //Constructor - PdfDocumentInformation._(PdfCatalog catalog, - {PdfDictionary? dictionary, - bool isLoaded = false, - PdfConformanceLevel? conformance}) { + PdfDocumentInformation._( + PdfCatalog catalog, { + PdfDictionary? dictionary, + bool isLoaded = false, + PdfConformanceLevel? conformance, + }) { _helper = PdfDocumentInformationHelper(this); _helper._catalog = catalog; if (conformance != null) { @@ -27,7 +29,9 @@ class PdfDocumentInformation implements IPdfWrapper { _helper._dictionary = PdfDictionary(); if (_helper.conformance != PdfConformanceLevel.a1b) { _helper._dictionary!.setDateTime( - PdfDictionaryProperties.creationDate, _helper.creationDate); + PdfDictionaryProperties.creationDate, + _helper.creationDate, + ); } } } @@ -45,12 +49,15 @@ class PdfDocumentInformation implements IPdfWrapper { /// Gets the creation date of the PDF document DateTime get creationDate { - if (_helper._dictionary! - .containsKey(PdfDictionaryProperties.creationDate) && + if (_helper._dictionary!.containsKey( + PdfDictionaryProperties.creationDate, + ) && _helper._dictionary![PdfDictionaryProperties.creationDate] is PdfString) { - return _helper.creationDate = _helper._dictionary!.getDateTime(_helper - ._dictionary![PdfDictionaryProperties.creationDate]! as PdfString); + return _helper.creationDate = _helper._dictionary!.getDateTime( + _helper._dictionary![PdfDictionaryProperties.creationDate]! + as PdfString, + ); } return _helper.creationDate = DateTime.now(); } @@ -60,19 +67,23 @@ class PdfDocumentInformation implements IPdfWrapper { if (_helper.creationDate != value) { _helper.creationDate = value; _helper._dictionary!.setDateTime( - PdfDictionaryProperties.creationDate, _helper.creationDate); + PdfDictionaryProperties.creationDate, + _helper.creationDate, + ); } } /// Gets the modification date of the PDF document DateTime get modificationDate { - if (_helper._dictionary! - .containsKey(PdfDictionaryProperties.modificationDate) && + if (_helper._dictionary!.containsKey( + PdfDictionaryProperties.modificationDate, + ) && _helper._dictionary![PdfDictionaryProperties.modificationDate] is PdfString) { return _helper.modificationDate = _helper._dictionary!.getDateTime( - _helper._dictionary![PdfDictionaryProperties.modificationDate]! - as PdfString); + _helper._dictionary![PdfDictionaryProperties.modificationDate]! + as PdfString, + ); } return _helper.modificationDate = DateTime.now(); } @@ -81,17 +92,19 @@ class PdfDocumentInformation implements IPdfWrapper { set modificationDate(DateTime value) { _helper.modificationDate = value; _helper._dictionary!.setDateTime( - PdfDictionaryProperties.modificationDate, _helper.modificationDate); + PdfDictionaryProperties.modificationDate, + _helper.modificationDate, + ); } /// Gets the title. String get title { if (_helper._dictionary!.containsKey(PdfDictionaryProperties.title) && _helper._dictionary![PdfDictionaryProperties.title] is PdfString) { - return _title = - (_helper._dictionary![PdfDictionaryProperties.title]! as PdfString) - .value! - .replaceAll('\u0000', ''); + return _title = (_helper._dictionary![PdfDictionaryProperties.title]! + as PdfString) + .value! + .replaceAll('\u0000', ''); } return _title = ''; } @@ -160,8 +173,10 @@ class PdfDocumentInformation implements IPdfWrapper { set keywords(String value) { if (_keywords != value) { _keywords = value; - _helper._dictionary! - .setString(PdfDictionaryProperties.keywords, _keywords); + _helper._dictionary!.setString( + PdfDictionaryProperties.keywords, + _keywords, + ); } if (_helper._catalog != null && _helper._catalog!.metadata != null) { _helper._xmp = _helper.xmpMetadata; @@ -204,29 +219,35 @@ class PdfDocumentInformation implements IPdfWrapper { set producer(String value) { if (_producer != value) { _producer = value; - _helper._dictionary! - .setString(PdfDictionaryProperties.producer, _producer); + _helper._dictionary!.setString( + PdfDictionaryProperties.producer, + _producer, + ); } } /// Remove the modification date from existing document. void removeModificationDate() { if (_helper._dictionary != null && - _helper._dictionary! - .containsKey(PdfDictionaryProperties.modificationDate)) { + _helper._dictionary!.containsKey( + PdfDictionaryProperties.modificationDate, + )) { _helper._dictionary!.remove(PdfDictionaryProperties.modificationDate); if (_helper._dictionary!.changed! && !_helper._catalog!.changed!) { PdfDocumentInformationHelper.getHelper( - _helper._catalog!.document!.documentInformation) - ._dictionary! - .remove(PdfDictionaryProperties.modificationDate); + _helper._catalog!.document!.documentInformation, + )._dictionary!.remove(PdfDictionaryProperties.modificationDate); PdfDocumentInformationHelper.getHelper( - _helper._catalog!.document!.documentInformation) - .isRemoveModifyDate = true; - _helper._xmp = - XmpMetadata(_helper._catalog!.document!.documentInformation); + _helper._catalog!.document!.documentInformation, + ).isRemoveModifyDate = + true; + _helper._xmp = XmpMetadata( + _helper._catalog!.document!.documentInformation, + ); _helper._catalog!.setProperty( - PdfDictionaryProperties.metadata, PdfReferenceHolder(_helper._xmp)); + PdfDictionaryProperties.metadata, + PdfReferenceHolder(_helper._xmp), + ); } } } @@ -246,12 +267,18 @@ class PdfDocumentInformationHelper { } /// internal method - static PdfDocumentInformation load(PdfCatalog catalog, - {PdfDictionary? dictionary, - bool isLoaded = false, - PdfConformanceLevel? conformance}) { - return PdfDocumentInformation._(catalog, - dictionary: dictionary, isLoaded: isLoaded, conformance: conformance); + static PdfDocumentInformation load( + PdfCatalog catalog, { + PdfDictionary? dictionary, + bool isLoaded = false, + PdfConformanceLevel? conformance, + }) { + return PdfDocumentInformation._( + catalog, + dictionary: dictionary, + isLoaded: isLoaded, + conformance: conformance, + ); } /// internal field @@ -284,27 +311,36 @@ class PdfDocumentInformationHelper { if (_xmp == null) { if (_catalog!.metadata == null && _catalog!.pages != null) { _xmp = XmpMetadata( - PdfSectionCollectionHelper.getHelper(_catalog!.pages!) - .document! - .documentInformation); + PdfSectionCollectionHelper.getHelper( + _catalog!.pages!, + ).document!.documentInformation, + ); _catalog!.setProperty( - PdfDictionaryProperties.metadata, PdfReferenceHolder(_xmp)); + PdfDictionaryProperties.metadata, + PdfReferenceHolder(_xmp), + ); } else { if (_dictionary!.changed! && !_catalog!.changed!) { _xmp = XmpMetadata(_catalog!.document!.documentInformation); _catalog!.setProperty( - PdfDictionaryProperties.metadata, PdfReferenceHolder(_xmp)); + PdfDictionaryProperties.metadata, + PdfReferenceHolder(_xmp), + ); } else { _xmp = _catalog!.metadata; _catalog!.setProperty( - PdfDictionaryProperties.metadata, PdfReferenceHolder(_xmp)); + PdfDictionaryProperties.metadata, + PdfReferenceHolder(_xmp), + ); } } } else if (_catalog!.metadata != null && _catalog!.document != null) { if (_dictionary!.changed! && !_catalog!.changed!) { _xmp = XmpMetadata(_catalog!.document!.documentInformation); _catalog!.setProperty( - PdfDictionaryProperties.metadata, PdfReferenceHolder(_xmp)); + PdfDictionaryProperties.metadata, + PdfReferenceHolder(_xmp), + ); } } return _xmp; 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 9ab931f72..4fc1484ed 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 @@ -9,23 +9,37 @@ import '../pages/pdf_section.dart'; class PdfDocumentTemplate { //Constructors /// Initializes a new instance of the [PdfDocumentTemplate] class. - 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}) { + 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, + }) { _helper = PdfDocumentTemplateHelper(this); - _intialize(left, top, right, bottom, evenLeft, evenTop, evenRight, - evenBottom, oddLeft, oddTop, oddRight, oddBottom, stamps); + _intialize( + left, + top, + right, + bottom, + evenLeft, + evenTop, + evenRight, + evenBottom, + oddLeft, + oddTop, + oddRight, + oddBottom, + stamps, + ); } //Fields @@ -149,19 +163,20 @@ class PdfDocumentTemplate { //Implementation 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) { + 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; } @@ -204,12 +219,16 @@ class PdfDocumentTemplate { } PdfPageTemplateElement? _checkElement( - PdfPageTemplateElement? templateElement, TemplateType type) { + PdfPageTemplateElement? templateElement, + TemplateType type, + ) { if (templateElement != null) { if (PdfPageTemplateElementHelper.getHelper(templateElement).type != TemplateType.none) { - throw ArgumentError.value(type, - "Can't reassign the template element. Please, create new one."); + throw ArgumentError.value( + type, + "Can't reassign the template element. Please, create new one.", + ); } PdfPageTemplateElementHelper.getHelper(templateElement).type = type; } @@ -317,16 +336,16 @@ class PdfDocumentTemplateHelper { bool _isEven(PdfPage page) { final PdfPageCollection pages = - PdfSectionHelper.getHelper(PdfPageHelper.getHelper(page).section!) - .document! - .pages; + PdfSectionHelper.getHelper( + PdfPageHelper.getHelper(page).section!, + ).document!.pages; int index = 0; - if (PdfPageCollectionHelper.getHelper(pages) - .pageCollectionIndex - .containsKey(page)) { + if (PdfPageCollectionHelper.getHelper( + pages, + ).pageCollectionIndex.containsKey(page)) { index = PdfPageCollectionHelper.getHelper(pages).pageCollectionIndex[page]! + - 1; + 1; } else { index = pages.indexOf(page) + 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 f43352347..fe2056da5 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 @@ -16,7 +16,7 @@ class PdfFileStructure { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfFileStructure() { @@ -46,7 +46,7 @@ class PdfFileStructure { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` late PdfCrossReferenceType crossReferenceType; @@ -64,7 +64,7 @@ class PdfFileStructure { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); /// ``` PdfVersion get version => _version; @@ -81,7 +81,7 @@ class PdfFileStructure { /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(0, 0, 0, 0)); /// //Save and dispose document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// document.dispose(); set version(PdfVersion value) { _version = value; 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 94cb8da16..2bc2728bc 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 @@ -7,6 +7,7 @@ import '../io/pdf_constants.dart'; import '../io/pdf_cross_table.dart'; import '../io/pdf_parser.dart'; import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_null.dart'; import '../primitives/pdf_number.dart'; /// internal class @@ -96,7 +97,7 @@ class PdfArray implements IPdfPrimitive, IPdfChangable { rectangle.left, rectangle.top, rectangle.right, - rectangle.bottom + rectangle.bottom, ]; return PdfArray(list); } @@ -196,9 +197,11 @@ class PdfArray implements IPdfPrimitive, IPdfChangable { if (writer != null) { writer.write(startMark); for (int i = 0; i < count; i++) { - this[i]!.save(writer); - if (i + 1 != count) { - writer.write(PdfOperators.whiteSpace); + if (this[i] != null && this[i] is! PdfNull) { + this[i]!.save(writer); + if (i + 1 != count) { + writer.write(PdfOperators.whiteSpace); + } } } writer.write(endMark); 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 b1cf8c580..2a4ef2cfd 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 @@ -43,6 +43,7 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { int? _objectCollectionIndex; int? _position; PdfObjectStatus? _status; + bool? _isSkip; /// internal field PdfCrossTable? crossTable; @@ -61,6 +62,16 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { /// Set the PdfDictionary items. operator []=(dynamic key, dynamic value) => addItems(key, value); + /// internal property + bool get isSkip { + _isSkip ??= false; + return _isSkip!; + } + + set isSkip(bool value) { + _isSkip = value; + } + /// internal method dynamic addItems(dynamic key, dynamic value) { if (key == null) { @@ -92,8 +103,9 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { /// internal method 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); } } @@ -167,7 +179,10 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { } /// internal method - void setName(PdfName key, String? name) { + void setName(dynamic key, String? name) { + if (key is String) { + key = PdfName(key); + } if (items!.containsKey(key)) { this[key] = PdfName(name); modify(); @@ -193,10 +208,12 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { /// internal method void setString(String key, String? str) { - final PdfString? pdfString = this[key] as PdfString?; - if (pdfString != null) { - pdfString.value = str; - modify(); + if (containsKey(key) && this[key] is PdfString) { + final IPdfPrimitive? pdfString = this[key]; + if (pdfString != null && pdfString is PdfString) { + pdfString.value = str; + modify(); + } } else { this[key] = PdfString(str!); } @@ -240,9 +257,11 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { final List fieldCollection = []; if (fields is PdfArray) { for (int k = 0; k < fields.count; k++) { - final PdfReferenceHolder fieldReference = - fields.elements[k]! as PdfReferenceHolder; - fieldCollection.add(fieldReference); + if (fields.elements[k] is PdfReferenceHolder) { + final PdfReferenceHolder referenceHolder = + fields.elements[k]! as PdfReferenceHolder; + fieldCollection.add(referenceHolder); + } } for (int i = 0; i < fields.count; i++) { if (fields.elements[i]! is PdfReferenceHolder) { @@ -252,13 +271,15 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { refHolder.referenceObject as PdfDictionary?; if (field != null) { if (field.beginSave != null) { - final SavePdfPrimitiveArgs args = - SavePdfPrimitiveArgs(writer); + final SavePdfPrimitiveArgs args = SavePdfPrimitiveArgs( + writer, + ); field.beginSave!(field, args); } if (!field.containsKey(PdfName(PdfDictionaryProperties.kids))) { - if (field.items! - .containsKey(PdfName(PdfDictionaryProperties.ft))) { + if (field.items!.containsKey( + PdfName(PdfDictionaryProperties.ft), + )) { final IPdfPrimitive? value = field.items![PdfName(PdfDictionaryProperties.ft)]; if (value != null && @@ -273,9 +294,11 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { final PdfDictionary field1 = fieldRef.object! as PdfDictionary; if (field1.items!.containsKey( - PdfName(PdfDictionaryProperties.t)) && + PdfName(PdfDictionaryProperties.t), + ) && field.items!.containsKey( - PdfName(PdfDictionaryProperties.t))) { + PdfName(PdfDictionaryProperties.t), + )) { final PdfString parentSignatureName = field1.items![PdfName(PdfDictionaryProperties.t)]! as PdfString; @@ -318,8 +341,9 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { /// internal method int getInt(String propertyName) { - final IPdfPrimitive? primitive = - PdfCrossTable.dereference(this[propertyName]); + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + this[propertyName], + ); return (primitive != null && primitive is PdfNumber) ? primitive.value!.toInt() : 0; @@ -327,8 +351,9 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { /// internal method PdfString? getString(String propertyName) { - final IPdfPrimitive? primitive = - PdfCrossTable.dereference(this[propertyName]); + final IPdfPrimitive? primitive = PdfCrossTable.dereference( + this[propertyName], + ); return (primitive != null && primitive is PdfString) ? primitive : null; } @@ -348,7 +373,7 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { } /// internal method - void setNumber(String key, int? value) { + void setNumber(String key, num? value) { final PdfNumber? pdfNumber = this[key] as PdfNumber?; if (pdfNumber != null) { pdfNumber.value = value; @@ -389,7 +414,8 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { modify(); } else { this[key] = PdfString( - "D:${dateFormat.format(dateTime)}+$offsetHours'$offsetMinutes'"); + "D:${dateFormat.format(dateTime)}+$offsetHours'$offsetMinutes'", + ); } } @@ -403,24 +429,33 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { value = dateTimeString.value!; } while (value[value.length - 1].contains(RegExp('[:-D-(-)]'))) { - dateTimeString.value = - value.replaceRange(value.length - 1, value.length, ''); + dateTimeString.value = value.replaceRange( + value.length - 1, + value.length, + '', + ); } if (dateTimeString.value!.startsWith('191')) { dateTimeString.value = dateTimeString.value!.replaceFirst('191', '20'); } final bool containPrefixD = dateTimeString.value!.contains(prefixD); const String dateTimeFormat = 'yyyyMMddHHmmss'; - dateTimeString.value = - dateTimeString.value!.padRight(dateTimeFormat.length, '0'); + dateTimeString.value = dateTimeString.value!.padRight( + dateTimeFormat.length, + '0', + ); String localTime = ''.padRight(dateTimeFormat.length); if (dateTimeString.value!.isEmpty) { return DateTime.now(); } if (dateTimeString.value!.length >= localTime.length) { - localTime = containPrefixD - ? dateTimeString.value!.substring(prefixD.length, localTime.length) - : dateTimeString.value!.substring(0, localTime.length); + localTime = + containPrefixD + ? dateTimeString.value!.substring( + prefixD.length, + localTime.length, + ) + : dateTimeString.value!.substring(0, localTime.length); } final String dateWithT = '${localTime.substring(0, 8)}T${localTime.substring(8)}'; @@ -602,5 +637,5 @@ class SavePdfPrimitiveArgs { } /// internal type definition -typedef SavePdfPrimitiveCallback = void Function( - Object sender, SavePdfPrimitiveArgs? args); +typedef SavePdfPrimitiveCallback = + void Function(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 a7fd71433..8e11b1cae 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 @@ -1,11 +1,13 @@ -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/io/enums.dart'; -import 'package:syncfusion_flutter_pdf/src/pdf/implementation/io/pdf_cross_table.dart'; -import 'package:syncfusion_flutter_pdf/src/pdf/interfaces/pdf_interface.dart'; +import '../../interfaces/pdf_interface.dart'; +import '../io/enums.dart'; +import '../io/pdf_cross_table.dart'; /// internal class class PdfName implements IPdfPrimitive { /// Constructor for creation [PdfName] object. - PdfName([this.name]); + PdfName([String? name]) { + this.name = name; + } //Constants /// internal field @@ -14,12 +16,22 @@ class PdfName implements IPdfPrimitive { //Fields /// internal field - final String? name; + String? _name; bool? _isSaving; int? _objectCollectionIndex; int? _position; PdfObjectStatus? _status; + //Properties + /// Gets or sets the name. + String? get name { + return _name; + } + + set name(String? value) { + _name = normalizeValue(value); + } + //Implementation String _escapeString(String value) { String result = ''; @@ -36,6 +48,37 @@ class PdfName implements IPdfPrimitive { return result; } + /// Replace the characters to hexa decimal format. + static String? normalizeValue(String? value) { + if (value != null && value.isNotEmpty) { + value = value + .replaceAll('\t', '#09') + .replaceAll('\n', '#0A') + .replaceAll('\r', '#0D') + .replaceAll(' ', '#20'); + } + return value; + } + + /// Replace the hexa decimal format to replace characters. + static String? decodeName(String? value) { + if (value != null) { + return value + .replaceAll('#9', '\t') + .replaceAll('#09', '\t') + .replaceAll('#A', '\n') + .replaceAll('#a', '\n') + .replaceAll('#0A', '\n') + .replaceAll('#0a', '\n') + .replaceAll('#D', '\r') + .replaceAll('#d', '\r') + .replaceAll('#0D', '\r') + .replaceAll('#0d', '\r') + .replaceAll('#20', ' '); + } + return null; + } + @override String toString() { return stringStartMark + _escapeString(name!); 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 54da82ff4..bc3d8494c 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 @@ -72,13 +72,11 @@ class PdfNumber implements IPdfPrimitive { @override void save(IPdfWriter? writer) { if (value is double) { - String numberValue = value!.toStringAsFixed(2); - if (numberValue.endsWith('.00')) { - if (numberValue.length == 3) { - numberValue = '0'; - } else { - numberValue = numberValue.substring(0, numberValue.length - 3); - } + String numberValue = value! + .toStringAsFixed(10) + .replaceAll(RegExp(r'0*$'), ''); + if (numberValue.endsWith('.')) { + numberValue = numberValue.replaceAll('.', ''); } writer!.write(numberValue); } else { 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 af95b30c0..1a1ca63ac 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 @@ -24,7 +24,8 @@ class PdfReferenceHolder implements IPdfPrimitive { object = obj; } else { throw ArgumentError.value( - 'argument is not set to an instance of an object'); + 'argument is not set to an instance of an object', + ); } } 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 d3cd72cf5..c4c5cbcf9 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,4 +1,5 @@ import 'dart:convert'; +import 'dart:typed_data'; import '../../interfaces/pdf_interface.dart'; import '../compression/compressed_stream_writer.dart'; @@ -23,13 +24,13 @@ class PdfStream extends PdfDictionary { /// internal constructor PdfStream([PdfDictionary? dictionary, List? data]) { if (dictionary == null && data == null) { - dataStream = []; + this.data = []; _compress = true; } else { ArgumentError.checkNotNull(data, 'data'); ArgumentError.checkNotNull(dictionary, 'dictionary'); _compress = false; - dataStream = []; + this.data = []; dataStream!.addAll(data!); copyDictionary(dictionary); this[PdfDictionaryProperties.length] = PdfNumber(dataStream!.length); @@ -47,16 +48,15 @@ class PdfStream extends PdfDictionary { //Fields /// internal field - List? dataStream; + List? data; bool? _compress; PdfStream? _clonedObject; /// internal field - @override - bool? isChanged; + late bool blockEncryption; /// internal field - late bool blockEncryption; + int? objNumber; //Properties /// internal property @@ -69,20 +69,41 @@ class PdfStream extends PdfDictionary { _modify(); } + /// internal property + List? get dataStream { + if (!decrypted! && + crossTable != null && + crossTable!.encryptor != null && + objNumber != null && + objNumber! > -1) { + decrypt(crossTable!.encryptor!, objNumber); + } + return data; + } + //Implementations void _modify() { isChanged = true; } List? _compressContent(PdfDocument? document) { - final List? data = dataStream; + final List? data = + (crossTable != null && + crossTable!.encryptor != null && + crossTable!.encryptor!.encryptAttachmentOnly!) + ? this.data + : dataStream; if (compress! && document!.compressionLevel != PdfCompressionLevel.none) { final List outputStream = []; final CompressedStreamWriter compressedWriter = CompressedStreamWriter( - outputStream, false, document.compressionLevel, false); + outputStream, + false, + document.compressionLevel, + false, + ); compressedWriter.write(data!, 0, data.length, false); compressedWriter.close(); - _addFilter(PdfDictionaryProperties.flateDecode); + addFilter(PdfDictionaryProperties.flateDecode); compress = false; return outputStream; } else { @@ -90,10 +111,44 @@ class PdfStream extends PdfDictionary { } } + List? _compressStream() { + final List? streamData = + (crossTable != null && + crossTable!.encryptor != null && + crossTable!.encryptor!.encryptAttachmentOnly!) + ? data + : dataStream; + final List outputStream = []; + if (streamData != null && streamData.isNotEmpty) { + CompressedStreamWriter( + outputStream, + false, + PdfCompressionLevel.best, + false, + ) + ..write(streamData, 0, streamData.length, false) + ..close(); + if (outputStream.isNotEmpty) { + clearStream(); + compress = false; + data = outputStream; + addFilter(PdfDictionaryProperties.flateDecode); + } + } + return dataStream; + } + /// internal method void decompress() { + data = getDecompressedData(true); + remove(PdfDictionaryProperties.filter); + _compress = true; + } + + List? getDecompressedData(bool modified) { String? filterName = ''; IPdfPrimitive? primitive = this[PdfDictionaryProperties.filter]; + List? decompressedData = dataStream; if (primitive is PdfReferenceHolder) { final PdfReferenceHolder holder = primitive; primitive = holder.object; @@ -102,11 +157,13 @@ class PdfStream extends PdfDictionary { if (primitive is PdfName) { final PdfName name = primitive; if (name.name == 'ASCIIHexDecode') { - dataStream = _decode(dataStream); + decompressedData = _decode(decompressedData); } else { - dataStream = _decompressData(dataStream!, name.name!); + decompressedData = _decompressData(decompressedData!, name.name!); + } + if (modified) { + _modify(); } - _modify(); } else if (primitive is PdfArray) { final PdfArray filter = primitive; for (int i = 0; i < filter.count; i++) { @@ -115,18 +172,19 @@ class PdfStream extends PdfDictionary { filterName = pdfFilter.name; } if (filterName == 'ASCIIHexDecode') { - dataStream = _decode(dataStream); + decompressedData = _decode(decompressedData); } else { - dataStream = _decompressData(dataStream!, filterName!); + decompressedData = _decompressData(decompressedData!, filterName!); + } + if (modified) { + _modify(); } - _modify(); } } else { throw ArgumentError.value(filterName, 'Invalid format'); } } - remove(PdfDictionaryProperties.filter); - _compress = true; + return decompressedData; } List? _decode(List? data) { @@ -134,7 +192,7 @@ class PdfStream extends PdfDictionary { } List _decompressData(List data, String filter) { - if (data.isEmpty) { + if (data.isEmpty || data.length == 1) { return data; } if (filter != PdfDictionaryProperties.crypt) { @@ -142,10 +200,13 @@ class PdfStream extends PdfDictionary { return data; } else if (filter == PdfDictionaryProperties.flateDecode || filter == PdfDictionaryProperties.flateDecodeShort) { - final PdfZlibCompressor compressor = PdfZlibCompressor(); - data = compressor.decompress(data); - data = _postProcess(data, filter); - return data; + try { + final PdfZlibCompressor compressor = PdfZlibCompressor(); + data = compressor.decompress(data); + } catch (e) { + //Non-compressed stream throws exception when try to decompress. + } + return _postProcess(data, filter); } else if (filter == PdfDictionaryProperties.ascii85Decode || filter == PdfDictionaryProperties.ascii85DecodeShort) { final PdfAscii85Compressor compressor = PdfAscii85Compressor(); @@ -249,8 +310,9 @@ class PdfStream extends PdfDictionary { return data; } - void _addFilter(String filterName) { - IPdfPrimitive? filter = items![PdfDictionaryProperties.filter]; + /// Internal method. + void addFilter(String filterName) { + IPdfPrimitive? filter = this[PdfDictionaryProperties.filter]; if (filter is PdfReferenceHolder) { filter = filter.referenceObject; } @@ -289,11 +351,13 @@ class PdfStream extends PdfDictionary { if ((pdfObject as List).isEmpty) { throw ArgumentError.value(pdfObject, 'value cannot be empty'); } - dataStream!.addAll(pdfObject); + data!.addAll(pdfObject); _modify(); } else { throw ArgumentError.value( - pdfObject, 'The method or operation is not implemented'); + pdfObject, + 'The method or operation is not implemented', + ); } } @@ -310,18 +374,19 @@ class PdfStream extends PdfDictionary { //IPdfPrimitive members @override void save(IPdfWriter? writer) { - final SavePdfPrimitiveArgs beginSaveArguments = - SavePdfPrimitiveArgs(writer); + final SavePdfPrimitiveArgs beginSaveArguments = SavePdfPrimitiveArgs( + writer, + ); onBeginSave(beginSaveArguments); List? data = _compressContent(writer!.document); final PdfSecurity security = writer.document!.security; if (PdfSecurityHelper.getHelper(security).encryptor.encrypt && - PdfSecurityHelper.getHelper(security) - .encryptor - .encryptAttachmentOnly!) { + PdfSecurityHelper.getHelper( + security, + ).encryptor.encryptAttachmentOnly!) { bool attachmentEncrypted = false; if (containsKey(PdfDictionaryProperties.type)) { - final IPdfPrimitive? primitive = items![PdfDictionaryProperties.type]; + final IPdfPrimitive? primitive = this[PdfDictionaryProperties.type]; if (primitive != null && primitive is PdfName && primitive.name == PdfDictionaryProperties.embeddedFile) { @@ -329,44 +394,47 @@ class PdfStream extends PdfDictionary { bool? isString; IPdfPrimitive? filterPrimitive; if (containsKey(PdfDictionaryProperties.filter)) { - filterPrimitive = items![PdfDictionaryProperties.filter]; + filterPrimitive = this[PdfDictionaryProperties.filter]; isArray = filterPrimitive is PdfArray; isString = filterPrimitive is PdfString; } if ((isArray == null && isString == null) || !isArray! || (isArray && - (filterPrimitive! as PdfArray) - .contains(PdfName(PdfDictionaryProperties.crypt)))) { + (filterPrimitive! as PdfArray).contains( + PdfName(PdfDictionaryProperties.crypt), + ))) { if (_compress! || !containsKey(PdfDictionaryProperties.filter) || (isArray! && (filterPrimitive! as PdfArray).contains( - PdfName(PdfDictionaryProperties.flateDecode)) || + PdfName(PdfDictionaryProperties.flateDecode), + ) || (isString! && (filterPrimitive! as PdfString).value == PdfDictionaryProperties.flateDecode))) { - data = _compressContent(writer.document); + data = _compressStream(); } attachmentEncrypted = true; data = _encryptContent(data, writer); - _addFilter(PdfDictionaryProperties.crypt); + addFilter(PdfDictionaryProperties.crypt); } if (!containsKey(PdfDictionaryProperties.decodeParms)) { final PdfArray decode = PdfArray(); final PdfDictionary decodeparms = PdfDictionary(); - decodeparms[PdfDictionaryProperties.name] = - PdfName(PdfDictionaryProperties.stdCF); + decodeparms[PdfDictionaryProperties.name] = PdfName( + PdfDictionaryProperties.stdCF, + ); decode.add(decodeparms); decode.add(PdfNull()); - items![PdfName(PdfDictionaryProperties.decodeParms)] = decode; + this[PdfName(PdfDictionaryProperties.decodeParms)] = decode; } } } if (!attachmentEncrypted) { if (containsKey(PdfDictionaryProperties.decodeParms)) { final IPdfPrimitive? primitive = - items![PdfDictionaryProperties.decodeParms]; + this[PdfDictionaryProperties.decodeParms]; if (primitive is PdfArray) { final PdfArray decodeParamArray = primitive; if (decodeParamArray.count > 0 && @@ -381,7 +449,7 @@ class PdfStream extends PdfDictionary { PdfArray? filter; if (containsKey(PdfDictionaryProperties.filter)) { final IPdfPrimitive? filterType = - items![PdfDictionaryProperties.filter]; + this[PdfDictionaryProperties.filter]; if (filterType is PdfArray) { filter = filterType; } @@ -389,10 +457,10 @@ class PdfStream extends PdfDictionary { if (filter == null || filter.contains(PdfName(PdfDictionaryProperties.crypt))) { if (_compress!) { - data = _compressContent(writer.document); + data = _compressStream(); } data = _encryptContent(data, writer); - _addFilter(PdfDictionaryProperties.crypt); + addFilter(PdfDictionaryProperties.crypt); } } } @@ -400,25 +468,27 @@ class PdfStream extends PdfDictionary { } } else if (containsKey(PdfDictionaryProperties.dl)) { if (_compress!) { - data = _compressContent(writer.document); + data = _compressStream(); } data = _encryptContent(data, writer); - _addFilter(PdfDictionaryProperties.crypt); + addFilter(PdfDictionaryProperties.crypt); if (!containsKey(PdfDictionaryProperties.decodeParms)) { final PdfArray decode = PdfArray(); final PdfDictionary decodeparms = PdfDictionary(); - decodeparms[PdfDictionaryProperties.name] = - PdfName(PdfDictionaryProperties.stdCF); + decodeparms[PdfDictionaryProperties.name] = PdfName( + PdfDictionaryProperties.stdCF, + ); decode.add(decodeparms); decode.add(PdfNull()); - items![PdfName(PdfDictionaryProperties.decodeParms)] = decode; + this[PdfName(PdfDictionaryProperties.decodeParms)] = decode; } } } - } - if (!PdfSecurityHelper.getHelper(security).encryptor.encryptOnlyMetadata! && + } else if (!PdfSecurityHelper.getHelper( + security, + ).encryptor.encryptOnlyMetadata! && containsKey(PdfDictionaryProperties.type)) { - final IPdfPrimitive? primitive = items![PdfDictionaryProperties.type]; + final IPdfPrimitive? primitive = this[PdfDictionaryProperties.type]; if (primitive != null && primitive is PdfName) { final PdfName fileType = primitive; if (fileType.name != PdfDictionaryProperties.metadata) { @@ -429,11 +499,13 @@ class PdfStream extends PdfDictionary { data = _encryptContent(data, writer); } - this[PdfDictionaryProperties.length] = PdfNumber(data!.length); + this[PdfDictionaryProperties.length] = PdfNumber( + data != null ? data.length : 0, + ); super.saveDictionary(writer, false); writer.write(prefix); writer.write(PdfOperators.newLine); - if (data.isNotEmpty) { + if (data != null && data.isNotEmpty) { writer.write(data); writer.write(PdfOperators.newLine); } @@ -448,10 +520,14 @@ class PdfStream extends PdfDictionary { @override void dispose() { - if (dataStream != null && dataStream!.isNotEmpty) { - dataStream!.clear(); - dataStream = null; + if (dataStream != null) { + if (dataStream is List && dataStream is! Uint8List) { + dataStream!.clear(); + } + data = null; } + _clonedObject = null; + objNumber = null; } @override @@ -468,15 +544,11 @@ class PdfStream extends PdfDictionary { return newStream; } - @override - bool? decrypted; - /// internal method void decrypt(PdfEncryptor encryptor, int? currentObjectNumber) { if (!decrypted!) { decrypted = true; - dataStream = - encryptor.encryptData(currentObjectNumber, dataStream!, false); + data = encryptor.encryptData(currentObjectNumber, dataStream!, false); _modify(); } } @@ -487,9 +559,10 @@ class PdfStream extends PdfDictionary { PdfSecurityHelper.getHelper(doc.security).encryptor; if (encryptor.encrypt && !blockEncryption) { data = encryptor.encryptData( - PdfDocumentHelper.getHelper(doc).currentSavingObject!.objNum, - data!, - true); + PdfDocumentHelper.getHelper(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 ef6661fa2..8151c6936 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,6 +1,7 @@ import 'dart:convert'; import '../../interfaces/pdf_interface.dart'; +import '../annotations/json_parser.dart'; import '../io/decode_big_endian.dart'; import '../io/enums.dart'; import '../io/pdf_cross_table.dart'; @@ -20,8 +21,8 @@ class PdfString implements IPdfPrimitive { 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)); + for (int i = 0; i < this.value!.length; i++) { + data!.add(this.value!.codeUnitAt(i).toUnsigned(8)); } } else { this.value = byteToString(data!); @@ -89,13 +90,25 @@ class PdfString implements IPdfPrimitive { /// internal field late bool isParentDecrypted; + /// internal field + bool isColorSpace = false; + //Implementations /// internal method List pdfEncode(PdfDocument? document) { final List result = []; - result.add(isHex! - ? PdfString.hexStringMark.codeUnitAt(0) - : PdfString.stringMark.codeUnitAt(0)); + final PdfSecurity? security = (document == null) ? null : document.security; + result.add( + isHex! + ? PdfString.hexStringMark.codeUnitAt(0) + : PdfString.stringMark.codeUnitAt(0), + ); + if ((data == null || data!.isEmpty) && !isNullOrEmpty(value)) { + data = []; + for (int i = 0; i < value!.length; i++) { + data!.add(value!.codeUnitAt(i).toUnsigned(8)); + } + } if (data != null && data!.isNotEmpty) { List tempData; if (isHex!) { @@ -106,7 +119,7 @@ class PdfString implements IPdfPrimitive { for (int i = 0; i < data.length; i++) { tempData.add(data[i].toUnsigned(8)); } - } else if (utf8.encode(value!).length != value!.length) { + } else if (!isColorSpace && utf8.encode(value!).length != value!.length) { tempData = toUnicodeArray(value!, true); tempData = escapeSymbols(tempData); } else { @@ -114,6 +127,10 @@ class PdfString implements IPdfPrimitive { for (int i = 0; i < value!.length; i++) { tempData.add(value!.codeUnitAt(i).toUnsigned(8)); } + if (security == null || + !PdfSecurityHelper.getHelper(security).encryptor.encrypt) { + tempData = escapeSymbols(tempData); + } } bool hex = false; tempData = _encryptIfNeeded(tempData, document); @@ -132,9 +149,11 @@ class PdfString implements IPdfPrimitive { } result.addAll(tempData); } - result.add(isHex! - ? PdfString.hexStringMark.codeUnitAt(1) - : PdfString.stringMark.codeUnitAt(1)); + result.add( + isHex! + ? PdfString.hexStringMark.codeUnitAt(1) + : PdfString.stringMark.codeUnitAt(1), + ); return result; } @@ -151,12 +170,18 @@ class PdfString implements IPdfPrimitive { /// internal method 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; + return data + .map((int byte) => byte.toRadixString(16).padLeft(2, '0')) + .join() + .toUpperCase(); + } + + /// internal method + static Future bytesToHexAsync(List data) async { + return data + .map((int byte) => byte.toRadixString(16).padLeft(2, '0')) + .join() + .toUpperCase(); } /// internal method @@ -322,13 +347,9 @@ class PdfString implements IPdfPrimitive { @override void dispose() { - if (data != null) { - data!.clear(); - data = null; - } - if (_status != null) { - _status = null; - } + data = data?.map((_) => 0).toList(); + data = null; + _status = null; } @override @@ -350,15 +371,16 @@ class PdfString implements IPdfPrimitive { final PdfSecurity? security = (document == null) ? null : document.security; if (security == null || (!PdfSecurityHelper.getHelper(security).encryptor.encrypt || - PdfSecurityHelper.getHelper(security) - .encryptor - .encryptAttachmentOnly!)) { + PdfSecurityHelper.getHelper( + security, + ).encryptor.encryptAttachmentOnly!)) { return data; } else { data = PdfSecurityHelper.getHelper(security).encryptor.encryptData( - PdfDocumentHelper.getHelper(document!).currentSavingObject!.objNum, - data, - true); + PdfDocumentHelper.getHelper(document!).currentSavingObject!.objNum, + data, + true, + ); } return escapeSymbols(data); } @@ -374,9 +396,18 @@ class PdfString implements IPdfPrimitive { !encryptor.encryptAttachmentOnly!) { decrypted = true; value = byteToString(data!); - final List bytes = - encryptor.encryptData(currentObjectNumber, data!, false); + final List bytes = encryptor.encryptData( + currentObjectNumber, + data!, + false, + ); value = byteToString(bytes); + const String bigEndianPreambleString = 'þÿ'; + if (value!.length > 1 && + !isColorSpace && + value!.startsWith(bigEndianPreambleString)) { + value = decodeBigEndian(bytes, 2, bytes.length - 2); + } data = bytes; } } @@ -391,5 +422,5 @@ enum ForceEncoding { ascii, /// internal enumerator - unicode + unicode, } 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 index 576c1357d..9b6564ce8 100644 --- 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 @@ -44,14 +44,14 @@ class IAsn1Collection implements IAsn1 { /// internal class abstract class Asn1 extends Asn1Encode { /// internal constructor - Asn1([List<_Asn1UniversalTags>? tag]) { + Asn1([List? tag]) { if (tag != null) { _tag = tag; } } //Fields - late List<_Asn1UniversalTags> _tag; + late List _tag; /// internal field List? bytes; @@ -98,7 +98,9 @@ abstract class Asn1 extends Asn1Encode { this.bytes!.add(getTagValue(_tag)); write(bytes.length); this.bytes!.addAll(bytes); - return this.bytes; + final List result = this.bytes!.toList(); + this.bytes!.clear(); + return result; } /// internal method @@ -130,104 +132,104 @@ abstract class Asn1 extends Asn1Encode { } /// internal method - int getTagValue(List<_Asn1UniversalTags> tags) { + int getTagValue(List tags) { int value = 0; - for (final _Asn1UniversalTags tag in tags) { + for (final Asn1UniversalTags tag in tags) { switch (tag) { - case _Asn1UniversalTags.reservedBER: + case Asn1UniversalTags.reservedBER: value |= 0; break; - case _Asn1UniversalTags.boolean: + case Asn1UniversalTags.boolean: value |= 1; break; - case _Asn1UniversalTags.integer: + case Asn1UniversalTags.integer: value |= 2; break; - case _Asn1UniversalTags.bitString: + case Asn1UniversalTags.bitString: value |= 3; break; - case _Asn1UniversalTags.octetString: + case Asn1UniversalTags.octetString: value |= 4; break; - case _Asn1UniversalTags.nullValue: + case Asn1UniversalTags.nullValue: value |= 5; break; - case _Asn1UniversalTags.objectIdentifier: + case Asn1UniversalTags.objectIdentifier: value |= 6; break; - case _Asn1UniversalTags.objectDescriptor: + case Asn1UniversalTags.objectDescriptor: value |= 7; break; - case _Asn1UniversalTags.externalValue: + case Asn1UniversalTags.externalValue: value |= 8; break; - case _Asn1UniversalTags.real: + case Asn1UniversalTags.real: value |= 9; break; - case _Asn1UniversalTags.enumerated: + case Asn1UniversalTags.enumerated: value |= 10; break; - case _Asn1UniversalTags.embeddedPDV: + case Asn1UniversalTags.embeddedPDV: value |= 11; break; - case _Asn1UniversalTags.utf8String: + case Asn1UniversalTags.utf8String: value |= 12; break; - case _Asn1UniversalTags.relativeOid: + case Asn1UniversalTags.relativeOid: value |= 13; break; - case _Asn1UniversalTags.sequence: + case Asn1UniversalTags.sequence: value |= 16; break; - case _Asn1UniversalTags.setValue: + case Asn1UniversalTags.setValue: value |= 17; break; - case _Asn1UniversalTags.numericString: + case Asn1UniversalTags.numericString: value |= 18; break; - case _Asn1UniversalTags.printableString: + case Asn1UniversalTags.printableString: value |= 19; break; - case _Asn1UniversalTags.teletexString: + case Asn1UniversalTags.teletexString: value |= 20; break; - case _Asn1UniversalTags.videotexString: + case Asn1UniversalTags.videotexString: value |= 21; break; - case _Asn1UniversalTags.ia5String: + case Asn1UniversalTags.ia5String: value |= 22; break; - case _Asn1UniversalTags.utfTime: + case Asn1UniversalTags.utfTime: value |= 23; break; - case _Asn1UniversalTags.generalizedTime: + case Asn1UniversalTags.generalizedTime: value |= 24; break; - case _Asn1UniversalTags.graphicsString: + case Asn1UniversalTags.graphicsString: value |= 25; break; - case _Asn1UniversalTags.visibleString: + case Asn1UniversalTags.visibleString: value |= 26; break; - case _Asn1UniversalTags.generalString: + case Asn1UniversalTags.generalString: value |= 27; break; - case _Asn1UniversalTags.universalString: + case Asn1UniversalTags.universalString: value |= 28; break; - case _Asn1UniversalTags.characterString: + case Asn1UniversalTags.characterString: value |= 29; break; - case _Asn1UniversalTags.bmpString: + case Asn1UniversalTags.bmpString: value |= 30; break; - case _Asn1UniversalTags.constructed: + case Asn1UniversalTags.constructed: value |= 32; break; - case _Asn1UniversalTags.application: + case Asn1UniversalTags.application: value |= 64; break; - case _Asn1UniversalTags.tagged: + case Asn1UniversalTags.tagged: value |= 128; break; } @@ -295,6 +297,46 @@ abstract class Asn1 extends Asn1Encode { bs[off + 2].toUnsigned(8) << 8 | bs[off + 3].toUnsigned(8); } + + /// internal method + static int beToUInt64(List bs, int off) { + final int hi = beToUInt32(bs, off); + final int lo = beToUInt32(bs, off + 4); + return (hi.toUnsigned(64) << 32) | lo.toUnsigned(64); + } + + /// internal method + static void uInt64ToBe(int n, List bs, int off) { + uInt32ToBe((n >> 32).toUnsigned(32), bs, off); + uInt32ToBe(n.toUnsigned(32), bs, off + 4); + } + + /// internal method + static BigInt leToUInt32(List bs, int off) { + return BigInt.from( + bs[off].toUnsigned(32) | + bs[off + 1].toUnsigned(32) << 8 | + bs[off + 2].toUnsigned(32) << 16 | + bs[off + 3].toUnsigned(32) << 24, + ); + } + + /// internal method + static void uInt32ToLe(BigInt n, List bs, int off) { + bs[off] = n.toUnsigned(8).toInt(); + bs[off + 1] = (n >> 8).toUnsigned(8).toInt(); + bs[off + 2] = (n >> 16).toUnsigned(8).toInt(); + bs[off + 3] = (n >> 24).toUnsigned(8).toInt(); + } + + /// internal method + static Asn1 fromByteArray(List data) { + try { + return Asn1Stream(PdfStreamReader(data)).readAsn1()!; + } catch (e) { + throw Exception('Invalid entry'); + } + } } /// internal class @@ -316,6 +358,19 @@ abstract class Asn1Encode implements IAsn1 { } } + /// internal method + Future?> getEncodedAsync([String? encoding]) async { + if (encoding == null) { + return (Asn1DerStream([])..writeObject(this)).stream; + } else { + if (encoding == Asn1.der) { + final DerStream stream = DerStream([])..writeObject(this); + return stream.stream; + } + return getEncoded(); + } + } + /// internal method List? getDerEncoded() { return getEncoded(Asn1.der); @@ -367,7 +422,7 @@ class Asn1EncodeCollection { class Asn1Octet extends Asn1 implements IAsn1Octet { /// internal constructor Asn1Octet(this.value) - : super(<_Asn1UniversalTags>[_Asn1UniversalTags.octetString]); + : super([Asn1UniversalTags.octetString]); /// internal constructor Asn1Octet.fromObject(Asn1Encode obj) { @@ -447,7 +502,7 @@ class Asn1Octet extends Asn1 implements IAsn1Octet { /// internal class abstract class Asn1Null extends Asn1 { /// internal constructor - Asn1Null() : super(<_Asn1UniversalTags>[_Asn1UniversalTags.nullValue]); + Asn1Null() : super([Asn1UniversalTags.nullValue]); //Implementation /// internal method List toArray() { @@ -475,15 +530,16 @@ class Asn1Sequence extends Asn1 { //Constructor /// internal constructor Asn1Sequence() - : super(<_Asn1UniversalTags>[ - _Asn1UniversalTags.sequence, - _Asn1UniversalTags.constructed - ]) { + : super([ + Asn1UniversalTags.sequence, + Asn1UniversalTags.constructed, + ]) { objects = []; } //Fields /// internal field List? objects; + Asn1SequenceHelper? _parser; /// internal field int get count { @@ -492,7 +548,7 @@ class Asn1Sequence extends Asn1 { /// internal property IAsn1Collection get parser { - return Asn1SequenceHelper(this); + return _parser ??= Asn1SequenceHelper(this); } /// internal property @@ -517,7 +573,8 @@ class Asn1Sequence extends Asn1 { result = Asn1Sequence.getSequence(obj.getAsn1()); } else if (obj is List) { result = Asn1Sequence.getSequence( - Asn1Stream(PdfStreamReader(obj)).readAsn1()); + Asn1Stream(PdfStreamReader(obj)).readAsn1(), + ); } else if (obj is Asn1Encode) { final Asn1? primitive = obj.getAsn1(); if (primitive != null && primitive is Asn1Sequence) { @@ -531,7 +588,10 @@ class Asn1Sequence extends Asn1 { if (explicitly) { if (!obj.explicit!) { throw ArgumentError.value( - explicitly, 'explicitly', 'Invalid entry in sequence'); + explicitly, + 'explicitly', + 'Invalid entry in sequence', + ); } result = inner as Asn1Sequence?; } else if (obj.explicit!) { @@ -617,7 +677,13 @@ class Asn1Sequence extends Asn1 { final List stream = []; for (final dynamic obj in objects!) { List? buffer; - if (obj is Asn1Null) { + if (obj is Asn1Integer) { + buffer = obj.asnEncode(); + } else if (obj is Asn1Boolean) { + buffer = obj.asnEncode(); + } else if (obj is Asn1Identifier) { + buffer = obj.asnEncode(); + } else if (obj is Asn1Null) { buffer = obj.asnEncode(); } else if (obj is Asn1Octet) { buffer = obj.asnEncode(); @@ -665,8 +731,10 @@ class Asn1SequenceCollection extends Asn1Encode { Asn1Set? attributes; @override Asn1 getAsn1() { - final Asn1EncodeCollection collection = - Asn1EncodeCollection([id, DerTag(0, value)]); + final Asn1EncodeCollection collection = Asn1EncodeCollection([ + id, + DerTag(0, value), + ]); if (attributes != null) { collection.encodableObjects.add(attributes); } @@ -712,9 +780,10 @@ class Asn1SequenceHelper implements IAsn1Collection { class Asn1Set extends Asn1 { /// internal constructor Asn1Set([int? capacity]) { - objects = capacity != null - ? List.generate(capacity, (dynamic i) => null) - : []; + objects = + capacity != null + ? List.generate(capacity, (dynamic i) => null) + : []; } //Fields /// internal field @@ -838,8 +907,9 @@ class Asn1Set extends Asn1 { } else if (obj is IAsn1SetHelper) { result = Asn1Set.getAsn1Set(obj.getAsn1()); } else if (obj is List) { - result = - Asn1Set.getAsn1Set(Asn1Stream(PdfStreamReader(obj)).readAsn1()); + result = Asn1Set.getAsn1Set( + Asn1Stream(PdfStreamReader(obj)).readAsn1(), + ); } else if (obj is Asn1Encode) { final Asn1? asn1 = obj.getAsn1(); if (asn1 != null && asn1 is Asn1Set) { @@ -862,9 +932,9 @@ class Asn1Set extends Asn1 { } else if (inner is Asn1Sequence) { final Asn1EncodeCollection collection = Asn1EncodeCollection(); // ignore: avoid_function_literals_in_foreach_calls - inner.objects! - .toList() - .forEach((dynamic entry) => collection.encodableObjects.add(entry)); + inner.objects!.toList().forEach( + (dynamic entry) => collection.encodableObjects.add(entry), + ); result = DerSet(collection: collection, isSort: false); } else { throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); @@ -1000,7 +1070,10 @@ class Asn1Tag extends Asn1 implements IAsn1Tag { return getObject(); } throw ArgumentError.value( - tagNumber, 'tagNumber', 'Implicit tagging is not supported'); + tagNumber, + 'tagNumber', + 'Implicit tagging is not supported', + ); } } @@ -1016,6 +1089,156 @@ class Asn1DerStream extends DerStream { } } +/// internal class +class Asn1Integer extends Asn1 { + /// internal constructor + Asn1Integer(this._value) + : super([Asn1UniversalTags.integer]); + + /// internal field + late final int _value; + + /// internal property + List _toArray() { + return _value < 255 ? [_value] : _getBytesFromLong(_value); + } + + /// internal method + List? asnEncode() { + return super.asn1Encode(_toArray()); + } + + @override + void encode(DerStream derOut) { + derOut.writeEncoded( + getTagValue([Asn1UniversalTags.integer]), + null, + ); + } + + List _getBytesFromLong(int value) { + final List bytes = []; + for (int i = 0; i < 8; i++) { + bytes.add((value >> (i * 8)) & 0xFF); + } + return bytes; + } +} + +/// internal class +class Asn1Boolean extends Asn1 { + /// internal constructor + Asn1Boolean(this._value) + : super([Asn1UniversalTags.boolean]); + + /// internal field + late final bool _value; + + /// internal method + List _toArray() { + return _value ? [0xff] : [0]; + } + + /// internal method + List? asnEncode() { + return super.asn1Encode(_toArray()); + } + + @override + void encode(DerStream derOut) { + derOut.writeEncoded( + getTagValue([Asn1UniversalTags.boolean]), + _toArray(), + ); + } +} + +/// internal class +class Asn1Identifier extends Asn1 { + /// internal constructor + Asn1Identifier(this._id) + : super([Asn1UniversalTags.objectIdentifier]); + + /// internal field + late final String _id; + + /// internal method + List _toArray() { + final List parts = _id.split('.'); + final int firstPart = int.parse(parts[0]); + final int secondPart = int.parse(parts[1]); + final List bytes = []; + _appendField(firstPart * 40 + secondPart, bytes); + for (int i = 2; i < parts.length; i++) { + final String part = parts[i]; + if (part.length < 18) { + _appendField(int.parse(part), bytes); + } else { + _appendFieldFromString(part, bytes); + } + } + return bytes; + } + + void _appendField(int value, List bytes) { + if (value >= (1 << 7)) { + if (value >= (1 << 14)) { + if (value >= (1 << 21)) { + if (value >= (1 << 28)) { + if (value >= (1 << 35)) { + if (value >= (1 << 42)) { + if (value >= (1 << 49)) { + if (value >= (1 << 56)) { + bytes.add(((value >> 56) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 49) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 42) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 35) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 28) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 21) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 14) | 0x80).toUnsigned(8)); + } + bytes.add(((value >> 7) | 0x80).toUnsigned(8)); + } + bytes.add((value & 0x7f).toUnsigned(8)); + } + + void _appendFieldFromString(String value, List bytes) { + int byteCount; + byteCount = ((utf8.encode(value).length) + 6) ~/ 7; + if (byteCount == 0) { + bytes.add(0); + } else { + int tmpValue = int.parse(value); + final List tmp = List.filled(byteCount, 0); + for (int i = byteCount - 1; i >= 0; i--) { + tmp[i] = (tmpValue & 0x7F) | 0x80; + tmpValue = tmpValue >> 7; + } + tmp[byteCount - 1] &= 0x7F; + bytes.addAll(tmp); + } + } + + /// internal method + List? asnEncode() { + return super.asn1Encode(_toArray()); + } + + @override + void encode(DerStream derOut) { + derOut.writeEncoded( + getTagValue([Asn1UniversalTags.objectIdentifier]), + _toArray(), + ); + } +} + /// internal class class GeneralizedTime extends Asn1 { /// internal constructor @@ -1107,7 +1330,7 @@ class Asn1Tags { static const int tagged = 0x80; } -enum _Asn1UniversalTags { +enum Asn1UniversalTags { reservedBER, boolean, integer, @@ -1139,5 +1362,5 @@ enum _Asn1UniversalTags { bmpString, constructed, application, - tagged + tagged, } 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 index 5ecb55e37..b5d3c707a 100644 --- 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 @@ -39,17 +39,26 @@ class Asn1Parser { } else { switch (tagNumber) { case Asn1Tags.setTag: - throw ArgumentError.value(tagNumber, 'tagNumber', - 'Constructed encoding is not used in the set'); + 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'); + 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'); + tagNumber, + 'tagNumber', + 'Implicit tagging is not supported', + ); } /// internal method @@ -78,7 +87,10 @@ class Asn1Parser { return BerSequenceHelper(this); default: throw ArgumentError.value( - tagValue, 'tagValue', 'Invalid entry in sequence'); + tagValue, + 'tagValue', + 'Invalid entry in sequence', + ); } } @@ -115,26 +127,33 @@ class Asn1Parser { } final Asn1LengthStream stream = Asn1LengthStream(_stream, _limit); final Asn1Parser helper = Asn1Parser(stream, _limit); - if ((tag & Asn1Tags.tagged) != 0) + 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))); + return BerTagHelper( + isConstructed, + tagNumber, + Asn1Parser(stream, Asn1Stream.getLimit(stream)), + ); } if (isConstructed) { switch (tagNumber) { case Asn1Tags.octetString: return BerOctetHelper( - Asn1Parser(stream, Asn1Stream.getLimit(stream))); + Asn1Parser(stream, Asn1Stream.getLimit(stream)), + ); case Asn1Tags.sequence: return DerSequenceHelper( - Asn1Parser(stream, Asn1Stream.getLimit(stream))); + Asn1Parser(stream, Asn1Stream.getLimit(stream)), + ); case Asn1Tags.setTag: return DerSetHelper( - Asn1Parser(stream, Asn1Stream.getLimit(stream))); + Asn1Parser(stream, Asn1Stream.getLimit(stream)), + ); default: return null; } 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 index ea94693c3..4e53adb17 100644 --- 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 @@ -30,7 +30,10 @@ class Asn1Stream { /// internal method static Asn1? getPrimitiveObject( - int tagNumber, Asn1StreamHelper stream, List>? buffers) { + int tagNumber, + Asn1StreamHelper stream, + List>? buffers, + ) { switch (tagNumber) { case Asn1Tags.boolean: return DerBoolean.fromBytes(getBytes(stream, buffers!)); @@ -139,13 +142,15 @@ class Asn1Stream { Asn1? buildObject(int tag, int tagNumber, int length) { final bool isConstructed = (tag & Asn1Tags.constructed) != 0; final Asn1StreamHelper stream = Asn1StreamHelper(_stream, length); - if ((tag & Asn1Tags.tagged) != 0) + if ((tag & Asn1Tags.tagged) != 0) { return Asn1Parser(stream).readTaggedObject(isConstructed, tagNumber); + } if (isConstructed) { switch (tagNumber) { case Asn1Tags.octetString: - return BerOctet(getBytesfromAsn1EncodeCollection( - getDerEncodableCollection(stream))); + return BerOctet( + getBytesfromAsn1EncodeCollection(getDerEncodableCollection(stream)), + ); case Asn1Tags.sequence: return createDerSequence(stream); case Asn1Tags.setTag: @@ -175,10 +180,15 @@ class Asn1Stream { if (length < 0) { if (!isConstructed) { throw ArgumentError.value( - length, 'length', 'Encodeing length is invalid'); + length, + 'length', + 'Encodeing length is invalid', + ); } - final Asn1Parser sp = - Asn1Parser(Asn1LengthStream(_stream, _limit), _limit); + final Asn1Parser sp = Asn1Parser( + Asn1LengthStream(_stream, _limit), + _limit, + ); if ((tag & Asn1Tags.tagged) != 0) { return BerTagHelper(true, tagNumber, sp).getAsn1(); } @@ -189,7 +199,10 @@ class Asn1Stream { return BerSequenceHelper(sp).getAsn1(); default: throw ArgumentError.value( - tagNumber, 'tag', 'Invalid object in the sequence'); + tagNumber, + 'tag', + 'Invalid object in the sequence', + ); } } else { return buildObject(tag, tagNumber, length); @@ -292,7 +305,7 @@ class Asn1BaseStream extends PdfStreamReader { class Asn1StreamHelper extends Asn1BaseStream { /// internal constructor Asn1StreamHelper(PdfStreamReader? stream, int length) - : super(stream, length) { + : super(stream, length) { if (length < 0) { throw Exception('Invalid length specified.'); } @@ -379,7 +392,7 @@ class Asn1StreamHelper extends Asn1BaseStream { /// internal class class Asn1LengthStream extends Asn1BaseStream { /// internal constructor - Asn1LengthStream(PdfStreamReader? stream, int? limit) : super(stream, limit) { + Asn1LengthStream(super.stream, super.limit) { byte = requireByte(); checkEndOfFile(); } 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 index b7043787c..98071e0a3 100644 --- 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 @@ -8,11 +8,11 @@ import 'der.dart'; /// internal class class BerOctet extends DerOctet { /// internal constructor - BerOctet(List bytes) : super(bytes); + BerOctet(super.bytes); /// internal constructor BerOctet.fromCollection(List octets) - : super(BerOctet.getBytes(octets)) { + : super(BerOctet.getBytes(octets)) { _octets = octets; } @@ -30,10 +30,15 @@ class BerOctet extends DerOctet { final List collection = []; for (int i = 0; i < value!.length; i += 1000) { final int endIndex = min(value!.length, i + 1000); - collection.add(DerOctet(List.generate( - endIndex - i, - (int index) => - ((i + index) < value!.length) ? value![i + index] : 0))); + collection.add( + DerOctet( + List.generate( + endIndex - i, + (int index) => + ((i + index) < value!.length) ? value![i + index] : 0, + ), + ), + ); } return collection; } @@ -144,7 +149,10 @@ class BerTagHelper implements IAsn1Tag { if (isExplicit) { if (!_isConstructed!) { throw ArgumentError.value( - isExplicit, 'isExplicit', 'Implicit tags identified'); + isExplicit, + 'isExplicit', + 'Implicit tags identified', + ); } return _helper.readObject(); } @@ -160,11 +168,10 @@ class BerTagHelper implements IAsn1Tag { /// internal class class BerSequence extends DerSequence { /// internal constructor - BerSequence({List? array, Asn1EncodeCollection? collection}) - : super(array: array, collection: collection); + BerSequence({List? super.array, super.collection}); /// internal constructor - BerSequence.fromObject(Asn1Encode? object) : super.fromObject(object); + BerSequence.fromObject(super.object) : super.fromObject(); /// internal constructor static BerSequence empty = BerSequence(); 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 index d245e40c7..f9900a48a 100644 --- 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 @@ -136,6 +136,28 @@ class DerAsciiString extends DerString { } return true; } + + /// internal method + static DerAsciiString? getAsciiStringFromObj(dynamic obj) { + if (obj == null || obj is DerAsciiString) { + return obj; + } + throw Exception('Invalid entry'); + } + + /// internal method + static DerAsciiString? getAsciiString(Asn1Tag tag, bool isExplicit) { + final Asn1? asn1 = tag.getObject(); + if (asn1 != null) { + if (isExplicit || asn1 is DerAsciiString) { + return getAsciiStringFromObj(asn1); + } + } + if (asn1 is Asn1Octet) { + return DerAsciiString.fromBytes(asn1.getOctets()!); + } + return null; + } } /// internal class @@ -159,7 +181,7 @@ class DerBitString extends DerString { 'C', 'D', 'E', - 'F' + 'F', ]; } @@ -185,8 +207,10 @@ class DerBitString extends DerString { @override // ignore: avoid_renaming_method_parameters void encode(DerStream stream) { - final List bytes = - List.generate(getBytes()!.length + 1, (int i) => 0); + final List bytes = List.generate( + getBytes()!.length + 1, + (int i) => 0, + ); bytes[0] = extra!; List.copyRange(bytes, 1, getBytes()!, 0, bytes.length - 1); stream.writeEncoded(Asn1Tags.bitString, bytes); @@ -255,8 +279,9 @@ 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)); + result += String.fromCharCode( + (bytes[2 * i] << 8) | (bytes[2 * i + 1] & 0xff), + ); } _value = result; } @@ -287,8 +312,10 @@ class DerBmpString extends DerString { @override // ignore: avoid_renaming_method_parameters void encode(DerStream stream) { - final List bytes = - List.generate(_value!.length * 2, (int i) => 0); + final List bytes = List.generate( + _value!.length * 2, + (int 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); @@ -464,10 +491,6 @@ class DerNull extends Asn1Null { bytes = []; } //Fields - /// internal field - @override - List? bytes; - /// internal field static DerNull value = DerNull(); //Implementation @@ -506,15 +529,22 @@ class DerObjectID extends Asn1 { bytes = Asn1.clone(bytes); } + /// internal constructor + DerObjectID.fromBranch(DerObjectID id, String branchId) { + if (!isValidBranchID(branchId, 0)) { + throw ArgumentError.value(id, 'id', 'Invalid ID'); + } + this.id = '${id.id!}.$branchId'; + } + /// internal field String? id; - /// internal field - @override - List? bytes; // ignore: prefer_final_fields - static List _objects = - List.generate(1024, (int i) => null); + static List _objects = List.generate( + 1024, + (int i) => null, + ); //Implemnetation /// internal method List? getBytes() { @@ -532,8 +562,10 @@ class DerObjectID extends Asn1 { if (token.length <= 18) { stream = writeField(stream, fieldValue: first + int.parse(token)); } else { - stream = writeField(stream, - numberValue: BigInt.parse(token) + BigInt.from(first)); + stream = writeField( + stream, + numberValue: BigInt.parse(token) + BigInt.from(first), + ); } while (oidToken.hasMoreTokens) { token = oidToken.nextToken()!; @@ -547,8 +579,11 @@ class DerObjectID extends Asn1 { } /// internal method - List writeField(List stream, - {int? fieldValue, BigInt? numberValue}) { + List writeField( + List stream, { + int? fieldValue, + BigInt? numberValue, + }) { if (fieldValue != null) { final List result = []; result.add((fieldValue & 0x7f).toUnsigned(8)); @@ -709,15 +744,20 @@ class DerObjectID extends Asn1 { _objects[first] = DerObjectID.fromBytes(bytes); return _objects[first]; } + + /// internal method + DerObjectID branch(String id) { + return DerObjectID.fromBranch(this, id); + } } /// internal class class DerOctet extends Asn1Octet { /// internal constructor - DerOctet(List bytes) : super(bytes); + DerOctet(List super.bytes); /// internal constructor - DerOctet.fromObject(Asn1Encode asn1) : super.fromObject(asn1); + DerOctet.fromObject(super.asn1) : super.fromObject(); @override void encode(DerStream stream) { stream.writeEncoded(Asn1Tags.octetString, value); @@ -728,7 +768,7 @@ class DerOctet extends Asn1Octet { class DerSequence extends Asn1Sequence { /// internal constructor DerSequence({List? array, Asn1EncodeCollection? collection}) - : super() { + : super() { if (array != null) { // ignore: prefer_foreach for (final Asn1Encode? entry in array) { @@ -760,7 +800,9 @@ class DerSequence extends Asn1Sequence { // ignore: avoid_function_literals_in_foreach_calls objects!.forEach((dynamic asn1) => stream.writeObject(asn1)); outputStream.writeEncoded( - Asn1Tags.sequence | Asn1Tags.constructed, stream.stream); + Asn1Tags.sequence | Asn1Tags.constructed, + stream.stream, + ); } } @@ -788,11 +830,11 @@ class DerSequenceHelper implements IAsn1Collection { class DerSet extends Asn1Set { //Constructor /// internal constructor - DerSet( - {List? array, - Asn1EncodeCollection? collection, - bool? isSort}) - : super() { + DerSet({ + List? array, + Asn1EncodeCollection? collection, + bool? isSort, + }) : super() { if (array != null) { // ignore: avoid_function_literals_in_foreach_calls array.forEach((Asn1Encode? asn1) => addObject(asn1)); @@ -815,7 +857,9 @@ class DerSet extends Asn1Set { // ignore: avoid_function_literals_in_foreach_calls objects.forEach((dynamic entry) => stream.writeObject(entry)); outputStream.writeEncoded( - Asn1Tags.setTag | Asn1Tags.constructed, stream.stream); + Asn1Tags.setTag | Asn1Tags.constructed, + stream.stream, + ); } } @@ -919,8 +963,7 @@ class DerStream { /// internal class class DerTag extends Asn1Tag { /// internal constructor - DerTag(int? tagNumber, Asn1Encode? asn1, [bool? isExplicit]) - : super(tagNumber, asn1) { + DerTag(super.tagNumber, super.asn1, [bool? isExplicit]) { if (isExplicit != null) { explicit = isExplicit; } @@ -931,7 +974,10 @@ class DerTag extends Asn1Tag { final List? bytes = object!.getDerEncoded(); if (explicit!) { stream.writeEncoded( - tagNumber, bytes, Asn1Tags.constructed | Asn1Tags.tagged); + tagNumber, + bytes, + Asn1Tags.constructed | Asn1Tags.tagged, + ); } else { final int flag = (bytes![0] & Asn1Tags.constructed) | Asn1Tags.tagged; stream.writeTag(flag, tagNumber!); @@ -994,12 +1040,10 @@ class DerUtcTime extends Asn1 { /// internal class class DerCatalogue extends Asn1 { /// internal constructor - DerCatalogue(this.bytes); - //Fields + DerCatalogue([List? bytes]) { + this.bytes = bytes; + } - /// internal field - @override - List? bytes; //Implemnetation @override // ignore: avoid_renaming_method_parameters 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 bdc7f212f..346b5623e 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 @@ -1,33 +1,57 @@ +import 'dart:typed_data'; + import 'aes_engine.dart'; import 'buffered_block_padding_base.dart'; import 'cipher_block_chaining_mode.dart'; +import 'ipadding.dart'; /// internal class class AesCipher { //Constructor /// internal constructor AesCipher(bool isEncryption, List key, List iv) { - _bp = BufferedCipher(CipherBlockChainingMode(AesEngine())); - final InvalidParameter ip = InvalidParameter(KeyParameter(key), iv); - _bp.initialize(isEncryption, ip); + cipher = PaddedCipherMode( + Pkcs7Padding(), + CipherBlockChainingMode(AesEngine()), + ); + final params = + BlockCipherPaddedParameters( + InvalidParameter( + KeyParameter(Uint8List.fromList(key)), + Uint8List.fromList(iv), + ), + null, + ); + cipher.initialize(isEncryption, params); } //Fields - late BufferedCipher _bp; + late PaddedCipherMode cipher; //Implementation /// internal method 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 blockSize = cipher.blockSize; + if (input.length % blockSize != 0) { + throw ArgumentError.value( + 'Data length is not a multiple of block size: ${input.length}', + ); + } + + final output = Uint8List(input.length); + int inOffset = 0; + int outOffset = 0; + + while (inOffset < input.length) { + cipher.processingBlock( + Uint8List.fromList(input), + inOffset, + output, + outOffset, + ); + inOffset += blockSize; + outOffset += blockSize; } - final Map result = - _bp.processBytes(input, inputOffset, inputLength, output, 0); - output = result['output'] as List?; return output; } } @@ -36,9 +60,9 @@ class AesCipher { class AesCipherNoPadding { //Constructor /// internal constructor - AesCipherNoPadding(bool isEncryption, List key) { + AesCipherNoPadding(bool isEncryption, KeyParameter parameters) { _cbc = CipherBlockChainingMode(AesEngine()); - _cbc.initialize(isEncryption, KeyParameter(key)); + _cbc.initialize(isEncryption, parameters); } //Fields @@ -46,1099 +70,24 @@ class AesCipherNoPadding { //Implementation /// internal method - 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); - int tempOffset = 0; - while (length > 0) { - final Map result = - _cbc.processBlock(input, offset, output, tempOffset); - output = result['output'] as List?; - length -= _cbc.blockSize!; - tempOffset += _cbc.blockSize!; - offset += _cbc.blockSize!; - } - return output; - } -} - -/// internal class -class AesEncryptor { - /// internal constructor - AesEncryptor(List key, List iv, bool isEncryption) { - 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); - if (isEncryption) { - _ivOff = _buf.length; - } - _isEncryption = isEncryption; - } - - //Fields - int? _blockSize; - late Aes _aes; - late bool _isEncryption; - late List _buf; - List? _cbcV; - int? _ivOff; - List? _nextBlockVector; - - //Implementation - /// internal method - 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); - } - - /// internal method - int getBlockSize(int length) { - final int total = length + _ivOff!; - final int leftOver = total % _buf.length; - return total - (leftOver == 0 ? _buf.length : leftOver); - } - - /// internal method - void processBytes( - List input, int inOff, int length, List output, int outOff) { - if (length < 0) { - throw ArgumentError.value(length, 'length cannot be negative'); - } - int resultLen = 0; - final int bytesLeft = _buf.length - _ivOff!; - if (length > bytesLeft) { - 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!; - } - } - List.copyRange(_buf, _ivOff!, input, inOff, inOff + length); - _ivOff = _ivOff! + length; - } - - /// internal method - int processBlock(List input, int inOff, List outBytes, int outOff) { - int length = 0; - 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]; - } - length = _aes._cipher(_cbcV, outBytes, outOff); - List.copyRange(_cbcV!, 0, outBytes, outOff, outOff + _cbcV!.length); - } else { - 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]; - } - final List? tmp = _cbcV; - _cbcV = _nextBlockVector; - _nextBlockVector = tmp; - } - return length; - } - - /// internal method - int calculateOutputSize() { - final int total = _ivOff!; - final int leftOver = total % _buf.length; - return leftOver == 0 - ? (_isEncryption ? (total + _buf.length) : total) - : (total - leftOver + _buf.length); - } - - /// internal method - int finalize(List output) { - int resultLen = 0; - const int outOff = 0; - if (_isEncryption) { - if (_ivOff == _blockSize) { - resultLen = processBlock(_buf, 0, output, outOff); - _ivOff = 0; - } - _ivOff = _addPadding(_buf, _ivOff!); - resultLen += processBlock(_buf, 0, output, outOff + resultLen); - } else { - if (_ivOff == _blockSize) { - resultLen = processBlock(_buf, 0, output, 0); - _ivOff = 0; - } - resultLen -= _checkPadding(output); + Uint8List process(Uint8List data) { + final blockSize = _cbc.blockSize!; + if (data.length % blockSize != 0) { + throw ArgumentError.value( + 'Data length is not a multiple of block size: ${data.length}', + ); } - return resultLen; - } - - int _addPadding(List input, int offset) { - final int data = (input.length - offset).toUnsigned(8); - while (offset < input.length) { - input[offset] = data; - offset++; - } - return offset; - } - int _checkPadding(List input) { - int count = input[input.length - 1] & 0xff; - for (int i = 1; i <= count; i++) { - if (input[input.length - i] != count) { - count = 0; - } - } - return count; - } -} + final output = Uint8List(data.length); + int inOffset = 0; + int outOffset = 0; -/// internal class -class Aes { - /// internal constructor - Aes(_KeySize keySize, List keyBytes) { - _keySize = keySize; - nb = 4; - if (_keySize == _KeySize.bits128) { - nk = 4; - nr = 10; - } else if (_keySize == _KeySize.bits192) { - nk = 6; - nr = 12; - } else if (_keySize == _KeySize.bits256) { - nk = 8; - nr = 14; + while (inOffset < data.length) { + _cbc.processingBlock(data, inOffset, output, outOffset); + inOffset += blockSize; + outOffset += blockSize; } - key = List.filled(nk * 4, 0, growable: true); - List.copyRange(key, 0, keyBytes, 0, key.length); - initialize(); - } - - //Fields - _KeySize? _keySize; - /// internal field - late int nb; - - /// internal field - late int nk; - - /// internal field - int? nr; - - /// internal field - late List key; - - /// internal field - late List> sBox; - - /// internal field - late List> iBox; - - /// internal field - late List> rCon; - - /// internal field - late List> keySheduleArray; - - /// internal field - late List> state; - - //Implemenation - /// internal method - void initialize() { - _buildSubstitutionBox(); - _buildInverseSubBox(); - _buildRoundConstants(); - _keyExpansion(); - } - - void _keyExpansion() { - keySheduleArray = List>.generate( - nb * (nr! + 1), (int i) => List.generate(4, (int j) => 0)); - for (int row = 0; row < nk; ++row) { - keySheduleArray[row][0] = key[4 * row]; - keySheduleArray[row][1] = key[(4 * row) + 1]; - keySheduleArray[row][2] = key[(4 * row) + 2]; - keySheduleArray[row][3] = key[(4 * row) + 3]; - } - List temp = List.filled(4, 0, growable: true); - 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]; - temp[3] = keySheduleArray[row - 1][3]; - if (row % nk == 0) { - temp = _subWord(_rotWord(temp)); - temp[0] = ((temp[0]).toSigned(32) ^ (rCon[row ~/ nk][0]).toSigned(32)) - .toUnsigned(8); - temp[1] = ((temp[1]).toSigned(32) ^ (rCon[row ~/ nk][1]).toSigned(32)) - .toUnsigned(8); - temp[2] = ((temp[2]).toSigned(32) ^ (rCon[row ~/ nk][2]).toSigned(32)) - .toUnsigned(8); - temp[3] = ((temp[3]).toSigned(32) ^ (rCon[row ~/ nk][3]).toSigned(32)) - .toUnsigned(8); - } else if (nk > 6 && (row % nk == 4)) { - temp = _subWord(temp); - } - keySheduleArray[row][0] = - ((keySheduleArray[row - nk][0]).toSigned(32) ^ temp[0].toSigned(32)) - .toUnsigned(8); - keySheduleArray[row][1] = - ((keySheduleArray[row - nk][1]).toSigned(32) ^ temp[1].toSigned(32)) - .toUnsigned(8); - keySheduleArray[row][2] = - ((keySheduleArray[row - nk][2]).toSigned(32) ^ temp[2].toSigned(32)) - .toUnsigned(8); - keySheduleArray[row][3] = - ((keySheduleArray[row - nk][3]).toSigned(32) ^ temp[3].toSigned(32)) - .toUnsigned(8); - } - } - - List _subWord(List word) { - final List result = List.filled(4, 0, growable: true); - result[0] = sBox[word[0] >> 4][word[0] & 0x0f]; - result[1] = sBox[word[1] >> 4][word[1] & 0x0f]; - result[2] = sBox[word[2] >> 4][word[2] & 0x0f]; - result[3] = sBox[word[3] >> 4][word[3] & 0x0f]; - return result; - } - - List _rotWord(List word) { - final List result = List.filled(4, 0, growable: true); - result[0] = word[1]; - result[1] = word[2]; - result[2] = word[3]; - result[3] = word[0]; - return result; - } - - int _cipher(List? input, List output, int outOff) { - initialize(); - state = List>.generate( - 4, (int i) => List.generate(nb, (int j) => 0)); - for (int i = 0; i < (4 * nb); ++i) { - state[i % 4][i ~/ 4] = input![i]; - } - _addRoundKey(0); - for (int round = 1; round <= (nr! - 1); ++round) { - _subBytes(); - _shiftRows(); - _mixColumns(); - _addRoundKey(round); - } - _subBytes(); - _shiftRows(); - _addRoundKey(nr); - for (int i = 0; i < (4 * nb); ++i) { - output[outOff++] = state[i % 4][i ~/ 4]; - } - return 16; - } - - int _invCipher(List? input, List output, int outOff) { - state = List>.generate( - 4, (int i) => List.generate(nb, (int j) => 0)); - for (int i = 0; i < (4 * nb); ++i) { - state[i % 4][i ~/ 4] = input![i]; - } - _addRoundKey(nr); - for (int round = nr! - 1; round >= 1; --round) { - _invShiftRows(); - _invSubBytes(); - _addRoundKey(round); - _invMixColumns(); - } - _invShiftRows(); - _invSubBytes(); - _addRoundKey(0); - for (int i = 0; i < (4 * nb); ++i) { - output[outOff++] = state[i % 4][i ~/ 4]; - } - return 16; - } - - void _mixColumns() { - final List> temp = List>.generate( - 4, (int i) => List.generate(4, (int j) => 0)); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - temp[r][c] = state[r][c]; - } - } - for (int c = 0; c < 4; ++c) { - state[0][c] = (_gfmultby02(temp[0][c]).toSigned(32) ^ - _gfmultby03(temp[1][c]).toSigned(32) ^ - (temp[2][c]).toSigned(32) ^ - (temp[3][c]).toSigned(32)) - .toUnsigned(8); - state[1][c] = ((temp[0][c]).toSigned(32) ^ - _gfmultby02(temp[1][c]).toSigned(32) ^ - _gfmultby03(temp[2][c]).toSigned(32) ^ - (temp[3][c]).toSigned(32)) - .toUnsigned(8); - state[2][c] = ((temp[0][c]).toSigned(32) ^ - (temp[1][c]).toSigned(32) ^ - _gfmultby02(temp[2][c]).toSigned(32) ^ - _gfmultby03(temp[3][c]).toSigned(32)) - .toUnsigned(8); - state[3][c] = (_gfmultby03(temp[0][c]).toSigned(32) ^ - (temp[1][c]).toSigned(32) ^ - (temp[2][c]).toSigned(32) ^ - _gfmultby02(temp[3][c]).toSigned(32)) - .toUnsigned(8); - } - } - - void _invMixColumns() { - final List> temp = List>.generate( - 4, (int i) => List.generate(4, (int j) => 0)); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - temp[r][c] = state[r][c]; - } - } - for (int c = 0; c < 4; ++c) { - state[0][c] = (_gfmultby0e(temp[0][c]).toSigned(32) ^ - _gfmultby0b(temp[1][c]).toSigned(32) ^ - _gfmultby0d(temp[2][c]).toSigned(32) ^ - _gfmultby09(temp[3][c]).toSigned(32)) - .toUnsigned(8); - state[1][c] = (_gfmultby09(temp[0][c]).toSigned(32) ^ - _gfmultby0e(temp[1][c]).toSigned(32) ^ - _gfmultby0b(temp[2][c]).toSigned(32) ^ - _gfmultby0d(temp[3][c]).toSigned(32)) - .toUnsigned(8); - state[2][c] = (_gfmultby0d(temp[0][c]).toSigned(32) ^ - _gfmultby09(temp[1][c]).toSigned(32) ^ - _gfmultby0e(temp[2][c]).toSigned(32) ^ - _gfmultby0b(temp[3][c]).toSigned(32)) - .toUnsigned(8); - state[3][c] = (_gfmultby0b(temp[0][c]).toSigned(32) ^ - _gfmultby0d(temp[1][c]).toSigned(32) ^ - _gfmultby09(temp[2][c]).toSigned(32) ^ - _gfmultby0e(temp[3][c]).toSigned(32)) - .toUnsigned(8); - } - } - - int _gfmultby0d(int b) { - return (_gfmultby02(_gfmultby02(_gfmultby02(b))).toSigned(32) ^ - _gfmultby02(_gfmultby02(b)).toSigned(32) ^ - b.toSigned(32)) - .toUnsigned(8); - } - - int _gfmultby09(int b) { - return (_gfmultby02(_gfmultby02(_gfmultby02(b))).toSigned(32) ^ - b.toSigned(32)) - .toUnsigned(8); - } - - int _gfmultby0b(int b) { - return (_gfmultby02(_gfmultby02(_gfmultby02(b))).toSigned(32) ^ - _gfmultby02(b).toSigned(32) ^ - b.toSigned(32)) - .toUnsigned(8); - } - - int _gfmultby0e(int b) { - return (_gfmultby02(_gfmultby02(_gfmultby02(b))).toSigned(32) ^ - _gfmultby02(_gfmultby02(b)).toSigned(32) ^ - _gfmultby02(b).toSigned(32)) - .toUnsigned(8); - } - - int _gfmultby02(int b) { - return (b < 0x80 - ? (b << 1).toSigned(32) - : ((b << 1).toSigned(32) ^ 0x1b.toSigned(32))) - .toUnsigned(8); - } - - int _gfmultby03(int b) { - return (_gfmultby02(b).toSigned(32) ^ b.toSigned(32)).toUnsigned(8); - } - - void _shiftRows() { - final List> temp = List>.generate( - 4, (int i) => List.generate(4, (int j) => 0)); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - temp[r][c] = state[r][c]; - } - } - for (int r = 1; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - state[r][c] = temp[r][(c + r) % nb]; - } - } - } - - void _invShiftRows() { - final List> temp = List>.generate( - 4, (int i) => List.generate(4, (int j) => 0)); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - temp[r][c] = state[r][c]; - } - } - for (int r = 1; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - state[r][(c + r) % nb] = temp[r][c]; - } - } - } - - void _subBytes() { - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - state[r][c] = sBox[(state[r][c] >> 4)][(state[r][c] & 0x0f)]; - } - } - } - - void _invSubBytes() { - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - state[r][c] = iBox[state[r][c] >> 4][state[r][c] & 0x0f]; - } - } - } - - 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)) - .toUnsigned(8); - } - } - } - - void _buildRoundConstants() { - rCon = >[ - [0x00, 0x00, 0x00, 0x00], - [0x01, 0x00, 0x00, 0x00], - [0x02, 0x00, 0x00, 0x00], - [0x04, 0x00, 0x00, 0x00], - [0x08, 0x00, 0x00, 0x00], - [0x10, 0x00, 0x00, 0x00], - [0x20, 0x00, 0x00, 0x00], - [0x40, 0x00, 0x00, 0x00], - [0x80, 0x00, 0x00, 0x00], - [0x1b, 0x00, 0x00, 0x00], - [0x36, 0x00, 0x00, 0x00] - ]; - } - - void _buildInverseSubBox() { - iBox = >[ - [ - 0x52, - 0x09, - 0x6a, - 0xd5, - 0x30, - 0x36, - 0xa5, - 0x38, - 0xbf, - 0x40, - 0xa3, - 0x9e, - 0x81, - 0xf3, - 0xd7, - 0xfb - ], - [ - 0x7c, - 0xe3, - 0x39, - 0x82, - 0x9b, - 0x2f, - 0xff, - 0x87, - 0x34, - 0x8e, - 0x43, - 0x44, - 0xc4, - 0xde, - 0xe9, - 0xcb - ], - [ - 0x54, - 0x7b, - 0x94, - 0x32, - 0xa6, - 0xc2, - 0x23, - 0x3d, - 0xee, - 0x4c, - 0x95, - 0x0b, - 0x42, - 0xfa, - 0xc3, - 0x4e - ], - [ - 0x08, - 0x2e, - 0xa1, - 0x66, - 0x28, - 0xd9, - 0x24, - 0xb2, - 0x76, - 0x5b, - 0xa2, - 0x49, - 0x6d, - 0x8b, - 0xd1, - 0x25 - ], - [ - 0x72, - 0xf8, - 0xf6, - 0x64, - 0x86, - 0x68, - 0x98, - 0x16, - 0xd4, - 0xa4, - 0x5c, - 0xcc, - 0x5d, - 0x65, - 0xb6, - 0x92 - ], - [ - 0x6c, - 0x70, - 0x48, - 0x50, - 0xfd, - 0xed, - 0xb9, - 0xda, - 0x5e, - 0x15, - 0x46, - 0x57, - 0xa7, - 0x8d, - 0x9d, - 0x84 - ], - [ - 0x90, - 0xd8, - 0xab, - 0x00, - 0x8c, - 0xbc, - 0xd3, - 0x0a, - 0xf7, - 0xe4, - 0x58, - 0x05, - 0xb8, - 0xb3, - 0x45, - 0x06 - ], - [ - 0xd0, - 0x2c, - 0x1e, - 0x8f, - 0xca, - 0x3f, - 0x0f, - 0x02, - 0xc1, - 0xaf, - 0xbd, - 0x03, - 0x01, - 0x13, - 0x8a, - 0x6b - ], - [ - 0x3a, - 0x91, - 0x11, - 0x41, - 0x4f, - 0x67, - 0xdc, - 0xea, - 0x97, - 0xf2, - 0xcf, - 0xce, - 0xf0, - 0xb4, - 0xe6, - 0x73 - ], - [ - 0x96, - 0xac, - 0x74, - 0x22, - 0xe7, - 0xad, - 0x35, - 0x85, - 0xe2, - 0xf9, - 0x37, - 0xe8, - 0x1c, - 0x75, - 0xdf, - 0x6e - ], - [ - 0x47, - 0xf1, - 0x1a, - 0x71, - 0x1d, - 0x29, - 0xc5, - 0x89, - 0x6f, - 0xb7, - 0x62, - 0x0e, - 0xaa, - 0x18, - 0xbe, - 0x1b - ], - [ - 0xfc, - 0x56, - 0x3e, - 0x4b, - 0xc6, - 0xd2, - 0x79, - 0x20, - 0x9a, - 0xdb, - 0xc0, - 0xfe, - 0x78, - 0xcd, - 0x5a, - 0xf4 - ], - [ - 0x1f, - 0xdd, - 0xa8, - 0x33, - 0x88, - 0x07, - 0xc7, - 0x31, - 0xb1, - 0x12, - 0x10, - 0x59, - 0x27, - 0x80, - 0xec, - 0x5f - ], - [ - 0x60, - 0x51, - 0x7f, - 0xa9, - 0x19, - 0xb5, - 0x4a, - 0x0d, - 0x2d, - 0xe5, - 0x7a, - 0x9f, - 0x93, - 0xc9, - 0x9c, - 0xef - ], - [ - 0xa0, - 0xe0, - 0x3b, - 0x4d, - 0xae, - 0x2a, - 0xf5, - 0xb0, - 0xc8, - 0xeb, - 0xbb, - 0x3c, - 0x83, - 0x53, - 0x99, - 0x61 - ], - [ - 0x17, - 0x2b, - 0x04, - 0x7e, - 0xba, - 0x77, - 0xd6, - 0x26, - 0xe1, - 0x69, - 0x14, - 0x63, - 0x55, - 0x21, - 0x0c, - 0x7d - ] - ]; - } - - void _buildSubstitutionBox() { - sBox = >[ - [ - 0x63, - 0x7c, - 0x77, - 0x7b, - 0xf2, - 0x6b, - 0x6f, - 0xc5, - 0x30, - 0x01, - 0x67, - 0x2b, - 0xfe, - 0xd7, - 0xab, - 0x76 - ], - [ - 0xca, - 0x82, - 0xc9, - 0x7d, - 0xfa, - 0x59, - 0x47, - 0xf0, - 0xad, - 0xd4, - 0xa2, - 0xaf, - 0x9c, - 0xa4, - 0x72, - 0xc0 - ], - [ - 0xb7, - 0xfd, - 0x93, - 0x26, - 0x36, - 0x3f, - 0xf7, - 0xcc, - 0x34, - 0xa5, - 0xe5, - 0xf1, - 0x71, - 0xd8, - 0x31, - 0x15 - ], - [ - 0x04, - 0xc7, - 0x23, - 0xc3, - 0x18, - 0x96, - 0x05, - 0x9a, - 0x07, - 0x12, - 0x80, - 0xe2, - 0xeb, - 0x27, - 0xb2, - 0x75 - ], - [ - 0x09, - 0x83, - 0x2c, - 0x1a, - 0x1b, - 0x6e, - 0x5a, - 0xa0, - 0x52, - 0x3b, - 0xd6, - 0xb3, - 0x29, - 0xe3, - 0x2f, - 0x84 - ], - [ - 0x53, - 0xd1, - 0x00, - 0xed, - 0x20, - 0xfc, - 0xb1, - 0x5b, - 0x6a, - 0xcb, - 0xbe, - 0x39, - 0x4a, - 0x4c, - 0x58, - 0xcf - ], - [ - 0xd0, - 0xef, - 0xaa, - 0xfb, - 0x43, - 0x4d, - 0x33, - 0x85, - 0x45, - 0xf9, - 0x02, - 0x7f, - 0x50, - 0x3c, - 0x9f, - 0xa8 - ], - [ - 0x51, - 0xa3, - 0x40, - 0x8f, - 0x92, - 0x9d, - 0x38, - 0xf5, - 0xbc, - 0xb6, - 0xda, - 0x21, - 0x10, - 0xff, - 0xf3, - 0xd2 - ], - [ - 0xcd, - 0x0c, - 0x13, - 0xec, - 0x5f, - 0x97, - 0x44, - 0x17, - 0xc4, - 0xa7, - 0x7e, - 0x3d, - 0x64, - 0x5d, - 0x19, - 0x73 - ], - [ - 0x60, - 0x81, - 0x4f, - 0xdc, - 0x22, - 0x2a, - 0x90, - 0x88, - 0x46, - 0xee, - 0xb8, - 0x14, - 0xde, - 0x5e, - 0x0b, - 0xdb - ], - [ - 0xe0, - 0x32, - 0x3a, - 0x0a, - 0x49, - 0x06, - 0x24, - 0x5c, - 0xc2, - 0xd3, - 0xac, - 0x62, - 0x91, - 0x95, - 0xe4, - 0x79 - ], - [ - 0xe7, - 0xc8, - 0x37, - 0x6d, - 0x8d, - 0xd5, - 0x4e, - 0xa9, - 0x6c, - 0x56, - 0xf4, - 0xea, - 0x65, - 0x7a, - 0xae, - 0x08 - ], - [ - 0xba, - 0x78, - 0x25, - 0x2e, - 0x1c, - 0xa6, - 0xb4, - 0xc6, - 0xe8, - 0xdd, - 0x74, - 0x1f, - 0x4b, - 0xbd, - 0x8b, - 0x8a - ], - [ - 0x70, - 0x3e, - 0xb5, - 0x66, - 0x48, - 0x03, - 0xf6, - 0x0e, - 0x61, - 0x35, - 0x57, - 0xb9, - 0x86, - 0xc1, - 0x1d, - 0x9e - ], - [ - 0xe1, - 0xf8, - 0x98, - 0x11, - 0x69, - 0xd9, - 0x8e, - 0x94, - 0x9b, - 0x1e, - 0x87, - 0xe9, - 0xce, - 0x55, - 0x28, - 0xdf - ], - [ - 0x8c, - 0xa1, - 0x89, - 0x0d, - 0xbf, - 0xe6, - 0x42, - 0x68, - 0x41, - 0x99, - 0x2d, - 0x0f, - 0xb0, - 0x54, - 0xbb, - 0x16 - ] - ]; + return output; } } - -/// Specifies the key size of AES. -enum _KeySize { - /// 128 Bit. - bits128, - - /// 192 Bit. - bits192, - - /// 256 Bit. - bits256 -} 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 6f26f61b4..67912dc5c 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,21 +1,31 @@ +import 'dart:typed_data'; +import 'cipher_block_chaining_mode.dart'; import 'ipadding.dart'; /// internal class -class AesEngine implements ICipher { +class AesEngine extends IBlockCipher { //Constructor /// internal constructor AesEngine() { _initializeConstants(); } - //Fields + static const _blockSize = 16; + static const int _m1 = 0x80808080; + static const int _m2 = 0x7f7f7f7f; + static const int _m3 = 0x0000001b; + static const int _m4 = 0xC0C0C0C0; + static const int _m5 = 0x3f3f3f3f; + @override - int? blockSize; - List>? _key; + int get blockSize => _blockSize; + + /// internal field + late List>? _key; /// internal field late int rounds; - bool? _isEncryption; + late bool? _isEncryption; /// internal field late List rcon; @@ -81,336 +91,516 @@ class AesEngine implements ICipher { @override bool get isBlock => false; + List _s = List.empty(); + //Initialize @override - void initialize(bool? isEncryption, ICipherParameter? parameter) { + void initialize(bool? isEncryption, covariant KeyParameter? parameter) { if (parameter != null) { - _key = _generateKey(parameter.keys!, isEncryption!); + _key = _generateKey(parameter, isEncryption!); _isEncryption = isEncryption; } + if (_isEncryption!) { + _s = List.from(sBox); + } else { + _s = List.from(sinv); + } } - 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, 'keyLength', 'Key length not 128/192/256 bits.'); + int _removeBlock(dynamic inp, int offset, Endian endian) { + if (inp is! ByteData) { + inp = ByteData.view(inp.buffer, inp.offsetInBytes, inp.length); } - rounds = keyLength + 6; - final List> newKey = List>.generate( - rounds + 1, (int i) => List.generate(4, (int j) => 0)); - int t = 0; - for (int i = 0; i < keys.length; t++) { - newKey[t >> 2][t & 3] = _convertToUnsignedInt32(keys, i); - i += 4; + return inp.getUint32(offset, endian); + } + + List> _generateKey(KeyParameter params, bool isEncryption) { + final key = params.keys; + final keyLen = key.length; + if (keyLen < 16 || keyLen > 32 || (keyLen & 7) != 0) { + throw ArgumentError('Invalid key length : $keyLen'); } - final int k = (rounds + 1) << 2; - for (int i = keyLength; i < k; i++) { - int temp = newKey[(i - 1) >> 2][(i - 1) & 3]; - if (i % keyLength == 0) { - temp = _subWord(_shift(temp, 8)) ^ rcon[(i ~/ keyLength) - 1]; - } else if (keyLength > 6 && (i % keyLength) == 4) { - temp = _subWord(temp); - } - newKey[i >> 2][i & 3] = - newKey[(i - keyLength) >> 2][(i - keyLength) & 3] ^ temp; + final kc = _shiftRight32(keyLen, 2); + rounds = kc + 6; + final w = List.generate(rounds + 1, (int i) => List.filled(4, 0)); + switch (kc) { + case 4: + var col0 = _removeBlock(key, 0, Endian.little); + w[0][0] = col0; + var col1 = _removeBlock(key, 4, Endian.little); + w[0][1] = col1; + var col2 = _removeBlock(key, 8, Endian.little); + w[0][2] = col2; + var col3 = _removeBlock(key, 12, Endian.little); + w[0][3] = col3; + for (var i = 1; i <= 10; ++i) { + final colx = _subWord(_shift(col3, 8)) ^ rcon[i - 1]; + col0 ^= colx; + w[i][0] = col0; + col1 ^= col0; + w[i][1] = col1; + col2 ^= col1; + w[i][2] = col2; + col3 ^= col2; + w[i][3] = col3; + } + break; + case 6: + var col0 = _removeBlock(key, 0, Endian.little); + w[0][0] = col0; + var col1 = _removeBlock(key, 4, Endian.little); + w[0][1] = col1; + var col2 = _removeBlock(key, 8, Endian.little); + w[0][2] = col2; + var col3 = _removeBlock(key, 12, Endian.little); + w[0][3] = col3; + var col4 = _removeBlock(key, 16, Endian.little); + var col5 = _removeBlock(key, 20, Endian.little); + int i = 1, rcon = 1, colx; + for (;;) { + w[i][0] = col4; + w[i][1] = col5; + colx = _subWord(_shift(col5, 8)) ^ rcon; + rcon <<= 1; + col0 ^= colx; + w[i][2] = col0; + col1 ^= col0; + w[i][3] = col1; + col2 ^= col1; + w[i + 1][0] = col2; + col3 ^= col2; + w[i + 1][1] = col3; + col4 ^= col3; + w[i + 1][2] = col4; + col5 ^= col4; + w[i + 1][3] = col5; + colx = _subWord(_shift(col5, 8)) ^ rcon; + rcon <<= 1; + col0 ^= colx; + w[i + 2][0] = col0; + col1 ^= col0; + w[i + 2][1] = col1; + col2 ^= col1; + w[i + 2][2] = col2; + col3 ^= col2; + w[i + 2][3] = col3; + if ((i += 3) >= 13) { + break; + } + col4 ^= col3; + col5 ^= col4; + } + break; + case 8: + { + var col0 = _removeBlock(key, 0, Endian.little); + w[0][0] = col0; + var col1 = _removeBlock(key, 4, Endian.little); + w[0][1] = col1; + var col2 = _removeBlock(key, 8, Endian.little); + w[0][2] = col2; + var col3 = _removeBlock(key, 12, Endian.little); + w[0][3] = col3; + var col4 = _removeBlock(key, 16, Endian.little); + w[1][0] = col4; + var col5 = _removeBlock(key, 20, Endian.little); + w[1][1] = col5; + var col6 = _removeBlock(key, 24, Endian.little); + w[1][2] = col6; + var col7 = _removeBlock(key, 28, Endian.little); + w[1][3] = col7; + int i = 2, rcon = 1, colx; + for (;;) { + colx = _subWord(_shift(col7, 8)) ^ rcon; + rcon <<= 1; + col0 ^= colx; + w[i][0] = col0; + col1 ^= col0; + w[i][1] = col1; + col2 ^= col1; + w[i][2] = col2; + col3 ^= col2; + w[i][3] = col3; + ++i; + if (i >= 15) { + break; + } + colx = _subWord(col3); + col4 ^= colx; + w[i][0] = col4; + col5 ^= col4; + w[i][1] = col5; + col6 ^= col5; + w[i][2] = col6; + col7 ^= col6; + w[i][3] = col7; + ++i; + } + break; + } + default: + { + throw StateError('Invalid key length: ${key.lengthInBytes}'); + } } if (!isEncryption) { - for (int j = 1; j < rounds; j++) { - final List w = newKey[j]; - for (int i = 0; i < 4; i++) { - w[i] = _inverseMultiply(w[i]).toUnsigned(32); + for (var j = 1; j < rounds; j++) { + for (var i = 0; i < 4; i++) { + w[j][i] = _inverseMultiply(w[j][i]); } } } - return newKey; + return w; } @override - Map processBlock( - [List? input, - int? inputOffset, - List? output, - int? outputOffset]) { - ArgumentError.checkNotNull(_key); - _unPackBlock(input!, inputOffset!); + int processingBlock([ + Uint8List? inputBytes, + int? inputOffset, + //need to be unit8list + Uint8List? outputBytes, + //List? output, + int? outputOffset, + ]) { + if ((inputOffset! + (32 / 2)) > inputBytes!.lengthInBytes) { + throw ArgumentError( + 'Invalid length in input buffer : ${inputBytes.lengthInBytes}', + ); + } + + if ((outputOffset! + (32 / 2)) > outputBytes!.lengthInBytes) { + throw ArgumentError( + 'Invalid length in output buffer : ${outputBytes.lengthInBytes}', + ); + } if (_isEncryption!) { - encryptBlock(); + encryptBlock(inputBytes, inputOffset, outputBytes, outputOffset, _key!); } else { - decryptBlock(); + decryptBlock(inputBytes, inputOffset, outputBytes, outputOffset, _key!); } - output = _packBlock(output!, outputOffset!); - return {'length': blockSize, 'output': output}; + + return _blockSize; } @override void reset() {} /// internal method - void encryptBlock() { - final List> keys = _key!; - int r = 0; - List kw = keys[r]; - c0 ^= kw[0]; - c1 ^= kw[1]; - c2 ^= kw[2]; - c3 ^= kw[3]; - int r4; - int r5; - int r6; - int r7; - while (r < (rounds - 2)) { - kw = keys[++r]; - r4 = r0[c0 & 255] ^ - r1[(c1 >> 8) & 255] ^ - r2[(c2 >> 16) & 255] ^ - r3[c3 >> 24] ^ - kw[0]; - r5 = r0[c1 & 255] ^ - r1[(c2 >> 8) & 255] ^ - r2[(c3 >> 16) & 255] ^ - r3[c0 >> 24] ^ - kw[1]; - r6 = r0[c2 & 255] ^ - r1[(c3 >> 8) & 255] ^ - r2[(c0 >> 16) & 255] ^ - r3[c1 >> 24] ^ - kw[2]; - r7 = r0[c3 & 255] ^ - r1[(c0 >> 8) & 255] ^ - r2[(c1 >> 16) & 255] ^ - r3[c2 >> 24] ^ - kw[3]; - kw = keys[++r]; - c0 = r0[r4 & 255] ^ - r1[(r5 >> 8) & 255] ^ - r2[(r6 >> 16) & 255] ^ - r3[r7 >> 24] ^ - kw[0]; - c1 = r0[r5 & 255] ^ - r1[(r6 >> 8) & 255] ^ - r2[(r7 >> 16) & 255] ^ - r3[r4 >> 24] ^ - kw[1]; - c2 = r0[r6 & 255] ^ - r1[(r7 >> 8) & 255] ^ - r2[(r4 >> 16) & 255] ^ - r3[r5 >> 24] ^ - kw[2]; - c3 = r0[r7 & 255] ^ - r1[(r4 >> 8) & 255] ^ - r2[(r5 >> 16) & 255] ^ - r3[r6 >> 24] ^ - kw[3]; + void encryptBlock( + input, + int inOff, + Uint8List out, + int outOff, + List> kw, + ) { + var c0 = _removeBlock(input, inOff + 0, Endian.little); + var c1 = _removeBlock(input, inOff + 4, Endian.little); + var c2 = _removeBlock(input, inOff + 8, Endian.little); + var c3 = _removeBlock(input, inOff + 12, Endian.little); + + var t0 = c0 ^ kw[0][0]; + var t1 = c1 ^ kw[0][1]; + var t2 = c2 ^ kw[0][2]; + + int r = 1, r01, r1, r2, r3 = c3 ^ kw[0][3]; + + while (r < rounds - 1) { + r01 = + r0[t0 & 255] ^ + _shift(r0[(t1 >> 8) & 255], 24) ^ + _shift(r0[(t2 >> 16) & 255], 16) ^ + _shift(r0[(r3 >> 24) & 255], 8) ^ + kw[r][0]; + r1 = + r0[t1 & 255] ^ + _shift(r0[(t2 >> 8) & 255], 24) ^ + _shift(r0[(r3 >> 16) & 255], 16) ^ + _shift(r0[(t0 >> 24) & 255], 8) ^ + kw[r][1]; + r2 = + r0[t2 & 255] ^ + _shift(r0[(r3 >> 8) & 255], 24) ^ + _shift(r0[(t0 >> 16) & 255], 16) ^ + _shift(r0[(t1 >> 24) & 255], 8) ^ + kw[r][2]; + r3 = + r0[r3 & 255] ^ + _shift(r0[(t0 >> 8) & 255], 24) ^ + _shift(r0[(t1 >> 16) & 255], 16) ^ + _shift(r0[(t2 >> 24) & 255], 8) ^ + kw[r++][3]; + t0 = + r0[r01 & 255] ^ + _shift(r0[(r1 >> 8) & 255], 24) ^ + _shift(r0[(r2 >> 16) & 255], 16) ^ + _shift(r0[(r3 >> 24) & 255], 8) ^ + kw[r][0]; + t1 = + r0[r1 & 255] ^ + _shift(r0[(r2 >> 8) & 255], 24) ^ + _shift(r0[(r3 >> 16) & 255], 16) ^ + _shift(r0[(r01 >> 24) & 255], 8) ^ + kw[r][1]; + t2 = + r0[r2 & 255] ^ + _shift(r0[(r3 >> 8) & 255], 24) ^ + _shift(r0[(r01 >> 16) & 255], 16) ^ + _shift(r0[(r1 >> 24) & 255], 8) ^ + kw[r][2]; + r3 = + r0[r3 & 255] ^ + _shift(r0[(r01 >> 8) & 255], 24) ^ + _shift(r0[(r1 >> 16) & 255], 16) ^ + _shift(r0[(r2 >> 24) & 255], 8) ^ + kw[r++][3]; } - kw = keys[++r]; - r4 = r0[c0 & 255] ^ - r1[(c1 >> 8) & 255] ^ - r2[(c2 >> 16) & 255] ^ - r3[c3 >> 24] ^ - kw[0]; - r5 = r0[c1 & 255] ^ - r1[(c2 >> 8) & 255] ^ - r2[(c3 >> 16) & 255] ^ - r3[c0 >> 24] ^ - kw[1]; - r6 = r0[c2 & 255] ^ - r1[(c3 >> 8) & 255] ^ - r2[(c0 >> 16) & 255] ^ - r3[c1 >> 24] ^ - kw[2]; - r7 = r0[c3 & 255] ^ - r1[(c0 >> 8) & 255] ^ - r2[(c1 >> 16) & 255] ^ - r3[c2 >> 24] ^ - kw[3]; - kw = keys[++r]; - c0 = sBox[r4 & 255] ^ - ((sBox[(r5 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sBox[(r6 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sBox[r7 >> 24]).toUnsigned(32) << 24) ^ - kw[0]; - c1 = sBox[r5 & 255] ^ - ((sBox[(r6 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sBox[(r7 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sBox[r4 >> 24]).toUnsigned(32) << 24) ^ - kw[1]; - c2 = sBox[r6 & 255] ^ - ((sBox[(r7 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sBox[(r4 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sBox[r5 >> 24]).toUnsigned(32) << 24) ^ - kw[2]; - c3 = sBox[r7 & 255] ^ - ((sBox[(r4 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sBox[(r5 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sBox[r6 >> 24]).toUnsigned(32) << 24) ^ - kw[3]; + + r01 = + r0[t0 & 255] ^ + _shift(r0[(t1 >> 8) & 255], 24) ^ + _shift(r0[(t2 >> 16) & 255], 16) ^ + _shift(r0[(r3 >> 24) & 255], 8) ^ + kw[r][0]; + r1 = + r0[t1 & 255] ^ + _shift(r0[(t2 >> 8) & 255], 24) ^ + _shift(r0[(r3 >> 16) & 255], 16) ^ + _shift(r0[(t0 >> 24) & 255], 8) ^ + kw[r][1]; + r2 = + r0[t2 & 255] ^ + _shift(r0[(r3 >> 8) & 255], 24) ^ + _shift(r0[(t0 >> 16) & 255], 16) ^ + _shift(r0[(t1 >> 24) & 255], 8) ^ + kw[r][2]; + r3 = + r0[r3 & 255] ^ + _shift(r0[(t0 >> 8) & 255], 24) ^ + _shift(r0[(t1 >> 16) & 255], 16) ^ + _shift(r0[(t2 >> 24) & 255], 8) ^ + kw[r++][3]; + + c0 = + (sBox[r01 & 255] & 255) ^ + ((sBox[(r1 >> 8) & 255] & 255) << 8) ^ + ((_s[(r2 >> 16) & 255] & 255) << 16) ^ + (_s[(r3 >> 24) & 255] << 24) ^ + kw[r][0]; + c1 = + (_s[r1 & 255] & 255) ^ + ((sBox[(r2 >> 8) & 255] & 255) << 8) ^ + ((sBox[(r3 >> 16) & 255] & 255) << 16) ^ + (_s[(r01 >> 24) & 255] << 24) ^ + kw[r][1]; + c2 = + (_s[r2 & 255] & 255) ^ + ((sBox[(r3 >> 8) & 255] & 255) << 8) ^ + ((sBox[(r01 >> 16) & 255] & 255) << 16) ^ + (sBox[(r1 >> 24) & 255] << 24) ^ + kw[r][2]; + c3 = + (_s[r3 & 255] & 255) ^ + ((_s[(r01 >> 8) & 255] & 255) << 8) ^ + ((_s[(r1 >> 16) & 255] & 255) << 16) ^ + (sBox[(r2 >> 24) & 255] << 24) ^ + kw[r][3]; + + _addBlock(c0, out, outOff + 0, Endian.little); + _addBlock(c1, out, outOff + 4, Endian.little); + _addBlock(c2, out, outOff + 8, Endian.little); + _addBlock(c3, out, outOff + 12, Endian.little); } /// internal method - void decryptBlock() { - final List> keys = _key!; - int r = rounds; - List kw = keys[r]; - c0 ^= kw[0]; - c1 ^= kw[1]; - c2 ^= kw[2]; - c3 ^= kw[3]; - int r4; - int r5; - int r6; - int r7; - while (r > 2) { - kw = keys[--r]; - r4 = rinv0[c0 & 255] ^ - rinv1[(c3 >> 8) & 255] ^ - rinv2[(c2 >> 16) & 255] ^ - rinv3[c1 >> 24] ^ - kw[0]; - r5 = rinv0[c1 & 255] ^ - rinv1[(c0 >> 8) & 255] ^ - rinv2[(c3 >> 16) & 255] ^ - rinv3[c2 >> 24] ^ - kw[1]; - r6 = rinv0[c2 & 255] ^ - rinv1[(c1 >> 8) & 255] ^ - rinv2[(c0 >> 16) & 255] ^ - rinv3[c3 >> 24] ^ - kw[2]; - r7 = rinv0[c3 & 255] ^ - rinv1[(c2 >> 8) & 255] ^ - rinv2[(c1 >> 16) & 255] ^ - rinv3[c0 >> 24] ^ - kw[3]; - kw = keys[--r]; - c0 = rinv0[r4 & 255] ^ - rinv1[(r7 >> 8) & 255] ^ - rinv2[(r6 >> 16) & 255] ^ - rinv3[r5 >> 24] ^ - kw[0]; - c1 = rinv0[r5 & 255] ^ - rinv1[(r4 >> 8) & 255] ^ - rinv2[(r7 >> 16) & 255] ^ - rinv3[r6 >> 24] ^ - kw[1]; - c2 = rinv0[r6 & 255] ^ - rinv1[(r5 >> 8) & 255] ^ - rinv2[(r4 >> 16) & 255] ^ - rinv3[r7 >> 24] ^ - kw[2]; - c3 = rinv0[r7 & 255] ^ - rinv1[(r6 >> 8) & 255] ^ - rinv2[(r5 >> 16) & 255] ^ - rinv3[r4 >> 24] ^ - kw[3]; + void decryptBlock( + input, + int inOff, + Uint8List out, + int outOff, + List> kw, + ) { + var c0 = _removeBlock(input, inOff + 0, Endian.little); + var c1 = _removeBlock(input, inOff + 4, Endian.little); + var c2 = _removeBlock(input, inOff + 8, Endian.little); + var c3 = _removeBlock(input, inOff + 12, Endian.little); + + var t0 = c0 ^ kw[rounds][0]; + var t1 = c1 ^ kw[rounds][1]; + var t2 = c2 ^ kw[rounds][2]; + + int r = rounds - 1, r01, r1, r2, r3 = c3 ^ kw[rounds][3]; + + while (r > 1) { + r01 = + rinv0[t0 & 255] ^ + _shift(rinv0[(r3 >> 8) & 255], 24) ^ + _shift(rinv0[(t2 >> 16) & 255], 16) ^ + _shift(rinv0[(t1 >> 24) & 255], 8) ^ + kw[r][0]; + r1 = + rinv0[t1 & 255] ^ + _shift(rinv0[(t0 >> 8) & 255], 24) ^ + _shift(rinv0[(r3 >> 16) & 255], 16) ^ + _shift(rinv0[(t2 >> 24) & 255], 8) ^ + kw[r][1]; + r2 = + rinv0[t2 & 255] ^ + _shift(rinv0[(t1 >> 8) & 255], 24) ^ + _shift(rinv0[(t0 >> 16) & 255], 16) ^ + _shift(rinv0[(r3 >> 24) & 255], 8) ^ + kw[r][2]; + r3 = + rinv0[r3 & 255] ^ + _shift(rinv0[(t2 >> 8) & 255], 24) ^ + _shift(rinv0[(t1 >> 16) & 255], 16) ^ + _shift(rinv0[(t0 >> 24) & 255], 8) ^ + kw[r--][3]; + t0 = + rinv0[r01 & 255] ^ + _shift(rinv0[(r3 >> 8) & 255], 24) ^ + _shift(rinv0[(r2 >> 16) & 255], 16) ^ + _shift(rinv0[(r1 >> 24) & 255], 8) ^ + kw[r][0]; + t1 = + rinv0[r1 & 255] ^ + _shift(rinv0[(r01 >> 8) & 255], 24) ^ + _shift(rinv0[(r3 >> 16) & 255], 16) ^ + _shift(rinv0[(r2 >> 24) & 255], 8) ^ + kw[r][1]; + t2 = + rinv0[r2 & 255] ^ + _shift(rinv0[(r1 >> 8) & 255], 24) ^ + _shift(rinv0[(r01 >> 16) & 255], 16) ^ + _shift(rinv0[(r3 >> 24) & 255], 8) ^ + kw[r][2]; + r3 = + rinv0[r3 & 255] ^ + _shift(rinv0[(r2 >> 8) & 255], 24) ^ + _shift(rinv0[(r1 >> 16) & 255], 16) ^ + _shift(rinv0[(r01 >> 24) & 255], 8) ^ + kw[r--][3]; } - kw = keys[--r]; - r4 = rinv0[c0 & 255] ^ - rinv1[(c3 >> 8) & 255] ^ - rinv2[(c2 >> 16) & 255] ^ - rinv3[c1 >> 24] ^ - kw[0]; - r5 = rinv0[c1 & 255] ^ - rinv1[(c0 >> 8) & 255] ^ - rinv2[(c3 >> 16) & 255] ^ - rinv3[c2 >> 24] ^ - kw[1]; - r6 = rinv0[c2 & 255] ^ - rinv1[(c1 >> 8) & 255] ^ - rinv2[(c0 >> 16) & 255] ^ - rinv3[c3 >> 24] ^ - kw[2]; - r7 = rinv0[c3 & 255] ^ - rinv1[(c2 >> 8) & 255] ^ - rinv2[(c1 >> 16) & 255] ^ - rinv3[c0 >> 24] ^ - kw[3]; + r01 = + rinv0[t0 & 255] ^ + _shift(rinv0[(r3 >> 8) & 255], 24) ^ + _shift(rinv0[(t2 >> 16) & 255], 16) ^ + _shift(rinv0[(t1 >> 24) & 255], 8) ^ + kw[r][0]; + r1 = + rinv0[t1 & 255] ^ + _shift(rinv0[(t0 >> 8) & 255], 24) ^ + _shift(rinv0[(r3 >> 16) & 255], 16) ^ + _shift(rinv0[(t2 >> 24) & 255], 8) ^ + kw[r][1]; + r2 = + rinv0[t2 & 255] ^ + _shift(rinv0[(t1 >> 8) & 255], 24) ^ + _shift(rinv0[(t0 >> 16) & 255], 16) ^ + _shift(rinv0[(r3 >> 24) & 255], 8) ^ + kw[r][2]; + r3 = + rinv0[r3 & 255] ^ + _shift(rinv0[(t2 >> 8) & 255], 24) ^ + _shift(rinv0[(t1 >> 16) & 255], 16) ^ + _shift(rinv0[(t0 >> 24) & 255], 8) ^ + kw[r][3]; + + c0 = + (sinv[r01 & 255] & 255) ^ + ((_s[(r3 >> 8) & 255] & 255) << 8) ^ + ((_s[(r2 >> 16) & 255] & 255) << 16) ^ + (sinv[(r1 >> 24) & 255] << 24) ^ + kw[0][0]; + c1 = + (_s[r1 & 255] & 255) ^ + ((_s[(r01 >> 8) & 255] & 255) << 8) ^ + ((sinv[(r3 >> 16) & 255] & 255) << 16) ^ + (_s[(r2 >> 24) & 255] << 24) ^ + kw[0][1]; + c2 = + (_s[r2 & 255] & 255) ^ + ((sinv[(r1 >> 8) & 255] & 255) << 8) ^ + ((sinv[(r01 >> 16) & 255] & 255) << 16) ^ + (_s[(r3 >> 24) & 255] << 24) ^ + kw[0][2]; + c3 = + (sinv[r3 & 255] & 255) ^ + ((_s[(r2 >> 8) & 255] & 255) << 8) ^ + ((_s[(r1 >> 16) & 255] & 255) << 16) ^ + (_s[(r01 >> 24) & 255] << 24) ^ + kw[0][3]; - kw = keys[--r]; - c0 = sinv[r4 & 255].toUnsigned(32) ^ - ((sinv[(r7 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sinv[(r6 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sinv[r5 >> 24]).toUnsigned(32) << 24) ^ - kw[0]; - c1 = sinv[r5 & 255].toUnsigned(32) ^ - ((sinv[(r4 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sinv[(r7 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sinv[r6 >> 24]).toUnsigned(32) << 24) ^ - kw[1]; - c2 = sinv[r6 & 255].toUnsigned(32) ^ - ((sinv[(r5 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sinv[(r4 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sinv[r7 >> 24]).toUnsigned(32) << 24) ^ - kw[2]; - c3 = sinv[r7 & 255].toUnsigned(32) ^ - ((sinv[(r6 >> 8) & 255]).toUnsigned(32) << 8) ^ - ((sinv[(r5 >> 16) & 255]).toUnsigned(32) << 16) ^ - ((sinv[r4 >> 24]).toUnsigned(32) << 24) ^ - kw[3]; + _addBlock(c0, out, outOff + 0, Endian.little); + _addBlock(c1, out, outOff + 4, Endian.little); + _addBlock(c2, out, outOff + 8, Endian.little); + _addBlock(c3, out, outOff + 12, Endian.little); } - int _convertToUnsignedInt32(List bytes, int offset) { - return bytes[offset].toUnsigned(32) | - (bytes[offset + 1].toUnsigned(32) << 8) | - (bytes[offset + 2].toUnsigned(32) << 16) | - (bytes[offset + 3].toUnsigned(32) << 24); + void _addBlock(int x, dynamic out, int offset, Endian endian) { + assert((x >= 0) && (x <= _mask_32)); + if (out is! ByteData) { + out = ByteData.view( + out.buffer as ByteBuffer, + out.offsetInBytes, + out.length, + ); + } + out.setUint32(offset, x, endian); } - List _convertFromUnsignedInt32(int n, List bytes, int offset) { - bytes[offset] = n.toUnsigned(8); - bytes[offset + 1] = (n >> 8).toUnsigned(8); - bytes[offset + 2] = (n >> 16).toUnsigned(8); - bytes[offset + 3] = (n >> 24).toUnsigned(8); - return bytes; + int _shift(int r, int shift) => _rotateRight32(r, shift); + + int _rotateRight32(int x, int n) { + assert(n >= 0); + assert((x >= 0) && (x <= _mask_32)); + n &= _mask_5; + return (x >> n) | _shiftLeft32(x, 32 - n); } - int _shift(int r, int shift) { - return (r >> shift).toSigned(32) | (r << (32 - shift)).toSigned(32); + int _shiftRight32(int x, int n) { + assert((x >= 0) && (x <= _mask_32)); + n &= _mask_5; + return x >> n; } - int _subWord(int x) { - return sBox[x & 255].toUnsigned(32) | - (sBox[(x >> 8) & 255].toUnsigned(32) << 8) | - (sBox[(x >> 16) & 255].toUnsigned(32) << 16) | - (sBox[(x >> 24) & 255].toUnsigned(32) << 24); + int _shiftLeft32(int x, int n) { + assert((x >= 0) && (x <= _mask_32)); + n &= _mask_5; + x &= _mask_bits[n]; + return (x << n) & _mask_32; } - int _inverseMultiply(int x) { - final int x1 = _mulX(x); - final int x2 = _mulX(x1); - final int x3 = _mulX(x2); - final int x4 = x ^ x3; - return x1 ^ - x2 ^ - x3 ^ - _shift(x1 ^ x4, 8) ^ - _shift(x2 ^ x4, 16) ^ - _shift(x4, 24); + int _subWord(int x) { + return sBox[x & 255] & 255 | + ((sBox[(x >> 8) & 255] & 255) << 8) | + ((sBox[(x >> 16) & 255] & 255) << 16) | + sBox[(x >> 24) & 255] << 24; } - int _mulX(int x) { - return ((x & mix2) << 1) ^ (((x & mix1) >> 7) * mix3); + int _inverseMultiply(int x) { + int t0, t1; + t0 = x; + t1 = t0 ^ _shift(t0, 8); + t0 ^= _xMultiply(t1); + t1 ^= _x2Multiply(t0); + t0 ^= t1 ^ _shift(t1, 16); + return t0; } - void _unPackBlock(List bytes, int offset) { - c0 = _convertToUnsignedInt32(bytes, offset); - c1 = _convertToUnsignedInt32(bytes, offset + 4); - c2 = _convertToUnsignedInt32(bytes, offset + 8); - c3 = _convertToUnsignedInt32(bytes, offset + 12); + int _xMultiply(int x) { + final lsr = _shiftRight32(x & _m1, 7); + return ((x & _m2) << 1) ^ lsr * _m3; } - List _packBlock(List bytes, int offset) { - bytes = _convertFromUnsignedInt32(c0, bytes, offset); - bytes = _convertFromUnsignedInt32(c1, bytes, offset + 4); - bytes = _convertFromUnsignedInt32(c2, bytes, offset + 8); - bytes = _convertFromUnsignedInt32(c3, bytes, offset + 12); - return bytes; + int _x2Multiply(int x) { + final t0 = _shiftLeft32(x & _m5, 2); + var t1 = x & _m4; + t1 ^= _shiftRight32(t1, 1); + return t0 ^ _shiftRight32(t1, 2) ^ _shiftRight32(t1, 5); } void _initializeConstants() { - blockSize = 16; rounds = 0; _isEncryption = false; mix1 = 0x80808080; @@ -450,7 +640,7 @@ class AesEngine implements ICipher { 0xfa, 0xef, 0xc5, - 0x91 + 0x91, ]; sBox = [ 99, @@ -708,7 +898,7 @@ class AesEngine implements ICipher { 176, 84, 187, - 22 + 22, ]; r0 = [ 0xa56363c6, @@ -966,7 +1156,7 @@ class AesEngine implements ICipher { 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, - 0x3a16162c + 0x3a16162c, ]; r1 = [ 0x6363c6a5, @@ -1224,7 +1414,7 @@ class AesEngine implements ICipher { 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, - 0x16162c3a + 0x16162c3a, ]; r2 = [ 0x63c6a563, @@ -1482,7 +1672,7 @@ class AesEngine implements ICipher { 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, - 0x162c3a16 + 0x162c3a16, ]; r3 = [ 0xc6a56363, @@ -1740,7 +1930,7 @@ class AesEngine implements ICipher { 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, - 0x2c3a1616 + 0x2c3a1616, ]; sinv = [ 82, @@ -1998,7 +2188,7 @@ class AesEngine implements ICipher { 85, 33, 12, - 125 + 125, ]; rinv0 = [ 0x50a7f451, @@ -2256,7 +2446,7 @@ class AesEngine implements ICipher { 0x6184cb7b, 0x70b632d5, 0x745c6c48, - 0x4257b8d0 + 0x4257b8d0, ]; rinv1 = [ 0xa7f45150, @@ -2514,7 +2704,7 @@ class AesEngine implements ICipher { 0x84cb7b61, 0xb632d570, 0x5c6c4874, - 0x57b8d042 + 0x57b8d042, ]; rinv2 = [ 0xf45150a7, @@ -2772,7 +2962,7 @@ class AesEngine implements ICipher { 0xcb7b6184, 0x32d570b6, 0x6c48745c, - 0xb8d04257 + 0xb8d04257, ]; rinv3 = [ 0x5150a7f4, @@ -3030,7 +3220,45 @@ class AesEngine implements ICipher { 0x7b6184cb, 0xd570b632, 0x48745c6c, - 0xd04257b8 + 0xd04257b8, ]; } } + +const _mask_32 = 0xFFFFFFFF; +const _mask_5 = 0x1F; +const _mask_bits = [ + 0xFFFFFFFF, + 0x7FFFFFFF, + 0x3FFFFFFF, + 0x1FFFFFFF, + 0x0FFFFFFF, + 0x07FFFFFF, + 0x03FFFFFF, + 0x01FFFFFF, + 0x00FFFFFF, + 0x007FFFFF, + 0x003FFFFF, + 0x001FFFFF, + 0x000FFFFF, + 0x0007FFFF, + 0x0003FFFF, + 0x0001FFFF, + 0x0000FFFF, + 0x00007FFF, + 0x00003FFF, + 0x00001FFF, + 0x00000FFF, + 0x000007FF, + 0x000003FF, + 0x000001FF, + 0x000000FF, + 0x0000007F, + 0x0000003F, + 0x0000001F, + 0x0000000F, + 0x00000007, + 0x00000003, + 0x00000001, + 0x00000000, +]; 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 index 31806f23d..e83b1dff8 100644 --- 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 @@ -1,10 +1,9 @@ -import 'dart:math'; +import 'dart:typed_data'; import '../asn1/asn1.dart'; import 'ipadding.dart'; -/// internal class -abstract class BufferedBlockPaddingBase implements IBufferedCipher { +abstract class BufferedBlockPaddingBase extends IBufferedCipher { /// internal constructor BufferedBlockPaddingBase(); //Fields @@ -52,13 +51,21 @@ abstract class BufferedBlockPaddingBase implements IBufferedCipher { List? readBytes(List input, int inOff, int length); @override Map processByteFromValues( - List input, List output, int outOff) { + 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) { + 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}; @@ -99,15 +106,28 @@ abstract class BufferedBlockPaddingBase implements IBufferedCipher { @override Map copyFinal( - List input, List output, int outOff) { + 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); + List input, + int inOff, + int length, + List? output, + int outOff, + ) { + Map result = processBytes( + input, + inOff, + length, + output, + outOff, + ); int len = result['length'] as int; output = result['output'] as List?; result = writeFinal(output!, outOff + len); @@ -205,8 +225,13 @@ class BufferedCipher extends BufferedBlockPaddingBase { final int outLength = getUpdateOutputSize(length); List? outBytes = outLength > 0 ? List.generate(outLength, (int i) => 0) : null; - final Map result = - processBytes(input, inOff, length, outBytes, 0); + final Map result = processBytes( + input, + inOff, + length, + outBytes, + 0, + ); final int? position = result['length'] as int?; outBytes = result['output'] as List?; if (outLength > 0 && position! < outLength) { @@ -219,8 +244,13 @@ class BufferedCipher extends BufferedBlockPaddingBase { /// internal method @override - Map processBytes(List? input, int inOffset, int length, - List? output, int outOffset) { + Map processBytes( + List? input, + int inOffset, + int length, + List? output, + int outOffset, + ) { Map? result; if (length < 1) { return {'length': 0, 'output': output}; @@ -239,7 +269,11 @@ class BufferedCipher extends BufferedBlockPaddingBase { inOffset += gapLength; while (length > _bytes!.length) { result = _cipher.processBlock( - input, inOffset, output, outOffset + resultLength); + input, + inOffset, + output, + outOffset + resultLength, + ); resultLength = resultLength + result!['length']! as int; output = result['output'] as List?; length -= blockSize!; @@ -249,8 +283,12 @@ class BufferedCipher extends BufferedBlockPaddingBase { List.copyRange(_bytes!, _offset!, input!, inOffset, inOffset + length); _offset = _offset! + length; if (_offset == _bytes!.length) { - result = - _cipher.processBlock(_bytes, 0, output, outOffset + resultLength); + result = _cipher.processBlock( + _bytes, + 0, + output, + outOffset + resultLength, + ); resultLength = resultLength + result!['length']! as int; output = result['output'] as List?; _offset = 0; @@ -299,8 +337,10 @@ class BufferedCipher extends BufferedBlockPaddingBase { position = position! + result['length']! as int; outBytes = result['output'] as List?; if (position < outBytes!.length) { - final List tempBytes = - List.generate(position, (int i) => 0); + final List tempBytes = List.generate( + position, + (int i) => 0, + ); List.copyRange(tempBytes, 0, outBytes, 0, position); outBytes = tempBytes; } @@ -327,7 +367,6 @@ class BufferedCipher extends BufferedBlockPaddingBase { } } -/// internal class class BufferedBlockPadding extends BufferedCipher { /// internal constructor BufferedBlockPadding(ICipher cipher, [IPadding? padding]) : super(cipher) { @@ -342,10 +381,9 @@ class BufferedBlockPadding extends BufferedCipher { @override void initialize(bool isEncryption, ICipherParameter? parameters) { _isEncryption = isEncryption; - Random? initRandom; reset(); - _padding.initialize(initRandom); _cipher.initialize(isEncryption, parameters); + _padding.initialize(parameters); } @override @@ -387,8 +425,13 @@ class BufferedBlockPadding extends BufferedCipher { } @override - Map processBytes(List? input, int inOffset, int length, - List? output, int outOffset) { + Map processBytes( + List? input, + int inOffset, + int length, + List? output, + int outOffset, + ) { if (length < 0) { throw ArgumentError.value(length, 'length', 'Invalid length'); } @@ -412,7 +455,11 @@ class BufferedBlockPadding extends BufferedCipher { inOffset += gapLength; while (length > _bytes!.length) { result = _cipher.processBlock( - input, inOffset, output, outOffset + resultLength); + input, + inOffset, + output, + outOffset + resultLength, + ); resultLength += result!['length']! as int; output = result['output'] as List?; length -= blockSize!; @@ -434,14 +481,17 @@ class BufferedBlockPadding extends BufferedCipher { if ((outOff! + 2 * blockSize!) > output!.length) { reset(); throw ArgumentError.value( - output, 'output', 'output buffer too short'); + output, + 'output', + 'output buffer too short', + ); } result = _cipher.processBlock(_bytes, 0, output, outOff); resultLen = result!['length'] as int; output = result['output'] as List?; _offset = 0; } - _padding.addPadding(_bytes, _offset); + _padding.addPadding(Uint8List.fromList(_bytes!), _offset!); result = _cipher.processBlock(_bytes, 0, output, outOff! + resultLen); resultLen += result!['length'] as int; output = result['output'] as List?; @@ -457,7 +507,7 @@ class BufferedBlockPadding extends BufferedCipher { throw ArgumentError.value(output, 'output', 'incomplete in decryption'); } try { - resultLen -= _padding.count(_bytes)!; + resultLen -= _padding.count(Uint8List.fromList(_bytes!)); List.copyRange(output!, outOff!, _bytes!, 0, resultLen); } finally { reset(); @@ -467,6 +517,119 @@ class BufferedBlockPadding extends BufferedCipher { } } +class PaddedCipherMode extends IBufferedCipher { + /// internal constructor + PaddedCipherMode(this.padding, this.cipher); + @override + final IPadding padding; + @override + final ICipher cipher; + + @override + String get algorithmName => '${cipher.algorithmName}/${padding.paddingName}'; + + @override + int get blockSize => cipher.blockSize!; + + bool? _isEncryption; + + @override + void reset() { + _isEncryption = null; + cipher.reset(); + } + + //Implemntation + @override + void initialize( + bool forEncryption, + covariant BlockCipherPaddedParameters parameters, + ) { + _isEncryption = forEncryption; + cipher.initialize(forEncryption, parameters.underlyingKeyParameters); + padding.initialize(parameters.paddingCipherParameters); + } + + @override + Uint8List process(Uint8List data) { + final inputBlocks = (data.length + blockSize - 1) ~/ blockSize; + int outputBlocks; + if (_isEncryption ?? false) { + outputBlocks = (data.length + blockSize) ~/ blockSize; + } else { + if ((data.length % blockSize) != 0) { + throw ArgumentError( + 'Input data length is not a multiple of the size of cipher block', + ); + } + outputBlocks = inputBlocks; + } + final out = Uint8List(outputBlocks * blockSize); + for (var i = 0; i < (inputBlocks - 1); i++) { + final offset = i * blockSize; + processingBlock(data, offset, out, offset); + } + final lastBlockOffset = (inputBlocks - 1) * blockSize; + final lastBlockSize = doFinalProcess( + data, + lastBlockOffset, + out, + lastBlockOffset, + ); + return out.sublist(0, lastBlockOffset + lastBlockSize); + } + + @override + int? processingBlock( + Uint8List inputBytes, + int inputOffset, + Uint8List outputBytes, + int outputOffset, + ) { + return cipher.processingBlock( + inputBytes, + inputOffset, + outputBytes, + outputOffset, + ); + } + + @override + int doFinalProcess( + Uint8List inputBytes, + int inputOffset, + Uint8List outputBytes, + int outputOffset, + ) { + if (_isEncryption ?? false) { + final lastBlock = Uint8List(blockSize) + ..setAll(0, inputBytes.sublist(inputOffset)); + final remainderBlock = inputBytes.length - inputOffset; + + if (remainderBlock < blockSize) { + padding.addPadding(lastBlock, inputBytes.length - inputOffset); + processingBlock(lastBlock, 0, outputBytes, outputOffset); + return blockSize; + } else { + processingBlock(inputBytes, inputOffset, outputBytes, outputOffset); + padding.addPadding(lastBlock, 0); + processingBlock(lastBlock, 0, outputBytes, outputOffset + blockSize); + return 2 * blockSize; + } + } else { + processingBlock(inputBytes, inputOffset, outputBytes, outputOffset); + final padCount = padding.count(outputBytes.sublist(outputOffset)); + final padOffsetInBlock = blockSize - padCount; + outputBytes.fillRange( + outputOffset + padOffsetInBlock, + outputBytes.length, + 0, + ); + return padOffsetInBlock; + } + } +} + /// internal class class Pkcs7Padding implements IPadding { /// internal constructor @@ -476,11 +639,11 @@ class Pkcs7Padding implements IPadding { String get paddingName => Asn1.pkcs7; //Implementation @override - void initialize(Random? random) {} + void initialize([ICipherParameter? params]) {} @override - int addPadding(List? bytes, int? offset) { - final int code = (bytes!.length - offset!).toUnsigned(8); - while (offset! < bytes.length) { + int addPadding(Uint8List bytes, int offset) { + final int code = bytes.length - offset; + while (offset < bytes.length) { bytes[offset] = code; offset++; } @@ -488,8 +651,8 @@ class Pkcs7Padding implements IPadding { } @override - int count(List? input) { - final int count = input![input.length - 1].toSigned(32); + int count(Uint8List input) { + final int count = _clipByte(input[input.length - 1]); if (count < 1 || count > input.length) { throw ArgumentError.value(input, 'input', 'Invalid pad'); } @@ -501,3 +664,19 @@ class Pkcs7Padding implements IPadding { return count; } } + +int _clipByte(int x) => x & _mask_8; +const _mask_8 = 0xFF; + +class BlockCipherPaddedParameters< + KeyCipherParameters extends ICipherParameter?, + PaddingCipherParameters extends ICipherParameter? +> + implements ICipherParameter { + BlockCipherPaddedParameters( + this.underlyingKeyParameters, + this.paddingCipherParameters, + ); + final KeyCipherParameters? underlyingKeyParameters; + final PaddingCipherParameters? paddingCipherParameters; +} 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 0300badb4..c8a01cea4 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,73 +1,91 @@ +import 'dart:typed_data'; import 'ipadding.dart'; /// internal class -class CipherBlockChainingMode implements ICipher { +class CipherBlockChainingMode extends IBlockCipher { //Constructor /// internal constructor 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); + + _bytes = Uint8List(_size!); + _cbcBytes = Uint8List(_size!); + _cbcNextBytes = Uint8List(_size!); + _isEncryption = false; } //Fields ICipher? _cipher; int? _size; - late List _bytes; - List? _cbcBytes; - List? _cbcNextBytes; + late Uint8List _bytes; + Uint8List? _cbcBytes; + Uint8List? _cbcNextBytes; bool? _isEncryption; //Fields @override int? get blockSize => _cipher!.blockSize; @override - String get algorithmName => '${_cipher!.algorithmName!}/CBC'; + String get algorithmName => '${_cipher!.algorithmName}/CBC'; @override bool get isBlock => false; //Implementation @override - void initialize(bool? isEncryption, ICipherParameter? parameters) { + void initialize(bool isEncryption, ICipherParameter? parameters) { final bool? oldEncryption = _isEncryption; _isEncryption = isEncryption; - if (parameters is InvalidParameter) { - final List bytes = parameters.keys; - if (bytes.length != _size) { - throw ArgumentError.value(parameters, 'Invalid size in block'); - } - List.copyRange(_bytes, 0, bytes, 0, bytes.length); - parameters = parameters.parameters; - } - reset(); if (parameters != null) { - _cipher!.initialize(_isEncryption, parameters); + if (parameters is InvalidParameter) { + final List iv = parameters.keys!; + if (iv.length != blockSize) { + throw ArgumentError.value( + iv, + 'Initialization vector must be the same length as block size', + ); + } + _bytes.setAll(0, iv); + _cipher!.initialize(isEncryption, parameters.parameters); + } else { + _cipher!.initialize(isEncryption, parameters); + } + reset(); } else if (oldEncryption != _isEncryption) { - throw ArgumentError.value(oldEncryption, - 'cannot change encrypting state without providing key.'); + throw ArgumentError.value( + oldEncryption, + 'cannot change encrypting state without providing key.', + ); } } @override void reset() { - _cbcBytes = List.from(_bytes); - _cbcNextBytes = List.filled(_size!, 0, growable: true); + _cbcBytes!.setAll(0, _bytes); + _cbcNextBytes!.fillRange(0, _cbcNextBytes!.length, 0); + _cipher!.reset(); } @override - Map processBlock(List? inputBytes, int inputOffset, - List? outputBytes, int? outputOffset) { + Map processBlock( + List? inBytes, + int inOffset, + List? outBytes, + int? outOffset, + ) { return _isEncryption! - ? encryptBlock(inputBytes!, inputOffset, outputBytes, outputOffset!) - : decryptBlock(inputBytes!, inputOffset, outputBytes, outputOffset); + ? encryptionBlock(inBytes!, inOffset, outBytes, outOffset!) + : decryptionBlock(inBytes!, inOffset, outBytes, outOffset); } /// internal method - Map encryptBlock(List inputBytes, int inputOffset, - List? outputBytes, int outputOffset) { + Map encryptionBlock( + List inputBytes, + int inputOffset, + List? outputBytes, + int outputOffset, + ) { if ((inputOffset + _size!) > inputBytes.length) { throw ArgumentError.value('Invalid length in input bytes'); } @@ -77,90 +95,162 @@ class CipherBlockChainingMode implements ICipher { final Map result = _cipher!.processBlock(_cbcBytes, 0, outputBytes, outputOffset)!; outputBytes = result['output'] as List?; - List.copyRange(_cbcBytes!, 0, outputBytes!, outputOffset, - outputOffset + _cbcBytes!.length); + List.copyRange( + _cbcBytes!, + 0, + outputBytes!, + outputOffset, + outputOffset + _cbcBytes!.length, + ); return result; } /// internal method - Map decryptBlock(List inputBytes, int inputOffset, - List? outputBytes, int? outputOffset) { + Map decryptionBlock( + 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?; + _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 Uint8List? tempBytes = _cbcBytes; _cbcBytes = _cbcNextBytes; _cbcNextBytes = tempBytes; return {'length': result['length'], 'output': outputBytes}; } + + @override + int processingBlock( + Uint8List inputBytes, + int inputOffset, + Uint8List outputBytes, + int outputOffset, + ) { + return _isEncryption! + ? encryptBlock(inputBytes, inputOffset, outputBytes, outputOffset) + : decryptBlock(inputBytes, inputOffset, outputBytes, outputOffset); + } + + /// internal method + int encryptBlock( + Uint8List? inputBytes, + int inputOffset, + Uint8List? 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]; + } + final result = _cipher!.processingBlock( + _cbcBytes!, + 0, + outputBytes!, + outputOffset, + ); + _cbcBytes!.setRange( + 0, + _size!, + Uint8List.view( + outputBytes.buffer, + outputBytes.offsetInBytes + outputOffset, + _size, + ), + ); + return result!; + } + + /// internal method + int decryptBlock( + Uint8List inputBytes, + int inputOffset, + Uint8List outputBytes, + int outputOffset, + ) { + if ((inputOffset + _size!) > inputBytes.length) { + throw ArgumentError.value('Invalid length in input bytes'); + } + _cbcNextBytes!.setRange( + 0, + _size!, + Uint8List.view( + inputBytes.buffer, + inputBytes.offsetInBytes + inputOffset, + blockSize, + ), + ); + final result = _cipher!.processingBlock( + inputBytes, + inputOffset, + outputBytes, + outputOffset, + ); + for (int i = 0; i < _size!; i++) { + outputBytes[outputOffset + i] ^= _cbcBytes![i]; + } + final Uint8List? tempBytes = _cbcBytes; + _cbcBytes = _cbcNextBytes; + _cbcNextBytes = tempBytes; + return result!; + } } /// internal class -class InvalidParameter implements ICipherParameter { +class InvalidParameter + implements ICipherParameter { //Constructor /// internal constructor - InvalidParameter(this.parameters, List bytes, - [int? offset, int? length]) { - length ??= bytes.length; - offset ??= 0; - this.bytes = List.filled(length, 0, growable: true); - List.copyRange(this.bytes!, 0, bytes, offset, offset + length); - } + InvalidParameter(this.parameters, this.keys); //Fields /// internal field - ICipherParameter? parameters; + UnderlyingKeyParameters? parameters; /// internal field - List? bytes; - - //Properties - @override - List get keys => List.from(bytes!); - @override - set keys(List? value) { - bytes = value; - } + Uint8List? keys; } /// internal class -class KeyParameter implements ICipherParameter { - //Constructor - /// internal constructor - KeyParameter(List bytes) { - this.bytes = List.from(bytes); - } +class KeyParameter extends ICipherParameter { + KeyParameter(this.keys); - /// internal constructor - 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'); - } - this.bytes = List.generate(length, (int i) => 0); - List.copyRange(this.bytes!, 0, bytes, offset, offset + length); + KeyParameter.fromLengthValue(Uint8List keys, int keyOff, int keyLen) { + this.keys = Uint8List(keyLen); + copyArray(keys, keyOff, this.keys, 0, keyLen); } + late Uint8List keys; +} - //Fields - /// internal field - List? bytes; - - //Properties - @override - List get keys => List.from(bytes!); - @override - set keys(List? value) { - bytes = value; +void copyArray( + Uint8List? sourceArr, + int sourcePos, + Uint8List? outArr, + int outPos, + int len, +) { + for (var i = 0; i < len; i++) { + outArr![outPos + i] = sourceArr![sourcePos + i]; } } @@ -175,9 +265,7 @@ class CipherParameter implements ICipherParameter { /// internal property bool? get isPrivate => _privateKey; - @override List? get keys => null; - @override set keys(List? value) {} @override // ignore: avoid_equals_and_hash_code_on_mutable_classes @@ -197,8 +285,7 @@ class CipherParameter implements ICipherParameter { /// internal class class RsaKeyParam extends CipherParameter { /// internal constructor - RsaKeyParam(bool isPrivate, BigInt? modulus, BigInt? exponent) - : super(isPrivate) { + RsaKeyParam(super.isPrivate, BigInt? modulus, BigInt? exponent) { _modulus = modulus; _exponent = exponent; } @@ -237,9 +324,16 @@ class RsaKeyParam extends CipherParameter { /// internal class class RsaPrivateKeyParam extends RsaKeyParam { /// internal constructor - RsaPrivateKeyParam(BigInt? modulus, this.publicExponent, - BigInt? privateExponent, this.p, this.q, this.dP, this.dQ, this.inverse) - : super(true, modulus, privateExponent) { + RsaPrivateKeyParam( + BigInt? modulus, + this.publicExponent, + BigInt? privateExponent, + this.p, + this.q, + this.dP, + this.dQ, + this.inverse, + ) : super(true, modulus, privateExponent) { validateValue(publicExponent!); validateValue(p!); validateValue(q!); 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 index e8e86efc6..1ce939dba 100644 --- 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 @@ -1,31 +1,33 @@ -import 'dart:math'; +import 'dart:typed_data'; /// internal class -class IPadding { +abstract class IPadding { /// internal method - void initialize(Random? random) {} + void initialize([ICipherParameter? params]) {} /// internal property String? get paddingName => null; /// internal method - int? addPadding(List? bytes, int? offset) => null; + int addPadding(Uint8List data, int offset); /// internal method - int? count(List? bytes) => null; + int count(Uint8List data); } /// internal class -class IBufferedCipher { - /// internal property - String? get algorithmName => null; +class IBufferedCipher extends ICipher { + IPadding? get padding => null; + + ICipher? get cipher => null; + + @override + Uint8List? process(Uint8List data) => null; /// internal method + @override void initialize(bool forEncryption, ICipherParameter? parameters) {} - /// internal property - int? get blockSize => null; - /// internal method int? getOutputSize(int inputLen) => null; @@ -47,17 +49,30 @@ class IBufferedCipher { /// internal method Map? processByteFromValues( - List input, List output, int outOff) => - null; + List input, + List output, + int outOff, + ) => null; /// internal method - Map? processBytes(List input, int inOff, int length, - List output, int outOff) => - null; + Map? processBytes( + List input, + int inOff, + int length, + List output, + int outOff, + ) => null; /// internal method List? doFinal() => null; + int? doFinalProcess( + Uint8List inputBytes, + int inputOffset, + Uint8List outputBytes, + int outputOffset, + ) => null; + /// internal method List? doFinalFromInput(List? input) => null; @@ -69,25 +84,31 @@ class IBufferedCipher { /// internal method Map? copyFinal( - List input, List output, int outOff) => - null; + List input, + List output, + int outOff, + ) => null; /// internal method - Map? readFinalValues(List input, int inOff, int length, - List output, int outOff) => - null; + Map? readFinalValues( + List input, + int inOff, + int length, + List output, + int outOff, + ) => null; /// internal method + @override void reset() {} } /// internal class -class ICipher { - /// internal property +abstract class ICipher { String? get algorithmName => null; - /// internal method - void initialize(bool? isEncryption, ICipherParameter? parameters) {} + /// Initialize the cipher + void initialize(bool isEncryption, ICipherParameter? parameters); /// internal property int? get blockSize => null; @@ -95,13 +116,34 @@ class ICipher { /// internal property bool? get isBlock => null; - /// internal method - Map? processBlock(List? inBytes, int inOffset, - List? outBytes, int? outOffset) => - null; + /// Process a full block + int? processingBlock( + Uint8List inputBytes, + int inputOffset, + Uint8List outputBytes, + int outputOffset, + ) => null; + + Map? processBlock( + List? inBytes, + int inOffset, + List? outBytes, + int? outOffset, + ) => null; + + /// Reset cipher + void reset(); + + Uint8List? process(Uint8List data) => null; +} - /// internal method - void reset() {} +abstract class IBlockCipher extends ICipher { + @override + Uint8List process(Uint8List data) { + final out = Uint8List(blockSize!); + final len = processingBlock(data, 0, out, 0); + return out.sublist(0, len); + } } /// internal class @@ -123,10 +165,7 @@ class ICipherBlock { } /// internal class -class ICipherParameter { - /// internal field - List? keys; -} +abstract class ICipherParameter {} /// internal class class ISigner { @@ -139,6 +178,9 @@ class ISigner { /// internal method List? generateSignature() => null; + /// internal method + bool validateSignature(List bytes) => false; + /// internal method void reset() {} } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/message_digest_utils.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/message_digest_utils.dart new file mode 100644 index 000000000..c531da415 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/message_digest_utils.dart @@ -0,0 +1,1306 @@ +import 'package:crypto/crypto.dart'; +import '../asn1/der.dart'; +import '../pdf_signature_dictionary.dart'; +import '../pkcs/pfx_data.dart'; + +/// Internal class +class MessageDigestFinder { + /// Internal constructor + MessageDigestFinder() { + //Initialize the 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'; + //Initialize the ids. + _ids['SHA-1'] = DerObjectID('1.3.14.3.2.26'); + _ids['SHA-256'] = NistObjectIds.sha256; + _ids['SHA-384'] = NistObjectIds.sha384; + _ids['SHA-512'] = NistObjectIds.sha512; + _ids['MD5'] = PkcsObjectId.md5; + _ids['RIPEMD160'] = NistObjectIds.ripeMD160; + } + + /// Internal field + late final Map _algorithms = {}; + late final Map _ids = {}; + + /// Internal method + List getDigest(String algorithm, List bytes) { + final String upper = algorithm.toUpperCase(); + String? digest = _algorithms[upper]; + digest ??= upper; + digest = digest.toLowerCase(); + if (digest == 'sha1' || digest == 'sha-1' || digest == 'sha_1') { + return sha1.convert(bytes).bytes; + } else if (digest == 'sha256' || + digest == 'sha-256' || + digest == 'sha_256') { + return sha256.convert(bytes).bytes; + } else if (digest == 'sha384' || + digest == 'sha-384' || + digest == 'sha_384') { + return sha384.convert(bytes).bytes; + } else if (digest == 'sha512' || + digest == 'sha-512' || + digest == 'sha_512') { + return sha512.convert(bytes).bytes; + } else if (digest == 'md5' || digest == 'md-5' || digest == 'md_5') { + return md5.convert(bytes).bytes; + } else if (digest == 'ripemd160') { + final IMessageDigest digest = RIPEMD160MessageDigest(); + digest.updateWithBytes(bytes, 0, bytes.length); + return doFinal(digest); + } else { + throw ArgumentError.value( + algorithm, + 'hashAlgorithm', + 'Invalid message digest algorithm', + ); + } + } + + /// Internal method + List doFinal(IMessageDigest digest) { + final List bytes = List.filled( + digest.messageDigestSize!, + 0, + growable: true, + ); + digest.doFinal(bytes, 0); + return bytes; + } +} + +/// Internal class +abstract class IMessageDigest { + /// Retuns the agorithm name + String? algorithmName; + + /// Gets the size in bytes + int? messageDigestSize; + + /// Gets the buffer length + int? byteLength; + + /// Updates the message digit + void update(int input); + + /// Updates the message digit + void updateWithBytes(List bytes, int offset, int length); + + /// Updates the message digit with block of bytes + void blockUpdate(List bytes, int offset, int length); + + /// Retruns the final dighit values + int doFinal(List bytes, int offset); + + /// Reset + void reset(); +} + +/// Internal class +abstract class MessageDigest extends IMessageDigest { + /// Internal constructor + MessageDigest() { + _buf = List.filled(4, 0, growable: true); + } + + ///Internal Fields + late List _buf; + int _bufOff = 0; + int _byteCount = 0; + + /// Updates the message digit + @override + void update(int input) { + _buf[_bufOff++] = input; + if (_bufOff == _buf.length) { + processWord(_buf, 0); + _bufOff = 0; + } + _byteCount++; + } + + /// Updates the message digit + @override + void updateWithBytes(List bytes, int offset, int length) { + while ((_bufOff != 0) && (length > 0)) { + update(bytes[offset]); + offset++; + length--; + } + while (length > _buf.length) { + processWord(bytes, offset); + offset += _buf.length; + length -= _buf.length; + _byteCount += _buf.length; + } + while (length > 0) { + update(bytes[offset]); + offset++; + length--; + } + } + + /// Updates the message digit with block of bytes + @override + void blockUpdate(List bytes, int offset, int length) { + while ((_bufOff != 0) && (length > 0)) { + update(bytes[offset]); + offset++; + length--; + } + while (length > _buf.length) { + processWord(bytes, offset); + offset += _buf.length; + length -= _buf.length; + _byteCount += _buf.length; + } + while (length > 0) { + update(bytes[offset]); + offset++; + length--; + } + } + + /// Retruns the final dighit values + @override + int doFinal(List bytes, int offset); + + /// Reset + @override + void reset() { + _byteCount = 0; + _bufOff = 0; + _buf.clear(); + } + + /// Internal method + void finish() { + final int bitLength = _byteCount << 3; + update(128); + while (_bufOff != 0) { + update(0); + } + processLength(bitLength); + processBlock(); + } + + /// Internal method + void processWord(List input, int inOff); + + /// Internal method + void processLength(int bitLength); + + /// Internal method + void processBlock(); +} + +/// Internal class +class RIPEMD160MessageDigest extends MessageDigest { + /// Internal constructor + RIPEMD160MessageDigest() { + reset(); + } + + /// Internal field + // static const int _digestLength = 20; + late int _h0, _h1, _h2, _h3, _h4; + List _x = []; + late int _xOffset; + + /// Retuns the agorithm name + @override + String? get algorithmName => 'RIPEMD160'; + + /// Gets the size in bytes + @override + int? get messageDigestSize => 20; + + @override + void reset() { + super.reset(); + _h0 = 0x67452301; + _h1 = 0xefcdab89; + _h2 = 0x98badcfe; + _h3 = 0x10325476; + _h4 = 0xc3d2e1f0; + _xOffset = 0; + _x.clear(); + _x = List.filled(16, 0, growable: true); + } + + @override + void processWord(List input, int inOff) { + _x[_xOffset++] = + (input[inOff] & 0xff) | + ((input[inOff + 1] & 0xff) << 8) | + ((input[inOff + 2] & 0xff) << 16) | + ((input[inOff + 3] & 0xff) << 24); + if (_xOffset == 16) { + processBlock(); + } + } + + @override + void processLength(int bitLength) { + if (_xOffset > 14) { + processBlock(); + } + _x[14] = bitLength & 0xffffffff; + _x[15] = bitLength.toUnsigned(64) >> 32; + } + + @override + int doFinal(List bytes, int offset) { + finish(); + unpackWord(_h0, bytes, offset); + unpackWord(_h1, bytes, offset + 4); + unpackWord(_h2, bytes, offset + 8); + unpackWord(_h3, bytes, offset + 12); + unpackWord(_h4, bytes, offset + 16); + reset(); + return messageDigestSize!; + } + + @override + void processBlock() { + int a, f; + int b, g; + int c, h; + int d, i; + int e, j; + a = f = _h0; + b = g = _h1; + c = h = _h2; + d = i = _h3; + e = j = _h4; + a = getRightToLeft(a + getBitLevelEXOR(b, c, d) + _x[0], 11) + e; + c = getRightToLeft(c, 10); + e = getRightToLeft(e + getBitLevelEXOR(a, b, c) + _x[1], 14) + d; + b = getRightToLeft(b, 10); + d = getRightToLeft(d + getBitLevelEXOR(e, a, b) + _x[2], 15) + c; + a = getRightToLeft(a, 10); + c = getRightToLeft(c + getBitLevelEXOR(d, e, a) + _x[3], 12) + b; + e = getRightToLeft(e, 10); + b = getRightToLeft(b + getBitLevelEXOR(c, d, e) + _x[4], 5) + a; + d = getRightToLeft(d, 10); + a = getRightToLeft(a + getBitLevelEXOR(b, c, d) + _x[5], 8) + e; + c = getRightToLeft(c, 10); + e = getRightToLeft(e + getBitLevelEXOR(a, b, c) + _x[6], 7) + d; + b = getRightToLeft(b, 10); + d = getRightToLeft(d + getBitLevelEXOR(e, a, b) + _x[7], 9) + c; + a = getRightToLeft(a, 10); + c = getRightToLeft(c + getBitLevelEXOR(d, e, a) + _x[8], 11) + b; + e = getRightToLeft(e, 10); + b = getRightToLeft(b + getBitLevelEXOR(c, d, e) + _x[9], 13) + a; + d = getRightToLeft(d, 10); + a = getRightToLeft(a + getBitLevelEXOR(b, c, d) + _x[10], 14) + e; + c = getRightToLeft(c, 10); + e = getRightToLeft(e + getBitLevelEXOR(a, b, c) + _x[11], 15) + d; + b = getRightToLeft(b, 10); + d = getRightToLeft(d + getBitLevelEXOR(e, a, b) + _x[12], 6) + c; + a = getRightToLeft(a, 10); + c = getRightToLeft(c + getBitLevelEXOR(d, e, a) + _x[13], 7) + b; + e = getRightToLeft(e, 10); + b = getRightToLeft(b + getBitLevelEXOR(c, d, e) + _x[14], 9) + a; + d = getRightToLeft(d, 10); + a = getRightToLeft(a + getBitLevelEXOR(b, c, d) + _x[15], 8) + e; + c = getRightToLeft(c, 10); + + f = + getRightToLeft( + f + getBitlevelReverseNegative(g, h, i) + _x[5] + 1352829926, + 8, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelReverseNegative(f, g, h) + _x[14] + 1352829926, + 9, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelReverseNegative(j, f, g) + _x[7] + 1352829926, + 9, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelReverseNegative(i, j, f) + _x[0] + 1352829926, + 11, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelReverseNegative(h, i, j) + _x[9] + 1352829926, + 13, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelReverseNegative(g, h, i) + _x[2] + 1352829926, + 15, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelReverseNegative(f, g, h) + _x[11] + 1352829926, + 15, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelReverseNegative(j, f, g) + _x[4] + 1352829926, + 5, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelReverseNegative(i, j, f) + _x[13] + 1352829926, + 7, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelReverseNegative(h, i, j) + _x[6] + 1352829926, + 7, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelReverseNegative(g, h, i) + _x[15] + 1352829926, + 8, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelReverseNegative(f, g, h) + _x[8] + 1352829926, + 11, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelReverseNegative(j, f, g) + _x[1] + 1352829926, + 14, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelReverseNegative(i, j, f) + _x[10] + 1352829926, + 14, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelReverseNegative(h, i, j) + _x[3] + 1352829926, + 12, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelReverseNegative(g, h, i) + _x[12] + 1352829926, + 6, + ) + + j; + h = getRightToLeft(h, 10); + + e = + getRightToLeft( + e + getBitlevelMultiplexer(a, b, c) + _x[7] + 1518500249, + 7, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelMultiplexer(e, a, b) + _x[4] + 1518500249, + 6, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelMultiplexer(d, e, a) + _x[13] + 1518500249, + 8, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelMultiplexer(c, d, e) + _x[1] + 1518500249, + 13, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelMultiplexer(b, c, d) + _x[10] + 1518500249, + 11, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelMultiplexer(a, b, c) + _x[6] + 1518500249, + 9, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelMultiplexer(e, a, b) + _x[15] + 1518500249, + 7, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelMultiplexer(d, e, a) + _x[3] + 1518500249, + 15, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelMultiplexer(c, d, e) + _x[12] + 1518500249, + 7, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelMultiplexer(b, c, d) + _x[0] + 1518500249, + 12, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelMultiplexer(a, b, c) + _x[9] + 1518500249, + 15, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelMultiplexer(e, a, b) + _x[5] + 1518500249, + 9, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelMultiplexer(d, e, a) + _x[2] + 1518500249, + 11, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelMultiplexer(c, d, e) + _x[14] + 1518500249, + 7, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelMultiplexer(b, c, d) + _x[11] + 1518500249, + 13, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelMultiplexer(a, b, c) + _x[8] + 1518500249, + 12, + ) + + d; + b = getRightToLeft(b, 10); + + j = + getRightToLeft( + j + getBitlevelDemultiplexer(f, g, h) + _x[6] + 1548603684, + 9, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelDemultiplexer(j, f, g) + _x[11] + 1548603684, + 13, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelDemultiplexer(i, j, f) + _x[3] + 1548603684, + 15, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelDemultiplexer(h, i, j) + _x[7] + 1548603684, + 7, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelDemultiplexer(g, h, i) + _x[0] + 1548603684, + 12, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelDemultiplexer(f, g, h) + _x[13] + 1548603684, + 8, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelDemultiplexer(j, f, g) + _x[5] + 1548603684, + 9, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelDemultiplexer(i, j, f) + _x[10] + 1548603684, + 11, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelDemultiplexer(h, i, j) + _x[14] + 1548603684, + 7, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelDemultiplexer(g, h, i) + _x[15] + 1548603684, + 7, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelDemultiplexer(f, g, h) + _x[8] + 1548603684, + 12, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelDemultiplexer(j, f, g) + _x[12] + 1548603684, + 7, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelDemultiplexer(i, j, f) + _x[4] + 1548603684, + 6, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelDemultiplexer(h, i, j) + _x[9] + 1548603684, + 15, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelDemultiplexer(g, h, i) + _x[1] + 1548603684, + 13, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelDemultiplexer(f, g, h) + _x[2] + 1548603684, + 11, + ) + + i; + g = getRightToLeft(g, 10); + + d = + getRightToLeft( + d + getBitlevelNegative(e, a, b) + _x[3] + 1859775393, + 11, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelNegative(d, e, a) + _x[10] + 1859775393, + 13, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelNegative(c, d, e) + _x[14] + 1859775393, + 6, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelNegative(b, c, d) + _x[4] + 1859775393, + 7, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelNegative(a, b, c) + _x[9] + 1859775393, + 14, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelNegative(e, a, b) + _x[15] + 1859775393, + 9, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelNegative(d, e, a) + _x[8] + 1859775393, + 13, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelNegative(c, d, e) + _x[1] + 1859775393, + 15, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelNegative(b, c, d) + _x[2] + 1859775393, + 14, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelNegative(a, b, c) + _x[7] + 1859775393, + 8, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelNegative(e, a, b) + _x[0] + 1859775393, + 13, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelNegative(d, e, a) + _x[6] + 1859775393, + 6, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelNegative(c, d, e) + _x[13] + 1859775393, + 5, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelNegative(b, c, d) + _x[11] + 1859775393, + 12, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelNegative(a, b, c) + _x[5] + 1859775393, + 7, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelNegative(e, a, b) + _x[12] + 1859775393, + 5, + ) + + c; + a = getRightToLeft(a, 10); + + i = + getRightToLeft( + i + getBitlevelNegative(j, f, g) + _x[15] + 1836072691, + 9, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelNegative(i, j, f) + _x[5] + 1836072691, + 7, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelNegative(h, i, j) + _x[1] + 1836072691, + 15, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelNegative(g, h, i) + _x[3] + 1836072691, + 11, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelNegative(f, g, h) + _x[7] + 1836072691, + 8, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelNegative(j, f, g) + _x[14] + 1836072691, + 6, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelNegative(i, j, f) + _x[6] + 1836072691, + 6, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelNegative(h, i, j) + _x[9] + 1836072691, + 14, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelNegative(g, h, i) + _x[11] + 1836072691, + 12, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelNegative(f, g, h) + _x[8] + 1836072691, + 13, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelNegative(j, f, g) + _x[12] + 1836072691, + 5, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelNegative(i, j, f) + _x[2] + 1836072691, + 14, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelNegative(h, i, j) + _x[10] + 1836072691, + 13, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelNegative(g, h, i) + _x[0] + 1836072691, + 13, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelNegative(f, g, h) + _x[4] + 1836072691, + 7, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelNegative(j, f, g) + _x[13] + 1836072691, + 5, + ) + + h; + f = getRightToLeft(f, 10); + + c = + getRightToLeft( + c + getBitlevelDemultiplexer(d, e, a) + _x[1] - 1894007588, + 11, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelDemultiplexer(c, d, e) + _x[9] - 1894007588, + 12, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelDemultiplexer(b, c, d) + _x[11] - 1894007588, + 14, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelDemultiplexer(a, b, c) + _x[10] - 1894007588, + 15, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelDemultiplexer(e, a, b) + _x[0] - 1894007588, + 14, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelDemultiplexer(d, e, a) + _x[8] - 1894007588, + 15, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelDemultiplexer(c, d, e) + _x[12] - 1894007588, + 9, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelDemultiplexer(b, c, d) + _x[4] - 1894007588, + 8, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelDemultiplexer(a, b, c) + _x[13] - 1894007588, + 9, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelDemultiplexer(e, a, b) + _x[3] - 1894007588, + 14, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelDemultiplexer(d, e, a) + _x[7] - 1894007588, + 5, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelDemultiplexer(c, d, e) + _x[15] - 1894007588, + 6, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelDemultiplexer(b, c, d) + _x[14] - 1894007588, + 8, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelDemultiplexer(a, b, c) + _x[5] - 1894007588, + 6, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelDemultiplexer(e, a, b) + _x[6] - 1894007588, + 5, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelDemultiplexer(d, e, a) + _x[2] - 1894007588, + 12, + ) + + b; + e = getRightToLeft(e, 10); + + h = + getRightToLeft( + h + getBitlevelMultiplexer(i, j, f) + _x[8] + 2053994217, + 15, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelMultiplexer(h, i, j) + _x[6] + 2053994217, + 5, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelMultiplexer(g, h, i) + _x[4] + 2053994217, + 8, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelMultiplexer(f, g, h) + _x[1] + 2053994217, + 11, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelMultiplexer(j, f, g) + _x[3] + 2053994217, + 14, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelMultiplexer(i, j, f) + _x[11] + 2053994217, + 14, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelMultiplexer(h, i, j) + _x[15] + 2053994217, + 6, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelMultiplexer(g, h, i) + _x[0] + 2053994217, + 14, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelMultiplexer(f, g, h) + _x[5] + 2053994217, + 6, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelMultiplexer(j, f, g) + _x[12] + 2053994217, + 9, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelMultiplexer(i, j, f) + _x[2] + 2053994217, + 12, + ) + + g; + j = getRightToLeft(j, 10); + g = + getRightToLeft( + g + getBitlevelMultiplexer(h, i, j) + _x[13] + 2053994217, + 9, + ) + + f; + i = getRightToLeft(i, 10); + f = + getRightToLeft( + f + getBitlevelMultiplexer(g, h, i) + _x[9] + 2053994217, + 12, + ) + + j; + h = getRightToLeft(h, 10); + j = + getRightToLeft( + j + getBitlevelMultiplexer(f, g, h) + _x[7] + 2053994217, + 5, + ) + + i; + g = getRightToLeft(g, 10); + i = + getRightToLeft( + i + getBitlevelMultiplexer(j, f, g) + _x[10] + 2053994217, + 15, + ) + + h; + f = getRightToLeft(f, 10); + h = + getRightToLeft( + h + getBitlevelMultiplexer(i, j, f) + _x[14] + 2053994217, + 8, + ) + + g; + j = getRightToLeft(j, 10); + + b = + getRightToLeft( + b + getBitlevelReverseNegative(c, d, e) + _x[4] - 1454113458, + 9, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelReverseNegative(b, c, d) + _x[0] - 1454113458, + 15, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelReverseNegative(a, b, c) + _x[5] - 1454113458, + 5, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelReverseNegative(e, a, b) + _x[9] - 1454113458, + 11, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelReverseNegative(d, e, a) + _x[7] - 1454113458, + 6, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelReverseNegative(c, d, e) + _x[12] - 1454113458, + 8, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelReverseNegative(b, c, d) + _x[2] - 1454113458, + 13, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelReverseNegative(a, b, c) + _x[10] - 1454113458, + 12, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelReverseNegative(e, a, b) + _x[14] - 1454113458, + 5, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelReverseNegative(d, e, a) + _x[1] - 1454113458, + 12, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelReverseNegative(c, d, e) + _x[3] - 1454113458, + 13, + ) + + a; + d = getRightToLeft(d, 10); + a = + getRightToLeft( + a + getBitlevelReverseNegative(b, c, d) + _x[8] - 1454113458, + 14, + ) + + e; + c = getRightToLeft(c, 10); + e = + getRightToLeft( + e + getBitlevelReverseNegative(a, b, c) + _x[11] - 1454113458, + 11, + ) + + d; + b = getRightToLeft(b, 10); + d = + getRightToLeft( + d + getBitlevelReverseNegative(e, a, b) + _x[6] - 1454113458, + 8, + ) + + c; + a = getRightToLeft(a, 10); + c = + getRightToLeft( + c + getBitlevelReverseNegative(d, e, a) + _x[15] - 1454113458, + 5, + ) + + b; + e = getRightToLeft(e, 10); + b = + getRightToLeft( + b + getBitlevelReverseNegative(c, d, e) + _x[13] - 1454113458, + 6, + ) + + a; + d = getRightToLeft(d, 10); + + g = getRightToLeft(g + getBitLevelEXOR(h, i, j) + _x[12], 8) + f; + i = getRightToLeft(i, 10); + f = getRightToLeft(f + getBitLevelEXOR(g, h, i) + _x[15], 5) + j; + h = getRightToLeft(h, 10); + j = getRightToLeft(j + getBitLevelEXOR(f, g, h) + _x[10], 12) + i; + g = getRightToLeft(g, 10); + i = getRightToLeft(i + getBitLevelEXOR(j, f, g) + _x[4], 9) + h; + f = getRightToLeft(f, 10); + h = getRightToLeft(h + getBitLevelEXOR(i, j, f) + _x[1], 12) + g; + j = getRightToLeft(j, 10); + g = getRightToLeft(g + getBitLevelEXOR(h, i, j) + _x[5], 5) + f; + i = getRightToLeft(i, 10); + f = getRightToLeft(f + getBitLevelEXOR(g, h, i) + _x[8], 14) + j; + h = getRightToLeft(h, 10); + j = getRightToLeft(j + getBitLevelEXOR(f, g, h) + _x[7], 6) + i; + g = getRightToLeft(g, 10); + i = getRightToLeft(i + getBitLevelEXOR(j, f, g) + _x[6], 8) + h; + f = getRightToLeft(f, 10); + h = getRightToLeft(h + getBitLevelEXOR(i, j, f) + _x[2], 13) + g; + j = getRightToLeft(j, 10); + g = getRightToLeft(g + getBitLevelEXOR(h, i, j) + _x[13], 6) + f; + i = getRightToLeft(i, 10); + f = getRightToLeft(f + getBitLevelEXOR(g, h, i) + _x[14], 5) + j; + h = getRightToLeft(h, 10); + j = getRightToLeft(j + getBitLevelEXOR(f, g, h) + _x[0], 15) + i; + g = getRightToLeft(g, 10); + i = getRightToLeft(i + getBitLevelEXOR(j, f, g) + _x[3], 13) + h; + f = getRightToLeft(f, 10); + h = getRightToLeft(h + getBitLevelEXOR(i, j, f) + _x[9], 11) + g; + j = getRightToLeft(j, 10); + g = getRightToLeft(g + getBitLevelEXOR(h, i, j) + _x[11], 11) + f; + i = getRightToLeft(i, 10); + + i += c + _h1; + _h1 = _h2 + d + j; + _h2 = _h3 + e + f; + _h3 = _h4 + a + g; + _h4 = _h0 + b + h; + _h0 = i; + + _xOffset = 0; + _x.fillRange(0, 16, 0); + } + + /// Internal method + void unpackWord(int word, List output, int offset) { + output[offset] = word.toUnsigned(8); + output[offset + 1] = (word.toUnsigned(32) >> 8).toUnsigned(8); + output[offset + 2] = (word.toUnsigned(32) >> 16).toUnsigned(8); + output[offset + 3] = (word.toUnsigned(32) >> 24).toUnsigned(8); + } + + /// Internal method + int getRightToLeft(int x, int n) { + return (x << n) | (x.toUnsigned(32) >> (32 - n)); + } + + /// Internal method + int getBitLevelEXOR(int x, int y, int z) { + return x ^ y ^ z; + } + + /// Internal method + int getBitlevelMultiplexer(int x, int y, int z) { + return (x & y) | (~x & z); + } + + /// Internal method + int getBitlevelNegative(int x, int y, int z) { + return (x | ~y) ^ z; + } + + /// Internal method + int getBitlevelDemultiplexer(int x, int y, int z) { + return (x & z) | (y & ~z); + } + + /// Internal method + int getBitlevelReverseNegative(int x, int y, int z) { + return x ^ (y | ~z); + } +} 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 index 6cb99b73c..6820302ab 100644 --- 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 @@ -54,7 +54,9 @@ class Pkcs1Encoding implements ICipherBlock { } } else { block = List.generate( - _cipher!.inputBlock!, (int i) => _random.nextInt(256)); + _cipher!.inputBlock!, + (int i) => _random.nextInt(256), + ); block[0] = 0x02; for (int i = 1; i != block.length - inLen - 1; i++) { while (block[i] == 0) { @@ -72,7 +74,10 @@ class Pkcs1Encoding implements ICipherBlock { final List block = _cipher!.processBlock(input, inOff, inLen)!; if (block.length < outputBlock!) { throw ArgumentError.value( - inLen, 'inLen', 'Invalid block. Block truncated'); + inLen, + 'inLen', + 'Invalid block. Block truncated', + ); } final int type = block[0]; if (type != 1 && type != 2) { @@ -95,8 +100,10 @@ class Pkcs1Encoding implements ICipherBlock { if (start > block.length || start < 10) { throw ArgumentError.value(start, 'start', 'no data in block'); } - final List result = - List.generate(block.length - start, (int i) => 0); + final List result = List.generate( + block.length - start, + (int 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/rsa_algorithm.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart index 8b1ac4ece..9298470b6 100644 --- 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 @@ -42,8 +42,11 @@ class RsaAlgorithm implements ICipherBlock { 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 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); @@ -117,8 +120,10 @@ class _RsaCoreAlgorithm { if (length > maxLength) { throw ArgumentError.value(length, 'length', 'Invalid length in inputs'); } - final BigInt input = - bigIntFromBytes(bytes.sublist(offset, offset + length), 1); + 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'); } @@ -132,9 +137,11 @@ class _RsaCoreAlgorithm { if (output.length < outSize) { final List bytes = List.generate(outSize, (int i) => 0); int j = 0; - for (int i = bytes.length - output.length; - j < output.length && i < bytes.length; - i++) { + for ( + int i = bytes.length - output.length; + j < output.length && i < bytes.length; + i++ + ) { bytes[i] = output[j]; j += 1; } @@ -152,8 +159,8 @@ class _RsaCoreAlgorithm { 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); + 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); 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 index bb775f252..44df4f9e8 100644 --- 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 @@ -97,8 +97,10 @@ BigInt bigIntFromRamdom(int value, Random? random) { result = BigInt.from(0); } else { final int nBytes = (value + 8 - 1) ~/ 8; - final List b = - List.generate(nBytes, (int i) => random!.nextInt(256)); + final List b = List.generate( + nBytes, + (int i) => random!.nextInt(256), + ); final int xBits = 8 * nBytes - value; b[0] &= (255 >> xBits).toUnsigned(8); result = bigIntFromBytes(b); 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 index 249839a97..b936ecb6b 100644 --- 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 @@ -37,6 +37,30 @@ class PdfCertificate { /// Gets the date and time after which the certificate is not valid. DateTime get validFrom => _validFrom!; + /// Gets the certificate chain in raw bytes. + /// + /// ```dart + /// //Load the certificate from the file + /// final PdfCertificate certificate = + /// PdfCertificate(File('PDF.pfx').readAsBytesSync(), 'password'); + /// //Get the certificate chain + /// List>? certificateChain = certificate.getCertificateChain(); + /// ``` + List>? getCertificateChain() { + List>? certificateChain; + try { + certificateChain = >[]; + _pkcsCertificate.getChainCertificates().forEach((X509Certificates entry) { + final List certificateBytes = + entry.certificate!.c!.getDerEncoded()!; + certificateChain!.add(certificateBytes); + }); + } catch (e) { + return null; + } + return certificateChain; + } + //Implementation void _initializeCertificate(List certificateBytes, String password) { _distinguishedNameCollection = >{}; @@ -58,16 +82,21 @@ class PdfCertificate { } void _loadDetails(X509Certificate certificate) { - _issuerName = - _getDistinguishedAttributes(certificate.c!.issuer.toString(), 'CN'); - _subjectName = - _getDistinguishedAttributes(certificate.c!.subject.toString(), 'CN'); + _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 = [] - // ignore: prefer_spread_collections - ..addAll(certificate.c!.serialNumber!.intValue!.reversed.toList()); + final List serialNumber = + [] + // ignore: prefer_spread_collections + ..addAll(certificate.c!.serialNumber!.intValue!.reversed.toList()); _serialNumber = serialNumber; } @@ -120,8 +149,10 @@ class PdfCertificate { void _addStringToDictionary(String name, Map? dictionary) { int index = name.indexOf('='); if (index > 0) { - final List keyNameArray = - List.generate(2, (int i) => null); + final List keyNameArray = List.generate( + 2, + (int i) => null, + ); keyNameArray[0] = name.substring(0, index).trimLeft().trimRight(); index++; keyNameArray[1] = @@ -148,7 +179,9 @@ class PdfCertificateHelper { /// internal method static void setPkcsCertificate( - PdfCertificate certificate, PdfPKCSCertificate pkcsCertificate) { + PdfCertificate certificate, + PdfPKCSCertificate pkcsCertificate, + ) { certificate._pkcsCertificate = pkcsCertificate; } } 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 index edd97da9b..e518af6b2 100644 --- 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 @@ -11,8 +11,13 @@ class IPdfExternalSigner { DigestAlgorithm get hashAlgorithm => _hashAlgorithm; //Public methods - /// Returns Signed Message Digest. - SignerResult? sign(List message) { + /// Asynchronously returns signed message digest. + Future sign(List message) async { + return null; + } + + /// Synchronously returns signed message digest. + SignerResult? signSync(List message) { return null; } } 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 index d376e795d..5a0eb325c 100644 --- 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 @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:typed_data'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; @@ -14,6 +15,7 @@ import 'cryptography/buffered_block_padding_base.dart'; import 'cryptography/cipher_block_chaining_mode.dart'; import 'cryptography/cipher_utils.dart'; import 'cryptography/ipadding.dart'; +import 'cryptography/message_digest_utils.dart'; import 'cryptography/pkcs1_encoding.dart'; import 'cryptography/rsa_algorithm.dart'; import 'cryptography/signature_utilities.dart'; @@ -43,8 +45,9 @@ class PdfPKCSCertificate { //Implementation void _loadCertificate(List certificateBytes, String password) { - final Asn1Sequence sequence = Asn1Stream(PdfStreamReader(certificateBytes)) - .readAsn1()! as Asn1Sequence; + final Asn1Sequence sequence = + Asn1Stream(PdfStreamReader(certificateBytes)).readAsn1()! + as Asn1Sequence; final _PfxData pfxData = _PfxData(sequence); final _ContentInformation information = pfxData._contentInformation!; bool isUnmarkedKey = false; @@ -54,12 +57,13 @@ class PdfPKCSCertificate { if (information._contentType!.id == PkcsObjectId.data.id) { final List? octs = (information._content! as Asn1Octet).getOctets(); final Asn1Sequence asn1Sequence = - (Asn1Stream(PdfStreamReader(octs)).readAsn1())! as Asn1Sequence; + Asn1Stream(PdfStreamReader(octs)).readAsn1()! as Asn1Sequence; final List<_ContentInformation?> contentInformation = <_ContentInformation?>[]; for (int i = 0; i < asn1Sequence.count; i++) { - contentInformation - .add(_ContentInformation.getInformation(asn1Sequence[i])); + contentInformation.add( + _ContentInformation.getInformation(asn1Sequence[i]), + ); } // ignore: avoid_function_literals_in_foreach_calls contentInformation.forEach((_ContentInformation? entry) { @@ -67,7 +71,7 @@ class PdfPKCSCertificate { if (type.id == PkcsObjectId.data.id) { final List? octets = (entry._content! as Asn1Octet).getOctets(); final Asn1Sequence asn1SubSequence = - (Asn1Stream(PdfStreamReader(octets)).readAsn1())! as Asn1Sequence; + Asn1Stream(PdfStreamReader(octets)).readAsn1()! as Asn1Sequence; for (int index = 0; index < asn1SubSequence.count; index++) { final dynamic subSequence = asn1SubSequence[index]; if (subSequence != null && subSequence is Asn1Sequence) { @@ -75,29 +79,36 @@ class PdfPKCSCertificate { Asn1SequenceCollection(subSequence); if (subSequenceCollection.id!.id == PkcsObjectId.pkcs8ShroudedKeyBag.id) { - final _EncryptedPrivateKey encryptedInformation = - _EncryptedPrivateKey.getEncryptedPrivateKeyInformation( - subSequenceCollection.value); - final _KeyInformation privateKeyInformation = + final EncryptedPrivateKey encryptedInformation = + EncryptedPrivateKey.getEncryptedPrivateKeyInformation( + subSequenceCollection.value, + ); + final KeyInformation privateKeyInformation = createPrivateKeyInfo( - password, isInvalidPassword, encryptedInformation)!; + password, + isInvalidPassword, + encryptedInformation, + )!; RsaPrivateKeyParam? rsaparam; if (privateKeyInformation._algorithms!.id!.id == PkcsObjectId.rsaEncryption.id || privateKeyInformation._algorithms!.id!.id == X509Objects.idEARsa.id) { final _RsaKey keyStructure = _RsaKey.fromSequence( - Asn1Sequence.getSequence( - privateKeyInformation._privateKey)!); + Asn1Sequence.getSequence( + privateKeyInformation._privateKey, + )!, + ); rsaparam = RsaPrivateKeyParam( - keyStructure._modulus, - keyStructure._publicExponent, - keyStructure._privateExponent, - keyStructure._prime1, - keyStructure._prime2, - keyStructure._exponent1, - keyStructure._exponent2, - keyStructure._coefficient); + keyStructure._modulus, + keyStructure._publicExponent, + keyStructure._privateExponent, + keyStructure._prime1, + keyStructure._prime2, + keyStructure._exponent1, + keyStructure._exponent2, + keyStructure._coefficient, + ); } final CipherParameter? privateKey = rsaparam; final Map attributes = {}; @@ -109,16 +120,20 @@ class PdfPKCSCertificate { for (int i = 0; i < sq.objects.length; i++) { final Asn1Encode? entry = sq.objects[i] as Asn1Encode?; if (entry is Asn1Sequence) { - final DerObjectID? algorithmId = - DerObjectID.getID(entry[0]); + final DerObjectID? algorithmId = DerObjectID.getID( + entry[0], + ); final Asn1Set attributeSet = entry[1]! as Asn1Set; Asn1Encode? attribute; if (attributeSet.objects.isNotEmpty) { 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'); + throw ArgumentError.value( + attributes, + 'attributes', + 'attempt to add existing attribute with different value', + ); } } else { attributes[algorithmId.id] = attribute; @@ -137,8 +152,9 @@ class PdfPKCSCertificate { } } if (localId != null) { - final String name = - PdfString.bytesToHex(localId.getOctets()!); + final String name = PdfString.bytesToHex( + localId.getOctets()!, + ); if (localIdentifier == null) { _keys.setValue(name, key); } else { @@ -158,13 +174,19 @@ class PdfPKCSCertificate { final Asn1Sequence sequence1 = entry._content! as Asn1Sequence; if (sequence1.count != 2) { throw ArgumentError.value( - entry, 'sequence', 'Invalid length of the sequence'); + 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'); + version, + 'version', + 'Invalid sequence version', + ); } final Asn1Sequence data = sequence1[1]! as Asn1Sequence; Asn1Octet? content; @@ -173,11 +195,12 @@ class PdfPKCSCertificate { content = Asn1Octet.getOctetString(taggedObject, false); } final List? octets = getCryptographicData( - false, - Algorithms.getAlgorithms(data[1])!, - password, - isInvalidPassword, - content!.getOctets()); + false, + Algorithms.getAlgorithms(data[1])!, + password, + isInvalidPassword, + content!.getOctets(), + ); final Asn1Sequence seq = Asn1Stream(PdfStreamReader(octets)).readAsn1()! as Asn1Sequence; // ignore: avoid_function_literals_in_foreach_calls @@ -188,27 +211,34 @@ class PdfPKCSCertificate { 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)!; + final EncryptedPrivateKey encryptedPrivateKeyInformation = + EncryptedPrivateKey.getEncryptedPrivateKeyInformation( + subSequenceCollection.value, + ); + final KeyInformation privateInformation = + createPrivateKeyInfo( + password, + isInvalidPassword, + encryptedPrivateKeyInformation, + )!; RsaPrivateKeyParam? rsaParameter; if (privateInformation._algorithms!.id!.id == PkcsObjectId.rsaEncryption.id || privateInformation._algorithms!.id!.id == X509Objects.idEARsa.id) { final _RsaKey keyStructure = _RsaKey.fromSequence( - Asn1Sequence.getSequence(privateInformation._privateKey)!); + Asn1Sequence.getSequence(privateInformation._privateKey)!, + ); rsaParameter = RsaPrivateKeyParam( - keyStructure._modulus, - keyStructure._publicExponent, - keyStructure._privateExponent, - keyStructure._prime1, - keyStructure._prime2, - keyStructure._exponent1, - keyStructure._exponent2, - keyStructure._coefficient); + keyStructure._modulus, + keyStructure._publicExponent, + keyStructure._privateExponent, + keyStructure._prime1, + keyStructure._prime2, + keyStructure._exponent1, + keyStructure._exponent2, + keyStructure._coefficient, + ); } final CipherParameter? privateKey = rsaParameter; final Map attributes = {}; @@ -224,8 +254,11 @@ class PdfPKCSCertificate { attribute = attributeSet.objects[0] as Asn1Encode?; if (attributes.containsKey(asn1Id!.id)) { if (!(attributes[asn1Id.id] == attribute)) { - throw ArgumentError.value(attributes, 'attributes', - 'attempt to add existing attribute with different value'); + throw ArgumentError.value( + attributes, + 'attributes', + 'attempt to add existing attribute with different value', + ); } } else { attributes[asn1Id.id] = attribute; @@ -238,33 +271,35 @@ class PdfPKCSCertificate { } } }); - final String name = - PdfString.bytesToHex(localIdentity!.getOctets()!); + 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)!; + final KeyInformation privateKeyInformation = + KeyInformation.getInformation(subSequenceCollection.value)!; RsaPrivateKeyParam? rsaParameter; if (privateKeyInformation._algorithms!.id!.id == PkcsObjectId.rsaEncryption.id || privateKeyInformation._algorithms!.id!.id == X509Objects.idEARsa.id) { final _RsaKey keyStructure = _RsaKey.fromSequence( - Asn1Sequence.getSequence( - privateKeyInformation._privateKey)!); + Asn1Sequence.getSequence(privateKeyInformation._privateKey)!, + ); rsaParameter = RsaPrivateKeyParam( - keyStructure._modulus, - keyStructure._publicExponent, - keyStructure._privateExponent, - keyStructure._prime1, - keyStructure._prime2, - keyStructure._exponent1, - keyStructure._exponent2, - keyStructure._coefficient); + keyStructure._modulus, + keyStructure._publicExponent, + keyStructure._privateExponent, + keyStructure._prime1, + keyStructure._prime2, + keyStructure._exponent1, + keyStructure._exponent2, + keyStructure._coefficient, + ); } final CipherParameter? privateKey = rsaParameter; String? key; @@ -281,8 +316,11 @@ class PdfPKCSCertificate { if (attributes.containsKey(id!.id)) { final Asn1Encode? attr = attributes[id.id] as Asn1Encode?; if (attr != null && attr != attribute) { - throw ArgumentError.value(sq, 'sequence', - 'attempt to add existing attribute with different value'); + throw ArgumentError.value( + sq, + 'sequence', + 'attempt to add existing attribute with different value', + ); } } else { attributes[id.id] = attribute; @@ -329,8 +367,11 @@ class PdfPKCSCertificate { 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'); + throw ArgumentError.value( + attributes, + 'attributes', + 'attempt to add existing attribute with different value', + ); } } else { attributes[aOid.id] = attr; @@ -343,10 +384,12 @@ class PdfPKCSCertificate { } } } - final _CertificateIdentifier certId = - _CertificateIdentifier(pubKey: certificate.getPublicKey()); - final X509Certificates certificateCollection = - X509Certificates(certificate); + final _CertificateIdentifier certId = _CertificateIdentifier( + pubKey: certificate.getPublicKey(), + ); + final X509Certificates certificateCollection = X509Certificates( + certificate, + ); _chainCertificates[certId] = certificateCollection; if (isUnmarkedKey) { if (_keyCertificates.isEmpty) { @@ -369,8 +412,13 @@ class PdfPKCSCertificate { } /// internal method - static List? getCryptographicData(bool forEncryption, Algorithms id, - String password, bool isZero, List? data) { + static List? getCryptographicData( + bool forEncryption, + Algorithms id, + String password, + bool isZero, + List? data, + ) { final _PasswordUtility utility = _PasswordUtility(); final IBufferedCipher? cipher = utility.createEncoder(id.id) as IBufferedCipher?; @@ -380,38 +428,54 @@ class PdfPKCSCertificate { final _Pkcs12PasswordParameter parameter = _Pkcs12PasswordParameter.getPbeParameter(id.parameters); final ICipherParameter? parameters = utility.generateCipherParameters( - id.id!.id!, password, isZero, parameter); + id.id!.id!, + password, + isZero, + parameter, + ); cipher.initialize(forEncryption, parameters); return cipher.doFinalFromInput(data); } /// internal method - _KeyInformation? createPrivateKeyInfo( - String passPhrase, bool isPkcs12empty, _EncryptedPrivateKey encInfo) { + 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'); + cipher, + 'cipher', + 'Unknown encryption algorithm', + ); } final ICipherParameter? cipherParameters = pbeU.generateCipherParameters( - algID.id!.id!, passPhrase, isPkcs12empty, algID.parameters); + algID.id!.id!, + passPhrase, + isPkcs12empty, + algID.parameters, + ); cipher.initialize(false, cipherParameters); - final List? keyBytes = - cipher.doFinalFromInput(encInfo._octet!.getOctets()); - return _KeyInformation.getInformation(keyBytes); + final List? keyBytes = cipher.doFinalFromInput( + encInfo._octet!.getOctets(), + ); + return KeyInformation.getInformation(keyBytes); } /// internal method - static _SubjectKeyID createSubjectKeyID(CipherParameter publicKey) { - _SubjectKeyID result; + 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); + Algorithms(PkcsObjectId.rsaEncryption, DerNull.value), + RsaPublicKey(publicKey.modulus, publicKey.exponent).getAsn1(), + ); + result = SubjectKeyID(information); } else { throw ArgumentError.value(publicKey, 'publicKey', 'Invalid Key'); } @@ -434,6 +498,22 @@ class PdfPKCSCertificate { return result; } + /// internal method + Future> getContentTableAsync() async { + final Map result = {}; + // ignore: avoid_function_literals_in_foreach_calls + _certificates.keys.forEach((String key) { + result[key] = 'cert'; + }); + // ignore: avoid_function_literals_in_foreach_calls + _keys.keys.forEach((String key) { + if (!result.containsKey(key)) { + result[key] = 'key'; + } + }); + return result; + } + /// internal method bool isKey(String key) { return _keys[key] != null; @@ -456,15 +536,46 @@ class PdfPKCSCertificate { } if (id != null) { if (_keyCertificates.containsKey(id)) { - certificates = _keyCertificates[id] is X509Certificates - ? _keyCertificates[id] - : null; + certificates = + _keyCertificates[id] is X509Certificates + ? _keyCertificates[id] + : null; + } + } else { + if (_keyCertificates.containsKey(key)) { + certificates = + _keyCertificates[key] is X509Certificates + ? _keyCertificates[key] + : null; + } + } + return certificates as X509Certificates?; + } + } + + /// internal method + Future getCertificateAsync(String key) async { + 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; + certificates = + _keyCertificates[key] is X509Certificates + ? _keyCertificates[key] + : null; } } return certificates as X509Certificates?; @@ -483,15 +594,17 @@ class PdfPKCSCertificate { while (certificates != null) { final X509Certificate x509Certificate = certificates.certificate!; X509Certificates? nextCertificate; - final Asn1Octet? x509Extension = - x509Certificate.getExtension(X509Extensions.authorityKeyIdentifier); + final Asn1Octet? x509Extension = x509Certificate.getExtension( + X509Extensions.authorityKeyIdentifier, + ); if (x509Extension != null) { final _KeyIdentifier id = _KeyIdentifier.getKeyIdentifier( - Asn1Stream(PdfStreamReader(x509Extension.getOctets())) - .readAsn1()); + Asn1Stream(PdfStreamReader(x509Extension.getOctets())).readAsn1(), + ); if (id.keyID != null) { - if (_chainCertificates - .containsKey(_CertificateIdentifier(id: id.keyID))) { + if (_chainCertificates.containsKey( + _CertificateIdentifier(id: id.keyID), + )) { nextCertificate = _chainCertificates[_CertificateIdentifier(id: id.keyID)]; } @@ -531,10 +644,88 @@ class PdfPKCSCertificate { } } return List.generate( - certificateList.length, (int i) => certificateList[i]); + certificateList.length, + (int i) => certificateList[i], + ); } return null; } + + /// internal method + Future?> getCertificateChainAsync(String key) async { + if (!isKey(key)) { + return null; + } + List? x509Certificates; + await getCertificateAsync(key).then((X509Certificates? certificates) { + if (certificates != null) { + final List certificateList = []; + 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(PdfStreamReader(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(); + // ignore: avoid_function_literals_in_foreach_calls + keys.forEach((_CertificateIdentifier 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; + } + } + x509Certificates = List.generate( + certificateList.length, + (int i) => certificateList[i], + ); + } + }); + return x509Certificates; + } + + /// internal method + List getChainCertificates() { + return _chainCertificates.values.toList(); + } } class _CertificateTable { @@ -605,9 +796,10 @@ class _CertificateTable { class _CertificateIdentifier { _CertificateIdentifier({CipherParameter? pubKey, List? id}) { - this.id = pubKey != null - ? PdfPKCSCertificate.createSubjectKeyID(pubKey)._bytes - : id; + this.id = + pubKey != null + ? PdfPKCSCertificate.createSubjectKeyID(pubKey)._bytes + : id; } //Fields List? id; @@ -735,10 +927,11 @@ class _PasswordUtility { } }); - if (mechanism != null && mechanism!.startsWith('PBEwithMD2') || - mechanism!.startsWith('PBEwithMD5') || - mechanism!.startsWith('PBEwithSHA-1') || - mechanism!.startsWith('PBEwithSHA-256')) { + 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'); @@ -756,8 +949,12 @@ class _PasswordUtility { return result; } - ICipherParameter? generateCipherParameters(String algorithm, String password, - bool isWrong, Asn1Encode? pbeParameters) { + ICipherParameter? generateCipherParameters( + String algorithm, + String password, + bool isWrong, + Asn1Encode? pbeParameters, + ) { final String mechanism = getAlgorithmFromUpeerInvariant(algorithm)!; late List keyBytes; List? salt; @@ -772,8 +969,14 @@ class _PasswordUtility { ICipherParameter? parameters; _PasswordGenerator generator; if (mechanism.startsWith('PBEwithSHA-1')) { - generator = getEncoder(_type[mechanism], DigestAlgorithms.sha1, keyBytes, - salt!, iterationCount, password); + 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') { @@ -798,8 +1001,14 @@ class _PasswordUtility { parameters = generator.generateParam(64, 'RC2', 64); } } else if (mechanism.startsWith('PBEwithSHA-256')) { - generator = getEncoder(_type[mechanism], DigestAlgorithms.sha256, - keyBytes, salt!, iterationCount, password); + 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') { @@ -808,10 +1017,17 @@ class _PasswordUtility { parameters = generator.generateParam(256, 'AES', 128); } } else if (mechanism.startsWith('PBEwithHmac')) { - final String digest = - getDigest(mechanism.substring('PBEwithHmac'.length)); + final String digest = getDigest( + mechanism.substring('PBEwithHmac'.length), + ); generator = getEncoder( - _type[mechanism], digest, keyBytes, salt!, iterationCount, password); + _type[mechanism], + digest, + keyBytes, + salt!, + iterationCount, + password, + ); final int? bitLen = getBlockSize(digest); parameters = generator.generateParam(bitLen); } @@ -847,14 +1063,17 @@ class _PasswordUtility { } ICipherParameter? fixDataEncryptionParity( - String mechanism, ICipherParameter? parameters) { + 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!); + fixDataEncryptionParity(mechanism, parameters.parameters), + parameters.keys, + ); } final KeyParameter kParam = parameters! as KeyParameter; final List keyBytes = kParam.keys; @@ -872,7 +1091,7 @@ class _PasswordUtility { 0x01)) .toUnsigned(8); } - return KeyParameter(keyBytes); + return KeyParameter(Uint8List.fromList(keyBytes)); } bool isPkcs12(String algorithm) { @@ -903,14 +1122,23 @@ class _PasswordUtility { return digest; } - _PasswordGenerator getEncoder(String? type, String digest, List key, - List salt, int iterationCount, String password) { + _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'); + type, + 'type', + 'Invalid Password Based Encryption type', + ); } generator.init(key, salt, iterationCount); return generator; @@ -945,8 +1173,10 @@ abstract class _PasswordGenerator { if (password.isEmpty) { return isWrong ? List.generate(2, (int i) => 0) : []; } - final List bytes = - List.generate((password.length + 1) * 2, (int i) => 0); + final List bytes = List.generate( + (password.length + 1) * 2, + (int i) => 0, + ); final List tempBytes = encodeBigEndian(password); int i = 0; // ignore: avoid_function_literals_in_foreach_calls @@ -1003,10 +1233,14 @@ class _Pkcs12AlgorithmGenerator extends _PasswordGenerator { keySize = keySize! ~/ 8; final List bytes = generateDerivedKey(_keyMaterial, keySize); final _ParamUtility util = _ParamUtility(); - final KeyParameter key = - util.createKeyParameter(algorithm!, bytes, 0, keySize); + final KeyParameter key = util.createKeyParameter( + algorithm!, + bytes, + 0, + keySize, + ); final List iv = generateDerivedKey(_invaidMaterial, size); - return InvalidParameter(key, iv, 0, size); + return InvalidParameter(key, Uint8List.fromList(iv)); } else if (algorithm != null) { keySize = keySize! ~/ 8; final List bytes = generateDerivedKey(_keyMaterial, keySize); @@ -1015,7 +1249,11 @@ class _Pkcs12AlgorithmGenerator extends _PasswordGenerator { } else { keySize = keySize! ~/ 8; final List bytes = generateDerivedKey(_keyMaterial, keySize); - return KeyParameter.fromLengthValue(bytes, 0, keySize); + return KeyParameter.fromLengthValue( + Uint8List.fromList(bytes), + 0, + keySize, + ); } } @@ -1028,8 +1266,9 @@ class _Pkcs12AlgorithmGenerator extends _PasswordGenerator { List s; if (_value != null && _value!.isNotEmpty) { s = List.generate( - _length * ((_value!.length + _length - 1) ~/ _length), - (int index) => 0); + _length * ((_value!.length + _length - 1) ~/ _length), + (int index) => 0, + ); for (int index = 0; index != s.length; index++) { s[index] = _value![index % _value!.length]; } @@ -1039,16 +1278,19 @@ class _Pkcs12AlgorithmGenerator extends _PasswordGenerator { List password; if (_password != null && _password!.isNotEmpty) { password = List.generate( - _length * ((_password!.length + _length - 1) ~/ _length), - (int index) => 0); + _length * ((_password!.length + _length - 1) ~/ _length), + (int 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, (int index) => 0); + List tempBytes = List.generate( + s.length + password.length, + (int 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, (int index) => 0); @@ -1071,8 +1313,13 @@ class _Pkcs12AlgorithmGenerator extends _PasswordGenerator { tempBytes = adjust(tempBytes, j * _length, b); } if (i == c) { - List.copyRange(derivedKey, (i - 1) * _size!, a!, 0, - derivedKey.length - ((i - 1) * _size!)); + List.copyRange( + derivedKey, + (i - 1) * _size!, + a!, + 0, + derivedKey.length - ((i - 1) * _size!), + ); } else { List.copyRange(derivedKey, (i - 1) * _size!, a!, 0, a.length); } @@ -1097,7 +1344,10 @@ class _Pkcs12PasswordParameter extends Asn1Encode { _Pkcs12PasswordParameter(Asn1Sequence sequence) { if (sequence.count != 2) { throw ArgumentError.value( - sequence, 'sequence', 'Invalid length in sequence'); + sequence, + 'sequence', + 'Invalid length in sequence', + ); } _octet = Asn1Octet.getOctetStringFromObject(sequence[0]); _iterations = DerInteger.getNumber(sequence[1]); @@ -1131,11 +1381,13 @@ class _ParamUtility { 'DESEDEWRAP', 'TDEA', DerObjectID('1.3.14.3.2.17'), - PkcsObjectId.idAlgCms3DesWrap + PkcsObjectId.idAlgCms3DesWrap, ]); addAlgorithm('DESEDE3', [PkcsObjectId.desEde3Cbc]); - addAlgorithm( - 'RC2', [PkcsObjectId.rc2Cbc, PkcsObjectId.idAlgCmsRC2Wrap]); + addAlgorithm('RC2', [ + PkcsObjectId.rc2Cbc, + PkcsObjectId.idAlgCmsRC2Wrap, + ]); } //Fields @@ -1155,7 +1407,11 @@ class _ParamUtility { } KeyParameter createKeyParameter( - String algorithm, List bytes, int offset, int? length) { + String algorithm, + List bytes, + int offset, + int? length, + ) { String? name; final String lower = algorithm.toLowerCase(); _algorithms.forEach((String key, String value) { @@ -1165,7 +1421,10 @@ class _ParamUtility { }); if (name == null) { throw ArgumentError.value( - algorithm, 'algorithm', 'Invalid entry. Algorithm'); + algorithm, + 'algorithm', + 'Invalid entry. Algorithm', + ); } if (name == 'DES') { return _DataEncryptionParameter.fromLengthValue(bytes, offset, length!); @@ -1173,23 +1432,35 @@ class _ParamUtility { if (name == 'DESEDE' || name == 'DESEDE3') { return _DesedeAlgorithmParameter(bytes, offset, length); } - return KeyParameter.fromLengthValue(bytes, offset, length!); + return KeyParameter.fromLengthValue( + Uint8List.fromList(bytes), + offset, + length!, + ); } } class _DataEncryptionParameter extends KeyParameter { - _DataEncryptionParameter(List keys) : super(keys) { + _DataEncryptionParameter(List keys) : super(Uint8List.fromList(keys)) { if (checkKey(keys, 0)) { throw ArgumentError.value( - keys, 'keys', 'Invalid Data Encryption keys creation'); + keys, + 'keys', + 'Invalid Data Encryption keys creation', + ); } } _DataEncryptionParameter.fromLengthValue( - List keys, int offset, int length) - : super.fromLengthValue(keys, offset, length) { + List keys, + int offset, + int length, + ) : super.fromLengthValue(Uint8List.fromList(keys), offset, length) { if (checkKey(keys, 0)) { throw ArgumentError.value( - keys, 'keys', 'Invalid Data Encryption keys creation'); + keys, + 'keys', + 'Invalid Data Encryption keys creation', + ); } } static List dataEncryptionWeekKeys = [ @@ -1320,7 +1591,7 @@ class _DataEncryptionParameter extends KeyParameter { 254, 241, 254, - 241 + 241, ]; static bool checkKey(List bytes, int offset) { @@ -1341,18 +1612,11 @@ class _DataEncryptionParameter extends KeyParameter { } 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)); + : super(fixKey(key, keyOffset, keyLength)); //Implementation static List fixKey(List key, int keyOffset, int? keyLength) { final List tmp = List.generate(24, (int i) => 0); @@ -1366,11 +1630,17 @@ class _DesedeAlgorithmParameter extends _DataEncryptionParameter { break; default: throw ArgumentError.value( - keyLength, 'keyLen', 'Bad length for DESede key'); + keyLength, + 'keyLen', + 'Bad length for DESede key', + ); } if (checkKeyValue(tmp, 0, tmp.length)) { throw ArgumentError.value( - key, 'key', 'Attempt to create weak DESede key'); + key, + 'key', + 'Attempt to create weak DESede key', + ); } return tmp; } @@ -1383,13 +1653,6 @@ class _DesedeAlgorithmParameter extends _DataEncryptionParameter { } return false; } - - @override - List get keys => List.from(bytes!); - @override - set keys(List? value) { - bytes = value; - } } class _CipherUtils { @@ -1431,9 +1694,6 @@ class _CipherUtils { case _CipherAlgorithm.rsa: asymBlockCipher = RsaAlgorithm(); break; - default: - throw ArgumentError.value( - cipherAlgorithm, 'algorithm', 'Invalid cipher algorithm'); } bool isPadded = true; IPadding? padding; @@ -1464,9 +1724,13 @@ class _CipherUtils { case _CipherPaddingType.pkcs7Padding: padding = Pkcs7Padding(); break; + // ignore: no_default_cases default: - throw ArgumentError.value(cipherPadding, 'cpiher padding algorithm', - 'Invalid cipher algorithm'); + throw ArgumentError.value( + cipherPadding, + 'cpiher padding algorithm', + 'Invalid cipher algorithm', + ); } } String mode = ''; @@ -1493,9 +1757,6 @@ class _CipherUtils { case _CipherMode.cts: blockCipher = CipherBlockChainingMode(blockCipher); break; - default: - throw ArgumentError.value( - cipherMode, 'CipherMode', 'Invalid cipher algorithm'); } } if (blockCipher != null) { @@ -1508,7 +1769,10 @@ class _CipherUtils { return BufferedBlockPadding(blockCipher); } throw ArgumentError.value( - blockCipher, 'Cipher Algorithm', 'Invalid cipher algorithm'); + blockCipher, + 'Cipher Algorithm', + 'Invalid cipher algorithm', + ); } _CipherAlgorithm getAlgorithm(String name) { @@ -1598,7 +1862,9 @@ class _CipherUtils { } enum _CipherAlgorithm { des, desede, rc2, rsa } + enum _CipherMode { ecb, none, cbc, cts } + enum _CipherPaddingType { noPadding, raw, @@ -1609,7 +1875,7 @@ enum _CipherPaddingType { pkcs7, pkcs7Padding, withCipherTextStealing, - x923Padding + x923Padding, } class _KeyIdentifier extends Asn1Encode { @@ -1628,7 +1894,10 @@ class _KeyIdentifier extends Asn1Encode { break; default: throw ArgumentError.value( - sequence, 'sequence', 'Invalid entry in sequence'); + sequence, + 'sequence', + 'Invalid entry in sequence', + ); } } }); @@ -1637,8 +1906,7 @@ class _KeyIdentifier extends Asn1Encode { Asn1Octet? _keyIdentifier; DerInteger? _serialNumber; //Properties - List? get keyID => - _keyIdentifier == null ? null : _keyIdentifier!.getOctets(); + List? get keyID => _keyIdentifier?.getOctets(); //Implementation static _KeyIdentifier getKeyIdentifier(dynamic obj) { _KeyIdentifier result; @@ -1692,8 +1960,11 @@ class _DesEdeAlogorithm extends _DataEncryption { } 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.'); + throw ArgumentError.value( + parameters, + 'parameters', + 'Invalid key size. Size must be 16 or 24 bytes.', + ); } _isEncryption = forEncryption; final List key1 = List.generate(8, (int i) => 0); @@ -1712,19 +1983,26 @@ class _DesEdeAlogorithm extends _DataEncryption { } @override - Map processBlock( - [List? inputBytes, - int? inOffset, - List? outputBytes, - int? outOffset]) { + 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'); + inOffset, + 'inOffset', + 'Invalid length in input bytes', + ); } if ((outOffset! + _blockSize!) > outputBytes!.length) { throw ArgumentError.value( - inOffset, 'inOffset', 'Invalid length in output bytes'); + inOffset, + 'inOffset', + 'Invalid length in output bytes', + ); } final List tempBytes = List.generate(_blockSize!, (int i) => 0); if (_isEncryption!) { @@ -1743,7 +2021,7 @@ class _DesEdeAlogorithm extends _DataEncryption { void reset() {} } -class _DataEncryption implements ICipher { +class _DataEncryption extends ICipher { _DataEncryption() { _blockSize = 8; byteBit = [128, 64, 32, 16, 8, 4, 2, 1]; @@ -1771,7 +2049,7 @@ class _DataEncryption implements ICipher { 0x8, 0x4, 0x2, - 0x1 + 0x1, ]; pc1 = [ 56, @@ -1829,7 +2107,7 @@ class _DataEncryption implements ICipher { 27, 19, 11, - 3 + 3, ]; toTrot = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; pc2 = [ @@ -1880,7 +2158,7 @@ class _DataEncryption implements ICipher { 49, 35, 28, - 31 + 31, ]; sp1 = [ 0x01010400, @@ -1946,7 +2224,7 @@ class _DataEncryption implements ICipher { 0x00010004, 0x00010400, 0x00000000, - 0x01010004 + 0x01010004, ]; sp2 = [ 0x80108020, @@ -2012,7 +2290,7 @@ class _DataEncryption implements ICipher { 0x80000000, 0x80100020, 0x80108020, - 0x00108000 + 0x00108000, ]; sp3 = [ 0x00000208, @@ -2078,7 +2356,7 @@ class _DataEncryption implements ICipher { 0x00020208, 0x00000008, 0x08020008, - 0x00020200 + 0x00020200, ]; sp4 = [ 0x00802001, @@ -2144,7 +2422,7 @@ class _DataEncryption implements ICipher { 0x00000080, 0x00800000, 0x00002000, - 0x00802080 + 0x00802080, ]; sp5 = [ 0x00000100, @@ -2210,7 +2488,7 @@ class _DataEncryption implements ICipher { 0x00000000, 0x40080000, 0x02080100, - 0x40000100 + 0x40000100, ]; sp6 = [ 0x20000010, @@ -2276,7 +2554,7 @@ class _DataEncryption implements ICipher { 0x20404000, 0x20000000, 0x00400010, - 0x20004010 + 0x20004010, ]; sp7 = [ 0x00200000, @@ -2342,7 +2620,7 @@ class _DataEncryption implements ICipher { 0x04000002, 0x04000800, 0x00000800, - 0x00200002 + 0x00200002, ]; sp8 = [ 0x10001040, @@ -2408,7 +2686,7 @@ class _DataEncryption implements ICipher { 0x00001040, 0x00040040, 0x10000000, - 0x10041000 + 0x10041000, ]; } @@ -2449,15 +2727,25 @@ class _DataEncryption implements ICipher { @override Map processBlock( - List? inBytes, int inOffset, List? outBytes, int? outOffset) { + 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'); + inOffset, + 'inOffset', + 'Invalid length in input bytes', + ); } if ((outOffset! + _blockSize!) > outBytes!.length) { throw ArgumentError.value( - outOffset, 'outOffset', 'Invalid length in output bytes'); + outOffset, + 'outOffset', + 'Invalid length in output bytes', + ); } encryptData(_keys, inBytes, inOffset, outBytes, outOffset); return {'length': _blockSize, 'output': outBytes}; @@ -2528,8 +2816,13 @@ class _DataEncryption implements ICipher { return newKeys; } - void encryptData(List? keys, List inputBytes, int inOffset, - List outBytes, int outOffset) { + void encryptData( + List? keys, + List inputBytes, + int inOffset, + List outBytes, + int outOffset, + ) { int left = Asn1.beToUInt32(inputBytes, inOffset); int right = Asn1.beToUInt32(inputBytes, inOffset + 4); int data = (((left >> 4) ^ right) & 0x0f0f0f0f).toUnsigned(32); @@ -2551,24 +2844,24 @@ class _DataEncryption implements ICipher { 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); + 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); + 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); + 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); + data = left ^ keys[round * 4 + 3].toUnsigned(32); value |= sp8[data & 0x3f]; value |= sp6[(data >> 8) & 0x3f]; value |= sp4[(data >> 16) & 0x3f]; @@ -2597,7 +2890,7 @@ class _DataEncryption implements ICipher { } } -class _Rc2Algorithm implements ICipher { +class _Rc2Algorithm extends ICipher { _Rc2Algorithm() { _piTable = [ 217, @@ -2855,7 +3148,7 @@ class _Rc2Algorithm implements ICipher { 254, 127, 193, - 173 + 173, ]; _blockSize = 8; } @@ -2917,7 +3210,11 @@ class _Rc2Algorithm implements ICipher { @override Map processBlock( - List? input, int inOff, List? output, int? outOff) { + List? input, + int inOff, + List? output, + int? outOff, + ) { if (_isEncrypt!) { encryptBlock(input!, inOff, output!, outOff!); } else { @@ -2932,7 +3229,11 @@ class _Rc2Algorithm implements ICipher { } void encryptBlock( - List input, int inOff, List outBytes, int outOff) { + 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); @@ -2974,7 +3275,11 @@ class _Rc2Algorithm implements ICipher { } void decryptBlock( - List input, int inOff, List outBytes, int outOff) { + 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); @@ -3038,7 +3343,7 @@ class _PfxData extends Asn1Encode { Asn1 getAsn1() { final Asn1EncodeCollection collection = Asn1EncodeCollection([ DerInteger(bigIntToBytes(BigInt.from(3))), - _contentInformation + _contentInformation, ]); if (_macInformation != null) { collection.encodableObjects.add(_macInformation); @@ -3051,7 +3356,10 @@ class _ContentInformation extends Asn1Encode { _ContentInformation(Asn1Sequence sequence) { if (sequence.count < 1 || sequence.count > 2) { throw ArgumentError.value( - sequence, 'sequence', 'Invalid length in sequence'); + sequence, + 'sequence', + 'Invalid length in sequence', + ); } _contentType = sequence[0] as DerObjectID?; if (sequence.count > 1) { @@ -3080,8 +3388,9 @@ class _ContentInformation extends Asn1Encode { @override Asn1 getAsn1() { - final Asn1EncodeCollection collection = - Asn1EncodeCollection([_contentType]); + final Asn1EncodeCollection collection = Asn1EncodeCollection([ + _contentType, + ]); if (_content != null) { collection.encodableObjects.add(DerTag(0, _content)); } @@ -3118,8 +3427,10 @@ class _MacInformation extends Asn1Encode { @override Asn1 getAsn1() { - final Asn1EncodeCollection collection = - Asn1EncodeCollection([_digest, DerOctet(_value!)]); + final Asn1EncodeCollection collection = Asn1EncodeCollection([ + _digest, + DerOctet(_value!), + ]); if (_count != BigInt.one) { collection.encodableObjects.add(DerInteger.fromNumber(_count)); } @@ -3127,11 +3438,14 @@ class _MacInformation extends Asn1Encode { } } -class _EncryptedPrivateKey extends Asn1Encode { - _EncryptedPrivateKey(Asn1Sequence sequence) { +class EncryptedPrivateKey extends Asn1Encode { + EncryptedPrivateKey(Asn1Sequence sequence) { if (sequence.count != 2) { throw ArgumentError.value( - sequence, 'sequence', 'Invalid length in sequence'); + sequence, + 'sequence', + 'Invalid length in sequence', + ); } _algorithms = Algorithms.getAlgorithms(sequence[0]); _octet = Asn1Octet.getOctetStringFromObject(sequence[1]); @@ -3147,35 +3461,40 @@ class _EncryptedPrivateKey extends Asn1Encode { return DerSequence(array: [_algorithms, _octet]); } - static _EncryptedPrivateKey getEncryptedPrivateKeyInformation(dynamic obj) { - if (obj is _EncryptedPrivateKey) { + static EncryptedPrivateKey getEncryptedPrivateKeyInformation(dynamic obj) { + if (obj is EncryptedPrivateKey) { return obj; } if (obj is Asn1Sequence) { - return _EncryptedPrivateKey(obj); + return EncryptedPrivateKey(obj); } throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); } } -class _KeyInformation extends Asn1Encode { - _KeyInformation(Algorithms algorithms, Asn1 privateKey, - [Asn1Set? attributes]) { +class KeyInformation extends Asn1Encode { + KeyInformation( + Algorithms algorithms, + Asn1 privateKey, [ + Asn1Set? attributes, + ]) { _privateKey = privateKey; _algorithms = algorithms; if (attributes != null) { _attributes = attributes; } } - _KeyInformation.fromSequence(Asn1Sequence? sequence) { + 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(PdfStreamReader(privateKeyValue.getOctets())) - .readAsn1(); + _privateKey = + Asn1Stream( + PdfStreamReader(privateKeyValue.getOctets()), + ).readAsn1(); } catch (e) { throw ArgumentError.value(sequence, 'sequence', 'Invalid sequence'); } @@ -3194,12 +3513,12 @@ class _KeyInformation extends Asn1Encode { Asn1Set? _attributes; //Implementation - static _KeyInformation? getInformation(dynamic obj) { - if (obj is _KeyInformation) { + static KeyInformation? getInformation(dynamic obj) { + if (obj is KeyInformation) { return obj; } if (obj != null) { - return _KeyInformation.fromSequence(Asn1Sequence.getSequence(obj)); + return KeyInformation.fromSequence(Asn1Sequence.getSequence(obj)); } return null; } @@ -3209,7 +3528,7 @@ class _KeyInformation extends Asn1Encode { final Asn1EncodeCollection v = Asn1EncodeCollection([ DerInteger.fromNumber(BigInt.from(0)), _algorithms, - DerOctet.fromObject(_privateKey!) + DerOctet.fromObject(_privateKey!), ]); if (_attributes != null) { v.encodableObjects.add(DerTag(0, _attributes, false)); @@ -3220,14 +3539,15 @@ class _KeyInformation extends Asn1Encode { class _RsaKey extends Asn1Encode { _RsaKey( - BigInt modulus, - BigInt publicExponent, - BigInt privateExponent, - BigInt prime1, - BigInt prime2, - BigInt exponent1, - BigInt exponent2, - BigInt coefficient) { + BigInt modulus, + BigInt publicExponent, + BigInt privateExponent, + BigInt prime1, + BigInt prime2, + BigInt exponent1, + BigInt exponent2, + BigInt coefficient, + ) { _modulus = modulus; _publicExponent = publicExponent; _privateExponent = privateExponent; @@ -3261,22 +3581,24 @@ class _RsaKey extends Asn1Encode { BigInt? _coefficient; @override Asn1 getAsn1() { - return DerSequence(array: [ - 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) - ]); + return DerSequence( + array: [ + 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 _SubjectKeyID extends Asn1Encode { - _SubjectKeyID(dynamic obj) { +class SubjectKeyID extends Asn1Encode { + SubjectKeyID(dynamic obj) { if (obj is Asn1Octet) { _bytes = obj.getOctets(); } else if (obj is PublicKeyInformation) { @@ -3290,8 +3612,100 @@ class _SubjectKeyID extends Asn1Encode { return sha1.convert(publicKey.publicKey!.data!).bytes; } + /// internal method + static PublicKeyInformation createSubjectKeyID(CipherParameter publicKey) { + if (publicKey is RsaKeyParam) { + final PublicKeyInformation information = PublicKeyInformation( + Algorithms(PkcsObjectId.rsaEncryption, DerNull.value), + RsaPublicKey(publicKey.modulus, publicKey.exponent).getAsn1(), + ); + return information; + } else { + throw ArgumentError.value(publicKey, 'publicKey', 'Invalid Key'); + } + } + @override Asn1 getAsn1() { return DerOctet(_bytes!); } } + +/// Internal class +class CertificateIdentity { + /// Internal constructor + CertificateIdentity( + String hashAlgorithm, + X509Certificate issuerCert, + DerInteger serialNumber, + ) { + final Algorithms algorithms = Algorithms( + DerObjectID(hashAlgorithm), + DerNull.value, + ); + try { + final String algorithm = algorithms.id!.id!; + final X509Name? issuerName = + SingnedCertificate.getCertificate( + Asn1.fromByteArray(issuerCert.getTbsCertificate()!), + )!.subject; + MessageDigestFinder utilities = MessageDigestFinder(); + final List issuerNameHash = utilities.getDigest( + algorithm, + issuerName!.getEncoded()!, + ); + final CipherParameter issuerKey = issuerCert.getPublicKey(); + final PublicKeyInformation info = SubjectKeyID.createSubjectKeyID( + issuerKey, + ); + utilities = MessageDigestFinder(); + final List issuerKeyHash = utilities.getDigest( + algorithm, + info.publicKey!.getBytes()!, + ); + id = CertificateIdentityHelper( + hash: algorithms, + issuerName: DerOctet(issuerNameHash), + issuerKey: DerOctet(issuerKeyHash), + serialNumber: serialNumber, + ); + } catch (e) { + throw Exception('Invalid certificate ID'); + } + } + + /// Internal field + CertificateIdentityHelper? id; + + /// Internal constant + static const String sha1 = '1.3.14.3.2.26'; +} + +/// Internal class +class CertificateIdentityHelper extends Asn1Encode { + /// Internal constructor + CertificateIdentityHelper({ + Algorithms? hash, + Asn1Octet? issuerName, + Asn1Octet? issuerKey, + DerInteger? serialNumber, + }) { + _hash = hash; + _issuerName = issuerName; + _issuerKey = issuerKey; + _serialNumber = serialNumber; + } + + /// Internal field + Algorithms? _hash; + Asn1Octet? _issuerName; + Asn1Octet? _issuerKey; + DerInteger? _serialNumber; + + @override + Asn1 getAsn1() { + return DerSequence( + array: [_hash!, _issuerName!, _issuerKey!, _serialNumber!], + ); + } +} 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 index a8ac9c611..e8d0e7268 100644 --- 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 @@ -1,4 +1,8 @@ import 'dart:convert'; +import 'dart:math'; + +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; import '../../../interfaces/pdf_interface.dart'; import '../../forms/pdf_field.dart'; @@ -10,31 +14,51 @@ import '../../pages/pdf_page.dart'; import '../../pdf_document/pdf_document.dart'; import '../../primitives/pdf_array.dart'; import '../../primitives/pdf_dictionary.dart'; +import '../../primitives/pdf_name.dart'; import '../../primitives/pdf_reference_holder.dart'; +import '../../primitives/pdf_stream.dart'; import '../../primitives/pdf_string.dart'; import '../enum.dart'; import '../pdf_security.dart'; +import 'asn1/asn1.dart'; +import 'asn1/asn1_stream.dart'; +import 'asn1/der.dart'; import 'pdf_certificate.dart'; import 'pdf_external_signer.dart'; import 'pdf_signature_dictionary.dart'; +import 'time_stamp_server/time_stamp_server.dart'; +import 'x509/ocsp_utils.dart'; import 'x509/x509_certificates.dart'; /// 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}) { + PdfSignature({ + String? signedName, + String? locationInfo, + String? reason, + String? contactInfo, + List? documentPermissions, + CryptographicStandard cryptographicStandard = CryptographicStandard.cms, + DigestAlgorithm digestAlgorithm = DigestAlgorithm.sha256, + PdfCertificate? certificate, + TimestampServer? timestampServer, + DateTime? signedDate, + }) { _helper = PdfSignatureHelper(this); - _init(signedName, locationInfo, reason, contactInfo, documentPermissions, - cryptographicStandard, digestAlgorithm, certificate); + _init( + signedName, + locationInfo, + reason, + contactInfo, + documentPermissions, + cryptographicStandard, + digestAlgorithm, + certificate, + signedDate, + timestampServer, + ); } //Fields @@ -44,7 +68,7 @@ class PdfSignature { //Properties /// Gets or sets the permission for certificated document. List documentPermissions = [ - PdfCertificationFlags.forbidChanges + PdfCertificationFlags.forbidChanges, ]; /// Gets or sets reason of signing. @@ -56,9 +80,15 @@ class PdfSignature { /// Gets or sets the signed name String? signedName; - /// Gets the signed date. + /// Gets or sets the signed date. + /// + /// NOTE: The signed date can only be set when signing the PDF document and does not work on existing signatures. DateTime? get signedDate => _helper.dateOfSign; + set signedDate(DateTime? value) { + _helper.dateOfSign = value; + } + /// Gets or sets cryptographic standard. late CryptographicStandard cryptographicStandard; @@ -72,16 +102,50 @@ class PdfSignature { /// Gets or sets the certificate PdfCertificate? certificate; + /// Gets or sets time stamping server unique resource identifier. + /// + /// The timestamp is only embedded when signing the PDF document and + /// saving asynchronously. + /// + /// ```dart + /// //Creates a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Adds a new page + /// PdfPage page = document.pages.add(); + /// //Creates a digital signature and sets signature information + /// PdfSignatureField field = PdfSignatureField(page, 'signature', + /// bounds: Rect.fromLTWH(0, 0, 200, 100), + /// signature: PdfSignature( + /// //Creates a certificate instance from the PFX file with a private key + /// certificate: PdfCertificate( + /// File('D:/PDF.pfx').readAsBytesSync(), 'syncfusion'), + /// contactInfo: 'johndoe@owned.us', + /// locationInfo: 'Honolulu, Hawaii', + /// reason: 'I am author of this document.', + /// //Create a new PDF time stamp server + /// timestampServer: + /// TimestampServer(Uri.parse('http://syncfusion.digistamp.com')))); + /// //Add a signature field to the form + /// document.form.fields.add(field); + /// //Save and dispose the PDF document + /// File('Output.pdf').writeAsBytes(await document.save()); + /// document.dispose(); + /// ``` + TimestampServer? timestampServer; + //Implementations void _init( - String? signedName, - String? locationInfo, - String? reason, - String? contactInfo, - List? documentPermissions, - CryptographicStandard cryptographicStandard, - DigestAlgorithm digestAlgorithm, - PdfCertificate? pdfCertificate) { + String? signedName, + String? locationInfo, + String? reason, + String? contactInfo, + List? documentPermissions, + CryptographicStandard cryptographicStandard, + DigestAlgorithm digestAlgorithm, + PdfCertificate? pdfCertificate, + DateTime? signedDate, + TimestampServer? timestampServer, + ) { this.cryptographicStandard = cryptographicStandard; this.digestAlgorithm = digestAlgorithm; if (signedName != null) { @@ -102,19 +166,86 @@ class PdfSignature { if (pdfCertificate != null) { certificate = pdfCertificate; } + if (signedDate != null) { + this.signedDate = signedDate; + } + if (timestampServer != null) { + this.timestampServer = timestampServer; + } } /// Add external signer for signature. void addExternalSigner( - IPdfExternalSigner signer, List> publicCertificatesData) { + IPdfExternalSigner signer, + List> publicCertificatesData, + ) { _helper.externalSigner = signer; _externalRootCert = publicCertificatesData; if (_externalRootCert != null) { final X509CertificateParser parser = X509CertificateParser(); _helper.externalChain = []; - _externalRootCert!.toList().forEach((List certRawData) => _helper - .externalChain! - .add(parser.readCertificate(PdfStreamReader(certRawData)))); + _externalRootCert!.toList().forEach( + (List certRawData) => _helper.externalChain!.add( + parser.readCertificate(PdfStreamReader(certRawData)), + ), + ); + } + } + + /// Creates long-term validation of the signature. + /// + /// ``` dart + /// // Load the existing PDF document. + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + /// // Create a new PDF document. + /// PdfSignatureField field = document.form.fields[0] as PdfSignatureField; + /// // Check if field is signed. + /// if (field.isSigned) { + /// // Create a long-term validation for the signature. + /// bool isLTV = await field.signature! + /// .createLongTermValidity(includePublicCertificates: true); + /// } + /// // Save the document. + /// List bytes = await document.save(); + /// // Dispose the document. + /// document.dispose(); + /// ``` + Future createLongTermValidity({ + List>? publicCertificatesData, + RevocationType type = RevocationType.ocspAndCrl, + bool includePublicCertificates = false, + }) async { + final List x509CertificateList = []; + if (publicCertificatesData != null) { + final X509CertificateParser parser = X509CertificateParser(); + for (final List certRawData in publicCertificatesData) { + final X509Certificate certificate = + parser.readCertificate(PdfStreamReader(certRawData))!; + x509CertificateList.add(certificate); + } + } else { + final List? certChain = + timestampServer != null + ? await _helper.getTimestampCertificateChain() + : _helper.getCertificateChain(); + if (certChain != null) { + for (final X509Certificate? certificate in certChain) { + if (certificate != null) { + x509CertificateList.add(certificate); + } + } + certChain.clear(); + } + } + if (x509CertificateList.isNotEmpty) { + return _helper.getDSSDetails( + x509CertificateList, + type, + includePublicCertificates, + ); + } else { + return false; } } } @@ -159,39 +290,46 @@ class PdfSignatureHelper { /// internal method List? externalChain; + List? _nameData; /// internal method /// To check annotation last elements have signature field void checkAnnotationElementsContainsSignature( - PdfPage page, String? signatureName) { - if (PdfPageHelper.getHelper(page) - .dictionary! - .containsKey(PdfDictionaryProperties.annots)) { + PdfPage page, + String? signatureName, + ) { + if (PdfPageHelper.getHelper( + page, + ).dictionary!.containsKey(PdfDictionaryProperties.annots)) { final IPdfPrimitive? annotationElements = PdfCrossTable.dereference( - PdfPageHelper.getHelper(page) - .dictionary![PdfDictionaryProperties.annots]); + PdfPageHelper.getHelper(page).dictionary![PdfDictionaryProperties + .annots], + ); IPdfPrimitive? lastElement; if (annotationElements != null && annotationElements is PdfArray && annotationElements.elements.isNotEmpty) { lastElement = PdfCrossTable.dereference( - annotationElements[annotationElements.elements.length - 1]); + annotationElements[annotationElements.elements.length - 1], + ); } if (lastElement != null && lastElement is PdfDictionary && lastElement.containsKey(PdfDictionaryProperties.t)) { - final IPdfPrimitive? name = - PdfCrossTable.dereference(lastElement[PdfDictionaryProperties.t]); + final IPdfPrimitive? name = PdfCrossTable.dereference( + lastElement[PdfDictionaryProperties.t], + ); String tempName = ''; if (name != null && name is PdfString) { - tempName = utf8.decode(name.data!); + tempName = utf8.decode(name.data!, allowMalformed: true); } if (tempName == signatureName && annotationElements != null && annotationElements is PdfArray && annotationElements.elements.isNotEmpty) { - annotationElements.elements - .removeAt(annotationElements.elements.length - 1); + annotationElements.elements.removeAt( + annotationElements.elements.length - 1, + ); } } } @@ -201,18 +339,22 @@ class PdfSignatureHelper { void catalogBeginSave(Object sender, SavePdfPrimitiveArgs? ars) { if (certificated) { IPdfPrimitive? permission = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(document!) - .catalog[PdfDictionaryProperties.perms]); + PdfDocumentHelper.getHelper(document!).catalog[PdfDictionaryProperties + .perms], + ); if (permission == null) { permission = PdfDictionary(); - (permission as PdfDictionary)[PdfDictionaryProperties.docMDP] = - PdfReferenceHolder(signatureDictionary); - PdfDocumentHelper.getHelper(document!) - .catalog[PdfDictionaryProperties.perms] = permission; + (permission as PdfDictionary)[PdfDictionaryProperties + .docMDP] = PdfReferenceHolder(signatureDictionary); + PdfDocumentHelper.getHelper(document!).catalog[PdfDictionaryProperties + .perms] = + permission; } else if (permission is PdfDictionary && !permission.containsKey(PdfDictionaryProperties.docMDP)) { - permission.setProperty(PdfDictionaryProperties.docMDP, - PdfReferenceHolder(signatureDictionary)); + permission.setProperty( + PdfDictionaryProperties.docMDP, + PdfReferenceHolder(signatureDictionary), + ); } } } @@ -223,8 +365,10 @@ class PdfSignatureHelper { final PdfFieldHelper helper = PdfFieldHelper.getHelper(field!); helper.dictionary!.encrypt = PdfSecurityHelper.getHelper(document!.security).encryptor.encrypt; - helper.dictionary! - .setProperty(PdfDictionaryProperties.ap, field!.appearance); + helper.dictionary!.setProperty( + PdfDictionaryProperties.ap, + field!.appearance, + ); } } @@ -273,4 +417,406 @@ class PdfSignatureHelper { } return result; } + + /// internal method + X509Certificate? getRoot(X509Certificate cert, List certs) { + X509Certificate parent; + for (int i = 0; i < certs.length; i++) { + parent = certs[i]; + if (!(cert.c!.issuer!.toString() == parent.c!.subject!.toString())) { + continue; + } + try { + cert.verify(parent.getPublicKey()); + return parent; + } catch (e) { + return null; + } + } + return null; + } + + /// internal method + Future getDSSDetails( + List certificates, + RevocationType type, + bool includePublicCertificates, + ) async { + final List> crlCollection = >[]; + final List> ocspCollection = >[]; + final List> certCollection = >[]; + if (includePublicCertificates) { + for (int i = 0; i < certificates.length; i++) { + certCollection.add(certificates[i].c!.getDerEncoded()!); + } + } + for (int k = 0; k < certificates.length; ++k) { + if (type == RevocationType.ocsp || + type == RevocationType.ocspAndCrl || + (crlCollection.isEmpty && type == RevocationType.ocspOrCrl)) { + final Ocsp ocsp = Ocsp(); + final List? ocspBytes = await ocsp.getEncodedOcspResponse( + certificates[k], + getRoot(certificates[k], certificates), + ); + if (ocspBytes != null) { + ocspCollection.add(buildOCSPResponse(ocspBytes)); + } + } + if (type == RevocationType.crl || + type == RevocationType.ocspAndCrl || + (ocspCollection.isEmpty && type == RevocationType.ocspOrCrl)) { + final List> cim = await RevocationList().getEncoded( + certificates[k], + ); + if (cim.isNotEmpty) { + for (final List crl in cim) { + bool duplicate = false; + for (final List element in crlCollection) { + if (_listsAreEqual(element, crl)) { + duplicate = true; + continue; + } + } + if (!duplicate) { + crlCollection.add(crl); + } + } + } + } + } + return initializeDssDictionary( + crlCollection, + ocspCollection, + certCollection, + ); + } + + bool _listsAreEqual(List list1, List list2) { + if (list1.length != list2.length) { + return false; + } + for (int i = 0; i < list1.length; i++) { + if (list1[i] != list2[i]) { + return false; + } + } + return true; + } + + /// internal method + bool initializeDssDictionary( + List> crlCollection, + List> ocspCollection, + List> certCollection, + ) { + if (crlCollection.isEmpty && + ocspCollection.isEmpty && + certCollection.isEmpty) { + return false; + } + PdfDictionary? dssDictionary; + if (document != null) { + final PdfDocumentHelper helper = PdfDocumentHelper.getHelper(document!); + if (helper.catalog.containsKey(PdfDictionaryProperties.dss)) { + final IPdfPrimitive? dss = PdfCrossTable.dereference( + helper.catalog[PdfDictionaryProperties.dss], + ); + if (dss != null && dss is PdfDictionary) { + dssDictionary = dss; + } + } + } + dssDictionary ??= PdfDictionary(); + PdfArray ocspArray = PdfArray(); + PdfArray crlArray = PdfArray(); + final PdfArray ocspVRI = PdfArray(); + final PdfArray crlVRI = PdfArray(); + PdfArray cetrsArray = PdfArray(); + if (dssDictionary.containsKey(PdfDictionaryProperties.ocsps)) { + final IPdfPrimitive? dssOcsp = PdfCrossTable.dereference( + dssDictionary[PdfDictionaryProperties.ocsps], + ); + if (dssOcsp != null && dssOcsp is PdfArray) { + ocspArray = dssOcsp; + } + } + if (dssDictionary.containsKey(PdfDictionaryProperties.crls)) { + final IPdfPrimitive? dsscrl = PdfCrossTable.dereference( + dssDictionary[PdfDictionaryProperties.crls], + ); + if (dsscrl != null && dsscrl is PdfArray) { + crlArray = dsscrl; + } + } + PdfDictionary vriDictionary = PdfDictionary(); + if (dssDictionary.containsKey(PdfDictionaryProperties.vri)) { + final IPdfPrimitive? dssVri = PdfCrossTable.dereference( + dssDictionary[PdfDictionaryProperties.vri], + ); + if (dssVri != null && dssVri is PdfDictionary) { + vriDictionary = dssVri; + } + } + if (dssDictionary.containsKey(PdfDictionaryProperties.certs)) { + final IPdfPrimitive? dssCerts = PdfCrossTable.dereference( + dssDictionary[PdfDictionaryProperties.certs], + ); + if (dssCerts != null && dssCerts is PdfArray) { + cetrsArray = dssCerts; + } + } + for (int i = 0; i < ocspCollection.length; i++) { + final PdfDictionary tempDictionary = PdfDictionary(); + final PdfStream stream = PdfStream(tempDictionary, ocspCollection[i]); + stream.compress = true; + final PdfReferenceHolder holder = PdfReferenceHolder(stream); + ocspArray.add(holder); + ocspVRI.add(holder); + } + for (int i = 0; i < crlCollection.length; i++) { + final PdfDictionary tempDictionary = PdfDictionary(); + final PdfStream stream = PdfStream(tempDictionary, crlCollection[i]); + stream.compress = true; + final PdfReferenceHolder holder = PdfReferenceHolder(stream); + crlArray.add(holder); + crlVRI.add(holder); + } + for (int i = 0; i < certCollection.length; i++) { + final PdfDictionary tempDictionary = PdfDictionary(); + final PdfStream stream = PdfStream(tempDictionary, certCollection[i]); + stream.compress = true; + final PdfReferenceHolder holder = PdfReferenceHolder(stream); + cetrsArray.add(holder); + } + final String vriName = getVRIName().toUpperCase(); + PdfDictionary vriDataDictionary = PdfDictionary(); + if (vriDictionary.containsKey(vriName)) { + final IPdfPrimitive? dssVriData = PdfCrossTable.dereference( + vriDictionary[vriName], + ); + if (dssVriData != null && dssVriData is PdfDictionary) { + vriDataDictionary = dssVriData; + } + if (vriDataDictionary.containsKey(PdfDictionaryProperties.ocsp)) { + final IPdfPrimitive? vriOCSP = PdfCrossTable.dereference( + vriDataDictionary[PdfDictionaryProperties.ocsp], + ); + if (vriOCSP != null && vriOCSP is PdfArray) { + for (int i = 0; i < ocspVRI.count; i++) { + if (!vriOCSP.contains(ocspVRI[i]!)) { + vriOCSP.add(ocspVRI[i]!); + } + } + } + } + if (vriDataDictionary.containsKey(PdfDictionaryProperties.crl)) { + final IPdfPrimitive? vriCRL = PdfCrossTable.dereference( + vriDataDictionary[PdfDictionaryProperties.crl], + ); + if (vriCRL != null && vriCRL is PdfArray) { + for (int i = 0; i < crlVRI.count; i++) { + if (!vriCRL.contains(crlVRI[i]!)) { + vriCRL.add(crlVRI[i]!); + } + } + } + } + } else { + vriDataDictionary.items![PdfName( + PdfDictionaryProperties.ocsp, + )] = PdfReferenceHolder(ocspArray); + vriDataDictionary.items![PdfName( + PdfDictionaryProperties.crl, + )] = PdfReferenceHolder(crlArray); + vriDictionary.items![PdfName( + getVRIName().toUpperCase(), + )] = PdfReferenceHolder(vriDataDictionary); + } + vriDictionary.modify(); + dssDictionary.items![PdfName( + PdfDictionaryProperties.ocsps, + )] = PdfReferenceHolder(ocspArray); + dssDictionary.items![PdfName( + PdfDictionaryProperties.crls, + )] = PdfReferenceHolder(crlArray); + dssDictionary.items![PdfName( + PdfDictionaryProperties.vri, + )] = PdfReferenceHolder(vriDictionary); + if (certCollection.isNotEmpty) { + dssDictionary.items![PdfName( + PdfDictionaryProperties.certs, + )] = PdfReferenceHolder(cetrsArray); + } + PdfDocumentHelper.getHelper(document!).catalog[PdfDictionaryProperties + .dss] = PdfReferenceHolder(dssDictionary); + dssDictionary.modify(); + return true; + } + + /// internal method + List buildOCSPResponse(List basicOCSPResponse) { + final DerOctet doctet = DerOctet(basicOCSPResponse); + final Asn1EncodeCollection v2 = Asn1EncodeCollection(); + v2.add([OcspConstants.ocspBasic]); + v2.add([doctet]); + final DerCatalogue den = DerCatalogue([0]); + final Asn1EncodeCollection v3 = Asn1EncodeCollection(); + v3.add([den]); + v3.add([DerTag(0, DerSequence(collection: v2), true)]); + final DerSequence seq = DerSequence(collection: v3); + final List b = seq.getEncoded()!; + return b; + } + + /// internal method + String getVRIName() { + if (_nameData == null && field != null) { + final PdfSignatureFieldHelper helper = PdfSignatureFieldHelper.getHelper( + field!, + ); + if (helper.dictionary!.containsKey(PdfDictionaryProperties.v)) { + final IPdfPrimitive? v = PdfCrossTable.dereference( + helper.dictionary![PdfDictionaryProperties.v], + ); + if (v != null && + v is PdfDictionary && + v.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? contents = PdfCrossTable.dereference( + v[PdfDictionaryProperties.contents], + ); + if (contents != null && contents is PdfString) { + _nameData = contents.data; + } + } + } + } + _nameData ??= utf8.encode(_generateUuid()); + final dynamic output = AccumulatorSink(); + final dynamic input = sha1.startChunkedConversion(output); + input.add(_nameData); + input.close(); + final List data = output.events.single.bytes as List; + return PdfString.bytesToHex(data); + } + + String _generateUuid() { + final Random random = Random(); + final StringBuffer buffer = StringBuffer(); + for (int i = 0; i < 32; i++) { + if (i == 8 || i == 12 || i == 16 || i == 20) { + buffer.write('-'); + } + final int digit = + i == 12 + ? 4 // Specifies the version (4) for UUID v4 + : (i == 16 ? (random.nextInt(4) + 8) : random.nextInt(16)); + buffer.write(digit.toRadixString(16)); + } + return buffer.toString(); + } + + /// internal method + List? getCertificateChain() { + if (field != null) { + try { + final PdfSignatureFieldHelper helper = + PdfSignatureFieldHelper.getHelper(field!); + if (helper.dictionary!.containsKey(PdfDictionaryProperties.v)) { + final IPdfPrimitive? v = PdfCrossTable.dereference( + helper.dictionary![PdfDictionaryProperties.v], + ); + if (v != null && + v is PdfDictionary && + v.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? contents = PdfCrossTable.dereference( + v[PdfDictionaryProperties.contents], + ); + if (contents != null && contents is PdfString) { + final List? sigByte = contents.data; + if (sigByte != null) { + final X509CertificateParser parser = X509CertificateParser(); + final List? certificateChain = parser + .getCertificateChain(PdfStreamReader(sigByte)); + if (certificateChain != null) { + return certificateChain; + } + _nameData = sigByte; + } + } + } + } + } catch (e) { + return null; + } + } + return null; + } + + /// internal method + Future?> getTimestampCertificateChain() async { + try { + final dynamic output = AccumulatorSink(); + final dynamic input = sha256.startChunkedConversion(output); + input.add(base64.decode('VABlAHMAdAAgAGQAYQB0AGEA')); //Test unicode data + input.close(); + final List hash = output.events.single.bytes as List; + final List asnEncodedTimestampRequest = TimeStampRequestCreator() + .getAsnEncodedTimestampRequest(hash); + final List? timeStampResponse = await fetchData( + base.timestampServer!.uri, + 'POST', + contentType: 'application/timestamp-query', + userName: base.timestampServer!.userName, + password: base.timestampServer!.password, + data: asnEncodedTimestampRequest, + timeOutDuration: base.timestampServer!.timeOut, + ); + if (timeStampResponse != null) { + List? encoded; + final Asn1Stream stream = Asn1Stream( + PdfStreamReader(timeStampResponse), + ); + final Asn1? asn1 = stream.readAsn1(); + if (asn1 != null && + asn1 is Asn1Sequence && + asn1.count > 1 && + asn1[1] != null && + asn1[1] is Asn1) { + final Asn1 asn1Sequence = asn1[1]! as Asn1; + final DerStream dOut = DerStream([]); + asn1Sequence.encode(dOut); + encoded = dOut.stream!.toList(); + dOut.stream!.clear(); + } + if (encoded != null) { + final X509CertificateParser parser = X509CertificateParser(); + final List? certificateChain = parser + .getCertificateChain(PdfStreamReader(encoded)); + if (certificateChain != null) { + return certificateChain; + } + _nameData = encoded; + } + } + } catch (e) { + return null; + } + return null; + } +} + +/// Specifies the type of revocation to be considered during the LTV enable process and their corresponding actions. +enum RevocationType { + /// Embeds the OCSP data to the PDF document. + ocsp, + + /// Embeds the CRL data to the PDF document. + crl, + + /// Embeds both OCSP and CRL data to the PDF document. + ocspAndCrl, + + /// Embeds OCSP or CRL data to the PDF document. + ocspOrCrl, } 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 index 5fedc87a7..ac3d63ed3 100644 --- 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 @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:math'; @@ -33,6 +34,8 @@ import 'pdf_external_signer.dart'; import 'pdf_signature.dart'; import 'pkcs/password_utility.dart'; import 'pkcs/pfx_data.dart'; +import 'time_stamp_server/time_stamp_server.dart'; +import 'x509/ocsp_utils.dart'; import 'x509/x509_certificates.dart'; /// Represents signature dictionary. @@ -41,11 +44,12 @@ class PdfSignatureDictionary implements IPdfWrapper { PdfSignatureDictionary(PdfDocument doc, PdfSignature sig) { _doc = doc; _sig = sig; - if (PdfDocumentHelper.getHelper(doc).documentSavedList == null) { - PdfDocumentHelper.getHelper(doc).documentSavedList = - []; - } - PdfDocumentHelper.getHelper(doc).documentSavedList!.add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedList ??= + []) + .add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedListAsync ??= + []) + .add(_documentSavedAsync); dictionary!.beginSaveList ??= []; dictionary!.beginSaveList!.add(_dictionaryBeginSave); _cert = sig.certificate; @@ -54,10 +58,12 @@ class PdfSignatureDictionary implements IPdfWrapper { /// internal constructor PdfSignatureDictionary.fromDictionary(PdfDocument doc, this.dictionary) { _doc = doc; - List? documentSavedList = - PdfDocumentHelper.getHelper(doc).documentSavedList; - documentSavedList ??= []; - documentSavedList.add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedList ??= + []) + .add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedListAsync ??= + []) + .add(_documentSavedAsync); dictionary!.beginSaveList ??= []; dictionary!.beginSaveList!.add(_dictionaryBeginSave); } @@ -71,6 +77,7 @@ class PdfSignatureDictionary implements IPdfWrapper { final String _docMdp = 'DocMDP'; final String _cmsFilterType = 'adbe.pkcs7.detached'; final String _cadasFilterType = 'ETSI.CAdES.detached'; + final String _rfcFilterType = 'ETSI.RFC3161'; int? _firstRangeLength; int? _secondRangeIndex; int? _startPositionByteRange; @@ -113,36 +120,47 @@ class PdfSignatureDictionary implements IPdfWrapper { if (_sig != null) { if (_sig!.reason != null) { dictionary!.setProperty( - PdfDictionaryProperties.reason, PdfString(_sig!.reason!)); + PdfDictionaryProperties.reason, + PdfString(_sig!.reason!), + ); } if (_sig!.locationInfo != null) { dictionary!.setProperty( - PdfDictionaryProperties.location, PdfString(_sig!.locationInfo!)); + PdfDictionaryProperties.location, + PdfString(_sig!.locationInfo!), + ); } if (_sig!.contactInfo != null) { dictionary!.setProperty( - PdfDictionaryProperties.contactInfo, PdfString(_sig!.contactInfo!)); + PdfDictionaryProperties.contactInfo, + PdfString(_sig!.contactInfo!), + ); } if (_sig!.signedName != null) { dictionary!.setString(PdfDictionaryProperties.name, _sig!.signedName); final PdfDictionary tempDictionary = PdfDictionary(); final PdfDictionary appDictionary = PdfDictionary(); tempDictionary.setName( - PdfName(PdfDictionaryProperties.name), _sig!.signedName); + PdfName(PdfDictionaryProperties.name), + _sig!.signedName, + ); appDictionary.setProperty('App', PdfReferenceHolder(tempDictionary)); dictionary!.setProperty( - PdfName('Prop_Build'), PdfReferenceHolder(appDictionary)); + PdfName('Prop_Build'), + PdfReferenceHolder(appDictionary), + ); } } } bool _allowMDP() { final IPdfPrimitive? perms = PdfCrossTable.dereference( - PdfDocumentHelper.getHelper(_doc) - .catalog[PdfDictionaryProperties.perms]); + PdfDocumentHelper.getHelper(_doc).catalog[PdfDictionaryProperties.perms], + ); if (perms != null && perms is PdfDictionary) { - final IPdfPrimitive? docMDP = - PdfCrossTable.dereference(perms[PdfDictionaryProperties.docMDP]); + final IPdfPrimitive? docMDP = PdfCrossTable.dereference( + perms[PdfDictionaryProperties.docMDP], + ); final IPdfPrimitive dicSig = dictionary!; return dicSig == docMDP; } @@ -155,8 +173,10 @@ class PdfSignatureDictionary implements IPdfWrapper { final PdfArray array = PdfArray(); trans[PdfDictionaryProperties.v] = PdfName('1.2'); trans[PdfDictionaryProperties.p] = PdfNumber( - PdfSignatureHelper.getHelper(_sig!) - .getCertificateFlagResult(_sig!.documentPermissions)); + PdfSignatureHelper.getHelper( + _sig!, + ).getCertificateFlagResult(_sig!.documentPermissions), + ); trans[PdfDictionaryProperties.type] = PdfName(_transParam); reference[PdfDictionaryProperties.transformMethod] = PdfName(_docMdp); reference[PdfDictionaryProperties.type] = PdfName('SigRef'); @@ -166,11 +186,21 @@ class PdfSignatureDictionary implements IPdfWrapper { } void _addType() { - dictionary!.setName(PdfName(PdfDictionaryProperties.type), 'Sig'); + if (_sig != null && _sig!.timestampServer != null && _cert == null) { + dictionary!.setName( + PdfName(PdfDictionaryProperties.type), + 'DocTimeStamp', + ); + } else { + dictionary!.setName(PdfName(PdfDictionaryProperties.type), 'Sig'); + } } void _addDate() { - final DateTime dateTime = DateTime.now(); + DateTime dateTime = DateTime.now(); + if (_sig != null && _sig!.signedDate != null) { + dateTime = _sig!.signedDate!; + } final DateFormat dateFormat = DateFormat('yyyyMMddHHmmss'); final int regionMinutes = dateTime.timeZoneOffset.inMinutes ~/ 11; String offsetMinutes = regionMinutes.toString(); @@ -183,45 +213,67 @@ class PdfSignatureDictionary implements IPdfWrapper { offsetHours = '0$offsetHours'; } dictionary!.setProperty( - PdfDictionaryProperties.m, - PdfString( - "D:${dateFormat.format(dateTime)}+$offsetHours'$offsetMinutes'")); + PdfDictionaryProperties.m, + PdfString( + "D:${dateFormat.format(dateTime)}+$offsetHours'$offsetMinutes'", + ), + ); } void _addFilter() { - dictionary! - .setName(PdfName(PdfDictionaryProperties.filter), 'Adobe.PPKLite'); + dictionary!.setName( + PdfName(PdfDictionaryProperties.filter), + 'Adobe.PPKLite', + ); } void _addSubFilter() { - dictionary!.setName( + if (_sig != null && _sig!.timestampServer != null && _cert == null) { + dictionary!.setName( + PdfName(PdfDictionaryProperties.subFilter), + _rfcFilterType, + ); + } else { + dictionary!.setName( PdfName(PdfDictionaryProperties.subFilter), _sig!.cryptographicStandard == CryptographicStandard.cades ? _cadasFilterType - : _cmsFilterType); + : _cmsFilterType, + ); + } } void _addContents(IPdfWriter writer) { - writer.write(PdfOperators.slash + - PdfDictionaryProperties.contents + - PdfOperators.whiteSpace); + writer.write( + PdfOperators.slash + + PdfDictionaryProperties.contents + + PdfOperators.whiteSpace, + ); _firstRangeLength = writer.position; int length = _estimatedSize * 2; if (_sig != null && _cert != null) { length = _estimatedSize; + if (_sig!.timestampServer != null) { + length += 4192; + } } - final List contents = - List.filled(length * 2 + 2, 0, growable: true); + final List contents = List.filled( + length * 2 + 2, + 0, + growable: true, + ); writer.write(contents); _secondRangeIndex = writer.position; writer.write(PdfOperators.newLine); } void _addRange(IPdfWriter writer) { - writer.write(PdfOperators.slash + - PdfDictionaryProperties.byteRange + - PdfOperators.whiteSpace + - PdfArray.startMark); + writer.write( + PdfOperators.slash + + PdfDictionaryProperties.byteRange + + PdfOperators.whiteSpace + + PdfArray.startMark, + ); _startPositionByteRange = writer.position; for (int i = 0; i < 32; i++) { writer.write(PdfOperators.whiteSpace); @@ -240,8 +292,10 @@ class PdfSignatureDictionary implements IPdfWrapper { PdfDictionary trans = PdfDictionary(); trans[PdfDictionaryProperties.v] = PdfName('1.2'); trans[PdfDictionaryProperties.p] = PdfNumber( - PdfSignatureHelper.getHelper(_sig!) - .getCertificateFlagResult(_sig!.documentPermissions)); + PdfSignatureHelper.getHelper( + _sig!, + ).getCertificateFlagResult(_sig!.documentPermissions), + ); trans[PdfDictionaryProperties.type] = PdfName(_transParam); writer.write(trans); writer.write(PdfName(PdfDictionaryProperties.transformMethod)); @@ -251,8 +305,9 @@ class PdfSignatureDictionary implements IPdfWrapper { writer.write(PdfName('DigestValue')); int position = writer.position!; // _docDigestPosition = position; - writer - .write(PdfString.fromBytes(List.filled(16, 0, growable: true))); + writer.write( + PdfString.fromBytes(List.filled(16, 0, growable: true)), + ); PdfArray digestLocation = PdfArray(); digestLocation.add(PdfNumber(position)); digestLocation.add(PdfNumber(34)); @@ -282,8 +337,9 @@ class PdfSignatureDictionary implements IPdfWrapper { writer.write(PdfName('DigestValue')); position = writer.position!; // _fieldsDigestPosition = position; - writer - .write(PdfString.fromBytes(List.filled(16, 0, growable: true))); + writer.write( + PdfString.fromBytes(List.filled(16, 0, growable: true)), + ); digestLocation = PdfArray(); digestLocation.add(PdfNumber(position)); digestLocation.add(PdfNumber(34)); @@ -318,22 +374,145 @@ class PdfSignatureDictionary implements IPdfWrapper { _stream = writer.buffer; final String text = PdfString.bytesToHex(getPkcs7Content()!); _stream!.replaceRange( - _firstRangeLength!, _firstRangeLength! + 1, utf8.encode('<')); + _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, (int i) => 0)); + final String emptyText = PdfString.bytesToHex( + List.generate(num3, (int i) => 0), + ); + _stream!.replaceRange( + newPos, + newPos + emptyText.length, + utf8.encode(emptyText), + ); _stream!.replaceRange( - newPos, newPos + emptyText.length, utf8.encode(emptyText)); - _stream!.replaceRange(newPos + emptyText.length, - newPos + emptyText.length + 1, utf8.encode('>')); + newPos + emptyText.length, + newPos + emptyText.length + 1, + utf8.encode('>'), + ); PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt = enabled; } + Future _documentSavedAsync(Object sender, DocumentSavedArgs e) async { + final bool enabled = + PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt; + PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt = false; + final PdfWriter writer = e.writer! as PdfWriter; + final int number = e.writer!.length! - _secondRangeIndex!; + const String str = '0 '; + final String str2 = '$_firstRangeLength '; + final String str3 = '$_secondRangeIndex '; + final String str4 = number.toString(); + await _saveRangeItemAsync(writer, str, _startPositionByteRange!).then(( + int startPosition, + ) async { + await _saveRangeItemAsync(writer, str2, startPosition).then(( + int startPosition, + ) async { + await _saveRangeItemAsync(writer, str3, startPosition).then(( + int startPosition, + ) async { + await _saveRangeItemAsync( + e.writer! as PdfWriter, + str4, + startPosition, + ).then((int startPosition) async { + _range = [ + 0, + int.parse(str2), + int.parse(str3), + int.parse(str4), + ]; + _stream = writer.buffer; + if (_cert != null || + (_sig != null && + PdfSignatureHelper.getHelper(_sig!).externalSigner != + null)) { + await getPkcs7ContentAsync().then((List? value) async { + await PdfString.bytesToHexAsync(value!).then(( + String text, + ) async { + _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; + await PdfString.bytesToHexAsync( + List.generate(num3, (int i) => 0), + ).then((String emptyText) async { + _stream!.replaceRange( + newPos, + newPos + emptyText.length, + utf8.encode(emptyText), + ); + _stream!.replaceRange( + newPos + emptyText.length, + newPos + emptyText.length + 1, + utf8.encode('>'), + ); + PdfSecurityHelper.getHelper(_doc.security) + .encryptor + .encrypt = enabled; + }); + }); + }); + } else if (_sig != null && _sig!.timestampServer != null) { + await _getPKCS7TimeStampContent().then((List? value) async { + await PdfString.bytesToHexAsync(value!).then(( + String text, + ) async { + _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; + await PdfString.bytesToHexAsync( + List.generate(num3, (int i) => 0), + ).then((String emptyText) async { + _stream!.replaceRange( + newPos, + newPos + emptyText.length, + utf8.encode(emptyText), + ); + _stream!.replaceRange( + newPos + emptyText.length, + newPos + emptyText.length + 1, + utf8.encode('>'), + ); + PdfSecurityHelper.getHelper(_doc.security) + .encryptor + .encrypt = enabled; + }); + }); + }); + } + }); + }); + }); + }); + } + /// internal method List? getPkcs7Content() { - String? hasalgorithm = ''; + String? hashAlgorithm = ''; _SignaturePrivateKey externalSignature; List>? crlBytes; List? ocspByte; @@ -345,67 +524,83 @@ class PdfSignatureDictionary implements IPdfWrapper { chain = PdfSignatureHelper.getHelper(_sig!).externalChain; final String digest = getDigestAlgorithm(externalSigner.hashAlgorithm); final _SignaturePrivateKey pks = _SignaturePrivateKey(digest); - hasalgorithm = pks.getHashAlgorithm(); + hashAlgorithm = pks.getHashAlgorithm(); externalSignature = pks; } else { String certificateAlias = ''; - final List keys = PdfCertificateHelper.getPkcsCertificate(_cert!) - .getContentTable() - .keys - .toList(); + final List keys = + PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getContentTable().keys.toList(); bool isContinue = true; // ignore: avoid_function_literals_in_foreach_calls keys.forEach((String key) { if (isContinue && PdfCertificateHelper.getPkcsCertificate(_cert!).isKey(key) && - PdfCertificateHelper.getPkcsCertificate(_cert!) - .getKey(key)! - .key! - .isPrivate!) { + PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getKey(key)!.key!.isPrivate!) { certificateAlias = key; isContinue = false; } }); - final KeyEntry pk = PdfCertificateHelper.getPkcsCertificate(_cert!) - .getKey(certificateAlias)!; + final KeyEntry pk = + PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getKey(certificateAlias)!; final List certificates = - PdfCertificateHelper.getPkcsCertificate(_cert!) - .getCertificateChain(certificateAlias)!; + PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getCertificateChain(certificateAlias)!; // ignore: avoid_function_literals_in_foreach_calls certificates.forEach((X509Certificates c) { chain!.add(c.certificate); }); final RsaPrivateKeyParam? parameters = pk.key as RsaPrivateKeyParam?; - final String digest = _sig != null - ? getDigestAlgorithm(_sig!.digestAlgorithm) - : MessageDigestAlgorithms.secureHash256; + final String digest = + _sig != null + ? getDigestAlgorithm(_sig!.digestAlgorithm) + : MessageDigestAlgorithms.secureHash256; final _SignaturePrivateKey pks = _SignaturePrivateKey(digest, parameters); - hasalgorithm = pks.getHashAlgorithm(); + hashAlgorithm = pks.getHashAlgorithm(); externalSignature = pks; } - final _PdfCmsSigner pkcs7 = - _PdfCmsSigner(null, chain!, hasalgorithm!, false); + final _PdfCmsSigner pkcs7 = _PdfCmsSigner( + null, + chain, + hashAlgorithm!, + false, + ); final IRandom source = getUnderlyingSource(); - final List sources = - List.generate(_range.length ~/ 2, (int i) => null); + final List sources = List.generate( + _range.length ~/ 2, + (int i) => null, + ); for (int j = 0; j < _range.length; j += 2) { sources[j ~/ 2] = _WindowRandom(source, _range[j], _range[j + 1]); } final PdfStreamReader data = _RandomStream(_RandomGroup(sources)); - final List hash = pkcs7._digestAlgorithm.digest(data, hasalgorithm)!; + final List hash = pkcs7._digestAlgorithm.digest(data, hashAlgorithm)!; final List? sh = pkcs7 .getSequenceDataSet( - hash, ocspByte, crlBytes, _sig!.cryptographicStandard) + hash, + ocspByte, + crlBytes, + _sig!.cryptographicStandard, + ) .getEncoded(Asn1.der); List? extSignature; if (externalSigner != null) { - final SignerResult? signerResult = externalSigner.sign(sh!); + final SignerResult? signerResult = externalSigner.signSync(sh!); if (signerResult != null && signerResult.signedData.isNotEmpty) { extSignature = signerResult.signedData; } if (extSignature != null) { pkcs7.setSignedData( - extSignature, null, externalSignature.getEncryptionAlgorithm()); + extSignature, + null, + externalSignature.getEncryptionAlgorithm(), + ); } else { return List.filled(_estimatedSize, 0, growable: true); } @@ -413,9 +608,192 @@ class PdfSignatureDictionary implements IPdfWrapper { extSignature = externalSignature.sign(sh!); } pkcs7.setSignedData( - extSignature!, null, externalSignature.getEncryptionAlgorithm()); - return pkcs7.sign(hash, null, null, ocspByte, crlBytes, - _sig!.cryptographicStandard, hasalgorithm); + extSignature!, + null, + externalSignature.getEncryptionAlgorithm(), + ); + return pkcs7.sign( + hash, + _sig!.timestampServer, + null, + ocspByte, + crlBytes, + _sig!.cryptographicStandard, + hashAlgorithm, + ); + } + + /// internal method + Future?> getPkcs7ContentAsync() async { + List? pkcs7Content; + String? hashAlgorithm = ''; + _SignaturePrivateKey? externalSignature; + List>? crlBytes; + List? ocspByte; + List? chain = []; + final IPdfExternalSigner? externalSigner = + PdfSignatureHelper.getHelper(_sig!).externalSigner; + if (externalSigner != null && + PdfSignatureHelper.getHelper(_sig!).externalChain != null) { + chain = PdfSignatureHelper.getHelper(_sig!).externalChain; + final String digest = getDigestAlgorithm(externalSigner.hashAlgorithm); + final _SignaturePrivateKey pks = _SignaturePrivateKey(digest); + hashAlgorithm = pks.getHashAlgorithm(); + externalSignature = pks; + } else { + String certificateAlias = ''; + await PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getContentTableAsync().then((Map contentTable) async { + final List keys = contentTable.keys.toList(); + bool isContinue = true; + // ignore: avoid_function_literals_in_foreach_calls + keys.forEach((String key) { + if (isContinue && + PdfCertificateHelper.getPkcsCertificate(_cert!).isKey(key) && + PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getKey(key)!.key!.isPrivate!) { + certificateAlias = key; + isContinue = false; + } + }); + final KeyEntry pk = + PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getKey(certificateAlias)!; + await PdfCertificateHelper.getPkcsCertificate( + _cert!, + ).getCertificateChainAsync(certificateAlias).then(( + List? certificates, + ) { + // ignore: avoid_function_literals_in_foreach_calls + certificates!.forEach((X509Certificates 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, + ); + hashAlgorithm = pks.getHashAlgorithm(); + externalSignature = pks; + }); + }); + } + final _PdfCmsSigner pkcs7 = _PdfCmsSigner( + null, + chain, + hashAlgorithm!, + false, + ); + final IRandom source = getUnderlyingSource(); + final List sources = List.generate( + _range.length ~/ 2, + (int i) => null, + ); + for (int j = 0; j < _range.length; j += 2) { + sources[j ~/ 2] = _WindowRandom(source, _range[j], _range[j + 1]); + } + final PdfStreamReader data = _RandomStream(_RandomGroup(sources)); + await pkcs7._digestAlgorithm.digestAsync(data, hashAlgorithm).then(( + List? hash, + ) async { + await pkcs7 + .getSequenceDataSetAsync( + hash!, + ocspByte, + crlBytes, + _sig!.cryptographicStandard, + ) + .then((DerSet derSet) async { + await derSet.getEncodedAsync(Asn1.der).then((List? sh) async { + List? extSignature; + if (externalSigner != null) { + await externalSigner.sign(sh!).then(( + SignerResult? signerResult, + ) async { + signerResult ??= externalSigner.signSync(sh); + if (signerResult != null && + signerResult.signedData.isNotEmpty) { + extSignature = signerResult.signedData; + } + if (extSignature != null) { + await pkcs7.setSignedDataAsync( + extSignature!, + null, + externalSignature!.getEncryptionAlgorithm(), + ); + } else { + pkcs7Content = List.filled( + _estimatedSize, + 0, + growable: true, + ); + } + }); + } else { + await externalSignature! + .signAsync(sh!) + .then((List? value) => extSignature = value); + } + if (pkcs7Content == null) { + await pkcs7.setSignedDataAsync( + extSignature!, + null, + externalSignature!.getEncryptionAlgorithm(), + ); + pkcs7Content = await pkcs7.signAsync( + hash, + _sig!.timestampServer, + null, + ocspByte, + crlBytes, + _sig!.cryptographicStandard, + hashAlgorithm, + ); + } + }); + }); + }); + return pkcs7Content; + } + + Future?> _getPKCS7TimeStampContent() async { + final _SignaturePrivateKey externalSignature = _SignaturePrivateKey( + MessageDigestAlgorithms.secureHash256, + ); + final String? hashAlgorithm = externalSignature.getHashAlgorithm(); + final _PdfCmsSigner pkcs7 = _PdfCmsSigner( + null, + null, + hashAlgorithm!, + false, + ); + final IRandom source = getUnderlyingSource(); + final List sources = List.filled( + _range.length ~/ 2, + null, + ); + for (int j = 0; j < _range.length; j += 2) { + sources[j ~/ 2] = _WindowRandom(source, _range[j], _range[j + 1]); + } + final PdfStreamReader data = _RandomStream(_RandomGroup(sources)); + final MessageDigestAlgorithms alg = MessageDigestAlgorithms(); + final List? hash = alg.digest(data, hashAlgorithm); + if (hash != null) { + pkcs7.setSignedData( + hash, + null, + externalSignature.getEncryptionAlgorithm(), + ); + return pkcs7.getEncodedTimestamp(hash, _sig!.timestampServer!); + } + return null; } /// internal method @@ -436,6 +814,7 @@ class PdfSignatureDictionary implements IPdfWrapper { case DigestAlgorithm.sha512: digestAlgorithm = MessageDigestAlgorithms.secureHash512; break; + // ignore: no_default_cases default: digestAlgorithm = MessageDigestAlgorithms.secureHash256; break; @@ -445,8 +824,25 @@ class PdfSignatureDictionary implements IPdfWrapper { int _saveRangeItem(PdfWriter writer, String str, int startPosition) { final List date = utf8.encode(str); - writer.buffer! - .replaceRange(startPosition, startPosition + date.length, date); + writer.buffer!.replaceRange( + startPosition, + startPosition + date.length, + date, + ); + return startPosition + str.length; + } + + Future _saveRangeItemAsync( + PdfWriter writer, + String str, + int startPosition, + ) async { + final List date = utf8.encode(str); + writer.buffer!.replaceRange( + startPosition, + startPosition + date.length, + date, + ); return startPosition + str.length; } @@ -496,16 +892,16 @@ class MessageDigestAlgorithms { _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[NistObjectIds.sha256.id] = 'SHA-256'; _algorithms['SHA384'] = 'SHA-384'; - _algorithms[_NistObjectIds.sha384.id] = 'SHA-384'; + _algorithms[NistObjectIds.sha384.id] = 'SHA-384'; _algorithms['SHA512'] = 'SHA-512'; - _algorithms[_NistObjectIds.sha512.id] = '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'; + _algorithms[NistObjectIds.ripeMD160.id] = 'RIPEMD160'; } /// internal field @@ -546,6 +942,18 @@ class MessageDigestAlgorithms { return result; } + /// internal method + Future getAllowedDigestsAsync(String name) async { + String? result; + final String lower = name.toLowerCase(); + _digests.forEach((String key, String value) { + if (lower == key.toLowerCase()) { + result = _digests[key]; + } + }); + return result; + } + /// internal method dynamic getMessageDigest(String hashAlgorithm) { String lower = hashAlgorithm.toLowerCase(); @@ -571,7 +979,43 @@ class MessageDigestAlgorithms { result = md5; } else { throw ArgumentError.value( - hashAlgorithm, 'hashAlgorithm', 'Invalid message digest algorithm'); + hashAlgorithm, + 'hashAlgorithm', + 'Invalid message digest algorithm', + ); + } + return result; + } + + /// internal method + Future getMessageDigestAsync(String hashAlgorithm) async { + 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; } @@ -594,6 +1038,27 @@ class MessageDigestAlgorithms { input.close(); return output.events.single.bytes as List?; } + + /// internal method + Future?> digestAsync( + PdfStreamReader data, + dynamic hashAlgorithm, + ) async { + dynamic algorithm; + algorithm = + hashAlgorithm is String + ? await getMessageDigestAsync(hashAlgorithm) + : hashAlgorithm; + final dynamic output = AccumulatorSink(); + final dynamic input = algorithm.startChunkedConversion(output); + int? count; + final List bytes = List.generate(8192, (int i) => 0); + while ((count = data.read(bytes, 0, bytes.length))! > 0) { + input.add(bytes.sublist(0, count)); + } + input.close(); + return output.events.single.bytes as List?; + } } class _SignaturePrivateKey { @@ -614,7 +1079,16 @@ class _SignaturePrivateKey { //Implementation List? sign(List bytes) { final String signMode = '${_hashAlgorithm!}with${_encryptionAlgorithm!}'; - final _SignerUtilities util = _SignerUtilities(); + final SignerUtilities util = SignerUtilities(); + final ISigner signer = util.getSigner(signMode); + signer.initialize(true, _key); + signer.blockUpdate(bytes, 0, bytes.length); + return signer.generateSignature(); + } + + Future?> signAsync(List bytes) async { + 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); @@ -630,8 +1104,10 @@ class _SignaturePrivateKey { } } -class _SignerUtilities { - _SignerUtilities() { +/// Internal class +class SignerUtilities { + /// Internal consturctor + SignerUtilities() { _algms['MD2WITHRSA'] = 'MD2withRSA'; _algms['MD2WITHRSAENCRYPTION'] = 'MD2withRSA'; _algms[PkcsObjectId.md2WithRsaEncryption.id] = 'MD2withRSA'; @@ -676,20 +1152,21 @@ class _SignerUtilities { _algms['SHA-256/DSA'] = 'SHA-256withDSA'; _algms['SHA256WITHDSA'] = 'SHA-256withDSA'; _algms['SHA-256WITHDSA'] = 'SHA-256withDSA'; - _algms[_NistObjectIds.dsaWithSHA256.id] = 'SHA-256withDSA'; + _algms[NistObjectIds.dsaWithSHA256.id] = 'SHA-256withDSA'; _algms['RIPEMD160WITHRSA'] = 'RIPEMD160withRSA'; _algms['RIPEMD160WITHRSAENCRYPTION'] = 'RIPEMD160withRSA'; - _algms[_NistObjectIds.rsaSignatureWithRipeMD160.id] = '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; + _oids['RIPEMD160withRSA'] = NistObjectIds.rsaSignatureWithRipeMD160; } //Fields final Map _algms = {}; final Map _oids = {}; //Implementation + /// Internal method ISigner getSigner(String algorithm) { ISigner result; final String lower = algorithm.toLowerCase(); @@ -714,30 +1191,70 @@ class _SignerUtilities { } return result; } + + /// Internal method + Future getSignerAsync(String algorithm) async { + 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 _PdfCmsSigner { - _PdfCmsSigner(ICipherParameter? privateKey, List certChain, - String hashAlgorithm, bool hasRSAdata) { + _PdfCmsSigner( + ICipherParameter? privateKey, + List? certChain, + String hashAlgorithm, + bool hasRSAdata, + ) { _digestAlgorithm = MessageDigestAlgorithms(); _digestAlgorithmOid = _digestAlgorithm.getAllowedDigests(hashAlgorithm); if (_digestAlgorithmOid == null) { throw ArgumentError.value( - hashAlgorithm, 'hashAlgorithm', 'Unknown Hash Algorithm'); + hashAlgorithm, + 'hashAlgorithm', + 'Unknown Hash Algorithm', + ); } _version = 1; _signerVersion = 1; - _certificates = List.generate( - certChain.length, (int i) => certChain[i]); _digestOid = {}; _digestOid[_digestAlgorithmOid] = null; - _signCert = _certificates[0]; + if (certChain != null) { + _certificates = List.generate( + certChain.length, + (int i) => certChain[i], + ); + _signCert = _certificates[0]; + } if (privateKey != null) { if (privateKey is RsaKeyParam) { _encryptionAlgorithmOid = _DigitalIdentifiers.rsa; } else { throw ArgumentError.value( - privateKey, 'privateKey', 'Unknown key algorithm'); + privateKey, + 'privateKey', + 'Unknown key algorithm', + ); } } if (hasRSAdata) { @@ -767,13 +1284,18 @@ class _PdfCmsSigner { } //Implementation - DerSet getSequenceDataSet(List secondDigest, List? ocsp, - List>? crlBytes, CryptographicStandard? sigtype) { + 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: [DerObjectID(_DigitalIdentifiers.pkcs7Data)])); + v.encodableObjects.add( + DerSet(array: [DerObjectID(_DigitalIdentifiers.pkcs7Data)]), + ); attribute.encodableObjects.add(DerSequence(collection: v)); v = Asn1EncodeCollection(); v.encodableObjects.add(DerObjectID(_DigitalIdentifiers.messageDigest)); @@ -781,31 +1303,101 @@ class _PdfCmsSigner { attribute.encodableObjects.add(DerSequence(collection: v)); if (sigtype == CryptographicStandard.cades) { v = Asn1EncodeCollection(); - v.encodableObjects - .add(DerObjectID(_DigitalIdentifiers.aaSigningCertificateV2)); + v.encodableObjects.add( + DerObjectID(_DigitalIdentifiers.aaSigningCertificateV2), + ); final Asn1EncodeCollection aaV2 = Asn1EncodeCollection(); final MessageDigestAlgorithms alg = MessageDigestAlgorithms(); - final String? sha256Oid = - alg.getAllowedDigests(MessageDigestAlgorithms.secureHash256); + 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(Asn1.der)) - .bytes as List; + final List dig = + alg + .getMessageDigest(hashAlgorithm!) + .convert(_signCert!.c!.getEncoded(Asn1.der)) + .bytes + as List; aaV2.encodableObjects.add(DerOctet(dig)); - v.encodableObjects.add(DerSet(array: [ - DerSequence.fromObject( - DerSequence.fromObject(DerSequence(collection: aaV2))) - ])); + v.encodableObjects.add( + DerSet( + array: [ + DerSequence.fromObject( + DerSequence.fromObject(DerSequence(collection: aaV2)), + ), + ], + ), + ); attribute.encodableObjects.add(DerSequence(collection: v)); } return DerSet(collection: attribute); } + //Implementation + Future getSequenceDataSetAsync( + List secondDigest, + List? ocsp, + List>? crlBytes, + CryptographicStandard? sigtype, + ) async { + final Asn1EncodeCollection attribute = Asn1EncodeCollection(); + Asn1EncodeCollection v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_DigitalIdentifiers.contentType)); + v.encodableObjects.add( + DerSet(array: [DerObjectID(_DigitalIdentifiers.pkcs7Data)]), + ); + attribute.encodableObjects.add(DerSequence(collection: v)); + v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_DigitalIdentifiers.messageDigest)); + v.encodableObjects.add(DerSet(array: [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(); + await alg + .getAllowedDigestsAsync(MessageDigestAlgorithms.secureHash256) + .then((String? sha256Oid) async { + if (sha256Oid != _digestAlgorithmOid) { + aaV2.encodableObjects.add( + Algorithms(DerObjectID(_digestAlgorithmOid)), + ); + } + await alg.getMessageDigestAsync(hashAlgorithm!).then(( + dynamic value, + ) { + aaV2.encodableObjects.add( + DerOctet( + value.convert(_signCert!.c!.getEncoded(Asn1.der)).bytes + as List, + ), + ); + v.encodableObjects.add( + DerSet( + array: [ + 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) { + List digest, + List? rsaData, + String? digestEncryptionAlgorithm, + ) { _signedData = digest; _signedRsaData = rsaData; if (digestEncryptionAlgorithm != null) { @@ -817,19 +1409,47 @@ class _PdfCmsSigner { _encryptionAlgorithmOid = _DigitalIdentifiers.ecdsa; } else { throw ArgumentError.value( - digestEncryptionAlgorithm, 'algorithm', 'Invalid entry'); + digestEncryptionAlgorithm, + 'algorithm', + 'Invalid entry', + ); + } + } + } + + Future setSignedDataAsync( + List digest, + List? rsaData, + String? digestEncryptionAlgorithm, + ) async { + _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) { + List secondDigest, + TimestampServer? server, + List? timeStampResponse, + List? ocsp, + List>? crls, + CryptographicStandard? sigtype, + String? hashAlgorithm, + ) { if (_signedData != null) { _digest = _signedData; if (_rsaData != null) { @@ -856,25 +1476,29 @@ class _PdfCmsSigner { // ignore: avoid_function_literals_in_foreach_calls _certificates.forEach((X509Certificate? xcert) { v.encodableObjects.add( - Asn1Stream(PdfStreamReader(xcert!.c!.getEncoded(Asn1.der))) - .readAsn1()); + Asn1Stream(PdfStreamReader(xcert!.c!.getEncoded(Asn1.der))).readAsn1(), + ); }); final DerSet dercertificates = DerSet(collection: v); final Asn1EncodeCollection signerinfo = Asn1EncodeCollection(); - signerinfo.encodableObjects - .add(DerInteger(bigIntToBytes(BigInt.from(_signerVersion)))); + signerinfo.encodableObjects.add( + DerInteger(bigIntToBytes(BigInt.from(_signerVersion))), + ); v = Asn1EncodeCollection(); - v.encodableObjects - .add(getIssuer(_signCert!.c!.tbsCertificate!.getEncoded(Asn1.der))); - v.encodableObjects - .add(DerInteger(bigIntToBytes(_signCert!.c!.serialNumber!.value))); + v.encodableObjects.add( + getIssuer(_signCert!.c!.tbsCertificate!.getEncoded(Asn1.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)); + signerinfo.encodableObjects.add( + DerTag(0, getSequenceDataSet(secondDigest, ocsp, crls, sigtype), false), + ); v = Asn1EncodeCollection(); v.encodableObjects.add(DerObjectID(_encryptionAlgorithmOid)); v.encodableObjects.add(DerNull.value); @@ -885,11 +1509,154 @@ class _PdfCmsSigner { body.encodableObjects.add(DerSet(collection: digestAlgorithms)); body.encodableObjects.add(contentinfo); body.encodableObjects.add(DerTag(0, dercertificates, false)); - body.encodableObjects - .add(DerSet(array: [DerSequence(collection: signerinfo)])); + body.encodableObjects.add( + DerSet(array: [DerSequence(collection: signerinfo)]), + ); final Asn1EncodeCollection whole = Asn1EncodeCollection(); - whole.encodableObjects - .add(DerObjectID(_DigitalIdentifiers.pkcs7SignedData)); + 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; + } + + Asn1EncodeCollection? getAttributes(List timeStampToken) { + final Asn1Stream tempstream = Asn1Stream(PdfStreamReader(timeStampToken)); + final Asn1EncodeCollection attributes = Asn1EncodeCollection(); + final Asn1EncodeCollection asn1Encode = Asn1EncodeCollection(); + asn1Encode.add([DerObjectID('1.2.840.113549.1.9.16.2.14')]); + final Asn1? seq = tempstream.readAsn1(); + if (seq != null && seq is Asn1Sequence) { + asn1Encode.add([ + DerSet(array: [seq]), + ]); + attributes.add([DerSequence(collection: asn1Encode)]); + } + return attributes; + } + + Future?> signAsync( + List secondDigest, + TimestampServer? server, + List? timeStampResponse, + List? ocsp, + List>? crls, + CryptographicStandard? sigtype, + String? hashAlgorithm, + ) async { + if (_signedData != null) { + _digest = _signedData; + if (_rsaData != null) { + _rsaData = _signedRsaData; + } + } + final Asn1EncodeCollection digestAlgorithms = Asn1EncodeCollection(); + final List keys = _digestOid.keys.toList(); + // ignore: avoid_function_literals_in_foreach_calls + keys.forEach((String? 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(); + // ignore: avoid_function_literals_in_foreach_calls + _certificates.forEach((X509Certificate? xcert) { + v.encodableObjects.add( + Asn1Stream(PdfStreamReader(xcert!.c!.getEncoded(Asn1.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(Asn1.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!)); + if (timeStampResponse == null && server != null) { + final dynamic output = AccumulatorSink(); + final dynamic input = sha256.startChunkedConversion(output); + input.add(_digest); + input.close(); + final List hash = output.events.single.bytes as List; + final List asnEncodedTimestampRequest = TimeStampRequestCreator() + .getAsnEncodedTimestampRequest(hash); + timeStampResponse = await fetchData( + server.uri, + 'POST', + contentType: 'application/timestamp-query', + userName: server.userName, + password: server.password, + data: asnEncodedTimestampRequest, + timeOutDuration: server.timeOut, + ); + if (timeStampResponse != null) { + final Asn1Stream stream = Asn1Stream( + PdfStreamReader(timeStampResponse), + ); + final Asn1? asn1 = stream.readAsn1(); + if (asn1 != null && + asn1 is Asn1Sequence && + asn1.count > 1 && + asn1[1] != null && + asn1[1] is Asn1) { + final Asn1 asn1Sequence = asn1[1]! as Asn1; + final DerStream dOut = DerStream([]); + asn1Sequence.encode(dOut); + timeStampResponse = dOut.stream!.toList(); + dOut.stream!.clear(); + } + } + } + if (timeStampResponse != null) { + final Asn1EncodeCollection? timeAsn1Encoded = getAttributes( + timeStampResponse, + ); + if (timeAsn1Encoded != null) { + signerinfo.add([ + DerTag(1, DerSet(collection: timeAsn1Encoded), false), + ]); + } + } + 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: [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)); @@ -901,6 +1668,41 @@ class _PdfCmsSigner { Asn1Stream(PdfStreamReader(data)).readAsn1()! as Asn1Sequence; return seq[seq[0] is Asn1Tag ? 3 : 2] as Asn1?; } + + /// Internal method + Future?> getEncodedTimestamp( + List secondDigest, + TimestampServer server, + ) async { + List? encoded; + final List asnEncodedTimestampRequest = TimeStampRequestCreator() + .getAsnEncodedTimestampRequest(secondDigest); + final List? respBytes = await fetchData( + server.uri, + 'POST', + contentType: 'application/timestamp-query', + userName: server.userName, + password: server.password, + data: asnEncodedTimestampRequest, + timeOutDuration: server.timeOut, + ); + if (respBytes != null) { + final Asn1Stream stream = Asn1Stream(PdfStreamReader(respBytes)); + final Asn1? asn1 = stream.readAsn1(); + if (asn1 != null && + asn1 is Asn1Sequence && + asn1.count > 1 && + asn1[1] != null && + asn1[1] is Asn1) { + final Asn1 asn1Sequence = asn1[1]! as Asn1; + final DerStream dOut = DerStream([]); + asn1Sequence.encode(dOut); + encoded = dOut.stream!.toList(); + dOut.stream!.clear(); + } + } + return encoded; + } } class _DigitalIdentifiers { @@ -1066,7 +1868,7 @@ class _SourceEntry { class _RandomStream extends PdfStreamReader { _RandomStream(IRandom source) - : super(List.generate(source.length!, (int i) => 0)) { + : super(List.generate(source.length!, (int i) => 0)) { _random = source; } //Fields @@ -1118,9 +1920,9 @@ class _RmdSigner implements ISigner { if (_map == null) { _map = {}; _map![DigestAlgorithms.sha1] = X509Objects.idSha1; - _map![DigestAlgorithms.sha256] = _NistObjectIds.sha256; - _map![DigestAlgorithms.sha384] = _NistObjectIds.sha384; - _map![DigestAlgorithms.sha512] = _NistObjectIds.sha512; + _map![DigestAlgorithms.sha256] = NistObjectIds.sha256; + _map![DigestAlgorithms.sha384] = NistObjectIds.sha384; + _map![DigestAlgorithms.sha512] = NistObjectIds.sha512; } return _map; } @@ -1171,6 +1973,48 @@ class _RmdSigner implements ISigner { return _rsaEngine.processBlock(data, 0, data.length); } + @override + bool validateSignature(List signature) { + if (_isSigning) { + throw Exception('Invalid entry'); + } + _input.close(); + final List? hash = _output.events.single.bytes as List?; + List sig; + List expected; + try { + sig = _rsaEngine.processBlock(signature, 0, signature.length)!; + expected = derEncode(hash)!; + } catch (e) { + return false; + } + if (sig.length == expected.length) { + for (int i = 0; i < sig.length; i++) { + if (sig[i] != expected[i]) { + return false; + } + } + } else if (sig.length == expected.length - 2) { + final int sigOffset = sig.length - hash!.length - 2; + final int expectedOffset = expected.length - hash.length - 2; + expected[1] -= 2; + expected[3] -= 2; + for (int i = 0; i < hash.length; i++) { + if (sig[sigOffset + i] != expected[expectedOffset + i]) { + return false; + } + } + for (int i = 0; i < sigOffset; i++) { + if (sig[i] != expected[i]) { + return false; + } + } + } else { + return false; + } + return true; + } + List? derEncode(List? hash) { if (_id == null) { return hash; @@ -1186,25 +2030,43 @@ class _RmdSigner implements ISigner { } // ignore: avoid_classes_with_only_static_members -class _NistObjectIds { +/// internal class +class NistObjectIds { + // ignore: public_member_api_docs static DerObjectID nistAlgorithm = DerObjectID('2.16.840.1.101.3.4'); + // ignore: public_member_api_docs static DerObjectID hashAlgs = DerObjectID('${nistAlgorithm.id!}.2'); + // ignore: public_member_api_docs static DerObjectID sha256 = DerObjectID('${hashAlgs.id!}.1'); + // ignore: public_member_api_docs static DerObjectID sha384 = DerObjectID('${hashAlgs.id!}.2'); + // ignore: public_member_api_docs static DerObjectID sha512 = DerObjectID('${hashAlgs.id!}.3'); + // ignore: public_member_api_docs static DerObjectID dsaWithSHA2 = DerObjectID('${nistAlgorithm.id!}.3'); + // ignore: public_member_api_docs static DerObjectID dsaWithSHA256 = DerObjectID('${dsaWithSHA2.id!}.2'); + // ignore: public_member_api_docs static DerObjectID tttAlgorithm = DerObjectID('1.3.36.3'); + // ignore: public_member_api_docs static DerObjectID ripeMD160 = DerObjectID('${tttAlgorithm.id!}.2.1'); - static DerObjectID tttRsaSignatureAlgorithm = - DerObjectID('${tttAlgorithm.id!}.3.1'); - static DerObjectID rsaSignatureWithRipeMD160 = - DerObjectID('${tttRsaSignatureAlgorithm.id!}.2'); + // ignore: public_member_api_docs + static DerObjectID tttRsaSignatureAlgorithm = DerObjectID( + '${tttAlgorithm.id!}.3.1', + ); + // ignore: public_member_api_docs + static DerObjectID rsaSignatureWithRipeMD160 = DerObjectID( + '${tttRsaSignatureAlgorithm.id!}.2', + ); } /// internal type definition -typedef DocumentSavedHandler = void Function( - Object sender, DocumentSavedArgs args); +typedef DocumentSavedHandler = + void Function(Object sender, DocumentSavedArgs args); + +/// internal type definition +typedef DocumentSavedHandlerAsync = + Future Function(Object sender, DocumentSavedArgs args); /// internal class class DocumentSavedArgs { 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 index 8a59a653c..bbd6aea3d 100644 --- 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 @@ -11,6 +11,13 @@ class Algorithms extends Asn1Encode { } } + /// internal constructor + Algorithms.fromAsn1Sequence(Asn1Identifier id, Asn1 asn1) { + _sequence = Asn1Sequence(); + _sequence.objects!.add(id); + _sequence.objects!.add(asn1); + } + /// internal constructor Algorithms.fromSequence(Asn1Sequence sequence) { if (sequence.count < 1 || sequence.count > 2) { @@ -54,8 +61,9 @@ class Algorithms extends Asn1Encode { @override Asn1 getAsn1() { - final Asn1EncodeCollection collection = - Asn1EncodeCollection([id]); + final Asn1EncodeCollection collection = Asn1EncodeCollection([ + id, + ]); if (_parametersDefined) { collection.encodableObjects.add(parameters ?? DerNull.value); } @@ -194,12 +202,14 @@ class PkcsObjectId { static DerObjectID pbeWithShaAnd40BitRC4 = DerObjectID('$pkcs12PbeIds.2'); /// internal field - static DerObjectID pbeWithShaAnd3KeyTripleDesCbc = - DerObjectID('$pkcs12PbeIds.3'); + static DerObjectID pbeWithShaAnd3KeyTripleDesCbc = DerObjectID( + '$pkcs12PbeIds.3', + ); /// internal field - static DerObjectID pbeWithShaAnd2KeyTripleDesCbc = - DerObjectID('$pkcs12PbeIds.4'); + static DerObjectID pbeWithShaAnd2KeyTripleDesCbc = DerObjectID( + '$pkcs12PbeIds.4', + ); /// internal field static DerObjectID pbeWithShaAnd128BitRC2Cbc = DerObjectID('$pkcs12PbeIds.5'); @@ -208,8 +218,9 @@ class PkcsObjectId { static DerObjectID pbewithShaAnd40BitRC2Cbc = DerObjectID('$pkcs12PbeIds.6'); /// internal field - static DerObjectID idAlgCms3DesWrap = - DerObjectID('1.2.840.113549.1.9.16.3.6'); + static DerObjectID idAlgCms3DesWrap = DerObjectID( + '1.2.840.113549.1.9.16.3.6', + ); /// internal field static DerObjectID idAlgCmsRC2Wrap = DerObjectID('1.2.840.113549.1.9.16.3.7'); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/time_stamp_server/time_stamp_server.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/time_stamp_server/time_stamp_server.dart new file mode 100644 index 000000000..23ec8b107 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/time_stamp_server/time_stamp_server.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; + +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; + +import '../x509/ocsp_utils.dart'; + +/// Represent a timestamp to add in PDF document +/// +/// ```dart +/// //Creates a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Adds a new page +/// PdfPage page = document.pages.add(); +/// //Creates a digital signature and sets signature information +/// PdfSignatureField field = PdfSignatureField(page, 'signature', +/// bounds: Rect.fromLTWH(0, 0, 200, 100), +/// signature: PdfSignature( +/// //Creates a certificate instance from the PFX file with a private key +/// certificate: PdfCertificate( +/// File('D:/PDF.pfx').readAsBytesSync(), 'syncfusion'), +/// contactInfo: 'johndoe@owned.us', +/// locationInfo: 'Honolulu, Hawaii', +/// reason: 'I am author of this document.', +/// //Create a new PDF time stamp server +/// timestampServer: +/// TimestampServer(Uri.parse('http://syncfusion.digistamp.com')))); +/// //Add a signature field to the form +/// document.form.fields.add(field); +/// //Save and dispose the PDF document +/// File('Output.pdf').writeAsBytes(await document.save()); +/// document.dispose(); +/// ``` +class TimestampServer { + /// Initialize a new instance of the [TimestampServer] class with timestamp server url. + /// + /// ```dart + /// //Creates a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Adds a new page + /// PdfPage page = document.pages.add(); + /// //Creates a digital signature and sets signature information + /// PdfSignatureField field = PdfSignatureField(page, 'signature', + /// bounds: Rect.fromLTWH(0, 0, 200, 100), + /// signature: PdfSignature( + /// //Creates a certificate instance from the PFX file with a private key + /// certificate: PdfCertificate( + /// File('D:/PDF.pfx').readAsBytesSync(), 'syncfusion'), + /// contactInfo: 'johndoe@owned.us', + /// locationInfo: 'Honolulu, Hawaii', + /// reason: 'I am author of this document.', + /// //Create a new PDF time stamp server + /// timestampServer: + /// TimestampServer(Uri.parse('http://syncfusion.digistamp.com')))); + /// //Add a signature field to the form + /// document.form.fields.add(field); + /// //Save and dispose the PDF document + /// File('Output.pdf').writeAsBytes(await document.save()); + /// document.dispose(); + /// ``` + TimestampServer(this.uri, {this.userName, this.password, this.timeOut}); + + /// Gets or set the server uri. + Uri uri; + + /// Gets or set the user name. + String? userName; + + /// Gets or set the password. + String? password; + + /// Gets or set the time out duration. + Duration? timeOut; + + /// Gets a value indicating whether the time stamp url is valid. + /// + /// ```dart + /// //Create a new PDF time stamp server + /// TimestampServer server = + /// TimestampServer(Uri.parse('http://syncfusion.digistamp.com')); + /// //Check whether the time stamp server is valid + /// bool isValid = await server.isValid; + /// ``` + Future get isValid => _isValidTimeStamp(); + + // Implementation. + Future _isValidTimeStamp() async { + bool isValid = false; + try { + final dynamic output = AccumulatorSink(); + final dynamic input = sha256.startChunkedConversion(output); + input.add(base64.decode('VABlAHMAdAAgAGQAYQB0AGEA')); //Test unicode data + input.close(); + final List hash = output.events.single.bytes as List; + final List asnEncodedTimestampRequest = TimeStampRequestCreator() + .getAsnEncodedTimestampRequest(hash); + final List? timeStampResponse = await fetchData( + uri, + 'POST', + contentType: 'application/timestamp-query', + userName: userName, + password: password, + data: asnEncodedTimestampRequest, + timeOutDuration: timeOut, + ); + if (timeStampResponse != null && timeStampResponse.length > 2) { + if (timeStampResponse[0] == 0x30 && timeStampResponse[1] == 0x82) { + isValid = true; + } + } + } catch (e) { + isValid = false; + } + return isValid; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/ocsp_utils.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/ocsp_utils.dart new file mode 100644 index 000000000..282df90cc --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/ocsp_utils.dart @@ -0,0 +1,867 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; + +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; +import 'package:http/http.dart' as http; + +import '../../../annotations/json_parser.dart'; +import '../../../io/stream_reader.dart'; +import '../asn1/asn1.dart'; +import '../asn1/asn1_stream.dart'; +import '../asn1/der.dart'; +import '../pdf_pkcs_certificate.dart'; +import '../pkcs/pfx_data.dart'; +import 'x509_certificates.dart'; +import 'x509_name.dart'; + +/// internal class +class Ocsp { + /// internal method + Future?> getEncodedOcspResponse( + X509Certificate? checkCertificate, + X509Certificate? rootCertificate, + ) async { + final OcspResponseHelper? helper = await getOcspResponse( + checkCertificate, + rootCertificate, + ); + if (helper != null) { + return helper.getResponseBytes(); + } + return null; + } + + /// internal method + Future getOcspResponse( + X509Certificate? checkCertificate, + X509Certificate? rootCertificate, + ) async { + if (checkCertificate == null || rootCertificate == null) { + return null; + } + final CertificateUtililty utility = CertificateUtililty(); + final String? url = utility.getOcspUrl(checkCertificate); + if (url == null) { + return null; + } + final OcspRequestHelper request = generateOcspRequest( + rootCertificate, + checkCertificate.c!.serialNumber!, + ); + final List? requestBody = request.getEncoded(); + if (requestBody != null) { + final List? data = await fetchData( + url, + 'POST', + data: requestBody, + contentType: 'application/ocsp-request', + timeOutDuration: const Duration(milliseconds: 5000), + ); + if (data != null) { + return OcspResponseHelper(data); + } + } + return null; + } + + /// internal method + OcspRequestHelper generateOcspRequest( + X509Certificate issuerCertificate, + DerInteger serialNumber, + ) { + final CertificateIdentity id = CertificateIdentity( + CertificateIdentity.sha1, + issuerCertificate, + serialNumber, + ); + final OcspRequestCreator requestCreator = OcspRequestCreator(); + requestCreator.addRequest(id); + final Map extensions = + {}; + extensions[OcspConstants.ocspNonce] = X509Extension( + false, + DerOctet(DerOctet(createDocumentId()).getEncoded()!), + ); + requestCreator.requestExtensions = X509Extensions(extensions); + return requestCreator.createRequest(); + } + + /// internal method + List createDocumentId() { + int time = + DateTime.now().microsecondsSinceEpoch + DateTime.now().millisecond; + final int random = 1000000 + Random().nextInt(9999999 - 1000000); + final List b = utf8.encode('$time+$random+${time++}'); + final dynamic output = AccumulatorSink(); + final dynamic input = md5.startChunkedConversion(output); + input.add(b); + input.close(); + final List hash = output.events.single.bytes as List; + return hash; + } +} + +/// Internal class +class RevocationList { + /// Internal method + Future>> getEncoded(X509Certificate certificate) async { + List? urls; + try { + final CertificateUtililty utility = CertificateUtililty(); + urls = utility.getCrlUrls(certificate); + } catch (ex) { + // + } + final List> byteList = >[]; + if (urls != null) { + List? data; + for (final String entry in urls) { + try { + data = await fetchData( + entry, + 'GET', + timeOutDuration: const Duration(milliseconds: 5000), + ); + } catch (e) { + // + } + if (data != null) { + byteList.add(data); + } + } + } + return byteList; + } +} + +/// Internal class +class RevocationPointList extends Asn1Encode { + /// Internal constructor. + RevocationPointList([Asn1Sequence? sequence]) { + if (sequence != null) { + _sequence = sequence; + } + } + + /// Internal field + Asn1Sequence? _sequence; + + /// Internal method + List getDistributionPoints() { + final List distributions = + []; + if (_sequence != null) { + final RevocationDistribution distribution = RevocationDistribution(); + for (int i = 0; i != _sequence!.count; ++i) { + distributions.add(distribution.getCrlDistribution(_sequence![i])!); + } + } + return distributions; + } + + /// Internal method + RevocationPointList getCrlPointList(dynamic obj) { + if (obj is RevocationPointList || obj == null) { + return obj; + } + if (obj is Asn1Sequence) { + return RevocationPointList(obj); + } + throw Exception('Invalid entry in sequence'); + } + + @override + Asn1 getAsn1() { + return _sequence!; + } +} + +/// Internal class +class RevocationDistribution extends Asn1Encode { + /// Internal constructor. + RevocationDistribution([Asn1Sequence? sequence]) { + if (sequence != null) { + for (int i = 0; i != sequence.count; i++) { + final Asn1Tag? tag = Asn1Tag.getTag(sequence[i]); + if (tag != null) { + switch (tag.tagNumber) { + case 0: + final RevocationDistributionType type = + RevocationDistributionType(); + distributionPoint = type.getDistributionType(tag, true); + break; + case 2: + final RevocationName name = RevocationName(); + _issuer = name.getCrlName(tag, false); + break; + } + } + } + } + } + + /// Internal field + RevocationDistributionType? distributionPoint; + RevocationName? _issuer; + + /// Internal method + RevocationDistribution? getCrlDistribution(dynamic obj) { + if (obj == null || obj is RevocationDistribution) { + return obj; + } + if (obj is Asn1Sequence) { + return RevocationDistribution(obj); + } + throw Exception('Invalid entry in CRL distribution point'); + } + + @override + Asn1 getAsn1() { + final Asn1EncodeCollection collection = Asn1EncodeCollection(); + if (distributionPoint != null) { + collection.add([DerTag(0, distributionPoint)]); + } + if (_issuer != null) { + collection.add([DerTag(2, _issuer, false)]); + } + return DerSequence(collection: collection); + } +} + +/// Internal class +class RevocationDistributionType extends Asn1Encode { + /// Internal constructor. + RevocationDistributionType([Asn1Tag? tag]) { + if (tag != null) { + type = tag.tagNumber; + if (type == fullName) { + final RevocationName crl = RevocationName(); + name = crl.getCrlName(tag, false); + } else { + name = Asn1Set.getAsn1Set(tag, false); + } + } + } + + /// Internal field + Asn1Encode? name; + + /// Internal fields + int? type; + + /// Internal constants + static const int fullName = 0; + + /// Internal method + RevocationDistributionType? getDistributionType( + Asn1Tag tag, + bool isExplicit, + ) { + return getDistributionTypeFromObj(Asn1Tag.getTag(tag, true)); + } + + /// Internal method + RevocationDistributionType? getDistributionTypeFromObj(dynamic obj) { + if (obj == null || obj is RevocationDistributionType) { + return obj; + } + if (obj is Asn1Tag) { + return RevocationDistributionType(obj); + } + throw Exception('Invalid entry in sequence'); + } + + @override + Asn1 getAsn1() { + return DerTag(type, name, false); + } +} + +/// Internal class +class RevocationName extends Asn1Encode { + /// Internal constructor. + RevocationName([Asn1Sequence? sequence]) { + if (sequence != null) { + names = []; + for (int i = 0; i != sequence.count; i++) { + final OcspTag name = OcspTag(); + names!.add(name.getOcspName(sequence[i])!); + } + } + } + + /// Internal field + List? names; + + /// Internal method + RevocationName? getCrlNameFromObj(dynamic obj) { + if (obj == null || obj is RevocationName) { + return obj; + } + if (obj is Asn1Sequence) { + return RevocationName(obj); + } + throw Exception('Invalid entry in sequence'); + } + + /// Internal method + RevocationName? getCrlName(Asn1Tag tag, bool isExplicit) { + return getCrlNameFromObj(Asn1Sequence.getSequence(tag, isExplicit)); + } + + @override + Asn1 getAsn1() { + return DerSequence(array: names); + } +} + +/// Internal class +class OcspTag extends Asn1Encode { + /// Internal constructor. + OcspTag([int? tag, Asn1Encode? encode]) { + if (encode != null) { + _encode = encode; + } + if (tag != null) { + tagNumber = tag; + } + } + + /// Internal fields + Asn1Encode? _encode; + + /// Internal fields + int? tagNumber; + + /// Internal method + OcspTag? getOcspName(dynamic obj) { + if (obj == null || obj is OcspTag) { + return obj; + } + if (obj is Asn1Tag) { + final Asn1Tag tag = obj; + final int? tagNumber = obj.tagNumber; + if (tagNumber != null) { + switch (tagNumber) { + case 0: + return OcspTag(tagNumber, Asn1Sequence.getSequence(tag, false)); + case 1: + return OcspTag( + tagNumber, + DerAsciiString.getAsciiString(tag, false), + ); + case 2: + return OcspTag( + tagNumber, + DerAsciiString.getAsciiString(tag, false), + ); + case 3: + throw Exception('Invalid tag number specified: $tagNumber'); + case 4: + return OcspTag(tagNumber, X509Name.getName(tag, true)); + case 5: + return OcspTag(tagNumber, Asn1Sequence.getSequence(tag, false)); + case 6: + return OcspTag( + tagNumber, + DerAsciiString.getAsciiString(tag, false), + ); + case 7: + return OcspTag(tagNumber, Asn1Octet.getOctetString(tag, false)); + case 8: + return OcspTag(tagNumber, DerObjectID.getID(tag.getObject())); + } + } + } + if (obj is List) { + try { + return getOcspName(Asn1.fromByteArray(obj)); + } catch (e) { + throw Exception('Invalid OCSP name to parse'); + } + } + throw Exception('Invalid entry in sequence'); + } + + @override + Asn1 getAsn1() { + return DerTag(tagNumber, _encode, tagNumber == 4); + } +} + +/// internal class +class CertificateUtililty { + /// internal method + List? getCrlUrls(X509Certificate certificate) { + final List urls = []; + try { + final Asn1? obj = getExtensionValue( + certificate, + X509Extensions.crlDistributionPoints.id!, + ); + if (obj == null) { + return null; + } + final RevocationPointList list = RevocationPointList(); + final RevocationPointList distributionList = list.getCrlPointList(obj); + final List distributionLists = + distributionList.getDistributionPoints(); + for (final RevocationDistribution entry in distributionLists) { + final RevocationDistributionType? distributionPointName = + entry.distributionPoint; + if (distributionPointName != null) { + if (RevocationDistributionType.fullName != + distributionPointName.type) { + continue; + } + if (distributionPointName.name != null && + distributionPointName.name is RevocationName) { + final RevocationName generalNames = + distributionPointName.name! as RevocationName; + final List? names = generalNames.names; + if (names != null) { + for (final OcspTag name in names) { + if (name.tagNumber != 6) { + continue; + } + final DerAsciiString? asciiString = + DerAsciiString.getAsciiString( + name.getAsn1() as Asn1Tag, + false, + ); + if (asciiString != null) { + final String? url = asciiString.getString(); + if (url != null && url.toLowerCase().endsWith('.crl') || + !isNullOrEmpty(url)) { + urls.add(url!); + } + } + } + } + } + } + } + return urls; + } catch (ex) { + return null; + } + } + + /// internal method + String? getOcspUrl(X509Certificate certificate) { + try { + final Asn1? asn1 = getExtensionValue( + certificate, + X509Extensions.authorityInfoAccess.id!, + ); + if (asn1 == null) { + return null; + } + if (asn1 is Asn1Sequence) { + final Asn1Sequence sequence = asn1; + for (int i = 0; i < sequence.count; i++) { + final dynamic asn1Sequence = sequence.objects![i]; + if (asn1Sequence is Asn1Sequence) { + if (asn1Sequence.count != 2) { + continue; + } else { + if (asn1Sequence.objects![0] is DerObjectID && + (asn1Sequence.objects![0]! as DerObjectID).id == + '1.3.6.1.5.5.7.48.1') { + final dynamic obj = asn1Sequence.objects![1]; + if (obj is Asn1) { + final String? accessLocation = getStringFromGeneralName(obj); + if (accessLocation == null) { + return ''; + } else { + return accessLocation; + } + } + } + } + } + } + } + } catch (e) { + return null; + } + return null; + } + + /// internal method + Asn1? getExtensionValue(X509Certificate certificate, String id) { + List? bytes; + final Asn1Octet? extension = certificate.getExtension(DerObjectID(id)); + if (extension != null) { + bytes = extension.getDerEncoded(); + } + if (bytes == null) { + return null; + } + Asn1Stream asn1 = Asn1Stream(PdfStreamReader(bytes)); + final Asn1? octet = asn1.readAsn1(); + if (octet is Asn1Octet) { + asn1 = Asn1Stream(PdfStreamReader(octet.getOctets())); + } + return asn1.readAsn1(); + } + + /// internal method + String? getStringFromGeneralName(Asn1 names) { + if (names is Asn1Tag) { + final Asn1Octet? octet = Asn1Octet.getOctetString(names, false); + if (octet != null) { + final List? bytes = octet.getOctets(); + if (bytes != null) { + return utf8.decode(bytes); + } + } + } + return null; + } +} + +/// internal class +class OcspRequestCreator { + /// Internal constructor + OcspRequestCreator() { + _list = []; + } + + /// Internal field + late List _list; + + /// Internal field + X509Extensions? requestExtensions; + OcspTag? _requestorName; + + /// Internal method + void addRequest(CertificateIdentity id) { + _list.add(RequestCreatorHelper(id, null)); + } + + /// Internal method + OcspRequestHelper createRequest() { + final Asn1EncodeCollection requests = Asn1EncodeCollection(); + for (final dynamic reqObj in _list) { + try { + requests.add([(reqObj as RequestCreatorHelper).toRequest()]); + } catch (e) { + throw Exception('Invalid request creation'); + } + } + final OcspRequestCollection requestList = OcspRequestCollection( + _requestorName, + DerSequence(collection: requests), + requestExtensions!, + ); + return OcspRequestHelper(RevocationListRequest(requestList)); + } +} + +/// Internal class +class OcspRequestHelper extends X509ExtensionBase { + /// Internal constructor + OcspRequestHelper(this._request); + + /// Internal field + final RevocationListRequest _request; + + @override + X509Extensions? getX509Extensions() { + return null; + } + + /// Internal method + List? getEncoded() { + return _request.getEncoded(); + } +} + +/// Internal class +class OcspRequestCollection extends Asn1Encode { + /// Internal constructor + OcspRequestCollection( + this._requestorName, + this._requestList, + this._requestExtensions, + ) { + _version = DerInteger([0]); + versionSet = false; + } + + /// Internal fields + late DerInteger _version; + late final OcspTag? _requestorName; + late final Asn1Sequence _requestList; + late final X509Extensions _requestExtensions; + + /// Internal fields + late bool versionSet; + + ///Internal method + @override + Asn1 getAsn1() { + final Asn1EncodeCollection collection = Asn1EncodeCollection(); + if (!(_version == DerInteger([0])) || versionSet) { + collection.add([DerTag(0, _version, true)]); + } + if (_requestorName != null) { + collection.add([DerTag(1, _requestorName, true)]); + } + collection.add([_requestList]); + collection.add([DerTag(2, _requestExtensions, true)]); + return DerSequence(collection: collection); + } +} + +/// Internal class +class RevocationListRequest extends Asn1Encode { + /// Internal constructor + RevocationListRequest(this._requests); + + /// Internal field + late final OcspRequestCollection _requests; + + @override + Asn1 getAsn1() { + return DerSequence( + collection: Asn1EncodeCollection([_requests]), + ); + } +} + +/// Internal class +class OcspResponseHelper { + /// Internal constructor + OcspResponseHelper(List data) { + _response = OcspResponse( + Asn1Stream(PdfStreamReader(data)).readAsn1()! as Asn1Sequence, + ); + } + + /// Internal field + late OcspResponse _response; + + /// Internal method + List? getResponseBytes() { + final RevocationResponseBytes? bytes = _response.responseBytes; + if (bytes == null) { + return null; + } + if (bytes.responseType.id == OcspConstants.ocspBasic.id) { + try { + return bytes.response.getOctets(); + } catch (e) { + throw Exception('Invalid response detected'); + } + } + return null; + } +} + +/// Internal class +class OcspResponse extends Asn1Encode { + /// Internal constructor + OcspResponse(Asn1Sequence sequence) { + if (sequence.count == 2) { + responseBytes = RevocationResponseBytes().getResponseBytes( + sequence[1]! as Asn1Tag, + true, + ); + } + } + + /// Internal field + RevocationResponseBytes? responseBytes; + + @override + Asn1 getAsn1() { + final Asn1EncodeCollection collection = Asn1EncodeCollection([ + DerCatalogue([0]), + ]); + if (responseBytes != null) { + collection.add([DerTag(0, responseBytes, true)]); + } + return DerSequence(collection: collection); + } +} + +/// Internal class +class RevocationResponseBytes extends Asn1Encode { + /// Internal constructor + RevocationResponseBytes([Asn1Sequence? sequence]) { + if (sequence != null) { + if (sequence.count != 2) { + ArgumentError.value(sequence, 'Invalid length in sequence'); + } + responseType = DerObjectID.getID(sequence[0])!; + response = Asn1Octet.getOctetStringFromObject(sequence[1])!; + } + } + + /// Internal field + late DerObjectID responseType; + + /// Internal field + late Asn1Octet response; + + /// Internal method + RevocationResponseBytes? getResponseBytes(Asn1Tag tag, bool isExplicit) { + return getResponseBytesFromObject( + Asn1Sequence.getSequence(tag, isExplicit), + ); + } + + /// Internal method + RevocationResponseBytes? getResponseBytesFromObject(dynamic obj) { + if (obj == null || obj is RevocationResponseBytes) { + return obj; + } else if (obj is Asn1Sequence) { + return RevocationResponseBytes(obj); + } + ArgumentError.checkNotNull(obj, 'Invalid entry in sequence'); + return null; + } + + @override + Asn1 getAsn1() { + return DerSequence(array: [responseType, response]); + } +} + +/// internal class +class RequestCreatorHelper { + /// internal constructor + RequestCreatorHelper(this._id, this._extensions); + + /// internal field + late final CertificateIdentity _id; + final X509Extensions? _extensions; + + /// internal method + RevocationRequest toRequest() { + return RevocationRequest(_id.id!, _extensions); + } +} + +/// internal class +class RevocationRequest extends Asn1Encode { + /// internal constructor + RevocationRequest(this._certificateID, this._singleRequestExtensions); + + /// internal field + late final CertificateIdentityHelper _certificateID; + final X509Extensions? _singleRequestExtensions; + + @override + Asn1 getAsn1() { + final Asn1EncodeCollection collection = Asn1EncodeCollection([ + _certificateID, + ]); + if (_singleRequestExtensions != null) { + collection.add([DerTag(0, _singleRequestExtensions, true)]); + } + return DerSequence(collection: collection); + } +} + +/// Internal class +class TimeStampRequestCreator extends Asn1 { + /// Internal field + static const String _idSHA256 = '2.16.840.1.101.3.4.2.1'; + + /// Internal method + List getAsnEncodedTimestampRequest(List hash) { + final Asn1Identifier digestAlgOid = Asn1Identifier(_idSHA256); + final Algorithms algID = Algorithms.fromAsn1Sequence( + digestAlgOid, + DerNull.value, + ); + final Asn1Sequence seq = Asn1Sequence(); + seq.objects!.add(algID); + seq.objects!.add(Asn1Octet(hash)); + final Asn1Sequence asn1Sequence = Asn1Sequence(); + asn1Sequence.objects!.add(Asn1Integer(1)); + asn1Sequence.objects!.add(seq); + asn1Sequence.objects!.add(Asn1Integer(100)); + asn1Sequence.objects!.add(Asn1Boolean(true)); + return asn1Sequence.asnEncode()!; + } + + @override + void encode(DerStream derOut) { + throw UnsupportedError('This functionality is not implemented yet.'); + } +} + +// ignore: avoid_classes_with_only_static_members +/// internal class +class OcspConstants { + /// internal field + static DerObjectID ocsp = DerObjectID('1.3.6.1.5.5.7.48.1'); + + /// internal field + static DerObjectID ocspBasic = DerObjectID('1.3.6.1.5.5.7.48.1.1'); + + /// internal field + static DerObjectID ocspNonce = DerObjectID('$ocsp.2'); + + /// internal field + static DerObjectID ocspCrl = DerObjectID('$ocsp.3'); + + /// internal field + static DerObjectID ocspResponse = DerObjectID('$ocsp.4'); + + /// internal field + static DerObjectID ocspNocheck = DerObjectID('$ocsp.5'); + + /// internal field + static DerObjectID ocspArchiveCutoff = DerObjectID('$ocsp.6'); + + /// internal field + static DerObjectID ocspServiceLocator = DerObjectID('$ocsp.7'); +} + +/// Send the data to the server and get the response. +Future?> fetchData( + dynamic uri, + String method, { + String? contentType, + String? userName, + String? password, + List? data, + Duration? timeOutDuration, +}) async { + final http.Client client = http.Client(); + try { + if (uri is String) { + uri = Uri.parse(uri); + } + final http.Request request = http.Request(method, uri as Uri); + if (contentType != null) { + request.headers.addAll({'Content-Type': contentType}); + } + if (password != null && userName != null) { + request.headers.addAll({ + 'Authorization': + 'Basic ${base64Encode(utf8.encode('$userName:$password'))}', + }); + } + if (data != null) { + request.bodyBytes = data; + } + final http.StreamedResponse response = + await ((timeOutDuration != null) + ? client.send(request).timeout(timeOutDuration) + : client.send(request)); + final List responseBytes = await response.stream.toBytes(); + return (response.statusCode == 200) ? responseBytes : null; + } catch (e) { + return null; + } finally { + client.close(); + } +} 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 index 036da49f2..c1b4b647a 100644 --- 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 @@ -3,7 +3,9 @@ import '../asn1/asn1.dart'; import '../asn1/asn1_stream.dart'; import '../asn1/der.dart'; import '../cryptography/cipher_block_chaining_mode.dart'; +import '../cryptography/ipadding.dart'; import '../cryptography/signature_utilities.dart'; +import '../pdf_signature_dictionary.dart'; import '../pkcs/pfx_data.dart'; import 'x509_name.dart'; import 'x509_time.dart'; @@ -90,8 +92,10 @@ class X509Extension { /// internal class class X509Extensions extends Asn1Encode { /// internal constructor - X509Extensions(Map extensions, - [List? ordering]) { + X509Extensions( + Map extensions, [ + List? ordering, + ]) { _extensions = {}; if (ordering == null) { final List der = []; @@ -118,13 +122,17 @@ class X509Extensions extends Asn1Encode { final Asn1Sequence s = Asn1Sequence.getSequence(ae.getAsn1())!; if (s.count < 2 || s.count > 3) { throw ArgumentError.value( - seq, 'count', 'Bad sequence size: ${s.count}'); + seq, + 'count', + 'Bad sequence size: ${s.count}', + ); } 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()); + final Asn1Octet? octets = Asn1Octet.getOctetStringFromObject( + s[s.count - 1]!.getAsn1(), + ); _extensions[oid] = X509Extension(isCritical, octets); _ordering.add(oid); } @@ -155,6 +163,13 @@ class X509Extensions extends Asn1Encode { /// internal field static DerObjectID authorityKeyIdentifier = DerObjectID('2.5.29.35'); + + /// internal field + static DerObjectID crlDistributionPoints = DerObjectID('2.5.29.31'); + + /// internal field + static DerObjectID authorityInfoAccess = DerObjectID('1.3.6.1.5.5.7.1.1'); + //Implementation @override Asn1 getAsn1() { @@ -185,12 +200,16 @@ class X509Certificate extends X509ExtensionBase { try { final Asn1Octet? str = getExtension(DerObjectID('2.5.29.15')); if (str != null) { - final DerBitString bits = DerBitString.getDetBitString( - Asn1Stream(PdfStreamReader(str.getOctets())).readAsn1())!; + final DerBitString bits = + DerBitString.getDetBitString( + Asn1Stream(PdfStreamReader(str.getOctets())).readAsn1(), + )!; final List bytes = bits.getBytes()!; final int length = (bytes.length * 8) - bits.extra!; - _keyUsage = - List.generate((length < 9) ? 9 : length, (int i) => false); + _keyUsage = List.generate( + (length < 9) ? 9 : length, + (int i) => false, + ); for (int i = 0; i != length; i++) { _keyUsage![i] = (bytes[i ~/ 8] & (0x80 >> (i % 8))) != 0; } @@ -199,7 +218,10 @@ class X509Certificate extends X509ExtensionBase { } } catch (e) { throw ArgumentError.value( - e, 'ArgumentError', 'cannot construct KeyUsage'); + e, + 'ArgumentError', + 'cannot construct KeyUsage', + ); } } //Fields @@ -217,6 +239,16 @@ class X509Certificate extends X509ExtensionBase { return createKey(c!.subjectPublicKeyInfo!); } + /// internal method + List? getTbsCertificate() { + return c!.tbsCertificate!.getDerEncoded(); + } + + /// internal method + List? getSignature() { + return c!.signature!.getBytes(); + } + /// internal method CipherParameter createKey(PublicKeyInformation keyInfo) { CipherParameter result; @@ -229,27 +261,69 @@ class X509Certificate extends X509ExtensionBase { result = RsaKeyParam(false, pubKey.modulus, pubKey.publicExponent); } else { throw ArgumentError.value( - keyInfo, 'keyInfo', 'algorithm identifier in key not recognised'); + keyInfo, + 'keyInfo', + 'algorithm identifier in key not recognised', + ); } return result; } + + /// internal method + void verify(CipherParameter key) { + if (c != null) { + final String sigName = c!.signatureAlgorithm!.id!.id!; + final SignerUtilities util = SignerUtilities(); + final ISigner signature = util.getSigner(sigName); + checkSignature(key, signature); + } + } + + /// internal method + void checkSignature(CipherParameter publicKey, ISigner signature) { + if (!isAlgIDEqual(c!.signatureAlgorithm!, c!.tbsCertificate!.signature!)) { + throw Exception('signature algorithm in TBS cert not same as outer cert'); + } + signature.initialize(false, publicKey); + final List? b = getTbsCertificate(); + if (b != null) { + signature.blockUpdate(b, 0, b.length); + final List? sig = getSignature(); + if (!signature.validateSignature(sig!)) { + throw Exception('Public key presented not for certificate signature'); + } + } + } + + /// internal method + bool isAlgIDEqual(Algorithms id1, Algorithms id2) { + if (!(id1.id == id2.id)) { + return false; + } + final Asn1Encode? p1 = id1.parameters; + final Asn1Encode? p2 = id2.parameters; + if ((p1 == null) == (p2 == null)) { + return p1 == p2; + } + return p1 == null ? p2!.getAsn1() is Asn1Null : p1.getAsn1() is Asn1Null; + } } /// internal class class X509CertificateStructure extends Asn1Encode { /// internal constructor X509CertificateStructure(Asn1Sequence seq) { - _tbsCert = _SingnedCertificate.getCertificate(seq[0]); + _tbsCert = SingnedCertificate.getCertificate(seq[0]); _sigAlgID = Algorithms.getAlgorithms(seq[1]); _sig = DerBitString.getDetBitString(seq[2]); } //Fields - _SingnedCertificate? _tbsCert; + SingnedCertificate? _tbsCert; Algorithms? _sigAlgID; DerBitString? _sig; //Properties /// internal property - _SingnedCertificate? get tbsCertificate => _tbsCert; + SingnedCertificate? get tbsCertificate => _tbsCert; /// internal property int get version => _tbsCert!.version; @@ -296,8 +370,10 @@ class X509CertificateStructure extends Asn1Encode { } } -class _SingnedCertificate extends Asn1Encode { - _SingnedCertificate(Asn1Sequence sequence) { +/// Internal class +class SingnedCertificate extends Asn1Encode { + /// internal constructor + SingnedCertificate(Asn1Sequence sequence) { int seqStart = 0; _sequence = sequence; if (sequence[0] is DerTag || sequence[0] is Asn1Tag) { @@ -313,11 +389,14 @@ class _SingnedCertificate extends Asn1Encode { _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--) { + _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: @@ -332,12 +411,14 @@ class _SingnedCertificate extends Asn1Encode { } } } - static _SingnedCertificate? getCertificate(dynamic obj) { - if (obj is _SingnedCertificate) { + + /// internal method + static SingnedCertificate? getCertificate(dynamic obj) { + if (obj is SingnedCertificate) { return obj; } if (obj != null) { - return _SingnedCertificate(Asn1Sequence.getSequence(obj)!); + return SingnedCertificate(Asn1Sequence.getSequence(obj)!); } return null; } @@ -356,16 +437,37 @@ class _SingnedCertificate extends Asn1Encode { DerBitString? _subjectID; X509Extensions? _extensions; //Properties + /// internal field int get version => _version!.value.toSigned(32).toInt() + 1; + + /// internal field DerInteger? get serialNumber => _serialNumber; + + /// internal field Algorithms? get signature => _signature; + + /// internal field X509Name? get issuer => _issuer; + + /// internal field X509Time? get startDate => _startDate; + + /// internal field X509Time? get endDate => _endDate; + + /// internal field X509Name? get subject => _subject; + + /// internal field PublicKeyInformation? get subjectPublicKeyInfo => _publicKeyInformation; + + /// internal field DerBitString? get issuerUniqueID => _issuerID; + + /// internal field DerBitString? get subjectUniqueID => _subjectID; + + /// internal field X509Extensions? get extensions => _extensions; //Implementation @override @@ -386,7 +488,10 @@ class PublicKeyInformation extends Asn1Encode { PublicKeyInformation.fromSequence(Asn1Sequence sequence) { if (sequence.count != 2) { throw ArgumentError.value( - sequence, 'sequence', 'Invalid length in sequence'); + sequence, + 'sequence', + 'Invalid length in sequence', + ); } _algorithms = Algorithms.getAlgorithms(sequence[0]); _publicKey = DerBitString.getDetBitString(sequence[1]); @@ -460,10 +565,12 @@ class RsaPublicKey extends Asn1Encode { @override Asn1 getAsn1() { - return DerSequence(array: [ - DerInteger.fromNumber(modulus), - DerInteger.fromNumber(publicExponent) - ]); + return DerSequence( + array: [ + DerInteger.fromNumber(modulus), + DerInteger.fromNumber(publicExponent), + ], + ); } } @@ -478,18 +585,38 @@ class X509CertificateParser { //Implementation /// internal method X509Certificate? readCertificate(PdfStreamReader inStream) { - if (_currentStream == null) { + if (_currentStream == null || _currentStream != inStream) { _currentStream = inStream; _sData = null; _sDataObjectCount = 0; - } else if (_currentStream != inStream) { + } + 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)); + } + + /// Internal method + List? getCertificateChain(PdfStreamReader inStream) { + if (_currentStream == null || _currentStream != inStream) { _currentStream = inStream; _sData = null; _sDataObjectCount = 0; } if (_sData != null) { if (_sDataObjectCount != _sData!.objects.length) { - return getCertificate(); + return _getCertificateChain(); } _sData = null; _sDataObjectCount = 0; @@ -501,7 +628,7 @@ class X509CertificateParser { return null; } pis.unread(tag); - return readDerCertificate(Asn1Stream(pis)); + return _readDerCertificates(Asn1Stream(pis)); } /// internal method @@ -512,7 +639,8 @@ class X509CertificateParser { _sDataObjectCount = _sDataObjectCount! + 1; if (obj is Asn1Sequence) { return createX509Certificate( - X509CertificateStructure.getInstance(obj)); + X509CertificateStructure.getInstance(obj), + ); } } } @@ -546,6 +674,51 @@ class X509CertificateParser { return createX509Certificate(X509CertificateStructure.getInstance(seq)); } + List? _getCertificateChain() { + final List certList = []; + if (_sData != null) { + while (_sDataObjectCount! < _sData!.objects.length) { + final dynamic obj = _sData![_sDataObjectCount!]; + _sDataObjectCount = _sDataObjectCount! + 1; + if (obj is Asn1Sequence) { + certList.add( + createX509Certificate(X509CertificateStructure.getInstance(obj)), + ); + } + } + } + return certList.isNotEmpty ? certList : null; + } + + /// internal method + List? _readDerCertificates(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; + // ignore: avoid_function_literals_in_foreach_calls + signedSequence.objects!.forEach((dynamic o) { + if (isContinue && o is Asn1Tag) { + if (o.tagNumber == 0) { + _sData = Asn1Set.getAsn1Set(o, false); + isContinue = false; + } + } + }); + } + return _getCertificateChain(); + } + } + } + return [ + createX509Certificate(X509CertificateStructure.getInstance(seq)), + ]; + } + /// internal method 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 index e4133cb44..e7428e74e 100644 --- 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 @@ -16,7 +16,10 @@ class X509Name extends Asn1Encode { Asn1Sequence.getSequence(asn1Set[i]!.getAsn1())!; if (asn1Sequence.count != 2) { throw ArgumentError.value( - sequence, 'sequence', 'Invalid length in sequence'); + sequence, + 'sequence', + 'Invalid length in sequence', + ); } _ordering.add(DerObjectID.getID(asn1Sequence[0]!.getAsn1())); final Asn1? asn1 = asn1Sequence[1]!.getAsn1(); @@ -210,11 +213,19 @@ class X509Name extends Asn1Encode { if (_added[i] as bool) { result = '+'; result = appendValue( - result, symbols, _ordering[i] as DerObjectID?, _values[i]); + result, + symbols, + _ordering[i] as DerObjectID?, + _values[i], + ); } else { result = ''; result = appendValue( - result, symbols, _ordering[i] as DerObjectID?, _values[i]); + result, + symbols, + _ordering[i] as DerObjectID?, + _values[i], + ); components.add(result); } } @@ -233,8 +244,12 @@ class X509Name extends Asn1Encode { } /// internal method - String appendValue(String builder, Map symbols, - DerObjectID? id, String value) { + String appendValue( + String builder, + Map symbols, + DerObjectID? id, + String value, + ) { final String? symbol = symbols[id!]; if (symbol != null) { builder += symbol; @@ -257,7 +272,8 @@ class X509Name extends Asn1Encode { (builder[index] == '<') || (builder[index] == '>') || (builder[index] == ';')) { - builder = builder.substring(0, index) + + builder = + builder.substring(0, index) + r'\' + builder.substring(index, builder.length); index++; 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 ca0690326..4eb93ad7b 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 @@ -7,7 +7,7 @@ enum PdfEncryptionOptions { encryptAllContentsExceptMetadata, /// To encrypt atttachment files only. - encryptOnlyAttachments + encryptOnlyAttachments, } /// Specifies the type of encryption algorithm. @@ -25,7 +25,7 @@ enum PdfEncryptionAlgorithm { aesx256Bit, /// AES encryption algorithm - 256-bit key with revision 6. - aesx256BitRevision6 + aesx256BitRevision6, } /// Specifies the type of PDF permissions. @@ -55,7 +55,7 @@ enum PdfPermissionsFlags { assembleDocument, /// Full quality print. - fullQualityPrint + fullQualityPrint, } /// Specifies the available permissions on certificated document. @@ -67,7 +67,7 @@ enum PdfCertificationFlags { allowFormFill, /// Only allow commenting and form fill-in actions on this document. - allowComments + allowComments, } /// Specifies the cryptographic standard. @@ -76,7 +76,7 @@ enum CryptographicStandard { cms, /// CMS Advanced Electronic Signatures - cades + cades, } /// Specifies the digestion algorithm. @@ -91,5 +91,5 @@ enum DigestAlgorithm { sha384, /// SHA512 message digest algorithm - sha512 + 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 a6847141c..93a2f0c96 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 @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:math'; +import 'dart:typed_data'; import 'package:crypto/crypto.dart'; @@ -13,6 +14,10 @@ import '../primitives/pdf_name.dart'; import '../primitives/pdf_number.dart'; import '../primitives/pdf_string.dart'; import 'digital_signature/cryptography/aes_cipher.dart'; +import 'digital_signature/cryptography/aes_engine.dart'; +import 'digital_signature/cryptography/buffered_block_padding_base.dart'; +import 'digital_signature/cryptography/cipher_block_chaining_mode.dart'; +import 'digital_signature/cryptography/ipadding.dart'; import 'enum.dart'; /// internal class @@ -103,7 +108,9 @@ class PdfEncryptor { if (_randomBytes == null) { final Random random = Random.secure(); _randomBytes = List.generate( - _randomBytesAmount!, (int i) => random.nextInt(256)); + _randomBytesAmount!, + (int i) => random.nextInt(256), + ); } return _randomBytes; } @@ -116,7 +123,8 @@ class PdfEncryptor { set permissions(List value) { changed = true; _permissions = value; - _permissionValue = (_getPermissionValue(_permissions!) | _permissionSet!) & + _permissionValue = + (_getPermissionValue(_permissions!) | _permissionSet!) & _permissionCleared!; if (revisionNumber! > 2) { _permissionValue = _permissionValue! & _permissionRevisionTwoMask!; @@ -129,8 +137,8 @@ class PdfEncryptor { final List perm = permissions; final bool bEncrypt = (!(perm.length == 1 && perm.contains(PdfPermissionsFlags.none))) || - _userPassword!.isNotEmpty || - _ownerPassword!.isNotEmpty; + _userPassword!.isNotEmpty || + _ownerPassword!.isNotEmpty; return isEncrypt! && bEncrypt; } @@ -241,7 +249,7 @@ class PdfEncryptor { 0x000100, 0x000200, 0x000400, - 0x000800 + 0x000800, ]; permissions = [PdfPermissionsFlags.none]; encryptionOptions = PdfEncryptionOptions.encryptAllContents; @@ -277,7 +285,7 @@ class PdfEncryptor { 100, 83, 105, - 122 + 122, ]; customArray = List.filled(_bytesAmount!, 0, growable: true); changed = false; @@ -319,7 +327,10 @@ class PdfEncryptor { List _create40BitUserPassword() { ArgumentError.checkNotNull(_encryptionKey); return _encryptDataByCustom( - List.from(paddingBytes!), _encryptionKey, _encryptionKey!.length); + List.from(paddingBytes!), + _encryptionKey, + _encryptionKey!.length, + ); } List _create128BitUserPassword() { @@ -328,22 +339,32 @@ class PdfEncryptor { data.addAll(paddingBytes!); data.addAll(randomBytes!); final List resultBytes = md5.convert(data).bytes; - final List dataForCustom = - List.generate(_randomBytesAmount!, (int i) => resultBytes[i]); + final List dataForCustom = List.generate( + _randomBytesAmount!, + (int i) => resultBytes[i], + ); List dataFromCustom = _encryptDataByCustom( - dataForCustom, _encryptionKey, _encryptionKey!.length); + dataForCustom, + _encryptionKey, + _encryptionKey!.length, + ); for (int i = 1; i < _ownerLoopNum2!; i++) { final List currentKey = _getKeyWithOwnerPassword(_encryptionKey!, i); - dataFromCustom = - _encryptDataByCustom(dataFromCustom, currentKey, currentKey.length); + dataFromCustom = _encryptDataByCustom( + dataFromCustom, + currentKey, + currentKey.length, + ); } return _padTrancateString(dataFromCustom); } List _create256BitUserPassword() { final Random random = Random.secure(); - _userRandomBytes = - List.generate(_randomBytesAmount!, (int i) => random.nextInt(256)); + _userRandomBytes = List.generate( + _randomBytesAmount!, + (int i) => random.nextInt(256), + ); final List userPasswordBytes = utf8.encode(_userPassword!); final List hash = []; hash.addAll(userPasswordBytes); @@ -358,15 +379,22 @@ class PdfEncryptor { 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); + final List userPasswordBytes = _padTrancateString( + utf8.encode(userPassword), + ); + List dataFromCustom = _encryptDataByCustom( + userPasswordBytes, + customKey, + customKey.length, + ); if (revisionNumber! > 2) { for (int i = 1; i < _ownerLoopNum2!; i++) { final List currentKey = _getKeyWithOwnerPassword(customKey, i); - dataFromCustom = - _encryptDataByCustom(dataFromCustom, currentKey, currentKey.length); + dataFromCustom = _encryptDataByCustom( + dataFromCustom, + currentKey, + currentKey.length, + ); } } return dataFromCustom; @@ -374,8 +402,10 @@ class PdfEncryptor { List _create256BitOwnerPassword() { final Random random = Random.secure(); - _ownerRandomBytes = - List.generate(_randomBytesAmount!, (int i) => random.nextInt(256)); + _ownerRandomBytes = List.generate( + _randomBytesAmount!, + (int i) => random.nextInt(256), + ); final String password = ownerPassword.isEmpty ? userPassword : ownerPassword; final List ownerPasswordBytes = utf8.encode(password); @@ -394,8 +424,10 @@ class PdfEncryptor { hash.addAll(utf8.encode(userPassword)); hash.addAll(List.generate(8, (int i) => _userRandomBytes![i + 8])); final List hashBytes = sha256.convert(hash).bytes; - return AesCipherNoPadding(true, hashBytes) - .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); + return AesCipherNoPadding( + true, + KeyParameter(Uint8List.fromList(hashBytes)), + ).process(Uint8List.fromList(_fileEncryptionKey!)); } List? _createOwnerEncryptionKey() { @@ -406,8 +438,10 @@ class PdfEncryptor { hash.addAll(List.generate(8, (int i) => _ownerRandomBytes![i + 8])); hash.addAll(_userPasswordOut!); final List hashBytes = sha256.convert(hash).bytes; - return AesCipherNoPadding(true, hashBytes) - .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); + return AesCipherNoPadding( + true, + KeyParameter(Uint8List.fromList(hashBytes)), + ).process(Uint8List.fromList(_fileEncryptionKey!)); } List? _createPermissionFlag() { @@ -428,19 +462,25 @@ class PdfEncryptor { 98, 98, 98, - 98 + 98, ]; - return AesCipherNoPadding(true, _fileEncryptionKey!) - .processBlock(permissionFlagBytes, 0, permissionFlagBytes.length); + return AesCipherNoPadding( + true, + KeyParameter(Uint8List.fromList(_fileEncryptionKey!)), + ).process(Uint8List.fromList(permissionFlagBytes)); } void _createAcrobatX256BitUserPassword() { final List userPasswordBytes = utf8.encode(_userPassword!); final Random random = Random.secure(); - final List userValidationSalt = - List.generate(8, (int i) => random.nextInt(256)); - final List userKeySalt = - List.generate(8, (int i) => random.nextInt(256)); + final List userValidationSalt = List.generate( + 8, + (int i) => random.nextInt(256), + ); + final List userKeySalt = List.generate( + 8, + (int i) => random.nextInt(256), + ); List hash = []; hash.addAll(userPasswordBytes); hash.addAll(userValidationSalt); @@ -453,8 +493,10 @@ class PdfEncryptor { hash.addAll(userPasswordBytes); hash.addAll(userKeySalt); hash = _acrobatXComputeHash(hash, userPasswordBytes, null); - _userEncryptionKeyOut = AesCipherNoPadding(true, hash) - .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); + _userEncryptionKeyOut = AesCipherNoPadding( + true, + KeyParameter(Uint8List.fromList(hash)), + ).process(Uint8List.fromList(_fileEncryptionKey!)); } void _createAcrobatX256BitOwnerPassword() { @@ -462,17 +504,26 @@ class PdfEncryptor { ownerPassword.isEmpty ? userPassword : ownerPassword; final List ownerPasswordBytes = utf8.encode(password); final Random random = Random.secure(); - final List ownerValidationSalt = - List.generate(8, (int i) => random.nextInt(256)); - final List ownerKeySalt = - List.generate(8, (int i) => random.nextInt(256)); + final List ownerValidationSalt = List.generate( + 8, + (int i) => random.nextInt(256), + ); + final List ownerKeySalt = List.generate( + 8, + (int i) => random.nextInt(256), + ); final List owenrPasswordOut = []; owenrPasswordOut.addAll(ownerPasswordBytes); owenrPasswordOut.addAll(ownerValidationSalt); owenrPasswordOut.addAll(_userPasswordOut!); _ownerPasswordOut = []; - _ownerPasswordOut!.addAll(_acrobatXComputeHash( - owenrPasswordOut, ownerPasswordBytes, _userPasswordOut)); + _ownerPasswordOut!.addAll( + _acrobatXComputeHash( + owenrPasswordOut, + ownerPasswordBytes, + _userPasswordOut, + ), + ); _ownerPasswordOut!.addAll(ownerValidationSalt); _ownerPasswordOut!.addAll(ownerKeySalt); List hash = []; @@ -480,8 +531,10 @@ class PdfEncryptor { hash.addAll(ownerKeySalt); hash.addAll(_userPasswordOut!); hash = _acrobatXComputeHash(hash, ownerPasswordBytes, _userPasswordOut); - _ownerEncryptionKeyOut = AesCipherNoPadding(true, hash) - .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); + _ownerEncryptionKeyOut = AesCipherNoPadding( + true, + KeyParameter(Uint8List.fromList(hash)), + ).process(Uint8List.fromList(_fileEncryptionKey!)); } void _createFileEncryptionKey() { @@ -490,7 +543,10 @@ class PdfEncryptor { } List _encryptDataByCustom( - List data, List? key, int keyLength) { + List data, + List? key, + int keyLength, + ) { final List buffer = List.filled(data.length, 0, growable: true); _recreateCustomArray(key, keyLength); keyLength = data.length; @@ -502,16 +558,20 @@ class PdfEncryptor { final int temp = customArray![tmp1]; customArray![tmp1] = customArray![tmp2]; customArray![tmp2] = temp; - final int byteXor = customArray![ - (customArray![tmp1] + customArray![tmp2]) % _bytesAmount!]; + final int byteXor = + customArray![(customArray![tmp1] + customArray![tmp2]) % + _bytesAmount!]; buffer[i] = (data[i] ^ byteXor).toUnsigned(8); } return buffer; } void _recreateCustomArray(List? key, int keyLength) { - final List tempArray = - List.filled(_bytesAmount!, 0, growable: true); + 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); @@ -531,11 +591,14 @@ class PdfEncryptor { final int? length = _getKeyLength(); if (revisionNumber! > 2) { for (int i = 0; i < _ownerLoopNum!; i++) { - currentHash = md5 - .convert(currentHash.length == length - ? currentHash - : List.generate(length!, (int i) => currentHash[i])) - .bytes; + currentHash = + md5 + .convert( + currentHash.length == length + ? currentHash + : List.generate(length!, (int i) => currentHash[i]), + ) + .bytes; } } return currentHash.length == length @@ -560,15 +623,19 @@ class PdfEncryptor { passwordBytes.addAll(source); } if (source.length < _stringLength!) { - passwordBytes.addAll(paddingBytes! - .getRange(0, paddingBytes!.length - passwordBytes.length)); + passwordBytes.addAll( + paddingBytes!.getRange(0, paddingBytes!.length - passwordBytes.length), + ); } return List.generate(_stringLength!, (int i) => passwordBytes[i]); } List _getKeyWithOwnerPassword(List originalKey, int index) { - final List result = - List.filled(originalKey.length, 0, growable: true); + final List result = List.filled( + originalKey.length, + 0, + growable: true, + ); for (int i = 0; i < originalKey.length; i++) { result[i] = (originalKey[i] ^ index).toUnsigned(8); } @@ -576,9 +643,12 @@ class PdfEncryptor { } List _createEncryptionKey( - String inputPassword, List ownerPasswordBytes) { - final List passwordBytes = - _padTrancateString(utf8.encode(inputPassword)); + String inputPassword, + List ownerPasswordBytes, + ) { + final List passwordBytes = _padTrancateString( + utf8.encode(inputPassword), + ); final List encryptionKeyData = []; encryptionKeyData.addAll(passwordBytes); encryptionKeyData.addAll(ownerPasswordBytes); @@ -586,7 +656,7 @@ class PdfEncryptor { _permissionValue!.toUnsigned(8), (_permissionValue! >> 8).toUnsigned(8), (_permissionValue! >> 16).toUnsigned(8), - (_permissionValue! >> 24).toUnsigned(8) + (_permissionValue! >> 24).toUnsigned(8), ]); encryptionKeyData.addAll(randomBytes!); int? revisionNumber; @@ -605,11 +675,14 @@ class PdfEncryptor { 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!, (int i) => currentHash[i])) - .bytes; + currentHash = + md5 + .convert( + currentHash.length == length + ? currentHash + : List.generate(length!, (int i) => currentHash[i]), + ) + .bytes; } } return currentHash.length == length @@ -618,18 +691,24 @@ class PdfEncryptor { } List _acrobatXComputeHash( - List input, List password, List? key) { + List input, + List password, + List? key, + ) { List hash = sha256.convert(input).bytes; List? finalHashKey; - for (int i = 0; - i < 64 || (finalHashKey![finalHashKey.length - 1] & 0xFF) > i - 32; - i++) { + for ( + int i = 0; + i < 64 || (finalHashKey![finalHashKey.length - 1] & 0xFF) > i - 32; + i++ + ) { final List roundHash = List.filled( - (key != null && key.length >= 48) - ? 64 * (password.length + hash.length + 48) - : 64 * (password.length + hash.length), - 0, - growable: true); + (key != null && key.length >= 48) + ? 64 * (password.length + hash.length + 48) + : 64 * (password.length + hash.length), + 0, + growable: true, + ); int position = 0; for (int j = 0; j < 64; j++) { List.copyRange(roundHash, position, password, 0, password.length); @@ -642,14 +721,21 @@ class PdfEncryptor { } } final List hashFirst = List.generate(16, (int i) => hash[i]); - final List hashSecond = - List.generate(16, (int i) => hash[i + 16]); + final List hashSecond = List.generate( + 16, + (int i) => hash[i + 16], + ); final AesCipher encrypt = AesCipher(true, hashFirst, hashSecond); finalHashKey = encrypt.update(roundHash, 0, roundHash.length); - final List finalHashKeyFirst = - List.generate(16, (int i) => finalHashKey![i]); - final BigInt finalKeyBigInteger = - _readBigIntFromBytes(finalHashKeyFirst, 0, finalHashKeyFirst.length); + final List finalHashKeyFirst = List.generate( + 16, + (int 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(); @@ -684,14 +770,17 @@ class PdfEncryptor { void readFromDictionary(PdfDictionary dictionary) { IPdfPrimitive? obj; if (dictionary.containsKey(PdfDictionaryProperties.filter)) { - obj = - PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.filter]); + obj = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.filter], + ); } if (obj != null && obj is PdfName && obj.name != PdfDictionaryProperties.standard) { throw ArgumentError.value( - obj, 'Invalid Format: Unsupported security filter'); + obj, + 'Invalid Format: Unsupported security filter', + ); } _permissionValue = dictionary.getInt(PdfDictionaryProperties.p); _updatePermissions(_permissionValue! & ~_permissionSet!); @@ -703,7 +792,8 @@ class PdfEncryptor { int keySize = dictionary.getInt(PdfDictionaryProperties.v); if (keySize == 4 && keySize != _revisionNumberOut) { throw ArgumentError.value( - 'Invalid Format: V and R entries of the Encryption dictionary does not match.'); + 'Invalid Format: V and R entries of the Encryption dictionary does not match.', + ); } if (keySize == 5) { _userEncryptionKeyOut = @@ -715,9 +805,10 @@ class PdfEncryptor { } _userPasswordOut = dictionary.getString(PdfDictionaryProperties.u)!.data; _ownerPasswordOut = dictionary.getString(PdfDictionaryProperties.o)!.data; - keyLength = dictionary.containsKey(PdfDictionaryProperties.length) - ? dictionary.getInt(PdfDictionaryProperties.length) - : (keySize == 1 ? 40 : (keySize == 2 ? 128 : 256)); + keyLength = + dictionary.containsKey(PdfDictionaryProperties.length) + ? dictionary.getInt(PdfDictionaryProperties.length) + : (keySize == 1 ? 40 : (keySize == 2 ? 128 : 256)); if (keyLength == 128 && _revisionNumberOut! < 4) { keySize = 2; encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x128Bit; @@ -739,9 +830,10 @@ class PdfEncryptor { (standardCryptFilter[PdfDictionaryProperties.cfm]! as PdfName).name; if (keyLength == 128) { keySize = 2; - encryptionAlgorithm = filterName != 'V2' - ? PdfEncryptionAlgorithm.aesx128Bit - : PdfEncryptionAlgorithm.rc4x128Bit; + encryptionAlgorithm = + filterName != 'V2' + ? PdfEncryptionAlgorithm.aesx128Bit + : PdfEncryptionAlgorithm.rc4x128Bit; } else { keySize = 3; encryptionAlgorithm = PdfEncryptionAlgorithm.aesx256Bit; @@ -767,7 +859,8 @@ class PdfEncryptor { keyLength! % 8 != 0 && (keySize == 1 || keySize == 2 || keySize == 3)) { throw ArgumentError.value( - 'Invalid format: Invalid/Unsupported security dictionary.'); + 'Invalid format: Invalid/Unsupported security dictionary.', + ); } hasComputedPasswordValues = true; } @@ -832,8 +925,11 @@ class PdfEncryptor { return _authenticate256BitUserPassword(password); } else { _encryptionKey = _createEncryptionKey(password, _ownerPasswordOut!); - return _compareByteArrays(_createUserPassword(), _userPasswordOut, - revisionNumber == 2 ? null : 0x10); + return _compareByteArrays( + _createUserPassword(), + _userPasswordOut, + revisionNumber == 2 ? null : 0x10, + ); } } @@ -845,13 +941,18 @@ class PdfEncryptor { _encryptionKey = _getKeyFromOwnerPassword(password); List? buff = _ownerPasswordOut; if (revisionNumber == 2) { - buff = - _encryptDataByCustom(buff!, _encryptionKey, _encryptionKey!.length); + buff = _encryptDataByCustom( + buff!, + _encryptionKey, + _encryptionKey!.length, + ); } else if (revisionNumber! > 2) { buff = _ownerPasswordOut; for (int i = 0; i < _ownerLoopNum2!; ++i) { final List currKey = _getKeyWithOwnerPassword( - _encryptionKey!, _ownerLoopNum2! - i - 1); + _encryptionKey!, + _ownerLoopNum2! - i - 1, + ); buff = _encryptDataByCustom(buff!, currKey, currKey.length); } } @@ -893,33 +994,63 @@ class PdfEncryptor { 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); + userPassword.length + userValidationSalt.length, + 0, + growable: true, + ); List.copyRange( - combinedUserpassword, 0, userPassword, 0, userPassword.length); - List.copyRange(combinedUserpassword, userPassword.length, - userValidationSalt, 0, userValidationSalt.length); + combinedUserpassword, + 0, + userPassword, + 0, + userPassword.length, + ); + List.copyRange( + combinedUserpassword, + userPassword.length, + userValidationSalt, + 0, + userValidationSalt.length, + ); hash = _acrobatXComputeHash(combinedUserpassword, userPassword, null); _advanceXUserFileEncryptionKey(password); return _compareByteArrays(hash, hashProvided); } else { List.copyRange( - hashProvided, 0, _userPasswordOut!, 0, hashProvided.length); + 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!, - userValidationSalt.length, - userKeySalt.length + userValidationSalt.length); + userValidationSalt, + 0, + _userRandomBytes!, + 0, + userValidationSalt.length, + ); + List.copyRange( + userKeySalt, + 0, + _userRandomBytes!, + userValidationSalt.length, + userKeySalt.length + userValidationSalt.length, + ); hash = List.filled( - userPassword.length + userValidationSalt.length, 0, - growable: true); + userPassword.length + userValidationSalt.length, + 0, + growable: true, + ); List.copyRange(hash, 0, userPassword, 0, userPassword.length); - List.copyRange(hash, userPassword.length, userValidationSalt, 0, - userValidationSalt.length); + List.copyRange( + hash, + userPassword.length, + userValidationSalt, + 0, + userValidationSalt.length, + ); final List hashFound = sha256.convert(hash).bytes; bool bEqual = false; if (hashFound.length == hashProvided.length) { @@ -939,8 +1070,11 @@ class PdfEncryptor { } bool _authenticate256BitOwnerPassword(String password) { - final List ownerValidationSalt = - List.filled(8, 0, growable: true); + final List ownerValidationSalt = List.filled( + 8, + 0, + growable: true, + ); final List ownerKeySalt = List.filled(8, 0, growable: true); final List hashProvided = List.filled(32, 0, growable: true); _ownerRandomBytes = List.filled(16, 0, growable: true); @@ -951,23 +1085,40 @@ class PdfEncryptor { List.copyRange(hashProvided, 0, _ownerPasswordOut!, 0, 32); List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut!, 32, 40); int userKeyLength = 48; - if (_userPasswordOut!.length < 48) + if (_userPasswordOut!.length < 48) { userKeyLength = _userPasswordOut!.length; + } final List mixedOwnerPassword = List.filled( - ownerPassword.length + ownerValidationSalt.length + userKeyLength, 0, - growable: true); + ownerPassword.length + ownerValidationSalt.length + userKeyLength, + 0, + growable: true, + ); List.copyRange( - mixedOwnerPassword, 0, ownerPassword, 0, ownerPassword.length); - List.copyRange(mixedOwnerPassword, ownerPassword.length, - ownerValidationSalt, 0, ownerValidationSalt.length); + mixedOwnerPassword, + 0, + ownerPassword, + 0, + ownerPassword.length, + ); List.copyRange( - mixedOwnerPassword, - ownerPassword.length + ownerValidationSalt.length, - _userPasswordOut!, - 0, - userKeyLength); + mixedOwnerPassword, + ownerPassword.length, + ownerValidationSalt, + 0, + ownerValidationSalt.length, + ); + List.copyRange( + mixedOwnerPassword, + ownerPassword.length + ownerValidationSalt.length, + _userPasswordOut!, + 0, + userKeyLength, + ); hash = _acrobatXComputeHash( - mixedOwnerPassword, ownerPassword, _userPasswordOut); + mixedOwnerPassword, + ownerPassword, + _userPasswordOut, + ); _acrobatXOwnerFileEncryptionKey(password); oEqual = _compareByteArrays(hash, hashProvided); if (oEqual == true) { @@ -988,23 +1139,49 @@ class PdfEncryptor { final List userPasswordOut = List.filled(48, 0, growable: true); List.copyRange(userPasswordOut, 0, _userPasswordOut!, 0, 48); List.copyRange( - hashProvided, 0, _ownerPasswordOut!, 0, hashProvided.length); + 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!, - ownerValidationSalt.length, ownerKeySalt.length); + List.copyRange( + ownerValidationSalt, + 0, + _ownerRandomBytes!, + 0, + ownerValidationSalt.length, + ); + List.copyRange( + ownerKeySalt, + 0, + _ownerRandomBytes!, + ownerValidationSalt.length, + ownerKeySalt.length, + ); hash = List.filled( - ownerPassword.length + - ownerValidationSalt.length + - userPasswordOut.length, - 0, - growable: true); + ownerPassword.length + + ownerValidationSalt.length + + userPasswordOut.length, + 0, + growable: true, + ); List.copyRange(hash, 0, ownerPassword, 0, ownerPassword.length); - List.copyRange(hash, ownerPassword.length, ownerValidationSalt, 0, - ownerValidationSalt.length); - List.copyRange(hash, ownerPassword.length + ownerValidationSalt.length, - userPasswordOut, 0, userPasswordOut.length); + List.copyRange( + hash, + ownerPassword.length, + ownerValidationSalt, + 0, + ownerValidationSalt.length, + ); + List.copyRange( + hash, + ownerPassword.length + ownerValidationSalt.length, + userPasswordOut, + 0, + userPasswordOut.length, + ); final List hashFound = sha256.convert(hash).bytes; bool bEqual = false; if (hashFound.length == hashProvided.length) { @@ -1036,8 +1213,11 @@ class PdfEncryptor { late List hashFound; List? forDecryption; if (_ownerRandomBytes != null) { - final List ownerValidationSalt = - List.filled(8, 0, growable: true); + 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); @@ -1045,35 +1225,59 @@ class PdfEncryptor { List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes!, 0, 8); List.copyRange(ownerKeySalt, 0, _ownerRandomBytes!, 8, 16); hash = List.filled( - ownerPassword.length + - ownerValidationSalt.length + - userPasswordOut.length, - 0, - growable: true); + ownerPassword.length + + ownerValidationSalt.length + + userPasswordOut.length, + 0, + growable: true, + ); List.copyRange(hash, 0, ownerPassword, 0, ownerPassword.length); List.copyRange( - hash, ownerPassword.length, ownerKeySalt, 0, ownerKeySalt.length); - List.copyRange(hash, ownerPassword.length + ownerValidationSalt.length, - userPasswordOut, 0, userPasswordOut.length); + hash, + ownerPassword.length, + ownerKeySalt, + 0, + ownerKeySalt.length, + ); + List.copyRange( + hash, + ownerPassword.length + ownerValidationSalt.length, + userPasswordOut, + 0, + userPasswordOut.length, + ); hashFound = sha256.convert(hash).bytes; forDecryption = _ownerEncryptionKeyOut; } else if (_userRandomBytes != null) { - final List userValidationSalt = - List.filled(8, 0, growable: true); + final List userValidationSalt = 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); - hash = List.filled(userPassword.length + userKeySalt.length, 0, - growable: true); + hash = List.filled( + userPassword.length + userKeySalt.length, + 0, + growable: true, + ); List.copyRange(hash, 0, userPassword, 0, userPassword.length); List.copyRange( - hash, userPassword.length, userKeySalt, 0, userKeySalt.length); + hash, + userPassword.length, + userKeySalt, + 0, + userKeySalt.length, + ); hashFound = sha256.convert(hash).bytes; forDecryption = _userEncryptionKeyOut; } - _fileEncryptionKey = AesCipherNoPadding(false, hashFound) - .processBlock(forDecryption, 0, forDecryption!.length); + _fileEncryptionKey = AesCipherNoPadding( + false, + KeyParameter(Uint8List.fromList(hashFound)), + ).process(Uint8List.fromList(forDecryption!)); } bool _compareByteArrays(List array1, List? array2, [int? size]) { @@ -1096,7 +1300,8 @@ class PdfEncryptor { result = array1 == array2; } else if (array1.length < size || array2.length < size) { throw ArgumentError.value( - 'Size of one of the arrays are less then requisted size.'); + 'Size of one of the arrays are less then requisted size.', + ); } else if (array1.length != array2.length) { result = false; } else { @@ -1112,8 +1317,11 @@ class PdfEncryptor { } void _acrobatXOwnerFileEncryptionKey(String password) { - final List ownerValidationSalt = - List.filled(8, 0, growable: true); + final List ownerValidationSalt = List.filled( + 8, + 0, + growable: true, + ); final List ownerPassword = utf8.encode(password); List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut!, 40, 48); int userKeyLength = 48; @@ -1121,22 +1329,35 @@ class PdfEncryptor { userKeyLength = _userPasswordOut!.length; } final List combinedPassword = List.filled( - ownerPassword.length + ownerValidationSalt.length + userKeyLength, 0, - growable: true); + ownerPassword.length + ownerValidationSalt.length + userKeyLength, + 0, + growable: true, + ); List.copyRange(combinedPassword, 0, ownerPassword, 0, ownerPassword.length); - List.copyRange(combinedPassword, ownerPassword.length, ownerValidationSalt, - 0, ownerValidationSalt.length); List.copyRange( - combinedPassword, - ownerPassword.length + ownerValidationSalt.length, - _userPasswordOut!, - 0, - userKeyLength); - final List hash = - _acrobatXComputeHash(combinedPassword, ownerPassword, _userPasswordOut); + combinedPassword, + ownerPassword.length, + ownerValidationSalt, + 0, + ownerValidationSalt.length, + ); + List.copyRange( + combinedPassword, + ownerPassword.length + ownerValidationSalt.length, + _userPasswordOut!, + 0, + userKeyLength, + ); + final List hash = _acrobatXComputeHash( + combinedPassword, + ownerPassword, + _userPasswordOut, + ); final List fileEncryptionKey = List.from(_ownerEncryptionKeyOut!); - _fileEncryptionKey = AesCipherNoPadding(false, hash) - .processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); + _fileEncryptionKey = AesCipherNoPadding( + false, + KeyParameter(Uint8List.fromList(hash)), + ).process(Uint8List.fromList(fileEncryptionKey)); } void _advanceXUserFileEncryptionKey(String password) { @@ -1144,22 +1365,34 @@ class PdfEncryptor { List.copyRange(userKeySalt, 0, _userPasswordOut!, 40, 48); final List userpassword = utf8.encode(password); final List combinedUserPassword = List.filled( - userpassword.length + userKeySalt.length, 0, - growable: true); + userpassword.length + userKeySalt.length, + 0, + growable: true, + ); List.copyRange( - combinedUserPassword, 0, userpassword, 0, userpassword.length); - List.copyRange(combinedUserPassword, userpassword.length, userKeySalt, 0, - userKeySalt.length); - final List hash = - _acrobatXComputeHash(combinedUserPassword, userpassword, null); + combinedUserPassword, + 0, + userpassword, + 0, + userpassword.length, + ); + List.copyRange( + combinedUserPassword, + userpassword.length, + userKeySalt, + 0, + userKeySalt.length, + ); + final List hash = _acrobatXComputeHash( + combinedUserPassword, + userpassword, + null, + ); final List fileEncryptionKey = List.from(_userEncryptionKeyOut!); - _fileEncryptionKey = AesCipherNoPadding(false, hash) - .processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); - } - - List _generateInitVector() { - final Random random = Random.secure(); - return List.generate(16, (int i) => random.nextInt(256)); + _fileEncryptionKey = AesCipherNoPadding( + false, + KeyParameter(Uint8List.fromList(hash)), + ).process(Uint8List.fromList(fileEncryptionKey)); } /// internal method @@ -1170,24 +1403,30 @@ class PdfEncryptor { _revision = 0; keyLength = 0; } - dictionary[PdfDictionaryProperties.filter] = - PdfName(PdfDictionaryProperties.standard); + dictionary[PdfDictionaryProperties.filter] = PdfName( + PdfDictionaryProperties.standard, + ); dictionary[PdfDictionaryProperties.p] = PdfNumber(_permissionValue!); - dictionary[PdfDictionaryProperties.u] = - PdfString.fromBytes(userPasswordOut); - dictionary[PdfDictionaryProperties.o] = - PdfString.fromBytes(ownerPasswordOut); + dictionary[PdfDictionaryProperties.u] = PdfString.fromBytes( + userPasswordOut, + ); + dictionary[PdfDictionaryProperties.o] = PdfString.fromBytes( + ownerPasswordOut, + ); if (dictionary.containsKey(PdfDictionaryProperties.length)) { keyLength = 0; } - dictionary[PdfDictionaryProperties.length] = - PdfNumber(_getKeyLength()! * 8); + dictionary[PdfDictionaryProperties.length] = PdfNumber( + _getKeyLength()! * 8, + ); const bool isAes4Dict = false; if (encryptAttachmentOnly! && (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.'); + 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 || @@ -1202,27 +1441,34 @@ class PdfEncryptor { dictionary[PdfDictionaryProperties.r] = PdfNumber(5); } if (encryptAttachmentOnly!) { - dictionary[PdfDictionaryProperties.stmF] = - PdfName(PdfDictionaryProperties.identity); - dictionary[PdfDictionaryProperties.strF] = - PdfName(PdfDictionaryProperties.identity); - dictionary[PdfDictionaryProperties.eff] = - PdfName(PdfDictionaryProperties.stdCF); - dictionary[PdfDictionaryProperties.encryptMetadata] = - PdfBoolean(encryptOnlyMetadata); + dictionary[PdfDictionaryProperties.stmF] = PdfName( + PdfDictionaryProperties.identity, + ); + dictionary[PdfDictionaryProperties.strF] = PdfName( + PdfDictionaryProperties.identity, + ); + dictionary[PdfDictionaryProperties.eff] = PdfName( + PdfDictionaryProperties.stdCF, + ); + dictionary[PdfDictionaryProperties.encryptMetadata] = PdfBoolean( + encryptOnlyMetadata, + ); } else { - dictionary[PdfDictionaryProperties.stmF] = - PdfName(PdfDictionaryProperties.stdCF); - dictionary[PdfDictionaryProperties.strF] = - PdfName(PdfDictionaryProperties.stdCF); + dictionary[PdfDictionaryProperties.stmF] = PdfName( + PdfDictionaryProperties.stdCF, + ); + dictionary[PdfDictionaryProperties.strF] = PdfName( + PdfDictionaryProperties.stdCF, + ); if (dictionary.containsKey(PdfDictionaryProperties.eff)) { dictionary.remove(PdfDictionaryProperties.eff); } } if (!encryptOnlyMetadata!) { if (!dictionary.containsKey(PdfDictionaryProperties.encryptMetadata)) { - dictionary[PdfDictionaryProperties.encryptMetadata] = - PdfBoolean(encryptOnlyMetadata); + dictionary[PdfDictionaryProperties.encryptMetadata] = PdfBoolean( + encryptOnlyMetadata, + ); } } else if (!encryptOnlyAttachment) { if (dictionary.containsKey(PdfDictionaryProperties.encryptMetadata)) { @@ -1232,22 +1478,137 @@ class PdfEncryptor { dictionary[PdfDictionaryProperties.cf] = _getCryptFilterDictionary(); if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { - dictionary[PdfDictionaryProperties.ue] = - PdfString.fromBytes(_userEncryptionKeyOut); - dictionary[PdfDictionaryProperties.oe] = - PdfString.fromBytes(_ownerEncryptionKeyOut); - dictionary[PdfDictionaryProperties.perms] = - PdfString.fromBytes(_permissionFlag); + dictionary[PdfDictionaryProperties.ue] = PdfString.fromBytes( + _userEncryptionKeyOut, + ); + dictionary[PdfDictionaryProperties.oe] = PdfString.fromBytes( + _ownerEncryptionKeyOut, + ); + dictionary[PdfDictionaryProperties.perms] = PdfString.fromBytes( + _permissionFlag, + ); + } + } else { + dictionary[PdfDictionaryProperties.r] = PdfNumber( + (_revisionNumberOut! > 0 && !isAes4Dict) + ? _revisionNumberOut! + : (_getKeySize() + 2), + ); + dictionary[PdfDictionaryProperties.v] = PdfNumber( + (_versionNumberOut! > 0 && !isAes4Dict) + ? _versionNumberOut! + : (_getKeySize() + 1), + ); + } + dictionary.archive = false; + return dictionary; + } + + /// internal method + Future saveToDictionaryAsync(PdfDictionary dictionary) async { + if (changed!) { + _revisionNumberOut = 0; + _versionNumberOut = 0; + _revision = 0; + keyLength = 0; + } + dictionary[PdfDictionaryProperties.filter] = PdfName( + PdfDictionaryProperties.standard, + ); + dictionary[PdfDictionaryProperties.p] = PdfNumber(_permissionValue!); + dictionary[PdfDictionaryProperties.u] = PdfString.fromBytes( + userPasswordOut, + ); + dictionary[PdfDictionaryProperties.o] = PdfString.fromBytes( + ownerPasswordOut, + ); + if (dictionary.containsKey(PdfDictionaryProperties.length)) { + keyLength = 0; + } + dictionary[PdfDictionaryProperties.length] = PdfNumber( + _getKeyLength()! * 8, + ); + const bool isAes4Dict = false; + if (encryptAttachmentOnly! && + (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[PdfDictionaryProperties.r] = PdfNumber(_getKeySize() + 3); + dictionary[PdfDictionaryProperties.v] = PdfNumber(_getKeySize() + 3); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + dictionary[PdfDictionaryProperties.v] = PdfNumber(5); + dictionary[PdfDictionaryProperties.r] = PdfNumber(6); + } else if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit) { + dictionary[PdfDictionaryProperties.v] = PdfNumber(5); + dictionary[PdfDictionaryProperties.r] = PdfNumber(5); + } + if (encryptAttachmentOnly!) { + dictionary[PdfDictionaryProperties.stmF] = PdfName( + PdfDictionaryProperties.identity, + ); + dictionary[PdfDictionaryProperties.strF] = PdfName( + PdfDictionaryProperties.identity, + ); + dictionary[PdfDictionaryProperties.eff] = PdfName( + PdfDictionaryProperties.stdCF, + ); + dictionary[PdfDictionaryProperties.encryptMetadata] = PdfBoolean( + encryptOnlyMetadata, + ); + } else { + dictionary[PdfDictionaryProperties.stmF] = PdfName( + PdfDictionaryProperties.stdCF, + ); + dictionary[PdfDictionaryProperties.strF] = PdfName( + PdfDictionaryProperties.stdCF, + ); + if (dictionary.containsKey(PdfDictionaryProperties.eff)) { + dictionary.remove(PdfDictionaryProperties.eff); + } + } + if (!encryptOnlyMetadata!) { + if (!dictionary.containsKey(PdfDictionaryProperties.encryptMetadata)) { + dictionary[PdfDictionaryProperties.encryptMetadata] = PdfBoolean( + encryptOnlyMetadata, + ); + } + } else if (!encryptOnlyAttachment) { + if (dictionary.containsKey(PdfDictionaryProperties.encryptMetadata)) { + dictionary.remove(PdfDictionaryProperties.encryptMetadata); + } + } + dictionary[PdfDictionaryProperties.cf] = + await _getCryptFilterDictionaryAsync(); + if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { + dictionary[PdfDictionaryProperties.ue] = PdfString.fromBytes( + _userEncryptionKeyOut, + ); + dictionary[PdfDictionaryProperties.oe] = PdfString.fromBytes( + _ownerEncryptionKeyOut, + ); + dictionary[PdfDictionaryProperties.perms] = PdfString.fromBytes( + _permissionFlag, + ); } } else { dictionary[PdfDictionaryProperties.r] = PdfNumber( - (_revisionNumberOut! > 0 && !isAes4Dict) - ? _revisionNumberOut! - : (_getKeySize() + 2)); + (_revisionNumberOut! > 0 && !isAes4Dict) + ? _revisionNumberOut! + : (_getKeySize() + 2), + ); dictionary[PdfDictionaryProperties.v] = PdfNumber( - (_versionNumberOut! > 0 && !isAes4Dict) - ? _versionNumberOut! - : (_getKeySize() + 1)); + (_versionNumberOut! > 0 && !isAes4Dict) + ? _versionNumberOut! + : (_getKeySize() + 1), + ); } dictionary.archive = false; return dictionary; @@ -1257,30 +1618,83 @@ class PdfEncryptor { final PdfDictionary standardCryptFilter = PdfDictionary(); if (!standardCryptFilter.containsKey(PdfDictionaryProperties.cfm)) { if (encryptAttachmentOnly!) { - standardCryptFilter[PdfDictionaryProperties.cfm] = - PdfName(PdfDictionaryProperties.aesv2); - standardCryptFilter[PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.cryptFilter); + standardCryptFilter[PdfDictionaryProperties.cfm] = PdfName( + PdfDictionaryProperties.aesv2, + ); + standardCryptFilter[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.cryptFilter, + ); + } else { + standardCryptFilter[PdfDictionaryProperties.cfm] = PdfName( + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) + ? PdfDictionaryProperties.aesv3 + : (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? 'V2' + : PdfDictionaryProperties.aesv2, + ); + } + } + if (!standardCryptFilter.containsKey(PdfDictionaryProperties.authEvent)) { + standardCryptFilter[PdfDictionaryProperties.authEvent] = PdfName( + encryptAttachmentOnly! + ? PdfDictionaryProperties.efOpen + : PdfDictionaryProperties.docOpen, + ); + } + standardCryptFilter[PdfDictionaryProperties.length] = PdfNumber( + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) + ? _key256! + : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? _key128! + : 128), + ); + final PdfDictionary cryptFilterDictionary = PdfDictionary(); + cryptFilterDictionary[PdfDictionaryProperties.stdCF] = standardCryptFilter; + return cryptFilterDictionary; + } + + Future _getCryptFilterDictionaryAsync() async { + final PdfDictionary standardCryptFilter = PdfDictionary(); + if (!standardCryptFilter.containsKey(PdfDictionaryProperties.cfm)) { + if (encryptAttachmentOnly!) { + standardCryptFilter[PdfDictionaryProperties.cfm] = PdfName( + PdfDictionaryProperties.aesv2, + ); + standardCryptFilter[PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.cryptFilter, + ); } else { standardCryptFilter[PdfDictionaryProperties.cfm] = PdfName( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit - ? PdfDictionaryProperties.aesv3 - : PdfDictionaryProperties.aesv2); + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) + ? PdfDictionaryProperties.aesv3 + : (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? 'V2' + : PdfDictionaryProperties.aesv2, + ); } } if (!standardCryptFilter.containsKey(PdfDictionaryProperties.authEvent)) { standardCryptFilter[PdfDictionaryProperties.authEvent] = PdfName( - encryptAttachmentOnly! - ? PdfDictionaryProperties.efOpen - : PdfDictionaryProperties.docOpen); + encryptAttachmentOnly! + ? PdfDictionaryProperties.efOpen + : PdfDictionaryProperties.docOpen, + ); } standardCryptFilter[PdfDictionaryProperties.length] = PdfNumber( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit - ? _key256! - : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || - encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) - ? _key128! - : 128)); + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) + ? _key256! + : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? _key128! + : 128), + ); final PdfDictionary cryptFilterDictionary = PdfDictionary(); cryptFilterDictionary[PdfDictionaryProperties.stdCF] = standardCryptFilter; return cryptFilterDictionary; @@ -1309,6 +1723,7 @@ class PdfEncryptor { case PdfEncryptionAlgorithm.aesx256BitRevision6: result = 3; break; + // ignore: no_default_cases default: result = 1; break; @@ -1318,7 +1733,10 @@ class PdfEncryptor { /// internal method List encryptData( - int? currentObjectNumber, List data, bool isEncryption) { + int? currentObjectNumber, + List data, + bool isEncryption, + ) { if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { return isEncryption @@ -1330,8 +1748,11 @@ class PdfEncryptor { int keyLen = 0; List newKey; if (_encryptionKey!.length == 5) { - newKey = List.filled(_encryptionKey!.length + _newKeyOffset!, 0, - growable: true); + newKey = List.filled( + _encryptionKey!.length + _newKeyOffset!, + 0, + growable: true, + ); for (int i = 0; i < _encryptionKey!.length; ++i) { newKey[i] = _encryptionKey![i]; } @@ -1345,15 +1766,16 @@ class PdfEncryptor { newKey = _prepareKeyForEncryption(newKey); } else { newKey = List.filled( - _encryptionKey!.length + - ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || - encryptionAlgorithm == - PdfEncryptionAlgorithm.aesx256BitRevision6 || - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) - ? 9 - : 5), - 0, - growable: true); + _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); @@ -1380,52 +1802,64 @@ class PdfEncryptor { } List _aesEncrypt(List data, List key) { - final List result = []; - final List iv = _generateInitVector(); - final AesEncryptor encryptor = AesEncryptor(key, iv, true); - int lengthNeeded = encryptor.getBlockSize(data.length); - final List output = List.filled(lengthNeeded, 0, growable: true); - encryptor.processBytes(data, 0, data.length, output, 0); - result.addAll(output); - lengthNeeded = encryptor.calculateOutputSize(); - final List tempOutput = - List.filled(lengthNeeded, 0, growable: true); - encryptor.finalize(tempOutput); - result.addAll(tempOutput); - return result; + if (key.isEmpty) { + return data; + } + final iv = Uint8List(16); + final Random random = Random.secure(); + for (int i = 0; i < iv.length; i++) { + iv[i] = random.nextInt(256); + } + + final cipher = PaddedCipherMode( + Pkcs7Padding(), + CipherBlockChainingMode(AesEngine()), + ); + + final params = + BlockCipherPaddedParameters( + InvalidParameter(KeyParameter(Uint8List.fromList(key)), iv), + null, + ); + cipher.initialize(true, params); + + try { + final encrypted = cipher.process(Uint8List.fromList(data)); + final results = Uint8List(iv.length + encrypted.length); + results.setRange(0, iv.length, iv); + results.setRange(iv.length, results.length, encrypted); + + return results; + } catch (e) { + return []; + } } List _aesDecrypt(List data, List? key) { - final List result = []; - final List iv = List.filled(16, 0, growable: true); - int length = data.length; - int ivPtr = 0; - final int minBlock = min(iv.length - ivPtr, length); - List.copyRange(iv, ivPtr, data, 0, minBlock); - length -= minBlock; - ivPtr += minBlock; - if (ivPtr == iv.length && length > 0) { - final AesEncryptor decryptor = AesEncryptor(key!, iv, false); - int lengthNeeded = decryptor.getBlockSize(length); - final List output = - List.filled(lengthNeeded, 0, growable: true); - decryptor.processBytes(data, ivPtr, length, output, 0); - result.addAll(output); - lengthNeeded = decryptor.calculateOutputSize(); - final List tempOutput = - List.filled(lengthNeeded, 0, growable: true); - length = decryptor.finalize(tempOutput); - if (tempOutput.length != length) { - final List temp = List.filled(length, 0, growable: true); - List.copyRange(temp, 0, tempOutput, 0, length); - result.addAll(temp); - } else { - result.addAll(tempOutput); - } - } else { + if (key == null || key.isEmpty || data.length < 16) { return data; } - return result; + + final ivBytes = Uint8List.fromList(data.take(16).toList()); + final encryptedData = Uint8List.fromList(data.skip(16).toList()); + + final cipher = PaddedCipherMode( + Pkcs7Padding(), + CipherBlockChainingMode(AesEngine()), + ); + + final params = + BlockCipherPaddedParameters( + InvalidParameter(KeyParameter(Uint8List.fromList(key)), ivBytes), + null, + ); + + cipher.initialize(false, params); + try { + return cipher.process(encryptedData); + } catch (e) { + return []; + } } /// internal method @@ -1446,10 +1880,15 @@ class PdfEncryptor { final int keyLen = originalKey.length; final List newKey = md5.convert(originalKey).bytes; if (keyLen > _randomBytesAmount!) { - final int newKeyLength = - min(_getKeyLength()! + _newKeyOffset!, _randomBytesAmount!); - final List result = - List.filled(newKeyLength, 0, growable: true); + final int newKeyLength = min( + _getKeyLength()! + _newKeyOffset!, + _randomBytesAmount!, + ); + final List result = List.filled( + newKeyLength, + 0, + growable: true, + ); List.copyRange(result, 0, newKey, 0, newKeyLength); return result; } else { @@ -1459,52 +1898,56 @@ class PdfEncryptor { /// internal method PdfEncryptor clone() { - final PdfEncryptor encryptor = PdfEncryptor() - .._stringLength = _cloneInt(_stringLength) - .._revisionNumber40Bit = _cloneInt(_revisionNumber40Bit) - .._revisionNumber128Bit = _cloneInt(_revisionNumber128Bit) - .._ownerLoopNum2 = _cloneInt(_ownerLoopNum2) - .._ownerLoopNum = _cloneInt(_ownerLoopNum) - ..paddingBytes = _cloneList(paddingBytes) - .._bytesAmount = _cloneInt(_bytesAmount) - .._permissionSet = _cloneInt(_permissionSet) - .._permissionCleared = _cloneInt(_permissionCleared) - .._permissionRevisionTwoMask = _cloneInt(_permissionRevisionTwoMask) - .._revisionNumberOut = _cloneInt(_revisionNumberOut) - .._versionNumberOut = _cloneInt(_versionNumberOut) - .._permissionValue = _cloneInt(_permissionValue) - .._randomBytes = _cloneList(_randomBytes) - .._key40 = _cloneInt(_key40) - .._key128 = _cloneInt(_key128) - .._key256 = _cloneInt(_key256) - .._randomBytesAmount = _cloneInt(_randomBytesAmount) - .._newKeyOffset = _cloneInt(_newKeyOffset) - ..isEncrypt = _cloneBool(isEncrypt) - ..changed = _cloneBool(changed) - ..hasComputedPasswordValues = _cloneBool(hasComputedPasswordValues) - .._revision = _cloneInt(_revision) - .._ownerPasswordOut = _cloneList(_ownerPasswordOut) - .._userPasswordOut = _cloneList(_userPasswordOut) - .._encryptionKey = _cloneList(_encryptionKey) - ..keyLength = _cloneInt(keyLength) - ..customArray = _cloneList(customArray) - .._permissionFlagValues = _cloneList(_permissionFlagValues) - .._fileEncryptionKey = _cloneList(_fileEncryptionKey) - .._userEncryptionKeyOut = _cloneList(_userEncryptionKeyOut) - .._ownerEncryptionKeyOut = _cloneList(_ownerEncryptionKeyOut) - .._permissionFlag = _cloneList(_permissionFlag) - .._userRandomBytes = _cloneList(_userRandomBytes) - .._ownerRandomBytes = _cloneList(_ownerRandomBytes) - ..encryptOnlyMetadata = _cloneBool(encryptOnlyMetadata) - ..encryptAttachmentOnly = _cloneBool(encryptAttachmentOnly) - ..encryptionAlgorithm = encryptionAlgorithm - .._userPassword = _userPassword - .._ownerPassword = _ownerPassword - ..encryptionOptions = encryptionOptions; - encryptor._permissions = _permissions != null - ? List.generate( - _permissions!.length, (int i) => _permissions![i]) - : null; + final PdfEncryptor encryptor = + PdfEncryptor() + .._stringLength = _cloneInt(_stringLength) + .._revisionNumber40Bit = _cloneInt(_revisionNumber40Bit) + .._revisionNumber128Bit = _cloneInt(_revisionNumber128Bit) + .._ownerLoopNum2 = _cloneInt(_ownerLoopNum2) + .._ownerLoopNum = _cloneInt(_ownerLoopNum) + ..paddingBytes = _cloneList(paddingBytes) + .._bytesAmount = _cloneInt(_bytesAmount) + .._permissionSet = _cloneInt(_permissionSet) + .._permissionCleared = _cloneInt(_permissionCleared) + .._permissionRevisionTwoMask = _cloneInt(_permissionRevisionTwoMask) + .._revisionNumberOut = _cloneInt(_revisionNumberOut) + .._versionNumberOut = _cloneInt(_versionNumberOut) + .._permissionValue = _cloneInt(_permissionValue) + .._randomBytes = _cloneList(_randomBytes) + .._key40 = _cloneInt(_key40) + .._key128 = _cloneInt(_key128) + .._key256 = _cloneInt(_key256) + .._randomBytesAmount = _cloneInt(_randomBytesAmount) + .._newKeyOffset = _cloneInt(_newKeyOffset) + ..isEncrypt = _cloneBool(isEncrypt) + ..changed = _cloneBool(changed) + ..hasComputedPasswordValues = _cloneBool(hasComputedPasswordValues) + .._revision = _cloneInt(_revision) + .._ownerPasswordOut = _cloneList(_ownerPasswordOut) + .._userPasswordOut = _cloneList(_userPasswordOut) + .._encryptionKey = _cloneList(_encryptionKey) + ..keyLength = _cloneInt(keyLength) + ..customArray = _cloneList(customArray) + .._permissionFlagValues = _cloneList(_permissionFlagValues) + .._fileEncryptionKey = _cloneList(_fileEncryptionKey) + .._userEncryptionKeyOut = _cloneList(_userEncryptionKeyOut) + .._ownerEncryptionKeyOut = _cloneList(_ownerEncryptionKeyOut) + .._permissionFlag = _cloneList(_permissionFlag) + .._userRandomBytes = _cloneList(_userRandomBytes) + .._ownerRandomBytes = _cloneList(_ownerRandomBytes) + ..encryptOnlyMetadata = _cloneBool(encryptOnlyMetadata) + ..encryptAttachmentOnly = _cloneBool(encryptAttachmentOnly) + ..encryptionAlgorithm = encryptionAlgorithm + .._userPassword = _userPassword + .._ownerPassword = _ownerPassword + ..encryptionOptions = encryptionOptions; + encryptor._permissions = + _permissions != null + ? List.generate( + _permissions!.length, + (int i) => _permissions![i], + ) + : null; return encryptor; } @@ -1530,8 +1973,7 @@ class PdfEncryptor { List? _cloneList(List? value) { if (value != null) { - return List.generate(value.length, (int i) => value[i], - growable: true); + return List.generate(value.length, (int i) => value[i]); } else { return null; } 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 4d862301d..cba5cf2a2 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 @@ -13,7 +13,7 @@ import 'pdf_encryptor.dart'; /// security.userPassword = 'password'; /// security.ownerPassword = 'syncfusion'; /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -37,7 +37,7 @@ class PdfSecurity { /// //Get encryption algorithm /// PdfEncryptionAlgorithm algorithm = document.security.algorithm; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -57,7 +57,7 @@ class PdfSecurity { /// security.userPassword = 'password'; /// security.ownerPassword = 'syncfusion'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -80,7 +80,7 @@ class PdfSecurity { /// //Get the owner password. /// String ownerPassword = document.security.ownerPassword; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -103,14 +103,15 @@ class PdfSecurity { /// security.userPassword = 'password'; /// security.ownerPassword = 'syncfusion'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` set ownerPassword(String value) { if (_helper.conformance) { throw ArgumentError( - 'Document encryption is not allowed with Conformance documents.'); + 'Document encryption is not allowed with Conformance documents.', + ); } _helper.encryptor.ownerPassword = value; _helper.encryptor.encrypt = true; @@ -127,7 +128,7 @@ class PdfSecurity { /// //Get the user password. /// String userPassword = document.security.userPassword; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -149,14 +150,15 @@ class PdfSecurity { /// security.userPassword = 'password'; /// security.ownerPassword = 'syncfusion'; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` set userPassword(String value) { if (_helper.conformance) { throw ArgumentError( - 'Document encryption is not allowed with Conformance documents.'); + 'Document encryption is not allowed with Conformance documents.', + ); } _helper.encryptor.userPassword = value; _helper.encryptor.encrypt = true; @@ -178,13 +180,15 @@ class PdfSecurity { /// //Remove permissions /// permissions.remove([PdfPermissionsFlags.editContent, PdfPermissionsFlags.copyContent]); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` PdfPermissions get permissions { - _permissions ??= - PdfPermissions._(_helper.encryptor, _helper.encryptor.permissions); + _permissions ??= PdfPermissions._( + _helper.encryptor, + _helper.encryptor.permissions, + ); return _permissions!; } @@ -207,7 +211,7 @@ class PdfSecurity { /// 'input.txt', File('input.txt').readAsBytesSync(), /// description: 'Text File', mimeType: 'application/txt')); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -216,7 +220,8 @@ class PdfSecurity { set encryptionOptions(PdfEncryptionOptions value) { if (_helper.conformance) { throw ArgumentError( - 'Document encryption is not allowed with Conformance documents.'); + 'Document encryption is not allowed with Conformance documents.', + ); } if (_helper.encryptor.encryptionOptions != value) { _helper.encryptor.encryptionOptions = value; @@ -304,14 +309,16 @@ class PdfSecurityHelper { /// //Add permissions. /// permissions.add([PdfPermissionsFlags.print]); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` class PdfPermissions { //constructor PdfPermissions._( - PdfEncryptor encryptor, List permissions) { + PdfEncryptor encryptor, + List permissions, + ) { _encryptor = encryptor; _permissions = permissions; } @@ -334,7 +341,7 @@ class PdfPermissions { /// //Gets the permission option at index 0. /// PdfPermissionsFlags permission = permissions[0]; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -366,7 +373,7 @@ class PdfPermissions { /// } /// }); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -392,7 +399,7 @@ class PdfPermissions { /// //Gets the permissions count. /// int count = permissions.count; /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -408,7 +415,7 @@ class PdfPermissions { /// //Add permission. /// security.permissions.add(PdfPermissionsFlags.editContent); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -433,7 +440,7 @@ class PdfPermissions { /// PdfPermissionsFlags.editContent, /// PdfPermissionsFlags.copyContent]); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -467,7 +474,7 @@ class PdfPermissions { /// //Remove permission. /// permissions.remove(PdfPermissionsFlags.editContent); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -492,7 +499,7 @@ class PdfPermissions { /// //Remove all permissions and set default. /// permissions.clear(); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 d05cf3f5f..4ed00fb69 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 @@ -11,7 +11,7 @@ enum PdfGridImagePosition { stretch, /// The image is rendered by tile mode. - tile + tile, } /// internal enumerator @@ -31,7 +31,7 @@ enum PdfGridStretchOption { uniformToFill, /// The content preserves its original size. - none + none, } /// Describe the possible values of [PdfHorizontalOverflowType]. @@ -42,7 +42,7 @@ enum PdfHorizontalOverflowType { nextPage, /// Draws the overflowing grid as last page - lastPage + lastPage, } /// Specifies the values of the border overlap style. @@ -369,5 +369,5 @@ enum PdfGridBuiltInStyle { tableGridLight, /// Specifies the grid to render Table Grid style. - tableGrid + tableGrid, } 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 664668033..4458a229d 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 @@ -25,7 +25,7 @@ import '../styles/style.dart'; /// internal class class PdfGridLayouter extends ElementLayouter { /// internal constructor - PdfGridLayouter(PdfGrid grid) : super(grid) { + PdfGridLayouter(PdfGrid super.grid) { _initialize(); } @@ -40,6 +40,7 @@ class PdfGridLayouter extends ElementLayouter { List>? _columnRanges; late int _cellEndIndex; late int _cellStartIndex; + late double userHeight; /// internal field static int repeatRowIndex = -1; @@ -87,13 +88,14 @@ class PdfGridLayouter extends ElementLayouter { _currentGraphics = graphics; if (PdfGraphicsHelper.getHelper(_currentGraphics!).layer != null && PdfGraphicsHelper.getHelper(_currentGraphics!).page != null) { - final int index = PdfSectionHelper.getHelper(PdfPageHelper.getHelper( - PdfGraphicsHelper.getHelper(_currentGraphics!).page!) - .section!) - .indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(index)) { + final int index = PdfSectionHelper.getHelper( + PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).section!, + ).indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(index)) { PdfGridHelper.getHelper(_grid!).listOfNavigatePages.add(index); } } @@ -148,13 +150,19 @@ class PdfGridLayouter extends ElementLayouter { _cellEndIndex = range[1]; if (_currentPage != null) { final Map pageLayoutResult = _raiseBeforePageLayout( - _currentPage, _currentBounds.rect, _currentRowIndex); - _currentBounds = - PdfRectangle.fromRect(pageLayoutResult['currentBounds']); + _currentPage, + _currentBounds.rect, + _currentRowIndex, + ); + _currentBounds = PdfRectangle.fromRect( + pageLayoutResult['currentBounds'], + ); _currentRowIndex = pageLayoutResult['currentRow'] as int; if (pageLayoutResult['cancel'] as bool) { - result = - PdfLayoutResultHelper.load(_currentPage!, _currentBounds.rect); + result = PdfLayoutResultHelper.load( + _currentPage!, + _currentBounds.rect, + ); break; } } @@ -164,7 +172,8 @@ class PdfGridLayouter extends ElementLayouter { if (PdfGridHelper.getHelper(_grid!).gridBuiltinStyle != PdfGridBuiltInStyle.tableGrid) { PdfGridHelper.getHelper(_grid!).applyBuiltinStyles( - PdfGridHelper.getHelper(_grid!).gridBuiltinStyle); + PdfGridHelper.getHelper(_grid!).gridBuiltinStyle, + ); } } for (int rowIndex = 0; rowIndex < _grid!.headers.count; rowIndex++) { @@ -210,27 +219,33 @@ class PdfGridLayouter extends ElementLayouter { _cellEndIndex = _cellStartIndex = PdfGridHelper.getHelper(_grid!).parentCellIndex; _parentCellIndexList = []; - _parentCellIndexList - .add(PdfGridHelper.getHelper(_grid!).parentCellIndex); + _parentCellIndexList.add( + PdfGridHelper.getHelper(_grid!).parentCellIndex, + ); PdfGridCellHelper.getHelper(PdfGridHelper.getHelper(_grid!).parentCell!) .present = true; - PdfGrid parentGrid = PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid; + PdfGrid parentGrid = + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid; while (PdfGridHelper.getHelper(parentGrid).parentCell != null) { - _parentCellIndexList - .add(PdfGridHelper.getHelper(parentGrid).parentCellIndex); + _parentCellIndexList.add( + PdfGridHelper.getHelper(parentGrid).parentCellIndex, + ); _cellEndIndex = PdfGridHelper.getHelper(parentGrid).parentCellIndex; _cellStartIndex = PdfGridHelper.getHelper(parentGrid).parentCellIndex; PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(parentGrid).parentCell!) - .present = true; - parentGrid = PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(parentGrid).parentCell!) - .row!) - .grid; + PdfGridHelper.getHelper(parentGrid).parentCell!, + ).present = + true; + parentGrid = + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(parentGrid).parentCell!, + ).row!, + ).grid; if (PdfGridHelper.getHelper(parentGrid).parentCell == null) { _parentCellIndexList.removeAt(_parentCellIndexList.length - 1); } @@ -238,20 +253,23 @@ class PdfGridLayouter extends ElementLayouter { PdfSection section = PdfPageHelper.getHelper(_currentPage!).section!; int index = PdfSectionHelper.getHelper(section).indexOf(_currentPage!); if ((!PdfGridHelper.getHelper(parentGrid).isDrawn) || - (!PdfGridHelper.getHelper(parentGrid) - .listOfNavigatePages - .contains(index))) { - section = PdfPageHelper.getHelper( - PdfGraphicsHelper.getHelper(_currentGraphics!).page!) - .section!; + (!PdfGridHelper.getHelper( + parentGrid, + ).listOfNavigatePages.contains(index))) { + section = + PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).section!; index = PdfSectionHelper.getHelper(section).indexOf(_currentPage!); PdfGridHelper.getHelper(parentGrid).isDrawn = true; for (int rowIndex = 0; rowIndex < parentGrid.rows.count; rowIndex++) { final PdfGridRow row = parentGrid.rows[rowIndex]; final PdfGridCell cell = row.cells[_cellStartIndex]; cell.value = ''; - final PdfPoint location = - PdfPoint(_currentBounds.x, _currentBounds.y); + final PdfPoint location = PdfPoint( + _currentBounds.x, + _currentBounds.y, + ); double width = parentGrid.columns[_cellStartIndex].width; if (width > _currentGraphics!.clientSize.width) { width = _currentGraphics!.clientSize.width - 2 * location.x; @@ -260,8 +278,11 @@ class PdfGridLayouter extends ElementLayouter { if (row.height > cell.height) { height = row.height; } - PdfGridCellHelper.getHelper(cell).draw(_currentGraphics, - PdfRectangle(location.x, location.y, width, height), false); + PdfGridCellHelper.getHelper(cell).draw( + _currentGraphics, + PdfRectangle(location.x, location.y, width, height), + false, + ); _currentBounds.y = _currentBounds.y + height; } _currentBounds.y = 0; @@ -279,43 +300,52 @@ class PdfGridLayouter extends ElementLayouter { startPage = _currentPage; repeatRowIndex = -1; if (flag && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid!) { + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid!) { startingHeight = originalHeight; flag = false; } - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid! && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell! - .rowSpan > + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell!.rowSpan > 1 && (startingHeight! + _childHeight).toInt() < (_currentBounds.y + row.height).toInt()) { if (_grid!.rows.count > i) { - final PdfGrid temp = PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row).grid) - .parentCell!) - .row!) - .grid; - for (int tempRowIndex = 0; - tempRowIndex < temp.rows.count; - tempRowIndex++) { + final PdfGrid temp = + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell!, + ).row!, + ).grid; + for ( + int tempRowIndex = 0; + tempRowIndex < temp.rows.count; + tempRowIndex++ + ) { final PdfGridRow tempRow = temp.rows[tempRowIndex]; if (tempRow.cells[PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row).grid) - .parentCellIndex] == - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell) { - final dynamic grid = tempRow - .cells[PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row).grid) - .parentCellIndex] - .value; + PdfGridRowHelper.getHelper(row).grid, + ).parentCellIndex] == + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell) { + final dynamic grid = + tempRow + .cells[PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCellIndex] + .value; if (grid is PdfGrid) { - PdfGridRowCollectionHelper.getRows(grid.rows) - .removeRange(0, i - 1); + PdfGridRowCollectionHelper.getRows( + grid.rows, + ).removeRange(0, i - 1); } } } @@ -332,22 +362,25 @@ class PdfGridLayouter extends ElementLayouter { 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 (PdfGridRowHelper.getHelper(grid.rows[m - 1]) - .rowBreakHeight > + if (PdfGridRowHelper.getHelper( + grid.rows[m - 1], + ).rowBreakHeight > 0) { isNestedRowBreak = true; break; } - if (PdfGridRowHelper.getHelper(grid.rows[m - 1]) - .isRowBreaksNextPage) { + if (PdfGridRowHelper.getHelper( + grid.rows[m - 1], + ).isRowBreaksNextPage) { PdfGridRowHelper.getHelper(row).rowBreakHeight = - PdfGridRowHelper.getHelper(grid.rows[m - 1]) - .rowBreakHeight; + PdfGridRowHelper.getHelper( + grid.rows[m - 1], + ).rowBreakHeight; break; } PdfGridRowHelper.getHelper(row).rowBreakHeight = PdfGridRowHelper.getHelper(row).rowBreakHeight + - grid.rows[m - 1].height; + grid.rows[m - 1].height; } } if (isNestedRowBreak) { @@ -361,33 +394,38 @@ class PdfGridLayouter extends ElementLayouter { PdfPage page = getNextPage(_currentPage!)!; final PdfSection section = PdfPageHelper.getHelper(_currentPage!).section!; - final int index = - PdfSectionHelper.getHelper(section).indexOf(page); - for (int k = 0; - k < - (PdfSectionHelper.getHelper(section) - .pageReferences! - .count - - 1) - - index; - k++) { + final int index = PdfSectionHelper.getHelper( + section, + ).indexOf(page); + for ( + int k = 0; + k < + (PdfSectionHelper.getHelper(section).pageReferences!.count - + 1) - + index; + k++ + ) { rect = PdfRectangle( - x, - 0, - PdfGridRowHelper.getHelper(row).grid.columns[j].width, - page.getClientSize().height); + x, + 0, + PdfGridRowHelper.getHelper(row).grid.columns[j].width, + page.getClientSize().height, + ); repeatRowIndex = -1; - PdfGridCellHelper.getHelper(row.cells[j]) - .draw(page.graphics, rect, false); + PdfGridCellHelper.getHelper( + row.cells[j], + ).draw(page.graphics, rect, false); page = getNextPage(page)!; } rect = PdfRectangle( - x, - 0, - PdfGridRowHelper.getHelper(row).grid.columns[j].width, - PdfGridRowHelper.getHelper(row).rowBreakHeight); - PdfGridCellHelper.getHelper(row.cells[j]) - .draw(page.graphics, rect, false); + x, + 0, + PdfGridRowHelper.getHelper(row).grid.columns[j].width, + PdfGridRowHelper.getHelper(row).rowBreakHeight, + ); + PdfGridCellHelper.getHelper( + row.cells[j], + ).draw(page.graphics, rect, false); } x += PdfGridRowHelper.getHelper(row).grid.columns[j].width; } @@ -402,39 +440,40 @@ class PdfGridLayouter extends ElementLayouter { while (!rowResult.isFinish && startPage != null) { final PdfLayoutResult tempResult = _getLayoutResult(); if (startPage != _currentPage) { - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid! && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell != + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell != null) { final PdfRectangle bounds = PdfRectangle( - format.paginateBounds.left, - format.paginateBounds.top, - param.bounds!.width, - tempResult.bounds.height); + format.paginateBounds.left, + format.paginateBounds.top, + param.bounds!.width, + tempResult.bounds.height, + ); bounds.x = bounds.x + param.bounds!.x; - bounds.y = bounds.y + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .top; + bounds.y = + bounds.y + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.top; if (bounds.height > _currentPageBounds.height) { bounds.height = _currentPageBounds.height - bounds.y; - bounds.height = bounds.height - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom; + bounds.height = + bounds.height - + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; } for (int c = 0; c < row.cells.count; c++) { final PdfGridCell cell = row.cells[c]; @@ -445,14 +484,17 @@ class PdfGridLayouter extends ElementLayouter { PdfGridRowHelper.getHelper(row).grid.columns[c].width; } } else { - cellWidth = max(cell.width, - PdfGridRowHelper.getHelper(row).grid.columns[c].width); + cellWidth = max( + cell.width, + PdfGridRowHelper.getHelper(row).grid.columns[c].width, + ); } - _currentGraphics = PdfGridCellHelper.getHelper(cell) - .drawCellBorders( - _currentGraphics!, - PdfRectangle( - bounds.x, bounds.y, cellWidth, bounds.height)); + _currentGraphics = PdfGridCellHelper.getHelper( + cell, + ).drawCellBorders( + _currentGraphics!, + PdfRectangle(bounds.x, bounds.y, cellWidth, bounds.height), + ); bounds.x = bounds.x + cellWidth; c += cell.columnSpan - 1; } @@ -468,8 +510,9 @@ class PdfGridLayouter extends ElementLayouter { _currentPage = _getNextPage(format); originalHeight = _currentBounds.y; final PdfPoint location = PdfPoint( - PdfGridHelper.getHelper(_grid!).defaultBorder.right.width / 2, - PdfGridHelper.getHelper(_grid!).defaultBorder.top.width / 2); + PdfGridHelper.getHelper(_grid!).defaultBorder.right.width / 2, + PdfGridHelper.getHelper(_grid!).defaultBorder.top.width / 2, + ); if (PdfRectangle.fromRect(format.paginateBounds) == PdfRectangle.empty && _startLocation == location) { @@ -477,75 +520,77 @@ class PdfGridLayouter extends ElementLayouter { _currentBounds.y = _currentBounds.y + _startLocation.y; } if (PdfGridHelper.getHelper(_grid!).isChildGrid! && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell != + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell != null) { - if (PdfGridStyleHelper.getPadding(PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style) != + if (PdfGridStyleHelper.getPadding( + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style, + ) != null) { if (PdfGridRowHelper.getHelper(row).rowBreakHeight + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top < - _currentBounds.height) { - _currentBounds.y = PdfGridRowHelper.getHelper( + PdfGridRowHelper.getHelper( PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top; + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top < + _currentBounds.height) { + _currentBounds.y = + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top; } } } - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell != + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell != null) { - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row).grid) - .parentCell!) - .row!) - .isRowBreaksNextPage = true; - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell!) - .row!) - .rowBreakHeight = + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell!, + ).row!, + ).isRowBreaksNextPage = + true; + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell!, + ).row!, + ).rowBreakHeight = PdfGridRowHelper.getHelper(row).rowBreakHeight + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top + - PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(PdfGridHelper.getHelper(_grid!).parentCell!).row!) - .grid - .style - .cellPadding - .bottom; + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; } if (PdfGridRowHelper.getHelper(row).noOfPageCount > 1) { final double temp = PdfGridRowHelper.getHelper(row).rowBreakHeight; - for (int j = 1; - j < PdfGridRowHelper.getHelper(row).noOfPageCount; - j++) { + for ( + int j = 1; + j < PdfGridRowHelper.getHelper(row).noOfPageCount; + j++ + ) { PdfGridRowHelper.getHelper(row).rowBreakHeight = 0; row.height = (PdfGridRowHelper.getHelper(row).noOfPageCount - 1) * - _currentPage!.getClientSize().height; + _currentPage!.getClientSize().height; _drawRow(row); _currentPage = _getNextPage(format); startPage = _currentPage; @@ -578,9 +623,9 @@ class PdfGridLayouter extends ElementLayouter { } } if (isWidthGreaterthanParent && - PdfGridCellHelper.getHelper(_grid! - .rows[j].cells[_rowBreakPageHeightCellIndex]) - .pageCount > + PdfGridCellHelper.getHelper( + _grid!.rows[j].cells[_rowBreakPageHeightCellIndex], + ).pageCount > 0) { isAddNextPage = true; } @@ -596,20 +641,23 @@ class PdfGridLayouter extends ElementLayouter { PdfSectionHelper.getHelper(section).isNewPageSection = false; _currentGraphics = _currentPage!.graphics; final Size clientSize = _currentPage!.getClientSize(); - _currentBounds = - PdfRectangle(0, 0, clientSize.width, clientSize.height); + _currentBounds = PdfRectangle( + 0, + 0, + clientSize.width, + clientSize.height, + ); final int pageindex = PdfSectionHelper.getHelper( - PdfPageHelper.getHelper( - PdfGraphicsHelper.getHelper(_currentGraphics!) - .page!) - .section!) - .indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(pageindex)) { - PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .add(pageindex); + PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).section!, + ).indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(pageindex)) { + PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.add(pageindex); } } else { if (endArgs.nextPage == null) { @@ -618,24 +666,30 @@ class PdfGridLayouter extends ElementLayouter { _currentPage = endArgs.nextPage; _currentGraphics = endArgs.nextPage!.graphics; _currentBounds = PdfRectangle( - 0, - 0, - _currentGraphics!.clientSize.width, - _currentGraphics!.clientSize.height); + 0, + 0, + _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height, + ); } } final bool isSameSection = PdfPageHelper.getHelper(_currentPage!).section == - PdfPageHelper.getHelper(param.page!).section; - _currentBounds.y = format.paginateBounds.top == 0 - ? PdfGridHelper.getHelper(_grid!).defaultBorder.top.width / 2 - : format.paginateBounds.top; + PdfPageHelper.getHelper(param.page!).section; + _currentBounds.y = + format.paginateBounds.top == 0 + ? PdfGridHelper.getHelper(_grid!).defaultBorder.top.width / 2 + : format.paginateBounds.top; if (_currentPage != null) { final Map pageLayoutResult = _raiseBeforePageLayout( - _currentPage, _currentBounds.rect, _currentRowIndex); - _currentBounds = - PdfRectangle.fromRect(pageLayoutResult['currentBounds']); + _currentPage, + _currentBounds.rect, + _currentRowIndex, + ); + _currentBounds = PdfRectangle.fromRect( + pageLayoutResult['currentBounds'], + ); _currentRowIndex = pageLayoutResult['currentRow'] as int; if (pageLayoutResult['cancel'] as bool) { break; @@ -659,9 +713,11 @@ class PdfGridLayouter extends ElementLayouter { _currentBounds.y = _currentBounds.y + _startLocation.x; } if (_grid!.repeatHeader) { - for (int headerIndex = 0; - headerIndex < _grid!.headers.count; - headerIndex++) { + for ( + int headerIndex = 0; + headerIndex < _grid!.headers.count; + headerIndex++ + ) { _drawRow(_grid!.headers[headerIndex]); } } @@ -675,8 +731,9 @@ class PdfGridLayouter extends ElementLayouter { _currentPage = PdfGridRowHelper.getHelper(row).gridResult!.page; _currentGraphics = _currentPage!.graphics; _startLocation = PdfPoint( - PdfGridRowHelper.getHelper(row).gridResult!.bounds.left, - PdfGridRowHelper.getHelper(row).gridResult!.bounds.top); + PdfGridRowHelper.getHelper(row).gridResult!.bounds.left, + PdfGridRowHelper.getHelper(row).gridResult!.bounds.top, + ); _currentBounds.y = PdfGridRowHelper.getHelper(row).gridResult!.bounds.bottom; if (startPage != _currentPage) { @@ -684,34 +741,45 @@ class PdfGridLayouter extends ElementLayouter { PdfPageHelper.getHelper(_currentPage!).section!; final int startIndex = PdfSectionHelper.getHelper(secion).indexOf(startPage!) + 1; - final int endIndex = - PdfSectionHelper.getHelper(secion).indexOf(_currentPage!); + final int endIndex = PdfSectionHelper.getHelper( + secion, + ).indexOf(_currentPage!); for (int page = startIndex; page < endIndex + 1; page++) { - PdfGraphics pageGraphics = PdfSectionHelper.getHelper(secion) - .getPageByIndex(page)! - .graphics; + PdfGraphics pageGraphics = + PdfSectionHelper.getHelper( + secion, + ).getPageByIndex(page)!.graphics; final PdfPoint location = PdfPoint( - format.paginateBounds.left, format.paginateBounds.top); + format.paginateBounds.left, + format.paginateBounds.top, + ); if (location == PdfPoint.empty && _currentBounds.x > location.x && - !PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid! && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell == + !PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell == null) { location.x = _currentBounds.x; } - double height = page == endIndex - ? (PdfGridRowHelper.getHelper(row).gridResult!.bounds.height - - param.bounds!.y) - : (_currentBounds.height - location.y); + double height = + page == endIndex + ? (PdfGridRowHelper.getHelper( + row, + ).gridResult!.bounds.height - + param.bounds!.y) + : (_currentBounds.height - location.y); if (height <= pageGraphics.clientSize.height) { height += param.bounds!.y; } - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid! && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .parentCell != + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).parentCell != null) { location.x = location.x + param.bounds!.x; } @@ -725,21 +793,27 @@ class PdfGridLayouter extends ElementLayouter { PdfGridRowHelper.getHelper(row).grid.columns[c].width; } } else { - cellWidth = PdfGridHelper.getHelper(_grid!).isWidthSet - ? min(cell.width, - PdfGridRowHelper.getHelper(row).grid.columns[c].width) - : max( - cell.width, - PdfGridRowHelper.getHelper(row) - .grid - .columns[c] - .width); + cellWidth = + PdfGridHelper.getHelper(_grid!).isWidthSet + ? min( + cell.width, + PdfGridRowHelper.getHelper( + row, + ).grid.columns[c].width, + ) + : max( + cell.width, + PdfGridRowHelper.getHelper( + row, + ).grid.columns[c].width, + ); } - pageGraphics = PdfGridCellHelper.getHelper(cell) - .drawCellBorders( - pageGraphics, - PdfRectangle( - location.x, location.y, cellWidth, height)); + pageGraphics = PdfGridCellHelper.getHelper( + cell, + ).drawCellBorders( + pageGraphics, + PdfRectangle(location.x, location.y, cellWidth, height), + ); location.x = location.x + cellWidth; c += cell.columnSpan - 1; } @@ -753,8 +827,10 @@ class PdfGridLayouter extends ElementLayouter { if (cellBounds.isNotEmpty) { maximumCellBoundsWidth = cellBounds[0]!; } - final List> largeNavigatePage = - List>.filled(1, List.filled(2, 0)); + 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) { @@ -765,10 +841,10 @@ class PdfGridLayouter extends ElementLayouter { isPdfGrid = true; if (largeNavigatePage[0][0]! < PdfGridHelper.getHelper(grid).listOfNavigatePages.length) { - largeNavigatePage[0][0] = PdfGridHelper.getHelper(grid) - .listOfNavigatePages - .length - .toDouble(); + largeNavigatePage[0][0] = + PdfGridHelper.getHelper( + grid, + ).listOfNavigatePages.length.toDouble(); largeNavigatePage[0][1] = cellBounds[c]; } else if ((largeNavigatePage[0][0] == PdfGridHelper.getHelper(grid).listOfNavigatePages.length) && @@ -796,12 +872,14 @@ class PdfGridLayouter extends ElementLayouter { if (largeNavigatePage[0][0]!.toInt() != 0) { final PdfSection section = PdfPageHelper.getHelper(_currentPage!).section!; - final int pageIndex = - PdfSectionHelper.getHelper(section).indexOf(_currentPage!); + final int pageIndex = PdfSectionHelper.getHelper( + section, + ).indexOf(_currentPage!); if (PdfSectionHelper.getHelper(section).count > pageIndex + largeNavigatePage[0][0]!.toInt()) { - _currentPage = PdfSectionHelper.getHelper(section) - .getPageByIndex(pageIndex + largeNavigatePage[0][0]!.toInt()); + _currentPage = PdfSectionHelper.getHelper( + section, + ).getPageByIndex(pageIndex + largeNavigatePage[0][0]!.toInt()); } else { _currentPage = PdfPage(); PdfSectionHelper.getHelper(section).isNewPageSection = true; @@ -810,26 +888,28 @@ class PdfGridLayouter extends ElementLayouter { } _currentGraphics = _currentPage!.graphics; _currentBounds = PdfRectangle( - 0, - 0, - _currentGraphics!.clientSize.width, - _currentGraphics!.clientSize.height); + 0, + 0, + _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height, + ); final int pageindex = PdfSectionHelper.getHelper( - PdfPageHelper.getHelper( - PdfGraphicsHelper.getHelper(_currentGraphics!).page!) - .section!) - .indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(pageindex)) { + PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).section!, + ).indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(pageindex)) { PdfGridHelper.getHelper(_grid!).listOfNavigatePages.add(pageindex); } } else { _currentPage = _getNextPage(format); } final PdfPoint location = PdfPoint( - PdfGridHelper.getHelper(_grid!).defaultBorder.right.width / 2, - PdfGridHelper.getHelper(_grid!).defaultBorder.top.width / 2); + PdfGridHelper.getHelper(_grid!).defaultBorder.right.width / 2, + PdfGridHelper.getHelper(_grid!).defaultBorder.top.width / 2, + ); if (PdfRectangle.fromRect(format.paginateBounds) == PdfRectangle.empty && _startLocation == location) { @@ -867,14 +947,18 @@ class PdfGridLayouter extends ElementLayouter { } final double height = row.height > cell.height ? row.height : cell.height; if (_isChildGrid!) { - PdfGridCellHelper.getHelper(cell).draw(_currentGraphics, - PdfRectangle(location.x, location.y, width, height), false); + PdfGridCellHelper.getHelper(cell).draw( + _currentGraphics, + PdfRectangle(location.x, location.y, width, height), + false, + ); } _currentBounds.y = _currentBounds.y + height; } for (int j = 0; j < grid.rows.count; j++) { - if (PdfGridCellHelper.getHelper(grid.rows[j].cells[_cellStartIndex]) - .present) { + if (PdfGridCellHelper.getHelper( + grid.rows[j].cells[_cellStartIndex], + ).present) { present = true; if (grid.rows[j].cells[_cellStartIndex].value is PdfGrid) { final PdfGrid? childGrid = @@ -886,7 +970,8 @@ class PdfGridLayouter extends ElementLayouter { _currentBounds.y = y!; } else { if (j == 0) { - _currentBounds.y = _currentBounds.y - + _currentBounds.y = + _currentBounds.y - PdfGridHelper.getHelper(grid).size.height; } else { int k = j; @@ -898,10 +983,12 @@ class PdfGridLayouter extends ElementLayouter { } PdfGridHelper.getHelper(childGrid!).isDrawn = true; grid.rows[j].cells[_cellStartIndex].value = childGrid; - _currentBounds.x = _currentBounds.x + + _currentBounds.x = + _currentBounds.x + grid.style.cellPadding.left + grid.style.cellPadding.right; - _currentBounds.y = _currentBounds.y + + _currentBounds.y = + _currentBounds.y + grid.style.cellPadding.top + grid.style.cellPadding.bottom; _currentBounds.width = _currentBounds.width - 2 * _currentBounds.x; @@ -914,15 +1001,18 @@ class PdfGridLayouter extends ElementLayouter { _parentCellIndexList.removeAt(_parentCellIndexList.length - 1); } _currentBounds.y = y!; - _currentBounds.x = _currentBounds.x + + _currentBounds.x = + _currentBounds.x + grid.style.cellPadding.left + grid.style.cellPadding.right; - _currentBounds.y = _currentBounds.y + + _currentBounds.y = + _currentBounds.y + grid.style.cellPadding.top + grid.style.cellPadding.bottom; final bool isPresent = _drawParentGridRow(childGrid!); if (!isPresent) { - _currentBounds.y = _currentBounds.y - + _currentBounds.y = + _currentBounds.y - PdfGridHelper.getHelper(childGrid).size.height; } _isChildGrid = false; @@ -938,8 +1028,11 @@ class PdfGridLayouter extends ElementLayouter { 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; @@ -948,8 +1041,9 @@ class PdfGridLayouter extends ElementLayouter { int currRowIndex = PdfGridRowCollectionHelper.indexOf(_grid!.rows, row); int maxSpan = PdfGridRowHelper.getHelper(row).maximumRowSpan; if (currRowIndex == -1) { - currRowIndex = PdfGridHeaderCollectionHelper.getHelper(_grid!.headers) - .indexOf(row); + currRowIndex = PdfGridHeaderCollectionHelper.getHelper( + _grid!.headers, + ).indexOf(row); if (currRowIndex != -1) { isHeader = true; } @@ -973,9 +1067,11 @@ class PdfGridLayouter extends ElementLayouter { _currentPageBounds.height) { rowHeightWithSpan -= isHeader ? _grid!.headers[i].height : _grid!.rows[i].height; - for (int j = 0; - j < _grid!.rows[currRowIndex].cells.count; - j++) { + 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)) { @@ -1042,92 +1138,81 @@ class PdfGridLayouter extends ElementLayouter { } } } - double? height = PdfGridRowHelper.getHelper(row).rowBreakHeight > 0.0 - ? PdfGridRowHelper.getHelper(row).rowBreakHeight - : row.height; + double? height = + PdfGridRowHelper.getHelper(row).rowBreakHeight > 0.0 + ? PdfGridRowHelper.getHelper(row).rowBreakHeight + : row.height; if (PdfGridHelper.getHelper(_grid!).isChildGrid! && PdfGridHelper.getHelper(_grid!).parentCell != null) { if (height + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top > + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top > _currentPageBounds.height) { + userHeight = _startLocation.y; if (_grid!.allowRowBreakingAcrossPages) { result.isFinish = true; if (PdfGridHelper.getHelper(_grid!).isChildGrid! && PdfGridRowHelper.getHelper(row).rowBreakHeight > 0) { - _currentBounds.y = _currentBounds.y + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top; + _currentBounds.y = + _currentBounds.y + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top; _currentBounds.x = _startLocation.x; } result.bounds = _currentBounds; result = _drawRowWithBreak(row, result, height); } else { - _currentBounds.y = _currentBounds.y + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top; - height = _currentBounds.height - + _currentBounds.y = + _currentBounds.y + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top; + height = + _currentBounds.height - _currentBounds.y - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom; + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; result.isFinish = false; _drawRow(row, result, height); } } else if (_currentBounds.y + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom + height > _currentPageBounds.height || _currentBounds.y + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom + height > _currentBounds.height || _currentBounds.y + PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom + + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom + rowHeightWithSpan > _currentPageBounds.height) { if (repeatRowIndex > -1 && @@ -1136,14 +1221,13 @@ class PdfGridLayouter extends ElementLayouter { result.isFinish = true; if (PdfGridHelper.getHelper(_grid!).isChildGrid! && PdfGridRowHelper.getHelper(row).rowBreakHeight > 0) { - _currentBounds.y = _currentBounds.y + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .top; + _currentBounds.y = + _currentBounds.y + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.top; _currentBounds.x = _startLocation.x; } @@ -1160,13 +1244,12 @@ class PdfGridLayouter extends ElementLayouter { result.isFinish = true; if (PdfGridHelper.getHelper(_grid!).isChildGrid! && PdfGridRowHelper.getHelper(row).rowBreakHeight > 0) { - height += PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper(_grid!).parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom; + height += + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper(_grid!).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; } _drawRow(row, result, height); } @@ -1180,7 +1263,7 @@ class PdfGridLayouter extends ElementLayouter { _drawRow(row, result, height); } } else if (_currentBounds.y + height > _currentPageBounds.height || - _currentBounds.y + height > _currentBounds.height || + (_currentBounds.y + height) - userHeight > _currentBounds.height || _currentBounds.y + rowHeightWithSpan > _currentPageBounds.height) { if (repeatRowIndex > -1 && repeatRowIndex == PdfGridRowHelper.getHelper(row).index) { @@ -1203,8 +1286,9 @@ class PdfGridLayouter extends ElementLayouter { } else { bool? skipcell = false; final PdfPoint location = _currentBounds.location; - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isChildGrid! && + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isChildGrid! && PdfGridRowHelper.getHelper(row).grid.allowRowBreakingAcrossPages && _startLocation.x != _currentBounds.x && PdfGridRowHelper.getHelper(row).getWidth() < @@ -1239,15 +1323,16 @@ class PdfGridLayouter extends ElementLayouter { PdfGridCellStyle cellstyle = row.cells[i].style; final Map bclResult = _raiseBeforeCellLayout( - _currentGraphics, - PdfGridRowHelper.getHelper(row).isHeaderRow - ? _currentHeaderRowIndex - : _currentRowIndex, - i, - PdfRectangle(location.x, location.y, size.width, size.height), - (row.cells[i].value is String) ? row.cells[i].value.toString() : '', - cellstyle, - PdfGridRowHelper.getHelper(row).isHeaderRow); + _currentGraphics, + PdfGridRowHelper.getHelper(row).isHeaderRow + ? _currentHeaderRowIndex + : _currentRowIndex, + i, + PdfRectangle(location.x, location.y, size.width, size.height), + (row.cells[i].value is String) ? row.cells[i].value.toString() : '', + cellstyle, + PdfGridRowHelper.getHelper(row).isHeaderRow, + ); cellstyle = bclResult['style'] as PdfGridCellStyle; final PdfGridBeginCellLayoutArgs? gridbclArgs = bclResult['args'] as PdfGridBeginCellLayoutArgs?; @@ -1262,51 +1347,52 @@ class PdfGridLayouter extends ElementLayouter { } final PdfStringLayoutResult? stringResult = PdfGridCellHelper.getHelper(row.cells[i]).draw( - _currentGraphics, - PdfRectangle(location.x, location.y, size.width, size.height), - cancelSpans); - if (PdfGridRowHelper.getHelper(row) - .grid - .style - .allowHorizontalOverflow && + _currentGraphics, + PdfRectangle(location.x, location.y, size.width, size.height), + cancelSpans, + ); + if (PdfGridRowHelper.getHelper( + row, + ).grid.style.allowHorizontalOverflow && (row.cells[i].columnSpan > _cellEndIndex || i + row.cells[i].columnSpan > _cellEndIndex + 1) && _cellEndIndex < row.cells.count - 1) { PdfGridRowHelper.getHelper(row).rowOverflowIndex = _cellEndIndex; } - if (PdfGridRowHelper.getHelper(row) - .grid - .style - .allowHorizontalOverflow && + if (PdfGridRowHelper.getHelper( + row, + ).grid.style.allowHorizontalOverflow && (PdfGridRowHelper.getHelper(row).rowOverflowIndex >= 0 && (row.cells[i].columnSpan > _cellEndIndex || i + row.cells[i].columnSpan > _cellEndIndex + 1)) && row.cells[i].columnSpan - _cellEndIndex + i - 1 > 0) { - row.cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] - .value = - stringResult != null && stringResult.remainder != null + row + .cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] + .value = stringResult != null && stringResult.remainder != null ? stringResult.remainder : ''; - row.cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] + row + .cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] .stringFormat = row.cells[i].stringFormat; - row.cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] + row + .cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] .style = row.cells[i].style; - row.cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] + row + .cells[PdfGridRowHelper.getHelper(row).rowOverflowIndex + 1] .columnSpan = row.cells[i].columnSpan - _cellEndIndex + i - 1; } } if (!cancelSpans) { _raiseAfterCellLayout( - _currentGraphics, - _currentRowIndex, - i, - PdfRectangle(location.x, location.y, size.width, size.height), - (row.cells[i].value is String) - ? row.cells[i].value.toString() - : '', - row.cells[i].style, - PdfGridRowHelper.getHelper(row).isHeaderRow); + _currentGraphics, + _currentRowIndex, + i, + PdfRectangle(location.x, location.y, size.width, size.height), + (row.cells[i].value is String) ? row.cells[i].value.toString() : '', + row.cells[i].style, + PdfGridRowHelper.getHelper(row).isHeaderRow, + ); } if (row.cells[i].value is PdfGrid) { @@ -1314,17 +1400,19 @@ class PdfGridLayouter extends ElementLayouter { PdfGridCellHelper.getHelper(row.cells[i]).pageCount = PdfGridHelper.getHelper(grid).listOfNavigatePages.length; _rowBreakPageHeightCellIndex = i; - for (int k = 0; - k < PdfGridHelper.getHelper(grid).listOfNavigatePages.length; - k++) { + for ( + int k = 0; + k < PdfGridHelper.getHelper(grid).listOfNavigatePages.length; + k++ + ) { final int pageIndex = PdfGridHelper.getHelper(grid).listOfNavigatePages[k]; - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(pageIndex)) { - PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .add(pageIndex); + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(pageIndex)) { + PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.add(pageIndex); } } if (_grid!.columns[i].width >= _currentGraphics!.clientSize.width) { @@ -1342,7 +1430,11 @@ class PdfGridLayouter extends ElementLayouter { _currentBounds.y = _currentBounds.y + height; } result.bounds = PdfRectangle( - result.bounds.x, result.bounds.y, location.x, location.y); + result.bounds.x, + result.bounds.y, + location.x, + location.y, + ); return null; } } @@ -1351,31 +1443,38 @@ class PdfGridLayouter extends ElementLayouter { double newHeight = 0.0; for (int i = _cellStartIndex; i <= _cellEndIndex; i++) { if (PdfGridCellHelper.getHelper(row!.cells[i]).remainingString != null && - PdfGridCellHelper.getHelper(row.cells[i]) - .remainingString! - .isNotEmpty) { - newHeight = max(newHeight, - PdfGridCellHelper.getHelper(row.cells[i]).measureHeight()); + PdfGridCellHelper.getHelper( + row.cells[i], + ).remainingString!.isNotEmpty) { + newHeight = max( + newHeight, + PdfGridCellHelper.getHelper(row.cells[i]).measureHeight(), + ); } } return max(height, newHeight); } _RowLayoutResult _drawRowWithBreak( - PdfGridRow row, _RowLayoutResult result, double? height) { + PdfGridRow row, + _RowLayoutResult result, + double? height, + ) { final PdfPoint location = _currentBounds.location; - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid! && + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid! && PdfGridRowHelper.getHelper(row).grid.allowRowBreakingAcrossPages && _startLocation.x != _currentBounds.x) { location.x = _startLocation.x; } result.bounds = PdfRectangle(location.x, location.y, 0, 0); - _newheight = PdfGridRowHelper.getHelper(row).rowBreakHeight > 0 - ? _currentBounds.height < _currentPageBounds.height - ? _currentBounds.height - : _currentPageBounds.height - : 0; + _newheight = + PdfGridRowHelper.getHelper(row).rowBreakHeight > 0 + ? _currentBounds.height < _currentPageBounds.height + ? _currentBounds.height + : _currentPageBounds.height + : 0; if (PdfGridRowHelper.getHelper(row).grid.style.cellPadding.top + _currentBounds.y + PdfGridRowHelper.getHelper(row).grid.style.cellPadding.bottom < @@ -1390,9 +1489,10 @@ class PdfGridLayouter extends ElementLayouter { for (int cellIndex = 0; cellIndex < row.cells.count; cellIndex++) { final PdfGridCell cell = row.cells[cellIndex]; if (PdfGridCellHelper.getHelper(cell).measureHeight() == height) { - PdfGridRowHelper.getHelper(row).rowBreakHeight = cell.value is PdfGrid - ? 0 - : _currentBounds.y + height - _currentBounds.height < + PdfGridRowHelper.getHelper(row).rowBreakHeight = + cell.value is PdfGrid + ? 0 + : _currentBounds.y + height - _currentBounds.height < _currentPageBounds.height ? _currentBounds.height : _currentPageBounds.height; @@ -1401,7 +1501,7 @@ class PdfGridLayouter extends ElementLayouter { for (int i = _cellStartIndex; i <= _cellEndIndex; i++) { final bool cancelSpans = row.cells[i].columnSpan + i > _cellEndIndex + 1 && - row.cells[i].columnSpan > 1; + row.cells[i].columnSpan > 1; if (!cancelSpans) { for (int j = 1; j < row.cells[i].columnSpan; j++) { PdfGridCellHelper.getHelper(row.cells[i + j]).isCellMergeContinue = @@ -1409,12 +1509,13 @@ class PdfGridLayouter extends ElementLayouter { } } PdfSize size = PdfSize( - _grid!.columns[i].width, - _newheight > 0.0 - ? _newheight - : _currentBounds.height < _currentPageBounds.height - ? _currentBounds.height - : _currentPageBounds.height); + _grid!.columns[i].width, + _newheight > 0.0 + ? _newheight + : _currentBounds.height < _currentPageBounds.height + ? _currentBounds.height + : _currentPageBounds.height, + ); if (size.width == 0) { size = PdfSize(row.cells[i].width, size.height); } @@ -1424,15 +1525,16 @@ class PdfGridLayouter extends ElementLayouter { } PdfGridCellStyle cellstyle = row.cells[i].style; final Map cellLayoutResult = _raiseBeforeCellLayout( - _currentGraphics, - PdfGridRowHelper.getHelper(row).isHeaderRow - ? _currentHeaderRowIndex - : _currentRowIndex, - i, - PdfRectangle(location.x, location.y, size.width, size.height), - row.cells[i].value is String ? row.cells[i].value.toString() : '', - cellstyle, - PdfGridRowHelper.getHelper(row).isHeaderRow); + _currentGraphics, + PdfGridRowHelper.getHelper(row).isHeaderRow + ? _currentHeaderRowIndex + : _currentRowIndex, + i, + PdfRectangle(location.x, location.y, size.width, size.height), + row.cells[i].value is String ? row.cells[i].value.toString() : '', + cellstyle, + PdfGridRowHelper.getHelper(row).isHeaderRow, + ); cellstyle = cellLayoutResult['style'] as PdfGridCellStyle; final PdfGridBeginCellLayoutArgs? bclArgs = cellLayoutResult['args'] as PdfGridBeginCellLayoutArgs?; @@ -1441,17 +1543,19 @@ class PdfGridLayouter extends ElementLayouter { PdfStringLayoutResult? stringResult; if (!skipcell) { stringResult = PdfGridCellHelper.getHelper(row.cells[i]).draw( - _currentGraphics, - PdfRectangle(location.x, location.y, size.width, size.height), - cancelSpans); + _currentGraphics, + PdfRectangle(location.x, location.y, size.width, size.height), + cancelSpans, + ); } if (PdfGridRowHelper.getHelper(row).rowBreakHeight > 0.0) { if (stringResult != null) { PdfGridCellHelper.getHelper(row.cells[i]).finished = false; PdfGridCellHelper.getHelper(row.cells[i]).remainingString = stringResult.remainder ?? ''; - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row).grid) - .isChildGrid!) { + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row).grid, + ).isChildGrid!) { PdfGridRowHelper.getHelper(row).rowBreakHeight = height - stringResult.size.height; } @@ -1459,32 +1563,36 @@ class PdfGridLayouter extends ElementLayouter { PdfGridCellHelper.getHelper(row.cells[i]).finished = false; } } - result.isFinish = (!result.isFinish) - ? result.isFinish - : PdfGridCellHelper.getHelper(row.cells[i]).finished; + result.isFinish = + (!result.isFinish) + ? result.isFinish + : PdfGridCellHelper.getHelper(row.cells[i]).finished; if (!cancelSpans) { _raiseAfterCellLayout( - _currentGraphics, - _currentRowIndex, - i, - PdfRectangle(location.x, location.y, size.width, size.height), - (row.cells[i].value is String) ? row.cells[i].value.toString() : '', - row.cells[i].style, - PdfGridRowHelper.getHelper(row).isHeaderRow); + _currentGraphics, + _currentRowIndex, + i, + PdfRectangle(location.x, location.y, size.width, size.height), + (row.cells[i].value is String) ? row.cells[i].value.toString() : '', + row.cells[i].style, + PdfGridRowHelper.getHelper(row).isHeaderRow, + ); } if (row.cells[i].value is PdfGrid) { final PdfGrid grid = row.cells[i].value as PdfGrid; _rowBreakPageHeightCellIndex = i; PdfGridCellHelper.getHelper(row.cells[i]).pageCount = PdfGridHelper.getHelper(grid).listOfNavigatePages.length; - for (int i = 0; - i < PdfGridHelper.getHelper(grid).listOfNavigatePages.length; - i++) { + for ( + int i = 0; + i < PdfGridHelper.getHelper(grid).listOfNavigatePages.length; + i++ + ) { final int pageIndex = PdfGridHelper.getHelper(grid).listOfNavigatePages[i]; - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(pageIndex)) { + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(pageIndex)) { PdfGridHelper.getHelper(_grid!).listOfNavigatePages.add(pageIndex); } } @@ -1500,8 +1608,12 @@ class PdfGridLayouter extends ElementLayouter { } _currentBounds.y = _currentBounds.y + (_newheight > 0.0 ? _newheight : height); - result.bounds = - PdfRectangle(result.bounds.x, result.bounds.y, location.x, location.y); + result.bounds = PdfRectangle( + result.bounds.x, + result.bounds.y, + location.x, + location.y, + ); return result; } @@ -1536,9 +1648,11 @@ class PdfGridLayouter extends ElementLayouter { PdfPageCollectionHelper.getHelper(document!.pages).remove(page); } for (int i = 0; i < layoutedPages.length; i++) { - for (int j = i; - j < layoutedPages.length; - j += layoutedPages.length ~/ _columnRanges!.length) { + for ( + int j = i; + j < layoutedPages.length; + j += layoutedPages.length ~/ _columnRanges!.length + ) { final PdfPage page = pages[j]!; if (document!.pages.indexOf(page) == -1) { PdfPageCollectionHelper.getHelper(document.pages).addPage(page); @@ -1594,8 +1708,9 @@ class PdfGridLayouter extends ElementLayouter { PdfPage? _getNextPage(PdfLayoutFormat format) { final PdfSection section = PdfPageHelper.getHelper(_currentPage!).section!; PdfPage? nextPage; - final int index = - PdfSectionHelper.getHelper(section).indexOf(_currentPage!); + final int index = PdfSectionHelper.getHelper( + section, + ).indexOf(_currentPage!); if (PdfPageHelper.getHelper(_currentPage!).document!.pages.count > 1 && _hType == PdfHorizontalOverflowType.nextPage && flag && @@ -1613,17 +1728,22 @@ class PdfGridLayouter extends ElementLayouter { nextPage = PdfSectionHelper.getHelper(section).getPageByIndex(index + 1); } _currentGraphics = nextPage!.graphics; - final int pageindex = PdfSectionHelper.getHelper(PdfPageHelper.getHelper( - PdfGraphicsHelper.getHelper(_currentGraphics!).page!) - .section!) - .indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(pageindex)) { + final int pageindex = PdfSectionHelper.getHelper( + PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).section!, + ).indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!); + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(pageindex)) { PdfGridHelper.getHelper(_grid!).listOfNavigatePages.add(pageindex); } - _currentBounds = PdfRectangle(0, 0, _currentGraphics!.clientSize.width, - _currentGraphics!.clientSize.height); + _currentBounds = PdfRectangle( + 0, + 0, + _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height, + ); if (PdfRectangle.fromRect(format.paginateBounds) != PdfRectangle.empty) { _currentBounds.x = format.paginateBounds.left; _currentBounds.y = format.paginateBounds.top; @@ -1642,31 +1762,49 @@ class PdfGridLayouter extends ElementLayouter { } } } - final Rect bounds = _isChanged - ? Rect.fromLTWH(_currentLocation.x, _currentLocation.y, - _currentBounds.width, _currentBounds.y - _currentLocation.y) - : Rect.fromLTWH(_startLocation.x, _startLocation.y, - _currentBounds.width, _currentBounds.y - _startLocation.y); + final Rect bounds = + _isChanged + ? Rect.fromLTWH( + _currentLocation.x, + _currentLocation.y, + _currentBounds.width, + _currentBounds.y - _currentLocation.y, + ) + : Rect.fromLTWH( + _startLocation.x, + _startLocation.y, + _currentBounds.width, + _currentBounds.y - _startLocation.y, + ); return PdfLayoutResultHelper.load(_currentPage!, bounds); } Map _raiseBeforePageLayout( - PdfPage? currentPage, Rect currentBounds, int? currentRow) { + PdfPage? currentPage, + Rect currentBounds, + int? currentRow, + ) { bool cancel = false; if (PdfLayoutElementHelper.getHelper(element!).raiseBeginPageLayout) { final PdfGridBeginPageLayoutArgs args = PdfGridBeginPageLayoutArgsHelper.load( - currentBounds, currentPage!, currentRow); + currentBounds, + currentPage!, + currentRow, + ); PdfLayoutElementHelper.getHelper(element!).onBeginPageLayout(args); if (PdfRectangle.fromRect(currentBounds) != PdfRectangle.fromRect(args.bounds)) { _isChanged = true; _currentLocation = PdfPoint(args.bounds.left, args.bounds.top); - PdfGridHelper.getHelper(_grid!).measureColumnsWidth(PdfRectangle( + PdfGridHelper.getHelper(_grid!).measureColumnsWidth( + PdfRectangle( args.bounds.left, args.bounds.top, args.bounds.width + args.bounds.left, - args.bounds.height)); + args.bounds.height, + ), + ); } cancel = args.cancel; currentBounds = args.bounds; @@ -1675,13 +1813,14 @@ class PdfGridLayouter extends ElementLayouter { return { 'cancel': cancel, 'currentBounds': currentBounds, - 'currentRow': currentRow + 'currentRow': currentRow, }; } PdfGridEndPageLayoutArgs _raisePageLayouted(PdfLayoutResult result) { - final PdfGridEndPageLayoutArgs args = - PdfGridEndPageLayoutArgsHelper.load(result); + final PdfGridEndPageLayoutArgs args = PdfGridEndPageLayoutArgsHelper.load( + result, + ); if (PdfLayoutElementHelper.getHelper(element!).raisePageLayouted) { PdfLayoutElementHelper.getHelper(element!).onEndPageLayout(args); } @@ -1689,17 +1828,25 @@ class PdfGridLayouter extends ElementLayouter { } Map _raiseBeforeCellLayout( - PdfGraphics? graphics, - int rowIndex, - int cellIndex, - PdfRectangle bounds, - String value, - PdfGridCellStyle? style, - bool isHeaderRow) { + PdfGraphics? graphics, + int rowIndex, + int cellIndex, + PdfRectangle bounds, + String value, + PdfGridCellStyle? style, + bool isHeaderRow, + ) { PdfGridBeginCellLayoutArgs? args; if (PdfGridHelper.getHelper(_grid!).raiseBeginCellLayout) { args = PdfGridBeginCellLayoutArgsHelper.load( - graphics!, rowIndex, cellIndex, bounds, value, style, isHeaderRow); + graphics!, + rowIndex, + cellIndex, + bounds, + value, + style, + isHeaderRow, + ); PdfGridHelper.getHelper(_grid!).onBeginCellLayout(args); style = args.style; } @@ -1707,17 +1854,25 @@ class PdfGridLayouter extends ElementLayouter { } void _raiseAfterCellLayout( - PdfGraphics? graphics, - int rowIndex, - int cellIndex, - PdfRectangle bounds, - String value, - PdfGridCellStyle? cellstyle, - bool isHeaderRow) { + PdfGraphics? graphics, + int rowIndex, + int cellIndex, + PdfRectangle bounds, + String value, + PdfGridCellStyle? cellstyle, + bool isHeaderRow, + ) { PdfGridEndCellLayoutArgs args; if (PdfGridHelper.getHelper(_grid!).raiseEndCellLayout) { - args = PdfGridEndCellLayoutArgsHelper.load(graphics!, rowIndex, cellIndex, - bounds, value, cellstyle, isHeaderRow); + args = PdfGridEndCellLayoutArgsHelper.load( + graphics!, + rowIndex, + cellIndex, + bounds, + value, + cellstyle, + isHeaderRow, + ); PdfGridHelper.getHelper(_grid!).onEndCellLayout(args); } } @@ -1747,38 +1902,43 @@ class PdfGridLayouter extends ElementLayouter { } if (PdfGraphicsHelper.getHelper(_currentGraphics!).layer != null) { final int index = - PdfGraphicsHelper.getHelper(_currentGraphics!).page is PdfPage - ? PdfSectionHelper.getHelper(PdfPageHelper.getHelper( - PdfGraphicsHelper.getHelper(_currentGraphics!).page!) - .section!) - .indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!) - : PdfGraphicsHelper.getHelper(_currentGraphics!) - .page! - .defaultLayerIndex; - if (!PdfGridHelper.getHelper(_grid!) - .listOfNavigatePages - .contains(index)) { + !PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).isLoadedPage + ? PdfSectionHelper.getHelper( + PdfPageHelper.getHelper( + PdfGraphicsHelper.getHelper(_currentGraphics!).page!, + ).section!, + ).indexOf(PdfGraphicsHelper.getHelper(_currentGraphics!).page!) + : PdfGraphicsHelper.getHelper( + _currentGraphics!, + ).page!.defaultLayerIndex; + if (!PdfGridHelper.getHelper( + _grid!, + ).listOfNavigatePages.contains(index)) { PdfGridHelper.getHelper(_grid!).listOfNavigatePages.add(index); } } _currentBounds = PdfRectangle( - param.bounds!.x, - param.bounds!.y, - format.breakType == PdfLayoutBreakType.fitColumnsToPage - ? PdfGridColumnCollectionHelper.getHelper(_grid!.columns) - .columnWidth - : _currentGraphics!.clientSize.width, - _currentGraphics!.clientSize.height); + param.bounds!.x, + param.bounds!.y, + format.breakType == PdfLayoutBreakType.fitColumnsToPage + ? PdfGridColumnCollectionHelper.getHelper(_grid!.columns).columnWidth + : _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); + _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); + _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 && @@ -1819,6 +1979,7 @@ class PdfGridLayouter extends ElementLayouter { } } } + userHeight = _startLocation.y; return _layoutOnPage(param); } } 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 981da8d81..b5052d403 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 @@ -57,7 +57,7 @@ import 'styles/style.dart'; /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -90,7 +90,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -134,7 +134,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -175,7 +175,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -237,7 +237,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -288,7 +288,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -329,7 +329,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -372,7 +372,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -415,7 +415,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -458,7 +458,7 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -523,27 +523,32 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @override - PdfLayoutResult? draw( - {Rect? bounds, - PdfLayoutFormat? format, - PdfGraphics? graphics, - PdfPage? page}) { + PdfLayoutResult? draw({ + Rect? bounds, + PdfLayoutFormat? format, + PdfGraphics? graphics, + PdfPage? page, + }) { final PdfRectangle rectangle = bounds != null ? PdfRectangle.fromRect(bounds) : PdfRectangle.empty; - _helper.initialWidth = rectangle.width == 0 - ? page != null - ? page.getClientSize().width - : graphics!.clientSize.width - : rectangle.width; + _helper.initialWidth = + rectangle.width == 0 + ? page != null + ? page.getClientSize().width + : graphics!.clientSize.width + : rectangle.width; _helper.isWidthSet = true; if (page != null) { - final PdfLayoutResult? result = - super.draw(page: page, bounds: bounds, format: format); + final PdfLayoutResult? result = super.draw( + page: page, + bounds: bounds, + format: format, + ); _helper.isComplete = true; return result; } else if (graphics != null) { @@ -587,17 +592,21 @@ class PdfGrid extends PdfLayoutElement { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - void applyBuiltInStyle(PdfGridBuiltInStyle gridStyle, - {PdfGridBuiltInStyleSettings? settings}) { + void applyBuiltInStyle( + PdfGridBuiltInStyle gridStyle, { + PdfGridBuiltInStyleSettings? settings, + }) { _intializeBuiltInStyle(gridStyle, settings: settings); } - void _intializeBuiltInStyle(PdfGridBuiltInStyle gridStyle, - {PdfGridBuiltInStyleSettings? settings}) { + void _intializeBuiltInStyle( + PdfGridBuiltInStyle gridStyle, { + PdfGridBuiltInStyleSettings? settings, + }) { if (settings != null) { _headerRow = settings.applyStyleForHeaderRow; _totalRow = settings.applyStyleForLastRow; @@ -619,17 +628,21 @@ class PdfGrid extends PdfLayoutElement { _boldFontCache ??= {}; if (font is PdfStandardFont) { final PdfStandardFont standardFont = font; - return PdfStandardFont(standardFont.fontFamily, font.size, - style: PdfFontStyle.bold); + return PdfStandardFont( + standardFont.fontFamily, + font.size, + style: PdfFontStyle.bold, + ); } else { if (_boldFontCache!.containsKey(font)) { return _boldFontCache![font as PdfTrueTypeFont]; } else { final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; final PdfFont boldStyleFont = PdfTrueTypeFont( - PdfTrueTypeFontHelper.getHelper(trueTypeFont).fontInternal.fontData, - font.size, - style: PdfFontStyle.bold); + PdfTrueTypeFontHelper.getHelper(trueTypeFont).fontInternal.fontData, + font.size, + style: PdfFontStyle.bold, + ); _boldFontCache![font] = boldStyleFont as PdfTrueTypeFont; return boldStyleFont; } @@ -640,17 +653,21 @@ class PdfGrid extends PdfLayoutElement { _regularFontCache ??= {}; if (font is PdfStandardFont) { final PdfStandardFont standardFont = font; - return PdfStandardFont(standardFont.fontFamily, font.size, - style: PdfFontStyle.regular); + return PdfStandardFont( + standardFont.fontFamily, + font.size, + style: PdfFontStyle.regular, + ); } else { if (_regularFontCache!.containsKey(font)) { return _regularFontCache![font as PdfTrueTypeFont]; } else { final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; final PdfFont ttfFont = PdfTrueTypeFont( - PdfTrueTypeFontHelper.getHelper(trueTypeFont).fontInternal.fontData, - font.size, - style: PdfFontStyle.regular); + PdfTrueTypeFontHelper.getHelper(trueTypeFont).fontInternal.fontData, + font.size, + style: PdfFontStyle.regular, + ); _regularFontCache![font] = ttfFont as PdfTrueTypeFont; return ttfFont; } @@ -661,17 +678,21 @@ class PdfGrid extends PdfLayoutElement { _italicFontCache ??= {}; if (font is PdfStandardFont) { final PdfStandardFont standardFont = font; - return PdfStandardFont(standardFont.fontFamily, font.size, - style: PdfFontStyle.italic); + return PdfStandardFont( + standardFont.fontFamily, + font.size, + style: PdfFontStyle.italic, + ); } else { if (_italicFontCache!.containsKey(font)) { return _italicFontCache![font! as PdfTrueTypeFont]; } else { final PdfTrueTypeFont trueTypeFont = font! as PdfTrueTypeFont; final PdfFont italicStyleFont = PdfTrueTypeFont( - PdfTrueTypeFontHelper.getHelper(trueTypeFont).fontInternal.fontData, - font.size, - style: PdfFontStyle.italic); + PdfTrueTypeFontHelper.getHelper(trueTypeFont).fontInternal.fontData, + font.size, + style: PdfFontStyle.italic, + ); _italicFontCache![font as PdfTrueTypeFont] = italicStyleFont as PdfTrueTypeFont; return italicStyleFont; @@ -690,7 +711,10 @@ class PdfGrid extends PdfLayoutElement { } PdfBrush? _applyBandedColStyle( - bool firstColumn, PdfColor backColor, int cellIndex) { + bool firstColumn, + PdfColor backColor, + int cellIndex, + ) { PdfBrush? backBrush; if (firstColumn) { if (cellIndex.isEven) { @@ -705,7 +729,10 @@ class PdfGrid extends PdfLayoutElement { } PdfBrush? _applyBandedRowStyle( - bool headerRow, PdfColor backColor, int rowIndex) { + bool headerRow, + PdfColor backColor, + int rowIndex, + ) { PdfBrush? backBrush; if (headerRow) { if (rowIndex.isOdd) { @@ -720,7 +747,7 @@ class PdfGrid extends PdfLayoutElement { } void _applyTableGridLight(PdfColor borderColor) { - final PdfPen borderPen = PdfPen(borderColor, width: 1); + final PdfPen borderPen = PdfPen(borderColor); if (headers.count > 0) { for (int i = 1; i <= headers.count; i++) { final PdfGridRow row = headers[i - 1]; @@ -750,15 +777,19 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = borderPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); cell.style.borders.all = borderPen; if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; @@ -770,7 +801,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -783,7 +815,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = backBrush; } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -801,7 +834,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = borderPen; if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -810,28 +844,44 @@ class PdfGrid extends PdfLayoutElement { } if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } } if (_lastColumn && j == row.cells.count) { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); - } - final PdfFont font = cell.style.font ?? + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); + } + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -840,7 +890,8 @@ class PdfGrid extends PdfLayoutElement { } if (_totalRow && i == rows.count) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -848,8 +899,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.top = PdfPen(borderColor); if (_bandedColumn) { if (!(_lastColumn && j == row.cells.count)) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } } } @@ -873,7 +927,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = borderPen; } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -891,7 +946,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.bottom = borderPen; } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -899,7 +955,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.left = emptyPen; } if (_lastColumn && j == row.cells.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -929,7 +986,8 @@ class PdfGrid extends PdfLayoutElement { } if (i == rows.count && _totalRow) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -942,7 +1000,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -954,7 +1013,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -980,11 +1040,15 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = whitePen; if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1012,7 +1076,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1024,7 +1089,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1054,11 +1120,17 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = whitePen; if (_bandedRow && _bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; } @@ -1067,8 +1139,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; @@ -1079,8 +1154,11 @@ class PdfGrid extends PdfLayoutElement { } } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; } @@ -1094,7 +1172,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = whitePen; } cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1104,8 +1183,11 @@ class PdfGrid extends PdfLayoutElement { cell.value = cellvalue.toUpperCase(); } if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; @@ -1115,7 +1197,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1129,7 +1212,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1141,8 +1225,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = null; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } cell.style.borders.all = whitePen; } else if (_bandedColumn) { @@ -1168,11 +1255,15 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = whitePen; if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1182,8 +1273,11 @@ class PdfGrid extends PdfLayoutElement { cell.value = cellvalue.toUpperCase(); } if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; @@ -1195,7 +1289,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1212,7 +1307,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = backBrush; } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1235,7 +1331,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = whitePen; if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1247,28 +1344,44 @@ class PdfGrid extends PdfLayoutElement { } } if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } } if (_lastColumn && j == row.cells.count) { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); - } - final PdfFont font = cell.style.font ?? + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); + } + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1282,7 +1395,8 @@ class PdfGrid extends PdfLayoutElement { } if (_totalRow && i == rows.count) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1297,8 +1411,11 @@ class PdfGrid extends PdfLayoutElement { } if (_bandedColumn) { if (!(_lastColumn && j == row.cells.count)) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } } } @@ -1319,7 +1436,8 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = whitePen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1331,8 +1449,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; @@ -1354,7 +1475,8 @@ class PdfGrid extends PdfLayoutElement { if (_firstColumn && j == 1) { cell.style.borders.all = whitePen; cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1364,7 +1486,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1384,10 +1507,16 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = whitePen; if (_bandedRow && _bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; @@ -1397,8 +1526,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; @@ -1408,8 +1540,11 @@ class PdfGrid extends PdfLayoutElement { } } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (_firstColumn && j == 2) { cell.style.borders.left = borderPen; } else { @@ -1421,7 +1556,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = PdfPen(PdfColor.empty); cell.style.backgroundBrush = null; cell.style.borders.top = borderPen; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1433,7 +1569,8 @@ class PdfGrid extends PdfLayoutElement { if (!(_totalRow && i == rows.count)) { cell.style.borders.all = whitePen; cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1445,7 +1582,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1473,21 +1611,24 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = borderPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); } else { if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); } if (_lastColumn && j == row.cells.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1509,7 +1650,8 @@ class PdfGrid extends PdfLayoutElement { if (_totalRow) { if (i == rows.count) { cell.style.borders.top = PdfPen(headerBottomColor); - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1518,7 +1660,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1527,7 +1670,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1557,24 +1701,26 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = emptyPen; } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); cell.style.borders.all = PdfPen(PdfColor.empty); if (PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(cell).row!) - .grid - .style - .cellSpacing > + PdfGridCellHelper.getHelper(cell).row!, + ).grid.style.cellSpacing > 0) { cell.style.borders.bottom = headerBorder; } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (j == 1) { cell.style.borders.left = backColorPen; @@ -1596,7 +1742,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1617,7 +1764,8 @@ class PdfGrid extends PdfLayoutElement { } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1639,10 +1787,16 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = emptyPen; } if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); - cell.style.backgroundBrush ??= - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); + cell.style.backgroundBrush ??= _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (j == 1) { cell.style.borders.left = backColorPen; @@ -1652,8 +1806,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { if (j == 1) { @@ -1664,8 +1821,11 @@ class PdfGrid extends PdfLayoutElement { } } if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (j == 1) { cell.style.borders.left = backColorPen; @@ -1676,7 +1836,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_totalRow && i == rows.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1687,7 +1848,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1696,15 +1858,19 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); cell.style.backgroundBrush = null; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush == null) { cell.style.borders.right = emptyPen; } @@ -1734,7 +1900,8 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = borderPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1742,8 +1909,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = whitePen; } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { if (i % 2 != 0) { @@ -1753,7 +1923,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1762,7 +1933,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1780,23 +1952,36 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = borderPen; if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } } if (_totalRow && i == rows.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1808,7 +1993,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1824,7 +2010,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1841,7 +2028,10 @@ class PdfGrid extends PdfLayoutElement { } void _applyGridTable4( - PdfColor borderColor, PdfColor backColor, PdfColor headerBackColor) { + PdfColor borderColor, + PdfColor backColor, + PdfColor headerBackColor, + ) { final PdfPen borderPen = PdfPen(borderColor, width: 0.5); final PdfBrush backBrush = PdfSolidBrush(backColor); final PdfPen headerBackColorPen = PdfPen(headerBackColor, width: 0.5); @@ -1852,7 +2042,8 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = borderPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1862,8 +2053,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = PdfSolidBrush(headerBackColor); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { if (i % 2 != 0) { @@ -1871,7 +2065,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1880,10 +2075,14 @@ class PdfGrid extends PdfLayoutElement { if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1901,7 +2100,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = borderPen; if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1910,28 +2110,44 @@ class PdfGrid extends PdfLayoutElement { } if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } } if (_totalRow && i == rows.count) { cell.style.backgroundBrush = null; if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1942,11 +2158,15 @@ class PdfGrid extends PdfLayoutElement { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1965,8 +2185,11 @@ class PdfGrid extends PdfLayoutElement { } } - void _applyGridTable5Dark(PdfColor headerBackColor, PdfColor oddRowBackColor, - PdfColor evenRowBackColor) { + void _applyGridTable5Dark( + PdfColor headerBackColor, + PdfColor oddRowBackColor, + PdfColor evenRowBackColor, + ) { final PdfPen whitePen = PdfPen(PdfColor(255, 255, 255), width: 0.5); final PdfBrush evenRowBrush = PdfSolidBrush(evenRowBackColor); final PdfBrush oddRowBrush = PdfSolidBrush(oddRowBackColor); @@ -1981,7 +2204,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = whitePen; cell.style.backgroundBrush = evenRowBrush; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -1996,8 +2220,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, oddRowBackColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + oddRowBackColor, + j, + ); cell.style.backgroundBrush ??= evenRowBrush; } @@ -2009,7 +2236,8 @@ class PdfGrid extends PdfLayoutElement { if ((_firstColumn && j == 1) || (_lastColumn && j == row.cells.count)) { cell.style.backgroundBrush = headerBrush; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2029,30 +2257,43 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = evenRowBrush; if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, oddRowBackColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + oddRowBackColor, + j, + ); if (cell.style.backgroundBrush == null) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, oddRowBackColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + oddRowBackColor, + i, + ); cell.style.backgroundBrush ??= evenRowBrush; } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, oddRowBackColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + oddRowBackColor, + j, + ); cell.style.backgroundBrush ??= evenRowBrush; } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, oddRowBackColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + oddRowBackColor, + i, + ); cell.style.backgroundBrush ??= evenRowBrush; } } if (_totalRow && i == rows.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2070,7 +2311,8 @@ class PdfGrid extends PdfLayoutElement { if ((_firstColumn && j == 1) || (_lastColumn && j == row.cells.count)) { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = headerBrush; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2083,7 +2325,10 @@ class PdfGrid extends PdfLayoutElement { } void _applyGridTable6Colorful( - PdfColor borderColor, PdfColor backColor, PdfColor textColor) { + PdfColor borderColor, + PdfColor backColor, + PdfColor textColor, + ) { final PdfPen borderPen = PdfPen(borderColor, width: 0.5); final PdfBrush backBrush = PdfSolidBrush(backColor); final PdfPen headerBottomPen = PdfPen(borderColor); @@ -2096,11 +2341,15 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = borderPen; cell.style.textBrush = textBrush; if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2115,7 +2364,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2126,7 +2376,8 @@ class PdfGrid extends PdfLayoutElement { if (i % 2 != 0) { cell.style.backgroundBrush = backBrush; } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2145,28 +2396,44 @@ class PdfGrid extends PdfLayoutElement { cell.style.textBrush = textBrush; if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } } if (_totalRow && i == rows.count) { cell.style.backgroundBrush = null; if (_bandedColumn && (!(_lastColumn && j == row.cells.count))) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - } - final PdfFont font = cell.style.font ?? + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + } + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2175,7 +2442,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2186,10 +2454,14 @@ class PdfGrid extends PdfLayoutElement { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); - } - final PdfFont font = cell.style.font ?? + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); + } + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2205,7 +2477,10 @@ class PdfGrid extends PdfLayoutElement { } void _applyGridTable7Colorful( - PdfColor borderColor, PdfColor backColor, PdfColor textColor) { + PdfColor borderColor, + PdfColor backColor, + PdfColor textColor, + ) { final PdfPen borderPen = PdfPen(borderColor, width: 0.5); final PdfBrush backBrush = PdfSolidBrush(backColor); final PdfBrush textBrush = PdfSolidBrush(textColor); @@ -2219,7 +2494,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.textBrush = textBrush; cell.style.borders.all = borderPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2227,8 +2503,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = PdfPen(PdfColor(255, 255, 255)); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { @@ -2239,7 +2518,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2248,7 +2528,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2267,24 +2548,37 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = borderPen; cell.style.textBrush = textBrush; if (_bandedRow && _bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); } } if (_totalRow && i == rows.count) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2298,7 +2592,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2316,7 +2611,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2342,7 +2638,8 @@ class PdfGrid extends PdfLayoutElement { final PdfGridCell cell = row.cells[j - 1]; cell.style.borders.all = emptyPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2353,8 +2650,11 @@ class PdfGrid extends PdfLayoutElement { } if (_bandedColumn) { if (_lastColumn && j == rows.count) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (cell.style.backgroundBrush != null) { cell.style.borders.all = backBrushPen; @@ -2366,8 +2666,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backBrushPen; } @@ -2381,7 +2684,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2393,7 +2697,8 @@ class PdfGrid extends PdfLayoutElement { if (_bandedRow && i % 2 != 0) { cell.style.backgroundBrush = backBrush; } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2411,25 +2716,37 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = emptyPen; if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backBrushPen; } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backBrushPen; } } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backBrushPen; } @@ -2437,7 +2754,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2451,10 +2769,14 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = null; cell.style.borders.all = emptyPen; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); - } - final PdfFont font = cell.style.font ?? + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); + } + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2464,15 +2786,19 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); if (_bandedColumn) { if (!(_lastColumn && j == row.cells.count)) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (cell.style.backgroundBrush != null) { cell.style.borders.all = backBrushPen; @@ -2504,14 +2830,18 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.bottom = borderPen; cell.style.borders.top = borderPen; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; } @@ -2522,8 +2852,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.right = backColorPen; cell.style.borders.left = backColorPen; @@ -2537,7 +2870,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2554,7 +2888,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = backColorPen; } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2573,27 +2908,39 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.bottom = borderPen; cell.style.borders.top = borderPen; if (_bandedRow && _bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.right = backColorPen; cell.style.borders.left = backColorPen; } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.right = backColorPen; cell.style.borders.left = backColorPen; } } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.left = backColorPen; cell.style.borders.right = backColorPen; @@ -2606,7 +2953,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.top = borderPen; cell.style.borders.bottom = borderPen; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2614,8 +2962,11 @@ class PdfGrid extends PdfLayoutElement { if (_bandedColumn) { if (!(j == row.cells.count && _lastColumn)) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (cell.style.backgroundBrush != null) { cell.style.borders.right = backColorPen; @@ -2625,7 +2976,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2638,13 +2990,17 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.left = emptyPen; cell.style.borders.right = emptyPen; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.right = backColorPen; } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2672,7 +3028,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = backColorPen; } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2682,14 +3039,16 @@ class PdfGrid extends PdfLayoutElement { cell.style.textBrush = PdfSolidBrush(PdfColor(255, 255, 255)); } else { if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); } if (_lastColumn && j == row.cells.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2726,7 +3085,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.top = backColorPen; } if (_totalRow && i == rows.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2735,7 +3095,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2744,7 +3105,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2756,7 +3118,10 @@ class PdfGrid extends PdfLayoutElement { } void _applyListTable4( - PdfColor borderColor, PdfColor headerBackColor, PdfColor bandRowColor) { + PdfColor borderColor, + PdfColor headerBackColor, + PdfColor bandRowColor, + ) { final PdfPen borderColorPen = PdfPen(borderColor, width: 0.5); final PdfBrush headerBrush = PdfSolidBrush(headerBackColor); final PdfBrush bandRowBrush = PdfSolidBrush(bandRowColor); @@ -2775,7 +3140,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = borderColorPen; } if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2785,8 +3151,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.textBrush = PdfSolidBrush(PdfColor(255, 255, 255)); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, bandRowColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + bandRowColor, + j, + ); } if (_bandedRow) { if (i % 2 != 0) { @@ -2794,7 +3163,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2806,7 +3176,8 @@ class PdfGrid extends PdfLayoutElement { if (_bandedRow && i % 2 != 0) { cell.style.backgroundBrush = bandRowBrush; } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2829,23 +3200,36 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = borderColorPen; } if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, bandRowColor, j); - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, bandRowColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + bandRowColor, + j, + ); + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + bandRowColor, + i, + ); } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, bandRowColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + bandRowColor, + j, + ); } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, bandRowColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + bandRowColor, + i, + ); } } if (_totalRow && i == rows.count) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2853,14 +3237,18 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.top = PdfPen(borderColor); if (_bandedColumn) { if (!(_lastColumn && j == rows.count)) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, bandRowColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + bandRowColor, + j, + ); } } } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2870,9 +3258,13 @@ class PdfGrid extends PdfLayoutElement { if (_lastColumn && j == row.cells.count) { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, bandRowColor, i); - final PdfFont font = cell.style.font ?? + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + bandRowColor, + i, + ); + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2902,15 +3294,18 @@ class PdfGrid extends PdfLayoutElement { cell.style.textBrush = whiteBrush; cell.style.backgroundBrush = backColorBrush; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); cell.style.backgroundBrush = backColorBrush; cell.style.textBrush = whiteBrush; - cell.style.borders.bottom = - PdfPen(PdfColor(255, 255, 255), width: 2); + cell.style.borders.bottom = PdfPen( + PdfColor(255, 255, 255), + width: 2, + ); if (_bandedColumn) { if (j > 1) { cell.style.borders.left = whitePen; @@ -2919,7 +3314,8 @@ class PdfGrid extends PdfLayoutElement { } else { if (_firstColumn) { if (j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2937,7 +3333,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.top = whitePen; } if (_lastColumn && j == row.cells.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2959,7 +3356,8 @@ class PdfGrid extends PdfLayoutElement { if (_firstColumn) { if (!(_totalRow && i == rows.count)) { if (j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2978,7 +3376,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.top = whitePen; } if (_totalRow && i == rows.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -2995,7 +3394,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3008,7 +3408,10 @@ class PdfGrid extends PdfLayoutElement { } void _applyListTable6Colorful( - PdfColor borderColor, PdfColor backColor, PdfColor textColor) { + PdfColor borderColor, + PdfColor backColor, + PdfColor textColor, + ) { final PdfBrush backColorBrush = PdfSolidBrush(backColor); final PdfPen borderColorPen = PdfPen(borderColor, width: 0.5); final PdfPen backColorPen = PdfPen(backColor, width: 0.5); @@ -3026,8 +3429,11 @@ class PdfGrid extends PdfLayoutElement { if (_headerRow) { if (_bandedColumn) { if (!(_lastColumn && j == row.cells.count)) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); } if (cell.style.backgroundBrush != null) { cell.style.borders.left = backColorPen; @@ -3035,7 +3441,8 @@ class PdfGrid extends PdfLayoutElement { } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3043,8 +3450,11 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.bottom = borderColorPen; } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { if (i == 1 && _headerRow) { cell.style.borders.top = borderColorPen; @@ -3065,7 +3475,8 @@ class PdfGrid extends PdfLayoutElement { } } if (_firstColumn && j == 1) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3080,7 +3491,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = backColorBrush; } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3106,11 +3518,17 @@ class PdfGrid extends PdfLayoutElement { } if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { if (j == 1) { cell.style.borders.left = backColorPen; @@ -3123,8 +3541,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.left = backColorPen; cell.style.borders.right = backColorPen; @@ -3134,8 +3555,11 @@ class PdfGrid extends PdfLayoutElement { } } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (j == 1) { cell.style.borders.left = backColorPen; } else if (j == row.cells.count) { @@ -3148,7 +3572,8 @@ class PdfGrid extends PdfLayoutElement { } if (_firstColumn && j == 1) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3161,14 +3586,18 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.left = emptyPen; cell.style.borders.right = emptyPen; if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.right = backColorPen; } } - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3184,15 +3613,19 @@ class PdfGrid extends PdfLayoutElement { cell.style.backgroundBrush = null; cell.style.borders.left = emptyPen; cell.style.borders.right = emptyPen; - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; cell.style.font = _changeFontStyle(font); cell.style.borders.top = PdfPen(borderColor); if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; } @@ -3207,7 +3640,10 @@ class PdfGrid extends PdfLayoutElement { } void _applyListTable7Colorful( - PdfColor borderColor, PdfColor backColor, PdfColor textColor) { + PdfColor borderColor, + PdfColor backColor, + PdfColor textColor, + ) { final PdfPen borderPen = PdfPen(borderColor, width: 0.5); final PdfPen emptyPen = PdfPen(PdfColor.empty); final PdfBrush backBrush = PdfSolidBrush(backColor); @@ -3222,7 +3658,8 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.all = emptyPen; cell.style.textBrush = textBrush; if (_headerRow) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3234,8 +3671,11 @@ class PdfGrid extends PdfLayoutElement { } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backColorPen; } @@ -3249,7 +3689,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3258,7 +3699,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3283,25 +3725,37 @@ class PdfGrid extends PdfLayoutElement { cell.style.textBrush = textBrush; if (_bandedColumn && _bandedRow) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); - - cell.style.backgroundBrush ??= - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); + + cell.style.backgroundBrush ??= _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backColorPen; } } else { if (_bandedColumn) { - cell.style.backgroundBrush = - _applyBandedColStyle(_firstColumn, backColor, j); + cell.style.backgroundBrush = _applyBandedColStyle( + _firstColumn, + backColor, + j, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backColorPen; } } if (_bandedRow) { - cell.style.backgroundBrush = - _applyBandedRowStyle(_headerRow, backColor, i); + cell.style.backgroundBrush = _applyBandedRowStyle( + _headerRow, + backColor, + i, + ); if (cell.style.backgroundBrush != null) { cell.style.borders.all = backColorPen; } @@ -3311,7 +3765,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3326,7 +3781,8 @@ class PdfGrid extends PdfLayoutElement { } if (_totalRow && i == rows.count) { - final PdfFont font = cell.style.font ?? + final PdfFont font = + cell.style.font ?? row.style.font ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3340,7 +3796,8 @@ 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 ?? PdfGridRowHelper.getHelper(row).grid.style.font ?? _helper.defaultFont; @@ -3474,11 +3931,13 @@ class PdfGridHelper { (cell.columnSpan > 1 || cell.rowSpan > 1)) { if (cell.columnSpan + j > row.cells.count) { throw ArgumentError.value( - 'Invalid span specified at row $j column $i'); + 'Invalid span specified at row $j column $i', + ); } if (cell.rowSpan + i > base.headers.count) { throw ArgumentError.value( - 'Invalid span specified at row $j column $i'); + 'Invalid span specified at row $j column $i', + ); } if (cell.columnSpan > 1 && cell.rowSpan > 1) { colSpan = cell.columnSpan; @@ -3499,20 +3958,24 @@ class PdfGridHelper { while (rowSpan > 1) { currentRowIndex++; PdfGridCellHelper.getHelper( - base.headers[currentRowIndex].cells[j]) - .isRowMergeContinue = true; + base.headers[currentRowIndex].cells[j], + ).isRowMergeContinue = + true; PdfGridCellHelper.getHelper( - base.headers[currentRowIndex].cells[currentCellIndex]) - .isRowMergeContinue = true; + base.headers[currentRowIndex].cells[currentCellIndex], + ).isRowMergeContinue = + true; rowSpan--; while (colSpan > 1) { currentCellIndex++; PdfGridCellHelper.getHelper( - base.headers[currentRowIndex].cells[currentCellIndex]) - .isCellMergeContinue = true; + base.headers[currentRowIndex].cells[currentCellIndex], + ).isCellMergeContinue = + true; PdfGridCellHelper.getHelper( - base.headers[currentRowIndex].cells[currentCellIndex]) - .isRowMergeContinue = true; + base.headers[currentRowIndex].cells[currentCellIndex], + ).isRowMergeContinue = + true; colSpan--; } colSpan = cell.columnSpan; @@ -3533,8 +3996,9 @@ class PdfGridHelper { while (rowSpan > 1) { currentRowIndex++; PdfGridCellHelper.getHelper( - base.headers[currentRowIndex].cells[j]) - .isRowMergeContinue = true; + base.headers[currentRowIndex].cells[j], + ).isRowMergeContinue = + true; rowSpan--; } } @@ -3556,11 +4020,13 @@ class PdfGridHelper { (cell.columnSpan > 1 || cell.rowSpan > 1)) { if (cell.columnSpan + j > row.cells.count) { throw ArgumentError.value( - 'Invalid span specified at row $j column $i'); + 'Invalid span specified at row $j column $i', + ); } if (cell.rowSpan + i > base.rows.count) { throw ArgumentError.value( - 'Invalid span specified at row $j column $i'); + 'Invalid span specified at row $j column $i', + ); } if (cell.columnSpan > 1 && cell.rowSpan > 1) { colSpan = cell.columnSpan; @@ -3582,19 +4048,24 @@ class PdfGridHelper { PdfGridCellHelper.getHelper(base.rows[currentRowIndex].cells[j]) .isRowMergeContinue = true; PdfGridCellHelper.getHelper( - base.rows[currentRowIndex].cells[currentCellIndex]) - .isRowMergeContinue = true; + base.rows[currentRowIndex].cells[currentCellIndex], + ).isRowMergeContinue = + true; rowSpan--; while (colSpan > 1) { currentCellIndex++; PdfGridCellHelper.getHelper( - PdfGridRowHelper.getHelper(base.rows[currentRowIndex]) - .cells![currentCellIndex]) - .isCellMergeContinue = true; + PdfGridRowHelper.getHelper( + base.rows[currentRowIndex], + ).cells![currentCellIndex], + ).isCellMergeContinue = + true; PdfGridCellHelper.getHelper( - PdfGridRowHelper.getHelper(base.rows[currentRowIndex]) - .cells![currentCellIndex]) - .isRowMergeContinue = true; + PdfGridRowHelper.getHelper( + base.rows[currentRowIndex], + ).cells![currentCellIndex], + ).isRowMergeContinue = + true; colSpan--; } colSpan = cell.columnSpan; @@ -3635,10 +4106,11 @@ class PdfGridHelper { for (int i = 0; i < base.headers[0].cells.count; i++) { for (int j = 0; j < base.headers.count; j++) { cellWidth = max( - cellWidth, - initialWidth > 0.0 - ? min(initialWidth, base.headers[j].cells[i].width) - : base.headers[j].cells[i].width); + cellWidth, + initialWidth > 0.0 + ? min(initialWidth, base.headers[j].cells[i].width) + : base.headers[j].cells[i].width, + ); } widths[i] = cellWidth; } @@ -3646,41 +4118,39 @@ class PdfGridHelper { cellWidth = 0; for (int i = 0; i < base.columns.count; i++) { for (int j = 0; j < base.rows.count; j++) { - final bool isGrid = base.rows[j].cells[i].value != null && + final bool isGrid = + base.rows[j].cells[i].value != null && base.rows[j].cells[i].value is PdfGrid; if ((base.rows[j].cells[i].columnSpan == 1 && - !PdfGridCellHelper.getHelper(base.rows[j].cells[i]) - .isCellMergeContinue) || + !PdfGridCellHelper.getHelper( + base.rows[j].cells[i], + ).isCellMergeContinue) || isGrid) { if (isGrid && - !PdfGridRowHelper.getHelper(base.rows[j]) - .grid - .style - .allowHorizontalOverflow && + !PdfGridRowHelper.getHelper( + base.rows[j], + ).grid.style.allowHorizontalOverflow && initialWidth != 0) { PdfGridHelper.getHelper(base.rows[j].cells[i].value as PdfGrid) - .initialWidth = - initialWidth - - (PdfGridRowHelper.getHelper(base.rows[j]) - .grid - .style - .cellPadding - .right + - PdfGridRowHelper.getHelper(base.rows[j]) - .grid - .style - .cellPadding - .left + - base.rows[j].cells[i].style.borders.left.width / 2 + - base._gridLocation.x); + .initialWidth = initialWidth - + (PdfGridRowHelper.getHelper( + base.rows[j], + ).grid.style.cellPadding.right + + PdfGridRowHelper.getHelper( + base.rows[j], + ).grid.style.cellPadding.left + + base.rows[j].cells[i].style.borders.left.width / 2 + + base._gridLocation.x); } cellWidth = max( - widths[i]!, - max( - cellWidth, - initialWidth > 0.0 - ? min(initialWidth, base.rows[j].cells[i].width) - : base.rows[j].cells[i].width)); + widths[i]!, + max( + cellWidth, + initialWidth > 0.0 + ? min(initialWidth, base.rows[j].cells[i].width) + : base.rows[j].cells[i].width, + ), + ); cellWidth = max(base.columns[i].width, cellWidth); } } @@ -3707,21 +4177,23 @@ class PdfGridHelper { } } if (isChildGrid! && initialWidth != 0) { - widths = PdfGridColumnCollectionHelper.getHelper(base.columns) - .getDefaultWidths(initialWidth); + widths = PdfGridColumnCollectionHelper.getHelper( + base.columns, + ).getDefaultWidths(initialWidth); } for (int i = 0; i < base.columns.count; i++) { if (base.columns[i].width < 0 || (base.columns[i].width > 0 && - !PdfGridColumnHelper.getHelper(base.columns[i]) - .isCustomWidth)) { + !PdfGridColumnHelper.getHelper( + base.columns[i], + ).isCustomWidth)) { PdfGridColumnHelper.getHelper(base.columns[i]).width = widths[i]!; } } } else { - List widths = - PdfGridColumnCollectionHelper.getHelper(base.columns) - .getDefaultWidths(bounds.width - bounds.x); + List widths = PdfGridColumnCollectionHelper.getHelper( + base.columns, + ).getDefaultWidths(bounds.width - bounds.x); for (int i = 0; i < base.columns.count; i++) { if (base.columns[i].width < 0) { PdfGridColumnHelper.getHelper(base.columns[i]).width = widths[i]!; @@ -3735,36 +4207,29 @@ class PdfGridHelper { if (parentCell != null && (!base.style.allowHorizontalOverflow) && (!PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(parentCell!).row!) - .grid - .style - .allowHorizontalOverflow)) { + PdfGridCellHelper.getHelper(parentCell!).row!, + ).grid.style.allowHorizontalOverflow)) { double padding = 0; double columnWidth = 0; int columnCount = base.columns.count; if (parentCell!.style.cellPadding != null) { - padding += parentCell!.style.cellPadding!.left + + padding += + parentCell!.style.cellPadding!.left + parentCell!.style.cellPadding!.right; } else { - padding += PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(parentCell!).row!) - .grid - .style - .cellPadding - .left + + padding += + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(parentCell!).row!, + ).grid.style.cellPadding.left + PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(parentCell!).row!) - .grid - .style - .cellPadding - .right; + PdfGridCellHelper.getHelper(parentCell!).row!, + ).grid.style.cellPadding.right; } for (int i = 0; i < parentCell!.columnSpan; i++) { - columnWidth += PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(parentCell!).row!) - .grid - .columns[parentCellIndex + i] - .width; + columnWidth += + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(parentCell!).row!, + ).grid.columns[parentCellIndex + i].width; } for (int i = 0; i < base.columns.count; i++) { if (base.columns[i].width > 0 && @@ -3779,8 +4244,9 @@ class PdfGridHelper { if (parentCell != null && parentCell!.stringFormat.alignment != PdfTextAlignment.right) { for (int j = 0; j < base.columns.count; j++) { - if (!PdfGridColumnHelper.getHelper(base.columns[j]) - .isCustomWidth) { + if (!PdfGridColumnHelper.getHelper( + base.columns[j], + ).isCustomWidth) { PdfGridColumnHelper.getHelper(base.columns[j]).width = childGridColumnWidth; } @@ -3790,16 +4256,17 @@ class PdfGridHelper { } if (parentCell != null && PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(parentCell!).row!) - .getWidth() > + PdfGridCellHelper.getHelper(parentCell!).row!, + ).getWidth() > 0) { if (isChildGrid! && size.width > PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(parentCell!).row!) - .getWidth()) { - widths = PdfGridColumnCollectionHelper.getHelper(base.columns) - .getDefaultWidths(bounds.width); + PdfGridCellHelper.getHelper(parentCell!).row!, + ).getWidth()) { + widths = PdfGridColumnCollectionHelper.getHelper( + base.columns, + ).getDefaultWidths(bounds.width); for (int i = 0; i < base.columns.count; i++) { base.columns[i].width = widths[i]!; } @@ -3865,7 +4332,9 @@ class PdfGridHelper { case PdfGridBuiltInStyle.plainTable1: base._applyPlainTable1( - PdfColor(191, 191, 191), PdfColor(242, 242, 242)); + PdfColor(191, 191, 191), + PdfColor(242, 242, 242), + ); break; case PdfGridBuiltInStyle.plainTable2: @@ -3874,7 +4343,9 @@ class PdfGridHelper { case PdfGridBuiltInStyle.plainTable3: base._applyPlainTable3( - PdfColor(127, 127, 127), PdfColor(242, 242, 242)); + PdfColor(127, 127, 127), + PdfColor(242, 242, 242), + ); break; case PdfGridBuiltInStyle.plainTable4: @@ -3883,41 +4354,57 @@ class PdfGridHelper { case PdfGridBuiltInStyle.plainTable5: base._applyPlainTable5( - PdfColor(127, 127, 127), PdfColor(242, 242, 242)); + PdfColor(127, 127, 127), + PdfColor(242, 242, 242), + ); break; case PdfGridBuiltInStyle.gridTable1Light: base._applyGridTable1Light( - PdfColor(153, 153, 153), PdfColor(102, 102, 102)); + PdfColor(153, 153, 153), + PdfColor(102, 102, 102), + ); break; case PdfGridBuiltInStyle.gridTable1LightAccent1: base._applyGridTable1Light( - PdfColor(189, 214, 238), PdfColor(156, 194, 229)); + PdfColor(189, 214, 238), + PdfColor(156, 194, 229), + ); break; case PdfGridBuiltInStyle.gridTable1LightAccent2: base._applyGridTable1Light( - PdfColor(247, 202, 172), PdfColor(244, 176, 131)); + PdfColor(247, 202, 172), + PdfColor(244, 176, 131), + ); break; case PdfGridBuiltInStyle.gridTable1LightAccent3: base._applyGridTable1Light( - PdfColor(219, 219, 219), PdfColor(201, 201, 201)); + PdfColor(219, 219, 219), + PdfColor(201, 201, 201), + ); break; case PdfGridBuiltInStyle.gridTable1LightAccent4: base._applyGridTable1Light( - PdfColor(255, 229, 153), PdfColor(255, 217, 102)); + PdfColor(255, 229, 153), + PdfColor(255, 217, 102), + ); break; case PdfGridBuiltInStyle.gridTable1LightAccent5: base._applyGridTable1Light( - PdfColor(180, 198, 231), PdfColor(142, 170, 219)); + PdfColor(180, 198, 231), + PdfColor(142, 170, 219), + ); break; case PdfGridBuiltInStyle.gridTable1LightAccent6: base._applyGridTable1Light( - PdfColor(192, 224, 179), PdfColor(168, 208, 141)); + PdfColor(192, 224, 179), + PdfColor(168, 208, 141), + ); break; case PdfGridBuiltInStyle.gridTable2: @@ -3977,177 +4464,275 @@ class PdfGridHelper { break; case PdfGridBuiltInStyle.gridTable4: - base._applyGridTable4(PdfColor(102, 102, 102), PdfColor(204, 204, 204), - PdfColor(0, 0, 0)); + base._applyGridTable4( + PdfColor(102, 102, 102), + PdfColor(204, 204, 204), + PdfColor(0, 0, 0), + ); break; case PdfGridBuiltInStyle.gridTable4Accent1: - base._applyGridTable4(PdfColor(156, 194, 229), PdfColor(222, 234, 246), - PdfColor(91, 155, 213)); + base._applyGridTable4( + PdfColor(156, 194, 229), + PdfColor(222, 234, 246), + PdfColor(91, 155, 213), + ); break; case PdfGridBuiltInStyle.gridTable4Accent2: - base._applyGridTable4(PdfColor(244, 176, 131), PdfColor(251, 228, 213), - PdfColor(237, 125, 49)); + base._applyGridTable4( + PdfColor(244, 176, 131), + PdfColor(251, 228, 213), + PdfColor(237, 125, 49), + ); break; case PdfGridBuiltInStyle.gridTable4Accent3: - base._applyGridTable4(PdfColor(201, 201, 201), PdfColor(237, 237, 237), - PdfColor(165, 165, 165)); + base._applyGridTable4( + PdfColor(201, 201, 201), + PdfColor(237, 237, 237), + PdfColor(165, 165, 165), + ); break; case PdfGridBuiltInStyle.gridTable4Accent4: - base._applyGridTable4(PdfColor(255, 217, 102), PdfColor(255, 242, 204), - PdfColor(255, 192, 0)); + base._applyGridTable4( + PdfColor(255, 217, 102), + PdfColor(255, 242, 204), + PdfColor(255, 192, 0), + ); break; case PdfGridBuiltInStyle.gridTable4Accent5: - base._applyGridTable4(PdfColor(142, 170, 219), PdfColor(217, 226, 243), - PdfColor(68, 114, 196)); + base._applyGridTable4( + PdfColor(142, 170, 219), + PdfColor(217, 226, 243), + PdfColor(68, 114, 196), + ); break; case PdfGridBuiltInStyle.gridTable4Accent6: - base._applyGridTable4(PdfColor(168, 208, 141), PdfColor(226, 239, 217), - PdfColor(112, 173, 71)); + base._applyGridTable4( + PdfColor(168, 208, 141), + PdfColor(226, 239, 217), + PdfColor(112, 173, 71), + ); break; case PdfGridBuiltInStyle.gridTable5Dark: - base._applyGridTable5Dark(PdfColor(0, 0, 0), PdfColor(153, 153, 153), - PdfColor(204, 204, 204)); + base._applyGridTable5Dark( + PdfColor(0, 0, 0), + PdfColor(153, 153, 153), + PdfColor(204, 204, 204), + ); break; case PdfGridBuiltInStyle.gridTable5DarkAccent1: - base._applyGridTable5Dark(PdfColor(91, 155, 213), - PdfColor(189, 214, 238), PdfColor(222, 234, 246)); + base._applyGridTable5Dark( + PdfColor(91, 155, 213), + PdfColor(189, 214, 238), + PdfColor(222, 234, 246), + ); break; case PdfGridBuiltInStyle.gridTable5DarkAccent2: - base._applyGridTable5Dark(PdfColor(237, 125, 49), - PdfColor(247, 202, 172), PdfColor(251, 228, 213)); + base._applyGridTable5Dark( + PdfColor(237, 125, 49), + PdfColor(247, 202, 172), + PdfColor(251, 228, 213), + ); break; case PdfGridBuiltInStyle.gridTable5DarkAccent3: - base._applyGridTable5Dark(PdfColor(165, 165, 165), - PdfColor(219, 219, 219), PdfColor(237, 237, 237)); + base._applyGridTable5Dark( + PdfColor(165, 165, 165), + PdfColor(219, 219, 219), + PdfColor(237, 237, 237), + ); break; case PdfGridBuiltInStyle.gridTable5DarkAccent4: - base._applyGridTable5Dark(PdfColor(255, 192, 0), - PdfColor(255, 229, 153), PdfColor(255, 242, 204)); + base._applyGridTable5Dark( + PdfColor(255, 192, 0), + PdfColor(255, 229, 153), + PdfColor(255, 242, 204), + ); break; case PdfGridBuiltInStyle.gridTable5DarkAccent5: - base._applyGridTable5Dark(PdfColor(68, 114, 196), - PdfColor(180, 198, 231), PdfColor(217, 226, 243)); + base._applyGridTable5Dark( + PdfColor(68, 114, 196), + PdfColor(180, 198, 231), + PdfColor(217, 226, 243), + ); break; case PdfGridBuiltInStyle.gridTable5DarkAccent6: - base._applyGridTable5Dark(PdfColor(112, 171, 71), - PdfColor(197, 224, 179), PdfColor(226, 239, 217)); + base._applyGridTable5Dark( + PdfColor(112, 171, 71), + PdfColor(197, 224, 179), + PdfColor(226, 239, 217), + ); break; case PdfGridBuiltInStyle.gridTable6Colorful: - base._applyGridTable6Colorful(PdfColor(102, 102, 102), - PdfColor(204, 204, 204), PdfColor(0, 0, 0)); + base._applyGridTable6Colorful( + PdfColor(102, 102, 102), + PdfColor(204, 204, 204), + PdfColor(0, 0, 0), + ); break; case PdfGridBuiltInStyle.gridTable6ColorfulAccent1: - base._applyGridTable6Colorful(PdfColor(156, 194, 229), - PdfColor(222, 234, 246), PdfColor(46, 116, 181)); + base._applyGridTable6Colorful( + PdfColor(156, 194, 229), + PdfColor(222, 234, 246), + PdfColor(46, 116, 181), + ); break; case PdfGridBuiltInStyle.gridTable6ColorfulAccent2: - base._applyGridTable6Colorful(PdfColor(244, 176, 131), - PdfColor(251, 228, 213), PdfColor(196, 89, 17)); + base._applyGridTable6Colorful( + PdfColor(244, 176, 131), + PdfColor(251, 228, 213), + PdfColor(196, 89, 17), + ); break; case PdfGridBuiltInStyle.gridTable6ColorfulAccent3: - base._applyGridTable6Colorful(PdfColor(201, 201, 201), - PdfColor(237, 237, 237), PdfColor(123, 123, 123)); + base._applyGridTable6Colorful( + PdfColor(201, 201, 201), + PdfColor(237, 237, 237), + PdfColor(123, 123, 123), + ); break; case PdfGridBuiltInStyle.gridTable6ColorfulAccent4: - base._applyGridTable6Colorful(PdfColor(255, 217, 102), - PdfColor(255, 242, 204), PdfColor(191, 143, 0)); + base._applyGridTable6Colorful( + PdfColor(255, 217, 102), + PdfColor(255, 242, 204), + PdfColor(191, 143, 0), + ); break; case PdfGridBuiltInStyle.gridTable6ColorfulAccent5: - base._applyGridTable6Colorful(PdfColor(142, 170, 219), - PdfColor(217, 226, 243), PdfColor(47, 84, 150)); + base._applyGridTable6Colorful( + PdfColor(142, 170, 219), + PdfColor(217, 226, 243), + PdfColor(47, 84, 150), + ); break; case PdfGridBuiltInStyle.gridTable6ColorfulAccent6: - base._applyGridTable6Colorful(PdfColor(168, 208, 141), - PdfColor(226, 239, 217), PdfColor(83, 129, 53)); + base._applyGridTable6Colorful( + PdfColor(168, 208, 141), + PdfColor(226, 239, 217), + PdfColor(83, 129, 53), + ); break; case PdfGridBuiltInStyle.gridTable7Colorful: - base._applyGridTable7Colorful(PdfColor(102, 102, 102), - PdfColor(204, 204, 204), PdfColor(0, 0, 0)); + base._applyGridTable7Colorful( + PdfColor(102, 102, 102), + PdfColor(204, 204, 204), + PdfColor(0, 0, 0), + ); break; case PdfGridBuiltInStyle.gridTable7ColorfulAccent1: - base._applyGridTable7Colorful(PdfColor(156, 194, 229), - PdfColor(222, 234, 246), PdfColor(46, 116, 181)); + base._applyGridTable7Colorful( + PdfColor(156, 194, 229), + PdfColor(222, 234, 246), + PdfColor(46, 116, 181), + ); break; case PdfGridBuiltInStyle.gridTable7ColorfulAccent2: - base._applyGridTable7Colorful(PdfColor(244, 176, 131), - PdfColor(251, 228, 213), PdfColor(196, 89, 17)); + base._applyGridTable7Colorful( + PdfColor(244, 176, 131), + PdfColor(251, 228, 213), + PdfColor(196, 89, 17), + ); break; case PdfGridBuiltInStyle.gridTable7ColorfulAccent3: - base._applyGridTable7Colorful(PdfColor(201, 201, 201), - PdfColor(237, 237, 237), PdfColor(123, 123, 123)); + base._applyGridTable7Colorful( + PdfColor(201, 201, 201), + PdfColor(237, 237, 237), + PdfColor(123, 123, 123), + ); break; case PdfGridBuiltInStyle.gridTable7ColorfulAccent4: - base._applyGridTable7Colorful(PdfColor(255, 217, 102), - PdfColor(255, 242, 204), PdfColor(191, 143, 0)); + base._applyGridTable7Colorful( + PdfColor(255, 217, 102), + PdfColor(255, 242, 204), + PdfColor(191, 143, 0), + ); break; case PdfGridBuiltInStyle.gridTable7ColorfulAccent5: - base._applyGridTable7Colorful(PdfColor(142, 170, 219), - PdfColor(217, 226, 243), PdfColor(47, 84, 150)); + base._applyGridTable7Colorful( + PdfColor(142, 170, 219), + PdfColor(217, 226, 243), + PdfColor(47, 84, 150), + ); break; case PdfGridBuiltInStyle.gridTable7ColorfulAccent6: - base._applyGridTable7Colorful(PdfColor(168, 208, 141), - PdfColor(226, 239, 217), PdfColor(83, 129, 53)); + base._applyGridTable7Colorful( + PdfColor(168, 208, 141), + PdfColor(226, 239, 217), + PdfColor(83, 129, 53), + ); break; case PdfGridBuiltInStyle.listTable1Light: base._applyListTable1Light( - PdfColor(102, 102, 102), PdfColor(204, 204, 204)); + PdfColor(102, 102, 102), + PdfColor(204, 204, 204), + ); break; case PdfGridBuiltInStyle.listTable1LightAccent1: base._applyListTable1Light( - PdfColor(156, 194, 229), PdfColor(222, 234, 246)); + PdfColor(156, 194, 229), + PdfColor(222, 234, 246), + ); break; case PdfGridBuiltInStyle.listTable1LightAccent2: base._applyListTable1Light( - PdfColor(244, 176, 131), PdfColor(251, 228, 213)); + PdfColor(244, 176, 131), + PdfColor(251, 228, 213), + ); break; case PdfGridBuiltInStyle.listTable1LightAccent3: base._applyListTable1Light( - PdfColor(201, 201, 201), PdfColor(237, 237, 237)); + PdfColor(201, 201, 201), + PdfColor(237, 237, 237), + ); break; case PdfGridBuiltInStyle.listTable1LightAccent4: base._applyListTable1Light( - PdfColor(255, 217, 102), PdfColor(255, 242, 204)); + PdfColor(255, 217, 102), + PdfColor(255, 242, 204), + ); break; case PdfGridBuiltInStyle.listTable1LightAccent5: base._applyListTable1Light( - PdfColor(142, 170, 219), PdfColor(217, 226, 243)); + PdfColor(142, 170, 219), + PdfColor(217, 226, 243), + ); break; case PdfGridBuiltInStyle.listTable1LightAccent6: base._applyListTable1Light( - PdfColor(168, 208, 141), PdfColor(226, 239, 217)); + PdfColor(168, 208, 141), + PdfColor(226, 239, 217), + ); break; case PdfGridBuiltInStyle.listTable2: @@ -4207,38 +4792,59 @@ class PdfGridHelper { break; case PdfGridBuiltInStyle.listTable4: - base._applyListTable4(PdfColor(102, 102, 102), PdfColor(0, 0, 0), - PdfColor(204, 204, 204)); + base._applyListTable4( + PdfColor(102, 102, 102), + PdfColor(0, 0, 0), + PdfColor(204, 204, 204), + ); break; case PdfGridBuiltInStyle.listTable4Accent1: - base._applyListTable4(PdfColor(156, 194, 229), PdfColor(91, 155, 213), - PdfColor(222, 234, 246)); + base._applyListTable4( + PdfColor(156, 194, 229), + PdfColor(91, 155, 213), + PdfColor(222, 234, 246), + ); break; case PdfGridBuiltInStyle.listTable4Accent2: - base._applyListTable4(PdfColor(244, 176, 131), PdfColor(237, 125, 49), - PdfColor(251, 228, 213)); + base._applyListTable4( + PdfColor(244, 176, 131), + PdfColor(237, 125, 49), + PdfColor(251, 228, 213), + ); break; case PdfGridBuiltInStyle.listTable4Accent3: - base._applyListTable4(PdfColor(201, 201, 201), PdfColor(165, 165, 165), - PdfColor(237, 237, 237)); + base._applyListTable4( + PdfColor(201, 201, 201), + PdfColor(165, 165, 165), + PdfColor(237, 237, 237), + ); break; case PdfGridBuiltInStyle.listTable4Accent4: - base._applyListTable4(PdfColor(255, 217, 102), PdfColor(255, 192, 0), - PdfColor(255, 242, 204)); + base._applyListTable4( + PdfColor(255, 217, 102), + PdfColor(255, 192, 0), + PdfColor(255, 242, 204), + ); break; case PdfGridBuiltInStyle.listTable4Accent5: - base._applyListTable4(PdfColor(142, 170, 219), PdfColor(68, 114, 196), - PdfColor(217, 226, 243)); + base._applyListTable4( + PdfColor(142, 170, 219), + PdfColor(68, 114, 196), + PdfColor(217, 226, 243), + ); break; case PdfGridBuiltInStyle.listTable4Accent6: - base._applyListTable4(PdfColor(168, 208, 141), PdfColor(112, 173, 71), - PdfColor(226, 239, 217)); + base._applyListTable4( + PdfColor(168, 208, 141), + PdfColor(112, 173, 71), + PdfColor(226, 239, 217), + ); break; case PdfGridBuiltInStyle.listTable5Dark: @@ -4270,100 +4876,143 @@ class PdfGridHelper { break; case PdfGridBuiltInStyle.listTable6Colorful: - base._applyListTable6Colorful(PdfColor(102, 102, 102), - PdfColor(204, 204, 204), PdfColor(0, 0, 0)); + base._applyListTable6Colorful( + PdfColor(102, 102, 102), + PdfColor(204, 204, 204), + PdfColor(0, 0, 0), + ); break; case PdfGridBuiltInStyle.listTable6ColorfulAccent1: - base._applyListTable6Colorful(PdfColor(91, 155, 213), - PdfColor(222, 234, 246), PdfColor(46, 116, 181)); + base._applyListTable6Colorful( + PdfColor(91, 155, 213), + PdfColor(222, 234, 246), + PdfColor(46, 116, 181), + ); break; case PdfGridBuiltInStyle.listTable6ColorfulAccent2: - base._applyListTable6Colorful(PdfColor(237, 125, 49), - PdfColor(251, 228, 213), PdfColor(196, 89, 17)); + base._applyListTable6Colorful( + PdfColor(237, 125, 49), + PdfColor(251, 228, 213), + PdfColor(196, 89, 17), + ); break; case PdfGridBuiltInStyle.listTable6ColorfulAccent3: - base._applyListTable6Colorful(PdfColor(165, 165, 165), - PdfColor(237, 237, 237), PdfColor(123, 123, 123)); + base._applyListTable6Colorful( + PdfColor(165, 165, 165), + PdfColor(237, 237, 237), + PdfColor(123, 123, 123), + ); break; case PdfGridBuiltInStyle.listTable6ColorfulAccent4: - base._applyListTable6Colorful(PdfColor(255, 192, 0), - PdfColor(255, 242, 204), PdfColor(191, 143, 0)); + base._applyListTable6Colorful( + PdfColor(255, 192, 0), + PdfColor(255, 242, 204), + PdfColor(191, 143, 0), + ); break; case PdfGridBuiltInStyle.listTable6ColorfulAccent5: - base._applyListTable6Colorful(PdfColor(68, 114, 196), - PdfColor(217, 226, 243), PdfColor(47, 84, 150)); + base._applyListTable6Colorful( + PdfColor(68, 114, 196), + PdfColor(217, 226, 243), + PdfColor(47, 84, 150), + ); break; case PdfGridBuiltInStyle.listTable6ColorfulAccent6: - base._applyListTable6Colorful(PdfColor(112, 173, 71), - PdfColor(226, 239, 217), PdfColor(83, 129, 53)); + base._applyListTable6Colorful( + PdfColor(112, 173, 71), + PdfColor(226, 239, 217), + PdfColor(83, 129, 53), + ); break; case PdfGridBuiltInStyle.listTable7Colorful: - base._applyListTable7Colorful(PdfColor(102, 102, 102), - PdfColor(204, 204, 204), PdfColor(0, 0, 0)); + base._applyListTable7Colorful( + PdfColor(102, 102, 102), + PdfColor(204, 204, 204), + PdfColor(0, 0, 0), + ); break; case PdfGridBuiltInStyle.listTable7ColorfulAccent1: - base._applyListTable7Colorful(PdfColor(91, 155, 213), - PdfColor(222, 234, 246), PdfColor(46, 116, 181)); + base._applyListTable7Colorful( + PdfColor(91, 155, 213), + PdfColor(222, 234, 246), + PdfColor(46, 116, 181), + ); break; case PdfGridBuiltInStyle.listTable7ColorfulAccent2: - base._applyListTable7Colorful(PdfColor(237, 125, 49), - PdfColor(251, 228, 213), PdfColor(196, 89, 17)); + base._applyListTable7Colorful( + PdfColor(237, 125, 49), + PdfColor(251, 228, 213), + PdfColor(196, 89, 17), + ); break; case PdfGridBuiltInStyle.listTable7ColorfulAccent3: - base._applyListTable7Colorful(PdfColor(165, 165, 165), - PdfColor(237, 237, 237), PdfColor(123, 123, 123)); + base._applyListTable7Colorful( + PdfColor(165, 165, 165), + PdfColor(237, 237, 237), + PdfColor(123, 123, 123), + ); break; case PdfGridBuiltInStyle.listTable7ColorfulAccent4: - base._applyListTable7Colorful(PdfColor(255, 192, 0), - PdfColor(255, 242, 204), PdfColor(191, 143, 0)); + base._applyListTable7Colorful( + PdfColor(255, 192, 0), + PdfColor(255, 242, 204), + PdfColor(191, 143, 0), + ); break; case PdfGridBuiltInStyle.listTable7ColorfulAccent5: - base._applyListTable7Colorful(PdfColor(68, 114, 196), - PdfColor(217, 226, 243), PdfColor(47, 84, 150)); + base._applyListTable7Colorful( + PdfColor(68, 114, 196), + PdfColor(217, 226, 243), + PdfColor(47, 84, 150), + ); break; case PdfGridBuiltInStyle.listTable7ColorfulAccent6: - base._applyListTable7Colorful(PdfColor(112, 173, 71), - PdfColor(226, 239, 217), PdfColor(83, 129, 53)); + base._applyListTable7Colorful( + PdfColor(112, 173, 71), + PdfColor(226, 239, 217), + PdfColor(83, 129, 53), + ); break; + // ignore: no_default_cases default: } } } /// Delegate for handling StartCellLayoutEvent. -typedef PdfGridBeginCellLayoutCallback = void Function( - Object sender, PdfGridBeginCellLayoutArgs args); +typedef PdfGridBeginCellLayoutCallback = + void Function(Object sender, PdfGridBeginCellLayoutArgs args); /// Delegate for handling EndCellLayoutEvent. -typedef PdfGridEndCellLayoutCallback = void Function( - Object sender, PdfGridEndCellLayoutArgs args); +typedef PdfGridEndCellLayoutCallback = + void Function(Object sender, PdfGridEndCellLayoutArgs args); /// Represents arguments of StartCellLayout Event. class PdfGridBeginCellLayoutArgs extends GridCellLayoutArgs { //Constructor PdfGridBeginCellLayoutArgs._( - PdfGraphics graphics, - int rowIndex, - int cellInder, - PdfRectangle bounds, - String value, - PdfGridCellStyle? style, - bool isHeaderRow) - : super._(graphics, rowIndex, cellInder, bounds, value, isHeaderRow) { + PdfGraphics graphics, + int rowIndex, + int cellInder, + PdfRectangle bounds, + String value, + PdfGridCellStyle? style, + bool isHeaderRow, + ) : super._(graphics, rowIndex, cellInder, bounds, value, isHeaderRow) { if (style != null) { this.style = style; } @@ -4406,7 +5055,7 @@ class PdfGridBeginCellLayoutArgs extends GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4421,15 +5070,23 @@ class PdfGridBeginCellLayoutArgs extends GridCellLayoutArgs { class PdfGridBeginCellLayoutArgsHelper { /// internal method static PdfGridBeginCellLayoutArgs load( - PdfGraphics graphics, - int rowIndex, - int cellInder, - PdfRectangle bounds, - String value, - PdfGridCellStyle? style, - bool isHeaderRow) { + PdfGraphics graphics, + int rowIndex, + int cellInder, + PdfRectangle bounds, + String value, + PdfGridCellStyle? style, + bool isHeaderRow, + ) { return PdfGridBeginCellLayoutArgs._( - graphics, rowIndex, cellInder, bounds, value, style, isHeaderRow); + graphics, + rowIndex, + cellInder, + bounds, + value, + style, + isHeaderRow, + ); } } @@ -4437,14 +5094,14 @@ class PdfGridBeginCellLayoutArgsHelper { class PdfGridEndCellLayoutArgs extends GridCellLayoutArgs { //Constructor PdfGridEndCellLayoutArgs._( - PdfGraphics graphics, - int rowIndex, - int cellInder, - PdfRectangle bounds, - String value, - PdfGridCellStyle? style, - bool isHeaderRow) - : super._(graphics, rowIndex, cellInder, bounds, value, isHeaderRow) { + PdfGraphics graphics, + int rowIndex, + int cellInder, + PdfRectangle bounds, + String value, + PdfGridCellStyle? style, + bool isHeaderRow, + ) : super._(graphics, rowIndex, cellInder, bounds, value, isHeaderRow) { if (style != null) { this.style = style; } @@ -4459,15 +5116,23 @@ class PdfGridEndCellLayoutArgs extends GridCellLayoutArgs { class PdfGridEndCellLayoutArgsHelper { /// internal method static PdfGridEndCellLayoutArgs load( - PdfGraphics graphics, - int rowIndex, - int cellInder, - PdfRectangle bounds, - String value, - PdfGridCellStyle? style, - bool isHeaderRow) { + PdfGraphics graphics, + int rowIndex, + int cellInder, + PdfRectangle bounds, + String value, + PdfGridCellStyle? style, + bool isHeaderRow, + ) { return PdfGridEndCellLayoutArgs._( - graphics, rowIndex, cellInder, bounds, value, style, isHeaderRow); + graphics, + rowIndex, + cellInder, + bounds, + value, + style, + isHeaderRow, + ); } } @@ -4475,8 +5140,14 @@ class PdfGridEndCellLayoutArgsHelper { abstract class GridCellLayoutArgs { //Constructors /// Initializes a new instance of the [StartCellLayoutArgs] class. - GridCellLayoutArgs._(PdfGraphics graphics, int rowIndex, int cellIndex, - PdfRectangle bounds, String value, bool isHeaderRow) { + GridCellLayoutArgs._( + PdfGraphics graphics, + int rowIndex, + int cellIndex, + PdfRectangle bounds, + String value, + bool isHeaderRow, + ) { _rowIndex = rowIndex; _cellIndex = cellIndex; _value = value; @@ -4539,7 +5210,7 @@ abstract class GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4590,7 +5261,7 @@ abstract class GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4641,7 +5312,7 @@ abstract class GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4692,7 +5363,7 @@ abstract class GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4743,7 +5414,7 @@ abstract class GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4794,7 +5465,7 @@ abstract class GridCellLayoutArgs { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4804,8 +5475,7 @@ abstract class GridCellLayoutArgs { /// Arguments of BeginPageLayoutEvent. class PdfGridBeginPageLayoutArgs extends BeginPageLayoutArgs { //Constructor - PdfGridBeginPageLayoutArgs._(Rect bounds, PdfPage page, int? startRow) - : super(bounds, page) { + PdfGridBeginPageLayoutArgs._(super.bounds, super.page, int? startRow) { startRowIndex = startRow ?? 0; } @@ -4819,7 +5489,10 @@ class PdfGridBeginPageLayoutArgs extends BeginPageLayoutArgs { class PdfGridBeginPageLayoutArgsHelper { /// internal method static PdfGridBeginPageLayoutArgs load( - Rect bounds, PdfPage page, int? startRow) { + Rect bounds, + PdfPage page, + int? startRow, + ) { return PdfGridBeginPageLayoutArgs._(bounds, page, startRow); } } @@ -4827,7 +5500,7 @@ class PdfGridBeginPageLayoutArgsHelper { /// Arguments of EndPageLayoutEvent. class PdfGridEndPageLayoutArgs extends EndPageLayoutArgs { //Constructor - PdfGridEndPageLayoutArgs._(PdfLayoutResult result) : super(result); + PdfGridEndPageLayoutArgs._(super.result); } // ignore: avoid_classes_with_only_static_members @@ -4843,13 +5516,14 @@ class PdfGridEndPageLayoutArgsHelper { class PdfGridBuiltInStyleSettings { // Constructor /// Represents the grid built-in style settings. - PdfGridBuiltInStyleSettings( - {this.applyStyleForBandedColumns = false, - this.applyStyleForBandedRows = true, - this.applyStyleForFirstColumn = false, - this.applyStyleForHeaderRow = true, - this.applyStyleForLastColumn = false, - this.applyStyleForLastRow = false}); + PdfGridBuiltInStyleSettings({ + this.applyStyleForBandedColumns = false, + this.applyStyleForBandedRows = true, + this.applyStyleForFirstColumn = false, + this.applyStyleForHeaderRow = true, + this.applyStyleForLastColumn = false, + this.applyStyleForLastRow = false, + }); //Fields /// Gets or sets a value indicating whether to apply style bands to the columns in a table. @@ -4887,7 +5561,7 @@ class PdfGridBuiltInStyleSettings { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4928,7 +5602,7 @@ class PdfGridBuiltInStyleSettings { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -4969,7 +5643,7 @@ class PdfGridBuiltInStyleSettings { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -5010,7 +5684,7 @@ class PdfGridBuiltInStyleSettings { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -5051,7 +5725,7 @@ class PdfGridBuiltInStyleSettings { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -5092,7 +5766,7 @@ class PdfGridBuiltInStyleSettings { /// grid.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 51be26188..cfeccca84 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 @@ -67,7 +67,7 @@ import 'styles/style.dart'; /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -114,16 +114,17 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfGridCell( - {PdfGridCellStyle? style, - PdfStringFormat? format, - PdfGridRow? row, - int? rowSpan, - int? columnSpan}) { + PdfGridCell({ + PdfGridCellStyle? style, + PdfStringFormat? format, + PdfGridRow? row, + int? rowSpan, + int? columnSpan, + }) { _helper = PdfGridCellHelper(this); _initialize(style, format, row, rowSpan, columnSpan); } @@ -195,14 +196,15 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` double get width { if (_width == -1 || - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(_helper.row!).grid) - .isComplete) { + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(_helper.row!).grid, + ).isComplete) { _width = _measureWidth(); } return double.parse(_width.toStringAsFixed(4)); @@ -255,7 +257,7 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -309,15 +311,18 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` int get rowSpan => _rowSpan; set rowSpan(int value) { if (value < 1) { - throw ArgumentError.value('value', 'row span', - 'Invalid span specified, must be greater than or equal to 1'); + throw ArgumentError.value( + 'value', + 'row span', + 'Invalid span specified, must be greater than or equal to 1', + ); } if (value > 1) { _rowSpan = value; @@ -378,15 +383,18 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` int get columnSpan => _columnSpan; set columnSpan(int value) { if (value < 1) { - throw ArgumentError.value('value', 'column span', - 'Invalid span specified, must be greater than or equal to 1'); + throw ArgumentError.value( + 'value', + 'column span', + 'Invalid span specified, must be greater than or equal to 1', + ); } if (value > 1) { _columnSpan = value; @@ -451,7 +459,7 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -499,7 +507,7 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -551,7 +559,7 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -593,7 +601,7 @@ class PdfGridCell { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -605,8 +613,13 @@ class PdfGridCell { } //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) { _helper.row = row; } @@ -664,8 +677,9 @@ class PdfGridCell { return style.font ?? _helper.row!.style.font ?? PdfGridRowHelper.getHelper(_helper.row!).grid.style.font ?? - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(_helper.row!).grid) - .defaultFont; + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(_helper.row!).grid, + ).defaultFont; } PdfBrush? _getTextBrush() { @@ -696,8 +710,12 @@ class PdfGridCell { defaultWidth = _getColumnWidth(); } final PdfStringLayoutResult result = layouter.layout( - value, _getTextFont()!, stringFormat, - width: defaultWidth, height: _maxValue); + value, + _getTextFont()!, + stringFormat, + width: defaultWidth, + height: _maxValue, + ); width += result.size.width; width += (style.borders.left.width + style.borders.right.width) * 2; } else if (value is PdfGrid) { @@ -710,14 +728,19 @@ class PdfGridCell { final PdfTextElement element = value as PdfTextElement; String? temp = element.text; if (!_helper.finished) { - temp = (_helper.remainingString != null && - _helper.remainingString!.isNotEmpty) - ? _helper.remainingString - : value as String; + temp = + (_helper.remainingString != null && + _helper.remainingString!.isNotEmpty) + ? _helper.remainingString + : value as String; } final PdfStringLayoutResult result = layouter.layout( - temp!, element.font, element.stringFormat ?? stringFormat, - width: defaultWidth, height: _maxValue); + temp!, + element.font, + element.stringFormat ?? stringFormat, + width: defaultWidth, + height: _maxValue, + ); width += result.size.width; width += (style.borders.left.width + style.borders.right.width) * 2; } @@ -725,22 +748,18 @@ class PdfGridCell { PdfGridRowHelper.getHelper(_helper.row!).grid.style.cellSpacing + (style.cellPadding != null ? (style.cellPadding!.left + style.cellPadding!.right) - : (PdfGridRowHelper.getHelper(_helper.row!) - .grid - .style - .cellPadding - .left + - PdfGridRowHelper.getHelper(_helper.row!) - .grid - .style - .cellPadding - .right)); + : (PdfGridRowHelper.getHelper( + _helper.row!, + ).grid.style.cellPadding.left + + PdfGridRowHelper.getHelper( + _helper.row!, + ).grid.style.cellPadding.right)); } double _getColumnWidth() { double defaultWidth = PdfGridCellHelper.getHelper(_parent!)._calculateWidth()! / - PdfGridRowHelper.getHelper(_helper.row!).grid.columns.count; + PdfGridRowHelper.getHelper(_helper.row!).grid.columns.count; if (defaultWidth <= 0) { defaultWidth = _maxValue; } @@ -787,7 +806,8 @@ class PdfGridCellHelper { /// internal method double measureHeight() { - final double width = _calculateWidth()! - + final double width = + _calculateWidth()! - (base.style.cellPadding == null ? (PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.right + PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.left) @@ -802,27 +822,39 @@ class PdfGridCellHelper { final PdfTextElement element = base.value as PdfTextElement; String? temp = element.text; if (!finished) { - temp = (remainingString != null && remainingString!.isNotEmpty) - ? remainingString - : base.value as String; + temp = + (remainingString != null && remainingString!.isNotEmpty) + ? remainingString + : base.value as String; } final PdfStringLayoutResult result = layouter.layout( - temp!, element.font, element.stringFormat ?? base.stringFormat, - width: width, height: base._maxValue); - height += result.size.height + + temp!, + element.font, + element.stringFormat ?? base.stringFormat, + width: width, + height: base._maxValue, + ); + height += + result.size.height + ((base.style.borders.top.width + base.style.borders.bottom.width) * 2); } else if (base.value is String || remainingString is String) { String? currentValue = base.value as String; if (!finished) { - currentValue = (remainingString != null && remainingString!.isNotEmpty) - ? remainingString - : base.value as String; + currentValue = + (remainingString != null && remainingString!.isNotEmpty) + ? remainingString + : base.value as String; } final PdfStringLayoutResult result = layouter.layout( - currentValue!, base._getTextFont()!, base.stringFormat, - width: width, height: base._maxValue); - height += result.size.height + + currentValue!, + base._getTextFont()!, + base.stringFormat, + width: width, + height: base._maxValue, + ); + height += + result.size.height + ((base.style.borders.top.width + base.style.borders.bottom.width) * 2); } else if (base.value is PdfGrid) { @@ -831,10 +863,11 @@ class PdfGridCellHelper { final PdfImage img = base._value as PdfImage; height = img.height / (96 / 72); } - height += base.style.cellPadding == null - ? (PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.top + - PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.bottom) - : (base.style.cellPadding!.top + base.style.cellPadding!.bottom); + height += + base.style.cellPadding == null + ? (PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.top + + PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.bottom) + : (base.style.cellPadding!.top + base.style.cellPadding!.bottom); height += PdfGridRowHelper.getHelper(row!).grid.style.cellSpacing; return height; } @@ -849,27 +882,28 @@ class PdfGridCellHelper { } if (base._parent != null && PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(base._parent!).row!) - .getWidth() > + PdfGridCellHelper.getHelper(base._parent!).row!, + ).getWidth() > 0 && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isChildGrid! && (PdfGridRowHelper.getHelper(row!).getWidth() > PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(base._parent!).row!) - .getWidth())) { + PdfGridCellHelper.getHelper(base._parent!).row!, + ).getWidth())) { width = 0; for (int j = 0; j < base._parent!.columnSpan; j++) { - width += PdfGridRowHelper.getHelper( - PdfGridCellHelper.getHelper(base._parent!).row!) - .grid - .columns[j] - .width; + width += + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(base._parent!).row!, + ).grid.columns[j].width; } width = width / row!.cells.count; } else if (base._parent != null && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isChildGrid! && width == -1) { width = _findGridColumnWidth(base._parent!); width = width / row!.cells.count; @@ -918,8 +952,10 @@ class PdfGridCellHelper { pen = base._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); + p2 = Offset( + graphics.clientSize.width - pen.width / 2, + bounds.y + bounds.height, + ); } if (base._style!.borders.right.dashStyle == PdfDashStyle.solid && !PdfPenHelper.getHelper(pen).isImmutable) { @@ -943,8 +979,10 @@ class PdfGridCellHelper { pen = base._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); + p1 = Offset( + bounds.x + bounds.width, + graphics.clientSize.height - pen.width / 2, + ); p2 = Offset(bounds.x, graphics.clientSize.height - pen.width / 2); } if (base._style!.borders.bottom.dashStyle == PdfDashStyle.solid && @@ -965,20 +1003,26 @@ class PdfGridCellHelper { /// internal method PdfStringLayoutResult? draw( - PdfGraphics? graphics, PdfRectangle bounds, bool cancelSubsequentSpans) { + PdfGraphics? graphics, + PdfRectangle bounds, + bool cancelSubsequentSpans, + ) { bool isrowbreak = false; - if (!PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isSingleGrid) { + if (!PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isSingleGrid) { if ((remainingString != null) || (PdfGridLayouter.repeatRowIndex != -1)) { _drawParentCells(graphics!, bounds, true); } else if (PdfGridRowHelper.getHelper(row!).grid.rows.count > 1) { - for (int i = 0; - i < PdfGridRowHelper.getHelper(row!).grid.rows.count; - i++) { + for ( + int i = 0; + i < PdfGridRowHelper.getHelper(row!).grid.rows.count; + i++ + ) { if (row == PdfGridRowHelper.getHelper(row!).grid.rows[i]) { if (PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[i]) - .rowBreakHeight > + PdfGridRowHelper.getHelper(row!).grid.rows[i], + ).rowBreakHeight > 0) { isrowbreak = true; } @@ -992,9 +1036,11 @@ class PdfGridCellHelper { PdfStringLayoutResult? result; if (cancelSubsequentSpans) { final int currentCellIndex = row!.cells.indexOf(base); - for (int i = currentCellIndex + 1; - i <= currentCellIndex + base._columnSpan; - i++) { + for ( + int i = currentCellIndex + 1; + i <= currentCellIndex + base._columnSpan; + i++ + ) { PdfGridCellHelper.getHelper(row!.cells[i]).isCellMergeContinue = false; PdfGridCellHelper.getHelper(row!.cells[i]).isRowMergeContinue = false; } @@ -1026,18 +1072,18 @@ class PdfGridCellHelper { if (PdfGridRowHelper.getHelper(row!).grid.allowRowBreakingAcrossPages) { innerLayoutArea.height = innerLayoutArea.height - innerLayoutArea.y; bounds.height = bounds.height - bounds.y; - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isChildGrid!) { - innerLayoutArea.height = innerLayoutArea.height - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom; + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isChildGrid!) { + innerLayoutArea.height = + innerLayoutArea.height - + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; } } else { innerLayoutArea.height = graphics.clientSize.height; @@ -1054,11 +1100,14 @@ class PdfGridCellHelper { PdfGridHelper.getHelper(childGrid).listOfNavigatePages = []; PdfGridLayouter layouter = PdfGridLayouter(childGrid); PdfLayoutFormat? format = PdfLayoutFormat(); - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .layoutFormat != + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).layoutFormat != null) { - format = PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .layoutFormat; + format = + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).layoutFormat; } else { format.layoutType = PdfLayoutType.paginate; } @@ -1087,12 +1136,14 @@ class PdfGridCellHelper { final String textElementString = textelement.text; PdfTextLayoutResult? textlayoutresult; if (finished) { - textlayoutresult = textelement.draw( - page: page, bounds: innerLayoutArea.rect) as PdfTextLayoutResult?; + textlayoutresult = + textelement.draw(page: page, bounds: innerLayoutArea.rect) + as PdfTextLayoutResult?; } else { textelement.text = remainingString!; - textlayoutresult = textelement.draw( - page: page, bounds: innerLayoutArea.rect) as PdfTextLayoutResult?; + textlayoutresult = + textelement.draw(page: page, bounds: innerLayoutArea.rect) + as PdfTextLayoutResult?; } if (textlayoutresult!.remainder != null && textlayoutresult.remainder!.isNotEmpty) { @@ -1107,69 +1158,68 @@ class PdfGridCellHelper { String? temp; PdfRectangle layoutRectangle; if (innerLayoutArea.height < font!.height) { - layoutRectangle = PdfRectangle(innerLayoutArea.x, innerLayoutArea.y, - innerLayoutArea.width, font.height); + layoutRectangle = PdfRectangle( + innerLayoutArea.x, + innerLayoutArea.y, + innerLayoutArea.width, + font.height, + ); } else { layoutRectangle = innerLayoutArea; } if (innerLayoutArea.height < font.height && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isChildGrid! && - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .parentCell != + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isChildGrid! && + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).parentCell != null) { - final double height = layoutRectangle.height - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom - + final double height = + layoutRectangle.height - + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom - PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.bottom; if (height > 0 && height < font.height) { layoutRectangle.height = height; } else if (height + - PdfGridRowHelper.getHelper(row!) - .grid - .style - .cellPadding - .bottom > + PdfGridRowHelper.getHelper( + row!, + ).grid.style.cellPadding.bottom > 0 && height + - PdfGridRowHelper.getHelper(row!) - .grid - .style - .cellPadding - .bottom < + PdfGridRowHelper.getHelper( + row!, + ).grid.style.cellPadding.bottom < font.height) { - layoutRectangle.height = height + + layoutRectangle.height = + height + PdfGridRowHelper.getHelper(row!).grid.style.cellPadding.bottom; } else if (bounds.height < font.height) { layoutRectangle.height = bounds.height; } else if (bounds.height - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom < + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom < font.height) { - layoutRectangle.height = bounds.height - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom; + layoutRectangle.height = + bounds.height - + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; } } if (base.style.cellPadding != null && @@ -1177,41 +1227,49 @@ class PdfGridCellHelper { base.style.cellPadding!.left == 0 && base.style.cellPadding!.right == 0 && base.style.cellPadding!.top == 0) { - layoutRectangle.width = layoutRectangle.width - + layoutRectangle.width = + layoutRectangle.width - base.style.borders.left.width + base.style.borders.right.width; } if (finished) { - temp = remainingString != null && remainingString!.isEmpty - ? remainingString - : base.value as String; - graphics.drawString(temp!, font, - pen: textPen, - brush: textBrush, - bounds: layoutRectangle.rect, - format: strFormat); + temp = + remainingString != null && remainingString!.isEmpty + ? remainingString + : base.value as String; + graphics.drawString( + temp!, + font, + pen: textPen, + brush: textBrush, + bounds: layoutRectangle.rect, + format: strFormat, + ); } else { - graphics.drawString(remainingString!, font, - pen: textPen, - brush: textBrush, - bounds: layoutRectangle.rect, - format: strFormat); + graphics.drawString( + remainingString!, + font, + pen: textPen, + brush: textBrush, + bounds: layoutRectangle.rect, + format: strFormat, + ); } result = PdfGraphicsHelper.getHelper(graphics).stringLayoutResult; - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .isChildGrid! && + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).isChildGrid! && PdfGridRowHelper.getHelper(row!).rowBreakHeight > 0 && result != null) { - bounds.height = bounds.height - - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper( - PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid) - .parentCell!) - .row!) - .grid - .style - .cellPadding - .bottom; + bounds.height = + bounds.height - + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper( + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).parentCell!, + ).row!, + ).grid.style.cellPadding.bottom; } } else if (base._value is PdfImage) { if (base.style.cellPadding != null && @@ -1219,19 +1277,21 @@ class PdfGridCellHelper { PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { final PdfPaddings padding = base.style.cellPadding!; bounds = PdfRectangle( - bounds.x + padding.left, - bounds.y + padding.top, - bounds.width - (padding.left + padding.right), - bounds.height - (padding.top + padding.bottom)); + bounds.x + padding.left, + bounds.y + padding.top, + bounds.width - (padding.left + padding.right), + bounds.height - (padding.top + padding.bottom), + ); } else if (PdfGridRowHelper.getHelper(row!).grid.style.cellPadding != PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { final PdfPaddings padding = PdfGridRowHelper.getHelper(row!).grid.style.cellPadding; bounds = PdfRectangle( - bounds.x + padding.left, - bounds.y + padding.top, - bounds.width - (padding.left + padding.right), - bounds.height - (padding.top + padding.bottom)); + bounds.x + padding.left, + bounds.y + padding.top, + bounds.width - (padding.left + padding.right), + bounds.height - (padding.top + padding.bottom), + ); } final PdfImage img = base.value as PdfImage; double? imgWidth = img.width.toDouble(); @@ -1285,14 +1345,21 @@ class PdfGridCellHelper { final PdfPage graphicsPage = PdfGraphicsHelper.getHelper(graphics).page!; final PdfGraphicsState st = graphicsPage.graphics.save(); - graphicsPage.graphics - .setClip(bounds: bounds.rect, mode: PdfFillMode.winding); + graphicsPage.graphics.setClip( + bounds: bounds.rect, + mode: PdfFillMode.winding, + ); graphicsPage.graphics.drawImage( - img, Rect.fromLTWH(bounds.x, bounds.y, imgWidth, imgHeight)); + img, + Rect.fromLTWH(bounds.x, bounds.y, imgWidth, imgHeight), + ); graphicsPage.graphics.restore(st); } else { - graphics = _setImagePosition(graphics, img, - PdfRectangle(bounds.x, bounds.y, imgWidth, imgHeight)); + graphics = _setImagePosition( + graphics, + img, + PdfRectangle(bounds.x, bounds.y, imgWidth, imgHeight), + ); } graphics!.save(); } @@ -1302,56 +1369,57 @@ class PdfGridCellHelper { void _drawParentCells(PdfGraphics graphics, PdfRectangle bounds, bool b) { final PdfPoint location = PdfPoint( - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .defaultBorder - .right - .width / - 2, - PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .defaultBorder - .top - .width / - 2); + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).defaultBorder.right.width / + 2, + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).defaultBorder.top.width / + 2, + ); if ((bounds.height < graphics.clientSize.height) && (b == true)) { bounds.height = bounds.height + bounds.y - location.y; } - final PdfRectangle rect = - PdfRectangle(location.x, location.y, bounds.width, bounds.height); + final PdfRectangle rect = PdfRectangle( + location.x, + location.y, + bounds.width, + bounds.height, + ); if (b == false) { rect.y = bounds.y; rect.height = bounds.height; } PdfGridCell? c = base; if (base._parent != null) { - if ((PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper(c).row!) - .grid - .rows - .count == + if ((PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(c).row!, + ).grid.rows.count == 1) && - (PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper(c).row!) - .grid - .rows[0] - .cells - .count == + (PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(c).row!, + ).grid.rows[0].cells.count == 1)) { PdfGridCellHelper.getHelper( - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper(c).row!) - .grid - .rows[0] - .cells[0]) - .present = true; + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(c).row!, + ).grid.rows[0].cells[0], + ).present = + true; } else { - for (int rowIndex = 0; - rowIndex < - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper(c).row!) - .grid - .rows - .count; - rowIndex++) { + for ( + int rowIndex = 0; + rowIndex < + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(c).row!, + ).grid.rows.count; + rowIndex++ + ) { final PdfGridRow r = - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper(c).row!) - .grid - .rows[rowIndex]; + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(c).row!, + ).grid.rows[rowIndex]; if (r == PdfGridCellHelper.getHelper(c).row) { for (int cellIndex = 0; cellIndex < row!.cells.count; cellIndex++) { final PdfGridCell cell = row!.cells[cellIndex]; @@ -1366,12 +1434,11 @@ class PdfGridCellHelper { while (c!._parent != null) { c = c._parent; PdfGridCellHelper.getHelper(c!).present = true; - rect.x = rect.x + - PdfGridRowHelper.getHelper(PdfGridCellHelper.getHelper(c).row!) - .grid - .style - .cellPadding - .left; + rect.x = + rect.x + + PdfGridRowHelper.getHelper( + PdfGridCellHelper.getHelper(c).row!, + ).grid.style.cellPadding.left; } } if (bounds.x >= rect.x) { @@ -1397,13 +1464,14 @@ class PdfGridCellHelper { } rect.width = PdfGridRowHelper.getHelper(pdfGrid.rows[i]).getWidth() - - cellwidth; + cellwidth; 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++) { - if ((PdfGridCellHelper.getHelper(grid.rows[l].cells[m]) - .present) && + if ((PdfGridCellHelper.getHelper( + grid.rows[l].cells[m], + ).present) && m > 0) { rect.width = grid.rows[l].cells[m].width; cellcount = m; @@ -1463,19 +1531,21 @@ class PdfGridCellHelper { PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { final PdfPaddings padding = base.style.cellPadding!; bounds = PdfRectangle( - bounds.x + padding.left, - bounds.y + padding.top, - bounds.width - (padding.left + padding.right), - bounds.height - (padding.top + padding.bottom)); + bounds.x + padding.left, + bounds.y + padding.top, + bounds.width - (padding.left + padding.right), + bounds.height - (padding.top + padding.bottom), + ); } else if (PdfGridRowHelper.getHelper(row!).grid.style.cellPadding != PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { final PdfPaddings padding = PdfGridRowHelper.getHelper(row!).grid.style.cellPadding; bounds = PdfRectangle( - bounds.x + padding.left, - bounds.y + padding.top, - bounds.width - (padding.left + padding.right), - bounds.height - (padding.top + padding.bottom)); + bounds.x + padding.left, + bounds.y + padding.top, + bounds.width - (padding.left + padding.right), + bounds.height - (padding.top + padding.bottom), + ); } return _setImagePosition(graphics, image, bounds); } @@ -1543,8 +1613,12 @@ class PdfGridCellHelper { final double cellSpacing = PdfGridRowHelper.getHelper(row!).grid.style.cellSpacing; if (cellSpacing > 0) { - bounds = PdfRectangle(bounds.x + cellSpacing, bounds.y + cellSpacing, - bounds.width - cellSpacing, bounds.height - cellSpacing); + bounds = PdfRectangle( + bounds.x + cellSpacing, + bounds.y + cellSpacing, + bounds.width - cellSpacing, + bounds.height - cellSpacing, + ); } final int currentColIndex = row!.cells.indexOf(base); if (base.columnSpan > 1 || @@ -1554,9 +1628,11 @@ class PdfGridCellHelper { isCellMergeContinue)) { int span = base.columnSpan; if (span == 1 && isCellMergeContinue) { - for (int j = currentColIndex + 1; - j < PdfGridRowHelper.getHelper(row!).grid.columns.count; - j++) { + for ( + int j = currentColIndex + 1; + j < PdfGridRowHelper.getHelper(row!).grid.columns.count; + j++ + ) { if (PdfGridCellHelper.getHelper(row!.cells[j]).isCellMergeContinue) { span++; } else { @@ -1566,29 +1642,30 @@ class PdfGridCellHelper { } double totalWidth = 0; for (int i = currentColIndex; i < currentColIndex + span; i++) { - if (PdfGridRowHelper.getHelper(row!) - .grid - .style - .allowHorizontalOverflow) { + if (PdfGridRowHelper.getHelper( + row!, + ).grid.style.allowHorizontalOverflow) { double width; - final double compWidth = PdfGridHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid) - .size - .width < - g!.clientSize.width - ? PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .size - .width - : g.clientSize.width; - if (PdfGridHelper.getHelper(PdfGridRowHelper.getHelper(row!).grid) - .size - .width > + final double compWidth = + PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).size.width < + g!.clientSize.width + ? PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).size.width + : g.clientSize.width; + if (PdfGridHelper.getHelper( + PdfGridRowHelper.getHelper(row!).grid, + ).size.width > g.clientSize.width) { - width = bounds.x + + width = + bounds.x + totalWidth + PdfGridRowHelper.getHelper(row!).grid.columns[i].width; } else { - width = totalWidth + + width = + totalWidth + PdfGridRowHelper.getHelper(row!).grid.columns[i].width; } if (width > compWidth) { @@ -1603,30 +1680,34 @@ class PdfGridCellHelper { if (base.rowSpan > 1 || PdfGridRowHelper.getHelper(row!).rowSpanExists) { int span = base.rowSpan; int currentRowIndex = PdfGridRowCollectionHelper.indexOf( - PdfGridRowHelper.getHelper(row!).grid.rows, row); + PdfGridRowHelper.getHelper(row!).grid.rows, + row, + ); if (currentRowIndex == -1) { currentRowIndex = PdfGridHeaderCollectionHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.headers) - .indexOf(row!); + PdfGridRowHelper.getHelper(row!).grid.headers, + ).indexOf(row!); if (currentRowIndex != -1) { isHeader = true; } } if (span == 1 && isCellMergeContinue) { - for (int j = currentRowIndex + 1; - j < PdfGridRowHelper.getHelper(row!).grid.rows.count; - j++) { + for ( + int j = currentRowIndex + 1; + j < PdfGridRowHelper.getHelper(row!).grid.rows.count; + j++ + ) { if (isHeader - ? PdfGridCellHelper.getHelper(PdfGridRowHelper.getHelper(row!) - .grid - .headers[j] - .cells[currentColIndex]) - .isCellMergeContinue - : PdfGridCellHelper.getHelper(PdfGridRowHelper.getHelper(row!) - .grid - .rows[j] - .cells[currentColIndex]) - .isCellMergeContinue) { + ? PdfGridCellHelper.getHelper( + PdfGridRowHelper.getHelper( + row!, + ).grid.headers[j].cells[currentColIndex], + ).isCellMergeContinue + : PdfGridCellHelper.getHelper( + PdfGridRowHelper.getHelper( + row!, + ).grid.rows[j].cells[currentColIndex], + ).isCellMergeContinue) { span++; } else { break; @@ -1645,42 +1726,50 @@ class PdfGridCellHelper { } else { for (int i = currentRowIndex; i < currentRowIndex + span; i++) { if (!PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[i]) - .isRowSpanRowHeightSet) { + PdfGridRowHelper.getHelper(row!).grid.rows[i], + ).isRowSpanRowHeightSet) { PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[i]) - .isRowHeightSet = false; + PdfGridRowHelper.getHelper(row!).grid.rows[i], + ).isRowHeightSet = + false; } - totalHeight += isHeader - ? PdfGridRowHelper.getHelper(row!).grid.headers[i].height - : PdfGridRowHelper.getHelper(row!).grid.rows[i].height; + totalHeight += + isHeader + ? PdfGridRowHelper.getHelper(row!).grid.headers[i].height + : PdfGridRowHelper.getHelper(row!).grid.rows[i].height; final PdfGridRow gridRow = PdfGridRowHelper.getHelper(row!).grid.rows[i]; final int rowIndex = PdfGridRowCollectionHelper.indexOf( - PdfGridRowHelper.getHelper(row!).grid.rows, gridRow); + PdfGridRowHelper.getHelper(row!).grid.rows, + gridRow, + ); if (base.rowSpan > 1) { - for (int cellIndex = 0; - cellIndex < gridRow.cells.count; - cellIndex++) { + for ( + int cellIndex = 0; + cellIndex < gridRow.cells.count; + cellIndex++ + ) { final PdfGridCell cell = gridRow.cells[cellIndex]; if (cell.rowSpan > 1) { double tempHeight = 0; for (int j = i; j < i + cell.rowSpan; j++) { if (!PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[j]) - .isRowSpanRowHeightSet) { + PdfGridRowHelper.getHelper(row!).grid.rows[j], + ).isRowSpanRowHeightSet) { PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[j]) - .isRowHeightSet = false; + PdfGridRowHelper.getHelper(row!).grid.rows[j], + ).isRowHeightSet = + false; } tempHeight += PdfGridRowHelper.getHelper(row!).grid.rows[j].height; if (!PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[j]) - .isRowSpanRowHeightSet) { + PdfGridRowHelper.getHelper(row!).grid.rows[j], + ).isRowSpanRowHeightSet) { PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[j]) - .isRowHeightSet = true; + PdfGridRowHelper.getHelper(row!).grid.rows[j], + ).isRowHeightSet = + true; } } if (cell.height > tempHeight) { @@ -1691,43 +1780,47 @@ class PdfGridCellHelper { max += base._tempRowSpanRemainingHeight; } final int index = gridRow.cells.indexOf(cell); - PdfGridCellHelper.getHelper(PdfGridRowHelper.getHelper(row!) - .grid - .rows[(rowIndex + cell.rowSpan) - 1] - .cells[index]) - .rowSpanRemainingHeight = max; + PdfGridCellHelper.getHelper( + PdfGridRowHelper.getHelper(row!) + .grid + .rows[(rowIndex + cell.rowSpan) - 1] + .cells[index], + ).rowSpanRemainingHeight = + max; base._tempRowSpanRemainingHeight = PdfGridCellHelper.getHelper( - PdfGridRowHelper.getHelper(row!) - .grid - .rows[(rowIndex + cell.rowSpan) - 1] - .cells[index]) - .rowSpanRemainingHeight; + PdfGridRowHelper.getHelper(row!) + .grid + .rows[(rowIndex + cell.rowSpan) - 1] + .cells[index], + ).rowSpanRemainingHeight; } } } } } if (!PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[i]) - .isRowSpanRowHeightSet) { + PdfGridRowHelper.getHelper(row!).grid.rows[i], + ).isRowSpanRowHeightSet) { PdfGridRowHelper.getHelper( - PdfGridRowHelper.getHelper(row!).grid.rows[i]) - .isRowHeightSet = true; + PdfGridRowHelper.getHelper(row!).grid.rows[i], + ).isRowHeightSet = + true; } } final int cellIndex = row!.cells.indexOf(base); totalHeight -= PdfGridRowHelper.getHelper(row!).grid.style.cellSpacing; if (row!.cells[cellIndex].height > totalHeight && - (!PdfGridRowHelper.getHelper(PdfGridRowHelper.getHelper(row!) - .grid - .rows[(currentRowIndex + span) - 1]) - .isRowHeightSet)) { - PdfGridCellHelper.getHelper(PdfGridRowHelper.getHelper(row!) - .grid - .rows[(currentRowIndex + span) - 1] - .cells[cellIndex]) - .rowSpanRemainingHeight = + (!PdfGridRowHelper.getHelper( + PdfGridRowHelper.getHelper( + row!, + ).grid.rows[(currentRowIndex + span) - 1], + ).isRowHeightSet)) { + PdfGridCellHelper.getHelper( + PdfGridRowHelper.getHelper( + row!, + ).grid.rows[(currentRowIndex + span) - 1].cells[cellIndex], + ).rowSpanRemainingHeight = row!.cells[cellIndex].height - totalHeight; totalHeight = row!.cells[cellIndex].height; bounds.height = totalHeight; @@ -1743,7 +1836,10 @@ class PdfGridCellHelper { } PdfGraphics? _setImagePosition( - PdfGraphics? graphics, PdfImage? image, PdfRectangle bounds) { + PdfGraphics? graphics, + PdfImage? image, + PdfRectangle bounds, + ) { if (base._imagePosition == PdfGridImagePosition.stretch) { graphics!.drawImage(image!, bounds.rect); } else if (base._imagePosition == PdfGridImagePosition.center) { @@ -1752,9 +1848,14 @@ class PdfGridCellHelper { 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)); + image!, + Rect.fromLTWH( + gridCentreX, + gridCentreY, + bounds.width / 2, + bounds.height / 2, + ), + ); } else if (base._imagePosition == PdfGridImagePosition.fit) { final double imageWidth = image!.physicalDimension.width; final double imageHeight = image.physicalDimension.height; @@ -1764,12 +1865,16 @@ class PdfGridCellHelper { y = bounds.y; x = bounds.x + bounds.width / 4; graphics!.drawImage( - image, Rect.fromLTWH(x, y, bounds.width / 2, bounds.height)); + 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)); + image, + Rect.fromLTWH(x, y, bounds.width, bounds.height / 2), + ); } } else if (base._imagePosition == PdfGridImagePosition.tile) { final double cellLeft = bounds.x; @@ -1781,8 +1886,10 @@ class PdfGridCellHelper { 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); + final PdfTemplate template = PdfTemplate( + bounds.right - x, + bounds.bottom - y, + ); template.graphics!.drawImage(image!, Rect.zero); graphics!.drawPdfTemplate(template, Offset(x, y)); } else if (x + pWidth > bounds.right) { @@ -1849,7 +1956,7 @@ class PdfGridCellHelper { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1902,7 +2009,7 @@ class PdfGridCellCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1943,7 +2050,7 @@ class PdfGridCellCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1985,7 +2092,7 @@ class PdfGridCellCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1996,6 +2103,7 @@ class PdfGridCellCollection { //Implementation PdfGridCell _returnValue(int index) { if (index < 0 || index >= _helper._cells.length) { + // ignore: deprecated_member_use throw IndexError(index, _helper._cells); } return _helper._cells[index]; 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 08b45d9f1..4fc729d1c 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 @@ -35,7 +35,7 @@ import 'pdf_grid_row.dart'; /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -74,7 +74,7 @@ class PdfGridColumn { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -127,7 +127,7 @@ class PdfGridColumn { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -173,7 +173,7 @@ class PdfGridColumn { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -218,7 +218,7 @@ class PdfGridColumn { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -284,7 +284,7 @@ class PdfGridColumnHelper { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -342,7 +342,7 @@ class PdfGridColumnCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -386,7 +386,7 @@ class PdfGridColumnCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -432,7 +432,7 @@ class PdfGridColumnCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -461,6 +461,7 @@ class PdfGridColumnCollection { //Implementation PdfGridColumn _returnValue(int index) { if (index < 0 || index >= _helper._columns.length) { + // ignore: deprecated_member_use throw IndexError(index, _helper._columns); } return _helper._columns[index]; 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 14978fd7c..de03b4e42 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 @@ -42,7 +42,7 @@ import 'styles/style.dart'; /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -84,7 +84,7 @@ class PdfGridRow { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -131,7 +131,7 @@ class PdfGridRow { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -184,7 +184,7 @@ class PdfGridRow { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -234,7 +234,7 @@ class PdfGridRow { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -281,8 +281,9 @@ class PdfGridRow { bool isHeader = false; double rowHeight = cells[0].rowSpan > 1 ? 0 : cells[0].height; double maxHeight = 0; - if (PdfGridHeaderCollectionHelper.getHelper(_helper.grid.headers) - .indexOf(this) != + if (PdfGridHeaderCollectionHelper.getHelper( + _helper.grid.headers, + ).indexOf(this) != -1) { isHeader = true; } @@ -439,7 +440,7 @@ class PdfGridRowHelper { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -486,7 +487,7 @@ class PdfGridRowCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -543,7 +544,7 @@ class PdfGridRowCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -590,7 +591,7 @@ class PdfGridRowCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -638,7 +639,7 @@ class PdfGridRowCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -699,7 +700,7 @@ class PdfGridRowCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -759,7 +760,7 @@ class PdfGridRowCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -783,6 +784,7 @@ class PdfGridRowCollection { //Implementation PdfGridRow _returnValue(int index) { if (index < 0 || index >= _rows.length) { + // ignore: deprecated_member_use throw IndexError(index, _rows); } return _rows[index]; @@ -851,7 +853,7 @@ class PdfGridRowCollectionHelper { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -900,7 +902,7 @@ class PdfGridHeaderCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -936,7 +938,7 @@ class PdfGridHeaderCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -972,7 +974,7 @@ class PdfGridHeaderCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1015,7 +1017,7 @@ class PdfGridHeaderCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1071,7 +1073,7 @@ class PdfGridHeaderCollection { /// grid.draw( /// page: document.pages.add(), bounds: Rect.zero); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -1082,6 +1084,7 @@ class PdfGridHeaderCollection { //Implementation PdfGridRow _returnValue(int index) { if (index < 0 || index >= count) { + // ignore: deprecated_member_use throw IndexError(index, _rows); } return _rows[index]; 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 880c91866..802ef6ed7 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 @@ -60,6 +60,7 @@ class PdfBorders { //Properties /// Sets all. + // ignore: avoid_setters_without_getters set all(PdfPen pen) { left = right = bottom = top = pen; } @@ -106,10 +107,14 @@ class PdfPaddings { int get hashCode => _left.hashCode; /// Sets space value to all sides of a cell Left,Right,Top,Bottom. + // ignore: avoid_setters_without_getters set all(double value) { if (value < 0) { ArgumentError.value( - value, 'all', 'value should greater than or equal to zero'); + value, + 'all', + 'value should greater than or equal to zero', + ); } _left = _right = _bottom = _top = value; } @@ -121,7 +126,10 @@ class PdfPaddings { set left(double value) { if (value < 0) { ArgumentError.value( - value, 'left', 'value should greater than or equal to zero'); + value, + 'left', + 'value should greater than or equal to zero', + ); } _left = value; } @@ -133,7 +141,10 @@ class PdfPaddings { set right(double value) { if (value < 0) { ArgumentError.value( - value, 'right', 'value should greater than or equal to zero'); + value, + 'right', + 'value should greater than or equal to zero', + ); } _right = value; } @@ -145,7 +156,10 @@ class PdfPaddings { set top(double value) { if (value < 0) { ArgumentError.value( - value, 'top', 'value should greater than or equal to zero'); + value, + 'top', + 'value should greater than or equal to zero', + ); } _top = value; } @@ -157,7 +171,10 @@ class PdfPaddings { set bottom(double value) { if (value < 0) { ArgumentError.value( - value, 'bottom', 'value should greater than or equal to zero'); + value, + 'bottom', + 'value should greater than or equal to zero', + ); } _bottom = value; } 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 6cad8c31f..557d3d295 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 @@ -25,17 +25,22 @@ abstract class PdfGridStyleBase { /// 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}) { + PdfGridRowStyle({ + 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; @@ -43,23 +48,19 @@ class PdfGridRowStyle extends PdfGridStyleBase { } } -/// Provides customization of the appearance for the +/// Provides customization of the appearance for the [PdfGridCell]. 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}) - : super( - backgroundBrush: backgroundBrush, - textBrush: textBrush, - textPen: textPen, - font: font) { + PdfGridCellStyle({ + PdfBorders? borders, + PdfStringFormat? format, + PdfImage? backgroundImage, + PdfPaddings? cellPadding, + super.backgroundBrush, + super.textBrush, + super.textPen, + super.font, + }) { _initializeCellStyle(borders, format, backgroundImage, cellPadding); } @@ -87,8 +88,12 @@ class PdfGridCellStyle extends PdfGridRowStyle { } //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; @@ -116,16 +121,24 @@ class PdfGridCellStyleHelper { 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}) { - _initializeStyle(cellSpacing, cellPadding, borderOverlapStyle, - backgroundBrush, textBrush, textPen, font); + PdfGridStyle({ + 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]. @@ -157,13 +170,14 @@ 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 152599fe0..2ce2dbaee 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 @@ -13,7 +13,7 @@ /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -22,7 +22,7 @@ enum PdfListMarkerAlignment { left, /// Right alignment for marker. - right + right, } /// Specifies the marker style. @@ -39,7 +39,7 @@ enum PdfListMarkerAlignment { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -66,5 +66,5 @@ enum PdfUnorderedMarkerStyle { customImage, /// Marker is custom template. - customTemplate + customTemplate, } 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 33f81caef..b1a5aaa69 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 @@ -17,7 +17,7 @@ import 'enums.dart'; /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -37,7 +37,7 @@ abstract class PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -58,7 +58,7 @@ abstract class PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -80,7 +80,7 @@ abstract class PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -102,7 +102,7 @@ abstract class PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -124,7 +124,7 @@ abstract class PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 513e2d26a..5d1285711 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 @@ -18,7 +18,7 @@ import 'pdf_marker.dart'; /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -39,15 +39,16 @@ class PdfOrderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfOrderedMarker( - {this.style = PdfNumberStyle.none, - PdfFont? font, - String suffix = '', - String delimiter = ''}) { + PdfOrderedMarker({ + this.style = PdfNumberStyle.none, + PdfFont? font, + String suffix = '', + String delimiter = '', + }) { _helper = PdfOrderedMarkerHelper(this); _delimiter = delimiter; _suffix = suffix; @@ -70,7 +71,7 @@ class PdfOrderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -100,7 +101,7 @@ class PdfOrderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -129,7 +130,7 @@ class PdfOrderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -159,7 +160,7 @@ class PdfOrderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -195,6 +196,8 @@ class PdfOrderedMarkerHelper extends PdfMarkerHelper { /// Gets the marker number. String getNumber() { return PdfAutomaticFieldHelper.convert( - _startNumber + currentIndex, base.style); + _startNumber + currentIndex, + base.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 81f5c53b5..0d257a0d2 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 @@ -25,7 +25,7 @@ import 'pdf_marker.dart'; /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -47,15 +47,16 @@ class PdfUnorderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfUnorderedMarker( - {this.style = PdfUnorderedMarkerStyle.none, - PdfFont? font, - String? text, - PdfTemplate? template}) { + PdfUnorderedMarker({ + this.style = PdfUnorderedMarkerStyle.none, + PdfFont? font, + String? text, + PdfTemplate? template, + }) { _helper = PdfUnorderedMarkerHelper(this); if (font != null) { this.font = font; @@ -84,7 +85,7 @@ class PdfUnorderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -118,7 +119,7 @@ class PdfUnorderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -144,7 +145,7 @@ class PdfUnorderedMarker extends PdfMarker { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -177,37 +178,50 @@ class PdfUnorderedMarkerHelper extends PdfMarkerHelper { late PdfFont unicodeFont; /// Draws the specified graphics. - void draw(PdfGraphics? graphics, Offset point, PdfBrush? brush, PdfPen? pen, - [PdfList? curList]) { + 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 (base.style) { case PdfUnorderedMarkerStyle.customTemplate: - templete.graphics! - .drawPdfTemplate(base._template!, Offset.zero, size!.size); + templete.graphics!.drawPdfTemplate( + base._template!, + Offset.zero, + size!.size, + ); offset = Offset( - point.dx, - point.dy + - ((curList!.font!.height > base.font!.height - ? curList.font!.height - : base.font!.height) / - 2) - - (size!.height / 2)); + point.dx, + point.dy + + ((curList!.font!.height > base.font!.height + ? curList.font!.height + : base.font!.height) / + 2) - + (size!.height / 2), + ); break; case PdfUnorderedMarkerStyle.customImage: // templete.graphics.drawImage( // _image, 1, 1, _size.width - 2, _size.height - 2); break; + // ignore: no_default_cases default: final PdfPoint location = PdfPoint.empty; if (pen != null) { location.x = location.x + pen.width; location.y = location.y + pen.width; } - templete.graphics!.drawString(getStyledText(), unicodeFont, - pen: pen, - brush: brush, - bounds: Rect.fromLTWH(location.x, location.y, 0, 0)); + templete.graphics!.drawString( + getStyledText(), + unicodeFont, + pen: pen, + brush: brush, + bounds: Rect.fromLTWH(location.x, location.y, 0, 0), + ); break; } graphics!.drawPdfTemplate(templete, offset); @@ -229,6 +243,7 @@ class PdfUnorderedMarkerHelper extends PdfMarkerHelper { case PdfUnorderedMarkerStyle.circle: text = '\x6D'; break; + // ignore: no_default_cases default: break; } 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 1980087de..7a783e870 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 @@ -31,7 +31,7 @@ import 'pdf_list_layouter.dart'; /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -55,7 +55,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -79,7 +79,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -104,7 +104,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -129,7 +129,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -153,7 +153,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -174,7 +174,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..items.add(PdfListItem(text: 'PDF')) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -201,7 +201,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -237,7 +237,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -267,7 +267,7 @@ abstract class PdfList extends PdfLayoutElement { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -359,7 +359,7 @@ class PdfListHelper { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -399,7 +399,7 @@ class BeginItemLayoutArgs { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -430,7 +430,7 @@ class BeginItemLayoutArgs { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -468,7 +468,7 @@ class BeginItemLayoutArgsHelper { /// oList.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -509,7 +509,7 @@ class EndItemLayoutArgs { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -539,7 +539,7 @@ class EndItemLayoutArgs { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -579,12 +579,12 @@ class EndItemLayoutArgsHelper { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` -typedef BeginItemLayoutCallback = void Function( - Object sender, BeginItemLayoutArgs args); +typedef BeginItemLayoutCallback = + void Function(Object sender, BeginItemLayoutArgs args); /// typedef for handling EndItemLayoutEvent. /// @@ -610,9 +610,9 @@ typedef BeginItemLayoutCallback = void Function( /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` -typedef EndItemLayoutCallback = void Function( - Object sender, EndItemLayoutArgs args); +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 cdf716635..5d45eeddb 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 @@ -18,7 +18,7 @@ import 'pdf_list.dart'; /// PdfListItem(text: 'PDF')) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -38,17 +38,18 @@ class PdfListItem { /// PdfListItem(text: 'PDF')) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfListItem( - {this.text = '', - this.font, - PdfStringFormat? format, - this.pen, - this.brush, - this.subList}) { + PdfListItem({ + this.text = '', + this.font, + PdfStringFormat? format, + this.pen, + this.brush, + this.subList, + }) { stringFormat = format; } @@ -68,7 +69,7 @@ class PdfListItem { /// brush: PdfBrushes.red)) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -89,7 +90,7 @@ class PdfListItem { /// brush: PdfBrushes.red)) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -110,7 +111,7 @@ class PdfListItem { /// brush: PdfBrushes.red)) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -131,7 +132,7 @@ class PdfListItem { /// brush: PdfBrushes.red)) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -149,7 +150,7 @@ class PdfListItem { /// ..subList = PdfOrderedList(items: PdfListItemCollection(['PDF']))) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -171,7 +172,7 @@ class PdfListItem { /// ..textIndent = 10) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -193,7 +194,7 @@ class PdfListItem { /// brush: PdfBrushes.red)) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 f49b0eeb9..2fb09c8f2 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 @@ -19,7 +19,7 @@ import 'pdf_list_item.dart'; /// textIndent: 10) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -43,7 +43,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// textIndent: 10) /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -70,7 +70,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -93,7 +93,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -117,7 +117,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -143,7 +143,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -168,7 +168,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -196,7 +196,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// oList.draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -222,7 +222,7 @@ class PdfListItemCollection extends PdfObjectCollection { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -256,7 +256,8 @@ class PdfListItemCollectionHelper extends PdfObjectCollectionHelper { PdfListItem getValue(int index) { if (index < 0 || index >= base.count) { throw RangeError( - "The index should be less than item's count or more or equal to 0"); + "The index should be less than item's count or more or equal to 0", + ); } return list[index] as PdfListItem; } @@ -274,7 +275,8 @@ class PdfListItemCollectionHelper extends PdfObjectCollectionHelper { void insert(int index, PdfListItem item, double? itemIndent) { if (index < 0 || index >= base.count) { throw ArgumentError( - "The index should be less than item's count or more or equal to 0, $index"); + "The index should be less than item's count or more or equal to 0, $index", + ); } if (itemIndent != null) { item.textIndent = itemIndent; @@ -294,7 +296,8 @@ class PdfListItemCollectionHelper extends PdfObjectCollectionHelper { void removeAt(int index) { if (index < 0 || index >= base.count) { throw ArgumentError( - "The index should be less than item's count or more or equal to 0, $index"); + "The index should be less than item's count or more or equal to 0, $index", + ); } list.removeAt(index); } 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 e5d5b9c95..9523f06a4 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 @@ -30,7 +30,7 @@ import 'pdf_unordered_list.dart'; /// Layouts list. class PdfListLayouter extends ElementLayouter { /// Initializes a new instance of the [PdfListLayouter] class. - PdfListLayouter(PdfList element) : super(element); + PdfListLayouter(PdfList super.element); /// Current graphics for lay outing. PdfGraphics? graphics; @@ -42,7 +42,7 @@ class PdfListLayouter extends ElementLayouter { PdfList? curList; /// List that contains ListInfo. - List<_ListInfo> info = <_ListInfo>[]; + List info = []; /// Index of item that lay outing. int index = 0; @@ -155,11 +155,17 @@ class PdfListLayouter extends ElementLayouter { } } info.clear(); - final Rect finalBounds = - Rect.fromLTWH(bounds.x, pageResult.y!, bounds.width, resultHeight ?? 0); + final Rect finalBounds = Rect.fromLTWH( + bounds.x, + pageResult.y!, + bounds.width, + resultHeight ?? 0, + ); if (currentPage != null) { - final PdfLayoutResult result = - PdfLayoutResultHelper.load(currentPage!, finalBounds); + final PdfLayoutResult result = PdfLayoutResultHelper.load( + currentPage!, + finalBounds, + ); return result; } else { return null; @@ -183,7 +189,16 @@ class PdfListLayouter extends ElementLayouter { _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'] as _PageLayoutResult?; height = returnedValue['height'] as double?; y = returnedValue['y'] as double?; @@ -201,8 +216,11 @@ class PdfListLayouter extends ElementLayouter { if (curList is PdfOrderedList) { final PdfOrderedList oList = curList! as PdfOrderedList; PdfOrderedMarkerHelper.getHelper(oList.marker).currentIndex = index; - final _ListInfo listInfo = _ListInfo(curList, index, - PdfOrderedMarkerHelper.getHelper(oList.marker).getNumber()); + final ListInfo listInfo = ListInfo( + curList, + index, + PdfOrderedMarkerHelper.getHelper(oList.marker).getNumber(), + ); listInfo.brush = currentBrush; listInfo.font = currentFont; listInfo.format = currentFormat; @@ -210,7 +228,7 @@ class PdfListLayouter extends ElementLayouter { listInfo.markerWidth = markerMaxWidth as double?; info.add(listInfo); } else { - final _ListInfo listInfo = _ListInfo(curList, index); + final ListInfo listInfo = ListInfo(curList, index); listInfo.brush = currentBrush; listInfo.font = currentFont; listInfo.format = currentFormat; @@ -219,8 +237,10 @@ class PdfListLayouter extends ElementLayouter { } curList = item.subList; if (curList is PdfOrderedList) { - markerMaxWidth = - _getMarkerMaxWidth(curList! as PdfOrderedList, info); + markerMaxWidth = _getMarkerMaxWidth( + curList! as PdfOrderedList, + info, + ); } index = -1; indent = indent! + curList!.indent; @@ -234,7 +254,7 @@ class PdfListLayouter extends ElementLayouter { finish = true; break; } - final _ListInfo listInfo = info.last; + final ListInfo listInfo = info.last; info.remove(listInfo); index = listInfo.index + 1; indent = indent! - curList!.indent; @@ -251,15 +271,16 @@ class PdfListLayouter extends ElementLayouter { /// Draws the item. Map _drawItem( - _PageLayoutResult pageResult, - double x, - PdfList curList, - int index, - double indent, - List<_ListInfo> listInfo, - PdfListItem item, - double height, - double y) { + _PageLayoutResult pageResult, + double x, + PdfList curList, + int index, + double indent, + List listInfo, + PdfListItem item, + double height, + double y, + ) { final PdfStringLayouter layouter = PdfStringLayouter(); PdfStringLayoutResult? markerResult; PdfStringLayoutResult? result; @@ -325,9 +346,13 @@ class PdfListLayouter extends ElementLayouter { if (markerText != null && ((marker is PdfUnorderedMarker) && (marker.style == PdfUnorderedMarkerStyle.customString))) { - markerResult = layouter.layout(markerText, _getMarkerFont(marker, item)!, - _getMarkerFormat(marker, item), - width: size.width, height: size.height); + markerResult = layouter.layout( + markerText, + _getMarkerFont(marker, item)!, + _getMarkerFormat(marker, item), + width: size.width, + height: size.height, + ); posX += markerResult.size.width; pageResult.markerWidth = markerResult.size.width; markerHeight = markerResult.size.height; @@ -353,9 +378,10 @@ class PdfListLayouter extends ElementLayouter { canDrawMarker = false; } } else { - posX += PdfUnorderedMarkerHelper.getHelper(marker as PdfUnorderedMarker) - .size! - .width; + posX += + PdfUnorderedMarkerHelper.getHelper( + marker as PdfUnorderedMarker, + ).size!.width; pageResult.markerWidth = PdfUnorderedMarkerHelper.getHelper(marker).size!.width; markerHeight = PdfUnorderedMarkerHelper.getHelper(marker).size!.height; @@ -383,7 +409,8 @@ class PdfListLayouter extends ElementLayouter { 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.'); + 'There is not enough space to layout the item text. Marker is too long or there is no enough space to draw it.', + ); } double itemX = posX; @@ -410,12 +437,27 @@ class PdfListLayouter extends ElementLayouter { } } - result = layouter.layout(text, itemFont!, itemFormat, - width: itemSize.width, height: itemSize.height); - final PdfRectangle rect = - PdfRectangle(itemX, posY, itemSize.width, itemSize.height); + result = layouter.layout( + text, + itemFont!, + itemFormat, + width: itemSize.width, + height: itemSize.height, + ); + final PdfRectangle rect = PdfRectangle( + itemX, + posY, + itemSize.width, + itemSize.height, + ); PdfGraphicsHelper.getHelper(graphics!).drawStringLayoutResult( - result, itemFont, itemPen, itemBrush, rect, itemFormat); + result, + itemFont, + itemPen, + itemBrush, + rect, + itemFormat, + ); y = posY; itemHeight = result.size.height; } @@ -478,31 +520,41 @@ class PdfListLayouter extends ElementLayouter { if ((marker is PdfUnorderedMarker) && marker.style == PdfUnorderedMarkerStyle.customString) { if (markerResult != null) { - wroteMaker = - _drawMarker(curList, item, markerResult, posY, pageResult.markerX); + wroteMaker = _drawMarker( + curList, + item, + markerResult, + posY, + pageResult.markerX, + ); pageResult.markerWrote = true; pageResult.markerWidth = markerResult.size.width; } } else { if (canDrawMarker && !pageResult.markerWrote) { - wroteMaker = - _drawMarker(curList, item, markerResult, posY, pageResult.markerX); + wroteMaker = _drawMarker( + curList, + item, + markerResult, + posY, + pageResult.markerX, + ); pageResult.markerWrote = wroteMaker; if (curList is PdfOrderedList) { pageResult.markerWidth = markerResult!.size.width; } else { pageResult.markerWidth = - PdfUnorderedMarkerHelper.getHelper(marker as PdfUnorderedMarker) - .size! - .width; + PdfUnorderedMarkerHelper.getHelper( + marker as PdfUnorderedMarker, + ).size!.width; } } } return { 'pageResult': pageResult, 'height': height, - 'y': y + 'y': y, }; } @@ -523,12 +575,17 @@ class PdfListLayouter extends ElementLayouter { } /// Gets the width of the marker max. - double? _getMarkerMaxWidth(PdfOrderedList list, List<_ListInfo> info) { + double? _getMarkerMaxWidth(PdfOrderedList list, List 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); + list, + list.items[i], + i + list.marker.startNumber, + info, + true, + ); if (width! < result.size.width) { width = result.size.width; @@ -538,8 +595,13 @@ class PdfListLayouter extends ElementLayouter { } /// Creates the ordered marker result. - PdfStringLayoutResult _createOrderedMarkerResult(PdfList list, - PdfListItem? item, int index, List<_ListInfo> info, bool findMaxWidth) { + PdfStringLayoutResult _createOrderedMarkerResult( + PdfList list, + PdfListItem? item, + int index, + List info, + bool findMaxWidth, + ) { PdfOrderedList orderedList = list as PdfOrderedList; PdfOrderedMarker marker = orderedList.marker; PdfOrderedMarkerHelper.getHelper(marker).currentIndex = index; @@ -547,13 +609,14 @@ class PdfListLayouter extends ElementLayouter { String text = ''; if (orderedList.marker.style != PdfNumberStyle.none) { - text = PdfOrderedMarkerHelper.getHelper(orderedList.marker).getNumber() + + text = + PdfOrderedMarkerHelper.getHelper(orderedList.marker).getNumber() + orderedList.marker.suffix; } if (orderedList.markerHierarchy) { - final List<_ListInfo> listInfos = info; + final List listInfos = info; for (int i = listInfos.length - 1; i >= 0; i--) { - final _ListInfo listInfo = listInfos[i]; + final ListInfo listInfo = listInfos[i]; if (listInfo.list is PdfUnorderedList) { break; } @@ -579,8 +642,12 @@ class PdfListLayouter extends ElementLayouter { markerFormat = _setMarkerStringFormat(marker, markerFormat); } final PdfStringLayoutResult result = layouter.layout( - text, markerFont, markerFormat, - width: markerSize.width, height: markerSize.height); + text, + markerFont, + markerFormat, + width: markerSize.width, + height: markerSize.height, + ); return result; } @@ -611,10 +678,13 @@ class PdfListLayouter extends ElementLayouter { /// Sets the marker alingment. PdfStringFormat _setMarkerStringFormat( - PdfOrderedMarker marker, PdfStringFormat? markerFormat) { - markerFormat = markerFormat == null - ? PdfStringFormat() - : _markerFormatClone(markerFormat); + PdfOrderedMarker marker, + PdfStringFormat? markerFormat, + ) { + markerFormat = + markerFormat == null + ? PdfStringFormat() + : _markerFormatClone(markerFormat); if (marker.stringFormat == null) { markerFormat.alignment = PdfTextAlignment.right; if (PdfMarkerHelper.getHelper(marker).rightToLeft) { @@ -629,7 +699,9 @@ class PdfListLayouter extends ElementLayouter { PdfStringFormat _markerFormatClone(PdfStringFormat format) { final PdfStringFormat markerFormat = PdfStringFormat( - alignment: format.alignment, lineAlignment: format.lineAlignment); + alignment: format.alignment, + lineAlignment: format.lineAlignment, + ); markerFormat.lineSpacing = format.lineSpacing; markerFormat.characterSpacing = format.characterSpacing; markerFormat.clipPath = format.clipPath; @@ -648,12 +720,18 @@ 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 (PdfLayoutElementHelper.getHelper(element!).raiseBeginPageLayout && currentPage != null) { - final ListBeginPageLayoutArgs args = - ListBeginPageLayoutArgs._(currentBounds, currentPage, list); + final ListBeginPageLayoutArgs args = ListBeginPageLayoutArgs._( + currentBounds, + currentPage, + list, + ); PdfLayoutElementHelper.getHelper(element!).onBeginPageLayout(args); cancel = args.cancel; @@ -665,12 +743,17 @@ class PdfListLayouter extends ElementLayouter { /// After the page layouted. ListEndPageLayoutArgs? _afterPageLayouted( - Rect bounds, PdfPage? currentPage, PdfList list) { + Rect bounds, + PdfPage? currentPage, + PdfList list, + ) { ListEndPageLayoutArgs? args; if (PdfLayoutElementHelper.getHelper(element!).raisePageLayouted && currentPage != null) { - final PdfLayoutResult result = - PdfLayoutResultHelper.load(currentPage, bounds); + final PdfLayoutResult result = PdfLayoutResultHelper.load( + currentPage, + bounds, + ); args = ListEndPageLayoutArgs._internal(result, list); PdfLayoutElementHelper.getHelper(element!).onEndPageLayout(args); } @@ -679,8 +762,10 @@ class PdfListLayouter extends ElementLayouter { /// Before the item layout. void _beforeItemLayout(PdfListItem item, PdfPage page) { - final BeginItemLayoutArgs args = - BeginItemLayoutArgsHelper.internal(item, page); + final BeginItemLayoutArgs args = BeginItemLayoutArgsHelper.internal( + item, + page, + ); PdfListHelper.getHelper(element!).onBeginItemLayout(args); } @@ -691,8 +776,13 @@ class PdfListLayouter extends ElementLayouter { } /// Draws the marker. - bool _drawMarker(PdfList curList, PdfListItem item, - PdfStringLayoutResult? markerResult, double posY, double posX) { + bool _drawMarker( + PdfList curList, + PdfListItem item, + PdfStringLayoutResult? markerResult, + double posY, + double posX, + ) { if (curList is PdfOrderedList) { if (markerResult != null) { if (curList.font != null && @@ -704,10 +794,11 @@ class PdfListLayouter extends ElementLayouter { _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 = markerResult.size.height + posY; @@ -720,33 +811,45 @@ class PdfListLayouter extends ElementLayouter { /// Draws the ordered marker. PdfStringLayoutResult _drawOrderedMarker( - PdfList curList, - PdfStringLayoutResult markerResult, - PdfListItem item, - double posX, - double posY) { + PdfList curList, + PdfStringLayoutResult markerResult, + PdfListItem item, + double posX, + 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 PdfRectangle rect = PdfRectangle(posX - markerMaxWidth!, posY, - markerResult.size.width, markerResult.size.height); + final PdfRectangle rect = PdfRectangle( + posX - markerMaxWidth!, + posY, + markerResult.size.width, + markerResult.size.height, + ); rect.width = markerMaxWidth! as double; markerFormat = _setMarkerStringFormat(marker, markerFormat); PdfGraphicsHelper.getHelper(graphics!).drawStringLayoutResult( - markerResult, markerFont, markerPen, markerBrush, rect, markerFormat); + markerResult, + markerFont, + markerPen, + markerBrush, + rect, + markerFormat, + ); return markerResult; } /// Draws the unordered marker. PdfStringLayoutResult? _drawUnorderedMarker( - PdfList curList, - PdfStringLayoutResult? markerResult, - PdfListItem item, - double posX, - double posY) { + PdfList curList, + 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); @@ -757,27 +860,36 @@ class PdfListLayouter extends ElementLayouter { final PdfPoint location = PdfPoint(posX - markerResult.size.width, posY); PdfUnorderedMarkerHelper.getHelper(marker).size = markerResult.size; if (marker.style == PdfUnorderedMarkerStyle.customString) { - final PdfRectangle rect = PdfRectangle(location.x, location.y, - markerResult.size.width, markerResult.size.height); + final PdfRectangle rect = PdfRectangle( + location.x, + location.y, + markerResult.size.width, + markerResult.size.height, + ); PdfGraphicsHelper.getHelper(graphics!).drawStringLayoutResult( - markerResult, - markerFont!, - markerPen, - markerBrush, - rect, - markerFormat); + markerResult, + markerFont!, + markerPen, + markerBrush, + rect, + markerFormat, + ); } else { PdfUnorderedMarkerHelper.getHelper(marker).unicodeFont = PdfStandardFont(PdfFontFamily.zapfDingbats, markerFont!.size); - PdfUnorderedMarkerHelper.getHelper(marker) - .draw(graphics, location.offset, markerBrush, markerPen); + PdfUnorderedMarkerHelper.getHelper( + marker, + ).draw(graphics, location.offset, markerBrush, markerPen); } } else { - PdfUnorderedMarkerHelper.getHelper(marker).size = - PdfSize(markerFont!.size, markerFont.size); + PdfUnorderedMarkerHelper.getHelper(marker).size = PdfSize( + markerFont!.size, + markerFont.size, + ); final PdfPoint location = PdfPoint(posX - markerFont.size, posY); - PdfUnorderedMarkerHelper.getHelper(marker) - .draw(graphics, location.offset, markerBrush, markerPen, curList); + PdfUnorderedMarkerHelper.getHelper( + marker, + ).draw(graphics, location.offset, markerBrush, markerPen, curList); } return null; } @@ -808,11 +920,20 @@ class PdfListLayouter extends ElementLayouter { /// Creates the marker result. PdfStringLayoutResult? _createMarkerResult( - int index, PdfList curList, List<_ListInfo> listInfo, PdfListItem item) { + int index, + PdfList curList, + List listInfo, + PdfListItem item, + ) { PdfStringLayoutResult? markerResult; if (curList is PdfOrderedList) { - markerResult = - _createOrderedMarkerResult(curList, item, index, info, false); + markerResult = _createOrderedMarkerResult( + curList, + item, + index, + info, + false, + ); } else { final PdfSize markerSize = PdfSize.empty; markerResult = _createUnorderedMarkerResult(curList, item, markerSize); @@ -822,7 +943,10 @@ class PdfListLayouter extends ElementLayouter { /// Creates the unordered marker result. PdfStringLayoutResult? _createUnorderedMarkerResult( - PdfList curList, PdfListItem item, PdfSize markerSize) { + PdfList curList, + PdfListItem item, + PdfSize markerSize, + ) { final PdfUnorderedMarker marker = (curList as PdfUnorderedList).marker; PdfStringLayoutResult? result; final PdfFont? markerFont = _getMarkerFont(marker, item); @@ -838,22 +962,33 @@ class PdfListLayouter extends ElementLayouter { PdfUnorderedMarkerHelper.getHelper(marker).size = markerSize; break; case PdfUnorderedMarkerStyle.customString: - result = layouter.layout(marker.text!, markerFont!, markerFormat, - width: size.width, height: size.height); + result = layouter.layout( + marker.text!, + markerFont!, + markerFormat, + width: size.width, + height: size.height, + ); break; + // ignore: no_default_cases default: - final PdfStandardFont uFont = - PdfStandardFont(PdfFontFamily.zapfDingbats, markerFont!.size); + final PdfStandardFont uFont = PdfStandardFont( + PdfFontFamily.zapfDingbats, + markerFont!.size, + ); result = layouter.layout( - PdfUnorderedMarkerHelper.getHelper(marker).getStyledText(), - uFont, - null, - width: size.width, - height: size.height); + PdfUnorderedMarkerHelper.getHelper(marker).getStyledText(), + uFont, + null, + width: size.width, + height: size.height, + ); PdfUnorderedMarkerHelper.getHelper(marker).size = result.size; if (marker.pen != null) { - result.size = PdfSize(result.size.width + 2 * marker.pen!.width, - result.size.height + 2 * marker.pen!.width); + result.size = PdfSize( + result.size.width + 2 * marker.pen!.width, + result.size.height + 2 * marker.pen!.width, + ); } break; } @@ -886,9 +1021,9 @@ class _PageLayoutResult { } /// Represents information about list. -class _ListInfo { - /// Initializes a new instance of the [_ListInfo] class. - _ListInfo(this.list, this.index, [this.number = '']); +class ListInfo { + /// Initializes a new instance of the [ListInfo] class. + ListInfo(this.list, this.index, [this.number = '']); /// Index of list. int index; @@ -918,8 +1053,7 @@ class _ListInfo { /// 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) - : super(bounds, page); + ListBeginPageLayoutArgs._(super.bounds, super.page, this.list); /// Gets the ended layout late PdfList list; @@ -930,7 +1064,10 @@ class ListBeginPageLayoutArgs extends BeginPageLayoutArgs { class ListBeginPageLayoutArgsHelper { /// internal method static ListBeginPageLayoutArgs internal( - Rect bounds, PdfPage page, PdfList list) { + Rect bounds, + PdfPage page, + PdfList list, + ) { return ListBeginPageLayoutArgs._(bounds, page, list); } } @@ -938,8 +1075,7 @@ class ListBeginPageLayoutArgsHelper { /// Represents begin page layout event arguments. class ListEndPageLayoutArgs extends EndPageLayoutArgs { /// Initializes a new instance of the [ListEndPageLayoutArgs] class. - ListEndPageLayoutArgs._internal(PdfLayoutResult result, this.list) - : super(result); + ListEndPageLayoutArgs._internal(super.result, this.list); /// Gets the ended layout 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 be1221828..4cecb78fb 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 @@ -23,7 +23,7 @@ import 'pdf_list_item_collection.dart'; /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -47,21 +47,21 @@ class PdfOrderedList extends PdfList { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfOrderedList( - {PdfOrderedMarker? marker, - PdfListItemCollection? items, - String? text, - PdfFont? font, - PdfNumberStyle style = PdfNumberStyle.numeric, - PdfStringFormat? format, - this.markerHierarchy = false, - double indent = 10, - double textIndent = 5}) - : super() { + PdfOrderedList({ + PdfOrderedMarker? marker, + PdfListItemCollection? items, + String? text, + PdfFont? font, + PdfNumberStyle style = PdfNumberStyle.numeric, + PdfStringFormat? format, + this.markerHierarchy = false, + double indent = 10, + double textIndent = 5, + }) : super() { final PdfListHelper helper = PdfListHelper(this); this.marker = marker ?? _createMarker(style); stringFormat = format; @@ -96,7 +96,7 @@ class PdfOrderedList extends PdfList { /// page: document.pages.add(), /// bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -120,7 +120,7 @@ class PdfOrderedList extends PdfList { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -129,6 +129,6 @@ class PdfOrderedList extends PdfList { //Static methods. //Creates the marker. static PdfOrderedMarker _createMarker(PdfNumberStyle style) { - return PdfOrderedMarker(style: style, font: null); + return PdfOrderedMarker(style: style); } } 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 4c923d170..10643424e 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 @@ -22,7 +22,7 @@ import 'pdf_list_item_collection.dart'; /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. -/// List bytes = document.save(); +/// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` @@ -45,20 +45,20 @@ class PdfUnorderedList extends PdfList { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` - PdfUnorderedList( - {PdfUnorderedMarker? marker, - PdfListItemCollection? items, - String? text, - PdfFont? font, - PdfUnorderedMarkerStyle style = PdfUnorderedMarkerStyle.disk, - PdfStringFormat? format, - double indent = 10, - double textIndent = 5}) - : super() { + PdfUnorderedList({ + PdfUnorderedMarker? marker, + PdfListItemCollection? items, + String? text, + PdfFont? font, + PdfUnorderedMarkerStyle style = PdfUnorderedMarkerStyle.disk, + PdfStringFormat? format, + double indent = 10, + double textIndent = 5, + }) : super() { final PdfListHelper helper = PdfListHelper(this); this.marker = marker ?? _createMarker(style); stringFormat = format; @@ -92,7 +92,7 @@ class PdfUnorderedList extends PdfList { /// ..draw( /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); /// //Save the document. - /// List bytes = document.save(); + /// List bytes = await document.save(); /// //Dispose the document. /// document.dispose(); /// ``` 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 548346a68..9eda7fc0e 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 @@ -72,7 +72,8 @@ class XmpMetadata implements IPdfWrapper { count++; if (count > 1) { throw ArgumentError( - 'More than one element satisfies the specified condition'); + 'More than one element satisfies the specified condition', + ); } } } @@ -114,10 +115,12 @@ class XmpMetadata implements IPdfWrapper { void _initializeStream() { _stream!.beginSave = beginSave; _stream!.endSave = endSave; - _stream![PdfDictionaryProperties.type] = - PdfName(PdfDictionaryProperties.metadata); - _stream![PdfDictionaryProperties.subtype] = - PdfName(PdfDictionaryProperties.xml); + _stream![PdfDictionaryProperties.type] = PdfName( + PdfDictionaryProperties.metadata, + ); + _stream![PdfDictionaryProperties.subtype] = PdfName( + PdfDictionaryProperties.xml, + ); _stream!.compress = false; } @@ -159,67 +162,118 @@ class XmpMetadata implements IPdfWrapper { final XmlElement rdf = _createElement('rdf', 'RDF', _rdfUri); _addNamespace('rdf', _rdfUri); if (!_isNullOrEmpty(info.producer) || !_isNullOrEmpty(info.keywords)) { - final String? pdfNamespace = - _addNamespace('pdf', 'http://ns.adobe.com/pdf/1.3/'); - final XmlElement rdfDescription = - _createElement('rdf', 'Description', _rdfUri); + final String? pdfNamespace = _addNamespace( + 'pdf', + 'http://ns.adobe.com/pdf/1.3/', + ); + final XmlElement rdfDescription = _createElement( + 'rdf', + 'Description', + _rdfUri, + ); rdfDescription.setAttribute('rdf:about', ' '); if (!_isNullOrEmpty(info.producer)) { - rdfDescription.children.add(XmlElement( - XmlName('Producer', 'pdf'), [ - XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace!) - ], [ - XmlText(info.producer) - ])); + rdfDescription.children.add( + XmlElement( + XmlName('Producer', 'pdf'), + [ + XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace!), + ], + [XmlText(info.producer)], + ), + ); } if (!_isNullOrEmpty(info.keywords)) { - rdfDescription.children.add(XmlElement( - XmlName('Keywords', 'pdf'), [ - XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace!) - ], [ - XmlText(info.keywords) - ])); + rdfDescription.children.add( + XmlElement( + XmlName('Keywords', 'pdf'), + [ + XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace!), + ], + [XmlText(info.keywords)], + ), + ); } rdf.children.add(rdfDescription); } if (!_isNullOrEmpty(info.creator)) { - final XmlElement xmpDescription = - _createElement('rdf', 'Description', _rdfUri); + final XmlElement xmpDescription = _createElement( + 'rdf', + 'Description', + _rdfUri, + ); xmpDescription.setAttribute('rdf:about', ' '); 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)])); + xmpDescription.children.add( + XmlElement(XmlName('CreatorTool', 'xmp'), [], [ + XmlText(info.creator), + ]), + ); } final String createDate = _getDateTime( - PdfDocumentInformationHelper.getHelper(info).creationDate); - xmpDescription.children.add(XmlElement(XmlName('CreateDate', 'xmp'), - [], [XmlText(createDate)])); + PdfDocumentInformationHelper.getHelper(info).creationDate, + ); + xmpDescription.children.add( + XmlElement(XmlName('CreateDate', 'xmp'), [], [ + XmlText(createDate), + ]), + ); if (!PdfDocumentInformationHelper.getHelper(info).isRemoveModifyDate) { final String modificationDate = _getDateTime( - PdfDocumentInformationHelper.getHelper(info).modificationDate); - xmpDescription.children.add(XmlElement(XmlName('ModifyDate', 'xmp'), - [], [XmlText(modificationDate)])); + PdfDocumentInformationHelper.getHelper(info).modificationDate, + ); + xmpDescription.children.add( + XmlElement(XmlName('ModifyDate', 'xmp'), [], [ + XmlText(modificationDate), + ]), + ); } rdf.children.add(xmpDescription); } //Dublin Core Schema final String? dublinNamespace = _addNamespace('dc', _dublinSchema); - final XmlElement dublinDescription = - _createElement('rdf', 'Description', _rdfUri); + final XmlElement dublinDescription = _createElement( + 'rdf', + 'Description', + _rdfUri, + ); dublinDescription.setAttribute('rdf:about', ' '); dublinDescription.setAttribute('xmlns:dc', dublinNamespace); - dublinDescription.children.add(XmlElement(XmlName('format', 'dc'), - [], [XmlText('application/pdf')])); + dublinDescription.children.add( + XmlElement(XmlName('format', 'dc'), [], [ + XmlText('application/pdf'), + ]), + ); _createDublinCoreContainer( - dublinDescription, 'title', info.title, true, 'Alt'); + dublinDescription, + 'title', + info.title, + true, + 'Alt', + ); _createDublinCoreContainer( - dublinDescription, 'description', info.subject, true, 'Alt'); + dublinDescription, + 'description', + info.subject, + true, + 'Alt', + ); _createDublinCoreContainer( - dublinDescription, 'subject', info.keywords, false, 'Bag'); + dublinDescription, + 'subject', + info.keywords, + false, + 'Bag', + ); _createDublinCoreContainer( - dublinDescription, 'creator', info.author, false, 'Seq'); + dublinDescription, + 'creator', + info.author, + false, + 'Seq', + ); rdf.children.add(dublinDescription); if (PdfDocumentInformationHelper.getHelper(_documentInfo!).conformance == PdfConformanceLevel.a1b || @@ -227,15 +281,18 @@ class XmpMetadata implements IPdfWrapper { PdfConformanceLevel.a2b || PdfDocumentInformationHelper.getHelper(_documentInfo!).conformance == PdfConformanceLevel.a3b) { - final String? pdfaid = - _addNamespace('pdfaid', 'http://www.aiim.org/pdfa/ns/id/'); + final String? pdfaid = _addNamespace( + 'pdfaid', + 'http://www.aiim.org/pdfa/ns/id/', + ); final XmlElement pdfA = _createElement('rdf', 'Description', _rdfUri); pdfA.setAttribute('rdf:about', ' '); if (PdfDocumentInformationHelper.getHelper(_documentInfo!).conformance == PdfConformanceLevel.a1b) { pdfA.setAttribute('pdfaid:part', '1'); - } else if (PdfDocumentInformationHelper.getHelper(_documentInfo!) - .conformance == + } else if (PdfDocumentInformationHelper.getHelper( + _documentInfo!, + ).conformance == PdfConformanceLevel.a2b) { pdfA.setAttribute('pdfaid:part', '2'); } else { @@ -255,19 +312,24 @@ class XmpMetadata implements IPdfWrapper { } XmlElement _createElement( - String prefix, String localName, String namespaceURI) { + String prefix, + String localName, + String namespaceURI, + ) { XmlElement element; if (!_namespaceCollection.containsKey(prefix) && prefix != 'xml' && prefix != 'xmlns') { element = XmlElement( - XmlName(localName, prefix), - [XmlAttribute(XmlName(prefix, 'xmlns'), namespaceURI)], - [], - false); + XmlName(localName, prefix), + [XmlAttribute(XmlName(prefix, 'xmlns'), namespaceURI)], + [], + false, + ); } else { - element = - XmlElement(XmlName(localName, prefix == 'xap' ? 'xmp' : prefix)); + element = XmlElement( + XmlName(localName, prefix == 'xap' ? 'xmp' : prefix), + ); } _addNamespace(prefix, namespaceURI); return element; @@ -302,11 +364,19 @@ class XmpMetadata implements IPdfWrapper { } //Creates a Dublin core containers. - void _createDublinCoreContainer(XmlElement dublinDesc, String containerName, - String? value, bool defaultLang, String element) { + void _createDublinCoreContainer( + XmlElement dublinDesc, + String containerName, + String? value, + bool defaultLang, + String element, + ) { if (!_isNullOrEmpty(value)) { - final XmlElement title = - _createElement('dc', containerName, _dublinSchema); + final XmlElement title = _createElement( + 'dc', + containerName, + _dublinSchema, + ); _addNamespace('rdf', _rdfUri); final XmlElement alt = _createElement('rdf', element, _rdfUri); XmlElement li = _createElement('rdf', 'li', _rdfUri); diff --git a/packages/syncfusion_flutter_pdf/pubspec.yaml b/packages/syncfusion_flutter_pdf/pubspec.yaml index 97a0ad7a7..827da5db3 100644 --- a/packages/syncfusion_flutter_pdf/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/pubspec.yaml @@ -1,19 +1,22 @@ name: 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: 20.1.47 -homepage: https://github.com/syncfusion/flutter-examples +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdf environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - intl: ">=0.17.0 <0.20.0" - xml: ">=5.1.0 <6.0.0" + intl: '>=0.18.0 <0.21.0' + xml: ">=6.5.0 <7.0.0" syncfusion_flutter_core: - path: ../syncfusion_flutter_core + path: ../syncfusion_flutter_core + + crypto: ">=3.0.0 <4.0.0" convert: ">=3.0.0 <4.0.0" + http: ^1.2.1 diff --git a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md index 278eb5b1e..7ce0ced74 100644 --- a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md @@ -1,5 +1,466 @@ ## Unreleased +**General** + +* The compatible version of our Flutter PDF Viewer widget has been updated to Flutter SDK 3.32.0. + +**Features** + +* Linux platform support has been provided. +* Added support for rendering pages using the open-source PDFium library through an optional package (`syncfusion_pdfviewer_android`). + +**Bugs** + +* Now, read-only form fields are rendered with the specified background color, or with a transparent background if no color is set. +* Now, screen readers read the selected text in PDF documents when accessibility mode is enabled. + +## [29.2.8] - 06/03/2025 + +**Bugs** + +* Now, in single page layout mode, the page changes only when swiped in the scroll direction, and not when slightly panned. +* Now, the `SfPdfViewer` widget will no longer crash with an Out of Memory exception when viewing a PDF document with large page dimensions. +* Now, in single-page layout mode, setting the zoom level in the onPageChanged callback will not cause the page to move to the bottom in the vertical scroll direction or to the right in the horizontal scroll direction. +* Now, the sticky note icon will maintain the same size on all pages, and the icon size is improved on mobile platforms. + +## [29.1.41] - 05/06/2025 + +**Bugs** + +* Improved the performance in loading large password-protected documents by leveraging the password parameter supported in the native (platform) PDF rendering APIs. +* Now, the pages will be centered in single-page layout mode when switching between pages in a PDF document with different page sizes. + +## [29.1.39] - 04/22/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +**Bugs** + +* Now, the page no longer jumps to a previous one when scrolling to the top or left edge in single page layout mode. + +## [29.1.33] - 03/25/2025 + +**General** + +* The compatible version of our Flutter PDF Viewer widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter PDF Viewer example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* Provided support for [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in iOS and macOS platforms. + +**Features** + +* Provided support to load PDF documents from any source using the unified `SfPdfViewer.new` constructor. +* Now, scrollbars will be displayed for single-page documents on desktop platforms. +* Now, scrollbars will be displayed in Single Page Layout mode on desktop platforms. + +## [28.2.7] - 02/25/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. +* Upgraded the `intl` package to the latest version 0.20.0. + +**Bugs** + +* Now, the scroll offest value will be accurate when the `SfPdfViewer` widget is used in Single page layout mode. + +## [28.2.5] - 02/11/2025 + +* Now, the application no longer crashes when loading a single-page document created using the `syncfusion_flutter_pdf` library on specific iOS devices. + +## [28.1.39] - 01/14/2025 + +**Bugs** + +* Now, the PDF document loads properly when using newer versions of the pdf.js library on the web platform. +* Now, the PDF document with invalid annotation bounds will be loaded properly in the `SfPdfViewer` widget. + +## [28.1.38] - 01/07/2024 + +**General** + +* Upgraded the [`device_info_plus`](https://pub.dev/packages/device_info_plus) package to the latest version `11.0.0`. + +## [27.2.5] - 12/03/2024 + +**Bugs** + +* Now, 'tel' links in PDF documents open properly in the `SfPdfViewer` widget. +* Now, the browser's built-in context menu is only disabled in routes with the `SfPdfViewer` widget. + +## [26.2.14] - 09/10/2024 + +**General** + +* The compatible version of our Flutter PDF Viewer widget has been updated to Flutter SDK 3.24.0. + +## [26.2.8] - 08/06/2024 + +**Bugs** + +* Now, the second page is rendered properly when loading a two-page PDF document in the `SfPdfViewer` widget on the Android platform. + +## [26.2.7] - 07/30/2024 + +**Bugs** + +* Now, the multi-line textbox form field will not lose focus as soon as it is focused. + +## [26.2.4] - 07/24/2024 + +**Features** + +* Provided text alignment support for form fields in PDF documents. + +## [26.1.42] - 07/16/2024 + +**Bugs** + +* Now, the `SfPdfViewer` will not crash unexpectedly when loading PDF documents while the device is in landscape orientation. +* Improved the performance of the `SfPdfViewer` widget when loading PDF documents with a large number of form fields. + +**Features** + +* Provided support to add, remove, modify, and save sticky note annotations in PDF files. + +**General** + +* Upgraded the `device_info_plus` package to the latest version 10.1.0. + +## [26.1.41] - 07/09/2024 + +**Bugs** + +* Now, the soft keyboard will not hide immediately when focusing on the text form field in the `SfPdfViewer` widget if `Scaffold.resizeToAvoidBottomInset` is set to false. + +## [26.1.40] - 07/02/2024 + +**Bugs** + +* Now, the PDF document with invalid annotation bounds will be loaded properly in the `SfPdfViewer` widget. + +**General** + +* Updated the 'js' package version constraints to '>=0.6.3 <0.8.0' in the `syncfusion_pdfviewer_web` package. + +## [26.1.39] - 06/25/2024 + +**Bugs** + +* Now, the copied text will correctly match the selected Right-to-Left (RTL) text in the `SfPdfViewer` widget. + +## [26.1.35] - 06/11/2024 + +**Features** + +* Now, the pages being rendered will have good quality at the initial zoom level. The rendering performance of pages in the viewer for large documents has also been improved for all platforms. Especially on the web and Android platforms, we have achieved approximately an 80% deduction in the rendering time for a document of 50 MB size. +* Provided support to scroll right-to-left (RTL) horizontally in the RTL layout. This will help you accommodate languages that are read from right to left for better continuity in reading. +* Provided support to customize the visibility of the built-in text selection menu. This can help users design their own customized text selection menu. + +## [25.2.6] - 05/28/2024 + +**Bugs** + +* Now, the soft keyboard will not hide immediately when focusing on the text form field in a particular PDF document. +* Now, the `SfPdfViewer` will not crash unexpectedly when loading a particular PDF document on the iOS platform. + +## [25.2.5] - 05/21/2024 + +**Bugs** + +* Now, the page number will be retained when switching the page layout from single page to continuous page layout in the `SfPdfViewer` widget. + +## [25.2.3] - 05/08/2024 + +**Features** + +* Provided support for letter spacing in the textbox form fields. + +## [25.1.41] - 04/23/2024 + +**Bugs** + +* Now, the page will be rendered properly after switching the device orientation from portrait to landscape or vice versa in the `SfPdfViewer` widget. + +## [25.1.38] - 04/02/2024 + +**Bugs** + +* Now, the `SfPdfViewer` will not crash unexpectedly due to the high memory usage when loading and unloading the large and high-resolution PDF documents frequently. + +## [25.1.35] - 03/15/2024 + +**Features** + +* Provided support for loading the PDF document on a specified page. +* Provided support for active viewport rendering. That is, at a higher zoom level, this feature renders only the part of the PDF document that is visible on the screen, ignoring the parts that are outside the viewport. +* Provided support to undo and redo the data filled in PDF forms. + +**General** + +* Provided the Material 3 theme support. + +## [24.2.9] - 03/05/2024 + +**General** + +* Upgraded the `js` package to the latest version 0.7.1 in the `syncfusion_pdfviewer_web` package. + +## [24.2.8] - 02/27/2024 + +**Bugs** + +* Now the page will be centered while zooming out in landscape orientation with pinch gestures in Single Page layout mode. + +## [24.2.7] - 02/20/2024 + +**Features** + +* Provided support to check and uncheck the grouped checkbox form fields that behave like a radio button in the PDF document. + +## [24.2.5] - 02/13/2024 + +**Bugs** + +* Now, the `SfPdfViewer` will not crash unexpectedly when closing the application before the PDF document is loaded on iOS and macOS platforms. + +## [24.1.47] - 01/23/2024 + +**Bugs** + +* Now, when viewing a single-page document in a continuous page layout, the user can fully zoom out of the document with a pinch gesture in the `SfPdfViewer` widget. + +## [24.1.46] - 01/17/2024 + +**Bugs** + +* Now, the application will not crash when switching the PDF document in the `SfPdfViewer` widget on the Windows platform. + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## [24.1.45] - 01/09/2024 + +**Bugs** + +* Now, the `SfPdfViewer` will render the PDF pages considering the crop box value in iOS. +* Now, when using German locale, the 'Open' label in the hyperlink and password dialogs will be translated properly. + +## [24.1.41] - 18/12/2023 + +**Bugs** + +* Now, the `SfPdfViewer` will be properly deployed without namespace errors when built with a Gradle version greater than 8.x. + +**Features** + +* Provided support to add, remove, modify, and save text markup annotations in PDF files. The available text markups are highlight, underline, strikethrough, and squiggly. +* Provided support to scroll vertically in single-page layout mode. + +## [23.2.6] - 11/28/2023 + +**Bugs** + +* Now, the `SfPdfViewer` will render the PDF pages considering the crop box value in iOS. +* Now, the position is maintained in the zoomed document when performing panning in `SfPdfViewer`. +* Now, the focus of the text form field is maintained when scrolling is performed in `SfPdfViewer`. + +**Features** + +* Enhanced the user experience of the scroll head by increasing its size in `SfPdfViewer`. + +## [23.2.4] - 11/20/2023 + +**Bugs** + +* Now, the `SfPdfViewer` will trigger the `onTap` callback when there is a slight touch slop too. + +## [23.1.44] - 11/07/2023 + +**Bugs** + +* Now, the `SfPdfViewer` does not trigger the `onTap` callback when performing a double-tap zoom. +* Now, in the single-page layout mode, the page position is maintained properly in landscape orientation when performing zoom in a specific PDF document. + +**Features** + +* Support to render the background color in the form fields has been provided. + +## [23.1.42] - 10/24/2023 + +**Bugs** + +* Now, the form fields are properly rendered in a document with a different page size in `SfPdfViewer`. + +**Features** + +* Provided support to render **digital signatures** in existing PDF documents in a non-interactive way to avoid data loss while viewing. The document's integrity is preserved if no editing operation is performed. + +## [23.1.40] - 10/10/2023 + +**Bugs** + +* Now, the default selection of the first item in the radio button form fields has been removed. + +**Features** + +* Provided the support to restrict the text form field editing based on its maximum length. + +## [23.1.38] - 09/26/2023 + +**Bugs** + +* Now, the PDF page content will be clear on iOS when zoomed in. + +**Features** + +* Provided support for the `continueImportOnError` option in the `importFormData` method. + +## [23.1.36] - 09/15/2023 + +**Bugs** + +* Now, the `pagePosition` property in `PdfGestureDetails` will return the tapped position in the PDF page coordinates. + +## [22.2.10] - 08/22/2023 + +**Bugs** + +* Now, the `AlertDialog` or `DatePicker` dialog is properly closed when invoked in the `onFormFieldFocusChange` callback. + +**Features** + +* Enhanced the user experience of the combo box form field by switching the `PopupMenuButton` with a `DropdownButton` widget in `SfPdfViewer`. +* Support for localization for the texts in the built-in signature pad dialog has been provided. + +## [22.2.5] - 07/27/2023 + +**Bugs** + +* The multi-line text form fields will now have the proper font size. + +**Features** + +* Support for filling out the list box form field has been provided. +* Support for filling or editing form fields programmatically has been provided. +* Provided focus change and value change callback support for form fields. +* Provided the support for the `onTap` callback to retrieve the page number, position, and page position when tapping on the `SfPdfViewer`. + +## [22.1.34] - 06/21/2023 + +**Features** + +* Support for filling out the form fields, such as text boxes, dropdown menus, checkboxes, radio buttons, and signatures, has been provided. It also offers additional features, such as the ability to save, export, and import the form data. +* Support for loading the pages with the width fitted on the Windows and Web platforms has been provided. + +**General** + +* Upgraded the `http` package to the latest version 1.0.0. +* Upgraded the `device_info_plus` package to the latest version 9.0.2. + +## [21.2.4] - 05/09/2023 + +**Bugs** + +* Now, the `SfPdfViewer` does not execute the `onDocumentLoaded` callback before the Pdf document loads. + +## [21.2.3] - 05/03/2023 + +**Features** + +* Support for customising the visibility of the page loading busy indicator has been provided. + +## [21.1.38] - 04/04/2023 + +**Bugs** + +* Now, the `SfPdfViewer` does not cause excessive widget rebuilding when no actions are performed on the PDF. + +## [20.4.55] - 03/21/2023 + +**Bugs** + +* Now, the `SfPdfViewer` does not cause text fields to lose focus when scrolling is active on an Android tablet or the iPad. +* Now, the keyboard shortcut navigation on the web platform works properly. + +**Features** + +* Support to set and adjust the maximum zoom level has been provided. + +## [20.4.51] - 02/21/2022 + +* The password dialog is now displayed properly in the `SfPdfViewer` when localized. + +## [20.4.43] - 01/10/2022 + +* Support for scrolling via remote button clicks on Android TV has been provided. + +## [20.3.58] - 11/22/2022 + +**Bugs** + +* Now, the PDF page will not be zoomed while performing a mouse scroll in the mobile view of the web platform. + +**Features** + +* Support for the text web link navigation has been provided. + +## [20.3.52] - 10/26/2022 + +* When copying PDF content from the `SfPdfViewer` widget, spacing between the words is now added properly. + +## [20.3.47] - 09/29/2022 + +**Features** + +* Now, text search will be performed asynchronously on mobile and desktop platforms. +* Now, the busy indicator will be displayed before rendering the pages. + +**Breaking changes** + +* The `searchText` method will now return just the `PdfTextSearchResult` object instead of the `Future`. Since the search will be performed asynchronously, the results will be returned periodically on a page-by-page basis, which can be retrieved using the `addListener` method in the application. +* When we navigate to a particular page and perform a search, then the first instance to be highlighted will be the document's first one instead of the navigated page's first instance. + +## [20.2.44-beta] - 08/16/2022 + +* Now, the scrolling works with the appropriate scrolling animation on a document with the default zoom level. + +## [20.2.43-beta] - 08/08/2022 + +**Bugs** + +* Now, the PDF pages are positioned properly when setting the `initialZoomLevel`. +* Now, the cache memory will be cleared properly after loading a PDF document in the `SfPdfViewer` widget. + +## [20.2.36-beta] - 06/30/2022 + +**Features** + +* Open URLs or website links in the default browser just by clicking them. Also, hide or customize the built-in hyperlink navigation dialog. +* Now, the `SfPdfViewer` supports changing the user interface and functionalities like text search and copying text to suit the RTL languages. +* Enhanced the performance of the scroll fling animation to provide smoother and fluid scrolling. That is, the time taken by scroll fling action has been reduced up to 60%. + +**Bugs** + +* The application will no longer crash while loading a high-quality document in the `SfPdfViewer` widget. +* Now, the PDF pages will be rendered properly while switching the device orientation from portrait to landscape or vice versa. + + +## [20.1.57-beta] - 05/24/2022 + +**Bugs** + +* Now, the application will not close unexpectedly when scrolling the specific PDF document on the iOS device. +* Now, the clarity of the PDF page is updated properly when changing the orientation. + +## [20.1.56-beta] - 05/17/2022 + +* Resolved linter warnings due to Flutter 3 SDK upgradation. + +## [20.1.47-beta] - 04/04/2022 + **Features** * Windows platform support has been provided. @@ -43,7 +504,7 @@ The following platform packages have been renamed. No changes in your pubspec.ya * Now, PDF pages won't be overlapped when multi PDFs are placed inside `IndexedStack` -## [19.3.57-beta] - 12/07/2021 +## [19.3.57-beta] - 12/07/2021 * `DisplayMetrics` deprecation warnings for Android R SDK in Android Plugin has been cleared now. @@ -58,7 +519,7 @@ The following platform packages have been renamed. No changes in your pubspec.ya ## [19.3.53-beta] - 11/12/2021 * Now, PDF pages can be panned when text selection is disabled. - + ## [19.3.46-beta] - 10/19/2021 * Support for text selection in multi-column PDF has been provided. @@ -71,7 +532,7 @@ The following platform packages have been renamed. No changes in your pubspec.ya **Features** -* Support for screen reading has been provided. +* Support for screen reading has been provided. * Now, PDF document can be viewed page by page horizontally. * Horizontal scrolling support has been provided. * Support for text selection and text search in rotated document has been provided. @@ -101,26 +562,26 @@ The following platform packages have been renamed. No changes in your pubspec.ya **Features** -* The Web platform support has been provided. +* 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 +## [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 +## [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 +## [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 +## [18.4.30-beta] - 12/17/2020 **Features** @@ -141,7 +602,7 @@ The following platform packages have been renamed. No changes in your pubspec.ya **Features** -* Now, the temporary PDF file created by Syncfusion Flutter SfPdfViewer will be inaccessible. +* Now, the temporary PDF file created by Syncfusion® Flutter SfPdfViewer will be inaccessible. ## [18.3.35-beta] - 10/01/2020 diff --git a/packages/syncfusion_flutter_pdfviewer/README.md b/packages/syncfusion_flutter_pdfviewer/README.md index fec4772c8..45c35a618 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) # Flutter PDF Viewer library -The Flutter PDF Viewer plugin lets you view the PDF documents seamlessly and efficiently in the Android, iOS, Web, Windows and macOS platforms. It has highly interactive and customizable features such as magnification, virtual bidirectional scrolling, page navigation, text selection, text search, page layout options, document link navigation, and bookmark navigation. +The Flutter PDF Viewer plugin lets you view PDF documents seamlessly and efficiently on the Android, iOS, Web, Windows, and macOS platforms. It has highly interactive and customizable features such as magnification, virtual bidirectional scrolling, page navigation, text selection, text search, page layout options, document link navigation, bookmark navigation, form filling, and reviewing with text markup annotations. -**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. +**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) @@ -12,22 +12,14 @@ The Flutter PDF Viewer plugin lets you view the PDF documents seamlessly and eff - [Useful links](#other-useful-links) - [Installation](#installation) - [Getting started](#getting-started) - - [Add PDF Viewer to the widget tree](#add-pdf-viewer-to-the-widget-tree) - - [Load encrypted PDF document](#load-encrypted-pdf-document) - - [Customize the visibility of scroll head and scroll status](#customize-the-visibility-of-scroll-head-and-scroll-status) - - [Customize the visibility of page navigation dialog](#customize-the-visibility-of-page-navigation-dialog) - - [Customize the visibility of password dialog](#customize-the-visibility-of-password-dialog) - - [Enable or disable the double-tap zoom ](#enable-or-disable-the-double-tap-zoom) - - [Change the page layout](#change-the-page-layout) - - [Switch scroll direction](#switch-scroll-direction) - - [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) + - [Add PDF Viewer to the widget tree](#add-pdf-viewer-to-the-widget-tree) + - [Load PDF document from the Asset](#load-pdf-document-from-the-asset) + - [Load PDF document from the Network](#load-pdf-document-from-the-network) + - [Load PDF document from the File](#load-pdf-document-from-the-file) + - [Load PDF document from the Memory](#load-pdf-document-from-the-memory) + - [Load encrypted PDF document](#load-encrypted-pdf-document) - [Support and feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## PDF Viewer features @@ -38,24 +30,48 @@ The Flutter PDF Viewer plugin lets you view the PDF documents seamlessly and eff * **Page Layout and Scroll Options** - Layout the pages efficiently in a page by page (single page) scrolling mode or continuous scrolling mode. Also, scroll through pages in both horizontal and vertical direction. * **Page navigation** - Navigate to the desired pages instantly. -![syncfusion_flutter_pdfviewer_page_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/pagination-dialog.png) + + ![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) + + ![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) + + ![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. + ![syncfusion_flutter_pdfviewer_bookmark_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation.png) + +* **Document link annotation navigation** - 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. + +* **Hyperlink navigation** - Detects hyperlinks, and tapping on the hyperlink will open the URL in a default web browser. + +* **Text markup annotations** - Add, remove, and modify text markup annotations in PDF files. The available text markups are highlight, underline, strikethrough and squiggly. This feature will help mark important passages, emphasize specific words or phrases, indicate that certain content should be removed or indicate that text contains possible errors. + + ![syncfusion_flutter_pdfviewer_text_markup_highlight](https://cdn.syncfusion.com/content/images/PDFViewer/highlight.png) + + ![syncfusion_flutter_pdfviewer_text_markup_underline](https://cdn.syncfusion.com/content/images/PDFViewer/underline.png) + + ![syncfusion_flutter_pdfviewer_text_markup_strikethrough](https://cdn.syncfusion.com/content/images/PDFViewer/strikethrough.png) + + ![syncfusion_flutter_pdfviewer_text_markup_squiggly](https://cdn.syncfusion.com/content/images/PDFViewer/squiggly.png) + +* **Form filling** - Fill, edit, flatten, save, export, and import AcroForm field data in a PDF document. + + ![syncfusion_flutter_pdfviewer_form_filling](https://cdn.syncfusion.com/content/images/PDFViewer/form-filling.gif) + +* **Right to Left (RTL)** - Change the user interface and functionalities such as text search and text copying to accommodate RTL languages such as Hebrew and Arabic. * **Themes** - Easily switch between the light and dark theme. -![syncfusion_flutter_pdfviewer_theme](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation-dark.png) + + ![syncfusion_flutter_pdfviewer_theme](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation-dark.png) * **Localization** - All static text within the PDF Viewer can be localized to any supported language. -![syncfusion_flutter_pdfviewer_localization](https://cdn.syncfusion.com/content/images/PDFViewer/localization.png) + + ![syncfusion_flutter_pdfviewer_localization](https://cdn.syncfusion.com/content/images/PDFViewer/localization.png) ## Get the demo application @@ -63,28 +79,25 @@ Explore the full capabilities of our Flutter widgets on your device by installin

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about Syncfusion Flutter PDF Viewer: +Take a look at the following to learn more about Syncfusion® Flutter PDF Viewer: -* [Syncfusion Flutter PDF Viewer product page](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer) +* [Syncfusion® Flutter PDF Viewer product page](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer) * [User guide documentation](https://help.syncfusion.com/flutter/pdf-viewer/overview) ## Installation -Install the latest version from [pub](https://pub.dartlang.org/packages/syncfusion_flutter_pdfviewer#-installing-tab-). +Install the latest version from [pub](https://pub.dev/packages/syncfusion_flutter_pdfviewer/install). ## Getting started @@ -96,7 +109,7 @@ import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; ## Add PDF Viewer to the widget tree -Add the SfPdfViewer widget as a child of any widget. Here, the SfPdfViewer widget is added as a child of the Container widget. +Add the **SfPdfViewer** widget as a child of any widget. Here, the **SfPdfViewer** widget is added as a child of the **Container** widget. ```dart @override @@ -115,372 +128,67 @@ We have used [PdfJs](https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.m On your `web/index.html` file, add the following `script` tags, somewhere in the `body` of the document: ```html - + ``` -## Load encrypted PDF document - -Encrypted or password-protected document can be loaded in the SfPdfViewer widget by specifying the password in **password** property. - -```dart -@override -Widget build(BuildContext context) { - return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/encrypted.pdf', - password: 'syncfusion'))); -} -``` - -## Customize the visibility of scroll head and scroll status +## Load PDF document from the Asset -As a default, the SfPdfViewer displays the scroll head and scroll status. You can customize the visibility of these items using the **canShowScrollHead** and **canShowScrollStatus** properties. +The [SfPdfViewer.asset](https://pub.dev/documentation/syncfusion_flutter_pdfviewer/latest/pdfviewer/SfPdfViewer/SfPdfViewer.asset.html) creates a widget that displays the PDF document obtained from an [AssetBundle](https://api.flutter.dev/flutter/services/AssetBundle-class.html). ```dart @override Widget build(BuildContext context) { return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - canShowScrollHead: false, - canShowScrollStatus: false))); + body: SfPdfViewer.asset( + 'assets/flutter-succinctly.pdf')); } ``` -## Customize the visibility of page navigation dialog +## Load PDF document from the Network -As a default, the page navigation dialog will be displayed when the scroll head is tapped. You can customize the visibility of the page navigation dialog using the **canShowPaginationDialog** property. +The [SfPdfViewer.network](https://pub.dev/documentation/syncfusion_flutter_pdfviewer/latest/pdfviewer/SfPdfViewer/SfPdfViewer.network.html) creates a widget that displays the PDF document obtained from a URL. ```dart @override Widget build(BuildContext context) { return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - canShowPaginationDialog: false))); -} -``` - -## Customize the visibility of password dialog - -As a default, the password dialog will be displayed when a password-protected or encrypted document is loaded. You can customize the visibility of the password dialog using the **canShowPasswordDialog** property. - -```dart -@override -Widget build(BuildContext context) { - return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/encrypted.pdf', - canShowPasswordDialog: false))); -} -``` - -## Change the page layout - -Page layout modes describe how the PDF page is displayed. As a default, the page layout will be in continuous mode. You can change the page layout mode using the **pageLayoutMode** property. - -```dart -@override -Widget build(BuildContext context) { - return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - pageLayoutMode: PdfPageLayoutMode.single))); -} -``` - -## Switch scroll direction - -Scrolling options describe how the PDF pages can be scrolled. As a default, the page will be scrolled in vertical direction. You can change the scroll direction using the **scrollDirection** property. In Single page layout mode, only horizontal scrolling is supported. - -```dart -@override -Widget build(BuildContext context) { - return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - scrollDirection: PdfScrollDirection.horizontal))); -} -``` - -## Enable or disable the double-tap zoom - -As a default, the SfPdfViewer will be zoomed in and out when double-tapped. You can enable or disable the double-tap zoom using the **enableDoubleTapZooming** property. - -```dart -@override -Widget build(BuildContext context) { - return Scaffold( - body: Container( - child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - enableDoubleTapZooming: false))); -} -``` - -## Change the zoom level factor - -The content of the document can be zoomed in and out either by pinch and zoom or changing the zoom level factor programmatically. You can change or control the zoom level factor using the **zoomLevel** property. The zoom level factor value can be set between 1.0 and 3.0. - -```dart -PdfViewerController _pdfViewerController; - -@override -void initState() { - _pdfViewerController = PdfViewerController(); - super.initState(); -} - -@override -Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Syncfusion Flutter PdfViewer'), - actions: [ - IconButton( - icon: Icon( - Icons.zoom_in, - color: Colors.white, - ), - onPressed: () { - _pdfViewerController.zoomLevel = 2; - }, - ), - ], - ), - body: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - controller: _pdfViewerController, - ), - ); -} -``` -## Navigate to the desired pages - -The SfPdfViewer provides options to navigate to the desired pages using the following controller methods. - -* **jumpToPage()** - Navigates to the specified page number in a PDF document. -* **nextPage()** - Navigates to the next page of a PDF document. -* **previousPage()** - Navigates to the previous page of a PDF document. -* **firstPage()** - Navigates to the first page of a PDF document. -* **lastPage()** - Navigates to the last page of a PDF document. - -```dart -PdfViewerController _pdfViewerController; - -@override -void initState() { - _pdfViewerController = PdfViewerController(); - super.initState(); -} - -@override -Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Syncfusion Flutter PdfViewer'), - actions: [ - IconButton( - icon: Icon( - Icons.keyboard_arrow_up, - color: Colors.white, - ), - onPressed: () { - _pdfViewerController.previousPage(); - }, - ), - IconButton( - icon: Icon( - Icons.keyboard_arrow_down, - color: Colors.white, - ), - onPressed: () { - _pdfViewerController.nextPage(); - }, - ) - ], - ), - body: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - controller: _pdfViewerController, - ), - ); -} -``` - -## Navigate to the desired bookmark topics - -As a default, the SfPdfViewer provides a default bookmark view to navigate to the bookmarked topics. You can also navigate to the desired bookmark topic programmatically using the **jumpToBookmark()** controller method. - -```dart -final GlobalKey _pdfViewerKey = GlobalKey(); - -@override -Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Syncfusion Flutter PdfViewer'), - actions: [ - IconButton( - icon: Icon( - Icons. bookmark, - color: Colors.white, - ), - onPressed: () { - _pdfViewerKey.currentState?.openBookmarkView(); - }, - ), - ], - ), - body: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - key: _pdfViewerKey, - ), - ); + body: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf')); } ``` -## Select and copy text +## Load PDF document from the File -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. +The [SfPdfViewer.file](https://pub.dev/documentation/syncfusion_flutter_pdfviewer/latest/pdfviewer/SfPdfViewer/SfPdfViewer.file.html) creates a widget that displays the PDF document obtained from a [File](https://api.flutter.dev/flutter/dart-io/File-class.html). ```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, - ), - ); + body: SfPdfViewer.file( + File('storage/emulated/0/Download/flutter-succinctly.pdf'))); } ``` -## Search text and navigate to its occurrences +## Load PDF document from the Memory -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. +The [SfPdfViewer.memory](https://pub.dev/documentation/syncfusion_flutter_pdfviewer/latest/pdfviewer/SfPdfViewer/SfPdfViewer.memory.html) creates a widget that displays the PDF document obtained from the [Uint8List](https://api.flutter.dev/flutter/dart-typed_data/Uint8List-class.html). ```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)); + body: SfPdfViewer.memory( + bytes)); } ``` -## Enable or disable the document link annotation +## Load encrypted PDF document -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. +Encrypted or password-protected document can be loaded in the **SfPdfViewer** widget by specifying the password in [password](https://pub.dev/documentation/syncfusion_flutter_pdfviewer/latest/pdfviewer/SfPdfViewer/password.html) property. ```dart @override @@ -488,18 +196,18 @@ Widget build(BuildContext context) { return Scaffold( body: Container( child: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - enableDocumentLinkAnnotation: false))); + 'https://cdn.syncfusion.com/content/PDFViewer/encrypted.pdf', + password: 'syncfusion'))); } ``` ## 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +## 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_pdfviewer/android/build.gradle b/packages/syncfusion_flutter_pdfviewer/android/build.gradle index c83bfc86e..4d81f8a15 100644 --- a/packages/syncfusion_flutter_pdfviewer/android/build.gradle +++ b/packages/syncfusion_flutter_pdfviewer/android/build.gradle @@ -4,24 +4,27 @@ version '1.0' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:7.4.2' } } rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } apply plugin: 'com.android.library' android { + if (project.android.hasProperty("namespace")) { + namespace 'com.syncfusion.flutter.pdfviewer' + } compileSdkVersion 31 defaultConfig { diff --git a/packages/syncfusion_flutter_pdfviewer/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_pdfviewer/android/gradle/wrapper/gradle-wrapper.properties index 01a286e96..3c472b99c 100644 --- a/packages/syncfusion_flutter_pdfviewer/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_pdfviewer/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ 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 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/syncfusion_flutter_pdfviewer/android/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_pdfviewer/android/src/main/AndroidManifest.xml index 791041199..a2f47b605 100644 --- a/packages/syncfusion_flutter_pdfviewer/android/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_pdfviewer/android/src/main/AndroidManifest.xml @@ -1,3 +1,2 @@ - + 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 60c1ae341..50a9d68a3 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 @@ -3,24 +3,19 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.pdf.PdfRenderer; import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.os.ParcelFileDescriptor; -import android.util.DisplayMetrics; -import android.view.Display; -import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -33,274 +28,229 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -/** - * SyncfusionFlutterPdfViewerPlugin - */ -public class SyncfusionFlutterPdfViewerPlugin implements FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private MethodChannel channel; - /// Pdf result. - private Result resultPdf; - double viewportWidth; - private Context context; - /// Width collections of rendered pages - private double[] pageWidth; - /// Height collections of rendered pages - private double[] pageHeight; - /// Document repository. - Map documentRepo = new HashMap<>(); - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "syncfusion_flutter_pdfviewer"); - channel.setMethodCallHandler(this); - context = flutterPluginBinding.getApplicationContext(); - } +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; - // This static function is optional and equivalent to onAttachedToEngine. It supports the old - // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting - // plugin registration via this function while apps migrate to use the new Android APIs - // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. - // - // It is encouraged to share logic between onAttachedToEngine and registerWith to keep - // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called - // depending on the user's project. onAttachedToEngine or registerWith must both be defined - // in the same class. - @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()); - } +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class SyncfusionFlutterPdfViewerPlugin implements FlutterPlugin, MethodCallHandler { + private MethodChannel channel; + private Context context; + private Map documentRepo = new HashMap<>(); + private ExecutorService executorService; - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - @Override - public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result result) { - resultPdf = result; - switch (call.method) { - case "getImage": - getImage(Integer.parseInt(Objects.requireNonNull(call.argument("index")).toString()), - Double.parseDouble(Objects.requireNonNull(call.argument("scale")).toString()), - (String) call.argument("documentID")); - break; - case "initializePdfRenderer": - result.success(initializePdfRenderer((byte[]) call.argument("documentBytes"), - (String) call.argument("documentID"))); - break; - case "getPagesWidth": - result.success(getPagesWidth((String) call.arguments)); - break; - case "getPagesHeight": - result.success(getPagesHeight((String) call.arguments)); - break; - case "closeDocument": - result.success(closeDocument((String) call.arguments)); - break; - default: - result.notImplemented(); - break; + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "syncfusion_flutter_pdfviewer"); + channel.setMethodCallHandler(this); + context = flutterPluginBinding.getApplicationContext(); + executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); } - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - } - ///Calculate the screenResolution of device with Android version R and above. - @RequiresApi(api = Build.VERSION_CODES.R) - void getScreenResolutionForAndroidR() - { - final WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - viewportWidth = manager.getCurrentWindowMetrics().getBounds().width(); - } - - ///Calculate the screenResolution of device with below Android version R. - @SuppressWarnings("deprecation") - void getScreenResolutionBeforeForAndroidR() - { - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - viewportWidth = metrics.widthPixels / metrics.density; - } - - ///Calculate the screenResolution of device. - private void getScreenResolution() { - if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.R) - { - getScreenResolutionForAndroidR(); - } - else - { - getScreenResolutionBeforeForAndroidR(); + @Override + public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result result) { + switch (call.method) { + case "getPage": + getPage(call, result); + break; + case "getTileImage": + getTileImage(call, result); + break; + case "initializePdfRenderer": + initializePdfRenderer(call, result); + break; + case "getPagesWidth": + getPagesWidth(call, result); + break; + case "getPagesHeight": + getPagesHeight(call, result); + break; + case "closeDocument": + closeDocument(call, result); + break; + default: + result.notImplemented(); + break; + } } - } - // Initializes the PDF Renderer and returns the page count. - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - String initializePdfRenderer(byte[] path, String documentID) { - try { - File file = File.createTempFile( - ".syncfusion", ".pdf" - ); - OutputStream stream = new FileOutputStream(file); - stream.write(path); - stream.close(); - ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - PdfRenderer renderer = new PdfRenderer(fileDescriptor); - PdfFileRenderer fileRenderer = new PdfFileRenderer(fileDescriptor, renderer); - documentRepo.put(documentID, fileRenderer); - int pageCount = renderer.getPageCount(); - return String.valueOf(pageCount); - } catch (Exception e) { - return e.toString(); + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + executorService.shutdown(); } - } - // Returns the height collection of rendered pages. - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - double[] getPagesHeight(String documentID) { - try { - int count = Objects.requireNonNull(documentRepo.get(documentID)).renderer.getPageCount(); - pageHeight = new double[count]; - pageWidth = new double[count]; - getScreenResolution(); - for (int i = 0; i < count; i++) { - PdfRenderer.Page page = Objects.requireNonNull(documentRepo.get(documentID)).renderer.openPage(i); - pageHeight[i] = page.getHeight(); - pageWidth[i] = page.getWidth(); - if(viewportWidth>pageWidth[i]) - { - double heightFactor=pageHeight[i] / pageWidth[i]; - pageWidth[i]=viewportWidth; - pageHeight[i]=pageWidth[i]*heightFactor; + private void getPage(MethodCall call, Result result) { + int pageIndex = call.argument("index"); + int width = call.argument("width"); + int height = call.argument("height"); + String documentID = call.argument("documentID"); + try { + PdfRenderer.Page page = Objects.requireNonNull(documentRepo.get(documentID)).renderer.openPage(pageIndex - 1); + 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(); + ByteBuffer buffer = ByteBuffer.allocate(bitmap.getByteCount()); + bitmap.copyPixelsToBuffer(buffer); + bitmap.recycle(); + final byte[] imageBytes = buffer.array(); + buffer.clear(); + result.success(imageBytes); + } catch (Exception e) { + result.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); } - page.close(); - } - return pageHeight; - } catch (Exception e) { - return null; } - } - // Returns the width collection of rendered pages. - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - double[] getPagesWidth(String documentID) { - try { - if (pageWidth == null) { - int count = Objects.requireNonNull(documentRepo.get(documentID)).renderer.getPageCount(); - pageWidth = new double[count]; - getScreenResolution(); - for (int i = 0; i < count; i++) { - PdfRenderer.Page page = Objects.requireNonNull(documentRepo.get(documentID)).renderer.openPage(i); - pageWidth[i] = page.getWidth(); - if (viewportWidth > pageWidth[i]) { - pageWidth[i] = viewportWidth; - } - page.close(); + private void getTileImage(MethodCall call, Result result) { + int pageNumber = call.argument("pageNumber"); + double scale = call.argument("scale"); + double x = call.argument("x"); + double y = call.argument("y"); + double width = call.argument("width"); + double height = call.argument("height"); + String documentID = call.argument("documentID"); + try { + PdfRenderer.Page page = Objects.requireNonNull(documentRepo.get(documentID)).renderer.openPage(pageNumber - 1); + final Bitmap bitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888); + bitmap.eraseColor(Color.WHITE); + Matrix matrix = new Matrix(); + matrix.postTranslate((float) -x, (float) -y); + matrix.postScale((float) (scale), (float) (scale)); + final Rect rect = new Rect(0, 0, (int) width, (int) height); + page.render(bitmap, rect, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); + page.close(); + ByteBuffer buffer = ByteBuffer.allocate(bitmap.getByteCount()); + bitmap.copyPixelsToBuffer(buffer); + bitmap.recycle(); + final byte[] imageBytes = buffer.array(); + buffer.clear(); + result.success(imageBytes); + } catch (Exception e) { + result.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); } - } - return pageWidth; - } catch (Exception e) { - return null; } - } - // Gets the specific page from PdfRenderer - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - void getImage(int pageIndex, double scale, String documentID) { - try { - ExecutorService executor = Executors.newCachedThreadPool(); - Runnable bitmapRunnable = new PdfRunnable(Objects.requireNonNull(documentRepo.get(documentID)).renderer, resultPdf, pageIndex, scale,pageWidth,pageHeight); - executor.submit(bitmapRunnable); - } catch (Exception e) { - resultPdf.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); - } - } + private void initializePdfRenderer(MethodCall call, Result result) { + executorService.execute(() -> { + byte[] bytes = call.argument("documentBytes"); + String documentID = call.argument("documentID"); + String password = call.argument("password"); + + try { + File file = File.createTempFile(".syncfusion", ".pdf", context.getCacheDir()); + try (FileOutputStream stream = new FileOutputStream(file)) { + stream.write(bytes); + } + + ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + + PdfRenderer renderer; + + if (Build.VERSION.SDK_INT >= 35 && password != null) { + renderer = createPdfRendererWithPassword(fileDescriptor, password); + } else { + renderer = new PdfRenderer(fileDescriptor); + } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - boolean closeDocument(String documentID) { - try { - Objects.requireNonNull(documentRepo.get(documentID)).renderer.close(); - Objects.requireNonNull(documentRepo.get(documentID)).fileDescriptor.close(); - documentRepo.remove(documentID); - } catch (IOException e) { - e.printStackTrace(); + PdfFileRenderer fileRenderer = new PdfFileRenderer(fileDescriptor, renderer); + documentRepo.put(documentID, fileRenderer); + int pageCount = renderer.getPageCount(); + file.delete(); + result.success(String.valueOf(pageCount)); + } catch (SecurityException e) { + result.error("PASSWORD_ERROR", "Incorrect password or document is encrypted", null); + } catch (Exception e) { + result.error("PDF_RENDERER_ERROR", e.getMessage(), null); + } + }); } - return true; - } -} -/// Represents class which holds PdfRenderer and FileDescriptor. -class PdfFileRenderer { - public PdfRenderer renderer; - public ParcelFileDescriptor fileDescriptor; + private PdfRenderer createPdfRendererWithPassword(ParcelFileDescriptor fileDescriptor, String password) throws Exception { + Class loadParamsBuilderClass = Class.forName("android.graphics.pdf.LoadParams$Builder"); + Constructor builderConstructor = loadParamsBuilderClass.getDeclaredConstructor(); + Object builder = builderConstructor.newInstance(); - PdfFileRenderer(ParcelFileDescriptor fileDescriptor, PdfRenderer renderer) { - this.renderer = renderer; - this.fileDescriptor = fileDescriptor; - } -} + Method setPasswordMethod = loadParamsBuilderClass.getMethod("setPassword", String.class); + setPasswordMethod.invoke(builder, password); -/// This runnable executes all the image fetch in separate thread. -class PdfRunnable implements Runnable { - private byte[] imageBytes = null; - final private PdfRenderer renderer; - final private Result resultPdf; - final private int pageIndex; - private double scale; - private double[] pageWidth; - private double[] pageHeight; - private PdfRenderer.Page page; + Method buildMethod = loadParamsBuilderClass.getMethod("build"); + Object loadParams = buildMethod.invoke(builder); - PdfRunnable(PdfRenderer renderer, Result resultPdf, int pageIndex, double scale,double[] pageWidth,double[] pageHeight) { - this.resultPdf = resultPdf; - this.renderer = renderer; - this.pageIndex = pageIndex; - this.scale = scale; - this.pageWidth = pageWidth; - this.pageHeight = pageHeight; - } + Class pdfRendererClass = PdfRenderer.class; + Constructor pdfRendererConstructor = pdfRendererClass.getDeclaredConstructor(ParcelFileDescriptor.class, loadParams.getClass()); + return (PdfRenderer) pdfRendererConstructor.newInstance(fileDescriptor, loadParams); + } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void dispose() { - imageBytes = null; - if (page != null) { - page.close(); - page = null; + private void getPagesWidth(MethodCall call, Result result) { + String documentID = (String) call.arguments; + PdfFileRenderer fileRenderer = documentRepo.get(documentID); + if (fileRenderer == null) { + result.error("DOCUMENT_NOT_FOUND", "Document with ID " + documentID + " not found", null); + return; + } + executorService.execute(() -> { + try { + int count = fileRenderer.renderer.getPageCount(); + double[] pageWidth = new double[count]; + for (int i = 0; i < count; i++) { + try (PdfRenderer.Page page = fileRenderer.renderer.openPage(i)) { + pageWidth[i] = page.getWidth(); + } + } + result.success(pageWidth); + } catch (Exception e) { + result.error("PAGE_WIDTH_ERROR", e.getMessage(), null); + } + }); } - } - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void run() { - page = renderer.openPage(pageIndex - 1); - if (scale < 1.75) - { - scale = 1.75; + private void getPagesHeight(MethodCall call, Result result) { + String documentID = (String) call.arguments; + PdfFileRenderer fileRenderer = documentRepo.get(documentID); + if (fileRenderer == null) { + result.error("DOCUMENT_NOT_FOUND", "Document with ID " + documentID + " not found", null); + return; + } + executorService.execute(() -> { + try { + int count = fileRenderer.renderer.getPageCount(); + double[] pageHeight = new double[count]; + for (int i = 0; i < count; i++) { + try (PdfRenderer.Page page = fileRenderer.renderer.openPage(i)) { + pageHeight[i] = page.getHeight(); + } + } + result.success(pageHeight); + } catch (Exception e) { + result.error("PAGE_HEIGHT_ERROR", e.getMessage(), null); + } + }); } - int width = (int) (pageWidth[pageIndex-1] * scale); - int height = (int) (pageHeight[pageIndex-1] * scale); - 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(); + + private void closeDocument(MethodCall call, Result result) { + String documentID = (String) call.arguments; + PdfFileRenderer fileRenderer = documentRepo.remove(documentID); + if (fileRenderer != null) { + try { + fileRenderer.renderer.close(); + fileRenderer.fileDescriptor.close(); + result.success(true); + } catch (IOException e) { + result.error("CLOSE_ERROR", e.getMessage(), null); + } + } else { + result.success(false); + } + } +} + +class PdfFileRenderer { + public PdfRenderer renderer; + public ParcelFileDescriptor fileDescriptor; + + PdfFileRenderer(ParcelFileDescriptor fileDescriptor, PdfRenderer renderer) { + this.renderer = renderer; + this.fileDescriptor = fileDescriptor; } - 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/assets/fonts/RobotoMono-Regular.ttf b/packages/syncfusion_flutter_pdfviewer/assets/fonts/RobotoMono-Regular.ttf new file mode 100644 index 000000000..6df2b2536 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/fonts/RobotoMono-Regular.ttf differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/highlight.png b/packages/syncfusion_flutter_pdfviewer/assets/highlight.png new file mode 100644 index 000000000..684ebd569 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/highlight.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/highlight.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/highlight.png new file mode 100644 index 000000000..feef09b63 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/highlight.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/squiggly.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/squiggly.png new file mode 100644 index 000000000..8634a97c9 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/squiggly.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/strikethrough.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/strikethrough.png new file mode 100644 index 000000000..e62450a1f Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/strikethrough.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/underline.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/underline.png new file mode 100644 index 000000000..f2c9ebab0 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/dark/underline.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/light/highlight.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/highlight.png new file mode 100644 index 000000000..befce6af8 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/highlight.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/light/squiggly.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/squiggly.png new file mode 100644 index 000000000..05a9c0961 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/squiggly.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/light/strikethrough.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/strikethrough.png new file mode 100644 index 000000000..53e225ee8 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/strikethrough.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/icons/light/underline.png b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/underline.png new file mode 100644 index 000000000..735fe05d2 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/icons/light/underline.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/squiggly.png b/packages/syncfusion_flutter_pdfviewer/assets/squiggly.png new file mode 100644 index 000000000..1bf662e80 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/squiggly.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/strikethrough.png b/packages/syncfusion_flutter_pdfviewer/assets/strikethrough.png new file mode 100644 index 000000000..97b6eee46 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/strikethrough.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/assets/underline.png b/packages/syncfusion_flutter_pdfviewer/assets/underline.png new file mode 100644 index 000000000..727c3cb72 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/assets/underline.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/.gitignore b/packages/syncfusion_flutter_pdfviewer/example/.gitignore new file mode 100644 index 000000000..79c113f9b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/syncfusion_flutter_pdfviewer/example/README.md b/packages/syncfusion_flutter_pdfviewer/example/README.md new file mode 100644 index 000000000..b2cb7332a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/README.md @@ -0,0 +1,16 @@ +# syncfusion_flutter_pdfviewer_example + +Demonstrates how to use the syncfusion_flutter_pdfviewer plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. 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..b08414c80 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/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_pdfviewer/example/android/.gitignore b/packages/syncfusion_flutter_pdfviewer/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/.gitignore +++ b/packages/syncfusion_flutter_pdfviewer/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/build.gradle b/packages/syncfusion_flutter_pdfviewer/example/android/app/build.gradle index 2211483eb..b5511a9a3 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_pdfviewer/example/android/app/build.gradle @@ -1,56 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -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 flutter.compileSdkVersion + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.syncfusion.flutter.pdfviewer.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' + source = "../.." } diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/debug/AndroidManifest.xml index 916dac852..399f6981d 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/AndroidManifest.xml index ea097f474..8f473daa2 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + @@ -8,6 +7,7 @@ android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" + android:taskAffinity="" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" @@ -31,4 +31,16 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/java/com/syncfusion/flutter/pdfviewer/example/MainActivity.java b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/java/com/syncfusion/flutter/pdfviewer/example/MainActivity.java deleted file mode 100644 index 7d6ca5155..000000000 --- a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/java/com/syncfusion/flutter/pdfviewer/example/MainActivity.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.syncfusion.flutter.pdfviewer.example; - -import io.flutter.embedding.android.FlutterActivity; - -public class MainActivity extends FlutterActivity { -} diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..70f8f08f2 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/kotlin/com/syncfusion/flutter/pdfviewer/example/MainActivity.kt b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/kotlin/com/syncfusion/flutter/pdfviewer/example/MainActivity.kt new file mode 100644 index 000000000..444c63ee7 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/kotlin/com/syncfusion/flutter/pdfviewer/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.syncfusion.flutter.pdfviewer.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/res/values-night/styles.xml index 3db14bb53..06952be74 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/res/values-night/styles.xml +++ b/packages/syncfusion_flutter_pdfviewer/example/android/app/src/main/res/values-night/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/build.gradle b/packages/syncfusion_flutter_pdfviewer/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/build.gradle +++ b/packages/syncfusion_flutter_pdfviewer/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/gradle.properties b/packages/syncfusion_flutter_pdfviewer/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/gradle.properties +++ b/packages/syncfusion_flutter_pdfviewer/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_pdfviewer/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_pdfviewer/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_pdfviewer/example/android/settings.gradle b/packages/syncfusion_flutter_pdfviewer/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/android/settings.gradle +++ b/packages/syncfusion_flutter_pdfviewer/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_pdfviewer/example/ios/.gitignore b/packages/syncfusion_flutter_pdfviewer/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/ios/.gitignore +++ b/packages/syncfusion_flutter_pdfviewer/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_pdfviewer/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_pdfviewer/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_pdfviewer/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.pbxproj index cdd1b2fe0..a6a988ee6 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -125,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -179,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -208,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -298,17 +371,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.syncfusionFlutterPdfviewerExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -316,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -432,17 +550,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.syncfusionFlutterPdfviewerExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -459,17 +572,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.syncfusionFlutterPdfviewerExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -480,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_pdfviewer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - syncfusion_flutter_pdfviewer_example + example CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_pdfviewer/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_pdfviewer/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart b/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart index 30293062e..7dca5f295 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart +++ b/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart @@ -2,14 +2,15 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; void main() { - runApp(MaterialApp( - title: 'Syncfusion PDF Viewer Demo', - home: HomePage(), - )); + runApp( + const MaterialApp(title: 'Syncfusion PDF Viewer Demo', home: HomePage()), + ); } /// Represents Homepage for Navigation class HomePage extends StatefulWidget { + const HomePage({super.key}); + @override _HomePage createState() => _HomePage(); } diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/.gitignore b/packages/syncfusion_flutter_pdfviewer/example/linux/.gitignore new file mode 100644 index 000000000..d3896c984 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer/example/linux/CMakeLists.txt new file mode 100644 index 000000000..7a9a314f9 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000..d5bd01648 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..409cfa602 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) syncfusion_pdfviewer_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SyncfusionPdfviewerLinuxPlugin"); + syncfusion_pdfviewer_linux_plugin_register_with_registrar(syncfusion_pdfviewer_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..7cb59559a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + syncfusion_pdfviewer_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/runner/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/CMakeLists.txt new file mode 100644 index 000000000..e97dabc70 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/runner/main.cc b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/main.cc new file mode 100644 index 000000000..e7c5c5437 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/runner/my_application.cc b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/my_application.cc new file mode 100644 index 000000000..6c8108238 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/linux/runner/my_application.h b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/my_application.h new file mode 100644 index 000000000..72271d5e4 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/.gitignore b/packages/syncfusion_flutter_pdfviewer/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/.gitignore +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Debug.xcconfig index 4b81f9b2d..c2efd0b60 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Release.xcconfig index 5caa9d157..c2efd0b60 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Release.xcconfig +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/GeneratedPluginRegistrant.swift index 141b3986b..cff392855 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation -import syncfusion_flutter_pdfviewer_macos +import device_info_plus +import syncfusion_pdfviewer_macos +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) SyncfusionFlutterPdfViewerPlugin.register(with: registry.registrar(forPlugin: "SyncfusionFlutterPdfViewerPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/project.pbxproj index f7aff497c..fca5bdc5c 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,15 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 6E8E02E370F413B79C859DA0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E058152E8CB8154B522B8E6E /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -53,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -67,26 +77,37 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 3D0E768F047FC6FAD1B84748 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 6BEE2183BD34C618223A91D2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - B2C672C9F4D1473714C4CD8F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - E058152E8CB8154B522B8E6E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6E8E02E370F413B79C859DA0 /* Pods_Runner.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -103,9 +124,9 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - 94A4CFA18FB15A5D4A32E723 /* Pods */, ); sourceTree = ""; }; @@ -113,6 +134,7 @@ isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -152,21 +174,9 @@ path = Runner; sourceTree = ""; }; - 94A4CFA18FB15A5D4A32E723 /* Pods */ = { - isa = PBXGroup; - children = ( - 6BEE2183BD34C618223A91D2 /* Pods-Runner.debug.xcconfig */, - B2C672C9F4D1473714C4CD8F /* Pods-Runner.release.xcconfig */, - 3D0E768F047FC6FAD1B84748 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - E058152E8CB8154B522B8E6E /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -174,17 +184,33 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 4472802D8B1D5913FEC51306 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 9E934E5FDBC0088D2C3D9142 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -192,6 +218,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; @@ -202,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -231,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -256,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -291,48 +337,17 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - 4472802D8B1D5913FEC51306 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 9E934E5FDBC0088D2C3D9142 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -346,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -366,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -394,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -404,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -444,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -467,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -483,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -497,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -520,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -530,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -596,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -627,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..9963aa361 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - + + + + + + - - - - diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Configs/AppInfo.xcconfig index d584e0d59..dda9752f6 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/Configs/AppInfo.xcconfig @@ -8,7 +8,7 @@ PRODUCT_NAME = example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.syncfusion. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/DebugProfile.entitlements b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/DebugProfile.entitlements index 73283f6cb..a1a3bebcd 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/DebugProfile.entitlements +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/DebugProfile.entitlements @@ -2,10 +2,10 @@ + com.apple.security.network.client + com.apple.security.app-sandbox - com.apple.security.network.client - com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_pdfviewer/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_pdfviewer/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml index 017eb8af6..4da2444a6 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml @@ -1,40 +1,19 @@ name: syncfusion_flutter_pdfviewer_example description: Demonstrates how to use the syncfusion_flutter_pdfviewer plugin. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - syncfusion_flutter_pdfviewer: - # When depending on this package from a real application you should use: - # syncfusion_flutter_pdfviewer: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. path: ../ - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - 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 \ No newline at end of file + uses-material-design: true diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-maskable-192.png b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-maskable-512.png b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/index.html b/packages/syncfusion_flutter_pdfviewer/example/web/index.html index 2b6660b29..e4bdf5fb7 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/web/index.html +++ b/packages/syncfusion_flutter_pdfviewer/example/web/index.html @@ -1,39 +1,52 @@ + + + - + - + example - - - + - - - + + + + diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json b/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json index 160be93ba..397609518 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json +++ b/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "syncfusion_flutter_pdfviewer example", - "short_name": "syncfusion_flutter_pdfviewer", + "name": "example", + "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer/example/windows/CMakeLists.txt index 1633297a0..d960948af 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/CMakeLists.txt @@ -1,13 +1,16 @@ +# Project-level configuration. cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. set(BINARY_NAME "example") -cmake_policy(SET CMP0063 NEW) +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Configure build options. +# Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" @@ -20,7 +23,7 @@ else() "Debug" "Profile" "Release") endif() endif() - +# Define settings for the Profile build mode. set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") @@ -30,6 +33,10 @@ set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") @@ -38,14 +45,14 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") - # Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) -# Application build +# Application build; see runner/CMakeLists.txt. add_subdirectory("runner") + # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) @@ -80,6 +87,12 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/CMakeLists.txt index b2e4bd8d6..903f4899d 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/CMakeLists.txt @@ -1,3 +1,4 @@ +# This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") @@ -9,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -91,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..9871f6b72 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + SyncfusionPdfviewerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SyncfusionPdfviewerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..248772383 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + syncfusion_pdfviewer_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/CMakeLists.txt index de2d8916b..394917c05 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/CMakeLists.txt +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" @@ -10,8 +15,26 @@ add_executable(${BINARY_NAME} WIN32 "Runner.rc" "runner.exe.manifest" ) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/Runner.rc index 414643cbf..289fc7ee6 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/Runner.rc +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif @@ -89,11 +89,11 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.syncfusion" "\0" + VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2022 com.syncfusion. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/flutter_window.cpp index b43b9095e..955ee3038 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/flutter_window.cpp +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/flutter_window.cpp @@ -26,6 +26,16 @@ bool FlutterWindow::OnCreate() { } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; } diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/main.cpp b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/main.cpp index bcb57b0e2..a61bf80d3 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/main.cpp +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/main.cpp @@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"example", origin, size)) { + if (!window.Create(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/runner.exe.manifest index c977c4a42..153653e8d 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/runner.exe.manifest +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/runner.exe.manifest @@ -7,14 +7,8 @@ - + - - - - - - diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/utils.cpp index d19bdbbcc..3a0b46511 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/utils.cpp +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/utils.cpp @@ -45,18 +45,19 @@ std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } - int target_length = ::WideCharToMultiByte( + unsigned int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr); - if (target_length == 0) { - return std::string(); - } + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, utf8_string.data(), - target_length, nullptr, nullptr); + input_length, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.cpp index c10f08dc7..60608d0fe 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.cpp +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.cpp @@ -1,13 +1,31 @@ #include "win32_window.h" +#include #include #include "resource.h" namespace { +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; @@ -31,8 +49,8 @@ void EnableFullDpiSupportIfAvailable(HWND hwnd) { GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); } + FreeLibrary(user32_module); } } // namespace @@ -42,7 +60,7 @@ class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; - // Returns the singleton registar instance. + // Returns the singleton registrar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); @@ -102,9 +120,9 @@ Win32Window::~Win32Window() { Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size) { +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { Destroy(); const wchar_t* window_class = @@ -117,7 +135,7 @@ bool Win32Window::CreateAndShow(const std::wstring& title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); @@ -126,9 +144,15 @@ bool Win32Window::CreateAndShow(const std::wstring& title, return false; } + UpdateTheme(window); + return OnCreate(); } +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, @@ -188,6 +212,10 @@ Win32Window::MessageHandler(HWND hwnd, SetFocus(child_content_); } return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); @@ -243,3 +271,18 @@ bool Win32Window::OnCreate() { void Win32Window::OnDestroy() { // No-op; provided for subclasses. } + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.h index 17ba43112..e901dde68 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.h +++ b/packages/syncfusion_flutter_pdfviewer/example/windows/runner/win32_window.h @@ -28,15 +28,16 @@ class Win32Window { Win32Window(); virtual ~Win32Window(); - // Creates and shows a win32 window with |title| and position and size using + // Creates a win32 window with |title| that is positioned and sized using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size to will treat the width height passed in to this function - // as logical pixels and scale to appropriate for the default monitor. Returns - // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size); + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); // Release OS resources associated with window. void Destroy(); @@ -76,7 +77,7 @@ class Win32Window { // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically - // responsponds to changes in DPI. All other messages are handled by + // responds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, @@ -86,6 +87,9 @@ class Win32Window { // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + bool quit_on_close_ = false; // window handle for top level window. diff --git a/packages/syncfusion_flutter_pdfviewer/ios/.gitignore b/packages/syncfusion_flutter_pdfviewer/ios/.gitignore index aa479fd3c..034771fc9 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/.gitignore +++ b/packages/syncfusion_flutter_pdfviewer/ios/.gitignore @@ -34,4 +34,5 @@ Icon? .tags* /Flutter/Generated.xcconfig -/Flutter/flutter_export_environment.sh \ No newline at end of file +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh diff --git a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift index 7c18e12ed..adef47ef8 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift @@ -1,28 +1,38 @@ import Flutter import UIKit - + + + // SyncfusionFlutterPdfViewerPlugin public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { - // Document repository var documentRepo = [String: CGPDFDocument?]() + let dispatcher = DispatchQueue(label: "syncfusion_flutter_pdfviewer") + // 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 == "initializePdfRenderer") { initializePdfRenderer(call:call,result:result) } - else if(call.method == "getImage") + else if(call.method == "getPage") + { + dispatcher.async { + self.getPage(call:call,result:result) + } + } + else if(call.method == "getTileImage") { - getImage(call:call,result:result) + dispatcher.async { + self.getTileImage(call:call,result:result) + } } else if(call.method == "getPagesWidth") { @@ -42,22 +52,22 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let args = argument as? [String: Any] - let documentBytes = args!["documentBytes"] as? FlutterStandardTypedData - let documentID = args!["documentID"] as! String - let byte = [UInt8](documentBytes!.data) - let cfData = CFDataCreate(nil, byte, byte.count)! - let dataProvider = CGDataProvider(data: cfData)! - let document = CGPDFDocument(dataProvider) + guard let args = argument as? [String: Any] else {return} + guard let documentBytes = args["documentBytes"] as? FlutterStandardTypedData else {return} + guard let documentID = args["documentID"] as? String else {return} + let byte = [UInt8](documentBytes.data) + guard let cfData = CFDataCreate(nil, byte, byte.count) else {return} + guard let dataProvider = CGDataProvider(data: cfData) else {return} + guard let document = CGPDFDocument(dataProvider) else {return} self.documentRepo[documentID] = document - let pageCount = NSNumber(value: document!.numberOfPages) + let pageCount = NSNumber(value: document.numberOfPages) result(pageCount.stringValue); } private func closeDocument(call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let documentID = argument as! String + guard let documentID = argument as? String else {return} self.documentRepo[documentID] = nil self.documentRepo.removeValue(forKey: documentID) } @@ -66,16 +76,16 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { private func getPagesWidth( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else {return} + let pageCount = NSNumber(value: document!.numberOfPages) var pagesWidth = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.mediaBox) - if(page!.rotationAngle > 0) + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) { - let angle = CGFloat(page!.rotationAngle) * CGFloat.pi/180 + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) } pagesWidth.append(Double(pageRect.width)) @@ -87,16 +97,16 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { private func getPagesHeight( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else { return } + let pageCount = NSNumber(value: document!.numberOfPages) var pagesHeight = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.mediaBox) - if(page!.rotationAngle > 0) + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) { - let angle = CGFloat(page!.rotationAngle) * CGFloat.pi/180 + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) } pagesHeight.append(Double(pageRect.height)) @@ -104,51 +114,118 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { result(pagesHeight) } + // Gets the image bytes of the specified page from the document at the specified width and height. + private func getPage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let index = args["index"] as? Int else {return} + guard let width = args["width"] as? Int else {return} + guard let height = args["height"] as? Int else {return} + guard let documentID = args["documentID"] as? String else {return} + guard let image = getImageForPlugin(index: index, width: width, height: height,documentID: documentID) else { + return + } + result(image) + } + + private func getImageForPlugin(index: Int,width:Int, height:Int,documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(index)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let imageRect = CGRect(x: 0,y: 0,width: width,height: height) + let scaleX = Double(width) / Double(pageWidth) + let scaleY = Double(height) / Double(pageHeight) + let stride = width * 4 + let bufSize = stride * height; + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: width, height: height, bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.translateBy(x: 0, y: 0) + context!.scaleBy(x: scaleX, y: scaleY) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } + } + // Gets the pdf page image from the specified page - private func getImage( call: FlutterMethodCall, result: @escaping FlutterResult) + private func getTileImage( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let args = argument as? [String: Any] - let index = args!["index"] as? Int - var scale = CGFloat(args!["scale"] as! Double) - if(scale < 2) - { - scale = 2 + guard let args = argument as? [String: Any] else {return} + guard let pageNumber = args["pageNumber"] as? Int else {return} + guard let scale = args["scale"] as? Double else {return} + guard let width = args["width"] as? Double else {return} + guard let height = args["height"] as? Double else {return} + guard let x = args["x"] as? Double else {return} + guard let y = args["y"] as? Double else {return} + + guard let documentID = args["documentID"] as? String else {return} + guard let tileImage = getTileImageForPlugin(pageNumber: pageNumber, scale: CGFloat(scale), + width: width, height: height, x: x, y: y, documentID: documentID) else { + return } - let documentID = args!["documentID"] as! String - result(getImageForPlugin(index: index!,scale: scale,documentID: documentID)) + result(tileImage) } - private func getImageForPlugin(index: Int,scale: CGFloat,documentID: String) -> FlutterStandardTypedData + // Gets the image for plugin + private func getTileImageForPlugin(pageNumber: Int, scale: CGFloat, width: Double, height: Double, x: Double, y: Double, documentID: String) -> FlutterStandardTypedData? { - let document = self.documentRepo[documentID]!! - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.mediaBox) - let imageRect = CGRect(x: 0,y: 0,width: pageRect.size.width*CGFloat(scale),height: pageRect.size.height*CGFloat(scale)) - if #available(iOS 10.0, *) { - let format = UIGraphicsImageRendererFormat() - if(imageRect.width > 4000 || imageRect.height > 4000) - { - format.scale = 1 - } - if #available(iOS 12.0, *) { - format.preferredRange = .standard - } else { - format.prefersExtendedRange = false - } - let renderer = UIGraphicsImageRenderer(size: imageRect.size,format: format) - 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: imageRect.size.height) - ctx.cgContext.scaleBy(x: CGFloat(scale), y: -CGFloat(scale)) - ctx.cgContext.concatenate(transform) - ctx.cgContext.drawPDFPage(page!) - ctx.cgContext.endPage() - } - return FlutterStandardTypedData(bytes: img.pngData()!) + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(pageNumber)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let bounds = CGRect(x: -(pageWidth * scale / 2) + (pageWidth / 2) - CGFloat(x), + y: -(pageHeight * scale / 2) + (pageHeight / 2) + CGFloat(y), + width: pageWidth * scale, height: pageHeight * scale) + + let stride = Int(width) * 4 + let bufSize = stride * Int(height); + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.translateBy(x: CGFloat(-x * scale), y: CGFloat(((y * scale) + height) - bounds.height)) + context!.scaleBy(x: scale, y: scale) + context!.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + context!.fill(bounds) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil } - return FlutterStandardTypedData() } -} +} \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer.podspec b/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer.podspec index 6ba7a3334..324c4f045 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer.podspec +++ b/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer.podspec @@ -1,23 +1,21 @@ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint syncfusion_flutter_pdfviewer.podspec' to validate before publishing. +# Run `pod lib lint syncfusion_flutter_pdfviewer.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'syncfusion_flutter_pdfviewer' s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' + s.summary = 'A new Flutter plugin project.' s.description = <<-DESC -A new flutter plugin project. +A new Flutter plugin project. DESC - s.homepage = 'http://example.com' + s.homepage = 'https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'syncfusion_flutter_pdfviewer/Sources/syncfusion_flutter_pdfviewer/**/*' s.dependency 'Flutter' - s.platform = :ios, '8.0' - - # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.platform = :ios, '12.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' end diff --git a/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer/Package.swift b/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer/Package.swift new file mode 100644 index 000000000..a6e1cc5b8 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "syncfusion_flutter_pdfviewer", + platforms: [ + .iOS("12.0") + ], + products: [ + .library(name: "syncfusion-flutter-pdfviewer", targets: ["syncfusion_flutter_pdfviewer"]) + ], + dependencies: [], + targets: [ + .target( + name: "syncfusion_flutter_pdfviewer", + dependencies: [], + resources: [] + ) + ] +) \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer/Sources/syncfusion_flutter_pdfviewer/SyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer/Sources/syncfusion_flutter_pdfviewer/SyncfusionFlutterPdfViewerPlugin.swift new file mode 100644 index 000000000..9615e260a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/ios/syncfusion_flutter_pdfviewer/Sources/syncfusion_flutter_pdfviewer/SyncfusionFlutterPdfViewerPlugin.swift @@ -0,0 +1,239 @@ +import Flutter +import UIKit + + + +// SyncfusionFlutterPdfViewerPlugin +public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { + // Document repository + var documentRepo = [String: CGPDFDocument?]() + + let dispatcher = DispatchQueue(label: "syncfusion_flutter_pdfviewer") + + // Registers the SyncfusionFlutterPdfViewerPlugin + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "syncfusion_flutter_pdfviewer", binaryMessenger: registrar.messenger()) + let instance = SyncfusionFlutterPdfViewerPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + // 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 == "getPage") + { + dispatcher.async { + self.getPage(call:call,result:result) + } + } + else if(call.method == "getTileImage") + { + dispatcher.async { + self.getTileImage(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(call:call,result:result) + } + } + + // Initializes the PDF Renderer and returns the page count. + private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let documentBytes = args["documentBytes"] as? FlutterStandardTypedData else {return} + guard let documentID = args["documentID"] as? String else {return} + let password = args["password"] as? String + let byte = [UInt8](documentBytes.data) + guard let cfData = CFDataCreate(nil, byte, byte.count) else {return} + guard let dataProvider = CGDataProvider(data: cfData) else {return} + guard let document = CGPDFDocument(dataProvider) else {return} + if let password = password, document.isEncrypted { + let unlocked = document.unlockWithPassword(password) + if !unlocked { + result(FlutterError(code: "PDF_UNLOCK_FAILED", message: "Failed to unlock the PDF with the provided password", details: nil)) + return + } + } + self.documentRepo[documentID] = document + let pageCount = NSNumber(value: document.numberOfPages) + result(pageCount.stringValue); + } + + private func closeDocument(call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let documentID = argument as? String else {return} + self.documentRepo[documentID] = nil + self.documentRepo.removeValue(forKey: documentID) + } + + // Returns the width collection of rendered pages. + private func getPagesWidth( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else {return} + let pageCount = NSNumber(value: document!.numberOfPages) + var pagesWidth = Array() + for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) + { + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 + pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) + } + pagesWidth.append(Double(pageRect.width)) + } + result(pagesWidth) + } + + // Returns the height collection of rendered pages. + private func getPagesHeight( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else { return } + let pageCount = NSNumber(value: document!.numberOfPages) + var pagesHeight = Array() + for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) + { + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 + pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) + } + pagesHeight.append(Double(pageRect.height)) + } + result(pagesHeight) + } + + // Gets the image bytes of the specified page from the document at the specified width and height. + private func getPage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let index = args["index"] as? Int else {return} + guard let width = args["width"] as? Int else {return} + guard let height = args["height"] as? Int else {return} + guard let documentID = args["documentID"] as? String else {return} + guard let image = getImageForPlugin(index: index, width: width, height: height,documentID: documentID) else { + return + } + result(image) + } + + private func getImageForPlugin(index: Int,width:Int, height:Int,documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(index)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let imageRect = CGRect(x: 0,y: 0,width: width,height: height) + let scaleX = Double(width) / Double(pageWidth) + let scaleY = Double(height) / Double(pageHeight) + let stride = width * 4 + let bufSize = stride * height; + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: width, height: height, bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.translateBy(x: 0, y: 0) + context!.scaleBy(x: scaleX, y: scaleY) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } + } + + // Gets the pdf page image from the specified page + private func getTileImage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let pageNumber = args["pageNumber"] as? Int else {return} + guard let scale = args["scale"] as? Double else {return} + guard let width = args["width"] as? Double else {return} + guard let height = args["height"] as? Double else {return} + guard let x = args["x"] as? Double else {return} + guard let y = args["y"] as? Double else {return} + + guard let documentID = args["documentID"] as? String else {return} + guard let tileImage = getTileImageForPlugin(pageNumber: pageNumber, scale: CGFloat(scale), + width: width, height: height, x: x, y: y, documentID: documentID) else { + return + } + result(tileImage) + } + + // Gets the image for plugin + private func getTileImageForPlugin(pageNumber: Int, scale: CGFloat, width: Double, height: Double, x: Double, y: Double, documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(pageNumber)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let bounds = CGRect(x: -(pageWidth * scale / 2) + (pageWidth / 2) - CGFloat(x), + y: -(pageHeight * scale / 2) + (pageHeight / 2) + CGFloat(y), + width: pageWidth * scale, height: pageHeight * scale) + + let stride = Int(width) * 4 + let bufSize = stride * Int(height); + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.translateBy(x: CGFloat(-x * scale), y: CGFloat(((y * scale) + height) - bounds.height)) + context!.scaleBy(x: scale, y: scale) + context!.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + context!.fill(bounds) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart index 88ab32027..2e1429b78 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart @@ -1,6 +1,44 @@ +/// Syncfusion Flutter PDF viewer lets you display the PDF document seamlessly and efficiently. +/// It is built in the way that a large PDF document can be opened in +/// minimal time and all their pages can be accessed spontaneously. +/// +/// To use, import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=f1zEJZRdo7w} +/// +/// See also: +/// * [Syncfusion Flutter PDF Viewer product page](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer) +/// * [User guide documentation](https://help.syncfusion.com/flutter/pdf-viewer/overview) +/// * [Video tutorials](https://www.syncfusion.com/tutorial-videos/flutter/pdf-viewer) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter) + library pdfviewer; +export 'src/annotation/annotation.dart' show Annotation; +export 'src/annotation/annotation_settings.dart' + show + PdfBaseAnnotationSettings, + PdfAnnotationAppearanceSetting, + PdfAnnotationSelectorSettings, + PdfAnnotationSettings, + PdfTextMarkupAnnotationSettings, + PdfStickyNoteAnnotationSettings; +export 'src/annotation/sticky_notes.dart' show StickyNoteAnnotation; +export 'src/annotation/text_markup.dart' + show + HighlightAnnotation, + StrikethroughAnnotation, + UnderlineAnnotation, + SquigglyAnnotation; +export 'src/common/pdf_source.dart'; export 'src/control/enums.dart'; export 'src/control/pdftextline.dart'; export 'src/control/pdfviewer_callback_details.dart'; +export 'src/form_fields/pdf_checkbox.dart' show PdfCheckboxFormField; +export 'src/form_fields/pdf_combo_box.dart' show PdfComboBoxFormField; +export 'src/form_fields/pdf_form_field.dart' show PdfFormField; +export 'src/form_fields/pdf_list_box.dart' show PdfListBoxFormField; +export 'src/form_fields/pdf_radio_button.dart' show PdfRadioFormField; +export 'src/form_fields/pdf_signature.dart' show PdfSignatureFormField; +export 'src/form_fields/pdf_text_box.dart' show PdfTextFormField; export 'src/pdfviewer.dart'; diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation.dart new file mode 100644 index 000000000..1ec2b41c0 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation.dart @@ -0,0 +1,305 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import 'annotation_view.dart'; +import 'text_markup.dart'; + +/// Callback definition for annotation property change. +typedef AnnotationPropertyChangedCallback = + void Function( + Annotation annotation, + String propertyName, + Object oldValue, + Object newValue, + ); + +/// Callback definition for annotation property change. +typedef AnnotationPropertyChangingCallback = + bool Function(Annotation annotation, String propertyName); + +/// Represents a PDF annotation. +abstract class Annotation extends ChangeNotifier { + /// Creates a new instance of the [Annotation] class. + Annotation({required int pageNumber}) : assert(pageNumber > 0) { + _pageNumber = pageNumber; + } + + bool _isLocked = false; + int _pageNumber = -1; + Rect _boundingBox = Rect.zero; + Rect _intermediateBounds = Rect.zero; + Color _color = Colors.transparent; + double _opacity = -1; + int _zOrder = -1; + bool _isSelected = false; + Rect _globalRect = Rect.zero; + AnnotationPropertyChangedCallback? _onPropertyChanged; + AnnotationPropertyChangingCallback? _onPropertyChange; + + /// The page number of the annotation. + int get pageNumber => _pageNumber; + + /// The name of the annotation. + String? name; + + /// The subject of the annotation. + String? subject; + + /// The author of the annotation. + String? author; + + /// The color of the [Annotation]. + Color get color => _color; + set color(Color newValue) { + if (_color != newValue) { + final bool canChange = _onPropertyChange?.call(this, 'color') ?? true; + if (canChange) { + final Color oldValue = _color; + _color = newValue; + _onPropertyChanged?.call(this, 'color', oldValue, newValue); + _notify(); + } + } + } + + /// The opacity of the [Annotation]. + double get opacity => _opacity; + set opacity(double newValue) { + if (_opacity != newValue) { + final bool canChange = _onPropertyChange?.call(this, 'opacity') ?? true; + if (canChange) { + final double oldValue = _opacity; + _opacity = newValue; + _onPropertyChanged?.call(this, 'opacity', oldValue, newValue); + _notify(); + } + } + } + + /// The lock state of the [Annotation]. + bool get isLocked => _isLocked; + set isLocked(bool newValue) { + if (_isLocked != newValue) { + final bool oldValue = _isLocked; + _isLocked = newValue; + _onPropertyChanged?.call(this, 'isLocked', oldValue, newValue); + _notify(); + } + } + + /// To notify the annotation changes. + void _notify() { + notifyListeners(); + } +} + +/// Extension methods for [Annotation]. +extension AnnotationExtension on Annotation { + /// Returns the [Rect] bounds of the [Annotation]. + Rect get boundingBox => _boundingBox; + + /// Returns the [Rect] bounds of the [Annotation]. + Rect get uiBounds => + isSelected + ? _intermediateBounds.inflate(selectionBorderMargin) + : _boundingBox.inflate(selectionBorderMargin); + + /// Return the global bounds of the [Annotation]. + Rect get globalRect => _globalRect; + set globalRect(Rect value) { + _globalRect = value; + } + + /// Callback definition for annotation property change. + AnnotationPropertyChangedCallback? get onPropertyChanged => + _onPropertyChanged; + set onPropertyChanged(AnnotationPropertyChangedCallback? value) { + _onPropertyChanged = value; + } + + /// Callback definition for annotation property change. + AnnotationPropertyChangingCallback? get onPropertyChange => _onPropertyChange; + set onPropertyChange(AnnotationPropertyChangingCallback? value) { + _onPropertyChange = value; + } + + /// The z-order of the [Annotation]. + int get zOrder => _zOrder; + set zOrder(int value) { + _zOrder = value; + } + + /// The selection state of the [Annotation]. + bool get isSelected => _isSelected; + set isSelected(bool value) { + _isSelected = value; + notifyChange(); + } + + /// Sets the [Rect] bounds of the [Annotation]. + void setBounds(Rect bounds) { + _boundingBox = bounds; + _intermediateBounds = bounds; + notifyChange(); + } + + /// Sets the color of the [Annotation]. + void setColor(Color value) { + _color = value; + notifyChange(); + } + + /// Sets the opacity of the [Annotation]. + void setOpacity(double value) { + _opacity = value; + notifyChange(); + } + + /// Sets the lock state of the [Annotation]. + void setIsLocked(bool value) { + _isLocked = value; + notifyChange(); + } + + /// Intermediate bounds of the [Annotation]. + /// Used when moving the annotation. + Rect get intermediateBounds => _intermediateBounds; + set intermediateBounds(Rect value) { + _intermediateBounds = value; + notifyChange(); + } + + /// Gets whether the annotation can be edited i.e., it is not locked + bool get canEdit => _onPropertyChange?.call(this, '') ?? true; + + /// Saves the [Annotation] to the given [PdfPage]. + PdfAnnotation saveToPage(PdfPage page, PdfAnnotation? pdfAnnotation) { + final Annotation annotation = this; + final String name = annotation.name ?? ''; + + if (pdfAnnotation == null) { + if (annotation is HighlightAnnotation || + annotation is StrikethroughAnnotation || + annotation is UnderlineAnnotation || + annotation is SquigglyAnnotation) { + final PdfColor pdfColor = annotation.color.pdfColor; + PdfTextMarkupAnnotationType type = + PdfTextMarkupAnnotationType.highlight; + List boundsCollection = []; + + if (annotation is HighlightAnnotation) { + boundsCollection = annotation.textMarkupRects; + type = PdfTextMarkupAnnotationType.highlight; + } else if (annotation is StrikethroughAnnotation) { + boundsCollection = annotation.textMarkupRects; + type = PdfTextMarkupAnnotationType.strikethrough; + } else if (annotation is UnderlineAnnotation) { + boundsCollection = annotation.textMarkupRects; + type = PdfTextMarkupAnnotationType.underline; + } else if (annotation is SquigglyAnnotation) { + boundsCollection = annotation.textMarkupRects; + type = PdfTextMarkupAnnotationType.squiggly; + } + if (boundsCollection.isNotEmpty) { + final PdfTextMarkupAnnotation pdfTextMarkupAnnotation = + PdfTextMarkupAnnotation(annotation.boundingBox, name, pdfColor); + pdfTextMarkupAnnotation.textMarkupAnnotationType = type; + if (annotation.author != null && annotation.author!.isNotEmpty) { + pdfTextMarkupAnnotation.author = annotation.author!; + } + if (annotation.subject != null && annotation.subject!.isNotEmpty) { + pdfTextMarkupAnnotation.subject = annotation.subject!; + } + + pdfAnnotation = pdfTextMarkupAnnotation; + pdfTextMarkupAnnotation.boundsCollection.addAll(boundsCollection); + } + } else if (annotation is StickyNoteAnnotation) { + final PdfPopupAnnotation pdfPopupAnnotation = PdfPopupAnnotation( + annotation._boundingBox, + annotation.text, + ); + pdfPopupAnnotation.icon = annotation.icon.pdfPopupIcon; + if (annotation.author != null && annotation.author!.isNotEmpty) { + pdfPopupAnnotation.author = annotation.author!; + } + if (annotation.subject != null && annotation.subject!.isNotEmpty) { + pdfPopupAnnotation.subject = annotation.subject!; + } + pdfAnnotation = pdfPopupAnnotation; + } + page.annotations.add(pdfAnnotation!); + } + + if (pdfAnnotation is PdfTextMarkupAnnotation) { + pdfAnnotation.color = annotation.color.pdfColor; + pdfAnnotation.setAppearance = true; + } else if (pdfAnnotation is PdfPopupAnnotation) { + pdfAnnotation.color = annotation.color.pdfColor; + pdfAnnotation.bounds = annotation.boundingBox; + if (annotation is StickyNoteAnnotation) { + pdfAnnotation.text = annotation.text; + pdfAnnotation.icon = annotation.icon.pdfPopupIcon; + } + } + pdfAnnotation.opacity = annotation.opacity; + + if (annotation.isLocked) { + if (pdfAnnotation is PdfPopupAnnotation) { + pdfAnnotation.annotationFlags = [ + PdfAnnotationFlags.locked, + PdfAnnotationFlags.print, + PdfAnnotationFlags.noZoom, + PdfAnnotationFlags.noRotate, + ]; + } else { + pdfAnnotation.annotationFlags = [ + PdfAnnotationFlags.print, + PdfAnnotationFlags.locked, + ]; + } + } else { + if (pdfAnnotation is PdfPopupAnnotation) { + pdfAnnotation.annotationFlags = [ + PdfAnnotationFlags.print, + PdfAnnotationFlags.noZoom, + PdfAnnotationFlags.noRotate, + ]; + } else { + pdfAnnotation.annotationFlags = [ + PdfAnnotationFlags.print, + ]; + } + } + + return pdfAnnotation; + } + + /// Notify the internal changes. + void notifyChange() { + _notify(); + } +} + +/// Extension methods for [PdfStickyNoteIcon]. +extension on PdfStickyNoteIcon { + PdfPopupIcon get pdfPopupIcon { + switch (this) { + case PdfStickyNoteIcon.comment: + return PdfPopupIcon.comment; + case PdfStickyNoteIcon.key: + return PdfPopupIcon.key; + case PdfStickyNoteIcon.note: + return PdfPopupIcon.note; + case PdfStickyNoteIcon.help: + return PdfPopupIcon.help; + case PdfStickyNoteIcon.newParagraph: + return PdfPopupIcon.newParagraph; + case PdfStickyNoteIcon.paragraph: + return PdfPopupIcon.paragraph; + case PdfStickyNoteIcon.insert: + return PdfPopupIcon.insert; + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_container.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_container.dart new file mode 100644 index 000000000..b4b842d07 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_container.dart @@ -0,0 +1,304 @@ +import 'package:flutter/material.dart'; +import 'annotation.dart'; +import 'annotation_settings.dart'; +import 'annotation_view.dart' show selectionBorderThickness; +import 'sticky_notes.dart'; +import 'text_markup.dart'; + +/// Annotation container widget. +class AnnotationContainer extends StatefulWidget { + /// Constructor for annotation container widget. + const AnnotationContainer({ + required this.annotations, + required this.pageSize, + required this.annotationSettings, + this.selectedAnnotation, + this.onAnnotationSelectionChanged, + this.onStickyNoteDoubleTapped, + this.isZooming = false, + this.heightPercentage = 1, + this.zoomLevel = 1, + this.pageNumber = 0, + this.onDragStart, + this.onDragEnd, + required this.onTap, + super.key, + }); + + /// The annotation settings. + final PdfAnnotationSettings annotationSettings; + + /// The annotations. + final Iterable annotations; + + /// The selected annotation. + final Annotation? selectedAnnotation; + + /// The page size. + final Size pageSize; + + /// Called when the selected annotation is changed. + final void Function(Annotation?)? onAnnotationSelectionChanged; + + /// Triggered when the sticky note annotation is double tapped. + final void Function(Annotation)? onStickyNoteDoubleTapped; + + /// The height percentage. + final double heightPercentage; + + /// Current zoom level + final double zoomLevel; + + /// The page number. + final int pageNumber; + + /// Called when the drag starts. + final VoidCallback? onDragStart; + + /// Called when the drag ends. + final VoidCallback? onDragEnd; + + /// Called when the tap is done. + final Function(Offset) onTap; + + final bool isZooming; + + @override + State createState() => _AnnotationContainerState(); +} + +class _AnnotationContainerState extends State { + Annotation? _selectedAnnotation; + @override + Widget build(BuildContext context) { + _selectedAnnotation = widget.selectedAnnotation; + List annotations = []; + if (_selectedAnnotation != null && + _selectedAnnotation!.pageNumber == widget.pageNumber) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _updateAnnotationGlobalRect(_selectedAnnotation!); + }); + for (final Annotation annotation in widget.annotations) { + if (annotation != _selectedAnnotation) { + annotations.add(annotation); + } + } + } else { + annotations = widget.annotations.toList(); + } + annotations.sort((Annotation a, Annotation b) { + return a.zOrder.compareTo(b.zOrder); + }); + return Listener( + onPointerUp: (PointerUpEvent details) { + widget.onTap(details.position); + }, + child: Stack( + children: [ + for (final Annotation annotation in annotations) + // Annotations with empty annotation bounds will not be rendered in the view. + if (!annotation.boundingBox.isEmpty) + _getPositionedAnnotationView(annotation), + if (_selectedAnnotation != null && + !_selectedAnnotation!.boundingBox.isEmpty && + widget.pageNumber == widget.selectedAnnotation!.pageNumber) + ListenableBuilder( + listenable: Listenable.merge([ + widget.selectedAnnotation!, + _getTypeSettings(_selectedAnnotation!), + _selectedAnnotation!, + ]), + builder: (BuildContext context, Widget? child) { + return _getPositionedAnnotationView(_selectedAnnotation!); + }, + ), + ], + ), + ); + } + + Widget _getPositionedAnnotationView(Annotation annotation) { + if (annotation is StickyNoteAnnotation) { + return ListenableBuilder( + listenable: annotation, + builder: (BuildContext context, Widget? child) { + return Positioned( + left: annotation.uiBounds.left / widget.heightPercentage, + top: annotation.uiBounds.top / widget.heightPercentage, + width: annotation.uiBounds.width / widget.zoomLevel, + height: annotation.uiBounds.height / widget.zoomLevel, + child: Visibility( + visible: !widget.isZooming, + child: _getAnnotationView(annotation), + ), + ); + }, + ); + } else { + return Positioned( + left: annotation.uiBounds.left / widget.heightPercentage, + top: annotation.uiBounds.top / widget.heightPercentage, + width: annotation.uiBounds.width / widget.heightPercentage, + height: annotation.uiBounds.height / widget.heightPercentage, + child: ListenableBuilder( + listenable: annotation, + builder: (BuildContext context, Widget? child) { + return _getAnnotationView(annotation); + }, + ), + ); + } + } + + bool _isLocked(Annotation annotation) { + return !widget.annotationSettings.canEdit(annotation); + } + + PdfBaseAnnotationSettings _getTypeSettings(Annotation annotation) { + if (annotation is HighlightAnnotation) { + return widget.annotationSettings.highlight; + } else if (annotation is StrikethroughAnnotation) { + return widget.annotationSettings.strikethrough; + } else if (annotation is UnderlineAnnotation) { + return widget.annotationSettings.underline; + } else if (annotation is SquigglyAnnotation) { + return widget.annotationSettings.squiggly; + } else if (annotation is StickyNoteAnnotation) { + return widget.annotationSettings.stickyNote; + } else { + throw ArgumentError.value( + annotation, + 'annotation', + 'The annotation type is not supported.', + ); + } + } + + Widget _getAnnotationView(Annotation annotation) { + Widget? annotationView; + + if (annotation is HighlightAnnotation || + annotation is StrikethroughAnnotation || + annotation is UnderlineAnnotation || + annotation is SquigglyAnnotation) { + annotationView = TextMarkupAnnotationView( + key: ValueKey(annotation), + annotation: annotation, + isSelected: annotation == _selectedAnnotation, + heightPercentage: widget.heightPercentage, + selectorColor: + _isLocked(annotation) + ? widget.annotationSettings.selector.lockedColor + : widget.annotationSettings.selector.color, + ); + } else if (annotation is StickyNoteAnnotation) { + final bool isLocked = _isLocked(annotation); + annotationView = StickyNoteAnnotationView( + key: ValueKey(annotation), + annotation: annotation, + isSelected: annotation == _selectedAnnotation, + zoomLevel: widget.zoomLevel, + canEdit: !isLocked, + selectorColor: + isLocked + ? widget.annotationSettings.selector.lockedColor + : widget.annotationSettings.selector.color, + selectorStorkeWidth: selectionBorderThickness / widget.zoomLevel, + onAnnotationMoved: annotation.isSelected ? onAnnotationMoved : null, + onAnnotationMoving: annotation.isSelected ? onAnnotationMoving : null, + onTap: () { + if (!annotation.isSelected) { + onAnnotationSelectionChanged(annotation); + } + }, + onDoubleTap: () { + if (!annotation.isSelected) { + onAnnotationSelectionChanged(annotation); + } + widget.onStickyNoteDoubleTapped?.call(annotation); + }, + ); + } + + if (annotationView != null) { + return annotationView; + } else { + return const SizedBox.shrink(); + } + } + + void onAnnotationSelectionChanged(Annotation? annotation) { + if (annotation != null) { + _updateAnnotationGlobalRect(annotation); + } + setState(() { + _selectedAnnotation = annotation; + widget.onAnnotationSelectionChanged?.call(annotation); + }); + } + + void onAnnotationMoved(Annotation annotation, Offset newPosition) { + if (annotation is StickyNoteAnnotation) { + annotation.position = annotation.intermediateBounds.topLeft; + } + _updateAnnotationGlobalRect(annotation); + } + + void onAnnotationMoving(Annotation annotation, Offset delta) { + if (_isLocked(annotation)) { + return; + } + Offset newPosition = + annotation.intermediateBounds.topLeft + delta * widget.heightPercentage; + + if (newPosition.dx < 0) { + newPosition = Offset(0, newPosition.dy); + } + if (newPosition.dy < 0) { + newPosition = Offset(newPosition.dx, 0); + } + if (newPosition.dx + + (annotation.intermediateBounds.width * widget.heightPercentage) > + widget.pageSize.width) { + newPosition = Offset( + widget.pageSize.width - + (annotation.intermediateBounds.width * widget.heightPercentage), + newPosition.dy, + ); + } + if (newPosition.dy + + (annotation.intermediateBounds.height * widget.heightPercentage) > + widget.pageSize.height) { + newPosition = Offset( + newPosition.dx, + widget.pageSize.height - + (annotation.intermediateBounds.height * widget.heightPercentage), + ); + } + if (annotation is StickyNoteAnnotation) { + annotation.intermediateBounds = newPosition & annotation.boundingBox.size; + } + } + + void _updateAnnotationGlobalRect(Annotation annotation) { + if (annotation is StickyNoteAnnotation) { + if (!mounted) { + return; + } + final renderObject = context.findRenderObject(); + if (renderObject is RenderBox) { + final Rect scaledRect = + annotation.uiBounds.topLeft & + annotation.uiBounds.size * widget.heightPercentage; + annotation.globalRect = Rect.fromPoints( + renderObject.localToGlobal( + scaledRect.topLeft / widget.heightPercentage, + ), + renderObject.localToGlobal( + scaledRect.bottomRight / widget.heightPercentage, + ), + ); + } + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_settings.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_settings.dart new file mode 100644 index 000000000..5c0a802fb --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_settings.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import '../control/enums.dart'; +import 'annotation.dart'; +import 'annotation_view.dart'; +import 'sticky_notes.dart'; +import 'text_markup.dart'; + +/// Base class for the AnnotationSettings and AnnotationAppearanceSettings. +class PdfBaseAnnotationSettings with ChangeNotifier { + bool _isLocked = false; + + /// Gets or sets a value indicating whether annotations should be locked. + bool get isLocked => _isLocked; + set isLocked(bool value) { + if (_isLocked != value) { + _isLocked = value; + notifyListeners(); + } + } +} + +/// Represents the settings that allows to customize the default appearance of annotations. +class PdfAnnotationAppearanceSetting extends PdfBaseAnnotationSettings { + /// Gets or sets the default stroke color of annotations. + Color color = defaultStrokeColor; + + /// Gets or sets the default opacity of annotations. + double opacity = defaultOpacity; +} + +/// Represents the settings that allows to customize the default appearance of annotation selector. +class PdfAnnotationSelectorSettings { + /// Color color Gets or sets the default color of annotation selector. + Color color = defaultSelectorColor; + + /// Gets or sets the default color of annotation selector when the selected annotation is locked. + Color lockedColor = defaultLockedSelectorColor; +} + +/// Represents the settings that allows to customize the default appearance of text markup annotations. +class PdfTextMarkupAnnotationSettings extends PdfAnnotationAppearanceSetting {} + +/// Represents the settings that allows to customize the default appearance of sticky note annotations. +class PdfStickyNoteAnnotationSettings extends PdfAnnotationAppearanceSetting { + /// Gets or sets the default icon of sticky note annotations. + PdfStickyNoteIcon icon = PdfStickyNoteIcon.comment; +} + +/// Represents the settings that allows to customize the default appearance and behavior of annotations. +class PdfAnnotationSettings extends PdfBaseAnnotationSettings { + /// Gets or sets a value that indicates the default author name for all annotations in the PDF. + String author = ''; + + /// Gets or sets the default settings for highlight annotations. The default color is yellow + PdfTextMarkupAnnotationSettings highlight = + PdfTextMarkupAnnotationSettings()..color = Colors.yellow; + + /// Gets or sets the default settings for underline annotations. The default color is green. + PdfTextMarkupAnnotationSettings underline = + PdfTextMarkupAnnotationSettings()..color = Colors.green; + + /// Gets or sets the default settings for strikethrough annotations. The default color is red. + PdfTextMarkupAnnotationSettings strikethrough = + PdfTextMarkupAnnotationSettings()..color = Colors.red; + + /// Gets or sets the default settings for squiggly annotations. The default color is green. + PdfTextMarkupAnnotationSettings squiggly = + PdfTextMarkupAnnotationSettings()..color = Colors.green; + + /// Gets or sets the default settings for sticky note annotations. The default color is yellow. Default icon is Comment + PdfStickyNoteAnnotationSettings stickyNote = + PdfStickyNoteAnnotationSettings() + ..color = Colors.yellow + ..icon = PdfStickyNoteIcon.comment; + + /// Gets or sets the default settings for the annotation selector. + PdfAnnotationSelectorSettings selector = PdfAnnotationSelectorSettings(); +} + +/// Internal extension methods for PdfAnnotationSettings. +extension AnnotationSettingsExtension on PdfAnnotationSettings { + /// Checks whether the annotation can be edited. + bool canEdit(Annotation annotation) { + bool isTypeLocked = false; + + if (annotation is HighlightAnnotation) { + isTypeLocked = highlight.isLocked; + } else if (annotation is StrikethroughAnnotation) { + isTypeLocked = strikethrough.isLocked; + } else if (annotation is UnderlineAnnotation) { + isTypeLocked = underline.isLocked; + } else if (annotation is SquigglyAnnotation) { + isTypeLocked = squiggly.isLocked; + } else if (annotation is StickyNoteAnnotation) { + isTypeLocked = stickyNote.isLocked; + } + + return !isLocked && !isTypeLocked && !annotation.isLocked; + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_view.dart new file mode 100644 index 000000000..39b17c1d2 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/annotation_view.dart @@ -0,0 +1,311 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'annotation.dart'; + +/// The default stroke color for annotations. +const Color defaultStrokeColor = Colors.red; + +/// The default opacity for annotations. +const double defaultOpacity = 1; + +/// The default selection border margin for annotations. +double selectionBorderMargin = 2; + +/// The default selection border thickness for annotations. +const double selectionBorderThickness = 1.5; + +/// The default color for the annotation selector. +const Color defaultSelectorColor = Color(0xFF6750A4); + +/// The default color for the annotation selector when the annotation is locked. +const Color defaultLockedSelectorColor = Colors.grey; + +/// Signature for annotation moved callback. +typedef AnnotationMoveEndedCallback = void Function(Annotation, Offset); + +/// Signature for annotation moving callback. +typedef AnnotationMovingCallback = void Function(Annotation, Offset); + +/// A widget representing an annotation. +mixin AnnotationView { + /// [Annotation] instance. + late final Annotation annotation; +} + +/// Base Widget for all the annotation views. +class InteractiveGraphicsView extends LeafRenderObjectWidget { + /// Creates a [InteractiveGraphicsView]. + const InteractiveGraphicsView({ + Key? key, + this.color = defaultStrokeColor, + this.opacity = defaultOpacity, + this.fillColor, + this.strokeWidth = 5, + this.isSelected = false, + this.canMove = true, + this.selectorColor = defaultSelectorColor, + this.selectorStorkeWidth = selectionBorderThickness, + }) : super(key: key); + + /// The color of the annotation. + final Color color; + + /// The opacity of the annotation. + final double opacity; + + /// The fill color of the annotation. + final Color? fillColor; + + /// The stroke width of the annotation. + final int strokeWidth; + + /// Whether the annotation is selected. + final bool isSelected; + + /// Whether the annotation can be moved. + final bool canMove; + + /// The color of the annotation selector. + final Color selectorColor; + + /// The stroke width of the annotation selector. + final double selectorStorkeWidth; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderInteractiveGraphicsView( + strokeColor: color, + opacity: opacity, + fillColor: fillColor, + strokeWidth: strokeWidth, + isSelected: isSelected, + canMove: canMove, + selectorColor: selectorColor, + selectorStorkeWidth: selectorStorkeWidth, + ); + } + + @override + void updateRenderObject( + BuildContext context, + covariant RenderInteractiveGraphicsView renderObject, + ) { + renderObject + ..color = color + ..opacity = opacity + ..fillColor = fillColor + ..strokeWidth = strokeWidth + ..isSelected = isSelected + ..canEdit = canMove + ..selectorColor = selectorColor + ..selectorStorkeWidth = selectorStorkeWidth; + } +} + +/// The render object for [InteractiveGraphicsView]. +class RenderInteractiveGraphicsView extends RenderBox { + /// Creates a [RenderInteractiveGraphicsView]. + RenderInteractiveGraphicsView({ + required Color strokeColor, + required double opacity, + Color? fillColor, + required int strokeWidth, + required bool isSelected, + required Color selectorColor, + bool canMove = true, + double selectorStorkeWidth = selectionBorderThickness, + }) { + _color = strokeColor; + _opacity = opacity; + _fillColor = fillColor; + _strokeWidth = strokeWidth; + _isSelected = isSelected; + _canMove = canMove; + _selectorColor = selectorColor; + _selectorStorkeWidth = selectorStorkeWidth; + + tapGestureRecognizer = + TapGestureRecognizer() + ..onTap = onTap + ..onTapDown = onTapDown + ..onTapUp = onTapUp + ..onTapCancel = onTapCancel; + + panGestureRecognizer = + PanGestureRecognizer() + ..onDown = onDragDown + ..onStart = onDragStart + ..onEnd = onDragEnd + ..onUpdate = onDragUpdate + ..onCancel = onDragCancel + ..gestureSettings = const DeviceGestureSettings(touchSlop: 0.0); + } + + late Color _color; + late double _opacity; + late Color? _fillColor; + late int _strokeWidth; + late bool _isSelected; + late bool _canMove; + late Color _selectorColor; + late double _selectorStorkeWidth; + + /// The color of the annotation. + Color get color => _color; + set color(Color value) { + if (_color == value) { + return; + } + _color = value; + markNeedsPaint(); + } + + /// The opacity of the annotation. + double get opacity => _opacity; + set opacity(double value) { + if (_opacity == value) { + return; + } + _opacity = value; + markNeedsPaint(); + } + + /// The fill color of the annotation. + Color? get fillColor => _fillColor; + set fillColor(Color? value) { + if (_fillColor == value) { + return; + } + _fillColor = value; + markNeedsPaint(); + } + + /// The stroke width of the annotation. + int get strokeWidth => _strokeWidth; + set strokeWidth(int value) { + if (_strokeWidth == value) { + return; + } + _strokeWidth = value; + markNeedsPaint(); + } + + /// Whether the annotation is selected. + bool get isSelected => _isSelected; + set isSelected(bool value) { + if (_isSelected == value) { + return; + } + _isSelected = value; + markNeedsPaint(); + } + + /// Whether the annotation can be moved. + bool get canEdit => _canMove; + set canEdit(bool value) { + if (_canMove == value) { + return; + } + _canMove = value; + } + + /// The color of the annotation selector. + Color get selectorColor => _selectorColor; + set selectorColor(Color value) { + if (_selectorColor == value) { + return; + } + _selectorColor = value; + markNeedsPaint(); + } + + /// The stroke width of the annotation selector. + double get selectorStorkeWidth => _selectorStorkeWidth; + set selectorStorkeWidth(double value) { + if (_selectorStorkeWidth == value) { + return; + } + _selectorStorkeWidth = value; + markNeedsPaint(); + } + + /// The tap gesture recognizer. + late TapGestureRecognizer tapGestureRecognizer; + + /// The pan gesture recognizer. + late PanGestureRecognizer panGestureRecognizer; + + @override + bool hitTestSelf(Offset position) => true; + + @override + void handleEvent(PointerEvent event, covariant BoxHitTestEntry entry) { + if (event is PointerDownEvent) { + tapGestureRecognizer.addPointer(event); + if (canEdit) { + panGestureRecognizer.addPointer(event); + } + } + } + + @override + bool get sizedByParent => true; + + @override + void performResize() { + size = constraints.biggest; + } + + @override + void paint(PaintingContext context, Offset offset) { + if (isSelected) { + drawSelectionBounds(context, offset); + } + } + + /// Draws the selection bounds for the annotation. + void drawSelectionBounds(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + final Rect selectorBounds = Rect.fromLTWH( + offset.dx, + offset.dy, + size.width, + size.height, + ); + final Paint selectorPaint = + Paint() + ..color = selectorColor + ..strokeWidth = selectorStorkeWidth + ..style = PaintingStyle.stroke; + canvas.drawRect(selectorBounds, selectorPaint); + } + + /// Override this method to handle tap down event. + void onTapDown(TapDownDetails details) {} + + /// Override this method to handle tap up event. + void onTapUp(TapUpDetails details) {} + + /// Override this method to handle tap cancel event. + void onTapCancel() {} + + /// Override this method to handle tap event. + void onTap() {} + + /// Override this method to handle drag down event. + void onDragDown(DragDownDetails details) {} + + /// Override this method to handle drag start event. + void onDragStart(DragStartDetails details) {} + + /// Override this method to handle drag end event. + void onDragEnd(DragEndDetails details) {} + + /// Override this method to handle drag update event. + void onDragUpdate(DragUpdateDetails details) {} + + /// Override this method to handle drag cancel event. + void onDragCancel() {} +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/sticky_notes.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/sticky_notes.dart new file mode 100644 index 000000000..52e8133ad --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/sticky_notes.dart @@ -0,0 +1,563 @@ +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../control/enums.dart'; +import 'annotation.dart'; +import 'annotation_view.dart'; + +/// Represents the sticky note annotation in the page. +class StickyNoteAnnotation extends Annotation { + /// Initializes a new instance of [StickyNoteAnnotation] class. + StickyNoteAnnotation({ + required super.pageNumber, + required String text, + required Offset position, + required PdfStickyNoteIcon icon, + }) { + _text = text; + _icon = icon; + setBounds(_getDefualtStickyNoteSize(position)); + } + + String _text = ''; + late PdfStickyNoteIcon _icon; + int _pageRotation = 0; + + /// Gets or sets the text of the [StickyNoteAnnotation]. + String get text => _text; + set text(String newValue) { + if (_text != newValue) { + final bool canChange = onPropertyChange?.call(this, 'text') ?? true; + if (canChange) { + final String oldValue = _text; + _text = newValue; + onPropertyChanged?.call(this, 'text', oldValue, newValue); + notifyChange(); + } + } + } + + /// Gets or sets the icon of [StickyNoteAnnotation]. + PdfStickyNoteIcon get icon => _icon; + set icon(PdfStickyNoteIcon newValue) { + if (_icon != newValue) { + final bool canChange = onPropertyChange?.call(this, 'icon') ?? true; + if (canChange) { + final PdfStickyNoteIcon oldValue = _icon; + _icon = newValue; + setBounds(_getDefualtStickyNoteSize(position)); + onPropertyChanged?.call(this, 'icon', oldValue, newValue); + notifyChange(); + } + } + } + + /// Gets or sets the position of the [StickyNoteAnnotation]. + Offset get position => boundingBox.topLeft; + set position(Offset newValue) { + if (position.dx != newValue.dx || position.dy != newValue.dy) { + final bool canChange = onPropertyChange?.call(this, 'position') ?? true; + if (canChange) { + final Offset oldValue = position; + setBounds(newValue & boundingBox.size); + onPropertyChanged?.call(this, 'position', oldValue, newValue); + notifyChange(); + } + } + } + + /// Default sticky note size. + Rect _getDefualtStickyNoteSize(Offset position) { + switch (icon) { + case PdfStickyNoteIcon.comment: + return Rect.fromLTWH(position.dx, position.dy, 24, 24); + case PdfStickyNoteIcon.note: + return Rect.fromLTWH(position.dx, position.dy, 18, 20); + case PdfStickyNoteIcon.help: + return Rect.fromLTWH(position.dx, position.dy, 20, 20); + case PdfStickyNoteIcon.insert: + return Rect.fromLTWH(position.dx, position.dy, 17, 20); + case PdfStickyNoteIcon.key: + return Rect.fromLTWH(position.dx, position.dy, 13, 18); + case PdfStickyNoteIcon.newParagraph: + return Rect.fromLTWH(position.dx, position.dy, 13, 20); + case PdfStickyNoteIcon.paragraph: + return Rect.fromLTWH(position.dx, position.dy, 20, 20); + } + } +} + +/// Extension methods for [StickyNoteAnnotation]. +extension StickyNoteAnnotationExtension on StickyNoteAnnotation { + /// Sets the pageRotation of the [StickyNoteAnnotation]. + int get pageRotation => _pageRotation; + set pageRotation(int value) { + _pageRotation = value; + } + + /// Sets the icon of the [StickyNoteAnnotation]. + void setIcon(PdfStickyNoteIcon icon) { + _icon = icon; + setBounds(_getDefualtStickyNoteSize(position)); + notifyChange(); + } + + /// Sets the position of the [StickyNoteAnnotation]. + void setPosition(Offset position) { + setBounds(position & boundingBox.size); + notifyChange(); + } + + /// Sets the text of the [StickyNoteAnnotation]. + void setText(String text) { + _text = text; + notifyChange(); + } +} + +/// A widget representing a sticky note annotation. +class StickyNoteAnnotationView extends InteractiveGraphicsView + with AnnotationView { + /// Creates a [StickyNoteAnnotationView]. + StickyNoteAnnotationView({ + Key? key, + required this.annotation, + this.onAnnotationMoved, + this.onAnnotationMoving, + this.onTap, + this.onDoubleTap, + bool isSelected = false, + bool canEdit = true, + Color selectorColor = defaultSelectorColor, + double selectorStorkeWidth = 1, + double zoomLevel = 1, + }) : super( + key: key, + color: annotation.color, + strokeWidth: 1, + opacity: annotation.opacity, + isSelected: isSelected, + selectorColor: selectorColor, + canMove: canEdit, + selectorStorkeWidth: selectorStorkeWidth, + ) { + _zoomLevel = zoomLevel; + } + + /// Zoom level of the pdf page. + late final double _zoomLevel; + + /// Called when the annotation is moved. + final AnnotationMoveEndedCallback? onAnnotationMoved; + + /// Called when the annotation is moving. + final AnnotationMovingCallback? onAnnotationMoving; + + /// Called when the annotation is tapped. + final VoidCallback? onTap; + + /// Called when the annotation is double tapped. + final VoidCallback? onDoubleTap; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderStickyNoteAnnotationView( + stickyNoteAnnotation: annotation, + color: color, + opacity: opacity, + isSelected: isSelected, + selectorColor: selectorColor, + selectorStorkeWidth: selectorStorkeWidth, + zoomLevel: _zoomLevel, + onAnnotationMoved: onAnnotationMoved, + onAnnotationMoving: onAnnotationMoving, + onDoubleTap: onDoubleTap, + onTap: onTap, + ); + } + + @override + void updateRenderObject( + BuildContext context, + covariant RenderInteractiveGraphicsView renderObject, + ) { + if (renderObject is RenderStickyNoteAnnotationView) { + renderObject + ..zoomLevel = _zoomLevel + ..selectorStorkeWidth = selectorStorkeWidth + ..onAnnotationMoved = onAnnotationMoved + ..onAnnotationMoving = onAnnotationMoving + .._onDoubleTap = onDoubleTap + .._onTap = onTap; + } + super.updateRenderObject(context, renderObject); + } + + @override + final StickyNoteAnnotation annotation; +} + +/// A render object widget representing a text markup annotation. +class RenderStickyNoteAnnotationView extends RenderInteractiveGraphicsView { + /// Creates a [RenderStickyNoteAnnotationView]. + RenderStickyNoteAnnotationView({ + required this.stickyNoteAnnotation, + required Color color, + required double opacity, + required bool isSelected, + Color selectorColor = defaultSelectorColor, + double selectorStorkeWidth = selectionBorderThickness, + this.onAnnotationMoved, + this.onAnnotationMoving, + VoidCallback? onTap, + void Function()? onDoubleTap, + double zoomLevel = 1, + }) : _onDoubleTap = onDoubleTap, + super( + strokeColor: color, + opacity: opacity, + strokeWidth: 1, + isSelected: isSelected, + selectorColor: selectorColor, + selectorStorkeWidth: selectorStorkeWidth, + ) { + _onTap = onTap; + _zoomLevel = zoomLevel; + _selectorStorkeWidth = selectorStorkeWidth; + + _doubleTapGestureRecognizer = + DoubleTapGestureRecognizer() + ..onDoubleTap = _onDoubleTap + ..gestureSettings = const DeviceGestureSettings(touchSlop: 0.0); + super.tapGestureRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 0.0, + ); + _strokePath = Path(); + _fillPath = Path(); + } + + late double _zoomLevel; + late DoubleTapGestureRecognizer _doubleTapGestureRecognizer; + late Path _fillPath; + late Path _strokePath; + late double _selectorStorkeWidth; + + /// The zoom level + double get zoomLevel => _zoomLevel; + set zoomLevel(double value) { + if (_zoomLevel == value) { + return; + } + _zoomLevel = value; + markNeedsPaint(); + } + + /// The selector stroke width. + @override + double get selectorStorkeWidth => _selectorStorkeWidth; + @override + set selectorStorkeWidth(double value) { + if (_selectorStorkeWidth == value) { + return; + } + _selectorStorkeWidth = value; + markNeedsPaint(); + } + + /// The annotation to be rendered. + final StickyNoteAnnotation stickyNoteAnnotation; + + /// Called when the annotation is moved. + AnnotationMoveEndedCallback? onAnnotationMoved; + + /// Called when the annotation is moving. + AnnotationMovingCallback? onAnnotationMoving; + + /// Called when the annotation is tapped. + VoidCallback? _onTap; + + /// Called when the annotation is double tapped. + VoidCallback? _onDoubleTap; + + Rect get _bounds { + return stickyNoteAnnotation.uiBounds; + } + + @override + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + _applyRotationTransform(canvas, stickyNoteAnnotation.pageRotation, offset); + _drawStickyNoteIcon(canvas, offset); + super.paint(context, offset); + canvas.restore(); + } + + Rect _getPaintRect(Rect rect, Offset offset) { + final Rect localRect = rect.translate(-_bounds.left, -_bounds.top); + final Offset globalOffset = Offset( + offset.dx + (localRect.left / zoomLevel), + offset.dy + (localRect.top / zoomLevel), + ); + return globalOffset & (localRect.size / zoomLevel); + } + + void _applyRotationTransform(Canvas canvas, int rotation, Offset offset) { + final double centerX = offset.dx + paintBounds.width / 2; + final double centerY = offset.dy + paintBounds.height / 2; + canvas.save(); + canvas.translate(centerX, centerY); + canvas.rotate(-rotation * math.pi / 180); + canvas.translate(-centerX, -centerY); + } + + void _drawStickyNoteIcon(Canvas canvas, Offset offset) { + final Paint fillPaint = Paint(); + fillPaint.color = color.withValues(alpha: opacity); + fillPaint.style = PaintingStyle.fill; + + final Paint strokePaint = + Paint() + ..color = Colors.black.withValues(alpha: opacity) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke; + + final Rect paintRect = _getPaintRect( + stickyNoteAnnotation.isSelected + ? stickyNoteAnnotation.intermediateBounds + : stickyNoteAnnotation.boundingBox, + offset, + ); + canvas.save(); + + _fillPath.reset(); + _strokePath.reset(); + + switch (stickyNoteAnnotation.icon) { + case PdfStickyNoteIcon.comment: + _fillPath = + Path() + ..moveTo(0, 3) + ..cubicTo(0, 0.89543, 0.89543, 0, 3, 0) + ..lineTo(23, 0) + ..cubicTo(24.1046, 0, 25, 0.89543, 25, 3) + ..lineTo(25, 24) + ..lineTo(20.7868, 19.7499) + ..cubicTo(20.4113, 19.371, 19.8999, 19.1579, 19.3665, 19.1579) + ..lineTo(3, 19.1579) + ..cubicTo(0.89543, 19.1579, 0, 18.2625, 0, 17.1579) + ..lineTo(0, 3) + ..close(); + _strokePath = + Path() + ..moveTo(5, 7) + ..lineTo(21, 7) + ..moveTo(5, 12) + ..lineTo(21, 12) + ..moveTo(25, 24) + ..lineTo(25, 3) + ..cubicTo(25, 0.89543, 24.1046, 0, 23, 0) + ..lineTo(3, 0) + ..cubicTo(0.89543, 0, 0, 0.89543, 0, 3) + ..lineTo(0, 17.1579) + ..cubicTo(0, 18.2625, 0.89543, 19.1579, 3, 19.1579) + ..lineTo(19.3665, 19.1579) + ..cubicTo(19.8999, 19.1579, 20.4113, 19.371, 20.7868, 19.7499) + ..lineTo(25, 24) + ..close(); + break; + case PdfStickyNoteIcon.note: + _fillPath = + Path() + ..moveTo(0, 23) + ..lineTo(0, 0) + ..lineTo(23, 0) + ..lineTo(23, 13) + ..lineTo(13, 23) + ..lineTo(0, 23) + ..close(); + _strokePath = + Path() + ..moveTo(13, 23) + ..lineTo(0, 23) + ..lineTo(0, 0) + ..lineTo(23, 0) + ..lineTo(23, 13) + ..moveTo(13, 23) + ..lineTo(23, 13) + ..moveTo(13, 23) + ..lineTo(13, 13) + ..lineTo(23, 13); + break; + case PdfStickyNoteIcon.help: + _fillPath = + Path() + ..moveTo(23, 12) + ..cubicTo(23, 18.0751, 18.0751, 23, 12, 23) + ..cubicTo(5.92487, 23, 0, 18.0751, 0, 12) + ..cubicTo(0, 5.92487, 5.92487, 0, 12, 0) + ..cubicTo(18.0751, 0, 23, 5.92487, 23, 12) + ..close(); + _strokePath = + Path() + ..moveTo(8.5, 10) + ..cubicTo(8.5, 8.93913, 8.86875, 7.92172, 9.52513, 7.17157) + ..cubicTo(10.1815, 6.42143, 11.0717, 6, 12, 6) + ..cubicTo(12.9283, 6, 13.8185, 6.42143, 14.4749, 7.17157) + ..cubicTo(15.1313, 7.92172, 15.5, 8.5, 15.5, 9.5) + ..cubicTo(15.5, 12.5, 12, 11.7106, 12, 14) + ..lineTo(12, 15) + ..moveTo(12, 19) + ..lineTo(12, 17) + ..moveTo(23, 12) + ..cubicTo(23, 18.0751, 18.0751, 23, 12, 23) + ..cubicTo(5.92487, 23, 0, 18.0751, 0, 12) + ..cubicTo(0, 5.92487, 5.92487, 0, 12, 0) + ..cubicTo(18.0751, 0, 23, 5.92487, 23, 12) + ..close(); + break; + case PdfStickyNoteIcon.insert: + _fillPath = + Path() + ..moveTo(0, 40) + ..lineTo(20, 0) + ..lineTo(40, 40) + ..close(); + _strokePath = + Path() + ..moveTo(0, 40) + ..lineTo(20, 0) + ..lineTo(40, 40) + ..close(); + break; + case PdfStickyNoteIcon.key: + _fillPath = + _strokePath = + Path() + ..moveTo(7, 0) + ..cubicTo(3.68629, 0, 0, 3.68629, 0, 7) + ..cubicTo(0, 9.22085, 2.2066, 11.1599, 4, 12.1973) + ..lineTo(4, 21) + ..lineTo(7, 22.5) + ..lineTo(9, 21) + ..lineTo(8.5, 19.5) + ..lineTo(9.5, 18.5) + ..lineTo(8.5, 16.5) + ..lineTo(10, 15) + ..lineTo(10, 12.1973) + ..cubicTo(11.7934, 11.1599, 13, 9.22085, 13, 7) + ..cubicTo(13, 3.68629, 10.3137, 0, 7, 0) + ..close(); + break; + case PdfStickyNoteIcon.newParagraph: + _fillPath = + Path() + ..moveTo(22, 14) + ..lineTo(0, 14) + ..lineTo(12, 0) + ..lineTo(22, 14) + ..close(); + _strokePath = + Path() + ..moveTo(15, 24) + ..lineTo(15, 21) + ..moveTo(15, 21) + ..lineTo(15, 18) + ..lineTo(17.5, 18) + ..cubicTo(18.3284, 18, 19, 18.6716, 19, 19.5) + ..cubicTo(19, 20.3284, 18.3284, 21, 17.5, 21) + ..lineTo(15, 21) + ..moveTo(5, 24) + ..lineTo(5, 18) + ..moveTo(5, 18) + ..lineTo(5, 17) + ..moveTo(5, 18) + ..lineTo(10, 23) + ..moveTo(10, 23) + ..lineTo(10, 17) + ..moveTo(10, 23) + ..lineTo(10, 24) + ..moveTo(0, 14) + ..lineTo(22, 14) + ..lineTo(12, 0) + ..lineTo(0, 14) + ..close(); + break; + case PdfStickyNoteIcon.paragraph: + _fillPath = + Path() + ..moveTo(11, 14) + ..lineTo(7.5, 14) + ..cubicTo(3.91015, 14, 0, 11.0899, 0, 7.5) + ..cubicTo(0, 3.91015, 3.91015, 0, 7.5, 0) + ..lineTo(11, 0) + ..lineTo(11, 14) + ..close(); + _strokePath = + Path() + ..moveTo(11, 14) + ..lineTo(7.5, 14) + ..cubicTo(3.91015, 14, 0, 11.0899, 0, 7.5) + ..cubicTo(0, 3.91015, 3.91015, 0, 7.5, 0) + ..lineTo(11, 0) + ..moveTo(11, 14) + ..lineTo(11, 24) + ..moveTo(11, 14) + ..lineTo(11, 0) + ..moveTo(24, 0) + ..lineTo(17, 0) + ..moveTo(17, 0) + ..lineTo(17, 24) + ..moveTo(17, 0) + ..lineTo(11, 0) + ..close(); + break; + } + + canvas.translate(paintRect.left, paintRect.top); + final Rect fillRect = _fillPath.getBounds(); + final Rect strokeRect = _strokePath.getBounds(); + final Rect iconRect = fillRect.expandToInclude(strokeRect); + + canvas.scale( + paintRect.width / iconRect.width, + paintRect.height / iconRect.height, + ); + + canvas.drawPath(_fillPath, fillPaint); + canvas.drawPath(_strokePath, strokePaint); + canvas.restore(); + } + + @override + void handleEvent(PointerEvent event, covariant BoxHitTestEntry entry) { + if (event is PointerDownEvent) { + tapGestureRecognizer.addPointer(event); + _doubleTapGestureRecognizer.addPointer(event); + if (canEdit) { + panGestureRecognizer.addPointer(event); + } + } + } + + @override + void onDragUpdate(DragUpdateDetails details) { + if (canEdit) { + onAnnotationMoving?.call(stickyNoteAnnotation, details.delta); + } + } + + @override + void onDragEnd(DragEndDetails details) { + if (canEdit) { + onAnnotationMoved?.call(stickyNoteAnnotation, Offset.zero); + } + } + + @override + void onTap() { + _onTap?.call(); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/text_markup.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/text_markup.dart new file mode 100644 index 000000000..efcd45ade --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/annotation/text_markup.dart @@ -0,0 +1,407 @@ +import 'dart:math' show Point, sqrt; +import 'package:flutter/material.dart'; + +import '../../pdfviewer.dart'; +import 'annotation.dart'; +import 'annotation_view.dart'; + +/// Represents the highlight annotation on the text contents in the page. +class HighlightAnnotation extends Annotation { + /// Initializes a new instance of [HighlightAnnotation] class. + /// + /// The [textBoundsCollection] represents the bounds collection of the highlight annotations that are added in the multiple lines of text. + HighlightAnnotation({required List textBoundsCollection}) + : assert(textBoundsCollection.isNotEmpty), + assert(_checkTextMarkupRects(textBoundsCollection)), + super(pageNumber: textBoundsCollection.first.pageNumber) { + _textMarkupRects = []; + + double minX = textBoundsCollection.first.bounds.left, + minY = textBoundsCollection.first.bounds.top, + maxX = textBoundsCollection.first.bounds.right, + maxY = textBoundsCollection.first.bounds.bottom; + + for (final PdfTextLine textLine in textBoundsCollection) { + final Rect rect = textLine.bounds; + _textMarkupRects.add(rect); + minX = minX < rect.left ? minX : rect.left; + minY = minY < rect.top ? minY : rect.top; + maxX = maxX > rect.right ? maxX : rect.right; + maxY = maxY > rect.bottom ? maxY : rect.bottom; + } + + setBounds(Rect.fromLTRB(minX, minY, maxX, maxY)); + } + + late final List _textMarkupRects; +} + +/// Represents the strikethrough annotation on the text contents in the page. +class StrikethroughAnnotation extends Annotation { + /// Initializes a new instance of [StrikethroughAnnotation] class. + /// + /// The [textBoundsCollection] represents the bounds collection of the strikethrough annotations that are added in the multiple lines of text. + StrikethroughAnnotation({required List textBoundsCollection}) + : assert(textBoundsCollection.isNotEmpty), + assert(_checkTextMarkupRects(textBoundsCollection)), + super(pageNumber: textBoundsCollection.first.pageNumber) { + _textMarkupRects = []; + + double minX = textBoundsCollection.first.bounds.left, + minY = textBoundsCollection.first.bounds.top, + maxX = textBoundsCollection.first.bounds.right, + maxY = textBoundsCollection.first.bounds.bottom; + + for (final PdfTextLine textLine in textBoundsCollection) { + final Rect rect = textLine.bounds; + _textMarkupRects.add(rect); + minX = minX < rect.left ? minX : rect.left; + minY = minY < rect.top ? minY : rect.top; + maxX = maxX > rect.right ? maxX : rect.right; + maxY = maxY > rect.bottom ? maxY : rect.bottom; + } + + setBounds(Rect.fromLTRB(minX, minY, maxX, maxY)); + } + + late final List _textMarkupRects; +} + +/// Represents the underline annotation on the text contents in the page. +class UnderlineAnnotation extends Annotation { + /// Initializes a new instance of [UnderlineAnnotation] class. + /// + /// The [textBoundsCollection] represents the bounds collection of the underline annotations that are added in the multiple lines of text. + UnderlineAnnotation({required List textBoundsCollection}) + : assert(textBoundsCollection.isNotEmpty), + assert(_checkTextMarkupRects(textBoundsCollection)), + super(pageNumber: textBoundsCollection.first.pageNumber) { + _textMarkupRects = []; + + double minX = textBoundsCollection.first.bounds.left, + minY = textBoundsCollection.first.bounds.top, + maxX = textBoundsCollection.first.bounds.right, + maxY = textBoundsCollection.first.bounds.bottom; + + for (final PdfTextLine textLine in textBoundsCollection) { + final Rect rect = textLine.bounds; + _textMarkupRects.add(rect); + minX = minX < rect.left ? minX : rect.left; + minY = minY < rect.top ? minY : rect.top; + maxX = maxX > rect.right ? maxX : rect.right; + maxY = maxY > rect.bottom ? maxY : rect.bottom; + } + + setBounds(Rect.fromLTRB(minX, minY, maxX, maxY)); + } + + late final List _textMarkupRects; +} + +/// Represents the squiggly annotation on the text contents in the page. +class SquigglyAnnotation extends Annotation { + /// Initializes a new instance of [SquigglyAnnotation] class. + /// + /// The [textBoundsCollection] represents the bounds collection of the squiggly annotations that are added in the multiple lines of text. + SquigglyAnnotation({required List textBoundsCollection}) + : assert(textBoundsCollection.isNotEmpty), + assert(_checkTextMarkupRects(textBoundsCollection)), + super(pageNumber: textBoundsCollection.first.pageNumber) { + _textMarkupRects = []; + + double minX = textBoundsCollection.first.bounds.left, + minY = textBoundsCollection.first.bounds.top, + maxX = textBoundsCollection.first.bounds.right, + maxY = textBoundsCollection.first.bounds.bottom; + + for (final PdfTextLine textLine in textBoundsCollection) { + final Rect rect = textLine.bounds; + _textMarkupRects.add(rect); + minX = minX < rect.left ? minX : rect.left; + minY = minY < rect.top ? minY : rect.top; + maxX = maxX > rect.right ? maxX : rect.right; + maxY = maxY > rect.bottom ? maxY : rect.bottom; + } + + setBounds(Rect.fromLTRB(minX, minY, maxX, maxY)); + } + + late final List _textMarkupRects; +} + +/// A widget representing a text markup annotation. +class TextMarkupAnnotationView extends InteractiveGraphicsView + with AnnotationView { + /// Creates a [TextMarkupAnnotationView]. + TextMarkupAnnotationView({ + Key? key, + required this.annotation, + bool isSelected = false, + Color selectorColor = defaultSelectorColor, + double heightPercentage = 1, + }) : super( + key: key, + color: annotation.color, + strokeWidth: 1, + opacity: annotation.opacity, + isSelected: isSelected, + canMove: false, + selectorColor: selectorColor, + ) { + _textMarkupType = + annotation is HighlightAnnotation + ? TextMarkupType.highlight + : annotation is StrikethroughAnnotation + ? TextMarkupType.strikethrough + : annotation is UnderlineAnnotation + ? TextMarkupType.underline + : TextMarkupType.squiggly; + _heightPercentage = heightPercentage; + } + + late final TextMarkupType _textMarkupType; + + late final double _heightPercentage; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderTextMarkupAnnotationView( + textMarkupType: _textMarkupType, + annotation: annotation, + color: color, + opacity: opacity, + isSelected: isSelected, + selectorColor: selectorColor, + heightPercentage: _heightPercentage, + ); + } + + @override + void updateRenderObject( + BuildContext context, + covariant RenderInteractiveGraphicsView renderObject, + ) { + if (renderObject is RenderTextMarkupAnnotationView) { + renderObject.heightPercentage = _heightPercentage; + } + super.updateRenderObject(context, renderObject); + } + + @override + final Annotation annotation; +} + +/// A render object widget representing a text markup annotation. +class RenderTextMarkupAnnotationView extends RenderInteractiveGraphicsView { + /// Creates a [RenderTextMarkupAnnotationView]. + RenderTextMarkupAnnotationView({ + required TextMarkupType textMarkupType, + required this.annotation, + required Color color, + required double opacity, + required bool isSelected, + required Color selectorColor, + double heightPercentage = 1, + }) : super( + strokeColor: color, + opacity: opacity, + strokeWidth: 1, + isSelected: isSelected, + selectorColor: selectorColor, + ) { + _textMarkupType = textMarkupType; + _heightPercentage = heightPercentage; + } + + late final TextMarkupType _textMarkupType; + late double _heightPercentage; + + /// The height percentage. + double get heightPercentage => _heightPercentage; + set heightPercentage(double value) { + if (_heightPercentage == value) { + return; + } + _heightPercentage = value; + markNeedsPaint(); + } + + /// The annotation to be rendered. + final Annotation annotation; + + Rect get _bounds { + return annotation.uiBounds; + } + + @override + void paint(PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + final Paint paint = Paint()..color = color.withValues(alpha: opacity); + + if (_textMarkupType == TextMarkupType.highlight) { + _drawHighlight(canvas, paint, offset); + } else if (_textMarkupType == TextMarkupType.strikethrough) { + _drawStrikethrough(canvas, paint, offset); + } else if (_textMarkupType == TextMarkupType.underline) { + _drawUnderline(canvas, paint, offset); + } else if (_textMarkupType == TextMarkupType.squiggly) { + _drawSquiggly(canvas, paint, offset); + } + super.paint(context, offset); + } + + Rect _getPaintRect(Rect rect, Offset offset) { + final Rect localRect = rect.translate(-_bounds.left, -_bounds.top); + final Offset globalOffset = Offset( + offset.dx + localRect.left / heightPercentage, + offset.dy + localRect.top / heightPercentage, + ); + return globalOffset & (localRect.size / heightPercentage); + } + + void _drawHighlight(Canvas canvas, Paint paint, Offset offset) { + paint.color = color.withValues(alpha: opacity * 0.3); + paint.style = PaintingStyle.fill; + final HighlightAnnotation highlightAnnotation = + annotation as HighlightAnnotation; + + for (final Rect rect in highlightAnnotation._textMarkupRects) { + canvas.drawRect(_getPaintRect(rect, offset), paint); + } + } + + void _drawStrikethrough(Canvas canvas, Paint paint, Offset offset) { + paint.style = PaintingStyle.stroke; + paint.strokeWidth = strokeWidth / heightPercentage; + for (final Rect rect + in (annotation as StrikethroughAnnotation)._textMarkupRects) { + final Rect strikethroughRect = _getPaintRect(rect, offset); + canvas.drawLine( + strikethroughRect.centerLeft, + strikethroughRect.centerRight, + paint, + ); + } + } + + void _drawUnderline(Canvas canvas, Paint paint, Offset offset) { + paint.style = PaintingStyle.stroke; + paint.strokeWidth = strokeWidth / heightPercentage; + for (final Rect rect + in (annotation as UnderlineAnnotation)._textMarkupRects) { + final Rect underlineRect = _getPaintRect(rect, offset); + canvas.drawLine( + underlineRect.bottomLeft, + underlineRect.bottomRight, + paint, + ); + } + } + + void _drawSquiggly(Canvas canvas, Paint paint, Offset offset) { + paint.style = PaintingStyle.stroke; + paint.strokeWidth = strokeWidth / heightPercentage; + for (final Rect rect + in (annotation as SquigglyAnnotation)._textMarkupRects) { + final Rect squigglyRect = _getPaintRect(rect, offset); + + canvas.drawPath( + _getSquigglyPath( + Point( + squigglyRect.bottomLeft.dx, + squigglyRect.bottomRight.dy, + ), + Point( + squigglyRect.bottomRight.dx, + squigglyRect.bottomRight.dy, + ), + squigglyRect.height, + ), + paint, + ); + } + } + + Path _getSquigglyPath( + Point startPoint, + Point endPoint, + double height, + ) { + final double dx = startPoint.x - endPoint.x; + final double dy = startPoint.y - endPoint.y; + final double length = sqrt(dx * dx + dy * dy); + final double x = startPoint.x; + final double y = startPoint.y; + bool showUnderlineAtStart = false; + final double spacing = height * 0.18; + final Path squigglyPath = Path(); + squigglyPath.moveTo(x, y); + for ( + double distance = 0; + distance + spacing < length; + distance += spacing + ) { + if (showUnderlineAtStart) { + squigglyPath.lineTo(x + distance + spacing, y); + } else { + squigglyPath.lineTo(x + distance + spacing, y - spacing); + } + showUnderlineAtStart = !showUnderlineAtStart; + } + return squigglyPath; + } + + @override + bool hitTestSelf(Offset position) => false; +} + +/// Highlight Annotation Extension +extension HighlightAnnotationExtension on HighlightAnnotation { + /// The list of text markup rectangles. + List get textMarkupRects => _textMarkupRects; +} + +/// Underline Annotation Extension +extension UnderlineAnnotationExtension on UnderlineAnnotation { + /// The list of text markup rectangles. + List get textMarkupRects => _textMarkupRects; +} + +/// Strikethrough Annotation Extension +extension StrikethroughAnnotationExtension on StrikethroughAnnotation { + /// The list of text markup rectangles. + List get textMarkupRects => _textMarkupRects; +} + +/// Squiggly Annotation Extension +extension SquigglyAnnotationExtension on SquigglyAnnotation { + /// The list of text markup rectangles. + List get textMarkupRects => _textMarkupRects; +} + +/// Asserts whether the text markup rectangles are valid. +bool _checkTextMarkupRects(List textMarkupRects) { + int pageNumber = textMarkupRects.first.pageNumber; + for (final PdfTextLine textLine in textMarkupRects) { + if (pageNumber <= 0 && textLine.pageNumber != pageNumber) { + return false; + } + pageNumber = textLine.pageNumber; + } + return true; +} + +/// Enumerates the values that represent the type of text markup annotation. +enum TextMarkupType { + /// Highlight + highlight, + + /// Underline + underline, + + /// Strikethrough + strikethrough, + + /// Squiggly + squiggly, +} 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 b7e05a2c0..5c4ae2fad 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart @@ -1,7 +1,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; +import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; /// Width of the back icon in the bookmark. const double _kPdfBackIconWidth = 24.0; @@ -42,17 +43,20 @@ const double _kPdfExpandIconRightPosition = 16.0; /// A material design bookmark. class BookmarkItem extends StatefulWidget { /// Creates a material design bookmark. - const 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}); + const BookmarkItem({ + super.key, + 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, + required this.textDirection, + }); /// Title for the bookmark. final String title; @@ -94,6 +98,9 @@ class BookmarkItem extends StatefulWidget { /// If true,MobileWebView is enabled.Default value is false. final bool isMobileWebView; + ///A direction of text flow. + final TextDirection textDirection; + @override _BookmarkItemState createState() => _BookmarkItemState(); } @@ -102,11 +109,18 @@ class BookmarkItem extends StatefulWidget { class _BookmarkItemState extends State { late Color _color; SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); - _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); + _color = + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -116,11 +130,14 @@ class _BookmarkItemState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; super.dispose(); } void _handleBackToParent() { - _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _color = + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -128,7 +145,9 @@ class _BookmarkItemState extends State { } void _handleExpandBookmarkList() { - _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _color = + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -146,9 +165,11 @@ class _BookmarkItemState extends State { void _handleTapDown(TapDownDetails details) { setState(() { if (kIsDesktop && !widget.isMobileWebView) { - _color = const Color(0xFF000000).withOpacity(0.08); + _color = const Color(0xFF000000).withValues(alpha: 0.08); } else { - _color = _pdfViewerThemeData!.bookmarkViewStyle?.selectionColor! ?? + _color = + _pdfViewerThemeData!.bookmarkViewStyle?.selectionColor! ?? + _effectiveThemeData!.bookmarkViewStyle?.selectionColor! ?? ((Theme.of(context).colorScheme.brightness == Brightness.light) ? const Color.fromRGBO(0, 0, 0, 0.08) : const Color.fromRGBO(255, 255, 255, 0.12)); @@ -162,7 +183,9 @@ class _BookmarkItemState extends State { void _handleCancelSelectionColor() { setState(() { - _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _color = + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -180,20 +203,26 @@ class _BookmarkItemState extends State { child: Container( height: widget.height, color: _color, - foregroundDecoration: widget.isBorderEnabled - ? BoxDecoration( - border: Border( - bottom: BorderSide( - color: _pdfViewerThemeData! - .bookmarkViewStyle?.titleSeparatorColor ?? - ((Theme.of(context).colorScheme.brightness == - Brightness.light) - ? const Color.fromRGBO(0, 0, 0, 0.16) - : const Color.fromRGBO(255, 255, 255, 0.16)), + foregroundDecoration: + widget.isBorderEnabled + ? BoxDecoration( + border: Border( + bottom: BorderSide( + color: + _pdfViewerThemeData! + .bookmarkViewStyle + ?.titleSeparatorColor ?? + _effectiveThemeData! + .bookmarkViewStyle + ?.titleSeparatorColor ?? + ((Theme.of(context).colorScheme.brightness == + Brightness.light) + ? const Color.fromRGBO(0, 0, 0, 0.16) + : const Color.fromRGBO(255, 255, 255, 0.16)), + ), ), - ), - ) - : const BoxDecoration(), + ) + : const BoxDecoration(), child: Stack( children: [ Visibility( @@ -210,10 +239,10 @@ class _BookmarkItemState extends State { size: _kPdfBackIconSize, color: _pdfViewerThemeData!.bookmarkViewStyle?.backIconColor ?? - Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.54), + _effectiveThemeData!.bookmarkViewStyle?.backIconColor ?? + Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.54), semanticLabel: 'Previous level bookmark', ), ), @@ -226,15 +255,16 @@ class _BookmarkItemState extends State { child: Text( widget.title, overflow: TextOverflow.ellipsis, - style: _pdfViewerThemeData!.bookmarkViewStyle?.titleTextStyle ?? - TextStyle( + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( fontSize: 14, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.87), - fontFamily: 'Roboto', - fontWeight: FontWeight.normal, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData!.bookmarkViewStyle?.titleTextStyle, ), ), ), @@ -250,17 +280,21 @@ class _BookmarkItemState extends State { child: Icon( Icons.arrow_forward_ios, size: _kPdfExpandIconSize, - color: _pdfViewerThemeData! - .bookmarkViewStyle?.navigationIconColor ?? - Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.54), + color: + _pdfViewerThemeData! + .bookmarkViewStyle + ?.navigationIconColor ?? + _effectiveThemeData! + .bookmarkViewStyle + ?.navigationIconColor ?? + Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.54), semanticLabel: 'Next level bookmark', ), ), ), - ) + ), ], ), ), @@ -270,12 +304,19 @@ class _BookmarkItemState extends State { cursor: SystemMouseCursors.click, onEnter: (PointerEnterEvent details) { setState(() { - _color = const Color(0xFF000000).withOpacity(0.04); + _color = + Theme.of(context).useMaterial3 + ? Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.08) + : const Color(0xFF000000).withValues(alpha: 0.04); }); }, onExit: (PointerExitEvent details) { setState(() { - _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _color = + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); 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 b4b966263..4beec8dd9 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../theme/theme.dart'; /// Height of the bookmark header bar. const double _kPdfHeaderBarHeight = 53.0; /// Height of the header text. -const double _kPdfHeaderTextHeight = 18.0; +const double _kPdfHeaderTextHeight = 24.0; /// Height of the close icon. const double _kPdfCloseIconHeight = 24.0; @@ -32,13 +33,20 @@ const double _kPdfCloseIconRightPosition = 16.0; /// A material design bookmark toolbar. class BookmarkToolbar extends StatefulWidget { /// Creates a material design bookmark toolbar. - const BookmarkToolbar(this.onCloseButtonPressed); + const BookmarkToolbar( + this.onCloseButtonPressed, + this.textDirection, { + super.key, + }); /// A tap with a close button is occurred. /// /// This triggers when close button in bookmark toolbar is tapped. final GestureTapCallback onCloseButtonPressed; + ///A direction of text flow. + final TextDirection textDirection; + @override State createState() => _BookmarkToolbarState(); } @@ -46,11 +54,18 @@ class BookmarkToolbar extends StatefulWidget { /// State for [BookmarkToolbar] class _BookmarkToolbarState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; + bool _isMaterial3 = false; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _isMaterial3 = Theme.of(context).useMaterial3; + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -58,6 +73,7 @@ class _BookmarkToolbarState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; super.dispose(); } @@ -65,11 +81,7 @@ class _BookmarkToolbarState extends State { @override Widget build(BuildContext context) { const List boxShadows = [ - BoxShadow( - color: Color.fromRGBO(0, 0, 0, 0.14), - blurRadius: 2, - offset: Offset.zero, - ), + BoxShadow(color: Color.fromRGBO(0, 0, 0, 0.14), blurRadius: 2), BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.12), blurRadius: 2, @@ -87,37 +99,52 @@ class _BookmarkToolbarState extends State { height: _kPdfHeaderBarHeight, margin: const EdgeInsets.only(bottom: 3), decoration: BoxDecoration( - color: _pdfViewerThemeData!.bookmarkViewStyle?.headerBarColor ?? + color: + _pdfViewerThemeData!.bookmarkViewStyle?.headerBarColor ?? + _effectiveThemeData!.bookmarkViewStyle?.headerBarColor ?? ((Theme.of(context).colorScheme.brightness == Brightness.light) ? const Color(0xFFFAFAFA) : const Color(0xFF424242)), - boxShadow: boxShadows, + boxShadow: _isMaterial3 ? null : boxShadows, + border: + _isMaterial3 + ? Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + ) + : null, ), child: Stack( children: [ - Positioned( + Positioned.directional( + textDirection: widget.textDirection, top: _kPdfHeaderTextTopPosition, - left: _kPdfHeaderTextLeftPosition, + start: _kPdfHeaderTextLeftPosition, height: _kPdfHeaderTextHeight, child: Text( _localizations!.pdfBookmarksLabel, - style: - _pdfViewerThemeData!.bookmarkViewStyle?.headerTextStyle ?? - TextStyle( - fontSize: 16, - fontFamily: 'Roboto', - fontWeight: FontWeight.normal, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.87), - ), + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData!.bookmarkViewStyle?.headerTextStyle ?? + _effectiveThemeData! + .bookmarkViewStyle + ?.headerTextStyle, + ), semanticsLabel: '', ), ), - Positioned( + Positioned.directional( + textDirection: widget.textDirection, top: _kPdfCloseIconTopPosition, - right: _kPdfCloseIconRightPosition, + end: _kPdfCloseIconRightPosition, height: _kPdfCloseIconHeight, width: _kPdfCloseIconWidth, child: RawMaterialButton( @@ -127,9 +154,12 @@ class _BookmarkToolbarState extends State { child: Icon( Icons.close, size: _kPdfCloseIconSize, - color: _pdfViewerThemeData! - .bookmarkViewStyle?.closeIconColor ?? - Theme.of(context).colorScheme.onSurface.withOpacity(0.54), + color: + _pdfViewerThemeData!.bookmarkViewStyle?.closeIconColor ?? + _effectiveThemeData!.bookmarkViewStyle?.closeIconColor ?? + Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.54), semanticLabel: 'Close Bookmark', ), ), 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 f0326f47f..819c96cc5 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart @@ -4,13 +4,17 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; 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 '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; import 'bookmark_item.dart'; import 'bookmark_toolbar.dart'; -///Triggers when the bookmark is opened or closed +/// List of bookmarks. +List bookmarkList = []; + +/// Triggers when the bookmark is opened or closed typedef _BookmarkView = void Function(bool); /// Standard tablet width of the bookmark view. @@ -26,8 +30,12 @@ const double _kPdfSubBookmarkTitlePosition = 55.0; class BookmarkView extends StatefulWidget { /// BookmarkView Constructor. const BookmarkView( - Key key, this.pdfDocument, this.controller, this._bookmarkView) - : super(key: key); + Key key, + this.pdfDocument, + this.controller, + this._bookmarkView, + this.textDirection, + ) : super(key: key); /// [PdfViewerController] instance of PdfViewer. final PdfViewerController controller; @@ -35,25 +43,32 @@ class BookmarkView extends StatefulWidget { /// [PdfDocument] instance of PDF library. final PdfDocument? pdfDocument; - ///Triggers when the bookmark is opened or closed + /// Triggers when the bookmark is opened or closed final _BookmarkView _bookmarkView; + /// A direction of text flow. + final TextDirection textDirection; + @override State createState() => BookmarkViewControllerState(); } /// State for [BookmarkView] class BookmarkViewControllerState extends State { + final GlobalKey _bookmarkTreeKey = + GlobalKey(); List? _bookmarkList = []; PdfBookmarkBase? _bookmarkBase; PdfBookmark? _parentBookmark; PdfBookmark? _childBookmark; bool _isExpanded = false; + bool _useMaterial3 = false; double? _totalWidth; bool _isTablet = false; int? _listCount; LocalHistoryEntry? _historyEntry; SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; /// If true, bookmark view is opened. @@ -61,7 +76,12 @@ class BookmarkViewControllerState extends State { @override void didChangeDependencies() { + _useMaterial3 = Theme.of(context).useMaterial3; _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -71,6 +91,7 @@ class BookmarkViewControllerState extends State { _bookmarkList!.clear(); _bookmarkList = null; _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; super.dispose(); } @@ -95,8 +116,7 @@ class BookmarkViewControllerState extends State { } /// Opens the bookmark view. - // ignore: avoid_void_async - void open() async { + Future open() async { await Future.sync(() => widget.controller.clearSelection()); _ensureHistoryEntry(); if (!showBookmark && widget.pdfDocument != null) { @@ -126,8 +146,9 @@ class BookmarkViewControllerState extends State { void _findDevice(BuildContext context) { _totalWidth = MediaQuery.of(context).size.width; final Size size = MediaQuery.of(context).size; - final double diagonal = - sqrt((size.width * size.width) + (size.height * size.height)); + final double diagonal = sqrt( + (size.width * size.width) + (size.height * size.height), + ); if (kIsDesktop && !(diagonal < _kPdfStandardDiagonalOffset)) { _isTablet = true; } else { @@ -160,19 +181,22 @@ class BookmarkViewControllerState extends State { List _populateBookmarkList() { _bookmarkList?.clear(); if (_isExpanded) { - _bookmarkList?.add(BookmarkItem( - title: _childBookmark!.title, - isBackIconVisible: true, - textPosition: _kPdfSubBookmarkTitlePosition, - onBackPressed: _handleBackPress, - isMobileWebView: !_isTablet, - onNavigate: () { - widget.controller.jumpToBookmark(_childBookmark!); - _handleClose(); - }, - isBorderEnabled: true, - onExpandPressed: () {}, - )); + _bookmarkList?.add( + BookmarkItem( + title: _childBookmark!.title, + isBackIconVisible: true, + textDirection: widget.textDirection, + textPosition: _kPdfSubBookmarkTitlePosition, + onBackPressed: _handleBackPress, + isMobileWebView: !_isTablet, + onNavigate: () { + widget.controller.jumpToBookmark(_childBookmark!); + _handleClose(); + }, + isBorderEnabled: true, + onExpandPressed: () {}, + ), + ); } final int bookmarkListCount = _isExpanded ? _childBookmark!.count : _bookmarkBase!.count; @@ -180,9 +204,11 @@ class BookmarkViewControllerState extends State { final BookmarkItem bookmarkItem = BookmarkItem( title: _isExpanded ? _childBookmark![i].title : _bookmarkBase![i].title, isMobileWebView: !_isTablet, - isExpandIconVisible: _isExpanded - ? _childBookmark![i].count != 0 - : _bookmarkBase![i].count != 0, + textDirection: widget.textDirection, + isExpandIconVisible: + _isExpanded + ? _childBookmark![i].count != 0 + : _bookmarkBase![i].count != 0, onNavigate: () { final PdfBookmark bookmark = _isExpanded ? _childBookmark![i] : _bookmarkBase![i]; @@ -200,6 +226,11 @@ class BookmarkViewControllerState extends State { return _bookmarkList!; } + void _handleTap(PdfBookmark bookmark) { + widget.controller.jumpToBookmark(bookmark); + _handleClose(); + } + @override Widget build(BuildContext context) { _findDevice(context); @@ -211,55 +242,363 @@ class BookmarkViewControllerState extends State { } return Visibility( visible: showBookmark, - child: Stack(children: [ - Visibility( - visible: _isTablet, - child: GestureDetector( - onTap: _handleClose, + child: Stack( + children: [ + Visibility( + visible: _isTablet, + child: GestureDetector( + onTap: _handleClose, + child: Container(color: Colors.black.withValues(alpha: 0.3)), + ), + ), + Align( + alignment: _isTablet ? Alignment.topRight : Alignment.center, child: Container( - color: Colors.black.withOpacity(0.3), + decoration: BoxDecoration( + boxShadow: + _useMaterial3 + ? const [ + BoxShadow( + color: Color(0x4D000000), + offset: Offset(0, 1), + blurRadius: 3, + ), + BoxShadow( + color: Color(0x26000000), + offset: Offset(0, 4), + blurRadius: 8, + spreadRadius: 3, + ), + ] + : null, + color: + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? + (Theme.of(context).useMaterial3 + ? Theme.of(context).colorScheme.surface + : (Theme.of(context).colorScheme.brightness == + Brightness.light + ? Colors.white + : const Color(0xFF212121))), + ), + width: _isTablet ? _kPdfTabletBookmarkWidth : _totalWidth, + child: Column( + children: [ + BookmarkToolbar(_handleClose, widget.textDirection), + Expanded( + child: + hasBookmark + ? _useMaterial3 + ? BookmarkTree( + pdfDocument: widget.pdfDocument, + onNavigate: _handleTap, + key: _bookmarkTreeKey, + textDirection: widget.textDirection, + ) + : ListView.builder( + itemCount: _listCount, + itemBuilder: ( + BuildContext context, + int index, + ) { + return _bookmarkList![index]; + }, + ) + : Center( + child: Text( + _localizations!.pdfNoBookmarksLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues( + alpha: 0.87, + ) + : Colors.white.withValues( + alpha: 0.87, + ), + ) + .merge( + _pdfViewerThemeData! + .bookmarkViewStyle + ?.titleTextStyle, + ), + ), + ), + ), + ], + ), ), ), - ), - Align( - alignment: _isTablet ? Alignment.topRight : Alignment.center, - child: Container( - color: _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == Brightness.light - ? Colors.white - : const Color(0xFF212121)), - width: _isTablet ? _kPdfTabletBookmarkWidth : _totalWidth, - child: Column(children: [ - BookmarkToolbar(_handleClose), - Expanded( - child: hasBookmark - ? ListView.builder( - itemCount: _listCount, - itemBuilder: (BuildContext context, int index) { - return _bookmarkList![index]; + ], + ), + ); + } +} + +/// A class representing a node in the bookmark tree. +class BookmarkNode { + BookmarkNode({ + required this.title, + this.children = const [], + this.isExpanded = false, + this.level = 0, + this.pdfBookmark, + }); + + /// The title of the node. + String title; + + /// The children of the node. + List children; + + /// Whether the node is expanded. + bool isExpanded; + + /// The level of the node. + int level; + + /// The pdfBookmark of the node. + PdfBookmark? pdfBookmark; +} + +/// A widget that displays the bookmark tree. +class BookmarkTree extends StatefulWidget { + /// Creates a widget that displays the bookmark tree. + const BookmarkTree({ + required this.pdfDocument, + required this.onNavigate, + required this.textDirection, + Key? key, + }) : super(key: key); + + /// The pdf document. + final PdfDocument? pdfDocument; + + /// A tap with a bookmark is occurred. + /// + /// This triggers when bookmark is tapped in the bookmark view. + final void Function(PdfBookmark bookmark) onNavigate; + + /// The text direction. + final TextDirection textDirection; + + @override + BookmarkTreeState createState() => BookmarkTreeState(); +} + +class BookmarkTreeState extends State { + SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; + + @override + void didChangeDependencies() { + _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); + super.didChangeDependencies(); + } + + @override + void initState() { + super.initState(); + if (bookmarkList.isEmpty) { + _loadBookmarks(); + } + } + + @override + void dispose() { + _pdfViewerThemeData = null; + _effectiveThemeData = null; + super.dispose(); + } + + // Load bookmarks from the PdfDocument + void _loadBookmarks() { + final PdfBookmarkBase bookmarkBase = widget.pdfDocument!.bookmarks; + bookmarkList = _buildBookmarkNodes(bookmarkBase); + } + + List _buildBookmarkNodes(PdfBookmarkBase bookmarkBase) { + final List nodes = []; + + for (int i = 0; i < bookmarkBase.count; i++) { + final PdfBookmark bookmark = bookmarkBase[i]; + final BookmarkNode node = BookmarkNode( + title: bookmark.title, + pdfBookmark: bookmark, + children: _buildBookmarkNodes(bookmark), + ); + nodes.add(node); + } + return nodes; + } + + /// Toggles the expansion state of the node. + void _toggleExpand(BookmarkNode node, int index) { + setState(() { + if (node.isExpanded) { + _collapseNode(node, index); + } else { + _expandNode(node, index); + } + }); + } + + /// Handles the tap on a bookmark. + void _handleTap(PdfBookmark bookmark) { + widget.onNavigate(bookmark); + } + + /// Expands the node and its children. + void _expandNode(BookmarkNode node, int index) { + int insertIndex = index + 1; + for (final child in node.children) { + child.level = node.level + 1; + bookmarkList.insert(insertIndex, child); + insertIndex++; + if (child.isExpanded) { + bookmarkList.insertAll(insertIndex, child.children); + insertIndex += child.children.length; + } + } + node.isExpanded = true; + } + + /// Collapses the node and its children. + void _collapseNode(BookmarkNode node, int index) { + final int removeIndex = index + 1; + while (removeIndex < bookmarkList.length && + bookmarkList[removeIndex].level > node.level) { + bookmarkList.removeAt(removeIndex); + } + node.isExpanded = false; + } + + @override + Widget build(BuildContext context) { + return _bookMarkItems(bookmarkList[0].pdfBookmark!); + } + + /// Builds bookmark items for the bookmark view. + Widget _bookMarkItems(PdfBookmark bookmark) { + final icon = Icon( + Icons.expand_more, + color: + _pdfViewerThemeData!.bookmarkViewStyle?.navigationIconColor ?? + _effectiveThemeData!.bookmarkViewStyle?.navigationIconColor ?? + Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.54), + size: 18, + ); + return ListView.builder( + key: const PageStorageKey('boomarkview'), + itemCount: bookmarkList.length, + itemBuilder: (context, index) { + final node = bookmarkList[index]; + return Material( + color: + _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? + Theme.of(context).colorScheme.surface, + child: InkWell( + splashColor: + _pdfViewerThemeData!.bookmarkViewStyle?.selectionColor! ?? + _effectiveThemeData!.bookmarkViewStyle?.selectionColor! ?? + ((Theme.of(context).colorScheme.brightness == Brightness.light) + ? const Color.fromRGBO(0, 0, 0, 0.08) + : const Color.fromRGBO(255, 255, 255, 0.12)), + hoverColor: + Theme.of(context).useMaterial3 + ? Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.08) + : const Color(0xFF000000).withValues(alpha: 0.04), + onTap: () { + if (node.pdfBookmark != null) { + _handleTap(node.pdfBookmark!); + } + }, + child: Padding( + padding: + widget.textDirection == TextDirection.rtl + ? EdgeInsets.only(right: node.level * 25.0) + : EdgeInsets.only(left: node.level * 25.0), + child: Row( + children: [ + if (node.children.isNotEmpty) + Padding( + padding: + widget.textDirection == TextDirection.rtl + ? const EdgeInsets.only(right: 16.0) + : const EdgeInsets.only(left: 16.0), + child: GestureDetector( + onTap: () { + _toggleExpand(node, index); }, - ) - : Center( - child: Text( - _localizations!.pdfNoBookmarksLabel, - style: _pdfViewerThemeData! - .bookmarkViewStyle?.titleTextStyle ?? - TextStyle( - fontSize: 14, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.87), - fontFamily: 'Roboto', - fontWeight: FontWeight.normal, - ), + child: SizedBox( + height: 40, + child: RotatedBox( + quarterTurns: + !node.isExpanded + ? widget.textDirection == TextDirection.rtl + ? 1 + : 3 + : 0, + child: icon, + ), + ), + ), + ), + if (node.children.isNotEmpty) const SizedBox(width: 12), + Expanded( + child: SizedBox( + height: 40, + child: Padding( + padding: + (!node.children.isNotEmpty) + ? widget.textDirection == TextDirection.rtl + ? const EdgeInsets.only(right: 45.0) + : const EdgeInsets.only(left: 45.0) + : EdgeInsets.zero, + child: Align( + alignment: + widget.textDirection == TextDirection.rtl + ? Alignment.centerRight + : Alignment.centerLeft, + child: Text( + node.title, + textAlign: + widget.textDirection == TextDirection.rtl + ? TextAlign.right + : TextAlign.left, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + color: + Theme.of(context).colorScheme.onSurface, + ) + .merge( + _pdfViewerThemeData! + .bookmarkViewStyle + ?.titleTextStyle, + ), + ), ), ), + ), + ), + ], ), - ]), + ), ), - ), - ]), + ); + }, ); } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart new file mode 100644 index 000000000..65e399924 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart' show Color, Offset; + +import '../annotation/annotation.dart'; +import '../annotation/sticky_notes.dart'; +import '../control/enums.dart'; +import '../form_fields/pdf_form_field.dart'; + +/// The base class for all annotation change commands. +abstract class ChangeCommand { + /// Undoes the change. + void undo(); + + /// Redoes the change. + void redo(); +} + +/// Represents a change in the properties of an annotation. +class AnnotationPropertyChangeTracker extends ChangeCommand { + /// Creates a new instance of [AnnotationPropertyChangeTracker]. + AnnotationPropertyChangeTracker({ + required this.annotation, + required this.propertyName, + required this.oldValue, + required this.newValue, + }); + + /// The annotation whose property was changed. + final Annotation annotation; + + /// The name of the property that was changed. + final String propertyName; + + /// The old value of the property. + final Object oldValue; + + /// The new value of the property. + final Object newValue; + + /// Undoes the change. + @override + void undo() { + _setAnnotationProperty(propertyName, oldValue); + } + + /// Redoes the change. + @override + void redo() { + _setAnnotationProperty(propertyName, newValue); + } + + void _setAnnotationProperty(String propertyName, dynamic value) { + if (propertyName == 'color') { + annotation.setColor(value as Color); + } else if (propertyName == 'opacity') { + annotation.setOpacity(value as double); + } else if (propertyName == 'isLocked') { + annotation.setIsLocked(value as bool); + } else if (propertyName == 'icon') { + if (annotation is StickyNoteAnnotation) { + (annotation as StickyNoteAnnotation).setIcon( + value as PdfStickyNoteIcon, + ); + } + } else if (propertyName == 'position') { + if (annotation is StickyNoteAnnotation) { + (annotation as StickyNoteAnnotation).setPosition(value as Offset); + } + } else if (propertyName == 'text') { + if (annotation is StickyNoteAnnotation) { + (annotation as StickyNoteAnnotation).setText(value as String); + } + } + } +} + +/// Represents a change in adding or removing an annotation. +class AnnotationAddOrRemoveTracker extends ChangeCommand { + /// Creates a new instance of [AnnotationAddOrRemoveTracker]. + AnnotationAddOrRemoveTracker({ + required this.annotation, + required this.undoCallback, + required this.redoCallback, + }); + + /// The annotation that was added or removed. + final Annotation annotation; + + /// The callback to be called when undoing the change. + final void Function(Annotation) undoCallback; + + /// The callback to be called when redoing the change. + final void Function(Annotation) redoCallback; + + /// Undoes the change. + @override + void undo() { + undoCallback(annotation); + } + + /// Redoes the change. + @override + void redo() { + redoCallback(annotation); + } +} + +/// Represents a change in adding or removing all annotations. +class ClearAnnotationsTracker extends ChangeCommand { + /// Creates a new instance of [ClearAnnotationsTracker]. + ClearAnnotationsTracker({ + required this.annotations, + required this.undoCallback, + required this.redoCallback, + }); + + /// The annotation that was added or removed. + final List annotations; + + /// The callback to be called when undoing the change. + final void Function(Annotation) undoCallback; + + /// The callback to be called when redoing the change. + final void Function(Annotation) redoCallback; + + @override + void undo() { + annotations.forEach(undoCallback); + } + + @override + void redo() { + annotations.forEach(redoCallback); + } +} + +/// Represents a change in the value of the form field. +class FormFieldValueChangeTracker extends ChangeCommand { + /// Creates a new instance of [FormFieldValueChangeTracker]. + FormFieldValueChangeTracker({ + required this.records, + required this.onUndoOrRedo, + }); + + /// The records of the changes made in the form fields + final List records; + + /// Occurs when undoing or redoing the change in the form fields. + final void Function(PdfFormField, Object?, bool) onUndoOrRedo; + + /// Undoes the change. + @override + void undo() { + for (final FormFieldValueChangeRecord record in records) { + onUndoOrRedo(record.formField, record.oldValue, true); + } + } + + /// Redoes the change. + @override + void redo() { + for (final FormFieldValueChangeRecord record in records) { + onUndoOrRedo(record.formField, record.newValue, true); + } + } +} + +/// Represents a change in the value of the form field. +class FormFieldValueChangeRecord { + /// Creates a new instance of [FormFieldValueChangeRecord]. + FormFieldValueChangeRecord({ + required this.formField, + required this.oldValue, + required this.newValue, + }); + + /// The form field whose value is changed. + final PdfFormField formField; + + /// The old value of the form field. + final Object? oldValue; + + /// The new value of the form field. + final Object? newValue; +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_tracker.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_tracker.dart new file mode 100644 index 000000000..4ee5105c6 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_tracker.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'change_command.dart'; + +/// Class that tracks changes in the annotation. +class ChangeTracker { + /// Initializes a new instance of the [ChangeTracker] class. + UndoHistoryController? _undoController; + + /// The undo controller. + UndoHistoryController get undoController => _undoController!; + set undoController(UndoHistoryController value) { + if (_undoController != value) { + _undoController = value; + _undoController!.onUndo.addListener(undo); + _undoController!.onRedo.addListener(redo); + _updateState(); + } + } + + final List _undoStack = []; + final List _redoStack = []; + + bool _changeInProgress = false; + + /// Indicates whether a change is in progress. + bool get changeInProgress => _changeInProgress; + + /// Adds the change to the undo stack. + void addChange(ChangeCommand change) { + if (_changeInProgress) { + return; + } + _undoStack.add(change); + _redoStack.clear(); + _updateState(); + } + + /// Undoes the change. + void undo() { + if (_undoStack.isNotEmpty) { + _changeInProgress = true; + final ChangeCommand change = _undoStack.removeLast(); + change.undo(); + _redoStack.add(change); + _changeInProgress = false; + _updateState(); + } + } + + /// Redoes the change. + void redo() { + if (_redoStack.isNotEmpty) { + _changeInProgress = true; + final ChangeCommand change = _redoStack.removeLast(); + change.redo(); + _undoStack.add(change); + _changeInProgress = false; + _updateState(); + } + } + + void _updateState() { + _undoController?.value = UndoHistoryValue( + canUndo: _undoStack.isNotEmpty, + canRedo: _redoStack.isNotEmpty, + ); + } + + /// Resets the undo and redo stacks. + void resetStacks() { + _undoStack.clear(); + _redoStack.clear(); + _updateState(); + } + + /// Resets the undo controller. + void resetController() { + _undoController?.removeListener(undo); + _undoController?.removeListener(redo); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart index c79e4aa23..3d06f649f 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart @@ -1,15 +1,33 @@ +import 'dart:ffi' as ffi; import 'dart:io'; /// Checks whether focus node of pdf page view has primary focus. bool hasPrimaryFocus = false; /// Prevents default menu. -void preventDefaultMenu() { - // ignore: avoid_returning_null_for_void - return null; -} +void preventDefaultMenu() {} + +/// Enabled default menu. +void enableDefaultMenu() {} /// Gets platform type. String getPlatformType() { return Platform.operatingSystem; } + +/// To check whether pdfium is loaded or not on Android platform +bool isPdfiumLoaded() { + if (!Platform.isAndroid) { + return false; + } + + try { + final ffi.DynamicLibrary library = ffi.DynamicLibrary.open('libpdfium.so'); + if (library.handle != ffi.nullptr) { + library.close(); + } + return true; + } catch (e) { + return false; + } +} 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 868e2bc31..5e033d1f6 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart @@ -1,10 +1,9 @@ import 'dart:io'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import '../../pdfviewer.dart'; /// Represents a base class of PDF document provider. /// The PDF provider can be from Asset, Memory, File and Network. @@ -30,7 +29,7 @@ class NetworkPdf extends PdfProvider { /// /// The arguments [url] must not be null. NetworkPdf(String url, Map? headers) - : assert(url.isNotEmpty) { + : assert(url.isNotEmpty) { _url = url; _headers = headers; } @@ -46,14 +45,7 @@ class NetworkPdf extends PdfProvider { @override Future getPdfBytes(BuildContext context) async { - if (_documentBytes == null) { - try { - _documentBytes = - await http.readBytes(Uri.parse(_url), headers: _headers); - } on Exception catch (e) { - throw e.toString(); - } - } + _documentBytes ??= await http.readBytes(Uri.parse(_url), headers: _headers); return Future.value(_documentBytes); } } @@ -95,7 +87,7 @@ class AssetPdf extends PdfProvider { /// /// [assetName] must not be null. AssetPdf(String assetName, AssetBundle? bundle) - : assert(assetName.isNotEmpty) { + : assert(assetName.isNotEmpty) { _pdfPath = assetName; _bundle = bundle; } @@ -107,14 +99,10 @@ class AssetPdf extends PdfProvider { @override Future getPdfBytes(BuildContext context) async { if (_documentBytes == null) { - try { - final ByteData bytes = await ((_bundle != null) - ? _bundle!.load(_pdfPath) - : DefaultAssetBundle.of(context).load(_pdfPath)); - _documentBytes = bytes.buffer.asUint8List(); - } on Exception catch (e) { - throw e.toString(); - } + final ByteData bytes = await ((_bundle != null) + ? _bundle!.load(_pdfPath) + : DefaultAssetBundle.of(context).load(_pdfPath)); + _documentBytes = bytes.buffer.asUint8List(); } return Future.value(_documentBytes); } @@ -140,13 +128,7 @@ class FilePdf extends PdfProvider { @override Future getPdfBytes(BuildContext context) async { - if (_documentBytes == null) { - try { - _documentBytes = await _file.readAsBytes(); - } on Exception catch (e) { - throw e.toString(); - } - } + _documentBytes ??= await _file.readAsBytes(); return Future.value(_documentBytes); } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_source.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_source.dart new file mode 100644 index 000000000..1c7d4edca --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_source.dart @@ -0,0 +1,148 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; +import '../../pdfviewer.dart'; + +/// Represents a base class of PDF document source. +/// +/// This abstract class defines the interface for various sources of PDF documents, +/// such as network, asset, file, or memory. Subclasses should implement the +/// [getBytes] method to provide the specific logic for retrieving the PDF data. +/// +/// See also: +/// * [URLPDFSource], for fetching a PDF from a URL. +/// * [AssetPDFSource], for loading a PDF from app assets. +/// * [FilePDFSource], for loading a PDF from a local file. +/// * [BytePDFSource], for loading a PDF stored in memory. +abstract class PDFSource { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const PDFSource(); + + /// Retrieves the byte data of the PDF document. + /// This method should be implemented by subclasses to provide the specific logic for obtaining the PDF data from their respective sources. + /// + /// The [context] parameter can be used if needed for asset resolution or other context-dependent operations. + /// Returns a [Future] that completes with a [Uint8List] containing the PDF document bytes. + Future getBytes(BuildContext context); +} + +/// Fetches the given PDF document URL from the network. +/// +/// This class provides functionality to load a PDF document from a remote URL. +/// The PDF will be downloaded and won’t be stored in memory for viewing. +/// +/// See also: +/// * [SfPdfViewer.network], which provides a convenient way to display a PDF from a URL. +class URLPDFSource extends PDFSource { + /// Creates a [URLPDFSource] that fetches a PDF document from the specified URL. + /// + /// The [url] parameter must not be null or empty. + /// The [headers] parameter can be used to add custom HTTP headers to the request. + URLPDFSource(String url, {Map? headers}) + : assert(url.isNotEmpty) { + _url = url; + _headers = headers; + } + + /// The URL from which the PDF will be fetched. + late String _url; + + /// The document headers + Map? _headers; + + /// The document bytes + Uint8List? _documentBytes; + + /// Retrieves the bytes of the PDF document from the network. + @override + Future getBytes(BuildContext context) async { + _documentBytes ??= await http.readBytes(Uri.parse(_url), headers: _headers); + return Future.value(_documentBytes); + } +} + +/// Decodes the given [Uint8List] buffer as a PDF document. +/// +/// This class provides functionality to load a PDF document directly from memory using a [Uint8List] buffer containing the PDF data. +/// +/// See also: +/// * [SfPdfViewer.memory], which provides a convenient way to display a PDF using a [Uint8List]. +class BytePDFSource extends PDFSource { + /// Creates a [BytePDFSource] that decodes the specified [Uint8List] as a PDF document. + BytePDFSource(Uint8List bytes) { + _pdfBytes = bytes; + } + + late Uint8List _pdfBytes; + + /// Retrieves the bytes of the PDF document from memory. + @override + Future getBytes(BuildContext context) async { + return Future.value(_pdfBytes); + } +} + +/// Fetches a PDF document from an [AssetBundle]. +/// +/// This class provides functionality to load a PDF document from an asset specified by [assetPath]. +/// It can use either a provided [AssetBundle] or the default asset bundle of the current [BuildContext]. +/// +/// See also: +/// * [SfPdfViewer.asset], which provides a convenient way to display a PDF viewer widget using an asset. +class AssetPDFSource extends PDFSource { + /// Creates an [AssetPDFSource] that fetches the PDF document from the specified asset. + /// + /// The [assetPath] parameter must not be null or empty. + /// The [bundle] parameter is optional. If not provided, the default asset bundle will be used. + AssetPDFSource(String assetPath, {AssetBundle? bundle}) + : assert(assetPath.isNotEmpty) { + _pdfPath = assetPath; + _bundle = bundle; + } + + late String _pdfPath; + AssetBundle? _bundle; + Uint8List? _documentBytes; + + /// Retrieves the bytes of the PDF document from the asset. + @override + Future getBytes(BuildContext context) async { + if (_documentBytes == null) { + final ByteData bytes = + await ((_bundle != null) + ? _bundle!.load(_pdfPath) + : DefaultAssetBundle.of(context).load(_pdfPath)); + _documentBytes = bytes.buffer.asUint8List(); + } + return Future.value(_documentBytes); + } +} + +/// Decodes a [File] object as a PDF document. +/// +/// This class provides functionality to load a PDF document from a [File] on the local file system. +/// +/// See also: +/// +/// * [SfPdfViewer.file], which provides a convenient way to display a PDF using a file. +class FilePDFSource extends PDFSource { + /// Creates a [FilePDFSource] that decodes the specified [File] as a PDF document. + FilePDFSource(File file) { + _file = file; + } + + late File _file; + + /// The document bytes + Uint8List? _documentBytes; + + /// Retrieves the bytes of the PDF document from the file. + @override + Future getBytes(BuildContext context) async { + _documentBytes ??= await _file.readAsBytes(); + 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 f67541e6b..d70628e04 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart @@ -3,17 +3,28 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/common/mobile_helper.dart' - if (dart.library.html) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' + +import '../control/pdftextline.dart'; +import 'mobile_helper.dart' + if (dart.library.js_interop) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' as helper; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdftextline.dart'; /// Indicates whether the current environment is running in Desktop -bool kIsDesktop = kIsWeb || Platform.isMacOS || Platform.isWindows; +bool kIsDesktop = + kIsWeb || Platform.isMacOS || Platform.isWindows || Platform.isLinux; /// Indicates whether the current environment is running in macOS bool kIsMacOS = helper.getPlatformType() == 'macos'; +/// Indicates the default padding for checkbox and radio button form fields on mobile platforms. +const double kFormFieldSelectionPadding = 3.0; + +/// Indicates the default width of signature pad. +const double kSignaturePadWidth = 306; + +/// Indicates the default height of signature pad. +const double kSignaturePadHeight = 172; + /// TextSelectionHelper for storing information of text selection. class TextSelectionHelper { /// It will be true,if text selection is started. @@ -90,6 +101,34 @@ class TextSelectionHelper { /// Gets the selected text lines. List selectedTextLines = []; + + void reset() { + selectionEnabled = false; + mouseSelectionEnabled = false; + firstSelectedGlyph = null; + viewId = null; + cursorPageNumber = null; + globalSelectedRegion = null; + copiedText = null; + startBubbleX = null; + startBubbleY = null; + endBubbleX = null; + endBubbleY = null; + heightPercentage = null; + textLines = null; + cursorTextLines = null; + startBubbleLine = null; + endBubbleLine = null; + historyEntry = null; + isCursorExit = false; + isCursorReachedTop = false; + initialScrollOffset = 0; + finalScrollOffset = 0; + enableTapSelection = false; + startIndex = 0; + endIndex = 0; + selectedTextLines.clear(); + } } /// Determines different page navigation. @@ -107,5 +146,45 @@ enum Navigation { lastPage, /// Navigates to previous page - previousPage + previousPage, +} + +/// The [PdfColor] extension for [Color]. +extension PdfColorExtension on PdfColor { + /// Converts the [PdfColor] to [Color]. + Color get materialColor => Color.fromRGBO(r, g, b, 1); +} + +/// The [Color] extension. +extension MaterialColorExtension on Color { + /// Converts the [Color] to [PdfColor]. + PdfColor get pdfColor => + PdfColor((r * 255).round(), (g * 255).round(), (b * 255).round()); + + /// Converts the [Color] to a lighter color based on the given factor. + Color getLightenColor(double factor) { + factor = factor.clamp(-1.0, 1.0); + + double red = r; + double green = g; + double blue = b; + + if (factor < 0) { + factor += 1; + red *= factor; + green *= factor; + blue *= factor; + } else { + red = (1 - red) * factor + red; + green = (1 - green) * factor + green; + blue = (1 - blue) * factor + blue; + } + + return Color.fromRGBO( + (red * 255).round(), + (green * 255).round(), + (blue * 255).round(), + a, + ); + } } 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 e536f14c3..b9d38d8d8 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart @@ -1,10 +1,5 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; import 'package:syncfusion_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; import 'package:uuid/uuid.dart'; @@ -14,157 +9,48 @@ class PdfViewerPlugin { int _pageCount = 0; List? _originalHeight; List? _originalWidth; - List? _renderingPages = []; String? _documentID; - Map>? _renderedPages = >{}; - Map? _pageScale = {}; - CancelableOperation? _nativeImage; /// Initialize the PDF renderer. - Future initializePdfRenderer(Uint8List documentBytes) async { + Future initializePdfRenderer( + Uint8List documentBytes, + String? password, + ) async { _documentID = const Uuid().v1(); final String? pageCount = await PdfViewerPlatform.instance - .initializePdfRenderer(documentBytes, _documentID!); + .initializePdfRenderer(documentBytes, _documentID!, password); _pageCount = int.parse(pageCount!); return _pageCount; } + /// Get the current document ID + String get documentID => _documentID ?? ''; + /// Retrieves original height of PDF pages. Future?> getPagesHeight() async { - _originalHeight = - await PdfViewerPlatform.instance.getPagesHeight(_documentID!); + _originalHeight = await PdfViewerPlatform.instance.getPagesHeight( + _documentID!, + ); return _originalHeight; } /// Retrieves original width of PDF pages. Future?> getPagesWidth() async { - _originalWidth = - await PdfViewerPlatform.instance.getPagesWidth(_documentID!); + _originalWidth = await PdfViewerPlatform.instance.getPagesWidth( + _documentID!, + ); return _originalWidth; } - bool _hasRenderedImage(int pageIndex, double scale, bool considerScale) { - bool hasImage = false; - _renderingPages?.forEach((RenderedImage image) { - if ((image.pageIndex == pageIndex && !considerScale) || - (image.pageIndex == pageIndex && - considerScale && - image.scale == scale)) { - hasImage = true; - } - }); - return hasImage; - } - - /// Get the specific image of PDF - Future>?> _getImage(int pageIndex, double currentScale, - bool isZoomChanged, int currentPageNumber) async { - if (_renderingPages != null && - (!_hasRenderedImage(pageIndex, currentScale, false) || - (isZoomChanged && - pageIndex == currentPageNumber && - !_hasRenderedImage(pageIndex, currentScale, true)))) { - final RenderedImage renderedImage = - RenderedImage(pageIndex, currentScale); - _renderingPages!.add(renderedImage); - _nativeImage = CancelableOperation.fromFuture( - PdfViewerPlatform.instance - .getImage(pageIndex, currentScale, _documentID!)); - Future imageFuture = _nativeImage!.value.whenComplete(() { - _renderingPages?.remove(renderedImage); - }); - if (!kIsDesktop) { - final Future image = imageFuture; - imageFuture = imageFuture.timeout(const Duration(milliseconds: 1000), - onTimeout: () { - _renderingPages?.remove(renderedImage); - return null; - }); - imageFuture = image; - } - final Uint8List? image = await imageFuture; - if (_renderedPages != null && image != null) { - if (kIsDesktop) { - _renderedPages![pageIndex] = Uint8List.fromList(image); - } else { - _renderedPages![pageIndex] = image; - } - } - } - return _renderedPages; - } - - /// Retrieves PDF pages as image collection for specified pages. - Future>?> getSpecificPages( - int startPageIndex, - int endPageIndex, - double currentScale, - bool isZoomChanged, - int currentPageNumber, - bool canRenderImage) async { - imageCache!.clear(); - if (!canRenderImage) { - _nativeImage?.cancel(); - _renderingPages?.clear(); - return _renderedPages; - } - for (int pageIndex = startPageIndex; - pageIndex <= endPageIndex; - pageIndex++) { - if (_renderedPages != null && - (!_renderedPages!.containsKey(pageIndex) || - (isZoomChanged && - pageIndex == currentPageNumber && - currentScale > 1.75))) { - currentScale = (pageIndex == currentPageNumber) ? currentScale : 1.0; - if (_pageScale != null && - (!_pageScale!.containsKey(pageIndex) || - _pageScale![pageIndex] != currentScale)) { - _pageScale![pageIndex] = currentScale; - await _getImage( - pageIndex, currentScale, isZoomChanged, currentPageNumber); - } - } - } - final List pdfPage = []; - _renderedPages?.forEach((int key, List value) { - if (!(key >= startPageIndex && key <= endPageIndex)) { - pdfPage.add(key); - } - }); - if (_pageScale != null) { - for (int index = _pageScale!.length - 1; index >= 0; index--) { - if (!_renderedPages!.containsKey(_pageScale!.keys.elementAt(index))) { - _pageScale!.remove(_pageScale!.keys.elementAt(index)); - } - } - } - - // ignore: avoid_function_literals_in_foreach_calls - pdfPage.forEach((int index) { - _renderedPages?.remove(index); - }); - return Future>?>.value(_renderedPages); - } - /// Dispose the rendered pages Future closeDocument() async { - imageCache!.clear(); + imageCache.clear(); if (_documentID != null) { await PdfViewerPlatform.instance.closeDocument(_documentID!); } _pageCount = 0; _originalWidth = null; _originalHeight = null; - _renderingPages = null; - _nativeImage?.cancel(); - _nativeImage = null; - if (_renderedPages != null) { - _renderedPages = null; - } - if (_pageScale != null) { - _pageScale = 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 index bb81442ba..55913f79a 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart @@ -1,19 +1,35 @@ -import 'dart:html' as html; +import 'dart:js_interop'; +import 'package:web/web.dart' as web; /// Checks whether focus node of pdf page view has primary focus. bool hasPrimaryFocus = false; +/// Context Menu Event Listener variable. +JSFunction _contextMenuListener = + (web.MouseEvent e) { + e.preventDefault(); + }.toJS; + +/// Keyboard Event Listener variable. +JSFunction _keyDownListener = _preventSpecificDefaultMenu.toJS; + /// Prevent default menu. void preventDefaultMenu() { - html.window.document.onKeyDown - .listen((html.KeyboardEvent e) => _preventSpecificDefaultMenu(e)); - html.window.document.onContextMenu - .listen((html.MouseEvent e) => e.preventDefault()); + web.window.document.addEventListener('keydown', _keyDownListener); + web.window.document.addEventListener('contextmenu', _contextMenuListener); +} + +/// Enable default menu. +void enableDefaultMenu() { + web.window.document.removeEventListener('keydown', _keyDownListener); + web.window.document.removeEventListener('contextmenu', _contextMenuListener); } /// Prevent specific default menu such as zoom panel,search. -void _preventSpecificDefaultMenu(html.KeyboardEvent e) { - if (e.keyCode == 114 || (e.ctrlKey && e.keyCode == 70)) { +void _preventSpecificDefaultMenu(web.KeyboardEvent e) { + if (e.keyCode == 114 || + (e.ctrlKey && e.keyCode == 70) || + (e.metaKey && e.keyCode == 70)) { e.preventDefault(); } if (hasPrimaryFocus && @@ -25,8 +41,11 @@ void _preventSpecificDefaultMenu(html.KeyboardEvent e) { /// Gets platform type. String getPlatformType() { - if (html.window.navigator.platform!.toLowerCase().contains('macintel')) { + if (web.window.navigator.platform.toLowerCase().contains('macintel')) { return 'macos'; } - return html.window.navigator.platform!.toLowerCase(); + return web.window.navigator.platform.toLowerCase(); } + +/// In web, pdfium cannot loaded. +bool isPdfiumLoaded() => false; diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/desktop_scrollbar.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/desktop_scrollbar.dart new file mode 100644 index 000000000..6fc7f54ec --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/desktop_scrollbar.dart @@ -0,0 +1,387 @@ +import 'dart:async'; +import 'dart:math' show max; + +import 'package:flutter/material.dart'; + +/// Default width of the scrollbar +const double _scrollbarWidth = 8; + +/// Minimum length of the scrollbar +const double _scrollbarLength = 50; + +/// Default padding around the scrollbar +const double _scrollbarPadding = 1; + +/// A custom scrollbar implementation for desktop applications. +/// +/// This widget provides customizable horizontal and vertical scrollbars +/// that can fade in/out based on user interaction. +class DesktopScrollbar extends StatefulWidget { + const DesktopScrollbar({ + super.key, + this.canShowHorizontalScrollbar = true, + this.canShowVerticalScrollbar = true, + this.canFadeHorizontalScrollbar = true, + this.canFadeVerticalScrollbar = true, + this.fadeOutDuration = const Duration(seconds: 2), + required this.controller, + required this.viewportSize, + required this.contentSize, + this.onVerticalDragStart, + this.onVerticalDragEnd, + this.onVerticalDragUpdate, + this.onHorizontalDragStart, + this.onHorizontalDragUpdate, + this.onHorizontalDragEnd, + this.verticalScrollbarPadding = const EdgeInsets.only( + right: _scrollbarPadding, + ), + this.horizontalScrollbarPadding = const EdgeInsets.only( + bottom: _scrollbarPadding, + ), + }); + + /// Controller that manages the viewport transformation + final TransformationController controller; + + /// Size of the visible viewport + final Size viewportSize; + + /// Size of the total content area + final Size contentSize; + + /// Whether to show the horizontal scrollbar + final bool canShowHorizontalScrollbar; + + /// Whether to show the vertical scrollbar + final bool canShowVerticalScrollbar; + + /// Whether the horizontal scrollbar can fade out when inactive + final bool canFadeHorizontalScrollbar; + + /// Whether the vertical scrollbar can fade out when inactive + final bool canFadeVerticalScrollbar; + + /// Duration after which scrollbars fade out when inactive + final Duration fadeOutDuration; + + /// Padding for the vertical scrollbar + final EdgeInsets verticalScrollbarPadding; + + /// Padding for the horizontal scrollbar + final EdgeInsets horizontalScrollbarPadding; + + /// Callback for vertical drag start events + final void Function(DragStartDetails)? onVerticalDragStart; + + /// Callback for vertical drag update events + final void Function(DragUpdateDetails)? onVerticalDragUpdate; + + /// Callback for vertical drag end events + final void Function(DragEndDetails)? onVerticalDragEnd; + + /// Callback for horizontal drag start events + final void Function(DragStartDetails)? onHorizontalDragStart; + + /// Callback for horizontal drag update events + final void Function(DragUpdateDetails)? onHorizontalDragUpdate; + + /// Callback for horizontal drag end events + final void Function(DragEndDetails)? onHorizontalDragEnd; + + @override + State createState() => _DesktopScrollbarState(); +} + +class _DesktopScrollbarState extends State + with SingleTickerProviderStateMixin { + /// The calculated length of the vertical scrollbar + late double _verticalScrollbarLength; + + /// The calculated length of the horizontal scrollbar + late double _horizontalScrollbarLength; + + /// Ratio of viewport width to content width for scrollbar positioning + late double _widthRatio; + + /// Ratio of viewport height to content height for scrollbar positioning + late double _heightRatio; + + /// Top position of the vertical scrollbar + late double _top; + + /// Left position of the horizontal scrollbar + late double _left; + + /// Maximum vertical scroll limit + late double _verticalLimit; + + /// Maximum horizontal scroll limit + late double _horizontalLimit; + + /// Whether the vertical scrollbar should be visible + bool _canShowVerticalScrollbar = false; + + /// Whether the horizontal scrollbar should be visible + bool _canShowHorizontalScrollbar = false; + + /// Key for the vertical scrollbar widget + final ValueKey _verticalScrollbarKey = const ValueKey( + 'vertical_scrollbar', + ); + + /// Key for the horizontal scrollbar widget + final ValueKey _horizontalScrollbarKey = const ValueKey( + 'horizontal_scrollbar', + ); + + /// Controller for the scrollbar fade animation + late final AnimationController _animationController; + + /// Animation that controls the opacity of scrollbars when fading out + late final Animation _fadeOutAnimation; + + /// Whether the mouse is currently hovering over a scrollbar + bool _isHovering = false; + + /// Whether the content is currently being scrolled + bool _isScrolling = false; + + /// Timer that tracks when to fade out scrollbars after scrolling stops + Timer? _scrollTimer; + + @override + void initState() { + super.initState(); + // Initialize the animation controller for fade effects + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + reverseDuration: Duration.zero, + ); + // Create the fade out animation + _fadeOutAnimation = Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeOut), + ); + // Listen for scroll events + widget.controller.addListener(_onScroll); + } + + @override + void dispose() { + // Clean up resources + widget.controller.removeListener(_onScroll); + _scrollTimer?.cancel(); + _animationController.dispose(); + super.dispose(); + } + + /// Handles scroll events from the controller + void _onScroll() { + if (!_isScrolling) { + _isScrolling = true; + _updateScrollbarVisibility(); + } + _startFadeOutTimer(); + } + + /// Starts or resets the timer for fading out scrollbars + void _startFadeOutTimer() { + _scrollTimer?.cancel(); + _scrollTimer = Timer(widget.fadeOutDuration, () { + _isScrolling = false; + _isHovering = false; + _updateScrollbarVisibility(); + }); + } + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: widget.controller, + builder: (BuildContext context, Matrix4 matrix, _) { + final double scale = widget.controller.value[0]; + final Offset offset = widget.controller.toScene(Offset.zero); + + // Determine if scrollbars should be visible based on content and viewport sizes + _canShowVerticalScrollbar = + widget.canShowVerticalScrollbar && + widget.contentSize.height * scale > widget.viewportSize.height; + _canShowHorizontalScrollbar = + widget.canShowHorizontalScrollbar && + widget.contentSize.width * scale > widget.viewportSize.width; + + Widget verticalScrollbar = const SizedBox.shrink(); + Widget horizontalScrollbar = const SizedBox.shrink(); + if (_canShowHorizontalScrollbar) { + // Calculate maximum horizontal scroll limit + _horizontalLimit = + widget.contentSize.width - (widget.viewportSize.width / scale); + + // Calculate the ratio of viewport to scaled content + final double viewportToContentRatio = + widget.viewportSize.width / (widget.contentSize.width * scale); + + // Calculate scrollbar length based on the viewport ratio + final double calculatedLength = + widget.viewportSize.width * viewportToContentRatio; + + // Ensure scrollbar has at least the minimum length + _horizontalScrollbarLength = max(_scrollbarLength, calculatedLength); + + // Calculate position ratio for scrollbar movement + _widthRatio = + (widget.viewportSize.width - _horizontalScrollbarLength) / + _horizontalLimit; + + // Calculate left position of the scrollbar + _left = (offset.dx * _widthRatio).clamp( + 0.0, + widget.viewportSize.width - _horizontalScrollbarLength, + ); + + horizontalScrollbar = Align( + alignment: Alignment.bottomLeft, + child: GestureDetector( + onHorizontalDragStart: widget.onHorizontalDragStart, + onHorizontalDragEnd: widget.onHorizontalDragEnd, + onHorizontalDragUpdate: (DragUpdateDetails details) { + // Convert drag delta to content scroll delta + final double dx = details.primaryDelta! / _widthRatio; + widget.controller.value = + widget.controller.value.clone()..translate(-dx); + widget.onHorizontalDragUpdate?.call(details); + }, + child: Container( + width: _horizontalScrollbarLength, + height: _scrollbarWidth, + margin: EdgeInsets.only(left: _left), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(_scrollbarWidth / 2), + ), + ), + ), + ); + + if (widget.canFadeHorizontalScrollbar) { + horizontalScrollbar = FadeTransition( + opacity: _fadeOutAnimation, + child: horizontalScrollbar, + ); + } + } + + if (_canShowVerticalScrollbar) { + // Calculate maximum vertical scroll limit + _verticalLimit = + widget.contentSize.height - (widget.viewportSize.height / scale); + + // Calculate the ratio of viewport to scaled content + final double viewportToContentRatio = + widget.viewportSize.height / (widget.contentSize.height * scale); + + // Calculate scrollbar length based on the viewport ratio + final double calculatedLength = + widget.viewportSize.height * viewportToContentRatio; + + // Ensure scrollbar has at least the minimum length + _verticalScrollbarLength = max(_scrollbarLength, calculatedLength); + + // Calculate position ratio for scrollbar movement + _heightRatio = + (widget.viewportSize.height - _verticalScrollbarLength) / + _verticalLimit; + + // Calculate top position of the scrollbar + _top = (offset.dy * _heightRatio).clamp( + 0.0, + widget.viewportSize.height - _verticalScrollbarLength, + ); + + verticalScrollbar = Align( + alignment: Alignment.topRight, + child: GestureDetector( + onVerticalDragStart: widget.onVerticalDragStart, + onVerticalDragEnd: widget.onVerticalDragEnd, + onVerticalDragUpdate: (DragUpdateDetails details) { + // Convert drag delta to content scroll delta + final double dy = details.primaryDelta! / _heightRatio; + widget.controller.value = + widget.controller.value.clone()..translate(0.0, -dy); + widget.onVerticalDragUpdate?.call(details); + }, + child: Container( + width: _scrollbarWidth, + height: _verticalScrollbarLength, + margin: EdgeInsets.only(top: _top), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(_scrollbarWidth / 2), + ), + ), + ), + ); + + if (widget.canFadeVerticalScrollbar) { + verticalScrollbar = FadeTransition( + opacity: _fadeOutAnimation, + child: verticalScrollbar, + ); + } + } + + return Stack( + children: [ + if (_canShowHorizontalScrollbar) + Positioned( + key: _horizontalScrollbarKey, + left: 0, + bottom: widget.horizontalScrollbarPadding.bottom, + width: widget.viewportSize.width, + height: 1.5 * _scrollbarWidth, + child: MouseRegion( + hitTestBehavior: HitTestBehavior.translucent, + cursor: SystemMouseCursors.basic, + onEnter: (_) { + _isHovering = true; + _updateScrollbarVisibility(); + }, + onExit: (_) => _startFadeOutTimer(), + child: horizontalScrollbar, + ), + ), + if (_canShowVerticalScrollbar) + Positioned( + key: _verticalScrollbarKey, + right: widget.verticalScrollbarPadding.right, + top: 0, + width: 1.5 * _scrollbarWidth, + height: widget.viewportSize.height, + child: MouseRegion( + hitTestBehavior: HitTestBehavior.translucent, + cursor: SystemMouseCursors.basic, + onEnter: (_) { + _isHovering = true; + _updateScrollbarVisibility(); + }, + onExit: (_) => _startFadeOutTimer(), + child: verticalScrollbar, + ), + ), + ], + ); + }, + ); + } + + /// Updates the scrollbar visibility based on hovering and scrolling states. + /// Shows scrollbars when user is interacting with them, hides when inactive. + void _updateScrollbarVisibility() { + if (_isScrolling || _isHovering) { + _animationController.reverse(); + } else { + _animationController.forward(); + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart index 472394f07..0c862cdd0 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart @@ -4,7 +4,7 @@ enum PdfInteractionMode { selection, /// Enables the panning mode in a desktop browser to move or scroll through the pages using mouse dragging. - pan + pan, } /// Represents different scrolling direction. @@ -13,7 +13,7 @@ enum PdfScrollDirection { vertical, /// Pages will be scrolled left and right. - horizontal + horizontal, } /// Represents different pdf layout mode. @@ -22,5 +22,59 @@ enum PdfPageLayoutMode { continuous, /// Page by page transition of pages. - single + single, +} + +/// Represents different flattening option. +enum PdfFlattenOption { + /// Do not flatten any form fields or annotations . + none, + + /// Flatten all the form fields. + formFields, +} + +/// Enumerates the values that represent the type of annotation that should be drawn using UI interaction on the PDF pages. +enum PdfAnnotationMode { + /// None + none, + + /// Highlight + highlight, + + /// Underline + underline, + + /// Strikethrough + strikethrough, + + /// Squiggly + squiggly, + + /// Sticky Note + stickyNote, +} + +/// Enumerates the values that represents different types of icons for a sticky note annotation. +enum PdfStickyNoteIcon { + /// Comment + comment, + + /// Key + key, + + /// Note + note, + + /// Help + help, + + /// New Paragraph + newParagraph, + + /// Paragraph + paragraph, + + /// Insert + insert, } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart index f146cedfa..13dedf618 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart @@ -1,4 +1,4 @@ -import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; +import '../common/pdfviewer_helper.dart'; /// Pagination class used for page navigation. class Pagination { 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 eb8bedad9..c6b7efc92 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,28 +1,36 @@ -import 'dart:typed_data'; +import 'dart:async'; +import 'dart:ui' as ui; +import 'package:async/async.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.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/pdfviewer.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/common/mobile_helper.dart' - if (dart.library.html) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' +import 'package:syncfusion_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; + +import '../../pdfviewer.dart'; +import '../annotation/annotation_container.dart'; +import '../common/mobile_helper.dart' + if (dart.library.js_interop) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' as helper; -import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_canvas.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/single_page_view.dart'; +import '../common/pdfviewer_helper.dart'; +import '../form_fields/form_field_container.dart'; +import '../theme/theme.dart'; +import 'pdf_scrollable.dart'; +import 'pdfviewer_canvas.dart'; +import 'single_page_view.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. const PdfPageView( Key key, - this.imageStream, this.viewportGlobalRect, this.parentViewport, this.interactionMode, + this.documentID, this.width, this.height, this.pageSpacing, @@ -30,9 +38,13 @@ class PdfPageView extends StatefulWidget { this.pdfPages, this.pageIndex, this.pdfViewerController, + this.undoController, + this.maxZoomLevel, this.enableDocumentLinkAnnotation, this.enableTextSelection, + this.textSelectionHelper, this.onTextSelectionChanged, + this.onHyperlinkClicked, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, this.currentSearchTextHighlightColor, @@ -48,10 +60,22 @@ class PdfPageView extends StatefulWidget { this.onPdfPagePointerUp, this.semanticLabel, this.isSinglePageView, + this.textDirection, + this.canShowHyperlinkDialog, + this.enableHyperlinkNavigation, + this.isAndroidTV, + this.canShowPageLoadingIndicator, + this.canShowSignaturePadDialog, + this.onTap, + this.formFields, + this.annotations, + this.selectedAnnotation, + this.onAnnotationSelectionChanged, + this.onStickyNoteDoubleTapped, ) : super(key: key); - /// Image stream - final Uint8List? imageStream; + /// Document ID of current pdf + final String documentID; /// Width of page final double width; @@ -74,6 +98,12 @@ class PdfPageView extends StatefulWidget { /// If true, document link annotation is enabled. final bool enableDocumentLinkAnnotation; + /// If true, hyperlink navigation is enabled. + final bool enableHyperlinkNavigation; + + /// Indicates whether hyperlink dialog must be shown or not. + final bool canShowHyperlinkDialog; + /// Index of page final int pageIndex; @@ -86,12 +116,24 @@ class PdfPageView extends StatefulWidget { /// Instance of [PdfViewerController] final PdfViewerController pdfViewerController; + /// Instance of [UndoHistoryController] + final UndoHistoryController undoController; + + /// Represents the maximum zoom level . + final double maxZoomLevel; + /// If false,text selection is disabled.Default value is true. final bool enableTextSelection; + /// Text selection helper instance. + final TextSelectionHelper textSelectionHelper; + /// Triggers when text selection is changed. final PdfTextSelectionChangedCallback? onTextSelectionChanged; + /// Triggers when Hyperlink is clicked. + final PdfHyperlinkClickedCallback? onHyperlinkClicked; + /// Triggers when text selection dragging started. final VoidCallback onTextSelectionDragStarted; @@ -137,6 +179,37 @@ class PdfPageView extends StatefulWidget { /// Determines layout option in PdfViewer. final bool isSinglePageView; + ///A direction of text flow. + final TextDirection textDirection; + + /// Returns true when the SfPdfViewer is deployed in Android TV. + final bool isAndroidTV; + + /// If true, the page loading indicator is enabled. + final bool canShowPageLoadingIndicator; + + /// Indicates whether the built-in signature pad dialog should be displayed or not. + /// Default value is true. + final bool canShowSignaturePadDialog; + + /// List of form fields. + final List formFields; + + /// List of annotations. + final List annotations; + + /// Selected annotation. + final Annotation? selectedAnnotation; + + /// Called when the user taps on the annotation. + final ValueChanged? onAnnotationSelectionChanged; + + /// Called when the user double taps on the sticky note annotation. + final ValueChanged? onStickyNoteDoubleTapped; + + /// Called when the user taps on the page. + final Function(Offset, int) onTap; + @override State createState() { return PdfPageViewState(); @@ -146,11 +219,15 @@ class PdfPageView extends StatefulWidget { /// State for [PdfPageView] class PdfPageViewState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; final GlobalKey _canvasKey = GlobalKey(); int _lastTap = DateTime.now().millisecondsSinceEpoch; int _consecutiveTaps = 1; final double _jumpOffset = 10.0; + int _imageWidth = 0; + int _imageHeight = 0; + /// Mouse cursor for mouse region widget SystemMouseCursor _cursor = SystemMouseCursors.basic; @@ -160,48 +237,78 @@ class PdfPageViewState extends State { /// CanvasRenderBox getter for accessing canvas properties. CanvasRenderBox? get canvasRenderBox => _canvasKey.currentContext?.findRenderObject() != null - ? - // ignore: avoid_as - (_canvasKey.currentContext?.findRenderObject())! as CanvasRenderBox + ? (_canvasKey.currentContext?.findRenderObject())! as CanvasRenderBox : null; + /// Height percentage of a page + double _heightPercentage = 1; + int _quarterTurns = 0; + bool _isRotatedTo90or270 = false; + late Size _originalPageSize; + double _previousImageFactor = -1.0; + bool _isTile = false; + + /// Form fields in the page + late List _formFields; + + late PdfInteractionMode _interactionMode; + + RawImage? _pdfPage; + RawImage? _tileImage; + CancelableOperation? _tileImageOperation; + CancelableOperation? _pageImageOperation; + Timer? _pageTimer; + TileImage? _tileImageCache; + double _dpr = 1.0; + int _numberOfActivePointers = 0; + bool _isZooming = false; + @override void initState() { if (kIsDesktop && !widget.isMobileWebView) { - helper.preventDefaultMenu(); focusNode.addListener(() { helper.hasPrimaryFocus = focusNode.hasFocus; }); } + _formFields = + widget.formFields + .where((formField) => formField.pageNumber == widget.pageIndex + 1) + .toList(); super.initState(); } @override void didChangeDependencies() { + _dpr = MediaQuery.devicePixelRatioOf(context); _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); super.didChangeDependencies(); } @override void dispose() { - PaintingBinding.instance?.imageCache?.clear(); - PaintingBinding.instance?.imageCache?.clearLiveImages(); + PaintingBinding.instance.imageCache.clear(); + PaintingBinding.instance.imageCache.clearLiveImages(); + clearPageImage(dispose: true); + focusNode.dispose(); _pdfViewerThemeData = null; + _effectiveThemeData = null; + _formFields.clear(); super.dispose(); } @override Widget build(BuildContext context) { - if (!kIsDesktop) { - PaintingBinding.instance?.imageCache?.clear(); - PaintingBinding.instance?.imageCache?.clearLiveImages(); - } final double pageSpacing = widget.pageIndex == widget.pdfViewerController.pageCount - 1 ? 0.0 : widget.pageSpacing; final double heightSpacing = - widget.scrollDirection == PdfScrollDirection.horizontal + widget.scrollDirection == PdfScrollDirection.horizontal || + widget.isSinglePageView ? 0.0 : pageSpacing; final double widthSpacing = @@ -209,295 +316,885 @@ class PdfPageViewState extends State { !widget.isSinglePageView ? pageSpacing : 0.0; - if (widget.imageStream != null) { - final PdfPageRotateAngle rotatedAngle = - widget.pdfDocument!.pages[widget.pageIndex].rotation; - final Widget image = Image.memory( - widget.imageStream!, - width: widget.width, - height: widget.height, - gaplessPlayback: true, - fit: BoxFit.fitWidth, - semanticLabel: widget.semanticLabel, - alignment: Alignment.center, - ); + if (_pdfPage != null) { + _calculateHeightPercentage(); + if (!kIsDesktop) { + PaintingBinding.instance.imageCache.clear(); + PaintingBinding.instance.imageCache.clearLiveImages(); + } + final Widget pdfPage = Container( height: widget.height + heightSpacing, width: widget.width + widthSpacing, color: Colors.white, alignment: Alignment.topCenter, - child: widget.scrollDirection == PdfScrollDirection.vertical - ? Column(children: [ - image, - Container( - height: pageSpacing, - color: _pdfViewerThemeData!.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == - Brightness.light - ? const Color(0xFFD6D6D6) - : const Color(0xFF303030)), - ) - ]) - : Row(children: [ - image, - Container( - width: widget.isSinglePageView ? 0.0 : pageSpacing, - color: _pdfViewerThemeData!.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == - Brightness.light - ? const Color(0xFFD6D6D6) - : const Color(0xFF303030)), + child: + widget.scrollDirection == PdfScrollDirection.vertical + ? Column( + children: [ + Stack( + children: [ + SizedBox( + width: widget.width, + height: widget.height, + child: Semantics( + label: widget.semanticLabel, + child: _pdfPage, + ), + ), + if (_tileImageCache != null && _tileImage != null) + Positioned( + top: + _tileImageCache!.visibleRect.top / + _heightPercentage, + left: + _tileImageCache!.visibleRect.left / + _heightPercentage, + width: + _tileImageCache!.visibleRect.width / + _heightPercentage, + height: + _tileImageCache!.visibleRect.height / + _heightPercentage, + child: SizedBox( + width: _tileImageCache!.imageSize.width, + height: _tileImageCache!.imageSize.height, + child: _tileImage, + ), + ), + ], + ), + Container( + height: widget.isSinglePageView ? 0.0 : pageSpacing, + color: + _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? + (Theme.of(context).colorScheme.brightness == + Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030)), + ), + ], ) - ]), + : Row( + children: [ + Stack( + children: [ + SizedBox( + width: widget.width, + height: widget.height, + child: Semantics( + label: widget.semanticLabel, + child: _pdfPage, + ), + ), + if (_tileImageCache != null && _tileImage != null) + Positioned( + top: + _tileImageCache!.visibleRect.top / + _heightPercentage, + left: + _tileImageCache!.visibleRect.left / + _heightPercentage, + width: + _tileImageCache!.visibleRect.width / + _heightPercentage, + height: + _tileImageCache!.visibleRect.height / + _heightPercentage, + child: SizedBox( + width: _tileImageCache!.imageSize.width, + height: _tileImageCache!.imageSize.height, + child: _tileImage, + ), + ), + ], + ), + Container( + width: widget.isSinglePageView ? 0.0 : pageSpacing, + color: + _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? + (Theme.of(context).colorScheme.brightness == + Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030)), + ), + ], + ), ); - int quarterTurns = 0; - if (rotatedAngle == PdfPageRotateAngle.rotateAngle90) { - quarterTurns = 1; - } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle180) { - quarterTurns = 2; - } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle270) { - quarterTurns = 3; - } - final bool isRotatedTo90or270 = - rotatedAngle == PdfPageRotateAngle.rotateAngle90 || - rotatedAngle == PdfPageRotateAngle.rotateAngle270; + + final PdfAnnotationMode annotationMode = + widget.pdfViewerController.annotationMode; + + _interactionMode = + (annotationMode == PdfAnnotationMode.highlight || + annotationMode == PdfAnnotationMode.strikethrough || + annotationMode == PdfAnnotationMode.underline || + annotationMode == PdfAnnotationMode.squiggly) + ? PdfInteractionMode.selection + : widget.interactionMode; + final Widget canvasContainer = Container( - height: isRotatedTo90or270 ? widget.width : widget.height, - width: isRotatedTo90or270 ? widget.height : widget.width, - alignment: Alignment.topCenter, - child: PdfViewerCanvas( - _canvasKey, - isRotatedTo90or270 ? widget.width : widget.height, - isRotatedTo90or270 ? widget.height : widget.width, - widget.pdfDocument, - widget.pageIndex, - widget.pdfPages, - widget.interactionMode, - widget.pdfViewerController, - widget.enableDocumentLinkAnnotation, - widget.enableTextSelection, - widget.onTextSelectionChanged, - widget.onTextSelectionDragStarted, - widget.onTextSelectionDragEnded, - widget.textCollection, - widget.currentSearchTextHighlightColor, - widget.otherSearchTextHighlightColor, - widget.pdfTextSearchResult, - widget.isMobileWebView, - widget.pdfScrollableStateKey, - widget.singlePageViewStateKey, - widget.viewportGlobalRect, - widget.scrollDirection, - widget.isSinglePageView)); - final Widget canvas = (kIsDesktop && - !widget.isMobileWebView && - canvasRenderBox != null) - ? RotatedBox( - quarterTurns: quarterTurns, - child: Listener( - onPointerSignal: (PointerSignalEvent details) { - if (widget.isSinglePageView && - details is PointerScrollEvent) { - widget.singlePageViewStateKey.currentState?.jumpTo( - yOffset: widget.pdfViewerController.scrollOffset.dy + + height: _isRotatedTo90or270 ? widget.width : widget.height, + width: _isRotatedTo90or270 ? widget.height : widget.width, + alignment: Alignment.topCenter, + child: PdfViewerCanvas( + _canvasKey, + _isRotatedTo90or270 ? widget.width : widget.height, + _isRotatedTo90or270 ? widget.height : widget.width, + widget.pdfDocument, + widget.pageIndex, + widget.pdfPages, + _interactionMode, + widget.pdfViewerController, + widget.enableDocumentLinkAnnotation, + widget.enableTextSelection, + widget.textSelectionHelper, + widget.onTextSelectionChanged, + widget.onHyperlinkClicked, + widget.onTextSelectionDragStarted, + widget.onTextSelectionDragEnded, + widget.textCollection, + widget.currentSearchTextHighlightColor, + widget.otherSearchTextHighlightColor, + widget.pdfTextSearchResult, + widget.isMobileWebView, + widget.pdfScrollableStateKey, + widget.singlePageViewStateKey, + widget.viewportGlobalRect, + widget.scrollDirection, + widget.isSinglePageView, + widget.textDirection, + widget.canShowHyperlinkDialog, + widget.enableHyperlinkNavigation, + widget.onAnnotationSelectionChanged, + ), + ); + final Widget canvas = + (kIsDesktop && !widget.isMobileWebView) + ? RotatedBox( + quarterTurns: _quarterTurns, + child: Listener( + onPointerSignal: (PointerSignalEvent details) { + if (widget.isSinglePageView && + details is PointerScrollEvent) { + widget.singlePageViewStateKey.currentState?.jumpTo( + yOffset: + widget.pdfViewerController.scrollOffset.dy + (details.scrollDelta.dy.isNegative ? -_jumpOffset - : _jumpOffset)); - } - canvasRenderBox!.updateContextMenuPosition(); - }, - onPointerDown: (PointerDownEvent details) { - widget.onPdfPagePointerDown(details); - if (kIsDesktop && !widget.isMobileWebView) { - final int now = DateTime.now().millisecondsSinceEpoch; - if (now - _lastTap <= 500) { - _consecutiveTaps++; - if (_consecutiveTaps == 2 && - details.buttons != kSecondaryButton) { - focusNode.requestFocus(); - canvasRenderBox!.handleDoubleTapDown(details); - } - if (_consecutiveTaps == 3 && - details.buttons != kSecondaryButton) { - focusNode.requestFocus(); - canvasRenderBox!.handleTripleTapDown(details); - } - } else { - _consecutiveTaps = 1; - } - _lastTap = now; - } - }, - onPointerMove: (PointerMoveEvent details) { - focusNode.requestFocus(); - widget.onPdfPagePointerMove(details); - if (widget.interactionMode == PdfInteractionMode.pan) { - _cursor = SystemMouseCursors.grabbing; - } - }, - onPointerUp: (PointerUpEvent details) { - widget.onPdfPagePointerUp(details); - if (widget.interactionMode == PdfInteractionMode.pan) { - _cursor = SystemMouseCursors.grab; - } - }, - child: RawKeyboardListener( - focusNode: focusNode, - onKey: (RawKeyEvent event) { - final bool isPrimaryKeyPressed = - kIsMacOS ? event.isMetaPressed : event.isControlPressed; - if ((canvasRenderBox! - .getSelectionDetails() - .mouseSelectionEnabled || - canvasRenderBox! - .getSelectionDetails() - .selectionEnabled) && - isPrimaryKeyPressed && - event.logicalKey == LogicalKeyboardKey.keyC) { - Clipboard.setData(ClipboardData( - text: canvasRenderBox! - .getSelectionDetails() - .copiedText ?? - '')); - } - if (isPrimaryKeyPressed && - event.logicalKey == LogicalKeyboardKey.digit0) { - widget.pdfViewerController.zoomLevel = 1.0; + : _jumpOffset), + ); } - if (isPrimaryKeyPressed && - 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; + canvasRenderBox?.updateContextMenuPosition(); + }, + onPointerDown: (PointerDownEvent details) { + _numberOfActivePointers++; + widget.onPdfPagePointerDown(details); + if (kIsDesktop && !widget.isMobileWebView) { + final int now = DateTime.now().millisecondsSinceEpoch; + if (now - _lastTap <= 500) { + _consecutiveTaps++; + if (_consecutiveTaps == 2 && + details.buttons != kSecondaryButton) { + focusNode.requestFocus(); + canvasRenderBox?.handleDoubleTapDown(details); } - widget.pdfViewerController.zoomLevel = zoomLevel; - } - } - if (isPrimaryKeyPressed && - 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; + if (_consecutiveTaps == 3 && + details.buttons != kSecondaryButton) { + focusNode.requestFocus(); + canvasRenderBox?.handleTripleTapDown(details); } - widget.pdfViewerController.zoomLevel = zoomLevel; + } else { + _consecutiveTaps = 1; } + _lastTap = now; } - if (event.runtimeType.toString() == 'RawKeyDownEvent') { - if (event.logicalKey == LogicalKeyboardKey.home || - (kIsMacOS && - event.logicalKey == LogicalKeyboardKey.fn && - event.logicalKey == - LogicalKeyboardKey.arrowLeft)) { - widget.pdfViewerController.jumpToPage(1); - } else if (event.logicalKey == LogicalKeyboardKey.end || - (kIsMacOS && - event.logicalKey == LogicalKeyboardKey.fn && - event.logicalKey == - LogicalKeyboardKey.arrowRight)) { - 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) { - canvasRenderBox!.scroll(true, false); + }, + onPointerMove: (PointerMoveEvent details) { + if (_numberOfActivePointers > 1 && + details.delta != Offset.zero && + !_isZooming && + mounted) { + setState(() { + _isZooming = true; + }); } - if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - canvasRenderBox!.scroll(false, false); + focusNode.requestFocus(); + widget.onPdfPagePointerMove(details); + if (_interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grabbing; } }, - child: MouseRegion( - cursor: _cursor, - onHover: (PointerHoverEvent details) { + onPointerUp: (PointerUpEvent details) { + _numberOfActivePointers--; + if (_numberOfActivePointers <= 1 && mounted) { setState(() { - 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) { - if (isRotatedTo90or270) { - _cursor = SystemMouseCursors.verticalText; + _isZooming = false; + }); + } + + widget.onPdfPagePointerUp(details); + _onPageTapped(details.localPosition); + if (_interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; + } + if (widget.pdfViewerController.annotationMode == + PdfAnnotationMode.highlight || + widget.pdfViewerController.annotationMode == + PdfAnnotationMode.underline || + widget.pdfViewerController.annotationMode == + PdfAnnotationMode.strikethrough || + widget.pdfViewerController.annotationMode == + PdfAnnotationMode.squiggly) { + if (_consecutiveTaps > 1) { + Future.delayed( + const Duration(milliseconds: 300), + () { + _addTextMarkupAnnotation( + widget.pdfViewerController.annotationMode + .toString() + .split('.') + .last, + ); + }, + ); + } + } + }, + child: KeyboardListener( + focusNode: focusNode, + onKeyEvent: (KeyEvent event) { + final bool isPrimaryKeyPressed = + kIsMacOS + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed; + if (canvasRenderBox != null && + (canvasRenderBox! + .getSelectionDetails() + .mouseSelectionEnabled || + canvasRenderBox! + .getSelectionDetails() + .selectionEnabled) && + isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.keyC) { + Clipboard.setData( + ClipboardData( + text: + canvasRenderBox! + .getSelectionDetails() + .copiedText ?? + '', + ), + ); + } + if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.digit0) { + widget.pdfViewerController.zoomLevel = 1.0; + } + if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.minus) { + if (event is KeyDownEvent) { + double zoomLevel = + widget.pdfViewerController.zoomLevel; + if (zoomLevel > 1) { + zoomLevel = zoomLevel - 0.5; + } + widget.pdfViewerController.zoomLevel = zoomLevel; + } + } + if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.equal) { + if (event is KeyDownEvent) { + double zoomLevel = + widget.pdfViewerController.zoomLevel; + zoomLevel = zoomLevel + 0.5; + widget.pdfViewerController.zoomLevel = zoomLevel; + } + } + if (event is KeyDownEvent) { + if (event.logicalKey == LogicalKeyboardKey.home || + (kIsMacOS && + event.logicalKey == LogicalKeyboardKey.fn && + event.logicalKey == + LogicalKeyboardKey.arrowLeft)) { + widget.pdfViewerController.jumpToPage(1); + } else if (event.logicalKey == LogicalKeyboardKey.end || + (kIsMacOS && + event.logicalKey == LogicalKeyboardKey.fn && + event.logicalKey == + LogicalKeyboardKey.arrowRight)) { + widget.pdfViewerController.jumpToPage( + widget.pdfViewerController.pageCount, + ); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowRight) { + widget.pdfViewerController.nextPage(); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowLeft) { + widget.pdfViewerController.previousPage(); + } else if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.keyZ) { + widget.undoController.undo(); + } else if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.keyY) { + widget.undoController.redo(); + } else if (event.logicalKey == + LogicalKeyboardKey.escape) { + widget.onAnnotationSelectionChanged?.call(null); + } else if (event.logicalKey == + LogicalKeyboardKey.delete) { + if (widget.selectedAnnotation != null) { + widget.pdfViewerController.removeAnnotation( + widget.selectedAnnotation!, + ); + } + } + } + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + canvasRenderBox?.scroll(true, false); + } + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + canvasRenderBox?.scroll(false, false); + } + }, + child: MouseRegion( + cursor: _cursor, + onHover: (PointerHoverEvent details) { + setState(() { + if (canvasRenderBox != null && + widget.pdfPages.isNotEmpty) { + final Annotation? annotation = canvasRenderBox! + .findAnnotation( + details.localPosition, + widget.pageIndex + 1, + ); + if (_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 && + annotation == null) { + if (_isRotatedTo90or270) { + _cursor = SystemMouseCursors.verticalText; + } else { + _cursor = SystemMouseCursors.text; + } } else { - _cursor = SystemMouseCursors.text; + _cursor = SystemMouseCursors.basic; } } 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; + final bool isTOC = canvasRenderBox!.findTOC( + details.localPosition, + ); + if (isTOC) { + _cursor = SystemMouseCursors.click; + } else if (_cursor != SystemMouseCursors.grab) { + _cursor = SystemMouseCursors.grab; + } } } - } - }); - }, - child: canvasContainer, + }); + }, + child: canvasContainer, + ), ), ), - ), - ) - : RotatedBox( - quarterTurns: quarterTurns, - child: Listener( + ) + : RotatedBox( + quarterTurns: _quarterTurns, + child: Listener( onPointerDown: (PointerDownEvent details) { + _numberOfActivePointers++; widget.onPdfPagePointerDown(details); }, - child: canvasContainer)); - return Stack(children: [ - pdfPage, - canvas, - ]); + onPointerMove: (PointerMoveEvent details) { + if (_numberOfActivePointers > 1 && + details.delta != Offset.zero && + !_isZooming && + mounted) { + setState(() { + _isZooming = true; + }); + } + widget.onPdfPagePointerMove(details); + }, + onPointerUp: (PointerUpEvent details) { + _numberOfActivePointers--; + if (_numberOfActivePointers <= 1 && mounted) { + setState(() { + _isZooming = false; + }); + } + widget.onPdfPagePointerUp(details); + _onPageTapped(details.localPosition); + }, + child: + widget.isAndroidTV + ? KeyboardListener( + focusNode: focusNode, + onKeyEvent: (KeyEvent event) { + if (event is KeyDownEvent) { + if (event.logicalKey == + LogicalKeyboardKey.arrowRight) { + widget.pdfViewerController.nextPage(); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowLeft) { + widget.pdfViewerController.previousPage(); + } + } + if (event.logicalKey == + LogicalKeyboardKey.arrowUp) { + canvasRenderBox?.scroll(true, false); + } + if (event.logicalKey == + LogicalKeyboardKey.arrowDown) { + canvasRenderBox?.scroll(false, false); + } + }, + child: canvasContainer, + ) + : canvasContainer, + ), + ); + + Widget? formFieldContainer; + if (_formFields.isNotEmpty) { + formFieldContainer = RotatedBox( + quarterTurns: _quarterTurns, + child: SizedBox( + height: _isRotatedTo90or270 ? widget.width : widget.height, + width: _isRotatedTo90or270 ? widget.height : widget.width, + child: FormFieldContainer( + formFields: _formFields, + onTap: _onPageTapped, + heightPercentage: _heightPercentage, + pdfViewerController: widget.pdfViewerController, + canShowSignaturePadDialog: widget.canShowSignaturePadDialog, + ), + ), + ); + } + + final Widget annotationContainer = RotatedBox( + quarterTurns: _quarterTurns, + child: Container( + height: _isRotatedTo90or270 ? widget.width : widget.height, + width: _isRotatedTo90or270 ? widget.height : widget.width, + alignment: Alignment.topCenter, + child: AnnotationContainer( + annotations: widget.annotations.where( + (Annotation annotation) => + annotation.pageNumber == widget.pageIndex + 1, + ), + annotationSettings: widget.pdfViewerController.annotationSettings, + selectedAnnotation: widget.selectedAnnotation, + onAnnotationSelectionChanged: widget.onAnnotationSelectionChanged, + isZooming: _isZooming, + heightPercentage: _heightPercentage, + zoomLevel: widget.pdfViewerController.zoomLevel, + pageNumber: widget.pageIndex + 1, + pageSize: _originalPageSize, + onStickyNoteDoubleTapped: (Annotation annotation) { + if (annotation is StickyNoteAnnotation) { + widget.onStickyNoteDoubleTapped?.call(annotation); + } + }, + onTap: _onPageTapped, + ), + ), + ); + + return Stack( + children: [ + pdfPage, + canvas, + if (formFieldContainer != null) formFieldContainer, + annotationContainer, + ], + ); + } + bool isVisible; + if (widget.pdfViewerController.pageNumber == widget.pageIndex + 1 || + widget.pdfViewerController.pageNumber == widget.pageIndex || + widget.pdfViewerController.pageNumber == widget.pageIndex + 2) { + isVisible = true; } else { - final BorderSide borderSide = BorderSide( - width: widget.isSinglePageView ? pageSpacing / 2 : pageSpacing, - color: _pdfViewerThemeData!.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == Brightness.light - ? const Color(0xFFD6D6D6) - : const Color(0xFF303030))); - final Widget child = Container( - height: widget.height + heightSpacing, - width: widget.width + widthSpacing, - color: Colors.white, - foregroundDecoration: BoxDecoration( - border: widget.isSinglePageView - ? Border(left: borderSide, right: borderSide) - : widget.scrollDirection == PdfScrollDirection.horizontal - ? Border(right: borderSide) - : Border(bottom: borderSide), + isVisible = false; + } + if (!widget.canShowPageLoadingIndicator) { + isVisible = widget.canShowPageLoadingIndicator; + } + final BorderSide borderSide = BorderSide( + width: widget.isSinglePageView ? pageSpacing / 2 : pageSpacing, + color: + _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? + (Theme.of(context).colorScheme.brightness == Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030)), + ); + final Widget child = Container( + height: widget.height + heightSpacing, + width: widget.width + widthSpacing, + color: Colors.white, + foregroundDecoration: BoxDecoration( + border: + widget.isSinglePageView + ? Border(left: borderSide, right: borderSide) + : widget.scrollDirection == PdfScrollDirection.horizontal + ? widget.textDirection == TextDirection.rtl + ? Border(left: borderSide) + : Border(right: borderSide) + : Border(bottom: borderSide), + ), + child: Center( + child: Visibility( + visible: isVisible, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + _pdfViewerThemeData!.progressBarColor ?? + _effectiveThemeData!.progressBarColor ?? + (Theme.of(context).colorScheme.primary), + ), + backgroundColor: + _pdfViewerThemeData!.progressBarColor != null + ? _pdfViewerThemeData!.progressBarColor!.withValues( + alpha: 0.2, + ) + : _effectiveThemeData!.progressBarColor != null + ? _effectiveThemeData!.progressBarColor!.withValues( + alpha: 0.2, + ) + : (Theme.of( + context, + ).colorScheme.primary.withValues(alpha: 0.2)), + ), ), + ), + ); + return child; + } + + void _calculateHeightPercentage() { + final PdfPageRotateAngle rotatedAngle = + widget.pdfDocument!.pages[widget.pageIndex].rotation; + _quarterTurns = 0; + if (rotatedAngle == PdfPageRotateAngle.rotateAngle90) { + _quarterTurns = 1; + } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle180) { + _quarterTurns = 2; + } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle270) { + _quarterTurns = 3; + } + _isRotatedTo90or270 = + rotatedAngle == PdfPageRotateAngle.rotateAngle90 || + rotatedAngle == PdfPageRotateAngle.rotateAngle270; + _originalPageSize = widget.pdfDocument!.pages[widget.pageIndex].size; + _heightPercentage = + (_isRotatedTo90or270 + ? _originalPageSize.width + : _originalPageSize.height) / + widget.pdfPages[widget.pageIndex + 1]!.pageSize.height; + } + + Future _getImage(Size viewportSize, double zoomLevel) async { + _calculateHeightPercentage(); + final Size originalPageSize = + widget.pdfDocument!.pages[widget.pageIndex].size; + final double ratio = 1 / _heightPercentage; + final double imageFactor = ratio * zoomLevel; + if (widget.pdfDocument != null && _previousImageFactor != imageFactor) { + _previousImageFactor = imageFactor; + if (ratio < 0.5 || + zoomLevel > 1.75 || + (!kIsDesktop && imageFactor > 2) || + (kIsDesktop && imageFactor > 4)) { + _isTile = true; + if (_pdfPage != null) { + return; + } + + // This condition checks if the original image size in bytes (width * height * 4) is + // less than or equal to 5 MB (5 * 1024 * 1024 bytes). The factor of 4 accounts for + // the RGBA channels (4 bytes per pixel). If the image size is within this limit, + // we use the original dimensions; otherwise, we adjust to the widget's dimensions. + if (originalPageSize.width * originalPageSize.height * 4 <= + 5 * 1024 * 1024) { + _imageWidth = originalPageSize.width.toInt(); + _imageHeight = originalPageSize.height.toInt(); + } else { + _imageWidth = widget.width.toInt(); + _imageHeight = widget.height.toInt(); + } + } else { + _tileImageCache = null; + _isTile = false; + _imageWidth = (widget.width * zoomLevel * _dpr).toInt(); + _imageHeight = (widget.height * zoomLevel * _dpr).toInt(); + } + + try { + _pageImageOperation = CancelableOperation.fromFuture( + PdfViewerPlatform.instance.getPage( + widget.pageIndex + 1, + _imageWidth, + _imageHeight, + widget.documentID, + ), + ); + await _pageImageOperation?.value.then((Uint8List? pageImage) { + if (pageImage != null) { + _createImage(pageImage, _imageWidth, _imageHeight).then(( + ui.Image image, + ) { + if (mounted) { + setState(() { + _pdfPage = RawImage(image: image, fit: BoxFit.fill); + }); + } + }); + } + }); + } catch (_) { + } finally { + _pageImageOperation?.cancel(); + _pageImageOperation = null; + } + } + } + + /// Method to rebuild the widget + void rebuild() { + if (mounted) { + setState(() {}); + } + } + + void _addTextMarkupAnnotation(String type) { + final List? selectedLines = + canvasRenderBox!.getSelectedTextLines(); + if (selectedLines != null && selectedLines.isNotEmpty) { + Annotation? annotation; + if (type == 'highlight') { + annotation = HighlightAnnotation(textBoundsCollection: selectedLines); + } else if (type == 'underline') { + annotation = UnderlineAnnotation(textBoundsCollection: selectedLines); + } else if (type == 'strikethrough') { + annotation = StrikethroughAnnotation( + textBoundsCollection: selectedLines, + ); + } else if (type == 'squiggly') { + annotation = SquigglyAnnotation(textBoundsCollection: selectedLines); + } + if (annotation != null) { + widget.pdfViewerController.addAnnotation(annotation); + } + } + } + + /// Create image from the given raw pixels + Future _createImage(Uint8List pixels, int width, int height) { + final Completer comp = Completer(); + ui.decodeImageFromPixels( + pixels, + width, + height, + ui.PixelFormat.rgba8888, + (ui.Image image) => comp.complete(image), + ); + + return comp.future; + } + + void _onPageTapped(Offset position) { + // Tranform the page coordinates from calculated page size to original page size. + final double x = position.dx * _heightPercentage; + final double y = position.dy * _heightPercentage; + final Offset pagePosition = Offset(x, y); + widget.onTap(pagePosition, widget.pageIndex); + } + + /// Get the tile image + Future getTileImage( + TransformationController transformationController, + Size viewportSize, + double zoomLevel, + ) async { + _getImage(viewportSize, zoomLevel); + if (!_isTile) { + return; + } + final Offset offset = + transformationController.toScene(Offset.zero) * zoomLevel; + final double x = offset.dx * _heightPercentage; + final double y = offset.dy * _heightPercentage; + + final Rect viewportRect = Rect.fromLTWH( + x, + y, + viewportSize.width * _heightPercentage, + viewportSize.height * _heightPercentage, + ); + + Rect pageBounds = Rect.zero; + if (!widget.isSinglePageView) { + if (widget.scrollDirection == PdfScrollDirection.vertical) { + pageBounds = Rect.fromLTWH( + 0, + widget.pdfPages[widget.pageIndex + 1]!.pageOffset * + zoomLevel * + _heightPercentage, + widget.width * zoomLevel * _heightPercentage, + widget.height * zoomLevel * _heightPercentage, + ); + } else { + pageBounds = Rect.fromLTWH( + widget.pdfPages[widget.pageIndex + 1]!.pageOffset * + zoomLevel * + _heightPercentage, + 0, + widget.width * zoomLevel * _heightPercentage, + widget.height * zoomLevel * _heightPercentage, + ); + } + } else { + pageBounds = Rect.fromLTWH( + 0, + 0, + widget.width * zoomLevel * _heightPercentage, + widget.height * zoomLevel * _heightPercentage, ); - return child; } + + Rect exposed = pageBounds.intersect(viewportRect); + if (!exposed.isEmpty && !exposed.hasNaN) { + if (!widget.isSinglePageView) { + if (widget.scrollDirection == PdfScrollDirection.vertical) { + exposed = exposed.translate( + 0, + -(widget.pdfPages[widget.pageIndex + 1]!.pageOffset * + zoomLevel * + _heightPercentage), + ); + } else { + exposed = exposed.translate( + -(widget.pdfPages[widget.pageIndex + 1]!.pageOffset * + zoomLevel * + _heightPercentage), + 0, + ); + } + } + + double ratio = 1 / _heightPercentage; + ratio = ratio < 1 && kIsDesktop ? 1 : ratio; + if (zoomLevel == transformationController.value[0]) { + try { + final Size tileImageSize = Size( + exposed.width * _dpr * ratio, + exposed.height * _dpr * ratio, + ); + _tileImageOperation = CancelableOperation.fromFuture( + PdfViewerPlatform.instance.getTileImage( + widget.pageIndex + 1, + zoomLevel * _dpr * ratio, + exposed.left / zoomLevel, + exposed.top / zoomLevel, + tileImageSize.width, + tileImageSize.height, + widget.documentID, + ), + ); + final Future imageFuture = _tileImageOperation!.value; + await imageFuture.then((Uint8List? tileImage) { + if (tileImage != null && + zoomLevel == transformationController.value[0]) { + _createImage( + tileImage, + tileImageSize.width.toInt(), + tileImageSize.height.toInt(), + ).then((ui.Image image) { + if (mounted) { + setState(() { + _tileImageCache = TileImage( + widget.pageIndex, + tileImage, + Rect.fromLTWH( + exposed.left / zoomLevel, + exposed.top / zoomLevel, + exposed.width / zoomLevel, + exposed.height / zoomLevel, + ), + tileImageSize, + ); + _tileImage = RawImage( + image: image, + width: tileImageSize.width, + height: tileImageSize.height, + fit: BoxFit.fill, + ); + }); + } + }); + } + }); + } catch (_) { + } finally { + _tileImageOperation?.cancel(); + _tileImageOperation = null; + } + } + } + } + + /// Get the page image + void getPageImage(Size viewportSize, double zoomLevel) { + _pageTimer ??= Timer(Durations.short2, () { + if (widget.pdfPages.isEmpty) { + return; + } + _getImage(viewportSize, zoomLevel).then((_) { + _pageTimer = null; + _pageImageOperation = null; + }); + }); + } + + /// Clear the page image + void clearPageImage({bool dispose = false}) { + _pdfPage?.image?.dispose(); + _tileImage?.image?.dispose(); + if (!dispose) { + if (mounted) { + setState(() { + _pdfPage = null; + _tileImage = null; + }); + } + } else { + _pdfPage = null; + _tileImage = null; + } + _tileImageOperation?.cancel(); + _pageImageOperation?.cancel(); + _tileImageOperation = null; + _pageImageOperation = null; + _tileImageCache = null; + _pageTimer?.cancel(); + _pageTimer = null; + _isTile = false; + _previousImageFactor = -1.0; } } @@ -507,8 +1204,26 @@ class PdfPageInfo { PdfPageInfo(this.pageOffset, this.pageSize); /// Page start offset - final double pageOffset; + double pageOffset; /// Size of page in the viewport - final Size pageSize; + Size pageSize; +} + +/// Information about the tile image +class TileImage { + /// Constructor of TileImage + TileImage(this.pageIndex, this.image, this.visibleRect, this.imageSize); + + /// Page index + final int pageIndex; + + /// Tile image + final Uint8List image; + + /// Visible rect + final Rect visibleRect; + + /// Image size + Size imageSize; } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart index 3155c1594..873f2ee72 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart @@ -3,10 +3,10 @@ import 'dart:ui'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_page_view.dart'; +import '../../pdfviewer.dart'; import '../common/pdfviewer_helper.dart'; +import 'pdf_page_view.dart'; import 'scroll_head_overlay.dart'; /// This callback triggered whenever offset is changed in PDF. @@ -17,30 +17,37 @@ typedef OffsetChangedCallback = void Function(Offset offset); class PdfScrollable extends StatefulWidget { /// Constructor for PdfScrollable. const PdfScrollable( - this.canShowPaginationDialog, - this.canShowScrollStatus, - this.canShowScrollHead, - this.pdfViewerController, - this.isMobileWebView, - this.pdfDimension, - this.totalImageSize, - this.viewportDimension, - this.onPdfOffsetChanged, - this.isPanEnabled, - this.maxScale, - this.minScale, - this.enableDoubleTapZooming, - this.interactionMode, - this.maxPdfPageWidth, - this.scaleEnabled, - this.maxScrollExtent, - this.pdfPages, - this.scrollDirection, - this.isBookmarkViewOpen, - this.child, - {Key? key, - this.onDoubleTap}) - : super(key: key); + this.transformationController, + this.canShowPaginationDialog, + this.canShowScrollStatus, + this.canShowScrollHead, + this.pdfViewerController, + this.isMobileWebView, + this.pdfDimension, + this.totalImageSize, + this.viewportDimension, + this.visibleViewportDimension, + this.onPdfOffsetChanged, + this.isPanEnabled, + this.maxScale, + this.minScale, + this.enableDoubleTapZooming, + this.interactionMode, + this.maxPdfPageWidth, + this.scaleEnabled, + this.maxScrollExtent, + this.pdfPages, + this.scrollDirection, + this.isBookmarkViewOpen, + this.textDirection, + this.child, + this.initiateTileRendering, { + Key? key, + this.onDoubleTap, + }) : super(key: key); + + /// Transformation controller of PdfViewer. + final TransformationController transformationController; /// Indicates whether page navigation dialog must be shown or not. final bool canShowPaginationDialog; @@ -81,6 +88,9 @@ class PdfScrollable extends StatefulWidget { /// Entire view port dimension. final Size viewportDimension; + /// Entire view port dimension without keyboard height. + final Size? visibleViewportDimension; + /// Represents the maximum page width. final double maxPdfPageWidth; @@ -109,6 +119,12 @@ class PdfScrollable extends StatefulWidget { /// opened or not. final bool isBookmarkViewOpen; + ///A direction of text flow. + final TextDirection textDirection; + + /// Callback to initiate tile rendering. + final VoidCallback? initiateTileRendering; + @override PdfScrollableState createState() => PdfScrollableState(); } @@ -117,9 +133,14 @@ class PdfScrollable extends StatefulWidget { class PdfScrollableState extends State { late TransformationController _transformationController; double? _currentScale; + double _previousScale = 1; bool? _setZoomLevel; bool _isOverFlowed = false; Timer? _scrollTimer; + Size? _previousVisibleViewportDimension; + + /// Indicates whether zoom value is changed. + bool isZoomChanged = false; /// Current Offset. late Offset currentOffset; @@ -144,17 +165,18 @@ class PdfScrollableState extends State { @override void initState() { super.initState(); - _transformationController = TransformationController(); + _transformationController = widget.transformationController; currentOffset = Offset.zero; currentZoomLevel = 1; if (widget.pdfViewerController.zoomLevel > 1) { - scaleTo(widget.pdfViewerController.zoomLevel, isZoomed: false); + Future.delayed(Duration.zero, () async { + scaleTo(widget.pdfViewerController.zoomLevel, isZoomed: false); + }); } } @override void dispose() { - _transformationController.dispose(); _scrollTimer?.cancel(); _scrollTimer = null; super.dispose(); @@ -169,6 +191,18 @@ class PdfScrollableState extends State { Widget build(BuildContext context) { currentOffset = _transformationController.toScene(Offset.zero); currentZoomLevel = _transformationController.value.getMaxScaleOnAxis(); + if (widget.visibleViewportDimension == null && + _previousVisibleViewportDimension != null && + currentOffset.dy.round() > + (widget.pdfDimension.height - + widget.viewportDimension.height / currentZoomLevel) + .round()) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + // Reset the offset when the keyboard is closed. + _handlePdfOffsetChanged(currentOffset); + }); + } + _previousVisibleViewportDimension = widget.visibleViewportDimension; return ScrollHeadOverlay( widget.canShowPaginationDialog, widget.canShowScrollStatus, @@ -189,6 +223,7 @@ class PdfScrollableState extends State { widget.pdfPages, widget.scrollDirection, widget.isBookmarkViewOpen, + widget.textDirection, widget.child, isPanEnabled: widget.isPanEnabled, onInteractionStart: _handleInteractionStart, @@ -196,12 +231,14 @@ class PdfScrollableState extends State { onInteractionEnd: _handleInteractionEnd, transformationController: _transformationController, onPdfOffsetChanged: _handlePdfOffsetChanged, + initiateTileRendering: widget.initiateTileRendering, key: scrollHeadStateKey, ); } /// Handles interaction start and updates the UI void _handleInteractionStart(ScaleStartDetails details) { + _previousScale = _transformationController.value.getMaxScaleOnAxis(); if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView) || (kIsDesktop && widget.scaleEnabled)) { @@ -209,6 +246,7 @@ class PdfScrollableState extends State { } paddingWidthScale = 0; paddingHeightScale = 0; + isZoomChanged = false; } void _onDoubleTapZoomInvoked(double scale) { @@ -221,6 +259,7 @@ class PdfScrollableState extends State { currentOffset = _transformationController.toScene(Offset.zero); _currentScale = _transformationController.value.getMaxScaleOnAxis(); widget.onPdfOffsetChanged!.call(currentOffset); + setState(() {}); if (details.scale <= 1) { if (kIsDesktop && !widget.isMobileWebView) { if (widget.viewportDimension.width.round() == @@ -253,9 +292,19 @@ class PdfScrollableState extends State { void _handleInteractionEnd(ScaleEndDetails details) { paddingWidthScale = 0; paddingHeightScale = 0; - final double totalPdfPageWidth = widget - .pdfPages[widget.pdfViewerController.pageCount]!.pageOffset + - widget.pdfPages[widget.pdfViewerController.pageCount]!.pageSize.width; + final double totalPdfPageWidth = + (widget.textDirection == TextDirection.rtl && + widget.scrollDirection == PdfScrollDirection.horizontal) + // In RTL direction, the last page is rendered at Offset.zero and the first page is rendered at the end. + ? widget.pdfPages[1]!.pageOffset + + widget.pdfPages[1]!.pageSize.width + : widget + .pdfPages[widget.pdfViewerController.pageCount]! + .pageOffset + + widget + .pdfPages[widget.pdfViewerController.pageCount]! + .pageSize + .width; if (_currentScale != widget.pdfViewerController.zoomLevel && _currentScale != null && _currentScale != 0.0 && @@ -264,23 +313,15 @@ class PdfScrollableState extends State { (kIsDesktop && widget.scaleEnabled))) { widget.pdfViewerController.zoomLevel = _currentScale!; } - if ((kIsDesktop && - !widget.isMobileWebView && - widget.scrollDirection == PdfScrollDirection.vertical && - widget.maxPdfPageWidth * widget.pdfViewerController.zoomLevel < - widget.viewportDimension.width) || - (widget.scrollDirection == PdfScrollDirection.horizontal && - widget.viewportDimension.width.round() > - (totalPdfPageWidth * widget.pdfViewerController.zoomLevel) - .round())) { + if (widget.scrollDirection == PdfScrollDirection.horizontal && + widget.viewportDimension.width.round() > + (totalPdfPageWidth * widget.pdfViewerController.zoomLevel) + .round()) { _transformationController.value.translate(currentOffset.dx); _isOverFlowed = false; } else { - if ((kIsDesktop && - !widget.isMobileWebView && - widget.scrollDirection == PdfScrollDirection.vertical) || - (widget.scrollDirection == PdfScrollDirection.horizontal && - totalPdfPageWidth < widget.viewportDimension.width)) { + if (widget.scrollDirection == PdfScrollDirection.horizontal && + totalPdfPageWidth < widget.viewportDimension.width) { /// Invoked when pdf pages width greater viewport width if (_isOverFlowed == false) { _transformationController.value.translate(currentOffset.dx); @@ -288,6 +329,13 @@ class PdfScrollableState extends State { } } } + if (_previousScale != _transformationController.value.getMaxScaleOnAxis()) { + setState(() { + isZoomChanged = true; + }); + } + + widget.initiateTileRendering?.call(); } ///Triggers when scrolling performed by touch pad. @@ -295,15 +343,14 @@ class PdfScrollableState extends State { isScrolled = true; if (event is PointerScrollEvent) { jumpTo( - xOffset: currentOffset.dx + event.scrollDelta.dx, - yOffset: currentOffset.dy + event.scrollDelta.dy); + xOffset: currentOffset.dx + event.scrollDelta.dx, + yOffset: currentOffset.dy + event.scrollDelta.dy, + ); } - _scrollTimer = Timer( - const Duration(milliseconds: 2000), - () { - isScrolled = false; - }, - ); + _scrollTimer?.cancel(); + _scrollTimer = Timer(const Duration(milliseconds: 100), () { + isScrolled = false; + }); } ///Triggers when scrolling performed by touch. @@ -318,6 +365,7 @@ class PdfScrollableState extends State { xOffset ??= currentOffset.dx; yOffset ??= currentOffset.dy; _handlePdfOffsetChanged(Offset(xOffset, yOffset)); + widget.initiateTileRendering?.call(); } /// Handles PDF offset changed and updates the matrix translation based on it. @@ -335,18 +383,25 @@ class PdfScrollableState extends State { widget.pdfViewerController.zoomLevel) { offset = Offset(offset.dx, 0); } - final double widthFactor = widget.pdfDimension.width - + final double widthFactor = + widget.pdfDimension.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); offset = Offset( - offset.dx.clamp( - _setZoomLevel == true ? -widthFactor : 0, widthFactor.abs()), - offset.dy.clamp( - 0, - (widget.pdfDimension.height - - (widget.viewportDimension.height / - widget.pdfViewerController.zoomLevel)) - .abs())); + offset.dx.clamp( + (_setZoomLevel ?? false) ? -widthFactor : 0, + widthFactor.abs(), + ), + offset.dy.clamp( + 0, + (widget.pdfDimension.height - + ((widget.visibleViewportDimension != null + ? widget.visibleViewportDimension!.height + : widget.viewportDimension.height) / + widget.pdfViewerController.zoomLevel)) + .abs(), + ), + ); _setZoomLevel = false; if (kIsDesktop && !widget.isMobileWebView) { if (widget.viewportDimension.width > @@ -357,44 +412,60 @@ class PdfScrollableState extends State { } } - final Offset previousOffset = - _transformationController.toScene(Offset.zero); + final Offset previousOffset = _transformationController.toScene( + Offset.zero, + ); _transformationController.value.translate( - previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); - widget.onPdfOffsetChanged! - .call(_transformationController.toScene(Offset.zero)); + previousOffset.dx - offset.dx, + previousOffset.dy - offset.dy, + ); + widget.onPdfOffsetChanged!.call( + _transformationController.toScene(Offset.zero), + ); + setState(() {}); } } /// Set the pixel in the matrix. void _setPixel(Offset offset) { - final double widthFactor = widget.pdfDimension.width - + if (!mounted) { + return; + } + final double widthFactor = + widget.pdfDimension.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); offset = Offset( - offset.dx.clamp(-widthFactor, widthFactor.abs()), - offset.dy.clamp( - 0, - (widget.pdfDimension.height - - (widget.viewportDimension.height / - widget.pdfViewerController.zoomLevel)) - .abs())); + offset.dx.clamp(-widthFactor, widthFactor.abs()), + offset.dy.clamp( + 0, + (widget.pdfDimension.height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + .abs(), + ), + ); - final Offset previousOffset = - _transformationController.toScene(Offset.zero); + final Offset previousOffset = _transformationController.toScene( + Offset.zero, + ); _transformationController.value.translate( - previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); - widget.onPdfOffsetChanged! - .call(_transformationController.toScene(Offset.zero)); + previousOffset.dx - offset.dx, + previousOffset.dy - offset.dy, + ); + widget.onPdfOffsetChanged!.call( + _transformationController.toScene(Offset.zero), + ); + setState(() {}); } /// Force the jump without restriction void forcePixels(Offset offset, {bool? isZoomInitiated}) { // add post frame restricted ,if the set scaling is progress - if (isZoomInitiated == true) { + if (isZoomInitiated ?? false) { _setPixel(offset); } else { // add post frame which is jumped once the layout is changed. - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _setPixel(offset); }); } @@ -407,8 +478,9 @@ class PdfScrollableState extends State { previousZoomLevel = currentZoomLevel; _setZoomLevel = true; final double zoomChangeFactor = zoomLevel / currentZoomLevel; - final Offset previousOffset = - _transformationController.toScene(Offset.zero); + final Offset previousOffset = _transformationController.toScene( + Offset.zero, + ); _transformationController.value.scale(zoomChangeFactor, zoomChangeFactor); if (kIsDesktop && !widget.isMobileWebView && @@ -427,17 +499,37 @@ class PdfScrollableState extends State { /// Retrieves the page number based on the offset of the page. int getPageNumber(double offset) { int pageNumber = 0; - for (int i = 1; i <= widget.pdfViewerController.pageCount; i++) { - if (i == widget.pdfViewerController.pageCount || - offset.round() >= widget.maxScrollExtent.round()) { - pageNumber = widget.pdfViewerController.pageCount; - break; - } else if (offset.round() >= widget.pdfPages[i]!.pageOffset.round() && - offset.round() < widget.pdfPages[i + 1]!.pageOffset.round()) { - pageNumber = i; - break; - } else { - continue; + if (widget.textDirection == TextDirection.rtl && + widget.scrollDirection == PdfScrollDirection.horizontal) { + for (int i = 1; i <= widget.pdfViewerController.pageCount; i++) { + if (i == widget.pdfViewerController.pageCount || offset.round() <= 0) { + pageNumber = widget.pdfViewerController.pageCount; + break; + } else if ((offset.round() + widget.viewportDimension.width.round()) <= + (widget.pdfPages[i]!.pageOffset.round() + + widget.pdfPages[i]!.pageSize.width.round()) && + (offset.round() + widget.viewportDimension.width.round()) > + (widget.pdfPages[i + 1]!.pageOffset.round() + + widget.pdfPages[i + 1]!.pageSize.width.round())) { + pageNumber = i; + break; + } else { + continue; + } + } + } else { + for (int i = 1; i <= widget.pdfViewerController.pageCount; i++) { + if (i == widget.pdfViewerController.pageCount || + offset.round() >= widget.maxScrollExtent.round()) { + pageNumber = widget.pdfViewerController.pageCount; + break; + } else if (offset.round() >= widget.pdfPages[i]!.pageOffset.round() && + offset.round() < widget.pdfPages[i + 1]!.pageOffset.round()) { + pageNumber = i; + break; + } else { + continue; + } } } 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 6b97d4100..52b21a6cd 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 @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../form_fields/pdf_form_field.dart'; + /// Holds the details for the [SfPdfViewer.onPageChanged] callback, /// such as [newPageNumber], [oldPageNumber], [isFirstPage] and [isLastPage]. class PdfPageChangedDetails { @@ -110,7 +112,9 @@ class PdfDocumentLoadFailedDetails { class PdfTextSelectionChangedDetails { /// PdfTextSelectionChangedDetails( - String? selectedText, Rect? globalSelectedRegion) { + String? selectedText, + Rect? globalSelectedRegion, + ) { _selectedText = selectedText; _globalSelectedRegion = globalSelectedRegion; } @@ -129,3 +133,82 @@ class PdfTextSelectionChangedDetails { return _globalSelectedRegion; } } + +/// Holds the details for the [SfPdfViewer.onHyperlinkClicked] callback, +/// such as [uri]. +class PdfHyperlinkClickedDetails { + /// Creates details for [SfPdfViewer.onHyperlinkClicked] callback. + PdfHyperlinkClickedDetails(String uri) { + _uri = uri; + } + + late String _uri; + + /// Holds the uri of the selected text + String get uri { + return _uri; + } +} + +/// Holds the details for the [SfPdfViewer.onTap] callback, +/// such as [pageNumber], [position] and [pagePosition]. +class PdfGestureDetails { + /// Creates details for [SfPdfViewer.onTap] callback. + PdfGestureDetails(this._pageNumber, this._position, this._pagePosition); + final int _pageNumber; + final Offset _position; + final Offset _pagePosition; + + /// Returns the number of the page on which the tap occurred. + /// + /// The value of this property ranges from 1 to the total page count of the PDF document. + /// + /// Note: The value of this property will be -1 if the tap occurred outside any PDF page bounds. + int get pageNumber => _pageNumber; + + /// Returns the tapped position on the [SfPdfViewer]. The coordinate space starts at the top-left of the [SfPdfViewer] widget. + Offset get position => _position; + + /// Returns the tapped position in the PDF page coordinates which have their origin at the top left corner of the page. + /// + /// Note: The tapped page is indicated by the [pageNumber] property. The value of this property will be (-1,-1) if the tap occurred outside page bounds. + Offset get pagePosition => _pagePosition; +} + +/// Holds the details for the [SfPdfViewer.onFormFieldValueChanged] callback, +/// such as [formField], [oldValue] and [newValue]. +class PdfFormFieldValueChangedDetails { + /// Creates details for [SfPdfViewer.onFormFieldValueChanged] callback. + PdfFormFieldValueChangedDetails( + this._formField, + this._oldValue, + this._newValue, + ); + final PdfFormField _formField; + final Object? _oldValue; + final Object? _newValue; + + /// Returns the form field object that has value changed. + PdfFormField get formField => _formField; + + /// The old value of the form field. + Object? get oldValue => _oldValue; + + /// The new value of the form field. + Object? get newValue => _newValue; +} + +/// Holds the details for the [SfPdfViewer.onFormFieldFocusChange] callback, +/// such as [formField] and [hasFocus]. +class PdfFormFieldFocusChangeDetails { + /// Creates details for [SfPdfViewer.onFormFieldFocusChange] callback. + PdfFormFieldFocusChangeDetails(this._formField, this._hasFocus); + final PdfFormField _formField; + final bool _hasFocus; + + /// Returns the form field object that has focus changes. + PdfFormField get formField => _formField; + + /// Indicates whether the form field has focus or not. + bool get hasFocus => _hasFocus; +} 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 3d9f71abd..d0d461273 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart @@ -2,15 +2,20 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' as intl; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; 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:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/single_page_view.dart'; +import 'package:url_launcher/url_launcher.dart'; -/// Instance of TextSelectionHelper. -TextSelectionHelper _textSelectionHelper = TextSelectionHelper(); +import '../../pdfviewer.dart'; +import '../annotation/annotation.dart'; +import '../annotation/text_markup.dart'; +import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; +import 'pdf_page_view.dart'; +import 'pdf_scrollable.dart'; +import 'single_page_view.dart'; /// [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 { @@ -26,7 +31,9 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { this.pdfViewerController, this.enableDocumentLinkNavigation, this.enableTextSelection, + this.textSelectionHelper, this.onTextSelectionChanged, + this.onHyperlinkClicked, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, this.textCollection, @@ -39,6 +46,10 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { this.viewportGlobalRect, this.scrollDirection, this.isSinglePageView, + this.textDirection, + this.canShowHyperlinkDialog, + this.enableHyperlinkNavigation, + this.onAnnotationSelectionChanged, ) : super(key: key); /// Height of page @@ -65,6 +76,9 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { /// If false,text selection is disabled.Default value is true. final bool enableTextSelection; + /// Instance of [TextSelectionHelper] + final TextSelectionHelper textSelectionHelper; + /// Triggers when text selection dragging started. final VoidCallback onTextSelectionDragStarted; @@ -74,6 +88,9 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { /// Triggers when text selection is changed. final PdfTextSelectionChangedCallback? onTextSelectionChanged; + /// Triggers when Hyperlink is clicked. + final PdfHyperlinkClickedCallback? onHyperlinkClicked; + /// Current instance search text highlight color. final Color currentSearchTextHighlightColor; @@ -107,32 +124,51 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { /// Determines layout option in PdfViewer. final bool isSinglePageView; + ///A direction of text flow. + final TextDirection textDirection; + + /// Indicates whether hyperlink dialog must be shown or not. + final bool canShowHyperlinkDialog; + + /// If true, hyperlink navigation is enabled. + final bool enableHyperlinkNavigation; + + /// Triggers when annotation is selected or deselected. + final void Function(Annotation?)? onAnnotationSelectionChanged; + @override RenderObject createRenderObject(BuildContext context) { return CanvasRenderBox( - height, - width, - context, - pdfDocument, - pdfPages, - pageIndex, - interactionMode, - pdfViewerController, - enableDocumentLinkNavigation, - enableTextSelection, - onTextSelectionChanged, - onTextSelectionDragStarted, - onTextSelectionDragEnded, - textCollection, - currentSearchTextHighlightColor, - otherSearchTextHighlightColor, - isMobileWebView, - pdfTextSearchResult, - pdfScrollableStateKey, - singlePageViewStateKey, - viewportGlobalRect, - scrollDirection, - isSinglePageView); + height, + width, + context, + pdfDocument, + pdfPages, + pageIndex, + interactionMode, + pdfViewerController, + enableDocumentLinkNavigation, + enableTextSelection, + textSelectionHelper, + onTextSelectionChanged, + onHyperlinkClicked, + onTextSelectionDragStarted, + onTextSelectionDragEnded, + textCollection, + currentSearchTextHighlightColor, + otherSearchTextHighlightColor, + isMobileWebView, + pdfTextSearchResult, + pdfScrollableStateKey, + singlePageViewStateKey, + viewportGlobalRect, + scrollDirection, + isSinglePageView, + textDirection, + canShowHyperlinkDialog, + enableHyperlinkNavigation, + onAnnotationSelectionChanged, + ); } @override @@ -141,17 +177,22 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { ..height = height ..width = width ..onTextSelectionChanged = onTextSelectionChanged + ..onHyperlinkClicked = onHyperlinkClicked ..pageIndex = pageIndex ..textCollection = textCollection ..interactionMode = interactionMode + ..isMobileWebView = isMobileWebView ..enableTextSelection = enableTextSelection ..enableDocumentLinkNavigation = enableDocumentLinkNavigation + ..enableHyperlinkNavigation = enableHyperlinkNavigation + ..canShowHyperlinkDialog = canShowHyperlinkDialog ..currentSearchTextHighlightColor = currentSearchTextHighlightColor ..otherSearchTextHighlightColor = otherSearchTextHighlightColor ..scrollDirection = scrollDirection ..isSinglePageView = isSinglePageView ..viewportGlobalRect = viewportGlobalRect - ..pdfTextSearchResult = pdfTextSearchResult; + ..pdfTextSearchResult = pdfTextSearchResult + ..onAnnotationSelectionChanged = onAnnotationSelectionChanged; renderObject.markNeedsPaint(); renderObject._scrollWhileSelection(); super.updateRenderObject(context, renderObject); @@ -162,47 +203,63 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { class CanvasRenderBox extends RenderBox { /// Constructor of CanvasRenderBox. CanvasRenderBox( - this.height, - this.width, - this.context, - this.pdfDocument, - this.pdfPages, - this.pageIndex, - this.interactionMode, - this.pdfViewerController, - this.enableDocumentLinkNavigation, - this.enableTextSelection, - this.onTextSelectionChanged, - this.onTextSelectionDragStarted, - this.onTextSelectionDragEnded, - this.textCollection, - this.currentSearchTextHighlightColor, - this.otherSearchTextHighlightColor, - this.isMobileWebView, - this.pdfTextSearchResult, - this.pdfScrollableStateKey, - this.singlePageViewStateKey, - this.viewportGlobalRect, - this.scrollDirection, - this.isSinglePageView) { + this.height, + this.width, + this.context, + this.pdfDocument, + this.pdfPages, + this.pageIndex, + this.interactionMode, + this.pdfViewerController, + this.enableDocumentLinkNavigation, + this.enableTextSelection, + this._textSelectionHelper, + this.onTextSelectionChanged, + this.onHyperlinkClicked, + this.onTextSelectionDragStarted, + this.onTextSelectionDragEnded, + this.textCollection, + this.currentSearchTextHighlightColor, + this.otherSearchTextHighlightColor, + this.isMobileWebView, + this.pdfTextSearchResult, + this.pdfScrollableStateKey, + this.singlePageViewStateKey, + this.viewportGlobalRect, + this.scrollDirection, + this.isSinglePageView, + this.textDirection, + this.canShowHyperlinkDialog, + this.enableHyperlinkNavigation, + this.onAnnotationSelectionChanged, + ) { final GestureArenaTeam team = GestureArenaTeam(); - _tapRecognizer = TapGestureRecognizer() - ..onTapUp = handleTapUp - ..onTapDown = handleTapDown; - _longPressRecognizer = LongPressGestureRecognizer() - ..onLongPressStart = handleLongPressStart; - _dragRecognizer = HorizontalDragGestureRecognizer() - ..team = team - ..onStart = handleDragStart - ..onUpdate = handleDragUpdate - ..onEnd = handleDragEnd - ..onDown = handleDragDown; - _verticalDragRecognizer = VerticalDragGestureRecognizer() - ..team = team - ..onStart = handleDragStart - ..onUpdate = handleDragUpdate - ..onEnd = handleDragEnd - ..onDown = handleDragDown; + _tapRecognizer = + TapGestureRecognizer() + ..onTapUp = handleTapUp + ..onTapDown = handleTapDown; + _longPressRecognizer = + LongPressGestureRecognizer()..onLongPressStart = handleLongPressStart; + _dragRecognizer = + HorizontalDragGestureRecognizer() + ..team = team + ..onStart = handleDragStart + ..onUpdate = handleDragUpdate + ..onEnd = handleDragEnd + ..onDown = handleDragDown; + _dragRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 10, + ); + _verticalDragRecognizer = + VerticalDragGestureRecognizer() + ..team = team + ..onStart = handleDragStart + ..onUpdate = handleDragUpdate + ..onEnd = handleDragEnd + ..onDown = handleDragDown; + _verticalDragRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 10, + ); } /// Height of Page @@ -241,6 +298,9 @@ class CanvasRenderBox extends RenderBox { /// Triggers when text selection is changed. late PdfTextSelectionChangedCallback? onTextSelectionChanged; + /// Triggers when Hyperlink is clicked. + late PdfHyperlinkClickedCallback? onHyperlinkClicked; + /// Indicates interaction mode of pdfViewer. late PdfInteractionMode interactionMode; @@ -257,7 +317,7 @@ class CanvasRenderBox extends RenderBox { late PdfTextSearchResult pdfTextSearchResult; /// If true,MobileWebView is enabled.Default value is false. - late final bool isMobileWebView; + late bool isMobileWebView; /// Key to access scrollable. final GlobalKey pdfScrollableStateKey; @@ -274,10 +334,26 @@ class CanvasRenderBox extends RenderBox { /// Determines layout option in PdfViewer. late bool isSinglePageView; + ///A direction of text flow. + late TextDirection textDirection; + + /// Indicates whether hyperlink dialog must be shown or not. + late bool canShowHyperlinkDialog; + + /// If true, hyperlink navigation is enabled. + late bool enableHyperlinkNavigation; + + /// Triggers when annotation is selected or deselected. + void Function(Annotation?)? onAnnotationSelectionChanged; + + /// Instance of [TextSelectionHelper] + late final TextSelectionHelper _textSelectionHelper; + int? _viewId; - int? _destinationPageIndex; + late int _destinationPageIndex; late Offset _totalPageOffset; bool _isTOCTapped = false; + bool _isHyperLinkTapped = false; bool _isMousePointer = false; double _startBubbleTapX = 0; double _endBubbleTapX = 0; @@ -287,10 +363,11 @@ class CanvasRenderBox extends RenderBox { bool _longPressed = false; bool _startBubbleDragging = false; bool _endBubbleDragging = false; + bool _isRTLText = false; double _zoomPercentage = 0.0; Offset? _tapDetails; Offset? _dragDetails; - Offset? _dragDownDetails; + late Offset _dragDownDetails; Color? _selectionColor; Color? _selectionHandleColor; late TapGestureRecognizer _tapRecognizer; @@ -298,8 +375,11 @@ class CanvasRenderBox extends RenderBox { late LongPressGestureRecognizer _longPressRecognizer; late VerticalDragGestureRecognizer _verticalDragRecognizer; late PdfDocumentLinkAnnotation? _documentLinkAnnotation; + late PdfUriAnnotation? _pdfUriAnnotation; + PdfTextWebLink? _pdfTextWebLink; late final PdfPageRotateAngle _rotatedAngle = pdfDocument!.pages[pageIndex].rotation; + bool _isConsecutiveTap = false; @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { @@ -307,17 +387,45 @@ class CanvasRenderBox extends RenderBox { _tapRecognizer.addPointer(event); if ((interactionMode == PdfInteractionMode.selection && kIsDesktop) || !kIsDesktop) { - if (enableTextSelection) { + if (pdfViewerController.annotationMode == PdfAnnotationMode.none) { + _dragRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 10, + ); + _verticalDragRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 10, + ); + } else { + _dragRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 0, + ); + _verticalDragRecognizer.gestureSettings = const DeviceGestureSettings( + touchSlop: 0, + ); + } + if (enableTextSelection && + pdfViewerController.annotationMode == PdfAnnotationMode.none) { _longPressRecognizer.addPointer(event); } if (event.kind == PointerDeviceKind.mouse) { _dragRecognizer.addPointer(event); _verticalDragRecognizer.addPointer(event); + } else if (pdfViewerController.annotationMode == + PdfAnnotationMode.highlight || + pdfViewerController.annotationMode == + PdfAnnotationMode.strikethrough || + pdfViewerController.annotationMode == PdfAnnotationMode.underline || + pdfViewerController.annotationMode == PdfAnnotationMode.squiggly) { + if (_checkTextInLocation(event.localPosition, event.kind)) { + _dragRecognizer.addPointer(event); + _verticalDragRecognizer.addPointer(event); + } } else if (_textSelectionHelper.selectionEnabled) { - final bool isStartDragPossible = - _checkStartBubblePosition(event.localPosition); - final bool isEndDragPossible = - _checkEndBubblePosition(event.localPosition); + final bool isStartDragPossible = _checkStartBubblePosition( + event.localPosition, + ); + final bool isEndDragPossible = _checkEndBubblePosition( + event.localPosition, + ); if (isStartDragPossible || isEndDragPossible) { _dragRecognizer.addPointer(event); _verticalDragRecognizer.addPointer(event); @@ -355,9 +463,481 @@ class CanvasRenderBox extends RenderBox { } } + SfPdfViewerThemeData? _pdfViewerThemeData; + late SfPdfViewerThemeData _effectiveThemeData; + late ThemeData _themeData; + late SfLocalizations _localizations; + + Future _showMobileHyperLinkDialog(Uri url) { + _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); + _themeData = Theme.of(context); + final bool isMaterial3 = _themeData.useMaterial3; + _localizations = SfLocalizations.of(context); + return showDialog( + context: context, + builder: (BuildContext context) { + final Orientation orientation = MediaQuery.of(context).orientation; + return Directionality( + textDirection: textDirection, + child: AlertDialog( + scrollable: true, + insetPadding: EdgeInsets.zero, + contentPadding: + orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.all(15), + buttonPadding: + orientation == Orientation.portrait + ? const EdgeInsets.all(8) + : const EdgeInsets.all(6), + backgroundColor: + _pdfViewerThemeData!.hyperlinkDialogStyle?.backgroundColor ?? + _effectiveThemeData.hyperlinkDialogStyle?.backgroundColor ?? + (Theme.of(context).colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242)), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _localizations.pdfHyperlinkLabel, + style: + isMaterial3 + ? Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: 24, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.headerTextStyle, + ) + : Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: 20, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.headerTextStyle, + ), + ), + SizedBox( + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, + child: RawMaterialButton( + onPressed: () { + Navigator.of(context).pop(); + _isHyperLinkTapped = false; + }, + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ) + : const RoundedRectangleBorder(), + child: Icon( + Icons.clear, + color: + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.closeIconColor ?? + _effectiveThemeData + .hyperlinkDialogStyle + ?.closeIconColor ?? + _themeData.colorScheme.onSurface.withValues( + alpha: 0.6, + ), + size: isMaterial3 ? 32 : 24, + ), + ), + ), + ], + ), + shape: + isMaterial3 + ? null + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + content: SingleChildScrollView( + child: SizedBox( + width: 296, + child: Column( + children: [ + Align( + alignment: + textDirection == TextDirection.rtl + ? Alignment.centerRight + : Alignment.centerLeft, + child: Padding( + padding: + orientation == Orientation.portrait + ? const EdgeInsets.fromLTRB(2, 0, 0, 8) + : const EdgeInsets.fromLTRB(10, 0, 0, 8), + child: Text( + _localizations.pdfHyperlinkContentLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.contentTextStyle, + ), + ), + ), + ), + Align( + alignment: + textDirection == TextDirection.rtl + ? Alignment.centerRight + : Alignment.centerLeft, + child: Padding( + padding: + orientation == Orientation.portrait + ? const EdgeInsets.fromLTRB(2, 0, 0, 0) + : const EdgeInsets.fromLTRB(10, 0, 0, 4), + child: Text( + '$url?', + textDirection: TextDirection.ltr, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.contentTextStyle, + ), + ), + ), + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + _isHyperLinkTapped = false; + }, + style: + isMaterial3 + ? ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ), + ) + : null, + child: Text( + _localizations.pdfHyperlinkDialogCancelLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.cancelTextStyle, + ), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), + child: TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await launchUrl(url, mode: LaunchMode.externalApplication); + }, + style: + isMaterial3 + ? ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ), + ) + : null, + child: Text( + _localizations.pdfHyperlinkDialogOpenLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: _themeData.colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.openTextStyle, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + Future _showDesktopHyperLinkDialog(Uri url) { + _pdfViewerThemeData = SfPdfViewerTheme.of(context); + final bool isMaterial3 = Theme.of(context).useMaterial3; + _effectiveThemeData = + isMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); + _themeData = Theme.of(context); + _localizations = SfLocalizations.of(context); + return showDialog( + context: context, + builder: (BuildContext context) { + return Directionality( + textDirection: textDirection, + child: AlertDialog( + scrollable: true, + insetPadding: EdgeInsets.zero, + titlePadding: isMaterial3 ? null : const EdgeInsets.all(16), + contentPadding: + isMaterial3 ? null : const EdgeInsets.symmetric(horizontal: 16), + buttonPadding: const EdgeInsets.all(24), + backgroundColor: + _pdfViewerThemeData!.hyperlinkDialogStyle?.backgroundColor ?? + _effectiveThemeData.hyperlinkDialogStyle?.backgroundColor ?? + (Theme.of(context).colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242)), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _localizations.pdfHyperlinkLabel, + style: Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: isMaterial3 ? 24 : 20, + color: + isMaterial3 + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.headerTextStyle, + ), + ), + SizedBox( + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, + child: RawMaterialButton( + onPressed: () { + Navigator.of(context).pop(); + _isHyperLinkTapped = false; + }, + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ) + : const RoundedRectangleBorder(), + child: Icon( + Icons.clear, + color: + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.closeIconColor ?? + _effectiveThemeData + .hyperlinkDialogStyle + ?.closeIconColor ?? + _themeData.colorScheme.onSurface.withValues( + alpha: 0.6, + ), + size: isMaterial3 ? 30 : 24, + ), + ), + ), + ], + ), + content: SingleChildScrollView( + child: SizedBox( + width: 361, + child: Column( + children: [ + Align( + alignment: + textDirection == TextDirection.rtl + ? Alignment.centerRight + : Alignment.centerLeft, + child: Padding( + padding: + isMaterial3 + ? const EdgeInsets.fromLTRB(2, 0, 0, 2) + : const EdgeInsets.fromLTRB(2, 0, 0, 8), + child: Text( + _localizations.pdfHyperlinkContentLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.contentTextStyle, + ), + ), + ), + ), + Align( + alignment: + textDirection == TextDirection.rtl + ? Alignment.centerRight + : Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.fromLTRB(2, 0, 0, 0), + child: Text( + '$url?', + textDirection: TextDirection.ltr, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.contentTextStyle, + ), + ), + ), + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + _isHyperLinkTapped = false; + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, + child: Text( + _localizations.pdfHyperlinkDialogCancelLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.cancelTextStyle, + ), + ), + ), + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await launchUrl(url, mode: LaunchMode.externalApplication); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, + child: Text( + _localizations.pdfHyperlinkDialogOpenLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: _themeData.colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .hyperlinkDialogStyle + ?.openTextStyle, + ), + ), + ), + ], + ), + ); + }, + ); + } + /// Handles the tap up event void handleTapUp(TapUpDetails details) { - if (textCollection == null && !_textSelectionHelper.enableTapSelection) { + if (!_textSelectionHelper.enableTapSelection) { clearSelection(); } if (_textSelectionHelper.enableTapSelection && @@ -365,8 +945,8 @@ class CanvasRenderBox extends RenderBox { updateContextMenuPosition(); _textSelectionHelper.enableTapSelection = false; } - if (enableDocumentLinkNavigation && pdfDocument != null) { - _viewId = pageIndex; + _viewId = pageIndex; + if (enableHyperlinkNavigation || enableDocumentLinkNavigation) { final double heightPercentage = pdfDocument!.pages[_viewId!].size.height / height; final double widthPercentage = @@ -374,84 +954,265 @@ class CanvasRenderBox extends RenderBox { 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 = - // ignore: avoid_as - page.annotations[index] as PdfDocumentLinkAnnotation; - assert(_documentLinkAnnotation != null); - if ((details.localPosition.dy >= - (_documentLinkAnnotation!.bounds.top / heightPercentage)) && - (details.localPosition.dy <= - (_documentLinkAnnotation!.bounds.bottom / - heightPercentage)) && - (details.localPosition.dx >= - (_documentLinkAnnotation!.bounds.left / heightPercentage)) && - (details.localPosition.dx <= - (_documentLinkAnnotation!.bounds.right / heightPercentage))) { - if (_documentLinkAnnotation!.destination?.page != null) { - _isTOCTapped = true; - final PdfPage destinationPage = - _documentLinkAnnotation!.destination!.page; - final int destinationPageIndex = - pdfDocument!.pages.indexOf(destinationPage) + 1; - Offset destinationPageOffset = - _documentLinkAnnotation!.destination!.location; - destinationPageOffset = getRotatedOffset(destinationPageOffset, - destinationPageIndex - 1, destinationPage.rotation); - final double positionX = - destinationPageOffset.dx / widthPercentage; - final double positionY = - destinationPageOffset.dy / heightPercentage; - final double pageOffset = - pdfPages[destinationPageIndex]!.pageOffset; - if (isSinglePageView) { - _totalPageOffset = Offset(positionX, positionY); - } else { - if (scrollDirection == PdfScrollDirection.horizontal) { - if (pdfViewerController.zoomLevel == 1) { - _totalPageOffset = Offset(pageOffset, positionY); + if (page.annotations[index] is PdfUriAnnotation && + enableHyperlinkNavigation) { + _pdfUriAnnotation = page.annotations[index] as PdfUriAnnotation; + assert(_pdfUriAnnotation != null); + if (_checkHyperLinkPosition( + details, + heightPercentage, + _pdfUriAnnotation!.bounds, + )) { + if (_pdfUriAnnotation!.uri.isNotEmpty) { + _isHyperLinkTapped = true; + final Uri uri = Uri.parse(_pdfUriAnnotation!.uri); + _showHyperLinkDialog(uri); + markNeedsPaint(); + break; + } + } + } else if (page.annotations[index] is PdfTextWebLink && + enableHyperlinkNavigation) { + _pdfTextWebLink = page.annotations[index] as PdfTextWebLink; + assert(_pdfTextWebLink != null); + if (_checkHyperLinkPosition( + details, + heightPercentage, + _pdfTextWebLink!.bounds, + )) { + if (_pdfTextWebLink!.url.isNotEmpty) { + _isHyperLinkTapped = true; + final bool isMailID = RegExp( + r'^.+@[a-zA-Z]+\.{1}[a-zA-Z]+(\.{0,1}[a-zA-Z]+)$', + ).hasMatch(_pdfTextWebLink!.url); + final bool isTel = _pdfTextWebLink!.url.startsWith('tel:'); + final String scheme = + isMailID + ? !_pdfTextWebLink!.url.contains('mailto') + ? 'mailto' + : '' + : isTel + ? 'tel' + : (!_pdfTextWebLink!.url.contains('https') && + !_pdfTextWebLink!.url.contains('http')) + ? 'https' + : ''; + final Uri url = + !_pdfTextWebLink!.url.contains(scheme) + ? scheme.contains('mailto') || scheme.contains('tel') + ? Uri(scheme: scheme, path: _pdfTextWebLink!.url) + : Uri(scheme: scheme, host: _pdfTextWebLink!.url) + : Uri.parse(_pdfTextWebLink!.url); + _showHyperLinkDialog(url); + markNeedsPaint(); + break; + } + } + } + if (enableDocumentLinkNavigation) { + if (page.annotations[index] is PdfDocumentLinkAnnotation) { + _documentLinkAnnotation = + page.annotations[index] as PdfDocumentLinkAnnotation; + assert(_documentLinkAnnotation != null); + if ((details.localPosition.dy >= + (_documentLinkAnnotation!.bounds.top / heightPercentage)) && + (details.localPosition.dy <= + (_documentLinkAnnotation!.bounds.bottom / + heightPercentage)) && + (details.localPosition.dx >= + (_documentLinkAnnotation!.bounds.left / + heightPercentage)) && + (details.localPosition.dx <= + (_documentLinkAnnotation!.bounds.right / + heightPercentage))) { + if (_documentLinkAnnotation!.destination?.page != null) { + final PdfPage destinationPage = + _documentLinkAnnotation!.destination!.page; + final int destinationPageIndex = + pdfDocument!.pages.indexOf(destinationPage) + 1; + Offset destinationPageOffset = + _documentLinkAnnotation!.destination!.location; + destinationPageOffset = getRotatedOffset( + destinationPageOffset, + destinationPageIndex - 1, + destinationPage.rotation, + ); + final double positionX = + destinationPageOffset.dx / widthPercentage; + final double positionY = + destinationPageOffset.dy / heightPercentage; + if (destinationPageIndex > 0) { + _isTOCTapped = true; + final double pageOffset = + pdfPages[destinationPageIndex]!.pageOffset; + if (isSinglePageView) { + _totalPageOffset = Offset(positionX, positionY); } else { - _totalPageOffset = - Offset(pageOffset + positionX, positionY); + if (scrollDirection == PdfScrollDirection.horizontal) { + if (pdfViewerController.zoomLevel == 1) { + _totalPageOffset = Offset(pageOffset, positionY); + } else { + _totalPageOffset = Offset( + pageOffset + positionX, + positionY, + ); + } + } else { + _totalPageOffset = Offset( + positionX, + pageOffset + positionY, + ); + } } - } else { - _totalPageOffset = Offset(positionX, pageOffset + positionY); + _viewId = pageIndex; + _destinationPageIndex = destinationPageIndex; + + /// Mark this render object as having changed its visual appearance. + /// + /// Rather than eagerly updating this render object's display list + /// in response to writes, we instead mark the render object as needing to + /// paint, which schedules a visual update. As part of the visual update, the + /// rendering pipeline will give this render object an opportunity to update + /// its display list. + /// + /// This mechanism batches the painting work so that multiple sequential + /// writes are coalesced, removing redundant computation. + /// + /// Once markNeedsPaint has been called on a render object, + /// debugNeedsPaint returns true for that render object until just after + /// the pipeline owner has called paint on the render object. + /// + /// See also: + /// + /// * RepaintBoundary, to scope a subtree of render objects to their own + /// layer, thus limiting the number of nodes that markNeedsPaint must mark + /// dirty. + markNeedsPaint(); + break; } } - _viewId = pageIndex; - _destinationPageIndex = destinationPageIndex; - - /// Mark this render object as having changed its visual appearance. - /// - /// Rather than eagerly updating this render object's display list - /// in response to writes, we instead mark the render object as needing to - /// paint, which schedules a visual update. As part of the visual update, the - /// rendering pipeline will give this render object an opportunity to update - /// its display list. - /// - /// This mechanism batches the painting work so that multiple sequential - /// writes are coalesced, removing redundant computation. - /// - /// Once markNeedsPaint has been called on a render object, - /// debugNeedsPaint returns true for that render object until just after - /// the pipeline owner has called paint on the render object. - /// - /// See also: - /// - /// * RepaintBoundary, to scope a subtree of render objects to their own - /// layer, thus limiting the number of nodes that markNeedsPaint must mark - /// dirty. - markNeedsPaint(); - break; } } } } } + _checkIfLinkAnnotationIsClicked(details.localPosition); + } + + /// Checks whether an annotation is present in the position. + Annotation? findAnnotation(Offset offset, int pageNumber) { + final double heightPercentage = + pdfDocument!.pages[pageIndex].size.height / height; + + final List annotations = + pdfViewerController + .getAnnotations() + .where( + (Annotation annotation) => annotation.pageNumber == pageNumber, + ) + .toList(); + + annotations.sort( + (Annotation b, Annotation a) => a.zOrder.compareTo(b.zOrder), + ); + for (final Annotation annotation in annotations) { + if (_canSelectAnnotation(offset, annotation, heightPercentage)) { + return annotation; + } + } + return null; + } + + /// Checks whether the Link annotation is clicked or not, + /// and skips annotation selection if the link annotation is clicked + /// and deselects the annotation if the annotation is already selected. + void _checkIfLinkAnnotationIsClicked(Offset offset) { + final Annotation? annotation = findAnnotation(offset, pageIndex + 1); + if (!_isHyperLinkTapped && !_isTOCTapped) { + if (annotation != null) { + if (!annotation.isSelected) { + onAnnotationSelectionChanged?.call(annotation); + } + } else { + onAnnotationSelectionChanged?.call(null); + } + } else if (annotation != null && annotation.isSelected) { + onAnnotationSelectionChanged?.call(null); + } + } + + /// Checks whether the annotation can be selected or not, + /// regardless of the annotation bounds. + bool _canSelectAnnotation( + Offset position, + Annotation annotation, + double heightPercentage, + ) { + List textMarkupRects = []; + final Offset tappedPagePosition = Offset( + position.dx * heightPercentage, + position.dy * heightPercentage, + ); + if (annotation is HighlightAnnotation) { + textMarkupRects = annotation.textMarkupRects; + } else if (annotation is StrikethroughAnnotation) { + textMarkupRects = annotation.textMarkupRects; + } else if (annotation is UnderlineAnnotation) { + textMarkupRects = annotation.textMarkupRects; + } else if (annotation is SquigglyAnnotation) { + textMarkupRects = annotation.textMarkupRects; + } + if (textMarkupRects.isNotEmpty) { + for (final Rect rect in textMarkupRects) { + if (rect.contains(tappedPagePosition)) { + return true; + } + } + } + + if (annotation is StickyNoteAnnotation) { + final Rect scaledBounds = + annotation.boundingBox.topLeft * heightPercentage & + (annotation.boundingBox.size / pdfViewerController.zoomLevel); + if (scaledBounds.contains(tappedPagePosition)) { + return true; + } + } + return false; + } + + /// Check if the hyperlink exists in the tapped position or not. + bool _checkHyperLinkPosition( + dynamic details, + double heightPercentage, + Rect bounds, + ) { + if ((details.localPosition.dy >= (bounds.top / heightPercentage)) && + (details.localPosition.dy <= (bounds.bottom / heightPercentage)) && + (details.localPosition.dx >= (bounds.left / heightPercentage)) && + (details.localPosition.dx <= (bounds.right / heightPercentage))) { + return true; + } + return false; + } + + /// Show the hyperlink navigation dialog. + void _showHyperLinkDialog(Uri uri) { + if (canShowHyperlinkDialog) { + if (uri.toString().contains('mailto')) { + launchUrl(uri); + } else { + kIsDesktop + ? _showDesktopHyperLinkDialog(uri) + : _showMobileHyperLinkDialog(uri); + } + } + triggerHyperLinkCallback(uri.toString()); } /// Handles the long press started event.cursorMode void handleLongPressStart(LongPressStartDetails details) { + _isConsecutiveTap = false; if (kIsDesktop && !isMobileWebView && pdfDocument != null) { clearMouseSelection(); final bool isTOC = findTOC(details.localPosition); @@ -467,29 +1228,40 @@ class CanvasRenderBox extends RenderBox { /// Handles the Drag start event. void handleDragStart(DragStartDetails details) { - _enableMouseSelection(details, 'DragStart'); - if (_textSelectionHelper.selectionEnabled) { - final bool isStartDragPossible = - _checkStartBubblePosition(_dragDownDetails!); - final bool isEndDragPossible = _checkEndBubblePosition(_dragDownDetails!); - if (isStartDragPossible) { - _startBubbleDragging = true; - onTextSelectionDragStarted(); - } else if (isEndDragPossible) { - _endBubbleDragging = true; - onTextSelectionDragStarted(); + _isConsecutiveTap = false; + final Annotation? annotation = findAnnotation( + details.localPosition, + pageIndex + 1, + ); + if (annotation == null) { + _enableMouseSelection(details, 'DragStart'); + if (_textSelectionHelper.selectionEnabled) { + final bool isStartDragPossible = _checkStartBubblePosition( + _dragDownDetails, + ); + final bool isEndDragPossible = _checkEndBubblePosition( + _dragDownDetails, + ); + if (isStartDragPossible) { + _startBubbleDragging = true; + onTextSelectionDragStarted(); + } else if (isEndDragPossible) { + _endBubbleDragging = true; + onTextSelectionDragStarted(); + } + } + if (details.kind == PointerDeviceKind.mouse) { + _isMousePointer = true; + } else { + _isMousePointer = false; } - } - if (details.kind == PointerDeviceKind.mouse) { - _isMousePointer = true; - } else { - _isMousePointer = false; } } /// Handles the drag update event. void handleDragUpdate(DragUpdateDetails details) { - if (kIsDesktop && !isMobileWebView && _isMousePointer) { + if ((kIsDesktop && !isMobileWebView && _isMousePointer) || + pdfViewerController.annotationMode != PdfAnnotationMode.none) { _updateSelectionPan(details); } if (_textSelectionHelper.selectionEnabled) { @@ -511,9 +1283,10 @@ class CanvasRenderBox extends RenderBox { /// Handles the drag end event. void handleDragEnd(DragEndDetails details) { - if (kIsDesktop && - !isMobileWebView && - _textSelectionHelper.mouseSelectionEnabled) { + if ((kIsDesktop && + !isMobileWebView && + _textSelectionHelper.mouseSelectionEnabled) || + pdfViewerController.annotationMode != PdfAnnotationMode.none) { if (_textSelectionHelper.isCursorExit) { _textSelectionHelper.isCursorExit = false; } @@ -542,31 +1315,107 @@ class CanvasRenderBox extends RenderBox { /// Handles the double tap down event. void handleDoubleTapDown(PointerDownEvent details) { _textSelectionHelper.enableTapSelection = true; - _enableMouseSelection(details, 'DoubleTap'); + _isConsecutiveTap = true; + final Annotation? annotation = findAnnotation( + details.localPosition, + pageIndex + 1, + ); + if (annotation == null) { + _enableMouseSelection(details, 'DoubleTap'); + } } /// Handles the triple tap down event. void handleTripleTapDown(PointerDownEvent details) { _textSelectionHelper.enableTapSelection = true; - _enableMouseSelection(details, 'TripleTap'); + _isConsecutiveTap = true; + final Annotation? annotation = findAnnotation( + details.localPosition, + pageIndex + 1, + ); + if (annotation == null) { + _enableMouseSelection(details, 'TripleTap'); + } + } + + bool _checkTextInLocation( + Offset position, + PointerDeviceKind pointerDeviceKind, + ) { + if (_textSelectionHelper.textLines == null || + _textSelectionHelper.viewId != pageIndex) { + _textSelectionHelper.viewId = pageIndex; + _textSelectionHelper.textLines = PdfTextExtractor( + pdfDocument!, + ).extractTextLines(startPageIndex: pageIndex); + } + for ( + int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex++ + ) { + final double heightPercentage = + pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; + for ( + int wordIndex = 0; + wordIndex < + _textSelectionHelper + .textLines![textLineIndex] + .wordCollection + .length; + wordIndex++ + ) { + final TextWord textWord = + _textSelectionHelper + .textLines![textLineIndex] + .wordCollection[wordIndex]; + for ( + int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++ + ) { + final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; + Rect glyphBounds = textGlyph.bounds; + if (pdfViewerController.annotationMode != PdfAnnotationMode.none) { + if (pointerDeviceKind == PointerDeviceKind.touch && + glyphIndex == 0) { + glyphBounds = Rect.fromLTRB( + glyphBounds.left - glyphBounds.width, + glyphBounds.top - glyphBounds.height, + glyphBounds.right, + glyphBounds.bottom, + ); + } + } + if (glyphBounds.contains(position * heightPercentage)) { + return true; + } + } + } + } + return false; } /// Enable mouse selection for mouse pointer,double tap and triple tap selection. void _enableMouseSelection(dynamic details, String gestureType) { - if (kIsDesktop && - !isMobileWebView && - enableTextSelection && - interactionMode == PdfInteractionMode.selection && - _isMousePointer) { + if ((kIsDesktop && + !isMobileWebView && + enableTextSelection && + interactionMode == PdfInteractionMode.selection && + _isMousePointer) || + pdfViewerController.annotationMode != PdfAnnotationMode.none) { final bool isTOC = findTOC(details.localPosition); _textSelectionHelper.initialScrollOffset = 0; _textSelectionHelper.finalScrollOffset = 0; - if (details.kind == PointerDeviceKind.mouse && !isTOC) { + if (pdfViewerController.annotationMode != PdfAnnotationMode.none || + details.kind == PointerDeviceKind.mouse && !isTOC) { if (_textSelectionHelper.selectionEnabled) { - final bool isStartDragPossible = - _checkStartBubblePosition(details.localPosition); - final bool isEndDragPossible = - _checkEndBubblePosition(details.localPosition); + final bool isStartDragPossible = _checkStartBubblePosition( + details.localPosition, + ); + final bool isEndDragPossible = _checkEndBubblePosition( + details.localPosition, + ); if (isStartDragPossible || isEndDragPossible) { _textSelectionHelper.mouseSelectionEnabled = false; } else { @@ -576,40 +1425,66 @@ class CanvasRenderBox extends RenderBox { if (_textSelectionHelper.textLines == null || _textSelectionHelper.viewId != pageIndex) { _textSelectionHelper.viewId = pageIndex; - _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument!) - .extractTextLines(startPageIndex: pageIndex); + _textSelectionHelper.textLines = PdfTextExtractor( + pdfDocument!, + ).extractTextLines(startPageIndex: pageIndex); } final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / - height; + height; if (gestureType == 'DragStart' && _textSelectionHelper.mouseSelectionEnabled) { - // ignore: avoid_as _textSelectionHelper.endBubbleX = (details.localPosition.dx as double) * heightPercentage; - // ignore: avoid_as _textSelectionHelper.endBubbleY = (details.localPosition.dy as double) * heightPercentage; + _textSelectionHelper.startBubbleX = + (details.localPosition.dx as double) * heightPercentage; + _textSelectionHelper.startBubbleY = + (details.localPosition.dy as double) * heightPercentage; } - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines!.length; - textLineIndex++) { + for ( + int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex++ + ) { final TextLine textLine = _textSelectionHelper.textLines![textLineIndex]; - for (int wordIndex = 0; - wordIndex < - _textSelectionHelper - .textLines![textLineIndex].wordCollection.length; - wordIndex++) { - final TextWord textWord = _textSelectionHelper - .textLines![textLineIndex].wordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { + for ( + int wordIndex = 0; + wordIndex < + _textSelectionHelper + .textLines![textLineIndex] + .wordCollection + .length; + wordIndex++ + ) { + final TextWord textWord = + _textSelectionHelper + .textLines![textLineIndex] + .wordCollection[wordIndex]; + for ( + int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++ + ) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (gestureType == 'DragStart') { - if (textGlyph.bounds - .contains(details.localPosition * heightPercentage)) { + Rect glyphBounds = textGlyph.bounds; + if (pdfViewerController.annotationMode != + PdfAnnotationMode.none && + glyphIndex == 0) { + glyphBounds = Rect.fromLTRB( + glyphBounds.left - glyphBounds.width, + glyphBounds.top - glyphBounds.height, + glyphBounds.right, + glyphBounds.bottom, + ); + } + + if (glyphBounds.contains( + details.localPosition * heightPercentage, + )) { _textSelectionHelper.firstSelectedGlyph = textGlyph; _textSelectionHelper.startIndex = textLineIndex; _textSelectionHelper.endIndex = textLineIndex; @@ -617,28 +1492,38 @@ class CanvasRenderBox extends RenderBox { } } else if (gestureType == 'DoubleTap') { triggerNullCallback(); - if (textWord.bounds - .contains(details.localPosition * heightPercentage)) { + if (textWord.bounds.contains( + details.localPosition * heightPercentage, + )) { _textSelectionHelper.firstSelectedGlyph = textWord.glyphs.first; _textSelectionHelper.endBubbleX = textWord.glyphs.last.bounds.right; _textSelectionHelper.endBubbleY = textWord.glyphs.last.bounds.bottom; + _textSelectionHelper.startBubbleX = + textWord.glyphs.first.bounds.right; + _textSelectionHelper.startBubbleY = + textWord.glyphs.first.bounds.bottom; _textSelectionHelper.startIndex = textLineIndex; _textSelectionHelper.endIndex = textLineIndex; _enableSelection(gestureType); } } else if (gestureType == 'TripleTap') { triggerNullCallback(); - if (textLine.bounds - .contains(details.localPosition * heightPercentage)) { + if (textLine.bounds.contains( + details.localPosition * heightPercentage, + )) { _textSelectionHelper.firstSelectedGlyph = textLine.wordCollection.first.glyphs.first; _textSelectionHelper.endBubbleX = textLine.wordCollection.last.bounds.right; _textSelectionHelper.endBubbleY = textLine.wordCollection.last.bounds.bottom; + _textSelectionHelper.startBubbleX = + textLine.wordCollection.first.bounds.right; + _textSelectionHelper.startBubbleY = + textLine.wordCollection.first.bounds.bottom; _textSelectionHelper.startIndex = textLineIndex; _textSelectionHelper.endIndex = textLineIndex; _enableSelection(gestureType); @@ -670,12 +1555,22 @@ class CanvasRenderBox extends RenderBox { } } - /// Triggers value callback for text selection. + /// Triggers value callback for text selection and hyperlink navigation. void triggerValueCallback() { if (onTextSelectionChanged != null) { - onTextSelectionChanged!(PdfTextSelectionChangedDetails( + onTextSelectionChanged!( + PdfTextSelectionChangedDetails( _textSelectionHelper.copiedText, - _textSelectionHelper.globalSelectedRegion)); + _textSelectionHelper.globalSelectedRegion, + ), + ); + } + } + + /// Triggers callback for hyperlink navigation. + void triggerHyperLinkCallback(String url) { + if (onHyperlinkClicked != null) { + onHyperlinkClicked!(PdfHyperlinkClickedDetails(url)); } } @@ -735,10 +1630,12 @@ class CanvasRenderBox extends RenderBox { !_textSelectionHelper.isCursorExit) { double endBubbleValue; if (_rotatedAngle == PdfPageRotateAngle.rotateAngle180) { - endBubbleValue = -(_textSelectionHelper.finalScrollOffset - - _textSelectionHelper.initialScrollOffset); + endBubbleValue = + -(_textSelectionHelper.finalScrollOffset - + _textSelectionHelper.initialScrollOffset); } else { - endBubbleValue = _textSelectionHelper.finalScrollOffset - + endBubbleValue = + _textSelectionHelper.finalScrollOffset - _textSelectionHelper.initialScrollOffset; } final double heightPercentage = @@ -747,6 +1644,10 @@ class CanvasRenderBox extends RenderBox { details.localPosition.dx * heightPercentage; _textSelectionHelper.endBubbleY = (details.localPosition.dy + endBubbleValue) * heightPercentage; + _textSelectionHelper.startBubbleX = + details.localPosition.dx * heightPercentage; + _textSelectionHelper.startBubbleY = + (details.localPosition.dy + endBubbleValue) * heightPercentage; markNeedsPaint(); } } @@ -786,11 +1687,12 @@ class CanvasRenderBox extends RenderBox { _textSelectionHelper.endBubbleY = _textSelectionHelper.endBubbleY! + endBubbleValue; } - final double position = pdfViewerController.scrollOffset.dy + + final double position = + pdfViewerController.scrollOffset.dy + (isReachedTop ? -_jumpOffset : _jumpOffset); if (isSelectionScroll) { - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { if (isSinglePageView) { singlePageViewStateKey.currentState?.jumpTo(yOffset: position); _scrollWhileSelection(); @@ -810,9 +1712,11 @@ class CanvasRenderBox extends RenderBox { /// Check the tap position same as the start bubble position. bool _checkStartBubblePosition(Offset details) { if (_textSelectionHelper.selectionEnabled) { - final double startBubbleX = _textSelectionHelper.startBubbleX! / + final double startBubbleX = + _textSelectionHelper.startBubbleX! / _textSelectionHelper.heightPercentage!; - final double startBubbleY = _textSelectionHelper.startBubbleY! / + final double startBubbleY = + _textSelectionHelper.startBubbleY! / _textSelectionHelper.heightPercentage!; if (details.dx >= startBubbleX - (_bubbleSize * _maximumZoomLevel) && details.dx <= startBubbleX && @@ -827,9 +1731,11 @@ class CanvasRenderBox extends RenderBox { /// Check the tap position same as the end bubble position. bool _checkEndBubblePosition(Offset details) { if (_textSelectionHelper.selectionEnabled) { - final double endBubbleX = _textSelectionHelper.endBubbleX! / + final double endBubbleX = + _textSelectionHelper.endBubbleX! / _textSelectionHelper.heightPercentage!; - final double endBubbleY = _textSelectionHelper.endBubbleY! / + final double endBubbleY = + _textSelectionHelper.endBubbleY! / _textSelectionHelper.heightPercentage!; if (details.dx >= endBubbleX && details.dx <= endBubbleX + (_bubbleSize * _maximumZoomLevel) && @@ -842,9 +1748,11 @@ class CanvasRenderBox extends RenderBox { } List _sortTextLines(List textLines) { - for (int textLineIndex = 0; - textLineIndex < textLines.length; - textLineIndex++) { + for ( + int textLineIndex = 0; + textLineIndex < textLines.length; + textLineIndex++ + ) { for (int index = textLineIndex + 1; index < textLines.length; index++) { if (textLines[textLineIndex].bounds.bottom > textLines[index].bounds.bottom) { @@ -859,22 +1767,31 @@ class CanvasRenderBox extends RenderBox { /// Gets rotated offset. Offset getRotatedOffset( - Offset offset, int pageIndex, PdfPageRotateAngle angle) { + Offset offset, + int pageIndex, + PdfPageRotateAngle angle, + ) { if (angle != PdfPageRotateAngle.rotateAngle0) { - List textLines = PdfTextExtractor(pdfDocument!) - .extractTextLines(startPageIndex: pageIndex); + List textLines = PdfTextExtractor( + pdfDocument!, + ).extractTextLines(startPageIndex: pageIndex); textLines = _sortTextLines(textLines); TextLine? textLine; - for (int textLineIndex = 0; - textLineIndex < textLines.length; - textLineIndex++) { + for ( + int textLineIndex = 0; + textLineIndex < textLines.length; + textLineIndex++ + ) { if (textLines[textLineIndex].bounds.topLeft.dy >= offset.dy) { textLine = textLines[textLineIndex]; break; } } - final Rect bounds = - getRotatedTextBounds(textLine!.bounds, pageIndex, angle); + final Rect bounds = getRotatedTextBounds( + textLine!.bounds, + pageIndex, + angle, + ); offset = bounds.topLeft; } return offset; @@ -882,7 +1799,10 @@ class CanvasRenderBox extends RenderBox { /// Gets rotated text bounds. Rect getRotatedTextBounds( - Rect textBounds, int pageIndex, PdfPageRotateAngle angle) { + Rect textBounds, + int pageIndex, + PdfPageRotateAngle angle, + ) { Rect? rotatedTextBounds; final double height = pdfDocument!.pages[pageIndex].size.height; final double width = pdfDocument!.pages[pageIndex].size.width; @@ -892,24 +1812,27 @@ class CanvasRenderBox extends RenderBox { break; case PdfPageRotateAngle.rotateAngle90: rotatedTextBounds = Rect.fromLTRB( - height - textBounds.bottom, - textBounds.left, - (height - textBounds.bottom) + textBounds.height, - textBounds.left + textBounds.width); + height - textBounds.bottom, + textBounds.left, + (height - textBounds.bottom) + textBounds.height, + textBounds.left + textBounds.width, + ); break; case PdfPageRotateAngle.rotateAngle180: rotatedTextBounds = Rect.fromLTRB( - width - textBounds.right, - height - textBounds.bottom, - (width - textBounds.right) + textBounds.width, - (height - textBounds.bottom) + textBounds.height); + width - textBounds.right, + height - textBounds.bottom, + (width - textBounds.right) + textBounds.width, + (height - textBounds.bottom) + textBounds.height, + ); break; case PdfPageRotateAngle.rotateAngle270: rotatedTextBounds = Rect.fromLTRB( - textBounds.top, - width - textBounds.right, - textBounds.top + textBounds.height, - (width - textBounds.right) + textBounds.width); + textBounds.top, + width - textBounds.right, + textBounds.top + textBounds.height, + (width - textBounds.right) + textBounds.width, + ); break; } return rotatedTextBounds; @@ -932,11 +1855,13 @@ class CanvasRenderBox extends RenderBox { Future.delayed(Duration.zero, () async { triggerValueCallback(); if ((!kIsDesktop || (kIsDesktop && isMobileWebView)) && - _textSelectionHelper.historyEntry == null) { + _textSelectionHelper.historyEntry == null && + context.mounted) { final ModalRoute? route = ModalRoute.of(context); if (route != null) { - _textSelectionHelper.historyEntry = - LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved); + _textSelectionHelper.historyEntry = LocalHistoryEntry( + onRemove: _handleHistoryEntryRemoved, + ); route.addLocalHistoryEntry(_textSelectionHelper.historyEntry!); } } @@ -945,7 +1870,8 @@ class CanvasRenderBox extends RenderBox { /// Remove history for Text Selection. void _handleHistoryEntryRemoved() { - if (textCollection != null && _textSelectionHelper.historyEntry != null) { + if (textCollection!.isNotEmpty && + _textSelectionHelper.historyEntry != null) { Navigator.of(context).maybePop(); } _textSelectionHelper.historyEntry = null; @@ -954,6 +1880,7 @@ class CanvasRenderBox extends RenderBox { /// clears Text Selection. bool clearSelection() { + _isRTLText = false; clearMouseSelection(); final bool clearTextSelection = !_textSelectionHelper.selectionEnabled; if (_textSelectionHelper.selectionEnabled) { @@ -991,16 +1918,19 @@ class CanvasRenderBox extends RenderBox { if (_textSelectionHelper.cursorTextLines == null || _textSelectionHelper.cursorPageNumber != pageIndex) { _textSelectionHelper.cursorPageNumber = pageIndex; - _textSelectionHelper.cursorTextLines = PdfTextExtractor(pdfDocument!) - .extractTextLines(startPageIndex: pageIndex); + _textSelectionHelper.cursorTextLines = PdfTextExtractor( + pdfDocument!, + ).extractTextLines(startPageIndex: pageIndex); } final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!].size.height / - height; + height; if (_textSelectionHelper.cursorTextLines != null) { - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.cursorTextLines!.length; - textLineIndex++) { + for ( + int textLineIndex = 0; + textLineIndex < _textSelectionHelper.cursorTextLines!.length; + textLineIndex++ + ) { if (_textSelectionHelper.cursorTextLines![textLineIndex].bounds .contains(details * heightPercentage)) { return _textSelectionHelper.cursorTextLines![textLineIndex]; @@ -1019,12 +1949,18 @@ class CanvasRenderBox extends RenderBox { pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!]; final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!].size.height / - height; + height; final Offset hoverDetails = details * heightPercentage; for (int index = 0; index < page.annotations.count; index++) { - if (page.annotations[index] is PdfDocumentLinkAnnotation) { + final bool hasTOC = + page.annotations[index] is PdfDocumentLinkAnnotation && + enableDocumentLinkNavigation; + final bool hasURI = + (page.annotations[index] is PdfUriAnnotation || + page.annotations[index] is PdfTextWebLink) && + enableHyperlinkNavigation; + if (hasTOC) { _documentLinkAnnotation = - // ignore: avoid_as page.annotations[index] as PdfDocumentLinkAnnotation; if ((hoverDetails.dy >= (_documentLinkAnnotation!.bounds.top)) && (hoverDetails.dy <= (_documentLinkAnnotation!.bounds.bottom)) && @@ -1032,6 +1968,21 @@ class CanvasRenderBox extends RenderBox { (hoverDetails.dx <= (_documentLinkAnnotation!.bounds.right))) { return true; } + } else if (hasURI) { + late Rect bounds; + if (page.annotations[index] is PdfUriAnnotation) { + _pdfUriAnnotation = page.annotations[index] as PdfUriAnnotation; + bounds = _pdfUriAnnotation!.bounds; + } else { + _pdfTextWebLink = page.annotations[index] as PdfTextWebLink; + bounds = _pdfTextWebLink!.bounds; + } + if ((hoverDetails.dy >= (bounds.top)) && + (hoverDetails.dy <= (bounds.bottom)) && + (hoverDetails.dx >= (bounds.left)) && + (hoverDetails.dx <= (bounds.right))) { + return true; + } } } return false; @@ -1047,8 +1998,9 @@ class CanvasRenderBox extends RenderBox { if ((!_textSelectionHelper.enableTapSelection || (_textSelectionHelper.globalSelectedRegion != null && _tapDetails != null && - !_textSelectionHelper.globalSelectedRegion! - .contains(_tapDetails!))) && + !_textSelectionHelper.globalSelectedRegion!.contains( + _tapDetails!, + ))) && _textSelectionHelper.mouseSelectionEnabled) { _textSelectionHelper.mouseSelectionEnabled = false; markNeedsPaint(); @@ -1060,24 +2012,52 @@ class CanvasRenderBox extends RenderBox { /// Check the text glyph inside the selected region. bool checkGlyphInRegion( - TextGlyph textGlyph, TextGlyph startGlyph, Offset details) { + TextGlyph textGlyph, + TextGlyph startGlyph, + Offset startBubbleDetails, + Offset endBubbleDetails, + bool isRTLText, + ) { 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)) { + if (isRTLText && !_isConsecutiveTap) { + if ((glyphCenterY > top && glyphCenterY < startBubbleDetails.dy) && + (glyphCenterX < startGlyph.bounds.right || glyphCenterY > bottom) && + (textGlyph.bounds.bottom < startBubbleDetails.dy || + glyphCenterX > startBubbleDetails.dx)) { + return true; + } + if (startBubbleDetails.dy < top || + (startBubbleDetails.dy < bottom && + startBubbleDetails.dx > startGlyph.bounds.right)) { + if ((glyphCenterY > startBubbleDetails.dy && glyphCenterY < bottom) && + (glyphCenterX < startBubbleDetails.dx || + textGlyph.bounds.top > startBubbleDetails.dy) && + (textGlyph.bounds.bottom < top || + glyphCenterX > startGlyph.bounds.right)) { + return true; + } + } + } else { + if ((glyphCenterY > top && glyphCenterY < endBubbleDetails.dy) && + (glyphCenterX > startGlyph.bounds.left || glyphCenterY > bottom) && + (textGlyph.bounds.bottom < endBubbleDetails.dy || + glyphCenterX < endBubbleDetails.dx)) { return true; } + if (endBubbleDetails.dy < top || + (endBubbleDetails.dy < bottom && + endBubbleDetails.dx < startGlyph.bounds.left)) { + if ((glyphCenterY > endBubbleDetails.dy && glyphCenterY < bottom) && + (glyphCenterX > endBubbleDetails.dx || + textGlyph.bounds.top > endBubbleDetails.dy) && + (textGlyph.bounds.bottom < top || + glyphCenterX < startGlyph.bounds.left)) { + return true; + } + } } return false; } @@ -1092,34 +2072,44 @@ class CanvasRenderBox extends RenderBox { /// Draw the start bubble. void _drawStartBubble( - Canvas canvas, Paint bubblePaint, Offset startBubbleOffset) { + Canvas canvas, + Paint bubblePaint, + Offset startBubbleOffset, + ) { canvas.drawRRect( - RRect.fromLTRBAndCorners( - startBubbleOffset.dx - (_bubbleSize / _zoomPercentage), - startBubbleOffset.dy, - startBubbleOffset.dx, - startBubbleOffset.dy + (_bubbleSize / _zoomPercentage), - topLeft: const Radius.circular(10.0), - topRight: const Radius.circular(1.0), - bottomRight: const Radius.circular(10.0), - bottomLeft: const Radius.circular(10.0)), - bubblePaint); + RRect.fromLTRBAndCorners( + startBubbleOffset.dx - (_bubbleSize / _zoomPercentage), + startBubbleOffset.dy, + startBubbleOffset.dx, + startBubbleOffset.dy + (_bubbleSize / _zoomPercentage), + topLeft: const Radius.circular(10.0), + topRight: const Radius.circular(1.0), + bottomRight: const Radius.circular(10.0), + bottomLeft: const Radius.circular(10.0), + ), + bubblePaint, + ); } /// Draw the end bubble. void _drawEndBubble( - Canvas canvas, Paint bubblePaint, Offset endBubbleOffset) { + Canvas canvas, + Paint bubblePaint, + Offset endBubbleOffset, + ) { canvas.drawRRect( - RRect.fromLTRBAndCorners( - endBubbleOffset.dx, - endBubbleOffset.dy, - endBubbleOffset.dx + (_bubbleSize / _zoomPercentage), - endBubbleOffset.dy + (_bubbleSize / _zoomPercentage), - topLeft: const Radius.circular(1.0), - topRight: const Radius.circular(10.0), - bottomRight: const Radius.circular(10.0), - bottomLeft: const Radius.circular(10.0)), - bubblePaint); + RRect.fromLTRBAndCorners( + endBubbleOffset.dx, + endBubbleOffset.dy, + endBubbleOffset.dx + (_bubbleSize / _zoomPercentage), + endBubbleOffset.dy + (_bubbleSize / _zoomPercentage), + topLeft: const Radius.circular(1.0), + topRight: const Radius.circular(10.0), + bottomRight: const Radius.circular(10.0), + bottomLeft: const Radius.circular(10.0), + ), + bubblePaint, + ); } /// Draw the Rect for selected text. @@ -1136,31 +2126,81 @@ class CanvasRenderBox extends RenderBox { return null; } + void _performHyperLinkNavigation(Canvas canvas, Offset offset) { + if (pageIndex == _viewId) { + if (_isHyperLinkTapped && enableHyperlinkNavigation) { + final Rect bounds = + _pdfTextWebLink != null + ? _pdfTextWebLink!.bounds + : _pdfUriAnnotation!.bounds; + _drawHyperLinkTapColor(canvas, offset, bounds); + _isHyperLinkTapped = false; + Future.delayed(Duration.zero, () async { + markNeedsPaint(); + }); + } + } + } + + /// Draw the selection background color while tapping the hyperlink. + void _drawHyperLinkTapColor(Canvas canvas, Offset offset, Rect bounds) { + final double heightPercentage = + pdfDocument!.pages[_viewId!].size.height / height; + final Paint wordPaint = + Paint()..color = const Color.fromRGBO(228, 238, 244, 0.75); + canvas.drawRect( + offset.translate( + bounds.left / heightPercentage, + bounds.top / heightPercentage, + ) & + Size( + bounds.width / heightPercentage, + bounds.height / heightPercentage, + ), + wordPaint, + ); + } + /// Perform document link navigation. void _performDocumentLinkNavigation(Canvas canvas, Offset offset) { if (pageIndex == _viewId) { if (_isTOCTapped) { final double heightPercentage = pdfDocument!.pages[_viewId!].size.height / height; - final Paint wordPaint = Paint() - ..color = const Color.fromRGBO(228, 238, 244, 0.75); + final Paint wordPaint = + Paint()..color = const 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), - wordPaint); + offset.translate( + _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 { if (isSinglePageView) { singlePageViewStateKey.currentState!.jumpOnZoomedDocument( - _destinationPageIndex!, - Offset(_totalPageOffset.dx, _totalPageOffset.dy)); + _destinationPageIndex, + Offset(_totalPageOffset.dx, _totalPageOffset.dy), + ); } else { + final double xOffSet = + textDirection == TextDirection.rtl && + scrollDirection == PdfScrollDirection.horizontal && + !isSinglePageView + ? (pdfScrollableStateKey.currentWidget! as PdfScrollable) + .maxScrollExtent - + _totalPageOffset.dx + : _totalPageOffset.dx; pdfViewerController.jumpTo( - xOffset: _totalPageOffset.dx, yOffset: _totalPageOffset.dy); + xOffset: xOffSet, + yOffset: _totalPageOffset.dy, + ); } }); _isTOCTapped = false; @@ -1170,25 +2210,29 @@ class CanvasRenderBox extends RenderBox { /// Perform text search. void _performTextSearch(Canvas canvas, Offset offset) { - if (textCollection != null && !_textSelectionHelper.selectionEnabled) { - final Paint currentInstancePaint = Paint() - ..color = currentSearchTextHighlightColor; - final Paint otherInstancePaint = Paint() - ..color = otherSearchTextHighlightColor; + if (textCollection!.isNotEmpty) { + final Paint currentInstancePaint = + Paint()..color = currentSearchTextHighlightColor; + final Paint otherInstancePaint = + Paint()..color = otherSearchTextHighlightColor; for (int i = 0; i < textCollection!.length; i++) { final MatchedItem item = textCollection![i]; final double heightPercentage = 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), - i == pdfTextSearchResult.currentInstanceIndex - 1 - ? currentInstancePaint - : otherInstancePaint); + offset.translate( + textCollection![i].bounds.left / heightPercentage, + textCollection![i].bounds.top / heightPercentage, + ) & + Size( + textCollection![i].bounds.width / heightPercentage, + textCollection![i].bounds.height / heightPercentage, + ), + i == pdfTextSearchResult.currentInstanceIndex - 1 + ? currentInstancePaint + : otherInstancePaint, + ); } else if (item.pageIndex > pageIndex) { break; } @@ -1198,76 +2242,178 @@ class CanvasRenderBox extends RenderBox { /// Perform mouse or touch selection. void _performSelection( - Canvas canvas, Offset offset, Paint textPaint, Paint bubblePaint) { + Canvas canvas, + Offset offset, + Paint textPaint, + Paint bubblePaint, + ) { if (_textSelectionHelper.viewId == pageIndex) { final TextGlyph startGlyph = _textSelectionHelper.firstSelectedGlyph!; - final Offset details = Offset( - _textSelectionHelper.endBubbleX!, _textSelectionHelper.endBubbleY!); + final Offset startBubbleDetails = Offset( + _textSelectionHelper.startBubbleX!, + _textSelectionHelper.startBubbleY!, + ); + final Offset endBubbleDetails = Offset( + _textSelectionHelper.endBubbleX!, + _textSelectionHelper.endBubbleY!, + ); final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; _textSelectionHelper.heightPercentage = heightPercentage; _textSelectionHelper.copiedText = ''; + double minX = 0, maxX = 0, minY = 0, maxY = 0; _textSelectionHelper.selectedTextLines.clear(); if (_textSelectionHelper.mouseSelectionEnabled) { - _findStartAndEndIndex(details, heightPercentage, true, - startPoint: startGlyph.bounds); + _findStartAndEndIndex( + endBubbleDetails, + heightPercentage, + true, + startPoint: startGlyph.bounds, + ); } - for (int textLineIndex = _textSelectionHelper.startIndex; - textLineIndex <= _textSelectionHelper.endIndex; - textLineIndex++) { + for ( + int textLineIndex = _textSelectionHelper.startIndex; + textLineIndex <= _textSelectionHelper.endIndex; + textLineIndex++ + ) { final TextLine line = _textSelectionHelper.textLines![textLineIndex]; + final bool isRTLText = intl.Bidi.detectRtlDirectionality(line.text); Rect? startPoint; Rect? endPoint; String glyphText = ''; + // Determines the selected text bounds for multi line selection. + if (_textSelectionHelper.startIndex != _textSelectionHelper.endIndex) { + if (textLineIndex == _textSelectionHelper.startIndex) { + minX = line.bounds.left; + minY = line.bounds.top; + maxX = line.bounds.right; + maxY = line.bounds.bottom; + } else { + minX = minX < line.bounds.left ? minX : line.bounds.left; + minY = minY < line.bounds.top ? minY : line.bounds.top; + maxX = maxX > line.bounds.right ? maxX : line.bounds.right; + maxY = maxY > line.bounds.bottom ? maxY : line.bounds.bottom; + } + } final List textWordCollection = line.wordCollection; - for (int wordIndex = 0; - wordIndex < textWordCollection.length; - wordIndex++) { + for ( + int wordIndex = 0; + wordIndex < textWordCollection.length; + wordIndex++ + ) { final TextWord textWord = textWordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { + for ( + int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++ + ) { final TextGlyph glyph = textWord.glyphs[glyphIndex]; - final bool canSelectGlyph = - checkGlyphInRegion(glyph, startGlyph, details); + final bool canSelectGlyph = checkGlyphInRegion( + glyph, + startGlyph, + startBubbleDetails, + endBubbleDetails, + isRTLText, + ); if (canSelectGlyph) { startPoint ??= glyph.bounds; endPoint = glyph.bounds; - glyphText = glyphText + glyph.text; - _textSelectionHelper.copiedText = - _textSelectionHelper.copiedText! + glyph.text; - final Rect textRectOffset = offset.translate( - glyph.bounds.left / heightPercentage, - glyph.bounds.top / heightPercentage) & - Size(glyph.bounds.width / heightPercentage, - glyph.bounds.height / heightPercentage); + glyphText = _getSelectedGlyphText( + glyphText, + glyph, + textWord, + glyphIndex, + ); + _textSelectionHelper.copiedText = glyphText; + final Rect textRectOffset = + offset.translate( + glyph.bounds.left / heightPercentage, + glyph.bounds.top / heightPercentage, + ) & + Size( + glyph.bounds.width / heightPercentage, + glyph.bounds.height / heightPercentage, + ); _drawTextRect(canvas, textPaint, textRectOffset); } + // When checking the last glyph + if (glyphIndex == textWord.glyphs.length - 1 && + wordIndex < textWordCollection.length - 1 && + !_isRTLText && + // When the glyph is selected + canSelectGlyph) { + final TextWord word = textWordCollection[wordIndex]; + final TextGlyph currentWordLast = + word.glyphs[word.text.length - 1]; + final TextGlyph nextWordFirst = + textWordCollection[wordIndex + 1].glyphs[0]; + + if ((((currentWordLast.bounds.left + + currentWordLast.bounds.width) - + nextWordFirst.bounds.left) + .abs()) > + 1.0) { + glyphText = '$glyphText '; + } + } 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), + _textSelectionHelper.selectedTextLines.add( + PdfTextLine( + Rect.fromLTRB( + startPoint.left, + startPoint.top, + endPoint.right, + endPoint.bottom, + ), glyphText, - _textSelectionHelper.viewId!)); + _textSelectionHelper.viewId! + 1, + ), + ); if (_textSelectionHelper.mouseSelectionEnabled) { - Offset startOffset = Offset( + Offset startOffset = Offset.zero; + Offset endOffset = Offset.zero; + if (_textSelectionHelper.startIndex == + _textSelectionHelper.endIndex) { + startOffset = Offset( startGlyph.bounds.left / heightPercentage, - startGlyph.bounds.top / heightPercentage); - final Offset endOffset = Offset( + startGlyph.bounds.top / heightPercentage, + ); + endOffset = Offset( endPoint.right / heightPercentage, - endPoint.bottom / heightPercentage); - if (details.dy < startGlyph.bounds.top) { - startOffset = Offset(details.dx / heightPercentage, - details.dy / heightPercentage); + endPoint.bottom / heightPercentage, + ); + } else { + startOffset = Offset( + minX / heightPercentage, + minY / heightPercentage, + ); + endOffset = Offset( + maxX / heightPercentage, + maxY / heightPercentage, + ); + } + if (endBubbleDetails.dy < startGlyph.bounds.top) { + startOffset = Offset( + endBubbleDetails.dx / heightPercentage, + endBubbleDetails.dy / heightPercentage, + ); } _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( - localToGlobal(startOffset), localToGlobal(endOffset)); + localToGlobal(startOffset), + localToGlobal(endOffset), + ); if (textLineIndex == _textSelectionHelper.endIndex) { _selectTextLinesInRegion( - canvas, offset, textPaint, heightPercentage, true, - startPoint: startOffset, endPoint: endPoint); + canvas, + offset, + textPaint, + heightPercentage, + true, + startPoint: startOffset, + endPoint: endPoint, + ); } } } @@ -1277,28 +2423,92 @@ class CanvasRenderBox extends RenderBox { /// forum link: https://www.syncfusion.com/forums/170024/ /// copy-text-from-pdf-content-does-not-consider-linefeed - if (_textSelectionHelper.selectedTextLines.length > 1 && kIsDesktop) { - _textSelectionHelper.copiedText = - _copiedTextLines(_textSelectionHelper.selectedTextLines); + if (_textSelectionHelper.selectedTextLines.length > 1) { + _textSelectionHelper.copiedText = _copiedTextLines( + _textSelectionHelper.selectedTextLines, + ); } if (_textSelectionHelper.selectionEnabled) { final Offset startBubbleOffset = offset.translate( - _textSelectionHelper.startBubbleX! / heightPercentage, - _textSelectionHelper.startBubbleY! / heightPercentage); + _textSelectionHelper.startBubbleX! / heightPercentage, + _textSelectionHelper.startBubbleY! / heightPercentage, + ); final Offset endBubbleOffset = offset.translate( - details.dx / heightPercentage, details.dy / heightPercentage); + endBubbleDetails.dx / heightPercentage, + endBubbleDetails.dy / heightPercentage, + ); _drawStartBubble(canvas, bubblePaint, startBubbleOffset); _drawEndBubble(canvas, bubblePaint, endBubbleOffset); + Offset startOffset = Offset.zero; + Offset endOffset = Offset.zero; + if (_textSelectionHelper.startIndex == _textSelectionHelper.endIndex) { + startOffset = Offset( + startGlyph.bounds.left / heightPercentage, + startGlyph.bounds.top / heightPercentage, + ); + endOffset = Offset( + endBubbleDetails.dx / heightPercentage, + endBubbleDetails.dy / heightPercentage, + ); + } else { + startOffset = Offset( + minX / heightPercentage, + minY / heightPercentage, + ); + endOffset = Offset(maxX / heightPercentage, maxY / heightPercentage); + } _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( - localToGlobal(Offset(startGlyph.bounds.left / heightPercentage, - startGlyph.bounds.top / heightPercentage)), - localToGlobal(Offset( - details.dx / heightPercentage, details.dy / heightPercentage))); + localToGlobal(startOffset), + localToGlobal(endOffset), + ); _selectTextLinesInRegion( - canvas, offset, textPaint, heightPercentage, false); + canvas, + offset, + textPaint, + heightPercentage, + false, + ); + } + } + } + + /// Gets selected glyph text. + String _getSelectedGlyphText( + String glyphText, + TextGlyph glyph, + TextWord textWord, + int glyphIndex, + ) { + final bool isRTLGlyph = intl.Bidi.detectRtlDirectionality(glyph.text); + if (isRTLGlyph) { + _isRTLText = true; + } + if (_isRTLText) { + String rtlText = ''; + final bool isRTLWord = intl.Bidi.detectRtlDirectionality(textWord.text); + if (!isRTLWord) { + rtlText = + textWord.glyphs[textWord.glyphs.length - (glyphIndex + 1)].text; + } else { + rtlText = glyph.text; + } + glyphText = rtlText + glyphText; + } else { + double glyphPosition = 0; + if (glyphIndex < textWord.text.length - 1) { + final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; + final double currentGlyph = + textGlyph.bounds.width + textGlyph.bounds.left; + final double nextGlyph = textWord.glyphs[glyphIndex + 1].bounds.left; + glyphPosition = (currentGlyph - nextGlyph).abs(); } + glyphText = + (glyphPosition > 1.0) + ? '$glyphText${glyph.text} ' + : glyphText + glyph.text; } + return glyphText; } /// Consider the line feed for the copied TextLines. @@ -1316,17 +2526,32 @@ class CanvasRenderBox extends RenderBox { /// Finds start and end index of selected textLine. void _findStartAndEndIndex( - Offset details, double heightPercentage, bool isMouseSelection, - {Rect startPoint = Rect.zero}) { + Offset details, + double heightPercentage, + bool isMouseSelection, { + Rect startPoint = Rect.zero, + }) { for (int i = 0; i < _textSelectionHelper.textLines!.length; i++) { final TextLine line = _textSelectionHelper.textLines![i]; + final bool isRTLText = intl.Bidi.detectRtlDirectionality(line.text); if (!isMouseSelection) { - if (line.bounds.contains(details * heightPercentage)) { - if (_startBubbleDragging && i <= _textSelectionHelper.endIndex) { - _textSelectionHelper.startIndex = i; - } else if (_endBubbleDragging && - i >= _textSelectionHelper.startIndex) { - _textSelectionHelper.endIndex = i; + if (isRTLText) { + if (line.bounds.contains(details * heightPercentage)) { + if (_startBubbleDragging && i >= _textSelectionHelper.startIndex) { + _textSelectionHelper.endIndex = i; + } else if (_endBubbleDragging && + i <= _textSelectionHelper.endIndex) { + _textSelectionHelper.startIndex = i; + } + } + } else { + if (line.bounds.contains(details * heightPercentage)) { + if (_startBubbleDragging && i <= _textSelectionHelper.endIndex) { + _textSelectionHelper.startIndex = i; + } else if (_endBubbleDragging && + i >= _textSelectionHelper.startIndex) { + _textSelectionHelper.endIndex = i; + } } } } else if ((line.bounds.contains(details) || @@ -1344,12 +2569,20 @@ class CanvasRenderBox extends RenderBox { } /// Selects the textLine which is inside the selected region. - void _selectTextLinesInRegion(Canvas canvas, Offset offset, Paint textPaint, - double heightPercentage, bool isMouseSelection, - {Offset startPoint = Offset.zero, Rect endPoint = Rect.zero}) { - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines!.length; - textLineIndex++) { + void _selectTextLinesInRegion( + Canvas canvas, + Offset offset, + Paint textPaint, + double heightPercentage, + bool isMouseSelection, { + Offset startPoint = Offset.zero, + Rect endPoint = Rect.zero, + }) { + for ( + int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex++ + ) { final TextLine line = _textSelectionHelper.textLines![textLineIndex]; if (textLineIndex < _textSelectionHelper.startIndex || textLineIndex > _textSelectionHelper.endIndex) { @@ -1359,7 +2592,9 @@ class CanvasRenderBox extends RenderBox { final double bottom = line.bounds.bottom; final Rect startGlyph = _textSelectionHelper.firstSelectedGlyph!.bounds; final Offset endOffset = Offset( - _textSelectionHelper.endBubbleX!, _textSelectionHelper.endBubbleY!); + _textSelectionHelper.endBubbleX!, + _textSelectionHelper.endBubbleY!, + ); if ((!isMouseSelection && left.floor() >= startGlyph.left.floor() && top.floor() >= startGlyph.top.floor() && @@ -1370,10 +2605,15 @@ class CanvasRenderBox extends RenderBox { top.floor() / heightPercentage >= startPoint.dy.floor() && right.floor() <= endPoint.right.floor() && bottom.floor() <= endPoint.bottom.floor())) { - final Rect lineRectOffset = offset.translate( - left / heightPercentage, top / heightPercentage) & - Size(line.bounds.width / heightPercentage, - line.bounds.height / heightPercentage); + final Rect lineRectOffset = + offset.translate( + left / heightPercentage, + top / heightPercentage, + ) & + Size( + line.bounds.width / heightPercentage, + line.bounds.height / heightPercentage, + ); _drawTextRect(canvas, textPaint, lineRectOffset); } } @@ -1387,14 +2627,16 @@ class CanvasRenderBox extends RenderBox { } final Canvas canvas = context.canvas; final ThemeData theme = Theme.of(this.context); - final TextSelectionThemeData selectionTheme = - TextSelectionTheme.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); + _selectionColor ??= + selectionTheme.selectionColor ?? + cupertinoTheme.primaryColor.withValues(alpha: 0.40); _selectionHandleColor ??= selectionTheme.selectionHandleColor ?? cupertinoTheme.primaryColor; break; @@ -1402,25 +2644,30 @@ class CanvasRenderBox extends RenderBox { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: - _selectionColor ??= selectionTheme.selectionColor ?? - theme.colorScheme.primary.withOpacity(0.40); + _selectionColor ??= + selectionTheme.selectionColor ?? + theme.colorScheme.primary.withValues(alpha: 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; + _zoomPercentage = + pdfViewerController.zoomLevel > _maximumZoomLevel + ? _maximumZoomLevel + : pdfViewerController.zoomLevel; _performDocumentLinkNavigation(canvas, offset); + _performHyperLinkNavigation(canvas, offset); _performTextSearch(canvas, offset); if (_textSelectionHelper.mouseSelectionEnabled && _textSelectionHelper.textLines != null && _textSelectionHelper.endBubbleX != null && - _textSelectionHelper.endBubbleY != null) { + _textSelectionHelper.endBubbleY != null && + _textSelectionHelper.startBubbleX != null && + _textSelectionHelper.startBubbleY != null) { _performSelection(canvas, offset, textPaint, bubblePaint); } @@ -1428,16 +2675,21 @@ class CanvasRenderBox extends RenderBox { final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; _textSelectionHelper.heightPercentage = heightPercentage; - _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument!) - .extractTextLines(startPageIndex: _textSelectionHelper.viewId); - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines!.length; - textLineIndex++) { + _textSelectionHelper.textLines = PdfTextExtractor( + pdfDocument!, + ).extractTextLines(startPageIndex: _textSelectionHelper.viewId); + for ( + int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex++ + ) { final TextLine line = _textSelectionHelper.textLines![textLineIndex]; final List textWordCollection = line.wordCollection; - for (int wordIndex = 0; - wordIndex < textWordCollection.length; - wordIndex++) { + for ( + int wordIndex = 0; + wordIndex < textWordCollection.length; + wordIndex++ + ) { final TextWord textWord = textWordCollection[wordIndex]; final Rect wordBounds = textWord.bounds; if (_tapDetails != null && @@ -1453,32 +2705,54 @@ class CanvasRenderBox extends RenderBox { _textSelectionHelper.endBubbleY = textWord.bounds.bottomRight.dy; _textSelectionHelper.startBubbleX = textWord.bounds.bottomLeft.dx; _textSelectionHelper.endBubbleX = textWord.bounds.bottomRight.dx; - final Rect textRectOffset = offset.translate( - textWord.bounds.left / heightPercentage, - textWord.bounds.top / heightPercentage) & - Size(wordBounds.width / heightPercentage, - wordBounds.height / heightPercentage); + final Rect textRectOffset = + offset.translate( + textWord.bounds.left / heightPercentage, + textWord.bounds.top / heightPercentage, + ) & + Size( + wordBounds.width / heightPercentage, + wordBounds.height / heightPercentage, + ); _drawTextRect(canvas, textPaint, textRectOffset); final Offset startBubbleOffset = offset.translate( - textWord.bounds.bottomLeft.dx / heightPercentage, - textWord.bounds.bottomLeft.dy / heightPercentage); + textWord.bounds.bottomLeft.dx / heightPercentage, + textWord.bounds.bottomLeft.dy / heightPercentage, + ); final Offset endBubbleOffset = offset.translate( - textWord.bounds.bottomRight.dx / heightPercentage, - textWord.bounds.bottomRight.dy / heightPercentage); + textWord.bounds.bottomRight.dx / heightPercentage, + textWord.bounds.bottomRight.dy / heightPercentage, + ); _drawStartBubble(canvas, bubblePaint, startBubbleOffset); _drawEndBubble(canvas, bubblePaint, endBubbleOffset); _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( - localToGlobal(Offset( - textWord.bounds.topLeft.dx / heightPercentage, - textWord.bounds.topLeft.dy / heightPercentage)), - localToGlobal(Offset( - textWord.bounds.bottomRight.dx / heightPercentage, - textWord.bounds.bottomRight.dy / heightPercentage))); - _textSelectionHelper.firstSelectedGlyph = textWord.glyphs.first; + localToGlobal( + Offset( + textWord.bounds.topLeft.dx / heightPercentage, + textWord.bounds.topLeft.dy / heightPercentage, + ), + ), + localToGlobal( + Offset( + textWord.bounds.bottomRight.dx / heightPercentage, + textWord.bounds.bottomRight.dy / heightPercentage, + ), + ), + ); + final bool isRTLGlyph = intl.Bidi.detectRtlDirectionality( + textWord.text, + ); + _textSelectionHelper.firstSelectedGlyph = + isRTLGlyph ? textWord.glyphs.last : textWord.glyphs.first; _textSelectionHelper.selectionEnabled = true; _textSelectionHelper.selectedTextLines.clear(); - _textSelectionHelper.selectedTextLines.add(PdfTextLine( - textWord.bounds, textWord.text, _textSelectionHelper.viewId!)); + _textSelectionHelper.selectedTextLines.add( + PdfTextLine( + textWord.bounds, + textWord.text, + _textSelectionHelper.viewId! + 1, + ), + ); _ensureHistoryEntry(); _textSelectionHelper.startIndex = textLineIndex; _textSelectionHelper.endIndex = textLineIndex; @@ -1494,101 +2768,210 @@ class CanvasRenderBox extends RenderBox { if (_dragDetails != null && _textSelectionHelper.textLines != null) { _findStartAndEndIndex(_dragDetails!, heightPercentage, false); } - if (_startBubbleDragging) { - for (int textLineIndex = _textSelectionHelper.startIndex; - textLineIndex <= _textSelectionHelper.endIndex; - textLineIndex++) { + if (_isRTLText ? _endBubbleDragging : _startBubbleDragging) { + for ( + int textLineIndex = _textSelectionHelper.startIndex; + textLineIndex <= _textSelectionHelper.endIndex; + textLineIndex++ + ) { final TextLine line = _textSelectionHelper.textLines![textLineIndex]; - if (_dragDetails != null && - _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) { - _textSelectionHelper.startBubbleLine = - _textSelectionHelper.endBubbleLine; - _textSelectionHelper.startBubbleY = - _textSelectionHelper.endBubbleLine!.bounds.bottom; + if (!_isRTLText) { + if (_dragDetails != null && + _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) { + _textSelectionHelper.startBubbleLine = + _textSelectionHelper.endBubbleLine; + _textSelectionHelper.startBubbleY = + _textSelectionHelper.endBubbleLine!.bounds.bottom; + } + } else { + if (_dragDetails != null && + _dragDetails!.dy <= + _textSelectionHelper.endBubbleY! / heightPercentage && + _dragDetails!.dy >= line.bounds.top / heightPercentage) { + _textSelectionHelper.endBubbleLine = line; + _textSelectionHelper.endBubbleY = line.bounds.bottomLeft.dy; + } else if (_dragDetails != null && + _dragDetails!.dy >= + _textSelectionHelper.endBubbleY! / heightPercentage) { + _textSelectionHelper.endBubbleLine = line; + _textSelectionHelper.endBubbleY = line.bounds.bottom; + } } - for (int wordIndex = 0; - wordIndex < - _textSelectionHelper.startBubbleLine!.wordCollection.length; - wordIndex++) { + for ( + int wordIndex = 0; + wordIndex < + (_isRTLText + ? _textSelectionHelper.endBubbleLine!.wordCollection.length + : _textSelectionHelper + .startBubbleLine! + .wordCollection + .length); + wordIndex++ + ) { final TextWord textWord = - _textSelectionHelper.startBubbleLine!.wordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { + _isRTLText + ? _textSelectionHelper + .endBubbleLine! + .wordCollection[wordIndex] + : _textSelectionHelper + .startBubbleLine! + .wordCollection[wordIndex]; + for ( + int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++ + ) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (_startBubbleTapX >= (textGlyph.bounds.bottomLeft.dx / heightPercentage) && + !_isRTLText && _startBubbleTapX <= (textGlyph.bounds.bottomRight.dx / heightPercentage)) { _textSelectionHelper.startBubbleX = textGlyph.bounds.bottomLeft.dx; _textSelectionHelper.firstSelectedGlyph = textGlyph; + } else if (_endBubbleTapX >= + (textGlyph.bounds.bottomLeft.dx / heightPercentage) && + _isRTLText && + _endBubbleTapX <= + (textGlyph.bounds.bottomRight.dx / heightPercentage)) { + _textSelectionHelper.endBubbleX = + textGlyph.bounds.bottomRight.dx; + _textSelectionHelper.firstSelectedGlyph = textGlyph; } } } - if (_startBubbleTapX < - (_textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx / - heightPercentage)) { - _textSelectionHelper.startBubbleX = - _textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx; - _textSelectionHelper.firstSelectedGlyph = _textSelectionHelper - .startBubbleLine!.wordCollection.first.glyphs.first; - } - if (_startBubbleTapX >= - (_textSelectionHelper.startBubbleLine!.bounds.bottomRight.dx / - heightPercentage)) { - _textSelectionHelper.startBubbleX = _textSelectionHelper - .startBubbleLine! - .wordCollection - .last - .glyphs - .last - .bounds - .bottomLeft - .dx; - _textSelectionHelper.firstSelectedGlyph = _textSelectionHelper - .startBubbleLine!.wordCollection.last.glyphs.last; - } - if (_textSelectionHelper.startBubbleLine!.bounds.bottom / - heightPercentage == - _textSelectionHelper.endBubbleLine!.bounds.bottom / - heightPercentage && - _startBubbleTapX >= _endBubbleTapX) { - for (int wordIndex = 0; + if (!_isRTLText) { + if (_startBubbleTapX < + (_textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx / + heightPercentage)) { + _textSelectionHelper.startBubbleX = + _textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx; + _textSelectionHelper.firstSelectedGlyph = + _textSelectionHelper + .startBubbleLine! + .wordCollection + .first + .glyphs + .first; + } + if (_startBubbleTapX >= + (_textSelectionHelper.startBubbleLine!.bounds.bottomRight.dx / + heightPercentage)) { + _textSelectionHelper.startBubbleX = + _textSelectionHelper + .startBubbleLine! + .wordCollection + .last + .glyphs + .last + .bounds + .bottomLeft + .dx; + _textSelectionHelper.firstSelectedGlyph = + _textSelectionHelper + .startBubbleLine! + .wordCollection + .last + .glyphs + .last; + } + if (_textSelectionHelper.startBubbleLine!.bounds.bottom / + heightPercentage == + _textSelectionHelper.endBubbleLine!.bounds.bottom / + heightPercentage && + _startBubbleTapX >= _endBubbleTapX) { + for ( + int wordIndex = 0; wordIndex < _textSelectionHelper.startBubbleLine!.wordCollection.length; - wordIndex++) { - final TextWord textWord = _textSelectionHelper - .startBubbleLine!.wordCollection[wordIndex]; - for (int glyphIndex = 0; + wordIndex++ + ) { + final TextWord textWord = + _textSelectionHelper + .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.startBubbleX = - textGlyph.bounds.bottomLeft.dx; - _textSelectionHelper.firstSelectedGlyph = textGlyph; - break; + glyphIndex++ + ) { + final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; + if (textGlyph.bounds.bottomRight.dx / heightPercentage == + _textSelectionHelper.endBubbleX! / heightPercentage) { + _textSelectionHelper.startBubbleX = + textGlyph.bounds.bottomLeft.dx; + _textSelectionHelper.firstSelectedGlyph = textGlyph; + break; + } } } } + } else { + if (_textSelectionHelper.endBubbleLine == + _textSelectionHelper.startBubbleLine) { + if (_textSelectionHelper.endBubbleX! <= + _textSelectionHelper.startBubbleX!) { + _textSelectionHelper.endBubbleX = + _textSelectionHelper.startBubbleX! + 1.0; + } + } + if (_textSelectionHelper.endBubbleLine != + _textSelectionHelper.startBubbleLine) { + if (_endBubbleTapX < + (_textSelectionHelper.endBubbleLine!.bounds.bottomLeft.dx / + heightPercentage)) { + _textSelectionHelper.endBubbleX = + _textSelectionHelper.endBubbleLine!.bounds.bottomLeft.dx; + _textSelectionHelper.firstSelectedGlyph = + _textSelectionHelper + .endBubbleLine! + .wordCollection + .first + .glyphs + .first; + } + if (_endBubbleTapX >= + (_textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx / + heightPercentage)) { + _textSelectionHelper.endBubbleX = + _textSelectionHelper + .endBubbleLine! + .wordCollection + .last + .glyphs + .last + .bounds + .bottomLeft + .dx; + _textSelectionHelper.firstSelectedGlyph = + _textSelectionHelper + .endBubbleLine! + .wordCollection + .last + .glyphs + .last; + } + } } } - } else if (_endBubbleDragging) { - for (int textLineIndex = _textSelectionHelper.startIndex; - textLineIndex <= _textSelectionHelper.endIndex; - textLineIndex++) { + } else if (_isRTLText ? _startBubbleDragging : _endBubbleDragging) { + for ( + int textLineIndex = _textSelectionHelper.startIndex; + textLineIndex <= _textSelectionHelper.endIndex; + textLineIndex++ + ) { final TextLine line = _textSelectionHelper.textLines![textLineIndex]; if (_dragDetails != null && + !_isRTLText && _dragDetails!.dy >= (_textSelectionHelper.startBubbleLine!.bounds.top / heightPercentage) && @@ -1596,69 +2979,154 @@ class CanvasRenderBox extends RenderBox { _textSelectionHelper.endBubbleLine = line; _textSelectionHelper.endBubbleY = line.bounds.bottomRight.dy; } - for (int wordIndex = 0; - wordIndex < - _textSelectionHelper.endBubbleLine!.wordCollection.length; - wordIndex++) { + if (_dragDetails != null && + _isRTLText && + _dragDetails!.dy >= + (_textSelectionHelper.startBubbleLine!.bounds.top / + heightPercentage) && + _dragDetails!.dy >= (line.bounds.topLeft.dy / heightPercentage)) { + _textSelectionHelper.startBubbleLine = line; + _textSelectionHelper.startBubbleY = line.bounds.bottomRight.dy; + } else if (_dragDetails != null && + _isRTLText && + _dragDetails!.dy <= + (_textSelectionHelper.startBubbleLine!.bounds.bottom / + heightPercentage)) { + _textSelectionHelper.startBubbleLine = line; + _textSelectionHelper.startBubbleY = line.bounds.bottom; + } + for ( + int wordIndex = 0; + wordIndex < + (_isRTLText + ? _textSelectionHelper + .startBubbleLine! + .wordCollection + .length + : _textSelectionHelper + .endBubbleLine! + .wordCollection + .length); + wordIndex++ + ) { final TextWord textWord = - _textSelectionHelper.endBubbleLine!.wordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { + _isRTLText + ? _textSelectionHelper + .startBubbleLine! + .wordCollection[wordIndex] + : _textSelectionHelper + .endBubbleLine! + .wordCollection[wordIndex]; + for ( + int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++ + ) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; - if (_endBubbleTapX >= + if (!_isRTLText && + _endBubbleTapX >= (textGlyph.bounds.bottomLeft.dx / heightPercentage) && _endBubbleTapX <= (textGlyph.bounds.bottomRight.dx / heightPercentage)) { _textSelectionHelper.endBubbleX = textGlyph.bounds.bottomRight.dx; + } else if (_isRTLText && + _startBubbleTapX >= + (textGlyph.bounds.bottomLeft.dx / heightPercentage) && + _startBubbleTapX <= + (textGlyph.bounds.bottomRight.dx / heightPercentage)) { + _textSelectionHelper.startBubbleX = + textGlyph.bounds.bottomRight.dx; } } } - if (_endBubbleTapX.floor() > - (_textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx / - heightPercentage) - .floor()) { - _textSelectionHelper.endBubbleX = - _textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx; - } - if (_endBubbleTapX.floor() <= - (_textSelectionHelper.endBubbleLine!.bounds.bottomLeft.dx / - heightPercentage) - .floor()) { - _textSelectionHelper.endBubbleX = _textSelectionHelper - .endBubbleLine! - .wordCollection - .first - .glyphs - .first - .bounds - .bottomRight - .dx; - } - if (_textSelectionHelper.endBubbleLine!.bounds.bottom / - heightPercentage == - _textSelectionHelper.startBubbleLine!.bounds.bottom / - heightPercentage && - _endBubbleTapX < _startBubbleTapX) { - for (int wordIndex = 0; + if (!_isRTLText) { + if (_endBubbleTapX.floor() > + (_textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx / + heightPercentage) + .floor()) { + _textSelectionHelper.endBubbleX = + _textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx; + } + if (_endBubbleTapX.floor() <= + (_textSelectionHelper.endBubbleLine!.bounds.bottomLeft.dx / + heightPercentage) + .floor()) { + _textSelectionHelper.endBubbleX = + _textSelectionHelper + .endBubbleLine! + .wordCollection + .first + .glyphs + .first + .bounds + .bottomRight + .dx; + } + if (_textSelectionHelper.endBubbleLine!.bounds.bottom / + heightPercentage == + _textSelectionHelper.startBubbleLine!.bounds.bottom / + heightPercentage && + _endBubbleTapX < _startBubbleTapX) { + for ( + int wordIndex = 0; wordIndex < _textSelectionHelper.endBubbleLine!.wordCollection.length; - wordIndex++) { - final TextWord textWord = - _textSelectionHelper.endBubbleLine!.wordCollection[wordIndex]; - for (int glyphIndex = 0; + wordIndex++ + ) { + final TextWord textWord = + _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.endBubbleX = - textGlyph.bounds.bottomRight.dx; - break; + glyphIndex++ + ) { + final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; + if (textGlyph.bounds.bottomLeft.dx / heightPercentage == + _textSelectionHelper.startBubbleX! / heightPercentage) { + _textSelectionHelper.endBubbleX = + textGlyph.bounds.bottomRight.dx; + break; + } } } } + } else { + if (_textSelectionHelper.startBubbleLine == + _textSelectionHelper.endBubbleLine) { + if (_textSelectionHelper.startBubbleX! >= + _textSelectionHelper.endBubbleX!) { + _textSelectionHelper.startBubbleX = + _textSelectionHelper.endBubbleX! - 1.0; + } + } + if (_textSelectionHelper.startBubbleLine != + _textSelectionHelper.endBubbleLine) { + if (_startBubbleTapX.floor() > + (_textSelectionHelper.startBubbleLine!.bounds.bottomRight.dx / + heightPercentage) + .floor()) { + _textSelectionHelper.startBubbleX = + _textSelectionHelper.startBubbleLine!.bounds.bottomRight.dx; + } + if (_startBubbleTapX.floor() <= + (_textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx / + heightPercentage) + .floor()) { + _textSelectionHelper.startBubbleX = + _textSelectionHelper + .startBubbleLine! + .wordCollection + .first + .glyphs + .first + .bounds + .bottomRight + .dx; + } + } } } } 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 f98ff3007..2c55bfc67 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; +import '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; -/// Height of the ScrollHead. -const double kPdfScrollHeadHeight = 32.0; +/// Size of the ScrollHead. +const double kPdfScrollHeadSize = 36.0; /// A material design scroll head. /// @@ -15,14 +16,16 @@ const double kPdfScrollHeadHeight = 32.0; class ScrollHead extends StatefulWidget { /// Constructor for ScrollHead. const ScrollHead( - this.canShowHorizontalScrollBar, - this.canShowVerticalScrollBar, - this.scrollHeadOffset, - this.pdfViewerController, - this.isMobileWebView, - this.scrollDirection, - this.isBookmarkViewOpen, - this.pageLayoutMode); + this.canShowHorizontalScrollBar, + this.canShowVerticalScrollBar, + this.scrollHeadOffset, + this.pdfViewerController, + this.isMobileWebView, + this.scrollDirection, + this.isBookmarkViewOpen, + this.pageLayoutMode, { + super.key, + }); /// Position of the [ScrollHead] in [SfPdfViewer]. final Offset scrollHeadOffset; @@ -56,21 +59,33 @@ class ScrollHead extends StatefulWidget { /// State for [ScrollHead] class _ScrollHeadState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; + bool _isMaterial3 = false; @override void didChangeDependencies() { + _isMaterial3 = Theme.of(context).useMaterial3; _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + _isMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); super.didChangeDependencies(); } @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; super.dispose(); } Widget _createScrollBar( - bool visible, Alignment alignment, EdgeInsets edgeInsets, Size size) { + bool visible, + Alignment alignment, + EdgeInsets edgeInsets, + Size size, + ) { return Visibility( visible: visible, child: Container( @@ -79,11 +94,7 @@ class _ScrollHeadState extends State { child: Material( color: Colors.grey, borderRadius: const BorderRadius.all(Radius.circular(7.0)), - child: Container( - constraints: BoxConstraints.tight( - size, - ), - ), + child: Container(constraints: BoxConstraints.tight(size)), ), ), ); @@ -93,32 +104,32 @@ class _ScrollHeadState extends State { Widget build(BuildContext context) { if (kIsDesktop) { final Widget verticalScrollBar = _createScrollBar( - widget.canShowVerticalScrollBar, - Alignment.topRight, - EdgeInsets.only(top: widget.scrollHeadOffset.dy), - const Size(10.0, 54.0)); + widget.canShowVerticalScrollBar, + Alignment.topRight, + EdgeInsets.only(top: widget.scrollHeadOffset.dy), + const Size(10.0, 54.0), + ); final Widget horizontalScrollBar = _createScrollBar( - widget.canShowHorizontalScrollBar, - Alignment.bottomLeft, - EdgeInsets.only(left: widget.scrollHeadOffset.dx), - const Size(54.0, 10.0)); + widget.canShowHorizontalScrollBar, + Alignment.bottomLeft, + EdgeInsets.only(left: widget.scrollHeadOffset.dx), + const Size(54.0, 10.0), + ); if (widget.scrollDirection == PdfScrollDirection.horizontal && widget.pageLayoutMode != PdfPageLayoutMode.single) { return Stack( - children: [verticalScrollBar, horizontalScrollBar]); - } else if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + children: [verticalScrollBar, horizontalScrollBar], + ); + } else if (widget.scrollDirection == PdfScrollDirection.horizontal && + widget.pageLayoutMode == PdfPageLayoutMode.single) { return horizontalScrollBar; } else { return verticalScrollBar; } } const List boxShadows = [ - BoxShadow( - color: Color.fromRGBO(0, 0, 0, 0.14), - blurRadius: 2, - offset: Offset.zero, - ), + BoxShadow(color: Color.fromRGBO(0, 0, 0, 0.14), blurRadius: 2), BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.12), blurRadius: 2, @@ -137,13 +148,13 @@ class _ScrollHeadState extends State { final BorderRadius borderRadius = widget.scrollDirection == PdfScrollDirection.horizontal ? const BorderRadius.only( - topRight: Radius.circular(kPdfScrollHeadHeight), - topLeft: Radius.circular(kPdfScrollHeadHeight), - ) + topRight: Radius.circular(kPdfScrollHeadSize), + topLeft: Radius.circular(kPdfScrollHeadSize), + ) : const BorderRadius.only( - topLeft: Radius.circular(kPdfScrollHeadHeight), - bottomLeft: Radius.circular(kPdfScrollHeadHeight), - ); + topLeft: Radius.circular(kPdfScrollHeadSize), + bottomLeft: Radius.circular(kPdfScrollHeadSize), + ); final Alignment alignment = widget.scrollDirection == PdfScrollDirection.horizontal ? Alignment.bottomLeft @@ -158,12 +169,15 @@ class _ScrollHeadState extends State { container: true, button: true, child: Align( - alignment: widget.scrollDirection == PdfScrollDirection.horizontal - ? Alignment.bottomCenter - : Alignment.centerRight, + alignment: + widget.scrollDirection == PdfScrollDirection.horizontal + ? Alignment.bottomCenter + : Alignment.centerRight, child: Container( decoration: BoxDecoration( - color: _pdfViewerThemeData!.scrollHeadStyle?.backgroundColor ?? + color: + _pdfViewerThemeData!.scrollHeadStyle?.backgroundColor ?? + _effectiveThemeData!.scrollHeadStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? const Color(0xFFFAFAFA) @@ -172,22 +186,29 @@ class _ScrollHeadState extends State { boxShadow: boxShadows, ), constraints: const BoxConstraints.tightFor( - width: kPdfScrollHeadHeight, height: kPdfScrollHeadHeight), + width: kPdfScrollHeadSize, + height: kPdfScrollHeadSize, + ), child: Align( - alignment: Alignment.center, child: Text( '${widget.pdfViewerController.pageNumber}', - style: _pdfViewerThemeData! - .scrollHeadStyle?.pageNumberTextStyle ?? - TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.87)), - semanticsLabel: widget.isBookmarkViewOpen - ? '' - : widget.pdfViewerController.pageNumber.toString(), + style: Theme.of(context).textTheme.bodySmall! + .copyWith( + fontSize: _isMaterial3 ? 14 : 12, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .scrollHeadStyle + ?.pageNumberTextStyle, + ), + semanticsLabel: + widget.isBookmarkViewOpen + ? '' + : widget.pdfViewerController.pageNumber.toString(), ), ), ), 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 ca3f6976e..6e49ca08e 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 @@ -4,13 +4,14 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/interactive_scroll_viewer_internal.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.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:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_head.dart'; +import '../../pdfviewer.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; +import 'desktop_scrollbar.dart'; +import 'pdf_page_view.dart'; +import 'pdf_scrollable.dart'; +import 'scroll_head.dart'; import 'scroll_status.dart'; /// Height of the scroll head. @@ -27,34 +28,36 @@ const double _kPdfPaginationTextFieldWidth = 328.0; class ScrollHeadOverlay extends StatefulWidget { /// Constructor for ScrollHeadOverlay. const ScrollHeadOverlay( - this.canShowPaginationDialog, - this.canShowScrollStatus, - this.canShowScrollHead, - this.pdfViewerController, - this.isMobileWebView, - this.pdfDimension, - this.totalImageSize, - this.viewportDimension, - this.currentOffset, - this.maxScale, - this.minScale, - this.onDoubleTapZoomInvoked, - this.enableDoubleTapZooming, - this.interactionMode, - this.scaleEnabled, - this.maxPdfPageWidth, - this.pdfPages, - this.scrollDirection, - this.isBookmarkViewOpen, - this.child, - {Key? key, - this.transformationController, - this.onInteractionStart, - this.onInteractionUpdate, - this.onInteractionEnd, - this.onPdfOffsetChanged, - this.isPanEnabled = true}) - : super(key: key); + this.canShowPaginationDialog, + this.canShowScrollStatus, + this.canShowScrollHead, + this.pdfViewerController, + this.isMobileWebView, + this.pdfDimension, + this.totalImageSize, + this.viewportDimension, + this.currentOffset, + this.maxScale, + this.minScale, + this.onDoubleTapZoomInvoked, + this.enableDoubleTapZooming, + this.interactionMode, + this.scaleEnabled, + this.maxPdfPageWidth, + this.pdfPages, + this.scrollDirection, + this.isBookmarkViewOpen, + this.textDirection, + this.child, { + Key? key, + this.transformationController, + this.onInteractionStart, + this.onInteractionUpdate, + this.onInteractionEnd, + this.onPdfOffsetChanged, + this.initiateTileRendering, + this.isPanEnabled = true, + }) : super(key: key); /// Indicates whether page navigation dialog must be shown or not. final bool canShowPaginationDialog; @@ -136,6 +139,12 @@ class ScrollHeadOverlay extends StatefulWidget { /// opened or not. final bool isBookmarkViewOpen; + /// Direction of text flow. + final TextDirection textDirection; + + /// Callback to initiate tile rendering. + final VoidCallback? initiateTileRendering; + @override ScrollHeadOverlayState createState() => ScrollHeadOverlayState(); } @@ -144,12 +153,13 @@ class ScrollHeadOverlay extends StatefulWidget { class ScrollHeadOverlayState extends State { final TextEditingController _textFieldController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); - final FocusNode _focusNode = FocusNode(); SfPdfViewerThemeData? _pdfViewerThemeData; - ThemeData? _themeData; + SfPdfViewerThemeData? _effectiveThemeData; + late ThemeData _themeData; SfLocalizations? _localizations; final GlobalKey _childKey = GlobalKey(); Timer? _scrollTimer; + EdgeInsets _boundaryMargin = EdgeInsets.zero; /// Indicates whether the user interaction has ended. bool _isInteractionEnded = true; @@ -157,6 +167,9 @@ class ScrollHeadOverlayState extends State { /// Indicates whether the user scrolls continuously. bool isScrolled = false; + /// Focus node for page navigation dialogue. + final FocusNode _focusNode = FocusNode(); + double _scale = 1; /// Scroll head y position. @@ -176,6 +189,10 @@ class ScrollHeadOverlayState extends State { @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _themeData = Theme.of(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); @@ -184,6 +201,7 @@ class ScrollHeadOverlayState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; _focusNode.dispose(); _scrollTimer?.cancel(); @@ -197,8 +215,8 @@ class ScrollHeadOverlayState extends State { widget.transformationController!.value.getMaxScaleOnAxis(); final double pdfPageHeight = widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height; - final double totalPageOffset = widget - .pdfPages[widget.pdfViewerController.pageCount]!.pageOffset + + final double totalPageOffset = + widget.pdfPages[widget.pdfViewerController.pageCount]!.pageOffset + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width; if (widget.pdfViewerController.zoomLevel <= 1) { //check if the total page offset less than viewport width in horizontal scroll direction @@ -212,45 +230,50 @@ class ScrollHeadOverlayState extends State { if (kIsDesktop && !widget.isMobileWebView) { if (widget.viewportDimension.width < widget.maxPdfPageWidth * widget.pdfViewerController.zoomLevel) { - final double clampedX = tapPosition.dx > widget.maxPdfPageWidth - ? ((widget.maxPdfPageWidth * 2) - - widget.viewportDimension.width) / - 2 - : 0; + final double clampedX = + tapPosition.dx > widget.maxPdfPageWidth + ? ((widget.maxPdfPageWidth * 2) - + widget.viewportDimension.width) / + 2 + : 0; offset = Offset( - (widget.scrollDirection == PdfScrollDirection.vertical) - ? clampedX - : offset.dx, - offset.dy); + (widget.scrollDirection == PdfScrollDirection.vertical) + ? clampedX + : offset.dx, + offset.dy, + ); } } } - final double widthFactor = (widget.pdfDimension.width) - + final double widthFactor = + (widget.pdfDimension.width) - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); if (widget.viewportDimension.height > pdfPageHeight && (widget.scrollDirection == PdfScrollDirection.horizontal || (widget.pdfViewerController.pageCount == 1 && widget.scrollDirection == PdfScrollDirection.vertical))) { offset = Offset( - (widget.scrollDirection == PdfScrollDirection.vertical) - ? offset.dx.clamp(-widthFactor, widthFactor.abs()) - : offset.dx, - ((tapPosition.dy > widget.viewportDimension.height / 2) - ? offset.dy + - (widget.viewportDimension.height - - widget - .pdfPages[ - widget.pdfViewerController.pageNumber]! - .pageSize - .height) / - 2 - : offset.dy / 2) - .clamp( - 0, - ((widget.viewportDimension.height - + (widget.scrollDirection == PdfScrollDirection.vertical) + ? offset.dx.clamp(-widthFactor, widthFactor.abs()) + : offset.dx, + ((tapPosition.dy > widget.viewportDimension.height / 2) + ? offset.dy + + (widget.viewportDimension.height - + widget + .pdfPages[widget + .pdfViewerController + .pageNumber]! + .pageSize + .height) / + 2 + : offset.dy / 2) + .clamp( + 0, + (((widget.viewportDimension.height - widget .pdfPages[widget - .pdfViewerController.pageNumber]! + .pdfViewerController + .pageNumber]! .pageSize .height) / 2 + @@ -258,67 +281,70 @@ class ScrollHeadOverlayState extends State { .pdfPages[widget.pdfViewerController.pageNumber]! .pageSize .height) / - 2)); + 2) + .abs(), + ), + ); } else { if ((widget.viewportDimension.width > totalPageOffset) && (widget.pdfDimension.width >= widget.viewportDimension.width)) { offset = Offset( - (offset.dx - (widget.viewportDimension.width - totalPageOffset)) - .clamp( - 0, - (offset.dx - - (widget.viewportDimension.width - totalPageOffset)) - .abs()), - offset.dy); + (offset.dx - (widget.viewportDimension.width - totalPageOffset)) + .clamp( + 0, + (offset.dx - (widget.viewportDimension.width - totalPageOffset)) + .abs(), + ), + offset.dy, + ); } offset = Offset( - offset.dx, - offset.dy.clamp( - 0, - (widget.pdfDimension.height - - (widget.viewportDimension.height / - widget.pdfViewerController.zoomLevel)) - .abs())); + offset.dx, + offset.dy.clamp( + 0, + (widget.pdfDimension.height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + .abs(), + ), + ); } + widget.initiateTileRendering?.call(); return offset; } @override Widget build(BuildContext context) { - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _updateScrollHeadPosition(); }); - // ignore: avoid_bool_literals_in_conditional_expressions - final bool _enableDoubleTapZoom = ((!kIsDesktop && - widget.enableDoubleTapZooming) || - (kIsDesktop && widget.interactionMode == PdfInteractionMode.pan) || - (kIsDesktop && - widget.isMobileWebView && - widget.enableDoubleTapZooming)) - ? true - : false; - final Widget scrollable = InteractiveScrollViewer( - widget.child, - minScale: widget.minScale, - maxScale: widget.maxScale, - onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, - transformationController: widget.transformationController, - key: _childKey, - enableDoubleTapZooming: _enableDoubleTapZoom, - // ignore: avoid_bool_literals_in_conditional_expressions - scaleEnabled: ((kIsDesktop && widget.isMobileWebView) || - !kIsDesktop || - (kIsDesktop && widget.scaleEnabled)) - ? true - : false, - panEnabled: widget.isPanEnabled, - onInteractionStart: _handleInteractionStart, - onInteractionUpdate: _handleInteractionChanged, - onInteractionEnd: _handleInteractionEnd, - constrained: false, + final bool enableDoubleTapZoom = + (!kIsDesktop && widget.enableDoubleTapZooming) || + (kIsDesktop && widget.interactionMode == PdfInteractionMode.pan) || + (kIsDesktop && widget.isMobileWebView && widget.enableDoubleTapZooming); + final Widget scrollable = Directionality( + textDirection: widget.textDirection, + child: InteractiveScrollViewer( + widget.child, + minScale: widget.minScale, + maxScale: widget.maxScale, + onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, + transformationController: widget.transformationController, + key: _childKey, + boundaryMargin: _boundaryMargin, + enableDoubleTapZooming: enableDoubleTapZoom, + scaleEnabled: !kIsDesktop || (kIsDesktop && widget.scaleEnabled), + panEnabled: widget.isPanEnabled, + onInteractionStart: _handleInteractionStart, + onInteractionUpdate: _handleInteractionChanged, + onInteractionEnd: _handleInteractionEnd, + constrained: false, + ), + ); + final Offset scrollHeadOffset = Offset( + _scrollHeadPositionX, + _scrollHeadPositionY, ); - final Offset scrollHeadOffset = - Offset(_scrollHeadPositionX, _scrollHeadPositionY); final bool hasBiggerWidth = widget.totalImageSize.width > widget.viewportDimension.width; final bool hasBiggerHeight = @@ -335,56 +361,127 @@ class ScrollHeadOverlayState extends State { return Stack( children: [ scrollable, - GestureDetector( - onVerticalDragStart: (DragStartDetails details) { - _handleScrollHeadDragStart(details, true); - }, - onVerticalDragUpdate: _handleVerticalScrollHeadDragUpdate, - onVerticalDragEnd: _handleScrollHeadDragEnd, - onHorizontalDragStart: - (widget.scrollDirection == PdfScrollDirection.horizontal) - ? (DragStartDetails details) { - _handleScrollHeadDragStart(details, false); - } - : null, - onHorizontalDragUpdate: - (widget.scrollDirection == PdfScrollDirection.horizontal) - ? _handleScrollHeadDragUpdate - : null, - onHorizontalDragEnd: - (widget.scrollDirection == PdfScrollDirection.horizontal) - ? _handleScrollHeadDragEnd - : null, - onTap: () { - if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { - _textFieldController.clear(); - if (!FocusScope.of(context).hasPrimaryFocus) { - FocusScope.of(context).unfocus(); - } - if (widget.canShowPaginationDialog) { - _showPaginationDialog(); - } - } - }, - child: Visibility( - visible: canShowScrollHead, - child: ScrollHead( - hasBiggerWidth, - hasBiggerHeight, - scrollHeadOffset, - widget.pdfViewerController, - widget.isMobileWebView, - widget.scrollDirection, - widget.isBookmarkViewOpen, - PdfPageLayoutMode.continuous)), - ), + if (kIsDesktop) + _buildDesktopScrollbar(context) + else + _buildScrollhead( + context, + canShowScrollHead, + hasBiggerWidth, + hasBiggerHeight, + scrollHeadOffset, + ), Visibility( - visible: isScrollHeadDragged && widget.canShowScrollStatus, - child: ScrollStatus(widget.pdfViewerController)), + visible: isScrollHeadDragged && widget.canShowScrollStatus, + child: ScrollStatus(widget.pdfViewerController), + ), ], ); } + /// Builds the scrollhead for mobile platforms. + Widget _buildScrollhead( + BuildContext context, + bool canShowScrollHead, + bool hasBiggerWidth, + bool hasBiggerHeight, + Offset scrollHeadOffset, + ) { + return GestureDetector( + onVerticalDragStart: (DragStartDetails details) { + _handleScrollHeadDragStart(details, true); + }, + onVerticalDragUpdate: _handleVerticalScrollHeadDragUpdate, + onVerticalDragEnd: _handleScrollHeadDragEnd, + onHorizontalDragStart: + (widget.scrollDirection == PdfScrollDirection.horizontal) + ? (DragStartDetails details) { + _handleScrollHeadDragStart(details, false); + } + : null, + onHorizontalDragUpdate: + (widget.scrollDirection == PdfScrollDirection.horizontal) + ? _handleScrollHeadDragUpdate + : null, + onHorizontalDragEnd: + (widget.scrollDirection == PdfScrollDirection.horizontal) + ? _handleScrollHeadDragEnd + : null, + onTap: () { + if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { + _textFieldController.clear(); + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); + } + if (widget.canShowPaginationDialog) { + _clearSelection(); + _showPaginationDialog(); + } + } + }, + child: Visibility( + visible: canShowScrollHead, + child: ScrollHead( + hasBiggerWidth, + hasBiggerHeight, + scrollHeadOffset, + widget.pdfViewerController, + widget.isMobileWebView, + widget.scrollDirection, + widget.isBookmarkViewOpen, + PdfPageLayoutMode.continuous, + ), + ), + ); + } + + /// Builds the scrollbar for desktopa and mobile webview platforms. + Widget _buildDesktopScrollbar(BuildContext context) { + return GestureDetector( + onTap: () { + if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { + _textFieldController.clear(); + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); + } + if (widget.canShowPaginationDialog) { + _clearSelection(); + _showPaginationDialog(); + } + } + }, + child: DesktopScrollbar( + controller: widget.transformationController!, + viewportSize: widget.viewportDimension, + contentSize: widget.pdfDimension, + canFadeHorizontalScrollbar: + widget.scrollDirection == PdfScrollDirection.vertical, + canFadeVerticalScrollbar: + widget.scrollDirection == PdfScrollDirection.horizontal, + onVerticalDragStart: (DragStartDetails details) { + _handleScrollHeadDragStart(details, true); + }, + onVerticalDragEnd: _handleScrollHeadDragEnd, + onVerticalDragUpdate: (_) { + final Offset currentOffset = widget.transformationController!.toScene( + Offset.zero, + ); + widget.onPdfOffsetChanged!(currentOffset); + }, + onHorizontalDragStart: (DragStartDetails details) { + _handleScrollHeadDragStart(details, false); + }, + onHorizontalDragEnd: _handleScrollHeadDragEnd, + onHorizontalDragUpdate: (_) { + final Offset currentOffset = widget.transformationController!.toScene( + Offset.zero, + ); + widget.onPdfOffsetChanged!(currentOffset); + }, + ), + ); + } + /// Clears the Text Selection. Future _clearSelection() async { return widget.pdfViewerController.clearSelection(); @@ -392,128 +489,280 @@ class ScrollHeadOverlayState extends State { /// Show the pagination dialog box Future _showPaginationDialog() async { - await _clearSelection(); + final bool isMaterial3 = Theme.of(context).useMaterial3; return showDialog( - context: context, - barrierDismissible: true, - builder: (BuildContext context) { - final Orientation orientation = MediaQuery.of(context).orientation; - return AlertDialog( + context: context, + builder: (BuildContext context) { + final Orientation orientation = MediaQuery.of(context).orientation; + return Directionality( + textDirection: widget.textDirection, + child: AlertDialog( scrollable: true, insetPadding: EdgeInsets.zero, - contentPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(24) - : const EdgeInsets.only(top: 0, right: 24, left: 24, bottom: 0), - buttonPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(8) - : const EdgeInsets.all(4), - backgroundColor: _pdfViewerThemeData!.backgroundColor ?? + contentPadding: + isMaterial3 + ? const EdgeInsets.only( + left: 24.0, + right: 24.0, + bottom: 24.0, + ) + : orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.only(right: 24, left: 24), + buttonPadding: + orientation == Orientation.portrait + ? const EdgeInsets.all(8) + : const EdgeInsets.all(4), + backgroundColor: + _pdfViewerThemeData!.paginationDialogStyle?.backgroundColor ?? + _effectiveThemeData!.paginationDialogStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF424242)), - title: Text( - _localizations!.pdfGoToPageLabel, - style: _pdfViewerThemeData! - .paginationDialogStyle?.headerTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 20, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.onSurface.withOpacity(0.87), + shape: + isMaterial3 + ? null + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _localizations!.pdfGoToPageLabel, + style: Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: isMaterial3 ? 24 : 20, + color: + isMaterial3 + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .paginationDialogStyle + ?.headerTextStyle, + ), + ), + if (isMaterial3) + IconButton( + icon: const Icon(Icons.clear), + iconSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + onPressed: () { + _textFieldController.clear(); + Navigator.of(context).pop(); + }, ), + ], + ), + content: SingleChildScrollView( + child: Column( + children: [ + if (isMaterial3) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '1 - ${widget.pdfViewerController.pageCount}', + style: Theme.of( + context, + ).textTheme.bodyLarge!.copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w400 : null, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ), + _paginationTextField(context), + ], + ), ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), - content: - SingleChildScrollView(child: _paginationTextField(context)), actions: [ TextButton( onPressed: () { _textFieldController.clear(); Navigator.of(context).pop(); }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, child: Text( _localizations!.pdfPaginationDialogCancelLabel, - style: _pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle?.color == - null - ? _pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle! - .copyWith(color: _themeData!.colorScheme.primary) - : _pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500), + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: _themeData.colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .paginationDialogStyle + ?.cancelTextStyle, + ), ), ), TextButton( onPressed: () { _handlePageNumberValidation(); }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, child: Text( _localizations!.pdfPaginationDialogOkLabel, - style: _pdfViewerThemeData! - .paginationDialogStyle?.okTextStyle!.color == - null - ? _pdfViewerThemeData!.paginationDialogStyle?.okTextStyle! - .copyWith(color: _themeData!.colorScheme.primary) - : _pdfViewerThemeData! - .paginationDialogStyle?.okTextStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500), + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: _themeData.colorScheme.primary, + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.okTextStyle, + ), ), - ) + ), ], - ); - }); + ), + ); + }, + ); } /// A material design Text field for pagination dialog box. Widget _paginationTextField(BuildContext context) { + final bool isMaterial3 = Theme.of(context).useMaterial3; return Form( key: _formKey, child: SizedBox( - width: _kPdfPaginationTextFieldWidth, + width: isMaterial3 ? 312.0 : _kPdfPaginationTextFieldWidth, child: TextFormField( - style: _pdfViewerThemeData! - .paginationDialogStyle?.inputFieldTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - color: _themeData!.colorScheme.onSurface.withOpacity(0.87)), + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.inputFieldTextStyle, + ), focusNode: _focusNode, decoration: InputDecoration( isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: _themeData!.colorScheme.primary), - ), - contentPadding: const EdgeInsets.symmetric(vertical: 6), + border: + isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _themeData.colorScheme.primary, + ), + ) + : null, + errorBorder: + isMaterial3 + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(3.5), + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + _themeData.colorScheme.error, + ), + ) + : null, + focusedBorder: + isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _themeData.colorScheme.primary, + width: 2, + ), + ) + : UnderlineInputBorder( + borderSide: BorderSide( + color: _themeData.colorScheme.primary, + ), + ), + contentPadding: + isMaterial3 + ? const EdgeInsets.all(16) + : const EdgeInsets.symmetric(vertical: 6), hintText: _localizations!.pdfEnterPageNumberLabel, - hintStyle: _pdfViewerThemeData! - .paginationDialogStyle?.hintTextStyle ?? - (TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - color: - _themeData!.colorScheme.onSurface.withOpacity(0.38))), + hintStyle: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.hintTextStyle, + ), counterText: - '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', - counterStyle: _pdfViewerThemeData! - .paginationDialogStyle?.pageInfoTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 12, - color: _themeData!.colorScheme.onSurface.withOpacity(0.6)), - errorStyle: _pdfViewerThemeData! - .paginationDialogStyle?.validationTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 12, - color: _themeData!.colorScheme.error), + isMaterial3 + ? null + : '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', + counterStyle: Theme.of(context).textTheme.bodySmall! + .copyWith( + fontSize: 12, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.pageInfoTextStyle, + ), + errorStyle: Theme.of(context).textTheme.bodySmall! + .copyWith( + fontSize: 12, + fontWeight: FontWeight.w400, + color: _themeData.colorScheme.error, + ) + .merge( + _pdfViewerThemeData! + .paginationDialogStyle + ?.validationTextStyle, + ), ), keyboardType: TextInputType.number, enableInteractiveSelection: false, @@ -556,13 +805,17 @@ class ScrollHeadOverlayState extends State { /// updates UI when scroll head drag is started. void _handleScrollHeadDragStart( - DragStartDetails details, bool isVerticalDrag) { + DragStartDetails details, + bool isVerticalDrag, + ) { _isInteractionEnded = false; - if (widget.scrollDirection == PdfScrollDirection.horizontal && - isVerticalDrag) { - isScrollHeadDragged = false; + if (widget.pdfViewerController.pageCount > 1) { + isScrollHeadDragged = + widget.scrollDirection == PdfScrollDirection.horizontal + ? !isVerticalDrag + : isVerticalDrag; } else { - isScrollHeadDragged = true; + isScrollHeadDragged = false; } } @@ -571,23 +824,30 @@ class ScrollHeadOverlayState extends State { _isInteractionEnded = false; if (!widget.viewportDimension.isEmpty) { final double dragOffset = details.delta.dx + _scrollHeadPositionX; - final double scrollHeadPosition = widget.viewportDimension.width - + final double scrollHeadPosition = + widget.viewportDimension.width - (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); if (dragOffset < scrollHeadPosition && dragOffset >= 0) { - widget.onPdfOffsetChanged!(Offset( + widget.onPdfOffsetChanged!( + Offset( (widget.pdfDimension.width - (widget.viewportDimension.width / _scale)) * (dragOffset / scrollHeadPosition), - widget.currentOffset.dy)); + widget.currentOffset.dy, + ), + ); _scrollHeadPositionX = dragOffset; } else { if (dragOffset < 0) { widget.onPdfOffsetChanged!(Offset(0, widget.currentOffset.dy)); } else { - widget.onPdfOffsetChanged!(Offset( + widget.onPdfOffsetChanged!( + Offset( widget.pdfDimension.width - (widget.viewportDimension.width / _scale), - widget.currentOffset.dy)); + widget.currentOffset.dy, + ), + ); } } } @@ -597,23 +857,30 @@ class ScrollHeadOverlayState extends State { void _handleVerticalScrollHeadDragUpdate(DragUpdateDetails details) { if (!widget.viewportDimension.isEmpty) { final double dragOffset = details.delta.dy + _scrollHeadPositionY; - final double scrollHeadPosition = widget.viewportDimension.height - + final double scrollHeadPosition = + widget.viewportDimension.height - (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); if (dragOffset < scrollHeadPosition && dragOffset >= 0) { - widget.onPdfOffsetChanged!(Offset( + widget.onPdfOffsetChanged!( + Offset( widget.currentOffset.dx, (widget.pdfDimension.height - (widget.viewportDimension.height / _scale)) * - (dragOffset / scrollHeadPosition))); + (dragOffset / scrollHeadPosition), + ), + ); _scrollHeadPositionY = dragOffset; } else { if (dragOffset < 0) { widget.onPdfOffsetChanged!(Offset(widget.currentOffset.dx, 0)); } else { - widget.onPdfOffsetChanged!(Offset( + widget.onPdfOffsetChanged!( + Offset( widget.currentOffset.dx, widget.pdfDimension.height - - (widget.viewportDimension.height / _scale))); + (widget.viewportDimension.height / _scale), + ), + ); } } } @@ -623,6 +890,7 @@ class ScrollHeadOverlayState extends State { void _handleScrollHeadDragEnd(DragEndDetails details) { _isInteractionEnded = true; isScrollHeadDragged = false; + widget.initiateTileRendering?.call(); } /// handles interaction start. @@ -636,6 +904,27 @@ class ScrollHeadOverlayState extends State { if (details.scale != 1) { _isInteractionEnded = false; } + if (details.scale < 1 && widget.pdfViewerController.zoomLevel > 1) { + final double verticalMargin = + widget.totalImageSize.height < widget.viewportDimension.height + ? (widget.viewportDimension.height - + widget.totalImageSize.height) / + 2 + : 0; + final double horizontalMargin = + widget.totalImageSize.width < widget.viewportDimension.width + ? (widget.viewportDimension.width - widget.totalImageSize.width) / + 2 + : 0; + _boundaryMargin = EdgeInsets.only( + top: verticalMargin, + bottom: verticalMargin, + left: horizontalMargin, + right: horizontalMargin, + ); + } else { + _boundaryMargin = EdgeInsets.zero; + } widget.onInteractionUpdate?.call(details); } @@ -643,47 +932,58 @@ class ScrollHeadOverlayState extends State { void _handleInteractionEnd(ScaleEndDetails details) { _isInteractionEnded = true; widget.onInteractionEnd?.call(details); - _scrollTimer = Timer( - const Duration(milliseconds: 2000), - () { - isScrolled = false; - }, - ); + _scrollTimer?.cancel(); + _scrollTimer = Timer(const Duration(milliseconds: 100), () { + isScrolled = false; + }); } /// updates the scroll head position based on the interaction results. void _updateScrollHeadPosition() { - if (widget.pdfDimension.height > 0 && widget.viewportDimension.height > 0) { + if (widget.pdfDimension.height > 0 && + widget.pdfDimension.width > 0 && + widget.viewportDimension.height > 0 && + widget.viewportDimension.width > 0) { _scale = widget.transformationController!.value.getMaxScaleOnAxis(); final double currentOffsetX = widget.transformationController!.toScene(Offset.zero).dx; final double currentOffsetY = widget.transformationController!.toScene(Offset.zero).dy; - final double scrollPercentX = currentOffsetX.abs() / + final double scrollPercentX = + currentOffsetX.abs() / (widget.pdfDimension.width - (widget.viewportDimension.width / _scale)); - final double scrollPercentY = currentOffsetY.abs() / + final double scrollPercentY = + currentOffsetY.abs() / (widget.pdfDimension.height - (widget.viewportDimension.height / _scale)); - final double scrollHeadMaxExtentX = widget.viewportDimension.width - + final double scrollHeadMaxExtentX = + widget.viewportDimension.width - (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); - final double scrollHeadMaxExtentY = widget.viewportDimension.height - + final double scrollHeadMaxExtentY = + widget.viewportDimension.height - (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); - final double newPositionX = (scrollPercentX * scrollHeadMaxExtentX) - .clamp(1, scrollHeadMaxExtentX); - final double newPositionY = (scrollPercentY * scrollHeadMaxExtentY) - .clamp(1, scrollHeadMaxExtentY); + final double newPositionX = (scrollPercentX * scrollHeadMaxExtentX).clamp( + 1, + scrollHeadMaxExtentX, + ); + final double newPositionY = (scrollPercentY * scrollHeadMaxExtentY).clamp( + 1, + scrollHeadMaxExtentY, + ); if (newPositionX.round() != _scrollHeadPositionX.round() && _isInteractionEnded) { _scrollHeadPositionX = newPositionX; widget.onPdfOffsetChanged!( - widget.transformationController!.toScene(Offset.zero)); + widget.transformationController!.toScene(Offset.zero), + ); } if (newPositionY.round() != _scrollHeadPositionY.round() && _isInteractionEnded) { _scrollHeadPositionY = newPositionY; widget.onPdfOffsetChanged!( - widget.transformationController!.toScene(Offset.zero)); + widget.transformationController!.toScene(Offset.zero), + ); } } } 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 2b0d88116..08b3db775 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import '../../pdfviewer.dart'; +import '../theme/theme.dart'; /// Bottom position of the [ScrollStatus] widget. const double _kPdfScrollStatusBottomPosition = 25.0; @@ -10,7 +11,11 @@ const double _kPdfScrollStatusBottomPosition = 25.0; @immutable class ScrollStatus extends StatefulWidget { /// Constructs the Scroll status for PdfViewer Widget - const ScrollStatus(this.pdfViewerController, {this.isSinglePageView = false}); + const ScrollStatus( + this.pdfViewerController, { + super.key, + this.isSinglePageView = false, + }); /// PdfViewer controller of PdfViewer final PdfViewerController pdfViewerController; @@ -25,11 +30,16 @@ class ScrollStatus extends StatefulWidget { /// State for [ScrollStatus] class _ScrollStatusState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -37,6 +47,7 @@ class _ScrollStatusState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; super.dispose(); } @@ -52,28 +63,42 @@ class _ScrollStatusState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - padding: - const EdgeInsets.only(left: 16, top: 6, right: 16, bottom: 6), + padding: const EdgeInsets.only( + left: 16, + top: 6, + right: 16, + bottom: 6, + ), constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.7, ), decoration: BoxDecoration( color: _pdfViewerThemeData!.scrollStatusStyle?.backgroundColor ?? - const Color(0xFF757575), - borderRadius: const BorderRadius.all( - Radius.circular(16.0), - ), + _effectiveThemeData!.scrollStatusStyle?.backgroundColor ?? + const Color(0xFF757575), + borderRadius: + Theme.of(context).useMaterial3 + ? const BorderRadius.all(Radius.circular(4.0)) + : const BorderRadius.all(Radius.circular(16.0)), ), child: Text( '${widget.pdfViewerController.pageNumber} ${_localizations!.pdfScrollStatusOfLabel} ${widget.pdfViewerController.pageCount}', textAlign: TextAlign.center, - style: - _pdfViewerThemeData!.scrollStatusStyle?.pageInfoTextStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - color: Colors.white), + textDirection: TextDirection.ltr, + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: Theme.of(context).useMaterial3 ? 14 : 16, + color: Colors.white, + ) + .merge( + _pdfViewerThemeData! + .scrollStatusStyle + ?.pageInfoTextStyle ?? + _effectiveThemeData! + .scrollStatusStyle + ?.pageInfoTextStyle, + ), ), ), ], diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart index 427fcd3a4..3c42ec67a 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart @@ -1,53 +1,76 @@ +import 'dart:math'; import 'dart:ui'; + +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/interactive_scroll_viewer_internal.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.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:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_head.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_status.dart'; +import 'package:vector_math/vector_math_64.dart' as vector; + +import '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; +import 'desktop_scrollbar.dart'; +import 'pdf_page_view.dart'; +import 'pdf_scrollable.dart'; +import 'scroll_head.dart'; +import 'scroll_status.dart'; /// Signature for [SfPdfViewer.onPageChanged] callback. typedef PageChangedCallback = void Function(int newPage); -/// Height of the scroll head. -const double _kPdfScrollHeadHeight = 48.0; +/// Size of the scroll head. +const double _kPdfScrollHeadSize = 48.0; /// Height of the pagination text field. const double _kPdfPaginationTextFieldWidth = 328.0; +/// A constant threshold value that determines how fast a swipe should be trigger a page change +const double _kSwipeThreshold = 700; + +const double _scrollbarPadding = 0.5; +const double _defaultDesktopScrollbarWidth = 10; + /// SinglePageView class for PdfViewer. @immutable class SinglePageView extends StatefulWidget { /// Constructor for PdfScrollable. const SinglePageView( - Key key, - this.pdfViewerController, - this.pageController, - this.onPageChanged, - this.interactionUpdate, - this.viewportDimension, - this.canShowPaginationDialog, - this.canShowScrollHead, - this.canShowScrollStatus, - this.pdfPages, - this.isMobileWebView, - this.enableDoubleTapZooming, - this.interactionMode, - this.scaleEnabled, - this.onZoomLevelChanged, - this.onDoubleTap, - this.onPdfOffsetChanged, - this.isBookmarkViewOpen, - this.children) - : super(key: key); + Key key, + this.pdfViewerController, + this.transformationController, + this.pageController, + this.onPageChanged, + this.interactionUpdate, + this.viewportDimension, + this.visibleViewportDimension, + this.maxZoomLevel, + this.canShowPaginationDialog, + this.canShowScrollHead, + this.canShowScrollStatus, + this.pdfPages, + this.isMobileWebView, + this.enableDoubleTapZooming, + this.interactionMode, + this.scaleEnabled, + this.onZoomLevelChanged, + this.onDoubleTap, + this.onPdfOffsetChanged, + this.isBookmarkViewOpen, + this.textDirection, + this.isTablet, + this.scrollDirection, + this.onInteractionEnd, + this.children, + ) : super(key: key); /// PdfViewer controller of PdfViewer. final PdfViewerController pdfViewerController; + /// Transformation controller of PdfViewer. + final TransformationControllerExt transformationController; + /// Page controller of the PdfViewer. final PageController pageController; @@ -69,6 +92,12 @@ class SinglePageView extends StatefulWidget { /// Viewport dimension of PdfViewer. final Size viewportDimension; + /// Viewport dimension of PdfViewer without keyboard height. + final Size? visibleViewportDimension; + + /// Represents the maximum zoom level + final double maxZoomLevel; + /// Indicates whether page navigation dialog must be shown or not. final bool canShowPaginationDialog; @@ -100,6 +129,18 @@ class SinglePageView extends StatefulWidget { /// opened or not. final bool isBookmarkViewOpen; + ///A direction of text flow. + final TextDirection textDirection; + + /// Indicates whether the current environment is running in Tablet + final bool isTablet; + + /// Represents the scroll direction + final PdfScrollDirection scrollDirection; + + /// Triggered when interaction end. + final VoidCallback? onInteractionEnd; + @override SinglePageViewState createState() => SinglePageViewState(); } @@ -107,27 +148,29 @@ class SinglePageView extends StatefulWidget { /// SinglePageView state class. class SinglePageViewState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; double _scrollHeadPosition = 0; - bool _canScroll = false; - bool _isOverFlowed = false; bool _setZoomLevel = false; - double _paddingWidthScale = 0; - double _paddingHeightScale = 0; - Offset _currentOffsetOfInteractionUpdate = Offset.zero; - TransformationController _transformationController = - TransformationController(); - bool _canPanOnZoom = false; final TextEditingController _textFieldController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); final FocusNode _focusNode = FocusNode(); Size _oldLayoutSize = Size.zero; - double _topMargin = 0, _leftMargin = 0; double _panStartOffset = 0.0; double _panUpdateOffset = 0.0; bool _canJumpPrevious = false; bool _canJumpNext = false; - bool _isMousePointer = false; + bool _canShowHorizontalScrollBar = true; + bool _canShowVerticalScrollBar = false; + Offset _scrollHeadOffset = Offset.zero; + bool _goToNextPage = false; + bool _goToPreviousPage = false; + bool _isZoomChanged = false; + bool _isPageChangedOnScroll = false; + Size? _previousVisibleViewportDimension; + + /// Number of touches currently active on the screen + int _fingersInteracting = 0; /// If true , when API jump is enable bool isJumpOnZoomedDocument = false; @@ -150,57 +193,80 @@ class SinglePageViewState extends State { /// Current offset of single page view Offset currentOffset = Offset.zero; + /// Offset value at the start of the interaction + Offset _offsetWhenInteractionStarts = Offset.zero; + + /// The current page number + int _currentPageNumber = 1; + + /// Transformation controller for the previous page (the page immediately before the current page in view) + TransformationControllerExt _previousPageTransformationController = + TransformationControllerExt(); + + /// Transformation controller for the current page (the page currently in view) + TransformationControllerExt _currentPageTransformationController = + TransformationControllerExt(); + + /// Transformation controller for the next page (the page immediately following the current page) + TransformationControllerExt _nextPageTransformationController = + TransformationControllerExt(); + @override void initState() { super.initState(); + _currentPageTransformationController.addListener(_onTransformationChanged); + } + + @override + void didUpdateWidget(covariant SinglePageView oldWidget) { + if (oldWidget.viewportDimension != widget.viewportDimension) { + _previousPageTransformationController.viewSize = + _currentPageTransformationController.viewSize = + _nextPageTransformationController.viewSize = + widget.viewportDimension; + } + if (widget.pdfPages[widget.pdfViewerController.pageNumber] != null) { + _currentPageTransformationController.contentSize = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; + } + super.didUpdateWidget(oldWidget); } @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = + Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); + _previousPageTransformationController.viewSize = + _currentPageTransformationController.viewSize = + _nextPageTransformationController.viewSize = + widget.viewportDimension; + if (widget.pdfPages[widget.pdfViewerController.pageNumber] != null) { + _currentPageTransformationController.contentSize = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; + } super.didChangeDependencies(); } @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; _focusNode.dispose(); + _currentPageTransformationController.removeListener( + _onTransformationChanged, + ); super.dispose(); } - Size _getChildSize(Size viewportDimension) { - double widthFactor = 1.0, heightFactor = 1.0; - widthFactor = _paddingWidthScale == 0 - ? widget.pdfViewerController.zoomLevel - : _paddingWidthScale; - heightFactor = _paddingHeightScale == 0 - ? widget.pdfViewerController.zoomLevel - : _paddingHeightScale; - final double zoomLevel = - _transformationController.value.getMaxScaleOnAxis(); - final double imageWidth = widget.pdfPages.isNotEmpty - ? widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .width * - zoomLevel - : 0; - final double childWidth = viewportDimension.width > imageWidth - ? viewportDimension.width / widthFactor.clamp(1, 3) - : imageWidth / widthFactor.clamp(1, 3); - final double imageHeight = widget.pdfPages.isNotEmpty - ? widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .height * - zoomLevel - : 0; - double childHeight = viewportDimension.height > imageHeight - ? viewportDimension.height / heightFactor.clamp(1, 3) - : imageHeight / heightFactor.clamp(1, 3); - if (childHeight > viewportDimension.height) { - childHeight = widget.viewportDimension.height; - } - - return Size(childWidth, childHeight); + /// Callback to assign the value to the transformation controller + void _onTransformationChanged() { + widget.transformationController.value = + _currentPageTransformationController.value.clone(); } ///Jump to the desired offset. @@ -208,11 +274,15 @@ class SinglePageViewState extends State { xOffset ??= 0.0; yOffset ??= 0.0; _handlePdfOffsetChanged(Offset(xOffset, yOffset)); + _changePage(isMouseWheel: true); + widget.onInteractionEnd?.call(); } /// Handles PDF offset changed and updates the matrix translation based on it. void _handlePdfOffsetChanged(Offset offset) { - final Offset currentOffset = _transformationController.toScene(Offset.zero); + final Offset currentOffset = _currentPageTransformationController.toScene( + Offset.zero, + ); final Size pdfDimension = widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; if (pdfDimension.height != 0) { @@ -225,41 +295,55 @@ class SinglePageViewState extends State { pdfDimension.height * widget.pdfViewerController.zoomLevel) { offset = Offset(offset.dx, 0); } - final double widthFactor = pdfDimension.width - + final double widthFactor = + pdfDimension.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); if (isJumpOnZoomedDocument) { final double actualMargin = greyAreaSize / 2; bool skipY = false; - final double pageHeight = widget - .pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height; + final double pageHeight = + widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height; if (widget.pdfViewerController.zoomLevel > 1 && pageHeight * widget.pdfViewerController.zoomLevel < widget.viewportDimension.height) { skipY = true; } offset = Offset( - offset.dx.clamp( - _setZoomLevel == true ? -widthFactor : 0, widthFactor.abs()), - skipY - ? 0 - : offset.dy.clamp( - actualMargin, - ((pdfDimension.height - - (widget.viewportDimension.height / - widget.pdfViewerController.zoomLevel)) + - actualMargin) - .abs())); + offset.dx.clamp( + _setZoomLevel == true ? -widthFactor : 0, + widthFactor.abs(), + ), + skipY + ? 0 + : offset.dy.clamp( + actualMargin, + ((pdfDimension.height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + + actualMargin) + .abs(), + ), + ); } else { offset = Offset( - offset.dx.clamp( - _setZoomLevel == true ? -widthFactor : 0, widthFactor.abs()), - offset.dy.clamp( - 0, - (pdfDimension.height - - (widget.viewportDimension.height / - widget.pdfViewerController.zoomLevel)) - .abs())); + offset.dx.clamp( + _setZoomLevel == true ? -widthFactor : 0, + widthFactor.abs(), + ), + offset.dy.clamp( + 0, + (pdfDimension.height - + ((widget.visibleViewportDimension != null + ? widget.visibleViewportDimension!.height + : widget.viewportDimension.height) / + widget.pdfViewerController.zoomLevel)) + .abs(), + ), + ); } _setZoomLevel = false; if (kIsDesktop && !widget.isMobileWebView) { @@ -278,313 +362,368 @@ class SinglePageViewState extends State { offset = Offset(0, offset.dy); } } else { - final double greyAreaWidthSize = widget.viewportDimension.width - - (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + final double greyAreaWidthSize = + widget.viewportDimension.width - + (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize .width); offset = Offset(greyAreaWidthSize / 2 + offset.dx, offset.dy); } } - final Offset previousOffset = - _transformationController.toScene(Offset.zero); - setState(() { - _transformationController.value.translate( - previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); - }); + final Offset previousOffset = _currentPageTransformationController + .toScene(Offset.zero); + _currentPageTransformationController.value = + _currentPageTransformationController.value.clone()..translate( + previousOffset.dx - offset.dx, + previousOffset.dy - offset.dy, + ); } - widget.onPdfOffsetChanged! - .call(_transformationController.toScene(Offset.zero)); + widget.onPdfOffsetChanged!.call( + _currentPageTransformationController.toScene(Offset.zero), + ); + } + + /// Get the current state of the transformation controller based on the specified page number. + TransformationControllerExt? _getController(int pageNumber) { + final int currentPageNumber = widget.pdfViewerController.pageNumber; + if (pageNumber == currentPageNumber) { + return _currentPageTransformationController; // Current page's transformation controller + } else if (pageNumber == currentPageNumber + 1) { + return _nextPageTransformationController; // Next page's transformation controller + } else if (pageNumber == currentPageNumber - 1) { + return _previousPageTransformationController; // Previous page's transformation controller + } else { + return null; // The requested page is not currently cached for viewing + } + } + + /// Rotate and assign the transformation controllers to the InteractiveViewer when navigating between pages. + /// The function reorganizes the transformation controllers depending on whether the page navigation is forward or backward. + void _rotateAndAssignControllers(int newPageNumber) { + if (newPageNumber > _currentPageNumber) { + // User navigated to the next page + _currentPageTransformationController.removeListener( + _onTransformationChanged, + ); + + // Shift the controllers forward: current becomes previous, next becomes current + _previousPageTransformationController = + _currentPageTransformationController; + _currentPageTransformationController = _nextPageTransformationController; + _currentPageTransformationController.addListener( + _onTransformationChanged, + ); + + // Prepare the transformation controller for the new next page + if (widget.pdfPages[newPageNumber + 1] != null) { + _nextPageTransformationController = + TransformationControllerExt() + ..viewSize = _oldLayoutSize + ..contentSize = widget.pdfPages[newPageNumber + 1]!.pageSize; + } + } else if (newPageNumber < _currentPageNumber) { + // User navigated to the previous page + _currentPageTransformationController.removeListener( + _onTransformationChanged, + ); + + // Shift the controllers backward: current becomes next, previous becomes current + _nextPageTransformationController = _currentPageTransformationController; + _currentPageTransformationController = + _previousPageTransformationController; + _currentPageTransformationController.addListener( + _onTransformationChanged, + ); + + // Prepare the transformation controller for the new previous page + if (widget.pdfPages[newPageNumber - 1] != null) { + _previousPageTransformationController = + TransformationControllerExt() + ..viewSize = _oldLayoutSize + ..contentSize = widget.pdfPages[newPageNumber - 1]!.pageSize; + } + } + + // Reset transformation states for the next and previous controllers to default + _nextPageTransformationController.value = Matrix4.identity(); + _previousPageTransformationController.value = Matrix4.identity(); } @override Widget build(BuildContext context) { - final Size childSize = _getChildSize(widget.viewportDimension); - currentOffset = _transformationController.toScene(Offset.zero); - // ignore: avoid_bool_literals_in_conditional_expressions - final bool _enableDoubleTapZoom = ((!kIsDesktop && - widget.enableDoubleTapZooming) || - (kIsDesktop && widget.interactionMode == PdfInteractionMode.pan) || - (kIsDesktop && - widget.isMobileWebView && - widget.enableDoubleTapZooming)) - ? true - : false; + currentOffset = _currentPageTransformationController.toScene(Offset.zero); + final bool enableDoubleTapZoom = + (!kIsDesktop && widget.enableDoubleTapZooming) || + (kIsDesktop && widget.interactionMode == PdfInteractionMode.pan) || + (kIsDesktop && widget.isMobileWebView && widget.enableDoubleTapZooming); final List pages = []; - for (int pageIndex = 0; pageIndex < widget.children.length; pageIndex++) { - final Widget page = widget.children[pageIndex]; - final bool isLandscape = - MediaQuery.of(context).orientation == Orientation.landscape; - double imageSize = widget.pdfPages[pageIndex + 1]!.pageSize.height * - widget.pdfViewerController.zoomLevel; - _topMargin = (widget.pdfPages[pageIndex + 1]!.pageSize.height - - widget.viewportDimension.height) / - 2; - greyAreaSize = widget.viewportDimension.height - - (widget.pdfPages[pageIndex + 1]!.pageSize.height); - bool isHeightFitted = false; - if (_topMargin == 0) { - isHeightFitted = true; - _leftMargin = (widget.pdfPages[pageIndex + 1]!.pageSize.width - - widget.viewportDimension.width) / - 2; - imageSize = widget.pdfPages[pageIndex + 1]!.pageSize.width * - double.parse( - widget.pdfViewerController.zoomLevel.toStringAsFixed(1)) - .round(); - } - pages.add(InteractiveScrollViewer( - SizedBox( - height: isLandscape && !kIsDesktop - ? childSize.height - : widget.viewportDimension.height, - width: - isLandscape ? childSize.width : widget.viewportDimension.width, - child: Center(child: page)), - clipBehavior: Clip.none, - boundaryMargin: EdgeInsets.only( - top: isHeightFitted || isLandscape - ? 0 - : (imageSize.round() <= widget.viewportDimension.height.round() - ? (childSize.height - widget.viewportDimension.height) / 2 - : _topMargin), - bottom: isHeightFitted || isLandscape - ? 0 - : (imageSize.round() <= widget.viewportDimension.height.round() - ? (childSize.height - widget.viewportDimension.height) / 2 - : _topMargin), - right: isHeightFitted - ? (imageSize <= widget.viewportDimension.width ? 0 : _leftMargin) - : 0, - left: isHeightFitted - ? (imageSize <= widget.viewportDimension.width ? 0 : _leftMargin) - : 0, - ), - constrained: false, - panEnabled: true, - onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, - // ignore: avoid_bool_literals_in_conditional_expressions - scaleEnabled: ((kIsDesktop && widget.isMobileWebView) || - !kIsDesktop || - (kIsDesktop && widget.scaleEnabled)) - ? true - : false, - enableDoubleTapZooming: _enableDoubleTapZoom, - transformationController: _transformationController, - onInteractionStart: (ScaleStartDetails details) { - _panStartOffset = details.localFocalPoint.dx; - if (!kIsDesktop || - (kIsDesktop && widget.isMobileWebView) || - (kIsDesktop && widget.scaleEnabled)) { - if (previousZoomLevel != _oldPreviousZoomLevel) { - _oldPreviousZoomLevel = previousZoomLevel; - } - previousZoomLevel = widget.pdfViewerController.zoomLevel; - } - _paddingWidthScale = 0; - _paddingHeightScale = 0; - }, - onInteractionUpdate: (ScaleUpdateDetails details) { - if (widget.interactionMode == PdfInteractionMode.pan) { - _panUpdateOffset = details.localFocalPoint.dx; - if (_panStartOffset != _panUpdateOffset) { - if (_panStartOffset < details.localFocalPoint.dx) { - _canJumpPrevious = true; - } else { - _canJumpNext = true; + if (widget.pdfPages.isNotEmpty) { + for (int pageIndex = 0; pageIndex < widget.children.length; pageIndex++) { + final Widget page = widget.children[pageIndex]; + greyAreaSize = + widget.viewportDimension.height - + (widget.pdfPages[pageIndex + 1]!.pageSize.height); + pages.add( + InteractiveScrollViewer( + page, + clipBehavior: Clip.none, + maxScale: widget.maxZoomLevel, + // When using infinite boundary margins, we can transform the content in the InteractiveViewer to any location. + // To restrict the content to the center of the viewport, we use the Matrix4 value. + boundaryMargin: const EdgeInsets.all(double.infinity), + constrained: false, + onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, + scaleEnabled: !kIsDesktop || (kIsDesktop && widget.scaleEnabled), + enableDoubleTapZooming: enableDoubleTapZoom, + transformationController: _getController(pageIndex + 1), + onInteractionStart: (ScaleStartDetails details) { + _panStartOffset = + widget.scrollDirection == PdfScrollDirection.horizontal + ? details.localFocalPoint.dx + : details.localFocalPoint.dy; + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + if (previousZoomLevel != _oldPreviousZoomLevel) { + _oldPreviousZoomLevel = previousZoomLevel; + } + previousZoomLevel = widget.pdfViewerController.zoomLevel; } - } - } - _currentOffsetOfInteractionUpdate = - _transformationController.toScene(Offset.zero); - if (!kIsDesktop || - (kIsDesktop && widget.isMobileWebView) || - (kIsDesktop && widget.scaleEnabled)) { - widget.interactionUpdate( - _transformationController.value.getMaxScaleOnAxis()); - } - final double currentScale = - _transformationController.value.getMaxScaleOnAxis(); - if (details.scale <= 1) { - if (((kIsDesktop && !widget.isMobileWebView) || - (widget.viewportDimension.width > - widget.viewportDimension.height)) && - widget.viewportDimension.width.round() >= - (widget.pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize.width * - currentScale) - .round()) { - setState(() { - _paddingWidthScale = details.scale * currentScale; - }); - } - if (widget.viewportDimension.height.round() >= - (widget.pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize.height * - _transformationController.value.getMaxScaleOnAxis()) - .round()) { - setState(() { - _paddingHeightScale = (details.scale) * currentScale; - }); - } - } - if (details.scale == 1) { - if (_transformationController - .toScene(Offset(widget.viewportDimension.width, 0)) - .dx - .round() + - _leftMargin.abs().round() >= - widget.viewportDimension.width || - _transformationController.toScene(Offset.zero).dx.round() <= - _leftMargin.abs().round()) { - _canPanOnZoom = true; - } else { - _canScroll = true; - } - } else { - _canScroll = true; - } - widget.onPdfOffsetChanged! - .call(_transformationController.toScene(Offset.zero)); - }, - onInteractionEnd: (ScaleEndDetails details) { - if (widget.interactionMode == PdfInteractionMode.pan) { - final double pannedDistance = - (_panStartOffset - _panUpdateOffset).abs(); - if (pannedDistance > 300) { - if (_canJumpPrevious && - widget.pdfViewerController.pageNumber != 1) { - widget.pageController.animateToPage( - widget.pdfViewerController.pageNumber - 2, - duration: const Duration(milliseconds: 500), - curve: Curves.ease); - } else if (_canJumpNext && - widget.pdfViewerController.pageNumber != - widget.pdfViewerController.pageCount) { - widget.pageController.animateToPage( - widget.pdfViewerController.pageNumber, - duration: const Duration(milliseconds: 500), - curve: Curves.ease); + _offsetWhenInteractionStarts = + _currentPageTransformationController.toScene(Offset.zero); + }, + onInteractionUpdate: (ScaleUpdateDetails details) { + _panUpdateOffset = + widget.scrollDirection == PdfScrollDirection.horizontal + ? details.localFocalPoint.dx + : details.localFocalPoint.dy; + if (_panStartOffset != _panUpdateOffset) { + if (_panStartOffset < _panUpdateOffset) { + _canJumpPrevious = true; + } else { + _canJumpNext = true; + } } - _canJumpPrevious = false; - _canJumpNext = false; - } else { - if (_canJumpPrevious) { - widget.pageController.animateTo( - widget.pageController.offset - 10, - duration: const Duration(milliseconds: 100), - curve: Curves.ease); - } else if (_canJumpNext) { - widget.pageController.animateTo( - widget.pageController.offset + 10, - duration: const Duration(milliseconds: 100), - curve: Curves.ease); + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + widget.interactionUpdate( + _currentPageTransformationController.value + .getMaxScaleOnAxis(), + ); } - } - } - if (!kIsDesktop || - (kIsDesktop && widget.isMobileWebView) || - (kIsDesktop && widget.scaleEnabled)) { - widget.onZoomLevelChanged( - _transformationController.value.getMaxScaleOnAxis()); - } - currentOffset = _transformationController.toScene(Offset.zero); - if (_canScroll) { - _canPanOnZoom = false; - _canScroll = false; - } - _paddingWidthScale = 0; - _paddingHeightScale = 0; - if (widget.viewportDimension.width > - widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .width * - _transformationController.value.getMaxScaleOnAxis()) { - setState(() { - if (!_isMousePointer) { - _transformationController.value - .translate(_currentOffsetOfInteractionUpdate.dx); - _isOverFlowed = false; + + _isZoomChanged = details.scale != 1; + + widget.onPdfOffsetChanged!.call( + _currentPageTransformationController.toScene(Offset.zero), + ); + }, + onInteractionEnd: (ScaleEndDetails details) { + if (widget.interactionMode == PdfInteractionMode.pan) { + final double pannedDistance = + (_panStartOffset - _panUpdateOffset).abs(); + if (pannedDistance > + 300 * widget.pdfViewerController.zoomLevel) { + if (_canJumpPrevious && + widget.pdfViewerController.pageNumber != 1) { + widget.pageController.previousPage( + duration: const Duration(milliseconds: 500), + curve: Curves.ease, + ); + } else if (_canJumpNext && + widget.pdfViewerController.pageNumber != + widget.pdfViewerController.pageCount) { + widget.pageController.nextPage( + duration: const Duration(milliseconds: 500), + curve: Curves.ease, + ); + } + _canJumpPrevious = false; + _canJumpNext = false; + } } - }); - } else { - if (kIsDesktop && !widget.isMobileWebView) { - /// Invoked when pdf pages width greater viewport width - if (_isOverFlowed == false) { - _transformationController.value - .translate(_currentOffsetOfInteractionUpdate.dx); - _isOverFlowed = true; + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + widget.onZoomLevelChanged( + _currentPageTransformationController.value + .getMaxScaleOnAxis(), + ); } - } - } - _isMousePointer = false; - setState(() {}); - }, - )); + currentOffset = _currentPageTransformationController.toScene( + Offset.zero, + ); + _changePage(isMouseWheel: false, velocity: details.velocity); + + _canJumpPrevious = false; + _canJumpNext = false; + _isZoomChanged = false; + widget.onInteractionEnd?.call(); + }, + ), + ); + } + } + _scrollHeadPosition = + widget.pdfViewerController.pageNumber == 1 + ? 0 + : widget.scrollDirection == PdfScrollDirection.horizontal + ? (widget.pdfViewerController.pageNumber / + widget.pdfViewerController.pageCount) * + (widget.viewportDimension.width - _kPdfScrollHeadSize) + : (widget.pdfViewerController.pageNumber / + widget.pdfViewerController.pageCount) * + (widget.viewportDimension.height - _kPdfScrollHeadSize); + + if (widget.scrollDirection == PdfScrollDirection.horizontal) { + _canShowHorizontalScrollBar = true; + _canShowVerticalScrollBar = false; + _scrollHeadOffset = Offset( + _scrollHeadPosition, + widget.viewportDimension.height, + ); + } else { + _canShowHorizontalScrollBar = false; + _canShowVerticalScrollBar = true; + _scrollHeadOffset = Offset( + widget.viewportDimension.width, + _scrollHeadPosition, + ); } - _scrollHeadPosition = widget.pdfViewerController.pageNumber == 1 - ? 0 - : (widget.pdfViewerController.pageNumber / - widget.pdfViewerController.pageCount) * - (widget.viewportDimension.width - _kPdfScrollHeadHeight); + if (widget.visibleViewportDimension == null && + _previousVisibleViewportDimension != null && + currentOffset.dy.round() > + (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height - + widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel) + .round()) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + // Reset the offset when the keyboard is closed. + _handlePdfOffsetChanged(currentOffset); + }); + } + _previousVisibleViewportDimension = widget.visibleViewportDimension; return Stack( children: [ LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (_oldLayoutSize != constraints.biggest) { - final Offset previousOffset = - _transformationController.toScene(Offset.zero); - double yPosition = !_oldLayoutSize.isEmpty - ? previousOffset.dy / _oldLayoutSize.height - : 0; - final double greyArea = widget - .pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize - .height - - constraints.biggest.height; - yPosition = yPosition * constraints.biggest.height; - if (!greyArea.isNegative && - greyArea < - (constraints.biggest.height / - widget.pdfViewerController.zoomLevel)) { - yPosition = - yPosition.clamp(0, (constraints.biggest.height) - greyArea); - } - double xPosition = !_oldLayoutSize.isEmpty - ? previousOffset.dx / _oldLayoutSize.width - : 0; - xPosition = - MediaQuery.of(context).orientation == Orientation.landscape - ? 0 - : xPosition * constraints.biggest.width; - _transformationController.value.translate( - previousOffset.dx - xPosition, previousOffset.dy - yPosition); - _oldLayoutSize = constraints.biggest; - _canPanOnZoom = false; - } - return Listener( - onPointerDown: (PointerDownEvent details) { - if (details.kind == PointerDeviceKind.mouse) { - _isMousePointer = true; + builder: (BuildContext context, BoxConstraints constraints) { + if (_oldLayoutSize != constraints.biggest && + widget.pdfPages.isNotEmpty) { + final Offset previousOffset = _currentPageTransformationController + .toScene(Offset.zero); + double yPosition = + !_oldLayoutSize.isEmpty + ? previousOffset.dy / _oldLayoutSize.height + : 0; + final double greyArea = + widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height - + constraints.biggest.height; + yPosition = yPosition * constraints.biggest.height; + if (!greyArea.isNegative && + greyArea < + (constraints.biggest.height / + widget.pdfViewerController.zoomLevel)) { + yPosition = yPosition.clamp( + 0, + (constraints.biggest.height) - greyArea, + ); } - }, - child: PageView( - controller: widget.pageController, - onPageChanged: (int value) { - _transformationController = TransformationController(); - widget.onPageChanged(value); + double xPosition = + !_oldLayoutSize.isEmpty + ? previousOffset.dx / _oldLayoutSize.width + : 0; + xPosition = + MediaQuery.of(context).orientation == Orientation.landscape + ? 0 + : xPosition * constraints.biggest.width; + if (kIsDesktop && !widget.isMobileWebView) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _currentPageTransformationController.value = + _currentPageTransformationController.value.clone() + ..translate( + previousOffset.dx - xPosition, + previousOffset.dy - yPosition, + ); + }); + } + _oldLayoutSize = constraints.biggest; + } + return Listener( + onPointerSignal: (PointerSignalEvent event) { + if (event is PointerScrollEvent && + widget.pdfViewerController.zoomLevel == 1 && + !_isPageChangedOnScroll) { + if (event.scrollDelta.dy > 0) { + widget.pdfViewerController.nextPage(); + } else if (event.scrollDelta.dy < 0) { + widget.pdfViewerController.previousPage(); + } + } + _isPageChangedOnScroll = false; }, - physics: (_canPanOnZoom || - _transformationController.value.getMaxScaleOnAxis() == - 1) || - (kIsDesktop && !widget.isMobileWebView) || - (MediaQuery.of(context).orientation == - Orientation.landscape && - (!kIsDesktop || - (kIsDesktop && widget.isMobileWebView))) - ? const BouncingScrollPhysics() - : const NeverScrollableScrollPhysics(), - children: pages, - ), - ); - }), + onPointerDown: (PointerDownEvent details) { + if (details.kind == PointerDeviceKind.mouse) { + } else if (details.kind == PointerDeviceKind.touch) { + setState(() { + _fingersInteracting++; + }); + } + }, + onPointerUp: (PointerUpEvent details) { + if (details.kind == PointerDeviceKind.touch) { + setState(() { + _fingersInteracting--; + }); + } + }, + onPointerCancel: (PointerCancelEvent details) { + if (details.kind == PointerDeviceKind.touch) { + setState(() { + _fingersInteracting--; + }); + } + }, + child: PageView( + controller: widget.pageController, + scrollDirection: + widget.scrollDirection == PdfScrollDirection.horizontal + ? Axis.horizontal + : Axis.vertical, + reverse: widget.textDirection != TextDirection.ltr, + onPageChanged: (int newPageIndex) { + // Reset the zoom level on page change + _currentPageTransformationController.value = + Matrix4.identity(); + final int newPageNumber = newPageIndex + 1; + if (widget.pdfPages[newPageNumber] != null) { + // Reset the content size of the TransformationController with the new page size + _currentPageTransformationController.contentSize = + widget.pdfPages[newPageNumber]!.pageSize; + } + _rotateAndAssignControllers(newPageNumber); + _currentPageNumber = newPageNumber; + widget.onPageChanged(newPageIndex); + }, + physics: + _currentPageTransformationController.value + .getMaxScaleOnAxis() == + 1 && + _fingersInteracting <= 1 + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), + children: pages, + ), + ); + }, + ), GestureDetector( onHorizontalDragStart: _handleDragStart, onHorizontalDragEnd: _handleDragEnd, @@ -596,101 +735,206 @@ class SinglePageViewState extends State { FocusScope.of(context).unfocus(); } if (widget.canShowPaginationDialog) { + _clearSelection(); _showPaginationDialog(); } } }, child: Visibility( - visible: widget.pdfViewerController.pageCount > 1 && + visible: + widget.pdfViewerController.pageCount > 1 && ((widget.canShowScrollHead && !kIsDesktop) || kIsDesktop), child: ScrollHead( - true, - false, - Offset(_scrollHeadPosition, widget.viewportDimension.height), - widget.pdfViewerController, - false, - PdfScrollDirection.horizontal, - widget.isBookmarkViewOpen, - PdfPageLayoutMode.single), + _canShowHorizontalScrollBar, + _canShowVerticalScrollBar, + _scrollHeadOffset, + widget.pdfViewerController, + false, + widget.scrollDirection, + widget.isBookmarkViewOpen, + PdfPageLayoutMode.single, + ), ), ), + if (kIsDesktop && !widget.isMobileWebView) + DesktopScrollbar( + contentSize: + widget + .pdfPages[widget.pdfViewerController.pageNumber] + ?.pageSize ?? + Size.zero, + viewportSize: widget.viewportDimension, + controller: _currentPageTransformationController, + onHorizontalDragEnd: (_) { + widget.onInteractionEnd?.call(); + }, + onVerticalDragEnd: (_) { + widget.onInteractionEnd?.call(); + }, + verticalScrollbarPadding: + widget.scrollDirection == PdfScrollDirection.vertical && + widget.pdfViewerController.pageCount > 1 + ? const EdgeInsets.only( + right: _defaultDesktopScrollbarWidth + _scrollbarPadding, + ) + : const EdgeInsets.only(right: _scrollbarPadding), + horizontalScrollbarPadding: + widget.scrollDirection == PdfScrollDirection.horizontal && + widget.pdfViewerController.pageCount > 1 + ? const EdgeInsets.only( + bottom: _defaultDesktopScrollbarWidth + _scrollbarPadding, + ) + : const EdgeInsets.only(bottom: _scrollbarPadding), + ), Visibility( - visible: isScrollHeadDragged && widget.canShowScrollStatus, - child: ScrollStatus(widget.pdfViewerController)) + visible: isScrollHeadDragged && widget.canShowScrollStatus, + child: ScrollStatus(widget.pdfViewerController), + ), ], ); } + void _changePage({ + required bool isMouseWheel, + Velocity velocity = Velocity.zero, + }) { + final double currentScale = + _currentPageTransformationController.value.getMaxScaleOnAxis(); + if (currentScale <= 1) { + return; + } + final Size pageSize = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; + final Size imageSize = pageSize * currentScale; + + if (widget.scrollDirection == PdfScrollDirection.vertical) { + final double topMargin = + ((widget.viewportDimension.height - imageSize.height) / 2) + .clamp(0, double.infinity) + .roundToDouble(); + final double pageBottom = pageSize.height.roundToDouble(); + final double currentBottomOffset = + _offsetWhenInteractionStarts.dy + + (widget.viewportDimension.height / currentScale); + + _goToNextPage = + -velocity.pixelsPerSecond.dy >= _kSwipeThreshold && + velocity.pixelsPerSecond.dx.abs() < _kSwipeThreshold && + (currentBottomOffset.roundToDouble() >= pageBottom) && + !isMouseWheel; + _goToPreviousPage = + velocity.pixelsPerSecond.dy >= _kSwipeThreshold && + velocity.pixelsPerSecond.dx.abs() < _kSwipeThreshold && + (_offsetWhenInteractionStarts.dy.roundToDouble() <= topMargin) && + !isMouseWheel; + } else if (widget.scrollDirection == PdfScrollDirection.horizontal) { + final double leftMargin = + ((widget.viewportDimension.width - imageSize.width) / 2) + .clamp(0, double.infinity) + .roundToDouble(); + final double pageRight = pageSize.width.roundToDouble(); + final double currentRightOffset = + _offsetWhenInteractionStarts.dx + + (widget.viewportDimension.width / currentScale); + + _goToNextPage = + -velocity.pixelsPerSecond.dx >= _kSwipeThreshold && + velocity.pixelsPerSecond.dy.abs() < _kSwipeThreshold && + (currentRightOffset.roundToDouble() >= pageRight) && + !isMouseWheel; + _goToPreviousPage = + velocity.pixelsPerSecond.dx >= _kSwipeThreshold && + velocity.pixelsPerSecond.dy.abs() < _kSwipeThreshold && + (_offsetWhenInteractionStarts.dx.roundToDouble() <= leftMargin) && + !isMouseWheel; + } + + if (_goToNextPage && + (!_isZoomChanged || + (isMouseWheel && + widget.scrollDirection == PdfScrollDirection.vertical))) { + if (widget.pdfViewerController.pageNumber != + widget.pdfViewerController.pageCount) { + widget.pageController.nextPage( + duration: const Duration(milliseconds: 500), + curve: Curves.ease, + ); + _isPageChangedOnScroll = true; + } + } else if (_goToPreviousPage && + (!_isZoomChanged || + (isMouseWheel && + widget.scrollDirection == PdfScrollDirection.vertical))) { + if (widget.pdfViewerController.pageNumber != 1) { + widget.pageController.previousPage( + duration: const Duration(milliseconds: 500), + curve: Curves.ease, + ); + _isPageChangedOnScroll = true; + } + } + + _goToNextPage = false; + _goToPreviousPage = false; + } + Offset _onDoubleTapZoomInvoked(Offset offset, Offset tapPosition) { - widget.onDoubleTap!(); + widget.onDoubleTap?.call(); previousZoomLevel = widget.pdfViewerController.zoomLevel; _oldPreviousZoomLevel = previousZoomLevel; widget.pdfViewerController.zoomLevel = - _transformationController.value.getMaxScaleOnAxis(); - final double pdfPageHeight = - widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height; - final double totalPageOffset = + _currentPageTransformationController.value.getMaxScaleOnAxis(); + + final double pdfPageWidth = widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width; + final bool isPortrait = + MediaQuery.of(context).orientation == Orientation.portrait; if (widget.pdfViewerController.zoomLevel <= 1) { if (kIsDesktop && !widget.isMobileWebView) { offset = Offset.zero; } else { - offset = Offset(0, offset.dy); + if ((widget.isMobileWebView || widget.isTablet) && + isPortrait && + widget.viewportDimension.width > pdfPageWidth) { + offset = Offset(offset.dx, 0); + } else { + offset = Offset(0, offset.dy); + } } } - if (widget.viewportDimension.height > pdfPageHeight) { - offset = Offset( - offset.dx, - (tapPosition.dy > widget.viewportDimension.height / 2) - ? offset.dy + - (widget.viewportDimension.height - - widget - .pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize - .height) / - 2 - : (offset.dy / 2)); - } - offset = Offset( - (offset.dx - (widget.viewportDimension.width - totalPageOffset)).clamp( - 0, - (offset.dx - (widget.viewportDimension.width - totalPageOffset)) - .abs()), - ((widget.pdfViewerController.zoomLevel) > 1 - ? offset.dy + greyAreaSize / 2 - : 0.0) - .clamp( - 0, - (((widget.viewportDimension.height - - widget - .pdfPages[ - widget.pdfViewerController.pageNumber]! - .pageSize - .height) + - widget - .pdfPages[ - widget.pdfViewerController.pageNumber]! - .pageSize - .height) / - 2) - - greyAreaSize / 2)); - setState(() { - _canPanOnZoom = false; - }); + widget.onInteractionEnd?.call(); return offset; } + /// Update the offset after zooming + void updateOffset() { + widget.onPdfOffsetChanged!.call( + _currentPageTransformationController.toScene(Offset.zero), + ); + } + void _handleDragStart(DragStartDetails details) { isScrollHeadDragged = true; } void _handleDragUpdate(DragUpdateDetails details) { - _scrollHeadPosition = details.localPosition.dx; - final int pageNumber = (_scrollHeadPosition / - (widget.viewportDimension.width - _kPdfScrollHeadHeight) * - widget.pdfViewerController.pageCount) - .round(); + int pageNumber = 0; + if (widget.scrollDirection == PdfScrollDirection.horizontal) { + _scrollHeadPosition = details.localPosition.dx; + pageNumber = + (_scrollHeadPosition / + (widget.viewportDimension.width - _kPdfScrollHeadSize) * + widget.pdfViewerController.pageCount) + .round(); + } else { + _scrollHeadPosition = details.localPosition.dy; + pageNumber = + (_scrollHeadPosition / + (widget.viewportDimension.height - _kPdfScrollHeadSize) * + widget.pdfViewerController.pageCount) + .round(); + } if (pageNumber > 0 && pageNumber != widget.pdfViewerController.pageNumber) { widget.pdfViewerController.jumpToPage(pageNumber); setState(() {}); @@ -705,64 +949,68 @@ class SinglePageViewState extends State { /// Scale to PDF double scaleTo(double zoomLevel) { - currentZoomLevel = _transformationController.value.getMaxScaleOnAxis(); + currentZoomLevel = + _currentPageTransformationController.value.getMaxScaleOnAxis(); if (currentZoomLevel != zoomLevel) { previousZoomLevel = currentZoomLevel; _oldPreviousZoomLevel = previousZoomLevel; _setZoomLevel = true; final double zoomChangeFactor = zoomLevel / currentZoomLevel; - final Offset previousOffset = - _transformationController.toScene(Offset.zero); - _transformationController.value.scale(zoomChangeFactor, zoomChangeFactor); - if (kIsDesktop && - !widget.isMobileWebView && - widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .width * - zoomLevel < - widget.viewportDimension.width) { - _isOverFlowed = false; - } - final Offset currentOffset = - _transformationController.toScene(Offset.zero); + final Offset previousOffset = _currentPageTransformationController + .toScene(Offset.zero); + _currentPageTransformationController.value = + _currentPageTransformationController.value.clone() + ..scale(zoomChangeFactor, zoomChangeFactor); + final Offset currentOffset = _currentPageTransformationController.toScene( + Offset.zero, + ); if ((kIsDesktop && !widget.isMobileWebView) || - (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize .width * - currentZoomLevel < + zoomLevel < widget.viewportDimension.width)) { - setState(() { - _transformationController.value.translate(previousOffset.dx, - currentOffset.dy / widget.pdfViewerController.zoomLevel); - }); + _currentPageTransformationController.value = + _currentPageTransformationController.value.clone()..translate( + currentOffset.dx, + currentOffset.dy / widget.pdfViewerController.zoomLevel, + ); } else { - greyAreaSize = widget.viewportDimension.height - - (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + greyAreaSize = + widget.viewportDimension.height - + (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize .height); double greyAreaOffset = 0; - setState(() { - _canPanOnZoom = false; - if (widget.viewportDimension.height > - widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .height * - previousZoomLevel) { - greyAreaOffset = greyAreaSize / 2; - } else { - greyAreaOffset = 0; - } - if (widget.viewportDimension.height > - widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .height * - widget.pdfViewerController.zoomLevel) { - _setPixel(Offset(previousOffset.dx, 0)); - } else { - _setPixel( - Offset(previousOffset.dx, previousOffset.dy + greyAreaOffset)); - } - }); + if (widget.viewportDimension.height > + widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height * + previousZoomLevel) { + greyAreaOffset = greyAreaSize / 2; + } else { + greyAreaOffset = 0; + } + if (widget.viewportDimension.height > + widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height * + widget.pdfViewerController.zoomLevel) { + _setPixel(Offset(previousOffset.dx, 0)); + } else { + _setPixel( + Offset(previousOffset.dx, previousOffset.dy + greyAreaOffset), + ); + } } } - widget.onPdfOffsetChanged! - .call(_transformationController.toScene(Offset.zero)); - + widget.onPdfOffsetChanged!.call( + _currentPageTransformationController.toScene(Offset.zero), + ); return zoomLevel; } @@ -777,7 +1025,9 @@ class SinglePageViewState extends State { widget.pdfViewerController.zoomLevel = currentPreviousZoomLevel; previousZoomLevel = _oldPreviousZoomLevel; if (widget.pdfViewerController.zoomLevel > 1) { - if (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + if (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize .width * widget.pdfViewerController.zoomLevel < widget.viewportDimension.width) { @@ -786,21 +1036,24 @@ class SinglePageViewState extends State { } else { if ((!kIsDesktop || kIsDesktop && widget.isMobileWebView) && widget.viewportDimension.height > - (widget.pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize.height * + (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height * widget.pdfViewerController.zoomLevel)) { final Size pdfDimension = widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; - final double widthFactor = pdfDimension.width - + final double widthFactor = + pdfDimension.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); - final Offset previousOffset = - _transformationController.toScene(Offset.zero); - _transformationController.value.translate( - previousOffset.dx - offset.dx.clamp(0, widthFactor.abs()), - -(widget.viewportDimension.height - - _getChildSize(widget.viewportDimension).height) / - 2); + final Offset previousOffset = _currentPageTransformationController + .toScene(Offset.zero); + _currentPageTransformationController.value = + _currentPageTransformationController.value.clone()..translate( + previousOffset.dx - offset.dx.clamp(0, widthFactor.abs()), + -(widget.viewportDimension.height - pdfDimension.height) / 2, + ); } else { if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { isJumpOnZoomedDocument = true; @@ -814,25 +1067,34 @@ class SinglePageViewState extends State { } void _setPixel(Offset offset) { - final double widthFactor = widget - .pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width - + final double widthFactor = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); offset = Offset( - offset.dx.clamp(-widthFactor, widthFactor.abs()), - offset.dy.clamp( - 0, - (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize - .height - - (widget.viewportDimension.height / - widget.pdfViewerController.zoomLevel)) - .abs())); - - final Offset previousOffset = - _transformationController.toScene(Offset.zero); - _transformationController.value.translate( - previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); - widget.onPdfOffsetChanged! - .call(_transformationController.toScene(Offset.zero)); + offset.dx.clamp(-widthFactor, widthFactor.abs()), + offset.dy.clamp( + 0, + (widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + .abs(), + ), + ); + + final Offset previousOffset = _currentPageTransformationController.toScene( + Offset.zero, + ); + _currentPageTransformationController.value = + _currentPageTransformationController.value.clone()..translate( + previousOffset.dx - offset.dx, + previousOffset.dy - offset.dy, + ); + widget.onPdfOffsetChanged!.call( + _currentPageTransformationController.toScene(Offset.zero), + ); } /// Clears the Text Selection. @@ -842,141 +1104,270 @@ class SinglePageViewState extends State { /// Show the pagination dialog box Future _showPaginationDialog() async { - await _clearSelection(); + final bool isMaterial3 = Theme.of(context).useMaterial3; return showDialog( - context: context, - barrierDismissible: true, - builder: (BuildContext context) { - final Orientation orientation = MediaQuery.of(context).orientation; - return AlertDialog( - scrollable: true, - insetPadding: EdgeInsets.zero, - contentPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(24) - : const EdgeInsets.only(top: 0, right: 24, left: 24, bottom: 0), - buttonPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(8) - : const EdgeInsets.all(4), - backgroundColor: _pdfViewerThemeData!.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == Brightness.light - ? Colors.white - : const Color(0xFF424242)), - title: Text( - _localizations!.pdfGoToPageLabel, - style: - _pdfViewerThemeData!.paginationDialogStyle?.headerTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.87), - ), - ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), - content: SingleChildScrollView(child: _paginationTextField()), - actions: [ - TextButton( - onPressed: () { - _textFieldController.clear(); - Navigator.of(context).pop(); - }, - child: Text( - _localizations!.pdfPaginationDialogCancelLabel, - style: _pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle!.color == - null - ? _pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle! - .copyWith( - color: Theme.of(context).colorScheme.primary) - : _pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500), - ), + context: context, + builder: (BuildContext context) { + final Orientation orientation = MediaQuery.of(context).orientation; + return AlertDialog( + scrollable: true, + insetPadding: EdgeInsets.zero, + contentPadding: + isMaterial3 + ? const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0) + : orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.only(right: 24, left: 24), + buttonPadding: + orientation == Orientation.portrait + ? const EdgeInsets.all(8) + : const EdgeInsets.all(4), + backgroundColor: + _pdfViewerThemeData!.paginationDialogStyle?.backgroundColor ?? + _effectiveThemeData!.paginationDialogStyle?.backgroundColor ?? + (Theme.of(context).colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242)), + shape: + isMaterial3 + ? null + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _localizations!.pdfGoToPageLabel, + style: Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: isMaterial3 ? 24 : 20, + color: + isMaterial3 + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .paginationDialogStyle + ?.headerTextStyle, + ), ), - TextButton( - onPressed: () { - _handlePageNumberValidation(); - }, - child: Text( - _localizations!.pdfPaginationDialogOkLabel, - style: _pdfViewerThemeData! - .paginationDialogStyle?.okTextStyle!.color == - null - ? _pdfViewerThemeData!.paginationDialogStyle?.okTextStyle! - .copyWith( - color: Theme.of(context).colorScheme.primary) - : _pdfViewerThemeData! - .paginationDialogStyle?.okTextStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500), + if (isMaterial3) + IconButton( + icon: const Icon(Icons.clear), + iconSize: 24, + color: Theme.of(context).colorScheme.onSurfaceVariant, + onPressed: () { + _textFieldController.clear(); + Navigator.of(context).pop(); + }, ), - ) ], - ); - }); + ), + content: SingleChildScrollView( + child: Column( + children: [ + if (isMaterial3) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '1 - ${widget.pdfViewerController.pageCount}', + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w400 : null, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ), + _paginationTextField(), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + _textFieldController.clear(); + Navigator.of(context).pop(); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, + child: Text( + _localizations!.pdfPaginationDialogCancelLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: Theme.of(context).colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .paginationDialogStyle + ?.cancelTextStyle, + ), + ), + ), + TextButton( + onPressed: () { + _handlePageNumberValidation(); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, + child: Text( + _localizations!.pdfPaginationDialogOkLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: Theme.of(context).colorScheme.primary, + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.okTextStyle, + ), + ), + ), + ], + ); + }, + ); } /// A material design Text field for pagination dialog box. Widget _paginationTextField() { + final bool isMaterial3 = Theme.of(context).useMaterial3; return Form( key: _formKey, child: SizedBox( - width: _kPdfPaginationTextFieldWidth, + width: isMaterial3 ? 312.0 : _kPdfPaginationTextFieldWidth, child: TextFormField( - style: - _pdfViewerThemeData!.paginationDialogStyle?.inputFieldTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.87)), + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.inputFieldTextStyle, + ), focusNode: _focusNode, decoration: InputDecoration( isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: - BorderSide(color: Theme.of(context).colorScheme.primary), - ), - contentPadding: const EdgeInsets.symmetric(vertical: 6), + border: + isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + Theme.of(context).colorScheme.primary, + ), + ) + : null, + errorBorder: + isMaterial3 + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(3.5), + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + Theme.of(context).colorScheme.error, + ), + ) + : null, + focusedBorder: + isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + Theme.of(context).colorScheme.primary, + width: 2, + ), + ) + : UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + ), + contentPadding: + isMaterial3 + ? const EdgeInsets.all(16) + : const EdgeInsets.symmetric(vertical: 6), hintText: _localizations!.pdfEnterPageNumberLabel, - hintStyle: - _pdfViewerThemeData!.paginationDialogStyle?.hintTextStyle ?? - (TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.38))), + hintStyle: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.hintTextStyle, + ), counterText: - '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', - counterStyle: - _pdfViewerThemeData!.paginationDialogStyle?.pageInfoTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 12, - color: Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.38)), - errorStyle: _pdfViewerThemeData! - .paginationDialogStyle?.validationTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 12, - color: Theme.of(context).colorScheme.error), + isMaterial3 + ? null + : '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', + counterStyle: Theme.of(context).textTheme.bodySmall! + .copyWith( + fontSize: 12, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData!.paginationDialogStyle?.pageInfoTextStyle, + ), + errorStyle: Theme.of(context).textTheme.bodySmall! + .copyWith( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Theme.of(context).colorScheme.error, + ) + .merge( + _pdfViewerThemeData! + .paginationDialogStyle + ?.validationTextStyle, + ), ), keyboardType: TextInputType.number, enableInteractiveSelection: false, @@ -1016,4 +1407,98 @@ class SinglePageViewState extends State { widget.pdfViewerController.jumpToPage(index); } } -} \ No newline at end of file +} + +/// TransformationController extension to restirct the PDF pages within the viewport boundaries. +class TransformationControllerExt extends TransformationController { + TransformationControllerExt({Matrix4? value}) : super(value); + + Size _viewSize = Size.zero; + Size _contentSize = Size.zero; + final double _minScale = 1; + + /// The size of the viewport + Size get viewSize => _viewSize; + set viewSize(Size size) { + if (_viewSize != size) { + _viewSize = size; + super.value = _boundWithinViewportLimits(value); + } + } + + /// The size of the content which is displayed in the viewport + Size get contentSize => _contentSize; + set contentSize(Size size) { + if (_contentSize != size) { + _contentSize = size; + super.value = _boundWithinViewportLimits(value); + } + } + + @override + set value(Matrix4 newValue) { + if (_contentSize.isEmpty || _viewSize.isEmpty) { + super.value = newValue; + return; + } + super.value = _boundWithinViewportLimits(newValue); + } + + Offset _calculateViewportCenter(Matrix4 matrix) { + return Offset( + -matrix.storage[12] + _viewSize.width / 2, + -matrix.storage[13] + _viewSize.height / 2, + ) / + matrix.storage[0]; + } + + /// Restrict matrix to the safe range. + Matrix4 _boundWithinViewportLimits(Matrix4 newValue) { + if (_contentSize.isEmpty || _viewSize.isEmpty) { + return newValue; + } + final double newScale = max(newValue.storage[0], _minScale); + final Offset position = _calculateViewportCenter(newValue); + + final double halfViewWidth = _viewSize.width / 2 / newScale; + final double halfViewHeight = _viewSize.height / 2 / newScale; + final double x = position.dx.range( + halfViewWidth, + _contentSize.width - halfViewWidth, + ); + final double y = position.dy.range( + halfViewHeight, + _contentSize.height - halfViewHeight, + ); + + return _composeMatrix(Offset(x, y), zoom: newScale, viewSize: _viewSize); + } + + /// Calculate matrix to center the specified position. + Matrix4 _composeMatrix( + Offset position, { + required double zoom, + required Size viewSize, + }) { + final double halfViewWidth = viewSize.width / 2; + final double halfViewHeight = viewSize.height / 2; + const double zCoordinate = 0; + + final Matrix4 matrix = Matrix4.compose( + vector.Vector3( + -(position.dx * zoom) + halfViewWidth, // x + -(position.dy * zoom) + halfViewHeight, // y + zCoordinate, // z + ), + vector.Quaternion.identity(), // rotation + vector.Vector3(zoom, zoom, zoom), // scale + ); + + return matrix; + } +} + +extension on double { + double range(double a, double b) => + a < b ? clamp(a, b) as double : (a + b) / 2; +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/sticky_note_edit_text.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/sticky_note_edit_text.dart new file mode 100644 index 000000000..db5b941e2 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/sticky_note_edit_text.dart @@ -0,0 +1,205 @@ +import 'package:flutter/material.dart'; + +import '../annotation/annotation.dart'; +import '../annotation/sticky_notes.dart'; +import '../change_tracker/change_tracker.dart'; +import '../common/pdfviewer_helper.dart'; + +/// The height of the sticky note edit text dialog +const double kStickyNoteEditTextDialogHeight = 180.0; + +/// The width of the sticky note edit text dialog +const double kStickyNoteEditTextDialogWidth = 254.0; + +/// A widget that displays the text of a [StickyNoteAnnotation] and allows editing +class StickyNoteEditText extends StatefulWidget { + /// Creates a widget that displays the text of a [StickyNoteAnnotation] and allows editing + const StickyNoteEditText({ + required this.stickyNote, + this.isNewAnnotation = false, + this.onClose, + this.backgroundColor = const Color.fromRGBO(254, 255, 204, 1), + required this.changeTracker, + super.key, + }); + + /// [StickyNoteAnnotation] instance. + final StickyNoteAnnotation stickyNote; + + /// True if the [StickyNoteAnnotation] is new + final bool isNewAnnotation; + + /// Called when the edit text is closed + final VoidCallback? onClose; + + /// The background color of the edit text dialog + final Color backgroundColor; + + /// The change tracker to detect and change the sticky note dialog text when undo or redo in invoked + final ChangeTracker changeTracker; + + @override + State createState() => _StickyNoteEditTextState(); +} + +class _StickyNoteEditTextState extends State { + bool _isEditing = false; + bool _isNewAnnotation = false; + late TextEditingController _controller; + late FocusNode _focusNode; + bool _canEdit = true; + late Color _backgroundColor; + + @override + void initState() { + _isEditing = _isNewAnnotation = widget.isNewAnnotation; + _controller = TextEditingController(text: widget.stickyNote.text); + _focusNode = FocusNode(); + _canEdit = widget.stickyNote.canEdit; + _backgroundColor = widget.backgroundColor; + widget.stickyNote.addListener(_onStickyNotePropertyChange); + if (widget.isNewAnnotation) { + _focusNode.requestFocus(); + } + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + widget.stickyNote.removeListener(_onStickyNotePropertyChange); + super.dispose(); + } + + void _onStickyNotePropertyChange() { + if (!mounted) { + return; + } + if (_canEdit != widget.stickyNote.canEdit) { + setState(() { + _canEdit = !widget.stickyNote.isLocked; + _isEditing = false; + }); + } + if (_controller.text != widget.stickyNote.text && + widget.changeTracker.changeInProgress) { + _controller.value = TextEditingValue( + text: widget.stickyNote.text, + selection: TextSelection.collapsed( + offset: widget.stickyNote.text.length, + ), + ); + } + + setState(() { + _backgroundColor = widget.stickyNote.color.getLightenColor(0.85); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Theme( + data: ThemeData.light(), + child: Container( + height: 180, + decoration: BoxDecoration( + color: _backgroundColor, + borderRadius: BorderRadius.circular(8.0), + boxShadow: const [ + BoxShadow( + color: Color(0x1E000000), + blurRadius: 10, + offset: Offset(0, 1), + ), + BoxShadow( + color: Color(0x23000000), + blurRadius: 5, + offset: Offset(0, 4), + ), + BoxShadow( + color: Color(0x05000000), + blurRadius: 4, + offset: Offset(0, 2), + spreadRadius: -1, + ), + ], + ), + child: Column( + children: [ + SizedBox( + height: 55, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Visibility( + visible: _canEdit, + child: IconButton( + color: Colors.black, + onPressed: () { + if (!widget.stickyNote.canEdit) { + return; + } + if (!_isEditing) { + setState(() { + _isEditing = true; + }); + _focusNode.requestFocus(); + } else { + if (_controller.text != widget.stickyNote.text) { + if (_isNewAnnotation) { + widget.stickyNote.setText(_controller.text); + _isNewAnnotation = false; + } else { + widget.stickyNote.text = _controller.text; + } + } + setState(() { + _isEditing = false; + }); + } + }, + icon: + _isEditing + ? const Icon(Icons.check_outlined) + : const Icon(Icons.edit_outlined), + ), + ), + IconButton( + color: Colors.black, + onPressed: widget.onClose, + icon: const Icon(Icons.clear), + ), + ], + ), + ), + const Divider(height: 1.0), + SizedBox( + height: 124, + child: TextField( + readOnly: !_isEditing, + controller: _controller, + focusNode: _focusNode, + maxLines: null, + expands: true, + textAlignVertical: TextAlignVertical.top, + decoration: const InputDecoration( + fillColor: Colors.transparent, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart new file mode 100644 index 000000000..261c4b172 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart @@ -0,0 +1,249 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; + +import '../common/pdfviewer_helper.dart'; + +/// Width of the text selection menu +const double kTextSelectionMenuWidth = 191.0; + +/// Height of the text selection menu +const double kTextSelectionMenuHeight = 216.0; + +/// Height of the text selection menu item +const double kTextSelectionMenuItemHeight = 40.0; + +/// Margin of the text selection menu +final double kTextSelectionMenuMargin = kIsDesktop ? 10 : 32; + +/// A text selection menu that can be used to display a list of actions +class TextSelectionMenu extends StatefulWidget { + /// Creates a text selection menu + const TextSelectionMenu({ + super.key, + this.textDirection, + this.onSelected, + this.themeData, + this.localizations, + }); + + /// Called when an item is selected + final void Function(String)? onSelected; + + /// The text direction to use for rendering the text selection menu. + final TextDirection? textDirection; + + /// The theme data of the text selection menu. + final ThemeData? themeData; + + ///The localizations of the text selection menu. + final SfLocalizations? localizations; + + @override + State createState() => _TextSelectionMenuState(); +} + +class _TextSelectionMenuState extends State { + @override + Widget build(BuildContext context) { + return Container( + height: kTextSelectionMenuHeight, + decoration: ShapeDecoration( + color: + widget.themeData!.useMaterial3 + ? (widget.themeData!.colorScheme.brightness == Brightness.light) + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1) + : (widget.themeData!.colorScheme.brightness == Brightness.light) + ? Colors.white + : const Color(0xFF303030), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + shadows: const [ + BoxShadow( + color: Color(0x1E000000), + blurRadius: 10, + offset: Offset(0, 1), + ), + BoxShadow( + color: Color(0x23000000), + blurRadius: 5, + offset: Offset(0, 4), + ), + BoxShadow( + color: Color(0x05000000), + blurRadius: 4, + offset: Offset(0, 2), + spreadRadius: -1, + ), + ], + ), + child: Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), + child: IntrinsicWidth( + child: SingleChildScrollView( + child: ListBody( + children: [ + TextSelectionMenuItem( + title: widget.localizations!.pdfTextSelectionMenuCopyLabel, + mode: 'Copy', + onSelected: widget.onSelected, + textDirection: widget.textDirection, + themeData: widget.themeData, + ), + TextSelectionMenuItem( + title: + widget.localizations!.pdfTextSelectionMenuHighlightLabel, + mode: 'Highlight', + onSelected: widget.onSelected, + textDirection: widget.textDirection, + themeData: widget.themeData, + ), + TextSelectionMenuItem( + title: + widget.localizations!.pdfTextSelectionMenuUnderlineLabel, + mode: 'Underline', + onSelected: widget.onSelected, + textDirection: widget.textDirection, + themeData: widget.themeData, + ), + TextSelectionMenuItem( + title: + widget + .localizations! + .pdfTextSelectionMenuStrikethroughLabel, + mode: 'Strikethrough', + onSelected: widget.onSelected, + textDirection: widget.textDirection, + themeData: widget.themeData, + ), + TextSelectionMenuItem( + title: + widget.localizations!.pdfTextSelectionMenuSquigglyLabel, + mode: 'Squiggly', + onSelected: widget.onSelected, + textDirection: widget.textDirection, + themeData: widget.themeData, + ), + ], + ), + ), + ), + ), + ); + } +} + +/// A text selection menu item that can be used to display a list of actions +class TextSelectionMenuItem extends StatefulWidget { + /// Creates a text selection menu item + const TextSelectionMenuItem({ + required this.title, + required this.mode, + required this.onSelected, + this.textDirection, + this.themeData, + super.key, + }); + + /// Title of the text selection menu item + final String title; + + /// Mode of the text selection menu item + final String mode; + + /// Called when an item is selected + final void Function(String)? onSelected; + + /// The text direction to use for rendering the title. + final TextDirection? textDirection; + + /// The theme data of the text selection menu item. + final ThemeData? themeData; + + @override + State createState() => _TextSelectionMenuItemState(); +} + +class _TextSelectionMenuItemState extends State { + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + widget.onSelected?.call(widget.mode); + }, + highlightColor: + widget.themeData!.useMaterial3 + ? widget.themeData!.colorScheme.primaryContainer + : (widget.themeData!.colorScheme.brightness == Brightness.light) + ? Colors.grey.withValues(alpha: 0.2) + : Colors.grey.withValues(alpha: 0.5), + child: Directionality( + textDirection: widget.textDirection ?? TextDirection.ltr, + child: Container( + height: kTextSelectionMenuItemHeight, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + child: Row( + children: [ + _getIcon(widget.themeData!.brightness.name, widget.mode), + const Divider(indent: 12), + Text( + widget.title, + overflow: TextOverflow.ellipsis, + style: widget.themeData!.textTheme.bodyMedium!.copyWith( + fontSize: 14, + color: + widget.themeData!.useMaterial3 + ? widget.themeData!.colorScheme.onSurface + : widget.themeData!.brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _getIcon(String theme, String mode) { + if (mode == 'Copy') { + return Icon( + Icons.copy, + size: 16, + color: + (widget.themeData!.colorScheme.brightness == Brightness.light) + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ); + } + mode = mode.toLowerCase().replaceAll(RegExp(r' '), ''); + return ImageIcon( + AssetImage( + 'assets/icons/$theme/$mode.png', + package: 'syncfusion_flutter_pdfviewer', + ), + size: 16, + ); + } +} + +/// The location of the text selection menu +enum TextSelectionMenuLocation { + /// Left + left, + + /// Top + top, + + /// Right + right, + + /// Bottom + bottom, + + /// Center + center, +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/form_field_container.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/form_field_container.dart new file mode 100644 index 000000000..d3bdf40df --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/form_field_container.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; + +import '../../pdfviewer.dart'; +import 'pdf_checkbox.dart'; +import 'pdf_combo_box.dart'; +import 'pdf_form_field.dart'; +import 'pdf_list_box.dart'; +import 'pdf_radio_button.dart'; +import 'pdf_signature.dart'; +import 'pdf_text_box.dart'; + +class FormFieldContainer extends StatefulWidget { + const FormFieldContainer({ + super.key, + required this.formFields, + this.onTap, + this.heightPercentage = 1, + this.canShowSignaturePadDialog = true, + required this.pdfViewerController, + }); + + final List formFields; + + final void Function(Offset)? onTap; + + final PdfViewerController pdfViewerController; + + final double heightPercentage; + + final bool canShowSignaturePadDialog; + + @override + State createState() => _FormFieldContainerState(); +} + +class _FormFieldContainerState extends State { + @override + Widget build(BuildContext context) { + return Listener( + onPointerUp: (PointerUpEvent event) { + widget.onTap?.call(event.localPosition); + }, + child: RepaintBoundary(child: Stack(children: _buildFormFields())), + ); + } + + List _buildFormFields() { + final List formFields = []; + if (widget.formFields.isNotEmpty) { + for (final PdfFormField formField in widget.formFields) { + final PdfFormFieldHelper helper = PdfFormFieldHelper.getHelper( + formField, + ); + WidgetsBinding.instance.addPostFrameCallback((_) { + _updateGlobalRect(helper); + }); + helper.onChanged = () { + if (mounted) { + setState(() {}); + } + }; + if (formField is PdfTextFormField) { + formFields.add( + (helper as PdfTextFormFieldHelper).build( + context, + widget.heightPercentage, + ), + ); + } else if (formField is PdfCheckboxFormField) { + formFields.add( + (helper as PdfCheckboxFormFieldHelper).build( + context, + widget.heightPercentage, + ), + ); + } else if (formField is PdfComboBoxFormField) { + formFields.add( + (helper as PdfComboBoxFormFieldHelper).build( + context, + widget.heightPercentage, + ), + ); + } else if (formField is PdfRadioFormField) { + formFields.addAll( + (helper as PdfRadioFormFieldHelper).build( + context, + widget.heightPercentage, + ), + ); + } else if (formField is PdfListBoxFormField) { + formFields.add( + (helper as PdfListBoxFormFieldHelper).build( + context, + widget.heightPercentage, + ), + ); + } else if (formField is PdfSignatureFormField) { + if (helper is PdfSignatureFormFieldHelper) { + helper.pdfViewerController = widget.pdfViewerController; + helper.canShowSignaturePadDialog = widget.canShowSignaturePadDialog; + formFields.add(helper.build(context, widget.heightPercentage)); + } + } + } + } + return formFields; + } + + /// Updates the global rect of the form field. + void _updateGlobalRect(PdfFormFieldHelper helper) { + if (!mounted) { + return; + } + final renderObject = context.findRenderObject(); + if (renderObject is RenderBox && renderObject.hasSize) { + helper.globalRect = Rect.fromPoints( + renderObject.localToGlobal( + helper.bounds.topLeft / widget.heightPercentage, + ), + renderObject.localToGlobal( + helper.bounds.bottomRight / widget.heightPercentage, + ), + ); + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart new file mode 100644 index 000000000..3a91c145d --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart @@ -0,0 +1,284 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import 'pdf_form_field.dart'; + +/// Represents the checkbox form field. +class PdfCheckboxFormField extends PdfFormField { + PdfCheckboxFormField._(); + + late bool _isChecked; + List? _children; + + /// Gets or sets the checked state of the [PdfCheckboxFormField]. + bool get isChecked => _isChecked; + + set isChecked(bool value) { + if (readOnly) { + return; + } + (PdfFormFieldHelper.getHelper(this) as PdfCheckboxFormFieldHelper) + .invokeValueChanged(value); + } + + @override + set readOnly(bool value) { + if (readOnly != value) { + super.readOnly = value; + if (_children != null && _children!.isNotEmpty) { + for (final PdfCheckboxFormField item in _children!) { + item.readOnly = value; + } + } + } + } + + /// Gets the child items associated with this [PdfCheckboxFormField]. + List? get children => + _children != null + ? List.unmodifiable(_children!) + : null; +} + +/// Helper class for [PdfCheckboxFormField]. +class PdfCheckboxFormFieldHelper extends PdfFormFieldHelper { + /// Initializes a new instance of the [PdfCheckboxFormFieldHelper] class. + PdfCheckboxFormFieldHelper( + this.pdfCheckboxField, + int pageIndex, { + this.pdfCheckBoxItem, + this.onValueChanged, + }) : super(pdfCheckboxField, pageIndex) { + bounds = pdfCheckboxField.bounds; + } + + /// The checkbox field object from PDF library. + final PdfCheckBoxField pdfCheckboxField; + + /// Gets or sets the checked state of the [PdfCheckboxFormField]. + final PdfCheckBoxItem? pdfCheckBoxItem; + + /// The callback which is called when the value of the form field is changed. + final PdfFormFieldValueChangedCallback? onValueChanged; + + /// The checkbox form field object. + late PdfCheckboxFormField checkboxFormField; + + /// Sets the checkbox form field child items. + set checkBoxFormFieldChildItems(List? groupedItems) { + checkboxFormField._children = groupedItems; + } + + /// Gets the checkbox form field child items. + List? get checkBoxFormFieldChildItems => + checkboxFormField._children; + + /// Updates the child value. + void import() { + if (pdfCheckBoxItem != null) { + checkboxFormField._isChecked = pdfCheckBoxItem!.checked; + } + } + + /// Creates the checkbox form field object. + PdfCheckboxFormField getFormField() { + checkboxFormField = + PdfCheckboxFormField._() + .._isChecked = + pdfCheckBoxItem != null + ? pdfCheckBoxItem!.checked + : pdfCheckboxField.isChecked; + super.load(checkboxFormField); + + return checkboxFormField; + } + + /// Invokes the value changed callback. + void invokeValueChanged(bool? newValue) { + if (checkboxFormField._isChecked != newValue) { + final bool oldValue = checkboxFormField._isChecked; + setCheckboxValue(newValue!); + if (onValueChanged != null) { + onValueChanged!( + PdfFormFieldValueChangedDetails( + checkboxFormField, + oldValue, + newValue, + ), + ); + } + rebuild(); + } + } + + /// Sets the checkbox value. + void setCheckboxValue(bool isChecked) { + checkboxFormField._isChecked = isChecked; + if (pdfCheckBoxItem != null) { + pdfCheckBoxItem!.checked = isChecked; + _updateChildItems(); + } else { + pdfCheckboxField.isChecked = isChecked; + } + } + + /// Updates the grouped field items. + void _updateChildItems() { + if (checkboxFormField._children != null && + checkboxFormField._children!.isNotEmpty) { + for ( + int index = 0; + index < checkboxFormField._children!.length; + index++ + ) { + final PdfCheckboxFormFieldHelper helper = + PdfFormFieldHelper.getHelper(checkboxFormField._children![index]) + as PdfCheckboxFormFieldHelper; + + if (helper.pdfCheckBoxItem != null) { + checkboxFormField._children![index]._isChecked = + helper.pdfCheckBoxItem!.checked; + helper.rebuild(); + } + } + } + } + + /// Builds the checkbox form field widget. + Widget build(BuildContext context, double heightPercentage) { + final double selectionPadding = kIsDesktop ? 0 : kFormFieldSelectionPadding; + final Rect adjustedBounds = bounds.inflate(selectionPadding); + return Positioned( + left: adjustedBounds.left / heightPercentage, + top: adjustedBounds.top / heightPercentage, + width: adjustedBounds.width / heightPercentage, + height: adjustedBounds.height / heightPercentage, + child: PdfCheckbox( + isChecked: checkboxFormField._isChecked, + readOnly: checkboxFormField.readOnly, + onChanged: invokeValueChanged, + heightPercentage: heightPercentage, + selectionPadding: selectionPadding, + fillColor: + pdfCheckboxField.backColor.isEmpty + ? pdfCheckboxField.readOnly + ? Colors.transparent + : const Color.fromARGB(255, 221, 228, 255) + : Color.fromRGBO( + pdfCheckboxField.backColor.r, + pdfCheckboxField.backColor.g, + pdfCheckboxField.backColor.b, + 1, + ), + borderColor: + pdfCheckboxField.borderColor.isEmpty + ? Colors.transparent + : Color.fromRGBO( + pdfCheckboxField.borderColor.r, + pdfCheckboxField.borderColor.g, + pdfCheckboxField.borderColor.b, + 1, + ), + borderWidth: + (pdfCheckboxField.borderWidth == 0 + ? 1 + : pdfCheckboxField.borderWidth) / + heightPercentage, + size: bounds.height / heightPercentage, + ), + ); + } +} + +/// Customized checkbox +class PdfCheckbox extends StatefulWidget { + /// Constructor of PdfCheckbox + const PdfCheckbox({ + Key? key, + required this.isChecked, + required this.onChanged, + this.readOnly = false, + required this.heightPercentage, + required this.selectionPadding, + required this.fillColor, + required this.borderColor, + required this.borderWidth, + this.size = 24.0, + }) : super(key: key); + + /// Checkbox padding + final double selectionPadding; + + /// Height percentage + final double heightPercentage; + + /// Indicates whether checkbox is checked or unchecked. + final bool isChecked; + + /// Checkbox read only + final bool readOnly; + + /// Checkbox on changed + final ValueChanged? onChanged; + + /// Checkbox fill color + final Color fillColor; + + /// Checkbox size + final double size; + + /// Checkbox border color + final Color borderColor; + + /// Checkbox border width + final double borderWidth; + + @override + _PdfCheckboxState createState() => _PdfCheckboxState(); +} + +class _PdfCheckboxState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (widget.readOnly) { + return; + } + setState(() { + widget.onChanged!(!widget.isChecked); + }); + }, + child: Padding( + padding: EdgeInsets.all( + widget.selectionPadding / widget.heightPercentage, + ), + child: Container( + width: widget.size, + height: widget.size, + decoration: BoxDecoration( + color: widget.fillColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: + widget.isChecked + ? Icon( + Icons.check_outlined, + size: widget.size - widget.borderWidth * 2, + color: Colors.black, + ) + : Container(), + ), + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart new file mode 100644 index 000000000..138fdfb7b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart @@ -0,0 +1,280 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../../pdfviewer.dart'; +import 'pdf_form_field.dart'; + +/// Represents the combo box form field. +class PdfComboBoxFormField extends PdfFormField { + PdfComboBoxFormField._(); + + late String _selectedItem; + late List _items; + + /// Gets the items of the [PdfComboBoxFormField]. + List get items => List.unmodifiable(_items); + + /// Gets or sets a selected item of the [PdfComboBoxFormField]. + String get selectedItem => _selectedItem; + + set selectedItem(String value) { + if (readOnly) { + return; + } + (PdfFormFieldHelper.getHelper(this) as PdfComboBoxFormFieldHelper) + .invokeValueChanged(value); + } +} + +/// Helper class for [PdfComboBoxFormField]. +class PdfComboBoxFormFieldHelper extends PdfFormFieldHelper { + /// Initializes a new instance of the [PdfComboBoxFormFieldHelper] class. + PdfComboBoxFormFieldHelper( + this.pdfComboBoxField, + int pageIndex, { + this.onValueChanged, + }) : super(pdfComboBoxField, pageIndex) { + bounds = pdfComboBoxField.bounds; + } + + /// The combo box field object from PDF library. + final PdfComboBoxField pdfComboBoxField; + + /// The callback which is called when the value of the form field is changed. + final PdfFormFieldValueChangedCallback? onValueChanged; + + /// The combo box form field object. + late PdfComboBoxFormField comboBoxFormField; + + /// Creates the combo box form field object. + PdfComboBoxFormField getFormField() { + final List items = []; + for (int index = 0; index < pdfComboBoxField.items.count; index++) { + items.add(pdfComboBoxField.items[index].text); + } + + final String selectedValue = + pdfComboBoxField.selectedIndex != -1 + ? pdfComboBoxField.items[pdfComboBoxField.selectedIndex].text + : ''; + + comboBoxFormField = + PdfComboBoxFormField._() + .._items = items + .._selectedItem = selectedValue; + super.load(comboBoxFormField); + + return comboBoxFormField; + } + + /// Invokes the value changed callback. + void invokeValueChanged(String newValue) { + if (comboBoxFormField._selectedItem != newValue) { + if (!comboBoxFormField.items.contains(newValue)) { + throw ArgumentError.value( + newValue, + 'selectedItem', + 'The value is not in the list of items.', + ); + } + final String oldValue = comboBoxFormField._selectedItem; + setComboBoxValue(newValue); + if (onValueChanged != null) { + onValueChanged!( + PdfFormFieldValueChangedDetails( + comboBoxFormField, + oldValue, + newValue, + ), + ); + } + rebuild(); + } + } + + /// Sets the combo box value. + void setComboBoxValue(String selectedItem) { + comboBoxFormField._selectedItem = selectedItem; + pdfComboBoxField.selectedValue = selectedItem; + } + + /// Builds the combo box form field widget. + Widget build(BuildContext context, double heightPercentage) { + return Positioned( + left: bounds.left / heightPercentage, + top: bounds.top / heightPercentage, + width: bounds.width / heightPercentage, + height: bounds.height / heightPercentage, + child: PdfComboBox( + bounds: bounds, + heightPercentage: heightPercentage, + items: comboBoxFormField._items, + selectedItem: comboBoxFormField._selectedItem, + readOnly: comboBoxFormField.readOnly, + font: pdfComboBoxField.font?.name, + textAlign: pdfComboBoxField.textAlignment.textAlign, + alignment: pdfComboBoxField.textAlignment.alignmentGeometry, + fillColor: + pdfComboBoxField.backColor.isEmpty + ? pdfComboBoxField.readOnly + ? Colors.transparent + : const Color.fromARGB(255, 221, 228, 255) + : Color.fromRGBO( + pdfComboBoxField.backColor.r, + pdfComboBoxField.backColor.g, + pdfComboBoxField.backColor.b, + 1, + ), + borderColor: + pdfComboBoxField.borderColor.isEmpty + ? Colors.transparent + : Color.fromRGBO( + pdfComboBoxField.borderColor.r, + pdfComboBoxField.borderColor.g, + pdfComboBoxField.borderColor.b, + 1, + ), + borderWidth: pdfComboBoxField.borderWidth / heightPercentage, + fontSize: (pdfComboBoxField.font?.size ?? 14.0) / heightPercentage, + onValueChanged: invokeValueChanged, + ), + ); + } +} + +/// Customized combo box +class PdfComboBox extends StatefulWidget { + /// Initializes a new instance of the [PdfComboBox] class. + const PdfComboBox({ + required this.bounds, + required this.heightPercentage, + required this.items, + required this.selectedItem, + this.readOnly = false, + required this.fillColor, + this.font, + this.fontSize, + this.onValueChanged, + required this.borderColor, + required this.borderWidth, + this.textAlign = TextAlign.left, + this.alignment = Alignment.centerLeft, + super.key, + }); + + /// Combo box bounds. + final Rect bounds; + + /// Combo box scale factor. + final double heightPercentage; + + /// Combo box selected item. + final String selectedItem; + + /// Combo box items. + final List items; + + /// Combo box read only. + final bool readOnly; + + /// Combo box fill color. + final Color fillColor; + + /// Combo box font name. + final String? font; + + /// Combo box font size. + final double? fontSize; + + /// Combo box value changed callback. + final ValueChanged? onValueChanged; + + /// Combo box border color + final Color borderColor; + + /// Combo box border width + final double borderWidth; + + /// Combo box text widget alignment + final TextAlign textAlign; + + /// Combo box text alignment + final AlignmentGeometry alignment; + + @override + State createState() => _PdfComboBoxState(); +} + +class _PdfComboBoxState extends State { + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Container( + decoration: BoxDecoration( + color: widget.fillColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: DropdownButton( + alignment: widget.alignment, + value: widget.selectedItem.isNotEmpty ? widget.selectedItem : null, + items: + widget.items.map((String value) { + return DropdownMenuItem( + alignment: widget.alignment, + value: value, + child: Tooltip( + excludeFromSemantics: true, + message: value, + child: Text( + textAlign: widget.textAlign, + value, + style: const TextStyle(fontSize: 16.0), + overflow: TextOverflow.ellipsis, + ), + ), + ); + }).toList(), + isExpanded: true, + onChanged: + widget.readOnly + ? null + : (String? newValue) { + if (widget.onValueChanged != null && newValue != null) { + widget.onValueChanged!(newValue); + } + }, + style: Theme.of(context).textTheme.bodyMedium, + selectedItemBuilder: (BuildContext context) { + return widget.items.map((String value) { + return Padding( + padding: EdgeInsets.only(left: 2 / widget.heightPercentage), + child: Align( + alignment: widget.alignment, + child: Text( + value, + textAlign: widget.textAlign, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: widget.fontSize, + color: Colors.black, + ), + ), + ), + ); + }).toList(); + }, + underline: Container(), + menuMaxHeight: 300, + icon: Icon( + Icons.arrow_drop_down_outlined, + size: widget.bounds.height / widget.heightPercentage, + color: Colors.black, + ), + ), + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_form_field.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_form_field.dart new file mode 100644 index 000000000..1e1222a5b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_form_field.dart @@ -0,0 +1,104 @@ +import 'package:flutter/widgets.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; + +/// Represents the form field. +abstract class PdfFormField { + late final String _name; + late final int _pageNumber; + bool _readOnly = false; + late final PdfFormFieldHelper _helper; + + /// Gets the name of the [PdfFormField]. + String get name => _name; + + /// Gets the page number of the [PdfFormField]. + int get pageNumber => _pageNumber; + + /// Gets or sets a value indicating whether the [PdfFormField] is read-only. + bool get readOnly => _readOnly; + set readOnly(bool value) { + if (_readOnly != value) { + _readOnly = value; + _helper.pdfField.readOnly = value; + _helper.rebuild(); + } + } +} + +/// Represents the form field helper. +abstract class PdfFormFieldHelper { + /// Initializes a new instance of the [PdfFormFieldHelper] class. + PdfFormFieldHelper(this.pdfField, this.pageIndex); + + /// The pdf field object from pdf library. + final PdfField pdfField; + + /// The page index of the form field. + final int pageIndex; + + /// Gets the bounds of the form field. + late Rect bounds; + + /// Gets the global bounds of the form field. + Rect globalRect = Rect.zero; + + /// The callback which is called when the form field value changed. + VoidCallback? onChanged; + + /// Sets the name and read only property of the form field. + void load(PdfFormField formField) { + formField + .._name = pdfField.name! + .._pageNumber = pageIndex + 1 + .._readOnly = pdfField.readOnly + .._helper = this; + } + + /// Gets the helper of the form field. + static PdfFormFieldHelper getHelper(PdfFormField formField) { + return formField._helper; + } + + /// Rebuilds the form field. + void rebuild() { + if (onChanged != null) { + onChanged!(); + } + } + + /// Disposes the form field. + void dispose() { + onChanged = null; + } +} + +/// Converts PdfTextAlignment value to respective TextAlign value. +extension PdfTextAlignmentExtension on PdfTextAlignment { + TextAlign get textAlign { + switch (this) { + case PdfTextAlignment.left: + return TextAlign.left; + case PdfTextAlignment.center: + return TextAlign.center; + case PdfTextAlignment.right: + return TextAlign.right; + case PdfTextAlignment.justify: + return TextAlign.left; + } + } +} + +/// Converts PdfTextAlignment value to respective Alignment value. +extension TextAlignmentExtension on PdfTextAlignment { + AlignmentGeometry get alignmentGeometry { + switch (this) { + case PdfTextAlignment.left: + case PdfTextAlignment.justify: + return Alignment.centerLeft; + case PdfTextAlignment.right: + return Alignment.centerRight; + case PdfTextAlignment.center: + return Alignment.center; + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart new file mode 100644 index 000000000..61f43e642 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart @@ -0,0 +1,415 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../../pdfviewer.dart'; +import 'pdf_form_field.dart'; + +/// Represents the list box form field. +class PdfListBoxFormField extends PdfFormField { + PdfListBoxFormField._(); + + late List? _selectedItems; + late List _items; + + /// Gets the items of the [PdfListBoxFormField]. + List get items => List.unmodifiable(_items); + + /// Gets or sets the selected items of the [PdfListBoxFormField]. + List? get selectedItems => _selectedItems; + + set selectedItems(List? value) { + if (readOnly) { + return; + } + (PdfFormFieldHelper.getHelper(this) as PdfListBoxFormFieldHelper) + .invokeValueChanged(value); + } +} + +/// Helper class for [PdfListBoxFormField]. +class PdfListBoxFormFieldHelper extends PdfFormFieldHelper { + /// Initializes a new instance of the [PdfListBoxFormFieldHelper] class. + PdfListBoxFormFieldHelper( + this.pdfListBoxField, + int pageIndex, { + this.onValueChanged, + }) : super(pdfListBoxField, pageIndex) { + bounds = pdfListBoxField.bounds; + } + + /// The list box field from PDF library. + final PdfListBoxField pdfListBoxField; + + /// The callback which is called when the value of the form field is changed. + final PdfFormFieldValueChangedCallback? onValueChanged; + + /// Gets whether the list box field is multi select or not. + bool get isMultiSelect => pdfListBoxField.multiSelect; + + /// Gets the list box form field. + late PdfListBoxFormField listBoxFormField; + + /// Creates the list box form field object. + PdfListBoxFormField getFormField() { + final List items = []; + for (int index = 0; index < pdfListBoxField.items.count; index++) { + items.add(pdfListBoxField.items[index].text); + } + + final List selectedItems = List.from( + pdfListBoxField.selectedValues, + growable: false, + ); + + listBoxFormField = + PdfListBoxFormField._() + .._items = items + .._selectedItems = selectedItems; + super.load(listBoxFormField); + + return listBoxFormField; + } + + /// Invokes the value changed callback. + void invokeValueChanged(List? newValue) { + if (!listEquals(listBoxFormField._selectedItems, newValue)) { + if (newValue == null || newValue.isEmpty) { + if (!isMultiSelect) { + newValue = List.unmodifiable([ + listBoxFormField.items[0], + ]); + } else { + newValue ??= List.empty(); + } + } else { + newValue = newValue.toSet().toList(growable: false); + for (int i = 0; i < newValue.length; i++) { + if (!listBoxFormField._items.contains(newValue[i])) { + throw ArgumentError.value( + newValue[i], + 'selectedItems', + 'The item is not in the list.', + ); + } + } + if (!isMultiSelect) { + newValue = List.unmodifiable([newValue[0]]); + } + } + + final List oldValue = + listBoxFormField._selectedItems != null + ? List.from(listBoxFormField._selectedItems!) + : List.empty(); + + setListBoxValue(newValue); + if (onValueChanged != null) { + onValueChanged!( + PdfFormFieldValueChangedDetails(listBoxFormField, oldValue, newValue), + ); + } + + rebuild(); + } + } + + /// Sets the list box value. + void setListBoxValue(List selectedItems) { + listBoxFormField._selectedItems = selectedItems; + pdfListBoxField.selectedValues = selectedItems; + } + + /// Builds the list box form field widget. + Widget build(BuildContext context, double heightPercentage) { + return Positioned( + left: bounds.left / heightPercentage, + top: bounds.top / heightPercentage, + width: bounds.width / heightPercentage, + height: bounds.height / heightPercentage, + child: PdfListBox( + heightPercentage: heightPercentage, + selectedItems: listBoxFormField._selectedItems!, + items: listBoxFormField._items, + readOnly: listBoxFormField.readOnly, + font: pdfListBoxField.font?.name, + fillColor: + pdfListBoxField.backColor.isEmpty + ? pdfListBoxField.readOnly + ? Colors.transparent + : const Color.fromARGB(255, 221, 228, 255) + : Color.fromRGBO( + pdfListBoxField.backColor.r, + pdfListBoxField.backColor.g, + pdfListBoxField.backColor.b, + 1, + ), + borderColor: + pdfListBoxField.borderColor.isEmpty + ? Colors.transparent + : Color.fromRGBO( + pdfListBoxField.borderColor.r, + pdfListBoxField.borderColor.g, + pdfListBoxField.borderColor.b, + 1, + ), + borderWidth: pdfListBoxField.borderWidth / heightPercentage, + textAlign: pdfListBoxField.textAlignment.textAlign, + fontSize: (pdfListBoxField.font?.size ?? 14.0) / heightPercentage, + onValueChanged: invokeValueChanged, + onTap: () { + if (listBoxFormField.readOnly) { + return; + } + _showListBoxDialog(context, this); + }, + ), + ); + } +} + +/// Customized list box +class PdfListBox extends StatefulWidget { + /// Initializes a new instance of the [PdfListBox] class. + const PdfListBox({ + required this.heightPercentage, + required this.selectedItems, + required this.items, + this.readOnly = false, + required this.fillColor, + this.font, + this.fontSize, + this.onValueChanged, + required this.onTap, + required this.borderColor, + required this.borderWidth, + this.textAlign = TextAlign.left, + super.key, + }); + + /// The list box form field scale factor. + final double heightPercentage; + + /// The list box form field selected items. + final List selectedItems; + + /// The list box form field items. + final List items; + + /// The list box form field read only. + final bool readOnly; + + /// The list box form field fill color. + final Color fillColor; + + /// The list box form field font. + final String? font; + + /// The list box form field font size. + final double? fontSize; + + /// The list box form field value changed callback. + final ValueChanged>? onValueChanged; + + /// The list box form field tap callback. + final VoidCallback onTap; + + /// The list box form field border color + final Color borderColor; + + /// The list box form field border width + final double borderWidth; + + /// The list box form field text alignment + final TextAlign textAlign; + + @override + State createState() => _PdfListBoxState(); +} + +class _PdfListBoxState extends State { + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: InkWell( + onTap: widget.onTap, + child: Container( + decoration: BoxDecoration( + color: widget.fillColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: ListView.builder( + itemCount: widget.items.length, + itemBuilder: (BuildContext context, int index) { + final bool selected = widget.selectedItems.contains( + widget.items[index], + ); + return Container( + padding: const EdgeInsets.only(left: 5), + decoration: BoxDecoration( + color: + selected + ? const Color.fromARGB(255, 46, 134, 193) + : Colors.transparent, + ), + child: Text( + widget.items[index], + textAlign: widget.textAlign, + style: TextStyle( + color: Colors.black, + fontSize: widget.fontSize, + fontFamily: widget.font, + ), + ), + ); + }, + ), + ), + ), + ); + } +} + +/// Shows the list box dialog when the list box form field is tapped. +void _showListBoxDialog( + BuildContext context, + PdfListBoxFormFieldHelper listBoxHelper, +) { + final bool isMaterial3 = Theme.of(context).useMaterial3; + List newItems = List.from( + listBoxHelper.listBoxFormField._selectedItems!, + ); + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: + isMaterial3 + ? const EdgeInsets.only(left: 24.0, right: 16.0, top: 16.0) + : EdgeInsets.zero, + actionsPadding: + isMaterial3 + ? const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0) + : null, + content: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SizedBox( + height: 56.0 * listBoxHelper.listBoxFormField._items.length, + width: 348, + child: ListView.builder( + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return listBoxHelper.isMultiSelect + ? CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + title: Text( + listBoxHelper.listBoxFormField._items[index], + ), + value: newItems.contains( + listBoxHelper.listBoxFormField._items[index], + ), + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ) + : null, + contentPadding: isMaterial3 ? EdgeInsets.zero : null, + onChanged: (bool? value) { + setState(() { + if (value != null) { + if (value) { + newItems.add( + listBoxHelper.listBoxFormField._items[index], + ); + } else { + newItems.remove( + listBoxHelper.listBoxFormField._items[index], + ); + } + } + }); + }, + ) + : RadioListTile( + controlAffinity: ListTileControlAffinity.leading, + title: Text( + listBoxHelper.listBoxFormField._items[index], + ), + value: listBoxHelper.listBoxFormField._items[index], + groupValue: newItems.isEmpty ? null : newItems.first, + onChanged: (String? value) { + setState(() { + if (value != null) { + newItems = [value]; + } + }); + }, + ); + }, + itemCount: listBoxHelper.listBoxFormField._items.length, + ), + ); + }, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 10, + ), + fixedSize: const Size(double.infinity, 40), + ) + : null, + child: const Text( + 'CANCEL', + style: TextStyle( + fontWeight: FontWeight.w500, + fontFamily: 'Roboto-Medium', + ), + ), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + listBoxHelper.invokeValueChanged(newItems); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 10, + ), + fixedSize: const Size(74, 40), + ) + : null, + child: const Text( + 'OK', + style: TextStyle( + fontWeight: FontWeight.w500, + fontFamily: 'Roboto-Medium', + ), + ), + ), + ], + ); + }, + ); +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart new file mode 100644 index 000000000..44501897b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import 'pdf_form_field.dart'; + +/// Represents the radio button form field. +class PdfRadioFormField extends PdfFormField { + PdfRadioFormField._(); + + late String _selectedItem; + late List _items; + + /// Gets the items of the [PdfRadioFormField]. + List get items => List.unmodifiable(_items); + + /// Gets or sets a selected item of the [PdfRadioFormField]. + String get selectedItem => _selectedItem; + + set selectedItem(String value) { + if (readOnly) { + return; + } + (PdfFormFieldHelper.getHelper(this) as PdfRadioFormFieldHelper) + .invokeValueChanged(value); + } +} + +/// Helper class for [PdfRadioFormField]. +class PdfRadioFormFieldHelper extends PdfFormFieldHelper { + /// Initializes a new instance of the [PdfRadioFormFieldHelper] class. + PdfRadioFormFieldHelper( + this.pdfRadioField, + int pageIndex, { + this.onValueChanged, + }) : super(pdfRadioField, pageIndex) { + bounds = pdfRadioField.bounds; + } + + /// THe radio button field object from PDF library. + final PdfRadioButtonListField pdfRadioField; + + /// The callback which is called when the value of the form field is changed. + final PdfFormFieldValueChangedCallback? onValueChanged; + + /// The radio button form field object. + late PdfRadioFormField radioFormField; + + /// Flag to determine whether the form field can be reset to null value. + late final bool canReset; + + /// Creates the radio button form field object. + PdfRadioFormField getFormField() { + final List items = []; + for (int index = 0; index < pdfRadioField.items.count; index++) { + items.add(pdfRadioField.items[index].value); + } + final String selectedValue = + pdfRadioField.selectedIndex != -1 + ? items[pdfRadioField.selectedIndex] + : ''; + canReset = selectedValue == ''; + radioFormField = + PdfRadioFormField._() + .._items = items + .._selectedItem = selectedValue; + super.load(radioFormField); + + return radioFormField; + } + + /// Invokes the value changed callback. + void invokeValueChanged(String newValue) { + if (radioFormField._selectedItem != newValue) { + if (!radioFormField.items.contains(newValue)) { + throw ArgumentError.value( + newValue, + 'selectedItem', + 'The value is not in the list of items.', + ); + } + final String oldValue = radioFormField._selectedItem; + setRadioButtonValue(newValue); + if (onValueChanged != null) { + onValueChanged!( + PdfFormFieldValueChangedDetails(radioFormField, oldValue, newValue), + ); + } + rebuild(); + } + } + + /// Sets the radio button value. + void setRadioButtonValue(String selectedItem) { + radioFormField._selectedItem = selectedItem; + pdfRadioField.selectedValue = selectedItem; + } + + /// Builds the radio button form field. + List build(BuildContext context, double heightPercentage) { + final List widgets = []; + final PdfRadioButtonItemCollection item = pdfRadioField.items; + + for (int j = 0; j < item.count; j++) { + final Rect bounds = item[j].bounds; + final double selectionPadding = + kIsDesktop ? 0 : kFormFieldSelectionPadding; + final Rect adjustedBounds = bounds.inflate(selectionPadding); + + widgets.add( + Positioned( + left: adjustedBounds.left / heightPercentage, + top: adjustedBounds.top / heightPercentage, + width: adjustedBounds.width / heightPercentage, + height: adjustedBounds.height / heightPercentage, + child: PdfRadioButton( + groupValue: radioFormField._selectedItem, + value: item[j].value, + readOnly: radioFormField.readOnly, + onChanged: invokeValueChanged, + heightPercentage: heightPercentage, + selectionPadding: selectionPadding, + fillColor: + pdfRadioField.items[j].backColor.isEmpty + ? pdfRadioField.readOnly + ? Colors.transparent + : const Color.fromARGB(255, 221, 228, 255) + : Color.fromRGBO( + pdfRadioField.items[j].backColor.r, + pdfRadioField.items[j].backColor.g, + pdfRadioField.items[j].backColor.b, + 1, + ), + borderColor: + pdfRadioField.items[j].borderColor.isEmpty + ? Colors.transparent + : Color.fromRGBO( + pdfRadioField.items[j].borderColor.r, + pdfRadioField.items[j].borderColor.g, + pdfRadioField.items[j].borderColor.b, + 1, + ), + borderWidth: pdfRadioField.items[j].borderWidth / heightPercentage, + size: bounds.height / heightPercentage, + ), + ), + ); + } + + return widgets; + } +} + +/// Customized radio button +class PdfRadioButton extends StatefulWidget { + /// Constructor for PdfRadioButton + const PdfRadioButton({ + Key? key, + required this.value, + required this.groupValue, + this.readOnly = false, + required this.onChanged, + required this.heightPercentage, + required this.selectionPadding, + required this.fillColor, + required this.borderColor, + required this.borderWidth, + this.size = 24.0, + }) : super(key: key); + + /// Height percentage + final double heightPercentage; + + /// Radio button value + final String value; + + /// Radio button groupValue + final String? groupValue; + + /// Radio button readOnly + final bool readOnly; + + /// Radio button onChanged + final ValueChanged onChanged; + + /// Radio button fill color + final Color fillColor; + + /// Radio button size + final double size; + + /// Radio button padding + final double selectionPadding; + + /// Radio button border color + final Color borderColor; + + /// Radio button border width + final double borderWidth; + + @override + _PdfRadioButtonState createState() => _PdfRadioButtonState(); +} + +class _PdfRadioButtonState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + if (widget.readOnly) { + return; + } + widget.onChanged(widget.value); + }, + child: Padding( + padding: EdgeInsets.all( + widget.selectionPadding / widget.heightPercentage, + ), + child: Container( + width: widget.size, + height: widget.size, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.fillColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: + widget.groupValue == widget.value + ? Icon( + Icons.circle, + size: widget.size / 2.0, + color: Colors.black, + ) + : Container(), + ), + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart new file mode 100644 index 000000000..beafc3d1f --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart @@ -0,0 +1,792 @@ +import 'dart:math'; +import 'dart:ui' as ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import 'package:syncfusion_flutter_signaturepad/signaturepad.dart'; +import '../../pdfviewer.dart'; +import '../common/pdfviewer_helper.dart'; +import 'pdf_form_field.dart'; + +/// Represents the signature form field. +class PdfSignatureFormField extends PdfFormField { + PdfSignatureFormField._(); + + Uint8List? _signature; + + /// Gets or sets the signature image bytes of the [PdfSignatureFormField]. + Uint8List? get signature => _signature; + + set signature(Uint8List? value) { + if (readOnly) { + return; + } + (PdfFormFieldHelper.getHelper(this) as PdfSignatureFormFieldHelper) + .invokeValueChanged(value); + } +} + +/// Represents the signature form field helper. +class PdfSignatureFormFieldHelper extends PdfFormFieldHelper { + /// Initializes a new instance of the [PdfSignatureFormFieldHelper] class. + PdfSignatureFormFieldHelper( + this.pdfSignatureField, + int pageIndex, { + this.onValueChanged, + this.onFocusChange, + }) : super(pdfSignatureField, pageIndex) { + bounds = pdfSignatureField.bounds; + } + + /// The signature field object from PDF library. + final PdfSignatureField pdfSignatureField; + + /// The callback which is called when the value of the form field is changed. + final PdfFormFieldValueChangedCallback? onValueChanged; + + /// The callback which is called when the focus of the form field changes. + final PdfFormFieldFocusChangeCallback? onFocusChange; + + /// The signature form field object. + late PdfSignatureFormField signatureFormField; + + /// The PdfViewerController instance. + late PdfViewerController pdfViewerController; + + /// Indicates whether the signature dialog is shown or not. + bool canShowSignaturePadDialog = true; + + /// Creates the signature form field object. + PdfSignatureFormField getFormField() { + signatureFormField = PdfSignatureFormField._(); + super.load(signatureFormField); + + return signatureFormField; + } + + /// Sets the signature form field value. + void setSignature(Uint8List? signature) { + signatureFormField._signature = signature; + rebuild(); + } + + /// Invokes the value changed callback. + Future invokeValueChanged(Uint8List? newValue) async { + if (!listEquals(signatureFormField._signature, newValue)) { + bool isValid = false; + if (newValue != null) { + isValid = await _isValidImage(newValue); + } else { + isValid = true; + } + + if (!isValid) { + return; + } + final Uint8List? oldValue = + signatureFormField._signature != null + ? Uint8List.fromList(signatureFormField._signature!) + : null; + + signatureFormField._signature = newValue; + if (onValueChanged != null) { + onValueChanged!( + PdfFormFieldValueChangedDetails( + signatureFormField, + oldValue, + newValue, + ), + ); + } + rebuild(); + } + } + + /// Checks whether the image is valid or not. + Future _isValidImage(Uint8List bytes) async { + try { + await ui.instantiateImageCodec(bytes); + return true; + } catch (e) { + return false; + } + } + + /// The callback which is called when the signature field is tapped. + void _onSignatureFieldTapUp( + BuildContext context, + TapUpDetails details, + double heightPercentage, + ) { + if (!canShowSignaturePadDialog) { + return; + } + signatureFormField.signature != null + ? _showSignatureContextMenu( + context, + this, + details.globalPosition, + bounds.height / heightPercentage, + ) + : _showSignaturePadDialog(context, this); + } + + void _onSignatureFieldTapDown() { + if (onFocusChange != null) { + final PdfFormFieldFocusChangeDetails details = + PdfFormFieldFocusChangeDetails(signatureFormField, true); + onFocusChange!(details); + } + } + + /// Builds the signature form field widget. + Widget build(BuildContext context, double heightPercentage) { + return Positioned( + left: bounds.left / heightPercentage, + top: bounds.top / heightPercentage, + width: bounds.width / heightPercentage, + height: bounds.height / heightPercentage, + child: PdfSignature( + bounds: bounds, + heightPercentage: heightPercentage, + signature: signatureFormField.signature, + readOnly: signatureFormField.readOnly, + fillColor: + pdfSignatureField.backColor.isEmpty + ? pdfSignatureField.readOnly + ? Colors.transparent + : const Color.fromARGB(255, 221, 228, 255) + : Color.fromRGBO( + pdfSignatureField.backColor.r, + pdfSignatureField.backColor.g, + pdfSignatureField.backColor.b, + 1, + ), + borderColor: + pdfSignatureField.borderColor.isEmpty + ? Colors.transparent + : Color.fromRGBO( + pdfSignatureField.borderColor.r, + pdfSignatureField.borderColor.g, + pdfSignatureField.borderColor.b, + 1, + ), + borderWidth: pdfSignatureField.borderWidth / heightPercentage, + onValueChanged: invokeValueChanged, + onSignatureFieldTapDown: (TapDownDetails details) { + if (signatureFormField.readOnly) { + return; + } + _onSignatureFieldTapDown(); + }, + onSignatureFieldTapUp: (TapUpDetails details) { + if (signatureFormField.readOnly || !canShowSignaturePadDialog) { + return; + } + _onSignatureFieldTapUp(context, details, heightPercentage); + }, + ), + ); + } +} + +/// Customized signature. +class PdfSignature extends StatefulWidget { + /// Initializes a new instance of the [PdfSignature] class. + const PdfSignature({ + required this.bounds, + required this.heightPercentage, + this.signature, + this.readOnly = false, + required this.fillColor, + this.onValueChanged, + required this.onSignatureFieldTapUp, + required this.onSignatureFieldTapDown, + required this.borderColor, + required this.borderWidth, + super.key, + }); + + /// Signature field bounds. + final Rect bounds; + + /// Signature field scale factor. + final double heightPercentage; + + /// Signature field signature. + final Uint8List? signature; + + /// Signature field read only. + final bool readOnly; + + /// Signature field fill color. + final Color fillColor; + + /// Signature field value changed callback. + final ValueChanged? onValueChanged; + + /// Signature field tap up callback. + final GestureTapUpCallback onSignatureFieldTapUp; + + /// Signature field tap down callback. + final GestureTapDownCallback onSignatureFieldTapDown; + + /// Signature field border color + final Color borderColor; + + /// Signature field border width + final double borderWidth; + + @override + State createState() => _PdfSignatureState(); +} + +class _PdfSignatureState extends State { + @override + Widget build(BuildContext context) { + return InkWell( + onTapDown: widget.onSignatureFieldTapDown, + onTapUp: widget.onSignatureFieldTapUp, + child: Container( + height: widget.bounds.height, + width: widget.bounds.width, + decoration: BoxDecoration( + color: widget.fillColor, + border: Border.all( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + child: + widget.signature != null + ? Image.memory(widget.signature!) + : Container(), + ), + ); + } +} + +/// Show context menu for signature field +void _showSignatureContextMenu( + BuildContext context, + PdfSignatureFormFieldHelper signatureFieldHelper, + Offset position, + double height, +) { + final bool isMaterial3 = Theme.of(context).useMaterial3; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject()! as RenderBox; + final RenderBox button = context.findRenderObject()! as RenderBox; + const double kPopUpMenuHeight = 48; + const double kPopupMenuWidth = 130; + final RenderBox? parentBox = context.findRenderObject() as RenderBox?; + final Offset buttonPosition = parentBox!.localToGlobal(Offset.zero); + + final Offset localPosition = button.globalToLocal(buttonPosition + position); + showMenu( + context: context, + constraints: const BoxConstraints(maxWidth: kPopupMenuWidth), + position: RelativeRect.fromLTRB( + localPosition.dx * signatureFieldHelper.pdfViewerController.zoomLevel, + (localPosition.dy * signatureFieldHelper.pdfViewerController.zoomLevel) - + (kPopUpMenuHeight + height), + overlay.size.width - + localPosition.dx * signatureFieldHelper.pdfViewerController.zoomLevel, + overlay.size.height - + localPosition.dy * signatureFieldHelper.pdfViewerController.zoomLevel, + ), + items: >[ + if (!isMaterial3) + PopupMenuItem( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.draw_sharp), + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + onPressed: () { + Navigator.pop(context); + _showSignaturePadDialog(context, signatureFieldHelper); + }, + ), + IconButton( + icon: const Icon(Icons.delete), + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + onPressed: () { + signatureFieldHelper.signatureFormField.signature = null; + Navigator.pop(context); + }, + ), + ], + ), + ) + else + CustomPopupMenuItem( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + margin: const EdgeInsets.only(left: 8, top: 4, bottom: 4), + width: 40, + height: 40, + child: InkWell( + borderRadius: BorderRadius.circular(6.0), + onTap: () { + Navigator.pop(context); + _showSignaturePadDialog(context, signatureFieldHelper); + }, + child: const Icon(Icons.draw_outlined), + ), + ), + Container( + margin: const EdgeInsets.only(right: 8, top: 4, bottom: 4), + width: 40, + height: 40, + child: InkWell( + borderRadius: BorderRadius.circular(8.0), + onTap: () { + signatureFieldHelper.signatureFormField.signature = null; + Navigator.pop(context); + }, + child: const Icon(Icons.delete_outline), + ), + ), + ], + ), + ), + ], + ); +} + +final GlobalKey _signaturePadKey = GlobalKey(); +late List _strokeColorWidgets; +late Color _strokeColor; +List _strokeColors = []; +int _selectedPenIndex = 0; +bool _isSignatureDrawn = false; + +/// Dialog view for signature pad +void _showSignaturePadDialog( + BuildContext context, + PdfSignatureFormFieldHelper signatureFieldHelper, +) { + final bool isMaterial3 = Theme.of(context).useMaterial3; + _addColors(); + _isSignatureDrawn = false; + final SfLocalizations localizations = SfLocalizations.of(context); + final ThemeData themeData = Theme.of(context); + showDialog( + barrierDismissible: false, + context: context, + builder: (BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final double signaturePadWidth = + kIsDesktop + ? max(constraints.maxWidth, constraints.maxHeight) * + (isMaterial3 ? 0.27 : 0.25) + : MediaQuery.of(context).size.width < kSignaturePadWidth + ? MediaQuery.of(context).size.width + : kSignaturePadWidth; + final double signaturePadHeight = + kIsDesktop + ? signaturePadWidth * (isMaterial3 ? 0.53 : 0.6) + : kSignaturePadHeight; + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return AlertDialog( + backgroundColor: + isMaterial3 + ? Theme.of(context).brightness == Brightness.light + ? const Color(0xFFEEE8F4) + : const Color(0xFF302D38) + : null, + insetPadding: const EdgeInsets.all(12.0), + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(28.0), + ) + : null, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + localizations.pdfSignaturePadDialogHeaderTextLabel, + style: + isMaterial3 + ? Theme.of(context).textTheme.bodyLarge!.copyWith( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.w400, + ) + : Theme.of( + context, + ).textTheme.titleMedium!.copyWith( + fontSize: 16, + fontFamily: 'Roboto-Medium', + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ), + ), + InkWell( + onTap: () { + Navigator.of(context).pop(); + if (signatureFieldHelper.onFocusChange != null) { + signatureFieldHelper.onFocusChange!( + PdfFormFieldFocusChangeDetails( + signatureFieldHelper.signatureFormField, + false, + ), + ); + } + }, + borderRadius: + isMaterial3 ? BorderRadius.circular(20.0) : null, + child: + isMaterial3 + ? SizedBox.square( + dimension: 40, + child: Icon( + Icons.clear, + size: 24, + color: + Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + ) + : const Icon(Icons.clear, size: 24.0), + ), + ], + ), + titlePadding: + isMaterial3 + ? const EdgeInsets.only( + left: 24.0, + top: 16.0, + right: 16.0, + bottom: 16, + ) + : const EdgeInsets.all(16.0), + content: SingleChildScrollView( + child: SizedBox( + width: signaturePadWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: signaturePadHeight, + decoration: BoxDecoration( + border: + isMaterial3 + ? Border.all( + color: + Theme.of( + context, + ).colorScheme.outlineVariant, + ) + : Border.all(color: Colors.grey[350]!), + color: Colors.white, + borderRadius: + isMaterial3 ? BorderRadius.circular(4.0) : null, + ), + child: SfSignaturePad( + strokeColor: _strokeColor, + key: _signaturePadKey, + onDrawEnd: () { + if (!_isSignatureDrawn) { + setState(() { + _isSignatureDrawn = true; + }); + } + }, + ), + ), + const SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + localizations.pdfSignaturePadDialogPenColorLabel, + style: Theme.of( + context, + ).textTheme.bodyMedium!.copyWith( + fontSize: 14, + fontWeight: + isMaterial3 ? FontWeight.w400 : null, + fontFamily: 'Roboto-Regular', + color: + isMaterial3 + ? Theme.of( + context, + ).colorScheme.onSurface + : Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ), + ), + SizedBox( + width: 128, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: _addStrokeColorPalettes( + setState, + context, + ), + ), + ), + ], + ), + ], + ), + ), + ), + contentPadding: + isMaterial3 + ? EdgeInsets.symmetric( + horizontal: kIsDesktop ? 20 : 24.0, + ) + : const EdgeInsets.symmetric(horizontal: 12.0), + actionsPadding: + isMaterial3 + ? const EdgeInsets.all(24) + : const EdgeInsets.all(8.0), + buttonPadding: EdgeInsets.zero, + actions: [ + TextButton( + onPressed: + !_isSignatureDrawn + ? null + : () { + _handleSignatureClearButtonPressed(); + setState(() { + _isSignatureDrawn = false; + }); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 10, + ), + ) + : null, + child: Text( + localizations.pdfSignaturePadDialogClearLabel, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w400 : null, + fontFamily: 'Roboto-Medium', + color: themeData.colorScheme.primary, + ), + ), + ), + const SizedBox(width: 8.0), + TextButton( + onPressed: + !_isSignatureDrawn + ? null + : () { + _handleSignatureSaveButtonPressed( + signatureFieldHelper, + ); + Navigator.of(context).pop(); + if (signatureFieldHelper.onFocusChange != null) { + signatureFieldHelper.onFocusChange!( + PdfFormFieldFocusChangeDetails( + signatureFieldHelper.signatureFormField, + false, + ), + ); + } + }, + style: + isMaterial3 + ? ElevatedButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.primary, + disabledBackgroundColor: + Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 10, + ), + fixedSize: const Size(double.infinity, 40), + ) + : null, + child: Text( + localizations.pdfSignaturePadDialogSaveLabel, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w400 : null, + color: + isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.primary, + fontFamily: 'Roboto-Medium', + ), + ), + ), + ], + ); + }, + ); + }, + ); + }, + ); +} + +/// Save the signature as image +Future _handleSignatureSaveButtonPressed( + PdfSignatureFormFieldHelper signatureFieldHelper, +) async { + final ui.Image imageData = await _signaturePadKey.currentState!.toImage( + pixelRatio: 3.0, + ); + final ByteData? bytes = await imageData.toByteData( + format: ui.ImageByteFormat.png, + ); + + signatureFieldHelper.signatureFormField.signature = + bytes!.buffer.asUint8List(); +} + +/// Clear the signature in the signaturepad +void _handleSignatureClearButtonPressed() { + _signaturePadKey.currentState!.clear(); +} + +/// Add colors to the stroke color palette +void _addColors() { + _strokeColors = []; + _strokeColors.add(const Color.fromRGBO(0, 0, 0, 1)); + _strokeColors.add(const Color.fromRGBO(35, 93, 217, 1)); + _strokeColors.add(const Color.fromRGBO(77, 180, 36, 1)); + _strokeColors.add(const Color.fromRGBO(228, 77, 49, 1)); + _strokeColor = _strokeColors[_selectedPenIndex]; +} + +/// Add stroke color palettes +List _addStrokeColorPalettes( + StateSetter stateChanged, + BuildContext context, +) { + final bool isMaterial3 = Theme.of(context).useMaterial3; + _strokeColorWidgets = []; + for (int i = 0; i < _strokeColors.length; i++) { + _strokeColorWidgets.add( + Material( + color: Colors.transparent, + child: Ink( + decoration: const BoxDecoration( + color: Colors.transparent, + shape: BoxShape.circle, + ), + child: InkWell( + onTap: + () => stateChanged(() { + _strokeColor = _strokeColors[i]; + _selectedPenIndex = i; + }), + overlayColor: + isMaterial3 + ? const WidgetStatePropertyAll(Colors.transparent) + : null, + child: Center( + child: Stack( + children: [ + Padding( + padding: + isMaterial3 ? const EdgeInsets.all(4) : EdgeInsets.zero, + child: Icon( + Icons.brightness_1, + size: isMaterial3 ? 24.0 : 25.0, + color: _strokeColors[i], + ), + ), + if (_selectedPenIndex == i) + isMaterial3 + ? Icon( + Icons.circle_outlined, + size: 32.0, + color: Theme.of(context).colorScheme.primary, + ) + : const Padding( + padding: EdgeInsets.all(5), + child: Icon( + Icons.check, + size: 15.0, + color: Colors.white, + ), + ) + else + const SizedBox(width: 8), + ], + ), + ), + ), + ), + ), + ); + } + return _strokeColorWidgets; +} + +/// A custom popup menu item +class CustomPopupMenuItem extends PopupMenuItem { + /// Creates a new instance of [CustomPopupMenuItem]. + const CustomPopupMenuItem({ + super.key, + super.value, + super.onTap, + super.enabled = true, + super.height = kMinInteractiveDimension, + super.padding, + super.textStyle, + super.labelTextStyle, + super.mouseCursor, + required super.child, + }); + + @override + CustomPopupMenuItemState> createState() => + CustomPopupMenuItemState>(); +} + +/// The state class for [CustomPopupMenuItem]. +class CustomPopupMenuItemState> + extends PopupMenuItemState { + @override + Widget build(BuildContext context) { + return MergeSemantics( + child: Semantics( + button: true, + child: ListTileTheme.merge( + contentPadding: EdgeInsets.zero, + child: buildChild()!, + ), + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart new file mode 100644 index 000000000..abd7e2d87 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart @@ -0,0 +1,387 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import '../../pdfviewer.dart'; +import '../change_tracker/change_tracker.dart'; +import 'pdf_form_field.dart'; + +/// Represents the text form field. +class PdfTextFormField extends PdfFormField { + PdfTextFormField._(); + + late String _text; + List? _children; + + /// Gets or sets the text of the [PdfTextFormField]. + String get text => _text; + + set text(String value) { + if (readOnly) { + return; + } + (PdfFormFieldHelper.getHelper(this) as PdfTextFormFieldHelper) + .invokeValueChanged(value); + } + + @override + set readOnly(bool value) { + if (readOnly != value) { + super.readOnly = value; + if (_children != null && _children!.isNotEmpty) { + for (final PdfTextFormField item in _children!) { + item.readOnly = value; + } + } + } + } + + /// Gets the child items associated with this [PdfTextFormField]. + List? get children => + _children != null + ? List.unmodifiable(_children!) + : null; +} + +/// Helper class for [PdfTextFormField]. +class PdfTextFormFieldHelper extends PdfFormFieldHelper { + /// Initializes a new instance of the [PdfTextFormFieldHelper] class. + PdfTextFormFieldHelper( + this.pdfTextField, + int pageIndex, { + this.onValueChanged, + this.onFocusChanged, + }) : super(pdfTextField, pageIndex) { + bounds = pdfTextField.bounds; + } + + bool _hasFocus = false; + + /// The text field object from PDF library. + final PdfTextBoxField pdfTextField; + + /// The callback which is called when the value of the form field is changed. + final PdfFormFieldValueChangedCallback? onValueChanged; + + /// The callback which is called when the focus of the form field changes. + final PdfFormFieldFocusChangeCallback? onFocusChanged; + + /// The text form field object. + late PdfTextFormField textFormField; + + /// The text editing controller. + late TextEditingController textEditingController; + + /// The focus node. + late FocusNode focusNode; + + /// Creates the text form field object. + PdfTextFormField getFormField(ChangeTracker changeTracker) { + textFormField = PdfTextFormField._().._text = pdfTextField.text; + super.load(textFormField); + + textEditingController = TextEditingController(text: textFormField._text); + focusNode = createFocusNode(changeTracker); + + return textFormField; + } + + /// Sets the child items of the text form field. + set textFormFieldChildItems(List? groupedItems) { + textFormField._children = groupedItems; + } + + /// Gets the child items of the text form field. + List? get textFormFieldChildItems => + textFormField._children; + + /// Gets the focus node of the [PdfTextFormField]. + FocusNode createFocusNode(ChangeTracker changeTracker) { + return FocusNode( + onKeyEvent: (FocusNode focusNode, KeyEvent event) { + final bool isControlOrMeta = + HardwareKeyboard.instance.isControlPressed || + HardwareKeyboard.instance.isMetaPressed; + final bool isLogicalOrPhysicalZ = + event.logicalKey == LogicalKeyboardKey.keyZ || + event.physicalKey == PhysicalKeyboardKey.keyZ; + final bool isLogicalOrPhysicalY = + event.logicalKey == LogicalKeyboardKey.keyY || + event.physicalKey == PhysicalKeyboardKey.keyY; + + if (isControlOrMeta && isLogicalOrPhysicalZ) { + if (event is KeyDownEvent) { + changeTracker.undoController.undo(); + } + return KeyEventResult.handled; + } else if (isControlOrMeta && isLogicalOrPhysicalY) { + if (event is KeyDownEvent) { + changeTracker.undoController.redo(); + } + return KeyEventResult.handled; + } else { + return KeyEventResult.ignored; + } + }, + )..addListener(() { + if (!focusNode.hasFocus) { + invokeFocusChange(focusNode.hasFocus); + } + }); + } + + /// Updates the text form field. + void invokeValueChanged(String newValue) { + if (textFormField._text != newValue) { + newValue = + pdfTextField.maxLength > 0 && newValue.length > pdfTextField.maxLength + ? newValue.substring(0, pdfTextField.maxLength) + : newValue; + final String oldValue = textFormField._text; + setTextBoxValue(newValue); + if (onValueChanged != null) { + onValueChanged!( + PdfFormFieldValueChangedDetails(textFormField, oldValue, newValue), + ); + } + rebuild(); + } + } + + /// Invokes the focus change. + void invokeFocusChange(bool hasFocus) { + if (!textFormField.readOnly && + _hasFocus != hasFocus && + onFocusChanged != null) { + onFocusChanged!(PdfFormFieldFocusChangeDetails(textFormField, hasFocus)); + } + _hasFocus = hasFocus; + } + + /// Sets the text box value. + void setTextBoxValue(String text) { + textFormField._text = text; + if (textEditingController.text != text) { + textEditingController.text = text; + } + _updateChildItems(text); + pdfTextField.text = text; + } + + /// Updates the grouped field items. + void _updateChildItems(String text) { + if (textFormField._children != null && + textFormField._children!.isNotEmpty) { + for (final PdfTextFormField item in textFormField._children!) { + final PdfFormFieldHelper childHelper = PdfFormFieldHelper.getHelper( + item, + ); + if (childHelper is PdfTextFormFieldHelper && + childHelper.textEditingController.text != text) { + childHelper.textEditingController.text = text; + } + } + } + } + + /// Builds the text form field widget. + Widget build(BuildContext context, double heightPercentage) { + return Positioned( + left: bounds.left / heightPercentage, + top: bounds.top / heightPercentage, + width: bounds.width / heightPercentage, + height: bounds.height / heightPercentage, + child: PdfTextBox( + textEditingController: textEditingController, + focusNode: focusNode, + readOnly: textFormField.readOnly, + font: pdfTextField.font.name, + fontSize: pdfTextField.font.size / heightPercentage, + isPassword: pdfTextField.isPassword, + fillColor: + pdfTextField.backColor.isEmpty + ? pdfTextField.readOnly + ? Colors.transparent + : const Color.fromARGB(255, 221, 228, 255) + : Color.fromRGBO( + pdfTextField.backColor.r, + pdfTextField.backColor.g, + pdfTextField.backColor.b, + 1, + ), + borderColor: + pdfTextField.borderColor.isEmpty + ? Colors.transparent + : Color.fromRGBO( + pdfTextField.borderColor.r, + pdfTextField.borderColor.g, + pdfTextField.borderColor.b, + 1, + ), + borderWidth: pdfTextField.borderWidth / heightPercentage, + textAlign: pdfTextField.textAlignment.textAlign, + multiline: pdfTextField.multiline, + maxLength: pdfTextField.maxLength, + letterSpacing: + pdfTextField.insertSpaces && pdfTextField.maxLength > 1 + ? (pdfTextField.bounds.width / pdfTextField.maxLength - 1) / + heightPercentage + : null, + onValueChanged: invokeValueChanged, + onFocusChange: invokeFocusChange, + ), + ); + } + + @override + void dispose() { + textEditingController.dispose(); + focusNode.dispose(); + super.dispose(); + } +} + +/// Customized text box. +class PdfTextBox extends StatefulWidget { + /// Creates a text form field widget. + const PdfTextBox({ + required this.textEditingController, + required this.focusNode, + this.readOnly = false, + required this.font, + required this.fontSize, + this.isPassword = false, + required this.fillColor, + this.multiline = false, + this.maxLength = 0, + this.letterSpacing, + this.onValueChanged, + this.onFocusChange, + required this.borderColor, + required this.borderWidth, + this.textAlign = TextAlign.left, + super.key, + }); + + /// Text form field text editing controller. + final TextEditingController textEditingController; + + /// Text form field focus node. + final FocusNode focusNode; + + /// Text form field read only. + final bool readOnly; + + /// Text form field is password. + final bool isPassword; + + /// Text form field fill color. + final Color fillColor; + + /// Text form field is multiline. + final bool multiline; + + /// Text form field font. + final String font; + + /// Text form field font size. + final double fontSize; + + /// Text form field value changed. + final ValueChanged? onValueChanged; + + /// Text form field focus change. + final ValueChanged? onFocusChange; + + /// Text form field maximum length. + final int maxLength; + + /// Text form field letter spacing. + final double? letterSpacing; + + /// Text form field border color + final Color borderColor; + + /// Text form field border width + final double borderWidth; + + /// Text form field text alignment + final TextAlign textAlign; + + @override + State createState() => _PdfTextBoxState(); +} + +class _PdfTextBoxState extends State { + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: TextFormField( + onTap: () { + if (widget.onFocusChange != null) { + widget.onFocusChange!(true); + } + }, + controller: widget.textEditingController, + focusNode: !widget.readOnly ? widget.focusNode : null, + readOnly: widget.readOnly, + maxLines: widget.multiline ? null : 1, + cursorColor: Colors.black, + obscureText: widget.isPassword, + onChanged: widget.onValueChanged, + inputFormatters: + widget.maxLength > 0 + ? [ + LengthLimitingTextInputFormatter(widget.maxLength), + ] + : null, + keyboardType: + widget.multiline ? TextInputType.multiline : TextInputType.text, + scrollPhysics: widget.multiline ? const ClampingScrollPhysics() : null, + cursorWidth: 0.5, + expands: widget.multiline, + textAlignVertical: TextAlignVertical.top, + textAlign: widget.textAlign, + style: + widget.letterSpacing != null + ? TextStyle( + color: Colors.black, + fontFamily: 'RobotoMono', + package: 'syncfusion_flutter_pdfviewer', + fontSize: widget.fontSize, + letterSpacing: widget.letterSpacing! - widget.fontSize / 2, + ) + : TextStyle( + color: Colors.black, + fontFamily: widget.font, + fontSize: widget.fontSize, + ), + decoration: InputDecoration( + filled: true, + fillColor: widget.fillColor, + hoverColor: widget.readOnly ? Colors.transparent : null, + contentPadding: + widget.multiline + ? const EdgeInsets.all(3) + : widget.letterSpacing != null + ? EdgeInsets.zero + : const EdgeInsets.symmetric(horizontal: 3), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.zero, + borderSide: BorderSide( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.zero, + borderSide: BorderSide( + color: widget.borderColor, + width: widget.borderWidth, + ), + ), + ), + ), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart index 8ec6aecb5..2789bc5da 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart @@ -1,23 +1,10 @@ -/// [SfPdfViewer] lets you display the PDF document seamlessly and efficiently. -/// It is built in the way that a large PDF document can be opened in -/// minimal time and all their pages can be accessed spontaneously. -/// -/// To use, import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=f1zEJZRdo7w} -/// -/// See also: -/// * [Syncfusion Flutter PDF Viewer product page](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer) -/// * [User guide documentation](https://help.syncfusion.com/flutter/pdf-viewer/overview) -/// * [Video tutorials](https://www.syncfusion.com/tutorial-videos/flutter/pdf-viewer) -/// * [Knowledge base](https://www.syncfusion.com/kb/flutter) - +import 'dart:async'; import 'dart:io'; +import 'dart:isolate'; import 'dart:math'; -import 'dart:typed_data'; -import 'dart:ui'; import 'package:async/async.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -25,30 +12,54 @@ import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_core/localizations.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_page_view.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; +import 'annotation/annotation.dart'; +import 'annotation/annotation_settings.dart'; +import 'annotation/sticky_notes.dart'; +import 'annotation/text_markup.dart'; import 'bookmark/bookmark_view.dart'; -import 'common/pdf_provider.dart'; +import 'change_tracker/change_command.dart'; +import 'change_tracker/change_tracker.dart'; +import 'common/mobile_helper.dart' + if (dart.library.js_interop) 'common/web_helper.dart' + as helper; +import 'common/pdf_source.dart'; import 'common/pdfviewer_helper.dart'; +import 'common/pdfviewer_plugin.dart'; import 'control/enums.dart'; import 'control/pagination.dart'; +import 'control/pdf_page_view.dart'; +import 'control/pdf_scrollable.dart'; import 'control/pdftextline.dart'; import 'control/pdfviewer_callback_details.dart'; import 'control/single_page_view.dart'; +import 'control/sticky_note_edit_text.dart'; +import 'control/text_selection_menu.dart'; +import 'form_fields/pdf_checkbox.dart'; +import 'form_fields/pdf_combo_box.dart'; +import 'form_fields/pdf_form_field.dart'; +import 'form_fields/pdf_list_box.dart'; +import 'form_fields/pdf_radio_button.dart'; +import 'form_fields/pdf_signature.dart'; +import 'form_fields/pdf_text_box.dart'; +import 'text_extraction/text_extraction.dart'; +import 'theme/theme.dart'; /// Signature for [SfPdfViewer.onTextSelectionChanged] callback. -typedef PdfTextSelectionChangedCallback = void Function( - PdfTextSelectionChangedDetails details); +typedef PdfTextSelectionChangedCallback = + void Function(PdfTextSelectionChangedDetails details); + +/// Signature for [SfPdfViewer.onHyperlinkClicked] callback. +typedef PdfHyperlinkClickedCallback = + void Function(PdfHyperlinkClickedDetails details); /// Signature for [SfPdfViewer.onDocumentLoaded] callback. -typedef PdfDocumentLoadedCallback = void Function( - PdfDocumentLoadedDetails details); +typedef PdfDocumentLoadedCallback = + void Function(PdfDocumentLoadedDetails details); /// Signature for [SfPdfViewer.onDocumentLoadFailed] callback. -typedef PdfDocumentLoadFailedCallback = void Function( - PdfDocumentLoadFailedDetails details); +typedef PdfDocumentLoadFailedCallback = + void Function(PdfDocumentLoadFailedDetails details); /// Signature for [SfPdfViewer.onZoomLevelChanged] callback. typedef PdfZoomLevelChangedCallback = void Function(PdfZoomDetails details); @@ -59,6 +70,20 @@ typedef PdfPageChangedCallback = void Function(PdfPageChangedDetails details); /// This callback invoked whenever listener called typedef _PdfControllerListener = void Function({String? property}); +/// Signature for [SfPdfViewer.onTap] callback. +typedef PdfGestureTapCallback = void Function(PdfGestureDetails details); + +/// Signature for [SfPdfViewer.onFormFieldValueChanged] callback. +typedef PdfFormFieldValueChangedCallback = + void Function(PdfFormFieldValueChangedDetails details); + +/// Signature for [SfPdfViewer.onFormFieldFocusChange] callback. +typedef PdfFormFieldFocusChangeCallback = + void Function(PdfFormFieldFocusChangeDetails details); + +/// Signature for [SfPdfViewer.onAnnotationAdded], [SfPdfViewer.onAnnotationSelected], [SfPdfViewer.onAnnotationDeselected], [SfPdfViewer.onAnnotationEdited] and [SfPdfViewer.onAnnotationRemoved] callbacks. +typedef PdfAnnotationCallback = void Function(Annotation annotation); + /// A widget to view PDF documents. /// /// [SfPdfViewer] lets you display the PDF document seamlessly and efficiently. @@ -94,6 +119,82 @@ typedef _PdfControllerListener = void Function({String? property}); /// ``` @immutable class SfPdfViewer extends StatefulWidget { + /// Creates a widget that displays a PDF document from various sources. + /// + /// The [source] parameter is required and can be one of the following: + /// * [AssetPDFSource]: For PDFs stored in the app's assets + /// * [URLPDFSource]: For PDFs accessed via a network URL + /// * [FilePDFSource]: For PDFs stored as files on the device + /// * [BytePDFSource]: For PDFs stored in memory as bytes + + /// Example usage: + /// ```dart + /// // From an asset + /// SfPdfViewer(source: AssetPDFSource('assets/document.pdf')) + + /// // From a network URL + /// SfPdfViewer(source: URLPDFSource('https://example.com/document.pdf')) + + /// // From a file + /// SfPdfViewer(source: FilePDFSource(File('/path/to/document.pdf'))) + + /// // From memory + /// SfPdfViewer(source: BytePDFSource(Uint8List(...))) + /// ``` + SfPdfViewer({ + /// The source of the PDF document to be displayed. + required PDFSource source, + Key? key, + this.canShowScrollHead = true, + this.pageSpacing = 4, + this.controller, + this.undoController, + this.onZoomLevelChanged, + this.canShowPageLoadingIndicator = true, + this.canShowScrollStatus = true, + this.onPageChanged, + this.onDocumentLoaded, + this.enableDoubleTapZooming = true, + this.enableTextSelection = true, + this.onTextSelectionChanged, + this.onHyperlinkClicked, + this.onDocumentLoadFailed, + this.onTap, + this.onFormFieldValueChanged, + this.onFormFieldFocusChange, + this.onAnnotationAdded, + this.onAnnotationSelected, + this.onAnnotationDeselected, + this.onAnnotationEdited, + this.onAnnotationRemoved, + this.enableDocumentLinkAnnotation = true, + this.canShowPaginationDialog = true, + this.canShowSignaturePadDialog = true, + this.initialScrollOffset = Offset.zero, + this.initialZoomLevel = 1, + this.initialPageNumber = 1, + this.maxZoomLevel = 3, + this.interactionMode = PdfInteractionMode.selection, + this.scrollDirection, + this.pageLayoutMode = PdfPageLayoutMode.continuous, + this.currentSearchTextHighlightColor = const Color.fromARGB( + 80, + 249, + 125, + 0, + ), + this.otherSearchTextHighlightColor = const Color.fromARGB(50, 255, 255, 1), + this.password, + this.canShowPasswordDialog = true, + this.canShowHyperlinkDialog = true, + this.enableHyperlinkNavigation = true, + this.canShowTextSelectionMenu = true, + }) : _source = source, + assert(pageSpacing >= 0), + assert(!maxZoomLevel.isNaN), + assert(maxZoomLevel >= 1), + super(key: key); + /// Creates a widget that displays the PDF document obtained from an asset bundle. /// /// ``` dart @@ -123,29 +224,52 @@ class SfPdfViewer extends StatefulWidget { this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, + this.undoController, this.onZoomLevelChanged, + this.canShowPageLoadingIndicator = true, this.canShowScrollStatus = true, this.onPageChanged, this.onDocumentLoaded, this.enableDoubleTapZooming = true, this.enableTextSelection = true, this.onTextSelectionChanged, + this.onHyperlinkClicked, this.onDocumentLoadFailed, + this.onTap, + this.onFormFieldValueChanged, + this.onFormFieldFocusChange, + this.onAnnotationAdded, + this.onAnnotationSelected, + this.onAnnotationDeselected, + this.onAnnotationEdited, + this.onAnnotationRemoved, this.enableDocumentLinkAnnotation = true, this.canShowPaginationDialog = true, + this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, + this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, - this.scrollDirection = PdfScrollDirection.vertical, + this.scrollDirection, this.pageLayoutMode = PdfPageLayoutMode.continuous, - this.currentSearchTextHighlightColor = - const Color.fromRGBO(229, 110, 0, 0.6), - this.otherSearchTextHighlightColor = const Color.fromRGBO(229, 110, 0, 0.3), + this.currentSearchTextHighlightColor = const Color.fromARGB( + 80, + 249, + 125, + 0, + ), + this.otherSearchTextHighlightColor = const Color.fromARGB(50, 255, 255, 1), this.password, this.canShowPasswordDialog = true, - }) : _provider = AssetPdf(name, bundle), - assert(pageSpacing >= 0), - super(key: key); + this.canShowHyperlinkDialog = true, + this.enableHyperlinkNavigation = true, + this.canShowTextSelectionMenu = true, + }) : _source = AssetPDFSource(name, bundle: bundle), + assert(pageSpacing >= 0), + assert(!maxZoomLevel.isNaN), + assert(maxZoomLevel >= 1), + super(key: key); /// Creates a widget that displays the PDF document obtained from the network. /// @@ -176,32 +300,56 @@ class SfPdfViewer extends StatefulWidget { this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, + this.undoController, this.onZoomLevelChanged, + this.canShowPageLoadingIndicator = true, this.canShowScrollStatus = true, this.onPageChanged, this.enableDoubleTapZooming = true, this.enableTextSelection = true, this.onTextSelectionChanged, + this.onHyperlinkClicked, this.onDocumentLoaded, this.onDocumentLoadFailed, + this.onTap, + this.onFormFieldValueChanged, + this.onFormFieldFocusChange, + this.onAnnotationAdded, + this.onAnnotationSelected, + this.onAnnotationDeselected, + this.onAnnotationEdited, + this.onAnnotationRemoved, this.enableDocumentLinkAnnotation = true, this.canShowPaginationDialog = true, + this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, + this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, - this.scrollDirection = PdfScrollDirection.vertical, + this.scrollDirection, this.pageLayoutMode = PdfPageLayoutMode.continuous, - this.currentSearchTextHighlightColor = - const Color.fromRGBO(229, 110, 0, 0.6), - this.otherSearchTextHighlightColor = const Color.fromRGBO(229, 110, 0, 0.3), + this.currentSearchTextHighlightColor = const Color.fromARGB( + 80, + 249, + 125, + 0, + ), + this.otherSearchTextHighlightColor = const Color.fromARGB(50, 255, 255, 1), this.password, this.canShowPasswordDialog = true, - }) : _provider = NetworkPdf(src, headers), - assert(pageSpacing >= 0), - super(key: key); + this.canShowHyperlinkDialog = true, + this.enableHyperlinkNavigation = true, + this.canShowTextSelectionMenu = true, + }) : _source = URLPDFSource(src, headers: headers), + assert(pageSpacing >= 0), + assert(!maxZoomLevel.isNaN), + assert(maxZoomLevel >= 1), + super(key: key); /// Creates a widget that displays the PDF document obtained from [Uint8List]. /// + /// ``` dart /// class MyAppState extends State{ /// @override /// void initState() { @@ -227,29 +375,52 @@ class SfPdfViewer extends StatefulWidget { this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, + this.undoController, this.onZoomLevelChanged, + this.canShowPageLoadingIndicator = true, this.canShowScrollStatus = true, this.onPageChanged, this.enableDoubleTapZooming = true, this.enableTextSelection = true, this.onTextSelectionChanged, + this.onHyperlinkClicked, this.onDocumentLoaded, this.onDocumentLoadFailed, + this.onTap, + this.onFormFieldValueChanged, + this.onFormFieldFocusChange, + this.onAnnotationAdded, + this.onAnnotationSelected, + this.onAnnotationDeselected, + this.onAnnotationEdited, + this.onAnnotationRemoved, this.enableDocumentLinkAnnotation = true, this.canShowPaginationDialog = true, + this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, + this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, - this.scrollDirection = PdfScrollDirection.vertical, + this.scrollDirection, this.pageLayoutMode = PdfPageLayoutMode.continuous, - this.currentSearchTextHighlightColor = - const Color.fromRGBO(229, 110, 0, 0.6), - this.otherSearchTextHighlightColor = const Color.fromRGBO(229, 110, 0, 0.3), + this.currentSearchTextHighlightColor = const Color.fromARGB( + 80, + 249, + 125, + 0, + ), + this.otherSearchTextHighlightColor = const Color.fromARGB(50, 255, 255, 1), this.password, this.canShowPasswordDialog = true, - }) : _provider = MemoryPdf(bytes), - assert(pageSpacing >= 0), - super(key: key); + this.canShowHyperlinkDialog = true, + this.enableHyperlinkNavigation = true, + this.canShowTextSelectionMenu = true, + }) : _source = BytePDFSource(bytes), + assert(pageSpacing >= 0), + assert(!maxZoomLevel.isNaN), + assert(maxZoomLevel >= 1), + super(key: key); /// Creates a widget that displays the PDF document obtained from [File]. /// @@ -282,32 +453,61 @@ class SfPdfViewer extends StatefulWidget { this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, + this.undoController, this.onZoomLevelChanged, + this.canShowPageLoadingIndicator = true, this.canShowScrollStatus = true, this.onPageChanged, this.enableDoubleTapZooming = true, this.enableTextSelection = true, this.onTextSelectionChanged, + this.onHyperlinkClicked, this.onDocumentLoaded, this.onDocumentLoadFailed, + this.onTap, + this.onFormFieldValueChanged, + this.onFormFieldFocusChange, + this.onAnnotationAdded, + this.onAnnotationSelected, + this.onAnnotationDeselected, + this.onAnnotationEdited, + this.onAnnotationRemoved, this.enableDocumentLinkAnnotation = true, this.canShowPaginationDialog = true, + this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, + this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, - this.scrollDirection = PdfScrollDirection.vertical, + this.scrollDirection, this.pageLayoutMode = PdfPageLayoutMode.continuous, - this.currentSearchTextHighlightColor = - const Color.fromRGBO(229, 110, 0, 0.6), - this.otherSearchTextHighlightColor = const Color.fromRGBO(229, 110, 0, 0.3), + this.currentSearchTextHighlightColor = const Color.fromARGB( + 80, + 249, + 125, + 0, + ), + this.otherSearchTextHighlightColor = const Color.fromARGB(50, 255, 255, 1), this.password, this.canShowPasswordDialog = true, - }) : _provider = FilePdf(file), - assert(pageSpacing >= 0), - super(key: key); + this.canShowHyperlinkDialog = true, + this.enableHyperlinkNavigation = true, + this.canShowTextSelectionMenu = true, + }) : _source = FilePDFSource(file), + // File is not supported on Flutter Web therefore neither this method. + assert( + !kIsWeb, + 'SfPdfViewer.file is not supported on Flutter Web. ' + 'Consider using either SfPdfViewer.asset or SfPdfViewer.memory or SfPdfViewer.network instead.', + ), + assert(pageSpacing >= 0), + assert(!maxZoomLevel.isNaN), + assert(maxZoomLevel >= 1), + super(key: key); /// PDF file provider. - final PdfProvider _provider; + final PDFSource _source; /// Indicates the interaction modes of [SfPdfViewer] in a desktop browser. /// @@ -353,6 +553,76 @@ class SfPdfViewer extends StatefulWidget { /// ``` final double initialZoomLevel; + /// Represents the initial page to be displayed when the [SfPdfViewer] widget is loaded. + /// + /// Defaults to 1.0 + /// + /// It is recommended not to use both the [initialScrollOffset] and [initialPageNumber] properties at the same time. If both properties are defined, then the [initialPageNumber] will be prioritized over the [initialScrollOffset]. + /// + /// ```dart + /// class MyAppState extends State{ + /// + /// late PdfViewerController _pdfViewerController; + /// + /// @override + /// void initState(){ + /// _pdfViewerController = PdfViewerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: Text('Syncfusion Flutter PdfViewer'), + /// ), + /// body: SfPdfViewer.asset( + /// 'assets/flutter-succinctly.pdf', + /// controller: _pdfViewerController, + /// initialPageNumber: 2, + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final int initialPageNumber; + + /// Represents the maximum allowed zoom level. + /// + /// Defaults to 3.0. + /// + /// If the [zoomLevel] value is set higher than the maximum zoom level, then it will be restricted to the maximum zoom level. + /// + /// This example demonstrates how to set the maximum allowed zoom level in the [SfPdfViewer]. + /// + /// ```dart + /// class MyAppState extends State { + /// final GlobalKey _pdfViewerKey = GlobalKey(); + /// + /// @override + /// void initState() { + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter PDF Viewer'), + /// ), + /// body: SfPdfViewer.network( + /// 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + /// key: _pdfViewerKey, + /// maxZoomLevel: 6, + /// ), + /// ); + /// } + /// } + /// ``` + final double maxZoomLevel; + /// Represents the initial scroll offset position to be displayed when the [SfPdfViewer] widget is loaded. /// /// Defaults to Offset(0, 0) @@ -493,6 +763,24 @@ class SfPdfViewer extends StatefulWidget { /// ``` final PdfViewerController? controller; + /// Controls the annotation undo state. + final UndoHistoryController? undoController; + + /// Occurs when an annotation is selected. + final PdfAnnotationCallback? onAnnotationSelected; + + /// Occurs when a selected annotation gets deselected. + final PdfAnnotationCallback? onAnnotationDeselected; + + /// Occurs when annotation is modified. + final PdfAnnotationCallback? onAnnotationEdited; + + /// Occurs when an annotation is added to a page. + final PdfAnnotationCallback? onAnnotationAdded; + + /// Occurs when annotation is removed. + final PdfAnnotationCallback? onAnnotationRemoved; + /// 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 @@ -555,12 +843,12 @@ class SfPdfViewer extends StatefulWidget { /// Current instance search text highlight color. /// - /// Defaults to Color.fromRGBO(229, 110, 0, 0.6). + /// Defaults to Color.fromARGB(80, 249, 125, 0). final Color currentSearchTextHighlightColor; ///Other instance search text highlight color. /// - /// Defaults to Color.fromRGBO(229, 110, 0, 0.3). + /// Defaults to Color.fromARGB(50, 255, 255, 1). final Color otherSearchTextHighlightColor; /// Called after the document is loaded in [SfPdfViewer]. @@ -599,6 +887,21 @@ class SfPdfViewer extends StatefulWidget { /// See also: [PdfZoomDetails]. final PdfZoomLevelChangedCallback? onZoomLevelChanged; + /// Called when the form field value changed in [SfPdfViewer]. + /// + /// See also: [PdfFormFieldValueChangedDetails]. + final PdfFormFieldValueChangedCallback? onFormFieldValueChanged; + + /// Called when the form field focus changes in [SfPdfViewer]. + /// + /// See also: [PdfFormFieldFocusChangeDetails]. + final PdfFormFieldFocusChangeCallback? onFormFieldFocusChange; + + /// Indicates whether the built-in signature pad dialog should be displayed or not. The default value is true. + /// + /// Note: This property will also hide the built-in signature editing context menu when the value is set to “false”. + final bool canShowSignaturePadDialog; + /// Called when the text is selected or deselected in [SfPdfViewer]. /// /// The [globalSelectedRegion] and [selectedText] values in the @@ -662,6 +965,13 @@ class SfPdfViewer extends StatefulWidget { /// ``` final PdfTextSelectionChangedCallback? onTextSelectionChanged; + ///Called when the hyperlink is tapped in [SfPdfViewer]. + /// + /// It holds the [uri] of the selected text. + /// + /// See also: [PdfHyperlinkClickedDetails]. + final PdfHyperlinkClickedCallback? onHyperlinkClicked; + /// Called when the page changes in [SfPdfViewer]. /// /// Called in the following scenarios where the page changes @@ -678,6 +988,11 @@ class SfPdfViewer extends StatefulWidget { /// See also: [PdfPageChangedDetails]. final PdfPageChangedCallback? onPageChanged; + /// Called when tapped on the [SfPdfViewer]. + /// + /// See also: [PdfGestureDetails]. + final PdfGestureTapCallback? onTap; + /// The direction in which the PDF page scrolls. /// /// Defaults to [PdfScrollDirection.vertical] @@ -716,7 +1031,7 @@ class SfPdfViewer extends StatefulWidget { /// } ///} /// ``` - final PdfScrollDirection scrollDirection; + final PdfScrollDirection? scrollDirection; /// The layout mode in which the PDF page will be rendered. /// @@ -800,6 +1115,82 @@ class SfPdfViewer extends StatefulWidget { /// Defaults to `true`. final bool canShowPasswordDialog; + /// Indicates whether the hyperlink dialog in [SfPdfViewer] can be displayed or not. + /// + /// If this property is set as `false`, the hyperlink dialog in [SfPdfViewer] will + /// not be displayed. + /// + /// Defaults to `true`. + final bool canShowHyperlinkDialog; + + /// Indicates whether the hyperlink navigation can be performed or not . + /// + /// If this property is set as `false`, then the hyperlink will not be navigated. + /// + /// Defaults to `true`. + final bool enableHyperlinkNavigation; + + /// Indicates whether the page loading busy indicator can be displayed or not. + /// + /// If this property is set as `false`, the page loading indicator in [SfPdfViewer] will not be displayed. + /// + /// Defaults to `true`. + /// + /// This example demonstrates how to set the visibility of the page loading indicator in the [SfPdfViewer]. + /// + /// ```dart + /// class MyAppState extends State { + /// final GlobalKey _pdfViewerKey = GlobalKey(); + /// + /// @override + /// void initState() { + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter PDF Viewer'), + /// ), + /// body: SfPdfViewer.network( + /// 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + /// key: _pdfViewerKey, + /// canShowPageLoadingIndicator: false, + /// ), + /// ); + /// } + /// } + /// ``` + final bool canShowPageLoadingIndicator; + + /// Indicates whether the text selection menu can be displayed after the text is selected. + /// + /// If this property is set to `false`, the text selection menu in the [SfPdfViewer] will not be displayed after selecting the text. + /// + /// Defaults to `true`. + /// + /// ```dart + /// class MyAppState extends State { + /// final GlobalKey _pdfViewerKey = GlobalKey(); + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter PDF Viewer'), + /// ), + /// body: SfPdfViewer.network( + /// 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + /// key: _pdfViewerKey, + /// canShowTextSelectionMenu: false, + /// ), + /// ); + /// } + /// } + /// ``` + final bool canShowTextSelectionMenu; + @override SfPdfViewerState createState() => SfPdfViewerState(); } @@ -820,7 +1211,6 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { double? _otherContextHeight; double _maxPdfPageWidth = 0.0; final double _minScale = 1; - final double _maxScale = 3; bool _isScaleEnabled = !kIsDesktop; bool _isPdfPageTapped = false; bool _isDocumentLoadInitiated = false; @@ -832,10 +1222,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { PdfDocument? _document; bool _hasError = false; bool _panEnabled = true; - bool _isMobile = false; + bool _isMobileView = false; bool _isSearchStarted = false; bool _isKeyPadRaised = false; - bool _isTextSelectionCleared = false; final Map _pdfPages = {}; final GlobalKey _childKey = GlobalKey(); final GlobalKey _singlePageViewKey = GlobalKey(); @@ -844,15 +1233,12 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { final Map> _pdfPagesKey = >{}; SystemMouseCursor _cursor = SystemMouseCursors.basic; - List? _textCollection; + List? _textCollection = []; PdfTextExtractor? _pdfTextExtractor; double _maxScrollExtent = 0; Size _pdfDimension = Size.zero; bool _isPageChanged = false; bool _isSinglePageViewPageChanged = false; - bool _isOverflowed = false; - bool _isZoomChanged = false; - int _startPage = 0, _endPage = 0, _bufferCount = 0; final List _renderedImages = []; final Map _pageTextExtractor = {}; Size _totalImageSize = Size.zero; @@ -874,6 +1260,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { bool _passwordVisible = true; bool _isEncrypted = false; final TextEditingController _textFieldController = TextEditingController(); + bool _isPasswordDialogOpenButtonEnabled = false; final GlobalKey _formKey = GlobalKey(); final FocusNode _focusNode = FocusNode(); bool _errorTextPresent = false; @@ -882,13 +1269,42 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { bool _visibility = false; bool _isPasswordUsed = false; double _previousTiledZoomLevel = 1; + TextDirection? _textDirection; + bool _isTextExtractionCompleted = false; + final List _matchedTextPageIndices = []; + final Map _extractedTextCollection = {}; + Isolate? _textSearchIsolate; + bool _isTablet = false; + bool _isAndroidTV = false; + bool _isAndroid = false; + bool? _isAtLeastApiLevel35; + final Map _textBoxFocusNodes = + {}; + bool _isLoaded = false; + int _tappedPageNumber = -1; + Offset _tappedPagePosition = const Offset(-1, -1); + bool _canInvokeOnTap = true; + Offset _pagePointerDownPosition = Offset.zero; + Duration _pagePointerDownTimeStamp = Duration.zero; + bool _isDoubleTapped = false; + Size _viewportSize = Size.zero; + bool _isTextDirectionChanged = false; + Timer? _tileTimer; + late TransformationControllerExt _transformationController; /// PdfViewer theme data. SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; + + /// To check whether the application is running in mobile web view. + bool? _isMobileWebView; ///Color scheme data ThemeData? _themeData; + /// Indicates whether the document needs to be reloaded to update the signed signature field. + bool _isSignatureSaved = false; + /// Indicates whether the built-in bookmark view in the [SfPdfViewer] is /// opened or not. /// @@ -896,79 +1312,174 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { bool get isBookmarkViewOpen => _bookmarkKey.currentState?.showBookmark ?? false; + /// The current selected annotation. + Annotation? _selectedAnnotation; + final List _removedAnnotations = []; + final Map _annotationMap = + {}; + + /// Undo redo controller + late ChangeTracker _changeTracker; + UndoHistoryController? _undoController; + UndoHistoryController get _effectiveUndoController => + widget.undoController ?? (_undoController ??= UndoHistoryController()); + + /// Flag to indicate whether to skip adding the form field value change record in the undo stack. + bool _skipAddingFormFieldChange = false; + + /// Text selection menu + bool _isTextSelectionVisibleInViewport = false; + int _selectedTextPageNumber = -1; + Offset _contextMenuPosition = Offset.zero; + Rect _textSelectionRegion = Rect.zero; + Rect? _viewportGlobalRect; + OverlayEntry? _textSelectionOverlayEntry; + OverlayEntry? _stickyNoteEditTextOverlyEntry; + final TextEditingController _stickyNoteTextController = + TextEditingController(); + final FocusNode _stickyNoteFocusNode = FocusNode(); + + /// Used to extract text from the PDF document. + TextExtractionEngine? _textExtractionEngine; + + /// Instance of [TextSelectionHelper]. + final TextSelectionHelper _textSelectionHelper = TextSelectionHelper(); + + /// Password used to open the encrypted PDF document. + String? _password; + @override void initState() { super.initState(); + if (kIsDesktop && !_isMobileView) { + helper.preventDefaultMenu(); + } + _transformationController = + TransformationControllerExt()..addListener(_updateScrollOffset); _plugin = PdfViewerPlugin(); - _scrollDirection = widget.pageLayoutMode == PdfPageLayoutMode.single - ? PdfScrollDirection.horizontal - : widget.scrollDirection; + _scrollDirection = + widget.scrollDirection != null + ? widget.scrollDirection! + : (widget.pageLayoutMode == PdfPageLayoutMode.single + ? PdfScrollDirection.horizontal + : PdfScrollDirection.vertical); _tempScrollDirection = _scrollDirection; _pageLayoutMode = widget.pageLayoutMode; _pdfViewerController = widget.controller ?? PdfViewerController(); - _pdfViewerController.addListener(_handleControllerValueChange); + _pdfViewerController._addListener(_handleControllerValueChange); + _changeTracker = ChangeTracker()..undoController = _effectiveUndoController; _setInitialScrollOffset(); _offsetBeforeOrientationChange = Offset.zero; _hasError = false; + _isLoaded = false; _panEnabled = true; - _isTextSelectionCleared = false; - _loadPdfDocument(false); + _isAndroid = !kIsWeb && Platform.isAndroid; + _loadPdfDocument(false, false); _previousPageNumber = 1; _maxPdfPageWidth = 0; - WidgetsBinding.instance?.addObserver(this); + WidgetsBinding.instance.addObserver(this); } @override void didChangeDependencies() { super.didChangeDependencies(); _pdfViewerThemeData = SfPdfViewerTheme.of(context); + final TextDirection newTextDirection = Directionality.of(context); + if (_textDirection != null && + _textDirection != newTextDirection && + widget.pageLayoutMode == PdfPageLayoutMode.continuous && + _scrollDirection == PdfScrollDirection.horizontal) { + _isTextDirectionChanged = true; + } + + _textDirection = newTextDirection; _themeData = Theme.of(context); + _effectiveThemeData = + _themeData!.useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); + + if (_textSelectionOverlayEntry != null) { + Future.delayed(Duration.zero, _checkPositionOfTextSelectionMenu); + } + Future.delayed(const Duration(milliseconds: 500), _getTileImage); } @override void didUpdateWidget(SfPdfViewer oldWidget) { super.didUpdateWidget(oldWidget); + final double oldZoomLevel = _pdfViewerController.zoomLevel; + final Offset oldScrollOffset = _pdfViewerController.scrollOffset; // Handle all cases of needing to dispose and initialize // _pdfViewerController. if (oldWidget.controller == null) { if (widget.controller != null) { - _pdfViewerController.removeListener(_handleControllerValueChange); + _pdfViewerController._removeListener(_handleControllerValueChange); _pdfViewerController._reset(); _pdfViewerController = widget.controller!; - _pdfViewerController.addListener(_handleControllerValueChange); + _pdfViewerController._addListener(_handleControllerValueChange); } } else { if (widget.controller == null) { - _pdfViewerController.removeListener(_handleControllerValueChange); + _pdfViewerController._removeListener(_handleControllerValueChange); _pdfViewerController = PdfViewerController(); - _pdfViewerController.addListener(_handleControllerValueChange); + _pdfViewerController._addListener(_handleControllerValueChange); } else if (widget.controller != oldWidget.controller) { - _pdfViewerController.removeListener(_handleControllerValueChange); + _pdfViewerController._removeListener(_handleControllerValueChange); _pdfViewerController = widget.controller!; - _pdfViewerController.addListener(_handleControllerValueChange); + _pdfViewerController._addListener(_handleControllerValueChange); } } - _scrollDirection = widget.pageLayoutMode == PdfPageLayoutMode.single - ? PdfScrollDirection.horizontal - : widget.scrollDirection; - _compareDocument(oldWidget._provider.getPdfBytes(context), - widget._provider.getPdfBytes(context), oldWidget.password); + if (oldWidget.undoController != widget.undoController) { + _changeTracker.resetController(); + _undoController?.dispose(); + _changeTracker.undoController = _effectiveUndoController; + } + _compareDocument( + oldWidget._source.getBytes(context), + widget._source.getBytes(context), + oldWidget.password, + ); if (oldWidget.pageLayoutMode != widget.pageLayoutMode) { - _updateOffsetOnLayoutChange(oldWidget.controller!.zoomLevel, - oldWidget.controller!.scrollOffset, oldWidget.pageLayoutMode); + _transformationController.value = Matrix4.identity(); + // Content size and view size is handled only for single page layout mode + if (widget.pageLayoutMode == PdfPageLayoutMode.continuous) { + _transformationController.contentSize = Size.zero; + _transformationController.viewSize = Size.zero; + } + _updateOffsetOnLayoutChange( + oldZoomLevel, + oldScrollOffset, + oldWidget.pageLayoutMode, + ); + } + _scrollDirection = + widget.scrollDirection != null + ? widget.scrollDirection! + : (widget.pageLayoutMode == PdfPageLayoutMode.single + ? PdfScrollDirection.horizontal + : PdfScrollDirection.vertical); + Future.delayed(Durations.short4, _getTileImage); + + if (oldWidget.canShowTextSelectionMenu != widget.canShowTextSelectionMenu) { + if (widget.canShowTextSelectionMenu) { + Future.delayed(Duration.zero, _checkPositionOfTextSelectionMenu); + } else { + _hideTextSelectionMenu(); + } } } /// sets the InitialScrollOffset void _setInitialScrollOffset() { - if (widget.key is PageStorageKey && PageStorage.of(context) != null) { - final dynamic offset = PageStorage.of(context)!.readState(context); + if (widget.key is PageStorageKey) { + final dynamic offset = PageStorage.of(context).readState(context); _pdfViewerController._verticalOffset = offset.dy as double; _pdfViewerController._horizontalOffset = offset.dx as double; - final dynamic zoomLevel = PageStorage.of(context) - ?.readState(context, identifier: 'zoomLevel_${widget.key}'); - // ignore: avoid_as + final dynamic zoomLevel = PageStorage.of( + context, + ).readState(context, identifier: 'zoomLevel_${widget.key}'); _pdfViewerController.zoomLevel = zoomLevel as double; } else { _pdfViewerController._verticalOffset = widget.initialScrollOffset.dy; @@ -978,51 +1489,84 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } // Compares the document bytes and load the PDF document if new bytes are provided. - Future _compareDocument(Future oldBytesData, - Future newBytesData, String? oldPassword) async { + Future _compareDocument( + Future oldBytesData, + Future newBytesData, + String? oldPassword, + ) async { final Uint8List oldBytes = await oldBytesData; final Uint8List newBytes = await newBytesData; if (!listEquals(oldBytes, newBytes) || (widget.password != null && widget.password != oldPassword)) { + _password = null; _pdfViewerController.clearSelection(); // PDF document gets loaded only when the user changes // the input source of PDF document. - await _loadPdfDocument(true); + await _loadPdfDocument(true, false); } } @override void dispose() { + bookmarkList.clear(); + helper.enableDefaultMenu(); _getPdfFileCancellableOperation?.cancel(); _pdfDocumentLoadCancellableOperation?.cancel(); _getHeightCancellableOperation?.cancel(); _getWidthCancellableOperation?.cancel(); + _matchedTextPageIndices.clear(); + _extractedTextCollection.clear(); _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; - imageCache?.clear(); + imageCache.clear(); + _killTextSearchIsolate(); _plugin.closeDocument(); + _textExtractionEngine?.dispose(); + _textExtractionEngine = null; _disposeCollection(_originalHeight); _disposeCollection(_originalWidth); _renderedImages.clear(); _pageTextExtractor.clear(); _pdfPages.clear(); _pdfPagesKey.clear(); + _disposeFormFields(); + _textBoxFocusNodes.clear(); _focusNode.dispose(); + _stickyNoteTextController.dispose(); + _stickyNoteFocusNode.dispose(); _document?.dispose(); _document = null; - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.canvasRenderBox ?.disposeSelection(); if (widget.onTextSelectionChanged != null) { - widget - .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); + widget.onTextSelectionChanged!( + PdfTextSelectionChangedDetails(null, null), + ); } - _pdfViewerController.removeListener(_handleControllerValueChange); - WidgetsBinding.instance?.removeObserver(this); + _changeTracker.resetStacks(); + _changeTracker.resetController(); + _undoController?.dispose(); + _pdfViewerController._removeListener(_handleControllerValueChange); + _hideTextSelectionMenu(); + _hideStickyNoteDialog(); + _transformationController.removeListener(_updateScrollOffset); + _transformationController.dispose(); + WidgetsBinding.instance.removeObserver(this); super.dispose(); } + void _disposeFormFields() { + for (final PdfFormField formField in _pdfViewerController._formFields) { + final PdfFormFieldHelper formFieldHelper = PdfFormFieldHelper.getHelper( + formField, + ); + formFieldHelper.dispose(); + } + + _pdfViewerController._formFields.clear(); + } + void _disposeCollection(List? list) { if (list != null) { list = null; @@ -1031,28 +1575,32 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Reset when PDF path is changed. void _reset() { - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.canvasRenderBox ?.disposeMouseSelection(); - _isTextSelectionCleared = false; + _isLoaded = false; + _textExtractionEngine?.dispose(); + _textExtractionEngine = null; + _killTextSearchIsolate(); _isEncrypted = false; + _matchedTextPageIndices.clear(); + _extractedTextCollection.clear(); + _isTextExtractionCompleted = false; _errorTextPresent = false; _passwordVisible = true; _isEncryptedDocument = false; _pdfScrollableStateKey.currentState?.reset(); _offsetBeforeOrientationChange = Offset.zero; _previousPageNumber = 1; + _disposeFormFields(); + _textBoxFocusNodes.clear(); + _deselectAnnotation(); _pdfViewerController._reset(); _pdfPages.clear(); _plugin.closeDocument(); _pageTextExtractor.clear(); _document?.dispose(); _document = null; - imageCache?.clear(); - _startPage = 0; - _endPage = 0; - _bufferCount = 0; + imageCache.clear(); _renderedImages.clear(); _hasError = false; _isDocumentLoadInitiated = false; @@ -1063,106 +1611,758 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _isPageChanged = false; _isSinglePageViewPageChanged = false; _isPasswordUsed = false; + _changeTracker.resetStacks(); + _removedAnnotations.clear(); + _annotationMap.clear(); + _hideTextSelectionMenu(); + _hideStickyNoteDialog(); + _textSelectionHelper.reset(); + _tileTimer?.cancel(); + _tileTimer = null; + // Resets the transformation controller + _transformationController.value = Matrix4.identity(); + bookmarkList.clear(); } - /// Loads a PDF document and gets the page count from Plugin - Future _loadPdfDocument(bool isPdfChanged) async { - try { - if (!_isEncrypted) { - _getPdfFileCancellableOperation = - CancelableOperation.fromFuture( - widget._provider.getPdfBytes(context), - ); - } - _pdfBytes = _isEncrypted - ? _decryptedBytes - : (await _getPdfFileCancellableOperation?.value)!; - if (isPdfChanged) { - _reset(); - _plugin = PdfViewerPlugin(); - _checkMount(); - } - _pdfDocumentLoadCancellableOperation = - CancelableOperation.fromFuture(_getPdfFile(_pdfBytes)); - _document = await _pdfDocumentLoadCancellableOperation?.value; - if (_document != null) { - _pdfTextExtractor = PdfTextExtractor(_document!); - } - final int pageCount = await _plugin.initializePdfRenderer(_pdfBytes); - _pdfViewerController._pageCount = pageCount; - if (pageCount > 0) { - _pdfViewerController._pageNumber = 1; - } - _pdfViewerController.zoomLevel = widget.initialZoomLevel; - _setInitialScrollOffset(); - if (_document != null && widget.onDocumentLoaded != null) { - _isDocumentLoadInitiated = false; - widget.onDocumentLoaded!(PdfDocumentLoadedDetails(_document!)); - } - _getHeightCancellableOperation = - CancelableOperation?>.fromFuture( - _plugin.getPagesHeight()); - _originalHeight = await _getHeightCancellableOperation?.value; - _getWidthCancellableOperation = - CancelableOperation?>.fromFuture( - _plugin.getPagesWidth()); - _originalWidth = await _getWidthCancellableOperation?.value; - } catch (e) { - _pdfViewerController._reset(); - _hasError = true; - final String errorMessage = e.toString(); - if (errorMessage.contains('Invalid cross reference table') || - errorMessage.contains('FormatException: Invalid radix-10 number') || - errorMessage.contains('RangeError (index): Index out of range') || - errorMessage.contains( - 'RangeError (end): Invalid value: Not in inclusive range')) { - if (widget.onDocumentLoadFailed != null) { - widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( - 'Format Error', - 'This document cannot be opened because it is corrupted or not a PDF.')); + /// Retrieves the form field details in the document + void _retrieveFormFieldsDetails() { + for (int i = 0; i < _document!.form.fields.count; i++) { + final PdfField field = _document!.form.fields[i]; + + final PdfPage? page = _document!.form.fields[i].page; + final int pageIndex = _document!.pages.indexOf(page!); + + // Retrieve the text box field details + if (field is PdfTextBoxField) { + if (field.items != null && field.items!.count > 0) { + final List groupedTextFormFields = + []; + for (int j = 0; j < field.items!.count; j++) { + final PdfFieldItem item = field.items![j]; + final int itemPageIndex = _document!.pages.indexOf(item.page!); + + final PdfTextFormFieldHelper helper = PdfTextFormFieldHelper( + field, + itemPageIndex, + onFocusChanged: _formFieldFocusChange, + onValueChanged: _formFieldValueChanged, + ); + helper.bounds = item.bounds; + + final PdfTextFormField textFormField = helper.getFormField( + _changeTracker, + ); + groupedTextFormFields.add(textFormField); + _pdfViewerController._formFields.add(textFormField); + _textBoxFocusNodes.putIfAbsent(helper, () => helper.focusNode); + } + + for (final PdfTextFormField textFormField in groupedTextFormFields) { + final PdfTextFormFieldHelper helper = + PdfFormFieldHelper.getHelper(textFormField) + as PdfTextFormFieldHelper; + + final List groupedItems = + groupedTextFormFields + .where( + (PdfTextFormField groupedTextFormField) => + groupedTextFormField != textFormField, + ) + .toList(); + + helper.textFormFieldChildItems = groupedItems; + } + } else { + final PdfTextFormFieldHelper helper = PdfTextFormFieldHelper( + field, + pageIndex, + onFocusChanged: _formFieldFocusChange, + onValueChanged: _formFieldValueChanged, + ); + + _pdfViewerController._formFields.add( + helper.getFormField(_changeTracker), + ); + _textBoxFocusNodes.putIfAbsent(helper, () => helper.focusNode); } - } else if (errorMessage.contains('Cannot open an encrypted document.')) { - if (!_isPasswordUsed) { - try { - _decryptedProtectedDocument(_pdfBytes, widget.password); - _isPasswordUsed = true; - } catch (e) { - if (widget.onDocumentLoadFailed != null) { - if (widget.password == '' || widget.password == null) { - widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( - 'Empty Password Error', - 'The provided `password` property is empty so unable to load the encrypted document.')); - } else { - widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( - 'Invalid Password Error', - 'The provided `password` property is invalid so unable to load the encrypted document.')); - } + } + + // Retrieve the checkbox details + if (field is PdfCheckBoxField) { + if (field.items != null && field.items!.count > 0) { + final List groupedCheckBoxFormFields = + []; + for (int j = 0; j < field.items!.count; j++) { + final PdfFieldItem item = field.items![j]; + final int itemPageIndex = _document!.pages.indexOf(item.page!); + if (item is PdfCheckBoxItem) { + final PdfCheckboxFormFieldHelper groupedItemHelper = + PdfCheckboxFormFieldHelper( + field, + itemPageIndex, + pdfCheckBoxItem: item, + onValueChanged: _formFieldValueChanged, + ); + groupedItemHelper.bounds = item.bounds; + final PdfCheckboxFormField groupedFormField = + groupedItemHelper.getFormField(); + groupedCheckBoxFormFields.add(groupedFormField); + _pdfViewerController._formFields.add(groupedFormField); } } - } - if (widget.canShowPasswordDialog && !_isPasswordUsed) { - if (_isMobile) { - _checkMount(); - _showPasswordDialog(); - } else { - _isEncryptedDocument = true; - _visibility = true; - _checkMount(); + + for (final PdfCheckboxFormField checkboxFormField + in groupedCheckBoxFormFields) { + final PdfCheckboxFormFieldHelper helper = + PdfFormFieldHelper.getHelper(checkboxFormField) + as PdfCheckboxFormFieldHelper; + + final List groupedItems = + groupedCheckBoxFormFields + .where( + (PdfCheckboxFormField groupedCheckboxFormField) => + groupedCheckboxFormField != checkboxFormField, + ) + .toList(); + + helper.checkBoxFormFieldChildItems = groupedItems; } + } else { + final PdfCheckboxFormFieldHelper helper = PdfCheckboxFormFieldHelper( + field, + pageIndex, + onValueChanged: _formFieldValueChanged, + ); + + _pdfViewerController._formFields.add(helper.getFormField()); } } - //if the path is invalid - else if (errorMessage.contains('Unable to load asset') || + + // Retrieve the combo box details + if (field is PdfComboBoxField) { + final PdfComboBoxFormFieldHelper helper = PdfComboBoxFormFieldHelper( + field, + pageIndex, + onValueChanged: _formFieldValueChanged, + ); + + _pdfViewerController._formFields.add(helper.getFormField()); + } + + // Retrieve the radio button details + if (field is PdfRadioButtonListField) { + final PdfRadioFormFieldHelper helper = PdfRadioFormFieldHelper( + field, + pageIndex, + onValueChanged: _formFieldValueChanged, + ); + + _pdfViewerController._formFields.add(helper.getFormField()); + } + + // Retrieve the signature field details + if (field is PdfSignatureField && field.signature == null) { + final PdfSignatureFormFieldHelper helper = PdfSignatureFormFieldHelper( + field, + pageIndex, + onValueChanged: _formFieldValueChanged, + onFocusChange: _formFieldFocusChange, + ); + + _pdfViewerController._formFields.add(helper.getFormField()); + } + + // Retrieve the list box field details + if (field is PdfListBoxField) { + final PdfListBoxFormFieldHelper helper = PdfListBoxFormFieldHelper( + field, + pageIndex, + onValueChanged: _formFieldValueChanged, + ); + + _pdfViewerController._formFields.add(helper.getFormField()); + } + } + } + + /// Called when the form field focus is changed. + void _formFieldFocusChange(PdfFormFieldFocusChangeDetails details) { + if (widget.onFormFieldFocusChange != null) { + widget.onFormFieldFocusChange!(details); + } + } + + /// Called when the value of a form field is changed. + void _formFieldValueChanged(PdfFormFieldValueChangedDetails details) { + if (widget.onFormFieldValueChanged != null && + !_changeTracker.changeInProgress) { + widget.onFormFieldValueChanged!(details); + } + if (!_skipAddingFormFieldChange) { + _changeTracker.addChange( + FormFieldValueChangeTracker( + records: [ + FormFieldValueChangeRecord( + formField: details.formField, + oldValue: details.oldValue, + newValue: details.newValue, + ), + ], + onUndoOrRedo: _updateFormField, + ), + ); + } + _changeLinkedFieldValue(details.formField); + } + + /// Change the value of grouped form fields. + void _changeLinkedFieldValue(PdfFormField editedField) { + _skipAddingFormFieldChange = true; + for (final PdfFormField field in _pdfViewerController._formFields) { + if (editedField != field && editedField.name == field.name) { + if (editedField is PdfTextFormField && field is PdfTextFormField) { + field.text = editedField.text; + } else if (editedField is PdfCheckboxFormField && + field is PdfCheckboxFormField) { + if (editedField.children == null) { + field.isChecked = editedField.isChecked; + } + } else if (editedField is PdfRadioFormField && + field is PdfRadioFormField) { + field.selectedItem = editedField.selectedItem; + } else if (editedField is PdfComboBoxFormField && + field is PdfComboBoxFormField) { + field.selectedItem = editedField.selectedItem; + } else if (editedField is PdfListBoxFormField && + field is PdfListBoxFormField) { + field.selectedItems = editedField.selectedItems; + } else if (editedField is PdfSignatureFormField && + field is PdfSignatureFormField) { + field.signature = editedField.signature; + } + } + } + _skipAddingFormFieldChange = false; + } + + /// Imports the form fields details from the document. + void _importFormFieldData() { + final List formFieldValueChangeRecords = + []; + for ( + int fieldIndex = 0; + fieldIndex < _document!.form.fields.count; + fieldIndex++ + ) { + final PdfField field = _document!.form.fields[fieldIndex]; + final List filteredFormFields = + _pdfViewerController._formFields + .where((PdfFormField formField) => formField.name == field.name) + .toList(); + + FormFieldValueChangeRecord? record; + for (final PdfFormField formField in filteredFormFields) { + if (field is PdfTextBoxField && formField is PdfTextFormField) { + if (!formField.readOnly) { + record = _updateFormField(formField, field.text); + } else { + field.text = formField.text; + } + } + + if (field is PdfCheckBoxField && formField is PdfCheckboxFormField) { + if (formField.children == null) { + if (!formField.readOnly) { + record = _updateFormField(formField, field.isChecked); + } else { + field.isChecked = formField.isChecked; + } + } else { + final bool oldValue = formField.isChecked; + (PdfFormFieldHelper.getHelper(formField) + as PdfCheckboxFormFieldHelper) + .import(); + record = FormFieldValueChangeRecord( + formField: formField, + oldValue: oldValue, + newValue: formField.isChecked, + ); + } + } + if (field is PdfComboBoxField && formField is PdfComboBoxFormField) { + if (!formField.readOnly) { + final String selectedItem = + field.selectedIndex != -1 + ? field.items[field.selectedIndex].text + : ''; + record = _updateFormField(formField, selectedItem); + } else { + field.selectedValue = formField.selectedItem; + } + } + if (field is PdfRadioButtonListField && + formField is PdfRadioFormField) { + if (!formField.readOnly) { + final String selectedItem = + field.selectedIndex != -1 + ? field.items[field.selectedIndex].value + : ''; + record = _updateFormField(formField, selectedItem); + } else { + field.selectedValue = formField.selectedItem; + } + } + if (field is PdfListBoxField && formField is PdfListBoxFormField) { + if (!formField.readOnly) { + final List selectedItems = List.from( + field.selectedValues, + growable: false, + ); + record = _updateFormField(formField, selectedItems); + } else { + field.selectedValues = formField.selectedItems!; + } + } + if (record != null) { + formFieldValueChangeRecords.add(record); + } + } + } + if (formFieldValueChangeRecords.isNotEmpty) { + _changeTracker.addChange( + FormFieldValueChangeTracker( + records: formFieldValueChangeRecords, + onUndoOrRedo: _updateFormField, + ), + ); + } + } + + /// Update the form field values. + FormFieldValueChangeRecord? _updateFormField( + PdfFormField field, + Object? value, [ + bool isUndoOrRedo = false, + ]) { + if (field.readOnly) { + return null; + } + Object? oldValue; + Object? newValue; + final PdfFormFieldHelper formFieldHelper = PdfFormFieldHelper.getHelper( + field, + ); + if (formFieldHelper is PdfTextFormFieldHelper && + field is PdfTextFormField && + value is String) { + oldValue = field.text; + formFieldHelper.setTextBoxValue(value); + newValue = field.text; + } else if (formFieldHelper is PdfCheckboxFormFieldHelper && + field is PdfCheckboxFormField && + value is bool) { + oldValue = field.isChecked; + formFieldHelper.setCheckboxValue(value); + newValue = field.isChecked; + } else if (formFieldHelper is PdfRadioFormFieldHelper && + field is PdfRadioFormField && + value is String && + (value == '' || field.items.contains(value))) { + oldValue = field.selectedItem; + formFieldHelper.setRadioButtonValue(value); + newValue = field.selectedItem; + } else if (formFieldHelper is PdfComboBoxFormFieldHelper && + field is PdfComboBoxFormField && + value is String && + field.items.contains(value)) { + oldValue = field.selectedItem; + formFieldHelper.setComboBoxValue(value); + newValue = field.selectedItem; + } else if (formFieldHelper is PdfListBoxFormFieldHelper && + field is PdfListBoxFormField && + value is List) { + oldValue = field.selectedItems; + formFieldHelper.setListBoxValue(value); + newValue = field.selectedItems; + } else if (formFieldHelper is PdfSignatureFormFieldHelper && + field is PdfSignatureFormField && + value is Uint8List?) { + oldValue = field.signature; + formFieldHelper.setSignature(value); + newValue = field.signature; + } + _changeLinkedFieldValue(field); + formFieldHelper.rebuild(); + if (oldValue != newValue && !isUndoOrRedo) { + return FormFieldValueChangeRecord( + formField: field, + oldValue: oldValue, + newValue: newValue, + ); + } else { + return null; + } + } + + /// Retrieves the annotation details from the document. + void _retrieveAnnotations() { + for (int pageIndex = 0; pageIndex < _document!.pages.count; pageIndex++) { + int zOrder = 0; + final PdfPage page = _document!.pages[pageIndex]; + final int pageNumber = pageIndex + 1; + final PdfAnnotationCollection annotations = page.annotations; + for ( + int annotationIndex = 0; + annotationIndex < annotations.count; + annotationIndex++ + ) { + final PdfAnnotation pdfAnnotation = annotations[annotationIndex]; + final Annotation? annotation = _createAnnotation( + pdfAnnotation, + pageNumber, + ); + + if (annotation != null) { + annotation.zOrder = zOrder++; + _pdfViewerController._annotations.add(annotation); + _annotationMap.putIfAbsent(annotation, () => pdfAnnotation); + } + } + } + } + + /// Creates the annotation from the PDF annotation. + Annotation? _createAnnotation(PdfAnnotation pdfAnnotation, int pageNumber) { + Annotation? annotation; + if (pdfAnnotation is PdfTextMarkupAnnotation) { + final PdfTextMarkupAnnotation textMarkup = pdfAnnotation; + final List textMarkupRects = textMarkup.boundsCollection; + // If textMarkupRects is empty (a rare case), we add an empty rectangle to resolve the assertion error. + if (textMarkupRects.isEmpty) { + textMarkupRects.add(Rect.zero); + } + + switch (textMarkup.textMarkupAnnotationType) { + case PdfTextMarkupAnnotationType.highlight: + annotation = HighlightAnnotation( + textBoundsCollection: _getTextLines(textMarkupRects, pageNumber), + ); + break; + case PdfTextMarkupAnnotationType.underline: + annotation = UnderlineAnnotation( + textBoundsCollection: _getTextLines(textMarkupRects, pageNumber), + ); + break; + case PdfTextMarkupAnnotationType.strikethrough: + annotation = StrikethroughAnnotation( + textBoundsCollection: _getTextLines(textMarkupRects, pageNumber), + ); + break; + case PdfTextMarkupAnnotationType.squiggly: + annotation = SquigglyAnnotation( + textBoundsCollection: _getTextLines(textMarkupRects, pageNumber), + ); + break; + } + + annotation.setColor(textMarkup.color.materialColor); + } else if (pdfAnnotation is PdfPopupAnnotation) { + final PdfPopupAnnotation popup = pdfAnnotation; + + PdfStickyNoteIcon icon = PdfStickyNoteIcon.comment; + + int rotation = 0; + final PdfPageRotateAngle pageRotation = pdfAnnotation.page!.rotation; + if (pageRotation == PdfPageRotateAngle.rotateAngle90) { + rotation = 90; + } else if (pageRotation == PdfPageRotateAngle.rotateAngle180) { + rotation = 180; + } else if (pageRotation == PdfPageRotateAngle.rotateAngle270) { + rotation = 270; + } + + switch (popup.icon) { + case PdfPopupIcon.comment: + icon = PdfStickyNoteIcon.comment; + break; + case PdfPopupIcon.help: + icon = PdfStickyNoteIcon.help; + break; + case PdfPopupIcon.insert: + icon = PdfStickyNoteIcon.insert; + break; + case PdfPopupIcon.key: + icon = PdfStickyNoteIcon.key; + break; + case PdfPopupIcon.newParagraph: + icon = PdfStickyNoteIcon.newParagraph; + break; + case PdfPopupIcon.note: + icon = PdfStickyNoteIcon.note; + break; + case PdfPopupIcon.paragraph: + icon = PdfStickyNoteIcon.paragraph; + break; + } + + annotation = StickyNoteAnnotation( + pageNumber: pageNumber, + text: popup.text, + position: Offset(popup.bounds.left, popup.bounds.top), + icon: icon, + ); + annotation.setBounds(pdfAnnotation.bounds); + annotation.setColor(popup.color.materialColor); + annotation.intermediateBounds = annotation.boundingBox; + (annotation as StickyNoteAnnotation).pageRotation = rotation; + } + + if (annotation != null) { + final bool isLocked = pdfAnnotation.annotationFlags.contains( + PdfAnnotationFlags.locked, + ); + + annotation.setOpacity(pdfAnnotation.opacity); + annotation.setIsLocked(isLocked); + annotation.onPropertyChange = _handleAnnotationPropertyChange; + annotation.onPropertyChanged = _handleAnnotationPropertyChanged; + + annotation.name = pdfAnnotation.text; + annotation.author = pdfAnnotation.author; + annotation.subject = pdfAnnotation.subject; + } + return annotation; + } + + /// Returns the text lines from the text markup annotation. + List _getTextLines(List rects, int pageNumber) { + final List textLines = []; + for (final Rect rect in rects) { + final PdfTextLine textLine = PdfTextLine(rect, '', pageNumber); + textLines.add(textLine); + } + return textLines; + } + + /// Save the PDF document with the modified data and returns the data bytes. + Future> _saveDocument() async { + // Update the signature form fields data + _updateSignatureFormFields(); + + // Update the annotations in the document + _updateAnnotations(); + + // Set the default appearance for the form. + _document!.form.setDefaultAppearance(false); + + // Flatten the form fields if the PdfFlattenOption is enabled. + if (_pdfViewerController._flattenOption == PdfFlattenOption.formFields) { + _document!.form.flattenAllFields(); + } + + // Save and reload the document + _pdfBytes = Uint8List(0); + _pdfBytes = Uint8List.fromList(await _document!.save()); + + if (_isSignatureSaved || + _pdfViewerController._flattenOption == PdfFlattenOption.formFields) { + _loadPdfDocument(true, true); + _isSignatureSaved = false; + } + + return _pdfBytes.toList(); + } + + /// Update the signature form fields data + void _updateSignatureFormFields() { + for (final PdfFormField signatureField + in _pdfViewerController._formFields) { + if (signatureField is PdfSignatureFormField) { + final PdfSignatureFormFieldHelper helper = + PdfFormFieldHelper.getHelper(signatureField) + as PdfSignatureFormFieldHelper; + final PdfPage page = helper.pdfSignatureField.page!; + if (signatureField.signature != null) { + final List? bitmapBytes = signatureField.signature; + if (bitmapBytes != null) { + page.graphics.drawImage( + PdfBitmap(bitmapBytes), + Rect.fromLTWH( + helper.bounds.left, + helper.bounds.top, + helper.bounds.width, + helper.bounds.height, + ), + ); + helper.pdfField.form!.fields.remove(helper.pdfSignatureField); + } + _isSignatureSaved = true; + } + } + } + } + + /// Updates the existing annotations and add new annotations. + void _updateAnnotations() { + for (final Annotation annotation in _pdfViewerController._annotations) { + final PdfPage pdfPage = _document!.pages[annotation.pageNumber - 1]; + PdfAnnotation? pdfAnnotation; + + if (_annotationMap.containsKey(annotation)) { + pdfAnnotation = _annotationMap[annotation]; + } + + pdfAnnotation = annotation.saveToPage(pdfPage, pdfAnnotation); + + if (!_annotationMap.containsKey(annotation)) { + _annotationMap.putIfAbsent(annotation, () => pdfAnnotation!); + } + } + _removeAnnotationsFromDocument(); + } + + /// Removes the annotations from the document if any existing annotation is removed. + void _removeAnnotationsFromDocument() { + for (final Annotation annotation in _removedAnnotations) { + final int pageNumber = annotation.pageNumber; + final PdfPage pdfPage = _document!.pages[pageNumber - 1]; + if (_annotationMap.containsKey(annotation)) { + final PdfAnnotation pdfAnnotation = _annotationMap[annotation]!; + _annotationMap.remove(annotation); + pdfPage.annotations.remove(pdfAnnotation); + } + } + } + + /// Loads a PDF document and gets the page count from Plugin + Future _loadPdfDocument(bool isPdfChanged, bool isDocumentSaved) async { + try { + if (!_isEncrypted && !isDocumentSaved) { + _getPdfFileCancellableOperation = + CancelableOperation.fromFuture( + widget._source.getBytes(context), + ); + } + if (_isAndroid) { + await _getAndroidDeviceDetails(); + } + _pdfBytes = + _isEncrypted + ? _decryptedBytes + : isDocumentSaved + ? _pdfBytes + : (await _getPdfFileCancellableOperation?.value)!; + if (isPdfChanged) { + _reset(); + _plugin = PdfViewerPlugin(); + _checkMount(); + } + _pdfDocumentLoadCancellableOperation = + CancelableOperation.fromFuture(_getPdfFile(_pdfBytes)); + _document = await _pdfDocumentLoadCancellableOperation?.value; + if (_document != null) { + _retrieveFormFieldsDetails(); + _retrieveAnnotations(); + _pdfTextExtractor = PdfTextExtractor(_document!); + if (!kIsWeb) { + _performTextExtraction(); + } + } + final int pageCount = await _plugin.initializePdfRenderer( + _renderDigitalSignatures() ?? _pdfBytes, + _password, + ); + _pdfViewerController._pageCount = pageCount; + if (pageCount > 0) { + _pdfViewerController._pageNumber = 1; + } + _pdfViewerController.zoomLevel = widget.initialZoomLevel; + _setInitialScrollOffset(); + _getHeightCancellableOperation = + CancelableOperation?>.fromFuture( + _plugin.getPagesHeight(), + ); + _originalHeight = await _getHeightCancellableOperation?.value; + _getWidthCancellableOperation = + CancelableOperation?>.fromFuture( + _plugin.getPagesWidth(), + ); + _originalWidth = await _getWidthCancellableOperation?.value; + } catch (e) { + _pdfViewerController._reset(); + _hasError = true; + _textExtractionEngine?.dispose(); + _textExtractionEngine = null; + final String errorMessage = e.toString(); + if (errorMessage.contains('Invalid cross reference table') || + errorMessage.contains('FormatException: Invalid radix-10 number') || + errorMessage.contains('RangeError (index): Index out of range') || + errorMessage.contains( + 'RangeError (end): Invalid value: Not in inclusive range', + )) { + if (widget.onDocumentLoadFailed != null) { + 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 (!_isPasswordUsed) { + try { + _decryptedProtectedDocument(_pdfBytes, widget.password); + _isPasswordUsed = true; + } catch (e) { + if (widget.onDocumentLoadFailed != null) { + if (widget.password == '' || widget.password == null) { + widget.onDocumentLoadFailed!( + PdfDocumentLoadFailedDetails( + 'Empty Password Error', + 'The provided `password` property is empty so unable to load the encrypted document.', + ), + ); + } else { + widget.onDocumentLoadFailed!( + PdfDocumentLoadFailedDetails( + 'Invalid Password Error', + 'The provided `password` property is invalid so unable to load the encrypted document.', + ), + ); + } + } + } + } + if (widget.canShowPasswordDialog && !_isPasswordUsed) { + if (_isMobileView) { + _checkMount(); + _showPasswordDialog(); + } else { + _isEncryptedDocument = true; + _visibility = true; + _checkMount(); + } + } + } + //if the path is invalid + 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.')); + 'The document cannot be opened because the provided path or link is invalid.', + ), + ); } } else { if (widget.onDocumentLoadFailed != null) { - widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( - 'Error', 'There was an error opening this document.')); + widget.onDocumentLoadFailed!( + PdfDocumentLoadFailedDetails( + 'Error', + 'There was an error opening this document.', + ), + ); } } } finally { @@ -1170,45 +2370,154 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } + /// Render and flatten digital signatures in the PDF document. + Uint8List? _renderDigitalSignatures() { + if (_document!.form.fields.count != 0) { + final PdfDocument pdfDocument = PdfDocument(inputBytes: _pdfBytes); + bool isDigitalSignature = false; + Uint8List? digitalSignatureBytes; + + /// Check if the PDF document has a signature field. + for (int i = 0; i < pdfDocument.form.fields.count; i++) { + final PdfField field = pdfDocument.form.fields[i]; + if (field is PdfSignatureField && field.signature != null) { + field.flatten(); + isDigitalSignature = true; + } + } + if (isDigitalSignature) { + digitalSignatureBytes = Uint8List.fromList(pdfDocument.saveSync()); + } + pdfDocument.dispose(); + return digitalSignatureBytes; + } + return null; + } + + /// Perform text extraction for mobile, windows and macOS platforms. + Future _performTextExtraction() async { + if (_document != null && _document!.pages.count > 0) { + _textExtractionEngine = TextExtractionEngine(_document!); + + _textExtractionEngine!.extractText().then((Map value) { + _extractedTextCollection.addAll(value); + _isTextExtractionCompleted = true; + if (_pdfViewerController._searchText.isNotEmpty) { + _pdfViewerController._notifyPropertyChangedListeners( + property: 'searchText', + ); + } + }); + } + } + /// Show the password dialog box for web. Widget _showWebPasswordDialogue() { + final bool isMaterial3 = _themeData!.useMaterial3; return Container( - color: (_themeData!.colorScheme.brightness == Brightness.light) - ? const Color(0xFFD6D6D6) - : const Color(0xFF303030), + decoration: BoxDecoration( + color: + isMaterial3 + ? _themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFA19CA5) + : const Color(0xFF221F27) + : (_themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030)), + boxShadow: + isMaterial3 + ? [ + const BoxShadow( + offset: Offset(0, 1), + blurRadius: 3, + color: Color(0x4D000000), + ), + const BoxShadow( + offset: Offset(0, 4), + blurRadius: 8, + spreadRadius: 3, + color: Color(0x26000000), + ), + ] + : [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.3), + blurRadius: 3, + offset: const Offset(0, 1), + ), + ], + ), child: Visibility( visible: _visibility, child: Center( child: Container( - height: 225, - width: 328, + height: isMaterial3 ? 264 : 230, + width: isMaterial3 ? 360 : 345, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: (_themeData!.colorScheme.brightness == Brightness.light) - ? Colors.white - : const Color(0xFF424242)), + borderRadius: + isMaterial3 + ? BorderRadius.circular(28) + : BorderRadius.circular(4), + color: + isMaterial3 + ? _pdfViewerThemeData + ?.passwordDialogStyle + ?.backgroundColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.backgroundColor + : (_themeData!.colorScheme.brightness == Brightness.light) + ? Colors.white + : const Color(0xFF424242), + ), child: Column( + mainAxisAlignment: + isMaterial3 + ? MainAxisAlignment.start + : MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 14, 17, 15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _localizations!.passwordDialogHeaderTextLabel, - style: _pdfViewerThemeData! - .passwordDialogStyle?.headerTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 20, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.87), - ), + Row( + children: [ + Expanded( + child: Padding( + padding: + isMaterial3 + ? const EdgeInsets.only(left: 24, top: 24) + : const EdgeInsets.only(left: 16, top: 10), + child: Text( + _localizations!.passwordDialogHeaderTextLabel, + style: Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: isMaterial3 ? 24 : 20, + color: + isMaterial3 + ? Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues( + alpha: 0.87, + ) + : Colors.white.withValues( + alpha: 0.87, + ) + : _themeData!.colorScheme.onSurface, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.headerTextStyle, + ), + ), ), - SizedBox( - height: 36, - width: 36, + ), + Padding( + padding: + isMaterial3 + ? const EdgeInsets.fromLTRB(0, 24, 16, 0) + : const EdgeInsets.fromLTRB(0, 10, 16, 0), + child: SizedBox( + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, child: RawMaterialButton( onPressed: () { setState(() { @@ -1219,137 +2528,71 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _passwordVisible = true; }); }, + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ) + : const RoundedRectangleBorder(), child: Icon( Icons.clear, - color: _pdfViewerThemeData! - .passwordDialogStyle?.closeIconColor ?? - _themeData!.colorScheme.onSurface - .withOpacity(0.6), + color: + _pdfViewerThemeData + ?.passwordDialogStyle + ?.closeIconColor ?? + _effectiveThemeData + ?.passwordDialogStyle + ?.closeIconColor ?? + _themeData?.colorScheme.onSurfaceVariant + .withValues(alpha: 0.6), size: 24, ), ), ), - ], - ), + ), + ], ), Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 24), + padding: + isMaterial3 + ? const EdgeInsets.fromLTRB(24, 8, 24, 4) + : const EdgeInsets.fromLTRB(16, 0, 16, 8), child: Text( _localizations!.passwordDialogContentLabel, - style: _pdfViewerThemeData! - .passwordDialogStyle?.contentTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - fontWeight: FontWeight.w400, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.6), + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: isMaterial3 ? 14 : 16, + color: + isMaterial3 + ? _themeData!.colorScheme.onSurfaceVariant + : Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.contentTextStyle, ), ), ), - SizedBox( - width: 296, - height: 70, - child: TextFormField( - style: _pdfViewerThemeData! - .passwordDialogStyle?.inputFieldTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 17, - fontWeight: FontWeight.w400, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.87), - ), - obscureText: _passwordVisible, - obscuringCharacter: '*', - decoration: InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - color: _pdfViewerThemeData! - .passwordDialogStyle?.inputFieldBorderColor ?? - _themeData!.colorScheme.primary, - )), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _pdfViewerThemeData! - .passwordDialogStyle?.errorBorderColor ?? - _themeData!.colorScheme.error, - )), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _pdfViewerThemeData! - .passwordDialogStyle?.inputFieldBorderColor ?? - _themeData!.colorScheme.primary, - )), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _pdfViewerThemeData! - .passwordDialogStyle?.errorBorderColor ?? - _themeData!.colorScheme.error, - )), - hintText: _localizations!.passwordDialogHintTextLabel, - errorText: _errorTextPresent ? 'Invalid Password' : null, - hintStyle: _pdfViewerThemeData! - .passwordDialogStyle?.inputFieldHintTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - fontWeight: FontWeight.w400, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.6), - ), - labelText: _localizations!.passwordDialogHintTextLabel, - labelStyle: _pdfViewerThemeData! - .passwordDialogStyle?.inputFieldLabelTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 18, - fontWeight: FontWeight.w500, - color: _errorTextPresent - ? _themeData!.colorScheme.error - : _themeData!.colorScheme.onSurface - .withOpacity(0.87), - ), - errorStyle: _pdfViewerThemeData! - .passwordDialogStyle?.errorTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.error, - ), - suffixIcon: IconButton( - icon: Icon( - _passwordVisible - ? Icons.visibility - : Icons.visibility_off, - color: _pdfViewerThemeData! - .passwordDialogStyle?.closeIconColor ?? - Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.6)), - onPressed: () { - setState(() { - _passwordVisible = !_passwordVisible; - }); - }), - ), - enableInteractiveSelection: false, - controller: _textFieldController, - autofocus: true, - focusNode: _focusNode, - textInputAction: TextInputAction.none, - onFieldSubmitted: (String value) { - _passwordValidation(value); - }, - ), + Padding( + padding: + isMaterial3 + ? const EdgeInsets.fromLTRB(24, 10, 24, 8) + : const EdgeInsets.fromLTRB(16, 0, 16, 0), + child: _textField(), ), Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 16, 14), + padding: + isMaterial3 + ? _errorTextPresent + ? const EdgeInsets.fromLTRB(24, 0, 32, 8) + : const EdgeInsets.fromLTRB(24, 19, 32, 8) + : const EdgeInsets.fromLTRB(0, 0, 16, 10), child: Row( mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, children: [ TextButton( onPressed: () { @@ -1360,37 +2603,96 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _errorTextPresent = false; }); }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, child: Text( _localizations!.pdfPasswordDialogCancelLabel, - style: _pdfViewerThemeData! - .passwordDialogStyle?.cancelTextStyle ?? - TextStyle( - fontFamily: 'Roboto', + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: + isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.cancelTextStyle, ), ), ), TextButton( - onPressed: () { - _passwordValidation(_textFieldController.text); - }, + onPressed: + isMaterial3 + ? (_isPasswordDialogOpenButtonEnabled + ? () { + _passwordValidation( + _textFieldController.text, + ); + } + : null) + : () { + _passwordValidation( + _textFieldController.text, + ); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, child: Text( _localizations!.pdfPasswordDialogOpenLabel, - style: _pdfViewerThemeData! - .passwordDialogStyle?.openTextStyle ?? - TextStyle( - fontFamily: 'Roboto', + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( fontSize: 14, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.primary, + fontWeight: + isMaterial3 ? FontWeight.w500 : null, + color: + isMaterial3 + ? (_isPasswordDialogOpenButtonEnabled + ? Theme.of( + context, + ).colorScheme.primary + : Theme.of(context).brightness == + Brightness.light + ? const Color.fromRGBO( + 28, + 27, + 31, + 1, + ).withValues(alpha: 0.38) + : const Color.fromRGBO( + 230, + 225, + 229, + 1, + ).withValues(alpha: 0.38)) + : Theme.of(context).colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.openTextStyle, ), ), ), ], ), - ) + ), ], ), ), @@ -1399,6 +2701,165 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ); } + // TextFormField of password dialogue + Widget _textField() { + final bool isMaterial3 = _themeData!.useMaterial3; + return SizedBox( + width: isMaterial3 ? 360 : 296, + height: isMaterial3 ? 70 : null, + child: TextFormField( + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 17, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData!.passwordDialogStyle?.inputFieldTextStyle, + ), + obscureText: _passwordVisible, + obscuringCharacter: '*', + decoration: InputDecoration( + contentPadding: + isMaterial3 + ? const EdgeInsets.symmetric(vertical: 20, horizontal: 20) + : null, + isDense: true, + border: OutlineInputBorder( + borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _themeData!.colorScheme.primary, + ), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, + color: + _pdfViewerThemeData!.passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData!.passwordDialogStyle?.errorBorderColor ?? + _themeData!.colorScheme.error, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _themeData!.colorScheme.primary, + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, + color: + _pdfViewerThemeData!.passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData!.passwordDialogStyle?.errorBorderColor ?? + _themeData!.colorScheme.error, + ), + ), + hintText: _localizations!.passwordDialogHintTextLabel, + errorText: _errorTextPresent ? 'Invalid Password' : null, + hintStyle: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + isMaterial3 + ? _themeData!.colorScheme.onSurfaceVariant + : Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldHintTextStyle, + ), + labelText: + isMaterial3 ? null : _localizations!.passwordDialogHintTextLabel, + labelStyle: Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: 18, + color: + Theme.of(context).brightness == Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldLabelTextStyle, + ), + errorStyle: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: _themeData!.colorScheme.error, + ) + .merge(_pdfViewerThemeData!.passwordDialogStyle?.errorTextStyle), + suffixIcon: Padding( + padding: EdgeInsets.only(right: isMaterial3 ? 12 : 0), + child: IconButton( + icon: Icon( + _passwordVisible + ? (isMaterial3 + ? Icons.visibility_outlined + : Icons.visibility) + : (isMaterial3 + ? Icons.visibility_off_outlined + : Icons.visibility_off), + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.visibleIconColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.visibleIconColor ?? + Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.6), + ), + onPressed: () { + setState(() { + _passwordVisible = !_passwordVisible; + }); + }, + ), + ), + ), + enableInteractiveSelection: false, + controller: _textFieldController, + autofocus: true, + focusNode: _focusNode, + textInputAction: TextInputAction.none, + onFieldSubmitted: (String value) { + _passwordValidation(value); + }, + onChanged: + isMaterial3 + ? (String value) { + setState(() { + _isPasswordDialogOpenButtonEnabled = value.isNotEmpty; + }); + } + : null, + ), + ); + } + ///validate the password for encrypted document for web. void _passwordValidation(String password) { try { @@ -1410,18 +2871,25 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } catch (e) { if (widget.onDocumentLoadFailed != null) { if (password.isEmpty || _textFieldController.text.isEmpty) { - widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( + widget.onDocumentLoadFailed!( + PdfDocumentLoadFailedDetails( 'Empty Password Error', - 'The provided `password` property is empty so unable to load the encrypted document.')); + 'The provided `password` property is empty so unable to load the encrypted document.', + ), + ); } else { - widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( + widget.onDocumentLoadFailed!( + PdfDocumentLoadFailedDetails( 'Invalid Password Error', - 'The provided `password` property is invalid so unable to load the encrypted document.')); + 'The provided `password` property is invalid so unable to load the encrypted document.', + ), + ); } } setState(() { _errorTextPresent = true; _textFieldController.clear(); + _isPasswordDialogOpenButtonEnabled = false; }); _focusNode.requestFocus(); } @@ -1429,253 +2897,434 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Show the password dialog box for mobile Future _showPasswordDialog() async { + final TextDirection textDirection = Directionality.of(context); + final bool isMaterial3 = _themeData!.useMaterial3; return showDialog( context: context, builder: (BuildContext context) { final Orientation orientation = MediaQuery.of(context).orientation; - - return AlertDialog( - scrollable: true, - insetPadding: EdgeInsets.zero, - contentPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(24) - : const EdgeInsets.only(top: 0, right: 24, left: 24, bottom: 0), - buttonPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(8) - : const EdgeInsets.all(4), - backgroundColor: _pdfViewerThemeData!.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == Brightness.light - ? Colors.white - : const Color(0xFF424242)), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _localizations!.passwordDialogHeaderTextLabel, - style: - _pdfViewerThemeData!.passwordDialogStyle?.headerTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 20, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.87), - ), - ), - SizedBox( - height: 36, - width: 36, - child: RawMaterialButton( - onPressed: () { - _focusNode.unfocus(); - _textFieldController.clear(); - Navigator.of(context).pop(); - }, - child: Icon( - Icons.clear, - color: _pdfViewerThemeData! - .passwordDialogStyle?.closeIconColor ?? - _themeData!.colorScheme.onSurface.withOpacity(0.6), - size: 24, - ), - ), - ), - ], - ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), - content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return SingleChildScrollView( - child: SizedBox( - width: 328, - child: Column( + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Directionality( + textDirection: textDirection, + child: AlertDialog( + scrollable: true, + insetPadding: EdgeInsets.zero, + contentPadding: + orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.only(right: 24, left: 24), + buttonPadding: + orientation == Orientation.portrait + ? const EdgeInsets.all(8) + : const EdgeInsets.all(4), + backgroundColor: + _pdfViewerThemeData!.passwordDialogStyle?.backgroundColor ?? + _effectiveThemeData!.passwordDialogStyle?.backgroundColor ?? + (isMaterial3 + ? _themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFA19CA5) + : const Color(0xFF221F27) + : (Theme.of(context).colorScheme.brightness == + Brightness.light + ? Colors.white + : const Color(0xFF424242))), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 24), - child: Text( - _localizations!.passwordDialogContentLabel, - style: _pdfViewerThemeData! - .passwordDialogStyle?.contentTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - fontWeight: FontWeight.w400, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.6), - ), + Expanded( + child: Text( + _localizations!.passwordDialogHeaderTextLabel, + style: Theme.of(context).textTheme.headlineMedium! + .copyWith( + fontSize: isMaterial3 ? 24 : 20, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: + isMaterial3 + ? Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87) + : _themeData!.colorScheme.onSurface, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.headerTextStyle, + ), + ), + ), + SizedBox( + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, + child: RawMaterialButton( + onPressed: () { + _focusNode.unfocus(); + _textFieldController.clear(); + Navigator.of(context).pop(); + }, + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ) + : const RoundedRectangleBorder(), + child: Icon( + Icons.clear, + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.closeIconColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.closeIconColor ?? + _themeData!.colorScheme.onSurfaceVariant, + size: 24, ), ), ), - Form( - key: _formKey, - child: TextFormField( - style: _pdfViewerThemeData! - .passwordDialogStyle?.inputFieldTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 17, - fontWeight: FontWeight.w400, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.87), - ), - obscureText: _passwordVisible, - obscuringCharacter: '*', - decoration: InputDecoration( - border: OutlineInputBorder( + ], + ), + shape: + isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(28), + ) + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + ), + content: SizedBox( + width: 328, + child: Column( + children: [ + Align( + alignment: + textDirection == TextDirection.ltr + ? Alignment.centerLeft + : Alignment.centerRight, + child: Padding( + padding: + isMaterial3 + ? const EdgeInsets.fromLTRB(0, 0, 0, 8) + : const EdgeInsets.fromLTRB(0, 0, 0, 24), + child: Text( + _localizations!.passwordDialogContentLabel, + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: isMaterial3 ? 14 : 16, + color: + isMaterial3 + ? _themeData! + .colorScheme + .onSurfaceVariant + : Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.contentTextStyle, + ), + ), + ), + ), + Form( + key: _formKey, + child: TextFormField( + style: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 17, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues(alpha: 0.87), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldTextStyle, + ), + obscureText: _passwordVisible, + obscuringCharacter: '*', + decoration: InputDecoration( + border: OutlineInputBorder( borderSide: BorderSide( - color: _pdfViewerThemeData!.passwordDialogStyle - ?.inputFieldBorderColor ?? - _themeData!.colorScheme.primary, - )), - errorBorder: OutlineInputBorder( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _themeData!.colorScheme.primary, + ), + ), + errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(3.5), borderSide: BorderSide( - color: _pdfViewerThemeData!.passwordDialogStyle + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle ?.errorBorderColor ?? _themeData!.colorScheme.error, - )), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _pdfViewerThemeData!.passwordDialogStyle - ?.inputFieldBorderColor ?? - _themeData!.colorScheme.primary, - )), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: _pdfViewerThemeData! - .passwordDialogStyle?.errorBorderColor ?? - _themeData!.colorScheme.error, - )), - hintText: _localizations!.passwordDialogHintTextLabel, - hintStyle: _pdfViewerThemeData!.passwordDialogStyle - ?.inputFieldHintTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 16, - fontWeight: FontWeight.w400, - color: _themeData!.colorScheme.onSurface - .withOpacity(0.6), ), - labelText: - _localizations!.passwordDialogHintTextLabel, - labelStyle: _pdfViewerThemeData!.passwordDialogStyle - ?.inputFieldLabelTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 18, - fontWeight: FontWeight.w500, - color: _errorTextPresent - ? _themeData!.colorScheme.error - : _themeData!.colorScheme.onSurface - .withOpacity(0.87), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.inputFieldBorderColor ?? + _themeData!.colorScheme.primary, ), - errorStyle: _pdfViewerThemeData! - .passwordDialogStyle?.errorTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.error, + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.errorBorderColor ?? + _themeData!.colorScheme.error, ), - suffixIcon: IconButton( + ), + hintText: + _localizations!.passwordDialogHintTextLabel, + hintStyle: Theme.of(context).textTheme.titleMedium! + .copyWith( + fontSize: 16, + color: + isMaterial3 + ? _themeData! + .colorScheme + .onSurfaceVariant + : Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.6), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldHintTextStyle, + ), + labelText: + isMaterial3 + ? null + : _localizations! + .passwordDialogHintTextLabel, + labelStyle: Theme.of(context) + .textTheme + .headlineMedium! + .copyWith( + fontSize: 18, + color: + Theme.of(context).brightness == + Brightness.light + ? Colors.black.withValues(alpha: 0.87) + : Colors.white.withValues( + alpha: 0.87, + ), + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.inputFieldLabelTextStyle, + ), + errorStyle: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: _themeData!.colorScheme.error, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.errorTextStyle, + ), + suffixIcon: IconButton( icon: Icon( - _passwordVisible - ? Icons.visibility - : Icons.visibility_off, - color: _pdfViewerThemeData! - .passwordDialogStyle - ?.closeIconColor ?? - Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.6)), + size: isMaterial3 ? 18 : null, + _passwordVisible + ? (isMaterial3 + ? Icons.visibility_outlined + : Icons.visibility) + : (isMaterial3 + ? Icons.visibility_off_outlined + : Icons.visibility_off), + color: + _pdfViewerThemeData! + .passwordDialogStyle + ?.visibleIconColor ?? + _effectiveThemeData! + .passwordDialogStyle + ?.visibleIconColor ?? + Theme.of(context).colorScheme.onSurface + .withValues(alpha: 0.6), + ), onPressed: () { setState(() { _passwordVisible = !_passwordVisible; }); - }), - ), - enableInteractiveSelection: false, - controller: _textFieldController, - autofocus: true, - focusNode: _focusNode, - onFieldSubmitted: (String value) { - _handlePasswordValidation(); - }, - validator: (String? value) { - try { - _decryptedProtectedDocument(_pdfBytes, value); - } catch (e) { - if (widget.onDocumentLoadFailed != null) { - if (value!.isEmpty) { - widget.onDocumentLoadFailed!( + }, + ), + ), + enableInteractiveSelection: false, + controller: _textFieldController, + autofocus: true, + focusNode: _focusNode, + onFieldSubmitted: (String value) { + _handlePasswordValidation(); + }, + onChanged: + isMaterial3 + ? (String value) { + setState(() { + _isPasswordDialogOpenButtonEnabled = + value.isNotEmpty; + }); + } + : null, + validator: (String? value) { + try { + _decryptedProtectedDocument(_pdfBytes, value); + } catch (e) { + if (widget.onDocumentLoadFailed != null) { + if (value!.isEmpty) { + widget.onDocumentLoadFailed!( PdfDocumentLoadFailedDetails( - 'Empty Password Error', - 'The provided `password` property is empty so unable to load the encrypted document.')); - } else { - widget.onDocumentLoadFailed!( + 'Empty Password Error', + 'The provided `password` property is empty so unable to load the encrypted document.', + ), + ); + } else { + widget.onDocumentLoadFailed!( PdfDocumentLoadFailedDetails( - 'Invalid Password Error', - 'The provided `password` property is invalid so unable to load the encrypted document.')); + 'Invalid Password Error', + 'The provided `password` property is invalid so unable to load the encrypted document.', + ), + ); + } } + _textFieldController.clear(); + setState(() { + _errorTextPresent = true; + _isPasswordDialogOpenButtonEnabled = false; + }); + _focusNode.requestFocus(); + return 'Invalid Password'; } - _textFieldController.clear(); - setState(() { - _errorTextPresent = true; - }); - _focusNode.requestFocus(); - return 'Invalid Password'; - } - return null; - }, + return null; + }, + ), ), - ), - ], + ], + ), ), - ), - ); - }), - actions: [ - TextButton( - onPressed: () { - _textFieldController.clear(); - Navigator.of(context).pop(); - }, - child: Text( - _localizations!.pdfPasswordDialogCancelLabel, - style: - _pdfViewerThemeData!.passwordDialogStyle?.cancelTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontSize: 14, - fontWeight: FontWeight.w500, - color: _themeData!.colorScheme.primary, - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), - child: TextButton( - onPressed: () { - _handlePasswordValidation(); - }, - child: Text( - _localizations!.pdfPasswordDialogOpenLabel, - style: - _pdfViewerThemeData!.passwordDialogStyle?.openTextStyle ?? - TextStyle( - fontFamily: 'Roboto', + actions: [ + TextButton( + onPressed: () { + _textFieldController.clear(); + Navigator.of(context).pop(); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, + child: Text( + _localizations!.pdfPasswordDialogCancelLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( fontSize: 14, - fontWeight: FontWeight.w500, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.cancelTextStyle, ), - ), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 8, 0), + child: TextButton( + onPressed: + isMaterial3 + ? (_isPasswordDialogOpenButtonEnabled + ? () { + _handlePasswordValidation(); + } + : () {}) + : () { + _handlePasswordValidation(); + }, + style: + isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 20, + ), + ) + : null, + child: Text( + _localizations!.pdfPasswordDialogOpenLabel, + style: Theme.of(context).textTheme.bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: + isMaterial3 + ? (_isPasswordDialogOpenButtonEnabled + ? Theme.of( + context, + ).colorScheme.primary + : Theme.of(context).brightness == + Brightness.light + ? const Color.fromRGBO( + 28, + 27, + 31, + 1, + ).withValues(alpha: 0.38) + : const Color.fromRGBO( + 230, + 225, + 229, + 1, + ).withValues(alpha: 0.38)) + : Theme.of(context).colorScheme.primary, + ) + .merge( + _pdfViewerThemeData! + .passwordDialogStyle + ?.openTextStyle, + ), + ), + ), + ), + ], ), - ), - ], + ); + }, ); }, ); @@ -1689,22 +3338,43 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } + /// To check whether the Android device version is 35 or more + Future _getAndroidDeviceDetails() async { + if (_isAndroid && _isAtLeastApiLevel35 == null) { + final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + _isAtLeastApiLevel35 = androidInfo.version.sdkInt >= 35; + } + } + ///Decrypt the password protected document. void _decryptedProtectedDocument(Uint8List pdfBytes, String? password) { - final PdfDocument document = - PdfDocument(inputBytes: pdfBytes, password: password); - document.security.userPassword = ''; - document.security.ownerPassword = ''; - final List bytes = document.save(); - _decryptedBytes = Uint8List.fromList(bytes); + final PdfDocument document = PdfDocument( + inputBytes: pdfBytes, + password: password, + ); + if (_isAndroid && !(_isAtLeastApiLevel35 ??= false)) { + if (helper.isPdfiumLoaded()) { + _decryptedBytes = pdfBytes; + _password = password; + } else { + document.security.userPassword = ''; + document.security.ownerPassword = ''; + _decryptedBytes = document.saveAsBytesSync(); + } + } else { + _decryptedBytes = pdfBytes; + _password = password; + } + document.dispose(); _isEncrypted = true; - _loadPdfDocument(true); + _loadPdfDocument(true, false); } /// Get the file of the Pdf. Future _getPdfFile(Uint8List? value) async { if (value != null) { - return PdfDocument(inputBytes: value); + return PdfDocument(inputBytes: value, password: _password); } return null; } @@ -1724,16 +3394,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { (!_pdfDimension.isEmpty && _pdfScrollableStateKey.currentState != null)) { final double xOffset = - widget.scrollDirection != PdfScrollDirection.vertical + _scrollDirection != PdfScrollDirection.vertical ? _pdfPages[_previousSinglePage]!.pageOffset : 0; final double yOffset = - widget.scrollDirection == PdfScrollDirection.vertical + _scrollDirection == PdfScrollDirection.vertical ? _pdfPages[_previousSinglePage]!.pageOffset : 0; _pdfScrollableStateKey.currentState!.jumpTo( - xOffset: xOffset + _layoutChangeOffset.dx, - yOffset: yOffset + _layoutChangeOffset.dy); + xOffset: xOffset + _layoutChangeOffset.dx, + yOffset: yOffset + _layoutChangeOffset.dy, + ); _layoutChangeOffset = Offset.zero; _previousSinglePage = 1; } @@ -1746,483 +3417,714 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfViewerController._verticalOffset != 0.0 || _pdfViewerController._horizontalOffset != 0.0) { _pdfViewerController.jumpTo( - xOffset: _pdfViewerController._horizontalOffset, - yOffset: _pdfViewerController._verticalOffset); + xOffset: _pdfViewerController._horizontalOffset, + yOffset: _pdfViewerController._verticalOffset, + ); } - _pdfViewerController.notifyPropertyChangedListeners( - property: 'pageNavigate'); - _pdfViewerController.notifyPropertyChangedListeners( - property: 'jumpToBookmark'); + if (widget.initialPageNumber > 1 && + widget.initialPageNumber <= _pdfViewerController._totalPages) { + _pdfViewerController.jumpToPage(widget.initialPageNumber); + } + _pdfViewerController._notifyPropertyChangedListeners( + property: 'pageNavigate', + ); + _pdfViewerController._notifyPropertyChangedListeners( + property: 'jumpToBookmark', + ); if (_pdfViewerController._searchText.isNotEmpty) { - _pdfViewerController.notifyPropertyChangedListeners( - property: 'searchText'); + _pdfViewerController._notifyPropertyChangedListeners( + property: 'searchText', + ); } if (_pdfViewerController.zoomLevel > 1 && widget.pageLayoutMode == PdfPageLayoutMode.single) { - _singlePageViewKey.currentState! - .scaleTo(_pdfViewerController.zoomLevel); + _singlePageViewKey.currentState!.scaleTo( + _pdfViewerController.zoomLevel, + ); } } } - /// Find whether device is mobile or Laptop. - void _findDevice(BuildContext context) { + /// Find whether device is mobile or tablet. + Future _findDevice(BuildContext context) async { /// Standard diagonal offset of tablet. - const double _kPdfStandardDiagonalOffset = 1100.0; + 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; + final double diagonal = sqrt( + (size.width * size.width) + (size.height * size.height), + ); + _isMobileView = diagonal < kPdfStandardDiagonalOffset; + if (!kIsWeb) { + _isMobileView |= Platform.isIOS || Platform.isAndroid; + } + if (!kIsDesktop && + !Platform.isIOS && + !Platform.environment.containsKey('FLUTTER_TEST')) { + final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + _isAndroidTV = androidInfo.systemFeatures.contains( + 'android.software.leanback', + ); + } + if (kIsWeb && _isMobileWebView == null) { + final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + final WebBrowserInfo webBrowserInfo = await deviceInfo.webBrowserInfo; + _isMobileWebView = + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS || + (defaultTargetPlatform == TargetPlatform.macOS && + (webBrowserInfo.maxTouchPoints ?? 0) > 0); + } + if (kIsWeb) { + _isMobileView |= _isMobileWebView ?? false; + } + _isTablet = + _isAndroidTV ? !_isAndroidTV : diagonal > kPdfStandardDiagonalOffset; } /// Get the global rect of viewport region. Rect? _getViewportGlobalRect() { Rect? viewportGlobalRect; - if (kIsDesktop && - !_isMobile && - ((widget.pageLayoutMode == PdfPageLayoutMode.single && - _singlePageViewKey.currentContext != null) || - (_pdfScrollableStateKey.currentContext != null && - widget.pageLayoutMode == PdfPageLayoutMode.continuous))) { + if ((widget.pageLayoutMode == PdfPageLayoutMode.single && + _singlePageViewKey.currentContext != null) || + (_pdfScrollableStateKey.currentContext != null && + widget.pageLayoutMode == PdfPageLayoutMode.continuous)) { RenderBox viewportRenderBox; if (widget.pageLayoutMode == PdfPageLayoutMode.single) { viewportRenderBox = - // ignore: avoid_as - (_singlePageViewKey.currentContext!.findRenderObject())! - as RenderBox; + _singlePageViewKey.currentContext!.findRenderObject()! as RenderBox; } else { viewportRenderBox = - // ignore: avoid_as - (_pdfScrollableStateKey.currentContext!.findRenderObject())! + _pdfScrollableStateKey.currentContext!.findRenderObject()! as RenderBox; } final Offset position = viewportRenderBox.localToGlobal(Offset.zero); - final Size containerSize = viewportRenderBox.size; - viewportGlobalRect = Rect.fromLTWH( - position.dx, position.dy, containerSize.width, containerSize.height); + if (viewportRenderBox.hasSize) { + final Size containerSize = viewportRenderBox.size; + viewportGlobalRect = Rect.fromLTWH( + position.dx, + position.dy, + containerSize.width, + containerSize.height, + ); + } } return viewportGlobalRect; } - @override - Widget build(BuildContext context) { - final Container emptyContainer = Container( - color: _pdfViewerThemeData!.backgroundColor ?? + Widget _getEmptyContainer() { + return Container( + color: + _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? (_themeData!.colorScheme.brightness == Brightness.light ? const Color(0xFFD6D6D6) : const Color(0xFF303030)), ); - final Stack emptyLinearProgressView = Stack( + } + + Widget _getReplacementWidget(int pageNumber, Size size) { + final double pageSpacing = + pageNumber == _pdfViewerController.pageCount ? 0.0 : widget.pageSpacing; + final double heightSpacing = + _scrollDirection == PdfScrollDirection.horizontal || + widget.pageLayoutMode == PdfPageLayoutMode.single + ? 0.0 + : pageSpacing; + final double widthSpacing = + widget.scrollDirection == PdfScrollDirection.horizontal && + widget.pageLayoutMode != PdfPageLayoutMode.single + ? pageSpacing + : 0.0; + return SizedBox( + height: size.height + heightSpacing, + width: size.width + widthSpacing, + ); + } + + Widget _getEmptyLinearProgressView() { + return Stack( children: [ - emptyContainer, + _getEmptyContainer(), LinearProgressIndicator( valueColor: AlwaysStoppedAnimation( - _pdfViewerThemeData!.progressBarColor ?? - _themeData!.colorScheme.primary), - backgroundColor: _pdfViewerThemeData!.progressBarColor == null - ? _themeData!.colorScheme.primary.withOpacity(0.2) - : _pdfViewerThemeData!.progressBarColor!.withOpacity(0.2), + _pdfViewerThemeData!.progressBarColor ?? + _effectiveThemeData!.progressBarColor ?? + _themeData!.colorScheme.primary, + ), + backgroundColor: + _pdfViewerThemeData!.progressBarColor != null + ? _pdfViewerThemeData!.progressBarColor!.withValues( + alpha: 0.2, + ) + : _effectiveThemeData!.progressBarColor != null + ? _effectiveThemeData!.progressBarColor!.withValues( + alpha: 0.2, + ) + : _themeData!.colorScheme.primary.withValues(alpha: 0.2), ), ], ); + } - // call PdfViewerController methods after ScrollController attached. - _isDocumentLoaded(); - + @override + Widget build(BuildContext context) { /// Find whether device is mobile or Laptop. _findDevice(context); - - final bool isPdfLoaded = _pdfViewerController.pageCount > 0 && + final bool isPdfLoaded = + _pdfViewerController.pageCount > 0 && _originalWidth != null && _originalHeight != null; _pdfDimension = - (_childKey.currentContext?.findRenderObject()?.paintBounds.size) ?? - Size.zero; + _childKey.currentContext?.findRenderObject()?.paintBounds.size ?? + Size.zero; return isPdfLoaded ? Listener( - onPointerSignal: _handlePointerSignal, - onPointerDown: _handlePointerDown, - onPointerMove: _handlePointerMove, - onPointerUp: _handlePointerUp, - child: Container( - color: _pdfViewerThemeData!.backgroundColor ?? - (_themeData!.colorScheme.brightness == Brightness.light - ? const Color(0xFFD6D6D6) - : const Color(0xFF303030)), - // ignore: always_specify_types - child: FutureBuilder( - future: _getImages(), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData) { - final dynamic _pdfImages = snapshot.data; - _renderedImages.clear(); - _viewportConstraints = context - .findRenderObject()! - // ignore: invalid_use_of_protected_member, avoid_as - .constraints as BoxConstraints; - double totalHeight = 0.0; - _isKeyPadRaised = - WidgetsBinding.instance?.window.viewInsets.bottom != - 0.0; - Size viewportDimension = _viewportConstraints.biggest; - if (_isKeyPadRaised) { - _iskeypadClosed = true; - double keyPadHeight = EdgeInsets.fromWindowPadding( - WidgetsBinding.instance!.window.viewInsets, - WidgetsBinding - .instance!.window.devicePixelRatio) - .bottom; - if ((widget.scrollDirection == - PdfScrollDirection.horizontal || - widget.pageLayoutMode == - PdfPageLayoutMode.single) && - keyPadHeight > 0) { - if (viewportDimension.height + keyPadHeight != - _viewportHeight) { - keyPadHeight = - _viewportHeight - viewportDimension.height; - } else { - _viewportHeight = - viewportDimension.height + keyPadHeight; - } - } - - viewportDimension = Size(viewportDimension.width, - viewportDimension.height + keyPadHeight); - } else { - if (_iskeypadClosed) { - viewportDimension = - Size(viewportDimension.width, _viewportHeight); - _iskeypadClosed = false; - } else { - _viewportHeight = viewportDimension.height; - } + onPointerSignal: _handlePointerSignal, + onPointerDown: _handlePointerDown, + onPointerMove: _handlePointerMove, + onPointerUp: _handlePointerUp, + child: Container( + color: + _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? + (_themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030)), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + _viewportConstraints = constraints; + if (_viewportSize != _viewportConstraints.biggest) { + _viewportSize = _viewportConstraints.biggest; + _getTileImage(); + } + double totalHeight = 0.0; + _isKeyPadRaised = View.of(context).viewInsets.bottom != 0.0; + Size viewportDimension = _viewportConstraints.biggest; + // Viewport dimension without keypad height. + Size? visibleViewportDimension; + if (_isKeyPadRaised) { + visibleViewportDimension = viewportDimension; + _iskeypadClosed = true; + double keyPadHeight = + EdgeInsets.fromViewPadding( + View.of(context).viewInsets, + View.of(context).devicePixelRatio, + ).bottom; + if (keyPadHeight > 0) { + if (viewportDimension.height + keyPadHeight != + _viewportHeight) { + keyPadHeight = _viewportHeight - viewportDimension.height; + } else { + _viewportHeight = viewportDimension.height + keyPadHeight; + } + } + + viewportDimension = Size( + viewportDimension.width, + viewportDimension.height + keyPadHeight, + ); + } else { + if (_iskeypadClosed) { + viewportDimension = Size( + viewportDimension.width, + _viewportHeight, + ); + _iskeypadClosed = false; + } else { + _viewportHeight = viewportDimension.height; + } + } + if (!isBookmarkViewOpen) { + _otherContextHeight ??= + MediaQuery.of(context).size.height - + _viewportConstraints.maxHeight; + } + if (_deviceOrientation == Orientation.landscape) { + _viewportHeightInLandscape ??= + MediaQuery.of(context).size.height - _otherContextHeight!; + } + if (!_pdfDimension.isEmpty) { + if (_scrollDirection == PdfScrollDirection.vertical) { + _maxScrollExtent = + _pdfDimension.height - + (viewportDimension.height / + _pdfViewerController.zoomLevel); + } else { + _maxScrollExtent = + _pdfDimension.width - + (viewportDimension.width / + _pdfViewerController.zoomLevel); + } + } + _viewportGlobalRect = _getViewportGlobalRect(); + Widget child; + final List children = List< + Widget + >.generate(_pdfViewerController.pageCount, (int index) { + if (index == 0) { + totalHeight = 0; + } + if (_originalWidth!.length != + _pdfViewerController.pageCount) { + return _getEmptyContainer(); + } + final int pageIndex = index + 1; + final Size calculatedSize = _calculateSize( + BoxConstraints(maxWidth: _viewportConstraints.maxWidth), + _originalWidth![index].toDouble(), + _originalHeight![index].toDouble(), + _viewportConstraints.maxWidth, + viewportDimension.height, + ); + if (!_pdfPagesKey.containsKey(pageIndex)) { + _pdfPagesKey[pageIndex] = GlobalKey(); + } + if (kIsDesktop && !_isMobileView) { + if (_originalWidth![index].toDouble() > _maxPdfPageWidth != + null) { + _maxPdfPageWidth = _originalWidth![index].toDouble(); + } + } + + Widget page = PdfPageView( + _pdfPagesKey[pageIndex]!, + _viewportGlobalRect, + viewportDimension, + widget.interactionMode, + _plugin.documentID, + calculatedSize.width, + calculatedSize.height, + widget.pageSpacing, + _document, + _pdfPages, + index, + _pdfViewerController, + _effectiveUndoController, + widget.maxZoomLevel, + widget.enableDocumentLinkAnnotation, + widget.enableTextSelection, + _textSelectionHelper, + _handleTextSelectionChanged, + widget.onHyperlinkClicked, + _handleTextSelectionDragStarted, + _handleTextSelectionDragEnded, + widget.currentSearchTextHighlightColor, + widget.otherSearchTextHighlightColor, + _textCollection, + _isMobileView, + _pdfViewerController._pdfTextSearchResult, + _pdfScrollableStateKey, + _singlePageViewKey, + _scrollDirection, + _handlePdfPagePointerDown, + _handlePdfPagePointerMove, + _handlePdfPagePointerUp, + isBookmarkViewOpen ? '' : _pageTextExtractor[index], + widget.pageLayoutMode == PdfPageLayoutMode.single, + _textDirection!, + widget.canShowHyperlinkDialog, + widget.enableHyperlinkNavigation, + _isAndroidTV, + widget.canShowPageLoadingIndicator, + widget.canShowSignaturePadDialog, + _handlePageTap, + _pdfViewerController._formFields, + _pdfViewerController._annotations, + _selectedAnnotation, + _onAnnotationSelectionChanged, + _onStickyNoteAnnotationDoubleTapped, + ); + final double pageSpacing = + index == _pdfViewerController._pageCount - 1 + ? 0.0 + : widget.pageSpacing; + _pdfPages[pageIndex] = PdfPageInfo( + totalHeight, + calculatedSize, + ); + if (_isMobileWebView ?? false) { + page = Visibility( + visible: _isPageVisible(pageIndex), + replacement: _getReplacementWidget( + pageIndex, + calculatedSize, + ), + child: page, + ); + } + // In RTL direction, the last page is rendered at Offset.zero. + // Hence, the we are interchanging the page offset value of all the pages. + if (_textDirection == TextDirection.rtl && + widget.pageLayoutMode == PdfPageLayoutMode.continuous && + _scrollDirection == PdfScrollDirection.horizontal && + _pdfViewerController.pageCount == pageIndex) { + for (int i = 1; i <= _pdfViewerController.pageCount; i++) { + _pdfPages[i]!.pageOffset = + totalHeight - _pdfPages[i]!.pageOffset; + } + } + + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + totalHeight += calculatedSize.height + pageSpacing; + } else { + totalHeight += calculatedSize.width + pageSpacing; + } + _updateScrollDirectionChange( + _offsetBeforeOrientationChange, + pageIndex, + totalHeight, + ); + _updateOffsetOnOrientationChange( + _offsetBeforeOrientationChange, + pageIndex, + totalHeight, + ); + return page; + }); + Widget? pdfContainer; + if (!_isLoaded) { + Future.delayed(Duration.zero, () async { + if (_document != null && widget.onDocumentLoaded != null) { + _isDocumentLoadInitiated = false; + widget.onDocumentLoaded!( + PdfDocumentLoadedDetails(_document!), + ); + } + _checkMount(); + _isLoaded = true; + }); + } + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + _pageController = PageController( + initialPage: _pdfViewerController._pageNumber - 1, + ); + pdfContainer = MouseRegion( + cursor: _cursor, + onHover: (PointerHoverEvent details) { + if (widget.interactionMode == PdfInteractionMode.pan && + _cursor != SystemMouseCursors.grab) { + setState(() { + _cursor = SystemMouseCursors.grab; + }); + } else if (widget.interactionMode == + PdfInteractionMode.selection && + _cursor != SystemMouseCursors.basic) { + setState(() { + _cursor = SystemMouseCursors.basic; + }); } - if (!isBookmarkViewOpen) { - _otherContextHeight ??= - MediaQuery.of(context).size.height - - _viewportConstraints.maxHeight; + }, + child: SinglePageView( + _singlePageViewKey, + _pdfViewerController, + _transformationController, + _pageController, + _handleSinglePageViewPageChanged, + _interactionUpdate, + viewportDimension, + visibleViewportDimension, + widget.maxZoomLevel, + widget.canShowPaginationDialog, + widget.canShowScrollHead, + widget.canShowScrollStatus, + _pdfPages, + _isMobileView, + widget.enableDoubleTapZooming, + widget.interactionMode, + _isScaleEnabled, + _handleSinglePageViewZoomLevelChanged, + _handleDoubleTap, + _handlePdfOffsetChanged, + isBookmarkViewOpen, + _textDirection!, + _isTablet, + _scrollDirection, + _getTileImage, + children, + ), + ); + if (_isSinglePageViewPageChanged && + _renderedImages.contains( + _pdfViewerController.pageNumber, + )) { + Future.delayed(Duration.zero, () async { + if (_pageController.hasClients) { + _pdfViewerController._scrollPositionX = 0; } - if (_deviceOrientation == Orientation.landscape) { - _viewportHeightInLandscape ??= - MediaQuery.of(context).size.height - - _otherContextHeight!; + if (!_isSearchStarted) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); } - if (!_pdfDimension.isEmpty) { - if (_scrollDirection == PdfScrollDirection.vertical) { - _maxScrollExtent = _pdfDimension.height - - (viewportDimension.height / - _pdfViewerController.zoomLevel); - } else { - _maxScrollExtent = _pdfDimension.width - - (viewportDimension.width / - _pdfViewerController.zoomLevel); - } + if (getSelectedTextLines().isNotEmpty && + getSelectedTextLines().first.pageNumber + 1 == + _pdfViewerController.pageNumber) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.updateContextMenuPosition(); } - Widget child; - final List children = List.generate( - _pdfViewerController.pageCount, (int index) { - if (index == 0) { - totalHeight = 0; - } - if (_originalWidth!.length != - _pdfViewerController.pageCount) { - return emptyContainer; - } - final int pageIndex = index + 1; - final Size calculatedSize = _calculateSize( - BoxConstraints( - maxWidth: _viewportConstraints.maxWidth, - maxHeight: double.infinity), - _originalWidth![index], - _originalHeight![index], - _viewportConstraints.maxWidth, - viewportDimension.height); - if (!_pdfPagesKey.containsKey(pageIndex)) { - _pdfPagesKey[pageIndex] = GlobalKey(); - } - _isOverflowed = _originalWidth![index] > - // ignore: avoid_as - _viewportConstraints.maxWidth as bool; - if (kIsDesktop && !_isMobile) { - if (_originalWidth![index] > _maxPdfPageWidth != - null) { - _maxPdfPageWidth = - // ignore: avoid_as - _originalWidth![index] as double; - } - } - if (_pdfImages[pageIndex] != null) { - if (_pageTextExtractor.isEmpty || - !_pageTextExtractor.containsKey(index)) { - _pageTextExtractor[index] = _pdfTextExtractor! - .extractText(startPageIndex: index); - } - } - Rect? viewportGlobalRect; - if (_isTextSelectionCleared) { - viewportGlobalRect = _getViewportGlobalRect(); - } - final PdfPageView page = PdfPageView( - _pdfPagesKey[pageIndex]!, - _pdfImages[pageIndex], - viewportGlobalRect, - viewportDimension, - widget.interactionMode, - (kIsDesktop && - !_isMobile && - !_isOverflowed && - widget.pageLayoutMode == - PdfPageLayoutMode.continuous) - ? _originalWidth![index] - : calculatedSize.width, - (kIsDesktop && - !_isMobile && - !_isOverflowed && - widget.pageLayoutMode == - PdfPageLayoutMode.continuous) - ? _originalHeight![index] - : calculatedSize.height, - widget.pageSpacing, - _document, - _pdfPages, - index, - _pdfViewerController, - widget.enableDocumentLinkAnnotation, - widget.enableTextSelection, - widget.onTextSelectionChanged, - _handleTextSelectionDragStarted, - _handleTextSelectionDragEnded, - widget.currentSearchTextHighlightColor, - widget.otherSearchTextHighlightColor, - _textCollection, - _isMobile, - _pdfViewerController._pdfTextSearchResult, - _pdfScrollableStateKey, - _singlePageViewKey, - _scrollDirection, - _handlePdfPagePointerDown, - _handlePdfPagePointerMove, - _handlePdfPagePointerUp, - isBookmarkViewOpen ? '' : _pageTextExtractor[index], - widget.pageLayoutMode == PdfPageLayoutMode.single); - final double pageSpacing = - index == _pdfViewerController.pageCount - 1 - ? 0.0 - : widget.pageSpacing; - if (kIsDesktop && !_isMobile && !_isOverflowed) { - _pdfPages[pageIndex] = PdfPageInfo( - totalHeight, - Size(_originalWidth![index], - _originalHeight![index])); - if (_scrollDirection == PdfScrollDirection.vertical && - widget.pageLayoutMode != - PdfPageLayoutMode.single) { - totalHeight += - _originalHeight![index] + pageSpacing; - } else { - if (widget.pageLayoutMode == - PdfPageLayoutMode.continuous) { - totalHeight += - _originalWidth![index] + pageSpacing; - } else { - _pdfPages[pageIndex] = - PdfPageInfo(totalHeight, calculatedSize); - totalHeight += - calculatedSize.height + pageSpacing; - } - } - } else { - _pdfPages[pageIndex] = - PdfPageInfo(totalHeight, calculatedSize); - if (_scrollDirection == PdfScrollDirection.vertical && - widget.pageLayoutMode != - PdfPageLayoutMode.single) { - totalHeight += calculatedSize.height + pageSpacing; - } else { - totalHeight += calculatedSize.width + pageSpacing; - } - } - _updateScrollDirectionChange( - _offsetBeforeOrientationChange, - pageIndex, - totalHeight); - _updateOffsetOnOrientationChange( - _offsetBeforeOrientationChange, - pageIndex, - totalHeight); - if (_pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox != - null && - !_isTextSelectionCleared) { - _isTextSelectionCleared = true; - if (kIsWeb || - !Platform.environment - .containsKey('FLUTTER_TEST')) { - Future.delayed(Duration.zero, () async { - _clearSelection(); - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.disposeMouseSelection(); - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.focusNode - .requestFocus(); - }); - } - } - if (page.imageStream != null) { - _renderedImages.add(pageIndex); - } - return page; - }); - Widget? pdfContainer; - if (widget.pageLayoutMode == PdfPageLayoutMode.single) { - _pageController = PageController( - initialPage: _pdfViewerController.pageNumber - 1); - pdfContainer = MouseRegion( - cursor: _cursor, - onHover: (PointerHoverEvent details) { - setState(() { - if (widget.interactionMode == - PdfInteractionMode.pan) { - _cursor = SystemMouseCursors.grab; - } else { - _cursor = SystemMouseCursors.basic; - } - }); - }, - child: SinglePageView( - _singlePageViewKey, - _pdfViewerController, - _pageController, - _handleSinglePageViewPageChanged, - _interactionUpdate, - viewportDimension, - widget.canShowPaginationDialog, - widget.canShowScrollHead, - widget.canShowScrollStatus, - _pdfPages, - _isMobile, - widget.enableDoubleTapZooming, - widget.interactionMode, - _isScaleEnabled, - _handleSinglePageViewZoomLevelChanged, - _handleDoubleTap, - _handlePdfOffsetChanged, - isBookmarkViewOpen, - children), - ); - if (_isSinglePageViewPageChanged && - _renderedImages - .contains(_pdfViewerController.pageNumber)) { - Future.delayed(Duration.zero, () async { - if (_pageController.hasClients) { - _pdfViewerController._scrollPositionX = - _pageController.offset; - } - if (!_isSearchStarted) { - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.focusNode - .requestFocus(); - } - if (getSelectedTextLines().isNotEmpty && - getSelectedTextLines().first.pageNumber + 1 == - _pdfViewerController.pageNumber) { - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.updateContextMenuPosition(); - } - _isSinglePageViewPageChanged = false; - }); - } - } else { - final Size childSize = _getChildSize(viewportDimension); - if (_scrollDirection == PdfScrollDirection.horizontal) { - child = Row( - key: _childKey, - mainAxisAlignment: MainAxisAlignment.center, - children: children); - } else { - child = Column( - key: _childKey, - mainAxisAlignment: MainAxisAlignment.center, - children: children); - } - child = MouseRegion( - cursor: _cursor, - onHover: (PointerHoverEvent details) { - setState(() { - if (widget.interactionMode == - PdfInteractionMode.pan) { - _cursor = SystemMouseCursors.grab; - } else { - _cursor = SystemMouseCursors.basic; - } - }); - }, - child: SizedBox( - height: childSize.height, - width: childSize.width, - child: child), - ); - pdfContainer = PdfScrollable( - widget.canShowPaginationDialog, - widget.canShowScrollStatus, - widget.canShowScrollHead, - _pdfViewerController, - _isMobile, - _pdfDimension, - _totalImageSize, - viewportDimension, - _handlePdfOffsetChanged, - _panEnabled, - _maxScale, - _minScale, - widget.enableDoubleTapZooming, - widget.interactionMode, - _maxPdfPageWidth, - _isScaleEnabled, - _maxScrollExtent, - _pdfPages, - _scrollDirection, - isBookmarkViewOpen, - child, - key: _pdfScrollableStateKey, - onDoubleTap: _handleDoubleTap, - ); - // Updates current offset when scrollDirection change occurs. - if (_isScrollDirectionChange) { - _pdfScrollableStateKey.currentState - ?.forcePixels(_scrollDirectionSwitchOffset); - _isScrollDirectionChange = false; - } + _isSinglePageViewPageChanged = false; + }); + } + } else { + final Size childSize = _getChildSize(viewportDimension); + if (_scrollDirection == PdfScrollDirection.horizontal) { + child = Row( + key: _childKey, + mainAxisAlignment: MainAxisAlignment.center, + children: children, + ); + } else { + child = Column( + key: _childKey, + mainAxisAlignment: MainAxisAlignment.center, + children: children, + ); + } + child = MouseRegion( + cursor: _cursor, + onHover: (PointerHoverEvent details) { + if (widget.interactionMode == PdfInteractionMode.pan && + _cursor != SystemMouseCursors.grab) { + setState(() { + _cursor = SystemMouseCursors.grab; + }); + } else if (widget.interactionMode == + PdfInteractionMode.selection && + _cursor != SystemMouseCursors.basic) { + setState(() { + _cursor = SystemMouseCursors.basic; + }); } - return Stack( - children: [ - pdfContainer, - BookmarkView(_bookmarkKey, _document, - _pdfViewerController, _handleBookmarkViewChanged), - ], + }, + child: SizedBox( + height: childSize.height, + width: childSize.width, + child: child, + ), + ); + pdfContainer = PdfScrollable( + _transformationController, + widget.canShowPaginationDialog, + widget.canShowScrollStatus, + widget.canShowScrollHead, + _pdfViewerController, + _isMobileView, + _pdfDimension, + _totalImageSize, + viewportDimension, + visibleViewportDimension, + _handlePdfOffsetChanged, + _panEnabled, + widget.maxZoomLevel, + _minScale, + widget.enableDoubleTapZooming, + widget.interactionMode, + _maxPdfPageWidth, + _isScaleEnabled, + _maxScrollExtent, + _pdfPages, + _scrollDirection, + isBookmarkViewOpen, + _textDirection!, + child, + _getTileImage, + key: _pdfScrollableStateKey, + onDoubleTap: _handleDoubleTap, + ); + // Updates current offset when scrollDirection change occurs. + if (_isScrollDirectionChange) { + _pdfScrollableStateKey.currentState?.forcePixels( + _scrollDirectionSwitchOffset, + ); + _isScrollDirectionChange = false; + } + } + WidgetsBinding.instance.addPostFrameCallback(( + Duration timeStamp, + ) { + if (super.mounted && context.mounted) { + // call PdfViewerController methods after ScrollController attached. + _isDocumentLoaded(); + + // If the text direction is changed, then jump to the current page to avoid the abrupt change in the view. + if (_isTextDirectionChanged) { + _isTextDirectionChanged = false; + _pdfViewerController.jumpToPage( + _pdfViewerController.pageNumber, ); - } else if (snapshot.hasError) { - return emptyContainer; - } else { - return emptyLinearProgressView; } - }), + } + }); + return Stack( + children: [ + pdfContainer, + BookmarkView( + _bookmarkKey, + _document, + _pdfViewerController, + _handleBookmarkViewChanged, + _textDirection!, + ), + ], + ); + }, ), - ) + ), + ) : (_hasError ? _isEncryptedDocument ? _showWebPasswordDialogue() - : emptyContainer - : emptyLinearProgressView); + : _getEmptyContainer() + : _getEmptyLinearProgressView()); + } + + void _checkVisiblePages() { + if (_pdfPages.isEmpty) { + return; + } + _renderedImages.clear(); + final double zoomLevel = _transformationController.value[0]; + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + if (!_pageTextExtractor.containsKey( + _pdfViewerController.pageNumber - 1, + )) { + _pageTextExtractor[_pdfViewerController.pageNumber - + 1] = _pdfTextExtractor!.extractText( + startPageIndex: _pdfViewerController.pageNumber - 1, + ); + } + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.getPageImage( + _viewportSize, + zoomLevel, + ); + _renderedImages.add(_pdfViewerController.pageNumber); + } else { + final Offset offset = _transformationController.toScene(Offset.zero); + final double x = offset.dx; + final double y = offset.dy; + + final Rect viewportRect = Rect.fromLTWH( + x, + y, + _viewportSize.width, + _viewportSize.height, + ); + // Render or clear images from the current page to the last page. + for ( + int pageNumber = _pdfViewerController.pageNumber; + pageNumber <= _pdfViewerController.pageCount; + pageNumber++ + ) { + final Rect pageRect = Rect.fromLTWH( + _scrollDirection == PdfScrollDirection.vertical + ? 0 + : _pdfPages[pageNumber]!.pageOffset, + _scrollDirection == PdfScrollDirection.vertical + ? _pdfPages[pageNumber]!.pageOffset + : 0, + _pdfPages[pageNumber]!.pageSize.width, + _pdfPages[pageNumber]!.pageSize.height, + ); + + if (!viewportRect.intersect(pageRect).isEmpty) { + _renderedImages.add(pageNumber); + //Extract page text only if it's not already available. + if (!_pageTextExtractor.containsKey(pageNumber - 1)) { + _pageTextExtractor[pageNumber - 1] = _pdfTextExtractor!.extractText( + startPageIndex: pageNumber - 1, + ); + } + _pdfPagesKey[pageNumber]?.currentState?.getPageImage( + _viewportSize, + zoomLevel, + ); + } else { + _pdfPagesKey[pageNumber]?.currentState?.clearPageImage(); + } + } + // Render or clear images from the current page to the first page. + for ( + int pageNumber = _pdfViewerController.pageNumber - 1; + pageNumber > 0; + pageNumber-- + ) { + final Rect pageRect = Rect.fromLTWH( + _scrollDirection == PdfScrollDirection.vertical + ? 0 + : _pdfPages[pageNumber]!.pageOffset, + _scrollDirection == PdfScrollDirection.vertical + ? _pdfPages[pageNumber]!.pageOffset + : 0, + _pdfPages[pageNumber]!.pageSize.width, + _pdfPages[pageNumber]!.pageSize.height, + ); + + if (!viewportRect.intersect(pageRect).isEmpty) { + _renderedImages.add(pageNumber); + //Extract page text only if it's not already available. + if (!_pageTextExtractor.containsKey(pageNumber - 1)) { + _pageTextExtractor[pageNumber - 1] = _pdfTextExtractor!.extractText( + startPageIndex: pageNumber - 1, + ); + } + _pdfPagesKey[pageNumber]?.currentState?.getPageImage( + _viewportSize, + zoomLevel, + ); + } else { + _pdfPagesKey[pageNumber]?.currentState?.clearPageImage(); + } + } + } + } + + bool _isPageVisible(int currentPageNumber) { + if (_pdfPages.isEmpty) { + return false; + } + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + if (currentPageNumber == _pdfViewerController.pageNumber || + currentPageNumber - 1 == _pdfViewerController.pageNumber || + currentPageNumber + 1 == _pdfViewerController.pageNumber) { + return true; + } + } else { + final Offset offset = _transformationController.toScene(Offset.zero); + final double x = + _textDirection == TextDirection.rtl && + _scrollDirection == PdfScrollDirection.horizontal + ? _maxScrollExtent - offset.dx + : offset.dx; + final double y = offset.dy; + + final Rect viewportRect = Rect.fromLTWH( + x - _viewportSize.width / 2, + y - _viewportSize.height / 2, + _viewportSize.width * 2, + _viewportSize.height * 2, + ); + + final Rect pageRect = Rect.fromLTWH( + _scrollDirection == PdfScrollDirection.vertical + ? 0 + : _pdfPages[currentPageNumber]!.pageOffset, + _scrollDirection == PdfScrollDirection.vertical + ? _pdfPages[currentPageNumber]!.pageOffset + : 0, + _pdfPages[currentPageNumber]!.pageSize.width, + _pdfPages[currentPageNumber]!.pageSize.height, + ); + + if (!viewportRect.intersect(pageRect).isEmpty) { + return true; + } + } + return false; } void _handleSinglePageViewPageChanged(int newPage) { @@ -2230,21 +4132,22 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfViewerController._zoomLevel = 1.0; if (_singlePageViewKey.currentState != null) { _singlePageViewKey.currentState!.previousZoomLevel = 1; - _pdfViewerController.notifyPropertyChangedListeners( - property: 'zoomLevel'); + _pdfViewerController._notifyPropertyChangedListeners( + property: 'zoomLevel', + ); } _previousHorizontalOffset = 0.0; _pageChanged(); - _isZoomChanged = true; _checkMount(); _isSinglePageViewPageChanged = true; if (widget.onTextSelectionChanged != null) { - widget - .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); + widget.onTextSelectionChanged!( + PdfTextSelectionChangedDetails(null, null), + ); } } - void _interactionUpdate(double zoomLevel) { + Future _interactionUpdate(double zoomLevel) async { _pdfViewerController._zoomLevel = zoomLevel; } @@ -2253,8 +4156,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { final double previousScale = _singlePageViewKey.currentState!.previousZoomLevel; if (previousScale != _pdfViewerController._zoomLevel) { - _pdfViewerController.notifyPropertyChangedListeners( - property: 'zoomLevel'); + _pdfViewerController._notifyPropertyChangedListeners( + property: 'zoomLevel', + ); } } } @@ -2264,17 +4168,24 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { double childHeight = 0, childWidth = 0; if (_pdfScrollableStateKey.currentState != null) { - widthFactor = _pdfScrollableStateKey.currentState!.paddingWidthScale == 0 - ? _pdfViewerController.zoomLevel - : _pdfScrollableStateKey.currentState!.paddingWidthScale; + widthFactor = + _pdfScrollableStateKey.currentState!.paddingWidthScale == 0 + ? _pdfViewerController.zoomLevel + : _pdfScrollableStateKey.currentState!.paddingWidthScale; heightFactor = _pdfScrollableStateKey.currentState!.paddingHeightScale == 0 ? _pdfViewerController.zoomLevel : _pdfScrollableStateKey.currentState!.paddingHeightScale; } if (_pdfPages[_pdfViewerController.pageCount] != null) { + // In RTL direction, the last page is rendered at Offset.zero and the first page is rendered at the end. + // Hence, we are considering the first page as the last page and vice versa. final PdfPageInfo lastPageInfo = - _pdfPages[_pdfViewerController.pageCount]!; + _textDirection == TextDirection.rtl && + widget.pageLayoutMode == PdfPageLayoutMode.continuous && + _scrollDirection == PdfScrollDirection.horizontal + ? _pdfPages[1]! + : _pdfPages[_pdfViewerController.pageCount]!; final double zoomLevel = _pdfViewerController.zoomLevel; final Size currentPageSize = _pdfPages[_pdfViewerController.pageNumber]!.pageSize; @@ -2283,29 +4194,35 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_scrollDirection == PdfScrollDirection.vertical) { totalImageWidth = currentPageSize.width * zoomLevel; } - childWidth = viewportDimension.width > totalImageWidth - ? viewportDimension.width / widthFactor.clamp(1, 3) - : totalImageWidth / widthFactor.clamp(1, 3); + childWidth = + viewportDimension.width > totalImageWidth + ? viewportDimension.width / + widthFactor.clamp(1, widget.maxZoomLevel) + : totalImageWidth / widthFactor.clamp(1, widget.maxZoomLevel); double totalImageHeight = currentPageSize.height * zoomLevel; if (_scrollDirection == PdfScrollDirection.vertical) { totalImageHeight = (lastPageInfo.pageOffset + lastPageInfo.pageSize.height) * - zoomLevel; + zoomLevel; } - childHeight = viewportDimension.height > totalImageHeight - ? viewportDimension.height / heightFactor.clamp(1, 3) - : totalImageHeight / heightFactor.clamp(1, 3); - _totalImageSize = - Size(totalImageWidth / zoomLevel, totalImageHeight / zoomLevel); - if (_isMobile && + childHeight = + viewportDimension.height > totalImageHeight + ? viewportDimension.height / + heightFactor.clamp(1, widget.maxZoomLevel) + : totalImageHeight / heightFactor.clamp(1, widget.maxZoomLevel); + _totalImageSize = Size( + totalImageWidth / zoomLevel, + totalImageHeight / zoomLevel, + ); + if (_isMobileView && !_isKeyPadRaised && childHeight > _viewportConstraints.maxHeight && (totalImageHeight / zoomLevel).floor() <= _viewportConstraints.maxHeight.floor()) { childHeight = _viewportConstraints.maxHeight; } - if (_isMobile && + if (_isMobileView && childWidth > _viewportConstraints.maxWidth && totalImageWidth / zoomLevel <= _viewportConstraints.maxWidth) { childWidth = _viewportConstraints.maxWidth; @@ -2319,71 +4236,193 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } void _handlePdfPagePointerMove(PointerMoveEvent details) { - if (details.kind == PointerDeviceKind.touch && kIsDesktop && !_isMobile) { - setState(() { - _isScaleEnabled = true; - }); + if (details.kind == PointerDeviceKind.touch && kIsDesktop) { + if (!_isScaleEnabled) { + setState(() { + _isScaleEnabled = true; + }); + } } } void _handlePdfPagePointerUp(PointerUpEvent details) { - if (details.kind == PointerDeviceKind.touch && kIsDesktop && !_isMobile) { - setState(() { - _isScaleEnabled = false; - }); + if (details.kind == PointerDeviceKind.touch && kIsDesktop) { + if (_isScaleEnabled) { + setState(() { + _isScaleEnabled = false; + }); + } } } + void _handlePageTap(Offset pagePosition, int pageIndex) { + _tappedPageNumber = pageIndex + 1; + _tappedPagePosition = pagePosition; + } + void _handlePointerSignal(PointerSignalEvent event) { if (!isBookmarkViewOpen) { _pdfScrollableStateKey.currentState?.receivedPointerSignal(event); } + _updateStickyNoteDialog(); } void _handlePointerDown(PointerDownEvent event) { + _canInvokeOnTap = true; + if (_pagePointerDownTimeStamp != Duration.zero && + event.timeStamp - _pagePointerDownTimeStamp < kDoubleTapTimeout) { + final Offset draggedDistance = + event.localPosition - _pagePointerDownPosition; + if (_pagePointerDownPosition != Offset.zero && + draggedDistance.dx.abs() < kDoubleTapSlop && + draggedDistance.dy.abs() < kDoubleTapSlop) { + _isDoubleTapped = true; + } else { + _isDoubleTapped = false; + } + } else { + _isDoubleTapped = false; + } + _pagePointerDownPosition = event.localPosition; + _pagePointerDownTimeStamp = event.timeStamp; if (!_isPdfPageTapped) { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox ?.clearSelection(); } - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.focusNode - .requestFocus(); + bool isTextFormFieldFocused = false; + + /// Requesting focus to the text form fields in mobile platforms + if (_textBoxFocusNodes.isNotEmpty) { + for (final MapEntry entry + in _textBoxFocusNodes.entries) { + if (entry.key.globalRect.contains(event.position)) { + entry.value.requestFocus(); + isTextFormFieldFocused = true; + } + } + } + if (!isTextFormFieldFocused) { + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.focusNode + .requestFocus(); + } } void _handlePointerMove(PointerMoveEvent event) { + final Offset draggedDistance = + event.localPosition - _pagePointerDownPosition; + if (event.kind == PointerDeviceKind.touch) { + _canInvokeOnTap &= + draggedDistance.dx.abs() < kTouchSlop && + draggedDistance.dy.abs() < kTouchSlop; + } else { + _canInvokeOnTap &= + draggedDistance.dx.abs() < kPrecisePointerHitSlop && + draggedDistance.dy.abs() < kPrecisePointerHitSlop; + } if (widget.interactionMode == PdfInteractionMode.pan) { - _cursor = SystemMouseCursors.grabbing; + if (_cursor != SystemMouseCursors.grabbing) { + setState(() { + _cursor = SystemMouseCursors.grabbing; + }); + } } if (!_isScaleEnabled && event.kind == PointerDeviceKind.touch && - (!kIsDesktop || _isMobile)) { + (!kIsDesktop)) { setState(() { _isScaleEnabled = true; }); } - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.scrollStarted(); + if (!_canInvokeOnTap) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.scrollStarted(); + } } void _handlePointerUp(PointerUpEvent details) { + _canInvokeOnTap &= + details.timeStamp - _pagePointerDownTimeStamp < kLongPressTimeout; + bool isSlopDistanceExceeded = false; + if (details.kind == PointerDeviceKind.touch) { + isSlopDistanceExceeded = + kTouchSlop > + (details.localPosition.dx - _pagePointerDownPosition.dx).abs() && + kTouchSlop > + (details.localPosition.dy - _pagePointerDownPosition.dy).abs(); + } else { + isSlopDistanceExceeded = + kPrecisePointerHitSlop > + (details.localPosition.dx - _pagePointerDownPosition.dx).abs() && + kPrecisePointerHitSlop > + (details.localPosition.dy - _pagePointerDownPosition.dy).abs(); + } + final bool isLongPressed = + (details.timeStamp - _pagePointerDownTimeStamp > kLongPressTimeout) && + isSlopDistanceExceeded; + Timer(kDoubleTapTimeout, () { + if (!_isDoubleTapped && _canInvokeOnTap && !isBookmarkViewOpen) { + if (widget.onTap != null) { + final Offset viewportPosition = + _textDirection == TextDirection.ltr + ? details.localPosition + : Offset( + _viewportWidth - details.localPosition.dx, + details.localPosition.dy, + ); + widget.onTap!( + PdfGestureDetails( + _tappedPageNumber, + viewportPosition, + _tappedPagePosition, + ), + ); + } + if (_pdfViewerController.annotationMode == + PdfAnnotationMode.stickyNote && + _tappedPageNumber != -1) { + if (kIsDesktop && !_isMobileView) { + _addSticyNoteAnnotation(_tappedPagePosition, '', _tappedPageNumber); + } else { + _showStickyNoteDialogMobile( + _tappedPagePosition, + null, + _tappedPageNumber, + ); + } + } + } + for (final MapEntry entry + in _textBoxFocusNodes.entries) { + if (entry.value.hasFocus && + !entry.key.globalRect.contains(details.position)) { + if ((!_isDoubleTapped && _canInvokeOnTap) || isLongPressed) { + entry.value.unfocus(); + } + } + } + _tappedPageNumber = -1; + _tappedPagePosition = const Offset(-1, -1); + }); + _isPdfPageTapped = false; if (widget.interactionMode == PdfInteractionMode.pan) { _cursor = SystemMouseCursors.grab; } - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.canvasRenderBox ?.scrollEnded(); + if (_selectedAnnotation != null && + _selectedAnnotation is StickyNoteAnnotation) { + _showStickyNoteDialog(_selectedAnnotation! as StickyNoteAnnotation); + } } void _handleDoubleTap() { _checkMount(); - if (!kIsDesktop || _isMobile) { + if (!kIsDesktop || _isMobileView) { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox @@ -2392,7 +4431,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } void _handleBookmarkViewChanged(bool hasBookmark) { - if (!kIsWeb || (kIsWeb && _isMobile)) { + if (!kIsWeb || (kIsWeb && _isMobileView)) { _checkMount(); } } @@ -2404,6 +4443,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// or device's back button. ALso we can close the bookmark programmatically by /// using Navigator.pop(context); void openBookmarkView() { + _deselectAnnotation(); _bookmarkKey.currentState?.open(); } @@ -2482,126 +4522,35 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { return selectedTextLines ?? []; } - int _findStartOrEndPage(int pageIndex, bool isLastPage) { - double pageSize = 0.0; - for (int start = isLastPage - ? _pdfViewerController.pageCount - : _pdfViewerController.pageNumber; - isLastPage ? start >= 1 : start <= _pdfViewerController.pageCount; - isLastPage ? start-- : start++) { - pageSize += _scrollDirection == PdfScrollDirection.vertical - ? _pdfPages[start]!.pageSize.height - : _pdfPages[start]!.pageSize.width; - if ((!isLastPage && start == _pdfViewerController.pageCount) || - (isLastPage && start == 1)) { - pageIndex = start; - break; - } else { - pageIndex = isLastPage ? start - 1 : start + 1; - } - final bool isPageIndexFound = - _scrollDirection == PdfScrollDirection.vertical - ? pageSize > _viewportConstraints.biggest.height - : pageSize > _viewportConstraints.biggest.width; - if (isPageIndexFound) { - break; - } - } - return pageIndex; - } - - /// Get the rendered pages from plugin. - Future>?>? _getImages() { - if (widget.pageLayoutMode == PdfPageLayoutMode.single) { - Future>?>? renderedPages; - final int startPage = _pdfViewerController.pageNumber - 1 != 0 - ? _pdfViewerController.pageNumber - 1 - : _pdfViewerController.pageNumber; - final int endPage = - _pdfViewerController.pageNumber + 1 < _pdfViewerController.pageCount - ? _pdfViewerController.pageNumber + 1 - : _pdfViewerController.pageCount; - final bool canRenderImage = - !(_singlePageViewKey.currentState?.isScrollHeadDragged ?? true); - renderedPages = _plugin - .getSpecificPages( - startPage, - endPage, - _pdfViewerController.zoomLevel, - _isZoomChanged || _isPageChanged, - _pdfViewerController.pageNumber, - canRenderImage) - .then((Map>? value) { - _isZoomChanged = false; - return value; - }); - if (!_renderedImages.contains(_pdfViewerController.pageNumber)) { - renderedPages.whenComplete(_checkMount); - } - return renderedPages; - } else { - int startPage = (kIsDesktop && _pdfViewerController.pageNumber != 1) - ? _pdfViewerController.pageNumber - 1 - : _pdfViewerController.pageNumber; - int endPage = _pdfViewerController.pageNumber; - Future>?>? renderedPages; - if (_pdfPages.isNotEmpty && !_pdfDimension.isEmpty) { - if (_pdfViewerController.pageCount == 1) { - endPage = _pdfViewerController.pageCount; - } else { - if (startPage == _pdfViewerController.pageCount) { - startPage = _findStartOrEndPage(startPage, true); - endPage = _pdfViewerController.pageCount; - } else { - endPage = _findStartOrEndPage(endPage, false); - if (kIsDesktop && endPage + 1 <= _pdfViewerController.pageCount) { - endPage = endPage + 1; - } - } - } + Future _getTileImage() async { + if (kIsWeb || !Platform.environment.containsKey('FLUTTER_TEST')) { + if (_tileTimer != null && _tileTimer!.isActive) { + _tileTimer?.cancel(); + _tileTimer = null; } - if (_pdfViewerController.zoomLevel >= 2) { - startPage = _endPage = _pdfViewerController.pageNumber; - } - final bool canRenderImage = !(_pdfScrollableStateKey.currentState - ?.scrollHeadStateKey.currentState?.isScrollHeadDragged ?? - true) && - !(widget.scrollDirection == PdfScrollDirection.vertical - ? _pdfScrollableStateKey.currentState?.scrollHeadStateKey - .currentState?.isScrolled ?? - false - : (_pdfScrollableStateKey.currentState?.isScrolled ?? false)); - renderedPages = _plugin - .getSpecificPages( - startPage, - endPage, - _pdfViewerController.zoomLevel, - _isZoomChanged || _isPageChanged, - _pdfViewerController.pageNumber, - canRenderImage || !_isDocumentLoadInitiated) - .then((Map>? value) { - if ((_pdfPages.isNotEmpty && !_pdfDimension.isEmpty) || - _isZoomChanged) { - for (int i = startPage; i <= endPage; i++) { - if (!_renderedImages.contains(i) || _isZoomChanged) { - _isZoomChanged = false; - _checkMount(); - break; - } + _checkVisiblePages(); + _tileTimer ??= Timer(Durations.medium4, () async { + _checkVisiblePages(); + final double zoomLevel = _transformationController.value[0]; + if (_pageLayoutMode == PdfPageLayoutMode.continuous) { + for (final int pageNumber in _renderedImages) { + _pdfPagesKey[pageNumber]?.currentState?.getTileImage( + _transformationController, + _viewportSize, + zoomLevel, + ); } + } else { + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState + ?.getTileImage( + _transformationController, + _viewportSize, + zoomLevel, + ); } - return value; + _checkMount(); + _tileTimer = null; }); - if ((_startPage != startPage && _endPage != endPage) || - (_bufferCount > 0 && _bufferCount <= (endPage - startPage) + 1)) { - renderedPages.whenComplete(_checkMount); - _startPage = startPage; - _endPage = endPage; - _bufferCount++; - } else { - _bufferCount = 0; - } - return renderedPages; } } @@ -2617,15 +4566,18 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_pdfViewerController.pageNumber != _previousPageNumber) { if (widget.onPageChanged != null) { /// Triggering the page changed callback and pass the page changed details - widget.onPageChanged!(PdfPageChangedDetails( - _pdfViewerController.pageNumber, - _previousPageNumber, - _pdfViewerController.pageNumber == 1, - _pdfViewerController.pageNumber == _pdfViewerController.pageCount, - )); + widget.onPageChanged!( + PdfPageChangedDetails( + _pdfViewerController.pageNumber, + _previousPageNumber, + _pdfViewerController.pageNumber == 1, + _pdfViewerController.pageNumber == _pdfViewerController.pageCount, + ), + ); } _previousPageNumber = _pdfViewerController.pageNumber; _isPageChanged = true; + _checkMount(); } } @@ -2634,45 +4586,56 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfViewerController._pdfTextSearchResult.hasResult && _pdfViewerController.pageNumber != (_textCollection![_pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - + ._pdfTextSearchResult + ._currentOccurrenceIndex - 1] .pageIndex + 1)) { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - _getInstanceInPage(_pdfViewerController.pageNumber, - lookForFirst: isNext); + _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( - Offset initialOffset, int pageIndex, double totalHeight) { + Offset initialOffset, + int pageIndex, + double totalHeight, + ) { if (_viewportWidth != _viewportConstraints.maxWidth && _deviceOrientation != MediaQuery.of(context).orientation) { - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _checkMount(); }); if (pageIndex == 1 && !_viewportConstraints.biggest.isEmpty && _pdfScrollableStateKey.currentState != null) { _offsetBeforeOrientationChange = Offset( - _pdfScrollableStateKey.currentState!.currentOffset.dx / - _pdfDimension.width, - _pdfScrollableStateKey.currentState!.currentOffset.dy / - _pdfDimension.height); + _pdfScrollableStateKey.currentState!.currentOffset.dx / + _pdfDimension.width, + _pdfScrollableStateKey.currentState!.currentOffset.dy / + _pdfDimension.height, + ); if (_pdfViewerController.pageCount == 1 && _pdfScrollableStateKey.currentState != null) { if (_viewportWidth != 0) { final double targetOffset = initialOffset.dy * totalHeight; - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox ?.updateContextMenuPosition(); - _pdfScrollableStateKey.currentState?.forcePixels(Offset( + _pdfScrollableStateKey.currentState?.forcePixels( + Offset( initialOffset.dx * _viewportConstraints.biggest.width, - targetOffset)); + targetOffset, + ), + ); }); } _viewportWidth = _viewportConstraints.maxWidth; @@ -2689,23 +4652,29 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } else { targetOffset = initialOffset.dx * totalHeight; } - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox ?.updateContextMenuPosition(); if (_scrollDirection == PdfScrollDirection.vertical && widget.pageLayoutMode != PdfPageLayoutMode.single) { - _pdfScrollableStateKey.currentState?.forcePixels(Offset( + _pdfScrollableStateKey.currentState?.forcePixels( + Offset( initialOffset.dx * _viewportConstraints.biggest.width, - targetOffset)); + targetOffset, + ), + ); } else { - _pdfScrollableStateKey.currentState?.forcePixels(Offset( + _pdfScrollableStateKey.currentState?.forcePixels( + Offset( targetOffset, initialOffset.dy * _pdfPages[_pdfViewerController.pageNumber]! .pageSize - .height)); + .height, + ), + ); } }); } @@ -2718,34 +4687,52 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } void _updateOffsetOnLayoutChange( - double zoomLevel, Offset scrollOffset, PdfPageLayoutMode oldLayoutMode) { + double zoomLevel, + Offset scrollOffset, + PdfPageLayoutMode oldLayoutMode, + ) { + if (_pdfPages.isEmpty) { + return; + } if (oldLayoutMode != widget.pageLayoutMode && oldLayoutMode == PdfPageLayoutMode.single) { _previousSinglePage = _pdfViewerController.pageNumber; final double greyArea = (_singlePageViewKey.currentState?.greyAreaSize ?? 0) / 2; - double heightPercentage = 1.0; - if (kIsDesktop && !_isMobile) { - heightPercentage = - _document!.pages[_pdfViewerController.pageNumber - 1].size.height / - _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height; - } + final double heightPercentage = + (kIsDesktop && !_isMobileView) + ? _document! + .pages[_pdfViewerController.pageNumber - 1] + .size + .height / + _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height + : 1.0; + Offset singleOffset = _singlePageViewKey.currentState?.currentOffset ?? Offset.zero; - singleOffset = Offset(singleOffset.dx * heightPercentage, - (singleOffset.dy - greyArea) * heightPercentage); + singleOffset = Offset( + singleOffset.dx * heightPercentage, + (singleOffset.dy - greyArea) * heightPercentage, + ); _layoutChangeOffset = singleOffset; } else { + _transformationController.viewSize = _viewportConstraints.biggest; + if (_pdfPages[_pdfViewerController.pageNumber] != null) { + _transformationController.contentSize = + _pdfPages[_pdfViewerController.pageNumber]!.pageSize; + } double xPosition = scrollOffset.dx; double yPosition = scrollOffset.dy; if (_pdfViewerController.pageNumber > 1 && - widget.scrollDirection == PdfScrollDirection.vertical) { - yPosition = scrollOffset.dy - + _scrollDirection == PdfScrollDirection.vertical) { + yPosition = + scrollOffset.dy - _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; } if (_pdfViewerController.pageNumber > 1 && - widget.scrollDirection == PdfScrollDirection.horizontal) { - xPosition = scrollOffset.dx - + _scrollDirection == PdfScrollDirection.horizontal) { + xPosition = + scrollOffset.dx - _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; } Future.delayed(Duration.zero, () async { @@ -2753,12 +4740,15 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfViewerController.zoomLevel = 1.0; } _pdfViewerController.zoomLevel = zoomLevel; - double heightPercentage = 1.0; - if (kIsDesktop && !_isMobile) { - heightPercentage = _document! - .pages[_pdfViewerController.pageNumber - 1].size.height / - _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height; - } + final double heightPercentage = + (kIsDesktop && !_isMobileView) + ? _document! + .pages[_pdfViewerController.pageNumber - 1] + .size + .height / + _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height + : 1.0; + if (widget.pageLayoutMode == PdfPageLayoutMode.single && _singlePageViewKey.currentState != null) { final double greyAreaHeight = @@ -2767,14 +4757,20 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height * _pdfViewerController.zoomLevel) { _singlePageViewKey.currentState!.jumpOnZoomedDocument( - _pdfViewerController.pageNumber, - Offset(xPosition / heightPercentage, - yPosition / heightPercentage)); + _pdfViewerController.pageNumber, + Offset( + xPosition / heightPercentage, + yPosition / heightPercentage, + ), + ); } else { _singlePageViewKey.currentState!.jumpOnZoomedDocument( - _pdfViewerController.pageNumber, - Offset(xPosition / heightPercentage, - (yPosition + greyAreaHeight) / heightPercentage)); + _pdfViewerController.pageNumber, + Offset( + xPosition / heightPercentage, + (yPosition + greyAreaHeight) / heightPercentage, + ), + ); } } }); @@ -2784,25 +4780,30 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Whenever scroll direction is changed, PDF page is changed based on viewport /// dimension so offset must be restored to avoid reading continuity loss. void _updateScrollDirectionChange( - Offset initialOffset, int pageIndex, double totalHeight) { + Offset initialOffset, + int pageIndex, + double totalHeight, + ) { if (_scrollDirection != _tempScrollDirection || _pageLayoutMode != widget.pageLayoutMode) { - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { _checkMount(); }); if (pageIndex == 1 && !_viewportConstraints.biggest.isEmpty && _pdfScrollableStateKey.currentState != null) { _offsetBeforeOrientationChange = Offset( - _pdfScrollableStateKey.currentState!.currentOffset.dx / - _pdfDimension.width, - _pdfScrollableStateKey.currentState!.currentOffset.dy / - _pdfDimension.height); + _pdfScrollableStateKey.currentState!.currentOffset.dx / + _pdfDimension.width, + _pdfScrollableStateKey.currentState!.currentOffset.dy / + _pdfDimension.height, + ); } else if (pageIndex == _pdfViewerController.pageCount && _pdfScrollableStateKey.currentState != null) { if (_viewportWidth != 0) { - WidgetsBinding.instance - ?.addPostFrameCallback((Duration timeStamp) async { + WidgetsBinding.instance.addPostFrameCallback(( + Duration timeStamp, + ) async { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox @@ -2823,116 +4824,712 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { widget.pageLayoutMode != PdfPageLayoutMode.single) { final dynamic pageOffset = _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; - final dynamic calculatedOffsetY = pageOffset + + final dynamic calculatedOffsetY = + pageOffset + (initialOffset.dy * _pdfPages[_pdfViewerController.pageNumber]! .pageSize .height); final dynamic calculatedOffsetX = (_pdfScrollableStateKey.currentState!.currentOffset.dx - - _pageOffsetBeforeScrollDirectionChange) * - (_pdfPages[_pdfViewerController.pageNumber]! - .pageSize - .width / - _pageSizeBeforeScrollDirectionChange.width); - - _scrollDirectionSwitchOffset = - Offset(calculatedOffsetX, calculatedOffsetY); + _pageOffsetBeforeScrollDirectionChange) * + (_pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .width / + _pageSizeBeforeScrollDirectionChange.width); + + _scrollDirectionSwitchOffset = Offset( + calculatedOffsetX, + calculatedOffsetY, + ); } else { final dynamic pageOffset = _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; - final dynamic calculatedOffsetX = pageOffset + + final dynamic calculatedOffsetX = + pageOffset + (initialOffset.dx * _pdfPages[_pdfViewerController.pageNumber]! .pageSize .width); final dynamic calculatedOffsetY = (_pdfScrollableStateKey.currentState!.currentOffset.dy - - _pageOffsetBeforeScrollDirectionChange) / - (_pageSizeBeforeScrollDirectionChange.height / - _pdfPages[_pdfViewerController.pageNumber]! - .pageSize - .height); - - _scrollDirectionSwitchOffset = - Offset(calculatedOffsetX, calculatedOffsetY); + _pageOffsetBeforeScrollDirectionChange) / + (_pageSizeBeforeScrollDirectionChange.height / + _pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .height); + + _scrollDirectionSwitchOffset = Offset( + calculatedOffsetX, + calculatedOffsetY, + ); } } _isScrollDirectionChange = true && _layoutChangeOffset == Offset.zero; }); } + if ((_isMobileWebView ?? false) && + _tempScrollDirection != _scrollDirection) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + Future.delayed(Durations.long1, _checkMount); + }); + } + _tempScrollDirection = _scrollDirection; + _pageLayoutMode = widget.pageLayoutMode; + } else if (widget.pageLayoutMode == PdfPageLayoutMode.single && + pageIndex == _pdfViewerController.pageCount) { _tempScrollDirection = _scrollDirection; _pageLayoutMode = widget.pageLayoutMode; } - } else if (widget.pageLayoutMode == PdfPageLayoutMode.continuous || - widget.pageLayoutMode != PdfPageLayoutMode.single) { - _pageOffsetBeforeScrollDirectionChange = - _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; - _pageSizeBeforeScrollDirectionChange = - _pdfPages[_pdfViewerController.pageNumber]!.pageSize; + } else if (widget.pageLayoutMode == PdfPageLayoutMode.continuous || + widget.pageLayoutMode != PdfPageLayoutMode.single) { + _pageOffsetBeforeScrollDirectionChange = + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + _pageSizeBeforeScrollDirectionChange = + _pdfPages[_pdfViewerController.pageNumber]!.pageSize; + } + } + + /// Calculates a size of PDF page image within the given constraints. + Size _calculateSize( + BoxConstraints constraints, + double originalWidth, + double originalHeight, + double newWidth, + double newHeight, + ) { + if (_viewportConstraints.maxWidth > newHeight && + !kIsDesktop && + _scrollDirection == PdfScrollDirection.horizontal && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + constraints = BoxConstraints.tightFor( + height: _viewportHeightInLandscape ?? newHeight, + ).enforce(constraints); + } else { + if (widget.pageLayoutMode == PdfPageLayoutMode.single && + (!_isMobileView || _viewportConstraints.maxWidth > newHeight)) { + constraints = BoxConstraints.tightFor( + height: newHeight, + ).enforce(constraints); + } else { + constraints = BoxConstraints.tightFor( + width: newWidth, + ).enforce(constraints); + } + } + // Maintained the aspect ratio while image is resized + // based on original page's width and height. + Size newSize = constraints.constrainSizeAndAttemptToPreserveAspectRatio( + Size(originalWidth, originalHeight), + ); + if ((widget.pageLayoutMode == PdfPageLayoutMode.single || + _scrollDirection == PdfScrollDirection.horizontal && + Orientation.portrait == MediaQuery.of(context).orientation) && + newSize.height > newHeight) { + BoxConstraints newConstraints = BoxConstraints( + maxWidth: _viewportConstraints.maxWidth, + maxHeight: newHeight, + ); + newConstraints = BoxConstraints.tightFor( + height: newHeight, + ).enforce(newConstraints); + newSize = newConstraints.constrainSizeAndAttemptToPreserveAspectRatio( + Size(originalWidth, originalHeight), + ); + } + + return newSize; + } + + /// Updates current page number when scrolling occurs. + void _updateCurrentPageNumber({double currentOffset = 0}) { + if (currentOffset > 0) { + _pdfViewerController._pageNumber = + _pdfScrollableStateKey.currentState?.getPageNumber(currentOffset) ?? + 0; + } else { + if (_textDirection == TextDirection.rtl && + _scrollDirection == PdfScrollDirection.horizontal) { + _pdfViewerController.pageCount > 0 + ? _pdfViewerController._pageNumber = _pdfViewerController.pageCount + : _pdfViewerController._pageNumber = 0; + } else { + _pdfViewerController.pageCount > 0 + ? _pdfViewerController._pageNumber = 1 + : _pdfViewerController._pageNumber = 0; + } + } + _pageChanged(); + } + + void _handleTextSelectionChanged(PdfTextSelectionChangedDetails details) { + if (_selectedAnnotation != null) { + _pdfViewerController.deselectAnnotation(_selectedAnnotation!); + } + // Trigger the text selection changed callback only if the annotation mode is none. + if (widget.onTextSelectionChanged != null && + _pdfViewerController.annotationMode == PdfAnnotationMode.none) { + widget.onTextSelectionChanged!(details); + } + + if (_pdfViewerController.annotationMode == PdfAnnotationMode.none) { + if (details.globalSelectedRegion != null) { + if (_textSelectionRegion != details.globalSelectedRegion!) { + _textSelectionRegion = details.globalSelectedRegion!; + if (details.selectedText != null && + details.selectedText!.isNotEmpty) { + _checkPositionOfTextSelectionMenu(); + } + } + } else { + _textSelectionRegion = Rect.zero; + _hideTextSelectionMenu(); + } + } + } + + /// Checks whether the text selection region is visible in the viewport + /// and displays the text selection menu. + void _checkPositionOfTextSelectionMenu() { + if (widget.canShowTextSelectionMenu) { + _isTextSelectionVisibleInViewport = _getViewportGlobalRect()!.overlaps( + _textSelectionRegion, + ); + final List seletedLines = getSelectedTextLines(); + if (seletedLines.isNotEmpty) { + _selectedTextPageNumber = seletedLines.first.pageNumber; + } + if (_isTextSelectionVisibleInViewport) { + final TextSelectionMenuLocation location = + _determineContextMenuLocation(); + _calculateContextMenuBounds(location); + _showTextSelectionMenu(); + } else { + _hideTextSelectionMenu(); + } + } + } + + /// Return the location of text selection menu based on the text selection region. + TextSelectionMenuLocation _determineContextMenuLocation() { + final double distanceFromTop = + _textSelectionRegion.top - _viewportGlobalRect!.top; + final double distanceFromBottom = + _viewportGlobalRect!.bottom - _textSelectionRegion.bottom; + final double distanceFromLeft = + _textSelectionRegion.left - _viewportGlobalRect!.left; + final double distanceFromRight = + _viewportGlobalRect!.right - _textSelectionRegion.right; + final double largestDistance = max( + max(distanceFromTop, distanceFromLeft), + max(distanceFromBottom, distanceFromRight), + ); + + if (largestDistance == distanceFromTop && + ((kTextSelectionMenuHeight + kTextSelectionMenuMargin) / + _pdfViewerController.zoomLevel) <= + distanceFromTop) { + return TextSelectionMenuLocation.top; + } else if (largestDistance == distanceFromLeft && + ((kTextSelectionMenuWidth + kTextSelectionMenuMargin) / + _pdfViewerController.zoomLevel) <= + distanceFromLeft) { + return TextSelectionMenuLocation.left; + } else if (largestDistance == distanceFromBottom && + ((kTextSelectionMenuHeight + kTextSelectionMenuMargin) / + _pdfViewerController.zoomLevel) <= + distanceFromBottom) { + return TextSelectionMenuLocation.bottom; + } else if (largestDistance == distanceFromRight && + ((kTextSelectionMenuWidth + kTextSelectionMenuMargin) / + _pdfViewerController.zoomLevel) <= + distanceFromRight) { + return TextSelectionMenuLocation.right; + } else { + return TextSelectionMenuLocation.center; + } + } + + /// Calculate the bounds of text selection menu based on the location. + void _calculateContextMenuBounds(TextSelectionMenuLocation location) { + Offset localPosition = Offset.zero; + switch (location) { + case TextSelectionMenuLocation.left: + localPosition = Offset( + _textSelectionRegion.left - + kTextSelectionMenuWidth - + kTextSelectionMenuMargin, + _textSelectionRegion.centerLeft.dy - kTextSelectionMenuHeight / 2, + ); + break; + case TextSelectionMenuLocation.right: + localPosition = Offset( + _textSelectionRegion.right + kTextSelectionMenuMargin, + _textSelectionRegion.centerRight.dy - kTextSelectionMenuHeight / 2, + ); + break; + case TextSelectionMenuLocation.top: + localPosition = Offset( + _textSelectionRegion.topCenter.dx - kTextSelectionMenuWidth / 2, + _textSelectionRegion.top - + kTextSelectionMenuHeight - + kTextSelectionMenuMargin, + ); + break; + case TextSelectionMenuLocation.bottom: + localPosition = Offset( + _textSelectionRegion.bottomCenter.dx - kTextSelectionMenuWidth / 2, + _textSelectionRegion.bottom + kTextSelectionMenuMargin, + ); + break; + case TextSelectionMenuLocation.center: + localPosition = Offset( + _textSelectionRegion.center.dx - kTextSelectionMenuWidth / 2, + _textSelectionRegion.center.dy - kTextSelectionMenuHeight / 2, + ); + break; + } + if (localPosition != Offset.zero) { + if (localPosition.dy < + _viewportGlobalRect!.top + kTextSelectionMenuMargin) { + localPosition = Offset( + localPosition.dx, + _viewportGlobalRect!.top + kTextSelectionMenuMargin, + ); + } else if (localPosition.dy + kTextSelectionMenuHeight > + _viewportGlobalRect!.bottom - kTextSelectionMenuMargin) { + localPosition = Offset( + localPosition.dx, + _viewportGlobalRect!.bottom - + kTextSelectionMenuMargin - + kTextSelectionMenuHeight, + ); + } + + if (localPosition.dx < + _viewportGlobalRect!.left + kTextSelectionMenuMargin) { + localPosition = Offset( + _viewportGlobalRect!.left + kTextSelectionMenuMargin, + localPosition.dy, + ); + } else if (localPosition.dx + kTextSelectionMenuWidth > + _viewportGlobalRect!.right - kTextSelectionMenuMargin) { + localPosition = Offset( + _viewportGlobalRect!.right - + kTextSelectionMenuMargin - + kTextSelectionMenuWidth, + localPosition.dy, + ); + } + + _contextMenuPosition = localPosition; + } + } + + /// Inserts the text selection menu into the overlay. + void _showTextSelectionMenu() { + _hideTextSelectionMenu(); + _textSelectionOverlayEntry ??= OverlayEntry( + maintainState: true, + builder: (BuildContext context) { + return Positioned( + top: _contextMenuPosition.dy, + left: _contextMenuPosition.dx, + child: TextSelectionMenu( + themeData: _themeData, + localizations: _localizations, + onSelected: (String value) { + if (value != 'Copy') { + final PdfAnnotationMode annotationMode = + value == 'Highlight' + ? PdfAnnotationMode.highlight + : value == 'Underline' + ? PdfAnnotationMode.underline + : value == 'Strikethrough' + ? PdfAnnotationMode.strikethrough + : PdfAnnotationMode.squiggly; + _addTextMarkupAnnotation(annotationMode); + } else if (value == 'Copy') { + Clipboard.setData( + ClipboardData( + text: + _pdfPagesKey[_selectedTextPageNumber] + ?.currentState + ?.canvasRenderBox! + .getSelectionDetails() + .copiedText ?? + '', + ), + ); + _pdfViewerController.clearSelection(); + } + }, + textDirection: _textDirection, + ), + ); + }, + ); + + Overlay.of(context, rootOverlay: true).insert(_textSelectionOverlayEntry!); + } + + /// Removes the text selection menu from the overlay. + void _hideTextSelectionMenu() { + if (_textSelectionOverlayEntry != null) { + _textSelectionOverlayEntry!.remove(); + _textSelectionOverlayEntry!.dispose(); + _textSelectionOverlayEntry = null; + } + } + + /// Return the location of sticky note dialog based on the sticky note annotation position. + Offset _determineOverlayLocation( + Rect viewportRect, + Rect overlayRect, + Size overlaySize, { + double margin = 0, + }) { + final double distanceFromTop = overlayRect.top - viewportRect.top; + final double distanceFromBottom = viewportRect.bottom - overlayRect.bottom; + final double distanceFromLeft = overlayRect.left - viewportRect.left; + final double distanceFromRight = viewportRect.right - overlayRect.right; + final double largestDistance = max( + max(distanceFromTop, distanceFromLeft), + max(distanceFromBottom, distanceFromRight), + ); + + TextSelectionMenuLocation location = TextSelectionMenuLocation.center; + + if (largestDistance == distanceFromTop && + ((overlaySize.height + margin) / _pdfViewerController.zoomLevel) <= + distanceFromTop) { + location = TextSelectionMenuLocation.top; + } else if (largestDistance == distanceFromLeft && + ((overlaySize.width + margin) / _pdfViewerController.zoomLevel) <= + distanceFromLeft) { + location = TextSelectionMenuLocation.left; + } else if (largestDistance == distanceFromBottom && + ((overlaySize.height + margin) / _pdfViewerController.zoomLevel) <= + distanceFromBottom) { + location = TextSelectionMenuLocation.bottom; + } else if (largestDistance == distanceFromRight && + ((overlaySize.width + margin) / _pdfViewerController.zoomLevel) <= + distanceFromRight) { + location = TextSelectionMenuLocation.right; + } else { + location = TextSelectionMenuLocation.center; + } + + Offset localPosition = Offset.zero; + + switch (location) { + case TextSelectionMenuLocation.left: + localPosition = Offset( + overlayRect.left - overlaySize.width - margin, + overlayRect.centerLeft.dy - overlaySize.height / 2, + ); + break; + case TextSelectionMenuLocation.right: + localPosition = Offset( + overlayRect.right + margin, + overlayRect.centerRight.dy - overlaySize.height / 2, + ); + break; + case TextSelectionMenuLocation.top: + localPosition = Offset( + overlayRect.topCenter.dx - overlaySize.width / 2, + overlayRect.top - overlaySize.height - margin, + ); + break; + case TextSelectionMenuLocation.bottom: + localPosition = Offset( + overlayRect.bottomCenter.dx - overlaySize.width / 2, + overlayRect.bottom + margin, + ); + break; + case TextSelectionMenuLocation.center: + localPosition = Offset( + overlayRect.center.dx - overlaySize.width / 2, + overlayRect.center.dy - overlaySize.height / 2, + ); + break; + } + + if (localPosition != Offset.zero) { + if (localPosition.dy < viewportRect.top + margin) { + localPosition = Offset(localPosition.dx, viewportRect.top + margin); + } else if (localPosition.dy + overlaySize.height > + viewportRect.bottom - margin) { + localPosition = Offset( + localPosition.dx, + viewportRect.bottom - margin - overlaySize.height, + ); + } + + if (localPosition.dx < viewportRect.left + margin) { + localPosition = Offset(viewportRect.left + margin, localPosition.dy); + } else if (localPosition.dx + overlaySize.width > + viewportRect.right - margin) { + localPosition = Offset( + viewportRect.right - margin - overlaySize.width, + localPosition.dy, + ); + } } + return localPosition; } - /// Calculates a size of PDF page image within the given constraints. - Size _calculateSize(BoxConstraints constraints, double originalWidth, - double originalHeight, double newWidth, double newHeight) { - if (_viewportConstraints.maxWidth > newHeight && - !kIsDesktop && - _scrollDirection == PdfScrollDirection.horizontal && - widget.pageLayoutMode != PdfPageLayoutMode.single) { - constraints = BoxConstraints.tightFor( - width: null, - height: _viewportHeightInLandscape, - ).enforce(constraints); + void _onStickyNoteAnnotationDoubleTapped(StickyNoteAnnotation stickyNote) { + if (kIsDesktop && !_isMobileView) { + _showStickyNoteDialog(stickyNote); } else { - if (widget.pageLayoutMode == PdfPageLayoutMode.single && - (!_isMobile || _viewportConstraints.maxWidth > newHeight)) { - constraints = BoxConstraints.tightFor( - width: null, - height: newHeight, - ).enforce(constraints); - } else { - constraints = BoxConstraints.tightFor( - width: newWidth, - height: null, - ).enforce(constraints); + _showStickyNoteDialogMobile(null, stickyNote, stickyNote.pageNumber); + } + } + + void _updateStickyNoteDialog() { + if (_selectedAnnotation != null && + _selectedAnnotation is StickyNoteAnnotation) { + _hideStickyNoteDialog(); + Future.delayed(Durations.medium4, () async { + _showStickyNoteDialog(_selectedAnnotation! as StickyNoteAnnotation); + }); + } + } + + void _showStickyNoteDialog( + StickyNoteAnnotation stickyNote, { + bool isNew = false, + }) { + if (_stickyNoteEditTextOverlyEntry != null) { + _stickyNoteEditTextOverlyEntry?.remove(); + _stickyNoteEditTextOverlyEntry = null; + } + if (!kIsDesktop || _isMobileView) { + return; + } + + if (_viewportGlobalRect != null && + _viewportGlobalRect!.overlaps(stickyNote.globalRect) && + (widget.pageLayoutMode == PdfPageLayoutMode.continuous || + widget.pageLayoutMode == PdfPageLayoutMode.single && + _pdfViewerController.pageNumber == stickyNote.pageNumber)) { + final Offset editTextdialogPosition = _determineOverlayLocation( + _viewportGlobalRect!, + stickyNote.globalRect, + const Size( + kStickyNoteEditTextDialogWidth, + kStickyNoteEditTextDialogHeight, + ), + margin: 10, + ); + _stickyNoteEditTextOverlyEntry ??= OverlayEntry( + maintainState: true, + builder: (BuildContext context) { + return Positioned( + left: editTextdialogPosition.dx, + top: editTextdialogPosition.dy, + width: kStickyNoteEditTextDialogWidth, + height: kStickyNoteEditTextDialogHeight, + child: StickyNoteEditText( + stickyNote: stickyNote, + onClose: _deselectAnnotation, + isNewAnnotation: isNew, + backgroundColor: stickyNote.color.getLightenColor(0.85), + changeTracker: _changeTracker, + ), + ); + }, + ); + + Overlay.of( + context, + rootOverlay: true, + ).insert(_stickyNoteEditTextOverlyEntry!); + } + } + + void _hideStickyNoteDialog() { + if (_stickyNoteEditTextOverlyEntry != null) { + _stickyNoteEditTextOverlyEntry?.remove(); + _stickyNoteEditTextOverlyEntry?.dispose(); + _stickyNoteEditTextOverlyEntry = null; + } + } + + void _addSticyNoteAnnotation(Offset position, String text, int pageNumber) { + if (pageNumber <= 0 || pageNumber > _document!.pages.count) { + return; + } + final List annotations = + _pdfViewerController + .getAnnotations() + .where( + (Annotation annotation) => annotation.pageNumber == pageNumber, + ) + .toList(); + + for (final Annotation annotation in annotations) { + if (annotation is StickyNoteAnnotation) { + final Rect scaledBounds = + annotation.boundingBox.topLeft & + (annotation.boundingBox.size / _pdfViewerController.zoomLevel); + if (scaledBounds.contains(position)) { + return; + } } } - // Maintained the aspect ratio while image is resized - // based on original page's width and height. - Size newSize = constraints.constrainSizeAndAttemptToPreserveAspectRatio( - Size(originalWidth, originalHeight)); - if ((widget.pageLayoutMode == PdfPageLayoutMode.single || - widget.scrollDirection == PdfScrollDirection.horizontal && - Orientation.portrait == MediaQuery.of(context).orientation) && - newSize.height > newHeight) { - BoxConstraints newConstraints = BoxConstraints( - maxWidth: _viewportConstraints.maxWidth, maxHeight: newHeight); - newConstraints = BoxConstraints.tightFor( - width: null, - height: newHeight, - ).enforce(newConstraints); - newSize = newConstraints.constrainSizeAndAttemptToPreserveAspectRatio( - Size(originalWidth, originalHeight)); + final PdfPageRotateAngle pageRotation = + _document!.pages[pageNumber - 1].rotation; + int rotation = 0; + if (pageRotation == PdfPageRotateAngle.rotateAngle90) { + rotation = 90; + } else if (pageRotation == PdfPageRotateAngle.rotateAngle180) { + rotation = 180; + } else if (pageRotation == PdfPageRotateAngle.rotateAngle270) { + rotation = 270; } + final StickyNoteAnnotation annotation = StickyNoteAnnotation( + pageNumber: pageNumber, + text: text, + position: position, + icon: _pdfViewerController.annotationSettings.stickyNote.icon, + ); + annotation.pageRotation = rotation; + _pdfViewerController.addAnnotation(annotation); + _pdfViewerController.annotationMode = PdfAnnotationMode.none; - return newSize; + setState(() { + _selectedAnnotation = annotation; + }); + widget.onAnnotationSelected?.call(annotation); + annotation.isSelected = true; + if (kIsDesktop && !_isMobileView) { + Future.delayed(Durations.medium4, () { + _showStickyNoteDialog(annotation, isNew: true); + }); + } } - /// Updates current page number when scrolling occurs. - void _updateCurrentPageNumber({double currentOffset = 0}) { - if (currentOffset > 0) { - _pdfViewerController._pageNumber = - _pdfScrollableStateKey.currentState?.getPageNumber(currentOffset) ?? - 0; + void _showStickyNoteDialogMobile( + Offset? position, + Annotation? annotation, + int pageNumber, + ) { + bool canEdit = true; + if (annotation != null && annotation is StickyNoteAnnotation) { + _stickyNoteTextController.text = annotation.text; + canEdit = _pdfViewerController.annotationSettings.canEdit(annotation); } else { - _pdfViewerController.pageCount > 0 - ? _pdfViewerController._pageNumber = 1 - : _pdfViewerController._pageNumber = 0; + _stickyNoteTextController.clear(); } - _pageChanged(); - _checkMount(); + + final Size screenSize = MediaQuery.sizeOf(context); + + double editTextDialogWidth = 0; + double editTextDialogHeight = 0; + + if (screenSize.width > 600 && screenSize.width < 1280) { + editTextDialogWidth = screenSize.width * 0.7; + editTextDialogHeight = screenSize.height * 0.7; + } else if (screenSize.width >= 1280) { + editTextDialogWidth = screenSize.width * 0.5; + editTextDialogHeight = screenSize.height * 0.5; + } else if (screenSize.width <= 600) { + editTextDialogWidth = screenSize.width; + editTextDialogHeight = screenSize.height; + } + + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Dialog( + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ), + backgroundColor: + annotation != null + ? annotation.color.getLightenColor(0.85) + : _pdfViewerController.annotationSettings.stickyNote.color + .getLightenColor(0.85), + child: Theme( + data: ThemeData.light(), + child: SizedBox( + height: editTextDialogHeight, + width: editTextDialogWidth, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon(Icons.close), + ), + Visibility( + visible: canEdit, + child: TextButton( + onPressed: () { + if (annotation != null && + annotation is StickyNoteAnnotation) { + annotation.text = + _stickyNoteTextController.text; + } else if (position != null) { + _addSticyNoteAnnotation( + position, + _stickyNoteTextController.text, + pageNumber, + ); + } + Navigator.pop(context); + }, + child: + annotation != null + ? const Text('SAVE') + : const Text('INSERT'), + ), + ), + ], + ), + Expanded( + child: TextField( + controller: _stickyNoteTextController, + focusNode: _stickyNoteFocusNode, + readOnly: !canEdit, + expands: true, + decoration: const InputDecoration( + hintText: 'Enter your text here', + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: EdgeInsets.all(10), + ), + maxLines: null, + keyboardType: TextInputType.multiline, + onChanged: (String value) { + setState(() {}); + }, + ), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ).timeout( + const Duration(milliseconds: 500), + onTimeout: () { + _stickyNoteFocusNode.requestFocus(); + }, + ); } /// Triggers when text selection dragging started. @@ -2947,6 +5544,37 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { setState(() { _panEnabled = true; }); + + if (_pdfViewerController.annotationMode == PdfAnnotationMode.highlight || + _pdfViewerController.annotationMode == + PdfAnnotationMode.strikethrough || + _pdfViewerController.annotationMode == PdfAnnotationMode.underline || + _pdfViewerController.annotationMode == PdfAnnotationMode.squiggly) { + _addTextMarkupAnnotation(_pdfViewerController.annotationMode); + } + } + + /// Adds the text markup annotation to the PDF document. + void _addTextMarkupAnnotation(PdfAnnotationMode annotationMode) { + final List selectedLines = getSelectedTextLines(); + if (selectedLines.isNotEmpty) { + Annotation? annotation; + if (annotationMode == PdfAnnotationMode.highlight) { + annotation = HighlightAnnotation(textBoundsCollection: selectedLines); + } else if (annotationMode == PdfAnnotationMode.underline) { + annotation = UnderlineAnnotation(textBoundsCollection: selectedLines); + } else if (annotationMode == PdfAnnotationMode.strikethrough) { + annotation = StrikethroughAnnotation( + textBoundsCollection: selectedLines, + ); + } else if (annotationMode == PdfAnnotationMode.squiggly) { + annotation = SquigglyAnnotation(textBoundsCollection: selectedLines); + } + _pdfViewerController.clearSelection(); + if (annotation != null) { + _pdfViewerController.addAnnotation(annotation); + } + } } /// Jump to the desired page. @@ -2956,12 +5584,16 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pageController.jumpToPage(pageNumber - 1); } } else if (_scrollDirection == PdfScrollDirection.horizontal) { - _pdfScrollableStateKey.currentState - ?.jumpTo(xOffset: _pdfPages[pageNumber]!.pageOffset); + _pdfScrollableStateKey.currentState?.jumpTo( + xOffset: _pdfPages[pageNumber]!.pageOffset, + yOffset: 0, + ); } else { - _pdfScrollableStateKey.currentState - ?.jumpTo(yOffset: _pdfPages[pageNumber]!.pageOffset); + _pdfScrollableStateKey.currentState?.jumpTo( + yOffset: _pdfPages[pageNumber]!.pageOffset, + ); } + _getTileImage(); } /// Jump to the bookmark location. @@ -2982,19 +5614,21 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { double yOffset = _pdfPages[index]!.pageOffset; final bool isRotatedTo90or270 = pdfPage.rotation == PdfPageRotateAngle.rotateAngle90 || - pdfPage.rotation == PdfPageRotateAngle.rotateAngle270; + pdfPage.rotation == PdfPageRotateAngle.rotateAngle270; if (bookmark.namedDestination != null) { - heightPercentage = bookmark - .namedDestination!.destination!.page.size.height / + heightPercentage = + bookmark.namedDestination!.destination!.page.size.height / (isRotatedTo90or270 ? revealedOffset.width : revealedOffset.height); - widthPercentage = bookmark - .namedDestination!.destination!.page.size.width / + widthPercentage = + bookmark.namedDestination!.destination!.page.size.width / (isRotatedTo90or270 ? revealedOffset.height : revealedOffset.width); bookmarkOffset = bookmark.namedDestination!.destination!.location; } else { - heightPercentage = bookmark.destination!.page.size.height / + heightPercentage = + bookmark.destination!.page.size.height / (isRotatedTo90or270 ? revealedOffset.width : revealedOffset.height); - widthPercentage = bookmark.destination!.page.size.width / + widthPercentage = + bookmark.destination!.page.size.width / (isRotatedTo90or270 ? revealedOffset.height : revealedOffset.width); bookmarkOffset = bookmark.destination!.location; } @@ -3009,9 +5643,11 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .getRotatedOffset(bookmarkOffset, index - 1, pdfPage.rotation); } if (kIsDesktop && - !_isMobile && + !_isMobileView && widget.pageLayoutMode == PdfPageLayoutMode.continuous) { - heightPercentage = 1.0; + heightPercentage = + _document!.pages[_pdfViewerController.pageNumber - 1].size.height / + _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height; } yOffset = yOffset + (bookmarkOffset.dy / heightPercentage); double xOffset = bookmarkOffset.dx / widthPercentage; @@ -3020,7 +5656,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { xOffset = _pdfPages[index]!.pageOffset; yOffset = bookmarkOffset.dy / heightPercentage; } else { - xOffset = _pdfPages[index]!.pageOffset + + xOffset = + _pdfPages[index]!.pageOffset + bookmarkOffset.dx / widthPercentage; yOffset = bookmarkOffset.dy / heightPercentage; } @@ -3031,12 +5668,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (widget.pageLayoutMode == PdfPageLayoutMode.single) { xOffset = bookmarkOffset.dx / widthPercentage; yOffset = bookmarkOffset.dy / heightPercentage; - _singlePageViewKey.currentState! - .jumpOnZoomedDocument(index, Offset(xOffset, yOffset)); + _singlePageViewKey.currentState!.jumpOnZoomedDocument( + index, + Offset(xOffset, yOffset), + ); } else { - _pdfScrollableStateKey.currentState - ?.jumpTo(xOffset: xOffset, yOffset: yOffset); + _pdfScrollableStateKey.currentState?.jumpTo( + xOffset: xOffset, + yOffset: yOffset, + ); } + _getTileImage(); } } @@ -3068,9 +5710,28 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_pdfPages.isNotEmpty) { _jumpToBookmark(_pdfViewerController._pdfBookmark); } + } else if (property == 'exportFormData') { + if (_document != null) { + _pdfViewerController._exportedFormDataBytes = _document!.form + .exportData(_pdfViewerController._exportDataFormat); + setState(() {}); + } + } else if (property == 'importFormData') { + if (_document != null) { + _document!.form.importData( + _pdfViewerController._importedFormDataBytes, + _pdfViewerController._importDataFormat, + _pdfViewerController._continueImportOnError, + ); + setState(() { + _importFormFieldData(); + }); + } + } else if (property == 'saveDocument') { + _pdfViewerController._savedDocumentBytes = _saveDocument(); } else if (property == 'zoomLevel') { - if (_pdfViewerController.zoomLevel > _maxScale) { - _pdfViewerController.zoomLevel = _maxScale; + if (_pdfViewerController.zoomLevel > widget.maxZoomLevel) { + _pdfViewerController.zoomLevel = widget.maxZoomLevel; } else if (_pdfViewerController.zoomLevel < _minScale) { _pdfViewerController.zoomLevel = _minScale; } @@ -3085,7 +5746,6 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (newZoomLevel != _previousTiledZoomLevel && (newZoomLevel - _previousTiledZoomLevel).abs() >= 0.25) { setState(() { - _isZoomChanged = true; _previousTiledZoomLevel = newZoomLevel; }); } @@ -3093,12 +5753,16 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { previousScale != _pdfViewerController._zoomLevel) { if (newZoomLevel != oldZoomLevel) { widget.onZoomLevelChanged!( - PdfZoomDetails(newZoomLevel, oldZoomLevel)); + PdfZoomDetails(newZoomLevel, oldZoomLevel), + ); } } - PageStorage.of(context)?.writeState( - context, _pdfViewerController.zoomLevel, - identifier: 'zoomLevel_${widget.key}'); + PageStorage.of(context).writeState( + context, + _pdfViewerController.zoomLevel, + identifier: 'zoomLevel_${widget.key}', + ); + _getTileImage(); } } else { if (_singlePageViewKey.currentState != null) { @@ -3112,7 +5776,6 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (newZoomLevel != _previousTiledZoomLevel && (newZoomLevel - _previousTiledZoomLevel).abs() >= 0.25) { setState(() { - _isZoomChanged = true; _previousTiledZoomLevel = newZoomLevel; }); } @@ -3120,13 +5783,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { previousScale != _pdfViewerController._zoomLevel) { if (newZoomLevel != oldZoomLevel) { widget.onZoomLevelChanged!( - PdfZoomDetails(newZoomLevel, oldZoomLevel)); + PdfZoomDetails(newZoomLevel, oldZoomLevel), + ); } } } - PageStorage.of(context)?.writeState( - context, _pdfViewerController.zoomLevel, - identifier: 'zoomLevel_${widget.key}'); + PageStorage.of(context).writeState( + context, + _pdfViewerController.zoomLevel, + identifier: 'zoomLevel_${widget.key}', + ); + _getTileImage(); } } } else if (property == 'clearTextSelection') { @@ -3140,85 +5807,560 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _jumpToPage(_getPageIndex(_pdfViewerController._horizontalOffset)); _previousHorizontalOffset = _pdfViewerController._horizontalOffset; } - if (_singlePageViewKey.currentState != null) { - _singlePageViewKey.currentState! - .jumpTo(yOffset: _pdfViewerController._verticalOffset); + if (_singlePageViewKey.currentState != null) { + _singlePageViewKey.currentState!.jumpTo( + yOffset: _pdfViewerController._verticalOffset, + ); + } + } else if (!_pdfDimension.isEmpty) { + _pdfScrollableStateKey.currentState?.jumpTo( + xOffset: + (_textDirection == TextDirection.rtl && + _scrollDirection == PdfScrollDirection.horizontal) + ? (_maxScrollExtent - _pdfViewerController._horizontalOffset) + : _pdfViewerController._horizontalOffset, + yOffset: _pdfViewerController._verticalOffset, + ); + } + _getTileImage(); + } else if (property == 'pageNavigate' && + _pdfViewerController._pageNavigator != null) { + _clearSelection(); + switch (_pdfViewerController._pageNavigator!.option) { + case Navigation.jumpToPage: + if (_pdfViewerController._pageNavigator!.index! > 0 && + _pdfViewerController._pageNavigator!.index! <= + _pdfViewerController.pageCount) { + _jumpToPage(_pdfViewerController._pageNavigator!.index!); + } + break; + case Navigation.firstPage: + _jumpToPage(1); + break; + case Navigation.lastPage: + if (_pdfViewerController.pageNumber != + _pdfViewerController.pageCount) { + _jumpToPage(_pdfViewerController.pageCount); + } + break; + case Navigation.previousPage: + if (_pdfViewerController.pageNumber > 1) { + _jumpToPage(_pdfViewerController.pageNumber - 1); + } + break; + case Navigation.nextPage: + if (_pdfViewerController.pageNumber != + _pdfViewerController.pageCount) { + _jumpToPage(_pdfViewerController.pageNumber + 1); + } + break; + } + } else if (property == 'searchText') { + _pdfViewerController.clearSelection(); + _deselectAnnotation(); + _isSearchStarted = true; + _matchedTextPageIndices.clear(); + _pdfViewerController._pdfTextSearchResult._removeListener( + _handleTextSearch, + ); + if (kIsWeb) { + _textCollection = _pdfTextExtractor?.findText([ + _pdfViewerController._searchText, + ], searchOption: _pdfViewerController._textSearchOption); + if (_textCollection!.isEmpty) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + } else { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _getInstanceInPage(_pdfViewerController.pageNumber); + if (_pdfPages.isNotEmpty) { + _jumpToSearchInstance(); + } + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = + _textCollection!.length; + _pdfViewerController._pdfTextSearchResult._updateResult(true); + } + _pdfViewerController._pdfTextSearchResult._addListener( + _handleTextSearch, + ); + setState(() {}); + } else { + if (_isTextExtractionCompleted) { + final String searchText = + _pdfViewerController._searchText.toLowerCase(); + _extractedTextCollection.forEach((int key, String value) { + if (value.contains(searchText)) { + _matchedTextPageIndices.add(key); + } + }); + _performTextSearch(); + } + } + } else if (property == 'clearFormData') { + final List formFieldValueChangeRecords = + []; + for (final PdfFormField field in _pdfViewerController._formFields) { + if (!field.readOnly) { + final PdfFormFieldHelper formFieldHelper = + PdfFormFieldHelper.getHelper(field); + final int formFieldPageNumber = formFieldHelper.pageIndex + 1; + if (_pdfViewerController._clearFormDataPageNumber == 0 || + formFieldPageNumber == + _pdfViewerController._clearFormDataPageNumber) { + FormFieldValueChangeRecord? record; + if (formFieldHelper is PdfTextFormFieldHelper && + field is PdfTextFormField) { + record = _updateFormField(field, ''); + } else if (formFieldHelper is PdfCheckboxFormFieldHelper && + field is PdfCheckboxFormField) { + record = _updateFormField(field, false); + } else if (formFieldHelper is PdfRadioFormFieldHelper && + field is PdfRadioFormField) { + if (formFieldHelper.canReset) { + record = _updateFormField(field, ''); + } else { + record = _updateFormField(field, field.items[0]); + } + } else if (formFieldHelper is PdfComboBoxFormFieldHelper && + field is PdfComboBoxFormField) { + record = _updateFormField(field, field.items[0]); + } else if (formFieldHelper is PdfListBoxFormFieldHelper && + field is PdfListBoxFormField) { + record = _updateFormField(field, List.empty()); + } else if (formFieldHelper is PdfSignatureFormFieldHelper && + field is PdfSignatureFormField) { + record = _updateFormField(field, null); + } + if (record != null) { + formFieldValueChangeRecords.add(record); + } + } + } + } + if (formFieldValueChangeRecords.isNotEmpty) { + _changeTracker.addChange( + FormFieldValueChangeTracker( + records: formFieldValueChangeRecords, + onUndoOrRedo: _updateFormField, + ), + ); + } + } else if (property == 'addAnnotation') { + if (_pdfViewerController._annotation != null) { + final Annotation newAnnotation = _pdfViewerController._annotation!; + _setDefaultProperties(newAnnotation); + newAnnotation.onPropertyChange = _handleAnnotationPropertyChange; + newAnnotation.onPropertyChanged = _handleAnnotationPropertyChanged; + setState(() { + _pdfViewerController._annotations.add(newAnnotation); + }); + _updateRemovedAnnotation(newAnnotation, false); + _changeTracker.addChange( + AnnotationAddOrRemoveTracker( + annotation: newAnnotation, + undoCallback: _pdfViewerController.removeAnnotation, + redoCallback: _pdfViewerController.addAnnotation, + ), + ); + if (!_changeTracker.changeInProgress) { + final int maxZOrder = _pdfViewerController._annotations + .where( + (Annotation annotation) => + annotation.pageNumber == newAnnotation.pageNumber, + ) + .fold(-1, (int previousValue, Annotation annotation) { + if (annotation.zOrder > previousValue) { + return annotation.zOrder; + } else { + return previousValue; + } + }); + newAnnotation.zOrder = maxZOrder + 1; + if (widget.onAnnotationAdded != null) { + widget.onAnnotationAdded!(newAnnotation); + } + } + } + _pdfViewerController._annotation = null; + } else if (property == 'selectAnnotation') { + if (_pdfViewerController._annotation != null) { + final Annotation annotation = _pdfViewerController._annotation!; + if (_selectedAnnotation != annotation) { + _deselectAnnotation(); + setState(() { + _selectedAnnotation = annotation; + }); + annotation.isSelected = true; + _pdfViewerController.jumpToPage(annotation.pageNumber); + if (_pdfViewerController.pageNumber != annotation.pageNumber && + widget.pageLayoutMode == PdfPageLayoutMode.continuous) { + final PdfPageRotateAngle rotatedAngle = + _document!.pages[annotation.pageNumber - 1].rotation; + final bool isRotatedTo90or270 = + rotatedAngle == PdfPageRotateAngle.rotateAngle90 || + rotatedAngle == PdfPageRotateAngle.rotateAngle270; + final Size originalPageSize = + _document!.pages[annotation.pageNumber - 1].size; + final double heightPercentage = + (isRotatedTo90or270 + ? originalPageSize.width + : originalPageSize.height) / + _pdfPages[annotation.pageNumber]!.pageSize.height; + + final Rect rotatedBounds = _adjustForRotation( + annotation.boundingBox, + originalPageSize.width, + originalPageSize.height, + rotatedAngle, + ); + + if (_scrollDirection == PdfScrollDirection.horizontal) { + _pdfViewerController.jumpTo( + xOffset: + _pdfViewerController.scrollOffset.dx + + rotatedBounds.left / heightPercentage, + yOffset: rotatedBounds.top / heightPercentage, + ); + } else { + _pdfViewerController.jumpTo( + xOffset: rotatedBounds.left / heightPercentage, + yOffset: + _pdfViewerController.scrollOffset.dy + + rotatedBounds.top / heightPercentage, + ); + } + } + if (widget.onAnnotationSelected != null) { + widget.onAnnotationSelected!(annotation); + } + if (annotation is StickyNoteAnnotation) { + _showStickyNoteDialog(annotation); + } + } + } + } else if (property == 'removeAnnotation') { + if (_pdfViewerController._annotation != null) { + final Annotation annotation = _pdfViewerController._annotation!; + if (_pdfViewerController.annotationSettings.canEdit(annotation)) { + if (annotation == _selectedAnnotation) { + _deselectAnnotation(); + } + setState(() { + _pdfViewerController._annotations.remove(annotation); + }); + _updateRemovedAnnotation(annotation, true); + _changeTracker.addChange( + AnnotationAddOrRemoveTracker( + annotation: annotation, + undoCallback: _pdfViewerController.addAnnotation, + redoCallback: _pdfViewerController.removeAnnotation, + ), + ); + if (widget.onAnnotationRemoved != null && + !_changeTracker.changeInProgress) { + widget.onAnnotationRemoved!(annotation); + } + } + } + } else if (property == 'deselectAnnotation') { + if (_selectedAnnotation != null) { + final Annotation annotationToDeselect = + _pdfViewerController._annotation!; + if (annotationToDeselect == _selectedAnnotation) { + _deselectAnnotation(); } - } else if (!_pdfDimension.isEmpty) { - _pdfScrollableStateKey.currentState?.jumpTo( - xOffset: _pdfViewerController._horizontalOffset, - yOffset: _pdfViewerController._verticalOffset); } - } else if (property == 'pageNavigate' && - _pdfViewerController._pageNavigator != null) { - _clearSelection(); - switch (_pdfViewerController._pageNavigator!.option) { - case Navigation.jumpToPage: - if (_pdfViewerController._pageNavigator!.index! > 0 && - _pdfViewerController._pageNavigator!.index! <= - _pdfViewerController.pageCount) { - _jumpToPage(_pdfViewerController._pageNavigator!.index!); - } - break; - case Navigation.firstPage: - _jumpToPage(1); - break; - case Navigation.lastPage: - if (_pdfViewerController.pageNumber != - _pdfViewerController.pageCount) { - _jumpToPage(_pdfViewerController.pageCount); + } else if (property == 'removeAllAnnotations') { + if (_pdfViewerController._annotations.isNotEmpty) { + final List annotationsToRemove = []; + for (final Annotation annotation in _pdfViewerController._annotations) { + if (_pdfViewerController.annotationSettings.canEdit(annotation)) { + if (_pdfViewerController._clearAnnotationPageNumber == 0 || + annotation.pageNumber == + _pdfViewerController._clearAnnotationPageNumber) { + annotationsToRemove.add(annotation); + } } - break; - case Navigation.previousPage: - if (_pdfViewerController.pageNumber > 1) { - _jumpToPage(_pdfViewerController.pageNumber - 1); + } + if (annotationsToRemove.isNotEmpty) { + if (_selectedAnnotation != null) { + if (annotationsToRemove.contains(_selectedAnnotation)) { + _deselectAnnotation(); + } } - break; - case Navigation.nextPage: - if (_pdfViewerController.pageNumber != - _pdfViewerController.pageCount) { - _jumpToPage(_pdfViewerController.pageNumber + 1); + for (final Annotation annotation in annotationsToRemove) { + _pdfViewerController._annotations.remove(annotation); + _updateRemovedAnnotation(annotation, true); } - break; + setState(() { + _changeTracker.addChange( + ClearAnnotationsTracker( + annotations: annotationsToRemove, + undoCallback: _pdfViewerController.addAnnotation, + redoCallback: _pdfViewerController.removeAnnotation, + ), + ); + }); + } } - } else if (property == 'searchText') { - _isSearchStarted = true; - _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 = 0; - _pdfViewerController._pdfTextSearchResult._updateResult(false); - } else { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - _getInstanceInPage(_pdfViewerController.pageNumber); + /// Gets the rotated bounds of the annotation. + Rect _adjustForRotation( + Rect originalBounds, + double pageWidth, + double pageHeight, + PdfPageRotateAngle angle, + ) { + Rect adjustedBounds = originalBounds; + switch (angle) { + case PdfPageRotateAngle.rotateAngle90: + adjustedBounds = Rect.fromLTWH( + pageWidth - originalBounds.bottom, + originalBounds.left, + originalBounds.height, + originalBounds.width, + ); + break; + case PdfPageRotateAngle.rotateAngle180: + adjustedBounds = Rect.fromLTWH( + pageWidth - originalBounds.right, + pageHeight - originalBounds.bottom, + originalBounds.width, + originalBounds.height, + ); + break; + case PdfPageRotateAngle.rotateAngle270: + adjustedBounds = Rect.fromLTWH( + originalBounds.top, + pageHeight - originalBounds.right, + originalBounds.height, + originalBounds.width, + ); + break; + case PdfPageRotateAngle.rotateAngle0: + return originalBounds; + } + return adjustedBounds; + } + + /// Assigns the default properties for the annotation. + void _setDefaultProperties(Annotation annotation) { + if (_pdfViewerController.annotationSettings.author.isNotEmpty && + annotation.author == null) { + annotation.author = _pdfViewerController.annotationSettings.author; + } + if (annotation is HighlightAnnotation) { + if (annotation.color == Colors.transparent) { + annotation.color = + _pdfViewerController.annotationSettings.highlight.color; + } + if (annotation.opacity == -1) { + annotation.opacity = + _pdfViewerController.annotationSettings.highlight.opacity; + } + } else if (annotation is StrikethroughAnnotation) { + if (annotation.color == Colors.transparent) { + annotation.color = + _pdfViewerController.annotationSettings.strikethrough.color; + } + if (annotation.opacity == -1) { + annotation.opacity = + _pdfViewerController.annotationSettings.strikethrough.opacity; + } + } else if (annotation is UnderlineAnnotation) { + if (annotation.color == Colors.transparent) { + annotation.color = + _pdfViewerController.annotationSettings.underline.color; + } + if (annotation.opacity == -1) { + annotation.opacity = + _pdfViewerController.annotationSettings.underline.opacity; + } + } else if (annotation is SquigglyAnnotation) { + if (annotation.color == Colors.transparent) { + annotation.color = + _pdfViewerController.annotationSettings.squiggly.color; + } + if (annotation.opacity == -1) { + annotation.opacity = + _pdfViewerController.annotationSettings.squiggly.opacity; + } + } else if (annotation is StickyNoteAnnotation) { + if (annotation.color == Colors.transparent) { + annotation.color = + _pdfViewerController.annotationSettings.stickyNote.color; + } + if (annotation.opacity == -1) { + annotation.opacity = + _pdfViewerController.annotationSettings.stickyNote.opacity; + } + } + } + + void _deselectAnnotation() { + if (_selectedAnnotation != null) { + final Annotation annotation = _selectedAnnotation!; + setState(() { + _selectedAnnotation = null; + }); + annotation.isSelected = false; + if (widget.onAnnotationDeselected != null) { + widget.onAnnotationDeselected!(annotation); + } + } + if (_stickyNoteEditTextOverlyEntry != null) { + _hideStickyNoteDialog(); + } + } + + void _updateRemovedAnnotation(Annotation annotation, bool shouldAdd) { + if (shouldAdd) { + if (!_removedAnnotations.contains(annotation)) { + _removedAnnotations.add(annotation); + } + } else { + if (_removedAnnotations.contains(annotation)) { + _removedAnnotations.remove(annotation); + } + } + } + + void _onAnnotationSelectionChanged(Annotation? annotation) { + if (annotation == null) { + _deselectAnnotation(); + } else { + if (annotation != _selectedAnnotation) { + _deselectAnnotation(); + } + annotation.isSelected = true; + if (widget.onAnnotationSelected != null) { + widget.onAnnotationSelected!(annotation); + } + if (annotation is StickyNoteAnnotation) { + _showStickyNoteDialog(annotation); + } + } + setState(() { + _selectedAnnotation = annotation; + }); + } + + bool _handleAnnotationPropertyChange( + Annotation annotation, + String propertyName, + ) { + return _pdfViewerController.annotationSettings.canEdit(annotation); + } + + void _handleAnnotationPropertyChanged( + Annotation annotation, + String propertyName, + dynamic oldValue, + dynamic newValue, + ) { + widget.onAnnotationEdited?.call(annotation); + if (kIsDesktop && + annotation is StickyNoteAnnotation && + annotation.isSelected && + propertyName == 'position') { + _updateStickyNoteDialog(); + } + _changeTracker.addChange( + AnnotationPropertyChangeTracker( + propertyName: propertyName, + oldValue: oldValue, + newValue: newValue, + annotation: annotation, + ), + ); + } + + /// Perform text search for mobile, windows and macOS platforms. + Future _performTextSearch() async { + _pdfViewerController._pdfTextSearchResult._addListener(_handleTextSearch); + setState(() {}); + _pdfViewerController._pdfTextSearchResult.clear(); + final ReceivePort receivePort = ReceivePort(); + receivePort.listen((dynamic message) { + if (message is SendPort) { + message.send([ + receivePort.sendPort, + _pdfTextExtractor, + _pdfViewerController._searchText, + _pdfViewerController._textSearchOption, + _matchedTextPageIndices, + ]); + } else if (message is List) { + _textCollection!.addAll(message); + if (_textCollection!.isNotEmpty && + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount == + 0) { + _pdfViewerController._pdfTextSearchResult._updateResult(true); + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 1; + _isPageChanged = false; if (_pdfPages.isNotEmpty) { _jumpToSearchInstance(); } _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = _textCollection!.length; - _pdfViewerController._pdfTextSearchResult._updateResult(true); } - _pdfViewerController._pdfTextSearchResult - .addListener(_handleTextSearch); - setState(() {}); + if (_textCollection!.isNotEmpty) { + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = + _textCollection!.length; + } + } else if (message is String) { + if (_textCollection!.isEmpty) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + } + _pdfViewerController._pdfTextSearchResult._updateSearchCompletedStatus( + true, + ); } + }); + _textSearchIsolate = await Isolate.spawn( + _findTextAsync, + receivePort.sendPort, + ); + } + + /// Text search is run in separate thread + static Future _findTextAsync(SendPort sendPort) async { + final ReceivePort receivePort = ReceivePort(); + sendPort.send(receivePort.sendPort); + final searchDetails = await receivePort.first; + final SendPort replyPort = searchDetails[0]; + for (int i = 0; i < searchDetails[4].length; i++) { + final List result = searchDetails[1].findText( + [searchDetails[2]], + startPageIndex: searchDetails[4][i], + searchOption: searchDetails[3], + ); + replyPort.send(result); + } + replyPort.send('SearchCompleted'); + } + + /// Terminates the text search isolate. + void _killTextSearchIsolate() { + if (_textSearchIsolate != null) { + _textSearchIsolate?.kill(priority: Isolate.immediate); } } int _getInstanceInPage(int pageNumber, {bool lookForFirst = true}) { int? instance = 0; if (lookForFirst) { - instance = _jumpToNextInstance(pageNumber) ?? + instance = + _jumpToNextInstance(pageNumber) ?? _jumpToPreviousInstance(pageNumber); } else { - instance = _jumpToPreviousInstance(pageNumber) ?? + instance = + _jumpToPreviousInstance(pageNumber) ?? _jumpToNextInstance(pageNumber); } return instance ?? 1; @@ -3247,9 +6389,12 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _updateSearchInstance(isNext: isNext); _isPageChanged = false; } - final int currentInstancePageIndex = _textCollection![ - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - - 1] + const int searchInstanceTopMargin = 20; + final int currentInstancePageIndex = + _textCollection![_pdfViewerController + ._pdfTextSearchResult + .currentInstanceIndex - + 1] .pageIndex + 1; Offset topOffset = Offset.zero; @@ -3258,84 +6403,97 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ?.currentState ?.canvasRenderBox != null) { - topOffset = _pdfPagesKey[_pdfViewerController.pageNumber]! - .currentState! - .canvasRenderBox! - .getRotatedTextBounds( - _textCollection![_pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds, - currentInstancePageIndex - 1, - _document!.pages[currentInstancePageIndex - 1].rotation) - .topLeft; - } - final double heightPercentage = (kIsDesktop && - !_isMobile && - !_isOverflowed && - widget.pageLayoutMode == PdfPageLayoutMode.continuous) - ? 1 - : _originalHeight![currentInstancePageIndex - 1] / - // ignore: avoid_as - _pdfPages[currentInstancePageIndex]!.pageSize.height as double; - - final double widthPercentage = (kIsDesktop && - !_isMobile && - !_isOverflowed && - widget.pageLayoutMode == PdfPageLayoutMode.continuous) - ? 1 - : _originalWidth![currentInstancePageIndex - 1] / - // ignore: avoid_as - _pdfPages[currentInstancePageIndex]!.pageSize.width as double; + topOffset = + _pdfPagesKey[_pdfViewerController.pageNumber]! + .currentState! + .canvasRenderBox! + .getRotatedTextBounds( + _textCollection![_pdfViewerController + ._pdfTextSearchResult + .currentInstanceIndex - + 1] + .bounds, + currentInstancePageIndex - 1, + _document!.pages[currentInstancePageIndex - 1].rotation, + ) + .topLeft; + } + final double heightPercentage = + _document!.pages[currentInstancePageIndex - 1].size.height / + _pdfPages[currentInstancePageIndex]!.pageSize.height; + final double widthPercentage = + _document!.pages[currentInstancePageIndex - 1].size.width / + _pdfPages[currentInstancePageIndex]!.pageSize.width; double searchOffsetX = topOffset.dx / widthPercentage; - double searchOffsetY = _pdfPages[currentInstancePageIndex]!.pageOffset + - (topOffset.dy / heightPercentage); + double searchOffsetY = + (_pdfPages[currentInstancePageIndex]!.pageOffset + + (topOffset.dy / heightPercentage)) - + searchInstanceTopMargin; + if (_scrollDirection == PdfScrollDirection.horizontal) { - searchOffsetX = _pdfPages[currentInstancePageIndex]!.pageOffset + + searchOffsetX = + _pdfPages[currentInstancePageIndex]!.pageOffset + topOffset.dx / widthPercentage; - searchOffsetY = topOffset.dy / heightPercentage; + searchOffsetY = + (topOffset.dy / heightPercentage) - searchInstanceTopMargin; } final Offset offset = _pdfScrollableStateKey.currentState?.currentOffset ?? Offset.zero; final Rect viewport = Rect.fromLTWH( - offset.dx, - offset.dy, - _viewportConstraints.biggest.width / _pdfViewerController.zoomLevel, - _viewportConstraints.biggest.height / _pdfViewerController.zoomLevel); + offset.dx, + offset.dy - searchInstanceTopMargin, + _viewportConstraints.biggest.width / _pdfViewerController.zoomLevel, + _viewportConstraints.biggest.height / _pdfViewerController.zoomLevel, + ); final Offset singleLayoutOffset = _singlePageViewKey.currentState?.currentOffset ?? Offset.zero; final Rect singleLayoutViewport = Rect.fromLTWH( - singleLayoutOffset.dx, - singleLayoutOffset.dy, - _viewportConstraints.biggest.width / _pdfViewerController.zoomLevel, - _viewportConstraints.biggest.height / _pdfViewerController.zoomLevel); + singleLayoutOffset.dx, + singleLayoutOffset.dy, + _viewportConstraints.biggest.width / _pdfViewerController.zoomLevel, + _viewportConstraints.biggest.height / _pdfViewerController.zoomLevel, + ); if (widget.pageLayoutMode == PdfPageLayoutMode.single) { - if (!singleLayoutViewport.contains(Offset( + if (!singleLayoutViewport.contains( + Offset( topOffset.dx / widthPercentage, (topOffset.dy / heightPercentage) + - _singlePageViewKey.currentState!.greyAreaSize)) || + _singlePageViewKey.currentState!.greyAreaSize, + ), + ) || currentInstancePageIndex != _pdfViewerController.pageNumber) { _singlePageViewKey.currentState!.jumpOnZoomedDocument( - currentInstancePageIndex, - Offset(topOffset.dx / widthPercentage, - topOffset.dy / heightPercentage)); + currentInstancePageIndex, + Offset( + topOffset.dx / widthPercentage, + topOffset.dy / heightPercentage, + ), + ); } - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { if (_isPageChanged) { _isPageChanged = false; } + _getTileImage(); }); } else { if (_pdfScrollableStateKey.currentState != null && !viewport.contains(Offset(searchOffsetX, searchOffsetY))) { _pdfViewerController.jumpTo( - xOffset: searchOffsetX, yOffset: searchOffsetY); - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + xOffset: + _textDirection == TextDirection.rtl && + _scrollDirection == PdfScrollDirection.horizontal + ? (_maxScrollExtent - searchOffsetX) + : searchOffsetX, + yOffset: searchOffsetY, + ); + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { if (_isPageChanged) { _isPageChanged = false; } + _getTileImage(); }); } } @@ -3346,48 +6504,57 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_pdfViewerController._pdfTextSearchResult.hasResult) { if (property == 'nextInstance') { setState(() { - _pdfViewerController._pdfTextSearchResult - ._currentOccurrenceIndex = _pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex < - _pdfViewerController._pdfTextSearchResult._totalInstanceCount - ? _pdfViewerController._pdfTextSearchResult.currentInstanceIndex + - 1 - : 1; - _jumpToSearchInstance(isNext: true); + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex < + _pdfViewerController + ._pdfTextSearchResult + ._totalInstanceCount + ? _pdfViewerController + ._pdfTextSearchResult + .currentInstanceIndex + + 1 + : 1; + _jumpToSearchInstance(); }); } else if (property == 'previousInstance') { setState(() { _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = _pdfViewerController._pdfTextSearchResult.currentInstanceIndex > 1 ? _pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - + ._pdfTextSearchResult + .currentInstanceIndex - 1 : _pdfViewerController - ._pdfTextSearchResult.totalInstanceCount; + ._pdfTextSearchResult + .totalInstanceCount; _jumpToSearchInstance(isNext: false); }); - } else if (property == 'clear') { - setState(() { - _isSearchStarted = false; - _textCollection = null; - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; - _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; - _pdfViewerController._pdfTextSearchResult._updateResult(false); - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.focusNode - .requestFocus(); - }); - return; } } + if (property == 'clear') { + if (!kIsWeb) { + _killTextSearchIsolate(); + } + + _isSearchStarted = false; + _textCollection = []; + + _pdfViewerController._pdfTextSearchResult._updateSearchCompletedStatus( + false, + ); + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.focusNode + .requestFocus(); + _checkMount(); + return; + } } void _handlePdfOffsetChanged(Offset offset) { if (!_isSearchStarted) { - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.focusNode + _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.focusNode .requestFocus(); } if (widget.pageLayoutMode == PdfPageLayoutMode.continuous) { @@ -3397,13 +6564,27 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _updateCurrentPageNumber(currentOffset: offset.dy); } } - if (widget.pageLayoutMode == PdfPageLayoutMode.single && - _pageController.hasClients) { - _pdfViewerController._scrollPositionX = _pageController.offset; - } else { - _pdfViewerController._scrollPositionX = offset.dx.abs(); + _updateScrollOffset(); + _hideTextSelectionMenu(); + _hideStickyNoteDialog(); + } + + /// Updates the [PdfViewerController.scrollOffset] API + void _updateScrollOffset() { + Offset offset = _transformationController.toScene(Offset.zero); + if (widget.pageLayoutMode == PdfPageLayoutMode.continuous) { + offset = Offset( + offset.dx.clamp(0, double.maxFinite), + offset.dy.clamp(0, double.maxFinite), + ); } - _pdfViewerController._scrollPositionY = offset.dy.abs(); + _pdfViewerController._scrollPositionX = + (_textDirection == TextDirection.rtl && + widget.pageLayoutMode == PdfPageLayoutMode.continuous && + _scrollDirection == PdfScrollDirection.horizontal) + ? (_maxScrollExtent - offset.dx) + : offset.dx; + _pdfViewerController._scrollPositionY = offset.dy; } } @@ -3464,7 +6645,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// } ///} /// ``` -class PdfViewerController extends _ValueChangeNotifier { +class PdfViewerController extends ChangeNotifier with _ValueChangeNotifier { /// Zoom level double _zoomLevel = 1; @@ -3483,15 +6664,65 @@ class PdfViewerController extends _ValueChangeNotifier { /// Sets the current page number. set _pageNumber(int num) { _currentPageNumber = num; - notifyPropertyChangedListeners(property: 'pageChanged'); + notifyListeners(); } + /// Gets the current page number. + int get _pageNumber => _currentPageNumber; + /// Sets the page count. set _pageCount(int pageCount) { _totalPages = pageCount; - notifyPropertyChangedListeners(property: 'pageCount'); + _notifyPropertyChangedListeners(property: 'pageCount'); } + /// Gets the page count. + int get _pageCount => _totalPages; + + /// Page number in which the form fields are to be cleared. + /// If the value is 0, all the form fields in the document will be cleared. + int _clearFormDataPageNumber = 0; + + /// List of form fields in the docuemnt. + final List _formFields = []; + + /// The page number in which the annotations are to be removed. + /// If the value is 0, all the annotations in the document will be removed. + int _clearAnnotationPageNumber = 0; + + /// Annotation instance + Annotation? _annotation; + + /// Current annotation mode + PdfAnnotationMode _annotationMode = PdfAnnotationMode.none; + + /// List of annotations in the document. + final List _annotations = []; + + /// Gets or sets the default annotation settings. + PdfAnnotationSettings annotationSettings = PdfAnnotationSettings(); + + /// Imported form data bytes + List _importedFormDataBytes = []; + + /// Exported form data bytes + List _exportedFormDataBytes = []; + + /// Import data format + DataFormat _importDataFormat = DataFormat.xfdf; + + /// Export data format + DataFormat _exportDataFormat = DataFormat.xfdf; + + /// Saved document bytes + Future> _savedDocumentBytes = Future>.value([]); + + /// Flatten option + PdfFlattenOption _flattenOption = PdfFlattenOption.none; + + /// Continue the form data import on error + bool _continueImportOnError = false; + /// PdfBookmark instance PdfBookmark? _pdfBookmark; @@ -3574,7 +6805,7 @@ class PdfViewerController extends _ValueChangeNotifier { return; } _zoomLevel = newValue; - notifyPropertyChangedListeners(property: 'zoomLevel'); + _notifyPropertyChangedListeners(property: 'zoomLevel'); } /// Current page number displayed in the [SfPdfViewer]. @@ -3789,7 +7020,49 @@ class PdfViewerController extends _ValueChangeNotifier { /// ``` void jumpToBookmark(PdfBookmark bookmark) { _pdfBookmark = bookmark; - notifyPropertyChangedListeners(property: 'jumpToBookmark'); + _notifyPropertyChangedListeners(property: 'jumpToBookmark'); + } + + /// Imports the form data with the specified [DataFormat]. + /// + /// * inputBytes – _required_ – Specifies the bytes of the form data. + /// * dataFormat – _required_ – Defines the constants that specify the format + /// of importing form data. + /// * continueImportOnError - _optional_ - Indicates whether the + /// `SfPdfViewer` should continue to import the form data even if any of them + /// has an error. + void importFormData( + List inputBytes, + DataFormat dataFormat, [ + bool continueImportOnError = false, + ]) { + _importedFormDataBytes = inputBytes; + _importDataFormat = dataFormat; + _continueImportOnError = continueImportOnError; + _notifyPropertyChangedListeners(property: 'importFormData'); + } + + /// Export the form data with the specified [DataFormat] and return the bytes + /// as list of int. + /// + /// dataFormat – required – Defines the constants that specify the format + /// of exporting form data. + List exportFormData({required DataFormat dataFormat}) { + _exportDataFormat = dataFormat; + _notifyPropertyChangedListeners(property: 'exportFormData'); + return _exportedFormDataBytes; + } + + /// Saves the document and return the saved bytes as future list of int. + /// + /// * flattenOptions – _optional_ – Defines the constants that specify the + /// option for flattening form fields. + Future> saveDocument({ + PdfFlattenOption flattenOption = PdfFlattenOption.none, + }) { + _flattenOption = flattenOption; + _notifyPropertyChangedListeners(property: 'saveDocument'); + return _savedDocumentBytes; } /// Jumps the scroll position of [SfPdfViewer] to the specified offset value. @@ -3849,7 +7122,7 @@ class PdfViewerController extends _ValueChangeNotifier { void jumpTo({double xOffset = 0.0, double yOffset = 0.0}) { _horizontalOffset = xOffset; _verticalOffset = yOffset; - notifyPropertyChangedListeners(property: 'jumpTo'); + _notifyPropertyChangedListeners(property: 'jumpTo'); } /// Navigates to the specified page number in a PDF document. @@ -3904,7 +7177,7 @@ class PdfViewerController extends _ValueChangeNotifier { /// ``` void jumpToPage(int pageNumber) { _pageNavigator = Pagination(Navigation.jumpToPage, index: pageNumber); - notifyPropertyChangedListeners(property: 'pageNavigate'); + _notifyPropertyChangedListeners(property: 'pageNavigate'); } /// Navigates to the previous page of a PDF document. @@ -3956,7 +7229,7 @@ class PdfViewerController extends _ValueChangeNotifier { /// ``` void previousPage() { _pageNavigator = Pagination(Navigation.previousPage); - notifyPropertyChangedListeners(property: 'pageNavigate'); + _notifyPropertyChangedListeners(property: 'pageNavigate'); } /// Navigates to the next page of a PDF document. @@ -4008,7 +7281,7 @@ class PdfViewerController extends _ValueChangeNotifier { /// ``` void nextPage() { _pageNavigator = Pagination(Navigation.nextPage); - notifyPropertyChangedListeners(property: 'pageNavigate'); + _notifyPropertyChangedListeners(property: 'pageNavigate'); } /// Navigates to the first page of a PDF document. @@ -4060,7 +7333,7 @@ class PdfViewerController extends _ValueChangeNotifier { /// ``` void firstPage() { _pageNavigator = Pagination(Navigation.firstPage); - notifyPropertyChangedListeners(property: 'pageNavigate'); + _notifyPropertyChangedListeners(property: 'pageNavigate'); } /// Navigates to the last page of a PDF document. @@ -4113,143 +7386,158 @@ class PdfViewerController extends _ValueChangeNotifier { /// ``` void lastPage() { _pageNavigator = Pagination(Navigation.lastPage); - notifyPropertyChangedListeners(property: 'pageNavigate'); + _notifyPropertyChangedListeners(property: 'pageNavigate'); } /// Searches the given text in the document. /// - /// Returns the [PdfTextSearchResult] object using which the search - /// navigation can be performed on the instances found. + /// This method returns the [PdfTextSearchResult] object using which the search navigation can be performed on the instances found. + /// + /// On mobile and desktop platforms, the search will be performed asynchronously + /// and so the results will be returned periodically on a page-by-page basis, + /// which can be retrieved using the [PdfTextSearchResult.addListener] method in the application. + /// + /// Whereas in the web platform, the search will be performed synchronously + /// and so the result will be returned only after completing the search on all the pages. + /// This is since [isolate] is not supported for the web platform yet. /// - /// * text - _required_ - The text to be searched in the document. - /// * searchOption - _optional_ - Defines the constants that specify the - /// option for text search. + /// * searchText - required - The text to be searched in the document. + /// * searchOption - optional - Defines the constants that specify the option for text search. /// /// This example demonstrates how to search text in [SfPdfViewer]. /// /// ```dart /// class MyAppState extends State { /// - /// late PdfViewerController _pdfViewerController; - /// late PdfTextSearchResult _searchResult; - /// - /// @override - /// void initState() { - /// _pdfViewerController = PdfViewerController(); - /// _searchResult = PdfTextSearchResult(); - /// super.initState(); - /// } + /// late PdfViewerController _pdfViewerController; + /// late PdfTextSearchResult _searchResult; /// - /// void _showDialog(BuildContext context) - /// { - /// showDialog( - /// context: context, - /// builder: (BuildContext context) { - /// return AlertDialog( - /// title: Text('Search Result'), - /// content: Text('No more occurrences found. Would you like to continue to search from the beginning?'), - /// actions: [ - /// FlatButton( - /// onPressed: () { - /// _searchResult.nextInstance(); - /// Navigator.of(context).pop(); - /// }, - /// child: Text('YES'), - /// ), - /// FlatButton( - /// onPressed: () { - /// _searchResult.clear(); - /// Navigator.of(context).pop(); - /// }, - /// child: Text('NO'), - /// ), - /// ], - /// ); - /// }, - /// ); - /// } + /// @override + /// void initState() { + /// _pdfViewerController = PdfViewerController(); + /// _searchResult = PdfTextSearchResult(); + /// super.initState(); + /// } /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// appBar: AppBar( - /// title: Text('Syncfusion Flutter PdfViewer'), + /// void _showDialog(BuildContext context) { + /// showDialog( + /// context: context, + /// builder: (BuildContext context) { + /// return AlertDialog( + /// title: const Text('Search Result'), + /// content: const Text( + /// 'No more occurrences found. Would you like to continue to search from the beginning?'), /// actions: [ - /// IconButton( - /// icon: Icon( - /// Icons.search, - /// color: Colors.white, - /// ), - /// onPressed: () async { - /// _searchResult = await _pdfViewerController.searchText( - /// 'PDF', searchOption: TextSearchOption.caseSensitive); - /// setState(() {}); - /// }, - /// ), - /// Visibility( - /// visible: _searchResult.hasResult, - /// child: IconButton( - /// icon: Icon( - /// Icons.clear, - /// color: Colors.white, - /// ), - /// onPressed: () { - /// setState(() { - /// _searchResult.clear(); - /// }); - /// }, - /// ), - /// ), - /// Visibility( - /// visible: _searchResult.hasResult, - /// child: IconButton( - /// icon: Icon( - /// Icons.navigate_before, - /// color: Colors.white, - /// ), - /// onPressed: () { - /// _searchResult.previousInstance(); - /// }, - /// ), + /// TextButton( + /// onPressed: () { + /// _searchResult.nextInstance(); + /// Navigator.of(context).pop(); + /// }, + /// child: const Text('YES'), /// ), - /// Visibility( - /// visible: _searchResult.hasResult, - /// child: IconButton( - /// icon: Icon( - /// Icons.navigate_next, - /// color: Colors.white, - /// ), - /// onPressed: () { - /// if (_searchResult.currentInstanceIndex == - /// _searchResult.totalInstanceCount) { - /// _showDialog(context); - /// } - /// else { - /// _searchResult.nextInstance(); - /// } - /// }, - /// ), + /// TextButton( + /// onPressed: () { + /// _searchResult.clear(); + /// Navigator.of(context).pop(); + /// }, + /// child: const Text('NO'), /// ), /// ], - /// ), - /// body: - /// SfPdfViewer.asset( - /// 'assets/flutter-succinctly.pdf', - /// controller: _pdfViewerController, - /// currentSearchTextHighlightColor: Colors.blue, - /// otherSearchTextHighlightColor: Colors.yellow, - /// ) - /// ) + /// ); + /// }, /// ); /// } - ///} + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter PdfViewer'), + /// actions: [ + /// IconButton( + /// icon: const Icon( + /// Icons.search, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// _searchResult = _pdfViewerController.searchText('the', + /// searchOption: TextSearchOption.caseSensitive); + /// if (kIsWeb) { + /// setState(() {}); + /// } else { + /// _searchResult.addListener(() { + /// if (_searchResult.hasResult) { + /// setState(() {}); + /// } + /// }); + /// } + /// }), + /// Visibility( + /// visible: _searchResult.hasResult, + /// child: IconButton( + /// icon: const Icon( + /// Icons.clear, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// setState(() { + /// _searchResult.clear(); + /// }); + /// }, + /// ), + /// ), + /// Visibility( + /// visible: _searchResult.hasResult, + /// child: IconButton( + /// icon: const Icon( + /// Icons.navigate_before, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// _searchResult.previousInstance(); + /// }, + /// ), + /// ), + /// Visibility( + /// visible: _searchResult.hasResult, + /// child: IconButton( + /// icon: const Icon( + /// Icons.navigate_next, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// if ((_searchResult.currentInstanceIndex == + /// _searchResult.totalInstanceCount && + /// kIsWeb) || + /// (_searchResult.currentInstanceIndex == + /// _searchResult.totalInstanceCount && + /// _searchResult.isSearchCompleted)) { + /// _showDialog(context); + /// } else { + /// _searchResult.nextInstance(); + /// } + /// }, + /// ), + /// ), + /// ], + /// ), + /// body: SfPdfViewer.network( + /// 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + /// controller: _pdfViewerController, + /// currentSearchTextHighlightColor: Colors.blue, + /// otherSearchTextHighlightColor: Colors.yellow, + /// ))); + /// } + /// } ///''' - Future searchText(String searchText, - {TextSearchOption? searchOption}) async { + PdfTextSearchResult searchText( + String searchText, { + TextSearchOption? searchOption, + }) { _searchText = searchText; _textSearchOption = searchOption; - notifyPropertyChangedListeners(property: 'searchText'); + _notifyPropertyChangedListeners(property: 'searchText'); return _pdfTextSearchResult; } @@ -4257,10 +7545,83 @@ class PdfViewerController extends _ValueChangeNotifier { /// /// Returns `true`, if the text selection is cleared properly. bool clearSelection() { - notifyPropertyChangedListeners(property: 'clearTextSelection'); + _notifyPropertyChangedListeners(property: 'clearTextSelection'); return _clearTextSelection; } + /// Gets the form fields data in the document. + List getFormFields() { + return List.unmodifiable(_formFields); + } + + /// Clears the form fields data in the PDF document. + /// + /// [pageNumber] - optional - The page number in which all form fields should be cleared. + void clearFormData({int pageNumber = 0}) { + _clearFormDataPageNumber = pageNumber; + _notifyPropertyChangedListeners(property: 'clearFormData'); + // Reset the page number to 0 after clearing the form fields. + _clearFormDataPageNumber = 0; + } + + /// Adds the given annotation to the page represented by the annotation’s PageNumber property.. + void addAnnotation(Annotation annotation) { + _annotation = annotation; + _notifyPropertyChangedListeners(property: 'addAnnotation'); + _annotation = null; + clearSelection(); + } + + /// Removes the given annotation from the page. + void removeAnnotation(Annotation annotation) { + if (_annotations.contains(annotation)) { + _annotation = annotation; + _notifyPropertyChangedListeners(property: 'removeAnnotation'); + _annotation = null; + } + } + + /// Selects the given annotation. + void selectAnnotation(Annotation annotation) { + if (_annotations.contains(annotation)) { + _annotation = annotation; + _notifyPropertyChangedListeners(property: 'selectAnnotation'); + _annotation = null; + } + } + + /// Deselects the given annotation. + void deselectAnnotation(Annotation annotation) { + if (_annotations.contains(annotation)) { + _annotation = annotation; + _notifyPropertyChangedListeners(property: 'deselectAnnotation'); + _annotation = null; + } + } + + /// Gets the list of annotations in the PDF. + List getAnnotations() { + return _annotations; + } + + /// Gets or sets a value indicating the type of annotation that should be drawn using UI interaction on the PDF pages. + PdfAnnotationMode get annotationMode => _annotationMode; + set annotationMode(PdfAnnotationMode value) { + if (_annotationMode != value) { + clearSelection(); + _annotationMode = value; + } + } + + /// Removes all annotations from the PDF document. + /// + /// [pageNumber] - optional – removes the annotation in the specified page + void removeAllAnnotations({int pageNumber = 0}) { + _clearAnnotationPageNumber = pageNumber; + _notifyPropertyChangedListeners(property: 'removeAllAnnotations'); + _clearAnnotationPageNumber = 0; + } + /// Resets the controller value when widget is updated. void _reset() { _zoomLevel = 1.0; @@ -4271,12 +7632,14 @@ class PdfViewerController extends _ValueChangeNotifier { _searchText = ''; _pageNavigator = null; _pdfBookmark = null; - notifyPropertyChangedListeners(); + annotationMode = PdfAnnotationMode.none; + _annotations.clear(); + _notifyPropertyChangedListeners(); } } /// PdfTextSearchResult holds the details of TextSearch -class PdfTextSearchResult extends _ValueChangeNotifier { +class PdfTextSearchResult extends ChangeNotifier with _ValueChangeNotifier { /// Current instance number of the searched text. int _currentInstanceIndex = 0; @@ -4286,12 +7649,18 @@ class PdfTextSearchResult extends _ValueChangeNotifier { /// Indicates whether the text search context is alive for searching bool _hasResult = false; + /// Indicates whether the text search is completed or not . + bool _isSearchCompleted = false; + /// Sets the current highlighted search text index in the document. set _currentOccurrenceIndex(int num) { _currentInstanceIndex = num; - notifyPropertyChangedListeners(property: 'currentInstance'); + notifyListeners(); } + /// Gets the current highlighted search text index in the document. + int get _currentOccurrenceIndex => _currentInstanceIndex; + /// The current highlighted search text index in the document. int get currentInstanceIndex { return _currentInstanceIndex; @@ -4300,7 +7669,12 @@ class PdfTextSearchResult extends _ValueChangeNotifier { /// Sets the total instance of the searched text in the PDF document. set _totalSearchTextCount(int totalInstanceCount) { _totalInstanceCount = totalInstanceCount; - notifyPropertyChangedListeners(property: 'totalInstance'); + notifyListeners(); + } + + /// Gets the total instance of the searched text in the PDF document. + int get _totalSearchTextCount { + return _totalInstanceCount; } /// Indicates the total instance of the searched text in the PDF document. @@ -4311,7 +7685,7 @@ class PdfTextSearchResult extends _ValueChangeNotifier { /// Updates whether the text search context is alive for searching void _updateResult(bool hasResult) { _hasResult = hasResult; - notifyPropertyChangedListeners(property: 'result'); + notifyListeners(); } /// Indicates whether the text search context is alive for searching @@ -4319,13 +7693,24 @@ class PdfTextSearchResult extends _ValueChangeNotifier { return _hasResult; } + /// Updates whether the text search is completed or not. + void _updateSearchCompletedStatus(bool isSearchCompleted) { + _isSearchCompleted = isSearchCompleted; + notifyListeners(); + } + + /// Indicates whether the text search is completed or not. + bool get isSearchCompleted { + return _isSearchCompleted; + } + /// Moves to the next searched text instance in the document. /// /// Using this method, the [SfPdfViewer] will move to the next searched text instance /// in the document. If this method is called after reaching the last instance, /// then the first instance will be again highlighted and the process continues. void nextInstance() { - notifyPropertyChangedListeners(property: 'nextInstance'); + _notifyPropertyChangedListeners(property: 'nextInstance'); } /// Moves to the previous searched text instance in the document. @@ -4334,7 +7719,7 @@ class PdfTextSearchResult extends _ValueChangeNotifier { /// instance in the document. If this method is called from the first instance, /// then the last (previous) instance will be highlighted and the process continues. void previousInstance() { - notifyPropertyChangedListeners(property: 'previousInstance'); + _notifyPropertyChangedListeners(property: 'previousInstance'); } /// Clears the [PdfTextSearchResult] object and cancels the search process. @@ -4343,28 +7728,28 @@ class PdfTextSearchResult extends _ValueChangeNotifier { /// the [PdfTextSearchResult] object will be cleared, which in turn changes the /// [hasResult] property value to 'false`. void clear() { - notifyPropertyChangedListeners(property: 'clear'); + _notifyPropertyChangedListeners(property: 'clear'); } } -/// _ValueChangeNotifier class listener invoked whenever PdfViewerController property changed. -class _ValueChangeNotifier { - late _PdfControllerListener listener; +/// _ValueChangeNotifier mixin listener invoked whenever PdfViewerController property changed. +mixin _ValueChangeNotifier { + late _PdfControllerListener _listener; final ObserverList<_PdfControllerListener> _listeners = ObserverList<_PdfControllerListener>(); - void addListener(_PdfControllerListener listener) { + void _addListener(_PdfControllerListener listener) { _listeners.add(listener); } - void removeListener(_PdfControllerListener listener) { + void _removeListener(_PdfControllerListener listener) { _listeners.remove(listener); } @protected - void notifyPropertyChangedListeners({String? property}) { - for (listener in _listeners) { - listener(property: property); + void _notifyPropertyChangedListeners({String? property}) { + for (_listener in _listeners) { + _listener(property: property); } } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/text_extraction/text_extraction.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/text_extraction/text_extraction.dart new file mode 100644 index 000000000..a413536bf --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/text_extraction/text_extraction.dart @@ -0,0 +1,70 @@ +import 'dart:isolate'; + +import 'package:async/async.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; + +/// Extracts the text from the PDF document. +class TextExtractionEngine { + /// Initializes the text extraction engine. + TextExtractionEngine(this._document); + + final PdfDocument _document; + + Isolate? _isolate; + SendPort? _sendPort; + + final ReceivePort _receivePort = ReceivePort(); + late final StreamQueue _receiveQueue = StreamQueue( + _receivePort, + ); + Map _textMap = {}; + + /// Extracts all the text from the PDF document. + Future> extractText() async { + try { + if (_isolate == null) { + _isolate = await Isolate.spawn(_extractText, _receivePort.sendPort); + _sendPort = await _receiveQueue.next; + } + _sendPort!.send(_document); + _textMap = await _receiveQueue.next; + _isolate?.kill(); + return _textMap; + } catch (e) { + return {}; + } + } + + /// Disposes the resources used by the text extraction engine. + void dispose() { + _isolate?.kill(priority: Isolate.immediate); + _receiveQueue.cancel(immediate: true); + _receivePort.close(); + _textMap.clear(); + } +} + +/// Extracts the text from all the pages in the PDF document. +void _extractText(SendPort sendPort) { + final ReceivePort receivePort = ReceivePort(); + sendPort.send(receivePort.sendPort); + + receivePort.listen((dynamic message) { + if (message is PdfDocument) { + final Map textMap = {}; + final PdfTextExtractor textExtractor = PdfTextExtractor(message); + final int pageCount = message.pages.count; + + for (int i = 0; i < pageCount; i++) { + final String text = + textExtractor + .extractText(startPageIndex: i) + // Remove the new line characters. + .replaceAll(RegExp(r'\r?\n'), '') + .toLowerCase(); + textMap[i] = text; + } + sendPort.send(textMap); + } + }); +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart new file mode 100644 index 000000000..0eae9cefd --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/theme/theme.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +/// Holds the default values if Material 3 is not enabled. +class SfPdfViewerThemeDataM2 extends SfPdfViewerThemeData { + /// Creates a [SfPdfViewerThemeDataM2] that holds the default values + /// for [SfPdfViewer] when Material 3 is not enabled. + SfPdfViewerThemeDataM2(this.context); + + /// The [BuildContext] of the widget. + final BuildContext context; + + /// The [SfColorScheme] of the widget. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + late final PdfScrollHeadStyle _scrollHeadStyle = PdfScrollHeadStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF424242), + ); + + late final PdfBookmarkViewStyle _bookmarkViewStyle = PdfBookmarkViewStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF212121), + closeIconColor: colorScheme.onSurfaceVariant[138], + backIconColor: colorScheme.onSurfaceVariant[138], + headerBarColor: + colorScheme.brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF424242), + navigationIconColor: colorScheme.onSurfaceVariant[138], + selectionColor: colorScheme.primaryContainer[20], + titleSeparatorColor: colorScheme.outlineVariant[41], + ); + + late final PdfPaginationDialogStyle _paginationDialogStyle = + PdfPaginationDialogStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242), + ); + + late final PdfHyperlinkDialogStyle _hyperlinkDialogStyle = + PdfHyperlinkDialogStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242), + closeIconColor: colorScheme.onSurfaceVariant[153], + ); + + late final PdfPasswordDialogStyle _passwordDialogStyle = + PdfPasswordDialogStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? Colors.white + : const Color(0xFF424242), + closeIconColor: colorScheme.onSurfaceVariant[153], + visibleIconColor: colorScheme.onSurfaceVariant[153], + ); + + @override + Color? get backgroundColor => + colorScheme.brightness == Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030); + + @override + PdfScrollHeadStyle get scrollHeadStyle => _scrollHeadStyle; + @override + PdfBookmarkViewStyle get bookmarkViewStyle => _bookmarkViewStyle; + + @override + PdfPaginationDialogStyle get paginationDialogStyle => _paginationDialogStyle; + + @override + PdfHyperlinkDialogStyle get hyperlinkDialogStyle => _hyperlinkDialogStyle; + + @override + PdfPasswordDialogStyle get passwordDialogStyle => _passwordDialogStyle; +} + +/// Holds the default values if Material 3 is enabled. +class SfPdfViewerThemeDataM3 extends SfPdfViewerThemeData { + /// Creates a [SfPdfViewerThemeDataM3] that holds the default values + /// for [SfPdfViewer] when Material 3 is enabled. + SfPdfViewerThemeDataM3(this.context); + + /// The [BuildContext] of the widget. + final BuildContext context; + + /// The [SfColorScheme] of the widget. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + late final PdfScrollHeadStyle _scrollHeadStyle = PdfScrollHeadStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1), + ); + + late final PdfScrollStatusStyle _scrollStatusStyle = PdfScrollStatusStyle( + backgroundColor: colorScheme.inverseSurface[255], + pageInfoTextStyle: TextStyle(color: colorScheme.onInverseSurface[255]), + ); + + late final PdfBookmarkViewStyle _bookmarkViewStyle = PdfBookmarkViewStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1), + closeIconColor: colorScheme.onSurfaceVariant[138], + backIconColor: colorScheme.onSurfaceVariant[138], + headerBarColor: + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(247, 242, 251, 1) + : const Color.fromRGBO(37, 35, 42, 1), + navigationIconColor: colorScheme.onSurfaceVariant[138], + selectionColor: colorScheme.primaryContainer[20], + titleSeparatorColor: colorScheme.outlineVariant[41], + ); + + late final PdfPaginationDialogStyle _paginationDialogStyle = + PdfPaginationDialogStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1), + ); + + late final PdfHyperlinkDialogStyle _hyperlinkDialogStyle = + PdfHyperlinkDialogStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1), + closeIconColor: colorScheme.onSurfaceVariant[153], + ); + + late final PdfPasswordDialogStyle _passwordDialogStyle = + PdfPasswordDialogStyle( + backgroundColor: + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1), + closeIconColor: colorScheme.onSurfaceVariant[153], + visibleIconColor: colorScheme.onSurfaceVariant[153], + ); + + @override + Color? get backgroundColor => + colorScheme.brightness == Brightness.light + ? const Color.fromRGBO(237, 230, 243, 1) + : const Color.fromRGBO(50, 46, 58, 1); + + @override + Color? get progressBarColor => colorScheme.primary; + + @override + PdfScrollHeadStyle get scrollHeadStyle => _scrollHeadStyle; + + @override + PdfScrollStatusStyle get scrollStatusStyle => _scrollStatusStyle; + + @override + PdfBookmarkViewStyle get bookmarkViewStyle => _bookmarkViewStyle; + + @override + PdfPaginationDialogStyle get paginationDialogStyle => _paginationDialogStyle; + + @override + PdfHyperlinkDialogStyle get hyperlinkDialogStyle => _hyperlinkDialogStyle; + + @override + PdfPasswordDialogStyle get passwordDialogStyle => _passwordDialogStyle; +} diff --git a/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer.podspec b/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer.podspec index 848151893..8156979b4 100644 --- a/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer.podspec +++ b/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer.podspec @@ -5,17 +5,17 @@ Pod::Spec.new do |s| s.name = 'syncfusion_flutter_pdfviewer' s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' + s.summary = 'A new Flutter plugin project.' s.description = <<-DESC -A new flutter plugin project. +A new Flutter plugin project. DESC - s.homepage = 'https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer' + s.homepage = 'https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'syncfusion_flutter_pdfviewer/Sources/syncfusion_flutter_pdfviewer/**/*' s.dependency 'FlutterMacOS' - s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' diff --git a/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer/Package.swift b/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer/Package.swift new file mode 100644 index 000000000..14b97926e --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/macos/syncfusion_flutter_pdfviewer/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "syncfusion_flutter_pdfviewer", + platforms: [ + .macOS("10.14") + ], + products: [ + .library(name: "syncfusion-flutter-pdfviewer", targets: ["syncfusion_flutter_pdfviewer"]) + ], + dependencies: [], + targets: [ + .target( + name: "syncfusion_flutter_pdfviewer", + dependencies: [], + resources: [] + ) + ] +) \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml index fa56fd0c8..3f65b964f 100644 --- a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml @@ -1,19 +1,14 @@ name: syncfusion_flutter_pdfviewer description: Flutter PDF Viewer library is used to display a PDF document seamlessly and efficiently. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=1.20.0" + sdk: ^3.7.0-0 + flutter: '>=1.20.0' flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' and Android 'package' identifiers should not ordinarily - # be modified. They are used by the tooling to maintain consistency when - # adding or updating assets for this project. - plugin: platforms: android: @@ -28,25 +23,68 @@ flutter: windows: default_package: syncfusion_pdfviewer_windows + assets: + - assets/icons/dark/ + - assets/icons/light/ + fonts: + - family: RobotoMono + fonts: + - asset: assets/fonts/RobotoMono-Regular.ttf + dependencies: flutter: sdk: flutter vector_math: ^2.1.0 + web: ^1.0.0 async: ^2.5.0 - http: ^0.13.0 - uuid: ^3.0.5 + http: ^1.0.0 + uuid: ^4.1.0 + device_info_plus: ^11.0.0 + intl: '>=0.18.1 <0.21.0' syncfusion_pdfviewer_platform_interface: - path: ../syncfusion_pdfviewer_platform_interface + path: ../syncfusion_flutter_pdfviewer_platform_interface + syncfusion_pdfviewer_web: path: ../syncfusion_pdfviewer_web + syncfusion_pdfviewer_macos: path: ../syncfusion_pdfviewer_macos + syncfusion_pdfviewer_windows: path: ../syncfusion_pdfviewer_windows + syncfusion_flutter_core: path: ../syncfusion_flutter_core + syncfusion_flutter_pdf: path: ../syncfusion_flutter_pdf - - + + syncfusion_flutter_signaturepad: + path: ../syncfusion_flutter_signaturepad + + url_launcher: ^6.1.0 + + + +screenshots: + - description: 'This screenshot shows the PdfViewer widget, which displays a PDF document.' + path: screenshots/flutter-pdf-viewer.png + - description: 'This screenshot shows the password alert dialog in the PdfViewer widget, which is used to enter the password to open the password-protected PDF document.' + path: screenshots/flutter-pdf-viewer-password-protected.png + - description: 'This screenshot shows the page navigation in the PdfViewer widget that is used to navigate to the desired page.' + path: screenshots/flutter-pdf-viewer-page-navigation.png + - description: 'This screenshot shows the bookmark navigation in the PdfViewer widget that is used to navigate to the desired bookmark.' + path: screenshots/flutter-pdf-viewer-bookmark-navigation.png + - description: 'This screenshot shows the selection and copying of text in the PdfViewer widget.' + path: screenshots/flutter-pdf-viewer-text-selection.png + - description: 'This screenshot shows the text search in the PdfViewer widget.' + path: screenshots/flutter-pdf-viewer-text-search.png + - description: 'This screenshot shows the text markup annotation support in the PdfViewer widget.' + path: screenshots/flutter-pdf-viewer-text-markup.png + - description: 'This screenshot shows the form filling support in the PdfViewer widget.' + path: screenshots/flutter-pdf-viewer-form-filling.gif + - description: 'This screenshot shows theme support in the PdfViewer widget.' + path: screenshots/flutter-pdf-viewer-dark-theme.png + - description: 'This screenshot shows the localization support in the PdfViewer widget.' + path: screenshots/flutter-pdf-viewer-localization.png diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-bookmark-navigation.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-bookmark-navigation.png new file mode 100644 index 000000000..8caaa6c19 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-bookmark-navigation.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-dark-theme.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-dark-theme.png new file mode 100644 index 000000000..62161e1c1 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-dark-theme.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-form-filling.gif b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-form-filling.gif new file mode 100644 index 000000000..0e2072e22 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-form-filling.gif differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-localization.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-localization.png new file mode 100644 index 000000000..0f586dabf Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-localization.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-page-navigation.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-page-navigation.png new file mode 100644 index 000000000..85588fe48 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-page-navigation.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-password-protected.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-password-protected.png new file mode 100644 index 000000000..8c38ab866 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-password-protected.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-markup.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-markup.png new file mode 100644 index 000000000..828a3f497 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-markup.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-search.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-search.png new file mode 100644 index 000000000..41c9b00e5 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-search.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-selection.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-selection.png new file mode 100644 index 000000000..87ec269b8 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer-text-selection.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer.png b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer.png new file mode 100644 index 000000000..79aba3fe0 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/screenshots/flutter-pdf-viewer.png differ diff --git a/packages/syncfusion_pdfviewer_platform_interface/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer_platform_interface/CHANGELOG.md similarity index 100% rename from packages/syncfusion_pdfviewer_platform_interface/CHANGELOG.md rename to packages/syncfusion_flutter_pdfviewer_platform_interface/CHANGELOG.md diff --git a/packages/syncfusion_pdfviewer_platform_interface/LICENSE b/packages/syncfusion_flutter_pdfviewer_platform_interface/LICENSE similarity index 100% rename from packages/syncfusion_pdfviewer_platform_interface/LICENSE rename to packages/syncfusion_flutter_pdfviewer_platform_interface/LICENSE diff --git a/packages/syncfusion_pdfviewer_platform_interface/README.md b/packages/syncfusion_flutter_pdfviewer_platform_interface/README.md similarity index 100% rename from packages/syncfusion_pdfviewer_platform_interface/README.md rename to packages/syncfusion_flutter_pdfviewer_platform_interface/README.md 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..c3d5c4ffe --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/analysis_options.yaml @@ -0,0 +1,8 @@ +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 + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/README.md b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/README.md new file mode 100644 index 000000000..a13562602 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/analysis_options.yaml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/build.gradle b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/build.gradle new file mode 100644 index 000000000..5fe3c929f --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/build.gradle @@ -0,0 +1,68 @@ +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 flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + 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_pdfviewer_platform_interface/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..3f41384db --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..e793a000d --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..3db14bb53 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d460d1e92 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/build.gradle b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/build.gradle new file mode 100644 index 000000000..4256f9173 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +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_pdfviewer_platform_interface/example/android/gradle.properties b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bc6a58afd --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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-6.7-all.zip diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/settings.gradle b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/.gitignore b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.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/ephemeral/ +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_pdfviewer_platform_interface/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..8d4492f97 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + 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_pdfviewer_platform_interface/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..6edd238e7 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + 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 = 1300; + 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; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + 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_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + 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; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + 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; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + 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_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c87d15a33 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/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_pdfviewer_platform_interface/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Info.plist new file mode 100644 index 000000000..5baf7a1cc --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + 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_pdfviewer_platform_interface/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart new file mode 100644 index 000000000..f1b6ed12b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/lib/main.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp( + MaterialApp( + title: 'Syncfusion PDF Viewer Demo', + theme: ThemeData(useMaterial3: false), + home: const HomePage(), + ), + ); +} + +/// Represents Homepage for Navigation +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + _HomePage createState() => _HomePage(); +} + +class _HomePage extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Syncfusion Flutter PDF Viewer')), + body: const Text('PDF Viewer'), + ); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/pubspec.yaml new file mode 100644 index 000000000..cc6ec2603 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: syncfusion_pdfviewer_platform_interface_example +description: Demonstrates how to use the syncfusion_pdfviewer_platform_interface plugin. +publish_to: 'none' + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/favicon.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/favicon.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/icons/Icon-192.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/icons/Icon-192.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/icons/Icon-512.png b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/icons/Icon-512.png differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/index.html b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/index.html new file mode 100644 index 000000000..bd74f3c23 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + example + + + + + + + + + + + \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/manifest.json b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/manifest.json new file mode 100644 index 000000000..096edf8fe --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "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" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/.gitignore b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/CMakeLists.txt new file mode 100644 index 000000000..1633297a0 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +set(BINARY_NAME "example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..b2e4bd8d6 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..b93c4c30c --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..de2d8916b --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/Runner.rc new file mode 100644 index 000000000..5fdea291c --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..b43b9095e --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/flutter_window.h b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/main.cpp b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/main.cpp new file mode 100644 index 000000000..bcb57b0e2 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/resource.h b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/resources/app_icon.ico b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000..c04e20caf Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/resources/app_icon.ico differ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..c977c4a42 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/utils.cpp new file mode 100644 index 000000000..d19bdbbcc --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/utils.h b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..c10f08dc7 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/win32_window.h new file mode 100644 index 000000000..17ba43112 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/syncfusion_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart similarity index 100% rename from packages/syncfusion_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart rename to packages/syncfusion_flutter_pdfviewer_platform_interface/lib/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..45d4afb6d --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart @@ -0,0 +1,78 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_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, + String documentID, [ + String? password, + ]) async { + return _channel.invokeMethod('initializePdfRenderer', { + 'documentBytes': documentBytes, + 'documentID': documentID, + 'password': password, + }); + } + + /// Gets the height of all pages in the document. + @override + Future getPagesHeight(String documentID) async { + return _channel.invokeMethod('getPagesHeight', documentID); + } + + /// Gets the width of all pages in the document. + @override + Future getPagesWidth(String documentID) async { + return _channel.invokeMethod('getPagesWidth', documentID); + } + + /// Gets the image bytes of the specified page from the document at the specified width and height. + @override + Future getPage( + int pageNumber, + int width, + int height, + String documentID, + ) async { + return _channel.invokeMethod('getPage', { + 'index': pageNumber, + 'width': width, + 'height': height, + 'documentID': documentID, + }); + } + + /// Gets the image's bytes information of the specified portion of the page + @override + Future getTileImage( + int pageNumber, + double currentScale, + double x, + double y, + double width, + double height, + String documentID, + ) async { + return _channel.invokeMethod('getTileImage', { + 'pageNumber': pageNumber, + 'scale': currentScale, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + 'documentID': documentID, + }); + } + + /// Closes the PDF document. + @override + Future closeDocument(String documentID) async { + return _channel.invokeMethod('closeDocument', documentID); + } +} 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..ae7fdcc4c --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart @@ -0,0 +1,93 @@ +library syncfusion_pdfviewer_platform_interface; + +import 'dart:async'; +import 'dart:typed_data'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:syncfusion_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, + String documentID, [ + String? password, + ]) async { + throw UnimplementedError( + 'initializePdfRenderer() has not been implemented.', + ); + } + + /// Gets the height of all pages in the document. + Future getPagesHeight(String documentID) async { + throw UnimplementedError('getPagesHeight() has not been implemented.'); + } + + /// Gets the width of all pages in the document. + Future getPagesWidth(String documentID) async { + throw UnimplementedError('getPagesWidth() has not been implemented.'); + } + + /// Gets the image's bytes information of the specified page. + Future getImage( + int pageNumber, + double scale, + String documentID, + ) async { + throw UnimplementedError('getImage() has not been implemented.'); + } + + /// Gets the image bytes of the specified page from the document at the specified width and height. + Future getPage( + int pageNumber, + int width, + int height, + String documentID, + ) async { + throw UnimplementedError('getPage() has not been implemented.'); + } + + /// Gets the image's bytes information of the specified portion of the page. + Future getTileImage( + int pageNumber, + double scale, + double x, + double y, + double width, + double height, + String documentID, + ) async { + throw UnimplementedError('getTileImage() has not been implemented.'); + } + + /// Closes the PDF document. + Future closeDocument(String documentID) 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..4eef9f1f4 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/pubspec.yaml @@ -0,0 +1,14 @@ + +name: syncfusion_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: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_pdfviewer_platform_interface + +environment: + sdk: ^3.7.0-0 + flutter: '>=1.20.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.0 diff --git a/packages/syncfusion_flutter_signaturepad/CHANGELOG.md b/packages/syncfusion_flutter_signaturepad/CHANGELOG.md index 42a961c22..31d80b500 100644 --- a/packages/syncfusion_flutter_signaturepad/CHANGELOG.md +++ b/packages/syncfusion_flutter_signaturepad/CHANGELOG.md @@ -1,5 +1,60 @@ ## Unreleased +**General** + +* The compatible version of our Flutter signaturepad widget has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter signaturepad widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter signaturepad example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter signaturepad example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter signaturepad widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter signaturepad widget has been updated to Flutter SDK 3.24.0. + +## [25.1.35] - 03/15/2024 + +**General** + +* Provided th​e Material 3 themes support. + +## [21.1.35] - 03/23/2023 + +**Bugs** + +* #FB42032: The vertical signing works properly when the signature pad is wrapped inside a scrollable widget. + ### Features * **onDraw callback** - Get the offset of each stroke in the Signature Pad with `onDraw` callback. @@ -20,4 +75,4 @@ Initial release. * **Signature stroke color customization** - The widget allows you to choose the stroke color for the signature. * **Signature stroke width customization** - The widget allows you to set the minimum and maximum stroke widths for the signature. * **SignaturePad background color customization** - The widget allows you to set the background color for the SignaturePad. -* **Save as image** - The widget provides the option to save the drawn signature as an image. This converted image can be embedded in documents to denote a signature. \ No newline at end of file +* **Save as image** - The widget provides the option to save the drawn signature as an image. This converted image can be embedded in documents to denote a signature. diff --git a/packages/syncfusion_flutter_signaturepad/LICENSE b/packages/syncfusion_flutter_signaturepad/LICENSE index 70f791939..ac606465b 100644 --- a/packages/syncfusion_flutter_signaturepad/LICENSE +++ b/packages/syncfusion_flutter_signaturepad/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter SignaturePad 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. +Syncfusion® Flutter SignaturePad 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +The Syncfusion® license that contains the terms and conditions can be found at https://www.syncfusion.com/content/downloads/syncfusion_license.pdf diff --git a/packages/syncfusion_flutter_signaturepad/README.md b/packages/syncfusion_flutter_signaturepad/README.md index d8d07239c..aecbf5fb9 100644 --- a/packages/syncfusion_flutter_signaturepad/README.md +++ b/packages/syncfusion_flutter_signaturepad/README.md @@ -1,14 +1,14 @@ ![syncfusion_flutter_signaturepad](https://cdn.syncfusion.com/content/images/FTControl/signature_pad_banner.png) -# Syncfusion Flutter SignaturePad +# Syncfusion® Flutter SignaturePad -Syncfusion Flutter SignaturePad library is written in Dart for capturing smooth and more realistic signatures and save it as an image to sync across devices and documents that needs signatures. +Syncfusion® Flutter SignaturePad library is written in Dart for capturing smooth and more realistic signatures and save it as an image to sync across devices and documents that needs signatures. ## Overview 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 [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. +**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. ![Flutter signature drawing](https://cdn.syncfusion.com/content/images/FTControl/signaturepad_overview.gif) @@ -20,7 +20,7 @@ This library is used to capture a signature through drawing gestures. You can us - [Installation](#installation) - [SignaturePad Getting Started](#getting-started) - [Support and feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## SignaturePad features @@ -34,23 +34,20 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

## Other useful links -Take a look at the following to learn more about the Syncfusion Flutter SignaturePad: +Take a look at the following to learn more about the Syncfusion® Flutter SignaturePad: -* [Syncfusion Flutter SignaturePad product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter SignaturePad product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation for SignaturePad](https://help.syncfusion.com/flutter/introduction/overview) * [Knowledge base](https://www.syncfusion.com/kb) @@ -254,11 +251,11 @@ You can clear the signature drawn in the SignaturePad using the clear() method a ## Support and feedback -* If you have any questions, you can reach out to our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post question on our [community forums](https://www.syncfusion.com/forums) . Submit a feature request or a bug through our [feedback portal](https://www.syncfusion.com/feedback/flutter). +* If you have any questions, you can reach out to our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) or post question on our [community forums](https://www.syncfusion.com/forums) . Submit a feature request or a bug 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 +## 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. +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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_signaturepad/analysis_options.yaml b/packages/syncfusion_flutter_signaturepad/analysis_options.yaml index 91f995863..7bd9823d9 100644 --- a/packages/syncfusion_flutter_signaturepad/analysis_options.yaml +++ b/packages/syncfusion_flutter_signaturepad/analysis_options.yaml @@ -1,7 +1 @@ -include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore - lines_longer_than_80_chars: ignore - invalid_dependency: ignore +include: package:syncfusion_flutter_core/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_signaturepad/example/README.md b/packages/syncfusion_flutter_signaturepad/example/README.md new file mode 100644 index 000000000..99ac62b73 --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/README.md @@ -0,0 +1,16 @@ +# signaturepadexample + +A new Flutter application. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_flutter_signaturepad/example/android/.gitignore b/packages/syncfusion_flutter_signaturepad/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/.gitignore +++ b/packages/syncfusion_flutter_signaturepad/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/build.gradle b/packages/syncfusion_flutter_signaturepad/example/android/app/build.gradle index 6ff70a493..1c380c08f 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.signaturepad_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.syncfusion.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.signaturepad_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_signaturepad/example/android/app/src/debug/AndroidManifest.xml index 630eb0ec7..399f6981d 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/AndroidManifest.xml index b0c36f687..374e84e2b 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/kotlin/com/example/signaturepad_example/MainActivity.kt b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/kotlin/com/example/signaturepad_example/MainActivity.kt new file mode 100644 index 000000000..dae47f03d --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/kotlin/com/example/signaturepad_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.signaturepad_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_signaturepad/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_signaturepad/example/android/build.gradle b/packages/syncfusion_flutter_signaturepad/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/build.gradle +++ b/packages/syncfusion_flutter_signaturepad/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_signaturepad/example/android/gradle.properties b/packages/syncfusion_flutter_signaturepad/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/gradle.properties +++ b/packages/syncfusion_flutter_signaturepad/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_signaturepad/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_signaturepad/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_signaturepad/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_signaturepad/example/android/settings.gradle b/packages/syncfusion_flutter_signaturepad/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_signaturepad/example/android/settings.gradle +++ b/packages/syncfusion_flutter_signaturepad/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_signaturepad/example/ios/.gitignore b/packages/syncfusion_flutter_signaturepad/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_signaturepad/example/ios/.gitignore +++ b/packages/syncfusion_flutter_signaturepad/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_signaturepad/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_signaturepad/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_signaturepad/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_signaturepad/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.pbxproj index 7deca8407..6056f6c8a 100644 --- a/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -101,6 +126,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -117,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -127,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -145,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -171,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -185,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -200,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -211,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -235,6 +315,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -264,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -272,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -289,17 +371,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadexample; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -307,10 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -340,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -354,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -366,6 +492,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -395,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -403,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -421,17 +550,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadexample; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -448,17 +572,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadexample; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -469,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -490,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_signaturepad/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Signaturepad Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - signaturepadexample + signaturepad_example CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents +
diff --git a/packages/syncfusion_flutter_signaturepad/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_signaturepad/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_signaturepad/example/lib/main.dart b/packages/syncfusion_flutter_signaturepad/example/lib/main.dart index 52279f860..c08ba5152 100644 --- a/packages/syncfusion_flutter_signaturepad/example/lib/main.dart +++ b/packages/syncfusion_flutter_signaturepad/example/lib/main.dart @@ -10,10 +10,7 @@ void main() { class SignaturePadApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'SfSignaturePad Demo', - home: _MyHomePage(), - ); + return MaterialApp(title: 'SfSignaturePad Demo', home: _MyHomePage()); } } @@ -37,8 +34,9 @@ class _MyHomePageState extends State<_MyHomePage> { } 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); await Navigator.of(context).push( MaterialPageRoute( @@ -60,32 +58,39 @@ class _MyHomePageState extends State<_MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( - body: Column( - children: [ + body: Column( + children: [ Padding( - padding: EdgeInsets.all(10), - child: Container( - child: SfSignaturePad( - key: signatureGlobalKey, - backgroundColor: Colors.white, - strokeColor: Colors.black, - minimumStrokeWidth: 1.0, - maximumStrokeWidth: 4.0), - decoration: - BoxDecoration(border: Border.all(color: Colors.grey)))), - SizedBox(height: 10), - Row(children: [ - TextButton( - child: Text('ToImage'), - onPressed: _handleSaveButtonPressed, + padding: EdgeInsets.all(10), + child: Container( + child: SfSignaturePad( + key: signatureGlobalKey, + backgroundColor: Colors.white, + strokeColor: Colors.black, + minimumStrokeWidth: 1.0, + maximumStrokeWidth: 4.0, + ), + decoration: BoxDecoration(border: Border.all(color: Colors.grey)), ), - TextButton( - child: Text('Clear'), - onPressed: _handleClearButtonPressed, - ) - ], mainAxisAlignment: MainAxisAlignment.spaceEvenly) + ), + SizedBox(height: 10), + Row( + children: [ + TextButton( + child: Text('ToImage'), + onPressed: _handleSaveButtonPressed, + ), + TextButton( + child: Text('Clear'), + onPressed: _handleClearButtonPressed, + ), + ], + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + ), ], - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center)); + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + ), + ); } } diff --git a/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_signaturepad/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/.gitignore b/packages/syncfusion_flutter_signaturepad/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/.gitignore +++ b/packages/syncfusion_flutter_signaturepad/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..1d4afc15c 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* signaturepad_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "signaturepad_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* signaturepad_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* signaturepad_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/signaturepad_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/signaturepad_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/signaturepad_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/signaturepad_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/signaturepad_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/signaturepad_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..990ee890f 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_signaturepad/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Configs/AppInfo.xcconfig index d584e0d59..b1b1e28f1 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = signaturepad_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.example +PRODUCT_BUNDLE_IDENTIFIER = com.syncfusion.signaturepadExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.syncfusion. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.syncfusion. All rights reserved. diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_signaturepad/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_signaturepad/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_signaturepad/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_signaturepad/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml b/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml index c97e4126e..1c87ebbad 100644 --- a/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml +++ b/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml @@ -4,13 +4,13 @@ description: This project demonstrates how to use Syncfusion Flutter SignaturePa version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - cupertino_icons: ^0.1.3 + cupertino_icons: '>=0.1.3 <1.0.7' syncfusion_flutter_signaturepad: path: ../ @@ -18,44 +18,7 @@ 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_signaturepad/example/web/index.html b/packages/syncfusion_flutter_signaturepad/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_flutter_signaturepad/example/web/index.html +++ b/packages/syncfusion_flutter_signaturepad/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_flutter_signaturepad/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_signaturepad/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_signaturepad/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_signaturepad/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart b/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart index 6fabd2890..a62052cc4 100644 --- a/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart +++ b/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart @@ -5,6 +5,7 @@ import 'dart:ui' as ui; import 'package:flutter/gestures.dart' show + DeviceGestureSettings, DragStartBehavior, GestureArenaTeam, HorizontalDragGestureRecognizer, @@ -161,16 +162,16 @@ class SfSignaturePad extends StatefulWidget { /// print("Signature has been completed in Signature Pad"); /// }); /// ``` - const SfSignaturePad( - {Key? key, - this.minimumStrokeWidth = _kMinimumStrokeWidth, - this.maximumStrokeWidth = _kMaximumStrokeWidth, - this.backgroundColor, - this.strokeColor, - this.onDrawStart, - this.onDraw, - this.onDrawEnd}) - : super(key: key); + const SfSignaturePad({ + Key? key, + this.minimumStrokeWidth = _kMinimumStrokeWidth, + this.maximumStrokeWidth = _kMaximumStrokeWidth, + this.backgroundColor, + this.strokeColor, + this.onDrawStart, + this.onDraw, + this.onDrawEnd, + }) : super(key: key); /// The minimum width of the signature stroke. /// @@ -327,13 +328,13 @@ class SfSignaturePadState extends State { /// ``` List toPathList() { final RenderObject? signatureRenderBox = context.findRenderObject(); - List _paths = []; + List paths = []; if (signatureRenderBox is RenderSignaturePad) { - _paths = List.unmodifiable(signatureRenderBox._pathCollection); + paths = List.unmodifiable(signatureRenderBox._pathCollection); } - return _paths; + return paths; } /// Saves the signature as an image. @@ -368,8 +369,9 @@ class SfSignaturePadState extends State { Future toImage({double pixelRatio = 1.0}) { final RenderObject? signatureRenderBox = context.findRenderObject(); // ignore: avoid_as - return (signatureRenderBox! as RenderSignaturePad) - .toImage(pixelRatio: pixelRatio); + return (signatureRenderBox! as RenderSignaturePad).toImage( + pixelRatio: pixelRatio, + ); } /// Clears all the signature strokes in the [SfSignaturePad]. @@ -451,18 +453,19 @@ class SfSignaturePadState extends State { Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; - final Color _backgroundColor = widget.backgroundColor ?? Colors.transparent; - final Color _strokeColor = + final Color backgroundColor = widget.backgroundColor ?? Colors.transparent; + final Color strokeColor = widget.strokeColor ?? (isDarkTheme ? Colors.white : Colors.black); return _SfSignaturePadRenderObjectWidget( - minimumStrokeWidth: widget.minimumStrokeWidth, - maximumStrokeWidth: widget.maximumStrokeWidth, - backgroundColor: _backgroundColor, - strokeColor: _strokeColor, - onDrawStart: widget.onDrawStart, - onDraw: widget.onDraw, - onDrawEnd: widget.onDrawEnd); + minimumStrokeWidth: widget.minimumStrokeWidth, + maximumStrokeWidth: widget.maximumStrokeWidth, + backgroundColor: backgroundColor, + strokeColor: strokeColor, + onDrawStart: widget.onDrawStart, + onDraw: widget.onDraw, + onDrawEnd: widget.onDrawEnd, + ); } } @@ -496,12 +499,15 @@ class _SfSignaturePadRenderObjectWidget extends LeafRenderObjectWidget { onDrawEnd: onDrawEnd, onDraw: onDraw, onDrawStart: onDrawStart, + gestureSettings: MediaQuery.of(context).gestureSettings, ); } @override void updateRenderObject( - BuildContext context, RenderSignaturePad renderObject) { + BuildContext context, + RenderSignaturePad renderObject, + ) { renderObject ..minimumStrokeWidth = minimumStrokeWidth ..maximumStrokeWidth = maximumStrokeWidth @@ -516,52 +522,61 @@ class _SfSignaturePadRenderObjectWidget extends LeafRenderObjectWidget { /// A render object for [SfSignaturePad] widget. class RenderSignaturePad extends RenderBox { /// Creates a new instance of [RenderSignaturePad]. - RenderSignaturePad( - {required double minimumStrokeWidth, - required double maximumStrokeWidth, - required Color backgroundColor, - required Color strokeColor, - SignatureOnDrawStartCallback? onDrawStart, - SignatureDrawCallback? onDraw, - VoidCallback? onDrawEnd}) - : _minimumStrokeWidth = minimumStrokeWidth, - _maximumStrokeWidth = maximumStrokeWidth, - _backgroundColor = backgroundColor, - _strokeColor = strokeColor, - _onDrawStart = onDrawStart, - _onDraw = onDraw, - _gestureArenaTeam = GestureArenaTeam(), - _onDrawEnd = onDrawEnd { - _panGestureRecognizer = PanGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..dragStartBehavior = DragStartBehavior.down; - - _verticalDragGestureRecognizer = VerticalDragGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _dragStart; - - _horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = _gestureArenaTeam - ..onStart = _dragStart; + RenderSignaturePad({ + required double minimumStrokeWidth, + required double maximumStrokeWidth, + required Color backgroundColor, + required Color strokeColor, + required DeviceGestureSettings gestureSettings, + SignatureOnDrawStartCallback? onDrawStart, + SignatureDrawCallback? onDraw, + VoidCallback? onDrawEnd, + }) : _minimumStrokeWidth = minimumStrokeWidth, + _maximumStrokeWidth = maximumStrokeWidth, + _backgroundColor = backgroundColor, + _strokeColor = strokeColor, + _onDrawStart = onDrawStart, + _onDraw = onDraw, + _gestureArenaTeam = GestureArenaTeam(), + _onDrawEnd = onDrawEnd { + _panGestureRecognizer = + PanGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..gestureSettings = gestureSettings + ..dragStartBehavior = DragStartBehavior.down; + + _verticalDragGestureRecognizer = + VerticalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..gestureSettings = gestureSettings + ..onStart = _dragStart; + + _horizontalDragGestureRecognizer = + HorizontalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..gestureSettings = gestureSettings + ..onStart = _dragStart; _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; _gestureArenaTeam.captain = _panGestureRecognizer; - _paintStrokeStyle = Paint() - ..color = _strokeColor - ..strokeWidth = _kMaximumStrokeWidth - ..strokeCap = StrokeCap.round - ..style = PaintingStyle.fill - ..isAntiAlias = true; + _paintStrokeStyle = + Paint() + ..color = _strokeColor + ..strokeWidth = _kMaximumStrokeWidth + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.fill + ..isAntiAlias = true; - _paintBackgroundStyle = Paint() - ..color = backgroundColor - ..style = PaintingStyle.fill - ..isAntiAlias = true; + _paintBackgroundStyle = + Paint() + ..color = backgroundColor + ..style = PaintingStyle.fill + ..isAntiAlias = true; _restrictBezierPathCalculation = _minimumStrokeWidth == _maximumStrokeWidth; _data = >[]; @@ -765,21 +780,21 @@ class RenderSignaturePad extends RenderBox { @override void performLayout() { - double _width = + double width = constraints.hasBoundedWidth ? constraints.maxWidth : _kDefaultWidth; if (constraints.minWidth > _kDefaultWidth) { - _width = constraints.minWidth; + width = constraints.minWidth; } - double _height = + double height = constraints.hasBoundedHeight ? constraints.maxHeight : _kDefaultHeight; if (constraints.minHeight > _kDefaultHeight) { - _height = constraints.minHeight; + height = constraints.minHeight; } - size = Size(_width, _height); + size = Size(width, height); } void _handleDragStart(DragStartDetails details) { @@ -805,8 +820,10 @@ class RenderSignaturePad extends RenderBox { } void _handleTapUp(TapUpDetails details) { - final _TouchPoint touchPoint = - _TouchPoint(x: details.localPosition.dx, y: details.localPosition.dy); + final _TouchPoint touchPoint = _TouchPoint( + x: details.localPosition.dx, + y: details.localPosition.dy, + ); final List<_TouchPoint> newPointGroup = <_TouchPoint>[touchPoint]; if (onDrawStart != null && onDrawStart!()) { return; @@ -866,12 +883,16 @@ class RenderSignaturePad extends RenderBox { final double x = touchOffset.dx; final double y = touchOffset.dy; - final _TouchPoint point = - _TouchPoint(x: x, y: y, time: DateTime.now().millisecondsSinceEpoch); + final _TouchPoint point = _TouchPoint( + x: x, + y: y, + time: DateTime.now().millisecondsSinceEpoch, + ); final List<_TouchPoint> lastPoints = _data[_data.length - 1]; - final double distance = lastPoints.isNotEmpty - ? _distance(lastPoints[lastPoints.length - 1], point) - : 1; + final double distance = + lastPoints.isNotEmpty + ? _distance(lastPoints[lastPoints.length - 1], point) + : 1; if (distance > 0) { if (!_restrictBezierPathCalculation) { final _Bezier? curve = _calculateBezierPath(point); @@ -903,12 +924,17 @@ class RenderSignaturePad extends RenderBox { final _TouchPoint endPoint = _lastPoints[2]; final double velocity = _velocityFilterWeight * _velocity(startPoint, endPoint) + - (1 - _velocityFilterWeight) * _lastVelocity; - final double newWidth = - max(_maximumStrokeWidth / (velocity + 1), _minimumStrokeWidth); + (1 - _velocityFilterWeight) * _lastVelocity; + final double newWidth = max( + _maximumStrokeWidth / (velocity + 1), + _minimumStrokeWidth, + ); final _Bezier curve = _Bezier.fromPoints( - points: _lastPoints, start: _lastWidth, end: newWidth); + points: _lastPoints, + start: _lastWidth, + end: newWidth, + ); _lastPoints.removeAt(0); _lastVelocity = velocity; _lastWidth = newWidth; @@ -939,8 +965,10 @@ class RenderSignaturePad extends RenderBox { y += 3 * u * tt * curve.control2.y; y += ttt * curve.endPoint.y; - final double width = - min(curve.startWidth + ttt * widthDelta, _maximumStrokeWidth); + final double width = min( + curve.startWidth + ttt * widthDelta, + _maximumStrokeWidth, + ); _bezierPoints.add(_CachePoint(x: x, y: y, width: width)); _currentPath.addArc(Rect.fromLTWH(x, y, width, width), 0, 180); @@ -966,8 +994,10 @@ class RenderSignaturePad extends RenderBox { /// * [renderToContext2D], renders the signature to a HTML canvas. Future toImage({double pixelRatio = 1.0}) async { // ignore: avoid_as - return (layer! as OffsetLayer) - .toImage(Offset.zero & size, pixelRatio: pixelRatio); + return (layer! as OffsetLayer).toImage( + Offset.zero & size, + pixelRatio: pixelRatio, + ); } /// Clears the signature strokes in [RenderSignaturePad]. @@ -997,7 +1027,10 @@ class RenderSignaturePad extends RenderBox { for (int i = 0; i < points.length; i++) { final _TouchPoint basicPoint = points[i]; final _TouchPoint point = _TouchPoint( - x: basicPoint.x, y: basicPoint.y, time: basicPoint.time); + x: basicPoint.x, + y: basicPoint.y, + time: basicPoint.time, + ); if (i == 0) { _reset(); _currentPath = Path(); @@ -1023,14 +1056,14 @@ class RenderSignaturePad extends RenderBox { /// Exports the signature to html canvas. void renderToContext2D(dynamic context2D) { - final String _strokePenColor = - '${strokeColor.red},${strokeColor.green},${strokeColor.blue},${strokeColor.opacity.toStringAsFixed(2)}'; + final String strokePenColor = + '${(strokeColor.r * 255).toInt()},${(strokeColor.g * 255).toInt()},${(strokeColor.b * 255).toInt()},${strokeColor.a.toStringAsFixed(2)}'; - final String _backgroundFillColor = - '${backgroundColor.red},${backgroundColor.green},${backgroundColor.blue},${backgroundColor.opacity.toStringAsFixed(2)}'; + final String backgroundFillColor = + '${(backgroundColor.r * 255).toInt()},${(backgroundColor.g * 255).toInt()},${(backgroundColor.b * 255).toInt()},${backgroundColor.a.toStringAsFixed(2)}'; //Drawing the background of the SignaturePad - context2D.fillStyle = 'rgba($_backgroundFillColor)'; + context2D.fillStyle = 'rgba($backgroundFillColor)'; context2D.fillRect(0, 0, size.width, size.height); context2D.fill(); @@ -1038,40 +1071,58 @@ class RenderSignaturePad extends RenderBox { if (!_restrictBezierPathCalculation) { for (int i = 0; i < _dotPoints.length; i++) { - final Offset _point = _dotPoints[i]; - context2D.moveTo(_point.dx, _point.dy); - context2D.arc(_point.dx, _point.dy, - (_minimumStrokeWidth + _maximumStrokeWidth) / 2, 0, pi * 2, true); + final Offset point = _dotPoints[i]; + context2D.moveTo(point.dx, point.dy); + context2D.arc( + point.dx, + point.dy, + (_minimumStrokeWidth + _maximumStrokeWidth) / 2, + 0, + pi * 2, + true, + ); } for (int i = 0; i < _bezierPoints.length; i++) { context2D.moveTo(_bezierPoints[i].x, _bezierPoints[i].y); - context2D.arc(_bezierPoints[i].x, _bezierPoints[i].y, - _bezierPoints[i].width / 2, 0, 2 * pi, false); + context2D.arc( + _bezierPoints[i].x, + _bezierPoints[i].y, + _bezierPoints[i].width / 2, + 0, + 2 * pi, + false, + ); } - context2D.fillStyle = 'rgba($_strokePenColor)'; + context2D.fillStyle = 'rgba($strokePenColor)'; context2D.fill(); } else { for (int i = 0; i < _data.length; i++) { if (_data[i].length == 1) { - final _TouchPoint _point = _data[i][0]; - context2D.moveTo(_point.x, _point.y); - context2D.arc(_point.x, _point.y, - (_minimumStrokeWidth + _maximumStrokeWidth) / 2, 0, pi * 2, true); - context2D.fillStyle = 'rgba($_strokePenColor)'; + final _TouchPoint point = _data[i][0]; + context2D.moveTo(point.x, point.y); + context2D.arc( + point.x, + point.y, + (_minimumStrokeWidth + _maximumStrokeWidth) / 2, + 0, + pi * 2, + true, + ); + context2D.fillStyle = 'rgba($strokePenColor)'; context2D.fill(); } else { - final List<_TouchPoint> _drawPath = _data[i]; - for (int i = 0; i < _drawPath.length; i++) { - if (i < _drawPath.length - 1) { - context2D.moveTo(_drawPath[i].x, _drawPath[i].y); - context2D.lineTo(_drawPath[i + 1].x, _drawPath[i + 1].y); + final List<_TouchPoint> drawPath = _data[i]; + for (int i = 0; i < drawPath.length; i++) { + if (i < drawPath.length - 1) { + context2D.moveTo(drawPath[i].x, drawPath[i].y); + context2D.lineTo(drawPath[i + 1].x, drawPath[i + 1].y); } } context2D.lineWidth = _maximumStrokeWidth; - context2D.strokeStyle = 'rgba($_strokePenColor)'; + context2D.strokeStyle = 'rgba($strokePenColor)'; context2D.lineCap = 'round'; context2D.stroke(); } @@ -1082,52 +1133,61 @@ class RenderSignaturePad extends RenderBox { @override void paint(PaintingContext context, Offset offset) { context.pushClipRect( - needsCompositing, offset, Rect.fromLTWH(0, 0, size.width, size.height), - (PaintingContext context, Offset offset) { - final Canvas canvas = context.canvas; - - //Drawing the background of the SignaturePad - canvas.drawRect( + needsCompositing, + offset, + Rect.fromLTWH(0, 0, size.width, size.height), + (PaintingContext context, Offset offset) { + final Canvas canvas = context.canvas; + + //Drawing the background of the SignaturePad + canvas.drawRect( Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height), - _paintBackgroundStyle); - - if (_restrictBezierPathCalculation) { - _paintStrokeStyle.strokeWidth = _minimumStrokeWidth; - for (int i = 0; i < _data.length; i++) { - if (_data[i].length == 1) { - final _TouchPoint _point = _data[i][0]; - canvas.drawCircle( - Offset(_point.x, _point.y), + _paintBackgroundStyle, + ); + + if (_restrictBezierPathCalculation) { + _paintStrokeStyle.strokeWidth = _minimumStrokeWidth; + for (int i = 0; i < _data.length; i++) { + if (_data[i].length == 1) { + final _TouchPoint point = _data[i][0]; + canvas.drawCircle( + Offset(point.x, point.y), (_minimumStrokeWidth + _maximumStrokeWidth) / 2, - _paintStrokeStyle); - } else { - final List<_TouchPoint> _path = _data[i]; - for (int i = 0; i < _path.length; i++) { - if (i < _path.length - 1) { - canvas.drawLine( - Offset(_path[i].x, _path[i].y), - Offset(_path[i + 1].x, _path[i + 1].y), - _paintStrokeStyle, - ); + _paintStrokeStyle, + ); + } else { + final List<_TouchPoint> path = _data[i]; + for (int i = 0; i < path.length; i++) { + if (i < path.length - 1) { + canvas.drawLine( + Offset(path[i].x, path[i].y), + Offset(path[i + 1].x, path[i + 1].y), + _paintStrokeStyle, + ); + } } } } - } - } else { - if (_dotPoints.isNotEmpty) { - _paintStrokeStyle.strokeWidth = - (_minimumStrokeWidth + _maximumStrokeWidth) / 2; - canvas.drawPoints(ui.PointMode.points, _dotPoints, _paintStrokeStyle); - } + } else { + if (_dotPoints.isNotEmpty) { + _paintStrokeStyle.strokeWidth = + (_minimumStrokeWidth + _maximumStrokeWidth) / 2; + canvas.drawPoints( + ui.PointMode.points, + _dotPoints, + _paintStrokeStyle, + ); + } - if (_pathCollection.isNotEmpty) { - _paintStrokeStyle.strokeWidth = _maximumStrokeWidth; - for (int i = 0; i < _pathCollection.length; i++) { - canvas.drawPath(_pathCollection[i], _paintStrokeStyle); + if (_pathCollection.isNotEmpty) { + _paintStrokeStyle.strokeWidth = _maximumStrokeWidth; + for (int i = 0; i < _pathCollection.length; i++) { + canvas.drawPath(_pathCollection[i], _paintStrokeStyle); + } } } - } - }); + }, + ); } @override @@ -1138,8 +1198,14 @@ class RenderSignaturePad extends RenderBox { } class _Bezier { - _Bezier(this.startPoint, this.control2, this.control1, this.endPoint, - this.startWidth, this.endWidth); + _Bezier( + this.startPoint, + this.control2, + this.control1, + this.endPoint, + this.startWidth, + this.endWidth, + ); final _TouchPoint startPoint; final _TouchPoint control2; @@ -1148,10 +1214,11 @@ class _Bezier { final double startWidth; final double endWidth; - static _Bezier fromPoints( - {required List<_TouchPoint> points, - required double start, - required double end}) { + static _Bezier fromPoints({ + required List<_TouchPoint> points, + required double start, + required double end, + }) { final _TouchPoint c2 = calculateControlPoints(points[0], points[1], points[2])[1]; final _TouchPoint c3 = @@ -1160,16 +1227,23 @@ class _Bezier { } static List<_TouchPoint> calculateControlPoints( - _TouchPoint s1, _TouchPoint s2, _TouchPoint s3) { + _TouchPoint s1, + _TouchPoint s2, + _TouchPoint s3, + ) { final double dx1 = s1.x - s2.x; final double dy1 = s1.y - s2.y; final double dx2 = s2.x - s3.x; final double dy2 = s2.y - s3.y; - final Point m1 = - Point((s1.x + s2.x) / 2.0, (s1.y + s2.y) / 2.0); - final Point m2 = - Point((s2.x + s3.x) / 2.0, (s2.y + s3.y) / 2.0); + final Point m1 = Point( + (s1.x + s2.x) / 2.0, + (s1.y + s2.y) / 2.0, + ); + final Point m2 = Point( + (s2.x + s3.x) / 2.0, + (s2.y + s3.y) / 2.0, + ); final double l1 = sqrt(dx1 * dx1 + dy1 * dy1); final double l2 = sqrt(dx2 * dx2 + dy2 * dy2); @@ -1201,10 +1275,20 @@ class _Bezier { for (int i = 0; i <= steps; i += 1) { final double t = i / steps; - final double cx = - point(t, startPoint.x, control1.x, control2.x, endPoint.x); - final double cy = - point(t, startPoint.y, control1.y, control2.y, endPoint.y); + final double cx = point( + t, + startPoint.x, + control1.x, + control2.x, + endPoint.x, + ); + final double cy = point( + t, + startPoint.y, + control1.y, + control2.y, + endPoint.y, + ); if (i > 0) { final double xDiff = cx - px; diff --git a/packages/syncfusion_flutter_signaturepad/pubspec.yaml b/packages/syncfusion_flutter_signaturepad/pubspec.yaml index 9aa214ebf..984cce566 100644 --- a/packages/syncfusion_flutter_signaturepad/pubspec.yaml +++ b/packages/syncfusion_flutter_signaturepad/pubspec.yaml @@ -1,10 +1,14 @@ name: 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: 20.1.47 -homepage: https://github.com/syncfusion/flutter-examples +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_signaturepad +screenshots: + - description: 'This demo shows signature drawn on signature pad.' + path: screenshots/signature_pad.gif + environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: diff --git a/packages/syncfusion_flutter_signaturepad/screenshots/signature_pad.gif b/packages/syncfusion_flutter_signaturepad/screenshots/signature_pad.gif new file mode 100644 index 000000000..291428b2e Binary files /dev/null and b/packages/syncfusion_flutter_signaturepad/screenshots/signature_pad.gif differ diff --git a/packages/syncfusion_flutter_sliders/CHANGELOG.md b/packages/syncfusion_flutter_sliders/CHANGELOG.md index ebb2e5a9a..429f16d24 100644 --- a/packages/syncfusion_flutter_sliders/CHANGELOG.md +++ b/packages/syncfusion_flutter_sliders/CHANGELOG.md @@ -1,5 +1,171 @@ ## Unreleased +**General** + +* The compatible version of our Flutter sliders widget has been updated to Flutter SDK 3.32.0. + +## Slider + +**Features** + +* #FR49315 - Now, the Slider labels can be customized individually using the `onLabelCreated` property. +* #FR66359 - Now, the Slider support keyboard navigation accessibility using the Left, Right, Up, Down arrow keys, and the Tab key. + +## Range Slider + +**Features** + +* #FR49315 - Now, the Range Slider labels can be customized individually using the `onLabelCreated` property. +* #FR66359 - Now, the Range Slider support keyboard navigation accessibility using the Left, Right, Up, Down arrow keys, and the Tab key. + +## Range Selector + +**Features** + +* #FR49315 - Now, the Range Selector labels can be customized individually using the `onLabelCreated` property. +* #FR66359 - Now, the Range Selector support keyboard navigation accessibility using the Left, Right, Up, Down arrow keys, and the Tab key. + +### Breaking changes + +The `labelFormatterCallback` property has been deprecated. Use `onLabelCreated` instead to customize the label's format and style. + +## [29.1.39] - 04/22/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 03/25/2025 + +**General** + +* The compatible version of our Flutter sliders widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter sliders example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter sliders example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.7] - 02/25/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter sliders widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.53] - 15/10/2024 + +**Bugs** + +* \#FB61614 - Now paddle tooltip is rendering properly in slider when the tooltip size is greater than parent widget. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter sliders widget has been updated to Flutter SDK 3.24.0. + +## [25.1.35] - 03/15/2024 + +**General** + +* Provided th​e Material 3 themes support. + +## [24.1.46] - 01/17/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## Range Slider + +**Bugs** + +* #FB48050 - Now, the tooltip will be displayed properly when its size is greater than the widget in range slider. + +## Slider + +**Bugs** + +* #FB48050 - Now, the tooltip will be displayed properly when its size is greater than the widget in slider. + +## [21.1.37] - 03/29/2023 + +## Range Selector + +**Bugs** + +* #FB41819 - The SfRangeSelector dragging works properly even when the range is too small. + +## [20.3.60] - 12/06/2022 + +## Range Slider + +**Bugs** + +* #FB39325 - Now, the "AnimationController methods should not be used after calling dispose" exception will no longer be thrown when dragging the range slider. + +## Slider + +**Bugs** + +* #FB39325 - Now, the "AnimationController methods should not be used after calling dispose" exception will no longer be thrown when dragging the slider. + +## [20.3.59] - 11/29/2022 + +## Range Selector + +**Bugs** + +* Now, the range selector thumb vertical and horizontal dragging works properly when you wrap it inside the scrollable widget. + +## Range Slider + +**Bugs** + +* Now, the range slider thumb vertical and horizontal dragging works properly when you wrap it inside the scrollable widget. + +## Slider + +**Bugs** + +* Now, the slider thumb vertical and horizontal dragging works properly when you wrap it inside the scrollable widget. + +## [20.3.47] - 09/29/2022 + +## Range Slider + +**Bug** +* #FB37062 - Now, the discrete RangeSlider thumbs get overlapped when placed inside a Row widget. + +## [20.2.36] - 07/01/2022 + +## Slider + +**Features** +* Now, the edge labels in the axis can be shifted inside the axis bounds if their position exceeds the axis bounds using the `edgeLabelPlacement` property. + +## Range Slider + +**Features** +* Now, the edge labels in the axis can be shifted inside the axis bounds if their position exceeds the axis bounds using the `edgeLabelPlacement` property. + +## Range Selector + +**Features** +* Now, the edge labels in the axis can be shifted inside the axis bounds if their position exceeds the axis bounds using the `edgeLabelPlacement` property. + +## [19.4.38] - 12/17/2021 + ## Slider **Features** diff --git a/packages/syncfusion_flutter_sliders/LICENSE b/packages/syncfusion_flutter_sliders/LICENSE index e7d6a4a25..606b80cfc 100644 --- a/packages/syncfusion_flutter_sliders/LICENSE +++ b/packages/syncfusion_flutter_sliders/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Sliders 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. +Syncfusion® Flutter Sliders 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_sliders/README.md b/packages/syncfusion_flutter_sliders/README.md index fb06f6eeb..f4bf26aa6 100644 --- a/packages/syncfusion_flutter_sliders/README.md +++ b/packages/syncfusion_flutter_sliders/README.md @@ -8,7 +8,7 @@ The Flutter Sliders package is written natively in Dart for creating highly inte 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 [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. +**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) @@ -27,7 +27,7 @@ This library is used to create three different types of sliders, namely slider, - [Add range selector to the widget tree](#add-range-selector-to-the-widget-tree) - [Add range selector elements](#add-range-selector-elements) - [Support and Feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Slider features @@ -83,22 +83,19 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

## Useful links -Take a look at the following to learn more about Syncfusion Flutter sliders: +Take a look at the following to learn more about Syncfusion® Flutter sliders: -* [Syncfusion Flutter Sliders product page](https://www.syncfusion.com/flutter-widgets) +* [Syncfusion® Flutter Sliders product page](https://www.syncfusion.com/flutter-widgets) * [User guide documentation for Slider](https://help.syncfusion.com/flutter/slider) * [User guide documentation for Range Slider](https://help.syncfusion.com/flutter/range-slider) * [User guide documentation for Range Selector](https://help.syncfusion.com/flutter/range-selector) @@ -389,11 +386,11 @@ The following screenshot illustrates the result of the above code sample. ## 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). +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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 +## 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. +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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_sliders/analysis_options.yaml b/packages/syncfusion_flutter_sliders/analysis_options.yaml index 2f547af5d..7bd9823d9 100644 --- a/packages/syncfusion_flutter_sliders/analysis_options.yaml +++ b/packages/syncfusion_flutter_sliders/analysis_options.yaml @@ -1,5 +1 @@ -include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore \ No newline at end of file +include: package:syncfusion_flutter_core/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_sliders/assets/fonts/Roboto-Medium.ttf b/packages/syncfusion_flutter_sliders/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 000000000..d629e9848 Binary files /dev/null and b/packages/syncfusion_flutter_sliders/assets/fonts/Roboto-Medium.ttf differ diff --git a/packages/syncfusion_flutter_sliders/assets/images/lake.jpg b/packages/syncfusion_flutter_sliders/assets/images/lake.jpg new file mode 100644 index 000000000..6c685bcc6 Binary files /dev/null and b/packages/syncfusion_flutter_sliders/assets/images/lake.jpg differ diff --git a/packages/syncfusion_flutter_sliders/example/README.md b/packages/syncfusion_flutter_sliders/example/README.md new file mode 100644 index 000000000..04f0fe821 --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/README.md @@ -0,0 +1,16 @@ +# sliders_sample + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_flutter_sliders/example/android/.gitignore b/packages/syncfusion_flutter_sliders/example/android/.gitignore index 6f568019d..55afd919c 100644 --- a/packages/syncfusion_flutter_sliders/example/android/.gitignore +++ b/packages/syncfusion_flutter_sliders/example/android/.gitignore @@ -7,7 +7,7 @@ gradle-wrapper.jar GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_sliders/example/android/app/build.gradle b/packages/syncfusion_flutter_sliders/example/android/app/build.gradle index 5fe3c929f..58e024b5f 100644 --- a/packages/syncfusion_flutter_sliders/example/android/app/build.gradle +++ b/packages/syncfusion_flutter_sliders/example/android/app/build.gradle @@ -1,68 +1,44 @@ -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' +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace = "com.example.sliders_sample" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + jvmTarget = JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.sliders_sample" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName } 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 + signingConfig = signingConfigs.debug } } } flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + source = "../.." } diff --git a/packages/syncfusion_flutter_sliders/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_sliders/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_sliders/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_sliders/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_sliders/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_sliders/example/android/app/src/main/AndroidManifest.xml index 3f41384db..0b8362455 100644 --- a/packages/syncfusion_flutter_sliders/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_sliders/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_sliders/example/android/app/src/main/kotlin/com/example/sliders_sample/MainActivity.kt b/packages/syncfusion_flutter_sliders/example/android/app/src/main/kotlin/com/example/sliders_sample/MainActivity.kt new file mode 100644 index 000000000..db564c5ca --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/android/app/src/main/kotlin/com/example/sliders_sample/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.sliders_sample + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_sliders/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_sliders/example/android/build.gradle b/packages/syncfusion_flutter_sliders/example/android/build.gradle index 4256f9173..d2ffbffa4 100644 --- a/packages/syncfusion_flutter_sliders/example/android/build.gradle +++ b/packages/syncfusion_flutter_sliders/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -18,14 +5,14 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/syncfusion_flutter_sliders/example/android/gradle.properties b/packages/syncfusion_flutter_sliders/example/android/gradle.properties index 94adc3a3f..259717082 100644 --- a/packages/syncfusion_flutter_sliders/example/android/gradle.properties +++ b/packages/syncfusion_flutter_sliders/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_sliders/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_sliders/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..7bb2df6ba 100644 --- a/packages/syncfusion_flutter_sliders/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_sliders/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/packages/syncfusion_flutter_sliders/example/android/settings.gradle b/packages/syncfusion_flutter_sliders/example/android/settings.gradle index 44e62bcf0..b9e43bd37 100644 --- a/packages/syncfusion_flutter_sliders/example/android/settings.gradle +++ b/packages/syncfusion_flutter_sliders/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -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" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/packages/syncfusion_flutter_sliders/example/ios/.gitignore b/packages/syncfusion_flutter_sliders/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/.gitignore +++ b/packages/syncfusion_flutter_sliders/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_sliders/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_sliders/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_sliders/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_sliders/example/ios/Flutter/Debug.xcconfig index e8efba114..592ceee85 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_sliders/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_sliders/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_sliders/example/ios/Flutter/Release.xcconfig index 399e9340e..592ceee85 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_sliders/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.pbxproj index 2393b352d..e61cb2b02 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,22 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -26,8 +34,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -37,14 +43,14 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; 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 = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; 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 = ""; }; @@ -57,20 +63,25 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -84,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -91,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -102,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -111,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -137,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -147,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -157,7 +187,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -165,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -191,20 +232,23 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -220,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -231,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -253,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -285,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -293,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -310,15 +371,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -328,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -362,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -376,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -386,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -418,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -426,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -444,15 +550,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -471,15 +572,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -492,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -513,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_sliders/example/ios/Runner/AppDelegate.swift index 70693e4a8..626664468 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_sliders/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_sliders/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_sliders/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_sliders/example/ios/Runner/Info.plist index e62ba62fd..cd7cdf6e7 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Sliders Sample CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_sliders/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_sliders/example/ios/Runner/Runner-Bridging-Header.h index 7335fdf90..308a2a560 100644 --- a/packages/syncfusion_flutter_sliders/example/ios/Runner/Runner-Bridging-Header.h +++ b/packages/syncfusion_flutter_sliders/example/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_sliders/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_sliders/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_sliders/example/lib/main.dart b/packages/syncfusion_flutter_sliders/example/lib/main.dart index b5803fbb8..f3d92fd06 100644 --- a/packages/syncfusion_flutter_sliders/example/lib/main.dart +++ b/packages/syncfusion_flutter_sliders/example/lib/main.dart @@ -4,17 +4,17 @@ import 'package:syncfusion_flutter_charts/charts.dart' hide LabelPlacement; import 'package:syncfusion_flutter_sliders/sliders.dart'; void main() { - return runApp(RangeSelectorApp()); + return runApp(const RangeSelectorApp()); } /// This widget will be the root of application. class RangeSelectorApp extends StatelessWidget { + ///Initialize the instance of the [RangeSelectorApp] class. + const RangeSelectorApp({super.key}); + @override Widget build(BuildContext context) { - return const MaterialApp( - title: 'Range Selector Demo', - home: MyHomePage(), - ); + return const MaterialApp(title: 'Range Selector Demo', home: MyHomePage()); } } @@ -31,69 +31,74 @@ class _MyHomePageState extends State { _MyHomePageState(); final List _chartData = [ - Data(x: DateTime(2003, 01, 01), y: 3.4), - Data(x: DateTime(2004, 01, 01), y: 2.8), - Data(x: DateTime(2005, 01, 01), y: 1.6), - Data(x: DateTime(2006, 01, 01), y: 2.3), - Data(x: DateTime(2007, 01, 01), y: 2.5), - Data(x: DateTime(2008, 01, 01), y: 2.9), - Data(x: DateTime(2009, 01, 01), y: 3.8), - Data(x: DateTime(2010, 01, 01), y: 2.0), + Data(x: DateTime(2003), y: 3.4), + Data(x: DateTime(2004), y: 2.8), + Data(x: DateTime(2005), y: 1.6), + Data(x: DateTime(2006), y: 2.3), + Data(x: DateTime(2007), y: 2.5), + Data(x: DateTime(2008), y: 2.9), + Data(x: DateTime(2009), y: 3.8), + Data(x: DateTime(2010), y: 2.0), ]; - final DateTime _dateMin = DateTime(2003, 01, 01); - final DateTime _dateMax = DateTime(2010, 01, 01); - final SfRangeValues _dateValues = - SfRangeValues(DateTime(2005, 01, 01), DateTime(2008, 01, 01)); + final DateTime _dateMin = DateTime(2003); + final DateTime _dateMax = DateTime(2010); + final SfRangeValues _dateValues = SfRangeValues( + DateTime(2005), + DateTime(2008), + ); @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Syncfusion Flutter Range Selector'), - ), + appBar: AppBar(title: const Text('Syncfusion Flutter Range Selector')), body: Container( - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - child: Stack( - children: [ - Padding( - padding: const EdgeInsets.only(top: 10), - child: Center( - // ignore: missing_required_param - child: SfRangeSelector( - min: _dateMin, - max: _dateMax, - initialValues: _dateValues, - labelPlacement: LabelPlacement.betweenTicks, - interval: 1, - dateIntervalType: DateIntervalType.years, - dateFormat: DateFormat.y(), - showTicks: true, - showLabels: true, - child: SizedBox( - child: SfCartesianChart( - margin: EdgeInsets.zero, - primaryXAxis: DateTimeAxis( - minimum: _dateMin, - maximum: _dateMax, - isVisible: false, - ), - primaryYAxis: NumericAxis(isVisible: false, maximum: 4), - series: >[ - SplineAreaSeries( - dataSource: _chartData, - xValueMapper: (Data sales, int index) => sales.x, - yValueMapper: (Data sales, int index) => sales.y) - ], + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Center( + // ignore: missing_required_param + child: SfRangeSelector( + min: _dateMin, + max: _dateMax, + initialValues: _dateValues, + labelPlacement: LabelPlacement.betweenTicks, + interval: 1, + dateIntervalType: DateIntervalType.years, + dateFormat: DateFormat.y(), + showTicks: true, + showLabels: true, + child: SizedBox( + height: 200, + child: SfCartesianChart( + margin: EdgeInsets.zero, + primaryXAxis: DateTimeAxis( + minimum: _dateMin, + maximum: _dateMax, + isVisible: false, ), - height: 200, + primaryYAxis: const NumericAxis( + isVisible: false, + maximum: 4, + ), + series: >[ + SplineAreaSeries( + dataSource: _chartData, + xValueMapper: (Data sales, int index) => sales.x, + yValueMapper: (Data sales, int index) => sales.y, + ), + ], ), ), ), ), - ], - )), + ), + ], + ), + ), ); } } diff --git a/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_sliders/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_sliders/example/macos/.gitignore b/packages/syncfusion_flutter_sliders/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/.gitignore +++ b/packages/syncfusion_flutter_sliders/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..7e6d46c99 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* sliders_sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sliders_sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* sliders_sample.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* sliders_sample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/sliders_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/sliders_sample"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/sliders_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/sliders_sample"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/sliders_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/sliders_sample"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..158579b9b 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_sliders/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_sliders/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_sliders/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_sliders/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_sliders/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_sliders/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_sliders/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..8a4a5abed 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_sliders/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = sliders_sample // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.slidersSample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_sliders/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_sliders/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_sliders/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_sliders/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_sliders/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_sliders/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_sliders/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_sliders/example/pubspec.yaml b/packages/syncfusion_flutter_sliders/example/pubspec.yaml index 833d5a4f8..59c70e0dc 100644 --- a/packages/syncfusion_flutter_sliders/example/pubspec.yaml +++ b/packages/syncfusion_flutter_sliders/example/pubspec.yaml @@ -1,77 +1,25 @@ name: sliders_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" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - intl: ^0.17.0 - syncfusion_flutter_sliders: ^19.1.54-beta - syncfusion_flutter_charts: ^19.1.54 + intl: ^0.19.0 + syncfusion_flutter_sliders: ^27.1.52 + syncfusion_flutter_charts: ^27.1.52 - # 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.8' 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_sliders/example/web/index.html b/packages/syncfusion_flutter_sliders/example/web/index.html index 0081e1894..34718f98e 100644 --- a/packages/syncfusion_flutter_sliders/example/web/index.html +++ b/packages/syncfusion_flutter_sliders/example/web/index.html @@ -27,72 +27,6 @@ - - + diff --git a/packages/syncfusion_flutter_sliders/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_sliders/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_sliders/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_sliders/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_sliders/lib/src/common.dart b/packages/syncfusion_flutter_sliders/lib/src/common.dart index a5f8bc01c..6e153067a 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/common.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/common.dart @@ -1,37 +1,112 @@ import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; // ignore_for_file: public_member_api_docs -/// Signature for formatting or changing the whole numeric or date label text. -typedef LabelFormatterCallback = String Function( +/// A label used in [SfSlider] to customize its value labels. +class SliderLabel { + /// Creates a [SliderLabel] with the specified [text] and [textStyle]. + const SliderLabel({required this.text, required this.textStyle}); - /// actualValue will be either [DateTime] or [double] - /// based on given [values]. - dynamic actualValue, + /// Contains the text of the slider label. + final String text; - /// If the actual value is [double], it is formatted by [numberFormat] and - /// if the actual value is [DateTime], it is formatted by [dateFormat]. - String formattedText); + /// The TextStyle to customize the label text. + final TextStyle textStyle; +} -/// Signature for formatting or changing the whole tooltip label text. -typedef TooltipTextFormatterCallback = String Function( +/// A label used in [SfRangeSlider] to customize its value labels. +class RangeSliderLabel extends SliderLabel { + /// Creates a [RangeSliderLabel] with the given [text] and [textStyle]. + const RangeSliderLabel({required super.text, required super.textStyle}); +} - /// actualValue will be either [DateTime] or [double] - /// based on given [values]. - dynamic actualValue, +/// A label used in [SfRangeSelector] to customize its value labels. +class RangeSelectorLabel extends RangeSliderLabel { + /// Creates a [RangeSelectorLabel] with the given [text] and [textStyle]. + const RangeSelectorLabel({required super.text, required super.textStyle}); +} + +/// Signature for formatting or changing the whole numeric or date label text. +typedef LabelFormatterCallback = + String Function( + /// The actual value, which will be either a [DateTime] or [double] + /// based on given [values]. + dynamic actualValue, + + /// If the actual value is [double], it is formatted by [numberFormat] and + /// if the actual value is [DateTime], it is formatted by [dateFormat]. + String formattedText, + ); + +/// Signature for customizing the appearance of the label by returning a +/// [SliderLabel] widget with the provided text and text style. +typedef SliderLabelCreatedCallback = + SliderLabel Function( + /// The actual value, which will be either a [DateTime] or [double] + /// based on given [values]. + dynamic actualValue, + + /// If the actual value is [double], it is formatted by [numberFormat] and + /// if the actual value is [DateTime], it is formatted by [dateFormat]. + String formattedText, + + /// Customizes the text style of the slider label. + TextStyle textStyle, + ); + +/// Signature for customizing the appearance of the label by returning a +/// [RangeSliderLabel] widget with the provided text and text style. +typedef RangeSliderLabelCreatedCallback = + RangeSliderLabel Function( + /// The actual value, which will be either a [DateTime] or [double] + /// based on given [values]. + dynamic actualValue, + + /// If the actual value is [double], it is formatted by [numberFormat] and + /// if the actual value is [DateTime], it is formatted by [dateFormat]. + String formattedText, + + /// Customizes the text style of the range slider label. + TextStyle textStyle, + ); + +/// Signature for customizing the appearance of the label by returning a +/// [RangeSelectorLabel] widget with the provided text and text style. +typedef RangeSelectorLabelCreatedCallback = + RangeSelectorLabel Function( + /// The actual value, which will be either a [DateTime] or [double] + /// based on given [values]. + dynamic actualValue, + + /// If the actual value is [double], it is formatted by [numberFormat] and + /// if the actual value is [DateTime], it is formatted by [dateFormat]. + String formattedText, + + /// Customizes the text style of the range selector label. + TextStyle textStyle, + ); - /// If the actual value is [double], it is formatted by [numberFormat] and - /// if the actual value is [DateTime], it is formatted by [dateFormat]. - String formattedText); +/// Signature for formatting or changing the whole tooltip label text. +typedef TooltipTextFormatterCallback = + String Function( + /// actualValue will be either [DateTime] or [double] + /// based on given [values]. + dynamic actualValue, + + /// If the actual value is [double], it is formatted by [numberFormat] and + /// if the actual value is [DateTime], it is formatted by [dateFormat]. + String formattedText, + ); /// The value will be either [double] or [DateTime] based on the `values`. typedef SfSliderSemanticFormatterCallback = String Function(dynamic value); -typedef RangeSliderSemanticFormatterCallback = String Function( - dynamic value, SfThumb thumb); +typedef RangeSliderSemanticFormatterCallback = + String Function(dynamic value, SfThumb thumb); -typedef RangeSelectorSemanticFormatterCallback = String Function( - dynamic value, SfThumb thumb); +typedef RangeSelectorSemanticFormatterCallback = + String Function(dynamic value, SfThumb thumb); /// Option to place the labels either between the major ticks /// or on the major ticks. @@ -40,7 +115,17 @@ enum LabelPlacement { onTicks, /// betweenTicks places the labels between the major ticks. - betweenTicks + betweenTicks, +} + +/// Placement of edge labels in the axis. +enum EdgeLabelPlacement { + /// - EdgeLabelPlacement.auto, places the edge labels in its own position. + auto, + + /// - EdgeLabelPlacement.inside, shift the edge labels inside the plot area + /// bounds. + inside, } /// The type of date interval. It can be years to seconds. @@ -96,7 +181,7 @@ enum DateIntervalType { /// `dateIntervalType` is [seconds] then range slider will render labels for /// `Jan 01, 2000 09:00:00`, `Jan 01, 2000 09:00:20`, `Jan 01, 2000 09:00:40`, /// and `Jan 01, 2000 09:01:00` respectively. - seconds + seconds, } /// Represents the [SfRangeSlider] or [SfRangeSelector] thumbs. @@ -111,7 +196,7 @@ enum SfThumb { both, /// represents none of the thumb. - none + none, } /// Represents the dragging behavior of the [SfRangeSelector] thumbs. @@ -130,8 +215,9 @@ enum SliderDragMode { /// When [SliderDragMode] is set to [SliderDragMode.both], individual thumb /// can be moved by dragging it, and also both the thumbs can be moved /// at the same time by dragging in the area between start and end thumbs. - both + both, } + enum SliderTooltipPosition { left, right } /// Represents the current selected values of [SfRangeSlider] @@ -155,14 +241,18 @@ class SfRangeValues extends DiagnosticableTree { // ignore: avoid_as final double value = start as double; return SfRangeValues( - DateTime.fromMillisecondsSinceEpoch(value.toInt()), end ?? this.end); + DateTime.fromMillisecondsSinceEpoch(value.toInt()), + end ?? this.end, + ); } else if (end != null && end.runtimeType == num && this.start.runtimeType == DateTime) { // ignore: avoid_as final double value = end as double; - return SfRangeValues(start ?? this.start, - DateTime.fromMillisecondsSinceEpoch(value.toInt())); + return SfRangeValues( + start ?? this.start, + DateTime.fromMillisecondsSinceEpoch(value.toInt()), + ); } return SfRangeValues(start ?? this.start, end ?? this.end); @@ -185,13 +275,14 @@ class SliderStepDuration extends DiagnosticableTree { /// The discrete position is calculated by adding the arguments /// given in the [SliderStepDuration] object. /// By default, all arguments values are zero. - const SliderStepDuration( - {this.years = 0, - this.months = 0, - this.days = 0, - this.hours = 0, - this.minutes = 0, - this.seconds = 0}); + const SliderStepDuration({ + this.years = 0, + this.months = 0, + this.days = 0, + this.hours = 0, + this.minutes = 0, + this.seconds = 0, + }); /// Moves the thumbs based on years. /// diff --git a/packages/syncfusion_flutter_sliders/lib/src/constants.dart b/packages/syncfusion_flutter_sliders/lib/src/constants.dart index a931dd51a..5be96360f 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/constants.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/constants.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; // ignore_for_file: public_member_api_docs @@ -41,3 +42,36 @@ enum ChildElements { } enum SliderType { horizontal, vertical } + +enum SliderKeyType { right, left, up, down } + +class SliderKeyIntent extends Intent { + const SliderKeyIntent({required this.type}); + + const SliderKeyIntent.right() : type = SliderKeyType.right; + + const SliderKeyIntent.left() : type = SliderKeyType.left; + + const SliderKeyIntent.up() : type = SliderKeyType.up; + + const SliderKeyIntent.down() : type = SliderKeyType.down; + + final SliderKeyType type; +} + +// Keyboard mapping for a focused slider. +const Map KeyboardNavShortcutMap = + { + SingleActivator(LogicalKeyboardKey.arrowUp): SliderKeyIntent.up(), + SingleActivator(LogicalKeyboardKey.arrowDown): SliderKeyIntent.down(), + SingleActivator(LogicalKeyboardKey.arrowLeft): SliderKeyIntent.left(), + SingleActivator(LogicalKeyboardKey.arrowRight): SliderKeyIntent.right(), + }; + +// Keyboard mapping for a focused slider when using directional navigation. +// The vertical inputs are not handled to allow navigating out of the slider. +const Map KeyboardDirectionalNavShortcutMap = + { + SingleActivator(LogicalKeyboardKey.arrowLeft): SliderKeyIntent.left(), + SingleActivator(LogicalKeyboardKey.arrowRight): SliderKeyIntent.right(), + }; diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart index e4b6805ba..2cc97b47f 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:math' as math; import 'package:flutter/foundation.dart'; +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, NumberFormat; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; @@ -11,6 +13,7 @@ import 'common.dart'; import 'constants.dart'; import 'range_slider_base.dart'; import 'slider_shapes.dart'; +import 'theme.dart'; /// A Material Design range selector. /// @@ -111,55 +114,61 @@ import 'slider_shapes.dart'; /// * [SfChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/charts-library.html) and [RangeController](https://pub.dev/documentation/syncfusion_flutter_core/latest/core/RangeController-class.html), for selection and zooming. class SfRangeSelector extends StatefulWidget { /// Creates a [SfRangeSelector]. - const SfRangeSelector( - {Key? key, - this.min = 0.0, - this.max = 1.0, - this.initialValues, - this.onChanged, - this.onChangeStart, - this.onChangeEnd, - this.controller, - this.enabled = true, - this.interval, - this.stepSize, - this.stepDuration, - this.deferredUpdateDelay = 500, - this.minorTicksPerInterval = 0, - this.showTicks = false, - this.showLabels = false, - this.showDividers = false, - this.enableTooltip = false, - this.shouldAlwaysShowTooltip = false, - this.enableIntervalSelection = false, - this.enableDeferredUpdate = false, - this.dragMode = SliderDragMode.onThumb, - 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.dividerShape = const SfDividerShape(), - 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}) - : assert(min != max), - assert(interval == null || interval > 0), - assert(stepSize == null || stepSize > 0), - assert(!enableIntervalSelection || - (enableIntervalSelection && (interval != null && interval > 0))), - assert(controller != null || initialValues != null), - super(key: key); + const SfRangeSelector({ + Key? key, + this.min = 0.0, + this.max = 1.0, + this.initialValues, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.controller, + this.enabled = true, + this.interval, + this.stepSize, + this.stepDuration, + this.deferredUpdateDelay = 500, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDividers = false, + this.enableTooltip = false, + this.shouldAlwaysShowTooltip = false, + this.enableIntervalSelection = false, + this.enableDeferredUpdate = false, + this.dragMode = SliderDragMode.onThumb, + this.inactiveColor, + this.activeColor, + this.labelPlacement = LabelPlacement.onTicks, + this.edgeLabelPlacement = EdgeLabelPlacement.auto, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + // ignore: deprecated_consistency + this.labelFormatterCallback, + this.onLabelCreated, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.dividerShape = const SfDividerShape(), + 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, + }) : assert(min != max), + assert(interval == null || interval > 0), + assert(stepSize == null || stepSize > 0), + assert( + !enableIntervalSelection || + (enableIntervalSelection && (interval != null && interval > 0)), + ), + assert(controller != null || initialValues != null), + super(key: key); /// The minimum value the user can select. /// @@ -1062,6 +1071,26 @@ class SfRangeSelector extends StatefulWidget { /// ``` final LabelPlacement labelPlacement; + /// Position of the edge labels. + /// + /// The edge labels in an axis can be shifted inside + /// the axis bounds or placed at the edges. + /// + /// Defaults to `EdgeLabelPlacement.auto`. + /// + /// Also refer [EdgeLabelPlacement]. + /// + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfRangeSelector( + /// edgeLabelPlacement: EdgeLabelPlacement.inside, + /// ); + ///} + ///``` + + final EdgeLabelPlacement edgeLabelPlacement; + /// Formats the numeric labels. /// /// Defaults to `null`. @@ -1087,7 +1116,9 @@ class SfRangeSelector extends StatefulWidget { /// ``` /// See also: /// + // ignore: deprecated_member_use_from_same_package /// * [labelFormatterCallback], for formatting the numeric and date labels. + /// * [onLabelCreated], for formatting and styling numeric and date labels. final NumberFormat? numberFormat; /// Formats the date labels. It is mandatory for date [SfRangeSelector]. @@ -1125,7 +1156,9 @@ class SfRangeSelector extends StatefulWidget { /// /// * [interval], for setting the interval. /// * [numberFormat], for formatting the numeric labels. - /// * [labelFormatterCallback], for formatting the numeric and date label. + // ignore: deprecated_member_use_from_same_package + /// * [labelFormatterCallback], for formatting the numeric and date labels. + /// * [onLabelCreated], for formatting and styling numeric and date label. /// * [dateIntervalType], for changing the interval type. final DateFormat? dateFormat; @@ -1190,8 +1223,64 @@ class SfRangeSelector extends StatefulWidget { /// ), /// ) /// ``` + @Deprecated('Use `onLabelCreated` instead') final LabelFormatterCallback? labelFormatterCallback; + /// Signature for customizing the text and style of numeric or date labels. + /// + /// * The actual value without formatting is given by `actualValue`. + /// It is either [DateTime] or [double] based on given [initialValues] or + /// controller start and end values. + /// * The formatted value based on the numeric or date format + /// is given by `formattedText`. + /// * Text styles can be applied to individual labels using the `textStyle` + /// property. + /// + /// This snippet shows how to format and style labels in [SfRangeSelector]. + /// + /// ```dart + /// SfRangeValues _initialValues = const SfRangeValues(3.0, 7.0); + /// + /// SfRangeSelector( + /// min: 3.0, + /// max: 8.0, + /// initialValues: _initialValues, + /// interval: 1, + /// showLabels: true, + /// onChanged: (SfRangeValues newValues) { + /// setState(() { + /// _initialValues = newValues; + /// }); + /// }, + /// onLabelCreated: ( + /// dynamic actualValue, + /// String formattedText, + /// TextStyle textStyle, + /// ) { + /// final int value = actualValue.toInt(); + /// final int startIndex = _initialValues.start.toInt(); + /// final int endIndex = _initialValues.end.toInt(); + /// return RangeSelectorLabel( + /// text: value == startIndex || value == endIndex + /// ? '$formattedText' + /// : '$actualValue', + /// textStyle: value == startIndex || value == endIndex + /// ? const TextStyle( + /// color: Colors.purple, + /// fontSize: 18, + /// fontWeight: FontWeight.bold, + /// ) + /// : textStyle, + /// ); + /// }, + /// child: Container( + /// height: 200, + /// color: Colors.green[100], + /// ), + /// ) + /// ``` + final RangeSelectorLabelCreatedCallback? onLabelCreated; + /// Signature for formatting or changing the whole tooltip label text. /// /// * The actual value without formatting is given by `actualValue`. @@ -1397,22 +1486,21 @@ class SfRangeSelector extends StatefulWidget { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); if (initialValues != null) { - properties.add( - initialValues!.toDiagnosticsNode(name: 'initialValues'), - ); + 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'), - ); + properties.add(controller!.toDiagnosticsNode(name: 'controller')); } - properties.add(FlagProperty('enabled', + properties.add( + FlagProperty( + 'enabled', value: enabled, ifTrue: 'Range selector is enabled', ifFalse: 'Range selector is disabled', - showName: false)); + ), + ); properties.add(DoubleProperty('interval', interval)); properties.add(DoubleProperty('stepSize', stepSize)); if (stepDuration != null) { @@ -1420,72 +1508,137 @@ class SfRangeSelector extends StatefulWidget { } properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); - properties.add(FlagProperty('showTicks', + properties.add( + FlagProperty( + 'showTicks', value: showTicks, ifTrue: 'Ticks are showing', ifFalse: 'Ticks are not showing', - showName: false)); - properties.add(FlagProperty('showLabels', + ), + ); + properties.add( + FlagProperty( + 'showLabels', value: showLabels, ifTrue: 'Labels are showing', ifFalse: 'Labels are not showing', - showName: false)); - properties.add(FlagProperty('showDividers', + ), + ); + properties.add( + FlagProperty( + 'showDividers', value: showDividers, ifTrue: 'Dividers are showing', ifFalse: 'Dividers are not showing', - showName: false)); + ), + ); if (shouldAlwaysShowTooltip) { - properties.add(FlagProperty('shouldAlwaysShowTooltip', + properties.add( + FlagProperty( + 'shouldAlwaysShowTooltip', value: shouldAlwaysShowTooltip, ifTrue: 'Tooltip is always visible', - showName: false)); + ), + ); } else { - properties.add(FlagProperty('enableTooltip', + properties.add( + FlagProperty( + 'enableTooltip', value: enableTooltip, ifTrue: 'Tooltip is enabled', ifFalse: 'Tooltip is disabled', - showName: false)); + ), + ); } - properties.add(FlagProperty('enableIntervalSelection', + properties.add( + FlagProperty( + 'enableIntervalSelection', value: enableIntervalSelection, ifTrue: 'Interval selection is enabled', ifFalse: 'Interval selection is disabled', - showName: false)); - properties.add(FlagProperty('enableDeferredUpdate', + ), + ); + properties.add( + FlagProperty( + 'enableDeferredUpdate', value: enableDeferredUpdate, ifTrue: 'Deferred update is enabled', ifFalse: 'Deferred update is disabled', - showName: false)); + ), + ); properties.add(EnumProperty('dragMode', dragMode)); properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('inactiveColor', inactiveColor)); - properties - .add(EnumProperty('labelPlacement', labelPlacement)); - properties - .add(DiagnosticsProperty('numberFormat', numberFormat)); + properties.add( + EnumProperty('labelPlacement', labelPlacement), + ); + properties.add( + EnumProperty( + 'edgeLabelPlacement', + edgeLabelPlacement, + ), + ); + properties.add( + DiagnosticsProperty('numberFormat', numberFormat), + ); if (initialValues != null && initialValues!.start.runtimeType == DateTime && dateFormat != null) { - properties.add(StringProperty('dateFormat', - 'Formatted value is ${dateFormat!.format(initialValues!.start)}')); + properties.add( + StringProperty( + 'dateFormat', + 'Formatted value is ${dateFormat!.format(initialValues!.start)}', + ), + ); } - properties.add(ObjectFlagProperty>.has( - 'onChanged', onChanged)); - properties.add(ObjectFlagProperty>.has( - 'onChangeStart', onChangeStart)); - properties.add(ObjectFlagProperty>.has( - 'onChangeEnd', onChangeEnd)); properties.add( - EnumProperty('dateIntervalType', dateIntervalType)); - properties.add(ObjectFlagProperty.has( - 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); - properties.add(ObjectFlagProperty.has( - 'labelFormatterCallback', labelFormatterCallback)); + ObjectFlagProperty>.has( + 'onChanged', + onChanged, + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'onChangeStart', + onChangeStart, + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'onChangeEnd', + onChangeEnd, + ), + ); + properties.add( + EnumProperty('dateIntervalType', dateIntervalType), + ); + properties.add( + ObjectFlagProperty.has( + 'tooltipTextFormatterCallback', + tooltipTextFormatterCallback, + ), + ); + properties.add( + // ignore: deprecated_member_use_from_same_package + ObjectFlagProperty.has( + 'labelFormatterCallback', + // ignore: deprecated_member_use_from_same_package + labelFormatterCallback, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'onLabelCreated', + onLabelCreated, + ), + ); properties.add( - ObjectFlagProperty.has( - 'semanticFormatterCallback', semanticFormatterCallback)); + ObjectFlagProperty.has( + 'semanticFormatterCallback', + semanticFormatterCallback, + ), + ); } } @@ -1498,13 +1651,86 @@ class _SfRangeSelectorState extends State late AnimationController stateController; late AnimationController tooltipAnimationStartController; late AnimationController tooltipAnimationEndController; - final Duration duration = const Duration(milliseconds: 100); + late Map> _keyboardActionMap; + late FocusNode _focusNode; + bool _focused = false; + SfThumb _focusedThumb = SfThumb.start; + SfRangeValues? _values; + final Duration duration = const Duration(milliseconds: 100); + final GlobalKey _renderObjectKey = GlobalKey(); + FocusNode get rangeSelectorFocusNode => _focusNode; + + void _handleNextFocus(NextFocusIntent intent) { + if (_focusedThumb == SfThumb.start) { + setState(() { + _focusedThumb = SfThumb.end; + }); + } else { + // Reset the _focusedThumb to start position when the focus is + // moved to the next widget. + setState(() { + _focusedThumb = SfThumb.start; + }); + FocusScope.of(context).nextFocus(); + } + } + + void _handlePreviousFocus(PreviousFocusIntent intent) { + if (_focusedThumb == SfThumb.end) { + setState(() { + _focusedThumb = SfThumb.start; + }); + } else { + FocusScope.of(context).previousFocus(); + } + } + + void _handleRangeSelectorFocusChanged(bool newFocused) { + if (newFocused != _focused) { + setState(() { + _focused = newFocused; + }); + } + } + + void _keyboardActionHandler(SliderKeyIntent intent) { + final TextDirection directionality = Directionality.of( + _renderObjectKey.currentContext!, + ); + final bool shouldIncrease = switch (intent.type) { + SliderKeyType.up => true, + SliderKeyType.down => false, + SliderKeyType.left => directionality == TextDirection.rtl, + SliderKeyType.right => directionality == TextDirection.ltr, + }; + + final _RenderRangeSelector rangeSelector = + _renderObjectKey.currentContext!.findRenderObject()! + as _RenderRangeSelector; + if (_focusedThumb == SfThumb.start) { + return shouldIncrease + ? rangeSelector._increaseStartAction() + : rangeSelector._decreaseStartAction(); + } else { + return shouldIncrease + ? rangeSelector._increaseEndAction() + : rangeSelector._decreaseEndAction(); + } + } String _getFormattedLabelText(dynamic actualText, String formattedText) { return formattedText; } + RangeSelectorLabel _handleLabelCreated( + dynamic actualValue, + String formattedText, + TextStyle textStyle, + ) { + return RangeSelectorLabel(text: formattedText, textStyle: textStyle); + } + String _getFormattedTooltipText(dynamic actualText, String formattedText) { return formattedText; } @@ -1512,107 +1738,132 @@ class _SfRangeSelectorState extends State SfRangeSelectorThemeData _getRangeSelectorThemeData(ThemeData themeData) { SfRangeSelectorThemeData rangeSelectorThemeData = SfRangeSelectorTheme.of(context)!; + final bool isMaterial3 = themeData.useMaterial3; + final SfRangeSelectorThemeData effectiveThemeData = RangeSelectorThemeData( + context, + ); + final Color labelColor = + isMaterial3 + ? themeData.colorScheme.onSurfaceVariant + : widget.enabled + ? themeData.textTheme.bodyLarge!.color!.withValues(alpha: 0.87) + : themeData.colorScheme.onSurface.withValues(alpha: 0.32); final double minTrackHeight = math.min( - rangeSelectorThemeData.activeTrackHeight, - rangeSelectorThemeData.inactiveTrackHeight); + rangeSelectorThemeData.activeTrackHeight, + rangeSelectorThemeData.inactiveTrackHeight, + ); final double maxTrackHeight = math.max( - rangeSelectorThemeData.activeTrackHeight, - rangeSelectorThemeData.inactiveTrackHeight); + rangeSelectorThemeData.activeTrackHeight, + rangeSelectorThemeData.inactiveTrackHeight, + ); rangeSelectorThemeData = rangeSelectorThemeData.copyWith( activeTrackHeight: rangeSelectorThemeData.activeTrackHeight, inactiveTrackHeight: rangeSelectorThemeData.inactiveTrackHeight, tickSize: rangeSelectorThemeData.tickSize, minorTickSize: rangeSelectorThemeData.minorTickSize, tickOffset: rangeSelectorThemeData.tickOffset, - labelOffset: rangeSelectorThemeData.labelOffset ?? + labelOffset: + rangeSelectorThemeData.labelOffset ?? (widget.showTicks ? const Offset(0.0, 5.0) : const Offset(0.0, 13.0)), - inactiveLabelStyle: rangeSelectorThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyText1!.copyWith( - color: widget.enabled - ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - activeLabelStyle: rangeSelectorThemeData.activeLabelStyle ?? - themeData.textTheme.bodyText1!.copyWith( - color: widget.enabled - ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - tooltipTextStyle: rangeSelectorThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyText1! - .copyWith(color: themeData.colorScheme.surface), - inactiveTrackColor: widget.inactiveColor ?? + inactiveLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSelectorThemeData.inactiveLabelStyle), + activeLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSelectorThemeData.activeLabelStyle), + tooltipTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: + isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.surface, + ) + .merge(rangeSelectorThemeData.tooltipTextStyle), + inactiveTrackColor: + widget.inactiveColor ?? rangeSelectorThemeData.inactiveTrackColor ?? - themeData.colorScheme.primary.withOpacity(0.24), - activeTrackColor: widget.activeColor ?? + effectiveThemeData.inactiveTrackColor, + activeTrackColor: + widget.activeColor ?? rangeSelectorThemeData.activeTrackColor ?? - themeData.colorScheme.primary, - thumbColor: widget.activeColor ?? + effectiveThemeData.activeTrackColor, + thumbColor: + widget.activeColor ?? rangeSelectorThemeData.thumbColor ?? - themeData.colorScheme.primary, - activeTickColor: rangeSelectorThemeData.activeTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - inactiveTickColor: rangeSelectorThemeData.inactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - disabledActiveTickColor: rangeSelectorThemeData.disabledActiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.thumbColor, + activeTickColor: + rangeSelectorThemeData.activeTickColor ?? + effectiveThemeData.activeTickColor, + inactiveTickColor: + rangeSelectorThemeData.inactiveTickColor ?? + effectiveThemeData.inactiveTickColor, + disabledActiveTickColor: + rangeSelectorThemeData.disabledActiveTickColor ?? + effectiveThemeData.disabledActiveTickColor, disabledInactiveTickColor: rangeSelectorThemeData.disabledInactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - activeMinorTickColor: rangeSelectorThemeData.activeMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - inactiveMinorTickColor: rangeSelectorThemeData.inactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.disabledInactiveTickColor, + activeMinorTickColor: + rangeSelectorThemeData.activeMinorTickColor ?? + effectiveThemeData.activeMinorTickColor, + inactiveMinorTickColor: + rangeSelectorThemeData.inactiveMinorTickColor ?? + effectiveThemeData.inactiveMinorTickColor, disabledActiveMinorTickColor: rangeSelectorThemeData.disabledActiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveMinorTickColor, // ignore: lines_longer_than_80_chars disabledInactiveMinorTickColor: rangeSelectorThemeData.disabledInactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - overlayColor: widget.activeColor?.withOpacity(0.12) ?? + effectiveThemeData.disabledInactiveMinorTickColor, + overlayColor: + widget.activeColor?.withValues(alpha: 0.12) ?? rangeSelectorThemeData.overlayColor ?? - themeData.colorScheme.primary.withOpacity(0.12), - inactiveDividerColor: widget.activeColor ?? + effectiveThemeData.overlayColor, + inactiveDividerColor: + widget.activeColor ?? rangeSelectorThemeData.inactiveDividerColor ?? - themeData.colorScheme.primary.withOpacity(0.54), - activeDividerColor: widget.inactiveColor ?? + effectiveThemeData.inactiveDividerColor, + activeDividerColor: + widget.inactiveColor ?? rangeSelectorThemeData.activeDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.54), + effectiveThemeData.activeDividerColor, disabledInactiveDividerColor: rangeSelectorThemeData.disabledInactiveDividerColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveDividerColor, disabledActiveDividerColor: rangeSelectorThemeData.disabledActiveDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.12), + effectiveThemeData.disabledActiveDividerColor, disabledActiveTrackColor: rangeSelectorThemeData.disabledActiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.32), + effectiveThemeData.disabledActiveTrackColor, disabledInactiveTrackColor: rangeSelectorThemeData.disabledInactiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), - disabledThumbColor: rangeSelectorThemeData.disabledThumbColor ?? - Color.alphaBlend(themeData.colorScheme.onSurface.withOpacity(0.38), - themeData.colorScheme.surface), + effectiveThemeData.disabledInactiveTrackColor, + disabledThumbColor: + rangeSelectorThemeData.disabledThumbColor ?? + effectiveThemeData.disabledThumbColor, thumbStrokeColor: rangeSelectorThemeData.thumbStrokeColor, overlappingThumbStrokeColor: rangeSelectorThemeData.overlappingThumbStrokeColor ?? - themeData.colorScheme.surface, + themeData.colorScheme.surface, activeDividerStrokeColor: rangeSelectorThemeData.activeDividerStrokeColor, inactiveDividerStrokeColor: rangeSelectorThemeData.inactiveDividerStrokeColor, overlappingTooltipStrokeColor: rangeSelectorThemeData.overlappingTooltipStrokeColor ?? - themeData.colorScheme.surface, + themeData.colorScheme.surface, activeRegionColor: - rangeSelectorThemeData.activeRegionColor ?? Colors.transparent, - inactiveRegionColor: widget.inactiveColor ?? + rangeSelectorThemeData.activeRegionColor ?? + effectiveThemeData.activeRegionColor, + inactiveRegionColor: + widget.inactiveColor ?? rangeSelectorThemeData.inactiveRegionColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.75) - : const Color.fromRGBO(48, 48, 48, 1).withOpacity(0.75)), - tooltipBackgroundColor: rangeSelectorThemeData.tooltipBackgroundColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(97, 97, 97, 1) - : const Color.fromRGBO(224, 224, 224, 1)), + effectiveThemeData.inactiveRegionColor, + tooltipBackgroundColor: + rangeSelectorThemeData.tooltipBackgroundColor ?? + effectiveThemeData.tooltipBackgroundColor, trackCornerRadius: rangeSelectorThemeData.trackCornerRadius ?? maxTrackHeight / 2, thumbRadius: rangeSelectorThemeData.thumbRadius, @@ -1650,25 +1901,57 @@ class _SfRangeSelectorState extends State _values = SfRangeValues(widget.controller!.start, widget.controller!.end); } - overlayStartController = - AnimationController(vsync: this, duration: duration); + overlayStartController = AnimationController( + vsync: this, + duration: duration, + ); overlayEndController = AnimationController(vsync: this, duration: duration); stateController = AnimationController(vsync: this, duration: duration); - startPositionController = - AnimationController(duration: Duration.zero, vsync: this); - endPositionController = - AnimationController(duration: Duration.zero, vsync: this); - tooltipAnimationStartController = - AnimationController(vsync: this, duration: duration); - tooltipAnimationEndController = - AnimationController(vsync: this, duration: duration); + startPositionController = AnimationController( + duration: Duration.zero, + vsync: this, + ); + endPositionController = AnimationController( + duration: Duration.zero, + vsync: this, + ); + tooltipAnimationStartController = AnimationController( + vsync: this, + duration: duration, + ); + tooltipAnimationEndController = AnimationController( + vsync: this, + duration: duration, + ); stateController.value = widget.enabled && (widget.min != widget.max) ? 1.0 : 0.0; + + _keyboardActionMap = >{ + SliderKeyIntent: CallbackAction( + onInvoke: _keyboardActionHandler, + ), + NextFocusIntent: CallbackAction( + onInvoke: _handleNextFocus, + ), + PreviousFocusIntent: CallbackAction( + onInvoke: _handlePreviousFocus, + ), + }; + _focusNode = FocusNode( + skipTraversal: widget.dragMode == SliderDragMode.betweenThumbs, + canRequestFocus: widget.dragMode != SliderDragMode.betweenThumbs, + ); super.initState(); } @override void didUpdateWidget(SfRangeSelector oldWidget) { + if (oldWidget.dragMode != widget.dragMode) { + _focusNode.skipTraversal = + widget.dragMode == SliderDragMode.betweenThumbs; + _focusNode.canRequestFocus = + widget.dragMode != SliderDragMode.betweenThumbs; + } if (oldWidget.shouldAlwaysShowTooltip != widget.shouldAlwaysShowTooltip) { if (widget.shouldAlwaysShowTooltip) { tooltipAnimationStartController.value = 1; @@ -1690,6 +1973,7 @@ class _SfRangeSelectorState extends State tooltipAnimationStartController.dispose(); tooltipAnimationEndController.dispose(); stateController.dispose(); + _focusNode.dispose(); super.dispose(); } @@ -1698,8 +1982,35 @@ class _SfRangeSelectorState extends State Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - return _RangeSelectorRenderObjectWidget( - key: widget.key, + VoidCallback? handleRangeSelectorAccessibilityFocus; + switch (themeData.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + break; + case TargetPlatform.windows: + handleRangeSelectorAccessibilityFocus = () { + // Automatically activate the slider when it receives a11y focus. + if (!rangeSelectorFocusNode.hasFocus && + rangeSelectorFocusNode.canRequestFocus) { + rangeSelectorFocusNode.requestFocus(); + } + }; + } + final Map keyboardShortcutMap = { + ...switch (MediaQuery.navigationModeOf(context)) { + NavigationMode.directional => KeyboardDirectionalNavShortcutMap, + NavigationMode.traditional => KeyboardNavShortcutMap, + }, + const SingleActivator(LogicalKeyboardKey.tab): const NextFocusIntent(), + const SingleActivator(LogicalKeyboardKey.tab, shift: true): + const PreviousFocusIntent(), + }; + + final Widget result = _RangeSelectorRenderObjectWidget( + key: _renderObjectKey, min: widget.min, max: widget.max, values: _values ?? widget.initialValues, @@ -1720,14 +2031,18 @@ class _SfRangeSelectorState extends State deferUpdate: widget.enableDeferredUpdate, dragMode: widget.dragMode, inactiveColor: - widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), + widget.inactiveColor ?? + themeData.primaryColor.withValues(alpha: 0.24), activeColor: widget.activeColor ?? themeData.primaryColor, labelPlacement: widget.labelPlacement, + edgeLabelPlacement: widget.edgeLabelPlacement, numberFormat: widget.numberFormat ?? NumberFormat('#.##'), dateFormat: widget.dateFormat, dateIntervalType: widget.dateIntervalType, labelFormatterCallback: + // ignore: deprecated_member_use_from_same_package widget.labelFormatterCallback ?? _getFormattedLabelText, + onLabelCreated: widget.onLabelCreated ?? _handleLabelCreated, tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, @@ -1738,11 +2053,24 @@ class _SfRangeSelectorState extends State tickShape: widget.tickShape, minorTickShape: widget.minorTickShape, tooltipShape: widget.tooltipShape, - child: widget.child, rangeSelectorThemeData: _getRangeSelectorThemeData(themeData), startThumbIcon: widget.startThumbIcon, endThumbIcon: widget.endThumbIcon, + hasFocus: _focused, + focusedThumb: _focusedThumb, state: this, + child: widget.child, + ); + + return Semantics( + onDidGainAccessibilityFocus: handleRangeSelectorAccessibilityFocus, + child: FocusableActionDetector( + actions: _keyboardActionMap, + shortcuts: keyboardShortcutMap, + focusNode: rangeSelectorFocusNode, + onShowFocusHighlight: _handleRangeSelectorFocusChanged, + child: result, + ), ); } } @@ -1772,10 +2100,12 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { required this.inactiveColor, required this.activeColor, required this.labelPlacement, + required this.edgeLabelPlacement, required this.numberFormat, required this.dateFormat, required this.dateIntervalType, required this.labelFormatterCallback, + required this.onLabelCreated, required this.tooltipTextFormatterCallback, required this.semanticFormatterCallback, required this.trackShape, @@ -1790,6 +2120,8 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { required this.startThumbIcon, required this.endThumbIcon, required this.state, + required this.hasFocus, + required this.focusedThumb, }) : super(key: key); final dynamic min; @@ -1817,11 +2149,14 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { final Color activeColor; final LabelPlacement labelPlacement; + final EdgeLabelPlacement edgeLabelPlacement; final NumberFormat numberFormat; final DateFormat? dateFormat; final DateIntervalType? dateIntervalType; final SfRangeSelectorThemeData rangeSelectorThemeData; + // ignore: deprecated_member_use_from_same_package final LabelFormatterCallback labelFormatterCallback; + final RangeSelectorLabelCreatedCallback onLabelCreated; final TooltipTextFormatterCallback tooltipTextFormatterCallback; final RangeSelectorSemanticFormatterCallback? semanticFormatterCallback; final SfTrackShape trackShape; @@ -1835,6 +2170,8 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { final Widget? startThumbIcon; final Widget? endThumbIcon; final _SfRangeSelectorState state; + final bool hasFocus; + final SfThumb focusedThumb; @override _RenderRangeSelectorElement createElement() => @@ -1864,10 +2201,12 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { deferUpdate: deferUpdate, dragMode: dragMode, labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, dateIntervalType: dateIntervalType, labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, tooltipTextFormatterCallback: tooltipTextFormatterCallback, semanticFormatterCallback: semanticFormatterCallback, trackShape: trackShape, @@ -1881,12 +2220,15 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { textDirection: Directionality.of(context), mediaQueryData: MediaQuery.of(context), state: state, + gestureSettings: MediaQuery.of(context).gestureSettings, ); } @override void updateRenderObject( - BuildContext context, _RenderRangeSelector renderObject) { + BuildContext context, + _RenderRangeSelector renderObject, + ) { renderObject ..min = min ..max = max @@ -1908,10 +2250,12 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { ..deferUpdate = deferUpdate ..dragMode = dragMode ..labelPlacement = labelPlacement + ..edgeLabelPlacement = edgeLabelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat ..dateIntervalType = dateIntervalType ..labelFormatterCallback = labelFormatterCallback + ..onLabelCreated = onLabelCreated ..tooltipTextFormatterCallback = tooltipTextFormatterCallback ..semanticFormatterCallback = semanticFormatterCallback ..trackShape = trackShape @@ -1923,13 +2267,15 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { ..tooltipShape = tooltipShape ..sliderThemeData = rangeSelectorThemeData ..textDirection = Directionality.of(context) - ..mediaQueryData = MediaQuery.of(context); + ..mediaQueryData = MediaQuery.of(context) + ..hasFocus = hasFocus + ..focusedThumb = focusedThumb; } } class _RenderRangeSelectorElement extends RenderObjectElement { _RenderRangeSelectorElement(_RangeSelectorRenderObjectWidget rangeSelector) - : super(rangeSelector); + : super(rangeSelector); final Map _slotToChild = {}; final Map _childToSlot = {}; @@ -2018,7 +2364,10 @@ class _RenderRangeSelectorElement extends RenderObjectElement { @override void moveRenderObjectChild( - RenderObject child, dynamic oldSlot, dynamic newSlot) { + RenderObject child, + dynamic oldSlot, + dynamic newSlot, + ) { assert(false, 'not reachable'); } } @@ -2046,10 +2395,13 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { required bool deferUpdate, required SliderDragMode dragMode, required LabelPlacement labelPlacement, + required EdgeLabelPlacement edgeLabelPlacement, required NumberFormat numberFormat, required DateFormat? dateFormat, required DateIntervalType? dateIntervalType, + // ignore: deprecated_member_use_from_same_package required LabelFormatterCallback labelFormatterCallback, + required RangeSelectorLabelCreatedCallback onLabelCreated, required TooltipTextFormatterCallback tooltipTextFormatterCallback, required RangeSelectorSemanticFormatterCallback? semanticFormatterCallback, required SfTrackShape trackShape, @@ -2063,47 +2415,53 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { required TextDirection textDirection, required MediaQueryData mediaQueryData, required _SfRangeSelectorState state, - }) : _state = state, - _isEnabled = enabled, - _deferUpdateDelay = deferUpdateDelay, - _deferUpdate = deferUpdate, - _semanticFormatterCallback = semanticFormatterCallback, - super( - min: min, - max: max, - values: values, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - interval: interval, - stepSize: stepSize, - stepDuration: stepDuration, - minorTicksPerInterval: minorTicksPerInterval, - showTicks: showTicks, - showLabels: showLabels, - showDividers: showDividers, - enableTooltip: enableTooltip, - shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, - isInversed: isInversed, - enableIntervalSelection: enableIntervalSelection, - dragMode: dragMode, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - trackShape: trackShape, - dividerShape: dividerShape, - overlayShape: overlayShape, - thumbShape: thumbShape, - tickShape: tickShape, - minorTickShape: minorTickShape, - tooltipShape: tooltipShape, - sliderThemeData: rangeSelectorThemeData, - sliderType: SliderType.horizontal, - tooltipPosition: null, - textDirection: textDirection, - mediaQueryData: mediaQueryData) { + required DeviceGestureSettings gestureSettings, + }) : _state = state, + _isEnabled = enabled, + _deferUpdateDelay = deferUpdateDelay, + _deferUpdate = deferUpdate, + + _semanticFormatterCallback = semanticFormatterCallback, + super( + min: min, + max: max, + values: values, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + minorTicksPerInterval: minorTicksPerInterval, + showTicks: showTicks, + showLabels: showLabels, + showDividers: showDividers, + enableTooltip: enableTooltip, + shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, + isInversed: isInversed, + enableIntervalSelection: enableIntervalSelection, + dragMode: dragMode, + labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + trackShape: trackShape, + dividerShape: dividerShape, + overlayShape: overlayShape, + thumbShape: thumbShape, + tickShape: tickShape, + minorTickShape: minorTickShape, + tooltipShape: tooltipShape, + sliderThemeData: rangeSelectorThemeData, + sliderType: SliderType.horizontal, + tooltipPosition: null, + textDirection: textDirection, + mediaQueryData: mediaQueryData, + gestureSettings: gestureSettings, + ) { _inactiveRegionColor = rangeSelectorThemeData.inactiveRegionColor!; _activeRegionColor = rangeSelectorThemeData.activeRegionColor!; } @@ -2179,6 +2537,9 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { @override bool get mounted => _state.mounted; + @override + bool get hasLabelCreated => _state.widget.onLabelCreated != null; + @override AnimationController get overlayStartController => _state.overlayStartController; @@ -2226,19 +2587,22 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { } double get elementsActualHeight => math.max( - 2 * trackOffset.dy, - trackOffset.dy + - maxTrackHeight / 2 + - math.max(actualTickHeight, actualMinorTickHeight) + - actualLabelHeight); + 2 * trackOffset.dy, + trackOffset.dy + + maxTrackHeight / 2 + + math.max(actualTickHeight, actualMinorTickHeight) + + actualLabelHeight, + ); // When the active track height and inactive track height are different, // a small gap is happens between min track height and child // So we adjust track offset to ignore that gap. - double get adjustTrackY => sliderThemeData.activeTrackHeight > - sliderThemeData.inactiveTrackHeight - ? sliderThemeData.activeTrackHeight - sliderThemeData.inactiveTrackHeight - : sliderThemeData.inactiveTrackHeight - sliderThemeData.activeTrackHeight; + double get adjustTrackY => + sliderThemeData.activeTrackHeight > sliderThemeData.inactiveTrackHeight + ? sliderThemeData.activeTrackHeight - + sliderThemeData.inactiveTrackHeight + : sliderThemeData.inactiveTrackHeight - + sliderThemeData.activeTrackHeight; void _updateNewValues(SfRangeValues newValues) { if (_state.widget.onChanged != null) { @@ -2259,8 +2623,9 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { (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(); } @@ -2272,10 +2637,12 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { if (newValues.start != values.start || newValues.end != values.end) { if (_deferUpdate) { _deferUpdateTimer?.cancel(); - _deferUpdateTimer = - Timer(Duration(milliseconds: _deferUpdateDelay), () { - _updateNewValues(newValues); - }); + _deferUpdateTimer = Timer( + Duration(milliseconds: _deferUpdateDelay), + () { + _updateNewValues(newValues); + }, + ); values = newValues; markNeedsPaint(); } else { @@ -2286,6 +2653,12 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { super.updateValues(newValues); } + @override + bool generateLabelsAndTicks() { + return _state.widget.onLabelCreated != null && + _state.widget.controller == null; + } + @override void updateIntervalTappedAndDeferredUpdateValues(SfRangeValues newValues) { if (isIntervalTapped) { @@ -2303,17 +2676,24 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { } } - void _drawRegions(PaintingContext context, Rect trackRect, Offset offset, - Offset startThumbCenter, Offset endThumbCenter) { - final Paint inactivePaint = Paint() - ..isAntiAlias = true - ..color = _inactiveRegionColor; + void _drawRegions( + PaintingContext context, + Rect trackRect, + Offset offset, + Offset startThumbCenter, + Offset endThumbCenter, + ) { + final Paint inactivePaint = + Paint() + ..isAntiAlias = true + ..color = _inactiveRegionColor; if (child != null && child!.size.height > 1 && child!.size.width > 1) { final double halfActiveTrackHeight = sliderThemeData.activeTrackHeight / 2; final double halfInactiveTrackHeight = sliderThemeData.inactiveTrackHeight / 2; - final bool isMaxActive = sliderThemeData.activeTrackHeight > + final bool isMaxActive = + sliderThemeData.activeTrackHeight > sliderThemeData.inactiveTrackHeight; Offset leftThumbCenter; Offset rightThumbCenter; @@ -2331,27 +2711,45 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { final double activeRegionAdj = !isMaxActive ? halfInactiveTrackHeight - halfActiveTrackHeight : 0; context.canvas.drawRect( - Rect.fromLTRB(trackRect.left, offset.dy, leftThumbCenter.dx, - trackRect.top + inactiveRegionAdj), - inactivePaint); - final Paint activePaint = Paint() - ..isAntiAlias = true - ..color = _activeRegionColor; + Rect.fromLTRB( + trackRect.left, + offset.dy, + leftThumbCenter.dx, + trackRect.top + inactiveRegionAdj, + ), + inactivePaint, + ); + final Paint activePaint = + Paint() + ..isAntiAlias = true + ..color = _activeRegionColor; context.canvas.drawRect( - Rect.fromLTRB(leftThumbCenter.dx, offset.dy, rightThumbCenter.dx, - trackRect.top + activeRegionAdj), - activePaint); + Rect.fromLTRB( + leftThumbCenter.dx, + offset.dy, + rightThumbCenter.dx, + trackRect.top + activeRegionAdj, + ), + activePaint, + ); context.canvas.drawRect( - Rect.fromLTRB(rightThumbCenter.dx, offset.dy, trackRect.right, - trackRect.top + inactiveRegionAdj), - inactivePaint); + Rect.fromLTRB( + rightThumbCenter.dx, + offset.dy, + trackRect.right, + trackRect.top + inactiveRegionAdj, + ), + inactivePaint, + ); } } void _increaseStartAction() { if (isInteractive) { - final SfRangeValues newValues = - SfRangeValues(increasedStartValue, values.end); + final SfRangeValues newValues = SfRangeValues( + increasedStartValue, + values.end, + ); if (getNumerizedValue(newValues.start) <= getNumerizedValue(newValues.end)) { _updateNewValues(newValues); @@ -2373,8 +2771,10 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { void _decreaseEndAction() { if (isInteractive) { - final SfRangeValues newValues = - SfRangeValues(values.start, decreasedEndValue); + final SfRangeValues newValues = SfRangeValues( + values.start, + decreasedEndValue, + ); if (getNumerizedValue(newValues.start) <= (getNumerizedValue(newValues.end))) { _updateNewValues(newValues); @@ -2415,16 +2815,19 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { double childHeight = 0.0; double childWidth = 0.0; final double minTrackHeight = math.min( - sliderThemeData.activeTrackHeight, sliderThemeData.inactiveTrackHeight); + 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); + trackCenterLeft.dy + minTrackHeight / 2, + maxTrackHeight / 2 + + minTrackHeight / 2 + + math.max(actualTickHeight, actualMinorTickHeight) + + actualLabelHeight, + ); if (constraints.maxHeight < elementsHeightWithoutChild) { final double actualChildHeight = @@ -2434,7 +2837,8 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { // Reduce the [elementsHeightAfterRenderedChild] from the // actual child height and remaining space in actual layout height to // match the given constraints height. - elementsHeightAfterRenderedChild = elementsHeightAfterRenderedChild - + elementsHeightAfterRenderedChild = + elementsHeightAfterRenderedChild - spaceLeftInActualLayoutHeight - actualChildHeight; } @@ -2442,10 +2846,12 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { if (child != null) { final double maxRadius = trackOffset.dx; final BoxConstraints childConstraints = constraints.deflate( - EdgeInsets.only( - left: maxRadius, - right: maxRadius, - bottom: elementsHeightAfterRenderedChild)); + EdgeInsets.only( + left: maxRadius, + right: maxRadius, + bottom: elementsHeightAfterRenderedChild, + ), + ); child!.layout(childConstraints, parentUsesSize: true); // ignore: avoid_as final BoxParentData childParentData = child!.parentData! as BoxParentData; @@ -2455,30 +2861,39 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { } final BoxConstraints contentConstraints = BoxConstraints.tightFor( - width: actualThumbSize.width, height: actualThumbSize.height); + width: actualThumbSize.width, + height: actualThumbSize.height, + ); startThumbIcon?.layout(contentConstraints, parentUsesSize: true); endThumbIcon?.layout(contentConstraints, parentUsesSize: true); - final double actualWidth = childWidth > 0.0 - ? (childWidth + 2 * trackOffset.dx) - : minTrackWidth + 2 * trackOffset.dx; + final double actualWidth = + childWidth > 0.0 + ? (childWidth + 2 * trackOffset.dx) + : minTrackWidth + 2 * trackOffset.dx; final double actualHeight = childHeight + elementsHeightAfterRenderedChild; size = Size( - constraints.hasBoundedWidth && (constraints.maxWidth < actualWidth) - ? constraints.maxWidth - : actualWidth, - constraints.hasBoundedHeight && (constraints.maxHeight < actualHeight) - ? constraints.maxHeight - : actualHeight); + constraints.hasBoundedWidth && (constraints.maxWidth < actualWidth) + ? constraints.maxWidth + : actualWidth, + constraints.hasBoundedHeight && (constraints.maxHeight < actualHeight) + ? constraints.maxHeight + : actualHeight, + ); generateLabelsAndMajorTicks(); generateMinorTicks(); } @override - void drawRegions(PaintingContext context, Rect trackRect, Offset offset, - Offset startThumbCenter, Offset endThumbCenter) { + void drawRegions( + PaintingContext context, + Rect trackRect, + Offset offset, + Offset startThumbCenter, + Offset endThumbCenter, + ) { _drawRegions(context, trackRect, offset, startThumbCenter, endThumbCenter); } @@ -2491,16 +2906,20 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { context.paintChild(child!, childParentData.offset + offset); childHeight = child!.size.height; if (childHeight >= constraints.maxHeight) { - childHeight -= elementsActualHeight - + childHeight -= + elementsActualHeight - math.max(actualOverlaySize.height, actualThumbSize.height) / 2; } } final Offset actualTrackOffset = Offset( - offset.dx, - offset.dy + - math.max(childHeight - adjustTrackY / 2, - trackOffset.dy - maxTrackHeight / 2)); + offset.dx, + offset.dy + + math.max( + childHeight - adjustTrackY / 2, + trackOffset.dy - maxTrackHeight / 2, + ), + ); drawRangeSliderElements(context, offset, actualTrackOffset); } @@ -2544,27 +2963,33 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { assert(children.isEmpty); final SemanticsConfiguration startSemanticsConfiguration = _createSemanticsConfiguration( - values.start, - increasedStartValue, - decreasedStartValue, - SfThumb.start, - _increaseStartAction, - _decreaseStartAction, - ); + values.start, + increasedStartValue, + decreasedStartValue, + SfThumb.start, + _increaseStartAction, + _decreaseStartAction, + ); final SemanticsConfiguration endSemanticsConfiguration = _createSemanticsConfiguration( - values.end, - increasedEndValue, - decreasedEndValue, - SfThumb.end, - _increaseEndAction, - _decreaseEndAction, - ); + 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); + final Rect leftRect = Rect.fromPoints( + node.rect.topLeft, + node.rect.bottomCenter, + ); + final Rect rightRect = Rect.fromPoints( + node.rect.topCenter, + node.rect.bottomRight, + ); + startSemanticsNode ??= SemanticsNode(); + endSemanticsNode ??= SemanticsNode(); switch (textDirection) { case TextDirection.ltr: startSemanticsNode!.rect = leftRect; @@ -2603,8 +3028,9 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(StringProperty('deferredUpdateDelay', '$_deferUpdateDelay ms')); + properties.add( + StringProperty('deferredUpdateDelay', '$_deferUpdateDelay ms'), + ); debugRangeSliderFillProperties(properties); } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart index 6a55e2c6c..98c66ccc0 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart @@ -1,7 +1,9 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; +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, NumberFormat; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; @@ -10,6 +12,7 @@ import 'common.dart'; import 'constants.dart'; import 'range_slider_base.dart'; import 'slider_shapes.dart'; +import 'theme.dart'; /// A Material Design range slider. /// @@ -110,51 +113,56 @@ import 'slider_shapes.dart'; /// * [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 horizontal [SfRangeSlider]. - const SfRangeSlider( - {Key? key, - this.min = 0.0, - this.max = 1.0, - required this.values, - required this.onChanged, - this.onChangeStart, - this.onChangeEnd, - this.interval, - this.stepSize, - this.stepDuration, - this.minorTicksPerInterval = 0, - this.showTicks = false, - this.showLabels = false, - this.showDividers = false, - this.enableTooltip = false, - this.shouldAlwaysShowTooltip = false, - this.enableIntervalSelection = false, - this.dragMode = SliderDragMode.onThumb, - 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.dividerShape = const SfDividerShape(), - this.overlayShape = const SfOverlayShape(), - this.thumbShape = const SfThumbShape(), - this.tickShape = const SfTickShape(), - this.minorTickShape = const SfMinorTickShape(), - this.tooltipShape = const SfRectangularTooltipShape(), - this.startThumbIcon, - this.endThumbIcon}) - : isInversed = false, - _sliderType = SliderType.horizontal, - _tooltipPosition = null, - assert(min != max), - assert(interval == null || interval > 0), - assert(!enableIntervalSelection || - (enableIntervalSelection && (interval != null && interval > 0))), - super(key: key); + const SfRangeSlider({ + Key? key, + this.min = 0.0, + this.max = 1.0, + required this.values, + required this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.interval, + this.stepSize, + this.stepDuration, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDividers = false, + this.enableTooltip = false, + this.shouldAlwaysShowTooltip = false, + this.enableIntervalSelection = false, + this.dragMode = SliderDragMode.onThumb, + this.inactiveColor, + this.activeColor, + this.labelPlacement = LabelPlacement.onTicks, + this.edgeLabelPlacement = EdgeLabelPlacement.auto, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + // ignore: deprecated_consistency + this.labelFormatterCallback, + this.onLabelCreated, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.dividerShape = const SfDividerShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), + this.startThumbIcon, + this.endThumbIcon, + }) : isInversed = false, + _sliderType = SliderType.horizontal, + _tooltipPosition = null, + assert(min != max), + assert(interval == null || interval > 0), + assert( + !enableIntervalSelection || + (enableIntervalSelection && (interval != null && interval > 0)), + ), + super(key: key); /// Creates a vertical [SfRangeSlider]. /// @@ -196,51 +204,54 @@ class SfRangeSlider extends StatefulWidget { /// 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.onChangeStart, - this.onChangeEnd, - this.interval, - this.stepSize, - this.stepDuration, - this.minorTicksPerInterval = 0, - this.showTicks = false, - this.showLabels = false, - this.showDividers = false, - this.enableTooltip = false, - this.shouldAlwaysShowTooltip = false, - this.enableIntervalSelection = false, - this.dragMode = SliderDragMode.onThumb, - this.isInversed = 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.dividerShape = const SfDividerShape(), - 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 != max), - assert(interval == null || interval > 0), - super(key: key); + const SfRangeSlider.vertical({ + Key? key, + this.min = 0.0, + this.max = 1.0, + required this.values, + required this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.interval, + this.stepSize, + this.stepDuration, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDividers = false, + this.enableTooltip = false, + this.shouldAlwaysShowTooltip = false, + this.enableIntervalSelection = false, + this.dragMode = SliderDragMode.onThumb, + this.isInversed = false, + this.inactiveColor, + this.activeColor, + this.labelPlacement = LabelPlacement.onTicks, + this.edgeLabelPlacement = EdgeLabelPlacement.auto, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + // ignore: deprecated_consistency + this.labelFormatterCallback, + this.onLabelCreated, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.dividerShape = const SfDividerShape(), + 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 != 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. @@ -957,6 +968,24 @@ class SfRangeSlider extends StatefulWidget { /// ``` final LabelPlacement labelPlacement; + /// Position of the edge labels. + /// + /// The edge labels in an axis can be shifted inside + /// the axis bounds or placed at the edges. + /// + /// Defaults to `EdgeLabelPlacement.auto`. + /// + /// Also refer [EdgeLabelPlacement]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfRangeSlider( + /// edgeLabelPlacement: EdgeLabelPlacement.inside, + /// ); + ///} + ///``` + final EdgeLabelPlacement edgeLabelPlacement; + /// Formats the numeric labels. /// /// Defaults to `null`. @@ -984,7 +1013,9 @@ class SfRangeSlider extends StatefulWidget { /// /// See also: /// + // ignore: deprecated_member_use_from_same_package /// * [labelFormatterCallback], for formatting the numeric and date labels. + /// * [onLabelCreated], for formatting and styling numeric and date labels. final NumberFormat? numberFormat; /// Formats the date labels. It is mandatory for date [SfRangeSlider]. @@ -1023,7 +1054,9 @@ class SfRangeSlider extends StatefulWidget { /// /// * [interval], for setting the interval. /// * [numberFormat], for formatting the numeric labels. - /// * [labelFormatterCallback], for formatting the numeric and date label. + // ignore: deprecated_member_use_from_same_package + /// * [labelFormatterCallback], for formatting the numeric and date labels. + /// * [onLabelCreated], for formatting and styling numeric and date labels. /// * [dateIntervalType], for changing the interval type. final DateFormat? dateFormat; @@ -1089,8 +1122,60 @@ class SfRangeSlider extends StatefulWidget { /// }, /// ) /// ``` + @Deprecated('Use `onLabelCreated` instead') final LabelFormatterCallback? labelFormatterCallback; + /// Signature for customizing the label text and style of numeric or date + /// values in the [SfRangeSlider]. + /// + /// * The actual value without formatting is given by `actualValue`. + /// It is either [DateTime] or [double] based on given [values]. + /// * The formatted value based on the numeric or date format + /// is given by `formattedText`. + /// * Text styles can be applied to individual labels using the `textStyle` + /// property. + /// + /// This snippet shows how to format and style labels in [SfRangeSlider]. + /// + /// ```dart + /// SfRangeValues _values = const SfRangeValues(3.0, 7.0); + /// + /// SfRangeSlider( + /// min: 0, + /// max: 8, + /// values: _values, + /// interval: 1, + /// showLabels: true, + /// onChanged: (SfRangeValues newValues) { + /// setState(() { + /// _values = newValues; + /// }); + /// }, + /// onLabelCreated: ( + /// dynamic actualValue, + /// String formattedText, + /// TextStyle textStyle, + /// ) { + /// final int value = actualValue.toInt(); + /// final int startIndex = _values.start.toInt(); + /// final int endIndex = _values.end.toInt(); + /// return RangeSliderLabel( + /// text: value == startIndex || value == endIndex + /// ? '$formattedText' + /// : '$actualValue', + /// textStyle: value == startIndex || value == endIndex + /// ? const TextStyle( + /// color: Colors.blue, + /// fontSize: 18, + /// fontWeight: FontWeight.bold, + /// ) + /// : textStyle, + /// ); + /// }, + /// ) + /// ``` + final RangeSliderLabelCreatedCallback? onLabelCreated; + /// Signature for formatting or changing the whole tooltip label text. /// /// * The actual value without formatting is given by `actualValue`. @@ -1285,76 +1370,138 @@ class SfRangeSlider extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add( - values.toDiagnosticsNode(name: 'values'), - ); + properties.add(values.toDiagnosticsNode(name: 'values')); properties.add(DiagnosticsProperty('min', min)); properties.add(DiagnosticsProperty('max', max)); - properties.add(DiagnosticsProperty('isInversed', isInversed, - defaultValue: false)); - properties.add(ObjectFlagProperty>( - 'onChanged', onChanged, - ifNull: 'disabled')); - properties.add(ObjectFlagProperty>.has( - 'onChangeStart', onChangeStart)); - properties.add(ObjectFlagProperty>.has( - 'onChangeEnd', onChangeEnd)); + properties.add( + DiagnosticsProperty('isInversed', isInversed, defaultValue: false), + ); + properties.add( + ObjectFlagProperty>( + 'onChanged', + onChanged, + ifNull: 'disabled', + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'onChangeStart', + onChangeStart, + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'onChangeEnd', + onChangeEnd, + ), + ); properties.add(DoubleProperty('interval', interval)); properties.add(DoubleProperty('stepSize', stepSize)); if (stepDuration != null) { properties.add(stepDuration!.toDiagnosticsNode(name: 'stepDuration')); } properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); - properties.add(FlagProperty('showTicks', + properties.add( + FlagProperty( + 'showTicks', value: showTicks, ifTrue: 'Ticks are showing', ifFalse: 'Ticks are not showing', - showName: false)); - properties.add(FlagProperty('showLabels', + ), + ); + properties.add( + FlagProperty( + 'showLabels', value: showLabels, ifTrue: 'Labels are showing', ifFalse: 'Labels are not showing', - showName: false)); - properties.add(FlagProperty('showDividers', + ), + ); + properties.add( + FlagProperty( + 'showDividers', value: showDividers, ifTrue: 'Dividers are showing', ifFalse: 'Dividers are not showing', - showName: false)); + ), + ); if (shouldAlwaysShowTooltip) { - properties.add(FlagProperty('shouldAlwaysShowTooltip', + properties.add( + FlagProperty( + 'shouldAlwaysShowTooltip', value: shouldAlwaysShowTooltip, ifTrue: 'Tooltip is always visible', - showName: false)); + ), + ); } else { - properties.add(FlagProperty('enableTooltip', + properties.add( + FlagProperty( + 'enableTooltip', value: enableTooltip, ifTrue: 'Tooltip is enabled', ifFalse: 'Tooltip is disabled', - showName: false)); + ), + ); } - properties.add(FlagProperty('enableIntervalSelection', + properties.add( + FlagProperty( + 'enableIntervalSelection', value: enableIntervalSelection, ifTrue: 'Interval selection is enabled', ifFalse: 'Interval selection is disabled', - showName: false)); + ), + ); properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('inactiveColor', inactiveColor)); - properties - .add(EnumProperty('labelPlacement', labelPlacement)); - properties - .add(DiagnosticsProperty('numberFormat', numberFormat)); + properties.add( + EnumProperty('labelPlacement', labelPlacement), + ); + properties.add( + EnumProperty( + 'edgeLabelPlacement', + edgeLabelPlacement, + ), + ); + properties.add( + DiagnosticsProperty('numberFormat', numberFormat), + ); if (values.start.runtimeType == DateTime && dateFormat != null) { - properties.add(StringProperty('dateFormat', - 'Formatted value is ${dateFormat!.format(values.start)}')); + properties.add( + StringProperty( + 'dateFormat', + 'Formatted value is ${dateFormat!.format(values.start)}', + ), + ); } properties.add( - EnumProperty('dateIntervalType', dateIntervalType)); - properties.add(ObjectFlagProperty.has( - 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); - properties.add(ObjectFlagProperty.has( - 'labelFormatterCallback', labelFormatterCallback)); - properties.add(ObjectFlagProperty.has( - 'semanticFormatterCallback', semanticFormatterCallback)); + EnumProperty('dateIntervalType', dateIntervalType), + ); + properties.add( + ObjectFlagProperty.has( + 'tooltipTextFormatterCallback', + tooltipTextFormatterCallback, + ), + ); + properties.add( + // ignore: deprecated_member_use_from_same_package + ObjectFlagProperty.has( + 'labelFormatterCallback', + // ignore: deprecated_member_use_from_same_package + labelFormatterCallback, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'onLabelCreated', + onLabelCreated, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'semanticFormatterCallback', + semanticFormatterCallback, + ), + ); } } @@ -1368,7 +1515,72 @@ class _SfRangeSliderState extends State late AnimationController tooltipAnimationStartController; late AnimationController tooltipAnimationEndController; late RangeController rangeController; + late Map> _keyboardActionMap; + late FocusNode _focusNode; + bool _focused = false; + SfThumb _focusedThumb = SfThumb.start; final Duration duration = const Duration(milliseconds: 100); + final GlobalKey _renderObjectKey = GlobalKey(); + bool get _enabled => widget.onChanged != null; + FocusNode get rangeSliderFocusNode => _focusNode; + + void _handleNextFocus(NextFocusIntent intent) { + if (_focusedThumb == SfThumb.start) { + setState(() { + _focusedThumb = SfThumb.end; + }); + } else { + // Reset the _focusedThumb to start position when the focus is + // moved to the next widget. + setState(() { + _focusedThumb = SfThumb.start; + }); + FocusScope.of(context).nextFocus(); + } + } + + void _handlePreviousFocus(PreviousFocusIntent intent) { + if (_focusedThumb == SfThumb.end) { + setState(() { + _focusedThumb = SfThumb.start; + }); + } else { + FocusScope.of(context).previousFocus(); + } + } + + void _handleRangeSliderFocusChanged(bool newFocused) { + if (newFocused != _focused) { + setState(() { + _focused = newFocused; + }); + } + } + + void _keyboardActionHandler(SliderKeyIntent intent) { + final TextDirection directionality = Directionality.of( + _renderObjectKey.currentContext!, + ); + final bool shouldIncrease = switch (intent.type) { + SliderKeyType.up => true, + SliderKeyType.down => false, + SliderKeyType.left => directionality == TextDirection.rtl, + SliderKeyType.right => directionality == TextDirection.ltr, + }; + + final _RenderRangeSlider rangeSlider = + _renderObjectKey.currentContext!.findRenderObject()! + as _RenderRangeSlider; + if (_focusedThumb == SfThumb.start) { + return shouldIncrease + ? rangeSlider._increaseStartAction() + : rangeSlider._decreaseStartAction(); + } else { + return shouldIncrease + ? rangeSlider._increaseEndAction() + : rangeSlider._decreaseEndAction(); + } + } void _onChanged(SfRangeValues values) { if (values != widget.values) { @@ -1392,103 +1604,138 @@ class _SfRangeSliderState extends State return formattedText; } + RangeSliderLabel _handleLabelCreated( + dynamic actualValue, + String formattedText, + TextStyle textStyle, + ) { + return RangeSliderLabel(text: formattedText, textStyle: textStyle); + } + String _getFormattedTooltipText(dynamic actualText, String formattedText) { return formattedText; } SfRangeSliderThemeData _getRangeSliderThemeData( - ThemeData themeData, bool isActive) { + ThemeData themeData, + bool isActive, + ) { SfRangeSliderThemeData rangeSliderThemeData = SfRangeSliderTheme.of(context)!; + final bool isMaterial3 = themeData.useMaterial3; + final SfRangeSliderThemeData effectiveThemeData = RangeSliderThemeData( + context, + ); + final Color labelColor = + isMaterial3 + ? themeData.colorScheme.onSurfaceVariant + : isActive + ? themeData.textTheme.bodyLarge!.color!.withValues(alpha: 0.87) + : themeData.colorScheme.onSurface.withValues(alpha: 0.32); final double minTrackHeight = math.min( - rangeSliderThemeData.activeTrackHeight, - rangeSliderThemeData.inactiveTrackHeight); + rangeSliderThemeData.activeTrackHeight, + rangeSliderThemeData.inactiveTrackHeight, + ); final double maxTrackHeight = math.max( - rangeSliderThemeData.activeTrackHeight, - rangeSliderThemeData.inactiveTrackHeight); + rangeSliderThemeData.activeTrackHeight, + rangeSliderThemeData.inactiveTrackHeight, + ); rangeSliderThemeData = rangeSliderThemeData.copyWith( activeTrackHeight: rangeSliderThemeData.activeTrackHeight, inactiveTrackHeight: rangeSliderThemeData.inactiveTrackHeight, tickOffset: rangeSliderThemeData.tickOffset, - inactiveLabelStyle: rangeSliderThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyText1!.copyWith( - color: isActive - ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - activeLabelStyle: rangeSliderThemeData.activeLabelStyle ?? - themeData.textTheme.bodyText1!.copyWith( - color: isActive - ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - tooltipTextStyle: rangeSliderThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyText1! - .copyWith(color: themeData.colorScheme.surface), - inactiveTrackColor: widget.inactiveColor ?? + inactiveLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSliderThemeData.inactiveLabelStyle), + activeLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSliderThemeData.activeLabelStyle), + tooltipTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: + isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.surface, + ) + .merge(rangeSliderThemeData.tooltipTextStyle), + inactiveTrackColor: + widget.inactiveColor ?? rangeSliderThemeData.inactiveTrackColor ?? - themeData.colorScheme.primary.withOpacity(0.24), - activeTrackColor: widget.activeColor ?? + effectiveThemeData.inactiveTrackColor, + activeTrackColor: + widget.activeColor ?? rangeSliderThemeData.activeTrackColor ?? - themeData.colorScheme.primary, - thumbColor: widget.activeColor ?? + effectiveThemeData.activeTrackColor, + thumbColor: + widget.activeColor ?? rangeSliderThemeData.thumbColor ?? - themeData.colorScheme.primary, - activeTickColor: rangeSliderThemeData.activeTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - inactiveTickColor: rangeSliderThemeData.inactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - disabledActiveTickColor: rangeSliderThemeData.disabledActiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.thumbColor, + activeTickColor: + rangeSliderThemeData.activeTickColor ?? + effectiveThemeData.activeTickColor, + inactiveTickColor: + rangeSliderThemeData.inactiveTickColor ?? + effectiveThemeData.inactiveTickColor, + disabledActiveTickColor: + rangeSliderThemeData.disabledActiveTickColor ?? + effectiveThemeData.disabledActiveTickColor, disabledInactiveTickColor: rangeSliderThemeData.disabledInactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - activeMinorTickColor: rangeSliderThemeData.activeMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - inactiveMinorTickColor: rangeSliderThemeData.inactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.disabledInactiveTickColor, + activeMinorTickColor: + rangeSliderThemeData.activeMinorTickColor ?? + effectiveThemeData.activeMinorTickColor, + inactiveMinorTickColor: + rangeSliderThemeData.inactiveMinorTickColor ?? + effectiveThemeData.disabledInactiveMinorTickColor, disabledActiveMinorTickColor: rangeSliderThemeData.disabledActiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveMinorTickColor, disabledInactiveMinorTickColor: rangeSliderThemeData.disabledInactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledInactiveMinorTickColor, // ignore: lines_longer_than_80_chars - overlayColor: widget.activeColor?.withOpacity(0.12) ?? + overlayColor: + widget.activeColor?.withValues(alpha: 0.12) ?? rangeSliderThemeData.overlayColor ?? - themeData.colorScheme.primary.withOpacity(0.12), - inactiveDividerColor: widget.activeColor ?? + effectiveThemeData.overlayColor, + inactiveDividerColor: + widget.activeColor ?? rangeSliderThemeData.inactiveDividerColor ?? - themeData.colorScheme.primary.withOpacity(0.54), - activeDividerColor: widget.inactiveColor ?? + effectiveThemeData.inactiveDividerColor, + activeDividerColor: + widget.inactiveColor ?? rangeSliderThemeData.activeDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.54), + effectiveThemeData.activeDividerColor, disabledInactiveDividerColor: rangeSliderThemeData.disabledInactiveDividerColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveDividerColor, disabledActiveDividerColor: rangeSliderThemeData.disabledActiveDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.12), - disabledActiveTrackColor: rangeSliderThemeData.disabledActiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.32), + effectiveThemeData.disabledActiveDividerColor, + disabledActiveTrackColor: + rangeSliderThemeData.disabledActiveTrackColor ?? + effectiveThemeData.disabledActiveTrackColor, disabledInactiveTrackColor: rangeSliderThemeData.disabledInactiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), - disabledThumbColor: rangeSliderThemeData.disabledThumbColor ?? - Color.alphaBlend(themeData.colorScheme.onSurface.withOpacity(0.38), - themeData.colorScheme.surface), - tooltipBackgroundColor: rangeSliderThemeData.tooltipBackgroundColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(97, 97, 97, 1) - : const Color.fromRGBO(224, 224, 224, 1)), + effectiveThemeData.disabledInactiveTrackColor, + disabledThumbColor: + rangeSliderThemeData.disabledThumbColor ?? + effectiveThemeData.disabledThumbColor, + tooltipBackgroundColor: + rangeSliderThemeData.tooltipBackgroundColor ?? + effectiveThemeData.tooltipBackgroundColor, thumbStrokeColor: rangeSliderThemeData.thumbStrokeColor, overlappingThumbStrokeColor: rangeSliderThemeData.overlappingThumbStrokeColor ?? - themeData.colorScheme.surface, + themeData.colorScheme.surface, activeDividerStrokeColor: rangeSliderThemeData.activeDividerStrokeColor, inactiveDividerStrokeColor: rangeSliderThemeData.inactiveDividerStrokeColor, overlappingTooltipStrokeColor: rangeSliderThemeData.overlappingTooltipStrokeColor ?? - themeData.colorScheme.surface, + themeData.colorScheme.surface, trackCornerRadius: rangeSliderThemeData.trackCornerRadius ?? maxTrackHeight / 2, thumbRadius: rangeSliderThemeData.thumbRadius, @@ -1505,46 +1752,82 @@ class _SfRangeSliderState extends State if (widget._sliderType == SliderType.horizontal) { 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))); + 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)), + ); } else { 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))); + 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)), + ); } } @override void initState() { - overlayStartController = - AnimationController(vsync: this, duration: duration); + overlayStartController = AnimationController( + vsync: this, + duration: duration, + ); overlayEndController = AnimationController(vsync: this, duration: duration); stateController = AnimationController(vsync: this, duration: duration); - startPositionController = - AnimationController(duration: Duration.zero, vsync: this); - endPositionController = - AnimationController(duration: Duration.zero, vsync: this); - tooltipAnimationStartController = - AnimationController(vsync: this, duration: duration); - tooltipAnimationEndController = - AnimationController(vsync: this, duration: duration); + startPositionController = AnimationController( + duration: Duration.zero, + vsync: this, + ); + endPositionController = AnimationController( + duration: Duration.zero, + vsync: this, + ); + 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; + + _keyboardActionMap = >{ + SliderKeyIntent: CallbackAction( + onInvoke: _keyboardActionHandler, + ), + NextFocusIntent: CallbackAction( + onInvoke: _handleNextFocus, + ), + PreviousFocusIntent: CallbackAction( + onInvoke: _handlePreviousFocus, + ), + }; + _focusNode = FocusNode( + skipTraversal: widget.dragMode == SliderDragMode.betweenThumbs, + canRequestFocus: widget.dragMode != SliderDragMode.betweenThumbs, + ); super.initState(); } @override void didUpdateWidget(SfRangeSlider oldWidget) { + if (oldWidget.dragMode != widget.dragMode) { + _focusNode.skipTraversal = + widget.dragMode == SliderDragMode.betweenThumbs; + _focusNode.canRequestFocus = + widget.dragMode != SliderDragMode.betweenThumbs; + } if (oldWidget.shouldAlwaysShowTooltip != widget.shouldAlwaysShowTooltip) { if (widget.shouldAlwaysShowTooltip) { tooltipAnimationStartController.value = 1; @@ -1566,6 +1849,7 @@ class _SfRangeSliderState extends State endPositionController.dispose(); tooltipAnimationStartController.dispose(); tooltipAnimationEndController.dispose(); + _focusNode.dispose(); super.dispose(); } @@ -1575,8 +1859,35 @@ class _SfRangeSliderState extends State widget.onChanged != null && (widget.min != widget.max); final ThemeData themeData = Theme.of(context); - return _RangeSliderRenderObjectWidget( - key: widget.key, + VoidCallback? handleRangeSliderAccessibilityFocus; + switch (themeData.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + break; + case TargetPlatform.windows: + handleRangeSliderAccessibilityFocus = () { + // Automatically activate the slider when it receives a11y focus. + if (!rangeSliderFocusNode.hasFocus && + rangeSliderFocusNode.canRequestFocus) { + rangeSliderFocusNode.requestFocus(); + } + }; + } + final Map keyboardShortcutMap = { + ...switch (MediaQuery.navigationModeOf(context)) { + NavigationMode.directional => KeyboardDirectionalNavShortcutMap, + NavigationMode.traditional => KeyboardNavShortcutMap, + }, + const SingleActivator(LogicalKeyboardKey.tab): const NextFocusIntent(), + const SingleActivator(LogicalKeyboardKey.tab, shift: true): + const PreviousFocusIntent(), + }; + + final Widget result = _RangeSliderRenderObjectWidget( + key: _renderObjectKey, min: widget.min, max: widget.max, values: widget.values, @@ -1594,18 +1905,23 @@ class _SfRangeSliderState extends State shouldAlwaysShowTooltip: widget.shouldAlwaysShowTooltip, dragMode: widget.dragMode, enableIntervalSelection: widget.enableIntervalSelection, - isInversed: widget._sliderType == SliderType.horizontal && + isInversed: + widget._sliderType == SliderType.horizontal && Directionality.of(context) == TextDirection.rtl || widget.isInversed, inactiveColor: - widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), + widget.inactiveColor ?? + themeData.primaryColor.withValues(alpha: 0.24), activeColor: widget.activeColor ?? themeData.primaryColor, labelPlacement: widget.labelPlacement, + edgeLabelPlacement: widget.edgeLabelPlacement, numberFormat: widget.numberFormat ?? NumberFormat('#.##'), dateIntervalType: widget.dateIntervalType, dateFormat: widget.dateFormat, labelFormatterCallback: + // ignore: deprecated_member_use_from_same_package widget.labelFormatterCallback ?? _getFormattedLabelText, + onLabelCreated: widget.onLabelCreated ?? _handleLabelCreated, tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, @@ -1621,8 +1937,22 @@ class _SfRangeSliderState extends State tooltipPosition: widget._tooltipPosition, startThumbIcon: widget.startThumbIcon, endThumbIcon: widget.endThumbIcon, + hasFocus: _focused, + focusedThumb: _focusedThumb, state: this, ); + + return Semantics( + onDidGainAccessibilityFocus: handleRangeSliderAccessibilityFocus, + child: FocusableActionDetector( + actions: _keyboardActionMap, + shortcuts: keyboardShortcutMap, + focusNode: rangeSliderFocusNode, + enabled: _enabled, + onShowFocusHighlight: _handleRangeSliderFocusChanged, + child: result, + ), + ); } } @@ -1650,10 +1980,12 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { required this.inactiveColor, required this.activeColor, required this.labelPlacement, + required this.edgeLabelPlacement, required this.numberFormat, required this.dateFormat, required this.dateIntervalType, required this.labelFormatterCallback, + required this.onLabelCreated, required this.tooltipTextFormatterCallback, required this.semanticFormatterCallback, required this.trackShape, @@ -1669,6 +2001,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { required this.tooltipPosition, required this.sliderType, required this.state, + required this.hasFocus, + required this.focusedThumb, }) : super(key: key); final SliderType sliderType; final SliderTooltipPosition? tooltipPosition; @@ -1696,11 +2030,14 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { final SliderDragMode dragMode; final LabelPlacement labelPlacement; + final EdgeLabelPlacement edgeLabelPlacement; final NumberFormat numberFormat; final DateIntervalType? dateIntervalType; final DateFormat? dateFormat; final SfRangeSliderThemeData rangeSliderThemeData; + // ignore: deprecated_member_use_from_same_package final LabelFormatterCallback labelFormatterCallback; + final RangeSliderLabelCreatedCallback onLabelCreated; final TooltipTextFormatterCallback tooltipTextFormatterCallback; final RangeSliderSemanticFormatterCallback? semanticFormatterCallback; final SfDividerShape dividerShape; @@ -1713,7 +2050,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { final Widget? startThumbIcon; final Widget? endThumbIcon; final _SfRangeSliderState state; - + final bool hasFocus; + final SfThumb focusedThumb; @override _RenderRangeSliderElement createElement() => _RenderRangeSliderElement(this); @@ -1739,10 +2077,12 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { dragMode: dragMode, isInversed: isInversed, labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, dateIntervalType: dateIntervalType, labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, tooltipTextFormatterCallback: tooltipTextFormatterCallback, semanticFormatterCallback: semanticFormatterCallback, trackShape: trackShape, @@ -1758,12 +2098,15 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { textDirection: Directionality.of(context), mediaQueryData: MediaQuery.of(context), state: state, + gestureSettings: MediaQuery.of(context).gestureSettings, ); } @override void updateRenderObject( - BuildContext context, _RenderRangeSlider renderObject) { + BuildContext context, + _RenderRangeSlider renderObject, + ) { renderObject ..min = min ..max = max @@ -1784,10 +2127,12 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { ..dragMode = dragMode ..isInversed = isInversed ..labelPlacement = labelPlacement + ..edgeLabelPlacement = edgeLabelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat ..dateIntervalType = dateIntervalType ..labelFormatterCallback = labelFormatterCallback + ..onLabelCreated = onLabelCreated ..tooltipTextFormatterCallback = tooltipTextFormatterCallback ..semanticFormatterCallback = semanticFormatterCallback ..trackShape = trackShape @@ -1800,13 +2145,15 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { ..tooltipPosition = tooltipPosition ..sliderThemeData = rangeSliderThemeData ..textDirection = Directionality.of(context) - ..mediaQueryData = MediaQuery.of(context); + ..mediaQueryData = MediaQuery.of(context) + ..hasFocus = hasFocus + ..focusedThumb = focusedThumb; } } class _RenderRangeSliderElement extends RenderObjectElement { _RenderRangeSliderElement(_RangeSliderRenderObjectWidget rangeSlider) - : super(rangeSlider); + : super(rangeSlider); final Map _slotToChild = {}; @@ -1890,7 +2237,10 @@ class _RenderRangeSliderElement extends RenderObjectElement { @override void moveRenderObjectChild( - RenderObject child, dynamic oldSlot, dynamic newSlot) { + RenderObject child, + dynamic oldSlot, + dynamic newSlot, + ) { assert(false, 'not reachable'); } } @@ -1916,10 +2266,13 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { required SliderDragMode dragMode, required bool isInversed, required LabelPlacement labelPlacement, + required EdgeLabelPlacement edgeLabelPlacement, required NumberFormat numberFormat, required DateFormat? dateFormat, required DateIntervalType? dateIntervalType, + // ignore: deprecated_member_use_from_same_package required LabelFormatterCallback labelFormatterCallback, + required RangeSliderLabelCreatedCallback onLabelCreated, required TooltipTextFormatterCallback tooltipTextFormatterCallback, required RangeSliderSemanticFormatterCallback? semanticFormatterCallback, required SfTrackShape trackShape, @@ -1935,45 +2288,51 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { required TextDirection textDirection, required MediaQueryData mediaQueryData, required _SfRangeSliderState state, - }) : _state = state, - _onChanged = onChanged, - _semanticFormatterCallback = semanticFormatterCallback, - super( - min: min, - max: max, - values: values, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - interval: interval, - stepSize: stepSize, - stepDuration: stepDuration, - minorTicksPerInterval: minorTicksPerInterval, - showTicks: showTicks, - showLabels: showLabels, - showDividers: showDividers, - enableTooltip: enableTooltip, - shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, - enableIntervalSelection: enableIntervalSelection, - dragMode: dragMode, - isInversed: isInversed, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - trackShape: trackShape, - dividerShape: dividerShape, - overlayShape: overlayShape, - thumbShape: thumbShape, - tickShape: tickShape, - minorTickShape: minorTickShape, - tooltipShape: tooltipShape, - sliderThemeData: sliderThemeData, - sliderType: sliderType, - tooltipPosition: tooltipPosition, - textDirection: textDirection, - mediaQueryData: mediaQueryData); + required DeviceGestureSettings gestureSettings, + }) : _state = state, + _onChanged = onChanged, + + _semanticFormatterCallback = semanticFormatterCallback, + super( + min: min, + max: max, + values: values, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + minorTicksPerInterval: minorTicksPerInterval, + showTicks: showTicks, + showLabels: showLabels, + showDividers: showDividers, + enableTooltip: enableTooltip, + shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, + enableIntervalSelection: enableIntervalSelection, + dragMode: dragMode, + isInversed: isInversed, + labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + trackShape: trackShape, + dividerShape: dividerShape, + overlayShape: overlayShape, + thumbShape: thumbShape, + tickShape: tickShape, + minorTickShape: minorTickShape, + tooltipShape: tooltipShape, + sliderThemeData: sliderThemeData, + sliderType: sliderType, + tooltipPosition: tooltipPosition, + textDirection: textDirection, + mediaQueryData: mediaQueryData, + gestureSettings: gestureSettings, + ); final _SfRangeSliderState _state; @@ -2038,6 +2397,9 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { @override bool get mounted => _state.mounted; + @override + bool get hasLabelCreated => _state.widget.onLabelCreated != null; + @override void updateValues(SfRangeValues newValues) { if (!isIntervalTapped && @@ -2056,8 +2418,10 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { void _increaseStartAction() { if (isInteractive) { - final SfRangeValues newValues = - SfRangeValues(increasedStartValue, values.end); + final SfRangeValues newValues = SfRangeValues( + increasedStartValue, + values.end, + ); if (getNumerizedValue(newValues.start) <= (getNumerizedValue(newValues.end))) { onChanged!(newValues); @@ -2079,8 +2443,10 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { void _decreaseEndAction() { if (isInteractive) { - final SfRangeValues newValues = - SfRangeValues(values.start, decreasedEndValue); + final SfRangeValues newValues = SfRangeValues( + values.start, + decreasedEndValue, + ); if (getNumerizedValue(newValues.start) <= (getNumerizedValue(newValues.end))) { onChanged!(newValues); @@ -2101,7 +2467,9 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { void performLayout() { super.performLayout(); final BoxConstraints contentConstraints = BoxConstraints.tightFor( - width: actualThumbSize.width, height: actualThumbSize.height); + width: actualThumbSize.width, + height: actualThumbSize.height, + ); startThumbIcon?.layout(contentConstraints, parentUsesSize: true); endThumbIcon?.layout(contentConstraints, parentUsesSize: true); } @@ -2129,19 +2497,22 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { @override void paint(PaintingContext context, Offset offset) { - final Offset actualTrackOffset = sliderType == SliderType.horizontal - ? Offset( - offset.dx, - offset.dy + - (size.height - actualHeight) / 2 + - trackOffset.dy - - maxTrackHeight / 2) - : Offset( - offset.dx + - (size.width - actualHeight) / 2 + - trackOffset.dx - - maxTrackHeight / 2, - offset.dy); + final Offset actualTrackOffset = + sliderType == SliderType.horizontal + ? Offset( + offset.dx, + offset.dy + + (size.height - actualHeight) / 2 + + trackOffset.dy - + maxTrackHeight / 2, + ) + : Offset( + offset.dx + + (size.width - actualHeight) / 2 + + trackOffset.dx - + maxTrackHeight / 2, + offset.dy, + ); drawRangeSliderElements(context, offset, actualTrackOffset); } @@ -2185,29 +2556,33 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { assert(children.isEmpty); final SemanticsConfiguration startSemanticsConfiguration = _createSemanticsConfiguration( - values.start, - increasedStartValue, - decreasedStartValue, - SfThumb.start, - _increaseStartAction, - _decreaseStartAction, - ); + values.start, + increasedStartValue, + decreasedStartValue, + SfThumb.start, + _increaseStartAction, + _decreaseStartAction, + ); final SemanticsConfiguration endSemanticsConfiguration = _createSemanticsConfiguration( - values.end, - increasedEndValue, - decreasedEndValue, - SfThumb.end, - _increaseEndAction, - _decreaseEndAction, - ); + values.end, + increasedEndValue, + decreasedEndValue, + SfThumb.end, + _increaseEndAction, + _decreaseEndAction, + ); // Split the semantics node area between the start and end nodes. - final Rect startRect = sliderType == SliderType.horizontal - ? Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter) - : Rect.fromPoints(node.rect.bottomRight, node.rect.centerLeft); - final Rect endRect = sliderType == SliderType.horizontal - ? Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight) - : Rect.fromPoints(node.rect.centerLeft, node.rect.topRight); + final Rect startRect = + sliderType == SliderType.horizontal + ? Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter) + : Rect.fromPoints(node.rect.bottomRight, node.rect.centerLeft); + final Rect endRect = + sliderType == SliderType.horizontal + ? Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight) + : Rect.fromPoints(node.rect.centerLeft, node.rect.topRight); + startSemanticsNode ??= SemanticsNode(); + endSemanticsNode ??= SemanticsNode(); if (sliderType == SliderType.vertical || textDirection == TextDirection.ltr) { startSemanticsNode!.rect = startRect; diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart index f4bbcfd89..7ecb5c2ee 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart @@ -38,10 +38,13 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider required bool enableIntervalSelection, required SliderDragMode dragMode, required LabelPlacement labelPlacement, + required EdgeLabelPlacement edgeLabelPlacement, required NumberFormat numberFormat, required DateFormat? dateFormat, required DateIntervalType? dateIntervalType, + // ignore: deprecated_member_use_from_same_package required LabelFormatterCallback labelFormatterCallback, + required RangeSliderLabelCreatedCallback onLabelCreated, required TooltipTextFormatterCallback tooltipTextFormatterCallback, required SfTrackShape trackShape, required SfDividerShape dividerShape, @@ -56,72 +59,92 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider required TextDirection textDirection, required MediaQueryData mediaQueryData, required bool isInversed, - }) : _values = values!, - _dragMode = dragMode, - _enableIntervalSelection = enableIntervalSelection, - super( - min: min, - max: max, - interval: interval, - stepSize: stepSize, - stepDuration: stepDuration, - minorTicksPerInterval: minorTicksPerInterval, - showTicks: showTicks, - showLabels: showLabels, - showDividers: showDividers, - enableTooltip: enableTooltip, - shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - trackShape: trackShape, - dividerShape: dividerShape, - overlayShape: overlayShape, - thumbShape: thumbShape, - tickShape: tickShape, - minorTickShape: minorTickShape, - tooltipShape: tooltipShape, - sliderThemeData: sliderThemeData, - sliderType: sliderType, - tooltipPosition: tooltipPosition, - textDirection: textDirection, - mediaQueryData: mediaQueryData, - isInversed: isInversed) { + required DeviceGestureSettings gestureSettings, + }) : _values = values!, + _dragMode = dragMode, + _enableIntervalSelection = enableIntervalSelection, + super( + min: min, + max: max, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + minorTicksPerInterval: minorTicksPerInterval, + showTicks: showTicks, + showLabels: showLabels, + showDividers: showDividers, + enableTooltip: enableTooltip, + shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, + labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + trackShape: trackShape, + dividerShape: dividerShape, + overlayShape: overlayShape, + thumbShape: thumbShape, + tickShape: tickShape, + minorTickShape: minorTickShape, + tooltipShape: tooltipShape, + sliderThemeData: sliderThemeData, + sliderType: sliderType, + tooltipPosition: tooltipPosition, + textDirection: textDirection, + mediaQueryData: mediaQueryData, + isInversed: isInversed, + ) { final GestureArenaTeam team = GestureArenaTeam(); if (sliderType == SliderType.horizontal) { - horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = team - ..onStart = _onDragStart - ..onUpdate = _onDragUpdate - ..onEnd = _onDragEnd - ..onCancel = _onDragCancel; + horizontalDragGestureRecognizer = + HorizontalDragGestureRecognizer() + ..team = team + ..onDown = _onDragDown + ..onStart = _onDragStart + ..onUpdate = _onDragUpdate + ..onEnd = _onDragEnd + ..onCancel = _onDragCancel + ..gestureSettings = gestureSettings; } if (sliderType == SliderType.vertical) { - verticalDragGestureRecognizer = VerticalDragGestureRecognizer() - ..team = team - ..onStart = _onVerticalDragStart - ..onUpdate = _onVerticalDragUpdate - ..onEnd = _onVerticalDragEnd - ..onCancel = _onVerticalDragCancel; + verticalDragGestureRecognizer = + VerticalDragGestureRecognizer() + ..team = team + ..onStart = _onVerticalDragStart + ..onUpdate = _onVerticalDragUpdate + ..onEnd = _onVerticalDragEnd + ..onCancel = _onVerticalDragCancel + ..gestureSettings = gestureSettings; } - tapGestureRecognizer = TapGestureRecognizer() - ..team = team - ..onTapDown = _onTapDown - ..onTapUp = _onTapUp; + tapGestureRecognizer = + TapGestureRecognizer() + ..team = team + ..onTapDown = _onTapDown + ..onTapUp = _onTapUp; _overlayStartAnimation = CurvedAnimation( - parent: overlayStartController, curve: Curves.fastOutSlowIn); + parent: overlayStartController, + curve: Curves.fastOutSlowIn, + ); _overlayEndAnimation = CurvedAnimation( - parent: overlayEndController, curve: Curves.fastOutSlowIn); - _stateAnimation = - CurvedAnimation(parent: stateController, curve: Curves.easeInOut); + parent: overlayEndController, + curve: Curves.fastOutSlowIn, + ); + _stateAnimation = CurvedAnimation( + parent: stateController, + curve: Curves.easeInOut, + ); _tooltipStartAnimation = CurvedAnimation( - parent: tooltipAnimationStartController, curve: Curves.fastOutSlowIn); + parent: tooltipAnimationStartController, + curve: Curves.fastOutSlowIn, + ); _tooltipEndAnimation = CurvedAnimation( - parent: tooltipAnimationEndController, curve: Curves.fastOutSlowIn); + parent: tooltipAnimationEndController, + curve: Curves.fastOutSlowIn, + ); if (shouldAlwaysShowTooltip) { tooltipAnimationStartController.value = 1; @@ -130,8 +153,14 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if (isDateTime) { _valuesInMilliseconds = SfRangeValues( - values.start.millisecondsSinceEpoch.toDouble(), - values.end.millisecondsSinceEpoch.toDouble()); + values.start.millisecondsSinceEpoch.toDouble(), + values.end.millisecondsSinceEpoch.toDouble(), + ); + } else { + _values = SfRangeValues( + (values.start as num).toDouble(), + (values.end as num).toDouble(), + ); } unformattedLabels = []; updateTextPainter(); @@ -177,8 +206,92 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider // method, which is used to check whether dragging is started or not. double _interactionStartOffset = 0.0; - static const Duration _positionAnimationDuration = - Duration(milliseconds: 500); + static const Duration _positionAnimationDuration = Duration( + milliseconds: 500, + ); + + SfThumb _focusedThumb = SfThumb.start; + bool _hasFocus = false; + + SfThumb get focusedThumb => _focusedThumb; + set focusedThumb(SfThumb value) { + if (_focusedThumb == value) { + return; + } + _focusedThumb = value; + + if (_hasFocus) { + _updateFocusedThumbState(); + } + markNeedsPaint(); + } + + bool get hasFocus => _hasFocus; + set hasFocus(bool value) { + if (value == _hasFocus) { + return; + } + _hasFocus = value; + _updateForFocus(_hasFocus); + markNeedsSemanticsUpdate(); + } + + void _updateForFocus(bool focused) { + if (shouldAlwaysShowTooltip) { + tooltipAnimationStartController.forward(); + tooltipAnimationEndController.forward(); + + if (focused) { + _updateFocusedThumbOverlay(); + } else { + overlayStartController.reverse(); + overlayEndController.reverse(); + } + } else if (focused) { + _updateFocusedThumbState(); + } else { + overlayStartController.reverse(); + overlayEndController.reverse(); + if (enableTooltip && !shouldAlwaysShowTooltip) { + tooltipAnimationStartController.reverse(); + tooltipAnimationEndController.reverse(); + } + } + } + + void _updateFocusedThumbOverlay() { + if (focusedThumb == SfThumb.start) { + overlayStartController.forward(); + overlayEndController.reverse(); + } else { + overlayEndController.forward(); + overlayStartController.reverse(); + } + } + + void _updateFocusedThumbState() { + if (focusedThumb == SfThumb.start) { + overlayStartController.forward(); + overlayEndController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + tooltipAnimationStartController.forward(); + if (!shouldAlwaysShowTooltip) { + tooltipAnimationEndController.reverse(); + } + } + } else { + overlayEndController.forward(); + overlayStartController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + tooltipAnimationEndController.forward(); + if (!shouldAlwaysShowTooltip) { + tooltipAnimationStartController.reverse(); + } + } + } + } SfRangeValues get values => _values; late SfRangeValues _values; @@ -186,12 +299,29 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if (_values == values) { return; } - _values = values; + _values = + isDateTime + ? values + : SfRangeValues( + (values.start as num).toDouble(), + (values.end as num).toDouble(), + ); if (isDateTime) { _valuesInMilliseconds = SfRangeValues( - _values.start.millisecondsSinceEpoch.toDouble(), - _values.end.millisecondsSinceEpoch.toDouble()); + _values.start.millisecondsSinceEpoch.toDouble(), + _values.end.millisecondsSinceEpoch.toDouble(), + ); + } + + if (generateLabelsAndTicks()) { + // For the range selector, labels and ticks are not generated while moving the thumb. + // After implementing the onLabelCreated callback, the labels are not updated dynamically. + // Therefore, generate labels and ticks if onLabelCreated is not null and the chart’s range + // controller is null. + generateLabelsAndMajorTicks(); + generateMinorTicks(); } + markNeedsPaint(); } @@ -218,15 +348,21 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider RenderBox? get startThumbIcon => _startThumbIcon; RenderBox? _startThumbIcon; set startThumbIcon(RenderBox? value) { - _startThumbIcon = - updateChild(_startThumbIcon, value, ChildElements.startThumbIcon); + _startThumbIcon = updateChild( + _startThumbIcon, + value, + ChildElements.startThumbIcon, + ); } RenderBox? get endThumbIcon => _endThumbIcon; RenderBox? _endThumbIcon; set endThumbIcon(RenderBox? value) { - _endThumbIcon = - updateChild(_endThumbIcon, value, ChildElements.endThumbIcon); + _endThumbIcon = updateChild( + _endThumbIcon, + value, + ChildElements.endThumbIcon, + ); } @override @@ -258,35 +394,55 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider endPositionController.value = getFactorFromValue(actualValues.end); } - double get minThumbGap => sliderType == SliderType.horizontal - ? (actualMax - actualMin) * (8 / actualTrackRect.width).clamp(0.0, 1.0) - : (actualMax - actualMin) * (8 / actualTrackRect.height).clamp(0.0, 1.0); + double get minThumbGap => + isDiscrete + ? 0 + : sliderType == SliderType.horizontal + ? (actualMax - actualMin) * + (8 / actualTrackRect.width).clamp(0.0, 1.0) + : (actualMax - actualMin) * + (8 / actualTrackRect.height).clamp(0.0, 1.0); SfRangeValues get actualValues => isDateTime ? _valuesInMilliseconds : _values; dynamic get increasedStartValue { - return getNextSemanticValue(values.start, semanticActionUnit, - actualValue: actualValues.start); + return getNextSemanticValue( + values.start, + semanticActionUnit, + actualValue: actualValues.start, + ); } dynamic get decreasedStartValue { - return getPrevSemanticValue(values.start, semanticActionUnit, - actualValue: actualValues.start); + return getPrevSemanticValue( + values.start, + semanticActionUnit, + actualValue: actualValues.start, + ); } dynamic get increasedEndValue { - return getNextSemanticValue(values.end, semanticActionUnit, - actualValue: actualValues.end); + return getNextSemanticValue( + values.end, + semanticActionUnit, + actualValue: actualValues.end, + ); } dynamic get decreasedEndValue { - return getPrevSemanticValue(values.end, semanticActionUnit, - actualValue: actualValues.end); + return getPrevSemanticValue( + values.end, + semanticActionUnit, + actualValue: actualValues.end, + ); } RenderBox? updateChild( - RenderBox? oldChild, RenderBox? newChild, ChildElements slot) { + RenderBox? oldChild, + RenderBox? newChild, + ChildElements slot, + ) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -302,9 +458,10 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; - _interactionStartOffset = sliderType == SliderType.horizontal - ? globalToLocal(details.globalPosition).dx - : globalToLocal(details.globalPosition).dy; + _interactionStartOffset = + sliderType == SliderType.horizontal + ? globalToLocal(details.globalPosition).dx + : globalToLocal(details.globalPosition).dy; mainAxisOffset = _interactionStartOffset; _beginInteraction(); } @@ -313,10 +470,13 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _endInteraction(); } - void _onDragStart(DragStartDetails details) { - _isDragStart = true; + void _onDragDown(DragDownDetails details) { _interactionStartOffset = globalToLocal(details.globalPosition).dx; mainAxisOffset = _interactionStartOffset; + } + + void _onDragStart(DragStartDetails details) { + _isDragStart = true; _beginInteraction(); } @@ -367,10 +527,12 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider // field in [endInteraction] method. isIntervalTapped = false; isInteractionEnd = false; - final double startPosition = - getPositionFromValue(actualValues.start.toDouble()); - final double endPosition = - getPositionFromValue(actualValues.end.toDouble()); + final double startPosition = getPositionFromValue( + actualValues.start.toDouble(), + ); + final double endPosition = getPositionFromValue( + actualValues.end.toDouble(), + ); final double leftThumbWidth = (startPosition - mainAxisOffset).abs(); final double rightThumbWidth = (endPosition - mainAxisOffset).abs(); @@ -423,7 +585,8 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider } bool _tappedBetweenThumbs(double startPosition, double endPosition) { - final double thumbRadius = (sliderType == SliderType.horizontal + final double thumbRadius = + (sliderType == SliderType.horizontal ? actualThumbSize.width : actualThumbSize.height) / 2; @@ -485,21 +648,25 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider final double factor = getFactorFromCurrentPosition(); final double value = lerpDouble(actualMin, actualMax, factor)!; final double start = getNumerizedValue( - isDateTime ? values.start : values.start.toDouble()); - final double end = - getNumerizedValue(isDateTime ? values.end : values.end.toDouble()); + isDateTime ? values.start : values.start.toDouble(), + ); + final double end = getNumerizedValue( + isDateTime ? values.end : values.end.toDouble(), + ); switch (activeThumb!) { case SfThumb.start: final double startValue = math.min(value, end - minThumbGap); - final dynamic actualStartValue = - getActualValue(valueInDouble: startValue); + final dynamic actualStartValue = getActualValue( + valueInDouble: startValue, + ); _newValues = values.copyWith(start: actualStartValue); break; case SfThumb.end: final double endValue = math.max(value, start + minThumbGap); - final dynamic actualEndValue = - getActualValue(valueInDouble: endValue); + final dynamic actualEndValue = getActualValue( + valueInDouble: endValue, + ); _newValues = values.copyWith(end: actualEndValue); break; case SfThumb.both: @@ -514,10 +681,12 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider SfRangeValues _getLockRangeValues(double? delta) { final bool isVertical = sliderType == SliderType.vertical; - double startPosition = - getPositionFromValue(getNumerizedValue(_beginValues.start)); - double endPosition = - getPositionFromValue(getNumerizedValue(_beginValues.end)); + double startPosition = getPositionFromValue( + getNumerizedValue(_beginValues.start), + ); + double endPosition = getPositionFromValue( + getNumerizedValue(_beginValues.end), + ); final double lockedRangeWidth = (!isVertical && isInversed) || (isVertical && !isInversed) ? startPosition - endPosition @@ -549,7 +718,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider endPosition = actualTrackRect.bottom - endPosition; } return SfRangeValues( - getValueFromPosition(startPosition), getValueFromPosition(endPosition)); + getValueFromPosition(startPosition), + getValueFromPosition(endPosition), + ); } /// Notify the new values to the users using [onChanged] callback in range @@ -558,13 +729,34 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider markNeedsSemanticsUpdate(); } + @override + bool isActiveLabelValue(currentValue) { + if (currentValue is DateTime && + values.start is DateTime && + values.end is DateTime) { + return currentValue.millisecondsSinceEpoch >= + values.start.millisecondsSinceEpoch && + currentValue.microsecondsSinceEpoch <= + values.end.microsecondsSinceEpoch; + } else { + return currentValue >= values.start && currentValue <= values.end; + } + } + + bool generateLabelsAndTicks() { + return false; + } + void _endInteraction() { if (!isInteractionEnd) { SfRangeValues newValues = values; if (_enableIntervalSelection) { if (isIntervalTapped) { - final double? value = - lerpDouble(actualMin, actualMax, getFactorFromCurrentPosition()); + final double? value = lerpDouble( + actualMin, + actualMax, + getFactorFromCurrentPosition(), + ); newValues = _getSelectedRange(value!); _newValues = newValues; _updatePositionControllerValue(newValues); @@ -612,9 +804,10 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider break; } } else { - start = isDateTime - ? DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()) - : currentLabel; + start = + isDateTime + ? DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()) + : currentLabel; // ignore: unnecessary_this end = this.max; rangeValues = SfRangeValues(start, end); @@ -633,25 +826,31 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider // ignore: avoid_as endDate = newValues.end as DateTime; } - final double startValueFactor = getFactorFromValue(isDateTime - ? startDate!.millisecondsSinceEpoch.toDouble() - : newValues.start); - final double endValueFactor = getFactorFromValue(isDateTime - ? endDate!.millisecondsSinceEpoch.toDouble() - : newValues.end); + final double startValueFactor = getFactorFromValue( + isDateTime + ? startDate!.millisecondsSinceEpoch.toDouble() + : newValues.start, + ); + final double endValueFactor = getFactorFromValue( + isDateTime ? endDate!.millisecondsSinceEpoch.toDouble() : newValues.end, + ); final double startDistanceFactor = (startValueFactor - startPositionController.value).abs(); final double endDistanceFactor = (endValueFactor - endPositionController.value).abs(); - startPositionController.duration = startDistanceFactor != 0.0 - ? _positionAnimationDuration * (1.0 / startDistanceFactor) - : Duration.zero; - endPositionController.duration = endDistanceFactor != 0.0 - ? _positionAnimationDuration * (1.0 / endDistanceFactor) - : Duration.zero; - startPositionController.animateTo(startValueFactor, - curve: Curves.easeInOut); + startPositionController.duration = + startDistanceFactor != 0.0 + ? _positionAnimationDuration * (1.0 / startDistanceFactor) + : Duration.zero; + endPositionController.duration = + endDistanceFactor != 0.0 + ? _positionAnimationDuration * (1.0 / endDistanceFactor) + : Duration.zero; + startPositionController.animateTo( + startValueFactor, + curve: Curves.easeInOut, + ); endPositionController.animateTo(endValueFactor, curve: Curves.easeInOut); } @@ -732,17 +931,21 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider void _handleHover(PointerHoverEvent details) { double cursorPosition = 0.0; - final double startThumbPosition = - getPositionFromValue(actualValues.start.toDouble()); - final double endThumbPosition = - getPositionFromValue(actualValues.end.toDouble()); - cursorPosition = sliderType == SliderType.horizontal - ? details.localPosition.dx - : details.localPosition.dy; + final double startThumbPosition = getPositionFromValue( + actualValues.start.toDouble(), + ); + final double endThumbPosition = getPositionFromValue( + actualValues.end.toDouble(), + ); + cursorPosition = + sliderType == SliderType.horizontal + ? details.localPosition.dx + : details.localPosition.dy; final double startThumbDistance = (cursorPosition - startThumbPosition).abs(); final double endThumbDistance = (cursorPosition - endThumbPosition).abs(); - final double thumbRadius = (sliderType == SliderType.horizontal + final double thumbRadius = + (sliderType == SliderType.horizontal ? actualThumbSize.width : actualThumbSize.height) / 2; @@ -847,7 +1050,8 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider // Ignore overlapping thumb stroke for bottom thumb. showOverlappingThumbStroke = false; if (thumbIcon != null) { - (thumbIcon.parentData! as BoxParentData).offset = thumbCenter - + (thumbIcon.parentData! as BoxParentData).offset = + thumbCenter - Offset(thumbIcon.size.width / 2, thumbIcon.size.height / 2) - paintOffset; } @@ -870,20 +1074,24 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider } // Drawing thumb. - thumbShape.paint(context, thumbCenter, - parentBox: this, - child: thumbIcon, - themeData: sliderThemeData, - currentValues: _values, - enableAnimation: _stateAnimation, - textDirection: textDirection, - thumb: isStartThumbActive ? SfThumb.end : SfThumb.start, - paint: null); + thumbShape.paint( + context, + thumbCenter, + parentBox: this, + child: thumbIcon, + themeData: sliderThemeData, + currentValues: _values, + enableAnimation: _stateAnimation, + textDirection: textDirection, + thumb: isStartThumbActive ? SfThumb.end : SfThumb.start, + paint: null, + ); thumbCenter = isStartThumbActive ? startThumbCenter : endThumbCenter; thumbIcon = isStartThumbActive ? _startThumbIcon : _endThumbIcon; if (thumbIcon != null) { - (thumbIcon.parentData! as BoxParentData).offset = thumbCenter - + (thumbIcon.parentData! as BoxParentData).offset = + thumbCenter - Offset(thumbIcon.size.width / 2, thumbIcon.size.height / 2) - paintOffset; } @@ -905,7 +1113,8 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider ); } - showOverlappingThumbStroke = (getFactorFromValue(actualValues.start) - + showOverlappingThumbStroke = + (getFactorFromValue(actualValues.start) - getFactorFromValue(actualValues.end)) .abs() * (sliderType == SliderType.horizontal @@ -914,119 +1123,145 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider actualThumbSize.width; // Drawing thumb. - thumbShape.paint(context, thumbCenter, - parentBox: this, - child: thumbIcon, - themeData: sliderThemeData, - currentValues: _values, - enableAnimation: _stateAnimation, - textDirection: textDirection, - thumb: isStartThumbActive ? SfThumb.start : SfThumb.end, - paint: null); + thumbShape.paint( + context, + thumbCenter, + parentBox: this, + child: thumbIcon, + themeData: sliderThemeData, + currentValues: _values, + enableAnimation: _stateAnimation, + textDirection: textDirection, + thumb: isStartThumbActive ? SfThumb.start : SfThumb.end, + paint: null, + ); } void _drawTooltip( - PaintingContext context, - Offset endThumbCenter, - Offset startThumbCenter, - Offset offset, - Offset actualTrackOffset, - Rect trackRect) { + PaintingContext context, + Offset endThumbCenter, + Offset startThumbCenter, + Offset offset, + Offset actualTrackOffset, + Rect trackRect, + ) { if (willDrawTooltip || shouldAlwaysShowTooltip) { - final Paint paint = Paint() - ..color = sliderThemeData.tooltipBackgroundColor! - ..style = PaintingStyle.fill - ..strokeWidth = 0; + final Paint paint = + Paint() + ..color = sliderThemeData.tooltipBackgroundColor! + ..style = PaintingStyle.fill + ..strokeWidth = 0; final bool isStartThumbActive = activeThumb == SfThumb.start; Offset thumbCenter = isStartThumbActive ? endThumbCenter : startThumbCenter; - dynamic actualText = (sliderType == SliderType.horizontal) - ? getValueFromPosition(thumbCenter.dx - offset.dx) - : getValueFromPosition(trackRect.bottom - thumbCenter.dy); + dynamic actualText = + (sliderType == SliderType.horizontal) + ? getValueFromPosition(thumbCenter.dx - offset.dx) + : getValueFromPosition(trackRect.bottom - thumbCenter.dy); String tooltipText = tooltipTextFormatterCallback( - actualText, getFormattedText(actualText)); - TextSpan textSpan = - TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); + actualText, + getFormattedText(actualText), + ); + TextSpan textSpan = TextSpan( + text: tooltipText, + style: sliderThemeData.tooltipTextStyle, + ); textPainter.text = textSpan; textPainter.layout(); Rect? bottomTooltipRect; if (tooltipShape is SfPaddleTooltipShape) { bottomTooltipRect = getPaddleTooltipRect( - textPainter, - actualThumbSize.width / 2, - Offset(actualTrackOffset.dx, tooltipStartY), - thumbCenter, - trackRect, - sliderThemeData); + textPainter, + actualThumbSize.width / 2, + Offset(actualTrackOffset.dx, tooltipStartY), + thumbCenter, + trackRect, + sliderThemeData, + ); } else if (tooltipShape is SfRectangularTooltipShape) { bottomTooltipRect = getRectangularTooltipRect( - textPainter, - Offset(actualTrackOffset.dx, tooltipStartY), - thumbCenter, - trackRect, - sliderThemeData); + textPainter, + Offset(actualTrackOffset.dx, tooltipStartY), + thumbCenter, + trackRect, + sliderThemeData, + ); } // Ignore overlapping tooltip stroke for bottom tooltip. showOverlappingTooltipStroke = false; - tooltipShape.paint(context, thumbCenter, - Offset(actualTrackOffset.dx, tooltipStartY), textPainter, - parentBox: this, - sliderThemeData: sliderThemeData, - paint: paint, - animation: isStartThumbActive - ? _tooltipEndAnimation - : _tooltipStartAnimation, - trackRect: trackRect); + tooltipShape.paint( + context, + thumbCenter, + Offset(actualTrackOffset.dx, tooltipStartY), + textPainter, + parentBox: this, + sliderThemeData: sliderThemeData, + paint: paint, + animation: + isStartThumbActive ? _tooltipEndAnimation : _tooltipStartAnimation, + trackRect: trackRect, + ); thumbCenter = isStartThumbActive ? startThumbCenter : endThumbCenter; - actualText = (sliderType == SliderType.horizontal) - ? getValueFromPosition(thumbCenter.dx - offset.dx) - : getValueFromPosition(trackRect.bottom - thumbCenter.dy); + actualText = + (sliderType == SliderType.horizontal) + ? getValueFromPosition(thumbCenter.dx - offset.dx) + : getValueFromPosition(trackRect.bottom - thumbCenter.dy); tooltipText = tooltipTextFormatterCallback( - actualText, getFormattedText(actualText)); - textSpan = - TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); + actualText, + getFormattedText(actualText), + ); + textSpan = TextSpan( + text: tooltipText, + style: sliderThemeData.tooltipTextStyle, + ); textPainter.text = textSpan; textPainter.layout(); Rect? topTooltipRect; if (tooltipShape is SfPaddleTooltipShape) { topTooltipRect = getPaddleTooltipRect( - textPainter, - actualThumbSize.width / 2, - Offset(actualTrackOffset.dx, tooltipStartY), - thumbCenter, - trackRect, - sliderThemeData); + textPainter, + actualThumbSize.width / 2, + Offset(actualTrackOffset.dx, tooltipStartY), + thumbCenter, + trackRect, + sliderThemeData, + ); } else if (tooltipShape is SfRectangularTooltipShape) { topTooltipRect = getRectangularTooltipRect( - textPainter, - Offset(actualTrackOffset.dx, tooltipStartY), - thumbCenter, - trackRect, - sliderThemeData); + textPainter, + Offset(actualTrackOffset.dx, tooltipStartY), + thumbCenter, + trackRect, + sliderThemeData, + ); } if (bottomTooltipRect != null && topTooltipRect != null) { final Rect overlapRect = topTooltipRect.intersect(bottomTooltipRect); - showOverlappingTooltipStroke = sliderType == SliderType.horizontal - ? overlapRect.right > overlapRect.left - : overlapRect.top < overlapRect.bottom; + showOverlappingTooltipStroke = + sliderType == SliderType.horizontal + ? overlapRect.right > overlapRect.left + : overlapRect.top < overlapRect.bottom; } - tooltipShape.paint(context, thumbCenter, - Offset(actualTrackOffset.dx, tooltipStartY), textPainter, - parentBox: this, - sliderThemeData: sliderThemeData, - paint: paint, - animation: isStartThumbActive - ? _tooltipStartAnimation - : _tooltipEndAnimation, - trackRect: trackRect); + tooltipShape.paint( + context, + thumbCenter, + Offset(actualTrackOffset.dx, tooltipStartY), + textPainter, + parentBox: this, + sliderThemeData: sliderThemeData, + paint: paint, + animation: + isStartThumbActive ? _tooltipStartAnimation : _tooltipEndAnimation, + trackRect: trackRect, + ); } } @@ -1035,8 +1270,13 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider void updateIntervalTappedAndDeferredUpdateValues(SfRangeValues values); /// Used to draw active and inactive regions. - void drawRegions(PaintingContext context, Rect trackRect, Offset offset, - Offset startThumbCenter, Offset endThumbCenter) { + void drawRegions( + PaintingContext context, + Rect trackRect, + Offset offset, + Offset startThumbCenter, + Offset endThumbCenter, + ) { /// This override method declared to draw active and inactive region in /// the [SfRangeSelector]. } @@ -1048,15 +1288,18 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _overlayStartAnimation.addListener(markNeedsPaint); _overlayEndAnimation.addListener(markNeedsPaint); startPositionController.addListener(markNeedsPaint); - startPositionController - .addStatusListener(_handlePositionControllerStatusChange); + startPositionController.addStatusListener( + _handlePositionControllerStatusChange, + ); endPositionController.addListener(markNeedsPaint); - endPositionController - .addStatusListener(_handlePositionControllerStatusChange); + endPositionController.addStatusListener( + _handlePositionControllerStatusChange, + ); _stateAnimation.addListener(markNeedsPaint); _tooltipStartAnimation.addListener(markNeedsPaint); - _tooltipStartAnimation - .addStatusListener(_handleTooltipAnimationStatusChange); + _tooltipStartAnimation.addStatusListener( + _handleTooltipAnimationStatusChange, + ); _tooltipEndAnimation.addListener(markNeedsPaint); _tooltipEndAnimation.addStatusListener(_handleTooltipAnimationStatusChange); } @@ -1067,21 +1310,33 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _overlayStartAnimation.removeListener(markNeedsPaint); _overlayEndAnimation.removeListener(markNeedsPaint); startPositionController.removeListener(markNeedsPaint); - startPositionController - .removeStatusListener(_handlePositionControllerStatusChange); + startPositionController.removeStatusListener( + _handlePositionControllerStatusChange, + ); endPositionController.removeListener(markNeedsPaint); - endPositionController - .removeStatusListener(_handlePositionControllerStatusChange); + endPositionController.removeStatusListener( + _handlePositionControllerStatusChange, + ); _stateAnimation.removeListener(markNeedsPaint); _tooltipStartAnimation.removeListener(markNeedsPaint); - _tooltipStartAnimation - .removeStatusListener(_handleTooltipAnimationStatusChange); + _tooltipStartAnimation.removeStatusListener( + _handleTooltipAnimationStatusChange, + ); _tooltipEndAnimation.removeListener(markNeedsPaint); - _tooltipEndAnimation - .removeStatusListener(_handleTooltipAnimationStatusChange); + _tooltipEndAnimation.removeStatusListener( + _handleTooltipAnimationStatusChange, + ); super.detach(); } + @override + void dispose() { + horizontalDragGestureRecognizer?.dispose(); + verticalDragGestureRecognizer?.dispose(); + tapGestureRecognizer.dispose(); + super.dispose(); + } + @override MouseCursor get cursor => MouseCursor.defer; @@ -1128,7 +1383,8 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider @override void handleEvent(PointerEvent event, HitTestEntry entry) { - final bool isDesktop = kIsWeb || + final bool isDesktop = + kIsWeb || defaultTargetPlatform == TargetPlatform.macOS || defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.linux; @@ -1141,54 +1397,97 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider /// This method used to draw all slider elements such as track, tick, /// dividers, thumb, and overlay. void drawRangeSliderElements( - PaintingContext context, Offset offset, Offset actualTrackOffset) { + PaintingContext context, + Offset offset, + Offset actualTrackOffset, + ) { Offset startThumbCenter; Offset endThumbCenter; // Drawing track. - final Rect trackRect = - trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); - double thumbStartPosition = isIntervalTapped - ? startPositionController.value - : getFactorFromValue(actualValues.start); - double thumbEndPosition = isIntervalTapped - ? endPositionController.value - : getFactorFromValue(actualValues.end); + final Rect trackRect = trackShape.getPreferredRect( + this, + sliderThemeData, + actualTrackOffset, + ); + double thumbStartPosition = + isIntervalTapped + ? startPositionController.value + : getFactorFromValue(actualValues.start); + double thumbEndPosition = + isIntervalTapped + ? endPositionController.value + : getFactorFromValue(actualValues.end); if (sliderType == SliderType.horizontal) { thumbStartPosition = thumbStartPosition * trackRect.width; thumbEndPosition = thumbEndPosition * trackRect.width; - startThumbCenter = - Offset(trackRect.left + thumbStartPosition, trackRect.center.dy); - endThumbCenter = - Offset(trackRect.left + thumbEndPosition, trackRect.center.dy); + startThumbCenter = Offset( + trackRect.left + thumbStartPosition, + trackRect.center.dy, + ); + endThumbCenter = Offset( + trackRect.left + thumbEndPosition, + trackRect.center.dy, + ); } else { thumbStartPosition = thumbStartPosition * trackRect.height; thumbEndPosition = thumbEndPosition * trackRect.height; - startThumbCenter = - Offset(trackRect.center.dx, trackRect.bottom - thumbStartPosition); - endThumbCenter = - Offset(trackRect.center.dx, trackRect.bottom - thumbEndPosition); + startThumbCenter = Offset( + trackRect.center.dx, + trackRect.bottom - thumbStartPosition, + ); + endThumbCenter = Offset( + trackRect.center.dx, + trackRect.bottom - thumbEndPosition, + ); } trackShape.paint( - context, actualTrackOffset, null, startThumbCenter, endThumbCenter, - parentBox: this, - themeData: sliderThemeData, - currentValues: values, - enableAnimation: _stateAnimation, - textDirection: textDirection, - activePaint: null, - inactivePaint: null); + context, + actualTrackOffset, + null, + startThumbCenter, + endThumbCenter, + parentBox: this, + themeData: sliderThemeData, + currentValues: values, + enableAnimation: _stateAnimation, + textDirection: textDirection, + activePaint: null, + inactivePaint: null, + ); if (showLabels || showTicks || showDividers) { - drawLabelsTicksAndDividers(context, trackRect, offset, null, - startThumbCenter, endThumbCenter, _stateAnimation, null, _values); + drawLabelsTicksAndDividers( + context, + trackRect, + offset, + null, + startThumbCenter, + endThumbCenter, + _stateAnimation, + null, + _values, + ); } drawRegions(context, trackRect, offset, startThumbCenter, endThumbCenter); _drawOverlayAndThumb(context, offset, endThumbCenter, startThumbCenter); - _drawTooltip(context, endThumbCenter, startThumbCenter, offset, - actualTrackOffset, trackRect); + // To avoid positioning the tooltip text on the edge, used a 5px margin. + final Rect tooltipTargetRect = Rect.fromLTWH( + 5.0, + trackRect.top, + mediaQueryData.size.width - 5.0, + trackRect.height, + ); + _drawTooltip( + context, + endThumbCenter, + startThumbCenter, + offset, + actualTrackOffset, + tooltipTargetRect, + ); } /// Describe the semantics of the start thumb. @@ -1199,23 +1498,45 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider /// Used to set values to the shape properties in diagnostics tree. void debugRangeSliderFillProperties(DiagnosticPropertiesBuilder properties) { - properties.add(StringProperty( - 'thumbSize', thumbShape.getPreferredSize(sliderThemeData).toString())); - properties.add(StringProperty( + properties.add( + StringProperty( + 'thumbSize', + thumbShape.getPreferredSize(sliderThemeData).toString(), + ), + ); + properties.add( + StringProperty( 'activeDividerSize', dividerShape .getPreferredSize(sliderThemeData, isActive: true) - .toString())); - properties.add(StringProperty( + .toString(), + ), + ); + properties.add( + StringProperty( 'inactiveDividerSize', dividerShape .getPreferredSize(sliderThemeData, isActive: false) - .toString())); - properties.add(StringProperty('overlaySize', - overlayShape.getPreferredSize(sliderThemeData).toString())); - properties.add(StringProperty( - 'tickSize', tickShape.getPreferredSize(sliderThemeData).toString())); - properties.add(StringProperty('minorTickSize', - minorTickShape.getPreferredSize(sliderThemeData).toString())); + .toString(), + ), + ); + properties.add( + StringProperty( + 'overlaySize', + overlayShape.getPreferredSize(sliderThemeData).toString(), + ), + ); + properties.add( + StringProperty( + 'tickSize', + tickShape.getPreferredSize(sliderThemeData).toString(), + ), + ); + properties.add( + StringProperty( + 'minorTickSize', + minorTickShape.getPreferredSize(sliderThemeData).toString(), + ), + ); } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider.dart b/packages/syncfusion_flutter_sliders/lib/src/slider.dart index d9cd2e9af..f1e45c0b8 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider.dart @@ -15,6 +15,7 @@ import 'common.dart'; import 'constants.dart'; import 'slider_base.dart'; import 'slider_shapes.dart'; +import 'theme.dart'; /// A Material Design slider. /// @@ -110,46 +111,49 @@ import 'slider_shapes.dart'; /// * [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 horizontal [SfSlider]. - const SfSlider( - {Key? key, - this.min = 0.0, - this.max = 1.0, - required this.value, - required this.onChanged, - this.onChangeStart, - this.onChangeEnd, - this.interval, - this.stepSize, - this.stepDuration, - this.minorTicksPerInterval = 0, - this.showTicks = false, - this.showLabels = false, - this.showDividers = false, - this.enableTooltip = false, - this.shouldAlwaysShowTooltip = 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.dividerShape = const SfDividerShape(), - this.overlayShape = const SfOverlayShape(), - this.thumbShape = const SfThumbShape(), - this.tickShape = const SfTickShape(), - this.minorTickShape = const SfMinorTickShape(), - this.tooltipShape = const SfRectangularTooltipShape(), - this.thumbIcon}) - : isInversed = false, - _sliderType = SliderType.horizontal, - _tooltipPosition = null, - assert(min != max), - assert(interval == null || interval > 0), - super(key: key); + const SfSlider({ + Key? key, + this.min = 0.0, + this.max = 1.0, + required this.value, + required this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.interval, + this.stepSize, + this.stepDuration, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDividers = false, + this.enableTooltip = false, + this.shouldAlwaysShowTooltip = false, + this.activeColor, + this.inactiveColor, + this.labelPlacement = LabelPlacement.onTicks, + this.edgeLabelPlacement = EdgeLabelPlacement.auto, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + // ignore: deprecated_consistency + this.labelFormatterCallback, + this.onLabelCreated, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.dividerShape = const SfDividerShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), + this.thumbIcon, + }) : isInversed = false, + _sliderType = SliderType.horizontal, + _tooltipPosition = null, + assert(min != max), + assert(interval == null || interval > 0), + super(key: key); /// Creates a vertical [SfSlider]. /// @@ -193,48 +197,52 @@ class SfSlider extends StatefulWidget { /// 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.onChangeStart, - this.onChangeEnd, - this.interval, - this.stepSize, - this.stepDuration, - this.minorTicksPerInterval = 0, - this.showTicks = false, - this.showLabels = false, - this.showDividers = false, - this.enableTooltip = false, - this.shouldAlwaysShowTooltip = false, - this.isInversed = 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.dividerShape = const SfDividerShape(), - 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 != max), - assert(interval == null || interval > 0), - super(key: key); + const SfSlider.vertical({ + Key? key, + this.min = 0.0, + this.max = 1.0, + required this.value, + required this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.interval, + this.stepSize, + this.stepDuration, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDividers = false, + this.enableTooltip = false, + this.shouldAlwaysShowTooltip = false, + this.isInversed = false, + this.activeColor, + this.inactiveColor, + this.labelPlacement = LabelPlacement.onTicks, + this.edgeLabelPlacement = EdgeLabelPlacement.auto, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + // ignore: deprecated_consistency + this.labelFormatterCallback, + this.onLabelCreated, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.dividerShape = const SfDividerShape(), + 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 != max), + assert(interval == null || interval > 0), + super(key: key); /// This is used to determine the type of the slider which is horizontal or /// vertical. @@ -863,6 +871,24 @@ class SfSlider extends StatefulWidget { /// ``` final LabelPlacement labelPlacement; + /// Position of the edge labels. + /// + /// The edge labels in an axis can be shifted inside + /// the axis bounds or placed at the edges. + /// + /// Defaults to `EdgeLabelPlacement.auto`. + /// + /// Also refer [EdgeLabelPlacement]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return SfSlider( + /// edgeLabelPlacement: EdgeLabelPlacement.inside, + /// ); + ///} + ///``` + final EdgeLabelPlacement edgeLabelPlacement; + /// Formats the numeric labels. /// /// Defaults to `null`. @@ -890,7 +916,9 @@ class SfSlider extends StatefulWidget { /// /// See also: /// + // ignore: deprecated_member_use_from_same_package /// * [labelFormatterCallback], for formatting the numeric and date labels. + /// * [onLabelCreated], for formatting and styling numeric and date labels. final NumberFormat? numberFormat; /// Formats the date labels. It is mandatory for date [SfSlider]. @@ -928,7 +956,9 @@ class SfSlider extends StatefulWidget { /// /// * [interval], for setting the interval. /// * [numberFormat], for formatting the numeric labels. + // ignore: deprecated_member_use_from_same_package /// * [labelFormatterCallback], for formatting the numeric and date label. + /// * [onLabelCreated], for formatting and styling numeric and date label. /// * [dateIntervalType], for changing the interval type. final DateFormat? dateFormat; @@ -993,8 +1023,57 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` + @Deprecated('Use `onLabelCreated` instead') final LabelFormatterCallback? labelFormatterCallback; + /// Signature for customizing the label text and style of numeric or date + /// values in the [SfSlider]. + /// + /// * The actual value without formatting is given by `actualValue`. + /// It is either [DateTime] or [double] based on given [value]. + /// * The formatted value based on the numeric or + /// date format is given by `formattedText`. + /// * Text styles can be applied to individual labels using the `textStyle` + /// property. + /// + /// This snippet shows how to format and style labels in [SfSlider]. + /// + /// ```dart + /// double _value = 4.0; + /// + /// SfSlider( + /// min: 0, + /// max: 8, + /// value: _value, + /// showLabels: true, + /// interval: 1, + /// onChanged: (dynamic value) { + /// setState(() { + /// _value = value; + /// }); + /// }, + /// onLabelCreated: ( + /// dynamic actualValue, + /// String formattedText, + /// TextStyle textStyle, + /// ) { + /// return SliderLabel( + /// text: actualValue == _value.toInt() + /// ? '$formattedText' + /// : '$actualValue', + /// textStyle: actualValue == _value.toInt() + /// ? const TextStyle( + /// color: Color.fromARGB(255, 243, 33, 229), + /// fontSize: 18, + /// fontWeight: FontWeight.bold, + /// ) + /// : textStyle, + /// ); + /// }, + /// ) + /// ``` + final SliderLabelCreatedCallback? onLabelCreated; + /// Signature for formatting or changing the whole tooltip label text. /// /// * The actual value without formatting is given by `actualValue`. @@ -1150,66 +1229,124 @@ class SfSlider extends StatefulWidget { properties.add(DiagnosticsProperty('value', value)); properties.add(DiagnosticsProperty('min', min)); properties.add(DiagnosticsProperty('max', max)); - properties.add(DiagnosticsProperty('isInversed', isInversed, - defaultValue: false)); - properties.add(ObjectFlagProperty>( - 'onChanged', onChanged, - ifNull: 'disabled')); - properties.add(ObjectFlagProperty>.has( - 'onChangeStart', onChangeStart)); - properties.add(ObjectFlagProperty>.has( - 'onChangeEnd', onChangeEnd)); + properties.add( + DiagnosticsProperty('isInversed', isInversed, defaultValue: false), + ); + properties.add( + ObjectFlagProperty>( + 'onChanged', + onChanged, + ifNull: 'disabled', + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'onChangeStart', + onChangeStart, + ), + ); + properties.add( + ObjectFlagProperty>.has('onChangeEnd', onChangeEnd), + ); properties.add(DoubleProperty('interval', interval)); properties.add(DoubleProperty('stepSize', stepSize)); if (stepDuration != null) { properties.add(stepDuration!.toDiagnosticsNode(name: 'stepDuration')); } properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); - properties.add(FlagProperty('showTicks', + properties.add( + FlagProperty( + 'showTicks', value: showTicks, ifTrue: 'Ticks are showing', ifFalse: 'Ticks are not showing', - showName: false)); - properties.add(FlagProperty('showLabels', + ), + ); + properties.add( + FlagProperty( + 'showLabels', value: showLabels, ifTrue: 'Labels are showing', ifFalse: 'Labels are not showing', - showName: false)); - properties.add(FlagProperty('showDividers', + ), + ); + properties.add( + FlagProperty( + 'showDividers', value: showDividers, ifTrue: 'Dividers are showing', ifFalse: 'Dividers are not showing', - showName: false)); + ), + ); if (shouldAlwaysShowTooltip) { - properties.add(FlagProperty('shouldAlwaysShowTooltip', + properties.add( + FlagProperty( + 'shouldAlwaysShowTooltip', value: shouldAlwaysShowTooltip, ifTrue: 'Tooltip is always visible', - showName: false)); + ), + ); } else { - properties.add(FlagProperty('enableTooltip', + properties.add( + FlagProperty( + 'enableTooltip', value: enableTooltip, ifTrue: 'Tooltip is enabled', ifFalse: 'Tooltip is disabled', - showName: false)); + ), + ); } properties.add(ColorProperty('activeColor', activeColor)); properties.add(ColorProperty('inactiveColor', inactiveColor)); - properties - .add(EnumProperty('labelPlacement', labelPlacement)); - properties - .add(DiagnosticsProperty('numberFormat', numberFormat)); + properties.add( + EnumProperty('labelPlacement', labelPlacement), + ); + properties.add( + EnumProperty( + 'edgeLabelPlacement', + edgeLabelPlacement, + ), + ); + properties.add( + DiagnosticsProperty('numberFormat', numberFormat), + ); if (value.runtimeType == DateTime && dateFormat != null) { - properties.add(StringProperty( - 'dateFormat', 'Formatted value is ${dateFormat!.format(value)}')); + properties.add( + StringProperty( + 'dateFormat', + 'Formatted value is ${dateFormat!.format(value)}', + ), + ); } properties.add( - EnumProperty('dateIntervalType', dateIntervalType)); - properties.add(ObjectFlagProperty.has( - 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); - properties.add(ObjectFlagProperty.has( - 'labelFormatterCallback', labelFormatterCallback)); - properties.add(ObjectFlagProperty>.has( - 'semanticFormatterCallback', semanticFormatterCallback)); + EnumProperty('dateIntervalType', dateIntervalType), + ); + properties.add( + ObjectFlagProperty.has( + 'tooltipTextFormatterCallback', + tooltipTextFormatterCallback, + ), + ); + properties.add( + // ignore: deprecated_member_use_from_same_package + ObjectFlagProperty.has( + 'labelFormatterCallback', + // ignore: deprecated_member_use_from_same_package + labelFormatterCallback, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'onLabelCreated', + onLabelCreated, + ), + ); + properties.add( + ObjectFlagProperty>.has( + 'semanticFormatterCallback', + semanticFormatterCallback, + ), + ); } } @@ -1217,8 +1354,38 @@ class _SfSliderState extends State with TickerProviderStateMixin { late AnimationController overlayController; late AnimationController stateController; late AnimationController tooltipAnimationController; + late Map> _keyboardActionMap; + late FocusNode _focusNode; + bool _focused = false; Timer? tooltipDelayTimer; final Duration duration = const Duration(milliseconds: 100); + final GlobalKey _renderObjectKey = GlobalKey(); + bool get _enabled => widget.onChanged != null; + FocusNode get sliderFocusNode => _focusNode; + + void _handleSliderFocusChanged(bool newFocused) { + if (newFocused != _focused) { + setState(() { + _focused = newFocused; + }); + } + } + + void _keyboardActionHandler(SliderKeyIntent intent) { + final TextDirection directionality = Directionality.of( + _renderObjectKey.currentContext!, + ); + final bool shouldIncrease = switch (intent.type) { + SliderKeyType.up => true, + SliderKeyType.down => false, + SliderKeyType.left => directionality == TextDirection.rtl, + SliderKeyType.right => directionality == TextDirection.ltr, + }; + + final _RenderSlider slider = + _renderObjectKey.currentContext!.findRenderObject()! as _RenderSlider; + return shouldIncrease ? slider.increaseAction() : slider.decreaseAction(); + } void _onChanged(dynamic value) { if (value != widget.value) { @@ -1242,86 +1409,122 @@ class _SfSliderState extends State with TickerProviderStateMixin { return formattedText; } + SliderLabel _handleLabelCreated( + dynamic actualValue, + String formattedText, + TextStyle textStyle, + ) { + return SliderLabel(text: formattedText, textStyle: textStyle); + } + String _getFormattedTooltipText(dynamic actualText, String formattedText) { return formattedText; } SfSliderThemeData _getSliderThemeData(ThemeData themeData, bool isActive) { SfSliderThemeData sliderThemeData = SfSliderTheme.of(context); + + /// An instance for SlidersThemeData class + final SfSliderThemeData effectiveThemeData = SlidersThemeData(context); + final bool isMaterial3 = themeData.useMaterial3; + final Color labelColor = + isMaterial3 + ? themeData.colorScheme.onSurfaceVariant + : isActive + ? themeData.textTheme.bodyLarge!.color!.withValues(alpha: 0.87) + : themeData.colorScheme.onSurface.withValues(alpha: 0.32); final double minTrackHeight = math.min( - sliderThemeData.activeTrackHeight, sliderThemeData.inactiveTrackHeight); + sliderThemeData.activeTrackHeight, + sliderThemeData.inactiveTrackHeight, + ); final double maxTrackHeight = math.max( - sliderThemeData.activeTrackHeight, sliderThemeData.inactiveTrackHeight); + sliderThemeData.activeTrackHeight, + sliderThemeData.inactiveTrackHeight, + ); sliderThemeData = sliderThemeData.copyWith( activeTrackHeight: sliderThemeData.activeTrackHeight, inactiveTrackHeight: sliderThemeData.inactiveTrackHeight, tickOffset: sliderThemeData.tickOffset, - inactiveLabelStyle: sliderThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyText1!.copyWith( - color: isActive - ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - activeLabelStyle: sliderThemeData.activeLabelStyle ?? - themeData.textTheme.bodyText1!.copyWith( - color: isActive - ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - tooltipTextStyle: sliderThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyText1! - .copyWith(color: themeData.colorScheme.surface), - inactiveTrackColor: widget.inactiveColor ?? + inactiveLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(sliderThemeData.inactiveLabelStyle), + activeLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(sliderThemeData.activeLabelStyle), + tooltipTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: + isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.surface, + ) + .merge(sliderThemeData.tooltipTextStyle), + inactiveTrackColor: + widget.inactiveColor ?? sliderThemeData.inactiveTrackColor ?? - themeData.colorScheme.primary.withOpacity(0.24), - activeTrackColor: widget.activeColor ?? + effectiveThemeData.inactiveTrackColor, + activeTrackColor: + widget.activeColor ?? sliderThemeData.activeTrackColor ?? - themeData.colorScheme.primary, - thumbColor: widget.activeColor ?? + effectiveThemeData.activeTrackColor, + thumbColor: + widget.activeColor ?? sliderThemeData.thumbColor ?? - themeData.colorScheme.primary, - activeTickColor: sliderThemeData.activeTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - inactiveTickColor: sliderThemeData.inactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - disabledActiveTickColor: sliderThemeData.disabledActiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - disabledInactiveTickColor: sliderThemeData.disabledInactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - activeMinorTickColor: sliderThemeData.activeMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), - inactiveMinorTickColor: sliderThemeData.inactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.thumbColor, + activeTickColor: + sliderThemeData.activeTickColor ?? effectiveThemeData.activeTickColor, + inactiveTickColor: + sliderThemeData.inactiveTickColor ?? + effectiveThemeData.inactiveTickColor, + disabledActiveTickColor: + sliderThemeData.disabledActiveTickColor ?? + effectiveThemeData.disabledActiveTickColor, + disabledInactiveTickColor: + sliderThemeData.disabledInactiveTickColor ?? + effectiveThemeData.disabledInactiveTickColor, + activeMinorTickColor: + sliderThemeData.activeMinorTickColor ?? + effectiveThemeData.activeMinorTickColor, + inactiveMinorTickColor: + sliderThemeData.inactiveMinorTickColor ?? + effectiveThemeData.inactiveMinorTickColor, disabledActiveMinorTickColor: sliderThemeData.disabledActiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveMinorTickColor, disabledInactiveMinorTickColor: sliderThemeData.disabledInactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - // ignore: lines_longer_than_80_chars - overlayColor: widget.activeColor?.withOpacity(0.12) ?? + effectiveThemeData.disabledInactiveMinorTickColor, + overlayColor: + widget.activeColor?.withValues(alpha: 0.12) ?? sliderThemeData.overlayColor ?? - themeData.colorScheme.primary.withOpacity(0.12), - inactiveDividerColor: widget.activeColor ?? + effectiveThemeData.overlayColor, + inactiveDividerColor: + widget.activeColor ?? sliderThemeData.inactiveDividerColor ?? - themeData.colorScheme.primary.withOpacity(0.54), - activeDividerColor: widget.inactiveColor ?? + effectiveThemeData.inactiveDividerColor, + activeDividerColor: + widget.inactiveColor ?? sliderThemeData.activeDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.54), + effectiveThemeData.activeDividerColor, disabledInactiveDividerColor: sliderThemeData.disabledInactiveDividerColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), - disabledActiveDividerColor: sliderThemeData.disabledActiveDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.12), - disabledActiveTrackColor: sliderThemeData.disabledActiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.32), - disabledInactiveTrackColor: sliderThemeData.disabledInactiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), - disabledThumbColor: sliderThemeData.disabledThumbColor ?? - Color.alphaBlend(themeData.colorScheme.onSurface.withOpacity(0.38), - themeData.colorScheme.surface), - tooltipBackgroundColor: sliderThemeData.tooltipBackgroundColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(97, 97, 97, 1) - : const Color.fromRGBO(224, 224, 224, 1)), + effectiveThemeData.disabledInactiveDividerColor, + disabledActiveDividerColor: + sliderThemeData.disabledActiveDividerColor ?? + effectiveThemeData.disabledActiveDividerColor, + disabledActiveTrackColor: + sliderThemeData.disabledActiveTrackColor ?? + effectiveThemeData.disabledActiveTrackColor, + disabledInactiveTrackColor: + sliderThemeData.disabledInactiveTrackColor ?? + effectiveThemeData.disabledInactiveTrackColor, + disabledThumbColor: + sliderThemeData.disabledThumbColor ?? + effectiveThemeData.disabledThumbColor, + tooltipBackgroundColor: + sliderThemeData.tooltipBackgroundColor ?? + effectiveThemeData.tooltipBackgroundColor, thumbStrokeColor: sliderThemeData.thumbStrokeColor, activeDividerStrokeColor: sliderThemeData.activeDividerStrokeColor, inactiveDividerStrokeColor: sliderThemeData.inactiveDividerStrokeColor, @@ -1339,20 +1542,24 @@ class _SfSliderState extends State with TickerProviderStateMixin { ); if (widget._sliderType == SliderType.horizontal) { 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))); + 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)), + ); } else { 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))); + 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)), + ); } } @@ -1373,10 +1580,18 @@ class _SfSliderState extends State with TickerProviderStateMixin { super.initState(); overlayController = AnimationController(vsync: this, duration: duration); stateController = AnimationController(vsync: this, duration: duration); - tooltipAnimationController = - AnimationController(vsync: this, duration: duration); + tooltipAnimationController = AnimationController( + vsync: this, + duration: duration, + ); stateController.value = widget.onChanged != null && (widget.min != widget.max) ? 1.0 : 0.0; + _keyboardActionMap = >{ + SliderKeyIntent: CallbackAction( + onInvoke: _keyboardActionHandler, + ), + }; + _focusNode = FocusNode(); } @override @@ -1384,104 +1599,150 @@ class _SfSliderState extends State with TickerProviderStateMixin { overlayController.dispose(); stateController.dispose(); tooltipAnimationController.dispose(); + _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + + VoidCallback? handleSliderAccessibilityFocus; + switch (themeData.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + break; + case TargetPlatform.windows: + handleSliderAccessibilityFocus = () { + // Automatically activate the slider when it receives a11y focus. + if (!sliderFocusNode.hasFocus && sliderFocusNode.canRequestFocus) { + sliderFocusNode.requestFocus(); + } + }; + } + final Map keyboardShortcutMap = + switch (MediaQuery.navigationModeOf(context)) { + NavigationMode.directional => KeyboardDirectionalNavShortcutMap, + NavigationMode.traditional => KeyboardNavShortcutMap, + }; + final bool isActive = widget.onChanged != null && (widget.min != widget.max); - final ThemeData themeData = Theme.of(context); - return _SliderRenderObjectWidget( - key: widget.key, - min: widget.min, - max: widget.max, - value: widget.value, - onChanged: isActive ? _onChanged : null, - onChangeStart: widget.onChangeStart != null ? _onChangeStart : null, - onChangeEnd: widget.onChangeEnd != null ? _onChangeEnd : null, - interval: widget.interval, - stepSize: widget.stepSize, - stepDuration: widget.stepDuration, - minorTicksPerInterval: widget.minorTicksPerInterval, - showTicks: widget.showTicks, - showLabels: widget.showLabels, - showDividers: widget.showDividers, - enableTooltip: widget.enableTooltip, - shouldAlwaysShowTooltip: widget.shouldAlwaysShowTooltip, - isInversed: widget._sliderType == SliderType.horizontal && - Directionality.of(context) == TextDirection.rtl || - widget.isInversed, - inactiveColor: - widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), - activeColor: widget.activeColor ?? themeData.primaryColor, - labelPlacement: widget.labelPlacement, - numberFormat: widget.numberFormat ?? NumberFormat('#.##'), - dateIntervalType: widget.dateIntervalType, - dateFormat: widget.dateFormat, - labelFormatterCallback: - widget.labelFormatterCallback ?? _getFormattedLabelText, - tooltipTextFormatterCallback: - widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, - semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape, - dividerShape: widget.dividerShape, - 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); + final Widget result = _SliderRenderObjectWidget( + key: _renderObjectKey, + min: widget.min, + max: widget.max, + value: widget.value, + onChanged: isActive ? _onChanged : null, + onChangeStart: widget.onChangeStart != null ? _onChangeStart : null, + onChangeEnd: widget.onChangeEnd != null ? _onChangeEnd : null, + interval: widget.interval, + stepSize: widget.stepSize, + stepDuration: widget.stepDuration, + minorTicksPerInterval: widget.minorTicksPerInterval, + showTicks: widget.showTicks, + showLabels: widget.showLabels, + showDividers: widget.showDividers, + enableTooltip: widget.enableTooltip, + shouldAlwaysShowTooltip: widget.shouldAlwaysShowTooltip, + isInversed: + widget._sliderType == SliderType.horizontal && + Directionality.of(context) == TextDirection.rtl || + widget.isInversed, + inactiveColor: + widget.inactiveColor ?? + themeData.primaryColor.withValues(alpha: 0.24), + activeColor: widget.activeColor ?? themeData.primaryColor, + labelPlacement: widget.labelPlacement, + edgeLabelPlacement: widget.edgeLabelPlacement, + numberFormat: widget.numberFormat ?? NumberFormat('#.##'), + dateIntervalType: widget.dateIntervalType, + dateFormat: widget.dateFormat, + labelFormatterCallback: + // ignore: deprecated_member_use_from_same_package + widget.labelFormatterCallback ?? _getFormattedLabelText, + onLabelCreated: widget.onLabelCreated ?? _handleLabelCreated, + tooltipTextFormatterCallback: + widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, + semanticFormatterCallback: widget.semanticFormatterCallback, + trackShape: widget.trackShape, + dividerShape: widget.dividerShape, + 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, + hasFocus: _focused, + state: this, + ); + + return Semantics( + onDidGainAccessibilityFocus: handleSliderAccessibilityFocus, + child: FocusableActionDetector( + actions: _keyboardActionMap, + shortcuts: keyboardShortcutMap, + focusNode: sliderFocusNode, + enabled: _enabled, + onShowFocusHighlight: _handleSliderFocusChanged, + child: result, + ), + ); } } class _SliderRenderObjectWidget extends RenderObjectWidget { - const _SliderRenderObjectWidget( - {Key? key, - required this.min, - required this.max, - required this.value, - required this.onChanged, - required this.onChangeStart, - required this.onChangeEnd, - required this.interval, - required this.stepSize, - required this.stepDuration, - required this.minorTicksPerInterval, - required this.showTicks, - required this.showLabels, - required this.showDividers, - required this.enableTooltip, - required this.shouldAlwaysShowTooltip, - required this.isInversed, - 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.dividerShape, - 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); + const _SliderRenderObjectWidget({ + Key? key, + required this.min, + required this.max, + required this.value, + required this.onChanged, + required this.onChangeStart, + required this.onChangeEnd, + required this.interval, + required this.stepSize, + required this.stepDuration, + required this.minorTicksPerInterval, + required this.showTicks, + required this.showLabels, + required this.showDividers, + required this.enableTooltip, + required this.shouldAlwaysShowTooltip, + required this.isInversed, + required this.inactiveColor, + required this.activeColor, + required this.labelPlacement, + required this.edgeLabelPlacement, + required this.numberFormat, + required this.dateFormat, + required this.dateIntervalType, + required this.labelFormatterCallback, + required this.onLabelCreated, + required this.tooltipTextFormatterCallback, + required this.semanticFormatterCallback, + required this.trackShape, + required this.dividerShape, + 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, + required this.hasFocus, + }) : super(key: key); final SliderType sliderType; final SliderTooltipPosition? tooltipPosition; @@ -1507,11 +1768,14 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { final Color activeColor; final LabelPlacement labelPlacement; + final EdgeLabelPlacement edgeLabelPlacement; final NumberFormat numberFormat; final DateIntervalType? dateIntervalType; final DateFormat? dateFormat; final SfSliderThemeData sliderThemeData; + // ignore: deprecated_member_use_from_same_package final LabelFormatterCallback labelFormatterCallback; + final SliderLabelCreatedCallback onLabelCreated; final TooltipTextFormatterCallback tooltipTextFormatterCallback; final SfSliderSemanticFormatterCallback? semanticFormatterCallback; final SfDividerShape dividerShape; @@ -1523,6 +1787,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { final SfTooltipShape tooltipShape; final Widget? thumbIcon; final _SfSliderState state; + final bool hasFocus; @override _RenderSliderElement createElement() => _RenderSliderElement(this); @@ -1530,42 +1795,47 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) { return _RenderSlider( - min: min, - max: max, - value: value, - onChanged: onChanged, - onChangeStart: onChangeStart, - onChangeEnd: onChangeEnd, - minorTicksPerInterval: minorTicksPerInterval, - interval: interval, - stepSize: stepSize, - stepDuration: stepDuration, - showTicks: showTicks, - showLabels: showLabels, - showDividers: showDividers, - enableTooltip: enableTooltip, - shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, - isInversed: isInversed, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - semanticFormatterCallback: semanticFormatterCallback, - trackShape: trackShape, - dividerShape: dividerShape, - 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); + min: min, + max: max, + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + minorTicksPerInterval: minorTicksPerInterval, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + showTicks: showTicks, + showLabels: showLabels, + showDividers: showDividers, + enableTooltip: enableTooltip, + shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, + isInversed: isInversed, + labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + semanticFormatterCallback: semanticFormatterCallback, + trackShape: trackShape, + dividerShape: dividerShape, + 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, + gestureSettings: MediaQuery.of(context).gestureSettings, + hasFocus: hasFocus, + ); } @override @@ -1588,10 +1858,12 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { ..shouldAlwaysShowTooltip = shouldAlwaysShowTooltip ..isInversed = isInversed ..labelPlacement = labelPlacement + ..edgeLabelPlacement = edgeLabelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat ..dateIntervalType = dateIntervalType ..labelFormatterCallback = labelFormatterCallback + ..onLabelCreated = onLabelCreated ..tooltipTextFormatterCallback = tooltipTextFormatterCallback ..semanticFormatterCallback = semanticFormatterCallback ..trackShape = trackShape @@ -1604,7 +1876,8 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { ..sliderThemeData = sliderThemeData ..tooltipPosition = tooltipPosition ..textDirection = Directionality.of(context) - ..mediaQueryData = MediaQuery.of(context); + ..mediaQueryData = MediaQuery.of(context) + ..hasFocus = hasFocus; } } @@ -1688,7 +1961,10 @@ class _RenderSliderElement extends RenderObjectElement { @override void moveRenderObjectChild( - RenderObject child, dynamic oldSlot, dynamic newSlot) { + RenderObject child, + dynamic oldSlot, + dynamic newSlot, + ) { assert(false, 'not reachable'); } } @@ -1712,10 +1988,13 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { required bool shouldAlwaysShowTooltip, required bool isInversed, required LabelPlacement labelPlacement, + required EdgeLabelPlacement edgeLabelPlacement, required NumberFormat numberFormat, required DateFormat? dateFormat, required DateIntervalType? dateIntervalType, + // ignore: deprecated_member_use_from_same_package required LabelFormatterCallback labelFormatterCallback, + required SliderLabelCreatedCallback onLabelCreated, required TooltipTextFormatterCallback tooltipTextFormatterCallback, required SfSliderSemanticFormatterCallback? semanticFormatterCallback, required SfTrackShape trackShape, @@ -1731,74 +2010,90 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { 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, - minorTicksPerInterval: minorTicksPerInterval, - showTicks: showTicks, - showLabels: showLabels, - showDividers: showDividers, - enableTooltip: enableTooltip, - shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, - isInversed: isInversed, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - trackShape: trackShape, - dividerShape: dividerShape, - overlayShape: overlayShape, - thumbShape: thumbShape, - tickShape: tickShape, - minorTickShape: minorTickShape, - tooltipShape: tooltipShape, - tooltipPosition: tooltipPosition, - sliderThemeData: sliderThemeData, - textDirection: textDirection, - mediaQueryData: mediaQueryData, - ) { + required DeviceGestureSettings gestureSettings, + required bool hasFocus, + }) : _state = state, + _value = value, + _semanticFormatterCallback = semanticFormatterCallback, + _onChanged = onChanged, + _hasFocus = hasFocus, + super( + min: min, + max: max, + sliderType: sliderType, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + minorTicksPerInterval: minorTicksPerInterval, + showTicks: showTicks, + showLabels: showLabels, + showDividers: showDividers, + enableTooltip: enableTooltip, + shouldAlwaysShowTooltip: shouldAlwaysShowTooltip, + isInversed: isInversed, + labelPlacement: labelPlacement, + edgeLabelPlacement: edgeLabelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + onLabelCreated: onLabelCreated, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + trackShape: trackShape, + dividerShape: dividerShape, + overlayShape: overlayShape, + thumbShape: thumbShape, + tickShape: tickShape, + minorTickShape: minorTickShape, + tooltipShape: tooltipShape, + tooltipPosition: tooltipPosition, + sliderThemeData: sliderThemeData, + textDirection: textDirection, + mediaQueryData: mediaQueryData, + ) { final GestureArenaTeam team = GestureArenaTeam(); if (sliderType == SliderType.horizontal) { - horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = team - ..onStart = _onDragStart - ..onUpdate = _onDragUpdate - ..onEnd = _onDragEnd - ..onCancel = _onDragCancel; + horizontalDragGestureRecognizer = + HorizontalDragGestureRecognizer() + ..team = team + ..onStart = _onDragStart + ..onUpdate = _onDragUpdate + ..onEnd = _onDragEnd + ..onCancel = _onDragCancel + ..gestureSettings = gestureSettings; } if (sliderType == SliderType.vertical) { - verticalDragGestureRecognizer = VerticalDragGestureRecognizer() - ..team = team - ..onStart = _onVerticalDragStart - ..onUpdate = _onVerticalDragUpdate - ..onEnd = _onVerticalDragEnd - ..onCancel = _onVerticalDragCancel; + verticalDragGestureRecognizer = + VerticalDragGestureRecognizer() + ..team = team + ..onStart = _onVerticalDragStart + ..onUpdate = _onVerticalDragUpdate + ..onEnd = _onVerticalDragEnd + ..onCancel = _onVerticalDragCancel + ..gestureSettings = gestureSettings; } - tapGestureRecognizer = TapGestureRecognizer() - ..team = team - ..onTapDown = _onTapDown - ..onTapUp = _onTapUp; + tapGestureRecognizer = + TapGestureRecognizer() + ..team = team + ..onTapDown = _onTapDown + ..onTapUp = _onTapUp; _overlayAnimation = CurvedAnimation( - parent: _state.overlayController, curve: Curves.fastOutSlowIn); + parent: _state.overlayController, + curve: Curves.fastOutSlowIn, + ); _stateAnimation = CurvedAnimation( - parent: _state.stateController, curve: Curves.easeInOut); + parent: _state.stateController, + curve: Curves.easeInOut, + ); _tooltipAnimation = CurvedAnimation( - parent: _state.tooltipAnimationController, curve: Curves.fastOutSlowIn); + parent: _state.tooltipAnimationController, + curve: Curves.fastOutSlowIn, + ); if (shouldAlwaysShowTooltip) { _state.tooltipAnimationController.value = 1; @@ -1837,6 +2132,9 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { final Map childToSlot = {}; + @override + bool get hasLabelCreated => _state.widget.onLabelCreated != null; + dynamic get value => _value; dynamic _value; @@ -1909,17 +2207,54 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { } dynamic get _increasedValue { - return getNextSemanticValue(value, semanticActionUnit, - actualValue: actualValue); + return getNextSemanticValue( + value, + semanticActionUnit, + actualValue: actualValue, + ); } dynamic get _decreasedValue { - return getPrevSemanticValue(value, semanticActionUnit, - actualValue: actualValue); + return getPrevSemanticValue( + value, + semanticActionUnit, + actualValue: actualValue, + ); + } + + bool get hasFocus => _hasFocus; + bool _hasFocus; + set hasFocus(bool value) { + if (value == _hasFocus) { + return; + } + _hasFocus = value; + + _updateForFocus(_hasFocus); + markNeedsSemanticsUpdate(); + } + + void _updateForFocus(bool focused) { + if (focused) { + _state.overlayController.forward(); + + if (enableTooltip) { + willDrawTooltip = true; + _state.tooltipAnimationController.forward(); + } + } else { + _state.overlayController.reverse(); + if (enableTooltip && !shouldAlwaysShowTooltip) { + _state.tooltipAnimationController.reverse(); + } + } } RenderBox? _updateChild( - RenderBox? oldChild, RenderBox? newChild, ChildElements slot) { + RenderBox? oldChild, + RenderBox? newChild, + ChildElements slot, + ) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -1935,9 +2270,10 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; - mainAxisOffset = sliderType == SliderType.horizontal - ? globalToLocal(details.globalPosition).dx - : globalToLocal(details.globalPosition).dy; + mainAxisOffset = + sliderType == SliderType.horizontal + ? globalToLocal(details.globalPosition).dx + : globalToLocal(details.globalPosition).dy; _beginInteraction(); } @@ -2047,34 +2383,58 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { } } - void _drawTooltip(PaintingContext context, Offset thumbCenter, Offset offset, - Offset actualTrackOffset, Rect trackRect) { + void _drawTooltip( + PaintingContext context, + Offset thumbCenter, + Offset offset, + Offset actualTrackOffset, + Rect trackRect, + ) { if (willDrawTooltip || shouldAlwaysShowTooltip) { - final Paint paint = Paint() - ..color = sliderThemeData.tooltipBackgroundColor! - ..style = PaintingStyle.fill - ..strokeWidth = 0; - - final dynamic actualText = sliderType == SliderType.horizontal - ? getValueFromPosition(thumbCenter.dx - offset.dx) - : getValueFromPosition(trackRect.bottom - thumbCenter.dy); + final Paint paint = + Paint() + ..color = sliderThemeData.tooltipBackgroundColor! + ..style = PaintingStyle.fill + ..strokeWidth = 0; + + final dynamic actualText = + sliderType == SliderType.horizontal + ? getValueFromPosition(thumbCenter.dx - offset.dx) + : getValueFromPosition(trackRect.bottom - thumbCenter.dy); final String tooltipText = tooltipTextFormatterCallback( - actualText, getFormattedText(actualText)); - final TextSpan textSpan = - TextSpan(text: tooltipText, style: sliderThemeData.tooltipTextStyle); + actualText, + getFormattedText(actualText), + ); + final TextSpan textSpan = TextSpan( + text: tooltipText, + style: sliderThemeData.tooltipTextStyle, + ); textPainter.text = textSpan; textPainter.layout(); - tooltipShape.paint(context, thumbCenter, - Offset(actualTrackOffset.dx, tooltipStartY), textPainter, - parentBox: this, - sliderThemeData: sliderThemeData, - paint: paint, - animation: _tooltipAnimation, - trackRect: trackRect); + tooltipShape.paint( + context, + thumbCenter, + Offset(actualTrackOffset.dx, tooltipStartY), + textPainter, + parentBox: this, + sliderThemeData: sliderThemeData, + paint: paint, + animation: _tooltipAnimation, + trackRect: trackRect, + ); } } + @override + bool isActiveLabelValue(currentValue) { + if (currentValue is DateTime && value is DateTime) { + return currentValue.millisecondsSinceEpoch <= + value.millisecondsSinceEpoch; + } + return currentValue <= value; + } + void increaseAction() { if (isInteractive) { onChanged!(_increasedValue); @@ -2137,6 +2497,14 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { } } + @override + void dispose() { + horizontalDragGestureRecognizer?.dispose(); + verticalDragGestureRecognizer?.dispose(); + tapGestureRecognizer.dispose(); + super.dispose(); + } + @override MouseCursor get cursor => MouseCursor.defer; @@ -2153,7 +2521,9 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { void performLayout() { super.performLayout(); final BoxConstraints contentConstraints = BoxConstraints.tightFor( - width: actualThumbSize.width, height: actualThumbSize.height); + width: actualThumbSize.width, + height: actualThumbSize.height, + ); _thumbIcon?.layout(contentConstraints, parentUsesSize: true); } @@ -2181,72 +2551,114 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { @override void paint(PaintingContext context, Offset offset) { - final Offset actualTrackOffset = sliderType == SliderType.horizontal - ? Offset( - offset.dx, - offset.dy + - (size.height - actualHeight) / 2 + - trackOffset.dy - - maxTrackHeight / 2) - : Offset( - offset.dx + - (size.width - actualHeight) / 2 + - trackOffset.dx - - maxTrackHeight / 2, - offset.dy); + final Offset actualTrackOffset = + sliderType == SliderType.horizontal + ? Offset( + offset.dx, + offset.dy + + (size.height - actualHeight) / 2 + + trackOffset.dy - + maxTrackHeight / 2, + ) + : Offset( + offset.dx + + (size.width - actualHeight) / 2 + + trackOffset.dx - + maxTrackHeight / 2, + offset.dy, + ); // Drawing track. - final Rect trackRect = - trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); - final double thumbPosition = getFactorFromValue(actualValue) * + final Rect trackRect = trackShape.getPreferredRect( + this, + sliderThemeData, + actualTrackOffset, + ); + final double thumbPosition = + getFactorFromValue(actualValue) * (sliderType == SliderType.horizontal ? trackRect.width : trackRect.height); - final Offset thumbCenter = sliderType == SliderType.horizontal - ? Offset(trackRect.left + thumbPosition, trackRect.center.dy) - : Offset(trackRect.center.dx, trackRect.bottom - thumbPosition); - - trackShape.paint(context, actualTrackOffset, thumbCenter, null, null, - parentBox: this, - currentValue: _value, - currentValues: null, - themeData: sliderThemeData, - enableAnimation: _stateAnimation, - textDirection: textDirection, - activePaint: null, - inactivePaint: null); + final Offset thumbCenter = + sliderType == SliderType.horizontal + ? Offset(trackRect.left + thumbPosition, trackRect.center.dy) + : Offset(trackRect.center.dx, trackRect.bottom - thumbPosition); + + trackShape.paint( + context, + actualTrackOffset, + thumbCenter, + null, + null, + parentBox: this, + currentValue: _value, + themeData: sliderThemeData, + enableAnimation: _stateAnimation, + textDirection: textDirection, + activePaint: null, + inactivePaint: null, + ); if (showLabels || showTicks || showDividers) { - drawLabelsTicksAndDividers(context, trackRect, offset, thumbCenter, null, - null, _stateAnimation, _value, null); + drawLabelsTicksAndDividers( + context, + trackRect, + offset, + thumbCenter, + null, + null, + _stateAnimation, + _value, + null, + ); } // Drawing overlay. - overlayShape.paint(context, thumbCenter, - parentBox: this, - currentValue: _value, - themeData: sliderThemeData, - animation: _overlayAnimation, - thumb: null, - paint: null); + overlayShape.paint( + context, + thumbCenter, + parentBox: this, + currentValue: _value, + themeData: sliderThemeData, + animation: _overlayAnimation, + thumb: null, + paint: null, + ); if (_thumbIcon != null) { - (_thumbIcon!.parentData! as BoxParentData).offset = thumbCenter - + (_thumbIcon!.parentData! as BoxParentData).offset = + thumbCenter - Offset(_thumbIcon!.size.width / 2, _thumbIcon!.size.height / 2) - offset; } // Drawing thumb. - thumbShape.paint(context, thumbCenter, - parentBox: this, - child: _thumbIcon, - currentValue: _value, - themeData: sliderThemeData, - enableAnimation: _stateAnimation, - textDirection: textDirection, - thumb: null, - paint: null); + thumbShape.paint( + context, + thumbCenter, + parentBox: this, + child: _thumbIcon, + currentValue: _value, + themeData: sliderThemeData, + enableAnimation: _stateAnimation, + textDirection: textDirection, + thumb: null, + paint: null, + ); - _drawTooltip(context, thumbCenter, offset, actualTrackOffset, trackRect); + // To avoid positioning the tooltip text on the edge, used a 5px margin. + final Rect tooltipTargetRect = Rect.fromLTWH( + 5.0, + trackRect.top, + mediaQueryData.size.width - 5.0, + trackRect.height, + ); + _drawTooltip( + context, + thumbCenter, + offset, + actualTrackOffset, + tooltipTargetRect, + ); } @override @@ -2272,23 +2684,45 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(StringProperty( - 'thumbSize', thumbShape.getPreferredSize(sliderThemeData).toString())); - properties.add(StringProperty( + properties.add( + StringProperty( + 'thumbSize', + thumbShape.getPreferredSize(sliderThemeData).toString(), + ), + ); + properties.add( + StringProperty( 'activeDividerSize', dividerShape .getPreferredSize(sliderThemeData, isActive: true) - .toString())); - properties.add(StringProperty( + .toString(), + ), + ); + properties.add( + StringProperty( 'inactiveDividerSize', dividerShape .getPreferredSize(sliderThemeData, isActive: false) - .toString())); - properties.add(StringProperty('overlaySize', - overlayShape.getPreferredSize(sliderThemeData).toString())); - properties.add(StringProperty( - 'tickSize', tickShape.getPreferredSize(sliderThemeData).toString())); - properties.add(StringProperty('minorTickSize', - minorTickShape.getPreferredSize(sliderThemeData).toString())); + .toString(), + ), + ); + properties.add( + StringProperty( + 'overlaySize', + overlayShape.getPreferredSize(sliderThemeData).toString(), + ), + ); + properties.add( + StringProperty( + 'tickSize', + tickShape.getPreferredSize(sliderThemeData).toString(), + ), + ); + properties.add( + StringProperty( + 'minorTickSize', + minorTickShape.getPreferredSize(sliderThemeData).toString(), + ), + ); } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart b/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart index db775d5f5..24c0b0bc6 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:intl/intl.dart' show DateFormat, NumberFormat; +import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import 'common.dart'; @@ -29,11 +30,14 @@ class RenderBaseSlider extends RenderProxyBox required bool enableTooltip, required bool shouldAlwaysShowTooltip, required LabelPlacement labelPlacement, + required EdgeLabelPlacement edgeLabelPlacement, required bool isInversed, required NumberFormat numberFormat, required DateFormat? dateFormat, required DateIntervalType? dateIntervalType, + // ignore: deprecated_member_use_from_same_package required LabelFormatterCallback labelFormatterCallback, + required SliderLabelCreatedCallback onLabelCreated, required TooltipTextFormatterCallback tooltipTextFormatterCallback, required SfTrackShape trackShape, required SfDividerShape dividerShape, @@ -47,35 +51,37 @@ class RenderBaseSlider extends RenderProxyBox SliderTooltipPosition? tooltipPosition, required TextDirection textDirection, required MediaQueryData mediaQueryData, - }) : _min = min, - _max = max, - _interval = interval, - _stepSize = stepSize, - _stepDuration = stepDuration, - _minorTicksPerInterval = minorTicksPerInterval, - _showTicks = showTicks, - _showLabels = showLabels, - _showDividers = showDividers, - _enableTooltip = enableTooltip, - _shouldAlwaysShowTooltip = shouldAlwaysShowTooltip, - _isInversed = isInversed, - _labelPlacement = labelPlacement, - _numberFormat = numberFormat, - _dateFormat = dateFormat, - _dateIntervalType = dateIntervalType, - _labelFormatterCallback = labelFormatterCallback, - _tooltipTextFormatterCallback = tooltipTextFormatterCallback, - _trackShape = trackShape, - _dividerShape = dividerShape, - _overlayShape = overlayShape, - _thumbShape = thumbShape, - _tickShape = tickShape, - _minorTickShape = minorTickShape, - _tooltipShape = tooltipShape, - _sliderThemeData = sliderThemeData, - _textDirection = textDirection, - _mediaQueryData = mediaQueryData, - _tooltipPosition = tooltipPosition { + }) : _min = min, + _max = max, + _interval = interval, + _stepSize = stepSize, + _stepDuration = stepDuration, + _minorTicksPerInterval = minorTicksPerInterval, + _showTicks = showTicks, + _showLabels = showLabels, + _showDividers = showDividers, + _enableTooltip = enableTooltip, + _shouldAlwaysShowTooltip = shouldAlwaysShowTooltip, + _isInversed = isInversed, + _labelPlacement = labelPlacement, + _edgeLabelPlacement = edgeLabelPlacement, + _numberFormat = numberFormat, + _dateFormat = dateFormat, + _dateIntervalType = dateIntervalType, + _labelFormatterCallback = labelFormatterCallback, + _onLabelCreated = onLabelCreated, + _tooltipTextFormatterCallback = tooltipTextFormatterCallback, + _trackShape = trackShape, + _dividerShape = dividerShape, + _overlayShape = overlayShape, + _thumbShape = thumbShape, + _tickShape = tickShape, + _minorTickShape = minorTickShape, + _tooltipShape = tooltipShape, + _sliderThemeData = sliderThemeData, + _textDirection = textDirection, + _mediaQueryData = mediaQueryData, + _tooltipPosition = tooltipPosition { maxTrackHeight = getMaxTrackHeight(); trackOffset = _getTrackOffset(); @@ -86,18 +92,22 @@ class RenderBaseSlider extends RenderProxyBox _maxInMilliseconds = (max as DateTime).millisecondsSinceEpoch.toDouble(); } - _visibleLabels = []; + _visibleLabels = []; _majorTickPositions = []; _minorTickPositions = []; - thumbElevationTween = - Tween(begin: defaultElevation, end: tappedElevation); + thumbElevationTween = Tween( + begin: defaultElevation, + end: tappedElevation, + ); } final double minTrackWidth = kMinInteractiveDimension * 3; final TextPainter textPainter = TextPainter(); + bool isInactive = false; + late double _minInMilliseconds; late double _maxInMilliseconds; @@ -112,7 +122,7 @@ class RenderBaseSlider extends RenderProxyBox //ignore: prefer_final_fields bool isInteractionEnd = true; - late List _visibleLabels; + late List _visibleLabels; late List _majorTickPositions; @@ -147,6 +157,8 @@ class RenderBaseSlider extends RenderProxyBox PointerType? currentPointerType; + bool hasLabelCreated = false; + dynamic get min => _min; dynamic _min; set min(dynamic value) { @@ -295,6 +307,16 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } + EdgeLabelPlacement get edgeLabelPlacement => _edgeLabelPlacement; + EdgeLabelPlacement _edgeLabelPlacement; + set edgeLabelPlacement(EdgeLabelPlacement value) { + if (_edgeLabelPlacement == value) { + return; + } + _edgeLabelPlacement = value; + markNeedsPaint(); + } + NumberFormat get numberFormat => _numberFormat; NumberFormat _numberFormat; set numberFormat(NumberFormat value) { @@ -329,8 +351,11 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } + // ignore: deprecated_member_use_from_same_package LabelFormatterCallback get labelFormatterCallback => _labelFormatterCallback; + // ignore: deprecated_member_use_from_same_package LabelFormatterCallback _labelFormatterCallback; + // ignore: deprecated_member_use_from_same_package set labelFormatterCallback(LabelFormatterCallback value) { if (_labelFormatterCallback == value) { return; @@ -339,6 +364,16 @@ class RenderBaseSlider extends RenderProxyBox markNeedsSemanticsUpdate(); } + dynamic get onLabelCreated => _onLabelCreated; + dynamic _onLabelCreated; + set onLabelCreated(dynamic value) { + if (_onLabelCreated == value) { + return; + } + _onLabelCreated = value; + markNeedsSemanticsUpdate(); + } + TooltipTextFormatterCallback get tooltipTextFormatterCallback => _tooltipTextFormatterCallback; TooltipTextFormatterCallback _tooltipTextFormatterCallback; @@ -485,8 +520,9 @@ class RenderBaseSlider extends RenderProxyBox _minorTickShape.getPreferredSize(_sliderThemeData); double get maximumFontSize => math.max( - _sliderThemeData.inactiveLabelStyle!.fontSize!, - _sliderThemeData.activeLabelStyle!.fontSize!); + _sliderThemeData.inactiveLabelStyle!.fontSize!, + _sliderThemeData.activeLabelStyle!.fontSize!, + ); // actualLabelSize is applicable only for horizontal sliders Size get actualLabelSize => Size.fromHeight(maximumFontSize); @@ -499,20 +535,22 @@ class RenderBaseSlider extends RenderProxyBox Size get actualOverlaySize => _overlayShape.getPreferredSize(_sliderThemeData); - double get actualTickHeight => _showTicks - ? _sliderThemeData.tickSize!.height + - (_sliderThemeData.tickOffset != null - ? _sliderThemeData.tickOffset!.dy - : 0) - : 0; + double get actualTickHeight => + _showTicks + ? _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!.dx - : 0) - : 0; + double get actualTickWidth => + _showTicks + ? _sliderThemeData.tickSize!.width + + (_sliderThemeData.tickOffset != null + ? _sliderThemeData.tickOffset!.dx + : 0) + : 0; double get actualMinorTickHeight => _minorTicksPerInterval > 0 ? actualMinorTickSize.height : 0; @@ -521,53 +559,60 @@ class RenderBaseSlider extends RenderProxyBox double get actualMinorTickWidth => _minorTicksPerInterval > 0 ? actualMinorTickSize.width : 0; - double get actualLabelHeight => _showLabels - ? actualLabelSize.height * textPainter.textScaleFactor + - (_sliderThemeData.labelOffset != null - ? _sliderThemeData.labelOffset!.dy - : 0) - : 0; + double get actualLabelHeight => + _showLabels + ? textPainter.textScaler.scale(actualLabelSize.height) + + (_sliderThemeData.labelOffset != null + ? _sliderThemeData.labelOffset!.dy + : 0) + : 0; // actualLabelOffset is applicable only for vertical sliders - double get actualLabelOffset => _showLabels - ? _sliderThemeData.labelOffset != null - ? (_sliderThemeData.labelOffset!.dx) - : 0 - : 0; + 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 => (sliderType == SliderType.horizontal) - ? _tooltipShape is SfPaddleTooltipShape - ? math.max(actualThumbSize.height, actualTrackRect.height) / 2 - : math.max(actualThumbSize.height, actualTrackRect.height) / 2 + 10 - : math.max(actualThumbSize.width, actualTrackRect.width) / 2 + 10; + double get tooltipStartY => + (sliderType == SliderType.horizontal) + ? _tooltipShape is SfPaddleTooltipShape + ? math.max(actualThumbSize.height, actualTrackRect.height) / 2 + : math.max(actualThumbSize.height, actualTrackRect.height) / 2 + + 10 + : math.max(actualThumbSize.width, actualTrackRect.width) / 2 + 10; double get adjustmentUnit => (actualMax - actualMin) / 10; - dynamic get semanticActionUnit => isDateTime - ? _stepDuration ?? adjustmentUnit - : _stepSize ?? adjustmentUnit; + dynamic get semanticActionUnit => + isDateTime + ? _stepDuration ?? adjustmentUnit + : _stepSize ?? adjustmentUnit; void updateTextPainter() { textPainter ..textDirection = _textDirection - ..textScaleFactor = _mediaQueryData.textScaleFactor; + ..textScaler = _mediaQueryData.textScaler; } Offset _getTrackOffset() { - final double dx = [ + final double dx = + [ actualOverlaySize.width, actualThumbSize.width, actualTickSize.width, - actualMinorTickSize.width + actualMinorTickSize.width, ].reduce(math.max) / 2; - final double dy = [ + final double dy = + [ actualOverlaySize.height, actualThumbSize.height, _actualDividerSize.height, - maxTrackHeight + maxTrackHeight, ].reduce(math.max) / 2; @@ -575,8 +620,10 @@ class RenderBaseSlider extends RenderProxyBox } double getMaxTrackHeight() { - return math.max(_sliderThemeData.activeTrackHeight, - _sliderThemeData.inactiveTrackHeight); + return math.max( + _sliderThemeData.activeTrackHeight, + _sliderThemeData.inactiveTrackHeight, + ); } String getFormattedText(dynamic value) { @@ -592,10 +639,11 @@ class RenderBaseSlider extends RenderProxyBox // If min and max are equal, the result will be NAN. This creates exception // and widget will not rendered. // So we have checked a condition (actualMax <= actualMin). - final double factor = (value == null || actualMax <= actualMin) - ? 0.0 - // ignore: avoid_as - : (value - actualMin) / (actualMax - actualMin) as double; + final double factor = + (value == null || actualMax <= actualMin) + ? 0.0 + // ignore: avoid_as + : (value - actualMin) / (actualMax - actualMin) as double; if (_isInversed) { return 1.0 - factor; } else { @@ -622,69 +670,125 @@ class RenderBaseSlider extends RenderProxyBox } } + bool isActiveLabelValue(dynamic currentValue) { + return false; + } + void _generateLabelsAndMajorTicksBasedOnInterval() { String label; + SliderLabel labelStyle; double labelPosition; int? valueInMilliseconds; dynamic currentValue = _min; - divisions = (isDateTime - ? _getDateTimeDifference(_min, _max, _dateIntervalType) - : _max - _min) - .toDouble() / - // ignore: avoid_as - _interval as double; + divisions = + (isDateTime + ? _getDateTimeDifference(_min, _max, _dateIntervalType) + : _max - _min) + .toDouble() / + // ignore: avoid_as + _interval + as double; for (int i = 0; i <= divisions!; i++) { - label = - _labelFormatterCallback(currentValue, getFormattedText(currentValue)); + label = _labelFormatterCallback( + currentValue, + getFormattedText(currentValue), + ); + + final TextStyle themeTextStyle = + isActiveLabelValue(currentValue) + ? _sliderThemeData.activeLabelStyle! + : _sliderThemeData.inactiveLabelStyle!; + labelStyle = _onLabelCreated(currentValue, label, themeTextStyle); if (isDateTime) { // ignore: avoid_as valueInMilliseconds = (currentValue as DateTime).millisecondsSinceEpoch; } - _visibleLabels.add(label); - unformattedLabels?.add(isDateTime - ? valueInMilliseconds!.toDouble() - : currentValue.toDouble()); + + _visibleLabels.add( + SliderLabel( + text: labelStyle.text, + textStyle: themeTextStyle.merge(labelStyle.textStyle), + ), + ); + + unformattedLabels?.add( + isDateTime ? valueInMilliseconds!.toDouble() : currentValue.toDouble(), + ); if (sliderType == SliderType.horizontal) { - labelPosition = getFactorFromValue( - isDateTime ? valueInMilliseconds : currentValue) * + labelPosition = + getFactorFromValue( + isDateTime ? valueInMilliseconds : currentValue, + ) * (actualTrackRect.width); } else { - labelPosition = getFactorFromValue( - isDateTime ? valueInMilliseconds : currentValue) * + labelPosition = + getFactorFromValue( + isDateTime ? valueInMilliseconds : currentValue, + ) * (actualTrackRect.height); } if (!_majorTickPositions.contains(labelPosition)) { _majorTickPositions.add(labelPosition); } - currentValue = isDateTime - ? _getNextDate(currentValue, _dateIntervalType, _interval!) - : currentValue + _interval; + currentValue = + isDateTime + ? _getNextDate(currentValue, _dateIntervalType, _interval!) + : currentValue + _interval; } } void _generateEdgeLabelsAndMajorTicks() { String label; + SliderLabel labelStyle; divisions = 1.0; label = _labelFormatterCallback(_min, getFormattedText(_min)); - _visibleLabels.add(label); + TextStyle themeTextStyle = + isActiveLabelValue(_min) + ? _sliderThemeData.activeLabelStyle! + : _sliderThemeData.inactiveLabelStyle!; + labelStyle = _onLabelCreated(_min, label, themeTextStyle); + _visibleLabels.add( + SliderLabel( + text: labelStyle.text, + textStyle: themeTextStyle.merge(labelStyle.textStyle), + ), + ); + unformattedLabels?.add( - isDateTime ? _min.millisecondsSinceEpoch.toDouble() : _min.toDouble()); + isDateTime ? _min.millisecondsSinceEpoch.toDouble() : _min.toDouble(), + ); label = _labelFormatterCallback(_max, getFormattedText(_max)); - _visibleLabels.add(label); + themeTextStyle = + isActiveLabelValue(_max) + ? _sliderThemeData.activeLabelStyle! + : _sliderThemeData.inactiveLabelStyle!; + labelStyle = _onLabelCreated(_max, label, themeTextStyle); + _visibleLabels.add( + SliderLabel( + text: labelStyle.text, + textStyle: themeTextStyle.merge(labelStyle.textStyle), + ), + ); + unformattedLabels?.add( - isDateTime ? _max.millisecondsSinceEpoch.toDouble() : _max.toDouble()); + isDateTime ? _max.millisecondsSinceEpoch.toDouble() : _max.toDouble(), + ); if (sliderType == SliderType.horizontal) { - _majorTickPositions - .add(getFactorFromValue(actualMin) * actualTrackRect.width); - _majorTickPositions - .add(getFactorFromValue(actualMax) * actualTrackRect.width); + _majorTickPositions.add( + getFactorFromValue(actualMin) * actualTrackRect.width, + ); + _majorTickPositions.add( + getFactorFromValue(actualMax) * actualTrackRect.width, + ); } else { - _majorTickPositions - .add(getFactorFromValue(actualMin) * actualTrackRect.height); - _majorTickPositions - .add(getFactorFromValue(actualMax) * actualTrackRect.height); + _majorTickPositions.add( + getFactorFromValue(actualMin) * actualTrackRect.height, + ); + _majorTickPositions.add( + getFactorFromValue(actualMax) * actualTrackRect.height, + ); } } @@ -705,13 +809,20 @@ class RenderBaseSlider extends RenderProxyBox final int majorTicksCount = _majorTickPositions.length; double minorTickPosition; DateTime nextDate = _getNextDate(_min, _dateIntervalType, _interval!); - DateTime currentActualDate = - _getNextDate(nextDate, _dateIntervalType, -_interval!); + DateTime currentActualDate = _getNextDate( + nextDate, + _dateIntervalType, + -_interval!, + ); for (int i = 1; i <= majorTicksCount; i++) { // Need to divide the region based on _minorTicksPerInterval. // So, added 1 with _minorTicksPerInterval. - final double intervalDiff = _getDateTimeDifference( - currentActualDate, nextDate, _dateIntervalType) / + final double intervalDiff = + _getDateTimeDifference( + currentActualDate, + nextDate, + _dateIntervalType, + ) / (_minorTicksPerInterval + 1); // On dividing region equally between two dates, then it will be // equal to 1.0. @@ -719,20 +830,25 @@ class RenderBaseSlider extends RenderProxyBox // To get nextDate of minorTick, need to pass intervals. // So, iteration value is used as interval and is started with 1. for (double j = 1; j <= _minorTicksPerInterval; j++) { - final DateTime nextMinorDate = - _getNextDate(currentActualDate, _dateIntervalType, j); + final DateTime nextMinorDate = _getNextDate( + currentActualDate, + _dateIntervalType, + j, + ); minorTickPosition = _updateMinorTicksPosition( - nextMinorDate.millisecondsSinceEpoch.toDouble()); + nextMinorDate.millisecondsSinceEpoch.toDouble(), + ); _minorTickPositions.add(minorTickPosition); } } else { - final double minorPositionDiff = (nextDate.millisecondsSinceEpoch - + final double minorPositionDiff = + (nextDate.millisecondsSinceEpoch - currentActualDate.millisecondsSinceEpoch) / (_minorTicksPerInterval + 1); for (int j = 1; j <= _minorTicksPerInterval; j++) { minorTickPosition = _updateMinorTicksPosition( - currentActualDate.millisecondsSinceEpoch + - (j * minorPositionDiff)); + currentActualDate.millisecondsSinceEpoch + (j * minorPositionDiff), + ); _minorTickPositions.add(minorTickPosition); } } @@ -751,7 +867,8 @@ class RenderBaseSlider extends RenderProxyBox void _generateNumericMinorTicks() { final int majorTicksCount = _majorTickPositions.length; for (int i = 0; i <= majorTicksCount - 1; i++) { - final double minorPositionDiff = (i + 1 < majorTicksCount + final double minorPositionDiff = + (i + 1 < majorTicksCount ? _majorTickPositions[i + 1] - _majorTickPositions[i] : ((sliderType == SliderType.horizontal ? actualTrackRect.width @@ -770,7 +887,10 @@ 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!) { @@ -794,26 +914,32 @@ class RenderBaseSlider extends RenderProxyBox /// 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!) { 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); + return DateTime(currentDate.year, currentDate.month + interval.ceil()); case DateIntervalType.days: currentDate = currentDate.add(Duration(days: interval.ceil())); return DateTime(currentDate.year, currentDate.month, currentDate.day); case DateIntervalType.hours: currentDate = currentDate.add(Duration(hours: interval.ceil())); - return DateTime(currentDate.year, currentDate.month, currentDate.day, - currentDate.hour); + return DateTime( + currentDate.year, + currentDate.month, + currentDate.day, + currentDate.hour, + ); case DateIntervalType.minutes: return currentDate.add(Duration(minutes: interval.ceil())); case DateIntervalType.seconds: return currentDate.add(Duration(seconds: interval.ceil())); case DateIntervalType.years: - return DateTime(currentDate.year + interval.ceil(), 1, 1); + return DateTime(currentDate.year + interval.ceil()); } } @@ -843,7 +969,8 @@ class RenderBaseSlider extends RenderProxyBox if (isDiscrete) { if (!isDateTime) { final double maxMinDiff = getNumerizedValue(_max - _min); - double factorValue = (getFactorFromValue(valueInDouble ?? value) * + double factorValue = + (getFactorFromValue(valueInDouble ?? value) * (maxMinDiff / _stepSize!)) .round() / (maxMinDiff / _stepSize!); @@ -862,12 +989,13 @@ 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!.hours, - currentDate.minute + _stepDuration!.minutes, - currentDate.second + _stepDuration!.seconds); + currentDate.year + _stepDuration!.years, + currentDate.month + _stepDuration!.months, + currentDate.day + _stepDuration!.days, + currentDate.hour + _stepDuration!.hours, + currentDate.minute + _stepDuration!.minutes, + currentDate.second + _stepDuration!.seconds, + ); final double currentDateInms = currentDate.millisecondsSinceEpoch.toDouble(); @@ -898,11 +1026,13 @@ class RenderBaseSlider extends RenderProxyBox } double getFactorFromCurrentPosition() { - final double factor = (sliderType == SliderType.horizontal) - ? ((mainAxisOffset - actualTrackRect.left) / actualTrackRect.width) - .clamp(0.0, 1.0) - : ((actualTrackRect.bottom - mainAxisOffset) / actualTrackRect.height) - .clamp(0.0, 1.0); + final double factor = + (sliderType == SliderType.horizontal) + ? ((mainAxisOffset - actualTrackRect.left) / actualTrackRect.width) + .clamp(0.0, 1.0) + : ((actualTrackRect.bottom - mainAxisOffset) / + actualTrackRect.height) + .clamp(0.0, 1.0); if (_isInversed) { return 1.0 - factor; } else { @@ -910,8 +1040,13 @@ class RenderBaseSlider extends RenderProxyBox } } - Rect getRectangularTooltipRect(TextPainter textPainter, Offset offset, - Offset thumbCenter, Rect trackRect, SfSliderThemeData themeData) { + Rect getRectangularTooltipRect( + TextPainter textPainter, + Offset offset, + Offset thumbCenter, + Rect trackRect, + SfSliderThemeData themeData, + ) { final double rectangularTooltipHeight = textPainter.height + tooltipTextPadding.dy > minTooltipHeight ? textPainter.height + tooltipTextPadding.dy @@ -921,19 +1056,23 @@ class RenderBaseSlider extends RenderProxyBox ? (textPainter.width + tooltipTextPadding.dx) / 2 : minTooltipWidth / 2; - double rightLineWidth = thumbCenter.dx + halfTextWidth > trackRect.right - ? trackRect.right - thumbCenter.dx - : halfTextWidth; - final double leftLineWidth = thumbCenter.dx - halfTextWidth < trackRect.left - ? thumbCenter.dx - trackRect.left - : (halfTextWidth * 2) - rightLineWidth; - rightLineWidth = leftLineWidth < halfTextWidth - ? halfTextWidth - leftLineWidth + rightLineWidth - : rightLineWidth; + double rightLineWidth = + thumbCenter.dx + halfTextWidth > trackRect.right + ? trackRect.right - thumbCenter.dx + : halfTextWidth; + final double leftLineWidth = + thumbCenter.dx - halfTextWidth < trackRect.left + ? thumbCenter.dx - trackRect.left + : (halfTextWidth * 2) - rightLineWidth; + rightLineWidth = + leftLineWidth < halfTextWidth + ? halfTextWidth - leftLineWidth + rightLineWidth + : rightLineWidth; final double left = thumbCenter.dx - leftLineWidth; final double right = thumbCenter.dx + rightLineWidth; - final double top = thumbCenter.dy - + final double top = + thumbCenter.dy - rectangularTooltipHeight - offset.dy - tooltipTriangleHeight; @@ -943,12 +1082,13 @@ class RenderBaseSlider extends RenderProxyBox } Rect getPaddleTooltipRect( - TextPainter textPainter, - double thumbRadius, - Offset offset, - Offset thumbCenter, - Rect trackRect, - SfSliderThemeData themeData) { + TextPainter textPainter, + double thumbRadius, + Offset offset, + Offset thumbCenter, + Rect trackRect, + SfSliderThemeData themeData, + ) { final double paddleTooltipRadius = textPainter.height > minPaddleTopCircleRadius ? textPainter.height @@ -959,14 +1099,21 @@ class RenderBaseSlider extends RenderProxyBox ? thumbRadius - neckDifference : 4.0; final double halfTextWidth = textPainter.width / 2 + textPadding; - final double halfPaddleWidth = halfTextWidth > paddleTooltipRadius - ? halfTextWidth - : paddleTooltipRadius; - final double shift = _getAdjustPaddleWidth(thumbCenter, offset, - halfTextWidth - paddleTooltipRadius, paddleTooltipRadius, trackRect); + final double halfPaddleWidth = + halfTextWidth > paddleTooltipRadius + ? halfTextWidth + : paddleTooltipRadius; + final double shift = _getAdjustPaddleWidth( + thumbCenter, + offset, + halfTextWidth - paddleTooltipRadius, + paddleTooltipRadius, + trackRect, + ); final double left = thumbCenter.dx - halfPaddleWidth - shift; final double right = thumbCenter.dx + halfPaddleWidth - shift; - final double top = thumbCenter.dy - + final double top = + thumbCenter.dy - paddleTooltipRadius - paddleTooltipRadius * (1.0 - moveNeckValue) - topNeckRadius - @@ -976,8 +1123,13 @@ class RenderBaseSlider extends RenderProxyBox return Rect.fromLTRB(left, top, right, bottom); } - double _getAdjustPaddleWidth(Offset thumbCenter, Offset offset, - double halfTextWidth, double paddleTopCircleRadius, Rect trackRect) { + double _getAdjustPaddleWidth( + Offset thumbCenter, + Offset offset, + double halfTextWidth, + double paddleTopCircleRadius, + Rect trackRect, + ) { final double leftShiftWidth = thumbCenter.dx - offset.dx - halfTextWidth - paddleTopCircleRadius; // Moving the paddle top circle width from left to right. @@ -987,35 +1139,38 @@ class RenderBaseSlider extends RenderProxyBox trackRect.right + trackRect.left - offset.dx; // Moving the paddle top circle width from right to left. // When the tooltip leaves to the render box. - shiftPaddleWidth = thumbCenter.dx + halfTextWidth + paddleTopCircleRadius > - rightEndPosition - ? thumbCenter.dx + - halfTextWidth + - paddleTopCircleRadius - - rightEndPosition - : shiftPaddleWidth; + shiftPaddleWidth = + thumbCenter.dx + halfTextWidth + paddleTopCircleRadius > + rightEndPosition + ? thumbCenter.dx + + halfTextWidth + + paddleTopCircleRadius - + rightEndPosition + : shiftPaddleWidth; return shiftPaddleWidth; } void drawLabelsTicksAndDividers( - PaintingContext context, - Rect trackRect, - Offset offset, - Offset? thumbCenter, - Offset? startThumbCenter, - Offset? endThumbCenter, - Animation stateAnimation, - dynamic value, - SfRangeValues? values) { + PaintingContext context, + Rect trackRect, + Offset offset, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + Animation stateAnimation, + dynamic value, + SfRangeValues? values, + ) { int dateTimePos = 0; bool isActive; final double dx = sliderType == SliderType.horizontal ? trackRect.left : trackRect.bottom; final double dy = sliderType == SliderType.horizontal ? trackRect.top : trackRect.left; - final double halfTrackHeight = sliderType == SliderType.horizontal - ? trackRect.height / 2 - : trackRect.width / 2; + final double halfTrackHeight = + sliderType == SliderType.horizontal + ? trackRect.height / 2 + : trackRect.width / 2; if (startThumbCenter != null) { if (sliderType == SliderType.horizontal) { isActive = @@ -1032,14 +1187,16 @@ class RenderBaseSlider extends RenderProxyBox } } - final double dividerRadius = _dividerShape + final double dividerRadius = + _dividerShape .getPreferredSize(_sliderThemeData, isActive: isActive) .width / 2; - final double tickRadius = sliderType == SliderType.horizontal - ? _tickShape.getPreferredSize(_sliderThemeData).width / 2 - : _tickShape.getPreferredSize(_sliderThemeData).height / 2; + final double tickRadius = + sliderType == SliderType.horizontal + ? _tickShape.getPreferredSize(_sliderThemeData).width / 2 + : _tickShape.getPreferredSize(_sliderThemeData).height / 2; // ignore: avoid_as double textValue = isDateTime ? 0.0 : _min.toDouble() as double; @@ -1055,19 +1212,20 @@ class RenderBaseSlider extends RenderProxyBox // Drawing ticks. if (_showTicks) { _drawTick( - dx, - tickPosition, - dy, - trackRect, - dateTimePos, - tickRadius, - context, - thumbCenter, - startThumbCenter, - endThumbCenter, - value, - values, - stateAnimation); + dx, + tickPosition, + dy, + trackRect, + dateTimePos, + tickRadius, + context, + thumbCenter, + startThumbCenter, + endThumbCenter, + value, + values, + stateAnimation, + ); } if (_interval != null && _interval! > 0) { @@ -1078,53 +1236,58 @@ class RenderBaseSlider extends RenderProxyBox _minorTickPositions[minorTickIndex]; minorTickIndex++; _drawMinorTick( - currentMinorTickPosition, - trackRect, - dx, - dy, - context, - thumbCenter, - startThumbCenter, - endThumbCenter, - value, - values, - stateAnimation); - } - } - - // Drawing dividers. - if (_showDividers) { - _drawDivider( + currentMinorTickPosition, + trackRect, dx, - tickPosition, dy, - halfTrackHeight, - dateTimePos, - dividerRadius, - trackRect, context, thumbCenter, startThumbCenter, endThumbCenter, value, values, - stateAnimation); + stateAnimation, + ); + } + } + + // Drawing dividers. + if (_showDividers) { + _drawDivider( + dx, + tickPosition, + dy, + halfTrackHeight, + dateTimePos, + dividerRadius, + trackRect, + context, + thumbCenter, + startThumbCenter, + endThumbCenter, + value, + values, + stateAnimation, + ); } } - // Drawing label. + // Drawing labels. if (_showLabels) { - final double dx = sliderType == SliderType.horizontal - ? trackRect.left - : trackRect.bottom; + final double dx = + sliderType == SliderType.horizontal + ? trackRect.left + : trackRect.bottom; - double offsetX = sliderType == SliderType.horizontal - ? dx + tickPosition - : dx - tickPosition; + double offsetX = + sliderType == SliderType.horizontal + ? dx + tickPosition + : dx - tickPosition; if (_labelPlacement == LabelPlacement.betweenTicks) { if (sliderType == SliderType.horizontal) { - offsetX += ((dateTimePos + 1 <= divisions! + offsetX += + ((dateTimePos + 1 <= divisions! ? _majorTickPositions[dateTimePos + 1] : (_isInversed ? trackRect.left : trackRect.width)) - tickPosition) / @@ -1144,31 +1307,63 @@ class RenderBaseSlider extends RenderProxyBox } } + final TextStyle textStyle = + hasLabelCreated + ? _visibleLabels[dateTimePos].textStyle + : isInactive + ? _sliderThemeData.inactiveLabelStyle! + : _sliderThemeData.activeLabelStyle!; + + if (_edgeLabelPlacement == EdgeLabelPlacement.inside && + _labelPlacement == LabelPlacement.onTicks) { + final Size labelSize = measureText( + _visibleLabels[dateTimePos].text, + textStyle, + ); + if (sliderType == SliderType.horizontal) { + if (dateTimePos == 0) { + offsetX += labelSize.width / 2; + } else if (dateTimePos == _majorTickPositions.length - 1) { + offsetX -= labelSize.width / 2; + } + } else { + if (dateTimePos == 0) { + offsetX -= labelSize.height / 2; + } else if (dateTimePos == _majorTickPositions.length - 1) { + offsetX += labelSize.height / 2; + } + } + } + _drawLabel( - dateTimePos, - dx, - tickPosition, - trackRect, - dy, - context, - thumbCenter, - startThumbCenter, - endThumbCenter, - value, - values, - stateAnimation, - offsetX); + dateTimePos, + dx, + tickPosition, + trackRect, + dy, + context, + thumbCenter, + startThumbCenter, + endThumbCenter, + value, + values, + stateAnimation, + offsetX, + textStyle, + ); } // When interval is not set but [showLabels], [showTicks] enabled, // we need to show labels/ticks on min, max values. So we used // interval as _max - _min. - final double intervalDiff = (isDateTime - ? 1.0 - : _interval != null && _interval! > 0 - ? _interval - // ignore: avoid_as - : _max.toDouble() - _min.toDouble()) as double; + final double intervalDiff = + (isDateTime + ? 1.0 + : _interval != null && _interval! > 0 + ? _interval + // ignore: avoid_as + : _max.toDouble() - _min.toDouble()) + as double; textValue += intervalDiff; dateTimePos += 1; } @@ -1176,26 +1371,27 @@ class RenderBaseSlider extends RenderProxyBox } void _drawTick( - double dx, - double tickPosition, - double dy, - Rect trackRect, - int dateTimePos, - double tickRadius, - PaintingContext context, - Offset? thumbCenter, - Offset? startThumbCenter, - Offset? endThumbCenter, - dynamic value, - SfRangeValues? values, - Animation stateAnimation) { + double dx, + double tickPosition, + double dy, + Rect trackRect, + int dateTimePos, + double tickRadius, + PaintingContext context, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, + Animation stateAnimation, + ) { Offset actualTickOffset; if (sliderType == SliderType.horizontal) { if (_majorTickPositions[dateTimePos] == 0.0) { actualTickOffset = Offset(dx + tickPosition + tickRadius, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? Offset.zero); + (_sliderThemeData.tickOffset ?? Offset.zero); } // Due to floating-point operations, last [_majorTickPosition] is greater // than [trackRect.height] or [trackRect.width]. This happens in some @@ -1206,16 +1402,17 @@ class RenderBaseSlider extends RenderProxyBox trackRect.width.toStringAsFixed(8)) { actualTickOffset = Offset(dx + tickPosition - tickRadius, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? Offset.zero); + (_sliderThemeData.tickOffset ?? Offset.zero); } else { - actualTickOffset = Offset(dx + tickPosition, dy + trackRect.height) + + actualTickOffset = + Offset(dx + tickPosition, dy + trackRect.height) + (_sliderThemeData.tickOffset ?? Offset.zero); } } else { if (_majorTickPositions[dateTimePos] == 0.0) { actualTickOffset = Offset(dy + trackRect.width, dx - tickPosition - tickRadius) + - (_sliderThemeData.tickOffset ?? Offset.zero); + (_sliderThemeData.tickOffset ?? Offset.zero); } // Due to floating-point operations, last [_majorTickPosition] is greater // than [trackRect.height] or [trackRect.width]. This happens in some @@ -1226,76 +1423,93 @@ class RenderBaseSlider extends RenderProxyBox trackRect.height.toStringAsFixed(8)) { actualTickOffset = Offset(dy + trackRect.width, dx - tickPosition + tickRadius) + - (_sliderThemeData.tickOffset ?? Offset.zero); + (_sliderThemeData.tickOffset ?? Offset.zero); } else { - actualTickOffset = Offset(dy + trackRect.width, dx - tickPosition) + + actualTickOffset = + Offset(dy + trackRect.width, dx - tickPosition) + (_sliderThemeData.tickOffset ?? Offset.zero); } } - _tickShape.paint(context, actualTickOffset, thumbCenter, startThumbCenter, - endThumbCenter, - parentBox: this, - themeData: _sliderThemeData, - currentValue: value, - currentValues: values, - enableAnimation: stateAnimation, - textDirection: _textDirection); + _tickShape.paint( + context, + actualTickOffset, + thumbCenter, + startThumbCenter, + endThumbCenter, + parentBox: this, + themeData: _sliderThemeData, + currentValue: value, + currentValues: values, + enableAnimation: stateAnimation, + textDirection: _textDirection, + ); } void _drawMinorTick( - double currentMinorTickPosition, - Rect trackRect, - double dx, - double dy, - PaintingContext context, - Offset? thumbCenter, - Offset? startThumbCenter, - Offset? endThumbCenter, - dynamic value, - SfRangeValues? values, - Animation stateAnimation) { + double currentMinorTickPosition, + Rect trackRect, + double dx, + double dy, + PaintingContext context, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, + Animation stateAnimation, + ) { if (currentMinorTickPosition < (sliderType == SliderType.horizontal ? trackRect.width : trackRect.height) && currentMinorTickPosition > 0) { - final Offset actualTickOffset = sliderType == SliderType.horizontal - ? Offset(dx + currentMinorTickPosition, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? Offset.zero) - : Offset(dy + trackRect.width, dx - currentMinorTickPosition) + - (_sliderThemeData.tickOffset ?? Offset.zero); - _minorTickShape.paint(context, actualTickOffset, thumbCenter, - startThumbCenter, endThumbCenter, - parentBox: this, - themeData: _sliderThemeData, - currentValue: value, - currentValues: values, - enableAnimation: stateAnimation, - textDirection: _textDirection); + final Offset actualTickOffset = + sliderType == SliderType.horizontal + ? Offset(dx + currentMinorTickPosition, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero) + : Offset(dy + trackRect.width, dx - currentMinorTickPosition) + + (_sliderThemeData.tickOffset ?? Offset.zero); + _minorTickShape.paint( + context, + actualTickOffset, + thumbCenter, + startThumbCenter, + endThumbCenter, + parentBox: this, + themeData: _sliderThemeData, + currentValue: value, + currentValues: values, + enableAnimation: stateAnimation, + textDirection: _textDirection, + ); } } void _drawDivider( - double dx, - double tickPosition, - double dy, - double halfTrackHeight, - int _dateTimePos, - double dividerRadius, - Rect trackRect, - PaintingContext context, - Offset? thumbCenter, - Offset? startThumbCenter, - Offset? endThumbCenter, - dynamic value, - SfRangeValues? values, - Animation stateAnimation) { + double dx, + double tickPosition, + double dy, + double halfTrackHeight, + // ignore: no_leading_underscores_for_local_identifiers + int _dateTimePos, + double dividerRadius, + Rect trackRect, + PaintingContext context, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, + Animation stateAnimation, + ) { Offset dividerCenter; if (sliderType == SliderType.horizontal) { if (_majorTickPositions[_dateTimePos] == 0.0) { - dividerCenter = - Offset(dx + tickPosition + dividerRadius, dy + halfTrackHeight); + dividerCenter = Offset( + dx + tickPosition + dividerRadius, + dy + halfTrackHeight, + ); } // Due to floating-point operations, last [_majorTickPosition] is greater // than [trackRect.height] or [trackRect.width]. This happens in some @@ -1304,15 +1518,19 @@ class RenderBaseSlider extends RenderProxyBox // Current [_majorTickPosition] = 100.0909890121) else if (_majorTickPositions[_dateTimePos].toStringAsFixed(8) == trackRect.width.toStringAsFixed(8)) { - dividerCenter = - Offset(dx + tickPosition - dividerRadius, dy + halfTrackHeight); + dividerCenter = Offset( + dx + tickPosition - dividerRadius, + dy + halfTrackHeight, + ); } else { dividerCenter = Offset(dx + tickPosition, dy + halfTrackHeight); } } else { if (_majorTickPositions[_dateTimePos] == 0.0) { - dividerCenter = - Offset(dy + halfTrackHeight, dx - tickPosition - dividerRadius); + dividerCenter = Offset( + dy + halfTrackHeight, + dx - tickPosition - dividerRadius, + ); } // Due to floating-point operations, last [_majorTickPosition] is greater // than [trackRect.height] or [trackRect.width]. This happens in some @@ -1321,68 +1539,92 @@ class RenderBaseSlider extends RenderProxyBox // Current [_majorTickPosition] = 100.0909890121) else if (_majorTickPositions[_dateTimePos].toStringAsFixed(8) == trackRect.height.toStringAsFixed(8)) { - dividerCenter = - Offset(dy + halfTrackHeight, dx - tickPosition + dividerRadius); + dividerCenter = Offset( + dy + halfTrackHeight, + dx - tickPosition + dividerRadius, + ); } else { dividerCenter = Offset(dy + halfTrackHeight, dx - tickPosition); } } _dividerShape.paint( - context, dividerCenter, thumbCenter, startThumbCenter, endThumbCenter, - parentBox: this, - themeData: _sliderThemeData, - currentValue: value, - currentValues: values, - enableAnimation: stateAnimation, - textDirection: _textDirection, - paint: null); + context, + dividerCenter, + thumbCenter, + startThumbCenter, + endThumbCenter, + parentBox: this, + themeData: _sliderThemeData, + currentValue: value, + currentValues: values, + enableAnimation: stateAnimation, + textDirection: _textDirection, + paint: null, + ); } void _drawLabel( - int _dateTimePos, - double dx, - double tickPosition, - Rect trackRect, - double dy, - PaintingContext context, - Offset? thumbCenter, - Offset? startThumbCenter, - Offset? endThumbCenter, - dynamic value, - SfRangeValues? values, - Animation stateAnimation, - double offsetX) { + // ignore: no_leading_underscores_for_local_identifiers + int _dateTimePos, + double dx, + double tickPosition, + Rect trackRect, + double dy, + PaintingContext context, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, + Animation stateAnimation, + double offsetX, + TextStyle labelStyle, + ) { final double dy = sliderType == SliderType.horizontal ? trackRect.top : trackRect.left; - final String labelText = _visibleLabels[_dateTimePos]; - final Offset actualLabelOffset = sliderType == SliderType.horizontal - ? Offset(offsetX, dy + trackRect.height + actualTickHeight) + - (_sliderThemeData.labelOffset ?? Offset.zero) - : Offset(dy + trackRect.width + actualTickWidth, offsetX) + - (_sliderThemeData.labelOffset ?? Offset.zero); - - _drawText(context, actualLabelOffset, thumbCenter, startThumbCenter, - endThumbCenter, labelText, - parentBox: this, - themeData: _sliderThemeData, - currentValue: value, - currentValues: values, - enableAnimation: stateAnimation, - textPainter: textPainter, - textDirection: _textDirection); - } - - void _drawText(PaintingContext context, Offset center, Offset? thumbCenter, - Offset? startThumbCenter, Offset? endThumbCenter, String text, - {required RenderProxyBox parentBox, - required SfSliderThemeData themeData, - dynamic currentValue, - SfRangeValues? currentValues, - required Animation enableAnimation, - required TextPainter textPainter, - required TextDirection textDirection}) { - bool isInactive; + final String labelText = _visibleLabels[_dateTimePos].text; + final Offset actualLabelOffset = + sliderType == SliderType.horizontal + ? Offset(offsetX, dy + trackRect.height + actualTickHeight) + + (_sliderThemeData.labelOffset ?? Offset.zero) + : Offset(dy + trackRect.width + actualTickWidth, offsetX) + + (_sliderThemeData.labelOffset ?? Offset.zero); + + _drawText( + context, + actualLabelOffset, + thumbCenter, + startThumbCenter, + endThumbCenter, + labelText, + labelStyle, + parentBox: this, + themeData: _sliderThemeData, + currentValue: value, + currentValues: values, + enableAnimation: stateAnimation, + textPainter: textPainter, + textDirection: _textDirection, + ); + } + + void _drawText( + PaintingContext context, + Offset center, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + String text, + TextStyle textStyle, { + required RenderProxyBox parentBox, + required SfSliderThemeData themeData, + dynamic currentValue, + SfRangeValues? currentValues, + required Animation enableAnimation, + required TextPainter textPainter, + required TextDirection textDirection, + }) { if (sliderType == SliderType.horizontal) { // Added this condition to check whether consider single thumb or // two thumbs for finding inactive range. @@ -1421,39 +1663,51 @@ class RenderBaseSlider extends RenderProxyBox } } - final TextSpan textSpan = TextSpan( - text: text, - style: isInactive - ? themeData.inactiveLabelStyle - : themeData.activeLabelStyle, - ); + final TextStyle labelTextStyle = + hasLabelCreated + ? textStyle + : isInactive + ? themeData.inactiveLabelStyle! + : themeData.activeLabelStyle!; + + final TextSpan textSpan = TextSpan(text: text, style: labelTextStyle); + textPainter.text = textSpan; textPainter.layout(); if (sliderType == SliderType.horizontal) { textPainter.paint( - context.canvas, Offset(center.dx - textPainter.width / 2, center.dy)); + context.canvas, + Offset(center.dx - textPainter.width / 2, center.dy), + ); } else { - textPainter.paint(context.canvas, - Offset(center.dx, center.dy - textPainter.height / 2)); + textPainter.paint( + context.canvas, + Offset(center.dx, center.dy - textPainter.height / 2), + ); } } - dynamic getNextSemanticValue(dynamic value, dynamic semanticActionUnit, - {required double actualValue}) { + dynamic getNextSemanticValue( + dynamic value, + dynamic semanticActionUnit, { + required double actualValue, + }) { if (isDateTime) { if (_stepDuration == null) { return DateTime.fromMillisecondsSinceEpoch( - (actualValue + semanticActionUnit) - .clamp(actualMin, actualMax) - .toInt()); + (actualValue + semanticActionUnit) + .clamp(actualMin, actualMax) + .toInt(), + ); } else { final DateTime nextDate = DateTime( - value.year + semanticActionUnit.years, - value.month + semanticActionUnit.months, - value.day + semanticActionUnit.days, - value.hour + semanticActionUnit.days, - value.minute + semanticActionUnit.minutes, - value.second + semanticActionUnit.seconds); + value.year + semanticActionUnit.years, + value.month + semanticActionUnit.months, + value.day + semanticActionUnit.days, + value.hour + semanticActionUnit.days, + value.minute + semanticActionUnit.minutes, + value.second + semanticActionUnit.seconds, + ); final double nextDateInms = nextDate.millisecondsSinceEpoch.toDouble(); return nextDateInms < actualMax ? nextDate : _max; @@ -1463,22 +1717,27 @@ class RenderBaseSlider extends RenderProxyBox } } - dynamic getPrevSemanticValue(dynamic value, dynamic semanticActionUnit, - {required double actualValue}) { + dynamic getPrevSemanticValue( + dynamic value, + dynamic semanticActionUnit, { + required double actualValue, + }) { if (isDateTime) { if (_stepDuration == null) { return DateTime.fromMillisecondsSinceEpoch( - (actualValue - semanticActionUnit) - .clamp(actualMin, actualMax) - .toInt()); + (actualValue - semanticActionUnit) + .clamp(actualMin, actualMax) + .toInt(), + ); } else { final DateTime prevDate = DateTime( - value.year - semanticActionUnit.years, - value.month - semanticActionUnit.months, - value.day - semanticActionUnit.days, - value.hour - semanticActionUnit.days, - value.minute - semanticActionUnit.minutes, - value.second - semanticActionUnit.seconds); + value.year - semanticActionUnit.years, + value.month - semanticActionUnit.months, + value.day - semanticActionUnit.days, + value.hour - semanticActionUnit.days, + value.minute - semanticActionUnit.minutes, + value.second - semanticActionUnit.seconds, + ); final double prevDateInms = prevDate.millisecondsSinceEpoch.toDouble(); return prevDateInms > actualMin ? prevDate : _min; @@ -1500,10 +1759,10 @@ 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); + text: TextSpan(text: text, style: TextStyle(fontSize: fontSize)), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout(); return textPainter.size; } @@ -1512,26 +1771,45 @@ class RenderBaseSlider extends RenderProxyBox double maxLabelWidth = 0.0; if (_showLabels && _interval != null && _interval! > 0) { String label; + SliderLabel labelStyle; dynamic currentValue = _min; double labelLength; - divisions = (isDateTime - ? _getDateTimeDifference(_min, _max, _dateIntervalType) - : _max - _min) - .toDouble() / - // ignore: avoid_as - _interval as double; + divisions = + (isDateTime + ? _getDateTimeDifference( + _min, + _max, + _dateIntervalType, + ) + : _max - _min) + .toDouble() / + // ignore: avoid_as + _interval + as double; for (int i = 0; i <= divisions!.toInt(); i++) { label = _labelFormatterCallback( - currentValue, getFormattedText(currentValue)); - - labelLength = _textSize(label, maximumFontSize).width; + currentValue, + getFormattedText(currentValue), + ); + final TextStyle themeTextStyle = + isActiveLabelValue(currentValue) + ? _sliderThemeData.activeLabelStyle! + : _sliderThemeData.inactiveLabelStyle!; + labelStyle = _onLabelCreated(currentValue, label, themeTextStyle); + + final double maximumLabelFontSize = math.max( + maximumFontSize, + labelStyle.textStyle.fontSize ?? 0, + ); + labelLength = _textSize(labelStyle.text, maximumLabelFontSize).width; if (maxLabelWidth < labelLength) { maxLabelWidth = labelLength; } - currentValue = isDateTime - ? _getNextDate(currentValue, _dateIntervalType, _interval!) - : currentValue + _interval; + currentValue = + isDateTime + ? _getNextDate(currentValue, _dateIntervalType, _interval!) + : currentValue + _interval; } } else if (_showLabels) { maxLabelWidth = _edgeLabelWidth(); @@ -1543,11 +1821,34 @@ class RenderBaseSlider extends RenderProxyBox double _edgeLabelWidth() { String minLabel; String maxLabel; + SliderLabel minLabelStyle; + SliderLabel maxLabelStyle; double maxLabelWidth; minLabel = _labelFormatterCallback(_min, getFormattedText(_min)); + TextStyle themeTextStyle = + isActiveLabelValue(_min) + ? _sliderThemeData.activeLabelStyle! + : _sliderThemeData.inactiveLabelStyle!; + minLabelStyle = _onLabelCreated(_min, minLabel, themeTextStyle); maxLabel = _labelFormatterCallback(_max, getFormattedText(_max)); - final double minLabelLength = _textSize(minLabel, maximumFontSize).width; - final double maxLabelLength = _textSize(maxLabel, maximumFontSize).width; + themeTextStyle = + isActiveLabelValue(_max) + ? _sliderThemeData.activeLabelStyle! + : _sliderThemeData.inactiveLabelStyle!; + maxLabelStyle = _onLabelCreated(_max, maxLabel, themeTextStyle); + + double maxFontFize = math.max( + maximumFontSize, + maxLabelStyle.textStyle.fontSize ?? 0, + ); + final double minLabelLength = + _textSize(minLabelStyle.text, maxFontFize).width; + maxFontFize = math.max( + maximumFontSize, + minLabelStyle.textStyle.fontSize ?? 0, + ); + final double maxLabelLength = + _textSize(maxLabelStyle.text, maxFontFize).width; maxLabelWidth = math.max(minLabelLength, maxLabelLength); return maxLabelWidth; } @@ -1572,30 +1873,34 @@ class RenderBaseSlider extends RenderProxyBox // vertical sliders is also considered as actualHeight. if (sliderType == SliderType.horizontal) { actualHeight = math.max( - 2 * trackOffset.dy, - trackOffset.dy + - maxTrackHeight / 2 + - math.max(actualTickHeight, actualMinorTickHeight) + - actualLabelHeight); + 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); + constraints.hasBoundedWidth + ? constraints.maxWidth + : minTrackWidth + 2 * trackOffset.dx, + constraints.hasBoundedHeight ? constraints.maxHeight : actualHeight, + ); } else { actualHeight = math.max( - 2 * trackOffset.dx, - trackOffset.dx + - maxTrackHeight / 2 + - math.max(actualTickWidth, actualMinorTickWidth) + - _maximumLabelWidth() + - actualLabelOffset); + 2 * trackOffset.dx, + trackOffset.dx + + maxTrackHeight / 2 + + math.max(actualTickWidth, actualMinorTickWidth) + + _maximumLabelWidth() + + actualLabelOffset, + ); size = Size( - constraints.hasBoundedWidth ? constraints.maxWidth : actualHeight, - constraints.hasBoundedHeight - ? constraints.maxHeight - : minTrackWidth + 2 * trackOffset.dy); + constraints.hasBoundedWidth ? constraints.maxWidth : actualHeight, + constraints.hasBoundedHeight + ? constraints.maxHeight + : minTrackWidth + 2 * trackOffset.dy, + ); } generateLabelsAndMajorTicks(); diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart index 9f603e884..49ea9dc35 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart @@ -19,36 +19,47 @@ class SfTrackShape { /// Returns the size based on the values passed to it. Rect getPreferredRect( - RenderBox parentBox, SfSliderThemeData themeData, Offset offset, - {bool? isActive}) { + RenderBox parentBox, + SfSliderThemeData themeData, + Offset offset, { + bool? isActive, + }) { final Size overlayPreferredSize = (parentBox as RenderBaseSlider) .overlayShape .getPreferredSize(themeData); - final Size thumbPreferredSize = - parentBox.thumbShape.getPreferredSize(themeData); - final Size tickPreferredSize = - parentBox.tickShape.getPreferredSize(themeData); + final Size thumbPreferredSize = parentBox.thumbShape.getPreferredSize( + themeData, + ); + final Size tickPreferredSize = parentBox.tickShape.getPreferredSize( + themeData, + ); double maxRadius; if (_isVertical(parentBox)) { maxRadius = math.max( - overlayPreferredSize.height / 2, - math.max( - thumbPreferredSize.height / 2, tickPreferredSize.height / 2)); + overlayPreferredSize.height / 2, + math.max(thumbPreferredSize.height / 2, tickPreferredSize.height / 2), + ); } else { - maxRadius = math.max(overlayPreferredSize.width / 2, - math.max(thumbPreferredSize.width / 2, tickPreferredSize.width / 2)); + maxRadius = math.max( + overlayPreferredSize.width / 2, + math.max(thumbPreferredSize.width / 2, tickPreferredSize.width / 2), + ); } - final double maxTrackHeight = - math.max(themeData.activeTrackHeight, themeData.inactiveTrackHeight); + final double maxTrackHeight = math.max( + themeData.activeTrackHeight, + themeData.inactiveTrackHeight, + ); // ignore: avoid_as if (_isVertical(parentBox)) { double left = offset.dx; if (isActive != null) { - left += isActive - ? (maxTrackHeight - themeData.activeTrackHeight) / 2 - : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; + left += + isActive + ? (maxTrackHeight - themeData.activeTrackHeight) / 2 + : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; } - final double right = left + + final double right = + left + (isActive == null ? maxTrackHeight : (isActive @@ -57,74 +68,100 @@ class SfTrackShape { 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); + 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; + top += + isActive + ? (maxTrackHeight - themeData.activeTrackHeight) / 2 + : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; } final double right = left + parentBox.size.width - (2 * maxRadius); - final double bottom = top + + 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); + 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, - {required RenderBox parentBox, - required SfSliderThemeData themeData, - SfRangeValues? currentValues, - dynamic currentValue, - required Animation enableAnimation, - required Paint? inactivePaint, - required Paint? activePaint, - required TextDirection textDirection}) { + 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 = - getPreferredRect(parentBox, themeData, offset, isActive: true); + final Rect inactiveTrackRect = getPreferredRect( + parentBox, + themeData, + offset, + isActive: false, + ); + final Rect activeTrackRect = getPreferredRect( + parentBox, + themeData, + offset, + isActive: true, + ); if (inactivePaint == null) { inactivePaint = Paint(); final ColorTween inactiveTrackColorTween = ColorTween( - begin: themeData.disabledInactiveTrackColor, - end: themeData.inactiveTrackColor); + begin: themeData.disabledInactiveTrackColor, + end: themeData.inactiveTrackColor, + ); inactivePaint.color = inactiveTrackColorTween.evaluate(enableAnimation)!; } if (activePaint == null) { activePaint = Paint(); final ColorTween activeTrackColorTween = ColorTween( - begin: themeData.disabledActiveTrackColor, - end: themeData.activeTrackColor); + begin: themeData.disabledActiveTrackColor, + end: themeData.activeTrackColor, + ); activePaint.color = activeTrackColorTween.evaluate(enableAnimation)!; } _drawTrackRect( - textDirection, - thumbCenter, - startThumbCenter, - endThumbCenter, - activePaint, - inactivePaint, - inactiveTrackRect, - radius, - context, - activeTrackRect, - // ignore: avoid_as - isVertical: _isVertical(parentBox as RenderBaseSlider), - isInversed: parentBox.isInversed); + textDirection, + thumbCenter, + startThumbCenter, + endThumbCenter, + activePaint, + inactivePaint, + inactiveTrackRect, + radius, + context, + activeTrackRect, + // ignore: avoid_as + isVertical: _isVertical(parentBox as RenderBaseSlider), + isInversed: parentBox.isInversed, + ); } void _drawTrackRect( @@ -175,63 +212,98 @@ class SfTrackShape { if (leftThumbCenter != null && rightThumbCenter != null) { // Drawing range slider track. - _drawRangeSliderTrack(inactiveTrackRect, leftThumbCenter, radius, context, - inactivePaint, activeTrackRect, rightThumbCenter, activePaint, - isVertical: isVertical); + _drawRangeSliderTrack( + inactiveTrackRect, + leftThumbCenter, + radius, + context, + inactivePaint, + activeTrackRect, + rightThumbCenter, + activePaint, + isVertical: isVertical, + ); } else { // Drawing slider track. - _drawSliderTrack(leftTrackRect!, thumbCenter!, radius, context, - leftTrackPaint!, rightTrackRect!, rightTrackPaint!, - isVertical: isVertical); + _drawSliderTrack( + leftTrackRect!, + thumbCenter!, + radius, + context, + leftTrackPaint!, + rightTrackRect!, + rightTrackPaint!, + isVertical: isVertical, + ); } } void _drawSliderTrack( - Rect activeTrackRect, - Offset thumbCenter, - Radius radius, - PaintingContext context, - Paint activePaint, - Rect inactiveTrackRect, - Paint inactivePaint, - {required bool isVertical}) { + Rect activeTrackRect, + Offset thumbCenter, + Radius radius, + PaintingContext context, + Paint activePaint, + Rect inactiveTrackRect, + Paint inactivePaint, { + required bool isVertical, + }) { RRect inactiveTrackRRect; if (!isVertical) { // 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); + 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); + thumbCenter.dx, + inactiveTrackRect.top, + inactiveTrackRect.width + inactiveTrackRect.left, + inactiveTrackRect.bottom, + ); + inactiveTrackRRect = RRect.fromRectAndCorners( + trackRect, + topRight: radius, + bottomRight: radius, + ); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); } else { - Rect trackRect = Rect.fromLTRB(activeTrackRect.left, thumbCenter.dy, - activeTrackRect.right, activeTrackRect.bottom); - final RRect activeTrackRRect = RRect.fromRectAndCorners(trackRect, - bottomRight: radius, bottomLeft: radius); + 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); + trackRect = Rect.fromLTRB( + inactiveTrackRect.left, + inactiveTrackRect.top, + inactiveTrackRect.right, + thumbCenter.dy, + ); + inactiveTrackRRect = RRect.fromRectAndCorners( + trackRect, + topLeft: radius, + topRight: radius, + ); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); } } @@ -250,53 +322,77 @@ class SfTrackShape { RRect inactiveTrackRRect; if (!isVertical) { // Drawing inactive track. - Rect trackRect = Rect.fromLTRB(inactiveTrackRect.left, - inactiveTrackRect.top, startThumbCenter.dx, inactiveTrackRect.bottom); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - topLeft: radius, bottomLeft: radius); + 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); + 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); + endThumbCenter.dx, + inactiveTrackRect.top, + inactiveTrackRect.width + inactiveTrackRect.left, + inactiveTrackRect.bottom, + ); + inactiveTrackRRect = RRect.fromRectAndCorners( + trackRect, + topRight: radius, + bottomRight: radius, + ); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); } else { // Drawing inactive track Rect trackRect = Rect.fromLTRB( - inactiveTrackRect.left, - startThumbCenter.dy, - inactiveTrackRect.right, - inactiveTrackRect.bottom); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - bottomLeft: radius, bottomRight: radius); + 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); + 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); + trackRect = Rect.fromLTRB( + inactiveTrackRect.left, + inactiveTrackRect.top, + inactiveTrackRect.right, + endThumbCenter.dy, + ); + inactiveTrackRRect = RRect.fromRectAndCorners( + trackRect, + topLeft: radius, + topRight: radius, + ); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); } } @@ -318,18 +414,22 @@ class SfThumbShape { } /// Paints the thumb based on the values passed to it. - void paint(PaintingContext context, Offset center, - {required RenderBox parentBox, - required RenderBox? child, - required SfSliderThemeData themeData, - SfRangeValues? currentValues, - dynamic currentValue, - required Paint? paint, - required Animation enableAnimation, - required TextDirection textDirection, - required SfThumb? thumb}) { + void paint( + PaintingContext context, + Offset center, { + required RenderBox parentBox, + required RenderBox? child, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, + dynamic currentValue, + required Paint? paint, + required Animation enableAnimation, + required TextDirection textDirection, + required SfThumb? thumb, + }) { final double radius = getPreferredSize(themeData).width / 2; - final bool hasThumbStroke = themeData.thumbStrokeColor != null && + final bool hasThumbStroke = + themeData.thumbStrokeColor != null && themeData.thumbStrokeColor != Colors.transparent && themeData.thumbStrokeWidth != null && themeData.thumbStrokeWidth! > 0; @@ -341,12 +441,13 @@ class SfThumbShape { final Path path = Path(); final bool isThumbActive = (parentRenderBox.activeThumb == thumb || thumb == null) && - parentRenderBox.currentPointerType != null && - parentRenderBox.currentPointerType != PointerType.up; + parentRenderBox.currentPointerType != null && + parentRenderBox.currentPointerType != PointerType.up; path.addOval(Rect.fromCircle(center: center, radius: radius)); - final double thumbElevation = isThumbActive - ? parentRenderBox.thumbElevationTween.evaluate(enableAnimation) - : defaultElevation; + final double thumbElevation = + isThumbActive + ? parentRenderBox.thumbElevationTween.evaluate(enableAnimation) + : defaultElevation; context.canvas.drawShadow(path, shadowColor, thumbElevation, true); } @@ -357,46 +458,55 @@ class SfThumbShape { themeData.thumbColor != Colors.transparent && themeData.overlappingThumbStrokeColor != null) { context.canvas.drawCircle( - center, - radius, - Paint() - ..color = themeData.overlappingThumbStrokeColor! - ..style = PaintingStyle.stroke - ..isAntiAlias = true - ..strokeWidth = 1.0); + center, + radius, + Paint() + ..color = themeData.overlappingThumbStrokeColor! + ..style = PaintingStyle.stroke + ..isAntiAlias = true + ..strokeWidth = 1.0, + ); } if (paint == null) { paint = Paint(); paint.isAntiAlias = true; - paint.color = ColorTween( - begin: themeData.disabledThumbColor, end: themeData.thumbColor) - .evaluate(enableAnimation)!; + paint.color = + ColorTween( + begin: themeData.disabledThumbColor, + end: themeData.thumbColor, + ).evaluate(enableAnimation)!; } context.canvas.drawCircle(center, radius, paint); if (child != null) { context.paintChild( - child, - Offset(center.dx - (child.size.width) / 2, - center.dy - (child.size.height) / 2)); + child, + Offset( + center.dx - (child.size.width) / 2, + center.dy - (child.size.height) / 2, + ), + ); } if (themeData.thumbStrokeColor != null && themeData.thumbStrokeWidth != null && themeData.thumbStrokeWidth! > 0) { - final Paint strokePaint = Paint() - ..color = themeData.thumbStrokeColor! - ..style = PaintingStyle.stroke - ..strokeWidth = themeData.thumbStrokeWidth! > radius - ? radius - : themeData.thumbStrokeWidth!; + final Paint strokePaint = + Paint() + ..color = themeData.thumbStrokeColor! + ..style = PaintingStyle.stroke + ..strokeWidth = + themeData.thumbStrokeWidth! > radius + ? radius + : themeData.thumbStrokeWidth!; context.canvas.drawCircle( - center, - themeData.thumbStrokeWidth! > radius - ? radius / 2 - : radius - themeData.thumbStrokeWidth! / 2, - strokePaint); + center, + themeData.thumbStrokeWidth! > radius + ? radius / 2 + : radius - themeData.thumbStrokeWidth! / 2, + strokePaint, + ); } } } @@ -413,23 +523,30 @@ class SfDividerShape { /// Returns the size based on the values passed to it. Size getPreferredSize(SfSliderThemeData themeData, {bool? isActive}) { - return Size.fromRadius(isActive != null - ? (isActive - ? themeData.activeDividerRadius! - : themeData.inactiveDividerRadius!) - : 0); + return Size.fromRadius( + isActive != null + ? (isActive + ? themeData.activeDividerRadius! + : themeData.inactiveDividerRadius!) + : 0, + ); } /// Paints the dividers based on the values passed to it. - void paint(PaintingContext context, Offset center, Offset? thumbCenter, - Offset? startThumbCenter, Offset? endThumbCenter, - {required RenderBox parentBox, - required SfSliderThemeData themeData, - SfRangeValues? currentValues, - dynamic currentValue, - required Paint? paint, - required Animation enableAnimation, - required TextDirection textDirection}) { + void paint( + PaintingContext context, + Offset center, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, { + required RenderBox parentBox, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, + dynamic currentValue, + required Paint? paint, + required Animation enableAnimation, + required TextDirection textDirection, + }) { late bool isActive; final bool isVertical = _isVertical(parentBox as RenderBaseSlider); @@ -438,10 +555,12 @@ class SfDividerShape { // two thumbs for finding active range. if (startThumbCenter != null) { if (!parentBox.isInversed) { - isActive = center.dx >= startThumbCenter.dx && + isActive = + center.dx >= startThumbCenter.dx && center.dx <= endThumbCenter!.dx; } else { - isActive = center.dx >= endThumbCenter!.dx && + isActive = + center.dx >= endThumbCenter!.dx && center.dx <= startThumbCenter.dx; } } else { @@ -456,10 +575,12 @@ class SfDividerShape { // two thumbs for finding active range. if (startThumbCenter != null) { if (!parentBox.isInversed) { - isActive = center.dy <= startThumbCenter.dy && + isActive = + center.dy <= startThumbCenter.dy && center.dy >= endThumbCenter!.dy; } else { - isActive = center.dy >= startThumbCenter.dy && + isActive = + center.dy >= startThumbCenter.dy && center.dy <= endThumbCenter!.dy; } } else { @@ -473,12 +594,14 @@ class SfDividerShape { if (paint == null) { paint = Paint(); - final Color begin = isActive - ? themeData.disabledActiveDividerColor! - : themeData.disabledInactiveDividerColor!; - final Color end = isActive - ? themeData.activeDividerColor! - : themeData.inactiveDividerColor!; + final Color begin = + isActive + ? themeData.disabledActiveDividerColor! + : themeData.disabledInactiveDividerColor!; + final Color end = + isActive + ? themeData.activeDividerColor! + : themeData.inactiveDividerColor!; paint.color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!; @@ -488,28 +611,32 @@ class SfDividerShape { getPreferredSize(themeData, isActive: isActive).width / 2; context.canvas.drawCircle(center, dividerRadius, paint); - final double? dividerStrokeWidth = isActive - ? themeData.activeDividerStrokeWidth - : themeData.inactiveDividerStrokeWidth; - final Color? dividerStrokeColor = isActive - ? themeData.activeDividerStrokeColor - : themeData.inactiveDividerStrokeColor; + final double? dividerStrokeWidth = + isActive + ? themeData.activeDividerStrokeWidth + : themeData.inactiveDividerStrokeWidth; + final Color? dividerStrokeColor = + isActive + ? themeData.activeDividerStrokeColor + : themeData.inactiveDividerStrokeColor; if (dividerStrokeColor != null && dividerStrokeWidth != null && dividerStrokeWidth > 0) { // Drawing divider stroke context.canvas.drawCircle( - center, - dividerStrokeWidth > dividerRadius - ? dividerRadius / 2 - : dividerRadius - dividerStrokeWidth / 2, - paint - ..color = dividerStrokeColor - ..style = PaintingStyle.stroke - ..strokeWidth = dividerStrokeWidth > dividerRadius - ? dividerRadius - : dividerStrokeWidth); + center, + dividerStrokeWidth > dividerRadius + ? dividerRadius / 2 + : dividerRadius - dividerStrokeWidth / 2, + paint + ..color = dividerStrokeColor + ..style = PaintingStyle.stroke + ..strokeWidth = + dividerStrokeWidth > dividerRadius + ? dividerRadius + : dividerStrokeWidth, + ); } } } @@ -526,14 +653,17 @@ class SfOverlayShape { } /// Paints the overlay based on the values passed to it. - void paint(PaintingContext context, Offset center, - {required RenderBox parentBox, - required SfSliderThemeData themeData, - SfRangeValues? currentValues, - dynamic currentValue, - required Paint? paint, - required Animation animation, - required SfThumb? thumb}) { + void paint( + PaintingContext context, + Offset center, { + required RenderBox parentBox, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, + dynamic currentValue, + 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); @@ -561,14 +691,19 @@ class SfTickShape { } /// Paints the major ticks based on the values passed to it. - 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 TextDirection textDirection}) { + 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 TextDirection textDirection, + }) { bool isInactive = false; final Size tickSize = getPreferredSize(themeData); final bool isVertical = _isVertical(parentBox as RenderBaseSlider); @@ -611,21 +746,31 @@ class SfTickShape { } } - final Color begin = isInactive - ? themeData.disabledInactiveTickColor! - : themeData.disabledActiveTickColor!; + final Color begin = + isInactive + ? themeData.disabledInactiveTickColor! + : themeData.disabledActiveTickColor!; final Color end = isInactive ? themeData.inactiveTickColor! : themeData.activeTickColor!; - final Paint paint = Paint() - ..isAntiAlias = true - ..strokeWidth = _isVertical(parentBox) ? tickSize.height : tickSize.width - ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!; + final Paint paint = + Paint() + ..isAntiAlias = true + ..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); + offset, + Offset(offset.dx + tickSize.width, offset.dy), + paint, + ); } else { context.canvas.drawLine( - offset, Offset(offset.dx, offset.dy + tickSize.height), paint); + offset, + Offset(offset.dx, offset.dy + tickSize.height), + paint, + ); } } } @@ -636,13 +781,17 @@ abstract class SfTooltipShape { const SfTooltipShape(); /// Draws the tooltip based on the values passed in the arguments. - void paint(PaintingContext context, Offset thumbCenter, Offset offset, - TextPainter textPainter, - {required RenderBox parentBox, - required SfSliderThemeData sliderThemeData, - required Paint paint, - required Animation animation, - required Rect trackRect}); + void paint( + PaintingContext context, + Offset thumbCenter, + Offset offset, + TextPainter textPainter, { + required RenderBox parentBox, + required SfSliderThemeData sliderThemeData, + required Paint paint, + required Animation animation, + required Rect trackRect, + }); } /// Base class for [SfSlider], [SfRangeSlider] and [SfRangeSelector] minor tick @@ -656,14 +805,19 @@ class SfMinorTickShape extends SfTickShape { } @override - void paint(PaintingContext context, Offset offset, Offset? thumbCenter, - Offset? startThumbCenter, Offset? endThumbCenter, - {required RenderBox parentBox, - SfRangeValues? currentValues, - dynamic currentValue, - required SfSliderThemeData themeData, - required Animation enableAnimation, - required TextDirection textDirection}) { + void paint( + PaintingContext context, + Offset offset, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, { + required RenderBox parentBox, + SfRangeValues? currentValues, + dynamic currentValue, + required SfSliderThemeData themeData, + required Animation enableAnimation, + required TextDirection textDirection, + }) { bool isInactive; final Size minorTickSize = getPreferredSize(themeData); final bool isVertical = _isVertical(parentBox as RenderBaseSlider); @@ -706,23 +860,35 @@ class SfMinorTickShape extends SfTickShape { } } - final Color begin = isInactive - ? themeData.disabledInactiveMinorTickColor! - : themeData.disabledActiveMinorTickColor!; - final Color end = isInactive - ? themeData.inactiveMinorTickColor! - : themeData.activeMinorTickColor!; - final Paint paint = Paint() - ..isAntiAlias = true - ..strokeWidth = - _isVertical(parentBox) ? minorTickSize.height : minorTickSize.width - ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!; + final Color begin = + isInactive + ? themeData.disabledInactiveMinorTickColor! + : themeData.disabledActiveMinorTickColor!; + final Color end = + isInactive + ? themeData.inactiveMinorTickColor! + : themeData.activeMinorTickColor!; + final Paint paint = + Paint() + ..isAntiAlias = true + ..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); + offset, + Offset(offset.dx + minorTickSize.width, offset.dy), + paint, + ); } else { context.canvas.drawLine( - offset, Offset(offset.dx, offset.dy + minorTickSize.height), paint); + offset, + Offset(offset.dx, offset.dy + minorTickSize.height), + paint, + ); } } } @@ -738,23 +904,24 @@ class SfPaddleTooltipShape extends SfTooltipShape { } void _drawPaddleTooltip( - RenderBox parentBox, - TextPainter textPainter, - double minPaddleTopCircleRadius, - double neckDifference, - SfSliderThemeData sliderThemeData, - double defaultThumbRadius, - double minBottomNeckRadius, - double textPadding, - Offset offset, - double moveNeckValue, - Offset thumbCenter, - Rect trackRect, - PaintingContext context, - Animation animation, - Paint? paint) { - final double thumbRadius = (parentBox as RenderBaseSlider) - .thumbShape + RenderBox parentBox, + TextPainter textPainter, + double minPaddleTopCircleRadius, + double neckDifference, + SfSliderThemeData sliderThemeData, + double defaultThumbRadius, + double minBottomNeckRadius, + double textPadding, + Offset offset, + double moveNeckValue, + Offset thumbCenter, + Rect trackRect, + PaintingContext context, + Animation animation, + Paint? paint, + ) { + final double thumbRadius = + (parentBox as RenderBaseSlider).thumbShape .getPreferredSize(sliderThemeData) .width / 2; @@ -763,64 +930,76 @@ class SfPaddleTooltipShape extends SfTooltipShape { ? textPainter.height : minPaddleTopCircleRadius; final double topNeckRadius = paddleTopCircleRadius - neckDifference; - final double bottomNeckRadius = thumbRadius > defaultThumbRadius - ? thumbRadius - neckDifference * 2 - : minBottomNeckRadius; + final double bottomNeckRadius = + thumbRadius > defaultThumbRadius + ? thumbRadius - neckDifference * 2 + : minBottomNeckRadius; final double halfTextWidth = textPainter.width / 2 + textPadding; - final double paddleTopCircleX = halfTextWidth > paddleTopCircleRadius - ? halfTextWidth - paddleTopCircleRadius - : 0; + final double paddleTopCircleX = + halfTextWidth > paddleTopCircleRadius + ? halfTextWidth - paddleTopCircleRadius + : 0; final double minPaddleWidth = paddleTopCircleRadius + topNeckRadius + neckDifference / 2; final Offset topNeckCenter = Offset( - topNeckRadius + neckDifference / 2, -offset.dy - bottomNeckRadius); + topNeckRadius + neckDifference / 2, + -offset.dy - bottomNeckRadius, + ); final Offset paddleTopCircleCenter = Offset( - paddleTopCircleX, - -paddleTopCircleRadius * (1.0 - moveNeckValue) - - topNeckRadius - - offset.dy - - bottomNeckRadius); + paddleTopCircleX, + -paddleTopCircleRadius * (1.0 - moveNeckValue) - + topNeckRadius - + offset.dy - + bottomNeckRadius, + ); final Offset bottomNeckCenter = Offset( - bottomNeckRadius + neckDifference / 2, - -thumbRadius - bottomNeckRadius * (1.0 - moveNeckValue)); - final double leftShiftWidth = thumbCenter.dx - offset.dx - halfTextWidth; - double shiftPaddleWidth = leftShiftWidth < 0 ? leftShiftWidth : 0; - final double rightEndPosition = - trackRect.right + trackRect.left - offset.dx; - shiftPaddleWidth = thumbCenter.dx + halfTextWidth > rightEndPosition - ? thumbCenter.dx + halfTextWidth - rightEndPosition - : shiftPaddleWidth; + bottomNeckRadius + neckDifference / 2, + -thumbRadius - bottomNeckRadius * (1.0 - moveNeckValue), + ); + final double leftShiftWidth = thumbCenter.dx - halfTextWidth; + double shiftPaddleWidth = + leftShiftWidth < trackRect.left ? leftShiftWidth : 0; + final double rightShiftWidth = thumbCenter.dx + halfTextWidth; + shiftPaddleWidth = + rightShiftWidth > trackRect.right + ? rightShiftWidth - trackRect.right + : shiftPaddleWidth; final double leftPaddleWidth = paddleTopCircleRadius + paddleTopCircleCenter.dx + shiftPaddleWidth; final double rightPaddleWidth = paddleTopCircleRadius + paddleTopCircleCenter.dx - shiftPaddleWidth; - final double moveLeftTopNeckY = leftPaddleWidth > paddleTopCircleRadius - ? leftPaddleWidth < minPaddleWidth - ? (leftPaddleWidth - topNeckRadius) * moveNeckValue - : paddleTopCircleRadius * moveNeckValue - : 0; - final double moveLeftTopNeckAngle = leftPaddleWidth > paddleTopCircleRadius - ? leftPaddleWidth < minPaddleWidth - ? moveLeftTopNeckY * math.pi / 180 - : 30 * math.pi / 180 - : 0; - final double moveRightTopNeckY = rightPaddleWidth > paddleTopCircleRadius - ? rightPaddleWidth < minPaddleWidth - ? (rightPaddleWidth - topNeckRadius) * moveNeckValue - : paddleTopCircleRadius * moveNeckValue - : 0; + final double moveLeftTopNeckY = + leftPaddleWidth > paddleTopCircleRadius + ? leftPaddleWidth < minPaddleWidth + ? (leftPaddleWidth - topNeckRadius) * moveNeckValue + : paddleTopCircleRadius * moveNeckValue + : 0; + final double moveLeftTopNeckAngle = + leftPaddleWidth > paddleTopCircleRadius + ? leftPaddleWidth < minPaddleWidth + ? moveLeftTopNeckY * math.pi / 180 + : 30 * math.pi / 180 + : 0; + final double moveRightTopNeckY = + rightPaddleWidth > paddleTopCircleRadius + ? rightPaddleWidth < minPaddleWidth + ? (rightPaddleWidth - topNeckRadius) * moveNeckValue + : paddleTopCircleRadius * moveNeckValue + : 0; final double moveRightTopNeckAngle = rightPaddleWidth > paddleTopCircleRadius ? rightPaddleWidth < minPaddleWidth ? moveRightTopNeckY * math.pi / 180 : 30 * math.pi / 180 : 0; - final double leftNeckStretchValue = leftPaddleWidth < minPaddleWidth - ? (1.0 - (leftPaddleWidth / minPaddleWidth)) - : 0; - final double rightNeckStretchValue = rightPaddleWidth < minPaddleWidth - ? (1.0 - (rightPaddleWidth / minPaddleWidth)) - : 0; + final double leftNeckStretchValue = + leftPaddleWidth < minPaddleWidth + ? (1.0 - (leftPaddleWidth / minPaddleWidth)) + : 0; + final double rightNeckStretchValue = + rightPaddleWidth < minPaddleWidth + ? (1.0 - (rightPaddleWidth / minPaddleWidth)) + : 0; final double adjustPaddleCircleLeftArcAngle = shiftPaddleWidth < 0 && leftPaddleWidth < minPaddleWidth ? (leftNeckStretchValue * (math.pi / 2 + moveLeftTopNeckAngle)) @@ -835,24 +1014,25 @@ class SfPaddleTooltipShape extends SfTooltipShape { adjustPaddleCircleRightArcAngle * (1.0 - moveNeckValue); final Path path = _getPaddleTooltipPath( - neckDifference, - topNeckCenter, - moveRightTopNeckY, - topNeckRadius, - moveRightTopNeckAngle, - adjustRightNeckArcAngle, - paddleTopCircleCenter, - shiftPaddleWidth, - paddleTopCircleRadius, - adjustPaddleCircleRightArcAngle, - adjustPaddleCircleLeftArcAngle, - moveLeftTopNeckY, - moveLeftTopNeckAngle, - adjustLeftNeckArcAngle, - bottomNeckCenter, - bottomNeckRadius, - thumbRadius, - sliderThemeData); + neckDifference, + topNeckCenter, + moveRightTopNeckY, + topNeckRadius, + moveRightTopNeckAngle, + adjustRightNeckArcAngle, + paddleTopCircleCenter, + shiftPaddleWidth, + paddleTopCircleRadius, + adjustPaddleCircleRightArcAngle, + adjustPaddleCircleLeftArcAngle, + moveLeftTopNeckY, + moveLeftTopNeckAngle, + adjustLeftNeckArcAngle, + bottomNeckCenter, + bottomNeckRadius, + thumbRadius, + sliderThemeData, + ); context.canvas.save(); context.canvas.translate(thumbCenter.dx, thumbCenter.dy); @@ -876,113 +1056,145 @@ class SfPaddleTooltipShape extends SfTooltipShape { context.canvas.drawPath(path, strokePaint); context.canvas.drawPath(path, paint!); textPainter.paint( - context.canvas, - Offset(-textPainter.width / 2 - shiftPaddleWidth, - paddleTopCircleCenter.dy - textPainter.height / 2)); + context.canvas, + Offset( + -textPainter.width / 2 - shiftPaddleWidth, + paddleTopCircleCenter.dy - textPainter.height / 2, + ), + ); context.canvas.restore(); } Path _getPaddleTooltipPath( - double neckDifference, - Offset topNeckCenter, - double moveRightTopNeckY, - double topNeckRadius, - double moveRightTopNeckAngle, - double adjustRightNeckArcAngle, - Offset paddleTopCircleCenter, - double shiftPaddleWidth, - double paddleTopCircleRadius, - double adjustPaddleCircleRightArcAngle, - double adjustPaddleCircleLeftArcAngle, - double moveLeftTopNeckY, - double moveLeftTopNeckAngle, - double adjustLeftNeckArcAngle, - Offset bottomNeckCenter, - double bottomNeckRadius, - double thumbRadius, - SfSliderThemeData sliderThemeData) { + double neckDifference, + Offset topNeckCenter, + double moveRightTopNeckY, + double topNeckRadius, + double moveRightTopNeckAngle, + double adjustRightNeckArcAngle, + Offset paddleTopCircleCenter, + double shiftPaddleWidth, + double paddleTopCircleRadius, + double adjustPaddleCircleRightArcAngle, + double adjustPaddleCircleLeftArcAngle, + double moveLeftTopNeckY, + double moveLeftTopNeckAngle, + double adjustLeftNeckArcAngle, + Offset bottomNeckCenter, + double bottomNeckRadius, + double thumbRadius, + SfSliderThemeData sliderThemeData, + ) { final Path path = Path(); path.moveTo( - neckDifference / 2, topNeckCenter.dy + topNeckRadius * moveNeckValue); + neckDifference / 2, + topNeckCenter.dy + topNeckRadius * moveNeckValue, + ); // Drawn top paddle shape. path.arcTo( - Rect.fromCircle( - center: - Offset(topNeckCenter.dx, topNeckCenter.dy + moveRightTopNeckY), - radius: topNeckRadius), - math.pi, - math.pi / 3 + moveRightTopNeckAngle - adjustRightNeckArcAngle, - false); + Rect.fromCircle( + center: Offset(topNeckCenter.dx, topNeckCenter.dy + moveRightTopNeckY), + radius: topNeckRadius, + ), + math.pi, + math.pi / 3 + moveRightTopNeckAngle - adjustRightNeckArcAngle, + false, + ); path.arcTo( - Rect.fromCircle( - center: Offset(paddleTopCircleCenter.dx - shiftPaddleWidth, - paddleTopCircleCenter.dy), - radius: paddleTopCircleRadius), - math.pi / 2 - adjustPaddleCircleRightArcAngle, - -math.pi + adjustPaddleCircleRightArcAngle, - false); + Rect.fromCircle( + center: Offset( + paddleTopCircleCenter.dx - shiftPaddleWidth, + paddleTopCircleCenter.dy, + ), + radius: paddleTopCircleRadius, + ), + math.pi / 2 - adjustPaddleCircleRightArcAngle, + -math.pi + adjustPaddleCircleRightArcAngle, + false, + ); path.arcTo( - Rect.fromCircle( - center: Offset(-paddleTopCircleCenter.dx - shiftPaddleWidth, - paddleTopCircleCenter.dy), - radius: paddleTopCircleRadius), - 3 * math.pi / 2, - -math.pi + adjustPaddleCircleLeftArcAngle, - false); + Rect.fromCircle( + center: Offset( + -paddleTopCircleCenter.dx - shiftPaddleWidth, + paddleTopCircleCenter.dy, + ), + radius: paddleTopCircleRadius, + ), + 3 * math.pi / 2, + -math.pi + adjustPaddleCircleLeftArcAngle, + false, + ); path.arcTo( - Rect.fromCircle( - center: - Offset(-topNeckCenter.dx, topNeckCenter.dy + moveLeftTopNeckY), - radius: topNeckRadius), - 5 * math.pi / 3 - moveLeftTopNeckAngle + adjustLeftNeckArcAngle, - math.pi / 3 + moveLeftTopNeckAngle - adjustLeftNeckArcAngle, - false); + Rect.fromCircle( + center: Offset(-topNeckCenter.dx, topNeckCenter.dy + moveLeftTopNeckY), + radius: topNeckRadius, + ), + 5 * math.pi / 3 - moveLeftTopNeckAngle + adjustLeftNeckArcAngle, + math.pi / 3 + moveLeftTopNeckAngle - adjustLeftNeckArcAngle, + false, + ); // Drawn bottom thumb shape. path.arcTo( - Rect.fromCircle( - center: Offset(-bottomNeckCenter.dx, bottomNeckCenter.dy), - radius: bottomNeckRadius), - 0.0, - math.pi / 3, - false); - path.arcTo(Rect.fromCircle(center: Offset.zero, radius: thumbRadius), - 3 * math.pi / 2, -math.pi, false); - path.arcTo(Rect.fromCircle(center: Offset.zero, radius: thumbRadius), - math.pi / 2, -math.pi, false); + Rect.fromCircle( + center: Offset(-bottomNeckCenter.dx, bottomNeckCenter.dy), + radius: bottomNeckRadius, + ), + 0.0, + math.pi / 3, + false, + ); path.arcTo( - Rect.fromCircle(center: bottomNeckCenter, radius: bottomNeckRadius), - 2 * math.pi / 3, - math.pi / 3, - false); + Rect.fromCircle(center: Offset.zero, radius: thumbRadius), + 3 * math.pi / 2, + -math.pi, + false, + ); + path.arcTo( + Rect.fromCircle(center: Offset.zero, radius: thumbRadius), + math.pi / 2, + -math.pi, + false, + ); + path.arcTo( + Rect.fromCircle(center: bottomNeckCenter, radius: bottomNeckRadius), + 2 * math.pi / 3, + math.pi / 3, + false, + ); return path; } /// Draws the tooltip based on the values passed in the arguments. @override - void paint(PaintingContext context, Offset thumbCenter, Offset offset, - TextPainter textPainter, - {required RenderBox parentBox, - required SfSliderThemeData sliderThemeData, - required Paint paint, - required Animation animation, - required Rect trackRect}) { + void paint( + PaintingContext context, + Offset thumbCenter, + Offset offset, + TextPainter textPainter, { + required RenderBox parentBox, + required SfSliderThemeData sliderThemeData, + required Paint paint, + required Animation animation, + required Rect trackRect, + }) { _drawPaddleTooltip( - parentBox, - textPainter, - minPaddleTopCircleRadius, - neckDifference, - sliderThemeData, - defaultThumbRadius, - minBottomNeckRadius, - textPadding, - offset, - moveNeckValue, - thumbCenter, - trackRect, - context, - animation, - paint); + parentBox, + textPainter, + minPaddleTopCircleRadius, + neckDifference, + sliderThemeData, + defaultThumbRadius, + minBottomNeckRadius, + textPadding, + offset, + moveNeckValue, + thumbCenter, + trackRect, + context, + animation, + paint, + ); } } @@ -1005,8 +1217,13 @@ class SfRectangularTooltipShape extends SfTooltipShape { } Path _updateRectangularTooltipWidth( - Size textSize, double tooltipStartY, Rect trackRect, double dx, - {required bool isVertical, bool? isLeftTooltip}) { + 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; @@ -1017,68 +1234,94 @@ class SfRectangularTooltipShape extends SfTooltipShape { const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; if (isVertical) { if (isLeftTooltip!) { - double topLineHeight = dx - halfTooltipHeight < trackRect.top - ? dx - trackRect.top - : halfTooltipHeight; + 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); + 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; + 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); + 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 + 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; + rightLineWidth = + leftLineWidth < halfTooltipWidth + ? halfTooltipWidth - leftLineWidth + rightLineWidth + : rightLineWidth; } - return _getRectangularPath(tooltipStartY, rightLineWidth, - halfTooltipTriangleWidth, dy, tooltipHeight, leftLineWidth, - isVertical: isVertical); + return _getRectangularPath( + tooltipStartY, + rightLineWidth, + halfTooltipTriangleWidth, + dy, + tooltipHeight, + leftLineWidth, + isVertical: isVertical, + ); } } Path _getRectangularPath( - double tooltipStartY, - double rightLineWidth, - double halfTooltipTriangleWidth, - double dy, - double tooltipHeight, - double leftLineWidth, - {required bool isVertical, - double? toolTipWidth, - bool? isLeftTooltip}) { + double tooltipStartY, + double rightLineWidth, + double halfTooltipTriangleWidth, + double dy, + double tooltipHeight, + double leftLineWidth, { + required bool isVertical, + double? toolTipWidth, + bool? isLeftTooltip, + }) { final Path path = Path(); if (isVertical && toolTipWidth != null) { if (isLeftTooltip!) { @@ -1086,29 +1329,33 @@ class SfRectangularTooltipShape extends SfTooltipShape { // / final bool canAdjustTooltipNose = rightLineWidth < halfTooltipTriangleWidth; - path.lineTo(-dy, - 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); + -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); + -dy - toolTipWidth + cornerRadius / 2, + -rightLineWidth, + -dy - toolTipWidth, + -rightLineWidth + cornerRadius / 2, + ); // | // | / // |__________| @@ -1131,8 +1378,12 @@ class SfRectangularTooltipShape extends SfTooltipShape { // | / // |__________| if (leftLineWidth > halfTooltipTriangleWidth) { - path.quadraticBezierTo(-dy - cornerRadius / 2, leftLineWidth, -dy, - leftLineWidth - cornerRadius / 2); + path.quadraticBezierTo( + -dy - cornerRadius / 2, + leftLineWidth, + -dy, + leftLineWidth - cornerRadius / 2, + ); path.lineTo(-dy, halfTooltipTriangleWidth); } // __________ @@ -1147,8 +1398,10 @@ class SfRectangularTooltipShape extends SfTooltipShape { // \ final bool canAdjustTooltipNose = rightLineWidth < halfTooltipTriangleWidth; - path.lineTo(dy, - canAdjustTooltipNose ? -rightLineWidth : -halfTooltipTriangleWidth); + path.lineTo( + dy, + canAdjustTooltipNose ? -rightLineWidth : -halfTooltipTriangleWidth, + ); // \ // | if (!canAdjustTooltipNose) { @@ -1157,21 +1410,23 @@ class SfRectangularTooltipShape extends SfTooltipShape { // \ // |__________ path.quadraticBezierTo( - dy, - canAdjustTooltipNose - ? -rightLineWidth - : -rightLineWidth + cornerRadius / 2, - dy + cornerRadius / 2, - -rightLineWidth); + 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); + dy + toolTipWidth - cornerRadius / 2, + -rightLineWidth, + dy + toolTipWidth, + -rightLineWidth + cornerRadius / 2, + ); path.lineTo(dy + toolTipWidth, leftLineWidth - cornerRadius / 2); // ___________ // | @@ -1191,8 +1446,12 @@ class SfRectangularTooltipShape extends SfTooltipShape { // \ | // |__________| if (leftLineWidth > halfTooltipTriangleWidth) { - path.quadraticBezierTo(dy + cornerRadius / 2, leftLineWidth, dy, - leftLineWidth - cornerRadius / 2); + path.quadraticBezierTo( + dy + cornerRadius / 2, + leftLineWidth, + dy, + leftLineWidth - cornerRadius / 2, + ); path.lineTo(dy, halfTooltipTriangleWidth); } // ___________ @@ -1208,34 +1467,51 @@ class SfRectangularTooltipShape extends SfTooltipShape { final bool canAdjustTooltipNose = rightLineWidth > halfTooltipTriangleWidth + cornerRadius / 2; path.lineTo( - canAdjustTooltipNose ? halfTooltipTriangleWidth : rightLineWidth, - -dy - (canAdjustTooltipNose ? 0 : cornerRadius / 2)); + canAdjustTooltipNose ? halfTooltipTriangleWidth : rightLineWidth, + -dy - (canAdjustTooltipNose ? 0 : cornerRadius / 2), + ); // ___ // / path.lineTo(rightLineWidth - cornerRadius, -dy); // ___| // / path.quadraticBezierTo( - rightLineWidth, -dy, rightLineWidth, -dy - cornerRadius); + rightLineWidth, + -dy, + rightLineWidth, + -dy - cornerRadius, + ); path.lineTo(rightLineWidth, -dy - tooltipHeight + cornerRadius); // _______ // ___| // / - path.quadraticBezierTo(rightLineWidth, -dy - tooltipHeight, - rightLineWidth - cornerRadius, -dy - tooltipHeight); + 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.quadraticBezierTo( + -leftLineWidth, + -dy - tooltipHeight, + -leftLineWidth, + -dy - tooltipHeight + cornerRadius, + ); path.lineTo(-leftLineWidth, -dy - cornerRadius); // ________ // |___ ___| // / if (leftLineWidth > halfTooltipTriangleWidth) { path.quadraticBezierTo( - -leftLineWidth, -dy, -leftLineWidth + cornerRadius, -dy); + -leftLineWidth, + -dy, + -leftLineWidth + cornerRadius, + -dy, + ); path.lineTo(-halfTooltipTriangleWidth, -dy); } // ________ @@ -1248,24 +1524,37 @@ class SfRectangularTooltipShape extends SfTooltipShape { /// Draws the tooltip based on the values passed in the arguments. @override - void paint(PaintingContext context, Offset thumbCenter, Offset offset, - TextPainter textPainter, - {required RenderBox parentBox, - required SfSliderThemeData sliderThemeData, - required Paint paint, - required Animation animation, - required Rect trackRect}) { + void paint( + PaintingContext context, + Offset thumbCenter, + Offset offset, + TextPainter textPainter, { + 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; // 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)); + 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); @@ -1302,54 +1591,60 @@ class SfRectangularTooltipShape extends SfTooltipShape { final double halfTextPainterHeight = textPainter.height / 2; final double rectTopPosition = thumbCenter.dy - halfPathHeight; if (_isLeftTooltip(parentBox)) { - final double dx = -offset.dy - + 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 - + final double dy = + rectTopPosition >= trackRect.top + ? thumbCenter.dy + halfPathHeight >= trackRect.bottom + ? -halfTextPainterHeight - + halfPathHeight - + thumbCenter.dy + + trackRect.bottom + : -halfTextPainterHeight + : -halfTextPainterHeight + halfPathHeight - thumbCenter.dy + - trackRect.bottom - : -halfTextPainterHeight - : -halfTextPainterHeight + - halfPathHeight - - thumbCenter.dy + - trackRect.top; + trackRect.top; textPainter.paint(context.canvas, Offset(dx, dy)); } else { - final double dx = offset.dy + + 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 - + final double dy = + rectTopPosition >= trackRect.top + ? thumbCenter.dy + halfPathHeight >= trackRect.bottom + ? -halfTextPainterHeight - + halfPathHeight - + thumbCenter.dy + + trackRect.bottom + : -halfTextPainterHeight + : -halfTextPainterHeight + halfPathHeight - thumbCenter.dy + - trackRect.bottom - : -halfTextPainterHeight - : -halfTextPainterHeight + - halfPathHeight - - thumbCenter.dy + - trackRect.top; + trackRect.top; textPainter.paint(context.canvas, Offset(dx, dy)); } } else { - final double dx = rectLeftPosition >= minLeftX - ? thumbCenter.dx + halfTextPainterWidth + leftPadding > - trackRect.right - ? -halfTextPainterWidth - + final double dx = + rectLeftPosition >= minLeftX + ? thumbCenter.dx + halfTextPainterWidth + leftPadding > + trackRect.right + ? -halfTextPainterWidth - + halfPathWidth + + trackRect.right - + thumbCenter.dx + : -halfTextPainterWidth + : -halfTextPainterWidth + halfPathWidth + - trackRect.right - - thumbCenter.dx - : -halfTextPainterWidth - : -halfTextPainterWidth + - halfPathWidth + - trackRect.left - - thumbCenter.dx; - final double dy = offset.dy + + trackRect.left - + thumbCenter.dx; + final double dy = + offset.dy + tooltipTriangleHeight + (pathRect.size.height - tooltipTriangleHeight) / 2 + textPainter.height / 2; diff --git a/packages/syncfusion_flutter_sliders/lib/src/theme.dart b/packages/syncfusion_flutter_sliders/lib/src/theme.dart new file mode 100644 index 000000000..54629c1b0 --- /dev/null +++ b/packages/syncfusion_flutter_sliders/lib/src/theme.dart @@ -0,0 +1,257 @@ +import 'package:flutter/material.dart'; + +import 'package:syncfusion_flutter_core/theme.dart'; + +///SfSlidersThemeData gives the color to [SfSlider] +///It gets the [context] as parameter in constructors +///[SlidersThemeData] override the properties +///[SlidersThemeData] get the color of properties. + +class SlidersThemeData extends SfSliderThemeData { + ///context is parameter of the constructor. + SlidersThemeData(this.context); + + ///[context] refers to the current state of the widget. + final BuildContext context; + + ///SfTheme.colorScheme(context) retrives the SfThemeData + ///[colorScheme] is a variable along with + /// late and final keyword to obtain the theme of the context + /// from [SfColorScheme]. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.primary[61]; + + @override + Color? get disabledActiveTrackColor => colorScheme.onSurface[82]; + + @override + Color? get disabledInactiveTrackColor => colorScheme.onSurface[31]; + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary[31]; + + @override + Color? get disabledThumbColor => + Color.alphaBlend(colorScheme.onSurface[97]!, colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.onSurface[94]; + + @override + Color? get inactiveTickColor => colorScheme.onSurface[94]; + + @override + Color? get activeMinorTickColor => colorScheme.onSurface[94]; + + @override + Color? get inactiveMinorTickColor => colorScheme.onSurface[94]; + + @override + Color? get activeDividerColor => colorScheme.onPrimary[138]; + + @override + Color? get inactiveDividerColor => colorScheme.primary[138]; + + @override + Color? get disabledActiveDividerColor => colorScheme.onPrimary[31]; + + @override + Color? get disabledInactiveDividerColor => colorScheme.onPrimary[31]; + + @override + Color? get disabledActiveTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledInactiveTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledActiveMinorTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledInactiveMinorTickColor => colorScheme.onSurface[61]; + + @override + Color? get tooltipBackgroundColor => colorScheme.primary[97]; +} + +///SfRangeSLiderThemeData gives the color to [SfRangeSlider] +///It gets the [context] as parameter in constructors +///[RangeSliderThemeData] override the properties +///[RangeSliderThemeData] get the color of properties. +/// +///return SfRangeSliderThemeData() as RangeSliderThemeData; +class RangeSliderThemeData extends SfRangeSliderThemeData { + ///context is parameter of the constructor. + RangeSliderThemeData(this.context); + + ///context refers to the current state of the widget. + final BuildContext context; + + ///SfTheme.of(context) retrives the SfThemeData + ///[colorScheme] is a variable along with + /// late and final keyword to obtain the theme of the context + /// from [SfColorScheme]. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.primary[61]; + + @override + Color? get disabledActiveTrackColor => colorScheme.onSurface[82]; + + @override + Color? get disabledInactiveTrackColor => colorScheme.onSurface[31]; + + @override + Color? get disabledThumbColor => + Color.alphaBlend(colorScheme.onSurface[97]!, colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.onSurface[94]; + + @override + Color? get inactiveTickColor => colorScheme.onSurface[94]; + + @override + Color? get activeMinorTickColor => colorScheme.onSurface[94]; + + @override + Color? get inactiveMinorTickColor => colorScheme.onSurface[94]; + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary[31]; + + @override + Color? get activeDividerColor => colorScheme.onPrimary[138]; + + @override + Color? get inactiveDividerColor => colorScheme.primary[138]; + + @override + Color? get disabledActiveDividerColor => colorScheme.onPrimary[31]; + + @override + Color? get disabledInactiveDividerColor => colorScheme.onPrimary[31]; + + @override + Color? get disabledActiveTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledInactiveTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledActiveMinorTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledInactiveMinorTickColor => colorScheme.onSurface[61]; + + @override + Color? get tooltipBackgroundColor => colorScheme.primary[97]; + + @override + Color? get overlappingTooltipStrokeColor => colorScheme.surface; + + @override + Color? get overlappingThumbStrokeColor => colorScheme.surface; +} + +///SfRangeSelectorThemeData gives the color to [SfRangeSelector] +///It gets the [context] as parameter in constructors +///[RangeSelectorThemeData] override the properties +///[RangeSelectorThemeData] get the color of properties. +/// +///return SfRangeSelectorThemeData() as RangeSelectorThemeData. +class RangeSelectorThemeData extends SfRangeSelectorThemeData { + ///context is parameter of the constructor. + RangeSelectorThemeData(this.context); + + ///context refers to the current state of the widget. + final BuildContext context; + + ///SfTheme.of(context) retrives the SfThemeData + ///[colorScheme] is a variable along with + /// late and final keyword to obtain the theme of the context + /// from [SfColorScheme]. + late final SfColorScheme colorScheme = SfTheme.colorScheme(context); + + @override + Color? get activeTrackColor => colorScheme.primary; + + @override + Color? get inactiveTrackColor => colorScheme.primary[61]; + + @override + Color? get disabledActiveTrackColor => colorScheme.onSurface[82]; + + @override + Color? get disabledInactiveTrackColor => colorScheme.onSurface[31]; + + @override + Color? get disabledThumbColor => + Color.alphaBlend(colorScheme.onSurface[97]!, colorScheme.surface); + + @override + Color? get activeTickColor => colorScheme.onSurface[94]; + + @override + Color? get inactiveTickColor => colorScheme.onSurface[94]; + + @override + Color? get activeMinorTickColor => colorScheme.onSurface[94]; + + @override + Color? get inactiveMinorTickColor => colorScheme.onSurface[94]; + + @override + Color? get thumbColor => colorScheme.primary; + + @override + Color? get overlayColor => colorScheme.primary[31]; + + @override + Color? get activeDividerColor => colorScheme.onPrimary[138]; + + @override + Color? get inactiveDividerColor => colorScheme.primary[138]; + + @override + Color? get disabledActiveDividerColor => colorScheme.onPrimary[31]; + + @override + Color? get disabledInactiveDividerColor => colorScheme.onPrimary[31]; + + @override + Color? get disabledActiveTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledInactiveTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledActiveMinorTickColor => colorScheme.onSurface[61]; + + @override + Color? get disabledInactiveMinorTickColor => colorScheme.onSurface[61]; + + @override + Color? get tooltipBackgroundColor => colorScheme.primary[97]; + + @override + Color? get activeRegionColor => Colors.transparent; + + @override + Color? get inactiveRegionColor => colorScheme.scrim[82]; +} diff --git a/packages/syncfusion_flutter_sliders/pubspec.yaml b/packages/syncfusion_flutter_sliders/pubspec.yaml index 0d7f32ddd..534d30a0a 100644 --- a/packages/syncfusion_flutter_sliders/pubspec.yaml +++ b/packages/syncfusion_flutter_sliders/pubspec.yaml @@ -1,18 +1,35 @@ name: syncfusion_flutter_sliders description: A Flutter Sliders library for creating highly customizable and UI-rich slider, range slider, and range selector widgets for filtering purposes. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_sliders +screenshots: + - description: 'This screenshot shows slider and range slider image.' + path: screenshots/slider.png + - description: 'This screenshot shows vertical slider and range slider image.' + path: screenshots/vertical_slider.png + - description: 'This screenshot shows range selector image.' + path: screenshots/range_selector_screenshot.png + - description: 'This demo shows charts zooming while manipulating the range selector.' + path: screenshots/range_selector_zooming.gif + environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: sdk: flutter - - intl: ">=0.15.0 <0.20.0" - + intl: '>=0.18.1 <0.21.0' syncfusion_flutter_core: - path: ../syncfusion_flutter_core + path: ../syncfusion_flutter_core + + +dev_dependencies: + syncfusion_flutter_charts: + path:../syncfusion_flutter_charts + -flutter: \ No newline at end of file +flutter: + assets: + - assets/fonts/Roboto-Medium.ttf + - assets/images/lake.jpg diff --git a/packages/syncfusion_flutter_sliders/screenshots/range_selector_screenshot.png b/packages/syncfusion_flutter_sliders/screenshots/range_selector_screenshot.png new file mode 100644 index 000000000..66c3238b0 Binary files /dev/null and b/packages/syncfusion_flutter_sliders/screenshots/range_selector_screenshot.png differ diff --git a/packages/syncfusion_flutter_sliders/screenshots/range_selector_zooming.gif b/packages/syncfusion_flutter_sliders/screenshots/range_selector_zooming.gif new file mode 100644 index 000000000..11ac9c9ad Binary files /dev/null and b/packages/syncfusion_flutter_sliders/screenshots/range_selector_zooming.gif differ diff --git a/packages/syncfusion_flutter_sliders/screenshots/slider.png b/packages/syncfusion_flutter_sliders/screenshots/slider.png new file mode 100644 index 000000000..21eea1e38 Binary files /dev/null and b/packages/syncfusion_flutter_sliders/screenshots/slider.png differ diff --git a/packages/syncfusion_flutter_sliders/screenshots/vertical_slider.png b/packages/syncfusion_flutter_sliders/screenshots/vertical_slider.png new file mode 100644 index 000000000..442fc321e Binary files /dev/null and b/packages/syncfusion_flutter_sliders/screenshots/vertical_slider.png differ diff --git a/packages/syncfusion_flutter_treemap/CHANGELOG.md b/packages/syncfusion_flutter_treemap/CHANGELOG.md index 7a9852f98..6730dedb2 100644 --- a/packages/syncfusion_flutter_treemap/CHANGELOG.md +++ b/packages/syncfusion_flutter_treemap/CHANGELOG.md @@ -1,5 +1,56 @@ ## Unreleased +**General** + +* The compatible version of our Flutter treemap widget has been updated to Flutter SDK 3.32.0. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter treemap widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter treemap example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter treemap example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 12/24/2024 + +**General** + +* The compatible version of our Flutter treemap widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter treemap widget has been updated to Flutter SDK 3.24.0. + +## [25.1.35] - 03/15/2024 + +**General** + +* Provided th​e Material 3 themes support. + +## [20.2.36] - 07/01/2022 + **Features** * Legend pointer - Provided an option to show a pointer on the solid bar legend while hover over the tile. diff --git a/packages/syncfusion_flutter_treemap/LICENSE b/packages/syncfusion_flutter_treemap/LICENSE index 54221e4e2..138970525 100644 --- a/packages/syncfusion_flutter_treemap/LICENSE +++ b/packages/syncfusion_flutter_treemap/LICENSE @@ -1,12 +1,12 @@ -Syncfusion License +Syncfusion® License -Syncfusion Flutter Treemap 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. +Syncfusion® Flutter Treemap 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. +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® 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. +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® license containing all terms and conditions. -The Syncfusion license that contains the terms and conditions can be found at +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_treemap/README.md b/packages/syncfusion_flutter_treemap/README.md index 8969937eb..003e21923 100644 --- a/packages/syncfusion_flutter_treemap/README.md +++ b/packages/syncfusion_flutter_treemap/README.md @@ -8,7 +8,7 @@ A Flutter Treemap library for creating interactive treemap to visualize flat and 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. +**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) @@ -20,7 +20,7 @@ Create a highly interactive and customizable Flutter Treemap that has features s - [Mapping the data source](#mapping-the-data-source) - [Add treemap elements](#add-treemap-elements) - [Support and Feedback](#support-and-feedback) -- [About Syncfusion](#about-syncfusion) +- [About Syncfusion®](#about-syncfusion) ## Treemap features @@ -78,23 +78,20 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

## Useful links -Take a look at the following to learn more about Syncfusion Flutter Treemap: +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) +* [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) @@ -252,11 +249,11 @@ The following screenshot illustrates the result of the above code sample. ## 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). +* For questions, reach out to our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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 +## 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. +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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_treemap/analysis_options.yaml b/packages/syncfusion_flutter_treemap/analysis_options.yaml index 2f547af5d..7bd9823d9 100644 --- a/packages/syncfusion_flutter_treemap/analysis_options.yaml +++ b/packages/syncfusion_flutter_treemap/analysis_options.yaml @@ -1,5 +1 @@ -include: package:syncfusion_flutter_core/analysis_options.yaml - -analyzer: - errors: - include_file_not_found: ignore \ No newline at end of file +include: package:syncfusion_flutter_core/analysis_options.yaml \ No newline at end of file diff --git a/packages/syncfusion_flutter_treemap/assets/fonts/Roboto-Medium.ttf b/packages/syncfusion_flutter_treemap/assets/fonts/Roboto-Medium.ttf new file mode 100644 index 000000000..d629e9848 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/fonts/Roboto-Medium.ttf differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/facebook.png b/packages/syncfusion_flutter_treemap/assets/images/facebook.png new file mode 100644 index 000000000..86d2e724b Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/facebook.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/instagram.png b/packages/syncfusion_flutter_treemap/assets/images/instagram.png new file mode 100644 index 000000000..af4bdf3d3 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/instagram.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/linkedin.png b/packages/syncfusion_flutter_treemap/assets/images/linkedin.png new file mode 100644 index 000000000..a1ce08c90 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/linkedin.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/snapchat.png b/packages/syncfusion_flutter_treemap/assets/images/snapchat.png new file mode 100644 index 000000000..a7cd07566 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/snapchat.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/telegram.png b/packages/syncfusion_flutter_treemap/assets/images/telegram.png new file mode 100644 index 000000000..3b9926cda Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/telegram.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/treemap_medal.png b/packages/syncfusion_flutter_treemap/assets/images/treemap_medal.png new file mode 100644 index 000000000..9c4e2ff21 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/treemap_medal.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/twitter.png b/packages/syncfusion_flutter_treemap/assets/images/twitter.png new file mode 100644 index 000000000..032a520af Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/twitter.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/we_chat.png b/packages/syncfusion_flutter_treemap/assets/images/we_chat.png new file mode 100644 index 000000000..f63641cc3 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/we_chat.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/whatsapp.png b/packages/syncfusion_flutter_treemap/assets/images/whatsapp.png new file mode 100644 index 000000000..16786577d Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/whatsapp.png differ diff --git a/packages/syncfusion_flutter_treemap/assets/images/youtube.png b/packages/syncfusion_flutter_treemap/assets/images/youtube.png new file mode 100644 index 000000000..95fbe39b7 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/assets/images/youtube.png differ 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 index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_flutter_treemap/example/android/.gitignore +++ b/packages/syncfusion_flutter_treemap/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_treemap/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_treemap/example/android/app/build.gradle.kts new file mode 100644 index 000000000..6d5efa5c0 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.treemap_sample" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.treemap_sample" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} 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 index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_treemap/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - 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 index 3f41384db..1c7f2c97d 100644 --- a/packages/syncfusion_flutter_treemap/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/kotlin/com/example/treemap_sample/MainActivity.kt b/packages/syncfusion_flutter_treemap/example/android/app/src/main/kotlin/com/example/treemap_sample/MainActivity.kt new file mode 100644 index 000000000..fd78e2aa2 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/kotlin/com/example/treemap_sample/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.treemap_sample + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + 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 index d460d1e92..cb1ef8805 100644 --- 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 @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_treemap/example/android/build.gradle.kts b/packages/syncfusion_flutter_treemap/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_treemap/example/android/gradle.properties b/packages/syncfusion_flutter_treemap/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_flutter_treemap/example/android/gradle.properties +++ b/packages/syncfusion_flutter_treemap/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=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 index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_flutter_treemap/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_treemap/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_treemap/example/android/settings.gradle.kts b/packages/syncfusion_flutter_treemap/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_treemap/example/ios/.gitignore b/packages/syncfusion_flutter_treemap/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_treemap/example/ios/.gitignore +++ b/packages/syncfusion_flutter_treemap/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf47..7c5696400 100644 --- a/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 12.0 diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj index 1aec2aa07..c9beac0cd 100644 --- a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -101,6 +126,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -117,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -127,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -145,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -171,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -185,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -200,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -211,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -235,6 +315,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -264,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -272,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -289,17 +371,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -307,10 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -340,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -354,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -366,6 +492,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -395,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -403,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -421,17 +550,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -448,17 +572,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -469,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -490,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency 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 index 1d526a16e..919434a62 100644 --- 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 @@ -2,6 +2,6 @@ + location = "self:"> 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 index a28140cfd..15c313e20 100644 --- 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 @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Treemap Sample CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - example + treemap_sample CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_treemap/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_treemap/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_treemap/example/lib/main.dart b/packages/syncfusion_flutter_treemap/example/lib/main.dart index 8406dfa85..bc72ed4f9 100644 --- a/packages/syncfusion_flutter_treemap/example/lib/main.dart +++ b/packages/syncfusion_flutter_treemap/example/lib/main.dart @@ -2,17 +2,16 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_treemap/treemap.dart'; void main() { - return runApp(TreemapApp()); + return runApp(const TreemapApp()); } /// This widget will be the root of application. class TreemapApp extends StatelessWidget { + const TreemapApp({super.key}); + @override Widget build(BuildContext context) { - return const MaterialApp( - title: 'Treemap Demo', - home: MyHomePage(), - ); + return const MaterialApp(title: 'Treemap Demo', home: MyHomePage()); } } @@ -68,8 +67,9 @@ class _MyHomePageState extends State { return Padding( padding: const EdgeInsets.all(10), child: Text( - '''Country : ${tile.group}\nSocial media : ${tile.weight}M''', - style: const TextStyle(color: Colors.black)), + '''Country : ${tile.group}\nSocial media : ${tile.weight}M''', + style: const TextStyle(color: Colors.black), + ), ); }, ), diff --git a/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_treemap/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_treemap/example/macos/.gitignore b/packages/syncfusion_flutter_treemap/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/.gitignore +++ b/packages/syncfusion_flutter_treemap/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..9ceef112a 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* treemap_sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "treemap_sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* treemap_sample.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* treemap_sample.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/treemap_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/treemap_sample"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/treemap_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/treemap_sample"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/treemap_sample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/treemap_sample"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..5be9b4e83 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_treemap/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_treemap/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_treemap/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_treemap/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_treemap/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_treemap/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_treemap/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..5249714b9 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_treemap/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = treemap_sample // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.treemapSample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_treemap/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_treemap/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_treemap/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_treemap/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_treemap/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_flutter_treemap/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_flutter_treemap/example/pubspec.yaml b/packages/syncfusion_flutter_treemap/example/pubspec.yaml index d2cda59d0..d8c631e2e 100644 --- a/packages/syncfusion_flutter_treemap/example/pubspec.yaml +++ b/packages/syncfusion_flutter_treemap/example/pubspec.yaml @@ -1,75 +1,24 @@ 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" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter syncfusion_flutter_treemap: - path: ../../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 + cupertino_icons: '>=0.1.2 <1.0.7' 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/example/web/index.html b/packages/syncfusion_flutter_treemap/example/web/index.html index 0081e1894..34718f98e 100644 --- a/packages/syncfusion_flutter_treemap/example/web/index.html +++ b/packages/syncfusion_flutter_treemap/example/web/index.html @@ -27,72 +27,6 @@ - - + diff --git a/packages/syncfusion_flutter_treemap/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_treemap/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_treemap/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_treemap/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_treemap/lib/src/animated_border.dart b/packages/syncfusion_flutter_treemap/lib/src/animated_border.dart index a01509314..f8adb272f 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/animated_border.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/animated_border.dart @@ -19,10 +19,11 @@ class AnimatedBorder extends RoundedRectangleBorder { Rect _getRect(Rect rect, double width) { if (scaleSize != null) { return Rect.fromLTRB( - rect.left + (width / scaleSize!.width), - rect.top + (width / scaleSize!.height), - rect.right - (width / scaleSize!.width), - rect.bottom - (width / scaleSize!.height)); + rect.left + (width / scaleSize!.width), + rect.top + (width / scaleSize!.height), + rect.right - (width / scaleSize!.width), + rect.bottom - (width / scaleSize!.height), + ); } return rect; @@ -44,33 +45,43 @@ class AnimatedBorder extends RoundedRectangleBorder { // But we need to reduce the border radius by using the current // border width. currentBorderRadius = BorderRadius.only( - topRight: Radius.elliptical( - currentBorderRadius.topRight.x - borderWidth, - currentBorderRadius.topRight.y - borderHeight), - bottomLeft: Radius.elliptical( - currentBorderRadius.bottomLeft.x - borderWidth, - currentBorderRadius.bottomLeft.y - borderHeight), - bottomRight: Radius.elliptical( - currentBorderRadius.bottomRight.x - borderWidth, - currentBorderRadius.bottomRight.y - borderHeight), - topLeft: Radius.elliptical( - currentBorderRadius.topLeft.x - borderWidth, - currentBorderRadius.topLeft.y - borderHeight)); + topRight: Radius.elliptical( + currentBorderRadius.topRight.x - borderWidth, + currentBorderRadius.topRight.y - borderHeight, + ), + bottomLeft: Radius.elliptical( + currentBorderRadius.bottomLeft.x - borderWidth, + currentBorderRadius.bottomLeft.y - borderHeight, + ), + bottomRight: Radius.elliptical( + currentBorderRadius.bottomRight.x - borderWidth, + currentBorderRadius.bottomRight.y - borderHeight, + ), + topLeft: Radius.elliptical( + currentBorderRadius.topLeft.x - borderWidth, + currentBorderRadius.topLeft.y - borderHeight, + ), + ); } if (width == 0.0) { innerRect = _getRect(rect, width); canvas.drawRRect( - borderRadius.resolve(textDirection).toRRect(innerRect), - side.toPaint()); + borderRadius.resolve(textDirection).toRRect(innerRect), + side.toPaint(), + ); } else { final RRect outer = borderRadius.resolve(textDirection).toRRect(rect); innerRect = _getRect( - Rect.fromLTRB(outer.left, outer.top, outer.right, outer.bottom), - width); - final RRect inner = scaleSize != null - ? currentBorderRadius.resolve(textDirection).toRRect(innerRect) - : outer.deflate(width); + Rect.fromLTRB(outer.left, outer.top, outer.right, outer.bottom), + width, + ); + final RRect inner = + scaleSize != null + ? currentBorderRadius + .resolve(textDirection) + .toRRect(innerRect) + : outer.deflate(width); final Paint paint = Paint()..color = side.color; canvas.drawDRRect(outer, inner, paint); } diff --git a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart index ecd86c1c3..209425c82 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart @@ -16,30 +16,42 @@ 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]) { +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); + ((1 - factor) * (color.r * 255) + factor * (mix.r * 255)).toInt(), + ((1 - factor) * (color.g * 255) + factor * (mix.g * 255)).toInt(), + ((1 - factor) * (color.b * 255) + factor * (mix.b * 255)).toInt(), + 1, + ); } /// Ignored scaling to the label builder and item builder and provides the /// opacity animation. Widget _buildAnimatedBuilder( - TreemapTile tile, Size scale, AnimationStatus status, Widget child) { + TreemapTile tile, + Size scale, + AnimationStatus status, + Widget child, +) { Size gap = Size.zero; if (tile.level.border != null) { - gap = Size(tile.level.border!.dimensions.horizontal, - tile.level.border!.dimensions.vertical); + gap = Size( + tile.level.border!.dimensions.horizontal, + tile.level.border!.dimensions.vertical, + ); } if (tile.level.padding != null) { - gap = Size(tile.level.padding!.horizontal + gap.width, - tile.level.padding!.vertical + gap.height); + gap = Size( + tile.level.padding!.horizontal + gap.width, + tile.level.padding!.vertical + gap.height, + ); } child = Transform( @@ -52,12 +64,14 @@ Widget _buildAnimatedBuilder( // We have to increase the label/item builder's size while drilling down // to maintain its position so used [OverflowBox]. if (scale.width < 1 || scale.height < 1) { - width = scale.width < 1 - ? tile._size!.width * scale.width - gap.width - : tile._size!.width - gap.width; - height = scale.height < 1 - ? tile._size!.height * scale.height - gap.height - : tile._size!.height - gap.height; + width = + scale.width < 1 + ? tile._size!.width * scale.width - gap.width + : tile._size!.width - gap.width; + height = + scale.height < 1 + ? tile._size!.height * scale.height - gap.height + : tile._size!.height - gap.height; return Align( alignment: Alignment.topLeft, child: SizedBox( @@ -72,31 +86,35 @@ Widget _buildAnimatedBuilder( ), ); } else { - width = scale.width > 1 - ? tile._size!.width * scale.width - gap.width - : tile._size!.width - gap.width; - height = scale.height > 1 - ? tile._size!.height * scale.height - gap.height - : tile._size!.height - gap.height; + width = + scale.width > 1 + ? tile._size!.width * scale.width - gap.width + : tile._size!.width - gap.width; + height = + scale.height > 1 + ? tile._size!.height * scale.height - gap.height + : tile._size!.height - gap.height; return OverflowBox( - alignment: Alignment.topLeft, - maxHeight: height > 0 ? height : 0.0, - maxWidth: width > 0 ? width : 0.0, - child: child); + alignment: Alignment.topLeft, + maxHeight: height > 0 ? height : 0.0, + maxWidth: width > 0 ? width : 0.0, + child: child, + ); } } Widget? _buildLabeAndItemBuilder( - Widget current, - TreemapTile tile, - int visibleIndex, - AnimationStatus animationStatus, - Animation scalingAndTranslationAnimation, - Animation opacityAnimation, - Tween currentLevelScale, - Tween nextLevelScale, - Tween tileOpacity, - Tween labelAndItemBuilderOpacity) { + Widget current, + TreemapTile tile, + int visibleIndex, + AnimationStatus animationStatus, + Animation scalingAndTranslationAnimation, + Animation opacityAnimation, + Tween currentLevelScale, + Tween nextLevelScale, + Tween tileOpacity, + Tween labelAndItemBuilderOpacity, +) { Size? scaleSize; if (animationStatus == AnimationStatus.forward && visibleIndex == tile._levelIndex) { @@ -107,30 +125,38 @@ Widget? _buildLabeAndItemBuilder( return Opacity( opacity: tileOpacity.evaluate(opacityAnimation), child: _buildAnimatedBuilder( - tile, - currentLevelScale.evaluate(scalingAndTranslationAnimation), - animationStatus, - current), + tile, + currentLevelScale.evaluate(scalingAndTranslationAnimation), + animationStatus, + current, + ), ); } else if (animationStatus == AnimationStatus.reverse) { if (visibleIndex == tile._levelIndex) { scaleSize = nextLevelScale.evaluate(scalingAndTranslationAnimation); - scaleSize = scaleSize.width > scaleSize.height - ? Size(scaleSize.width / scaleSize.width, - scaleSize.height / scaleSize.width) - : Size(scaleSize.width / scaleSize.height, - scaleSize.height / scaleSize.height); + scaleSize = + scaleSize.width > scaleSize.height + ? Size( + scaleSize.width / scaleSize.width, + scaleSize.height / scaleSize.width, + ) + : Size( + scaleSize.width / scaleSize.height, + scaleSize.height / scaleSize.height, + ); return Opacity( - opacity: - labelAndItemBuilderOpacity.evaluate(scalingAndTranslationAnimation), + opacity: labelAndItemBuilderOpacity.evaluate( + scalingAndTranslationAnimation, + ), child: _buildAnimatedBuilder(tile, scaleSize, animationStatus, current), ); } else { current = _buildAnimatedBuilder( - tile, - currentLevelScale.evaluate(scalingAndTranslationAnimation), - animationStatus, - current); + tile, + currentLevelScale.evaluate(scalingAndTranslationAnimation), + animationStatus, + current, + ); if (tile._isDrilled) { current = Opacity( opacity: tileOpacity.evaluate(opacityAnimation), @@ -149,7 +175,7 @@ enum PointerKind { touch, /// Indicates the pointer kind as hover. - hover + hover, } /// The [SfTreemap]'s layout type. @@ -161,7 +187,7 @@ enum LayoutType { slice, /// Dice layout. - dice + dice, } /// Contains information about the treemap tile. @@ -273,19 +299,30 @@ mixin _TreemapMixin { /// Calculates the scale and translation value by using the current /// drilldown tile size and offset. void _computeDrilldownTweenValue( - TreemapTile visibleTile, - AnimationController drilldownAnimationController, - GlobalKey breadcrumbKey, - bool isDrilledIn) { + TreemapTile visibleTile, + AnimationController drilldownAnimationController, + GlobalKey breadcrumbKey, + bool isDrilledIn, + ) { if (isDrilledIn) { - final Size currentScale = Size(size!.width / visibleTile._size!.width, - size!.height / visibleTile._size!.height); - final Offset currentTranslation = - _getTranslation(visibleTile._offset!, currentScale); - _scaleAndTranslationAnimation.curve = - const Interval(0, 0.5, curve: Curves.easeInOut); - _opacityAnimation.curve = - const Interval(0.5, 1.0, curve: Curves.easeInOut); + final Size currentScale = Size( + size!.width / visibleTile._size!.width, + size!.height / visibleTile._size!.height, + ); + final Offset currentTranslation = _getTranslation( + visibleTile._offset!, + currentScale, + ); + _scaleAndTranslationAnimation.curve = const Interval( + 0, + 0.5, + curve: Curves.easeInOut, + ); + _opacityAnimation.curve = const Interval( + 0.5, + 1.0, + curve: Curves.easeInOut, + ); _visibleTileTranslation ..begin = Offset.zero ..end = currentTranslation; @@ -297,8 +334,11 @@ mixin _TreemapMixin { ..end = 0.0; drilldownAnimationController.forward(from: 0.01); } else { - _scaleAndTranslationAnimation.curve = - const Interval(0.5, 1.0, curve: Curves.easeInOut); + _scaleAndTranslationAnimation.curve = const Interval( + 0.5, + 1.0, + curve: Curves.easeInOut, + ); _opacityAnimation.curve = const Interval(0, 0.5, curve: Curves.easeInOut); _computeDrilledOutTweenValue(visibleTile, breadcrumbKey); drilldownAnimationController.reverse(from: 0.99); @@ -308,7 +348,9 @@ mixin _TreemapMixin { } void _computeDrilledOutTweenValue( - TreemapTile tappedTile, GlobalKey breadcrumbKey) { + TreemapTile tappedTile, + GlobalKey breadcrumbKey, + ) { late TreemapTile currentDrilledTile; if (breadcrumbKey.currentState != null) { final _BreadcrumbsState breadcrumbRenderBox = @@ -325,20 +367,27 @@ mixin _TreemapMixin { // It represents which size we need to increase the from scale // value to the current level tiles. final Size currentLevelScale = Size( - size!.width / currentDrilledTile._size!.width, - size!.height / currentDrilledTile._size!.height); + size!.width / currentDrilledTile._size!.width, + size!.height / currentDrilledTile._size!.height, + ); // It represents the offset we need to translate in the animation begin // to the current level tiles. - final Offset currentLevelTranslation = - _getTranslation(currentDrilledTile._offset!, currentLevelScale); + final Offset currentLevelTranslation = _getTranslation( + currentDrilledTile._offset!, + currentLevelScale, + ); // It represents which size we need to increase the from scale // value to the current visible tiles. - final Size nextLevelScale = - Size(1.0 / currentLevelScale.width, 1.0 / currentLevelScale.height); + final Size nextLevelScale = Size( + 1.0 / currentLevelScale.width, + 1.0 / currentLevelScale.height, + ); // It represents the offset we need to translate in the animation begin // to the current visible tiles. - final Offset nextLevelTranslation = - _getTranslation(currentLevelTranslation, nextLevelScale); + final Offset nextLevelTranslation = _getTranslation( + currentLevelTranslation, + nextLevelScale, + ); _nextTileTranslation ..begin = nextLevelTranslation ..end = Offset.zero; @@ -361,14 +410,18 @@ mixin _TreemapMixin { Offset _getTranslation(Offset tileOffset, Size scaleSize) { return -Offset( - tileOffset.dx * scaleSize.width, tileOffset.dy * scaleSize.height); + tileOffset.dx * scaleSize.width, + tileOffset.dy * scaleSize.height, + ); } /// Scales to the current visible level and load its descendants /// with animation. Widget _buildAnimatedTreemap( - AnimationController controller, GlobalKey breadcrumbKey, - {TreemapLayoutDirection direction = TreemapLayoutDirection.topLeft}) { + AnimationController controller, + GlobalKey breadcrumbKey, { + TreemapLayoutDirection direction = TreemapLayoutDirection.topLeft, + }) { late List breadcrumbTiles; if (breadcrumbKey.currentState != null) { final _BreadcrumbsState breadcrumbRenderBox = @@ -376,30 +429,39 @@ mixin _TreemapMixin { breadcrumbTiles = breadcrumbRenderBox._tiles; } - final TreemapTile tile = controller.status == AnimationStatus.reverse - ? _visibleTile - // Gets the previous tapped tiles. - : breadcrumbTiles[breadcrumbTiles.length - 2]; + final TreemapTile tile = + controller.status == AnimationStatus.reverse + ? _visibleTile + // Gets the previous tapped tiles. + : breadcrumbTiles[breadcrumbTiles.length - 2]; return AnimatedBuilder( animation: controller, builder: (BuildContext context, Widget? child) { - final Offset translation = _getLayoutDirectionTranslation(direction, - _visibleTileTranslation.evaluate(_scaleAndTranslationAnimation)); - final Size scaleSize = - _visibleTileScaleSize.evaluate(_scaleAndTranslationAnimation); + final Offset translation = _getLayoutDirectionTranslation( + direction, + _visibleTileTranslation.evaluate(_scaleAndTranslationAnimation), + ); + final Size scaleSize = _visibleTileScaleSize.evaluate( + _scaleAndTranslationAnimation, + ); if (controller.status != AnimationStatus.reverse) { return Stack( children: [ Transform( alignment: _getEffectiveAlignment(direction), - transform: Matrix4.identity() - ..scale(scaleSize.width, scaleSize.height) - ..leftTranslate(translation.dx, translation.dy), + transform: + Matrix4.identity() + ..scale(scaleSize.width, scaleSize.height) + ..leftTranslate(translation.dx, translation.dy), child: Stack( clipBehavior: Clip.none, - children: _buildTiles(tile._descendants!, tile.weight, size!, - canLayoutTiles: false), + children: _buildTiles( + tile._descendants!, + tile.weight, + size!, + canLayoutTiles: false, + ), ), ), if (controller.value > 0.5) @@ -408,65 +470,89 @@ mixin _TreemapMixin { child: Stack( clipBehavior: Clip.none, children: _buildTiles( - _visibleTile._descendants!, _visibleTile.weight, size!), + _visibleTile._descendants!, + _visibleTile.weight, + size!, + ), ), - ) + ), ], ); } else { return _buildDrilledOutAnimatedTiles( - tile, breadcrumbTiles.last, scaleSize, translation, direction); + tile, + breadcrumbTiles.last, + scaleSize, + translation, + direction, + ); } }, ); } Widget _buildDrilledOutAnimatedTiles( - TreemapTile tile, - TreemapTile lastBreadcrumbTile, - Size scaleSize, - Offset translation, - TreemapLayoutDirection direction) { + TreemapTile tile, + TreemapTile lastBreadcrumbTile, + Size scaleSize, + Offset translation, + TreemapLayoutDirection direction, + ) { final Offset descendantsTranslation = _getLayoutDirectionTranslation( - direction, - _nextTileTranslation.evaluate(_scaleAndTranslationAnimation)); - final Size descendantsScaleSize = - _nextTileScaleSize.evaluate(_scaleAndTranslationAnimation); + direction, + _nextTileTranslation.evaluate(_scaleAndTranslationAnimation), + ); + final Size descendantsScaleSize = _nextTileScaleSize.evaluate( + _scaleAndTranslationAnimation, + ); return Stack( children: [ Transform( alignment: _getEffectiveAlignment(direction), - transform: Matrix4.identity() - ..scale(scaleSize.width, scaleSize.height) - ..leftTranslate(translation.dx, translation.dy), + transform: + Matrix4.identity() + ..scale(scaleSize.width, scaleSize.height) + ..leftTranslate(translation.dx, translation.dy), child: Stack( clipBehavior: Clip.none, - children: _buildTiles(tile._descendants!, tile.weight, size!, - canLayoutTiles: false), + children: _buildTiles( + tile._descendants!, + tile.weight, + size!, + canLayoutTiles: false, + ), ), ), Transform( alignment: _getEffectiveAlignment(direction), - transform: Matrix4.identity() - ..scale(descendantsScaleSize.width, descendantsScaleSize.height) - ..leftTranslate( - descendantsTranslation.dx, descendantsTranslation.dy), + transform: + Matrix4.identity() + ..scale(descendantsScaleSize.width, descendantsScaleSize.height) + ..leftTranslate( + descendantsTranslation.dx, + descendantsTranslation.dy, + ), child: Opacity( opacity: 1.0 - _tileOpacity.evaluate(_opacityAnimation), child: Stack( clipBehavior: Clip.none, - children: _buildTiles(lastBreadcrumbTile._descendants!, - lastBreadcrumbTile.weight, size!, - canLayoutTiles: false), + children: _buildTiles( + lastBreadcrumbTile._descendants!, + lastBreadcrumbTile.weight, + size!, + canLayoutTiles: false, + ), ), ), - ) + ), ], ); } Offset _getLayoutDirectionTranslation( - TreemapLayoutDirection layoutDirection, Offset translation) { + TreemapLayoutDirection layoutDirection, + Offset translation, + ) { switch (layoutDirection) { case TreemapLayoutDirection.topLeft: return translation; @@ -480,7 +566,8 @@ mixin _TreemapMixin { } AlignmentGeometry _getEffectiveAlignment( - TreemapLayoutDirection layoutDirection) { + TreemapLayoutDirection layoutDirection, + ) { switch (layoutDirection) { case TreemapLayoutDirection.topLeft: return Alignment.topLeft; @@ -494,9 +581,13 @@ mixin _TreemapMixin { } /// Loads label builder and descendants for the tiles. - Widget? _getDescendants(BuildContext context, TreemapTile tile, - TreemapController controller, bool enableDrilldown, - {AnimationController? animationController}) { + Widget? _getDescendants( + BuildContext context, + TreemapTile tile, + TreemapController controller, + bool enableDrilldown, { + AnimationController? animationController, + }) { Widget? child; // Ignored current tile descendants while enable drilldown. if (enableDrilldown) { @@ -537,20 +628,25 @@ mixin _TreemapMixin { // 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( + 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: _buildTiles( + tile._size!.height - constraints.maxHeight, + ); + return Stack( + children: _buildTiles( tile._descendants!, tile.weight, - Size(tile._size!.width - tile._labelBuilderSize.width, - tile._size!.height - tile._labelBuilderSize.height)), - ); - }), + Size( + tile._size!.width - tile._labelBuilderSize.width, + tile._size!.height - tile._labelBuilderSize.height, + ), + ), + ); + }, + ), ), ], ); @@ -571,21 +667,26 @@ mixin _TreemapMixin { Widget _buildDescendants(TreemapTile tile) { return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Stack( + builder: (BuildContext context, BoxConstraints constraints) { + return Stack( children: _buildTiles( - tile._descendants!, - tile.weight, - constraints.biggest, - )); - }); + tile._descendants!, + tile.weight, + constraints.biggest, + ), + ); + }, + ); } /// Creates the get tiles methods and overrides to the slice, dice /// and squarified class. List _buildTiles( - Map source, double aggregatedWeight, Size size, - {bool canLayoutTiles = true}) { + Map source, + double aggregatedWeight, + Size size, { + bool canLayoutTiles = true, + }) { return []; } } @@ -690,65 +791,71 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { late Future _computeDataSource; _SquarifiedTreemap get _squarified => _SquarifiedTreemap( - dataCount: widget.dataCount, - layoutDirection: widget.layoutDirection, - tooltipKey: _tooltipKey, - breadcrumbKey: _breadcrumbKey, - onSelectionChanged: widget.onSelectionChanged, - selectionSettings: widget.selectionSettings, - enableDrillDown: widget.enableDrilldown, - controller: _controller, - drilldownAnimationController: _drilldownAnimationController, - initialTile: TreemapTile._() - .._group = 'Home' - .._levelIndex = -1 - .._offset = Offset.zero - .._descendants = _dataSource - .._weight = _totalWeight - .._isDrilled = true); + dataCount: widget.dataCount, + layoutDirection: widget.layoutDirection, + tooltipKey: _tooltipKey, + breadcrumbKey: _breadcrumbKey, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + enableDrillDown: widget.enableDrilldown, + controller: _controller, + drilldownAnimationController: _drilldownAnimationController, + initialTile: + TreemapTile._() + .._group = 'Home' + .._levelIndex = -1 + .._offset = Offset.zero + .._descendants = _dataSource + .._weight = _totalWeight + .._isDrilled = true, + ); _SliceAndDiceTreemap get _sliceAndDice => _SliceAndDiceTreemap( - dataCount: widget.dataCount, - sortAscending: widget.sortAscending, - type: widget.layoutType, - tooltipKey: _tooltipKey, - breadcrumbKey: _breadcrumbKey, - onSelectionChanged: widget.onSelectionChanged, - selectionSettings: widget.selectionSettings, - enableDrillDown: widget.enableDrilldown, - controller: _controller, - drilldownAnimationController: _drilldownAnimationController, - initialTile: TreemapTile._() + dataCount: widget.dataCount, + sortAscending: widget.sortAscending, + type: widget.layoutType, + tooltipKey: _tooltipKey, + breadcrumbKey: _breadcrumbKey, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + enableDrillDown: widget.enableDrilldown, + controller: _controller, + drilldownAnimationController: _drilldownAnimationController, + initialTile: + TreemapTile._() .._group = 'Home' .._levelIndex = -1 .._offset = Offset.zero .._descendants = _dataSource .._weight = _totalWeight .._isDrilled = true, - ); + ); TreemapTooltip get _tooltip => TreemapTooltip(key: _tooltipKey, settings: widget.tooltipSettings); Widget _buildTreemap(BuildContext context, bool hasData) { - Widget current = widget.layoutType == LayoutType.squarified - ? _squarified - : _sliceAndDice; + Widget current = + widget.layoutType == LayoutType.squarified + ? _squarified + : _sliceAndDice; if (widget.enableDrilldown) { + // ignore: no_leading_underscores_for_local_identifiers final _Breadcrumbs _breadcrumbs = _Breadcrumbs( key: _breadcrumbKey, settings: widget.breadcrumbs!, controller: _controller, animationController: _drilldownAnimationController!, levels: widget.levels, - initialTile: TreemapTile._() - .._group = 'Home' - .._levelIndex = -1 - .._offset = Offset.zero - .._descendants = _dataSource - .._weight = _totalWeight - .._isDrilled = true, + initialTile: + TreemapTile._() + .._group = 'Home' + .._levelIndex = -1 + .._offset = Offset.zero + .._descendants = _dataSource + .._weight = _totalWeight + .._isDrilled = true, ); switch (widget.breadcrumbs!.position) { case TreemapBreadcrumbPosition.top: @@ -756,7 +863,7 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { crossAxisAlignment: CrossAxisAlignment.start, children: [ _breadcrumbs, - Expanded(child: ClipRect(child: current)) + Expanded(child: ClipRect(child: current)), ], ); break; @@ -823,8 +930,9 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { // To place the pointer at the center of the segment in case of // solid bar legend type. normalized = 0.5; - } else + } else { normalized = value / (length - 1); + } } } return Offset(normalized, normalized); @@ -848,8 +956,9 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { } void _obtainDataSource() { - _computeDataSource = - _obtainDataSourceAndBindColorMappers().then((bool value) => value); + _computeDataSource = _obtainDataSourceAndBindColorMappers().then( + (bool value) => value, + ); } Future _obtainDataSourceAndBindColorMappers() async { @@ -870,8 +979,12 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { return true; } - void _groupTiles(double weight, int dataIndex, - {int currentLevelIndex = 0, TreemapTile? ancestor}) { + 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; @@ -885,8 +998,12 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { // 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); + _groupTiles( + weight, + dataIndex, + currentLevelIndex: nextLevelIndex, + ancestor: ancestor, + ); } return; } @@ -917,48 +1034,62 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { // checked for the next stages. ((ancestor?._descendants ??= {}) ?? _dataSource) .update( - groupKey, - (TreemapTile tile) { - tile._weight += weight; - tile.indices.add(dataIndex); - tile._levelIndex = currentLevelIndex; - 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 - .._levelIndex = currentLevelIndex - .._hasDescendants = false; - if (color != null) { - tile._color = color; - } + groupKey, + (TreemapTile tile) { + tile._weight += weight; + tile.indices.add(dataIndex); + tile._levelIndex = currentLevelIndex; + 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 + .._levelIndex = currentLevelIndex + .._hasDescendants = false; + if (color != null) { + tile._color = color; + } - if (nextLevelIndex < _levelsLength) { - _groupTiles(weight, dataIndex, - currentLevelIndex: nextLevelIndex, ancestor: tile); - } - ancestor?._hasDescendants = true; - return tile; - }, - ); + if (nextLevelIndex < _levelsLength) { + _groupTiles( + weight, + dataIndex, + currentLevelIndex: nextLevelIndex, + ancestor: tile, + ); + } + ancestor?._hasDescendants = true; + return tile; + }, + ); } void _bindColorMappersIntoDataSource(Map? source) { if (source != null && source.isNotEmpty) { - final double baseWeight = - source.values.map((TreemapTile tile) => tile._weight).reduce(max); + final double baseWeight = source.values + .map((TreemapTile tile) => tile._weight) + .reduce(max); for (final TreemapTile tile in source.values) { - tile._color = _getColor(tile) ?? + tile._color = + _getColor(tile) ?? _getSaturatedColor( - _baseColor, 1 - (tile._weight / baseWeight), Colors.white); + _baseColor, + 1 - (tile._weight / baseWeight), + Colors.white, + ); _bindColorMappersIntoDataSource(tile._descendants); } } @@ -998,18 +1129,22 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { Color? _getRangeColor(num value, List colorMappers) { for (final TreemapColorMapper mapper in colorMappers) { - assert(mapper.from != null && - mapper.to != null && - mapper.from! <= mapper.to!); + assert( + mapper.from != null && mapper.to != null && mapper.from! <= mapper.to!, + ); if ((mapper.from != null && mapper.to != null) && (mapper.from! <= value && mapper.to! >= value)) { if (mapper.minSaturation != null && mapper.maxSaturation != null) { return _getSaturatedColor( - mapper.color, - 1 - - lerpDouble(mapper.minSaturation, mapper.maxSaturation, - (value - mapper.from!) / (mapper.to! - mapper.from!))!, - Colors.white); + mapper.color, + 1 - + lerpDouble( + mapper.minSaturation, + mapper.maxSaturation, + (value - mapper.from!) / (mapper.to! - mapper.from!), + )!, + Colors.white, + ); } return mapper.color; } @@ -1037,7 +1172,9 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { _controller = TreemapController(); if (widget.enableDrilldown) { _drilldownAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 1000)); + vsync: this, + duration: const Duration(milliseconds: 1000), + ); _controller.addDrillDownListener(_handleDrillDown); } _pointerController = PointerController(); @@ -1049,10 +1186,10 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { 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); + (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; @@ -1079,7 +1216,8 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - _isDesktop = kIsWeb || + _isDesktop = + kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; @@ -1087,32 +1225,38 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { future: _computeDataSource, builder: (BuildContext context, AsyncSnapshot snapshot) { return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final Size size = Size( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = Size( constraints.hasBoundedWidth ? constraints.maxWidth : 300, - constraints.hasBoundedHeight ? constraints.maxHeight : 300); - - // While resizing or changing orientation in an running drill down - // animation, force it to complete. - if (widget.enableDrilldown && - _drilldownAnimationController!.isAnimating) { - _drilldownAnimationController!.value = - _drilldownAnimationController!.status == AnimationStatus.forward - ? 1.0 - : 0.0; - } + 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, - ); - }); + // While resizing or changing orientation in an running drill down + // animation, force it to complete. + if (widget.enableDrilldown && + _drilldownAnimationController!.isAnimating) { + _drilldownAnimationController!.value = + _drilldownAnimationController!.status == + AnimationStatus.forward + ? 1.0 + : 0.0; + } + + return SizedBox( + width: size.width, + height: size.height, + child: + snapshot.hasData + ? Stack( + children: [ + _buildTreemap(context, snapshot.hasData), + if (_isTooltipEnabled) _tooltip, + ], + ) + : null, + ); + }, + ); }, ); } @@ -1159,11 +1303,14 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> // its location and size for its children. @override List _buildTiles( - Map source, double aggregatedWeight, Size size, - {Offset offset = Offset.zero, - int start = 0, - int? end, - bool canLayoutTiles = true}) { + Map source, + double aggregatedWeight, + Size size, { + Offset offset = Offset.zero, + int start = 0, + int? end, + bool canLayoutTiles = true, + }) { final Size widgetSize = size; double groupArea = 0; double referenceArea; @@ -1171,22 +1318,32 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> double? groupInitialTileArea; final List tiles = source.values.toList(); // Sorting the tiles in descending order. - tiles.sort((TreemapTile src, TreemapTile target) => - target.weight.compareTo(src.weight)); + tiles.sort( + (TreemapTile src, TreemapTile target) => + target.weight.compareTo(src.weight), + ); end ??= tiles.length; final List children = []; for (int i = start; i < end; i++) { final TreemapTile tile = tiles[i]; if (!canLayoutTiles) { - children.addAll(_getTileWidgets( - tiles, size, offset, start, tiles.length, - canLayoutTiles: false)); + children.addAll( + _getTileWidgets( + tiles, + size, + offset, + start, + tiles.length, + canLayoutTiles: false, + ), + ); return children; } else { // 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 * + tile._area = + widgetSize.height * widgetSize.width * (tile.weight / aggregatedWeight); groupInitialTileArea ??= tile._area; @@ -1194,8 +1351,9 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> final double area = (groupArea + tile._area!) / size.shortestSide; referenceArea = groupInitialTileArea! / area; final double currentAspectRatio = max( - _getAspectRatio(referenceArea, area), - _getAspectRatio(tile._area! / area, area)); + _getAspectRatio(referenceArea, area), + _getAspectRatio(tile._area! / area, area), + ); if (prevAspectRatio == null || currentAspectRatio < prevAspectRatio) { prevAspectRatio = currentAspectRatio; groupArea += tile._area!; @@ -1203,29 +1361,47 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> // Aligning the tiles vertically. if (size.width > size.height) { children.addAll( - _getTileWidgets(tiles, Size(groupArea / size.height, size.height), - offset, start, i, - axis: Axis.vertical), + _getTileWidgets( + tiles, + Size(groupArea / size.height, size.height), + offset, + start, + i, + axis: Axis.vertical, + ), ); offset += Offset(groupArea / size.height, 0); - size = - Size(max(0, size.width) - groupArea / size.height, size.height); + 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), offset, start, i, - axis: Axis.horizontal)); + children.addAll( + _getTileWidgets( + tiles, + Size(size.width, groupArea / size.width), + offset, + start, + i, + axis: Axis.horizontal, + ), + ); offset += Offset(0, groupArea / size.width); - size = - Size(size.width, max(0, size.height) - 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); + prevAspectRatio = _getAspectRatio( + referenceArea, + tile._area! / size.shortestSide, + ); } } } @@ -1234,15 +1410,25 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> // the given source. if (size.width > size.height) { children.addAll( - _getTileWidgets(tiles, Size(groupArea / size.height, size.height), - offset, start, end, - axis: Axis.vertical), + _getTileWidgets( + tiles, + Size(groupArea / size.height, size.height), + offset, + start, + end, + axis: Axis.vertical, + ), ); } else { children.addAll( - _getTileWidgets(tiles, Size(groupArea / size.height, size.height), - offset, start, end, - axis: Axis.horizontal), + _getTileWidgets( + tiles, + Size(groupArea / size.height, size.height), + offset, + start, + end, + axis: Axis.horizontal, + ), ); } @@ -1250,8 +1436,14 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> } List _getTileWidgets( - List source, Size size, Offset offset, int start, int end, - {Axis? axis, bool canLayoutTiles = true}) { + List source, + Size size, + Offset offset, + int start, + int end, { + Axis? axis, + bool canLayoutTiles = true, + }) { final List<_Tile> tiles = <_Tile>[]; for (int i = start; i < end; i++) { final TreemapTile tileDetails = source[i]; @@ -1269,20 +1461,26 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> } } - tiles.add(_Tile( - size: tileDetails._size!, - details: tileDetails, - tooltipKey: widget.tooltipKey, - layoutDirection: widget.layoutDirection, - sortAscending: false, - controller: widget.controller, - child: _getDescendants( - context, tileDetails, widget.controller, widget.enableDrillDown, - animationController: widget.drilldownAnimationController), - onSelectionChanged: widget.onSelectionChanged, - selectionSettings: widget.selectionSettings, - drilldownAnimationController: widget.drilldownAnimationController, - )); + tiles.add( + _Tile( + size: tileDetails._size!, + details: tileDetails, + tooltipKey: widget.tooltipKey, + layoutDirection: widget.layoutDirection, + sortAscending: false, + controller: widget.controller, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + drilldownAnimationController: widget.drilldownAnimationController, + child: _getDescendants( + context, + tileDetails, + widget.controller, + widget.enableDrillDown, + animationController: widget.drilldownAnimationController, + ), + ), + ); } return tiles; @@ -1294,8 +1492,12 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> void _handleDrillDown(TreemapTile tile, bool isDrilledIn) { setState(() { - _computeDrilldownTweenValue(tile, widget.drilldownAnimationController!, - widget.breadcrumbKey, isDrilledIn); + _computeDrilldownTweenValue( + tile, + widget.drilldownAnimationController!, + widget.breadcrumbKey, + isDrilledIn, + ); }); } @@ -1315,9 +1517,13 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> _visibleTile = widget.initialTile; if (widget.enableDrillDown) { _scaleAndTranslationAnimation = CurvedAnimation( - parent: widget.drilldownAnimationController!, curve: Curves.linear); + parent: widget.drilldownAnimationController!, + curve: Curves.linear, + ); _opacityAnimation = CurvedAnimation( - parent: widget.drilldownAnimationController!, curve: Curves.linear); + parent: widget.drilldownAnimationController!, + curve: Curves.linear, + ); _tileOpacity = Tween(); _labelAndItemBuilderOpacity = Tween(); _visibleTileTranslation = Tween(); @@ -1325,8 +1531,9 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> _nextTileTranslation = Tween(); _nextTileScaleSize = Tween(); widget.controller.addDrillDownListener(_handleDrillDown); - widget.drilldownAnimationController! - .addStatusListener(_handleAnimationStatusChange); + widget.drilldownAnimationController!.addStatusListener( + _handleAnimationStatusChange, + ); } super.initState(); @@ -1336,8 +1543,9 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> void didUpdateWidget(_SquarifiedTreemap oldWidget) { if (oldWidget.layoutDirection != widget.layoutDirection) { if (widget.tooltipKey.currentContext != null) { - final RenderTooltip tooltipRenderBox = widget.tooltipKey.currentContext! - .findRenderObject()! as RenderTooltip; + final RenderTooltip tooltipRenderBox = + widget.tooltipKey.currentContext!.findRenderObject()! + as RenderTooltip; tooltipRenderBox.hide(immediately: true); } } @@ -1348,8 +1556,9 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> void dispose() { if (widget.enableDrillDown) { widget.controller.removeDrillDownListener(_handleDrillDown); - widget.drilldownAnimationController! - .removeStatusListener(_handleAnimationStatusChange); + widget.drilldownAnimationController!.removeStatusListener( + _handleAnimationStatusChange, + ); } super.dispose(); @@ -1421,56 +1630,69 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> with SingleTickerProviderStateMixin, _TreemapMixin { @override List _buildTiles( - Map source, double aggregatedWeight, Size size, - {bool canLayoutTiles = true}) { + Map source, + double aggregatedWeight, + Size size, { + bool canLayoutTiles = true, + }) { final List children = []; final List tiles = source.values.toList(); Offset offset = Offset.zero; // Sorting the tiles in ascending/descending order based on the // [sortAscending]. if (widget.sortAscending) { - tiles.sort((TreemapTile src, TreemapTile target) => - src.weight.compareTo(target.weight)); + tiles.sort( + (TreemapTile src, TreemapTile target) => + src.weight.compareTo(target.weight), + ); } else { - tiles.sort((TreemapTile target, TreemapTile src) => - src.weight.compareTo(target.weight)); + tiles.sort( + (TreemapTile target, TreemapTile src) => + src.weight.compareTo(target.weight), + ); } for (final TreemapTile tile in tiles) { if (canLayoutTiles) { if (widget.type == LayoutType.slice) { tile - .._size = - Size(size.width, size.height * (tile.weight / aggregatedWeight)) + .._size = Size( + size.width, + size.height * (tile.weight / aggregatedWeight), + ) .._offset = offset; offset += Offset(0, tile._size!.height); } else { tile - .._size = - Size(size.width * (tile.weight / aggregatedWeight), size.height) + .._size = Size( + size.width * (tile.weight / aggregatedWeight), + size.height, + ) .._offset = offset; offset += Offset(tile._size!.width, 0); } } - children.add(_Tile( - type: widget.type, - size: tile._size!, - details: tile, - tooltipKey: widget.tooltipKey, - layoutDirection: TreemapLayoutDirection.topLeft, - sortAscending: widget.sortAscending, - controller: widget.controller, - child: _getDescendants( - context, - tile, - widget.controller, - widget.enableDrillDown, - animationController: widget.drilldownAnimationController, + children.add( + _Tile( + type: widget.type, + size: tile._size!, + details: tile, + tooltipKey: widget.tooltipKey, + layoutDirection: TreemapLayoutDirection.topLeft, + sortAscending: widget.sortAscending, + controller: widget.controller, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + drilldownAnimationController: widget.drilldownAnimationController, + child: _getDescendants( + context, + tile, + widget.controller, + widget.enableDrillDown, + animationController: widget.drilldownAnimationController, + ), ), - onSelectionChanged: widget.onSelectionChanged, - selectionSettings: widget.selectionSettings, - drilldownAnimationController: widget.drilldownAnimationController, - )); + ); } return children; @@ -1478,8 +1700,12 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> void _handleDrillDown(TreemapTile tile, bool isDrilledIn) { setState(() { - _computeDrilldownTweenValue(tile, widget.drilldownAnimationController!, - widget.breadcrumbKey, isDrilledIn); + _computeDrilldownTweenValue( + tile, + widget.drilldownAnimationController!, + widget.breadcrumbKey, + isDrilledIn, + ); }); } @@ -1499,9 +1725,13 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> _visibleTile = widget.initialTile; if (widget.enableDrillDown) { _scaleAndTranslationAnimation = CurvedAnimation( - parent: widget.drilldownAnimationController!, curve: Curves.linear); + parent: widget.drilldownAnimationController!, + curve: Curves.linear, + ); _opacityAnimation = CurvedAnimation( - parent: widget.drilldownAnimationController!, curve: Curves.linear); + parent: widget.drilldownAnimationController!, + curve: Curves.linear, + ); _tileOpacity = Tween(); _labelAndItemBuilderOpacity = Tween(); _visibleTileTranslation = Tween(); @@ -1509,8 +1739,9 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> _nextTileTranslation = Tween(); _nextTileScaleSize = Tween(); widget.controller.addDrillDownListener(_handleDrillDown); - widget.drilldownAnimationController! - .addStatusListener(_handleAnimationStatusChange); + widget.drilldownAnimationController!.addStatusListener( + _handleAnimationStatusChange, + ); } super.initState(); @@ -1520,8 +1751,9 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> void dispose() { if (widget.enableDrillDown) { widget.controller.removeDrillDownListener(_handleDrillDown); - widget.drilldownAnimationController! - .removeStatusListener(_handleAnimationStatusChange); + widget.drilldownAnimationController!.removeStatusListener( + _handleAnimationStatusChange, + ); } super.dispose(); @@ -1539,7 +1771,9 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> if (widget.drilldownAnimationController != null && widget.drilldownAnimationController!.isAnimating) { return _buildAnimatedTreemap( - widget.drilldownAnimationController!, widget.breadcrumbKey); + widget.drilldownAnimationController!, + widget.breadcrumbKey, + ); } return Stack( @@ -1585,9 +1819,10 @@ class _Tile extends StatelessWidget { @override Widget build(BuildContext context) { - final dynamic ancestor = type == LayoutType.squarified - ? context.findAncestorStateOfType<_SquarifiedTreemapState>()! - : context.findAncestorStateOfType<_SliceAndDiceTreemapState>()!; + final dynamic ancestor = + type == LayoutType.squarified + ? context.findAncestorStateOfType<_SquarifiedTreemapState>()! + : context.findAncestorStateOfType<_SliceAndDiceTreemapState>()!; Widget current = _TileDecor( size: size, @@ -1595,16 +1830,17 @@ class _Tile extends StatelessWidget { tooltipKey: tooltipKey, sortAscending: sortAscending, controller: controller, - child: child, onSelectionChanged: onSelectionChanged, selectionSettings: selectionSettings, drilldownAnimationController: drilldownAnimationController, ancestor: ancestor, + child: child, ); if (details._padding != null) { - EdgeInsets padding = - details._padding!.resolve(Directionality.of(context)); + EdgeInsets padding = details._padding!.resolve( + Directionality.of(context), + ); // if we forward 0 -> 1st level, we need to ignore scaling to the 0th // level tiles only. If we backward 1 -> 0th level we need to ignore // scaling for both levels. @@ -1620,18 +1856,25 @@ class _Tile extends StatelessWidget { // tiles. if (drilldownAnimationController!.status == AnimationStatus.reverse && controller.visibleLevelIndex == details._levelIndex) { - scaleSize = ancestor._nextTileScaleSize! - .evaluate(ancestor._scaleAndTranslationAnimation) as Size; + scaleSize = + ancestor._nextTileScaleSize!.evaluate( + ancestor._scaleAndTranslationAnimation, + ) + as Size; } else { - scaleSize = ancestor._visibleTileScaleSize! - .evaluate(ancestor._scaleAndTranslationAnimation) as Size; + scaleSize = + ancestor._visibleTileScaleSize!.evaluate( + ancestor._scaleAndTranslationAnimation, + ) + as Size; } padding = EdgeInsets.fromLTRB( - padding.left / scaleSize.width, - padding.top / scaleSize.height, - padding.right / scaleSize.width, - padding.bottom / scaleSize.height); + padding.left / scaleSize.width, + padding.top / scaleSize.height, + padding.right / scaleSize.width, + padding.bottom / scaleSize.height, + ); } current = Padding(padding: padding, child: current); @@ -1736,9 +1979,9 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } if (border != null && widget.details.level.border != null) { return widget.details.level.border!.copyWith( - side: border.side, - borderRadius: - border.borderRadius.resolve(Directionality.of(context))); + side: border.side, + borderRadius: border.borderRadius.resolve(Directionality.of(context)), + ); } return border ?? widget.details.level.border; } @@ -1746,7 +1989,8 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { Widget _buildTileDecor(Widget child, BuildContext context) { Widget current = child; if (_canAddInteractableWidget || _ancestor.enableDrilldown) { - final bool allow = !_ancestor.enableDrilldown || + final bool allow = + !_ancestor.enableDrilldown || (widget.controller.visibleLevelIndex == widget.details._levelIndex && !widget.drilldownAnimationController!.isAnimating); @@ -1764,8 +2008,9 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { widget.controller.hoveredTile = widget.details; } - treemapState - ._updateLegendPointer(widget.details._colorValue?.toDouble()); + treemapState._updateLegendPointer( + widget.details._colorValue?.toDouble(), + ); }, onHover: (PointerHoverEvent event) { // Updating the [widget.controller.hoveredTile] when hover @@ -1831,7 +2076,11 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { widget.tooltipKey.currentContext!.findRenderObject()! as RenderTooltip; tooltipRenderBox.show( - globalPosition, widget.details, referenceTileSize, kind); + globalPosition, + widget.details, + referenceTileSize, + kind, + ); } } @@ -1850,7 +2099,7 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { return; } final bool mouseIsConnected = - RendererBinding.instance!.mouseTracker.mouseIsConnected; + RendererBinding.instance.mouseTracker.mouseIsConnected; if (mouseIsConnected != _mouseIsConnected) { setState(() { _mouseIsConnected = mouseIsConnected; @@ -1918,15 +2167,18 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } else if (_ancestor.tileHoverColor == Colors.transparent) { _colorTween.begin = widget.details.color; } else if (_ancestor.tileHoverColor == null) { - _colorTween.begin = - _getSaturatedColor(widget.details.color, hoverSaturateFactor); + _colorTween.begin = _getSaturatedColor( + widget.details.color, + hoverSaturateFactor, + ); } } else { _colorTween.begin = widget.details.color; } - _colorTween.end = selectionColor == null - ? _getSaturatedColor(widget.details.color, selectionSaturateFactor) - : selectionColor == Colors.transparent + _colorTween.end = + selectionColor == null + ? _getSaturatedColor(widget.details.color, selectionSaturateFactor) + : selectionColor == Colors.transparent ? widget.details.color : selectionColor; } @@ -1937,8 +2189,9 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } if (widget.details.indices.length < source.indices.length) { - return widget.details.indices - .any((int index) => source.indices.contains(index)); + 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); @@ -1975,8 +2228,10 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } else if (_ancestor.tileHoverColor == Colors.transparent) { _colorTween.end = widget.details.color; } else { - _colorTween.end = - _getSaturatedColor(widget.details.color, _getColorFactor()); + _colorTween.end = _getSaturatedColor( + widget.details.color, + _getColorFactor(), + ); } } @@ -1988,10 +2243,11 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } else if (widget.drilldownAnimationController!.status == AnimationStatus.reverse) { if (widget.details._isDrilled) { - _colorTween.begin = widget.ancestor._visibleTile._levelIndex + 1 == - widget.details._levelIndex - ? widget.details.color - : Colors.transparent; + _colorTween.begin = + widget.ancestor._visibleTile._levelIndex + 1 == + widget.details._levelIndex + ? widget.details.color + : Colors.transparent; _colorTween.end = Colors.transparent; } } @@ -2006,11 +2262,16 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { return _colorTween.evaluate(_animation)!; } - Widget _buildTileBorder(Widget child, EdgeInsets dimensions, - RoundedRectangleBorder? border, Size? scaleSize) { + Widget _buildTileBorder( + Widget child, + EdgeInsets dimensions, + RoundedRectangleBorder? border, + Size? scaleSize, + ) { if (border != null) { - BorderRadius borderRadius = - border.borderRadius.resolve(Directionality.of(context)); + BorderRadius borderRadius = border.borderRadius.resolve( + Directionality.of(context), + ); if (widget.drilldownAnimationController != null && ((widget.drilldownAnimationController!.status == AnimationStatus.forward && @@ -2025,21 +2286,26 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { if (widget.drilldownAnimationController!.status == AnimationStatus.reverse && widget.controller.visibleLevelIndex == widget.details._levelIndex) { - scaleSize = widget.ancestor._nextTileScaleSize! - .evaluate(widget.ancestor._scaleAndTranslationAnimation) as Size; + scaleSize = + widget.ancestor._nextTileScaleSize!.evaluate( + widget.ancestor._scaleAndTranslationAnimation, + ) + as Size; } borderRadius = _getBorderRadius(borderRadius, scaleSize!); border = AnimatedBorder( - borderRadius: borderRadius, - side: border.side, - scaleSize: scaleSize); + borderRadius: borderRadius, + side: border.side, + scaleSize: scaleSize, + ); dimensions = border.dimensions.resolve(Directionality.of(context)); dimensions = EdgeInsets.fromLTRB( - dimensions.left / scaleSize.width, - dimensions.top / scaleSize.height, - dimensions.right / scaleSize.width, - dimensions.bottom / scaleSize.height); + dimensions.left / scaleSize.width, + dimensions.top / scaleSize.height, + dimensions.right / scaleSize.width, + dimensions.bottom / scaleSize.height, + ); } } @@ -2047,28 +2313,38 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { size: widget.size, foregroundPainter: _BorderPainter(border), child: ClipPath.shape( - shape: border ?? const RoundedRectangleBorder(), - child: Padding(padding: dimensions, child: child)), + shape: border ?? const RoundedRectangleBorder(), + child: Padding(padding: dimensions, child: child), + ), ); } BorderRadius _getBorderRadius(BorderRadius borderRadius, Size scaleSize) { return BorderRadius.only( - topRight: Radius.elliptical(borderRadius.topRight.x / scaleSize.width, - borderRadius.topRight.y / scaleSize.height), - bottomLeft: Radius.elliptical( - borderRadius.bottomLeft.x / scaleSize.width, - borderRadius.bottomLeft.y / scaleSize.height), - bottomRight: Radius.elliptical( - borderRadius.bottomRight.x / scaleSize.width, - borderRadius.bottomRight.y / scaleSize.height), - topLeft: Radius.elliptical(borderRadius.topLeft.x / scaleSize.width, - borderRadius.topLeft.y / scaleSize.height)); + topRight: Radius.elliptical( + borderRadius.topRight.x / scaleSize.width, + borderRadius.topRight.y / scaleSize.height, + ), + bottomLeft: Radius.elliptical( + borderRadius.bottomLeft.x / scaleSize.width, + borderRadius.bottomLeft.y / scaleSize.height, + ), + bottomRight: Radius.elliptical( + borderRadius.bottomRight.x / scaleSize.width, + borderRadius.bottomRight.y / scaleSize.height, + ), + topLeft: Radius.elliptical( + borderRadius.topLeft.x / scaleSize.width, + borderRadius.topLeft.y / scaleSize.height, + ), + ); } Widget? _buildItemBuilder(Widget? current) { - Widget? itemBuilder = - widget.details.level.itemBuilder!(context, widget.details); + Widget? itemBuilder = widget.details.level.itemBuilder!( + context, + widget.details, + ); if (itemBuilder == null) { return current; } @@ -2089,10 +2365,7 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } if (current != null) { - current = Stack(children: [ - itemBuilder!, - current, - ]); + current = Stack(children: [itemBuilder!, current]); } else { current = itemBuilder; } @@ -2102,18 +2375,22 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { @override void initState() { - _mouseIsConnected = - RendererBinding.instance?.mouseTracker.mouseIsConnected ?? false; - RendererBinding.instance?.mouseTracker - .addListener(_handleMouseTrackerChange); + _mouseIsConnected = RendererBinding.instance.mouseTracker.mouseIsConnected; + RendererBinding.instance.mouseTracker.addListener( + _handleMouseTrackerChange, + ); _controller = AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); + 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); + _colorTween = ColorTween( + begin: widget.details.color, + end: widget.details.color, + ); widget.controller ..addSelectionListener(_handleSelectionChange) @@ -2132,12 +2409,16 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { @override void didUpdateWidget(_TileDecor oldWidget) { - if (widget.sortAscending != oldWidget.sortAscending) { - _colorTween = - ColorTween(begin: widget.details.color, end: widget.details.color); + if (widget.sortAscending != oldWidget.sortAscending || + widget.details.color != oldWidget.details.color) { + _colorTween = ColorTween( + begin: widget.details.color, + end: widget.details.color, + ); if (widget.details.level.tooltipBuilder != null) { - final RenderTooltip tooltipRenderBox = widget.tooltipKey.currentContext! - .findRenderObject()! as RenderTooltip; + final RenderTooltip tooltipRenderBox = + widget.tooltipKey.currentContext!.findRenderObject()! + as RenderTooltip; tooltipRenderBox.hide(immediately: true); } } @@ -2153,8 +2434,9 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { ..removeSelectionListener(_handleSelectionChange) ..removeHoverListener(_handleHover); - RendererBinding.instance?.mouseTracker - .removeListener(_handleMouseTrackerChange); + RendererBinding.instance.mouseTracker.removeListener( + _handleMouseTrackerChange, + ); super.dispose(); } @@ -2169,8 +2451,11 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { Size? scaleSize; if (_ancestor.enableDrilldown && widget.ancestor._visibleTileScaleSize.begin != null) { - scaleSize = widget.ancestor._visibleTileScaleSize! - .evaluate(widget.ancestor._scaleAndTranslationAnimation) as Size; + scaleSize = + widget.ancestor._visibleTileScaleSize!.evaluate( + widget.ancestor._scaleAndTranslationAnimation, + ) + as Size; } if (widget.details.level.itemBuilder != null) { @@ -2187,8 +2472,12 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { final RoundedRectangleBorder? border = _getBorder(); final EdgeInsetsGeometry dimensions = border?.dimensions ?? EdgeInsets.zero; if (border != null || widget.onSelectionChanged != null) { - current = _buildTileBorder(current, - dimensions.resolve(Directionality.of(context)), border, scaleSize); + current = _buildTileBorder( + current, + dimensions.resolve(Directionality.of(context)), + border, + scaleSize, + ); } if (!widget.details.hasDescendants || _ancestor.enableDrilldown) { @@ -2204,7 +2493,7 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { if (widget.child != null) _ancestor.enableDrilldown ? widget.child! - : Padding(padding: dimensions, child: widget.child) + : Padding(padding: dimensions, child: widget.child), ], ); } @@ -2285,9 +2574,11 @@ class _BreadcrumbsState extends State<_Breadcrumbs> final int nextLevelIndex = _current._levelIndex + 1; for (int i = 0; i < breadcrumbTilesLength; i++) { - _breadcrumbs.add(i > nextLevelIndex - ? fadeOutBreadcrumb[i] - : widget.settings.builder(context, _tiles[i], i == nextLevelIndex)!); + _breadcrumbs.add( + i > nextLevelIndex + ? fadeOutBreadcrumb[i] + : widget.settings.builder(context, _tiles[i], i == nextLevelIndex)!, + ); } } @@ -2319,7 +2610,9 @@ class _BreadcrumbsState extends State<_Breadcrumbs> _breadcrumbItemKey = GlobalKey(); _opacityAnimation = CurvedAnimation( - parent: widget.animationController, curve: Curves.decelerate); + parent: widget.animationController, + curve: Curves.decelerate, + ); widget.animationController.addStatusListener(_handleStatusChange); widget.controller.addDrillDownListener(_handleDrilldown); @@ -2341,9 +2634,11 @@ class _BreadcrumbsState extends State<_Breadcrumbs> if (_breadcrumbs.isEmpty) { _breadcrumbs.add(widget.settings.builder(context, _current, true)); } - final Color dividerColor = - Theme.of(context).colorScheme.onSurface.withOpacity(0.54); - final Widget divider = widget.settings.divider ?? + final Color dividerColor = Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.54); + final Widget divider = + widget.settings.divider ?? Icon(Icons.chevron_right, size: 16.0, color: dividerColor); for (int i = 0; i < length; i++) { @@ -2362,17 +2657,19 @@ class _BreadcrumbsState extends State<_Breadcrumbs> final bool canApplyOpacity = (widget.animationController.status == AnimationStatus.forward && - _current._levelIndex == _tiles[i]._levelIndex) || - widget.animationController.status == AnimationStatus.reverse && - _current._levelIndex < _tiles[i]._levelIndex; - - children.add(_Breadcrumb( - tile: _tiles[i], - controller: widget.controller, - animation: _opacityAnimation, - canApplyOpacity: canApplyOpacity, - child: current, - )); + _current._levelIndex == _tiles[i]._levelIndex) || + widget.animationController.status == AnimationStatus.reverse && + _current._levelIndex < _tiles[i]._levelIndex; + + children.add( + _Breadcrumb( + tile: _tiles[i], + controller: widget.controller, + animation: _opacityAnimation, + canApplyOpacity: canApplyOpacity, + child: current, + ), + ); } } @@ -2390,12 +2687,16 @@ class _BreadcrumbsState extends State<_Breadcrumbs> key: _breadcrumbItemKey, duration: const Duration(milliseconds: 1000), padding: EdgeInsets.only( - left: padding.left, top: 11.0, bottom: 9.0, right: padding.right), + left: padding.left, + top: 11.0, + bottom: 9.0, + right: padding.right, + ), child: Stack( alignment: Alignment.centerLeft, children: [ Opacity(opacity: 0.0, child: divider), - if (current != null) current + if (current != null) current, ], ), ); @@ -2405,7 +2706,9 @@ class _BreadcrumbsState extends State<_Breadcrumbs> } return SingleChildScrollView( - scrollDirection: Axis.horizontal, child: current); + scrollDirection: Axis.horizontal, + child: current, + ); } } @@ -2437,9 +2740,10 @@ class _BreadcrumbState extends State<_Breadcrumb> { // State is not preserved, when a widget's visual tree is changed // dynamically. So that gesture widget wrapped into the ignore pointer. current = IgnorePointer( - ignoring: !((widget.animation.isCompleted || - widget.animation.isDismissed) && - widget.tile._levelIndex != widget.controller.visibleLevelIndex - 1), + ignoring: + !((widget.animation.isCompleted || widget.animation.isDismissed) && + widget.tile._levelIndex != + widget.controller.visibleLevelIndex - 1), child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( diff --git a/packages/syncfusion_flutter_treemap/lib/src/legend.dart b/packages/syncfusion_flutter_treemap/lib/src/legend.dart index c15234fa7..84d2b7de5 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/legend.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/legend.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/legend_internal.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; import '../treemap.dart'; @@ -16,8 +17,8 @@ enum _LegendType { } /// Signature to return a [Widget] for the given value. -typedef TreemapLegendPointerBuilder = Widget Function( - BuildContext context, dynamic value); +typedef TreemapLegendPointerBuilder = + Widget Function(BuildContext context, dynamic value); /// Positions the legend in the different directions. enum TreemapLegendPosition { @@ -68,7 +69,7 @@ enum TreemapLabelOverflow { /// It trims the labels based on the available space in their respective /// legend item. - ellipsis + ellipsis, } /// Option to place the labels either between the bars or on the bar in bar @@ -80,7 +81,7 @@ enum TreemapLegendLabelsPlacement { /// [TreemapLegendLabelsPlacement.betweenItems] places labels /// in-between two bars. - betweenItems + betweenItems, } /// Placement of edge labels in the bar legend. @@ -90,7 +91,7 @@ enum TreemapLegendEdgeLabelsPlacement { /// Place the edge labels in the center of the starting position of the /// legend bars. - center + center, } /// Applies gradient or solid color for the bar segments. @@ -99,7 +100,7 @@ enum TreemapLegendPaintingStyle { solid, /// Applies gradient color for bar segments. - gradient + gradient, } /// Shows legend for the data rendered in the treemap. @@ -309,6 +310,7 @@ class TreemapLegend extends DiagnosticableTree { /// See also: /// * [TreemapLegend.bar] named constructor, for bar legend type. const TreemapLegend({ + this.shouldAlwaysShowScrollbar = false, this.title, this.position = TreemapLegendPosition.top, this.offset, @@ -319,17 +321,17 @@ class TreemapLegend extends DiagnosticableTree { this.textStyle, this.iconType = TreemapIconType.circle, this.iconSize = const Size(8.0, 8.0), - }) : _type = _LegendType.vector, - segmentSize = null, - labelsPlacement = null, - edgeLabelsPlacement = TreemapLegendEdgeLabelsPlacement.inside, - labelOverflow = TreemapLabelOverflow.visible, - segmentPaintingStyle = TreemapLegendPaintingStyle.solid, - showPointerOnHover = false, - pointerBuilder = null, - pointerColor = null, - pointerSize = const Size(16, 12), - assert(spacing >= 0); + }) : _type = _LegendType.vector, + segmentSize = null, + labelsPlacement = null, + edgeLabelsPlacement = TreemapLegendEdgeLabelsPlacement.inside, + labelOverflow = TreemapLabelOverflow.visible, + segmentPaintingStyle = TreemapLegendPaintingStyle.solid, + showPointerOnHover = false, + pointerBuilder = null, + pointerColor = null, + pointerSize = const Size(16, 12), + assert(spacing >= 0); /// Creates a bar type legend for the tree map. /// @@ -422,6 +424,7 @@ class TreemapLegend extends DiagnosticableTree { /// * [TreemapLegend], for adding default legend type with different icon /// styles like circle, diamond, rectangle and triangle. const TreemapLegend.bar({ + this.shouldAlwaysShowScrollbar = false, this.title, this.overflowMode = TreemapLegendOverflowMode.scroll, this.padding = const EdgeInsets.all(10.0), @@ -439,10 +442,10 @@ class TreemapLegend extends DiagnosticableTree { this.pointerBuilder, this.pointerColor, this.pointerSize = const Size(16, 12), - }) : _type = _LegendType.bar, - iconType = TreemapIconType.circle, - iconSize = const Size(8.0, 8.0), - assert(spacing >= 0); + }) : _type = _LegendType.bar, + iconType = TreemapIconType.circle, + iconSize = const Size(8.0, 8.0), + assert(spacing >= 0); /// Enables a title for the legends to provide a small note about the legends. /// @@ -670,6 +673,25 @@ class TreemapLegend extends DiagnosticableTree { /// * [offset], to place the legend in custom position. final TreemapLegendPosition position; + ///Toggles the scrollbar visibility. + /// + /// When set to false, the scrollbar appears only when scrolling else the + /// scrollbar fades out. When true, the scrollbar will never fade out and + /// will always be visible when the items are overflown. + /// + ///Defaults to `false`. + /// + ///```dart + /// SfTreemap( + /// legend:Legend( + /// isVisible: true, + /// shouldAlwaysShowScrollbar: true, + /// overflowMode: TreemapLegendOverflowMode.scroll, + /// ) + /// ) + ///``` + final bool shouldAlwaysShowScrollbar; + /// Places the legend in custom position. /// /// If the [offset] has been set and if the [position] is top, then the legend @@ -1443,16 +1465,25 @@ class TreemapLegend extends DiagnosticableTree { } @override - int get hashCode => hashValues(padding, offset, spacing, direction, - overflowMode, position, textStyle, title); + int get hashCode => Object.hash( + padding, + offset, + spacing, + direction, + overflowMode, + position, + textStyle, + title, + ); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(EnumProperty<_LegendType>('legendType', _type)); if (padding != null) { - properties - .add(DiagnosticsProperty('padding', padding)); + properties.add( + DiagnosticsProperty('padding', padding), + ); } if (offset != null) { properties.add(DiagnosticsProperty('offset', offset)); @@ -1462,7 +1493,8 @@ class TreemapLegend extends DiagnosticableTree { properties.add(EnumProperty('direction', direction)); } properties.add( - EnumProperty('overflowMode', overflowMode)); + EnumProperty('overflowMode', overflowMode), + ); properties.add(EnumProperty('position', position)); if (textStyle != null) { properties.add(textStyle!.toDiagnosticsNode(name: 'textStyle')); @@ -1474,14 +1506,27 @@ class TreemapLegend extends DiagnosticableTree { if (segmentSize != null) { 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)); + properties.add( + EnumProperty( + 'labelsPlacement', + labelsPlacement, + ), + ); + properties.add( + EnumProperty( + 'edgeLabelsPlacement', + edgeLabelsPlacement, + ), + ); + properties.add( + EnumProperty('labelOverflowMode', labelOverflow), + ); + properties.add( + EnumProperty( + 'segmentPaintingStyle', + segmentPaintingStyle, + ), + ); } } } @@ -1496,8 +1541,8 @@ class Legend extends StatelessWidget { required this.legend, required this.controller, required this.child, - }) : assert(colorMappers != null || dataSource != null), - super(key: key); + }) : assert(colorMappers != null || dataSource != null), + super(key: key); /// Collection of [TreemapColorMapper] which specifies treemap color based /// on the data. @@ -1531,15 +1576,18 @@ class Legend extends StatelessWidget { final String startValue = _getStartSegmentLabel(colorMapper); items.add(LegendItem(text: startValue, color: colorMapper.color)); } else { - final String text = colorMapper.from != null - ? colorMapper.name ?? colorMapper.to.toString() - : colorMapper.value!; + final String text = + colorMapper.from != null + ? colorMapper.name ?? colorMapper.to.toString() + : colorMapper.value!; items.add(LegendItem(text: text, color: colorMapper.color)); } } else { - final String text = colorMapper.from != null - ? colorMapper.name ?? '${colorMapper.from} - ${colorMapper.to}' - : colorMapper.value!; + final String text = + colorMapper.from != null + ? colorMapper.name ?? + '${colorMapper.from} - ${colorMapper.to}' + : colorMapper.value!; items.add(LegendItem(text: text, color: colorMapper.color)); } } @@ -1590,7 +1638,8 @@ class Legend extends StatelessWidget { } LegendPaintingStyle _getEffectiveSegmentPaintingStyle( - TreemapLegendPaintingStyle paintingStyle) { + TreemapLegendPaintingStyle paintingStyle, + ) { switch (paintingStyle) { case TreemapLegendPaintingStyle.solid: return LegendPaintingStyle.solid; @@ -1600,7 +1649,8 @@ class Legend extends StatelessWidget { } LegendLabelOverflow _getEffectiveLabelOverflow( - TreemapLabelOverflow labelOverflow) { + TreemapLabelOverflow labelOverflow, + ) { switch (labelOverflow) { case TreemapLabelOverflow.visible: return LegendLabelOverflow.visible; @@ -1612,7 +1662,8 @@ class Legend extends StatelessWidget { } LegendEdgeLabelsPlacement _getEffectiveEdgeLabelsPlacement( - TreemapLegendEdgeLabelsPlacement edgeLabelsPlacement) { + TreemapLegendEdgeLabelsPlacement edgeLabelsPlacement, + ) { switch (edgeLabelsPlacement) { case TreemapLegendEdgeLabelsPlacement.center: return LegendEdgeLabelsPlacement.center; @@ -1622,7 +1673,8 @@ class Legend extends StatelessWidget { } LegendLabelsPlacement _getEffectiveLabelPlacement( - TreemapLegendLabelsPlacement labelsPlacement) { + TreemapLegendLabelsPlacement labelsPlacement, + ) { switch (labelsPlacement) { case TreemapLegendLabelsPlacement.betweenItems: return LegendLabelsPlacement.betweenItems; @@ -1645,7 +1697,8 @@ class Legend extends StatelessWidget { } LegendOverflowMode _getEffectiveOverflowMode( - TreemapLegendOverflowMode overflowMode) { + TreemapLegendOverflowMode overflowMode, + ) { switch (overflowMode) { case TreemapLegendOverflowMode.scroll: return LegendOverflowMode.scroll; @@ -1669,26 +1722,30 @@ class Legend extends StatelessWidget { @override Widget build(BuildContext context) { + final TextStyle legendTextStyle = Theme.of(context).textTheme.bodySmall! + .merge(SfTreemapTheme.of(context).legendTextStyle) + .merge(legend.textStyle); switch (legend._type) { case _LegendType.vector: return SfLegend( + shouldAlwaysShowScrollbar: legend.shouldAlwaysShowScrollbar, items: _getLegendItems(), - child: child, direction: legend.direction, offset: legend.offset, padding: legend.padding, position: _getEffectivePosition(legend.position), overflowMode: _getEffectiveOverflowMode(legend.overflowMode), itemSpacing: legend.spacing, - textStyle: legend.textStyle, + textStyle: legendTextStyle, title: legend.title, iconType: _getEffectiveLegendIconType(legend.iconType), iconSize: legend.iconSize, + child: child, ); case _LegendType.bar: return SfLegend.bar( + shouldAlwaysShowScrollbar: legend.shouldAlwaysShowScrollbar, items: _getLegendItems(), - child: child, title: legend.title, position: _getEffectivePosition(legend.position), overflowMode: _getEffectiveOverflowMode(legend.overflowMode), @@ -1696,20 +1753,24 @@ class Legend extends StatelessWidget { direction: legend.direction, offset: legend.offset, padding: legend.padding, - textStyle: legend.textStyle, - labelsPlacement: - _getEffectiveLabelPlacement(_getActualLabelsPlacement()), - edgeLabelsPlacement: - _getEffectiveEdgeLabelsPlacement(legend.edgeLabelsPlacement), + textStyle: legendTextStyle, + labelsPlacement: _getEffectiveLabelPlacement( + _getActualLabelsPlacement(), + ), + edgeLabelsPlacement: _getEffectiveEdgeLabelsPlacement( + legend.edgeLabelsPlacement, + ), labelOverflow: _getEffectiveLabelOverflow(legend.labelOverflow), segmentSize: legend.segmentSize, - segmentPaintingStyle: - _getEffectiveSegmentPaintingStyle(legend.segmentPaintingStyle), + segmentPaintingStyle: _getEffectiveSegmentPaintingStyle( + legend.segmentPaintingStyle, + ), pointerBuilder: legend.pointerBuilder, pointerColor: legend.pointerColor, pointerSize: legend.showPointerOnHover ? legend.pointerSize : Size.zero, pointerController: controller, + child: child, ); } } diff --git a/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart b/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart index f64641fca..169edc61b 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart @@ -37,7 +37,9 @@ class _TreemapTooltipState extends State @override void initState() { controller = AnimationController( - vsync: this, duration: const Duration(milliseconds: 200)); + vsync: this, + duration: const Duration(milliseconds: 200), + ); super.initState(); } @@ -50,25 +52,33 @@ class _TreemapTooltipState extends State @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); - _isDesktop = kIsWeb || + _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))); + 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); + settings: settings, + state: this, + child: _child, + ); } } /// 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); + const _TreemapTooltipRenderObjectWidget({ + Widget? child, + required this.settings, + required this.state, + }) : super(child: child); final _TreemapTooltipState state; final TreemapTooltipSettings settings; @@ -98,11 +108,13 @@ class RenderTooltip extends RenderProxyBox { required MediaQueryData mediaQueryData, // ignore: library_private_types_in_public_api required _TreemapTooltipState state, - }) : _settings = settings, - _mediaQueryData = mediaQueryData, - _state = state { - _scaleAnimation = - CurvedAnimation(parent: _state.controller, curve: Curves.easeOutBack); + }) : _settings = settings, + _mediaQueryData = mediaQueryData, + _state = state { + _scaleAnimation = CurvedAnimation( + parent: _state.controller, + curve: Curves.easeOutBack, + ); _textDirection = Directionality.of(_state.context); } @@ -116,7 +128,7 @@ class RenderTooltip extends RenderProxyBox { late Animation _scaleAnimation; Timer? _showTimer; Timer? _hideDeferTimer; - Size? _tileSize; + late Size? _tileSize; Offset? _position; bool _preferTooltipOnTop = true; bool _shouldCalculateTooltipPosition = false; @@ -146,8 +158,12 @@ class RenderTooltip extends RenderProxyBox { } /// Shows the tooltip. - void show(Offset globalFocalPoint, TreemapTile tile, Size tileSize, - PointerKind kind) { + void show( + Offset globalFocalPoint, + TreemapTile tile, + Size tileSize, + PointerKind kind, + ) { _pointerKind = kind; if (_state._isDesktop && _previousTile == tile) { return; @@ -235,8 +251,16 @@ class RenderTooltip extends RenderProxyBox { _shouldCalculateTooltipPosition = false; } - _tooltipShape.paint(context, offset, _position!, _preferTooltipOnTop, - this, _scaleAnimation, _settings, _textDirection); + _tooltipShape.paint( + context, + offset, + _position!, + _preferTooltipOnTop, + this, + _scaleAnimation, + _settings, + _textDirection, + ); } } @@ -253,8 +277,9 @@ class RenderTooltip extends RenderProxyBox { // 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)); + _preferTooltipOnTop = paintBounds.contains( + _position! - Offset(0.0, height), + ); } } } @@ -267,21 +292,23 @@ class _TooltipShape { /// 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) { + 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); + BorderRadius borderRadius = tooltipSettings.borderRadius.resolve( + textDirection, + ); final double tooltipWidth = parentBox.child!.size.width; double tooltipHeight = parentBox.child!.size.height; final double halfTooltipWidth = tooltipWidth / 2; @@ -292,29 +319,36 @@ class _TooltipShape { 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; + 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) { @@ -329,42 +363,54 @@ class _TooltipShape { tooltipHeight *= -1; borderRadius = BorderRadius.only( topRight: Radius.elliptical( - borderRadius.bottomRight.x, -borderRadius.bottomRight.y), + borderRadius.bottomRight.x, + -borderRadius.bottomRight.y, + ), bottomRight: Radius.elliptical( - borderRadius.topRight.x, -borderRadius.topRight.y), + borderRadius.topRight.x, + -borderRadius.topRight.y, + ), topLeft: Radius.elliptical( - borderRadius.bottomLeft.x, -borderRadius.bottomLeft.y), - bottomLeft: - Radius.elliptical(borderRadius.topLeft.x, -borderRadius.topLeft.y), + 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); + path, + triangleHeight, + halfTooltipHeight, + halfTooltipTriangleWidth, + tooltipTriangleOffsetY, + moveNosePoint, + rightLineWidth, + leftLineWidth, + borderRadius, + tooltipHeight, + ); context.canvas.save(); - context.canvas - .translate(center.dx, center.dy - triangleHeight - halfTooltipHeight); + 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; + 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 @@ -374,22 +420,25 @@ class _TooltipShape { 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(); } Path _getTooltipPath( - Path path, - double tooltipTriangleHeight, - double halfTooltipHeight, - double halfTooltipTriangleWidth, - double tooltipTriangleOffsetY, - double moveNosePoint, - double rightLineWidth, - double leftLineWidth, - BorderRadius borderRadius, - double tooltipHeight) { + 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); @@ -399,7 +448,9 @@ class _TooltipShape { // preferTooltipOnTop is false, // \ path.lineTo( - halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + halfTooltipTriangleWidth + moveNosePoint, + tooltipTriangleOffsetY, + ); // preferTooltipOnTop is true, // ___ // / @@ -407,7 +458,9 @@ class _TooltipShape { // preferTooltipOnTop is false, // \___ path.lineTo( - rightLineWidth - borderRadius.bottomRight.x, tooltipTriangleOffsetY); + rightLineWidth - borderRadius.bottomRight.x, + tooltipTriangleOffsetY, + ); // preferTooltipOnTop is true, // ___| // / @@ -415,10 +468,16 @@ class _TooltipShape { // preferTooltipOnTop is false, // \___ // | - path.quadraticBezierTo(rightLineWidth, tooltipTriangleOffsetY, - rightLineWidth, tooltipTriangleOffsetY - borderRadius.bottomRight.y); - path.lineTo(rightLineWidth, - tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y); + path.quadraticBezierTo( + rightLineWidth, + tooltipTriangleOffsetY, + rightLineWidth, + tooltipTriangleOffsetY - borderRadius.bottomRight.y, + ); + path.lineTo( + rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y, + ); // preferTooltipOnTop is true, // _______ // ___| @@ -428,12 +487,15 @@ class _TooltipShape { // \___ // ________| path.quadraticBezierTo( - rightLineWidth, - tooltipTriangleOffsetY - tooltipHeight, - rightLineWidth - borderRadius.topRight.x, - tooltipTriangleOffsetY - tooltipHeight); - path.lineTo(-leftLineWidth + borderRadius.topLeft.x, - tooltipTriangleOffsetY - tooltipHeight); + rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + rightLineWidth - borderRadius.topRight.x, + tooltipTriangleOffsetY - tooltipHeight, + ); + path.lineTo( + -leftLineWidth + borderRadius.topLeft.x, + tooltipTriangleOffsetY - tooltipHeight, + ); // preferTooltipOnTop is true, // _______ // | ___| @@ -443,12 +505,15 @@ class _TooltipShape { // \___ // |________| path.quadraticBezierTo( - -leftLineWidth, - tooltipTriangleOffsetY - tooltipHeight, - -leftLineWidth, - tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y); + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y, + ); path.lineTo( - -leftLineWidth, tooltipTriangleOffsetY - borderRadius.bottomLeft.y); + -leftLineWidth, + tooltipTriangleOffsetY - borderRadius.bottomLeft.y, + ); // preferTooltipOnTop is true, // ________ // |___ ___| @@ -457,10 +522,16 @@ class _TooltipShape { // preferTooltipOnTop is false, // ___ \___ // |________| - path.quadraticBezierTo(-leftLineWidth, tooltipTriangleOffsetY, - -leftLineWidth + borderRadius.bottomLeft.x, tooltipTriangleOffsetY); + path.quadraticBezierTo( + -leftLineWidth, + tooltipTriangleOffsetY, + -leftLineWidth + borderRadius.bottomLeft.x, + tooltipTriangleOffsetY, + ); path.lineTo( - -halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + -halfTooltipTriangleWidth + moveNosePoint, + tooltipTriangleOffsetY, + ); // preferTooltipOnTop is true, // ________ // |___ ___| @@ -475,7 +546,10 @@ class _TooltipShape { } Offset _getShiftPosition( - Offset offset, Offset center, RenderProxyBox parentBox) { + Offset offset, + Offset center, + RenderProxyBox parentBox, + ) { final Size childSize = parentBox.child!.size; final double halfChildWidth = childSize.width / 2; final double halfChildHeight = childSize.height / 2; @@ -484,8 +558,9 @@ class _TooltipShape { // 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); + 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. diff --git a/packages/syncfusion_flutter_treemap/lib/treemap.dart b/packages/syncfusion_flutter_treemap/lib/treemap.dart index 4f59b2999..1b142be33 100644 --- a/packages/syncfusion_flutter_treemap/lib/treemap.dart +++ b/packages/syncfusion_flutter_treemap/lib/treemap.dart @@ -51,8 +51,8 @@ typedef TreemapTileColorValueMapper = dynamic Function(TreemapTile tile); /// * [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); +typedef TreemapTileWidgetBuilder = + Widget? Function(BuildContext context, TreemapTile tile); /// Signature to return a widget based on the given tile. /// @@ -66,8 +66,8 @@ typedef TreemapTileWidgetBuilder = Widget? Function( /// * [TreemapTileColorValueMapper] returns a dynamic value based on the group /// and parent. /// * [TreemapTileWidgetBuilder] returns a widget based on a given tile. -typedef TreemapBreadcrumbBuilder = Widget? Function( - BuildContext context, TreemapTile tile, bool isCurrent); +typedef TreemapBreadcrumbBuilder = + Widget? Function(BuildContext context, TreemapTile tile, bool isCurrent); /// Positions the tiles in the different corners. enum TreemapLayoutDirection { @@ -737,23 +737,45 @@ class TreemapLevel extends DiagnosticableTree { properties.add(ColorProperty('color', color)); } if (border != null) { - properties - .add(DiagnosticsProperty('border', border)); + properties.add( + DiagnosticsProperty('border', border), + ); } if (padding != null) { - properties - .add(DiagnosticsProperty('padding', padding)); + properties.add( + DiagnosticsProperty('padding', padding), + ); } - properties.add(ObjectFlagProperty.has( - 'groupMapper', groupMapper)); - properties.add(ObjectFlagProperty.has( - 'colorValueMapper', colorValueMapper)); - properties.add(ObjectFlagProperty.has( - 'tooltipBuilder', tooltipBuilder)); - properties.add(ObjectFlagProperty.has( - 'labelBuilder', labelBuilder)); - properties.add(ObjectFlagProperty.has( - 'itemBuilder', itemBuilder)); + properties.add( + ObjectFlagProperty.has( + 'groupMapper', + groupMapper, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'colorValueMapper', + colorValueMapper, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'tooltipBuilder', + tooltipBuilder, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'labelBuilder', + labelBuilder, + ), + ); + properties.add( + ObjectFlagProperty.has( + 'itemBuilder', + itemBuilder, + ), + ); } @override @@ -776,8 +798,14 @@ class TreemapLevel extends DiagnosticableTree { } @override - int get hashCode => hashValues( - groupMapper, color, border, colorValueMapper, padding, tooltipBuilder); + int get hashCode => Object.hash( + groupMapper, + color, + border, + colorValueMapper, + padding, + tooltipBuilder, + ); } /// Customized the appearance of the tiles in selection state. @@ -977,8 +1005,9 @@ class TreemapSelectionSettings extends DiagnosticableTree { properties.add(ColorProperty('color', color)); } if (border != null) { - properties - .add(DiagnosticsProperty('border', border)); + properties.add( + DiagnosticsProperty('border', border), + ); } } @@ -996,7 +1025,7 @@ class TreemapSelectionSettings extends DiagnosticableTree { } @override - int get hashCode => hashValues(color, border); + int get hashCode => Object.hash(color, border); } /// Customizes the appearance of the tooltip. @@ -1133,9 +1162,7 @@ class TreemapTooltipSettings extends DiagnosticableTree { this.hideDelay = 3.0, this.borderColor, this.borderWidth = 1.0, - this.borderRadius = const BorderRadius.all( - Radius.circular(4.0), - ), + this.borderRadius = const BorderRadius.all(Radius.circular(4.0)), }); /// Fills the tooltip by this color. @@ -1190,8 +1217,9 @@ class TreemapTooltipSettings extends DiagnosticableTree { properties.add(ColorProperty('borderColor', borderColor)); } properties.add(DoubleProperty('borderWidth', borderWidth)); - properties.add(DiagnosticsProperty( - 'borderRadius', borderRadius)); + properties.add( + DiagnosticsProperty('borderRadius', borderRadius), + ); } @override @@ -1212,7 +1240,7 @@ class TreemapTooltipSettings extends DiagnosticableTree { @override int get hashCode => - hashValues(color, hideDelay, borderColor, borderWidth, borderRadius); + Object.hash(color, hideDelay, borderColor, borderWidth, borderRadius); /// Creates a copy of this class but with the given fields /// replaced with the new values. @@ -1224,11 +1252,12 @@ class TreemapTooltipSettings extends DiagnosticableTree { BorderRadiusGeometry? borderRadius, }) { return TreemapTooltipSettings( - color: color ?? this.color, - hideDelay: hideDelay ?? this.hideDelay, - borderColor: borderColor ?? this.borderColor, - borderWidth: borderWidth ?? this.borderWidth, - borderRadius: borderRadius ?? this.borderRadius); + color: color ?? this.color, + hideDelay: hideDelay ?? this.hideDelay, + borderColor: borderColor ?? this.borderColor, + borderWidth: borderWidth ?? this.borderWidth, + borderRadius: borderRadius ?? this.borderRadius, + ); } } @@ -1437,14 +1466,16 @@ class TreemapColorMapper extends DiagnosticableTree { this.minSaturation, this.maxSaturation, this.name, - }) : assert(from != null && to != null && from <= to), - assert((minSaturation == null && maxSaturation == null) || - (minSaturation != null && - maxSaturation != null && - minSaturation < maxSaturation && - (minSaturation >= 0 && minSaturation <= 1) && - (maxSaturation >= 0 && maxSaturation <= 1))), - value = null; + }) : assert(from != null && to != null && from <= to), + assert( + (minSaturation == null && maxSaturation == null) || + (minSaturation != null && + maxSaturation != null && + minSaturation < maxSaturation && + (minSaturation >= 0 && minSaturation <= 1) && + (maxSaturation >= 0 && maxSaturation <= 1)), + ), + value = null; /// Applies the color to the tiles which is equal to the given /// [TreemapColorMapper.value]. The [TreemapColorMapper.value] must not be @@ -1512,11 +1543,11 @@ class TreemapColorMapper extends DiagnosticableTree { /// See also: /// * [SfTreemap.legend], to enable and customize the legend. const TreemapColorMapper.value({required this.value, required this.color}) - : from = null, - to = null, - minSaturation = null, - maxSaturation = null, - name = null; + : from = null, + to = null, + minSaturation = null, + maxSaturation = null, + name = null; /// Specifies the color applies to the tile based on the value returned in /// the [TreemapLevel.colorValueMapper]. @@ -1839,13 +1870,13 @@ class SfTreemap extends StatelessWidget { this.tooltipSettings = const TreemapTooltipSettings(), this.enableDrilldown = false, this.breadcrumbs, - }) : assert(dataCount > 0), - assert(levels.length > 0), - assert(colorMappers == null || colorMappers.length > 0), - assert(!enableDrilldown || (enableDrilldown && breadcrumbs != null)), - sortAscending = false, - _layoutType = LayoutType.squarified, - super(key: key); + }) : assert(dataCount > 0), + assert(levels.length > 0), + assert(colorMappers == null || colorMappers.length > 0), + assert(!enableDrilldown || (enableDrilldown && breadcrumbs != null)), + sortAscending = false, + _layoutType = LayoutType.squarified, + super(key: key); /// Creates a treemap based on the slice algorithm. /// @@ -1960,13 +1991,13 @@ class SfTreemap extends StatelessWidget { this.tileHoverBorder, this.enableDrilldown = false, this.breadcrumbs, - }) : assert(dataCount > 0), - assert(levels.length > 0), - assert(colorMappers == null || colorMappers.length > 0), - assert(!enableDrilldown || (enableDrilldown && breadcrumbs != null)), - layoutDirection = TreemapLayoutDirection.topLeft, - _layoutType = LayoutType.slice, - super(key: key); + }) : assert(dataCount > 0), + assert(levels.length > 0), + assert(colorMappers == null || colorMappers.length > 0), + assert(!enableDrilldown || (enableDrilldown && breadcrumbs != null)), + layoutDirection = TreemapLayoutDirection.topLeft, + _layoutType = LayoutType.slice, + super(key: key); /// Creates a treemap based on the dice algorithm. /// @@ -2080,13 +2111,13 @@ class SfTreemap extends StatelessWidget { this.tileHoverBorder, this.enableDrilldown = false, this.breadcrumbs, - }) : assert(dataCount > 0), - assert(levels.length > 0), - assert(colorMappers == null || colorMappers.length > 0), - assert(!enableDrilldown || (enableDrilldown && breadcrumbs != null)), - _layoutType = LayoutType.dice, - layoutDirection = TreemapLayoutDirection.topLeft, - super(key: key); + }) : assert(dataCount > 0), + assert(levels.length > 0), + assert(colorMappers == null || colorMappers.length > 0), + assert(!enableDrilldown || (enableDrilldown && breadcrumbs != null)), + _layoutType = LayoutType.dice, + layoutDirection = TreemapLayoutDirection.topLeft, + super(key: key); /// Specifies the length of the data source. /// @@ -3040,11 +3071,13 @@ class SfTreemap extends StatelessWidget { colorMappers: colorMappers, legend: legend, tileHoverColor: tileHoverColor, - tileHoverBorder: tileHoverBorder ?? + tileHoverBorder: + tileHoverBorder ?? RoundedRectangleBorder( side: BorderSide( - width: 1, - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.5), + color: Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.5), ), ), onSelectionChanged: onSelectionChanged, @@ -3063,29 +3096,45 @@ class SfTreemap extends StatelessWidget { if (tileHoverColor != null) { properties.add(ColorProperty('tileHoverColor', tileHoverColor)); } - properties.add(DiagnosticsProperty( - 'tileHoverBorder', tileHoverBorder)); + properties.add( + DiagnosticsProperty( + 'tileHoverBorder', + tileHoverBorder, + ), + ); if (levels.isNotEmpty) { properties.add(_DebugLevelsTree(levels).toDiagnosticsNode()); } - properties.add(ObjectFlagProperty.has( - 'weightValueMapper', weightValueMapper)); + properties.add( + ObjectFlagProperty.has( + 'weightValueMapper', + weightValueMapper, + ), + ); if (colorMappers != null && colorMappers!.isNotEmpty) { properties.add(_DebugColorMappersTree(colorMappers!).toDiagnosticsNode()); } if (legend != null) { properties.add(legend!.toDiagnosticsNode(name: 'legend')); } - properties.add(ObjectFlagProperty>.has( - 'onSelectionChanged', onSelectionChanged)); - properties - .add(selectionSettings.toDiagnosticsNode(name: 'selectionSettings')); + properties.add( + ObjectFlagProperty>.has( + 'onSelectionChanged', + onSelectionChanged, + ), + ); + properties.add( + selectionSettings.toDiagnosticsNode(name: 'selectionSettings'), + ); properties.add(tooltipSettings.toDiagnosticsNode(name: 'tooltipSettings')); - properties.add(FlagProperty('enableDrilldown', + properties.add( + FlagProperty( + 'enableDrilldown', value: enableDrilldown, ifTrue: 'drilldown is enabled', ifFalse: 'drilldown is disabled', - showName: false)); + ), + ); if (breadcrumbs != null) { properties.add(breadcrumbs!.toDiagnosticsNode(name: 'breadcrumbs')); } @@ -3123,8 +3172,9 @@ class _DebugColorMappersTree extends DiagnosticableTree { @override List debugDescribeChildren() { if (colorMappers.isNotEmpty) { - return colorMappers - .map((TreemapColorMapper colorMapper) { + return colorMappers.map(( + TreemapColorMapper colorMapper, + ) { return colorMapper.toDiagnosticsNode(); }).toList(); } @@ -3143,10 +3193,11 @@ class _DebugColorMappersTree extends DiagnosticableTree { @immutable class TreemapBreadcrumbs extends DiagnosticableTree { /// Creates a [TreemapBreadcrumbs]. - const TreemapBreadcrumbs( - {required this.builder, - this.divider, - this.position = TreemapBreadcrumbPosition.top}); + const TreemapBreadcrumbs({ + required this.builder, + this.divider, + this.position = TreemapBreadcrumbPosition.top, + }); /// Returns a widget for the breadcrumb divider. /// @@ -3331,10 +3382,12 @@ class TreemapBreadcrumbs extends DiagnosticableTree { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties - .add(EnumProperty('position', position)); properties.add( - ObjectFlagProperty.has('builder', builder)); + EnumProperty('position', position), + ); + properties.add( + ObjectFlagProperty.has('builder', builder), + ); if (divider != null) { properties.add(ObjectFlagProperty.has('divider', divider)); } diff --git a/packages/syncfusion_flutter_treemap/pubspec.yaml b/packages/syncfusion_flutter_treemap/pubspec.yaml index 5a7362024..e3273e1e5 100644 --- a/packages/syncfusion_flutter_treemap/pubspec.yaml +++ b/packages/syncfusion_flutter_treemap/pubspec.yaml @@ -1,10 +1,20 @@ 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: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_treemap +screenshots: + - description: 'This screenshot shows the treemap drill down gif.' + path: screenshots/flutter-treemap-drilldown.gif + - description: 'This screenshot shows the treemap hierarchical level.' + path: screenshots/flutter-treemap-hierarchical-level.png + - description: 'This screenshot shows the treemap slice dice layout.' + path: screenshots/flutter-treemap-slice-dice-layout.png + - description: 'This screenshot shows the treemap squarified layout.' + path: screenshots/flutter-treemap-squarified-layout.png + environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: @@ -12,8 +22,12 @@ dependencies: syncfusion_flutter_core: path: ../syncfusion_flutter_core - + +flutter: + assets: + - assets/images/ + - assets/fonts/Roboto-Medium.ttf diff --git a/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-drilldown.gif b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-drilldown.gif new file mode 100644 index 000000000..d34a87d8c Binary files /dev/null and b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-drilldown.gif differ diff --git a/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-hierarchical-level.png b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-hierarchical-level.png new file mode 100644 index 000000000..41b07cdd9 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-hierarchical-level.png differ diff --git a/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-slice-dice-layout.png b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-slice-dice-layout.png new file mode 100644 index 000000000..a792a91e6 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-slice-dice-layout.png differ diff --git a/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-squarified-layout.png b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-squarified-layout.png new file mode 100644 index 000000000..c245b082f Binary files /dev/null and b/packages/syncfusion_flutter_treemap/screenshots/flutter-treemap-squarified-layout.png differ diff --git a/packages/syncfusion_flutter_xlsio/CHANGELOG.md b/packages/syncfusion_flutter_xlsio/CHANGELOG.md index 47d3c2065..0305cde4e 100644 --- a/packages/syncfusion_flutter_xlsio/CHANGELOG.md +++ b/packages/syncfusion_flutter_xlsio/CHANGELOG.md @@ -1,3 +1,56 @@ +## Unreleased + +**General** + +* Upgraded the archive package. The Flutter XlsIO widget supports archive package version 4.0.0 and above. +* The compatible version of our Flutter XlsIO widget has been updated to Flutter SDK 3.32.0. + + +## [29.2.7] - 27/05/2025 + +**Bugs** + +* Excel file gets corrupted when saving a formula containing single quotes in flutter is now resolved. + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter XlsIO widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter XlsIO example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter XlsIO example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 24/12/2024 + +**General** + +* The compatible version of our Flutter XlsIO widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter XlsIO widget has been updated to Flutter SDK 3.24.0. + ## [19.2.44-beta] - 06/30/2021 **Features** diff --git a/packages/syncfusion_flutter_xlsio/README.md b/packages/syncfusion_flutter_xlsio/README.md index e770604fa..50910e978 100644 --- a/packages/syncfusion_flutter_xlsio/README.md +++ b/packages/syncfusion_flutter_xlsio/README.md @@ -53,15 +53,12 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

@@ -637,11 +634,11 @@ workbook.dispose(); ## 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). +* For any other queries, contact our [Syncfusion support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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), [.NET MAUI](https://www.syncfusion.com/maui-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), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls) and [WinUI](https://www.syncfusion.com/winui-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. +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_flutter_xlsio/example/README.md b/packages/syncfusion_flutter_xlsio/example/README.md new file mode 100644 index 000000000..7cc026711 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/README.md @@ -0,0 +1,3 @@ +# xlsio_example + +Demo for creating a Excel file using syncfusion_flutter_xlsio package. diff --git a/packages/syncfusion_flutter_xlsio/example/android/.gitignore b/packages/syncfusion_flutter_xlsio/example/android/.gitignore index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_flutter_xlsio/example/android/.gitignore +++ b/packages/syncfusion_flutter_xlsio/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/build.gradle.kts b/packages/syncfusion_flutter_xlsio/example/android/app/build.gradle.kts new file mode 100644 index 000000000..1ae81f61b --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.xlsio_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.xlsio_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_xlsio/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_flutter_xlsio/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_flutter_xlsio/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/AndroidManifest.xml index 3f41384db..81fa7fc55 100644 --- a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/kotlin/com/example/xlsio_example/MainActivity.kt b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/kotlin/com/example/xlsio_example/MainActivity.kt new file mode 100644 index 000000000..cef30ca54 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/kotlin/com/example/xlsio_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.xlsio_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_flutter_xlsio/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_flutter_xlsio/example/android/build.gradle.kts b/packages/syncfusion_flutter_xlsio/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_flutter_xlsio/example/android/gradle.properties b/packages/syncfusion_flutter_xlsio/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_flutter_xlsio/example/android/gradle.properties +++ b/packages/syncfusion_flutter_xlsio/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_flutter_xlsio/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_xlsio/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_flutter_xlsio/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_flutter_xlsio/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_flutter_xlsio/example/android/settings.gradle.kts b/packages/syncfusion_flutter_xlsio/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_flutter_xlsio/example/ios/.gitignore b/packages/syncfusion_flutter_xlsio/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/.gitignore +++ b/packages/syncfusion_flutter_xlsio/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_xlsio/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_flutter_xlsio/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Debug.xcconfig index 592ceee85..ec97fc6f3 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Release.xcconfig index 592ceee85..c4855bfe2 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_flutter_xlsio/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.pbxproj index 6b50bd568..02730dd1e 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,32 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3C5267F394692B2B0B2C76DD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AB1F016181979F4EF2A57D6 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 */; }; + 9D02296D15C4E4FC0CA88560 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DCC12DF16CF3A1B6F47B403 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -29,9 +43,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 055A9B40BDBDDB24685D43D2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 0DCC12DF16CF3A1B6F47B403 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + 18002A3FC439C4046381BD5E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 202953B6669D8F942CC158D3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 2F24FC65271FABD34C0D2C11 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 54697996BCC70D5031EF9CB2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 6AB1F016181979F4EF2A57D6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -42,6 +65,7 @@ 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 = ""; }; + ABA758EF4468B84689EA1130 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,12 +73,53 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 3C5267F394692B2B0B2C76DD /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D3CF69220DF750AB29B9AAEA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D02296D15C4E4FC0CA88560 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 057E58E8D50F04B4025CF784 /* Pods */ = { + isa = PBXGroup; + children = ( + 202953B6669D8F942CC158D3 /* Pods-Runner.debug.xcconfig */, + 54697996BCC70D5031EF9CB2 /* Pods-Runner.release.xcconfig */, + 055A9B40BDBDDB24685D43D2 /* Pods-Runner.profile.xcconfig */, + 18002A3FC439C4046381BD5E /* Pods-RunnerTests.debug.xcconfig */, + 2F24FC65271FABD34C0D2C11 /* Pods-RunnerTests.release.xcconfig */, + ABA758EF4468B84689EA1130 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 820ED33559EA1F7447A56E07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6AB1F016181979F4EF2A57D6 /* Pods_Runner.framework */, + 0DCC12DF16CF3A1B6F47B403 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +137,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 057E58E8D50F04B4025CF784 /* Pods */, + 820ED33559EA1F7447A56E07 /* Frameworks */, ); sourceTree = ""; }; @@ -79,6 +147,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +159,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,32 +167,49 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 2F2DEDEBD43BE511FDC02CD2 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + D3CF69220DF750AB29B9AAEA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 058227A5375270C73A44358A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 027BA4A504178BB5B3CD7BC4 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +220,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +243,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -177,12 +278,75 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 027BA4A504178BB5B3CD7BC4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 058227A5375270C73A44358A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2F2DEDEBD43BE511FDC02CD2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +357,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -208,6 +373,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +392,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +422,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +454,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +463,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -298,15 +480,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -316,11 +493,61 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 18002A3FC439C4046381BD5E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2F24FC65271FABD34C0D2C11 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ABA758EF4468B84689EA1130 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +577,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +592,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +602,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +634,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +643,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -432,15 +662,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -459,15 +684,10 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -480,6 +700,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +731,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_xlsio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - + + diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_xlsio/example/ios/Runner/AppDelegate.swift index 70693e4a8..626664468 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_xlsio/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ -import UIKit import Flutter +import UIKit -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/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_xlsio/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Info.plist index 44da86d86..a179fe094 100644 --- a/packages/syncfusion_flutter_xlsio/example/ios/Runner/Info.plist +++ b/packages/syncfusion_flutter_xlsio/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Xlsio Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart index 12863e435..856ba8bdb 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:open_file/open_file.dart' as open_file; import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; ///To save the Excel file in the device @@ -19,8 +20,9 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else { path = await PathProviderPlatform.instance.getApplicationSupportPath(); } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + final File file = File( + Platform.isWindows ? '$path\\$fileName' : '$path/$fileName', + ); await file.writeAsBytes(bytes, flush: true); if (Platform.isAndroid || Platform.isIOS) { //Launch the file (used open_file package) @@ -30,7 +32,8 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else if (Platform.isMacOS) { await Process.run('open', ['$path/$fileName'], runInShell: true); } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); + await Process.run('xdg-open', [ + '$path/$fileName', + ], runInShell: true); } } diff --git a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart index a455ce682..6037410a9 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart @@ -1,15 +1,23 @@ ///Dart imports import 'dart:async'; import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; -///To save the Excel file in the device -///To save the Excel file in the device +// ignore: depend_on_referenced_packages +import 'package:web/web.dart'; + +// Function to save and launch a file for download in a web environment Future saveAndLaunchFile(List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); + final HTMLAnchorElement anchor = + document.createElement('a') as HTMLAnchorElement + ..href = 'data:application/octet-stream;base64,${base64Encode(bytes)}' + ..style.display = 'none' + ..download = fileName; + + // Insert the new element into the DOM + document.body!.appendChild(anchor); + + // Initiate the download + anchor.click(); + // Clean up the DOM by removing the anchor element + document.body!.removeChild(anchor); } diff --git a/packages/syncfusion_flutter_xlsio/example/lib/main.dart b/packages/syncfusion_flutter_xlsio/example/lib/main.dart index 96eba294e..fac606caa 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/main.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/main.dart @@ -1,16 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column, Alignment; +import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column; //Local imports import 'helper/save_file_mobile.dart' if (dart.library.html) 'helper/save_file_web.dart'; void main() { - runApp(CreateExcelWidget()); + runApp(const CreateExcelWidget()); } /// Represents the XlsIO widget class. class CreateExcelWidget extends StatelessWidget { + // Constructor with a named key parameter + const CreateExcelWidget({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return const MaterialApp( @@ -23,7 +26,7 @@ class CreateExcelWidget extends StatelessWidget { class CreateExcelStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreateExcelStatefulWidget] class. const CreateExcelStatefulWidget({Key? key, required this.title}) - : super(key: key); + : super(key: key); /// title. final String title; @@ -36,22 +39,20 @@ class _CreateExcelState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( - child: const Text('Generate Excel'), style: TextButton.styleFrom( - primary: Colors.white, + foregroundColor: Colors.white, backgroundColor: Colors.lightBlue, - onSurface: Colors.grey, + disabledForegroundColor: Colors.grey, ), onPressed: generateExcel, - ) + child: const Text('Generate Excel'), + ), ], ), ), diff --git a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc index e71a16d23..eb768d3a6 100644 --- a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); } diff --git a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugins.cmake index 51436ae8c..eb06039dc 100644 --- a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_file_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) @@ -13,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_xlsio/example/macos/.gitignore b/packages/syncfusion_flutter_xlsio/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/.gitignore +++ b/packages/syncfusion_flutter_xlsio/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b60..4b81f9b2d 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Release.xcconfig index c2efd0b60..5caa9d157 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Release.xcconfig +++ b/packages/syncfusion_flutter_xlsio/example/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_xlsio/example/macos/Flutter/GeneratedPluginRegistrant.swift index 7ce0a99b8..4d6f3f9e6 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_flutter_xlsio/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -2,13 +2,13 @@ // Generated file. Do not edit. // -// clang-format off - import FlutterMacOS import Foundation -import path_provider_macos +import open_file_mac +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..341828ace 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,25 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 6E554659619C45F1B4F1AEA8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B791694AD452CEAD49153077 /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 8B03C180DF20D6C18247F0F2 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34DEBC8095C3E0D324D595DE /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +63,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 013127C52F66C302FAF46652 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 0CBBA491782BC6ADC62B1E15 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* xlsio_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = xlsio_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,21 +81,45 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 34DEBC8095C3E0D324D595DE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 815A7E2380707E8FCC33D5E8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 92A28012832125CD05731EE5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 933D6E1C76BB85109AE8EBDA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + B421793FE027C1D1DADA721E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B791694AD452CEAD49153077 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B03C180DF20D6C18247F0F2 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + 6E554659619C45F1B4F1AEA8 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,15 +136,18 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + AF4828E9C6C7DE94BF2F4C5A /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* xlsio_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -145,9 +187,25 @@ path = Runner; sourceTree = ""; }; + AF4828E9C6C7DE94BF2F4C5A /* Pods */ = { + isa = PBXGroup; + children = ( + 0CBBA491782BC6ADC62B1E15 /* Pods-Runner.debug.xcconfig */, + 815A7E2380707E8FCC33D5E8 /* Pods-Runner.release.xcconfig */, + B421793FE027C1D1DADA721E /* Pods-Runner.profile.xcconfig */, + 933D6E1C76BB85109AE8EBDA /* Pods-RunnerTests.debug.xcconfig */, + 92A28012832125CD05731EE5 /* Pods-RunnerTests.release.xcconfig */, + 013127C52F66C302FAF46652 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + B791694AD452CEAD49153077 /* Pods_Runner.framework */, + 34DEBC8095C3E0D324D595DE /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -155,15 +213,36 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 038561ED10E7D213276D93B0 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + A58EFC42878FD8C455CB5AC7 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 487988B0386F5F5E200A1D65 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -171,8 +250,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* xlsio_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +263,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +297,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -233,8 +331,31 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 038561ED10E7D213276D93B0 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -270,9 +391,56 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 487988B0386F5F5E200A1D65 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A58EFC42878FD8C455CB5AC7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +454,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +479,57 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 933D6E1C76BB85109AE8EBDA /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/xlsio_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/xlsio_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 92A28012832125CD05731EE5 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/xlsio_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/xlsio_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 013127C52F66C302FAF46652 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/xlsio_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/xlsio_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +553,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +565,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +605,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +629,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +647,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +661,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +685,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +697,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +763,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +804,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..a6ea185d1 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16e..21a3cc14c 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_xlsio/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..2e210b71a 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = xlsio_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_xlsio/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_xlsio/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_flutter_xlsio/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_flutter_xlsio/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_flutter_xlsio/example/pubspec.yaml b/packages/syncfusion_flutter_xlsio/example/pubspec.yaml index 494339b5c..60ff00bd2 100644 --- a/packages/syncfusion_flutter_xlsio/example/pubspec.yaml +++ b/packages/syncfusion_flutter_xlsio/example/pubspec.yaml @@ -2,7 +2,7 @@ name: xlsio_example description: Demo for creating a Excel file using syncfusion_flutter_xlsio package. environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: diff --git a/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_flutter_xlsio/lib/src/test/async.dart b/packages/syncfusion_flutter_xlsio/lib/src/test/async.dart new file mode 100644 index 000000000..29069fe0a --- /dev/null +++ b/packages/syncfusion_flutter_xlsio/lib/src/test/async.dart @@ -0,0 +1,3035 @@ +import 'dart:ui'; + +// ignore: depend_on_referenced_packages +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/intl.dart'; + +import '../../xlsio.dart'; +import 'xlsio_workbook.dart'; + +// ignore: public_member_api_docs +void xlsioAsync() { + group('Excel Async', () { + test('AutoFilter Async', () async { + final Workbook workbook = Workbook(4); + final Worksheet sheet = workbook.worksheets[0]; + + ///Loading data for text filter + sheet.getRangeByName('A1').setText('Angela Davis'); + sheet.getRangeByName('A2').setText('aNgeLa DaViS.'); + sheet.getRangeByName('A3').setText('Enoch Powell'); + sheet.getRangeByName('A4').setText('Al-Biruni'); + sheet.getRangeByName('A5').setText('ANgeLa DAViS'); + sheet.getRangeByName('A6').setText('Will Roscoe'); + sheet.getRangeByName('A7').setText('al-biruNi'); + sheet.getRangeByName('A8').setText('Christopher Hogwood'); + sheet.getRangeByName('A9').setText('Al-BirUni'); + sheet.getRangeByName('A10').setText('KarlMarx'); + + sheet.getRangeByName('B1').setText('India'); + sheet.getRangeByName('B2').setText('America'); + sheet.getRangeByName('B3').setText('Australia'); + sheet.getRangeByName('B4').setText('Russia'); + sheet.getRangeByName('B5').setText('Canada'); + sheet.getRangeByName('B6').setText('Japan'); + sheet.getRangeByName('B7').setText('China'); + sheet.getRangeByName('B8').setText('Srilanka'); + sheet.getRangeByName('B9').setText('Africa'); + sheet.getRangeByName('B10').setText('France'); + + //Intialize text filter + sheet.autoFilters.filterRange = sheet.getRangeByName('A1:B10'); + final AutoFilter autofilter = sheet.autoFilters[0]; + autofilter.addTextFilter({'Angela Davis', 'Al-BirUni'}); + + final Worksheet sheet2 = workbook.worksheets[1]; + + ///Loading data for color filter + sheet2.getRangeByName('E1').setText('Title'); + sheet2.getRangeByName('E2').setText('Sales Representative'); + sheet2.getRangeByName('E3').setText('Owner'); + sheet2.getRangeByName('E4').setText('Owner'); + sheet2.getRangeByName('E5').setText('Sales Representative'); + sheet2.getRangeByName('E6').setText('Order Administrator'); + sheet2.getRangeByName('E7').setText('Sales Representative'); + sheet2.getRangeByName('E8').setText('Marketing Manager'); + sheet2.getRangeByName('E9').setText('Owner'); + sheet2.getRangeByName('E10').setText('Owner'); + + sheet2.getRangeByName('F1').setText('DOJ'); + sheet2.getRangeByName('F2').dateTime = DateTime(2006, 9, 10); + sheet2.getRangeByName('F3').dateTime = DateTime(2000, 6, 10); + sheet2.getRangeByName('F4').dateTime = DateTime(2002, 9, 18); + sheet2.getRangeByName('F5').dateTime = DateTime(2009, 5, 23); + sheet2.getRangeByName('F6').dateTime = DateTime(2012, 1, 6); + sheet2.getRangeByName('F7').dateTime = DateTime(2007, 7, 19); + sheet2.getRangeByName('F8').dateTime = DateTime(2008, 6, 30); + sheet2.getRangeByName('F9').dateTime = DateTime(2002, 4, 16); + sheet2.getRangeByName('F10').dateTime = DateTime(2008, 11, 29); + + sheet2.getRangeByName('G1').setText('City'); + sheet2.getRangeByName('G2').setText('Berlin'); + sheet2.getRangeByName('G3').setText('México D.F.'); + sheet2.getRangeByName('G4').setText('México D.F.'); + sheet2.getRangeByName('G5').setText('London'); + sheet2.getRangeByName('G6').setText('Luleå'); + sheet2.getRangeByName('G7').setText('Mannheim'); + sheet2.getRangeByName('G8').setText('Strasbourg'); + sheet2.getRangeByName('G9').setText('Madrid'); + sheet2.getRangeByName('G10').setText('Marseille'); + + sheet2.getRangeByName('E2').cellStyle.backColor = '#008000'; + sheet2.getRangeByName('E3').cellStyle.backColor = '#0000FF'; + sheet2.getRangeByName('E4').cellStyle.backColor = '#FF0000'; + sheet2.getRangeByName('E5').cellStyle.backColor = '#FF0000'; + sheet2.getRangeByName('E6').cellStyle.backColor = '#FFFFFF'; + sheet2.getRangeByName('E7').cellStyle.backColor = '#FF0000'; + sheet2.getRangeByName('E8').cellStyle.backColor = '#FFFFFF'; + sheet2.getRangeByName('E9').cellStyle.backColor = '#0000FF'; + sheet2.getRangeByName('E10').cellStyle.backColor = '#008000'; + + sheet2.getRangeByName('G2').cellStyle.fontColor = '#FF0000'; + sheet2.getRangeByName('G3').cellStyle.fontColor = '#008000'; + sheet2.getRangeByName('G4').cellStyle.fontColor = '#0000FF'; + sheet2.getRangeByName('G5').cellStyle.fontColor = '#000000'; + sheet2.getRangeByName('G6').cellStyle.fontColor = '#FF0000'; + sheet2.getRangeByName('G7').cellStyle.fontColor = '#008000'; + sheet2.getRangeByName('G8').cellStyle.fontColor = '#0000FF'; + sheet2.getRangeByName('G9').cellStyle.fontColor = '#000000'; + sheet2.getRangeByName('G10').cellStyle.fontColor = '#FF0000'; + + //Intialize Filter Range + sheet2.autoFilters.filterRange = sheet2.getRangeByName('E1:G10'); + final AutoFilter autofilter2 = sheet2.autoFilters[2]; + autofilter2.addColorFilter('#0000FF', ExcelColorFilterType.fontColor); + sheet2.getRangeByName('E1:G10').autoFitColumns(); + + ///Loading data for number fulter + final Worksheet sheet3 = workbook.worksheets[2]; + + sheet3.getRangeByName('A1').setNumber(10); + sheet3.getRangeByName('A2').setNumber(15); + sheet3.getRangeByName('A3').setNumber(15.4); + sheet3.getRangeByName('A4').setNumber(20.4567678); + sheet3.getRangeByName('A5').setNumber(10); + sheet3.getRangeByName('A6').setNumber(20.00087788767667657577557007); + sheet3.getRangeByName('A7').setNumber(233); + sheet3.getRangeByName('A8').setNumber(10); + sheet3.getRangeByName('A9').setNumber(10.01); + sheet3.getRangeByName('A10').setNumber(9.99); + + sheet3.getRangeByName('B1').setText('Angela Davis'); + sheet3.getRangeByName('B2').setText('Sigmund Freud.'); + sheet3.getRangeByName('B3').setText('Enoch Powell'); + sheet3.getRangeByName('B4').setText('Al-Biruni'); + sheet3.getRangeByName('B5').setText('Joseph Campbell'); + sheet3.getRangeByName('B6').setText('Will Roscoe'); + sheet3.getRangeByName('B7').setText('Barry Bishop'); + sheet3.getRangeByName('B8').setText('Christopher Hogwood'); + sheet3.getRangeByName('B9').setText('Cornel West'); + sheet3.getRangeByName('B10').setText('KarlMarx'); + + //Intialize Filter Range + sheet3.autoFilters.filterRange = sheet3.getRangeByName('A1:C10'); + final AutoFilter filter = sheet3.autoFilters[0]; + final AutoFilterCondition firstCondition = filter.firstCondition; + firstCondition.conditionOperator = ExcelFilterCondition.equal; + firstCondition.numberValue = 10; + + //Loading data for datetime fulter + final Worksheet sheet4 = workbook.worksheets[3]; + + sheet4.getRangeByName('A1').setText('Title'); + sheet4.getRangeByName('A2').setText('Sales Representative'); + sheet4.getRangeByName('A3').setText('Owner'); + sheet4.getRangeByName('A4').setText('Owner'); + sheet4.getRangeByName('A5').setText('Sales Representative'); + sheet4.getRangeByName('A6').setText('Order Administrator'); + sheet4.getRangeByName('A7').setText('Sales Representative'); + sheet4.getRangeByName('A8').setText('Marketing Manager'); + sheet4.getRangeByName('A9').setText('Owner'); + sheet4.getRangeByName('A10').setText('Owner'); + + sheet4.getRangeByName('B1').setText('DOJ'); + sheet4.getRangeByName('B2').dateTime = DateTime(2006, 9, 10); + sheet4.getRangeByName('B3').dateTime = DateTime(2000, 6, 10); + sheet4.getRangeByName('B4').dateTime = DateTime(2002, 9, 18); + sheet4.getRangeByName('B5').dateTime = DateTime(2009, 5, 23); + sheet4.getRangeByName('B6').dateTime = DateTime(2012, 1, 6); + sheet4.getRangeByName('B7').dateTime = DateTime(2007, 7, 19); + sheet4.getRangeByName('B8').dateTime = DateTime(2008, 6, 30); + sheet4.getRangeByName('B9').dateTime = DateTime(2002, 4, 16); + sheet4.getRangeByName('B10').dateTime = DateTime(2008, 11, 29); + + sheet4.getRangeByName('C1').setText('City'); + sheet4.getRangeByName('C2').setText('Berlin'); + sheet4.getRangeByName('C3').setText('México D.F.'); + sheet4.getRangeByName('C4').setText('México D.F.'); + sheet4.getRangeByName('C5').setText('London'); + sheet4.getRangeByName('C6').setText('Luleå'); + sheet4.getRangeByName('C7').setText('Mannheim'); + sheet4.getRangeByName('C8').setText('Strasbourg'); + sheet4.getRangeByName('C9').setText('Madrid'); + sheet4.getRangeByName('C10').setText('Marseille'); + + //Intialize Filter Range + sheet4.autoFilters.filterRange = sheet4.getRangeByName('A1:C10'); + final AutoFilter autofilter3 = sheet4.autoFilters[1]; + autofilter3.addDateFilter(DateTime(2002), DateTimeFilterType.year); + + //saving Sheet + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'AutoFilterAsync.xlsx'); + workbook.dispose(); + }); + test('ConditionalFormats Async', () async { + final Workbook workbook = Workbook(3); + + // Conditional Formats for data bar + final Worksheet sheet = workbook.worksheets[0]; + + sheet.getRangeByName('A1').number = 1277; + sheet.getRangeByName('A2').number = 1003; + sheet.getRangeByName('A3').number = 1105; + sheet.getRangeByName('A4').number = 952; + sheet.getRangeByName('A5').number = 770; + sheet.getRangeByName('A6').number = 621; + sheet.getRangeByName('A7').number = 1311; + sheet.getRangeByName('A8').number = 730; + + final ConditionalFormats conditionalFormats = + sheet.getRangeByName('A1:A8').conditionalFormats; + final ConditionalFormat conditionalFormat = + conditionalFormats.addCondition(); + + conditionalFormat.formatType = ExcelCFType.dataBar; + final DataBar dataBar = conditionalFormat.dataBar!; + + dataBar.minPoint.type = ConditionValueType.lowestValue; + dataBar.maxPoint.type = ConditionValueType.highestValue; + + dataBar.barColor = '#9CD0F3'; + + dataBar.showValue = false; + + // Conditional Formats for color scale + final Worksheet sheet2 = workbook.worksheets[1]; + + sheet2.getRangeByName('A1').number = 12; + sheet2.getRangeByName('A2').number = 29; + sheet2.getRangeByName('A3').number = 41; + sheet2.getRangeByName('A4').number = 84; + sheet2.getRangeByName('A5').number = 90; + sheet2.getRangeByName('A6').number = 112; + sheet2.getRangeByName('A7').number = 131; + sheet2.getRangeByName('A8').number = 20; + sheet2.getRangeByName('A9').number = 54; + sheet2.getRangeByName('A10').number = 63; + + final ConditionalFormats conditionalFormats2 = + sheet2.getRangeByName('A1:A10').conditionalFormats; + final ConditionalFormat conditionalFormat2 = + conditionalFormats2.addCondition(); + + conditionalFormat2.formatType = ExcelCFType.colorScale; + final ColorScale colorScale = conditionalFormat2.colorScale!; + + colorScale.setConditionCount(2); + colorScale.criteria[0].formatColor = '#63BE7B'; + colorScale.criteria[0].type = ConditionValueType.lowestValue; + + colorScale.criteria[1].formatColor = '#FFEF9C'; + colorScale.criteria[1].type = ConditionValueType.highestValue; + + // Conditional Formats for icon set + final Worksheet sheet3 = workbook.worksheets[2]; + + sheet3.getRangeByName('A1').number = 98; + sheet3.getRangeByName('A2').number = 89; + sheet3.getRangeByName('A3').number = 13; + sheet3.getRangeByName('A4').number = 78; + sheet3.getRangeByName('A5').number = 68; + sheet3.getRangeByName('A6').number = 47; + sheet3.getRangeByName('A7').number = 34; + sheet3.getRangeByName('A8').number = 21; + sheet3.getRangeByName('A9').number = 53; + sheet3.getRangeByName('A10').number = 08; + + final ConditionalFormats conditions = + sheet3.getRangeByName('A1:A10').conditionalFormats; + final ConditionalFormat condition = conditions.addCondition(); + + condition.formatType = ExcelCFType.iconSet; + final IconSet iconSet = condition.iconSet!; + iconSet.iconSet = ExcelIconSetType.fourRating; + iconSet.iconCriteria[1].type = ConditionValueType.percent; + iconSet.iconCriteria[1].value = '40'; + iconSet.iconCriteria[2].type = ConditionValueType.percent; + iconSet.iconCriteria[2].value = '60'; + iconSet.iconCriteria[3].type = ConditionValueType.percent; + iconSet.iconCriteria[3].value = '80'; + iconSet.showIconOnly = true; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ConditionalFormatsAsync.xlsx'); + workbook.dispose(); + }); + test('Excel BuildInStyle Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + // Good, Bad, and Neutral + final Range range1 = sheet.getRangeByIndex(1, 1); + range1.number = 4; + range1.builtInStyle = BuiltInStyles.bad; + + final Range range2 = sheet.getRangeByName('A2'); + range2.text = 'M'; + range2.builtInStyle = BuiltInStyles.good; + + final Range range3 = sheet.getRangeByName('A3'); + range3.text = 'Zee'; + range3.builtInStyle = BuiltInStyles.neutral; + // Excel BuildInStyle NumberFormat + final Range range4 = sheet.getRangeByName('B1'); + range4.number = 44; + range4.builtInStyle = BuiltInStyles.comma0; + + final Range range5 = sheet.getRangeByName('B2'); + range5.number = 444; + range5.builtInStyle = BuiltInStyles.currency; + + final Range range6 = sheet.getRangeByName('B3'); + range6.number = 4444; + range6.builtInStyle = BuiltInStyles.currency0; + + final Range range7 = sheet.getRangeByName('B4'); + range7.number = 4; + range7.builtInStyle = BuiltInStyles.percent; + // Data and Model + final Range range8 = sheet.getRangeByIndex(1, 3); + range8.number = 22; + range8.builtInStyle = BuiltInStyles.calculation; + + final Range range9 = sheet.getRangeByName('C2'); + range9.text = 'Hai'; + range9.builtInStyle = BuiltInStyles.checkCell; + + final Range range10 = sheet.getRangeByName('C3'); + range10.text = 'Jumbo'; + range10.builtInStyle = BuiltInStyles.explanatoryText; + + final Range range11 = sheet.getRangeByIndex(4, 3); + range11.number = 44; + range11.builtInStyle = BuiltInStyles.input; + + final Range range12 = sheet.getRangeByIndex(5, 3); + range12.text = 'MJ'; + range12.builtInStyle = BuiltInStyles.linkedCell; + + final Range range13 = sheet.getRangeByIndex(6, 3); + range13.setNumber(-40); + range13.builtInStyle = BuiltInStyles.note; + + final Range range14 = sheet.getRangeByIndex(7, 3); + range14.setText('zeee'); + range14.builtInStyle = BuiltInStyles.output; + + final Range range15 = sheet.getRangeByIndex(8, 3); + range15.text = 'Wrong!'; + range15.builtInStyle = BuiltInStyles.warningText; + // Titles and Heading + final Range range16 = sheet.getRangeByIndex(1, 4); + range16.text = 'Time'; + range16.builtInStyle = BuiltInStyles.heading1; + + final Range range17 = sheet.getRangeByName('D2'); + range17.text = 'POWER'; + range17.builtInStyle = BuiltInStyles.heading2; + + final Range range18 = sheet.getRangeByName('D3'); + range18.text = 'TriAngle'; + range18.builtInStyle = BuiltInStyles.heading3; + + final Range range19 = sheet.getRangeByIndex(4, 4); + range19.number = 1000; + range19.builtInStyle = BuiltInStyles.heading4; + + final Range range20 = sheet.getRangeByIndex(5, 4); + range20.text = 'Man'; + range20.builtInStyle = BuiltInStyles.title; + + final Range range21 = sheet.getRangeByIndex(6, 4); + range21.setNumber(200); + range21.builtInStyle = BuiltInStyles.total; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelBuildInStyleAsync.xlsx'); + workbook.dispose(); + }); + test('Freezepane Async', () async { + //Creating a workbook. + final Workbook workbook = Workbook(0); + //Adding a Sheet with name to workbook. + final Worksheet sheet = workbook.worksheets.addWithName('Table'); + + //Load data + sheet.getRangeByName('A1').setText('Products'); + sheet.getRangeByName('B1').setText('Qtr1'); + sheet.getRangeByName('C1').setText('Qtr2'); + sheet.getRangeByName('D1').setText('Qtr3'); + sheet.getRangeByName('E1').setText('Qtr4'); + + sheet.getRangeByName('A2').setText('Phone Holder'); + sheet.getRangeByName('B2').setNumber(744.6); + sheet.getRangeByName('C2').setNumber(162.56); + sheet.getRangeByName('D2').setNumber(5079.6); + sheet.getRangeByName('E2').setNumber(1249.2); + + sheet.getRangeByName('A3').setText('Digital Picture Frame'); + sheet.getRangeByName('B3').setNumber(5079.6); + sheet.getRangeByName('C3').setNumber(1249.2); + sheet.getRangeByName('D3').setNumber(943.89); + sheet.getRangeByName('E3').setNumber(349.6); + + sheet.getRangeByName('A4').setText('USB Charging Cable'); + sheet.getRangeByName('B4').setNumber(1267.5); + sheet.getRangeByName('C4').setNumber(1062.5); + sheet.getRangeByName('D4').setNumber(744.6); + sheet.getRangeByName('E4').setNumber(162.56); + + sheet.getRangeByName('A5').setText('Selfie Stick Tripod'); + sheet.getRangeByName('B5').setNumber(1418); + sheet.getRangeByName('C5').setNumber(756); + sheet.getRangeByName('D5').setNumber(1267.5); + sheet.getRangeByName('E5').setNumber(1062.5); + + sheet.getRangeByName('A6').setText('MicroSD Card'); + sheet.getRangeByName('B6').setNumber(4728); + sheet.getRangeByName('C6').setNumber(4547.92); + sheet.getRangeByName('D6').setNumber(1418); + sheet.getRangeByName('E6').setNumber(756); + + sheet.getRangeByName('A7').setText('HDMI Cable'); + sheet.getRangeByName('B7').setNumber(943.89); + sheet.getRangeByName('C7').setNumber(349.6); + sheet.getRangeByName('D7').setNumber(4728); + sheet.getRangeByName('E7').setNumber(4547.92); + + sheet.getRangeByName('A8').setText('Key Finder'); + sheet.getRangeByName('B8').setNumber(149.33); + sheet.getRangeByName('C8').setNumber(642.04); + sheet.getRangeByName('D8').setNumber(1249.83); + sheet.getRangeByName('E8').setNumber(7850.1); + + sheet.getRangeByName('A9').setText('Light Stand'); + sheet.getRangeByName('B9').setNumber(6534.22); + sheet.getRangeByName('C9').setNumber(3201.95); + sheet.getRangeByName('D9').setNumber(1002.25); + sheet.getRangeByName('E9').setNumber(124.60); + + sheet.getRangeByName('A10').setText('External Hard Drive'); + sheet.getRangeByName('B10').setNumber(245.45); + sheet.getRangeByName('C10').setNumber(955.2); + sheet.getRangeByName('D10').setNumber(4655.99); + sheet.getRangeByName('E10').setNumber(8745.45); + + sheet.getRangeByName('A11').setText('Laptop'); + sheet.getRangeByName('B11').setNumber(450.105); + sheet.getRangeByName('C11').setNumber(1049.54); + sheet.getRangeByName('D11').setNumber(1248.35); + sheet.getRangeByName('E11').setNumber(204.1); + + sheet.getRangeByName('A12').setText('Graphic Card'); + sheet.getRangeByName('B12').setNumber(764.77); + sheet.getRangeByName('C12').setNumber(955.2); + sheet.getRangeByName('D12').setNumber(100.4); + sheet.getRangeByName('E12').setNumber(8383.9); + + sheet.getRangeByName('A13').setText('Keyboard'); + sheet.getRangeByName('B13').setNumber(943.89); + sheet.getRangeByName('C13').setNumber(349.6); + sheet.getRangeByName('D13').setNumber(4728); + sheet.getRangeByName('E13').setNumber(4547.92); + + sheet.getRangeByName('A14').setText('Mouse'); + sheet.getRangeByName('B14').setNumber(234.33); + sheet.getRangeByName('C14').setNumber(982.6); + sheet.getRangeByName('D14').setNumber(719.7); + sheet.getRangeByName('E14').setNumber(7249); + + sheet.getRangeByName('A15').setText('Webcam'); + sheet.getRangeByName('B15').setNumber(749.11); + sheet.getRangeByName('C15').setNumber(349.2); + sheet.getRangeByName('D15').setNumber(743.09); + sheet.getRangeByName('E15').setNumber(564.97); + + sheet.getRangeByName('A16').setText('eBook Reader'); + sheet.getRangeByName('B16').setNumber(3217.04); + sheet.getRangeByName('C16').setNumber(652.5); + sheet.getRangeByName('D16').setNumber(842.6); + sheet.getRangeByName('E16').setNumber(242.56); + + sheet.getRangeByName('A17').setText('GPS Navigator'); + sheet.getRangeByName('B17').setNumber(843.5); + sheet.getRangeByName('C17').setNumber(619.3); + sheet.getRangeByName('D17').setNumber(167.56); + sheet.getRangeByName('E17').setNumber(189.5); + + sheet.getRangeByName('A18').setText('Monitor'); + sheet.getRangeByName('B18').setNumber(952.8); + sheet.getRangeByName('C18').setNumber(4547); + sheet.getRangeByName('D18').setNumber(1418); + sheet.getRangeByName('E18').setNumber(756); + + sheet.getRangeByName('A19').setText('Laptop Stand'); + sheet.getRangeByName('B19').setNumber(413.89); + sheet.getRangeByName('C19').setNumber(349.6); + sheet.getRangeByName('D19').setNumber(4728); + sheet.getRangeByName('E19').setNumber(4547.92); + + sheet.getRangeByName('A20').setText('Smartwatch'); + sheet.getRangeByName('B20').setNumber(948.07); + sheet.getRangeByName('C20').setNumber(642.04); + sheet.getRangeByName('D20').setNumber(1249.83); + sheet.getRangeByName('E20').setNumber(7850.1); + + sheet.getRangeByName('A21').setText('Bluetooth Earphones'); + sheet.getRangeByName('B21').setNumber(1134); + sheet.getRangeByName('C21').setNumber(3201.95); + sheet.getRangeByName('D21').setNumber(1002.25); + sheet.getRangeByName('E21').setNumber(124.60); + + sheet.getRangeByName('A22').setText('Reader Case'); + sheet.getRangeByName('B22').setNumber(865.76); + sheet.getRangeByName('C22').setNumber(955.2); + sheet.getRangeByName('D22').setNumber(4655.99); + sheet.getRangeByName('E22').setNumber(8745.45); + + sheet.getRangeByName('A23').setText('Smart Glass'); + sheet.getRangeByName('B23').setNumber(1450.5); + sheet.getRangeByName('C23').setNumber(1049.54); + sheet.getRangeByName('D23').setNumber(1248.35); + sheet.getRangeByName('E23').setNumber(204.1); + + sheet.getRangeByName('A24').setText('Remote Controller'); + sheet.getRangeByName('B24').setNumber(877.75); + sheet.getRangeByName('C24').setNumber(955.2); + sheet.getRangeByName('D24').setNumber(100.4); + sheet.getRangeByName('E24').setNumber(8383.9); + + sheet.getRangeByName('A25').setText('Game Console'); + sheet.getRangeByName('B25').setNumber(343.89); + sheet.getRangeByName('C25').setNumber(349.6); + sheet.getRangeByName('D25').setNumber(4728); + sheet.getRangeByName('E25').setNumber(447.92); + + ///Create a table with the data in a range + final ExcelTable table = sheet.tableCollection + .create('Table1', sheet.getRangeByName('A1:E25')); + + ///Formatting table with a built-in style + table.builtInTableStyle = ExcelTableBuiltInStyle.tableStyleMedium9; + + table.showTotalRow = true; + table.showFirstColumn = true; + table.showBandedColumns = true; + table.showBandedRows = true; + table.columns[0].totalRowLabel = 'Total'; + table.columns[1].totalFormula = ExcelTableTotalFormula.sum; + table.columns[2].totalFormula = ExcelTableTotalFormula.sum; + table.columns[3].totalFormula = ExcelTableTotalFormula.sum; + table.columns[4].totalFormula = ExcelTableTotalFormula.sum; + + final Range range = sheet.getRangeByName('B2:E26'); + range.numberFormat = r'_($* #,##0.00_)'; + + sheet.getRangeByName('A1:E25').autoFitColumns(); + //Freezepane + sheet.getRangeByName('A2').freezePanes(); + + //Save and dispose Workbook + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FreezepaneAsync.xlsx'); + workbook.dispose(); + }); + test('Excel PageSetup Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + final Style style = workbook.styles.add('style'); + style.fontColor = '#C67878'; + final Range range = sheet.getRangeByName('A1:D4'); + range.text = 'BlackWhite'; + range.cellStyle = style; + // Blackandwhite + sheet.pageSetup.isBlackAndWhite = true; + // grid line + sheet.pageSetup.showGridlines = true; + // Print headings + sheet.pageSetup.showHeadings = true; + // Center Horizontally and center Vertically + sheet.pageSetup.isCenterHorizontally = true; + sheet.pageSetup.isCenterVertically = true; + // landscape + sheet.pageSetup.orientation = ExcelPageOrientation.landscape; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'PageSetupAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Named range Async', () async { + final Workbook workbook = Workbook(2); + final Worksheet sheet1 = workbook.worksheets[0]; + final Worksheet sheet2 = workbook.worksheets[1]; + sheet1.getRangeByName('A1:G1').number = 10; + sheet1.getRangeByName('A2:G2').number = 20; + sheet1.getRangeByName('A3:G3').number = 25; + final Range range1 = sheet1.getRangeByName('A1'); + final Range range2 = sheet1.getRangeByName('A2'); + final Range range3 = sheet1.getRangeByName('A3'); + workbook.names.add('NumberOne', range1); + workbook.names.add('NumberTwo', range2); + workbook.names.add('NumberThree', range3); + final Range range4 = sheet1.getRangeByName('B1:B3'); + workbook.names.add('namedRange', range4); + final Range range5 = sheet1.getRangeByName('C1'); + final Range range6 = sheet1.getRangeByName('C2'); + workbook.names.add('FirstNumber', range5); + workbook.names.add('SecondNumber', range6); + // Formula + final Range rangeFormula1 = sheet1.getRangeByIndex(4, 1); + rangeFormula1.formula = '=NumberOne-NumberTwo+NumberThree'; + final Range rangeFormula2 = sheet1.getRangeByIndex(4, 2); + rangeFormula2.formula = '=SUM(namedRange)'; + final Range rangeFormula3 = sheet1.getRangeByIndex(4, 3); + rangeFormula3.formula = '=FirstNumber>SecondNumber'; + final Range rangeFormula4 = sheet1.getRangeByIndex(4, 4); + rangeFormula4.formula = '=IF(FirstNumber bytes = await workbook.save(); + saveAsExcel(bytes, 'NamedRangeAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Text async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + final Range range = sheet.getRangeByIndex(1, 1); + range.text = 'M\nM'; + final Range range1 = sheet.getRangeByName('A2'); + range1.text = 'M\tJ'; + final Range range2 = sheet.getRangeByIndex(3, 1); + range2.text = '--'; + final Range range3 = sheet.getRangeByName('A4'); + range3.text = '#'; + final Range range4 = sheet.getRangeByName('A5'); + range4.text = '|'; + final Range range5 = sheet.getRangeByIndex(6, 1); + range5.text = r'\\'; + final Range range6 = sheet.getRangeByIndex(7, 1); + range6.text = "'"; + final Range range7 = sheet.getRangeByName('A8'); + range7.text = '@'; + final Range range8 = sheet.getRangeByIndex(9, 1); + range8.text = '!'; + final Range range9 = sheet.getRangeByName('A10'); + range9.text = r'$'; + final Range range10 = sheet.getRangeByName('A11'); + range10.text = '%'; + final Range range11 = sheet.getRangeByIndex(12, 1); + range11.text = '^'; + final Range range12 = sheet.getRangeByName('A13'); + range12.text = '&'; + final Range range13 = sheet.getRangeByIndex(14, 1); + range13.text = '*'; + final Range range14 = sheet.getRangeByName('A15'); + range14.text = '('; + final Range range15 = sheet.getRangeByIndex(16, 1); + range15.text = ')'; + final Range range16 = sheet.getRangeByIndex(17, 1); + range16.text = '+'; + final Range range17 = sheet.getRangeByName('A18'); + range17.text = '}'; + final Range range18 = sheet.getRangeByIndex(19, 1); + range18.text = ']'; + final Range range19 = sheet.getRangeByName('A20'); + range19.text = '{'; + final Range range20 = sheet.getRangeByName('A21'); + range20.text = '['; + final Range range21 = sheet.getRangeByIndex(22, 1); + range21.text = '"'; + final Range range22 = sheet.getRangeByName('A23'); + range22.text = ':'; + final Range range23 = sheet.getRangeByIndex(24, 1); + range23.text = ';'; + final Range range24 = sheet.getRangeByName('A25'); + range24.text = '?'; + final Range range25 = sheet.getRangeByIndex(26, 1); + range25.text = '/'; + final Range range26 = sheet.getRangeByIndex(27, 1); + range26.text = '>'; + final Range range27 = sheet.getRangeByName('A28'); + range27.text = '.'; + final Range range28 = sheet.getRangeByIndex(29, 1); + range28.text = '<'; + final Range range29 = sheet.getRangeByName('A30'); + range29.text = ','; + final Range range30 = sheet.getRangeByName('A31'); + range30.text = '`'; + final Range range31 = sheet.getRangeByName('A32'); + range31.text = '~'; + final Range range32 = sheet.getRangeByIndex(33, 1); + range32.text = '='; + final Range range33 = sheet.getRangeByName('A34'); + range33.text = '_'; + final Range range34 = sheet.getRangeByName('A35'); + range34.text = '0'; + final Range range35 = sheet.getRangeByIndex(36, 1); + range35.text = '4'; + final Range range36 = sheet.getRangeByIndex(37, 1); + range36.text = '44'; + final Range range37 = sheet.getRangeByName('A38'); + range37.text = '444'; + final Range range38 = sheet.getRangeByIndex(39, 1); + range38.text = 'a'; + final Range range39 = sheet.getRangeByName('A40'); + range39.text = 'b'; + final Range range40 = sheet.getRangeByName('A41'); + range40.text = 'c'; + final Range range41 = sheet.getRangeByName('A42'); + range41.text = 'd'; + final Range range42 = sheet.getRangeByIndex(43, 1); + range42.text = 'e'; + final Range range43 = sheet.getRangeByName('A44'); + range43.text = 'f'; + final Range range44 = sheet.getRangeByName('A45'); + range44.text = 'g'; + final Range range45 = sheet.getRangeByIndex(46, 1); + range45.text = 'h'; + final Range range46 = sheet.getRangeByIndex(47, 1); + range46.text = 'i'; + final Range range47 = sheet.getRangeByName('A48'); + range47.text = 'j'; + final Range range48 = sheet.getRangeByIndex(49, 1); + range48.text = 'k'; + final Range range49 = sheet.getRangeByName('A50'); + range49.text = 'l'; + final Range range50 = sheet.getRangeByName('A51'); + range50.text = 'm'; + final Range range51 = sheet.getRangeByName('A52'); + range51.text = 'n'; + final Range range52 = sheet.getRangeByIndex(53, 1); + range52.text = 'o'; + final Range range53 = sheet.getRangeByName('A54'); + range53.text = 'p'; + final Range range54 = sheet.getRangeByName('A55'); + range54.text = 'q'; + final Range range55 = sheet.getRangeByIndex(56, 1); + range55.text = 'r'; + final Range range56 = sheet.getRangeByIndex(57, 1); + range56.text = 's'; + final Range range57 = sheet.getRangeByName('A58'); + range57.text = 't'; + final Range range58 = sheet.getRangeByIndex(59, 1); + range58.text = 'u'; + final Range range59 = sheet.getRangeByName('A60'); + range59.text = 'v'; + final Range range60 = sheet.getRangeByName('A61'); + range60.text = 'w'; + final Range range61 = sheet.getRangeByName('A62'); + range61.text = 'x'; + final Range range62 = sheet.getRangeByIndex(63, 1); + range62.text = 'y'; + final Range range63 = sheet.getRangeByName('A64'); + range63.text = 'z'; + final Range range64 = sheet.getRangeByName('A65'); + range64.text = 'A'; + final Range range65 = sheet.getRangeByIndex(66, 1); + range65.text = 'B'; + final Range range66 = sheet.getRangeByIndex(67, 1); + range66.text = 'C'; + final Range range67 = sheet.getRangeByName('A68'); + range67.text = 'D'; + final Range range68 = sheet.getRangeByIndex(69, 1); + range68.text = 'E'; + final Range range69 = sheet.getRangeByName('A70'); + range69.text = 'F'; + final Range range70 = sheet.getRangeByName('A71'); + range70.text = 'G'; + final Range range71 = sheet.getRangeByName('A72'); + range71.text = 'H'; + final Range range72 = sheet.getRangeByIndex(73, 1); + range72.text = 'I'; + final Range range73 = sheet.getRangeByName('A74'); + range73.text = 'J'; + final Range range74 = sheet.getRangeByName('A75'); + range74.text = 'K'; + final Range range75 = sheet.getRangeByIndex(76, 1); + range75.text = 'L'; + final Range range76 = sheet.getRangeByIndex(77, 1); + range76.text = 'M'; + final Range range77 = sheet.getRangeByName('A78'); + range77.text = 'N'; + final Range range78 = sheet.getRangeByIndex(79, 1); + range78.text = 'O'; + final Range range79 = sheet.getRangeByName('A80'); + range79.text = 'P'; + final Range range80 = sheet.getRangeByName('A81'); + range80.text = 'Q'; + final Range range81 = sheet.getRangeByName('A82'); + range81.text = 'R'; + final Range range82 = sheet.getRangeByIndex(83, 1); + range82.text = 'S'; + final Range range83 = sheet.getRangeByName('A84'); + range83.text = 'T'; + final Range range84 = sheet.getRangeByName('A85'); + range84.text = 'U'; + final Range range85 = sheet.getRangeByIndex(86, 1); + range85.text = 'V'; + final Range range86 = sheet.getRangeByIndex(87, 1); + range86.text = 'W'; + final Range range87 = sheet.getRangeByName('A88'); + range87.text = 'X'; + final Range range88 = sheet.getRangeByIndex(89, 1); + range88.text = 'Y'; + final Range range89 = sheet.getRangeByName('A90'); + range89.text = 'Z'; + final Range range90 = sheet.getRangeByName('A91'); + range90.text = '0'; + final Range range91 = sheet.getRangeByName('A92'); + range91.text = '1'; + final Range range92 = sheet.getRangeByIndex(93, 1); + range92.text = '2'; + final Range range93 = sheet.getRangeByName('A94'); + range93.text = '3'; + final Range range94 = sheet.getRangeByName('A95'); + range94.text = '4'; + final Range range95 = sheet.getRangeByIndex(96, 1); + range95.text = '5'; + final Range range96 = sheet.getRangeByIndex(97, 1); + range96.text = '6'; + final Range range97 = sheet.getRangeByName('A98'); + range97.text = '7'; + final Range range98 = sheet.getRangeByIndex(99, 1); + range98.text = '8'; + final Range range99 = sheet.getRangeByName('A100'); + range99.text = '9'; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'TextAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Number Async', () async { + final Workbook workbook = Workbook(2); + final Worksheet sheet = workbook.worksheets[1]; + // number for single range + final Range range = sheet.getRangeByIndex(1, 1); + range.number = -100; + // number for multiple range + final Range range1 = sheet.getRangeByIndex(2, 1, 2, 2); + range1.number = 26; + // number with Name Index + final Range range2 = sheet.getRangeByName('A3'); + range2.number = 100000; + // number for Multiple final Range with Name Index + final Range range3 = sheet.getRangeByName('A4:C4'); + range3.number = -1000; + // number for single range + final Range range4 = sheet.getRangeByIndex(1, 1); + range4.setNumber(-10); + // number for multiple range + final Range range5 = sheet.getRangeByIndex(2, 1, 2, 2); + range5.setNumber(26); + // number with Name Index + final Range range6 = sheet.getRangeByName('A3'); + range6.setNumber(100000); + // number for Multiple final Range with Name Index + final Range range7 = sheet.getRangeByName('A4:C4'); + range7.setNumber(-1000); + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'NumberAsync.xlsx'); + workbook.dispose(); + }); + test('Excel DateTime Async', () async { + final Workbook workbook = Workbook(2); + final Worksheet sheet = workbook.worksheets[0]; + // setDateTime() for single and Multiple Range + final Range range = sheet.getRangeByIndex(1, 1); + range.setDateTime(DateTime(2004, 12, 24, 18, 30, 50)); + final Range range1 = sheet.getRangeByIndex(4, 1, 4, 4); + range1.setDateTime(DateTime(1990, 3, 25, 16, 50, 40)); + final Range range2 = sheet.getRangeByName('J15:M20'); + range2.setDateTime(DateTime.now()); + final Range range3 = sheet.getRangeByName('O2:R6'); + range3.setDateTime(DateTime(2002, 5, 5, 22, 49, 20)); + // dateTime() for single and Multiple Range + final Range range4 = sheet.getRangeByIndex(2, 1); + range4.dateTime = DateTime(2011, 1, 20, 20, 37, 80); + final Range range5 = sheet.getRangeByIndex(3, 1, 3, 4); + range5.dateTime = DateTime(1999, 6, 5, 6, 0, 40); + final Range range6 = sheet.getRangeByName('K20:N30'); + range6.dateTime = DateTime.now(); + final Range range7 = sheet.getRangeByName('S12:V16'); + range7.dateTime = DateTime(2323, 10, 15, 2, 9); + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelDateTimeAsync.xlsx'); + workbook.dispose(); + }); + test('Excel DisplayText Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + final Range range = sheet.getRangeByIndex(1, 1); + range.numberFormat = 'h:mm:ss AM/PM'; + range.setDateTime(DateTime(2020, 8, 23, 10, 15, 20)); + // ignore: unused_local_variable + String text = range.displayText; + final Range range1 = sheet.getRangeByIndex(1, 2); + range1.numberFormat = 'h:mm'; + range1.setDateTime(DateTime(2020, 08, 23, 8, 15, 20)); + text = range1.displayText; + final Range range2 = sheet.getRangeByIndex(1, 3); + range2.numberFormat = 'mm:ss.0'; + range2.setDateTime(DateTime(2020, 08, 23, 8, 15, 20)); + text = range2.displayText; + final Range range3 = sheet.getRangeByIndex(1, 4); + range3.numberFormat = '[h]:mm:ss'; + range3.setDateTime(DateTime(2020, 08, 23, 8, 15, 20)); + text = range3.displayText; + final Range range4 = sheet.getRangeByIndex(1, 5); + range4.numberFormat = 'h.mm'; + range4.setDateTime(DateTime(2011, 04, 04, 12, 47, 6)); + text = range4.displayText; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'DisplayTextAsync.xlsx'); + workbook.dispose(); + }); + test('Excel NumberFormat Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + final Range range1 = sheet.getRangeByName('A1'); + range1.number = 100; + range1.numberFormat = '0'; + + final Range range2 = sheet.getRangeByIndex(3, 1); + range2.number = 10; + range2.numberFormat = '0.00'; + + final Range range3 = sheet.getRangeByName('A5'); + range3.number = 44; + range3.numberFormat = '#,##0'; + + final Range range4 = sheet.getRangeByIndex(17, 1); + range4.number = 1; + range4.numberFormat = '#,##0.00'; + + final Range range5 = sheet.getRangeByName('A7'); + range5.number = 4.00; + range5.numberFormat = '#,##0'; + + final Range range6 = sheet.getRangeByIndex(9, 1); + range6.number = 16; + range6.numberFormat = r"'$'#,##0_)"; + + final Range range7 = sheet.getRangeByName('A11'); + range7.number = 22; + range7.numberFormat = r"\('$'#,##0\)"; + + final Range range8 = sheet.getRangeByIndex(13, 1); + range8.number = -33; + range8.numberFormat = r"'$'#,##0_)"; + + final Range range9 = sheet.getRangeByName('A15'); + range9.number = 2.20; + range9.numberFormat = r"[Red]\('$'#,##0\)"; + + final Range range10 = sheet.getRangeByIndex(1, 3); + range10.number = -64; + range10.numberFormat = r"'$'#,##0.00_)"; + + final Range range11 = sheet.getRangeByName('C3'); + range11.number = 25; + range11.numberFormat = r"\('$'#,##0.00\)"; + + final Range range12 = sheet.getRangeByIndex(5, 3); + range12.number = 55; + range12.numberFormat = r"'$'#,##0.00_)"; + + final Range range13 = sheet.getRangeByName('C7'); + range13.number = 30; + range13.numberFormat = r"[Red]\('$'#,##0.00\)"; + + final Range range14 = sheet.getRangeByIndex(9, 3); + range14.number = 11; + range14.numberFormat = '0%'; + + final Range range15 = sheet.getRangeByName('C11'); + range15.number = 4; + range15.numberFormat = '0.00%'; + + final Range range16 = sheet.getRangeByIndex(13, 3); + range16.number = 432561; + range16.numberFormat = '0.00E+00'; + + final Range range17 = sheet.getRangeByName('C15'); + range17.number = 38.00; + range17.numberFormat = '# ?/?'; + + final Range range18 = sheet.getRangeByIndex(1, 5); + range18.number = -33; + range18.numberFormat = '# ??/??'; + + final Range range19 = sheet.getRangeByName('E3'); + range19.dateTime = DateTime(2022, 08, 23, 8, 15, 20); + range19.numberFormat = r'm/d/yyyy'; + + final Range range20 = sheet.getRangeByIndex(5, 5); + range20.dateTime = DateTime(2024, 01, 03, 18, 45, 60); + range20.numberFormat = r'd\-mmm\-yy'; + + final Range range21 = sheet.getRangeByName('E7'); + range21.dateTime = DateTime(2000, 12, 12); + range21.numberFormat = r'd\-mmm'; + + final Range range22 = sheet.getRangeByIndex(9, 5); + range22.dateTime = DateTime(2011, 11, 11); + range22.numberFormat = r'mmm\-yy'; + + final Range range23 = sheet.getRangeByName('E11'); + range23.dateTime = DateTime(2120, 08, 09, 10, 11, 12); + range23.numberFormat = r'h:mm\\ AM/PM'; + + final Range range24 = sheet.getRangeByIndex(13, 5); + range24.dateTime = DateTime(1997, 09, 10, 11, 12, 13); + range24.numberFormat = r'h:mm:ss\\ AM/PM'; + + final Range range25 = sheet.getRangeByName('E15'); + range25.dateTime = DateTime.now(); + range25.numberFormat = 'h:mm'; + + final Range range26 = sheet.getRangeByIndex(1, 7); + range26.dateTime = DateTime(2021, 11, 07, 20, 40, 19); + range26.numberFormat = 'h:mm:ss'; + + final Range range27 = sheet.getRangeByName('G3'); + range27.dateTime = DateTime(2024, 03, 07, 2, 4, 6); + range27.numberFormat = r'm/d/yyyy\\ h:mm'; + + final Range range28 = sheet.getRangeByIndex(5, 7); + range28.number = -11; + range28.numberFormat = '#,##0_)'; + + final Range range29 = sheet.getRangeByName('G7'); + range29.number = 2.59; + range29.numberFormat = '(#,##0)'; + + final Range range30 = sheet.getRangeByIndex(9, 7); + range30.number = -39; + range30.numberFormat = '#,##0_)'; + + final Range range31 = sheet.getRangeByName('G11'); + range31.number = 194; + range31.numberFormat = '[Red](#,##0)'; + + final Range range32 = sheet.getRangeByIndex(13, 7); + range32.number = -10; + range32.numberFormat = '#,##0.00_)'; + + final Range range33 = sheet.getRangeByName('G15'); + range33.number = 54; + range33.numberFormat = '(#,##0.00)'; + + final Range range34 = sheet.getRangeByIndex(1, 9); + range34.number = 60; + range34.numberFormat = '#,##0.00_)'; + + final Range range35 = sheet.getRangeByName('I3'); + range35.number = 99.9; + range35.numberFormat = '[Red](#,##0.00)'; + + final Range range36 = sheet.getRangeByIndex(5, 9); + range36.number = 160; + range36.numberFormat = r'_(* #,##0_)'; + + final Range range37 = sheet.getRangeByName('I7'); + range37.number = 37.00; + range37.numberFormat = r'_(* \\(#,##0\\)'; + + final Range range38 = sheet.getRangeByIndex(9, 9); + range38.number = -3.3; + range38.numberFormat = r"_(* '-'_)"; + + final Range range39 = sheet.getRangeByName('I11'); + range39.number = 763; + range39.numberFormat = r'_(@_)'; + + final Range range40 = sheet.getRangeByIndex(13, 9); + range40.number = 3828; + range40.numberFormat = r"_('$'* #,##0_)"; + + final Range range41 = sheet.getRangeByName('I15'); + range41.number = 4.40; + range41.numberFormat = r"_('$'* \(#,##0\)"; + + final Range range42 = sheet.getRangeByIndex(1, 11); + range42.number = 112; + range42.numberFormat = r"_('$'* '-'_)"; + + final Range range43 = sheet.getRangeByName('K3'); + range43.number = 44; + range43.numberFormat = r'_(@_)'; + + final Range range44 = sheet.getRangeByIndex(5, 11); + range44.number = 29; + range44.numberFormat = '_(* #,##0.00_)'; + + final Range range45 = sheet.getRangeByName('K7'); + range45.number = -231.9; + range45.numberFormat = r'_(* \\(#,##0.00\\)'; + + final Range range46 = sheet.getRangeByIndex(9, 11); + range46.number = 134; + range46.numberFormat = r"_(* '-'??_)"; + + final Range range47 = sheet.getRangeByName('K11'); + range47.number = 2212; + range47.numberFormat = r'_(@_)'; + + final Range range48 = sheet.getRangeByIndex(13, 11); + range48.number = -33.6; + range48.numberFormat = r"_('$'* #,##0.00_)"; + + final Range range49 = sheet.getRangeByName('K15'); + range49.number = 25.8; + range49.numberFormat = r"_('$'* \(#,##0.00\)"; + + final Range range50 = sheet.getRangeByIndex(1, 13); + range50.number = -64.2; + range50.numberFormat = r"_('$'* '-'??_)"; + + final Range range51 = sheet.getRangeByName('M3'); + range51.number = 100; + range51.numberFormat = r'_(@_)'; + + final Range range52 = sheet.getRangeByIndex(5, 13); + range52.dateTime = DateTime(2008, 08, 08, 8, 8, 8); + range52.numberFormat = 'mm:ss'; + + final Range range53 = sheet.getRangeByName('M7'); + range53.dateTime = DateTime(2019, 09, 09, 9, 19, 9); + range53.numberFormat = '[h]:mm:ss'; + + final Range range54 = sheet.getRangeByIndex(9, 13); + range54.dateTime = DateTime(2007, 07, 07, 7, 7, 7); + range54.numberFormat = 'mm:ss.0'; + + final Range range55 = sheet.getRangeByName('M11'); + range55.number = 567; + range55.numberFormat = '##0.0E+0'; + + final Range range56 = sheet.getRangeByIndex(13, 13); + range56.number = 1622; + range56.numberFormat = '@'; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelNumberFormatAsync.xlsx'); + workbook.dispose(); + }); + test('Excel MergeCells Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + // Merge row + final Range range1 = sheet.getRangeByName('A1:D1'); + range1.number = 10; + range1.merge(); + // Unmerge row + final Range range2 = sheet.getRangeByName('E4:H4'); + range2.text = 'M'; + range2.merge(); + range2.unmerge(); + // Merge column + final Range range3 = sheet.getRangeByIndex(4, 2, 7, 2); + range3.dateTime = DateTime(1997, 11, 22); + range3.merge(); + // Unmerge column + final Range range4 = sheet.getRangeByName('P8:P40'); + range4.number = 55; + range4.merge(); + range4.unmerge(); + // Merge Rows and columns + final Range range5 = sheet.getRangeByIndex(1, 7, 8, 10); + range5.text = 'Helloo'; + range5.merge(); + // Unmerge Rows and columns + final Range range6 = sheet.getRangeByName('G15:I20'); + range6.dateTime = DateTime(2222, 10, 10); + range6.merge(); + range6.unmerge(); + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelMergeCellAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Cell style async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + // number formate + final CellStyle cellStyle1 = CellStyle(workbook); + cellStyle1.name = 'Style1'; + cellStyle1.numberFormat = '#,##0.00'; + workbook.styles.addStyle(cellStyle1); + final Range range = sheet.getRangeByName('A1'); + range.number = 10; + range.cellStyle = cellStyle1; + // border + final CellStyle cellStyle2 = CellStyle(workbook); + cellStyle2.name = 'Style1'; + cellStyle2.borders.all.lineStyle = LineStyle.double; + cellStyle2.borders.all.color = '#111111'; + workbook.styles.addStyle(cellStyle2); + final Range range1 = sheet.getRangeByName('A2'); + range1.cellStyle = cellStyle2; + // font + final CellStyle cellStyle3 = CellStyle(workbook); + cellStyle3.name = 'Style1'; + cellStyle3.fontName = 'Comic Sans MS'; + cellStyle3.fontColor = '#839202'; + cellStyle3.fontSize = 11; + cellStyle3.bold = true; + cellStyle3.italic = true; + cellStyle3.underline = true; + workbook.styles.addStyle(cellStyle3); + final Range range2 = sheet.getRangeByName('A3'); + range2.text = 'Hello'; + range2.cellStyle = cellStyle3; + final Range range3 = sheet.getRangeByName('A4'); + range3.number = 1000; + range3.cellStyle = cellStyle3; + // alignment + final CellStyle cellStyle4 = CellStyle(workbook); + cellStyle4.name = 'Style1'; + cellStyle4.hAlign = HAlignType.center; + workbook.styles.addStyle(cellStyle4); + final Range range4 = sheet.getRangeByName('A5'); + range4.text = 'Hi'; + range4.cellStyle = cellStyle4; + final Range range5 = sheet.getRangeByName('A6'); + range5.number = 10; + range5.cellStyle = cellStyle4; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'CellStylesAsync.xlsx'); + workbook.dispose(); + }); + test('Excel tables Async', () async { + // Create a new Excel Document. + final Workbook workbook = Workbook(2); + + // Accessing sheet via index. + final Worksheet sheet = workbook.worksheets[0]; + final Worksheet sheet1 = workbook.worksheets[1]; + + // Load data + sheet.getRangeByName('A1').setText('Fruits'); + sheet.getRangeByName('A2').setText('banana'); + sheet.getRangeByName('A3').setText('Cherry'); + sheet.getRangeByName('A4').setText('Banana'); + + sheet.getRangeByName('B1').setText('CostA'); + sheet.getRangeByName('B2').setNumber(744.6); + sheet.getRangeByName('B3').setNumber(5079.6); + sheet.getRangeByName('B4').setNumber(1267.5); + + sheet.getRangeByName('C1').setText('CostB'); + sheet.getRangeByName('C2').setNumber(162.56); + sheet.getRangeByName('C3').setNumber(1249.2); + sheet.getRangeByName('C4').setNumber(1062.5); + + sheet.getRangeByName('J8').setText('Name'); + sheet.getRangeByName('J9').setText('Rahul'); + sheet.getRangeByName('J10').setText('Mark'); + sheet.getRangeByName('J11').setText('Levi'); + + sheet.getRangeByName('K8').setText('SubjectA'); + sheet.getRangeByName('K9').setNumber(80); + sheet.getRangeByName('K10').setNumber(90); + sheet.getRangeByName('K11').setNumber(92); + + sheet.getRangeByName('L8').setText('SubjectB'); + sheet.getRangeByName('L9').setNumber(76); + sheet.getRangeByName('L10').setNumber(71); + sheet.getRangeByName('L11').setNumber(89); + + sheet1.getRangeByName('F1').setText('Vegetables'); + sheet1.getRangeByName('F2').setText('Egg Plant'); + sheet1.getRangeByName('F3').setText('DrumStick'); + sheet1.getRangeByName('F4').setText('Tomato'); + + sheet1.getRangeByName('G1').setText('CostA1'); + sheet1.getRangeByName('G2').setNumber(744.6); + sheet1.getRangeByName('G3').setNumber(5079.6); + sheet1.getRangeByName('G4').setNumber(1267.5); + + sheet1.getRangeByName('H1').setText('CostB1'); + sheet1.getRangeByName('H2').setNumber(162.56); + sheet1.getRangeByName('H3').setNumber(1249.2); + sheet1.getRangeByName('H4').setNumber(1062.5); + + sheet1.getRangeByName('A6').setText('Product A'); + sheet1.getRangeByName('A7').setText('shirt'); + sheet1.getRangeByName('A8').setText('bags'); + sheet1.getRangeByName('A9').setText('Trousers'); + + sheet1.getRangeByName('B6').setText('Cost1'); + sheet1.getRangeByName('B7').setNumber(654); + sheet1.getRangeByName('B8').setNumber(745); + sheet1.getRangeByName('B9').setNumber(187); + + sheet1.getRangeByName('C6').setText('Cost2'); + sheet1.getRangeByName('C7').setNumber(967); + sheet1.getRangeByName('C8').setNumber(543); + sheet1.getRangeByName('C9').setNumber(864); + + /// Create table with the data in given range + final ExcelTable table = + sheet.tableCollection.create('Table1', sheet.getRangeByName('A1:C4')); + + final ExcelTable table1 = sheet1.tableCollection + .create('Table2', sheet1.getRangeByName('F1:H4')); + + final ExcelTable table2 = sheet.tableCollection + .create('Table3', sheet.getRangeByName('J8:L11')); + + final ExcelTable table3 = sheet1.tableCollection + .create('Table4', sheet1.getRangeByName('A6:C9')); + + /// Multiple Sheet with Multiple Table + /// Formatting table with a built-in style + table.builtInTableStyle = ExcelTableBuiltInStyle.tableStyleMedium15; + table1.builtInTableStyle = ExcelTableBuiltInStyle.tableStyleDark10; + table2.builtInTableStyle = ExcelTableBuiltInStyle.tableStyleDark5; + table3.builtInTableStyle = ExcelTableBuiltInStyle.tableStyleLight9; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'TablesAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Formula Async', () async { + final Workbook workbook = Workbook(1); + // Accessing sheet via index. + final Worksheet sheet = workbook.worksheets[0]; + + // set the value to the cell. + sheet.getRangeByName('A1').setText('Apple'); + sheet.getRangeByName('A2').setText('Grapes'); + sheet.getRangeByName('A3').setText('Banana'); + sheet.getRangeByName('A4').setText('Grapes'); + sheet.getRangeByName('A5').setText('Grapes'); + sheet.getRangeByName('A6').setText('Banana'); + sheet.getRangeByName('A7').setText('Apple'); + sheet.getRangeByName('A8').setText('Banana'); + sheet.getRangeByName('A9').setText('Apple'); + sheet.getRangeByName('A10').setText('Grapes'); + sheet.getRangeByName('B1').setText('red'); + sheet.getRangeByName('B2').setText('blue'); + sheet.getRangeByName('B3').setText('yellow'); + sheet.getRangeByName('B4').setText('blue1'); + sheet.getRangeByName('B5').setText('blue'); + sheet.getRangeByName('B6').setText('yellow'); + sheet.getRangeByName('B7').setText('red1'); + sheet.getRangeByName('B8').setText('yellow'); + sheet.getRangeByName('B9').setText('red1'); + sheet.getRangeByName('B10').setText('blue1'); + sheet.getRangeByName('C1').setNumber(58); + sheet.getRangeByName('C2').setNumber(1200); + sheet.getRangeByName('C3').setNumber(300); + sheet.getRangeByName('C4').setNumber(500); + sheet.getRangeByName('C5').setNumber(1000); + sheet.getRangeByName('C6').setNumber(600); + sheet.getRangeByName('C7').setNumber(200); + sheet.getRangeByName('C8').setNumber(339); + sheet.getRangeByName('C9').setNumber(400); + sheet.getRangeByName('C10').setNumber(100); + sheet.getRangeByName('D1').setNumber(2); + sheet.getRangeByName('D2').setNumber(3); + sheet.getRangeByName('D3').setNumber(4); + sheet.getRangeByName('D4').setNumber(2); + sheet.getRangeByName('D5').setNumber(1); + sheet.getRangeByName('D6').setNumber(4); + sheet.getRangeByName('D7').setNumber(3); + sheet.getRangeByName('D8').setNumber(2); + sheet.getRangeByName('D9').setNumber(1); + sheet.getRangeByName('D10').setNumber(2); + + // Formula calculation is enabled for the sheet. + sheet.enableSheetCalculations(); + + // Setting formula in the cell. + Range range = sheet.getRangeByName('D12'); + range.setFormula('=AVERAGEIFS(C1:C10,D1:D10,">2")'); + range.calculatedValue; + range = sheet.getRangeByName('D13'); + range.setFormula('=AVERAGEIFS(C1:C10,A1:A10,"Apple")'); + range.calculatedValue; + range = sheet.getRangeByName('D14'); + range.setFormula('=AVERAGEIFS(C1:C10,A1:A10,"Apple",B1:B10,"red1")'); + range.calculatedValue; + range = sheet.getRangeByName('D15'); + range.setFormula( + '=AVERAGEIFS(C1:C10,A1:A10,"Apple",B1:B10,"red",D1:D10,">=1")'); + range.calculatedValue; + range = sheet.getRangeByName('D16'); + range.setFormula( + '=AVERAGEIFS(C1:C10,A1:A10,"Grapes",D1:D10,"<=2",B1:B10,"blue")'); + range.calculatedValue; + range = sheet.getRangeByName('D17'); + range.setFormula('=AVERAGEIFS(C1:C10,C1:D10,">2")'); + range.calculatedValue; + range = sheet.getRangeByName('D18'); + range.setFormula('=AVERAGEIFS(C1:C10,D1:D10,"2")'); + range.calculatedValue; + final Worksheet sheet2 = workbook.worksheets.add(); + final Worksheet sheet3 = workbook.worksheets.add(); + final Worksheet sheet4 = workbook.worksheets.add(); + sheet2.getRangeByIndex(1, 1).number = 10; + sheet2.getRangeByIndex(1, 2).number = 20; + sheet2.enableSheetCalculations(); + + sheet3.getRangeByIndex(1, 1).formula = '=Sheet2!A1+Sheet2!B1'; + sheet3.getRangeByIndex(1, 2).formula = '=Sheet2!A1-Sheet2!B1'; + sheet3.getRangeByIndex(1, 3).formula = '=Sheet2!A1*Sheet2!B1'; + sheet3.getRangeByIndex(1, 4).formula = '=Sheet2!A1/Sheet2!B1'; + + final Range range1 = sheet4.getRangeByIndex(1, 1); + range1.formula = '=Sheet3!A1'; + final Range range2 = sheet4.getRangeByIndex(1, 2); + range2.formula = '=Sheet3!B1'; + final Range range3 = sheet4.getRangeByIndex(1, 3); + range3.formula = '=Sheet3!C1'; + final Range range4 = sheet4.getRangeByIndex(1, 4); + range4.formula = '=Sheet3!D1'; + final Range range5 = sheet4.getRangeByIndex(1, 5); + range5.formula = '=SUM(Sheet3!A1:A2)'; + + final Worksheet sheet5 = workbook.worksheets.add(); + sheet5.getRangeByIndex(1, 1).number = 10; + sheet5.getRangeByIndex(1, 2).number = 20; + sheet5.getRangeByIndex(1, 3).number = 12; + sheet5.getRangeByIndex(2, 1).number = 23; + sheet5.getRangeByIndex(2, 2).number = 43; + sheet5.getRangeByIndex(2, 3).number = 31; + sheet5.getRangeByIndex(3, 1).number = 25; + sheet5.getRangeByIndex(3, 2).number = 52; + sheet5.getRangeByIndex(3, 3).number = 23; + sheet5.getRangeByIndex(4, 1).number = 41; + sheet5.getRangeByIndex(4, 2).number = 75; + sheet5.getRangeByIndex(4, 3).number = 54; + + sheet5.enableSheetCalculations(); + + Range range6 = sheet5.getRangeByName('A6'); + range6.formula = '=B1-A1'; + expect('10.0', range6.calculatedValue); + range6 = sheet5.getRangeByName('A7'); + range6.formula = '=C1*B1'; + expect('240.0', range6.calculatedValue); + range6 = sheet5.getRangeByName('A8'); + range6.formula = '=B3/B2'; + expect('1.2093023255813953', range6.calculatedValue); + range6 = sheet5.getRangeByName('A9'); + range6.formula = '=B2>A1'; + expect('TRUE', range6.calculatedValue); + range6 = sheet5.getRangeByName('A10'); + range6.formula = '=B3 bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelFormulaAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Culture Async', () async { + final Workbook workbook = Workbook.withCulture('en-IN'); + final Worksheet sheet = workbook.worksheets[0]; + + final Range range1 = sheet.getRangeByIndex(2, 2); + range1.numberFormat = r'm/d/yyyy'; + range1.dateTime = DateTime(2021, 12, 22); + range1.displayText; + + final Range range2 = sheet.getRangeByIndex(4, 4); + range2.numberFormat = 'dd MMMM yyyy'; + range2.dateTime = DateTime(2022, 11, 21); + range2.displayText; + + final Worksheet sheet2 = workbook.worksheets.add(); + + final Range range3 = sheet2.getRangeByIndex(2, 2); + range3.numberFormat = 'h:mm'; + range3.dateTime = DateTime(2021, 12, 22, 22, 22, 22); + range3.displayText; + + final Range range4 = sheet2.getRangeByIndex(4, 4); + range4.numberFormat = 'h:mm:ss'; + range4.dateTime = DateTime(2022, 11, 21, 21, 21, 21); + range4.displayText; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelCultureAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Image Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + final Picture picture1 = sheet.pictures.addBase64(1, 2, image24png); + picture1.rotation = 60; + + final Picture picture2 = sheet.pictures.addBase64(10, 10, image6png); + picture2.row = 20; + picture2.lastRow = 25; + picture2.column = 15; + picture2.lastColumn = 20; + + final Picture picture3 = sheet.pictures.addBase64(1, 15, image16jpg); + picture3.height = 300; + picture3.width = 500; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelImageAsync.xlsx'); + workbook.dispose(); + }); + test('multiple sheets', () async { + final Workbook workbook = Workbook(2); + + final Worksheet sheet = workbook.worksheets[0]; + final Range range = sheet.getRangeByName('A1'); + sheet.hyperlinks + .add(range, HyperlinkType.url, 'http://www.syncfusion.com'); + final Range range1 = sheet.getRangeByIndex(10, 10); + sheet.hyperlinks.add(range1, HyperlinkType.url, 'http://www.google.com'); + sheet.hyperlinks.add(sheet.getRangeByName('J16'), HyperlinkType.url, + 'http://www.gmail.com'); + final Hyperlink link1 = sheet.hyperlinks.add(sheet.getRangeByName('G16'), + HyperlinkType.url, 'http://www.gmail.com'); + link1.screenTip = 'login here'; + + final Worksheet sheet1 = workbook.worksheets[1]; + sheet1.hyperlinks.add(sheet1.getRangeByIndex(2, 10), HyperlinkType.url, + 'http://www.fb.com'); + sheet1.hyperlinks.add(sheet1.getRangeByName('D16'), HyperlinkType.url, + 'http://www.yahoo.com'); + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelHyperlinkMultipleSheet.xlsx'); + }); + test('Excel AutoFit Async', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + // Range AutoFitColumn + Range range = sheet.getRangeByName('A1'); + range.setText('Test for AutoFit Column'); + range = sheet.getRangeByName('A2'); + range.setText( + 'WrapText WrapText WrapText WrapText WrapText WrapText WrapText'); + + sheet.getRangeByName('A1:A2').autoFitColumns(); + // Range AutoFitRow + Range range2 = sheet.getRangeByName('C1'); + range2.setText('Test for AutoFit Row'); + range2.cellStyle.fontSize = 15; + range2 = sheet.getRangeByName('C2'); + range2.setText( + 'WrapText WrapText WrapText WrapText WrapText WrapText WrapText'); + range2.cellStyle.wrapText = true; + + sheet.getRangeByName('C1:C2').autoFitRows(); + // Range Autofit + Range range3 = sheet.getRangeByName('F1'); + range3.setText('WrapText WrapText WrapText WrapText WrapText WrapText'); + range3.cellStyle.wrapText = true; + range3 = sheet.getRangeByName('F2'); + range3.setText('Test for AutoFit Text'); + range3.cellStyle.fontSize = 15; + + range3 = sheet.getRangeByName('G1'); + range3.setText('WrapText WrapText WrapText WrapText WrapText WrapText'); + range3 = sheet.getRangeByName('G2'); + range3.setText('Test for AutoFit Text'); + range3.cellStyle.fontSize = 15; + + sheet.getRangeByName('F1:G2').autoFit(); + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelAutoFitAsync.xlsx'); + workbook.dispose(); + }); + test('Excel Image Hyperlink Async', () async { + final Workbook workbook = Workbook(1); + final Worksheet sheet = workbook.worksheets[0]; + Hyperlink link = sheet.hyperlinks.add(sheet.getRangeByIndex(3, 10), + HyperlinkType.url, 'http://www.fb.com', 'Fb login'); + link.textToDisplay = 'FaceBook'; + final Picture picture1 = sheet.pictures.addBase64(1, 1, image14png); + link = sheet.hyperlinks + .addImage(picture1, HyperlinkType.url, 'http://www.syncfusion.com'); + link = sheet.hyperlinks.add( + sheet.getRangeByIndex(1, 4), HyperlinkType.workbook, 'Sheet1!R5'); + + final Picture picture2 = sheet.pictures.addBase64(10, 10, image24png); + link = sheet.hyperlinks + .addImage(picture2, HyperlinkType.url, 'http://www.gmail.com'); + sheet.pictures.addBase64(30, 3, image10jpg); + sheet.pictures.addBase64(40, 15, image16jpg); + final Picture picture5 = sheet.pictures.addBase64(50, 25, image18jpg); + sheet.hyperlinks + .addImage(picture5, HyperlinkType.url, 'http://www.fb.com'); + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelImageHyperlinkAsync.xlsx'); + workbook.dispose(); + }); + }); + group('Sample Browser Samples', () { + // Create styles for worksheet + List + + + diff --git a/packages/syncfusion_localizations/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_localizations/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_localizations/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_localizations/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_localizations/example/android/build.gradle.kts b/packages/syncfusion_localizations/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_localizations/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_localizations/example/android/gradle.properties b/packages/syncfusion_localizations/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_localizations/example/android/gradle.properties +++ b/packages/syncfusion_localizations/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_localizations/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_localizations/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_localizations/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_localizations/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_localizations/example/android/settings.gradle.kts b/packages/syncfusion_localizations/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_localizations/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_localizations/example/ios/.gitignore b/packages/syncfusion_localizations/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_localizations/example/ios/.gitignore +++ b/packages/syncfusion_localizations/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_localizations/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_localizations/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_localizations/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_localizations/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_localizations/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_localizations/example/ios/Flutter/Debug.xcconfig index e8efba114..592ceee85 100644 --- a/packages/syncfusion_localizations/example/ios/Flutter/Debug.xcconfig +++ b/packages/syncfusion_localizations/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_localizations/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_localizations/example/ios/Flutter/Release.xcconfig index 399e9340e..592ceee85 100644 --- a/packages/syncfusion_localizations/example/ios/Flutter/Release.xcconfig +++ b/packages/syncfusion_localizations/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/syncfusion_localizations/example/ios/Podfile b/packages/syncfusion_localizations/example/ios/Podfile new file mode 100644 index 000000000..5a69b8902 --- /dev/null +++ b/packages/syncfusion_localizations/example/ios/Podfile @@ -0,0 +1,84 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.pbxproj index 624469a8d..006231f73 100644 --- a/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,19 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,14 +43,15 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; 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; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 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 = ""; }; @@ -51,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -74,7 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -82,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -89,30 +111,38 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -129,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -139,11 +172,17 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; }; }; }; @@ -156,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -182,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -196,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -211,18 +264,33 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -245,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -277,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -285,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -299,29 +368,74 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -351,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -365,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -375,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -407,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -415,10 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -429,20 +547,19 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample; 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; @@ -452,20 +569,18 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -473,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -494,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..15c313e20 100644 --- a/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_localizations/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + - - Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf030..7353c41ec 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b0b..6ed2d933e 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118..4cd7b0099 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e7e..fe730945a 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc2306c..321773cd8 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd967..797d452e4 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8f5..502f463a9 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609..0ec303439 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a..e9f5fea27 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39..84ac32ae7 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41e1..8953cba09 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f5853..0467bf12a 100644 Binary files a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_localizations/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_localizations/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_localizations/example/ios/Runner/Info.plist b/packages/syncfusion_localizations/example/ios/Runner/Info.plist index 8524ff376..419334a9d 100644 --- a/packages/syncfusion_localizations/example/ios/Runner/Info.plist +++ b/packages/syncfusion_localizations/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Localization Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_localizations/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_localizations/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_localizations/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_localizations/example/ios/RunnerTests/RunnerTests.swift b/packages/syncfusion_localizations/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/syncfusion_localizations/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_localizations/example/lib/main.dart b/packages/syncfusion_localizations/example/lib/main.dart index 1b8009362..f0867e63f 100644 --- a/packages/syncfusion_localizations/example/lib/main.dart +++ b/packages/syncfusion_localizations/example/lib/main.dart @@ -17,7 +17,7 @@ class CalendarApp extends StatelessWidget { localizationsDelegates: [ GlobalMaterialLocalizations.delegate, // ... app-specific localization delegate[s] here - SfGlobalLocalizations.delegate + SfGlobalLocalizations.delegate, ], //ignore: always_specify_types supportedLocales: const [ @@ -43,23 +43,38 @@ class _MyHomePageState extends State<_MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( - body: SfCalendar( - view: CalendarView.month, - dataSource: _MeetingDataSource(_getDataSource()), - monthViewSettings: MonthViewSettings( + body: SfCalendar( + view: CalendarView.month, + dataSource: _MeetingDataSource(_getDataSource()), + monthViewSettings: MonthViewSettings( appointmentDisplayMode: MonthAppointmentDisplayMode.appointment, - showAgenda: true), - )); + showAgenda: true, + ), + ), + ); } List<_Meeting> _getDataSource() { final List<_Meeting> meetings = <_Meeting>[]; final DateTime today = DateTime.now(); - final DateTime startTime = - DateTime(today.year, today.month, today.day, 9, 0, 0); + final DateTime startTime = DateTime( + today.year, + today.month, + today.day, + 9, + 0, + 0, + ); final DateTime endTime = startTime.add(const Duration(hours: 2)); - meetings.add(_Meeting( - 'Conference', startTime, endTime, const Color(0xFF0F8644), false)); + meetings.add( + _Meeting( + 'Conference', + startTime, + endTime, + const Color(0xFF0F8644), + false, + ), + ); return meetings; } } diff --git a/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_localizations/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_localizations/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_localizations/example/linux/flutter/generated_plugins.cmake index 51436ae8c..2e1de87a7 100644 --- a/packages/syncfusion_localizations/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_localizations/example/linux/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_localizations/example/macos/.gitignore b/packages/syncfusion_localizations/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_localizations/example/macos/.gitignore +++ b/packages/syncfusion_localizations/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..1ed284107 100644 --- a/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* localization_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "localization_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* localization_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* localization_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/localization_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/localization_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/localization_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/localization_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/localization_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/localization_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..059c42f06 100644 --- a/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_localizations/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_localizations/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_localizations/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_localizations/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_localizations/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_localizations/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_localizations/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_localizations/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_localizations/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_localizations/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/packages/syncfusion_localizations/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_localizations/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..0656fe6d9 100644 --- a/packages/syncfusion_localizations/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_localizations/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = localization_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.localizationExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_localizations/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_localizations/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_localizations/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_localizations/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_localizations/example/macos/RunnerTests/RunnerTests.swift b/packages/syncfusion_localizations/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..61f3bd1fc --- /dev/null +++ b/packages/syncfusion_localizations/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/syncfusion_localizations/example/pubspec.yaml b/packages/syncfusion_localizations/example/pubspec.yaml index 383b1e8e9..7bea5f83e 100644 --- a/packages/syncfusion_localizations/example/pubspec.yaml +++ b/packages/syncfusion_localizations/example/pubspec.yaml @@ -3,17 +3,17 @@ description: This project holds information about the usage of the syncfusion_lo version: 1.0.0 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter - syncfusion_localizations: ^19.1.57 - syncfusion_flutter_calendar: ^19.1.57 - cupertino_icons: ^0.1.3 + syncfusion_localizations: ^29.2.7 + syncfusion_flutter_calendar: ^29.2.7 + cupertino_icons: '^1.0.8' flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/packages/syncfusion_localizations/example/web/index.html b/packages/syncfusion_localizations/example/web/index.html index 1460b5e9b..d7e47f1bc 100644 --- a/packages/syncfusion_localizations/example/web/index.html +++ b/packages/syncfusion_localizations/example/web/index.html @@ -30,16 +30,7 @@ - - + diff --git a/packages/syncfusion_localizations/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_localizations/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_localizations/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_localizations/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_localizations/lib/src/global_localizations.dart b/packages/syncfusion_localizations/lib/src/global_localizations.dart index 594b5794a..3be05276c 100644 --- a/packages/syncfusion_localizations/lib/src/global_localizations.dart +++ b/packages/syncfusion_localizations/lib/src/global_localizations.dart @@ -39,8 +39,8 @@ abstract class SfGlobalLocalizations implements SfLocalizations { const SfGlobalLocalizations({ required String localeName, // ignore: unnecessary_null_comparison - }) : assert(localeName != null), - _localeName = localeName; + }) : assert(localeName != null), + _localeName = localeName; // ignore: unused_field final String _localeName; //ignore: public_member_api_docs @@ -63,9 +63,7 @@ abstract class SfGlobalLocalizations implements SfLocalizations { /// ``` /// static const List> delegates = - >[ - SfGlobalLocalizations.delegate, - ]; + >[SfGlobalLocalizations.delegate]; } class _SfLocalizationsDelegate extends LocalizationsDelegate { @@ -82,17 +80,18 @@ class _SfLocalizationsDelegate extends LocalizationsDelegate { Future load(Locale locale) { assert(isSupported(locale)); return _loadedTranslations.putIfAbsent(locale, () { - final String localeName = - intl.Intl.canonicalizedLocale(locale.toString()); + final String localeName = intl.Intl.canonicalizedLocale( + locale.toString(), + ); assert( locale.toString() == localeName, 'Flutter does not support the non-standard locale form $locale (which ' 'might be $localeName', ); - return SynchronousFuture(getSyncfusionTranslation( - locale, - )!); + return SynchronousFuture( + getSyncfusionTranslation(locale)!, + ); }); } @@ -100,6 +99,7 @@ class _SfLocalizationsDelegate extends LocalizationsDelegate { bool shouldReload(_SfLocalizationsDelegate old) => false; @override - String toString() => 'SfGlobalLocalizations.delegate(' + String toString() => + 'SfGlobalLocalizations.delegate(' '${kSyncfusionSupportedLanguages.length} locales)'; } diff --git a/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart b/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart index 1ff27efb6..919f810fe 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart +++ b/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart @@ -16,11 +16,14 @@ import '../global_localizations.dart'; /// The translations for Afrikaans (`af`). class SfLocalizationsAf extends SfGlobalLocalizations { /// Creating an argument constructor of SfLocalizationsAf class - const SfLocalizationsAf({ - String localeName = 'af', - }) : super( - localeName: localeName, - ); + const SfLocalizationsAf({String localeName = 'af'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Na'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Na Of Gelyk'; @override String get allDayLabel => r'Heeldag'; @@ -41,10 +44,10 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Tydlyn Maand'; @override - String get allowedViewTimelineWeekLabel => r'Tydlynweek'; + String get allowedViewTimelineWeekLabel => r'Tydlyn Week'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tydlynwerkweek'; + String get allowedViewTimelineWorkWeekLabel => r'TTydlyn Werksweek'; @override String get allowedViewWeekLabel => r'Week'; @@ -52,6 +55,30 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Werksweek'; + @override + String get andDataGridFilteringLabel => r'En'; + + @override + String get beforeDataGridFilteringLabel => r'Voorheen'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Voor Of Gelyk'; + + @override + String get beginsWithDataGridFilteringLabel => r'Begin Met'; + + @override + String get cancelDataGridFilteringLabel => r'Kanselleer'; + + @override + String get clearFilterDataGridFilteringLabel => r'Vee filter uit'; + + @override + String get containsDataGridFilteringLabel => r'Bevat'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Datum filters'; + @override String get daySpanCountLabel => r'Dag'; @@ -59,7 +86,40 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Begin nie met'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Bevat nie'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Eindig nie met'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Is Nie Gelyk nie'; + + @override + String get emptyDataGridFilteringLabel => r'Leeg'; + + @override + String get endsWithDataGridFilteringLabel => r'Eindig met'; + + @override + String get equalsDataGridFilteringLabel => r'Gelyk'; + + @override + String get fromDataGridFilteringLabel => r'Van'; + + @override + String get greaterThanDataGridFilteringLabel => r'Groter as'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Groter as of gelyk'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -67,6 +127,12 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get jumada2Label => r'Jumada al-thani'; + @override + String get lessThanDataGridFilteringLabel => r'Minder as'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Minder as of gelyk'; + @override String get muharramLabel => r'Muharram'; @@ -74,11 +140,32 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Geen gebeure nie'; @override - String get noSelectedDateCalendarLabel => r'Geen gekose datum nie'; + String get noMatchesDataGridFilteringLabel => r'Geen wedstryde nie'; + + @override + String get noSelectedDateCalendarLabel => r'Geen datum gekies'; + + @override + String get notEmptyDataGridFilteringLabel => r'Nie leeg nie'; + + @override + String get notNullDataGridFilteringLabel => r'Nie Nul nie'; + + @override + String get nullDataGridFilteringLabel => r'Nul'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Getalfilters'; @override String get ofDataPagerLabel => r'van'; + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'Of'; + @override String get pagesDataPagerLabel => r'bladsye'; @@ -104,9 +191,23 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get pdfGoToPageLabel => r'Gaan na bladsy'; + @override + String get pdfHyperlinkContentLabel => r'Wil jy die bladsy oopmaak by'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'KANSELLEER'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'OOP'; + + @override + String get pdfHyperlinkLabel => r'Maak webblad oop'; + @override String get pdfInvalidPageNumberLabel => - r'Voer asseblief ' "'" r'n geldige nommer in'; + r'Voer asseblief ' + "'" + r'n geldige nommer in'; @override String get pdfNoBookmarksLabel => r'Geen boekmerke gevind nie'; @@ -127,10 +228,43 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'van'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get pdfSignaturePadDialogClearLabel => r'DUIDELIK'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Teken jou handtekening'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Pen Kleur'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'SPAAR'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopieer'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Merk'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Snorkelend'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Strek deur'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Onderstreep'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override String get rajabLabel => r'Rajab'; @@ -144,20 +278,35 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get safarLabel => r'Safar'; + @override + String get searchDataGridFilteringLabel => r'Soek'; + + @override + String get selectAllDataGridFilteringLabel => r'Selekteer alles'; + @override String get series => r'Reeks'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override String get shortJumada1Label => r'Jum. ek'; @@ -189,6 +338,37 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get shortShawwalLabel => r'Shaw.'; + @override + String get showRowsWhereDataGridFilteringLabel => r'Wys rye waar'; + + @override + String get sortAToZDataGridFilteringLabel => r'Sorteer A tot Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sorteer en Filter'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sorteer Grootste Na Kleinste'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Sorteer nuutste na oudste'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Sorteer oudste na nuutste'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sorteer Kleinste Na Grootste'; + + @override + String get sortZToADataGridFilteringLabel => r'Sorteer Z tot A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Teks filters'; + @override String get todayLabel => r'Vandag'; @@ -199,11 +379,14 @@ class SfLocalizationsAf extends SfGlobalLocalizations { /// The translations for Amharic (`am`). class SfLocalizationsAm extends SfGlobalLocalizations { /// Creating an argument constructor of SfLocalizationsAm class - const SfLocalizationsAm({ - String localeName = 'am', - }) : super( - localeName: localeName, - ); + const SfLocalizationsAm({String localeName = 'am'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'በኋላ'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'በኋላ ወይም እኩል'; @override String get allDayLabel => r'ሙሉ ቀን'; @@ -235,6 +418,30 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'የስራ ሳምንት'; + @override + String get andDataGridFilteringLabel => r'እና'; + + @override + String get beforeDataGridFilteringLabel => r'ከዚህ በፊት'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'በፊት ወይም እኩል'; + + @override + String get beginsWithDataGridFilteringLabel => r'ጋር ይጀምራል'; + + @override + String get cancelDataGridFilteringLabel => r'ሰርዝ'; + + @override + String get clearFilterDataGridFilteringLabel => r'ማጣሪያን አጽዳ'; + + @override + String get containsDataGridFilteringLabel => r'ይዟል'; + + @override + String get dateFiltersDataGridFilteringLabel => r'የቀን ማጣሪያዎች'; + @override String get daySpanCountLabel => r'ቀን'; @@ -244,24 +451,81 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get dhualqiLabel => r'ዙ አል-ቂዳህ'; + @override + String get doesNotBeginWithDataGridFilteringLabel => r'በ አይጀምርም።'; + + @override + String get doesNotContainDataGridFilteringLabel => r'አልያዘም።'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'አያልቅም።'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'እኩል አይደለም'; + + @override + String get emptyDataGridFilteringLabel => r'ባዶ'; + + @override + String get endsWithDataGridFilteringLabel => r'ጋር ያበቃል'; + + @override + String get equalsDataGridFilteringLabel => r'እኩል ነው።'; + + @override + String get fromDataGridFilteringLabel => r'ከ'; + + @override + String get greaterThanDataGridFilteringLabel => r'ከዚያ ይበልጣል'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'ይበልጣል ወይም እኩል'; + @override String get jumada1Label => r'ጁማዳ አል-አወል'; @override String get jumada2Label => r'ጁማዳ አል-ታኒ'; + @override + String get lessThanDataGridFilteringLabel => r'ያነሰ'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'ያነሰ ወይም እኩል'; + @override String get muharramLabel => r'ሙሀረም'; @override String get noEventsCalendarLabel => r'ምንም ክስተቶች የሉም'; + @override + String get noMatchesDataGridFilteringLabel => r'ምንም ግጥሚያዎች የሉም'; + @override String get noSelectedDateCalendarLabel => r'የተመረጠ ቀን የለም።'; + @override + String get notEmptyDataGridFilteringLabel => r'ባዶ አይደለም'; + + @override + String get notNullDataGridFilteringLabel => r'ኑል አይደለም።'; + + @override + String get nullDataGridFilteringLabel => r'ከንቱ'; + + @override + String get numberFiltersDataGridFilteringLabel => r'የቁጥር ማጣሪያዎች'; + @override String get ofDataPagerLabel => r'የ'; + @override + String get okDataGridFilteringLabel => r'እሺ'; + + @override + String get orDataGridFilteringLabel => r'ወይም'; + @override String get pagesDataPagerLabel => r'ገጾች'; @@ -287,7 +551,19 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'ወደ ገጽ ይሂዱ'; @override - String get pdfInvalidPageNumberLabel => r'እባክህ የሚሰራ ቁጥር አስገባ'; + String get pdfHyperlinkContentLabel => r'ገጹን በ ላይ መክፈት ይፈልጋሉ'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ሰርዝ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ክፈት'; + + @override + String get pdfHyperlinkLabel => r'ድህረ ገጽ ክፈት'; + + @override + String get pdfInvalidPageNumberLabel => r'እባክዎ ትክክለኛ ቁጥር ያስገቡ'; @override String get pdfNoBookmarksLabel => r'ምንም ዕልባቶች አልተገኙም።'; @@ -307,6 +583,33 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get pdfScrollStatusOfLabel => r'የ'; + @override + String get pdfSignaturePadDialogClearLabel => r'አጽዳ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'ፊርማዎን ይሳሉ'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'የብዕር ቀለም'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'አስቀምጥ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'ገልብጥ'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'አድምቅ'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'በቅንጦት'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'መጋረድ'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'ይሰመርበት'; + @override String get rabi1Label => r'ረቢዑል አወል'; @@ -325,6 +628,12 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get safarLabel => r'ሳፋራ'; + @override + String get searchDataGridFilteringLabel => r'ፈልግ'; + + @override + String get selectAllDataGridFilteringLabel => r'ሁሉንም ምረጥ'; + @override String get series => r'ተከታታይ'; @@ -332,7 +641,7 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get shaabanLabel => r'ሻዕባን'; @override - String get shawwalLabel => r'ሻውል'; + String get shawwalLabel => r'ሻዋል'; @override String get shortDhualhiLabel => r'ዙል-ሀ'; @@ -347,7 +656,7 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get shortJumada2Label => r'ጁም. II'; @override - String get shortMuharramLabel => r'ሙህ.'; + String get shortMuharramLabel => r'ሙህ'; @override String get shortRabi1Label => r'ራቢ. አይ'; @@ -370,6 +679,33 @@ class SfLocalizationsAm extends SfGlobalLocalizations { @override String get shortShawwalLabel => r'ሻው'; + @override + String get showRowsWhereDataGridFilteringLabel => r'ረድፎችን የት አሳይ'; + + @override + String get sortAToZDataGridFilteringLabel => r'ከ A እስከ Z ደርድር'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'ደርድር እና አጣራ'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => r'ትልቁን ወደ ትንሹ ደርድር'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => r'አዲሱን ወደ አሮጌው ደርድር'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => r'አሮጌውን ወደ አዲሱ ደርድር'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => r'ትንሹን ወደ ትልቁ ደርድር'; + + @override + String get sortZToADataGridFilteringLabel => r'Z ወደ A ደርድር'; + + @override + String get textFiltersDataGridFilteringLabel => r'የጽሑፍ ማጣሪያዎች'; + @override String get todayLabel => r'ዛሬ'; @@ -380,11 +716,14 @@ class SfLocalizationsAm extends SfGlobalLocalizations { /// The translations for Arabic (`ar`). class SfLocalizationsAr extends SfGlobalLocalizations { /// Creating an argument constructor of SfLocalizationsAr class - const SfLocalizationsAr({ - String localeName = 'ar', - }) : super( - localeName: localeName, - ); + const SfLocalizationsAr({String localeName = 'ar'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'بعد، بعدما'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'بعد أو يساوي'; @override String get allDayLabel => r'طوال اليوم'; @@ -417,2680 +756,2735 @@ class SfLocalizationsAr extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'أسبوع العمل'; @override - String get daySpanCountLabel => r'يوم'; + String get andDataGridFilteringLabel => r'و'; @override - String get dhualhiLabel => r'ذو الحجة'; + String get beforeDataGridFilteringLabel => r'قبل'; @override - String get dhualqiLabel => r'ذو القعدة'; + String get beforeOrEqualDataGridFilteringLabel => r'قبل أو يساوي'; @override - String get jumada1Label => r'جمادى الاول'; + String get beginsWithDataGridFilteringLabel => r'يبدأ ب'; @override - String get jumada2Label => r'جمادى الثانية'; + String get cancelDataGridFilteringLabel => r'إلغاء'; @override - String get muharramLabel => r'شهر محرم'; + String get clearFilterDataGridFilteringLabel => r'مسح المرشح'; @override - String get noEventsCalendarLabel => r'لا أحداث'; + String get containsDataGridFilteringLabel => r'يحتوي على'; @override - String get noSelectedDateCalendarLabel => r'لا يوجد تاريخ محدد'; + String get dateFiltersDataGridFilteringLabel => r'مرشحات التاريخ'; @override - String get ofDataPagerLabel => r'من'; + String get daySpanCountLabel => r'يوم'; @override - String get pagesDataPagerLabel => r'الصفحات'; + String get dhualhiLabel => r'ذو الحجة'; @override - String get passwordDialogContentLabel => r'أدخل كلمة المرور لفتح ملف PDF هذا'; + String get dhualqiLabel => r'ذو القعدة'; @override - String get passwordDialogHeaderTextLabel => r'محمية بكلمة مرور'; + String get doesNotBeginWithDataGridFilteringLabel => r'لا تبدأ بـ'; @override - String get passwordDialogHintTextLabel => r'أدخل كلمة المرور'; + String get doesNotContainDataGridFilteringLabel => r'لا يحتوي'; @override - String get passwordDialogInvalidPasswordLabel => r'رمز مرور خاطئ'; + String get doesNotEndWithDataGridFilteringLabel => r'لا تنتهي بـ'; @override - String get pdfBookmarksLabel => r'إشارات مرجعية'; + String get doesNotEqualDataGridFilteringLabel => r'لا يساوي'; @override - String get pdfEnterPageNumberLabel => r'أدخل رقم الصفحة'; + String get emptyDataGridFilteringLabel => r'فارغة'; @override - String get pdfGoToPageLabel => r'انتقل إلى صفحة'; + String get endsWithDataGridFilteringLabel => r'ينتهي بـ'; @override - String get pdfInvalidPageNumberLabel => r'من فضلك أدخل رقما صالحا'; + String get equalsDataGridFilteringLabel => r'يساوي'; @override - String get pdfNoBookmarksLabel => r'لم يتم العثور على إشارات مرجعية'; + String get fromDataGridFilteringLabel => r'من'; @override - String get pdfPaginationDialogCancelLabel => r'إلغاء'; + String get greaterThanDataGridFilteringLabel => r'أكثر من'; @override - String get pdfPaginationDialogOkLabel => r'نعم'; + String get greaterThanOrEqualDataGridFilteringLabel => r'أكبر من أو يساوي'; @override - String get pdfPasswordDialogCancelLabel => r'إلغاء'; + String get jumada1Label => r'جمادى الاول'; @override - String get pdfPasswordDialogOpenLabel => r'افتح'; + String get jumada2Label => r'جمادى الثانية'; @override - String get pdfScrollStatusOfLabel => r'من'; + String get lessThanDataGridFilteringLabel => r'أقل من'; @override - String get rabi1Label => r'ربيع الأول'; + String get lessThanOrEqualDataGridFilteringLabel => r'اصغر من او يساوي'; @override - String get rabi2Label => r'ربيع الثاني'; + String get muharramLabel => r'شهر محرم'; @override - String get rajabLabel => r'رجب'; + String get noEventsCalendarLabel => r'لا أحداث'; @override - String get ramadanLabel => r'رمضان'; + String get noMatchesDataGridFilteringLabel => r'لا يوجد تطابق'; @override - String get rowsPerPageDataPagerLabel => r'عدد الصفوف في الصفحة'; + String get noSelectedDateCalendarLabel => r'لا يوجد تاريخ محدد'; @override - String get safarLabel => r'سفر'; + String get notEmptyDataGridFilteringLabel => r'ليس فارغًا'; @override - String get series => r'مسلسل'; + String get notNullDataGridFilteringLabel => r'غير فارغة'; @override - String get shaabanLabel => r'شعبان'; + String get nullDataGridFilteringLabel => r'لا شيء'; @override - String get shawwalLabel => r'شوال'; + String get numberFiltersDataGridFilteringLabel => r'مرشحات الرقم'; @override - String get shortDhualhiLabel => r'ذو الحجة'; + String get ofDataPagerLabel => r'من'; @override - String get shortDhualqiLabel => r'ذو القعدة'; + String get okDataGridFilteringLabel => r'نعم'; @override - String get shortJumada1Label => r'جام. أنا'; + String get orDataGridFilteringLabel => r'أو'; @override - String get shortJumada2Label => r'جام. ثانيًا'; + String get pagesDataPagerLabel => r'الصفحات'; @override - String get shortMuharramLabel => r'موه.'; + String get passwordDialogContentLabel => r'أدخل كلمة المرور لفتح ملف PDF هذا'; @override - String get shortRabi1Label => r'ربيع. أنا'; + String get passwordDialogHeaderTextLabel => r'محمية بكلمة مرور'; @override - String get shortRabi2Label => r'ربيع. ثانيًا'; + String get passwordDialogHintTextLabel => r'أدخل كلمة المرور'; @override - String get shortRajabLabel => r'راج.'; + String get passwordDialogInvalidPasswordLabel => r'رمز مرور خاطئ'; @override - String get shortRamadanLabel => - r'الرامات الذاكرة العشوائية في الهواتف والحواسيب.'; + String get pdfBookmarksLabel => r'إشارات مرجعية'; @override - String get shortSafarLabel => r'ساف.'; + String get pdfEnterPageNumberLabel => r'أدخل رقم الصفحة'; @override - String get shortShaabanLabel => r'شا.'; + String get pdfGoToPageLabel => r'انتقل إلى صفحة'; @override - String get shortShawwalLabel => r'شو.'; + String get pdfHyperlinkContentLabel => r'هل تريد فتح الصفحة في'; @override - String get todayLabel => r'اليوم'; + String get pdfHyperlinkDialogCancelLabel => r'إلغاء'; @override - String get weeknumberLabel => r'أسبوع'; -} - -/// The translations for Azerbaijani (`az`). -class SfLocalizationsAz extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsAz class - const SfLocalizationsAz({ - String localeName = 'az', - }) : super( - localeName: localeName, - ); + String get pdfHyperlinkDialogOpenLabel => r'افتح'; @override - String get allDayLabel => r'Bütün gün'; + String get pdfHyperlinkLabel => r'افتح صفحة الويب'; @override - String get allowedViewDayLabel => r'Gün'; + String get pdfInvalidPageNumberLabel => r'من فضلك أدخل رقما صالحا'; @override - String get allowedViewMonthLabel => r'ay'; + String get pdfNoBookmarksLabel => r'لم يتم العثور على إشارات مرجعية'; @override - String get allowedViewScheduleLabel => r'Cədvəl'; + String get pdfPaginationDialogCancelLabel => r'إلغاء'; @override - String get allowedViewTimelineDayLabel => r'Zaman qrafiki günü'; + String get pdfPaginationDialogOkLabel => r'موافق'; @override - String get allowedViewTimelineMonthLabel => r'Zaman qrafiki ayı'; + String get pdfPasswordDialogCancelLabel => r'إلغاء'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Həftəsi'; + String get pdfPasswordDialogOpenLabel => r'افتح'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline İş Həftəsi'; + String get pdfScrollStatusOfLabel => r'من'; @override - String get allowedViewWeekLabel => r'Həftə'; + String get pdfSignaturePadDialogClearLabel => r'مسح'; @override - String get allowedViewWorkWeekLabel => r'İş həftəsi'; + String get pdfSignaturePadDialogHeaderTextLabel => r'ارسم توقيعك'; @override - String get daySpanCountLabel => r'Gün'; + String get pdfSignaturePadDialogPenColorLabel => r'لون القلم'; @override - String get dhualhiLabel => r'Zilhiccə'; + String get pdfSignaturePadDialogSaveLabel => r'حفظ'; @override - String get dhualqiLabel => r'Zilqidə'; + String get pdfTextSelectionMenuCopyLabel => r'نسخ'; @override - String get jumada1Label => r'Cümə əl-əvvəl'; + String get pdfTextSelectionMenuHighlightLabel => r'تسليط الضوء'; @override - String get jumada2Label => r'Cümədə əl-sani'; + String get pdfTextSelectionMenuSquigglyLabel => r'متعرج'; @override - String get muharramLabel => r'Məhərrəm'; + String get pdfTextSelectionMenuStrikethroughLabel => r'يتوسطه خط'; @override - String get noEventsCalendarLabel => r'Tədbir yoxdur'; + String get pdfTextSelectionMenuUnderlineLabel => r'تسطير'; @override - String get noSelectedDateCalendarLabel => r'Seçilmiş tarix yoxdur'; + String get rabi1Label => r'ربيع الأول'; @override - String get ofDataPagerLabel => r'of'; + String get rabi2Label => r'ربيع الثاني'; @override - String get pagesDataPagerLabel => r'səhifələr'; + String get rajabLabel => r'رجب'; @override - String get passwordDialogContentLabel => - r'Bu PDF faylını açmaq üçün parolu daxil edin'; + String get ramadanLabel => r'رمضان'; @override - String get passwordDialogHeaderTextLabel => r'Parol Qorunur'; + String get rowsPerPageDataPagerLabel => r'الصفوف لكل صفحة'; @override - String get passwordDialogHintTextLabel => r'Parol daxil edin'; + String get safarLabel => r'سفر'; @override - String get passwordDialogInvalidPasswordLabel => r'etibarsız Şifrə'; + String get searchDataGridFilteringLabel => r'يبحث'; @override - String get pdfBookmarksLabel => r'Əlfəcinlər'; + String get selectAllDataGridFilteringLabel => r'اختر الكل'; @override - String get pdfEnterPageNumberLabel => r'Səhifə nömrəsini daxil edin'; + String get series => r'سلسلة'; @override - String get pdfGoToPageLabel => r'Səhifəyə daxil ol'; + String get shaabanLabel => r'شعبان'; @override - String get pdfInvalidPageNumberLabel => r'Etibarlı nömrə daxil edin'; + String get shawwalLabel => r'شوال'; @override - String get pdfNoBookmarksLabel => r'Əlfəcin tapılmadı'; + String get shortDhualhiLabel => r'ذو الحجة'; @override - String get pdfPaginationDialogCancelLabel => r'LƏĞV EDİN'; + String get shortDhualqiLabel => r'ذو القعدة'; @override - String get pdfPaginationDialogOkLabel => r'tamam'; + String get shortJumada1Label => r'جم. أنا'; @override - String get pdfPasswordDialogCancelLabel => r'LƏĞV EDİN'; + String get shortJumada2Label => r'جم. ثانيًا'; @override - String get pdfPasswordDialogOpenLabel => r'AÇIQ'; + String get shortMuharramLabel => r'موه.'; @override - String get pdfScrollStatusOfLabel => r'of'; + String get shortRabi1Label => r'ربيع. أنا'; @override - String get rabi1Label => r'Rəbiul-əvvəl'; + String get shortRabi2Label => r'ربيع. ثانيًا'; @override - String get rabi2Label => r'Rəbi əl-sani'; + String get shortRajabLabel => r'راج.'; @override - String get rajabLabel => r'Rəcəb'; + String get shortRamadanLabel => + r'الرامات الذاكرة العشوائية في الهواتف والحواسيب.'; @override - String get ramadanLabel => r'Ramazan'; + String get shortSafarLabel => r'ساف.'; @override - String get rowsPerPageDataPagerLabel => r'Səhifə başına satırlar'; + String get shortShaabanLabel => r'شا.'; @override - String get safarLabel => r'Səfər'; + String get shortShawwalLabel => r'شو.'; @override - String get series => r'Serial'; + String get showRowsWhereDataGridFilteringLabel => r'إظهار الصفوف حيث'; @override - String get shaabanLabel => r'Şaban'; + String get sortAToZDataGridFilteringLabel => r'فرز من الألف إلى الياء'; @override - String get shawwalLabel => r'Şəvval'; + String get sortAndFilterDataGridFilteringLabel => r'الفرز والتصفية'; @override - String get shortDhualhiLabel => r'Zül-H'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'الفرز من الأكبر إلى الأصغر'; @override - String get shortDhualqiLabel => r'Zül-Q'; + String get sortNewestToOldestDataGridFilteringLabel => + r'فرز من الأحدث إلى الأقدم'; @override - String get shortJumada1Label => r'Cümə. I'; + String get sortOldestToNewestDataGridFilteringLabel => + r'فرز من الأقدم إلى الأحدث'; @override - String get shortJumada2Label => r'Cümə. II'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'الفرز من الأصغر إلى الأكبر'; @override - String get shortMuharramLabel => r'Muh.'; + String get sortZToADataGridFilteringLabel => r'فرز Z إلى A.'; @override - String get shortRabi1Label => r'Rəbi. I'; + String get textFiltersDataGridFilteringLabel => r'مرشحات النص'; @override - String get shortRabi2Label => r'Rəbi. II'; + String get todayLabel => r'اليوم'; @override - String get shortRajabLabel => r'Raj.'; + String get weeknumberLabel => r'أسبوع'; +} - @override - String get shortRamadanLabel => r'Ram.'; +/// The translations for Azerbaijani (`az`). +class SfLocalizationsAz extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsAz class + const SfLocalizationsAz({String localeName = 'az'}) + : super(localeName: localeName); @override - String get shortSafarLabel => r'Saf.'; + String get afterDataGridFilteringLabel => r'sonra'; @override - String get shortShaabanLabel => r'Şa.'; + String get afterOrEqualDataGridFilteringLabel => r'Sonra Və ya Bərabər'; @override - String get shortShawwalLabel => r'Şou.'; + String get allDayLabel => r'Bütün gün'; @override - String get todayLabel => r'Bu gün'; + String get allowedViewDayLabel => r'Gün'; @override - String get weeknumberLabel => r'Həftə'; -} - -/// The translations for Belarusian (`be`). -class SfLocalizationsBe extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsBe class - const SfLocalizationsBe({ - String localeName = 'be', - }) : super( - localeName: localeName, - ); + String get allowedViewMonthLabel => r'ay'; @override - String get allDayLabel => r'Увесь дзень'; + String get allowedViewScheduleLabel => r'Cədvəl'; @override - String get allowedViewDayLabel => r'Дзень'; + String get allowedViewTimelineDayLabel => r'Zaman qrafiki günü'; @override - String get allowedViewMonthLabel => r'Месяц'; + String get allowedViewTimelineMonthLabel => r'Zaman qrafiki ayı'; @override - String get allowedViewScheduleLabel => r'Расклад'; + String get allowedViewTimelineWeekLabel => r'Timeline Həftəsi'; @override - String get allowedViewTimelineDayLabel => r'Дзень шкалы часу'; + String get allowedViewTimelineWorkWeekLabel => r'Timeline İş Həftəsi'; @override - String get allowedViewTimelineMonthLabel => r'Месяц часавай шкалы'; + String get allowedViewWeekLabel => r'Həftə'; @override - String get allowedViewTimelineWeekLabel => r'Тыдзень хронікі'; + String get allowedViewWorkWeekLabel => r'İş həftəsi'; @override - String get allowedViewTimelineWorkWeekLabel => r'Тэрмін працоўнага тыдня'; + String get andDataGridFilteringLabel => r'Və'; @override - String get allowedViewWeekLabel => r'Тыдзень'; + String get beforeDataGridFilteringLabel => r'Əvvəl'; @override - String get allowedViewWorkWeekLabel => r'Працоўны тыдзень'; + String get beforeOrEqualDataGridFilteringLabel => r'Əvvəl Və ya Bərabər'; @override - String get daySpanCountLabel => r'Дзень'; + String get beginsWithDataGridFilteringLabel => r'ilə başlayır'; @override - String get dhualhiLabel => r'Зу аль-Хіджа'; + String get cancelDataGridFilteringLabel => r'Ləğv et'; @override - String get dhualqiLabel => r'Зу аль-Кіда'; + String get clearFilterDataGridFilteringLabel => r'Filtri təmizləyin'; @override - String get jumada1Label => r'Джумада аль-авваль'; + String get containsDataGridFilteringLabel => r'ehtiva edir'; @override - String get jumada2Label => r'Джумада аль-Тані'; + String get dateFiltersDataGridFilteringLabel => r'Tarix Filtrləri'; @override - String get muharramLabel => r'Мухарам'; + String get daySpanCountLabel => r'Gün'; @override - String get noEventsCalendarLabel => r'Няма падзей'; + String get dhualhiLabel => r'Zilhiccə'; @override - String get noSelectedDateCalendarLabel => r'Няма выбранай даты'; + String get dhualqiLabel => r'Zilqidə'; @override - String get ofDataPagerLabel => r'з'; + String get doesNotBeginWithDataGridFilteringLabel => r'İlə Başlamaz'; @override - String get pagesDataPagerLabel => r'старонак'; + String get doesNotContainDataGridFilteringLabel => r'Tərkibində Yoxdur'; @override - String get passwordDialogContentLabel => - r'Увядзіце пароль, каб адкрыць гэты файл PDF'; + String get doesNotEndWithDataGridFilteringLabel => r'İlə Bitmir'; @override - String get passwordDialogHeaderTextLabel => r'Абаронены паролем'; + String get doesNotEqualDataGridFilteringLabel => r'Bərabər Deyil'; @override - String get passwordDialogHintTextLabel => r'Увядзіце пароль'; + String get emptyDataGridFilteringLabel => r'Boş'; @override - String get passwordDialogInvalidPasswordLabel => r'Няправільны пароль'; + String get endsWithDataGridFilteringLabel => r'ilə bitir'; @override - String get pdfBookmarksLabel => r'Закладкі'; + String get equalsDataGridFilteringLabel => r'Bərabərdir'; @override - String get pdfEnterPageNumberLabel => r'Увядзіце нумар старонкі'; + String get fromDataGridFilteringLabel => r'From'; @override - String get pdfGoToPageLabel => r'Перайсці на старонку'; + String get greaterThanDataGridFilteringLabel => r'Böyük'; @override - String get pdfInvalidPageNumberLabel => - r'Калі ласка, увядзіце сапраўдны нумар'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Böyük Və ya Bərabər'; @override - String get pdfNoBookmarksLabel => r'Закладак не знойдзена'; + String get jumada1Label => r'Cümə əl-əvvəl'; @override - String get pdfPaginationDialogCancelLabel => r'АДМЕНАЦЬ'; + String get jumada2Label => r'Cüməda əl-sani'; @override - String get pdfPaginationDialogOkLabel => r'добра'; + String get lessThanDataGridFilteringLabel => r'Daha az'; @override - String get pdfPasswordDialogCancelLabel => r'АДМЕНАЦЬ'; + String get lessThanOrEqualDataGridFilteringLabel => r'Az Və ya Bərabər'; @override - String get pdfPasswordDialogOpenLabel => r'АДКРЫЦЬ'; + String get muharramLabel => r'Məhərrəm'; @override - String get pdfScrollStatusOfLabel => r'з'; + String get noEventsCalendarLabel => r'Tədbir yoxdur'; @override - String get rabi1Label => r'Рабі аль-авваль'; + String get noMatchesDataGridFilteringLabel => r'Uyğunluq yoxdur'; @override - String get rabi2Label => r'Рабі аль-Тані'; + String get noSelectedDateCalendarLabel => r'Seçilmiş tarix yoxdur'; @override - String get rajabLabel => r'Раджаб'; + String get notEmptyDataGridFilteringLabel => r'Boş deyil'; @override - String get ramadanLabel => r'Рамадан'; + String get notNullDataGridFilteringLabel => r'Null deyil'; @override - String get rowsPerPageDataPagerLabel => r'Радкі на старонцы'; + String get nullDataGridFilteringLabel => r'Sıfır'; @override - String get safarLabel => r'Сафар'; + String get numberFiltersDataGridFilteringLabel => r'Nömrə Filtrləri'; @override - String get series => r'Серыял'; + String get ofDataPagerLabel => r'of'; @override - String get shaabanLabel => r'Шаабан'; + String get okDataGridFilteringLabel => r'tamam'; @override - String get shawwalLabel => r'Шаўваль'; + String get orDataGridFilteringLabel => r'Və ya'; @override - String get shortDhualhiLabel => r'Зуль-Х'; + String get pagesDataPagerLabel => r'səhifələr'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get passwordDialogContentLabel => + r'Bu PDF faylını açmaq üçün parolu daxil edin'; @override - String get shortJumada1Label => r'Jum. я'; + String get passwordDialogHeaderTextLabel => r'Parol Qorunur'; @override - String get shortJumada2Label => r'Jum. II'; + String get passwordDialogHintTextLabel => r'Parol daxil edin'; @override - String get shortMuharramLabel => r'Мух.'; + String get passwordDialogInvalidPasswordLabel => r'etibarsız Şifrə'; @override - String get shortRabi1Label => r'Рабі. я'; + String get pdfBookmarksLabel => r'Əlfəcinlər'; @override - String get shortRabi2Label => r'Рабі. II'; + String get pdfEnterPageNumberLabel => r'Səhifə nömrəsini daxil edin'; @override - String get shortRajabLabel => r'Радж.'; + String get pdfGoToPageLabel => r'Səhifəyə daxil ol'; @override - String get shortRamadanLabel => r'Баран.'; + String get pdfHyperlinkContentLabel => + r'ünvanında səhifəni açmaq istəyirsiniz'; @override - String get shortSafarLabel => r'Саф.'; + String get pdfHyperlinkDialogCancelLabel => r'LƏĞV EDİN'; @override - String get shortShaabanLabel => r'Ша.'; + String get pdfHyperlinkDialogOpenLabel => r'AÇIQ'; @override - String get shortShawwalLabel => r'Шо.'; + String get pdfHyperlinkLabel => r'Veb səhifəni açın'; @override - String get todayLabel => r'Сёння'; + String get pdfInvalidPageNumberLabel => r'Etibarlı nömrə daxil edin'; @override - String get weeknumberLabel => r'Тыдзень'; -} - -/// The translations for Bulgarian (`bg`). -class SfLocalizationsBg extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsBg class - const SfLocalizationsBg({ - String localeName = 'bg', - }) : super( - localeName: localeName, - ); + String get pdfNoBookmarksLabel => r'Əlfəcin tapılmadı'; @override - String get allDayLabel => r'Цял ден'; + String get pdfPaginationDialogCancelLabel => r'LƏĞV EDİN'; @override - String get allowedViewDayLabel => r'ден'; + String get pdfPaginationDialogOkLabel => r'Tamam'; @override - String get allowedViewMonthLabel => r'месец'; + String get pdfPasswordDialogCancelLabel => r'LƏĞV EDİN'; @override - String get allowedViewScheduleLabel => r'График'; + String get pdfPasswordDialogOpenLabel => r'AÇIQ'; @override - String get allowedViewTimelineDayLabel => r'Ден на хронологията'; + String get pdfScrollStatusOfLabel => r'of'; @override - String get allowedViewTimelineMonthLabel => r'Месец на хронологията'; + String get pdfSignaturePadDialogClearLabel => r'TƏMİZLƏ'; @override - String get allowedViewTimelineWeekLabel => r'Седмица на хронологията'; + String get pdfSignaturePadDialogHeaderTextLabel => r'İmzanızı çəkin'; @override - String get allowedViewTimelineWorkWeekLabel => r'График на работната седмица'; + String get pdfSignaturePadDialogPenColorLabel => r'Qələm Rəngi'; @override - String get allowedViewWeekLabel => r'седмица'; + String get pdfSignaturePadDialogSaveLabel => r'YADDA SAXLA'; @override - String get allowedViewWorkWeekLabel => r'Работна седмица'; + String get pdfTextSelectionMenuCopyLabel => r'Kopyalayın'; @override - String get daySpanCountLabel => r'ден'; + String get pdfTextSelectionMenuHighlightLabel => r'Vurğulayın'; @override - String get dhualhiLabel => r'Зу ал-Хиджа'; + String get pdfTextSelectionMenuSquigglyLabel => r'Dalğalı'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Üstündən xətt çək'; @override - String get jumada1Label => r'Джумада ал-аввал'; + String get pdfTextSelectionMenuUnderlineLabel => r'Altını çiz'; @override - String get jumada2Label => r'Джумада ал-Тани'; + String get rabi1Label => r'Rəbiul-əvvəl'; @override - String get muharramLabel => r'Мухарам'; + String get rabi2Label => r'Rəbi əl-sani'; @override - String get noEventsCalendarLabel => r'Няма събития'; + String get rajabLabel => r'Rəcəb'; @override - String get noSelectedDateCalendarLabel => r'Няма избрана дата'; + String get ramadanLabel => r'Ramazan'; @override - String get ofDataPagerLabel => r'на'; + String get rowsPerPageDataPagerLabel => r'Səhifə başına satırlar'; @override - String get pagesDataPagerLabel => r'страници'; + String get safarLabel => r'Səfər'; @override - String get passwordDialogContentLabel => - r'Въведете паролата, за да отворите този PDF файл'; + String get searchDataGridFilteringLabel => r'Axtar'; @override - String get passwordDialogHeaderTextLabel => r'Защитен с парола'; + String get selectAllDataGridFilteringLabel => r'Hamısını seç'; @override - String get passwordDialogHintTextLabel => r'Въведете паролата'; + String get series => r'Serial'; @override - String get passwordDialogInvalidPasswordLabel => r'Невалидна парола'; + String get shaabanLabel => r'Şaban'; @override - String get pdfBookmarksLabel => r'Отметки'; + String get shawwalLabel => r'Şəvval'; @override - String get pdfEnterPageNumberLabel => r'Въведете номера на страницата'; + String get shortDhualhiLabel => r'Zül-H'; @override - String get pdfGoToPageLabel => r'Отиди на страница'; + String get shortDhualqiLabel => r'Zül-Q'; @override - String get pdfInvalidPageNumberLabel => r'Моля въведете валиден номер'; + String get shortJumada1Label => r'Cümə. I'; @override - String get pdfNoBookmarksLabel => r'Няма намерени отметки'; + String get shortJumada2Label => r'Cümə. II'; @override - String get pdfPaginationDialogCancelLabel => r'ОТМЕНИ'; + String get shortMuharramLabel => r'Muh.'; @override - String get pdfPaginationDialogOkLabel => r'Добре'; + String get shortRabi1Label => r'Rəbi. I'; @override - String get pdfPasswordDialogCancelLabel => r'ОТМЕНИ'; + String get shortRabi2Label => r'Rəbi. II'; @override - String get pdfPasswordDialogOpenLabel => r'ОТВОРЕНО'; + String get shortRajabLabel => r'Raj.'; @override - String get pdfScrollStatusOfLabel => r'на'; + String get shortRamadanLabel => r'Ram.'; @override - String get rabi1Label => r'Раби ал-аввал'; + String get shortSafarLabel => r'Saf.'; @override - String get rabi2Label => r'Раби ал-Тани'; + String get shortShaabanLabel => r'Şa.'; @override - String get rajabLabel => r'Раджаб'; + String get shortShawwalLabel => r'Şou.'; @override - String get ramadanLabel => r'Рамадан'; + String get showRowsWhereDataGridFilteringLabel => + r'Sətirləri harada göstərin'; @override - String get rowsPerPageDataPagerLabel => r'Редове на страница'; + String get sortAToZDataGridFilteringLabel => r'A-dan Z-yə çeşidləyin'; @override - String get safarLabel => r'Сафар'; + String get sortAndFilterDataGridFilteringLabel => r'Çeşidləyin və Filtr edin'; @override - String get series => r'Серия'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ən böyükdən kiçiyə çeşidləyin'; @override - String get shaabanLabel => r'Шаабан'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Ən yenidən köhnəyə çeşidləyin'; @override - String get shawwalLabel => r'Shawwal'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Ən köhnədən ən yeniyə çeşidləyin'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ən kiçikdən böyüyə çeşidləyin'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get sortZToADataGridFilteringLabel => r'Z-dən A sıralayın'; @override - String get shortJumada1Label => r'Jum. аз'; + String get textFiltersDataGridFilteringLabel => r'Mətn Filtrləri'; @override - String get shortJumada2Label => r'Jum. II'; + String get todayLabel => r'Bu gün'; @override - String get shortMuharramLabel => r'Мъх'; + String get weeknumberLabel => r'Həftə'; +} + +/// The translations for Belarusian (`be`). +class SfLocalizationsBe extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsBe class + const SfLocalizationsBe({String localeName = 'be'}) + : super(localeName: localeName); @override - String get shortRabi1Label => r'Раби. аз'; + String get afterDataGridFilteringLabel => r'Пасля'; @override - String get shortRabi2Label => r'Раби. II'; + String get afterOrEqualDataGridFilteringLabel => r'Пасля або роўна'; @override - String get shortRajabLabel => r'Радж.'; + String get allDayLabel => r'Увесь дзень'; @override - String get shortRamadanLabel => r'Рам.'; + String get allowedViewDayLabel => r'Дзень'; @override - String get shortSafarLabel => r'Saf.'; + String get allowedViewMonthLabel => r'Месяц'; @override - String get shortShaabanLabel => r'Ша'; + String get allowedViewScheduleLabel => r'Расклад'; @override - String get shortShawwalLabel => r'Шоу.'; + String get allowedViewTimelineDayLabel => r'Дзень часовай шкалы'; @override - String get todayLabel => r'днес'; + String get allowedViewTimelineMonthLabel => r'Часавая шкала месяца'; @override - String get weeknumberLabel => r'седмица'; -} + String get allowedViewTimelineWeekLabel => r'Часавая шкала тыдня'; -/// The translations for Bengali Bangla (`bn`). -class SfLocalizationsBn extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsBn class - const SfLocalizationsBn({ - String localeName = 'bn', - }) : super( - localeName: localeName, - ); + @override + String get allowedViewTimelineWorkWeekLabel => + r'Часавая шкала працоўнага тыдня'; @override - String get allDayLabel => r'সারাদিন'; + String get allowedViewWeekLabel => r'Тыдзень'; @override - String get allowedViewDayLabel => r'দিন'; + String get allowedViewWorkWeekLabel => r'Працоўны тыдзень'; @override - String get allowedViewMonthLabel => r'মাস'; + String get andDataGridFilteringLabel => r'І'; @override - String get allowedViewScheduleLabel => r'সময়সূচী'; + String get beforeDataGridFilteringLabel => r'Раней'; @override - String get allowedViewTimelineDayLabel => r'টাইমলাইন দিন'; + String get beforeOrEqualDataGridFilteringLabel => r'Раней або роўна'; @override - String get allowedViewTimelineMonthLabel => r'টাইমলাইন মাস'; + String get beginsWithDataGridFilteringLabel => r'Пачынаецца з'; @override - String get allowedViewTimelineWeekLabel => r'টাইমলাইন সপ্তাহ'; + String get cancelDataGridFilteringLabel => r'Адмяніць'; @override - String get allowedViewTimelineWorkWeekLabel => r'টাইমলাইন কাজের সপ্তাহ'; + String get clearFilterDataGridFilteringLabel => r'Ачысціць фільтр'; @override - String get allowedViewWeekLabel => r'সপ্তাহ'; + String get containsDataGridFilteringLabel => r'Змяшчае'; @override - String get allowedViewWorkWeekLabel => r'কর্ম সপ্তাহ'; + String get dateFiltersDataGridFilteringLabel => r'Фільтры даты'; @override - String get daySpanCountLabel => r'দিন'; + String get daySpanCountLabel => r'Дзень'; @override - String get dhualhiLabel => r'জুল হিজ্জাহ'; + String get dhualhiLabel => r'Зу аль-Хіджа'; @override - String get dhualqiLabel => r'যুল-কিদাহ'; + String get dhualqiLabel => r'Зу аль-Кіда'; @override - String get jumada1Label => r'জুমাদা আল আউয়াল'; + String get doesNotBeginWithDataGridFilteringLabel => r'Не пачынаецца з'; @override - String get jumada2Label => r'জুমাদা আল-থানি'; + String get doesNotContainDataGridFilteringLabel => r'Не ўтрымлівае'; @override - String get muharramLabel => r'মহররম'; + String get doesNotEndWithDataGridFilteringLabel => r'Не заканчваецца'; @override - String get noEventsCalendarLabel => r'কোনো ঘটনা নেই'; + String get doesNotEqualDataGridFilteringLabel => r'Не роўна'; @override - String get noSelectedDateCalendarLabel => r'কোনো নির্বাচিত তারিখ নেই'; + String get emptyDataGridFilteringLabel => r'Пусты'; @override - String get ofDataPagerLabel => r'এর'; + String get endsWithDataGridFilteringLabel => r'Заканчваецца с'; @override - String get pagesDataPagerLabel => r'পৃষ্ঠাগুলি'; + String get equalsDataGridFilteringLabel => r'Роўнае'; @override - String get passwordDialogContentLabel => - r'এই PDF ফাইল খুলতে পাসওয়ার্ড লিখুন'; + String get fromDataGridFilteringLabel => r'Ад'; @override - String get passwordDialogHeaderTextLabel => r'পাসওয়ার্ড সুরক্ষিত'; + String get greaterThanDataGridFilteringLabel => r'Больш чым'; @override - String get passwordDialogHintTextLabel => r'পাসওয়ার্ড লিখুন'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Больш або роўна'; @override - String get passwordDialogInvalidPasswordLabel => r'অবৈধ পাসওয়ার্ড'; + String get jumada1Label => r'Джумада аль-аўваль'; @override - String get pdfBookmarksLabel => r'বুকমার্ক'; + String get jumada2Label => r'Джумада аль-тані'; @override - String get pdfEnterPageNumberLabel => r'পৃষ্ঠা নম্বর লিখুন'; + String get lessThanDataGridFilteringLabel => r'Менш чым'; @override - String get pdfGoToPageLabel => r'পৃষ্ঠায় যান'; + String get lessThanOrEqualDataGridFilteringLabel => r'Менш або роўна'; @override - String get pdfInvalidPageNumberLabel => r'একটি বৈধ নম্বর লিখুন'; + String get muharramLabel => r'Мухарам'; @override - String get pdfNoBookmarksLabel => r'কোন বুকমার্ক পাওয়া যায়নি'; + String get noEventsCalendarLabel => r'Няма падзей'; @override - String get pdfPaginationDialogCancelLabel => r'বাতিল করুন'; + String get noMatchesDataGridFilteringLabel => r'Супадзенняў няма'; @override - String get pdfPaginationDialogOkLabel => r'ঠিক আছে'; + String get noSelectedDateCalendarLabel => r'Дата не выбрана'; @override - String get pdfPasswordDialogCancelLabel => r'বাতিল করুন'; + String get notEmptyDataGridFilteringLabel => r'Не пусты'; @override - String get pdfPasswordDialogOpenLabel => r'খোলা'; + String get notNullDataGridFilteringLabel => r'Не Null'; @override - String get pdfScrollStatusOfLabel => r'এর'; + String get nullDataGridFilteringLabel => r'Нуль'; @override - String get rabi1Label => r'রবিউল আউয়াল'; + String get numberFiltersDataGridFilteringLabel => r'Лікавыя фільтры'; @override - String get rabi2Label => r'রাবি' "'" r'আল-থানি'; + String get ofDataPagerLabel => r'з'; @override - String get rajabLabel => r'রজব'; + String get okDataGridFilteringLabel => r'добра'; @override - String get ramadanLabel => r'রমজান'; + String get orDataGridFilteringLabel => r'Або'; @override - String get rowsPerPageDataPagerLabel => r'প্রতি পৃষ্ঠায় সারি'; + String get pagesDataPagerLabel => r'старонкі'; @override - String get safarLabel => r'সাফার'; + String get passwordDialogContentLabel => + r'Увядзіце пароль, каб адкрыць гэты файл PDF'; @override - String get series => r'সিরিজ'; + String get passwordDialogHeaderTextLabel => r'Абарона паролем'; @override - String get shaabanLabel => r'শা' "'" r'বান'; + String get passwordDialogHintTextLabel => r'Увядзіце пароль'; @override - String get shawwalLabel => r'শাওয়াল'; + String get passwordDialogInvalidPasswordLabel => r'Няправільны пароль'; @override - String get shortDhualhiLabel => r'ধুল-এইচ'; + String get pdfBookmarksLabel => r'Закладкі'; @override - String get shortDhualqiLabel => r'যুল-ক্ব'; + String get pdfEnterPageNumberLabel => r'Увядзіце нумар старонкі'; @override - String get shortJumada1Label => r'জুম। আমি'; + String get pdfGoToPageLabel => r'Перайсці на старонку'; @override - String get shortJumada2Label => r'জুম। ২'; + String get pdfHyperlinkContentLabel => + r'Вы хочаце адкрыць старонку па адрасе'; @override - String get shortMuharramLabel => r'মুহ.'; + String get pdfHyperlinkDialogCancelLabel => r'СКАСАВАЦЬ'; @override - String get shortRabi1Label => r'রাবি। আমি'; + String get pdfHyperlinkDialogOpenLabel => r'АДКРЫТАЯ'; @override - String get shortRabi2Label => r'রাবি। ২'; + String get pdfHyperlinkLabel => r'Адкрыць вэб-старонку'; @override - String get shortRajabLabel => r'রাজ।'; + String get pdfInvalidPageNumberLabel => r'Увядзіце правільны нумар'; @override - String get shortRamadanLabel => r'র্যাম.'; + String get pdfNoBookmarksLabel => r'Закладкі не знойдзены'; @override - String get shortSafarLabel => r'সাফ.'; + String get pdfPaginationDialogCancelLabel => r'СКАСАВАЦЬ'; @override - String get shortShaabanLabel => r'শা.'; + String get pdfPaginationDialogOkLabel => r'ОК'; @override - String get shortShawwalLabel => r'শ.'; + String get pdfPasswordDialogCancelLabel => r'СКАСАВАЦЬ'; @override - String get todayLabel => r'আজ'; + String get pdfPasswordDialogOpenLabel => r'АДКРЫТАЯ'; @override - String get weeknumberLabel => r'সপ্তাহ'; -} + String get pdfScrollStatusOfLabel => r'з'; -/// The translations for Bosnian (`bs`). -class SfLocalizationsBs extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsBs class - const SfLocalizationsBs({ - String localeName = 'bs', - }) : super( - localeName: localeName, - ); + @override + String get pdfSignaturePadDialogClearLabel => r'АЧЫСТКА'; @override - String get allDayLabel => r'Cijeli dan'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Намалюйце свой подпіс'; @override - String get allowedViewDayLabel => r'Dan'; + String get pdfSignaturePadDialogPenColorLabel => r'Колер ручкі'; @override - String get allowedViewMonthLabel => r'Mjesec'; + String get pdfSignaturePadDialogSaveLabel => r'ЗАХАВАЦЬ'; @override - String get allowedViewScheduleLabel => r'Raspored'; + String get pdfTextSelectionMenuCopyLabel => r'Капіяваць'; @override - String get allowedViewTimelineDayLabel => r'Timeline Day'; + String get pdfTextSelectionMenuHighlightLabel => r'Вылучыць'; @override - String get allowedViewTimelineMonthLabel => r'Timeline Month'; + String get pdfTextSelectionMenuSquigglyLabel => r'Звілістыя'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Закрэсліванне'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Vremenski okvir Radna sedmica'; + String get pdfTextSelectionMenuUnderlineLabel => r'Падкрэсліце'; @override - String get allowedViewWeekLabel => r'Sedmica'; + String get rabi1Label => r'Рабі аль-аўваль'; @override - String get allowedViewWorkWeekLabel => r'Radna sedmica'; + String get rabi2Label => r'Рабі аль-тані'; @override - String get daySpanCountLabel => r'Dan'; + String get rajabLabel => r'Раджаб'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get ramadanLabel => r'Рамадан'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get rowsPerPageDataPagerLabel => r'Радкоў на старонцы'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get safarLabel => r'Сафар'; @override - String get jumada2Label => r'Jumada al-thani'; + String get searchDataGridFilteringLabel => r'Пошук'; @override - String get muharramLabel => r'Muharram'; + String get selectAllDataGridFilteringLabel => r'Абраць усё'; @override - String get noEventsCalendarLabel => r'Nema događaja'; + String get series => r'серыял'; @override - String get noSelectedDateCalendarLabel => r'Nema odabranog datuma'; + String get shaabanLabel => r'Шаабан'; @override - String get ofDataPagerLabel => r'of'; + String get shawwalLabel => r'Шаўваль'; @override - String get pagesDataPagerLabel => r'stranice'; + String get shortDhualhiLabel => + r'Зу' + "'" + r'л-Х'; @override - String get passwordDialogContentLabel => - r'Unesite lozinku za otvaranje ove PDF datoteke'; + String get shortDhualqiLabel => + r'Зу' + "'" + r'л-К' + "'" + r'ю'; @override - String get passwordDialogHeaderTextLabel => r'Zaštićeno lozinkom'; + String get shortJumada1Label => r'Джам. я'; @override - String get passwordDialogHintTextLabel => r'Unesite lozinku'; + String get shortJumada2Label => r'Джам. II'; @override - String get passwordDialogInvalidPasswordLabel => r'Nevažeća lozinka'; + String get shortMuharramLabel => r'мух'; @override - String get pdfBookmarksLabel => r'Bookmarks'; + String get shortRabi1Label => r'Рабіна. я'; @override - String get pdfEnterPageNumberLabel => r'Unesite broj stranice'; + String get shortRabi2Label => r'Рабіна. II'; @override - String get pdfGoToPageLabel => r'Idi na stranicu'; + String get shortRajabLabel => r'Радж.'; @override - String get pdfInvalidPageNumberLabel => r'Unesite ispravan broj'; + String get shortRamadanLabel => r'Таран.'; @override - String get pdfNoBookmarksLabel => r'Nema pronađenih oznaka'; + String get shortSafarLabel => r'Саф.'; @override - String get pdfPaginationDialogCancelLabel => r'OTKAZI'; + String get shortShaabanLabel => r'Ша.'; @override - String get pdfPaginationDialogOkLabel => r'uredu'; + String get shortShawwalLabel => r'Шоу.'; @override - String get pdfPasswordDialogCancelLabel => r'OTKAZI'; + String get showRowsWhereDataGridFilteringLabel => r'Паказаць радкі, дзе'; @override - String get pdfPasswordDialogOpenLabel => r'OTVOREN'; + String get sortAToZDataGridFilteringLabel => r'Сартаванне ад А да Я'; @override - String get pdfScrollStatusOfLabel => r'of'; + String get sortAndFilterDataGridFilteringLabel => r'Сартаваць і фільтраваць'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Сартаваць ад самага вялікага да меншага'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Сартаваць ад найноўшых да самых старых'; @override - String get rajabLabel => r'Rajab'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Сартаваць ад старых да новых'; @override - String get ramadanLabel => r'Ramazan'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Сартаванне ад найменшага да самага вялікага'; @override - String get rowsPerPageDataPagerLabel => r'Redova po stranici'; + String get sortZToADataGridFilteringLabel => r'Сартаваць ад Я да А'; @override - String get safarLabel => r'Safar'; + String get textFiltersDataGridFilteringLabel => r'Тэкставыя фільтры'; @override - String get series => r'Serije'; + String get todayLabel => r'Сёння'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get weeknumberLabel => r'Тыдзень'; +} + +/// The translations for Bulgarian (`bg`). +class SfLocalizationsBg extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsBg class + const SfLocalizationsBg({String localeName = 'bg'}) + : super(localeName: localeName); @override - String get shawwalLabel => r'Shawwal'; + String get afterDataGridFilteringLabel => r'След'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get afterOrEqualDataGridFilteringLabel => r'След Или Равно'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get allDayLabel => r'Цял ден'; @override - String get shortJumada1Label => r'Jum. I'; + String get allowedViewDayLabel => r'Ден'; @override - String get shortJumada2Label => r'Jum. II'; + String get allowedViewMonthLabel => r'Месец'; @override - String get shortMuharramLabel => r'Muh.'; + String get allowedViewScheduleLabel => r'График'; @override - String get shortRabi1Label => r'Rabi. I'; + String get allowedViewTimelineDayLabel => r'Хронология на деня'; @override - String get shortRabi2Label => r'Rabi. II'; + String get allowedViewTimelineMonthLabel => r'Времева линия Месец'; @override - String get shortRajabLabel => r'Raj.'; + String get allowedViewTimelineWeekLabel => r'Хронология на седмицата'; @override - String get shortRamadanLabel => r'RAM.'; + String get allowedViewTimelineWorkWeekLabel => + r'Времева линия Работна седмица'; @override - String get shortSafarLabel => r'Saf.'; + String get allowedViewWeekLabel => r'Седмица'; @override - String get shortShaabanLabel => r'Sha.'; + String get allowedViewWorkWeekLabel => r'Работна седмица'; @override - String get shortShawwalLabel => r'Shaw.'; + String get andDataGridFilteringLabel => r'И'; @override - String get todayLabel => r'Danas'; + String get beforeDataGridFilteringLabel => r'Преди'; @override - String get weeknumberLabel => r'Sedmica'; -} + String get beforeOrEqualDataGridFilteringLabel => r'Преди или Равно'; -/// The translations for Catalan Valencian (`ca`). -class SfLocalizationsCa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsCa class - const SfLocalizationsCa({ - String localeName = 'ca', - }) : super( - localeName: localeName, - ); + @override + String get beginsWithDataGridFilteringLabel => r'Започва с'; @override - String get allDayLabel => r'Tot el dia'; + String get cancelDataGridFilteringLabel => r'Отказ'; @override - String get allowedViewDayLabel => r'Dia'; + String get clearFilterDataGridFilteringLabel => r'Изчистване на филтъра'; @override - String get allowedViewMonthLabel => r'Mes'; + String get containsDataGridFilteringLabel => r'Съдържа'; @override - String get allowedViewScheduleLabel => r'Horari'; + String get dateFiltersDataGridFilteringLabel => r'Филтри за дата'; @override - String get allowedViewTimelineDayLabel => r'Dia del cronograma'; + String get daySpanCountLabel => r'ден'; @override - String get allowedViewTimelineMonthLabel => r'Mes cronològic'; + String get dhualhiLabel => r'Зу ал-Хиджа'; @override - String get allowedViewTimelineWeekLabel => r'Setmana del cronograma'; + String get dhualqiLabel => r'Зу ал-Кида'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Setmana de treball cronològic'; + String get doesNotBeginWithDataGridFilteringLabel => r'Не започва с'; @override - String get allowedViewWeekLabel => r'Setmana'; + String get doesNotContainDataGridFilteringLabel => r'Не съдържа'; @override - String get allowedViewWorkWeekLabel => r'Setmana Laboral'; + String get doesNotEndWithDataGridFilteringLabel => r'Не завършва с'; @override - String get daySpanCountLabel => r'Dia'; + String get doesNotEqualDataGridFilteringLabel => r'Не е равно'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get emptyDataGridFilteringLabel => r'празна'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get endsWithDataGridFilteringLabel => r'Завършва със'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get equalsDataGridFilteringLabel => r'Се равнява'; @override - String get jumada2Label => r'Jumada al-thani'; + String get fromDataGridFilteringLabel => r'от'; @override - String get muharramLabel => r'Muharram'; + String get greaterThanDataGridFilteringLabel => r'По-голяма от'; @override - String get noEventsCalendarLabel => r'No hi ha esdeveniments'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'По-голямо от или равно'; @override - String get noSelectedDateCalendarLabel => r'Cap data seleccionada'; + String get jumada1Label => r'Джумада ал-аввал'; @override - String get ofDataPagerLabel => r'de'; + String get jumada2Label => r'Джумада ал-тани'; @override - String get pagesDataPagerLabel => r'pàgines'; + String get lessThanDataGridFilteringLabel => r'По-малко от'; @override - String get passwordDialogContentLabel => - r'Introduïu la contrasenya per obrir aquest fitxer PDF'; + String get lessThanOrEqualDataGridFilteringLabel => r'По-малко или равно'; @override - String get passwordDialogHeaderTextLabel => r'Protegit amb contrasenya'; + String get muharramLabel => r'Мухаррам'; @override - String get passwordDialogHintTextLabel => r'Introduir la contrasenya'; + String get noEventsCalendarLabel => r'Няма събития'; @override - String get passwordDialogInvalidPasswordLabel => r'contrasenya invàlida'; + String get noMatchesDataGridFilteringLabel => r'Няма съвпадения'; @override - String get pdfBookmarksLabel => r'Adreces d' "'" r'interès'; + String get noSelectedDateCalendarLabel => r'Няма избрана дата'; @override - String get pdfEnterPageNumberLabel => r'Introduïu el número de pàgina'; + String get notEmptyDataGridFilteringLabel => r'Не е празно'; @override - String get pdfGoToPageLabel => r'Ves a la pàgina'; + String get notNullDataGridFilteringLabel => r'Не е нула'; @override - String get pdfInvalidPageNumberLabel => r'Introduïu un número vàlid'; + String get nullDataGridFilteringLabel => r'Нула'; @override - String get pdfNoBookmarksLabel => - r'No s' "'" r'han trobat adreces d' "'" r'interès'; + String get numberFiltersDataGridFilteringLabel => r'Числови филтри'; @override - String get pdfPaginationDialogCancelLabel => r'CANCEL · LAR'; + String get ofDataPagerLabel => r'на'; @override - String get pdfPaginationDialogOkLabel => r'D' "'" r'acord'; + String get okDataGridFilteringLabel => r'Добре'; @override - String get pdfPasswordDialogCancelLabel => r'CANCEL · LAR'; + String get orDataGridFilteringLabel => r'Или'; @override - String get pdfPasswordDialogOpenLabel => r'OBERT'; + String get pagesDataPagerLabel => r'страници'; @override - String get pdfScrollStatusOfLabel => r'de'; + String get passwordDialogContentLabel => + r'Въведете паролата, за да отворите този PDF файл'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get passwordDialogHeaderTextLabel => r'Защитен с парола'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get passwordDialogHintTextLabel => r'Въведете паролата'; @override - String get rajabLabel => r'Rajab'; + String get passwordDialogInvalidPasswordLabel => r'Невалидна парола'; @override - String get ramadanLabel => r'Ramadà'; + String get pdfBookmarksLabel => r'Отметки'; @override - String get rowsPerPageDataPagerLabel => r'Files per pàgina'; + String get pdfEnterPageNumberLabel => r'Въведете номера на страницата'; @override - String get safarLabel => r'Safar'; + String get pdfGoToPageLabel => r'Отиди на страница'; @override - String get series => r'Sèrie'; + String get pdfHyperlinkContentLabel => r'Искате ли да отворите страницата на'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get pdfHyperlinkDialogCancelLabel => r'ОТМЕНЯНЕ'; @override - String get shawwalLabel => r'Shawwal'; + String get pdfHyperlinkDialogOpenLabel => r'ОТВОРЕНО'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get pdfHyperlinkLabel => r'Отворете уеб страницата'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get pdfInvalidPageNumberLabel => r'Моля въведете валиден номер'; @override - String get shortJumada1Label => r'Jum. jo'; + String get pdfNoBookmarksLabel => r'Няма намерени отметки'; @override - String get shortJumada2Label => r'Jum. II'; + String get pdfPaginationDialogCancelLabel => r'ОТМЕНЯНЕ'; @override - String get shortMuharramLabel => r'Muh.'; + String get pdfPaginationDialogOkLabel => r'Добре'; @override - String get shortRabi1Label => r'Rabi. jo'; + String get pdfPasswordDialogCancelLabel => r'ОТМЕНЯНЕ'; @override - String get shortRabi2Label => r'Rabi. II'; + String get pdfPasswordDialogOpenLabel => r'ОТВОРЕНО'; @override - String get shortRajabLabel => r'Raj.'; + String get pdfScrollStatusOfLabel => r'на'; @override - String get shortRamadanLabel => r'Ram.'; + String get pdfSignaturePadDialogClearLabel => r'ИЗЧИСТИ'; @override - String get shortSafarLabel => r'Saf.'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Начертайте своя подпис'; @override - String get shortShaabanLabel => r'Sha.'; + String get pdfSignaturePadDialogPenColorLabel => r'Цвят на писалката'; @override - String get shortShawwalLabel => r'Shaw.'; + String get pdfSignaturePadDialogSaveLabel => r'ЗАПАЗЕТЕ'; @override - String get todayLabel => r'Avui'; + String get pdfTextSelectionMenuCopyLabel => r'Копирай'; @override - String get weeknumberLabel => r'Setmana'; -} + String get pdfTextSelectionMenuHighlightLabel => r'Маркирайте'; -/// The translations for Czech (`cs`). -class SfLocalizationsCs extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsCs class - const SfLocalizationsCs({ - String localeName = 'cs', - }) : super( - localeName: localeName, - ); + @override + String get pdfTextSelectionMenuSquigglyLabel => r'криволичещо'; @override - String get allDayLabel => r'Celý den'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Зачертано'; @override - String get allowedViewDayLabel => r'Den'; + String get pdfTextSelectionMenuUnderlineLabel => r'Подчертайте'; @override - String get allowedViewMonthLabel => r'Měsíc'; + String get rabi1Label => r'Раби ал-аввал'; @override - String get allowedViewScheduleLabel => r'Plán'; + String get rabi2Label => r'Раби ал-тани'; @override - String get allowedViewTimelineDayLabel => r'Den časové osy'; + String get rajabLabel => r'Раджаб'; @override - String get allowedViewTimelineMonthLabel => r'Časová osa Měsíc'; + String get ramadanLabel => r'Рамадан'; @override - String get allowedViewTimelineWeekLabel => r'Časová osa týden'; + String get rowsPerPageDataPagerLabel => r'Редове на страница'; @override - String get allowedViewTimelineWorkWeekLabel => r'Časová osa pracovní týden'; + String get safarLabel => r'Сафар'; @override - String get allowedViewWeekLabel => r'Týden'; + String get searchDataGridFilteringLabel => r'Търсене'; @override - String get allowedViewWorkWeekLabel => r'Pracovní týden'; + String get selectAllDataGridFilteringLabel => r'Избери всички'; @override - String get daySpanCountLabel => r'Den'; + String get series => r'Серия'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get shaabanLabel => r'Шаабан'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get shawwalLabel => r'Шавал'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortDhualhiLabel => r'Зул-Х'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shortDhualqiLabel => r'Зул-Кю'; @override - String get muharramLabel => r'Muharram'; + String get shortJumada1Label => r'Джъм. аз'; @override - String get noEventsCalendarLabel => r'Žádné události'; + String get shortJumada2Label => r'Джъм. II'; @override - String get noSelectedDateCalendarLabel => r'Žádné vybrané datum'; + String get shortMuharramLabel => r'Мъх.'; @override - String get ofDataPagerLabel => r'z'; + String get shortRabi1Label => r'Раби. аз'; @override - String get pagesDataPagerLabel => r'stránky'; + String get shortRabi2Label => r'Раби. II'; @override - String get passwordDialogContentLabel => - r'Zadejte heslo pro otevření tohoto souboru PDF'; + String get shortRajabLabel => r'Радж.'; @override - String get passwordDialogHeaderTextLabel => r'Chráněno heslem'; + String get shortRamadanLabel => r'Рам'; @override - String get passwordDialogHintTextLabel => r'Zadejte heslo'; + String get shortSafarLabel => r'Саф.'; @override - String get passwordDialogInvalidPasswordLabel => r'Neplatné heslo'; + String get shortShaabanLabel => r'Ша.'; @override - String get pdfBookmarksLabel => r'Záložky'; + String get shortShawwalLabel => r'Шоу.'; @override - String get pdfEnterPageNumberLabel => r'Zadejte číslo stránky'; + String get showRowsWhereDataGridFilteringLabel => r'Покажи редове къде'; @override - String get pdfGoToPageLabel => r'Jdi na stránku'; + String get sortAToZDataGridFilteringLabel => r'Сортиране от А до Я'; @override - String get pdfInvalidPageNumberLabel => r'Prosím zadejte platné číslo'; + String get sortAndFilterDataGridFilteringLabel => r'Сортиране и филтриране'; @override - String get pdfNoBookmarksLabel => r'Nebyly nalezeny žádné záložky'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Сортиране от най-големия към най-малкия'; @override - String get pdfPaginationDialogCancelLabel => r'ZRUŠENÍ'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Сортиране от най-новите към най-старите'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Сортиране от най-старите към най-новите'; @override - String get pdfPasswordDialogCancelLabel => r'ZRUŠENÍ'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Сортиране от най-малкото към най-голямото'; @override - String get pdfPasswordDialogOpenLabel => r'OTEVŘENO'; + String get sortZToADataGridFilteringLabel => r'Сортиране от Я до А'; @override - String get pdfScrollStatusOfLabel => r'z'; + String get textFiltersDataGridFilteringLabel => r'Текстови филтри'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get todayLabel => r'Днес'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get weeknumberLabel => r'Седмица'; +} + +/// The translations for Bengali Bangla (`bn`). +class SfLocalizationsBn extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsBn class + const SfLocalizationsBn({String localeName = 'bn'}) + : super(localeName: localeName); @override - String get rajabLabel => r'Rajab'; + String get afterDataGridFilteringLabel => r'পরে'; @override - String get ramadanLabel => r'Ramadán'; + String get afterOrEqualDataGridFilteringLabel => r'পরে বা সমান'; @override - String get rowsPerPageDataPagerLabel => r'Řádky na stránku'; + String get allDayLabel => r'সারাদিন'; @override - String get safarLabel => r'Šafář'; + String get allowedViewDayLabel => r'দিন'; @override - String get series => r'Série'; + String get allowedViewMonthLabel => r'মাস'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get allowedViewScheduleLabel => r'সময়সূচী'; @override - String get shawwalLabel => r'Shawwal'; + String get allowedViewTimelineDayLabel => r'টাইমলাইন দিন'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get allowedViewTimelineMonthLabel => r'টাইমলাইন মাস'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get allowedViewTimelineWeekLabel => r'টাইমলাইন সপ্তাহ'; @override - String get shortJumada1Label => r'Jum. já'; + String get allowedViewTimelineWorkWeekLabel => r'টাইমলাইন কাজের সপ্তাহ'; @override - String get shortJumada2Label => r'Jum. II'; + String get allowedViewWeekLabel => r'সপ্তাহ'; @override - String get shortMuharramLabel => r'Muh.'; + String get allowedViewWorkWeekLabel => r'কর্ম সপ্তাহ'; @override - String get shortRabi1Label => r'Rabi. já'; + String get andDataGridFilteringLabel => r'এবং'; @override - String get shortRabi2Label => r'Rabi. II'; + String get beforeDataGridFilteringLabel => r'আগে'; @override - String get shortRajabLabel => r'Raj.'; + String get beforeOrEqualDataGridFilteringLabel => r'আগে বা সমান'; @override - String get shortRamadanLabel => r'RAM.'; + String get beginsWithDataGridFilteringLabel => r'সঙ্গে শুরু'; @override - String get shortSafarLabel => r'Saf.'; + String get cancelDataGridFilteringLabel => r'বাতিল করুন'; @override - String get shortShaabanLabel => r'Sha.'; + String get clearFilterDataGridFilteringLabel => r'স্বচ্ছ ছাকুনী'; @override - String get shortShawwalLabel => r'Shaw.'; + String get containsDataGridFilteringLabel => r'ধারণ করে'; @override - String get todayLabel => r'Dnes'; + String get dateFiltersDataGridFilteringLabel => r'তারিখ ফিল্টার'; @override - String get weeknumberLabel => r'Týden'; -} + String get daySpanCountLabel => r'দিন'; -/// The translations for Danish (`da`). -class SfLocalizationsDa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsDa class - const SfLocalizationsDa({ - String localeName = 'da', - }) : super( - localeName: localeName, - ); + @override + String get dhualhiLabel => r'জুল হিজ্জাহ'; @override - String get allDayLabel => r'Hele dagen'; + String get dhualqiLabel => r'যুল-কিদাহ'; @override - String get allowedViewDayLabel => r'Dag'; + String get doesNotBeginWithDataGridFilteringLabel => r'দিয়ে শুরু হয় না'; @override - String get allowedViewMonthLabel => r'Måned'; + String get doesNotContainDataGridFilteringLabel => r'থাকে না'; @override - String get allowedViewScheduleLabel => r'Tidsplan'; + String get doesNotEndWithDataGridFilteringLabel => r'দিয়ে শেষ হয় না'; @override - String get allowedViewTimelineDayLabel => r'Tidslinje Dag'; + String get doesNotEqualDataGridFilteringLabel => r'সমান না'; @override - String get allowedViewTimelineMonthLabel => r'Tidslinje måned'; + String get emptyDataGridFilteringLabel => r'খালি'; @override - String get allowedViewTimelineWeekLabel => r'Tidslinje uge'; + String get endsWithDataGridFilteringLabel => r'দিয়ে শেষ হয়'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbejdsuge'; + String get equalsDataGridFilteringLabel => r'সমান'; @override - String get allowedViewWeekLabel => r'Uge'; + String get fromDataGridFilteringLabel => r'থেকে'; @override - String get allowedViewWorkWeekLabel => r'Arbejdsuge'; + String get greaterThanDataGridFilteringLabel => r'অপেক্ষা বৃহত্তর'; @override - String get daySpanCountLabel => r'Dag'; + String get greaterThanOrEqualDataGridFilteringLabel => r'বৃহত্তর অথবা সমান'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get jumada1Label => r'জুমাদা আল আউয়াল'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get jumada2Label => r'জুমাদা আল-থানি'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get lessThanDataGridFilteringLabel => r'এর চেয়ে কম'; @override - String get jumada2Label => r'Jumada al-thani'; + String get lessThanOrEqualDataGridFilteringLabel => r'কম বা সমান'; @override - String get muharramLabel => r'Muharram'; + String get muharramLabel => r'মহরম'; @override - String get noEventsCalendarLabel => r'Ingen begivenheder'; + String get noEventsCalendarLabel => r'কোনো ঘটনা নেই'; @override - String get noSelectedDateCalendarLabel => r'Ingen valgt dato'; + String get noMatchesDataGridFilteringLabel => r'মিল নেই'; @override - String get ofDataPagerLabel => r'af'; + String get noSelectedDateCalendarLabel => r'কোনো তারিখ নির্বাচিত হয়নি'; @override - String get pagesDataPagerLabel => r'sider'; + String get notEmptyDataGridFilteringLabel => r'খালি না'; @override - String get passwordDialogContentLabel => - r'Indtast adgangskoden for at åbne denne PDF-fil'; + String get notNullDataGridFilteringLabel => r'নাল না'; @override - String get passwordDialogHeaderTextLabel => r'Adgangskodebeskyttet'; + String get nullDataGridFilteringLabel => r'শূন্য'; @override - String get passwordDialogHintTextLabel => r'Indtast adgangskode'; + String get numberFiltersDataGridFilteringLabel => r'নম্বর ফিল্টার'; @override - String get passwordDialogInvalidPasswordLabel => r'forkert kodeord'; + String get ofDataPagerLabel => r'এর'; @override - String get pdfBookmarksLabel => r'Bogmærker'; + String get okDataGridFilteringLabel => r'ঠিক আছে'; @override - String get pdfEnterPageNumberLabel => r'Indtast sidenummer'; + String get orDataGridFilteringLabel => r'বা'; @override - String get pdfGoToPageLabel => r'Gå til side'; + String get pagesDataPagerLabel => r'পৃষ্ঠাগুলি'; @override - String get pdfInvalidPageNumberLabel => r'Indtast venligst et gyldigt nummer'; + String get passwordDialogContentLabel => + r'এই পিডিএফ ফাইল খুলতে পাসওয়ার্ড লিখুন'; @override - String get pdfNoBookmarksLabel => r'Ingen bogmærker fundet'; + String get passwordDialogHeaderTextLabel => r'পাসওয়ার্ড সুরক্ষিত'; @override - String get pdfPaginationDialogCancelLabel => r'AFBESTILLE'; + String get passwordDialogHintTextLabel => r'পাসওয়ার্ড লিখুন'; @override - String get pdfPaginationDialogOkLabel => r'Okay'; + String get passwordDialogInvalidPasswordLabel => r'অবৈধ পাসওয়ার্ড'; @override - String get pdfPasswordDialogCancelLabel => r'AFBESTILLE'; + String get pdfBookmarksLabel => r'বুকমার্ক'; @override - String get pdfPasswordDialogOpenLabel => r'ÅBEN'; + String get pdfEnterPageNumberLabel => r'পৃষ্ঠা নম্বর লিখুন'; @override - String get pdfScrollStatusOfLabel => r'af'; + String get pdfGoToPageLabel => r'পৃষ্ঠায় যান'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get pdfHyperlinkContentLabel => r'আপনি পৃষ্ঠা খুলতে চান'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get pdfHyperlinkDialogCancelLabel => r'বাতিল করুন'; @override - String get rajabLabel => r'Rajab'; + String get pdfHyperlinkDialogOpenLabel => r'খোলা'; @override - String get ramadanLabel => r'Ramadan'; + String get pdfHyperlinkLabel => r'ওয়েব পেজ খুলুন'; @override - String get rowsPerPageDataPagerLabel => r'Rækker pr. side'; + String get pdfInvalidPageNumberLabel => r'একটি বৈধ নম্বর লিখুন'; @override - String get safarLabel => r'Safar'; + String get pdfNoBookmarksLabel => r'কোন বুকমার্ক পাওয়া যায়নি'; @override - String get series => r'Serie'; + String get pdfPaginationDialogCancelLabel => r'বাতিল করুন'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get pdfPaginationDialogOkLabel => r'ঠিক আছে'; @override - String get shawwalLabel => r'Shawwal'; + String get pdfPasswordDialogCancelLabel => r'বাতিল করুন'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get pdfPasswordDialogOpenLabel => r'খোলা'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get pdfScrollStatusOfLabel => r'এর'; @override - String get shortJumada1Label => r'Jum. jeg'; + String get pdfSignaturePadDialogClearLabel => r'পরিষ্কার'; @override - String get shortJumada2Label => r'Jum. II'; + String get pdfSignaturePadDialogHeaderTextLabel => r'আপনার স্বাক্ষর আঁকুন'; @override - String get shortMuharramLabel => r'Muh.'; + String get pdfSignaturePadDialogPenColorLabel => r'কলমের রঙ'; @override - String get shortRabi1Label => r'Rabi. jeg'; + String get pdfSignaturePadDialogSaveLabel => r'সংরক্ষণ'; @override - String get shortRabi2Label => r'Rabi. II'; + String get pdfTextSelectionMenuCopyLabel => r'কপি'; @override - String get shortRajabLabel => r'Raj.'; + String get pdfTextSelectionMenuHighlightLabel => r'লক্ষণীয় করা'; @override - String get shortRamadanLabel => r'Vædder.'; + String get pdfTextSelectionMenuSquigglyLabel => r'স্কুইগ্লি'; @override - String get shortSafarLabel => r'Saf.'; + String get pdfTextSelectionMenuStrikethroughLabel => r'স্ট্রাইকথ্রু'; @override - String get shortShaabanLabel => r'Sha.'; + String get pdfTextSelectionMenuUnderlineLabel => r'আন্ডারলাইন করুন'; @override - String get shortShawwalLabel => r'Shaw.'; + String get rabi1Label => r'রবিউল আউয়াল'; @override - String get todayLabel => r'I dag'; + String get rabi2Label => + r'রাবি' + "'" + r'আল-থানি'; @override - String get weeknumberLabel => r'Uge'; -} + String get rajabLabel => r'রজব'; -/// The translations for German (`de`). -class SfLocalizationsDe extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsDe class - const SfLocalizationsDe({ - String localeName = 'de', - }) : super( - localeName: localeName, - ); + @override + String get ramadanLabel => r'রমজান'; @override - String get allDayLabel => r'Den ganzen Tag'; + String get rowsPerPageDataPagerLabel => r'প্রতি পৃষ্ঠায় সারি'; @override - String get allowedViewDayLabel => r'Tag'; + String get safarLabel => r'সাফার'; @override - String get allowedViewMonthLabel => r'Monat'; + String get searchDataGridFilteringLabel => r'অনুসন্ধান করুন'; @override - String get allowedViewScheduleLabel => r'Zeitplan'; + String get selectAllDataGridFilteringLabel => r'সব নির্বাচন করুন'; @override - String get allowedViewTimelineDayLabel => r'Zeitleiste Tag'; + String get series => r'সিরিজ'; @override - String get allowedViewTimelineMonthLabel => r'Zeitachse Monat'; + String get shaabanLabel => + r'শা' + "'" + r'বান'; @override - String get allowedViewTimelineWeekLabel => r'Zeitleiste Woche'; + String get shawwalLabel => r'শাওয়াল'; @override - String get allowedViewTimelineWorkWeekLabel => r'Zeitleiste Arbeitswoche'; + String get shortDhualhiLabel => r'ধুল-এইচ'; @override - String get allowedViewWeekLabel => r'Woche'; + String get shortDhualqiLabel => r'যুল-ক্ব'; @override - String get allowedViewWorkWeekLabel => r'Arbeitswoche'; + String get shortJumada1Label => r'জুম। আমি'; @override - String get daySpanCountLabel => r'Tag'; + String get shortJumada2Label => r'জুম। ২'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get shortMuharramLabel => r'মুহ.'; @override - String get dhualqiLabel => r'Dhu al-Qidah'; + String get shortRabi1Label => r'রাবি। আমি'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortRabi2Label => r'রাবি। ২'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shortRajabLabel => r'রাজ।'; @override - String get muharramLabel => r'Muharram'; + String get shortRamadanLabel => r'র্যাম.'; @override - String get noEventsCalendarLabel => r'Keine Ereignisse'; + String get shortSafarLabel => r'সাফ.'; @override - String get noSelectedDateCalendarLabel => r'Kein ausgewähltes Datum'; + String get shortShaabanLabel => r'শা.'; @override - String get ofDataPagerLabel => r'von'; + String get shortShawwalLabel => r'শ.'; @override - String get pagesDataPagerLabel => r'Seiten'; + String get showRowsWhereDataGridFilteringLabel => r'যেখানে সারি দেখান'; @override - String get passwordDialogContentLabel => - r'Geben Sie das Passwort ein, um diese PDF-Datei zu öffnen'; + String get sortAToZDataGridFilteringLabel => r'A থেকে Z সাজান'; @override - String get passwordDialogHeaderTextLabel => r'Passwortgeschützt'; + String get sortAndFilterDataGridFilteringLabel => r'বাছাই এবং ফিল্টার'; @override - String get passwordDialogHintTextLabel => r'Passwort eingeben'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'সবচেয়ে বড় থেকে ছোট সাজান'; @override - String get passwordDialogInvalidPasswordLabel => r'Ungültiges Passwort'; + String get sortNewestToOldestDataGridFilteringLabel => + r'নতুন থেকে পুরাতন সাজান'; @override - String get pdfBookmarksLabel => r'Lesezeichen'; + String get sortOldestToNewestDataGridFilteringLabel => + r'পুরাতন থেকে নতুন বাছাই করুন'; @override - String get pdfEnterPageNumberLabel => r'Seitenzahl eingeben'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'সবচেয়ে ছোট থেকে বড় সাজান'; @override - String get pdfGoToPageLabel => r'Gehen Sie zur Seite'; + String get sortZToADataGridFilteringLabel => r'Z থেকে A সাজান'; @override - String get pdfInvalidPageNumberLabel => - r'Bitte geben Sie eine gültige Nummer ein'; + String get textFiltersDataGridFilteringLabel => r'টেক্সট ফিল্টার'; @override - String get pdfNoBookmarksLabel => r'Keine Lesezeichen gefunden'; + String get todayLabel => r'আজ'; @override - String get pdfPaginationDialogCancelLabel => r'ABBRECHEN'; + String get weeknumberLabel => r'সপ্তাহ'; +} + +/// The translations for Bosnian (`bs`). +class SfLocalizationsBs extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsBs class + const SfLocalizationsBs({String localeName = 'bs'}) + : super(localeName: localeName); @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get afterDataGridFilteringLabel => r'Poslije'; @override - String get pdfPasswordDialogCancelLabel => r'ABBRECHEN'; + String get afterOrEqualDataGridFilteringLabel => r'Nakon ili jednako'; @override - String get pdfPasswordDialogOpenLabel => r'OFFEN'; + String get allDayLabel => r'Cijeli dan'; @override - String get pdfScrollStatusOfLabel => r'von'; + String get allowedViewDayLabel => r'Dan'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get allowedViewMonthLabel => r'Mjesec'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get allowedViewScheduleLabel => r'Raspored'; @override - String get rajabLabel => r'Rajab'; + String get allowedViewTimelineDayLabel => r'Dan vremenske linije'; @override - String get ramadanLabel => r'Ramadan'; + String get allowedViewTimelineMonthLabel => r'Mjesec vremenske linije'; @override - String get rowsPerPageDataPagerLabel => r'Zeilen pro Seite'; + String get allowedViewTimelineWeekLabel => r'Sedmica vremenske linije'; @override - String get safarLabel => r'Safar'; + String get allowedViewTimelineWorkWeekLabel => + r'Radna sedmica vremenske linije'; @override - String get series => r'Serie'; + String get allowedViewWeekLabel => r'Sedmica'; @override - String get shaabanLabel => r'Schaaban'; + String get allowedViewWorkWeekLabel => r'Radna sedmica'; @override - String get shawwalLabel => r'Shawwal'; + String get andDataGridFilteringLabel => r'I'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get beforeDataGridFilteringLabel => r'Prije'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get beforeOrEqualDataGridFilteringLabel => r'Prije ili jednako'; @override - String get shortJumada1Label => r'Jum. ich'; + String get beginsWithDataGridFilteringLabel => r'Počinje sa'; @override - String get shortJumada2Label => r'Jum. II'; + String get cancelDataGridFilteringLabel => r'Otkaži'; @override - String get shortMuharramLabel => r'Muh.'; + String get clearFilterDataGridFilteringLabel => r'Obriši filter'; @override - String get shortRabi1Label => r'Rabi. ich'; + String get containsDataGridFilteringLabel => r'Sadrži'; @override - String get shortRabi2Label => r'Rabi. II'; + String get dateFiltersDataGridFilteringLabel => r'Filteri datuma'; @override - String get shortRajabLabel => r'Raj.'; + String get daySpanCountLabel => r'Dan'; @override - String get shortRamadanLabel => r'RAM.'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get shortSafarLabel => r'Sicher.'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get shortShaabanLabel => r'Scha.'; + String get doesNotBeginWithDataGridFilteringLabel => r'Ne počinje sa'; @override - String get shortShawwalLabel => r'Schau.'; + String get doesNotContainDataGridFilteringLabel => r'Ne sadrži'; @override - String get todayLabel => r'Heute'; + String get doesNotEndWithDataGridFilteringLabel => r'Ne završava sa'; @override - String get weeknumberLabel => r'Woche'; -} + String get doesNotEqualDataGridFilteringLabel => r'Nije jednako'; -/// The translations for Modern Greek (`el`). -class SfLocalizationsEl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsEl class - const SfLocalizationsEl({ - String localeName = 'el', - }) : super( - localeName: localeName, - ); + @override + String get emptyDataGridFilteringLabel => r'Prazan'; @override - String get allDayLabel => r'Ολη μέρα'; + String get endsWithDataGridFilteringLabel => r'Završava sa'; @override - String get allowedViewDayLabel => r'Ημέρα'; + String get equalsDataGridFilteringLabel => r'Jednako'; @override - String get allowedViewMonthLabel => r'Μήνας'; + String get fromDataGridFilteringLabel => r'Od'; @override - String get allowedViewScheduleLabel => r'Πρόγραμμα'; + String get greaterThanDataGridFilteringLabel => r'veće od'; @override - String get allowedViewTimelineDayLabel => r'Ημέρα χρονολογίου'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Veće ili jednako'; @override - String get allowedViewTimelineMonthLabel => r'Χρονολόγιο Μήνας'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get allowedViewTimelineWeekLabel => r'Εβδομάδα Χρονολογίου'; + String get jumada2Label => r'Jumada al-thani'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Χρονολόγιο Εβδομάδα εργασίας'; + String get lessThanDataGridFilteringLabel => r'Manje od'; @override - String get allowedViewWeekLabel => r'Εβδομάδα'; + String get lessThanOrEqualDataGridFilteringLabel => r'Manje od ili jednako'; @override - String get allowedViewWorkWeekLabel => r'Εβδομάδα εργασίας'; + String get muharramLabel => r'Muharram'; @override - String get daySpanCountLabel => r'Ημέρα'; + String get noEventsCalendarLabel => r'Nema događaja'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get noMatchesDataGridFilteringLabel => r'Nema poklapanja'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get noSelectedDateCalendarLabel => r'Nema odabranog datuma'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get notEmptyDataGridFilteringLabel => r'Nije prazno'; @override - String get jumada2Label => r'Τζουμάντα αλ-θάνι'; + String get notNullDataGridFilteringLabel => r'Ne Null'; @override - String get muharramLabel => r'Μουχαράμ'; + String get nullDataGridFilteringLabel => r'Null'; @override - String get noEventsCalendarLabel => r'Δεν υπάρχουν εκδηλώσεις'; + String get numberFiltersDataGridFilteringLabel => r'Brojčani filteri'; @override - String get noSelectedDateCalendarLabel => r'Καμία επιλεγμένη ημερομηνία'; + String get ofDataPagerLabel => r'od'; @override - String get ofDataPagerLabel => r'του'; + String get okDataGridFilteringLabel => r'uredu'; @override - String get pagesDataPagerLabel => r'σελίδες'; + String get orDataGridFilteringLabel => r'Or'; @override - String get passwordDialogContentLabel => - r'Εισαγάγετε τον κωδικό πρόσβασης για να ανοίξετε αυτό το αρχείο PDF'; + String get pagesDataPagerLabel => r'stranice'; @override - String get passwordDialogHeaderTextLabel => - r'Προστατεύεται με κωδικό πρόσβασης'; + String get passwordDialogContentLabel => + r'Unesite lozinku za otvaranje ove PDF datoteke'; @override - String get passwordDialogHintTextLabel => r'Εισάγετε τον κωδικό πρόσβασης'; + String get passwordDialogHeaderTextLabel => r'Zaštićeno lozinkom'; @override - String get passwordDialogInvalidPasswordLabel => r'Λανθασμένος κωδικός'; + String get passwordDialogHintTextLabel => r'Unesite lozinku'; @override - String get pdfBookmarksLabel => r'Σελιδοδείκτες'; + String get passwordDialogInvalidPasswordLabel => r'Nevažeća lozinka'; @override - String get pdfEnterPageNumberLabel => r'Εισαγάγετε τον αριθμό σελίδας'; + String get pdfBookmarksLabel => r'Bookmarks'; @override - String get pdfGoToPageLabel => r'Πήγαινε στην σελίδα'; + String get pdfEnterPageNumberLabel => r'Unesite broj stranice'; @override - String get pdfInvalidPageNumberLabel => - r'Παρακαλώ εισάγετε έναν έγκυρο αριθμό'; + String get pdfGoToPageLabel => r'Idi na stranicu'; @override - String get pdfNoBookmarksLabel => r'Δεν βρέθηκαν σελιδοδείκτες'; + String get pdfHyperlinkContentLabel => + r'Da li želite da otvorite stranicu na'; @override - String get pdfPaginationDialogCancelLabel => r'ΜΑΤΑΙΩΣΗ'; + String get pdfHyperlinkDialogCancelLabel => r'OTKAZI'; @override - String get pdfPaginationDialogOkLabel => r'Εντάξει'; + String get pdfHyperlinkDialogOpenLabel => r'OTVOREN'; @override - String get pdfPasswordDialogCancelLabel => r'ΜΑΤΑΙΩΣΗ'; + String get pdfHyperlinkLabel => r'Otvorite web stranicu'; @override - String get pdfPasswordDialogOpenLabel => r'ΑΝΟΙΞΕ'; + String get pdfInvalidPageNumberLabel => r'Unesite ispravan broj'; @override - String get pdfScrollStatusOfLabel => r'του'; + String get pdfNoBookmarksLabel => r'Nema pronađenih oznaka'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get pdfPaginationDialogCancelLabel => r'OTKAZI'; @override - String get rabi2Label => r'Ραμπί' "'" r' αλ-θάνι'; + String get pdfPaginationDialogOkLabel => r'uredu'; @override - String get rajabLabel => r'Ρατζάμπ'; + String get pdfPasswordDialogCancelLabel => r'OTKAZI'; @override - String get ramadanLabel => r'Ραμαζάνι'; + String get pdfPasswordDialogOpenLabel => r'OTVOREN'; @override - String get rowsPerPageDataPagerLabel => r'Σειρές ανά σελίδα'; + String get pdfScrollStatusOfLabel => r'of'; @override - String get safarLabel => r'Σαφάρ'; + String get pdfSignaturePadDialogClearLabel => r'CLEAR'; @override - String get series => r'Σειρά'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Nacrtajte svoj potpis'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get pdfSignaturePadDialogPenColorLabel => r'Pen Color'; @override - String get shawwalLabel => r'Shawwal'; + String get pdfSignaturePadDialogSaveLabel => r'SAVE'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get pdfTextSelectionMenuCopyLabel => r'Kopiraj'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get pdfTextSelectionMenuHighlightLabel => r'Istaknite'; @override - String get shortJumada1Label => r'Jum. Εγώ'; + String get pdfTextSelectionMenuSquigglyLabel => r'Squiggly'; @override - String get shortJumada2Label => r'Jum. II'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Precrtano'; @override - String get shortMuharramLabel => r'Muh.'; + String get pdfTextSelectionMenuUnderlineLabel => r'Podvući'; @override - String get shortRabi1Label => r'Ραμπί. Εγώ'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get shortRabi2Label => r'Ραμπί. II'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get shortRajabLabel => r'Κυριαρχία.'; + String get rajabLabel => r'Rajab'; @override - String get shortRamadanLabel => r'Εμβολο.'; + String get ramadanLabel => r'Ramazan'; @override - String get shortSafarLabel => r'Saf.'; + String get rowsPerPageDataPagerLabel => r'Redova po stranici'; @override - String get shortShaabanLabel => r'Sha.'; + String get safarLabel => r'Safar'; @override - String get shortShawwalLabel => r'Shaw.'; + String get searchDataGridFilteringLabel => r'Traži'; @override - String get todayLabel => r'Σήμερα'; + String get selectAllDataGridFilteringLabel => r'Označi sve'; @override - String get weeknumberLabel => r'Εβδομάδα'; -} - -/// The translations for English (`en`). -class SfLocalizationsEn extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsEn class - const SfLocalizationsEn({ - String localeName = 'en', - }) : super( - localeName: localeName, - ); + String get series => r'Serije'; @override - String get allDayLabel => r'All Day'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get allowedViewDayLabel => r'Day'; + String get shawwalLabel => r'Shawwal'; @override - String get allowedViewMonthLabel => r'Month'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get allowedViewScheduleLabel => r'Schedule'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get allowedViewTimelineDayLabel => r'Timeline Day'; + String get shortJumada1Label => r'Jum. I'; @override - String get allowedViewTimelineMonthLabel => r'Timeline Month'; + String get shortJumada2Label => r'Jum. II'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get shortMuharramLabel => r'Muh.'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get shortRabi1Label => r'Rabi. I'; @override - String get allowedViewWeekLabel => r'Week'; + String get shortRabi2Label => r'Rabi. II'; @override - String get allowedViewWorkWeekLabel => r'Work Week'; + String get shortRajabLabel => r'Raj.'; @override - String get daySpanCountLabel => r'Day'; + String get shortRamadanLabel => r'RAM.'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get shortSafarLabel => r'Saf.'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get shortShaabanLabel => r'Sha.'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortShawwalLabel => r'Shaw.'; @override - String get jumada2Label => r'Jumada al-thani'; + String get showRowsWhereDataGridFilteringLabel => r'Pokaži redove gdje'; @override - String get muharramLabel => r'Muharram'; + String get sortAToZDataGridFilteringLabel => r'Sortiraj od A do Ž'; @override - String get noEventsCalendarLabel => r'No events'; + String get sortAndFilterDataGridFilteringLabel => r'Sortiraj i filtriraj'; @override - String get noSelectedDateCalendarLabel => r'No selected date'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sortiraj od najvećeg do najmanjeg'; @override - String get ofDataPagerLabel => r'of'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sortiraj od najnovijeg do najstarijeg'; @override - String get pagesDataPagerLabel => r'pages'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sortiraj od najstarijih do najnovijih'; @override - String get passwordDialogContentLabel => - r'Enter the password to open this PDF file'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sortiraj od najmanjeg do najvećeg'; @override - String get passwordDialogHeaderTextLabel => r'Password Protected'; + String get sortZToADataGridFilteringLabel => r'Sortiraj od Z do A'; @override - String get passwordDialogHintTextLabel => r'Enter Password'; + String get textFiltersDataGridFilteringLabel => r'Filteri teksta'; @override - String get passwordDialogInvalidPasswordLabel => r'Invalid Password'; + String get todayLabel => r'Danas'; @override - String get pdfBookmarksLabel => r'Bookmarks'; + String get weeknumberLabel => r'Sedmica'; +} + +/// The translations for Catalan Valencian (`ca`). +class SfLocalizationsCa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsCa class + const SfLocalizationsCa({String localeName = 'ca'}) + : super(localeName: localeName); @override - String get pdfEnterPageNumberLabel => r'Enter page number'; + String get afterDataGridFilteringLabel => r'Després'; @override - String get pdfGoToPageLabel => r'Go to page'; + String get afterOrEqualDataGridFilteringLabel => r'Després o igual'; @override - String get pdfInvalidPageNumberLabel => r'Please enter a valid number'; + String get allDayLabel => r'Tot el dia'; @override - String get pdfNoBookmarksLabel => r'No bookmarks found'; + String get allowedViewDayLabel => r'Dia'; @override - String get pdfPaginationDialogCancelLabel => r'CANCEL'; + String get allowedViewMonthLabel => r'Mes'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get allowedViewScheduleLabel => r'Horari'; @override - String get pdfPasswordDialogCancelLabel => r'CANCEL'; + String get allowedViewTimelineDayLabel => r'Dia del cronograma'; @override - String get pdfPasswordDialogOpenLabel => r'OPEN'; + String get allowedViewTimelineMonthLabel => r'Mes cronològic'; @override - String get pdfScrollStatusOfLabel => r'of'; + String get allowedViewTimelineWeekLabel => r'Setmana del cronograma'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get allowedViewTimelineWorkWeekLabel => + r'Setmana de treball cronològic'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get allowedViewWeekLabel => r'Setmana'; @override - String get rajabLabel => r'Rajab'; + String get allowedViewWorkWeekLabel => r'Setmana Laboral'; @override - String get ramadanLabel => r'Ramadan'; + String get andDataGridFilteringLabel => r'I'; @override - String get rowsPerPageDataPagerLabel => r'Rows per page'; + String get beforeDataGridFilteringLabel => r'Abans'; @override - String get safarLabel => r'Safar'; + String get beforeOrEqualDataGridFilteringLabel => r'Abans o igual'; @override - String get series => r'Series'; + String get beginsWithDataGridFilteringLabel => r'Comença amb'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get cancelDataGridFilteringLabel => r'Cancel · lar'; @override - String get shawwalLabel => r'Shawwal'; + String get clearFilterDataGridFilteringLabel => r'Esborra el filtre'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get containsDataGridFilteringLabel => r'Conté'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get dateFiltersDataGridFilteringLabel => r'Filtres de data'; @override - String get shortJumada1Label => r'Jum. I'; + String get daySpanCountLabel => r'Dia'; @override - String get shortJumada2Label => r'Jum. II'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get shortMuharramLabel => r'Muh.'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get shortRabi1Label => r'Rabi. I'; + String get doesNotBeginWithDataGridFilteringLabel => r'No comença amb'; @override - String get shortRabi2Label => r'Rabi. II'; + String get doesNotContainDataGridFilteringLabel => r'No Conté'; @override - String get shortRajabLabel => r'Raj.'; + String get doesNotEndWithDataGridFilteringLabel => r'No Acaba Amb'; @override - String get shortRamadanLabel => r'Ram.'; + String get doesNotEqualDataGridFilteringLabel => r'No és igual'; @override - String get shortSafarLabel => r'Saf.'; + String get emptyDataGridFilteringLabel => r'Buit'; @override - String get shortShaabanLabel => r'Sha.'; + String get endsWithDataGridFilteringLabel => r'Acaba amb'; @override - String get shortShawwalLabel => r'Shaw.'; + String get equalsDataGridFilteringLabel => r'És igual'; @override - String get todayLabel => r'Today'; + String get fromDataGridFilteringLabel => r'Des de'; @override - String get weeknumberLabel => r'Week'; -} + String get greaterThanDataGridFilteringLabel => r'Més gran que'; -/// The translations for Spanish Castilian (`es`). -class SfLocalizationsEs extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsEs class - const SfLocalizationsEs({ - String localeName = 'es', - }) : super( - localeName: localeName, - ); + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Major que o igual'; @override - String get allDayLabel => r'Todo el dia'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get allowedViewDayLabel => r'Día'; + String get jumada2Label => r'Jumada al-thani'; @override - String get allowedViewMonthLabel => r'Mes'; + String get lessThanDataGridFilteringLabel => r'Menys que'; @override - String get allowedViewScheduleLabel => r'Calendario'; + String get lessThanOrEqualDataGridFilteringLabel => r'Menys que o igual'; @override - String get allowedViewTimelineDayLabel => r'Día de la línea de tiempo'; + String get muharramLabel => r'Muharram'; @override - String get allowedViewTimelineMonthLabel => r'Mes de la línea de tiempo'; + String get noEventsCalendarLabel => r'No hi ha esdeveniments'; @override - String get allowedViewTimelineWeekLabel => r'Semana de la línea de tiempo'; + String get noMatchesDataGridFilteringLabel => r'Sense coincidències'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Línea de tiempo Semana laboral'; + String get noSelectedDateCalendarLabel => r'Cap data seleccionada'; @override - String get allowedViewWeekLabel => r'Semana'; + String get notEmptyDataGridFilteringLabel => r'No buit'; @override - String get allowedViewWorkWeekLabel => r'Semana de trabajo'; + String get notNullDataGridFilteringLabel => r'No nul'; @override - String get daySpanCountLabel => r'Día'; + String get nullDataGridFilteringLabel => r'Nul'; @override - String get dhualhiLabel => r'Dhu al-Hiyyah'; + String get numberFiltersDataGridFilteringLabel => r'Filtres de nombre'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get ofDataPagerLabel => r'de'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get okDataGridFilteringLabel => + r'D' + "'" + r'acord'; @override - String get jumada2Label => r'Jumada al-thani'; + String get orDataGridFilteringLabel => r'O'; @override - String get muharramLabel => r'muharram'; + String get pagesDataPagerLabel => r'pàgines'; @override - String get noEventsCalendarLabel => r'No hay eventos'; + String get passwordDialogContentLabel => + r'Introduïu la contrasenya per obrir aquest fitxer PDF'; @override - String get noSelectedDateCalendarLabel => r'Sin fecha seleccionada'; + String get passwordDialogHeaderTextLabel => r'Protegit amb contrasenya'; @override - String get ofDataPagerLabel => r'de'; + String get passwordDialogHintTextLabel => r'Introduir la contrasenya'; @override - String get pagesDataPagerLabel => r'paginas'; + String get passwordDialogInvalidPasswordLabel => r'contrasenya invàlida'; @override - String get passwordDialogContentLabel => - r'Introduzca la contraseña para abrir este archivo PDF'; + String get pdfBookmarksLabel => + r'Adreces d' + "'" + r'interès'; @override - String get passwordDialogHeaderTextLabel => r'Contraseña protegida'; + String get pdfEnterPageNumberLabel => r'Introduïu el número de pàgina'; @override - String get passwordDialogHintTextLabel => r'Introducir la contraseña'; + String get pdfGoToPageLabel => r'Ves a la pàgina'; @override - String get passwordDialogInvalidPasswordLabel => r'Contraseña invalida'; + String get pdfHyperlinkContentLabel => r'Vols obrir la pàgina a'; @override - String get pdfBookmarksLabel => r'Marcadores'; + String get pdfHyperlinkDialogCancelLabel => r'Cancel·lar'; @override - String get pdfEnterPageNumberLabel => r'Introduzca el número de página'; + String get pdfHyperlinkDialogOpenLabel => r'OBERT'; @override - String get pdfGoToPageLabel => r'ir a la página'; + String get pdfHyperlinkLabel => r'Obre la pàgina web'; @override - String get pdfInvalidPageNumberLabel => r'por favor ingrese un número valido'; + String get pdfInvalidPageNumberLabel => r'Introduïu un número vàlid'; @override - String get pdfNoBookmarksLabel => r'No se encontraron marcadores'; + String get pdfNoBookmarksLabel => + r'No s' + "'" + r'han trobat adreces d' + "'" + r'interès'; @override - String get pdfPaginationDialogCancelLabel => r'CANCELAR'; + String get pdfPaginationDialogCancelLabel => r'Cancel·lar'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get pdfPaginationDialogOkLabel => + r'D' + "'" + r'acord'; @override - String get pdfPasswordDialogCancelLabel => r'CANCELAR'; + String get pdfPasswordDialogCancelLabel => r'Cancel·lar'; @override - String get pdfPasswordDialogOpenLabel => r'ABIERTO'; + String get pdfPasswordDialogOpenLabel => r'OBERT'; @override String get pdfScrollStatusOfLabel => r'de'; @override - String get rabi1Label => r'Rabí al-awwal'; + String get pdfSignaturePadDialogClearLabel => r'CLAR'; @override - String get rabi2Label => r'Rabí al-thani'; + String get pdfSignaturePadDialogHeaderTextLabel => + r'Dibuixa la teva signatura'; @override - String get rajabLabel => r'Rayab'; + String get pdfSignaturePadDialogPenColorLabel => r'Color del bolígraf'; @override - String get ramadanLabel => r'Ramadán'; + String get pdfSignaturePadDialogSaveLabel => r'DESA'; @override - String get rowsPerPageDataPagerLabel => r'Filas por página'; + String get pdfTextSelectionMenuCopyLabel => r'Còpia'; @override - String get safarLabel => r'Safar'; + String get pdfTextSelectionMenuHighlightLabel => r'Ressaltar'; @override - String get series => r'Serie'; + String get pdfTextSelectionMenuSquigglyLabel => r'Squiggly'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Tallat'; @override - String get shawwalLabel => r'Shawwal'; + String get pdfTextSelectionMenuUnderlineLabel => r'Subratllar'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get shortJumada1Label => r'Jum. I'; + String get rajabLabel => r'Rajab'; @override - String get shortJumada2Label => r'Jum. II'; + String get ramadanLabel => r'Ramadà'; @override - String get shortMuharramLabel => r'Muh.'; + String get rowsPerPageDataPagerLabel => r'Files per pàgina'; @override - String get shortRabi1Label => r'Rabí. I'; + String get safarLabel => r'Safar'; @override - String get shortRabi2Label => r'Rabi. II'; + String get searchDataGridFilteringLabel => r'Cerca'; @override - String get shortRajabLabel => r'Raj.'; + String get selectAllDataGridFilteringLabel => r'Seleccionar tot'; @override - String get shortRamadanLabel => r'RAM.'; + String get series => r'Sèrie'; @override - String get shortSafarLabel => r'seguro'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get shortShaabanLabel => r'Sha.'; + String get shawwalLabel => r'Shawwal'; @override - String get shortShawwalLabel => r'Shaw.'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get todayLabel => r'Hoy dia'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get weeknumberLabel => r'Semana'; -} + String get shortJumada1Label => r'Jum. jo'; -/// The translations for Estonian (`et`). -class SfLocalizationsEt extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsEt class - const SfLocalizationsEt({ - String localeName = 'et', - }) : super( - localeName: localeName, - ); + @override + String get shortJumada2Label => r'Jum. II'; @override - String get allDayLabel => r'Terve päev'; + String get shortMuharramLabel => r'Muh.'; @override - String get allowedViewDayLabel => r'päev'; + String get shortRabi1Label => r'Rabi. jo'; @override - String get allowedViewMonthLabel => r'Kuu'; + String get shortRabi2Label => r'Rabi. II'; @override - String get allowedViewScheduleLabel => r'Ajakava'; + String get shortRajabLabel => r'Raj.'; @override - String get allowedViewTimelineDayLabel => r'Ajaskaala päev'; + String get shortRamadanLabel => r'Ram.'; @override - String get allowedViewTimelineMonthLabel => r'Ajaskaala Kuu'; + String get shortSafarLabel => r'Saf.'; @override - String get allowedViewTimelineWeekLabel => r'Ajaskaala nädal'; + String get shortShaabanLabel => r'Sha.'; @override - String get allowedViewTimelineWorkWeekLabel => r'Ajaskaala Töönädal'; + String get shortShawwalLabel => r'Shaw.'; @override - String get allowedViewWeekLabel => r'Nädal'; + String get showRowsWhereDataGridFilteringLabel => r'Mostra les files on'; @override - String get allowedViewWorkWeekLabel => r'Töönädal'; + String get sortAToZDataGridFilteringLabel => r'Ordena de la A a la Z'; @override - String get daySpanCountLabel => r'päev'; + String get sortAndFilterDataGridFilteringLabel => r'Ordena i filtra'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ordena del més gran al més petit'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Ordena el més nou al més antic'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Ordena del més antic al més recent'; @override - String get jumada2Label => r'Jumada al-thani'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ordena del més petit al més gran'; @override - String get muharramLabel => r'Muharram'; + String get sortZToADataGridFilteringLabel => r'Ordena de Z a A'; @override - String get noEventsCalendarLabel => r'Sündmusi pole'; + String get textFiltersDataGridFilteringLabel => r'Filtres de text'; @override - String get noSelectedDateCalendarLabel => r'Kuupäeva pole valitud'; + String get todayLabel => r'Avui'; @override - String get ofDataPagerLabel => r'kohta'; + String get weeknumberLabel => r'Setmana'; +} + +/// The translations for Czech (`cs`). +class SfLocalizationsCs extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsCs class + const SfLocalizationsCs({String localeName = 'cs'}) + : super(localeName: localeName); @override - String get pagesDataPagerLabel => r'lehekülgi'; + String get afterDataGridFilteringLabel => r'Po'; @override - String get passwordDialogContentLabel => - r'Selle PDF-faili avamiseks sisestage parool'; + String get afterOrEqualDataGridFilteringLabel => r'Po nebo rovno'; @override - String get passwordDialogHeaderTextLabel => r'Parooliga kaitstud'; + String get allDayLabel => r'Celý den'; @override - String get passwordDialogHintTextLabel => r'Sisestage parool'; + String get allowedViewDayLabel => r'Den'; @override - String get passwordDialogInvalidPasswordLabel => r'vale parool'; + String get allowedViewMonthLabel => r'Měsíc'; @override - String get pdfBookmarksLabel => r'Järjehoidjad'; + String get allowedViewScheduleLabel => r'Plán'; @override - String get pdfEnterPageNumberLabel => r'Sisestage lehekülje number'; + String get allowedViewTimelineDayLabel => r'Den časové osy'; @override - String get pdfGoToPageLabel => r'Mine lehele'; + String get allowedViewTimelineMonthLabel => r'Časová osa Měsíc'; @override - String get pdfInvalidPageNumberLabel => r'Sisestage kehtiv number'; + String get allowedViewTimelineWeekLabel => r'Časová osa týden'; @override - String get pdfNoBookmarksLabel => r'Järjehoidjaid ei leitud'; + String get allowedViewTimelineWorkWeekLabel => r'Časová osa pracovní týden'; @override - String get pdfPaginationDialogCancelLabel => r'TÜHISTA'; + String get allowedViewWeekLabel => r'Týden'; @override - String get pdfPaginationDialogOkLabel => r'Okei'; + String get allowedViewWorkWeekLabel => r'Pracovní týden'; @override - String get pdfPasswordDialogCancelLabel => r'TÜHISTA'; + String get andDataGridFilteringLabel => r'A'; @override - String get pdfPasswordDialogOpenLabel => r'AVATUD'; + String get beforeDataGridFilteringLabel => r'Před'; @override - String get pdfScrollStatusOfLabel => r'kohta'; + String get beforeOrEqualDataGridFilteringLabel => r'Před nebo rovno'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get beginsWithDataGridFilteringLabel => r'Začíná s'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get cancelDataGridFilteringLabel => r'zrušit'; @override - String get rajabLabel => r'Rajab'; + String get clearFilterDataGridFilteringLabel => r'Vymazat filtr'; @override - String get ramadanLabel => r'Ramadan'; + String get containsDataGridFilteringLabel => r'Obsahuje'; @override - String get rowsPerPageDataPagerLabel => r'Rida lehekülje kohta'; + String get dateFiltersDataGridFilteringLabel => r'Datumové filtry'; @override - String get safarLabel => r'Safar'; + String get daySpanCountLabel => r'Den'; @override - String get series => r'seeria'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get shawwalLabel => r'Shawwal'; + String get doesNotBeginWithDataGridFilteringLabel => r'Nezačíná s'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get doesNotContainDataGridFilteringLabel => r'Neobsahuje'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get doesNotEndWithDataGridFilteringLabel => r'Nekončí s'; @override - String get shortJumada1Label => r'Jum. ma'; + String get doesNotEqualDataGridFilteringLabel => r'Nerovná se'; @override - String get shortJumada2Label => r'Jum. II'; + String get emptyDataGridFilteringLabel => r'Prázdný'; @override - String get shortMuharramLabel => r'Muh.'; + String get endsWithDataGridFilteringLabel => r'Končí s'; @override - String get shortRabi1Label => r'Rabi. ma'; + String get equalsDataGridFilteringLabel => r'Rovná se'; @override - String get shortRabi2Label => r'Rabi. II'; + String get fromDataGridFilteringLabel => r'Z'; @override - String get shortRajabLabel => r'Raj.'; + String get greaterThanDataGridFilteringLabel => r'Větší než'; @override - String get shortRamadanLabel => r'Ram.'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Větší než nebo rovno'; @override - String get shortSafarLabel => r'Saf.'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get shortShaabanLabel => r'Sha.'; + String get jumada2Label => r'Jumada al-thani'; @override - String get shortShawwalLabel => r'Shaw.'; + String get lessThanDataGridFilteringLabel => r'Méně než'; @override - String get todayLabel => r'Täna'; + String get lessThanOrEqualDataGridFilteringLabel => r'Méně než nebo rovno'; @override - String get weeknumberLabel => r'Nädal'; -} + String get muharramLabel => r'Muharram'; -/// The translations for Basque (`eu`). -class SfLocalizationsEu extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsEu class - const SfLocalizationsEu({ - String localeName = 'eu', - }) : super( - localeName: localeName, - ); + @override + String get noEventsCalendarLabel => r'Žádné události'; @override - String get allDayLabel => r'Egun osoan'; + String get noMatchesDataGridFilteringLabel => r'Žádné shody'; @override - String get allowedViewDayLabel => r'Eguna'; + String get noSelectedDateCalendarLabel => r'Žádné datum vybráno'; @override - String get allowedViewMonthLabel => r'Hilabetea'; + String get notEmptyDataGridFilteringLabel => r'Není prázdný'; @override - String get allowedViewScheduleLabel => r'Ordutegia'; + String get notNullDataGridFilteringLabel => r'Nenulový'; @override - String get allowedViewTimelineDayLabel => r'Kronograma Eguna'; + String get nullDataGridFilteringLabel => r'Nula'; @override - String get allowedViewTimelineMonthLabel => r'Kronograma Hilabetea'; + String get numberFiltersDataGridFilteringLabel => r'Filtry čísel'; @override - String get allowedViewTimelineWeekLabel => r'Denboraren Astea'; + String get ofDataPagerLabel => r'z'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Lan Astea'; + String get okDataGridFilteringLabel => r'OK'; @override - String get allowedViewWeekLabel => r'Astea'; + String get orDataGridFilteringLabel => r'Nebo'; @override - String get allowedViewWorkWeekLabel => r'Lan Astea'; + String get pagesDataPagerLabel => r'stránky'; @override - String get daySpanCountLabel => r'Eguna'; + String get passwordDialogContentLabel => + r'Zadejte heslo pro otevření tohoto souboru PDF'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get passwordDialogHeaderTextLabel => r'Chráněno heslem'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get passwordDialogHintTextLabel => r'Zadejte heslo'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get passwordDialogInvalidPasswordLabel => r'Neplatné heslo'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfBookmarksLabel => r'Záložky'; @override - String get muharramLabel => r'Muharram'; + String get pdfEnterPageNumberLabel => r'Zadejte číslo stránky'; @override - String get noEventsCalendarLabel => r'Ez dago ekitaldirik'; + String get pdfGoToPageLabel => r'Jdi na stránku'; @override - String get noSelectedDateCalendarLabel => r'Ez dago data hautaturik'; + String get pdfHyperlinkContentLabel => r'Chcete otevřít stránku na'; @override - String get ofDataPagerLabel => r'de'; + String get pdfHyperlinkDialogCancelLabel => r'ZRUŠENÍ'; @override - String get pagesDataPagerLabel => r'orrialdeak'; + String get pdfHyperlinkDialogOpenLabel => r'OTEVŘENO'; @override - String get passwordDialogContentLabel => - r'Sartu pasahitza PDF fitxategi hau irekitzeko'; + String get pdfHyperlinkLabel => r'Otevřete webovou stránku'; @override - String get passwordDialogHeaderTextLabel => r'Pasahitz babestuta'; + String get pdfInvalidPageNumberLabel => r'Prosím zadejte platné číslo'; @override - String get passwordDialogHintTextLabel => r'Sartu pasahitza'; + String get pdfNoBookmarksLabel => r'Nebyly nalezeny žádné záložky'; @override - String get passwordDialogInvalidPasswordLabel => r'pasahitz okerra'; + String get pdfPaginationDialogCancelLabel => r'ZRUŠENÍ'; @override - String get pdfBookmarksLabel => r'Laster-markak'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get pdfEnterPageNumberLabel => r'Sartu orriaren zenbakia'; + String get pdfPasswordDialogCancelLabel => r'ZRUŠENÍ'; @override - String get pdfGoToPageLabel => r'Joan orrialdera'; + String get pdfPasswordDialogOpenLabel => r'OTEVŘENO'; @override - String get pdfInvalidPageNumberLabel => - r'Mesedez, sartu baliozko zenbaki bat'; + String get pdfScrollStatusOfLabel => r'z'; @override - String get pdfNoBookmarksLabel => r'Ez da laster-markarik aurkitu'; + String get pdfSignaturePadDialogClearLabel => r'Vymazat'; @override - String get pdfPaginationDialogCancelLabel => r'Utzi'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Nakreslete svůj podpis'; @override - String get pdfPaginationDialogOkLabel => r'Ados'; + String get pdfSignaturePadDialogPenColorLabel => r'Barva pera'; @override - String get pdfPasswordDialogCancelLabel => r'Utzi'; + String get pdfSignaturePadDialogSaveLabel => r'ULOŽIT'; @override - String get pdfPasswordDialogOpenLabel => r'IREKI'; + String get pdfTextSelectionMenuCopyLabel => r'kopírovat'; @override - String get pdfScrollStatusOfLabel => r'de'; + String get pdfTextSelectionMenuHighlightLabel => r'Zvýraznit'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Zkrouceně'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Přeškrtnutí'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get pdfTextSelectionMenuUnderlineLabel => r'Zdůraznit'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override String get rajabLabel => r'Rajab'; @override - String get ramadanLabel => r'Ramadana'; + String get ramadanLabel => r'Ramadán'; @override - String get rowsPerPageDataPagerLabel => r'Orrialde bakoitzeko errenkadak'; + String get rowsPerPageDataPagerLabel => r'Řádky na stránku'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Šafář'; @override - String get series => r'Seriea'; + String get searchDataGridFilteringLabel => r'Vyhledávání'; + + @override + String get selectAllDataGridFilteringLabel => r'Vybrat vše'; + + @override + String get series => r'Série'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get shortJumada1Label => r'Salto. I'; + String get shortJumada1Label => r'Jum. já'; @override - String get shortJumada2Label => r'Salto. II'; + String get shortJumada2Label => r'Jum. II'; @override String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. I'; + String get shortRabi1Label => r'Rabi. já'; @override String get shortRabi2Label => r'Rabi. II'; @@ -3111,341 +3505,338 @@ class SfLocalizationsEu extends SfGlobalLocalizations { String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Gaur'; + String get showRowsWhereDataGridFilteringLabel => r'Zobrazit řádky kde'; @override - String get weeknumberLabel => r'Astea'; -} + String get sortAToZDataGridFilteringLabel => r'Seřadit od A do Z'; -/// The translations for Persian (`fa`). -class SfLocalizationsFa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsFa class - const SfLocalizationsFa({ - String localeName = 'fa', - }) : super( - localeName: localeName, - ); + @override + String get sortAndFilterDataGridFilteringLabel => r'Řadit a filtrovat'; @override - String get allDayLabel => r'تمام روز'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Seřadit od největšího po nejmenší'; @override - String get allowedViewDayLabel => r'روز'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Řadit od nejnovějšího k nejstaršímu'; @override - String get allowedViewMonthLabel => r'ماه'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Seřadit od nejstarších po nejnovější'; @override - String get allowedViewScheduleLabel => r'برنامه'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Seřadit od nejmenšího po největší'; @override - String get allowedViewTimelineDayLabel => r'روز جدول زمانی'; + String get sortZToADataGridFilteringLabel => r'Seřadit od Z do A'; @override - String get allowedViewTimelineMonthLabel => r'ماه خط زمانی'; + String get textFiltersDataGridFilteringLabel => r'Textové filtry'; @override - String get allowedViewTimelineWeekLabel => r'هفته جدول زمانی'; + String get todayLabel => r'Dnes'; @override - String get allowedViewTimelineWorkWeekLabel => r'جدول زمانی هفته کاری'; + String get weeknumberLabel => r'Týden'; +} + +/// The translations for Danish (`da`). +class SfLocalizationsDa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsDa class + const SfLocalizationsDa({String localeName = 'da'}) + : super(localeName: localeName); @override - String get allowedViewWeekLabel => r'هفته'; + String get afterDataGridFilteringLabel => r'Efter'; @override - String get allowedViewWorkWeekLabel => r'هفته کاری'; + String get afterOrEqualDataGridFilteringLabel => r'Efter Eller Lige'; @override - String get daySpanCountLabel => r'روز'; + String get allDayLabel => r'Hele dagen'; @override - String get dhualhiLabel => r'ذی الحجه'; + String get allowedViewDayLabel => r'Dag'; @override - String get dhualqiLabel => r'ذی القعده'; + String get allowedViewMonthLabel => r'Måned'; @override - String get jumada1Label => r'جمادی الاول'; + String get allowedViewScheduleLabel => r'Tidsplan'; @override - String get jumada2Label => r'جمادی الثانی'; + String get allowedViewTimelineDayLabel => r'Tidslinje Dag'; @override - String get muharramLabel => r'محرم'; + String get allowedViewTimelineMonthLabel => r'Tidslinje Måned'; @override - String get noEventsCalendarLabel => r'هیچ رویدادی وجود ندارد'; + String get allowedViewTimelineWeekLabel => r'Tidslinje Uge'; @override - String get noSelectedDateCalendarLabel => r'تاریخ انتخابی وجود ندارد'; + String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbejdsuge'; @override - String get ofDataPagerLabel => r'از'; + String get allowedViewWeekLabel => r'Uge'; @override - String get pagesDataPagerLabel => r'صفحات'; + String get allowedViewWorkWeekLabel => r'Arbejdsuge'; @override - String get passwordDialogContentLabel => - r'رمز عبور را برای باز کردن این فایل PDF وارد کنید'; + String get andDataGridFilteringLabel => r'Og'; @override - String get passwordDialogHeaderTextLabel => r'رمز عبور محافظت شده است'; + String get beforeDataGridFilteringLabel => r'Før'; @override - String get passwordDialogHintTextLabel => r'رمز عبور را وارد کنید'; + String get beforeOrEqualDataGridFilteringLabel => r'Før Eller Lige'; @override - String get passwordDialogInvalidPasswordLabel => r'رمز عبور نامعتبر'; + String get beginsWithDataGridFilteringLabel => r'Starter med'; @override - String get pdfBookmarksLabel => r'نشانک ها'; + String get cancelDataGridFilteringLabel => r'Afbestille'; @override - String get pdfEnterPageNumberLabel => r'شماره صفحه را وارد کنید'; + String get clearFilterDataGridFilteringLabel => r'Ryd filter'; @override - String get pdfGoToPageLabel => r'برو به صفحه'; + String get containsDataGridFilteringLabel => r'Indeholder'; @override - String get pdfInvalidPageNumberLabel => r'لطفا یک شماره معتبر وارد کنید'; + String get dateFiltersDataGridFilteringLabel => r'Dato filtre'; @override - String get pdfNoBookmarksLabel => r'هیچ نشانکی یافت نشد'; + String get daySpanCountLabel => r'Dag'; @override - String get pdfPaginationDialogCancelLabel => r'لغو'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get pdfPaginationDialogOkLabel => r'خوب'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get pdfPasswordDialogCancelLabel => r'لغو'; + String get doesNotBeginWithDataGridFilteringLabel => r'Begynder ikke med'; @override - String get pdfPasswordDialogOpenLabel => r'باز کن'; + String get doesNotContainDataGridFilteringLabel => r'Indeholder ikke'; @override - String get pdfScrollStatusOfLabel => r'از'; + String get doesNotEndWithDataGridFilteringLabel => r'Slutter ikke med'; @override - String get rabi1Label => r'ربیع الاول'; + String get doesNotEqualDataGridFilteringLabel => r'Er ikke ens'; @override - String get rabi2Label => r'ربیع الثانی'; + String get emptyDataGridFilteringLabel => r'Tom'; @override - String get rajabLabel => r'رجب'; + String get endsWithDataGridFilteringLabel => r'Ender med'; @override - String get ramadanLabel => r'رمضان'; + String get equalsDataGridFilteringLabel => r'Lige med'; @override - String get rowsPerPageDataPagerLabel => r'ردیف در هر صفحه'; + String get fromDataGridFilteringLabel => r'Fra'; @override - String get safarLabel => r'صفر'; + String get greaterThanDataGridFilteringLabel => r'Større end'; @override - String get series => r'سلسله'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Større end eller lige'; @override - String get shaabanLabel => r'شعبان'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get shawwalLabel => r'شوال'; + String get jumada2Label => r'Jumada al-thani'; @override - String get shortDhualhiLabel => r'ذوالح'; + String get lessThanDataGridFilteringLabel => r'Mindre end'; @override - String get shortDhualqiLabel => r'ذوالق'; + String get lessThanOrEqualDataGridFilteringLabel => r'Mindre end eller lige'; @override - String get shortJumada1Label => r'جام. من'; + String get muharramLabel => r'Muharram'; @override - String get shortJumada2Label => r'جام. II'; + String get noEventsCalendarLabel => r'Ingen begivenheder'; @override - String get shortMuharramLabel => r'ماه'; + String get noMatchesDataGridFilteringLabel => r'Ingen kampe'; @override - String get shortRabi1Label => r'ربیع من'; + String get noSelectedDateCalendarLabel => r'Ingen valgt dato'; @override - String get shortRabi2Label => r'ربیع II'; + String get notEmptyDataGridFilteringLabel => r'Ikke tom'; @override - String get shortRajabLabel => r'راج'; + String get notNullDataGridFilteringLabel => r'Ikke Nul'; @override - String get shortRamadanLabel => r'رم.'; + String get nullDataGridFilteringLabel => r'Nul'; @override - String get shortSafarLabel => r'ساف'; + String get numberFiltersDataGridFilteringLabel => r'Nummerfiltre'; @override - String get shortShaabanLabel => r'شا.'; + String get ofDataPagerLabel => r'af'; @override - String get shortShawwalLabel => r'شاو'; + String get okDataGridFilteringLabel => r'Okay'; @override - String get todayLabel => r'امروز'; + String get orDataGridFilteringLabel => r'Eller'; @override - String get weeknumberLabel => r'هفته'; -} - -/// The translations for Finnish (`fi`). -class SfLocalizationsFi extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsFi class - const SfLocalizationsFi({ - String localeName = 'fi', - }) : super( - localeName: localeName, - ); - - @override - String get allDayLabel => r'Koko päivä'; - - @override - String get allowedViewDayLabel => r'Päivä'; - - @override - String get allowedViewMonthLabel => r'Kuukausi'; - - @override - String get allowedViewScheduleLabel => r'Ajoittaa'; + String get pagesDataPagerLabel => r'sider'; @override - String get allowedViewTimelineDayLabel => r'Aikajanan päivä'; - - @override - String get allowedViewTimelineMonthLabel => r'Aikajanan kuukausi'; + String get passwordDialogContentLabel => + r'Indtast adgangskoden for at åbne denne PDF-fil'; @override - String get allowedViewTimelineWeekLabel => r'Aikajanan viikko'; + String get passwordDialogHeaderTextLabel => r'Adgangskodebeskyttet'; @override - String get allowedViewTimelineWorkWeekLabel => r'Aikajanan työviikko'; + String get passwordDialogHintTextLabel => r'Indtast adgangskode'; @override - String get allowedViewWeekLabel => r'Viikko'; + String get passwordDialogInvalidPasswordLabel => r'forkert kodeord'; @override - String get allowedViewWorkWeekLabel => r'Työviikko'; + String get pdfBookmarksLabel => r'Bogmærker'; @override - String get daySpanCountLabel => r'Päivä'; + String get pdfEnterPageNumberLabel => r'Indtast sidenummer'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfGoToPageLabel => r'Gå til side'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfHyperlinkContentLabel => r'Vil du åbne siden kl'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfHyperlinkDialogCancelLabel => r'AFBESTILLE'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfHyperlinkDialogOpenLabel => r'ÅBEN'; @override - String get muharramLabel => r'Muharram'; + String get pdfHyperlinkLabel => r'Åbn webside'; @override - String get noEventsCalendarLabel => r'Ei tapahtumia'; + String get pdfInvalidPageNumberLabel => r'Indtast venligst et gyldigt nummer'; @override - String get noSelectedDateCalendarLabel => r'Ei valittua päivämäärää'; + String get pdfNoBookmarksLabel => r'Ingen bogmærker fundet'; @override - String get ofDataPagerLabel => r'/'; + String get pdfPaginationDialogCancelLabel => r'AFBESTILLE'; @override - String get pagesDataPagerLabel => r'sivuja'; + String get pdfPaginationDialogOkLabel => r'Okay'; @override - String get passwordDialogContentLabel => - r'Kirjoita salasana avataksesi tämän PDF-tiedoston'; + String get pdfPasswordDialogCancelLabel => r'AFBESTILLE'; @override - String get passwordDialogHeaderTextLabel => r'Salasana suojattu'; + String get pdfPasswordDialogOpenLabel => r'ÅBEN'; @override - String get passwordDialogHintTextLabel => r'Kirjoita salasana'; + String get pdfScrollStatusOfLabel => r'af'; @override - String get passwordDialogInvalidPasswordLabel => r'väärä salasana'; + String get pdfSignaturePadDialogClearLabel => r'Ryd'; @override - String get pdfBookmarksLabel => r'Kirjanmerkit'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Tegn din signatur'; @override - String get pdfEnterPageNumberLabel => r'Syötä sivunumero'; + String get pdfSignaturePadDialogPenColorLabel => r'Pen farve'; @override - String get pdfGoToPageLabel => r'Mene sivulle'; + String get pdfSignaturePadDialogSaveLabel => r'GEMME'; @override - String get pdfInvalidPageNumberLabel => r'Anna kelvollinen numero'; + String get pdfTextSelectionMenuCopyLabel => r'Kopi'; @override - String get pdfNoBookmarksLabel => r'Kirjanmerkkejä ei löytynyt'; + String get pdfTextSelectionMenuHighlightLabel => r'Fremhæv'; @override - String get pdfPaginationDialogCancelLabel => r'PERUUTTAA'; + String get pdfTextSelectionMenuSquigglyLabel => r'Krydret'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Gennemstregning'; @override - String get pdfPasswordDialogCancelLabel => r'PERUUTTAA'; + String get pdfTextSelectionMenuUnderlineLabel => r'Understrege'; @override - String get pdfPasswordDialogOpenLabel => r'AVATA'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get pdfScrollStatusOfLabel => r'/'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get rajabLabel => r'Rajab'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get ramadanLabel => r'Ramadan'; @override - String get rajabLabel => r'Rajab'; + String get rowsPerPageDataPagerLabel => r'Rækker pr. side'; @override - String get ramadanLabel => r'Ramadan'; + String get safarLabel => r'Safar'; @override - String get rowsPerPageDataPagerLabel => r'Rivejä per sivu'; + String get searchDataGridFilteringLabel => r'Søg'; @override - String get safarLabel => r'Safar'; + String get selectAllDataGridFilteringLabel => r'Vælg alle'; @override - String get series => r'Sarja'; + String get series => r'Serie'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get shortJumada1Label => r'Jum. minä'; + String get shortJumada1Label => r'Jum. jeg'; @override String get shortJumada2Label => r'Jum. II'; @@ -3454,7 +3845,7 @@ class SfLocalizationsFi extends SfGlobalLocalizations { String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. minä'; + String get shortRabi1Label => r'Rabi. jeg'; @override String get shortRabi2Label => r'Rabi. II'; @@ -3463,7 +3854,7 @@ class SfLocalizationsFi extends SfGlobalLocalizations { String get shortRajabLabel => r'Raj.'; @override - String get shortRamadanLabel => r'RAM.'; + String get shortRamadanLabel => r'Vædder.'; @override String get shortSafarLabel => r'Saf.'; @@ -3475,354 +3866,344 @@ class SfLocalizationsFi extends SfGlobalLocalizations { String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Tänään'; - - @override - String get weeknumberLabel => r'Viikko'; -} - -/// The translations for Filipino Pilipino (`fil`). -class SfLocalizationsFil extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsFil class - const SfLocalizationsFil({ - String localeName = 'fil', - }) : super( - localeName: localeName, - ); - - @override - String get allDayLabel => r'Buong araw'; + String get showRowsWhereDataGridFilteringLabel => r'Vis rækker hvor'; @override - String get allowedViewDayLabel => r'Araw'; - - @override - String get allowedViewMonthLabel => r'buwan'; + String get sortAToZDataGridFilteringLabel => r'Sorter fra A til Z'; @override - String get allowedViewScheduleLabel => r'Iskedyul'; + String get sortAndFilterDataGridFilteringLabel => r'Sorter og filtrer'; @override - String get allowedViewTimelineDayLabel => r'Araw ng Timeline'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sorter Størst Til Mindst'; @override - String get allowedViewTimelineMonthLabel => r'Buwan ng Timeline'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sorter nyeste til ældste'; @override - String get allowedViewTimelineWeekLabel => r'Linggo ng Timeline'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sorter ældst til nyeste'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Linggo ng Trabaho sa Timeline'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sorter mindst til størst'; @override - String get allowedViewWeekLabel => r'Linggo'; + String get sortZToADataGridFilteringLabel => r'Sorter Z til A'; @override - String get allowedViewWorkWeekLabel => r'Linggo ng trabaho'; + String get textFiltersDataGridFilteringLabel => r'Tekstfiltre'; @override - String get daySpanCountLabel => r'Araw'; + String get todayLabel => r'I dag'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get weeknumberLabel => r'Uge'; +} - @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; +/// The translations for German (`de`). +class SfLocalizationsDe extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsDe class + const SfLocalizationsDe({String localeName = 'de'}) + : super(localeName: localeName); @override - String get jumada1Label => r'Jumada al-awwal'; + String get afterDataGridFilteringLabel => r'Nach'; @override - String get jumada2Label => r'Jumada al-thani'; + String get afterOrEqualDataGridFilteringLabel => r'Nach oder gleich'; @override - String get muharramLabel => r'Muharram'; + String get allDayLabel => r'Ganztägig'; @override - String get noEventsCalendarLabel => r'Walang mga kaganapan'; + String get allowedViewDayLabel => r'Tag'; @override - String get noSelectedDateCalendarLabel => r'Walang napiling petsa'; + String get allowedViewMonthLabel => r'Monat'; @override - String get ofDataPagerLabel => r'ng'; + String get allowedViewScheduleLabel => r'Zeitplan'; @override - String get pagesDataPagerLabel => r'mga pahina'; + String get allowedViewTimelineDayLabel => r'Zeitleiste Tag'; @override - String get passwordDialogContentLabel => - r'Ilagay ang password para buksan ang PDF file na ito'; + String get allowedViewTimelineMonthLabel => r'Zeitleiste Monat"'; @override - String get passwordDialogHeaderTextLabel => r'Pinoprotektahan ng Password'; + String get allowedViewTimelineWeekLabel => r'Zeitleiste Woche'; @override - String get passwordDialogHintTextLabel => r'Ilagay ang password'; + String get allowedViewTimelineWorkWeekLabel => r'Zeitleiste Arbeitswoche'; @override - String get passwordDialogInvalidPasswordLabel => r'di wastong password'; + String get allowedViewWeekLabel => r'Woche'; @override - String get pdfBookmarksLabel => r'Mga bookmark'; + String get allowedViewWorkWeekLabel => r'Arbeitswoche'; @override - String get pdfEnterPageNumberLabel => r'Ipasok ang numero ng pahina'; + String get andDataGridFilteringLabel => r'Und'; @override - String get pdfGoToPageLabel => r'Pumunta sa pahina'; + String get beforeDataGridFilteringLabel => r'Vor'; @override - String get pdfInvalidPageNumberLabel => - r'Mangyaring magpasok ng wastong numero'; + String get beforeOrEqualDataGridFilteringLabel => r'Vor oder gleich'; @override - String get pdfNoBookmarksLabel => r'Walang nakitang mga bookmark'; + String get beginsWithDataGridFilteringLabel => r'Beginnt mit'; @override - String get pdfPaginationDialogCancelLabel => r'KANSELAHIN'; + String get cancelDataGridFilteringLabel => r'Abbrechen'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get clearFilterDataGridFilteringLabel => r'Filter löschen'; @override - String get pdfPasswordDialogCancelLabel => r'KANSELAHIN'; + String get containsDataGridFilteringLabel => r'Enthält'; @override - String get pdfPasswordDialogOpenLabel => r'BUKAS'; + String get dateFiltersDataGridFilteringLabel => r'Datumsfilter'; @override - String get pdfScrollStatusOfLabel => r'ng'; + String get daySpanCountLabel => r'Tag'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get dhualqiLabel => r'Dhu al-Qidah'; @override - String get rajabLabel => r'Rajab'; + String get doesNotBeginWithDataGridFilteringLabel => r'Beginnt nicht mit'; @override - String get ramadanLabel => r'Ramadan'; + String get doesNotContainDataGridFilteringLabel => r'Beinhaltet nicht'; @override - String get rowsPerPageDataPagerLabel => r'Mga hilera bawat pahina'; + String get doesNotEndWithDataGridFilteringLabel => r'Endet nicht mit'; @override - String get safarLabel => r'Safar'; + String get doesNotEqualDataGridFilteringLabel => r'Ist nicht gleich'; @override - String get series => r'Serye'; + String get emptyDataGridFilteringLabel => r'Leer'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get endsWithDataGridFilteringLabel => r'Endet mit'; @override - String get shawwalLabel => r'Shawwal'; + String get equalsDataGridFilteringLabel => r'Gleich'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get fromDataGridFilteringLabel => r'Aus'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get greaterThanDataGridFilteringLabel => r'Größer als'; @override - String get shortJumada1Label => r'Jum. ako'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Größer als oder gleich'; @override - String get shortJumada2Label => r'Jum. II'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get shortMuharramLabel => r'Muh.'; + String get jumada2Label => r'Jumada al-thani'; @override - String get shortRabi1Label => r'Rabi. ako'; + String get lessThanDataGridFilteringLabel => r'Weniger als'; @override - String get shortRabi2Label => r'Rabi. II'; + String get lessThanOrEqualDataGridFilteringLabel => + r'Weniger als oder gleich'; @override - String get shortRajabLabel => r'Raj.'; + String get muharramLabel => r'Muharram'; @override - String get shortRamadanLabel => r'Ram.'; + String get noEventsCalendarLabel => r'Keine Ereignisse'; @override - String get shortSafarLabel => r'Saf.'; + String get noMatchesDataGridFilteringLabel => r'Keine Treffer'; @override - String get shortShaabanLabel => r'Sha.'; + String get noSelectedDateCalendarLabel => r'Kein Datum ausgewählt'; @override - String get shortShawwalLabel => r'Shaw.'; + String get notEmptyDataGridFilteringLabel => r'Nicht leer'; @override - String get todayLabel => r'Ngayong araw'; + String get notNullDataGridFilteringLabel => r'Nicht null'; @override - String get weeknumberLabel => r'Linggo'; -} - -/// The translations for French (`fr`). -class SfLocalizationsFr extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsFr class - const SfLocalizationsFr({ - String localeName = 'fr', - }) : super( - localeName: localeName, - ); + String get nullDataGridFilteringLabel => r'Null'; @override - String get allDayLabel => r'Toute la journée'; + String get numberFiltersDataGridFilteringLabel => r'Zahlenfilter'; @override - String get allowedViewDayLabel => r'Jour'; + String get ofDataPagerLabel => r'von'; @override - String get allowedViewMonthLabel => r'Mois'; + String get okDataGridFilteringLabel => r'OK'; @override - String get allowedViewScheduleLabel => r'Programme'; + String get orDataGridFilteringLabel => r'Oder'; @override - String get allowedViewTimelineDayLabel => r'Chronologie Jour'; + String get pagesDataPagerLabel => r'Seiten'; @override - String get allowedViewTimelineMonthLabel => r'Chronologie Mois'; + String get passwordDialogContentLabel => + r'Geben Sie das Passwort ein, um diese PDF-Datei zu öffnen'; @override - String get allowedViewTimelineWeekLabel => r'Chronologie Semaine'; + String get passwordDialogHeaderTextLabel => r'Passwortgeschützt'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Chronologie de la semaine de travail'; + String get passwordDialogHintTextLabel => r'Passwort eingeben'; @override - String get allowedViewWeekLabel => r'Semaine'; + String get passwordDialogInvalidPasswordLabel => r'Ungültiges Passwort'; @override - String get allowedViewWorkWeekLabel => r'Semaine de travail'; + String get pdfBookmarksLabel => r'Lesezeichen'; @override - String get daySpanCountLabel => r'Jour'; + String get pdfEnterPageNumberLabel => r'Seitenzahl eingeben'; @override - String get dhualhiLabel => r'Safar'; + String get pdfGoToPageLabel => r'Gehen Sie zur Seite'; @override - String get dhualqiLabel => r'Dhou al-Qi' "'" r'dah'; + String get pdfHyperlinkContentLabel => r'Möchten Sie die Seite öffnen unter'; @override - String get jumada1Label => r'Djoumada al-awwal'; + String get pdfHyperlinkDialogCancelLabel => r'ABBRECHEN'; @override - String get jumada2Label => r'Djoumada al-thani'; + String get pdfHyperlinkDialogOpenLabel => r'ÖFFNEN'; @override - String get muharramLabel => r'Mouharram'; + String get pdfHyperlinkLabel => r'Webseite öffnen'; @override - String get noEventsCalendarLabel => r'Pas d' "'" r'événements'; + String get pdfInvalidPageNumberLabel => + r'Bitte geben Sie eine gültige Nummer ein'; @override - String get noSelectedDateCalendarLabel => r'Aucune date sélectionnée'; + String get pdfNoBookmarksLabel => r'Keine Lesezeichen gefunden'; @override - String get ofDataPagerLabel => r'de'; + String get pdfPaginationDialogCancelLabel => r'ABBRECHEN'; @override - String get pagesDataPagerLabel => r'pages'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get passwordDialogContentLabel => - r'Entrez le mot de passe pour ouvrir ce fichier PDF'; + String get pdfPasswordDialogCancelLabel => r'ABBRECHEN'; @override - String get passwordDialogHeaderTextLabel => r'Protégé par mot de passe'; + String get pdfPasswordDialogOpenLabel => r'ÖFFNEN'; @override - String get passwordDialogHintTextLabel => r'Entrer le mot de passe'; + String get pdfScrollStatusOfLabel => r'von'; @override - String get passwordDialogInvalidPasswordLabel => r'Mot de passe incorrect'; + String get pdfSignaturePadDialogClearLabel => r'KLAR'; @override - String get pdfBookmarksLabel => r'Signets'; + String get pdfSignaturePadDialogHeaderTextLabel => + r'Zeichnen Sie Ihre Unterschrift'; @override - String get pdfEnterPageNumberLabel => r'Entrer le numéro de page'; + String get pdfSignaturePadDialogPenColorLabel => r'Stiftfarbe'; @override - String get pdfGoToPageLabel => r'Aller à la page'; + String get pdfSignaturePadDialogSaveLabel => r'SPEICHERN'; @override - String get pdfInvalidPageNumberLabel => - r'S' "'" r'il vous plait, entrez un nombre valide'; + String get pdfTextSelectionMenuCopyLabel => r'Kopieren'; @override - String get pdfNoBookmarksLabel => r'Aucun favori trouvé'; + String get pdfTextSelectionMenuHighlightLabel => r'Markieren'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULER'; + String get pdfTextSelectionMenuSquigglyLabel => r'Schnörkellos'; @override - String get pdfPaginationDialogOkLabel => r'd' "'" r'accord'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Durchgestrichen'; @override - String get pdfPasswordDialogCancelLabel => r'ANNULER'; + String get pdfTextSelectionMenuUnderlineLabel => r'Unterstreichen'; @override - String get pdfPasswordDialogOpenLabel => r'OUVRIR'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get pdfScrollStatusOfLabel => r'de'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get rajabLabel => r'Rajab'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get ramadanLabel => r'Ramadan'; @override - String get rajabLabel => r'Rajab'; + String get rowsPerPageDataPagerLabel => r'Zeilen pro Seite'; @override - String get ramadanLabel => r'Ramadan'; + String get safarLabel => r'Safar'; @override - String get rowsPerPageDataPagerLabel => r'Lignes par page'; + String get searchDataGridFilteringLabel => r'Suche'; @override - String get safarLabel => r'Safar'; + String get selectAllDataGridFilteringLabel => r'Wählen Sie Alle'; @override - String get series => r'Séries'; + String get series => r'Serie'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'Shaaban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get shortJumada1Label => r'Jum. je'; + String get shortJumada1Label => r'Jum. ich'; @override String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Mouh.'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. je'; + String get shortRabi1Label => r'Rabi. ich'; @override String get shortRabi2Label => r'Rabi. II'; @@ -3834,9738 +4215,22760 @@ class SfLocalizationsFr extends SfGlobalLocalizations { String get shortRamadanLabel => r'RAM.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'Sicher.'; @override - String get shortShaabanLabel => r'Cha.'; + String get shortShaabanLabel => r'Scha.'; @override - String get shortShawwalLabel => r'Shaw.'; + String get shortShawwalLabel => r'Schau.'; @override - String get todayLabel => r'Aujourd' "'" r'hui'; + String get showRowsWhereDataGridFilteringLabel => r'Zeig Zeilen wo'; @override - String get weeknumberLabel => r'Semaine'; -} - -/// The translations for Galician (`gl`). -class SfLocalizationsGl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsGl class - const SfLocalizationsGl({ - String localeName = 'gl', - }) : super( - localeName: localeName, - ); + String get sortAToZDataGridFilteringLabel => r'A bis Z sortieren'; @override - String get allDayLabel => r'Todo o día'; + String get sortAndFilterDataGridFilteringLabel => r'Sortieren und filtern'; @override - String get allowedViewDayLabel => r'Día'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Vom Größten zum Kleinsten sortieren'; @override - String get allowedViewMonthLabel => r'Mes'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Vom Neusten zum Ältesten sortieren'; @override - String get allowedViewScheduleLabel => r'Horario'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Vom Ältesten zum Neuesten sortieren'; @override - String get allowedViewTimelineDayLabel => r'Día da cronoloxía'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Vom Kleinsten zum Größten sortieren'; @override - String get allowedViewTimelineMonthLabel => r'Mes do cronograma'; + String get sortZToADataGridFilteringLabel => r'Z bis A sortieren'; @override - String get allowedViewTimelineWeekLabel => r'Semana do cronograma'; + String get textFiltersDataGridFilteringLabel => r'Textfilter'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get todayLabel => r'Heute'; @override - String get allowedViewWeekLabel => r'Semana'; + String get weeknumberLabel => r'Woche'; +} - @override - String get allowedViewWorkWeekLabel => r'Semana de Traballo'; +/// The translations for Modern Greek (`el`). +class SfLocalizationsEl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsEl class + const SfLocalizationsEl({String localeName = 'el'}) + : super(localeName: localeName); @override - String get daySpanCountLabel => r'Día'; + String get afterDataGridFilteringLabel => r'Μετά'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get afterOrEqualDataGridFilteringLabel => r'Μετά Ή Ίσα'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get allDayLabel => r'Ολη μέρα'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get allowedViewDayLabel => r'Ημέρα'; @override - String get jumada2Label => r'Jumada al-thani'; + String get allowedViewMonthLabel => r'Μήνας'; @override - String get muharramLabel => r'Muharram'; + String get allowedViewScheduleLabel => r'Πρόγραμμα'; @override - String get noEventsCalendarLabel => r'Non hai eventos'; + String get allowedViewTimelineDayLabel => r'Ημέρα Χρονολογίου'; @override - String get noSelectedDateCalendarLabel => r'Sen data seleccionada'; + String get allowedViewTimelineMonthLabel => r'Χρονολόγιο Μήνας'; @override - String get ofDataPagerLabel => r'de'; + String get allowedViewTimelineWeekLabel => r'Χρονολόγιο Εβδομάδα'; @override - String get pagesDataPagerLabel => r'páxinas'; + String get allowedViewTimelineWorkWeekLabel => + r'Χρονολόγιο Εβδομάδα εργασίας'; @override - String get passwordDialogContentLabel => - r'Introduza o contrasinal para abrir este ficheiro PDF'; + String get allowedViewWeekLabel => r'Εβδομάδα'; @override - String get passwordDialogHeaderTextLabel => r'Protexido por contrasinal'; + String get allowedViewWorkWeekLabel => r'Εβδομάδα εργασίας'; @override - String get passwordDialogHintTextLabel => r'Escriba o contrasinal'; + String get andDataGridFilteringLabel => r'Και'; @override - String get passwordDialogInvalidPasswordLabel => r'Contrasinal incorrecta'; + String get beforeDataGridFilteringLabel => r'Πριν'; @override - String get pdfBookmarksLabel => r'Marcadores'; + String get beforeOrEqualDataGridFilteringLabel => r'Πριν ή ίσο'; @override - String get pdfEnterPageNumberLabel => r'Introduza o número de páxina'; + String get beginsWithDataGridFilteringLabel => r'Ξεκινάει με'; @override - String get pdfGoToPageLabel => r'Ir á páxina'; + String get cancelDataGridFilteringLabel => r'Ματαίωση'; @override - String get pdfInvalidPageNumberLabel => r'Introduce un número válido'; + String get clearFilterDataGridFilteringLabel => r'Διαγραφή φίλτρου'; @override - String get pdfNoBookmarksLabel => r'Non se atoparon marcadores'; + String get containsDataGridFilteringLabel => r'Περιέχει'; @override - String get pdfPaginationDialogCancelLabel => r'CANCELAR'; + String get dateFiltersDataGridFilteringLabel => r'Φίλτρα ημερομηνίας'; @override - String get pdfPaginationDialogOkLabel => r'Ok'; + String get daySpanCountLabel => r'Ημέρα'; @override - String get pdfPasswordDialogCancelLabel => r'CANCELAR'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get pdfPasswordDialogOpenLabel => r'ABERTO'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get pdfScrollStatusOfLabel => r'de'; + String get doesNotBeginWithDataGridFilteringLabel => r'Δεν ξεκινά με'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get doesNotContainDataGridFilteringLabel => r'Δεν περιέχει'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get doesNotEndWithDataGridFilteringLabel => r'Δεν τελειώνει με'; @override - String get rajabLabel => r'Rajab'; + String get doesNotEqualDataGridFilteringLabel => r'Δεν ισούται'; @override - String get ramadanLabel => r'Ramadán'; + String get emptyDataGridFilteringLabel => r'Αδειάζω'; @override - String get rowsPerPageDataPagerLabel => r'Filas por páxina'; + String get endsWithDataGridFilteringLabel => r'Τελειώνει με'; @override - String get safarLabel => r'Safar'; + String get equalsDataGridFilteringLabel => r'Ίσο'; @override - String get series => r'Serie'; + String get fromDataGridFilteringLabel => r'Από'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get greaterThanDataGridFilteringLabel => r'Μεγαλύτερος από'; @override - String get shawwalLabel => r'Shawwal'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Μεγαλύτερο από ή ίσο'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get jumada2Label => r'Τζουμάντα αλ-θάνι'; @override - String get shortJumada1Label => r'Jum. eu'; + String get lessThanDataGridFilteringLabel => r'Λιγότερο από'; @override - String get shortJumada2Label => r'Jum. II'; + String get lessThanOrEqualDataGridFilteringLabel => r'Λιγότερο από ή ίσο'; @override - String get shortMuharramLabel => r'Muh.'; + String get muharramLabel => r'Μουχαράμ'; @override - String get shortRabi1Label => r'Rabi. eu'; + String get noEventsCalendarLabel => r'Δεν υπάρχουν εκδηλώσεις'; @override - String get shortRabi2Label => r'Rabi. II'; + String get noMatchesDataGridFilteringLabel => r'Χωρίς αγώνες'; @override - String get shortRajabLabel => r'Raj.'; + String get noSelectedDateCalendarLabel => r'Δεν έχει επιλεγεί ημερομηνία'; @override - String get shortRamadanLabel => r'RAM.'; + String get notEmptyDataGridFilteringLabel => r'Οχι άδειο'; @override - String get shortSafarLabel => r'Saf.'; + String get notNullDataGridFilteringLabel => r'Οχι κενό'; @override - String get shortShaabanLabel => r'Sha.'; + String get nullDataGridFilteringLabel => r'Μηδενικό'; @override - String get shortShawwalLabel => r'Shaw.'; + String get numberFiltersDataGridFilteringLabel => r'Φίλτρα αριθμών'; @override - String get todayLabel => r'Hoxe'; + String get ofDataPagerLabel => r'του'; @override - String get weeknumberLabel => r'Semana'; -} - -/// The translations for Gujarati (`gu`). -class SfLocalizationsGu extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsGu class - const SfLocalizationsGu({ - String localeName = 'gu', - }) : super( - localeName: localeName, - ); + String get okDataGridFilteringLabel => r'Εντάξει'; @override - String get allDayLabel => r'બધા દિવસ'; + String get orDataGridFilteringLabel => r'Ή'; @override - String get allowedViewDayLabel => r'દિવસ'; + String get pagesDataPagerLabel => r'σελίδες'; @override - String get allowedViewMonthLabel => r'માસ'; + String get passwordDialogContentLabel => + r'Εισαγάγετε τον κωδικό πρόσβασης για να ανοίξετε αυτό το αρχείο PDF'; @override - String get allowedViewScheduleLabel => r'અનુસૂચિ'; + String get passwordDialogHeaderTextLabel => + r'Προστατεύεται με κωδικό πρόσβασης'; @override - String get allowedViewTimelineDayLabel => r'સમયરેખા દિવસ'; + String get passwordDialogHintTextLabel => r'Εισάγετε τον κωδικό πρόσβασης'; @override - String get allowedViewTimelineMonthLabel => r'સમયરેખા મહિનો'; + String get passwordDialogInvalidPasswordLabel => r'Λανθασμένος κωδικός'; @override - String get allowedViewTimelineWeekLabel => r'સમયરેખા અઠવાડિયું'; + String get pdfBookmarksLabel => r'Σελιδοδείκτες'; @override - String get allowedViewTimelineWorkWeekLabel => r'સમયરેખા કાર્ય સપ્તાહ'; + String get pdfEnterPageNumberLabel => r'Εισαγάγετε τον αριθμό σελίδας'; @override - String get allowedViewWeekLabel => r'સપ્તાહ'; + String get pdfGoToPageLabel => r'Πήγαινε στην σελίδα'; @override - String get allowedViewWorkWeekLabel => r'કાર્ય સપ્તાહ'; + String get pdfHyperlinkContentLabel => r'Θέλετε να ανοίξετε τη σελίδα στο'; @override - String get daySpanCountLabel => r'દિવસ'; + String get pdfHyperlinkDialogCancelLabel => r'ΜΑΤΑΙΩΣΗ'; @override - String get dhualhiLabel => r'ધુ અલ-હિજ્જા'; + String get pdfHyperlinkDialogOpenLabel => r'ΑΝΟΙΞΕ'; @override - String get dhualqiLabel => r'ધુ અલ-કિદાહ'; + String get pdfHyperlinkLabel => r'Ανοίξτε την ιστοσελίδα'; @override - String get jumada1Label => r'જુમાદા અલ-અવ્વલ'; + String get pdfInvalidPageNumberLabel => + r'Παρακαλώ εισάγετε έναν έγκυρο αριθμό'; @override - String get jumada2Label => r'જુમાદા અલ-થાની'; + String get pdfNoBookmarksLabel => r'Δεν βρέθηκαν σελιδοδείκτες'; @override - String get muharramLabel => r'મોહરમ'; + String get pdfPaginationDialogCancelLabel => r'ΜΑΤΑΙΩΣΗ'; @override - String get noEventsCalendarLabel => r'કોઈ ઇવેન્ટ નથી'; + String get pdfPaginationDialogOkLabel => r'Εντάξει'; @override - String get noSelectedDateCalendarLabel => r'કોઈ તારીખ પસંદ કરી નથી'; + String get pdfPasswordDialogCancelLabel => r'ΜΑΤΑΙΩΣΗ'; @override - String get ofDataPagerLabel => r'ના'; + String get pdfPasswordDialogOpenLabel => r'ΑΝΟΙΞΕ'; @override - String get pagesDataPagerLabel => r'પૃષ્ઠો'; + String get pdfScrollStatusOfLabel => r'του'; @override - String get passwordDialogContentLabel => - r'આ PDF ફાઇલ ખોલવા માટે પાસવર્ડ દાખલ કરો'; + String get pdfSignaturePadDialogClearLabel => r'Καθαρισμός'; @override - String get passwordDialogHeaderTextLabel => r'પાસવર્ડ સુરક્ષિત'; + String get pdfSignaturePadDialogHeaderTextLabel => + r'Σχεδιάστε την υπογραφή σας'; @override - String get passwordDialogHintTextLabel => r'પાસવર્ડ દાખલ કરો'; + String get pdfSignaturePadDialogPenColorLabel => r'Χρώμα στυλό'; @override - String get passwordDialogInvalidPasswordLabel => r'અમાન્ય પાસવર્ડ'; + String get pdfSignaturePadDialogSaveLabel => r'Αποθήκευση'; @override - String get pdfBookmarksLabel => r'બુકમાર્ક્સ'; + String get pdfTextSelectionMenuCopyLabel => r'Αντιγραφή'; @override - String get pdfEnterPageNumberLabel => r'પૃષ્ઠ નંબર દાખલ કરો'; + String get pdfTextSelectionMenuHighlightLabel => r'Επισήμανση'; @override - String get pdfGoToPageLabel => r'પૃષ્ઠ પર જાઓ'; + String get pdfTextSelectionMenuSquigglyLabel => r'Κυματιστή υπογράμμιση'; @override - String get pdfInvalidPageNumberLabel => r'કૃપા કરીને માન્ય નંબર દાખલ કરો'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Διαγραφή'; @override - String get pdfNoBookmarksLabel => r'કોઈ બુકમાર્ક્સ મળ્યાં નથી'; + String get pdfTextSelectionMenuUnderlineLabel => r'Υπογράμμιση'; @override - String get pdfPaginationDialogCancelLabel => r'રદ કરો'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get pdfPaginationDialogOkLabel => r'બરાબર'; + String get rabi2Label => + r'Ραμπί' + "'" + r' αλ-θάνι'; @override - String get pdfPasswordDialogCancelLabel => r'રદ કરો'; + String get rajabLabel => r'Ρατζάμπ'; @override - String get pdfPasswordDialogOpenLabel => r'ખુલ્લા'; + String get ramadanLabel => r'Ραμαζάνι'; @override - String get pdfScrollStatusOfLabel => r'ના'; + String get rowsPerPageDataPagerLabel => r'Σειρές ανά σελίδα'; @override - String get rabi1Label => r'રબી અલ અવ્વલ'; + String get safarLabel => r'Σαφάρ'; @override - String get rabi2Label => r'રબી અલ-થાની'; + String get searchDataGridFilteringLabel => r'Αναζήτηση'; @override - String get rajabLabel => r'રજબ'; + String get selectAllDataGridFilteringLabel => r'Επιλογή όλων'; @override - String get ramadanLabel => r'રમઝાન'; + String get series => r'Σειρά'; @override - String get rowsPerPageDataPagerLabel => r'પૃષ્ઠ દીઠ પંક્તિઓ'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get safarLabel => r'સફર'; + String get shawwalLabel => r'Shawwal'; @override - String get series => r'શ્રેણી'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shaabanLabel => r'શાઅબાન'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get shawwalLabel => r'શવ્વાલ'; + String get shortJumada1Label => r'Jum. Εγώ'; @override - String get shortDhualhiLabel => r'ધુલ-એચ'; + String get shortJumada2Label => r'Jum. II'; @override - String get shortDhualqiLabel => r'ધુલ-પ્ર'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortJumada1Label => r'જમ. આઈ'; + String get shortRabi1Label => r'Ραμπί. Εγώ'; @override - String get shortJumada2Label => r'જમ. II'; + String get shortRabi2Label => r'Ραμπί. II'; @override - String get shortMuharramLabel => r'મુહ.'; + String get shortRajabLabel => r'Κυριαρχία.'; @override - String get shortRabi1Label => r'રબી. આઈ'; + String get shortRamadanLabel => r'Εμβολο.'; @override - String get shortRabi2Label => r'રબી. II'; + String get shortSafarLabel => r'Saf.'; @override - String get shortRajabLabel => r'રાજ.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortRamadanLabel => r'રામ.'; + String get shortShawwalLabel => r'Shaw.'; @override - String get shortSafarLabel => r'સેફ.'; + String get showRowsWhereDataGridFilteringLabel => r'Εμφάνιση σειρών όπου'; @override - String get shortShaabanLabel => r'શા.'; + String get sortAToZDataGridFilteringLabel => r'Ταξινόμηση από Α έως Ω'; @override - String get shortShawwalLabel => r'શૉ.'; + String get sortAndFilterDataGridFilteringLabel => + r'Ταξινόμηση και φιλτράρισμα'; @override - String get todayLabel => r'આજે'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ταξινόμηση από το μεγαλύτερο στο μικρότερο'; @override - String get weeknumberLabel => r'સપ્તાહ'; -} - -/// The translations for Hebrew (`he`). -class SfLocalizationsHe extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsHe class - const SfLocalizationsHe({ - String localeName = 'he', - }) : super( - localeName: localeName, - ); + String get sortNewestToOldestDataGridFilteringLabel => + r'Ταξινόμηση νεότερο προς παλαιότερο'; @override - String get allDayLabel => r'כל היום'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Ταξινόμηση από το παλαιότερο στο νεότερο'; @override - String get allowedViewDayLabel => r'יְוֹם'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ταξινόμηση από το μικρότερο προς το μεγαλύτερο'; @override - String get allowedViewMonthLabel => r'חוֹדֶשׁ'; + String get sortZToADataGridFilteringLabel => r'Ταξινόμηση Z σε A'; @override - String get allowedViewScheduleLabel => r'לוח זמנים'; + String get textFiltersDataGridFilteringLabel => r'Φίλτρα κειμένου'; @override - String get allowedViewTimelineDayLabel => r'יום ציר הזמן'; + String get todayLabel => r'Σήμερα'; @override - String get allowedViewTimelineMonthLabel => r'חודש ציר הזמן'; + String get weeknumberLabel => r'Εβδομάδα'; +} - @override - String get allowedViewTimelineWeekLabel => r'שבוע ציר הזמן'; +/// The translations for English (`en`). +class SfLocalizationsEn extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsEn class + const SfLocalizationsEn({String localeName = 'en'}) + : super(localeName: localeName); @override - String get allowedViewTimelineWorkWeekLabel => r'שבוע העבודה של ציר הזמן'; + String get afterDataGridFilteringLabel => r'After'; @override - String get allowedViewWeekLabel => r'שָׁבוּעַ'; + String get afterOrEqualDataGridFilteringLabel => r'After Or Equal'; @override - String get allowedViewWorkWeekLabel => r'שבוע עבודה'; + String get allDayLabel => r'All Day'; @override - String get daySpanCountLabel => r'יְוֹם'; + String get allowedViewDayLabel => r'Day'; @override - String get dhualhiLabel => r'דהו אל-היג' "'" r'ה'; + String get allowedViewMonthLabel => r'Month'; @override - String get dhualqiLabel => r'דהו אל-קיאדה'; + String get allowedViewScheduleLabel => r'Schedule'; @override - String get jumada1Label => r'ג' "'" r'ומדה אל-אוואל'; + String get allowedViewTimelineDayLabel => r'Timeline Day'; @override - String get jumada2Label => r'ג' "'" r'ומדה אל-ת' "'" r'אני'; + String get allowedViewTimelineMonthLabel => r'Timeline Month'; @override - String get muharramLabel => r'מוחרם'; + String get allowedViewTimelineWeekLabel => r'Timeline Week'; @override - String get noEventsCalendarLabel => r'אין אירועים'; + String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; @override - String get noSelectedDateCalendarLabel => r'לא נבחר תאריך'; + String get allowedViewWeekLabel => r'Week'; @override - String get ofDataPagerLabel => r'שֶׁל'; + String get allowedViewWorkWeekLabel => r'Work Week'; @override - String get pagesDataPagerLabel => r'דפים'; + String get andDataGridFilteringLabel => r'And'; @override - String get passwordDialogContentLabel => - r'הזן את הסיסמה כדי לפתוח קובץ PDF זה'; + String get beforeDataGridFilteringLabel => r'Before'; @override - String get passwordDialogHeaderTextLabel => r'סיסמא מוגנת'; + String get beforeOrEqualDataGridFilteringLabel => r'Before Or Equal'; @override - String get passwordDialogHintTextLabel => r'הזן את הסיסמה'; + String get beginsWithDataGridFilteringLabel => r'Begins With'; @override - String get passwordDialogInvalidPasswordLabel => r'סיסמה שגויה'; + String get cancelDataGridFilteringLabel => r'Cancel'; @override - String get pdfBookmarksLabel => r'סימניות'; + String get clearFilterDataGridFilteringLabel => r'Clear Filter'; @override - String get pdfEnterPageNumberLabel => r'הזן מספר עמוד'; + String get containsDataGridFilteringLabel => r'Contains'; @override - String get pdfGoToPageLabel => r'לך לעמוד'; + String get dateFiltersDataGridFilteringLabel => r'Date Filters'; @override - String get pdfInvalidPageNumberLabel => r'נא הכנס מספר תקף'; + String get daySpanCountLabel => r'Day'; @override - String get pdfNoBookmarksLabel => r'לא נמצאו סימניות'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get pdfPaginationDialogCancelLabel => r'לְבַטֵל'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get pdfPaginationDialogOkLabel => r'בסדר'; + String get doesNotBeginWithDataGridFilteringLabel => r'Does Not Begin With'; @override - String get pdfPasswordDialogCancelLabel => r'לְבַטֵל'; + String get doesNotContainDataGridFilteringLabel => r'Does Not Contain'; @override - String get pdfPasswordDialogOpenLabel => r'לִפְתוֹחַ'; + String get doesNotEndWithDataGridFilteringLabel => r'Does Not End With'; @override - String get pdfScrollStatusOfLabel => r'שֶׁל'; + String get doesNotEqualDataGridFilteringLabel => r'Does Not Equal'; @override - String get rabi1Label => r'רבי אל אווול'; + String get emptyDataGridFilteringLabel => r'Empty'; @override - String get rabi2Label => r'רבי אל-תאני'; + String get endsWithDataGridFilteringLabel => r'Ends With'; @override - String get rajabLabel => r'רג' "'" r'אב'; + String get equalsDataGridFilteringLabel => r'Equals'; @override - String get ramadanLabel => r'רמדאן'; + String get fromDataGridFilteringLabel => r'From'; @override - String get rowsPerPageDataPagerLabel => r'שורות בעמוד'; + String get greaterThanDataGridFilteringLabel => r'Greater Than'; @override - String get safarLabel => r'ספאר'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Greater Than Or Equal'; @override - String get series => r'סִדרָה'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get shaabanLabel => r'שעבן'; + String get jumada2Label => r'Jumada al-thani'; @override - String get shawwalLabel => r'שוואל'; + String get lessThanDataGridFilteringLabel => r'Less Than'; @override - String get shortDhualhiLabel => r'דהו' "'" "'" r'ל-ה'; + String get lessThanOrEqualDataGridFilteringLabel => r'Less Than Or Equal'; @override - String get shortDhualqiLabel => r'דהו' "'" r'ל-ק'; + String get muharramLabel => r'Muharram'; @override - String get shortJumada1Label => r'ג' "'" r'אם. אני'; + String get noEventsCalendarLabel => r'No events'; @override - String get shortJumada2Label => r'ג' "'" r'אם. II'; + String get noMatchesDataGridFilteringLabel => r'No matches'; @override - String get shortMuharramLabel => r'מוה.'; + String get noSelectedDateCalendarLabel => r'No date selected'; @override - String get shortRabi1Label => r'רבי. אני'; + String get notEmptyDataGridFilteringLabel => r'Not Empty'; @override - String get shortRabi2Label => r'רבי. II'; + String get notNullDataGridFilteringLabel => r'Not Null'; @override - String get shortRajabLabel => r'ראג' "'" r'.'; + String get nullDataGridFilteringLabel => r'Null'; @override - String get shortRamadanLabel => r'RAM.'; + String get numberFiltersDataGridFilteringLabel => r'Number Filters'; @override - String get shortSafarLabel => r'סאף.'; + String get ofDataPagerLabel => r'of'; @override - String get shortShaabanLabel => r'שא.'; + String get okDataGridFilteringLabel => r'OK'; @override - String get shortShawwalLabel => r'שו.'; + String get orDataGridFilteringLabel => r'Or'; @override - String get todayLabel => r'היום'; + String get pagesDataPagerLabel => r'pages'; @override - String get weeknumberLabel => r'שָׁבוּעַ'; -} + String get passwordDialogContentLabel => + r'Enter the password to open this PDF file'; -/// The translations for Hindi (`hi`). -class SfLocalizationsHi extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsHi class - const SfLocalizationsHi({ - String localeName = 'hi', - }) : super( - localeName: localeName, - ); + @override + String get passwordDialogHeaderTextLabel => r'Password Protected'; @override - String get allDayLabel => r'पूरे दिन'; + String get passwordDialogHintTextLabel => r'Enter Password'; @override - String get allowedViewDayLabel => r'दिन'; + String get passwordDialogInvalidPasswordLabel => r'Invalid Password'; @override - String get allowedViewMonthLabel => r'महीना'; + String get pdfBookmarksLabel => r'Bookmarks'; @override - String get allowedViewScheduleLabel => r'अनुसूची'; - - @override - String get allowedViewTimelineDayLabel => r'समयरेखा दिवस'; + String get pdfEnterPageNumberLabel => r'Enter page number'; @override - String get allowedViewTimelineMonthLabel => r'समयरेखा महीना'; + String get pdfGoToPageLabel => r'Go to page'; @override - String get allowedViewTimelineWeekLabel => r'समयरेखा सप्ताह'; + String get pdfHyperlinkContentLabel => r'Do you want to open the page at'; @override - String get allowedViewTimelineWorkWeekLabel => r'समयरेखा कार्य सप्ताह'; + String get pdfHyperlinkDialogCancelLabel => r'CANCEL'; @override - String get allowedViewWeekLabel => r'सप्ताह'; + String get pdfHyperlinkDialogOpenLabel => r'OPEN'; @override - String get allowedViewWorkWeekLabel => r'कार्य सप्ताह'; + String get pdfHyperlinkLabel => r'Open Web Page'; @override - String get daySpanCountLabel => r'दिन'; + String get pdfInvalidPageNumberLabel => r'Please enter a valid number'; @override - String get dhualhiLabel => r'धू अल-हिज्जाही'; + String get pdfNoBookmarksLabel => r'No bookmarks found'; @override - String get dhualqiLabel => r'धू अल क़िदाही'; + String get pdfPaginationDialogCancelLabel => r'CANCEL'; @override - String get jumada1Label => r'जुमादा अल-अव्वल'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get jumada2Label => r'जुमादा अल-थानी'; + String get pdfPasswordDialogCancelLabel => r'CANCEL'; @override - String get muharramLabel => r'मुहर्रम'; + String get pdfPasswordDialogOpenLabel => r'OPEN'; @override - String get noEventsCalendarLabel => r'कोई आयोजन नहीं'; + String get pdfScrollStatusOfLabel => r'of'; @override - String get noSelectedDateCalendarLabel => r'कोई चयनित तिथि नहीं'; + String get pdfSignaturePadDialogClearLabel => r'CLEAR'; @override - String get ofDataPagerLabel => r'का'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Draw your signature'; @override - String get pagesDataPagerLabel => r'पृष्ठों'; + String get pdfSignaturePadDialogPenColorLabel => r'Pen Color'; @override - String get passwordDialogContentLabel => - r'इस पीडीएफ फाइल को खोलने के लिए पासवर्ड दर्ज करें'; + String get pdfSignaturePadDialogSaveLabel => r'SAVE'; @override - String get passwordDialogHeaderTextLabel => r'पासवर्ड से सुरक्षित'; + String get pdfTextSelectionMenuCopyLabel => r'Copy'; @override - String get passwordDialogHintTextLabel => r'पास वर्ड दर्ज करें'; + String get pdfTextSelectionMenuHighlightLabel => r'Highlight'; @override - String get passwordDialogInvalidPasswordLabel => r'अवैध पासवर्ड'; + String get pdfTextSelectionMenuSquigglyLabel => r'Squiggly'; @override - String get pdfBookmarksLabel => r'बुकमार्क'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Strikethrough'; @override - String get pdfEnterPageNumberLabel => r'पेज नंबर दर्ज करें'; + String get pdfTextSelectionMenuUnderlineLabel => r'Underline'; @override - String get pdfGoToPageLabel => r'पृष्ठ पर जाओ'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get pdfInvalidPageNumberLabel => r'कृपया एक सही संख्या डालिये'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get pdfNoBookmarksLabel => r'कोई बुकमार्क नहीं मिला'; + String get rajabLabel => r'Rajab'; @override - String get pdfPaginationDialogCancelLabel => r'रद्द करना'; + String get ramadanLabel => r'Ramadan'; @override - String get pdfPaginationDialogOkLabel => r'ठीक'; + String get rowsPerPageDataPagerLabel => r'Rows per page'; @override - String get pdfPasswordDialogCancelLabel => r'रद्द करना'; + String get safarLabel => r'Safar'; @override - String get pdfPasswordDialogOpenLabel => r'खुला हुआ'; + String get searchDataGridFilteringLabel => r'Search'; @override - String get pdfScrollStatusOfLabel => r'का'; + String get selectAllDataGridFilteringLabel => r'Select All'; @override - String get rabi1Label => r'रबी अल-अव्वल'; + String get series => r'Series'; @override - String get rabi2Label => r'रबी अल-थानी'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get rajabLabel => r'राजाबी'; + String get shawwalLabel => r'Shawwal'; @override - String get ramadanLabel => r'रमजान'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get rowsPerPageDataPagerLabel => r'प्रति पृष्ठ पंक्तियाँ'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get safarLabel => r'सफ़र'; + String get shortJumada1Label => r'Jum. I'; @override - String get series => r'श्रृंखला'; + String get shortJumada2Label => r'Jum. II'; @override - String get shaabanLabel => r'शाबानो'; + String get shortMuharramLabel => r'Muh.'; @override - String get shawwalLabel => r'शावाल'; + String get shortRabi1Label => r'Rabi. I'; @override - String get shortDhualhiLabel => r'धुल-हो'; + String get shortRabi2Label => r'Rabi. II'; @override - String get shortDhualqiLabel => r'धुल-क्यू'; + String get shortRajabLabel => r'Raj.'; @override - String get shortJumada1Label => r'जम. मैं'; + String get shortRamadanLabel => r'Ram.'; @override - String get shortJumada2Label => r'जम. द्वितीय'; + String get shortSafarLabel => r'Saf.'; @override - String get shortMuharramLabel => r'मुह.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortRabi1Label => r'रबी। मैं'; + String get shortShawwalLabel => r'Shaw.'; @override - String get shortRabi2Label => r'रबी। द्वितीय'; + String get showRowsWhereDataGridFilteringLabel => r'Show rows where'; @override - String get shortRajabLabel => r'राज.'; + String get sortAToZDataGridFilteringLabel => r'Sort A To Z'; @override - String get shortRamadanLabel => r'टक्कर मारना।'; + String get sortAndFilterDataGridFilteringLabel => r'Sort and Filter'; @override - String get shortSafarLabel => r'साफ.'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sort Largest To Smallest'; @override - String get shortShaabanLabel => r'शा.'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sort Newest To Oldest'; @override - String get shortShawwalLabel => r'शॉ।'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sort Oldest To Newest'; @override - String get todayLabel => r'आज'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sort Smallest To Largest'; @override - String get weeknumberLabel => r'सप्ताह'; -} - -/// The translations for Croatian (`hr`). -class SfLocalizationsHr extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsHr class - const SfLocalizationsHr({ - String localeName = 'hr', - }) : super( - localeName: localeName, - ); + String get sortZToADataGridFilteringLabel => r'Sort Z To A'; @override - String get allDayLabel => r'Cijeli dan'; + String get textFiltersDataGridFilteringLabel => r'Text Filters'; @override - String get allowedViewDayLabel => r'Dan'; + String get todayLabel => r'Today'; @override - String get allowedViewMonthLabel => r'Mjesec'; + String get weeknumberLabel => r'Week'; +} - @override - String get allowedViewScheduleLabel => r'Raspored'; +/// The translations for Spanish Castilian (`es`). +class SfLocalizationsEs extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsEs class + const SfLocalizationsEs({String localeName = 'es'}) + : super(localeName: localeName); @override - String get allowedViewTimelineDayLabel => r'Dan vremenske trake'; + String get afterDataGridFilteringLabel => r'Después'; @override - String get allowedViewTimelineMonthLabel => r'Mjesec vremenske trake'; + String get afterOrEqualDataGridFilteringLabel => r'después o igual'; @override - String get allowedViewTimelineWeekLabel => r'Tjedan vremenske trake'; + String get allDayLabel => r'Todo el dia'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Vremenski okvir Radni tjedan'; + String get allowedViewDayLabel => r'Día'; @override - String get allowedViewWeekLabel => r'Tjedan'; + String get allowedViewMonthLabel => r'Mes'; @override - String get allowedViewWorkWeekLabel => r'Radni tjedan'; + String get allowedViewScheduleLabel => r'Calendario'; @override - String get daySpanCountLabel => r'Dan'; + String get allowedViewTimelineDayLabel => r'Día de la línea de tiempo'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get allowedViewTimelineMonthLabel => r'Mes de la línea de tiempo'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get allowedViewTimelineWeekLabel => r'Semana de la línea de tiempo'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get allowedViewTimelineWorkWeekLabel => + r'Línea de tiempo Semana laboral'; @override - String get jumada2Label => r'Jumada al-thani'; + String get allowedViewWeekLabel => r'Semana'; @override - String get muharramLabel => r'Muharem'; + String get allowedViewWorkWeekLabel => r'Semana de trabajo'; @override - String get noEventsCalendarLabel => r'Nema događaja'; + String get andDataGridFilteringLabel => r'Y'; @override - String get noSelectedDateCalendarLabel => r'Nema odabranog datuma'; + String get beforeDataGridFilteringLabel => r'Antes'; @override - String get ofDataPagerLabel => r'od'; + String get beforeOrEqualDataGridFilteringLabel => r'antes o igual'; @override - String get pagesDataPagerLabel => r'stranicama'; + String get beginsWithDataGridFilteringLabel => r'Empieza con'; @override - String get passwordDialogContentLabel => - r'Unesite lozinku za otvaranje ove PDF datoteke'; + String get cancelDataGridFilteringLabel => r'Cancelar'; @override - String get passwordDialogHeaderTextLabel => r'Lozinka zaštićena'; + String get clearFilterDataGridFilteringLabel => r'Filtro claro'; @override - String get passwordDialogHintTextLabel => r'Upišite lozinku'; + String get containsDataGridFilteringLabel => r'Contiene'; @override - String get passwordDialogInvalidPasswordLabel => r'Netočna zaporka'; + String get dateFiltersDataGridFilteringLabel => r'Filtros de fecha'; @override - String get pdfBookmarksLabel => r'Oznake'; + String get daySpanCountLabel => r'Día'; @override - String get pdfEnterPageNumberLabel => r'Unesite broj stranice'; + String get dhualhiLabel => r'Dhu al-Hiyyah'; @override - String get pdfGoToPageLabel => r'Idi na stranicu'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get pdfInvalidPageNumberLabel => r'Unesite valjani broj'; + String get doesNotBeginWithDataGridFilteringLabel => r'no comienza con'; @override - String get pdfNoBookmarksLabel => r'Nije pronađena nijedna oznaka'; + String get doesNotContainDataGridFilteringLabel => r'No contiene'; @override - String get pdfPaginationDialogCancelLabel => r'OTKAZATI'; + String get doesNotEndWithDataGridFilteringLabel => r'no termina con'; @override - String get pdfPaginationDialogOkLabel => r'u redu'; + String get doesNotEqualDataGridFilteringLabel => r'No es igual'; @override - String get pdfPasswordDialogCancelLabel => r'OTKAZATI'; + String get emptyDataGridFilteringLabel => r'Vacío'; @override - String get pdfPasswordDialogOpenLabel => r'OTVORENA'; + String get endsWithDataGridFilteringLabel => r'Termina con'; @override - String get pdfScrollStatusOfLabel => r'od'; + String get equalsDataGridFilteringLabel => r'igual'; @override - String get rabi1Label => r'Rabi' "'" r' el-evval'; + String get fromDataGridFilteringLabel => r'De'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get greaterThanDataGridFilteringLabel => r'Mas grande que'; @override - String get rajabLabel => r'Radžab'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Mayor que o igual'; @override - String get ramadanLabel => r'ramazana'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get rowsPerPageDataPagerLabel => r'Redovi po stranici'; + String get jumada2Label => r'Jumada al-thani'; @override - String get safarLabel => r'Safar'; + String get lessThanDataGridFilteringLabel => r'Menos que'; @override - String get series => r'Niz'; + String get lessThanOrEqualDataGridFilteringLabel => r'Menor o igual'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get muharramLabel => r'muharram'; @override - String get shawwalLabel => r'Shawwal'; + String get noEventsCalendarLabel => r'No hay eventos'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get noMatchesDataGridFilteringLabel => r'No hay coincidencias'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get noSelectedDateCalendarLabel => r'Sin fecha seleccionada'; @override - String get shortJumada1Label => r'Jum. ja'; + String get notEmptyDataGridFilteringLabel => r'No vacío'; @override - String get shortJumada2Label => r'Jum. II'; + String get notNullDataGridFilteringLabel => r'No nulo'; @override - String get shortMuharramLabel => r'Muh.'; + String get nullDataGridFilteringLabel => r'Nulo'; @override - String get shortRabi1Label => r'Rabi. ja'; + String get numberFiltersDataGridFilteringLabel => r'Filtros numéricos'; @override - String get shortRabi2Label => r'Rabi. II'; + String get ofDataPagerLabel => r'de'; @override - String get shortRajabLabel => r'Raj.'; + String get okDataGridFilteringLabel => r'OK'; @override - String get shortRamadanLabel => r'Radna memorija.'; + String get orDataGridFilteringLabel => r'O'; @override - String get shortSafarLabel => r'Saf.'; + String get pagesDataPagerLabel => r'paginas'; @override - String get shortShaabanLabel => r'Sha.'; + String get passwordDialogContentLabel => + r'Introduzca la contraseña para abrir este archivo PDF'; @override - String get shortShawwalLabel => r'Shaw.'; + String get passwordDialogHeaderTextLabel => r'Contraseña protegida'; @override - String get todayLabel => r'Danas'; + String get passwordDialogHintTextLabel => r'Introducir la contraseña'; @override - String get weeknumberLabel => r'Tjedan'; -} - -/// The translations for Hungarian (`hu`). -class SfLocalizationsHu extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsHu class - const SfLocalizationsHu({ - String localeName = 'hu', - }) : super( - localeName: localeName, - ); + String get passwordDialogInvalidPasswordLabel => r'Contraseña invalida'; @override - String get allDayLabel => r'Egész nap'; + String get pdfBookmarksLabel => r'Marcadores'; @override - String get allowedViewDayLabel => r'Nap'; + String get pdfEnterPageNumberLabel => r'Introduzca el número de página'; @override - String get allowedViewMonthLabel => r'Hónap'; + String get pdfGoToPageLabel => r'ir a la página'; @override - String get allowedViewScheduleLabel => r'Menetrend'; + String get pdfHyperlinkContentLabel => r'¿Quieres abrir la página en'; @override - String get allowedViewTimelineDayLabel => r'Idővonal nap'; + String get pdfHyperlinkDialogCancelLabel => r'CANCELAR'; @override - String get allowedViewTimelineMonthLabel => r'Idővonal hónap'; + String get pdfHyperlinkDialogOpenLabel => r'ABRIR'; @override - String get allowedViewTimelineWeekLabel => r'Idővonal hét'; + String get pdfHyperlinkLabel => r'Abrir página web'; @override - String get allowedViewTimelineWorkWeekLabel => r'Idővonal munkahét'; + String get pdfInvalidPageNumberLabel => + r'Por favor, ingrese un número válido'; @override - String get allowedViewWeekLabel => r'Hét'; + String get pdfNoBookmarksLabel => r'No se encontraron marcadores'; @override - String get allowedViewWorkWeekLabel => r'Munkahét'; + String get pdfPaginationDialogCancelLabel => r'CANCELAR'; @override - String get daySpanCountLabel => r'Nap'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfPasswordDialogCancelLabel => r'CANCELAR'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfPasswordDialogOpenLabel => r'ABIERTO'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfScrollStatusOfLabel => r'de'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfSignaturePadDialogClearLabel => r'Limpiar'; @override - String get muharramLabel => r'Muharram'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Dibuja tu firma'; @override - String get noEventsCalendarLabel => r'Nincsenek események'; + String get pdfSignaturePadDialogPenColorLabel => r'Color de la pluma'; @override - String get noSelectedDateCalendarLabel => r'Nincs kiválasztott dátum'; + String get pdfSignaturePadDialogSaveLabel => r'Guardar'; @override - String get ofDataPagerLabel => r'nak,-nek'; + String get rabi1Label => r'Rabí al-awwal'; @override - String get pagesDataPagerLabel => r'oldalakat'; + String get rabi2Label => r'Rabí al-thani'; @override - String get passwordDialogContentLabel => - r'Írja be a jelszót a PDF-fájl megnyitásához'; + String get rajabLabel => r'Rayab'; @override - String get passwordDialogHeaderTextLabel => r'Jelszóval védett'; + String get pdfTextSelectionMenuCopyLabel => r'Copiar'; @override - String get passwordDialogHintTextLabel => r'Írd be a jelszót'; + String get pdfTextSelectionMenuHighlightLabel => r'Resaltar'; @override - String get passwordDialogInvalidPasswordLabel => r'Érvénytelen jelszó'; + String get pdfTextSelectionMenuSquigglyLabel => r'Ondulado'; @override - String get pdfBookmarksLabel => r'Könyvjelzők'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Tachado'; @override - String get pdfEnterPageNumberLabel => r'Írja be az oldalszámot'; + String get pdfTextSelectionMenuUnderlineLabel => r'Subrayar'; @override - String get pdfGoToPageLabel => r'Menj az oldalra'; + String get ramadanLabel => r'Ramadán'; @override - String get pdfInvalidPageNumberLabel => r'Adjon meg egy érvényes számot'; + String get rowsPerPageDataPagerLabel => r'Filas por página'; @override - String get pdfNoBookmarksLabel => r'Nem találhatók könyvjelzők'; + String get safarLabel => r'Safar'; @override - String get pdfPaginationDialogCancelLabel => r'MEGSZÜNTETI'; + String get searchDataGridFilteringLabel => r'Búsqueda'; @override - String get pdfPaginationDialogOkLabel => r'rendben'; + String get selectAllDataGridFilteringLabel => r'Seleccionar todo'; @override - String get pdfPasswordDialogCancelLabel => r'MEGSZÜNTETI'; + String get series => r'Serie'; @override - String get pdfPasswordDialogOpenLabel => r'NYISD KI'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get pdfScrollStatusOfLabel => r'nak,-nek'; + String get shawwalLabel => r'Shawwal'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get rajabLabel => r'Rajab'; + String get shortJumada1Label => r'Jum. yo'; @override - String get ramadanLabel => r'Ramadan'; + String get shortJumada2Label => r'Jum. II'; @override - String get rowsPerPageDataPagerLabel => r'Sorok oldalanként'; + String get shortMuharramLabel => r'Muh.'; @override - String get safarLabel => r'Safar'; + String get shortRabi1Label => r'Rabí. yo'; @override - String get series => r'Sorozat'; + String get shortRabi2Label => r'Rabí. Yo'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shortRajabLabel => r'Raj.'; @override - String get shawwalLabel => r'Shawwal'; + String get shortRamadanLabel => r'RAM.'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortSafarLabel => r'seguro'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortJumada1Label => r'Jum. én'; + String get shortShawwalLabel => r'Shaw.'; @override - String get shortJumada2Label => r'Jum. II'; + String get showRowsWhereDataGridFilteringLabel => r'Mostrar filas donde'; @override - String get shortMuharramLabel => r'Muh.'; + String get sortAToZDataGridFilteringLabel => r'Ordenar de la A a la Z'; @override - String get shortRabi1Label => r'Rabi. én'; + String get sortAndFilterDataGridFilteringLabel => r'Ordenar y filtrar'; @override - String get shortRabi2Label => r'Rabi. II'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ordenar de mayor a menor'; @override - String get shortRajabLabel => r'Raj.'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Ordenar de más reciente a más antiguo'; @override - String get shortRamadanLabel => r'Ram.'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Ordenar de más antiguo a más nuevo'; @override - String get shortSafarLabel => r'Saf.'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ordenar de menor a mayor'; @override - String get shortShaabanLabel => r'Sha.'; + String get sortZToADataGridFilteringLabel => r'Ordenar de Z a A'; @override - String get shortShawwalLabel => r'Shaw.'; + String get textFiltersDataGridFilteringLabel => r'Filtros de texto'; @override - String get todayLabel => r'Ma'; + String get todayLabel => r'Hoy'; @override - String get weeknumberLabel => r'Hét'; + String get weeknumberLabel => r'Semana'; } -/// The translations for Armenian (`hy`). -class SfLocalizationsHy extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsHy class - const SfLocalizationsHy({ - String localeName = 'hy', - }) : super( - localeName: localeName, - ); +/// The translations for Estonian (`et`). +class SfLocalizationsEt extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsEt class + const SfLocalizationsEt({String localeName = 'et'}) + : super(localeName: localeName); @override - String get allDayLabel => r'Ամբողջ օրը'; + String get afterDataGridFilteringLabel => r'Pärast'; @override - String get allowedViewDayLabel => r'Օր'; + String get afterOrEqualDataGridFilteringLabel => r'Pärast või Võrdne'; @override - String get allowedViewMonthLabel => r'Ամիս'; + String get allDayLabel => r'Terve päev'; @override - String get allowedViewScheduleLabel => r'Ժամանակացույց'; + String get allowedViewDayLabel => r'Päev'; @override - String get allowedViewTimelineDayLabel => r'Ժամանակացույցի օր'; + String get allowedViewMonthLabel => r'Kuu'; @override - String get allowedViewTimelineMonthLabel => r'Ժամանակացույց ամիս'; + String get allowedViewScheduleLabel => r'Ajakava'; @override - String get allowedViewTimelineWeekLabel => r'Ժամանակացույցի շաբաթ'; + String get allowedViewTimelineDayLabel => r'Ajaskaala päev'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Ժամանակացույց Աշխատանքային շաբաթ'; + String get allowedViewTimelineMonthLabel => r'Ajaskaala Kuu'; @override - String get allowedViewWeekLabel => r'Շաբաթ'; + String get allowedViewTimelineWeekLabel => r'Ajaskaala nädal'; @override - String get allowedViewWorkWeekLabel => r'Աշխատանքային շաբաթ'; + String get allowedViewTimelineWorkWeekLabel => r'Ajaskaala Töönädal'; @override - String get daySpanCountLabel => r'Օր'; + String get allowedViewWeekLabel => r'Nädal'; @override - String get dhualhiLabel => r'Դհու ալ-Հիջա'; + String get allowedViewWorkWeekLabel => r'Töönädal'; @override - String get dhualqiLabel => r'Դհու ալ-Քիդա'; + String get andDataGridFilteringLabel => r'Ja'; @override - String get jumada1Label => r'Ջումադա ալ-ավալ'; + String get beforeDataGridFilteringLabel => r'Enne'; @override - String get jumada2Label => r'Ջումադա ալ-Թանի'; + String get beforeOrEqualDataGridFilteringLabel => r'Enne või Võrdne'; @override - String get muharramLabel => r'Մուհարամ'; + String get beginsWithDataGridFilteringLabel => r'Algab'; @override - String get noEventsCalendarLabel => r'Միջոցառումներ չկան'; + String get cancelDataGridFilteringLabel => r'Tühista'; @override - String get noSelectedDateCalendarLabel => r'Ընտրված ամսաթիվ չկա'; + String get clearFilterDataGridFilteringLabel => r'Tühjenda filter'; @override - String get ofDataPagerLabel => r'-ից'; + String get containsDataGridFilteringLabel => r'Sisaldab'; @override - String get pagesDataPagerLabel => r'էջեր'; + String get dateFiltersDataGridFilteringLabel => r'Kuupäeva filtrid'; @override - String get passwordDialogContentLabel => - r'Մուտքագրեք գաղտնաբառը՝ այս PDF ֆայլը բացելու համար'; + String get daySpanCountLabel => r'päev'; @override - String get passwordDialogHeaderTextLabel => r'Գաղտնաբառը պաշտպանված է'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get passwordDialogHintTextLabel => r'Մուտքագրեք գաղտնաբառը'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get passwordDialogInvalidPasswordLabel => r'Անվավեր գաղտնաբառ'; + String get doesNotBeginWithDataGridFilteringLabel => r'Ei alga'; @override - String get pdfBookmarksLabel => r'Էջանիշեր'; + String get doesNotContainDataGridFilteringLabel => r'Ei sisalda'; @override - String get pdfEnterPageNumberLabel => r'Մուտքագրեք էջի համարը'; + String get doesNotEndWithDataGridFilteringLabel => r'Ei lõpe'; @override - String get pdfGoToPageLabel => r'Գնալ դեպի էջ'; + String get doesNotEqualDataGridFilteringLabel => r'Ei võrdu'; @override - String get pdfInvalidPageNumberLabel => r'Խնդրում ենք մուտքագրել վավեր համար'; + String get emptyDataGridFilteringLabel => r'Tühi'; @override - String get pdfNoBookmarksLabel => r'Էջանիշեր չեն գտնվել'; + String get endsWithDataGridFilteringLabel => r'Lõpeb'; @override - String get pdfPaginationDialogCancelLabel => r'ՉԵՂԱՐԿԵԼ'; + String get equalsDataGridFilteringLabel => r'Võrdub'; @override - String get pdfPaginationDialogOkLabel => r'լավ'; + String get fromDataGridFilteringLabel => r'Alates'; @override - String get pdfPasswordDialogCancelLabel => r'ՉԵՂԱՐԿԵԼ'; + String get greaterThanDataGridFilteringLabel => r'Suurem kui'; @override - String get pdfPasswordDialogOpenLabel => r'ԲԱՑ'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Suurem Kui või Võrdne'; @override - String get pdfScrollStatusOfLabel => r'-ից'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get rabi1Label => r'Ռաբի ալ-ավալ'; + String get jumada2Label => r'Jumada al-thani'; @override - String get rabi2Label => r'Ռաբի ալ-Թանի'; + String get lessThanDataGridFilteringLabel => r'Vähem kui'; @override - String get rajabLabel => r'Ռաջաբ'; + String get lessThanOrEqualDataGridFilteringLabel => r'Väiksem või võrdne'; @override - String get ramadanLabel => r'Ռամադան'; + String get muharramLabel => r'Muharram'; @override - String get rowsPerPageDataPagerLabel => r'Տողեր մեկ էջի համար'; + String get noEventsCalendarLabel => r'Sündmusi pole'; @override - String get safarLabel => r'Սաֆար'; + String get noMatchesDataGridFilteringLabel => r'Vasteid pole'; @override - String get series => r'Սերիա'; + String get noSelectedDateCalendarLabel => r'Kuupäeva pole valitud'; @override - String get shaabanLabel => r'Շաաբան'; + String get notEmptyDataGridFilteringLabel => r'Pole tühi'; @override - String get shawwalLabel => r'Շավվալ'; + String get notNullDataGridFilteringLabel => r'Mitte Null'; @override - String get shortDhualhiLabel => r'Դհուլ-Հ'; + String get nullDataGridFilteringLabel => r'Null'; @override - String get shortDhualqiLabel => r'Դհուլ-Ք'; + String get numberFiltersDataGridFilteringLabel => r'Numbrifiltrid'; @override - String get shortJumada1Label => r'Jum. Ի'; + String get ofDataPagerLabel => r'kohta'; @override - String get shortJumada2Label => r'Jum. II'; + String get okDataGridFilteringLabel => r'Okei'; @override - String get shortMuharramLabel => r'Մուհ.'; + String get orDataGridFilteringLabel => r'Või'; @override - String get shortRabi1Label => r'Ռաբի. Ի'; + String get pagesDataPagerLabel => r'lehekülgi'; @override - String get shortRabi2Label => r'Ռաբի. II'; + String get passwordDialogContentLabel => + r'Selle PDF-faili avamiseks sisestage parool'; @override - String get shortRajabLabel => r'Ռաջ.'; + String get passwordDialogHeaderTextLabel => r'Parooliga kaitstud'; @override - String get shortRamadanLabel => r'Խոյ.'; + String get passwordDialogHintTextLabel => r'Sisestage parool'; @override - String get shortSafarLabel => r'Սաֆ.'; + String get passwordDialogInvalidPasswordLabel => r'vale parool'; @override - String get shortShaabanLabel => r'Շա.'; + String get pdfBookmarksLabel => r'Järjehoidjad'; @override - String get shortShawwalLabel => r'Շոու.'; + String get pdfEnterPageNumberLabel => r'Sisestage lehekülje number'; @override - String get todayLabel => r'Այսօր'; + String get pdfGoToPageLabel => r'Mine lehele'; @override - String get weeknumberLabel => r'Շաբաթ'; -} - -/// The translations for Indonesian (`id`). -class SfLocalizationsId extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsId class - const SfLocalizationsId({ - String localeName = 'id', - }) : super( - localeName: localeName, - ); + String get pdfHyperlinkContentLabel => r'Kas soovite lehe avada aadressil'; @override - String get allDayLabel => r'Sepanjang hari'; + String get pdfHyperlinkDialogCancelLabel => r'TÜHISTA'; @override - String get allowedViewDayLabel => r'Hari'; + String get pdfHyperlinkDialogOpenLabel => r'AVA'; @override - String get allowedViewMonthLabel => r'Bulan'; + String get pdfHyperlinkLabel => r'Ava veebileht'; @override - String get allowedViewScheduleLabel => r'Jadwal'; + String get pdfInvalidPageNumberLabel => r'Sisestage kehtiv number'; @override - String get allowedViewTimelineDayLabel => r'Hari Garis Waktu'; + String get pdfNoBookmarksLabel => r'Järjehoidjaid ei leitud'; @override - String get allowedViewTimelineMonthLabel => r'Garis Waktu Bulan'; + String get pdfPaginationDialogCancelLabel => r'TÜHISTA'; @override - String get allowedViewTimelineWeekLabel => r'Garis Waktu Minggu'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get allowedViewTimelineWorkWeekLabel => r'Garis Waktu Minggu Kerja'; + String get pdfPasswordDialogCancelLabel => r'TÜHISTA'; @override - String get allowedViewWeekLabel => r'Pekan'; + String get pdfPasswordDialogOpenLabel => r'AVA'; @override - String get allowedViewWorkWeekLabel => r'minggu kerja'; + String get pdfScrollStatusOfLabel => r'kohta'; @override - String get daySpanCountLabel => r'Hari'; + String get pdfSignaturePadDialogClearLabel => r'Puhasta'; @override - String get dhualhiLabel => r'Dzulhijjah'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Joonistage oma allkiri'; @override - String get dhualqiLabel => r'Dzulqidah'; + String get pdfSignaturePadDialogPenColorLabel => r'Pliiatsi värv'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfSignaturePadDialogSaveLabel => r'Salvesta'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfTextSelectionMenuCopyLabel => r'Kopeeri'; @override - String get muharramLabel => r'Muharram'; + String get pdfTextSelectionMenuHighlightLabel => r'Esile tõsta'; @override - String get noEventsCalendarLabel => r'Tidak ada acara'; + String get pdfTextSelectionMenuSquigglyLabel => r'Laineline'; @override - String get noSelectedDateCalendarLabel => r'Tidak ada tanggal yang dipilih'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Läbikriipsutatud'; @override - String get ofDataPagerLabel => r'dari'; + String get pdfTextSelectionMenuUnderlineLabel => r'Allajoonimine'; @override - String get pagesDataPagerLabel => r'halaman'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get passwordDialogContentLabel => - r'Masukkan kata sandi untuk membuka file PDF ini'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get passwordDialogHeaderTextLabel => r'Dilindungi Kata Sandi'; + String get rajabLabel => r'Rajab'; @override - String get passwordDialogHintTextLabel => r'Masukkan kata kunci'; + String get ramadanLabel => r'Ramadan'; @override - String get passwordDialogInvalidPasswordLabel => r'kata sandi salah'; + String get rowsPerPageDataPagerLabel => r'Rida lehekülje kohta'; @override - String get pdfBookmarksLabel => r'Bookmark'; + String get safarLabel => r'Safar'; @override - String get pdfEnterPageNumberLabel => r'Masukkan nomor halaman'; + String get searchDataGridFilteringLabel => r'Otsing'; @override - String get pdfGoToPageLabel => r'Buka halaman'; + String get selectAllDataGridFilteringLabel => r'Vali kõik'; @override - String get pdfInvalidPageNumberLabel => r'Harap masukkan nomor yang valid'; + String get series => r'seeria'; @override - String get pdfNoBookmarksLabel => r'Tidak ada bookmark yang ditemukan'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get pdfPaginationDialogCancelLabel => r'MEMBATALKAN'; + String get shawwalLabel => r'Shawwal'; @override - String get pdfPaginationDialogOkLabel => r'oke'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get pdfPasswordDialogCancelLabel => r'MEMBATALKAN'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get pdfPasswordDialogOpenLabel => r'MEMBUKA'; + String get shortJumada1Label => r'Jum. I'; @override - String get pdfScrollStatusOfLabel => r'dari'; + String get shortJumada2Label => r'Jum. II'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get shortMuharramLabel => r'Muh.'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get shortRabi1Label => r'Rabi. I'; @override - String get rajabLabel => r'Rajab'; + String get shortRabi2Label => r'Rabi. II'; @override - String get ramadanLabel => r'Ramadan'; + String get shortRajabLabel => r'Raj.'; @override - String get rowsPerPageDataPagerLabel => r'Baris per halaman'; + String get shortRamadanLabel => r'Ram.'; @override - String get safarLabel => r'Safari'; + String get shortSafarLabel => r'Saf.'; @override - String get series => r'Seri'; + String get shortShaabanLabel => r'Sha.'; @override - String get shaabanLabel => r'Sya' "'" r'ban'; + String get shortShawwalLabel => r'Shaw.'; @override - String get shawwalLabel => r'Syawal'; + String get showRowsWhereDataGridFilteringLabel => r'Näita ridu, kus'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get sortAToZDataGridFilteringLabel => r'Sorteeri A–Z'; @override - String get shortDhualqiLabel => r'Dzul Q'; + String get sortAndFilterDataGridFilteringLabel => r'Sorteeri ja filtreeri'; @override - String get shortJumada1Label => r'Jum. saya'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sorteeri suurimast väikseimani'; @override - String get shortJumada2Label => r'Jum. II'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sorteeri uusimatest vanimateks'; @override - String get shortMuharramLabel => r'Muh.'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sorteeri vanimast uusimaks'; @override - String get shortRabi1Label => r'Rabi. saya'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sorteeri väikseimast suurimaks'; @override - String get shortRabi2Label => r'Rabi. II'; + String get sortZToADataGridFilteringLabel => r'Sorteeri Z kuni A'; @override - String get shortRajabLabel => r'Raj.'; + String get textFiltersDataGridFilteringLabel => r'Tekstifiltrid'; @override - String get shortRamadanLabel => r'Rama.'; + String get todayLabel => r'Täna'; @override - String get shortSafarLabel => r'saf.'; + String get weeknumberLabel => r'Nädal'; +} - @override - String get shortShaabanLabel => r'Sha.'; +/// The translations for Basque (`eu`). +class SfLocalizationsEu extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsEu class + const SfLocalizationsEu({String localeName = 'eu'}) + : super(localeName: localeName); @override - String get shortShawwalLabel => r'Shaw'; + String get afterDataGridFilteringLabel => r'Ondoren'; @override - String get todayLabel => r'Hari ini'; + String get afterOrEqualDataGridFilteringLabel => r'Ondoren Edo Berdin'; @override - String get weeknumberLabel => r'Pekan'; -} - -/// The translations for Icelandic (`is`). -class SfLocalizationsIs extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsIs class - const SfLocalizationsIs({ - String localeName = 'is', - }) : super( - localeName: localeName, - ); + String get allDayLabel => r'Egun osoan'; @override - String get allDayLabel => r'Allan daginn'; + String get allowedViewDayLabel => r'Eguna'; @override - String get allowedViewDayLabel => r'Dagur'; + String get allowedViewMonthLabel => r'Hilabetea'; @override - String get allowedViewMonthLabel => r'Mánuður'; + String get allowedViewScheduleLabel => r'Ordutegia'; @override - String get allowedViewScheduleLabel => r'Dagskrá'; + String get allowedViewTimelineDayLabel => r'Kronograma Eguna'; @override - String get allowedViewTimelineDayLabel => r'Tímalínudagur'; + String get allowedViewTimelineMonthLabel => r'Kronograma Hilabetea'; @override - String get allowedViewTimelineMonthLabel => r'Tímalína mánuður'; + String get allowedViewTimelineWeekLabel => r'Denboraren Astea'; @override - String get allowedViewTimelineWeekLabel => r'Tímalínuvika'; + String get allowedViewTimelineWorkWeekLabel => r'Timeline Lan Astea'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tímalína vinnuvika'; + String get allowedViewWeekLabel => r'Astea'; @override - String get allowedViewWeekLabel => r'Vika'; + String get allowedViewWorkWeekLabel => r'Lan Astea'; @override - String get allowedViewWorkWeekLabel => r'Vinnuvika'; + String get andDataGridFilteringLabel => r'Eta'; @override - String get daySpanCountLabel => r'Dagur'; + String get beforeDataGridFilteringLabel => r'Aurretik'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get beforeOrEqualDataGridFilteringLabel => r'Aurretik Edo Berdin'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get beginsWithDataGridFilteringLabel => r'Hasten da'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get cancelDataGridFilteringLabel => r'Utzi'; @override - String get jumada2Label => r'Jumada al-thani'; + String get clearFilterDataGridFilteringLabel => r'Garbitu iragazkia'; @override - String get muharramLabel => r'Muharram'; + String get containsDataGridFilteringLabel => r'Dauka'; @override - String get noEventsCalendarLabel => r'Engir viðburðir'; + String get dateFiltersDataGridFilteringLabel => r'Data-iragazkiak'; @override - String get noSelectedDateCalendarLabel => r'Engin valin dagsetning'; + String get daySpanCountLabel => r'Eguna'; @override - String get ofDataPagerLabel => r'af'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get pagesDataPagerLabel => r'síður'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get passwordDialogContentLabel => - r'Sláðu inn lykilorðið til að opna þessa PDF-skrá'; + String get doesNotBeginWithDataGridFilteringLabel => r'Ez Da Hasten'; @override - String get passwordDialogHeaderTextLabel => r'Lykilorð varið'; + String get doesNotContainDataGridFilteringLabel => r'Ez dauka'; @override - String get passwordDialogHintTextLabel => r'Sláðu inn lykilorð'; + String get doesNotEndWithDataGridFilteringLabel => r'Ez Du Bukatzen'; @override - String get passwordDialogInvalidPasswordLabel => r'ógilt lykilorð'; + String get doesNotEqualDataGridFilteringLabel => r'Ez Du Berdin'; @override - String get pdfBookmarksLabel => r'Bókamerki'; + String get emptyDataGridFilteringLabel => r'Hutsik'; @override - String get pdfEnterPageNumberLabel => r'Sláðu inn blaðsíðunúmer'; + String get endsWithDataGridFilteringLabel => r'Honekin amaitzen da'; @override - String get pdfGoToPageLabel => r'Farðu á síðu'; + String get equalsDataGridFilteringLabel => r'Berdin'; @override - String get pdfInvalidPageNumberLabel => r'Vinsamlega sláðu inn gilt númer'; + String get fromDataGridFilteringLabel => r'Bertatik'; @override - String get pdfNoBookmarksLabel => r'Engin bókamerki fundust'; + String get greaterThanDataGridFilteringLabel => r'Baino handiagoa'; @override - String get pdfPaginationDialogCancelLabel => r'HÆTTA við'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Handiagoa Edo Berdin'; @override - String get pdfPaginationDialogOkLabel => r'Allt í lagi'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get pdfPasswordDialogCancelLabel => r'HÆTTA við'; + String get jumada2Label => r'Jumada al-thani'; @override - String get pdfPasswordDialogOpenLabel => r'OPNA'; + String get lessThanDataGridFilteringLabel => r'Baino gutxiago'; @override - String get pdfScrollStatusOfLabel => r'af'; + String get lessThanOrEqualDataGridFilteringLabel => r'Gutxiago Edo Berdin'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get muharramLabel => r'Muharram'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get noEventsCalendarLabel => r'Ez dago ekitaldirik'; @override - String get rajabLabel => r'Rajab'; + String get noMatchesDataGridFilteringLabel => r'Partidurik ez'; @override - String get ramadanLabel => r'Ramadan'; + String get noSelectedDateCalendarLabel => r'Ez dago data hautaturik'; @override - String get rowsPerPageDataPagerLabel => r'Raðir á síðu'; + String get notEmptyDataGridFilteringLabel => r'Ez Hutsik'; @override - String get safarLabel => r'Safar'; + String get notNullDataGridFilteringLabel => r'Ez Nulua'; @override - String get series => r'Röð'; + String get nullDataGridFilteringLabel => r'Nulua'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get numberFiltersDataGridFilteringLabel => r'Zenbaki-iragazkiak'; @override - String get shawwalLabel => r'Shawwal'; + String get ofDataPagerLabel => r'de'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get okDataGridFilteringLabel => r'Ados'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get orDataGridFilteringLabel => r'Edo'; @override - String get shortJumada1Label => r'Jum. ég'; + String get pagesDataPagerLabel => r'orrialdeak'; @override - String get shortJumada2Label => r'Jum. II'; + String get passwordDialogContentLabel => + r'Sartu pasahitza PDF fitxategi hau irekitzeko'; @override - String get shortMuharramLabel => r'Múh.'; + String get passwordDialogHeaderTextLabel => r'Pasahitz babestuta'; @override - String get shortRabi1Label => r'Rabi. ég'; + String get passwordDialogHintTextLabel => r'Sartu pasahitza'; @override - String get shortRabi2Label => r'Rabi. II'; + String get passwordDialogInvalidPasswordLabel => r'pasahitz okerra'; @override - String get shortRajabLabel => r'Raj.'; + String get pdfBookmarksLabel => r'Laster-markak'; @override - String get shortRamadanLabel => r'Vinnsluminni.'; + String get pdfEnterPageNumberLabel => r'Sartu orriaren zenbakia'; @override - String get shortSafarLabel => r'Saf.'; + String get pdfGoToPageLabel => r'Joan orrialdera'; @override - String get shortShaabanLabel => r'Sha.'; + String get pdfHyperlinkContentLabel => r'Orria ireki nahi al duzu hemen'; @override - String get shortShawwalLabel => r'Shaw.'; + String get pdfHyperlinkDialogCancelLabel => r'Utzi'; @override - String get todayLabel => r'Í dag'; + String get pdfHyperlinkDialogOpenLabel => r'IREKI'; @override - String get weeknumberLabel => r'Vika'; -} + String get pdfHyperlinkLabel => r'Ireki Web orria'; -/// The translations for Italian (`it`). -class SfLocalizationsIt extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsIt class - const SfLocalizationsIt({ - String localeName = 'it', - }) : super( - localeName: localeName, - ); + @override + String get pdfInvalidPageNumberLabel => + r'Mesedez, sartu baliozko zenbaki bat'; @override - String get allDayLabel => r'Tutto il giorno'; + String get pdfNoBookmarksLabel => r'Ez da laster-markarik aurkitu'; @override - String get allowedViewDayLabel => r'Giorno'; + String get pdfPaginationDialogCancelLabel => r'Utzi'; @override - String get allowedViewMonthLabel => r'Mese'; + String get pdfPaginationDialogOkLabel => r'Ados'; @override - String get allowedViewScheduleLabel => r'Programma'; + String get pdfPasswordDialogCancelLabel => r'Utzi'; @override - String get allowedViewTimelineDayLabel => - r'Giornata della sequenza temporale'; + String get pdfPasswordDialogOpenLabel => r'IREKI'; @override - String get allowedViewTimelineMonthLabel => r'Mese della sequenza temporale'; + String get pdfScrollStatusOfLabel => r'de'; @override - String get allowedViewTimelineWeekLabel => - r'Settimana della sequenza temporale'; + String get pdfSignaturePadDialogClearLabel => r'Garbitu'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Settimana del lavoro cronologico'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Marraztu zure sinadura'; @override - String get allowedViewWeekLabel => r'Settimana'; + String get pdfSignaturePadDialogPenColorLabel => r'Boligrafoaren kolorea'; @override - String get allowedViewWorkWeekLabel => r'Settimana di lavoro'; + String get pdfSignaturePadDialogSaveLabel => r'Gorde'; @override - String get daySpanCountLabel => r'Giorno'; + String get pdfTextSelectionMenuCopyLabel => r'Kopiatu'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfTextSelectionMenuHighlightLabel => r'Nabarmendu'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfTextSelectionMenuSquigglyLabel => r'Zinta'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Markatua'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfTextSelectionMenuUnderlineLabel => r'Azpimarratu'; @override - String get muharramLabel => r'Muharram'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get noEventsCalendarLabel => r'Nessun evento'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get noSelectedDateCalendarLabel => r'Nessuna data selezionata'; + String get rajabLabel => r'Rajab'; @override - String get ofDataPagerLabel => r'di'; + String get ramadanLabel => r'Ramadana'; @override - String get pagesDataPagerLabel => r'pagine'; + String get rowsPerPageDataPagerLabel => r'Orrialde bakoitzeko errenkadak'; @override - String get passwordDialogContentLabel => - r'Inserisci la password per aprire questo file PDF'; + String get safarLabel => r'Safar'; @override - String get passwordDialogHeaderTextLabel => r'Protetto da password'; + String get searchDataGridFilteringLabel => r'Bilatu'; @override - String get passwordDialogHintTextLabel => r'Inserire la password'; + String get selectAllDataGridFilteringLabel => r'Hautatu guztiak'; @override - String get passwordDialogInvalidPasswordLabel => r'Password non valida'; + String get series => r'Seriea'; @override - String get pdfBookmarksLabel => r'Segnalibri'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get pdfEnterPageNumberLabel => r'Inserisci il numero di pagina'; + String get shawwalLabel => r'Shawwal'; @override - String get pdfGoToPageLabel => r'Vai alla pagina'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get pdfInvalidPageNumberLabel => - r'Per favore, inserire un numero valido'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get pdfNoBookmarksLabel => r'Nessun segnalibro trovato'; + String get shortJumada1Label => r'Salto. I'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULLA'; + String get shortJumada2Label => r'Salto. II'; @override - String get pdfPaginationDialogOkLabel => r'ok'; + String get shortMuharramLabel => r'Muh.'; @override - String get pdfPasswordDialogCancelLabel => r'ANNULLA'; + String get shortRabi1Label => r'Rabi. I'; @override - String get pdfPasswordDialogOpenLabel => r'APRIRE'; + String get shortRabi2Label => r'Rabi. II'; @override - String get pdfScrollStatusOfLabel => r'di'; + String get shortRajabLabel => r'Raj.'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get shortRamadanLabel => r'RAM.'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get shortSafarLabel => r'Saf.'; @override - String get rajabLabel => r'Rajab'; + String get shortShaabanLabel => r'Sha.'; @override - String get ramadanLabel => r'Ramadan'; + String get shortShawwalLabel => r'Shaw.'; @override - String get rowsPerPageDataPagerLabel => r'Righe per pagina'; + String get showRowsWhereDataGridFilteringLabel => r'Erakutsi errenkadak non'; @override - String get safarLabel => r'Safar'; + String get sortAToZDataGridFilteringLabel => r'Ordenatu Atik Z'; @override - String get series => r'Serie'; + String get sortAndFilterDataGridFilteringLabel => r'Ordenatu eta Iragazi'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ordenatu handienetik txikienetik'; @override - String get shawwalLabel => r'Shawwal'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Ordenatu berrienetik zaharrenetik'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Ordenatu zaharrenetik berrienera'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ordenatu txikienetik handienera'; @override - String get shortJumada1Label => r'Salto. io'; + String get sortZToADataGridFilteringLabel => r'Ordenatu Ztik A'; @override - String get shortJumada2Label => r'Salto. II'; + String get textFiltersDataGridFilteringLabel => r'Testu-iragazkiak'; @override - String get shortMuharramLabel => r'Mah.'; + String get todayLabel => r'Gaur'; @override - String get shortRabi1Label => r'Rabi. io'; + String get weeknumberLabel => r'Astea'; +} - @override - String get shortRabi2Label => r'Rabi. II'; +/// The translations for Persian (`fa`). +class SfLocalizationsFa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsFa class + const SfLocalizationsFa({String localeName = 'fa'}) + : super(localeName: localeName); @override - String get shortRajabLabel => r'Raj.'; + String get afterDataGridFilteringLabel => r'بعد از'; @override - String get shortRamadanLabel => r'Ariete.'; + String get afterOrEqualDataGridFilteringLabel => r'بعد یا برابر'; @override - String get shortSafarLabel => r'Saf.'; + String get allDayLabel => r'تمام روز'; @override - String get shortShaabanLabel => r'Sha.'; + String get allowedViewDayLabel => r'روز'; @override - String get shortShawwalLabel => r'Shaw.'; + String get allowedViewMonthLabel => r'ماه'; @override - String get todayLabel => r'In data odierna'; + String get allowedViewScheduleLabel => r'برنامه'; @override - String get weeknumberLabel => r'Settimana'; -} + String get allowedViewTimelineDayLabel => r'روز جدول زمانی'; -/// The translations for Japanese (`ja`). -class SfLocalizationsJa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsJa class - const SfLocalizationsJa({ - String localeName = 'ja', - }) : super( - localeName: localeName, - ); + @override + String get allowedViewTimelineMonthLabel => r'ماه خط زمانی'; @override - String get allDayLabel => r'一日中'; + String get allowedViewTimelineWeekLabel => r'هفته جدول زمانی'; @override - String get allowedViewDayLabel => r'日'; + String get allowedViewTimelineWorkWeekLabel => r'جدول زمانی هفته کاری'; @override - String get allowedViewMonthLabel => r'月'; + String get allowedViewWeekLabel => r'هفته'; @override - String get allowedViewScheduleLabel => r'スケジュール'; + String get allowedViewWorkWeekLabel => r'هفته کاری'; @override - String get allowedViewTimelineDayLabel => r'タイムラインの日'; + String get andDataGridFilteringLabel => r'و'; @override - String get allowedViewTimelineMonthLabel => r'タイムライン月'; + String get beforeDataGridFilteringLabel => r'قبل از'; @override - String get allowedViewTimelineWeekLabel => r'タイムラインウィーク'; + String get beforeOrEqualDataGridFilteringLabel => r'قبل یا برابر'; @override - String get allowedViewTimelineWorkWeekLabel => r'タイムラインワークウィーク'; + String get beginsWithDataGridFilteringLabel => r'شروع با'; @override - String get allowedViewWeekLabel => r'週'; + String get cancelDataGridFilteringLabel => r'لغو کنید'; @override - String get allowedViewWorkWeekLabel => r'労働週'; + String get clearFilterDataGridFilteringLabel => r'فیلتر را پاک کنید'; @override - String get daySpanCountLabel => r'日'; + String get containsDataGridFilteringLabel => r'حاوی'; @override - String get dhualhiLabel => r'ズーアルヒジャ'; + String get dateFiltersDataGridFilteringLabel => r'فیلترهای تاریخ'; @override - String get dhualqiLabel => r'ズーアルチーダー'; + String get daySpanCountLabel => r'روز'; @override - String get jumada1Label => r'ジュマーダ・アル・アウワル'; + String get dhualhiLabel => r'ذی الحجه'; @override - String get jumada2Label => r'ジュマーダ・アルタニ'; + String get dhualqiLabel => r'ذی القعده'; @override - String get muharramLabel => r'ムハッラム'; + String get doesNotBeginWithDataGridFilteringLabel => r'با شروع نمی شود'; @override - String get noEventsCalendarLabel => r'イベントなし'; + String get doesNotContainDataGridFilteringLabel => r'شامل نمی شود'; @override - String get noSelectedDateCalendarLabel => r'日付が選択されていません'; + String get doesNotEndWithDataGridFilteringLabel => r'به پایان نمی رسد'; @override - String get ofDataPagerLabel => r'の'; + String get doesNotEqualDataGridFilteringLabel => r'برابر نیست'; @override - String get pagesDataPagerLabel => r'ページ'; + String get emptyDataGridFilteringLabel => r'خالی'; @override - String get passwordDialogContentLabel => r'このPDFファイルを開くためのパスワードを入力してください'; + String get endsWithDataGridFilteringLabel => r'به پایان می رسد با'; @override - String get passwordDialogHeaderTextLabel => r'守られたパスワード'; + String get equalsDataGridFilteringLabel => r'برابر است'; @override - String get passwordDialogHintTextLabel => r'パスワードを入力する'; + String get fromDataGridFilteringLabel => r'از جانب'; @override - String get passwordDialogInvalidPasswordLabel => r'無効なパスワード'; + String get greaterThanDataGridFilteringLabel => r'بزرگتر از'; @override - String get pdfBookmarksLabel => r'ブックマーク'; + String get greaterThanOrEqualDataGridFilteringLabel => r'بزرگتر یا مساوی'; @override - String get pdfEnterPageNumberLabel => r'ページ番号を入力してください'; + String get jumada1Label => r'جمادی الاول'; @override - String get pdfGoToPageLabel => r'ページに移動'; + String get jumada2Label => r'جمادی الثانی'; @override - String get pdfInvalidPageNumberLabel => r'有効な数値を入力してください'; + String get lessThanDataGridFilteringLabel => r'کمتر از'; @override - String get pdfNoBookmarksLabel => r'ブックマークが見つかりません'; + String get lessThanOrEqualDataGridFilteringLabel => r'کمتر یا برابر'; @override - String get pdfPaginationDialogCancelLabel => r'キャンセル'; + String get muharramLabel => r'محرم'; @override - String get pdfPaginationDialogOkLabel => r'わかった'; + String get noEventsCalendarLabel => r'هیچ رویدادی وجود ندارد'; @override - String get pdfPasswordDialogCancelLabel => r'キャンセル'; + String get noMatchesDataGridFilteringLabel => r'هیچ بازی'; @override - String get pdfPasswordDialogOpenLabel => r'開いた'; + String get noSelectedDateCalendarLabel => r'تاریخی انتخاب نشده است'; @override - String get pdfScrollStatusOfLabel => r'の'; + String get notEmptyDataGridFilteringLabel => r'خالی نیست'; @override - String get rabi1Label => r'ラビー・ウル・アウワル'; + String get notNullDataGridFilteringLabel => r'تهی نیست'; @override - String get rabi2Label => r'ラビー・アル・タニ'; + String get nullDataGridFilteringLabel => r'خالی'; @override - String get rajabLabel => r'ラジャブ'; + String get numberFiltersDataGridFilteringLabel => r'فیلترهای اعداد'; @override - String get ramadanLabel => r'ラマダン'; + String get ofDataPagerLabel => r'از'; @override - String get rowsPerPageDataPagerLabel => r'1ページあたりの行数'; + String get okDataGridFilteringLabel => r'خوب'; @override - String get safarLabel => r'Safar'; + String get orDataGridFilteringLabel => r'یا'; @override - String get series => r'シリーズ'; + String get pagesDataPagerLabel => r'صفحات'; @override - String get shaabanLabel => r'シャアバーン'; + String get passwordDialogContentLabel => + r'رمز عبور را برای باز کردن این فایل PDF وارد کنید'; @override - String get shawwalLabel => r'シャウワール'; + String get passwordDialogHeaderTextLabel => r'رمز عبور محافظت شده است'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get passwordDialogHintTextLabel => r'رمز عبور را وارد کنید'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get passwordDialogInvalidPasswordLabel => r'رمز عبور نامعتبر'; @override - String get shortJumada1Label => r'ジャム。私'; + String get pdfBookmarksLabel => r'نشانک ها'; @override - String get shortJumada2Label => r'ジャム。 II'; + String get pdfEnterPageNumberLabel => r'شماره صفحه را وارد کنید'; @override - String get shortMuharramLabel => r'ムー。'; + String get pdfGoToPageLabel => r'برو به صفحه'; @override - String get shortRabi1Label => r'ラビ。私'; + String get pdfHyperlinkContentLabel => r'آیا می خواهید صفحه را در'; @override - String get shortRabi2Label => r'ラビ。 II'; + String get pdfHyperlinkDialogCancelLabel => r'لغو'; @override - String get shortRajabLabel => r'ラージ。'; + String get pdfHyperlinkDialogOpenLabel => r'باز کن'; @override - String get shortRamadanLabel => r'RAM。'; + String get pdfHyperlinkLabel => r'صفحه وب را باز کنید'; @override - String get shortSafarLabel => r'Saf。'; + String get pdfInvalidPageNumberLabel => r'لطفا یک شماره معتبر وارد کنید'; @override - String get shortShaabanLabel => r'シャ。'; + String get pdfNoBookmarksLabel => r'هیچ نشانکی یافت نشد'; @override - String get shortShawwalLabel => r'ショー。'; + String get pdfPaginationDialogCancelLabel => r'لغو'; @override - String get todayLabel => r'今日'; + String get pdfPaginationDialogOkLabel => r'تأیید'; @override - String get weeknumberLabel => r'週'; -} + String get pdfPasswordDialogCancelLabel => r'لغو'; -/// The translations for Georgian (`ka`). -class SfLocalizationsKa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsKa class - const SfLocalizationsKa({ - String localeName = 'ka', - }) : super( - localeName: localeName, - ); + @override + String get pdfPasswordDialogOpenLabel => r'باز کن'; @override - String get allDayLabel => r'Მთელი დღე'; + String get pdfScrollStatusOfLabel => r'از'; @override - String get allowedViewDayLabel => r'Დღეს'; + String get pdfSignaturePadDialogClearLabel => r'پاک کردن'; @override - String get allowedViewMonthLabel => r'თვე'; + String get pdfSignaturePadDialogHeaderTextLabel => r'امضاتو بکش'; @override - String get allowedViewScheduleLabel => r'განრიგი'; + String get pdfSignaturePadDialogPenColorLabel => r'رنگ قلم'; @override - String get allowedViewTimelineDayLabel => r'ვადების დღე'; + String get pdfSignaturePadDialogSaveLabel => r'ذخيره'; @override - String get allowedViewTimelineMonthLabel => r'ვადები თვე'; + String get pdfTextSelectionMenuCopyLabel => r'کپی'; @override - String get allowedViewTimelineWeekLabel => r'ვადები კვირა'; + String get pdfTextSelectionMenuHighlightLabel => r'برجسته'; @override - String get allowedViewTimelineWorkWeekLabel => r'ვადები სამუშაო კვირა'; + String get pdfTextSelectionMenuSquigglyLabel => r'موج‌دار'; @override - String get allowedViewWeekLabel => r'კვირა'; + String get pdfTextSelectionMenuStrikethroughLabel => r'خط خوردگی'; @override - String get allowedViewWorkWeekLabel => r'Სამუშაო კვირა'; + String get pdfTextSelectionMenuUnderlineLabel => r'زیرخط'; @override - String get daySpanCountLabel => r'Დღეს'; + String get rabi1Label => r'ربیع الاول'; @override - String get dhualhiLabel => r'დჰულ-ჰიჯა'; + String get rabi2Label => r'ربیع الثانی'; @override - String get dhualqiLabel => r'დჰულ-ქიდა'; + String get rajabLabel => r'رجب'; @override - String get jumada1Label => r'ჯუმადა ალ-ავვალი'; + String get ramadanLabel => r'رمضان'; @override - String get jumada2Label => r'ჯუმადა ალ-თანი'; + String get rowsPerPageDataPagerLabel => r'ردیف در هر صفحه'; @override - String get muharramLabel => r'მუჰარამი'; + String get safarLabel => r'صفر'; @override - String get noEventsCalendarLabel => r'არანაირი მოვლენა'; + String get searchDataGridFilteringLabel => r'جستجو کردن'; @override - String get noSelectedDateCalendarLabel => r'არჩეული თარიღი არ არის'; + String get selectAllDataGridFilteringLabel => r'انتخاب همه'; @override - String get ofDataPagerLabel => r'დან'; + String get series => r'سلسله'; @override - String get pagesDataPagerLabel => r'გვერდები'; + String get shaabanLabel => r'شعبان'; @override - String get passwordDialogContentLabel => - r'შეიყვანეთ პაროლი ამ PDF ფაილის გასახსნელად'; + String get shawwalLabel => r'شوال'; @override - String get passwordDialogHeaderTextLabel => r'პაროლით დაცული'; + String get shortDhualhiLabel => r'ذوالح'; @override - String get passwordDialogHintTextLabel => r'შეიყვანეთ პაროლი'; + String get shortDhualqiLabel => r'ذوالق'; @override - String get passwordDialogInvalidPasswordLabel => r'არასწორი პაროლი'; + String get shortJumada1Label => r'جام. من'; @override - String get pdfBookmarksLabel => r'სანიშნეები'; + String get shortJumada2Label => r'جام. II'; @override - String get pdfEnterPageNumberLabel => r'შეიყვანეთ გვერდის ნომერი'; + String get shortMuharramLabel => r'مه'; @override - String get pdfGoToPageLabel => r'Გადადით გვერდზე'; + String get shortRabi1Label => r'ربیع من'; @override - String get pdfInvalidPageNumberLabel => r'გთხოვთ, შეიყვანოთ სწორი ნომერი'; + String get shortRabi2Label => r'ربیع II'; @override - String get pdfNoBookmarksLabel => r'სანიშნეები ვერ მოიძებნა'; + String get shortRajabLabel => r'راج'; @override - String get pdfPaginationDialogCancelLabel => r'გაუქმება'; + String get shortRamadanLabel => r'رم.'; @override - String get pdfPaginationDialogOkLabel => r'კარგი'; + String get shortSafarLabel => r'ساف'; @override - String get pdfPasswordDialogCancelLabel => r'გაუქმება'; + String get shortShaabanLabel => r'شا.'; @override - String get pdfPasswordDialogOpenLabel => r'გახსენით'; + String get shortShawwalLabel => r'شاو'; @override - String get pdfScrollStatusOfLabel => r'დან'; + String get showRowsWhereDataGridFilteringLabel => r'نشان دادن ردیف ها در کجا'; @override - String get rabi1Label => r'რაბი ალ-ავვალი'; + String get sortAToZDataGridFilteringLabel => r'مرتب سازی A به Z'; @override - String get rabi2Label => r'რაბი ალ-თანი'; + String get sortAndFilterDataGridFilteringLabel => r'مرتب سازی و فیلتر کردن'; @override - String get rajabLabel => r'რაჯაბი'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'مرتب سازی بزرگترین به کوچکترین'; @override - String get ramadanLabel => r'რამადანი'; + String get sortNewestToOldestDataGridFilteringLabel => + r'مرتب سازی جدیدترین به قدیمی ترین'; @override - String get rowsPerPageDataPagerLabel => r'რიგები თითო გვერდზე'; + String get sortOldestToNewestDataGridFilteringLabel => + r'مرتب سازی قدیمی ترین به جدیدترین'; @override - String get safarLabel => r'საფარი'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'مرتب سازی کوچک ترین به بزرگ ترین'; @override - String get series => r'სერიალი'; + String get sortZToADataGridFilteringLabel => r'Z به A مرتب کنید'; @override - String get shaabanLabel => r'შააბანი'; + String get textFiltersDataGridFilteringLabel => r'فیلترهای متن'; @override - String get shawwalLabel => r'შავვალი'; + String get todayLabel => r'امروز'; @override - String get shortDhualhiLabel => r'დულ-ჰ'; + String get weeknumberLabel => r'هفته'; +} + +/// The translations for Finnish (`fi`). +class SfLocalizationsFi extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsFi class + const SfLocalizationsFi({String localeName = 'fi'}) + : super(localeName: localeName); @override - String get shortDhualqiLabel => r'დულ-ქ'; + String get afterDataGridFilteringLabel => r'Jälkeen'; @override - String get shortJumada1Label => r'ჯუმ. მე'; + String get afterOrEqualDataGridFilteringLabel => r'Jälkeen tai Equal'; @override - String get shortJumada2Label => r'ჯუმ. II'; + String get allDayLabel => r'Koko päivä'; @override - String get shortMuharramLabel => r'მუჰ.'; + String get allowedViewDayLabel => r'Päivä'; @override - String get shortRabi1Label => r'რაბი. მე'; + String get allowedViewMonthLabel => r'Kuukausi'; @override - String get shortRabi2Label => r'რაბი. II'; + String get allowedViewScheduleLabel => r'Ajoittaa'; @override - String get shortRajabLabel => r'რაჯ.'; + String get allowedViewTimelineDayLabel => r'Aikajanan päivä'; @override - String get shortRamadanLabel => r'ვერძი.'; + String get allowedViewTimelineMonthLabel => r'Aikajanan kuukausi'; @override - String get shortSafarLabel => r'საფ.'; + String get allowedViewTimelineWeekLabel => r'Aikajanan viikko'; @override - String get shortShaabanLabel => r'შა.'; + String get allowedViewTimelineWorkWeekLabel => r'Aikajanan työviikko'; @override - String get shortShawwalLabel => r'შოუ.'; + String get allowedViewWeekLabel => r'Viikko'; @override - String get todayLabel => r'დღეს'; + String get allowedViewWorkWeekLabel => r'Työviikko'; @override - String get weeknumberLabel => r'კვირა'; -} + String get andDataGridFilteringLabel => r'Ja'; -/// The translations for Kazakh (`kk`). -class SfLocalizationsKk extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsKk class - const SfLocalizationsKk({ - String localeName = 'kk', - }) : super( - localeName: localeName, - ); + @override + String get beforeDataGridFilteringLabel => r'Ennen'; @override - String get allDayLabel => r'Күні бойы'; + String get beforeOrEqualDataGridFilteringLabel => r'Ennen tai Tasa-arvoinen'; @override - String get allowedViewDayLabel => r'Күн'; + String get beginsWithDataGridFilteringLabel => r'Alkaa jollakin'; @override - String get allowedViewMonthLabel => r'Ай'; + String get cancelDataGridFilteringLabel => r'Peruuta'; @override - String get allowedViewScheduleLabel => r'Кесте'; + String get clearFilterDataGridFilteringLabel => r'Tyhjennä suodatin'; @override - String get allowedViewTimelineDayLabel => r'Хронология күні'; + String get containsDataGridFilteringLabel => r'Sisältää'; @override - String get allowedViewTimelineMonthLabel => r'Хронология айы'; + String get dateFiltersDataGridFilteringLabel => r'Päivämääräsuodattimet'; @override - String get allowedViewTimelineWeekLabel => r'Хронология аптасы'; + String get daySpanCountLabel => r'Päivä'; @override - String get allowedViewTimelineWorkWeekLabel => r'Хронологиялық жұмыс аптасы'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get allowedViewWeekLabel => r'Апта'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get allowedViewWorkWeekLabel => r'Жұмыс аптасы'; + String get doesNotBeginWithDataGridFilteringLabel => r'Ei ala'; @override - String get daySpanCountLabel => r'Күн'; + String get doesNotContainDataGridFilteringLabel => r'Ei sisällä'; @override - String get dhualhiLabel => r'Зул-Хиджа'; + String get doesNotEndWithDataGridFilteringLabel => r'Ei lopu'; @override - String get dhualqiLabel => r'Зу әл-Қида'; + String get doesNotEqualDataGridFilteringLabel => r'Ei ole sama'; @override - String get jumada1Label => r'Жұмада әл-әууәл'; + String get emptyDataGridFilteringLabel => r'Tyhjä'; @override - String get jumada2Label => r'Жұмада әл-Тани'; + String get endsWithDataGridFilteringLabel => r'Loppuu'; @override - String get muharramLabel => r'Мухаррам'; + String get equalsDataGridFilteringLabel => r'Yhtä'; @override - String get noEventsCalendarLabel => r'Оқиғалар жоқ'; + String get fromDataGridFilteringLabel => r'From'; @override - String get noSelectedDateCalendarLabel => r'Таңдалған күн жоқ'; + String get greaterThanDataGridFilteringLabel => r'Suurempi kuin'; @override - String get ofDataPagerLabel => r'бойынша'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Suurempi kuin tai yhtä suuri'; @override - String get pagesDataPagerLabel => r'беттер'; + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Vähemmän kuin'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + r'Pienempi kuin tai yhtä suuri'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Ei tapahtumia'; + + @override + String get noMatchesDataGridFilteringLabel => r'Ei osumia'; + + @override + String get noSelectedDateCalendarLabel => r'Ei valittua päivämäärää'; + + @override + String get notEmptyDataGridFilteringLabel => r'Ei tyhjä'; + + @override + String get notNullDataGridFilteringLabel => r'Ei tyhjä'; + + @override + String get nullDataGridFilteringLabel => r'Tyhjä'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Numerosuodattimet'; + + @override + String get ofDataPagerLabel => r'/'; + + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'Tai'; + + @override + String get pagesDataPagerLabel => r'sivuja'; @override String get passwordDialogContentLabel => - r'Осы PDF файлын ашу үшін құпия сөзді енгізіңіз'; + r'Syötä salasana avataksesi tämän PDF-tiedoston'; @override - String get passwordDialogHeaderTextLabel => r'Құпия сөзбен қорғалған'; + String get passwordDialogHeaderTextLabel => r'Salasana suojattu'; @override - String get passwordDialogHintTextLabel => r'Құпия сөзді енгізіңіз'; + String get passwordDialogHintTextLabel => r'Kirjoita salasana'; @override - String get passwordDialogInvalidPasswordLabel => r'Құпия сөз жарамсыз'; + String get passwordDialogInvalidPasswordLabel => r'väärä salasana'; @override - String get pdfBookmarksLabel => r'Бетбелгілер'; + String get pdfBookmarksLabel => r'Kirjanmerkit'; @override - String get pdfEnterPageNumberLabel => r'Бет нөмірін енгізіңіз'; + String get pdfEnterPageNumberLabel => r'Syötä sivunumero'; @override - String get pdfGoToPageLabel => r'Бетке өту'; + String get pdfGoToPageLabel => r'Mene sivulle'; @override - String get pdfInvalidPageNumberLabel => r'Жарамды нөмірді енгізіңіз'; + String get pdfHyperlinkContentLabel => r'Haluatko avata sivun osoitteessa'; @override - String get pdfNoBookmarksLabel => r'Ешқандай бетбелгі табылмады'; + String get pdfHyperlinkDialogCancelLabel => r'PERUUTTAA'; @override - String get pdfPaginationDialogCancelLabel => r'БАС ТАРТУ'; + String get pdfHyperlinkDialogOpenLabel => r'Avaa'; @override - String get pdfPaginationDialogOkLabel => r'ЖАРАЙДЫ МА'; + String get pdfHyperlinkLabel => r'Avaa Web-sivu'; @override - String get pdfPasswordDialogCancelLabel => r'БАС ТАРТУ'; + String get pdfInvalidPageNumberLabel => r'Anna kelvollinen numero'; @override - String get pdfPasswordDialogOpenLabel => r'АШЫҚ'; + String get pdfNoBookmarksLabel => r'Kirjanmerkkejä ei löytynyt'; @override - String get pdfScrollStatusOfLabel => r'бойынша'; + String get pdfPaginationDialogCancelLabel => r'Peruuta'; @override - String get rabi1Label => r'Раби' "'" r' әл-әууәл'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get rabi2Label => r'Раби' "'" r' әл-Тани'; + String get pdfPasswordDialogCancelLabel => r'PERUUTTAA'; @override - String get rajabLabel => r'Ражаб'; + String get pdfPasswordDialogOpenLabel => r'Avaa'; @override - String get ramadanLabel => r'Рамазан'; + String get pdfScrollStatusOfLabel => r'/'; @override - String get rowsPerPageDataPagerLabel => r'Әр беттегі жолдар'; + String get pdfSignaturePadDialogClearLabel => r'Tyhjennä'; @override - String get safarLabel => r'Сафар'; + String get pdfSignaturePadDialogHeaderTextLabel => + r'Piirrä allekirjoituksesi'; @override - String get series => r'Сериялар'; + String get pdfSignaturePadDialogPenColorLabel => r'Kynän väri'; @override - String get shaabanLabel => r'Шағбан'; + String get pdfSignaturePadDialogSaveLabel => r'Tallenna'; @override - String get shawwalLabel => r'Шәууәл'; + String get pdfTextSelectionMenuCopyLabel => r'Kopio'; @override - String get shortDhualhiLabel => r'Зул-Х'; + String get pdfTextSelectionMenuHighlightLabel => r'Korosta'; @override - String get shortDhualqiLabel => r'Зул-Қ'; + String get pdfTextSelectionMenuSquigglyLabel => r'Aaltoviiva'; @override - String get shortJumada1Label => r'Jum. I'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Yliviivattu'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Alleviivaus'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Rivejä per sivu'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Hae'; + + @override + String get selectAllDataGridFilteringLabel => r'Valitse kaikki'; + + @override + String get series => r'Sarja'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. minä'; @override String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Мұх.'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Раби. I'; + String get shortRabi1Label => r'Rabi. minä'; @override - String get shortRabi2Label => r'Раби. II'; + String get shortRabi2Label => r'Rabi. II'; @override - String get shortRajabLabel => r'Радж.'; + String get shortRajabLabel => r'Raj.'; @override - String get shortRamadanLabel => r'Жедел Жадтау Құрылғысы.'; + String get shortRamadanLabel => r'RAM.'; @override String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Ша.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'Шоу.'; + String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Бүгін'; + String get showRowsWhereDataGridFilteringLabel => r'Näytä rivit missä'; @override - String get weeknumberLabel => r'Апта'; + String get sortAToZDataGridFilteringLabel => r'Lajittele A–Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Lajittele ja Suodata'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Lajittele suurimmasta pienimpään'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Lajittele uusimmasta vanhimpaan'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Lajittele vanhimmasta uusimpaan'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Lajittele pienimmästä suurimpaan'; + + @override + String get sortZToADataGridFilteringLabel => r'Lajittele Z-A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Tekstisuodattimet'; + + @override + String get todayLabel => r'Tänään'; + + @override + String get weeknumberLabel => r'Viikko'; } -/// The translations for Khmer Central Khmer (`km`). -class SfLocalizationsKm extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsKm class - const SfLocalizationsKm({ - String localeName = 'km', - }) : super( - localeName: localeName, - ); +/// The translations for Filipino Pilipino (`fil`). +class SfLocalizationsFil extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsFil class + const SfLocalizationsFil({String localeName = 'fil'}) + : super(localeName: localeName); @override - String get allDayLabel => r'ពេញមួយថ្ងៃ'; + String get afterDataGridFilteringLabel => r'Pagkatapos'; @override - String get allowedViewDayLabel => r'ថ្ងៃ'; + String get afterOrEqualDataGridFilteringLabel => r'Pagkatapos ng Or Equal'; @override - String get allowedViewMonthLabel => r'ខែ'; + String get allDayLabel => r'Buong araw'; @override - String get allowedViewScheduleLabel => r'កាលវិភាគ'; + String get allowedViewDayLabel => r'Araw'; @override - String get allowedViewTimelineDayLabel => r'ថ្ងៃកំណត់ពេលវេលា'; + String get allowedViewMonthLabel => r'Buwan'; @override - String get allowedViewTimelineMonthLabel => r'តារាងពេលវេលាខែ'; + String get allowedViewScheduleLabel => r'Iskedyul'; @override - String get allowedViewTimelineWeekLabel => r'Timeline សប្តាហ៍'; + String get allowedViewTimelineDayLabel => r'Araw ng Timeline'; @override - String get allowedViewTimelineWorkWeekLabel => r'កាលវិភាគការងារប្រចាំសប្តាហ៍'; + String get allowedViewTimelineMonthLabel => r'Buwan ng Timeline'; @override - String get allowedViewWeekLabel => r'សប្តាហ៍'; + String get allowedViewTimelineWeekLabel => r'Linggo ng Timeline'; @override - String get allowedViewWorkWeekLabel => r'សប្តាហ៍ការងារ'; + String get allowedViewTimelineWorkWeekLabel => + r'Linggo ng Trabaho sa Timeline'; @override - String get daySpanCountLabel => r'ថ្ងៃ'; + String get allowedViewWeekLabel => r'Linggo'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get allowedViewWorkWeekLabel => r'Linggo ng trabaho'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get andDataGridFilteringLabel => r'At'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get beforeDataGridFilteringLabel => r'Bago'; @override - String get jumada2Label => r'ជូម៉ាដា អាល់ថានី'; + String get beforeOrEqualDataGridFilteringLabel => r'Bago O Kapantay'; @override - String get muharramLabel => r'Muharram'; + String get beginsWithDataGridFilteringLabel => r'Nagsisimula sa'; @override - String get noEventsCalendarLabel => r'គ្មានព្រឹត្តិការណ៍'; + String get cancelDataGridFilteringLabel => r'Kanselahin'; @override - String get noSelectedDateCalendarLabel => - r'គ្មានកាលបរិច្ឆេទដែលបានជ្រើសរើសទេ។'; + String get clearFilterDataGridFilteringLabel => r'I-clear ang Filter'; @override - String get ofDataPagerLabel => r'នៃ'; + String get containsDataGridFilteringLabel => r'Naglalaman'; @override - String get pagesDataPagerLabel => r'ទំព័រ'; + String get dateFiltersDataGridFilteringLabel => r'Mga Filter ng Petsa'; @override - String get passwordDialogContentLabel => - r'បញ្ចូលពាក្យសម្ងាត់ដើម្បីបើកឯកសារ PDF នេះ។'; + String get daySpanCountLabel => r'Araw'; @override - String get passwordDialogHeaderTextLabel => r'ពាក្យសម្ងាត់ការពារ'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get passwordDialogHintTextLabel => r'បញ្ចូលពាក្យសម្ងាត់'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get passwordDialogInvalidPasswordLabel => r'ពាក្យសម្ងាត់មិនត្រឹមត្រូវ'; + String get doesNotBeginWithDataGridFilteringLabel => r'Hindi Nagsisimula Sa'; @override - String get pdfBookmarksLabel => r'ចំណាំ'; + String get doesNotContainDataGridFilteringLabel => r'Hindi Naglalaman'; @override - String get pdfEnterPageNumberLabel => r'បញ្ចូលលេខទំព័រ'; + String get doesNotEndWithDataGridFilteringLabel => r'Hindi Nagtatapos Sa'; @override - String get pdfGoToPageLabel => r'ទៅកាន់​ទំព័រ'; + String get doesNotEqualDataGridFilteringLabel => r'Ay hindi katumbas ng'; @override - String get pdfInvalidPageNumberLabel => r'សូមបញ្ចូលលេខត្រឹមត្រូវ។'; + String get emptyDataGridFilteringLabel => r'Walang laman'; @override - String get pdfNoBookmarksLabel => r'រកមិនឃើញចំណាំទេ។'; + String get endsWithDataGridFilteringLabel => r'Nagtatapos Sa'; @override - String get pdfPaginationDialogCancelLabel => r'បោះបង់'; + String get equalsDataGridFilteringLabel => r'katumbas'; @override - String get pdfPaginationDialogOkLabel => r'យល់ព្រម'; + String get fromDataGridFilteringLabel => r'Mula sa'; @override - String get pdfPasswordDialogCancelLabel => r'បោះបង់'; + String get greaterThanDataGridFilteringLabel => r'Mahigit sa'; @override - String get pdfPasswordDialogOpenLabel => r'បើក'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Higit sa O Katumbas'; @override - String get pdfScrollStatusOfLabel => r'នៃ'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get rabi1Label => r'រ៉ាប៊ី អាល់ អាវ៉ាល់'; + String get jumada2Label => r'Jumada al-thani'; @override - String get rabi2Label => r'រ៉ាប៊ី អាល់ថានី'; + String get lessThanDataGridFilteringLabel => r'Mas mababa sa'; @override - String get rajabLabel => r'រ៉ាចាប'; + String get lessThanOrEqualDataGridFilteringLabel => + r'Mas Mababa sa O Katumbas'; @override - String get ramadanLabel => r'រ៉ាម៉ាដាន'; + String get muharramLabel => r'Muharram'; @override - String get rowsPerPageDataPagerLabel => r'ជួរដេកក្នុងមួយទំព័រ'; + String get noEventsCalendarLabel => r'Walang mga kaganapan'; @override - String get safarLabel => r'សាហ្វារ'; + String get noMatchesDataGridFilteringLabel => r'Walang tugma'; @override - String get series => r'ស៊េរី'; + String get noSelectedDateCalendarLabel => r'Walang napiling petsa'; @override - String get shaabanLabel => r'សារ៉ាបាន'; + String get notEmptyDataGridFilteringLabel => r'Hindi Walang laman'; @override - String get shawwalLabel => r'Shawwal'; + String get notNullDataGridFilteringLabel => r'Hindi Null'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get nullDataGridFilteringLabel => r'Wala'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get numberFiltersDataGridFilteringLabel => r'Mga Filter ng Numero'; @override - String get shortJumada1Label => r'ជុម ខ្ញុំ'; + String get ofDataPagerLabel => r'ng'; @override - String get shortJumada2Label => r'ជុម II'; + String get okDataGridFilteringLabel => r'OK'; @override - String get shortMuharramLabel => r'ម៉ោ'; + String get orDataGridFilteringLabel => r'O kaya'; @override - String get shortRabi1Label => r'រ៉ាប៊ី ខ្ញុំ'; + String get pagesDataPagerLabel => r'mga pahina'; @override - String get shortRabi2Label => r'រ៉ាប៊ី II'; + String get passwordDialogContentLabel => + r'Ilagay ang password para buksan ang PDF file na ito'; @override - String get shortRajabLabel => r'រាជ'; + String get passwordDialogHeaderTextLabel => r'Pinoprotektahan ng Password'; @override - String get shortRamadanLabel => r'អង្គ​ចងចាំ។'; + String get passwordDialogHintTextLabel => r'Ilagay ang password'; @override - String get shortSafarLabel => r'សុវត្ថិភាព។'; + String get passwordDialogInvalidPasswordLabel => r'di wastong password'; @override - String get shortShaabanLabel => r'សា។'; + String get pdfBookmarksLabel => r'Mga bookmark'; @override - String get shortShawwalLabel => r'Shaw ។'; + String get pdfEnterPageNumberLabel => r'Ipasok ang numero ng pahina'; @override - String get todayLabel => r'ថ្ងៃនេះ'; + String get pdfGoToPageLabel => r'Pumunta sa pahina'; @override - String get weeknumberLabel => r'សប្តាហ៍'; -} + String get pdfHyperlinkContentLabel => r'Gusto mo bang buksan ang pahina sa'; -/// The translations for Kannada (`kn`). -class SfLocalizationsKn extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsKn class - const SfLocalizationsKn({ - String localeName = 'kn', - }) : super( - localeName: localeName, - ); + @override + String get pdfHyperlinkDialogCancelLabel => r'KANSELAHIN'; @override - String get allDayLabel => - '\u{c87}\u{ca1}\u{cbf}\u{cd5}\u{20}\u{ca6}\u{cbf}\u{ca8}'; + String get pdfHyperlinkDialogOpenLabel => r'Bukas'; @override - String get allowedViewDayLabel => '\u{ca6}\u{cbf}\u{ca8}'; + String get pdfHyperlinkLabel => r'Buksan ang Web Page'; @override - String get allowedViewMonthLabel => - '\u{ca4}\u{cbf}\u{c82}\u{c97}\u{cb3}\u{cc1}'; + String get pdfInvalidPageNumberLabel => + r'Mangyaring magpasok ng wastong numero'; @override - String get allowedViewScheduleLabel => - '\u{cb5}\u{cc7}\u{cb3}\u{cbe}\u{caa}\u{c9f}\u{ccd}\u{c9f}\u{cbf}'; + String get pdfNoBookmarksLabel => r'Walang nakitang mga bookmark'; @override - String get allowedViewTimelineDayLabel => - '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{ca6}\u{cbf}\u{ca8}'; + String get pdfPaginationDialogCancelLabel => r'Kanselahin'; @override - String get allowedViewTimelineMonthLabel => - '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{ca4}\u{cbf}\u{c82}\u{c97}\u{cb3}\u{cc1}'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get allowedViewTimelineWeekLabel => - '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + String get pdfPasswordDialogCancelLabel => r'KANSELAHIN'; + + @override + String get pdfPasswordDialogOpenLabel => r'Bukas'; + + @override + String get pdfScrollStatusOfLabel => r'ng'; + + @override + String get pdfSignaturePadDialogClearLabel => r'I-clear'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Iguhit ang iyong lagda'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Kulay ng Panulat'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'I-save'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopya'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'I-highlight'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Aalugin'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Tanggalin'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Salungguhit'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Mga hilera bawat pahina'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Maghanap'; + + @override + String get selectAllDataGridFilteringLabel => r'Piliin lahat'; + + @override + String get series => r'Serye'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. ako'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. ako'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => + r'Ipakita ang mga hilera kung saan'; + + @override + String get sortAToZDataGridFilteringLabel => r'Pagbukud-bukurin A hanggang Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => + r'Pagbukud-bukurin at Salain'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakamalaki Hanggang Pinakamaliit'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakabago Hanggang sa Pinakaluma'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakaluma Hanggang Pinakabago'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakamaliit Hanggang Pinakamalaki'; + + @override + String get sortZToADataGridFilteringLabel => + r'Pagbukud-bukurin ang Z Hanggang A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Mga Filter ng Teksto'; + + @override + String get todayLabel => r'Ngayong araw'; + + @override + String get weeknumberLabel => r'Linggo'; +} + +/// The translations for French (`fr`). +class SfLocalizationsFr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsFr class + const SfLocalizationsFr({String localeName = 'fr'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Après'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Après ou égal'; + + @override + String get allDayLabel => r'Toute la journée'; + + @override + String get allowedViewDayLabel => r'Jour'; + + @override + String get allowedViewMonthLabel => r'Mois'; + + @override + String get allowedViewScheduleLabel => r'Programme'; + + @override + String get allowedViewTimelineDayLabel => r'Chronologie Jour'; + + @override + String get allowedViewTimelineMonthLabel => r'Chronologie Mois'; + + @override + String get allowedViewTimelineWeekLabel => r'Chronologie Semaine'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Chronologie Semaine de travail'; + + @override + String get allowedViewWeekLabel => r'Semaine'; + + @override + String get allowedViewWorkWeekLabel => r'Semaine de travail'; + + @override + String get andDataGridFilteringLabel => r'Et'; + + @override + String get beforeDataGridFilteringLabel => r'Avant de'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Avant ou égal'; + + @override + String get beginsWithDataGridFilteringLabel => r'Commence par'; + + @override + String get cancelDataGridFilteringLabel => r'Annuler'; + + @override + String get clearFilterDataGridFilteringLabel => r'Effacer le filtre'; + + @override + String get containsDataGridFilteringLabel => r'Contient'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filtres de dates'; + + @override + String get daySpanCountLabel => r'Jour'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhou al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Ne commence pas par'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Ne contient pas'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Ne se termine pas par'; + + @override + String get doesNotEqualDataGridFilteringLabel => + r'N' + "'" + r'est pas égal'; + + @override + String get emptyDataGridFilteringLabel => r'Vide'; + + @override + String get endsWithDataGridFilteringLabel => r'Se termine par'; + + @override + String get equalsDataGridFilteringLabel => r'Équivaut à'; + + @override + String get fromDataGridFilteringLabel => r'De'; + + @override + String get greaterThanDataGridFilteringLabel => r'Plus grand que'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Meilleur que ou égal'; + + @override + String get jumada1Label => r'Djoumada al-awwal'; + + @override + String get jumada2Label => r'Djoumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Moins que'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Inférieur ou égal'; + + @override + String get muharramLabel => r'Mouharram'; + + @override + String get noEventsCalendarLabel => + r'Pas d' + "'" + r'événements'; + + @override + String get noMatchesDataGridFilteringLabel => r'Pas de correspondance'; + + @override + String get noSelectedDateCalendarLabel => r'Aucune date sélectionnée'; + + @override + String get notEmptyDataGridFilteringLabel => r'Pas vide'; + + @override + String get notNullDataGridFilteringLabel => r'Non nul'; + + @override + String get nullDataGridFilteringLabel => r'Nul'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filtres numériques'; + + @override + String get ofDataPagerLabel => r'de'; + + @override + String get okDataGridFilteringLabel => + r'D' + "'" + r'ACCORD'; + + @override + String get orDataGridFilteringLabel => r'Ou'; + + @override + String get pagesDataPagerLabel => r'pages'; + + @override + String get passwordDialogContentLabel => + r'Entrez le mot de passe pour ouvrir ce fichier PDF'; + + @override + String get passwordDialogHeaderTextLabel => r'Protégé par mot de passe'; + + @override + String get passwordDialogHintTextLabel => r'Entrer le mot de passe'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Mot de passe incorrect'; + + @override + String get pdfBookmarksLabel => r'Signets'; + + @override + String get pdfEnterPageNumberLabel => r'Entrer le numéro de page'; + + @override + String get pdfGoToPageLabel => r'Aller à la page'; + + @override + String get pdfHyperlinkContentLabel => r'Voulez-vous ouvrir la page à'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ANNULER'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'Ouvrir'; + + @override + String get pdfHyperlinkLabel => r'Ouvrir la page Web'; + + @override + String get pdfInvalidPageNumberLabel => + r'S' + "'" + r'il vous plait, entrez un nombre valide'; + + @override + String get pdfNoBookmarksLabel => r'Aucun signet trouvé'; + + @override + String get pdfPaginationDialogCancelLabel => r'ANNULER'; + + @override + String get pdfPaginationDialogOkLabel => r'OK'; + + @override + String get pdfPasswordDialogCancelLabel => r'ANNULER'; + + @override + String get pdfPasswordDialogOpenLabel => r'Ouvrir'; + + @override + String get pdfScrollStatusOfLabel => r'de'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Effacer'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Dessinez votre signature'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Couleur du stylo'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Enregistrer'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Copie'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Mettre en surbrillance'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Surligneur vague'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Barré'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Souligner'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Lignes par page'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Chercher'; + + @override + String get selectAllDataGridFilteringLabel => r'Tout sélectionner'; + + @override + String get series => r'Série'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. je'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Mouh.'; + + @override + String get shortRabi1Label => r'Rabi. je'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Cha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Afficher les lignes où'; + + @override + String get sortAToZDataGridFilteringLabel => r'Trier de A à Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Trier et filtrer'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Trier du plus grand au plus petit'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Trier du plus récent au plus ancien'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Trier du plus ancien au plus récent'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Trier du plus petit au plus grand'; + + @override + String get sortZToADataGridFilteringLabel => r'Trier Z à A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Filtres de texte'; + + @override + String get todayLabel => + r'Aujourd' + "'" + r'hui'; + + @override + String get weeknumberLabel => r'Semaine'; +} + +/// The translations for Galician (`gl`). +class SfLocalizationsGl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsGl class + const SfLocalizationsGl({String localeName = 'gl'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Despois'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Despois Ou Igual'; + + @override + String get allDayLabel => r'Todo o día'; + + @override + String get allowedViewDayLabel => r'Día'; + + @override + String get allowedViewMonthLabel => r'Mes'; + + @override + String get allowedViewScheduleLabel => r'Horario'; + + @override + String get allowedViewTimelineDayLabel => r'Día da cronoloxía'; + + @override + String get allowedViewTimelineMonthLabel => r'Mes da cronoloxía'; + + @override + String get allowedViewTimelineWeekLabel => r'Semana da cronoloxía'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Semana de traballo da cronoloxía'; + + @override + String get allowedViewWeekLabel => r'Semana'; + + @override + String get allowedViewWorkWeekLabel => r'Semana de Traballo'; + + @override + String get andDataGridFilteringLabel => r'E'; + + @override + String get beforeDataGridFilteringLabel => r'Antes'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Antes Ou Igual'; + + @override + String get beginsWithDataGridFilteringLabel => r'Comeza por'; + + @override + String get cancelDataGridFilteringLabel => r'Cancelar'; + + @override + String get clearFilterDataGridFilteringLabel => r'Borrar filtro'; + + @override + String get containsDataGridFilteringLabel => r'Contén'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filtros de data'; + + @override + String get daySpanCountLabel => r'Día'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Non Comeza Con'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Non Contén'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Non Remata Con'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Non é igual'; + + @override + String get emptyDataGridFilteringLabel => r'Baleiro'; + + @override + String get endsWithDataGridFilteringLabel => r'Remata con'; + + @override + String get equalsDataGridFilteringLabel => r'Iguais'; + + @override + String get fromDataGridFilteringLabel => r'Desde'; + + @override + String get greaterThanDataGridFilteringLabel => r'Máis grande cá'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Maior Que Ou Igual'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Menos de'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Menos Ou Igual'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Non hai eventos'; + + @override + String get noMatchesDataGridFilteringLabel => r'Non hai coincidencias'; + + @override + String get noSelectedDateCalendarLabel => r'Sen data seleccionada'; + + @override + String get notEmptyDataGridFilteringLabel => r'Non baleiro'; + + @override + String get notNullDataGridFilteringLabel => r'Non nulo'; + + @override + String get nullDataGridFilteringLabel => r'Nulo'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filtros de número'; + + @override + String get ofDataPagerLabel => r'de'; + + @override + String get okDataGridFilteringLabel => r'Ok'; + + @override + String get orDataGridFilteringLabel => r'Ou'; + + @override + String get pagesDataPagerLabel => r'páxinas'; + + @override + String get passwordDialogContentLabel => + r'Introduza o contrasinal para abrir este ficheiro PDF'; + + @override + String get passwordDialogHeaderTextLabel => r'Protexido por contrasinal'; + + @override + String get passwordDialogHintTextLabel => r'Escriba o contrasinal'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Contrasinal incorrecto'; + + @override + String get pdfBookmarksLabel => r'Marcadores'; + + @override + String get pdfEnterPageNumberLabel => r'Introduza o número de páxina'; + + @override + String get pdfGoToPageLabel => r'Ir á páxina'; + + @override + String get pdfHyperlinkContentLabel => r'Queres abrir a páxina en'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'CANCELAR'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ABERTO'; + + @override + String get pdfHyperlinkLabel => r'Abrir páxina web'; + + @override + String get pdfInvalidPageNumberLabel => r'Introduce un número válido'; + + @override + String get pdfNoBookmarksLabel => r'Non se atoparon marcadores'; + + @override + String get pdfPaginationDialogCancelLabel => r'CANCELAR'; + + @override + String get pdfPaginationDialogOkLabel => r'Ok'; + + @override + String get pdfPasswordDialogCancelLabel => r'CANCELAR'; + + @override + String get pdfPasswordDialogOpenLabel => r'ABERTO'; + + @override + String get pdfScrollStatusOfLabel => r'de'; + + @override + String get pdfSignaturePadDialogClearLabel => r'BORRAR'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Debuxa a túa sinatura'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Cor da pluma'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'GARDAR'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Copiar'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Destacar'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Esquivo'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Tachado'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Subliñado'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadán'; + + @override + String get rowsPerPageDataPagerLabel => r'Filas por páxina'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Busca'; + + @override + String get selectAllDataGridFilteringLabel => r'Seleccionar todo'; + + @override + String get series => r'Serie'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. eu'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. eu'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Mostrar as filas onde'; + + @override + String get sortAToZDataGridFilteringLabel => r'Ordenar da A a Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Ordenar e filtrar'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ordenar de maior a menor'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Ordenar o máis novo ao máis antigo'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Ordenar do máis antigo ao máis novo'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ordenar de menor a maior'; + + @override + String get sortZToADataGridFilteringLabel => r'Ordenar de Z a A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Filtros de texto'; + + @override + String get todayLabel => r'Hoxe'; + + @override + String get weeknumberLabel => r'Semana'; +} + +/// The translations for Gujarati (`gu`). +class SfLocalizationsGu extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsGu class + const SfLocalizationsGu({String localeName = 'gu'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'પછી'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'અથવા સમાન પછી'; + + @override + String get allDayLabel => r'બધા દિવસ'; + + @override + String get allowedViewDayLabel => r'દિવસ'; + + @override + String get allowedViewMonthLabel => r'માસ'; + + @override + String get allowedViewScheduleLabel => r'અનુસૂચિ'; + + @override + String get allowedViewTimelineDayLabel => r'સમયરેખા દિવસ'; + + @override + String get allowedViewTimelineMonthLabel => r'સમયરેખા મહિનો'; + + @override + String get allowedViewTimelineWeekLabel => r'સમયરેખા અઠવાડિયું'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'સમયરેખા કાર્ય સપ્તાહ'; + + @override + String get allowedViewWeekLabel => r'અઠવાડિયું'; + + @override + String get allowedViewWorkWeekLabel => r'કાર્ય સપ્તાહ'; + + @override + String get andDataGridFilteringLabel => r'અને'; + + @override + String get beforeDataGridFilteringLabel => r'પહેલાં'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'પહેલાં અથવા સમાન'; + + @override + String get beginsWithDataGridFilteringLabel => r'સાથે શરૂ થાય છે'; + + @override + String get cancelDataGridFilteringLabel => r'રદ કરો'; + + @override + String get clearFilterDataGridFilteringLabel => r'ફિલ્ટર સાફ કરો'; + + @override + String get containsDataGridFilteringLabel => r'સમાવે છે'; + + @override + String get dateFiltersDataGridFilteringLabel => r'તારીખ ફિલ્ટર્સ'; + + @override + String get daySpanCountLabel => r'દિવસ'; + + @override + String get dhualhiLabel => r'ધુ અલ-હિજ્જા'; + + @override + String get dhualqiLabel => r'ધુ અલ-કિદાહ'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'સાથે શરૂ થતું નથી'; + + @override + String get doesNotContainDataGridFilteringLabel => r'સમાવતું નથી'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'સાથે સમાપ્ત થતું નથી'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'સમાન નથી'; + + @override + String get emptyDataGridFilteringLabel => r'ખાલી'; + + @override + String get endsWithDataGridFilteringLabel => r'સાથે સમાપ્ત થાય છે'; + + @override + String get equalsDataGridFilteringLabel => r'સમકક્ષ'; + + @override + String get fromDataGridFilteringLabel => r'થી'; + + @override + String get greaterThanDataGridFilteringLabel => r'કરતા વધારે'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'તેના કરતા વધારે અથવા સમાન'; + + @override + String get jumada1Label => r'જુમાદા અલ-અવ્વલ'; + + @override + String get jumada2Label => r'જુમાદા અલ-થાની'; + + @override + String get lessThanDataGridFilteringLabel => r'કરતાં ઓછી'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'તેનાથી ઓછું અથવા સમાન'; + + @override + String get muharramLabel => r'મોહરમ'; + + @override + String get noEventsCalendarLabel => r'કોઈ ઇવેન્ટ નથી'; + + @override + String get noMatchesDataGridFilteringLabel => r'કોઈ મેળ નથી'; + + @override + String get noSelectedDateCalendarLabel => r'કોઈ તારીખ પસંદ કરી નથી'; + + @override + String get notEmptyDataGridFilteringLabel => r'ખાલી નથી'; + + @override + String get notNullDataGridFilteringLabel => r'શૂન્ય નથી'; + + @override + String get nullDataGridFilteringLabel => r'શૂન્ય'; + + @override + String get numberFiltersDataGridFilteringLabel => r'નંબર ફિલ્ટર્સ'; + + @override + String get ofDataPagerLabel => r'ના'; + + @override + String get okDataGridFilteringLabel => r'બરાબર'; + + @override + String get orDataGridFilteringLabel => r'અથવા'; + + @override + String get pagesDataPagerLabel => r'પૃષ્ઠો'; + + @override + String get passwordDialogContentLabel => + r'આ PDF ફાઇલ ખોલવા માટે પાસવર્ડ દાખલ કરો'; + + @override + String get passwordDialogHeaderTextLabel => r'પાસવર્ડ સુરક્ષિત'; + + @override + String get passwordDialogHintTextLabel => r'પાસવર્ડ દાખલ કરો'; + + @override + String get passwordDialogInvalidPasswordLabel => r'અમાન્ય પાસવર્ડ'; + + @override + String get pdfBookmarksLabel => r'બુકમાર્ક્સ'; + + @override + String get pdfEnterPageNumberLabel => r'પૃષ્ઠ નંબર દાખલ કરો'; + + @override + String get pdfGoToPageLabel => r'પૃષ્ઠ પર જાઓ'; + + @override + String get pdfHyperlinkContentLabel => r'શું તમે પાનું ખોલવા માંગો છો'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'રદ કરો'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ખુલ્લા'; + + @override + String get pdfHyperlinkLabel => r'વેબ પેજ ખોલો'; + + @override + String get pdfInvalidPageNumberLabel => r'કૃપા કરીને માન્ય નંબર દાખલ કરો'; + + @override + String get pdfNoBookmarksLabel => r'કોઈ બુકમાર્ક્સ મળ્યાં નથી'; + + @override + String get pdfPaginationDialogCancelLabel => r'રદ કરો'; + + @override + String get pdfPaginationDialogOkLabel => r'બરાબર'; + + @override + String get pdfPasswordDialogCancelLabel => r'રદ કરો'; + + @override + String get pdfPasswordDialogOpenLabel => r'ખુલ્લા'; + + @override + String get pdfScrollStatusOfLabel => r'ના'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ચોખ્ખુ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'તમારી સહી દોરો'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'પેન કલર'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'સાચવો'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'નકલ કરો'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'હાઇલાઇટ કરો'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'સ્ક્વિગલી'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'સ્ટ્રાઈકથ્રુ'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'રેખાંકિત કરો'; + + @override + String get rabi1Label => r'રબી અલ અવ્વલ'; + + @override + String get rabi2Label => r'રબી અલ-થાની'; + + @override + String get rajabLabel => r'રજબ'; + + @override + String get ramadanLabel => r'રમઝાન'; + + @override + String get rowsPerPageDataPagerLabel => r'પૃષ્ઠ દીઠ પંક્તિઓ'; + + @override + String get safarLabel => r'સફર'; + + @override + String get searchDataGridFilteringLabel => r'શોધો'; + + @override + String get selectAllDataGridFilteringLabel => r'બધા પસંદ કરો'; + + @override + String get series => r'શ્રેણી'; + + @override + String get shaabanLabel => r'શાઅબાન'; + + @override + String get shawwalLabel => r'શવ્વાલ'; + + @override + String get shortDhualhiLabel => r'ધુલ-એચ'; + + @override + String get shortDhualqiLabel => r'ધુલ-પ્ર'; + + @override + String get shortJumada1Label => r'જમ. આઈ'; + + @override + String get shortJumada2Label => r'જમ. II'; + + @override + String get shortMuharramLabel => r'મુહ.'; + + @override + String get shortRabi1Label => r'રબી. આઈ'; + + @override + String get shortRabi2Label => r'રબી. II'; + + @override + String get shortRajabLabel => r'રાજ.'; + + @override + String get shortRamadanLabel => r'રામ.'; + + @override + String get shortSafarLabel => r'સેફ.'; + + @override + String get shortShaabanLabel => r'શા.'; + + @override + String get shortShawwalLabel => r'શૉ.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'જ્યાં પંક્તિઓ બતાવો'; + + @override + String get sortAToZDataGridFilteringLabel => r'A થી Z સૉર્ટ કરો'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'સૉર્ટ કરો અને ફિલ્ટર કરો'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'સૌથી મોટાથી નાનામાં સૉર્ટ કરો'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'સૌથી નવાથી જૂનામાં સૉર્ટ કરો'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'સૌથી જૂનાથી નવામાં સૉર્ટ કરો'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'સૌથી નાનાથી મોટામાં સૉર્ટ કરો'; + + @override + String get sortZToADataGridFilteringLabel => r'Z થી A સૉર્ટ કરો'; + + @override + String get textFiltersDataGridFilteringLabel => r'ટેક્સ્ટ ફિલ્ટર્સ'; + + @override + String get todayLabel => r'આજે'; + + @override + String get weeknumberLabel => r'અઠવાડિયું'; +} + +/// The translations for Hebrew (`he`). +class SfLocalizationsHe extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsHe class + const SfLocalizationsHe({String localeName = 'he'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'לאחר'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'אחרי או שווה'; + + @override + String get allDayLabel => r'כל היום'; + + @override + String get allowedViewDayLabel => r'יְוֹם'; + + @override + String get allowedViewMonthLabel => r'חוֹדֶשׁ'; + + @override + String get allowedViewScheduleLabel => r'לוח זמנים'; + + @override + String get allowedViewTimelineDayLabel => r'יום ציר הזמן'; + + @override + String get allowedViewTimelineMonthLabel => r'חודש ציר הזמן'; + + @override + String get allowedViewTimelineWeekLabel => r'שבוע ציר הזמן'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'שבוע העבודה של ציר זמן'; + + @override + String get allowedViewWeekLabel => r'שָׁבוּעַ'; + + @override + String get allowedViewWorkWeekLabel => r'שבוע עבודה'; + + @override + String get andDataGridFilteringLabel => r'ו'; + + @override + String get beforeDataGridFilteringLabel => r'לפני'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'לפני או שווה'; + + @override + String get beginsWithDataGridFilteringLabel => r'מתחיל עם'; + + @override + String get cancelDataGridFilteringLabel => r'לְבַטֵל'; + + @override + String get clearFilterDataGridFilteringLabel => r'נקה מסנן'; + + @override + String get containsDataGridFilteringLabel => r'מכיל'; + + @override + String get dateFiltersDataGridFilteringLabel => r'מסנני תאריכים'; + + @override + String get daySpanCountLabel => r'יְוֹם'; + + @override + String get dhualhiLabel => + r'דהו אל-היג' + "'" + r'ה'; + + @override + String get dhualqiLabel => r'דהו אל-קיאדה'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'לא מתחיל עם'; + + @override + String get doesNotContainDataGridFilteringLabel => r'לא מכיל'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'לא מסתיים ב'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'לא שווה'; + + @override + String get emptyDataGridFilteringLabel => r'ריק'; + + @override + String get endsWithDataGridFilteringLabel => r'מסתיים עם'; + + @override + String get equalsDataGridFilteringLabel => r'שווים'; + + @override + String get fromDataGridFilteringLabel => r'מ'; + + @override + String get greaterThanDataGridFilteringLabel => r'גדול מ'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'גדול או שווה'; + + @override + String get jumada1Label => + r'ג' + "'" + r'ומדה אל-אוול'; + + @override + String get jumada2Label => + r'ג' + "'" + r'ומדה אל-ת' + "'" + r'אני'; + + @override + String get lessThanDataGridFilteringLabel => r'פחות מ'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'פחות מ או שווה'; + + @override + String get muharramLabel => r'מוחרם'; + + @override + String get noEventsCalendarLabel => r'אין אירועים'; + + @override + String get noMatchesDataGridFilteringLabel => r'אין התאמה'; + + @override + String get noSelectedDateCalendarLabel => r'לא נבחר תאריך'; + + @override + String get notEmptyDataGridFilteringLabel => r'לא ריק'; + + @override + String get notNullDataGridFilteringLabel => r'לא ריק'; + + @override + String get nullDataGridFilteringLabel => r'ריק'; + + @override + String get numberFiltersDataGridFilteringLabel => r'מסנני מספרים'; + + @override + String get ofDataPagerLabel => r'שֶׁל'; + + @override + String get okDataGridFilteringLabel => r'בסדר'; + + @override + String get orDataGridFilteringLabel => r'אוֹ'; + + @override + String get pagesDataPagerLabel => r'דפים'; + + @override + String get passwordDialogContentLabel => + r'הזן את הסיסמה כדי לפתוח קובץ PDF זה'; + + @override + String get passwordDialogHeaderTextLabel => r'סיסמא מוגנת'; + + @override + String get passwordDialogHintTextLabel => r'הזן את הסיסמה'; + + @override + String get passwordDialogInvalidPasswordLabel => r'סיסמה שגויה'; + + @override + String get pdfBookmarksLabel => r'סימניות'; + + @override + String get pdfEnterPageNumberLabel => r'הזן מספר עמוד'; + + @override + String get pdfGoToPageLabel => r'לך לעמוד'; + + @override + String get pdfHyperlinkContentLabel => r'האם אתה רוצה לפתוח את העמוד ב'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'לְבַטֵל'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'לִפְתוֹחַ'; + + @override + String get pdfHyperlinkLabel => r'פתח את דף האינטרנט'; + + @override + String get pdfInvalidPageNumberLabel => r'נא הכנס מספר תקף'; + + @override + String get pdfNoBookmarksLabel => r'לא נמצאו סימניות'; + + @override + String get pdfPaginationDialogCancelLabel => r'לְבַטֵל'; + + @override + String get pdfPaginationDialogOkLabel => r'בסדר'; + + @override + String get pdfPasswordDialogCancelLabel => r'לְבַטֵל'; + + @override + String get pdfPasswordDialogOpenLabel => r'לִפְתוֹחַ'; + + @override + String get pdfScrollStatusOfLabel => r'שֶׁל'; + + @override + String get pdfSignaturePadDialogClearLabel => r'לְנַקוֹת'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'צייר את החתימה שלך'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'צבע עט'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'לשמור'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'עותק'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'שִׂיא'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'מתפתל'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'חוצה'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'לָשִׂים דָגֵשׁ'; + + @override + String get rabi1Label => r'רבי אל אווול'; + + @override + String get rabi2Label => r'רבי אל-תאני'; + + @override + String get rajabLabel => + r'רג' + "'" + r'אב'; + + @override + String get ramadanLabel => r'רמדאן'; + + @override + String get rowsPerPageDataPagerLabel => r'שורות לכל עמוד'; + + @override + String get safarLabel => r'ספאר'; + + @override + String get searchDataGridFilteringLabel => r'לחפש'; + + @override + String get selectAllDataGridFilteringLabel => r'בחר הכל'; + + @override + String get series => r'סִדרָה'; + + @override + String get shaabanLabel => r'שעבן'; + + @override + String get shawwalLabel => r'שווואל'; + + @override + String get shortDhualhiLabel => + r'דהו' + "'" + "'" + r'ל-ה'; + + @override + String get shortDhualqiLabel => + r'דהו' + "'" + r'ל-ק'; + + @override + String get shortJumada1Label => + r'ג' + "'" + r'אם. אני'; + + @override + String get shortJumada2Label => + r'ג' + "'" + r'אם. II'; + + @override + String get shortMuharramLabel => r'מוה.'; + + @override + String get shortRabi1Label => r'רבי. אני'; + + @override + String get shortRabi2Label => r'רבי. II'; + + @override + String get shortRajabLabel => + r'ראג' + "'" + r'.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'סאף.'; + + @override + String get shortShaabanLabel => r'שא.'; + + @override + String get shortShawwalLabel => r'שו.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'הצג שורות היכן'; + + @override + String get sortAToZDataGridFilteringLabel => + r'מיין מא' + "'" + r' עד ת' + "'"; + + @override + String get sortAndFilterDataGridFilteringLabel => r'מיון ומסנן'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'מיין מהגדול לקטן ביותר'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'מיין מהחדש לישן ביותר'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'מיין מהישן ביותר לחדש ביותר'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'מיין מהקטן לגדול ביותר'; + + @override + String get sortZToADataGridFilteringLabel => r'מיין Z עד A'; + + @override + String get textFiltersDataGridFilteringLabel => r'מסנני טקסט'; + + @override + String get todayLabel => r'היום'; + + @override + String get weeknumberLabel => r'שָׁבוּעַ'; +} + +/// The translations for Hindi (`hi`). +class SfLocalizationsHi extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsHi class + const SfLocalizationsHi({String localeName = 'hi'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'बाद में'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'के बाद या बराबर'; + + @override + String get allDayLabel => r'पूरे दिन'; + + @override + String get allowedViewDayLabel => r'दिन'; + + @override + String get allowedViewMonthLabel => r'महीना'; + + @override + String get allowedViewScheduleLabel => r'अनुसूची'; + + @override + String get allowedViewTimelineDayLabel => r'समयरेखा दिवस'; + + @override + String get allowedViewTimelineMonthLabel => r'समयरेखा महीना'; + + @override + String get allowedViewTimelineWeekLabel => r'समयरेखा सप्ताह'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'समयरेखा कार्य सप्ताह'; + + @override + String get allowedViewWeekLabel => r'सप्ताह'; + + @override + String get allowedViewWorkWeekLabel => r'कार्य सप्ताह'; + + @override + String get andDataGridFilteringLabel => r'और'; + + @override + String get beforeDataGridFilteringLabel => r'पहले'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'पहले या बराबर'; + + @override + String get beginsWithDataGridFilteringLabel => r'साथ शुरू होता है'; + + @override + String get cancelDataGridFilteringLabel => r'रद्द करना'; + + @override + String get clearFilterDataGridFilteringLabel => r'स्पष्ट निस्यंदक'; + + @override + String get containsDataGridFilteringLabel => r'में शामिल है'; + + @override + String get dateFiltersDataGridFilteringLabel => r'दिनांक फ़िल्टर'; + + @override + String get daySpanCountLabel => r'दिन'; + + @override + String get dhualhiLabel => r'धू अल-हिज्जाह'; + + @override + String get dhualqiLabel => r'धू अल-क़ीदाह'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => + r'से प्रारंभ नहीं होता है'; + + @override + String get doesNotContainDataGridFilteringLabel => r'शामिल नहीं है'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'से समाप्त नहीं होता है'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'बराबर नही हैं'; + + @override + String get emptyDataGridFilteringLabel => r'खाली'; + + @override + String get endsWithDataGridFilteringLabel => r'इसी के साथ समाप्त होता है'; + + @override + String get equalsDataGridFilteringLabel => r'बराबर'; + + @override + String get fromDataGridFilteringLabel => r'से'; + + @override + String get greaterThanDataGridFilteringLabel => r'से अधिक'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'से बड़ा या बराबर'; + + @override + String get jumada1Label => r'जुमादा अल-अव्वल'; + + @override + String get jumada2Label => r'जुमादा अल-थानी'; + + @override + String get lessThanDataGridFilteringLabel => r'से कम'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'इससे कम या इसके बराबर'; + + @override + String get muharramLabel => r'मुहर्रम'; + + @override + String get noEventsCalendarLabel => r'कोई आयोजन नहीं'; + + @override + String get noMatchesDataGridFilteringLabel => r'कोई मेल नहीं'; + + @override + String get noSelectedDateCalendarLabel => r'कोई चयनित तिथि नहीं'; + + @override + String get notEmptyDataGridFilteringLabel => r'खाली नहीं है'; + + @override + String get notNullDataGridFilteringLabel => r'शून्य नहीं'; + + @override + String get nullDataGridFilteringLabel => r'शून्य'; + + @override + String get numberFiltersDataGridFilteringLabel => r'नंबर फिल्टर'; + + @override + String get ofDataPagerLabel => r'का'; + + @override + String get okDataGridFilteringLabel => r'ठीक है'; + + @override + String get orDataGridFilteringLabel => r'या'; + + @override + String get pagesDataPagerLabel => r'पृष्ठों'; + + @override + String get passwordDialogContentLabel => + r'इस पीडीएफ फाइल को खोलने के लिए पासवर्ड डालें'; + + @override + String get passwordDialogHeaderTextLabel => r'पासवर्ड से सुरक्षित'; + + @override + String get passwordDialogHintTextLabel => r'पास वर्ड दर्ज करें'; + + @override + String get passwordDialogInvalidPasswordLabel => r'अवैध पासवर्ड'; + + @override + String get pdfBookmarksLabel => r'बुकमार्क'; + + @override + String get pdfEnterPageNumberLabel => r'पृष्ठ संख्या दर्ज करें'; + + @override + String get pdfGoToPageLabel => r'पृष्ठ पर जाओ'; + + @override + String get pdfHyperlinkContentLabel => r'क्या आप पृष्ठ खोलना चाहते हैं'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'रद्द करना'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'खोलना'; + + @override + String get pdfHyperlinkLabel => r'वेब पेज खोलें'; + + @override + String get pdfInvalidPageNumberLabel => r'कृपया सही अंक दर्ज करें'; + + @override + String get pdfNoBookmarksLabel => r'कोई बुकमार्क नहीं मिला'; + + @override + String get pdfPaginationDialogCancelLabel => r'रद्द करना'; + + @override + String get pdfPaginationDialogOkLabel => r'ठीक है'; + + @override + String get pdfPasswordDialogCancelLabel => r'रद्द करना'; + + @override + String get pdfPasswordDialogOpenLabel => r'खोलना'; + + @override + String get pdfScrollStatusOfLabel => r'का'; + + @override + String get pdfSignaturePadDialogClearLabel => r'साफ़ करें'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'अपना हस्ताक्षर बनाएं'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'कलम का रंग'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'सहेजें'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'प्रतिलिपि'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'हाइलाइट करें'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'लहरदार रेखा'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'स्ट्राइकथ्रू'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'रेखांकित करें'; + + @override + String get rabi1Label => r'रबी अल-अव्वल'; + + @override + String get rabi2Label => + r'रबी ' + "'" + r'अल-थानी'; + + @override + String get rajabLabel => r'रज्जब'; + + @override + String get ramadanLabel => r'रमजान'; + + @override + String get rowsPerPageDataPagerLabel => r'प्रति पृष्ठ पंक्तियाँ'; + + @override + String get safarLabel => r'सफ़र'; + + @override + String get searchDataGridFilteringLabel => r'खोज'; + + @override + String get selectAllDataGridFilteringLabel => r'सभी का चयन करे'; + + @override + String get series => r'श्रृंखला'; + + @override + String get shaabanLabel => r'शाबान'; + + @override + String get shawwalLabel => r'शावाल'; + + @override + String get shortDhualhiLabel => r'धुल-एच'; + + @override + String get shortDhualqiLabel => r'धुल-क्यू'; + + @override + String get shortJumada1Label => r'जम। मैं'; + + @override + String get shortJumada2Label => r'जम। द्वितीय'; + + @override + String get shortMuharramLabel => r'मुह।'; + + @override + String get shortRabi1Label => r'रबी। मैं'; + + @override + String get shortRabi2Label => r'रबी। द्वितीय'; + + @override + String get shortRajabLabel => r'राज।'; + + @override + String get shortRamadanLabel => r'टक्कर मारना।'; + + @override + String get shortSafarLabel => r'सफ।'; + + @override + String get shortShaabanLabel => r'शा.'; + + @override + String get shortShawwalLabel => r'शॉ।'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'पंक्तियाँ कहाँ दिखाएँ'; + + @override + String get sortAToZDataGridFilteringLabel => r'A से Z तक क्रमबद्ध करें'; + + @override + String get sortAndFilterDataGridFilteringLabel => + r'क्रमबद्ध करें और फ़िल्टर करें'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'सबसे बड़े को सबसे छोटे के क्रम में लगाएं'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'सबसे नए से सबसे पुराने के क्रम में लगाएं'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'सबसे पुराने से नए के क्रम में लगाएं'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'सबसे छोटे से बड़े के क्रम में लगाएं'; + + @override + String get sortZToADataGridFilteringLabel => r'Z से A तक क्रमबद्ध करें'; + + @override + String get textFiltersDataGridFilteringLabel => r'पाठ फ़िल्टर'; + + @override + String get todayLabel => r'आज'; + + @override + String get weeknumberLabel => r'सप्ताह'; +} + +/// The translations for Croatian (`hr`). +class SfLocalizationsHr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsHr class + const SfLocalizationsHr({String localeName = 'hr'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Nakon'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Nakon ili jednako'; + + @override + String get allDayLabel => r'Cijeli dan'; + + @override + String get allowedViewDayLabel => r'Dan'; + + @override + String get allowedViewMonthLabel => r'Mjesec'; + + @override + String get allowedViewScheduleLabel => r'Raspored'; + + @override + String get allowedViewTimelineDayLabel => r'Dan vremenske trake'; + + @override + String get allowedViewTimelineMonthLabel => r'Mjesec vremenske trake'; + + @override + String get allowedViewTimelineWeekLabel => r'Tjedan vremenske trake'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Vremenski okvir Radni tjedan'; + + @override + String get allowedViewWeekLabel => r'Tjedan'; + + @override + String get allowedViewWorkWeekLabel => r'Radni tjedan'; + + @override + String get andDataGridFilteringLabel => r'I'; + + @override + String get beforeDataGridFilteringLabel => r'Prije'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Prije ili jednako'; + + @override + String get beginsWithDataGridFilteringLabel => r'Počinje sa'; + + @override + String get cancelDataGridFilteringLabel => r'Otkazati'; + + @override + String get clearFilterDataGridFilteringLabel => r'Obriši filtar'; + + @override + String get containsDataGridFilteringLabel => r'Sadrži'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filtri datuma'; + + @override + String get daySpanCountLabel => r'Dan'; + + @override + String get dhualhiLabel => r'zul-hidždže'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Ne počinje s'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Ne sadrži'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Ne završava sa'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Nije jednako'; + + @override + String get emptyDataGridFilteringLabel => r'Prazan'; + + @override + String get endsWithDataGridFilteringLabel => r'Završava sa'; + + @override + String get equalsDataGridFilteringLabel => r'Jednako'; + + @override + String get fromDataGridFilteringLabel => r'Iz'; + + @override + String get greaterThanDataGridFilteringLabel => r'Veći od'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Veće ili jednako'; + + @override + String get jumada1Label => r'džumade el-evval'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Manje od'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Manje ili jednako'; + + @override + String get muharramLabel => r'Muharrema'; + + @override + String get noEventsCalendarLabel => r'Nema događaja'; + + @override + String get noMatchesDataGridFilteringLabel => r'Nema podudaranja'; + + @override + String get noSelectedDateCalendarLabel => r'Nema odabranog datuma'; + + @override + String get notEmptyDataGridFilteringLabel => r'Nije prazno'; + + @override + String get notNullDataGridFilteringLabel => r'Nije Null'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filtri brojeva'; + + @override + String get ofDataPagerLabel => r'od'; + + @override + String get okDataGridFilteringLabel => r'u redu'; + + @override + String get orDataGridFilteringLabel => r'Ili'; + + @override + String get pagesDataPagerLabel => r'stranice'; + + @override + String get passwordDialogContentLabel => + r'Unesite lozinku za otvaranje ove PDF datoteke'; + + @override + String get passwordDialogHeaderTextLabel => r'Lozinka zaštićena'; + + @override + String get passwordDialogHintTextLabel => r'Upišite lozinku'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Netočna zaporka'; + + @override + String get pdfBookmarksLabel => r'Oznake'; + + @override + String get pdfEnterPageNumberLabel => r'Unesite broj stranice'; + + @override + String get pdfGoToPageLabel => r'Idi na stranicu'; + + @override + String get pdfHyperlinkContentLabel => r'Želite li otvoriti stranicu na'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'OTKAZATI'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'OTVORENA'; + + @override + String get pdfHyperlinkLabel => r'Otvori web stranicu'; + + @override + String get pdfInvalidPageNumberLabel => r'Unesite ispravan broj'; + + @override + String get pdfNoBookmarksLabel => r'Nema pronađenih oznaka'; + + @override + String get pdfPaginationDialogCancelLabel => r'OTKAZATI'; + + @override + String get pdfPaginationDialogOkLabel => r'U redu'; + + @override + String get pdfPasswordDialogCancelLabel => r'OTKAZATI'; + + @override + String get pdfPasswordDialogOpenLabel => r'OTVORENA'; + + @override + String get pdfScrollStatusOfLabel => r'od'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Očistiti'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Nacrtajte svoj potpis'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Boja olovke'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Spremi'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopirati'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Istaknuti'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'vijugavo'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Precrtano'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Podvući'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Radžeb'; + + @override + String get ramadanLabel => r'Ramazan'; + + @override + String get rowsPerPageDataPagerLabel => r'Redovi po stranici'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'traži'; + + @override + String get selectAllDataGridFilteringLabel => r'Odaberi sve'; + + @override + String get series => r'Niz'; + + @override + String get shaabanLabel => + r'ša' + "'" + r'aban'; + + @override + String get shawwalLabel => r'ševval'; + + @override + String get shortDhualhiLabel => r'Zul-H'; + + @override + String get shortDhualqiLabel => r'Zul-Q'; + + @override + String get shortJumada1Label => r'Jum. ja'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. ja'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Radna memorija.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Pokaži retke gdje'; + + @override + String get sortAToZDataGridFilteringLabel => r'Poredaj od A do Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sortiraj i filtriraj'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Poredaj od najvećeg prema najmanjem'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Poredaj od najnovijeg do najstarijeg'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Poredaj od najstarijeg do najnovijeg'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Poredaj od najmanjeg do najvećeg'; + + @override + String get sortZToADataGridFilteringLabel => r'Poredaj od Z do A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Filtri teksta'; + + @override + String get todayLabel => r'Danas'; + + @override + String get weeknumberLabel => r'Tjedan'; +} + +/// The translations for Hungarian (`hu`). +class SfLocalizationsHu extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsHu class + const SfLocalizationsHu({String localeName = 'hu'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Után'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Után vagy egyenlő'; + + @override + String get allDayLabel => r'Egész nap'; + + @override + String get allowedViewDayLabel => r'Nap'; + + @override + String get allowedViewMonthLabel => r'Hónap'; + + @override + String get allowedViewScheduleLabel => r'Menetrend'; + + @override + String get allowedViewTimelineDayLabel => r'Idővonal nap'; + + @override + String get allowedViewTimelineMonthLabel => r'Idővonal hónap'; + + @override + String get allowedViewTimelineWeekLabel => r'Idővonal hét'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Idővonal munkahét'; + + @override + String get allowedViewWeekLabel => r'Hét'; + + @override + String get allowedViewWorkWeekLabel => r'Munkahét'; + + @override + String get andDataGridFilteringLabel => r'És'; + + @override + String get beforeDataGridFilteringLabel => r'Előtt'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Előtte vagy egyenlő'; + + @override + String get beginsWithDataGridFilteringLabel => r'Ezzel kezdődik'; + + @override + String get cancelDataGridFilteringLabel => r'Megszünteti'; + + @override + String get clearFilterDataGridFilteringLabel => r'Szűrő törlése'; + + @override + String get containsDataGridFilteringLabel => r'Tartalmaz'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Dátumszűrők'; + + @override + String get daySpanCountLabel => r'Nap'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Nem azzal kezdődik'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Nem tartalmaz'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Nem ér véget'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Nem egyenlő'; + + @override + String get emptyDataGridFilteringLabel => r'Üres'; + + @override + String get endsWithDataGridFilteringLabel => r'Végződik'; + + @override + String get equalsDataGridFilteringLabel => r'Egyenlő'; + + @override + String get fromDataGridFilteringLabel => r'Tól től'; + + @override + String get greaterThanDataGridFilteringLabel => r'Nagyobb, mint'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Nagyobb, mint vagy egyenlő'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Kevesebb, mint'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + r'Kevesebb, mint vagy egyenlő'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Nincsenek események'; + + @override + String get noMatchesDataGridFilteringLabel => r'Nincs egyezés'; + + @override + String get noSelectedDateCalendarLabel => r'Nincs kiválasztott dátum'; + + @override + String get notEmptyDataGridFilteringLabel => r'Nem üres'; + + @override + String get notNullDataGridFilteringLabel => r'Nem nulla'; + + @override + String get nullDataGridFilteringLabel => r'Nulla'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Számszűrők'; + + @override + String get ofDataPagerLabel => r'nak,-nek'; + + @override + String get okDataGridFilteringLabel => r'rendben'; + + @override + String get orDataGridFilteringLabel => r'Vagy'; + + @override + String get pagesDataPagerLabel => r'oldalakat'; + + @override + String get passwordDialogContentLabel => + r'Írja be a jelszót a PDF-fájl megnyitásához'; + + @override + String get passwordDialogHeaderTextLabel => r'Jelszóval védett'; + + @override + String get passwordDialogHintTextLabel => r'Írd be a jelszót'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Érvénytelen jelszó'; + + @override + String get pdfBookmarksLabel => r'Könyvjelzők'; + + @override + String get pdfEnterPageNumberLabel => r'Írja be az oldalszámot'; + + @override + String get pdfGoToPageLabel => r'Menj az oldalra'; + + @override + String get pdfHyperlinkContentLabel => r'Meg akarja nyitni az oldalt a címen'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'MEGSZÜNTETI'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'NYISD KI'; + + @override + String get pdfHyperlinkLabel => r'Weboldal megnyitása'; + + @override + String get pdfInvalidPageNumberLabel => + r'Kérjük, adjon meg egy érvényes számot'; + + @override + String get pdfNoBookmarksLabel => r'Nem találhatók könyvjelzők'; + + @override + String get pdfPaginationDialogCancelLabel => r'Megszakít'; + + @override + String get pdfPaginationDialogOkLabel => r'Rendben'; + + @override + String get pdfPasswordDialogCancelLabel => r'MEGSZÜNTETI'; + + @override + String get pdfPasswordDialogOpenLabel => r'NYISD KI'; + + @override + String get pdfScrollStatusOfLabel => r'nak,-nek'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Törlés'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Rajzolja le az aláírását'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Toll színe'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Mentés'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Másolás'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Kiemel'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Hullámos'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Áthúzott'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Aláhúzás'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadán'; + + @override + String get rowsPerPageDataPagerLabel => r'Sorok oldalanként'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Keresés'; + + @override + String get selectAllDataGridFilteringLabel => r'Mindet kiválaszt'; + + @override + String get series => r'Sorozat'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. én'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. én'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Sorok megjelenítése hol'; + + @override + String get sortAToZDataGridFilteringLabel => r'Rendezés A-tól Z-ig'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Rendezés és szűrés'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Rendezés a legnagyobbtól a legkisebbig'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Rendezés a legújabbtól a legrégebbig'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Rendezés a legrégebbitől a legújabbig'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Rendezés a legkisebbtől a legnagyobbig'; + + @override + String get sortZToADataGridFilteringLabel => r'Rendezés Z-ből A-ba'; + + @override + String get textFiltersDataGridFilteringLabel => r'Szövegszűrők'; + + @override + String get todayLabel => r'Ma'; + + @override + String get weeknumberLabel => r'Hét'; +} + +/// The translations for Armenian (`hy`). +class SfLocalizationsHy extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsHy class + const SfLocalizationsHy({String localeName = 'hy'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'հետո'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'հետո կամ հավասար'; + + @override + String get allDayLabel => r'Ամբողջ օրը'; + + @override + String get allowedViewDayLabel => r'Օր'; + + @override + String get allowedViewMonthLabel => r'Ամիս'; + + @override + String get allowedViewScheduleLabel => r'Ժամանակացույց'; + + @override + String get allowedViewTimelineDayLabel => r'Ժամանակացույցի օր'; + + @override + String get allowedViewTimelineMonthLabel => r'Ժամանակացույց ամիս'; + + @override + String get allowedViewTimelineWeekLabel => r'Ժամանակացույցի շաբաթ'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Ժամանակացույց Աշխատանքային շաբաթ'; + + @override + String get allowedViewWeekLabel => r'Շաբաթ'; + + @override + String get allowedViewWorkWeekLabel => r'Աշխատանքային շաբաթ'; + + @override + String get andDataGridFilteringLabel => r'Եվ'; + + @override + String get beforeDataGridFilteringLabel => r'Նախքան'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Նախքան կամ հավասարը'; + + @override + String get beginsWithDataGridFilteringLabel => r'Սկսվում է'; + + @override + String get cancelDataGridFilteringLabel => r'Չեղարկել'; + + @override + String get clearFilterDataGridFilteringLabel => r'Մաքրել զտիչը'; + + @override + String get containsDataGridFilteringLabel => r'Պարունակում է'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Ամսաթվի զտիչներ'; + + @override + String get daySpanCountLabel => r'Օր'; + + @override + String get dhualhiLabel => r'Դհու ալ-Հիջա'; + + @override + String get dhualqiLabel => r'Դհու ալ-Քիդա'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Չի սկսվում'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Չի պարունակում'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Չի ավարտվում'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Չի հավասարվում'; + + @override + String get emptyDataGridFilteringLabel => r'Դատարկ'; + + @override + String get endsWithDataGridFilteringLabel => r'Ավարտվում է'; + + @override + String get equalsDataGridFilteringLabel => r'Հավասար է'; + + @override + String get fromDataGridFilteringLabel => r'Սկսած'; + + @override + String get greaterThanDataGridFilteringLabel => r'Ավելի մեծ քան'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Ավելի մեծ, քան հավասար'; + + @override + String get jumada1Label => r'Ջումադա ալ-ավվալ'; + + @override + String get jumada2Label => r'Ջումադա ալ-Թանի'; + + @override + String get lessThanDataGridFilteringLabel => r'Ավելի քիչ քան'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Ավելի քիչ, քան հավասար'; + + @override + String get muharramLabel => r'Մուհարրամ'; + + @override + String get noEventsCalendarLabel => r'Միջոցառումներ չկան'; + + @override + String get noMatchesDataGridFilteringLabel => r'Համընկնումներ չկան'; + + @override + String get noSelectedDateCalendarLabel => r'Ընտրված ամսաթիվ չկա'; + + @override + String get notEmptyDataGridFilteringLabel => r'Ոչ դատարկ'; + + @override + String get notNullDataGridFilteringLabel => r'Ոչ զրոյական'; + + @override + String get nullDataGridFilteringLabel => r'Դատարկ'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Թվերի զտիչներ'; + + @override + String get ofDataPagerLabel => r'ի'; + + @override + String get okDataGridFilteringLabel => r'լավ'; + + @override + String get orDataGridFilteringLabel => r'Կամ'; + + @override + String get pagesDataPagerLabel => r'էջեր'; + + @override + String get passwordDialogContentLabel => + r'Մուտքագրեք գաղտնաբառը՝ այս PDF ֆայլը բացելու համար'; + + @override + String get passwordDialogHeaderTextLabel => r'Գաղտնաբառը պաշտպանված է'; + + @override + String get passwordDialogHintTextLabel => r'Մուտքագրեք գաղտնաբառը'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Անվավեր գաղտնաբառ'; + + @override + String get pdfBookmarksLabel => r'Էջանիշեր'; + + @override + String get pdfEnterPageNumberLabel => r'Մուտքագրեք էջի համարը'; + + @override + String get pdfGoToPageLabel => r'Գնալ դեպի էջ'; + + @override + String get pdfHyperlinkContentLabel => r'Ցանկանու՞մ եք բացել էջը'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ՉԵՂԱՐԿԵԼ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ԲԱՑ'; + + @override + String get pdfHyperlinkLabel => r'Բացեք վեբ էջը'; + + @override + String get pdfInvalidPageNumberLabel => r'Խնդրում ենք մուտքագրել վավեր համար'; + + @override + String get pdfNoBookmarksLabel => r'Էջանիշեր չեն գտնվել'; + + @override + String get pdfPaginationDialogCancelLabel => r'ՉԵՂԱՐԿԵԼ'; + + @override + String get pdfPaginationDialogOkLabel => r'Լավ է'; + + @override + String get pdfPasswordDialogCancelLabel => r'ՉԵՂԱՐԿԵԼ'; + + @override + String get pdfPasswordDialogOpenLabel => r'ԲԱՑ'; + + @override + String get pdfScrollStatusOfLabel => r'-ից'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Մաքրել'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Քո ստորագրությունը նկարիր'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Գրչի գույնը'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Պահպանել'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Պատճենել'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Ընդգծել'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Կրկնակի թեքումով'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Ականջած'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Ընդգծել'; + + @override + String get rabi1Label => r'Ռաբի ալ-ավալ'; + + @override + String get rabi2Label => r'Ռաբի ալ-Թանի'; + + @override + String get rajabLabel => r'Ռաջաբ'; + + @override + String get ramadanLabel => r'Ռամադան'; + + @override + String get rowsPerPageDataPagerLabel => r'Տողեր մեկ էջի համար'; + + @override + String get safarLabel => r'Սաֆար'; + + @override + String get searchDataGridFilteringLabel => r'Որոնում'; + + @override + String get selectAllDataGridFilteringLabel => r'Ընտրել բոլորը'; + + @override + String get series => r'Սերիա'; + + @override + String get shaabanLabel => r'Շաաբան'; + + @override + String get shawwalLabel => r'Շավվալ'; + + @override + String get shortDhualhiLabel => r'Դհուլ-Հ'; + + @override + String get shortDhualqiLabel => r'Դհուլ-Ք'; + + @override + String get shortJumada1Label => r'Jum. Ի'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Մուհ.'; + + @override + String get shortRabi1Label => r'Ռաբի. Ի'; + + @override + String get shortRabi2Label => r'Ռաբի. II'; + + @override + String get shortRajabLabel => r'Ռաջ.'; + + @override + String get shortRamadanLabel => r'Խոյ.'; + + @override + String get shortSafarLabel => r'Սաֆ.'; + + @override + String get shortShaabanLabel => r'Շա.'; + + @override + String get shortShawwalLabel => r'Շոու.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Ցույց տալ տողերը, որտեղ'; + + @override + String get sortAToZDataGridFilteringLabel => r'Տեսակավորել A-ից Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Տեսակավորել և զտել'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Տեսակավորել ամենամեծից փոքրից'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Տեսակավորել ամենանորից ամենահինը'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Տեսակավորել ամենահինը նորագույնին'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Տեսակավորել ամենափոքրից ամենամեծը'; + + @override + String get sortZToADataGridFilteringLabel => r'Տեսակավորել Z-ը A-ին'; + + @override + String get textFiltersDataGridFilteringLabel => r'Տեքստի զտիչներ'; + + @override + String get todayLabel => r'Այսօր'; + + @override + String get weeknumberLabel => r'Շաբաթ'; +} + +/// The translations for Indonesian (`id`). +class SfLocalizationsId extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsId class + const SfLocalizationsId({String localeName = 'id'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Setelah'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Setelah Atau Sama'; + + @override + String get allDayLabel => r'Sepanjang hari'; + + @override + String get allowedViewDayLabel => r'Hari'; + + @override + String get allowedViewMonthLabel => r'Bulan'; + + @override + String get allowedViewScheduleLabel => r'Jadwal'; + + @override + String get allowedViewTimelineDayLabel => r'Hari Garis Waktu'; + + @override + String get allowedViewTimelineMonthLabel => r'Bulan Garis Waktu'; + + @override + String get allowedViewTimelineWeekLabel => r'Minggu Garis Waktu'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Minggu Kerja Timeline'; + + @override + String get allowedViewWeekLabel => r'Pekan'; + + @override + String get allowedViewWorkWeekLabel => r'Minggu Kerja'; + + @override + String get andDataGridFilteringLabel => r'Dan'; + + @override + String get beforeDataGridFilteringLabel => r'Sebelum'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Sebelum Atau Sama'; + + @override + String get beginsWithDataGridFilteringLabel => r'Dimulai dengan'; + + @override + String get cancelDataGridFilteringLabel => r'Membatalkan'; + + @override + String get clearFilterDataGridFilteringLabel => r'Hapus Filter'; + + @override + String get containsDataGridFilteringLabel => r'Mengandung'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filter Tanggal'; + + @override + String get daySpanCountLabel => r'Hari'; + + @override + String get dhualhiLabel => r'Dzulhijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qidah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Tidak Dimulai Dengan'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Tidak mengandung'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Tidak Berakhir Dengan'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Tidak sama'; + + @override + String get emptyDataGridFilteringLabel => r'Kosong'; + + @override + String get endsWithDataGridFilteringLabel => r'Berakhir dengan'; + + @override + String get equalsDataGridFilteringLabel => r'Sama dengan'; + + @override + String get fromDataGridFilteringLabel => r'Dari'; + + @override + String get greaterThanDataGridFilteringLabel => r'Lebih besar dari'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Lebih Besar Dari Atau Sama Dengan'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Kurang dari'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Kurang Dari Atau Sama'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Tidak ada acara'; + + @override + String get noMatchesDataGridFilteringLabel => r'Tidak ada yang cocok'; + + @override + String get noSelectedDateCalendarLabel => r'Tidak ada tanggal yang dipilih'; + + @override + String get notEmptyDataGridFilteringLabel => r'Tidak kosong'; + + @override + String get notNullDataGridFilteringLabel => r'Bukan Nol'; + + @override + String get nullDataGridFilteringLabel => r'Batal'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filter Angka'; + + @override + String get ofDataPagerLabel => r'dari'; + + @override + String get okDataGridFilteringLabel => r'Oke'; + + @override + String get orDataGridFilteringLabel => r'Atau'; + + @override + String get pagesDataPagerLabel => r'halaman'; + + @override + String get passwordDialogContentLabel => + r'Masukkan kata sandi untuk membuka file PDF ini'; + + @override + String get passwordDialogHeaderTextLabel => r'Dilindungi Kata Sandi'; + + @override + String get passwordDialogHintTextLabel => r'Masukkan kata kunci'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Kata sandi tidak valid'; + + @override + String get pdfBookmarksLabel => r'Bookmark'; + + @override + String get pdfEnterPageNumberLabel => r'Masukkan nomor halaman'; + + @override + String get pdfGoToPageLabel => r'Buka halaman'; + + @override + String get pdfHyperlinkContentLabel => + r'Apakah Anda ingin membuka halaman di'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'MEMBATALKAN'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'MEMBUKA'; + + @override + String get pdfHyperlinkLabel => r'Buka Halaman Web'; + + @override + String get pdfInvalidPageNumberLabel => r'Masukkan nomor yang valid'; + + @override + String get pdfNoBookmarksLabel => r'Tidak ada bookmark yang ditemukan'; + + @override + String get pdfPaginationDialogCancelLabel => r'Batal'; + + @override + String get pdfPaginationDialogOkLabel => r'Oke'; + + @override + String get pdfPasswordDialogCancelLabel => r'MEMBATALKAN'; + + @override + String get pdfPasswordDialogOpenLabel => r'MEMBUKA'; + + @override + String get pdfScrollStatusOfLabel => r'dari'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Bersihkan'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Gambar tanda tangan Anda'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Warna Pena'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Simpan'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Salin'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Sorot'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Bergelombang'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Dicoret'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Menggarisbawahi'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Baris per halaman'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Mencari'; + + @override + String get selectAllDataGridFilteringLabel => r'Pilih Semua'; + + @override + String get series => r'Seri'; + + @override + String get shaabanLabel => + r'Sya' + "'" + r'ban'; + + @override + String get shawwalLabel => r'Syawal'; + + @override + String get shortDhualhiLabel => r'Dzul-H'; + + @override + String get shortDhualqiLabel => r'Dzul-Q'; + + @override + String get shortJumada1Label => r'Jum. Saya'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Saya'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Aman'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Tampilkan baris di mana'; + + @override + String get sortAToZDataGridFilteringLabel => r'Urutkan A Sampai Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sortir dan Filter'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Urutkan Terbesar Hingga Terkecil'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Urutkan Terbaru Ke Terlama'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Urutkan Terlama Ke Terbaru'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Urutkan Terkecil Hingga Terbesar'; + + @override + String get sortZToADataGridFilteringLabel => r'Urutkan Z Ke A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Filter Teks'; + + @override + String get todayLabel => r'Hari ini'; + + @override + String get weeknumberLabel => r'Pekan'; +} + +/// The translations for Icelandic (`is`). +class SfLocalizationsIs extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsIs class + const SfLocalizationsIs({String localeName = 'is'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Eftir'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Eftir Or Equal'; + + @override + String get allDayLabel => r'Allan daginn'; + + @override + String get allowedViewDayLabel => r'Dagur'; + + @override + String get allowedViewMonthLabel => r'Mánuður'; + + @override + String get allowedViewScheduleLabel => r'Dagskrá'; + + @override + String get allowedViewTimelineDayLabel => r'Tímalínudagur'; + + @override + String get allowedViewTimelineMonthLabel => r'Tímalína mánuður'; + + @override + String get allowedViewTimelineWeekLabel => r'Tímalínuvika'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Tímalína vinnuvika'; + + @override + String get allowedViewWeekLabel => r'Vika'; + + @override + String get allowedViewWorkWeekLabel => r'Vinnuvika'; + + @override + String get andDataGridFilteringLabel => r'Og'; + + @override + String get beforeDataGridFilteringLabel => r'Áður'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Áður eða jafnt'; + + @override + String get beginsWithDataGridFilteringLabel => r'Byrjar með'; + + @override + String get cancelDataGridFilteringLabel => r'Hætta við'; + + @override + String get clearFilterDataGridFilteringLabel => r'Hreinsaðu síu'; + + @override + String get containsDataGridFilteringLabel => r'Inniheldur'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Dagsetningarsíur'; + + @override + String get daySpanCountLabel => r'Dagur'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Byrjar ekki á'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Inniheldur ekki'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Endar ekki með'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Er ekki jafn'; + + @override + String get emptyDataGridFilteringLabel => r'Tómt'; + + @override + String get endsWithDataGridFilteringLabel => r'Endar Með'; + + @override + String get equalsDataGridFilteringLabel => r'Jafnt'; + + @override + String get fromDataGridFilteringLabel => r'Frá'; + + @override + String get greaterThanDataGridFilteringLabel => r'Meiri en'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Stærri en eða jafn'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Minna en'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Minna en eða jafnt'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Engir viðburðir'; + + @override + String get noMatchesDataGridFilteringLabel => r'Engar samsvörun'; + + @override + String get noSelectedDateCalendarLabel => r'Engin dagsetning valin'; + + @override + String get notEmptyDataGridFilteringLabel => r'Ekki tómt'; + + @override + String get notNullDataGridFilteringLabel => r'Ekki Null'; + + @override + String get nullDataGridFilteringLabel => r'Núll'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Númerasíur'; + + @override + String get ofDataPagerLabel => r'af'; + + @override + String get okDataGridFilteringLabel => r'Allt í lagi'; + + @override + String get orDataGridFilteringLabel => r'Eða'; + + @override + String get pagesDataPagerLabel => r'síður'; + + @override + String get passwordDialogContentLabel => + r'Sláðu inn lykilorðið til að opna þessa PDF-skrá'; + + @override + String get passwordDialogHeaderTextLabel => r'Lykilorð varið'; + + @override + String get passwordDialogHintTextLabel => r'Sláðu inn lykilorð'; + + @override + String get passwordDialogInvalidPasswordLabel => r'ógilt lykilorð'; + + @override + String get pdfBookmarksLabel => r'Bókamerki'; + + @override + String get pdfEnterPageNumberLabel => r'Sláðu inn blaðsíðunúmer'; + + @override + String get pdfGoToPageLabel => r'Farðu á síðu'; + + @override + String get pdfHyperlinkContentLabel => r'Viltu opna síðuna á'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'HÆTTA við'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'OPNA'; + + @override + String get pdfHyperlinkLabel => r'Opnaðu vefsíðu'; + + @override + String get pdfInvalidPageNumberLabel => r'Vinsamlegast sláðu inn gilt númer'; + + @override + String get pdfNoBookmarksLabel => r'Engin bókamerki fundust'; + + @override + String get pdfPaginationDialogCancelLabel => r'Hætta við'; + + @override + String get pdfPaginationDialogOkLabel => r'Allt í lagi'; + + @override + String get pdfPasswordDialogCancelLabel => r'HÆTTA við'; + + @override + String get pdfPasswordDialogOpenLabel => r'Opna'; + + @override + String get pdfScrollStatusOfLabel => r'af'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Hreinsa'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Teiknaðu undirskriftina þína'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Pennalitur'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Vista'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Afrita'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Merkja'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Skýrt'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Ástrikun'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Undirstrika'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Raðir á síðu'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Leita'; + + @override + String get selectAllDataGridFilteringLabel => r'Velja allt'; + + @override + String get series => r'Röð'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. ég'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Múh.'; + + @override + String get shortRabi1Label => r'Rabi. ég'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Vinnsluminni.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Sýna línur hvar'; + + @override + String get sortAToZDataGridFilteringLabel => r'Raða A Til Ö'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Raða og sía'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Raða Stærsta Til Minnsta'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Raða nýjustu í elstu'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Raða elstu í nýjustu'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Raða minnstu í stærstu'; + + @override + String get sortZToADataGridFilteringLabel => r'Raða Z Til A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Textasíur'; + + @override + String get todayLabel => r'Í dag'; + + @override + String get weeknumberLabel => r'Vika'; +} + +/// The translations for Italian (`it`). +class SfLocalizationsIt extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsIt class + const SfLocalizationsIt({String localeName = 'it'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Dopo'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Dopo o uguale'; + + @override + String get allDayLabel => r'Tutto il giorno'; + + @override + String get allowedViewDayLabel => r'Giorno'; + + @override + String get allowedViewMonthLabel => r'Mese'; + + @override + String get allowedViewScheduleLabel => r'Programma'; + + @override + String get allowedViewTimelineDayLabel => r'Giorno della cronologia'; + + @override + String get allowedViewTimelineMonthLabel => r'Mese cronologico'; + + @override + String get allowedViewTimelineWeekLabel => r'Settimana della cronologia'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Cronologia della settimana lavorativa'; + + @override + String get allowedViewWeekLabel => r'Settimana'; + + @override + String get allowedViewWorkWeekLabel => r'Settimana di lavoro'; + + @override + String get andDataGridFilteringLabel => r'E'; + + @override + String get beforeDataGridFilteringLabel => r'Prima'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Prima o uguale'; + + @override + String get beginsWithDataGridFilteringLabel => r'Inizia con'; + + @override + String get cancelDataGridFilteringLabel => r'Annulla'; + + @override + String get clearFilterDataGridFilteringLabel => r'Filtro pulito'; + + @override + String get containsDataGridFilteringLabel => r'Contiene'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filtri data'; + + @override + String get daySpanCountLabel => r'Giorno'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => r'Dhu al-Qidah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Non inizia con'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Non contiene'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Non finisce con'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Non è uguale'; + + @override + String get emptyDataGridFilteringLabel => r'Vuoto'; + + @override + String get endsWithDataGridFilteringLabel => r'Finisce con'; + + @override + String get equalsDataGridFilteringLabel => r'È uguale a'; + + @override + String get fromDataGridFilteringLabel => r'Da'; + + @override + String get greaterThanDataGridFilteringLabel => r'Più grande di'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Maggiore o uguale'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Meno di'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Minore o uguale'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Nessun evento'; + + @override + String get noMatchesDataGridFilteringLabel => r'Nessuna corrispondenza'; + + @override + String get noSelectedDateCalendarLabel => r'Nessuna data selezionata'; + + @override + String get notEmptyDataGridFilteringLabel => r'Non vuoto'; + + @override + String get notNullDataGridFilteringLabel => r'Non nullo'; + + @override + String get nullDataGridFilteringLabel => r'Nullo'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filtri numerici'; + + @override + String get ofDataPagerLabel => r'di'; + + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'O'; + + @override + String get pagesDataPagerLabel => r'pagine'; + + @override + String get passwordDialogContentLabel => + r'Inserisci la password per aprire questo file PDF'; + + @override + String get passwordDialogHeaderTextLabel => r'Protetto da password'; + + @override + String get passwordDialogHintTextLabel => r'Inserire la password'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Password non valida'; + + @override + String get pdfBookmarksLabel => r'Segnalibri'; + + @override + String get pdfEnterPageNumberLabel => r'Inserisci il numero di pagina'; + + @override + String get pdfGoToPageLabel => r'Vai alla pagina'; + + @override + String get pdfHyperlinkContentLabel => r'Vuoi aprire la pagina in'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ANNULLA'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'APRIRE'; + + @override + String get pdfHyperlinkLabel => r'Apri pagina web'; + + @override + String get pdfInvalidPageNumberLabel => + r'Per favore, inserire un numero valido'; + + @override + String get pdfNoBookmarksLabel => r'Nessun segnalibro trovato'; + + @override + String get pdfPaginationDialogCancelLabel => r'ANNULLA'; + + @override + String get pdfPaginationDialogOkLabel => r'OK'; + + @override + String get pdfPasswordDialogCancelLabel => r'ANNULLA'; + + @override + String get pdfPasswordDialogOpenLabel => r'APRIRE'; + + @override + String get pdfScrollStatusOfLabel => r'di'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Pulisci'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Disegna la tua firma'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Colore penna'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'SALVA'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'copia'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Evidenzia'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Ondulato'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Barrato'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Sottolineare'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Righe per pagina'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Ricerca'; + + @override + String get selectAllDataGridFilteringLabel => r'Seleziona tutto'; + + @override + String get series => r'Serie'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. io'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabbi. io'; + + @override + String get shortRabi2Label => r'Rabbi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ariete.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Mostra righe dove'; + + @override + String get sortAToZDataGridFilteringLabel => r'Ordina dalla A alla Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Ordina e filtra'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ordina dal più grande al più piccolo'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Ordina dal più recente al più vecchio'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Ordina dal più vecchio al più recente'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ordina dal più piccolo al più grande'; + + @override + String get sortZToADataGridFilteringLabel => r'Ordina dalla Z alla A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Filtri di testo'; + + @override + String get todayLabel => r'In data odierna'; + + @override + String get weeknumberLabel => r'Settimana'; +} + +/// The translations for Japanese (`ja`). +class SfLocalizationsJa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsJa class + const SfLocalizationsJa({String localeName = 'ja'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'後'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'後または等しい'; + + @override + String get allDayLabel => r'一日中'; + + @override + String get allowedViewDayLabel => r'日'; + + @override + String get allowedViewMonthLabel => r'月'; + + @override + String get allowedViewScheduleLabel => r'スケジュール'; + + @override + String get allowedViewTimelineDayLabel => r'タイムラインの日'; + + @override + String get allowedViewTimelineMonthLabel => r'タイムライン月'; + + @override + String get allowedViewTimelineWeekLabel => r'タイムライン 週'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'タイムライン 作業週'; + + @override + String get allowedViewWeekLabel => r'週'; + + @override + String get allowedViewWorkWeekLabel => r'勤務週'; + + @override + String get andDataGridFilteringLabel => r'と'; + + @override + String get beforeDataGridFilteringLabel => r'前'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'前または等しい'; + + @override + String get beginsWithDataGridFilteringLabel => r'次で始まる'; + + @override + String get cancelDataGridFilteringLabel => r'キャンセル'; + + @override + String get clearFilterDataGridFilteringLabel => r'フィルターをクリア'; + + @override + String get containsDataGridFilteringLabel => r'含む'; + + @override + String get dateFiltersDataGridFilteringLabel => r'日付フィルター'; + + @override + String get daySpanCountLabel => r'日'; + + @override + String get dhualhiLabel => r'ドゥ アル ヒジャ'; + + @override + String get dhualqiLabel => r'ドゥ アルキダ'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'次で始まらない'; + + @override + String get doesNotContainDataGridFilteringLabel => r'含まない'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'次で終わらない'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'等しくない'; + + @override + String get emptyDataGridFilteringLabel => r'空の'; + + @override + String get endsWithDataGridFilteringLabel => r'で終わる'; + + @override + String get equalsDataGridFilteringLabel => r'等しい'; + + @override + String get fromDataGridFilteringLabel => r'から'; + + @override + String get greaterThanDataGridFilteringLabel => r'より大きい'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'以上'; + + @override + String get jumada1Label => r'ジュマーダ・アル・アワル'; + + @override + String get jumada2Label => r'ジュマーダ アルタニ'; + + @override + String get lessThanDataGridFilteringLabel => r'未満'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'以下'; + + @override + String get muharramLabel => r'ムハラム'; + + @override + String get noEventsCalendarLabel => r'イベントなし'; + + @override + String get noMatchesDataGridFilteringLabel => r'一致するものはありません'; + + @override + String get noSelectedDateCalendarLabel => r'日付が選択されていません'; + + @override + String get notEmptyDataGridFilteringLabel => r'空ではない'; + + @override + String get notNullDataGridFilteringLabel => r'ヌルではない'; + + @override + String get nullDataGridFilteringLabel => r'ヌル'; + + @override + String get numberFiltersDataGridFilteringLabel => r'数値フィルター'; + + @override + String get ofDataPagerLabel => r'の'; + + @override + String get okDataGridFilteringLabel => r'わかった'; + + @override + String get orDataGridFilteringLabel => r'または'; + + @override + String get pagesDataPagerLabel => r'ページ'; + + @override + String get passwordDialogContentLabel => r'このPDFファイルを開くにはパスワードを入力してください'; + + @override + String get passwordDialogHeaderTextLabel => r'パスワードで保護'; + + @override + String get passwordDialogHintTextLabel => r'パスワードを入力'; + + @override + String get passwordDialogInvalidPasswordLabel => r'無効なパスワード'; + + @override + String get pdfBookmarksLabel => r'ブックマーク'; + + @override + String get pdfEnterPageNumberLabel => r'ページ番号を入力してください'; + + @override + String get pdfGoToPageLabel => r'ページに移動'; + + @override + String get pdfHyperlinkContentLabel => r'でページを開きますか?'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'キャンセル'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'開いた'; + + @override + String get pdfHyperlinkLabel => r'ウェブページを開く'; + + @override + String get pdfInvalidPageNumberLabel => r'有効な数値を入力してください'; + + @override + String get pdfNoBookmarksLabel => r'ブックマークが見つかりません'; + + @override + String get pdfPaginationDialogCancelLabel => r'キャンセル'; + + @override + String get pdfPaginationDialogOkLabel => r'わかった'; + + @override + String get pdfPasswordDialogCancelLabel => r'キャンセル'; + + @override + String get pdfPasswordDialogOpenLabel => r'開く'; + + @override + String get pdfScrollStatusOfLabel => r'の'; + + @override + String get pdfSignaturePadDialogClearLabel => r'クリア'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'サインを描いてください'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'ペンの色'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'保存'; + + @override + String get rabi1Label => r'ラビアルアワル'; + + @override + String get rabi2Label => r'ラビアルタニ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'コピー'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'ハイライト'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'波打つ'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'取り消し線'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'下線'; + + @override + String get rajabLabel => r'ラジャブ'; + + @override + String get ramadanLabel => r'ラマダン'; + + @override + String get rowsPerPageDataPagerLabel => r'ページあたりの行数'; + + @override + String get safarLabel => r'サファル'; + + @override + String get searchDataGridFilteringLabel => r'探す'; + + @override + String get selectAllDataGridFilteringLabel => r'すべて選択'; + + @override + String get series => r'シリーズ'; + + @override + String get shaabanLabel => r'シャアバン'; + + @override + String get shawwalLabel => r'シャワル'; + + @override + String get shortDhualhiLabel => r'デュル-H'; + + @override + String get shortDhualqiLabel => r'デュルQ'; + + @override + String get shortJumada1Label => r'ジャム。私'; + + @override + String get shortJumada2Label => r'ジャム。 Ⅱ'; + + @override + String get shortMuharramLabel => r'うーん。'; + + @override + String get shortRabi1Label => r'ラビ。私'; + + @override + String get shortRabi2Label => r'ラビ。 Ⅱ'; + + @override + String get shortRajabLabel => r'ラージ。'; + + @override + String get shortRamadanLabel => r'RAM。'; + + @override + String get shortSafarLabel => r'サフ。'; + + @override + String get shortShaabanLabel => r'しゃ。'; + + @override + String get shortShawwalLabel => r'ショー。'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'行を表示'; + + @override + String get sortAToZDataGridFilteringLabel => r'A から Z に並べ替える'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'並べ替えとフィルター'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'大きいものから小さいものへ並べ替え'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => r'新しいものから古いものへ並べ替え'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => r'古いものから新しいものへ並べ替え'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => r'最小から最大への並べ替え'; + + @override + String get sortZToADataGridFilteringLabel => r'Z から A に並べ替え'; + + @override + String get textFiltersDataGridFilteringLabel => r'テキスト フィルター'; + + @override + String get todayLabel => r'今日'; + + @override + String get weeknumberLabel => r'週'; +} + +/// The translations for Georgian (`ka`). +class SfLocalizationsKa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsKa class + const SfLocalizationsKa({String localeName = 'ka'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'შემდეგ'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'შემდეგ ან თანაბარი'; + + @override + String get allDayLabel => r'Მთელი დღე'; + + @override + String get allowedViewDayLabel => r'Დღის'; + + @override + String get allowedViewMonthLabel => r'თვე'; + + @override + String get allowedViewScheduleLabel => r'განრიგი'; + + @override + String get allowedViewTimelineDayLabel => r'ვადების დღე'; + + @override + String get allowedViewTimelineMonthLabel => r'ვადები თვე'; + + @override + String get allowedViewTimelineWeekLabel => r'ვადები კვირა'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'ვადები სამუშაო კვირა'; + + @override + String get allowedViewWeekLabel => r'კვირა'; + + @override + String get allowedViewWorkWeekLabel => r'Სამუშაო კვირა'; + + @override + String get andDataGridFilteringLabel => r'და'; + + @override + String get beforeDataGridFilteringLabel => r'მანამდე'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'ადრე ან თანაბარი'; + + @override + String get beginsWithDataGridFilteringLabel => r'Იწყება'; + + @override + String get cancelDataGridFilteringLabel => r'გაუქმება'; + + @override + String get clearFilterDataGridFilteringLabel => r'ფილტრის გასუფთავება'; + + @override + String get containsDataGridFilteringLabel => r'შეიცავს'; + + @override + String get dateFiltersDataGridFilteringLabel => r'თარიღის ფილტრები'; + + @override + String get daySpanCountLabel => r'Დღის'; + + @override + String get dhualhiLabel => r'დჰულ-ჰიჯა'; + + @override + String get dhualqiLabel => r'დჰულ-ქიდა'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'არ იწყება'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Არ შეიცავს'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'არ მთავრდება'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'არ უდრის'; + + @override + String get emptyDataGridFilteringLabel => r'ცარიელი'; + + @override + String get endsWithDataGridFilteringLabel => r'მთავრდება'; + + @override + String get equalsDataGridFilteringLabel => r'უდრის'; + + @override + String get fromDataGridFilteringLabel => r'დან'; + + @override + String get greaterThanDataGridFilteringLabel => r'Მეტია, ვიდრე'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'მეტი ან თანაბარი'; + + @override + String get jumada1Label => r'ჯუმადა ალ-ავვალი'; + + @override + String get jumada2Label => r'ჯუმადა ალ-თანი'; + + @override + String get lessThanDataGridFilteringLabel => r'Ნაკლები ვიდრე'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'ნაკლები ან თანაბარი'; + + @override + String get muharramLabel => r'მუჰარამი'; + + @override + String get noEventsCalendarLabel => r'არანაირი მოვლენა'; + + @override + String get noMatchesDataGridFilteringLabel => r'არანაირი მატჩი'; + + @override + String get noSelectedDateCalendarLabel => r'არჩეული თარიღი არ არის'; + + @override + String get notEmptyDataGridFilteringLabel => r'Არ არის ცარიელი'; + + @override + String get notNullDataGridFilteringLabel => r'არა ნულოვანი'; + + @override + String get nullDataGridFilteringLabel => r'ნულოვანი'; + + @override + String get numberFiltersDataGridFilteringLabel => r'რიცხვების ფილტრები'; + + @override + String get ofDataPagerLabel => r'დან'; + + @override + String get okDataGridFilteringLabel => r'კარგი'; + + @override + String get orDataGridFilteringLabel => r'ან'; + + @override + String get pagesDataPagerLabel => r'გვერდები'; + + @override + String get passwordDialogContentLabel => + r'შეიყვანეთ პაროლი ამ PDF ფაილის გასახსნელად'; + + @override + String get passwordDialogHeaderTextLabel => r'პაროლით დაცული'; + + @override + String get passwordDialogHintTextLabel => r'შეიყვანეთ პაროლი'; + + @override + String get passwordDialogInvalidPasswordLabel => r'არასწორი პაროლი'; + + @override + String get pdfBookmarksLabel => r'სანიშნეები'; + + @override + String get pdfEnterPageNumberLabel => r'შეიყვანეთ გვერდის ნომერი'; + + @override + String get pdfGoToPageLabel => r'Გადადით გვერდზე'; + + @override + String get pdfHyperlinkContentLabel => r'გსურთ გახსნათ გვერდი'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'გაუქმება'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'გახსენით'; + + @override + String get pdfHyperlinkLabel => r'გახსენით ვებ გვერდი'; + + @override + String get pdfInvalidPageNumberLabel => r'გთხოვთ, შეიყვანოთ სწორი ნომერი'; + + @override + String get pdfNoBookmarksLabel => r'სანიშნეები ვერ მოიძებნა'; + + @override + String get pdfPaginationDialogCancelLabel => r'გაუქმება'; + + @override + String get pdfPaginationDialogOkLabel => r'კარგი'; + + @override + String get pdfPasswordDialogCancelLabel => r'გაუქმება'; + + @override + String get pdfPasswordDialogOpenLabel => r'გახსენით'; + + @override + String get pdfScrollStatusOfLabel => r'დან'; + + @override + String get pdfSignaturePadDialogClearLabel => r'გასუფთავება'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'დახატე შენი ხელმოწერა'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'კალმის ფერი'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'შენახვა'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'კოპირება'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'მონიშნეთ'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'ცბიერად'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'გადახაზვა'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'ხაზი გაუსვით'; + + @override + String get rabi1Label => r'რაბი ალ-ავვალი'; + + @override + String get rabi2Label => r'რაბი ალ-თანი'; + + @override + String get rajabLabel => r'რაჯაბი'; + + @override + String get ramadanLabel => r'რამადანი'; + + @override + String get rowsPerPageDataPagerLabel => r'რიგები თითო გვერდზე'; + + @override + String get safarLabel => r'საფარი'; + + @override + String get searchDataGridFilteringLabel => r'ძიება'; + + @override + String get selectAllDataGridFilteringLabel => r'Მონიშნე ყველა'; + + @override + String get series => r'სერიალი'; + + @override + String get shaabanLabel => r'შააბანი'; + + @override + String get shawwalLabel => r'შავვალი'; + + @override + String get shortDhualhiLabel => r'დულ-ჰ'; + + @override + String get shortDhualqiLabel => r'დულ-ქ'; + + @override + String get shortJumada1Label => r'ჯუმ. მე'; + + @override + String get shortJumada2Label => r'ჯუმ. II'; + + @override + String get shortMuharramLabel => r'მუჰ.'; + + @override + String get shortRabi1Label => r'რაბი. მე'; + + @override + String get shortRabi2Label => r'რაბი. II'; + + @override + String get shortRajabLabel => r'რაჯ.'; + + @override + String get shortRamadanLabel => r'ვერძი.'; + + @override + String get shortSafarLabel => r'საფ.'; + + @override + String get shortShaabanLabel => r'შა.'; + + @override + String get shortShawwalLabel => r'შოუ.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'აჩვენე რიგები სად'; + + @override + String get sortAToZDataGridFilteringLabel => r'დალაგება A-დან Z-მდე'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'დალაგება და გაფილტვრა'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'სორტირება დიდიდან პატარამდე'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'დალაგება უახლესიდან ძველზე'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'დალაგება უძველესიდან უახლესზე'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'დახარისხება პატარადან დიდამდე'; + + @override + String get sortZToADataGridFilteringLabel => r'დახარისხება Z-ზე A'; + + @override + String get textFiltersDataGridFilteringLabel => r'ტექსტის ფილტრები'; + + @override + String get todayLabel => r'დღეს'; + + @override + String get weeknumberLabel => r'კვირა'; +} + +/// The translations for Kazakh (`kk`). +class SfLocalizationsKk extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsKk class + const SfLocalizationsKk({String localeName = 'kk'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Кейін'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Кейін немесе Тең'; + + @override + String get allDayLabel => r'Күні бойы'; + + @override + String get allowedViewDayLabel => r'Күн'; + + @override + String get allowedViewMonthLabel => r'Ай'; + + @override + String get allowedViewScheduleLabel => r'Кесте'; + + @override + String get allowedViewTimelineDayLabel => r'Хронология күні'; + + @override + String get allowedViewTimelineMonthLabel => r'Хронология айы'; + + @override + String get allowedViewTimelineWeekLabel => r'Хронология аптасы'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Хронологиялық жұмыс аптасы'; + + @override + String get allowedViewWeekLabel => r'Апта'; + + @override + String get allowedViewWorkWeekLabel => r'Жұмыс аптасы'; + + @override + String get andDataGridFilteringLabel => r'Және'; + + @override + String get beforeDataGridFilteringLabel => r'Бұрын'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Бұрын немесе Тең'; + + @override + String get beginsWithDataGridFilteringLabel => r'-мен басталады'; + + @override + String get cancelDataGridFilteringLabel => r'Болдырмау'; + + @override + String get clearFilterDataGridFilteringLabel => r'Сүзгіні тазалау'; + + @override + String get containsDataGridFilteringLabel => r'Құрамында'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Күн сүзгілері'; + + @override + String get daySpanCountLabel => r'Күн'; + + @override + String get dhualhiLabel => r'Зул-Хиджа'; + + @override + String get dhualqiLabel => r'Зул-Қида'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Бастамайды'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Құрамында жоқ'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'-мен бітпейді'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Тең емес'; + + @override + String get emptyDataGridFilteringLabel => r'Бос'; + + @override + String get endsWithDataGridFilteringLabel => r'Аяқталады'; + + @override + String get equalsDataGridFilteringLabel => r'Тең'; + + @override + String get fromDataGridFilteringLabel => r'бастап'; + + @override + String get greaterThanDataGridFilteringLabel => r'Үлкенірек'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Үлкен немесе тең'; + + @override + String get jumada1Label => r'Жұмада әл-әууәл'; + + @override + String get jumada2Label => r'Жұмада әл-Тани'; + + @override + String get lessThanDataGridFilteringLabel => r'Одан азырақ'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Кіші немесе Тең'; + + @override + String get muharramLabel => r'Мухаррам'; + + @override + String get noEventsCalendarLabel => r'Оқиғалар жоқ'; + + @override + String get noMatchesDataGridFilteringLabel => r'Сәйкестік жоқ'; + + @override + String get noSelectedDateCalendarLabel => r'Таңдалған күн жоқ'; + + @override + String get notEmptyDataGridFilteringLabel => r'Бос емес'; + + @override + String get notNullDataGridFilteringLabel => r'Нөл емес'; + + @override + String get nullDataGridFilteringLabel => r'Нөл'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Сандық сүзгілер'; + + @override + String get ofDataPagerLabel => r'ның'; + + @override + String get okDataGridFilteringLabel => r'ЖАРАЙДЫ МА'; + + @override + String get orDataGridFilteringLabel => r'Немесе'; + + @override + String get pagesDataPagerLabel => r'беттер'; + + @override + String get passwordDialogContentLabel => + r'Осы PDF файлын ашу үшін құпия сөзді енгізіңіз'; + + @override + String get passwordDialogHeaderTextLabel => r'Құпия сөзбен қорғалған'; + + @override + String get passwordDialogHintTextLabel => r'Құпия сөзді енгізіңіз'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Жарамсыз құпия сөз'; + + @override + String get pdfBookmarksLabel => r'Бетбелгілер'; + + @override + String get pdfEnterPageNumberLabel => r'Бет нөмірін енгізіңіз'; + + @override + String get pdfGoToPageLabel => r'Бетке өту'; + + @override + String get pdfHyperlinkContentLabel => r'бетті ашқыңыз келе ме'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'Болдырмау'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'АШЫҚ'; + + @override + String get pdfHyperlinkLabel => r'Веб-бетті ашыңыз'; + + @override + String get pdfInvalidPageNumberLabel => r'Жарамды нөмірді енгізіңіз'; + + @override + String get pdfNoBookmarksLabel => r'Ешқандай бетбелгі табылмады'; + + @override + String get pdfPaginationDialogCancelLabel => r'Болдырмау'; + + @override + String get pdfPaginationDialogOkLabel => r'ОК'; + + @override + String get pdfPasswordDialogCancelLabel => r'Болдырмау'; + + @override + String get pdfPasswordDialogOpenLabel => r'АШЫҚ'; + + @override + String get pdfScrollStatusOfLabel => r'ның'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ТАЗАЛАУ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Қолтаңбаңызды сызыңыз'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Қалам түсі'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'САҚТАУ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Көшіру'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Бөлектеу'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Айналмалы'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Сызық'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Астын сызу'; + + @override + String get rabi1Label => + r'Раби' + "'" + r' әл-әууәл'; + + @override + String get rabi2Label => + r'Раби' + "'" + r' әл-Тани'; + + @override + String get rajabLabel => r'Ражаб'; + + @override + String get ramadanLabel => r'Рамазан'; + + @override + String get rowsPerPageDataPagerLabel => r'Әр беттегі жолдар'; + + @override + String get safarLabel => r'Сафар'; + + @override + String get searchDataGridFilteringLabel => r'Іздеу'; + + @override + String get selectAllDataGridFilteringLabel => r'Барлығын таңдаңыз'; + + @override + String get series => r'Сериялар'; + + @override + String get shaabanLabel => r'Шағбан'; + + @override + String get shawwalLabel => r'Шәууәл'; + + @override + String get shortDhualhiLabel => r'Зул-Х'; + + @override + String get shortDhualqiLabel => r'Зуль-Қ'; + + @override + String get shortJumada1Label => r'Jum. I'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Мұх.'; + + @override + String get shortRabi1Label => r'Раби. I'; + + @override + String get shortRabi2Label => r'Раби. II'; + + @override + String get shortRajabLabel => r'Радж.'; + + @override + String get shortRamadanLabel => r'Жедел Жадтау Құрылғысы.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шоу.'; + + @override + String get showRowsWhereDataGridFilteringLabel => + r'Қай жерде жолдарды көрсетіңіз'; + + @override + String get sortAToZDataGridFilteringLabel => r'Адан Яға дейін сұрыптау'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Сұрыптау және сүзу'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Үлкеннен кішіге сұрыптау'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Ең жаңадан ең ескіге сұрыптау'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Ең ескіден ең жаңасына сұрыптау'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Ең кішіден үлкенге сұрыптау'; + + @override + String get sortZToADataGridFilteringLabel => r'Z-ден A-ға сұрыптау'; + + @override + String get textFiltersDataGridFilteringLabel => r'Мәтін сүзгілері'; + + @override + String get todayLabel => r'Бүгін'; + + @override + String get weeknumberLabel => r'Апта'; +} + +/// The translations for Khmer Central Khmer (`km`). +class SfLocalizationsKm extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsKm class + const SfLocalizationsKm({String localeName = 'km'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'បន្ទាប់ពី'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'បន្ទាប់ពី ឬស្មើ'; + + @override + String get allDayLabel => r'ពេញមួយថ្ងៃ'; + + @override + String get allowedViewDayLabel => r'ថ្ងៃ'; + + @override + String get allowedViewMonthLabel => r'ខែ'; + + @override + String get allowedViewScheduleLabel => r'កាលវិភាគ'; + + @override + String get allowedViewTimelineDayLabel => r'ថ្ងៃកំណត់ពេលវេលា'; + + @override + String get allowedViewTimelineMonthLabel => r'ខែពេលវេលា'; + + @override + String get allowedViewTimelineWeekLabel => r'Timeline សប្តាហ៍'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'កាលវិភាគការងារប្រចាំសប្តាហ៍'; + + @override + String get allowedViewWeekLabel => r'សប្តាហ៍'; + + @override + String get allowedViewWorkWeekLabel => r'សប្តាហ៍ការងារ'; + + @override + String get andDataGridFilteringLabel => r'និង'; + + @override + String get beforeDataGridFilteringLabel => r'ពីមុន'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'មុន ឬស្មើ'; + + @override + String get beginsWithDataGridFilteringLabel => r'ចាប់ផ្តើមជាមួយ'; + + @override + String get cancelDataGridFilteringLabel => r'បោះបង់'; + + @override + String get clearFilterDataGridFilteringLabel => r'ជម្រះតម្រង'; + + @override + String get containsDataGridFilteringLabel => r'មាន'; + + @override + String get dateFiltersDataGridFilteringLabel => r'តម្រងកាលបរិច្ឆេទ'; + + @override + String get daySpanCountLabel => r'ថ្ងៃ'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'មិនចាប់ផ្តើមជាមួយ'; + + @override + String get doesNotContainDataGridFilteringLabel => r'មិនមានផ្ទុក'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'មិនបញ្ចប់ដោយ'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'មិនស្មើគ្នា'; + + @override + String get emptyDataGridFilteringLabel => r'ទទេ'; + + @override + String get endsWithDataGridFilteringLabel => r'បញ្ចប់ដោយ'; + + @override + String get equalsDataGridFilteringLabel => r'ស្មើ'; + + @override + String get fromDataGridFilteringLabel => r'ពី'; + + @override + String get greaterThanDataGridFilteringLabel => r'ធំជាង'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'ធំជាង ឬស្មើ'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'ជូម៉ាដា អាល់ថានី'; + + @override + String get lessThanDataGridFilteringLabel => r'តិច​ជាង'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'តិចជាង ឬស្មើ'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'គ្មានព្រឹត្តិការណ៍'; + + @override + String get noMatchesDataGridFilteringLabel => r'គ្មានការប្រកួត'; + + @override + String get noSelectedDateCalendarLabel => + r'គ្មានកាលបរិច្ឆេទដែលបានជ្រើសរើសទេ។'; + + @override + String get notEmptyDataGridFilteringLabel => r'មិនទទេ'; + + @override + String get notNullDataGridFilteringLabel => r'មិនមែន Null'; + + @override + String get nullDataGridFilteringLabel => r'ទុកជាមោឃៈ'; + + @override + String get numberFiltersDataGridFilteringLabel => r'តម្រងលេខ'; + + @override + String get ofDataPagerLabel => r'នៃ'; + + @override + String get okDataGridFilteringLabel => r'យល់ព្រម'; + + @override + String get orDataGridFilteringLabel => r'ឬ'; + + @override + String get pagesDataPagerLabel => r'ទំព័រ'; + + @override + String get passwordDialogContentLabel => + r'បញ្ចូលពាក្យសម្ងាត់ដើម្បីបើកឯកសារ PDF នេះ។'; + + @override + String get passwordDialogHeaderTextLabel => r'ពាក្យសម្ងាត់ការពារ'; + + @override + String get passwordDialogHintTextLabel => r'បញ្ចូលពាក្យសម្ងាត់'; + + @override + String get passwordDialogInvalidPasswordLabel => r'ពាក្យសម្ងាត់មិនត្រឹមត្រូវ'; + + @override + String get pdfBookmarksLabel => r'ចំណាំ'; + + @override + String get pdfEnterPageNumberLabel => r'បញ្ចូលលេខទំព័រ'; + + @override + String get pdfGoToPageLabel => r'ទៅកាន់​ទំព័រ'; + + @override + String get pdfHyperlinkContentLabel => r'តើអ្នកចង់បើកទំព័រនៅ'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'បោះបង់'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'បើក'; + + @override + String get pdfHyperlinkLabel => r'បើកគេហទំព័រ'; + + @override + String get pdfInvalidPageNumberLabel => r'សូមបញ្ចូលលេខត្រឹមត្រូវ។'; + + @override + String get pdfNoBookmarksLabel => r'រកមិនឃើញចំណាំទេ។'; + + @override + String get pdfPaginationDialogCancelLabel => r'បោះបង់'; + + @override + String get pdfPaginationDialogOkLabel => r'យល់ព្រម'; + + @override + String get pdfPasswordDialogCancelLabel => r'បោះបង់'; + + @override + String get pdfPasswordDialogOpenLabel => r'បើក'; + + @override + String get pdfScrollStatusOfLabel => r'នៃ'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ជម្រះ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'គូរហត្ថលេខារបស់អ្នក។'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'ពណ៌ប៊ិច'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'រក្សាទុក'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'ចម្លង'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'បន្លិច'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'ញាក់សាច់'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'ការវាយឆ្មក់'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'គូសបន្ទាត់ពីក្រោម'; + + @override + String get rabi1Label => r'រ៉ាប៊ី អាល់ អាវ៉ាល់'; + + @override + String get rabi2Label => r'រ៉ាប៊ី អាល់ថានី'; + + @override + String get rajabLabel => r'រ៉ាចាប'; + + @override + String get ramadanLabel => r'រ៉ាម៉ាដាន'; + + @override + String get rowsPerPageDataPagerLabel => r'ជួរដេកក្នុងមួយទំព័រ'; + + @override + String get safarLabel => r'សាហ្វារ'; + + @override + String get searchDataGridFilteringLabel => r'ស្វែងរក'; + + @override + String get selectAllDataGridFilteringLabel => r'ជ្រើសរើស​ទាំងអស់'; + + @override + String get series => r'ស៊េរី'; + + @override + String get shaabanLabel => r'សាបាន'; + + @override + String get shawwalLabel => r'សាវ៉ាវ'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'ជុម ខ្ញុំ'; + + @override + String get shortJumada2Label => r'ជុម II'; + + @override + String get shortMuharramLabel => r'ម៉ោ'; + + @override + String get shortRabi1Label => r'រ៉ាប៊ី ខ្ញុំ'; + + @override + String get shortRabi2Label => r'រ៉ាប៊ី II'; + + @override + String get shortRajabLabel => r'រាជ'; + + @override + String get shortRamadanLabel => r'អង្គ​ចងចាំ។'; + + @override + String get shortSafarLabel => r'សុវត្ថិភាព។'; + + @override + String get shortShaabanLabel => r'សា។'; + + @override + String get shortShawwalLabel => r'Shaw ។'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'បង្ហាញជួរនៅកន្លែងណា'; + + @override + String get sortAToZDataGridFilteringLabel => r'តម្រៀប A ដល់ Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'តម្រៀបនិងត្រង'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'តម្រៀបធំបំផុតទៅតូចបំផុត។'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'តម្រៀបថ្មីបំផុតទៅចាស់បំផុត។'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'តម្រៀបចាស់បំផុតទៅថ្មីបំផុត។'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'តម្រៀបតូចបំផុតទៅធំបំផុត។'; + + @override + String get sortZToADataGridFilteringLabel => r'តម្រៀប Z ទៅ A'; + + @override + String get textFiltersDataGridFilteringLabel => r'តម្រងអត្ថបទ'; + + @override + String get todayLabel => r'ថ្ងៃនេះ'; + + @override + String get weeknumberLabel => r'សប្តាហ៍'; +} + +/// The translations for Kannada (`kn`). +class SfLocalizationsKn extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsKn class + const SfLocalizationsKn({String localeName = 'kn'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => '\u{ca8}\u{c82}\u{ca4}\u{cb0}'; + + @override + String get afterOrEqualDataGridFilteringLabel => + '\u{ca8}\u{c82}\u{ca4}\u{cb0}\u{20}\u{c85}\u{ca5}\u{cb5}\u{cbe}\u{20}\u{cb8}\u{cae}\u{cbe}\u{ca8}'; + + @override + String get allDayLabel => + '\u{c87}\u{ca1}\u{cbf}\u{cd5}\u{20}\u{ca6}\u{cbf}\u{ca8}'; + + @override + String get allowedViewDayLabel => '\u{ca6}\u{cbf}\u{ca8}'; + + @override + String get allowedViewMonthLabel => + '\u{ca4}\u{cbf}\u{c82}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get allowedViewScheduleLabel => + '\u{cb5}\u{cc7}\u{cb3}\u{cbe}\u{caa}\u{c9f}\u{ccd}\u{c9f}\u{cbf}'; + + @override + String get allowedViewTimelineDayLabel => + '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{ca6}\u{cbf}\u{ca8}'; + + @override + String get allowedViewTimelineMonthLabel => + '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{ca4}\u{cbf}\u{c82}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get allowedViewTimelineWeekLabel => + '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + + @override + String get allowedViewTimelineWorkWeekLabel => + '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{c95}\u{cc6}\u{cb2}\u{cb8}\u{ca6}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + + @override + String get allowedViewWeekLabel => '\u{cb5}\u{cbe}\u{cb0}'; + + @override + String get allowedViewWorkWeekLabel => + '\u{c95}\u{cc6}\u{cb2}\u{cb8}\u{ca6}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + + @override + String get andDataGridFilteringLabel => '\u{cae}\u{ca4}\u{ccd}\u{ca4}\u{cc1}'; + + @override + String get beforeDataGridFilteringLabel => + '\u{cae}\u{cca}\u{ca6}\u{cb2}\u{cc1}'; + + @override + String get beforeOrEqualDataGridFilteringLabel => + '\u{cae}\u{cca}\u{ca6}\u{cb2}\u{cc1}\u{20}\u{c85}\u{ca5}\u{cb5}\u{cbe}\u{20}\u{cb8}\u{cae}\u{cbe}\u{ca8}'; + + @override + String get beginsWithDataGridFilteringLabel => + '\u{c87}\u{ca6}\u{cb0}\u{cca}\u{c82}\u{ca6}\u{cbf}\u{c97}\u{cc6}\u{20}\u{caa}\u{ccd}\u{cb0}\u{cbe}\u{cb0}\u{c82}\u{cad}\u{cb5}\u{cbe}\u{c97}\u{cc1}\u{ca4}\u{ccd}\u{ca4}\u{ca6}\u{cc6}'; + + @override + String get cancelDataGridFilteringLabel => + '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; + + @override + String get clearFilterDataGridFilteringLabel => + '\u{cab}\u{cbf}\u{cb2}\u{ccd}\u{c9f}\u{cb0}\u{ccd}\u{20}\u{c85}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca4}\u{cc6}\u{cb0}\u{cb5}\u{cc1}\u{c97}\u{cca}\u{cb3}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get containsDataGridFilteringLabel => + '\u{c92}\u{cb3}\u{c97}\u{cca}\u{c82}\u{ca1}\u{cbf}\u{ca6}\u{cc6}'; + + @override + String get dateFiltersDataGridFilteringLabel => + '\u{ca6}\u{cbf}\u{ca8}\u{cbe}\u{c82}\u{c95}\u{20}\u{cb6}\u{ccb}\u{ca7}\u{c95}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get daySpanCountLabel => '\u{ca6}\u{cbf}\u{ca8}'; + + @override + String get dhualhiLabel => + '\u{ca7}\u{cc1}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{cb9}\u{cbf}\u{c9c}\u{ccd}\u{c9c}\u{cbe}'; + + @override + String get dhualqiLabel => + '\u{ca7}\u{cc1}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c96}\u{cbf}\u{ca6}\u{cbe}'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => + '\u{c87}\u{ca6}\u{cb0}\u{cca}\u{c82}\u{ca6}\u{cbf}\u{c97}\u{cc6}\u{20}\u{caa}\u{ccd}\u{cb0}\u{cbe}\u{cb0}\u{c82}\u{cad}\u{cb5}\u{cbe}\u{c97}\u{cc1}\u{cb5}\u{cc1}\u{ca6}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get doesNotContainDataGridFilteringLabel => + '\u{c92}\u{cb3}\u{c97}\u{cca}\u{c82}\u{ca1}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get doesNotEndWithDataGridFilteringLabel => + '\u{c87}\u{ca6}\u{cb0}\u{cca}\u{c82}\u{ca6}\u{cbf}\u{c97}\u{cc6}\u{20}\u{c95}\u{cca}\u{ca8}\u{cc6}\u{c97}\u{cca}\u{cb3}\u{ccd}\u{cb3}\u{cc1}\u{cb5}\u{cc1}\u{ca6}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get doesNotEqualDataGridFilteringLabel => + '\u{cb8}\u{cae}\u{cbe}\u{ca8}\u{cb5}\u{cbe}\u{c97}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get emptyDataGridFilteringLabel => '\u{c96}\u{cbe}\u{cb2}\u{cbf}'; + + @override + String get endsWithDataGridFilteringLabel => + '\u{c87}\u{ca6}\u{cb0}\u{cca}\u{c82}\u{ca6}\u{cbf}\u{c97}\u{cc6}\u{20}\u{c95}\u{cca}\u{ca8}\u{cc6}\u{c97}\u{cca}\u{cb3}\u{ccd}\u{cb3}\u{cc1}\u{ca4}\u{ccd}\u{ca4}\u{ca6}\u{cc6}'; + + @override + String get equalsDataGridFilteringLabel => + '\u{cb8}\u{cae}\u{ca8}\u{cbe}\u{c97}\u{cbf}\u{cb0}\u{cc1}\u{ca4}\u{ccd}\u{ca4}\u{ca6}\u{cc6}'; + + @override + String get fromDataGridFilteringLabel => '\u{c87}\u{c82}\u{ca6}'; + + @override + String get greaterThanDataGridFilteringLabel => + '\u{c85}\u{ca6}\u{c95}\u{ccd}\u{c95}\u{cbf}\u{c82}\u{ca4}\u{20}\u{cb9}\u{cc6}\u{c9a}\u{ccd}\u{c9a}\u{cc1}'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + '\u{c97}\u{ccd}\u{cb0}\u{cc7}\u{c9f}\u{cb0}\u{ccd}\u{20}\u{ca6}\u{ccd}\u{caf}\u{cbe}\u{ca8}\u{ccd}\u{20}\u{c85}\u{ca5}\u{cb5}\u{cbe}\u{20}\u{c88}\u{c95}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; + + @override + String get jumada1Label => + '\u{c9c}\u{cc1}\u{cae}\u{cbe}\u{ca6}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; + + @override + String get jumada2Label => + '\u{c9c}\u{cc1}\u{cae}\u{cbe}\u{ca6}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + + @override + String get lessThanDataGridFilteringLabel => + '\u{c95}\u{ca1}\u{cbf}\u{cae}\u{cc6}'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + '\u{c95}\u{ca1}\u{cbf}\u{cae}\u{cc6}\u{20}\u{c85}\u{ca5}\u{cb5}\u{cbe}\u{20}\u{cb8}\u{cae}\u{cbe}\u{ca8}'; + + @override + String get muharramLabel => '\u{cae}\u{cca}\u{cb9}\u{cb0}\u{c82}'; + + @override + String get noEventsCalendarLabel => + '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{c98}\u{c9f}\u{ca8}\u{cc6}\u{c97}\u{cb3}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get noMatchesDataGridFilteringLabel => + '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{cb9}\u{cca}\u{c82}\u{ca6}\u{cbe}\u{ca3}\u{cbf}\u{c95}\u{cc6}\u{c97}\u{cb3}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get noSelectedDateCalendarLabel => + '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{c86}\u{caf}\u{ccd}\u{c95}\u{cc6}\u{20}\u{ca6}\u{cbf}\u{ca8}\u{cbe}\u{c82}\u{c95}\u{cb5}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get notEmptyDataGridFilteringLabel => + '\u{c96}\u{cbe}\u{cb2}\u{cbf}\u{20}\u{c87}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get notNullDataGridFilteringLabel => + '\u{cb6}\u{cc2}\u{ca8}\u{ccd}\u{caf}\u{cb5}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get nullDataGridFilteringLabel => + '\u{cb6}\u{cc2}\u{ca8}\u{ccd}\u{caf}'; + + @override + String get numberFiltersDataGridFilteringLabel => + '\u{cb8}\u{c82}\u{c96}\u{ccd}\u{caf}\u{cc6}\u{20}\u{cb6}\u{ccb}\u{ca7}\u{c95}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get ofDataPagerLabel => '\u{ca8}'; + + @override + String get okDataGridFilteringLabel => '\u{cb8}\u{cb0}\u{cbf}'; + + @override + String get orDataGridFilteringLabel => '\u{c85}\u{ca5}\u{cb5}\u{cbe}'; + + @override + String get pagesDataPagerLabel => + '\u{caa}\u{cc1}\u{c9f}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get passwordDialogContentLabel => + '\u{c88}\u{20}\u{50}\u{44}\u{46}\u{20}\u{cab}\u{cc8}\u{cb2}\u{ccd}\u{20}\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cb2}\u{cc1}\u{20}\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{200c}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get passwordDialogHeaderTextLabel => + '\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}\u{20}\u{cb0}\u{c95}\u{ccd}\u{cb7}\u{cbf}\u{cb8}\u{cb2}\u{cbe}\u{c97}\u{cbf}\u{ca6}\u{cc6}'; + + @override + String get passwordDialogHintTextLabel => + '\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get passwordDialogInvalidPasswordLabel => + '\u{c85}\u{cae}\u{cbe}\u{ca8}\u{ccd}\u{caf}\u{cb5}\u{cbe}\u{ca6}\u{20}\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}'; + + @override + String get pdfBookmarksLabel => + '\u{cac}\u{cc1}\u{c95}\u{ccd}\u{200c}\u{cae}\u{cbe}\u{cb0}\u{ccd}\u{c95}\u{ccd}\u{200c}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get pdfEnterPageNumberLabel => + '\u{caa}\u{cc1}\u{c9f}\u{20}\u{cb8}\u{c82}\u{c96}\u{ccd}\u{caf}\u{cc6}\u{caf}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get pdfGoToPageLabel => + '\u{caa}\u{cc1}\u{c9f}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb9}\u{cc6}\u{cc2}\u{cd5}\u{c97}\u{cbf}\u{cb0}\u{cbf}'; + + @override + String get pdfHyperlinkContentLabel => + '\u{ca8}\u{cc0}\u{cb5}\u{cc1}\u{20}\u{caa}\u{cc1}\u{c9f}\u{cb5}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cb2}\u{cc1}\u{20}\u{cac}\u{caf}\u{cb8}\u{cc1}\u{cb5}\u{cbf}\u{cb0}\u{cbe}'; + + @override + String get pdfHyperlinkDialogCancelLabel => + '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{cae}\u{cbe}\u{ca1}\u{cc1}'; + + @override + String get pdfHyperlinkDialogOpenLabel => + '\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cbf}\u{cb0}\u{cbf}'; + + @override + String get pdfHyperlinkLabel => + '\u{cb5}\u{cc6}\u{cac}\u{ccd}\u{20}\u{caa}\u{cc1}\u{c9f}\u{cb5}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cbf}\u{cb0}\u{cbf}'; + + @override + String get pdfInvalidPageNumberLabel => + '\u{ca6}\u{caf}\u{cb5}\u{cbf}\u{c9f}\u{ccd}\u{c9f}\u{cc1}\u{20}\u{cae}\u{cbe}\u{ca8}\u{ccd}\u{caf}\u{cb5}\u{cbe}\u{ca6}\u{20}\u{cb8}\u{c82}\u{c96}\u{ccd}\u{caf}\u{cc6}\u{caf}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get pdfNoBookmarksLabel => + '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{cac}\u{cc1}\u{c95}\u{ccd}\u{200c}\u{cae}\u{cbe}\u{cb0}\u{ccd}\u{c95}\u{ccd}\u{200c}\u{c97}\u{cb3}\u{cc1}\u{20}\u{c95}\u{c82}\u{ca1}\u{cc1}\u{cac}\u{c82}\u{ca6}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + + @override + String get pdfPaginationDialogCancelLabel => + '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{cae}\u{cbe}\u{ca1}\u{cc1}'; + + @override + String get pdfPaginationDialogOkLabel => '\u{cb8}\u{cb0}\u{cbf}'; + + @override + String get pdfPasswordDialogCancelLabel => + '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{cae}\u{cbe}\u{ca1}\u{cc1}'; + + @override + String get pdfPasswordDialogOpenLabel => + '\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cbf}\u{cb0}\u{cbf}'; + + @override + String get pdfScrollStatusOfLabel => '\u{ca8}'; + + @override + String get rabi1Label => + '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; + + @override + String get rabi2Label => + '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + + @override + String get pdfSignaturePadDialogClearLabel => + '\u{cb8}\u{ccd}\u{caa}\u{cb7}\u{ccd}\u{c9f}'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + '\u{ca8}\u{cbf}\u{cae}\u{ccd}\u{cae}\u{20}\u{cb8}\u{cb9}\u{cbf}\u{caf}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{c8e}\u{cb3}\u{cc6}\u{caf}\u{cbf}\u{cb0}\u{cbf}'; + + @override + String get pdfSignaturePadDialogPenColorLabel => + '\u{caa}\u{cc6}\u{ca8}\u{ccd}\u{20}\u{cac}\u{ca3}\u{ccd}\u{ca3}'; + + @override + String get pdfSignaturePadDialogSaveLabel => + '\u{c89}\u{cb3}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get pdfTextSelectionMenuCopyLabel => + '\u{ca8}\u{c95}\u{cb2}\u{cc1}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; + + @override + String get pdfTextSelectionMenuHighlightLabel => + '\u{cb9}\u{cc8}\u{cb2}\u{cc8}\u{c9f}\u{ccd}'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => + '\u{c95}\u{cc1}\u{c82}\u{c9c}'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => + '\u{cb8}\u{ccd}\u{c9f}\u{ccd}\u{cb0}\u{cc8}\u{c95}\u{ccd}\u{ca5}\u{ccd}\u{cb0}\u{cc2}'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => + '\u{c85}\u{c82}\u{ca1}\u{cb0}\u{ccd}\u{cb2}\u{cc8}\u{ca8}\u{ccd}'; + + @override + String get rajabLabel => '\u{cb0}\u{c9c}\u{cac}\u{ccd}'; + + @override + String get ramadanLabel => '\u{cb0}\u{c82}\u{c9c}\u{cbe}\u{ca8}\u{ccd}'; + + @override + String get rowsPerPageDataPagerLabel => + '\u{caa}\u{ccd}\u{cb0}\u{ca4}\u{cbf}\u{20}\u{caa}\u{cc1}\u{c9f}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb8}\u{cbe}\u{cb2}\u{cc1}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get safarLabel => '\u{cb8}\u{cab}\u{cb0}\u{ccd}'; + + @override + String get searchDataGridFilteringLabel => + '\u{ca5}\u{cb1}\u{cca}\u{cb1}\u{ccd}\u{20}\u{c0f}'; + + @override + String get selectAllDataGridFilteringLabel => + '\u{c8e}\u{cb2}\u{ccd}\u{cb2}\u{cb5}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{c86}\u{cb0}\u{cbf}\u{cb8}\u{cc1}'; + + @override + String get series => '\u{cb8}\u{cb0}\u{ca3}\u{cbf}'; + + @override + String get shaabanLabel => '\u{cb6}\u{cbe}\u{cac}\u{cbe}\u{ca8}\u{ccd}'; + + @override + String get shawwalLabel => + '\u{cb6}\u{cb5}\u{ccd}\u{cb5}\u{cbe}\u{cb2}\u{ccd}'; + + @override + String get shortDhualhiLabel => + '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{c8e}\u{c9a}\u{ccd}'; + + @override + String get shortDhualqiLabel => + '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{c95}\u{ccd}\u{caf}\u{cc2}'; + + @override + String get shortJumada1Label => '\u{c9c}\u{c82}\u{2e}\u{20}\u{49}'; + + @override + String get shortJumada2Label => '\u{c9c}\u{c82}\u{2e}\u{20}\u{49}\u{49}'; + + @override + String get shortMuharramLabel => '\u{cae}\u{cc1}\u{cb9}\u{ccd}'; + + @override + String get shortRabi1Label => '\u{cb0}\u{cac}\u{cbf}\u{20}\u{49}'; + + @override + String get shortRabi2Label => '\u{cb0}\u{cac}\u{cbf}\u{20}\u{49}\u{49}'; + + @override + String get shortRajabLabel => '\u{cb0}\u{cbe}\u{c9c}\u{ccd}\u{2e}'; + + @override + String get shortRamadanLabel => '\u{cb0}\u{cbe}\u{cae}\u{ccd}\u{2e}'; + + @override + String get shortSafarLabel => '\u{cb8}\u{cc7}\u{cab}\u{ccd}'; + + @override + String get shortShaabanLabel => '\u{cb6}\u{cbe}\u{2e}'; + + @override + String get shortShawwalLabel => '\u{cb6}\u{cbe}\u{2e}'; + + @override + String get showRowsWhereDataGridFilteringLabel => + '\u{c85}\u{cb2}\u{ccd}\u{cb2}\u{cbf}\u{20}\u{cb8}\u{cbe}\u{cb2}\u{cc1}\u{c97}\u{cb3}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca4}\u{ccb}\u{cb0}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get sortAToZDataGridFilteringLabel => + '\u{41}\u{20}\u{ca8}\u{cbf}\u{c82}\u{ca6}\u{20}\u{5a}\u{20}\u{c97}\u{cc6}\u{20}\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get sortAndFilterDataGridFilteringLabel => + '\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}\u{20}\u{cae}\u{ca4}\u{ccd}\u{ca4}\u{cc1}\u{20}\u{cab}\u{cbf}\u{cb2}\u{ccd}\u{c9f}\u{cb0}\u{ccd}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + '\u{ca6}\u{cca}\u{ca1}\u{ccd}\u{ca1}\u{ca6}\u{cb0}\u{cbf}\u{c82}\u{ca6}\u{20}\u{c9a}\u{cbf}\u{c95}\u{ccd}\u{c95}\u{ca6}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + '\u{cb9}\u{cca}\u{cb8}\u{ca6}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{cb9}\u{cb3}\u{cc6}\u{caf}\u{ca6}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + '\u{cb9}\u{cb3}\u{cc6}\u{caf}\u{ca6}\u{cb0}\u{cbf}\u{c82}\u{ca6}\u{20}\u{cb9}\u{cca}\u{cb8}\u{ca6}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + '\u{c9a}\u{cbf}\u{c95}\u{ccd}\u{c95}\u{ca6}\u{cb0}\u{cbf}\u{c82}\u{ca6}\u{20}\u{ca6}\u{cca}\u{ca1}\u{ccd}\u{ca1}\u{ca6}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get sortZToADataGridFilteringLabel => + '\u{5a}\u{20}\u{ca8}\u{cbf}\u{c82}\u{ca6}\u{20}\u{41}\u{20}\u{c97}\u{cc6}\u{20}\u{cb5}\u{cbf}\u{c82}\u{c97}\u{ca1}\u{cbf}\u{cb8}\u{cbf}'; + + @override + String get textFiltersDataGridFilteringLabel => + '\u{caa}\u{ca0}\u{ccd}\u{caf}\u{20}\u{cb6}\u{ccb}\u{ca7}\u{c95}\u{c97}\u{cb3}\u{cc1}'; + + @override + String get todayLabel => '\u{c87}\u{c82}\u{ca6}\u{cc1}'; + + @override + String get weeknumberLabel => '\u{cb5}\u{cbe}\u{cb0}'; +} + +/// The translations for Korean (`ko`). +class SfLocalizationsKo extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsKo class + const SfLocalizationsKo({String localeName = 'ko'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'후에'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'후 또는 같음'; + + @override + String get allDayLabel => r'하루 종일'; + + @override + String get allowedViewDayLabel => r'일'; + + @override + String get allowedViewMonthLabel => r'월'; + + @override + String get allowedViewScheduleLabel => r'일정'; + + @override + String get allowedViewTimelineDayLabel => r'타임라인 데이'; + + @override + String get allowedViewTimelineMonthLabel => r'타임라인 월'; + + @override + String get allowedViewTimelineWeekLabel => r'타임라인 위크'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'타임라인 작업 주'; + + @override + String get allowedViewWeekLabel => r'주'; + + @override + String get allowedViewWorkWeekLabel => r'근무 주'; + + @override + String get andDataGridFilteringLabel => r'그리고'; + + @override + String get beforeDataGridFilteringLabel => r'전에'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'이전 또는 같음'; + + @override + String get beginsWithDataGridFilteringLabel => r'다음으로 시작'; + + @override + String get cancelDataGridFilteringLabel => r'취소'; + + @override + String get clearFilterDataGridFilteringLabel => r'필터 지우기'; + + @override + String get containsDataGridFilteringLabel => r'포함'; + + @override + String get dateFiltersDataGridFilteringLabel => r'날짜 필터'; + + @override + String get daySpanCountLabel => r'낮'; + + @override + String get dhualhiLabel => r'두 알 히자'; + + @override + String get dhualqiLabel => r'두 알 키이다'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'다음으로 시작하지 않음'; + + @override + String get doesNotContainDataGridFilteringLabel => r'포함되어 있지 않다'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'다음으로 끝나지 않음'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'같지 않음'; + + @override + String get emptyDataGridFilteringLabel => r'비어 있는'; + + @override + String get endsWithDataGridFilteringLabel => r'로 끝나다'; + + @override + String get equalsDataGridFilteringLabel => r'같음'; + + @override + String get fromDataGridFilteringLabel => r'에서'; + + @override + String get greaterThanDataGridFilteringLabel => r'보다 큰'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'크거나 같음'; + + @override + String get jumada1Label => r'주마다 알-아왈'; + + @override + String get jumada2Label => r'주마다 알 타니'; + + @override + String get lessThanDataGridFilteringLabel => r'미만'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'작거나 같음'; + + @override + String get muharramLabel => r'무하람'; + + @override + String get noEventsCalendarLabel => r'일정 없음'; + + @override + String get noMatchesDataGridFilteringLabel => r'맞지 않는다'; + + @override + String get noSelectedDateCalendarLabel => r'선택한 날짜 없음'; + + @override + String get notEmptyDataGridFilteringLabel => r'비어 있지 않음'; + + @override + String get notNullDataGridFilteringLabel => r'널이 아님'; + + @override + String get nullDataGridFilteringLabel => r'없는'; + + @override + String get numberFiltersDataGridFilteringLabel => r'숫자 필터'; + + @override + String get ofDataPagerLabel => r'의'; + + @override + String get okDataGridFilteringLabel => r'확인'; + + @override + String get orDataGridFilteringLabel => r'또는'; + + @override + String get pagesDataPagerLabel => r'페이지'; + + @override + String get passwordDialogContentLabel => r'이 PDF 파일을 열려면 비밀번호를 입력하세요'; + + @override + String get passwordDialogHeaderTextLabel => r'암호로 보호됨'; + + @override + String get passwordDialogHintTextLabel => r'암호를 입력'; + + @override + String get passwordDialogInvalidPasswordLabel => r'유효하지 않은 비밀번호'; + + @override + String get pdfBookmarksLabel => r'북마크'; + + @override + String get pdfEnterPageNumberLabel => r'페이지 번호 입력'; + + @override + String get pdfGoToPageLabel => r'페이지로 이동'; + + @override + String get pdfHyperlinkContentLabel => r'에서 페이지를 열시겠습니까?'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'취소'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'열기'; + + @override + String get pdfHyperlinkLabel => r'웹 페이지 열기'; + + @override + String get pdfInvalidPageNumberLabel => r'유효한 숫자를 입력하세요.'; + + @override + String get pdfNoBookmarksLabel => r'북마크가 없습니다.'; + + @override + String get pdfPaginationDialogCancelLabel => r'취소'; + + @override + String get pdfPaginationDialogOkLabel => r'확인'; + + @override + String get pdfPasswordDialogCancelLabel => r'취소'; + + @override + String get pdfPasswordDialogOpenLabel => r'열기'; + + @override + String get pdfScrollStatusOfLabel => r'의'; + + @override + String get pdfSignaturePadDialogClearLabel => r'비우기'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'서명하기'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'펜 색상'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'저장'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'복사'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'강조'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'물결선'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'취소선'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'밑줄'; + + @override + String get rabi1Label => r'라비 알-아왈'; + + @override + String get rabi2Label => r'라비 알 타니'; + + @override + String get rajabLabel => r'라잡'; + + @override + String get ramadanLabel => r'라마단'; + + @override + String get rowsPerPageDataPagerLabel => r'페이지당 행'; + + @override + String get safarLabel => r'사파르'; + + @override + String get searchDataGridFilteringLabel => r'검색'; + + @override + String get selectAllDataGridFilteringLabel => r'모두 선택'; + + @override + String get series => r'시리즈'; + + @override + String get shaabanLabel => r'샤아반'; + + @override + String get shawwalLabel => r'샤왈'; + + @override + String get shortDhualhiLabel => r'둘-H'; + + @override + String get shortDhualqiLabel => r'둘큐'; + + @override + String get shortJumada1Label => r'줌. 나'; + + @override + String get shortJumada2Label => r'줌. II'; + + @override + String get shortMuharramLabel => r'머.'; + + @override + String get shortRabi1Label => r'라비. 나'; + + @override + String get shortRabi2Label => r'라비. II'; + + @override + String get shortRajabLabel => r'주권.'; + + @override + String get shortRamadanLabel => r'램.'; + + @override + String get shortSafarLabel => r'사프.'; + + @override + String get shortShaabanLabel => r'샤.'; + + @override + String get shortShawwalLabel => r'쇼.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'어디에 행 표시'; + + @override + String get sortAToZDataGridFilteringLabel => r'A부터 Z까지 정렬'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'정렬 및 필터링'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => r'내림차순 정렬'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => r'최신순으로 정렬'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => r'가장 오래된 것부터 최신순으로 정렬'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => r'작은 것부터 큰 것까지 정렬'; + + @override + String get sortZToADataGridFilteringLabel => r'Z를 A로 정렬'; + + @override + String get textFiltersDataGridFilteringLabel => r'텍스트 필터'; + + @override + String get todayLabel => r'오늘'; + + @override + String get weeknumberLabel => r'주'; +} + +/// The translations for Kirghiz Kyrgyz (`ky`). +class SfLocalizationsKy extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsKy class + const SfLocalizationsKy({String localeName = 'ky'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Кийин'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Кийин же барабар'; + + @override + String get allDayLabel => r'Күн бою'; + + @override + String get allowedViewDayLabel => r'күн'; + + @override + String get allowedViewMonthLabel => r'Ай'; + + @override + String get allowedViewScheduleLabel => r'График'; + + @override + String get allowedViewTimelineDayLabel => r'Timeline Day'; + + @override + String get allowedViewTimelineMonthLabel => r'Убакыт тилкеси айы'; + + @override + String get allowedViewTimelineWeekLabel => r'Timeline Week'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Хронология иш жумасы'; + + @override + String get allowedViewWeekLabel => r'Апта'; + + @override + String get allowedViewWorkWeekLabel => r'Жумуш аптасы'; + + @override + String get andDataGridFilteringLabel => r'Жана'; + + @override + String get beforeDataGridFilteringLabel => r'Мурда'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Мурун же барабар'; + + @override + String get beginsWithDataGridFilteringLabel => r'Менен башталат'; + + @override + String get cancelDataGridFilteringLabel => r'Жокко чыгаруу'; + + @override + String get clearFilterDataGridFilteringLabel => r'Чыпканы тазалоо'; + + @override + String get containsDataGridFilteringLabel => r'камтыйт'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Дата чыпкалары'; + + @override + String get daySpanCountLabel => r'күн'; + + @override + String get dhualhiLabel => r'Зул Хижжа'; + + @override + String get dhualqiLabel => r'Зул кыда'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'менен башталбайт'; + + @override + String get doesNotContainDataGridFilteringLabel => r'камтыбайт'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'менен бүтпөйт'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Тең эмес'; + + @override + String get emptyDataGridFilteringLabel => r'бош'; + + @override + String get endsWithDataGridFilteringLabel => r'менен аяктайт'; + + @override + String get equalsDataGridFilteringLabel => r'барабар'; + + @override + String get fromDataGridFilteringLabel => r'From'; + + @override + String get greaterThanDataGridFilteringLabel => r'Чоңураак'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Чоңураак же барабар'; + + @override + String get jumada1Label => r'Жумада аль-аввал'; + + @override + String get jumada2Label => r'Жумада ал-Тани'; + + @override + String get lessThanDataGridFilteringLabel => r'Азыраак'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Аз же барабар'; + + @override + String get muharramLabel => r'Мухаррам'; + + @override + String get noEventsCalendarLabel => r'Окуялар жок'; + + @override + String get noMatchesDataGridFilteringLabel => r'Дал келгендер жок'; + + @override + String get noSelectedDateCalendarLabel => r'Тандалган дата жок'; + + @override + String get notEmptyDataGridFilteringLabel => r'Бош эмес'; + + @override + String get notNullDataGridFilteringLabel => r'Null эмес'; + + @override + String get nullDataGridFilteringLabel => r'Нөл'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Сан чыпкалары'; + + @override + String get ofDataPagerLabel => r'нын'; + + @override + String get okDataGridFilteringLabel => r'макул'; + + @override + String get orDataGridFilteringLabel => r'Же'; + + @override + String get pagesDataPagerLabel => r'барактар'; + + @override + String get passwordDialogContentLabel => + r'Бул PDF файлын ачуу үчүн сырсөздү киргизиңиз'; + + @override + String get passwordDialogHeaderTextLabel => r'Сырсөз менен корголгон'; + + @override + String get passwordDialogHintTextLabel => r'Сырсөздү киргизиңиз'; + + @override + String get passwordDialogInvalidPasswordLabel => r'туура эмес пароль'; + + @override + String get pdfBookmarksLabel => r'Кыстармалар'; + + @override + String get pdfEnterPageNumberLabel => r'Барактын номерин киргизиңиз'; + + @override + String get pdfGoToPageLabel => r'Баракка өтүү'; + + @override + String get pdfHyperlinkContentLabel => r'Баракчаны ачкыңыз келеби?'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ЖОК КЫЛУУ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'АЧУУ'; + + @override + String get pdfHyperlinkLabel => r'Веб баракчаны ачуу'; + + @override + String get pdfInvalidPageNumberLabel => r'Жарактуу номер киргизиңиз'; + + @override + String get pdfNoBookmarksLabel => r'Кыстармалар табылган жок'; + + @override + String get pdfPaginationDialogCancelLabel => r'ЖОК КЫЛУУ'; + + @override + String get pdfPaginationDialogOkLabel => r'макул'; + + @override + String get pdfPasswordDialogCancelLabel => r'ЖОК КЫЛУУ'; + + @override + String get pdfPasswordDialogOpenLabel => r'АЧУУ'; + + @override + String get pdfScrollStatusOfLabel => r'нын'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ТАЗАЛОО'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Колуңузду тартыңыз'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Калем түсү'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'САКТОО'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Көчүрүү'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Баса белгилөө'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Squiggly'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Чийүү'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Астын сыз'; + + @override + String get rabi1Label => r'Рабиъ аль-аввал'; + + @override + String get rabi2Label => r'Рабиъ ат-Тани'; + + @override + String get rajabLabel => r'Ражаб'; + + @override + String get ramadanLabel => r'Орозо айт'; + + @override + String get rowsPerPageDataPagerLabel => r'Ар бир беттеги саптар'; + + @override + String get safarLabel => r'Сафар'; + + @override + String get searchDataGridFilteringLabel => r'Издөө'; + + @override + String get selectAllDataGridFilteringLabel => r'Баарын тандаңыз'; + + @override + String get series => r'Сериялар'; + + @override + String get shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шаввал'; + + @override + String get shortDhualhiLabel => r'Зул-Х'; + + @override + String get shortDhualqiLabel => r'Зуль-К'; + + @override + String get shortJumada1Label => r'Jum. И'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Мух.'; + + @override + String get shortRabi1Label => r'Раби. И'; + + @override + String get shortRabi2Label => r'Раби. II'; + + @override + String get shortRajabLabel => r'Радж.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шоу.'; + + @override + String get showRowsWhereDataGridFilteringLabel => + r'Кайсы жерде катарларды көрсөтүү'; + + @override + String get sortAToZDataGridFilteringLabel => r'Адан Яга чейин сорттоо'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Сорттоо жана чыпкалоо'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Чоңунан кичинесинен иреттөө'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Эң жаңыдан эң эскиге иреттөө'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Эң эскиден эң жаңысына иреттөө'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Кичинеден чоңго иреттөө'; + + @override + String get sortZToADataGridFilteringLabel => r'Zдан Ага чейин сорттоо'; + + @override + String get textFiltersDataGridFilteringLabel => r'Текст чыпкалары'; + + @override + String get todayLabel => r'Бүгүн'; + + @override + String get weeknumberLabel => r'Апта'; +} + +/// The translations for Lao (`lo`). +class SfLocalizationsLo extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsLo class + const SfLocalizationsLo({String localeName = 'lo'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'ຫຼັງຈາກ'; + + @override + String get afterOrEqualDataGridFilteringLabel => + r'ຫຼັງ​ຈາກ​ຫຼື​ເທົ່າ​ທຽມ​ກັນ​'; + + @override + String get allDayLabel => r'ຫມົດ​ມື້'; + + @override + String get allowedViewDayLabel => r'ມື້'; + + @override + String get allowedViewMonthLabel => r'ເດືອນ'; + + @override + String get allowedViewScheduleLabel => r'ຕາຕະລາງ'; + + @override + String get allowedViewTimelineDayLabel => r'ທາມລາຍວັນ'; + + @override + String get allowedViewTimelineMonthLabel => r'ທາມລາຍເດືອນ'; + + @override + String get allowedViewTimelineWeekLabel => r'ທາມລາຍອາທິດ'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'ທາມລາຍອາທິດການເຮັດວຽກ'; + + @override + String get allowedViewWeekLabel => r'ອາທິດ'; + + @override + String get allowedViewWorkWeekLabel => r'ອາທິດເຮັດວຽກ'; + + @override + String get andDataGridFilteringLabel => r'ແລະ'; + + @override + String get beforeDataGridFilteringLabel => r'ກ່ອນ'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'ກ່ອນ ຫຼື ເທົ່າກັນ'; + + @override + String get beginsWithDataGridFilteringLabel => r'ເລີ່ມຕົ້ນດ້ວຍ'; + + @override + String get cancelDataGridFilteringLabel => r'ຍົກເລີກ'; + + @override + String get clearFilterDataGridFilteringLabel => r'ລ້າງການກັ່ນຕອງ'; + + @override + String get containsDataGridFilteringLabel => r'ປະກອບດ້ວຍ'; + + @override + String get dateFiltersDataGridFilteringLabel => r'ການກັ່ນຕອງວັນທີ'; + + @override + String get daySpanCountLabel => r'ມື້'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'ບໍ່ໄດ້ເລີ່ມຕົ້ນດ້ວຍ'; + + @override + String get doesNotContainDataGridFilteringLabel => r'ບໍ່ມີ'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'ບໍ່ສິ້ນສຸດດ້ວຍ'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'ບໍ່ເທົ່າກັນ'; + + @override + String get emptyDataGridFilteringLabel => r'ຫວ່າງເປົ່າ'; + + @override + String get endsWithDataGridFilteringLabel => r'ສິ້ນສຸດດ້ວຍ'; + + @override + String get equalsDataGridFilteringLabel => r'ເທົ່າກັບ'; + + @override + String get fromDataGridFilteringLabel => r'ຈາກ'; + + @override + String get greaterThanDataGridFilteringLabel => r'ໃຫຍ່​ກວ່າ'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'ຍິ່ງໃຫຍ່ກວ່າຫຼືເທົ່າທຽມກັນ'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'ຈູມາດາ ອານທານີ'; + + @override + String get lessThanDataGridFilteringLabel => r'ຫນ້ອຍ​ກ​່​ວາ'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'ໜ້ອຍກວ່າ ຫຼື ເທົ່າກັນ'; + + @override + String get muharramLabel => r'ມູຮາຣາມ'; + + @override + String get noEventsCalendarLabel => r'ບໍ່ມີເຫດການ'; + + @override + String get noMatchesDataGridFilteringLabel => r'ບໍ່ມີຂໍ້ມູນທີ່ກົງກັນ'; + + @override + String get noSelectedDateCalendarLabel => r'ບໍ່ມີວັນທີເລືອກ'; + + @override + String get notEmptyDataGridFilteringLabel => r'ບໍ່ຫວ່າງ'; + + @override + String get notNullDataGridFilteringLabel => r'ບໍ່ແມ່ນ Null'; + + @override + String get nullDataGridFilteringLabel => r'null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'ຕົວກອງຕົວເລກ'; + + @override + String get ofDataPagerLabel => r'ຂອງ'; + + @override + String get okDataGridFilteringLabel => r'ຕົກ​ລົງ'; + + @override + String get orDataGridFilteringLabel => r'ຫຼື'; + + @override + String get pagesDataPagerLabel => r'ໜ້າ'; + + @override + String get passwordDialogContentLabel => r'ໃສ່ລະຫັດຜ່ານເພື່ອເປີດໄຟລ໌ PDF ນີ້'; + + @override + String get passwordDialogHeaderTextLabel => r'ປ້ອງກັນລະຫັດຜ່ານ'; + + @override + String get passwordDialogHintTextLabel => r'ໃສ່ລະຫັດຜ່ານ'; + + @override + String get passwordDialogInvalidPasswordLabel => r'ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ'; + + @override + String get pdfBookmarksLabel => r'ບຸກມາກ'; + + @override + String get pdfEnterPageNumberLabel => r'ໃສ່ເລກໜ້າ'; + + @override + String get pdfGoToPageLabel => r'ໄປທີ່ໜ້າ'; + + @override + String get pdfHyperlinkContentLabel => r'ທ່ານຕ້ອງການເປີດໜ້ານີ້ບໍ?'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ຍົກເລີກ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ເປີດ'; + + @override + String get pdfHyperlinkLabel => r'ເປີດໜ້າເວັບ'; + + @override + String get pdfInvalidPageNumberLabel => r'ກະລຸນາໃສ່ຕົວເລກທີ່ຖືກຕ້ອງ'; + + @override + String get pdfNoBookmarksLabel => r'ບໍ່ມີບຸກມາກ'; + + @override + String get pdfPaginationDialogCancelLabel => r'ຍົກເລີກ'; + + @override + String get pdfPaginationDialogOkLabel => r'ຕົກ​ລົງ'; + + @override + String get pdfPasswordDialogCancelLabel => r'ຍົກເລີກ'; + + @override + String get pdfPasswordDialogOpenLabel => r'ເປີດ'; + + @override + String get pdfScrollStatusOfLabel => r'ຂອງ'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ລ້າງ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'ແຕ້ມລາຍເຊັນຂອງເຈົ້າ'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'ສີປາກກາ'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'ບັນທຶກ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'ສຳເນົາ'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'ຈຸດເດັ່ນ'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'ຂີດສຸ້ມ'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'ຂີດໄຂ້'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'ຂີດເສັ້ນລຸ່ມ'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'ຣາມາດານ'; + + @override + String get rowsPerPageDataPagerLabel => r'ແຖວຕໍ່ຫນ້າ'; + + @override + String get safarLabel => r'ຊາຟາ'; + + @override + String get searchDataGridFilteringLabel => r'ຊອກຫາ'; + + @override + String get selectAllDataGridFilteringLabel => r'ເລືອກ​ທັງ​ຫມົດ'; + + @override + String get series => r'ຊຸດ'; + + @override + String get shaabanLabel => r'ຊາອາບານ'; + + @override + String get shawwalLabel => r'ຊະວາວ'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'ຈູມ. I'; + + @override + String get shortJumada2Label => r'ຈູມ. II'; + + @override + String get shortMuharramLabel => r'ມ.'; + + @override + String get shortRabi1Label => r'ຣາບີ. I'; + + @override + String get shortRabi2Label => r'ຣາບີ. II'; + + @override + String get shortRajabLabel => r'ລາດ.'; + + @override + String get shortRamadanLabel => r'ຣາມ.'; + + @override + String get shortSafarLabel => r'ປອດໄພ.'; + + @override + String get shortShaabanLabel => r'ຊາ.'; + + @override + String get shortShawwalLabel => r'ແຊວ.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'ສະແດງແຖວຢູ່ບ່ອນ'; + + @override + String get sortAToZDataGridFilteringLabel => r'ຈັດຮຽງ A ຫາ Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'ຄັດແລະການກັ່ນຕອງ'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'ຈັດຮຽງໃຫຍ່ສຸດຫານ້ອຍສຸດ'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'ຈັດຮຽງໃໝ່ສຸດຫາເກົ່າທີ່ສຸດ'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'ຈັດຮຽງເກົ່າສຸດຫາໃໝ່ສຸດ'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'ຈັດລຽງນ້ອຍສຸດຫາໃຫຍ່ທີ່ສຸດ'; + + @override + String get sortZToADataGridFilteringLabel => r'ຈັດຮຽງ Z ຫາ A'; + + @override + String get textFiltersDataGridFilteringLabel => r'ຕົວກອງຂໍ້ຄວາມ'; + + @override + String get todayLabel => r'ມື້​ນີ້'; + + @override + String get weeknumberLabel => r'ອາທິດ'; +} + +/// The translations for Lithuanian (`lt`). +class SfLocalizationsLt extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsLt class + const SfLocalizationsLt({String localeName = 'lt'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Po to'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Po arba lygus'; + + @override + String get allDayLabel => r'Visą dieną'; + + @override + String get allowedViewDayLabel => r'Diena'; + + @override + String get allowedViewMonthLabel => r'Mėnuo'; + + @override + String get allowedViewScheduleLabel => r'Tvarkaraštis'; + + @override + String get allowedViewTimelineDayLabel => r'Laiko juostos diena'; + + @override + String get allowedViewTimelineMonthLabel => r'Laiko juostos mėnuo'; + + @override + String get allowedViewTimelineWeekLabel => r'Laiko juostos savaitė'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Laiko juostos darbo savaitė'; + + @override + String get allowedViewWeekLabel => r'Savaitė'; + + @override + String get allowedViewWorkWeekLabel => r'Darbo savaitė'; + + @override + String get andDataGridFilteringLabel => r'Ir'; + + @override + String get beforeDataGridFilteringLabel => r'Prieš'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Prieš arba lygus'; + + @override + String get beginsWithDataGridFilteringLabel => r'Prasideda su'; + + @override + String get cancelDataGridFilteringLabel => r'Atšaukti'; + + @override + String get clearFilterDataGridFilteringLabel => r'Išvalyti filtrą'; + + @override + String get containsDataGridFilteringLabel => r'Sudėtyje yra'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Datos filtrai'; + + @override + String get daySpanCountLabel => r'Diena'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Neprasideda su'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Sudėtyje nėra'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Nesibaigia'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Nelygu'; + + @override + String get emptyDataGridFilteringLabel => r'Tuščia'; + + @override + String get endsWithDataGridFilteringLabel => r'Baigiasi su'; + + @override + String get equalsDataGridFilteringLabel => r'Lygu'; + + @override + String get fromDataGridFilteringLabel => r'Iš'; + + @override + String get greaterThanDataGridFilteringLabel => r'Geresnis negu'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Didesnis nei lygus'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-Thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Mažiau nei'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Mažiau nei arba lygus'; + + @override + String get muharramLabel => r'Muharramas'; + + @override + String get noEventsCalendarLabel => r'Jokių įvykių'; + + @override + String get noMatchesDataGridFilteringLabel => r'Jokių atitikmenų'; + + @override + String get noSelectedDateCalendarLabel => r'Nėra pasirinktos datos'; + + @override + String get notEmptyDataGridFilteringLabel => r'Ne Tuščias'; + + @override + String get notNullDataGridFilteringLabel => r'Ne Null'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Skaičių filtrai'; + + @override + String get ofDataPagerLabel => r'apie'; + + @override + String get okDataGridFilteringLabel => r'Gerai'; + + @override + String get orDataGridFilteringLabel => r'Arba'; + + @override + String get pagesDataPagerLabel => r'puslapių'; + + @override + String get passwordDialogContentLabel => + r'Įveskite slaptažodį, kad atidarytumėte šį PDF failą'; + + @override + String get passwordDialogHeaderTextLabel => r'Apsaugotas slaptažodžiu'; + + @override + String get passwordDialogHintTextLabel => r'Įvesti slaptažodį'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Neteisingas slaptažodis'; + + @override + String get pdfBookmarksLabel => r'Žymės'; + + @override + String get pdfEnterPageNumberLabel => r'Įveskite puslapio numerį'; + + @override + String get pdfGoToPageLabel => r'Eiti į puslapį'; + + @override + String get pdfHyperlinkContentLabel => r'Ar norite atidaryti puslapį adresu'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ATŠAUKTI'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ATVIRAS'; + + @override + String get pdfHyperlinkLabel => r'Atidarykite tinklalapį'; + + @override + String get pdfInvalidPageNumberLabel => r'Įveskite tinkamą numerį'; + + @override + String get pdfNoBookmarksLabel => r'Žymių nerasta'; + + @override + String get pdfPaginationDialogCancelLabel => r'ATŠAUKTI'; + + @override + String get pdfPaginationDialogOkLabel => r'Gerai'; + + @override + String get pdfPasswordDialogCancelLabel => r'ATŠAUKTI'; + + @override + String get pdfPasswordDialogOpenLabel => r'ATVIRAS'; + + @override + String get pdfScrollStatusOfLabel => r'apie'; + + @override + String get pdfSignaturePadDialogClearLabel => r'IŠVALYTI'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Nupiešk savo parašą'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Rašiklio spalva'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'SUTAUPYTI'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopijuoti'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Paryškinti'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'raibulis'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Perbraukta'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Pabraukite'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => r'Rabis al-Thanis'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadanas'; + + @override + String get rowsPerPageDataPagerLabel => r'eilutėsPerPuslapį'; + + @override + String get safarLabel => r'Safaras'; + + @override + String get searchDataGridFilteringLabel => r'Paieška'; + + @override + String get selectAllDataGridFilteringLabel => r'Pasirinkti viską'; + + @override + String get series => r'Serija'; + + @override + String get shaabanLabel => r'Šaabanas'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. aš'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. aš'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Rodyti eilutes kur'; + + @override + String get sortAToZDataGridFilteringLabel => r'Rūšiuoti nuo A iki Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Rūšiuoti ir filtruoti'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Rūšiuoti nuo didžiausio iki mažiausio'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Rūšiuoti nuo naujausių iki seniausių'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Rūšiuoti nuo seniausių iki naujausių'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Rūšiuoti nuo mažiausio iki didžiausio'; + + @override + String get sortZToADataGridFilteringLabel => r'Rūšiuoti nuo Z iki A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Teksto filtrai'; + + @override + String get todayLabel => r'Šiandien'; + + @override + String get weeknumberLabel => r'Savaitė'; +} + +/// The translations for Latvian (`lv`). +class SfLocalizationsLv extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsLv class + const SfLocalizationsLv({String localeName = 'lv'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Pēc'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Pēc vai vienāds'; + + @override + String get allDayLabel => r'Visu dienu'; + + @override + String get allowedViewDayLabel => r'diena'; + + @override + String get allowedViewMonthLabel => r'Mēnesis'; + + @override + String get allowedViewScheduleLabel => r'Grafiks'; + + @override + String get allowedViewTimelineDayLabel => r'Laika skalas diena'; + + @override + String get allowedViewTimelineMonthLabel => r'Laika skalas mēnesis'; + + @override + String get allowedViewTimelineWeekLabel => r'Laika skalas nedēļa'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Laika skala Darba nedēļa'; + + @override + String get allowedViewWeekLabel => r'nedēļa'; + + @override + String get allowedViewWorkWeekLabel => r'Darba nedēļa'; + + @override + String get andDataGridFilteringLabel => r'Un'; + + @override + String get beforeDataGridFilteringLabel => r'Pirms'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Pirms vai vienāds'; + + @override + String get beginsWithDataGridFilteringLabel => r'Sākas ar'; + + @override + String get cancelDataGridFilteringLabel => r'Atcelt'; + + @override + String get clearFilterDataGridFilteringLabel => r'Notīrīt filtru'; + + @override + String get containsDataGridFilteringLabel => r'Satur'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Datumu filtri'; + + @override + String get daySpanCountLabel => r'diena'; + + @override + String get dhualhiLabel => r'Dhu al-Hidžja'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Nesākas ar'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Nesatur'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Nebeidzas ar'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Nav vienāds'; + + @override + String get emptyDataGridFilteringLabel => r'Tukšs'; + + @override + String get endsWithDataGridFilteringLabel => r'Beidzas ar'; + + @override + String get equalsDataGridFilteringLabel => r'Vienāds'; + + @override + String get fromDataGridFilteringLabel => r'No'; + + @override + String get greaterThanDataGridFilteringLabel => r'Lielāks par'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Lielāks par vai vienāds'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-tani'; + + @override + String get lessThanDataGridFilteringLabel => r'Mazāk nekā'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Mazāk nekā vai vienāds'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Nav notikumu'; + + @override + String get noMatchesDataGridFilteringLabel => r'nav atbilstību'; + + @override + String get noSelectedDateCalendarLabel => r'Nav izvēlēts datums'; + + @override + String get notEmptyDataGridFilteringLabel => r'Nav tukšs'; + + @override + String get notNullDataGridFilteringLabel => r'Nav Null'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Skaitļu filtri'; + + @override + String get ofDataPagerLabel => r'no'; + + @override + String get okDataGridFilteringLabel => r'labi'; + + @override + String get orDataGridFilteringLabel => r'Or'; + + @override + String get pagesDataPagerLabel => r'lapas'; + + @override + String get passwordDialogContentLabel => + r'Ievadiet paroli, lai atvērtu šo PDF failu'; + + @override + String get passwordDialogHeaderTextLabel => r'Aizsargāts ar paroli'; + + @override + String get passwordDialogHintTextLabel => r'Ievadi paroli'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Nepareiza parole'; + + @override + String get pdfBookmarksLabel => r'Grāmatzīmes'; + + @override + String get pdfEnterPageNumberLabel => r'Ievadiet lapas numuru'; + + @override + String get pdfGoToPageLabel => r'Iet uz lapu'; + + @override + String get pdfHyperlinkContentLabel => r'Vai vēlaties atvērt lapu vietnē'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ATCELT'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ATVĒRTS'; + + @override + String get pdfHyperlinkLabel => r'Atveriet tīmekļa lapu'; + + @override + String get pdfInvalidPageNumberLabel => r'Lūdzu, ievadiet derīgu numuru'; + + @override + String get pdfNoBookmarksLabel => r'Netika atrasta neviena grāmatzīme'; + + @override + String get pdfPaginationDialogCancelLabel => r'ATCELT'; + + @override + String get pdfPaginationDialogOkLabel => r'labi'; + + @override + String get pdfPasswordDialogCancelLabel => r'ATCELT'; + + @override + String get pdfPasswordDialogOpenLabel => r'ATVĒRTS'; + + @override + String get pdfScrollStatusOfLabel => r'no'; + + @override + String get pdfSignaturePadDialogClearLabel => r'DZĪRS'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Uzzīmējiet savu parakstu'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Pildspalvas krāsa'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'SAGLABĀT'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopēt'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Izcelt'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Svītrains'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Pārsvītrots'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Pasvītrot'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => r'Rabī al-tani'; + + @override + String get rajabLabel => r'Radžabs'; + + @override + String get ramadanLabel => r'Ramadāns'; + + @override + String get rowsPerPageDataPagerLabel => r'Rindas vienā lapā'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Meklēt'; + + @override + String get selectAllDataGridFilteringLabel => r'Izvēlēties visus'; + + @override + String get series => r'sērija'; + + @override + String get shaabanLabel => r'Šaabans'; + + @override + String get shawwalLabel => r'Šovals'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. es'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. es'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Ša.'; + + @override + String get shortShawwalLabel => r'Šo.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Rādīt rindas, kur'; + + @override + String get sortAToZDataGridFilteringLabel => r'Kārtot no A līdz Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Kārtot un filtrēt'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Kārtot no lielākā uz mazāko'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Kārtot no jaunākajiem uz vecākajiem'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Kārtot no vecāko uz jaunāko'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Kārtot no mazākā uz lielāko'; + + @override + String get sortZToADataGridFilteringLabel => r'Kārtot no Z līdz A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Teksta filtri'; + + @override + String get todayLabel => r'Šodien'; + + @override + String get weeknumberLabel => r'nedēļa'; +} + +/// The translations for Macedonian (`mk`). +class SfLocalizationsMk extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsMk class + const SfLocalizationsMk({String localeName = 'mk'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'По'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'По Или еднакви'; + + @override + String get allDayLabel => r'Цел ден'; + + @override + String get allowedViewDayLabel => r'Ден'; + + @override + String get allowedViewMonthLabel => r'Месец'; + + @override + String get allowedViewScheduleLabel => r'Распоред'; + + @override + String get allowedViewTimelineDayLabel => r'Ден на времеплов'; + + @override + String get allowedViewTimelineMonthLabel => r'Месец на времеплов'; + + @override + String get allowedViewTimelineWeekLabel => r'Недела на времеплов'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Временска рамка Работна недела'; + + @override + String get allowedViewWeekLabel => r'Недела'; + + @override + String get allowedViewWorkWeekLabel => r'Работна недела'; + + @override + String get andDataGridFilteringLabel => r'И'; + + @override + String get beforeDataGridFilteringLabel => r'Пред'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Пред или еднакви'; + + @override + String get beginsWithDataGridFilteringLabel => r'Започнува со'; + + @override + String get cancelDataGridFilteringLabel => r'Откажи'; + + @override + String get clearFilterDataGridFilteringLabel => r'Исчистете го филтерот'; + + @override + String get containsDataGridFilteringLabel => r'Содржи'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Филтри за датум'; + + @override + String get daySpanCountLabel => r'Ден'; + + @override + String get dhualhiLabel => r'Зу ал Хиџа'; + + @override + String get dhualqiLabel => r'Зу ал-Кида'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Не започнува со'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Не содржи'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Не завршува со'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Не е еднакво'; + + @override + String get emptyDataGridFilteringLabel => r'Празен'; + + @override + String get endsWithDataGridFilteringLabel => r'Завршува со'; + + @override + String get equalsDataGridFilteringLabel => r'Еднакви'; + + @override + String get fromDataGridFilteringLabel => r'Од'; + + @override + String get greaterThanDataGridFilteringLabel => r'Поголема од'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Поголема од Или еднаква'; + + @override + String get jumada1Label => r'Џумада ал-аввал'; + + @override + String get jumada2Label => r'Џумада ал-тани'; + + @override + String get lessThanDataGridFilteringLabel => r'Помалку од'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Помалку од или еднакви'; + + @override + String get muharramLabel => r'Мухарам'; + + @override + String get noEventsCalendarLabel => r'Нема настани'; + + @override + String get noMatchesDataGridFilteringLabel => r'Нема совпаѓања'; + + @override + String get noSelectedDateCalendarLabel => r'Нема избран датум'; + + @override + String get notEmptyDataGridFilteringLabel => r'Не е празен'; + + @override + String get notNullDataGridFilteringLabel => r'Не Нулта'; + + @override + String get nullDataGridFilteringLabel => r'Нула'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Филтри за броеви'; + + @override + String get ofDataPagerLabel => r'на'; + + @override + String get okDataGridFilteringLabel => r'добро'; + + @override + String get orDataGridFilteringLabel => r'Или'; + + @override + String get pagesDataPagerLabel => r'страници'; + + @override + String get passwordDialogContentLabel => + r'Внесете ја лозинката за да ја отворите оваа PDF-датотека'; + + @override + String get passwordDialogHeaderTextLabel => r'Заштитено со лозинка'; + + @override + String get passwordDialogHintTextLabel => r'Внесете ја лозинката'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Невалидна лозинка'; + + @override + String get pdfBookmarksLabel => r'Обележувачи'; + + @override + String get pdfEnterPageNumberLabel => r'Внесете го бројот на страницата'; + + @override + String get pdfGoToPageLabel => r'Оди на страна'; + + @override + String get pdfHyperlinkContentLabel => + r'Дали сакате да ја отворите страницата на'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ОТКАЖИ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ОТВОРЕНО'; + + @override + String get pdfHyperlinkLabel => r'Отворете ја веб-страницата'; + + @override + String get pdfInvalidPageNumberLabel => r'Внесете важечки број'; + + @override + String get pdfNoBookmarksLabel => r'Не се пронајдени обележувачи'; + + @override + String get pdfPaginationDialogCancelLabel => r'Откажи'; + + @override + String get pdfPaginationDialogOkLabel => r'Добро'; + + @override + String get pdfPasswordDialogCancelLabel => r'Откажи'; + + @override + String get pdfPasswordDialogOpenLabel => r'Отвори'; + + @override + String get pdfScrollStatusOfLabel => r'на'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Исчисти'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'Нацртајте го вашиот потпис'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Боја на пенкало'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Зачувај'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Копирај'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Истакни'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Брановито'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Прецртај'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Подвлечи'; + + @override + String get rabi1Label => r'Раби ал-аввал'; + + @override + String get rabi2Label => r'Раби ал-тани'; + + @override + String get rajabLabel => r'Раџаб'; + + @override + String get ramadanLabel => r'Рамазан'; + + @override + String get rowsPerPageDataPagerLabel => r'Редови по страница'; + + @override + String get safarLabel => r'Сафар'; + + @override + String get searchDataGridFilteringLabel => r'Пребарување'; + + @override + String get selectAllDataGridFilteringLabel => r'Селектирај се'; + + @override + String get series => r'Серии'; + + @override + String get shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шавал'; + + @override + String get shortDhualhiLabel => r'Дул-Х'; + + @override + String get shortDhualqiLabel => + r'Ду' + "'" + r'л-К'; + + @override + String get shortJumada1Label => r'Џум. Јас'; + + @override + String get shortJumada2Label => r'Џум. II'; + + @override + String get shortMuharramLabel => r'Мух.'; + + @override + String get shortRabi1Label => r'Раби. Јас'; + + @override + String get shortRabi2Label => r'Раби. II'; + + @override + String get shortRajabLabel => r'Раџ.'; + + @override + String get shortRamadanLabel => r'Овен.'; + + @override + String get shortSafarLabel => r'Саф.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шо.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Прикажи ги редовите каде'; + + @override + String get sortAToZDataGridFilteringLabel => r'Подреди од А до Ш'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Сортирање и филтрирање'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Подреди од најголем до најмал'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Подреди најновото до најстарото'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Подреди од најстарото до најновото'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Подреди од најмал до најголем'; + + @override + String get sortZToADataGridFilteringLabel => r'Подреди од Ш до А'; + + @override + String get textFiltersDataGridFilteringLabel => r'Филтри за текст'; + + @override + String get todayLabel => r'Денес'; + + @override + String get weeknumberLabel => r'Недела'; +} + +/// The translations for Malayalam (`ml`). +class SfLocalizationsMl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsMl class + const SfLocalizationsMl({String localeName = 'ml'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'ശേഷം'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'ശേഷം അല്ലെങ്കിൽ തുല്യം'; + + @override + String get allDayLabel => r'ദിവസം മുഴുവൻ'; + + @override + String get allowedViewDayLabel => r'ദിവസം'; + + @override + String get allowedViewMonthLabel => r'മാസം'; + + @override + String get allowedViewScheduleLabel => r'പട്ടിക'; + + @override + String get allowedViewTimelineDayLabel => r'ടൈംലൈൻ ദിവസം'; + + @override + String get allowedViewTimelineMonthLabel => r'ടൈംലൈൻ മാസം'; + + @override + String get allowedViewTimelineWeekLabel => r'ടൈംലൈൻ ആഴ്ച'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'ടൈംലൈൻ പ്രവൃത്തി ആഴ്ച'; + + @override + String get allowedViewWeekLabel => r'ആഴ്ച'; + + @override + String get allowedViewWorkWeekLabel => r'പ്രവൃത്തി ആഴ്ച'; + + @override + String get andDataGridFilteringLabel => r'ഒപ്പം'; + + @override + String get beforeDataGridFilteringLabel => r'മുമ്പ്'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'മുമ്പോ തുല്യമോ'; + + @override + String get beginsWithDataGridFilteringLabel => r'കൂടെ തുടങ്ങുന്നു'; + + @override + String get cancelDataGridFilteringLabel => r'റദ്ദാക്കുക'; + + @override + String get clearFilterDataGridFilteringLabel => r'ഫിൽട്ടർ മായ്‌ക്കുക'; + + @override + String get containsDataGridFilteringLabel => r'അടങ്ങിയിരിക്കുന്നു'; + + @override + String get dateFiltersDataGridFilteringLabel => r'തീയതി ഫിൽട്ടറുകൾ'; + + @override + String get daySpanCountLabel => r'ദിവസം'; + + @override + String get dhualhiLabel => r'ദു അൽ-ഹിജ്ജ'; + + @override + String get dhualqiLabel => r'ദു അൽ-ക്വിദ'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'കൂടെ തുടങ്ങുന്നില്ല'; + + @override + String get doesNotContainDataGridFilteringLabel => r'അടങ്ങിയിട്ടില്ല'; + + @override + String get doesNotEndWithDataGridFilteringLabel => + r'കൊണ്ട് അവസാനിക്കുന്നില്ല'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'തുല്യമല്ല'; + + @override + String get emptyDataGridFilteringLabel => r'ശൂന്യം'; + + @override + String get endsWithDataGridFilteringLabel => r'കൂടെ അവസാനിക്കുന്നു'; + + @override + String get equalsDataGridFilteringLabel => r'തുല്യമാണ്'; + + @override + String get fromDataGridFilteringLabel => r'നിന്ന്'; + + @override + String get greaterThanDataGridFilteringLabel => r'അതിലും വലുത്'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'വലുത് അല്ലെങ്കിൽ തുല്യം'; + + @override + String get jumada1Label => r'ജുമാദ അൽ അവ്വൽ'; + + @override + String get jumada2Label => r'ജുമാദ അൽതാനി'; + + @override + String get lessThanDataGridFilteringLabel => r'അതിൽ കുറവ്'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + r'കുറവ് അല്ലെങ്കിൽ തുല്യം'; + + @override + String get muharramLabel => r'മുഹറം'; + + @override + String get noEventsCalendarLabel => r'ഇവന്റുകളൊന്നുമില്ല'; + + @override + String get noMatchesDataGridFilteringLabel => r'പൊരുത്തങ്ങളൊന്നുമില്ല'; + + @override + String get noSelectedDateCalendarLabel => r'തിരഞ്ഞെടുത്ത തീയതിയില്ല'; + + @override + String get notEmptyDataGridFilteringLabel => r'ശൂന്യമല്ല'; + + @override + String get notNullDataGridFilteringLabel => r'നൾ അല്ല'; + + @override + String get nullDataGridFilteringLabel => r'ശൂന്യം'; + + @override + String get numberFiltersDataGridFilteringLabel => r'നമ്പർ ഫിൽട്ടറുകൾ'; + + @override + String get ofDataPagerLabel => r'യുടെ'; + + @override + String get okDataGridFilteringLabel => r'ശരി'; + + @override + String get orDataGridFilteringLabel => r'അഥവാ'; + + @override + String get pagesDataPagerLabel => r'പേജുകൾ'; + + @override + String get passwordDialogContentLabel => + r'ഈ PDF ഫയൽ തുറക്കാൻ പാസ്‌വേഡ് നൽകുക'; + + @override + String get passwordDialogHeaderTextLabel => r'പാസ്‌വേഡ് പരിരക്ഷിതം'; + + @override + String get passwordDialogHintTextLabel => r'പാസ്വേഡ് നല്കൂ'; + + @override + String get passwordDialogInvalidPasswordLabel => r'അസാധുവായ പാസ്‌വേഡ്'; + + @override + String get pdfBookmarksLabel => r'ബുക്ക്മാർക്കുകൾ'; + + @override + String get pdfEnterPageNumberLabel => r'പേജ് നമ്പർ നൽകുക'; + + @override + String get pdfGoToPageLabel => r'പേജിലേക്ക് പോകുക'; + + @override + String get pdfHyperlinkContentLabel => r'എന്നതിൽ പേജ് തുറക്കണോ?'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'റദ്ദാക്കുക'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'തുറക്കുക'; + + @override + String get pdfHyperlinkLabel => r'വെബ് പേജ് തുറക്കുക'; + + @override + String get pdfInvalidPageNumberLabel => r'ദയവായി ഒരു സാധുവായ നമ്പർ നൽകുക'; + + @override + String get pdfNoBookmarksLabel => r'ബുക്ക്‌മാർക്കുകളൊന്നും കണ്ടെത്തിയില്ല'; + + @override + String get pdfPaginationDialogCancelLabel => r'റദ്ദാക്കുക'; + + @override + String get pdfPaginationDialogOkLabel => r'ശരി'; + + @override + String get pdfPasswordDialogCancelLabel => r'റദ്ദാക്കുക'; + + @override + String get pdfPasswordDialogOpenLabel => r'തുറക്കുക'; + + @override + String get pdfScrollStatusOfLabel => r'യുടെ'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ക്ലിയർ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'നിങ്ങളുടെ ഒപ്പ് വരയ്ക്കുക'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'പേന നിറം'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'രക്ഷിക്കും'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'പകർത്തുക'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'ഹൈലൈറ്റ് ചെയ്യുക'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'സ്ക്വിഗ്ലി'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'സ്ട്രൈക്ക്ത്രൂ'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'അടിവരയിടുക'; + + @override + String get rabi1Label => r'റബീഉൽ അവ്വൽ'; + + @override + String get rabi2Label => r'റാബി അൽതാനി'; + + @override + String get rajabLabel => r'റജബ്'; + + @override + String get ramadanLabel => r'റമദാൻ'; + + @override + String get rowsPerPageDataPagerLabel => r'ഓരോ പേജിലും വരികൾ'; + + @override + String get safarLabel => r'സഫർ'; + + @override + String get searchDataGridFilteringLabel => r'തിരയുക'; + + @override + String get selectAllDataGridFilteringLabel => r'എല്ലാം തിരഞ്ഞെടുക്കുക'; + + @override + String get series => r'പരമ്പര'; + + @override + String get shaabanLabel => r'ശഅബാൻ'; + + @override + String get shawwalLabel => r'ശവ്വാൽ'; + + @override + String get shortDhualhiLabel => r'ദുൽ-എച്ച്'; + + @override + String get shortDhualqiLabel => r'ദുൽ-ക്യു'; + + @override + String get shortJumada1Label => r'ജം. ഐ'; + + @override + String get shortJumada2Label => r'ജം. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'റാബി. ഐ'; + + @override + String get shortRabi2Label => r'റാബി. II'; + + @override + String get shortRajabLabel => r'രാജ്.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'സാഫ്.'; + + @override + String get shortShaabanLabel => r'ഷാ.'; + + @override + String get shortShawwalLabel => r'ഷാ.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'എവിടെ വരികൾ കാണിക്കുക'; + + @override + String get sortAToZDataGridFilteringLabel => r'A മുതൽ Z വരെ അടുക്കുക'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'അടുക്കി ഫിൽട്ടർ ചെയ്യുക'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'ഏറ്റവും വലുത് മുതൽ ചെറുത് വരെ അടുക്കുക'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'ഏറ്റവും പുതിയത് മുതൽ പഴയത് വരെ അടുക്കുക'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'ഏറ്റവും പഴയത് മുതൽ പുതിയത് വരെ അടുക്കുക'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'ചെറുത് മുതൽ വലുത് വരെ അടുക്കുക'; + + @override + String get sortZToADataGridFilteringLabel => r'Z മുതൽ A വരെ അടുക്കുക'; + + @override + String get textFiltersDataGridFilteringLabel => r'ടെക്സ്റ്റ് ഫിൽട്ടറുകൾ'; + + @override + String get todayLabel => r'ഇന്ന്'; + + @override + String get weeknumberLabel => r'ആഴ്ച'; +} + +/// The translations for Mongolian (`mn`). +class SfLocalizationsMn extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsMn class + const SfLocalizationsMn({String localeName = 'mn'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Дараа'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Дараа эсвэл тэнцүү'; + + @override + String get allDayLabel => r'Бүх өдөр'; + + @override + String get allowedViewDayLabel => r'Өдөр'; + + @override + String get allowedViewMonthLabel => r'Сар'; + + @override + String get allowedViewScheduleLabel => r'Хуваарь'; + + @override + String get allowedViewTimelineDayLabel => r'Хугацааны өдөр'; + + @override + String get allowedViewTimelineMonthLabel => r'Он цагийн хэлхээсийн сар'; + + @override + String get allowedViewTimelineWeekLabel => r'Хугацааны долоо хоног'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Ажлын долоо хоног'; + + @override + String get allowedViewWeekLabel => r'Долоо хоног'; + + @override + String get allowedViewWorkWeekLabel => r'Ажлын долоо хоног'; + + @override + String get andDataGridFilteringLabel => r'Тэгээд'; + + @override + String get beforeDataGridFilteringLabel => r'Өмнө нь'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Өмнө эсвэл Тэнцүү'; + + @override + String get beginsWithDataGridFilteringLabel => r'-аар эхэлдэг'; + + @override + String get cancelDataGridFilteringLabel => r'Цуцлах'; + + @override + String get clearFilterDataGridFilteringLabel => r'Шүүлтүүрийг арилгах'; + + @override + String get containsDataGridFilteringLabel => r'агуулсан'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Огноо шүүлтүүрүүд'; + + @override + String get daySpanCountLabel => r'Өдөр'; + + @override + String get dhualhiLabel => r'Зул Хижжа'; + + @override + String get dhualqiLabel => r'Зу аль-Кида'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'-аас эхэлдэггүй'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Агуулаагүй'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Үүгээр дуусахгүй'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Тэнцэхгүй'; + + @override + String get emptyDataGridFilteringLabel => r'Хоосон'; + + @override + String get endsWithDataGridFilteringLabel => r'-ээр төгсдөг'; + + @override + String get equalsDataGridFilteringLabel => r'Тэнцүү'; + + @override + String get fromDataGridFilteringLabel => r'-аас'; + + @override + String get greaterThanDataGridFilteringLabel => r'Илүү их'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Их буюу тэнцүү'; + + @override + String get jumada1Label => r'Жумада аль-аввал'; + + @override + String get jumada2Label => r'Жумада аль-Тани'; + + @override + String get lessThanDataGridFilteringLabel => r'Ээс бага'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'-ээс бага эсвэл тэнцүү'; + + @override + String get muharramLabel => r'Мухаррам'; + + @override + String get noEventsCalendarLabel => r'Үйл явдал алга'; + + @override + String get noMatchesDataGridFilteringLabel => r'Тохирох зүйл алга'; + + @override + String get noSelectedDateCalendarLabel => r'Сонгосон огноо алга'; + + @override + String get notEmptyDataGridFilteringLabel => r'Хоосон биш'; + + @override + String get notNullDataGridFilteringLabel => r'Null биш'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Тооны шүүлтүүрүүд'; + + @override + String get ofDataPagerLabel => r'ийн'; + + @override + String get okDataGridFilteringLabel => r'БОЛЖ БАЙНА УУ'; + + @override + String get orDataGridFilteringLabel => r'Эсвэл'; + + @override + String get pagesDataPagerLabel => r'хуудаснууд'; + + @override + String get passwordDialogContentLabel => + r'Энэ PDF файлыг нээхийн тулд нууц үгээ оруулна уу'; + + @override + String get passwordDialogHeaderTextLabel => r'Нууц үгээр хамгаалагдсан'; + + @override + String get passwordDialogHintTextLabel => r'Нууц үгээ оруулна'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Нууц үг буруу байна'; + + @override + String get pdfBookmarksLabel => r'Хавчуурга'; + + @override + String get pdfEnterPageNumberLabel => r'Хуудасны дугаарыг оруулна уу'; + + @override + String get pdfGoToPageLabel => r'Хуудас руу оч'; + + @override + String get pdfHyperlinkContentLabel => + r'хаягаар хуудсыг нээхийг хүсэж байна уу'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'Цуцлах'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'НЭЭЛТТЭЙ'; + + @override + String get pdfHyperlinkLabel => r'Вэб хуудсыг нээх'; + + @override + String get pdfInvalidPageNumberLabel => r'Хүчинтэй дугаар оруулна уу'; + + @override + String get pdfNoBookmarksLabel => r'Хавчуурга олдсонгүй'; + + @override + String get pdfPaginationDialogCancelLabel => r'Цуцлах'; + + @override + String get pdfPaginationDialogOkLabel => r'Зөвшөөрөх'; + + @override + String get pdfPasswordDialogCancelLabel => r'Цуцлах'; + + @override + String get pdfPasswordDialogOpenLabel => r'НЭЭЛТТЭЙ'; + + @override + String get pdfScrollStatusOfLabel => r'-ийн'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ЦЭВЭРЛЭХ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Гарын үсгээ зур'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Үзэгний өнгө'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'ХАДГАЛАХ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Хуулах'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Онцлох'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Муухай'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Зураас'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Доогуур зур'; + + @override + String get rabi1Label => + r'Раби' + "'" + r' аль-аввал'; + + @override + String get rabi2Label => + r'Раби' + "'" + r' аль-Тани'; + + @override + String get rajabLabel => r'Ражаб'; + + @override + String get ramadanLabel => r'Рамадан'; + + @override + String get rowsPerPageDataPagerLabel => r'Хуудас бүрийн мөр'; + + @override + String get safarLabel => r'Сафар'; + + @override + String get searchDataGridFilteringLabel => r'Хайх'; + + @override + String get selectAllDataGridFilteringLabel => r'Бүгдийг сонгох'; + + @override + String get series => r'Цуврал'; + + @override + String get shaabanLabel => r'Шаабан'; + + @override + String get shawwalLabel => r'Шаввал'; + + @override + String get shortDhualhiLabel => r'Зуль-Х'; + + @override + String get shortDhualqiLabel => r'Зуль-К'; + + @override + String get shortJumada1Label => r'Jum. I'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Мух.'; + + @override + String get shortRabi1Label => r'Раби. I'; + + @override + String get shortRabi2Label => r'Раби. II'; + + @override + String get shortRajabLabel => r'Раж.'; + + @override + String get shortRamadanLabel => r'Рам.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Ша.'; + + @override + String get shortShawwalLabel => r'Шоу.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Хаана мөрүүдийг харуул'; + + @override + String get sortAToZDataGridFilteringLabel => r'А-аас Я хүртэл эрэмбэлэх'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Ангилах, шүүх'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Томоос жижиг рүү ангилах'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Хамгийн шинэээс хамгийн хуучин руу эрэмбэлэх'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Хамгийн хуучинаас шинэ рүү эрэмбэлэх'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Хамгийн жижигээс томд эрэмбэлэх'; + + @override + String get sortZToADataGridFilteringLabel => r'Z-ээс А хүртэл эрэмбэлэх'; + + @override + String get textFiltersDataGridFilteringLabel => r'Текст шүүлтүүрүүд'; + + @override + String get todayLabel => r'Өнөөдөр'; + + @override + String get weeknumberLabel => r'Долоо хоног'; +} + +/// The translations for Marathi (`mr`). +class SfLocalizationsMr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsMr class + const SfLocalizationsMr({String localeName = 'mr'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'नंतर'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'नंतर किंवा समान'; + + @override + String get allDayLabel => r'संपूर्ण दिवस'; + + @override + String get allowedViewDayLabel => r'दिवस'; + + @override + String get allowedViewMonthLabel => r'महिना'; + + @override + String get allowedViewScheduleLabel => r'वेळापत्रक'; + + @override + String get allowedViewTimelineDayLabel => r'टाइमलाइन दिवस'; + + @override + String get allowedViewTimelineMonthLabel => r'टाइमलाइन महिना'; + + @override + String get allowedViewTimelineWeekLabel => r'टाइमलाइन आठवडा'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कामाचा आठवडा'; + + @override + String get allowedViewWeekLabel => r'आठवडा'; + + @override + String get allowedViewWorkWeekLabel => r'कामाचा आठवडा'; + + @override + String get andDataGridFilteringLabel => r'आणि'; + + @override + String get beforeDataGridFilteringLabel => r'आधी'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'आधी किंवा समान'; + + @override + String get beginsWithDataGridFilteringLabel => r'ने सुरुवात होते'; + + @override + String get cancelDataGridFilteringLabel => r'रद्द करा'; + + @override + String get clearFilterDataGridFilteringLabel => r'फिल्टर साफ करा'; + + @override + String get containsDataGridFilteringLabel => r'समाविष्ट आहे'; + + @override + String get dateFiltersDataGridFilteringLabel => r'तारीख फिल्टर'; + + @override + String get daySpanCountLabel => r'दिवस'; + + @override + String get dhualhiLabel => r'धु अल-हिज्जा'; + + @override + String get dhualqiLabel => r'धु अल-किदाह'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'ने सुरुवात होत नाही'; + + @override + String get doesNotContainDataGridFilteringLabel => r'समाविष्ट नाही'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'सह संपत नाही'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'समान नाही'; + + @override + String get emptyDataGridFilteringLabel => r'रिकामे'; + + @override + String get endsWithDataGridFilteringLabel => r'यासह समाप्त होते'; + + @override + String get equalsDataGridFilteringLabel => r'बरोबरी'; + + @override + String get fromDataGridFilteringLabel => r'पासून'; + + @override + String get greaterThanDataGridFilteringLabel => r'या पेक्षा मोठे'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'पेक्षा मोठे किंवा समान'; + + @override + String get jumada1Label => r'जुमदा अल-अव्वाल'; + + @override + String get jumada2Label => r'जुमादा अल-थानी'; + + @override + String get lessThanDataGridFilteringLabel => r'च्या पेक्षा कमी'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'पेक्षा कमी किंवा समान'; + + @override + String get muharramLabel => r'मोहरम'; + + @override + String get noEventsCalendarLabel => r'कार्यक्रम नाहीत'; + + @override + String get noMatchesDataGridFilteringLabel => r'कोणतेही सामने नाहीत'; + + @override + String get noSelectedDateCalendarLabel => r'निवडलेली तारीख नाही'; + + @override + String get notEmptyDataGridFilteringLabel => r'रिकामे नाही'; + + @override + String get notNullDataGridFilteringLabel => r'शून्य नाही'; + + @override + String get nullDataGridFilteringLabel => r'निरर्थक'; + + @override + String get numberFiltersDataGridFilteringLabel => r'संख्या फिल्टर'; + + @override + String get ofDataPagerLabel => r'च्या'; + + @override + String get okDataGridFilteringLabel => r'ठीक आहे'; + + @override + String get orDataGridFilteringLabel => r'किंवा'; + + @override + String get pagesDataPagerLabel => r'पृष्ठे'; + + @override + String get passwordDialogContentLabel => + r'ही PDF फाईल उघडण्यासाठी पासवर्ड टाका'; + + @override + String get passwordDialogHeaderTextLabel => r'पासवर्ड संरक्षित'; + + @override + String get passwordDialogHintTextLabel => r'पासवर्ड टाका'; + + @override + String get passwordDialogInvalidPasswordLabel => r'अवैध पासवर्ड'; + + @override + String get pdfBookmarksLabel => r'बुकमार्क'; + + @override + String get pdfEnterPageNumberLabel => r'पृष्ठ क्रमांक प्रविष्ट करा'; + + @override + String get pdfGoToPageLabel => r'पृष्ठावर जा'; + + @override + String get pdfHyperlinkContentLabel => r'तुम्हाला येथे पृष्ठ उघडायचे आहे का'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'रद्द करा'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'उघडा'; + + @override + String get pdfHyperlinkLabel => r'वेब पृष्ठ उघडा'; + + @override + String get pdfInvalidPageNumberLabel => r'कृपया एक वैध क्रमांक प्रविष्ट करा'; + + @override + String get pdfNoBookmarksLabel => r'कोणतेही बुकमार्क आढळले नाहीत'; + + @override + String get pdfPaginationDialogCancelLabel => r'रद्द करा'; + + @override + String get pdfPaginationDialogOkLabel => r'ठीक'; + + @override + String get pdfPasswordDialogCancelLabel => r'रद्द करा'; + + @override + String get pdfPasswordDialogOpenLabel => r'उघडा'; + + @override + String get pdfScrollStatusOfLabel => r'च्या'; + + @override + String get pdfSignaturePadDialogClearLabel => r'साफ करा'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'तुमची स्वाक्षरी काढा'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'पेन रंग'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'जतन करा'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'कॉपी करा'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'हायलाइट करा'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'स्क्विग्ली'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'स्ट्राइकथ्रू'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'अधोरेखित करा'; + + @override + String get rabi1Label => r'रबी अल अव्वल'; + + @override + String get rabi2Label => r'रबी अल थानी'; + + @override + String get rajabLabel => r'रजब'; + + @override + String get ramadanLabel => r'रमजान'; + + @override + String get rowsPerPageDataPagerLabel => r'प्रति पृष्ठ पंक्ती'; + + @override + String get safarLabel => r'सफर'; + + @override + String get searchDataGridFilteringLabel => r'शोधा'; + + @override + String get selectAllDataGridFilteringLabel => r'सर्व निवडा'; + + @override + String get series => r'मालिका'; + + @override + String get shaabanLabel => r'शाबान'; + + @override + String get shawwalLabel => r'शव्वाल'; + + @override + String get shortDhualhiLabel => r'धुल-एच'; + + @override + String get shortDhualqiLabel => r'धूल-प्र'; + + @override + String get shortJumada1Label => r'जुम. आय'; + + @override + String get shortJumada2Label => r'जुम. II'; + + @override + String get shortMuharramLabel => r'मुह.'; + + @override + String get shortRabi1Label => r'रबी. आय'; + + @override + String get shortRabi2Label => r'रबी. II'; + + @override + String get shortRajabLabel => r'राज.'; + + @override + String get shortRamadanLabel => r'रॅम.'; + + @override + String get shortSafarLabel => r'सफ.'; + + @override + String get shortShaabanLabel => r'शा.'; + + @override + String get shortShawwalLabel => r'शॉ.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'कुठे पंक्ती दाखवा'; + + @override + String get sortAToZDataGridFilteringLabel => r'A ते Z क्रमवारी लावा'; + + @override + String get sortAndFilterDataGridFilteringLabel => + r'क्रमवारी लावा आणि फिल्टर करा'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'सर्वात मोठ्या ते सर्वात लहान क्रमवारी लावा'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'सर्वात नवीन ते सर्वात जुने क्रमवारी लावा'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'सर्वात जुनी ते नवीनतम क्रमवारी लावा'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'सर्वात लहान ते सर्वात मोठ्या क्रमवारी लावा'; + + @override + String get sortZToADataGridFilteringLabel => r'Z ते A क्रमवारी लावा'; + + @override + String get textFiltersDataGridFilteringLabel => r'मजकूर फिल्टर'; + + @override + String get todayLabel => r'आज'; + + @override + String get weeknumberLabel => r'आठवडा'; +} + +/// The translations for Malay (`ms`). +class SfLocalizationsMs extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsMs class + const SfLocalizationsMs({String localeName = 'ms'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Selepas'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Selepas Atau Sama'; + + @override + String get allDayLabel => r'Sepanjang hari'; + + @override + String get allowedViewDayLabel => r'Hari'; + + @override + String get allowedViewMonthLabel => r'Bulan'; + + @override + String get allowedViewScheduleLabel => r'Jadual'; + + @override + String get allowedViewTimelineDayLabel => r'Hari Garis Masa'; + + @override + String get allowedViewTimelineMonthLabel => r'Bulan Garis Masa'; + + @override + String get allowedViewTimelineWeekLabel => r'Minggu Garis Masa'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Minggu Kerja Garis Masa'; + + @override + String get allowedViewWeekLabel => r'Minggu'; + + @override + String get allowedViewWorkWeekLabel => r'Minggu Kerja'; + + @override + String get andDataGridFilteringLabel => r'Dan'; + + @override + String get beforeDataGridFilteringLabel => r'Sebelum'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Sebelum Atau Sama'; + + @override + String get beginsWithDataGridFilteringLabel => r'Bermula dengan'; + + @override + String get cancelDataGridFilteringLabel => r'Batal'; + + @override + String get clearFilterDataGridFilteringLabel => r'Kosongkan Penapis'; + + @override + String get containsDataGridFilteringLabel => r'Mengandungi'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Penapis Tarikh'; + + @override + String get daySpanCountLabel => r'Hari'; + + @override + String get dhualhiLabel => r'Dzulhijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Tidak Bermula Dengan'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Tidak mengandungi'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Tidak Berakhir Dengan'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Tidak Sama'; + + @override + String get emptyDataGridFilteringLabel => r'kosong'; + + @override + String get endsWithDataGridFilteringLabel => r'Berakhir Dengan'; + + @override + String get equalsDataGridFilteringLabel => r'sama'; + + @override + String get fromDataGridFilteringLabel => r'daripada'; + + @override + String get greaterThanDataGridFilteringLabel => r'Lebih besar daripada'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Lebih Besar Daripada Atau Sama'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Kurang daripada'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + r'Kurang Daripada Atau Sama'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Tiada acara'; + + @override + String get noMatchesDataGridFilteringLabel => r'Tiada perlawanan'; + + @override + String get noSelectedDateCalendarLabel => r'Tiada tarikh yang dipilih'; + + @override + String get notEmptyDataGridFilteringLabel => r'Tidak kosong'; + + @override + String get notNullDataGridFilteringLabel => r'Bukan Null'; + + @override + String get nullDataGridFilteringLabel => r'batal'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Penapis Nombor'; + + @override + String get ofDataPagerLabel => r'daripada'; + + @override + String get okDataGridFilteringLabel => r'okey'; + + @override + String get orDataGridFilteringLabel => r'Ataupun'; + + @override + String get pagesDataPagerLabel => r'muka surat'; + + @override + String get passwordDialogContentLabel => + r'Masukkan kata laluan untuk membuka fail PDF ini'; + + @override + String get passwordDialogHeaderTextLabel => r'Kata laluan dilindungi'; + + @override + String get passwordDialogHintTextLabel => r'Masukkan kata laluan'; + + @override + String get passwordDialogInvalidPasswordLabel => r'kata laluan tidak sah'; + + @override + String get pdfBookmarksLabel => r'Penanda buku'; + + @override + String get pdfEnterPageNumberLabel => r'Masukkan nombor halaman'; + + @override + String get pdfGoToPageLabel => r'Pergi ke halaman'; + + @override + String get pdfHyperlinkContentLabel => + r'Adakah anda ingin membuka halaman di'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'Batal'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'Buka'; + + @override + String get pdfHyperlinkLabel => r'Buka Halaman Web'; + + @override + String get pdfInvalidPageNumberLabel => r'sila masukkan nombor yang sah'; + + @override + String get pdfNoBookmarksLabel => r'Tiada penanda halaman ditemui'; + + @override + String get pdfPaginationDialogCancelLabel => r'Batal'; + + @override + String get pdfPaginationDialogOkLabel => r'OK'; + + @override + String get pdfPasswordDialogCancelLabel => r'Batal'; + + @override + String get pdfPasswordDialogOpenLabel => r'Buka'; + + @override + String get pdfScrollStatusOfLabel => r'daripada'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Kosongkan'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Lukis tandatangan anda'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Warna Pen'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Simpan'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Salin'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Serlahkan'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Garis berlekuk'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Garis silang'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Garis bawah'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadhan'; + + @override + String get rowsPerPageDataPagerLabel => r'Baris setiap halaman'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Cari'; + + @override + String get selectAllDataGridFilteringLabel => r'Pilih semua'; + + @override + String get series => r'Siri'; + + @override + String get shaabanLabel => + r'Sya' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Syawal'; + + @override + String get shortDhualhiLabel => r'Dzul-H'; + + @override + String get shortDhualqiLabel => r'Dzul-Q'; + + @override + String get shortJumada1Label => r'Jum. saya'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. saya'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Ram.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Tunjukkan baris di mana'; + + @override + String get sortAToZDataGridFilteringLabel => r'Susun A Hingga Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Isih dan Tapis'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Susun Terbesar Kepada Terkecil'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Susun Terbaharu Hingga Terlama'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Isih Terlama Kepada Terbaharu'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Susun Terkecil Kepada Terbesar'; + + @override + String get sortZToADataGridFilteringLabel => r'Isih Z Hingga A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Penapis Teks'; + + @override + String get todayLabel => r'Hari ini'; + + @override + String get weeknumberLabel => r'Minggu'; +} + +/// The translations for Burmese (`my`). +class SfLocalizationsMy extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsMy class + const SfLocalizationsMy({String localeName = 'my'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'ပြီးနောက်'; + + @override + String get afterOrEqualDataGridFilteringLabel => + r'ပြီးနောက် သို့မဟုတ် အနှိုင်းမဲ့'; + + @override + String get allDayLabel => r'တနေကုန်'; + + @override + String get allowedViewDayLabel => r'နေ့'; + + @override + String get allowedViewMonthLabel => r'လ'; + + @override + String get allowedViewScheduleLabel => r'အချိန်ဇယား'; + + @override + String get allowedViewTimelineDayLabel => r'အချိန်ဇယားနေ့'; + + @override + String get allowedViewTimelineMonthLabel => r'အချိန်ဇယားလ'; + + @override + String get allowedViewTimelineWeekLabel => r'အချိန်ဇယားအပတ်'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'အချိန်ဇယားအလုပ်သုံးအပတ်'; + + @override + String get allowedViewWeekLabel => r'အေးလေ'; + + @override + String get allowedViewWorkWeekLabel => r'အလုပ်ရက်သတ္တပတ်'; + + @override + String get andDataGridFilteringLabel => r'နှင့်'; + + @override + String get beforeDataGridFilteringLabel => r'မတိုင်မီ'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'မီ သို့မဟုတ် သာတူညီမျှ'; + + @override + String get beginsWithDataGridFilteringLabel => r'ဖြင့်စတင်သည်။'; + + @override + String get cancelDataGridFilteringLabel => r'မလုပ်တော့'; + + @override + String get clearFilterDataGridFilteringLabel => r'Filter ကိုရှင်းလင်းပါ။'; + + @override + String get containsDataGridFilteringLabel => r'ပါရှိသည်။'; + + @override + String get dateFiltersDataGridFilteringLabel => r'ရက်စွဲ စစ်ထုတ်မှုများ'; + + @override + String get daySpanCountLabel => r'နေ့'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'ဖြင့် မစတင်ပါ။'; + + @override + String get doesNotContainDataGridFilteringLabel => r'မပါဝင်ပါ။'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'ဖြင့် မဆုံးပါ။'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'မညီမျှပါ။'; + + @override + String get emptyDataGridFilteringLabel => r'ဗလာ'; + + @override + String get endsWithDataGridFilteringLabel => r'ဖြင့် အဆုံးသတ်သည်။'; + + @override + String get equalsDataGridFilteringLabel => r'ညီမျှသည်။'; + + @override + String get fromDataGridFilteringLabel => r'ထံမှ'; + + @override + String get greaterThanDataGridFilteringLabel => r'ထက်မြတ်'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'ပိုကြီးသည် သို့မဟုတ် သာတူညီမျှ'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'ထက်ငယ်သော'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + r'လျော့နည်းသည် သို့မဟုတ် ညီမျှသည်။'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'ပွဲများမရှိပါ။'; + + @override + String get noMatchesDataGridFilteringLabel => r'တိုက်ဆိုင်မှု မရှိပါ။'; + + @override + String get noSelectedDateCalendarLabel => r'ရွေးထားသည့်ရက်စွဲမရှိပါ။'; + + @override + String get notEmptyDataGridFilteringLabel => r'ဗလာမဟုတ်ပါ။'; + + @override + String get notNullDataGridFilteringLabel => r'Null မဟုတ်ပါ။'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'နံပါတ် စစ်ထုတ်မှုများ'; + + @override + String get ofDataPagerLabel => r'၏'; + + @override + String get okDataGridFilteringLabel => r'အဆင်ပြေလား'; + + @override + String get orDataGridFilteringLabel => r'သို့မဟုတ်'; + + @override + String get pagesDataPagerLabel => r'စာမျက်နှာများ'; + + @override + String get passwordDialogContentLabel => + r'ဤ PDF ဖိုင်ကိုဖွင့်ရန် စကားဝှက်ကို ထည့်ပါ။'; + + @override + String get passwordDialogHeaderTextLabel => r'စကားဝှက်ကို ကာကွယ်ထားသည်။'; + + @override + String get passwordDialogHintTextLabel => r'Password ရိုက်ထည့်ပါ။'; + + @override + String get passwordDialogInvalidPasswordLabel => r'မမှန်ကန်သော စကားဝှက်'; + + @override + String get pdfBookmarksLabel => r'ညှပ်'; + + @override + String get pdfEnterPageNumberLabel => r'စာမျက်နှာနံပါတ်ထည့်ပါ။'; + + @override + String get pdfGoToPageLabel => r'စာမျက်နှာသို့သွားပါ'; + + @override + String get pdfHyperlinkContentLabel => r'စာမျက်နှာမှာ ဖွင့်ချင်ပါသလား။'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ပယ်ဖျက်ပါ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ဖွင့်မည်'; + + @override + String get pdfHyperlinkLabel => r'ဝဘ်စာမျက်နှာကိုဖွင့်ပါ။'; + + @override + String get pdfInvalidPageNumberLabel => + r'ကျေးဇူးပြု၍ တရားဝင်နံပါတ်တစ်ခုထည့်ပါ။'; + + @override + String get pdfNoBookmarksLabel => r'စာညှပ်များမတွေ့ပါ။'; + + @override + String get pdfPaginationDialogCancelLabel => r'မလုပ်တော့ဘူး'; + + @override + String get pdfPaginationDialogOkLabel => r'အတည်ပြုမည်'; + + @override + String get pdfPasswordDialogCancelLabel => r'ပယ်ဖျက်ပါ'; + + @override + String get pdfPasswordDialogOpenLabel => r'ဖွင့်မည်'; + + @override + String get pdfScrollStatusOfLabel => r'၏'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ရှင်းပါ'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'သင်၏လက်မှတ်ကိုဆွဲပါ။'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'ဘောပင်အရောင်'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'သိမ်းပါ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'ကော်ပီ'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'အထင်ဖျော်'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'ကြောင်းပျပ်ကြည့်'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'ထိုးနှက်ချက်'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'မျဉ်းသား'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'ရာဂျပ်'; + + @override + String get ramadanLabel => r'ရမဿွာန်'; + + @override + String get rowsPerPageDataPagerLabel => r'စာမျက်နှာအလိုက် အတန်းများ'; + + @override + String get safarLabel => r'ဆာဖာရာ'; + + @override + String get searchDataGridFilteringLabel => r'ရှာရန်'; + + @override + String get selectAllDataGridFilteringLabel => r'အားလုံးကို ရွေးပါ။'; + + @override + String get series => r'စီးရီး'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum ငါ'; + + @override + String get shortJumada2Label => r'Jum II'; + + @override + String get shortMuharramLabel => r'Muh'; + + @override + String get shortRabi1Label => r'ရာဘီ။ ငါ'; + + @override + String get shortRabi2Label => r'ရာဘီ။ II'; + + @override + String get shortRajabLabel => r'Raj'; + + @override + String get shortRamadanLabel => r'ရမ်။'; + + @override + String get shortSafarLabel => r'အန္တရာယ်ကင်း။'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'ရှော။'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'အတန်းပြပါ။'; + + @override + String get sortAToZDataGridFilteringLabel => r'A မှ Z ကိုစီပါ။'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'စီစစ်ပြီး စစ်ထုတ်ပါ။'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'အကြီးဆုံးမှအသေးဆုံးကိုစီပါ။'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'အသစ်ဆုံးမှ အဟောင်းဆုံးသို့ စီပါ။'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'အဟောင်းဆုံးမှ အသစ်ဆုံးသို့ စီပါ။'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'အသေးဆုံးမှ အကြီးဆုံးသို့ စီပါ။'; + + @override + String get sortZToADataGridFilteringLabel => r'Z မှ A ကိုစီပါ။'; + + @override + String get textFiltersDataGridFilteringLabel => r'စာသားစစ်ထုတ်မှုများ'; + + @override + String get todayLabel => r'ဒီနေ့'; + + @override + String get weeknumberLabel => r'အေးလေ'; +} + +/// The translations for Norwegian Bokmål (`nb`). +class SfLocalizationsNb extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsNb class + const SfLocalizationsNb({String localeName = 'nb'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Etter'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Etter Eller Like'; + + @override + String get allDayLabel => r'Hele dagen'; + + @override + String get allowedViewDayLabel => r'Dag'; + + @override + String get allowedViewMonthLabel => r'Måned'; + + @override + String get allowedViewScheduleLabel => r'Rute'; + + @override + String get allowedViewTimelineDayLabel => r'Tidslinjedag'; + + @override + String get allowedViewTimelineMonthLabel => r'Tidslinjemåned'; + + @override + String get allowedViewTimelineWeekLabel => r'Tidslinje uke'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbeidsuke'; + + @override + String get allowedViewWeekLabel => r'Uke'; + + @override + String get allowedViewWorkWeekLabel => r'Arbeidsuke'; + + @override + String get andDataGridFilteringLabel => r'Og'; + + @override + String get beforeDataGridFilteringLabel => r'Før'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Før eller lik'; + + @override + String get beginsWithDataGridFilteringLabel => r'Begynner med'; + + @override + String get cancelDataGridFilteringLabel => r'Avbryt'; + + @override + String get clearFilterDataGridFilteringLabel => r'Tøm filter'; + + @override + String get containsDataGridFilteringLabel => r'Inneholder'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Datofiltre'; + + @override + String get daySpanCountLabel => r'Dag'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Begynner ikke med'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Inneholder ikke'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Slutter ikke med'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Er ikke lik'; + + @override + String get emptyDataGridFilteringLabel => r'Tømme'; + + @override + String get endsWithDataGridFilteringLabel => r'Slutter med'; + + @override + String get equalsDataGridFilteringLabel => r'Er lik'; + + @override + String get fromDataGridFilteringLabel => r'Fra'; + + @override + String get greaterThanDataGridFilteringLabel => r'Større enn'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Større enn eller lik'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Mindre enn'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Mindre enn eller lik'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Ingen hendelser'; + + @override + String get noMatchesDataGridFilteringLabel => r'Ingen treff'; + + @override + String get noSelectedDateCalendarLabel => r'Ingen valgt dato'; + + @override + String get notEmptyDataGridFilteringLabel => r'Ikke tom'; + + @override + String get notNullDataGridFilteringLabel => r'Ikke null'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Nummerfiltre'; + + @override + String get ofDataPagerLabel => r'av'; + + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'Eller'; + + @override + String get pagesDataPagerLabel => r'sider'; + + @override + String get passwordDialogContentLabel => + r'Skriv inn passordet for å åpne denne PDF-filen'; + + @override + String get passwordDialogHeaderTextLabel => r'Passordbeskyttet'; + + @override + String get passwordDialogHintTextLabel => r'Oppgi passord'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Ugyldig passord'; + + @override + String get pdfBookmarksLabel => r'Bokmerker'; + + @override + String get pdfEnterPageNumberLabel => r'Skriv inn sidenummer'; + + @override + String get pdfGoToPageLabel => r'Gå til side'; + + @override + String get pdfHyperlinkContentLabel => r'Vil du åpne siden på'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'AVBRYT'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'Åpne'; + + @override + String get pdfHyperlinkLabel => r'Åpne webside'; + + @override + String get pdfInvalidPageNumberLabel => r'Vennligst oppgi et gyldig nummer'; + + @override + String get pdfNoBookmarksLabel => r'Fant ingen bokmerker'; + + @override + String get pdfPaginationDialogCancelLabel => r'AVBRYT'; + + @override + String get pdfPaginationDialogOkLabel => r'OK'; + + @override + String get pdfPasswordDialogCancelLabel => r'AVBRYT'; + + @override + String get pdfPasswordDialogOpenLabel => r'ÅPEN'; + + @override + String get pdfScrollStatusOfLabel => r'av'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Fjern'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Tegn signaturen din'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Pennefarge'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'LAGRE'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopi'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Fremheve'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Snurrete'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Gjennomstreking'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Understrek'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Rader per side'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Søk'; + + @override + String get selectAllDataGridFilteringLabel => r'Velg alle'; + + @override + String get series => r'Serie'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. Jeg'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Jeg'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Vis rader hvor'; + + @override + String get sortAToZDataGridFilteringLabel => r'Sorter A til Å'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sorter og filtrer'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sorter størst til minste'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Sorter nyeste til eldste'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Sorter eldste til nyeste'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sorter minste til største'; + + @override + String get sortZToADataGridFilteringLabel => r'Sorter Z til A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Tekstfiltre'; + + @override + String get todayLabel => r'I dag'; + + @override + String get weeknumberLabel => r'Uke'; +} + +/// The translations for Nepali (`ne`). +class SfLocalizationsNe extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsNe class + const SfLocalizationsNe({String localeName = 'ne'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'पछि'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'पछि वा बराबर'; + + @override + String get allDayLabel => r'दिनभरि'; + + @override + String get allowedViewDayLabel => r'दिन'; + + @override + String get allowedViewMonthLabel => r'महिना'; + + @override + String get allowedViewScheduleLabel => r'तालिका'; + + @override + String get allowedViewTimelineDayLabel => r'टाइमलाइन दिन'; + + @override + String get allowedViewTimelineMonthLabel => r'टाइमलाइन महिना'; + + @override + String get allowedViewTimelineWeekLabel => r'टाइमलाइन हप्ता'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य हप्ता'; + + @override + String get allowedViewWeekLabel => r'हप्ता'; + + @override + String get allowedViewWorkWeekLabel => r'कार्य हप्ता'; + + @override + String get andDataGridFilteringLabel => r'र'; + + @override + String get beforeDataGridFilteringLabel => r'पहिले'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'अघि वा बराबर'; + + @override + String get beginsWithDataGridFilteringLabel => r'बाट सुरु हुन्छ'; + + @override + String get cancelDataGridFilteringLabel => r'रद्द गर्नुहोस्'; + + @override + String get clearFilterDataGridFilteringLabel => r'फिल्टर खाली गर्नुहोस्'; + + @override + String get containsDataGridFilteringLabel => r'समावेश गर्दछ'; + + @override + String get dateFiltersDataGridFilteringLabel => r'मिति फिल्टरहरू'; + + @override + String get daySpanCountLabel => r'दिन'; + + @override + String get dhualhiLabel => r'धु उल हिज्जा'; + + @override + String get dhualqiLabel => r'धु अल-किदाह'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'बाट सुरु हुँदैन'; + + @override + String get doesNotContainDataGridFilteringLabel => r'समावेश गर्दैन'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'संग समाप्त हुँदैन'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'बराबर हुँदैन'; + + @override + String get emptyDataGridFilteringLabel => r'खाली'; + + @override + String get endsWithDataGridFilteringLabel => r'संग समाप्त हुन्छ'; + + @override + String get equalsDataGridFilteringLabel => r'बराबर हुन्छ'; + + @override + String get fromDataGridFilteringLabel => r'बाट'; + + @override + String get greaterThanDataGridFilteringLabel => r'भन्दा ठुलो'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'भन्दा ग्रेटर वा बराबर'; + + @override + String get jumada1Label => r'जुमाद अल अव्वल'; + + @override + String get jumada2Label => r'जुमादा अल थानी'; + + @override + String get lessThanDataGridFilteringLabel => r'भन्दा कम'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'भन्दा कम वा बराबर'; + + @override + String get muharramLabel => r'मुहर्रम'; + + @override + String get noEventsCalendarLabel => r'कुनै घटनाहरू छैनन्'; + + @override + String get noMatchesDataGridFilteringLabel => r'कुनै मेल छैन'; + + @override + String get noSelectedDateCalendarLabel => r'कुनै चयन गरिएको मिति छैन'; + + @override + String get notEmptyDataGridFilteringLabel => r'खाली छैन'; + + @override + String get notNullDataGridFilteringLabel => r'शून्य होइन'; + + @override + String get nullDataGridFilteringLabel => r'खाली'; + + @override + String get numberFiltersDataGridFilteringLabel => r'नम्बर फिल्टरहरू'; + + @override + String get ofDataPagerLabel => r'को'; + + @override + String get okDataGridFilteringLabel => r'ठिक छ'; + + @override + String get orDataGridFilteringLabel => r'वा'; + + @override + String get pagesDataPagerLabel => r'पृष्ठहरू'; + + @override + String get passwordDialogContentLabel => + r'यो PDF फाइल खोल्न पासवर्ड प्रविष्ट गर्नुहोस्'; + + @override + String get passwordDialogHeaderTextLabel => r'पासवर्ड सुरक्षित'; + + @override + String get passwordDialogHintTextLabel => r'पासवर्ड प्रविष्ट गर्नुहोस्'; + + @override + String get passwordDialogInvalidPasswordLabel => r'अवैध पासवर्ड'; + + @override + String get pdfBookmarksLabel => r'बुकमार्कहरू'; + + @override + String get pdfEnterPageNumberLabel => r'पृष्ठ नम्बर प्रविष्ट गर्नुहोस्'; + + @override + String get pdfGoToPageLabel => r'पृष्ठमा जानुहोस्'; + + @override + String get pdfHyperlinkContentLabel => r'के तपाइँ पृष्ठ खोल्न चाहनुहुन्छ'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'रद्द गर्नुहोस्'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'खोल्नुहोस्'; + + @override + String get pdfHyperlinkLabel => r'वेब पृष्ठ खोल्नुहोस्'; + + @override + String get pdfInvalidPageNumberLabel => + r'कृपया मान्य नम्बर प्रविष्ट गर्नुहोस्'; + + @override + String get pdfNoBookmarksLabel => r'कुनै बुकमार्क फेला परेन'; + + @override + String get pdfPaginationDialogCancelLabel => r'रद्द गर्नुहोस्'; + + @override + String get pdfPaginationDialogOkLabel => r'ठिक छ'; + + @override + String get pdfPasswordDialogCancelLabel => r'रद्द गर्नुहोस्'; + + @override + String get pdfPasswordDialogOpenLabel => r'खोल्नुहोस्'; + + @override + String get pdfScrollStatusOfLabel => r'को'; + + @override + String get pdfSignaturePadDialogClearLabel => r'खाली गर्नुहोस्'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'आफ्नो हस्ताक्षर कोर्नुहोस्'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'कलम रंग'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'बचत गर्नुहोस्'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'कापी'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'हाइलाइट गर्नुहोस्'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'चकचके'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'स्ट्राइकथ्रु'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'अन्डरलाइन'; + + @override + String get rabi1Label => r'रबि अल अव्वल'; + + @override + String get rabi2Label => r'रबी अल थानी'; + + @override + String get rajabLabel => r'रजब'; + + @override + String get ramadanLabel => r'रमजान'; + + @override + String get rowsPerPageDataPagerLabel => r'प्रति पृष्ठ पङ्क्तिहरू'; + + @override + String get safarLabel => r'सफार'; + + @override + String get searchDataGridFilteringLabel => r'खोज्नुहोस्'; + + @override + String get selectAllDataGridFilteringLabel => r'सबै छान्नु'; + + @override + String get series => r'शृङ्खला'; + + @override + String get shaabanLabel => r'शाबान'; + + @override + String get shawwalLabel => r'सवल'; + + @override + String get shortDhualhiLabel => r'धुल-एच'; + + @override + String get shortDhualqiLabel => r'धुल-क'; + + @override + String get shortJumada1Label => r'जुम। म'; + + @override + String get shortJumada2Label => r'जुम। II'; + + @override + String get shortMuharramLabel => r'मुह।'; + + @override + String get shortRabi1Label => r'रबि। म'; + + @override + String get shortRabi2Label => r'रबि। II'; + + @override + String get shortRajabLabel => r'राज।'; + + @override + String get shortRamadanLabel => r'राम।'; + + @override + String get shortSafarLabel => r'सफ।'; + + @override + String get shortShaabanLabel => r'शा।'; + + @override + String get shortShawwalLabel => r'शा।'; + + @override + String get showRowsWhereDataGridFilteringLabel => + r'जहाँ पङ्क्तिहरू देखाउनुहोस्'; + + @override + String get sortAToZDataGridFilteringLabel => r'A देखि Z क्रमबद्ध गर्नुहोस्'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'क्रमबद्ध र फिल्टर'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'सबैभन्दा ठूलो देखि सानो क्रमबद्ध गर्नुहोस्'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'सबैभन्दा नयाँ देखि पुरानो क्रमबद्ध गर्नुहोस्'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'सबैभन्दा पुरानो देखि नयाँ क्रमबद्ध गर्नुहोस्'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'सबैभन्दा सानो देखि ठुलो क्रमबद्ध गर्नुहोस्'; + + @override + String get sortZToADataGridFilteringLabel => r'Z मा A क्रमबद्ध गर्नुहोस्'; + + @override + String get textFiltersDataGridFilteringLabel => r'पाठ फिल्टरहरू'; + + @override + String get todayLabel => r'आज'; + + @override + String get weeknumberLabel => r'हप्ता'; +} + +/// The translations for Dutch Flemish (`nl`). +class SfLocalizationsNl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsNl class + const SfLocalizationsNl({String localeName = 'nl'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Na'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Na of gelijk'; + + @override + String get allDayLabel => r'De hele dag'; + + @override + String get allowedViewDayLabel => r'Dag'; + + @override + String get allowedViewMonthLabel => r'Maand'; + + @override + String get allowedViewScheduleLabel => r'Schema'; + + @override + String get allowedViewTimelineDayLabel => r'Tijdlijn dag'; + + @override + String get allowedViewTimelineMonthLabel => r'Tijdlijn maand'; + + @override + String get allowedViewTimelineWeekLabel => r'Tijdlijn week'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Tijdlijn Werkweek'; + + @override + String get allowedViewWeekLabel => r'Week'; + + @override + String get allowedViewWorkWeekLabel => r'Werkweek'; + + @override + String get andDataGridFilteringLabel => r'En'; + + @override + String get beforeDataGridFilteringLabel => r'Voordat'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Voor of gelijk'; + + @override + String get beginsWithDataGridFilteringLabel => r'Begint met'; + + @override + String get cancelDataGridFilteringLabel => r'Annuleren'; + + @override + String get clearFilterDataGridFilteringLabel => r'Filter wissen'; + + @override + String get containsDataGridFilteringLabel => r'Bevat'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Datumfilters'; + + @override + String get daySpanCountLabel => r'Dag'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Begint niet met'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Bevat geen'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Eindigt niet met'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Is niet gelijk aan'; + + @override + String get emptyDataGridFilteringLabel => r'Leeg'; + + @override + String get endsWithDataGridFilteringLabel => r'Eindigt met'; + + @override + String get equalsDataGridFilteringLabel => r'gelijk aan'; + + @override + String get fromDataGridFilteringLabel => r'Van'; + + @override + String get greaterThanDataGridFilteringLabel => r'Groter dan'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Groter dan of gelijk aan'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Minder dan'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Minder dan of gelijk'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Geen evenementen'; + + @override + String get noMatchesDataGridFilteringLabel => r'Geen overeenkomsten'; + + @override + String get noSelectedDateCalendarLabel => r'Geen geselecteerde datum'; + + @override + String get notEmptyDataGridFilteringLabel => r'Niet leeg'; + + @override + String get notNullDataGridFilteringLabel => r'Niet nul'; + + @override + String get nullDataGridFilteringLabel => r'Nul'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Nummerfilters'; + + @override + String get ofDataPagerLabel => r'van'; + + @override + String get okDataGridFilteringLabel => r'Oké'; + + @override + String get orDataGridFilteringLabel => r'Of'; + + @override + String get pagesDataPagerLabel => + r'Pagina' + "'" + r's'; + + @override + String get passwordDialogContentLabel => + r'Voer het wachtwoord in om dit PDF-bestand te openen'; + + @override + String get passwordDialogHeaderTextLabel => r'Beschermd met een wachtwoord'; + + @override + String get passwordDialogHintTextLabel => r'Voer wachtwoord in'; + + @override + String get passwordDialogInvalidPasswordLabel => r'ongeldig wachtwoord'; + + @override + String get pdfBookmarksLabel => r'Bladwijzers'; + + @override + String get pdfEnterPageNumberLabel => r'Voer paginanummer in'; + + @override + String get pdfGoToPageLabel => r'Ga naar pagina'; + + @override + String get pdfHyperlinkContentLabel => r'Wilt u de pagina openen op'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ANNULEREN'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'OPEN'; + + @override + String get pdfHyperlinkLabel => r'Webpagina openen'; + + @override + String get pdfInvalidPageNumberLabel => r'Voer een geldig nummer in'; + + @override + String get pdfNoBookmarksLabel => r'Geen bladwijzers gevonden'; + + @override + String get pdfPaginationDialogCancelLabel => r'ANNULEREN'; + + @override + String get pdfPaginationDialogOkLabel => r'Oké'; + + @override + String get pdfPasswordDialogCancelLabel => r'ANNULEREN'; + + @override + String get pdfPasswordDialogOpenLabel => r'OPEN'; + + @override + String get pdfScrollStatusOfLabel => r'van'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Wissen'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Teken je handtekening'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Pen Kleur'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-Awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Opslaan'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopiëren'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Markeren'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Kronkelig'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Doorhalen'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Onderstrepen'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Rijen per pagina'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Zoeken'; + + @override + String get selectAllDataGridFilteringLabel => r'Selecteer alles'; + + @override + String get series => r'Serie'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => r'Dhul-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. l'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. l'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'Veilig'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Toon rijen waar'; + + @override + String get sortAToZDataGridFilteringLabel => r'Sorteer van A tot Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sorteren en filteren'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sorteer van groot naar klein'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Sorteer nieuwste naar oudste'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Sorteer oudste naar nieuwste'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sorteer klein naar groot'; + + @override + String get sortZToADataGridFilteringLabel => r'Sorteer Z naar A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Tekstfilters'; + + @override + String get todayLabel => r'Vandaag'; + + @override + String get weeknumberLabel => r'Week'; +} + +/// The translations for Norwegian (`no`). +class SfLocalizationsNo extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsNo class + const SfLocalizationsNo({String localeName = 'no'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Etter'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Etter Eller Like'; + + @override + String get allDayLabel => r'Hele dagen'; + + @override + String get allowedViewDayLabel => r'Dag'; + + @override + String get allowedViewMonthLabel => r'Måned'; + + @override + String get allowedViewScheduleLabel => r'Rute'; + + @override + String get allowedViewTimelineDayLabel => r'Tidslinje Dag'; + + @override + String get allowedViewTimelineMonthLabel => r'Tidslinje Måned'; + + @override + String get allowedViewTimelineWeekLabel => r'Tidslinje Uke'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbeidsuke'; + + @override + String get allowedViewWeekLabel => r'Uke'; + + @override + String get allowedViewWorkWeekLabel => r'Arbeidsuke'; + + @override + String get andDataGridFilteringLabel => r'Og'; + + @override + String get beforeDataGridFilteringLabel => r'Før'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Før eller lik'; + + @override + String get beginsWithDataGridFilteringLabel => r'Begynner med'; + + @override + String get cancelDataGridFilteringLabel => r'Avbryt'; + + @override + String get clearFilterDataGridFilteringLabel => r'Tøm filter'; + + @override + String get containsDataGridFilteringLabel => r'Inneholder'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Datofiltre'; + + @override + String get daySpanCountLabel => r'Dag'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Begynner ikke med'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Inneholder ikke'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Slutter ikke med'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Er ikke lik'; + + @override + String get emptyDataGridFilteringLabel => r'Tømme'; + + @override + String get endsWithDataGridFilteringLabel => r'Slutter med'; + + @override + String get equalsDataGridFilteringLabel => r'Er lik'; + + @override + String get fromDataGridFilteringLabel => r'Fra'; + + @override + String get greaterThanDataGridFilteringLabel => r'Større enn'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Større enn eller lik'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Mindre enn'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Mindre enn eller lik'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Ingen hendelser'; + + @override + String get noMatchesDataGridFilteringLabel => r'Ingen treff'; + + @override + String get noSelectedDateCalendarLabel => r'Ingen valgt dato'; + + @override + String get notEmptyDataGridFilteringLabel => r'Ikke tom'; + + @override + String get notNullDataGridFilteringLabel => r'Ikke null'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Nummerfiltre'; + + @override + String get ofDataPagerLabel => r'av'; + + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'Eller'; + + @override + String get pagesDataPagerLabel => r'sider'; + + @override + String get passwordDialogContentLabel => + r'Skriv inn passordet for å åpne denne PDF-filen'; + + @override + String get passwordDialogHeaderTextLabel => r'Passordbeskyttet'; + + @override + String get passwordDialogHintTextLabel => r'Oppgi passord'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Ugyldig passord'; + + @override + String get pdfBookmarksLabel => r'Bokmerker'; + + @override + String get pdfEnterPageNumberLabel => r'Skriv inn sidenummer'; + + @override + String get pdfGoToPageLabel => r'Gå til side'; + + @override + String get pdfHyperlinkContentLabel => r'Vil du åpne siden på'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'AVBRYT'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ÅPEN'; + + @override + String get pdfHyperlinkLabel => r'Åpne webside'; + + @override + String get pdfInvalidPageNumberLabel => r'Vennligst oppgi et gyldig nummer'; + + @override + String get pdfNoBookmarksLabel => r'Fant ingen bokmerker'; + + @override + String get pdfPaginationDialogCancelLabel => r'AVBRYT'; + + @override + String get pdfPaginationDialogOkLabel => r'OK'; + + @override + String get pdfPasswordDialogCancelLabel => r'AVBRYT'; + + @override + String get pdfPasswordDialogOpenLabel => r'ÅPEN'; + + @override + String get pdfScrollStatusOfLabel => r'av'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Tøm'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Tegn signaturen din'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Pennefarge'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Lagre'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopier'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Fremhev'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Snurrete'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Gjennomstreking'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Understrek'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Rajab'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Rader per side'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Søk'; + + @override + String get selectAllDataGridFilteringLabel => r'Velg alle'; + + @override + String get series => r'Serie'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'Jum. Jeg'; + + @override + String get shortJumada2Label => r'Jum. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. Jeg'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'RAM.'; + + @override + String get shortSafarLabel => r'Saf.'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shaw.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Vis rader hvor'; + + @override + String get sortAToZDataGridFilteringLabel => r'Sorter A til Å'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sorter og filtrer'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sorter størst til minste'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Sorter nyeste til eldste'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Sorter eldste til nyeste'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sorter minste til største'; + + @override + String get sortZToADataGridFilteringLabel => r'Sorter Z til A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Tekstfiltre'; + + @override + String get todayLabel => r'I dag'; + + @override + String get weeknumberLabel => r'Uke'; +} + +/// The translations for Oriya (`or`). +class SfLocalizationsOr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsOr class + const SfLocalizationsOr({String localeName = 'or'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'ପରେ'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'କିମ୍ବା ସମାନ ପରେ |'; + + @override + String get allDayLabel => r'ଦିନ ସାରା'; + + @override + String get allowedViewDayLabel => r'ଦିନ'; + + @override + String get allowedViewMonthLabel => r'ମାସ'; + + @override + String get allowedViewScheduleLabel => r'କାର୍ଯ୍ୟସୂଚୀ'; + + @override + String get allowedViewTimelineDayLabel => r'ସମୟସୀମା ଦିନ |'; + + @override + String get allowedViewTimelineMonthLabel => r'ସମୟସୀମା ମାସ |'; + + @override + String get allowedViewTimelineWeekLabel => r'ସମୟସୀମା ସପ୍ତାହ |'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'ସମୟସୀମା କାର୍ଯ୍ୟ ସପ୍ତାହ |'; + + @override + String get allowedViewWeekLabel => r'ସପ୍ତାହ'; + + @override + String get allowedViewWorkWeekLabel => r'କାର୍ଯ୍ୟ ସପ୍ତାହ'; + + @override + String get andDataGridFilteringLabel => r'ଏବଂ'; + + @override + String get beforeDataGridFilteringLabel => r'ପୂର୍ବରୁ'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'ପୂର୍ବରୁ କିମ୍ବା ସମାନ |'; + + @override + String get beginsWithDataGridFilteringLabel => r'ସହିତ ଆରମ୍ଭ ହୁଏ |'; + + @override + String get cancelDataGridFilteringLabel => r'ବାତିଲ୍ କରନ୍ତୁ |'; + + @override + String get clearFilterDataGridFilteringLabel => r'ଫିଲ୍ଟର୍ ସଫା କରନ୍ତୁ |'; + + @override + String get containsDataGridFilteringLabel => r'ଧାରଣ କରେ |'; + + @override + String get dateFiltersDataGridFilteringLabel => r'ତାରିଖ ଫିଲ୍ଟର୍ |'; + + @override + String get daySpanCountLabel => r'ଦିନ'; + + @override + String get dhualhiLabel => r'ଧୁ ଅଲ-ହିଜା'; + + @override + String get dhualqiLabel => r'ଧୁ ଅଲ-କଇଦା |'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => + r'ସହିତ ଆରମ୍ଭ ହୁଏ ନାହିଁ |'; + + @override + String get doesNotContainDataGridFilteringLabel => r'ଧାରଣ କରେ ନାହିଁ |'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'ସହିତ ସମାପ୍ତ ହୁଏ ନାହିଁ |'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'ସମାନ ନୁହେଁ |'; + + @override + String get emptyDataGridFilteringLabel => r'ଖାଲି'; + + @override + String get endsWithDataGridFilteringLabel => r'ସହିତ ଶେଷ ହୁଏ |'; + + @override + String get equalsDataGridFilteringLabel => r'ସମାନ |'; + + @override + String get fromDataGridFilteringLabel => r'ଠାରୁ'; + + @override + String get greaterThanDataGridFilteringLabel => r'ଠାରୁ ବଡ'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'ଠାରୁ ବଡ କିମ୍ବା ସମାନ |'; + + @override + String get jumada1Label => r'ଜୁମାଡା ଅଲ-ଅୱାଲ |'; + + @override + String get jumada2Label => r'ଜୁମାଡା ଅଲ-ଥାନି |'; + + @override + String get lessThanDataGridFilteringLabel => r'ଠାରୁ କମ୍'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'କମ୍ କିମ୍ବା ସମାନ |'; + + @override + String get muharramLabel => r'ମୁହରାମ'; + + @override + String get noEventsCalendarLabel => r'କ events ଣସି ଘଟଣା ନାହିଁ |'; + + @override + String get noMatchesDataGridFilteringLabel => r'କ matches ଣସି ମେଳକ ନାହିଁ |'; + + @override + String get noSelectedDateCalendarLabel => + r'କ selected ଣସି ମନୋନୀତ ତାରିଖ ନାହିଁ |'; + + @override + String get notEmptyDataGridFilteringLabel => r'ଖାଲି ନୁହେଁ |'; + + @override + String get notNullDataGridFilteringLabel => r'ନଲ୍ ନୁହେଁ |'; + + @override + String get nullDataGridFilteringLabel => r'ନଲ୍'; + + @override + String get numberFiltersDataGridFilteringLabel => r'ସଂଖ୍ୟା ଫିଲ୍ଟର୍ |'; + + @override + String get ofDataPagerLabel => r'ର'; + + @override + String get okDataGridFilteringLabel => r'ଠିକ୍ ଅଛି'; + + @override + String get orDataGridFilteringLabel => r'କିମ୍ବା'; + + @override + String get pagesDataPagerLabel => r'ପୃଷ୍ଠାଗୁଡ଼ିକ |'; + + @override + String get passwordDialogContentLabel => + r'ଏହି PDF ଫାଇଲ୍ ଖୋଲିବା ପାଇଁ ପାସୱାର୍ଡ ପ୍ରବେଶ କରନ୍ତୁ |'; + + @override + String get passwordDialogHeaderTextLabel => r'ପାସୱାର୍ଡ ସୁରକ୍ଷିତ |'; + + @override + String get passwordDialogHintTextLabel => r'ପାସୱାର୍ଡ ପ୍ରବେଶ କରନ୍ତୁ |'; + + @override + String get passwordDialogInvalidPasswordLabel => r'ଅବୈଧ ପାସୱାର୍ଡ'; + + @override + String get pdfBookmarksLabel => r'ବୁକମାର୍କଗୁଡିକ'; + + @override + String get pdfEnterPageNumberLabel => r'ପୃଷ୍ଠା ନମ୍ବର ପ୍ରବେଶ କରନ୍ତୁ |'; + + @override + String get pdfGoToPageLabel => r'ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |'; + + @override + String get pdfHyperlinkContentLabel => r'ଆପଣ ପୃଷ୍ଠା ଖୋଲିବାକୁ ଚାହାଁନ୍ତି କି?'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ବାତିଲ୍'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ଖୋଲନ୍ତୁ |'; + + @override + String get pdfHyperlinkLabel => r'ୱେବ୍ ପୃଷ୍ଠା ଖୋଲନ୍ତୁ |'; + + @override + String get pdfInvalidPageNumberLabel => r'ଦୟାକରି ଏକ ବୈଧ ନମ୍ବର ପ୍ରବେଶ କରନ୍ତୁ'; + + @override + String get pdfNoBookmarksLabel => r'କୌଣସି ବୁକମାର୍କ ମିଳିଲା ନାହିଁ'; + + @override + String get pdfPaginationDialogCancelLabel => r'ବାତିଲ୍'; + + @override + String get pdfPaginationDialogOkLabel => r'ଠିକ୍ ଅଛି'; + + @override + String get pdfPasswordDialogCancelLabel => r'ବାତିଲ୍'; + + @override + String get pdfPasswordDialogOpenLabel => r'ଖୋଲନ୍ତୁ |'; + + @override + String get pdfScrollStatusOfLabel => r'ର'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ସଫା କର |'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => + r'ତୁମର ଦସ୍ତଖତ ଅଙ୍କନ କରନ୍ତୁ'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'କଲମ ରଙ୍ଗ |'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'ସଞ୍ଚୟ କରନ୍ତୁ |'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'କପି କରନ୍ତୁ |'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'ହାଇଲାଇଟ୍ କରନ୍ତୁ |'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'ସ୍କ୍ୱିଗ୍ଲି |'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'ଷ୍ଟ୍ରାଇକଥ୍ରୁ |'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'ଅଣ୍ଡରଲାଇନ୍ |'; + + @override + String get rabi1Label => + r'ରବି ' + "'" + r'ଅଲ-ଅୱାଲ୍ |'; + + @override + String get rabi2Label => + r'ରବି ' + "'" + r'ଅଲ-ଥାନି |'; + + @override + String get rajabLabel => r'ରାଜାବ'; + + @override + String get ramadanLabel => r'ରମଜାନ |'; + + @override + String get rowsPerPageDataPagerLabel => r'ପ୍ରତି ପୃଷ୍ଠାରେ ଧାଡି |'; + + @override + String get safarLabel => r'ସଫର |'; + + @override + String get searchDataGridFilteringLabel => r'ସନ୍ଧାନ କର |'; + + @override + String get selectAllDataGridFilteringLabel => r'ସବୁ ବାଛ'; + + @override + String get series => r'ସିରିଜ୍'; + + @override + String get shaabanLabel => r'ଶାବାନ୍ |'; + + @override + String get shawwalLabel => r'ଶାୱାଲ୍ |'; + + @override + String get shortDhualhiLabel => r'ଧୁଲ୍-ଏଚ୍'; + + @override + String get shortDhualqiLabel => r'ଧୁଲ୍-ପ୍ର'; + + @override + String get shortJumada1Label => r'ଜମ୍। ମୁଁ'; + + @override + String get shortJumada2Label => r'ଜମ୍। II'; + + @override + String get shortMuharramLabel => r'ମୁହ।'; + + @override + String get shortRabi1Label => r'ରବି | ମୁଁ'; + + @override + String get shortRabi2Label => r'ରବି | II'; + + @override + String get shortRajabLabel => r'ରାଜ।'; + + @override + String get shortRamadanLabel => r'ରାମ।'; + + @override + String get shortSafarLabel => r'ନିରାପଦ'; + + @override + String get shortShaabanLabel => r'ଶ।'; + + @override + String get shortShawwalLabel => r'ଶ।'; + + @override + String get showRowsWhereDataGridFilteringLabel => + r'ଧାଡିଗୁଡିକ କେଉଁଠାରେ ଦେଖାନ୍ତୁ |'; + + @override + String get sortAToZDataGridFilteringLabel => r'A ରୁ Z ସର୍ଟ କରନ୍ତୁ |'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'ସର୍ଟ ଏବଂ ଫିଲ୍ଟର୍ |'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'ବଡ଼ରୁ ଛୋଟ ପର୍ଯ୍ୟନ୍ତ ସର୍ଟ କରନ୍ତୁ |'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'ପୁରାତନରୁ ନୂତନକୁ ସର୍ଟ କରନ୍ତୁ |'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'ପୁରାତନରୁ ନୂତନକୁ ସର୍ଟ କରନ୍ତୁ |'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'ଛୋଟରୁ ବଡକୁ ସର୍ଟ କରନ୍ତୁ |'; + + @override + String get sortZToADataGridFilteringLabel => r'Z ରୁ A ସର୍ଟ କରନ୍ତୁ |'; + + @override + String get textFiltersDataGridFilteringLabel => r'ପାଠ୍ୟ ଫିଲ୍ଟର୍ |'; + + @override + String get todayLabel => r'ଆଜି |'; + + @override + String get weeknumberLabel => r'ସପ୍ତାହ'; +} + +/// The translations for Panjabi Punjabi (`pa`). +class SfLocalizationsPa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsPa class + const SfLocalizationsPa({String localeName = 'pa'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'ਤੋਂ ਬਾਅਦ'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'ਬਾਅਦ ਜਾਂ ਬਰਾਬਰ'; + + @override + String get allDayLabel => r'ਸਾਰਾ ਦਿਨ'; + + @override + String get allowedViewDayLabel => r'ਦਿਨ'; + + @override + String get allowedViewMonthLabel => r'ਮਹੀਨਾ'; + + @override + String get allowedViewScheduleLabel => r'ਸਮਾਸੂਚੀ, ਕਾਰਜ - ਕ੍ਰਮ'; + + @override + String get allowedViewTimelineDayLabel => r'ਸਮਾਂਰੇਖਾ ਦਿਨ'; + + @override + String get allowedViewTimelineMonthLabel => r'ਸਮਾਂਰੇਖਾ ਮਹੀਨਾ'; + + @override + String get allowedViewTimelineWeekLabel => r'ਸਮਾਂਰੇਖਾ ਹਫ਼ਤਾ'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'ਟਾਈਮਲਾਈਨ ਕੰਮ ਹਫ਼ਤਾ'; + + @override + String get allowedViewWeekLabel => r'ਹਫ਼ਤਾ'; + + @override + String get allowedViewWorkWeekLabel => r'ਕੰਮ ਦਾ ਹਫ਼ਤਾ'; + + @override + String get andDataGridFilteringLabel => r'ਅਤੇ'; + + @override + String get beforeDataGridFilteringLabel => r'ਅੱਗੇ'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'ਅੱਗੇ ਜਾਂ ਬਰਾਬਰ'; + + @override + String get beginsWithDataGridFilteringLabel => r'ਨਾਲ ਸ਼ੁਰੂ ਹੁੰਦਾ ਹੈ'; + + @override + String get cancelDataGridFilteringLabel => r'ਰੱਦ ਕਰੋ'; + + @override + String get clearFilterDataGridFilteringLabel => r'ਫਿਲਟਰ ਸਾਫ਼ ਕਰੋ'; + + @override + String get containsDataGridFilteringLabel => r'ਸ਼ਾਮਿਲ ਹੈ'; + + @override + String get dateFiltersDataGridFilteringLabel => r'ਮਿਤੀ ਫਿਲਟਰ'; + + @override + String get daySpanCountLabel => r'ਦਿਨ'; + + @override + String get dhualhiLabel => r'ਧੂ ਅਲ-ਹਿੱਜਾ'; + + @override + String get dhualqiLabel => r'ਧੂ ਅਲ-ਕਾਇਦਾ'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'ਨਾਲ ਸ਼ੁਰੂ ਨਹੀਂ ਹੁੰਦਾ'; + + @override + String get doesNotContainDataGridFilteringLabel => r'ਸ਼ਾਮਲ ਨਹੀਂ ਹੈ'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'ਨਾਲ ਖਤਮ ਨਹੀਂ ਹੁੰਦਾ'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'ਬਰਾਬਰ ਨਹੀਂ ਹੈ'; + + @override + String get emptyDataGridFilteringLabel => r'ਖਾਲੀ'; + + @override + String get endsWithDataGridFilteringLabel => r'ਨਾਲ ਖਤਮ ਹੁੰਦਾ ਹੈ'; + + @override + String get equalsDataGridFilteringLabel => r'ਬਰਾਬਰ'; + + @override + String get fromDataGridFilteringLabel => r'ਤੋਂ'; + + @override + String get greaterThanDataGridFilteringLabel => r'ਵੱਧ'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'ਇਸ ਤੋਂ ਵੱਧ ਜਾਂ ਬਰਾਬਰ'; + + @override + String get jumada1Label => r'ਜੁਮਾਦਾ ਅਲ-ਅੱਵਲ'; + + @override + String get jumada2Label => r'ਜੁਮਾਦਾ ਅਲ-ਥਾਨੀ'; + + @override + String get lessThanDataGridFilteringLabel => r'ਉਸ ਤੋਂ ਘਟ'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'ਇਸ ਤੋਂ ਘੱਟ ਜਾਂ ਬਰਾਬਰ'; + + @override + String get muharramLabel => r'ਮੁਹੱਰਮ'; + + @override + String get noEventsCalendarLabel => r'ਕੋਈ ਸਮਾਗਮ ਨਹੀਂ'; + + @override + String get noMatchesDataGridFilteringLabel => r'ਕੋਈ ਮੇਲ ਨਹੀਂ'; + + @override + String get noSelectedDateCalendarLabel => r'ਕੋਈ ਤਾਰੀਖ ਚੁਣੀ ਨਹੀਂ ਗਈ'; + + @override + String get notEmptyDataGridFilteringLabel => r'ਖਾਲੀ ਨਹੀਂ'; + + @override + String get notNullDataGridFilteringLabel => r'ਨਲ ਨਹੀਂ'; + + @override + String get nullDataGridFilteringLabel => r'ਨਲ'; + + @override + String get numberFiltersDataGridFilteringLabel => r'ਨੰਬਰ ਫਿਲਟਰ'; + + @override + String get ofDataPagerLabel => r'ਦੇ'; + + @override + String get okDataGridFilteringLabel => r'ਠੀਕ ਹੈ'; + + @override + String get orDataGridFilteringLabel => r'ਜਾਂ'; + + @override + String get pagesDataPagerLabel => r'ਪੰਨੇ'; + + @override + String get passwordDialogContentLabel => + r'ਇਸ PDF ਫਾਈਲ ਨੂੰ ਖੋਲ੍ਹਣ ਲਈ ਪਾਸਵਰਡ ਦਰਜ ਕਰੋ'; + + @override + String get passwordDialogHeaderTextLabel => r'ਪਾਸਵਰਡ ਸੁਰੱਖਿਅਤ ਹੈ'; + + @override + String get passwordDialogHintTextLabel => r'ਪਾਸਵਰਡ ਦਰਜ ਕਰੋ'; + + @override + String get passwordDialogInvalidPasswordLabel => r'ਗਲਤ ਪਾਸਵਰਡ'; + + @override + String get pdfBookmarksLabel => r'ਬੁੱਕਮਾਰਕਸ'; + + @override + String get pdfEnterPageNumberLabel => r'ਪੰਨਾ ਨੰਬਰ ਦਰਜ ਕਰੋ'; + + @override + String get pdfGoToPageLabel => + r'ਪੰਨੇ ' + "'" + r'ਤੇ ਜਾਓ'; + + @override + String get pdfHyperlinkContentLabel => + r'ਕੀ ਤੁਸੀਂ ' + "'" + r'ਤੇ ਪੰਨਾ ਖੋਲ੍ਹਣਾ ਚਾਹੁੰਦੇ ਹੋ'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'ਰੱਦ ਕਰੋ'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'ਖੋਲ੍ਹੋ'; + + @override + String get pdfHyperlinkLabel => r'ਵੈੱਬ ਪੰਨਾ ਖੋਲ੍ਹੋ'; + + @override + String get pdfInvalidPageNumberLabel => r'ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਵੈਧ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; + + @override + String get pdfNoBookmarksLabel => r'ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲੇ'; + + @override + String get pdfPaginationDialogCancelLabel => r'ਰੱਦ ਕਰੋ'; + + @override + String get pdfPaginationDialogOkLabel => r'ਠੀਕ ਹੈ'; + + @override + String get pdfPasswordDialogCancelLabel => r'ਰੱਦ ਕਰੋ'; + + @override + String get pdfPasswordDialogOpenLabel => r'ਖੋਲ੍ਹੋ'; + + @override + String get pdfScrollStatusOfLabel => r'ਦੇ'; + + @override + String get pdfSignaturePadDialogClearLabel => r'ਸਾਫ਼'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'ਆਪਣੇ ਦਸਤਖਤ ਖਿੱਚੋ'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'ਪੈੱਨ ਦਾ ਰੰਗ'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'ਸੇਵ ਕਰੋ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'ਕਾਪੀ ਕਰੋ'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'ਹਾਈਲਾਈਟ ਕਰੋ'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'ਕੁਰਲ਼'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'ਸਟ੍ਰਾਈਕਥਰੂ'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'ਰੇਖਾਂਕਿਤ ਕਰੋ'; + + @override + String get rabi1Label => r'ਰਬੀ ਅਲ-ਅੱਵਲ'; + + @override + String get rabi2Label => r'ਰਬੀ ਅਲ-ਥਾਨੀ'; + + @override + String get rajabLabel => r'ਰਜਬ'; + + @override + String get ramadanLabel => r'ਰਮਜ਼ਾਨ'; + + @override + String get rowsPerPageDataPagerLabel => r'ਪ੍ਰਤੀ ਪੰਨਾ ਕਤਾਰਾਂ'; + + @override + String get safarLabel => r'ਸਫਰ'; + + @override + String get searchDataGridFilteringLabel => r'ਖੋਜ'; + + @override + String get selectAllDataGridFilteringLabel => r'ਸਾਰਿਆ ਨੂੰ ਚੁਣੋ'; + + @override + String get series => r'ਲੜੀ'; + + @override + String get shaabanLabel => r'ਸ਼ਾਅਬਾਨ'; + + @override + String get shawwalLabel => r'ਸ਼ਵਾਲ'; + + @override + String get shortDhualhiLabel => r'ਧੂਲ-ਐੱਚ'; + + @override + String get shortDhualqiLabel => r'ਧੂਲ-ਕਿਊ'; + + @override + String get shortJumada1Label => r'ਜਮ. ਆਈ'; + + @override + String get shortJumada2Label => r'ਜਮ. II'; + + @override + String get shortMuharramLabel => r'ਮੁਹ.'; + + @override + String get shortRabi1Label => r'ਰਬੀ. ਆਈ'; + + @override + String get shortRabi2Label => r'ਰਬੀ. II'; + + @override + String get shortRajabLabel => r'ਰਾਜ.'; + + @override + String get shortRamadanLabel => r'ਰਾਮ.'; + + @override + String get shortSafarLabel => r'ਸਫ਼.'; + + @override + String get shortShaabanLabel => r'ਸ਼ਾ.'; + + @override + String get shortShawwalLabel => r'ਸ਼ਾ.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'ਕਤਾਰਾਂ ਦਿਖਾਓ ਕਿੱਥੇ'; + + @override + String get sortAToZDataGridFilteringLabel => r'A ਤੋਂ Z ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰੋ'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'ਕ੍ਰਮਬੱਧ ਅਤੇ ਫਿਲਟਰ'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'ਸਭ ਤੋਂ ਵੱਡੇ ਤੋਂ ਛੋਟੇ ਨੂੰ ਛਾਂਟੋ'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'ਸਭ ਤੋਂ ਨਵੇਂ ਤੋਂ ਪੁਰਾਣੇ ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰੋ'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'ਸਭ ਤੋਂ ਪੁਰਾਣੇ ਤੋਂ ਨਵੀਨਤਮ ਕ੍ਰਮਬੱਧ ਕਰੋ'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'ਸਭ ਤੋਂ ਛੋਟੇ ਤੋਂ ਵੱਡੇ ਨੂੰ ਛਾਂਟੋ'; + + @override + String get sortZToADataGridFilteringLabel => r'Z ਤੋਂ A ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰੋ'; + + @override + String get textFiltersDataGridFilteringLabel => r'ਟੈਕਸਟ ਫਿਲਟਰ'; + + @override + String get todayLabel => r'ਅੱਜ'; + + @override + String get weeknumberLabel => r'ਹਫ਼ਤਾ'; +} + +/// The translations for Polish (`pl`). +class SfLocalizationsPl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsPl class + const SfLocalizationsPl({String localeName = 'pl'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Później'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Po lub równe'; + + @override + String get allDayLabel => r'Cały dzień'; + + @override + String get allowedViewDayLabel => r'Dzień'; + + @override + String get allowedViewMonthLabel => r'Miesiąc'; + + @override + String get allowedViewScheduleLabel => r'Harmonogram'; + + @override + String get allowedViewTimelineDayLabel => r'Dzień osi czasu'; + + @override + String get allowedViewTimelineMonthLabel => r'Miesiąc osi czasu'; + + @override + String get allowedViewTimelineWeekLabel => r'Tydzień osi czasu'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'Tydzień roboczy osi czasu'; + + @override + String get allowedViewWeekLabel => r'Tydzień'; + + @override + String get allowedViewWorkWeekLabel => r'Tydzień pracy'; + + @override + String get andDataGridFilteringLabel => r'I'; + + @override + String get beforeDataGridFilteringLabel => r'Zanim'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Przed Lub Równy'; + + @override + String get beginsWithDataGridFilteringLabel => r'Zaczyna się z'; + + @override + String get cancelDataGridFilteringLabel => r'Anulować'; + + @override + String get clearFilterDataGridFilteringLabel => r'Czysty filtr'; + + @override + String get containsDataGridFilteringLabel => r'Zawiera'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filtry daty'; + + @override + String get daySpanCountLabel => r'Dzień'; + + @override + String get dhualhiLabel => r'Du al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Nie zaczyna się od'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Nie zawiera'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Nie kończy się na'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Nie równa się'; + + @override + String get emptyDataGridFilteringLabel => r'Pusty'; + + @override + String get endsWithDataGridFilteringLabel => r'Kończy się na'; + + @override + String get equalsDataGridFilteringLabel => r'Równa się'; + + @override + String get fromDataGridFilteringLabel => r'Z'; + + @override + String get greaterThanDataGridFilteringLabel => r'Większe niż'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Większe lub równe'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al Thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Mniej niż'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Mniej niż lub równo'; + + @override + String get muharramLabel => r'muharrama'; + + @override + String get noEventsCalendarLabel => r'Brak wydarzeń'; + + @override + String get noMatchesDataGridFilteringLabel => r'Brak wyników'; + + @override + String get noSelectedDateCalendarLabel => r'Brak wybranej daty'; + + @override + String get notEmptyDataGridFilteringLabel => r'Nie pusty'; + + @override + String get notNullDataGridFilteringLabel => r'Nie jest zerem'; + + @override + String get nullDataGridFilteringLabel => r'Zero'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filtry liczbowe'; + + @override + String get ofDataPagerLabel => r'z'; + + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'Lub'; + + @override + String get pagesDataPagerLabel => r'strony'; + + @override + String get passwordDialogContentLabel => + r'Wprowadź hasło, aby otworzyć ten plik PDF'; + + @override + String get passwordDialogHeaderTextLabel => r'Hasło chronione'; + + @override + String get passwordDialogHintTextLabel => r'Wprowadź hasło'; + + @override + String get passwordDialogInvalidPasswordLabel => r'Nieprawidłowe hasło'; + + @override + String get pdfBookmarksLabel => r'Zakładki'; + + @override + String get pdfEnterPageNumberLabel => r'Wprowadź numer strony'; + + @override + String get pdfGoToPageLabel => r'Idź do strony'; + + @override + String get pdfHyperlinkContentLabel => r'Czy chcesz otworzyć stronę o godz'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'Anuluj'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'Otwórz'; + + @override + String get pdfHyperlinkLabel => r'Otwórz stronę internetową'; + + @override + String get pdfInvalidPageNumberLabel => r'Proszę wprowadzić poprawny numer'; + + @override + String get pdfNoBookmarksLabel => r'Nie znaleziono zakładek'; + + @override + String get pdfPaginationDialogCancelLabel => r'Anuluj'; + + @override + String get pdfPaginationDialogOkLabel => r'OK'; + + @override + String get pdfPasswordDialogCancelLabel => r'Anuluj'; + + @override + String get pdfPasswordDialogOpenLabel => r'Otwórz'; + + @override + String get pdfScrollStatusOfLabel => r'z'; + + @override + String get pdfSignaturePadDialogClearLabel => r'Wyczyść'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Narysuj swój podpis'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Kolor pióra'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'Zapisz'; + + @override + String get rabi1Label => r'Rabi al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; + + @override + String get rajabLabel => r'Radżab'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopiuj'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Podświetl'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Faliste'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'Przekreślenie'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Podkreślać'; + + @override + String get ramadanLabel => r'Ramadan'; + + @override + String get rowsPerPageDataPagerLabel => r'Wiersze na stronę'; + + @override + String get safarLabel => r'Safar'; + + @override + String get searchDataGridFilteringLabel => r'Szukaj'; + + @override + String get selectAllDataGridFilteringLabel => r'Zaznacz wszystko'; + + @override + String get series => r'Seria'; + + @override + String get shaabanLabel => + r'Sha' + "'" + r'aban'; + + @override + String get shawwalLabel => r'Shawwal'; + + @override + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; + + @override + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; + + @override + String get shortJumada1Label => r'sok. I'; + + @override + String get shortJumada2Label => r'sok. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'Rabi. I'; + + @override + String get shortRabi2Label => r'Rabi. II'; + + @override + String get shortRajabLabel => r'Raj.'; + + @override + String get shortRamadanLabel => r'Baran.'; + + @override + String get shortSafarLabel => r'bezpieczny'; + + @override + String get shortShaabanLabel => r'Sha.'; + + @override + String get shortShawwalLabel => r'Shawa.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'Pokaż wiersze gdzie'; + + @override + String get sortAToZDataGridFilteringLabel => r'Sortuj od A do Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Sortuj i filtruj'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sortuj od największego do najmniejszego'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Sortuj od najnowszych do najstarszych'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Sortuj od najstarszych do najnowszych'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sortuj od najmniejszego do największego'; + + @override + String get sortZToADataGridFilteringLabel => r'Sortuj od Z do A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Filtry tekstowe'; + + @override + String get todayLabel => r'Dziś'; + + @override + String get weeknumberLabel => r'Tydzień'; +} + +/// The translations for Pushto Pashto (`ps`). +class SfLocalizationsPs extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsPs class + const SfLocalizationsPs({String localeName = 'ps'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'وروسته'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'وروسته یا مساوي'; + + @override + String get allDayLabel => r'ټوله ورځ'; + + @override + String get allowedViewDayLabel => r'ورځ'; + + @override + String get allowedViewMonthLabel => r'میاشت'; + + @override + String get allowedViewScheduleLabel => r'مهالویش'; + + @override + String get allowedViewTimelineDayLabel => r'د مهال ویش ورځ'; + + @override + String get allowedViewTimelineMonthLabel => r'د مهال ویش میاشت'; + + @override + String get allowedViewTimelineWeekLabel => r'د مهال ویش اونۍ'; + + @override + String get allowedViewTimelineWorkWeekLabel => r'مهال ویش کاري اونۍ'; + + @override + String get allowedViewWeekLabel => r'اونۍ'; + + @override + String get allowedViewWorkWeekLabel => r'کاري اونۍ'; + + @override + String get andDataGridFilteringLabel => r'او'; + + @override + String get beforeDataGridFilteringLabel => r'مخکې'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'مخکې یا مساوي'; + + @override + String get beginsWithDataGridFilteringLabel => r'سره پیل کیږي'; + + @override + String get cancelDataGridFilteringLabel => r'لغوه کړئ'; + + @override + String get clearFilterDataGridFilteringLabel => r'فلټر پاک کړئ'; + + @override + String get containsDataGridFilteringLabel => r'لري'; + + @override + String get dateFiltersDataGridFilteringLabel => r'د نیټې فلټرونه'; + + @override + String get daySpanCountLabel => r'ورځ'; + + @override + String get dhualhiLabel => r'ذوالحجې'; + + @override + String get dhualqiLabel => r'ذو القعده'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'سره پیل نه کوي'; + + @override + String get doesNotContainDataGridFilteringLabel => r'نه لري'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'سره پای نه لري'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'برابر نه دی'; + + @override + String get emptyDataGridFilteringLabel => r'خالي'; + + @override + String get endsWithDataGridFilteringLabel => r'سره پای ته رسیږي'; + + @override + String get equalsDataGridFilteringLabel => r'مساوي'; + + @override + String get fromDataGridFilteringLabel => r'څخه'; + + @override + String get greaterThanDataGridFilteringLabel => r'په پرتله لوی'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'په پرتله لوی یا مساوي'; + + @override + String get jumada1Label => r'جمعه الاول'; + + @override + String get jumada2Label => r'جمعه الثاني'; + + @override + String get lessThanDataGridFilteringLabel => r'په پرتله لږ'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'لږ یا مساوي'; + + @override + String get muharramLabel => r'محرم'; + + @override + String get noEventsCalendarLabel => r'هیڅ پیښه نشته'; + + @override + String get noMatchesDataGridFilteringLabel => r'هیڅ ټکي نه دي'; + + @override + String get noSelectedDateCalendarLabel => r'نه ټاکل شوې نیټه'; + + @override + String get notEmptyDataGridFilteringLabel => r'نه خالي'; + + @override + String get notNullDataGridFilteringLabel => r'نه نول'; + + @override + String get nullDataGridFilteringLabel => r'نول'; + + @override + String get numberFiltersDataGridFilteringLabel => r'د شمیر فلټرونه'; + + @override + String get ofDataPagerLabel => r'د'; + + @override + String get okDataGridFilteringLabel => r'سمه ده'; + + @override + String get orDataGridFilteringLabel => r'یا'; + + @override + String get pagesDataPagerLabel => r'پاڼې'; + + @override + String get passwordDialogContentLabel => + r'د دې پی ډی ایف فایل خلاصولو لپاره پاسورډ دننه کړئ'; + + @override + String get passwordDialogHeaderTextLabel => r'پټنوم خوندي شوی'; + + @override + String get passwordDialogHintTextLabel => r'پاسورډ دننه کړئ'; + + @override + String get passwordDialogInvalidPasswordLabel => r'بې اعتباره پټنوم'; + + @override + String get pdfBookmarksLabel => r'بک مارکونه'; + + @override + String get pdfEnterPageNumberLabel => r'د پاڼې شمیره دننه کړئ'; + + @override + String get pdfGoToPageLabel => r'مخ ته لاړ شئ'; + + @override + String get pdfHyperlinkContentLabel => r'ایا تاسو غواړئ پاڼه پرانیزئ'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'فسخه کول'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'خلاص'; + + @override + String get pdfHyperlinkLabel => r'ویب پاڼه پرانیزئ'; + + @override + String get pdfInvalidPageNumberLabel => + r'مهرباني وکړئ یو باوري شمیره دننه کړئ'; + + @override + String get pdfNoBookmarksLabel => r'هیڅ بک مارک ونه موندل شو'; + + @override + String get pdfPaginationDialogCancelLabel => r'فسخه کول'; + + @override + String get pdfPaginationDialogOkLabel => r'سمه ده'; + + @override + String get pdfPasswordDialogCancelLabel => r'فسخه کول'; + + @override + String get pdfPasswordDialogOpenLabel => r'خلاص'; + + @override + String get pdfScrollStatusOfLabel => r'د'; + + @override + String get pdfSignaturePadDialogClearLabel => r'روښانه'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'خپل لاسلیک رسم کړئ'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'د قلم رنګ'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'خوندي کړئ'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'کاپي'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'روښانه کول'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'موجي کرښه'; + + @override + String get pdfTextSelectionMenuStrikethroughLabel => r'کرښه کول'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'لاندې'; + + @override + String get rabi1Label => r'ربیع الاول'; + + @override + String get rabi2Label => r'ربیع الثاني'; + + @override + String get rajabLabel => r'رجب'; + + @override + String get ramadanLabel => r'رمضان'; + + @override + String get rowsPerPageDataPagerLabel => r'په هره پاڼه کې قطارونه'; + + @override + String get safarLabel => r'صفر'; + + @override + String get searchDataGridFilteringLabel => r'لټون'; + + @override + String get selectAllDataGridFilteringLabel => r'ټول وټاکئ'; + + @override + String get series => r'لړۍ'; + + @override + String get shaabanLabel => r'شعبان'; + + @override + String get shawwalLabel => r'شوال'; + + @override + String get shortDhualhiLabel => r'ذوالح'; + + @override + String get shortDhualqiLabel => r'ذوالق'; + + @override + String get shortJumada1Label => r'جم. زه'; + + @override + String get shortJumada2Label => r'جم. II'; + + @override + String get shortMuharramLabel => r'Muh.'; + + @override + String get shortRabi1Label => r'ربیع. زه'; + + @override + String get shortRabi2Label => r'ربیع. II'; + + @override + String get shortRajabLabel => r'راج.'; + + @override + String get shortRamadanLabel => r'رام'; + + @override + String get shortSafarLabel => r'سف'; + + @override + String get shortShaabanLabel => r'شا'; + + @override + String get shortShawwalLabel => r'شو.'; + + @override + String get showRowsWhereDataGridFilteringLabel => r'قطارونه وښایاست چیرته'; + + @override + String get sortAToZDataGridFilteringLabel => r'له A څخه Z ترتیب کړئ'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'ترتیب او فلټر کړئ'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'له لوی څخه کوچنی ترتیب کړئ'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'له نوي څخه تر ټولو زوړ ترتیب کړئ'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'له زاړه څخه تر نوي پورې ترتیب کړئ'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'له کوچنی څخه تر ټولو لوی ترتیب کړئ'; + + @override + String get sortZToADataGridFilteringLabel => r'له Z څخه A ترتیب کړئ'; + + @override + String get textFiltersDataGridFilteringLabel => r'د متن فلټرونه'; + + @override + String get todayLabel => r'نن'; + + @override + String get weeknumberLabel => r'اونۍ'; +} + +/// The translations for Portuguese (`pt`). +class SfLocalizationsPt extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsPt class + const SfLocalizationsPt({String localeName = 'pt'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Depois'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Depois Ou Igual'; + + @override + String get allDayLabel => r'Dia todo'; + + @override + String get allowedViewDayLabel => r'Dia'; + + @override + String get allowedViewMonthLabel => r'Mês'; + + @override + String get allowedViewScheduleLabel => r'Cronograma'; + + @override + String get allowedViewTimelineDayLabel => r'Dia da linha do tempo'; + + @override + String get allowedViewTimelineMonthLabel => r'Mês da linha do tempo'; + + @override + String get allowedViewTimelineWeekLabel => r'Semana da linha do tempo'; + + @override + String get allowedViewTimelineWorkWeekLabel => + r'Semana de trabalho da linha do tempo'; + + @override + String get allowedViewWeekLabel => r'Semana'; + + @override + String get allowedViewWorkWeekLabel => r'Semana de trabalho'; + + @override + String get andDataGridFilteringLabel => r'E'; + + @override + String get beforeDataGridFilteringLabel => r'Antes'; + + @override + String get beforeOrEqualDataGridFilteringLabel => r'Antes Ou Igual'; + + @override + String get beginsWithDataGridFilteringLabel => r'Começa com'; + + @override + String get cancelDataGridFilteringLabel => r'Cancelar'; + + @override + String get clearFilterDataGridFilteringLabel => r'Filtro limpo'; + + @override + String get containsDataGridFilteringLabel => r'contém'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Filtros de Data'; + + @override + String get daySpanCountLabel => r'Dia'; + + @override + String get dhualhiLabel => r'Dhu al-Hijjah'; + + @override + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Não começa com'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Não contém'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Não termina com'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Não é igual'; + + @override + String get emptyDataGridFilteringLabel => r'Vazio'; + + @override + String get endsWithDataGridFilteringLabel => r'Termina com'; + + @override + String get equalsDataGridFilteringLabel => r'É igual a'; + + @override + String get fromDataGridFilteringLabel => r'A partir de'; + + @override + String get greaterThanDataGridFilteringLabel => r'Maior que'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => r'Maior ou igual'; + + @override + String get jumada1Label => r'Jumada al-awwal'; + + @override + String get jumada2Label => r'Jumada al-thani'; + + @override + String get lessThanDataGridFilteringLabel => r'Menor que'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => r'Menor ou igual'; + + @override + String get muharramLabel => r'Muharram'; + + @override + String get noEventsCalendarLabel => r'Sem eventos'; + + @override + String get noMatchesDataGridFilteringLabel => r'Sem combinações'; + + @override + String get noSelectedDateCalendarLabel => r'Nenhuma data selecionada'; + + @override + String get notEmptyDataGridFilteringLabel => r'Não está vazio'; + + @override + String get notNullDataGridFilteringLabel => r'Não nulo'; + + @override + String get nullDataGridFilteringLabel => r'Nulo'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Filtros de número'; + + @override + String get ofDataPagerLabel => r'de'; + + @override + String get okDataGridFilteringLabel => r'OK'; + + @override + String get orDataGridFilteringLabel => r'Ou'; + + @override + String get pagesDataPagerLabel => r'Páginas'; @override - String get allowedViewTimelineWorkWeekLabel => - '\u{c9f}\u{cc8}\u{cae}\u{ccd}\u{200c}\u{cb2}\u{cc8}\u{ca8}\u{ccd}\u{20}\u{c95}\u{cc6}\u{cb2}\u{cb8}\u{ca6}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + String get passwordDialogContentLabel => + r'Digite a senha para abrir este arquivo PDF'; @override - String get allowedViewWeekLabel => '\u{cb5}\u{cbe}\u{cb0}'; + String get passwordDialogHeaderTextLabel => r'Protegido por senha'; @override - String get allowedViewWorkWeekLabel => - '\u{c95}\u{cc6}\u{cb2}\u{cb8}\u{ca6}\u{20}\u{cb5}\u{cbe}\u{cb0}'; + String get passwordDialogHintTextLabel => r'Digite a senha'; @override - String get daySpanCountLabel => '\u{ca6}\u{cbf}\u{ca8}'; + String get passwordDialogInvalidPasswordLabel => r'senha inválida'; @override - String get dhualhiLabel => - '\u{ca7}\u{cc1}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{cb9}\u{cbf}\u{c9c}\u{ccd}\u{c9c}\u{cbe}'; + String get pdfBookmarksLabel => r'Favoritos'; @override - String get dhualqiLabel => - '\u{ca7}\u{cc1}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c96}\u{cbf}\u{ca6}\u{cbe}'; + String get pdfEnterPageNumberLabel => r'Insira o número da página'; @override - String get jumada1Label => - '\u{c9c}\u{cc1}\u{cae}\u{cbe}\u{ca6}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; + String get pdfGoToPageLabel => r'Vá para página'; @override - String get jumada2Label => - '\u{c9c}\u{cc1}\u{cae}\u{cbe}\u{ca6}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + String get pdfHyperlinkContentLabel => r'Deseja abrir a página em'; @override - String get muharramLabel => '\u{cae}\u{cca}\u{cb9}\u{cb0}\u{c82}'; + String get pdfHyperlinkDialogCancelLabel => r'CANCELAR'; @override - String get noEventsCalendarLabel => - '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{c98}\u{c9f}\u{ca8}\u{cc6}\u{c97}\u{cb3}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + String get pdfHyperlinkDialogOpenLabel => r'ABRIR'; @override - String get noSelectedDateCalendarLabel => - '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{c86}\u{caf}\u{ccd}\u{c95}\u{cc6}\u{20}\u{ca6}\u{cbf}\u{ca8}\u{cbe}\u{c82}\u{c95}\u{cb5}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + String get pdfHyperlinkLabel => r'Abrir página da Web'; @override - String get ofDataPagerLabel => '\u{ca8}'; + String get pdfInvalidPageNumberLabel => r'Por favor insira um número válido'; @override - String get pagesDataPagerLabel => - '\u{caa}\u{cc1}\u{c9f}\u{c97}\u{cb3}\u{cc1}'; + String get pdfNoBookmarksLabel => r'Nenhum favorito encontrado'; @override - String get passwordDialogContentLabel => - '\u{c88}\u{20}\u{50}\u{44}\u{46}\u{20}\u{cab}\u{cc8}\u{cb2}\u{ccd}\u{20}\u{c85}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cb2}\u{cc1}\u{20}\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}\u{20}\u{c85}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + String get pdfPaginationDialogCancelLabel => r'CANCELAR'; @override - String get passwordDialogHeaderTextLabel => - '\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}\u{20}\u{cb0}\u{c95}\u{ccd}\u{cb7}\u{cbf}\u{cb8}\u{cb2}\u{cbe}\u{c97}\u{cbf}\u{ca6}\u{cc6}'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get passwordDialogHintTextLabel => - '\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + String get pdfPasswordDialogCancelLabel => r'CANCELAR'; @override - String get passwordDialogInvalidPasswordLabel => - '\u{c85}\u{cae}\u{cbe}\u{ca8}\u{ccd}\u{caf}\u{cb5}\u{cbe}\u{ca6}\u{20}\u{caa}\u{cbe}\u{cb8}\u{ccd}\u{cb5}\u{cb0}\u{ccd}\u{ca1}\u{ccd}'; + String get pdfPasswordDialogOpenLabel => r'ABRIR'; @override - String get pdfBookmarksLabel => - '\u{cac}\u{cc1}\u{c95}\u{ccd}\u{200c}\u{cae}\u{cbe}\u{cb0}\u{ccd}\u{c95}\u{ccd}\u{200c}\u{c97}\u{cb3}\u{cc1}'; + String get pdfScrollStatusOfLabel => r'do'; @override - String get pdfEnterPageNumberLabel => - '\u{caa}\u{cc1}\u{c9f}\u{20}\u{cb8}\u{c82}\u{c96}\u{ccd}\u{caf}\u{cc6}\u{caf}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + String get pdfSignaturePadDialogClearLabel => r'Limpar'; @override - String get pdfGoToPageLabel => - '\u{caa}\u{cc1}\u{c9f}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb9}\u{cc6}\u{cc2}\u{cd5}\u{c97}\u{cbf}\u{cb0}\u{cbf}'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Desenhe sua assinatura'; @override - String get pdfInvalidPageNumberLabel => - '\u{ca6}\u{caf}\u{cb5}\u{cbf}\u{c9f}\u{ccd}\u{c9f}\u{cc1}\u{20}\u{cae}\u{cbe}\u{ca8}\u{ccd}\u{caf}\u{cb5}\u{cbe}\u{ca6}\u{20}\u{cb8}\u{c82}\u{c96}\u{ccd}\u{caf}\u{cc6}\u{caf}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{ca8}\u{cae}\u{cc2}\u{ca6}\u{cbf}\u{cb8}\u{cbf}'; + String get pdfSignaturePadDialogPenColorLabel => r'Cor da caneta'; @override - String get pdfNoBookmarksLabel => - '\u{caf}\u{cbe}\u{cb5}\u{cc1}\u{ca6}\u{cc7}\u{20}\u{cac}\u{cc1}\u{c95}\u{ccd}\u{200c}\u{cae}\u{cbe}\u{cb0}\u{ccd}\u{c95}\u{ccd}\u{200c}\u{c97}\u{cb3}\u{cc1}\u{20}\u{c95}\u{c82}\u{ca1}\u{cc1}\u{cac}\u{c82}\u{ca6}\u{cbf}\u{cb2}\u{ccd}\u{cb2}'; + String get pdfSignaturePadDialogSaveLabel => r'SALVAR'; @override - String get pdfPaginationDialogCancelLabel => - '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{cae}\u{cbe}\u{ca1}\u{cc1}'; + String get pdfTextSelectionMenuCopyLabel => r'Copiar'; @override - String get pdfPaginationDialogOkLabel => '\u{cb8}\u{cb0}\u{cbf}'; + String get pdfTextSelectionMenuHighlightLabel => r'Destaque'; @override - String get pdfPasswordDialogCancelLabel => - '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{cae}\u{cbe}\u{ca1}\u{cc1}'; + String get pdfTextSelectionMenuSquigglyLabel => r'Ondulado'; @override - String get pdfPasswordDialogOpenLabel => - '\u{ca4}\u{cc6}\u{cb0}\u{cc6}\u{caf}\u{cbf}\u{cb0}\u{cbf}'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Tachado'; @override - String get pdfScrollStatusOfLabel => '\u{ca8}'; + String get pdfTextSelectionMenuUnderlineLabel => r'Sublinhado'; @override String get rabi1Label => - '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; + r'Rabi' + "'" + r' al-awwal'; @override String get rabi2Label => - '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + r'Rabi' + "'" + r' al-thani'; @override - String get rajabLabel => '\u{cb0}\u{c9c}\u{cac}\u{ccd}'; + String get rajabLabel => r'Rajab'; @override - String get ramadanLabel => '\u{cb0}\u{c82}\u{c9c}\u{cbe}\u{ca8}\u{ccd}'; + String get ramadanLabel => r'Ramadã'; @override - String get rowsPerPageDataPagerLabel => - '\u{caa}\u{ccd}\u{cb0}\u{ca4}\u{cbf}\u{20}\u{caa}\u{cc1}\u{c9f}\u{c95}\u{ccd}\u{c95}\u{cc6}\u{20}\u{cb8}\u{cbe}\u{cb2}\u{cc1}\u{c97}\u{cb3}\u{cc1}'; + String get rowsPerPageDataPagerLabel => r'Linhas por página'; @override - String get safarLabel => '\u{cb8}\u{cab}\u{cb0}\u{ccd}'; + String get safarLabel => r'Safar'; @override - String get series => '\u{cb8}\u{cb0}\u{ca3}\u{cbf}'; + String get searchDataGridFilteringLabel => r'Procurar'; @override - String get shaabanLabel => '\u{cb6}\u{cbe}\u{cac}\u{cbe}\u{ca8}\u{ccd}'; + String get selectAllDataGridFilteringLabel => r'Selecionar tudo'; @override - String get shawwalLabel => - '\u{cb6}\u{cb5}\u{ccd}\u{cb5}\u{cbe}\u{cb2}\u{ccd}'; + String get series => r'Series'; @override - String get shortDhualhiLabel => - '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{c8e}\u{c9a}\u{ccd}'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get shortDhualqiLabel => - '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{c95}\u{ccd}\u{caf}\u{cc2}'; + String get shawwalLabel => r'Shawwal'; @override - String get shortJumada1Label => '\u{c9c}\u{c82}\u{2e}\u{20}\u{49}'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortJumada2Label => '\u{c9c}\u{c82}\u{2e}\u{20}\u{49}\u{49}'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get shortMuharramLabel => '\u{cae}\u{cc1}\u{cb9}\u{ccd}'; + String get shortJumada1Label => r'Jum. EU'; @override - String get shortRabi1Label => '\u{cb0}\u{cac}\u{cbf}\u{2e}\u{20}\u{49}'; + String get shortJumada2Label => r'Jum. II'; @override - String get shortRabi2Label => '\u{cb0}\u{cac}\u{cbf}\u{2e}\u{20}\u{49}\u{49}'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortRajabLabel => '\u{cb0}\u{cbe}\u{c9c}\u{ccd}\u{2e}'; + String get shortRabi1Label => r'Rabi. EU'; @override - String get shortRamadanLabel => '\u{cb0}\u{cbe}\u{cae}\u{ccd}\u{2e}'; + String get shortRabi2Label => r'Rabi. II'; @override - String get shortSafarLabel => '\u{cb8}\u{cc7}\u{cab}\u{ccd}'; + String get shortRajabLabel => r'Raj.'; @override - String get shortShaabanLabel => '\u{cb6}\u{cbe}\u{2e}'; + String get shortRamadanLabel => r'RAM.'; @override - String get shortShawwalLabel => '\u{cb6}\u{cbe}\u{2e}'; + String get shortSafarLabel => r'seguro'; @override - String get todayLabel => '\u{c87}\u{c82}\u{ca6}\u{cc1}'; + String get shortShaabanLabel => r'Sha.'; @override - String get weeknumberLabel => '\u{cb5}\u{cbe}\u{cb0}'; -} - -/// The translations for Korean (`ko`). -class SfLocalizationsKo extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsKo class - const SfLocalizationsKo({ - String localeName = 'ko', - }) : super( - localeName: localeName, - ); + String get shortShawwalLabel => r'Shaw.'; @override - String get allDayLabel => r'하루 종일'; + String get showRowsWhereDataGridFilteringLabel => r'Mostrar linhas onde'; @override - String get allowedViewDayLabel => r'일'; + String get sortAToZDataGridFilteringLabel => r'Ordenar de A a Z'; @override - String get allowedViewMonthLabel => r'월'; + String get sortAndFilterDataGridFilteringLabel => r'Classificar e Filtrar'; @override - String get allowedViewScheduleLabel => r'일정'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Ordenar do maior para o menor'; @override - String get allowedViewTimelineDayLabel => r'타임라인의 날'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Ordenar do mais recente ao mais antigo'; @override - String get allowedViewTimelineMonthLabel => r'타임라인 월'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Classificar do mais antigo para o mais recente'; @override - String get allowedViewTimelineWeekLabel => r'타임라인 주간'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Classificar do menor para o maior'; @override - String get allowedViewTimelineWorkWeekLabel => r'타임라인 작업 주간'; + String get sortZToADataGridFilteringLabel => r'Ordenar de Z a A'; @override - String get allowedViewWeekLabel => r'주'; + String get textFiltersDataGridFilteringLabel => r'Filtros de texto'; @override - String get allowedViewWorkWeekLabel => r'작업 주간'; + String get todayLabel => r'Hoje'; @override - String get daySpanCountLabel => r'일'; + String get weeknumberLabel => r'Semana'; +} - @override - String get dhualhiLabel => r'두 알 히자'; +/// The translations for Portuguese, as used in Portugal (`pt_PT`). +class SfLocalizationsPtPt extends SfLocalizationsPt { + /// Creating an argument constructor of SfLocalizationsPtPt class + const SfLocalizationsPtPt({String localeName = 'pt_PT'}) + : super(localeName: localeName); @override - String get dhualqiLabel => r'두 알 키다'; + String get pdfEnterPageNumberLabel => r'Digite o número da página'; @override - String get jumada1Label => r'주마다 알-아왈'; + String get pdfTextSelectionMenuHighlightLabel => r'Realçar'; +} + +/// The translations for Romanian Moldavian Moldovan (`ro`). +class SfLocalizationsRo extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsRo class + const SfLocalizationsRo({String localeName = 'ro'}) + : super(localeName: localeName); @override - String get jumada2Label => r'주마다 알타니'; + String get afterDataGridFilteringLabel => r'După'; @override - String get muharramLabel => r'무하람'; + String get afterOrEqualDataGridFilteringLabel => r'După Sau Egal'; @override - String get noEventsCalendarLabel => r'이벤트 없음'; + String get allDayLabel => r'Toată ziua'; @override - String get noSelectedDateCalendarLabel => r'선택한 날짜가 없습니다'; + String get allowedViewDayLabel => r'Zi'; @override - String get ofDataPagerLabel => r'의'; + String get allowedViewMonthLabel => r'Lună'; @override - String get pagesDataPagerLabel => r'페이지'; + String get allowedViewScheduleLabel => r'Programa'; @override - String get passwordDialogContentLabel => r'이 PDF 파일을 열려면 비밀번호를 입력하세요'; + String get allowedViewTimelineDayLabel => r'Ziua cronologică'; @override - String get passwordDialogHeaderTextLabel => r'암호로 보호됨'; + String get allowedViewTimelineMonthLabel => r'Mês da linha do tempo'; @override - String get passwordDialogHintTextLabel => r'암호를 입력'; + String get allowedViewTimelineWeekLabel => r'Semana da linha do tempo'; @override - String get passwordDialogInvalidPasswordLabel => r'유효하지 않은 비밀번호'; + String get allowedViewTimelineWorkWeekLabel => + r'Semana de trabalho da linha do tempo'; @override - String get pdfBookmarksLabel => r'책갈피'; + String get allowedViewWeekLabel => r'Săptămână'; @override - String get pdfEnterPageNumberLabel => r'페이지 번호 입력'; + String get allowedViewWorkWeekLabel => r'Saptamana de lucru'; @override - String get pdfGoToPageLabel => r'페이지로 이동'; + String get andDataGridFilteringLabel => r'Și'; @override - String get pdfInvalidPageNumberLabel => r'유효한 숫자를 입력하세요'; + String get beforeDataGridFilteringLabel => r'Inainte de'; @override - String get pdfNoBookmarksLabel => r'북마크를 찾을 수 없습니다'; + String get beforeOrEqualDataGridFilteringLabel => r'Înainte sau egal'; @override - String get pdfPaginationDialogCancelLabel => r'취소'; + String get beginsWithDataGridFilteringLabel => r'Începe cu'; @override - String get pdfPaginationDialogOkLabel => r'좋아요'; + String get cancelDataGridFilteringLabel => r'Anulare'; @override - String get pdfPasswordDialogCancelLabel => r'취소'; + String get clearFilterDataGridFilteringLabel => r'Șterge filtrul'; @override - String get pdfPasswordDialogOpenLabel => r'열려있는'; + String get containsDataGridFilteringLabel => r'Conține'; @override - String get pdfScrollStatusOfLabel => r'의'; + String get dateFiltersDataGridFilteringLabel => r'Filtre de dată'; @override - String get rabi1Label => r'라비 알-아왈'; + String get daySpanCountLabel => r'Zi'; @override - String get rabi2Label => r'라비 알타니'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get rajabLabel => r'라자브'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get ramadanLabel => r'라마단'; + String get doesNotBeginWithDataGridFilteringLabel => r'Nu Începe Cu'; @override - String get rowsPerPageDataPagerLabel => r'페이지당 행'; + String get doesNotContainDataGridFilteringLabel => r'Nu contine'; @override - String get safarLabel => r'사파르'; + String get doesNotEndWithDataGridFilteringLabel => r'Nu se termină cu'; @override - String get series => r'시리즈'; + String get doesNotEqualDataGridFilteringLabel => r'Nu este egal'; @override - String get shaabanLabel => r'샤아반'; + String get emptyDataGridFilteringLabel => r'Gol'; @override - String get shawwalLabel => r'샤왈'; + String get endsWithDataGridFilteringLabel => r'Se termină cu'; @override - String get shortDhualhiLabel => r'둘-H'; + String get equalsDataGridFilteringLabel => r'Egal'; @override - String get shortDhualqiLabel => r'둘큐'; + String get fromDataGridFilteringLabel => r'Din'; @override - String get shortJumada1Label => r'줌. 나'; + String get greaterThanDataGridFilteringLabel => r'Mai mare ca'; @override - String get shortJumada2Label => r'줌. II'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Mai mare sau egal'; @override - String get shortMuharramLabel => r'음.'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get shortRabi1Label => r'라비. 나'; + String get jumada2Label => r'Jumada al-thani'; @override - String get shortRabi2Label => r'라비. II'; + String get lessThanDataGridFilteringLabel => r'Mai puțin decât'; @override - String get shortRajabLabel => r'주권.'; + String get lessThanOrEqualDataGridFilteringLabel => r'Mai puțin sau egal'; @override - String get shortRamadanLabel => r'램.'; + String get muharramLabel => r'Muharram'; @override - String get shortSafarLabel => r'사프.'; + String get noEventsCalendarLabel => r'Fără evenimente'; @override - String get shortShaabanLabel => r'샤.'; + String get noMatchesDataGridFilteringLabel => r'Nici-o potrivire'; @override - String get shortShawwalLabel => r'쇼.'; + String get noSelectedDateCalendarLabel => r'Nicio dată selectată'; @override - String get todayLabel => r'오늘'; + String get notEmptyDataGridFilteringLabel => r'Nu Gol'; @override - String get weeknumberLabel => r'주'; -} + String get notNullDataGridFilteringLabel => r'Nu nul'; -/// The translations for Kirghiz Kyrgyz (`ky`). -class SfLocalizationsKy extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsKy class - const SfLocalizationsKy({ - String localeName = 'ky', - }) : super( - localeName: localeName, - ); + @override + String get nullDataGridFilteringLabel => r'Nul'; @override - String get allDayLabel => r'Күн бою'; + String get numberFiltersDataGridFilteringLabel => r'Filtre de numere'; @override - String get allowedViewDayLabel => r'Күн'; + String get ofDataPagerLabel => r'de'; @override - String get allowedViewMonthLabel => r'Ай'; + String get okDataGridFilteringLabel => r'O.K'; @override - String get allowedViewScheduleLabel => r'График'; + String get orDataGridFilteringLabel => r'Sau'; @override - String get allowedViewTimelineDayLabel => r'Timeline Day'; + String get pagesDataPagerLabel => r'pagini'; @override - String get allowedViewTimelineMonthLabel => r'Убакыт тилкеси айы'; + String get passwordDialogContentLabel => + r'Introduceți parola pentru a deschide acest fișier PDF'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get passwordDialogHeaderTextLabel => r'Protectie cu parola'; @override - String get allowedViewTimelineWorkWeekLabel => r'Иш жумалыгы'; + String get passwordDialogHintTextLabel => r'Introdu parola'; @override - String get allowedViewWeekLabel => r'Апта'; + String get passwordDialogInvalidPasswordLabel => r'Parolă Invalidă'; @override - String get allowedViewWorkWeekLabel => r'Жумуш аптасы'; + String get pdfBookmarksLabel => r'Marcaje'; @override - String get daySpanCountLabel => r'Күн'; + String get pdfEnterPageNumberLabel => r'Introduceți numărul paginii'; @override - String get dhualhiLabel => r'Зул Хижжа'; + String get pdfGoToPageLabel => r'Mergi la pagina'; @override - String get dhualqiLabel => r'Зул кыда'; + String get pdfHyperlinkContentLabel => r'Doriți să deschideți pagina la'; @override - String get jumada1Label => r'Жумада аль-аввал'; + String get pdfHyperlinkDialogCancelLabel => r'ANULARE'; @override - String get jumada2Label => r'Жумада ал-Тани'; + String get pdfHyperlinkDialogOpenLabel => r'DESCHIS'; @override - String get muharramLabel => r'Мухаррам'; + String get pdfHyperlinkLabel => r'Deschideți pagina web'; @override - String get noEventsCalendarLabel => r'Окуялар жок'; + String get pdfInvalidPageNumberLabel => + r'Vă rugăm să introduceți un număr valid'; @override - String get noSelectedDateCalendarLabel => r'Тандалган дата жок'; + String get pdfNoBookmarksLabel => r'Nu s-au găsit marcaje'; @override - String get ofDataPagerLabel => r'нын'; + String get pdfPaginationDialogCancelLabel => r'ANULARE'; @override - String get pagesDataPagerLabel => r'барактар'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get passwordDialogContentLabel => - r'Бул PDF файлын ачуу үчүн сырсөздү киргизиңиз'; + String get pdfPasswordDialogCancelLabel => r'ANULARE'; @override - String get passwordDialogHeaderTextLabel => r'Сырсөз менен корголгон'; + String get pdfPasswordDialogOpenLabel => r'DESCHIS'; @override - String get passwordDialogHintTextLabel => r'Сырсөздү киргизиңиз'; + String get pdfScrollStatusOfLabel => r'de'; @override - String get passwordDialogInvalidPasswordLabel => r'туура эмес пароль'; + String get pdfSignaturePadDialogClearLabel => r'ȘTERGE'; @override - String get pdfBookmarksLabel => r'Кыстармалар'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Desenează-ți semnătura'; @override - String get pdfEnterPageNumberLabel => r'Барактын номерин киргизиңиз'; + String get pdfSignaturePadDialogPenColorLabel => r'Culoare stilou'; @override - String get pdfGoToPageLabel => r'Баракка өтүү'; + String get pdfSignaturePadDialogSaveLabel => r'SALVEAZĂ'; @override - String get pdfInvalidPageNumberLabel => r'Жарактуу номер киргизиңиз'; + String get pdfTextSelectionMenuCopyLabel => r'Copiere'; @override - String get pdfNoBookmarksLabel => r'Кыстармалар табылган жок'; + String get pdfTextSelectionMenuHighlightLabel => r'Evidențiere'; @override - String get pdfPaginationDialogCancelLabel => r'ЖОК КЫЛУУ'; + String get pdfTextSelectionMenuSquigglyLabel => r'Sinuos'; @override - String get pdfPaginationDialogOkLabel => r'макул'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Tăiere'; @override - String get pdfPasswordDialogCancelLabel => r'ЖОК КЫЛУУ'; + String get pdfTextSelectionMenuUnderlineLabel => r'Subliniați'; @override - String get pdfPasswordDialogOpenLabel => r'АЧУУ'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get pdfScrollStatusOfLabel => r'нын'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get rabi1Label => r'Рабиъ аль-аввал'; + String get rajabLabel => r'Rajab'; @override - String get rabi2Label => r'Рабиъ ат-Тани'; + String get ramadanLabel => r'Ramadanul'; @override - String get rajabLabel => r'Ражаб'; + String get rowsPerPageDataPagerLabel => r'Rânduri pe pagină'; @override - String get ramadanLabel => r'Рамазан'; + String get safarLabel => r'Safar'; @override - String get rowsPerPageDataPagerLabel => r'Ар бир беттеги саптар'; + String get searchDataGridFilteringLabel => r'Căutare'; @override - String get safarLabel => r'Сафар'; + String get selectAllDataGridFilteringLabel => r'Selectează tot'; @override - String get series => r'Сериялар'; + String get series => r'Serie'; @override - String get shaabanLabel => r'Шаабан'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get shawwalLabel => r'Шаввал'; + String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Зул-Х'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortDhualqiLabel => r'Зуль-К'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get shortJumada1Label => r'Jum. И'; + String get shortJumada1Label => r'Jum. eu'; @override String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Мух.'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Раби. И'; + String get shortRabi1Label => r'Rabi. eu'; @override - String get shortRabi2Label => r'Раби. II'; + String get shortRabi2Label => r'Rabi. II'; @override - String get shortRajabLabel => r'Радж.'; + String get shortRajabLabel => r'Raj.'; @override - String get shortRamadanLabel => r'RAM.'; + String get shortRamadanLabel => r'Berbec.'; @override String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Ша.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'Шоу.'; + String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Бүгүн'; + String get showRowsWhereDataGridFilteringLabel => r'Afișați rândurile unde'; @override - String get weeknumberLabel => r'Апта'; -} - -/// The translations for Lao (`lo`). -class SfLocalizationsLo extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsLo class - const SfLocalizationsLo({ - String localeName = 'lo', - }) : super( - localeName: localeName, - ); + String get sortAToZDataGridFilteringLabel => r'Sortați de la A la Z'; @override - String get allDayLabel => r'ຫມົດ​ມື້'; + String get sortAndFilterDataGridFilteringLabel => r'Sortați și filtrați'; @override - String get allowedViewDayLabel => r'ມື້'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sortați de la cel mai mare la cel mai mic'; @override - String get allowedViewMonthLabel => r'ເດືອນ'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sortați de la cel mai nou la cel mai vechi'; @override - String get allowedViewScheduleLabel => r'ຕາຕະລາງ'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sortați de la cel mai vechi la cel mai nou'; @override - String get allowedViewTimelineDayLabel => r'ທາມລາຍວັນ'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sortați de la cel mai mic la cel mai mare'; @override - String get allowedViewTimelineMonthLabel => r'ທາມລາຍເດືອນ'; + String get sortZToADataGridFilteringLabel => r'Sortați de la Z la A'; @override - String get allowedViewTimelineWeekLabel => r'ທາມລາຍອາທິດ'; + String get textFiltersDataGridFilteringLabel => r'Filtre de text'; @override - String get allowedViewTimelineWorkWeekLabel => r'ທາມລາຍອາທິດການເຮັດວຽກ'; + String get todayLabel => r'Astăzi'; @override - String get allowedViewWeekLabel => r'ອາທິດ'; + String get weeknumberLabel => r'Săptămână'; +} + +/// The translations for Russian (`ru`). +class SfLocalizationsRu extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsRu class + const SfLocalizationsRu({String localeName = 'ru'}) + : super(localeName: localeName); @override - String get allowedViewWorkWeekLabel => r'ອາທິດເຮັດວຽກ'; + String get afterDataGridFilteringLabel => r'После'; @override - String get daySpanCountLabel => r'ມື້'; + String get afterOrEqualDataGridFilteringLabel => r'После или равно'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get allDayLabel => r'Весь день'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get allowedViewDayLabel => r'День'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get allowedViewMonthLabel => r'Месяц'; @override - String get jumada2Label => r'ຈູມາດາ ອານທານີ'; + String get allowedViewScheduleLabel => r'Расписание'; @override - String get muharramLabel => r'ມູຮາຣາມ'; + String get allowedViewTimelineDayLabel => r'День временной шкалы'; @override - String get noEventsCalendarLabel => r'ບໍ່ມີເຫດການ'; + String get allowedViewTimelineMonthLabel => r'Месяц временной шкалы'; @override - String get noSelectedDateCalendarLabel => r'ບໍ່ມີວັນທີທີ່ເລືອກ'; + String get allowedViewTimelineWeekLabel => r'Рабочая неделя временной шкалы'; @override - String get ofDataPagerLabel => r'ຂອງ'; + String get allowedViewTimelineWorkWeekLabel => r'Рабочая неделя'; @override - String get pagesDataPagerLabel => r'ໜ້າ'; + String get allowedViewWeekLabel => r'Неделя'; @override - String get passwordDialogContentLabel => r'ໃສ່ລະຫັດຜ່ານເພື່ອເປີດໄຟລ໌ PDF ນີ້'; + String get allowedViewWorkWeekLabel => r'Рабочая неделя'; @override - String get passwordDialogHeaderTextLabel => r'ປ້ອງກັນລະຫັດຜ່ານ'; + String get andDataGridFilteringLabel => r'И'; @override - String get passwordDialogHintTextLabel => r'ໃສ່ລະຫັດຜ່ານ'; + String get beforeDataGridFilteringLabel => r'До'; @override - String get passwordDialogInvalidPasswordLabel => r'ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ'; + String get beforeOrEqualDataGridFilteringLabel => r'Перед или равным'; @override - String get pdfBookmarksLabel => r'ບຸກມາກ'; + String get beginsWithDataGridFilteringLabel => r'Начинается с'; @override - String get pdfEnterPageNumberLabel => r'ໃສ່ເລກໜ້າ'; + String get cancelDataGridFilteringLabel => r'Отмена'; @override - String get pdfGoToPageLabel => r'ໄປທີ່ໜ້າ'; + String get clearFilterDataGridFilteringLabel => r'Очистить фильтр'; @override - String get pdfInvalidPageNumberLabel => r'ກະລຸນາໃສ່ຕົວເລກທີ່ຖືກຕ້ອງ'; + String get containsDataGridFilteringLabel => r'Содержит'; @override - String get pdfNoBookmarksLabel => r'ບໍ່ພົບບຸກມາກ'; + String get dateFiltersDataGridFilteringLabel => r'Фильтры даты'; @override - String get pdfPaginationDialogCancelLabel => r'ຍົກເລີກ'; + String get daySpanCountLabel => r'День'; @override - String get pdfPaginationDialogOkLabel => r'ຕົກ​ລົງ'; + String get dhualhiLabel => r'Зу аль-Хиджа'; @override - String get pdfPasswordDialogCancelLabel => r'ຍົກເລີກ'; + String get dhualqiLabel => r'Зу аль-Кида'; @override - String get pdfPasswordDialogOpenLabel => r'ເປີດ'; + String get doesNotBeginWithDataGridFilteringLabel => r'Не начинается с'; @override - String get pdfScrollStatusOfLabel => r'ຂອງ'; + String get doesNotContainDataGridFilteringLabel => r'Не содержит'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get doesNotEndWithDataGridFilteringLabel => r'Не заканчивается с'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get doesNotEqualDataGridFilteringLabel => r'Не равно'; @override - String get rajabLabel => r'Rajab'; + String get emptyDataGridFilteringLabel => r'Пустой'; @override - String get ramadanLabel => r'ຣາມາດານ'; + String get endsWithDataGridFilteringLabel => r'Заканчивается с'; @override - String get rowsPerPageDataPagerLabel => r'ແຖວຕໍ່ຫນ້າ'; + String get equalsDataGridFilteringLabel => r'Равно'; @override - String get safarLabel => r'ຊາຟາ'; + String get fromDataGridFilteringLabel => r'Из'; @override - String get series => r'ຊຸດ'; + String get greaterThanDataGridFilteringLabel => r'Больше чем'; @override - String get shaabanLabel => r'ຊາອາບານ'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Больше или равно'; @override - String get shawwalLabel => r'ຊວາວ'; + String get jumada1Label => r'Джумада аль-авваль'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get jumada2Label => r'Джумада аль-тани'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get lessThanDataGridFilteringLabel => r'Меньше, чем'; @override - String get shortJumada1Label => r'ຈູມ. I'; + String get lessThanOrEqualDataGridFilteringLabel => r'Меньше или равно'; @override - String get shortJumada2Label => r'ຈູມ. II'; + String get muharramLabel => r'Мухаррам'; @override - String get shortMuharramLabel => r'ມ.'; + String get noEventsCalendarLabel => r'Нет событий'; @override - String get shortRabi1Label => r'ຣາບີ. I'; + String get noMatchesDataGridFilteringLabel => r'Нет совпадений'; @override - String get shortRabi2Label => r'ຣາບີ. II'; + String get noSelectedDateCalendarLabel => r'Дата не выбрана'; @override - String get shortRajabLabel => r'ລາດ.'; + String get notEmptyDataGridFilteringLabel => r'Не пустой'; @override - String get shortRamadanLabel => r'ຣາມ.'; + String get notNullDataGridFilteringLabel => r'Ненулевой'; @override - String get shortSafarLabel => r'ປອດໄພ.'; + String get nullDataGridFilteringLabel => r'Нулевой'; @override - String get shortShaabanLabel => r'ຊາ.'; + String get numberFiltersDataGridFilteringLabel => r'Числовые фильтры'; @override - String get shortShawwalLabel => r'ແຊວ.'; + String get ofDataPagerLabel => r'из'; @override - String get todayLabel => r'ມື້​ນີ້'; + String get okDataGridFilteringLabel => r'ХОРОШО'; @override - String get weeknumberLabel => r'ອາທິດ'; -} + String get orDataGridFilteringLabel => r'Или же'; -/// The translations for Lithuanian (`lt`). -class SfLocalizationsLt extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsLt class - const SfLocalizationsLt({ - String localeName = 'lt', - }) : super( - localeName: localeName, - ); + @override + String get pagesDataPagerLabel => r'страницы'; @override - String get allDayLabel => r'Visą dieną'; + String get passwordDialogContentLabel => + r'Введите пароль, чтобы открыть этот файл PDF'; @override - String get allowedViewDayLabel => r'Diena'; + String get passwordDialogHeaderTextLabel => r'Пароль защищен'; @override - String get allowedViewMonthLabel => r'Mėnuo'; + String get passwordDialogHintTextLabel => r'Введите пароль'; @override - String get allowedViewScheduleLabel => r'Tvarkaraštis'; + String get passwordDialogInvalidPasswordLabel => r'Неверный пароль'; @override - String get allowedViewTimelineDayLabel => r'Laiko juostos diena'; + String get pdfBookmarksLabel => r'Закладки'; @override - String get allowedViewTimelineMonthLabel => r'Laiko juostos mėnuo'; + String get pdfEnterPageNumberLabel => r'Введите номер страницы'; @override - String get allowedViewTimelineWeekLabel => r'Laiko juostos savaitė'; + String get pdfGoToPageLabel => r'Перейти на страницу'; @override - String get allowedViewTimelineWorkWeekLabel => r'Laiko juostos darbo savaitė'; + String get pdfHyperlinkContentLabel => r'Вы хотите открыть страницу в'; @override - String get allowedViewWeekLabel => r'Savaitė'; + String get pdfHyperlinkDialogCancelLabel => r'ОТМЕНИТЬ'; @override - String get allowedViewWorkWeekLabel => r'Darbo savaitė'; + String get pdfHyperlinkDialogOpenLabel => r'ОТКРЫТЫМ'; @override - String get daySpanCountLabel => r'Diena'; + String get pdfHyperlinkLabel => r'Открыть веб-страницу'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfInvalidPageNumberLabel => + r'Пожалуйста, введите правильное число'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfNoBookmarksLabel => r'Закладки не найдены'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfPaginationDialogCancelLabel => r'ОТМЕНИТЬ'; @override - String get jumada2Label => r'Jumada al-Thani'; + String get pdfPaginationDialogOkLabel => r'ОК'; @override - String get muharramLabel => r'Muharramas'; + String get pdfPasswordDialogCancelLabel => r'ОТМЕНИТЬ'; @override - String get noEventsCalendarLabel => r'Jokių įvykių'; + String get pdfPasswordDialogOpenLabel => r'ОТКРЫТЬ'; @override - String get noSelectedDateCalendarLabel => r'Nėra pasirinktos datos'; + String get pdfScrollStatusOfLabel => r'из'; @override - String get ofDataPagerLabel => r'apie'; + String get pdfSignaturePadDialogClearLabel => r'ОЧИСТИТЬ'; @override - String get pagesDataPagerLabel => r'puslapių'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Нарисуй свою подпись'; @override - String get passwordDialogContentLabel => - r'Įveskite slaptažodį, kad atidarytumėte šį PDF failą'; + String get pdfSignaturePadDialogPenColorLabel => r'Цвет пера'; @override - String get passwordDialogHeaderTextLabel => r'Apsaugotas slaptažodžiu'; + String get pdfSignaturePadDialogSaveLabel => r'СОХРАНИТЬ'; @override - String get passwordDialogHintTextLabel => r'Įvesti slaptažodį'; + String get pdfTextSelectionMenuCopyLabel => r'Копировать'; @override - String get passwordDialogInvalidPasswordLabel => r'Neteisingas slaptažodis'; + String get pdfTextSelectionMenuHighlightLabel => r'Подсветить'; @override - String get pdfBookmarksLabel => r'Žymės'; + String get pdfTextSelectionMenuSquigglyLabel => r'Зигзаг'; @override - String get pdfEnterPageNumberLabel => r'Įveskite puslapio numerį'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Зачеркивание'; @override - String get pdfGoToPageLabel => r'Eiti į puslapį'; + String get pdfTextSelectionMenuUnderlineLabel => r'Подчеркнуть'; @override - String get pdfInvalidPageNumberLabel => r'Įveskite tinkamą numerį'; + String get rabi1Label => r'Раби аль-авваль'; @override - String get pdfNoBookmarksLabel => r'Žymių nerasta'; + String get rabi2Label => r'Раби аль-Тани'; @override - String get pdfPaginationDialogCancelLabel => r'ATŠAUKTI'; + String get rajabLabel => r'Раджаб'; @override - String get pdfPaginationDialogOkLabel => r'Gerai'; + String get ramadanLabel => r'Рамадан'; @override - String get pdfPasswordDialogCancelLabel => r'ATŠAUKTI'; + String get rowsPerPageDataPagerLabel => r'Строк на странице'; @override - String get pdfPasswordDialogOpenLabel => r'ATVIRAS'; + String get safarLabel => r'Сафар'; @override - String get pdfScrollStatusOfLabel => r'apie'; + String get searchDataGridFilteringLabel => r'Поиск'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get selectAllDataGridFilteringLabel => r'Выбрать все'; @override - String get rabi2Label => r'Rabis al-Thanis'; + String get series => r'Серии'; @override - String get rajabLabel => r'Rajab'; + String get shaabanLabel => r'Шаабан'; @override - String get ramadanLabel => r'Ramadanas'; + String get shawwalLabel => r'Шавваль'; @override - String get rowsPerPageDataPagerLabel => r'Eilučių puslapyje'; + String get shortDhualhiLabel => r'Зуль-Х'; @override - String get safarLabel => r'Safaras'; + String get shortDhualqiLabel => r'Зуль-Кью'; @override - String get series => r'Serija'; + String get shortJumada1Label => r'Джум. я'; @override - String get shaabanLabel => r'Šaabanas'; + String get shortJumada2Label => r'Джум. II'; @override - String get shawwalLabel => r'Shawwal'; + String get shortMuharramLabel => r'Мух.'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortRabi1Label => r'Раби. я'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortRabi2Label => r'Раби. II'; @override - String get shortJumada1Label => r'Jum. aš'; + String get shortRajabLabel => r'Радж.'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortRamadanLabel => r'Баран.'; @override - String get shortMuharramLabel => r'aha.'; + String get shortSafarLabel => r'Саф.'; @override - String get shortRabi1Label => r'Rabi. aš'; + String get shortShaabanLabel => r'Ша.'; @override - String get shortRabi2Label => r'Rabi. II'; + String get shortShawwalLabel => r'Шоу.'; @override - String get shortRajabLabel => r'Raj.'; + String get showRowsWhereDataGridFilteringLabel => r'Показать строки, где'; @override - String get shortRamadanLabel => r'Ram.'; + String get sortAToZDataGridFilteringLabel => r'Сортировать от А до Я'; @override - String get shortSafarLabel => r'Saf.'; + String get sortAndFilterDataGridFilteringLabel => r'Сортировка и фильтрация'; @override - String get shortShaabanLabel => r'Sha.'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Сортировать от большего к меньшему'; @override - String get shortShawwalLabel => r'Shaw.'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Сортировать от новых к старым'; @override - String get todayLabel => r'Šiandien'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Сортировать от старых к новым'; @override - String get weeknumberLabel => r'Savaitė'; -} + String get sortSmallestToLargestDataGridFilteringLabel => + r'Сортировать от меньшего к большему'; -/// The translations for Latvian (`lv`). -class SfLocalizationsLv extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsLv class - const SfLocalizationsLv({ - String localeName = 'lv', - }) : super( - localeName: localeName, - ); + @override + String get sortZToADataGridFilteringLabel => r'Сортировать от Я до А'; @override - String get allDayLabel => r'Visu dienu'; + String get textFiltersDataGridFilteringLabel => r'Текстовые фильтры'; @override - String get allowedViewDayLabel => r'diena'; + String get todayLabel => r'Сегодня'; @override - String get allowedViewMonthLabel => r'Mēnesis'; + String get weeknumberLabel => r'Неделя'; +} + +/// The translations for Sinhala Sinhalese (`si`). +class SfLocalizationsSi extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSi class + const SfLocalizationsSi({String localeName = 'si'}) + : super(localeName: localeName); @override - String get allowedViewScheduleLabel => r'Grafiks'; + String get afterDataGridFilteringLabel => r'පසු'; @override - String get allowedViewTimelineDayLabel => r'Laika skalas diena'; + String get afterOrEqualDataGridFilteringLabel => r'පසු හෝ සමාන'; @override - String get allowedViewTimelineMonthLabel => r'Laika skalas mēnesis'; + String get allDayLabel => r'මුළු දවසම'; @override - String get allowedViewTimelineWeekLabel => r'Laika skalas nedēļa'; + String get allowedViewDayLabel => r'දින'; @override - String get allowedViewTimelineWorkWeekLabel => r'Laika skala Darba nedēļa'; + String get allowedViewMonthLabel => r'මාසික'; @override - String get allowedViewWeekLabel => r'nedēļa'; + String get allowedViewScheduleLabel => r'කාලසටහන'; @override - String get allowedViewWorkWeekLabel => r'Darba nedēļa'; + String get allowedViewTimelineDayLabel => r'කාලරේඛා දිනය'; @override - String get daySpanCountLabel => r'diena'; + String get allowedViewTimelineMonthLabel => r'කාලරේඛා මාසය'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get allowedViewTimelineWeekLabel => r'කාලරේඛා සතිය'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get allowedViewTimelineWorkWeekLabel => r'කාලසටහන වැඩ සතිය'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get allowedViewWeekLabel => r'සතිය'; @override - String get jumada2Label => r'Jumada al-tani'; + String get allowedViewWorkWeekLabel => r'වැඩ සතිය'; @override - String get muharramLabel => r'Muharram'; + String get andDataGridFilteringLabel => r'හා'; @override - String get noEventsCalendarLabel => r'Nav notikumu'; + String get beforeDataGridFilteringLabel => r'කලින්'; @override - String get noSelectedDateCalendarLabel => r'Nav izvēlēts datums'; + String get beforeOrEqualDataGridFilteringLabel => r'පෙර හෝ සමාන'; @override - String get ofDataPagerLabel => r'no'; + String get beginsWithDataGridFilteringLabel => r'සමඟ ආරම්භ වේ'; @override - String get pagesDataPagerLabel => r'lapas'; + String get cancelDataGridFilteringLabel => r'අවලංගු කරන්න'; @override - String get passwordDialogContentLabel => - r'Ievadiet paroli, lai atvērtu šo PDF failu'; + String get clearFilterDataGridFilteringLabel => r'පෙරහන හිස් කරන්න'; @override - String get passwordDialogHeaderTextLabel => r'Aizsargāts ar paroli'; + String get containsDataGridFilteringLabel => r'අඩංගු වේ'; @override - String get passwordDialogHintTextLabel => r'Ievadi paroli'; + String get dateFiltersDataGridFilteringLabel => r'දින පෙරහන්'; @override - String get passwordDialogInvalidPasswordLabel => r'Nepareiza parole'; + String get daySpanCountLabel => r'දින'; @override - String get pdfBookmarksLabel => r'Grāmatzīmes'; + String get dhualhiLabel => r'ඩු අල්-හිජ්ජා'; @override - String get pdfEnterPageNumberLabel => r'Ievadiet lapas numuru'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get pdfGoToPageLabel => r'Iet uz lapu'; + String get doesNotBeginWithDataGridFilteringLabel => r'සමඟ ආරම්භ නොවේ'; @override - String get pdfInvalidPageNumberLabel => r'Lūdzu, ievadiet derīgu numuru'; + String get doesNotContainDataGridFilteringLabel => r'අඩංගු නොවේ'; @override - String get pdfNoBookmarksLabel => r'Netika atrasta neviena grāmatzīme'; + String get doesNotEndWithDataGridFilteringLabel => r'සමඟ අවසන් නොවේ'; @override - String get pdfPaginationDialogCancelLabel => r'ATCELT'; + String get doesNotEqualDataGridFilteringLabel => r'සමාන නොවේ'; @override - String get pdfPaginationDialogOkLabel => r'labi'; + String get emptyDataGridFilteringLabel => r'හිස්'; @override - String get pdfPasswordDialogCancelLabel => r'ATCELT'; + String get endsWithDataGridFilteringLabel => r'සමඟ අවසන් වේ'; @override - String get pdfPasswordDialogOpenLabel => r'ATVĒRTS'; + String get equalsDataGridFilteringLabel => r'සමාන'; @override - String get pdfScrollStatusOfLabel => r'no'; + String get fromDataGridFilteringLabel => r'සිට'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get greaterThanDataGridFilteringLabel => r'වඩා විශාලයි'; @override - String get rabi2Label => r'Rabī al-tani'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'වඩා විශාල හෝ සමාන වේ'; @override - String get rajabLabel => r'Radžabs'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get ramadanLabel => r'Ramadāns'; + String get jumada2Label => r'ජුමාදා අල්-තානි'; @override - String get rowsPerPageDataPagerLabel => r'Rindas vienā lapā'; + String get lessThanDataGridFilteringLabel => r'අඩු'; @override - String get safarLabel => r'Safar'; + String get lessThanOrEqualDataGridFilteringLabel => r'අඩු හෝ සමානයි'; @override - String get series => r'sērija'; + String get muharramLabel => r'මුහර්රම්'; @override - String get shaabanLabel => r'Šaabans'; + String get noEventsCalendarLabel => r'සිදුවීම් නැත'; @override - String get shawwalLabel => r'Šovals'; + String get noMatchesDataGridFilteringLabel => r'ගැලපීම් නැත'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get noSelectedDateCalendarLabel => r'තෝරාගත් දිනයක් නැත'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get notEmptyDataGridFilteringLabel => r'හිස් නොවේ'; @override - String get shortJumada1Label => r'Jum. es'; + String get notNullDataGridFilteringLabel => r'Null නොවේ'; @override - String get shortJumada2Label => r'Jum. II'; + String get nullDataGridFilteringLabel => r'ශුන්‍ය'; @override - String get shortMuharramLabel => r'Muh.'; + String get numberFiltersDataGridFilteringLabel => r'අංක පෙරහන්'; @override - String get shortRabi1Label => r'Rabi. es'; + String get ofDataPagerLabel => r'වල'; @override - String get shortRabi2Label => r'Rabi. II'; + String get okDataGridFilteringLabel => r'හරි'; @override - String get shortRajabLabel => r'Raj.'; + String get orDataGridFilteringLabel => r'හෝ'; @override - String get shortRamadanLabel => r'Ram.'; + String get pagesDataPagerLabel => r'පිටු'; @override - String get shortSafarLabel => r'Saf.'; + String get passwordDialogContentLabel => + r'මෙම PDF ගොනුව විවෘත කිරීමට මුරපදය ඇතුළත් කරන්න'; @override - String get shortShaabanLabel => r'Ša.'; + String get passwordDialogHeaderTextLabel => r'මුරපදය ආරක්ෂිතයි'; @override - String get shortShawwalLabel => r'Šo.'; + String get passwordDialogHintTextLabel => r'මුරපදය ඇතුළත් කරන්න'; @override - String get todayLabel => r'Šodien'; + String get passwordDialogInvalidPasswordLabel => r'වලංගු නොවන මුරපදය'; @override - String get weeknumberLabel => r'nedēļa'; -} + String get pdfBookmarksLabel => r'පිටු සලකුණු'; -/// The translations for Macedonian (`mk`). -class SfLocalizationsMk extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsMk class - const SfLocalizationsMk({ - String localeName = 'mk', - }) : super( - localeName: localeName, - ); + @override + String get pdfEnterPageNumberLabel => r'පිටු අංකය ඇතුලත් කරන්න'; @override - String get allDayLabel => r'Цел ден'; + String get pdfGoToPageLabel => r'පිටුවට යන්න'; @override - String get allowedViewDayLabel => r'Ден'; + String get pdfHyperlinkContentLabel => r'ඔබට පිටුව විවෘත කිරීමට අවශ්‍යද'; @override - String get allowedViewMonthLabel => r'Месец'; + String get pdfHyperlinkDialogCancelLabel => r'අවලංගු කරන්න'; @override - String get allowedViewScheduleLabel => r'Распоред'; + String get pdfHyperlinkDialogOpenLabel => r'විවෘත කරන්න'; @override - String get allowedViewTimelineDayLabel => r'Ден на времеплов'; + String get pdfHyperlinkLabel => r'වෙබ් පිටුව විවෘත කරන්න'; @override - String get allowedViewTimelineMonthLabel => r'Времеплов месец'; + String get pdfInvalidPageNumberLabel => r'කරුණාකර වලංගු අංකයක් ඇතුළත් කරන්න'; @override - String get allowedViewTimelineWeekLabel => r'Недела на времеплов'; + String get pdfNoBookmarksLabel => r'පිටු සලකුණු හමු නොවීය'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Временска рамка Работна недела'; + String get pdfPaginationDialogCancelLabel => r'අවලංගු කරන්න'; @override - String get allowedViewWeekLabel => r'Недела'; + String get pdfPaginationDialogOkLabel => r'හරි'; @override - String get allowedViewWorkWeekLabel => r'Работна недела'; + String get pdfPasswordDialogCancelLabel => r'අවලංගු කරන්න'; @override - String get daySpanCountLabel => r'Ден'; + String get pdfPasswordDialogOpenLabel => r'විවෘත කරන්න'; @override - String get dhualhiLabel => r'Зу ал-Хиџа'; + String get pdfScrollStatusOfLabel => r'වල'; @override - String get dhualqiLabel => r'Зу ал-Кида'; + String get pdfSignaturePadDialogClearLabel => r'මකන්න'; @override - String get jumada1Label => r'Џумада ал-аввал'; + String get pdfSignaturePadDialogHeaderTextLabel => r'ඔබේ අත්සන අඳින්න'; @override - String get jumada2Label => r'Џумада ал-тани'; + String get pdfSignaturePadDialogPenColorLabel => r'පෑන වර්ණය'; @override - String get muharramLabel => r'Мухарам'; + String get pdfSignaturePadDialogSaveLabel => r'සුරකින්න'; @override - String get noEventsCalendarLabel => r'Нема настани'; + String get pdfTextSelectionMenuCopyLabel => r'පිටපත් කරන්න'; @override - String get noSelectedDateCalendarLabel => r'Нема избран датум'; + String get pdfTextSelectionMenuHighlightLabel => r'ඉස්මතු කිරීම්'; @override - String get ofDataPagerLabel => r'на'; + String get pdfTextSelectionMenuSquigglyLabel => r'වක්‍රක'; @override - String get pagesDataPagerLabel => r'страници'; + String get pdfTextSelectionMenuStrikethroughLabel => r'පහර හරහා'; @override - String get passwordDialogContentLabel => - r'Внесете ја лозинката за да ја отворите оваа PDF-датотека'; + String get pdfTextSelectionMenuUnderlineLabel => r'යටිකුරු'; @override - String get passwordDialogHeaderTextLabel => r'Заштитено со лозинка'; + String get rabi1Label => r'රබී අල්-අව්වල්'; @override - String get passwordDialogHintTextLabel => r'Внесете ја лозинката'; + String get rabi2Label => r'රබී අල්-තානි'; @override - String get passwordDialogInvalidPasswordLabel => r'невалидна лозинка'; + String get rajabLabel => r'රාජාබ්'; @override - String get pdfBookmarksLabel => r'Обележувачи'; + String get ramadanLabel => r'රාමසාන්'; @override - String get pdfEnterPageNumberLabel => r'Внесете го бројот на страницата'; + String get rowsPerPageDataPagerLabel => r'පිටුවකට පේළි'; @override - String get pdfGoToPageLabel => r'Оди на страна'; + String get safarLabel => r'සෆාර්'; @override - String get pdfInvalidPageNumberLabel => r'Ве молиме внесете важечки број'; + String get searchDataGridFilteringLabel => r'සොයන්න'; @override - String get pdfNoBookmarksLabel => r'Не се пронајдени обележувачи'; + String get selectAllDataGridFilteringLabel => r'සියල්ල තෝරන්න'; @override - String get pdfPaginationDialogCancelLabel => r'ОТКАЖИ'; + String get series => r'මාලාවක්'; @override - String get pdfPaginationDialogOkLabel => r'добро'; + String get shaabanLabel => r'ෂාබාන්'; @override - String get pdfPasswordDialogCancelLabel => r'ОТКАЖИ'; + String get shawwalLabel => r'ෂව්වාල්'; @override - String get pdfPasswordDialogOpenLabel => r'ОТВОРЕНО'; + String get shortDhualhiLabel => r'දුල්-එච්'; @override - String get pdfScrollStatusOfLabel => r'на'; + String get shortDhualqiLabel => r'දුල්-කිව්'; @override - String get rabi1Label => r'Раби ал-аввал'; + String get shortJumada1Label => r'ජුම්. මම'; @override - String get rabi2Label => r'Раби ал-тани'; + String get shortJumada2Label => r'ජුම්. II'; @override - String get rajabLabel => r'Раџаб'; + String get shortMuharramLabel => r'මුහ්'; @override - String get ramadanLabel => r'Рамазан'; + String get shortRabi1Label => r'රබී. මම'; @override - String get rowsPerPageDataPagerLabel => r'Редови по страница'; + String get shortRabi2Label => r'රබී. II'; @override - String get safarLabel => r'Сафар'; + String get shortRajabLabel => r'රාජ්.'; @override - String get series => r'Серии'; + String get shortRamadanLabel => r'RAM.'; @override - String get shaabanLabel => r'Шаабан'; + String get shortSafarLabel => r'Saf.'; @override - String get shawwalLabel => r'Шавал'; + String get shortShaabanLabel => r'ශා.'; @override - String get shortDhualhiLabel => r'Дул-Х'; + String get shortShawwalLabel => r'ෂෝ.'; @override - String get shortDhualqiLabel => r'Дул-К'; + String get showRowsWhereDataGridFilteringLabel => r'කොහෙද පේළි පෙන්වන්න'; @override - String get shortJumada1Label => r'Џум. Јас'; + String get sortAToZDataGridFilteringLabel => r'A සිට Z දක්වා වර්ග කරන්න'; @override - String get shortJumada2Label => r'Џум. II'; + String get sortAndFilterDataGridFilteringLabel => r'වර්ග කිරීම සහ පෙරීම'; @override - String get shortMuharramLabel => r'Мух.'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'විශාලතම සිට කුඩාම දක්වා වර්ග කරන්න'; @override - String get shortRabi1Label => r'Раби. Јас'; + String get sortNewestToOldestDataGridFilteringLabel => + r'අලුත්ම සිට පැරණිම දක්වා වර්ග කරන්න'; @override - String get shortRabi2Label => r'Раби. II'; + String get sortOldestToNewestDataGridFilteringLabel => + r'පැරණිතම සිට අලුත්ම දක්වා වර්ග කරන්න'; @override - String get shortRajabLabel => r'Раџ.'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'කුඩාම සිට විශාල දක්වා වර්ග කරන්න'; @override - String get shortRamadanLabel => r'Рам.'; + String get sortZToADataGridFilteringLabel => r'Z සිට A දක්වා වර්ග කරන්න'; @override - String get shortSafarLabel => r'Саф.'; + String get textFiltersDataGridFilteringLabel => r'පෙළ පෙරහන්'; @override - String get shortShaabanLabel => r'Ша.'; + String get todayLabel => r'අද'; @override - String get shortShawwalLabel => r'Шо.'; + String get weeknumberLabel => r'සතිය'; +} + +/// The translations for Slovak (`sk`). +class SfLocalizationsSk extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSk class + const SfLocalizationsSk({String localeName = 'sk'}) + : super(localeName: localeName); @override - String get todayLabel => r'Денеска'; + String get afterDataGridFilteringLabel => r'Po'; @override - String get weeknumberLabel => r'Недела'; -} + String get afterOrEqualDataGridFilteringLabel => r'Po alebo Equal'; -/// The translations for Malayalam (`ml`). -class SfLocalizationsMl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsMl class - const SfLocalizationsMl({ - String localeName = 'ml', - }) : super( - localeName: localeName, - ); + @override + String get allDayLabel => r'Celý deň'; @override - String get allDayLabel => r'ദിവസം മുഴുവൻ'; + String get allowedViewDayLabel => r'Deň'; @override - String get allowedViewDayLabel => r'ദിവസം'; + String get allowedViewMonthLabel => r'Mesiac'; @override - String get allowedViewMonthLabel => r'മാസം'; + String get allowedViewScheduleLabel => r'Rozvrh'; @override - String get allowedViewScheduleLabel => r'പട്ടിക'; + String get allowedViewTimelineDayLabel => r'Deň časovej osi'; @override - String get allowedViewTimelineDayLabel => r'ടൈംലൈൻ ദിവസം'; + String get allowedViewTimelineMonthLabel => r'Mesiac časovej osi'; @override - String get allowedViewTimelineMonthLabel => r'ടൈംലൈൻ മാസം'; + String get allowedViewTimelineWeekLabel => r'Časová os týždeň'; @override - String get allowedViewTimelineWeekLabel => r'ടൈംലൈൻ ആഴ്ച'; + String get allowedViewTimelineWorkWeekLabel => r'Časová os Pracovný týždeň'; @override - String get allowedViewTimelineWorkWeekLabel => r'ടൈംലൈൻ പ്രവൃത്തി ആഴ്ച'; + String get allowedViewWeekLabel => r'Týždeň'; @override - String get allowedViewWeekLabel => r'ആഴ്ച'; + String get allowedViewWorkWeekLabel => r'Pracovný týždeň'; @override - String get allowedViewWorkWeekLabel => r'പ്രവൃത്തി ആഴ്ച'; + String get andDataGridFilteringLabel => r'A'; @override - String get daySpanCountLabel => r'ദിവസം'; + String get beforeDataGridFilteringLabel => r'Predtým'; @override - String get dhualhiLabel => r'ദു അൽ-ഹിജ്ജ'; + String get beforeOrEqualDataGridFilteringLabel => r'Pred alebo Equal'; @override - String get dhualqiLabel => r'ദു അൽ-ക്വിദ'; + String get beginsWithDataGridFilteringLabel => r'Zacina s'; @override - String get jumada1Label => r'ജുമാദ അൽ അവ്വൽ'; + String get cancelDataGridFilteringLabel => r'Zrušiť'; @override - String get jumada2Label => r'ജുമാദ അൽതാനി'; + String get clearFilterDataGridFilteringLabel => r'Vymazať filter'; @override - String get muharramLabel => r'മുഹറം'; + String get containsDataGridFilteringLabel => r'Obsahuje'; @override - String get noEventsCalendarLabel => r'ഇവന്റുകളൊന്നുമില്ല'; + String get dateFiltersDataGridFilteringLabel => r'Dátumové filtre'; @override - String get noSelectedDateCalendarLabel => r'തിരഞ്ഞെടുത്ത തീയതിയില്ല'; + String get daySpanCountLabel => r'deň'; @override - String get ofDataPagerLabel => r'യുടെ'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get pagesDataPagerLabel => r'പേജുകൾ'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get passwordDialogContentLabel => - r'ഈ PDF ഫയൽ തുറക്കാൻ പാസ്‌വേഡ് നൽകുക'; + String get doesNotBeginWithDataGridFilteringLabel => r'Nezačína s'; @override - String get passwordDialogHeaderTextLabel => r'പാസ്‌വേഡ് പരിരക്ഷിതം'; + String get doesNotContainDataGridFilteringLabel => r'Neobsahuje'; @override - String get passwordDialogHintTextLabel => r'പാസ്വേഡ് നല്കൂ'; + String get doesNotEndWithDataGridFilteringLabel => r'Nekončí s'; @override - String get passwordDialogInvalidPasswordLabel => r'അസാധുവായ പാസ്‌വേഡ്'; + String get doesNotEqualDataGridFilteringLabel => r'Nerovná sa'; @override - String get pdfBookmarksLabel => r'ബുക്ക്മാർക്കുകൾ'; + String get emptyDataGridFilteringLabel => r'Prázdny'; @override - String get pdfEnterPageNumberLabel => r'പേജ് നമ്പർ നൽകുക'; + String get endsWithDataGridFilteringLabel => r'Končí s'; @override - String get pdfGoToPageLabel => r'പേജിലേക്ക് പോകുക'; + String get equalsDataGridFilteringLabel => r'Rovná sa'; @override - String get pdfInvalidPageNumberLabel => r'ദയവായി ഒരു സാധുവായ നമ്പർ നൽകുക'; + String get fromDataGridFilteringLabel => r'Od'; @override - String get pdfNoBookmarksLabel => r'ബുക്ക്‌മാർക്കുകളൊന്നും കണ്ടെത്തിയില്ല'; + String get greaterThanDataGridFilteringLabel => r'Väčší než'; @override - String get pdfPaginationDialogCancelLabel => r'റദ്ദാക്കുക'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Väčšie alebo rovné'; @override - String get pdfPaginationDialogOkLabel => r'ശരി'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get pdfPasswordDialogCancelLabel => r'റദ്ദാക്കുക'; + String get jumada2Label => r'Jumada al-thani'; @override - String get pdfPasswordDialogOpenLabel => r'തുറക്കുക'; + String get lessThanDataGridFilteringLabel => r'Menej ako'; @override - String get pdfScrollStatusOfLabel => r'യുടെ'; + String get lessThanOrEqualDataGridFilteringLabel => r'Menej než alebo rovné'; @override - String get rabi1Label => r'റബീഉൽ അവ്വൽ'; + String get muharramLabel => r'Muharram'; @override - String get rabi2Label => r'റാബി അൽതാനി'; + String get noEventsCalendarLabel => r'Žiadne udalosti'; @override - String get rajabLabel => r'റജബ്'; + String get noMatchesDataGridFilteringLabel => r'Žiadne zhody'; @override - String get ramadanLabel => r'റമദാൻ'; + String get noSelectedDateCalendarLabel => r'Žiadny vybratý dátum'; @override - String get rowsPerPageDataPagerLabel => r'ഓരോ പേജിലും വരികൾ'; + String get notEmptyDataGridFilteringLabel => r'Nie prázdny'; @override - String get safarLabel => r'സഫർ'; + String get notNullDataGridFilteringLabel => r'Nie Null'; @override - String get series => r'പരമ്പര'; + String get nullDataGridFilteringLabel => r'Nulový'; @override - String get shaabanLabel => r'ശഅബാൻ'; + String get numberFiltersDataGridFilteringLabel => r'Filtre čísel'; @override - String get shawwalLabel => r'ശവ്വാൽ'; + String get ofDataPagerLabel => r'z'; @override - String get shortDhualhiLabel => r'ദുൽ-എച്ച്'; + String get okDataGridFilteringLabel => r'OK'; @override - String get shortDhualqiLabel => r'ദുൽ-ക്യു'; + String get orDataGridFilteringLabel => r'Alebo'; @override - String get shortJumada1Label => r'ജം. ഐ'; + String get pagesDataPagerLabel => r'stránky'; @override - String get shortJumada2Label => r'ജം. II'; + String get passwordDialogContentLabel => + r'Zadajte heslo na otvorenie tohto súboru PDF'; @override - String get shortMuharramLabel => r'Muh.'; + String get passwordDialogHeaderTextLabel => r'Chránené heslom'; @override - String get shortRabi1Label => r'റാബി. ഐ'; + String get passwordDialogHintTextLabel => r'Zadajte heslo'; @override - String get shortRabi2Label => r'റാബി. II'; + String get passwordDialogInvalidPasswordLabel => r'nesprávne heslo'; @override - String get shortRajabLabel => r'രാജ്.'; + String get pdfBookmarksLabel => r'Záložky'; @override - String get shortRamadanLabel => r'RAM.'; + String get pdfEnterPageNumberLabel => r'Zadajte číslo strany'; @override - String get shortSafarLabel => r'സാഫ്.'; + String get pdfGoToPageLabel => r'Chod na stranu'; @override - String get shortShaabanLabel => r'ഷാ.'; + String get pdfHyperlinkContentLabel => r'Chcete otvoriť stránku na'; @override - String get shortShawwalLabel => r'ഷാ.'; + String get pdfHyperlinkDialogCancelLabel => r'ZRUŠIŤ'; @override - String get todayLabel => r'ഇന്ന്'; + String get pdfHyperlinkDialogOpenLabel => r'OTVORENÉ'; @override - String get weeknumberLabel => r'ആഴ്ച'; -} + String get pdfHyperlinkLabel => r'Otvorte webovú stránku'; -/// The translations for Mongolian (`mn`). -class SfLocalizationsMn extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsMn class - const SfLocalizationsMn({ - String localeName = 'mn', - }) : super( - localeName: localeName, - ); + @override + String get pdfInvalidPageNumberLabel => + r'Prosím, zadajte platné číslo strany'; @override - String get allDayLabel => r'Бүх өдөр'; + String get pdfNoBookmarksLabel => r'Nenašli sa žiadne záložky'; @override - String get allowedViewDayLabel => r'Өдөр'; + String get pdfPaginationDialogCancelLabel => r'ZRUŠIŤ'; @override - String get allowedViewMonthLabel => r'Сар'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get allowedViewScheduleLabel => r'Хуваарь'; + String get pdfPasswordDialogCancelLabel => r'ZRUŠIŤ'; @override - String get allowedViewTimelineDayLabel => r'Хугацааны өдөр'; + String get pdfPasswordDialogOpenLabel => r'OTVORENÉ'; @override - String get allowedViewTimelineMonthLabel => r'Он цагийн хэлхээсийн сар'; + String get pdfScrollStatusOfLabel => r'z'; @override - String get allowedViewTimelineWeekLabel => r'Хугацааны долоо хоног'; + String get pdfSignaturePadDialogClearLabel => r'Vymazať'; @override - String get allowedViewTimelineWorkWeekLabel => r'Ажлын долоо хоног'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Nakreslite svoj podpis'; @override - String get allowedViewWeekLabel => r'Долоо хоног'; + String get pdfSignaturePadDialogPenColorLabel => r'Farba pera'; @override - String get allowedViewWorkWeekLabel => r'Ажлын долоо хоног'; + String get pdfSignaturePadDialogSaveLabel => r'ULOŽIŤ'; @override - String get daySpanCountLabel => r'Өдөр'; + String get pdfTextSelectionMenuCopyLabel => r'Kopírovať'; @override - String get dhualhiLabel => r'Зу аль-Хижа'; + String get pdfTextSelectionMenuHighlightLabel => r'Zvýrazniť'; @override - String get dhualqiLabel => r'Зу аль-Кида'; + String get pdfTextSelectionMenuSquigglyLabel => r'Squiggly'; @override - String get jumada1Label => r'Жумада аль-аввал'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Prečiarknuté'; @override - String get jumada2Label => r'Жумада аль-Тани'; + String get pdfTextSelectionMenuUnderlineLabel => r'Zdôrazniť'; @override - String get muharramLabel => r'Мухаррам'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get noEventsCalendarLabel => r'Үйл явдал алга'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get noSelectedDateCalendarLabel => r'Сонгосон огноо алга'; + String get rajabLabel => r'Rajab'; @override - String get ofDataPagerLabel => r'-ийн'; + String get ramadanLabel => r'Ramadán'; @override - String get pagesDataPagerLabel => r'хуудаснууд'; + String get rowsPerPageDataPagerLabel => r'Počet riadkov na stránku'; @override - String get passwordDialogContentLabel => - r'Энэ PDF файлыг нээхийн тулд нууц үгээ оруулна уу'; + String get safarLabel => r'Šafár'; @override - String get passwordDialogHeaderTextLabel => r'Нууц үгээр хамгаалагдсан'; + String get searchDataGridFilteringLabel => r'Vyhľadávanie'; @override - String get passwordDialogHintTextLabel => r'Нууц үгээ оруулна'; + String get selectAllDataGridFilteringLabel => r'Vybrať všetko'; @override - String get passwordDialogInvalidPasswordLabel => r'Нууц үг буруу байна'; + String get series => r'séria'; @override - String get pdfBookmarksLabel => r'Хавчуурга'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get pdfEnterPageNumberLabel => r'Хуудасны дугаарыг оруулна уу'; + String get shawwalLabel => r'Shawwal'; @override - String get pdfGoToPageLabel => r'Хуудас руу оч'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get pdfInvalidPageNumberLabel => r'Хүчинтэй дугаар оруулна уу'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get pdfNoBookmarksLabel => r'Хавчуурга олдсонгүй'; + String get shortJumada1Label => r'Jum. ja'; @override - String get pdfPaginationDialogCancelLabel => r'Цуцлах'; + String get shortJumada2Label => r'Jum. II'; @override - String get pdfPaginationDialogOkLabel => r'БОЛЖ БАЙНА УУ'; + String get shortMuharramLabel => r'Muh.'; @override - String get pdfPasswordDialogCancelLabel => r'Цуцлах'; + String get shortRabi1Label => r'Rabi. ja'; @override - String get pdfPasswordDialogOpenLabel => r'НЭЭЛТТЭЙ'; + String get shortRabi2Label => r'Rabi. II'; @override - String get pdfScrollStatusOfLabel => r'-ийн'; + String get shortRajabLabel => r'Raj.'; @override - String get rabi1Label => r'Раби' "'" r' аль-аввал'; + String get shortRamadanLabel => r'Ram.'; @override - String get rabi2Label => r'Раби' "'" r' аль-Тани'; + String get shortSafarLabel => r'Saf.'; @override - String get rajabLabel => r'Ражаб'; + String get shortShaabanLabel => r'Sha.'; @override - String get ramadanLabel => r'Рамадан'; + String get shortShawwalLabel => r'Shaw.'; @override - String get rowsPerPageDataPagerLabel => r'Хуудас бүрийн мөр'; + String get showRowsWhereDataGridFilteringLabel => r'Zobraziť riadky kde'; @override - String get safarLabel => r'Сафар'; + String get sortAToZDataGridFilteringLabel => r'Zoradiť od A po Z'; @override - String get series => r'Цуврал'; + String get sortAndFilterDataGridFilteringLabel => r'Triediť a filtrovať'; @override - String get shaabanLabel => r'Шаабан'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Zoradiť od najväčšieho po najmenšie'; @override - String get shawwalLabel => r'Шаввал'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Zoradiť od najnovších po najstaršie'; @override - String get shortDhualhiLabel => r'Зуль-Х'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Zoradiť od najstarších po najnovšie'; @override - String get shortDhualqiLabel => r'Зуль-К'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Zoradiť od najmenšieho po najväčšie'; @override - String get shortJumada1Label => r'Jum. I'; + String get sortZToADataGridFilteringLabel => r'Zoradiť od Z po A'; @override - String get shortJumada2Label => r'Jum. II'; + String get textFiltersDataGridFilteringLabel => r'Textové filtre'; @override - String get shortMuharramLabel => r'Мух.'; + String get todayLabel => r'Dnes'; @override - String get shortRabi1Label => r'Раби. I'; + String get weeknumberLabel => r'Týždeň'; +} - @override - String get shortRabi2Label => r'Раби. II'; +/// The translations for Slovenian (`sl`). +class SfLocalizationsSl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSl class + const SfLocalizationsSl({String localeName = 'sl'}) + : super(localeName: localeName); @override - String get shortRajabLabel => r'Раж.'; + String get afterDataGridFilteringLabel => r'Po'; @override - String get shortRamadanLabel => r'Рам.'; + String get afterOrEqualDataGridFilteringLabel => r'Po ali enako'; @override - String get shortSafarLabel => r'Saf.'; + String get allDayLabel => r'Ves dan'; @override - String get shortShaabanLabel => r'Ша.'; + String get allowedViewDayLabel => r'Dan'; @override - String get shortShawwalLabel => r'Шоу.'; + String get allowedViewMonthLabel => r'Mesec'; @override - String get todayLabel => r'Өнөөдөр'; + String get allowedViewScheduleLabel => r'Urnik'; @override - String get weeknumberLabel => r'Долоо хоног'; -} + String get allowedViewTimelineDayLabel => r'Dan časovnice'; -/// The translations for Marathi (`mr`). -class SfLocalizationsMr extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsMr class - const SfLocalizationsMr({ - String localeName = 'mr', - }) : super( - localeName: localeName, - ); + @override + String get allowedViewTimelineMonthLabel => r'Mesec časovnice'; @override - String get allDayLabel => r'संपूर्ण दिवस'; + String get allowedViewTimelineWeekLabel => r'Teden časovnice'; @override - String get allowedViewDayLabel => r'दिवस'; + String get allowedViewTimelineWorkWeekLabel => r'Časovnica Delovni teden'; @override - String get allowedViewMonthLabel => r'महिना'; + String get allowedViewWeekLabel => r'Teden'; @override - String get allowedViewScheduleLabel => r'वेळापत्रक'; + String get allowedViewWorkWeekLabel => r'Delovni teden'; @override - String get allowedViewTimelineDayLabel => r'टाइमलाइन दिवस'; + String get andDataGridFilteringLabel => r'in'; @override - String get allowedViewTimelineMonthLabel => r'टाइमलाइन महिना'; + String get beforeDataGridFilteringLabel => r'prej'; @override - String get allowedViewTimelineWeekLabel => r'टाइमलाइन आठवडा'; + String get beforeOrEqualDataGridFilteringLabel => r'Pred ali enako'; @override - String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कामाचा आठवडा'; + String get beginsWithDataGridFilteringLabel => r'Začne se z'; @override - String get allowedViewWeekLabel => r'आठवडा'; + String get cancelDataGridFilteringLabel => r'Prekliči'; @override - String get allowedViewWorkWeekLabel => r'कामाचा आठवडा'; + String get clearFilterDataGridFilteringLabel => r'Počisti filter'; @override - String get daySpanCountLabel => r'दिवस'; + String get containsDataGridFilteringLabel => r'Vsebuje'; @override - String get dhualhiLabel => r'धु अल-हिज्जा'; + String get dateFiltersDataGridFilteringLabel => r'Datumski filtri'; @override - String get dhualqiLabel => r'धु अल-किदाह'; + String get daySpanCountLabel => r'Dan'; @override - String get jumada1Label => r'जुमदा अल-अव्वल'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get jumada2Label => r'जुमादा अल-थानी'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get muharramLabel => r'मोहरम'; + String get doesNotBeginWithDataGridFilteringLabel => r'Se ne začne z'; @override - String get noEventsCalendarLabel => r'कार्यक्रम नाहीत'; + String get doesNotContainDataGridFilteringLabel => r'Ne vsebuje'; @override - String get noSelectedDateCalendarLabel => r'निवडलेली तारीख नाही'; + String get doesNotEndWithDataGridFilteringLabel => r'Se ne konča z'; @override - String get ofDataPagerLabel => r'च्या'; + String get doesNotEqualDataGridFilteringLabel => r'Ni enako'; @override - String get pagesDataPagerLabel => r'पृष्ठे'; + String get emptyDataGridFilteringLabel => r'Prazno'; @override - String get passwordDialogContentLabel => - r'ही PDF फाईल उघडण्यासाठी पासवर्ड टाका'; + String get endsWithDataGridFilteringLabel => r'Konča se z'; @override - String get passwordDialogHeaderTextLabel => r'पासवर्ड संरक्षित'; + String get equalsDataGridFilteringLabel => r'Enako'; @override - String get passwordDialogHintTextLabel => r'पासवर्ड टाका'; + String get fromDataGridFilteringLabel => r'Od'; @override - String get passwordDialogInvalidPasswordLabel => r'अवैध पासवर्ड'; + String get greaterThanDataGridFilteringLabel => r'Večji kot'; @override - String get pdfBookmarksLabel => r'बुकमार्क'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Večje ali enako'; @override - String get pdfEnterPageNumberLabel => r'पृष्ठ क्रमांक प्रविष्ट करा'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get pdfGoToPageLabel => r'पृष्ठावर जा'; + String get jumada2Label => r'Jumada al-thani'; @override - String get pdfInvalidPageNumberLabel => r'कृपया वैध क्रमांक प्रविष्ट करा'; + String get lessThanDataGridFilteringLabel => r'Manj kot'; @override - String get pdfNoBookmarksLabel => r'कोणतेही बुकमार्क आढळले नाहीत'; + String get lessThanOrEqualDataGridFilteringLabel => r'Manj ali enako'; @override - String get pdfPaginationDialogCancelLabel => r'रद्द करा'; + String get muharramLabel => r'Muharram'; @override - String get pdfPaginationDialogOkLabel => r'ठीक आहे'; + String get noEventsCalendarLabel => r'Ni dogodkov'; @override - String get pdfPasswordDialogCancelLabel => r'रद्द करा'; + String get noMatchesDataGridFilteringLabel => r'Ni ujemanja'; @override - String get pdfPasswordDialogOpenLabel => r'उघडा'; + String get noSelectedDateCalendarLabel => r'Ni izbranega datuma'; @override - String get pdfScrollStatusOfLabel => r'च्या'; + String get notEmptyDataGridFilteringLabel => r'Ni prazno'; @override - String get rabi1Label => r'रबी अल अव्वल'; + String get notNullDataGridFilteringLabel => r'Ni Null'; @override - String get rabi2Label => r'रबी अल-थानी'; + String get nullDataGridFilteringLabel => r'Nič'; @override - String get rajabLabel => r'रजब'; + String get numberFiltersDataGridFilteringLabel => r'Številčni filtri'; @override - String get ramadanLabel => r'रमजान'; + String get ofDataPagerLabel => r'od'; @override - String get rowsPerPageDataPagerLabel => r'प्रति पृष्ठ पंक्ती'; + String get okDataGridFilteringLabel => r'v redu'; @override - String get safarLabel => r'सफर'; + String get orDataGridFilteringLabel => r'oz'; @override - String get series => r'मालिका'; + String get pagesDataPagerLabel => r'strani'; @override - String get shaabanLabel => r'शाबान'; + String get passwordDialogContentLabel => + r'Vnesite geslo, da odprete to datoteko PDF'; @override - String get shawwalLabel => r'शव्वाल'; + String get passwordDialogHeaderTextLabel => r'Zaščiteno z geslom'; @override - String get shortDhualhiLabel => r'धुल-एच'; + String get passwordDialogHintTextLabel => r'Vnesite geslo'; @override - String get shortDhualqiLabel => r'धूल-प्र'; + String get passwordDialogInvalidPasswordLabel => r'Neveljavno geslo'; @override - String get shortJumada1Label => r'जम. आय'; + String get pdfBookmarksLabel => r'Zaznamki'; @override - String get shortJumada2Label => r'जम. II'; + String get pdfEnterPageNumberLabel => r'Vnesite številko strani'; @override - String get shortMuharramLabel => r'मुह.'; + String get pdfGoToPageLabel => r'Pojdi na stran'; @override - String get shortRabi1Label => r'रबी. आय'; + String get pdfHyperlinkContentLabel => r'Ali želite odpreti stran na'; @override - String get shortRabi2Label => r'रबी. II'; + String get pdfHyperlinkDialogCancelLabel => r'PREKLIC'; @override - String get shortRajabLabel => r'राज.'; + String get pdfHyperlinkDialogOpenLabel => r'ODPRTO'; @override - String get shortRamadanLabel => r'रॅम.'; + String get pdfHyperlinkLabel => r'Odpri spletno stran'; @override - String get shortSafarLabel => r'सफ.'; + String get pdfInvalidPageNumberLabel => r'Vnesite veljavno številko'; @override - String get shortShaabanLabel => r'शा.'; + String get pdfNoBookmarksLabel => r'Ni najdenih zaznamkov'; @override - String get shortShawwalLabel => r'शॉ.'; + String get pdfPaginationDialogCancelLabel => r'PREKLIC'; @override - String get todayLabel => r'आज'; + String get pdfPaginationDialogOkLabel => r'v redu'; @override - String get weeknumberLabel => r'आठवडा'; -} + String get pdfPasswordDialogCancelLabel => r'PREKLIC'; -/// The translations for Malay (`ms`). -class SfLocalizationsMs extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsMs class - const SfLocalizationsMs({ - String localeName = 'ms', - }) : super( - localeName: localeName, - ); + @override + String get pdfPasswordDialogOpenLabel => r'ODPRTO'; @override - String get allDayLabel => r'Sepanjang hari'; + String get pdfScrollStatusOfLabel => r'od'; @override - String get allowedViewDayLabel => r'Hari'; + String get pdfSignaturePadDialogClearLabel => r'Počisti'; @override - String get allowedViewMonthLabel => r'bulan'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Nariši svoj podpis'; @override - String get allowedViewScheduleLabel => r'Jadual'; + String get pdfSignaturePadDialogPenColorLabel => r'Barva peresa'; @override - String get allowedViewTimelineDayLabel => r'Hari Garis Masa'; + String get pdfSignaturePadDialogSaveLabel => r'SHRANI'; @override - String get allowedViewTimelineMonthLabel => r'Bulan Garis Masa'; + String get pdfTextSelectionMenuCopyLabel => r'Kopirati'; @override - String get allowedViewTimelineWeekLabel => r'Minggu Garis Masa'; + String get pdfTextSelectionMenuHighlightLabel => r'Zaznamuj'; @override - String get allowedViewTimelineWorkWeekLabel => r'Minggu Kerja Garis Masa'; + String get pdfTextSelectionMenuSquigglyLabel => r'Vijugasto'; @override - String get allowedViewWeekLabel => r'Minggu'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Prečrtano'; @override - String get allowedViewWorkWeekLabel => r'Minggu Kerja'; + String get pdfTextSelectionMenuUnderlineLabel => r'Podčrtaj'; @override - String get daySpanCountLabel => r'Hari'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get dhualhiLabel => r'Dzulhijjah'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get rajabLabel => r'Rajab'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get ramadanLabel => r'Ramadan'; @override - String get jumada2Label => r'Jumada al-thani'; + String get rowsPerPageDataPagerLabel => r'Vrstic na stran'; @override - String get muharramLabel => r'Muharram'; + String get safarLabel => r'Safar'; @override - String get noEventsCalendarLabel => r'Tiada acara'; + String get searchDataGridFilteringLabel => r'Iskanje'; @override - String get noSelectedDateCalendarLabel => r'Tiada tarikh yang dipilih'; + String get selectAllDataGridFilteringLabel => r'Izberi vse'; @override - String get ofDataPagerLabel => r'daripada'; + String get series => r'serija'; @override - String get pagesDataPagerLabel => r'muka surat'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get passwordDialogContentLabel => - r'Masukkan kata laluan untuk membuka fail PDF ini'; + String get shawwalLabel => r'Shawwal'; @override - String get passwordDialogHeaderTextLabel => r'Kata laluan dilindungi'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get passwordDialogHintTextLabel => r'Masukkan kata laluan'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get passwordDialogInvalidPasswordLabel => r'kata laluan tidak sah'; + String get shortJumada1Label => r'Jum. jaz'; @override - String get pdfBookmarksLabel => r'Penanda buku'; + String get shortJumada2Label => r'Jum. II'; @override - String get pdfEnterPageNumberLabel => r'Masukkan nombor halaman'; + String get shortMuharramLabel => r'Muh.'; @override - String get pdfGoToPageLabel => r'Pergi ke halaman'; + String get shortRabi1Label => r'Rabi. jaz'; @override - String get pdfInvalidPageNumberLabel => r'sila masukkan nombor yang sah'; + String get shortRabi2Label => r'Rabi. II'; @override - String get pdfNoBookmarksLabel => r'Tiada penanda halaman ditemui'; + String get shortRajabLabel => r'Raj.'; @override - String get pdfPaginationDialogCancelLabel => r'BATALKAN'; + String get shortRamadanLabel => r'Oven.'; @override - String get pdfPaginationDialogOkLabel => r'okey'; + String get shortSafarLabel => r'Saf.'; @override - String get pdfPasswordDialogCancelLabel => r'BATALKAN'; + String get shortShaabanLabel => r'Sha.'; @override - String get pdfPasswordDialogOpenLabel => r'BUKA'; + String get shortShawwalLabel => r'Shaw.'; @override - String get pdfScrollStatusOfLabel => r'daripada'; + String get showRowsWhereDataGridFilteringLabel => r'Pokaži vrstice, kje'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get sortAToZDataGridFilteringLabel => r'Razvrsti od A do Ž'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get sortAndFilterDataGridFilteringLabel => r'Razvrsti in filtriraj'; @override - String get rajabLabel => r'Rajab'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Razvrsti od največjega do najmanjšega'; @override - String get ramadanLabel => r'Ramadhan'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Razvrsti od najnovejšega do najstarejšega'; @override - String get rowsPerPageDataPagerLabel => r'Baris setiap halaman'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Razvrsti od starejšega do najnovejšega'; @override - String get safarLabel => r'Safar'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Razvrsti od najmanjšega do največjega'; @override - String get series => r'Siri'; + String get sortZToADataGridFilteringLabel => r'Razvrsti od Ž do A'; @override - String get shaabanLabel => r'Sya' "'" r'aban'; + String get textFiltersDataGridFilteringLabel => r'Besedilni filtri'; @override - String get shawwalLabel => r'Syawal'; + String get todayLabel => r'Danes'; @override - String get shortDhualhiLabel => r'Dzul-H'; + String get weeknumberLabel => r'Teden'; +} + +/// The translations for Albanian (`sq`). +class SfLocalizationsSq extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSq class + const SfLocalizationsSq({String localeName = 'sq'}) + : super(localeName: localeName); @override - String get shortDhualqiLabel => r'Dzul-Q'; + String get afterDataGridFilteringLabel => r'Pas'; @override - String get shortJumada1Label => r'Jum. saya'; + String get afterOrEqualDataGridFilteringLabel => r'Pas Ose E barabartë'; @override - String get shortJumada2Label => r'Jum. II'; + String get allDayLabel => r'Gjithe diten'; @override - String get shortMuharramLabel => r'Muh.'; + String get allowedViewDayLabel => r'Dita'; @override - String get shortRabi1Label => r'Rabi. saya'; + String get allowedViewMonthLabel => r'Muaj'; @override - String get shortRabi2Label => r'Rabi. II'; + String get allowedViewScheduleLabel => r'Orari'; @override - String get shortRajabLabel => r'Raj.'; + String get allowedViewTimelineDayLabel => r'Dita e afatit kohor'; @override - String get shortRamadanLabel => r'Ram.'; + String get allowedViewTimelineMonthLabel => r'Afati kohor Muaji'; @override - String get shortSafarLabel => r'Saf.'; + String get allowedViewTimelineWeekLabel => r'Java e afatit kohor'; @override - String get shortShaabanLabel => r'Sha.'; + String get allowedViewTimelineWorkWeekLabel => r'Afati kohor Java e punës'; @override - String get shortShawwalLabel => r'Shaw.'; + String get allowedViewWeekLabel => r'javë'; @override - String get todayLabel => r'Hari ini'; + String get allowedViewWorkWeekLabel => r'Java e punës'; @override - String get weeknumberLabel => r'Minggu'; -} + String get andDataGridFilteringLabel => r'Dhe'; -/// The translations for Burmese (`my`). -class SfLocalizationsMy extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsMy class - const SfLocalizationsMy({ - String localeName = 'my', - }) : super( - localeName: localeName, - ); + @override + String get beforeDataGridFilteringLabel => r'Përpara'; @override - String get allDayLabel => r'တနေကုန်'; + String get beforeOrEqualDataGridFilteringLabel => r'Para Ose të Barabarta'; @override - String get allowedViewDayLabel => r'နေ့'; + String get beginsWithDataGridFilteringLabel => r'Fillon me'; @override - String get allowedViewMonthLabel => r'လ'; + String get cancelDataGridFilteringLabel => r'Anulo'; @override - String get allowedViewScheduleLabel => r'အချိန်ဇယား'; + String get clearFilterDataGridFilteringLabel => r'Pastro filtrin'; @override - String get allowedViewTimelineDayLabel => r'Timeline နေ့'; + String get containsDataGridFilteringLabel => r'Përmban'; @override - String get allowedViewTimelineMonthLabel => r'အချိန်စာရင်းလ'; + String get dateFiltersDataGridFilteringLabel => r'Filtrat e datës'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get daySpanCountLabel => r'Dita'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline အလုပ်ရက်သတ္တပတ်'; + String get dhualhiLabel => r'Dhul Hixhe'; @override - String get allowedViewWeekLabel => r'အေးလေ'; + String get dhualqiLabel => r'Dhul-Kida'; @override - String get allowedViewWorkWeekLabel => r'အလုပ်ရက်သတ္တပတ်'; + String get doesNotBeginWithDataGridFilteringLabel => r'Nuk Fillon Me'; @override - String get daySpanCountLabel => r'နေ့'; + String get doesNotContainDataGridFilteringLabel => r'Nuk permban'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get doesNotEndWithDataGridFilteringLabel => r'Nuk përfundon me'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get doesNotEqualDataGridFilteringLabel => r'Nuk është e barabartë'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get emptyDataGridFilteringLabel => r'Bosh'; @override - String get jumada2Label => r'Jumada al-thani'; + String get endsWithDataGridFilteringLabel => r'Përfundon me'; @override - String get muharramLabel => r'Muharram'; + String get equalsDataGridFilteringLabel => r'Të barabartë'; @override - String get noEventsCalendarLabel => r'ပွဲများမရှိပါ။'; + String get fromDataGridFilteringLabel => r'Nga'; @override - String get noSelectedDateCalendarLabel => r'ရွေးထားသည့်ရက်စွဲမရှိပါ။'; + String get greaterThanDataGridFilteringLabel => r'Më e madhe se'; @override - String get ofDataPagerLabel => r'၏'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Më i madh se Ose i barabartë'; @override - String get pagesDataPagerLabel => r'စာမျက်နှာများ'; + String get jumada1Label => r'Xhumade el-evvel'; @override - String get passwordDialogContentLabel => - r'ဤ PDF ဖိုင်ကိုဖွင့်ရန် စကားဝှက်ကို ထည့်ပါ။'; + String get jumada2Label => r'Xhumada al-thani'; @override - String get passwordDialogHeaderTextLabel => r'စကားဝှက်ကို ကာကွယ်ထားသည်။'; + String get lessThanDataGridFilteringLabel => r'Më pak se'; @override - String get passwordDialogHintTextLabel => r'စကားဝှက်ထည့်ပါ။'; + String get lessThanOrEqualDataGridFilteringLabel => + r'Më pak se ose e barabartë'; @override - String get passwordDialogInvalidPasswordLabel => r'မမှန်ကန်သော စကားဝှက်'; + String get muharramLabel => r'Muharrem'; @override - String get pdfBookmarksLabel => r'ညှပ်'; + String get noEventsCalendarLabel => r'Asnjë ngjarje'; @override - String get pdfEnterPageNumberLabel => r'စာမျက်နှာနံပါတ်ထည့်ပါ။'; + String get noMatchesDataGridFilteringLabel => r'Nuk ka ndeshje'; @override - String get pdfGoToPageLabel => r'စာမျက်နှာသို့သွားပါ'; + String get noSelectedDateCalendarLabel => r'Asnjë datë e zgjedhur'; @override - String get pdfInvalidPageNumberLabel => - r'ကျေးဇူးပြု၍ တရားဝင်နံပါတ်တစ်ခုထည့်ပါ။'; + String get notEmptyDataGridFilteringLabel => r'Jo bosh'; @override - String get pdfNoBookmarksLabel => r'စာညှပ်များမတွေ့ပါ။'; + String get notNullDataGridFilteringLabel => r'Jo Null'; @override - String get pdfPaginationDialogCancelLabel => r'မလုပ်တော့ပါ။'; + String get nullDataGridFilteringLabel => r'I pavlefshëm'; @override - String get pdfPaginationDialogOkLabel => r'အိုကေတယ်နော်'; + String get numberFiltersDataGridFilteringLabel => r'Filtrat e numrave'; @override - String get pdfPasswordDialogCancelLabel => r'မလုပ်တော့ပါ။'; + String get ofDataPagerLabel => r'e'; @override - String get pdfPasswordDialogOpenLabel => r'ဖွင့်ပါ။'; + String get okDataGridFilteringLabel => r'Ne rregull'; @override - String get pdfScrollStatusOfLabel => r'၏'; + String get orDataGridFilteringLabel => r'Ose'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get pagesDataPagerLabel => r'faqet'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get passwordDialogContentLabel => + r'Futni fjalëkalimin për të hapur këtë skedar PDF'; @override - String get rajabLabel => r'ရာဂျပ်'; + String get passwordDialogHeaderTextLabel => r'Fjalëkalimi i mbrojtur'; @override - String get ramadanLabel => r'ရမ်ဇာန်'; + String get passwordDialogHintTextLabel => r'Shkruani fjalëkalimin'; @override - String get rowsPerPageDataPagerLabel => r'စာမျက်နှာအလိုက် အတန်းများ'; + String get passwordDialogInvalidPasswordLabel => r'Fjalëkalim i pavlefshëm'; @override - String get safarLabel => r'ဆာဖာရာ'; + String get pdfBookmarksLabel => r'Faqerojtësit'; @override - String get series => r'စီးရီး'; + String get pdfEnterPageNumberLabel => r'Fut numrin e faqes'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get pdfGoToPageLabel => r'Shkoni në faqe'; @override - String get shawwalLabel => r'Shawwal'; + String get pdfHyperlinkContentLabel => r'Dëshironi të hapni faqen në'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get pdfHyperlinkDialogCancelLabel => r'ANULON'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get pdfHyperlinkDialogOpenLabel => r'HAPUR'; @override - String get shortJumada1Label => r'Jum ငါ'; + String get pdfHyperlinkLabel => r'Hapni faqen e internetit'; @override - String get shortJumada2Label => r'Jum II'; + String get pdfInvalidPageNumberLabel => + r'Ju lutemi shkruani një numër faqeje të vlefshëm'; @override - String get shortMuharramLabel => r'Muh'; + String get pdfNoBookmarksLabel => r'Nuk u gjetën faqeshënues'; @override - String get shortRabi1Label => r'ရာဘီ။ ငါ'; + String get pdfPaginationDialogCancelLabel => r'ANULON'; @override - String get shortRabi2Label => r'ရာဘီ။ II'; + String get pdfPaginationDialogOkLabel => r'Në rregull'; @override - String get shortRajabLabel => r'Raj'; + String get pdfPasswordDialogCancelLabel => r'ANULON'; @override - String get shortRamadanLabel => r'ရမ်။'; + String get pdfPasswordDialogOpenLabel => r'HAP'; @override - String get shortSafarLabel => r'အန္တရာယ်ကင်း။'; + String get pdfScrollStatusOfLabel => r'e'; @override - String get shortShaabanLabel => r'Sha.'; + String get pdfSignaturePadDialogClearLabel => r'QARTË'; @override - String get shortShawwalLabel => r'ရှော။'; + String get pdfSignaturePadDialogHeaderTextLabel => + r'Vizatoni nënshkrimin tuaj'; @override - String get todayLabel => r'ဒီနေ့'; + String get pdfSignaturePadDialogPenColorLabel => r'Ngjyra e stilolapsit'; @override - String get weeknumberLabel => r'အေးလေ'; -} - -/// The translations for Norwegian Bokmål (`nb`). -class SfLocalizationsNb extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsNb class - const SfLocalizationsNb({ - String localeName = 'nb', - }) : super( - localeName: localeName, - ); + String get pdfSignaturePadDialogSaveLabel => r'RUAJ'; @override - String get allDayLabel => r'Hele dagen'; + String get pdfTextSelectionMenuCopyLabel => r'Kopjo'; @override - String get allowedViewDayLabel => r'Dag'; + String get pdfTextSelectionMenuHighlightLabel => r'Theksoj'; @override - String get allowedViewMonthLabel => r'Måned'; + String get pdfTextSelectionMenuSquigglyLabel => r'Zigzag'; @override - String get allowedViewScheduleLabel => r'Rute'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Strikethrough'; @override - String get allowedViewTimelineDayLabel => r'Tidslinjedag'; + String get pdfTextSelectionMenuUnderlineLabel => r'Nënvizoj'; @override - String get allowedViewTimelineMonthLabel => r'Tidslinjemåned'; + String get rabi1Label => + r'Rabi' + "'" + r'ul-evvel'; @override - String get allowedViewTimelineWeekLabel => r'Tidslinje uke'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbeidsuke'; + String get rajabLabel => r'Rexheb'; @override - String get allowedViewWeekLabel => r'Uke'; + String get ramadanLabel => r'Ramazani'; @override - String get allowedViewWorkWeekLabel => r'Arbeidsuke'; + String get rowsPerPageDataPagerLabel => r'Rreshtat për faqe'; @override - String get daySpanCountLabel => r'Dag'; + String get safarLabel => r'Safar'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get searchDataGridFilteringLabel => r'Kërko'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get selectAllDataGridFilteringLabel => r'Selektoj të gjitha'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get series => r'Seria'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shaabanLabel => r'Shaban'; @override - String get muharramLabel => r'Muharram'; + String get shawwalLabel => r'Shevali'; @override - String get noEventsCalendarLabel => r'Ingen hendelser'; + String get shortDhualhiLabel => r'Dhul-H'; @override - String get noSelectedDateCalendarLabel => r'Ingen valgt dato'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-K'; @override - String get ofDataPagerLabel => r'av'; + String get shortJumada1Label => r'Jum. I'; @override - String get pagesDataPagerLabel => r'sider'; + String get shortJumada2Label => r'Jum. II'; @override - String get passwordDialogContentLabel => - r'Skriv inn passordet for å åpne denne PDF-filen'; + String get shortMuharramLabel => r'Muh.'; @override - String get passwordDialogHeaderTextLabel => r'Passordbeskyttet'; + String get shortRabi1Label => r'Rabi. I'; @override - String get passwordDialogHintTextLabel => r'Oppgi passord'; + String get shortRabi2Label => r'Rabi. II'; @override - String get passwordDialogInvalidPasswordLabel => r'Ugyldig passord'; + String get shortRajabLabel => r'Raj.'; @override - String get pdfBookmarksLabel => r'Bokmerker'; + String get shortRamadanLabel => r'Ram.'; @override - String get pdfEnterPageNumberLabel => r'Skriv inn sidenummer'; + String get shortSafarLabel => r'Saf.'; @override - String get pdfGoToPageLabel => r'Gå til side'; + String get shortShaabanLabel => r'Sha.'; @override - String get pdfInvalidPageNumberLabel => r'Vennligst oppgi et gyldig nummer'; + String get shortShawwalLabel => r'Shaw.'; @override - String get pdfNoBookmarksLabel => r'Fant ingen bokmerker'; + String get showRowsWhereDataGridFilteringLabel => r'Trego rreshtat ku'; @override - String get pdfPaginationDialogCancelLabel => r'AVBRYT'; + String get sortAToZDataGridFilteringLabel => r'Rendit nga A në Z'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get sortAndFilterDataGridFilteringLabel => r'Rendit dhe filtro'; @override - String get pdfPasswordDialogCancelLabel => r'AVBRYT'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Rendit nga më i madhi tek më i vogli'; @override - String get pdfPasswordDialogOpenLabel => r'ÅPEN'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Rendit nga më i riu tek më i vjetri'; @override - String get pdfScrollStatusOfLabel => r'av'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Rendit nga më i vjetri tek më i riu'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Rendit nga më i vogli në më i madhi'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get sortZToADataGridFilteringLabel => r'Rendit Z në A'; @override - String get rajabLabel => r'Rajab'; + String get textFiltersDataGridFilteringLabel => r'Filtrat e tekstit'; @override - String get ramadanLabel => r'Ramadan'; + String get todayLabel => r'Sot'; @override - String get rowsPerPageDataPagerLabel => r'Rader per side'; + String get weeknumberLabel => r'javë'; +} - @override - String get safarLabel => r'Safar'; +/// The translations for Serbian (`sr`). +class SfLocalizationsSr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSr class + const SfLocalizationsSr({String localeName = 'sr'}) + : super(localeName: localeName); @override - String get series => r'Serie'; + String get afterDataGridFilteringLabel => r'После'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get afterOrEqualDataGridFilteringLabel => r'После или једнако'; @override - String get shawwalLabel => r'Shawwal'; + String get allDayLabel => r'Цео дан'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get allowedViewDayLabel => r'Дан'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get allowedViewMonthLabel => r'Месец'; @override - String get shortJumada1Label => r'Jum. Jeg'; + String get allowedViewScheduleLabel => r'Распоред'; @override - String get shortJumada2Label => r'Jum. II'; + String get allowedViewTimelineDayLabel => r'Дан временске линије'; @override - String get shortMuharramLabel => r'Muh.'; + String get allowedViewTimelineMonthLabel => r'Месец временске линије'; @override - String get shortRabi1Label => r'Rabi. Jeg'; + String get allowedViewTimelineWeekLabel => r'Тимелине Веек'; @override - String get shortRabi2Label => r'Rabi. II'; + String get allowedViewTimelineWorkWeekLabel => + r'Временски оквир Радна недеља'; @override - String get shortRajabLabel => r'Raj.'; + String get allowedViewWeekLabel => r'Недеља'; @override - String get shortRamadanLabel => r'RAM.'; + String get allowedViewWorkWeekLabel => r'Радна недеља'; @override - String get shortSafarLabel => r'Saf.'; + String get andDataGridFilteringLabel => r'И'; @override - String get shortShaabanLabel => r'Sha.'; + String get beforeDataGridFilteringLabel => r'пре него што'; @override - String get shortShawwalLabel => r'Shaw.'; + String get beforeOrEqualDataGridFilteringLabel => r'Пре или једнако'; @override - String get todayLabel => r'I dag'; + String get beginsWithDataGridFilteringLabel => r'Почиње са'; @override - String get weeknumberLabel => r'Uke'; -} + String get cancelDataGridFilteringLabel => r'Поништити, отказати'; -/// The translations for Nepali (`ne`). -class SfLocalizationsNe extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsNe class - const SfLocalizationsNe({ - String localeName = 'ne', - }) : super( - localeName: localeName, - ); + @override + String get clearFilterDataGridFilteringLabel => r'Обриши филтер'; @override - String get allDayLabel => r'दिनभरि'; + String get containsDataGridFilteringLabel => r'Садржи'; @override - String get allowedViewDayLabel => r'दिन'; + String get dateFiltersDataGridFilteringLabel => r'Дате Филтерс'; @override - String get allowedViewMonthLabel => r'महिना'; + String get daySpanCountLabel => r'Дан'; @override - String get allowedViewScheduleLabel => r'तालिका'; + String get dhualhiLabel => r'Дху ал-Хијјах'; @override - String get allowedViewTimelineDayLabel => r'टाइमलाइन दिन'; + String get dhualqiLabel => + r'Дху ал-Ки' + "'" + r'дах'; @override - String get allowedViewTimelineMonthLabel => r'टाइमलाइन महिना'; + String get doesNotBeginWithDataGridFilteringLabel => r'Не почиње са'; @override - String get allowedViewTimelineWeekLabel => r'टाइमलाइन हप्ता'; + String get doesNotContainDataGridFilteringLabel => r'Не садржи'; @override - String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य हप्ता'; + String get doesNotEndWithDataGridFilteringLabel => r'Не завршава са'; @override - String get allowedViewWeekLabel => r'हप्ता'; + String get doesNotEqualDataGridFilteringLabel => r'Није једнако'; @override - String get allowedViewWorkWeekLabel => r'कार्य हप्ता'; + String get emptyDataGridFilteringLabel => r'Празан'; @override - String get daySpanCountLabel => r'दिन'; + String get endsWithDataGridFilteringLabel => r'Завршава са'; @override - String get dhualhiLabel => r'धु उल हिज्जा'; + String get equalsDataGridFilteringLabel => r'Једнако'; @override - String get dhualqiLabel => r'धु अल-किदाह'; + String get fromDataGridFilteringLabel => r'Од'; @override - String get jumada1Label => r'जुमाद अल अव्वल'; + String get greaterThanDataGridFilteringLabel => r'Веће од'; @override - String get jumada2Label => r'जुमादा अल थानी'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Веће или једнако'; @override - String get muharramLabel => r'मुहर्रम'; + String get jumada1Label => r'Јумада ал-аввал'; @override - String get noEventsCalendarLabel => r'कुनै घटनाहरू छैनन्'; + String get jumada2Label => r'Јумада ал-тхани'; @override - String get noSelectedDateCalendarLabel => r'कुनै चयन गरिएको मिति छैन'; + String get lessThanDataGridFilteringLabel => r'Мање од'; @override - String get ofDataPagerLabel => r'को'; + String get lessThanOrEqualDataGridFilteringLabel => r'Мање од или једнако'; @override - String get pagesDataPagerLabel => r'पृष्ठहरू'; + String get muharramLabel => r'Мухаррам'; @override - String get passwordDialogContentLabel => - r'यो PDF फाइल खोल्न पासवर्ड प्रविष्ट गर्नुहोस्'; + String get noEventsCalendarLabel => r'Нема догађаја'; @override - String get passwordDialogHeaderTextLabel => r'पासवर्ड सुरक्षित'; + String get noMatchesDataGridFilteringLabel => r'Нема резултата'; @override - String get passwordDialogHintTextLabel => r'पासवर्ड प्रविष्ट गर्नुहोस्'; + String get noSelectedDateCalendarLabel => r'Нема изабраног датума'; @override - String get passwordDialogInvalidPasswordLabel => r'अवैध पासवर्ड'; + String get notEmptyDataGridFilteringLabel => r'Није празна'; @override - String get pdfBookmarksLabel => r'बुकमार्कहरू'; + String get notNullDataGridFilteringLabel => r'Не Нулл'; @override - String get pdfEnterPageNumberLabel => r'पृष्ठ नम्बर प्रविष्ट गर्नुहोस्'; + String get nullDataGridFilteringLabel => r'Нула'; @override - String get pdfGoToPageLabel => r'पृष्ठमा जानुहोस्'; + String get numberFiltersDataGridFilteringLabel => r'Бројчани филтери'; @override - String get pdfInvalidPageNumberLabel => - r'कृपया मान्य नम्बर प्रविष्ट गर्नुहोस्'; + String get ofDataPagerLabel => r'оф'; @override - String get pdfNoBookmarksLabel => r'कुनै बुकमार्क फेला परेन'; + String get okDataGridFilteringLabel => r'У реду'; @override - String get pdfPaginationDialogCancelLabel => r'रद्द गर्नुहोस्'; + String get orDataGridFilteringLabel => r'Ор'; @override - String get pdfPaginationDialogOkLabel => r'ठिक छ'; + String get pagesDataPagerLabel => r'странице'; @override - String get pdfPasswordDialogCancelLabel => r'रद्द गर्नुहोस्'; + String get passwordDialogContentLabel => + r'Унесите лозинку да бисте отворили ову ПДФ датотеку'; @override - String get pdfPasswordDialogOpenLabel => r'खोल्नुहोस्'; + String get passwordDialogHeaderTextLabel => r'Заштићено лозинком'; @override - String get pdfScrollStatusOfLabel => r'को'; + String get passwordDialogHintTextLabel => r'Унесите лозинку'; @override - String get rabi1Label => r'रबि अल अव्वल'; + String get passwordDialogInvalidPasswordLabel => r'Неважећа лозинка'; @override - String get rabi2Label => r'रबी अल थानी'; + String get pdfBookmarksLabel => r'Обележивачи'; @override - String get rajabLabel => r'रजब'; + String get pdfEnterPageNumberLabel => r'Унесите број странице'; @override - String get ramadanLabel => r'रमजान'; + String get pdfGoToPageLabel => r'Иди на страну'; @override - String get rowsPerPageDataPagerLabel => r'प्रति पृष्ठ पङ्क्तिहरू'; + String get pdfHyperlinkContentLabel => + r'Да ли желите да отворите страницу на'; @override - String get safarLabel => r'सफार'; + String get pdfHyperlinkDialogCancelLabel => r'ОТКАЖИ'; @override - String get series => r'शृङ्खला'; + String get pdfHyperlinkDialogOpenLabel => r'ОТВОРИ'; @override - String get shaabanLabel => r'शाबान'; + String get pdfHyperlinkLabel => r'Отворите веб страницу'; @override - String get shawwalLabel => r'सवल'; + String get pdfInvalidPageNumberLabel => r'Молимо Вас да унесете важећи број'; @override - String get shortDhualhiLabel => r'धुल-एच'; + String get pdfNoBookmarksLabel => r'Нема пронађених обележивача'; @override - String get shortDhualqiLabel => r'धुल-क'; + String get pdfPaginationDialogCancelLabel => r'ОТКАЖИ'; @override - String get shortJumada1Label => r'जुम। म'; + String get pdfPaginationDialogOkLabel => r'У реду'; @override - String get shortJumada2Label => r'जुम। II'; + String get pdfPasswordDialogCancelLabel => r'ОТКАЖИ'; @override - String get shortMuharramLabel => r'मुह।'; + String get pdfPasswordDialogOpenLabel => r'ОТВОРИ'; @override - String get shortRabi1Label => r'रबि। म'; + String get pdfScrollStatusOfLabel => r'од'; @override - String get shortRabi2Label => r'रबि। II'; + String get pdfSignaturePadDialogClearLabel => r'ЈАСНО'; @override - String get shortRajabLabel => r'राज।'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Нацртајте свој потпис'; @override - String get shortRamadanLabel => r'राम।'; + String get pdfSignaturePadDialogPenColorLabel => r'Боја пера'; @override - String get shortSafarLabel => r'सफ।'; + String get pdfSignaturePadDialogSaveLabel => r'САЧУВАТИ'; @override - String get shortShaabanLabel => r'शा।'; + String get pdfTextSelectionMenuCopyLabel => r'Копирај'; @override - String get shortShawwalLabel => r'शा।'; + String get pdfTextSelectionMenuHighlightLabel => r'Истакните'; @override - String get todayLabel => r'आज'; + String get pdfTextSelectionMenuSquigglyLabel => r'Скуиггли'; @override - String get weeknumberLabel => r'हप्ता'; -} + String get pdfTextSelectionMenuStrikethroughLabel => r'Прецртано'; -/// The translations for Dutch Flemish (`nl`). -class SfLocalizationsNl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsNl class - const SfLocalizationsNl({ - String localeName = 'nl', - }) : super( - localeName: localeName, - ); + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Подвући'; @override - String get allDayLabel => r'De hele dag'; + String get rabi1Label => + r'Раби' + "'" + r' ал-аввал'; @override - String get allowedViewDayLabel => r'Dag'; + String get rabi2Label => + r'Раби' + "'" + r' ал-тхани'; @override - String get allowedViewMonthLabel => r'Maand'; + String get rajabLabel => r'Раџаб'; @override - String get allowedViewScheduleLabel => r'Schema'; + String get ramadanLabel => r'Рамазана'; @override - String get allowedViewTimelineDayLabel => r'Tijdlijn Dag'; + String get rowsPerPageDataPagerLabel => r'Редова по страници'; @override - String get allowedViewTimelineMonthLabel => r'Tijdlijn maand'; + String get safarLabel => r'Сафар'; @override - String get allowedViewTimelineWeekLabel => r'Tijdlijnweek'; + String get searchDataGridFilteringLabel => r'Претрага'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tijdlijn Werkweek'; + String get selectAllDataGridFilteringLabel => r'Изабери све'; @override - String get allowedViewWeekLabel => r'Week'; + String get series => r'Сериес'; @override - String get allowedViewWorkWeekLabel => r'Werkweek'; + String get shaabanLabel => + r'Сха' + "'" + r'абан'; @override - String get daySpanCountLabel => r'Dag'; + String get shawwalLabel => r'Схаввал'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get shortDhualhiLabel => + r'Дху' + "'" + r'л-Х'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get shortDhualqiLabel => + r'Дху' + "'" + r'л-К'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortJumada1Label => r'Јум. И'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shortJumada2Label => r'Јум. ИИ'; @override - String get muharramLabel => r'Muharram'; + String get shortMuharramLabel => r'Мух.'; @override - String get noEventsCalendarLabel => r'Geen evenementen'; + String get shortRabi1Label => r'Раби. И'; @override - String get noSelectedDateCalendarLabel => r'Geen geselecteerde datum'; + String get shortRabi2Label => r'Раби. ИИ'; @override - String get ofDataPagerLabel => r'van'; + String get shortRajabLabel => r'Рај.'; @override - String get pagesDataPagerLabel => r'Pagina' "'" r's'; + String get shortRamadanLabel => r'РАМ.'; @override - String get passwordDialogContentLabel => - r'Voer het wachtwoord in om dit PDF-bestand te openen'; + String get shortSafarLabel => r'Саф.'; @override - String get passwordDialogHeaderTextLabel => r'Beschermd met een wachtwoord'; + String get shortShaabanLabel => r'Сха.'; @override - String get passwordDialogHintTextLabel => r'Voer wachtwoord in'; + String get shortShawwalLabel => r'Схав.'; @override - String get passwordDialogInvalidPasswordLabel => r'ongeldig wachtwoord'; + String get showRowsWhereDataGridFilteringLabel => r'Покажи редове где'; @override - String get pdfBookmarksLabel => r'Bladwijzers'; + String get sortAToZDataGridFilteringLabel => r'Сортирај од А до З'; @override - String get pdfEnterPageNumberLabel => r'Voer paginanummer in'; + String get sortAndFilterDataGridFilteringLabel => r'Сортирај и филтрирај'; @override - String get pdfGoToPageLabel => r'Ga naar pagina'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Сортирај од највећег до најмањег'; @override - String get pdfInvalidPageNumberLabel => r'Gelieve een geldig nummer invoeren'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Сортирај од најновијег до најстаријег'; @override - String get pdfNoBookmarksLabel => r'Geen bladwijzers gevonden'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Сортирај од најстаријих до најновијих'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULEREN'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Сортирај од најмањег до највећег'; @override - String get pdfPaginationDialogOkLabel => r'Oke'; + String get sortZToADataGridFilteringLabel => r'Сортирај од З до А'; @override - String get pdfPasswordDialogCancelLabel => r'ANNULEREN'; + String get textFiltersDataGridFilteringLabel => r'Филтери за текст'; @override - String get pdfPasswordDialogOpenLabel => r'OPEN'; + String get todayLabel => r'Данас'; @override - String get pdfScrollStatusOfLabel => r'van'; + String get weeknumberLabel => r'Недеља'; +} - @override - String get rabi1Label => r'Rabi' "'" r' al-awwali'; +/// The translations for Serbian, using the Cyrillic script (`sr_Cyrl`). +class SfLocalizationsSrCyrl extends SfLocalizationsSr { + /// Creating an argument constructor of SfLocalizationsSrCyrl class + const SfLocalizationsSrCyrl({String localeName = 'sr_Cyrl'}) + : super(localeName: localeName); +} - @override - String get rabi2Label => r'Rabi' "'" r' al Thani'; +/// The translations for Swedish (`sv`). +class SfLocalizationsSv extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSv class + const SfLocalizationsSv({String localeName = 'sv'}) + : super(localeName: localeName); @override - String get rajabLabel => r'Rajab'; + String get afterDataGridFilteringLabel => r'Efter'; @override - String get ramadanLabel => r'Ramadan'; + String get afterOrEqualDataGridFilteringLabel => r'Efter Eller Lika'; @override - String get rowsPerPageDataPagerLabel => r'Rijen per pagina'; + String get allDayLabel => r'Hela dagen'; @override - String get safarLabel => r'Safari'; + String get allowedViewDayLabel => r'Dag'; @override - String get series => r'Serie'; + String get allowedViewMonthLabel => r'Månad'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get allowedViewScheduleLabel => r'Schema'; @override - String get shawwalLabel => r'Shawwal'; + String get allowedViewTimelineDayLabel => r'Tidslinjedagen'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get allowedViewTimelineMonthLabel => r'Tidslinje månad'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get allowedViewTimelineWeekLabel => r'Tidslinjevecka'; @override - String get shortJumada1Label => r'jum. l'; + String get allowedViewTimelineWorkWeekLabel => r'Tidslinje arbetsvecka'; @override - String get shortJumada2Label => r'jum. II'; + String get allowedViewWeekLabel => r'Vecka'; @override - String get shortMuharramLabel => r'Muh.'; + String get allowedViewWorkWeekLabel => r'Arbetsvecka'; @override - String get shortRabi1Label => r'Rabi. l'; + String get andDataGridFilteringLabel => r'Och'; @override - String get shortRabi2Label => r'Rabi. II'; + String get beforeDataGridFilteringLabel => r'Innan'; @override - String get shortRajabLabel => r'Raj.'; + String get beforeOrEqualDataGridFilteringLabel => r'Före Eller Lika'; @override - String get shortRamadanLabel => r'RAM.'; + String get beginsWithDataGridFilteringLabel => r'Börjar med'; @override - String get shortSafarLabel => r'saf.'; + String get cancelDataGridFilteringLabel => r'Avbryt'; @override - String get shortShaabanLabel => r'scha.'; + String get clearFilterDataGridFilteringLabel => r'Rensa filtret'; @override - String get shortShawwalLabel => r'Sjaa.'; + String get containsDataGridFilteringLabel => r'Innehåller'; @override - String get todayLabel => r'Vandaag'; + String get dateFiltersDataGridFilteringLabel => r'Datumfilter'; @override - String get weeknumberLabel => r'Week'; -} - -/// The translations for Panjabi Punjabi (`pa`). -class SfLocalizationsPa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsPa class - const SfLocalizationsPa({ - String localeName = 'pa', - }) : super( - localeName: localeName, - ); + String get daySpanCountLabel => r'Dag'; @override - String get allDayLabel => r'ਸਾਰਾ ਦਿਨ'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get allowedViewDayLabel => r'ਦਿਨ'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get allowedViewMonthLabel => r'ਮਹੀਨਾ'; + String get doesNotBeginWithDataGridFilteringLabel => r'Börjar inte med'; @override - String get allowedViewScheduleLabel => r'ਸਮਾਸੂਚੀ, ਕਾਰਜ - ਕ੍ਰਮ'; + String get doesNotContainDataGridFilteringLabel => r'Innehåller inte'; @override - String get allowedViewTimelineDayLabel => r'ਸਮਾਂਰੇਖਾ ਦਿਨ'; + String get doesNotEndWithDataGridFilteringLabel => r'Slutar inte med'; @override - String get allowedViewTimelineMonthLabel => r'ਸਮਾਂਰੇਖਾ ਮਹੀਨਾ'; + String get doesNotEqualDataGridFilteringLabel => r'Är inte lika med'; @override - String get allowedViewTimelineWeekLabel => r'ਸਮਾਂਰੇਖਾ ਹਫ਼ਤਾ'; + String get emptyDataGridFilteringLabel => r'Tömma'; @override - String get allowedViewTimelineWorkWeekLabel => r'ਸਮਾਂਰੇਖਾ ਕਾਰਜ ਹਫ਼ਤਾ'; + String get endsWithDataGridFilteringLabel => r'Slutar med'; @override - String get allowedViewWeekLabel => r'ਹਫ਼ਤਾ'; + String get equalsDataGridFilteringLabel => r'Lika'; @override - String get allowedViewWorkWeekLabel => r'ਕੰਮ ਦਾ ਹਫ਼ਤਾ'; + String get fromDataGridFilteringLabel => r'Från'; @override - String get daySpanCountLabel => r'ਦਿਨ'; + String get greaterThanDataGridFilteringLabel => r'Större än'; @override - String get dhualhiLabel => r'ਧੂ ਅਲ-ਹਿੱਜਾ'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Större än eller lika med'; @override - String get dhualqiLabel => r'ਧੂ ਅਲ-ਕਾਇਦਾ'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get jumada1Label => r'ਜੁਮਾਦਾ ਅਲ-ਅੱਵਲ'; + String get jumada2Label => r'Jumada al-thani'; @override - String get jumada2Label => r'ਜੁਮਾਦਾ ਅਲ-ਥਾਨੀ'; + String get lessThanDataGridFilteringLabel => r'Mindre än'; @override - String get muharramLabel => r'ਮੁਹੱਰਮ'; + String get lessThanOrEqualDataGridFilteringLabel => r'Mindre än eller lika'; @override - String get noEventsCalendarLabel => r'ਕੋਈ ਸਮਾਗਮ ਨਹੀਂ'; + String get muharramLabel => r'Muharram'; @override - String get noSelectedDateCalendarLabel => r'ਕੋਈ ਚੁਣੀ ਤਾਰੀਖ ਨਹੀਂ ਹੈ'; + String get noEventsCalendarLabel => r'Inga händelser'; @override - String get ofDataPagerLabel => r'ਦੇ'; + String get noMatchesDataGridFilteringLabel => r'Inga träffar'; @override - String get pagesDataPagerLabel => r'ਪੰਨੇ'; + String get noSelectedDateCalendarLabel => r'Inget valt datum'; @override - String get passwordDialogContentLabel => - r'ਇਸ PDF ਫਾਈਲ ਨੂੰ ਖੋਲ੍ਹਣ ਲਈ ਪਾਸਵਰਡ ਦਰਜ ਕਰੋ'; + String get notEmptyDataGridFilteringLabel => r'Inte tom'; @override - String get passwordDialogHeaderTextLabel => r'ਪਾਸਵਰਡ ਸੁਰੱਖਿਅਤ ਹੈ'; + String get notNullDataGridFilteringLabel => r'Inte Null'; @override - String get passwordDialogHintTextLabel => r'ਪਾਸਵਰਡ ਦਰਜ ਕਰੋ'; + String get nullDataGridFilteringLabel => r'Null'; @override - String get passwordDialogInvalidPasswordLabel => r'ਗਲਤ ਪਾਸਵਰਡ'; + String get numberFiltersDataGridFilteringLabel => r'Nummerfilter'; @override - String get pdfBookmarksLabel => r'ਬੁੱਕਮਾਰਕਸ'; + String get ofDataPagerLabel => r'av'; @override - String get pdfEnterPageNumberLabel => r'ਪੰਨਾ ਨੰਬਰ ਦਰਜ ਕਰੋ'; + String get okDataGridFilteringLabel => r'OK'; @override - String get pdfGoToPageLabel => r'ਪੰਨੇ ' "'" r'ਤੇ ਜਾਓ'; + String get orDataGridFilteringLabel => r'Eller'; @override - String get pdfInvalidPageNumberLabel => r'ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਵੈਧ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; + String get pagesDataPagerLabel => r'sidor'; @override - String get pdfNoBookmarksLabel => r'ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲੇ'; + String get passwordDialogContentLabel => + r'Ange lösenordet för att öppna denna PDF-fil'; @override - String get pdfPaginationDialogCancelLabel => r'ਰੱਦ ਕਰੋ'; + String get passwordDialogHeaderTextLabel => r'Lösenordsskyddat'; @override - String get pdfPaginationDialogOkLabel => r'ਠੀਕ ਹੈ'; + String get passwordDialogHintTextLabel => r'Skriv in lösenord'; @override - String get pdfPasswordDialogCancelLabel => r'ਰੱਦ ਕਰੋ'; + String get passwordDialogInvalidPasswordLabel => r'Felaktigt lösenord'; @override - String get pdfPasswordDialogOpenLabel => r'ਖੋਲ੍ਹੋ'; + String get pdfBookmarksLabel => r'Bokmärken'; @override - String get pdfScrollStatusOfLabel => r'ਦੇ'; + String get pdfEnterPageNumberLabel => r'Ange sidnummer'; @override - String get rabi1Label => r'ਰਬੀ ਅਲ-ਅੱਵਲ'; + String get pdfGoToPageLabel => r'Gå till sidan'; @override - String get rabi2Label => r'ਰਬੀ ਅਲ-ਥਾਨੀ'; + String get pdfHyperlinkContentLabel => r'Vill du öppna sidan på'; @override - String get rajabLabel => r'ਰਜਬ'; + String get pdfHyperlinkDialogCancelLabel => r'AVBRYT'; @override - String get ramadanLabel => r'ਰਮਜ਼ਾਨ'; + String get pdfHyperlinkDialogOpenLabel => r'ÖPPNA'; @override - String get rowsPerPageDataPagerLabel => r'ਪ੍ਰਤੀ ਪੰਨਾ ਕਤਾਰਾਂ'; + String get pdfHyperlinkLabel => r'Öppna webbsidan'; @override - String get safarLabel => r'ਸਫਰ'; + String get pdfInvalidPageNumberLabel => + r'Var vänlig skriv in ett giltigt nummer'; @override - String get series => r'ਲੜੀ'; + String get pdfNoBookmarksLabel => r'Inga bokmärken hittades'; @override - String get shaabanLabel => r'ਸ਼ਾਅਬਾਨ'; + String get pdfPaginationDialogCancelLabel => r'AVBRYT'; @override - String get shawwalLabel => r'ਸ਼ਵਾਲ'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get shortDhualhiLabel => r'ਧੂਲ-ਐੱਚ'; + String get pdfPasswordDialogCancelLabel => r'AVBRYT'; @override - String get shortDhualqiLabel => r'ਧੂਲ-ਕਿਊ'; + String get pdfPasswordDialogOpenLabel => r'ÖPPNA'; @override - String get shortJumada1Label => r'ਜਮ. ਆਈ'; + String get pdfScrollStatusOfLabel => r'av'; @override - String get shortJumada2Label => r'ਜਮ. II'; + String get pdfSignaturePadDialogClearLabel => r'KLAR'; @override - String get shortMuharramLabel => r'ਮੁਹ.'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Rita din signatur'; @override - String get shortRabi1Label => r'ਰਬੀ. ਆਈ'; + String get pdfSignaturePadDialogPenColorLabel => r'Färg på pennan'; @override - String get shortRabi2Label => r'ਰਬੀ. II'; + String get pdfSignaturePadDialogSaveLabel => r'SPARA'; @override - String get shortRajabLabel => r'ਰਾਜ.'; + String get pdfTextSelectionMenuCopyLabel => r'Kopiera'; @override - String get shortRamadanLabel => r'ਰਾਮ.'; + String get pdfTextSelectionMenuHighlightLabel => r'Markera'; @override - String get shortSafarLabel => r'ਸਫ਼.'; + String get pdfTextSelectionMenuSquigglyLabel => r'Snyggt'; @override - String get shortShaabanLabel => r'ਸ਼ਾ.'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Genomstruken'; @override - String get shortShawwalLabel => r'ਸ਼ਾ.'; + String get pdfTextSelectionMenuUnderlineLabel => r'Understrykning'; @override - String get todayLabel => r'ਅੱਜ'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get weeknumberLabel => r'ਹਫ਼ਤਾ'; -} - -/// The translations for Polish (`pl`). -class SfLocalizationsPl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsPl class - const SfLocalizationsPl({ - String localeName = 'pl', - }) : super( - localeName: localeName, - ); + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get allDayLabel => r'Cały dzień'; + String get rajabLabel => r'Rajab'; @override - String get allowedViewDayLabel => r'Dzień'; + String get ramadanLabel => r'Ramadan'; @override - String get allowedViewMonthLabel => r'Miesiąc'; + String get rowsPerPageDataPagerLabel => r'Rader per sida'; @override - String get allowedViewScheduleLabel => r'Harmonogram'; + String get safarLabel => r'Safar'; @override - String get allowedViewTimelineDayLabel => r'Dzień osi czasu'; + String get searchDataGridFilteringLabel => r'Sök'; @override - String get allowedViewTimelineMonthLabel => r'Miesiąc osi czasu'; + String get selectAllDataGridFilteringLabel => r'Välj alla'; @override - String get allowedViewTimelineWeekLabel => r'Tydzień na osi czasu'; + String get series => r'Serier'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tydzień pracy na osi czasu'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get allowedViewWeekLabel => r'Tydzień'; + String get shawwalLabel => r'Shawwal'; @override - String get allowedViewWorkWeekLabel => r'Tydzień pracy'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get daySpanCountLabel => r'Dzień'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get dhualhiLabel => r'Zu al-Hidżdżah'; + String get shortJumada1Label => r'Jum. jag'; @override - String get dhualqiLabel => r'Zu al-Qi' "'" r'dah'; + String get shortJumada2Label => r'Jum. II'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortMuharramLabel => r'Muh.'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shortRabi1Label => r'Rabi. jag'; @override - String get muharramLabel => r'Muharram'; + String get shortRabi2Label => r'Rabi. II'; @override - String get noEventsCalendarLabel => r'Brak wydarzeń'; + String get shortRajabLabel => r'Raj.'; @override - String get noSelectedDateCalendarLabel => r'Nie wybrano daty'; + String get shortRamadanLabel => r'Bagge.'; @override - String get ofDataPagerLabel => r'z'; + String get shortSafarLabel => r'Saf.'; @override - String get pagesDataPagerLabel => r'strony'; + String get shortShaabanLabel => r'Sha.'; @override - String get passwordDialogContentLabel => - r'Wprowadź hasło, aby otworzyć ten plik PDF'; + String get shortShawwalLabel => r'Shaw.'; @override - String get passwordDialogHeaderTextLabel => r'Hasło chronione'; + String get showRowsWhereDataGridFilteringLabel => r'Visa rader var'; @override - String get passwordDialogHintTextLabel => r'Wprowadź hasło'; + String get sortAToZDataGridFilteringLabel => r'Sortera A till Ö'; @override - String get passwordDialogInvalidPasswordLabel => r'nieprawidłowe hasło'; + String get sortAndFilterDataGridFilteringLabel => r'Sortera och filtrera'; @override - String get pdfBookmarksLabel => r'Zakładki'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sortera Störst Till Minst'; @override - String get pdfEnterPageNumberLabel => r'Wpisz numer strony'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sortera Nyast Till Äldst'; @override - String get pdfGoToPageLabel => r'Idź do strony'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sortera äldst till nyaste'; @override - String get pdfInvalidPageNumberLabel => r'Proszę wprowadzić poprawny numer'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sortera minsta till största'; @override - String get pdfNoBookmarksLabel => r'Nie znaleziono zakładek'; + String get sortZToADataGridFilteringLabel => r'Sortera från Z till A'; @override - String get pdfPaginationDialogCancelLabel => r'ANULUJ'; + String get textFiltersDataGridFilteringLabel => r'Textfilter'; @override - String get pdfPaginationDialogOkLabel => r'ok'; + String get todayLabel => r'Idag'; @override - String get pdfPasswordDialogCancelLabel => r'ANULUJ'; + String get weeknumberLabel => r'Vecka'; +} - @override - String get pdfPasswordDialogOpenLabel => r'OTWARTY'; +/// The translations for Swahili (`sw`). +class SfLocalizationsSw extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsSw class + const SfLocalizationsSw({String localeName = 'sw'}) + : super(localeName: localeName); @override - String get pdfScrollStatusOfLabel => r'z'; + String get afterDataGridFilteringLabel => r'Baada ya'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get afterOrEqualDataGridFilteringLabel => r'Baada ya Au Sawa'; @override - String get rabi2Label => r'Rabi’ al-thani'; + String get allDayLabel => r'Siku nzima'; @override - String get rajabLabel => r'Rajab'; + String get allowedViewDayLabel => r'Siku'; @override - String get ramadanLabel => r'Ramadan'; + String get allowedViewMonthLabel => r'Mwezi'; @override - String get rowsPerPageDataPagerLabel => r'Wiersze na stronę'; + String get allowedViewScheduleLabel => r'Ratiba'; @override - String get safarLabel => r'Safar'; + String get allowedViewTimelineDayLabel => r'Siku ya Ratiba'; @override - String get series => r'Seria'; + String get allowedViewTimelineMonthLabel => r'Muda wa Mwezi'; @override - String get shaabanLabel => r'Szaaban'; + String get allowedViewTimelineWeekLabel => r'Wiki ya Ratiba'; @override - String get shawwalLabel => r'Shawwal'; + String get allowedViewTimelineWorkWeekLabel => r'Wiki ya Kazi ya Ratiba'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get allowedViewWeekLabel => r'Wiki'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get allowedViewWorkWeekLabel => r'Wiki ya Kazi'; @override - String get shortJumada1Label => r'Skok. i'; + String get andDataGridFilteringLabel => r'Na'; @override - String get shortJumada2Label => r'Skok. II'; + String get beforeDataGridFilteringLabel => r'Kabla'; @override - String get shortMuharramLabel => r'Muh.'; + String get beforeOrEqualDataGridFilteringLabel => r'Kabla Au Sawa'; @override - String get shortRabi1Label => r'Rabi. i'; + String get beginsWithDataGridFilteringLabel => r'Huanza Na'; @override - String get shortRabi2Label => r'Rabi. II'; + String get cancelDataGridFilteringLabel => r'Ghairi'; @override - String get shortRajabLabel => r'Raj.'; + String get clearFilterDataGridFilteringLabel => r'Futa Kichujio'; @override - String get shortRamadanLabel => r'Baran.'; + String get containsDataGridFilteringLabel => r'Ina'; @override - String get shortSafarLabel => r'Saf.'; + String get dateFiltersDataGridFilteringLabel => r'Vichujio vya Tarehe'; @override - String get shortShaabanLabel => r'Sha.'; + String get daySpanCountLabel => r'Siku'; @override - String get shortShawwalLabel => r'Shaw.'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get todayLabel => r'Dziś'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get weeknumberLabel => r'Tydzień'; -} - -/// The translations for Pushto Pashto (`ps`). -class SfLocalizationsPs extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsPs class - const SfLocalizationsPs({ - String localeName = 'ps', - }) : super( - localeName: localeName, - ); + String get doesNotBeginWithDataGridFilteringLabel => r'Huanza Na'; @override - String get allDayLabel => r'ټوله ورځ'; + String get doesNotContainDataGridFilteringLabel => r'Haina'; @override - String get allowedViewDayLabel => r'ورځ'; + String get doesNotEndWithDataGridFilteringLabel => r'Haiishii Na'; @override - String get allowedViewMonthLabel => r'میاشت'; + String get doesNotEqualDataGridFilteringLabel => r'Hailingani'; @override - String get allowedViewScheduleLabel => r'مهالویش'; + String get emptyDataGridFilteringLabel => r'Tupu'; @override - String get allowedViewTimelineDayLabel => r'د مهال ویش ورځ'; + String get endsWithDataGridFilteringLabel => r'Inaisha Na'; @override - String get allowedViewTimelineMonthLabel => r'د مهال ویش میاشت'; + String get equalsDataGridFilteringLabel => r'Sawa'; @override - String get allowedViewTimelineWeekLabel => r'د مهال ویش اونۍ'; + String get fromDataGridFilteringLabel => r'Kutoka'; @override - String get allowedViewTimelineWorkWeekLabel => r'مهال ویش کاري اونۍ'; + String get greaterThanDataGridFilteringLabel => r'Kubwa kuliko'; @override - String get allowedViewWeekLabel => r'اونۍ'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Kubwa Kuliko Au Sawa'; @override - String get allowedViewWorkWeekLabel => r'کاري اونۍ'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get daySpanCountLabel => r'ورځ'; + String get jumada2Label => r'Jumada al-thani'; @override - String get dhualhiLabel => r'ذوالحجې'; + String get lessThanDataGridFilteringLabel => r'Chini ya'; @override - String get dhualqiLabel => r'ذو القعده'; + String get lessThanOrEqualDataGridFilteringLabel => r'Chini ya Au Sawa'; @override - String get jumada1Label => r'جمادی الاول'; + String get muharramLabel => r'Muharram'; @override - String get jumada2Label => r'جمعه الثاني'; + String get noEventsCalendarLabel => r'Hakuna matukio'; @override - String get muharramLabel => r'محرم'; + String get noMatchesDataGridFilteringLabel => r'Hakuna zinazolingana'; @override - String get noEventsCalendarLabel => r'هیڅ پیښه نشته'; + String get noSelectedDateCalendarLabel => r'Hakuna tarehe iliyochaguliwa'; @override - String get noSelectedDateCalendarLabel => r'نه ټاکل شوې نیټه'; + String get notEmptyDataGridFilteringLabel => r'Sio Tupu'; @override - String get ofDataPagerLabel => r'د'; + String get notNullDataGridFilteringLabel => r'Sio Null'; @override - String get pagesDataPagerLabel => r'پاڼې'; + String get nullDataGridFilteringLabel => r'Null'; @override - String get passwordDialogContentLabel => - r'د دې پی ډی ایف فایل خلاصولو لپاره پاسورډ دننه کړئ'; + String get numberFiltersDataGridFilteringLabel => r'Vichujio vya Nambari'; @override - String get passwordDialogHeaderTextLabel => r'پټنوم خوندي شوی'; + String get ofDataPagerLabel => r'ya'; @override - String get passwordDialogHintTextLabel => r'پاسورډ دننه کړئ'; + String get okDataGridFilteringLabel => r'sawa'; @override - String get passwordDialogInvalidPasswordLabel => r'بې اعتباره پټنوم'; + String get orDataGridFilteringLabel => r'Au'; @override - String get pdfBookmarksLabel => r'بک مارکونه'; + String get pagesDataPagerLabel => r'kurasa'; @override - String get pdfEnterPageNumberLabel => r'د پاڼې شمیره دننه کړئ'; + String get passwordDialogContentLabel => + r'Weka nenosiri ili kufungua faili hii ya PDF'; @override - String get pdfGoToPageLabel => r'پاڼې ته لاړ شئ'; + String get passwordDialogHeaderTextLabel => r'Nenosiri Limelindwa'; @override - String get pdfInvalidPageNumberLabel => - r'مهرباني وکړئ یو باوري شمیره دننه کړئ'; + String get passwordDialogHintTextLabel => r'Weka Nenosiri'; @override - String get pdfNoBookmarksLabel => r'هیڅ بک مارک ونه موندل شو'; + String get passwordDialogInvalidPasswordLabel => r'Nenosiri batili'; @override - String get pdfPaginationDialogCancelLabel => r'لغوه کول'; + String get pdfBookmarksLabel => r'Alamisho'; @override - String get pdfPaginationDialogOkLabel => r'سمه ده'; + String get pdfEnterPageNumberLabel => r'Ingiza nambari ya ukurasa'; @override - String get pdfPasswordDialogCancelLabel => r'لغوه کول'; + String get pdfGoToPageLabel => r'Nenda kwenye ukurasa'; @override - String get pdfPasswordDialogOpenLabel => r'خلاص'; + String get pdfHyperlinkContentLabel => r'Je, unataka kufungua ukurasa katika'; @override - String get pdfScrollStatusOfLabel => r'د'; + String get pdfHyperlinkDialogCancelLabel => r'GHAIRI'; @override - String get rabi1Label => r'ربیع الاول'; + String get pdfHyperlinkDialogOpenLabel => r'FUNGUA'; @override - String get rabi2Label => r'ربیع الثاني'; + String get pdfHyperlinkLabel => r'Fungua Ukurasa wa Wavuti'; @override - String get rajabLabel => r'رجب'; + String get pdfInvalidPageNumberLabel => r'Tafadhali weka nambari halali'; @override - String get ramadanLabel => r'رمضان'; + String get pdfNoBookmarksLabel => r'Hakuna alamisho zilizopatikana'; @override - String get rowsPerPageDataPagerLabel => r'په هره پاڼه کې قطارونه'; + String get pdfPaginationDialogCancelLabel => r'Ghairi'; @override - String get safarLabel => r'صفر'; + String get pdfPaginationDialogOkLabel => r'Sawa'; @override - String get series => r'لړۍ'; + String get pdfPasswordDialogCancelLabel => r'Ghairi'; @override - String get shaabanLabel => r'شعبان'; + String get pdfPasswordDialogOpenLabel => r'FUNGUA'; @override - String get shawwalLabel => r'شوال'; + String get pdfScrollStatusOfLabel => r'ya'; @override - String get shortDhualhiLabel => r'ذوالح'; + String get pdfSignaturePadDialogClearLabel => r'WAZI'; @override - String get shortDhualqiLabel => r'ذوالق'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Chora saini yako'; @override - String get shortJumada1Label => r'جم. زه'; + String get pdfSignaturePadDialogPenColorLabel => r'Rangi ya kalamu'; @override - String get shortJumada2Label => r'جم. II'; + String get pdfSignaturePadDialogSaveLabel => r'HIFADHI'; @override - String get shortMuharramLabel => r'Muh.'; + String get pdfTextSelectionMenuCopyLabel => r'Nakili'; @override - String get shortRabi1Label => r'ربیع زه'; + String get pdfTextSelectionMenuHighlightLabel => r'Kuonyesha'; @override - String get shortRabi2Label => r'ربیع II'; + String get pdfTextSelectionMenuSquigglyLabel => r'Kwa mbwembwe'; @override - String get shortRajabLabel => r'راج.'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Mgomo'; @override - String get shortRamadanLabel => r'رام'; + String get pdfTextSelectionMenuUnderlineLabel => r'Piga mstari'; @override - String get shortSafarLabel => r'سف'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get shortShaabanLabel => r'شا'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get shortShawwalLabel => r'شو.'; + String get rajabLabel => r'Rajab'; @override - String get todayLabel => r'نن'; + String get ramadanLabel => r'Ramadhani'; @override - String get weeknumberLabel => r'اونۍ'; -} + String get rowsPerPageDataPagerLabel => r'Safu kwa kila ukurasa'; -/// The translations for Portuguese (`pt`). -class SfLocalizationsPt extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsPt class - const SfLocalizationsPt({ - String localeName = 'pt', - }) : super( - localeName: localeName, - ); + @override + String get safarLabel => r'Safar'; @override - String get allDayLabel => r'Dia todo'; + String get searchDataGridFilteringLabel => r'Tafuta'; @override - String get allowedViewDayLabel => r'Dia'; + String get selectAllDataGridFilteringLabel => r'Chagua Zote'; @override - String get allowedViewMonthLabel => r'Mês'; + String get series => r'Msururu'; @override - String get allowedViewScheduleLabel => r'Agendar'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get allowedViewTimelineDayLabel => r'Dia da linha do tempo'; + String get shawwalLabel => r'Shawwal'; @override - String get allowedViewTimelineMonthLabel => r'Mês da linha do tempo'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get allowedViewTimelineWeekLabel => r'Semana da linha do tempo'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Semana de trabalho da linha do tempo'; + String get shortJumada1Label => r'Jum. I'; @override - String get allowedViewWeekLabel => r'Semana'; + String get shortJumada2Label => r'Jum. II'; @override - String get allowedViewWorkWeekLabel => r'Semana de trabalho'; + String get shortMuharramLabel => r'Muh.'; @override - String get daySpanCountLabel => r'Dia'; + String get shortRabi1Label => r'Rabi. I'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get shortRabi2Label => r'Rabi. II'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get shortRajabLabel => r'Raj.'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortRamadanLabel => r'Ram.'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shortSafarLabel => r'Saf.'; @override - String get muharramLabel => r'Muharram'; + String get shortShaabanLabel => r'Sha.'; @override - String get noEventsCalendarLabel => r'Sem eventos'; + String get shortShawwalLabel => r'Shaw.'; @override - String get noSelectedDateCalendarLabel => r'Nenhuma data selecionada'; + String get showRowsWhereDataGridFilteringLabel => r'Onyesha safu mlalo wapi'; @override - String get ofDataPagerLabel => r'do'; + String get sortAToZDataGridFilteringLabel => r'Panga A hadi Z'; @override - String get pagesDataPagerLabel => r'Páginas'; + String get sortAndFilterDataGridFilteringLabel => r'Panga na Chuja'; @override - String get passwordDialogContentLabel => - r'Digite a senha para abrir este arquivo PDF'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Panga Kubwa Hadi Ndogo'; @override - String get passwordDialogHeaderTextLabel => r'Protegido por senha'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Panga Mpya Zaidi Kwa Kongwe Zaidi'; @override - String get passwordDialogHintTextLabel => r'Digite a senha'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Panga Kongwe Kwa Mpya Zaidi'; @override - String get passwordDialogInvalidPasswordLabel => r'senha inválida'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Panga Ndogo Kwa Kubwa Zaidi'; @override - String get pdfBookmarksLabel => r'Favoritos'; + String get sortZToADataGridFilteringLabel => r'Panga Z hadi A'; @override - String get pdfEnterPageNumberLabel => r'Digite o número da página'; + String get textFiltersDataGridFilteringLabel => r'Vichujio vya Maandishi'; @override - String get pdfGoToPageLabel => r'Vá para página'; + String get todayLabel => r'Leo'; @override - String get pdfInvalidPageNumberLabel => r'Por favor insira um número válido'; + String get weeknumberLabel => r'Wiki'; +} + +/// The translations for Tamil (`ta`). +class SfLocalizationsTa extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsTa class + const SfLocalizationsTa({String localeName = 'ta'}) + : super(localeName: localeName); @override - String get pdfNoBookmarksLabel => r'Nenhum favorito encontrado'; + String get afterDataGridFilteringLabel => r'பிறகு'; @override - String get pdfPaginationDialogCancelLabel => r'CANCELAR'; + String get afterOrEqualDataGridFilteringLabel => r'பிறகு அல்லது சமம்'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get allDayLabel => r'நாள் முழுவதும்'; @override - String get pdfPasswordDialogCancelLabel => r'CANCELAR'; + String get allowedViewDayLabel => r'நாள்'; @override - String get pdfPasswordDialogOpenLabel => r'ABRIR'; + String get allowedViewMonthLabel => r'மாதம்'; @override - String get pdfScrollStatusOfLabel => r'do'; + String get allowedViewScheduleLabel => r'அட்டவணை'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get allowedViewTimelineDayLabel => r'காலக்கெடு நாள்'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get allowedViewTimelineMonthLabel => r'காலவரிசை மாதம்'; @override - String get rajabLabel => r'Rajab'; + String get allowedViewTimelineWeekLabel => r'காலவரிசை வாரம்'; @override - String get ramadanLabel => r'Ramadã'; + String get allowedViewTimelineWorkWeekLabel => r'காலக்கெடு வேலை வாரம்'; @override - String get rowsPerPageDataPagerLabel => r'Linhas por página'; + String get allowedViewWeekLabel => r'வாரம்'; @override - String get safarLabel => r'Safar'; + String get allowedViewWorkWeekLabel => r'வேலை வாரம்'; @override - String get series => r'Series'; + String get andDataGridFilteringLabel => r'மற்றும்'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get beforeDataGridFilteringLabel => r'முன்பு'; @override - String get shawwalLabel => r'Shawwal'; + String get beforeOrEqualDataGridFilteringLabel => r'முன் அல்லது சமம்'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get beginsWithDataGridFilteringLabel => r'உடன் தொடங்குகிறது'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get cancelDataGridFilteringLabel => r'ரத்து செய்'; @override - String get shortJumada1Label => r'Jum. eu'; + String get clearFilterDataGridFilteringLabel => r'வடிகட்டியை அழி'; @override - String get shortJumada2Label => r'Jum. II'; + String get containsDataGridFilteringLabel => r'கொண்டுள்ளது'; @override - String get shortMuharramLabel => r'Muh.'; + String get dateFiltersDataGridFilteringLabel => r'தேதி வடிகட்டிகள்'; @override - String get shortRabi1Label => r'Rabino. eu'; + String get daySpanCountLabel => r'நாள்'; @override - String get shortRabi2Label => r'Rabino. II'; + String get dhualhiLabel => r'து அல்-ஹிஜ்ஜா'; @override - String get shortRajabLabel => r'Raj.'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get shortRamadanLabel => r'RAM.'; + String get doesNotBeginWithDataGridFilteringLabel => r'உடன் தொடங்கவில்லை'; @override - String get shortSafarLabel => r'Saf.'; + String get doesNotContainDataGridFilteringLabel => r'கொண்டிருக்கும் இல்லை'; @override - String get shortShaabanLabel => r'Sha.'; + String get doesNotEndWithDataGridFilteringLabel => r'உடன் முடிவடையவில்லை'; @override - String get shortShawwalLabel => r'Shaw.'; + String get doesNotEqualDataGridFilteringLabel => r'சமமாக இல்லை'; @override - String get todayLabel => r'Hoje'; + String get emptyDataGridFilteringLabel => r'காலியாக'; @override - String get weeknumberLabel => r'Semana'; -} + String get endsWithDataGridFilteringLabel => r'உடன் முடிகிறது'; -/// The translations for Portuguese, as used in Portugal (`pt_PT`). -class SfLocalizationsPtPt extends SfLocalizationsPt { - /// Creating an argument constructor of SfLocalizationsPtPt class - const SfLocalizationsPtPt({ - String localeName = 'pt_PT', - }) : super( - localeName: localeName, - ); + @override + String get equalsDataGridFilteringLabel => r'சமம்'; @override - String get noEventsCalendarLabel => r'Nenhum evento'; + String get fromDataGridFilteringLabel => r'இருந்து'; @override - String get ofDataPagerLabel => r'de'; + String get greaterThanDataGridFilteringLabel => r'விட பெரியது'; @override - String get pagesDataPagerLabel => r'páginas'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'பெரியது அல்லது சமமானது'; @override - String get pdfBookmarksLabel => r'Marcadores'; + String get jumada1Label => r'ஜுமாதா அல்-அவ்வல்'; @override - String get pdfScrollStatusOfLabel => r'de'; + String get jumada2Label => r'ஜுமாதா அல்-தானி'; @override - String get pdfGoToPageLabel => r'Ir para a página'; + String get lessThanDataGridFilteringLabel => r'விட குறைவாக'; @override - String get pdfInvalidPageNumberLabel => r'Insira um número válido'; + String get lessThanOrEqualDataGridFilteringLabel => + r'குறைவானது அல்லது சமமானது'; @override - String get passwordDialogInvalidPasswordLabel => r'Senha inválida'; + String get muharramLabel => r'முஹர்ரம்'; @override - String get pdfPasswordDialogOpenLabel => r'ABERTO'; + String get noEventsCalendarLabel => r'நிகழ்வுகள் இல்லை'; @override - String get allowedViewScheduleLabel => r'Cronograma'; + String get noMatchesDataGridFilteringLabel => r'பொருத்தங்கள் இல்லை'; @override - String get allDayLabel => r'Todo o dia'; + String get noSelectedDateCalendarLabel => r'தேர்ந்தெடுக்கப்பட்ட தேதி இல்லை'; @override - String get shortRamadanLabel => r'Carneiro.'; + String get notEmptyDataGridFilteringLabel => r'காலியாக இல்லை'; @override - String get series => r'Série'; -} + String get notNullDataGridFilteringLabel => r'பூஜ்யமாக இல்லை'; -/// The translations for Romanian Moldavian Moldovan (`ro`). -class SfLocalizationsRo extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsRo class - const SfLocalizationsRo({ - String localeName = 'ro', - }) : super( - localeName: localeName, - ); + @override + String get nullDataGridFilteringLabel => r'ஏதுமில்லை'; @override - String get allDayLabel => r'Toată ziua'; + String get numberFiltersDataGridFilteringLabel => r'எண் வடிப்பான்கள்'; @override - String get allowedViewDayLabel => r'Zi'; + String get ofDataPagerLabel => r'இன்'; @override - String get allowedViewMonthLabel => r'Lună'; + String get okDataGridFilteringLabel => r'சரி'; @override - String get allowedViewScheduleLabel => r'Programa'; + String get orDataGridFilteringLabel => r'அல்லது'; @override - String get allowedViewTimelineDayLabel => r'Ziua cronologică'; + String get pagesDataPagerLabel => r'பக்கங்கள்'; @override - String get allowedViewTimelineMonthLabel => r'Cronologie Luna'; + String get passwordDialogContentLabel => + r'இந்த PDF கோப்பைத் திறக்க கடவுச்சொல்லை உள்ளிடவும்'; @override - String get allowedViewTimelineWeekLabel => r'Săptămâna cronologică'; + String get passwordDialogHeaderTextLabel => r'கடவுச்சொல் பாதுகாக்கப்பட்டது'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Cronologie Săptămâna de lucru'; + String get passwordDialogHintTextLabel => r'கடவுச்சொல்லை உள்ளிடவும்'; @override - String get allowedViewWeekLabel => r'Săptămână'; + String get passwordDialogInvalidPasswordLabel => r'தவறான கடவுச்சொல்'; @override - String get allowedViewWorkWeekLabel => r'Saptamana de lucru'; + String get pdfBookmarksLabel => r'புக்மார்க்குகள்'; @override - String get daySpanCountLabel => r'Zi'; + String get pdfEnterPageNumberLabel => r'பக்க எண்ணை உள்ளிடவும்'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfGoToPageLabel => r'பக்கத்திற்கு செல்'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfHyperlinkContentLabel => + r'நீங்கள் பக்கத்தைத் திறக்க விரும்புகிறீர்களா?'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfHyperlinkDialogCancelLabel => r'ரத்துசெய்'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfHyperlinkDialogOpenLabel => r'திற'; @override - String get muharramLabel => r'Muharram'; + String get pdfHyperlinkLabel => r'இணையப் பக்கத்தைத் திற'; @override - String get noEventsCalendarLabel => r'Fără evenimente'; + String get pdfInvalidPageNumberLabel => r'சரியான எண்ணை உள்ளிடவும்'; @override - String get noSelectedDateCalendarLabel => r'Nicio dată selectată'; + String get pdfNoBookmarksLabel => r'புக்மார்க்குகள் எதுவும் இல்லை'; @override - String get ofDataPagerLabel => r'de'; + String get pdfPaginationDialogCancelLabel => r'ரத்துசெய்'; @override - String get pagesDataPagerLabel => r'pagini'; + String get pdfPaginationDialogOkLabel => r'சரி'; @override - String get passwordDialogContentLabel => - r'Introduceți parola pentru a deschide acest fișier PDF'; + String get pdfPasswordDialogCancelLabel => r'ரத்துசெய்'; @override - String get passwordDialogHeaderTextLabel => r'Protectie cu parola'; + String get pdfPasswordDialogOpenLabel => r'திற'; @override - String get passwordDialogHintTextLabel => r'Introdu parola'; + String get pdfScrollStatusOfLabel => r'இன்'; @override - String get passwordDialogInvalidPasswordLabel => r'Parolă Invalidă'; + String get pdfSignaturePadDialogClearLabel => r'அழி'; @override - String get pdfBookmarksLabel => r'Marcaje'; + String get pdfSignaturePadDialogHeaderTextLabel => + r'உங்கள் கையொப்பத்தை வரையவும்'; @override - String get pdfEnterPageNumberLabel => r'Introduceți numărul paginii'; + String get pdfSignaturePadDialogPenColorLabel => r'பேனா நிறம்'; @override - String get pdfGoToPageLabel => r'Mergi la pagina'; + String get pdfSignaturePadDialogSaveLabel => r'சேமிக்கவும்'; @override - String get pdfInvalidPageNumberLabel => - r'Vă rugăm să introduceți un număr valid'; + String get pdfTextSelectionMenuCopyLabel => r'நகலெடுக்கவும்'; @override - String get pdfNoBookmarksLabel => r'Nu s-au găsit marcaje'; + String get pdfTextSelectionMenuHighlightLabel => r'முன்னிலைப்படுத்த'; @override - String get pdfPaginationDialogCancelLabel => r'ANULARE'; + String get pdfTextSelectionMenuSquigglyLabel => r'ஸ்க்விக்லி'; @override - String get pdfPaginationDialogOkLabel => r'Bine'; + String get pdfTextSelectionMenuStrikethroughLabel => r'வேலைநிறுத்தம்'; @override - String get pdfPasswordDialogCancelLabel => r'ANULARE'; + String get pdfTextSelectionMenuUnderlineLabel => r'அடிக்கோடு'; @override - String get pdfPasswordDialogOpenLabel => r'DESCHIS'; + String get rabi1Label => r'ரபி அல் அவ்வல்'; @override - String get pdfScrollStatusOfLabel => r'de'; + String get rabi2Label => r'ரபி அல்-தானி'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get rajabLabel => r'ரஜப்'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get ramadanLabel => r'ரமலான்'; @override - String get rajabLabel => r'Rajab'; + String get rowsPerPageDataPagerLabel => r'ஒரு பக்கத்திற்கு வரிசைகள்'; @override - String get ramadanLabel => r'Ramadanul'; + String get safarLabel => r'சஃபர்'; @override - String get rowsPerPageDataPagerLabel => r'Rânduri pe pagină'; + String get searchDataGridFilteringLabel => r'தேடு'; @override - String get safarLabel => r'Safar'; + String get selectAllDataGridFilteringLabel => r'அனைத்தையும் தெரிவுசெய்'; @override - String get series => r'Serie'; + String get series => r'தொடர்'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'ஷஅபான்'; @override - String get shawwalLabel => r'Shawwal'; + String get shawwalLabel => r'ஷவ்வால்'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'துல்-எச்'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'துல்-கே'; @override - String get shortJumada1Label => r'Jum. eu'; + String get shortJumada1Label => r'ஜும். நான்'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortJumada2Label => r'ஜும். II'; @override - String get shortMuharramLabel => r'Muh.'; + String get shortMuharramLabel => r'முஹ்.'; @override - String get shortRabi1Label => r'Rabi. eu'; + String get shortRabi1Label => r'ரபி. நான்'; @override - String get shortRabi2Label => r'Rabi. II'; + String get shortRabi2Label => r'ரபி. II'; @override - String get shortRajabLabel => r'Raj.'; + String get shortRajabLabel => r'ராஜ்.'; @override - String get shortRamadanLabel => r'Berbec.'; + String get shortRamadanLabel => r'ரேம்.'; @override String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Sha.'; + String get shortShaabanLabel => r'ஷா.'; @override - String get shortShawwalLabel => r'Shaw.'; + String get shortShawwalLabel => r'ஷா'; @override - String get todayLabel => r'Azi'; + String get showRowsWhereDataGridFilteringLabel => r'எங்கே வரிசைகளைக் காட்டு'; @override - String get weeknumberLabel => r'Săptămână'; -} - -/// The translations for Russian (`ru`). -class SfLocalizationsRu extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsRu class - const SfLocalizationsRu({ - String localeName = 'ru', - }) : super( - localeName: localeName, - ); + String get sortAToZDataGridFilteringLabel => + r'A முதல் Z வரை வரிசைப்படுத்தவும்'; @override - String get allDayLabel => r'Весь день'; + String get sortAndFilterDataGridFilteringLabel => + r'வரிசைப்படுத்தி வடிகட்டவும்'; @override - String get allowedViewDayLabel => r'День'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'பெரியது முதல் சிறியது வரை வரிசைப்படுத்தவும்'; @override - String get allowedViewMonthLabel => r'Месяц'; + String get sortNewestToOldestDataGridFilteringLabel => + r'புதியது முதல் பழையது என வரிசைப்படுத்தவும்'; @override - String get allowedViewScheduleLabel => r'Расписание'; + String get sortOldestToNewestDataGridFilteringLabel => + r'பழமையானது முதல் புதியது வரை வரிசைப்படுத்தவும்'; @override - String get allowedViewTimelineDayLabel => r'День'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'சிறியது முதல் பெரியது வரை வரிசைப்படுத்தவும்'; @override - String get allowedViewTimelineMonthLabel => r'Месяц'; + String get sortZToADataGridFilteringLabel => + r'Z முதல் A வரை வரிசைப்படுத்தவும்'; @override - String get allowedViewTimelineWeekLabel => r'Неделя временной шкалы'; + String get textFiltersDataGridFilteringLabel => r'உரை வடிப்பான்கள்'; @override - String get allowedViewTimelineWorkWeekLabel => r'Рабочая неделя'; + String get todayLabel => r'இன்று'; @override - String get allowedViewWeekLabel => r'Неделю'; + String get weeknumberLabel => r'வாரம்'; +} + +/// The translations for Telugu (`te`). +class SfLocalizationsTe extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsTe class + const SfLocalizationsTe({String localeName = 'te'}) + : super(localeName: localeName); @override - String get allowedViewWorkWeekLabel => r'Рабочая неделя'; + String get afterDataGridFilteringLabel => r'తర్వాత'; @override - String get daySpanCountLabel => r'День'; + String get afterOrEqualDataGridFilteringLabel => r'తర్వాత లేదా సమానం'; @override - String get dhualhiLabel => r'Зу аль-Хиджа'; + String get allDayLabel => r'రోజంతా'; @override - String get dhualqiLabel => r'Зу аль-Кида'; + String get allowedViewDayLabel => r'రోజు'; @override - String get jumada1Label => r'Джумада аль-авваль'; + String get allowedViewMonthLabel => r'నెల'; @override - String get jumada2Label => r'Джумада аль-тани'; + String get allowedViewScheduleLabel => r'షెడ్యూల్'; @override - String get muharramLabel => r'Мухаррам'; + String get allowedViewTimelineDayLabel => r'కాలక్రమం రోజు'; @override - String get noEventsCalendarLabel => r'Нет событий'; + String get allowedViewTimelineMonthLabel => r'కాలక్రమం నెల'; @override - String get noSelectedDateCalendarLabel => r'Дата не выбрана'; + String get allowedViewTimelineWeekLabel => r'కాలక్రమం వారం'; @override - String get ofDataPagerLabel => r'из'; + String get allowedViewTimelineWorkWeekLabel => r'టైమ్‌లైన్ పని వారం'; @override - String get pagesDataPagerLabel => r'страницы'; + String get allowedViewWeekLabel => r'వారం'; @override - String get passwordDialogContentLabel => - r'Введите пароль, чтобы открыть этот файл PDF'; + String get allowedViewWorkWeekLabel => r'పని వారం'; @override - String get passwordDialogHeaderTextLabel => r'Пароль защищен'; + String get andDataGridFilteringLabel => r'మరియు'; @override - String get passwordDialogHintTextLabel => r'Введите пароль'; + String get beforeDataGridFilteringLabel => r'ముందు'; @override - String get passwordDialogInvalidPasswordLabel => r'неправильный пароль'; + String get beforeOrEqualDataGridFilteringLabel => r'ముందు లేదా సమానం'; @override - String get pdfBookmarksLabel => r'Закладки'; + String get beginsWithDataGridFilteringLabel => r'తో ప్రారంభమవుతుంద'; @override - String get pdfEnterPageNumberLabel => r'Введите номер страницы'; + String get cancelDataGridFilteringLabel => r'రద్దు చేయండి'; @override - String get pdfGoToPageLabel => r'Перейти на страницу'; + String get clearFilterDataGridFilteringLabel => r'ఫిల్టర్‌ని క్లియర్ చేయండి'; @override - String get pdfInvalidPageNumberLabel => - r'пожалуйста введите правильное число'; + String get containsDataGridFilteringLabel => r'కలిగి ఉంది'; @override - String get pdfNoBookmarksLabel => r'Закладки не найдены'; + String get dateFiltersDataGridFilteringLabel => r'తేదీ ఫిల్టర్లు'; @override - String get pdfPaginationDialogCancelLabel => r'ОТМЕНИТЬ'; + String get daySpanCountLabel => r'రోజు'; @override - String get pdfPaginationDialogOkLabel => r'Ok'; + String get dhualhiLabel => r'ధు అల్-హిజ్జా'; @override - String get pdfPasswordDialogCancelLabel => r'ОТМЕНИТЬ'; + String get dhualqiLabel => + r'ధు అల్-కి' + "'" + r'దా'; @override - String get pdfPasswordDialogOpenLabel => r'ОТКРЫТЫМ'; + String get doesNotBeginWithDataGridFilteringLabel => r'తో ప్రారంభం కాదు'; @override - String get pdfScrollStatusOfLabel => r'из'; + String get doesNotContainDataGridFilteringLabel => r'దింట్లో ఉండదు'; @override - String get rabi1Label => r'Раби аль-авваль'; + String get doesNotEndWithDataGridFilteringLabel => r'దీనితో అంతం కాదు'; @override - String get rabi2Label => r'Раби аль-Тани'; + String get doesNotEqualDataGridFilteringLabel => r'సమానం కాదు'; @override - String get rajabLabel => r'Раджаб'; + String get emptyDataGridFilteringLabel => r'ఖాళీ'; @override - String get ramadanLabel => r'Рамадан'; + String get endsWithDataGridFilteringLabel => r'తో ముగుస్తుంది'; @override - String get rowsPerPageDataPagerLabel => r'Строк на странице'; + String get equalsDataGridFilteringLabel => r'సమానం'; @override - String get safarLabel => r'Сафар'; + String get fromDataGridFilteringLabel => r'నుండి'; @override - String get series => r'Ряд'; + String get greaterThanDataGridFilteringLabel => r'కంటే ఎక్కువ'; @override - String get shaabanLabel => r'Шаабан'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'గ్రేటర్ దాన్ లేదా ఈక్వల్'; @override - String get shawwalLabel => r'Шавваль'; + String get jumada1Label => r'జుమాదా అల్-అవ్వల్'; @override - String get shortDhualhiLabel => r'Зуль-Х'; + String get jumada2Label => r'జుమాదా అల్-థాని'; @override - String get shortDhualqiLabel => r'Зуль-Кью'; + String get lessThanDataGridFilteringLabel => r'కంటే తక్కువ'; @override - String get shortJumada1Label => r'Джум. я'; + String get lessThanOrEqualDataGridFilteringLabel => r'తక్కువ లేదా సమానం'; @override - String get shortJumada2Label => r'Джум. II'; + String get muharramLabel => r'ముహర్రం'; @override - String get shortMuharramLabel => r'Мух.'; + String get noEventsCalendarLabel => r'ఈవెంట్‌లు లేవు'; @override - String get shortRabi1Label => r'Раби. я'; + String get noMatchesDataGridFilteringLabel => r'మ్యాచ్‌లు లేవు'; @override - String get shortRabi2Label => r'Раби. II'; + String get noSelectedDateCalendarLabel => r'ఎంచుకున్న తేదీ లేదు'; @override - String get shortRajabLabel => r'Радж.'; + String get notEmptyDataGridFilteringLabel => r'ఖాళీ కాదు'; @override - String get shortRamadanLabel => r'ОЗУ.'; + String get notNullDataGridFilteringLabel => r'శూన్యం కాదు'; @override - String get shortSafarLabel => r'Саф.'; + String get nullDataGridFilteringLabel => r'శూన్య'; @override - String get shortShaabanLabel => r'Ша.'; + String get numberFiltersDataGridFilteringLabel => r'సంఖ్య ఫిల్టర్లు'; @override - String get shortShawwalLabel => r'Шоу.'; + String get ofDataPagerLabel => r'యొక్క'; @override - String get todayLabel => r'Сегодня'; + String get okDataGridFilteringLabel => r'అలాగే'; @override - String get weeknumberLabel => r'Неделю'; -} + String get orDataGridFilteringLabel => r'లేదా'; -/// The translations for Sinhala Sinhalese (`si`). -class SfLocalizationsSi extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSi class - const SfLocalizationsSi({ - String localeName = 'si', - }) : super( - localeName: localeName, - ); + @override + String get pagesDataPagerLabel => r'పేజీలు'; @override - String get allDayLabel => r'මුළු දවසම'; + String get passwordDialogContentLabel => + r'ఈ PDF ఫైల్‌ను తెరవడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి'; @override - String get allowedViewDayLabel => r'දිනය'; + String get passwordDialogHeaderTextLabel => r'పాస్‌వర్డ్ రక్షించబడింది'; @override - String get allowedViewMonthLabel => r'මාසික'; + String get passwordDialogHintTextLabel => r'రహస్య సంకేతం తెలపండి'; @override - String get allowedViewScheduleLabel => r'කාලසටහන'; + String get passwordDialogInvalidPasswordLabel => r'చెల్లని పాస్వర్డ్'; @override - String get allowedViewTimelineDayLabel => r'කාලරේඛා දිනය'; + String get pdfBookmarksLabel => r'బుక్‌మార్క్‌లు'; @override - String get allowedViewTimelineMonthLabel => r'කාලරේඛා මාසය'; + String get pdfEnterPageNumberLabel => r'పేజీ సంఖ్యను నమోదు చేయండి'; @override - String get allowedViewTimelineWeekLabel => r'කාලරේඛා සතිය'; + String get pdfGoToPageLabel => r'పుటకు వెళ్ళు'; @override - String get allowedViewTimelineWorkWeekLabel => r'කාලසටහන වැඩ සතිය'; + String get pdfHyperlinkContentLabel => + r'మీరు పేజీని ఇక్కడ తెరవాలనుకుంటున్నారా'; @override - String get allowedViewWeekLabel => r'සතිය'; + String get pdfHyperlinkDialogCancelLabel => r'రద్దు చేయండి'; @override - String get allowedViewWorkWeekLabel => r'වැඩ සතිය'; + String get pdfHyperlinkDialogOpenLabel => r'తెరవండి'; @override - String get daySpanCountLabel => r'දිනය'; + String get pdfHyperlinkLabel => r'వెబ్ పేజీని తెరవండి'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfInvalidPageNumberLabel => + r'దయచేసి చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfNoBookmarksLabel => r'బుక్‌మార్క్‌లు ఏవీ కనుగొనబడలేదు'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfPaginationDialogCancelLabel => r'రద్దు చేయండి'; @override - String get jumada2Label => r'ජුමාදා අල්-තානි'; + String get pdfPaginationDialogOkLabel => r'సరే'; @override - String get muharramLabel => r'මුහර්රම්'; + String get pdfPasswordDialogCancelLabel => r'రద్దు చేయండి'; @override - String get noEventsCalendarLabel => r'සිදුවීම් නැත'; + String get pdfPasswordDialogOpenLabel => r'తెరవండి'; @override - String get noSelectedDateCalendarLabel => r'තෝරාගත් දිනයක් නැත'; + String get pdfScrollStatusOfLabel => r'యొక్క'; @override - String get ofDataPagerLabel => r'වල'; + String get pdfSignaturePadDialogClearLabel => r'క్లియర్'; @override - String get pagesDataPagerLabel => r'පිටු'; + String get pdfSignaturePadDialogHeaderTextLabel => r'మీ సంతకాన్ని గీయండి'; @override - String get passwordDialogContentLabel => - r'මෙම PDF ගොනුව විවෘත කිරීමට මුරපදය ඇතුළත් කරන්න'; + String get pdfSignaturePadDialogPenColorLabel => r'పెన్ రంగు'; @override - String get passwordDialogHeaderTextLabel => r'මුරපදය ආරක්ෂිතයි'; + String get pdfSignaturePadDialogSaveLabel => r'భద్రపరచండి'; @override - String get passwordDialogHintTextLabel => r'මුරපදය ඇතුළත් කරන්න'; + String get pdfTextSelectionMenuCopyLabel => r'నకలు'; @override - String get passwordDialogInvalidPasswordLabel => r'වලංගු නොවන මුරපදය'; + String get pdfTextSelectionMenuHighlightLabel => r'హైలైట్'; @override - String get pdfBookmarksLabel => r'පිටු සලකුණු'; + String get pdfTextSelectionMenuSquigglyLabel => r'స్క్విగ్లీ'; @override - String get pdfEnterPageNumberLabel => r'පිටු අංකය ඇතුලත් කරන්න'; + String get pdfTextSelectionMenuStrikethroughLabel => r'స్ట్రైక్‌త్రూ'; @override - String get pdfGoToPageLabel => r'පිටුවට යන්න'; + String get pdfTextSelectionMenuUnderlineLabel => r'అండర్లైన్'; @override - String get pdfInvalidPageNumberLabel => r'කරුණාකර වලංගු අංකයක් ඇතුළත් කරන්න'; + String get rabi1Label => r'రబీ అల్-అవ్వల్'; @override - String get pdfNoBookmarksLabel => r'පිටු සලකුණු හමු නොවීය'; + String get rabi2Label => r'రబీ అల్-థాని'; @override - String get pdfPaginationDialogCancelLabel => r'අවලංගු කරන්න'; + String get rajabLabel => r'రజబ్'; @override - String get pdfPaginationDialogOkLabel => r'හරි'; + String get ramadanLabel => r'రంజాన్'; @override - String get pdfPasswordDialogCancelLabel => r'අවලංගු කරන්න'; + String get rowsPerPageDataPagerLabel => r'ప్రతి పేజీకి వరుసలు'; @override - String get pdfPasswordDialogOpenLabel => r'විවෘත'; + String get safarLabel => r'సఫర్'; @override - String get pdfScrollStatusOfLabel => r'වල'; + String get searchDataGridFilteringLabel => r'వెతకండి'; @override - String get rabi1Label => r'රබී අල්-අව්වල්'; + String get selectAllDataGridFilteringLabel => r'అన్ని ఎంచుకోండి'; @override - String get rabi2Label => r'රබී අල්-තානි'; + String get series => r'సిరీస్'; @override - String get rajabLabel => r'රාජාබ්'; + String get shaabanLabel => r'షాబాన్'; @override - String get ramadanLabel => r'රාමසාන්'; + String get shawwalLabel => r'షవ్వాల్'; @override - String get rowsPerPageDataPagerLabel => r'පිටුවකට පේළි'; + String get shortDhualhiLabel => r'ధుల్-హెచ్'; @override - String get safarLabel => r'සෆාර්'; + String get shortDhualqiLabel => r'ధుల్-క్యూ'; @override - String get series => r'මාලාවක්'; + String get shortJumada1Label => r'జం. I'; @override - String get shaabanLabel => r'ෂාබාන්'; + String get shortJumada2Label => r'జం. II'; @override - String get shawwalLabel => r'ෂව්වාල්'; + String get shortMuharramLabel => r'ముహ్.'; @override - String get shortDhualhiLabel => r'දුල්-එච්'; + String get shortRabi1Label => r'రబీ I'; @override - String get shortDhualqiLabel => r'දුල්-කිව්'; + String get shortRabi2Label => r'రబీ II'; @override - String get shortJumada1Label => r'ජුම්. මම'; + String get shortRajabLabel => r'రాజ్.'; @override - String get shortJumada2Label => r'ජුම්. II'; + String get shortRamadanLabel => r'రామ్'; @override - String get shortMuharramLabel => r'මුහ්'; + String get shortSafarLabel => r'సాఫ్.'; @override - String get shortRabi1Label => r'රබී. මම'; + String get shortShaabanLabel => r'శా.'; @override - String get shortRabi2Label => r'රබී. II'; + String get shortShawwalLabel => r'షా'; @override - String get shortRajabLabel => r'රාජ්.'; + String get showRowsWhereDataGridFilteringLabel => + r'అడ్డు వరుసలను ఎక్కడ చూపించు'; @override - String get shortRamadanLabel => r'RAM.'; + String get sortAToZDataGridFilteringLabel => + r'A నుండి Z వరకు క్రమబద్ధీకరించండి'; @override - String get shortSafarLabel => r'Saf.'; + String get sortAndFilterDataGridFilteringLabel => + r'క్రమబద్ధీకరించండి మరియు ఫిల్టర్ చేయండి'; @override - String get shortShaabanLabel => r'ශා.'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'పెద్దది నుండి చిన్నది వరకు క్రమబద్ధీకరించండి'; @override - String get shortShawwalLabel => r'ෂෝ.'; + String get sortNewestToOldestDataGridFilteringLabel => + r'సరికొత్తది నుండి పాతది వరకు క్రమబద్ధీకరించండి'; @override - String get todayLabel => r'අද'; + String get sortOldestToNewestDataGridFilteringLabel => + r'పాతది నుండి కొత్తది వరకు క్రమబద్ధీకరించండి'; @override - String get weeknumberLabel => r'සතිය'; -} + String get sortSmallestToLargestDataGridFilteringLabel => + r'చిన్నది నుండి పెద్దది వరకు క్రమబద్ధీకరించండి'; -/// The translations for Slovak (`sk`). -class SfLocalizationsSk extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSk class - const SfLocalizationsSk({ - String localeName = 'sk', - }) : super( - localeName: localeName, - ); + @override + String get sortZToADataGridFilteringLabel => + r'Z నుండి A వరకు క్రమబద్ధీకరించండి'; @override - String get allDayLabel => r'Celý deň'; + String get textFiltersDataGridFilteringLabel => r'టెక్స్ట్ ఫిల్టర్లు'; @override - String get allowedViewDayLabel => r'deň'; + String get todayLabel => r'ఈరోజు'; @override - String get allowedViewMonthLabel => r'mesiac'; + String get weeknumberLabel => r'వారం'; +} + +/// The translations for Thai (`th`). +class SfLocalizationsTh extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsTh class + const SfLocalizationsTh({String localeName = 'th'}) + : super(localeName: localeName); @override - String get allowedViewScheduleLabel => r'Rozvrh'; + String get afterDataGridFilteringLabel => r'หลังจาก'; @override - String get allowedViewTimelineDayLabel => r'Deň časovej osi'; + String get afterOrEqualDataGridFilteringLabel => r'หลังจากหรือเท่ากับ'; @override - String get allowedViewTimelineMonthLabel => r'Mesiac časovej osi'; + String get allDayLabel => r'ทั้งวัน'; @override - String get allowedViewTimelineWeekLabel => r'Časová os týždeň'; + String get allowedViewDayLabel => r'วัน'; @override - String get allowedViewTimelineWorkWeekLabel => r'Časová os Pracovný týždeň'; + String get allowedViewMonthLabel => r'เดือน'; @override - String get allowedViewWeekLabel => r'týždeň'; + String get allowedViewScheduleLabel => r'กำหนดการ'; @override - String get allowedViewWorkWeekLabel => r'Pracovný týždeň'; + String get allowedViewTimelineDayLabel => r'วันไทม์ไลน์'; @override - String get daySpanCountLabel => r'deň'; + String get allowedViewTimelineMonthLabel => r'ไทม์ไลน์เดือน'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get allowedViewTimelineWeekLabel => r'ไทม์ไลน์สัปดาห์'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get allowedViewTimelineWorkWeekLabel => r'ไทม์ไลน์สัปดาห์การทำงาน'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get allowedViewWeekLabel => r'สัปดาห์'; @override - String get jumada2Label => r'Jumada al-thani'; + String get allowedViewWorkWeekLabel => r'สัปดาห์การทำงาน'; @override - String get muharramLabel => r'Muharram'; + String get andDataGridFilteringLabel => r'และ'; @override - String get noEventsCalendarLabel => r'Žiadne udalosti'; + String get beforeDataGridFilteringLabel => r'ก่อน'; @override - String get noSelectedDateCalendarLabel => r'Nie je vybraný dátum'; + String get beforeOrEqualDataGridFilteringLabel => r'ก่อนหรือเท่ากับ'; @override - String get ofDataPagerLabel => r'z'; + String get beginsWithDataGridFilteringLabel => r'เริ่มต้นด้วย'; @override - String get pagesDataPagerLabel => r'stránky'; + String get cancelDataGridFilteringLabel => r'ยกเลิก'; @override - String get passwordDialogContentLabel => - r'Zadajte heslo na otvorenie tohto súboru PDF'; + String get clearFilterDataGridFilteringLabel => r'ล้างตัวกรอง'; @override - String get passwordDialogHeaderTextLabel => r'Chránené heslom'; + String get containsDataGridFilteringLabel => r'ประกอบด้วย'; @override - String get passwordDialogHintTextLabel => r'Zadajte heslo'; + String get dateFiltersDataGridFilteringLabel => r'ตัวกรองวันที่'; @override - String get passwordDialogInvalidPasswordLabel => r'nesprávne heslo'; + String get daySpanCountLabel => r'วัน'; @override - String get pdfBookmarksLabel => r'Záložky'; + String get dhualhiLabel => r'ดุอัลฮิจญะห์'; @override - String get pdfEnterPageNumberLabel => r'Zadajte číslo strany'; + String get dhualqiLabel => r'ดุอัลกิดาห์'; @override - String get pdfGoToPageLabel => r'Chod na stranu'; + String get doesNotBeginWithDataGridFilteringLabel => r'ไม่ได้ขึ้นต้นด้วย'; @override - String get pdfInvalidPageNumberLabel => r'Prosím zadajte platné číslo'; + String get doesNotContainDataGridFilteringLabel => r'ไม่มี'; @override - String get pdfNoBookmarksLabel => r'Nenašli sa žiadne záložky'; + String get doesNotEndWithDataGridFilteringLabel => r'ไม่สิ้นสุดด้วย'; @override - String get pdfPaginationDialogCancelLabel => r'ZRUŠIŤ'; + String get doesNotEqualDataGridFilteringLabel => r'ไม่เท่าเทียมกัน'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get emptyDataGridFilteringLabel => r'ว่างเปล่า'; @override - String get pdfPasswordDialogCancelLabel => r'ZRUŠIŤ'; + String get endsWithDataGridFilteringLabel => r'ลงท้ายด้วย'; @override - String get pdfPasswordDialogOpenLabel => r'OTVORENÉ'; + String get equalsDataGridFilteringLabel => r'เท่ากับ'; @override - String get pdfScrollStatusOfLabel => r'z'; + String get fromDataGridFilteringLabel => r'จาก'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get greaterThanDataGridFilteringLabel => r'มากกว่า'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get greaterThanOrEqualDataGridFilteringLabel => r'มากกว่าหรือเท่ากับ'; @override - String get rajabLabel => r'Rajab'; + String get jumada1Label => r'ญุมาดา อัลเอาวัล'; @override - String get ramadanLabel => r'Ramadán'; + String get jumada2Label => r'ญุมาดา อัล-ธานี'; @override - String get rowsPerPageDataPagerLabel => r'Počet riadkov na stránku'; + String get lessThanDataGridFilteringLabel => r'น้อยกว่า'; @override - String get safarLabel => r'Šafár'; + String get lessThanOrEqualDataGridFilteringLabel => r'น้อยกว่าหรือเท่ากับ'; @override - String get series => r'séria'; + String get muharramLabel => r'มุฮัรรอม'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get noEventsCalendarLabel => r'ไม่มีกิจกรรม'; @override - String get shawwalLabel => r'Shawwal'; + String get noMatchesDataGridFilteringLabel => r'ไม่ตรงกัน'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get noSelectedDateCalendarLabel => r'ไม่มีวันที่เลือก'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get notEmptyDataGridFilteringLabel => r'ไม่ว่างเปล่า'; @override - String get shortJumada1Label => r'Jum. ja'; + String get notNullDataGridFilteringLabel => r'ไม่เป็นโมฆะ'; @override - String get shortJumada2Label => r'Jum. II'; + String get nullDataGridFilteringLabel => r'โมฆะ'; @override - String get shortMuharramLabel => r'Muh.'; + String get numberFiltersDataGridFilteringLabel => r'ตัวกรองตัวเลข'; @override - String get shortRabi1Label => r'Rabi. ja'; + String get ofDataPagerLabel => r'ของ'; @override - String get shortRabi2Label => r'Rabi. II'; + String get okDataGridFilteringLabel => r'ตกลง'; @override - String get shortRajabLabel => r'Raj.'; + String get orDataGridFilteringLabel => r'หรือ'; @override - String get shortRamadanLabel => r'Ram.'; + String get pagesDataPagerLabel => r'หน้า'; @override - String get shortSafarLabel => r'Saf.'; + String get passwordDialogContentLabel => r'ป้อนรหัสผ่านเพื่อเปิดไฟล์ PDF นี้'; @override - String get shortShaabanLabel => r'Sha.'; + String get passwordDialogHeaderTextLabel => r'ป้องกันด้วยพาสเวิร์ด'; @override - String get shortShawwalLabel => r'Shaw.'; + String get passwordDialogHintTextLabel => r'ใส่รหัสผ่าน'; @override - String get todayLabel => r'dnes'; + String get passwordDialogInvalidPasswordLabel => r'รหัสผ่านไม่ถูกต้อง'; @override - String get weeknumberLabel => r'týždeň'; -} + String get pdfBookmarksLabel => r'ที่คั่นหนังสือ'; -/// The translations for Slovenian (`sl`). -class SfLocalizationsSl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSl class - const SfLocalizationsSl({ - String localeName = 'sl', - }) : super( - localeName: localeName, - ); + @override + String get pdfEnterPageNumberLabel => r'ใส่เลขหน้า'; @override - String get allDayLabel => r'Ves dan'; + String get pdfGoToPageLabel => r'ไปที่หน้า'; @override - String get allowedViewDayLabel => r'dan'; + String get pdfHyperlinkContentLabel => r'คุณต้องการเปิดเพจที่'; @override - String get allowedViewMonthLabel => r'mesec'; + String get pdfHyperlinkDialogCancelLabel => r'ยกเลิก'; @override - String get allowedViewScheduleLabel => r'Urnik'; + String get pdfHyperlinkDialogOpenLabel => r'เปิด'; @override - String get allowedViewTimelineDayLabel => r'Dan časovnice'; + String get pdfHyperlinkLabel => r'เปิดหน้าเว็บ'; @override - String get allowedViewTimelineMonthLabel => r'Mesec časovnice'; + String get pdfInvalidPageNumberLabel => r'โปรดป้อนหมายเลขที่ถูกต้อง'; @override - String get allowedViewTimelineWeekLabel => r'Teden časovnice'; + String get pdfNoBookmarksLabel => r'ไม่พบบุ๊กมาร์ก'; @override - String get allowedViewTimelineWorkWeekLabel => r'Časovnica Delovni teden'; + String get pdfPaginationDialogCancelLabel => r'ยกเลิก'; @override - String get allowedViewWeekLabel => r'teden'; + String get pdfPaginationDialogOkLabel => r'ตกลง'; @override - String get allowedViewWorkWeekLabel => r'Delovni teden'; + String get pdfPasswordDialogCancelLabel => r'ยกเลิก'; @override - String get daySpanCountLabel => r'dan'; + String get pdfPasswordDialogOpenLabel => r'เปิด'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get pdfScrollStatusOfLabel => r'ของ'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get pdfSignaturePadDialogClearLabel => r'ล้างข้อมูล'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get pdfSignaturePadDialogHeaderTextLabel => r'วาดลายเซ็นของคุณ'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pdfSignaturePadDialogPenColorLabel => r'ปากกาสี'; @override - String get muharramLabel => r'Muharram'; + String get pdfSignaturePadDialogSaveLabel => r'บันทึก'; @override - String get noEventsCalendarLabel => r'Brez dogodkov'; + String get rabi1Label => r'รอบิอัลเอาวัล'; @override - String get noSelectedDateCalendarLabel => r'Ni izbranega datuma'; + String get rabi2Label => r'รอบิอัล-ธานี'; @override - String get ofDataPagerLabel => r'od'; + String get rajabLabel => r'ราชาบ'; @override - String get pagesDataPagerLabel => r'strani'; + String get ramadanLabel => r'เดือนรอมฎอน'; @override - String get passwordDialogContentLabel => - r'Vnesite geslo za odpiranje te datoteke PDF'; + String get pdfTextSelectionMenuCopyLabel => r'สำเนา'; @override - String get passwordDialogHeaderTextLabel => r'Zaščiteno z geslom'; + String get pdfTextSelectionMenuHighlightLabel => r'ไฮไลท์'; @override - String get passwordDialogHintTextLabel => r'Vnesite geslo'; + String get pdfTextSelectionMenuSquigglyLabel => r'ไก่เขี่ย'; @override - String get passwordDialogInvalidPasswordLabel => r'Neveljavno geslo'; + String get pdfTextSelectionMenuStrikethroughLabel => r'ขีดทับ'; @override - String get pdfBookmarksLabel => r'Zaznamki'; + String get pdfTextSelectionMenuUnderlineLabel => r'ขีดเส้นใต้'; @override - String get pdfEnterPageNumberLabel => r'Vnesite številko strani'; + String get rowsPerPageDataPagerLabel => r'แถวต่อหน้า'; @override - String get pdfGoToPageLabel => r'Pojdi na stran'; + String get safarLabel => r'ซาฟาร์'; @override - String get pdfInvalidPageNumberLabel => r'Vnesite veljavno številko'; + String get searchDataGridFilteringLabel => r'ค้นหา'; @override - String get pdfNoBookmarksLabel => r'Najden ni bil noben zaznamek'; + String get selectAllDataGridFilteringLabel => r'เลือกทั้งหมด'; @override - String get pdfPaginationDialogCancelLabel => r'PREKLIC'; + String get series => r'ชุด'; @override - String get pdfPaginationDialogOkLabel => r'v redu'; + String get shaabanLabel => r'ชะอฺบาน'; @override - String get pdfPasswordDialogCancelLabel => r'PREKLIC'; + String get shawwalLabel => r'เชาวาล'; @override - String get pdfPasswordDialogOpenLabel => r'ODPRTO'; + String get shortDhualhiLabel => r'ดุอัล-เอช'; @override - String get pdfScrollStatusOfLabel => r'od'; + String get shortDhualqiLabel => r'ดุล-คิว'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get shortJumada1Label => r'จุ๋ม. ฉัน'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get shortJumada2Label => r'จุ๋ม. ครั้งที่สอง'; @override - String get rajabLabel => r'Radžab'; + String get shortMuharramLabel => r'มึ.'; @override - String get ramadanLabel => r'Ramadan'; + String get shortRabi1Label => r'ระบี. ฉัน'; @override - String get rowsPerPageDataPagerLabel => r'Vrstice na stran'; + String get shortRabi2Label => r'ระบี. ครั้งที่สอง'; @override - String get safarLabel => r'Safar'; + String get shortRajabLabel => r'ราชา'; @override - String get series => r'Serija'; + String get shortRamadanLabel => r'แกะ.'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shortSafarLabel => r'ปลอดภัย'; @override - String get shawwalLabel => r'Shawwal'; + String get shortShaabanLabel => r'ชา'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortShawwalLabel => r'ชอว์'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get showRowsWhereDataGridFilteringLabel => r'แสดงแถวที่'; @override - String get shortJumada1Label => r'Jum. jaz'; + String get sortAToZDataGridFilteringLabel => r'เรียง A ถึง Z'; @override - String get shortJumada2Label => r'Jum. II'; + String get sortAndFilterDataGridFilteringLabel => r'จัดเรียงและกรอง'; @override - String get shortMuharramLabel => r'Muh.'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'เรียงจากมากไปน้อย'; @override - String get shortRabi1Label => r'Rabi. jaz'; + String get sortNewestToOldestDataGridFilteringLabel => + r'เรียงลำดับใหม่ที่สุดไปเก่าที่สุด'; @override - String get shortRabi2Label => r'Rabi. II'; + String get sortOldestToNewestDataGridFilteringLabel => + r'เรียงจากเก่าสุดไปใหม่สุด'; @override - String get shortRajabLabel => r'Raj.'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'เรียงลำดับจากน้อยไปมาก'; @override - String get shortRamadanLabel => r'Oven.'; + String get sortZToADataGridFilteringLabel => r'เรียง Z ถึง A'; @override - String get shortSafarLabel => r'Saf.'; + String get textFiltersDataGridFilteringLabel => r'ตัวกรองข้อความ'; @override - String get shortShaabanLabel => r'Sha'; + String get todayLabel => r'วันนี้'; @override - String get shortShawwalLabel => r'Shaw.'; + String get weeknumberLabel => r'สัปดาห์'; +} - @override - String get todayLabel => r'danes'; +/// The translations for Tagalog (`tl`). +class SfLocalizationsTl extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsTl class + const SfLocalizationsTl({String localeName = 'tl'}) + : super(localeName: localeName); @override - String get weeknumberLabel => r'teden'; -} + String get afterDataGridFilteringLabel => r'Pagkatapos'; -/// The translations for Albanian (`sq`). -class SfLocalizationsSq extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSq class - const SfLocalizationsSq({ - String localeName = 'sq', - }) : super( - localeName: localeName, - ); + @override + String get afterOrEqualDataGridFilteringLabel => r'Pagkatapos o Pantay'; @override - String get allDayLabel => r'Gjithe diten'; + String get allDayLabel => r'Buong araw'; @override - String get allowedViewDayLabel => r'ditë'; + String get allowedViewDayLabel => r'Araw'; @override - String get allowedViewMonthLabel => r'Muaj'; + String get allowedViewMonthLabel => r'buwan'; @override - String get allowedViewScheduleLabel => r'Orari'; + String get allowedViewScheduleLabel => r'Iskedyul'; @override - String get allowedViewTimelineDayLabel => r'Dita e afatit kohor'; + String get allowedViewTimelineDayLabel => r'Araw ng Timeline'; @override - String get allowedViewTimelineMonthLabel => r'Afati kohor Muaji'; + String get allowedViewTimelineMonthLabel => r'Buwan ng Timeline'; @override - String get allowedViewTimelineWeekLabel => r'Java e afatit kohor'; + String get allowedViewTimelineWeekLabel => r'Linggo ng Timeline'; @override - String get allowedViewTimelineWorkWeekLabel => r'Afati kohor Java e punës'; + String get allowedViewTimelineWorkWeekLabel => + r'Linggo ng Trabaho sa Timeline'; @override - String get allowedViewWeekLabel => r'javë'; + String get allowedViewWeekLabel => r'Linggo'; @override - String get allowedViewWorkWeekLabel => r'Java e punës'; + String get allowedViewWorkWeekLabel => r'Linggo ng trabaho'; @override - String get daySpanCountLabel => r'ditë'; + String get andDataGridFilteringLabel => r'At'; @override - String get dhualhiLabel => r'Dhul Hixhe'; + String get beforeDataGridFilteringLabel => r'Bago'; @override - String get dhualqiLabel => r'Dhul Kida'; + String get beforeOrEqualDataGridFilteringLabel => r'Bago O Kapantay'; @override - String get jumada1Label => r'Xhumada el-evvel'; + String get beginsWithDataGridFilteringLabel => r'Nagsisimula sa'; @override - String get jumada2Label => r'Xhumada al-thani'; + String get cancelDataGridFilteringLabel => r'Kanselahin'; @override - String get muharramLabel => r'Muharrem'; + String get clearFilterDataGridFilteringLabel => r'I-clear ang Filter'; @override - String get noEventsCalendarLabel => r'Asnjë ngjarje'; + String get containsDataGridFilteringLabel => r'Naglalaman'; @override - String get noSelectedDateCalendarLabel => r'Asnjë datë e zgjedhur'; + String get dateFiltersDataGridFilteringLabel => r'Mga Filter ng Petsa'; @override - String get ofDataPagerLabel => r'e'; + String get daySpanCountLabel => r'Araw'; @override - String get pagesDataPagerLabel => r'faqet'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get passwordDialogContentLabel => - r'Futni fjalëkalimin për të hapur këtë skedar PDF'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get passwordDialogHeaderTextLabel => r'Fjalëkalimi i mbrojtur'; + String get doesNotBeginWithDataGridFilteringLabel => r'Hindi Nagsisimula Sa'; @override - String get passwordDialogHintTextLabel => r'Shkruani fjalëkalimin'; + String get doesNotContainDataGridFilteringLabel => r'Hindi Naglalaman'; @override - String get passwordDialogInvalidPasswordLabel => r'Fjalëkalim i pavlefshëm'; + String get doesNotEndWithDataGridFilteringLabel => r'Hindi Nagtatapos Sa'; @override - String get pdfBookmarksLabel => r'Faqerojtësit'; + String get doesNotEqualDataGridFilteringLabel => r'Ay hindi katumbas ng'; @override - String get pdfEnterPageNumberLabel => r'Fut numrin e faqes'; + String get emptyDataGridFilteringLabel => r'Walang laman'; @override - String get pdfGoToPageLabel => r'Shkoni në faqe'; + String get endsWithDataGridFilteringLabel => r'Nagtatapos Sa'; @override - String get pdfInvalidPageNumberLabel => - r'Ju lutemi shkruani një numër të vlefshëm'; + String get equalsDataGridFilteringLabel => r'katumbas'; @override - String get pdfNoBookmarksLabel => r'Nuk u gjetën faqeshënues'; + String get fromDataGridFilteringLabel => r'Mula sa'; @override - String get pdfPaginationDialogCancelLabel => r'ANULON'; + String get greaterThanDataGridFilteringLabel => r'Mahigit sa'; @override - String get pdfPaginationDialogOkLabel => r'Ne rregull'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Higit sa O Katumbas'; @override - String get pdfPasswordDialogCancelLabel => r'ANULON'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get pdfPasswordDialogOpenLabel => r'HAPUR'; + String get jumada2Label => r'Jumada al-thani'; @override - String get pdfScrollStatusOfLabel => r'e'; + String get lessThanDataGridFilteringLabel => r'Mas mababa sa'; @override - String get rabi1Label => r'Rabi' "'" r'ul-evvel'; + String get lessThanOrEqualDataGridFilteringLabel => + r'Mas Mababa sa O Katumbas'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get muharramLabel => r'Muharram'; @override - String get rajabLabel => r'Rexheb'; + String get noEventsCalendarLabel => r'Walang mga kaganapan'; @override - String get ramadanLabel => r'Ramazani'; + String get noMatchesDataGridFilteringLabel => r'Walang tugma'; @override - String get rowsPerPageDataPagerLabel => r'Rreshtat për faqe'; + String get noSelectedDateCalendarLabel => r'Walang napiling petsa'; @override - String get safarLabel => r'Safar'; + String get notEmptyDataGridFilteringLabel => r'Hindi Walang laman'; @override - String get series => r'Seria'; + String get notNullDataGridFilteringLabel => r'Hindi Null'; @override - String get shaabanLabel => r'Sha' "'" r'ban'; + String get nullDataGridFilteringLabel => r'Wala'; @override - String get shawwalLabel => r'Shevali'; + String get numberFiltersDataGridFilteringLabel => r'Mga Filter ng Numero'; @override - String get shortDhualhiLabel => r'Dhul-H'; + String get ofDataPagerLabel => r'ng'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-K'; + String get okDataGridFilteringLabel => r'OK'; @override - String get shortJumada1Label => r'Jum. I'; + String get orDataGridFilteringLabel => r'O kaya'; @override - String get shortJumada2Label => r'Jum. II'; + String get pagesDataPagerLabel => r'mga pahina'; @override - String get shortMuharramLabel => r'Muh.'; + String get passwordDialogContentLabel => + r'Ilagay ang password para buksan ang PDF file na ito'; @override - String get shortRabi1Label => r'Rabi. I'; + String get passwordDialogHeaderTextLabel => r'Pinoprotektahan ng Password'; @override - String get shortRabi2Label => r'Rabi. II'; + String get passwordDialogHintTextLabel => r'Ilagay ang password'; @override - String get shortRajabLabel => r'Raj.'; + String get passwordDialogInvalidPasswordLabel => r'di wastong password'; @override - String get shortRamadanLabel => r'Ram.'; + String get pdfBookmarksLabel => r'Mga bookmark'; @override - String get shortSafarLabel => r'Saf.'; + String get pdfEnterPageNumberLabel => r'Ipasok ang numero ng pahina'; @override - String get shortShaabanLabel => r'Sha.'; + String get pdfGoToPageLabel => r'Pumunta sa pahina'; @override - String get shortShawwalLabel => r'Shaw.'; + String get pdfHyperlinkContentLabel => r'Gusto mo bang buksan ang pahina sa'; @override - String get todayLabel => r'Sot'; + String get pdfHyperlinkDialogCancelLabel => r'Kanselahin'; @override - String get weeknumberLabel => r'javë'; -} + String get pdfHyperlinkDialogOpenLabel => r'Bukas'; -/// The translations for Serbian (`sr`). -class SfLocalizationsSr extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSr class - const SfLocalizationsSr({ - String localeName = 'sr', - }) : super( - localeName: localeName, - ); + @override + String get pdfHyperlinkLabel => r'Buksan ang Web Page'; @override - String get allDayLabel => r'Цео дан'; + String get pdfInvalidPageNumberLabel => + r'Mangyaring magpasok ng wastong numero'; @override - String get allowedViewDayLabel => r'Дан'; + String get pdfNoBookmarksLabel => r'Walang nakitang mga bookmark'; @override - String get allowedViewMonthLabel => r'Месец дана'; + String get pdfPaginationDialogCancelLabel => r'Kanselahin'; @override - String get allowedViewScheduleLabel => r'Распоред'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get allowedViewTimelineDayLabel => r'Дан временске линије'; + String get pdfPasswordDialogCancelLabel => r'Kanselahin'; @override - String get allowedViewTimelineMonthLabel => r'Месец временске линије'; + String get pdfPasswordDialogOpenLabel => r'Bukas'; @override - String get allowedViewTimelineWeekLabel => r'Тимелине Веек'; + String get pdfScrollStatusOfLabel => r'ng'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Временски оквир Радна недеља'; + String get pdfSignaturePadDialogClearLabel => r'Maliwanag'; @override - String get allowedViewWeekLabel => r'Недеља'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Iguhit ang iyong lagda'; @override - String get allowedViewWorkWeekLabel => r'Радна недеља'; + String get pdfSignaturePadDialogPenColorLabel => r'Kulay ng Panulat'; @override - String get daySpanCountLabel => r'Дан'; + String get pdfSignaturePadDialogSaveLabel => r'Mag-ipon'; @override - String get dhualhiLabel => r'Дху ал-Хијјах'; + String get pdfTextSelectionMenuCopyLabel => r'Kopya'; @override - String get dhualqiLabel => r'Дху ал-Ки' "'" r'дах'; + String get pdfTextSelectionMenuHighlightLabel => r'I-highlight'; @override - String get jumada1Label => r'Јумада ал-аввал'; + String get pdfTextSelectionMenuSquigglyLabel => r'Guhit-guhit'; @override - String get jumada2Label => r'Јумада ал-тхани'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Strikethrough'; @override - String get muharramLabel => r'Мухаррам'; + String get pdfTextSelectionMenuUnderlineLabel => r'Salungguhit'; @override - String get noEventsCalendarLabel => r'Нема догађаја'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get noSelectedDateCalendarLabel => r'Нема изабраног датума'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get ofDataPagerLabel => r'оф'; + String get rajabLabel => r'Rajab'; @override - String get pagesDataPagerLabel => r'странице'; + String get ramadanLabel => r'Ramadan'; @override - String get passwordDialogContentLabel => - r'Унесите лозинку да бисте отворили ову ПДФ датотеку'; + String get rowsPerPageDataPagerLabel => r'Mga hilera bawat pahina'; @override - String get passwordDialogHeaderTextLabel => r'Заштићено лозинком'; + String get safarLabel => r'Safar'; @override - String get passwordDialogHintTextLabel => r'Унесите лозинку'; + String get searchDataGridFilteringLabel => r'Maghanap'; @override - String get passwordDialogInvalidPasswordLabel => r'Неважећа лозинка'; + String get selectAllDataGridFilteringLabel => r'Piliin lahat'; @override - String get pdfBookmarksLabel => r'обележивача'; + String get series => r'Serye'; @override - String get pdfEnterPageNumberLabel => r'Унесите број странице'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get pdfGoToPageLabel => r'Иди на страну'; + String get shawwalLabel => r'Shawwal'; @override - String get pdfInvalidPageNumberLabel => r'Молимо Вас да унесете важећи број'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get pdfNoBookmarksLabel => r'Нема пронађених обележивача'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get pdfPaginationDialogCancelLabel => r'ПОНИШТИТИ, ОТКАЗАТИ'; + String get shortJumada1Label => r'Jum. ako'; @override - String get pdfPaginationDialogOkLabel => r'У реду'; + String get shortJumada2Label => r'Jum. II'; @override - String get pdfPasswordDialogCancelLabel => r'ПОНИШТИТИ, ОТКАЗАТИ'; + String get shortMuharramLabel => r'Muh.'; @override - String get pdfPasswordDialogOpenLabel => r'ОПЕН'; + String get shortRabi1Label => r'Rabi. ako'; @override - String get pdfScrollStatusOfLabel => r'оф'; + String get shortRabi2Label => r'Rabi. II'; @override - String get rabi1Label => r'Раби' "'" r' ал-аввал'; + String get shortRajabLabel => r'Raj.'; @override - String get rabi2Label => r'Раби' "'" r' ал-тхани'; + String get shortRamadanLabel => r'Ram.'; @override - String get rajabLabel => r'Раџаб'; + String get shortSafarLabel => r'Saf.'; @override - String get ramadanLabel => r'Рамазана'; + String get shortShaabanLabel => r'Sha.'; @override - String get rowsPerPageDataPagerLabel => r'Редова по страници'; + String get shortShawwalLabel => r'Shaw.'; @override - String get safarLabel => r'Сафар'; + String get showRowsWhereDataGridFilteringLabel => + r'Ipakita ang mga hilera kung saan'; @override - String get series => r'Серије'; + String get sortAToZDataGridFilteringLabel => r'Pagbukud-bukurin A hanggang Z'; @override - String get shaabanLabel => r'Сха' "'" r'абан'; + String get sortAndFilterDataGridFilteringLabel => + r'Pagbukud-bukurin at Salain'; @override - String get shawwalLabel => r'Схаввал'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakamalaki Hanggang Pinakamaliit'; @override - String get shortDhualhiLabel => r'Дху' "'" r'л-Х'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakabago Hanggang sa Pinakaluma'; @override - String get shortDhualqiLabel => r'Дху' "'" r'л-К'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakamatanda Hanggang Pinakabago'; @override - String get shortJumada1Label => r'Јум. И'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Pagbukud-bukurin ang Pinakamaliit Hanggang Pinakamalaki'; @override - String get shortJumada2Label => r'Јум. ИИ'; + String get sortZToADataGridFilteringLabel => + r'Pagbukud-bukurin ang Z Hanggang A'; @override - String get shortMuharramLabel => r'Мух.'; + String get textFiltersDataGridFilteringLabel => r'Mga Filter ng Teksto'; @override - String get shortRabi1Label => r'Раби. И'; + String get todayLabel => r'Ngayong araw'; @override - String get shortRabi2Label => r'Раби. ИИ'; + String get weeknumberLabel => r'Linggo'; +} + +/// The translations for Turkish (`tr`). +class SfLocalizationsTr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsTr class + const SfLocalizationsTr({String localeName = 'tr'}) + : super(localeName: localeName); @override - String get shortRajabLabel => r'Рај.'; + String get afterDataGridFilteringLabel => r'Sonrasında'; @override - String get shortRamadanLabel => r'РАМ.'; + String get afterOrEqualDataGridFilteringLabel => r'Sonra Veya Eşittir'; @override - String get shortSafarLabel => r'Саф.'; + String get allDayLabel => r'Tüm gün'; @override - String get shortShaabanLabel => r'Сха.'; + String get allowedViewDayLabel => r'Gün'; @override - String get shortShawwalLabel => r'Схав.'; + String get allowedViewMonthLabel => r'Ay'; @override - String get todayLabel => r'Данас'; + String get allowedViewScheduleLabel => r'Takvim'; @override - String get weeknumberLabel => r'Недеља'; -} + String get allowedViewTimelineDayLabel => r'Zaman Çizelgesi Günü'; -/// The translations for Serbian, using the Cyrillic script (`sr_Cyrl`). -class SfLocalizationsSrCyrl extends SfLocalizationsSr { - /// Creating an argument constructor of SfLocalizationsSrCyrl class - const SfLocalizationsSrCyrl({ - String localeName = 'sr_Cyrl', - }) : super( - localeName: localeName, - ); -} + @override + String get allowedViewTimelineMonthLabel => r'Zaman Çizelgesi Ayı'; -/// The translations for Swedish (`sv`). -class SfLocalizationsSv extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSv class - const SfLocalizationsSv({ - String localeName = 'sv', - }) : super( - localeName: localeName, - ); + @override + String get allowedViewTimelineWeekLabel => r'Zaman Çizelgesi Haftası'; @override - String get allDayLabel => r'Hela dagen'; + String get allowedViewTimelineWorkWeekLabel => + r'Zaman Çizelgesi Çalışma Haftası'; @override - String get allowedViewDayLabel => r'Dag'; + String get allowedViewWeekLabel => r'Hafta'; @override - String get allowedViewMonthLabel => r'Månad'; + String get allowedViewWorkWeekLabel => r'Çalışma Haftası'; @override - String get allowedViewScheduleLabel => r'Schema'; + String get andDataGridFilteringLabel => r'Ve'; @override - String get allowedViewTimelineDayLabel => r'Tidslinjedagen'; + String get beforeDataGridFilteringLabel => r'Önce'; @override - String get allowedViewTimelineMonthLabel => r'Tidslinje månad'; + String get beforeOrEqualDataGridFilteringLabel => r'Önce veya Eşit'; @override - String get allowedViewTimelineWeekLabel => r'Tidslinjevecka'; + String get beginsWithDataGridFilteringLabel => r'İle başlar'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbetsvecka'; + String get cancelDataGridFilteringLabel => r'İptal'; @override - String get allowedViewWeekLabel => r'Vecka'; + String get clearFilterDataGridFilteringLabel => r'Temiz filtre'; @override - String get allowedViewWorkWeekLabel => r'Arbetsvecka'; + String get containsDataGridFilteringLabel => r'içerir'; @override - String get daySpanCountLabel => r'Dag'; + String get dateFiltersDataGridFilteringLabel => r'Tarih Filtreleri'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get daySpanCountLabel => r'Gün'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualhiLabel => r'Zilhicce'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get jumada2Label => r'Jumada al-thani'; + String get doesNotBeginWithDataGridFilteringLabel => r'İle Başlamıyor'; @override - String get muharramLabel => r'Muharram'; + String get doesNotContainDataGridFilteringLabel => r'İçermiyor'; @override - String get noEventsCalendarLabel => r'Inga händelser'; + String get doesNotEndWithDataGridFilteringLabel => r'ile bitmiyor'; @override - String get noSelectedDateCalendarLabel => r'Inget valt datum'; + String get doesNotEqualDataGridFilteringLabel => r'Eşit değil'; @override - String get ofDataPagerLabel => r'av'; + String get emptyDataGridFilteringLabel => r'Boş'; @override - String get pagesDataPagerLabel => r'sidor'; + String get endsWithDataGridFilteringLabel => r'ile biter'; @override - String get passwordDialogContentLabel => - r'Ange lösenordet för att öppna denna PDF-fil'; + String get equalsDataGridFilteringLabel => r'eşittir'; @override - String get passwordDialogHeaderTextLabel => r'Lösenord skyddat'; + String get fromDataGridFilteringLabel => r'İtibaren'; @override - String get passwordDialogHintTextLabel => r'Skriv in lösenord'; + String get greaterThanDataGridFilteringLabel => r'Daha büyük'; @override - String get passwordDialogInvalidPasswordLabel => r'felaktigt lösenord'; + String get greaterThanOrEqualDataGridFilteringLabel => + r'Büyüktür veya Eşittir'; @override - String get pdfBookmarksLabel => r'Bokmärken'; + String get jumada1Label => r'Jumada al-evvel'; @override - String get pdfEnterPageNumberLabel => r'Ange sidnummer'; + String get jumada2Label => r'Jumada al-thani'; @override - String get pdfGoToPageLabel => r'Gå till sidan'; + String get lessThanDataGridFilteringLabel => r'Daha az'; @override - String get pdfInvalidPageNumberLabel => - r'var vänlig skriv in ett giltigt nummer'; + String get lessThanOrEqualDataGridFilteringLabel => r'Az veya eşit'; @override - String get pdfNoBookmarksLabel => r'Inga bokmärken hittades'; + String get muharramLabel => r'Muharrem'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULLERA'; + String get noEventsCalendarLabel => r'Olay yok'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get noMatchesDataGridFilteringLabel => r'Eşleşme yok'; @override - String get pdfPasswordDialogCancelLabel => r'ANNULLERA'; + String get noSelectedDateCalendarLabel => r'Tarih seçilmedi'; @override - String get pdfPasswordDialogOpenLabel => r'ÖPPET'; + String get notEmptyDataGridFilteringLabel => r'Boş değil'; @override - String get pdfScrollStatusOfLabel => r'av'; + String get notNullDataGridFilteringLabel => r'Geçersiz değil'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get nullDataGridFilteringLabel => r'Hükümsüz'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get numberFiltersDataGridFilteringLabel => r'Sayı Filtreleri'; @override - String get rajabLabel => r'Rajab'; + String get ofDataPagerLabel => r'nın-nin'; @override - String get ramadanLabel => r'Ramadan'; + String get okDataGridFilteringLabel => r'TAMAM'; @override - String get rowsPerPageDataPagerLabel => r'Rader per sida'; + String get orDataGridFilteringLabel => r'Veya'; @override - String get safarLabel => r'Safar'; + String get pagesDataPagerLabel => r'sayfalar'; @override - String get series => r'Serier'; + String get passwordDialogContentLabel => + r'Bu PDF dosyasını açmak için şifreyi girin'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get passwordDialogHeaderTextLabel => r'Şifre korumalı'; @override - String get shawwalLabel => r'Shawwal'; + String get passwordDialogHintTextLabel => r'Şifre girin'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get passwordDialogInvalidPasswordLabel => r'geçersiz şifre'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get pdfBookmarksLabel => r'Yer imleri'; @override - String get shortJumada1Label => r'Jum. jag'; + String get pdfEnterPageNumberLabel => r'sayfa numarasını girin'; @override - String get shortJumada2Label => r'Jum. II'; + String get pdfGoToPageLabel => r'Sayfaya git'; @override - String get shortMuharramLabel => r'Muh.'; + String get pdfHyperlinkContentLabel => + r'Sayfayı şu adreste açmak istiyor musunuz?'; @override - String get shortRabi1Label => r'Rabi. jag'; + String get pdfHyperlinkDialogCancelLabel => r'İptal Et'; @override - String get shortRabi2Label => r'Rabi. II'; + String get pdfHyperlinkDialogOpenLabel => r'Aç'; @override - String get shortRajabLabel => r'Raj.'; + String get pdfHyperlinkLabel => r'Web Sayfasını Aç'; @override - String get shortRamadanLabel => r'Bagge.'; + String get pdfInvalidPageNumberLabel => + r'Lütfen geçerli bir sayfa numarası girin'; @override - String get shortSafarLabel => r'Saf.'; + String get pdfNoBookmarksLabel => r'Yer işareti bulunamadı'; @override - String get shortShaabanLabel => r'Sha.'; + String get pdfPaginationDialogCancelLabel => r'İptal Et'; @override - String get shortShawwalLabel => r'Shaw.'; + String get pdfPaginationDialogOkLabel => r'TAMAM'; @override - String get todayLabel => r'I dag'; + String get pdfPasswordDialogCancelLabel => r'İptal'; @override - String get weeknumberLabel => r'Vecka'; -} + String get pdfPasswordDialogOpenLabel => r'Aç'; -/// The translations for Swahili (`sw`). -class SfLocalizationsSw extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsSw class - const SfLocalizationsSw({ - String localeName = 'sw', - }) : super( - localeName: localeName, - ); + @override + String get pdfScrollStatusOfLabel => r'nın-nin'; @override - String get allDayLabel => r'Siku nzima'; + String get pdfSignaturePadDialogClearLabel => r'Temizle'; @override - String get allowedViewDayLabel => r'Siku'; + String get pdfSignaturePadDialogHeaderTextLabel => r'imzanı çiz'; @override - String get allowedViewMonthLabel => r'Mwezi'; + String get pdfSignaturePadDialogPenColorLabel => r'Kalem Rengi'; @override - String get allowedViewScheduleLabel => r'Ratiba'; + String get pdfSignaturePadDialogSaveLabel => r'Kaydet'; @override - String get allowedViewTimelineDayLabel => r'Siku ya Ratiba'; + String get pdfTextSelectionMenuCopyLabel => r'Kopyala'; @override - String get allowedViewTimelineMonthLabel => r'Muda wa Mwezi'; + String get pdfTextSelectionMenuHighlightLabel => r'Vurgulamak'; @override - String get allowedViewTimelineWeekLabel => r'Wiki ya Ratiba'; + String get pdfTextSelectionMenuSquigglyLabel => r'Dalgalı'; @override - String get allowedViewTimelineWorkWeekLabel => r'Wiki ya Kazi ya Ratiba'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Üstü çizili'; @override - String get allowedViewWeekLabel => r'Wiki'; + String get pdfTextSelectionMenuUnderlineLabel => r'Altını çizmek'; @override - String get allowedViewWorkWeekLabel => r'Wiki ya Kazi'; + String get rabi1Label => + r'Rabi' + "'" + r'ul-evvel'; @override - String get daySpanCountLabel => r'Siku'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get rajabLabel => r'Recep'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get ramadanLabel => r'Ramazan'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get rowsPerPageDataPagerLabel => r'Sayfa başına satır'; @override - String get jumada2Label => r'Jumada al-thani'; + String get safarLabel => r'safar'; @override - String get muharramLabel => r'Muharram'; + String get searchDataGridFilteringLabel => r'Arama'; @override - String get noEventsCalendarLabel => r'Hakuna matukio'; + String get selectAllDataGridFilteringLabel => r'Hepsini seç'; @override - String get noSelectedDateCalendarLabel => r'Hakuna tarehe iliyochaguliwa'; + String get series => r'Diziler'; @override - String get ofDataPagerLabel => r'ya'; + String get shaabanLabel => r'Şaban'; @override - String get pagesDataPagerLabel => r'kurasa'; + String get shawwalLabel => r'Şevval'; @override - String get passwordDialogContentLabel => - r'Weka nenosiri ili kufungua faili hii ya PDF'; + String get shortDhualhiLabel => r'Zül-H'; @override - String get passwordDialogHeaderTextLabel => r'Nenosiri Limelindwa'; + String get shortDhualqiLabel => r'Zül-Q'; @override - String get passwordDialogHintTextLabel => r'Weka Nenosiri'; + String get shortJumada1Label => r'Cuma. ben'; @override - String get passwordDialogInvalidPasswordLabel => r'Nenosiri batili'; + String get shortJumada2Label => r'Cuma. III'; @override - String get pdfBookmarksLabel => r'Alamisho'; + String get shortMuharramLabel => r'Muh.'; @override - String get pdfEnterPageNumberLabel => r'Ingiza nambari ya ukurasa'; + String get shortRabi1Label => r'Rabi. ben'; @override - String get pdfGoToPageLabel => r'Nenda kwa ukurasa'; + String get shortRabi2Label => r'Rabi. III'; @override - String get pdfInvalidPageNumberLabel => r'Tafadhali weka nambari halali'; + String get shortRajabLabel => r'Raj.'; @override - String get pdfNoBookmarksLabel => r'Hakuna alamisho zilizopatikana'; + String get shortRamadanLabel => r'Veri deposu.'; @override - String get pdfPaginationDialogCancelLabel => r'GHAIRI'; + String get shortSafarLabel => r'Saf.'; @override - String get pdfPaginationDialogOkLabel => r'sawa'; + String get shortShaabanLabel => r'Şa.'; @override - String get pdfPasswordDialogCancelLabel => r'GHAIRI'; + String get shortShawwalLabel => r'Shaw.'; @override - String get pdfPasswordDialogOpenLabel => r'FUNGUA'; + String get showRowsWhereDataGridFilteringLabel => r'satırları göster'; @override - String get pdfScrollStatusOfLabel => r'ya'; + String get sortAToZDataGridFilteringLabel => + r'A' + "'" + r'dan Z' + "'" + r'ye Sırala'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get sortAndFilterDataGridFilteringLabel => r'Sırala ve Filtrele'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Büyükten Küçüğe Sırala'; @override - String get rajabLabel => r'Rajab'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Yeniden En Eskiye Sırala'; @override - String get ramadanLabel => r'Ramadhani'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Eskiden Yeniye Sırala'; @override - String get rowsPerPageDataPagerLabel => r'Safu kwa kila ukurasa'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Küçükten Büyüğe Sırala'; @override - String get safarLabel => r'Safar'; + String get sortZToADataGridFilteringLabel => + r'Z' + "'" + r'den A' + "'" + r'ya Sırala'; @override - String get series => r'Msururu'; + String get textFiltersDataGridFilteringLabel => r'Metin Filtreleri'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get todayLabel => r'Bugün'; @override - String get shawwalLabel => r'Shawwal'; + String get weeknumberLabel => r'Hafta'; +} + +/// The translations for Ukrainian (`uk`). +class SfLocalizationsUk extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsUk class + const SfLocalizationsUk({String localeName = 'uk'}) + : super(localeName: localeName); @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get afterDataGridFilteringLabel => r'після'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get afterOrEqualDataGridFilteringLabel => r'Після або дорівнює'; @override - String get shortJumada1Label => r'Jum. I'; + String get allDayLabel => r'Весь день'; @override - String get shortJumada2Label => r'Jum. II'; + String get allowedViewDayLabel => r'День'; @override - String get shortMuharramLabel => r'Muh.'; + String get allowedViewMonthLabel => r'Місяць'; @override - String get shortRabi1Label => r'Rabi. I'; + String get allowedViewScheduleLabel => r'Розклад'; @override - String get shortRabi2Label => r'Rabi. II'; + String get allowedViewTimelineDayLabel => r'День хронології'; @override - String get shortRajabLabel => r'Raj.'; + String get allowedViewTimelineMonthLabel => r'Місяць хронології'; @override - String get shortRamadanLabel => r'Ram.'; + String get allowedViewTimelineWeekLabel => r'Тиждень хронології'; @override - String get shortSafarLabel => r'Saf.'; + String get allowedViewTimelineWorkWeekLabel => r'Графік робочого тижня'; @override - String get shortShaabanLabel => r'Sha.'; + String get allowedViewWeekLabel => r'Тиждень'; @override - String get shortShawwalLabel => r'Shaw.'; + String get allowedViewWorkWeekLabel => r'Робочий тиждень'; @override - String get todayLabel => r'Leo'; + String get andDataGridFilteringLabel => r'І'; @override - String get weeknumberLabel => r'Wiki'; -} + String get beforeDataGridFilteringLabel => r'Раніше'; -/// The translations for Tamil (`ta`). -class SfLocalizationsTa extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsTa class - const SfLocalizationsTa({ - String localeName = 'ta', - }) : super( - localeName: localeName, - ); + @override + String get beforeOrEqualDataGridFilteringLabel => r'До або дорівнює'; @override - String get allDayLabel => r'நாள் முழுவதும்'; + String get beginsWithDataGridFilteringLabel => r'Починається з'; @override - String get allowedViewDayLabel => r'நாள்'; + String get cancelDataGridFilteringLabel => r'Скасувати'; @override - String get allowedViewMonthLabel => r'மாதம்'; + String get clearFilterDataGridFilteringLabel => r'Очистити фільтр'; @override - String get allowedViewScheduleLabel => r'அட்டவணை'; + String get containsDataGridFilteringLabel => r'Містить'; @override - String get allowedViewTimelineDayLabel => r'காலவரிசை நாள்'; + String get dateFiltersDataGridFilteringLabel => r'Фільтри дати'; @override - String get allowedViewTimelineMonthLabel => r'காலவரிசை மாதம்'; + String get daySpanCountLabel => r'День'; @override - String get allowedViewTimelineWeekLabel => r'காலவரிசை வாரம்'; + String get dhualhiLabel => r'Зу аль-Хіджа'; @override - String get allowedViewTimelineWorkWeekLabel => r'காலக்கெடு வேலை வாரம்'; + String get dhualqiLabel => r'Зу аль-Кіда'; @override - String get allowedViewWeekLabel => r'வாரம்'; + String get doesNotBeginWithDataGridFilteringLabel => r'Не починається з'; @override - String get allowedViewWorkWeekLabel => r'வேலை வாரம்'; + String get doesNotContainDataGridFilteringLabel => r'Не містить'; @override - String get daySpanCountLabel => r'நாள்'; + String get doesNotEndWithDataGridFilteringLabel => r'Не закінчується'; @override - String get dhualhiLabel => r'து அல்-ஹிஜ்ஜா'; + String get doesNotEqualDataGridFilteringLabel => r'Не дорівнює'; @override - String get dhualqiLabel => r'து அல்-கிடா'; + String get emptyDataGridFilteringLabel => r'Порожній'; @override - String get jumada1Label => r'ஜுமாதா அல்-அவ்வல்'; + String get endsWithDataGridFilteringLabel => r'Закінчується на'; @override - String get jumada2Label => r'ஜுமாதா அல்-தானி'; + String get equalsDataGridFilteringLabel => r'Дорівнює'; @override - String get muharramLabel => r'முஹர்ரம்'; + String get fromDataGridFilteringLabel => r'Від'; @override - String get noEventsCalendarLabel => r'நிகழ்வுகள் இல்லை'; + String get greaterThanDataGridFilteringLabel => r'більше ніж'; @override - String get noSelectedDateCalendarLabel => r'தேர்ந்தெடுக்கப்பட்ட தேதி இல்லை'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Більше або дорівнює'; @override - String get ofDataPagerLabel => r'இன்'; + String get jumada1Label => r'Джумада аль-авваль'; @override - String get pagesDataPagerLabel => r'பக்கங்கள்'; + String get jumada2Label => r'Джумада аль-тані'; @override - String get passwordDialogContentLabel => - r'இந்த PDF கோப்பைத் திறக்க கடவுச்சொல்லை உள்ளிடவும்'; + String get lessThanDataGridFilteringLabel => r'Менше ніж'; @override - String get passwordDialogHeaderTextLabel => r'கடவுச்சொல் பாதுகாக்கப்பட்டது'; + String get lessThanOrEqualDataGridFilteringLabel => r'Менше або дорівнює'; @override - String get passwordDialogHintTextLabel => r'கடவுச்சொல்லை உள்ளிடவும்'; + String get muharramLabel => r'Мухаррам'; @override - String get passwordDialogInvalidPasswordLabel => r'தவறான கடவுச்சொல்'; + String get noEventsCalendarLabel => r'Подій немає'; @override - String get pdfBookmarksLabel => r'புக்மார்க்குகள்'; + String get noMatchesDataGridFilteringLabel => r'Немає збігів'; @override - String get pdfEnterPageNumberLabel => r'பக்க எண்ணை உள்ளிடவும்'; + String get noSelectedDateCalendarLabel => r'Дата не вибрана'; @override - String get pdfGoToPageLabel => r'பக்கத்திற்கு செல்'; + String get notEmptyDataGridFilteringLabel => r'Не порожній'; @override - String get pdfInvalidPageNumberLabel => r'சரியான எண்ணை உள்ளிடவும்'; + String get notNullDataGridFilteringLabel => r'Не Null'; @override - String get pdfNoBookmarksLabel => r'புக்மார்க்குகள் எதுவும் இல்லை'; + String get nullDataGridFilteringLabel => r'Нуль'; @override - String get pdfPaginationDialogCancelLabel => r'ரத்துசெய்'; + String get numberFiltersDataGridFilteringLabel => r'Числові фільтри'; @override - String get pdfPaginationDialogOkLabel => r'சரி'; + String get ofDataPagerLabel => r'з'; @override - String get pdfPasswordDialogCancelLabel => r'ரத்துசெய்'; + String get okDataGridFilteringLabel => r'в порядку'; @override - String get pdfPasswordDialogOpenLabel => r'திறந்த'; + String get orDataGridFilteringLabel => r'Або'; @override - String get pdfScrollStatusOfLabel => r'இன்'; + String get pagesDataPagerLabel => r'сторінки'; @override - String get rabi1Label => r'ரபி அல் அவ்வல்'; + String get passwordDialogContentLabel => + r'Введіть пароль, щоб відкрити цей файл PDF'; @override - String get rabi2Label => r'ரபி அல்-தானி'; + String get passwordDialogHeaderTextLabel => r'Захищено паролем'; @override - String get rajabLabel => r'ரஜப்'; + String get passwordDialogHintTextLabel => r'Введіть пароль'; @override - String get ramadanLabel => r'ரமலான்'; + String get passwordDialogInvalidPasswordLabel => r'Невірний пароль'; @override - String get rowsPerPageDataPagerLabel => r'ஒரு பக்கத்திற்கு வரிசைகள்'; + String get pdfBookmarksLabel => r'Закладки'; @override - String get safarLabel => r'சஃபர்'; + String get pdfEnterPageNumberLabel => r'Введіть номер сторінки'; @override - String get series => r'தொடர்'; + String get pdfGoToPageLabel => r'Перейти на сторінку'; @override - String get shaabanLabel => r'ஷஅபான்'; + String get pdfHyperlinkContentLabel => r'Хочете відкрити сторінку за адресою'; @override - String get shawwalLabel => r'ஷவ்வால்'; + String get pdfHyperlinkDialogCancelLabel => r'СКАСУВАТИ'; @override - String get shortDhualhiLabel => r'துல்-எச்'; + String get pdfHyperlinkDialogOpenLabel => r'ВІДЧИНЕНО'; @override - String get shortDhualqiLabel => r'துல்-கே'; + String get pdfHyperlinkLabel => r'Відкрити веб-сторінку'; @override - String get shortJumada1Label => r'ஜும். நான்'; + String get pdfInvalidPageNumberLabel => r'Введіть дійсний номер'; @override - String get shortJumada2Label => r'ஜும். II'; + String get pdfNoBookmarksLabel => r'Закладок не знайдено'; @override - String get shortMuharramLabel => r'முஹ்.'; + String get pdfPaginationDialogCancelLabel => r'СКАСУВАТИ'; @override - String get shortRabi1Label => r'ரபி. நான்'; + String get pdfPaginationDialogOkLabel => r'ОК'; @override - String get shortRabi2Label => r'ரபி. II'; + String get pdfPasswordDialogCancelLabel => r'СКАСУВАТИ'; @override - String get shortRajabLabel => r'ராஜ்.'; + String get pdfPasswordDialogOpenLabel => r'ВІДЧИНЕНО'; @override - String get shortRamadanLabel => r'ரேம்.'; + String get pdfScrollStatusOfLabel => r'з'; @override - String get shortSafarLabel => r'Saf.'; + String get pdfSignaturePadDialogClearLabel => r'Очистити'; @override - String get shortShaabanLabel => r'ஷா.'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Намалюйте свій підпис'; @override - String get shortShawwalLabel => r'ஷா.'; + String get pdfSignaturePadDialogPenColorLabel => r'Колір пера'; @override - String get todayLabel => r'இன்று'; + String get pdfSignaturePadDialogSaveLabel => r'ЗБЕРЕГТИ'; @override - String get weeknumberLabel => r'வாரம்'; -} + String get pdfTextSelectionMenuCopyLabel => r'Копія'; -/// The translations for Telugu (`te`). -class SfLocalizationsTe extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsTe class - const SfLocalizationsTe({ - String localeName = 'te', - }) : super( - localeName: localeName, - ); + @override + String get pdfTextSelectionMenuHighlightLabel => r'Виділіть'; @override - String get allDayLabel => r'రోజంతా'; + String get pdfTextSelectionMenuSquigglyLabel => r'звивистий'; @override - String get allowedViewDayLabel => r'రోజు'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Закреслення'; @override - String get allowedViewMonthLabel => r'నెల'; + String get pdfTextSelectionMenuUnderlineLabel => r'Підкреслити'; @override - String get allowedViewScheduleLabel => r'షెడ్యూల్'; + String get rabi1Label => r'Рабі аль-авваль'; @override - String get allowedViewTimelineDayLabel => r'కాలక్రమం రోజు'; + String get rabi2Label => r'Рабі аль-тані'; @override - String get allowedViewTimelineMonthLabel => r'కాలక్రమం నెల'; + String get rajabLabel => r'Раджаб'; @override - String get allowedViewTimelineWeekLabel => r'కాలక్రమం వారం'; + String get ramadanLabel => r'Рамадан'; @override - String get allowedViewTimelineWorkWeekLabel => r'టైమ్‌లైన్ పని వారం'; + String get rowsPerPageDataPagerLabel => r'Рядків на сторінці'; @override - String get allowedViewWeekLabel => r'వారం'; + String get safarLabel => r'Сафар'; @override - String get allowedViewWorkWeekLabel => r'పని వారం'; + String get searchDataGridFilteringLabel => r'Пошук'; @override - String get daySpanCountLabel => r'రోజు'; + String get selectAllDataGridFilteringLabel => r'Вибрати все'; @override - String get dhualhiLabel => r'ధు అల్-హిజ్జా'; + String get series => r'Серія'; @override - String get dhualqiLabel => r'ధు అల్-ఖైదా'; + String get shaabanLabel => r'Шаабан'; @override - String get jumada1Label => r'జుమాదా అల్-అవ్వల్'; + String get shawwalLabel => r'Шавваль'; @override - String get jumada2Label => r'జుమాదా అల్-థాని'; + String get shortDhualhiLabel => + r'Зу' + "'" + r'л-Х'; @override - String get muharramLabel => r'ముహర్రం'; + String get shortDhualqiLabel => + r'Зу' + "'" + r'л-К' + "'" + r'ю'; @override - String get noEventsCalendarLabel => r'ఈవెంట్‌లు లేవు'; + String get shortJumada1Label => r'стрибати я'; @override - String get noSelectedDateCalendarLabel => r'ఎంచుకున్న తేదీ లేదు'; + String get shortJumada2Label => r'стрибати II'; @override - String get ofDataPagerLabel => r'యొక్క'; + String get shortMuharramLabel => r'мух'; @override - String get pagesDataPagerLabel => r'పేజీలు'; + String get shortRabi1Label => r'Рабі. я'; @override - String get passwordDialogContentLabel => - r'ఈ PDF ఫైల్‌ను తెరవడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి'; + String get shortRabi2Label => r'Рабі. II'; @override - String get passwordDialogHeaderTextLabel => r'పాస్‌వర్డ్ రక్షించబడింది'; + String get shortRajabLabel => r'Радж.'; @override - String get passwordDialogHintTextLabel => r'రహస్య సంకేతం తెలపండి'; + String get shortRamadanLabel => r'ОЗП.'; @override - String get passwordDialogInvalidPasswordLabel => r'చెల్లని పాస్వర్డ్'; + String get shortSafarLabel => r'Саф.'; @override - String get pdfBookmarksLabel => r'బుక్‌మార్క్‌లు'; + String get shortShaabanLabel => r'Ша.'; @override - String get pdfEnterPageNumberLabel => r'పేజీ సంఖ్యను నమోదు చేయండి'; + String get shortShawwalLabel => r'Шоу.'; @override - String get pdfGoToPageLabel => r'పుటకు వెళ్ళు'; + String get showRowsWhereDataGridFilteringLabel => r'Показати рядки, де'; @override - String get pdfInvalidPageNumberLabel => - r'దయచేసి చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి'; + String get sortAToZDataGridFilteringLabel => r'Сортування від А до Я'; @override - String get pdfNoBookmarksLabel => r'బుక్‌మార్క్‌లు ఏవీ కనుగొనబడలేదు'; + String get sortAndFilterDataGridFilteringLabel => r'Сортування та фільтр'; @override - String get pdfPaginationDialogCancelLabel => r'రద్దు చేయండి'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Сортування від найбільшого до найменшого'; @override - String get pdfPaginationDialogOkLabel => r'అలాగే'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Сортування від найновіших до найстаріших'; @override - String get pdfPasswordDialogCancelLabel => r'రద్దు చేయండి'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Сортувати від найстарішого до найновішого'; @override - String get pdfPasswordDialogOpenLabel => r'తెరవండి'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Сортування від найменшого до найбільшого'; @override - String get pdfScrollStatusOfLabel => r'యొక్క'; + String get sortZToADataGridFilteringLabel => r'Сортувати від Я до А'; @override - String get rabi1Label => r'రబీ అల్-అవ్వల్'; + String get textFiltersDataGridFilteringLabel => r'Текстові фільтри'; @override - String get rabi2Label => r'రబీ అల్-థాని'; + String get todayLabel => r'Сьогодні'; @override - String get rajabLabel => r'రజబ్'; + String get weeknumberLabel => r'Тиждень'; +} + +/// The translations for Urdu (`ur`). +class SfLocalizationsUr extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsUr class + const SfLocalizationsUr({String localeName = 'ur'}) + : super(localeName: localeName); @override - String get ramadanLabel => r'రంజాన్'; + String get afterDataGridFilteringLabel => r'کے بعد'; @override - String get rowsPerPageDataPagerLabel => r'ప్రతి పేజీకి వరుసలు'; + String get afterOrEqualDataGridFilteringLabel => r'کے بعد یا برابر'; @override - String get safarLabel => r'సఫర్'; + String get allDayLabel => r'تمام دن'; @override - String get series => r'సిరీస్'; + String get allowedViewDayLabel => r'دن'; @override - String get shaabanLabel => r'షాబాన్'; + String get allowedViewMonthLabel => r'مہینہ'; @override - String get shawwalLabel => r'షవ్వాల్'; + String get allowedViewScheduleLabel => r'شیڈول'; @override - String get shortDhualhiLabel => r'ధుల్-హెచ్'; + String get allowedViewTimelineDayLabel => r'ٹائم لائن کا دن'; @override - String get shortDhualqiLabel => r'ధుల్-క్యూ'; + String get allowedViewTimelineMonthLabel => r'ٹائم لائن مہینہ'; @override - String get shortJumada1Label => r'జం. I'; + String get allowedViewTimelineWeekLabel => r'ٹائم لائن ہفتہ'; @override - String get shortJumada2Label => r'జం. II'; + String get allowedViewTimelineWorkWeekLabel => r'ٹائم لائن ورک ہفتہ'; @override - String get shortMuharramLabel => r'ముహ్.'; + String get allowedViewWeekLabel => r'ہفتہ'; @override - String get shortRabi1Label => r'రబీ I'; + String get allowedViewWorkWeekLabel => r'کام کا ہفتہ'; @override - String get shortRabi2Label => r'రబీ II'; + String get andDataGridFilteringLabel => r'اور'; @override - String get shortRajabLabel => r'రాజ్.'; + String get beforeDataGridFilteringLabel => r'سے پہلے'; @override - String get shortRamadanLabel => r'రామ్'; + String get beforeOrEqualDataGridFilteringLabel => r'پہلے یا برابر'; @override - String get shortSafarLabel => r'సాఫ్.'; + String get beginsWithDataGridFilteringLabel => r'سے شروع ہوتا ہے۔'; @override - String get shortShaabanLabel => r'శా.'; + String get cancelDataGridFilteringLabel => r'منسوخ کریں۔'; @override - String get shortShawwalLabel => r'షా'; + String get clearFilterDataGridFilteringLabel => r'فلٹر صاف کریں۔'; @override - String get todayLabel => r'ఈరోజు'; + String get containsDataGridFilteringLabel => r'مشتمل'; @override - String get weeknumberLabel => r'వారం'; -} - -/// The translations for Thai (`th`). -class SfLocalizationsTh extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsTh class - const SfLocalizationsTh({ - String localeName = 'th', - }) : super( - localeName: localeName, - ); + String get dateFiltersDataGridFilteringLabel => r'تاریخ کے فلٹرز'; @override - String get allDayLabel => r'ทั้งวัน'; + String get daySpanCountLabel => r'دن'; @override - String get allowedViewDayLabel => r'วัน'; + String get dhualhiLabel => r'ذی الحجہ'; @override - String get allowedViewMonthLabel => r'เดือน'; + String get dhualqiLabel => r'ذی القعدہ'; @override - String get allowedViewScheduleLabel => r'กำหนดการ'; + String get doesNotBeginWithDataGridFilteringLabel => r'سے شروع نہیں ہوتا'; @override - String get allowedViewTimelineDayLabel => r'วันไทม์ไลน์'; + String get doesNotContainDataGridFilteringLabel => r'پر مشتمل نہیں ہے۔'; @override - String get allowedViewTimelineMonthLabel => r'ไทม์ไลน์เดือน'; + String get doesNotEndWithDataGridFilteringLabel => r'کے ساتھ ختم نہیں ہوتا'; @override - String get allowedViewTimelineWeekLabel => r'ไทม์ไลน์สัปดาห์'; + String get doesNotEqualDataGridFilteringLabel => r'برابر نہیں ہے۔'; @override - String get allowedViewTimelineWorkWeekLabel => r'ไทม์ไลน์งานสัปดาห์'; + String get emptyDataGridFilteringLabel => r'خالی'; @override - String get allowedViewWeekLabel => r'สัปดาห์'; + String get endsWithDataGridFilteringLabel => r'کے ساتھ ختم ہوتا ہے۔'; @override - String get allowedViewWorkWeekLabel => r'สัปดาห์การทำงาน'; + String get equalsDataGridFilteringLabel => r'برابر ہے۔'; @override - String get daySpanCountLabel => r'วัน'; + String get fromDataGridFilteringLabel => r'سے'; @override - String get dhualhiLabel => r'ดูอัลฮิจญะฮ์'; + String get greaterThanDataGridFilteringLabel => r'اس سے بڑا'; @override - String get dhualqiLabel => r'ญุมาดา อัล-ธานี'; + String get greaterThanOrEqualDataGridFilteringLabel => r'اس سے بڑا یا برابر'; @override - String get jumada1Label => r'ญุมาดา อัลเอาวัล'; + String get jumada1Label => r'جمادی الاول'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'جمعۃ الثانی'; @override - String get muharramLabel => r'มูฮัรรอม'; + String get lessThanDataGridFilteringLabel => r'سے کم'; @override - String get noEventsCalendarLabel => r'ไม่มีกิจกรรม'; + String get lessThanOrEqualDataGridFilteringLabel => r'اس سے کم یا برابر'; @override - String get noSelectedDateCalendarLabel => r'ไม่ได้เลือกวันที่'; + String get muharramLabel => r'محرم ۔۔۔'; @override - String get ofDataPagerLabel => r'ของ'; + String get noEventsCalendarLabel => r'کوئی واقعات نہیں'; @override - String get pagesDataPagerLabel => r'หน้า'; + String get noMatchesDataGridFilteringLabel => r'کوئی میچ نہیں'; @override - String get passwordDialogContentLabel => r'ป้อนรหัสผ่านเพื่อเปิดไฟล์ PDF นี้'; + String get noSelectedDateCalendarLabel => r'کوئی تاریخ منتخب نہیں ہے۔'; @override - String get passwordDialogHeaderTextLabel => r'ป้องกันด้วยพาสเวิร์ด'; + String get notEmptyDataGridFilteringLabel => r'خالی نہیں۔'; @override - String get passwordDialogHintTextLabel => r'ใส่รหัสผ่าน'; + String get notNullDataGridFilteringLabel => r'کالعدم نہیں۔'; @override - String get passwordDialogInvalidPasswordLabel => r'รหัสผ่านไม่ถูกต้อง'; + String get nullDataGridFilteringLabel => r'خالی'; @override - String get pdfBookmarksLabel => r'ที่คั่นหนังสือ'; + String get numberFiltersDataGridFilteringLabel => r'نمبر فلٹرز'; @override - String get pdfEnterPageNumberLabel => r'ใส่เลขหน้า'; + String get ofDataPagerLabel => r'کی'; @override - String get pdfGoToPageLabel => r'ไปที่หน้า'; + String get okDataGridFilteringLabel => r'ٹھیک ہے'; @override - String get pdfInvalidPageNumberLabel => r'กรุณาใส่ตัวเลขที่ถูกต้อง'; + String get orDataGridFilteringLabel => r'یا'; @override - String get pdfNoBookmarksLabel => r'ไม่พบบุ๊คมาร์ค'; + String get pagesDataPagerLabel => r'صفحات'; @override - String get pdfPaginationDialogCancelLabel => r'ยกเลิก'; + String get passwordDialogContentLabel => + r'اس پی ڈی ایف فائل کو کھولنے کے لیے پاس ورڈ درج کریں۔'; @override - String get pdfPaginationDialogOkLabel => r'ตกลง'; + String get passwordDialogHeaderTextLabel => r'پاس ورڈ محفوظ ہے۔'; @override - String get pdfPasswordDialogCancelLabel => r'ยกเลิก'; + String get passwordDialogHintTextLabel => r'پاس ورڈ درج کریں'; @override - String get pdfPasswordDialogOpenLabel => r'เปิด'; + String get passwordDialogInvalidPasswordLabel => r'غلط پاسورڈ'; @override - String get pdfScrollStatusOfLabel => r'ของ'; + String get pdfBookmarksLabel => r'بک مارکس'; @override - String get rabi1Label => r'เราะบี อัลเอาวัล'; + String get pdfEnterPageNumberLabel => r'صفحہ نمبر درج کریں۔'; @override - String get rabi2Label => r'รอบีอัล-ธานี'; + String get pdfGoToPageLabel => r'صفحے پر جائیں'; @override - String get rajabLabel => r'ราชภัฏ'; + String get pdfHyperlinkContentLabel => r'کیا آپ صفحہ کھولنا چاہتے ہیں؟'; @override - String get ramadanLabel => r'รอมฎอน'; + String get pdfHyperlinkDialogCancelLabel => r'منسوخ کریں۔'; @override - String get rowsPerPageDataPagerLabel => r'แถวต่อหน้า'; + String get pdfHyperlinkDialogOpenLabel => r'کھولیں۔'; @override - String get safarLabel => r'ซาฟาร์'; + String get pdfHyperlinkLabel => r'ویب صفحہ کھولیں۔'; @override - String get series => r'ชุด'; + String get pdfInvalidPageNumberLabel => + r'براہ مہربانی ایک درست نمبر درج کریں'; @override - String get shaabanLabel => r'ชะอฺบาน'; + String get pdfNoBookmarksLabel => r'کوئی بک مارکس نہیں ملا'; @override - String get shawwalLabel => r'เชาวาล'; + String get pdfPaginationDialogCancelLabel => r'منسوخ کریں۔'; @override - String get shortDhualhiLabel => r'ดุลฮัก'; + String get pdfPaginationDialogOkLabel => r'ٹھیک ہے'; @override - String get shortDhualqiLabel => r'ดุล-คิว'; + String get pdfPasswordDialogCancelLabel => r'منسوخ کریں۔'; @override - String get shortJumada1Label => r'จั้ม. ฉัน'; + String get pdfPasswordDialogOpenLabel => r'کھولیں۔'; @override - String get shortJumada2Label => r'จั้ม. II'; + String get pdfScrollStatusOfLabel => r'کی'; @override - String get shortMuharramLabel => r'มุ้ย.'; + String get pdfSignaturePadDialogClearLabel => r'صاف کریں۔'; @override - String get shortRabi1Label => r'รบี. ฉัน'; + String get pdfSignaturePadDialogHeaderTextLabel => r'اپنے دستخط کھینچیں۔'; @override - String get shortRabi2Label => r'รบี. II'; + String get pdfSignaturePadDialogPenColorLabel => r'قلم کا رنگ'; @override - String get shortRajabLabel => r'ราช.'; + String get pdfSignaturePadDialogSaveLabel => r'محفوظ کریں۔'; @override - String get shortRamadanLabel => r'แกะ.'; + String get pdfTextSelectionMenuCopyLabel => r'کاپی'; @override - String get shortSafarLabel => r'เซฟ.'; + String get pdfTextSelectionMenuHighlightLabel => r'نمایاں کریں۔'; @override - String get shortShaabanLabel => r'ชา.'; + String get pdfTextSelectionMenuSquigglyLabel => r'دھڑلے سے'; @override - String get shortShawwalLabel => r'ชอว์.'; + String get pdfTextSelectionMenuStrikethroughLabel => r'سٹرائیک تھرو'; @override - String get todayLabel => r'วันนี้'; + String get pdfTextSelectionMenuUnderlineLabel => r'انڈر لائن'; @override - String get weeknumberLabel => r'สัปดาห์'; -} + String get rabi1Label => r'ربیع الاول'; -/// The translations for Tagalog (`tl`). -class SfLocalizationsTl extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsTl class - const SfLocalizationsTl({ - String localeName = 'tl', - }) : super( - localeName: localeName, - ); + @override + String get rabi2Label => r'ربیع الثانی'; @override - String get allDayLabel => r'Buong araw'; + String get rajabLabel => r'رجب'; @override - String get allowedViewDayLabel => r'Araw'; + String get ramadanLabel => r'رمضان'; @override - String get allowedViewMonthLabel => r'buwan'; + String get rowsPerPageDataPagerLabel => r'قطار فی صفحہ'; @override - String get allowedViewScheduleLabel => r'Iskedyul'; + String get safarLabel => r'صفر'; @override - String get allowedViewTimelineDayLabel => r'Araw ng Timeline'; + String get searchDataGridFilteringLabel => r'تلاش کریں۔'; @override - String get allowedViewTimelineMonthLabel => r'Buwan ng Timeline'; + String get selectAllDataGridFilteringLabel => r'تمام منتخب کریں'; @override - String get allowedViewTimelineWeekLabel => r'Linggo ng Timeline'; + String get series => r'سلسلہ'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Linggo ng Trabaho sa Timeline'; + String get shaabanLabel => r'شعبان'; @override - String get allowedViewWeekLabel => r'Linggo'; + String get shawwalLabel => r'شوال'; @override - String get allowedViewWorkWeekLabel => r'Linggo ng trabaho'; + String get shortDhualhiLabel => r'ذوالحجہ'; @override - String get daySpanCountLabel => r'Araw'; + String get shortDhualqiLabel => r'ذوالقع'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get shortJumada1Label => r'جم میں'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get shortJumada2Label => r'جم II'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get shortMuharramLabel => r'مہ'; @override - String get jumada2Label => r'Jumada al-thani'; + String get shortRabi1Label => r'ربیع میں'; @override - String get muharramLabel => r'Muharram'; + String get shortRabi2Label => r'ربیع II'; @override - String get noEventsCalendarLabel => r'Walang mga kaganapan'; + String get shortRajabLabel => r'راج'; @override - String get noSelectedDateCalendarLabel => r'Walang napiling petsa'; + String get shortRamadanLabel => r'رام'; @override - String get ofDataPagerLabel => r'ng'; + String get shortSafarLabel => r'صف۔'; @override - String get pagesDataPagerLabel => r'mga pahina'; + String get shortShaabanLabel => r'شا'; @override - String get passwordDialogContentLabel => - r'Ilagay ang password para buksan ang PDF file na ito'; + String get shortShawwalLabel => r'شا'; @override - String get passwordDialogHeaderTextLabel => r'Pinoprotektahan ng Password'; + String get showRowsWhereDataGridFilteringLabel => r'قطاریں دکھائیں جہاں'; @override - String get passwordDialogHintTextLabel => r'Ilagay ang password'; + String get sortAToZDataGridFilteringLabel => r'A سے Z ترتیب دیں۔'; @override - String get passwordDialogInvalidPasswordLabel => r'di wastong password'; + String get sortAndFilterDataGridFilteringLabel => r'ترتیب دیں اور فلٹر کریں۔'; @override - String get pdfBookmarksLabel => r'Mga bookmark'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'چھانٹیں بڑے سے چھوٹے'; @override - String get pdfEnterPageNumberLabel => r'Ipasok ang numero ng pahina'; + String get sortNewestToOldestDataGridFilteringLabel => + r'تازہ ترین سے قدیم ترین ترتیب دیں۔'; @override - String get pdfGoToPageLabel => r'Pumunta sa pahina'; + String get sortOldestToNewestDataGridFilteringLabel => + r'سب سے قدیم سے تازہ ترین ترتیب دیں۔'; @override - String get pdfInvalidPageNumberLabel => - r'Mangyaring magpasok ng wastong numero'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'چھانٹیں سب سے چھوٹی سے بڑی'; @override - String get pdfNoBookmarksLabel => r'Walang nakitang mga bookmark'; + String get sortZToADataGridFilteringLabel => r'Z سے ​​A ترتیب دیں۔'; @override - String get pdfPaginationDialogCancelLabel => r'KANSELAHIN'; + String get textFiltersDataGridFilteringLabel => r'ٹیکسٹ فلٹرز'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get todayLabel => r'آج'; @override - String get pdfPasswordDialogCancelLabel => r'KANSELAHIN'; + String get weeknumberLabel => r'ہفتہ'; +} + +/// The translations for Uzbek (`uz`). +class SfLocalizationsUz extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsUz class + const SfLocalizationsUz({String localeName = 'uz'}) + : super(localeName: localeName); @override - String get pdfPasswordDialogOpenLabel => r'BUKAS'; + String get afterDataGridFilteringLabel => r'Keyin'; @override - String get pdfScrollStatusOfLabel => r'ng'; + String get afterOrEqualDataGridFilteringLabel => r'Keyin yoki teng'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get allDayLabel => r'Butun kun'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get allowedViewDayLabel => r'Kun'; @override - String get rajabLabel => r'Rajab'; + String get allowedViewMonthLabel => r'Oy'; @override - String get ramadanLabel => r'Ramadan'; + String get allowedViewScheduleLabel => r'Jadval'; @override - String get rowsPerPageDataPagerLabel => r'Mga hilera bawat pahina'; + String get allowedViewTimelineDayLabel => r'Vaqt jadvali kuni'; @override - String get safarLabel => r'Safar'; + String get allowedViewTimelineMonthLabel => r'Vaqt jadvali oyi'; @override - String get series => r'Serye'; + String get allowedViewTimelineWeekLabel => r'Xronologiya haftasi'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get allowedViewTimelineWorkWeekLabel => r'Xronologiya ish haftasi'; @override - String get shawwalLabel => r'Shawwal'; + String get allowedViewWeekLabel => r'Hafta'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get allowedViewWorkWeekLabel => r'Ish haftasi'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get andDataGridFilteringLabel => r'Va'; @override - String get shortJumada1Label => r'Jum. ako'; + String get beforeDataGridFilteringLabel => r'Oldin'; @override - String get shortJumada2Label => r'Jum. II'; + String get beforeOrEqualDataGridFilteringLabel => r'Oldin yoki teng'; @override - String get shortMuharramLabel => r'Muh.'; + String get beginsWithDataGridFilteringLabel => r'Bilan boshlanadi'; @override - String get shortRabi1Label => r'Rabi. ako'; + String get cancelDataGridFilteringLabel => r'Bekor qilish'; @override - String get shortRabi2Label => r'Rabi. II'; + String get clearFilterDataGridFilteringLabel => r'Filtrni tozalash'; @override - String get shortRajabLabel => r'Raj.'; + String get containsDataGridFilteringLabel => r'Tarkibida'; @override - String get shortRamadanLabel => r'Ram.'; + String get dateFiltersDataGridFilteringLabel => r'Sana filtrlari'; @override - String get shortSafarLabel => r'Saf.'; + String get daySpanCountLabel => r'kun'; @override - String get shortShaabanLabel => r'Sha.'; + String get dhualhiLabel => r'Zulhijja'; @override - String get shortShawwalLabel => r'Shaw.'; + String get dhualqiLabel => r'Zulqida'; @override - String get todayLabel => r'Ngayong araw'; + String get doesNotBeginWithDataGridFilteringLabel => r'Bilan boshlanmaydi'; @override - String get weeknumberLabel => r'Linggo'; -} + String get doesNotContainDataGridFilteringLabel => r'Tarkibiga kirmaydi'; -/// The translations for Turkish (`tr`). -class SfLocalizationsTr extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsTr class - const SfLocalizationsTr({ - String localeName = 'tr', - }) : super( - localeName: localeName, - ); + @override + String get doesNotEndWithDataGridFilteringLabel => r'Bilan tugamaydi'; @override - String get allDayLabel => r'Tüm gün'; + String get doesNotEqualDataGridFilteringLabel => r'Teng emas'; @override - String get allowedViewDayLabel => r'Gün'; + String get emptyDataGridFilteringLabel => + r'Bo' + "'" + r'sh'; @override - String get allowedViewMonthLabel => r'Ay'; + String get endsWithDataGridFilteringLabel => r'Bilan tugaydi'; @override - String get allowedViewScheduleLabel => r'Takvim'; + String get equalsDataGridFilteringLabel => r'Teng'; @override - String get allowedViewTimelineDayLabel => r'Zaman Çizelgesi Günü'; + String get fromDataGridFilteringLabel => r'Kimdan'; @override - String get allowedViewTimelineMonthLabel => r'Zaman Çizelgesi Ayı'; + String get greaterThanDataGridFilteringLabel => r'Kattaroq'; @override - String get allowedViewTimelineWeekLabel => r'Zaman Çizelgesi Haftası'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Kattaroq yoki teng'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Zaman Çizelgesi Çalışma Haftası'; + String get jumada1Label => r'Jumada ul-avval'; @override - String get allowedViewWeekLabel => r'Hafta'; + String get jumada2Label => r'Jumada al-Tani'; @override - String get allowedViewWorkWeekLabel => r'Çalışma haftası'; + String get lessThanDataGridFilteringLabel => r'Kamroq'; @override - String get daySpanCountLabel => r'Gün'; + String get lessThanOrEqualDataGridFilteringLabel => r'Kichik yoki teng'; @override - String get dhualhiLabel => r'Zilhicce'; + String get muharramLabel => r'Muharram'; @override - String get dhualqiLabel => r'Zil Qi' "'" r'dah'; + String get noEventsCalendarLabel => r'Voqea yoʻq'; @override - String get jumada1Label => r'Cumada el-evvel'; + String get noMatchesDataGridFilteringLabel => r'Mos kelmaydi'; @override - String get jumada2Label => r'Jumada al-thani'; + String get noSelectedDateCalendarLabel => r'Tanlangan sana yo‘q'; @override - String get muharramLabel => r'Muharrem'; + String get notEmptyDataGridFilteringLabel => + r'Bo' + "'" + r'sh emas'; @override - String get noEventsCalendarLabel => r'Olay yok'; + String get notNullDataGridFilteringLabel => r'Null emas'; @override - String get noSelectedDateCalendarLabel => r'Seçili tarih yok'; + String get nullDataGridFilteringLabel => r'Null'; @override - String get ofDataPagerLabel => r'ile ilgili'; + String get numberFiltersDataGridFilteringLabel => r'Raqamli filtrlar'; @override - String get pagesDataPagerLabel => r'sayfalar'; + String get ofDataPagerLabel => r'ning'; @override - String get passwordDialogContentLabel => - r'Bu PDF dosyasını açmak için şifreyi girin'; + String get okDataGridFilteringLabel => r'OK'; @override - String get passwordDialogHeaderTextLabel => r'Şifre korumalı'; + String get orDataGridFilteringLabel => r'Yoki'; @override - String get passwordDialogHintTextLabel => r'Parolanı Gir'; + String get pagesDataPagerLabel => r'sahifalar'; @override - String get passwordDialogInvalidPasswordLabel => r'geçersiz şifre'; + String get passwordDialogContentLabel => + r'Ushbu PDF faylni ochish uchun parolni kiriting'; @override - String get pdfBookmarksLabel => r'Yer imleri'; + String get passwordDialogHeaderTextLabel => r'Parol bilan himoyalangan'; @override - String get pdfEnterPageNumberLabel => r'Sayfa numarasını girin'; + String get passwordDialogHintTextLabel => r'Parolni kiriting'; @override - String get pdfGoToPageLabel => r'Sayfaya git'; + String get passwordDialogInvalidPasswordLabel => r'Yaroqsiz parol'; @override - String get pdfInvalidPageNumberLabel => r'Lütfen geçerli bir numara girin'; + String get pdfBookmarksLabel => r'Xatcho‘plar'; @override - String get pdfNoBookmarksLabel => r'Yer işareti bulunamadı'; + String get pdfEnterPageNumberLabel => r'Sahifa raqamini kiriting'; @override - String get pdfPaginationDialogCancelLabel => r'İPTAL ETMEK'; + String get pdfGoToPageLabel => + r'Sahifaga o' + "'" + r'tish'; @override - String get pdfPaginationDialogOkLabel => r'Tamam'; + String get pdfHyperlinkContentLabel => r'sahifani ochmoqchimisiz'; @override - String get pdfPasswordDialogCancelLabel => r'İPTAL ETMEK'; + String get pdfHyperlinkDialogCancelLabel => r'BEKOR'; @override - String get pdfPasswordDialogOpenLabel => r'AÇIK'; + String get pdfHyperlinkDialogOpenLabel => r'OCHIQ'; @override - String get pdfScrollStatusOfLabel => r'ile ilgili'; + String get pdfHyperlinkLabel => r'Veb-sahifani oching'; @override - String get rabi1Label => r'Rebiülevvel'; + String get pdfInvalidPageNumberLabel => r'Yaroqli raqam kiriting'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get pdfNoBookmarksLabel => r'Hech qanday xatcho‘p topilmadi'; @override - String get rajabLabel => r'Recep'; + String get pdfPaginationDialogCancelLabel => r'BEKOR'; @override - String get ramadanLabel => r'Ramazan'; + String get pdfPaginationDialogOkLabel => r'OK'; @override - String get rowsPerPageDataPagerLabel => r'Sayfa başına satır sayısı'; + String get pdfPasswordDialogCancelLabel => r'BEKOR'; @override - String get safarLabel => r'Safar'; + String get pdfPasswordDialogOpenLabel => r'OCHIQ'; @override - String get series => r'Dizi'; + String get pdfScrollStatusOfLabel => r'ning'; @override - String get shaabanLabel => r'Şaban'; + String get pdfSignaturePadDialogClearLabel => r'TOZLASH'; @override - String get shawwalLabel => r'Şevval'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Imzongizni chizing'; @override - String get shortDhualhiLabel => r'Zül-H'; + String get pdfSignaturePadDialogPenColorLabel => r'Qalam rangi'; @override - String get shortDhualqiLabel => r'Zil-Q'; + String get pdfSignaturePadDialogSaveLabel => r'SAQLASH'; @override - String get shortJumada1Label => r'Jum. i'; + String get pdfTextSelectionMenuCopyLabel => r'Nusxalash'; @override - String get shortJumada2Label => r'Jum. II'; + String get pdfTextSelectionMenuHighlightLabel => r'Ajratish'; @override - String get shortMuharramLabel => r'Müh.'; + String get pdfTextSelectionMenuSquigglyLabel => + r'Buzg' + "'" + r'unchi'; @override - String get shortRabi1Label => r'Rabi. i'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Chizilgan'; @override - String get shortRabi2Label => r'Rabi. II'; + String get pdfTextSelectionMenuUnderlineLabel => r'tagiga chizish'; @override - String get shortRajabLabel => r'Raj.'; + String get rabi1Label => + r'Rabi' + "'" + r'ul-avval'; @override - String get shortRamadanLabel => r'Veri deposu.'; + String get rabi2Label => + r'Rabi' + "'" + r' as-saniy'; @override - String get shortSafarLabel => r'Saf.'; + String get rajabLabel => r'Rajab'; @override - String get shortShaabanLabel => r'Sha.'; + String get ramadanLabel => r'Ramazon'; @override - String get shortShawwalLabel => r'Shaw.'; + String get rowsPerPageDataPagerLabel => r'Har bir sahifadagi qatorlar'; @override - String get todayLabel => r'Bugün'; + String get safarLabel => r'Safar'; @override - String get weeknumberLabel => r'Hafta'; -} + String get searchDataGridFilteringLabel => r'Qidirmoq'; -/// The translations for Ukrainian (`uk`). -class SfLocalizationsUk extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsUk class - const SfLocalizationsUk({ - String localeName = 'uk', - }) : super( - localeName: localeName, - ); + @override + String get selectAllDataGridFilteringLabel => r'Hammasini belgilash'; @override - String get allDayLabel => r'Весь день'; + String get series => r'Seriya'; @override - String get allowedViewDayLabel => r'День'; + String get shaabanLabel => + r'Sha' + "'" + r'bon'; @override - String get allowedViewMonthLabel => r'Місяць'; + String get shawwalLabel => r'Shavvol'; @override - String get allowedViewScheduleLabel => r'Розклад'; + String get shortDhualhiLabel => r'Zul-h'; @override - String get allowedViewTimelineDayLabel => r'День хронології'; + String get shortDhualqiLabel => r'Zul-q'; @override - String get allowedViewTimelineMonthLabel => r'Хронологічний місяць'; + String get shortJumada1Label => r'Jum. I'; @override - String get allowedViewTimelineWeekLabel => r'Тиждень хронології'; + String get shortJumada2Label => r'Jum. II'; @override - String get allowedViewTimelineWorkWeekLabel => r'Розклад робочого тижня'; + String get shortMuharramLabel => r'Muh.'; @override - String get allowedViewWeekLabel => r'тиждень'; + String get shortRabi1Label => r'Rabi. I'; @override - String get allowedViewWorkWeekLabel => r'Робочий тиждень'; + String get shortRabi2Label => r'Rabi. II'; @override - String get daySpanCountLabel => r'День'; + String get shortRajabLabel => r'Raj.'; @override - String get dhualhiLabel => r'Зу аль-Хіджа'; + String get shortRamadanLabel => r'Ram.'; @override - String get dhualqiLabel => r'Зу аль-Кіда'; + String get shortSafarLabel => r'Saf.'; @override - String get jumada1Label => r'Джумада аль-авваль'; + String get shortShaabanLabel => r'Sha.'; @override - String get jumada2Label => r'Джумада аль-Тані'; + String get shortShawwalLabel => r'Shou.'; @override - String get muharramLabel => r'Мухаррам'; + String get showRowsWhereDataGridFilteringLabel => + r'Qaerda qatorlarni ko' + "'" + r'rsatish'; @override - String get noEventsCalendarLabel => r'Жодних подій'; + String get sortAToZDataGridFilteringLabel => r'A dan Z gacha tartiblang'; @override - String get noSelectedDateCalendarLabel => r'Немає вибраної дати'; + String get sortAndFilterDataGridFilteringLabel => r'Saralash va filtrlash'; @override - String get ofDataPagerLabel => r'з'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Eng kattadan kichikga saralash'; @override - String get pagesDataPagerLabel => r'сторінки'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Eng yangidan eng eskiga saralash'; @override - String get passwordDialogContentLabel => - r'Введіть пароль, щоб відкрити цей PDF-файл'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Eng eskidan eng yangisiga saralash'; @override - String get passwordDialogHeaderTextLabel => r'Захищено паролем'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Eng kichikdan kattaga saralash'; @override - String get passwordDialogHintTextLabel => r'Введіть пароль'; + String get sortZToADataGridFilteringLabel => r'Z dan A ga tartiblang'; @override - String get passwordDialogInvalidPasswordLabel => r'Недійсний пароль'; + String get textFiltersDataGridFilteringLabel => r'Matn filtrlari'; @override - String get pdfBookmarksLabel => r'Закладки'; + String get todayLabel => r'Bugun'; @override - String get pdfEnterPageNumberLabel => r'Введіть номер сторінки'; + String get weeknumberLabel => r'Hafta'; +} - @override - String get pdfGoToPageLabel => r'Перейти на сторінку'; +/// The translations for Vietnamese (`vi`). +class SfLocalizationsVi extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsVi class + const SfLocalizationsVi({String localeName = 'vi'}) + : super(localeName: localeName); @override - String get pdfInvalidPageNumberLabel => r'Введіть дійсне число'; + String get afterDataGridFilteringLabel => r'Sau'; @override - String get pdfNoBookmarksLabel => r'Закладок не знайдено'; + String get afterOrEqualDataGridFilteringLabel => r'Sau Hoặc Bằng'; @override - String get pdfPaginationDialogCancelLabel => r'СКАСУВАТИ'; + String get allDayLabel => r'Cả ngày'; @override - String get pdfPaginationDialogOkLabel => r'гаразд'; + String get allowedViewDayLabel => r'Ngày'; @override - String get pdfPasswordDialogCancelLabel => r'СКАСУВАТИ'; + String get allowedViewMonthLabel => r'Tháng'; @override - String get pdfPasswordDialogOpenLabel => r'ВІДЧИНЕНО'; + String get allowedViewScheduleLabel => r'Lịch trình'; @override - String get pdfScrollStatusOfLabel => r'з'; + String get allowedViewTimelineDayLabel => r'Dòng thời gian ngày'; @override - String get rabi1Label => r'Рабі аль-авваль'; + String get allowedViewTimelineMonthLabel => r'Dòng thời gian Tháng'; @override - String get rabi2Label => r'Рабі аль-Тані'; + String get allowedViewTimelineWeekLabel => r'Dòng thời gian Tuần'; @override - String get rajabLabel => r'Раджаб'; + String get allowedViewTimelineWorkWeekLabel => + r'Dòng thời gian tuần làm việc'; @override - String get ramadanLabel => r'Рамадан'; + String get allowedViewWeekLabel => r'Tuần'; @override - String get rowsPerPageDataPagerLabel => r'Рядків на сторінці'; + String get allowedViewWorkWeekLabel => r'Tuần làm việc'; @override - String get safarLabel => r'Сафар'; + String get andDataGridFilteringLabel => r'Và'; @override - String get series => r'Серія'; + String get beforeDataGridFilteringLabel => r'Trước'; @override - String get shaabanLabel => r'Шаабан'; + String get beforeOrEqualDataGridFilteringLabel => r'Trước Hoặc Bằng'; @override - String get shawwalLabel => r'Shawwal'; + String get beginsWithDataGridFilteringLabel => r'Bắt đầu với'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get cancelDataGridFilteringLabel => r'Hủy bỏ'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get clearFilterDataGridFilteringLabel => r'LÀm sạch bộ lọc'; @override - String get shortJumada1Label => r'Jum. я'; + String get containsDataGridFilteringLabel => r'Chứa'; @override - String get shortJumada2Label => r'Jum. II'; + String get dateFiltersDataGridFilteringLabel => r'Bộ lọc ngày'; @override - String get shortMuharramLabel => r'Мух'; + String get daySpanCountLabel => r'Ngày'; @override - String get shortRabi1Label => r'Рабі. я'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get shortRabi2Label => r'Рабі. II'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get shortRajabLabel => r'Радж.'; + String get doesNotBeginWithDataGridFilteringLabel => r'không bắt đầu bằng'; @override - String get shortRamadanLabel => r'ОЗП.'; + String get doesNotContainDataGridFilteringLabel => r'Không chứa'; @override - String get shortSafarLabel => r'Saf.'; + String get doesNotEndWithDataGridFilteringLabel => r'không kết thúc với'; @override - String get shortShaabanLabel => r'Ша.'; + String get doesNotEqualDataGridFilteringLabel => r'không bằng'; @override - String get shortShawwalLabel => r'Шоу.'; + String get emptyDataGridFilteringLabel => r'Trống rỗng'; @override - String get todayLabel => r'Сьогодні'; + String get endsWithDataGridFilteringLabel => r'kết thúc với'; @override - String get weeknumberLabel => r'тиждень'; -} - -/// The translations for Urdu (`ur`). -class SfLocalizationsUr extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsUr class - const SfLocalizationsUr({ - String localeName = 'ur', - }) : super( - localeName: localeName, - ); + String get equalsDataGridFilteringLabel => r'bằng'; @override - String get allDayLabel => r'تمام دن'; + String get fromDataGridFilteringLabel => r'Từ'; @override - String get allowedViewDayLabel => r'دن'; + String get greaterThanDataGridFilteringLabel => r'Lớn hơn'; @override - String get allowedViewMonthLabel => r'مہینہ'; + String get greaterThanOrEqualDataGridFilteringLabel => r'Lớn hơn hoặc bằng'; @override - String get allowedViewScheduleLabel => r'شیڈول'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get allowedViewTimelineDayLabel => r'ٹائم لائن کا دن'; + String get jumada2Label => r'Jumada al-thani'; @override - String get allowedViewTimelineMonthLabel => r'ٹائم لائن مہینہ'; + String get lessThanDataGridFilteringLabel => r'Ít hơn'; @override - String get allowedViewTimelineWeekLabel => r'ٹائم لائن ہفتہ'; + String get lessThanOrEqualDataGridFilteringLabel => r'Nhỏ hơn hoặc bằng'; @override - String get allowedViewTimelineWorkWeekLabel => r'ٹائم لائن ورک ہفتہ'; + String get muharramLabel => r'Muharram'; @override - String get allowedViewWeekLabel => r'ہفتہ'; + String get noEventsCalendarLabel => r'Không có sự kiện'; @override - String get allowedViewWorkWeekLabel => r'کام کا ہفتہ'; + String get noMatchesDataGridFilteringLabel => r'Không có trận đấu'; @override - String get daySpanCountLabel => r'دن'; + String get noSelectedDateCalendarLabel => r'Không có ngày được chọn'; @override - String get dhualhiLabel => r'ذی الحجہ'; + String get notEmptyDataGridFilteringLabel => r'Không trống'; @override - String get dhualqiLabel => r'ذی القعدہ'; + String get notNullDataGridFilteringLabel => r'Có giá trị'; @override - String get jumada1Label => r'جمادی الاول'; + String get nullDataGridFilteringLabel => r'Vô giá trị'; @override - String get jumada2Label => r'جمعۃ الثانی'; + String get numberFiltersDataGridFilteringLabel => r'Bộ lọc số'; @override - String get muharramLabel => r'محرم ۔۔۔'; + String get ofDataPagerLabel => r'của'; @override - String get noEventsCalendarLabel => r'کوئی واقعات نہیں۔'; + String get okDataGridFilteringLabel => r'ĐƯỢC RỒI'; @override - String get noSelectedDateCalendarLabel => r'کوئی تاریخ منتخب نہیں ہے۔'; + String get orDataGridFilteringLabel => r'Hoặc'; @override - String get ofDataPagerLabel => r'کا'; + String get pagesDataPagerLabel => r'trang'; @override - String get pagesDataPagerLabel => r'صفحات'; + String get passwordDialogContentLabel => r'Nhập mật khẩu để mở tệp PDF này'; @override - String get passwordDialogContentLabel => - r'اس پی ڈی ایف فائل کو کھولنے کے لیے پاس ورڈ درج کریں۔'; + String get passwordDialogHeaderTextLabel => r'Mật khẩu được bảo vệ'; @override - String get passwordDialogHeaderTextLabel => r'پاس ورڈ محفوظ ہے۔'; + String get passwordDialogHintTextLabel => r'Nhập mật khẩu'; @override - String get passwordDialogHintTextLabel => r'پاس ورڈ درج کریں'; + String get passwordDialogInvalidPasswordLabel => r'Mật khẩu không hợp lệ'; @override - String get passwordDialogInvalidPasswordLabel => r'غلط پاسورڈ'; + String get pdfBookmarksLabel => r'Dấu trang'; @override - String get pdfBookmarksLabel => r'بک مارکس'; + String get pdfEnterPageNumberLabel => r'Nhập số trang'; @override - String get pdfEnterPageNumberLabel => r'صفحہ نمبر درج کریں۔'; + String get pdfGoToPageLabel => r'Đi tới trang'; @override - String get pdfGoToPageLabel => r'صفحے پر جائیں'; + String get pdfHyperlinkContentLabel => r'Bạn có muốn mở trang này không?'; @override - String get pdfInvalidPageNumberLabel => - r'براہ مہربانی ایک درست نمبر درج کریں'; + String get pdfHyperlinkDialogCancelLabel => r'Hủy'; @override - String get pdfNoBookmarksLabel => r'کوئی بک مارکس نہیں ملے'; + String get pdfHyperlinkDialogOpenLabel => r'Mở'; @override - String get pdfPaginationDialogCancelLabel => r'منسوخ کریں۔'; + String get pdfHyperlinkLabel => r'Mở trang web'; @override - String get pdfPaginationDialogOkLabel => r'ٹھیک ہے'; + String get pdfInvalidPageNumberLabel => r'Vui lòng nhập một số hợp lệ'; @override - String get pdfPasswordDialogCancelLabel => r'منسوخ کریں۔'; + String get pdfNoBookmarksLabel => r'Không tìm thấy dấu trang nào'; @override - String get pdfPasswordDialogOpenLabel => r'کھولیں'; + String get pdfPaginationDialogCancelLabel => r'Hủy'; @override - String get pdfScrollStatusOfLabel => r'کا'; + String get pdfPaginationDialogOkLabel => r'Đồng ý'; @override - String get rabi1Label => r'ربیع الاول'; + String get pdfPasswordDialogCancelLabel => r'Hủy'; @override - String get rabi2Label => r'ربیع الثانی'; + String get pdfPasswordDialogOpenLabel => r'Mở'; @override - String get rajabLabel => r'رجب'; + String get pdfScrollStatusOfLabel => r'của'; @override - String get ramadanLabel => r'رمضان'; + String get pdfSignaturePadDialogClearLabel => r'Xóa'; @override - String get rowsPerPageDataPagerLabel => r'قطار فی صفحہ'; + String get pdfSignaturePadDialogHeaderTextLabel => r'Vẽ chữ ký của bạn'; @override - String get safarLabel => r'صفر'; + String get pdfSignaturePadDialogPenColorLabel => r'màu bút'; @override - String get series => r'سلسلہ'; + String get pdfSignaturePadDialogSaveLabel => r'Lưu'; @override - String get shaabanLabel => r'شعبان'; + String get pdfTextSelectionMenuCopyLabel => r'Sao chép'; @override - String get shawwalLabel => r'شوال'; + String get pdfTextSelectionMenuHighlightLabel => r'Tô đậm'; @override - String get shortDhualhiLabel => r'ذوالحجہ'; + String get pdfTextSelectionMenuSquigglyLabel => r'Gạch lượn sóng'; @override - String get shortDhualqiLabel => r'ذوالقع'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Gạch ngang'; @override - String get shortJumada1Label => r'جم میں'; + String get pdfTextSelectionMenuUnderlineLabel => r'gạch chân'; @override - String get shortJumada2Label => r'جم II'; + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; @override - String get shortMuharramLabel => r'مہ'; + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override - String get shortRabi1Label => r'ربیع میں'; + String get rajabLabel => r'Rajab'; @override - String get shortRabi2Label => r'ربیع II'; + String get ramadanLabel => r'lễ ramadan'; @override - String get shortRajabLabel => r'راج'; + String get rowsPerPageDataPagerLabel => r'Hàng trên mỗi trang'; @override - String get shortRamadanLabel => r'رام'; + String get safarLabel => r'safar'; @override - String get shortSafarLabel => r'صف۔'; + String get searchDataGridFilteringLabel => r'Tìm kiếm'; @override - String get shortShaabanLabel => r'شا'; + String get selectAllDataGridFilteringLabel => r'Chọn tất cả'; @override - String get shortShawwalLabel => r'شا'; + String get series => r'Loạt'; @override - String get todayLabel => r'آج'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override - String get weeknumberLabel => r'ہفتہ'; -} - -/// The translations for Uzbek (`uz`). -class SfLocalizationsUz extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsUz class - const SfLocalizationsUz({ - String localeName = 'uz', - }) : super( - localeName: localeName, - ); + String get shawwalLabel => r'Shawwal'; @override - String get allDayLabel => r'Butun kun'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get allowedViewDayLabel => r'kun'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get allowedViewMonthLabel => r'Oy'; + String get shortJumada1Label => r'Jum. Tôi'; @override - String get allowedViewScheduleLabel => r'Jadval'; + String get shortJumada2Label => r'Jum. II'; @override - String get allowedViewTimelineDayLabel => r'Vaqt jadvali kuni'; + String get shortMuharramLabel => r'ừm.'; @override - String get allowedViewTimelineMonthLabel => r'Vaqt jadvali oyi'; + String get shortRabi1Label => r'Rabi. Tôi'; @override - String get allowedViewTimelineWeekLabel => r'Xronologiya haftasi'; + String get shortRabi2Label => r'Rabi. II'; @override - String get allowedViewTimelineWorkWeekLabel => r'Xronologiya ish haftasi'; + String get shortRajabLabel => r'Raj.'; @override - String get allowedViewWeekLabel => r'Hafta'; + String get shortRamadanLabel => r'Đập.'; @override - String get allowedViewWorkWeekLabel => r'Ish haftasi'; + String get shortSafarLabel => r'an toàn.'; @override - String get daySpanCountLabel => r'kun'; + String get shortShaabanLabel => r'Sha.'; @override - String get dhualhiLabel => r'Zulhijja'; + String get shortShawwalLabel => r'Shaw.'; @override - String get dhualqiLabel => r'Zulqida'; + String get showRowsWhereDataGridFilteringLabel => r'Hiển thị các hàng ở đâu'; @override - String get jumada1Label => r'Jumada ul-avval'; + String get sortAToZDataGridFilteringLabel => r'Sắp xếp từ A đến Z'; @override - String get jumada2Label => r'Jumada al-Tani'; + String get sortAndFilterDataGridFilteringLabel => r'Sắp xếp và lọc'; @override - String get muharramLabel => r'Muharram'; + String get sortLargestToSmallestDataGridFilteringLabel => + r'Sắp xếp lớn nhất đến nhỏ nhất'; @override - String get noEventsCalendarLabel => r'Voqea yoʻq'; + String get sortNewestToOldestDataGridFilteringLabel => + r'Sắp xếp mới nhất đến cũ nhất'; @override - String get noSelectedDateCalendarLabel => r'Tanlangan sana yo‘q'; + String get sortOldestToNewestDataGridFilteringLabel => + r'Sắp xếp Cũ nhất đến Mới nhất'; @override - String get ofDataPagerLabel => r'ning'; + String get sortSmallestToLargestDataGridFilteringLabel => + r'Sắp xếp từ nhỏ nhất đến lớn nhất'; @override - String get pagesDataPagerLabel => r'sahifalar'; + String get sortZToADataGridFilteringLabel => r'Sắp xếp từ Z đến A'; @override - String get passwordDialogContentLabel => - r'Ushbu PDF faylni ochish uchun parolni kiriting'; + String get textFiltersDataGridFilteringLabel => r'Bộ lọc văn bản'; @override - String get passwordDialogHeaderTextLabel => r'Parol bilan himoyalangan'; + String get todayLabel => r'Hôm nay'; @override - String get passwordDialogHintTextLabel => r'Parolni kiriting'; + String get weeknumberLabel => r'Tuần'; +} - @override - String get passwordDialogInvalidPasswordLabel => r'Yaroqsiz parol'; +/// The translations for Chinese (`zh`). +class SfLocalizationsZh extends SfGlobalLocalizations { + /// Creating an argument constructor of SfLocalizationsZh class + const SfLocalizationsZh({String localeName = 'zh'}) + : super(localeName: localeName); @override - String get pdfBookmarksLabel => r'Xatcho‘plar'; + String get afterDataGridFilteringLabel => r'后'; @override - String get pdfEnterPageNumberLabel => r'Sahifa raqamini kiriting'; + String get afterOrEqualDataGridFilteringLabel => r'等于或之后'; @override - String get pdfGoToPageLabel => r'Sahifaga o' "'" r'tish'; + String get allDayLabel => r'一整天'; @override - String get pdfInvalidPageNumberLabel => r'Yaroqli raqam kiriting'; + String get allowedViewDayLabel => r'天'; @override - String get pdfNoBookmarksLabel => r'Hech qanday xatcho‘p topilmadi'; + String get allowedViewMonthLabel => r'月'; @override - String get pdfPaginationDialogCancelLabel => r'BEKOR'; + String get allowedViewScheduleLabel => r'日程'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get allowedViewTimelineDayLabel => r'时间轴日'; @override - String get pdfPasswordDialogCancelLabel => r'BEKOR'; + String get allowedViewTimelineMonthLabel => r'时间表月份'; @override - String get pdfPasswordDialogOpenLabel => r'OCHIQ'; + String get allowedViewTimelineWeekLabel => r'时间线周'; @override - String get pdfScrollStatusOfLabel => r'ning'; + String get allowedViewTimelineWorkWeekLabel => r'时间表工作周'; @override - String get rabi1Label => r'Rabi' "'" r'ul-avval'; + String get allowedViewWeekLabel => r'星期'; @override - String get rabi2Label => r'Rabi' "'" r' as-saniy'; + String get allowedViewWorkWeekLabel => r'工作周'; @override - String get rajabLabel => r'Rajab'; + String get andDataGridFilteringLabel => r'和'; @override - String get ramadanLabel => r'Ramazon'; + String get beforeDataGridFilteringLabel => r'前'; @override - String get rowsPerPageDataPagerLabel => r'Har bir sahifadagi qatorlar'; + String get beforeOrEqualDataGridFilteringLabel => r'早于或等于'; @override - String get safarLabel => r'Safar'; + String get beginsWithDataGridFilteringLabel => r'开始于'; @override - String get series => r'Seriya'; + String get cancelDataGridFilteringLabel => r'取消'; @override - String get shaabanLabel => r'Sha' "'" r'bon'; + String get clearFilterDataGridFilteringLabel => r'清除过滤器'; @override - String get shawwalLabel => r'Shavvol'; + String get containsDataGridFilteringLabel => r'包含'; @override - String get shortDhualhiLabel => r'Zul-h'; + String get dateFiltersDataGridFilteringLabel => r'日期过滤器'; @override - String get shortDhualqiLabel => r'Zul-q'; + String get daySpanCountLabel => r'天'; @override - String get shortJumada1Label => r'Jum. I'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get shortJumada2Label => r'Jum. II'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; @override - String get shortMuharramLabel => r'Muh.'; + String get doesNotBeginWithDataGridFilteringLabel => r'不以开头'; @override - String get shortRabi1Label => r'Rabi. I'; + String get doesNotContainDataGridFilteringLabel => r'不含'; @override - String get shortRabi2Label => r'Rabi. II'; + String get doesNotEndWithDataGridFilteringLabel => r'不结束于'; @override - String get shortRajabLabel => r'Raj.'; + String get doesNotEqualDataGridFilteringLabel => r'不等于'; @override - String get shortRamadanLabel => r'Ram.'; + String get emptyDataGridFilteringLabel => r'空的'; @override - String get shortSafarLabel => r'Saf.'; + String get endsWithDataGridFilteringLabel => r'以。。结束'; @override - String get shortShaabanLabel => r'Sha.'; + String get equalsDataGridFilteringLabel => r'等于'; @override - String get shortShawwalLabel => r'Shou.'; + String get fromDataGridFilteringLabel => r'从'; @override - String get todayLabel => r'Bugun'; + String get greaterThanDataGridFilteringLabel => r'比...更棒'; @override - String get weeknumberLabel => r'Hafta'; -} + String get greaterThanOrEqualDataGridFilteringLabel => r'大于或等于'; -/// The translations for Vietnamese (`vi`). -class SfLocalizationsVi extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsVi class - const SfLocalizationsVi({ - String localeName = 'vi', - }) : super( - localeName: localeName, - ); + @override + String get jumada1Label => r'朱马达·阿瓦瓦尔'; @override - String get allDayLabel => r'Cả ngày'; + String get jumada2Label => r'祖玛达·阿萨尼'; @override - String get allowedViewDayLabel => r'Ngày'; + String get lessThanDataGridFilteringLabel => r'少于'; @override - String get allowedViewMonthLabel => r'Tháng'; + String get lessThanOrEqualDataGridFilteringLabel => r'小于或等于'; @override - String get allowedViewScheduleLabel => r'Lịch trình'; + String get muharramLabel => r'回历'; @override - String get allowedViewTimelineDayLabel => r'Ngày dòng thời gian'; + String get noEventsCalendarLabel => r'没有活动'; @override - String get allowedViewTimelineMonthLabel => r'Dòng thời gian Tháng'; + String get noMatchesDataGridFilteringLabel => r'无匹配'; @override - String get allowedViewTimelineWeekLabel => r'Dòng thời gian trong tuần'; + String get noSelectedDateCalendarLabel => r'没有选择日期'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Dòng thời gian làm việc trong tuần'; + String get notEmptyDataGridFilteringLabel => r'不是空的'; @override - String get allowedViewWeekLabel => r'Tuần'; + String get notNullDataGridFilteringLabel => r'不为空'; @override - String get allowedViewWorkWeekLabel => r'Tuần làm việc'; + String get nullDataGridFilteringLabel => r'无效的'; @override - String get daySpanCountLabel => r'Ngày'; + String get numberFiltersDataGridFilteringLabel => r'数字过滤器'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get ofDataPagerLabel => r'的'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get okDataGridFilteringLabel => r'好的'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get orDataGridFilteringLabel => r'或者'; @override - String get jumada2Label => r'Jumada al-thani'; + String get pagesDataPagerLabel => r'页数'; @override - String get muharramLabel => r'Muharram'; + String get passwordDialogContentLabel => r'输入密码打开此 PDF 文件'; @override - String get noEventsCalendarLabel => r'Không có sự kiện'; + String get passwordDialogHeaderTextLabel => r'密码保护'; @override - String get noSelectedDateCalendarLabel => r'Không có ngày được chọn'; + String get passwordDialogHintTextLabel => r'输入密码'; @override - String get ofDataPagerLabel => r'của'; + String get passwordDialogInvalidPasswordLabel => r'无效的密码'; @override - String get pagesDataPagerLabel => r'trang'; + String get pdfBookmarksLabel => r'书签'; @override - String get passwordDialogContentLabel => r'Nhập mật khẩu để mở tệp PDF này'; + String get pdfEnterPageNumberLabel => r'输入页码'; @override - String get passwordDialogHeaderTextLabel => r'Mật khẩu được bảo vệ'; + String get pdfGoToPageLabel => r'转到页面'; @override - String get passwordDialogHintTextLabel => r'Nhập mật khẩu'; + String get pdfHyperlinkContentLabel => r'你想打开页面吗'; @override - String get passwordDialogInvalidPasswordLabel => r'Mật khẩu không hợp lệ'; + String get pdfHyperlinkDialogCancelLabel => r'取消'; @override - String get pdfBookmarksLabel => r'Dấu trang'; + String get pdfHyperlinkDialogOpenLabel => r'打开'; @override - String get pdfEnterPageNumberLabel => r'Nhập số trang'; + String get pdfHyperlinkLabel => r'打开网页'; @override - String get pdfGoToPageLabel => r'Đi tới trang'; + String get pdfInvalidPageNumberLabel => r'请输入有效号码'; @override - String get pdfInvalidPageNumberLabel => r'Vui lòng nhập một số hợp lệ'; + String get pdfNoBookmarksLabel => r'找不到书签'; @override - String get pdfNoBookmarksLabel => r'Không tìm thấy dấu trang'; + String get pdfPaginationDialogCancelLabel => r'取消'; @override - String get pdfPaginationDialogCancelLabel => r'SỰ HỦY BỎ'; + String get pdfPaginationDialogOkLabel => r'确认'; @override - String get pdfPaginationDialogOkLabel => r'VÂNG'; + String get pdfPasswordDialogCancelLabel => r'取消'; @override - String get pdfPasswordDialogCancelLabel => r'SỰ HỦY BỎ'; + String get pdfPasswordDialogOpenLabel => r'打开'; @override - String get pdfPasswordDialogOpenLabel => r'MỞ'; + String get pdfScrollStatusOfLabel => r'的'; @override - String get pdfScrollStatusOfLabel => r'của'; + String get pdfSignaturePadDialogClearLabel => r'清除'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get pdfSignaturePadDialogHeaderTextLabel => r'画出你的签名'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get pdfSignaturePadDialogPenColorLabel => r'笔颜色'; @override - String get rajabLabel => r'Rajab'; + String get pdfSignaturePadDialogSaveLabel => r'保存'; @override - String get ramadanLabel => r'Ramadan'; + String get rabi1Label => r'拉比·奥瓦尔'; @override - String get rowsPerPageDataPagerLabel => r'Hàng trên mỗi trang'; + String get pdfTextSelectionMenuCopyLabel => r'复制'; @override - String get safarLabel => r'Safar'; + String get pdfTextSelectionMenuHighlightLabel => r'突出显示'; @override - String get series => r'Hàng loạt'; + String get pdfTextSelectionMenuSquigglyLabel => r'波浪形'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get pdfTextSelectionMenuStrikethroughLabel => r'删除线'; @override - String get shawwalLabel => r'Shawwal'; + String get pdfTextSelectionMenuUnderlineLabel => r'下划线'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get rabi2Label => r'拉比阿勒萨尼'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get rajabLabel => r'拉杰卜'; @override - String get shortJumada1Label => r'Jum. tôi'; + String get ramadanLabel => r'斋月'; @override - String get shortJumada2Label => r'Jum. II'; + String get rowsPerPageDataPagerLabel => r'每页行数'; @override - String get shortMuharramLabel => r'Ờ.'; + String get safarLabel => r'萨法尔'; @override - String get shortRabi1Label => r'Rabi. tôi'; + String get searchDataGridFilteringLabel => r'搜索'; @override - String get shortRabi2Label => r'Rabi. II'; + String get selectAllDataGridFilteringLabel => r'全选'; @override - String get shortRajabLabel => r'Raj.'; + String get series => r'系列'; @override - String get shortRamadanLabel => r'Ram.'; + String get shaabanLabel => r'沙班'; @override - String get shortSafarLabel => r'Két sắt.'; + String get shawwalLabel => r'肖瓦尔'; @override - String get shortShaabanLabel => r'Sha.'; + String get shortDhualhiLabel => + r'Dhu' + "'" + r'l-H'; @override - String get shortShawwalLabel => r'Shaw.'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override - String get todayLabel => r'Hôm nay'; + String get shortJumada1Label => r'滚。我'; @override - String get weeknumberLabel => r'Tuần'; -} + String get shortJumada2Label => r'滚。二'; -/// The translations for Chinese (`zh`). -class SfLocalizationsZh extends SfGlobalLocalizations { - /// Creating an argument constructor of SfLocalizationsZh class - const SfLocalizationsZh({ - String localeName = 'zh', - }) : super( - localeName: localeName, - ); + @override + String get shortMuharramLabel => r'嗯。'; @override - String get allDayLabel => r'一整天'; + String get shortRabi1Label => r'拉比。我'; @override - String get allowedViewDayLabel => r'日'; + String get shortRabi2Label => r'拉比。二'; @override - String get allowedViewMonthLabel => r'月'; + String get shortRajabLabel => r'拉吉。'; @override - String get allowedViewScheduleLabel => r'日程'; + String get shortRamadanLabel => r'内存。'; @override - String get allowedViewTimelineDayLabel => r'时间表日'; + String get shortSafarLabel => r'安全。'; @override - String get allowedViewTimelineMonthLabel => r'时间线月份'; + String get shortShaabanLabel => r'沙。'; @override - String get allowedViewTimelineWeekLabel => r'时间线周'; + String get shortShawwalLabel => r'肖。'; @override - String get allowedViewTimelineWorkWeekLabel => r'时间表工作周'; + String get showRowsWhereDataGridFilteringLabel => r'显示行在哪里'; @override - String get allowedViewWeekLabel => r'星期'; + String get sortAToZDataGridFilteringLabel => r'从 A 到 Z 排序'; @override - String get allowedViewWorkWeekLabel => r'工作周'; + String get sortAndFilterDataGridFilteringLabel => r'排序和过滤'; @override - String get daySpanCountLabel => r'日'; + String get sortLargestToSmallestDataGridFilteringLabel => r'从大到小排序'; @override - String get dhualhiLabel => r'杜哈杰'; + String get sortNewestToOldestDataGridFilteringLabel => r'从最新到最旧排序'; @override - String get dhualqiLabel => r'杜阿尔-基达'; + String get sortOldestToNewestDataGridFilteringLabel => r'从旧到新排序'; @override - String get jumada1Label => r'胡玛达·奥瓦尔'; + String get sortSmallestToLargestDataGridFilteringLabel => r'从小到大排序'; @override - String get jumada2Label => r'朱马达·萨尼'; + String get sortZToADataGridFilteringLabel => r'将 Z 排序为 A'; @override - String get muharramLabel => r'穆哈拉姆'; + String get textFiltersDataGridFilteringLabel => r'文本过滤器'; @override - String get noEventsCalendarLabel => r'没有事件'; + String get todayLabel => r'今天'; @override - String get noSelectedDateCalendarLabel => r'未选择日期'; + String get weeknumberLabel => r'星期'; +} + +/// The translations for Chinese, using the Han script (`zh_Hans`). +class SfLocalizationsZhHans extends SfLocalizationsZh { + /// Creating an argument constructor of SfLocalizationsZhHans class + const SfLocalizationsZhHans({String localeName = 'zh_Hans'}) + : super(localeName: localeName); +} + +/// The translations for Chinese, using the Han script (`zh_Hant`). +class SfLocalizationsZhHant extends SfLocalizationsZh { + /// Creating an argument constructor of SfLocalizationsZhHant class + const SfLocalizationsZhHant({String localeName = 'zh_Hant'}) + : super(localeName: localeName); @override - String get ofDataPagerLabel => r'的'; + String get afterDataGridFilteringLabel => r'後'; @override - String get pagesDataPagerLabel => r'页面'; + String get afterOrEqualDataGridFilteringLabel => r'等於或之後'; @override - String get passwordDialogContentLabel => r'输入密码以打开此 PDF 文件'; + String get allowedViewTimelineDayLabel => r'時間軸日'; @override - String get passwordDialogHeaderTextLabel => r'密码保护'; + String get allowedViewTimelineMonthLabel => r'時間表月份'; @override - String get passwordDialogHintTextLabel => r'输入密码'; + String get allowedViewTimelineWeekLabel => r'時間線週'; @override - String get passwordDialogInvalidPasswordLabel => r'无效的密码'; + String get allowedViewTimelineWorkWeekLabel => r'時間表工作週'; @override - String get pdfBookmarksLabel => r'书签'; + String get allowedViewWorkWeekLabel => r'工作週'; @override - String get pdfEnterPageNumberLabel => r'输入页码'; + String get beforeDataGridFilteringLabel => r'前'; @override - String get pdfGoToPageLabel => r'转到页面'; + String get beforeOrEqualDataGridFilteringLabel => r'早於或等於'; @override - String get pdfInvalidPageNumberLabel => r'请输入有效号码'; + String get beginsWithDataGridFilteringLabel => r'開始於'; @override - String get pdfNoBookmarksLabel => r'未找到书签'; + String get clearFilterDataGridFilteringLabel => r'清除過濾器'; @override - String get pdfPaginationDialogCancelLabel => r'取消'; + String get dateFiltersDataGridFilteringLabel => r'日期過濾器'; @override - String get pdfPaginationDialogOkLabel => r'好的'; + String get doesNotBeginWithDataGridFilteringLabel => r'不以開頭'; @override - String get pdfPasswordDialogCancelLabel => r'取消'; + String get doesNotEndWithDataGridFilteringLabel => r'不結束於'; @override - String get pdfPasswordDialogOpenLabel => r'打开'; + String get doesNotEqualDataGridFilteringLabel => r'不等於'; @override - String get pdfScrollStatusOfLabel => r'的'; + String get endsWithDataGridFilteringLabel => r'以。。結束'; @override - String get rabi1Label => r'拉比奥瓦尔'; + String get equalsDataGridFilteringLabel => r'等於'; @override - String get rabi2Label => r'拉比阿勒萨尼'; + String get fromDataGridFilteringLabel => r'從'; @override - String get rajabLabel => r'拉贾布'; + String get greaterThanOrEqualDataGridFilteringLabel => r'大於或等於'; @override - String get ramadanLabel => r'斋月'; + String get jumada1Label => r'朱馬達·阿瓦瓦爾'; @override - String get rowsPerPageDataPagerLabel => r'每页行数'; + String get jumada2Label => r'祖瑪達·阿薩尼'; @override - String get safarLabel => r'萨法尔'; + String get lessThanDataGridFilteringLabel => r'少於'; @override - String get series => r'系列'; + String get lessThanOrEqualDataGridFilteringLabel => r'小於或等於'; @override - String get shaabanLabel => r'沙班'; + String get muharramLabel => r'回曆'; @override - String get shawwalLabel => r'肖瓦尔'; + String get noEventsCalendarLabel => r'沒有活動'; @override - String get shortDhualhiLabel => r'杜尔-H'; + String get noMatchesDataGridFilteringLabel => r'無匹配'; @override - String get shortDhualqiLabel => r'杜尔-Q'; + String get noSelectedDateCalendarLabel => r'沒有選擇日期'; @override - String get shortJumada1Label => r'朱姆。一世'; + String get notNullDataGridFilteringLabel => r'不為空'; @override - String get shortJumada2Label => r'朱姆。二'; + String get nullDataGridFilteringLabel => r'無效的'; @override - String get shortMuharramLabel => r'嗯。'; + String get numberFiltersDataGridFilteringLabel => r'數字過濾器'; @override - String get shortRabi1Label => r'拉比。一世'; + String get pagesDataPagerLabel => r'頁數'; @override - String get shortRabi2Label => r'拉比。二'; + String get passwordDialogContentLabel => r'輸入密碼打開此 PDF 文件'; @override - String get shortRajabLabel => r'拉吉。'; + String get passwordDialogHeaderTextLabel => r'密碼保護'; @override - String get shortRamadanLabel => r'内存。'; + String get passwordDialogHintTextLabel => r'輸入密碼'; @override - String get shortSafarLabel => r'安全。'; + String get passwordDialogInvalidPasswordLabel => r'無效的密碼'; @override - String get shortShaabanLabel => r'沙。'; + String get pdfBookmarksLabel => r'書籤'; @override - String get shortShawwalLabel => r'肖。'; + String get pdfEnterPageNumberLabel => r'輸入頁碼'; @override - String get todayLabel => r'今天'; + String get pdfGoToPageLabel => r'轉到頁面'; @override - String get weeknumberLabel => r'星期'; -} + String get pdfHyperlinkContentLabel => r'你想打開頁面嗎'; -/// The translations for Chinese, using the Han script (`zh_Hans`). -class SfLocalizationsZhHans extends SfLocalizationsZh { - /// Creating an argument constructor of SfLocalizationsZhHans class - const SfLocalizationsZhHans({ - String localeName = 'zh_Hans', - }) : super( - localeName: localeName, - ); -} + @override + String get pdfHyperlinkDialogOpenLabel => r'打開'; -/// The translations for Chinese, using the Han script (`zh_Hant`). -class SfLocalizationsZhHant extends SfLocalizationsZh { - /// Creating an argument constructor of SfLocalizationsZhHant class - const SfLocalizationsZhHant({ - String localeName = 'zh_Hant', - }) : super( - localeName: localeName, - ); + @override + String get pdfHyperlinkLabel => r'打開網頁'; @override - String get allowedViewTimelineDayLabel => r'時間表日'; + String get pdfInvalidPageNumberLabel => r'請輸入有效號碼'; @override - String get allowedViewTimelineMonthLabel => r'時間線月份'; + String get pdfNoBookmarksLabel => r'找不到書籤'; @override - String get allowedViewTimelineWeekLabel => r'時間線週'; + String get pdfPaginationDialogOkLabel => r'好的'; @override - String get allowedViewTimelineWorkWeekLabel => r'時間表工作週'; + String get pdfPasswordDialogOpenLabel => r'開啟'; @override - String get allowedViewWorkWeekLabel => r'工作週'; + String get pdfSignaturePadDialogHeaderTextLabel => r'畫出你的簽名'; @override - String get dhualhiLabel => r'杜哈傑'; + String get pdfSignaturePadDialogPenColorLabel => r'筆顏色'; @override - String get dhualqiLabel => r'杜阿爾-基達'; + String get pdfSignaturePadDialogSaveLabel => r'節省'; @override - String get jumada1Label => r'胡瑪達·奧瓦爾'; + String get rabi1Label => r'拉比·奧瓦爾'; @override - String get jumada2Label => r'朱馬達·薩尼'; + String get pdfTextSelectionMenuCopyLabel => r'複製'; @override - String get noEventsCalendarLabel => r'沒有事件'; + String get pdfTextSelectionMenuHighlightLabel => r'標記'; @override - String get noSelectedDateCalendarLabel => r'未選擇日期'; + String get pdfTextSelectionMenuSquigglyLabel => r'波浪線'; @override - String get pagesDataPagerLabel => r'頁面'; + String get pdfTextSelectionMenuStrikethroughLabel => r'刪除線'; @override - String get passwordDialogContentLabel => r'輸入密碼以打開此 PDF 文件'; + String get pdfTextSelectionMenuUnderlineLabel => r'下划線'; @override - String get passwordDialogHeaderTextLabel => r'密碼保護'; + String get rabi2Label => r'拉比阿勒薩尼'; @override - String get passwordDialogHintTextLabel => r'輸入密碼'; + String get rajabLabel => r'拉傑卜'; @override - String get passwordDialogInvalidPasswordLabel => r'無效的密碼'; + String get ramadanLabel => r'齋月'; @override - String get pdfBookmarksLabel => r'書籤'; + String get rowsPerPageDataPagerLabel => r'每頁行數'; @override - String get pdfEnterPageNumberLabel => r'輸入頁碼'; + String get safarLabel => r'薩法爾'; @override - String get pdfGoToPageLabel => r'轉到頁面'; + String get selectAllDataGridFilteringLabel => r'全選'; @override - String get pdfInvalidPageNumberLabel => r'請輸入有效號碼'; + String get shawwalLabel => r'肖瓦爾'; @override - String get pdfNoBookmarksLabel => r'未找到書籤'; + String get shortJumada1Label => r'滾。我'; @override - String get pdfPasswordDialogOpenLabel => r'打開'; + String get shortJumada2Label => r'滾。二'; @override - String get rabi1Label => r'拉比奧瓦爾'; + String get shortRamadanLabel => r'內存。'; @override - String get rabi2Label => r'拉比阿勒薩尼'; + String get showRowsWhereDataGridFilteringLabel => r'顯示行在哪裡'; @override - String get rajabLabel => r'拉賈布'; + String get sortAToZDataGridFilteringLabel => r'從 A 到 Z 排序'; @override - String get ramadanLabel => r'齋月'; + String get sortAndFilterDataGridFilteringLabel => r'排序和過濾'; @override - String get rowsPerPageDataPagerLabel => r'每頁行數'; + String get sortLargestToSmallestDataGridFilteringLabel => r'從大到小排序'; @override - String get safarLabel => r'薩法爾'; + String get sortNewestToOldestDataGridFilteringLabel => r'從最新到最舊排序'; @override - String get shawwalLabel => r'肖瓦爾'; + String get sortOldestToNewestDataGridFilteringLabel => r'從舊到新排序'; @override - String get shortDhualhiLabel => r'杜爾-H'; + String get sortSmallestToLargestDataGridFilteringLabel => r'從小到大排序'; @override - String get shortDhualqiLabel => r'杜爾-Q'; + String get sortZToADataGridFilteringLabel => r'將 Z 排序為 A'; @override - String get shortRamadanLabel => r'內存。'; + String get textFiltersDataGridFilteringLabel => r'文本過濾器'; } /// The translations for Chinese, as used in Hong Kong, using the Han script (`zh_Hant_HK`). class SfLocalizationsZhHantHk extends SfLocalizationsZhHant { /// Creating an argument constructor of SfLocalizationsZhHantHk class - const SfLocalizationsZhHantHk({ - String localeName = 'zh_Hant_HK', - }) : super( - localeName: localeName, - ); + const SfLocalizationsZhHantHk({String localeName = 'zh_Hant_HK'}) + : super(localeName: localeName); } /// The translations for Chinese, as used in Taiwan, using the Han script (`zh_Hant_TW`). class SfLocalizationsZhHantTw extends SfLocalizationsZhHant { /// Creating an argument constructor of SfLocalizationsZhHantTw class - const SfLocalizationsZhHantTw({ - String localeName = 'zh_Hant_TW', - }) : super( - localeName: localeName, - ); + const SfLocalizationsZhHantTw({String localeName = 'zh_Hant_TW'}) + : super(localeName: localeName); + + @override + String get pdfPasswordDialogOpenLabel => r'打開'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'標示'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'下劃線'; } /// The translations for Zulu (`zu`). class SfLocalizationsZu extends SfGlobalLocalizations { /// Creating an argument constructor of SfLocalizationsZu class - const SfLocalizationsZu({ - String localeName = 'zu', - }) : super( - localeName: localeName, - ); + const SfLocalizationsZu({String localeName = 'zu'}) + : super(localeName: localeName); + + @override + String get afterDataGridFilteringLabel => r'Ngemva'; + + @override + String get afterOrEqualDataGridFilteringLabel => r'Ngemva Noma Ukulingana'; @override String get allDayLabel => r'Usuku lonke'; @@ -13598,6 +27001,31 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get allowedViewWorkWeekLabel => r'Iviki Lomsebenzi'; + @override + String get andDataGridFilteringLabel => r'Futhi'; + + @override + String get beforeDataGridFilteringLabel => r'Ngaphambili'; + + @override + String get beforeOrEqualDataGridFilteringLabel => + r'Ngaphambi Noma Ukulingana'; + + @override + String get beginsWithDataGridFilteringLabel => r'Iqala Nga'; + + @override + String get cancelDataGridFilteringLabel => r'Khansela'; + + @override + String get clearFilterDataGridFilteringLabel => r'Sula Isihlungi'; + + @override + String get containsDataGridFilteringLabel => r'Iqukethe'; + + @override + String get dateFiltersDataGridFilteringLabel => r'Izihlungi Zedethi'; + @override String get daySpanCountLabel => r'Usuku'; @@ -13605,7 +27033,41 @@ class SfLocalizationsZu extends SfGlobalLocalizations { String get dhualhiLabel => r'Dhu al-Hijjah'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => + r'Dhu al-Qi' + "'" + r'dah'; + + @override + String get doesNotBeginWithDataGridFilteringLabel => r'Ayiqali Nge'; + + @override + String get doesNotContainDataGridFilteringLabel => r'Ayiqukethe'; + + @override + String get doesNotEndWithDataGridFilteringLabel => r'Akugcini Nge'; + + @override + String get doesNotEqualDataGridFilteringLabel => r'Ayilingani'; + + @override + String get emptyDataGridFilteringLabel => r'Akunalutho'; + + @override + String get endsWithDataGridFilteringLabel => r'Iphetha Nge'; + + @override + String get equalsDataGridFilteringLabel => r'Kuyalingana'; + + @override + String get fromDataGridFilteringLabel => r'Kusuka'; + + @override + String get greaterThanDataGridFilteringLabel => r'Okukhulu Kunokuthi'; + + @override + String get greaterThanOrEqualDataGridFilteringLabel => + r'Okukhulu Kunokuba Kulinganayo'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -13613,18 +27075,46 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get jumada2Label => r'Jumada al-thani'; + @override + String get lessThanDataGridFilteringLabel => r'Ngaphansi kwe'; + + @override + String get lessThanOrEqualDataGridFilteringLabel => + r'Ngaphansi Kwanoma Kuyalingana'; + @override String get muharramLabel => r'Muharram'; @override String get noEventsCalendarLabel => r'Ayikho imicimbi'; + @override + String get noMatchesDataGridFilteringLabel => r'Akukho okufanayo'; + @override String get noSelectedDateCalendarLabel => r'Ayikho idethi ekhethiwe'; + @override + String get notEmptyDataGridFilteringLabel => r'Akunalutho'; + + @override + String get notNullDataGridFilteringLabel => r'Hhayi Null'; + + @override + String get nullDataGridFilteringLabel => r'Null'; + + @override + String get numberFiltersDataGridFilteringLabel => r'Izihlungi Zezinombolo'; + @override String get ofDataPagerLabel => r'kwe'; + @override + String get okDataGridFilteringLabel => r'KULUNGILE'; + + @override + String get orDataGridFilteringLabel => r'Noma'; + @override String get pagesDataPagerLabel => r'amakhasi'; @@ -13650,6 +27140,18 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get pdfGoToPageLabel => r'Yiya ekhasini'; + @override + String get pdfHyperlinkContentLabel => r'Ingabe ufuna ukuvula ikhasi ku'; + + @override + String get pdfHyperlinkDialogCancelLabel => r'KHANSELA'; + + @override + String get pdfHyperlinkDialogOpenLabel => r'VULA'; + + @override + String get pdfHyperlinkLabel => r'Vula Ikhasi Lewebhu'; + @override String get pdfInvalidPageNumberLabel => r'Sicela ufake inombolo evumelekile'; @@ -13672,10 +27174,43 @@ class SfLocalizationsZu extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'kwe'; @override - String get rabi1Label => r'Rabi' "'" r' al-awwal'; + String get pdfSignaturePadDialogClearLabel => r'SULA'; + + @override + String get pdfSignaturePadDialogHeaderTextLabel => r'Dweba isiginesha yakho'; + + @override + String get pdfSignaturePadDialogPenColorLabel => r'Umbala Wepeni'; + + @override + String get pdfSignaturePadDialogSaveLabel => r'GCINA'; + + @override + String get pdfTextSelectionMenuCopyLabel => r'Kopisha'; + + @override + String get pdfTextSelectionMenuHighlightLabel => r'Gqamisa'; + + @override + String get pdfTextSelectionMenuSquigglyLabel => r'Squiggly'; @override - String get rabi2Label => r'Rabi' "'" r' al-thani'; + String get pdfTextSelectionMenuStrikethroughLabel => r'Thayela'; + + @override + String get pdfTextSelectionMenuUnderlineLabel => r'Dwebela'; + + @override + String get rabi1Label => + r'Rabi' + "'" + r' al-awwal'; + + @override + String get rabi2Label => + r'Rabi' + "'" + r' al-thani'; @override String get rajabLabel => r'Rajab'; @@ -13689,20 +27224,35 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get safarLabel => r'I-Safar'; + @override + String get searchDataGridFilteringLabel => r'Sesha'; + + @override + String get selectAllDataGridFilteringLabel => r'Khetha konke'; + @override String get series => r'Uchungechunge'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => + r'Sha' + "'" + r'aban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'U-Dhu' "'" r'l-H'; + String get shortDhualhiLabel => + r'U-Dhu' + "'" + r'l-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => + r'Dhu' + "'" + r'l-Q'; @override String get shortJumada1Label => r'Jum. I'; @@ -13734,6 +27284,37 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get shortShawwalLabel => r'Shaw.'; + @override + String get showRowsWhereDataGridFilteringLabel => r'Bonisa imigqa lapho'; + + @override + String get sortAToZDataGridFilteringLabel => r'Hlunga A ukuya ku-Z'; + + @override + String get sortAndFilterDataGridFilteringLabel => r'Hlunga futhi Hlunga'; + + @override + String get sortLargestToSmallestDataGridFilteringLabel => + r'Hlunga Okukhulu Kuye Kokuncane Kakhulu'; + + @override + String get sortNewestToOldestDataGridFilteringLabel => + r'Hlunga Okusha Kwakudala'; + + @override + String get sortOldestToNewestDataGridFilteringLabel => + r'Hlunga Okudala Kunazo Zonke'; + + @override + String get sortSmallestToLargestDataGridFilteringLabel => + r'Hlunga Okuncane Ukuya Kwekhulu Kakhulu'; + + @override + String get sortZToADataGridFilteringLabel => r'Hlunga u-Z ukuya ku-A'; + + @override + String get textFiltersDataGridFilteringLabel => r'Izihlungi Zombhalo'; + @override String get todayLabel => r'Namuhla'; @@ -13742,83 +27323,86 @@ class SfLocalizationsZu extends SfGlobalLocalizations { } /// The set of supported languages, as language code strings. -final Set kSyncfusionSupportedLanguages = - HashSet.from(const [ - 'af', // Afrikaans - 'am', // Amharic - 'ar', // Arabic - 'az', // Azerbaijani - 'be', // Belarusian - 'bg', // Bulgarian - 'bn', // Bengali Bangla - 'bs', // Bosnian - 'ca', // Catalan Valencian - 'cs', // Czech - 'da', // Danish - 'de', // German - 'el', // Modern Greek - 'en', // English - 'es', // Spanish Castilian - 'et', // Estonian - 'eu', // Basque - 'fa', // Persian - 'fi', // Finnish - 'fil', // Filipino Pilipino - 'fr', // French - 'gl', // Galician - 'gu', // Gujarati - 'he', // Hebrew - 'hi', // Hindi - 'hr', // Croatian - 'hu', // Hungarian - 'hy', // Armenian - 'id', // Indonesian - 'is', // Icelandic - 'it', // Italian - 'ja', // Japanese - 'ka', // Georgian - 'kk', // Kazakh - 'km', // Khmer Central Khmer - 'kn', // Kannada - 'ko', // Korean - 'ky', // Kirghiz Kyrgyz - 'lo', // Lao - 'lt', // Lithuanian - 'lv', // Latvian - 'mk', // Macedonian - 'ml', // Malayalam - 'mn', // Mongolian - 'mr', // Marathi - 'ms', // Malay - 'my', // Burmese - 'nb', // Norwegian Bokmål - 'ne', // Nepali - 'nl', // Dutch Flemish - 'pa', // Panjabi Punjabi - 'pl', // Polish - 'ps', // Pushto Pashto - 'pt', // Portuguese - 'ro', // Romanian Moldavian Moldovan - 'ru', // Russian - 'si', // Sinhala Sinhalese - 'sk', // Slovak - 'sl', // Slovenian - 'sq', // Albanian - 'sr', // Serbian - 'sv', // Swedish - 'sw', // Swahili - 'ta', // Tamil - 'te', // Telugu - 'th', // Thai - 'tl', // Tagalog - 'tr', // Turkish - 'uk', // Ukrainian - 'ur', // Urdu - 'uz', // Uzbek - 'vi', // Vietnamese - 'zh', // Chinese - 'zu', // Zulu -]); +final Set kSyncfusionSupportedLanguages = HashSet.from( + const [ + 'af', // Afrikaans + 'am', // Amharic + 'ar', // Arabic + 'az', // Azerbaijani + 'be', // Belarusian + 'bg', // Bulgarian + 'bn', // Bengali Bangla + 'bs', // Bosnian + 'ca', // Catalan Valencian + 'cs', // Czech + 'da', // Danish + 'de', // German + 'el', // Modern Greek + 'en', // English + 'es', // Spanish Castilian + 'et', // Estonian + 'eu', // Basque + 'fa', // Persian + 'fi', // Finnish + 'fil', // Filipino Pilipino + 'fr', // French + 'gl', // Galician + 'gu', // Gujarati + 'he', // Hebrew + 'hi', // Hindi + 'hr', // Croatian + 'hu', // Hungarian + 'hy', // Armenian + 'id', // Indonesian + 'is', // Icelandic + 'it', // Italian + 'ja', // Japanese + 'ka', // Georgian + 'kk', // Kazakh + 'km', // Khmer Central Khmer + 'kn', // Kannada + 'ko', // Korean + 'ky', // Kirghiz Kyrgyz + 'lo', // Lao + 'lt', // Lithuanian + 'lv', // Latvian + 'mk', // Macedonian + 'ml', // Malayalam + 'mn', // Mongolian + 'mr', // Marathi + 'ms', // Malay + 'my', // Burmese + 'nb', // Norwegian Bokmål + 'ne', // Nepali + 'nl', // Dutch Flemish + 'no', // Norwegian + 'or', // Oriya + 'pa', // Panjabi Punjabi + 'pl', // Polish + 'ps', // Pushto Pashto + 'pt', // Portuguese + 'ro', // Romanian Moldavian Moldovan + 'ru', // Russian + 'si', // Sinhala Sinhalese + 'sk', // Slovak + 'sl', // Slovenian + 'sq', // Albanian + 'sr', // Serbian + 'sv', // Swedish + 'sw', // Swahili + 'ta', // Tamil + 'te', // Telugu + 'th', // Thai + 'tl', // Tagalog + 'tr', // Turkish + 'uk', // Ukrainian + 'ur', // Urdu + 'uz', // Uzbek + 'vi', // Vietnamese + 'zh', // Chinese + 'zu', // Zulu + ], +); /// Creates a [SfGlobalLocalizations] instance for the given `locale`. /// @@ -13880,6 +27464,8 @@ final Set kSyncfusionSupportedLanguages = /// * `nb` - Norwegian Bokmål /// * `ne` - Nepali /// * `nl` - Dutch Flemish +/// * `no` - Norwegian +/// * `or` - Oriya /// * `pa` - Panjabi Punjabi /// * `pl` - Polish /// * `ps` - Pushto Pashto @@ -13908,9 +27494,7 @@ final Set kSyncfusionSupportedLanguages = /// /// Generally speaking, this method is only intended to be used by /// [SfGlobalLocalizations.delegate]. -SfGlobalLocalizations? getSyncfusionTranslation( - Locale locale, -) { +SfGlobalLocalizations? getSyncfusionTranslation(Locale locale) { switch (locale.languageCode) { case 'af': return const SfLocalizationsAf(); @@ -14012,6 +27596,10 @@ SfGlobalLocalizations? getSyncfusionTranslation( return const SfLocalizationsNe(); case 'nl': return const SfLocalizationsNl(); + case 'no': + return const SfLocalizationsNo(); + case 'or': + return const SfLocalizationsOr(); case 'pa': return const SfLocalizationsPa(); case 'pl': @@ -14099,7 +27687,9 @@ SfGlobalLocalizations? getSyncfusionTranslation( case 'zu': return const SfLocalizationsZu(); } - assert(false, - 'getSyncfusionTranslation() called for unsupported locale "$locale"'); + assert( + false, + 'getSyncfusionTranslation() called for unsupported locale "$locale"', + ); return null; } diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb index 31897ca06..baef03736 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Geen gekose datum nie", +"noSelectedDateCalendarLabel" : "Geen datum gekies", "noEventsCalendarLabel" : "Geen gebeure nie", "ofDataPagerLabel" : "van", "pagesDataPagerLabel" : "bladsye", @@ -18,14 +18,23 @@ "passwordDialogInvalidPasswordLabel" : "Ongeldige Wagwoord", "pdfPasswordDialogOpenLabel" : "OOP", "pdfPasswordDialogCancelLabel" : "KANSELLEER", +"pdfSignaturePadDialogHeaderTextLabel" : "Teken jou handtekening", +"pdfSignaturePadDialogPenColorLabel" : "Pen Kleur", +"pdfSignaturePadDialogClearLabel" : "DUIDELIK", +"pdfSignaturePadDialogSaveLabel" : "SPAAR", +"pdfTextSelectionMenuCopyLabel" : "Kopieer", +"pdfTextSelectionMenuHighlightLabel" : "Merk", +"pdfTextSelectionMenuStrikethroughLabel" : "Strek deur", +"pdfTextSelectionMenuUnderlineLabel" : "Onderstreep", +"pdfTextSelectionMenuSquigglyLabel" : "Snorkelend", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Week", "allowedViewWorkWeekLabel" : "Werksweek", "allowedViewMonthLabel" : "Maand", "allowedViewScheduleLabel" : "Skedule", "allowedViewTimelineDayLabel" : "Tydlyn Dag", -"allowedViewTimelineWeekLabel" : "Tydlynweek", -"allowedViewTimelineWorkWeekLabel" : "Tydlynwerkweek", +"allowedViewTimelineWeekLabel" : "Tydlyn Week", +"allowedViewTimelineWorkWeekLabel" : "Tydlyn Werksweek", "allowedViewTimelineMonthLabel" : "Tydlyn Maand", "todayLabel" : "Vandag", "weeknumberLabel" : "Week", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dag", -"series" : "Reeks" +"series" : "Reeks", +"pdfHyperlinkLabel" : "Maak webblad oop", +"pdfHyperlinkContentLabel" : "Wil jy die bladsy oopmaak by", +"pdfHyperlinkDialogOpenLabel" : "OOP", +"pdfHyperlinkDialogCancelLabel" : "KANSELLEER", +"afterDataGridFilteringLabel" : "Na", +"afterOrEqualDataGridFilteringLabel" : "Na Of Gelyk", +"beforeDataGridFilteringLabel" : "Voorheen", +"beforeOrEqualDataGridFilteringLabel" : "Voor Of Gelyk", +"beginsWithDataGridFilteringLabel" : "Begin Met", +"containsDataGridFilteringLabel" : "Bevat", +"doesNotBeginWithDataGridFilteringLabel" : "Begin nie met", +"doesNotContainDataGridFilteringLabel" : "Bevat nie", +"doesNotEndWithDataGridFilteringLabel" : "Eindig nie met", +"doesNotEqualDataGridFilteringLabel" : "Is Nie Gelyk nie", +"emptyDataGridFilteringLabel" : "Leeg", +"endsWithDataGridFilteringLabel" : "Eindig met", +"equalsDataGridFilteringLabel" : "Gelyk", +"greaterThanDataGridFilteringLabel" : "Groter as", +"greaterThanOrEqualDataGridFilteringLabel" : "Groter as of gelyk", +"lessThanDataGridFilteringLabel" : "Minder as", +"lessThanOrEqualDataGridFilteringLabel" : "Minder as of gelyk", +"notEmptyDataGridFilteringLabel" : "Nie leeg nie", +"notNullDataGridFilteringLabel" : "Nie Nul nie", +"nullDataGridFilteringLabel" : "Nul", +"sortSmallestToLargestDataGridFilteringLabel" : "Sorteer Kleinste Na Grootste", +"sortLargestToSmallestDataGridFilteringLabel" : "Sorteer Grootste Na Kleinste", +"sortAToZDataGridFilteringLabel" : "Sorteer A tot Z", +"sortZToADataGridFilteringLabel" : "Sorteer Z tot A", +"sortOldestToNewestDataGridFilteringLabel" : "Sorteer oudste na nuutste", +"sortNewestToOldestDataGridFilteringLabel" : "Sorteer nuutste na oudste", +"textFiltersDataGridFilteringLabel" : "Teks filters", +"numberFiltersDataGridFilteringLabel" : "Getalfilters", +"dateFiltersDataGridFilteringLabel" : "Datum filters", +"searchDataGridFilteringLabel" : "Soek", +"noMatchesDataGridFilteringLabel" : "Geen wedstryde nie", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Kanselleer", +"showRowsWhereDataGridFilteringLabel" : "Wys rye waar", +"andDataGridFilteringLabel" : "En", +"orDataGridFilteringLabel" : "Of", +"selectAllDataGridFilteringLabel" : "Selekteer alles", +"sortAndFilterDataGridFilteringLabel" : "Sorteer en Filter", +"clearFilterDataGridFilteringLabel" : "Vee filter uit", +"fromDataGridFilteringLabel" : "Van" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb index a220a6000..ef2d6997b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb @@ -9,7 +9,7 @@ "pdfScrollStatusOfLabel" : "የ", "pdfGoToPageLabel" : "ወደ ገጽ ይሂዱ", "pdfEnterPageNumberLabel" : "የገጽ ቁጥር አስገባ", -"pdfInvalidPageNumberLabel" : "እባክህ የሚሰራ ቁጥር አስገባ", +"pdfInvalidPageNumberLabel" : "እባክዎ ትክክለኛ ቁጥር ያስገቡ", "pdfPaginationDialogOkLabel" : "እሺ", "pdfPaginationDialogCancelLabel" : "ሰርዝ", "passwordDialogHeaderTextLabel" : "በይለፍ ቃል የተጠበቀ", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "የተሳሳተ የሚስጥርቃል", "pdfPasswordDialogOpenLabel" : "ክፈት", "pdfPasswordDialogCancelLabel" : "ሰርዝ", +"pdfSignaturePadDialogHeaderTextLabel" : "ፊርማዎን ይሳሉ", +"pdfSignaturePadDialogPenColorLabel" : "የብዕር ቀለም", +"pdfSignaturePadDialogClearLabel" : "አጽዳ", +"pdfSignaturePadDialogSaveLabel" : "አስቀምጥ", +"pdfTextSelectionMenuCopyLabel" : "ገልብጥ", +"pdfTextSelectionMenuHighlightLabel" : "አድምቅ", +"pdfTextSelectionMenuStrikethroughLabel" : "መጋረድ", +"pdfTextSelectionMenuUnderlineLabel" : "ይሰመርበት", +"pdfTextSelectionMenuSquigglyLabel" : "በቅንጦት", "allowedViewDayLabel" : "ቀን", "allowedViewWeekLabel" : "ሳምንት", "allowedViewWorkWeekLabel" : "የስራ ሳምንት", @@ -39,10 +48,10 @@ "rajabLabel" : "ራጀብ", "shaabanLabel" : "ሻዕባን", "ramadanLabel" : "ረመዳን", -"shawwalLabel" : "ሻውል", +"shawwalLabel" : "ሻዋል", "dhualqiLabel" : "ዙ አል-ቂዳህ", "dhualhiLabel" : "ዙልሂጃህ", -"shortMuharramLabel" : "ሙህ.", +"shortMuharramLabel" : "ሙህ", "shortSafarLabel" : "ሴፍ.", "shortRabi1Label" : "ራቢ. አይ", "shortRabi2Label" : "ራቢ. II", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ዙል-ቁ", "shortDhualhiLabel" : "ዙል-ሀ", "daySpanCountLabel" : "ቀን", -"series" : "ተከታታይ" +"series" : "ተከታታይ", +"pdfHyperlinkLabel" : "ድህረ ገጽ ክፈት", +"pdfHyperlinkContentLabel" : "ገጹን በ ላይ መክፈት ይፈልጋሉ", +"pdfHyperlinkDialogOpenLabel" : "ክፈት", +"pdfHyperlinkDialogCancelLabel" : "ሰርዝ", +"afterDataGridFilteringLabel" : "በኋላ", +"afterOrEqualDataGridFilteringLabel" : "በኋላ ወይም እኩል", +"beforeDataGridFilteringLabel" : "ከዚህ በፊት", +"beforeOrEqualDataGridFilteringLabel" : "በፊት ወይም እኩል", +"beginsWithDataGridFilteringLabel" : "ጋር ይጀምራል", +"containsDataGridFilteringLabel" : "ይዟል", +"doesNotBeginWithDataGridFilteringLabel" : "በ አይጀምርም።", +"doesNotContainDataGridFilteringLabel" : "አልያዘም።", +"doesNotEndWithDataGridFilteringLabel" : "አያልቅም።", +"doesNotEqualDataGridFilteringLabel" : "እኩል አይደለም", +"emptyDataGridFilteringLabel" : "ባዶ", +"endsWithDataGridFilteringLabel" : "ጋር ያበቃል", +"equalsDataGridFilteringLabel" : "እኩል ነው።", +"greaterThanDataGridFilteringLabel" : "ከዚያ ይበልጣል", +"greaterThanOrEqualDataGridFilteringLabel" : "ይበልጣል ወይም እኩል", +"lessThanDataGridFilteringLabel" : "ያነሰ", +"lessThanOrEqualDataGridFilteringLabel" : "ያነሰ ወይም እኩል", +"notEmptyDataGridFilteringLabel" : "ባዶ አይደለም", +"notNullDataGridFilteringLabel" : "ኑል አይደለም።", +"nullDataGridFilteringLabel" : "ከንቱ", +"sortSmallestToLargestDataGridFilteringLabel" : "ትንሹን ወደ ትልቁ ደርድር", +"sortLargestToSmallestDataGridFilteringLabel" : "ትልቁን ወደ ትንሹ ደርድር", +"sortAToZDataGridFilteringLabel" : "ከ A እስከ Z ደርድር", +"sortZToADataGridFilteringLabel" : "Z ወደ A ደርድር", +"sortOldestToNewestDataGridFilteringLabel" : "አሮጌውን ወደ አዲሱ ደርድር", +"sortNewestToOldestDataGridFilteringLabel" : "አዲሱን ወደ አሮጌው ደርድር", +"textFiltersDataGridFilteringLabel" : "የጽሑፍ ማጣሪያዎች", +"numberFiltersDataGridFilteringLabel" : "የቁጥር ማጣሪያዎች", +"dateFiltersDataGridFilteringLabel" : "የቀን ማጣሪያዎች", +"searchDataGridFilteringLabel" : "ፈልግ", +"noMatchesDataGridFilteringLabel" : "ምንም ግጥሚያዎች የሉም", +"okDataGridFilteringLabel" : "እሺ", +"cancelDataGridFilteringLabel" : "ሰርዝ", +"showRowsWhereDataGridFilteringLabel" : "ረድፎችን የት አሳይ", +"andDataGridFilteringLabel" : "እና", +"orDataGridFilteringLabel" : "ወይም", +"selectAllDataGridFilteringLabel" : "ሁሉንም ምረጥ", +"sortAndFilterDataGridFilteringLabel" : "ደርድር እና አጣራ", +"clearFilterDataGridFilteringLabel" : "ማጣሪያን አጽዳ", +"fromDataGridFilteringLabel" : "ከ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb index 12f5abb74..8c36ae06b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb @@ -3,14 +3,14 @@ "noEventsCalendarLabel" : "لا أحداث", "ofDataPagerLabel" : "من", "pagesDataPagerLabel" : "الصفحات", -"rowsPerPageDataPagerLabel" : "عدد الصفوف في الصفحة", +"rowsPerPageDataPagerLabel" : "الصفوف لكل صفحة", "pdfBookmarksLabel" : "إشارات مرجعية", "pdfNoBookmarksLabel" : "لم يتم العثور على إشارات مرجعية", "pdfScrollStatusOfLabel" : "من", "pdfGoToPageLabel" : "انتقل إلى صفحة", "pdfEnterPageNumberLabel" : "أدخل رقم الصفحة", "pdfInvalidPageNumberLabel" : "من فضلك أدخل رقما صالحا", -"pdfPaginationDialogOkLabel" : "نعم", +"pdfPaginationDialogOkLabel" : "موافق", "pdfPaginationDialogCancelLabel" : "إلغاء", "passwordDialogHeaderTextLabel" : "محمية بكلمة مرور", "passwordDialogContentLabel" : "أدخل كلمة المرور لفتح ملف PDF هذا", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "رمز مرور خاطئ", "pdfPasswordDialogOpenLabel" : "افتح", "pdfPasswordDialogCancelLabel" : "إلغاء", +"pdfSignaturePadDialogHeaderTextLabel" : "ارسم توقيعك", +"pdfSignaturePadDialogPenColorLabel" : "لون القلم", +"pdfSignaturePadDialogClearLabel" : "مسح", +"pdfSignaturePadDialogSaveLabel" : "حفظ", +"pdfTextSelectionMenuCopyLabel" : "نسخ", +"pdfTextSelectionMenuHighlightLabel" : "تسليط الضوء", +"pdfTextSelectionMenuStrikethroughLabel" : "يتوسطه خط", +"pdfTextSelectionMenuUnderlineLabel" : "تسطير", +"pdfTextSelectionMenuSquigglyLabel" : "متعرج", "allowedViewDayLabel" : "يوم", "allowedViewWeekLabel" : "أسبوع", "allowedViewWorkWeekLabel" : "أسبوع العمل", @@ -46,14 +55,58 @@ "shortSafarLabel" : "ساف.", "shortRabi1Label" : "ربيع. أنا", "shortRabi2Label" : "ربيع. ثانيًا", -"shortJumada1Label" : "جام. أنا", -"shortJumada2Label" : "جام. ثانيًا", +"shortJumada1Label" : "جم. أنا", +"shortJumada2Label" : "جم. ثانيًا", "shortRajabLabel" : "راج.", "shortShaabanLabel" : "شا.", -"shortRamadanLabel" : "الرامات الذاكرة العشوائية في الهواتف والحواسيب.", +"shortRamadanLabel" : "الرامات الذاكرة العشوائية في الهواتف والحواسيب.", "shortShawwalLabel" : "شو.", "shortDhualqiLabel" : "ذو القعدة", "shortDhualhiLabel" : "ذو الحجة", "daySpanCountLabel" : "يوم", -"series" : "مسلسل" +"series" : "سلسلة", +"pdfHyperlinkLabel" : "افتح صفحة الويب", +"pdfHyperlinkContentLabel" : "هل تريد فتح الصفحة في", +"pdfHyperlinkDialogOpenLabel" : "افتح", +"pdfHyperlinkDialogCancelLabel" : "إلغاء", +"afterDataGridFilteringLabel" : "بعد، بعدما", +"afterOrEqualDataGridFilteringLabel" : "بعد أو يساوي", +"beforeDataGridFilteringLabel" : "قبل", +"beforeOrEqualDataGridFilteringLabel" : "قبل أو يساوي", +"beginsWithDataGridFilteringLabel" : "يبدأ ب", +"containsDataGridFilteringLabel" : "يحتوي على", +"doesNotBeginWithDataGridFilteringLabel" : "لا تبدأ بـ", +"doesNotContainDataGridFilteringLabel" : "لا يحتوي", +"doesNotEndWithDataGridFilteringLabel" : "لا تنتهي بـ", +"doesNotEqualDataGridFilteringLabel" : "لا يساوي", +"emptyDataGridFilteringLabel" : "فارغة", +"endsWithDataGridFilteringLabel" : "ينتهي بـ", +"equalsDataGridFilteringLabel" : "يساوي", +"greaterThanDataGridFilteringLabel" : "أكثر من", +"greaterThanOrEqualDataGridFilteringLabel" : "أكبر من أو يساوي", +"lessThanDataGridFilteringLabel" : "أقل من", +"lessThanOrEqualDataGridFilteringLabel" : "اصغر من او يساوي", +"notEmptyDataGridFilteringLabel" : "ليس فارغًا", +"notNullDataGridFilteringLabel" : "غير فارغة", +"nullDataGridFilteringLabel" : "لا شيء", +"sortSmallestToLargestDataGridFilteringLabel" : "الفرز من الأصغر إلى الأكبر", +"sortLargestToSmallestDataGridFilteringLabel" : "الفرز من الأكبر إلى الأصغر", +"sortAToZDataGridFilteringLabel" : "فرز من الألف إلى الياء", +"sortZToADataGridFilteringLabel" : "فرز Z إلى A.", +"sortOldestToNewestDataGridFilteringLabel" : "فرز من الأقدم إلى الأحدث", +"sortNewestToOldestDataGridFilteringLabel" : "فرز من الأحدث إلى الأقدم", +"textFiltersDataGridFilteringLabel" : "مرشحات النص", +"numberFiltersDataGridFilteringLabel" : "مرشحات الرقم", +"dateFiltersDataGridFilteringLabel" : "مرشحات التاريخ", +"searchDataGridFilteringLabel" : "يبحث", +"noMatchesDataGridFilteringLabel" : "لا يوجد تطابق", +"okDataGridFilteringLabel" : "نعم", +"cancelDataGridFilteringLabel" : "إلغاء", +"showRowsWhereDataGridFilteringLabel" : "إظهار الصفوف حيث", +"andDataGridFilteringLabel" : "و", +"orDataGridFilteringLabel" : "أو", +"selectAllDataGridFilteringLabel" : "اختر الكل", +"sortAndFilterDataGridFilteringLabel" : "الفرز والتصفية", +"clearFilterDataGridFilteringLabel" : "مسح المرشح", +"fromDataGridFilteringLabel" : "من" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb index 7ecc9aad8..9f46bff4a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Səhifəyə daxil ol", "pdfEnterPageNumberLabel" : "Səhifə nömrəsini daxil edin", "pdfInvalidPageNumberLabel" : "Etibarlı nömrə daxil edin", -"pdfPaginationDialogOkLabel" : "tamam", +"pdfPaginationDialogOkLabel" : "Tamam", "pdfPaginationDialogCancelLabel" : "LƏĞV EDİN", "passwordDialogHeaderTextLabel" : "Parol Qorunur", "passwordDialogContentLabel" : "Bu PDF faylını açmaq üçün parolu daxil edin", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "etibarsız Şifrə", "pdfPasswordDialogOpenLabel" : "AÇIQ", "pdfPasswordDialogCancelLabel" : "LƏĞV EDİN", +"pdfSignaturePadDialogHeaderTextLabel" : "İmzanızı çəkin", +"pdfSignaturePadDialogPenColorLabel" : "Qələm Rəngi", +"pdfSignaturePadDialogClearLabel" : "TƏMİZLƏ", +"pdfSignaturePadDialogSaveLabel" : "YADDA SAXLA", +"pdfTextSelectionMenuCopyLabel" : "Kopyalayın", +"pdfTextSelectionMenuHighlightLabel" : "Vurğulayın", +"pdfTextSelectionMenuStrikethroughLabel" : "Üstündən xətt çək", +"pdfTextSelectionMenuUnderlineLabel" : "Altını çiz", +"pdfTextSelectionMenuSquigglyLabel" : "Dalğalı", "allowedViewDayLabel" : "Gün", "allowedViewWeekLabel" : "Həftə", "allowedViewWorkWeekLabel" : "İş həftəsi", @@ -35,7 +44,7 @@ "rabi1Label" : "Rəbiul-əvvəl", "rabi2Label" : "Rəbi əl-sani", "jumada1Label" : "Cümə əl-əvvəl", -"jumada2Label" : "Cümədə əl-sani", +"jumada2Label" : "Cüməda əl-sani", "rajabLabel" : "Rəcəb", "shaabanLabel" : "Şaban", "ramadanLabel" : "Ramazan", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Zül-Q", "shortDhualhiLabel" : "Zül-H", "daySpanCountLabel" : "Gün", -"series" : "Serial" +"series" : "Serial", +"pdfHyperlinkLabel" : "Veb səhifəni açın", +"pdfHyperlinkContentLabel" : "ünvanında səhifəni açmaq istəyirsiniz", +"pdfHyperlinkDialogOpenLabel" : "AÇIQ", +"pdfHyperlinkDialogCancelLabel" : "LƏĞV EDİN", +"afterDataGridFilteringLabel" : "sonra", +"afterOrEqualDataGridFilteringLabel" : "Sonra Və ya Bərabər", +"beforeDataGridFilteringLabel" : "Əvvəl", +"beforeOrEqualDataGridFilteringLabel" : "Əvvəl Və ya Bərabər", +"beginsWithDataGridFilteringLabel" : "ilə başlayır", +"containsDataGridFilteringLabel" : "ehtiva edir", +"doesNotBeginWithDataGridFilteringLabel" : "İlə Başlamaz", +"doesNotContainDataGridFilteringLabel" : "Tərkibində Yoxdur", +"doesNotEndWithDataGridFilteringLabel" : "İlə Bitmir", +"doesNotEqualDataGridFilteringLabel" : "Bərabər Deyil", +"emptyDataGridFilteringLabel" : "Boş", +"endsWithDataGridFilteringLabel" : "ilə bitir", +"equalsDataGridFilteringLabel" : "Bərabərdir", +"greaterThanDataGridFilteringLabel" : "Böyük", +"greaterThanOrEqualDataGridFilteringLabel" : "Böyük Və ya Bərabər", +"lessThanDataGridFilteringLabel" : "Daha az", +"lessThanOrEqualDataGridFilteringLabel" : "Az Və ya Bərabər", +"notEmptyDataGridFilteringLabel" : "Boş deyil", +"notNullDataGridFilteringLabel" : "Null deyil", +"nullDataGridFilteringLabel" : "Sıfır", +"sortSmallestToLargestDataGridFilteringLabel" : "Ən kiçikdən böyüyə çeşidləyin", +"sortLargestToSmallestDataGridFilteringLabel" : "Ən böyükdən kiçiyə çeşidləyin", +"sortAToZDataGridFilteringLabel" : "A-dan Z-yə çeşidləyin", +"sortZToADataGridFilteringLabel" : "Z-dən A sıralayın", +"sortOldestToNewestDataGridFilteringLabel" : "Ən köhnədən ən yeniyə çeşidləyin", +"sortNewestToOldestDataGridFilteringLabel" : "Ən yenidən köhnəyə çeşidləyin", +"textFiltersDataGridFilteringLabel" : "Mətn Filtrləri", +"numberFiltersDataGridFilteringLabel" : "Nömrə Filtrləri", +"dateFiltersDataGridFilteringLabel" : "Tarix Filtrləri", +"searchDataGridFilteringLabel" : "Axtar", +"noMatchesDataGridFilteringLabel" : "Uyğunluq yoxdur", +"okDataGridFilteringLabel" : "tamam", +"cancelDataGridFilteringLabel" : "Ləğv et", +"showRowsWhereDataGridFilteringLabel" : "Sətirləri harada göstərin", +"andDataGridFilteringLabel" : "Və", +"orDataGridFilteringLabel" : "Və ya", +"selectAllDataGridFilteringLabel" : "Hamısını seç", +"sortAndFilterDataGridFilteringLabel" : "Çeşidləyin və Filtr edin", +"clearFilterDataGridFilteringLabel" : "Filtri təmizləyin", +"fromDataGridFilteringLabel" : "From" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb index a80c68aa3..fd42a2766 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb @@ -1,59 +1,112 @@ { -"noSelectedDateCalendarLabel" : "Няма выбранай даты", +"noSelectedDateCalendarLabel" : "Дата не выбрана", "noEventsCalendarLabel" : "Няма падзей", "ofDataPagerLabel" : "з", -"pagesDataPagerLabel" : "старонак", -"rowsPerPageDataPagerLabel" : "Радкі на старонцы", +"pagesDataPagerLabel" : "старонкі", +"rowsPerPageDataPagerLabel" : "Радкоў на старонцы", "pdfBookmarksLabel" : "Закладкі", -"pdfNoBookmarksLabel" : "Закладак не знойдзена", +"pdfNoBookmarksLabel" : "Закладкі не знойдзены", "pdfScrollStatusOfLabel" : "з", "pdfGoToPageLabel" : "Перайсці на старонку", "pdfEnterPageNumberLabel" : "Увядзіце нумар старонкі", -"pdfInvalidPageNumberLabel" : "Калі ласка, увядзіце сапраўдны нумар", -"pdfPaginationDialogOkLabel" : "добра", -"pdfPaginationDialogCancelLabel" : "АДМЕНАЦЬ", -"passwordDialogHeaderTextLabel" : "Абаронены паролем", +"pdfInvalidPageNumberLabel" : "Увядзіце правільны нумар", +"pdfPaginationDialogOkLabel" : "ОК", +"pdfPaginationDialogCancelLabel" : "СКАСАВАЦЬ", +"passwordDialogHeaderTextLabel" : "Абарона паролем", "passwordDialogContentLabel" : "Увядзіце пароль, каб адкрыць гэты файл PDF", "passwordDialogHintTextLabel" : "Увядзіце пароль", "passwordDialogInvalidPasswordLabel" : "Няправільны пароль", -"pdfPasswordDialogOpenLabel" : "АДКРЫЦЬ", -"pdfPasswordDialogCancelLabel" : "АДМЕНАЦЬ", +"pdfPasswordDialogOpenLabel" : "АДКРЫТАЯ", +"pdfPasswordDialogCancelLabel" : "СКАСАВАЦЬ", +"pdfSignaturePadDialogHeaderTextLabel" : "Намалюйце свой подпіс", +"pdfSignaturePadDialogPenColorLabel" : "Колер ручкі", +"pdfSignaturePadDialogClearLabel" : "АЧЫСТКА", +"pdfSignaturePadDialogSaveLabel" : "ЗАХАВАЦЬ", +"pdfTextSelectionMenuCopyLabel" : "Капіяваць", +"pdfTextSelectionMenuHighlightLabel" : "Вылучыць", +"pdfTextSelectionMenuStrikethroughLabel" : "Закрэсліванне", +"pdfTextSelectionMenuUnderlineLabel" : "Падкрэсліце", +"pdfTextSelectionMenuSquigglyLabel" : "Звілістыя", "allowedViewDayLabel" : "Дзень", "allowedViewWeekLabel" : "Тыдзень", "allowedViewWorkWeekLabel" : "Працоўны тыдзень", "allowedViewMonthLabel" : "Месяц", "allowedViewScheduleLabel" : "Расклад", -"allowedViewTimelineDayLabel" : "Дзень шкалы часу", -"allowedViewTimelineWeekLabel" : "Тыдзень хронікі", -"allowedViewTimelineWorkWeekLabel" : "Тэрмін працоўнага тыдня", -"allowedViewTimelineMonthLabel" : "Месяц часавай шкалы", +"allowedViewTimelineDayLabel" : "Дзень часовай шкалы", +"allowedViewTimelineWeekLabel" : "Часавая шкала тыдня", +"allowedViewTimelineWorkWeekLabel" : "Часавая шкала працоўнага тыдня", +"allowedViewTimelineMonthLabel" : "Часавая шкала месяца", "todayLabel" : "Сёння", "weeknumberLabel" : "Тыдзень", "allDayLabel" : "Увесь дзень", "muharramLabel" : "Мухарам", "safarLabel" : "Сафар", -"rabi1Label" : "Рабі аль-авваль", -"rabi2Label" : "Рабі аль-Тані", -"jumada1Label" : "Джумада аль-авваль", -"jumada2Label" : "Джумада аль-Тані", +"rabi1Label" : "Рабі аль-аўваль", +"rabi2Label" : "Рабі аль-тані", +"jumada1Label" : "Джумада аль-аўваль", +"jumada2Label" : "Джумада аль-тані", "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", "shawwalLabel" : "Шаўваль", "dhualqiLabel" : "Зу аль-Кіда", "dhualhiLabel" : "Зу аль-Хіджа", -"shortMuharramLabel" : "Мух.", +"shortMuharramLabel" : "мух", "shortSafarLabel" : "Саф.", -"shortRabi1Label" : "Рабі. я", -"shortRabi2Label" : "Рабі. II", -"shortJumada1Label" : "Jum. я", -"shortJumada2Label" : "Jum. II", +"shortRabi1Label" : "Рабіна. я", +"shortRabi2Label" : "Рабіна. II", +"shortJumada1Label" : "Джам. я", +"shortJumada2Label" : "Джам. II", "shortRajabLabel" : "Радж.", "shortShaabanLabel" : "Ша.", -"shortRamadanLabel" : "Баран.", -"shortShawwalLabel" : "Шо.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Зуль-Х", +"shortRamadanLabel" : "Таран.", +"shortShawwalLabel" : "Шоу.", +"shortDhualqiLabel" : "Зу'л-К'ю", +"shortDhualhiLabel" : "Зу'л-Х", "daySpanCountLabel" : "Дзень", -"series" : "Серыял" +"series" : "серыял", +"pdfHyperlinkLabel" : "Адкрыць вэб-старонку", +"pdfHyperlinkContentLabel" : "Вы хочаце адкрыць старонку па адрасе", +"pdfHyperlinkDialogOpenLabel" : "АДКРЫТАЯ", +"pdfHyperlinkDialogCancelLabel" : "СКАСАВАЦЬ", +"afterDataGridFilteringLabel" : "Пасля", +"afterOrEqualDataGridFilteringLabel" : "Пасля або роўна", +"beforeDataGridFilteringLabel" : "Раней", +"beforeOrEqualDataGridFilteringLabel" : "Раней або роўна", +"beginsWithDataGridFilteringLabel" : "Пачынаецца з", +"containsDataGridFilteringLabel" : "Змяшчае", +"doesNotBeginWithDataGridFilteringLabel" : "Не пачынаецца з", +"doesNotContainDataGridFilteringLabel" : "Не ўтрымлівае", +"doesNotEndWithDataGridFilteringLabel" : "Не заканчваецца", +"doesNotEqualDataGridFilteringLabel" : "Не роўна", +"emptyDataGridFilteringLabel" : "Пусты", +"endsWithDataGridFilteringLabel" : "Заканчваецца с", +"equalsDataGridFilteringLabel" : "Роўнае", +"greaterThanDataGridFilteringLabel" : "Больш чым", +"greaterThanOrEqualDataGridFilteringLabel" : "Больш або роўна", +"lessThanDataGridFilteringLabel" : "Менш чым", +"lessThanOrEqualDataGridFilteringLabel" : "Менш або роўна", +"notEmptyDataGridFilteringLabel" : "Не пусты", +"notNullDataGridFilteringLabel" : "Не Null", +"nullDataGridFilteringLabel" : "Нуль", +"sortSmallestToLargestDataGridFilteringLabel" : "Сартаванне ад найменшага да самага вялікага", +"sortLargestToSmallestDataGridFilteringLabel" : "Сартаваць ад самага вялікага да меншага", +"sortAToZDataGridFilteringLabel" : "Сартаванне ад А да Я", +"sortZToADataGridFilteringLabel" : "Сартаваць ад Я да А", +"sortOldestToNewestDataGridFilteringLabel" : "Сартаваць ад старых да новых", +"sortNewestToOldestDataGridFilteringLabel" : "Сартаваць ад найноўшых да самых старых", +"textFiltersDataGridFilteringLabel" : "Тэкставыя фільтры", +"numberFiltersDataGridFilteringLabel" : "Лікавыя фільтры", +"dateFiltersDataGridFilteringLabel" : "Фільтры даты", +"searchDataGridFilteringLabel" : "Пошук", +"noMatchesDataGridFilteringLabel" : "Супадзенняў няма", +"okDataGridFilteringLabel" : "добра", +"cancelDataGridFilteringLabel" : "Адмяніць", +"showRowsWhereDataGridFilteringLabel" : "Паказаць радкі, дзе", +"andDataGridFilteringLabel" : "І", +"orDataGridFilteringLabel" : "Або", +"selectAllDataGridFilteringLabel" : "Абраць усё", +"sortAndFilterDataGridFilteringLabel" : "Сартаваць і фільтраваць", +"clearFilterDataGridFilteringLabel" : "Ачысціць фільтр", +"fromDataGridFilteringLabel" : "Ад" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb index 4c401acbf..d2d1a3cee 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb @@ -11,49 +11,102 @@ "pdfEnterPageNumberLabel" : "Въведете номера на страницата", "pdfInvalidPageNumberLabel" : "Моля въведете валиден номер", "pdfPaginationDialogOkLabel" : "Добре", -"pdfPaginationDialogCancelLabel" : "ОТМЕНИ", +"pdfPaginationDialogCancelLabel" : "ОТМЕНЯНЕ", "passwordDialogHeaderTextLabel" : "Защитен с парола", "passwordDialogContentLabel" : "Въведете паролата, за да отворите този PDF файл", "passwordDialogHintTextLabel" : "Въведете паролата", "passwordDialogInvalidPasswordLabel" : "Невалидна парола", "pdfPasswordDialogOpenLabel" : "ОТВОРЕНО", -"pdfPasswordDialogCancelLabel" : "ОТМЕНИ", -"allowedViewDayLabel" : "ден", -"allowedViewWeekLabel" : "седмица", +"pdfPasswordDialogCancelLabel" : "ОТМЕНЯНЕ", +"pdfSignaturePadDialogHeaderTextLabel" : "Начертайте своя подпис", +"pdfSignaturePadDialogPenColorLabel" : "Цвят на писалката", +"pdfSignaturePadDialogClearLabel" : "ИЗЧИСТИ", +"pdfSignaturePadDialogSaveLabel" : "ЗАПАЗЕТЕ", +"pdfTextSelectionMenuCopyLabel" : "Копирай", +"pdfTextSelectionMenuHighlightLabel" : "Маркирайте", +"pdfTextSelectionMenuStrikethroughLabel" : "Зачертано", +"pdfTextSelectionMenuUnderlineLabel" : "Подчертайте", +"pdfTextSelectionMenuSquigglyLabel" : "криволичещо", +"allowedViewDayLabel" : "Ден", +"allowedViewWeekLabel" : "Седмица", "allowedViewWorkWeekLabel" : "Работна седмица", -"allowedViewMonthLabel" : "месец", +"allowedViewMonthLabel" : "Месец", "allowedViewScheduleLabel" : "График", -"allowedViewTimelineDayLabel" : "Ден на хронологията", -"allowedViewTimelineWeekLabel" : "Седмица на хронологията", -"allowedViewTimelineWorkWeekLabel" : "График на работната седмица", -"allowedViewTimelineMonthLabel" : "Месец на хронологията", -"todayLabel" : "днес", -"weeknumberLabel" : "седмица", +"allowedViewTimelineDayLabel" : "Хронология на деня", +"allowedViewTimelineWeekLabel" : "Хронология на седмицата", +"allowedViewTimelineWorkWeekLabel" : "Времева линия Работна седмица", +"allowedViewTimelineMonthLabel" : "Времева линия Месец", +"todayLabel" : "Днес", +"weeknumberLabel" : "Седмица", "allDayLabel" : "Цял ден", -"muharramLabel" : "Мухарам", +"muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", "rabi1Label" : "Раби ал-аввал", -"rabi2Label" : "Раби ал-Тани", +"rabi2Label" : "Раби ал-тани", "jumada1Label" : "Джумада ал-аввал", -"jumada2Label" : "Джумада ал-Тани", +"jumada2Label" : "Джумада ал-тани", "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", -"shawwalLabel" : "Shawwal", -"dhualqiLabel" : "Dhu al-Qi'dah", +"shawwalLabel" : "Шавал", +"dhualqiLabel" : "Зу ал-Кида", "dhualhiLabel" : "Зу ал-Хиджа", -"shortMuharramLabel" : "Мъх", -"shortSafarLabel" : "Saf.", +"shortMuharramLabel" : "Мъх.", +"shortSafarLabel" : "Саф.", "shortRabi1Label" : "Раби. аз", "shortRabi2Label" : "Раби. II", -"shortJumada1Label" : "Jum. аз", -"shortJumada2Label" : "Jum. II", +"shortJumada1Label" : "Джъм. аз", +"shortJumada2Label" : "Джъм. II", "shortRajabLabel" : "Радж.", -"shortShaabanLabel" : "Ша", -"shortRamadanLabel" : "Рам.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "Рам", "shortShawwalLabel" : "Шоу.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "Зул-Кю", +"shortDhualhiLabel" : "Зул-Х", "daySpanCountLabel" : "ден", -"series" : "Серия" +"series" : "Серия", +"pdfHyperlinkLabel" : "Отворете уеб страницата", +"pdfHyperlinkContentLabel" : "Искате ли да отворите страницата на", +"pdfHyperlinkDialogOpenLabel" : "ОТВОРЕНО", +"pdfHyperlinkDialogCancelLabel" : "ОТМЕНЯНЕ", +"afterDataGridFilteringLabel" : "След", +"afterOrEqualDataGridFilteringLabel" : "След Или Равно", +"beforeDataGridFilteringLabel" : "Преди", +"beforeOrEqualDataGridFilteringLabel" : "Преди или Равно", +"beginsWithDataGridFilteringLabel" : "Започва с", +"containsDataGridFilteringLabel" : "Съдържа", +"doesNotBeginWithDataGridFilteringLabel" : "Не започва с", +"doesNotContainDataGridFilteringLabel" : "Не съдържа", +"doesNotEndWithDataGridFilteringLabel" : "Не завършва с", +"doesNotEqualDataGridFilteringLabel" : "Не е равно", +"emptyDataGridFilteringLabel" : "празна", +"endsWithDataGridFilteringLabel" : "Завършва със", +"equalsDataGridFilteringLabel" : "Се равнява", +"greaterThanDataGridFilteringLabel" : "По-голяма от", +"greaterThanOrEqualDataGridFilteringLabel" : "По-голямо от или равно", +"lessThanDataGridFilteringLabel" : "По-малко от", +"lessThanOrEqualDataGridFilteringLabel" : "По-малко или равно", +"notEmptyDataGridFilteringLabel" : "Не е празно", +"notNullDataGridFilteringLabel" : "Не е нула", +"nullDataGridFilteringLabel" : "Нула", +"sortSmallestToLargestDataGridFilteringLabel" : "Сортиране от най-малкото към най-голямото", +"sortLargestToSmallestDataGridFilteringLabel" : "Сортиране от най-големия към най-малкия", +"sortAToZDataGridFilteringLabel" : "Сортиране от А до Я", +"sortZToADataGridFilteringLabel" : "Сортиране от Я до А", +"sortOldestToNewestDataGridFilteringLabel" : "Сортиране от най-старите към най-новите", +"sortNewestToOldestDataGridFilteringLabel" : "Сортиране от най-новите към най-старите", +"textFiltersDataGridFilteringLabel" : "Текстови филтри", +"numberFiltersDataGridFilteringLabel" : "Числови филтри", +"dateFiltersDataGridFilteringLabel" : "Филтри за дата", +"searchDataGridFilteringLabel" : "Търсене", +"noMatchesDataGridFilteringLabel" : "Няма съвпадения", +"okDataGridFilteringLabel" : "Добре", +"cancelDataGridFilteringLabel" : "Отказ", +"showRowsWhereDataGridFilteringLabel" : "Покажи редове къде", +"andDataGridFilteringLabel" : "И", +"orDataGridFilteringLabel" : "Или", +"selectAllDataGridFilteringLabel" : "Избери всички", +"sortAndFilterDataGridFilteringLabel" : "Сортиране и филтриране", +"clearFilterDataGridFilteringLabel" : "Изчистване на филтъра", +"fromDataGridFilteringLabel" : "от" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb index 4b87ebc16..a1f5cffe3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "কোনো নির্বাচিত তারিখ নেই", +"noSelectedDateCalendarLabel" : "কোনো তারিখ নির্বাচিত হয়নি", "noEventsCalendarLabel" : "কোনো ঘটনা নেই", "ofDataPagerLabel" : "এর", "pagesDataPagerLabel" : "পৃষ্ঠাগুলি", @@ -13,11 +13,20 @@ "pdfPaginationDialogOkLabel" : "ঠিক আছে", "pdfPaginationDialogCancelLabel" : "বাতিল করুন", "passwordDialogHeaderTextLabel" : "পাসওয়ার্ড সুরক্ষিত", -"passwordDialogContentLabel" : "এই PDF ফাইল খুলতে পাসওয়ার্ড লিখুন", +"passwordDialogContentLabel" : "এই পিডিএফ ফাইল খুলতে পাসওয়ার্ড লিখুন", "passwordDialogHintTextLabel" : "পাসওয়ার্ড লিখুন", "passwordDialogInvalidPasswordLabel" : "অবৈধ পাসওয়ার্ড", "pdfPasswordDialogOpenLabel" : "খোলা", "pdfPasswordDialogCancelLabel" : "বাতিল করুন", +"pdfSignaturePadDialogHeaderTextLabel" : "আপনার স্বাক্ষর আঁকুন", +"pdfSignaturePadDialogPenColorLabel" : "কলমের রঙ", +"pdfSignaturePadDialogClearLabel" : "পরিষ্কার", +"pdfSignaturePadDialogSaveLabel" : "সংরক্ষণ", +"pdfTextSelectionMenuCopyLabel" : "কপি", +"pdfTextSelectionMenuHighlightLabel" : "লক্ষণীয় করা", +"pdfTextSelectionMenuStrikethroughLabel" : "স্ট্রাইকথ্রু", +"pdfTextSelectionMenuUnderlineLabel" : "আন্ডারলাইন করুন", +"pdfTextSelectionMenuSquigglyLabel" : "স্কুইগ্লি", "allowedViewDayLabel" : "দিন", "allowedViewWeekLabel" : "সপ্তাহ", "allowedViewWorkWeekLabel" : "কর্ম সপ্তাহ", @@ -30,7 +39,7 @@ "todayLabel" : "আজ", "weeknumberLabel" : "সপ্তাহ", "allDayLabel" : "সারাদিন", -"muharramLabel" : "মহররম", +"muharramLabel" : "মহরম", "safarLabel" : "সাফার", "rabi1Label" : "রবিউল আউয়াল", "rabi2Label" : "রাবি'আল-থানি", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "যুল-ক্ব", "shortDhualhiLabel" : "ধুল-এইচ", "daySpanCountLabel" : "দিন", -"series" : "সিরিজ" +"series" : "সিরিজ", +"pdfHyperlinkLabel" : "ওয়েব পেজ খুলুন", +"pdfHyperlinkContentLabel" : "আপনি পৃষ্ঠা খুলতে চান", +"pdfHyperlinkDialogOpenLabel" : "খোলা", +"pdfHyperlinkDialogCancelLabel" : "বাতিল করুন", +"afterDataGridFilteringLabel" : "পরে", +"afterOrEqualDataGridFilteringLabel" : "পরে বা সমান", +"beforeDataGridFilteringLabel" : "আগে", +"beforeOrEqualDataGridFilteringLabel" : "আগে বা সমান", +"beginsWithDataGridFilteringLabel" : "সঙ্গে শুরু", +"containsDataGridFilteringLabel" : "ধারণ করে", +"doesNotBeginWithDataGridFilteringLabel" : "দিয়ে শুরু হয় না", +"doesNotContainDataGridFilteringLabel" : "থাকে না", +"doesNotEndWithDataGridFilteringLabel" : "দিয়ে শেষ হয় না", +"doesNotEqualDataGridFilteringLabel" : "সমান না", +"emptyDataGridFilteringLabel" : "খালি", +"endsWithDataGridFilteringLabel" : "দিয়ে শেষ হয়", +"equalsDataGridFilteringLabel" : "সমান", +"greaterThanDataGridFilteringLabel" : "অপেক্ষা বৃহত্তর", +"greaterThanOrEqualDataGridFilteringLabel" : "বৃহত্তর অথবা সমান", +"lessThanDataGridFilteringLabel" : "এর চেয়ে কম", +"lessThanOrEqualDataGridFilteringLabel" : "কম বা সমান", +"notEmptyDataGridFilteringLabel" : "খালি না", +"notNullDataGridFilteringLabel" : "নাল না", +"nullDataGridFilteringLabel" : "শূন্য", +"sortSmallestToLargestDataGridFilteringLabel" : "সবচেয়ে ছোট থেকে বড় সাজান", +"sortLargestToSmallestDataGridFilteringLabel" : "সবচেয়ে বড় থেকে ছোট সাজান", +"sortAToZDataGridFilteringLabel" : "A থেকে Z সাজান", +"sortZToADataGridFilteringLabel" : "Z থেকে A সাজান", +"sortOldestToNewestDataGridFilteringLabel" : "পুরাতন থেকে নতুন বাছাই করুন", +"sortNewestToOldestDataGridFilteringLabel" : "নতুন থেকে পুরাতন সাজান", +"textFiltersDataGridFilteringLabel" : "টেক্সট ফিল্টার", +"numberFiltersDataGridFilteringLabel" : "নম্বর ফিল্টার", +"dateFiltersDataGridFilteringLabel" : "তারিখ ফিল্টার", +"searchDataGridFilteringLabel" : "অনুসন্ধান করুন", +"noMatchesDataGridFilteringLabel" : "মিল নেই", +"okDataGridFilteringLabel" : "ঠিক আছে", +"cancelDataGridFilteringLabel" : "বাতিল করুন", +"showRowsWhereDataGridFilteringLabel" : "যেখানে সারি দেখান", +"andDataGridFilteringLabel" : "এবং", +"orDataGridFilteringLabel" : "বা", +"selectAllDataGridFilteringLabel" : "সব নির্বাচন করুন", +"sortAndFilterDataGridFilteringLabel" : "বাছাই এবং ফিল্টার", +"clearFilterDataGridFilteringLabel" : "স্বচ্ছ ছাকুনী", +"fromDataGridFilteringLabel" : "থেকে" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb index a7079d27e..98eaa7228 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "Nema odabranog datuma", "noEventsCalendarLabel" : "Nema događaja", -"ofDataPagerLabel" : "of", +"ofDataPagerLabel" : "od", "pagesDataPagerLabel" : "stranice", "rowsPerPageDataPagerLabel" : "Redova po stranici", "pdfBookmarksLabel" : "Bookmarks", @@ -18,15 +18,24 @@ "passwordDialogInvalidPasswordLabel" : "Nevažeća lozinka", "pdfPasswordDialogOpenLabel" : "OTVOREN", "pdfPasswordDialogCancelLabel" : "OTKAZI", +"pdfSignaturePadDialogHeaderTextLabel" : "Nacrtajte svoj potpis", +"pdfSignaturePadDialogPenColorLabel" : "Pen Color", +"pdfSignaturePadDialogClearLabel" : "CLEAR", +"pdfSignaturePadDialogSaveLabel" : "SAVE", +"pdfTextSelectionMenuCopyLabel" : "Kopiraj", +"pdfTextSelectionMenuHighlightLabel" : "Istaknite", +"pdfTextSelectionMenuStrikethroughLabel" : "Precrtano", +"pdfTextSelectionMenuUnderlineLabel" : "Podvući", +"pdfTextSelectionMenuSquigglyLabel" : "Squiggly", "allowedViewDayLabel" : "Dan", "allowedViewWeekLabel" : "Sedmica", "allowedViewWorkWeekLabel" : "Radna sedmica", "allowedViewMonthLabel" : "Mjesec", "allowedViewScheduleLabel" : "Raspored", -"allowedViewTimelineDayLabel" : "Timeline Day", -"allowedViewTimelineWeekLabel" : "Timeline Week", -"allowedViewTimelineWorkWeekLabel" : "Vremenski okvir Radna sedmica", -"allowedViewTimelineMonthLabel" : "Timeline Month", +"allowedViewTimelineDayLabel" : "Dan vremenske linije", +"allowedViewTimelineWeekLabel" : "Sedmica vremenske linije", +"allowedViewTimelineWorkWeekLabel" : "Radna sedmica vremenske linije", +"allowedViewTimelineMonthLabel" : "Mjesec vremenske linije", "todayLabel" : "Danas", "weeknumberLabel" : "Sedmica", "allDayLabel" : "Cijeli dan", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dan", -"series" : "Serije" +"series" : "Serije", +"pdfHyperlinkLabel" : "Otvorite web stranicu", +"pdfHyperlinkContentLabel" : "Da li želite da otvorite stranicu na", +"pdfHyperlinkDialogOpenLabel" : "OTVOREN", +"pdfHyperlinkDialogCancelLabel" : "OTKAZI", +"afterDataGridFilteringLabel" : "Poslije", +"afterOrEqualDataGridFilteringLabel" : "Nakon ili jednako", +"beforeDataGridFilteringLabel" : "Prije", +"beforeOrEqualDataGridFilteringLabel" : "Prije ili jednako", +"beginsWithDataGridFilteringLabel" : "Počinje sa", +"containsDataGridFilteringLabel" : "Sadrži", +"doesNotBeginWithDataGridFilteringLabel" : "Ne počinje sa", +"doesNotContainDataGridFilteringLabel" : "Ne sadrži", +"doesNotEndWithDataGridFilteringLabel" : "Ne završava sa", +"doesNotEqualDataGridFilteringLabel" : "Nije jednako", +"emptyDataGridFilteringLabel" : "Prazan", +"endsWithDataGridFilteringLabel" : "Završava sa", +"equalsDataGridFilteringLabel" : "Jednako", +"greaterThanDataGridFilteringLabel" : "veće od", +"greaterThanOrEqualDataGridFilteringLabel" : "Veće ili jednako", +"lessThanDataGridFilteringLabel" : "Manje od", +"lessThanOrEqualDataGridFilteringLabel" : "Manje od ili jednako", +"notEmptyDataGridFilteringLabel" : "Nije prazno", +"notNullDataGridFilteringLabel" : "Ne Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Sortiraj od najmanjeg do najvećeg", +"sortLargestToSmallestDataGridFilteringLabel" : "Sortiraj od najvećeg do najmanjeg", +"sortAToZDataGridFilteringLabel" : "Sortiraj od A do Ž", +"sortZToADataGridFilteringLabel" : "Sortiraj od Z do A", +"sortOldestToNewestDataGridFilteringLabel" : "Sortiraj od najstarijih do najnovijih", +"sortNewestToOldestDataGridFilteringLabel" : "Sortiraj od najnovijeg do najstarijeg", +"textFiltersDataGridFilteringLabel" : "Filteri teksta", +"numberFiltersDataGridFilteringLabel" : "Brojčani filteri", +"dateFiltersDataGridFilteringLabel" : "Filteri datuma", +"searchDataGridFilteringLabel" : "Traži", +"noMatchesDataGridFilteringLabel" : "Nema poklapanja", +"okDataGridFilteringLabel" : "uredu", +"cancelDataGridFilteringLabel" : "Otkaži", +"showRowsWhereDataGridFilteringLabel" : "Pokaži redove gdje", +"andDataGridFilteringLabel" : "I", +"orDataGridFilteringLabel" : "Or", +"selectAllDataGridFilteringLabel" : "Označi sve", +"sortAndFilterDataGridFilteringLabel" : "Sortiraj i filtriraj", +"clearFilterDataGridFilteringLabel" : "Obriši filter", +"fromDataGridFilteringLabel" : "Od" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb index ac3526bed..43c6c6bbb 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb @@ -11,13 +11,22 @@ "pdfEnterPageNumberLabel" : "Introduïu el número de pàgina", "pdfInvalidPageNumberLabel" : "Introduïu un número vàlid", "pdfPaginationDialogOkLabel" : "D'acord", -"pdfPaginationDialogCancelLabel" : "CANCEL · LAR", +"pdfPaginationDialogCancelLabel" : "Cancel·lar", "passwordDialogHeaderTextLabel" : "Protegit amb contrasenya", "passwordDialogContentLabel" : "Introduïu la contrasenya per obrir aquest fitxer PDF", "passwordDialogHintTextLabel" : "Introduir la contrasenya", "passwordDialogInvalidPasswordLabel" : "contrasenya invàlida", "pdfPasswordDialogOpenLabel" : "OBERT", -"pdfPasswordDialogCancelLabel" : "CANCEL · LAR", +"pdfPasswordDialogCancelLabel" : "Cancel·lar", +"pdfSignaturePadDialogHeaderTextLabel" : "Dibuixa la teva signatura", +"pdfSignaturePadDialogPenColorLabel" : "Color del bolígraf", +"pdfSignaturePadDialogClearLabel" : "CLAR", +"pdfSignaturePadDialogSaveLabel" : "DESA", +"pdfTextSelectionMenuCopyLabel" : "Còpia", +"pdfTextSelectionMenuHighlightLabel" : "Ressaltar", +"pdfTextSelectionMenuStrikethroughLabel" : "Tallat", +"pdfTextSelectionMenuUnderlineLabel" : "Subratllar", +"pdfTextSelectionMenuSquigglyLabel" : "Squiggly", "allowedViewDayLabel" : "Dia", "allowedViewWeekLabel" : "Setmana", "allowedViewWorkWeekLabel" : "Setmana Laboral", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dia", -"series" : "Sèrie" +"series" : "Sèrie", +"pdfHyperlinkLabel" : "Obre la pàgina web", +"pdfHyperlinkContentLabel" : "Vols obrir la pàgina a", +"pdfHyperlinkDialogOpenLabel" : "OBERT", +"pdfHyperlinkDialogCancelLabel" : "Cancel·lar", +"afterDataGridFilteringLabel" : "Després", +"afterOrEqualDataGridFilteringLabel" : "Després o igual", +"beforeDataGridFilteringLabel" : "Abans", +"beforeOrEqualDataGridFilteringLabel" : "Abans o igual", +"beginsWithDataGridFilteringLabel" : "Comença amb", +"containsDataGridFilteringLabel" : "Conté", +"doesNotBeginWithDataGridFilteringLabel" : "No comença amb", +"doesNotContainDataGridFilteringLabel" : "No Conté", +"doesNotEndWithDataGridFilteringLabel" : "No Acaba Amb", +"doesNotEqualDataGridFilteringLabel" : "No és igual", +"emptyDataGridFilteringLabel" : "Buit", +"endsWithDataGridFilteringLabel" : "Acaba amb", +"equalsDataGridFilteringLabel" : "És igual", +"greaterThanDataGridFilteringLabel" : "Més gran que", +"greaterThanOrEqualDataGridFilteringLabel" : "Major que o igual", +"lessThanDataGridFilteringLabel" : "Menys que", +"lessThanOrEqualDataGridFilteringLabel" : "Menys que o igual", +"notEmptyDataGridFilteringLabel" : "No buit", +"notNullDataGridFilteringLabel" : "No nul", +"nullDataGridFilteringLabel" : "Nul", +"sortSmallestToLargestDataGridFilteringLabel" : "Ordena del més petit al més gran", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordena del més gran al més petit", +"sortAToZDataGridFilteringLabel" : "Ordena de la A a la Z", +"sortZToADataGridFilteringLabel" : "Ordena de Z a A", +"sortOldestToNewestDataGridFilteringLabel" : "Ordena del més antic al més recent", +"sortNewestToOldestDataGridFilteringLabel" : "Ordena el més nou al més antic", +"textFiltersDataGridFilteringLabel" : "Filtres de text", +"numberFiltersDataGridFilteringLabel" : "Filtres de nombre", +"dateFiltersDataGridFilteringLabel" : "Filtres de data", +"searchDataGridFilteringLabel" : "Cerca", +"noMatchesDataGridFilteringLabel" : "Sense coincidències", +"okDataGridFilteringLabel" : "D'acord", +"cancelDataGridFilteringLabel" : "Cancel · lar", +"showRowsWhereDataGridFilteringLabel" : "Mostra les files on", +"andDataGridFilteringLabel" : "I", +"orDataGridFilteringLabel" : "O", +"selectAllDataGridFilteringLabel" : "Seleccionar tot", +"sortAndFilterDataGridFilteringLabel" : "Ordena i filtra", +"clearFilterDataGridFilteringLabel" : "Esborra el filtre", +"fromDataGridFilteringLabel" : "Des de" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb index 61d63623a..e81125e74 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Žádné vybrané datum", +"noSelectedDateCalendarLabel" : "Žádné datum vybráno", "noEventsCalendarLabel" : "Žádné události", "ofDataPagerLabel" : "z", "pagesDataPagerLabel" : "stránky", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Neplatné heslo", "pdfPasswordDialogOpenLabel" : "OTEVŘENO", "pdfPasswordDialogCancelLabel" : "ZRUŠENÍ", +"pdfSignaturePadDialogHeaderTextLabel" : "Nakreslete svůj podpis", +"pdfSignaturePadDialogPenColorLabel" : "Barva pera", +"pdfSignaturePadDialogClearLabel" : "Vymazat", +"pdfSignaturePadDialogSaveLabel" : "ULOŽIT", +"pdfTextSelectionMenuCopyLabel" : "kopírovat", +"pdfTextSelectionMenuHighlightLabel" : "Zvýraznit", +"pdfTextSelectionMenuStrikethroughLabel" : "Přeškrtnutí", +"pdfTextSelectionMenuUnderlineLabel" : "Zdůraznit", +"pdfTextSelectionMenuSquigglyLabel" : "Zkrouceně", "allowedViewDayLabel" : "Den", "allowedViewWeekLabel" : "Týden", "allowedViewWorkWeekLabel" : "Pracovní týden", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Den", -"series" : "Série" +"series" : "Série", +"pdfHyperlinkLabel" : "Otevřete webovou stránku", +"pdfHyperlinkContentLabel" : "Chcete otevřít stránku na", +"pdfHyperlinkDialogOpenLabel" : "OTEVŘENO", +"pdfHyperlinkDialogCancelLabel" : "ZRUŠENÍ", +"afterDataGridFilteringLabel" : "Po", +"afterOrEqualDataGridFilteringLabel" : "Po nebo rovno", +"beforeDataGridFilteringLabel" : "Před", +"beforeOrEqualDataGridFilteringLabel" : "Před nebo rovno", +"beginsWithDataGridFilteringLabel" : "Začíná s", +"containsDataGridFilteringLabel" : "Obsahuje", +"doesNotBeginWithDataGridFilteringLabel" : "Nezačíná s", +"doesNotContainDataGridFilteringLabel" : "Neobsahuje", +"doesNotEndWithDataGridFilteringLabel" : "Nekončí s", +"doesNotEqualDataGridFilteringLabel" : "Nerovná se", +"emptyDataGridFilteringLabel" : "Prázdný", +"endsWithDataGridFilteringLabel" : "Končí s", +"equalsDataGridFilteringLabel" : "Rovná se", +"greaterThanDataGridFilteringLabel" : "Větší než", +"greaterThanOrEqualDataGridFilteringLabel" : "Větší než nebo rovno", +"lessThanDataGridFilteringLabel" : "Méně než", +"lessThanOrEqualDataGridFilteringLabel" : "Méně než nebo rovno", +"notEmptyDataGridFilteringLabel" : "Není prázdný", +"notNullDataGridFilteringLabel" : "Nenulový", +"nullDataGridFilteringLabel" : "Nula", +"sortSmallestToLargestDataGridFilteringLabel" : "Seřadit od nejmenšího po největší", +"sortLargestToSmallestDataGridFilteringLabel" : "Seřadit od největšího po nejmenší", +"sortAToZDataGridFilteringLabel" : "Seřadit od A do Z", +"sortZToADataGridFilteringLabel" : "Seřadit od Z do A", +"sortOldestToNewestDataGridFilteringLabel" : "Seřadit od nejstarších po nejnovější", +"sortNewestToOldestDataGridFilteringLabel" : "Řadit od nejnovějšího k nejstaršímu", +"textFiltersDataGridFilteringLabel" : "Textové filtry", +"numberFiltersDataGridFilteringLabel" : "Filtry čísel", +"dateFiltersDataGridFilteringLabel" : "Datumové filtry", +"searchDataGridFilteringLabel" : "Vyhledávání", +"noMatchesDataGridFilteringLabel" : "Žádné shody", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "zrušit", +"showRowsWhereDataGridFilteringLabel" : "Zobrazit řádky kde", +"andDataGridFilteringLabel" : "A", +"orDataGridFilteringLabel" : "Nebo", +"selectAllDataGridFilteringLabel" : "Vybrat vše", +"sortAndFilterDataGridFilteringLabel" : "Řadit a filtrovat", +"clearFilterDataGridFilteringLabel" : "Vymazat filtr", +"fromDataGridFilteringLabel" : "Z" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb index e6cbec613..c53c08434 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb @@ -18,15 +18,24 @@ "passwordDialogInvalidPasswordLabel" : "forkert kodeord", "pdfPasswordDialogOpenLabel" : "ÅBEN", "pdfPasswordDialogCancelLabel" : "AFBESTILLE", +"pdfSignaturePadDialogHeaderTextLabel" : "Tegn din signatur", +"pdfSignaturePadDialogPenColorLabel" : "Pen farve", +"pdfSignaturePadDialogClearLabel" : "Ryd", +"pdfSignaturePadDialogSaveLabel" : "GEMME", +"pdfTextSelectionMenuCopyLabel" : "Kopi", +"pdfTextSelectionMenuHighlightLabel" : "Fremhæv", +"pdfTextSelectionMenuStrikethroughLabel" : "Gennemstregning", +"pdfTextSelectionMenuUnderlineLabel" : "Understrege", +"pdfTextSelectionMenuSquigglyLabel" : "Krydret", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Uge", "allowedViewWorkWeekLabel" : "Arbejdsuge", "allowedViewMonthLabel" : "Måned", "allowedViewScheduleLabel" : "Tidsplan", "allowedViewTimelineDayLabel" : "Tidslinje Dag", -"allowedViewTimelineWeekLabel" : "Tidslinje uge", +"allowedViewTimelineWeekLabel" : "Tidslinje Uge", "allowedViewTimelineWorkWeekLabel" : "Tidslinje Arbejdsuge", -"allowedViewTimelineMonthLabel" : "Tidslinje måned", +"allowedViewTimelineMonthLabel" : "Tidslinje Måned", "todayLabel" : "I dag", "weeknumberLabel" : "Uge", "allDayLabel" : "Hele dagen", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dag", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Åbn webside", +"pdfHyperlinkContentLabel" : "Vil du åbne siden kl", +"pdfHyperlinkDialogOpenLabel" : "ÅBEN", +"pdfHyperlinkDialogCancelLabel" : "AFBESTILLE", +"afterDataGridFilteringLabel" : "Efter", +"afterOrEqualDataGridFilteringLabel" : "Efter Eller Lige", +"beforeDataGridFilteringLabel" : "Før", +"beforeOrEqualDataGridFilteringLabel" : "Før Eller Lige", +"beginsWithDataGridFilteringLabel" : "Starter med", +"containsDataGridFilteringLabel" : "Indeholder", +"doesNotBeginWithDataGridFilteringLabel" : "Begynder ikke med", +"doesNotContainDataGridFilteringLabel" : "Indeholder ikke", +"doesNotEndWithDataGridFilteringLabel" : "Slutter ikke med", +"doesNotEqualDataGridFilteringLabel" : "Er ikke ens", +"emptyDataGridFilteringLabel" : "Tom", +"endsWithDataGridFilteringLabel" : "Ender med", +"equalsDataGridFilteringLabel" : "Lige med", +"greaterThanDataGridFilteringLabel" : "Større End", +"greaterThanOrEqualDataGridFilteringLabel" : "Større end eller lige", +"lessThanDataGridFilteringLabel" : "Mindre end", +"lessThanOrEqualDataGridFilteringLabel" : "Mindre end eller lige", +"notEmptyDataGridFilteringLabel" : "Ikke tom", +"notNullDataGridFilteringLabel" : "Ikke Nul", +"nullDataGridFilteringLabel" : "Nul", +"sortSmallestToLargestDataGridFilteringLabel" : "Sorter mindst til størst", +"sortLargestToSmallestDataGridFilteringLabel" : "Sorter Størst Til Mindst", +"sortAToZDataGridFilteringLabel" : "Sorter fra A til Z", +"sortZToADataGridFilteringLabel" : "Sorter Z til A", +"sortOldestToNewestDataGridFilteringLabel" : "Sorter ældst til nyeste", +"sortNewestToOldestDataGridFilteringLabel" : "Sorter nyeste til ældste", +"textFiltersDataGridFilteringLabel" : "Tekstfiltre", +"numberFiltersDataGridFilteringLabel" : "Nummerfiltre", +"dateFiltersDataGridFilteringLabel" : "Dato filtre", +"searchDataGridFilteringLabel" : "Søg", +"noMatchesDataGridFilteringLabel" : "Ingen kampe", +"okDataGridFilteringLabel" : "Okay", +"cancelDataGridFilteringLabel" : "Afbestille", +"showRowsWhereDataGridFilteringLabel" : "Vis rækker hvor", +"andDataGridFilteringLabel" : "Og", +"orDataGridFilteringLabel" : "Eller", +"selectAllDataGridFilteringLabel" : "Vælg alle", +"sortAndFilterDataGridFilteringLabel" : "Sorter og filtrer", +"clearFilterDataGridFilteringLabel" : "Ryd filter", +"fromDataGridFilteringLabel" : "Fra" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb index 9e3d75810..b4bf1e413 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Kein ausgewähltes Datum", +"noSelectedDateCalendarLabel" : "Kein Datum ausgewählt", "noEventsCalendarLabel" : "Keine Ereignisse", "ofDataPagerLabel" : "von", "pagesDataPagerLabel" : "Seiten", @@ -16,8 +16,17 @@ "passwordDialogContentLabel" : "Geben Sie das Passwort ein, um diese PDF-Datei zu öffnen", "passwordDialogHintTextLabel" : "Passwort eingeben", "passwordDialogInvalidPasswordLabel" : "Ungültiges Passwort", -"pdfPasswordDialogOpenLabel" : "OFFEN", +"pdfPasswordDialogOpenLabel" : "ÖFFNEN", "pdfPasswordDialogCancelLabel" : "ABBRECHEN", +"pdfSignaturePadDialogHeaderTextLabel" : "Zeichnen Sie Ihre Unterschrift", +"pdfSignaturePadDialogPenColorLabel" : "Stiftfarbe", +"pdfSignaturePadDialogClearLabel" : "KLAR", +"pdfSignaturePadDialogSaveLabel" : "SPEICHERN", +"pdfTextSelectionMenuCopyLabel" : "Kopieren", +"pdfTextSelectionMenuHighlightLabel" : "Markieren", +"pdfTextSelectionMenuStrikethroughLabel" : "Durchgestrichen", +"pdfTextSelectionMenuUnderlineLabel" : "Unterstreichen", +"pdfTextSelectionMenuSquigglyLabel" : "Schnörkellos", "allowedViewDayLabel" : "Tag", "allowedViewWeekLabel" : "Woche", "allowedViewWorkWeekLabel" : "Arbeitswoche", @@ -26,10 +35,10 @@ "allowedViewTimelineDayLabel" : "Zeitleiste Tag", "allowedViewTimelineWeekLabel" : "Zeitleiste Woche", "allowedViewTimelineWorkWeekLabel" : "Zeitleiste Arbeitswoche", -"allowedViewTimelineMonthLabel" : "Zeitachse Monat", +"allowedViewTimelineMonthLabel" : "Zeitleiste Monat", "todayLabel" : "Heute", "weeknumberLabel" : "Woche", -"allDayLabel" : "Den ganzen Tag", +"allDayLabel" : "Ganztägig", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi' al-awwal", @@ -37,7 +46,7 @@ "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", -"shaabanLabel" : "Schaaban", +"shaabanLabel" : "Shaaban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qidah", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Tag", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Webseite öffnen", +"pdfHyperlinkContentLabel" : "Möchten Sie die Seite öffnen unter", +"pdfHyperlinkDialogOpenLabel" : "ÖFFNEN", +"pdfHyperlinkDialogCancelLabel" : "ABBRECHEN", +"afterDataGridFilteringLabel" : "Nach", +"afterOrEqualDataGridFilteringLabel" : "Nach oder gleich", +"beforeDataGridFilteringLabel" : "Vor", +"beforeOrEqualDataGridFilteringLabel" : "Vor oder gleich", +"beginsWithDataGridFilteringLabel" : "Beginnt mit", +"containsDataGridFilteringLabel" : "Enthält", +"doesNotBeginWithDataGridFilteringLabel" : "Beginnt nicht mit", +"doesNotContainDataGridFilteringLabel" : "Beinhaltet nicht", +"doesNotEndWithDataGridFilteringLabel" : "Endet nicht mit", +"doesNotEqualDataGridFilteringLabel" : "Ist nicht gleich", +"emptyDataGridFilteringLabel" : "Leer", +"endsWithDataGridFilteringLabel" : "Endet mit", +"equalsDataGridFilteringLabel" : "Gleich", +"greaterThanDataGridFilteringLabel" : "Größer als", +"greaterThanOrEqualDataGridFilteringLabel" : "Größer als oder gleich", +"lessThanDataGridFilteringLabel" : "Weniger als", +"lessThanOrEqualDataGridFilteringLabel" : "Weniger als oder gleich", +"notEmptyDataGridFilteringLabel" : "Nicht leer", +"notNullDataGridFilteringLabel" : "Nicht null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Vom Kleinsten zum Größten sortieren", +"sortLargestToSmallestDataGridFilteringLabel" : "Vom Größten zum Kleinsten sortieren", +"sortAToZDataGridFilteringLabel" : "A bis Z sortieren", +"sortZToADataGridFilteringLabel" : "Z bis A sortieren", +"sortOldestToNewestDataGridFilteringLabel" : "Vom Ältesten zum Neuesten sortieren", +"sortNewestToOldestDataGridFilteringLabel" : "Vom Neusten zum Ältesten sortieren", +"textFiltersDataGridFilteringLabel" : "Textfilter", +"numberFiltersDataGridFilteringLabel" : "Zahlenfilter", +"dateFiltersDataGridFilteringLabel" : "Datumsfilter", +"searchDataGridFilteringLabel" : "Suche", +"noMatchesDataGridFilteringLabel" : "Keine Treffer", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Abbrechen", +"showRowsWhereDataGridFilteringLabel" : "Zeig Zeilen wo", +"andDataGridFilteringLabel" : "Und", +"orDataGridFilteringLabel" : "Oder", +"selectAllDataGridFilteringLabel" : "Wählen Sie Alle", +"sortAndFilterDataGridFilteringLabel" : "Sortieren und filtern", +"clearFilterDataGridFilteringLabel" : "Filter löschen", +"fromDataGridFilteringLabel" : "Aus" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb index 56483a8d5..7063658ad 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Καμία επιλεγμένη ημερομηνία", +"noSelectedDateCalendarLabel" : "Δεν έχει επιλεγεί ημερομηνία", "noEventsCalendarLabel" : "Δεν υπάρχουν εκδηλώσεις", "ofDataPagerLabel" : "του", "pagesDataPagerLabel" : "σελίδες", @@ -18,13 +18,22 @@ "passwordDialogInvalidPasswordLabel" : "Λανθασμένος κωδικός", "pdfPasswordDialogOpenLabel" : "ΑΝΟΙΞΕ", "pdfPasswordDialogCancelLabel" : "ΜΑΤΑΙΩΣΗ", +"pdfSignaturePadDialogHeaderTextLabel" : "Σχεδιάστε την υπογραφή σας", +"pdfSignaturePadDialogPenColorLabel" : "Χρώμα στυλό", +"pdfSignaturePadDialogClearLabel" : "Καθαρισμός", +"pdfSignaturePadDialogSaveLabel" : "Αποθήκευση", +"pdfTextSelectionMenuCopyLabel" : "Αντιγραφή", +"pdfTextSelectionMenuHighlightLabel" : "Επισήμανση", +"pdfTextSelectionMenuStrikethroughLabel" : "Διαγραφή", +"pdfTextSelectionMenuUnderlineLabel" : "Υπογράμμιση", +"pdfTextSelectionMenuSquigglyLabel" : "Κυματιστή υπογράμμιση", "allowedViewDayLabel" : "Ημέρα", "allowedViewWeekLabel" : "Εβδομάδα", "allowedViewWorkWeekLabel" : "Εβδομάδα εργασίας", "allowedViewMonthLabel" : "Μήνας", "allowedViewScheduleLabel" : "Πρόγραμμα", -"allowedViewTimelineDayLabel" : "Ημέρα χρονολογίου", -"allowedViewTimelineWeekLabel" : "Εβδομάδα Χρονολογίου", +"allowedViewTimelineDayLabel" : "Ημέρα Χρονολογίου", +"allowedViewTimelineWeekLabel" : "Χρονολόγιο Εβδομάδα", "allowedViewTimelineWorkWeekLabel" : "Χρονολόγιο Εβδομάδα εργασίας", "allowedViewTimelineMonthLabel" : "Χρονολόγιο Μήνας", "todayLabel" : "Σήμερα", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Ημέρα", -"series" : "Σειρά" +"series" : "Σειρά", +"pdfHyperlinkLabel" : "Ανοίξτε την ιστοσελίδα", +"pdfHyperlinkContentLabel" : "Θέλετε να ανοίξετε τη σελίδα στο", +"pdfHyperlinkDialogOpenLabel" : "ΑΝΟΙΞΕ", +"pdfHyperlinkDialogCancelLabel" : "ΜΑΤΑΙΩΣΗ", +"afterDataGridFilteringLabel" : "Μετά", +"afterOrEqualDataGridFilteringLabel" : "Μετά Ή Ίσα", +"beforeDataGridFilteringLabel" : "Πριν", +"beforeOrEqualDataGridFilteringLabel" : "Πριν ή ίσο", +"beginsWithDataGridFilteringLabel" : "Ξεκινάει με", +"containsDataGridFilteringLabel" : "Περιέχει", +"doesNotBeginWithDataGridFilteringLabel" : "Δεν ξεκινά με", +"doesNotContainDataGridFilteringLabel" : "Δεν περιέχει", +"doesNotEndWithDataGridFilteringLabel" : "Δεν τελειώνει με", +"doesNotEqualDataGridFilteringLabel" : "Δεν ισούται", +"emptyDataGridFilteringLabel" : "Αδειάζω", +"endsWithDataGridFilteringLabel" : "Τελειώνει με", +"equalsDataGridFilteringLabel" : "Ίσο", +"greaterThanDataGridFilteringLabel" : "Μεγαλύτερος από", +"greaterThanOrEqualDataGridFilteringLabel" : "Μεγαλύτερο από ή ίσο", +"lessThanDataGridFilteringLabel" : "Λιγότερο από", +"lessThanOrEqualDataGridFilteringLabel" : "Λιγότερο από ή ίσο", +"notEmptyDataGridFilteringLabel" : "Οχι άδειο", +"notNullDataGridFilteringLabel" : "Οχι κενό", +"nullDataGridFilteringLabel" : "Μηδενικό", +"sortSmallestToLargestDataGridFilteringLabel" : "Ταξινόμηση από το μικρότερο προς το μεγαλύτερο", +"sortLargestToSmallestDataGridFilteringLabel" : "Ταξινόμηση από το μεγαλύτερο στο μικρότερο", +"sortAToZDataGridFilteringLabel" : "Ταξινόμηση από Α έως Ω", +"sortZToADataGridFilteringLabel" : "Ταξινόμηση Z σε A", +"sortOldestToNewestDataGridFilteringLabel" : "Ταξινόμηση από το παλαιότερο στο νεότερο", +"sortNewestToOldestDataGridFilteringLabel" : "Ταξινόμηση νεότερο προς παλαιότερο", +"textFiltersDataGridFilteringLabel" : "Φίλτρα κειμένου", +"numberFiltersDataGridFilteringLabel" : "Φίλτρα αριθμών", +"dateFiltersDataGridFilteringLabel" : "Φίλτρα ημερομηνίας", +"searchDataGridFilteringLabel" : "Αναζήτηση", +"noMatchesDataGridFilteringLabel" : "Χωρίς αγώνες", +"okDataGridFilteringLabel" : "Εντάξει", +"cancelDataGridFilteringLabel" : "Ματαίωση", +"showRowsWhereDataGridFilteringLabel" : "Εμφάνιση σειρών όπου", +"andDataGridFilteringLabel" : "Και", +"orDataGridFilteringLabel" : "Ή", +"selectAllDataGridFilteringLabel" : "Επιλογή όλων", +"sortAndFilterDataGridFilteringLabel" : "Ταξινόμηση και φιλτράρισμα", +"clearFilterDataGridFilteringLabel" : "Διαγραφή φίλτρου", +"fromDataGridFilteringLabel" : "Από" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb index faeba7bdb..d08b7c6ee 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb @@ -113,6 +113,30 @@ "type": "text" }, + "pdfSignaturePadDialogHeaderTextLabel": "Draw your signature", + "@pdfSignaturePadDialogHeaderTextLabel": { + "description": "Label that is displayed in the header of signature pad dialog in PdfViewer.", + "type": "text" + }, + + "pdfSignaturePadDialogPenColorLabel": "Pen Color", + "@pdfSignaturePadDialogPenColorLabel": { + "description": "Label that is displayed in the signature pad dialog in PdfViewer to represent the Pen Color text.", + "type": "text" + }, + + "pdfSignaturePadDialogClearLabel": "CLEAR", + "@pdfSignaturePadDialogClearLabel": { + "description": "Label that is displayed in the signature pad dialog of PdfViewer to represent the CLEAR confirmation button.", + "type": "text" + }, + + "pdfSignaturePadDialogSaveLabel": "SAVE", + "@pdfSignaturePadDialogSaveLabel": { + "description": " Label that is displayed in the signature pad dialog of PdfViewer to represent the SAVE confirmation button.", + "type": "text" + }, + "allowedViewDayLabel": "Day", "@allowedViewDayLabel": { "description": "Label that is displayed in the calendar header view when allowed views have calendar day view.", @@ -339,5 +363,298 @@ "@series": { "description": "The label is displayed as the text for the legend in the cartesian chart. When the name of the series is not specified, then this label with the series count is displayed as a legend.", "type": "text" + }, + + "pdfHyperlinkLabel": "Open Web Page", + "@pdfHyperlinkLabel": { + "description": "Label that is displayed in the hyperlink dialog header of PdfViewer.", + "type": "text" + }, + + "pdfHyperlinkContentLabel": "Do you want to open the page at", + "@pdfHyperlinkContentLabel": { + "description": "Label that is displayed in the url of the hyperlink.", + "type": "text" + }, + + "pdfHyperlinkDialogOpenLabel": "OPEN", + "@pdfHyperlinkDialogOpenLabel": { + "description": "Label that is displayed in the hyperlink dialog of PdfViewer to represent the OPEN confirmation button.", + "type": "text" + }, + + "pdfHyperlinkDialogCancelLabel": "CANCEL", + "@pdfHyperlinkDialogCancelLabel": { + "description": "Label that is displayed in the hyperlink dialog of PdfViewer to represent the CANCEL confirmation button.", + "type": "text" + }, + + "pdfTextSelectionMenuCopyLabel": "Copy", + "@pdfTextSelectionMenuCopyLabel": { + "description": "Label that is displayed in the text selection context menu of PdfViewer for copying the selected text.", + "type": "text" + }, + + "pdfTextSelectionMenuHighlightLabel": "Highlight", + "@pdfTextSelectionMenuHighlightLabel": { + "description": "Label that is displayed in the text selection context menu of PdfViewer for highlighting the selected text.", + "type": "text" + }, + + "pdfTextSelectionMenuStrikethroughLabel": "Strikethrough", + "@pdfTextSelectionMenuStrikethroughLabel": { + "description": "Label that is displayed in the text selection context menu of PdfViewer for striking through the selected text.", + "type": "text" + }, + + "pdfTextSelectionMenuUnderlineLabel": "Underline", + "@pdfTextSelectionMenuUnderlineLabel": { + "description": "Label that is displayed in the text selection context menu of PdfViewer for underlining the selected text.", + "type": "text" + }, + + "pdfTextSelectionMenuSquigglyLabel": "Squiggly", + "@pdfTextSelectionMenuSquigglyLabel": { + "description": "Label that is displayed in the text selection context menu of PdfViewer for underlining the selected text with squiggly style.", + "type": "text" + }, + + "afterDataGridFilteringLabel": "After", + "@afterDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `After` option in drop down widget.", + "type" : "text" + }, + "afterOrEqualDataGridFilteringLabel": "After Or Equal", + "@afterOrEqualDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `After Or Equal` option in drop down widget.", + "type" : "text" + }, + + "beforeDataGridFilteringLabel": "Before", + "@beforeDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Before` option in drop down widget.", + "type" : "text" + }, + + "beforeOrEqualDataGridFilteringLabel": "Before Or Equal", + "@beforeOrEqualDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Before Or Equal` option in drop down widget.", + "type" : "text" + }, + + "beginsWithDataGridFilteringLabel": "Begins With", + "@beginsWithDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Begins With` option in drop down widget.", + "type" : "text" + }, + + "containsDataGridFilteringLabel": "Contains", + "@containsDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Contains` option in drop down widget.", + "type" : "text" + }, + + "doesNotBeginWithDataGridFilteringLabel": "Does Not Begin With", + "@doesNotBeginWithDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Does Not Begin With` option in drop down widget.", + "type" : "text" + }, + + "doesNotContainDataGridFilteringLabel": "Does Not Contain", + "@doesNotContainDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Does Not Contain` option in drop down widget.", + "type" : "text" + }, + + "doesNotEndWithDataGridFilteringLabel": "Does Not End With", + "@doesNotEndWithDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Does Not End With` option in drop down widget.", + "type" : "text" + }, + + "doesNotEqualDataGridFilteringLabel": "Does Not Equal", + "@doesNotEqualDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Does Not Equal` option in drop down widget.", + "type" : "text" + }, + + "emptyDataGridFilteringLabel": "Empty", + "@emptyDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Empty` option in drop down widget.", + "type" : "text" + }, + + "endsWithDataGridFilteringLabel": "Ends With", + "@endsWithDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Ends With` option in drop down widget.", + "type" : "text" + }, + + "equalsDataGridFilteringLabel": "Equals", + "@equalsDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Equals` option in drop down widget.", + "type" : "text" + }, + + "greaterThanDataGridFilteringLabel": "Greater Than", + "@greaterThanDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Greater Than` option in drop down widget.", + "type" : "text" + }, + + "greaterThanOrEqualDataGridFilteringLabel": "Greater Than Or Equal", + "@greaterThanOrEqualDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Greater Than Or Equal` option in drop down widget.", + "type" : "text" + }, + + "lessThanDataGridFilteringLabel": "Less Than", + "@lessThanDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Less Than` option in drop down widget.", + "type" : "text" + }, + + "lessThanOrEqualDataGridFilteringLabel": "Less Than Or Equal", + "@lessThanOrEqualDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Less Than Or Equal` option in drop down widget.", + "type" : "text" + }, + + "notEmptyDataGridFilteringLabel": "Not Empty", + "@notEmptyDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Not Empty` option in drop down widget.", + "type" : "text" + }, + + "notNullDataGridFilteringLabel": "Not Null", + "@notNullDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Not Null` option in drop down widget.", + "type" : "text" + }, + + "nullDataGridFilteringLabel": "Null", + "@nullDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Null` option in drop down widget.", + "type" : "text" + }, + + "sortSmallestToLargestDataGridFilteringLabel": "Sort Smallest To Largest", + "@sortSmallestToLargestDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort Smallest To Largest` option in drop down widget.", + "type" : "text" + }, + + "sortLargestToSmallestDataGridFilteringLabel": "Sort Largest To Smallest", + "@sortLargestToSmallestDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort Largest To Smallest` option in drop down widget.", + "type" : "text" + }, + + "sortAToZDataGridFilteringLabel": "Sort A To Z", + "@sortAToZDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort A To Z` option in drop down widget.", + "type" : "text" + }, + + "sortZToADataGridFilteringLabel": "Sort Z To A", + "@sortZToADataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort Z To A` option in drop down widget.", + "type" : "text" + }, + + "sortOldestToNewestDataGridFilteringLabel": "Sort Oldest To Newest", + "@sortOldestToNewestDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort Oldest To Newest` option in drop down widget.", + "type" : "text" + }, + + "sortNewestToOldestDataGridFilteringLabel": "Sort Newest To Oldest", + "@sortNewestToOldestDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort Newest To Oldest` option in drop down widget.", + "type" : "text" + }, + + "textFiltersDataGridFilteringLabel": "Text Filters", + "@textFiltersDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Text Filters` option in drop down widget.", + "type" : "text" + }, + + "numberFiltersDataGridFilteringLabel": "Number Filters", + "@numberFiltersDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Number Filters` option in drop down widget.", + "type" : "text" + }, + + "dateFiltersDataGridFilteringLabel": "Date Filters", + "@dateFiltersDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Date Filters` option in drop down widget.", + "type" : "text" + }, + + "searchDataGridFilteringLabel": "Search", + "@searchDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Search` option in drop down widget.", + "type" : "text" + }, + + "noMatchesDataGridFilteringLabel": "No matches", + "@noMatchesDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `No matches` option in drop down widget.", + "type" : "text" + }, + + "okDataGridFilteringLabel": "OK", + "@okDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `OK` option in drop down widget.", + "type" : "text" + }, + + "cancelDataGridFilteringLabel": "Cancel", + "@cancelDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Cancel` option in drop down widget.", + "type" : "text" + }, + + "showRowsWhereDataGridFilteringLabel": "Show rows where", + "@showRowsWhereDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Show rows where` option in drop down widget.", + "type" : "text" + }, + + "andDataGridFilteringLabel": "And", + "@andDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `And` option in drop down widget.", + "type" : "text" + }, + + "orDataGridFilteringLabel": "Or", + "@orDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Or` option in drop down widget.", + "type" : "text" + }, + + "selectAllDataGridFilteringLabel": "Select All", + "@selectAllDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Select All` option in drop down widget.", + "type" : "text" + }, + + "sortAndFilterDataGridFilteringLabel": "Sort and Filter", + "@sortAndFilterDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Sort And Filter` option in drop down widget.", + "type" : "text" + }, + + "clearFilterDataGridFilteringLabel": "Clear Filter", + "@clearFilterDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `Clear Filter` text in `Clear Filter From` option in the popup menu.", + "type" : "text" + }, + + "fromDataGridFilteringLabel": "From", + "@fromDataGridFilteringLabel": { + "description" : "The label that is displayed in the filter view in SfDataGrid for `From` text in `Clear Filter From` option in the popup menu.", + "type" : "text" } } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json index 02272368f..0266acbe7 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json @@ -1,5 +1,5 @@ { - "noSelectedDateCalendarLabel": "No selected date", + "noSelectedDateCalendarLabel": "No date selected", "noEventsCalendarLabel": "No events", "ofDataPagerLabel": "of", "pagesDataPagerLabel": "pages", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel": "Invalid Password", "pdfPasswordDialogOpenLabel": "OPEN", "pdfPasswordDialogCancelLabel": "CANCEL", + "pdfSignaturePadDialogHeaderTextLabel": "Draw your signature", + "pdfSignaturePadDialogPenColorLabel": "Pen Color", + "pdfSignaturePadDialogClearLabel": "CLEAR", + "pdfSignaturePadDialogSaveLabel": "SAVE", + "pdfTextSelectionMenuCopyLabel" : "Copy", + "pdfTextSelectionMenuHighlightLabel" : "Highlight", + "pdfTextSelectionMenuStrikethroughLabel" : "Strikethrough", + "pdfTextSelectionMenuUnderlineLabel" :"Underline", + "pdfTextSelectionMenuSquigglyLabel":"Squiggly", "allowedViewDayLabel": "Day", "allowedViewWeekLabel": "Week", "allowedViewWorkWeekLabel": "Work Week", @@ -55,5 +64,49 @@ "shortDhualqiLabel": "Dhu'l-Q", "shortDhualhiLabel": "Dhu'l-H", "daySpanCountLabel": "Day", - "series": "Series" + "series": "Series", + "pdfHyperlinkLabel": "Open Web Page", + "pdfHyperlinkContentLabel": "Do you want to open the page at", + "pdfHyperlinkDialogOpenLabel": "OPEN", + "pdfHyperlinkDialogCancelLabel": "CANCEL", + "afterDataGridFilteringLabel": "After", + "afterOrEqualDataGridFilteringLabel": "After Or Equal", + "beforeDataGridFilteringLabel": "Before", + "beforeOrEqualDataGridFilteringLabel": "Before Or Equal", + "beginsWithDataGridFilteringLabel": "Begins With", + "containsDataGridFilteringLabel": "Contains", + "doesNotBeginWithDataGridFilteringLabel": "Does Not Begin With", + "doesNotContainDataGridFilteringLabel": "Does Not Contain", + "doesNotEndWithDataGridFilteringLabel": "Does Not End With", + "doesNotEqualDataGridFilteringLabel": "Does Not Equal", + "emptyDataGridFilteringLabel": "Empty", + "endsWithDataGridFilteringLabel": "Ends With", + "equalsDataGridFilteringLabel": "Equals", + "greaterThanDataGridFilteringLabel": "Greater Than", + "greaterThanOrEqualDataGridFilteringLabel": "Greater Than Or Equal", + "lessThanDataGridFilteringLabel": "Less Than", + "lessThanOrEqualDataGridFilteringLabel": "Less Than Or Equal", + "notEmptyDataGridFilteringLabel": "Not Empty", + "notNullDataGridFilteringLabel": "Not Null", + "nullDataGridFilteringLabel": "Null", + "sortSmallestToLargestDataGridFilteringLabel": "Sort Smallest To Largest", + "sortLargestToSmallestDataGridFilteringLabel": "Sort Largest To Smallest", + "sortAToZDataGridFilteringLabel": "Sort A To Z", + "sortZToADataGridFilteringLabel": "Sort Z To A", + "sortOldestToNewestDataGridFilteringLabel": "Sort Oldest To Newest", + "sortNewestToOldestDataGridFilteringLabel": "Sort Newest To Oldest", + "textFiltersDataGridFilteringLabel": "Text Filters", + "numberFiltersDataGridFilteringLabel": "Number Filters", + "dateFiltersDataGridFilteringLabel": "Date Filters", + "searchDataGridFilteringLabel": "Search", + "noMatchesDataGridFilteringLabel": "No matches", + "okDataGridFilteringLabel": "OK", + "cancelDataGridFilteringLabel": "Cancel", + "showRowsWhereDataGridFilteringLabel": "Show rows where", + "andDataGridFilteringLabel": "And", + "orDataGridFilteringLabel": "Or", + "selectAllDataGridFilteringLabel": "Select All", + "sortAndFilterDataGridFilteringLabel": "Sort and Filter", + "clearFilterDataGridFilteringLabel": "Clear Filter", + "fromDataGridFilteringLabel": "From" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb index 65abb5f6d..fc756a036 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb @@ -9,7 +9,7 @@ "pdfScrollStatusOfLabel" : "de", "pdfGoToPageLabel" : "ir a la página", "pdfEnterPageNumberLabel" : "Introduzca el número de página", -"pdfInvalidPageNumberLabel" : "por favor ingrese un número valido", +"pdfInvalidPageNumberLabel" : "Por favor, ingrese un número válido", "pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "CANCELAR", "passwordDialogHeaderTextLabel" : "Contraseña protegida", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Contraseña invalida", "pdfPasswordDialogOpenLabel" : "ABIERTO", "pdfPasswordDialogCancelLabel" : "CANCELAR", +"pdfSignaturePadDialogHeaderTextLabel" : "Dibuja tu firma", +"pdfSignaturePadDialogPenColorLabel" : "Color de la pluma", +"pdfSignaturePadDialogClearLabel" : "Limpiar", +"pdfSignaturePadDialogSaveLabel" : "Guardar", +"pdfTextSelectionMenuCopyLabel" : "Copiar", +"pdfTextSelectionMenuHighlightLabel" : "Resaltar", +"pdfTextSelectionMenuStrikethroughLabel" : "Tachado", +"pdfTextSelectionMenuUnderlineLabel" : "Subrayar", +"pdfTextSelectionMenuSquigglyLabel" : "Ondulado", "allowedViewDayLabel" : "Día", "allowedViewWeekLabel" : "Semana", "allowedViewWorkWeekLabel" : "Semana de trabajo", @@ -27,7 +36,7 @@ "allowedViewTimelineWeekLabel" : "Semana de la línea de tiempo", "allowedViewTimelineWorkWeekLabel" : "Línea de tiempo Semana laboral", "allowedViewTimelineMonthLabel" : "Mes de la línea de tiempo", -"todayLabel" : "Hoy dia", +"todayLabel" : "Hoy", "weeknumberLabel" : "Semana", "allDayLabel" : "Todo el dia", "muharramLabel" : "muharram", @@ -44,9 +53,9 @@ "dhualhiLabel" : "Dhu al-Hiyyah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "seguro", -"shortRabi1Label" : "Rabí. I", -"shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. I", +"shortRabi1Label" : "Rabí. yo", +"shortRabi2Label" : "Rabí. Yo", +"shortJumada1Label" : "Jum. yo", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Día", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Abrir página web", +"pdfHyperlinkContentLabel" : "¿Quieres abrir la página en", +"pdfHyperlinkDialogOpenLabel" : "ABRIR", +"pdfHyperlinkDialogCancelLabel" : "CANCELAR", +"afterDataGridFilteringLabel" : "Después", +"afterOrEqualDataGridFilteringLabel" : "después o igual", +"beforeDataGridFilteringLabel" : "Antes", +"beforeOrEqualDataGridFilteringLabel" : "antes o igual", +"beginsWithDataGridFilteringLabel" : "Empieza con", +"containsDataGridFilteringLabel" : "Contiene", +"doesNotBeginWithDataGridFilteringLabel" : "no comienza con", +"doesNotContainDataGridFilteringLabel" : "No contiene", +"doesNotEndWithDataGridFilteringLabel" : "no termina con", +"doesNotEqualDataGridFilteringLabel" : "No es igual", +"emptyDataGridFilteringLabel" : "Vacío", +"endsWithDataGridFilteringLabel" : "Termina con", +"equalsDataGridFilteringLabel" : "igual", +"greaterThanDataGridFilteringLabel" : "Mas grande que", +"greaterThanOrEqualDataGridFilteringLabel" : "Mayor que o igual", +"lessThanDataGridFilteringLabel" : "Menos que", +"lessThanOrEqualDataGridFilteringLabel" : "Menor o igual", +"notEmptyDataGridFilteringLabel" : "No vacío", +"notNullDataGridFilteringLabel" : "No nulo", +"nullDataGridFilteringLabel" : "Nulo", +"sortSmallestToLargestDataGridFilteringLabel" : "Ordenar de menor a mayor", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordenar de mayor a menor", +"sortAToZDataGridFilteringLabel" : "Ordenar de la A a la Z", +"sortZToADataGridFilteringLabel" : "Ordenar de Z a A", +"sortOldestToNewestDataGridFilteringLabel" : "Ordenar de más antiguo a más nuevo", +"sortNewestToOldestDataGridFilteringLabel" : "Ordenar de más reciente a más antiguo", +"textFiltersDataGridFilteringLabel" : "Filtros de texto", +"numberFiltersDataGridFilteringLabel" : "Filtros numéricos", +"dateFiltersDataGridFilteringLabel" : "Filtros de fecha", +"searchDataGridFilteringLabel" : "Búsqueda", +"noMatchesDataGridFilteringLabel" : "No hay coincidencias", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Cancelar", +"showRowsWhereDataGridFilteringLabel" : "Mostrar filas donde", +"andDataGridFilteringLabel" : "Y", +"orDataGridFilteringLabel" : "O", +"selectAllDataGridFilteringLabel" : "Seleccionar todo", +"sortAndFilterDataGridFilteringLabel" : "Ordenar y filtrar", +"clearFilterDataGridFilteringLabel" : "Filtro claro", +"fromDataGridFilteringLabel" : "De" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb index 51fba8632..20349fa28 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb @@ -10,15 +10,24 @@ "pdfGoToPageLabel" : "Mine lehele", "pdfEnterPageNumberLabel" : "Sisestage lehekülje number", "pdfInvalidPageNumberLabel" : "Sisestage kehtiv number", -"pdfPaginationDialogOkLabel" : "Okei", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "TÜHISTA", "passwordDialogHeaderTextLabel" : "Parooliga kaitstud", "passwordDialogContentLabel" : "Selle PDF-faili avamiseks sisestage parool", "passwordDialogHintTextLabel" : "Sisestage parool", "passwordDialogInvalidPasswordLabel" : "vale parool", -"pdfPasswordDialogOpenLabel" : "AVATUD", +"pdfPasswordDialogOpenLabel" : "AVA", "pdfPasswordDialogCancelLabel" : "TÜHISTA", -"allowedViewDayLabel" : "päev", +"pdfSignaturePadDialogHeaderTextLabel" : "Joonistage oma allkiri", +"pdfSignaturePadDialogPenColorLabel" : "Pliiatsi värv", +"pdfSignaturePadDialogClearLabel" : "Puhasta", +"pdfSignaturePadDialogSaveLabel" : "Salvesta", +"pdfTextSelectionMenuCopyLabel" : "Kopeeri", +"pdfTextSelectionMenuHighlightLabel" : "Esile tõsta", +"pdfTextSelectionMenuStrikethroughLabel" : "Läbikriipsutatud", +"pdfTextSelectionMenuUnderlineLabel" : "Allajoonimine", +"pdfTextSelectionMenuSquigglyLabel" : "Laineline", +"allowedViewDayLabel" : "Päev", "allowedViewWeekLabel" : "Nädal", "allowedViewWorkWeekLabel" : "Töönädal", "allowedViewMonthLabel" : "Kuu", @@ -44,9 +53,9 @@ "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. ma", +"shortRabi1Label" : "Rabi. I", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. ma", +"shortJumada1Label" : "Jum. I", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "päev", -"series" : "seeria" +"series" : "seeria", +"pdfHyperlinkLabel" : "Ava veebileht", +"pdfHyperlinkContentLabel" : "Kas soovite lehe avada aadressil", +"pdfHyperlinkDialogOpenLabel" : "AVA", +"pdfHyperlinkDialogCancelLabel" : "TÜHISTA", +"afterDataGridFilteringLabel" : "Pärast", +"afterOrEqualDataGridFilteringLabel" : "Pärast või Võrdne", +"beforeDataGridFilteringLabel" : "Enne", +"beforeOrEqualDataGridFilteringLabel" : "Enne või Võrdne", +"beginsWithDataGridFilteringLabel" : "Algab", +"containsDataGridFilteringLabel" : "Sisaldab", +"doesNotBeginWithDataGridFilteringLabel" : "Ei alga", +"doesNotContainDataGridFilteringLabel" : "Ei sisalda", +"doesNotEndWithDataGridFilteringLabel" : "Ei lõpe", +"doesNotEqualDataGridFilteringLabel" : "Ei võrdu", +"emptyDataGridFilteringLabel" : "Tühi", +"endsWithDataGridFilteringLabel" : "Lõpeb", +"equalsDataGridFilteringLabel" : "Võrdub", +"greaterThanDataGridFilteringLabel" : "Suurem kui", +"greaterThanOrEqualDataGridFilteringLabel" : "Suurem Kui või Võrdne", +"lessThanDataGridFilteringLabel" : "Vähem kui", +"lessThanOrEqualDataGridFilteringLabel" : "Väiksem või võrdne", +"notEmptyDataGridFilteringLabel" : "Pole tühi", +"notNullDataGridFilteringLabel" : "Mitte Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Sorteeri väikseimast suurimaks", +"sortLargestToSmallestDataGridFilteringLabel" : "Sorteeri suurimast väikseimani", +"sortAToZDataGridFilteringLabel" : "Sorteeri A–Z", +"sortZToADataGridFilteringLabel" : "Sorteeri Z kuni A", +"sortOldestToNewestDataGridFilteringLabel" : "Sorteeri vanimast uusimaks", +"sortNewestToOldestDataGridFilteringLabel" : "Sorteeri uusimatest vanimateks", +"textFiltersDataGridFilteringLabel" : "Tekstifiltrid", +"numberFiltersDataGridFilteringLabel" : "Numbrifiltrid", +"dateFiltersDataGridFilteringLabel" : "Kuupäeva filtrid", +"searchDataGridFilteringLabel" : "Otsing", +"noMatchesDataGridFilteringLabel" : "Vasteid pole", +"okDataGridFilteringLabel" : "Okei", +"cancelDataGridFilteringLabel" : "Tühista", +"showRowsWhereDataGridFilteringLabel" : "Näita ridu, kus", +"andDataGridFilteringLabel" : "Ja", +"orDataGridFilteringLabel" : "Või", +"selectAllDataGridFilteringLabel" : "Vali kõik", +"sortAndFilterDataGridFilteringLabel" : "Sorteeri ja filtreeri", +"clearFilterDataGridFilteringLabel" : "Tühjenda filter", +"fromDataGridFilteringLabel" : "Alates" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb index b68d46d55..e6f794277 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "pasahitz okerra", "pdfPasswordDialogOpenLabel" : "IREKI", "pdfPasswordDialogCancelLabel" : "Utzi", +"pdfSignaturePadDialogHeaderTextLabel" : "Marraztu zure sinadura", +"pdfSignaturePadDialogPenColorLabel" : "Boligrafoaren kolorea", +"pdfSignaturePadDialogClearLabel" : "Garbitu", +"pdfSignaturePadDialogSaveLabel" : "Gorde", +"pdfTextSelectionMenuCopyLabel" : "Kopiatu", +"pdfTextSelectionMenuHighlightLabel" : "Nabarmendu", +"pdfTextSelectionMenuStrikethroughLabel" : "Markatua", +"pdfTextSelectionMenuUnderlineLabel" : "Azpimarratu", +"pdfTextSelectionMenuSquigglyLabel" : "Zinta", "allowedViewDayLabel" : "Eguna", "allowedViewWeekLabel" : "Astea", "allowedViewWorkWeekLabel" : "Lan Astea", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Eguna", -"series" : "Seriea" +"series" : "Seriea", +"pdfHyperlinkLabel" : "Ireki Web orria", +"pdfHyperlinkContentLabel" : "Orria ireki nahi al duzu hemen", +"pdfHyperlinkDialogOpenLabel" : "IREKI", +"pdfHyperlinkDialogCancelLabel" : "Utzi", +"afterDataGridFilteringLabel" : "Ondoren", +"afterOrEqualDataGridFilteringLabel" : "Ondoren Edo Berdin", +"beforeDataGridFilteringLabel" : "Aurretik", +"beforeOrEqualDataGridFilteringLabel" : "Aurretik Edo Berdin", +"beginsWithDataGridFilteringLabel" : "Hasten da", +"containsDataGridFilteringLabel" : "Dauka", +"doesNotBeginWithDataGridFilteringLabel" : "Ez Da Hasten", +"doesNotContainDataGridFilteringLabel" : "Ez dauka", +"doesNotEndWithDataGridFilteringLabel" : "Ez Du Bukatzen", +"doesNotEqualDataGridFilteringLabel" : "Ez Du Berdin", +"emptyDataGridFilteringLabel" : "Hutsik", +"endsWithDataGridFilteringLabel" : "Honekin amaitzen da", +"equalsDataGridFilteringLabel" : "Berdin", +"greaterThanDataGridFilteringLabel" : "Baino handiagoa", +"greaterThanOrEqualDataGridFilteringLabel" : "Handiagoa Edo Berdin", +"lessThanDataGridFilteringLabel" : "Baino gutxiago", +"lessThanOrEqualDataGridFilteringLabel" : "Gutxiago Edo Berdin", +"notEmptyDataGridFilteringLabel" : "Ez Hutsik", +"notNullDataGridFilteringLabel" : "Ez Nulua", +"nullDataGridFilteringLabel" : "Nulua", +"sortSmallestToLargestDataGridFilteringLabel" : "Ordenatu txikienetik handienera", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordenatu handienetik txikienetik", +"sortAToZDataGridFilteringLabel" : "Ordenatu Atik Z", +"sortZToADataGridFilteringLabel" : "Ordenatu Ztik A", +"sortOldestToNewestDataGridFilteringLabel" : "Ordenatu zaharrenetik berrienera", +"sortNewestToOldestDataGridFilteringLabel" : "Ordenatu berrienetik zaharrenetik", +"textFiltersDataGridFilteringLabel" : "Testu-iragazkiak", +"numberFiltersDataGridFilteringLabel" : "Zenbaki-iragazkiak", +"dateFiltersDataGridFilteringLabel" : "Data-iragazkiak", +"searchDataGridFilteringLabel" : "Bilatu", +"noMatchesDataGridFilteringLabel" : "Partidurik ez", +"okDataGridFilteringLabel" : "Ados", +"cancelDataGridFilteringLabel" : "Utzi", +"showRowsWhereDataGridFilteringLabel" : "Erakutsi errenkadak non", +"andDataGridFilteringLabel" : "Eta", +"orDataGridFilteringLabel" : "Edo", +"selectAllDataGridFilteringLabel" : "Hautatu guztiak", +"sortAndFilterDataGridFilteringLabel" : "Ordenatu eta Iragazi", +"clearFilterDataGridFilteringLabel" : "Garbitu iragazkia", +"fromDataGridFilteringLabel" : "Bertatik" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb index 49b868f34..98bca57ef 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "تاریخ انتخابی وجود ندارد", +"noSelectedDateCalendarLabel" : "تاریخی انتخاب نشده است", "noEventsCalendarLabel" : "هیچ رویدادی وجود ندارد", "ofDataPagerLabel" : "از", "pagesDataPagerLabel" : "صفحات", @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "برو به صفحه", "pdfEnterPageNumberLabel" : "شماره صفحه را وارد کنید", "pdfInvalidPageNumberLabel" : "لطفا یک شماره معتبر وارد کنید", -"pdfPaginationDialogOkLabel" : "خوب", +"pdfPaginationDialogOkLabel" : "تأیید", "pdfPaginationDialogCancelLabel" : "لغو", "passwordDialogHeaderTextLabel" : "رمز عبور محافظت شده است", "passwordDialogContentLabel" : "رمز عبور را برای باز کردن این فایل PDF وارد کنید", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "رمز عبور نامعتبر", "pdfPasswordDialogOpenLabel" : "باز کن", "pdfPasswordDialogCancelLabel" : "لغو", +"pdfSignaturePadDialogHeaderTextLabel" : "امضاتو بکش", +"pdfSignaturePadDialogPenColorLabel" : "رنگ قلم", +"pdfSignaturePadDialogClearLabel" : "پاک کردن", +"pdfSignaturePadDialogSaveLabel" : "ذخيره", +"pdfTextSelectionMenuCopyLabel" : "کپی", +"pdfTextSelectionMenuHighlightLabel" : "برجسته", +"pdfTextSelectionMenuStrikethroughLabel" : "خط خوردگی", +"pdfTextSelectionMenuUnderlineLabel" : "زیرخط", +"pdfTextSelectionMenuSquigglyLabel" : "موج‌دار", "allowedViewDayLabel" : "روز", "allowedViewWeekLabel" : "هفته", "allowedViewWorkWeekLabel" : "هفته کاری", @@ -42,7 +51,7 @@ "shawwalLabel" : "شوال", "dhualqiLabel" : "ذی القعده", "dhualhiLabel" : "ذی الحجه", -"shortMuharramLabel" : "ماه", +"shortMuharramLabel" : "مه", "shortSafarLabel" : "ساف", "shortRabi1Label" : "ربیع من", "shortRabi2Label" : "ربیع II", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ذوالق", "shortDhualhiLabel" : "ذوالح", "daySpanCountLabel" : "روز", -"series" : "سلسله" +"series" : "سلسله", +"pdfHyperlinkLabel" : "صفحه وب را باز کنید", +"pdfHyperlinkContentLabel" : "آیا می خواهید صفحه را در", +"pdfHyperlinkDialogOpenLabel" : "باز کن", +"pdfHyperlinkDialogCancelLabel" : "لغو", +"afterDataGridFilteringLabel" : "بعد از", +"afterOrEqualDataGridFilteringLabel" : "بعد یا برابر", +"beforeDataGridFilteringLabel" : "قبل از", +"beforeOrEqualDataGridFilteringLabel" : "قبل یا برابر", +"beginsWithDataGridFilteringLabel" : "شروع با", +"containsDataGridFilteringLabel" : "حاوی", +"doesNotBeginWithDataGridFilteringLabel" : "با شروع نمی شود", +"doesNotContainDataGridFilteringLabel" : "شامل نمی شود", +"doesNotEndWithDataGridFilteringLabel" : "به پایان نمی رسد", +"doesNotEqualDataGridFilteringLabel" : "برابر نیست", +"emptyDataGridFilteringLabel" : "خالی", +"endsWithDataGridFilteringLabel" : "به پایان می رسد با", +"equalsDataGridFilteringLabel" : "برابر است", +"greaterThanDataGridFilteringLabel" : "بزرگتر از", +"greaterThanOrEqualDataGridFilteringLabel" : "بزرگتر یا مساوی", +"lessThanDataGridFilteringLabel" : "کمتر از", +"lessThanOrEqualDataGridFilteringLabel" : "کمتر یا برابر", +"notEmptyDataGridFilteringLabel" : "خالی نیست", +"notNullDataGridFilteringLabel" : "تهی نیست", +"nullDataGridFilteringLabel" : "خالی", +"sortSmallestToLargestDataGridFilteringLabel" : "مرتب سازی کوچک ترین به بزرگ ترین", +"sortLargestToSmallestDataGridFilteringLabel" : "مرتب سازی بزرگترین به کوچکترین", +"sortAToZDataGridFilteringLabel" : "مرتب سازی A به Z", +"sortZToADataGridFilteringLabel" : "Z به A مرتب کنید", +"sortOldestToNewestDataGridFilteringLabel" : "مرتب سازی قدیمی ترین به جدیدترین", +"sortNewestToOldestDataGridFilteringLabel" : "مرتب سازی جدیدترین به قدیمی ترین", +"textFiltersDataGridFilteringLabel" : "فیلترهای متن", +"numberFiltersDataGridFilteringLabel" : "فیلترهای اعداد", +"dateFiltersDataGridFilteringLabel" : "فیلترهای تاریخ", +"searchDataGridFilteringLabel" : "جستجو کردن", +"noMatchesDataGridFilteringLabel" : "هیچ بازی", +"okDataGridFilteringLabel" : "خوب", +"cancelDataGridFilteringLabel" : "لغو کنید", +"showRowsWhereDataGridFilteringLabel" : "نشان دادن ردیف ها در کجا", +"andDataGridFilteringLabel" : "و", +"orDataGridFilteringLabel" : "یا", +"selectAllDataGridFilteringLabel" : "انتخاب همه", +"sortAndFilterDataGridFilteringLabel" : "مرتب سازی و فیلتر کردن", +"clearFilterDataGridFilteringLabel" : "فیلتر را پاک کنید", +"fromDataGridFilteringLabel" : "از جانب" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb index c84933f44..1818522c1 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb @@ -11,13 +11,22 @@ "pdfEnterPageNumberLabel" : "Syötä sivunumero", "pdfInvalidPageNumberLabel" : "Anna kelvollinen numero", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "PERUUTTAA", +"pdfPaginationDialogCancelLabel" : "Peruuta", "passwordDialogHeaderTextLabel" : "Salasana suojattu", -"passwordDialogContentLabel" : "Kirjoita salasana avataksesi tämän PDF-tiedoston", +"passwordDialogContentLabel" : "Syötä salasana avataksesi tämän PDF-tiedoston", "passwordDialogHintTextLabel" : "Kirjoita salasana", "passwordDialogInvalidPasswordLabel" : "väärä salasana", -"pdfPasswordDialogOpenLabel" : "AVATA", +"pdfPasswordDialogOpenLabel" : "Avaa", "pdfPasswordDialogCancelLabel" : "PERUUTTAA", +"pdfSignaturePadDialogHeaderTextLabel" : "Piirrä allekirjoituksesi", +"pdfSignaturePadDialogPenColorLabel" : "Kynän väri", +"pdfSignaturePadDialogClearLabel" : "Tyhjennä", +"pdfSignaturePadDialogSaveLabel" : "Tallenna", +"pdfTextSelectionMenuCopyLabel" : "Kopio", +"pdfTextSelectionMenuHighlightLabel" : "Korosta", +"pdfTextSelectionMenuStrikethroughLabel" : "Yliviivattu", +"pdfTextSelectionMenuUnderlineLabel" : "Alleviivaus", +"pdfTextSelectionMenuSquigglyLabel" : "Aaltoviiva", "allowedViewDayLabel" : "Päivä", "allowedViewWeekLabel" : "Viikko", "allowedViewWorkWeekLabel" : "Työviikko", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Päivä", -"series" : "Sarja" +"series" : "Sarja", +"pdfHyperlinkLabel" : "Avaa Web-sivu", +"pdfHyperlinkContentLabel" : "Haluatko avata sivun osoitteessa", +"pdfHyperlinkDialogOpenLabel" : "Avaa", +"pdfHyperlinkDialogCancelLabel" : "PERUUTTAA", +"afterDataGridFilteringLabel" : "Jälkeen", +"afterOrEqualDataGridFilteringLabel" : "Jälkeen tai Equal", +"beforeDataGridFilteringLabel" : "Ennen", +"beforeOrEqualDataGridFilteringLabel" : "Ennen tai Tasa-arvoinen", +"beginsWithDataGridFilteringLabel" : "Alkaa jollakin", +"containsDataGridFilteringLabel" : "Sisältää", +"doesNotBeginWithDataGridFilteringLabel" : "Ei ala", +"doesNotContainDataGridFilteringLabel" : "Ei sisällä", +"doesNotEndWithDataGridFilteringLabel" : "Ei lopu", +"doesNotEqualDataGridFilteringLabel" : "Ei ole sama", +"emptyDataGridFilteringLabel" : "Tyhjä", +"endsWithDataGridFilteringLabel" : "Loppuu", +"equalsDataGridFilteringLabel" : "Yhtä", +"greaterThanDataGridFilteringLabel" : "Suurempi kuin", +"greaterThanOrEqualDataGridFilteringLabel" : "Suurempi kuin tai yhtä suuri", +"lessThanDataGridFilteringLabel" : "Vähemmän kuin", +"lessThanOrEqualDataGridFilteringLabel" : "Pienempi kuin tai yhtä suuri", +"notEmptyDataGridFilteringLabel" : "Ei tyhjä", +"notNullDataGridFilteringLabel" : "Ei tyhjä", +"nullDataGridFilteringLabel" : "Tyhjä", +"sortSmallestToLargestDataGridFilteringLabel" : "Lajittele pienimmästä suurimpaan", +"sortLargestToSmallestDataGridFilteringLabel" : "Lajittele suurimmasta pienimpään", +"sortAToZDataGridFilteringLabel" : "Lajittele A–Z", +"sortZToADataGridFilteringLabel" : "Lajittele Z-A", +"sortOldestToNewestDataGridFilteringLabel" : "Lajittele vanhimmasta uusimpaan", +"sortNewestToOldestDataGridFilteringLabel" : "Lajittele uusimmasta vanhimpaan", +"textFiltersDataGridFilteringLabel" : "Tekstisuodattimet", +"numberFiltersDataGridFilteringLabel" : "Numerosuodattimet", +"dateFiltersDataGridFilteringLabel" : "Päivämääräsuodattimet", +"searchDataGridFilteringLabel" : "Hae", +"noMatchesDataGridFilteringLabel" : "Ei osumia", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Peruuta", +"showRowsWhereDataGridFilteringLabel" : "Näytä rivit missä", +"andDataGridFilteringLabel" : "Ja", +"orDataGridFilteringLabel" : "Tai", +"selectAllDataGridFilteringLabel" : "Valitse kaikki", +"sortAndFilterDataGridFilteringLabel" : "Lajittele ja Suodata", +"clearFilterDataGridFilteringLabel" : "Tyhjennä suodatin", +"fromDataGridFilteringLabel" : "From" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb index 9c1457cf8..3c9f9b6bf 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb @@ -11,17 +11,26 @@ "pdfEnterPageNumberLabel" : "Ipasok ang numero ng pahina", "pdfInvalidPageNumberLabel" : "Mangyaring magpasok ng wastong numero", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "KANSELAHIN", +"pdfPaginationDialogCancelLabel" : "Kanselahin", "passwordDialogHeaderTextLabel" : "Pinoprotektahan ng Password", "passwordDialogContentLabel" : "Ilagay ang password para buksan ang PDF file na ito", "passwordDialogHintTextLabel" : "Ilagay ang password", "passwordDialogInvalidPasswordLabel" : "di wastong password", -"pdfPasswordDialogOpenLabel" : "BUKAS", +"pdfPasswordDialogOpenLabel" : "Bukas", "pdfPasswordDialogCancelLabel" : "KANSELAHIN", +"pdfSignaturePadDialogHeaderTextLabel" : "Iguhit ang iyong lagda", +"pdfSignaturePadDialogPenColorLabel" : "Kulay ng Panulat", +"pdfSignaturePadDialogClearLabel" : "I-clear", +"pdfSignaturePadDialogSaveLabel" : "I-save", +"pdfTextSelectionMenuCopyLabel" : "Kopya", +"pdfTextSelectionMenuHighlightLabel" : "I-highlight", +"pdfTextSelectionMenuStrikethroughLabel" : "Tanggalin", +"pdfTextSelectionMenuUnderlineLabel" : "Salungguhit", +"pdfTextSelectionMenuSquigglyLabel" : "Aalugin", "allowedViewDayLabel" : "Araw", "allowedViewWeekLabel" : "Linggo", "allowedViewWorkWeekLabel" : "Linggo ng trabaho", -"allowedViewMonthLabel" : "buwan", +"allowedViewMonthLabel" : "Buwan", "allowedViewScheduleLabel" : "Iskedyul", "allowedViewTimelineDayLabel" : "Araw ng Timeline", "allowedViewTimelineWeekLabel" : "Linggo ng Timeline", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Araw", -"series" : "Serye" +"series" : "Serye", +"pdfHyperlinkLabel" : "Buksan ang Web Page", +"pdfHyperlinkContentLabel" : "Gusto mo bang buksan ang pahina sa", +"pdfHyperlinkDialogOpenLabel" : "Bukas", +"pdfHyperlinkDialogCancelLabel" : "KANSELAHIN", +"afterDataGridFilteringLabel" : "Pagkatapos", +"afterOrEqualDataGridFilteringLabel" : "Pagkatapos ng Or Equal", +"beforeDataGridFilteringLabel" : "Bago O Kapantay", +"beforeOrEqualDataGridFilteringLabel" : "bago", +"beginsWithDataGridFilteringLabel" : "Nagsisimula sa", +"containsDataGridFilteringLabel" : "Naglalaman", +"doesNotBeginWithDataGridFilteringLabel" : "Hindi Nagsisimula Sa", +"doesNotContainDataGridFilteringLabel" : "Hindi Naglalaman", +"doesNotEndWithDataGridFilteringLabel" : "Hindi Nagtatapos Sa", +"doesNotEqualDataGridFilteringLabel" : "Ay hindi katumbas ng", +"emptyDataGridFilteringLabel" : "Walang laman", +"endsWithDataGridFilteringLabel" : "Nagtatapos Sa", +"equalsDataGridFilteringLabel" : "katumbas", +"greaterThanDataGridFilteringLabel" : "Mahigit sa", +"greaterThanOrEqualDataGridFilteringLabel" : "Higit sa O Katumbas", +"lessThanDataGridFilteringLabel" : "Mas mababa sa", +"lessThanOrEqualDataGridFilteringLabel" : "Mas Mababa sa O Katumbas", +"notEmptyDataGridFilteringLabel" : "Hindi Walang laman", +"notNullDataGridFilteringLabel" : "Hindi Null", +"nullDataGridFilteringLabel" : "Wala", +"sortSmallestToLargestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakamaliit Hanggang Pinakamalaki", +"sortLargestToSmallestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakamalaki Hanggang Pinakamaliit", +"sortAToZDataGridFilteringLabel" : "Pagbukud-bukurin A hanggang Z", +"sortZToADataGridFilteringLabel" : "Pagbukud-bukurin ang Z Hanggang A", +"sortOldestToNewestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakaluma Hanggang Pinakabago", +"sortNewestToOldestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakabago Hanggang sa Pinakaluma", +"textFiltersDataGridFilteringLabel" : "Mga Filter ng Teksto", +"numberFiltersDataGridFilteringLabel" : "Mga Filter ng Numero", +"dateFiltersDataGridFilteringLabel" : "Mga Filter ng Petsa", +"searchDataGridFilteringLabel" : "Maghanap", +"noMatchesDataGridFilteringLabel" : "Walang tugma", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Kanselahin", +"showRowsWhereDataGridFilteringLabel" : "Ipakita ang mga hilera kung saan", +"andDataGridFilteringLabel" : "At", +"orDataGridFilteringLabel" : "O kaya", +"selectAllDataGridFilteringLabel" : "Piliin lahat", +"sortAndFilterDataGridFilteringLabel" : "Pagbukud-bukurin at Salain", +"clearFilterDataGridFilteringLabel" : "I-clear ang Filter", +"fromDataGridFilteringLabel" : "Mula sa" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb index 106ab4872..207268916 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb @@ -5,19 +5,28 @@ "pagesDataPagerLabel" : "pages", "rowsPerPageDataPagerLabel" : "Lignes par page", "pdfBookmarksLabel" : "Signets", -"pdfNoBookmarksLabel" : "Aucun favori trouvé", +"pdfNoBookmarksLabel" : "Aucun signet trouvé", "pdfScrollStatusOfLabel" : "de", "pdfGoToPageLabel" : "Aller à la page", "pdfEnterPageNumberLabel" : "Entrer le numéro de page", "pdfInvalidPageNumberLabel" : "S'il vous plait, entrez un nombre valide", -"pdfPaginationDialogOkLabel" : "d'accord", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "ANNULER", "passwordDialogHeaderTextLabel" : "Protégé par mot de passe", "passwordDialogContentLabel" : "Entrez le mot de passe pour ouvrir ce fichier PDF", "passwordDialogHintTextLabel" : "Entrer le mot de passe", "passwordDialogInvalidPasswordLabel" : "Mot de passe incorrect", -"pdfPasswordDialogOpenLabel" : "OUVRIR", +"pdfPasswordDialogOpenLabel" : "Ouvrir", "pdfPasswordDialogCancelLabel" : "ANNULER", +"pdfSignaturePadDialogHeaderTextLabel" : "Dessinez votre signature", +"pdfSignaturePadDialogPenColorLabel" : "Couleur du stylo", +"pdfSignaturePadDialogClearLabel" : "Effacer", +"pdfSignaturePadDialogSaveLabel" : "Enregistrer", +"pdfTextSelectionMenuCopyLabel" : "Copie", +"pdfTextSelectionMenuHighlightLabel" : "Mettre en surbrillance", +"pdfTextSelectionMenuStrikethroughLabel" : "Barré", +"pdfTextSelectionMenuUnderlineLabel" : "Souligner", +"pdfTextSelectionMenuSquigglyLabel" : "Surligneur vague", "allowedViewDayLabel" : "Jour", "allowedViewWeekLabel" : "Semaine", "allowedViewWorkWeekLabel" : "Semaine de travail", @@ -25,7 +34,7 @@ "allowedViewScheduleLabel" : "Programme", "allowedViewTimelineDayLabel" : "Chronologie Jour", "allowedViewTimelineWeekLabel" : "Chronologie Semaine", -"allowedViewTimelineWorkWeekLabel" : "Chronologie de la semaine de travail", +"allowedViewTimelineWorkWeekLabel" : "Chronologie Semaine de travail", "allowedViewTimelineMonthLabel" : "Chronologie Mois", "todayLabel" : "Aujourd'hui", "weeknumberLabel" : "Semaine", @@ -41,7 +50,7 @@ "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhou al-Qi'dah", -"dhualhiLabel" : "Safar", +"dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Mouh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. je", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Jour", -"series" : "Séries" +"series" : "Série", +"pdfHyperlinkLabel" : "Ouvrir la page Web", +"pdfHyperlinkContentLabel" : "Voulez-vous ouvrir la page à", +"pdfHyperlinkDialogOpenLabel" : "Ouvrir", +"pdfHyperlinkDialogCancelLabel" : "ANNULER", +"afterDataGridFilteringLabel" : "Après", +"afterOrEqualDataGridFilteringLabel" : "Après ou égal", +"beforeDataGridFilteringLabel" : "Avant de", +"beforeOrEqualDataGridFilteringLabel" : "Avant ou égal", +"beginsWithDataGridFilteringLabel" : "Commence par", +"containsDataGridFilteringLabel" : "Contient", +"doesNotBeginWithDataGridFilteringLabel" : "Ne commence pas par", +"doesNotContainDataGridFilteringLabel" : "Ne contient pas", +"doesNotEndWithDataGridFilteringLabel" : "Ne se termine pas par", +"doesNotEqualDataGridFilteringLabel" : "N'est pas égal", +"emptyDataGridFilteringLabel" : "Vide", +"endsWithDataGridFilteringLabel" : "Se termine par", +"equalsDataGridFilteringLabel" : "Équivaut à", +"greaterThanDataGridFilteringLabel" : "Plus grand que", +"greaterThanOrEqualDataGridFilteringLabel" : "Meilleur que ou égal", +"lessThanDataGridFilteringLabel" : "Moins que", +"lessThanOrEqualDataGridFilteringLabel" : "Inférieur ou égal", +"notEmptyDataGridFilteringLabel" : "Pas vide", +"notNullDataGridFilteringLabel" : "Non nul", +"nullDataGridFilteringLabel" : "Nul", +"sortSmallestToLargestDataGridFilteringLabel" : "Trier du plus petit au plus grand", +"sortLargestToSmallestDataGridFilteringLabel" : "Trier du plus grand au plus petit", +"sortAToZDataGridFilteringLabel" : "Trier de A à Z", +"sortZToADataGridFilteringLabel" : "Trier Z à A", +"sortOldestToNewestDataGridFilteringLabel" : "Trier du plus ancien au plus récent", +"sortNewestToOldestDataGridFilteringLabel" : "Trier du plus récent au plus ancien", +"textFiltersDataGridFilteringLabel" : "Filtres de texte", +"numberFiltersDataGridFilteringLabel" : "Filtres numériques", +"dateFiltersDataGridFilteringLabel" : "Filtres de dates", +"searchDataGridFilteringLabel" : "Chercher", +"noMatchesDataGridFilteringLabel" : "Pas de correspondance", +"okDataGridFilteringLabel" : "D'ACCORD", +"cancelDataGridFilteringLabel" : "Annuler", +"showRowsWhereDataGridFilteringLabel" : "Afficher les lignes où", +"andDataGridFilteringLabel" : "Et", +"orDataGridFilteringLabel" : "Ou", +"selectAllDataGridFilteringLabel" : "Tout sélectionner", +"sortAndFilterDataGridFilteringLabel" : "Trier et filtrer", +"clearFilterDataGridFilteringLabel" : "Effacer le filtre", +"fromDataGridFilteringLabel" : "De" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb index 364beef96..76682183c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb @@ -15,18 +15,27 @@ "passwordDialogHeaderTextLabel" : "Protexido por contrasinal", "passwordDialogContentLabel" : "Introduza o contrasinal para abrir este ficheiro PDF", "passwordDialogHintTextLabel" : "Escriba o contrasinal", -"passwordDialogInvalidPasswordLabel" : "Contrasinal incorrecta", +"passwordDialogInvalidPasswordLabel" : "Contrasinal incorrecto", "pdfPasswordDialogOpenLabel" : "ABERTO", "pdfPasswordDialogCancelLabel" : "CANCELAR", +"pdfSignaturePadDialogHeaderTextLabel" : "Debuxa a túa sinatura", +"pdfSignaturePadDialogPenColorLabel" : "Cor da pluma", +"pdfSignaturePadDialogClearLabel" : "BORRAR", +"pdfSignaturePadDialogSaveLabel" : "GARDAR", +"pdfTextSelectionMenuCopyLabel" : "Copiar", +"pdfTextSelectionMenuHighlightLabel" : "Destacar", +"pdfTextSelectionMenuStrikethroughLabel" : "Tachado", +"pdfTextSelectionMenuUnderlineLabel" : "Subliñado", +"pdfTextSelectionMenuSquigglyLabel" : "Esquivo", "allowedViewDayLabel" : "Día", "allowedViewWeekLabel" : "Semana", "allowedViewWorkWeekLabel" : "Semana de Traballo", "allowedViewMonthLabel" : "Mes", "allowedViewScheduleLabel" : "Horario", "allowedViewTimelineDayLabel" : "Día da cronoloxía", -"allowedViewTimelineWeekLabel" : "Semana do cronograma", -"allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", -"allowedViewTimelineMonthLabel" : "Mes do cronograma", +"allowedViewTimelineWeekLabel" : "Semana da cronoloxía", +"allowedViewTimelineWorkWeekLabel" : "Semana de traballo da cronoloxía", +"allowedViewTimelineMonthLabel" : "Mes da cronoloxía", "todayLabel" : "Hoxe", "weeknumberLabel" : "Semana", "allDayLabel" : "Todo o día", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Día", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Abrir páxina web", +"pdfHyperlinkContentLabel" : "Queres abrir a páxina en", +"pdfHyperlinkDialogOpenLabel" : "ABERTO", +"pdfHyperlinkDialogCancelLabel" : "CANCELAR", +"afterDataGridFilteringLabel" : "Despois", +"afterOrEqualDataGridFilteringLabel" : "Despois Ou Igual", +"beforeDataGridFilteringLabel" : "Antes", +"beforeOrEqualDataGridFilteringLabel" : "Antes Ou Igual", +"beginsWithDataGridFilteringLabel" : "Comeza por", +"containsDataGridFilteringLabel" : "Contén", +"doesNotBeginWithDataGridFilteringLabel" : "Non Comeza Con", +"doesNotContainDataGridFilteringLabel" : "Non Contén", +"doesNotEndWithDataGridFilteringLabel" : "Non Remata Con", +"doesNotEqualDataGridFilteringLabel" : "Non é igual", +"emptyDataGridFilteringLabel" : "Baleiro", +"endsWithDataGridFilteringLabel" : "Remata con", +"equalsDataGridFilteringLabel" : "Iguais", +"greaterThanDataGridFilteringLabel" : "Máis grande cá", +"greaterThanOrEqualDataGridFilteringLabel" : "Maior Que Ou Igual", +"lessThanDataGridFilteringLabel" : "Menos de", +"lessThanOrEqualDataGridFilteringLabel" : "Menos Ou Igual", +"notEmptyDataGridFilteringLabel" : "Non baleiro", +"notNullDataGridFilteringLabel" : "Non nulo", +"nullDataGridFilteringLabel" : "Nulo", +"sortSmallestToLargestDataGridFilteringLabel" : "Ordenar de menor a maior", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordenar de maior a menor", +"sortAToZDataGridFilteringLabel" : "Ordenar da A a Z", +"sortZToADataGridFilteringLabel" : "Ordenar de Z a A", +"sortOldestToNewestDataGridFilteringLabel" : "Ordenar do máis antigo ao máis novo", +"sortNewestToOldestDataGridFilteringLabel" : "Ordenar o máis novo ao máis antigo", +"textFiltersDataGridFilteringLabel" : "Filtros de texto", +"numberFiltersDataGridFilteringLabel" : "Filtros de número", +"dateFiltersDataGridFilteringLabel" : "Filtros de data", +"searchDataGridFilteringLabel" : "Busca", +"noMatchesDataGridFilteringLabel" : "Non hai coincidencias", +"okDataGridFilteringLabel" : "Ok", +"cancelDataGridFilteringLabel" : "Cancelar", +"showRowsWhereDataGridFilteringLabel" : "Mostrar as filas onde", +"andDataGridFilteringLabel" : "E", +"orDataGridFilteringLabel" : "Ou", +"selectAllDataGridFilteringLabel" : "Seleccionar todo", +"sortAndFilterDataGridFilteringLabel" : "Ordenar e filtrar", +"clearFilterDataGridFilteringLabel" : "Borrar filtro", +"fromDataGridFilteringLabel" : "Desde" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb index 7e129ecf5..4815263e2 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb @@ -18,8 +18,17 @@ "passwordDialogInvalidPasswordLabel" : "અમાન્ય પાસવર્ડ", "pdfPasswordDialogOpenLabel" : "ખુલ્લા", "pdfPasswordDialogCancelLabel" : "રદ કરો", +"pdfSignaturePadDialogHeaderTextLabel" : "તમારી સહી દોરો", +"pdfSignaturePadDialogPenColorLabel" : "પેન કલર", +"pdfSignaturePadDialogClearLabel" : "ચોખ્ખુ", +"pdfSignaturePadDialogSaveLabel" : "સાચવો", +"pdfTextSelectionMenuCopyLabel" : "નકલ કરો", +"pdfTextSelectionMenuHighlightLabel" : "હાઇલાઇટ કરો", +"pdfTextSelectionMenuStrikethroughLabel" : "સ્ટ્રાઈકથ્રુ", +"pdfTextSelectionMenuUnderlineLabel" : "રેખાંકિત કરો", +"pdfTextSelectionMenuSquigglyLabel" : "સ્ક્વિગલી", "allowedViewDayLabel" : "દિવસ", -"allowedViewWeekLabel" : "સપ્તાહ", +"allowedViewWeekLabel" : "અઠવાડિયું", "allowedViewWorkWeekLabel" : "કાર્ય સપ્તાહ", "allowedViewMonthLabel" : "માસ", "allowedViewScheduleLabel" : "અનુસૂચિ", @@ -28,7 +37,7 @@ "allowedViewTimelineWorkWeekLabel" : "સમયરેખા કાર્ય સપ્તાહ", "allowedViewTimelineMonthLabel" : "સમયરેખા મહિનો", "todayLabel" : "આજે", -"weeknumberLabel" : "સપ્તાહ", +"weeknumberLabel" : "અઠવાડિયું", "allDayLabel" : "બધા દિવસ", "muharramLabel" : "મોહરમ", "safarLabel" : "સફર", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ધુલ-પ્ર", "shortDhualhiLabel" : "ધુલ-એચ", "daySpanCountLabel" : "દિવસ", -"series" : "શ્રેણી" +"series" : "શ્રેણી", +"pdfHyperlinkLabel" : "વેબ પેજ ખોલો", +"pdfHyperlinkContentLabel" : "શું તમે પાનું ખોલવા માંગો છો", +"pdfHyperlinkDialogOpenLabel" : "ખુલ્લા", +"pdfHyperlinkDialogCancelLabel" : "રદ કરો", +"afterDataGridFilteringLabel" : "પછી", +"afterOrEqualDataGridFilteringLabel" : "અથવા સમાન પછી", +"beforeDataGridFilteringLabel" : "પહેલાં", +"beforeOrEqualDataGridFilteringLabel" : "પહેલાં અથવા સમાન", +"beginsWithDataGridFilteringLabel" : "સાથે શરૂ થાય છે", +"containsDataGridFilteringLabel" : "સમાવે છે", +"doesNotBeginWithDataGridFilteringLabel" : "સાથે શરૂ થતું નથી", +"doesNotContainDataGridFilteringLabel" : "સમાવતું નથી", +"doesNotEndWithDataGridFilteringLabel" : "સાથે સમાપ્ત થતું નથી", +"doesNotEqualDataGridFilteringLabel" : "સમાન નથી", +"emptyDataGridFilteringLabel" : "ખાલી", +"endsWithDataGridFilteringLabel" : "સાથે સમાપ્ત થાય છે", +"equalsDataGridFilteringLabel" : "સમકક્ષ", +"greaterThanDataGridFilteringLabel" : "કરતા વધારે", +"greaterThanOrEqualDataGridFilteringLabel" : "તેના કરતા વધારે અથવા સમાન", +"lessThanDataGridFilteringLabel" : "કરતાં ઓછી", +"lessThanOrEqualDataGridFilteringLabel" : "તેનાથી ઓછું અથવા સમાન", +"notEmptyDataGridFilteringLabel" : "ખાલી નથી", +"notNullDataGridFilteringLabel" : "શૂન્ય નથી", +"nullDataGridFilteringLabel" : "શૂન્ય", +"sortSmallestToLargestDataGridFilteringLabel" : "સૌથી નાનાથી મોટામાં સૉર્ટ કરો", +"sortLargestToSmallestDataGridFilteringLabel" : "સૌથી મોટાથી નાનામાં સૉર્ટ કરો", +"sortAToZDataGridFilteringLabel" : "A થી Z સૉર્ટ કરો", +"sortZToADataGridFilteringLabel" : "Z થી A સૉર્ટ કરો", +"sortOldestToNewestDataGridFilteringLabel" : "સૌથી જૂનાથી નવામાં સૉર્ટ કરો", +"sortNewestToOldestDataGridFilteringLabel" : "સૌથી નવાથી જૂનામાં સૉર્ટ કરો", +"textFiltersDataGridFilteringLabel" : "ટેક્સ્ટ ફિલ્ટર્સ", +"numberFiltersDataGridFilteringLabel" : "નંબર ફિલ્ટર્સ", +"dateFiltersDataGridFilteringLabel" : "તારીખ ફિલ્ટર્સ", +"searchDataGridFilteringLabel" : "શોધો", +"noMatchesDataGridFilteringLabel" : "કોઈ મેળ નથી", +"okDataGridFilteringLabel" : "બરાબર", +"cancelDataGridFilteringLabel" : "રદ કરો", +"showRowsWhereDataGridFilteringLabel" : "જ્યાં પંક્તિઓ બતાવો", +"andDataGridFilteringLabel" : "અને", +"orDataGridFilteringLabel" : "અથવા", +"selectAllDataGridFilteringLabel" : "બધા પસંદ કરો", +"sortAndFilterDataGridFilteringLabel" : "સૉર્ટ કરો અને ફિલ્ટર કરો", +"clearFilterDataGridFilteringLabel" : "ફિલ્ટર સાફ કરો", +"fromDataGridFilteringLabel" : "થી" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb index d49a1c019..8b5bdb21d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb @@ -3,7 +3,7 @@ "noEventsCalendarLabel" : "אין אירועים", "ofDataPagerLabel" : "שֶׁל", "pagesDataPagerLabel" : "דפים", -"rowsPerPageDataPagerLabel" : "שורות בעמוד", +"rowsPerPageDataPagerLabel" : "שורות לכל עמוד", "pdfBookmarksLabel" : "סימניות", "pdfNoBookmarksLabel" : "לא נמצאו סימניות", "pdfScrollStatusOfLabel" : "שֶׁל", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "סיסמה שגויה", "pdfPasswordDialogOpenLabel" : "לִפְתוֹחַ", "pdfPasswordDialogCancelLabel" : "לְבַטֵל", +"pdfSignaturePadDialogHeaderTextLabel" : "צייר את החתימה שלך", +"pdfSignaturePadDialogPenColorLabel" : "צבע עט", +"pdfSignaturePadDialogClearLabel" : "לְנַקוֹת", +"pdfSignaturePadDialogSaveLabel" : "לשמור", +"pdfTextSelectionMenuCopyLabel" : "עותק", +"pdfTextSelectionMenuHighlightLabel" : "שִׂיא", +"pdfTextSelectionMenuStrikethroughLabel" : "חוצה", +"pdfTextSelectionMenuUnderlineLabel" : "לָשִׂים דָגֵשׁ", +"pdfTextSelectionMenuSquigglyLabel" : "מתפתל", "allowedViewDayLabel" : "יְוֹם", "allowedViewWeekLabel" : "שָׁבוּעַ", "allowedViewWorkWeekLabel" : "שבוע עבודה", @@ -25,7 +34,7 @@ "allowedViewScheduleLabel" : "לוח זמנים", "allowedViewTimelineDayLabel" : "יום ציר הזמן", "allowedViewTimelineWeekLabel" : "שבוע ציר הזמן", -"allowedViewTimelineWorkWeekLabel" : "שבוע העבודה של ציר הזמן", +"allowedViewTimelineWorkWeekLabel" : "שבוע העבודה של ציר זמן", "allowedViewTimelineMonthLabel" : "חודש ציר הזמן", "todayLabel" : "היום", "weeknumberLabel" : "שָׁבוּעַ", @@ -34,12 +43,12 @@ "safarLabel" : "ספאר", "rabi1Label" : "רבי אל אווול", "rabi2Label" : "רבי אל-תאני", -"jumada1Label" : "ג'ומדה אל-אוואל", +"jumada1Label" : "ג'ומדה אל-אוול", "jumada2Label" : "ג'ומדה אל-ת'אני", "rajabLabel" : "רג'אב", "shaabanLabel" : "שעבן", "ramadanLabel" : "רמדאן", -"shawwalLabel" : "שוואל", +"shawwalLabel" : "שווואל", "dhualqiLabel" : "דהו אל-קיאדה", "dhualhiLabel" : "דהו אל-היג'ה", "shortMuharramLabel" : "מוה.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "דהו'ל-ק", "shortDhualhiLabel" : "דהו''ל-ה", "daySpanCountLabel" : "יְוֹם", -"series" : "סִדרָה" +"series" : "סִדרָה", +"pdfHyperlinkLabel" : "פתח את דף האינטרנט", +"pdfHyperlinkContentLabel" : "האם אתה רוצה לפתוח את העמוד ב", +"pdfHyperlinkDialogOpenLabel" : "לִפְתוֹחַ", +"pdfHyperlinkDialogCancelLabel" : "לְבַטֵל", +"afterDataGridFilteringLabel" : "לאחר", +"afterOrEqualDataGridFilteringLabel" : "אחרי או שווה", +"beforeDataGridFilteringLabel" : "לפני", +"beforeOrEqualDataGridFilteringLabel" : "לפני או שווה", +"beginsWithDataGridFilteringLabel" : "מתחיל עם", +"containsDataGridFilteringLabel" : "מכיל", +"doesNotBeginWithDataGridFilteringLabel" : "לא מתחיל עם", +"doesNotContainDataGridFilteringLabel" : "לא מכיל", +"doesNotEndWithDataGridFilteringLabel" : "לא מסתיים ב", +"doesNotEqualDataGridFilteringLabel" : "לא שווה", +"emptyDataGridFilteringLabel" : "ריק", +"endsWithDataGridFilteringLabel" : "מסתיים עם", +"equalsDataGridFilteringLabel" : "שווים", +"greaterThanDataGridFilteringLabel" : "גדול מ", +"greaterThanOrEqualDataGridFilteringLabel" : "גדול או שווה", +"lessThanDataGridFilteringLabel" : "פחות מ", +"lessThanOrEqualDataGridFilteringLabel" : "פחות מ או שווה", +"notEmptyDataGridFilteringLabel" : "לא ריק", +"notNullDataGridFilteringLabel" : "לא ריק", +"nullDataGridFilteringLabel" : "ריק", +"sortSmallestToLargestDataGridFilteringLabel" : "מיין מהקטן לגדול ביותר", +"sortLargestToSmallestDataGridFilteringLabel" : "מיין מהגדול לקטן ביותר", +"sortAToZDataGridFilteringLabel" : "מיין מא' עד ת'", +"sortZToADataGridFilteringLabel" : "מיין Z עד A", +"sortOldestToNewestDataGridFilteringLabel" : "מיין מהישן ביותר לחדש ביותר", +"sortNewestToOldestDataGridFilteringLabel" : "מיין מהחדש לישן ביותר", +"textFiltersDataGridFilteringLabel" : "מסנני טקסט", +"numberFiltersDataGridFilteringLabel" : "מסנני מספרים", +"dateFiltersDataGridFilteringLabel" : "מסנני תאריכים", +"searchDataGridFilteringLabel" : "לחפש", +"noMatchesDataGridFilteringLabel" : "אין התאמה", +"okDataGridFilteringLabel" : "בסדר", +"cancelDataGridFilteringLabel" : "לְבַטֵל", +"showRowsWhereDataGridFilteringLabel" : "הצג שורות היכן", +"andDataGridFilteringLabel" : "ו", +"orDataGridFilteringLabel" : "אוֹ", +"selectAllDataGridFilteringLabel" : "בחר הכל", +"sortAndFilterDataGridFilteringLabel" : "מיון ומסנן", +"clearFilterDataGridFilteringLabel" : "נקה מסנן", +"fromDataGridFilteringLabel" : "מ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb index 5223bf660..a5a825e1c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb @@ -8,16 +8,25 @@ "pdfNoBookmarksLabel" : "कोई बुकमार्क नहीं मिला", "pdfScrollStatusOfLabel" : "का", "pdfGoToPageLabel" : "पृष्ठ पर जाओ", -"pdfEnterPageNumberLabel" : "पेज नंबर दर्ज करें", -"pdfInvalidPageNumberLabel" : "कृपया एक सही संख्या डालिये", -"pdfPaginationDialogOkLabel" : "ठीक", +"pdfEnterPageNumberLabel" : "पृष्ठ संख्या दर्ज करें", +"pdfInvalidPageNumberLabel" : "कृपया सही अंक दर्ज करें", +"pdfPaginationDialogOkLabel" : "ठीक है", "pdfPaginationDialogCancelLabel" : "रद्द करना", "passwordDialogHeaderTextLabel" : "पासवर्ड से सुरक्षित", -"passwordDialogContentLabel" : "इस पीडीएफ फाइल को खोलने के लिए पासवर्ड दर्ज करें", +"passwordDialogContentLabel" : "इस पीडीएफ फाइल को खोलने के लिए पासवर्ड डालें", "passwordDialogHintTextLabel" : "पास वर्ड दर्ज करें", "passwordDialogInvalidPasswordLabel" : "अवैध पासवर्ड", -"pdfPasswordDialogOpenLabel" : "खुला हुआ", +"pdfPasswordDialogOpenLabel" : "खोलना", "pdfPasswordDialogCancelLabel" : "रद्द करना", +"pdfSignaturePadDialogHeaderTextLabel" : "अपना हस्ताक्षर बनाएं", +"pdfSignaturePadDialogPenColorLabel" : "कलम का रंग", +"pdfSignaturePadDialogClearLabel" : "साफ़ करें", +"pdfSignaturePadDialogSaveLabel" : "सहेजें", +"pdfTextSelectionMenuCopyLabel" : "प्रतिलिपि", +"pdfTextSelectionMenuHighlightLabel" : "हाइलाइट करें", +"pdfTextSelectionMenuStrikethroughLabel" : "स्ट्राइकथ्रू", +"pdfTextSelectionMenuUnderlineLabel" : "रेखांकित करें", +"pdfTextSelectionMenuSquigglyLabel" : "लहरदार रेखा", "allowedViewDayLabel" : "दिन", "allowedViewWeekLabel" : "सप्ताह", "allowedViewWorkWeekLabel" : "कार्य सप्ताह", @@ -33,27 +42,71 @@ "muharramLabel" : "मुहर्रम", "safarLabel" : "सफ़र", "rabi1Label" : "रबी अल-अव्वल", -"rabi2Label" : "रबी अल-थानी", +"rabi2Label" : "रबी 'अल-थानी", "jumada1Label" : "जुमादा अल-अव्वल", "jumada2Label" : "जुमादा अल-थानी", -"rajabLabel" : "राजाबी", -"shaabanLabel" : "शाबानो", +"rajabLabel" : "रज्जब", +"shaabanLabel" : "शाबान", "ramadanLabel" : "रमजान", "shawwalLabel" : "शावाल", -"dhualqiLabel" : "धू अल क़िदाही", -"dhualhiLabel" : "धू अल-हिज्जाही", -"shortMuharramLabel" : "मुह.", -"shortSafarLabel" : "साफ.", +"dhualqiLabel" : "धू अल-क़ीदाह", +"dhualhiLabel" : "धू अल-हिज्जाह", +"shortMuharramLabel" : "मुह।", +"shortSafarLabel" : "सफ।", "shortRabi1Label" : "रबी। मैं", "shortRabi2Label" : "रबी। द्वितीय", -"shortJumada1Label" : "जम. मैं", -"shortJumada2Label" : "जम. द्वितीय", -"shortRajabLabel" : "राज.", +"shortJumada1Label" : "जम। मैं", +"shortJumada2Label" : "जम। द्वितीय", +"shortRajabLabel" : "राज।", "shortShaabanLabel" : "शा.", "shortRamadanLabel" : "टक्कर मारना।", "shortShawwalLabel" : "शॉ।", "shortDhualqiLabel" : "धुल-क्यू", -"shortDhualhiLabel" : "धुल-हो", +"shortDhualhiLabel" : "धुल-एच", "daySpanCountLabel" : "दिन", -"series" : "श्रृंखला" +"series" : "श्रृंखला", +"pdfHyperlinkLabel" : "वेब पेज खोलें", +"pdfHyperlinkContentLabel" : "क्या आप पृष्ठ खोलना चाहते हैं", +"pdfHyperlinkDialogOpenLabel" : "खोलना", +"pdfHyperlinkDialogCancelLabel" : "रद्द करना", +"afterDataGridFilteringLabel" : "बाद में", +"afterOrEqualDataGridFilteringLabel" : "के बाद या बराबर", +"beforeDataGridFilteringLabel" : "पहले", +"beforeOrEqualDataGridFilteringLabel" : "पहले या बराबर", +"beginsWithDataGridFilteringLabel" : "साथ शुरू होता है", +"containsDataGridFilteringLabel" : "में शामिल है", +"doesNotBeginWithDataGridFilteringLabel" : "से प्रारंभ नहीं होता है", +"doesNotContainDataGridFilteringLabel" : "शामिल नहीं है", +"doesNotEndWithDataGridFilteringLabel" : "से समाप्त नहीं होता है", +"doesNotEqualDataGridFilteringLabel" : "बराबर नही हैं", +"emptyDataGridFilteringLabel" : "खाली", +"endsWithDataGridFilteringLabel" : "इसी के साथ समाप्त होता है", +"equalsDataGridFilteringLabel" : "बराबर", +"greaterThanDataGridFilteringLabel" : "से अधिक", +"greaterThanOrEqualDataGridFilteringLabel" : "से बड़ा या बराबर", +"lessThanDataGridFilteringLabel" : "से कम", +"lessThanOrEqualDataGridFilteringLabel" : "इससे कम या इसके बराबर", +"notEmptyDataGridFilteringLabel" : "खाली नहीं है", +"notNullDataGridFilteringLabel" : "शून्य नहीं", +"nullDataGridFilteringLabel" : "शून्य", +"sortSmallestToLargestDataGridFilteringLabel" : "सबसे छोटे से बड़े के क्रम में लगाएं", +"sortLargestToSmallestDataGridFilteringLabel" : "सबसे बड़े को सबसे छोटे के क्रम में लगाएं", +"sortAToZDataGridFilteringLabel" : "A से Z तक क्रमबद्ध करें", +"sortZToADataGridFilteringLabel" : "Z से A तक क्रमबद्ध करें", +"sortOldestToNewestDataGridFilteringLabel" : "सबसे पुराने से नए के क्रम में लगाएं", +"sortNewestToOldestDataGridFilteringLabel" : "सबसे नए से सबसे पुराने के क्रम में लगाएं", +"textFiltersDataGridFilteringLabel" : "पाठ फ़िल्टर", +"numberFiltersDataGridFilteringLabel" : "नंबर फिल्टर", +"dateFiltersDataGridFilteringLabel" : "दिनांक फ़िल्टर", +"searchDataGridFilteringLabel" : "खोज", +"noMatchesDataGridFilteringLabel" : "कोई मेल नहीं", +"okDataGridFilteringLabel" : "ठीक है", +"cancelDataGridFilteringLabel" : "रद्द करना", +"showRowsWhereDataGridFilteringLabel" : "पंक्तियाँ कहाँ दिखाएँ", +"andDataGridFilteringLabel" : "और", +"orDataGridFilteringLabel" : "या", +"selectAllDataGridFilteringLabel" : "सभी का चयन करे", +"sortAndFilterDataGridFilteringLabel" : "क्रमबद्ध करें और फ़िल्टर करें", +"clearFilterDataGridFilteringLabel" : "स्पष्ट निस्यंदक", +"fromDataGridFilteringLabel" : "से" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb index 86737ade9..156533d99 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb @@ -2,15 +2,15 @@ "noSelectedDateCalendarLabel" : "Nema odabranog datuma", "noEventsCalendarLabel" : "Nema događaja", "ofDataPagerLabel" : "od", -"pagesDataPagerLabel" : "stranicama", +"pagesDataPagerLabel" : "stranice", "rowsPerPageDataPagerLabel" : "Redovi po stranici", "pdfBookmarksLabel" : "Oznake", -"pdfNoBookmarksLabel" : "Nije pronađena nijedna oznaka", +"pdfNoBookmarksLabel" : "Nema pronađenih oznaka", "pdfScrollStatusOfLabel" : "od", "pdfGoToPageLabel" : "Idi na stranicu", "pdfEnterPageNumberLabel" : "Unesite broj stranice", -"pdfInvalidPageNumberLabel" : "Unesite valjani broj", -"pdfPaginationDialogOkLabel" : "u redu", +"pdfInvalidPageNumberLabel" : "Unesite ispravan broj", +"pdfPaginationDialogOkLabel" : "U redu", "pdfPaginationDialogCancelLabel" : "OTKAZATI", "passwordDialogHeaderTextLabel" : "Lozinka zaštićena", "passwordDialogContentLabel" : "Unesite lozinku za otvaranje ove PDF datoteke", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Netočna zaporka", "pdfPasswordDialogOpenLabel" : "OTVORENA", "pdfPasswordDialogCancelLabel" : "OTKAZATI", +"pdfSignaturePadDialogHeaderTextLabel" : "Nacrtajte svoj potpis", +"pdfSignaturePadDialogPenColorLabel" : "Boja olovke", +"pdfSignaturePadDialogClearLabel" : "Očistiti", +"pdfSignaturePadDialogSaveLabel" : "Spremi", +"pdfTextSelectionMenuCopyLabel" : "Kopirati", +"pdfTextSelectionMenuHighlightLabel" : "Istaknuti", +"pdfTextSelectionMenuStrikethroughLabel" : "Precrtano", +"pdfTextSelectionMenuUnderlineLabel" : "Podvući", +"pdfTextSelectionMenuSquigglyLabel" : "vijugavo", "allowedViewDayLabel" : "Dan", "allowedViewWeekLabel" : "Tjedan", "allowedViewWorkWeekLabel" : "Radni tjedan", @@ -30,18 +39,18 @@ "todayLabel" : "Danas", "weeknumberLabel" : "Tjedan", "allDayLabel" : "Cijeli dan", -"muharramLabel" : "Muharem", +"muharramLabel" : "Muharrema", "safarLabel" : "Safar", -"rabi1Label" : "Rabi' el-evval", +"rabi1Label" : "Rabi' al-awwal", "rabi2Label" : "Rabi' al-thani", -"jumada1Label" : "Jumada al-awwal", +"jumada1Label" : "džumade el-evval", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Radžab", -"shaabanLabel" : "Sha'aban", -"ramadanLabel" : "ramazana", -"shawwalLabel" : "Shawwal", +"rajabLabel" : "Radžeb", +"shaabanLabel" : "ša'aban", +"ramadanLabel" : "Ramazan", +"shawwalLabel" : "ševval", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualhiLabel" : "zul-hidždže", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. ja", @@ -52,8 +61,52 @@ "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Radna memorija.", "shortShawwalLabel" : "Shaw.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "Zul-Q", +"shortDhualhiLabel" : "Zul-H", "daySpanCountLabel" : "Dan", -"series" : "Niz" +"series" : "Niz", +"pdfHyperlinkLabel" : "Otvori web stranicu", +"pdfHyperlinkContentLabel" : "Želite li otvoriti stranicu na", +"pdfHyperlinkDialogOpenLabel" : "OTVORENA", +"pdfHyperlinkDialogCancelLabel" : "OTKAZATI", +"afterDataGridFilteringLabel" : "Nakon", +"afterOrEqualDataGridFilteringLabel" : "Nakon ili jednako", +"beforeDataGridFilteringLabel" : "Prije", +"beforeOrEqualDataGridFilteringLabel" : "Prije ili jednako", +"beginsWithDataGridFilteringLabel" : "Počinje sa", +"containsDataGridFilteringLabel" : "Sadrži", +"doesNotBeginWithDataGridFilteringLabel" : "Ne počinje s", +"doesNotContainDataGridFilteringLabel" : "Ne sadrži", +"doesNotEndWithDataGridFilteringLabel" : "Ne završava sa", +"doesNotEqualDataGridFilteringLabel" : "Nije jednako", +"emptyDataGridFilteringLabel" : "Prazan", +"endsWithDataGridFilteringLabel" : "Završava sa", +"equalsDataGridFilteringLabel" : "Jednako", +"greaterThanDataGridFilteringLabel" : "Veći od", +"greaterThanOrEqualDataGridFilteringLabel" : "Veće ili jednako", +"lessThanDataGridFilteringLabel" : "Manje od", +"lessThanOrEqualDataGridFilteringLabel" : "Manje ili jednako", +"notEmptyDataGridFilteringLabel" : "Nije prazno", +"notNullDataGridFilteringLabel" : "Nije Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Poredaj od najmanjeg do najvećeg", +"sortLargestToSmallestDataGridFilteringLabel" : "Poredaj od najvećeg prema najmanjem", +"sortAToZDataGridFilteringLabel" : "Poredaj od A do Z", +"sortZToADataGridFilteringLabel" : "Poredaj od Z do A", +"sortOldestToNewestDataGridFilteringLabel" : "Poredaj od najstarijeg do najnovijeg", +"sortNewestToOldestDataGridFilteringLabel" : "Poredaj od najnovijeg do najstarijeg", +"textFiltersDataGridFilteringLabel" : "Filtri teksta", +"numberFiltersDataGridFilteringLabel" : "Filtri brojeva", +"dateFiltersDataGridFilteringLabel" : "Filtri datuma", +"searchDataGridFilteringLabel" : "traži", +"noMatchesDataGridFilteringLabel" : "Nema podudaranja", +"okDataGridFilteringLabel" : "u redu", +"cancelDataGridFilteringLabel" : "Otkazati", +"showRowsWhereDataGridFilteringLabel" : "Pokaži retke gdje", +"andDataGridFilteringLabel" : "I", +"orDataGridFilteringLabel" : "Ili", +"selectAllDataGridFilteringLabel" : "Odaberi sve", +"sortAndFilterDataGridFilteringLabel" : "Sortiraj i filtriraj", +"clearFilterDataGridFilteringLabel" : "Obriši filtar", +"fromDataGridFilteringLabel" : "Iz" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb index cb1ae86aa..59c3ca7e4 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb @@ -9,15 +9,24 @@ "pdfScrollStatusOfLabel" : "nak,-nek", "pdfGoToPageLabel" : "Menj az oldalra", "pdfEnterPageNumberLabel" : "Írja be az oldalszámot", -"pdfInvalidPageNumberLabel" : "Adjon meg egy érvényes számot", -"pdfPaginationDialogOkLabel" : "rendben", -"pdfPaginationDialogCancelLabel" : "MEGSZÜNTETI", +"pdfInvalidPageNumberLabel" : "Kérjük, adjon meg egy érvényes számot", +"pdfPaginationDialogOkLabel" : "Rendben", +"pdfPaginationDialogCancelLabel" : "Megszakít", "passwordDialogHeaderTextLabel" : "Jelszóval védett", "passwordDialogContentLabel" : "Írja be a jelszót a PDF-fájl megnyitásához", "passwordDialogHintTextLabel" : "Írd be a jelszót", "passwordDialogInvalidPasswordLabel" : "Érvénytelen jelszó", "pdfPasswordDialogOpenLabel" : "NYISD KI", "pdfPasswordDialogCancelLabel" : "MEGSZÜNTETI", +"pdfSignaturePadDialogHeaderTextLabel" : "Rajzolja le az aláírását", +"pdfSignaturePadDialogPenColorLabel" : "Toll színe", +"pdfSignaturePadDialogClearLabel" : "Törlés", +"pdfSignaturePadDialogSaveLabel" : "Mentés", +"pdfTextSelectionMenuCopyLabel" : "Másolás", +"pdfTextSelectionMenuHighlightLabel" : "Kiemel", +"pdfTextSelectionMenuStrikethroughLabel" : "Áthúzott", +"pdfTextSelectionMenuUnderlineLabel" : "Aláhúzás", +"pdfTextSelectionMenuSquigglyLabel" : "Hullámos", "allowedViewDayLabel" : "Nap", "allowedViewWeekLabel" : "Hét", "allowedViewWorkWeekLabel" : "Munkahét", @@ -38,7 +47,7 @@ "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", -"ramadanLabel" : "Ramadan", +"ramadanLabel" : "Ramadán", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Nap", -"series" : "Sorozat" +"series" : "Sorozat", +"pdfHyperlinkLabel" : "Weboldal megnyitása", +"pdfHyperlinkContentLabel" : "Meg akarja nyitni az oldalt a címen", +"pdfHyperlinkDialogOpenLabel" : "NYISD KI", +"pdfHyperlinkDialogCancelLabel" : "MEGSZÜNTETI", +"afterDataGridFilteringLabel" : "Után", +"afterOrEqualDataGridFilteringLabel" : "Után vagy egyenlő", +"beforeDataGridFilteringLabel" : "Előtt", +"beforeOrEqualDataGridFilteringLabel" : "Előtte vagy egyenlő", +"beginsWithDataGridFilteringLabel" : "Ezzel kezdődik", +"containsDataGridFilteringLabel" : "Tartalmaz", +"doesNotBeginWithDataGridFilteringLabel" : "Nem azzal kezdődik", +"doesNotContainDataGridFilteringLabel" : "Nem tartalmaz", +"doesNotEndWithDataGridFilteringLabel" : "Nem ér véget", +"doesNotEqualDataGridFilteringLabel" : "Nem egyenlő", +"emptyDataGridFilteringLabel" : "Üres", +"endsWithDataGridFilteringLabel" : "Végződik", +"equalsDataGridFilteringLabel" : "Egyenlő", +"greaterThanDataGridFilteringLabel" : "Nagyobb, mint", +"greaterThanOrEqualDataGridFilteringLabel" : "Nagyobb, mint vagy egyenlő", +"lessThanDataGridFilteringLabel" : "Kevesebb, mint", +"lessThanOrEqualDataGridFilteringLabel" : "Kevesebb, mint vagy egyenlő", +"notEmptyDataGridFilteringLabel" : "Nem üres", +"notNullDataGridFilteringLabel" : "Nem nulla", +"nullDataGridFilteringLabel" : "Nulla", +"sortSmallestToLargestDataGridFilteringLabel" : "Rendezés a legkisebbtől a legnagyobbig", +"sortLargestToSmallestDataGridFilteringLabel" : "Rendezés a legnagyobbtól a legkisebbig", +"sortAToZDataGridFilteringLabel" : "Rendezés A-tól Z-ig", +"sortZToADataGridFilteringLabel" : "Rendezés Z-ből A-ba", +"sortOldestToNewestDataGridFilteringLabel" : "Rendezés a legrégebbitől a legújabbig", +"sortNewestToOldestDataGridFilteringLabel" : "Rendezés a legújabbtól a legrégebbig", +"textFiltersDataGridFilteringLabel" : "Szövegszűrők", +"numberFiltersDataGridFilteringLabel" : "Számszűrők", +"dateFiltersDataGridFilteringLabel" : "Dátumszűrők", +"searchDataGridFilteringLabel" : "Keresés", +"noMatchesDataGridFilteringLabel" : "Nincs egyezés", +"okDataGridFilteringLabel" : "rendben", +"cancelDataGridFilteringLabel" : "Megszünteti", +"showRowsWhereDataGridFilteringLabel" : "Sorok megjelenítése hol", +"andDataGridFilteringLabel" : "És", +"orDataGridFilteringLabel" : "Vagy", +"selectAllDataGridFilteringLabel" : "Mindet kiválaszt", +"sortAndFilterDataGridFilteringLabel" : "Rendezés és szűrés", +"clearFilterDataGridFilteringLabel" : "Szűrő törlése", +"fromDataGridFilteringLabel" : "Tól től" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb index 47ecc8038..7452d8bc6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "Ընտրված ամսաթիվ չկա", "noEventsCalendarLabel" : "Միջոցառումներ չկան", -"ofDataPagerLabel" : "-ից", +"ofDataPagerLabel" : "ի", "pagesDataPagerLabel" : "էջեր", "rowsPerPageDataPagerLabel" : "Տողեր մեկ էջի համար", "pdfBookmarksLabel" : "Էջանիշեր", @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Գնալ դեպի էջ", "pdfEnterPageNumberLabel" : "Մուտքագրեք էջի համարը", "pdfInvalidPageNumberLabel" : "Խնդրում ենք մուտքագրել վավեր համար", -"pdfPaginationDialogOkLabel" : "լավ", +"pdfPaginationDialogOkLabel" : "Լավ է", "pdfPaginationDialogCancelLabel" : "ՉԵՂԱՐԿԵԼ", "passwordDialogHeaderTextLabel" : "Գաղտնաբառը պաշտպանված է", "passwordDialogContentLabel" : "Մուտքագրեք գաղտնաբառը՝ այս PDF ֆայլը բացելու համար", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Անվավեր գաղտնաբառ", "pdfPasswordDialogOpenLabel" : "ԲԱՑ", "pdfPasswordDialogCancelLabel" : "ՉԵՂԱՐԿԵԼ", +"pdfSignaturePadDialogHeaderTextLabel" : "Քո ստորագրությունը նկարիր", +"pdfSignaturePadDialogPenColorLabel" : "Գրչի գույնը", +"pdfSignaturePadDialogClearLabel" : "Մաքրել", +"pdfSignaturePadDialogSaveLabel" : "Պահպանել", +"pdfTextSelectionMenuCopyLabel" : "Պատճենել", +"pdfTextSelectionMenuHighlightLabel" : "Ընդգծել", +"pdfTextSelectionMenuStrikethroughLabel" : "Ականջած", +"pdfTextSelectionMenuUnderlineLabel" : "Ընդգծել", +"pdfTextSelectionMenuSquigglyLabel" : "Կրկնակի թեքումով", "allowedViewDayLabel" : "Օր", "allowedViewWeekLabel" : "Շաբաթ", "allowedViewWorkWeekLabel" : "Աշխատանքային շաբաթ", @@ -30,11 +39,11 @@ "todayLabel" : "Այսօր", "weeknumberLabel" : "Շաբաթ", "allDayLabel" : "Ամբողջ օրը", -"muharramLabel" : "Մուհարամ", +"muharramLabel" : "Մուհարրամ", "safarLabel" : "Սաֆար", "rabi1Label" : "Ռաբի ալ-ավալ", "rabi2Label" : "Ռաբի ալ-Թանի", -"jumada1Label" : "Ջումադա ալ-ավալ", +"jumada1Label" : "Ջումադա ալ-ավվալ", "jumada2Label" : "Ջումադա ալ-Թանի", "rajabLabel" : "Ռաջաբ", "shaabanLabel" : "Շաաբան", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Դհուլ-Ք", "shortDhualhiLabel" : "Դհուլ-Հ", "daySpanCountLabel" : "Օր", -"series" : "Սերիա" +"series" : "Սերիա", +"pdfHyperlinkLabel" : "Բացեք վեբ էջը", +"pdfHyperlinkContentLabel" : "Ցանկանու՞մ եք բացել էջը", +"pdfHyperlinkDialogOpenLabel" : "ԲԱՑ", +"pdfHyperlinkDialogCancelLabel" : "ՉԵՂԱՐԿԵԼ", +"afterDataGridFilteringLabel" : "հետո", +"afterOrEqualDataGridFilteringLabel" : "հետո կամ հավասար", +"beforeDataGridFilteringLabel" : "Նախքան", +"beforeOrEqualDataGridFilteringLabel" : "Նախքան կամ հավասարը", +"beginsWithDataGridFilteringLabel" : "Սկսվում է", +"containsDataGridFilteringLabel" : "Պարունակում է", +"doesNotBeginWithDataGridFilteringLabel" : "Չի սկսվում", +"doesNotContainDataGridFilteringLabel" : "Չի պարունակում", +"doesNotEndWithDataGridFilteringLabel" : "Չի ավարտվում", +"doesNotEqualDataGridFilteringLabel" : "Չի հավասարվում", +"emptyDataGridFilteringLabel" : "Դատարկ", +"endsWithDataGridFilteringLabel" : "Ավարտվում է", +"equalsDataGridFilteringLabel" : "Հավասար է", +"greaterThanDataGridFilteringLabel" : "Ավելի մեծ քան", +"greaterThanOrEqualDataGridFilteringLabel" : "Ավելի մեծ, քան հավասար", +"lessThanDataGridFilteringLabel" : "Ավելի քիչ քան", +"lessThanOrEqualDataGridFilteringLabel" : "Ավելի քիչ, քան հավասար", +"notEmptyDataGridFilteringLabel" : "Ոչ դատարկ", +"notNullDataGridFilteringLabel" : "Ոչ զրոյական", +"nullDataGridFilteringLabel" : "Դատարկ", +"sortSmallestToLargestDataGridFilteringLabel" : "Տեսակավորել ամենափոքրից ամենամեծը", +"sortLargestToSmallestDataGridFilteringLabel" : "Տեսակավորել ամենամեծից փոքրից", +"sortAToZDataGridFilteringLabel" : "Տեսակավորել A-ից Z", +"sortZToADataGridFilteringLabel" : "Տեսակավորել Z-ը A-ին", +"sortOldestToNewestDataGridFilteringLabel" : "Տեսակավորել ամենահինը նորագույնին", +"sortNewestToOldestDataGridFilteringLabel" : "Տեսակավորել ամենանորից ամենահինը", +"textFiltersDataGridFilteringLabel" : "Տեքստի զտիչներ", +"numberFiltersDataGridFilteringLabel" : "Թվերի զտիչներ", +"dateFiltersDataGridFilteringLabel" : "Ամսաթվի զտիչներ", +"searchDataGridFilteringLabel" : "Որոնում", +"noMatchesDataGridFilteringLabel" : "Համընկնումներ չկան", +"okDataGridFilteringLabel" : "լավ", +"cancelDataGridFilteringLabel" : "Չեղարկել", +"showRowsWhereDataGridFilteringLabel" : "Ցույց տալ տողերը, որտեղ", +"andDataGridFilteringLabel" : "Եվ", +"orDataGridFilteringLabel" : "Կամ", +"selectAllDataGridFilteringLabel" : "Ընտրել բոլորը", +"sortAndFilterDataGridFilteringLabel" : "Տեսակավորել և զտել", +"clearFilterDataGridFilteringLabel" : "Մաքրել զտիչը", +"fromDataGridFilteringLabel" : "Սկսած" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb index 61d3e0837..9fa79ab50 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb @@ -9,30 +9,39 @@ "pdfScrollStatusOfLabel" : "dari", "pdfGoToPageLabel" : "Buka halaman", "pdfEnterPageNumberLabel" : "Masukkan nomor halaman", -"pdfInvalidPageNumberLabel" : "Harap masukkan nomor yang valid", -"pdfPaginationDialogOkLabel" : "oke", -"pdfPaginationDialogCancelLabel" : "MEMBATALKAN", +"pdfInvalidPageNumberLabel" : "Masukkan nomor yang valid", +"pdfPaginationDialogOkLabel" : "Oke", +"pdfPaginationDialogCancelLabel" : "Batal", "passwordDialogHeaderTextLabel" : "Dilindungi Kata Sandi", "passwordDialogContentLabel" : "Masukkan kata sandi untuk membuka file PDF ini", "passwordDialogHintTextLabel" : "Masukkan kata kunci", -"passwordDialogInvalidPasswordLabel" : "kata sandi salah", +"passwordDialogInvalidPasswordLabel" : "Kata sandi tidak valid", "pdfPasswordDialogOpenLabel" : "MEMBUKA", "pdfPasswordDialogCancelLabel" : "MEMBATALKAN", +"pdfSignaturePadDialogHeaderTextLabel" : "Gambar tanda tangan Anda", +"pdfSignaturePadDialogPenColorLabel" : "Warna Pena", +"pdfSignaturePadDialogClearLabel" : "Bersihkan", +"pdfSignaturePadDialogSaveLabel" : "Simpan", +"pdfTextSelectionMenuCopyLabel" : "Salin", +"pdfTextSelectionMenuHighlightLabel" : "Sorot", +"pdfTextSelectionMenuStrikethroughLabel" : "Dicoret", +"pdfTextSelectionMenuUnderlineLabel" : "Menggarisbawahi", +"pdfTextSelectionMenuSquigglyLabel" : "Bergelombang", "allowedViewDayLabel" : "Hari", "allowedViewWeekLabel" : "Pekan", -"allowedViewWorkWeekLabel" : "minggu kerja", +"allowedViewWorkWeekLabel" : "Minggu Kerja", "allowedViewMonthLabel" : "Bulan", "allowedViewScheduleLabel" : "Jadwal", "allowedViewTimelineDayLabel" : "Hari Garis Waktu", -"allowedViewTimelineWeekLabel" : "Garis Waktu Minggu", -"allowedViewTimelineWorkWeekLabel" : "Garis Waktu Minggu Kerja", -"allowedViewTimelineMonthLabel" : "Garis Waktu Bulan", +"allowedViewTimelineWeekLabel" : "Minggu Garis Waktu", +"allowedViewTimelineWorkWeekLabel" : "Minggu Kerja Timeline", +"allowedViewTimelineMonthLabel" : "Bulan Garis Waktu", "todayLabel" : "Hari ini", "weeknumberLabel" : "Pekan", "allDayLabel" : "Sepanjang hari", "muharramLabel" : "Muharram", -"safarLabel" : "Safari", -"rabi1Label" : "Rabi' al-awwal", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi' al-awal", "rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", @@ -40,20 +49,64 @@ "shaabanLabel" : "Sya'ban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Syawal", -"dhualqiLabel" : "Dzulqidah", +"dhualqiLabel" : "Dhu al-Qidah", "dhualhiLabel" : "Dzulhijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "saf.", -"shortRabi1Label" : "Rabi. saya", +"shortSafarLabel" : "Aman", +"shortRabi1Label" : "Rabi. Saya", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. saya", +"shortJumada1Label" : "Jum. Saya", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", -"shortRamadanLabel" : "Rama.", -"shortShawwalLabel" : "Shaw", -"shortDhualqiLabel" : "Dzul Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortRamadanLabel" : "Ram.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dzul-Q", +"shortDhualhiLabel" : "Dzul-H", "daySpanCountLabel" : "Hari", -"series" : "Seri" +"series" : "Seri", +"pdfHyperlinkLabel" : "Buka Halaman Web", +"pdfHyperlinkContentLabel" : "Apakah Anda ingin membuka halaman di", +"pdfHyperlinkDialogOpenLabel" : "MEMBUKA", +"pdfHyperlinkDialogCancelLabel" : "MEMBATALKAN", +"afterDataGridFilteringLabel" : "Setelah", +"afterOrEqualDataGridFilteringLabel" : "Setelah Atau Sama", +"beforeDataGridFilteringLabel" : "Sebelum", +"beforeOrEqualDataGridFilteringLabel" : "Sebelum Atau Sama", +"beginsWithDataGridFilteringLabel" : "Dimulai dengan", +"containsDataGridFilteringLabel" : "Mengandung", +"doesNotBeginWithDataGridFilteringLabel" : "Tidak Dimulai Dengan", +"doesNotContainDataGridFilteringLabel" : "Tidak mengandung", +"doesNotEndWithDataGridFilteringLabel" : "Tidak Berakhir Dengan", +"doesNotEqualDataGridFilteringLabel" : "Tidak sama", +"emptyDataGridFilteringLabel" : "Kosong", +"endsWithDataGridFilteringLabel" : "Berakhir dengan", +"equalsDataGridFilteringLabel" : "Sama dengan", +"greaterThanDataGridFilteringLabel" : "Lebih besar dari", +"greaterThanOrEqualDataGridFilteringLabel" : "Lebih Besar Dari Atau Sama Dengan", +"lessThanDataGridFilteringLabel" : "Kurang dari", +"lessThanOrEqualDataGridFilteringLabel" : "Kurang Dari Atau Sama", +"notEmptyDataGridFilteringLabel" : "Tidak kosong", +"notNullDataGridFilteringLabel" : "Bukan Nol", +"nullDataGridFilteringLabel" : "Batal", +"sortSmallestToLargestDataGridFilteringLabel" : "Urutkan Terkecil Hingga Terbesar", +"sortLargestToSmallestDataGridFilteringLabel" : "Urutkan Terbesar Hingga Terkecil", +"sortAToZDataGridFilteringLabel" : "Urutkan A Sampai Z", +"sortZToADataGridFilteringLabel" : "Urutkan Z Ke A", +"sortOldestToNewestDataGridFilteringLabel" : "Urutkan Terlama Ke Terbaru", +"sortNewestToOldestDataGridFilteringLabel" : "Urutkan Terbaru Ke Terlama", +"textFiltersDataGridFilteringLabel" : "Filter Teks", +"numberFiltersDataGridFilteringLabel" : "Filter Angka", +"dateFiltersDataGridFilteringLabel" : "Filter Tanggal", +"searchDataGridFilteringLabel" : "Mencari", +"noMatchesDataGridFilteringLabel" : "Tidak ada yang cocok", +"okDataGridFilteringLabel" : "Oke", +"cancelDataGridFilteringLabel" : "Membatalkan", +"showRowsWhereDataGridFilteringLabel" : "Tampilkan baris di mana", +"andDataGridFilteringLabel" : "Dan", +"orDataGridFilteringLabel" : "Atau", +"selectAllDataGridFilteringLabel" : "Pilih Semua", +"sortAndFilterDataGridFilteringLabel" : "Sortir dan Filter", +"clearFilterDataGridFilteringLabel" : "Hapus Filter", +"fromDataGridFilteringLabel" : "Dari" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb index d1e852a4f..63758214c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Engin valin dagsetning", +"noSelectedDateCalendarLabel" : "Engin dagsetning valin", "noEventsCalendarLabel" : "Engir viðburðir", "ofDataPagerLabel" : "af", "pagesDataPagerLabel" : "síður", @@ -9,15 +9,24 @@ "pdfScrollStatusOfLabel" : "af", "pdfGoToPageLabel" : "Farðu á síðu", "pdfEnterPageNumberLabel" : "Sláðu inn blaðsíðunúmer", -"pdfInvalidPageNumberLabel" : "Vinsamlega sláðu inn gilt númer", +"pdfInvalidPageNumberLabel" : "Vinsamlegast sláðu inn gilt númer", "pdfPaginationDialogOkLabel" : "Allt í lagi", -"pdfPaginationDialogCancelLabel" : "HÆTTA við", +"pdfPaginationDialogCancelLabel" : "Hætta við", "passwordDialogHeaderTextLabel" : "Lykilorð varið", "passwordDialogContentLabel" : "Sláðu inn lykilorðið til að opna þessa PDF-skrá", "passwordDialogHintTextLabel" : "Sláðu inn lykilorð", "passwordDialogInvalidPasswordLabel" : "ógilt lykilorð", -"pdfPasswordDialogOpenLabel" : "OPNA", +"pdfPasswordDialogOpenLabel" : "Opna", "pdfPasswordDialogCancelLabel" : "HÆTTA við", +"pdfSignaturePadDialogHeaderTextLabel" : "Teiknaðu undirskriftina þína", +"pdfSignaturePadDialogPenColorLabel" : "Pennalitur", +"pdfSignaturePadDialogClearLabel" : "Hreinsa", +"pdfSignaturePadDialogSaveLabel" : "Vista", +"pdfTextSelectionMenuCopyLabel" : "Afrita", +"pdfTextSelectionMenuHighlightLabel" : "Merkja", +"pdfTextSelectionMenuStrikethroughLabel" : "Ástrikun", +"pdfTextSelectionMenuUnderlineLabel" : "Undirstrika", +"pdfTextSelectionMenuSquigglyLabel" : "Skýrt", "allowedViewDayLabel" : "Dagur", "allowedViewWeekLabel" : "Vika", "allowedViewWorkWeekLabel" : "Vinnuvika", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dagur", -"series" : "Röð" +"series" : "Röð", +"pdfHyperlinkLabel" : "Opnaðu vefsíðu", +"pdfHyperlinkContentLabel" : "Viltu opna síðuna á", +"pdfHyperlinkDialogOpenLabel" : "OPNA", +"pdfHyperlinkDialogCancelLabel" : "HÆTTA við", +"afterDataGridFilteringLabel" : "Eftir", +"afterOrEqualDataGridFilteringLabel" : "Eftir Or Equal", +"beforeDataGridFilteringLabel" : "Áður", +"beforeOrEqualDataGridFilteringLabel" : "Áður eða jafnt", +"beginsWithDataGridFilteringLabel" : "Byrjar með", +"containsDataGridFilteringLabel" : "Inniheldur", +"doesNotBeginWithDataGridFilteringLabel" : "Byrjar ekki á", +"doesNotContainDataGridFilteringLabel" : "Inniheldur ekki", +"doesNotEndWithDataGridFilteringLabel" : "Endar ekki með", +"doesNotEqualDataGridFilteringLabel" : "Er ekki jafn", +"emptyDataGridFilteringLabel" : "Tómt", +"endsWithDataGridFilteringLabel" : "Endar Með", +"equalsDataGridFilteringLabel" : "Jafnt", +"greaterThanDataGridFilteringLabel" : "Meiri en", +"greaterThanOrEqualDataGridFilteringLabel" : "Stærri en eða jafn", +"lessThanDataGridFilteringLabel" : "Minna en", +"lessThanOrEqualDataGridFilteringLabel" : "Minna en eða jafnt", +"notEmptyDataGridFilteringLabel" : "Ekki tómt", +"notNullDataGridFilteringLabel" : "Ekki Null", +"nullDataGridFilteringLabel" : "Núll", +"sortSmallestToLargestDataGridFilteringLabel" : "Raða minnstu í stærstu", +"sortLargestToSmallestDataGridFilteringLabel" : "Raða Stærsta Til Minnsta", +"sortAToZDataGridFilteringLabel" : "Raða A Til Ö", +"sortZToADataGridFilteringLabel" : "Raða Z Til A", +"sortOldestToNewestDataGridFilteringLabel" : "Raða elstu í nýjustu", +"sortNewestToOldestDataGridFilteringLabel" : "Raða nýjustu í elstu", +"textFiltersDataGridFilteringLabel" : "Textasíur", +"numberFiltersDataGridFilteringLabel" : "Númerasíur", +"dateFiltersDataGridFilteringLabel" : "Dagsetningarsíur", +"searchDataGridFilteringLabel" : "Leita", +"noMatchesDataGridFilteringLabel" : "Engar samsvörun", +"okDataGridFilteringLabel" : "Allt í lagi", +"cancelDataGridFilteringLabel" : "Hætta við", +"showRowsWhereDataGridFilteringLabel" : "Sýna línur hvar", +"andDataGridFilteringLabel" : "Og", +"orDataGridFilteringLabel" : "Eða", +"selectAllDataGridFilteringLabel" : "Velja allt", +"sortAndFilterDataGridFilteringLabel" : "Raða og sía", +"clearFilterDataGridFilteringLabel" : "Hreinsaðu síu", +"fromDataGridFilteringLabel" : "Frá" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb index 3a39ef04b..4d0bc6b7c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Vai alla pagina", "pdfEnterPageNumberLabel" : "Inserisci il numero di pagina", "pdfInvalidPageNumberLabel" : "Per favore, inserire un numero valido", -"pdfPaginationDialogOkLabel" : "ok", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "ANNULLA", "passwordDialogHeaderTextLabel" : "Protetto da password", "passwordDialogContentLabel" : "Inserisci la password per aprire questo file PDF", @@ -18,15 +18,24 @@ "passwordDialogInvalidPasswordLabel" : "Password non valida", "pdfPasswordDialogOpenLabel" : "APRIRE", "pdfPasswordDialogCancelLabel" : "ANNULLA", +"pdfSignaturePadDialogHeaderTextLabel" : "Disegna la tua firma", +"pdfSignaturePadDialogPenColorLabel" : "Colore penna", +"pdfSignaturePadDialogClearLabel" : "Pulisci", +"pdfSignaturePadDialogSaveLabel" : "SALVA", +"pdfTextSelectionMenuCopyLabel" : "copia", +"pdfTextSelectionMenuHighlightLabel" : "Evidenzia", +"pdfTextSelectionMenuStrikethroughLabel" : "Barrato", +"pdfTextSelectionMenuUnderlineLabel" : "Sottolineare", +"pdfTextSelectionMenuSquigglyLabel" : "Ondulato", "allowedViewDayLabel" : "Giorno", "allowedViewWeekLabel" : "Settimana", "allowedViewWorkWeekLabel" : "Settimana di lavoro", "allowedViewMonthLabel" : "Mese", "allowedViewScheduleLabel" : "Programma", -"allowedViewTimelineDayLabel" : "Giornata della sequenza temporale", -"allowedViewTimelineWeekLabel" : "Settimana della sequenza temporale", -"allowedViewTimelineWorkWeekLabel" : "Settimana del lavoro cronologico", -"allowedViewTimelineMonthLabel" : "Mese della sequenza temporale", +"allowedViewTimelineDayLabel" : "Giorno della cronologia", +"allowedViewTimelineWeekLabel" : "Settimana della cronologia", +"allowedViewTimelineWorkWeekLabel" : "Cronologia della settimana lavorativa", +"allowedViewTimelineMonthLabel" : "Mese cronologico", "todayLabel" : "In data odierna", "weeknumberLabel" : "Settimana", "allDayLabel" : "Tutto il giorno", @@ -40,14 +49,14 @@ "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", -"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualqiLabel" : "Dhu al-Qidah", "dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Mah.", +"shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. io", -"shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Salto. io", -"shortJumada2Label" : "Salto. II", +"shortRabi1Label" : "Rabbi. io", +"shortRabi2Label" : "Rabbi. II", +"shortJumada1Label" : "Jum. io", +"shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Ariete.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Giorno", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Apri pagina web", +"pdfHyperlinkContentLabel" : "Vuoi aprire la pagina in", +"pdfHyperlinkDialogOpenLabel" : "APRIRE", +"pdfHyperlinkDialogCancelLabel" : "ANNULLA", +"afterDataGridFilteringLabel" : "Dopo", +"afterOrEqualDataGridFilteringLabel" : "Dopo o uguale", +"beforeDataGridFilteringLabel" : "Prima", +"beforeOrEqualDataGridFilteringLabel" : "Prima o uguale", +"beginsWithDataGridFilteringLabel" : "Inizia con", +"containsDataGridFilteringLabel" : "Contiene", +"doesNotBeginWithDataGridFilteringLabel" : "Non inizia con", +"doesNotContainDataGridFilteringLabel" : "Non contiene", +"doesNotEndWithDataGridFilteringLabel" : "Non finisce con", +"doesNotEqualDataGridFilteringLabel" : "Non è uguale", +"emptyDataGridFilteringLabel" : "Vuoto", +"endsWithDataGridFilteringLabel" : "Finisce con", +"equalsDataGridFilteringLabel" : "È uguale a", +"greaterThanDataGridFilteringLabel" : "Più grande di", +"greaterThanOrEqualDataGridFilteringLabel" : "Maggiore o uguale", +"lessThanDataGridFilteringLabel" : "Meno di", +"lessThanOrEqualDataGridFilteringLabel" : "Minore o uguale", +"notEmptyDataGridFilteringLabel" : "Non vuoto", +"notNullDataGridFilteringLabel" : "Non nullo", +"nullDataGridFilteringLabel" : "Nullo", +"sortSmallestToLargestDataGridFilteringLabel" : "Ordina dal più piccolo al più grande", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordina dal più grande al più piccolo", +"sortAToZDataGridFilteringLabel" : "Ordina dalla A alla Z", +"sortZToADataGridFilteringLabel" : "Ordina dalla Z alla A", +"sortOldestToNewestDataGridFilteringLabel" : "Ordina dal più vecchio al più recente", +"sortNewestToOldestDataGridFilteringLabel" : "Ordina dal più recente al più vecchio", +"textFiltersDataGridFilteringLabel" : "Filtri di testo", +"numberFiltersDataGridFilteringLabel" : "Filtri numerici", +"dateFiltersDataGridFilteringLabel" : "Filtri data", +"searchDataGridFilteringLabel" : "Ricerca", +"noMatchesDataGridFilteringLabel" : "Nessuna corrispondenza", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Annulla", +"showRowsWhereDataGridFilteringLabel" : "Mostra righe dove", +"andDataGridFilteringLabel" : "E", +"orDataGridFilteringLabel" : "O", +"selectAllDataGridFilteringLabel" : "Seleziona tutto", +"sortAndFilterDataGridFilteringLabel" : "Ordina e filtra", +"clearFilterDataGridFilteringLabel" : "Filtro pulito", +"fromDataGridFilteringLabel" : "Da" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb index b7d3b4271..16c39b59d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb @@ -3,7 +3,7 @@ "noEventsCalendarLabel" : "イベントなし", "ofDataPagerLabel" : "の", "pagesDataPagerLabel" : "ページ", -"rowsPerPageDataPagerLabel" : "1ページあたりの行数", +"rowsPerPageDataPagerLabel" : "ページあたりの行数", "pdfBookmarksLabel" : "ブックマーク", "pdfNoBookmarksLabel" : "ブックマークが見つかりません", "pdfScrollStatusOfLabel" : "の", @@ -12,48 +12,101 @@ "pdfInvalidPageNumberLabel" : "有効な数値を入力してください", "pdfPaginationDialogOkLabel" : "わかった", "pdfPaginationDialogCancelLabel" : "キャンセル", -"passwordDialogHeaderTextLabel" : "守られたパスワード", -"passwordDialogContentLabel" : "このPDFファイルを開くためのパスワードを入力してください", -"passwordDialogHintTextLabel" : "パスワードを入力する", +"passwordDialogHeaderTextLabel" : "パスワードで保護", +"passwordDialogContentLabel" : "このPDFファイルを開くにはパスワードを入力してください", +"passwordDialogHintTextLabel" : "パスワードを入力", "passwordDialogInvalidPasswordLabel" : "無効なパスワード", -"pdfPasswordDialogOpenLabel" : "開いた", +"pdfPasswordDialogOpenLabel" : "開く", "pdfPasswordDialogCancelLabel" : "キャンセル", +"pdfSignaturePadDialogHeaderTextLabel" : "サインを描いてください", +"pdfSignaturePadDialogPenColorLabel" : "ペンの色", +"pdfSignaturePadDialogClearLabel" : "クリア", +"pdfSignaturePadDialogSaveLabel" : "保存", +"pdfTextSelectionMenuCopyLabel" : "コピー", +"pdfTextSelectionMenuHighlightLabel" : "ハイライト", +"pdfTextSelectionMenuStrikethroughLabel" : "取り消し線", +"pdfTextSelectionMenuUnderlineLabel" : "下線", +"pdfTextSelectionMenuSquigglyLabel" : "波打つ", "allowedViewDayLabel" : "日", "allowedViewWeekLabel" : "週", -"allowedViewWorkWeekLabel" : "労働週", +"allowedViewWorkWeekLabel" : "勤務週", "allowedViewMonthLabel" : "月", "allowedViewScheduleLabel" : "スケジュール", "allowedViewTimelineDayLabel" : "タイムラインの日", -"allowedViewTimelineWeekLabel" : "タイムラインウィーク", -"allowedViewTimelineWorkWeekLabel" : "タイムラインワークウィーク", +"allowedViewTimelineWeekLabel" : "タイムライン 週", +"allowedViewTimelineWorkWeekLabel" : "タイムライン 作業週", "allowedViewTimelineMonthLabel" : "タイムライン月", "todayLabel" : "今日", "weeknumberLabel" : "週", "allDayLabel" : "一日中", -"muharramLabel" : "ムハッラム", -"safarLabel" : "Safar", -"rabi1Label" : "ラビー・ウル・アウワル", -"rabi2Label" : "ラビー・アル・タニ", -"jumada1Label" : "ジュマーダ・アル・アウワル", -"jumada2Label" : "ジュマーダ・アルタニ", +"muharramLabel" : "ムハラム", +"safarLabel" : "サファル", +"rabi1Label" : "ラビアルアワル", +"rabi2Label" : "ラビアルタニ", +"jumada1Label" : "ジュマーダ・アル・アワル", +"jumada2Label" : "ジュマーダ アルタニ", "rajabLabel" : "ラジャブ", -"shaabanLabel" : "シャアバーン", +"shaabanLabel" : "シャアバン", "ramadanLabel" : "ラマダン", -"shawwalLabel" : "シャウワール", -"dhualqiLabel" : "ズーアルチーダー", -"dhualhiLabel" : "ズーアルヒジャ", -"shortMuharramLabel" : "ムー。", -"shortSafarLabel" : "Saf。", +"shawwalLabel" : "シャワル", +"dhualqiLabel" : "ドゥ アルキダ", +"dhualhiLabel" : "ドゥ アル ヒジャ", +"shortMuharramLabel" : "うーん。", +"shortSafarLabel" : "サフ。", "shortRabi1Label" : "ラビ。私", -"shortRabi2Label" : "ラビ。 II", +"shortRabi2Label" : "ラビ。 Ⅱ", "shortJumada1Label" : "ジャム。私", -"shortJumada2Label" : "ジャム。 II", +"shortJumada2Label" : "ジャム。 Ⅱ", "shortRajabLabel" : "ラージ。", -"shortShaabanLabel" : "シャ。", +"shortShaabanLabel" : "しゃ。", "shortRamadanLabel" : "RAM。", "shortShawwalLabel" : "ショー。", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "デュルQ", +"shortDhualhiLabel" : "デュル-H", "daySpanCountLabel" : "日", -"series" : "シリーズ" +"series" : "シリーズ", +"pdfHyperlinkLabel" : "ウェブページを開く", +"pdfHyperlinkContentLabel" : "でページを開きますか?", +"pdfHyperlinkDialogOpenLabel" : "開いた", +"pdfHyperlinkDialogCancelLabel" : "キャンセル", +"afterDataGridFilteringLabel" : "後", +"afterOrEqualDataGridFilteringLabel" : "後または等しい", +"beforeDataGridFilteringLabel" : "前", +"beforeOrEqualDataGridFilteringLabel" : "前または等しい", +"beginsWithDataGridFilteringLabel" : "次で始まる", +"containsDataGridFilteringLabel" : "含む", +"doesNotBeginWithDataGridFilteringLabel" : "次で始まらない", +"doesNotContainDataGridFilteringLabel" : "含まない", +"doesNotEndWithDataGridFilteringLabel" : "次で終わらない", +"doesNotEqualDataGridFilteringLabel" : "等しくない", +"emptyDataGridFilteringLabel" : "空の", +"endsWithDataGridFilteringLabel" : "で終わる", +"equalsDataGridFilteringLabel" : "等しい", +"greaterThanDataGridFilteringLabel" : "より大きい", +"greaterThanOrEqualDataGridFilteringLabel" : "以上", +"lessThanDataGridFilteringLabel" : "未満", +"lessThanOrEqualDataGridFilteringLabel" : "以下", +"notEmptyDataGridFilteringLabel" : "空ではない", +"notNullDataGridFilteringLabel" : "ヌルではない", +"nullDataGridFilteringLabel" : "ヌル", +"sortSmallestToLargestDataGridFilteringLabel" : "最小から最大への並べ替え", +"sortLargestToSmallestDataGridFilteringLabel" : "大きいものから小さいものへ並べ替え", +"sortAToZDataGridFilteringLabel" : "A から Z に並べ替える", +"sortZToADataGridFilteringLabel" : "Z から A に並べ替え", +"sortOldestToNewestDataGridFilteringLabel" : "古いものから新しいものへ並べ替え", +"sortNewestToOldestDataGridFilteringLabel" : "新しいものから古いものへ並べ替え", +"textFiltersDataGridFilteringLabel" : "テキスト フィルター", +"numberFiltersDataGridFilteringLabel" : "数値フィルター", +"dateFiltersDataGridFilteringLabel" : "日付フィルター", +"searchDataGridFilteringLabel" : "探す", +"noMatchesDataGridFilteringLabel" : "一致するものはありません", +"okDataGridFilteringLabel" : "わかった", +"cancelDataGridFilteringLabel" : "キャンセル", +"showRowsWhereDataGridFilteringLabel" : "行を表示", +"andDataGridFilteringLabel" : "と", +"orDataGridFilteringLabel" : "または", +"selectAllDataGridFilteringLabel" : "すべて選択", +"sortAndFilterDataGridFilteringLabel" : "並べ替えとフィルター", +"clearFilterDataGridFilteringLabel" : "フィルターをクリア", +"fromDataGridFilteringLabel" : "から" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb index 02e4da972..d811f16ee 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb @@ -18,7 +18,16 @@ "passwordDialogInvalidPasswordLabel" : "არასწორი პაროლი", "pdfPasswordDialogOpenLabel" : "გახსენით", "pdfPasswordDialogCancelLabel" : "გაუქმება", -"allowedViewDayLabel" : "Დღეს", +"pdfSignaturePadDialogHeaderTextLabel" : "დახატე შენი ხელმოწერა", +"pdfSignaturePadDialogPenColorLabel" : "კალმის ფერი", +"pdfSignaturePadDialogClearLabel" : "გასუფთავება", +"pdfSignaturePadDialogSaveLabel" : "შენახვა", +"allowedViewDayLabel" : "Დღის", +"pdfTextSelectionMenuCopyLabel" : "კოპირება", +"pdfTextSelectionMenuHighlightLabel" : "მონიშნეთ", +"pdfTextSelectionMenuStrikethroughLabel" : "გადახაზვა", +"pdfTextSelectionMenuUnderlineLabel" : "ხაზი გაუსვით", +"pdfTextSelectionMenuSquigglyLabel" : "ცბიერად", "allowedViewWeekLabel" : "კვირა", "allowedViewWorkWeekLabel" : "Სამუშაო კვირა", "allowedViewMonthLabel" : "თვე", @@ -54,6 +63,50 @@ "shortShawwalLabel" : "შოუ.", "shortDhualqiLabel" : "დულ-ქ", "shortDhualhiLabel" : "დულ-ჰ", -"daySpanCountLabel" : "Დღეს", -"series" : "სერიალი" +"daySpanCountLabel" : "Დღის", +"series" : "სერიალი", +"pdfHyperlinkLabel" : "გახსენით ვებ გვერდი", +"pdfHyperlinkContentLabel" : "გსურთ გახსნათ გვერდი", +"pdfHyperlinkDialogOpenLabel" : "გახსენით", +"pdfHyperlinkDialogCancelLabel" : "გაუქმება", +"afterDataGridFilteringLabel" : "შემდეგ", +"afterOrEqualDataGridFilteringLabel" : "შემდეგ ან თანაბარი", +"beforeDataGridFilteringLabel" : "მანამდე", +"beforeOrEqualDataGridFilteringLabel" : "ადრე ან თანაბარი", +"beginsWithDataGridFilteringLabel" : "Იწყება", +"containsDataGridFilteringLabel" : "შეიცავს", +"doesNotBeginWithDataGridFilteringLabel" : "არ იწყება", +"doesNotContainDataGridFilteringLabel" : "Არ შეიცავს", +"doesNotEndWithDataGridFilteringLabel" : "არ მთავრდება", +"doesNotEqualDataGridFilteringLabel" : "არ უდრის", +"emptyDataGridFilteringLabel" : "ცარიელი", +"endsWithDataGridFilteringLabel" : "მთავრდება", +"equalsDataGridFilteringLabel" : "უდრის", +"greaterThanDataGridFilteringLabel" : "Მეტია, ვიდრე", +"greaterThanOrEqualDataGridFilteringLabel" : "მეტი ან თანაბარი", +"lessThanDataGridFilteringLabel" : "Ნაკლები ვიდრე", +"lessThanOrEqualDataGridFilteringLabel" : "ნაკლები ან თანაბარი", +"notEmptyDataGridFilteringLabel" : "Არ არის ცარიელი", +"notNullDataGridFilteringLabel" : "არა ნულოვანი", +"nullDataGridFilteringLabel" : "ნულოვანი", +"sortSmallestToLargestDataGridFilteringLabel" : "დახარისხება პატარადან დიდამდე", +"sortLargestToSmallestDataGridFilteringLabel" : "სორტირება დიდიდან პატარამდე", +"sortAToZDataGridFilteringLabel" : "დალაგება A-დან Z-მდე", +"sortZToADataGridFilteringLabel" : "დახარისხება Z-ზე A", +"sortOldestToNewestDataGridFilteringLabel" : "დალაგება უძველესიდან უახლესზე", +"sortNewestToOldestDataGridFilteringLabel" : "დალაგება უახლესიდან ძველზე", +"textFiltersDataGridFilteringLabel" : "ტექსტის ფილტრები", +"numberFiltersDataGridFilteringLabel" : "რიცხვების ფილტრები", +"dateFiltersDataGridFilteringLabel" : "თარიღის ფილტრები", +"searchDataGridFilteringLabel" : "ძიება", +"noMatchesDataGridFilteringLabel" : "არანაირი მატჩი", +"okDataGridFilteringLabel" : "კარგი", +"cancelDataGridFilteringLabel" : "გაუქმება", +"showRowsWhereDataGridFilteringLabel" : "აჩვენე რიგები სად", +"andDataGridFilteringLabel" : "და", +"orDataGridFilteringLabel" : "ან", +"selectAllDataGridFilteringLabel" : "Მონიშნე ყველა", +"sortAndFilterDataGridFilteringLabel" : "დალაგება და გაფილტვრა", +"clearFilterDataGridFilteringLabel" : "ფილტრის გასუფთავება", +"fromDataGridFilteringLabel" : "დან" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb index c13fa7965..467fdea7a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb @@ -1,23 +1,32 @@ { "noSelectedDateCalendarLabel" : "Таңдалған күн жоқ", "noEventsCalendarLabel" : "Оқиғалар жоқ", -"ofDataPagerLabel" : "бойынша", +"ofDataPagerLabel" : "ның", "pagesDataPagerLabel" : "беттер", "rowsPerPageDataPagerLabel" : "Әр беттегі жолдар", "pdfBookmarksLabel" : "Бетбелгілер", "pdfNoBookmarksLabel" : "Ешқандай бетбелгі табылмады", -"pdfScrollStatusOfLabel" : "бойынша", +"pdfScrollStatusOfLabel" : "ның", "pdfGoToPageLabel" : "Бетке өту", "pdfEnterPageNumberLabel" : "Бет нөмірін енгізіңіз", "pdfInvalidPageNumberLabel" : "Жарамды нөмірді енгізіңіз", -"pdfPaginationDialogOkLabel" : "ЖАРАЙДЫ МА", -"pdfPaginationDialogCancelLabel" : "БАС ТАРТУ", +"pdfPaginationDialogOkLabel" : "ОК", +"pdfPaginationDialogCancelLabel" : "Болдырмау", "passwordDialogHeaderTextLabel" : "Құпия сөзбен қорғалған", "passwordDialogContentLabel" : "Осы PDF файлын ашу үшін құпия сөзді енгізіңіз", "passwordDialogHintTextLabel" : "Құпия сөзді енгізіңіз", -"passwordDialogInvalidPasswordLabel" : "Құпия сөз жарамсыз", +"passwordDialogInvalidPasswordLabel" : "Жарамсыз құпия сөз", "pdfPasswordDialogOpenLabel" : "АШЫҚ", -"pdfPasswordDialogCancelLabel" : "БАС ТАРТУ", +"pdfPasswordDialogCancelLabel" : "Болдырмау", +"pdfSignaturePadDialogHeaderTextLabel" : "Қолтаңбаңызды сызыңыз", +"pdfSignaturePadDialogPenColorLabel" : "Қалам түсі", +"pdfSignaturePadDialogClearLabel" : "ТАЗАЛАУ", +"pdfSignaturePadDialogSaveLabel" : "САҚТАУ", +"pdfTextSelectionMenuCopyLabel" : "Көшіру", +"pdfTextSelectionMenuHighlightLabel" : "Бөлектеу", +"pdfTextSelectionMenuStrikethroughLabel" : "Сызық", +"pdfTextSelectionMenuUnderlineLabel" : "Астын сызу", +"pdfTextSelectionMenuSquigglyLabel" : "Айналмалы", "allowedViewDayLabel" : "Күн", "allowedViewWeekLabel" : "Апта", "allowedViewWorkWeekLabel" : "Жұмыс аптасы", @@ -40,7 +49,7 @@ "shaabanLabel" : "Шағбан", "ramadanLabel" : "Рамазан", "shawwalLabel" : "Шәууәл", -"dhualqiLabel" : "Зу әл-Қида", +"dhualqiLabel" : "Зул-Қида", "dhualhiLabel" : "Зул-Хиджа", "shortMuharramLabel" : "Мұх.", "shortSafarLabel" : "Saf.", @@ -52,8 +61,52 @@ "shortShaabanLabel" : "Ша.", "shortRamadanLabel" : "Жедел Жадтау Құрылғысы.", "shortShawwalLabel" : "Шоу.", -"shortDhualqiLabel" : "Зул-Қ", +"shortDhualqiLabel" : "Зуль-Қ", "shortDhualhiLabel" : "Зул-Х", "daySpanCountLabel" : "Күн", -"series" : "Сериялар" +"series" : "Сериялар", +"pdfHyperlinkLabel" : "Веб-бетті ашыңыз", +"pdfHyperlinkContentLabel" : "бетті ашқыңыз келе ме", +"pdfHyperlinkDialogOpenLabel" : "АШЫҚ", +"pdfHyperlinkDialogCancelLabel" : "Болдырмау", +"afterDataGridFilteringLabel" : "Кейін", +"afterOrEqualDataGridFilteringLabel" : "Кейін немесе Тең", +"beforeDataGridFilteringLabel" : "Бұрын", +"beforeOrEqualDataGridFilteringLabel" : "Бұрын немесе Тең", +"beginsWithDataGridFilteringLabel" : "-мен басталады", +"containsDataGridFilteringLabel" : "Құрамында", +"doesNotBeginWithDataGridFilteringLabel" : "Бастамайды", +"doesNotContainDataGridFilteringLabel" : "Құрамында жоқ", +"doesNotEndWithDataGridFilteringLabel" : "-мен бітпейді", +"doesNotEqualDataGridFilteringLabel" : "Тең емес", +"emptyDataGridFilteringLabel" : "Бос", +"endsWithDataGridFilteringLabel" : "Аяқталады", +"equalsDataGridFilteringLabel" : "Тең", +"greaterThanDataGridFilteringLabel" : "Үлкенірек", +"greaterThanOrEqualDataGridFilteringLabel" : "Үлкен немесе тең", +"lessThanDataGridFilteringLabel" : "Одан азырақ", +"lessThanOrEqualDataGridFilteringLabel" : "Кіші немесе Тең", +"notEmptyDataGridFilteringLabel" : "Бос емес", +"notNullDataGridFilteringLabel" : "Нөл емес", +"nullDataGridFilteringLabel" : "Нөл", +"sortSmallestToLargestDataGridFilteringLabel" : "Ең кішіден үлкенге сұрыптау", +"sortLargestToSmallestDataGridFilteringLabel" : "Үлкеннен кішіге сұрыптау", +"sortAToZDataGridFilteringLabel" : "Адан Яға дейін сұрыптау", +"sortZToADataGridFilteringLabel" : "Z-ден A-ға сұрыптау", +"sortOldestToNewestDataGridFilteringLabel" : "Ең ескіден ең жаңасына сұрыптау", +"sortNewestToOldestDataGridFilteringLabel" : "Ең жаңадан ең ескіге сұрыптау", +"textFiltersDataGridFilteringLabel" : "Мәтін сүзгілері", +"numberFiltersDataGridFilteringLabel" : "Сандық сүзгілер", +"dateFiltersDataGridFilteringLabel" : "Күн сүзгілері", +"searchDataGridFilteringLabel" : "Іздеу", +"noMatchesDataGridFilteringLabel" : "Сәйкестік жоқ", +"okDataGridFilteringLabel" : "ЖАРАЙДЫ МА", +"cancelDataGridFilteringLabel" : "Болдырмау", +"showRowsWhereDataGridFilteringLabel" : "Қай жерде жолдарды көрсетіңіз", +"andDataGridFilteringLabel" : "Және", +"orDataGridFilteringLabel" : "Немесе", +"selectAllDataGridFilteringLabel" : "Барлығын таңдаңыз", +"sortAndFilterDataGridFilteringLabel" : "Сұрыптау және сүзу", +"clearFilterDataGridFilteringLabel" : "Сүзгіні тазалау", +"fromDataGridFilteringLabel" : "бастап" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb index 4880e6385..15ccf6904 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "ពាក្យសម្ងាត់មិនត្រឹមត្រូវ", "pdfPasswordDialogOpenLabel" : "បើក", "pdfPasswordDialogCancelLabel" : "បោះបង់", +"pdfSignaturePadDialogHeaderTextLabel" : "គូរហត្ថលេខារបស់អ្នក។", +"pdfSignaturePadDialogPenColorLabel" : "ពណ៌ប៊ិច", +"pdfSignaturePadDialogClearLabel" : "ជម្រះ", +"pdfSignaturePadDialogSaveLabel" : "រក្សាទុក", +"pdfTextSelectionMenuCopyLabel" : "ចម្លង", +"pdfTextSelectionMenuHighlightLabel" : "បន្លិច", +"pdfTextSelectionMenuStrikethroughLabel" : "ការវាយឆ្មក់", +"pdfTextSelectionMenuUnderlineLabel" : "គូសបន្ទាត់ពីក្រោម", +"pdfTextSelectionMenuSquigglyLabel" : "ញាក់សាច់", "allowedViewDayLabel" : "ថ្ងៃ", "allowedViewWeekLabel" : "សប្តាហ៍", "allowedViewWorkWeekLabel" : "សប្តាហ៍ការងារ", @@ -26,7 +35,7 @@ "allowedViewTimelineDayLabel" : "ថ្ងៃកំណត់ពេលវេលា", "allowedViewTimelineWeekLabel" : "Timeline សប្តាហ៍", "allowedViewTimelineWorkWeekLabel" : "កាលវិភាគការងារប្រចាំសប្តាហ៍", -"allowedViewTimelineMonthLabel" : "តារាងពេលវេលាខែ", +"allowedViewTimelineMonthLabel" : "ខែពេលវេលា", "todayLabel" : "ថ្ងៃនេះ", "weeknumberLabel" : "សប្តាហ៍", "allDayLabel" : "ពេញមួយថ្ងៃ", @@ -37,9 +46,9 @@ "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "ជូម៉ាដា អាល់ថានី", "rajabLabel" : "រ៉ាចាប", -"shaabanLabel" : "សារ៉ាបាន", +"shaabanLabel" : "សាបាន", "ramadanLabel" : "រ៉ាម៉ាដាន", -"shawwalLabel" : "Shawwal", +"shawwalLabel" : "សាវ៉ាវ", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "ម៉ោ", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "ថ្ងៃ", -"series" : "ស៊េរី" +"series" : "ស៊េរី", +"pdfHyperlinkLabel" : "បើកគេហទំព័រ", +"pdfHyperlinkContentLabel" : "តើអ្នកចង់បើកទំព័រនៅ", +"pdfHyperlinkDialogOpenLabel" : "បើក", +"pdfHyperlinkDialogCancelLabel" : "បោះបង់", +"afterDataGridFilteringLabel" : "បន្ទាប់ពី", +"afterOrEqualDataGridFilteringLabel" : "បន្ទាប់ពី ឬស្មើ", +"beforeDataGridFilteringLabel" : "ពីមុន", +"beforeOrEqualDataGridFilteringLabel" : "មុន ឬស្មើ", +"beginsWithDataGridFilteringLabel" : "ចាប់ផ្តើមជាមួយ", +"containsDataGridFilteringLabel" : "មាន", +"doesNotBeginWithDataGridFilteringLabel" : "មិនចាប់ផ្តើមជាមួយ", +"doesNotContainDataGridFilteringLabel" : "មិនមានផ្ទុក", +"doesNotEndWithDataGridFilteringLabel" : "មិនបញ្ចប់ដោយ", +"doesNotEqualDataGridFilteringLabel" : "មិនស្មើគ្នា", +"emptyDataGridFilteringLabel" : "ទទេ", +"endsWithDataGridFilteringLabel" : "បញ្ចប់ដោយ", +"equalsDataGridFilteringLabel" : "ស្មើ", +"greaterThanDataGridFilteringLabel" : "ធំជាង", +"greaterThanOrEqualDataGridFilteringLabel" : "ធំជាង ឬស្មើ", +"lessThanDataGridFilteringLabel" : "តិច​ជាង", +"lessThanOrEqualDataGridFilteringLabel" : "តិចជាង ឬស្មើ", +"notEmptyDataGridFilteringLabel" : "មិនទទេ", +"notNullDataGridFilteringLabel" : "មិនមែន Null", +"nullDataGridFilteringLabel" : "ទុកជាមោឃៈ", +"sortSmallestToLargestDataGridFilteringLabel" : "តម្រៀបតូចបំផុតទៅធំបំផុត។", +"sortLargestToSmallestDataGridFilteringLabel" : "តម្រៀបធំបំផុតទៅតូចបំផុត។", +"sortAToZDataGridFilteringLabel" : "តម្រៀប A ដល់ Z", +"sortZToADataGridFilteringLabel" : "តម្រៀប Z ទៅ A", +"sortOldestToNewestDataGridFilteringLabel" : "តម្រៀបចាស់បំផុតទៅថ្មីបំផុត។", +"sortNewestToOldestDataGridFilteringLabel" : "តម្រៀបថ្មីបំផុតទៅចាស់បំផុត។", +"textFiltersDataGridFilteringLabel" : "តម្រងអត្ថបទ", +"numberFiltersDataGridFilteringLabel" : "តម្រងលេខ", +"dateFiltersDataGridFilteringLabel" : "តម្រងកាលបរិច្ឆេទ", +"searchDataGridFilteringLabel" : "ស្វែងរក", +"noMatchesDataGridFilteringLabel" : "គ្មានការប្រកួត", +"okDataGridFilteringLabel" : "យល់ព្រម", +"cancelDataGridFilteringLabel" : "បោះបង់", +"showRowsWhereDataGridFilteringLabel" : "បង្ហាញជួរនៅកន្លែងណា", +"andDataGridFilteringLabel" : "និង", +"orDataGridFilteringLabel" : "ឬ", +"selectAllDataGridFilteringLabel" : "ជ្រើសរើស​ទាំងអស់", +"sortAndFilterDataGridFilteringLabel" : "តម្រៀបនិងត្រង", +"clearFilterDataGridFilteringLabel" : "ជម្រះតម្រង", +"fromDataGridFilteringLabel" : "ពី" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb index 3261d7752..3a9308da8 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb @@ -13,11 +13,20 @@ "pdfPaginationDialogOkLabel" : "ಸರಿ", "pdfPaginationDialogCancelLabel" : "ರದ್ದುಮಾಡು", "passwordDialogHeaderTextLabel" : "ಪಾಸ್ವರ್ಡ್ ರಕ್ಷಿಸಲಾಗಿದೆ", -"passwordDialogContentLabel" : "ಈ PDF ಫೈಲ್ ಅನ್ನು ತೆರೆಯಲು ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ನಮೂದಿಸಿ", +"passwordDialogContentLabel" : "ಈ PDF ಫೈಲ್ ತೆರೆಯಲು ಪಾಸ್‌ವರ್ಡ್ ನಮೂದಿಸಿ", "passwordDialogHintTextLabel" : "ಪಾಸ್ವರ್ಡ್ ನಮೂದಿಸಿ", "passwordDialogInvalidPasswordLabel" : "ಅಮಾನ್ಯವಾದ ಪಾಸ್ವರ್ಡ್", "pdfPasswordDialogOpenLabel" : "ತೆರೆಯಿರಿ", "pdfPasswordDialogCancelLabel" : "ರದ್ದುಮಾಡು", +"pdfSignaturePadDialogHeaderTextLabel" : "ನಿಮ್ಮ ಸಹಿಯನ್ನು ಎಳೆಯಿರಿ", +"pdfSignaturePadDialogPenColorLabel" : "ಪೆನ್ ಬಣ್ಣ", +"pdfSignaturePadDialogClearLabel" : "ಸ್ಪಷ್ಟ", +"pdfSignaturePadDialogSaveLabel" : "ಉಳಿಸಿ", +"pdfTextSelectionMenuCopyLabel" : "ನಕಲು ಮಾಡಿ", +"pdfTextSelectionMenuHighlightLabel" : "ಹೈಲೈಟ್", +"pdfTextSelectionMenuStrikethroughLabel" : "ಸ್ಟ್ರೈಕ್ಥ್ರೂ", +"pdfTextSelectionMenuUnderlineLabel" : "ಅಂಡರ್ಲೈನ್", +"pdfTextSelectionMenuSquigglyLabel" : "ಕುಂಜ", "allowedViewDayLabel" : "ದಿನ", "allowedViewWeekLabel" : "ವಾರ", "allowedViewWorkWeekLabel" : "ಕೆಲಸದ ವಾರ", @@ -44,8 +53,8 @@ "dhualhiLabel" : "ಧು ಅಲ್-ಹಿಜ್ಜಾ", "shortMuharramLabel" : "ಮುಹ್", "shortSafarLabel" : "ಸೇಫ್", -"shortRabi1Label" : "ರಬಿ. I", -"shortRabi2Label" : "ರಬಿ. II", +"shortRabi1Label" : "ರಬಿ I", +"shortRabi2Label" : "ರಬಿ II", "shortJumada1Label" : "ಜಂ. I", "shortJumada2Label" : "ಜಂ. II", "shortRajabLabel" : "ರಾಜ್.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ಧುಲ್-ಕ್ಯೂ", "shortDhualhiLabel" : "ಧುಲ್-ಎಚ್", "daySpanCountLabel" : "ದಿನ", -"series" : "ಸರಣಿ" +"series" : "ಸರಣಿ", +"pdfHyperlinkLabel" : "ವೆಬ್ ಪುಟವನ್ನು ತೆರೆಯಿರಿ", +"pdfHyperlinkContentLabel" : "ನೀವು ಪುಟವನ್ನು ತೆರೆಯಲು ಬಯಸುವಿರಾ", +"pdfHyperlinkDialogOpenLabel" : "ತೆರೆಯಿರಿ", +"pdfHyperlinkDialogCancelLabel" : "ರದ್ದುಮಾಡು", +"afterDataGridFilteringLabel" : "ನಂತರ", +"afterOrEqualDataGridFilteringLabel" : "ನಂತರ ಅಥವಾ ಸಮಾನ", +"beforeDataGridFilteringLabel" : "ಮೊದಲು", +"beforeOrEqualDataGridFilteringLabel" : "ಮೊದಲು ಅಥವಾ ಸಮಾನ", +"beginsWithDataGridFilteringLabel" : "ಇದರೊಂದಿಗೆ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ", +"containsDataGridFilteringLabel" : "ಒಳಗೊಂಡಿದೆ", +"doesNotBeginWithDataGridFilteringLabel" : "ಇದರೊಂದಿಗೆ ಪ್ರಾರಂಭವಾಗುವುದಿಲ್ಲ", +"doesNotContainDataGridFilteringLabel" : "ಒಳಗೊಂಡಿಲ್ಲ", +"doesNotEndWithDataGridFilteringLabel" : "ಇದರೊಂದಿಗೆ ಕೊನೆಗೊಳ್ಳುವುದಿಲ್ಲ", +"doesNotEqualDataGridFilteringLabel" : "ಸಮಾನವಾಗಿಲ್ಲ", +"emptyDataGridFilteringLabel" : "ಖಾಲಿ", +"endsWithDataGridFilteringLabel" : "ಇದರೊಂದಿಗೆ ಕೊನೆಗೊಳ್ಳುತ್ತದೆ", +"equalsDataGridFilteringLabel" : "ಸಮನಾಗಿರುತ್ತದೆ", +"greaterThanDataGridFilteringLabel" : "ಅದಕ್ಕಿಂತ ಹೆಚ್ಚು", +"greaterThanOrEqualDataGridFilteringLabel" : "ಗ್ರೇಟರ್ ದ್ಯಾನ್ ಅಥವಾ ಈಕ್ವಲ್", +"lessThanDataGridFilteringLabel" : "ಕಡಿಮೆ", +"lessThanOrEqualDataGridFilteringLabel" : "ಕಡಿಮೆ ಅಥವಾ ಸಮಾನ", +"notEmptyDataGridFilteringLabel" : "ಖಾಲಿ ಇಲ್ಲ", +"notNullDataGridFilteringLabel" : "ಶೂನ್ಯವಲ್ಲ", +"nullDataGridFilteringLabel" : "ಶೂನ್ಯ", +"sortSmallestToLargestDataGridFilteringLabel" : "ಚಿಕ್ಕದರಿಂದ ದೊಡ್ಡದಕ್ಕೆ ವಿಂಗಡಿಸಿ", +"sortLargestToSmallestDataGridFilteringLabel" : "ದೊಡ್ಡದರಿಂದ ಚಿಕ್ಕದಕ್ಕೆ ವಿಂಗಡಿಸಿ", +"sortAToZDataGridFilteringLabel" : "A ನಿಂದ Z ಗೆ ವಿಂಗಡಿಸಿ", +"sortZToADataGridFilteringLabel" : "Z ನಿಂದ A ಗೆ ವಿಂಗಡಿಸಿ", +"sortOldestToNewestDataGridFilteringLabel" : "ಹಳೆಯದರಿಂದ ಹೊಸದಕ್ಕೆ ವಿಂಗಡಿಸಿ", +"sortNewestToOldestDataGridFilteringLabel" : "ಹೊಸದನ್ನು ಹಳೆಯದಕ್ಕೆ ವಿಂಗಡಿಸಿ", +"textFiltersDataGridFilteringLabel" : "ಪಠ್ಯ ಶೋಧಕಗಳು", +"numberFiltersDataGridFilteringLabel" : "ಸಂಖ್ಯೆ ಶೋಧಕಗಳು", +"dateFiltersDataGridFilteringLabel" : "ದಿನಾಂಕ ಶೋಧಕಗಳು", +"searchDataGridFilteringLabel" : "ಹುಡುಕಿ", +"noMatchesDataGridFilteringLabel" : "ಯಾವುದೇ ಹೊಂದಾಣಿಕೆಗಳಿಲ್ಲ", +"okDataGridFilteringLabel" : "ಸರಿ", +"cancelDataGridFilteringLabel" : "ರದ್ದುಮಾಡಿ", +"showRowsWhereDataGridFilteringLabel" : "ಅಲ್ಲಿ ಸಾಲುಗಳನ್ನು ತೋರಿಸಿ", +"andDataGridFilteringLabel" : "ಮತ್ತು", +"orDataGridFilteringLabel" : "ಅಥವಾ", +"selectAllDataGridFilteringLabel" : "ಎಲ್ಲವನ್ನು ಆರಿಸು", +"sortAndFilterDataGridFilteringLabel" : "ವಿಂಗಡಿಸಿ ಮತ್ತು ಫಿಲ್ಟರ್ ಮಾಡಿ", +"clearFilterDataGridFilteringLabel" : "ಫಿಲ್ಟರ್ ಅನ್ನು ತೆರವುಗೊಳಿಸಿ", +"fromDataGridFilteringLabel" : "ಇಂದ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb index 272d1ebd4..aac27ab7c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb @@ -1,31 +1,40 @@ { -"noSelectedDateCalendarLabel" : "선택한 날짜가 없습니다", -"noEventsCalendarLabel" : "이벤트 없음", +"noSelectedDateCalendarLabel" : "선택한 날짜 없음", +"noEventsCalendarLabel" : "일정 없음", "ofDataPagerLabel" : "의", "pagesDataPagerLabel" : "페이지", "rowsPerPageDataPagerLabel" : "페이지당 행", -"pdfBookmarksLabel" : "책갈피", -"pdfNoBookmarksLabel" : "북마크를 찾을 수 없습니다", +"pdfBookmarksLabel" : "북마크", +"pdfNoBookmarksLabel" : "북마크가 없습니다.", "pdfScrollStatusOfLabel" : "의", "pdfGoToPageLabel" : "페이지로 이동", "pdfEnterPageNumberLabel" : "페이지 번호 입력", -"pdfInvalidPageNumberLabel" : "유효한 숫자를 입력하세요", -"pdfPaginationDialogOkLabel" : "좋아요", +"pdfInvalidPageNumberLabel" : "유효한 숫자를 입력하세요.", +"pdfPaginationDialogOkLabel" : "확인", "pdfPaginationDialogCancelLabel" : "취소", "passwordDialogHeaderTextLabel" : "암호로 보호됨", "passwordDialogContentLabel" : "이 PDF 파일을 열려면 비밀번호를 입력하세요", "passwordDialogHintTextLabel" : "암호를 입력", "passwordDialogInvalidPasswordLabel" : "유효하지 않은 비밀번호", -"pdfPasswordDialogOpenLabel" : "열려있는", +"pdfPasswordDialogOpenLabel" : "열기", "pdfPasswordDialogCancelLabel" : "취소", +"pdfSignaturePadDialogHeaderTextLabel" : "서명하기", +"pdfSignaturePadDialogPenColorLabel" : "펜 색상", +"pdfSignaturePadDialogClearLabel" : "비우기", +"pdfSignaturePadDialogSaveLabel" : "저장", +"pdfTextSelectionMenuCopyLabel" : "복사", +"pdfTextSelectionMenuHighlightLabel" : "강조", +"pdfTextSelectionMenuStrikethroughLabel" : "취소선", +"pdfTextSelectionMenuUnderlineLabel" : "밑줄", +"pdfTextSelectionMenuSquigglyLabel" : "물결선", "allowedViewDayLabel" : "일", "allowedViewWeekLabel" : "주", -"allowedViewWorkWeekLabel" : "작업 주간", +"allowedViewWorkWeekLabel" : "근무 주", "allowedViewMonthLabel" : "월", "allowedViewScheduleLabel" : "일정", -"allowedViewTimelineDayLabel" : "타임라인의 날", -"allowedViewTimelineWeekLabel" : "타임라인 주간", -"allowedViewTimelineWorkWeekLabel" : "타임라인 작업 주간", +"allowedViewTimelineDayLabel" : "타임라인 데이", +"allowedViewTimelineWeekLabel" : "타임라인 위크", +"allowedViewTimelineWorkWeekLabel" : "타임라인 작업 주", "allowedViewTimelineMonthLabel" : "타임라인 월", "todayLabel" : "오늘", "weeknumberLabel" : "주", @@ -33,16 +42,16 @@ "muharramLabel" : "무하람", "safarLabel" : "사파르", "rabi1Label" : "라비 알-아왈", -"rabi2Label" : "라비 알타니", +"rabi2Label" : "라비 알 타니", "jumada1Label" : "주마다 알-아왈", -"jumada2Label" : "주마다 알타니", -"rajabLabel" : "라자브", +"jumada2Label" : "주마다 알 타니", +"rajabLabel" : "라잡", "shaabanLabel" : "샤아반", "ramadanLabel" : "라마단", "shawwalLabel" : "샤왈", -"dhualqiLabel" : "두 알 키다", +"dhualqiLabel" : "두 알 키이다", "dhualhiLabel" : "두 알 히자", -"shortMuharramLabel" : "음.", +"shortMuharramLabel" : "머.", "shortSafarLabel" : "사프.", "shortRabi1Label" : "라비. 나", "shortRabi2Label" : "라비. II", @@ -54,6 +63,50 @@ "shortShawwalLabel" : "쇼.", "shortDhualqiLabel" : "둘큐", "shortDhualhiLabel" : "둘-H", -"daySpanCountLabel" : "일", -"series" : "시리즈" +"daySpanCountLabel" : "낮", +"series" : "시리즈", +"pdfHyperlinkLabel" : "웹 페이지 열기", +"pdfHyperlinkContentLabel" : "에서 페이지를 열시겠습니까?", +"pdfHyperlinkDialogOpenLabel" : "열기", +"pdfHyperlinkDialogCancelLabel" : "취소", +"afterDataGridFilteringLabel" : "후에", +"afterOrEqualDataGridFilteringLabel" : "후 또는 같음", +"beforeDataGridFilteringLabel" : "전에", +"beforeOrEqualDataGridFilteringLabel" : "이전 또는 같음", +"beginsWithDataGridFilteringLabel" : "다음으로 시작", +"containsDataGridFilteringLabel" : "포함", +"doesNotBeginWithDataGridFilteringLabel" : "다음으로 시작하지 않음", +"doesNotContainDataGridFilteringLabel" : "포함되어 있지 않다", +"doesNotEndWithDataGridFilteringLabel" : "다음으로 끝나지 않음", +"doesNotEqualDataGridFilteringLabel" : "같지 않음", +"emptyDataGridFilteringLabel" : "비어 있는", +"endsWithDataGridFilteringLabel" : "로 끝나다", +"equalsDataGridFilteringLabel" : "같음", +"greaterThanDataGridFilteringLabel" : "보다 큰", +"greaterThanOrEqualDataGridFilteringLabel" : "크거나 같음", +"lessThanDataGridFilteringLabel" : "미만", +"lessThanOrEqualDataGridFilteringLabel" : "작거나 같음", +"notEmptyDataGridFilteringLabel" : "비어 있지 않음", +"notNullDataGridFilteringLabel" : "널이 아님", +"nullDataGridFilteringLabel" : "없는", +"sortSmallestToLargestDataGridFilteringLabel" : "작은 것부터 큰 것까지 정렬", +"sortLargestToSmallestDataGridFilteringLabel" : "내림차순 정렬", +"sortAToZDataGridFilteringLabel" : "A부터 Z까지 정렬", +"sortZToADataGridFilteringLabel" : "Z를 A로 정렬", +"sortOldestToNewestDataGridFilteringLabel" : "가장 오래된 것부터 최신순으로 정렬", +"sortNewestToOldestDataGridFilteringLabel" : "최신순으로 정렬", +"textFiltersDataGridFilteringLabel" : "텍스트 필터", +"numberFiltersDataGridFilteringLabel" : "숫자 필터", +"dateFiltersDataGridFilteringLabel" : "날짜 필터", +"searchDataGridFilteringLabel" : "검색", +"noMatchesDataGridFilteringLabel" : "맞지 않는다", +"okDataGridFilteringLabel" : "확인", +"cancelDataGridFilteringLabel" : "취소", +"showRowsWhereDataGridFilteringLabel" : "어디에 행 표시", +"andDataGridFilteringLabel" : "그리고", +"orDataGridFilteringLabel" : "또는", +"selectAllDataGridFilteringLabel" : "모두 선택", +"sortAndFilterDataGridFilteringLabel" : "정렬 및 필터링", +"clearFilterDataGridFilteringLabel" : "필터 지우기", +"fromDataGridFilteringLabel" : "에서" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb index 3da319ee9..86e1987fa 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb @@ -18,14 +18,23 @@ "passwordDialogInvalidPasswordLabel" : "туура эмес пароль", "pdfPasswordDialogOpenLabel" : "АЧУУ", "pdfPasswordDialogCancelLabel" : "ЖОК КЫЛУУ", -"allowedViewDayLabel" : "Күн", +"pdfSignaturePadDialogHeaderTextLabel" : "Колуңузду тартыңыз", +"pdfSignaturePadDialogPenColorLabel" : "Калем түсү", +"pdfSignaturePadDialogClearLabel" : "ТАЗАЛОО", +"pdfSignaturePadDialogSaveLabel" : "САКТОО", +"allowedViewDayLabel" : "күн", +"pdfTextSelectionMenuCopyLabel" : "Көчүрүү", +"pdfTextSelectionMenuHighlightLabel" : "Баса белгилөө", +"pdfTextSelectionMenuStrikethroughLabel" : "Чийүү", +"pdfTextSelectionMenuUnderlineLabel" : "Астын сыз", +"pdfTextSelectionMenuSquigglyLabel" : "Squiggly", "allowedViewWeekLabel" : "Апта", "allowedViewWorkWeekLabel" : "Жумуш аптасы", "allowedViewMonthLabel" : "Ай", "allowedViewScheduleLabel" : "График", "allowedViewTimelineDayLabel" : "Timeline Day", "allowedViewTimelineWeekLabel" : "Timeline Week", -"allowedViewTimelineWorkWeekLabel" : "Иш жумалыгы", +"allowedViewTimelineWorkWeekLabel" : "Хронология иш жумасы", "allowedViewTimelineMonthLabel" : "Убакыт тилкеси айы", "todayLabel" : "Бүгүн", "weeknumberLabel" : "Апта", @@ -38,7 +47,7 @@ "jumada2Label" : "Жумада ал-Тани", "rajabLabel" : "Ражаб", "shaabanLabel" : "Шаабан", -"ramadanLabel" : "Рамазан", +"ramadanLabel" : "Орозо айт", "shawwalLabel" : "Шаввал", "dhualqiLabel" : "Зул кыда", "dhualhiLabel" : "Зул Хижжа", @@ -54,6 +63,50 @@ "shortShawwalLabel" : "Шоу.", "shortDhualqiLabel" : "Зуль-К", "shortDhualhiLabel" : "Зул-Х", -"daySpanCountLabel" : "Күн", -"series" : "Сериялар" +"daySpanCountLabel" : "күн", +"series" : "Сериялар", +"pdfHyperlinkLabel" : "Веб баракчаны ачуу", +"pdfHyperlinkContentLabel" : "Баракчаны ачкыңыз келеби?", +"pdfHyperlinkDialogOpenLabel" : "АЧУУ", +"pdfHyperlinkDialogCancelLabel" : "ЖОК КЫЛУУ", +"afterDataGridFilteringLabel" : "Кийин", +"afterOrEqualDataGridFilteringLabel" : "Кийин же барабар", +"beforeDataGridFilteringLabel" : "Мурда", +"beforeOrEqualDataGridFilteringLabel" : "Мурун же барабар", +"beginsWithDataGridFilteringLabel" : "Менен башталат", +"containsDataGridFilteringLabel" : "камтыйт", +"doesNotBeginWithDataGridFilteringLabel" : "менен башталбайт", +"doesNotContainDataGridFilteringLabel" : "камтыбайт", +"doesNotEndWithDataGridFilteringLabel" : "менен бүтпөйт", +"doesNotEqualDataGridFilteringLabel" : "Тең эмес", +"emptyDataGridFilteringLabel" : "бош", +"endsWithDataGridFilteringLabel" : "менен аяктайт", +"equalsDataGridFilteringLabel" : "барабар", +"greaterThanDataGridFilteringLabel" : "Чоңураак", +"greaterThanOrEqualDataGridFilteringLabel" : "Чоңураак же барабар", +"lessThanDataGridFilteringLabel" : "Азыраак", +"lessThanOrEqualDataGridFilteringLabel" : "Аз же барабар", +"notEmptyDataGridFilteringLabel" : "Бош эмес", +"notNullDataGridFilteringLabel" : "Null эмес", +"nullDataGridFilteringLabel" : "Нөл", +"sortSmallestToLargestDataGridFilteringLabel" : "Кичинеден чоңго иреттөө", +"sortLargestToSmallestDataGridFilteringLabel" : "Чоңунан кичинесинен иреттөө", +"sortAToZDataGridFilteringLabel" : "Адан Яга чейин сорттоо", +"sortZToADataGridFilteringLabel" : "Zдан Ага чейин сорттоо", +"sortOldestToNewestDataGridFilteringLabel" : "Эң эскиден эң жаңысына иреттөө", +"sortNewestToOldestDataGridFilteringLabel" : "Эң жаңыдан эң эскиге иреттөө", +"textFiltersDataGridFilteringLabel" : "Текст чыпкалары", +"numberFiltersDataGridFilteringLabel" : "Сан чыпкалары", +"dateFiltersDataGridFilteringLabel" : "Дата чыпкалары", +"searchDataGridFilteringLabel" : "Издөө", +"noMatchesDataGridFilteringLabel" : "Дал келгендер жок", +"okDataGridFilteringLabel" : "макул", +"cancelDataGridFilteringLabel" : "Жокко чыгаруу", +"showRowsWhereDataGridFilteringLabel" : "Кайсы жерде катарларды көрсөтүү", +"andDataGridFilteringLabel" : "Жана", +"orDataGridFilteringLabel" : "Же", +"selectAllDataGridFilteringLabel" : "Баарын тандаңыз", +"sortAndFilterDataGridFilteringLabel" : "Сорттоо жана чыпкалоо", +"clearFilterDataGridFilteringLabel" : "Чыпканы тазалоо", +"fromDataGridFilteringLabel" : "From" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb index 1bca90eb6..64a004940 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb @@ -1,11 +1,11 @@ { -"noSelectedDateCalendarLabel" : "ບໍ່ມີວັນທີທີ່ເລືອກ", +"noSelectedDateCalendarLabel" : "ບໍ່ມີວັນທີເລືອກ", "noEventsCalendarLabel" : "ບໍ່ມີເຫດການ", "ofDataPagerLabel" : "ຂອງ", "pagesDataPagerLabel" : "ໜ້າ", "rowsPerPageDataPagerLabel" : "ແຖວຕໍ່ຫນ້າ", "pdfBookmarksLabel" : "ບຸກມາກ", -"pdfNoBookmarksLabel" : "ບໍ່ພົບບຸກມາກ", +"pdfNoBookmarksLabel" : "ບໍ່ມີບຸກມາກ", "pdfScrollStatusOfLabel" : "ຂອງ", "pdfGoToPageLabel" : "ໄປທີ່ໜ້າ", "pdfEnterPageNumberLabel" : "ໃສ່ເລກໜ້າ", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ", "pdfPasswordDialogOpenLabel" : "ເປີດ", "pdfPasswordDialogCancelLabel" : "ຍົກເລີກ", +"pdfSignaturePadDialogHeaderTextLabel" : "ແຕ້ມລາຍເຊັນຂອງເຈົ້າ", +"pdfSignaturePadDialogPenColorLabel" : "ສີປາກກາ", +"pdfSignaturePadDialogClearLabel" : "ລ້າງ", +"pdfSignaturePadDialogSaveLabel" : "ບັນທຶກ", +"pdfTextSelectionMenuCopyLabel" : "ສຳເນົາ", +"pdfTextSelectionMenuHighlightLabel" : "ຈຸດເດັ່ນ", +"pdfTextSelectionMenuStrikethroughLabel" : "ຂີດໄຂ້", +"pdfTextSelectionMenuUnderlineLabel" : "ຂີດເສັ້ນລຸ່ມ", +"pdfTextSelectionMenuSquigglyLabel" : "ຂີດສຸ້ມ", "allowedViewDayLabel" : "ມື້", "allowedViewWeekLabel" : "ອາທິດ", "allowedViewWorkWeekLabel" : "ອາທິດເຮັດວຽກ", @@ -39,7 +48,7 @@ "rajabLabel" : "Rajab", "shaabanLabel" : "ຊາອາບານ", "ramadanLabel" : "ຣາມາດານ", -"shawwalLabel" : "ຊວາວ", +"shawwalLabel" : "ຊະວາວ", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "ມ.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "ມື້", -"series" : "ຊຸດ" +"series" : "ຊຸດ", +"pdfHyperlinkLabel" : "ເປີດໜ້າເວັບ", +"pdfHyperlinkContentLabel" : "ທ່ານຕ້ອງການເປີດໜ້ານີ້ບໍ?", +"pdfHyperlinkDialogOpenLabel" : "ເປີດ", +"pdfHyperlinkDialogCancelLabel" : "ຍົກເລີກ", +"afterDataGridFilteringLabel" : "ຫຼັງຈາກ", +"afterOrEqualDataGridFilteringLabel" : "ຫຼັງ​ຈາກ​ຫຼື​ເທົ່າ​ທຽມ​ກັນ​", +"beforeDataGridFilteringLabel" : "ກ່ອນ", +"beforeOrEqualDataGridFilteringLabel" : "ກ່ອນ ຫຼື ເທົ່າກັນ", +"beginsWithDataGridFilteringLabel" : "ເລີ່ມຕົ້ນດ້ວຍ", +"containsDataGridFilteringLabel" : "ປະກອບດ້ວຍ", +"doesNotBeginWithDataGridFilteringLabel" : "ບໍ່ໄດ້ເລີ່ມຕົ້ນດ້ວຍ", +"doesNotContainDataGridFilteringLabel" : "ບໍ່ມີ", +"doesNotEndWithDataGridFilteringLabel" : "ບໍ່ສິ້ນສຸດດ້ວຍ", +"doesNotEqualDataGridFilteringLabel" : "ບໍ່ເທົ່າກັນ", +"emptyDataGridFilteringLabel" : "ຫວ່າງເປົ່າ", +"endsWithDataGridFilteringLabel" : "ສິ້ນສຸດດ້ວຍ", +"equalsDataGridFilteringLabel" : "ເທົ່າກັບ", +"greaterThanDataGridFilteringLabel" : "ໃຫຍ່​ກວ່າ", +"greaterThanOrEqualDataGridFilteringLabel" : "ຍິ່ງໃຫຍ່ກວ່າຫຼືເທົ່າທຽມກັນ", +"lessThanDataGridFilteringLabel" : "ຫນ້ອຍ​ກ​່​ວາ", +"lessThanOrEqualDataGridFilteringLabel" : "ໜ້ອຍກວ່າ ຫຼື ເທົ່າກັນ", +"notEmptyDataGridFilteringLabel" : "ບໍ່ຫວ່າງ", +"notNullDataGridFilteringLabel" : "ບໍ່ແມ່ນ Null", +"nullDataGridFilteringLabel" : "null", +"sortSmallestToLargestDataGridFilteringLabel" : "ຈັດລຽງນ້ອຍສຸດຫາໃຫຍ່ທີ່ສຸດ", +"sortLargestToSmallestDataGridFilteringLabel" : "ຈັດຮຽງໃຫຍ່ສຸດຫານ້ອຍສຸດ", +"sortAToZDataGridFilteringLabel" : "ຈັດຮຽງ A ຫາ Z", +"sortZToADataGridFilteringLabel" : "ຈັດຮຽງ Z ຫາ A", +"sortOldestToNewestDataGridFilteringLabel" : "ຈັດຮຽງເກົ່າສຸດຫາໃໝ່ສຸດ", +"sortNewestToOldestDataGridFilteringLabel" : "ຈັດຮຽງໃໝ່ສຸດຫາເກົ່າທີ່ສຸດ", +"textFiltersDataGridFilteringLabel" : "ຕົວກອງຂໍ້ຄວາມ", +"numberFiltersDataGridFilteringLabel" : "ຕົວກອງຕົວເລກ", +"dateFiltersDataGridFilteringLabel" : "ການກັ່ນຕອງວັນທີ", +"searchDataGridFilteringLabel" : "ຊອກຫາ", +"noMatchesDataGridFilteringLabel" : "ບໍ່ມີຂໍ້ມູນທີ່ກົງກັນ", +"okDataGridFilteringLabel" : "ຕົກ​ລົງ", +"cancelDataGridFilteringLabel" : "ຍົກເລີກ", +"showRowsWhereDataGridFilteringLabel" : "ສະແດງແຖວຢູ່ບ່ອນ", +"andDataGridFilteringLabel" : "ແລະ", +"orDataGridFilteringLabel" : "ຫຼື", +"selectAllDataGridFilteringLabel" : "ເລືອກ​ທັງ​ຫມົດ", +"sortAndFilterDataGridFilteringLabel" : "ຄັດແລະການກັ່ນຕອງ", +"clearFilterDataGridFilteringLabel" : "ລ້າງການກັ່ນຕອງ", +"fromDataGridFilteringLabel" : "ຈາກ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb index 839e62714..ee64df711 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb @@ -3,7 +3,7 @@ "noEventsCalendarLabel" : "Jokių įvykių", "ofDataPagerLabel" : "apie", "pagesDataPagerLabel" : "puslapių", -"rowsPerPageDataPagerLabel" : "Eilučių puslapyje", +"rowsPerPageDataPagerLabel" : "eilutėsPerPuslapį", "pdfBookmarksLabel" : "Žymės", "pdfNoBookmarksLabel" : "Žymių nerasta", "pdfScrollStatusOfLabel" : "apie", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Neteisingas slaptažodis", "pdfPasswordDialogOpenLabel" : "ATVIRAS", "pdfPasswordDialogCancelLabel" : "ATŠAUKTI", +"pdfSignaturePadDialogHeaderTextLabel" : "Nupiešk savo parašą", +"pdfSignaturePadDialogPenColorLabel" : "Rašiklio spalva", +"pdfSignaturePadDialogClearLabel" : "IŠVALYTI", +"pdfSignaturePadDialogSaveLabel" : "SUTAUPYTI", +"pdfTextSelectionMenuCopyLabel" : "Kopijuoti", +"pdfTextSelectionMenuHighlightLabel" : "Paryškinti", +"pdfTextSelectionMenuStrikethroughLabel" : "Perbraukta", +"pdfTextSelectionMenuUnderlineLabel" : "Pabraukite", +"pdfTextSelectionMenuSquigglyLabel" : "raibulis", "allowedViewDayLabel" : "Diena", "allowedViewWeekLabel" : "Savaitė", "allowedViewWorkWeekLabel" : "Darbo savaitė", @@ -42,7 +51,7 @@ "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "aha.", +"shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. aš", "shortRabi2Label" : "Rabi. II", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Diena", -"series" : "Serija" +"series" : "Serija", +"pdfHyperlinkLabel" : "Atidarykite tinklalapį", +"pdfHyperlinkContentLabel" : "Ar norite atidaryti puslapį adresu", +"pdfHyperlinkDialogOpenLabel" : "ATVIRAS", +"pdfHyperlinkDialogCancelLabel" : "ATŠAUKTI", +"afterDataGridFilteringLabel" : "Po to", +"afterOrEqualDataGridFilteringLabel" : "Po arba lygus", +"beforeDataGridFilteringLabel" : "Prieš", +"beforeOrEqualDataGridFilteringLabel" : "Prieš arba lygus", +"beginsWithDataGridFilteringLabel" : "Prasideda su", +"containsDataGridFilteringLabel" : "Sudėtyje yra", +"doesNotBeginWithDataGridFilteringLabel" : "Neprasideda su", +"doesNotContainDataGridFilteringLabel" : "Sudėtyje nėra", +"doesNotEndWithDataGridFilteringLabel" : "Nesibaigia", +"doesNotEqualDataGridFilteringLabel" : "Nelygu", +"emptyDataGridFilteringLabel" : "Tuščia", +"endsWithDataGridFilteringLabel" : "Baigiasi su", +"equalsDataGridFilteringLabel" : "Lygu", +"greaterThanDataGridFilteringLabel" : "Geresnis negu", +"greaterThanOrEqualDataGridFilteringLabel" : "Didesnis nei lygus", +"lessThanDataGridFilteringLabel" : "Mažiau nei", +"lessThanOrEqualDataGridFilteringLabel" : "Mažiau nei arba lygus", +"notEmptyDataGridFilteringLabel" : "Ne Tuščias", +"notNullDataGridFilteringLabel" : "Ne Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Rūšiuoti nuo mažiausio iki didžiausio", +"sortLargestToSmallestDataGridFilteringLabel" : "Rūšiuoti nuo didžiausio iki mažiausio", +"sortAToZDataGridFilteringLabel" : "Rūšiuoti nuo A iki Z", +"sortZToADataGridFilteringLabel" : "Rūšiuoti nuo Z iki A", +"sortOldestToNewestDataGridFilteringLabel" : "Rūšiuoti nuo seniausių iki naujausių", +"sortNewestToOldestDataGridFilteringLabel" : "Rūšiuoti nuo naujausių iki seniausių", +"textFiltersDataGridFilteringLabel" : "Teksto filtrai", +"numberFiltersDataGridFilteringLabel" : "Skaičių filtrai", +"dateFiltersDataGridFilteringLabel" : "Datos filtrai", +"searchDataGridFilteringLabel" : "Paieška", +"noMatchesDataGridFilteringLabel" : "Jokių atitikmenų", +"okDataGridFilteringLabel" : "Gerai", +"cancelDataGridFilteringLabel" : "Atšaukti", +"showRowsWhereDataGridFilteringLabel" : "Rodyti eilutes kur", +"andDataGridFilteringLabel" : "Ir", +"orDataGridFilteringLabel" : "Arba", +"selectAllDataGridFilteringLabel" : "Pasirinkti viską", +"sortAndFilterDataGridFilteringLabel" : "Rūšiuoti ir filtruoti", +"clearFilterDataGridFilteringLabel" : "Išvalyti filtrą", +"fromDataGridFilteringLabel" : "Iš" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb index b3f8a24cc..b5314a319 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Nepareiza parole", "pdfPasswordDialogOpenLabel" : "ATVĒRTS", "pdfPasswordDialogCancelLabel" : "ATCELT", +"pdfSignaturePadDialogHeaderTextLabel" : "Uzzīmējiet savu parakstu", +"pdfSignaturePadDialogPenColorLabel" : "Pildspalvas krāsa", +"pdfSignaturePadDialogClearLabel" : "DZĪRS", +"pdfSignaturePadDialogSaveLabel" : "SAGLABĀT", +"pdfTextSelectionMenuCopyLabel" : "Kopēt", +"pdfTextSelectionMenuHighlightLabel" : "Izcelt", +"pdfTextSelectionMenuStrikethroughLabel" : "Pārsvītrots", +"pdfTextSelectionMenuUnderlineLabel" : "Pasvītrot", +"pdfTextSelectionMenuSquigglyLabel" : "Svītrains", "allowedViewDayLabel" : "diena", "allowedViewWeekLabel" : "nedēļa", "allowedViewWorkWeekLabel" : "Darba nedēļa", @@ -41,7 +50,7 @@ "ramadanLabel" : "Ramadāns", "shawwalLabel" : "Šovals", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualhiLabel" : "Dhu al-Hidžja", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. es", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "diena", -"series" : "sērija" +"series" : "sērija", +"pdfHyperlinkLabel" : "Atveriet tīmekļa lapu", +"pdfHyperlinkContentLabel" : "Vai vēlaties atvērt lapu vietnē", +"pdfHyperlinkDialogOpenLabel" : "ATVĒRTS", +"pdfHyperlinkDialogCancelLabel" : "ATCELT", +"afterDataGridFilteringLabel" : "Pēc", +"afterOrEqualDataGridFilteringLabel" : "Pēc vai vienāds", +"beforeDataGridFilteringLabel" : "Pirms", +"beforeOrEqualDataGridFilteringLabel" : "Pirms vai vienāds", +"beginsWithDataGridFilteringLabel" : "Sākas ar", +"containsDataGridFilteringLabel" : "Satur", +"doesNotBeginWithDataGridFilteringLabel" : "Nesākas ar", +"doesNotContainDataGridFilteringLabel" : "Nesatur", +"doesNotEndWithDataGridFilteringLabel" : "Nebeidzas ar", +"doesNotEqualDataGridFilteringLabel" : "Nav vienāds", +"emptyDataGridFilteringLabel" : "Tukšs", +"endsWithDataGridFilteringLabel" : "Beidzas ar", +"equalsDataGridFilteringLabel" : "Vienāds", +"greaterThanDataGridFilteringLabel" : "Lielāks par", +"greaterThanOrEqualDataGridFilteringLabel" : "Lielāks par vai vienāds", +"lessThanDataGridFilteringLabel" : "Mazāk nekā", +"lessThanOrEqualDataGridFilteringLabel" : "Mazāk nekā vai vienāds", +"notEmptyDataGridFilteringLabel" : "Nav tukšs", +"notNullDataGridFilteringLabel" : "Nav Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Kārtot no mazākā uz lielāko", +"sortLargestToSmallestDataGridFilteringLabel" : "Kārtot no lielākā uz mazāko", +"sortAToZDataGridFilteringLabel" : "Kārtot no A līdz Z", +"sortZToADataGridFilteringLabel" : "Kārtot no Z līdz A", +"sortOldestToNewestDataGridFilteringLabel" : "Kārtot no vecāko uz jaunāko", +"sortNewestToOldestDataGridFilteringLabel" : "Kārtot no jaunākajiem uz vecākajiem", +"textFiltersDataGridFilteringLabel" : "Teksta filtri", +"numberFiltersDataGridFilteringLabel" : "Skaitļu filtri", +"dateFiltersDataGridFilteringLabel" : "Datumu filtri", +"searchDataGridFilteringLabel" : "Meklēt", +"noMatchesDataGridFilteringLabel" : "nav atbilstību", +"okDataGridFilteringLabel" : "labi", +"cancelDataGridFilteringLabel" : "Atcelt", +"showRowsWhereDataGridFilteringLabel" : "Rādīt rindas, kur", +"andDataGridFilteringLabel" : "Un", +"orDataGridFilteringLabel" : "Or", +"selectAllDataGridFilteringLabel" : "Izvēlēties visus", +"sortAndFilterDataGridFilteringLabel" : "Kārtot un filtrēt", +"clearFilterDataGridFilteringLabel" : "Notīrīt filtru", +"fromDataGridFilteringLabel" : "No" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb index f613c71d2..cf9c4833e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb @@ -9,15 +9,24 @@ "pdfScrollStatusOfLabel" : "на", "pdfGoToPageLabel" : "Оди на страна", "pdfEnterPageNumberLabel" : "Внесете го бројот на страницата", -"pdfInvalidPageNumberLabel" : "Ве молиме внесете важечки број", -"pdfPaginationDialogOkLabel" : "добро", -"pdfPaginationDialogCancelLabel" : "ОТКАЖИ", +"pdfInvalidPageNumberLabel" : "Внесете важечки број", +"pdfPaginationDialogOkLabel" : "Добро", +"pdfPaginationDialogCancelLabel" : "Откажи", "passwordDialogHeaderTextLabel" : "Заштитено со лозинка", "passwordDialogContentLabel" : "Внесете ја лозинката за да ја отворите оваа PDF-датотека", "passwordDialogHintTextLabel" : "Внесете ја лозинката", -"passwordDialogInvalidPasswordLabel" : "невалидна лозинка", -"pdfPasswordDialogOpenLabel" : "ОТВОРЕНО", -"pdfPasswordDialogCancelLabel" : "ОТКАЖИ", +"passwordDialogInvalidPasswordLabel" : "Невалидна лозинка", +"pdfPasswordDialogOpenLabel" : "Отвори", +"pdfPasswordDialogCancelLabel" : "Откажи", +"pdfSignaturePadDialogHeaderTextLabel" : "Нацртајте го вашиот потпис", +"pdfSignaturePadDialogPenColorLabel" : "Боја на пенкало", +"pdfSignaturePadDialogClearLabel" : "Исчисти", +"pdfSignaturePadDialogSaveLabel" : "Зачувај", +"pdfTextSelectionMenuCopyLabel" : "Копирај", +"pdfTextSelectionMenuHighlightLabel" : "Истакни", +"pdfTextSelectionMenuStrikethroughLabel" : "Прецртај", +"pdfTextSelectionMenuUnderlineLabel" : "Подвлечи", +"pdfTextSelectionMenuSquigglyLabel" : "Брановито", "allowedViewDayLabel" : "Ден", "allowedViewWeekLabel" : "Недела", "allowedViewWorkWeekLabel" : "Работна недела", @@ -26,8 +35,8 @@ "allowedViewTimelineDayLabel" : "Ден на времеплов", "allowedViewTimelineWeekLabel" : "Недела на времеплов", "allowedViewTimelineWorkWeekLabel" : "Временска рамка Работна недела", -"allowedViewTimelineMonthLabel" : "Времеплов месец", -"todayLabel" : "Денеска", +"allowedViewTimelineMonthLabel" : "Месец на времеплов", +"todayLabel" : "Денес", "weeknumberLabel" : "Недела", "allDayLabel" : "Цел ден", "muharramLabel" : "Мухарам", @@ -41,7 +50,7 @@ "ramadanLabel" : "Рамазан", "shawwalLabel" : "Шавал", "dhualqiLabel" : "Зу ал-Кида", -"dhualhiLabel" : "Зу ал-Хиџа", +"dhualhiLabel" : "Зу ал Хиџа", "shortMuharramLabel" : "Мух.", "shortSafarLabel" : "Саф.", "shortRabi1Label" : "Раби. Јас", @@ -50,10 +59,54 @@ "shortJumada2Label" : "Џум. II", "shortRajabLabel" : "Раџ.", "shortShaabanLabel" : "Ша.", -"shortRamadanLabel" : "Рам.", +"shortRamadanLabel" : "Овен.", "shortShawwalLabel" : "Шо.", -"shortDhualqiLabel" : "Дул-К", +"shortDhualqiLabel" : "Ду'л-К", "shortDhualhiLabel" : "Дул-Х", "daySpanCountLabel" : "Ден", -"series" : "Серии" +"series" : "Серии", +"pdfHyperlinkLabel" : "Отворете ја веб-страницата", +"pdfHyperlinkContentLabel" : "Дали сакате да ја отворите страницата на", +"pdfHyperlinkDialogOpenLabel" : "ОТВОРЕНО", +"pdfHyperlinkDialogCancelLabel" : "ОТКАЖИ", +"afterDataGridFilteringLabel" : "По", +"afterOrEqualDataGridFilteringLabel" : "По Или еднакви", +"beforeDataGridFilteringLabel" : "Пред", +"beforeOrEqualDataGridFilteringLabel" : "Пред или еднакви", +"beginsWithDataGridFilteringLabel" : "Започнува со", +"containsDataGridFilteringLabel" : "Содржи", +"doesNotBeginWithDataGridFilteringLabel" : "Не започнува со", +"doesNotContainDataGridFilteringLabel" : "Не содржи", +"doesNotEndWithDataGridFilteringLabel" : "Не завршува со", +"doesNotEqualDataGridFilteringLabel" : "Не е еднакво", +"emptyDataGridFilteringLabel" : "Празен", +"endsWithDataGridFilteringLabel" : "Завршува со", +"equalsDataGridFilteringLabel" : "Еднакви", +"greaterThanDataGridFilteringLabel" : "Поголема од", +"greaterThanOrEqualDataGridFilteringLabel" : "Поголема од Или еднаква", +"lessThanDataGridFilteringLabel" : "Помалку од", +"lessThanOrEqualDataGridFilteringLabel" : "Помалку од или еднакви", +"notEmptyDataGridFilteringLabel" : "Не е празен", +"notNullDataGridFilteringLabel" : "Не Нулта", +"nullDataGridFilteringLabel" : "Нула", +"sortSmallestToLargestDataGridFilteringLabel" : "Подреди од најмал до најголем", +"sortLargestToSmallestDataGridFilteringLabel" : "Подреди од најголем до најмал", +"sortAToZDataGridFilteringLabel" : "Подреди од А до Ш", +"sortZToADataGridFilteringLabel" : "Подреди од Ш до А", +"sortOldestToNewestDataGridFilteringLabel" : "Подреди од најстарото до најновото", +"sortNewestToOldestDataGridFilteringLabel" : "Подреди најновото до најстарото", +"textFiltersDataGridFilteringLabel" : "Филтри за текст", +"numberFiltersDataGridFilteringLabel" : "Филтри за броеви", +"dateFiltersDataGridFilteringLabel" : "Филтри за датум", +"searchDataGridFilteringLabel" : "Пребарување", +"noMatchesDataGridFilteringLabel" : "Нема совпаѓања", +"okDataGridFilteringLabel" : "добро", +"cancelDataGridFilteringLabel" : "Откажи", +"showRowsWhereDataGridFilteringLabel" : "Прикажи ги редовите каде", +"andDataGridFilteringLabel" : "И", +"orDataGridFilteringLabel" : "Или", +"selectAllDataGridFilteringLabel" : "Селектирај се", +"sortAndFilterDataGridFilteringLabel" : "Сортирање и филтрирање", +"clearFilterDataGridFilteringLabel" : "Исчистете го филтерот", +"fromDataGridFilteringLabel" : "Од" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb index 0dda8a0b4..12bdffd34 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "അസാധുവായ പാസ്‌വേഡ്", "pdfPasswordDialogOpenLabel" : "തുറക്കുക", "pdfPasswordDialogCancelLabel" : "റദ്ദാക്കുക", +"pdfSignaturePadDialogHeaderTextLabel" : "നിങ്ങളുടെ ഒപ്പ് വരയ്ക്കുക", +"pdfSignaturePadDialogPenColorLabel" : "പേന നിറം", +"pdfSignaturePadDialogClearLabel" : "ക്ലിയർ", +"pdfSignaturePadDialogSaveLabel" : "രക്ഷിക്കും", +"pdfTextSelectionMenuCopyLabel" : "പകർത്തുക", +"pdfTextSelectionMenuHighlightLabel" : "ഹൈലൈറ്റ് ചെയ്യുക", +"pdfTextSelectionMenuStrikethroughLabel" : "സ്ട്രൈക്ക്ത്രൂ", +"pdfTextSelectionMenuUnderlineLabel" : "അടിവരയിടുക", +"pdfTextSelectionMenuSquigglyLabel" : "സ്ക്വിഗ്ലി", "allowedViewDayLabel" : "ദിവസം", "allowedViewWeekLabel" : "ആഴ്ച", "allowedViewWorkWeekLabel" : "പ്രവൃത്തി ആഴ്ച", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ദുൽ-ക്യു", "shortDhualhiLabel" : "ദുൽ-എച്ച്", "daySpanCountLabel" : "ദിവസം", -"series" : "പരമ്പര" +"series" : "പരമ്പര", +"pdfHyperlinkLabel" : "വെബ് പേജ് തുറക്കുക", +"pdfHyperlinkContentLabel" : "എന്നതിൽ പേജ് തുറക്കണോ?", +"pdfHyperlinkDialogOpenLabel" : "തുറക്കുക", +"pdfHyperlinkDialogCancelLabel" : "റദ്ദാക്കുക", +"afterDataGridFilteringLabel" : "ശേഷം", +"afterOrEqualDataGridFilteringLabel" : "ശേഷം അല്ലെങ്കിൽ തുല്യം", +"beforeDataGridFilteringLabel" : "മുമ്പ്", +"beforeOrEqualDataGridFilteringLabel" : "മുമ്പോ തുല്യമോ", +"beginsWithDataGridFilteringLabel" : "കൂടെ തുടങ്ങുന്നു", +"containsDataGridFilteringLabel" : "അടങ്ങിയിരിക്കുന്നു", +"doesNotBeginWithDataGridFilteringLabel" : "കൂടെ തുടങ്ങുന്നില്ല", +"doesNotContainDataGridFilteringLabel" : "അടങ്ങിയിട്ടില്ല", +"doesNotEndWithDataGridFilteringLabel" : "കൊണ്ട് അവസാനിക്കുന്നില്ല", +"doesNotEqualDataGridFilteringLabel" : "തുല്യമല്ല", +"emptyDataGridFilteringLabel" : "ശൂന്യം", +"endsWithDataGridFilteringLabel" : "കൂടെ അവസാനിക്കുന്നു", +"equalsDataGridFilteringLabel" : "തുല്യമാണ്", +"greaterThanDataGridFilteringLabel" : "അതിലും വലുത്", +"greaterThanOrEqualDataGridFilteringLabel" : "വലുത് അല്ലെങ്കിൽ തുല്യം", +"lessThanDataGridFilteringLabel" : "അതിൽ കുറവ്", +"lessThanOrEqualDataGridFilteringLabel" : "കുറവ് അല്ലെങ്കിൽ തുല്യം", +"notEmptyDataGridFilteringLabel" : "ശൂന്യമല്ല", +"notNullDataGridFilteringLabel" : "നൾ അല്ല", +"nullDataGridFilteringLabel" : "ശൂന്യം", +"sortSmallestToLargestDataGridFilteringLabel" : "ചെറുത് മുതൽ വലുത് വരെ അടുക്കുക", +"sortLargestToSmallestDataGridFilteringLabel" : "ഏറ്റവും വലുത് മുതൽ ചെറുത് വരെ അടുക്കുക", +"sortAToZDataGridFilteringLabel" : "A മുതൽ Z വരെ അടുക്കുക", +"sortZToADataGridFilteringLabel" : "Z മുതൽ A വരെ അടുക്കുക", +"sortOldestToNewestDataGridFilteringLabel" : "ഏറ്റവും പഴയത് മുതൽ പുതിയത് വരെ അടുക്കുക", +"sortNewestToOldestDataGridFilteringLabel" : "ഏറ്റവും പുതിയത് മുതൽ പഴയത് വരെ അടുക്കുക", +"textFiltersDataGridFilteringLabel" : "ടെക്സ്റ്റ് ഫിൽട്ടറുകൾ", +"numberFiltersDataGridFilteringLabel" : "നമ്പർ ഫിൽട്ടറുകൾ", +"dateFiltersDataGridFilteringLabel" : "തീയതി ഫിൽട്ടറുകൾ", +"searchDataGridFilteringLabel" : "തിരയുക", +"noMatchesDataGridFilteringLabel" : "പൊരുത്തങ്ങളൊന്നുമില്ല", +"okDataGridFilteringLabel" : "ശരി", +"cancelDataGridFilteringLabel" : "റദ്ദാക്കുക", +"showRowsWhereDataGridFilteringLabel" : "എവിടെ വരികൾ കാണിക്കുക", +"andDataGridFilteringLabel" : "ഒപ്പം", +"orDataGridFilteringLabel" : "അഥവാ", +"selectAllDataGridFilteringLabel" : "എല്ലാം തിരഞ്ഞെടുക്കുക", +"sortAndFilterDataGridFilteringLabel" : "അടുക്കി ഫിൽട്ടർ ചെയ്യുക", +"clearFilterDataGridFilteringLabel" : "ഫിൽട്ടർ മായ്‌ക്കുക", +"fromDataGridFilteringLabel" : "നിന്ന്" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb index 6c52e609e..e1c17ebca 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb @@ -1,7 +1,7 @@ { "noSelectedDateCalendarLabel" : "Сонгосон огноо алга", "noEventsCalendarLabel" : "Үйл явдал алга", -"ofDataPagerLabel" : "-ийн", +"ofDataPagerLabel" : "ийн", "pagesDataPagerLabel" : "хуудаснууд", "rowsPerPageDataPagerLabel" : "Хуудас бүрийн мөр", "pdfBookmarksLabel" : "Хавчуурга", @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Хуудас руу оч", "pdfEnterPageNumberLabel" : "Хуудасны дугаарыг оруулна уу", "pdfInvalidPageNumberLabel" : "Хүчинтэй дугаар оруулна уу", -"pdfPaginationDialogOkLabel" : "БОЛЖ БАЙНА УУ", +"pdfPaginationDialogOkLabel" : "Зөвшөөрөх", "pdfPaginationDialogCancelLabel" : "Цуцлах", "passwordDialogHeaderTextLabel" : "Нууц үгээр хамгаалагдсан", "passwordDialogContentLabel" : "Энэ PDF файлыг нээхийн тулд нууц үгээ оруулна уу", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Нууц үг буруу байна", "pdfPasswordDialogOpenLabel" : "НЭЭЛТТЭЙ", "pdfPasswordDialogCancelLabel" : "Цуцлах", +"pdfSignaturePadDialogHeaderTextLabel" : "Гарын үсгээ зур", +"pdfSignaturePadDialogPenColorLabel" : "Үзэгний өнгө", +"pdfSignaturePadDialogClearLabel" : "ЦЭВЭРЛЭХ", +"pdfSignaturePadDialogSaveLabel" : "ХАДГАЛАХ", +"pdfTextSelectionMenuCopyLabel" : "Хуулах", +"pdfTextSelectionMenuHighlightLabel" : "Онцлох", +"pdfTextSelectionMenuStrikethroughLabel" : "Зураас", +"pdfTextSelectionMenuUnderlineLabel" : "Доогуур зур", +"pdfTextSelectionMenuSquigglyLabel" : "Муухай", "allowedViewDayLabel" : "Өдөр", "allowedViewWeekLabel" : "Долоо хоног", "allowedViewWorkWeekLabel" : "Ажлын долоо хоног", @@ -41,7 +50,7 @@ "ramadanLabel" : "Рамадан", "shawwalLabel" : "Шаввал", "dhualqiLabel" : "Зу аль-Кида", -"dhualhiLabel" : "Зу аль-Хижа", +"dhualhiLabel" : "Зул Хижжа", "shortMuharramLabel" : "Мух.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Раби. I", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Зуль-К", "shortDhualhiLabel" : "Зуль-Х", "daySpanCountLabel" : "Өдөр", -"series" : "Цуврал" +"series" : "Цуврал", +"pdfHyperlinkLabel" : "Вэб хуудсыг нээх", +"pdfHyperlinkContentLabel" : "хаягаар хуудсыг нээхийг хүсэж байна уу", +"pdfHyperlinkDialogOpenLabel" : "НЭЭЛТТЭЙ", +"pdfHyperlinkDialogCancelLabel" : "Цуцлах", +"afterDataGridFilteringLabel" : "Дараа", +"afterOrEqualDataGridFilteringLabel" : "Дараа эсвэл тэнцүү", +"beforeDataGridFilteringLabel" : "Өмнө нь", +"beforeOrEqualDataGridFilteringLabel" : "Өмнө эсвэл Тэнцүү", +"beginsWithDataGridFilteringLabel" : "-аар эхэлдэг", +"containsDataGridFilteringLabel" : "агуулсан", +"doesNotBeginWithDataGridFilteringLabel" : "-аас эхэлдэггүй", +"doesNotContainDataGridFilteringLabel" : "Агуулаагүй", +"doesNotEndWithDataGridFilteringLabel" : "Үүгээр дуусахгүй", +"doesNotEqualDataGridFilteringLabel" : "Тэнцэхгүй", +"emptyDataGridFilteringLabel" : "Хоосон", +"endsWithDataGridFilteringLabel" : "-ээр төгсдөг", +"equalsDataGridFilteringLabel" : "Тэнцүү", +"greaterThanDataGridFilteringLabel" : "Илүү их", +"greaterThanOrEqualDataGridFilteringLabel" : "Их буюу тэнцүү", +"lessThanDataGridFilteringLabel" : "Ээс бага", +"lessThanOrEqualDataGridFilteringLabel" : "-ээс бага эсвэл тэнцүү", +"notEmptyDataGridFilteringLabel" : "Хоосон биш", +"notNullDataGridFilteringLabel" : "Null биш", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Хамгийн жижигээс томд эрэмбэлэх", +"sortLargestToSmallestDataGridFilteringLabel" : "Томоос жижиг рүү ангилах", +"sortAToZDataGridFilteringLabel" : "А-аас Я хүртэл эрэмбэлэх", +"sortZToADataGridFilteringLabel" : "Z-ээс А хүртэл эрэмбэлэх", +"sortOldestToNewestDataGridFilteringLabel" : "Хамгийн хуучинаас шинэ рүү эрэмбэлэх", +"sortNewestToOldestDataGridFilteringLabel" : "Хамгийн шинэээс хамгийн хуучин руу эрэмбэлэх", +"textFiltersDataGridFilteringLabel" : "Текст шүүлтүүрүүд", +"numberFiltersDataGridFilteringLabel" : "Тооны шүүлтүүрүүд", +"dateFiltersDataGridFilteringLabel" : "Огноо шүүлтүүрүүд", +"searchDataGridFilteringLabel" : "Хайх", +"noMatchesDataGridFilteringLabel" : "Тохирох зүйл алга", +"okDataGridFilteringLabel" : "БОЛЖ БАЙНА УУ", +"cancelDataGridFilteringLabel" : "Цуцлах", +"showRowsWhereDataGridFilteringLabel" : "Хаана мөрүүдийг харуул", +"andDataGridFilteringLabel" : "Тэгээд", +"orDataGridFilteringLabel" : "Эсвэл", +"selectAllDataGridFilteringLabel" : "Бүгдийг сонгох", +"sortAndFilterDataGridFilteringLabel" : "Ангилах, шүүх", +"clearFilterDataGridFilteringLabel" : "Шүүлтүүрийг арилгах", +"fromDataGridFilteringLabel" : "-аас" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb index 15588fdcd..81637210d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb @@ -9,8 +9,8 @@ "pdfScrollStatusOfLabel" : "च्या", "pdfGoToPageLabel" : "पृष्ठावर जा", "pdfEnterPageNumberLabel" : "पृष्ठ क्रमांक प्रविष्ट करा", -"pdfInvalidPageNumberLabel" : "कृपया वैध क्रमांक प्रविष्ट करा", -"pdfPaginationDialogOkLabel" : "ठीक आहे", +"pdfInvalidPageNumberLabel" : "कृपया एक वैध क्रमांक प्रविष्ट करा", +"pdfPaginationDialogOkLabel" : "ठीक", "pdfPaginationDialogCancelLabel" : "रद्द करा", "passwordDialogHeaderTextLabel" : "पासवर्ड संरक्षित", "passwordDialogContentLabel" : "ही PDF फाईल उघडण्यासाठी पासवर्ड टाका", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "अवैध पासवर्ड", "pdfPasswordDialogOpenLabel" : "उघडा", "pdfPasswordDialogCancelLabel" : "रद्द करा", +"pdfSignaturePadDialogHeaderTextLabel" : "तुमची स्वाक्षरी काढा", +"pdfSignaturePadDialogPenColorLabel" : "पेन रंग", +"pdfSignaturePadDialogClearLabel" : "साफ करा", +"pdfSignaturePadDialogSaveLabel" : "जतन करा", +"pdfTextSelectionMenuCopyLabel" : "कॉपी करा", +"pdfTextSelectionMenuHighlightLabel" : "हायलाइट करा", +"pdfTextSelectionMenuStrikethroughLabel" : "स्ट्राइकथ्रू", +"pdfTextSelectionMenuUnderlineLabel" : "अधोरेखित करा", +"pdfTextSelectionMenuSquigglyLabel" : "स्क्विग्ली", "allowedViewDayLabel" : "दिवस", "allowedViewWeekLabel" : "आठवडा", "allowedViewWorkWeekLabel" : "कामाचा आठवडा", @@ -33,8 +42,8 @@ "muharramLabel" : "मोहरम", "safarLabel" : "सफर", "rabi1Label" : "रबी अल अव्वल", -"rabi2Label" : "रबी अल-थानी", -"jumada1Label" : "जुमदा अल-अव्वल", +"rabi2Label" : "रबी अल थानी", +"jumada1Label" : "जुमदा अल-अव्वाल", "jumada2Label" : "जुमादा अल-थानी", "rajabLabel" : "रजब", "shaabanLabel" : "शाबान", @@ -46,8 +55,8 @@ "shortSafarLabel" : "सफ.", "shortRabi1Label" : "रबी. आय", "shortRabi2Label" : "रबी. II", -"shortJumada1Label" : "जम. आय", -"shortJumada2Label" : "जम. II", +"shortJumada1Label" : "जुम. आय", +"shortJumada2Label" : "जुम. II", "shortRajabLabel" : "राज.", "shortShaabanLabel" : "शा.", "shortRamadanLabel" : "रॅम.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "धूल-प्र", "shortDhualhiLabel" : "धुल-एच", "daySpanCountLabel" : "दिवस", -"series" : "मालिका" +"series" : "मालिका", +"pdfHyperlinkLabel" : "वेब पृष्ठ उघडा", +"pdfHyperlinkContentLabel" : "तुम्हाला येथे पृष्ठ उघडायचे आहे का", +"pdfHyperlinkDialogOpenLabel" : "उघडा", +"pdfHyperlinkDialogCancelLabel" : "रद्द करा", +"afterDataGridFilteringLabel" : "नंतर", +"afterOrEqualDataGridFilteringLabel" : "नंतर किंवा समान", +"beforeDataGridFilteringLabel" : "आधी", +"beforeOrEqualDataGridFilteringLabel" : "आधी किंवा समान", +"beginsWithDataGridFilteringLabel" : "ने सुरुवात होते", +"containsDataGridFilteringLabel" : "समाविष्ट आहे", +"doesNotBeginWithDataGridFilteringLabel" : "ने सुरुवात होत नाही", +"doesNotContainDataGridFilteringLabel" : "समाविष्ट नाही", +"doesNotEndWithDataGridFilteringLabel" : "सह संपत नाही", +"doesNotEqualDataGridFilteringLabel" : "समान नाही", +"emptyDataGridFilteringLabel" : "रिकामे", +"endsWithDataGridFilteringLabel" : "यासह समाप्त होते", +"equalsDataGridFilteringLabel" : "बरोबरी", +"greaterThanDataGridFilteringLabel" : "या पेक्षा मोठे", +"greaterThanOrEqualDataGridFilteringLabel" : "पेक्षा मोठे किंवा समान", +"lessThanDataGridFilteringLabel" : "च्या पेक्षा कमी", +"lessThanOrEqualDataGridFilteringLabel" : "पेक्षा कमी किंवा समान", +"notEmptyDataGridFilteringLabel" : "रिकामे नाही", +"notNullDataGridFilteringLabel" : "शून्य नाही", +"nullDataGridFilteringLabel" : "निरर्थक", +"sortSmallestToLargestDataGridFilteringLabel" : "सर्वात लहान ते सर्वात मोठ्या क्रमवारी लावा", +"sortLargestToSmallestDataGridFilteringLabel" : "सर्वात मोठ्या ते सर्वात लहान क्रमवारी लावा", +"sortAToZDataGridFilteringLabel" : "A ते Z क्रमवारी लावा", +"sortZToADataGridFilteringLabel" : "Z ते A क्रमवारी लावा", +"sortOldestToNewestDataGridFilteringLabel" : "सर्वात जुनी ते नवीनतम क्रमवारी लावा", +"sortNewestToOldestDataGridFilteringLabel" : "सर्वात नवीन ते सर्वात जुने क्रमवारी लावा", +"textFiltersDataGridFilteringLabel" : "मजकूर फिल्टर", +"numberFiltersDataGridFilteringLabel" : "संख्या फिल्टर", +"dateFiltersDataGridFilteringLabel" : "तारीख फिल्टर", +"searchDataGridFilteringLabel" : "शोधा", +"noMatchesDataGridFilteringLabel" : "कोणतेही सामने नाहीत", +"okDataGridFilteringLabel" : "ठीक आहे", +"cancelDataGridFilteringLabel" : "रद्द करा", +"showRowsWhereDataGridFilteringLabel" : "कुठे पंक्ती दाखवा", +"andDataGridFilteringLabel" : "आणि", +"orDataGridFilteringLabel" : "किंवा", +"selectAllDataGridFilteringLabel" : "सर्व निवडा", +"sortAndFilterDataGridFilteringLabel" : "क्रमवारी लावा आणि फिल्टर करा", +"clearFilterDataGridFilteringLabel" : "फिल्टर साफ करा", +"fromDataGridFilteringLabel" : "पासून" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb index 575e02d08..f122112b3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb @@ -10,18 +10,27 @@ "pdfGoToPageLabel" : "Pergi ke halaman", "pdfEnterPageNumberLabel" : "Masukkan nombor halaman", "pdfInvalidPageNumberLabel" : "sila masukkan nombor yang sah", -"pdfPaginationDialogOkLabel" : "okey", -"pdfPaginationDialogCancelLabel" : "BATALKAN", +"pdfPaginationDialogOkLabel" : "OK", +"pdfPaginationDialogCancelLabel" : "Batal", "passwordDialogHeaderTextLabel" : "Kata laluan dilindungi", "passwordDialogContentLabel" : "Masukkan kata laluan untuk membuka fail PDF ini", "passwordDialogHintTextLabel" : "Masukkan kata laluan", "passwordDialogInvalidPasswordLabel" : "kata laluan tidak sah", -"pdfPasswordDialogOpenLabel" : "BUKA", -"pdfPasswordDialogCancelLabel" : "BATALKAN", +"pdfPasswordDialogOpenLabel" : "Buka", +"pdfPasswordDialogCancelLabel" : "Batal", +"pdfSignaturePadDialogHeaderTextLabel" : "Lukis tandatangan anda", +"pdfSignaturePadDialogPenColorLabel" : "Warna Pen", +"pdfSignaturePadDialogClearLabel" : "Kosongkan", +"pdfSignaturePadDialogSaveLabel" : "Simpan", +"pdfTextSelectionMenuCopyLabel" : "Salin", +"pdfTextSelectionMenuHighlightLabel" : "Serlahkan", +"pdfTextSelectionMenuStrikethroughLabel" : "Garis silang", +"pdfTextSelectionMenuUnderlineLabel" : "Garis bawah", +"pdfTextSelectionMenuSquigglyLabel" : "Garis berlekuk", "allowedViewDayLabel" : "Hari", "allowedViewWeekLabel" : "Minggu", "allowedViewWorkWeekLabel" : "Minggu Kerja", -"allowedViewMonthLabel" : "bulan", +"allowedViewMonthLabel" : "Bulan", "allowedViewScheduleLabel" : "Jadual", "allowedViewTimelineDayLabel" : "Hari Garis Masa", "allowedViewTimelineWeekLabel" : "Minggu Garis Masa", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dzul-Q", "shortDhualhiLabel" : "Dzul-H", "daySpanCountLabel" : "Hari", -"series" : "Siri" +"series" : "Siri", +"pdfHyperlinkLabel" : "Buka Halaman Web", +"pdfHyperlinkContentLabel" : "Adakah anda ingin membuka halaman di", +"pdfHyperlinkDialogOpenLabel" : "Buka", +"pdfHyperlinkDialogCancelLabel" : "Batal", +"afterDataGridFilteringLabel" : "Selepas", +"afterOrEqualDataGridFilteringLabel" : "Selepas Atau Sama", +"beforeDataGridFilteringLabel" : "Sebelum", +"beforeOrEqualDataGridFilteringLabel" : "Sebelum Atau Sama", +"beginsWithDataGridFilteringLabel" : "Bermula dengan", +"containsDataGridFilteringLabel" : "Mengandungi", +"doesNotBeginWithDataGridFilteringLabel" : "Tidak Bermula Dengan", +"doesNotContainDataGridFilteringLabel" : "Tidak mengandungi", +"doesNotEndWithDataGridFilteringLabel" : "Tidak Berakhir Dengan", +"doesNotEqualDataGridFilteringLabel" : "Tidak Sama", +"emptyDataGridFilteringLabel" : "kosong", +"endsWithDataGridFilteringLabel" : "Berakhir Dengan", +"equalsDataGridFilteringLabel" : "sama", +"greaterThanDataGridFilteringLabel" : "Lebih besar daripada", +"greaterThanOrEqualDataGridFilteringLabel" : "Lebih Besar Daripada Atau Sama", +"lessThanDataGridFilteringLabel" : "Kurang daripada", +"lessThanOrEqualDataGridFilteringLabel" : "Kurang Daripada Atau Sama", +"notEmptyDataGridFilteringLabel" : "Tidak kosong", +"notNullDataGridFilteringLabel" : "Bukan Null", +"nullDataGridFilteringLabel" : "batal", +"sortSmallestToLargestDataGridFilteringLabel" : "Susun Terkecil Kepada Terbesar", +"sortLargestToSmallestDataGridFilteringLabel" : "Susun Terbesar Kepada Terkecil", +"sortAToZDataGridFilteringLabel" : "Susun A Hingga Z", +"sortZToADataGridFilteringLabel" : "Isih Z Hingga A", +"sortOldestToNewestDataGridFilteringLabel" : "Isih Terlama Kepada Terbaharu", +"sortNewestToOldestDataGridFilteringLabel" : "Susun Terbaharu Hingga Terlama", +"textFiltersDataGridFilteringLabel" : "Penapis Teks", +"numberFiltersDataGridFilteringLabel" : "Penapis Nombor", +"dateFiltersDataGridFilteringLabel" : "Penapis Tarikh", +"searchDataGridFilteringLabel" : "Cari", +"noMatchesDataGridFilteringLabel" : "Tiada perlawanan", +"okDataGridFilteringLabel" : "okey", +"cancelDataGridFilteringLabel" : "Batal", +"showRowsWhereDataGridFilteringLabel" : "Tunjukkan baris di mana", +"andDataGridFilteringLabel" : "Dan", +"orDataGridFilteringLabel" : "Ataupun", +"selectAllDataGridFilteringLabel" : "Pilih semua", +"sortAndFilterDataGridFilteringLabel" : "Isih dan Tapis", +"clearFilterDataGridFilteringLabel" : "Kosongkan Penapis", +"fromDataGridFilteringLabel" : "daripada" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb index 832d30ff1..03b1e634c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb @@ -10,23 +10,32 @@ "pdfGoToPageLabel" : "စာမျက်နှာသို့သွားပါ", "pdfEnterPageNumberLabel" : "စာမျက်နှာနံပါတ်ထည့်ပါ။", "pdfInvalidPageNumberLabel" : "ကျေးဇူးပြု၍ တရားဝင်နံပါတ်တစ်ခုထည့်ပါ။", -"pdfPaginationDialogOkLabel" : "အိုကေတယ်နော်", -"pdfPaginationDialogCancelLabel" : "မလုပ်တော့ပါ။", +"pdfPaginationDialogOkLabel" : "အတည်ပြုမည်", +"pdfPaginationDialogCancelLabel" : "မလုပ်တော့ဘူး", "passwordDialogHeaderTextLabel" : "စကားဝှက်ကို ကာကွယ်ထားသည်။", "passwordDialogContentLabel" : "ဤ PDF ဖိုင်ကိုဖွင့်ရန် စကားဝှက်ကို ထည့်ပါ။", -"passwordDialogHintTextLabel" : "စကားဝှက်ထည့်ပါ။", +"passwordDialogHintTextLabel" : "Password ရိုက်ထည့်ပါ။", "passwordDialogInvalidPasswordLabel" : "မမှန်ကန်သော စကားဝှက်", -"pdfPasswordDialogOpenLabel" : "ဖွင့်ပါ။", -"pdfPasswordDialogCancelLabel" : "မလုပ်တော့ပါ။", +"pdfPasswordDialogOpenLabel" : "ဖွင့်မည်", +"pdfPasswordDialogCancelLabel" : "ပယ်ဖျက်ပါ", +"pdfSignaturePadDialogHeaderTextLabel" : "သင်၏လက်မှတ်ကိုဆွဲပါ။", +"pdfSignaturePadDialogPenColorLabel" : "ဘောပင်အရောင်", +"pdfSignaturePadDialogClearLabel" : "ရှင်းပါ", +"pdfSignaturePadDialogSaveLabel" : "သိမ်းပါ", +"pdfTextSelectionMenuCopyLabel" : "ကော်ပီ", +"pdfTextSelectionMenuHighlightLabel" : "အထင်ဖျော်", +"pdfTextSelectionMenuStrikethroughLabel" : "ထိုးနှက်ချက်", +"pdfTextSelectionMenuUnderlineLabel" : "မျဉ်းသား", +"pdfTextSelectionMenuSquigglyLabel" : "ကြောင်းပျပ်ကြည့်", "allowedViewDayLabel" : "နေ့", "allowedViewWeekLabel" : "အေးလေ", "allowedViewWorkWeekLabel" : "အလုပ်ရက်သတ္တပတ်", "allowedViewMonthLabel" : "လ", "allowedViewScheduleLabel" : "အချိန်ဇယား", -"allowedViewTimelineDayLabel" : "Timeline နေ့", -"allowedViewTimelineWeekLabel" : "Timeline Week", -"allowedViewTimelineWorkWeekLabel" : "Timeline အလုပ်ရက်သတ္တပတ်", -"allowedViewTimelineMonthLabel" : "အချိန်စာရင်းလ", +"allowedViewTimelineDayLabel" : "အချိန်ဇယားနေ့", +"allowedViewTimelineWeekLabel" : "အချိန်ဇယားအပတ်", +"allowedViewTimelineWorkWeekLabel" : "အချိန်ဇယားအလုပ်သုံးအပတ်", +"allowedViewTimelineMonthLabel" : "အချိန်ဇယားလ", "todayLabel" : "ဒီနေ့", "weeknumberLabel" : "အေးလေ", "allDayLabel" : "တနေကုန်", @@ -38,7 +47,7 @@ "jumada2Label" : "Jumada al-thani", "rajabLabel" : "ရာဂျပ်", "shaabanLabel" : "Sha'aban", -"ramadanLabel" : "ရမ်ဇာန်", +"ramadanLabel" : "ရမဿွာန်", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "နေ့", -"series" : "စီးရီး" +"series" : "စီးရီး", +"pdfHyperlinkLabel" : "ဝဘ်စာမျက်နှာကိုဖွင့်ပါ။", +"pdfHyperlinkContentLabel" : "စာမျက်နှာမှာ ဖွင့်ချင်ပါသလား။", +"pdfHyperlinkDialogOpenLabel" : "ဖွင့်မည်", +"pdfHyperlinkDialogCancelLabel" : "ပယ်ဖျက်ပါ", +"afterDataGridFilteringLabel" : "ပြီးနောက်", +"afterOrEqualDataGridFilteringLabel" : "ပြီးနောက် သို့မဟုတ် အနှိုင်းမဲ့", +"beforeDataGridFilteringLabel" : "မတိုင်မီ", +"beforeOrEqualDataGridFilteringLabel" : "မတိုင်မီ သို့မဟုတ် သာတူညီမျှ", +"beginsWithDataGridFilteringLabel" : "ဖြင့်စတင်သည်။", +"containsDataGridFilteringLabel" : "ပါရှိသည်။", +"doesNotBeginWithDataGridFilteringLabel" : "ဖြင့် မစတင်ပါ။", +"doesNotContainDataGridFilteringLabel" : "မပါဝင်ပါ။", +"doesNotEndWithDataGridFilteringLabel" : "ဖြင့် မဆုံးပါ။", +"doesNotEqualDataGridFilteringLabel" : "မညီမျှပါ။", +"emptyDataGridFilteringLabel" : "ဗလာ", +"endsWithDataGridFilteringLabel" : "ဖြင့် အဆုံးသတ်သည်။", +"equalsDataGridFilteringLabel" : "ညီမျှသည်။", +"greaterThanDataGridFilteringLabel" : "ထက်မြတ်", +"greaterThanOrEqualDataGridFilteringLabel" : "ပိုကြီးသည် သို့မဟုတ် သာတူညီမျှ", +"lessThanDataGridFilteringLabel" : "ထက်ငယ်သော", +"lessThanOrEqualDataGridFilteringLabel" : "လျော့နည်းသည် သို့မဟုတ် ညီမျှသည်။", +"notEmptyDataGridFilteringLabel" : "ဗလာမဟုတ်ပါ။", +"notNullDataGridFilteringLabel" : "Null မဟုတ်ပါ။", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "အသေးဆုံးမှ အကြီးဆုံးသို့ စီပါ။", +"sortLargestToSmallestDataGridFilteringLabel" : "အကြီးဆုံးမှအသေးဆုံးကိုစီပါ။", +"sortAToZDataGridFilteringLabel" : "A မှ Z ကိုစီပါ။", +"sortZToADataGridFilteringLabel" : "Z မှ A ကိုစီပါ။", +"sortOldestToNewestDataGridFilteringLabel" : "အဟောင်းဆုံးမှ အသစ်ဆုံးသို့ စီပါ။", +"sortNewestToOldestDataGridFilteringLabel" : "အသစ်ဆုံးမှ အဟောင်းဆုံးသို့ စီပါ။", +"textFiltersDataGridFilteringLabel" : "စာသားစစ်ထုတ်မှုများ", +"numberFiltersDataGridFilteringLabel" : "နံပါတ် စစ်ထုတ်မှုများ", +"dateFiltersDataGridFilteringLabel" : "ရက်စွဲ စစ်ထုတ်မှုများ", +"searchDataGridFilteringLabel" : "ရှာရန်", +"noMatchesDataGridFilteringLabel" : "တိုက်ဆိုင်မှု မရှိပါ။", +"okDataGridFilteringLabel" : "အဆင်ပြေလား", +"cancelDataGridFilteringLabel" : "မလုပ်တော့", +"showRowsWhereDataGridFilteringLabel" : "အတန်းပြပါ။", +"andDataGridFilteringLabel" : "နှင့်", +"orDataGridFilteringLabel" : "သို့မဟုတ်", +"selectAllDataGridFilteringLabel" : "အားလုံးကို ရွေးပါ။", +"sortAndFilterDataGridFilteringLabel" : "စီစစ်ပြီး စစ်ထုတ်ပါ။", +"clearFilterDataGridFilteringLabel" : "Filter ကိုရှင်းလင်းပါ။", +"fromDataGridFilteringLabel" : "ထံမှ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb index 24baaa17c..4a783f393 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Ugyldig passord", "pdfPasswordDialogOpenLabel" : "ÅPEN", "pdfPasswordDialogCancelLabel" : "AVBRYT", +"pdfSignaturePadDialogHeaderTextLabel" : "Tegn signaturen din", +"pdfSignaturePadDialogPenColorLabel" : "Pennefarge", +"pdfSignaturePadDialogClearLabel" : "Fjern", +"pdfSignaturePadDialogSaveLabel" : "LAGRE", +"pdfTextSelectionMenuCopyLabel" : "Kopi", +"pdfTextSelectionMenuHighlightLabel" : "Fremheve", +"pdfTextSelectionMenuStrikethroughLabel" : "Gjennomstreking", +"pdfTextSelectionMenuUnderlineLabel" : "Understrek", +"pdfTextSelectionMenuSquigglyLabel" : "Snurrete", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Uke", "allowedViewWorkWeekLabel" : "Arbeidsuke", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dag", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Åpne webside", +"pdfHyperlinkContentLabel" : "Vil du åpne siden på", +"pdfHyperlinkDialogOpenLabel" : "Åpne", +"pdfHyperlinkDialogCancelLabel" : "AVBRYT", +"afterDataGridFilteringLabel" : "Etter", +"afterOrEqualDataGridFilteringLabel" : "Etter Eller Like", +"beforeDataGridFilteringLabel" : "Før", +"beforeOrEqualDataGridFilteringLabel" : "Før eller lik", +"beginsWithDataGridFilteringLabel" : "Begynner med", +"containsDataGridFilteringLabel" : "Inneholder", +"doesNotBeginWithDataGridFilteringLabel" : "Begynner ikke med", +"doesNotContainDataGridFilteringLabel" : "Inneholder ikke", +"doesNotEndWithDataGridFilteringLabel" : "Slutter ikke med", +"doesNotEqualDataGridFilteringLabel" : "Er ikke lik", +"emptyDataGridFilteringLabel" : "Tømme", +"endsWithDataGridFilteringLabel" : "Slutter med", +"equalsDataGridFilteringLabel" : "Er lik", +"greaterThanDataGridFilteringLabel" : "Større enn", +"greaterThanOrEqualDataGridFilteringLabel" : "Større enn eller lik", +"lessThanDataGridFilteringLabel" : "Mindre enn", +"lessThanOrEqualDataGridFilteringLabel" : "Mindre enn eller lik", +"notEmptyDataGridFilteringLabel" : "Ikke tom", +"notNullDataGridFilteringLabel" : "Ikke null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Sorter minste til største", +"sortLargestToSmallestDataGridFilteringLabel" : "Sorter størst til minste", +"sortAToZDataGridFilteringLabel" : "Sorter A til Å", +"sortZToADataGridFilteringLabel" : "Sorter Z til A", +"sortOldestToNewestDataGridFilteringLabel" : "Sorter eldste til nyeste", +"sortNewestToOldestDataGridFilteringLabel" : "Sorter nyeste til eldste", +"textFiltersDataGridFilteringLabel" : "Tekstfiltre", +"numberFiltersDataGridFilteringLabel" : "Nummerfiltre", +"dateFiltersDataGridFilteringLabel" : "Datofiltre", +"searchDataGridFilteringLabel" : "Søk", +"noMatchesDataGridFilteringLabel" : "Ingen treff", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Avbryt", +"showRowsWhereDataGridFilteringLabel" : "Vis rader hvor", +"andDataGridFilteringLabel" : "Og", +"orDataGridFilteringLabel" : "Eller", +"selectAllDataGridFilteringLabel" : "Velg alle", +"sortAndFilterDataGridFilteringLabel" : "Sorter og filtrer", +"clearFilterDataGridFilteringLabel" : "Tøm filter", +"fromDataGridFilteringLabel" : "Fra" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb index d6e0f7129..8705bb43f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "अवैध पासवर्ड", "pdfPasswordDialogOpenLabel" : "खोल्नुहोस्", "pdfPasswordDialogCancelLabel" : "रद्द गर्नुहोस्", +"pdfSignaturePadDialogHeaderTextLabel" : "आफ्नो हस्ताक्षर कोर्नुहोस्", +"pdfSignaturePadDialogPenColorLabel" : "कलम रंग", +"pdfSignaturePadDialogClearLabel" : "खाली गर्नुहोस्", +"pdfSignaturePadDialogSaveLabel" : "बचत गर्नुहोस्", +"pdfTextSelectionMenuCopyLabel" : "कापी", +"pdfTextSelectionMenuHighlightLabel" : "हाइलाइट गर्नुहोस्", +"pdfTextSelectionMenuStrikethroughLabel" : "स्ट्राइकथ्रु", +"pdfTextSelectionMenuUnderlineLabel" : "अन्डरलाइन", +"pdfTextSelectionMenuSquigglyLabel" : "चकचके", "allowedViewDayLabel" : "दिन", "allowedViewWeekLabel" : "हप्ता", "allowedViewWorkWeekLabel" : "कार्य हप्ता", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "धुल-क", "shortDhualhiLabel" : "धुल-एच", "daySpanCountLabel" : "दिन", -"series" : "शृङ्खला" +"series" : "शृङ्खला", +"pdfHyperlinkLabel" : "वेब पृष्ठ खोल्नुहोस्", +"pdfHyperlinkContentLabel" : "के तपाइँ पृष्ठ खोल्न चाहनुहुन्छ", +"pdfHyperlinkDialogOpenLabel" : "खोल्नुहोस्", +"pdfHyperlinkDialogCancelLabel" : "रद्द गर्नुहोस्", +"afterDataGridFilteringLabel" : "पछि", +"afterOrEqualDataGridFilteringLabel" : "पछि वा बराबर", +"beforeDataGridFilteringLabel" : "पहिले", +"beforeOrEqualDataGridFilteringLabel" : "अघि वा बराबर", +"beginsWithDataGridFilteringLabel" : "बाट सुरु हुन्छ", +"containsDataGridFilteringLabel" : "समावेश गर्दछ", +"doesNotBeginWithDataGridFilteringLabel" : "बाट सुरु हुँदैन", +"doesNotContainDataGridFilteringLabel" : "समावेश गर्दैन", +"doesNotEndWithDataGridFilteringLabel" : "संग समाप्त हुँदैन", +"doesNotEqualDataGridFilteringLabel" : "बराबर हुँदैन", +"emptyDataGridFilteringLabel" : "खाली", +"endsWithDataGridFilteringLabel" : "संग समाप्त हुन्छ", +"equalsDataGridFilteringLabel" : "बराबर हुन्छ", +"greaterThanDataGridFilteringLabel" : "भन्दा ठुलो", +"greaterThanOrEqualDataGridFilteringLabel" : "भन्दा ग्रेटर वा बराबर", +"lessThanDataGridFilteringLabel" : "भन्दा कम", +"lessThanOrEqualDataGridFilteringLabel" : "भन्दा कम वा बराबर", +"notEmptyDataGridFilteringLabel" : "खाली छैन", +"notNullDataGridFilteringLabel" : "शून्य होइन", +"nullDataGridFilteringLabel" : "खाली", +"sortSmallestToLargestDataGridFilteringLabel" : "सबैभन्दा सानो देखि ठुलो क्रमबद्ध गर्नुहोस्", +"sortLargestToSmallestDataGridFilteringLabel" : "सबैभन्दा ठूलो देखि सानो क्रमबद्ध गर्नुहोस्", +"sortAToZDataGridFilteringLabel" : "A देखि Z क्रमबद्ध गर्नुहोस्", +"sortZToADataGridFilteringLabel" : "Z मा A क्रमबद्ध गर्नुहोस्", +"sortOldestToNewestDataGridFilteringLabel" : "सबैभन्दा पुरानो देखि नयाँ क्रमबद्ध गर्नुहोस्", +"sortNewestToOldestDataGridFilteringLabel" : "सबैभन्दा नयाँ देखि पुरानो क्रमबद्ध गर्नुहोस्", +"textFiltersDataGridFilteringLabel" : "पाठ फिल्टरहरू", +"numberFiltersDataGridFilteringLabel" : "नम्बर फिल्टरहरू", +"dateFiltersDataGridFilteringLabel" : "मिति फिल्टरहरू", +"searchDataGridFilteringLabel" : "खोज्नुहोस्", +"noMatchesDataGridFilteringLabel" : "कुनै मेल छैन", +"okDataGridFilteringLabel" : "ठिक छ", +"cancelDataGridFilteringLabel" : "रद्द गर्नुहोस्", +"showRowsWhereDataGridFilteringLabel" : "जहाँ पङ्क्तिहरू देखाउनुहोस्", +"andDataGridFilteringLabel" : "र", +"orDataGridFilteringLabel" : "वा", +"selectAllDataGridFilteringLabel" : "सबै छान्नु", +"sortAndFilterDataGridFilteringLabel" : "क्रमबद्ध र फिल्टर", +"clearFilterDataGridFilteringLabel" : "फिल्टर खाली गर्नुहोस्", +"fromDataGridFilteringLabel" : "बाट" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb index 57926e823..dbdd945a0 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb @@ -9,8 +9,8 @@ "pdfScrollStatusOfLabel" : "van", "pdfGoToPageLabel" : "Ga naar pagina", "pdfEnterPageNumberLabel" : "Voer paginanummer in", -"pdfInvalidPageNumberLabel" : "Gelieve een geldig nummer invoeren", -"pdfPaginationDialogOkLabel" : "Oke", +"pdfInvalidPageNumberLabel" : "Voer een geldig nummer in", +"pdfPaginationDialogOkLabel" : "Oké", "pdfPaginationDialogCancelLabel" : "ANNULEREN", "passwordDialogHeaderTextLabel" : "Beschermd met een wachtwoord", "passwordDialogContentLabel" : "Voer het wachtwoord in om dit PDF-bestand te openen", @@ -18,22 +18,31 @@ "passwordDialogInvalidPasswordLabel" : "ongeldig wachtwoord", "pdfPasswordDialogOpenLabel" : "OPEN", "pdfPasswordDialogCancelLabel" : "ANNULEREN", +"pdfSignaturePadDialogHeaderTextLabel" : "Teken je handtekening", +"pdfSignaturePadDialogPenColorLabel" : "Pen Kleur", +"pdfSignaturePadDialogClearLabel" : "Wissen", +"pdfSignaturePadDialogSaveLabel" : "Opslaan", +"pdfTextSelectionMenuCopyLabel" : "Kopiëren", +"pdfTextSelectionMenuHighlightLabel" : "Markeren", +"pdfTextSelectionMenuStrikethroughLabel" : "Doorhalen", +"pdfTextSelectionMenuUnderlineLabel" : "Onderstrepen", +"pdfTextSelectionMenuSquigglyLabel" : "Kronkelig", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Week", "allowedViewWorkWeekLabel" : "Werkweek", "allowedViewMonthLabel" : "Maand", "allowedViewScheduleLabel" : "Schema", -"allowedViewTimelineDayLabel" : "Tijdlijn Dag", -"allowedViewTimelineWeekLabel" : "Tijdlijnweek", +"allowedViewTimelineDayLabel" : "Tijdlijn dag", +"allowedViewTimelineWeekLabel" : "Tijdlijn week", "allowedViewTimelineWorkWeekLabel" : "Tijdlijn Werkweek", "allowedViewTimelineMonthLabel" : "Tijdlijn maand", "todayLabel" : "Vandaag", "weeknumberLabel" : "Week", "allDayLabel" : "De hele dag", "muharramLabel" : "Muharram", -"safarLabel" : "Safari", -"rabi1Label" : "Rabi' al-awwali", -"rabi2Label" : "Rabi' al Thani", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi' al-Awwal", +"rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", @@ -43,17 +52,61 @@ "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "saf.", +"shortSafarLabel" : "Veilig", "shortRabi1Label" : "Rabi. l", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "jum. l", -"shortJumada2Label" : "jum. II", +"shortJumada1Label" : "Jum. l", +"shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", -"shortShaabanLabel" : "scha.", +"shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "RAM.", -"shortShawwalLabel" : "Sjaa.", +"shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualhiLabel" : "Dhul-H", "daySpanCountLabel" : "Dag", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Webpagina openen", +"pdfHyperlinkContentLabel" : "Wilt u de pagina openen op", +"pdfHyperlinkDialogOpenLabel" : "OPEN", +"pdfHyperlinkDialogCancelLabel" : "ANNULEREN", +"afterDataGridFilteringLabel" : "Na", +"afterOrEqualDataGridFilteringLabel" : "Na of gelijk", +"beforeDataGridFilteringLabel" : "Voordat", +"beforeOrEqualDataGridFilteringLabel" : "Voor of gelijk", +"beginsWithDataGridFilteringLabel" : "Begint met", +"containsDataGridFilteringLabel" : "Bevat", +"doesNotBeginWithDataGridFilteringLabel" : "Begint niet met", +"doesNotContainDataGridFilteringLabel" : "Bevat geen", +"doesNotEndWithDataGridFilteringLabel" : "Eindigt niet met", +"doesNotEqualDataGridFilteringLabel" : "Is niet gelijk aan", +"emptyDataGridFilteringLabel" : "Leeg", +"endsWithDataGridFilteringLabel" : "Eindigt met", +"equalsDataGridFilteringLabel" : "gelijk aan", +"greaterThanDataGridFilteringLabel" : "Groter dan", +"greaterThanOrEqualDataGridFilteringLabel" : "Groter dan of gelijk aan", +"lessThanDataGridFilteringLabel" : "Minder dan", +"lessThanOrEqualDataGridFilteringLabel" : "Minder dan of gelijk", +"notEmptyDataGridFilteringLabel" : "Niet leeg", +"notNullDataGridFilteringLabel" : "Niet nul", +"nullDataGridFilteringLabel" : "Nul", +"sortSmallestToLargestDataGridFilteringLabel" : "Sorteer klein naar groot", +"sortLargestToSmallestDataGridFilteringLabel" : "Sorteer van groot naar klein", +"sortAToZDataGridFilteringLabel" : "Sorteer van A tot Z", +"sortZToADataGridFilteringLabel" : "Sorteer Z naar A", +"sortOldestToNewestDataGridFilteringLabel" : "Sorteer oudste naar nieuwste", +"sortNewestToOldestDataGridFilteringLabel" : "Sorteer nieuwste naar oudste", +"textFiltersDataGridFilteringLabel" : "Tekstfilters", +"numberFiltersDataGridFilteringLabel" : "Nummerfilters", +"dateFiltersDataGridFilteringLabel" : "Datumfilters", +"searchDataGridFilteringLabel" : "Zoeken", +"noMatchesDataGridFilteringLabel" : "Geen overeenkomsten", +"okDataGridFilteringLabel" : "Oké", +"cancelDataGridFilteringLabel" : "Annuleren", +"showRowsWhereDataGridFilteringLabel" : "Toon rijen waar", +"andDataGridFilteringLabel" : "En", +"orDataGridFilteringLabel" : "Of", +"selectAllDataGridFilteringLabel" : "Selecteer alles", +"sortAndFilterDataGridFilteringLabel" : "Sorteren en filteren", +"clearFilterDataGridFilteringLabel" : "Filter wissen", +"fromDataGridFilteringLabel" : "Van" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_no.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_no.arb new file mode 100644 index 000000000..e147579f8 --- /dev/null +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_no.arb @@ -0,0 +1,112 @@ +{ +"noSelectedDateCalendarLabel" : "Ingen valgt dato", +"noEventsCalendarLabel" : "Ingen hendelser", +"ofDataPagerLabel" : "av", +"pagesDataPagerLabel" : "sider", +"rowsPerPageDataPagerLabel" : "Rader per side", +"pdfBookmarksLabel" : "Bokmerker", +"pdfNoBookmarksLabel" : "Fant ingen bokmerker", +"pdfScrollStatusOfLabel" : "av", +"pdfGoToPageLabel" : "Gå til side", +"pdfEnterPageNumberLabel" : "Skriv inn sidenummer", +"pdfInvalidPageNumberLabel" : "Vennligst oppgi et gyldig nummer", +"pdfPaginationDialogOkLabel" : "OK", +"pdfPaginationDialogCancelLabel" : "AVBRYT", +"passwordDialogHeaderTextLabel" : "Passordbeskyttet", +"passwordDialogContentLabel" : "Skriv inn passordet for å åpne denne PDF-filen", +"passwordDialogHintTextLabel" : "Oppgi passord", +"passwordDialogInvalidPasswordLabel" : "Ugyldig passord", +"pdfPasswordDialogOpenLabel" : "ÅPEN", +"pdfPasswordDialogCancelLabel" : "AVBRYT", +"pdfSignaturePadDialogHeaderTextLabel" : "Tegn signaturen din", +"pdfSignaturePadDialogPenColorLabel" : "Pennefarge", +"pdfSignaturePadDialogClearLabel" : "Tøm", +"pdfSignaturePadDialogSaveLabel" : "Lagre", +"pdfTextSelectionMenuCopyLabel" : "Kopier", +"pdfTextSelectionMenuHighlightLabel" : "Fremhev", +"pdfTextSelectionMenuStrikethroughLabel" : "Gjennomstreking", +"pdfTextSelectionMenuUnderlineLabel" : "Understrek", +"pdfTextSelectionMenuSquigglyLabel" : "Snurrete", +"allowedViewDayLabel" : "Dag", +"allowedViewWeekLabel" : "Uke", +"allowedViewWorkWeekLabel" : "Arbeidsuke", +"allowedViewMonthLabel" : "Måned", +"allowedViewScheduleLabel" : "Rute", +"allowedViewTimelineDayLabel" : "Tidslinje Dag", +"allowedViewTimelineWeekLabel" : "Tidslinje Uke", +"allowedViewTimelineWorkWeekLabel" : "Tidslinje Arbeidsuke", +"allowedViewTimelineMonthLabel" : "Tidslinje Måned", +"todayLabel" : "I dag", +"weeknumberLabel" : "Uke", +"allDayLabel" : "Hele dagen", +"muharramLabel" : "Muharram", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi' al-awwal", +"rabi2Label" : "Rabi' al-thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Sha'aban", +"ramadanLabel" : "Ramadan", +"shawwalLabel" : "Shawwal", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", +"shortRabi1Label" : "Rabi. Jeg", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. Jeg", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "RAM.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H", +"daySpanCountLabel" : "Dag", +"series" : "Serie", +"pdfHyperlinkLabel" : "Åpne webside", +"pdfHyperlinkContentLabel" : "Vil du åpne siden på", +"pdfHyperlinkDialogOpenLabel" : "ÅPEN", +"pdfHyperlinkDialogCancelLabel" : "AVBRYT", +"afterDataGridFilteringLabel" : "Etter", +"afterOrEqualDataGridFilteringLabel" : "Etter Eller Like", +"beforeDataGridFilteringLabel" : "Før", +"beforeOrEqualDataGridFilteringLabel" : "Før eller lik", +"beginsWithDataGridFilteringLabel" : "Begynner med", +"containsDataGridFilteringLabel" : "Inneholder", +"doesNotBeginWithDataGridFilteringLabel" : "Begynner ikke med", +"doesNotContainDataGridFilteringLabel" : "Inneholder ikke", +"doesNotEndWithDataGridFilteringLabel" : "Slutter ikke med", +"doesNotEqualDataGridFilteringLabel" : "Er ikke lik", +"emptyDataGridFilteringLabel" : "Tømme", +"endsWithDataGridFilteringLabel" : "Slutter med", +"equalsDataGridFilteringLabel" : "Er lik", +"greaterThanDataGridFilteringLabel" : "Større enn", +"greaterThanOrEqualDataGridFilteringLabel" : "Større enn eller lik", +"lessThanDataGridFilteringLabel" : "Mindre enn", +"lessThanOrEqualDataGridFilteringLabel" : "Mindre enn eller lik", +"notEmptyDataGridFilteringLabel" : "Ikke tom", +"notNullDataGridFilteringLabel" : "Ikke null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Sorter minste til største", +"sortLargestToSmallestDataGridFilteringLabel" : "Sorter størst til minste", +"sortAToZDataGridFilteringLabel" : "Sorter A til Å", +"sortZToADataGridFilteringLabel" : "Sorter Z til A", +"sortOldestToNewestDataGridFilteringLabel" : "Sorter eldste til nyeste", +"sortNewestToOldestDataGridFilteringLabel" : "Sorter nyeste til eldste", +"textFiltersDataGridFilteringLabel" : "Tekstfiltre", +"numberFiltersDataGridFilteringLabel" : "Nummerfiltre", +"dateFiltersDataGridFilteringLabel" : "Datofiltre", +"searchDataGridFilteringLabel" : "Søk", +"noMatchesDataGridFilteringLabel" : "Ingen treff", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Avbryt", +"showRowsWhereDataGridFilteringLabel" : "Vis rader hvor", +"andDataGridFilteringLabel" : "Og", +"orDataGridFilteringLabel" : "Eller", +"selectAllDataGridFilteringLabel" : "Velg alle", +"sortAndFilterDataGridFilteringLabel" : "Sorter og filtrer", +"clearFilterDataGridFilteringLabel" : "Tøm filter", +"fromDataGridFilteringLabel" : "Fra" +} \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_or.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_or.arb new file mode 100644 index 000000000..d21f6c004 --- /dev/null +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_or.arb @@ -0,0 +1,112 @@ +{ +"noSelectedDateCalendarLabel" : "କ selected ଣସି ମନୋନୀତ ତାରିଖ ନାହିଁ |", +"noEventsCalendarLabel" : "କ events ଣସି ଘଟଣା ନାହିଁ |", +"ofDataPagerLabel" : "ର", +"pagesDataPagerLabel" : "ପୃଷ୍ଠାଗୁଡ଼ିକ |", +"rowsPerPageDataPagerLabel" : "ପ୍ରତି ପୃଷ୍ଠାରେ ଧାଡି |", +"pdfBookmarksLabel" : "ବୁକମାର୍କଗୁଡିକ", +"pdfNoBookmarksLabel" : "କୌଣସି ବୁକମାର୍କ ମିଳିଲା ନାହିଁ", +"pdfScrollStatusOfLabel" : "ର", +"pdfGoToPageLabel" : "ପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ |", +"pdfEnterPageNumberLabel" : "ପୃଷ୍ଠା ନମ୍ବର ପ୍ରବେଶ କରନ୍ତୁ |", +"pdfInvalidPageNumberLabel" : "ଦୟାକରି ଏକ ବୈଧ ନମ୍ବର ପ୍ରବେଶ କରନ୍ତୁ", +"pdfPaginationDialogOkLabel" : "ଠିକ୍ ଅଛି", +"pdfPaginationDialogCancelLabel" : "ବାତିଲ୍", +"passwordDialogHeaderTextLabel" : "ପାସୱାର୍ଡ ସୁରକ୍ଷିତ |", +"passwordDialogContentLabel" : "ଏହି PDF ଫାଇଲ୍ ଖୋଲିବା ପାଇଁ ପାସୱାର୍ଡ ପ୍ରବେଶ କରନ୍ତୁ |", +"passwordDialogHintTextLabel" : "ପାସୱାର୍ଡ ପ୍ରବେଶ କରନ୍ତୁ |", +"passwordDialogInvalidPasswordLabel" : "ଅବୈଧ ପାସୱାର୍ଡ", +"pdfPasswordDialogOpenLabel" : "ଖୋଲନ୍ତୁ |", +"pdfPasswordDialogCancelLabel" : "ବାତିଲ୍", +"pdfSignaturePadDialogHeaderTextLabel" : "ତୁମର ଦସ୍ତଖତ ଅଙ୍କନ କରନ୍ତୁ", +"pdfSignaturePadDialogPenColorLabel" : "କଲମ ରଙ୍ଗ |", +"pdfSignaturePadDialogClearLabel" : "ସଫା କର |", +"pdfSignaturePadDialogSaveLabel" : "ସଞ୍ଚୟ କରନ୍ତୁ |", +"pdfTextSelectionMenuCopyLabel" : "କପି କରନ୍ତୁ |", +"pdfTextSelectionMenuHighlightLabel" : "ହାଇଲାଇଟ୍ କରନ୍ତୁ |", +"pdfTextSelectionMenuStrikethroughLabel" : "ଷ୍ଟ୍ରାଇକଥ୍ରୁ |", +"pdfTextSelectionMenuUnderlineLabel" : "ଅଣ୍ଡରଲାଇନ୍ |", +"pdfTextSelectionMenuSquigglyLabel" : "ସ୍କ୍ୱିଗ୍ଲି |", +"allowedViewDayLabel" : "ଦିନ", +"allowedViewWeekLabel" : "ସପ୍ତାହ", +"allowedViewWorkWeekLabel" : "କାର୍ଯ୍ୟ ସପ୍ତାହ", +"allowedViewMonthLabel" : "ମାସ", +"allowedViewScheduleLabel" : "କାର୍ଯ୍ୟସୂଚୀ", +"allowedViewTimelineDayLabel" : "ସମୟସୀମା ଦିନ |", +"allowedViewTimelineWeekLabel" : "ସମୟସୀମା ସପ୍ତାହ |", +"allowedViewTimelineWorkWeekLabel" : "ସମୟସୀମା କାର୍ଯ୍ୟ ସପ୍ତାହ |", +"allowedViewTimelineMonthLabel" : "ସମୟସୀମା ମାସ |", +"todayLabel" : "ଆଜି |", +"weeknumberLabel" : "ସପ୍ତାହ", +"allDayLabel" : "ଦିନ ସାରା", +"muharramLabel" : "ମୁହରାମ", +"safarLabel" : "ସଫର |", +"rabi1Label" : "ରବି 'ଅଲ-ଅୱାଲ୍ |", +"rabi2Label" : "ରବି 'ଅଲ-ଥାନି |", +"jumada1Label" : "ଜୁମାଡା ଅଲ-ଅୱାଲ |", +"jumada2Label" : "ଜୁମାଡା ଅଲ-ଥାନି |", +"rajabLabel" : "ରାଜାବ", +"shaabanLabel" : "ଶାବାନ୍ |", +"ramadanLabel" : "ରମଜାନ |", +"shawwalLabel" : "ଶାୱାଲ୍ |", +"dhualqiLabel" : "ଧୁ ଅଲ-କଇଦା |", +"dhualhiLabel" : "ଧୁ ଅଲ-ହିଜା", +"shortMuharramLabel" : "ମୁହ।", +"shortSafarLabel" : "ନିରାପଦ", +"shortRabi1Label" : "ରବି | ମୁଁ", +"shortRabi2Label" : "ରବି | II", +"shortJumada1Label" : "ଜମ୍। ମୁଁ", +"shortJumada2Label" : "ଜମ୍। II", +"shortRajabLabel" : "ରାଜ।", +"shortShaabanLabel" : "ଶ।", +"shortRamadanLabel" : "ରାମ।", +"shortShawwalLabel" : "ଶ।", +"shortDhualqiLabel" : "ଧୁଲ୍-ପ୍ର", +"shortDhualhiLabel" : "ଧୁଲ୍-ଏଚ୍", +"daySpanCountLabel" : "ଦିନ", +"series" : "ସିରିଜ୍", +"pdfHyperlinkLabel" : "ୱେବ୍ ପୃଷ୍ଠା ଖୋଲନ୍ତୁ |", +"pdfHyperlinkContentLabel" : "ଆପଣ ପୃଷ୍ଠା ଖୋଲିବାକୁ ଚାହାଁନ୍ତି କି?", +"pdfHyperlinkDialogOpenLabel" : "ଖୋଲନ୍ତୁ |", +"pdfHyperlinkDialogCancelLabel" : "ବାତିଲ୍", +"afterDataGridFilteringLabel" : "ପରେ", +"afterOrEqualDataGridFilteringLabel" : "କିମ୍ବା ସମାନ ପରେ |", +"beforeDataGridFilteringLabel" : "ପୂର୍ବରୁ", +"beforeOrEqualDataGridFilteringLabel" : "ପୂର୍ବରୁ କିମ୍ବା ସମାନ |", +"beginsWithDataGridFilteringLabel" : "ସହିତ ଆରମ୍ଭ ହୁଏ |", +"containsDataGridFilteringLabel" : "ଧାରଣ କରେ |", +"doesNotBeginWithDataGridFilteringLabel" : "ସହିତ ଆରମ୍ଭ ହୁଏ ନାହିଁ |", +"doesNotContainDataGridFilteringLabel" : "ଧାରଣ କରେ ନାହିଁ |", +"doesNotEndWithDataGridFilteringLabel" : "ସହିତ ସମାପ୍ତ ହୁଏ ନାହିଁ |", +"doesNotEqualDataGridFilteringLabel" : "ସମାନ ନୁହେଁ |", +"emptyDataGridFilteringLabel" : "ଖାଲି", +"endsWithDataGridFilteringLabel" : "ସହିତ ଶେଷ ହୁଏ |", +"equalsDataGridFilteringLabel" : "ସମାନ |", +"greaterThanDataGridFilteringLabel" : "ଠାରୁ ବଡ", +"greaterThanOrEqualDataGridFilteringLabel" : "ଠାରୁ ବଡ କିମ୍ବା ସମାନ |", +"lessThanDataGridFilteringLabel" : "ଠାରୁ କମ୍", +"lessThanOrEqualDataGridFilteringLabel" : "କମ୍ କିମ୍ବା ସମାନ |", +"notEmptyDataGridFilteringLabel" : "ଖାଲି ନୁହେଁ |", +"notNullDataGridFilteringLabel" : "ନଲ୍ ନୁହେଁ |", +"nullDataGridFilteringLabel" : "ନଲ୍", +"sortSmallestToLargestDataGridFilteringLabel" : "ଛୋଟରୁ ବଡକୁ ସର୍ଟ କରନ୍ତୁ |", +"sortLargestToSmallestDataGridFilteringLabel" : "ବଡ଼ରୁ ଛୋଟ ପର୍ଯ୍ୟନ୍ତ ସର୍ଟ କରନ୍ତୁ |", +"sortAToZDataGridFilteringLabel" : "A ରୁ Z ସର୍ଟ କରନ୍ତୁ |", +"sortZToADataGridFilteringLabel" : "Z ରୁ A ସର୍ଟ କରନ୍ତୁ |", +"sortOldestToNewestDataGridFilteringLabel" : "ପୁରାତନରୁ ନୂତନକୁ ସର୍ଟ କରନ୍ତୁ |", +"sortNewestToOldestDataGridFilteringLabel" : "ପୁରାତନରୁ ନୂତନକୁ ସର୍ଟ କରନ୍ତୁ |", +"textFiltersDataGridFilteringLabel" : "ପାଠ୍ୟ ଫିଲ୍ଟର୍ |", +"numberFiltersDataGridFilteringLabel" : "ସଂଖ୍ୟା ଫିଲ୍ଟର୍ |", +"dateFiltersDataGridFilteringLabel" : "ତାରିଖ ଫିଲ୍ଟର୍ |", +"searchDataGridFilteringLabel" : "ସନ୍ଧାନ କର |", +"noMatchesDataGridFilteringLabel" : "କ matches ଣସି ମେଳକ ନାହିଁ |", +"okDataGridFilteringLabel" : "ଠିକ୍ ଅଛି", +"cancelDataGridFilteringLabel" : "ବାତିଲ୍ କରନ୍ତୁ |", +"showRowsWhereDataGridFilteringLabel" : "ଧାଡିଗୁଡିକ କେଉଁଠାରେ ଦେଖାନ୍ତୁ |", +"andDataGridFilteringLabel" : "ଏବଂ", +"orDataGridFilteringLabel" : "କିମ୍ବା", +"selectAllDataGridFilteringLabel" : "ସବୁ ବାଛ", +"sortAndFilterDataGridFilteringLabel" : "ସର୍ଟ ଏବଂ ଫିଲ୍ଟର୍ |", +"clearFilterDataGridFilteringLabel" : "ଫିଲ୍ଟର୍ ସଫା କରନ୍ତୁ |", +"fromDataGridFilteringLabel" : "ଠାରୁ" +} \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb index f9e72f826..edaf8e5a8 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "ਕੋਈ ਚੁਣੀ ਤਾਰੀਖ ਨਹੀਂ ਹੈ", +"noSelectedDateCalendarLabel" : "ਕੋਈ ਤਾਰੀਖ ਚੁਣੀ ਨਹੀਂ ਗਈ", "noEventsCalendarLabel" : "ਕੋਈ ਸਮਾਗਮ ਨਹੀਂ", "ofDataPagerLabel" : "ਦੇ", "pagesDataPagerLabel" : "ਪੰਨੇ", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "ਗਲਤ ਪਾਸਵਰਡ", "pdfPasswordDialogOpenLabel" : "ਖੋਲ੍ਹੋ", "pdfPasswordDialogCancelLabel" : "ਰੱਦ ਕਰੋ", +"pdfSignaturePadDialogHeaderTextLabel" : "ਆਪਣੇ ਦਸਤਖਤ ਖਿੱਚੋ", +"pdfSignaturePadDialogPenColorLabel" : "ਪੈੱਨ ਦਾ ਰੰਗ", +"pdfSignaturePadDialogClearLabel" : "ਸਾਫ਼", +"pdfSignaturePadDialogSaveLabel" : "ਸੇਵ ਕਰੋ", +"pdfTextSelectionMenuCopyLabel" : "ਕਾਪੀ ਕਰੋ", +"pdfTextSelectionMenuHighlightLabel" : "ਹਾਈਲਾਈਟ ਕਰੋ", +"pdfTextSelectionMenuStrikethroughLabel" : "ਸਟ੍ਰਾਈਕਥਰੂ", +"pdfTextSelectionMenuUnderlineLabel" : "ਰੇਖਾਂਕਿਤ ਕਰੋ", +"pdfTextSelectionMenuSquigglyLabel" : "ਕੁਰਲ਼", "allowedViewDayLabel" : "ਦਿਨ", "allowedViewWeekLabel" : "ਹਫ਼ਤਾ", "allowedViewWorkWeekLabel" : "ਕੰਮ ਦਾ ਹਫ਼ਤਾ", @@ -25,7 +34,7 @@ "allowedViewScheduleLabel" : "ਸਮਾਸੂਚੀ, ਕਾਰਜ - ਕ੍ਰਮ", "allowedViewTimelineDayLabel" : "ਸਮਾਂਰੇਖਾ ਦਿਨ", "allowedViewTimelineWeekLabel" : "ਸਮਾਂਰੇਖਾ ਹਫ਼ਤਾ", -"allowedViewTimelineWorkWeekLabel" : "ਸਮਾਂਰੇਖਾ ਕਾਰਜ ਹਫ਼ਤਾ", +"allowedViewTimelineWorkWeekLabel" : "ਟਾਈਮਲਾਈਨ ਕੰਮ ਹਫ਼ਤਾ", "allowedViewTimelineMonthLabel" : "ਸਮਾਂਰੇਖਾ ਮਹੀਨਾ", "todayLabel" : "ਅੱਜ", "weeknumberLabel" : "ਹਫ਼ਤਾ", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ਧੂਲ-ਕਿਊ", "shortDhualhiLabel" : "ਧੂਲ-ਐੱਚ", "daySpanCountLabel" : "ਦਿਨ", -"series" : "ਲੜੀ" +"series" : "ਲੜੀ", +"pdfHyperlinkLabel" : "ਵੈੱਬ ਪੰਨਾ ਖੋਲ੍ਹੋ", +"pdfHyperlinkContentLabel" : "ਕੀ ਤੁਸੀਂ 'ਤੇ ਪੰਨਾ ਖੋਲ੍ਹਣਾ ਚਾਹੁੰਦੇ ਹੋ", +"pdfHyperlinkDialogOpenLabel" : "ਖੋਲ੍ਹੋ", +"pdfHyperlinkDialogCancelLabel" : "ਰੱਦ ਕਰੋ", +"afterDataGridFilteringLabel" : "ਤੋਂ ਬਾਅਦ", +"afterOrEqualDataGridFilteringLabel" : "ਬਾਅਦ ਜਾਂ ਬਰਾਬਰ", +"beforeDataGridFilteringLabel" : "ਅੱਗੇ", +"beforeOrEqualDataGridFilteringLabel" : "ਅੱਗੇ ਜਾਂ ਬਰਾਬਰ", +"beginsWithDataGridFilteringLabel" : "ਨਾਲ ਸ਼ੁਰੂ ਹੁੰਦਾ ਹੈ", +"containsDataGridFilteringLabel" : "ਸ਼ਾਮਿਲ ਹੈ", +"doesNotBeginWithDataGridFilteringLabel" : "ਨਾਲ ਸ਼ੁਰੂ ਨਹੀਂ ਹੁੰਦਾ", +"doesNotContainDataGridFilteringLabel" : "ਸ਼ਾਮਲ ਨਹੀਂ ਹੈ", +"doesNotEndWithDataGridFilteringLabel" : "ਨਾਲ ਖਤਮ ਨਹੀਂ ਹੁੰਦਾ", +"doesNotEqualDataGridFilteringLabel" : "ਬਰਾਬਰ ਨਹੀਂ ਹੈ", +"emptyDataGridFilteringLabel" : "ਖਾਲੀ", +"endsWithDataGridFilteringLabel" : "ਨਾਲ ਖਤਮ ਹੁੰਦਾ ਹੈ", +"equalsDataGridFilteringLabel" : "ਬਰਾਬਰ", +"greaterThanDataGridFilteringLabel" : "ਵੱਧ", +"greaterThanOrEqualDataGridFilteringLabel" : "ਇਸ ਤੋਂ ਵੱਧ ਜਾਂ ਬਰਾਬਰ", +"lessThanDataGridFilteringLabel" : "ਉਸ ਤੋਂ ਘਟ", +"lessThanOrEqualDataGridFilteringLabel" : "ਇਸ ਤੋਂ ਘੱਟ ਜਾਂ ਬਰਾਬਰ", +"notEmptyDataGridFilteringLabel" : "ਖਾਲੀ ਨਹੀਂ", +"notNullDataGridFilteringLabel" : "ਨਲ ਨਹੀਂ", +"nullDataGridFilteringLabel" : "ਨਲ", +"sortSmallestToLargestDataGridFilteringLabel" : "ਸਭ ਤੋਂ ਛੋਟੇ ਤੋਂ ਵੱਡੇ ਨੂੰ ਛਾਂਟੋ", +"sortLargestToSmallestDataGridFilteringLabel" : "ਸਭ ਤੋਂ ਵੱਡੇ ਤੋਂ ਛੋਟੇ ਨੂੰ ਛਾਂਟੋ", +"sortAToZDataGridFilteringLabel" : "A ਤੋਂ Z ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰੋ", +"sortZToADataGridFilteringLabel" : "Z ਤੋਂ A ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰੋ", +"sortOldestToNewestDataGridFilteringLabel" : "ਸਭ ਤੋਂ ਪੁਰਾਣੇ ਤੋਂ ਨਵੀਨਤਮ ਕ੍ਰਮਬੱਧ ਕਰੋ", +"sortNewestToOldestDataGridFilteringLabel" : "ਸਭ ਤੋਂ ਨਵੇਂ ਤੋਂ ਪੁਰਾਣੇ ਨੂੰ ਕ੍ਰਮਬੱਧ ਕਰੋ", +"textFiltersDataGridFilteringLabel" : "ਟੈਕਸਟ ਫਿਲਟਰ", +"numberFiltersDataGridFilteringLabel" : "ਨੰਬਰ ਫਿਲਟਰ", +"dateFiltersDataGridFilteringLabel" : "ਮਿਤੀ ਫਿਲਟਰ", +"searchDataGridFilteringLabel" : "ਖੋਜ", +"noMatchesDataGridFilteringLabel" : "ਕੋਈ ਮੇਲ ਨਹੀਂ", +"okDataGridFilteringLabel" : "ਠੀਕ ਹੈ", +"cancelDataGridFilteringLabel" : "ਰੱਦ ਕਰੋ", +"showRowsWhereDataGridFilteringLabel" : "ਕਤਾਰਾਂ ਦਿਖਾਓ ਕਿੱਥੇ", +"andDataGridFilteringLabel" : "ਅਤੇ", +"orDataGridFilteringLabel" : "ਜਾਂ", +"selectAllDataGridFilteringLabel" : "ਸਾਰਿਆ ਨੂੰ ਚੁਣੋ", +"sortAndFilterDataGridFilteringLabel" : "ਕ੍ਰਮਬੱਧ ਅਤੇ ਫਿਲਟਰ", +"clearFilterDataGridFilteringLabel" : "ਫਿਲਟਰ ਸਾਫ਼ ਕਰੋ", +"fromDataGridFilteringLabel" : "ਤੋਂ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb index c27911849..4682e6755 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Nie wybrano daty", +"noSelectedDateCalendarLabel" : "Brak wybranej daty", "noEventsCalendarLabel" : "Brak wydarzeń", "ofDataPagerLabel" : "z", "pagesDataPagerLabel" : "strony", @@ -8,52 +8,105 @@ "pdfNoBookmarksLabel" : "Nie znaleziono zakładek", "pdfScrollStatusOfLabel" : "z", "pdfGoToPageLabel" : "Idź do strony", -"pdfEnterPageNumberLabel" : "Wpisz numer strony", +"pdfEnterPageNumberLabel" : "Wprowadź numer strony", "pdfInvalidPageNumberLabel" : "Proszę wprowadzić poprawny numer", -"pdfPaginationDialogOkLabel" : "ok", -"pdfPaginationDialogCancelLabel" : "ANULUJ", +"pdfPaginationDialogOkLabel" : "OK", +"pdfPaginationDialogCancelLabel" : "Anuluj", "passwordDialogHeaderTextLabel" : "Hasło chronione", "passwordDialogContentLabel" : "Wprowadź hasło, aby otworzyć ten plik PDF", "passwordDialogHintTextLabel" : "Wprowadź hasło", -"passwordDialogInvalidPasswordLabel" : "nieprawidłowe hasło", -"pdfPasswordDialogOpenLabel" : "OTWARTY", -"pdfPasswordDialogCancelLabel" : "ANULUJ", +"passwordDialogInvalidPasswordLabel" : "Nieprawidłowe hasło", +"pdfPasswordDialogOpenLabel" : "Otwórz", +"pdfPasswordDialogCancelLabel" : "Anuluj", +"pdfSignaturePadDialogHeaderTextLabel" : "Narysuj swój podpis", +"pdfSignaturePadDialogPenColorLabel" : "Kolor pióra", +"pdfSignaturePadDialogClearLabel" : "Wyczyść", +"pdfSignaturePadDialogSaveLabel" : "Zapisz", +"pdfTextSelectionMenuCopyLabel" : "Kopiuj", +"pdfTextSelectionMenuHighlightLabel" : "Podświetl", +"pdfTextSelectionMenuStrikethroughLabel" : "Przekreślenie", +"pdfTextSelectionMenuUnderlineLabel" : "Podkreślać", +"pdfTextSelectionMenuSquigglyLabel" : "Faliste", "allowedViewDayLabel" : "Dzień", "allowedViewWeekLabel" : "Tydzień", "allowedViewWorkWeekLabel" : "Tydzień pracy", "allowedViewMonthLabel" : "Miesiąc", "allowedViewScheduleLabel" : "Harmonogram", "allowedViewTimelineDayLabel" : "Dzień osi czasu", -"allowedViewTimelineWeekLabel" : "Tydzień na osi czasu", -"allowedViewTimelineWorkWeekLabel" : "Tydzień pracy na osi czasu", +"allowedViewTimelineWeekLabel" : "Tydzień osi czasu", +"allowedViewTimelineWorkWeekLabel" : "Tydzień roboczy osi czasu", "allowedViewTimelineMonthLabel" : "Miesiąc osi czasu", "todayLabel" : "Dziś", "weeknumberLabel" : "Tydzień", "allDayLabel" : "Cały dzień", -"muharramLabel" : "Muharram", +"muharramLabel" : "muharrama", "safarLabel" : "Safar", -"rabi1Label" : "Rabi' al-awwal", -"rabi2Label" : "Rabi’ al-thani", +"rabi1Label" : "Rabi al-awwal", +"rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", -"jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Rajab", -"shaabanLabel" : "Szaaban", +"jumada2Label" : "Jumada al Thani", +"rajabLabel" : "Radżab", +"shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", -"dhualqiLabel" : "Zu al-Qi'dah", -"dhualhiLabel" : "Zu al-Hidżdżah", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Du al-Hijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. i", +"shortSafarLabel" : "bezpieczny", +"shortRabi1Label" : "Rabi. I", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Skok. i", -"shortJumada2Label" : "Skok. II", +"shortJumada1Label" : "sok. I", +"shortJumada2Label" : "sok. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Baran.", -"shortShawwalLabel" : "Shaw.", +"shortShawwalLabel" : "Shawa.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dzień", -"series" : "Seria" +"series" : "Seria", +"pdfHyperlinkLabel" : "Otwórz stronę internetową", +"pdfHyperlinkContentLabel" : "Czy chcesz otworzyć stronę o godz", +"pdfHyperlinkDialogOpenLabel" : "Otwórz", +"pdfHyperlinkDialogCancelLabel" : "Anuluj", +"afterDataGridFilteringLabel" : "Później", +"afterOrEqualDataGridFilteringLabel" : "Po lub równe", +"beforeDataGridFilteringLabel" : "Zanim", +"beforeOrEqualDataGridFilteringLabel" : "Przed Lub Równy", +"beginsWithDataGridFilteringLabel" : "Zaczyna się z", +"containsDataGridFilteringLabel" : "Zawiera", +"doesNotBeginWithDataGridFilteringLabel" : "Nie zaczyna się od", +"doesNotContainDataGridFilteringLabel" : "Nie zawiera", +"doesNotEndWithDataGridFilteringLabel" : "Nie kończy się na", +"doesNotEqualDataGridFilteringLabel" : "Nie równa się", +"emptyDataGridFilteringLabel" : "Pusty", +"endsWithDataGridFilteringLabel" : "Kończy się na", +"equalsDataGridFilteringLabel" : "Równa się", +"greaterThanDataGridFilteringLabel" : "Większe niż", +"greaterThanOrEqualDataGridFilteringLabel" : "Większe lub równe", +"lessThanDataGridFilteringLabel" : "Mniej niż", +"lessThanOrEqualDataGridFilteringLabel" : "Mniej niż lub równo", +"notEmptyDataGridFilteringLabel" : "Nie pusty", +"notNullDataGridFilteringLabel" : "Nie jest zerem", +"nullDataGridFilteringLabel" : "Zero", +"sortSmallestToLargestDataGridFilteringLabel" : "Sortuj od najmniejszego do największego", +"sortLargestToSmallestDataGridFilteringLabel" : "Sortuj od największego do najmniejszego", +"sortAToZDataGridFilteringLabel" : "Sortuj od A do Z", +"sortZToADataGridFilteringLabel" : "Sortuj od Z do A", +"sortOldestToNewestDataGridFilteringLabel" : "Sortuj od najstarszych do najnowszych", +"sortNewestToOldestDataGridFilteringLabel" : "Sortuj od najnowszych do najstarszych", +"textFiltersDataGridFilteringLabel" : "Filtry tekstowe", +"numberFiltersDataGridFilteringLabel" : "Filtry liczbowe", +"dateFiltersDataGridFilteringLabel" : "Filtry daty", +"searchDataGridFilteringLabel" : "Szukaj", +"noMatchesDataGridFilteringLabel" : "Brak wyników", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Anulować", +"showRowsWhereDataGridFilteringLabel" : "Pokaż wiersze gdzie", +"andDataGridFilteringLabel" : "I", +"orDataGridFilteringLabel" : "Lub", +"selectAllDataGridFilteringLabel" : "Zaznacz wszystko", +"sortAndFilterDataGridFilteringLabel" : "Sortuj i filtruj", +"clearFilterDataGridFilteringLabel" : "Czysty filtr", +"fromDataGridFilteringLabel" : "Z" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb index 71f8e5513..8c5727e7e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb @@ -7,17 +7,26 @@ "pdfBookmarksLabel" : "بک مارکونه", "pdfNoBookmarksLabel" : "هیڅ بک مارک ونه موندل شو", "pdfScrollStatusOfLabel" : "د", -"pdfGoToPageLabel" : "پاڼې ته لاړ شئ", +"pdfGoToPageLabel" : "مخ ته لاړ شئ", "pdfEnterPageNumberLabel" : "د پاڼې شمیره دننه کړئ", "pdfInvalidPageNumberLabel" : "مهرباني وکړئ یو باوري شمیره دننه کړئ", "pdfPaginationDialogOkLabel" : "سمه ده", -"pdfPaginationDialogCancelLabel" : "لغوه کول", +"pdfPaginationDialogCancelLabel" : "فسخه کول", "passwordDialogHeaderTextLabel" : "پټنوم خوندي شوی", "passwordDialogContentLabel" : "د دې پی ډی ایف فایل خلاصولو لپاره پاسورډ دننه کړئ", "passwordDialogHintTextLabel" : "پاسورډ دننه کړئ", "passwordDialogInvalidPasswordLabel" : "بې اعتباره پټنوم", "pdfPasswordDialogOpenLabel" : "خلاص", -"pdfPasswordDialogCancelLabel" : "لغوه کول", +"pdfPasswordDialogCancelLabel" : "فسخه کول", +"pdfSignaturePadDialogHeaderTextLabel" : "خپل لاسلیک رسم کړئ", +"pdfSignaturePadDialogPenColorLabel" : "د قلم رنګ", +"pdfSignaturePadDialogClearLabel" : "روښانه", +"pdfSignaturePadDialogSaveLabel" : "خوندي کړئ", +"pdfTextSelectionMenuCopyLabel" : "کاپي", +"pdfTextSelectionMenuHighlightLabel" : "روښانه کول", +"pdfTextSelectionMenuStrikethroughLabel" : "کرښه کول", +"pdfTextSelectionMenuUnderlineLabel" : "لاندې", +"pdfTextSelectionMenuSquigglyLabel" : "موجي کرښه", "allowedViewDayLabel" : "ورځ", "allowedViewWeekLabel" : "اونۍ", "allowedViewWorkWeekLabel" : "کاري اونۍ", @@ -34,7 +43,7 @@ "safarLabel" : "صفر", "rabi1Label" : "ربیع الاول", "rabi2Label" : "ربیع الثاني", -"jumada1Label" : "جمادی الاول", +"jumada1Label" : "جمعه الاول", "jumada2Label" : "جمعه الثاني", "rajabLabel" : "رجب", "shaabanLabel" : "شعبان", @@ -44,8 +53,8 @@ "dhualhiLabel" : "ذوالحجې", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "سف", -"shortRabi1Label" : "ربیع زه", -"shortRabi2Label" : "ربیع II", +"shortRabi1Label" : "ربیع. زه", +"shortRabi2Label" : "ربیع. II", "shortJumada1Label" : "جم. زه", "shortJumada2Label" : "جم. II", "shortRajabLabel" : "راج.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ذوالق", "shortDhualhiLabel" : "ذوالح", "daySpanCountLabel" : "ورځ", -"series" : "لړۍ" +"series" : "لړۍ", +"pdfHyperlinkLabel" : "ویب پاڼه پرانیزئ", +"pdfHyperlinkContentLabel" : "ایا تاسو غواړئ پاڼه پرانیزئ", +"pdfHyperlinkDialogOpenLabel" : "خلاص", +"pdfHyperlinkDialogCancelLabel" : "فسخه کول", +"afterDataGridFilteringLabel" : "وروسته", +"afterOrEqualDataGridFilteringLabel" : "وروسته یا مساوي", +"beforeDataGridFilteringLabel" : "مخکې", +"beforeOrEqualDataGridFilteringLabel" : "مخکې یا مساوي", +"beginsWithDataGridFilteringLabel" : "سره پیل کیږي", +"containsDataGridFilteringLabel" : "لري", +"doesNotBeginWithDataGridFilteringLabel" : "سره پیل نه کوي", +"doesNotContainDataGridFilteringLabel" : "نه لري", +"doesNotEndWithDataGridFilteringLabel" : "سره پای نه لري", +"doesNotEqualDataGridFilteringLabel" : "برابر نه دی", +"emptyDataGridFilteringLabel" : "خالي", +"endsWithDataGridFilteringLabel" : "سره پای ته رسیږي", +"equalsDataGridFilteringLabel" : "مساوي", +"greaterThanDataGridFilteringLabel" : "په پرتله لوی", +"greaterThanOrEqualDataGridFilteringLabel" : "په پرتله لوی یا مساوي", +"lessThanDataGridFilteringLabel" : "په پرتله لږ", +"lessThanOrEqualDataGridFilteringLabel" : "لږ یا مساوي", +"notEmptyDataGridFilteringLabel" : "نه خالي", +"notNullDataGridFilteringLabel" : "نه نول", +"nullDataGridFilteringLabel" : "نول", +"sortSmallestToLargestDataGridFilteringLabel" : "له کوچنی څخه تر ټولو لوی ترتیب کړئ", +"sortLargestToSmallestDataGridFilteringLabel" : "له لوی څخه کوچنی ترتیب کړئ", +"sortAToZDataGridFilteringLabel" : "له A څخه Z ترتیب کړئ", +"sortZToADataGridFilteringLabel" : "له Z څخه A ترتیب کړئ", +"sortOldestToNewestDataGridFilteringLabel" : "له زاړه څخه تر نوي پورې ترتیب کړئ", +"sortNewestToOldestDataGridFilteringLabel" : "له نوي څخه تر ټولو زوړ ترتیب کړئ", +"textFiltersDataGridFilteringLabel" : "د متن فلټرونه", +"numberFiltersDataGridFilteringLabel" : "د شمیر فلټرونه", +"dateFiltersDataGridFilteringLabel" : "د نیټې فلټرونه", +"searchDataGridFilteringLabel" : "لټون", +"noMatchesDataGridFilteringLabel" : "هیڅ ټکي نه دي", +"okDataGridFilteringLabel" : "سمه ده", +"cancelDataGridFilteringLabel" : "لغوه کړئ", +"showRowsWhereDataGridFilteringLabel" : "قطارونه وښایاست چیرته", +"andDataGridFilteringLabel" : "او", +"orDataGridFilteringLabel" : "یا", +"selectAllDataGridFilteringLabel" : "ټول وټاکئ", +"sortAndFilterDataGridFilteringLabel" : "ترتیب او فلټر کړئ", +"clearFilterDataGridFilteringLabel" : "فلټر پاک کړئ", +"fromDataGridFilteringLabel" : "څخه" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb index f9d0a6b6c..92aa1dc89 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb @@ -1,14 +1,14 @@ { "noSelectedDateCalendarLabel" : "Nenhuma data selecionada", "noEventsCalendarLabel" : "Sem eventos", -"ofDataPagerLabel" : "do", +"ofDataPagerLabel" : "de", "pagesDataPagerLabel" : "Páginas", "rowsPerPageDataPagerLabel" : "Linhas por página", "pdfBookmarksLabel" : "Favoritos", "pdfNoBookmarksLabel" : "Nenhum favorito encontrado", "pdfScrollStatusOfLabel" : "do", "pdfGoToPageLabel" : "Vá para página", -"pdfEnterPageNumberLabel" : "Digite o número da página", +"pdfEnterPageNumberLabel" : "Insira o número da página", "pdfInvalidPageNumberLabel" : "Por favor insira um número válido", "pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "CANCELAR", @@ -18,11 +18,20 @@ "passwordDialogInvalidPasswordLabel" : "senha inválida", "pdfPasswordDialogOpenLabel" : "ABRIR", "pdfPasswordDialogCancelLabel" : "CANCELAR", +"pdfSignaturePadDialogHeaderTextLabel" : "Desenhe sua assinatura", +"pdfSignaturePadDialogPenColorLabel" : "Cor da caneta", +"pdfSignaturePadDialogClearLabel" : "Limpar", +"pdfSignaturePadDialogSaveLabel" : "SALVAR", +"pdfTextSelectionMenuCopyLabel" : "Copiar", +"pdfTextSelectionMenuHighlightLabel" : "Destaque", +"pdfTextSelectionMenuStrikethroughLabel" : "Tachado", +"pdfTextSelectionMenuUnderlineLabel" : "Sublinhado", +"pdfTextSelectionMenuSquigglyLabel" : "Ondulado", "allowedViewDayLabel" : "Dia", "allowedViewWeekLabel" : "Semana", "allowedViewWorkWeekLabel" : "Semana de trabalho", "allowedViewMonthLabel" : "Mês", -"allowedViewScheduleLabel" : "Agendar", +"allowedViewScheduleLabel" : "Cronograma", "allowedViewTimelineDayLabel" : "Dia da linha do tempo", "allowedViewTimelineWeekLabel" : "Semana da linha do tempo", "allowedViewTimelineWorkWeekLabel" : "Semana de trabalho da linha do tempo", @@ -43,10 +52,10 @@ "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabino. eu", -"shortRabi2Label" : "Rabino. II", -"shortJumada1Label" : "Jum. eu", +"shortSafarLabel" : "seguro", +"shortRabi1Label" : "Rabi. EU", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. EU", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dia", -"series" : "Series" +"series" : "Series", +"pdfHyperlinkLabel" : "Abrir página da Web", +"pdfHyperlinkContentLabel" : "Deseja abrir a página em", +"pdfHyperlinkDialogOpenLabel" : "ABRIR", +"pdfHyperlinkDialogCancelLabel" : "CANCELAR", +"afterDataGridFilteringLabel" : "Depois", +"afterOrEqualDataGridFilteringLabel" : "Depois Ou Igual", +"beforeDataGridFilteringLabel" : "Antes", +"beforeOrEqualDataGridFilteringLabel" : "Antes Ou Igual", +"beginsWithDataGridFilteringLabel" : "Começa com", +"containsDataGridFilteringLabel" : "contém", +"doesNotBeginWithDataGridFilteringLabel" : "Não começa com", +"doesNotContainDataGridFilteringLabel" : "Não contém", +"doesNotEndWithDataGridFilteringLabel" : "Não termina com", +"doesNotEqualDataGridFilteringLabel" : "Não é igual", +"emptyDataGridFilteringLabel" : "Vazio", +"endsWithDataGridFilteringLabel" : "Termina com", +"equalsDataGridFilteringLabel" : "É igual a", +"greaterThanDataGridFilteringLabel" : "Maior que", +"greaterThanOrEqualDataGridFilteringLabel" : "Maior ou igual", +"lessThanDataGridFilteringLabel" : "Menor que", +"lessThanOrEqualDataGridFilteringLabel" : "Menor ou igual", +"notEmptyDataGridFilteringLabel" : "Não está vazio", +"notNullDataGridFilteringLabel" : "Não nulo", +"nullDataGridFilteringLabel" : "Nulo", +"sortSmallestToLargestDataGridFilteringLabel" : "Classificar do menor para o maior", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordenar do maior para o menor", +"sortAToZDataGridFilteringLabel" : "Ordenar de A a Z", +"sortZToADataGridFilteringLabel" : "Ordenar de Z a A", +"sortOldestToNewestDataGridFilteringLabel" : "Classificar do mais antigo para o mais recente", +"sortNewestToOldestDataGridFilteringLabel" : "Ordenar do mais recente ao mais antigo", +"textFiltersDataGridFilteringLabel" : "Filtros de texto", +"numberFiltersDataGridFilteringLabel" : "Filtros de número", +"dateFiltersDataGridFilteringLabel" : "Filtros de Data", +"searchDataGridFilteringLabel" : "Procurar", +"noMatchesDataGridFilteringLabel" : "Sem combinações", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Cancelar", +"showRowsWhereDataGridFilteringLabel" : "Mostrar linhas onde", +"andDataGridFilteringLabel" : "E", +"orDataGridFilteringLabel" : "Ou", +"selectAllDataGridFilteringLabel" : "Selecionar tudo", +"sortAndFilterDataGridFilteringLabel" : "Classificar e Filtrar", +"clearFilterDataGridFilteringLabel" : "Filtro limpo", +"fromDataGridFilteringLabel" : "A partir de" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb index 068f8b961..f1f517aa8 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb @@ -1,23 +1,32 @@ { "noSelectedDateCalendarLabel" : "Nenhuma data selecionada", -"noEventsCalendarLabel" : "Nenhum evento", +"noEventsCalendarLabel" : "Sem eventos", "ofDataPagerLabel" : "de", -"pagesDataPagerLabel" : "páginas", +"pagesDataPagerLabel" : "Páginas", "rowsPerPageDataPagerLabel" : "Linhas por página", -"pdfBookmarksLabel" : "Marcadores", +"pdfBookmarksLabel" : "Favoritos", "pdfNoBookmarksLabel" : "Nenhum favorito encontrado", -"pdfScrollStatusOfLabel" : "de", -"pdfGoToPageLabel" : "Ir para a página", +"pdfScrollStatusOfLabel" : "do", +"pdfGoToPageLabel" : "Vá para página", "pdfEnterPageNumberLabel" : "Digite o número da página", -"pdfInvalidPageNumberLabel" : "Insira um número válido", +"pdfInvalidPageNumberLabel" : "Por favor insira um número válido", "pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "CANCELAR", "passwordDialogHeaderTextLabel" : "Protegido por senha", "passwordDialogContentLabel" : "Digite a senha para abrir este arquivo PDF", "passwordDialogHintTextLabel" : "Digite a senha", -"passwordDialogInvalidPasswordLabel" : "Senha inválida", -"pdfPasswordDialogOpenLabel" : "ABERTO", +"passwordDialogInvalidPasswordLabel" : "senha inválida", +"pdfPasswordDialogOpenLabel" : "ABRIR", "pdfPasswordDialogCancelLabel" : "CANCELAR", +"pdfSignaturePadDialogHeaderTextLabel" : "Desenhe sua assinatura", +"pdfSignaturePadDialogPenColorLabel" : "Cor da caneta", +"pdfSignaturePadDialogClearLabel" : "Limpar", +"pdfSignaturePadDialogSaveLabel" : "SALVAR", +"pdfTextSelectionMenuCopyLabel" : "Copiar", +"pdfTextSelectionMenuHighlightLabel" : "Realçar", +"pdfTextSelectionMenuStrikethroughLabel" : "Tachado", +"pdfTextSelectionMenuUnderlineLabel" : "Sublinhado", +"pdfTextSelectionMenuSquigglyLabel" : "Ondulado", "allowedViewDayLabel" : "Dia", "allowedViewWeekLabel" : "Semana", "allowedViewWorkWeekLabel" : "Semana de trabalho", @@ -29,7 +38,7 @@ "allowedViewTimelineMonthLabel" : "Mês da linha do tempo", "todayLabel" : "Hoje", "weeknumberLabel" : "Semana", -"allDayLabel" : "Todo o dia", +"allDayLabel" : "Dia todo", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi' al-awwal", @@ -43,17 +52,61 @@ "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabino. eu", -"shortRabi2Label" : "Rabino. II", -"shortJumada1Label" : "Jum. eu", +"shortSafarLabel" : "seguro", +"shortRabi1Label" : "Rabi. EU", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "Jum. EU", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", -"shortRamadanLabel" : "Carneiro.", +"shortRamadanLabel" : "RAM.", "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dia", -"series" : "Série" +"series" : "Series", +"pdfHyperlinkLabel" : "Abrir página da Web", +"pdfHyperlinkContentLabel" : "Deseja abrir a página em", +"pdfHyperlinkDialogOpenLabel" : "ABRIR", +"pdfHyperlinkDialogCancelLabel" : "CANCELAR", +"afterDataGridFilteringLabel" : "Depois", +"afterOrEqualDataGridFilteringLabel" : "Depois Ou Igual", +"beforeDataGridFilteringLabel" : "Antes", +"beforeOrEqualDataGridFilteringLabel" : "Antes Ou Igual", +"beginsWithDataGridFilteringLabel" : "Começa com", +"containsDataGridFilteringLabel" : "contém", +"doesNotBeginWithDataGridFilteringLabel" : "Não começa com", +"doesNotContainDataGridFilteringLabel" : "Não contém", +"doesNotEndWithDataGridFilteringLabel" : "Não termina com", +"doesNotEqualDataGridFilteringLabel" : "Não é igual", +"emptyDataGridFilteringLabel" : "Vazio", +"endsWithDataGridFilteringLabel" : "Termina com", +"equalsDataGridFilteringLabel" : "É igual a", +"greaterThanDataGridFilteringLabel" : "Maior que", +"greaterThanOrEqualDataGridFilteringLabel" : "Maior ou igual", +"lessThanDataGridFilteringLabel" : "Menor que", +"lessThanOrEqualDataGridFilteringLabel" : "Menor ou igual", +"notEmptyDataGridFilteringLabel" : "Não está vazio", +"notNullDataGridFilteringLabel" : "Não nulo", +"nullDataGridFilteringLabel" : "Nulo", +"sortSmallestToLargestDataGridFilteringLabel" : "Classificar do menor para o maior", +"sortLargestToSmallestDataGridFilteringLabel" : "Ordenar do maior para o menor", +"sortAToZDataGridFilteringLabel" : "Ordenar de A a Z", +"sortZToADataGridFilteringLabel" : "Ordenar de Z a A", +"sortOldestToNewestDataGridFilteringLabel" : "Classificar do mais antigo para o mais recente", +"sortNewestToOldestDataGridFilteringLabel" : "Ordenar do mais recente ao mais antigo", +"textFiltersDataGridFilteringLabel" : "Filtros de texto", +"numberFiltersDataGridFilteringLabel" : "Filtros de número", +"dateFiltersDataGridFilteringLabel" : "Filtros de Data", +"searchDataGridFilteringLabel" : "Procurar", +"noMatchesDataGridFilteringLabel" : "Sem combinações", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Cancelar", +"showRowsWhereDataGridFilteringLabel" : "Mostrar linhas onde", +"andDataGridFilteringLabel" : "E", +"orDataGridFilteringLabel" : "Ou", +"selectAllDataGridFilteringLabel" : "Selecionar tudo", +"sortAndFilterDataGridFilteringLabel" : "Classificar e Filtrar", +"clearFilterDataGridFilteringLabel" : "Filtro limpo", +"fromDataGridFilteringLabel" : "A partir de" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb index a33228abe..9d96cdfbd 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Mergi la pagina", "pdfEnterPageNumberLabel" : "Introduceți numărul paginii", "pdfInvalidPageNumberLabel" : "Vă rugăm să introduceți un număr valid", -"pdfPaginationDialogOkLabel" : "Bine", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "ANULARE", "passwordDialogHeaderTextLabel" : "Protectie cu parola", "passwordDialogContentLabel" : "Introduceți parola pentru a deschide acest fișier PDF", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Parolă Invalidă", "pdfPasswordDialogOpenLabel" : "DESCHIS", "pdfPasswordDialogCancelLabel" : "ANULARE", +"pdfSignaturePadDialogHeaderTextLabel" : "Desenează-ți semnătura", +"pdfSignaturePadDialogPenColorLabel" : "Culoare stilou", +"pdfSignaturePadDialogClearLabel" : "ȘTERGE", +"pdfSignaturePadDialogSaveLabel" : "SALVEAZĂ", +"pdfTextSelectionMenuCopyLabel" : "Copiere", +"pdfTextSelectionMenuHighlightLabel" : "Evidențiere", +"pdfTextSelectionMenuStrikethroughLabel" : "Tăiere", +"pdfTextSelectionMenuUnderlineLabel" : "Subliniați", +"pdfTextSelectionMenuSquigglyLabel" : "Sinuos", "allowedViewDayLabel" : "Zi", "allowedViewWeekLabel" : "Săptămână", "allowedViewWorkWeekLabel" : "Saptamana de lucru", @@ -27,7 +36,7 @@ "allowedViewTimelineWeekLabel" : "Săptămâna cronologică", "allowedViewTimelineWorkWeekLabel" : "Cronologie Săptămâna de lucru", "allowedViewTimelineMonthLabel" : "Cronologie Luna", -"todayLabel" : "Azi", +"todayLabel" : "Astăzi", "weeknumberLabel" : "Săptămână", "allDayLabel" : "Toată ziua", "muharramLabel" : "Muharram", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Zi", -"series" : "Serie" +"series" : "Serie", +"pdfHyperlinkLabel" : "Deschideți pagina web", +"pdfHyperlinkContentLabel" : "Doriți să deschideți pagina la", +"pdfHyperlinkDialogOpenLabel" : "DESCHIS", +"pdfHyperlinkDialogCancelLabel" : "ANULARE", +"afterDataGridFilteringLabel" : "După", +"afterOrEqualDataGridFilteringLabel" : "După Sau Egal", +"beforeDataGridFilteringLabel" : "Inainte de", +"beforeOrEqualDataGridFilteringLabel" : "Înainte sau egal", +"beginsWithDataGridFilteringLabel" : "Începe cu", +"containsDataGridFilteringLabel" : "Conține", +"doesNotBeginWithDataGridFilteringLabel" : "Nu Începe Cu", +"doesNotContainDataGridFilteringLabel" : "Nu contine", +"doesNotEndWithDataGridFilteringLabel" : "Nu se termină cu", +"doesNotEqualDataGridFilteringLabel" : "Nu este egal", +"emptyDataGridFilteringLabel" : "Gol", +"endsWithDataGridFilteringLabel" : "Se termină cu", +"equalsDataGridFilteringLabel" : "Egal", +"greaterThanDataGridFilteringLabel" : "Mai mare ca", +"greaterThanOrEqualDataGridFilteringLabel" : "Mai mare sau egal", +"lessThanDataGridFilteringLabel" : "Mai puțin decât", +"lessThanOrEqualDataGridFilteringLabel" : "Mai puțin sau egal", +"notEmptyDataGridFilteringLabel" : "Nu Gol", +"notNullDataGridFilteringLabel" : "Nu nul", +"nullDataGridFilteringLabel" : "Nul", +"sortSmallestToLargestDataGridFilteringLabel" : "Sortați de la cel mai mic la cel mai mare", +"sortLargestToSmallestDataGridFilteringLabel" : "Sortați de la cel mai mare la cel mai mic", +"sortAToZDataGridFilteringLabel" : "Sortați de la A la Z", +"sortZToADataGridFilteringLabel" : "Sortați de la Z la A", +"sortOldestToNewestDataGridFilteringLabel" : "Sortați de la cel mai vechi la cel mai nou", +"sortNewestToOldestDataGridFilteringLabel" : "Sortați de la cel mai nou la cel mai vechi", +"textFiltersDataGridFilteringLabel" : "Filtre de text", +"numberFiltersDataGridFilteringLabel" : "Filtre de numere", +"dateFiltersDataGridFilteringLabel" : "Filtre de dată", +"searchDataGridFilteringLabel" : "Căutare", +"noMatchesDataGridFilteringLabel" : "Nici-o potrivire", +"okDataGridFilteringLabel" : "O.K", +"cancelDataGridFilteringLabel" : "Anulare", +"showRowsWhereDataGridFilteringLabel" : "Afișați rândurile unde", +"andDataGridFilteringLabel" : "Și", +"orDataGridFilteringLabel" : "Sau", +"selectAllDataGridFilteringLabel" : "Selectează tot", +"sortAndFilterDataGridFilteringLabel" : "Sortați și filtrați", +"clearFilterDataGridFilteringLabel" : "Șterge filtrul", +"fromDataGridFilteringLabel" : "Din" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb index 82c9c50e8..896c63e83 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb @@ -9,26 +9,35 @@ "pdfScrollStatusOfLabel" : "из", "pdfGoToPageLabel" : "Перейти на страницу", "pdfEnterPageNumberLabel" : "Введите номер страницы", -"pdfInvalidPageNumberLabel" : "пожалуйста введите правильное число", -"pdfPaginationDialogOkLabel" : "Ok", +"pdfInvalidPageNumberLabel" : "Пожалуйста, введите правильное число", +"pdfPaginationDialogOkLabel" : "ОК", "pdfPaginationDialogCancelLabel" : "ОТМЕНИТЬ", "passwordDialogHeaderTextLabel" : "Пароль защищен", "passwordDialogContentLabel" : "Введите пароль, чтобы открыть этот файл PDF", "passwordDialogHintTextLabel" : "Введите пароль", -"passwordDialogInvalidPasswordLabel" : "неправильный пароль", -"pdfPasswordDialogOpenLabel" : "ОТКРЫТЫМ", +"passwordDialogInvalidPasswordLabel" : "Неверный пароль", +"pdfPasswordDialogOpenLabel" : "ОТКРЫТЬ", "pdfPasswordDialogCancelLabel" : "ОТМЕНИТЬ", +"pdfSignaturePadDialogHeaderTextLabel" : "Нарисуй свою подпись", +"pdfSignaturePadDialogPenColorLabel" : "Цвет пера", +"pdfSignaturePadDialogClearLabel" : "ОЧИСТИТЬ", +"pdfSignaturePadDialogSaveLabel" : "СОХРАНИТЬ", +"pdfTextSelectionMenuCopyLabel" : "Копировать", +"pdfTextSelectionMenuHighlightLabel" : "Подсветить", +"pdfTextSelectionMenuStrikethroughLabel" : "Зачеркивание", +"pdfTextSelectionMenuUnderlineLabel" : "Подчеркнуть", +"pdfTextSelectionMenuSquigglyLabel" : "Зигзаг", "allowedViewDayLabel" : "День", -"allowedViewWeekLabel" : "Неделю", +"allowedViewWeekLabel" : "Неделя", "allowedViewWorkWeekLabel" : "Рабочая неделя", "allowedViewMonthLabel" : "Месяц", "allowedViewScheduleLabel" : "Расписание", -"allowedViewTimelineDayLabel" : "День", +"allowedViewTimelineDayLabel" : "День временной шкалы", "allowedViewTimelineWeekLabel" : "Неделя временной шкалы", -"allowedViewTimelineWorkWeekLabel" : "Рабочая неделя", -"allowedViewTimelineMonthLabel" : "Месяц", +"allowedViewTimelineWorkWeekLabel" : "Рабочая неделя временной шкалы", +"allowedViewTimelineMonthLabel" : "Месяц временной шкалы", "todayLabel" : "Сегодня", -"weeknumberLabel" : "Неделю", +"weeknumberLabel" : "Неделя", "allDayLabel" : "Весь день", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", @@ -50,10 +59,54 @@ "shortJumada2Label" : "Джум. II", "shortRajabLabel" : "Радж.", "shortShaabanLabel" : "Ша.", -"shortRamadanLabel" : "ОЗУ.", +"shortRamadanLabel" : "Баран.", "shortShawwalLabel" : "Шоу.", "shortDhualqiLabel" : "Зуль-Кью", "shortDhualhiLabel" : "Зуль-Х", "daySpanCountLabel" : "День", -"series" : "Ряд" +"series" : "Серии", +"pdfHyperlinkLabel" : "Открыть веб-страницу", +"pdfHyperlinkContentLabel" : "Вы хотите открыть страницу в", +"pdfHyperlinkDialogOpenLabel" : "ОТКРЫТЫМ", +"pdfHyperlinkDialogCancelLabel" : "ОТМЕНИТЬ", +"afterDataGridFilteringLabel" : "После", +"afterOrEqualDataGridFilteringLabel" : "После или равно", +"beforeDataGridFilteringLabel" : "До", +"beforeOrEqualDataGridFilteringLabel" : "Перед или равным", +"beginsWithDataGridFilteringLabel" : "Начинается с", +"containsDataGridFilteringLabel" : "Содержит", +"doesNotBeginWithDataGridFilteringLabel" : "Не начинается с", +"doesNotContainDataGridFilteringLabel" : "Не содержит", +"doesNotEndWithDataGridFilteringLabel" : "Не заканчивается с", +"doesNotEqualDataGridFilteringLabel" : "Не равно", +"emptyDataGridFilteringLabel" : "Пустой", +"endsWithDataGridFilteringLabel" : "Заканчивается с", +"equalsDataGridFilteringLabel" : "Равно", +"greaterThanDataGridFilteringLabel" : "Больше чем", +"greaterThanOrEqualDataGridFilteringLabel" : "Больше или равно", +"lessThanDataGridFilteringLabel" : "Меньше, чем", +"lessThanOrEqualDataGridFilteringLabel" : "Меньше или равно", +"notEmptyDataGridFilteringLabel" : "Не пустой", +"notNullDataGridFilteringLabel" : "Ненулевой", +"nullDataGridFilteringLabel" : "Нулевой", +"sortSmallestToLargestDataGridFilteringLabel" : "Сортировать от меньшего к большему", +"sortLargestToSmallestDataGridFilteringLabel" : "Сортировать от большего к меньшему", +"sortAToZDataGridFilteringLabel" : "Сортировать от А до Я", +"sortZToADataGridFilteringLabel" : "Сортировать от Я до А", +"sortOldestToNewestDataGridFilteringLabel" : "Сортировать от старых к новым", +"sortNewestToOldestDataGridFilteringLabel" : "Сортировать от новых к старым", +"textFiltersDataGridFilteringLabel" : "Текстовые фильтры", +"numberFiltersDataGridFilteringLabel" : "Числовые фильтры", +"dateFiltersDataGridFilteringLabel" : "Фильтры даты", +"searchDataGridFilteringLabel" : "Поиск", +"noMatchesDataGridFilteringLabel" : "Нет совпадений", +"okDataGridFilteringLabel" : "ХОРОШО", +"cancelDataGridFilteringLabel" : "Отмена", +"showRowsWhereDataGridFilteringLabel" : "Показать строки, где", +"andDataGridFilteringLabel" : "И", +"orDataGridFilteringLabel" : "Или же", +"selectAllDataGridFilteringLabel" : "Выбрать все", +"sortAndFilterDataGridFilteringLabel" : "Сортировка и фильтрация", +"clearFilterDataGridFilteringLabel" : "Очистить фильтр", +"fromDataGridFilteringLabel" : "Из" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb index f8e535f59..5c8388ac5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb @@ -16,9 +16,18 @@ "passwordDialogContentLabel" : "මෙම PDF ගොනුව විවෘත කිරීමට මුරපදය ඇතුළත් කරන්න", "passwordDialogHintTextLabel" : "මුරපදය ඇතුළත් කරන්න", "passwordDialogInvalidPasswordLabel" : "වලංගු නොවන මුරපදය", -"pdfPasswordDialogOpenLabel" : "විවෘත", +"pdfPasswordDialogOpenLabel" : "විවෘත කරන්න", "pdfPasswordDialogCancelLabel" : "අවලංගු කරන්න", -"allowedViewDayLabel" : "දිනය", +"pdfSignaturePadDialogHeaderTextLabel" : "ඔබේ අත්සන අඳින්න", +"pdfSignaturePadDialogPenColorLabel" : "පෑන වර්ණය", +"pdfSignaturePadDialogClearLabel" : "මකන්න", +"pdfSignaturePadDialogSaveLabel" : "සුරකින්න", +"allowedViewDayLabel" : "දින", +"pdfTextSelectionMenuCopyLabel" : "පිටපත් කරන්න", +"pdfTextSelectionMenuHighlightLabel" : "ඉස්මතු කිරීම්", +"pdfTextSelectionMenuStrikethroughLabel" : "පහර හරහා", +"pdfTextSelectionMenuUnderlineLabel" : "යටිකුරු", +"pdfTextSelectionMenuSquigglyLabel" : "වක්‍රක", "allowedViewWeekLabel" : "සතිය", "allowedViewWorkWeekLabel" : "වැඩ සතිය", "allowedViewMonthLabel" : "මාසික", @@ -41,7 +50,7 @@ "ramadanLabel" : "රාමසාන්", "shawwalLabel" : "ෂව්වාල්", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualhiLabel" : "ඩු අල්-හිජ්ජා", "shortMuharramLabel" : "මුහ්", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "රබී. මම", @@ -54,6 +63,50 @@ "shortShawwalLabel" : "ෂෝ.", "shortDhualqiLabel" : "දුල්-කිව්", "shortDhualhiLabel" : "දුල්-එච්", -"daySpanCountLabel" : "දිනය", -"series" : "මාලාවක්" +"daySpanCountLabel" : "දින", +"series" : "මාලාවක්", +"pdfHyperlinkLabel" : "වෙබ් පිටුව විවෘත කරන්න", +"pdfHyperlinkContentLabel" : "ඔබට පිටුව විවෘත කිරීමට අවශ්‍යද", +"pdfHyperlinkDialogOpenLabel" : "විවෘත කරන්න", +"pdfHyperlinkDialogCancelLabel" : "අවලංගු කරන්න", +"afterDataGridFilteringLabel" : "පසු", +"afterOrEqualDataGridFilteringLabel" : "පසු හෝ සමාන", +"beforeDataGridFilteringLabel" : "කලින්", +"beforeOrEqualDataGridFilteringLabel" : "පෙර හෝ සමාන", +"beginsWithDataGridFilteringLabel" : "සමඟ ආරම්භ වේ", +"containsDataGridFilteringLabel" : "අඩංගු වේ", +"doesNotBeginWithDataGridFilteringLabel" : "සමඟ ආරම්භ නොවේ", +"doesNotContainDataGridFilteringLabel" : "අඩංගු නොවේ", +"doesNotEndWithDataGridFilteringLabel" : "සමඟ අවසන් නොවේ", +"doesNotEqualDataGridFilteringLabel" : "සමාන නොවේ", +"emptyDataGridFilteringLabel" : "හිස්", +"endsWithDataGridFilteringLabel" : "සමඟ අවසන් වේ", +"equalsDataGridFilteringLabel" : "සමාන", +"greaterThanDataGridFilteringLabel" : "වඩා විශාලයි", +"greaterThanOrEqualDataGridFilteringLabel" : "වඩා විශාල හෝ සමාන වේ", +"lessThanDataGridFilteringLabel" : "අඩු", +"lessThanOrEqualDataGridFilteringLabel" : "අඩු හෝ සමානයි", +"notEmptyDataGridFilteringLabel" : "හිස් නොවේ", +"notNullDataGridFilteringLabel" : "Null නොවේ", +"nullDataGridFilteringLabel" : "ශුන්‍ය", +"sortSmallestToLargestDataGridFilteringLabel" : "කුඩාම සිට විශාල දක්වා වර්ග කරන්න", +"sortLargestToSmallestDataGridFilteringLabel" : "විශාලතම සිට කුඩාම දක්වා වර්ග කරන්න", +"sortAToZDataGridFilteringLabel" : "A සිට Z දක්වා වර්ග කරන්න", +"sortZToADataGridFilteringLabel" : "Z සිට A දක්වා වර්ග කරන්න", +"sortOldestToNewestDataGridFilteringLabel" : "පැරණිතම සිට අලුත්ම දක්වා වර්ග කරන්න", +"sortNewestToOldestDataGridFilteringLabel" : "අලුත්ම සිට පැරණිම දක්වා වර්ග කරන්න", +"textFiltersDataGridFilteringLabel" : "පෙළ පෙරහන්", +"numberFiltersDataGridFilteringLabel" : "අංක පෙරහන්", +"dateFiltersDataGridFilteringLabel" : "දින පෙරහන්", +"searchDataGridFilteringLabel" : "සොයන්න", +"noMatchesDataGridFilteringLabel" : "ගැලපීම් නැත", +"okDataGridFilteringLabel" : "හරි", +"cancelDataGridFilteringLabel" : "අවලංගු කරන්න", +"showRowsWhereDataGridFilteringLabel" : "කොහෙද පේළි පෙන්වන්න", +"andDataGridFilteringLabel" : "හා", +"orDataGridFilteringLabel" : "හෝ", +"selectAllDataGridFilteringLabel" : "සියල්ල තෝරන්න", +"sortAndFilterDataGridFilteringLabel" : "වර්ග කිරීම සහ පෙරීම", +"clearFilterDataGridFilteringLabel" : "පෙරහන හිස් කරන්න", +"fromDataGridFilteringLabel" : "සිට" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb index 628a7db67..da677c924 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Nie je vybraný dátum", +"noSelectedDateCalendarLabel" : "Žiadny vybratý dátum", "noEventsCalendarLabel" : "Žiadne udalosti", "ofDataPagerLabel" : "z", "pagesDataPagerLabel" : "stránky", @@ -9,7 +9,7 @@ "pdfScrollStatusOfLabel" : "z", "pdfGoToPageLabel" : "Chod na stranu", "pdfEnterPageNumberLabel" : "Zadajte číslo strany", -"pdfInvalidPageNumberLabel" : "Prosím zadajte platné číslo", +"pdfInvalidPageNumberLabel" : "Prosím, zadajte platné číslo strany", "pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "ZRUŠIŤ", "passwordDialogHeaderTextLabel" : "Chránené heslom", @@ -18,17 +18,26 @@ "passwordDialogInvalidPasswordLabel" : "nesprávne heslo", "pdfPasswordDialogOpenLabel" : "OTVORENÉ", "pdfPasswordDialogCancelLabel" : "ZRUŠIŤ", -"allowedViewDayLabel" : "deň", -"allowedViewWeekLabel" : "týždeň", +"pdfSignaturePadDialogHeaderTextLabel" : "Nakreslite svoj podpis", +"pdfSignaturePadDialogPenColorLabel" : "Farba pera", +"pdfSignaturePadDialogClearLabel" : "Vymazať", +"pdfSignaturePadDialogSaveLabel" : "ULOŽIŤ", +"pdfTextSelectionMenuCopyLabel" : "Kopírovať", +"pdfTextSelectionMenuHighlightLabel" : "Zvýrazniť", +"pdfTextSelectionMenuStrikethroughLabel" : "Prečiarknuté", +"pdfTextSelectionMenuUnderlineLabel" : "Zdôrazniť", +"pdfTextSelectionMenuSquigglyLabel" : "Squiggly", +"allowedViewDayLabel" : "Deň", +"allowedViewWeekLabel" : "Týždeň", "allowedViewWorkWeekLabel" : "Pracovný týždeň", -"allowedViewMonthLabel" : "mesiac", +"allowedViewMonthLabel" : "Mesiac", "allowedViewScheduleLabel" : "Rozvrh", "allowedViewTimelineDayLabel" : "Deň časovej osi", "allowedViewTimelineWeekLabel" : "Časová os týždeň", "allowedViewTimelineWorkWeekLabel" : "Časová os Pracovný týždeň", "allowedViewTimelineMonthLabel" : "Mesiac časovej osi", -"todayLabel" : "dnes", -"weeknumberLabel" : "týždeň", +"todayLabel" : "Dnes", +"weeknumberLabel" : "Týždeň", "allDayLabel" : "Celý deň", "muharramLabel" : "Muharram", "safarLabel" : "Šafár", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "deň", -"series" : "séria" +"series" : "séria", +"pdfHyperlinkLabel" : "Otvorte webovú stránku", +"pdfHyperlinkContentLabel" : "Chcete otvoriť stránku na", +"pdfHyperlinkDialogOpenLabel" : "OTVORENÉ", +"pdfHyperlinkDialogCancelLabel" : "ZRUŠIŤ", +"afterDataGridFilteringLabel" : "Po", +"afterOrEqualDataGridFilteringLabel" : "Po alebo Equal", +"beforeDataGridFilteringLabel" : "Predtým", +"beforeOrEqualDataGridFilteringLabel" : "Pred alebo Equal", +"beginsWithDataGridFilteringLabel" : "Zacina s", +"containsDataGridFilteringLabel" : "Obsahuje", +"doesNotBeginWithDataGridFilteringLabel" : "Nezačína s", +"doesNotContainDataGridFilteringLabel" : "Neobsahuje", +"doesNotEndWithDataGridFilteringLabel" : "Nekončí s", +"doesNotEqualDataGridFilteringLabel" : "Nerovná sa", +"emptyDataGridFilteringLabel" : "Prázdny", +"endsWithDataGridFilteringLabel" : "Končí s", +"equalsDataGridFilteringLabel" : "Rovná sa", +"greaterThanDataGridFilteringLabel" : "Väčší než", +"greaterThanOrEqualDataGridFilteringLabel" : "Väčšie alebo rovné", +"lessThanDataGridFilteringLabel" : "Menej ako", +"lessThanOrEqualDataGridFilteringLabel" : "Menej než alebo rovné", +"notEmptyDataGridFilteringLabel" : "Nie prázdny", +"notNullDataGridFilteringLabel" : "Nie Null", +"nullDataGridFilteringLabel" : "Nulový", +"sortSmallestToLargestDataGridFilteringLabel" : "Zoradiť od najmenšieho po najväčšie", +"sortLargestToSmallestDataGridFilteringLabel" : "Zoradiť od najväčšieho po najmenšie", +"sortAToZDataGridFilteringLabel" : "Zoradiť od A po Z", +"sortZToADataGridFilteringLabel" : "Zoradiť od Z po A", +"sortOldestToNewestDataGridFilteringLabel" : "Zoradiť od najstarších po najnovšie", +"sortNewestToOldestDataGridFilteringLabel" : "Zoradiť od najnovších po najstaršie", +"textFiltersDataGridFilteringLabel" : "Textové filtre", +"numberFiltersDataGridFilteringLabel" : "Filtre čísel", +"dateFiltersDataGridFilteringLabel" : "Dátumové filtre", +"searchDataGridFilteringLabel" : "Vyhľadávanie", +"noMatchesDataGridFilteringLabel" : "Žiadne zhody", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Zrušiť", +"showRowsWhereDataGridFilteringLabel" : "Zobraziť riadky kde", +"andDataGridFilteringLabel" : "A", +"orDataGridFilteringLabel" : "Alebo", +"selectAllDataGridFilteringLabel" : "Vybrať všetko", +"sortAndFilterDataGridFilteringLabel" : "Triediť a filtrovať", +"clearFilterDataGridFilteringLabel" : "Vymazať filter", +"fromDataGridFilteringLabel" : "Od" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb index 7d542b7eb..f5de4e666 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb @@ -1,11 +1,11 @@ { "noSelectedDateCalendarLabel" : "Ni izbranega datuma", -"noEventsCalendarLabel" : "Brez dogodkov", +"noEventsCalendarLabel" : "Ni dogodkov", "ofDataPagerLabel" : "od", "pagesDataPagerLabel" : "strani", -"rowsPerPageDataPagerLabel" : "Vrstice na stran", +"rowsPerPageDataPagerLabel" : "Vrstic na stran", "pdfBookmarksLabel" : "Zaznamki", -"pdfNoBookmarksLabel" : "Najden ni bil noben zaznamek", +"pdfNoBookmarksLabel" : "Ni najdenih zaznamkov", "pdfScrollStatusOfLabel" : "od", "pdfGoToPageLabel" : "Pojdi na stran", "pdfEnterPageNumberLabel" : "Vnesite številko strani", @@ -13,22 +13,31 @@ "pdfPaginationDialogOkLabel" : "v redu", "pdfPaginationDialogCancelLabel" : "PREKLIC", "passwordDialogHeaderTextLabel" : "Zaščiteno z geslom", -"passwordDialogContentLabel" : "Vnesite geslo za odpiranje te datoteke PDF", +"passwordDialogContentLabel" : "Vnesite geslo, da odprete to datoteko PDF", "passwordDialogHintTextLabel" : "Vnesite geslo", "passwordDialogInvalidPasswordLabel" : "Neveljavno geslo", "pdfPasswordDialogOpenLabel" : "ODPRTO", "pdfPasswordDialogCancelLabel" : "PREKLIC", -"allowedViewDayLabel" : "dan", -"allowedViewWeekLabel" : "teden", +"pdfSignaturePadDialogHeaderTextLabel" : "Nariši svoj podpis", +"pdfSignaturePadDialogPenColorLabel" : "Barva peresa", +"pdfSignaturePadDialogClearLabel" : "Počisti", +"pdfSignaturePadDialogSaveLabel" : "SHRANI", +"pdfTextSelectionMenuCopyLabel" : "Kopirati", +"pdfTextSelectionMenuHighlightLabel" : "Zaznamuj", +"pdfTextSelectionMenuStrikethroughLabel" : "Prečrtano", +"pdfTextSelectionMenuUnderlineLabel" : "Podčrtaj", +"pdfTextSelectionMenuSquigglyLabel" : "Vijugasto", +"allowedViewDayLabel" : "Dan", +"allowedViewWeekLabel" : "Teden", "allowedViewWorkWeekLabel" : "Delovni teden", -"allowedViewMonthLabel" : "mesec", +"allowedViewMonthLabel" : "Mesec", "allowedViewScheduleLabel" : "Urnik", "allowedViewTimelineDayLabel" : "Dan časovnice", "allowedViewTimelineWeekLabel" : "Teden časovnice", "allowedViewTimelineWorkWeekLabel" : "Časovnica Delovni teden", "allowedViewTimelineMonthLabel" : "Mesec časovnice", -"todayLabel" : "danes", -"weeknumberLabel" : "teden", +"todayLabel" : "Danes", +"weeknumberLabel" : "Teden", "allDayLabel" : "Ves dan", "muharramLabel" : "Muharram", "safarLabel" : "Safar", @@ -36,7 +45,7 @@ "rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Radžab", +"rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", @@ -49,11 +58,55 @@ "shortJumada1Label" : "Jum. jaz", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", -"shortShaabanLabel" : "Sha", +"shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Oven.", "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", -"daySpanCountLabel" : "dan", -"series" : "Serija" +"daySpanCountLabel" : "Dan", +"series" : "serija", +"pdfHyperlinkLabel" : "Odpri spletno stran", +"pdfHyperlinkContentLabel" : "Ali želite odpreti stran na", +"pdfHyperlinkDialogOpenLabel" : "ODPRTO", +"pdfHyperlinkDialogCancelLabel" : "PREKLIC", +"afterDataGridFilteringLabel" : "Po", +"afterOrEqualDataGridFilteringLabel" : "Po ali enako", +"beforeDataGridFilteringLabel" : "prej", +"beforeOrEqualDataGridFilteringLabel" : "Pred ali enako", +"beginsWithDataGridFilteringLabel" : "Začne se z", +"containsDataGridFilteringLabel" : "Vsebuje", +"doesNotBeginWithDataGridFilteringLabel" : "Se ne začne z", +"doesNotContainDataGridFilteringLabel" : "Ne vsebuje", +"doesNotEndWithDataGridFilteringLabel" : "Se ne konča z", +"doesNotEqualDataGridFilteringLabel" : "Ni enako", +"emptyDataGridFilteringLabel" : "Prazno", +"endsWithDataGridFilteringLabel" : "Konča se z", +"equalsDataGridFilteringLabel" : "Enako", +"greaterThanDataGridFilteringLabel" : "Večji kot", +"greaterThanOrEqualDataGridFilteringLabel" : "Večje ali enako", +"lessThanDataGridFilteringLabel" : "Manj kot", +"lessThanOrEqualDataGridFilteringLabel" : "Manj ali enako", +"notEmptyDataGridFilteringLabel" : "Ni prazno", +"notNullDataGridFilteringLabel" : "Ni Null", +"nullDataGridFilteringLabel" : "Nič", +"sortSmallestToLargestDataGridFilteringLabel" : "Razvrsti od najmanjšega do največjega", +"sortLargestToSmallestDataGridFilteringLabel" : "Razvrsti od največjega do najmanjšega", +"sortAToZDataGridFilteringLabel" : "Razvrsti od A do Ž", +"sortZToADataGridFilteringLabel" : "Razvrsti od Ž do A", +"sortOldestToNewestDataGridFilteringLabel" : "Razvrsti od starejšega do najnovejšega", +"sortNewestToOldestDataGridFilteringLabel" : "Razvrsti od najnovejšega do najstarejšega", +"textFiltersDataGridFilteringLabel" : "Besedilni filtri", +"numberFiltersDataGridFilteringLabel" : "Številčni filtri", +"dateFiltersDataGridFilteringLabel" : "Datumski filtri", +"searchDataGridFilteringLabel" : "Iskanje", +"noMatchesDataGridFilteringLabel" : "Ni ujemanja", +"okDataGridFilteringLabel" : "v redu", +"cancelDataGridFilteringLabel" : "Prekliči", +"showRowsWhereDataGridFilteringLabel" : "Pokaži vrstice, kje", +"andDataGridFilteringLabel" : "in", +"orDataGridFilteringLabel" : "oz", +"selectAllDataGridFilteringLabel" : "Izberi vse", +"sortAndFilterDataGridFilteringLabel" : "Razvrsti in filtriraj", +"clearFilterDataGridFilteringLabel" : "Počisti filter", +"fromDataGridFilteringLabel" : "Od" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb index 88108d826..b94ea3e79 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb @@ -9,16 +9,25 @@ "pdfScrollStatusOfLabel" : "e", "pdfGoToPageLabel" : "Shkoni në faqe", "pdfEnterPageNumberLabel" : "Fut numrin e faqes", -"pdfInvalidPageNumberLabel" : "Ju lutemi shkruani një numër të vlefshëm", -"pdfPaginationDialogOkLabel" : "Ne rregull", +"pdfInvalidPageNumberLabel" : "Ju lutemi shkruani një numër faqeje të vlefshëm", +"pdfPaginationDialogOkLabel" : "Në rregull", "pdfPaginationDialogCancelLabel" : "ANULON", "passwordDialogHeaderTextLabel" : "Fjalëkalimi i mbrojtur", "passwordDialogContentLabel" : "Futni fjalëkalimin për të hapur këtë skedar PDF", "passwordDialogHintTextLabel" : "Shkruani fjalëkalimin", "passwordDialogInvalidPasswordLabel" : "Fjalëkalim i pavlefshëm", -"pdfPasswordDialogOpenLabel" : "HAPUR", +"pdfPasswordDialogOpenLabel" : "HAP", "pdfPasswordDialogCancelLabel" : "ANULON", -"allowedViewDayLabel" : "ditë", +"pdfSignaturePadDialogHeaderTextLabel" : "Vizatoni nënshkrimin tuaj", +"pdfSignaturePadDialogPenColorLabel" : "Ngjyra e stilolapsit", +"pdfSignaturePadDialogClearLabel" : "QARTË", +"pdfSignaturePadDialogSaveLabel" : "RUAJ", +"pdfTextSelectionMenuCopyLabel" : "Kopjo", +"pdfTextSelectionMenuHighlightLabel" : "Theksoj", +"pdfTextSelectionMenuStrikethroughLabel" : "Strikethrough", +"pdfTextSelectionMenuUnderlineLabel" : "Nënvizoj", +"pdfTextSelectionMenuSquigglyLabel" : "Zigzag", +"allowedViewDayLabel" : "Dita", "allowedViewWeekLabel" : "javë", "allowedViewWorkWeekLabel" : "Java e punës", "allowedViewMonthLabel" : "Muaj", @@ -34,13 +43,13 @@ "safarLabel" : "Safar", "rabi1Label" : "Rabi'ul-evvel", "rabi2Label" : "Rabi' al-thani", -"jumada1Label" : "Xhumada el-evvel", +"jumada1Label" : "Xhumade el-evvel", "jumada2Label" : "Xhumada al-thani", "rajabLabel" : "Rexheb", -"shaabanLabel" : "Sha'ban", +"shaabanLabel" : "Shaban", "ramadanLabel" : "Ramazani", "shawwalLabel" : "Shevali", -"dhualqiLabel" : "Dhul Kida", +"dhualqiLabel" : "Dhul-Kida", "dhualhiLabel" : "Dhul Hixhe", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", @@ -54,6 +63,50 @@ "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-K", "shortDhualhiLabel" : "Dhul-H", -"daySpanCountLabel" : "ditë", -"series" : "Seria" +"daySpanCountLabel" : "Dita", +"series" : "Seria", +"pdfHyperlinkLabel" : "Hapni faqen e internetit", +"pdfHyperlinkContentLabel" : "Dëshironi të hapni faqen në", +"pdfHyperlinkDialogOpenLabel" : "HAPUR", +"pdfHyperlinkDialogCancelLabel" : "ANULON", +"afterDataGridFilteringLabel" : "Pas", +"afterOrEqualDataGridFilteringLabel" : "Pas Ose E barabartë", +"beforeDataGridFilteringLabel" : "Përpara", +"beforeOrEqualDataGridFilteringLabel" : "Para Ose të Barabarta", +"beginsWithDataGridFilteringLabel" : "Fillon me", +"containsDataGridFilteringLabel" : "Përmban", +"doesNotBeginWithDataGridFilteringLabel" : "Nuk Fillon Me", +"doesNotContainDataGridFilteringLabel" : "Nuk permban", +"doesNotEndWithDataGridFilteringLabel" : "Nuk përfundon me", +"doesNotEqualDataGridFilteringLabel" : "Nuk është e barabartë", +"emptyDataGridFilteringLabel" : "Bosh", +"endsWithDataGridFilteringLabel" : "Përfundon me", +"equalsDataGridFilteringLabel" : "Të barabartë", +"greaterThanDataGridFilteringLabel" : "Më e madhe se", +"greaterThanOrEqualDataGridFilteringLabel" : "Më i madh se Ose i barabartë", +"lessThanDataGridFilteringLabel" : "Më pak se", +"lessThanOrEqualDataGridFilteringLabel" : "Më pak se ose e barabartë", +"notEmptyDataGridFilteringLabel" : "Jo bosh", +"notNullDataGridFilteringLabel" : "Jo Null", +"nullDataGridFilteringLabel" : "I pavlefshëm", +"sortSmallestToLargestDataGridFilteringLabel" : "Rendit nga më i vogli në më i madhi", +"sortLargestToSmallestDataGridFilteringLabel" : "Rendit nga më i madhi tek më i vogli", +"sortAToZDataGridFilteringLabel" : "Rendit nga A në Z", +"sortZToADataGridFilteringLabel" : "Rendit Z në A", +"sortOldestToNewestDataGridFilteringLabel" : "Rendit nga më i vjetri tek më i riu", +"sortNewestToOldestDataGridFilteringLabel" : "Rendit nga më i riu tek më i vjetri", +"textFiltersDataGridFilteringLabel" : "Filtrat e tekstit", +"numberFiltersDataGridFilteringLabel" : "Filtrat e numrave", +"dateFiltersDataGridFilteringLabel" : "Filtrat e datës", +"searchDataGridFilteringLabel" : "Kërko", +"noMatchesDataGridFilteringLabel" : "Nuk ka ndeshje", +"okDataGridFilteringLabel" : "Ne rregull", +"cancelDataGridFilteringLabel" : "Anulo", +"showRowsWhereDataGridFilteringLabel" : "Trego rreshtat ku", +"andDataGridFilteringLabel" : "Dhe", +"orDataGridFilteringLabel" : "Ose", +"selectAllDataGridFilteringLabel" : "Selektoj të gjitha", +"sortAndFilterDataGridFilteringLabel" : "Rendit dhe filtro", +"clearFilterDataGridFilteringLabel" : "Pastro filtrin", +"fromDataGridFilteringLabel" : "Nga" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb index cefab2f14..3afd25da2 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb @@ -4,24 +4,33 @@ "ofDataPagerLabel" : "оф", "pagesDataPagerLabel" : "странице", "rowsPerPageDataPagerLabel" : "Редова по страници", -"pdfBookmarksLabel" : "обележивача", +"pdfBookmarksLabel" : "Обележивачи", "pdfNoBookmarksLabel" : "Нема пронађених обележивача", -"pdfScrollStatusOfLabel" : "оф", +"pdfScrollStatusOfLabel" : "од", "pdfGoToPageLabel" : "Иди на страну", "pdfEnterPageNumberLabel" : "Унесите број странице", "pdfInvalidPageNumberLabel" : "Молимо Вас да унесете важећи број", "pdfPaginationDialogOkLabel" : "У реду", -"pdfPaginationDialogCancelLabel" : "ПОНИШТИТИ, ОТКАЗАТИ", +"pdfPaginationDialogCancelLabel" : "ОТКАЖИ", "passwordDialogHeaderTextLabel" : "Заштићено лозинком", "passwordDialogContentLabel" : "Унесите лозинку да бисте отворили ову ПДФ датотеку", "passwordDialogHintTextLabel" : "Унесите лозинку", "passwordDialogInvalidPasswordLabel" : "Неважећа лозинка", -"pdfPasswordDialogOpenLabel" : "ОПЕН", -"pdfPasswordDialogCancelLabel" : "ПОНИШТИТИ, ОТКАЗАТИ", +"pdfPasswordDialogOpenLabel" : "ОТВОРИ", +"pdfPasswordDialogCancelLabel" : "ОТКАЖИ", +"pdfSignaturePadDialogHeaderTextLabel" : "Нацртајте свој потпис", +"pdfSignaturePadDialogPenColorLabel" : "Боја пера", +"pdfSignaturePadDialogClearLabel" : "ЈАСНО", +"pdfSignaturePadDialogSaveLabel" : "САЧУВАТИ", +"pdfTextSelectionMenuCopyLabel" : "Копирај", +"pdfTextSelectionMenuHighlightLabel" : "Истакните", +"pdfTextSelectionMenuStrikethroughLabel" : "Прецртано", +"pdfTextSelectionMenuUnderlineLabel" : "Подвући", +"pdfTextSelectionMenuSquigglyLabel" : "Скуиггли", "allowedViewDayLabel" : "Дан", "allowedViewWeekLabel" : "Недеља", "allowedViewWorkWeekLabel" : "Радна недеља", -"allowedViewMonthLabel" : "Месец дана", +"allowedViewMonthLabel" : "Месец", "allowedViewScheduleLabel" : "Распоред", "allowedViewTimelineDayLabel" : "Дан временске линије", "allowedViewTimelineWeekLabel" : "Тимелине Веек", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Дху'л-К", "shortDhualhiLabel" : "Дху'л-Х", "daySpanCountLabel" : "Дан", -"series" : "Серије" +"series" : "Сериес", +"pdfHyperlinkLabel" : "Отворите веб страницу", +"pdfHyperlinkContentLabel" : "Да ли желите да отворите страницу на", +"pdfHyperlinkDialogOpenLabel" : "ОТВОРИ", +"pdfHyperlinkDialogCancelLabel" : "ОТКАЖИ", +"afterDataGridFilteringLabel" : "После", +"afterOrEqualDataGridFilteringLabel" : "После или једнако", +"beforeDataGridFilteringLabel" : "пре него што", +"beforeOrEqualDataGridFilteringLabel" : "Пре или једнако", +"beginsWithDataGridFilteringLabel" : "Почиње са", +"containsDataGridFilteringLabel" : "Садржи", +"doesNotBeginWithDataGridFilteringLabel" : "Не почиње са", +"doesNotContainDataGridFilteringLabel" : "Не садржи", +"doesNotEndWithDataGridFilteringLabel" : "Не завршава са", +"doesNotEqualDataGridFilteringLabel" : "Није једнако", +"emptyDataGridFilteringLabel" : "Празан", +"endsWithDataGridFilteringLabel" : "Завршава са", +"equalsDataGridFilteringLabel" : "Једнако", +"greaterThanDataGridFilteringLabel" : "Веће од", +"greaterThanOrEqualDataGridFilteringLabel" : "Веће или једнако", +"lessThanDataGridFilteringLabel" : "Мање од", +"lessThanOrEqualDataGridFilteringLabel" : "Мање од или једнако", +"notEmptyDataGridFilteringLabel" : "Није празна", +"notNullDataGridFilteringLabel" : "Не Нулл", +"nullDataGridFilteringLabel" : "Нула", +"sortSmallestToLargestDataGridFilteringLabel" : "Сортирај од најмањег до највећег", +"sortLargestToSmallestDataGridFilteringLabel" : "Сортирај од највећег до најмањег", +"sortAToZDataGridFilteringLabel" : "Сортирај од А до З", +"sortZToADataGridFilteringLabel" : "Сортирај од З до А", +"sortOldestToNewestDataGridFilteringLabel" : "Сортирај од најстаријих до најновијих", +"sortNewestToOldestDataGridFilteringLabel" : "Сортирај од најновијег до најстаријег", +"textFiltersDataGridFilteringLabel" : "Филтери за текст", +"numberFiltersDataGridFilteringLabel" : "Бројчани филтери", +"dateFiltersDataGridFilteringLabel" : "Дате Филтерс", +"searchDataGridFilteringLabel" : "Претрага", +"noMatchesDataGridFilteringLabel" : "Нема резултата", +"okDataGridFilteringLabel" : "У реду", +"cancelDataGridFilteringLabel" : "Поништити, отказати", +"showRowsWhereDataGridFilteringLabel" : "Покажи редове где", +"andDataGridFilteringLabel" : "И", +"orDataGridFilteringLabel" : "Ор", +"selectAllDataGridFilteringLabel" : "Изабери све", +"sortAndFilterDataGridFilteringLabel" : "Сортирај и филтрирај", +"clearFilterDataGridFilteringLabel" : "Обриши филтер", +"fromDataGridFilteringLabel" : "Од" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb index 07c50e6b9..ce7b76961 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb @@ -9,15 +9,24 @@ "pdfScrollStatusOfLabel" : "av", "pdfGoToPageLabel" : "Gå till sidan", "pdfEnterPageNumberLabel" : "Ange sidnummer", -"pdfInvalidPageNumberLabel" : "var vänlig skriv in ett giltigt nummer", +"pdfInvalidPageNumberLabel" : "Var vänlig skriv in ett giltigt nummer", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "ANNULLERA", -"passwordDialogHeaderTextLabel" : "Lösenord skyddat", +"pdfPaginationDialogCancelLabel" : "AVBRYT", +"passwordDialogHeaderTextLabel" : "Lösenordsskyddat", "passwordDialogContentLabel" : "Ange lösenordet för att öppna denna PDF-fil", "passwordDialogHintTextLabel" : "Skriv in lösenord", -"passwordDialogInvalidPasswordLabel" : "felaktigt lösenord", -"pdfPasswordDialogOpenLabel" : "ÖPPET", -"pdfPasswordDialogCancelLabel" : "ANNULLERA", +"passwordDialogInvalidPasswordLabel" : "Felaktigt lösenord", +"pdfPasswordDialogOpenLabel" : "ÖPPNA", +"pdfPasswordDialogCancelLabel" : "AVBRYT", +"pdfSignaturePadDialogHeaderTextLabel" : "Rita din signatur", +"pdfSignaturePadDialogPenColorLabel" : "Färg på pennan", +"pdfSignaturePadDialogClearLabel" : "KLAR", +"pdfSignaturePadDialogSaveLabel" : "SPARA", +"pdfTextSelectionMenuCopyLabel" : "Kopiera", +"pdfTextSelectionMenuHighlightLabel" : "Markera", +"pdfTextSelectionMenuStrikethroughLabel" : "Genomstruken", +"pdfTextSelectionMenuUnderlineLabel" : "Understrykning", +"pdfTextSelectionMenuSquigglyLabel" : "Snyggt", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Vecka", "allowedViewWorkWeekLabel" : "Arbetsvecka", @@ -25,9 +34,9 @@ "allowedViewScheduleLabel" : "Schema", "allowedViewTimelineDayLabel" : "Tidslinjedagen", "allowedViewTimelineWeekLabel" : "Tidslinjevecka", -"allowedViewTimelineWorkWeekLabel" : "Tidslinje Arbetsvecka", +"allowedViewTimelineWorkWeekLabel" : "Tidslinje arbetsvecka", "allowedViewTimelineMonthLabel" : "Tidslinje månad", -"todayLabel" : "I dag", +"todayLabel" : "Idag", "weeknumberLabel" : "Vecka", "allDayLabel" : "Hela dagen", "muharramLabel" : "Muharram", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dag", -"series" : "Serier" +"series" : "Serier", +"pdfHyperlinkLabel" : "Öppna webbsidan", +"pdfHyperlinkContentLabel" : "Vill du öppna sidan på", +"pdfHyperlinkDialogOpenLabel" : "ÖPPNA", +"pdfHyperlinkDialogCancelLabel" : "AVBRYT", +"afterDataGridFilteringLabel" : "Efter", +"afterOrEqualDataGridFilteringLabel" : "Efter Eller Lika", +"beforeDataGridFilteringLabel" : "Innan", +"beforeOrEqualDataGridFilteringLabel" : "Före Eller Lika", +"beginsWithDataGridFilteringLabel" : "Börjar med", +"containsDataGridFilteringLabel" : "Innehåller", +"doesNotBeginWithDataGridFilteringLabel" : "Börjar inte med", +"doesNotContainDataGridFilteringLabel" : "Innehåller inte", +"doesNotEndWithDataGridFilteringLabel" : "Slutar inte med", +"doesNotEqualDataGridFilteringLabel" : "Är inte lika med", +"emptyDataGridFilteringLabel" : "Tömma", +"endsWithDataGridFilteringLabel" : "Slutar med", +"equalsDataGridFilteringLabel" : "Lika", +"greaterThanDataGridFilteringLabel" : "Större än", +"greaterThanOrEqualDataGridFilteringLabel" : "Större än eller lika med", +"lessThanDataGridFilteringLabel" : "Mindre än", +"lessThanOrEqualDataGridFilteringLabel" : "Mindre än eller lika", +"notEmptyDataGridFilteringLabel" : "Inte tom", +"notNullDataGridFilteringLabel" : "Inte Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Sortera minsta till största", +"sortLargestToSmallestDataGridFilteringLabel" : "Sortera Störst Till Minst", +"sortAToZDataGridFilteringLabel" : "Sortera A till Ö", +"sortZToADataGridFilteringLabel" : "Sortera från Z till A", +"sortOldestToNewestDataGridFilteringLabel" : "Sortera äldst till nyaste", +"sortNewestToOldestDataGridFilteringLabel" : "Sortera Nyast Till Äldst", +"textFiltersDataGridFilteringLabel" : "Textfilter", +"numberFiltersDataGridFilteringLabel" : "Nummerfilter", +"dateFiltersDataGridFilteringLabel" : "Datumfilter", +"searchDataGridFilteringLabel" : "Sök", +"noMatchesDataGridFilteringLabel" : "Inga träffar", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Avbryt", +"showRowsWhereDataGridFilteringLabel" : "Visa rader var", +"andDataGridFilteringLabel" : "Och", +"orDataGridFilteringLabel" : "Eller", +"selectAllDataGridFilteringLabel" : "Välj alla", +"sortAndFilterDataGridFilteringLabel" : "Sortera och filtrera", +"clearFilterDataGridFilteringLabel" : "Rensa filtret", +"fromDataGridFilteringLabel" : "Från" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb index 559a9a156..bba74079d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb @@ -7,17 +7,26 @@ "pdfBookmarksLabel" : "Alamisho", "pdfNoBookmarksLabel" : "Hakuna alamisho zilizopatikana", "pdfScrollStatusOfLabel" : "ya", -"pdfGoToPageLabel" : "Nenda kwa ukurasa", +"pdfGoToPageLabel" : "Nenda kwenye ukurasa", "pdfEnterPageNumberLabel" : "Ingiza nambari ya ukurasa", "pdfInvalidPageNumberLabel" : "Tafadhali weka nambari halali", -"pdfPaginationDialogOkLabel" : "sawa", -"pdfPaginationDialogCancelLabel" : "GHAIRI", +"pdfPaginationDialogOkLabel" : "Sawa", +"pdfPaginationDialogCancelLabel" : "Ghairi", "passwordDialogHeaderTextLabel" : "Nenosiri Limelindwa", "passwordDialogContentLabel" : "Weka nenosiri ili kufungua faili hii ya PDF", "passwordDialogHintTextLabel" : "Weka Nenosiri", "passwordDialogInvalidPasswordLabel" : "Nenosiri batili", "pdfPasswordDialogOpenLabel" : "FUNGUA", -"pdfPasswordDialogCancelLabel" : "GHAIRI", +"pdfPasswordDialogCancelLabel" : "Ghairi", +"pdfSignaturePadDialogHeaderTextLabel" : "Chora saini yako", +"pdfSignaturePadDialogPenColorLabel" : "Rangi ya kalamu", +"pdfSignaturePadDialogClearLabel" : "WAZI", +"pdfSignaturePadDialogSaveLabel" : "HIFADHI", +"pdfTextSelectionMenuCopyLabel" : "Nakili", +"pdfTextSelectionMenuHighlightLabel" : "Kuonyesha", +"pdfTextSelectionMenuStrikethroughLabel" : "Mgomo", +"pdfTextSelectionMenuUnderlineLabel" : "Piga mstari", +"pdfTextSelectionMenuSquigglyLabel" : "Kwa mbwembwe", "allowedViewDayLabel" : "Siku", "allowedViewWeekLabel" : "Wiki", "allowedViewWorkWeekLabel" : "Wiki ya Kazi", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Siku", -"series" : "Msururu" +"series" : "Msururu", +"pdfHyperlinkLabel" : "Fungua Ukurasa wa Wavuti", +"pdfHyperlinkContentLabel" : "Je, unataka kufungua ukurasa katika", +"pdfHyperlinkDialogOpenLabel" : "FUNGUA", +"pdfHyperlinkDialogCancelLabel" : "GHAIRI", +"afterDataGridFilteringLabel" : "Baada ya", +"afterOrEqualDataGridFilteringLabel" : "Baada ya Au Sawa", +"beforeDataGridFilteringLabel" : "Kabla", +"beforeOrEqualDataGridFilteringLabel" : "Kabla Au Sawa", +"beginsWithDataGridFilteringLabel" : "Huanza Na", +"containsDataGridFilteringLabel" : "Ina", +"doesNotBeginWithDataGridFilteringLabel" : "Huanza Na", +"doesNotContainDataGridFilteringLabel" : "Haina", +"doesNotEndWithDataGridFilteringLabel" : "Haiishii Na", +"doesNotEqualDataGridFilteringLabel" : "Hailingani", +"emptyDataGridFilteringLabel" : "Tupu", +"endsWithDataGridFilteringLabel" : "Inaisha Na", +"equalsDataGridFilteringLabel" : "Sawa", +"greaterThanDataGridFilteringLabel" : "Kubwa kuliko", +"greaterThanOrEqualDataGridFilteringLabel" : "Kubwa Kuliko Au Sawa", +"lessThanDataGridFilteringLabel" : "Chini ya", +"lessThanOrEqualDataGridFilteringLabel" : "Chini ya Au Sawa", +"notEmptyDataGridFilteringLabel" : "Sio Tupu", +"notNullDataGridFilteringLabel" : "Sio Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Panga Ndogo Kwa Kubwa Zaidi", +"sortLargestToSmallestDataGridFilteringLabel" : "Panga Kubwa Hadi Ndogo", +"sortAToZDataGridFilteringLabel" : "Panga A hadi Z", +"sortZToADataGridFilteringLabel" : "Panga Z hadi A", +"sortOldestToNewestDataGridFilteringLabel" : "Panga Kongwe Kwa Mpya Zaidi", +"sortNewestToOldestDataGridFilteringLabel" : "Panga Mpya Zaidi Kwa Kongwe Zaidi", +"textFiltersDataGridFilteringLabel" : "Vichujio vya Maandishi", +"numberFiltersDataGridFilteringLabel" : "Vichujio vya Nambari", +"dateFiltersDataGridFilteringLabel" : "Vichujio vya Tarehe", +"searchDataGridFilteringLabel" : "Tafuta", +"noMatchesDataGridFilteringLabel" : "Hakuna zinazolingana", +"okDataGridFilteringLabel" : "sawa", +"cancelDataGridFilteringLabel" : "Ghairi", +"showRowsWhereDataGridFilteringLabel" : "Onyesha safu mlalo wapi", +"andDataGridFilteringLabel" : "Na", +"orDataGridFilteringLabel" : "Au", +"selectAllDataGridFilteringLabel" : "Chagua Zote", +"sortAndFilterDataGridFilteringLabel" : "Panga na Chuja", +"clearFilterDataGridFilteringLabel" : "Futa Kichujio", +"fromDataGridFilteringLabel" : "Kutoka" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb index 5ba70cba6..d1368f4d3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb @@ -16,14 +16,23 @@ "passwordDialogContentLabel" : "இந்த PDF கோப்பைத் திறக்க கடவுச்சொல்லை உள்ளிடவும்", "passwordDialogHintTextLabel" : "கடவுச்சொல்லை உள்ளிடவும்", "passwordDialogInvalidPasswordLabel" : "தவறான கடவுச்சொல்", -"pdfPasswordDialogOpenLabel" : "திறந்த", +"pdfPasswordDialogOpenLabel" : "திற", "pdfPasswordDialogCancelLabel" : "ரத்துசெய்", +"pdfSignaturePadDialogHeaderTextLabel" : "உங்கள் கையொப்பத்தை வரையவும்", +"pdfSignaturePadDialogPenColorLabel" : "பேனா நிறம்", +"pdfSignaturePadDialogClearLabel" : "அழி", +"pdfSignaturePadDialogSaveLabel" : "சேமிக்கவும்", +"pdfTextSelectionMenuCopyLabel" : "நகலெடுக்கவும்", +"pdfTextSelectionMenuHighlightLabel" : "முன்னிலைப்படுத்த", +"pdfTextSelectionMenuStrikethroughLabel" : "வேலைநிறுத்தம்", +"pdfTextSelectionMenuUnderlineLabel" : "அடிக்கோடு", +"pdfTextSelectionMenuSquigglyLabel" : "ஸ்க்விக்லி", "allowedViewDayLabel" : "நாள்", "allowedViewWeekLabel" : "வாரம்", "allowedViewWorkWeekLabel" : "வேலை வாரம்", "allowedViewMonthLabel" : "மாதம்", "allowedViewScheduleLabel" : "அட்டவணை", -"allowedViewTimelineDayLabel" : "காலவரிசை நாள்", +"allowedViewTimelineDayLabel" : "காலக்கெடு நாள்", "allowedViewTimelineWeekLabel" : "காலவரிசை வாரம்", "allowedViewTimelineWorkWeekLabel" : "காலக்கெடு வேலை வாரம்", "allowedViewTimelineMonthLabel" : "காலவரிசை மாதம்", @@ -40,7 +49,7 @@ "shaabanLabel" : "ஷஅபான்", "ramadanLabel" : "ரமலான்", "shawwalLabel" : "ஷவ்வால்", -"dhualqiLabel" : "து அல்-கிடா", +"dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "து அல்-ஹிஜ்ஜா", "shortMuharramLabel" : "முஹ்.", "shortSafarLabel" : "Saf.", @@ -51,9 +60,53 @@ "shortRajabLabel" : "ராஜ்.", "shortShaabanLabel" : "ஷா.", "shortRamadanLabel" : "ரேம்.", -"shortShawwalLabel" : "ஷா.", +"shortShawwalLabel" : "ஷா", "shortDhualqiLabel" : "துல்-கே", "shortDhualhiLabel" : "துல்-எச்", "daySpanCountLabel" : "நாள்", -"series" : "தொடர்" +"series" : "தொடர்", +"pdfHyperlinkLabel" : "இணையப் பக்கத்தைத் திற", +"pdfHyperlinkContentLabel" : "நீங்கள் பக்கத்தைத் திறக்க விரும்புகிறீர்களா?", +"pdfHyperlinkDialogOpenLabel" : "திற", +"pdfHyperlinkDialogCancelLabel" : "ரத்துசெய்", +"afterDataGridFilteringLabel" : "பிறகு", +"afterOrEqualDataGridFilteringLabel" : "பிறகு அல்லது சமம்", +"beforeDataGridFilteringLabel" : "முன்பு", +"beforeOrEqualDataGridFilteringLabel" : "முன் அல்லது சமம்", +"beginsWithDataGridFilteringLabel" : "உடன் தொடங்குகிறது", +"containsDataGridFilteringLabel" : "கொண்டுள்ளது", +"doesNotBeginWithDataGridFilteringLabel" : "உடன் தொடங்கவில்லை", +"doesNotContainDataGridFilteringLabel" : "கொண்டிருக்கும் இல்லை", +"doesNotEndWithDataGridFilteringLabel" : "உடன் முடிவடையவில்லை", +"doesNotEqualDataGridFilteringLabel" : "சமமாக இல்லை", +"emptyDataGridFilteringLabel" : "காலியாக", +"endsWithDataGridFilteringLabel" : "உடன் முடிகிறது", +"equalsDataGridFilteringLabel" : "சமம்", +"greaterThanDataGridFilteringLabel" : "விட பெரியது", +"greaterThanOrEqualDataGridFilteringLabel" : "பெரியது அல்லது சமமானது", +"lessThanDataGridFilteringLabel" : "விட குறைவாக", +"lessThanOrEqualDataGridFilteringLabel" : "குறைவானது அல்லது சமமானது", +"notEmptyDataGridFilteringLabel" : "காலியாக இல்லை", +"notNullDataGridFilteringLabel" : "பூஜ்யமாக இல்லை", +"nullDataGridFilteringLabel" : "ஏதுமில்லை", +"sortSmallestToLargestDataGridFilteringLabel" : "சிறியது முதல் பெரியது வரை வரிசைப்படுத்தவும்", +"sortLargestToSmallestDataGridFilteringLabel" : "பெரியது முதல் சிறியது வரை வரிசைப்படுத்தவும்", +"sortAToZDataGridFilteringLabel" : "A முதல் Z வரை வரிசைப்படுத்தவும்", +"sortZToADataGridFilteringLabel" : "Z முதல் A வரை வரிசைப்படுத்தவும்", +"sortOldestToNewestDataGridFilteringLabel" : "பழமையானது முதல் புதியது வரை வரிசைப்படுத்தவும்", +"sortNewestToOldestDataGridFilteringLabel" : "புதியது முதல் பழையது என வரிசைப்படுத்தவும்", +"textFiltersDataGridFilteringLabel" : "உரை வடிப்பான்கள்", +"numberFiltersDataGridFilteringLabel" : "எண் வடிப்பான்கள்", +"dateFiltersDataGridFilteringLabel" : "தேதி வடிகட்டிகள்", +"searchDataGridFilteringLabel" : "தேடு", +"noMatchesDataGridFilteringLabel" : "பொருத்தங்கள் இல்லை", +"okDataGridFilteringLabel" : "சரி", +"cancelDataGridFilteringLabel" : "ரத்து செய்", +"showRowsWhereDataGridFilteringLabel" : "எங்கே வரிசைகளைக் காட்டு", +"andDataGridFilteringLabel" : "மற்றும்", +"orDataGridFilteringLabel" : "அல்லது", +"selectAllDataGridFilteringLabel" : "அனைத்தையும் தெரிவுசெய்", +"sortAndFilterDataGridFilteringLabel" : "வரிசைப்படுத்தி வடிகட்டவும்", +"clearFilterDataGridFilteringLabel" : "வடிகட்டியை அழி", +"fromDataGridFilteringLabel" : "இருந்து" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb index f4ce93a3c..fd96cb916 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "పుటకు వెళ్ళు", "pdfEnterPageNumberLabel" : "పేజీ సంఖ్యను నమోదు చేయండి", "pdfInvalidPageNumberLabel" : "దయచేసి చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి", -"pdfPaginationDialogOkLabel" : "అలాగే", +"pdfPaginationDialogOkLabel" : "సరే", "pdfPaginationDialogCancelLabel" : "రద్దు చేయండి", "passwordDialogHeaderTextLabel" : "పాస్‌వర్డ్ రక్షించబడింది", "passwordDialogContentLabel" : "ఈ PDF ఫైల్‌ను తెరవడానికి పాస్‌వర్డ్‌ను నమోదు చేయండి", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "చెల్లని పాస్వర్డ్", "pdfPasswordDialogOpenLabel" : "తెరవండి", "pdfPasswordDialogCancelLabel" : "రద్దు చేయండి", +"pdfSignaturePadDialogHeaderTextLabel" : "మీ సంతకాన్ని గీయండి", +"pdfSignaturePadDialogPenColorLabel" : "పెన్ రంగు", +"pdfSignaturePadDialogClearLabel" : "క్లియర్", +"pdfSignaturePadDialogSaveLabel" : "భద్రపరచండి", +"pdfTextSelectionMenuCopyLabel" : "నకలు", +"pdfTextSelectionMenuHighlightLabel" : "హైలైట్", +"pdfTextSelectionMenuStrikethroughLabel" : "స్ట్రైక్‌త్రూ", +"pdfTextSelectionMenuUnderlineLabel" : "అండర్లైన్", +"pdfTextSelectionMenuSquigglyLabel" : "స్క్విగ్లీ", "allowedViewDayLabel" : "రోజు", "allowedViewWeekLabel" : "వారం", "allowedViewWorkWeekLabel" : "పని వారం", @@ -40,7 +49,7 @@ "shaabanLabel" : "షాబాన్", "ramadanLabel" : "రంజాన్", "shawwalLabel" : "షవ్వాల్", -"dhualqiLabel" : "ధు అల్-ఖైదా", +"dhualqiLabel" : "ధు అల్-కి'దా", "dhualhiLabel" : "ధు అల్-హిజ్జా", "shortMuharramLabel" : "ముహ్.", "shortSafarLabel" : "సాఫ్.", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ధుల్-క్యూ", "shortDhualhiLabel" : "ధుల్-హెచ్", "daySpanCountLabel" : "రోజు", -"series" : "సిరీస్" +"series" : "సిరీస్", +"pdfHyperlinkLabel" : "వెబ్ పేజీని తెరవండి", +"pdfHyperlinkContentLabel" : "మీరు పేజీని ఇక్కడ తెరవాలనుకుంటున్నారా", +"pdfHyperlinkDialogOpenLabel" : "తెరవండి", +"pdfHyperlinkDialogCancelLabel" : "రద్దు చేయండి", +"afterDataGridFilteringLabel" : "తర్వాత", +"afterOrEqualDataGridFilteringLabel" : "తర్వాత లేదా సమానం", +"beforeDataGridFilteringLabel" : "ముందు", +"beforeOrEqualDataGridFilteringLabel" : "ముందు లేదా సమానం", +"beginsWithDataGridFilteringLabel" : "తో ప్రారంభమవుతుంది", +"containsDataGridFilteringLabel" : "కలిగి ఉంది", +"doesNotBeginWithDataGridFilteringLabel" : "తో ప్రారంభం కాదు", +"doesNotContainDataGridFilteringLabel" : "దింట్లో ఉండదు", +"doesNotEndWithDataGridFilteringLabel" : "దీనితో అంతం కాదు", +"doesNotEqualDataGridFilteringLabel" : "సమానం కాదు", +"emptyDataGridFilteringLabel" : "ఖాళీ", +"endsWithDataGridFilteringLabel" : "తో ముగుస్తుంది", +"equalsDataGridFilteringLabel" : "సమానం", +"greaterThanDataGridFilteringLabel" : "కంటే ఎక్కువ", +"greaterThanOrEqualDataGridFilteringLabel" : "గ్రేటర్ దాన్ లేదా ఈక్వల్", +"lessThanDataGridFilteringLabel" : "కంటే తక్కువ", +"lessThanOrEqualDataGridFilteringLabel" : "తక్కువ లేదా సమానం", +"notEmptyDataGridFilteringLabel" : "ఖాళీ కాదు", +"notNullDataGridFilteringLabel" : "శూన్యం కాదు", +"nullDataGridFilteringLabel" : "శూన్య", +"sortSmallestToLargestDataGridFilteringLabel" : "చిన్నది నుండి పెద్దది వరకు క్రమబద్ధీకరించండి", +"sortLargestToSmallestDataGridFilteringLabel" : "పెద్దది నుండి చిన్నది వరకు క్రమబద్ధీకరించండి", +"sortAToZDataGridFilteringLabel" : "A నుండి Z వరకు క్రమబద్ధీకరించండి", +"sortZToADataGridFilteringLabel" : "Z నుండి A వరకు క్రమబద్ధీకరించండి", +"sortOldestToNewestDataGridFilteringLabel" : "పాతది నుండి కొత్తది వరకు క్రమబద్ధీకరించండి", +"sortNewestToOldestDataGridFilteringLabel" : "సరికొత్తది నుండి పాతది వరకు క్రమబద్ధీకరించండి", +"textFiltersDataGridFilteringLabel" : "టెక్స్ట్ ఫిల్టర్లు", +"numberFiltersDataGridFilteringLabel" : "సంఖ్య ఫిల్టర్లు", +"dateFiltersDataGridFilteringLabel" : "తేదీ ఫిల్టర్లు", +"searchDataGridFilteringLabel" : "వెతకండి", +"noMatchesDataGridFilteringLabel" : "మ్యాచ్‌లు లేవు", +"okDataGridFilteringLabel" : "అలాగే", +"cancelDataGridFilteringLabel" : "రద్దు చేయండి", +"showRowsWhereDataGridFilteringLabel" : "అడ్డు వరుసలను ఎక్కడ చూపించు", +"andDataGridFilteringLabel" : "మరియు", +"orDataGridFilteringLabel" : "లేదా", +"selectAllDataGridFilteringLabel" : "అన్ని ఎంచుకోండి", +"sortAndFilterDataGridFilteringLabel" : "క్రమబద్ధీకరించండి మరియు ఫిల్టర్ చేయండి", +"clearFilterDataGridFilteringLabel" : "ఫిల్టర్‌ని క్లియర్ చేయండి", +"fromDataGridFilteringLabel" : "నుండి" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb index 50572ed20..b744816bc 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb @@ -1,15 +1,15 @@ { -"noSelectedDateCalendarLabel" : "ไม่ได้เลือกวันที่", +"noSelectedDateCalendarLabel" : "ไม่มีวันที่เลือก", "noEventsCalendarLabel" : "ไม่มีกิจกรรม", "ofDataPagerLabel" : "ของ", "pagesDataPagerLabel" : "หน้า", "rowsPerPageDataPagerLabel" : "แถวต่อหน้า", "pdfBookmarksLabel" : "ที่คั่นหนังสือ", -"pdfNoBookmarksLabel" : "ไม่พบบุ๊คมาร์ค", +"pdfNoBookmarksLabel" : "ไม่พบบุ๊กมาร์ก", "pdfScrollStatusOfLabel" : "ของ", "pdfGoToPageLabel" : "ไปที่หน้า", "pdfEnterPageNumberLabel" : "ใส่เลขหน้า", -"pdfInvalidPageNumberLabel" : "กรุณาใส่ตัวเลขที่ถูกต้อง", +"pdfInvalidPageNumberLabel" : "โปรดป้อนหมายเลขที่ถูกต้อง", "pdfPaginationDialogOkLabel" : "ตกลง", "pdfPaginationDialogCancelLabel" : "ยกเลิก", "passwordDialogHeaderTextLabel" : "ป้องกันด้วยพาสเวิร์ด", @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "รหัสผ่านไม่ถูกต้อง", "pdfPasswordDialogOpenLabel" : "เปิด", "pdfPasswordDialogCancelLabel" : "ยกเลิก", +"pdfSignaturePadDialogHeaderTextLabel" : "วาดลายเซ็นของคุณ", +"pdfSignaturePadDialogPenColorLabel" : "ปากกาสี", +"pdfSignaturePadDialogClearLabel" : "ล้างข้อมูล", +"pdfSignaturePadDialogSaveLabel" : "บันทึก", +"pdfTextSelectionMenuCopyLabel" : "สำเนา", +"pdfTextSelectionMenuHighlightLabel" : "ไฮไลท์", +"pdfTextSelectionMenuStrikethroughLabel" : "ขีดทับ", +"pdfTextSelectionMenuUnderlineLabel" : "ขีดเส้นใต้", +"pdfTextSelectionMenuSquigglyLabel" : "ไก่เขี่ย", "allowedViewDayLabel" : "วัน", "allowedViewWeekLabel" : "สัปดาห์", "allowedViewWorkWeekLabel" : "สัปดาห์การทำงาน", @@ -25,35 +34,79 @@ "allowedViewScheduleLabel" : "กำหนดการ", "allowedViewTimelineDayLabel" : "วันไทม์ไลน์", "allowedViewTimelineWeekLabel" : "ไทม์ไลน์สัปดาห์", -"allowedViewTimelineWorkWeekLabel" : "ไทม์ไลน์งานสัปดาห์", +"allowedViewTimelineWorkWeekLabel" : "ไทม์ไลน์สัปดาห์การทำงาน", "allowedViewTimelineMonthLabel" : "ไทม์ไลน์เดือน", "todayLabel" : "วันนี้", "weeknumberLabel" : "สัปดาห์", "allDayLabel" : "ทั้งวัน", -"muharramLabel" : "มูฮัรรอม", +"muharramLabel" : "มุฮัรรอม", "safarLabel" : "ซาฟาร์", -"rabi1Label" : "เราะบี อัลเอาวัล", -"rabi2Label" : "รอบีอัล-ธานี", +"rabi1Label" : "รอบิอัลเอาวัล", +"rabi2Label" : "รอบิอัล-ธานี", "jumada1Label" : "ญุมาดา อัลเอาวัล", -"jumada2Label" : "Jumada al-thani", -"rajabLabel" : "ราชภัฏ", +"jumada2Label" : "ญุมาดา อัล-ธานี", +"rajabLabel" : "ราชาบ", "shaabanLabel" : "ชะอฺบาน", -"ramadanLabel" : "รอมฎอน", +"ramadanLabel" : "เดือนรอมฎอน", "shawwalLabel" : "เชาวาล", -"dhualqiLabel" : "ญุมาดา อัล-ธานี", -"dhualhiLabel" : "ดูอัลฮิจญะฮ์", -"shortMuharramLabel" : "มุ้ย.", -"shortSafarLabel" : "เซฟ.", -"shortRabi1Label" : "รบี. ฉัน", -"shortRabi2Label" : "รบี. II", -"shortJumada1Label" : "จั้ม. ฉัน", -"shortJumada2Label" : "จั้ม. II", -"shortRajabLabel" : "ราช.", -"shortShaabanLabel" : "ชา.", +"dhualqiLabel" : "ดุอัลกิดาห์", +"dhualhiLabel" : "ดุอัลฮิจญะห์", +"shortMuharramLabel" : "มึ.", +"shortSafarLabel" : "ปลอดภัย", +"shortRabi1Label" : "ระบี. ฉัน", +"shortRabi2Label" : "ระบี. ครั้งที่สอง", +"shortJumada1Label" : "จุ๋ม. ฉัน", +"shortJumada2Label" : "จุ๋ม. ครั้งที่สอง", +"shortRajabLabel" : "ราชา", +"shortShaabanLabel" : "ชา", "shortRamadanLabel" : "แกะ.", -"shortShawwalLabel" : "ชอว์.", +"shortShawwalLabel" : "ชอว์", "shortDhualqiLabel" : "ดุล-คิว", -"shortDhualhiLabel" : "ดุลฮัก", +"shortDhualhiLabel" : "ดุอัล-เอช", "daySpanCountLabel" : "วัน", -"series" : "ชุด" +"series" : "ชุด", +"pdfHyperlinkLabel" : "เปิดหน้าเว็บ", +"pdfHyperlinkContentLabel" : "คุณต้องการเปิดเพจที่", +"pdfHyperlinkDialogOpenLabel" : "เปิด", +"pdfHyperlinkDialogCancelLabel" : "ยกเลิก", +"afterDataGridFilteringLabel" : "หลังจาก", +"afterOrEqualDataGridFilteringLabel" : "หลังจากหรือเท่ากับ", +"beforeDataGridFilteringLabel" : "ก่อน", +"beforeOrEqualDataGridFilteringLabel" : "ก่อนหรือเท่ากับ", +"beginsWithDataGridFilteringLabel" : "เริ่มต้นด้วย", +"containsDataGridFilteringLabel" : "ประกอบด้วย", +"doesNotBeginWithDataGridFilteringLabel" : "ไม่ได้ขึ้นต้นด้วย", +"doesNotContainDataGridFilteringLabel" : "ไม่มี", +"doesNotEndWithDataGridFilteringLabel" : "ไม่สิ้นสุดด้วย", +"doesNotEqualDataGridFilteringLabel" : "ไม่เท่าเทียมกัน", +"emptyDataGridFilteringLabel" : "ว่างเปล่า", +"endsWithDataGridFilteringLabel" : "ลงท้ายด้วย", +"equalsDataGridFilteringLabel" : "เท่ากับ", +"greaterThanDataGridFilteringLabel" : "มากกว่า", +"greaterThanOrEqualDataGridFilteringLabel" : "มากกว่าหรือเท่ากับ", +"lessThanDataGridFilteringLabel" : "น้อยกว่า", +"lessThanOrEqualDataGridFilteringLabel" : "น้อยกว่าหรือเท่ากับ", +"notEmptyDataGridFilteringLabel" : "ไม่ว่างเปล่า", +"notNullDataGridFilteringLabel" : "ไม่เป็นโมฆะ", +"nullDataGridFilteringLabel" : "โมฆะ", +"sortSmallestToLargestDataGridFilteringLabel" : "เรียงลำดับจากน้อยไปมาก", +"sortLargestToSmallestDataGridFilteringLabel" : "เรียงจากมากไปน้อย", +"sortAToZDataGridFilteringLabel" : "เรียง A ถึง Z", +"sortZToADataGridFilteringLabel" : "เรียง Z ถึง A", +"sortOldestToNewestDataGridFilteringLabel" : "เรียงจากเก่าสุดไปใหม่สุด", +"sortNewestToOldestDataGridFilteringLabel" : "เรียงลำดับใหม่ที่สุดไปเก่าที่สุด", +"textFiltersDataGridFilteringLabel" : "ตัวกรองข้อความ", +"numberFiltersDataGridFilteringLabel" : "ตัวกรองตัวเลข", +"dateFiltersDataGridFilteringLabel" : "ตัวกรองวันที่", +"searchDataGridFilteringLabel" : "ค้นหา", +"noMatchesDataGridFilteringLabel" : "ไม่ตรงกัน", +"okDataGridFilteringLabel" : "ตกลง", +"cancelDataGridFilteringLabel" : "ยกเลิก", +"showRowsWhereDataGridFilteringLabel" : "แสดงแถวที่", +"andDataGridFilteringLabel" : "และ", +"orDataGridFilteringLabel" : "หรือ", +"selectAllDataGridFilteringLabel" : "เลือกทั้งหมด", +"sortAndFilterDataGridFilteringLabel" : "จัดเรียงและกรอง", +"clearFilterDataGridFilteringLabel" : "ล้างตัวกรอง", +"fromDataGridFilteringLabel" : "จาก" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb index 9c1457cf8..75fd95525 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb @@ -11,13 +11,22 @@ "pdfEnterPageNumberLabel" : "Ipasok ang numero ng pahina", "pdfInvalidPageNumberLabel" : "Mangyaring magpasok ng wastong numero", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "KANSELAHIN", +"pdfPaginationDialogCancelLabel" : "Kanselahin", "passwordDialogHeaderTextLabel" : "Pinoprotektahan ng Password", "passwordDialogContentLabel" : "Ilagay ang password para buksan ang PDF file na ito", "passwordDialogHintTextLabel" : "Ilagay ang password", "passwordDialogInvalidPasswordLabel" : "di wastong password", -"pdfPasswordDialogOpenLabel" : "BUKAS", -"pdfPasswordDialogCancelLabel" : "KANSELAHIN", +"pdfPasswordDialogOpenLabel" : "Bukas", +"pdfPasswordDialogCancelLabel" : "Kanselahin", +"pdfSignaturePadDialogHeaderTextLabel" : "Iguhit ang iyong lagda", +"pdfSignaturePadDialogPenColorLabel" : "Kulay ng Panulat", +"pdfSignaturePadDialogClearLabel" : "Maliwanag", +"pdfSignaturePadDialogSaveLabel" : "Mag-ipon", +"pdfTextSelectionMenuCopyLabel" : "Kopya", +"pdfTextSelectionMenuHighlightLabel" : "I-highlight", +"pdfTextSelectionMenuStrikethroughLabel" : "Strikethrough", +"pdfTextSelectionMenuUnderlineLabel" : "Salungguhit", +"pdfTextSelectionMenuSquigglyLabel" : "Guhit-guhit", "allowedViewDayLabel" : "Araw", "allowedViewWeekLabel" : "Linggo", "allowedViewWorkWeekLabel" : "Linggo ng trabaho", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Araw", -"series" : "Serye" +"series" : "Serye", +"pdfHyperlinkLabel" : "Buksan ang Web Page", +"pdfHyperlinkContentLabel" : "Gusto mo bang buksan ang pahina sa", +"pdfHyperlinkDialogOpenLabel" : "Bukas", +"pdfHyperlinkDialogCancelLabel" : "Kanselahin", +"afterDataGridFilteringLabel" : "Pagkatapos", +"afterOrEqualDataGridFilteringLabel" : "Pagkatapos o Pantay", +"beforeDataGridFilteringLabel" : "Bago", +"beforeOrEqualDataGridFilteringLabel" : "Bago O Kapantay", +"beginsWithDataGridFilteringLabel" : "Nagsisimula sa", +"containsDataGridFilteringLabel" : "Naglalaman", +"doesNotBeginWithDataGridFilteringLabel" : "Hindi Nagsisimula Sa", +"doesNotContainDataGridFilteringLabel" : "Hindi Naglalaman", +"doesNotEndWithDataGridFilteringLabel" : "Hindi Nagtatapos Sa", +"doesNotEqualDataGridFilteringLabel" : "Ay hindi katumbas ng", +"emptyDataGridFilteringLabel" : "Walang laman", +"endsWithDataGridFilteringLabel" : "Nagtatapos Sa", +"equalsDataGridFilteringLabel" : "katumbas", +"greaterThanDataGridFilteringLabel" : "Mahigit sa", +"greaterThanOrEqualDataGridFilteringLabel" : "Higit sa O Katumbas", +"lessThanDataGridFilteringLabel" : "Mas mababa sa", +"lessThanOrEqualDataGridFilteringLabel" : "Mas Mababa sa O Katumbas", +"notEmptyDataGridFilteringLabel" : "Hindi Walang laman", +"notNullDataGridFilteringLabel" : "Hindi Null", +"nullDataGridFilteringLabel" : "Wala", +"sortSmallestToLargestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakamaliit Hanggang Pinakamalaki", +"sortLargestToSmallestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakamalaki Hanggang Pinakamaliit", +"sortAToZDataGridFilteringLabel" : "Pagbukud-bukurin A hanggang Z", +"sortZToADataGridFilteringLabel" : "Pagbukud-bukurin ang Z Hanggang A", +"sortOldestToNewestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakamatanda Hanggang Pinakabago", +"sortNewestToOldestDataGridFilteringLabel" : "Pagbukud-bukurin ang Pinakabago Hanggang sa Pinakaluma", +"textFiltersDataGridFilteringLabel" : "Mga Filter ng Teksto", +"numberFiltersDataGridFilteringLabel" : "Mga Filter ng Numero", +"dateFiltersDataGridFilteringLabel" : "Mga Filter ng Petsa", +"searchDataGridFilteringLabel" : "Maghanap", +"noMatchesDataGridFilteringLabel" : "Walang tugma", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Kanselahin", +"showRowsWhereDataGridFilteringLabel" : "Ipakita ang mga hilera kung saan", +"andDataGridFilteringLabel" : "At", +"orDataGridFilteringLabel" : "O kaya", +"selectAllDataGridFilteringLabel" : "Piliin lahat", +"sortAndFilterDataGridFilteringLabel" : "Pagbukud-bukurin at Salain", +"clearFilterDataGridFilteringLabel" : "I-clear ang Filter", +"fromDataGridFilteringLabel" : "Mula sa" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb index 51451dfd5..a1ed65d90 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb @@ -1,26 +1,35 @@ { -"noSelectedDateCalendarLabel" : "Seçili tarih yok", +"noSelectedDateCalendarLabel" : "Tarih seçilmedi", "noEventsCalendarLabel" : "Olay yok", -"ofDataPagerLabel" : "ile ilgili", +"ofDataPagerLabel" : "nın-nin", "pagesDataPagerLabel" : "sayfalar", -"rowsPerPageDataPagerLabel" : "Sayfa başına satır sayısı", +"rowsPerPageDataPagerLabel" : "Sayfa başına satır", "pdfBookmarksLabel" : "Yer imleri", "pdfNoBookmarksLabel" : "Yer işareti bulunamadı", -"pdfScrollStatusOfLabel" : "ile ilgili", +"pdfScrollStatusOfLabel" : "nın-nin", "pdfGoToPageLabel" : "Sayfaya git", -"pdfEnterPageNumberLabel" : "Sayfa numarasını girin", -"pdfInvalidPageNumberLabel" : "Lütfen geçerli bir numara girin", -"pdfPaginationDialogOkLabel" : "Tamam", -"pdfPaginationDialogCancelLabel" : "İPTAL ETMEK", +"pdfEnterPageNumberLabel" : "sayfa numarasını girin", +"pdfInvalidPageNumberLabel" : "Lütfen geçerli bir sayfa numarası girin", +"pdfPaginationDialogOkLabel" : "TAMAM", +"pdfPaginationDialogCancelLabel" : "İptal Et", "passwordDialogHeaderTextLabel" : "Şifre korumalı", "passwordDialogContentLabel" : "Bu PDF dosyasını açmak için şifreyi girin", -"passwordDialogHintTextLabel" : "Parolanı Gir", +"passwordDialogHintTextLabel" : "Şifre girin", "passwordDialogInvalidPasswordLabel" : "geçersiz şifre", -"pdfPasswordDialogOpenLabel" : "AÇIK", -"pdfPasswordDialogCancelLabel" : "İPTAL ETMEK", +"pdfPasswordDialogOpenLabel" : "Aç", +"pdfPasswordDialogCancelLabel" : "İptal", +"pdfSignaturePadDialogHeaderTextLabel" : "imzanı çiz", +"pdfSignaturePadDialogPenColorLabel" : "Kalem Rengi", +"pdfSignaturePadDialogClearLabel" : "Temizle", +"pdfSignaturePadDialogSaveLabel" : "Kaydet", +"pdfTextSelectionMenuCopyLabel" : "Kopyala", +"pdfTextSelectionMenuHighlightLabel" : "Vurgulamak", +"pdfTextSelectionMenuStrikethroughLabel" : "Üstü çizili", +"pdfTextSelectionMenuUnderlineLabel" : "Altını çizmek", +"pdfTextSelectionMenuSquigglyLabel" : "Dalgalı", "allowedViewDayLabel" : "Gün", "allowedViewWeekLabel" : "Hafta", -"allowedViewWorkWeekLabel" : "Çalışma haftası", +"allowedViewWorkWeekLabel" : "Çalışma Haftası", "allowedViewMonthLabel" : "Ay", "allowedViewScheduleLabel" : "Takvim", "allowedViewTimelineDayLabel" : "Zaman Çizelgesi Günü", @@ -31,29 +40,73 @@ "weeknumberLabel" : "Hafta", "allDayLabel" : "Tüm gün", "muharramLabel" : "Muharrem", -"safarLabel" : "Safar", -"rabi1Label" : "Rebiülevvel", +"safarLabel" : "safar", +"rabi1Label" : "Rabi'ul-evvel", "rabi2Label" : "Rabi' al-thani", -"jumada1Label" : "Cumada el-evvel", +"jumada1Label" : "Jumada al-evvel", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Recep", "shaabanLabel" : "Şaban", "ramadanLabel" : "Ramazan", "shawwalLabel" : "Şevval", -"dhualqiLabel" : "Zil Qi'dah", +"dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Zilhicce", -"shortMuharramLabel" : "Müh.", +"shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. i", -"shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. i", -"shortJumada2Label" : "Jum. II", +"shortRabi1Label" : "Rabi. ben", +"shortRabi2Label" : "Rabi. III", +"shortJumada1Label" : "Cuma. ben", +"shortJumada2Label" : "Cuma. III", "shortRajabLabel" : "Raj.", -"shortShaabanLabel" : "Sha.", +"shortShaabanLabel" : "Şa.", "shortRamadanLabel" : "Veri deposu.", "shortShawwalLabel" : "Shaw.", -"shortDhualqiLabel" : "Zil-Q", +"shortDhualqiLabel" : "Zül-Q", "shortDhualhiLabel" : "Zül-H", "daySpanCountLabel" : "Gün", -"series" : "Dizi" +"series" : "Diziler", +"pdfHyperlinkLabel" : "Web Sayfasını Aç", +"pdfHyperlinkContentLabel" : "Sayfayı şu adreste açmak istiyor musunuz?", +"pdfHyperlinkDialogOpenLabel" : "Aç", +"pdfHyperlinkDialogCancelLabel" : "İptal Et", +"afterDataGridFilteringLabel" : "Sonrasında", +"afterOrEqualDataGridFilteringLabel" : "Sonra Veya Eşittir", +"beforeDataGridFilteringLabel" : "Önce", +"beforeOrEqualDataGridFilteringLabel" : "Önce veya Eşit", +"beginsWithDataGridFilteringLabel" : "İle başlar", +"containsDataGridFilteringLabel" : "içerir", +"doesNotBeginWithDataGridFilteringLabel" : "İle Başlamıyor", +"doesNotContainDataGridFilteringLabel" : "İçermiyor", +"doesNotEndWithDataGridFilteringLabel" : "ile bitmiyor", +"doesNotEqualDataGridFilteringLabel" : "Eşit değil", +"emptyDataGridFilteringLabel" : "Boş", +"endsWithDataGridFilteringLabel" : "ile biter", +"equalsDataGridFilteringLabel" : "eşittir", +"greaterThanDataGridFilteringLabel" : "Daha büyük", +"greaterThanOrEqualDataGridFilteringLabel" : "Büyüktür veya Eşittir", +"lessThanDataGridFilteringLabel" : "Daha az", +"lessThanOrEqualDataGridFilteringLabel" : "Az veya eşit", +"notEmptyDataGridFilteringLabel" : "Boş değil", +"notNullDataGridFilteringLabel" : "Geçersiz değil", +"nullDataGridFilteringLabel" : "Hükümsüz", +"sortSmallestToLargestDataGridFilteringLabel" : "Küçükten Büyüğe Sırala", +"sortLargestToSmallestDataGridFilteringLabel" : "Büyükten Küçüğe Sırala", +"sortAToZDataGridFilteringLabel" : "A'dan Z'ye Sırala", +"sortZToADataGridFilteringLabel" : "Z'den A'ya Sırala", +"sortOldestToNewestDataGridFilteringLabel" : "Eskiden Yeniye Sırala", +"sortNewestToOldestDataGridFilteringLabel" : "Yeniden En Eskiye Sırala", +"textFiltersDataGridFilteringLabel" : "Metin Filtreleri", +"numberFiltersDataGridFilteringLabel" : "Sayı Filtreleri", +"dateFiltersDataGridFilteringLabel" : "Tarih Filtreleri", +"searchDataGridFilteringLabel" : "Arama", +"noMatchesDataGridFilteringLabel" : "Eşleşme yok", +"okDataGridFilteringLabel" : "TAMAM", +"cancelDataGridFilteringLabel" : "İptal", +"showRowsWhereDataGridFilteringLabel" : "satırları göster", +"andDataGridFilteringLabel" : "Ve", +"orDataGridFilteringLabel" : "Veya", +"selectAllDataGridFilteringLabel" : "Hepsini seç", +"sortAndFilterDataGridFilteringLabel" : "Sırala ve Filtrele", +"clearFilterDataGridFilteringLabel" : "Temiz filtre", +"fromDataGridFilteringLabel" : "İtibaren" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb index 0fa63d0a5..cdcc2fa7d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb @@ -1,6 +1,6 @@ { -"noSelectedDateCalendarLabel" : "Немає вибраної дати", -"noEventsCalendarLabel" : "Жодних подій", +"noSelectedDateCalendarLabel" : "Дата не вибрана", +"noEventsCalendarLabel" : "Подій немає", "ofDataPagerLabel" : "з", "pagesDataPagerLabel" : "сторінки", "rowsPerPageDataPagerLabel" : "Рядків на сторінці", @@ -9,51 +9,104 @@ "pdfScrollStatusOfLabel" : "з", "pdfGoToPageLabel" : "Перейти на сторінку", "pdfEnterPageNumberLabel" : "Введіть номер сторінки", -"pdfInvalidPageNumberLabel" : "Введіть дійсне число", -"pdfPaginationDialogOkLabel" : "гаразд", +"pdfInvalidPageNumberLabel" : "Введіть дійсний номер", +"pdfPaginationDialogOkLabel" : "ОК", "pdfPaginationDialogCancelLabel" : "СКАСУВАТИ", "passwordDialogHeaderTextLabel" : "Захищено паролем", -"passwordDialogContentLabel" : "Введіть пароль, щоб відкрити цей PDF-файл", +"passwordDialogContentLabel" : "Введіть пароль, щоб відкрити цей файл PDF", "passwordDialogHintTextLabel" : "Введіть пароль", -"passwordDialogInvalidPasswordLabel" : "Недійсний пароль", +"passwordDialogInvalidPasswordLabel" : "Невірний пароль", "pdfPasswordDialogOpenLabel" : "ВІДЧИНЕНО", "pdfPasswordDialogCancelLabel" : "СКАСУВАТИ", +"pdfSignaturePadDialogHeaderTextLabel" : "Намалюйте свій підпис", +"pdfSignaturePadDialogPenColorLabel" : "Колір пера", +"pdfSignaturePadDialogClearLabel" : "Очистити", +"pdfSignaturePadDialogSaveLabel" : "ЗБЕРЕГТИ", +"pdfTextSelectionMenuCopyLabel" : "Копія", +"pdfTextSelectionMenuHighlightLabel" : "Виділіть", +"pdfTextSelectionMenuStrikethroughLabel" : "Закреслення", +"pdfTextSelectionMenuUnderlineLabel" : "Підкреслити", +"pdfTextSelectionMenuSquigglyLabel" : "звивистий", "allowedViewDayLabel" : "День", -"allowedViewWeekLabel" : "тиждень", +"allowedViewWeekLabel" : "Тиждень", "allowedViewWorkWeekLabel" : "Робочий тиждень", "allowedViewMonthLabel" : "Місяць", "allowedViewScheduleLabel" : "Розклад", "allowedViewTimelineDayLabel" : "День хронології", "allowedViewTimelineWeekLabel" : "Тиждень хронології", -"allowedViewTimelineWorkWeekLabel" : "Розклад робочого тижня", -"allowedViewTimelineMonthLabel" : "Хронологічний місяць", +"allowedViewTimelineWorkWeekLabel" : "Графік робочого тижня", +"allowedViewTimelineMonthLabel" : "Місяць хронології", "todayLabel" : "Сьогодні", -"weeknumberLabel" : "тиждень", +"weeknumberLabel" : "Тиждень", "allDayLabel" : "Весь день", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", "rabi1Label" : "Рабі аль-авваль", -"rabi2Label" : "Рабі аль-Тані", +"rabi2Label" : "Рабі аль-тані", "jumada1Label" : "Джумада аль-авваль", -"jumada2Label" : "Джумада аль-Тані", +"jumada2Label" : "Джумада аль-тані", "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", -"shawwalLabel" : "Shawwal", +"shawwalLabel" : "Шавваль", "dhualqiLabel" : "Зу аль-Кіда", "dhualhiLabel" : "Зу аль-Хіджа", -"shortMuharramLabel" : "Мух", -"shortSafarLabel" : "Saf.", +"shortMuharramLabel" : "мух", +"shortSafarLabel" : "Саф.", "shortRabi1Label" : "Рабі. я", "shortRabi2Label" : "Рабі. II", -"shortJumada1Label" : "Jum. я", -"shortJumada2Label" : "Jum. II", +"shortJumada1Label" : "стрибати я", +"shortJumada2Label" : "стрибати II", "shortRajabLabel" : "Радж.", "shortShaabanLabel" : "Ша.", "shortRamadanLabel" : "ОЗП.", "shortShawwalLabel" : "Шоу.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "Зу'л-К'ю", +"shortDhualhiLabel" : "Зу'л-Х", "daySpanCountLabel" : "День", -"series" : "Серія" +"series" : "Серія", +"pdfHyperlinkLabel" : "Відкрити веб-сторінку", +"pdfHyperlinkContentLabel" : "Хочете відкрити сторінку за адресою", +"pdfHyperlinkDialogOpenLabel" : "ВІДЧИНЕНО", +"pdfHyperlinkDialogCancelLabel" : "СКАСУВАТИ", +"afterDataGridFilteringLabel" : "після", +"afterOrEqualDataGridFilteringLabel" : "Після або дорівнює", +"beforeDataGridFilteringLabel" : "Раніше", +"beforeOrEqualDataGridFilteringLabel" : "До або дорівнює", +"beginsWithDataGridFilteringLabel" : "Починається з", +"containsDataGridFilteringLabel" : "Містить", +"doesNotBeginWithDataGridFilteringLabel" : "Не починається з", +"doesNotContainDataGridFilteringLabel" : "Не містить", +"doesNotEndWithDataGridFilteringLabel" : "Не закінчується", +"doesNotEqualDataGridFilteringLabel" : "Не дорівнює", +"emptyDataGridFilteringLabel" : "Порожній", +"endsWithDataGridFilteringLabel" : "Закінчується на", +"equalsDataGridFilteringLabel" : "Дорівнює", +"greaterThanDataGridFilteringLabel" : "більше ніж", +"greaterThanOrEqualDataGridFilteringLabel" : "Більше або дорівнює", +"lessThanDataGridFilteringLabel" : "Менше ніж", +"lessThanOrEqualDataGridFilteringLabel" : "Менше або дорівнює", +"notEmptyDataGridFilteringLabel" : "Не порожній", +"notNullDataGridFilteringLabel" : "Не Null", +"nullDataGridFilteringLabel" : "Нуль", +"sortSmallestToLargestDataGridFilteringLabel" : "Сортування від найменшого до найбільшого", +"sortLargestToSmallestDataGridFilteringLabel" : "Сортування від найбільшого до найменшого", +"sortAToZDataGridFilteringLabel" : "Сортування від А до Я", +"sortZToADataGridFilteringLabel" : "Сортувати від Я до А", +"sortOldestToNewestDataGridFilteringLabel" : "Сортувати від найстарішого до найновішого", +"sortNewestToOldestDataGridFilteringLabel" : "Сортування від найновіших до найстаріших", +"textFiltersDataGridFilteringLabel" : "Текстові фільтри", +"numberFiltersDataGridFilteringLabel" : "Числові фільтри", +"dateFiltersDataGridFilteringLabel" : "Фільтри дати", +"searchDataGridFilteringLabel" : "Пошук", +"noMatchesDataGridFilteringLabel" : "Немає збігів", +"okDataGridFilteringLabel" : "в порядку", +"cancelDataGridFilteringLabel" : "Скасувати", +"showRowsWhereDataGridFilteringLabel" : "Показати рядки, де", +"andDataGridFilteringLabel" : "І", +"orDataGridFilteringLabel" : "Або", +"selectAllDataGridFilteringLabel" : "Вибрати все", +"sortAndFilterDataGridFilteringLabel" : "Сортування та фільтр", +"clearFilterDataGridFilteringLabel" : "Очистити фільтр", +"fromDataGridFilteringLabel" : "Від" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb index e6c77f88d..c4d8fe6b2 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb @@ -1,12 +1,12 @@ { "noSelectedDateCalendarLabel" : "کوئی تاریخ منتخب نہیں ہے۔", -"noEventsCalendarLabel" : "کوئی واقعات نہیں۔", -"ofDataPagerLabel" : "کا", +"noEventsCalendarLabel" : "کوئی واقعات نہیں", +"ofDataPagerLabel" : "کی", "pagesDataPagerLabel" : "صفحات", "rowsPerPageDataPagerLabel" : "قطار فی صفحہ", "pdfBookmarksLabel" : "بک مارکس", -"pdfNoBookmarksLabel" : "کوئی بک مارکس نہیں ملے", -"pdfScrollStatusOfLabel" : "کا", +"pdfNoBookmarksLabel" : "کوئی بک مارکس نہیں ملا", +"pdfScrollStatusOfLabel" : "کی", "pdfGoToPageLabel" : "صفحے پر جائیں", "pdfEnterPageNumberLabel" : "صفحہ نمبر درج کریں۔", "pdfInvalidPageNumberLabel" : "براہ مہربانی ایک درست نمبر درج کریں", @@ -16,8 +16,17 @@ "passwordDialogContentLabel" : "اس پی ڈی ایف فائل کو کھولنے کے لیے پاس ورڈ درج کریں۔", "passwordDialogHintTextLabel" : "پاس ورڈ درج کریں", "passwordDialogInvalidPasswordLabel" : "غلط پاسورڈ", -"pdfPasswordDialogOpenLabel" : "کھولیں", +"pdfPasswordDialogOpenLabel" : "کھولیں۔", "pdfPasswordDialogCancelLabel" : "منسوخ کریں۔", +"pdfSignaturePadDialogHeaderTextLabel" : "اپنے دستخط کھینچیں۔", +"pdfSignaturePadDialogPenColorLabel" : "قلم کا رنگ", +"pdfSignaturePadDialogClearLabel" : "صاف کریں۔", +"pdfSignaturePadDialogSaveLabel" : "محفوظ کریں۔", +"pdfTextSelectionMenuCopyLabel" : "کاپی", +"pdfTextSelectionMenuHighlightLabel" : "نمایاں کریں۔", +"pdfTextSelectionMenuStrikethroughLabel" : "سٹرائیک تھرو", +"pdfTextSelectionMenuUnderlineLabel" : "انڈر لائن", +"pdfTextSelectionMenuSquigglyLabel" : "دھڑلے سے", "allowedViewDayLabel" : "دن", "allowedViewWeekLabel" : "ہفتہ", "allowedViewWorkWeekLabel" : "کام کا ہفتہ", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "ذوالقع", "shortDhualhiLabel" : "ذوالحجہ", "daySpanCountLabel" : "دن", -"series" : "سلسلہ" +"series" : "سلسلہ", +"pdfHyperlinkLabel" : "ویب صفحہ کھولیں۔", +"pdfHyperlinkContentLabel" : "کیا آپ صفحہ کھولنا چاہتے ہیں؟", +"pdfHyperlinkDialogOpenLabel" : "کھولیں۔", +"pdfHyperlinkDialogCancelLabel" : "منسوخ کریں۔", +"afterDataGridFilteringLabel" : "کے بعد", +"afterOrEqualDataGridFilteringLabel" : "کے بعد یا برابر", +"beforeDataGridFilteringLabel" : "سے پہلے", +"beforeOrEqualDataGridFilteringLabel" : "پہلے یا برابر", +"beginsWithDataGridFilteringLabel" : "سے شروع ہوتا ہے۔", +"containsDataGridFilteringLabel" : "مشتمل", +"doesNotBeginWithDataGridFilteringLabel" : "سے شروع نہیں ہوتا", +"doesNotContainDataGridFilteringLabel" : "پر مشتمل نہیں ہے۔", +"doesNotEndWithDataGridFilteringLabel" : "کے ساتھ ختم نہیں ہوتا", +"doesNotEqualDataGridFilteringLabel" : "برابر نہیں ہے۔", +"emptyDataGridFilteringLabel" : "خالی", +"endsWithDataGridFilteringLabel" : "کے ساتھ ختم ہوتا ہے۔", +"equalsDataGridFilteringLabel" : "برابر ہے۔", +"greaterThanDataGridFilteringLabel" : "اس سے بڑا", +"greaterThanOrEqualDataGridFilteringLabel" : "اس سے بڑا یا برابر", +"lessThanDataGridFilteringLabel" : "سے کم", +"lessThanOrEqualDataGridFilteringLabel" : "اس سے کم یا برابر", +"notEmptyDataGridFilteringLabel" : "خالی نہیں۔", +"notNullDataGridFilteringLabel" : "کالعدم نہیں۔", +"nullDataGridFilteringLabel" : "خالی", +"sortSmallestToLargestDataGridFilteringLabel" : "چھانٹیں سب سے چھوٹی سے بڑی", +"sortLargestToSmallestDataGridFilteringLabel" : "چھانٹیں بڑے سے چھوٹے", +"sortAToZDataGridFilteringLabel" : "A سے Z ترتیب دیں۔", +"sortZToADataGridFilteringLabel" : "Z سے ​​A ترتیب دیں۔", +"sortOldestToNewestDataGridFilteringLabel" : "سب سے قدیم سے تازہ ترین ترتیب دیں۔", +"sortNewestToOldestDataGridFilteringLabel" : "تازہ ترین سے قدیم ترین ترتیب دیں۔", +"textFiltersDataGridFilteringLabel" : "ٹیکسٹ فلٹرز", +"numberFiltersDataGridFilteringLabel" : "نمبر فلٹرز", +"dateFiltersDataGridFilteringLabel" : "تاریخ کے فلٹرز", +"searchDataGridFilteringLabel" : "تلاش کریں۔", +"noMatchesDataGridFilteringLabel" : "کوئی میچ نہیں", +"okDataGridFilteringLabel" : "ٹھیک ہے", +"cancelDataGridFilteringLabel" : "منسوخ کریں۔", +"showRowsWhereDataGridFilteringLabel" : "قطاریں دکھائیں جہاں", +"andDataGridFilteringLabel" : "اور", +"orDataGridFilteringLabel" : "یا", +"selectAllDataGridFilteringLabel" : "تمام منتخب کریں", +"sortAndFilterDataGridFilteringLabel" : "ترتیب دیں اور فلٹر کریں۔", +"clearFilterDataGridFilteringLabel" : "فلٹر صاف کریں۔", +"fromDataGridFilteringLabel" : "سے" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb index 911d4995c..b52d943e8 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb @@ -18,7 +18,16 @@ "passwordDialogInvalidPasswordLabel" : "Yaroqsiz parol", "pdfPasswordDialogOpenLabel" : "OCHIQ", "pdfPasswordDialogCancelLabel" : "BEKOR", -"allowedViewDayLabel" : "kun", +"pdfSignaturePadDialogHeaderTextLabel" : "Imzongizni chizing", +"pdfSignaturePadDialogPenColorLabel" : "Qalam rangi", +"pdfSignaturePadDialogClearLabel" : "TOZLASH", +"pdfSignaturePadDialogSaveLabel" : "SAQLASH", +"pdfTextSelectionMenuCopyLabel" : "Nusxalash", +"pdfTextSelectionMenuHighlightLabel" : "Ajratish", +"pdfTextSelectionMenuStrikethroughLabel" : "Chizilgan", +"pdfTextSelectionMenuUnderlineLabel" : "tagiga chizish", +"pdfTextSelectionMenuSquigglyLabel" : "Buzg'unchi", +"allowedViewDayLabel" : "Kun", "allowedViewWeekLabel" : "Hafta", "allowedViewWorkWeekLabel" : "Ish haftasi", "allowedViewMonthLabel" : "Oy", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Zul-q", "shortDhualhiLabel" : "Zul-h", "daySpanCountLabel" : "kun", -"series" : "Seriya" +"series" : "Seriya", +"pdfHyperlinkLabel" : "Veb-sahifani oching", +"pdfHyperlinkContentLabel" : "sahifani ochmoqchimisiz", +"pdfHyperlinkDialogOpenLabel" : "OCHIQ", +"pdfHyperlinkDialogCancelLabel" : "BEKOR", +"afterDataGridFilteringLabel" : "Keyin", +"afterOrEqualDataGridFilteringLabel" : "Keyin yoki teng", +"beforeDataGridFilteringLabel" : "Oldin", +"beforeOrEqualDataGridFilteringLabel" : "Oldin yoki teng", +"beginsWithDataGridFilteringLabel" : "Bilan boshlanadi", +"containsDataGridFilteringLabel" : "Tarkibida", +"doesNotBeginWithDataGridFilteringLabel" : "Bilan boshlanmaydi", +"doesNotContainDataGridFilteringLabel" : "Tarkibiga kirmaydi", +"doesNotEndWithDataGridFilteringLabel" : "Bilan tugamaydi", +"doesNotEqualDataGridFilteringLabel" : "Teng emas", +"emptyDataGridFilteringLabel" : "Bo'sh", +"endsWithDataGridFilteringLabel" : "Bilan tugaydi", +"equalsDataGridFilteringLabel" : "Teng", +"greaterThanDataGridFilteringLabel" : "Kattaroq", +"greaterThanOrEqualDataGridFilteringLabel" : "Kattaroq yoki teng", +"lessThanDataGridFilteringLabel" : "Kamroq", +"lessThanOrEqualDataGridFilteringLabel" : "Kichik yoki teng", +"notEmptyDataGridFilteringLabel" : "Bo'sh emas", +"notNullDataGridFilteringLabel" : "Null emas", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Eng kichikdan kattaga saralash", +"sortLargestToSmallestDataGridFilteringLabel" : "Eng kattadan kichikga saralash", +"sortAToZDataGridFilteringLabel" : "A dan Z gacha tartiblang", +"sortZToADataGridFilteringLabel" : "Z dan A ga tartiblang", +"sortOldestToNewestDataGridFilteringLabel" : "Eng eskidan eng yangisiga saralash", +"sortNewestToOldestDataGridFilteringLabel" : "Eng yangidan eng eskiga saralash", +"textFiltersDataGridFilteringLabel" : "Matn filtrlari", +"numberFiltersDataGridFilteringLabel" : "Raqamli filtrlar", +"dateFiltersDataGridFilteringLabel" : "Sana filtrlari", +"searchDataGridFilteringLabel" : "Qidirmoq", +"noMatchesDataGridFilteringLabel" : "Mos kelmaydi", +"okDataGridFilteringLabel" : "OK", +"cancelDataGridFilteringLabel" : "Bekor qilish", +"showRowsWhereDataGridFilteringLabel" : "Qaerda qatorlarni ko'rsatish", +"andDataGridFilteringLabel" : "Va", +"orDataGridFilteringLabel" : "Yoki", +"selectAllDataGridFilteringLabel" : "Hammasini belgilash", +"sortAndFilterDataGridFilteringLabel" : "Saralash va filtrlash", +"clearFilterDataGridFilteringLabel" : "Filtrni tozalash", +"fromDataGridFilteringLabel" : "Kimdan" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb index eb1a204d6..b8f2cdb1b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb @@ -5,55 +5,108 @@ "pagesDataPagerLabel" : "trang", "rowsPerPageDataPagerLabel" : "Hàng trên mỗi trang", "pdfBookmarksLabel" : "Dấu trang", -"pdfNoBookmarksLabel" : "Không tìm thấy dấu trang", +"pdfNoBookmarksLabel" : "Không tìm thấy dấu trang nào", "pdfScrollStatusOfLabel" : "của", "pdfGoToPageLabel" : "Đi tới trang", "pdfEnterPageNumberLabel" : "Nhập số trang", "pdfInvalidPageNumberLabel" : "Vui lòng nhập một số hợp lệ", -"pdfPaginationDialogOkLabel" : "VÂNG", -"pdfPaginationDialogCancelLabel" : "SỰ HỦY BỎ", +"pdfPaginationDialogOkLabel" : "Đồng ý", +"pdfPaginationDialogCancelLabel" : "Hủy", "passwordDialogHeaderTextLabel" : "Mật khẩu được bảo vệ", "passwordDialogContentLabel" : "Nhập mật khẩu để mở tệp PDF này", "passwordDialogHintTextLabel" : "Nhập mật khẩu", "passwordDialogInvalidPasswordLabel" : "Mật khẩu không hợp lệ", -"pdfPasswordDialogOpenLabel" : "MỞ", -"pdfPasswordDialogCancelLabel" : "SỰ HỦY BỎ", +"pdfPasswordDialogOpenLabel" : "Mở", +"pdfPasswordDialogCancelLabel" : "Hủy", +"pdfSignaturePadDialogHeaderTextLabel" : "Vẽ chữ ký của bạn", +"pdfSignaturePadDialogPenColorLabel" : "màu bút", +"pdfSignaturePadDialogClearLabel" : "Xóa", +"pdfSignaturePadDialogSaveLabel" : "Lưu", +"pdfTextSelectionMenuCopyLabel" : "Sao chép", +"pdfTextSelectionMenuHighlightLabel" : "Tô đậm", +"pdfTextSelectionMenuStrikethroughLabel" : "Gạch ngang", +"pdfTextSelectionMenuUnderlineLabel" : "gạch chân", +"pdfTextSelectionMenuSquigglyLabel" : "Gạch lượn sóng", "allowedViewDayLabel" : "Ngày", "allowedViewWeekLabel" : "Tuần", "allowedViewWorkWeekLabel" : "Tuần làm việc", "allowedViewMonthLabel" : "Tháng", "allowedViewScheduleLabel" : "Lịch trình", -"allowedViewTimelineDayLabel" : "Ngày dòng thời gian", -"allowedViewTimelineWeekLabel" : "Dòng thời gian trong tuần", -"allowedViewTimelineWorkWeekLabel" : "Dòng thời gian làm việc trong tuần", +"allowedViewTimelineDayLabel" : "Dòng thời gian ngày", +"allowedViewTimelineWeekLabel" : "Dòng thời gian Tuần", +"allowedViewTimelineWorkWeekLabel" : "Dòng thời gian tuần làm việc", "allowedViewTimelineMonthLabel" : "Dòng thời gian Tháng", "todayLabel" : "Hôm nay", "weeknumberLabel" : "Tuần", "allDayLabel" : "Cả ngày", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", +"safarLabel" : "safar", +"rabi1Label" : "Rabi' al-awwal", +"rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", -"ramadanLabel" : "Ramadan", +"ramadanLabel" : "lễ ramadan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Ờ.", -"shortSafarLabel" : "Két sắt.", -"shortRabi1Label" : "Rabi. tôi", +"shortMuharramLabel" : "ừm.", +"shortSafarLabel" : "an toàn.", +"shortRabi1Label" : "Rabi. Tôi", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. tôi", +"shortJumada1Label" : "Jum. Tôi", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", -"shortRamadanLabel" : "Ram.", +"shortRamadanLabel" : "Đập.", "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Ngày", -"series" : "Hàng loạt" +"series" : "Loạt", +"pdfHyperlinkLabel" : "Mở trang web", +"pdfHyperlinkContentLabel" : "Bạn có muốn mở trang này không?", +"pdfHyperlinkDialogOpenLabel" : "Mở", +"pdfHyperlinkDialogCancelLabel" : "Hủy", +"afterDataGridFilteringLabel" : "Sau", +"afterOrEqualDataGridFilteringLabel" : "Sau Hoặc Bằng", +"beforeDataGridFilteringLabel" : "Trước", +"beforeOrEqualDataGridFilteringLabel" : "Trước Hoặc Bằng", +"beginsWithDataGridFilteringLabel" : "Bắt đầu với", +"containsDataGridFilteringLabel" : "Chứa", +"doesNotBeginWithDataGridFilteringLabel" : "không bắt đầu bằng", +"doesNotContainDataGridFilteringLabel" : "Không chứa", +"doesNotEndWithDataGridFilteringLabel" : "không kết thúc với", +"doesNotEqualDataGridFilteringLabel" : "không bằng", +"emptyDataGridFilteringLabel" : "Trống rỗng", +"endsWithDataGridFilteringLabel" : "kết thúc với", +"equalsDataGridFilteringLabel" : "bằng", +"greaterThanDataGridFilteringLabel" : "Lớn hơn", +"greaterThanOrEqualDataGridFilteringLabel" : "Lớn hơn hoặc bằng", +"lessThanDataGridFilteringLabel" : "Ít hơn", +"lessThanOrEqualDataGridFilteringLabel" : "Nhỏ hơn hoặc bằng", +"notEmptyDataGridFilteringLabel" : "Không trống", +"notNullDataGridFilteringLabel" : "Có giá trị", +"nullDataGridFilteringLabel" : "Vô giá trị", +"sortSmallestToLargestDataGridFilteringLabel" : "Sắp xếp từ nhỏ nhất đến lớn nhất", +"sortLargestToSmallestDataGridFilteringLabel" : "Sắp xếp lớn nhất đến nhỏ nhất", +"sortAToZDataGridFilteringLabel" : "Sắp xếp từ A đến Z", +"sortZToADataGridFilteringLabel" : "Sắp xếp từ Z đến A", +"sortOldestToNewestDataGridFilteringLabel" : "Sắp xếp Cũ nhất đến Mới nhất", +"sortNewestToOldestDataGridFilteringLabel" : "Sắp xếp mới nhất đến cũ nhất", +"textFiltersDataGridFilteringLabel" : "Bộ lọc văn bản", +"numberFiltersDataGridFilteringLabel" : "Bộ lọc số", +"dateFiltersDataGridFilteringLabel" : "Bộ lọc ngày", +"searchDataGridFilteringLabel" : "Tìm kiếm", +"noMatchesDataGridFilteringLabel" : "Không có trận đấu", +"okDataGridFilteringLabel" : "ĐƯỢC RỒI", +"cancelDataGridFilteringLabel" : "Hủy bỏ", +"showRowsWhereDataGridFilteringLabel" : "Hiển thị các hàng ở đâu", +"andDataGridFilteringLabel" : "Và", +"orDataGridFilteringLabel" : "Hoặc", +"selectAllDataGridFilteringLabel" : "Chọn tất cả", +"sortAndFilterDataGridFilteringLabel" : "Sắp xếp và lọc", +"clearFilterDataGridFilteringLabel" : "LÀm sạch bộ lọc", +"fromDataGridFilteringLabel" : "Từ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb index e81200092..0348d4feb 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb @@ -1,59 +1,112 @@ { -"noSelectedDateCalendarLabel" : "未选择日期", -"noEventsCalendarLabel" : "没有事件", +"noSelectedDateCalendarLabel" : "没有选择日期", +"noEventsCalendarLabel" : "没有活动", "ofDataPagerLabel" : "的", -"pagesDataPagerLabel" : "页面", +"pagesDataPagerLabel" : "页数", "rowsPerPageDataPagerLabel" : "每页行数", "pdfBookmarksLabel" : "书签", -"pdfNoBookmarksLabel" : "未找到书签", +"pdfNoBookmarksLabel" : "找不到书签", "pdfScrollStatusOfLabel" : "的", "pdfGoToPageLabel" : "转到页面", "pdfEnterPageNumberLabel" : "输入页码", "pdfInvalidPageNumberLabel" : "请输入有效号码", -"pdfPaginationDialogOkLabel" : "好的", +"pdfPaginationDialogOkLabel" : "确认", "pdfPaginationDialogCancelLabel" : "取消", "passwordDialogHeaderTextLabel" : "密码保护", -"passwordDialogContentLabel" : "输入密码以打开此 PDF 文件", +"passwordDialogContentLabel" : "输入密码打开此 PDF 文件", "passwordDialogHintTextLabel" : "输入密码", "passwordDialogInvalidPasswordLabel" : "无效的密码", "pdfPasswordDialogOpenLabel" : "打开", "pdfPasswordDialogCancelLabel" : "取消", -"allowedViewDayLabel" : "日", +"pdfSignaturePadDialogHeaderTextLabel" : "画出你的签名", +"pdfSignaturePadDialogPenColorLabel" : "笔颜色", +"pdfSignaturePadDialogClearLabel" : "清除", +"pdfSignaturePadDialogSaveLabel" : "保存", +"pdfTextSelectionMenuCopyLabel" : "复制", +"pdfTextSelectionMenuHighlightLabel" : "突出显示", +"pdfTextSelectionMenuStrikethroughLabel" : "删除线", +"pdfTextSelectionMenuUnderlineLabel" : "下划线", +"pdfTextSelectionMenuSquigglyLabel" : "波浪形", +"allowedViewDayLabel" : "天", "allowedViewWeekLabel" : "星期", "allowedViewWorkWeekLabel" : "工作周", "allowedViewMonthLabel" : "月", "allowedViewScheduleLabel" : "日程", -"allowedViewTimelineDayLabel" : "时间表日", +"allowedViewTimelineDayLabel" : "时间轴日", "allowedViewTimelineWeekLabel" : "时间线周", "allowedViewTimelineWorkWeekLabel" : "时间表工作周", -"allowedViewTimelineMonthLabel" : "时间线月份", +"allowedViewTimelineMonthLabel" : "时间表月份", "todayLabel" : "今天", "weeknumberLabel" : "星期", "allDayLabel" : "一整天", -"muharramLabel" : "穆哈拉姆", +"muharramLabel" : "回历", "safarLabel" : "萨法尔", -"rabi1Label" : "拉比奥瓦尔", +"rabi1Label" : "拉比·奥瓦尔", "rabi2Label" : "拉比阿勒萨尼", -"jumada1Label" : "胡玛达·奥瓦尔", -"jumada2Label" : "朱马达·萨尼", -"rajabLabel" : "拉贾布", +"jumada1Label" : "朱马达·阿瓦瓦尔", +"jumada2Label" : "祖玛达·阿萨尼", +"rajabLabel" : "拉杰卜", "shaabanLabel" : "沙班", "ramadanLabel" : "斋月", "shawwalLabel" : "肖瓦尔", -"dhualqiLabel" : "杜阿尔-基达", -"dhualhiLabel" : "杜哈杰", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "嗯。", "shortSafarLabel" : "安全。", -"shortRabi1Label" : "拉比。一世", +"shortRabi1Label" : "拉比。我", "shortRabi2Label" : "拉比。二", -"shortJumada1Label" : "朱姆。一世", -"shortJumada2Label" : "朱姆。二", +"shortJumada1Label" : "滚。我", +"shortJumada2Label" : "滚。二", "shortRajabLabel" : "拉吉。", "shortShaabanLabel" : "沙。", "shortRamadanLabel" : "内存。", "shortShawwalLabel" : "肖。", -"shortDhualqiLabel" : "杜尔-Q", -"shortDhualhiLabel" : "杜尔-H", -"daySpanCountLabel" : "日", -"series" : "系列" +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H", +"daySpanCountLabel" : "天", +"series" : "系列", +"pdfHyperlinkLabel" : "打开网页", +"pdfHyperlinkContentLabel" : "你想打开页面吗", +"pdfHyperlinkDialogOpenLabel" : "打开", +"pdfHyperlinkDialogCancelLabel" : "取消", +"afterDataGridFilteringLabel" : "后", +"afterOrEqualDataGridFilteringLabel" : "等于或之后", +"beforeDataGridFilteringLabel" : "前", +"beforeOrEqualDataGridFilteringLabel" : "早于或等于", +"beginsWithDataGridFilteringLabel" : "开始于", +"containsDataGridFilteringLabel" : "包含", +"doesNotBeginWithDataGridFilteringLabel" : "不以开头", +"doesNotContainDataGridFilteringLabel" : "不含", +"doesNotEndWithDataGridFilteringLabel" : "不结束于", +"doesNotEqualDataGridFilteringLabel" : "不等于", +"emptyDataGridFilteringLabel" : "空的", +"endsWithDataGridFilteringLabel" : "以。。结束", +"equalsDataGridFilteringLabel" : "等于", +"greaterThanDataGridFilteringLabel" : "比...更棒", +"greaterThanOrEqualDataGridFilteringLabel" : "大于或等于", +"lessThanDataGridFilteringLabel" : "少于", +"lessThanOrEqualDataGridFilteringLabel" : "小于或等于", +"notEmptyDataGridFilteringLabel" : "不是空的", +"notNullDataGridFilteringLabel" : "不为空", +"nullDataGridFilteringLabel" : "无效的", +"sortSmallestToLargestDataGridFilteringLabel" : "从小到大排序", +"sortLargestToSmallestDataGridFilteringLabel" : "从大到小排序", +"sortAToZDataGridFilteringLabel" : "从 A 到 Z 排序", +"sortZToADataGridFilteringLabel" : "将 Z 排序为 A", +"sortOldestToNewestDataGridFilteringLabel" : "从旧到新排序", +"sortNewestToOldestDataGridFilteringLabel" : "从最新到最旧排序", +"textFiltersDataGridFilteringLabel" : "文本过滤器", +"numberFiltersDataGridFilteringLabel" : "数字过滤器", +"dateFiltersDataGridFilteringLabel" : "日期过滤器", +"searchDataGridFilteringLabel" : "搜索", +"noMatchesDataGridFilteringLabel" : "无匹配", +"okDataGridFilteringLabel" : "好的", +"cancelDataGridFilteringLabel" : "取消", +"showRowsWhereDataGridFilteringLabel" : "显示行在哪里", +"andDataGridFilteringLabel" : "和", +"orDataGridFilteringLabel" : "或者", +"selectAllDataGridFilteringLabel" : "全选", +"sortAndFilterDataGridFilteringLabel" : "排序和过滤", +"clearFilterDataGridFilteringLabel" : "清除过滤器", +"fromDataGridFilteringLabel" : "从" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb index 87213e2c4..6cff1a4a1 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb @@ -1,11 +1,11 @@ { -"noSelectedDateCalendarLabel" : "未選擇日期", -"noEventsCalendarLabel" : "沒有事件", +"noSelectedDateCalendarLabel" : "沒有選擇日期", +"noEventsCalendarLabel" : "沒有活動", "ofDataPagerLabel" : "的", -"pagesDataPagerLabel" : "頁面", +"pagesDataPagerLabel" : "頁數", "rowsPerPageDataPagerLabel" : "每頁行數", "pdfBookmarksLabel" : "書籤", -"pdfNoBookmarksLabel" : "未找到書籤", +"pdfNoBookmarksLabel" : "找不到書籤", "pdfScrollStatusOfLabel" : "的", "pdfGoToPageLabel" : "轉到頁面", "pdfEnterPageNumberLabel" : "輸入頁碼", @@ -13,47 +13,100 @@ "pdfPaginationDialogOkLabel" : "好的", "pdfPaginationDialogCancelLabel" : "取消", "passwordDialogHeaderTextLabel" : "密碼保護", -"passwordDialogContentLabel" : "輸入密碼以打開此 PDF 文件", +"passwordDialogContentLabel" : "輸入密碼打開此 PDF 文件", "passwordDialogHintTextLabel" : "輸入密碼", "passwordDialogInvalidPasswordLabel" : "無效的密碼", -"pdfPasswordDialogOpenLabel" : "打開", +"pdfPasswordDialogOpenLabel" : "開啟", "pdfPasswordDialogCancelLabel" : "取消", -"allowedViewDayLabel" : "日", +"pdfSignaturePadDialogHeaderTextLabel" : "畫出你的簽名", +"pdfSignaturePadDialogPenColorLabel" : "筆顏色", +"pdfSignaturePadDialogClearLabel" : "清除", +"pdfSignaturePadDialogSaveLabel" : "保存", +"pdfTextSelectionMenuCopyLabel" : "複製", +"pdfTextSelectionMenuHighlightLabel" : "標記", +"pdfTextSelectionMenuStrikethroughLabel" : "刪除線", +"pdfTextSelectionMenuUnderlineLabel" : "下划線", +"pdfTextSelectionMenuSquigglyLabel" : "波浪線", +"allowedViewDayLabel" : "天", "allowedViewWeekLabel" : "星期", "allowedViewWorkWeekLabel" : "工作週", "allowedViewMonthLabel" : "月", "allowedViewScheduleLabel" : "日程", -"allowedViewTimelineDayLabel" : "時間表日", +"allowedViewTimelineDayLabel" : "時間軸日", "allowedViewTimelineWeekLabel" : "時間線週", "allowedViewTimelineWorkWeekLabel" : "時間表工作週", -"allowedViewTimelineMonthLabel" : "時間線月份", +"allowedViewTimelineMonthLabel" : "時間表月份", "todayLabel" : "今天", "weeknumberLabel" : "星期", "allDayLabel" : "一整天", -"muharramLabel" : "穆哈拉姆", +"muharramLabel" : "回曆", "safarLabel" : "薩法爾", -"rabi1Label" : "拉比奧瓦爾", +"rabi1Label" : "拉比·奧瓦爾", "rabi2Label" : "拉比阿勒薩尼", -"jumada1Label" : "胡瑪達·奧瓦爾", -"jumada2Label" : "朱馬達·薩尼", -"rajabLabel" : "拉賈布", +"jumada1Label" : "朱馬達·阿瓦瓦爾", +"jumada2Label" : "祖瑪達·阿薩尼", +"rajabLabel" : "拉傑卜", "shaabanLabel" : "沙班", "ramadanLabel" : "齋月", "shawwalLabel" : "肖瓦爾", -"dhualqiLabel" : "杜阿爾-基達", -"dhualhiLabel" : "杜哈傑", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "嗯。", "shortSafarLabel" : "安全。", -"shortRabi1Label" : "拉比。一世", +"shortRabi1Label" : "拉比。我", "shortRabi2Label" : "拉比。二", -"shortJumada1Label" : "朱姆。一世", -"shortJumada2Label" : "朱姆。二", +"shortJumada1Label" : "滾。我", +"shortJumada2Label" : "滾。二", "shortRajabLabel" : "拉吉。", "shortShaabanLabel" : "沙。", "shortRamadanLabel" : "內存。", "shortShawwalLabel" : "肖。", -"shortDhualqiLabel" : "杜爾-Q", -"shortDhualhiLabel" : "杜爾-H", -"daySpanCountLabel" : "日", -"series" : "系列" +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H", +"daySpanCountLabel" : "天", +"series" : "系列", +"pdfHyperlinkLabel" : "打開網頁", +"pdfHyperlinkContentLabel" : "你想打開頁面嗎", +"pdfHyperlinkDialogOpenLabel" : "打開", +"pdfHyperlinkDialogCancelLabel" : "取消", +"afterDataGridFilteringLabel" : "後", +"afterOrEqualDataGridFilteringLabel" : "等於或之後", +"beforeDataGridFilteringLabel" : "前", +"beforeOrEqualDataGridFilteringLabel" : "早於或等於", +"beginsWithDataGridFilteringLabel" : "開始於", +"containsDataGridFilteringLabel" : "包含", +"doesNotBeginWithDataGridFilteringLabel" : "不以開頭", +"doesNotContainDataGridFilteringLabel" : "不含", +"doesNotEndWithDataGridFilteringLabel" : "不結束於", +"doesNotEqualDataGridFilteringLabel" : "不等於", +"emptyDataGridFilteringLabel" : "空的", +"endsWithDataGridFilteringLabel" : "以。。結束", +"equalsDataGridFilteringLabel" : "等於", +"greaterThanDataGridFilteringLabel" : "比...更棒", +"greaterThanOrEqualDataGridFilteringLabel" : "大於或等於", +"lessThanDataGridFilteringLabel" : "少於", +"lessThanOrEqualDataGridFilteringLabel" : "小於或等於", +"notEmptyDataGridFilteringLabel" : "不是空的", +"notNullDataGridFilteringLabel" : "不為空", +"nullDataGridFilteringLabel" : "無效的", +"sortSmallestToLargestDataGridFilteringLabel" : "從小到大排序", +"sortLargestToSmallestDataGridFilteringLabel" : "從大到小排序", +"sortAToZDataGridFilteringLabel" : "從 A 到 Z 排序", +"sortZToADataGridFilteringLabel" : "將 Z 排序為 A", +"sortOldestToNewestDataGridFilteringLabel" : "從舊到新排序", +"sortNewestToOldestDataGridFilteringLabel" : "從最新到最舊排序", +"textFiltersDataGridFilteringLabel" : "文本過濾器", +"numberFiltersDataGridFilteringLabel" : "數字過濾器", +"dateFiltersDataGridFilteringLabel" : "日期過濾器", +"searchDataGridFilteringLabel" : "搜索", +"noMatchesDataGridFilteringLabel" : "無匹配", +"okDataGridFilteringLabel" : "好的", +"cancelDataGridFilteringLabel" : "取消", +"showRowsWhereDataGridFilteringLabel" : "顯示行在哪裡", +"andDataGridFilteringLabel" : "和", +"orDataGridFilteringLabel" : "或者", +"selectAllDataGridFilteringLabel" : "全選", +"sortAndFilterDataGridFilteringLabel" : "排序和過濾", +"clearFilterDataGridFilteringLabel" : "清除過濾器", +"fromDataGridFilteringLabel" : "從" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb index 87213e2c4..8a566b6e3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb @@ -1,11 +1,11 @@ { -"noSelectedDateCalendarLabel" : "未選擇日期", -"noEventsCalendarLabel" : "沒有事件", +"noSelectedDateCalendarLabel" : "沒有選擇日期", +"noEventsCalendarLabel" : "沒有活動", "ofDataPagerLabel" : "的", -"pagesDataPagerLabel" : "頁面", +"pagesDataPagerLabel" : "頁數", "rowsPerPageDataPagerLabel" : "每頁行數", "pdfBookmarksLabel" : "書籤", -"pdfNoBookmarksLabel" : "未找到書籤", +"pdfNoBookmarksLabel" : "找不到書籤", "pdfScrollStatusOfLabel" : "的", "pdfGoToPageLabel" : "轉到頁面", "pdfEnterPageNumberLabel" : "輸入頁碼", @@ -13,47 +13,100 @@ "pdfPaginationDialogOkLabel" : "好的", "pdfPaginationDialogCancelLabel" : "取消", "passwordDialogHeaderTextLabel" : "密碼保護", -"passwordDialogContentLabel" : "輸入密碼以打開此 PDF 文件", +"passwordDialogContentLabel" : "輸入密碼打開此 PDF 文件", "passwordDialogHintTextLabel" : "輸入密碼", "passwordDialogInvalidPasswordLabel" : "無效的密碼", "pdfPasswordDialogOpenLabel" : "打開", "pdfPasswordDialogCancelLabel" : "取消", -"allowedViewDayLabel" : "日", +"pdfSignaturePadDialogHeaderTextLabel" : "畫出你的簽名", +"pdfSignaturePadDialogPenColorLabel" : "筆顏色", +"pdfSignaturePadDialogClearLabel" : "清除", +"pdfSignaturePadDialogSaveLabel" : "保存", +"pdfTextSelectionMenuCopyLabel" : "複製", +"pdfTextSelectionMenuHighlightLabel" : "標示", +"pdfTextSelectionMenuStrikethroughLabel" : "刪除線", +"pdfTextSelectionMenuUnderlineLabel" : "下劃線", +"pdfTextSelectionMenuSquigglyLabel" : "波浪線", +"allowedViewDayLabel" : "天", "allowedViewWeekLabel" : "星期", "allowedViewWorkWeekLabel" : "工作週", "allowedViewMonthLabel" : "月", "allowedViewScheduleLabel" : "日程", -"allowedViewTimelineDayLabel" : "時間表日", +"allowedViewTimelineDayLabel" : "時間軸日", "allowedViewTimelineWeekLabel" : "時間線週", "allowedViewTimelineWorkWeekLabel" : "時間表工作週", -"allowedViewTimelineMonthLabel" : "時間線月份", +"allowedViewTimelineMonthLabel" : "時間表月份", "todayLabel" : "今天", "weeknumberLabel" : "星期", "allDayLabel" : "一整天", -"muharramLabel" : "穆哈拉姆", +"muharramLabel" : "回曆", "safarLabel" : "薩法爾", -"rabi1Label" : "拉比奧瓦爾", +"rabi1Label" : "拉比·奧瓦爾", "rabi2Label" : "拉比阿勒薩尼", -"jumada1Label" : "胡瑪達·奧瓦爾", -"jumada2Label" : "朱馬達·薩尼", -"rajabLabel" : "拉賈布", +"jumada1Label" : "朱馬達·阿瓦瓦爾", +"jumada2Label" : "祖瑪達·阿薩尼", +"rajabLabel" : "拉傑卜", "shaabanLabel" : "沙班", "ramadanLabel" : "齋月", "shawwalLabel" : "肖瓦爾", -"dhualqiLabel" : "杜阿爾-基達", -"dhualhiLabel" : "杜哈傑", +"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "嗯。", "shortSafarLabel" : "安全。", -"shortRabi1Label" : "拉比。一世", +"shortRabi1Label" : "拉比。我", "shortRabi2Label" : "拉比。二", -"shortJumada1Label" : "朱姆。一世", -"shortJumada2Label" : "朱姆。二", +"shortJumada1Label" : "滾。我", +"shortJumada2Label" : "滾。二", "shortRajabLabel" : "拉吉。", "shortShaabanLabel" : "沙。", "shortRamadanLabel" : "內存。", "shortShawwalLabel" : "肖。", -"shortDhualqiLabel" : "杜爾-Q", -"shortDhualhiLabel" : "杜爾-H", -"daySpanCountLabel" : "日", -"series" : "系列" +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H", +"daySpanCountLabel" : "天", +"series" : "系列", +"pdfHyperlinkLabel" : "打開網頁", +"pdfHyperlinkContentLabel" : "你想打開頁面嗎", +"pdfHyperlinkDialogOpenLabel" : "打開", +"pdfHyperlinkDialogCancelLabel" : "取消", +"afterDataGridFilteringLabel" : "後", +"afterOrEqualDataGridFilteringLabel" : "等於或之後", +"beforeDataGridFilteringLabel" : "前", +"beforeOrEqualDataGridFilteringLabel" : "早於或等於", +"beginsWithDataGridFilteringLabel" : "開始於", +"containsDataGridFilteringLabel" : "包含", +"doesNotBeginWithDataGridFilteringLabel" : "不以開頭", +"doesNotContainDataGridFilteringLabel" : "不含", +"doesNotEndWithDataGridFilteringLabel" : "不結束於", +"doesNotEqualDataGridFilteringLabel" : "不等於", +"emptyDataGridFilteringLabel" : "空的", +"endsWithDataGridFilteringLabel" : "以。。結束", +"equalsDataGridFilteringLabel" : "等於", +"greaterThanDataGridFilteringLabel" : "比...更棒", +"greaterThanOrEqualDataGridFilteringLabel" : "大於或等於", +"lessThanDataGridFilteringLabel" : "少於", +"lessThanOrEqualDataGridFilteringLabel" : "小於或等於", +"notEmptyDataGridFilteringLabel" : "不是空的", +"notNullDataGridFilteringLabel" : "不為空", +"nullDataGridFilteringLabel" : "無效的", +"sortSmallestToLargestDataGridFilteringLabel" : "從小到大排序", +"sortLargestToSmallestDataGridFilteringLabel" : "從大到小排序", +"sortAToZDataGridFilteringLabel" : "從 A 到 Z 排序", +"sortZToADataGridFilteringLabel" : "將 Z 排序為 A", +"sortOldestToNewestDataGridFilteringLabel" : "從舊到新排序", +"sortNewestToOldestDataGridFilteringLabel" : "從最新到最舊排序", +"textFiltersDataGridFilteringLabel" : "文本過濾器", +"numberFiltersDataGridFilteringLabel" : "數字過濾器", +"dateFiltersDataGridFilteringLabel" : "日期過濾器", +"searchDataGridFilteringLabel" : "搜索", +"noMatchesDataGridFilteringLabel" : "無匹配", +"okDataGridFilteringLabel" : "好的", +"cancelDataGridFilteringLabel" : "取消", +"showRowsWhereDataGridFilteringLabel" : "顯示行在哪裡", +"andDataGridFilteringLabel" : "和", +"orDataGridFilteringLabel" : "或者", +"selectAllDataGridFilteringLabel" : "全選", +"sortAndFilterDataGridFilteringLabel" : "排序和過濾", +"clearFilterDataGridFilteringLabel" : "清除過濾器", +"fromDataGridFilteringLabel" : "從" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb index d4f0aeb84..9199e0c28 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb @@ -18,6 +18,15 @@ "passwordDialogInvalidPasswordLabel" : "Iphasiwedi engavumelekile", "pdfPasswordDialogOpenLabel" : "VULA", "pdfPasswordDialogCancelLabel" : "KHANSELA", +"pdfSignaturePadDialogHeaderTextLabel" : "Dweba isiginesha yakho", +"pdfSignaturePadDialogPenColorLabel" : "Umbala Wepeni", +"pdfSignaturePadDialogClearLabel" : "SULA", +"pdfSignaturePadDialogSaveLabel" : "GCINA", +"pdfTextSelectionMenuCopyLabel" : "Kopisha", +"pdfTextSelectionMenuHighlightLabel" : "Gqamisa", +"pdfTextSelectionMenuStrikethroughLabel" : "Thayela", +"pdfTextSelectionMenuUnderlineLabel" : "Dwebela", +"pdfTextSelectionMenuSquigglyLabel" : "Squiggly", "allowedViewDayLabel" : "Usuku", "allowedViewWeekLabel" : "Isonto", "allowedViewWorkWeekLabel" : "Iviki Lomsebenzi", @@ -55,5 +64,49 @@ "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "U-Dhu'l-H", "daySpanCountLabel" : "Usuku", -"series" : "Uchungechunge" +"series" : "Uchungechunge", +"pdfHyperlinkLabel" : "Vula Ikhasi Lewebhu", +"pdfHyperlinkContentLabel" : "Ingabe ufuna ukuvula ikhasi ku", +"pdfHyperlinkDialogOpenLabel" : "VULA", +"pdfHyperlinkDialogCancelLabel" : "KHANSELA", +"afterDataGridFilteringLabel" : "Ngemva", +"afterOrEqualDataGridFilteringLabel" : "Ngemva Noma Ukulingana", +"beforeDataGridFilteringLabel" : "Ngaphambili", +"beforeOrEqualDataGridFilteringLabel" : "Ngaphambi Noma Ukulingana", +"beginsWithDataGridFilteringLabel" : "Iqala Nga", +"containsDataGridFilteringLabel" : "Iqukethe", +"doesNotBeginWithDataGridFilteringLabel" : "Ayiqali Nge", +"doesNotContainDataGridFilteringLabel" : "Ayiqukethe", +"doesNotEndWithDataGridFilteringLabel" : "Akugcini Nge", +"doesNotEqualDataGridFilteringLabel" : "Ayilingani", +"emptyDataGridFilteringLabel" : "Akunalutho", +"endsWithDataGridFilteringLabel" : "Iphetha Nge", +"equalsDataGridFilteringLabel" : "Kuyalingana", +"greaterThanDataGridFilteringLabel" : "Okukhulu Kunokuthi", +"greaterThanOrEqualDataGridFilteringLabel" : "Okukhulu Kunokuba Kulinganayo", +"lessThanDataGridFilteringLabel" : "Ngaphansi kwe", +"lessThanOrEqualDataGridFilteringLabel" : "Ngaphansi Kwanoma Kuyalingana", +"notEmptyDataGridFilteringLabel" : "Akunalutho", +"notNullDataGridFilteringLabel" : "Hhayi Null", +"nullDataGridFilteringLabel" : "Null", +"sortSmallestToLargestDataGridFilteringLabel" : "Hlunga Okuncane Ukuya Kwekhulu Kakhulu", +"sortLargestToSmallestDataGridFilteringLabel" : "Hlunga Okukhulu Kuye Kokuncane Kakhulu", +"sortAToZDataGridFilteringLabel" : "Hlunga A ukuya ku-Z", +"sortZToADataGridFilteringLabel" : "Hlunga u-Z ukuya ku-A", +"sortOldestToNewestDataGridFilteringLabel" : "Hlunga Okudala Kunazo Zonke", +"sortNewestToOldestDataGridFilteringLabel" : "Hlunga Okusha Kwakudala", +"textFiltersDataGridFilteringLabel" : "Izihlungi Zombhalo", +"numberFiltersDataGridFilteringLabel" : "Izihlungi Zezinombolo", +"dateFiltersDataGridFilteringLabel" : "Izihlungi Zedethi", +"searchDataGridFilteringLabel" : "Sesha", +"noMatchesDataGridFilteringLabel" : "Akukho okufanayo", +"okDataGridFilteringLabel" : "KULUNGILE", +"cancelDataGridFilteringLabel" : "Khansela", +"showRowsWhereDataGridFilteringLabel" : "Bonisa imigqa lapho", +"andDataGridFilteringLabel" : "Futhi", +"orDataGridFilteringLabel" : "Noma", +"selectAllDataGridFilteringLabel" : "Khetha konke", +"sortAndFilterDataGridFilteringLabel" : "Hlunga futhi Hlunga", +"clearFilterDataGridFilteringLabel" : "Sula Isihlungi", +"fromDataGridFilteringLabel" : "Kusuka" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/pubspec.yaml b/packages/syncfusion_localizations/pubspec.yaml index 34d5730d8..06639979b 100644 --- a/packages/syncfusion_localizations/pubspec.yaml +++ b/packages/syncfusion_localizations/pubspec.yaml @@ -1,17 +1,21 @@ name: syncfusion_localizations description: Syncfusion Localizations package contains localized text for 77 cultures for all the applicable Syncfusion Flutter Widgets. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_localizations environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0 dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.20.0" + intl: '>=0.18.1 <0.21.0' syncfusion_flutter_core: path: ../syncfusion_flutter_core + + + + flutter: \ No newline at end of file diff --git a/packages/syncfusion_officechart/CHANGELOG.md b/packages/syncfusion_officechart/CHANGELOG.md index 2e000e233..99e7ed336 100644 --- a/packages/syncfusion_officechart/CHANGELOG.md +++ b/packages/syncfusion_officechart/CHANGELOG.md @@ -1,4 +1,48 @@ -## [Unreleased] +## Unreleased + +**General** + +* Upgraded the archive package. The Flutter XlsIO widget supports archive package version 4.0.0 and above. +* The compatible version of our Flutter OfficeChart widget has been updated to Flutter SDK 3.32.0 + +## [29.1.39] - 22/04/2025 + +**General** + +* The minimum Dart version has been updated to 3.7. + +## [29.1.33] - 25/03/2025 + +**General** + +* The compatible version of our Flutter OfficeChart widget has been updated to Flutter SDK 3.29.0. +* The Syncfusion® Flutter OfficeChart example sample have been updated to support [kotlin build scripts](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) in Android platform. +* The Syncfusion® Flutter OfficeChart example sample have been updated to support [Swift package manager](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers) in macOS and iOS platforms. + +## [28.2.6] - 18/02/2025 + +**General** + +* The minimum Dart version of our Flutter widgets has been updated to 3.4 from 3.3. + +## [28.1.36] - 24/12/2024 + +**General** + +* The compatible version of our Flutter OfficeChart widget has been updated to Flutter SDK 3.27.0. + +## [28.1.33] - 12/12/2024 + +**General** + +* All of our Syncfusion® Flutter widgets have been updated to support [`WebAssembly`](https://docs.flutter.dev/platform-integration/web/wasm) (WASM) as a compilation target for building web applications. +* The minimum Dart version of our Flutter widgets has been updated to 3.3 from 2.17. + +## [27.1.48] - 09/18/2024 + +**General** + +* The compatible version of our Flutter OfficeChart widget has been updated to Flutter SDK 3.24.0. **Features** diff --git a/packages/syncfusion_officechart/README.md b/packages/syncfusion_officechart/README.md index 0ff296ed0..379686135 100644 --- a/packages/syncfusion_officechart/README.md +++ b/packages/syncfusion_officechart/README.md @@ -46,15 +46,12 @@ Explore the full capability of our Flutter widgets on your device by installing

- - + +

- -

-

@@ -479,18 +476,11 @@ workbook.dispose(); ``` ## 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). +* For any other queries, contact our [Syncfusion support team](https://support.syncfusion.com/support/tickets/create) 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). * To renew the 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), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-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. - - - - - - - +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [.NET MAUI](https://www.syncfusion.com/maui-controls?utm_source=pubdev&utm_medium=listing&utm_campaign=flutter-charts-pubdev), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_officechart/example/README.md b/packages/syncfusion_officechart/example/README.md new file mode 100644 index 000000000..8aaa2a895 --- /dev/null +++ b/packages/syncfusion_officechart/example/README.md @@ -0,0 +1,3 @@ +# xlsio_example + +Demo for creating a Excel file with charts using syncfusion_officechart package. diff --git a/packages/syncfusion_officechart/example/android/.gitignore b/packages/syncfusion_officechart/example/android/.gitignore index 6f568019d..be3943c96 100644 --- a/packages/syncfusion_officechart/example/android/.gitignore +++ b/packages/syncfusion_officechart/example/android/.gitignore @@ -5,9 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties **/*.keystore **/*.jks diff --git a/packages/syncfusion_officechart/example/android/app/build.gradle.kts b/packages/syncfusion_officechart/example/android/app/build.gradle.kts new file mode 100644 index 000000000..cdf716b6e --- /dev/null +++ b/packages/syncfusion_officechart/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.officechart_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.officechart_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml index c208884f3..399f6981d 100644 --- a/packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml index 3f41384db..4d7d81a0e 100644 --- a/packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml +++ b/packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - - + + + + + + + + diff --git a/packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt b/packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt new file mode 100644 index 000000000..710a56310 --- /dev/null +++ b/packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.officechart_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_officechart/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_officechart/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_officechart/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_officechart/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_officechart/example/android/build.gradle.kts b/packages/syncfusion_officechart/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_officechart/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_officechart/example/android/gradle.properties b/packages/syncfusion_officechart/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_officechart/example/android/gradle.properties +++ b/packages/syncfusion_officechart/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_officechart/example/android/settings.gradle.kts b/packages/syncfusion_officechart/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_officechart/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_officechart/example/ios/.gitignore b/packages/syncfusion_officechart/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_officechart/example/ios/.gitignore +++ b/packages/syncfusion_officechart/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj index 6b50bd568..255896b9f 100644 --- a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -125,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -179,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -208,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -298,17 +371,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -316,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -432,17 +550,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -459,17 +572,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -480,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..d795332e1 100644 --- a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + @@ -61,8 +89,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Officechart Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - xlsio_example + officechart_example CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart index 12863e435..856ba8bdb 100644 --- a/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart +++ b/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:open_file/open_file.dart' as open_file; import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; ///To save the Excel file in the device @@ -19,8 +20,9 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else { path = await PathProviderPlatform.instance.getApplicationSupportPath(); } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + final File file = File( + Platform.isWindows ? '$path\\$fileName' : '$path/$fileName', + ); await file.writeAsBytes(bytes, flush: true); if (Platform.isAndroid || Platform.isIOS) { //Launch the file (used open_file package) @@ -30,7 +32,8 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else if (Platform.isMacOS) { await Process.run('open', ['$path/$fileName'], runInShell: true); } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); + await Process.run('xdg-open', [ + '$path/$fileName', + ], runInShell: true); } } diff --git a/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart b/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart index a455ce682..38e168526 100644 --- a/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart +++ b/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart @@ -1,15 +1,22 @@ ///Dart imports import 'dart:async'; import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; +// ignore: depend_on_referenced_packages +import 'package:web/web.dart'; -///To save the Excel file in the device -///To save the Excel file in the device +// Function to save and launch a file for download in a web environment Future saveAndLaunchFile(List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); + final HTMLAnchorElement anchor = + document.createElement('a') as HTMLAnchorElement + ..href = 'data:application/octet-stream;base64,${base64Encode(bytes)}' + ..style.display = 'none' + ..download = fileName; + + // Insert the new element into the DOM + document.body!.appendChild(anchor); + + // Initiate the download + anchor.click(); + // Clean up the DOM by removing the anchor element + document.body!.removeChild(anchor); } diff --git a/packages/syncfusion_officechart/example/lib/main.dart b/packages/syncfusion_officechart/example/lib/main.dart index 08a6b4353..00312ea6a 100644 --- a/packages/syncfusion_officechart/example/lib/main.dart +++ b/packages/syncfusion_officechart/example/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column, Alignment; +// ignore: depend_on_referenced_packages +import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column; import 'package:syncfusion_officechart/officechart.dart'; //Local imports @@ -7,11 +8,14 @@ import 'helper/save_file_mobile.dart' if (dart.library.html) 'helper/save_file_web.dart'; void main() { - runApp(CreateOfficeChartWidget()); + runApp(const CreateOfficeChartWidget()); } /// Represents the office chart widget class. class CreateOfficeChartWidget extends StatelessWidget { + // Constructor with a named key parameter + const CreateOfficeChartWidget({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return const MaterialApp( @@ -24,7 +28,7 @@ class CreateOfficeChartWidget extends StatelessWidget { class CreateOfficeChartStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreateOfficeChartStatefulWidget] class. const CreateOfficeChartStatefulWidget({Key? key, required this.title}) - : super(key: key); + : super(key: key); /// title. final String title; @@ -37,22 +41,20 @@ class _CreateOfficeChartState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( - child: const Text('Generate Excel Chart'), style: TextButton.styleFrom( - primary: Colors.white, + foregroundColor: Colors.white, backgroundColor: Colors.lightBlue, - onSurface: Colors.grey, + disabledForegroundColor: Colors.grey, ), onPressed: generateOfficeChart, - ) + child: const Text('Generate Excel Chart'), + ), ], ), ), diff --git a/packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.cc index e71a16d23..eb768d3a6 100644 --- a/packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); } diff --git a/packages/syncfusion_officechart/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_officechart/example/linux/flutter/generated_plugins.cmake index 51436ae8c..eb06039dc 100644 --- a/packages/syncfusion_officechart/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_officechart/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_file_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) @@ -13,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_officechart/example/macos/.gitignore b/packages/syncfusion_officechart/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_officechart/example/macos/.gitignore +++ b/packages/syncfusion_officechart/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift index 7ce0a99b8..4d6f3f9e6 100644 --- a/packages/syncfusion_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -2,13 +2,13 @@ // Generated file. Do not edit. // -// clang-format off - import FlutterMacOS import Foundation -import path_provider_macos +import open_file_mac +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..97c34d3e6 100644 --- a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* officechart_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "officechart_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* officechart_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* officechart_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/officechart_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/officechart_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/officechart_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/officechart_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/officechart_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/officechart_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..42e2443a8 100644 --- a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_officechart/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_officechart/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_officechart/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_officechart/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_officechart/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_officechart/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_officechart/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_officechart/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@
+ + + + diff --git a/packages/syncfusion_officechart/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_officechart/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..9495ea9de 100644 --- a/packages/syncfusion_officechart/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_officechart/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = officechart_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.officechartExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_officechart/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_officechart/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_officechart/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_officechart/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_officechart/example/pubspec.yaml b/packages/syncfusion_officechart/example/pubspec.yaml index 12ec7b558..3aad8e935 100644 --- a/packages/syncfusion_officechart/example/pubspec.yaml +++ b/packages/syncfusion_officechart/example/pubspec.yaml @@ -2,7 +2,7 @@ name: officechart_example description: Demo for creating a Excel file with chart using syncfusion_officechart package. environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: diff --git a/packages/syncfusion_officechart/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_officechart/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_officechart/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_officechart/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_officechart/lib/officechart.dart b/packages/syncfusion_officechart/lib/officechart.dart index 57a3a8e0f..ee0d45daa 100644 --- a/packages/syncfusion_officechart/lib/officechart.dart +++ b/packages/syncfusion_officechart/lib/officechart.dart @@ -1,26 +1,27 @@ -library officechart; - -import 'dart:convert'; import 'dart:core'; -import 'package:archive/archive.dart'; - -import 'package:syncfusion_flutter_xlsio/xlsio.dart'; -import 'package:xml/xml.dart'; - -part 'src/chart/chart_axis.dart'; -part 'src/chart/chart_category_axis.dart'; -part 'src/chart/chart_collection.dart'; -part 'src/chart/chart_datalabel.dart'; -part 'src/chart/chart_enum.dart'; +export 'src/chart/chart_axis.dart' show ChartAxis; +export 'src/chart/chart_category_axis.dart' show ChartCategoryAxis; +export 'src/chart/chart_collection.dart' show ChartCollection; +export 'src/chart/chart_datalabel.dart' show ChartDataLabels; +export 'src/chart/chart_enum.dart' + show + ExcelChartType, + ExcelChartLinePattern, + ExcelAxisType, + ExcelChartMarkerType, + ExcelLegendPosition; +export 'src/chart/chart_fomat_impl.dart' hide ChartformatImpl; +export 'src/chart/chart_format.dart' show ChartFormat; //Src -part 'src/chart/chart_impl.dart'; -part 'src/chart/chart_legend.dart'; -part 'src/chart/chart_plotarea.dart'; -part 'src/chart/chart_serialization.dart'; -part 'src/chart/chart_serie.dart'; -part 'src/chart/chart_series_collection.dart'; -part 'src/chart/chart_text_area.dart'; -part 'src/chart/chart_value_axis.dart'; - -//testing +export 'src/chart/chart_impl.dart' show Chart; +export 'src/chart/chart_legend.dart' show ChartLegend; +export 'src/chart/chart_plotarea.dart' show ChartPlotArea; +export 'src/chart/chart_serialization.dart' show ChartSerialization; +export 'src/chart/chart_serie.dart' show ChartSerie; +export 'src/chart/chart_serie_dataformat.dart' show ChartSerieDataFormat; +export 'src/chart/chart_series_collection.dart' show ChartSeriesCollection; +export 'src/chart/chart_text_area.dart' show ChartTextArea; +export 'src/chart/chart_value_axis.dart' show ChartValueAxis; +export 'src/chart/chartserie_dataformat_impl.dart' + hide ChartSerieDataFormatImpl; diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_axis.dart b/packages/syncfusion_officechart/lib/src/chart/chart_axis.dart index 79add9163..5dca5bc19 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_axis.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_axis.dart @@ -1,4 +1,5 @@ -part of officechart; +import 'chart_impl.dart'; +import 'chart_text_area.dart'; /// Represents an axis on the chart. class ChartAxis { @@ -38,6 +39,16 @@ class ChartAxis { _titleArea = value; } + /// Gets the parent chart. + Chart get parentChart { + return _parentChart; + } + + /// Sets the parent chart. + set parentChart(Chart? value) { + _parentChart = value!; + } + /// Gets chart axis title. String? get title { if (_titleArea == null) { @@ -52,7 +63,7 @@ class ChartAxis { } /// Gets indicates whether chart axis have title or not. - bool get _hasAxisTitle { + bool get hasAxisTitle { return _titleArea != null; } @@ -77,4 +88,14 @@ class ChartAxis { _maximumValue = value; _isAutoMax = false; } + + /// Gets indicates whether auto max or not. + bool get isAutoMax { + return _isAutoMax; + } + + // ignore: public_member_api_docs + bool get isAutoMin { + return _isAutoMin; + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart b/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart index 5d87b08ed..3f3ab4566 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart @@ -1,4 +1,5 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; /// Represents an axis on the chart. class ChartCategoryAxis extends ChartAxis { @@ -6,7 +7,7 @@ class ChartCategoryAxis extends ChartAxis { ChartCategoryAxis(Worksheet worksheet, Chart chart) { _sheet = worksheet; _chart = chart; - super._parentChart = _chart; + super.parentChart = _chart; } /// Parent worksheet. @@ -15,17 +16,23 @@ class ChartCategoryAxis extends ChartAxis { /// True to cut unused plot area. otherwise False. Default for area and surface charts. // ignore: prefer_final_fields - bool _isBetween = false; + bool isBetween = false; /// Parent chart. late Chart _chart; /// sets the category labels for the chart. - set _categoryLabels(Range? value) { + // ignore: avoid_setters_without_getters + set categoryLabels(Range? value) { final ChartSeriesCollection coll = _chart.series; final int iLen = coll.count; for (int i = 0; i < iLen; i++) { - coll[i]._categoryLabels = value; + coll[i].categoryLabels = value; } } + + /// Gets the parent chart. + Chart get chart { + return _chart; + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart b/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart index 6a4a60422..340860a7d 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart @@ -1,4 +1,7 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import 'package:xml/xml.dart'; + +import '../../officechart.dart'; /// Represents the worksheet rows. class ChartCollection extends ChartHelper { @@ -55,11 +58,11 @@ class ChartCollection extends ChartHelper { /// ``` Chart add() { final Chart chart = Chart(_worksheet); - chart._series = ChartSeriesCollection(_worksheet, chart); - chart._primaryCategoryAxis = ChartCategoryAxis(_worksheet, chart); - chart._primaryValueAxis = ChartValueAxis(_worksheet, chart); - chart._primaryValueAxis.hasMajorGridLines = true; - chart._plotArea = ChartPlotArea(_worksheet, chart); + chart.series = ChartSeriesCollection(_worksheet, chart); + chart.primaryCategoryAxis = ChartCategoryAxis(_worksheet, chart); + chart.primaryValueAxis = ChartValueAxis(_worksheet, chart); + chart.primaryValueAxis.hasMajorGridLines = true; + chart.plotArea = ChartPlotArea(_worksheet, chart); innerList.add(chart); chart.name = 'Chart${innerList.length}'; chart.index = innerList.length; @@ -75,17 +78,36 @@ class ChartCollection extends ChartHelper { /// Serialize the charts. @override - void serializeCharts(Worksheet sheet) { + void serializeChartsSync(Worksheet sheet) { + _chartSerialization ??= ChartSerialization(sheet.workbook); + + _chartSerialization!.saveCharts(sheet); + } + + /// Serialize the charts. + @override + Future serializeCharts(Worksheet sheet) async { + _chartSerialization ??= ChartSerialization(sheet.workbook); + + _chartSerialization!.saveChartsAsync(sheet); + } + + /// Serialize the chart drawings. + @override + void serializeChartDrawingSync(XmlBuilder builder, Worksheet sheet) { _chartSerialization ??= ChartSerialization(sheet.workbook); - _chartSerialization!._saveCharts(sheet); + _chartSerialization!.serializeChartDrawing(builder, sheet); } /// Serialize the chart drawings. @override - void serializeChartDrawing(XmlBuilder builder, Worksheet sheet) { + Future serializeChartDrawing( + XmlBuilder builder, + Worksheet sheet, + ) async { _chartSerialization ??= ChartSerialization(sheet.workbook); - _chartSerialization!._serializeChartDrawing(builder, sheet); + _chartSerialization!.serializeChartDrawingAsync(builder, sheet); } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart b/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart index 25494e487..32774151f 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart @@ -1,4 +1,5 @@ -part of officechart; +import 'chart_serie.dart'; +import 'chart_text_area.dart'; /// Represents the Chart data labels class. class ChartDataLabels { diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart b/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart index a81dff8cc..4d7a070cc 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart @@ -1,16 +1,26 @@ -part of officechart; - /// Specifies the chart type. enum ExcelChartType { /// Represents the clustered column chart. column, + /// Represnts the 3D column chart + column3D, + /// Represents the column stacked chart. columnStacked, /// Represents the 100% stacked column chart. columnStacked100, + /// Represents the 3D clustered column chart. + columnClustered3D, + + /// Represents the 3D stacked column chart. + columnStacked3D, + + /// Represents the 3D 100% stacked column chart. + columnStacked1003D, + /// Represents the clustered bar chart. bar, @@ -20,6 +30,15 @@ enum ExcelChartType { /// Represents the 100% stacked bar chart. barStacked100, + /// Represents the 3D stacked bar chart. + barStacked3D, + + /// Represents the 3D clustered bar chart. + barClustered3D, + + /// Represents the 3D 100% stacked bar chart. + barStacked1003D, + /// Represents the line chart. line, @@ -29,9 +48,30 @@ enum ExcelChartType { /// Represents the 100% stacked line chart. lineStacked100, + /// Represents the line chart with markers. + lineMarkers, + + /// Represents the stacked line chart with markers. + lineMarkersStacked, + + /// Represents the 100% stacked line chart with markers. + lineMarkersStacked100, + + /// Represents the 3D line chart. + line3D, + /// Represents the Pie chart. pie, + /// Represents the Pie chart. + pie3D, + + /// Represents the pie of pie chart. + pieOfPie, + + /// Represents the bar of pie chart. + pieBar, + /// Represents the area chart. area, @@ -39,7 +79,25 @@ enum ExcelChartType { areaStacked, /// Represents the 100% stacked area chart. - areaStacked100 + areaStacked100, + + /// Represents the stock chart with high, low and close values. + stockHighLowClose, + + /// Represents the stock chart with open, high, low and close values. + stockOpenHighLowClose, + + /// Represents the stock chart with volume, high, low and close values. + stockVolumeHighLowClose, + + /// Represents the stock chart with volume, open, high, low and close values. + stockVolumeOpenHighLowClose, + + /// Represents the doughnut chart. + doughnut, + + /// Represents the doughnut Exploded chart. + doughnutExploded, } /// Specifies the line pattern for the border. @@ -104,3 +162,36 @@ enum ExcelAxisType { /// Axis displays data series. serie, } + +/// Specifies the marker style for a point or series in a line chart, scatter chart, or radar chart. +enum ExcelChartMarkerType { + /// Represents no markers. + none, + + /// Represents square markers. + square, + + /// Represents circular marker + circle, + + /// Represents diamond shaped markers. + diamond, + + /// Represents triangle markers. + triangle, + + /// Represents square markers with X. + xSquare, + + /// Represents dow jones style custom marker + dowJones, + + /// Represents plus sign marker + plusSign, + + /// Represents square markers with asterisk + starSquare, + + /// Represents standard deviation style custome marker + standardDeviation, +} diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_fomat_impl.dart b/packages/syncfusion_officechart/lib/src/chart/chart_fomat_impl.dart new file mode 100644 index 000000000..ddaf82a50 --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/chart/chart_fomat_impl.dart @@ -0,0 +1,129 @@ +import '../../officechart.dart'; + +///Represents chart format implement class +class ChartformatImpl implements ChartFormat { + /// Create an instances of [ChartformatImpl] class. + ChartformatImpl(Chart chart) { + _firstSliceAngle = 0; + _doughnutHoleSize = 75; + _gapWidth = 150; + _gapDepth = 150; + _pieSecondSize = 75; + _chart = chart; + } + + /// Represents the angle of the first pie-chart or dough-nut chart slice, in degrees (clockwise from vertical). + /// Can be a value from 0 through 360. + late int _firstSliceAngle; + + /// Represents the hole size is expressed as a percentage of the chart size, between 10 and 90 percent. + late int _doughnutHoleSize; + + /// Represents the space between bar/column clusters, as a percentage of the bar/column width in bar/column charts. + /// Represents the space between the primary and secondary sections, in Pie of Pie and Bar of Pie charts. + late int _gapWidth; + + /// Represents or sets the distance between the data series in a 3-D chart, as a percentage of the marker width.( 0 - 500 ) + late int _gapDepth; + + /// Represents percentage of the size of the secondary pie. ( 5 - 200 ). + late int _pieSecondSize; + + /// Parent chart. + late Chart _chart; + + ///Get first slice angle + @override + int get firstSliceAngle { + return _firstSliceAngle; + } + + ///Set first slice angle + @override + set firstSliceAngle(int value) { + if (value < 0 || value > 360) { + throw Exception('First slice angle'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as ChartformatImpl) + ._firstSliceAngle = value; + } + } + + ///Get doughnut chart hole size + @override + int get holeSizePercent { + return _doughnutHoleSize; + } + + ///Set doughnut chart hole size + @override + set holeSizePercent(int value) { + if (value < 0 || value > 90) { + throw Exception('DonutHoleSize'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as ChartformatImpl) + ._doughnutHoleSize = value; + } + } + + ///Get gap width for chart series + @override + int get gapWidth { + return _gapWidth; + } + + ///Set gapWidth for chart series + @override + set gapWidth(int value) { + if (value < 0 || value > 500) { + throw Exception('gapWidth'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as ChartformatImpl)._gapWidth = + value; + } + } + + ///Get second pie/bar size + @override + int get pieSecondSize { + return _pieSecondSize; + } + + ///Set second pie/bar size + @override + set pieSecondSize(int value) { + if (value < 0 || value > 200) { + throw Exception('gapWidth'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as ChartformatImpl) + ._pieSecondSize = value; + } + } + + ///Get gap depth for chart series + @override + int get gapDepth { + return _gapDepth; + } + + ///Set gap depth for chart series + @override + set gapDepth(int value) { + if (value < 0 || value > 500) { + throw Exception('gapDepth'); + } + for (int i = 0; i < _chart.series.count; i++) { + final ChartSerie serie1 = _chart.series[i]; + (serie1.serieFormat.commonSerieOptions as ChartformatImpl)._gapDepth = + value; + } + } +} diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_format.dart b/packages/syncfusion_officechart/lib/src/chart/chart_format.dart new file mode 100644 index 000000000..8b3ba1afa --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/chart/chart_format.dart @@ -0,0 +1,19 @@ +///Represents chart format +class ChartFormat { + /// Represents the angle of first slice in a doughnut or pie chart., in degrees (clockwise from vertical). + /// Can be a value from 0 through 360. + late int firstSliceAngle; + + /// Represents the hole size percentage of a doughnut chart., between 10 and 90 percent. + late int holeSizePercent; + + /// Represents the space between bar/column clusters, as a percentage of the bar/column width in bar/column charts. + /// Represents the space between the primary and secondary sections, in Pie of Pie and Bar of Pie charts. + late int gapWidth; + + /// Represents the distance between the data series in a 3-D chart, as a percentage of the marker width.( 0 - 500 ); + late int gapDepth; + + /// Represents percentage of the size of the secondary pie. ( 5 - 200 ). + late int pieSecondSize; +} diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart b/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart index 69871f777..76d03612b 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart @@ -1,4 +1,7 @@ -part of officechart; +// ignore_for_file: unnecessary_getters_setters + +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; /// Represents a chart sheet in the workbook. class Chart { @@ -49,7 +52,9 @@ class Chart { /// Represent the clustered chart collection. final List _chartsCluster = [ ExcelChartType.bar, - ExcelChartType.column + ExcelChartType.column, + ExcelChartType.columnClustered3D, + ExcelChartType.barClustered3D, ]; /// Represent the stacked chart collection. @@ -57,7 +62,10 @@ class Chart { ExcelChartType.barStacked, ExcelChartType.columnStacked, ExcelChartType.lineStacked, + ExcelChartType.lineMarkersStacked, ExcelChartType.areaStacked, + ExcelChartType.columnStacked3D, + ExcelChartType.barStacked3D, ]; /// Represent 100% charts.Here each value in a series is shown as a portion of 100%. @@ -65,12 +73,43 @@ class Chart { ExcelChartType.columnStacked100, ExcelChartType.barStacked100, ExcelChartType.lineStacked100, - ExcelChartType.areaStacked100 + ExcelChartType.areaStacked100, + ExcelChartType.columnStacked1003D, + ExcelChartType.barStacked1003D, + ExcelChartType.areaStacked100, + ExcelChartType.lineMarkersStacked100, ]; /// Chart type. ExcelChartType _chartType = ExcelChartType.column; + ///Indicated wheather chart type is 3D + bool _is3DChart = false; + + ///Indicates wheather chart type is bar/column + bool _isColumnOrBar = false; + + /// Indicates whether rotation has default value. + bool _isDefaultRotation = true; + + /// Indicates whether elevation has default value. + bool _isdefaultElevation = true; + + ///Represents rotation of 3D chart + int _rotation = 20; + + ///Represents perspective of 3D chart + int _perspective = 15; + + ///Represents elvation angle of 3D chart + int _elevationAngle = 15; + + /// Depth of points relative to width. + int _depthPercent = 100; + + ///Indicates whether the chart axes are at right angles, independent of chart rotation or elevation. + bool _rightAngleAxes = false; + /// Represent chart index. late int index; @@ -200,7 +239,14 @@ class Chart { set chartType(ExcelChartType value) { _chartType = value; if (!_chartType.toString().contains('area')) { - _primaryCategoryAxis._isBetween = true; + _primaryCategoryAxis.isBetween = true; + } + if (value.toString().endsWith('3D')) { + _is3DChart = true; + } + if (value.toString().contains('column') || + value.toString().contains('bar')) { + _isColumnOrBar = true; } } @@ -449,17 +495,37 @@ class Chart { } /// Gets a boolean value indicating whether the chart has plot area. - bool get _hasPlotArea { + bool get hasPlotArea { return _plotArea != null; } + // ignore: public_member_api_docs + bool get is3DChart { + return _is3DChart; + } + + // ignore: public_member_api_docs + bool get isdefaultElevation { + return _isdefaultElevation; + } + + // ignore: public_member_api_docs + bool get isDefaultRotation { + return _isDefaultRotation; + } + + // ignore: public_member_api_docs + bool get isColumnOrBar { + return _isColumnOrBar; + } + /// True if chart has a category axis. False otherwise. Read-only. - bool get _isCategoryAxisAvail { + bool get isCategoryAxisAvail { return true; } /// True if chart has a value axis. False otherwise. Read-only. - bool get _isValueAxisAvail { + bool get isValueAxisAvail { return true; } @@ -494,7 +560,10 @@ class Chart { final int iCount = _series.count; if (dataRange == null && iCount != 0) { - throw 'This property supported only in chart where can detect data range.'; + final Error error = ArgumentError( + 'This property supported only in chart where can detect data range.', + ); + throw error; } if (_bSeriesInRows != value) { @@ -603,11 +672,40 @@ class Chart { return _series; } + set series(ChartSeriesCollection value) { + _series = value; + } + /// Gets the data range for the chart series. Range? get dataRange { return _dataRange; } + ///Set the primaryCategoryAxis + set primaryCategoryAxis(ChartCategoryAxis value) { + _primaryCategoryAxis = value; + } + + ///Set the primaryValueAxis + set primaryValueAxis(ChartValueAxis value) { + _primaryValueAxis = value; + } + + ///Set the chart plot area + set plotArea(ChartPlotArea value) { + _plotArea = value; + } + + // ignore: public_member_api_docs + set worksheet(Worksheet value) { + _worksheet = value; + } + + // ignore: public_member_api_docs + Worksheet get worksheet { + return _worksheet; + } + /// sets the data range for the chart series. /// /// ```dart @@ -643,10 +741,255 @@ class Chart { } } + /// sets the rotation of the 3D chart view + /// (the rotation of the plot area around the z-axis, in degrees)-(0 to 360 degrees). + /// The value of this property must be from 0 to 360, except for 3-D bar charts, + /// where the value must be from 0 to 44. The default value is 20. Applies only to 3-D charts. + /// The following code illustrates how to set see cref="Rotation" for 3-D charts. + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.rotation = 20; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get rotation { + return _rotation; + } + + ///Set the rotation of 3D view of chart. + set rotation(int value) { + if (value < 0 || value > 360) { + throw Exception('rotation'); + } + _rotation = value; + _isDefaultRotation = false; + } + + /// Get the perspective for the 3D chart view (0 to 100). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.perspective = 45; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get perspective { + return _perspective; + } + + ///set the persepective of 3D chart + set perspective(int value) { + if (value < 0 || value > 100) { + throw Exception('elevation'); + } + + _perspective = value; + } + + /// Get the elevation of the 3-D chart view, in degrees (-90 to +90 degrees). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.elevation = 15; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get elevation { + return _elevationAngle; + } + + ///set the elevation of 3D chart + set elevation(int value) { + if (value < -90 || value > 90) { + throw Exception('elevation'); + } + _elevationAngle = value; + _isdefaultElevation = false; + } + + /// Returns the depth of a 3-D chart as a percentage of the chart width + /// (between 20 and 2000 percent). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.depthPercent = 45; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + int get depthPercent { + return _depthPercent; + } + + ///set the elevation of 3D chart + set depthPercent(int value) { + if (value < -90 || value > 90) { + throw Exception('elevation'); + } + _depthPercent = value; + } + + /// True if the chart axes are at right angles, independent of chart rotation or elevation. otherwise False. + /// Returns the RightAngleAxes of a 3-D chart as a percentage of the chart width + /// (between 20 and 2000 percent). + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// sheet.getRangeByName('A11').text = 'Venue'; + /// sheet.getRangeByName('A12').text = 'Seating & Decor'; + /// sheet.getRangeByName('A13').text = 'Technical Team'; + /// sheet.getRangeByName('A14').text = 'performers'; + /// sheet.getRangeByName('A15').text = "performer's Transport"; + /// sheet.getRangeByName('A16').text = "performer's stay"; + /// sheet.getRangeByName('A17').text = 'Marketing'; + /// sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + /// sheet.getRangeByName('B11').number = 17500; + /// sheet.getRangeByName('B12').number = 1828; + /// sheet.getRangeByName('B13').number = 800; + /// sheet.getRangeByName('B14').number = 14000; + /// sheet.getRangeByName('B15').number = 2600; + /// sheet.getRangeByName('B16').number = 4464; + /// sheet.getRangeByName('B17').number = 2700; + /// final ChartCollection charts = ChartCollection(sheet); + /// final Chart chart = charts.add(); + /// chart.chartType = ExcelChartType.line3D; + /// chart.dataRange = sheet.getRangeByName('A11:B17'); + /// chart.isSeriesInRows = false; + /// chart.chartTitle = 'Line Chart 3D'; + /// chart.depthPercent = 45; + /// chart.topRow = 8; + /// chart.leftColumn = 1; + /// chart.bottomRow = 23; + /// chart.rightColumn = 8; + ///chart.rightAngleAxes=false; + /// sheet.charts = charts; + /// final List bytes = workbook.saveAsStream(); + /// saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + /// ``` + bool get rightAngleAxes { + return _rightAngleAxes; + } + + ///set the right angle axes of 3D chart + set rightAngleAxes(bool value) { + _rightAngleAxes = value; + if (!value) { + _isColumnOrBar = false; + } + } + /// Finds the category range in the specified chart range. // ignore: unused_element Range _getCategoryRange( - Range chartValues, Range values, double count, bool bIsInRow) { + Range chartValues, + Range values, + double count, + bool bIsInRow, + ) { final int firstRow = chartValues.row; final int lastRow = chartValues.lastRow; final int firstColumn = chartValues.column; @@ -656,28 +999,57 @@ class Chart { values = chartValues; return chartValues; } - result = bIsInRow - ? chartValues.worksheet - .getRangeByIndex(firstRow, firstColumn, lastRow, firstColumn) - : chartValues.worksheet - .getRangeByIndex(firstRow, firstColumn, firstRow, lastColumn); + result = + bIsInRow + ? chartValues.worksheet.getRangeByIndex( + firstRow, + firstColumn, + lastRow, + firstColumn, + ) + : chartValues.worksheet.getRangeByIndex( + firstRow, + firstColumn, + firstRow, + lastColumn, + ); if (firstRow == lastRow && firstColumn == lastColumn) { values = result; } else if (firstRow == lastRow) { - values = bIsInRow - ? chartValues.worksheet - .getRangeByIndex(firstRow, firstColumn, lastRow, lastColumn) - : chartValues - ..worksheet.getRangeByIndex(firstRow, firstColumn, lastRow, lastColumn); + values = + bIsInRow + ? chartValues.worksheet.getRangeByIndex( + firstRow, + firstColumn, + lastRow, + lastColumn, + ) + : chartValues + ..worksheet.getRangeByIndex( + firstRow, + firstColumn, + lastRow, + lastColumn, + ); } else { - final int add = bIsInRow - ? (firstColumn == lastColumn ? 0 : 1) - : (firstRow == lastRow ? 0 : 1); - values = bIsInRow - ? chartValues.worksheet - .getRangeByIndex(firstRow, firstColumn + add, lastRow, lastColumn) - : chartValues.worksheet.getRangeByIndex( - firstRow + add, firstColumn, lastRow, lastColumn); + final int add = + bIsInRow + ? (firstColumn == lastColumn ? 0 : 1) + : (firstRow == lastRow ? 0 : 1); + values = + bIsInRow + ? chartValues.worksheet.getRangeByIndex( + firstRow, + firstColumn + add, + lastRow, + lastColumn, + ) + : chartValues.worksheet.getRangeByIndex( + firstRow + add, + firstColumn, + lastRow, + lastColumn, + ); } return result; } @@ -685,37 +1057,48 @@ class Chart { /// This method is called if DataRange was changed. void _onDataRangeChanged(ExcelChartType type) { if (_dataRange == null) { - _series._clear(); + _series.clear(); return; } Range? serieValue, serieNameRange, axisRange; - serieNameRange = - _getSerieOrAxisRange(_dataRange, _bSeriesInRows, serieValue); + serieNameRange = _getSerieOrAxisRange( + _dataRange, + _bSeriesInRows, + serieValue, + ); axisRange = _getSerieOrAxisRange(_serieValue, !_bSeriesInRows, _serieValue); // } if (!_validateSerieRangeForChartType(_serieValue, type, _bSeriesInRows)) { - throw "Can't set data range."; + final Error error = ArgumentError("Can't set data range."); + throw error; } - primaryCategoryAxis._categoryLabels = axisRange; + primaryCategoryAxis.categoryLabels = axisRange; int iIndex = 0; if (serieNameRange != null && axisRange != null) { - iIndex = _bSeriesInRows - ? axisRange.lastRow - axisRange.row + 1 - : axisRange.lastColumn - axisRange.column + 1; + iIndex = + _bSeriesInRows + ? axisRange.lastRow - axisRange.row + 1 + : axisRange.lastColumn - axisRange.column + 1; } _updateSeriesByDataRange( - _serieValue, serieNameRange, axisRange, iIndex, _bSeriesInRows); + _serieValue, + serieNameRange, + axisRange, + iIndex, + _bSeriesInRows, + ); } /// Gets data range that represents series name or category axis. Range? _getSerieOrAxisRange(Range? range, bool bIsInRow, Range? serieRange) { if (range == null) { - throw 'range-Value should not be null'; + final Error error = ArgumentError('range-Value should not be null'); + throw error; } final int iFirstLen = bIsInRow ? range.row : range.column; @@ -729,12 +1112,18 @@ class Chart { bool bIsName = false; for (int i = iFirsCount; i < iLastCount && !bIsName; i++) { - final Range curRange = bIsInRow - ? range.worksheet.getRangeByIndex(iRowColumn, i) - : range.worksheet.getRangeByIndex(i, iRowColumn); - - bIsName = (curRange.number != null || - (curRange.dateTime == null && curRange.text == null)) || + final Range curRange = + bIsInRow + ? range.worksheet.getRangeByIndex(iRowColumn, i) + : range.worksheet.getRangeByIndex(i, iRowColumn); + + bIsName = + (curRange.number != null && + curRange.dateTime == null && + curRange.text == null) || + (curRange.dateTime == null && + curRange.text == null && + curRange.number == null) || curRange.formula != null; if (!bIsName) { @@ -748,24 +1137,47 @@ class Chart { return null; } - final Range result = bIsInRow - ? range.worksheet - .getRangeByIndex(iFirstLen, iFirsCount, iRowColumn, iIndex) - : range.worksheet - .getRangeByIndex(iFirsCount, iFirstLen, iIndex, iRowColumn); - - serieRange = bIsInRow - ? range.worksheet.getRangeByIndex( - range.row, result.lastColumn + 1, range.lastRow, range.lastColumn) - : range.worksheet.getRangeByIndex( - result.lastRow + 1, range.column, range.lastRow, range.lastColumn); + final Range result = + bIsInRow + ? range.worksheet.getRangeByIndex( + iFirstLen, + iFirsCount, + iRowColumn, + iIndex, + ) + : range.worksheet.getRangeByIndex( + iFirsCount, + iFirstLen, + iIndex, + iRowColumn, + ); + + serieRange = + bIsInRow + ? range.worksheet.getRangeByIndex( + range.row, + result.lastColumn + 1, + range.lastRow, + range.lastColumn, + ) + : range.worksheet.getRangeByIndex( + result.lastRow + 1, + range.column, + range.lastRow, + range.lastColumn, + ); _serieValue = serieRange; return result; } /// Updates series value by data range. - void _updateSeriesByDataRange(Range? serieValue, Range? serieNameRange, - Range? axisRange, int iIndex, bool isSeriesInRows) { + void _updateSeriesByDataRange( + Range? serieValue, + Range? serieNameRange, + Range? axisRange, + int iIndex, + bool isSeriesInRows, + ) { Worksheet? sheet; if (serieValue != null) { sheet = serieValue.worksheet; @@ -776,41 +1188,68 @@ class Chart { sheet ??= _worksheet; final int iLen = _series.count; for (int i = 0; i < iLen; i++) { - final Range value = isSeriesInRows - ? sheet.getRangeByIndex(serieValue!.row + i, serieValue.column, - serieValue.row + i, serieValue.lastColumn) - : sheet.getRangeByIndex(serieValue!.row, serieValue.column + i, - serieValue.lastRow, serieValue.column + i); + final Range value = + isSeriesInRows + ? sheet.getRangeByIndex( + serieValue!.row + i, + serieValue.column, + serieValue.row + i, + serieValue.lastColumn, + ) + : sheet.getRangeByIndex( + serieValue!.row, + serieValue.column + i, + serieValue.lastRow, + serieValue.column + i, + ); final ChartSerie serie = series[i]; serie.name = 'Serie${i + 1}'; - serie._index = i; + serie.index = i; int iAddIndex = iIndex; - serie._values = value; - serie._isDefaultName = true; + serie.values = value; if (serieNameRange != null) { + serie.isDefaultName = true; iAddIndex += isSeriesInRows ? serieNameRange.row : serieNameRange.column; - String? formula = isSeriesInRows - ? sheet - .getRangeByIndex(iAddIndex + i, serieNameRange.column, - iAddIndex + i, serieNameRange.lastColumn) - .addressGlobal - : sheet - .getRangeByIndex(serieNameRange.row, iAddIndex + i, - serieNameRange.lastRow, iAddIndex + i) - .addressGlobal; - serie._nameOrFormula = formula; - formula = isSeriesInRows - ? sheet - .getRangeByIndex(iAddIndex + i, serieNameRange.column, - iAddIndex + i, serieNameRange.lastColumn) - .text - : sheet - .getRangeByIndex(serieNameRange.row, iAddIndex + i, - serieNameRange.lastRow, iAddIndex + i) - .text; + String? formula = + isSeriesInRows + ? sheet + .getRangeByIndex( + iAddIndex + i, + serieNameRange.column, + iAddIndex + i, + serieNameRange.lastColumn, + ) + .addressGlobal + : sheet + .getRangeByIndex( + serieNameRange.row, + iAddIndex + i, + serieNameRange.lastRow, + iAddIndex + i, + ) + .addressGlobal; + serie.nameOrFormula = formula; + formula = + isSeriesInRows + ? sheet + .getRangeByIndex( + iAddIndex + i, + serieNameRange.column, + iAddIndex + i, + serieNameRange.lastColumn, + ) + .text + : sheet + .getRangeByIndex( + serieNameRange.row, + iAddIndex + i, + serieNameRange.lastRow, + iAddIndex + i, + ) + .text; serie.name = formula; } } @@ -818,14 +1257,19 @@ 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 Error error = ArgumentError('serieValue - Value cannot be null'); + throw error; } - final int iSeriesInRangeCount = isSeriesInRows - ? serieValue.lastRow - serieValue.row + 1 - : serieValue.lastColumn - serieValue.column + 1; + final int iSeriesInRangeCount = + isSeriesInRows + ? serieValue.lastRow - serieValue.row + 1 + : serieValue.lastColumn - serieValue.column + 1; final int iSeriesCount = _series.count; final bool bRemove = iSeriesCount > iSeriesInRangeCount; final int iStart = bRemove ? iSeriesInRangeCount : iSeriesCount; @@ -835,7 +1279,7 @@ class Chart { if (bRemove) { _series.innerList.removeAt(iLen - i + iStart - 1); } else { - _series._add(); + _series.add(); } } return true; @@ -851,13 +1295,28 @@ class Chart { return _stackedCharts.contains(chartType); } + // ignore: public_member_api_docs + bool getIsStacked(ExcelChartType chartType) { + return _getIsStacked(chartType); + } + /// Indicates whether if given chart type is clustered chart or not. bool _getIsClustered(ExcelChartType chartType) { return _chartsCluster.contains(chartType); } + // ignore: public_member_api_docs + bool getIsClustered(ExcelChartType chartType) { + return _getIsClustered(chartType); + } + /// Indicates whether if given chart type is clustered chart or not. bool _getIs100(ExcelChartType chartType) { return _charts100.contains(chartType); } + + // ignore: public_member_api_docs + bool getIs100(ExcelChartType chartType) { + return _getIs100(chartType); + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart b/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart index a04b3b958..d518fbe2e 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart @@ -1,4 +1,5 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; /// Represents an legend on the chart. class ChartLegend { @@ -22,7 +23,7 @@ class ChartLegend { ChartTextArea? _textArea; /// Gets the chart legend have text area or not. - bool get _hasTextArea { + bool get hasTextArea { return _textArea != null; } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart b/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart index 6de5c6e35..809781ec4 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart @@ -1,4 +1,5 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; /// Represents chart plot area object. class ChartPlotArea { diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart b/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart index 9a96328ac..4f7a64657 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart @@ -1,4 +1,13 @@ -part of officechart; +// ignore_for_file: unused_element + +import 'dart:convert'; + +import 'package:archive/archive.dart'; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +// ignore: depend_on_referenced_packages, directives_ordering +import 'package:xml/xml.dart'; + +import '../../officechart.dart'; /// Represent the chart serialize class class ChartSerialization { @@ -17,7 +26,7 @@ class ChartSerialization { ExcelChartLinePattern.longDash: 'lgDash', ExcelChartLinePattern.dashDot: 'dashDot', ExcelChartLinePattern.longDashDot: 'lgDashDot', - ExcelChartLinePattern.longDashDotDot: 'lgDashDotDot' + ExcelChartLinePattern.longDashDotDot: 'lgDashDotDot', }; /// serializes charts from the worksheet. @@ -26,267 +35,466 @@ class ChartSerialization { sheet.workbook.chartCount++; final XmlBuilder builder = XmlBuilder(); builder.processing('xml', 'version="1.0"'); - builder.element('c:chartSpace', nest: () { - builder.attribute( - 'xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); - builder.attribute('xmlns:r', - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - builder.attribute('xmlns:c', - 'http://schemas.openxmlformats.org/drawingml/2006/chart'); - - if (chart._hasPlotArea) { - builder.element('c:roundedCorners', nest: () { - builder.attribute('val', 0); - }); - } - builder.element('c:chart', nest: () { - if (chart.hasTitle) { - _serializeTitle(builder, chart.chartTitleArea); - } - _serializePlotArea(builder, chart); - if (chart.series.count > 0 && chart.hasLegend) { - _serializeLegend(builder, chart.legend!); + builder.element( + 'c:chartSpace', + nest: () { + builder.attribute( + 'xmlns:a', + 'http://schemas.openxmlformats.org/drawingml/2006/main', + ); + builder.attribute( + 'xmlns:r', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + ); + builder.attribute( + 'xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart', + ); + + if (chart.hasPlotArea) { + builder.element( + 'c:roundedCorners', + nest: () { + builder.attribute('val', 0); + }, + ); } - builder.element('c:plotVisOnly', nest: () { - builder.attribute('val', '1'); - }); - builder.element('c:dispBlanksAs', nest: () { - builder.attribute('val', 'gap'); - }); - }); - _serializeFill( - builder, chart.linePattern, chart.linePatternColor, false); - _serializePrinterSettings(builder, chart); - }); + builder.element( + 'c:chart', + nest: () { + if (chart.hasTitle) { + _serializeTitle(builder, chart.chartTitleArea); + } + if (chart.is3DChart) { + _serializeView3D(builder, chart); + } + _serializePlotArea(builder, chart); + if (chart.series.count > 0 && chart.hasLegend) { + _serializeLegend(builder, chart.legend!); + } + builder.element( + 'c:plotVisOnly', + nest: () { + builder.attribute('val', '1'); + }, + ); + builder.element( + 'c:dispBlanksAs', + nest: () { + builder.attribute('val', 'gap'); + }, + ); + }, + ); + _serializeFill( + builder, + chart.linePattern, + chart.linePatternColor, + false, + ); + _serializePrinterSettings(builder, chart); + }, + ); final String stringXml = builder.buildDocument().copy().toString(); final List bytes = utf8.encode(stringXml); _addToArchive(bytes, 'xl/charts/chart${sheet.workbook.chartCount}.xml'); } } + // ignore: public_member_api_docs + void saveCharts(Worksheet sheet) { + _saveCharts(sheet); + } + /// serializes chart's legend. void _serializeLegend(XmlBuilder builder, ChartLegend chartLegend) { - builder.element('c:legend', nest: () { - builder.element('c:legendPos', nest: () { - final ExcelLegendPosition legendPostion = chartLegend.position; - final String positionStr = legendPostion == ExcelLegendPosition.bottom - ? 'b' - : legendPostion == ExcelLegendPosition.left - ? 'l' - : legendPostion == ExcelLegendPosition.right + builder.element( + 'c:legend', + nest: () { + builder.element( + 'c:legendPos', + nest: () { + final ExcelLegendPosition legendPostion = chartLegend.position; + final String positionStr = + legendPostion == ExcelLegendPosition.bottom + ? 'b' + : legendPostion == ExcelLegendPosition.left + ? 'l' + : legendPostion == ExcelLegendPosition.right ? 'r' : legendPostion == ExcelLegendPosition.top - ? 't' - : 'r'; - builder.attribute('val', positionStr); - }); - if (chartLegend._hasTextArea) { - _serializeDefaultTextAreaProperties(builder, chartLegend.textArea); - } - builder.element('c:layout', nest: () {}); - builder.element('c:overlay', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:spPr', nest: () {}); - }); + ? 't' + : 'r'; + builder.attribute('val', positionStr); + }, + ); + if (chartLegend.hasTextArea) { + _serializeDefaultTextAreaProperties(builder, chartLegend.textArea); + } + builder.element('c:layout', nest: () {}); + builder.element( + 'c:overlay', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element('c:spPr', nest: () {}); + }, + ); } /// serializes chart's drawing. void _serializeChartDrawing(XmlBuilder builder, Worksheet sheet) { for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { - builder.element('xdr:twoCellAnchor', nest: () { - builder.attribute('editAs', 'twoCell'); - builder.element('xdr:from', nest: () { - builder.element('xdr:col', - nest: chart.leftColumn > 0 ? chart.leftColumn - 1 : 0); - builder.element('xdr:colOff', nest: 0); - builder.element('xdr:row', - nest: chart.topRow > 0 ? chart.topRow - 1 : 0); - builder.element('xdr:rowOff', nest: 0); - }); - - builder.element('xdr:to', nest: () { - builder.element('xdr:col', - nest: chart.rightColumn > 0 - ? chart.rightColumn - 1 - : chart.leftColumn + 7); - builder.element('xdr:colOff', nest: 0); - builder.element('xdr:row', - nest: chart.bottomRow > 0 - ? chart.bottomRow - 1 - : chart.topRow + 15); - builder.element('xdr:rowOff', nest: 0); - }); - - builder.element('xdr:graphicFrame', nest: () { - builder.attribute('macro', ''); - builder.element('xdr:nvGraphicFramePr', nest: () { - builder.element('xdr:cNvPr', nest: () { - builder.attribute( - 'id', 1024 + sheet.workbook.chartCount + chart.index); - builder.attribute( - 'name', 'Chart ${sheet.workbook.chartCount + chart.index}'); - }); - builder.element('xdr:cNvGraphicFramePr', nest: () {}); - }); - builder.element('xdr:xfrm', nest: () { - builder.element('a:off', nest: () { - builder.attribute('x', '0'); - builder.attribute('y', 0); - }); - builder.element('a:ext', nest: () { - builder.attribute('cx', '0'); - builder.attribute('cy', 0); - }); - }); - builder.element('a:graphic', nest: () { - builder.element('a:graphicData', nest: () { - builder.attribute('uri', - 'http://schemas.openxmlformats.org/drawingml/2006/chart'); - - builder.element('c:chart', nest: () { - builder.attribute('p7:id', 'rId${chart.index}'); - builder.attribute('xmlns:p7', - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - builder.attribute('xmlns:c', - 'http://schemas.openxmlformats.org/drawingml/2006/chart'); - }); - }); - }); - }); - builder.element('xdr:clientData', nest: () {}); - }); + builder.element( + 'xdr:twoCellAnchor', + nest: () { + builder.attribute('editAs', 'twoCell'); + builder.element( + 'xdr:from', + nest: () { + builder.element( + 'xdr:col', + nest: chart.leftColumn > 0 ? chart.leftColumn - 1 : 0, + ); + builder.element('xdr:colOff', nest: 0); + builder.element( + 'xdr:row', + nest: chart.topRow > 0 ? chart.topRow - 1 : 0, + ); + builder.element('xdr:rowOff', nest: 0); + }, + ); + + builder.element( + 'xdr:to', + nest: () { + builder.element( + 'xdr:col', + nest: + chart.rightColumn > 0 + ? chart.rightColumn - 1 + : chart.leftColumn + 7, + ); + builder.element('xdr:colOff', nest: 0); + builder.element( + 'xdr:row', + nest: + chart.bottomRow > 0 + ? chart.bottomRow - 1 + : chart.topRow + 15, + ); + builder.element('xdr:rowOff', nest: 0); + }, + ); + + builder.element( + 'xdr:graphicFrame', + nest: () { + builder.attribute('macro', ''); + builder.element( + 'xdr:nvGraphicFramePr', + nest: () { + builder.element( + 'xdr:cNvPr', + nest: () { + builder.attribute( + 'id', + 1024 + sheet.workbook.chartCount + chart.index, + ); + builder.attribute( + 'name', + 'Chart ${sheet.workbook.chartCount + chart.index}', + ); + }, + ); + builder.element('xdr:cNvGraphicFramePr', nest: () {}); + }, + ); + builder.element( + 'xdr:xfrm', + nest: () { + builder.element( + 'a:off', + nest: () { + builder.attribute('x', '0'); + builder.attribute('y', 0); + }, + ); + builder.element( + 'a:ext', + nest: () { + builder.attribute('cx', '0'); + builder.attribute('cy', 0); + }, + ); + }, + ); + builder.element( + 'a:graphic', + nest: () { + builder.element( + 'a:graphicData', + nest: () { + builder.attribute( + 'uri', + 'http://schemas.openxmlformats.org/drawingml/2006/chart', + ); + + builder.element( + 'c:chart', + nest: () { + builder.attribute('p7:id', 'rId${chart.index}'); + builder.attribute( + 'xmlns:p7', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + ); + builder.attribute( + 'xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart', + ); + }, + ); + }, + ); + }, + ); + }, + ); + builder.element('xdr:clientData', nest: () {}); + }, + ); } } + // ignore: public_member_api_docs + void serializeChartDrawing(XmlBuilder builder, Worksheet sheet) { + _serializeChartDrawing(builder, sheet); + } + /// serialize default text area properties. void _serializeDefaultTextAreaProperties( - XmlBuilder builder, ChartTextArea textArea) { - builder.element('c:txPr', nest: () { - builder.element('a:bodyPr ', nest: () {}); - builder.element('a:lstStyle ', nest: () {}); - builder.element('a:p', nest: () { - builder.element('a:pPr', nest: () { - builder.element('a:defRPr', nest: () { - final double size = textArea.size * 100; - builder.attribute('sz', size.toInt().toString()); - builder.attribute('b', textArea.bold ? '1' : '0'); - builder.attribute('i', textArea.italic ? '1' : '0'); - if (textArea.color != '' && textArea.color != 'FF000000') { - builder.element('a:solidFill', nest: () { - builder.element('a:srgbClr', nest: () { - builder.attribute('val', textArea.color.replaceAll('#', '')); - }); - }); - } - builder.element('a:latin', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - builder.element('a:cs', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - }); - }); - builder.element('a:endParaRPr', nest: () { - builder.attribute('lang', 'en-US'); - }); - }); - }); + XmlBuilder builder, + ChartTextArea textArea, + ) { + builder.element( + 'c:txPr', + nest: () { + builder.element('a:bodyPr ', nest: () {}); + builder.element('a:lstStyle ', nest: () {}); + builder.element( + 'a:p', + nest: () { + builder.element( + 'a:pPr', + nest: () { + builder.element( + 'a:defRPr', + nest: () { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute( + 'val', + textArea.color.replaceAll('#', ''), + ); + }, + ); + }, + ); + } + builder.element( + 'a:latin', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:cs', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + }, + ); + }, + ); + builder.element( + 'a:endParaRPr', + nest: () { + builder.attribute('lang', 'en-US'); + }, + ); + }, + ); + }, + ); } /// serialize printer settings of charts void _serializePrinterSettings(XmlBuilder builder, Chart chart) { - builder.element('c:printSettings', nest: () { - builder.element('c:headerFooter', nest: () { - builder.attribute('scaleWithDoc', '1'); - builder.attribute('alignWithMargins', '0'); - builder.attribute('differentFirst', '0'); - builder.attribute('differentOddEven', '0'); - }); - builder.element('c:pageMargins', nest: () { - builder.attribute('l', '0.7'); - builder.attribute('r', '0.7'); - builder.attribute('t', '0.75'); - builder.attribute('b', '0.75'); - builder.attribute('header', '0.3'); - builder.attribute('footer', '0.3'); - }); - }); + builder.element( + 'c:printSettings', + nest: () { + builder.element( + 'c:headerFooter', + nest: () { + builder.attribute('scaleWithDoc', '1'); + builder.attribute('alignWithMargins', '0'); + builder.attribute('differentFirst', '0'); + builder.attribute('differentOddEven', '0'); + }, + ); + builder.element( + 'c:pageMargins', + nest: () { + builder.attribute('l', '0.7'); + builder.attribute('r', '0.7'); + builder.attribute('t', '0.75'); + builder.attribute('b', '0.75'); + builder.attribute('header', '0.3'); + builder.attribute('footer', '0.3'); + }, + ); + }, + ); } /// serialize chart title. void _serializeTitle(XmlBuilder builder, ChartTextArea chartTextArea) { - builder.element('c:title', nest: () { - if (chartTextArea._hasText) { - builder.element('c:tx', nest: () { - builder.element('c:rich', nest: () { - builder.element('a:bodyPr ', nest: () {}); - builder.element('a:lstStyle', nest: () {}); - builder.element('a:p', nest: () { - builder.element('a:pPr', nest: () { - builder.attribute('algn', 'ctr'); - builder.element('a:defRPr', nest: () {}); - }); - builder.element('a:r', nest: () { - _serializeParagraphRunProperties(builder, chartTextArea); - builder.element('a:t', nest: () { - builder.text(chartTextArea.text!); - }); - }); - }); - }); - }); - } - builder.element('c:layout', nest: () {}); - builder.element('c:overlay', nest: () { - builder.attribute('val', chartTextArea._overlay ? '1' : '0'); - }); - _serializeFill(builder, ExcelChartLinePattern.none, null, false); - }); + builder.element( + 'c:title', + nest: () { + if (chartTextArea.hasText) { + builder.element( + 'c:tx', + nest: () { + builder.element( + 'c:rich', + nest: () { + builder.element('a:bodyPr ', nest: () {}); + builder.element('a:lstStyle', nest: () {}); + builder.element( + 'a:p', + nest: () { + builder.element( + 'a:pPr', + nest: () { + builder.attribute('algn', 'ctr'); + builder.element('a:defRPr', nest: () {}); + }, + ); + builder.element( + 'a:r', + nest: () { + _serializeParagraphRunProperties( + builder, + chartTextArea, + ); + builder.element( + 'a:t', + nest: () { + builder.text(chartTextArea.text!); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + builder.element('c:layout', nest: () {}); + builder.element( + 'c:overlay', + nest: () { + builder.attribute('val', chartTextArea.overlay ? '1' : '0'); + }, + ); + _serializeFill(builder, ExcelChartLinePattern.none, null, false); + }, + ); } /// serialize paragraph run properties. void _serializeParagraphRunProperties( - XmlBuilder builder, ChartTextArea textArea) { - builder.element('a:rPr', nest: () { - builder.attribute('lang', 'en-US'); - final double size = textArea.size * 100; - builder.attribute('sz', size.toInt().toString()); - builder.attribute('b', textArea.bold ? '1' : '0'); - builder.attribute('i', textArea.italic ? '1' : '0'); - builder.attribute('baseline', '0'); - if (textArea.color != '' && textArea.color != 'FF000000') { - builder.element('a:solidFill', nest: () { - builder.element('a:srgbClr', nest: () { - builder.attribute('val', textArea.color.replaceAll('#', '')); - }); - }); - } - builder.element('a:latin', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - builder.element('a:ea', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - builder.element('a:cs', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - }); + XmlBuilder builder, + ChartTextArea textArea, + ) { + builder.element( + 'a:rPr', + nest: () { + builder.attribute('lang', 'en-US'); + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }, + ); + }, + ); + } + builder.element( + 'a:latin', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:ea', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:cs', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + }, + ); } /// serialize plotarea of the chart void _serializePlotArea(XmlBuilder builder, Chart chart) { - builder.element('c:plotArea', nest: () { - builder.element('c:layout', nest: () {}); - if (chart._series.count > 0) { - _serializeMainChartTypeTag(builder, chart); - } else { - _serializeEmptyChart(builder, chart); - _serializeAxes(builder, chart); - } - _serializeFill(builder, chart.plotArea.linePattern, - chart.plotArea.linePatternColor, false); - }); + builder.element( + 'c:plotArea', + nest: () { + builder.element('c:layout', nest: () {}); + if (chart.series.count > 0) { + _serializeMainChartTypeTag(builder, chart); + } else { + _serializeEmptyChart(builder, chart); + _serializeAxes(builder, chart); + } + _serializeFill( + builder, + chart.plotArea.linePattern, + chart.plotArea.linePatternColor, + false, + ); + }, + ); } /// serializes main chart tag. @@ -303,7 +511,10 @@ class ChartSerialization { case ExcelChartType.line: case ExcelChartType.lineStacked: + case ExcelChartType.lineMarkers: case ExcelChartType.lineStacked100: + case ExcelChartType.lineMarkersStacked: + case ExcelChartType.lineMarkersStacked100: _serializeLineChart(builder, chart); break; @@ -316,333 +527,663 @@ class ChartSerialization { case ExcelChartType.pie: _serializePieChart(builder, chart); break; + + case ExcelChartType.pieBar: + case ExcelChartType.pieOfPie: + _serializeOfPieChart(builder, chart); + break; + case ExcelChartType.pie3D: + _serializeOfPie3DChart(builder, chart); + break; + case ExcelChartType.line3D: + _serializeLine3DChart(builder, chart); + break; + case ExcelChartType.barStacked1003D: + case ExcelChartType.barStacked3D: + case ExcelChartType.barClustered3D: + case ExcelChartType.columnClustered3D: + case ExcelChartType.columnStacked3D: + case ExcelChartType.columnStacked1003D: + case ExcelChartType.column3D: + _serializeBar3DChart(builder, chart); + break; + + case ExcelChartType.stockHighLowClose: + case ExcelChartType.stockOpenHighLowClose: + case ExcelChartType.stockVolumeHighLowClose: + case ExcelChartType.stockVolumeOpenHighLowClose: + _serializeStockChart(builder, chart); + break; + case ExcelChartType.doughnut: + case ExcelChartType.doughnutExploded: + _serializedoughnutchart(builder, chart); + break; } } /// serializes Line chart. void _serializeLineChart(XmlBuilder builder, Chart chart) { - builder.element('c:lineChart', nest: () { - _serializeChartGrouping(builder, chart); - builder.element('c:varyColors', nest: () { - builder.attribute('val', 0); - }); - for (int i = 0; i < chart.series.count; i++) { - final ChartSerie firstSerie = chart.series[i]; - _serializeSerie(builder, firstSerie); - } - builder.element('c:axId', nest: () { - builder.attribute('val', 59983360); - }); - builder.element('c:axId', nest: () { - builder.attribute('val', 57253888); - }); - }); + final ExcelChartType type = chart.series[0].serieType; + builder.element( + 'c:lineChart', + nest: () { + _serializeChartGrouping(builder, chart); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + if (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100) { + builder.element( + 'c:marker', + nest: () { + builder.attribute('val', 1); + }, + ); + } + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + }, + ); _serializeAxes(builder, chart); } /// serializes Bar/ColumnClustered chart. void _serializeBarChart(XmlBuilder builder, Chart chart) { - builder.element('c:barChart', nest: () { - final String strDirection = - chart.chartType.toString().contains('bar') ? 'bar' : 'col'; - builder.element('c:barDir', nest: () { - builder.attribute('val', strDirection); - }); - _serializeChartGrouping(builder, chart); - builder.element('c:varyColors', nest: () { - builder.attribute('val', 0); - }); - for (int i = 0; i < chart.series.count; i++) { - final ChartSerie firstSerie = chart.series[i]; - _serializeSerie(builder, firstSerie); - } - builder.element('c:gapWidth', nest: () { - builder.attribute('val', 150); - }); - if (chart._getIsStacked(chart.chartType) || - chart._getIs100(chart.chartType)) { - builder.element('c:overlap', nest: () { - builder.attribute('val', '100'); - }); - } - builder.element('c:axId', nest: () { - builder.attribute('val', 59983360); - }); - builder.element('c:axId', nest: () { - builder.attribute('val', 57253888); - }); - }); + late int gapwidth; + builder.element( + 'c:barChart', + nest: () { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + builder.element( + 'c:barDir', + nest: () { + builder.attribute('val', strDirection); + }, + ); + _serializeChartGrouping(builder, chart); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + builder.element( + 'c:gapWidth', + nest: () { + builder.attribute('val', gapwidth); + }, + ); + if (chart.getIsStacked(chart.chartType) || + chart.getIs100(chart.chartType)) { + builder.element( + 'c:overlap', + nest: () { + builder.attribute('val', '100'); + }, + ); + } + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + }, + ); _serializeAxes(builder, chart); } /// serializes Area chart. void _serializeAreaChart(XmlBuilder builder, Chart chart) { - builder.element('c:areaChart', nest: () { - _serializeChartGrouping(builder, chart); - builder.element('c:varyColors', nest: () { - builder.attribute('val', 0); - }); - for (int i = 0; i < chart.series.count; i++) { - final ChartSerie firstSerie = chart.series[i]; - _serializeSerie(builder, firstSerie); - } - builder.element('c:axId', nest: () { - builder.attribute('val', 59983360); - }); - builder.element('c:axId', nest: () { - builder.attribute('val', 57253888); - }); - }); + builder.element( + 'c:areaChart', + nest: () { + _serializeChartGrouping(builder, chart); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + }, + ); _serializeAxes(builder, chart); } /// serialize chart grouping. void _serializeChartGrouping(XmlBuilder builder, Chart chart) { String strGrouping; - if (chart._getIsClustered(chart.chartType)) { + if (chart.getIsClustered(chart.chartType)) { strGrouping = 'clustered'; - } else if (chart._getIs100(chart.chartType)) { + } else if (chart.getIs100(chart.chartType)) { strGrouping = 'percentStacked'; - } else if (chart._getIsStacked(chart.chartType)) { + } else if (chart.getIsStacked(chart.chartType)) { strGrouping = 'stacked'; } else { strGrouping = 'standard'; } - builder.element('c:grouping', nest: () { - builder.attribute('val', strGrouping); - }); + builder.element( + 'c:grouping', + nest: () { + builder.attribute('val', strGrouping); + }, + ); } /// serializes pie chart. void _serializePieChart(XmlBuilder builder, Chart chart) { - builder.element('c:pieChart', nest: () { - builder.element('c:varyColors', nest: () { - builder.attribute('val', 1); - }); - for (int i = 0; i < chart.series.count; i++) { - final ChartSerie firstSerie = chart.series[i]; - _serializeSerie(builder, firstSerie); - } - builder.element('c:firstSliceAng', nest: () { - builder.attribute('val', 0); - }); - }); + builder.element( + 'c:pieChart', + nest: () { + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 1); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + builder.element( + 'c:firstSliceAng', + nest: () { + builder.attribute('val', 0); + }, + ); + }, + ); } /// serializes series of chart. void _serializeSerie(XmlBuilder builder, ChartSerie firstSerie) { - builder.element('c:ser', nest: () { - builder.element('c:idx', nest: () { - builder.attribute('val', firstSerie._index); - }); - builder.element('c:order', nest: () { - builder.attribute('val', firstSerie._index); - }); - if (firstSerie._isDefaultName) { - builder.element('c:tx', nest: () { - final String strName = firstSerie._nameOrFormula; - if (strName.isNotEmpty) { - _serializeStringReference( - builder, strName, firstSerie, 'text', null); - } - }); - } - if (firstSerie.linePattern != ExcelChartLinePattern.none) { - _serializeFill( - builder, firstSerie.linePattern, firstSerie.linePatternColor, true); - } - if (firstSerie._serieType == ExcelChartType.line) { - builder.element('c:marker', nest: () { - builder.element('c:symbol', nest: () { - builder.attribute('val', 'none'); - }); - }); - } - 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!); - builder.attribute('sourceLinked', '0'); - }); - } - builder.element('c:spPr', nest: () { - builder.element('a:noFill', nest: () {}); - builder.element('a:ln', nest: () { - builder.element('a:noFill', nest: () {}); - }); - builder.element('a:effectLst', nest: () {}); - }); - final ChartTextArea textArea = firstSerie.dataLabels.textArea; - _serializeChartTextArea(builder, textArea); - builder.element('c:showLegendKey', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showVal', nest: () { - builder.attribute('val', firstSerie.dataLabels.isValue ? 1 : 0); - }); - builder.element('c:showCatName', nest: () { - builder.attribute( - 'val', firstSerie.dataLabels.isCategoryName ? 1 : 0); - }); - builder.element('c:showSerName', nest: () { - builder.attribute( - 'val', firstSerie.dataLabels.isSeriesName ? 1 : 0); - }); - builder.element('c:showPercent', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showBubbleSize', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showLeaderLines', nest: () { - builder.attribute('val', 0); - }); - }); - } - if (firstSerie._categoryLabels != null) { - final Range firstRange = firstSerie._chart._worksheet.getRangeByIndex( - firstSerie._categoryLabels!.row, - firstSerie._categoryLabels!.column); - builder.element('c:cat', nest: () { - Worksheet tempSheet = firstSerie._chart._worksheet; - if (firstSerie._categoryLabels!.addressGlobal != '') { - for (final Worksheet sheet - in firstSerie._chart._worksheet.workbook.worksheets.innerList) { - if (firstSerie._categoryLabels!.addressGlobal - .contains(RegExp('${sheet.name}!')) || - firstSerie._categoryLabels!.addressGlobal - .contains(RegExp("${sheet.name}'!"))) { - tempSheet = sheet; - break; + final ExcelChartType type = firstSerie.serieType; + builder.element( + 'c:ser', + nest: () { + builder.element( + 'c:idx', + nest: () { + builder.attribute('val', firstSerie.index); + }, + ); + builder.element( + 'c:order', + nest: () { + builder.attribute('val', firstSerie.index); + }, + ); + if (firstSerie.isDefaultName) { + builder.element( + 'c:tx', + nest: () { + final String strName = firstSerie.nameOrFormula; + if (strName.isNotEmpty) { + _serializeStringReference( + builder, + strName, + firstSerie, + 'text', + null, + ); } - } - } - if (firstRange.text == null && firstRange.number != null) { - builder.element('c:numRef', nest: () { - builder.element('c:f', - nest: firstSerie._categoryLabels!.addressGlobal); - final Range firstRange = firstSerie._chart._worksheet - .getRangeByIndex(firstSerie._categoryLabels!.row, - firstSerie._categoryLabels!.column); - builder.element('c:numCache', nest: () { - if (firstRange.numberFormat != null && - firstRange.numberFormat != 'General') { - builder.element('c:formatCode', - nest: firstRange.numberFormat); - } - _serializeNumCacheValues(builder, firstSerie, tempSheet); - }); - }); + }, + ); + } + + if (firstSerie.serieFormat.pieExplosionPercent != 0 || + (type == ExcelChartType.doughnutExploded && + firstSerie.chart.series.count == 1)) { + builder.element( + 'c:explosion', + nest: () { + builder.attribute( + 'val', + firstSerie.serieFormat.pieExplosionPercent, + ); + }, + ); + } + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:ln', + nest: () { + builder.element('a:noFill', nest: () {}); + }, + ); + }, + ); + } else if (type == ExcelChartType.stockHighLowClose) { + if (firstSerie.index == 2) { + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:ln', + nest: () { + builder.attribute('w', '3175'); + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', '000000'); + }, + ); + }, + ); + builder.element( + 'a:prstDash', + nest: () { + builder.attribute('val', 'solid'); + }, + ); + }, + ); + }, + ); } else { - _serializeStringReference( - builder, - firstSerie._categoryLabels!.addressGlobal, - firstSerie, - 'cat', - tempSheet); + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:ln', + nest: () { + builder.element('a:noFill', nest: () {}); + }, + ); + }, + ); } - }); - } - if (firstSerie._values != null) { - builder.element('c:val', nest: () { - builder.element('c:numRef', nest: () { - builder.element('c:f', nest: firstSerie._values!.addressGlobal); - final Range firstRange = firstSerie._chart._worksheet - .getRangeByIndex( - firstSerie._values!.row, firstSerie._values!.column); - Worksheet tempSheet = firstSerie._chart._worksheet; - if (firstSerie._values!.addressGlobal != '') { - for (final Worksheet sheet in firstSerie - ._chart._worksheet.workbook.worksheets.innerList) { - if (firstSerie._values!.addressGlobal - .contains(RegExp('${sheet.name}!')) || - firstSerie._values!.addressGlobal - .contains(RegExp("${sheet.name}'!"))) { - tempSheet = sheet; - break; + } + if (firstSerie.linePattern != ExcelChartLinePattern.none) { + _serializeFill( + builder, + firstSerie.linePattern, + firstSerie.linePatternColor, + true, + ); + } + + _serializeMarker(builder, firstSerie); + + 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', + // ignore: unnecessary_null_checks + firstSerie.dataLabels.numberFormat!, + ); + builder.attribute('sourceLinked', '0'); + }, + ); + } + builder.element( + 'c:spPr', + nest: () { + builder.element('a:noFill', nest: () {}); + builder.element( + 'a:ln', + nest: () { + builder.element('a:noFill', nest: () {}); + }, + ); + builder.element('a:effectLst', nest: () {}); + }, + ); + final ChartTextArea textArea = firstSerie.dataLabels.textArea; + _serializeChartTextArea(builder, textArea); + builder.element( + 'c:showLegendKey', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showVal', + nest: () { + builder.attribute( + 'val', + firstSerie.dataLabels.isValue ? 1 : 0, + ); + }, + ); + builder.element( + 'c:showCatName', + nest: () { + builder.attribute( + 'val', + firstSerie.dataLabels.isCategoryName ? 1 : 0, + ); + }, + ); + builder.element( + 'c:showSerName', + nest: () { + builder.attribute( + 'val', + firstSerie.dataLabels.isSeriesName ? 1 : 0, + ); + }, + ); + builder.element( + 'c:showPercent', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showBubbleSize', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showLeaderLines', + nest: () { + builder.attribute('val', 0); + }, + ); + }, + ); + } + if (firstSerie.categoryLabels != null) { + final Range firstRange = firstSerie.chart.worksheet.getRangeByIndex( + firstSerie.categoryLabels!.row, + firstSerie.categoryLabels!.column, + ); + builder.element( + 'c:cat', + nest: () { + Worksheet tempSheet = firstSerie.chart.worksheet; + if (firstSerie.categoryLabels!.addressGlobal != '') { + for (final Worksheet sheet + in firstSerie + .chart + .worksheet + .workbook + .worksheets + .innerList) { + if (firstSerie.categoryLabels!.addressGlobal.contains( + RegExp('${sheet.name}!'), + ) || + firstSerie.categoryLabels!.addressGlobal.contains( + RegExp("${sheet.name}'!"), + )) { + tempSheet = sheet; + break; + } } } - } - builder.element('c:numCache', nest: () { - if (firstRange.numberFormat != null && - firstRange.numberFormat != 'General') { - builder.element('c:formatCode', nest: firstRange.numberFormat); + if (firstRange.text == null && firstRange.number != null) { + builder.element( + 'c:numRef', + nest: () { + builder.element( + 'c:f', + nest: firstSerie.categoryLabels!.addressGlobal, + ); + final Range firstRange = firstSerie.chart.worksheet + .getRangeByIndex( + firstSerie.categoryLabels!.row, + firstSerie.categoryLabels!.column, + ); + builder.element( + 'c:numCache', + nest: () { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element( + 'c:formatCode', + nest: firstRange.numberFormat, + ); + } + _serializeNumCacheValues( + builder, + firstSerie, + tempSheet, + ); + }, + ); + }, + ); } else { - builder.element('c:formatCode', nest: 'General'); + _serializeStringReference( + builder, + firstSerie.categoryLabels!.addressGlobal, + firstSerie, + 'cat', + tempSheet, + ); } - _serializeNumCacheValues(builder, firstSerie, tempSheet); - }); - }); - }); - } - if (firstSerie._serieType == ExcelChartType.line) { - builder.element('c:smooth', nest: () { - builder.attribute('val', 0); - }); - } - }); + }, + ); + } + if (firstSerie.values != null) { + builder.element( + 'c:val', + nest: () { + builder.element( + 'c:numRef', + nest: () { + builder.element( + 'c:f', + nest: firstSerie.values!.addressGlobal, + ); + final Range firstRange = firstSerie.chart.worksheet + .getRangeByIndex( + firstSerie.values!.row, + firstSerie.values!.column, + ); + Worksheet tempSheet = firstSerie.chart.worksheet; + if (firstSerie.values!.addressGlobal != '') { + for (final Worksheet sheet + in firstSerie + .chart + .worksheet + .workbook + .worksheets + .innerList) { + if (firstSerie.values!.addressGlobal.contains( + RegExp('${sheet.name}!'), + ) || + firstSerie.values!.addressGlobal.contains( + RegExp("${sheet.name}'!"), + )) { + tempSheet = sheet; + break; + } + } + } + builder.element( + 'c:numCache', + nest: () { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element( + 'c:formatCode', + nest: firstRange.numberFormat, + ); + } else { + builder.element('c:formatCode', nest: 'General'); + } + _serializeNumCacheValues(builder, firstSerie, tempSheet); + }, + ); + }, + ); + }, + ); + } + if (firstSerie.serieType.toString().contains('line') || + firstSerie.serieType.toString().contains('stock')) { + builder.element( + 'c:smooth', + nest: () { + builder.attribute('val', 0); + }, + ); + } + }, + ); } /// serializes chart text area. void _serializeChartTextArea(XmlBuilder builder, ChartTextArea textArea) { - builder.element('c:txPr', nest: () { - builder.element('a:bodyPr ', nest: () {}); - builder.element('a:lstStyle', nest: () {}); - builder.element('a:p', nest: () { - builder.element('a:pPr', nest: () { - builder.element('a:defRPr', nest: () { - final double size = textArea.size * 100; - builder.attribute('sz', size.toInt().toString()); - builder.attribute('b', textArea.bold ? '1' : '0'); - builder.attribute('i', textArea.italic ? '1' : '0'); - builder.attribute('baseline', '0'); - if (textArea.color != '' && textArea.color != 'FF000000') { - builder.element('a:solidFill', nest: () { - builder.element('a:srgbClr', nest: () { - builder.attribute('val', textArea.color.replaceAll('#', '')); - }); - }); - } - builder.element('a:latin', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - builder.element('a:ea', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - builder.element('a:cs', nest: () { - builder.attribute('typeface', textArea.fontName); - }); - }); - }); - }); - }); + builder.element( + 'c:txPr', + nest: () { + builder.element('a:bodyPr ', nest: () {}); + builder.element('a:lstStyle', nest: () {}); + builder.element( + 'a:p', + nest: () { + builder.element( + 'a:pPr', + nest: () { + builder.element( + 'a:defRPr', + nest: () { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute( + 'val', + textArea.color.replaceAll('#', ''), + ); + }, + ); + }, + ); + } + builder.element( + 'a:latin', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:ea', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:cs', + nest: () { + builder.attribute('typeface', textArea.fontName); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); } /// serializes number cache values. void _serializeNumCacheValues( - XmlBuilder builder, ChartSerie firstSerie, Worksheet dataSheet) { - final Range? serieRange = firstSerie._values; + XmlBuilder builder, + ChartSerie firstSerie, + Worksheet dataSheet, + ) { + 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); - }); + builder.element( + 'c:ptCount', + nest: () { + builder.attribute('val', count); + }, + ); int index = 0; while (index < count) { - final double? value = dataSheet - .getRangeByIndex(serieStartRow + index, serieStartColumn) - .number; + 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); - }); + builder.element( + 'c:pt', + nest: () { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }, + ); } index++; } @@ -650,43 +1191,65 @@ class ChartSerialization { } /// serializes string cache reference. - void _serializeStringReference(XmlBuilder builder, String range, - ChartSerie firstSerie, String tagName, Worksheet? dataSheet) { - builder.element('c:strRef', nest: () { - builder.element('c:f', nest: range); - builder.element('c:strCache', nest: () { - if (tagName == 'cat') { - _serializeCategoryTagCacheValues(builder, firstSerie, dataSheet); - } else { - _serializeTextTagCacheValues(builder, firstSerie); - } - }); - }); + void _serializeStringReference( + XmlBuilder builder, + String range, + ChartSerie firstSerie, + String tagName, + Worksheet? dataSheet, + ) { + builder.element( + 'c:strRef', + nest: () { + builder.element('c:f', nest: range); + builder.element( + 'c:strCache', + nest: () { + if (tagName == 'cat') { + _serializeCategoryTagCacheValues(builder, firstSerie, dataSheet); + } else { + _serializeTextTagCacheValues(builder, firstSerie); + } + }, + ); + }, + ); } /// serializes catergory cache values. void _serializeCategoryTagCacheValues( - XmlBuilder builder, ChartSerie firstSerie, Worksheet? dataSheet) { - final Range? serieRange = firstSerie._categoryLabels; + XmlBuilder builder, + ChartSerie firstSerie, + Worksheet? dataSheet, + ) { + // ignore: unnecessary_nullable_for_final_variable_declarations + 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); - }); + 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 - : ''; + final String? value = + dataSheet != null + ? dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .text + : ''; if (value != null) { - builder.element('c:pt', nest: () { - builder.attribute('idx', index); - builder.element('c:v', nest: value); - }); + builder.element( + 'c:pt', + nest: () { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }, + ); } index++; } @@ -695,239 +1258,417 @@ class ChartSerialization { /// serializes text cache values. void _serializeTextTagCacheValues(XmlBuilder builder, ChartSerie firstSerie) { - builder.element('c:ptCount', nest: () { - builder.attribute('val', 1); - }); - builder.element('c:pt', nest: () { - builder.attribute('idx', 0); - if (firstSerie.name != null) { - builder.element('c:v', nest: firstSerie.name); - } - }); + builder.element( + 'c:ptCount', + nest: () { + builder.attribute('val', 1); + }, + ); + builder.element( + 'c:pt', + nest: () { + builder.attribute('idx', 0); + if (firstSerie.name != null) { + builder.element('c:v', nest: firstSerie.name); + } + }, + ); } /// serializes fill for the charts. - void _serializeFill(XmlBuilder builder, ExcelChartLinePattern linePattern, - String? lineColor, bool hasSerie) { - builder.element('c:spPr', nest: () { - if (lineColor == null) { - builder.element('a:solidFill', nest: () { - builder.element('a:srgbClr', nest: () { - builder.attribute('val', 'FFFFFF'); - }); - }); - } - builder.element('a:ln', nest: () { - if (linePattern != ExcelChartLinePattern.none) { - if (!hasSerie || lineColor != null) { - builder.element('a:solidFill', nest: () { - builder.element('a:srgbClr', nest: () { - if (lineColor != null) { - builder.attribute('val', lineColor.replaceAll('#', '')); - } else { - builder.attribute('val', '0070C0'); - } - }); - }); - } - if (linePattern != ExcelChartLinePattern.solid) { - builder.element('a:prstDash', nest: () { - builder.attribute('val', _linePattern[linePattern]); - }); - } - } else { - builder.element('a:noFill', nest: () {}); + void _serializeFill( + XmlBuilder builder, + ExcelChartLinePattern linePattern, + String? lineColor, + bool hasSerie, + ) { + builder.element( + 'c:spPr', + nest: () { + if (lineColor == null) { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', 'FFFFFF'); + }, + ); + }, + ); } - builder.element('a:round', nest: () {}); - }); - }); + builder.element( + 'a:ln', + nest: () { + if (linePattern != ExcelChartLinePattern.none) { + if (!hasSerie || lineColor != null) { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + if (lineColor != null) { + builder.attribute( + 'val', + lineColor.replaceAll('#', ''), + ); + } else { + builder.attribute('val', '0070C0'); + } + }, + ); + }, + ); + } + if (linePattern != ExcelChartLinePattern.solid) { + builder.element( + 'a:prstDash', + nest: () { + builder.attribute('val', _linePattern[linePattern]); + }, + ); + } + } else { + builder.element('a:noFill', nest: () {}); + } + builder.element('a:round', nest: () {}); + }, + ); + }, + ); } /// serializes axies of the series. void _serializeAxes(XmlBuilder builder, Chart chart) { - if (chart._isCategoryAxisAvail) { + if (chart.isCategoryAxisAvail) { _serializeCategoryAxis(builder, chart.primaryCategoryAxis); } - if (chart._isValueAxisAvail) { + if (chart.isValueAxisAvail) { _serializeValueAxis(builder, chart.primaryValueAxis); } } /// serializes empty charts. void _serializeEmptyChart(XmlBuilder builder, Chart chart) { - builder.element('c:barChart', nest: () { - builder.element('c:barDir', nest: () { - builder.attribute('val', 'col'); - }); - builder.element('c:grouping', nest: () { - builder.attribute('val', 'clustered'); - }); - builder.element('c:varyColors', nest: () { - builder.attribute('val', 0); - }); - _serializeEmptyChartDataLabels(builder); - builder.element('c:gapWidth', nest: () { - builder.attribute('val', 150); - }); - builder.element('c:axId', nest: () { - builder.attribute('val', 59983360); - }); - builder.element('c:axId', nest: () { - builder.attribute('val', 57253888); - }); - }); + builder.element( + 'c:barChart', + nest: () { + builder.element( + 'c:barDir', + nest: () { + builder.attribute('val', 'col'); + }, + ); + builder.element( + 'c:grouping', + nest: () { + builder.attribute('val', 'clustered'); + }, + ); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + _serializeEmptyChartDataLabels(builder); + builder.element( + 'c:gapWidth', + nest: () { + builder.attribute('val', 150); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + }, + ); } /// serializes category axis of the chart. void _serializeCategoryAxis(XmlBuilder builder, ChartCategoryAxis axis) { - builder.element('c:catAx', nest: () { - builder.element('c:axId', nest: () { - builder.attribute('val', 59983360); - }); - builder.element('c:scaling', nest: () { - builder.element('c:orientation', nest: () { - builder.attribute('val', 'minMax'); - }); - }); - builder.element('c:delete', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:axPos', nest: () { - builder.attribute('val', 'b'); - }); - if (axis._hasAxisTitle) { - _serializeChartTextArea(builder, axis.titleArea); - } - if (axis.numberFormat != '' && axis.numberFormat != 'General') { - builder.element('c:numFmt', nest: () { - builder.attribute('formatCode', axis.numberFormat); - builder.attribute('sourceLinked', '0'); - }); - } - builder.element('c:majorTickMark', nest: () { - builder.attribute('val', 'out'); - }); - builder.element('c:minorTickMark', nest: () { - builder.attribute('val', 'none'); - }); - builder.element('c:tickLblPos', nest: () { - builder.attribute('val', 'nextTo'); - }); - builder.element('c:spPr', nest: () { - builder.element('a:ln', nest: () {}); - }); - builder.element('c:crossAx', nest: () { - builder.attribute('val', 57253888); - }); - builder.element('c:crosses', nest: () { - builder.attribute('val', 'autoZero'); - }); - builder.element('c:auto', nest: () { - builder.attribute('val', 1); - }); - builder.element('c:lblAlgn', nest: () { - builder.attribute('val', 'ctr'); - }); - builder.element('c:lblOffset', nest: () { - builder.attribute('val', 100); - }); - builder.element('c:noMultiLvlLbl', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:tickMarkSkip', nest: () { - builder.attribute('val', 1); - }); - }); + builder.element( + 'c:catAx', + nest: () { + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:scaling', + nest: () { + builder.element( + 'c:orientation', + nest: () { + builder.attribute('val', 'minMax'); + }, + ); + }, + ); + builder.element( + 'c:delete', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:axPos', + nest: () { + builder.attribute('val', 'b'); + }, + ); + if (axis.hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + builder.element( + 'c:majorTickMark', + nest: () { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () { + builder.element('a:ln', nest: () {}); + }, + ); + builder.element( + 'c:crossAx', + nest: () { + builder.attribute('val', 57253888); + }, + ); + builder.element( + 'c:crosses', + nest: () { + builder.attribute('val', 'autoZero'); + }, + ); + builder.element( + 'c:auto', + nest: () { + builder.attribute('val', 1); + }, + ); + builder.element( + 'c:lblAlgn', + nest: () { + builder.attribute('val', 'ctr'); + }, + ); + builder.element( + 'c:lblOffset', + nest: () { + builder.attribute('val', 100); + }, + ); + builder.element( + 'c:noMultiLvlLbl', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:tickMarkSkip', + nest: () { + builder.attribute('val', 1); + }, + ); + }, + ); } /// serializes Value axis of the chart. void _serializeValueAxis(XmlBuilder builder, ChartValueAxis axis) { - builder.element('c:valAx', nest: () { - builder.element('c:axId', nest: () { - builder.attribute('val', 57253888); - }); - builder.element('c:scaling', nest: () { - builder.element('c:orientation', nest: () { - builder.attribute('val', 'minMax'); - }); - if (!axis._isAutoMax) { - builder.element('c:max', nest: () { - builder.attribute('val', axis.maximumValue); - }); - } - if (axis._isAutoMin) { - builder.element('c:min', nest: () { - builder.attribute('val', axis.minimumValue); - }); - } - }); - builder.element('c:delete', nest: () { - builder.attribute('val', '0'); - }); - builder.element('c:axPos', nest: () { - builder.attribute('val', 'l'); - }); - if (axis._hasAxisTitle) { - _serializeChartTextArea(builder, axis.titleArea); - } - if (axis.numberFormat != '' && axis.numberFormat != 'General') { - builder.element('c:numFmt', nest: () { - builder.attribute('formatCode', axis.numberFormat); - builder.attribute('sourceLinked', '0'); - }); - } - if (axis.hasMajorGridLines) { - builder.element('c:majorGridlines', nest: () {}); - } - builder.element('c:majorTickMark', nest: () { - builder.attribute('val', 'out'); - }); - builder.element('c:minorTickMark', nest: () { - builder.attribute('val', 'none'); - }); - builder.element('c:tickLblPos', nest: () { - builder.attribute('val', 'nextTo'); - }); - builder.element('c:spPr', nest: () { - builder.element('a:ln', nest: () {}); - }); - builder.element('c:crossAx', nest: () { - builder.attribute('val', 59983360); - }); - builder.element('c:crosses', nest: () { - builder.attribute('val', 'autoZero'); - }); - final Chart chart = axis._parentChart; - final String strCrossBetween = - chart.primaryCategoryAxis._isBetween ? 'between' : 'midCat'; - builder.element('c:crossBetween', nest: () { - builder.attribute('val', strCrossBetween); - }); - }); + builder.element( + 'c:valAx', + nest: () { + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + builder.element( + 'c:scaling', + nest: () { + builder.element( + 'c:orientation', + nest: () { + builder.attribute('val', 'minMax'); + }, + ); + if (!axis.isAutoMax) { + builder.element( + 'c:max', + nest: () { + builder.attribute('val', axis.maximumValue); + }, + ); + } + if (axis.isAutoMin) { + builder.element( + 'c:min', + nest: () { + builder.attribute('val', axis.minimumValue); + }, + ); + } + }, + ); + builder.element( + 'c:delete', + nest: () { + builder.attribute('val', '0'); + }, + ); + builder.element( + 'c:axPos', + nest: () { + builder.attribute('val', 'l'); + }, + ); + if (axis.hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () {}); + } + builder.element( + 'c:majorTickMark', + nest: () { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () { + builder.element('a:ln', nest: () {}); + }, + ); + builder.element( + 'c:crossAx', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:crosses', + nest: () { + builder.attribute('val', 'autoZero'); + }, + ); + final Chart chart = axis.parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis.isBetween ? 'between' : 'midCat'; + builder.element( + 'c:crossBetween', + nest: () { + builder.attribute('val', strCrossBetween); + }, + ); + }, + ); } /// serializes empty chart's datalabels. void _serializeEmptyChartDataLabels(XmlBuilder builder) { - builder.element('c:dLbls', nest: () { - builder.element('c:showLegendKey', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showVal', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showCatName', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showSerName', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showPercent', nest: () { - builder.attribute('val', 0); - }); - builder.element('c:showBubbleSize', nest: () { - builder.attribute('val', 0); - }); - }); + builder.element( + 'c:dLbls', + nest: () { + builder.element( + 'c:showLegendKey', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showVal', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showCatName', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showSerName', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showPercent', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showBubbleSize', + nest: () { + builder.attribute('val', 0); + }, + ); + }, + ); } /// Add the workbook data with filename to ZipArchive. @@ -935,4 +1676,3559 @@ class ChartSerialization { final ArchiveFile item = ArchiveFile(fileName, data.length, data); _workbook.archive.addFile(item); } + + /// Serialize line 3D Chart. + void _serializeLine3DChart(XmlBuilder builder, Chart chart) { + late int gapdepth; + builder.element( + 'c:line3DChart', + nest: () { + _serializeChartGrouping(builder, chart); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + } + builder.element( + 'c:gapDepth', + nest: () { + builder.attribute('val', gapdepth); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 63149376); + }, + ); + }, + ); + _serializeAxes(builder, chart); + } + + ///Serialize view 3D + void _serializeView3D(XmlBuilder builder, Chart chart) { + final ChartSeriesCollection firstSerie = chart.series; + + builder.element( + 'c:view3D', + nest: () { + if (!chart.isdefaultElevation) { + builder.element( + 'c:rotX', + nest: () { + builder.attribute('val', chart.elevation); + }, + ); + } + if (firstSerie.innerList[0].serieType == ExcelChartType.pie3D) { + for (int i = 0; i < chart.series.count; i++) { + chart.rotation = + chart.series[i].serieFormat.commonSerieOptions.firstSliceAngle; + } + } + if (!chart.isDefaultRotation) { + builder.element( + 'c:rotY', + nest: () { + builder.attribute('val', chart.rotation); + }, + ); + } + builder.element( + 'c:depthPercent', + nest: () { + builder.attribute('val', chart.depthPercent); + }, + ); + builder.element( + 'c:rAngAx', + nest: () { + int defaultValue = 0; + + if (chart.rightAngleAxes || chart.isColumnOrBar) { + defaultValue = 1; + } + + builder.attribute('val', defaultValue); + }, + ); + builder.element( + 'perspective', + nest: () { + builder.attribute('val', chart.perspective * 2); + }, + ); + }, + ); + } + + /// Serialize bar3D chart. + void _serializeBar3DChart(XmlBuilder builder, Chart chart) { + late int gapdepth; + late int gapwidth; + builder.element( + 'c:bar3DChart', + nest: () { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + + builder.element( + 'c:barDir', + nest: () { + builder.attribute('val', strDirection); + }, + ); + + _serializeChartGrouping(builder, chart); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element( + 'c:gapWidth', + nest: () { + builder.attribute('val', gapwidth); + }, + ); + builder.element( + 'c:gapDepth', + nest: () { + builder.attribute('val', gapdepth); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + }, + ); + _serializeAxes(builder, chart); + } + + // Serializes pie of pie or pie of bar chart. + void _serializeOfPieChart(XmlBuilder builder, Chart chart) { + late int gapwidth; + late int pieSecondSize; + final ExcelChartType type = chart.series[0].serieType; + late String isPieOrBar; + if (type == ExcelChartType.pieOfPie) { + isPieOrBar = 'pie'; + } else if (type == ExcelChartType.pieBar) { + isPieOrBar = 'bar'; + } + builder.element( + 'c:ofPieChart', + nest: () { + builder.element( + 'c:ofPieType', + nest: () { + builder.attribute('val', isPieOrBar); + }, + ); + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 1); + }, + ); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + pieSecondSize = + firstSerie.serieFormat.commonSerieOptions.pieSecondSize; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element( + 'c:gapWidth', + nest: () { + builder.attribute('val', gapwidth); + }, + ); + builder.element( + 'c:secondPieSize', + nest: () { + builder.attribute('val', pieSecondSize); + }, + ); + builder.element( + 'c:serLines', + nest: () { + builder.element( + 'c:spPr', + nest: () { + builder.element('a:ln', nest: () {}); + }, + ); + }, + ); + }, + ); + } + + ///Serialize pie 3D chart. + void _serializeOfPie3DChart(XmlBuilder builder, Chart chart) { + builder.element( + 'c:pie3DChart', + nest: () { + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 1); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + }, + ); + } + + /// Serializes stock chart. + void _serializeStockChart(XmlBuilder builder, Chart chart) { + final ExcelChartType type = chart.series.innerList[0].serieType; + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + builder.element( + 'c:barChart', + nest: () { + builder.element( + 'c:barDir', + nest: () { + builder.attribute('val', 'col'); + }, + ); + builder.element( + 'c:grouping', + nest: () { + builder.attribute('val', 'clustered'); + }, + ); + + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 0); + }, + ); + + final ChartSerie firstSerie = chart.series[0]; + _serializeSerie(builder, firstSerie); + + builder.element( + 'c:gapWidth', + nest: () { + builder.attribute( + 'val', + firstSerie.serieFormat.commonSerieOptions.gapWidth, + ); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 57253888); + }, + ); + }, + ); + } + builder.element( + 'c:stockChart', + nest: () { + if (type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + } else if (type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + if ((type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) && + chart.series.innerList[0] != chart.series.innerList[i]) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + } + } + } + + builder.element( + 'c:hiLowLines', + nest: () { + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:ln', + nest: () { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', '000000'); + }, + ); + }, + ); + builder.element( + 'a:prstDash', + nest: () { + builder.attribute('val', 'solid'); + }, + ); + }, + ); + }, + ); + }, + ); + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element( + 'c:upDownBars', + nest: () { + builder.element( + 'c:gapWidth', + nest: () { + builder.attribute('val', '100'); + }, + ); + builder.element( + 'c:upBars', + nest: () { + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', 'FFFFFF'); + }, + ); + }, + ); + }, + ); + }, + ); + builder.element( + 'c:downBars', + nest: () { + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', '000000'); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + if (chart.series[0].serieFormat.markerStyle != + ExcelChartMarkerType.none) { + builder.element( + 'c:marker', + nest: () { + builder.attribute('val', 1); + }, + ); + } + + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 62908672); + }, + ); + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', 61870848); + }, + ); + }, + ); + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + _serializeAxesforStockChart(builder, chart, 59983360, 57253888, true); + _serializeAxesforStockChart(builder, chart, 62908672, 61870848, false); + } else { + _serializeAxesforStockChart(builder, chart, 62908672, 61870848, true); + } + } + + ///serialize stock axes + void _serializeAxesforStockChart( + XmlBuilder builder, + Chart chart, + int axisId, + int crossAx, + bool isBar, + ) { + if (chart.isCategoryAxisAvail) { + _serializeCategoryAxisForStock( + builder, + chart.primaryCategoryAxis, + axisId, + crossAx, + isBar, + ); + } + if (chart.isValueAxisAvail) { + _serializeValueAxisForStockchart( + builder, + chart.primaryCategoryAxis, + axisId, + crossAx, + isBar, + ); + } + } + + ///Serialize catogory axis for stock chart + void _serializeCategoryAxisForStock( + XmlBuilder builder, + ChartCategoryAxis axis, + int axisId, + int crossAx, + bool isBar, + ) { + builder.element( + 'c:catAx', + nest: () { + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', axisId); + }, + ); + builder.element( + 'c:scaling', + nest: () { + builder.element( + 'c:orientation', + nest: () { + builder.attribute('val', 'minMax'); + }, + ); + }, + ); + int delete = 0; + String axpos = 'b'; + if (!isBar) { + delete = 1; + axpos = 't'; + } + builder.element( + 'c:delete', + nest: () { + builder.attribute('val', delete); + }, + ); + builder.element( + 'c:axPos', + nest: () { + builder.attribute('val', axpos); + }, + ); + if (axis.hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + builder.element( + 'c:majorTickMark', + nest: () { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:ln', + nest: () { + if (!isBar) { + builder.attribute('w', 12700); + } + }, + ); + }, + ); + builder.element( + 'c:crossAx', + nest: () { + builder.attribute('val', crossAx); + }, + ); + builder.element( + 'c:crosses', + nest: () { + builder.attribute('val', 'autoZero'); + }, + ); + builder.element( + 'c:auto', + nest: () { + builder.attribute('val', 1); + }, + ); + builder.element( + 'c:lblAlgn', + nest: () { + builder.attribute('val', 'ctr'); + }, + ); + builder.element( + 'c:lblOffset', + nest: () { + builder.attribute('val', 100); + }, + ); + builder.element( + 'c:noMultiLvlLbl', + nest: () { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:tickMarkSkip', + nest: () { + builder.attribute('val', 1); + }, + ); + }, + ); + } + + ///Serialize value axis for stock chart + void _serializeValueAxisForStockchart( + XmlBuilder builder, + ChartCategoryAxis axis, + int axisId, + int crossAx, + bool isBar, + ) { + builder.element( + 'c:valAx', + nest: () { + builder.element( + 'c:axId', + nest: () { + builder.attribute('val', crossAx); + }, + ); + builder.element( + 'c:scaling', + nest: () { + builder.element( + 'c:orientation', + nest: () { + builder.attribute('val', 'minMax'); + }, + ); + if (!axis.isAutoMax) { + builder.element( + 'c:max', + nest: () { + builder.attribute('val', axis.maximumValue); + }, + ); + } + if (axis.isAutoMin) { + builder.element( + 'c:min', + nest: () { + builder.attribute('val', axis.minimumValue); + }, + ); + } + }, + ); + builder.element( + 'c:delete', + nest: () { + builder.attribute('val', '0'); + }, + ); + String axpos = 'l'; + + if (!isBar) { + axpos = 'r'; + } + + builder.element( + 'c:axPos', + nest: () { + builder.attribute('val', axpos); + }, + ); + + if (axpos == 'l') { + builder.element('c:majorGridlines', nest: () {}); + } + + if (axis.hasAxisTitle) { + _serializeChartTextArea(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () {}); + } + builder.element( + 'c:majorTickMark', + nest: () { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () { + builder.element('a:ln', nest: () {}); + }, + ); + builder.element( + 'c:crossAx', + nest: () { + builder.attribute('val', axisId); + }, + ); + String crosses = 'autoZero'; + if (!isBar) { + crosses = 'max'; + } + builder.element( + 'c:crosses', + nest: () { + builder.attribute('val', crosses); + }, + ); + final Chart chart = axis.parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis.isBetween ? 'between' : 'midCat'; + builder.element( + 'c:crossBetween', + nest: () { + builder.attribute('val', strCrossBetween); + }, + ); + }, + ); + } + + ///Serialize doughnut/doughnut_exploded charts + void _serializedoughnutchart(XmlBuilder builder, Chart chart) { + late int doughnutHoleSize; + late int firstSliceAngle; + + builder.element( + 'c:doughnutChart', + nest: () { + builder.element( + 'c:varyColors', + nest: () { + builder.attribute('val', 1); + }, + ); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerie(builder, firstSerie); + firstSliceAngle = + firstSerie.serieFormat.commonSerieOptions.firstSliceAngle; + doughnutHoleSize = + firstSerie.serieFormat.commonSerieOptions.holeSizePercent; + } + builder.element( + 'c:firstSliceAng', + nest: () { + builder.attribute('val', firstSliceAngle); + }, + ); + builder.element( + 'c:holeSize', + nest: () { + builder.attribute('val', doughnutHoleSize); + }, + ); + }, + ); + } + + ///Serialize marker for stock and line charts + void _serializeMarker(XmlBuilder builder, ChartSerie firstSerie) { + final ExcelChartType type = firstSerie.serieType; + + if ((firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.none) && + (type == ExcelChartType.line || + type == ExcelChartType.lineStacked || + type == ExcelChartType.lineStacked100 || + type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockOpenHighLowClose)) { + builder.element( + 'c:marker', + nest: () { + builder.element( + 'c:symbol', + nest: () { + builder.attribute('val', 'none'); + }, + ); + }, + ); + } else if ((firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.none) && + (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100)) { + builder.element( + 'c:marker', + nest: () { + builder.element( + 'c:symbol', + nest: () { + builder.attribute('val', 'circle'); + }, + ); + builder.element( + 'c:size', + nest: () { + builder.attribute('val', '5'); + }, + ); + }, + ); + } else if (firstSerie.serieFormat.markerStyle != + ExcelChartMarkerType.none) { + final String markerBackgroundColor = firstSerie + .serieFormat + .markerBackgroundColor + .replaceAll('#', ''); + final String markerBorderColor = firstSerie.serieFormat.markerBorderColor + .replaceAll('#', ''); + late String exclMarkertype; + if (firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.diamond) { + exclMarkertype = 'diamond'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.circle) { + exclMarkertype = 'circle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.xSquare) { + exclMarkertype = 'x'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.dowJones) { + exclMarkertype = 'dot'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.square) { + exclMarkertype = 'square'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.plusSign) { + exclMarkertype = 'plus'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.standardDeviation) { + exclMarkertype = 'triangle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.starSquare) { + exclMarkertype = 'star'; + } + + builder.element( + 'c:marker', + nest: () { + builder.element( + 'c:symbol', + nest: () { + builder.attribute('val', exclMarkertype); + }, + ); + builder.element( + 'c:size', + nest: () { + builder.attribute('val', '5'); + }, + ); + builder.element( + 'c:spPr', + nest: () { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', markerBackgroundColor); + }, + ); + }, + ); + builder.element( + 'a:ln', + nest: () { + builder.element( + 'a:solidFill', + nest: () { + builder.element( + 'a:srgbClr', + nest: () { + builder.attribute('val', markerBorderColor); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + } + + /// serializes charts from the worksheet. + Future _saveChartsAsync(Worksheet sheet) async { + for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { + sheet.workbook.chartCount++; + final XmlBuilder builder = XmlBuilder(); + builder.processing('xml', 'version="1.0"'); + builder.element( + 'c:chartSpace', + nest: () async { + builder.attribute( + 'xmlns:a', + 'http://schemas.openxmlformats.org/drawingml/2006/main', + ); + builder.attribute( + 'xmlns:r', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + ); + builder.attribute( + 'xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart', + ); + + if (chart.hasPlotArea) { + builder.element( + 'c:roundedCorners', + nest: () async { + builder.attribute('val', 0); + }, + ); + } + builder.element( + 'c:chart', + nest: () async { + if (chart.hasTitle) { + _serializeTitleAsync(builder, chart.chartTitleArea); + } + if (chart.is3DChart) { + _serializeView3DAsync(builder, chart); + } + _serializePlotAreaAsync(builder, chart); + if (chart.series.count > 0 && chart.hasLegend) { + _serializeLegendAsync(builder, chart.legend!); + } + builder.element( + 'c:plotVisOnly', + nest: () async { + builder.attribute('val', '1'); + }, + ); + builder.element( + 'c:dispBlanksAs', + nest: () async { + builder.attribute('val', 'gap'); + }, + ); + }, + ); + _serializeFillAsync( + builder, + chart.linePattern, + chart.linePatternColor, + false, + ); + _serializePrinterSettingsAsync(builder, chart); + }, + ); + final String stringXml = builder.buildDocument().copy().toString(); + final List bytes = utf8.encode(stringXml); + _addToArchiveAsync( + bytes, + 'xl/charts/chart${sheet.workbook.chartCount}.xml', + ); + } + } + + // ignore: public_member_api_docs + Future saveChartsAsync(Worksheet sheet) async { + _saveChartsAsync(sheet); + } + + /// serializes chart's legend. + Future _serializeLegendAsync( + XmlBuilder builder, + ChartLegend chartLegend, + ) async { + builder.element( + 'c:legend', + nest: () async { + builder.element( + 'c:legendPos', + nest: () async { + final ExcelLegendPosition legendPostion = chartLegend.position; + final String positionStr = + legendPostion == ExcelLegendPosition.bottom + ? 'b' + : legendPostion == ExcelLegendPosition.left + ? 'l' + : legendPostion == ExcelLegendPosition.right + ? 'r' + : legendPostion == ExcelLegendPosition.top + ? 't' + : 'r'; + builder.attribute('val', positionStr); + }, + ); + if (chartLegend.hasTextArea) { + _serializeDefaultTextAreaPropertiesAsync( + builder, + chartLegend.textArea, + ); + } + builder.element('c:layout', nest: () async {}); + builder.element( + 'c:overlay', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element('c:spPr', nest: () async {}); + }, + ); + } + + /// serializes chart's drawing. + Future _serializeChartDrawingAsync( + XmlBuilder builder, + Worksheet sheet, + ) async { + for (final Chart chart in (sheet.charts! as ChartCollection).innerList) { + builder.element( + 'xdr:twoCellAnchor', + nest: () async { + builder.attribute('editAs', 'twoCell'); + builder.element( + 'xdr:from', + nest: () async { + builder.element( + 'xdr:col', + nest: chart.leftColumn > 0 ? chart.leftColumn - 1 : 0, + ); + builder.element('xdr:colOff', nest: 0); + builder.element( + 'xdr:row', + nest: chart.topRow > 0 ? chart.topRow - 1 : 0, + ); + builder.element('xdr:rowOff', nest: 0); + }, + ); + + builder.element( + 'xdr:to', + nest: () async { + builder.element( + 'xdr:col', + nest: + chart.rightColumn > 0 + ? chart.rightColumn - 1 + : chart.leftColumn + 7, + ); + builder.element('xdr:colOff', nest: 0); + builder.element( + 'xdr:row', + nest: + chart.bottomRow > 0 + ? chart.bottomRow - 1 + : chart.topRow + 15, + ); + builder.element('xdr:rowOff', nest: 0); + }, + ); + + builder.element( + 'xdr:graphicFrame', + nest: () async { + builder.attribute('macro', ''); + builder.element( + 'xdr:nvGraphicFramePr', + nest: () async { + builder.element( + 'xdr:cNvPr', + nest: () async { + builder.attribute( + 'id', + 1024 + sheet.workbook.chartCount + chart.index, + ); + builder.attribute( + 'name', + 'Chart ${sheet.workbook.chartCount + chart.index}', + ); + }, + ); + builder.element('xdr:cNvGraphicFramePr', nest: () async {}); + }, + ); + builder.element( + 'xdr:xfrm', + nest: () async { + builder.element( + 'a:off', + nest: () async { + builder.attribute('x', '0'); + builder.attribute('y', 0); + }, + ); + builder.element( + 'a:ext', + nest: () async { + builder.attribute('cx', '0'); + builder.attribute('cy', 0); + }, + ); + }, + ); + builder.element( + 'a:graphic', + nest: () async { + builder.element( + 'a:graphicData', + nest: () async { + builder.attribute( + 'uri', + 'http://schemas.openxmlformats.org/drawingml/2006/chart', + ); + + builder.element( + 'c:chart', + nest: () async { + builder.attribute('p7:id', 'rId${chart.index}'); + builder.attribute( + 'xmlns:p7', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + ); + builder.attribute( + 'xmlns:c', + 'http://schemas.openxmlformats.org/drawingml/2006/chart', + ); + }, + ); + }, + ); + }, + ); + }, + ); + builder.element('xdr:clientData', nest: () async {}); + }, + ); + } + } + + // ignore: public_member_api_docs + Future serializeChartDrawingAsync( + XmlBuilder builder, + Worksheet sheet, + ) async { + _serializeChartDrawingAsync(builder, sheet); + } + + /// serialize default text area properties. + Future _serializeDefaultTextAreaPropertiesAsync( + XmlBuilder builder, + ChartTextArea textArea, + ) async { + builder.element( + 'c:txPr', + nest: () async { + builder.element('a:bodyPr ', nest: () async {}); + builder.element('a:lstStyle ', nest: () async {}); + builder.element( + 'a:p', + nest: () async { + builder.element( + 'a:pPr', + nest: () async { + builder.element( + 'a:defRPr', + nest: () async { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute( + 'val', + textArea.color.replaceAll('#', ''), + ); + }, + ); + }, + ); + } + builder.element( + 'a:latin', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:cs', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + }, + ); + }, + ); + builder.element( + 'a:endParaRPr', + nest: () async { + builder.attribute('lang', 'en-US'); + }, + ); + }, + ); + }, + ); + } + + /// serialize printer settings of charts + Future _serializePrinterSettingsAsync( + XmlBuilder builder, + Chart chart, + ) async { + builder.element( + 'c:printSettings', + nest: () async { + builder.element( + 'c:headerFooter', + nest: () async { + builder.attribute('scaleWithDoc', '1'); + builder.attribute('alignWithMargins', '0'); + builder.attribute('differentFirst', '0'); + builder.attribute('differentOddEven', '0'); + }, + ); + builder.element( + 'c:pageMargins', + nest: () async { + builder.attribute('l', '0.7'); + builder.attribute('r', '0.7'); + builder.attribute('t', '0.75'); + builder.attribute('b', '0.75'); + builder.attribute('header', '0.3'); + builder.attribute('footer', '0.3'); + }, + ); + }, + ); + } + + /// serialize chart title. + Future _serializeTitleAsync( + XmlBuilder builder, + ChartTextArea chartTextArea, + ) async { + builder.element( + 'c:title', + nest: () async { + if (chartTextArea.hasText) { + builder.element( + 'c:tx', + nest: () async { + builder.element( + 'c:rich', + nest: () async { + builder.element('a:bodyPr ', nest: () async {}); + builder.element('a:lstStyle', nest: () async {}); + builder.element( + 'a:p', + nest: () async { + builder.element( + 'a:pPr', + nest: () async { + builder.attribute('algn', 'ctr'); + builder.element('a:defRPr', nest: () async {}); + }, + ); + builder.element( + 'a:r', + nest: () async { + _serializeParagraphRunPropertiesAsync( + builder, + chartTextArea, + ); + builder.element( + 'a:t', + nest: () async { + builder.text(chartTextArea.text!); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + builder.element('c:layout', nest: () async {}); + builder.element( + 'c:overlay', + nest: () async { + builder.attribute('val', chartTextArea.overlay ? '1' : '0'); + }, + ); + _serializeFillAsync(builder, ExcelChartLinePattern.none, null, false); + }, + ); + } + + /// serialize paragraph run properties. + Future _serializeParagraphRunPropertiesAsync( + XmlBuilder builder, + ChartTextArea textArea, + ) async { + builder.element( + 'a:rPr', + nest: () async { + builder.attribute('lang', 'en-US'); + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', textArea.color.replaceAll('#', '')); + }, + ); + }, + ); + } + builder.element( + 'a:latin', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:ea', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:cs', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + }, + ); + } + + /// serialize plotarea of the chart + Future _serializePlotAreaAsync(XmlBuilder builder, Chart chart) async { + builder.element( + 'c:plotArea', + nest: () async { + builder.element('c:layout', nest: () async {}); + if (chart.series.count > 0) { + _serializeMainChartTypeTagAsync(builder, chart); + } else { + _serializeEmptyChartAsync(builder, chart); + _serializeAxesAsync(builder, chart); + } + _serializeFillAsync( + builder, + chart.plotArea.linePattern, + chart.plotArea.linePatternColor, + false, + ); + }, + ); + } + + /// serializes main chart tag. + Future _serializeMainChartTypeTagAsync( + XmlBuilder builder, + Chart chart, + ) async { + switch (chart.chartType) { + case ExcelChartType.column: + case ExcelChartType.columnStacked: + case ExcelChartType.columnStacked100: + case ExcelChartType.bar: + case ExcelChartType.barStacked: + case ExcelChartType.barStacked100: + _serializeBarChartAsync(builder, chart); + break; + + case ExcelChartType.line: + case ExcelChartType.lineStacked: + case ExcelChartType.lineMarkers: + case ExcelChartType.lineStacked100: + case ExcelChartType.lineMarkersStacked: + case ExcelChartType.lineMarkersStacked100: + _serializeLineChartAsync(builder, chart); + break; + + case ExcelChartType.area: + case ExcelChartType.areaStacked: + case ExcelChartType.areaStacked100: + _serializeAreaChartAsync(builder, chart); + break; + + case ExcelChartType.pie: + _serializePieChartAsync(builder, chart); + break; + + case ExcelChartType.pieBar: + case ExcelChartType.pieOfPie: + _serializeOfPieChartAsync(builder, chart); + break; + case ExcelChartType.pie3D: + _serializeOfPie3DChartAsync(builder, chart); + break; + case ExcelChartType.line3D: + _serializeLine3DChartAsync(builder, chart); + break; + case ExcelChartType.barStacked1003D: + case ExcelChartType.barStacked3D: + case ExcelChartType.barClustered3D: + case ExcelChartType.columnClustered3D: + case ExcelChartType.columnStacked3D: + case ExcelChartType.columnStacked1003D: + case ExcelChartType.column3D: + _serializeBar3DChartAsync(builder, chart); + break; + + case ExcelChartType.stockHighLowClose: + case ExcelChartType.stockOpenHighLowClose: + case ExcelChartType.stockVolumeHighLowClose: + case ExcelChartType.stockVolumeOpenHighLowClose: + _serializeStockChartAsync(builder, chart); + break; + case ExcelChartType.doughnut: + case ExcelChartType.doughnutExploded: + _serializedoughnutchartAsync(builder, chart); + break; + } + } + + /// serializes Line chart. + Future _serializeLineChartAsync(XmlBuilder builder, Chart chart) async { + final ExcelChartType type = chart.series[0].serieType; + builder.element( + 'c:lineChart', + nest: () async { + _serializeChartGroupingAsync(builder, chart); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + if (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100) { + builder.element( + 'c:marker', + nest: () async { + builder.attribute('val', 1); + }, + ); + } + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + }, + ); + _serializeAxesAsync(builder, chart); + } + + /// serializes Bar/ColumnClustered chart. + Future _serializeBarChartAsync(XmlBuilder builder, Chart chart) async { + late int gapwidth; + builder.element( + 'c:barChart', + nest: () async { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + builder.element( + 'c:barDir', + nest: () async { + builder.attribute('val', strDirection); + }, + ); + _serializeChartGroupingAsync(builder, chart); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + builder.element( + 'c:gapWidth', + nest: () async { + builder.attribute('val', gapwidth); + }, + ); + if (chart.getIsStacked(chart.chartType) || + chart.getIs100(chart.chartType)) { + builder.element( + 'c:overlap', + nest: () async { + builder.attribute('val', '100'); + }, + ); + } + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + }, + ); + _serializeAxesAsync(builder, chart); + } + + /// serializes Area chart. + Future _serializeAreaChartAsync(XmlBuilder builder, Chart chart) async { + builder.element( + 'c:areaChart', + nest: () async { + _serializeChartGroupingAsync(builder, chart); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + }, + ); + _serializeAxesAsync(builder, chart); + } + + /// serialize chart grouping. + Future _serializeChartGroupingAsync( + XmlBuilder builder, + Chart chart, + ) async { + String strGrouping; + if (chart.getIsClustered(chart.chartType)) { + strGrouping = 'clustered'; + } else if (chart.getIs100(chart.chartType)) { + strGrouping = 'percentStacked'; + } else if (chart.getIsStacked(chart.chartType)) { + strGrouping = 'stacked'; + } else { + strGrouping = 'standard'; + } + builder.element( + 'c:grouping', + nest: () async { + builder.attribute('val', strGrouping); + }, + ); + } + + /// serializes pie chart. + Future _serializePieChartAsync(XmlBuilder builder, Chart chart) async { + builder.element( + 'c:pieChart', + nest: () async { + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 1); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + builder.element( + 'c:firstSliceAng', + nest: () async { + builder.attribute('val', 0); + }, + ); + }, + ); + } + + /// serializes series of chart. + Future _serializeSerieAsync( + XmlBuilder builder, + ChartSerie firstSerie, + ) async { + final ExcelChartType type = firstSerie.serieType; + builder.element( + 'c:ser', + nest: () async { + builder.element( + 'c:idx', + nest: () async { + builder.attribute('val', firstSerie.index); + }, + ); + builder.element( + 'c:order', + nest: () async { + builder.attribute('val', firstSerie.index); + }, + ); + if (firstSerie.isDefaultName) { + builder.element( + 'c:tx', + nest: () async { + final String strName = firstSerie.nameOrFormula; + if (strName.isNotEmpty) { + _serializeStringReferenceAsync( + builder, + strName, + firstSerie, + 'text', + null, + ); + } + }, + ); + } + + if (firstSerie.serieFormat.pieExplosionPercent != 0 || + (type == ExcelChartType.doughnutExploded && + firstSerie.chart.series.count == 1)) { + builder.element( + 'c:explosion', + nest: () async { + builder.attribute( + 'val', + firstSerie.serieFormat.pieExplosionPercent, + ); + }, + ); + } + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:ln', + nest: () async { + builder.element('a:noFill', nest: () async {}); + }, + ); + }, + ); + } else if (type == ExcelChartType.stockHighLowClose) { + if (firstSerie.index == 2) { + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:ln', + nest: () async { + builder.attribute('w', '3175'); + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', '000000'); + }, + ); + }, + ); + builder.element( + 'a:prstDash', + nest: () async { + builder.attribute('val', 'solid'); + }, + ); + }, + ); + }, + ); + } else { + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:ln', + nest: () async { + builder.element('a:noFill', nest: () async {}); + }, + ); + }, + ); + } + } + if (firstSerie.linePattern != ExcelChartLinePattern.none) { + _serializeFillAsync( + builder, + firstSerie.linePattern, + firstSerie.linePatternColor, + true, + ); + } + + _serializeMarkerAsync(builder, firstSerie); + + if (firstSerie.dataLabels.isValue) { + builder.element( + 'c:dLbls', + nest: () async { + final format = firstSerie.dataLabels.numberFormat; + if (format != null && format.isNotEmpty && format != 'General') { + builder.element( + 'c:numFmt', + nest: () async { + builder.attribute('formatCode', format); + builder.attribute('sourceLinked', '0'); + }, + ); + } + builder.element( + 'c:spPr', + nest: () async { + builder.element('a:noFill', nest: () async {}); + builder.element( + 'a:ln', + nest: () async { + builder.element('a:noFill', nest: () async {}); + }, + ); + builder.element('a:effectLst', nest: () async {}); + }, + ); + final ChartTextArea textArea = firstSerie.dataLabels.textArea; + _serializeChartTextAreaAsync(builder, textArea); + builder.element( + 'c:showLegendKey', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showVal', + nest: () async { + builder.attribute( + 'val', + firstSerie.dataLabels.isValue ? 1 : 0, + ); + }, + ); + builder.element( + 'c:showCatName', + nest: () async { + builder.attribute( + 'val', + firstSerie.dataLabels.isCategoryName ? 1 : 0, + ); + }, + ); + builder.element( + 'c:showSerName', + nest: () async { + builder.attribute( + 'val', + firstSerie.dataLabels.isSeriesName ? 1 : 0, + ); + }, + ); + builder.element( + 'c:showPercent', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showBubbleSize', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showLeaderLines', + nest: () async { + builder.attribute('val', 0); + }, + ); + }, + ); + } + if (firstSerie.categoryLabels != null) { + final Range firstRange = firstSerie.chart.worksheet.getRangeByIndex( + firstSerie.categoryLabels!.row, + firstSerie.categoryLabels!.column, + ); + builder.element( + 'c:cat', + nest: () async { + Worksheet tempSheet = firstSerie.chart.worksheet; + if (firstSerie.categoryLabels!.addressGlobal != '') { + for (final Worksheet sheet + in firstSerie + .chart + .worksheet + .workbook + .worksheets + .innerList) { + if (firstSerie.categoryLabels!.addressGlobal.contains( + RegExp('${sheet.name}!'), + ) || + firstSerie.categoryLabels!.addressGlobal.contains( + RegExp("${sheet.name}'!"), + )) { + tempSheet = sheet; + break; + } + } + } + if (firstRange.text == null && firstRange.number != null) { + builder.element( + 'c:numRef', + nest: () async { + builder.element( + 'c:f', + nest: firstSerie.categoryLabels!.addressGlobal, + ); + final Range firstRange = firstSerie.chart.worksheet + .getRangeByIndex( + firstSerie.categoryLabels!.row, + firstSerie.categoryLabels!.column, + ); + builder.element( + 'c:numCache', + nest: () async { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element( + 'c:formatCode', + nest: firstRange.numberFormat, + ); + } + _serializeNumCacheValuesAsync( + builder, + firstSerie, + tempSheet, + ); + }, + ); + }, + ); + } else { + _serializeStringReferenceAsync( + builder, + firstSerie.categoryLabels!.addressGlobal, + firstSerie, + 'cat', + tempSheet, + ); + } + }, + ); + } + if (firstSerie.values != null) { + builder.element( + 'c:val', + nest: () async { + builder.element( + 'c:numRef', + nest: () async { + builder.element( + 'c:f', + nest: firstSerie.values!.addressGlobal, + ); + final Range firstRange = firstSerie.chart.worksheet + .getRangeByIndex( + firstSerie.values!.row, + firstSerie.values!.column, + ); + Worksheet tempSheet = firstSerie.chart.worksheet; + if (firstSerie.values!.addressGlobal != '') { + for (final Worksheet sheet + in firstSerie + .chart + .worksheet + .workbook + .worksheets + .innerList) { + if (firstSerie.values!.addressGlobal.contains( + RegExp('${sheet.name}!'), + ) || + firstSerie.values!.addressGlobal.contains( + RegExp("${sheet.name}'!"), + )) { + tempSheet = sheet; + break; + } + } + } + builder.element( + 'c:numCache', + nest: () async { + if (firstRange.numberFormat != null && + firstRange.numberFormat != 'General') { + builder.element( + 'c:formatCode', + nest: firstRange.numberFormat, + ); + } else { + builder.element('c:formatCode', nest: 'General'); + } + _serializeNumCacheValuesAsync( + builder, + firstSerie, + tempSheet, + ); + }, + ); + }, + ); + }, + ); + } + if (firstSerie.serieType.toString().contains('line') || + firstSerie.serieType.toString().contains('stock')) { + builder.element( + 'c:smooth', + nest: () async { + builder.attribute('val', 0); + }, + ); + } + }, + ); + } + + /// serializes chart text area. + Future _serializeChartTextAreaAsync( + XmlBuilder builder, + ChartTextArea textArea, + ) async { + builder.element( + 'c:txPr', + nest: () async { + builder.element('a:bodyPr ', nest: () async {}); + builder.element('a:lstStyle', nest: () async {}); + builder.element( + 'a:p', + nest: () async { + builder.element( + 'a:pPr', + nest: () async { + builder.element( + 'a:defRPr', + nest: () async { + final double size = textArea.size * 100; + builder.attribute('sz', size.toInt().toString()); + builder.attribute('b', textArea.bold ? '1' : '0'); + builder.attribute('i', textArea.italic ? '1' : '0'); + builder.attribute('baseline', '0'); + if (textArea.color != '' && textArea.color != 'FF000000') { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute( + 'val', + textArea.color.replaceAll('#', ''), + ); + }, + ); + }, + ); + } + builder.element( + 'a:latin', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:ea', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + builder.element( + 'a:cs', + nest: () async { + builder.attribute('typeface', textArea.fontName); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + + /// serializes number cache values. + Future _serializeNumCacheValuesAsync( + XmlBuilder builder, + ChartSerie firstSerie, + Worksheet dataSheet, + ) async { + 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: () async { + 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: () async { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }, + ); + } + index++; + } + } + } + + /// serializes string cache reference. + Future _serializeStringReferenceAsync( + XmlBuilder builder, + String range, + ChartSerie firstSerie, + String tagName, + Worksheet? dataSheet, + ) async { + builder.element( + 'c:strRef', + nest: () async { + builder.element('c:f', nest: range); + builder.element( + 'c:strCache', + nest: () async { + if (tagName == 'cat') { + _serializeCategoryTagCacheValuesAsync( + builder, + firstSerie, + dataSheet, + ); + } else { + _serializeTextTagCacheValuesAsync(builder, firstSerie); + } + }, + ); + }, + ); + } + + /// serializes catergory cache values. + Future _serializeCategoryTagCacheValuesAsync( + XmlBuilder builder, + ChartSerie firstSerie, + Worksheet? dataSheet, + ) async { + // ignore: unnecessary_nullable_for_final_variable_declarations + 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: () async { + builder.attribute('val', count); + }, + ); + int index = 0; + while (index < count) { + final String? value = + dataSheet != null + ? dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .text + : ''; + if (value != null) { + builder.element( + 'c:pt', + nest: () async { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }, + ); + } + index++; + } + } + } + + /// serializes text cache values. + Future _serializeTextTagCacheValuesAsync( + XmlBuilder builder, + ChartSerie firstSerie, + ) async { + builder.element( + 'c:ptCount', + nest: () async { + builder.attribute('val', 1); + }, + ); + builder.element( + 'c:pt', + nest: () async { + builder.attribute('idx', 0); + if (firstSerie.name != null) { + builder.element('c:v', nest: firstSerie.name); + } + }, + ); + } + + /// serializes fill for the charts. + Future _serializeFillAsync( + XmlBuilder builder, + ExcelChartLinePattern linePattern, + String? lineColor, + bool hasSerie, + ) async { + builder.element( + 'c:spPr', + nest: () async { + if (lineColor == null) { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', 'FFFFFF'); + }, + ); + }, + ); + } + builder.element( + 'a:ln', + nest: () async { + if (linePattern != ExcelChartLinePattern.none) { + if (!hasSerie || lineColor != null) { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + if (lineColor != null) { + builder.attribute( + 'val', + lineColor.replaceAll('#', ''), + ); + } else { + builder.attribute('val', '0070C0'); + } + }, + ); + }, + ); + } + if (linePattern != ExcelChartLinePattern.solid) { + builder.element( + 'a:prstDash', + nest: () async { + builder.attribute('val', _linePattern[linePattern]); + }, + ); + } + } else { + builder.element('a:noFill', nest: () async {}); + } + builder.element('a:round', nest: () async {}); + }, + ); + }, + ); + } + + /// serializes axies of the series. + Future _serializeAxesAsync(XmlBuilder builder, Chart chart) async { + if (chart.isCategoryAxisAvail) { + _serializeCategoryAxisAsync(builder, chart.primaryCategoryAxis); + } + if (chart.isValueAxisAvail) { + _serializeValueAxisAsync(builder, chart.primaryValueAxis); + } + } + + /// serializes empty charts. + Future _serializeEmptyChartAsync( + XmlBuilder builder, + Chart chart, + ) async { + builder.element( + 'c:barChart', + nest: () async { + builder.element( + 'c:barDir', + nest: () async { + builder.attribute('val', 'col'); + }, + ); + builder.element( + 'c:grouping', + nest: () async { + builder.attribute('val', 'clustered'); + }, + ); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + _serializeEmptyChartDataLabelsAsync(builder); + builder.element( + 'c:gapWidth', + nest: () async { + builder.attribute('val', 150); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + }, + ); + } + + /// serializes category axis of the chart. + Future _serializeCategoryAxisAsync( + XmlBuilder builder, + ChartCategoryAxis axis, + ) async { + builder.element( + 'c:catAx', + nest: () async { + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:scaling', + nest: () async { + builder.element( + 'c:orientation', + nest: () async { + builder.attribute('val', 'minMax'); + }, + ); + }, + ); + builder.element( + 'c:delete', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:axPos', + nest: () async { + builder.attribute('val', 'b'); + }, + ); + if (axis.hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + builder.element( + 'c:majorTickMark', + nest: () async { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () async { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () async { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () async { + builder.element('a:ln', nest: () async {}); + }, + ); + builder.element( + 'c:crossAx', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + builder.element( + 'c:crosses', + nest: () async { + builder.attribute('val', 'autoZero'); + }, + ); + builder.element( + 'c:auto', + nest: () async { + builder.attribute('val', 1); + }, + ); + builder.element( + 'c:lblAlgn', + nest: () async { + builder.attribute('val', 'ctr'); + }, + ); + builder.element( + 'c:lblOffset', + nest: () async { + builder.attribute('val', 100); + }, + ); + builder.element( + 'c:noMultiLvlLbl', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:tickMarkSkip', + nest: () async { + builder.attribute('val', 1); + }, + ); + }, + ); + } + + /// serializes Value axis of the chart. + Future _serializeValueAxisAsync( + XmlBuilder builder, + ChartValueAxis axis, + ) async { + builder.element( + 'c:valAx', + nest: () async { + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + builder.element( + 'c:scaling', + nest: () async { + builder.element( + 'c:orientation', + nest: () async { + builder.attribute('val', 'minMax'); + }, + ); + if (!axis.isAutoMax) { + builder.element( + 'c:max', + nest: () async { + builder.attribute('val', axis.maximumValue); + }, + ); + } + if (axis.isAutoMin) { + builder.element( + 'c:min', + nest: () async { + builder.attribute('val', axis.minimumValue); + }, + ); + } + }, + ); + builder.element( + 'c:delete', + nest: () async { + builder.attribute('val', '0'); + }, + ); + builder.element( + 'c:axPos', + nest: () async { + builder.attribute('val', 'l'); + }, + ); + if (axis.hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () async {}); + } + builder.element( + 'c:majorTickMark', + nest: () async { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () async { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () async { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () async { + builder.element('a:ln', nest: () async {}); + }, + ); + builder.element( + 'c:crossAx', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:crosses', + nest: () async { + builder.attribute('val', 'autoZero'); + }, + ); + final Chart chart = axis.parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis.isBetween ? 'between' : 'midCat'; + builder.element( + 'c:crossBetween', + nest: () async { + builder.attribute('val', strCrossBetween); + }, + ); + }, + ); + } + + /// serializes empty chart's datalabels. + Future _serializeEmptyChartDataLabelsAsync(XmlBuilder builder) async { + builder.element( + 'c:dLbls', + nest: () async { + builder.element( + 'c:showLegendKey', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showVal', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showCatName', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showSerName', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showPercent', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:showBubbleSize', + nest: () async { + builder.attribute('val', 0); + }, + ); + }, + ); + } + + /// Add the workbook data with filename to ZipArchive. + Future _addToArchiveAsync(List data, String fileName) async { + final ArchiveFile item = ArchiveFile(fileName, data.length, data); + _workbook.archive.addFile(item); + } + + /// Serialize line 3D Chart. + Future _serializeLine3DChartAsync( + XmlBuilder builder, + Chart chart, + ) async { + late int gapdepth; + builder.element( + 'c:line3DChart', + nest: () async { + _serializeChartGroupingAsync(builder, chart); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + } + builder.element( + 'c:gapDepth', + nest: () async { + builder.attribute('val', gapdepth); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 63149376); + }, + ); + }, + ); + _serializeAxesAsync(builder, chart); + } + + ///Serialize view 3D + Future _serializeView3DAsync(XmlBuilder builder, Chart chart) async { + final ChartSeriesCollection firstSerie = chart.series; + + builder.element( + 'c:view3D', + nest: () async { + if (!chart.isdefaultElevation) { + builder.element( + 'c:rotX', + nest: () async { + builder.attribute('val', chart.elevation); + }, + ); + } + if (firstSerie.innerList[0].serieType == ExcelChartType.pie3D) { + for (int i = 0; i < chart.series.count; i++) { + chart.rotation = + chart.series[i].serieFormat.commonSerieOptions.firstSliceAngle; + } + } + if (!chart.isDefaultRotation) { + builder.element( + 'c:rotY', + nest: () async { + builder.attribute('val', chart.rotation); + }, + ); + } + builder.element( + 'c:depthPercent', + nest: () async { + builder.attribute('val', chart.depthPercent); + }, + ); + builder.element( + 'c:rAngAx', + nest: () async { + int defaultValue = 0; + + if (chart.rightAngleAxes || chart.isColumnOrBar) { + defaultValue = 1; + } + + builder.attribute('val', defaultValue); + }, + ); + builder.element( + 'perspective', + nest: () async { + builder.attribute('val', chart.perspective * 2); + }, + ); + }, + ); + } + + /// Serialize bar3D chart. + Future _serializeBar3DChartAsync( + XmlBuilder builder, + Chart chart, + ) async { + late int gapdepth; + late int gapwidth; + builder.element( + 'c:bar3DChart', + nest: () async { + final String strDirection = + chart.chartType.toString().contains('bar') ? 'bar' : 'col'; + + builder.element( + 'c:barDir', + nest: () async { + builder.attribute('val', strDirection); + }, + ); + + _serializeChartGroupingAsync(builder, chart); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + gapdepth = firstSerie.serieFormat.commonSerieOptions.gapDepth; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element( + 'c:gapWidth', + nest: () async { + builder.attribute('val', gapwidth); + }, + ); + builder.element( + 'c:gapDepth', + nest: () async { + builder.attribute('val', gapdepth); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + }, + ); + _serializeAxesAsync(builder, chart); + } + + // Serializes pie of pie or pie of bar chart. + Future _serializeOfPieChartAsync( + XmlBuilder builder, + Chart chart, + ) async { + late int gapwidth; + late int pieSecondSize; + final ExcelChartType type = chart.series[0].serieType; + late String isPieOrBar; + if (type == ExcelChartType.pieOfPie) { + isPieOrBar = 'pie'; + } else if (type == ExcelChartType.pieBar) { + isPieOrBar = 'bar'; + } + builder.element( + 'c:ofPieChart', + nest: () async { + builder.element( + 'c:ofPieType', + nest: () async { + builder.attribute('val', isPieOrBar); + }, + ); + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 1); + }, + ); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + pieSecondSize = + firstSerie.serieFormat.commonSerieOptions.pieSecondSize; + gapwidth = firstSerie.serieFormat.commonSerieOptions.gapWidth; + } + + builder.element( + 'c:gapWidth', + nest: () async { + builder.attribute('val', gapwidth); + }, + ); + builder.element( + 'c:secondPieSize', + nest: () async { + builder.attribute('val', pieSecondSize); + }, + ); + builder.element( + 'c:serLines', + nest: () async { + builder.element( + 'c:spPr', + nest: () async { + builder.element('a:ln', nest: () async {}); + }, + ); + }, + ); + }, + ); + } + + ///Serialize pie 3D chart. + Future _serializeOfPie3DChartAsync( + XmlBuilder builder, + Chart chart, + ) async { + builder.element( + 'c:pie3DChart', + nest: () async { + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 1); + }, + ); + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + }, + ); + } + + /// Serializes stock chart. + Future _serializeStockChartAsync( + XmlBuilder builder, + Chart chart, + ) async { + final ExcelChartType type = chart.series.innerList[0].serieType; + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + builder.element( + 'c:barChart', + nest: () async { + builder.element( + 'c:barDir', + nest: () async { + builder.attribute('val', 'col'); + }, + ); + builder.element( + 'c:grouping', + nest: () async { + builder.attribute('val', 'clustered'); + }, + ); + + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 0); + }, + ); + + final ChartSerie firstSerie = chart.series[0]; + _serializeSerieAsync(builder, firstSerie); + + builder.element( + 'c:gapWidth', + nest: () async { + builder.attribute( + 'val', + firstSerie.serieFormat.commonSerieOptions.gapWidth, + ); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 59983360); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 57253888); + }, + ); + }, + ); + } + builder.element( + 'c:stockChart', + nest: () async { + if (type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + } else if (type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + for (int i = 0; i < chart.series.count; i++) { + if ((type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) && + chart.series.innerList[0] != chart.series.innerList[i]) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + } + } + } + + builder.element( + 'c:hiLowLines', + nest: () async { + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:ln', + nest: () async { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', '000000'); + }, + ); + }, + ); + builder.element( + 'a:prstDash', + nest: () async { + builder.attribute('val', 'solid'); + }, + ); + }, + ); + }, + ); + }, + ); + if (type == ExcelChartType.stockOpenHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose) { + builder.element( + 'c:upDownBars', + nest: () async { + builder.element( + 'c:gapWidth', + nest: () async { + builder.attribute('val', '100'); + }, + ); + builder.element( + 'c:upBars', + nest: () async { + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', 'FFFFFF'); + }, + ); + }, + ); + }, + ); + }, + ); + builder.element( + 'c:downBars', + nest: () async { + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', '000000'); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + if (chart.series[0].serieFormat.markerStyle != + ExcelChartMarkerType.none) { + builder.element( + 'c:marker', + nest: () async { + builder.attribute('val', 1); + }, + ); + } + + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 62908672); + }, + ); + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', 61870848); + }, + ); + }, + ); + if (type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose) { + _serializeAxesforStockChartAsync( + builder, + chart, + 59983360, + 57253888, + true, + ); + _serializeAxesforStockChartAsync( + builder, + chart, + 62908672, + 61870848, + false, + ); + } else { + _serializeAxesforStockChartAsync( + builder, + chart, + 62908672, + 61870848, + true, + ); + } + } + + ///serialize stock axes + Future _serializeAxesforStockChartAsync( + XmlBuilder builder, + Chart chart, + int axisId, + int crossAx, + bool isBar, + ) async { + if (chart.isCategoryAxisAvail) { + _serializeCategoryAxisForStockAsync( + builder, + chart.primaryCategoryAxis, + axisId, + crossAx, + isBar, + ); + } + if (chart.isValueAxisAvail) { + _serializeValueAxisForStockchartAsync( + builder, + chart.primaryCategoryAxis, + axisId, + crossAx, + isBar, + ); + } + } + + ///Serialize catogory axis for stock chart + Future _serializeCategoryAxisForStockAsync( + XmlBuilder builder, + ChartCategoryAxis axis, + int axisId, + int crossAx, + bool isBar, + ) async { + builder.element( + 'c:catAx', + nest: () async { + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', axisId); + }, + ); + builder.element( + 'c:scaling', + nest: () async { + builder.element( + 'c:orientation', + nest: () async { + builder.attribute('val', 'minMax'); + }, + ); + }, + ); + int delete = 0; + String axpos = 'b'; + if (!isBar) { + delete = 1; + axpos = 't'; + } + builder.element( + 'c:delete', + nest: () async { + builder.attribute('val', delete); + }, + ); + builder.element( + 'c:axPos', + nest: () async { + builder.attribute('val', axpos); + }, + ); + if (axis.hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + builder.element( + 'c:majorTickMark', + nest: () async { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () async { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () async { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:ln', + nest: () async { + if (!isBar) { + builder.attribute('w', 12700); + } + }, + ); + }, + ); + builder.element( + 'c:crossAx', + nest: () async { + builder.attribute('val', crossAx); + }, + ); + builder.element( + 'c:crosses', + nest: () async { + builder.attribute('val', 'autoZero'); + }, + ); + builder.element( + 'c:auto', + nest: () async { + builder.attribute('val', 1); + }, + ); + builder.element( + 'c:lblAlgn', + nest: () async { + builder.attribute('val', 'ctr'); + }, + ); + builder.element( + 'c:lblOffset', + nest: () async { + builder.attribute('val', 100); + }, + ); + builder.element( + 'c:noMultiLvlLbl', + nest: () async { + builder.attribute('val', 0); + }, + ); + builder.element( + 'c:tickMarkSkip', + nest: () async { + builder.attribute('val', 1); + }, + ); + }, + ); + } + + ///Serialize value axis for stock chart + Future _serializeValueAxisForStockchartAsync( + XmlBuilder builder, + ChartCategoryAxis axis, + int axisId, + int crossAx, + bool isBar, + ) async { + builder.element( + 'c:valAx', + nest: () async { + builder.element( + 'c:axId', + nest: () async { + builder.attribute('val', crossAx); + }, + ); + builder.element( + 'c:scaling', + nest: () async { + builder.element( + 'c:orientation', + nest: () async { + builder.attribute('val', 'minMax'); + }, + ); + if (!axis.isAutoMax) { + builder.element( + 'c:max', + nest: () async { + builder.attribute('val', axis.maximumValue); + }, + ); + } + if (axis.isAutoMin) { + builder.element( + 'c:min', + nest: () async { + builder.attribute('val', axis.minimumValue); + }, + ); + } + }, + ); + builder.element( + 'c:delete', + nest: () async { + builder.attribute('val', '0'); + }, + ); + String axpos = 'l'; + + if (!isBar) { + axpos = 'r'; + } + + builder.element( + 'c:axPos', + nest: () async { + builder.attribute('val', axpos); + }, + ); + + if (axpos == 'l') { + builder.element('c:majorGridlines', nest: () async {}); + } + + if (axis.hasAxisTitle) { + _serializeChartTextAreaAsync(builder, axis.titleArea); + } + if (axis.numberFormat != '' && axis.numberFormat != 'General') { + builder.element( + 'c:numFmt', + nest: () async { + builder.attribute('formatCode', axis.numberFormat); + builder.attribute('sourceLinked', '0'); + }, + ); + } + if (axis.hasMajorGridLines) { + builder.element('c:majorGridlines', nest: () async {}); + } + builder.element( + 'c:majorTickMark', + nest: () async { + builder.attribute('val', 'out'); + }, + ); + builder.element( + 'c:minorTickMark', + nest: () async { + builder.attribute('val', 'none'); + }, + ); + builder.element( + 'c:tickLblPos', + nest: () async { + builder.attribute('val', 'nextTo'); + }, + ); + builder.element( + 'c:spPr', + nest: () async { + builder.element('a:ln', nest: () async {}); + }, + ); + builder.element( + 'c:crossAx', + nest: () async { + builder.attribute('val', axisId); + }, + ); + String crosses = 'autoZero'; + if (!isBar) { + crosses = 'max'; + } + builder.element( + 'c:crosses', + nest: () async { + builder.attribute('val', crosses); + }, + ); + final Chart chart = axis.parentChart; + final String strCrossBetween = + chart.primaryCategoryAxis.isBetween ? 'between' : 'midCat'; + builder.element( + 'c:crossBetween', + nest: () async { + builder.attribute('val', strCrossBetween); + }, + ); + }, + ); + } + + ///Serialize doughnut/doughnut_exploded charts + Future _serializedoughnutchartAsync( + XmlBuilder builder, + Chart chart, + ) async { + late int doughnutHoleSize; + late int firstSliceAngle; + + builder.element( + 'c:doughnutChart', + nest: () async { + builder.element( + 'c:varyColors', + nest: () async { + builder.attribute('val', 1); + }, + ); + + for (int i = 0; i < chart.series.count; i++) { + final ChartSerie firstSerie = chart.series[i]; + _serializeSerieAsync(builder, firstSerie); + firstSliceAngle = + firstSerie.serieFormat.commonSerieOptions.firstSliceAngle; + doughnutHoleSize = + firstSerie.serieFormat.commonSerieOptions.holeSizePercent; + } + builder.element( + 'c:firstSliceAng', + nest: () async { + builder.attribute('val', firstSliceAngle); + }, + ); + builder.element( + 'c:holeSize', + nest: () async { + builder.attribute('val', doughnutHoleSize); + }, + ); + }, + ); + } + + ///Serialize marker for stock and line charts + Future _serializeMarkerAsync( + XmlBuilder builder, + ChartSerie firstSerie, + ) async { + final ExcelChartType type = firstSerie.serieType; + + if ((firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.none) && + (type == ExcelChartType.line || + type == ExcelChartType.lineStacked || + type == ExcelChartType.lineStacked100 || + type == ExcelChartType.stockHighLowClose || + type == ExcelChartType.stockVolumeOpenHighLowClose || + type == ExcelChartType.stockVolumeHighLowClose || + type == ExcelChartType.stockOpenHighLowClose)) { + builder.element( + 'c:marker', + nest: () async { + builder.element( + 'c:symbol', + nest: () async { + builder.attribute('val', 'none'); + }, + ); + }, + ); + } else if ((firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.none) && + (type == ExcelChartType.lineMarkers || + type == ExcelChartType.lineMarkersStacked || + type == ExcelChartType.lineMarkersStacked100)) { + builder.element( + 'c:marker', + nest: () async { + builder.element( + 'c:symbol', + nest: () async { + builder.attribute('val', 'circle'); + }, + ); + builder.element( + 'c:size', + nest: () async { + builder.attribute('val', '5'); + }, + ); + }, + ); + } else if (firstSerie.serieFormat.markerStyle != + ExcelChartMarkerType.none) { + final String markerBackgroundColor = firstSerie + .serieFormat + .markerBackgroundColor + .replaceAll('#', ''); + final String markerBorderColor = firstSerie.serieFormat.markerBorderColor + .replaceAll('#', ''); + late String exclMarkertype; + if (firstSerie.serieFormat.markerStyle == ExcelChartMarkerType.diamond) { + exclMarkertype = 'diamond'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.circle) { + exclMarkertype = 'circle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.xSquare) { + exclMarkertype = 'x'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.dowJones) { + exclMarkertype = 'dot'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.square) { + exclMarkertype = 'square'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.plusSign) { + exclMarkertype = 'plus'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.standardDeviation) { + exclMarkertype = 'triangle'; + } else if (firstSerie.serieFormat.markerStyle == + ExcelChartMarkerType.starSquare) { + exclMarkertype = 'star'; + } + + builder.element( + 'c:marker', + nest: () async { + builder.element( + 'c:symbol', + nest: () async { + builder.attribute('val', exclMarkertype); + }, + ); + builder.element( + 'c:size', + nest: () async { + builder.attribute('val', '5'); + }, + ); + builder.element( + 'c:spPr', + nest: () async { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', markerBackgroundColor); + }, + ); + }, + ); + builder.element( + 'a:ln', + nest: () async { + builder.element( + 'a:solidFill', + nest: () async { + builder.element( + 'a:srgbClr', + nest: () async { + builder.attribute('val', markerBorderColor); + }, + ); + }, + ); + }, + ); + }, + ); + }, + ); + } + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart b/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart index 984015330..397b1d2ad 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart @@ -1,4 +1,8 @@ -part of officechart; +// ignore_for_file: unnecessary_getters_setters + +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; +import 'chartserie_dataformat_impl.dart'; /// This class represents ChartSeries object. class ChartSerie { @@ -18,6 +22,9 @@ class ChartSerie { /// Represent the serie name. String? name; + ///Represents chart series format + ChartSerieDataFormat? _chartSeriesDataFormat; + /// serie name. late int _index; @@ -45,10 +52,15 @@ class ChartSerie { String? linePatternColor; /// Chart type for the series. - ExcelChartType get _serieType { + ExcelChartType get serieType { return _chart.chartType; } + ///Represents chart serie format. + ChartSerieDataFormat get serieFormat { + return _chartSeriesDataFormat ??= ChartSerieDataFormatImpl(_chart); + } + /// Gets chart text area object. /// /// ```dart @@ -81,4 +93,64 @@ class ChartSerie { ChartDataLabels get dataLabels { return _dataLabels ??= ChartDataLabels(this); } + + // ignore: public_member_api_docs + Chart get chart { + return _chart; + } + + ///Sets the parent chart + set chart(Chart value) { + _chart = value; + } + + // ignore: public_member_api_docs + Range? get categoryLabels { + return _categoryLabels; + } + + ///Set the chart range values + set categoryLabels(Range? value) { + _categoryLabels = value; + } + + // ignore: public_member_api_docs + set index(int value) { + _index = value; + } + + // ignore: public_member_api_docs + int get index { + return _index; + } + + // ignore: public_member_api_docs + set nameOrFormula(String value) { + _nameOrFormula = value; + } + + // ignore: public_member_api_docs + String get nameOrFormula { + return _nameOrFormula; + } + + // ignore: public_member_api_docs + set isDefaultName(bool value) { + _isDefaultName = value; + } + + // ignore: public_member_api_docs + bool get isDefaultName { + return _isDefaultName; + } + + // ignore: public_member_api_docs + set values(Range? value) { + _values = value; + } + + // ignore: public_member_api_docs + Range? get values { + return _values; + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serie_dataformat.dart b/packages/syncfusion_officechart/lib/src/chart/chart_serie_dataformat.dart new file mode 100644 index 000000000..2a7041c48 --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/chart/chart_serie_dataformat.dart @@ -0,0 +1,20 @@ +import 'chart_enum.dart'; +import 'chart_format.dart'; + +/// Represents chart series format class +class ChartSerieDataFormat { + /// Represents background color of a marker. + late String markerBackgroundColor; + + /// Represents border color of a marker. + late String markerBorderColor; + + /// Represents the type of marker. + late ExcelChartMarkerType markerStyle; + + ///Represents the chart series options. + late ChartFormat commonSerieOptions; + + /// Represents the distance of a pie slice from the center of pie chart. + late int pieExplosionPercent; +} diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart b/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart index 561cd81c3..e9c405a16 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart @@ -1,4 +1,5 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; /// Represents the Chart serie collection. class ChartSeriesCollection { @@ -37,7 +38,7 @@ class ChartSeriesCollection { ChartSerie operator [](dynamic index) => innerList[index]; /// Add serie to the chart serie collection. - ChartSerie _add() { + ChartSerie add() { final ChartSerie serie = ChartSerie(_worksheet, _chart); innerList.add(serie); return serie; @@ -47,4 +48,9 @@ class ChartSeriesCollection { void _clear() { _innerList.clear(); } + + // ignore: public_member_api_docs + void clear() { + _clear(); + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart b/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart index f73372438..ab1d9b3db 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart @@ -1,4 +1,6 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; + +import '../../officechart.dart'; /// Represent the Chart Text Area. class ChartTextArea { @@ -8,11 +10,11 @@ class ChartTextArea { if (_parent is Chart) { _chart = _parent as Chart; } else if (_parent is ChartCategoryAxis) { - _chart = (_parent as ChartCategoryAxis)._chart; + _chart = (_parent as ChartCategoryAxis).chart; } else if (_parent is ChartValueAxis) { - _chart = (_parent as ChartValueAxis)._chart; + _chart = (_parent as ChartValueAxis).chart; } else if (_parent is ChartSerie) { - _chart = (_parent as ChartSerie)._chart; + _chart = (_parent as ChartSerie).chart; } _createFont(); } @@ -87,6 +89,16 @@ class ChartTextArea { return text != null; } + // ignore: public_member_api_docs + bool get hasText { + return _hasText; + } + + // ignore: public_member_api_docs + bool get overlay { + return _overlay; + } + /// Create a new font. void _createFont() { _font = Font(); diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart b/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart index b6196deb2..9c00482d3 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart +++ b/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart @@ -1,12 +1,13 @@ -part of officechart; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; /// Represents an axis on the chart. class ChartValueAxis extends ChartAxis { /// Create an instances of [ChartValueAxis] class. ChartValueAxis(Worksheet worksheet, Chart chart) { _worksheet = worksheet; - _chart = chart; - super._parentChart = _chart; + chart = chart; + super.parentChart = chart; } // Parent worksheet. @@ -15,4 +16,9 @@ class ChartValueAxis extends ChartAxis { // Parent chart. late Chart _chart; + + // ignore: public_member_api_docs + Chart get chart { + return _chart; + } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chartserie_dataformat_impl.dart b/packages/syncfusion_officechart/lib/src/chart/chartserie_dataformat_impl.dart new file mode 100644 index 000000000..7b4a31eef --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/chart/chartserie_dataformat_impl.dart @@ -0,0 +1,93 @@ +import '../../officechart.dart'; +import 'chart_fomat_impl.dart'; + +///Represents chart series format +class ChartSerieDataFormatImpl implements ChartSerieDataFormat { + /// Create a instance of [ChartSerieDataFormatImpl] class. + ChartSerieDataFormatImpl(Chart chart) { + _markerBackgroundColor = '#000000'; + _markerForegroundColor = '#000000'; + _markerStyle = ExcelChartMarkerType.none; + _chart = chart; + } + + /// Represents the marker background fill color. + late String _markerBackgroundColor; + + /// Represents the marker Border fill color. + late String _markerForegroundColor; + + ///Represnets the marker style for a series in a line chart,stock chart. + late ExcelChartMarkerType _markerStyle; + + ///Represents chart format + ChartFormat? _chartFormat; + + /// Parent chart. + late Chart _chart; + + ///Represents the distance of pie slice from center of pie. + int _percent = 0; + + ///Get marker background color + @override + String get markerBackgroundColor { + return _markerBackgroundColor; + } + + ///Set marker background color + @override + set markerBackgroundColor(String value) { + _markerBackgroundColor = value; + } + + ///Get marker Border color + @override + String get markerBorderColor { + return _markerForegroundColor; + } + + ///Set marker Border color + @override + set markerBorderColor(String value) { + _markerForegroundColor = value; + } + + ///Set marker style + @override + set markerStyle(ExcelChartMarkerType value) { + _markerStyle = value; + } + + ///Get marker style. + @override + ExcelChartMarkerType get markerStyle { + return _markerStyle; + } + + ///Get chart format + @override + ChartFormat get commonSerieOptions { + _chartFormat ??= ChartformatImpl(_chart); + return _chartFormat!; + } + + ///Set chart format + @override + set commonSerieOptions(ChartFormat? chartFormat) {} + + /// Get the distance of pie slice from center of pie. + @override + int get pieExplosionPercent { + return _percent; + } + + /// Set the distance of pie slice from center of pie. + @override + set pieExplosionPercent(int value) { + if (value < 0 || value > 400) { + throw Exception('percent'); + } + _percent = value; + } +} diff --git a/packages/syncfusion_officechart/lib/src/test/chart.dart b/packages/syncfusion_officechart/lib/src/test/chart.dart new file mode 100644 index 000000000..b60426d38 --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/test/chart.dart @@ -0,0 +1,4626 @@ +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +// ignore: depend_on_referenced_packages, directives_ordering +import 'package:flutter_test/flutter_test.dart'; +import '../../officechart.dart'; + +// ignore: public_member_api_docs +void saveAsExcel(List? bytes, String fileName) { + //Comment the below line when saving Excel document in local machine + bytes = null; + //Uncomment the below lines when saving Excel document in local machine + //Directory('output').create(recursive: true); + //File('output/$fileName').writeAsBytes(bytes!); +} + +// ignore: public_member_api_docs +void xlsiochart() { + group('Charts', () { + test('EmptyChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + final ChartCollection charts = ChartCollection(sheet); + charts.add(); + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelEmptyChart.xlsx'); + }); + test('FLUT-6975-Line with marker Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.lineMarkers; + chart1.dataRange = sheet.getRangeByName('A1:D6'); + chart1.chartTitle = 'Line chart with marker'; + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_ExcelLineChartwithmarker.xlsx'); + }); + test('FLUT-6975-Line stacked with marker', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.lineMarkersStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.chartTitle = 'Stacked line chart with marker'; + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT-6975-ExcelLineStaChartwithmarker.xlsx'); + }); + + test('FLUT-6975-Line stacked 100% with marker Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.lineMarkersStacked100; + chart.dataRange = sheet.getRangeByName('A1:D6'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Line stcked 100 chart with marker '; + + //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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'FLUT-6975-Linestacked100%withMarker.xlsx'); + }); + + test('FLUT_6975_3D_LineChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + sheet.getRangeByName('C11:C17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('C11').number = 18584; + sheet.getRangeByName('C12').number = 1348; + sheet.getRangeByName('C13').number = 400; + sheet.getRangeByName('C14').number = 12000; + sheet.getRangeByName('C15').number = 1800; + sheet.getRangeByName('C16').number = 477; + sheet.getRangeByName('C17').number = 1908; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line3D; + chart.dataRange = sheet.getRangeByName('A11:C17'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Line Chart 3D'; + + //Set Rotation and Elevation + chart.rotation = 20; + chart.elevation = 15; + chart.perspective = 45; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + + ///Set gap depth for 3d line + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 120; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_3D_LineChart.xlsx'); + }); + test('FLUT_6975_3D_Column_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + //Set Chart Title + chart1.chartTitle = 'Column 3D'; + + //Set Rotation and Elevation + chart1.rotation = 40; + chart1.elevation = 75; + chart1.perspective = 25; + + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.column3D; + chart1.dataRange = sheet.getRangeByName('A2:C6'); + + //set gap width and gap depth + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 120; + serie1.serieFormat.commonSerieOptions.gapWidth = 125; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_3D_Column_chart.xlsx'); + }); + test('FLUT_6975_3D_Column_Clustered3D_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnClustered3D; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + //Set Chart Title + chart.chartTitle = 'Column Clustered 3D chart'; + + //Set Rotation and Elevation + chart.rotation = 35; + chart.elevation = 25; + chart.perspective = 45; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + + chart.legend!.position = ExcelLegendPosition.right; + chart.legend!.textArea.bold = true; + chart.legend!.textArea.italic = true; + chart.legend!.textArea.size = 14; + chart.legend!.textArea.fontName = 'Arial'; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 105; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_3D_Column_Clustered3D_chart.xlsx'); + }); + test('FLUT_6975_Column_stacked_3D_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnStacked3D; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + //Set Chart Title + chart.chartTitle = 'Column stacked 3D chart'; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 135; + serie1.serieFormat.commonSerieOptions.gapWidth = 120; + + //Set Rotation and Elevation + chart.rotation = 60; + chart.elevation = 25; + chart.perspective = 25; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_columnStacked3D.xlsx'); + }); + test('FLUT-6975-columnStacked100Chart3D', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnStacked1003D; + chart.dataRange = sheet.getRangeByName('A1:D6'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Column stacked 100 3D chart'; + + //Set Rotation and Elevation + chart.rotation = 30; + chart.elevation = 55; + chart.perspective = 50; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 125; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + //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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'FLUT_6975_columnStacked1003D_chart.xlsx'); + }); + test('FLUT_6975_BarStacked3DChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.barStacked3D; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + //Set Chart Title + chart.chartTitle = 'Bar stacked 3D chart'; + + //Set Rotation and Elevation + chart.rotation = 30; + chart.elevation = 25; + chart.perspective = 60; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 145; + serie1.serieFormat.commonSerieOptions.gapWidth = 165; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_BarStacked3DChart.xlsx'); + }); + test('FLUT_6975_Clustered3DBarchart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.barClustered3D; + chart.dataRange = sheet.getRangeByName('A1:D6'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Bar Clustered 3D chart'; + + //Set Rotation and Elevation + chart.rotation = 20; + chart.elevation = 40; + chart.perspective = 45; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 145; + serie1.serieFormat.commonSerieOptions.gapWidth = 120; + + //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; + + sheet.charts = charts; + + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'FLUT_6975_Clustered3DBarchart.xlsx'); + }); + + test('FLUT_6975_BarStacked1003DChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.barStacked1003D; + chart.dataRange = sheet.getRangeByName('A1:D6'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Barstacked1003Dchart'; + + //Set Rotation and Elevation + chart.rotation = 60; + chart.elevation = 15; + chart.perspective = 25; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 130; + serie1.serieFormat.commonSerieOptions.gapWidth = 140; + + //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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'FLUT_6975_BarStacked1003DChart.xlsx'); + }); + test('FLUT_6975_PieOfPieChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + final Chart chart2 = charts.add(); + chart1.chartType = ExcelChartType.pieOfPie; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + chart2.chartType = ExcelChartType.pieOfPie; + chart2.dataRange = sheet.getRangeByName('A11:B17'); + chart2.isSeriesInRows = false; + //Set Chart Title + chart1.chartTitle = 'pie of pie chart'; + + ///Set position for chart 2 + chart2.topRow = 27; + chart2.leftColumn = 1; + chart2.bottomRow = 42; + chart2.rightColumn = 8; + + //set second pie size, gapwidth and explosion + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.pieSecondSize = 75; + serie1.serieFormat.commonSerieOptions.gapWidth = 80; + serie1.serieFormat.pieExplosionPercent = 10; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_PieOfPieChart.xlsx'); + }); + + test('FLUT_6975_BarOfPieChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + final Chart chart2 = charts.add(); + chart1.chartType = ExcelChartType.pieBar; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + + chart2.chartType = ExcelChartType.pieBar; + chart2.dataRange = sheet.getRangeByName('A11:B17'); + chart2.isSeriesInRows = false; + //Set Chart Title + chart1.chartTitle = 'pie of pie chart'; + + ///Set position for chart 2 + chart2.topRow = 27; + chart2.leftColumn = 1; + chart2.bottomRow = 42; + chart2.rightColumn = 8; + + //set second pie size, gapwidth and explosion + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.pieSecondSize = 65; + serie1.serieFormat.commonSerieOptions.gapWidth = 70; + serie1.serieFormat.pieExplosionPercent = 10; + + chart1.chartTitle = 'Bar of pie chart'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_BarOfPieChart.xlsx'); + }); + test('FLUT_6975_Pie3DChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.pie3D; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + + //set first slice Angle and explosion + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.firstSliceAngle = 90; + serie1.serieFormat.pieExplosionPercent = 105; + chart1.elevation = 45; + chart1.perspective = 20; + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_Pie3DChart.xlsx'); + }); + test('FLUT_6975_Volume_High_low_close_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').dateTime = DateTime(2017, 4); + sheet.getRangeByName('A3').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('A4').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('A5').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('A6').dateTime = DateTime(2017, 4, 5); + + sheet.getRangeByName('B1').text = 'Volume'; + sheet.getRangeByName('B2').number = 10000; + sheet.getRangeByName('B3').number = 20000; + sheet.getRangeByName('B4').number = 30000; + sheet.getRangeByName('B5').number = 25000; + sheet.getRangeByName('B6').number = 15000; + + sheet.getRangeByName('C1').text = 'High'; + sheet.getRangeByName('C2').number = 50; + sheet.getRangeByName('C3').number = 60; + sheet.getRangeByName('C4').number = 55; + sheet.getRangeByName('C5').number = 65; + sheet.getRangeByName('C6').number = 70; + + sheet.getRangeByName('D1').text = 'Low'; + sheet.getRangeByName('D2').number = 10; + sheet.getRangeByName('D3').number = 20; + sheet.getRangeByName('D4').number = 15; + sheet.getRangeByName('D5').number = 25; + sheet.getRangeByName('D6').number = 30; + + sheet.getRangeByName('E1').text = 'Close'; + sheet.getRangeByName('E2').number = 40; + sheet.getRangeByName('E3').number = 30; + sheet.getRangeByName('E4').number = 45; + sheet.getRangeByName('E5').number = 35; + sheet.getRangeByName('E6').number = 60; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:E6'); + //Set Chart Title + chart1.chartTitle = 'Volume-High-Low-close'; + chart1.isSeriesInRows = false; + + //set gap width + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockVolumeHighLowClose; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_Volume_High_low_close_chart.xlsx'); + }); + test('FLUT_6975_Volume_open_High_low_close_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').dateTime = DateTime(2017, 4); + sheet.getRangeByName('A3').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('A4').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('A5').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('A6').dateTime = DateTime(2017, 4, 5); + + sheet.getRangeByName('B1').text = 'Volume'; + sheet.getRangeByName('B2').number = 10000; + sheet.getRangeByName('B3').number = 20000; + sheet.getRangeByName('B4').number = 30000; + sheet.getRangeByName('B5').number = 25000; + sheet.getRangeByName('B6').number = 15000; + + sheet.getRangeByName('C1').text = 'Open'; + sheet.getRangeByName('C2').number = 30; + sheet.getRangeByName('C3').number = 40; + sheet.getRangeByName('C4').number = 35; + sheet.getRangeByName('C5').number = 45; + sheet.getRangeByName('C6').number = 50; + + sheet.getRangeByName('D1').text = 'High'; + sheet.getRangeByName('D2').number = 50; + sheet.getRangeByName('D3').number = 60; + sheet.getRangeByName('D4').number = 55; + sheet.getRangeByName('D5').number = 65; + sheet.getRangeByName('D6').number = 70; + + sheet.getRangeByName('E1').text = 'Low'; + sheet.getRangeByName('E2').number = 10; + sheet.getRangeByName('E3').number = 20; + sheet.getRangeByName('E4').number = 15; + sheet.getRangeByName('E5').number = 25; + sheet.getRangeByName('E6').number = 30; + + sheet.getRangeByName('F1').text = 'Close'; + sheet.getRangeByName('F2').number = 40; + sheet.getRangeByName('F3').number = 30; + sheet.getRangeByName('F4').number = 45; + sheet.getRangeByName('F5').number = 35; + sheet.getRangeByName('F6').number = 60; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.stockVolumeOpenHighLowClose; + chart1.dataRange = sheet.getRangeByName('A1:F6'); + + //set gap width + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + + saveAsExcel(bytes, 'FLUT_6975_Volume_open_High_low_close_chart.xlsx'); + }); + test('FLUT_6975_Stock_Open_High_low_close_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').text = 'Open'; + sheet.getRangeByName('A3').text = 'High'; + sheet.getRangeByName('A4').text = 'Low'; + sheet.getRangeByName('A5').text = 'Close'; + + sheet.getRangeByName('B1').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B2').number = 30; + sheet.getRangeByName('B3').number = 50; + sheet.getRangeByName('B4').number = 10; + sheet.getRangeByName('B5').number = 40; + + sheet.getRangeByName('C1').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C2').number = 40; + sheet.getRangeByName('C3').number = 60; + sheet.getRangeByName('C4').number = 20; + sheet.getRangeByName('C5').number = 30; + + sheet.getRangeByName('D1').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D2').number = 35; + sheet.getRangeByName('D3').number = 55; + sheet.getRangeByName('D4').number = 15; + sheet.getRangeByName('D5').number = 45; + + sheet.getRangeByName('E1').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E2').number = 45; + sheet.getRangeByName('E3').number = 65; + sheet.getRangeByName('E4').number = 25; + sheet.getRangeByName('E5').number = 35; + + sheet.getRangeByName('F1').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F2').number = 50; + sheet.getRangeByName('F3').number = 70; + sheet.getRangeByName('F4').number = 30; + sheet.getRangeByName('F5').number = 60; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:F5'); + //Set Chart Title + chart1.chartTitle = 'Open_highlow-close'; + + //Positioning the chart in the worksheet + chart1.topRow = 3; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockOpenHighLowClose; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_Stock_Open_High_low_close_chart.xlsx'); + }); + test('FLUT_6975_Stock_High_low_close', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').text = 'High'; + sheet.getRangeByName('A3').text = 'Low'; + sheet.getRangeByName('A4').text = 'Close'; + + sheet.getRangeByName('B1').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B2').number = 50; + sheet.getRangeByName('B3').number = 10; + sheet.getRangeByName('B4').number = 40; + + sheet.getRangeByName('C1').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C2').number = 60; + sheet.getRangeByName('C3').number = 20; + sheet.getRangeByName('C4').number = 30; + + sheet.getRangeByName('D1').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D2').number = 55; + sheet.getRangeByName('D3').number = 15; + sheet.getRangeByName('D4').number = 45; + + sheet.getRangeByName('E1').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E2').number = 65; + sheet.getRangeByName('E3').number = 25; + sheet.getRangeByName('E4').number = 35; + + sheet.getRangeByName('F1').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F2').number = 70; + sheet.getRangeByName('F3').number = 30; + sheet.getRangeByName('F4').number = 60; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:F4'); + //Set Chart Title + chart1.chartTitle = 'High-Low-close'; + + //Positioning the chart in the worksheet + chart1.topRow = 3; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockHighLowClose; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_Stock_High_low_close.xlsx'); + }); + test('FLUT_6975_Stock_charts in single sheet', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').dateTime = DateTime(2017, 4); + sheet.getRangeByName('A3').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('A4').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('A5').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('A6').dateTime = DateTime(2017, 4, 5); + + sheet.getRangeByName('B1').text = 'Volume'; + sheet.getRangeByName('B2').number = 10000; + sheet.getRangeByName('B3').number = 20000; + sheet.getRangeByName('B4').number = 30000; + sheet.getRangeByName('B5').number = 25000; + sheet.getRangeByName('B6').number = 15000; + + sheet.getRangeByName('C1').text = 'Open'; + sheet.getRangeByName('C2').number = 30; + sheet.getRangeByName('C3').number = 40; + sheet.getRangeByName('C4').number = 35; + sheet.getRangeByName('C5').number = 45; + sheet.getRangeByName('C6').number = 50; + + sheet.getRangeByName('D1').text = 'High'; + sheet.getRangeByName('D2').number = 50; + sheet.getRangeByName('D3').number = 60; + sheet.getRangeByName('D4').number = 55; + sheet.getRangeByName('D5').number = 65; + sheet.getRangeByName('D6').number = 70; + + sheet.getRangeByName('E1').text = 'Low'; + sheet.getRangeByName('E2').number = 10; + sheet.getRangeByName('E3').number = 20; + sheet.getRangeByName('E4').number = 15; + sheet.getRangeByName('E5').number = 25; + sheet.getRangeByName('E6').number = 30; + + sheet.getRangeByName('F1').text = 'Close'; + sheet.getRangeByName('F2').number = 40; + sheet.getRangeByName('F3').number = 30; + sheet.getRangeByName('F4').number = 45; + sheet.getRangeByName('F5').number = 35; + sheet.getRangeByName('F6').number = 60; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:F6'); + //Set Chart Title + chart1.chartTitle = 'Stock chart'; + chart1.isSeriesInRows = false; + + //set gap width + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockVolumeOpenHighLowClose; + sheet.charts = charts; + + sheet.getRangeByName('A7').text = 'Date'; + sheet.getRangeByName('A8').text = 'Open'; + sheet.getRangeByName('A9').text = 'High'; + sheet.getRangeByName('A10').text = 'Low'; + sheet.getRangeByName('A11').text = 'Close'; + + sheet.getRangeByName('B7').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B8').number = 30; + sheet.getRangeByName('B9').number = 50; + sheet.getRangeByName('B10').number = 10; + sheet.getRangeByName('B11').number = 40; + + sheet.getRangeByName('C7').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C8').number = 40; + sheet.getRangeByName('C9').number = 60; + sheet.getRangeByName('C10').number = 20; + sheet.getRangeByName('C11').number = 30; + + sheet.getRangeByName('D7').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D8').number = 35; + sheet.getRangeByName('D9').number = 55; + sheet.getRangeByName('D10').number = 15; + sheet.getRangeByName('D11').number = 45; + + sheet.getRangeByName('E7').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E8').number = 45; + sheet.getRangeByName('E9').number = 65; + sheet.getRangeByName('E10').number = 25; + sheet.getRangeByName('E11').number = 35; + + sheet.getRangeByName('F7').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F8').number = 50; + sheet.getRangeByName('F9').number = 70; + sheet.getRangeByName('F10').number = 30; + sheet.getRangeByName('F11').number = 60; + + final Chart chart2 = charts.add(); + chart2.dataRange = sheet.getRangeByName('A7:F11'); + //Set Chart Title + chart2.chartTitle = 'Stock chart'; + chart2.isSeriesInRows = true; + chart2.chartType = ExcelChartType.stockOpenHighLowClose; + + sheet.getRangeByName('A12').text = 'Date'; + sheet.getRangeByName('A13').text = 'High'; + sheet.getRangeByName('A14').text = 'Low'; + sheet.getRangeByName('A15').text = 'Close'; + + sheet.getRangeByName('B12').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B13').number = 50; + sheet.getRangeByName('B14').number = 10; + sheet.getRangeByName('B15').number = 40; + + sheet.getRangeByName('C12').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C13').number = 60; + sheet.getRangeByName('C14').number = 20; + sheet.getRangeByName('C15').number = 30; + + sheet.getRangeByName('D12').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D13').number = 55; + sheet.getRangeByName('D14').number = 15; + sheet.getRangeByName('D15').number = 45; + + sheet.getRangeByName('E12').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E13').number = 65; + sheet.getRangeByName('E14').number = 25; + sheet.getRangeByName('E15').number = 35; + + sheet.getRangeByName('F12').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F13').number = 70; + sheet.getRangeByName('F14').number = 30; + sheet.getRangeByName('F15').number = 60; + + final Chart chart3 = charts.add(); + chart3.dataRange = sheet.getRangeByName('A12:F15'); + //Set Chart Title + chart3.chartTitle = 'High-Low-close'; + chart3.isSeriesInRows = true; + chart3.chartType = ExcelChartType.stockHighLowClose; + + sheet.getRangeByName('A16').text = 'Date'; + sheet.getRangeByName('A17').dateTime = DateTime(2017, 4); + sheet.getRangeByName('A18').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('A19').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('A20').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('A21').dateTime = DateTime(2017, 4, 5); + + sheet.getRangeByName('B16').text = 'Volume'; + sheet.getRangeByName('B17').number = 10000; + sheet.getRangeByName('B18').number = 20000; + sheet.getRangeByName('B19').number = 30000; + sheet.getRangeByName('B20').number = 25000; + sheet.getRangeByName('B21').number = 15000; + + sheet.getRangeByName('C16').text = 'High'; + sheet.getRangeByName('C17').number = 50; + sheet.getRangeByName('C18').number = 60; + sheet.getRangeByName('C19').number = 55; + sheet.getRangeByName('C20').number = 65; + sheet.getRangeByName('C21').number = 70; + + sheet.getRangeByName('D16').text = 'Low'; + sheet.getRangeByName('D17').number = 10; + sheet.getRangeByName('D18').number = 20; + sheet.getRangeByName('D19').number = 15; + sheet.getRangeByName('D20').number = 25; + sheet.getRangeByName('D21').number = 30; + + sheet.getRangeByName('E16').text = 'Close'; + sheet.getRangeByName('E17').number = 40; + sheet.getRangeByName('E18').number = 30; + sheet.getRangeByName('E19').number = 45; + sheet.getRangeByName('E20').number = 35; + sheet.getRangeByName('E21').number = 60; + + final Chart chart4 = charts.add(); + chart4.dataRange = sheet.getRangeByName('A16:E21'); + //Set Chart Title + chart4.chartTitle = 'Volume-High-Low-close-chart'; + chart4.isSeriesInRows = false; + + //set gap width + final ChartSerie serie2 = chart4.series[0]; + serie2.serieFormat.commonSerieOptions.gapWidth = 105; + + //Positioning the charts in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + chart2.topRow = 8; + chart2.leftColumn = 10; + chart2.bottomRow = 22; + chart2.rightColumn = 19; + + chart3.topRow = 27; + chart3.leftColumn = 1; + chart3.bottomRow = 42; + chart3.rightColumn = 8; + + chart4.topRow = 27; + chart4.leftColumn = 10; + chart4.bottomRow = 42; + chart4.rightColumn = 19; + + chart4.chartType = ExcelChartType.stockVolumeHighLowClose; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + + saveAsExcel(bytes, 'FLUT_6975_Sock_charts.xlsx'); + }); + test('FLUT_6975_3D_charts', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + final Chart chart2 = charts.add(); + final Chart chart3 = charts.add(); + final Chart chart4 = charts.add(); + final Chart chart5 = charts.add(); + + chart1.chartType = ExcelChartType.line3D; + chart1.dataRange = sheet.getRangeByName('A1:D6'); + + chart2.chartType = ExcelChartType.column3D; + chart2.dataRange = sheet.getRangeByName('A1:D6'); + + chart3.chartType = ExcelChartType.columnClustered3D; + chart3.dataRange = sheet.getRangeByName('A1:D6'); + + chart4.chartType = ExcelChartType.columnStacked1003D; + chart4.dataRange = sheet.getRangeByName('A1:D6'); + + chart5.chartType = ExcelChartType.barStacked1003D; + chart5.dataRange = sheet.getRangeByName('A1:D6'); + + //Set Chart Title + chart1.chartTitle = 'Line 3D chart'; + chart2.chartTitle = 'column 3D chart'; + chart3.chartTitle = 'Column clustered 3D chart'; + chart4.chartTitle = 'ColumnStacked1003D chart'; + chart5.chartTitle = 'Barstacked100 chart'; + //Set Rotation and Elevation,perspective + chart1.rotation = 20; + chart1.elevation = 15; + chart1.perspective = 25; + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 55; + + chart2.rotation = 20; + chart2.elevation = 15; + chart2.perspective = 30; + chart2.rightAngleAxes = false; + + chart3.rotation = 25; + chart3.elevation = 10; + chart3.perspective = 40; + chart3.rightAngleAxes = false; + + chart3.rotation = 40; + chart3.elevation = 55; + chart3.perspective = 15; + + chart4.rotation = 30; + chart4.elevation = 25; + chart4.perspective = 25; + + chart5.rotation = 35; + chart5.elevation = 60; + chart5.perspective = 10; + + //Positioning the charts in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + chart2.topRow = 8; + chart2.leftColumn = 10; + chart2.bottomRow = 22; + chart2.rightColumn = 19; + + chart3.topRow = 27; + chart3.leftColumn = 1; + chart3.bottomRow = 42; + chart3.rightColumn = 8; + + chart4.topRow = 27; + chart4.leftColumn = 10; + chart4.bottomRow = 42; + chart4.rightColumn = 19; + + chart5.topRow = 45; + chart5.leftColumn = 1; + chart5.bottomRow = 60; + chart5.rightColumn = 8; + + chart1.isSeriesInRows = false; + chart2.isSeriesInRows = false; + + sheet.charts = charts; + + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_3D_charts.xlsx'); + }); + test('FLUT_6975_doughNutExploded_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Food'; + sheet.getRangeByName('A2').text = 'Fruit'; + sheet.getRangeByName('A3').text = 'Vegetable'; + sheet.getRangeByName('A4').text = 'Dairy'; + sheet.getRangeByName('A5').text = 'Proptein'; + sheet.getRangeByName('A6').text = 'Grains'; + sheet.getRangeByName('A7').text = 'Others'; + + sheet.getRangeByName('B1').text = 'Percentage'; + sheet.getRangeByName('B2').number = 0.36; + sheet.getRangeByName('B3').number = 0.14; + sheet.getRangeByName('B4').number = 0.13; + sheet.getRangeByName('B5').number = 0.28; + sheet.getRangeByName('B6').number = 0.9; + sheet.getRangeByName('B7').number = 0.15; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.doughnutExploded; + chart.dataRange = sheet.getRangeByName('A2:B7'); + chart.isSeriesInRows = false; + + ///set doughnut hole size + chart.series[0].serieFormat.commonSerieOptions.holeSizePercent = 50; + + ///set firstslice angle + chart.series[0].serieFormat.commonSerieOptions.firstSliceAngle = 20; + + ///set explosion percentage + chart.series[0].serieFormat.pieExplosionPercent = 30; + + //Set Chart Title + chart.chartTitle = 'Exploded Doughnut Chart '; + + chart.series[0].dataLabels.isValue = true; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_doughnutExploded_chart.xlsx'); + }); + test('FLUT_6975_doughNut_chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Company'; + sheet.getRangeByName('A2').text = 'Company A'; + sheet.getRangeByName('A3').text = 'Company B'; + sheet.getRangeByName('A4').text = 'Company C'; + sheet.getRangeByName('A5').text = 'Company D'; + sheet.getRangeByName('A6').text = 'Company E'; + sheet.getRangeByName('A7').text = 'Others'; + + sheet.getRangeByName('B1').dateTime = DateTime(2016); + sheet.getRangeByName('B2').number = 0.28; + sheet.getRangeByName('B3').number = 0.5; + sheet.getRangeByName('B4').number = 0.17; + sheet.getRangeByName('B5').number = 0.18; + sheet.getRangeByName('B6').number = 0.17; + sheet.getRangeByName('B7').number = 0.15; + + sheet.getRangeByName('C1').dateTime = DateTime(2017); + sheet.getRangeByName('C2').number = 0.25; + sheet.getRangeByName('C3').number = 0.9; + sheet.getRangeByName('C4').number = 0.19; + sheet.getRangeByName('C5').number = 0.22; + sheet.getRangeByName('C6').number = 0.15; + sheet.getRangeByName('C7').number = 0.10; + + sheet.getRangeByName('D1').dateTime = DateTime(2018); + sheet.getRangeByName('D2').number = 0.35; + sheet.getRangeByName('D3').number = 0.1; + sheet.getRangeByName('D4').number = 0.145; + sheet.getRangeByName('D5').number = 0.55; + sheet.getRangeByName('D6').number = 0.55; + sheet.getRangeByName('D7').number = 0.40; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.doughnut; + chart.dataRange = sheet.getRangeByName('A2:D7'); + chart.isSeriesInRows = false; + + chart.series[0].serieFormat.commonSerieOptions.holeSizePercent = 80; + chart.series[0].serieFormat.commonSerieOptions.firstSliceAngle = 10; + chart.series[0].serieFormat.pieExplosionPercent = 50; + + //Set Chart Title + chart.chartTitle = 'Doughnut Chart '; + + chart.series[0].dataLabels.isValue = true; + chart.series[1].dataLabels.isValue = true; + chart.series[2].dataLabels.isValue = true; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_doughnut_chart.xlsx'); + }); + test('FLUT_6975_Stockchartwithmarker', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').text = 'Open'; + sheet.getRangeByName('A3').text = 'High'; + sheet.getRangeByName('A4').text = 'Low'; + sheet.getRangeByName('A5').text = 'Close'; + + sheet.getRangeByName('B1').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B2').number = 30; + sheet.getRangeByName('B3').number = 50; + sheet.getRangeByName('B4').number = 10; + sheet.getRangeByName('B5').number = 40; + + sheet.getRangeByName('C1').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C2').number = 40; + sheet.getRangeByName('C3').number = 60; + sheet.getRangeByName('C4').number = 20; + sheet.getRangeByName('C5').number = 30; + + sheet.getRangeByName('D1').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D2').number = 35; + sheet.getRangeByName('D3').number = 55; + sheet.getRangeByName('D4').number = 15; + sheet.getRangeByName('D5').number = 45; + + sheet.getRangeByName('E1').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E2').number = 45; + sheet.getRangeByName('E3').number = 65; + sheet.getRangeByName('E4').number = 25; + sheet.getRangeByName('E5').number = 35; + + sheet.getRangeByName('F1').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F2').number = 50; + sheet.getRangeByName('F3').number = 70; + sheet.getRangeByName('F4').number = 30; + sheet.getRangeByName('F5').number = 60; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:F5'); + //Set Chart Title + chart1.chartTitle = 'Open_highlow-close'; + + ///apply marker + final ChartSerie serie1 = chart1.series[0]; + final ChartSerie serie2 = chart1.series[1]; + final ChartSerie serie3 = chart1.series[2]; + final ChartSerie serie4 = chart1.series[3]; + serie1.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie1.serieFormat.markerBackgroundColor = '#0000FF'; + serie1.serieFormat.markerBorderColor = '#0000FF'; + + serie2.serieFormat.markerStyle = ExcelChartMarkerType.square; + serie2.serieFormat.markerBackgroundColor = '#FFA500'; + serie2.serieFormat.markerBorderColor = '#FFA500'; + + serie3.serieFormat.markerStyle = ExcelChartMarkerType.starSquare; + serie3.serieFormat.markerBackgroundColor = '#023020'; + serie3.serieFormat.markerBorderColor = '#023020'; + + serie4.serieFormat.markerStyle = ExcelChartMarkerType.diamond; + serie4.serieFormat.markerBackgroundColor = '#964B00'; + serie4.serieFormat.markerBorderColor = '#964B00'; + + //Positioning the chart in the worksheet + chart1.topRow = 3; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockOpenHighLowClose; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'Stockchartwithmarker.xlsx'); + }); + + test('FLUT-6975-Line with marker Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:D6'); + chart1.chartTitle = 'Line chart with marker'; + chart1.isSeriesInRows = false; + + ///apply marker + final ChartSerie serie1 = chart1.series[0]; + final ChartSerie serie2 = chart1.series[1]; + final ChartSerie serie3 = chart1.series[2]; + + serie1.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie1.serieFormat.markerBackgroundColor = '#0000FF'; + serie1.serieFormat.markerBorderColor = '#0000FF'; + + serie2.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie2.serieFormat.markerBackgroundColor = '#FFA500'; + serie2.serieFormat.markerBorderColor = '#FFA500'; + + serie3.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie3.serieFormat.markerBackgroundColor = '#023020'; + serie3.serieFormat.markerBorderColor = '#023020'; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'FLUT_6975_Line chart with custom marker.xlsx'); + }); + + test('RangeSeriesNotRow', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('B11').number = 1700; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 1400; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.column; + chart.dataRange = sheet.getRangeByName('A11:B14'); + chart.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelSeriesNotRowChart.xlsx'); + }); + + test('RangeSeriesRow', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A2:C6'); + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelRangeSeriesRow.xlsx'); + }); + + test('ColumnChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelColumnChart.xlsx'); + }); + + test('PieChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.pie; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelPieChart.xlsx'); + }); + + test('LineChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A11:B17'); + chart.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelLineChart.xlsx'); + }); + + test('PieChartPosition', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.pie; + chart.dataRange = sheet.getRangeByName('A11:B17'); + chart.isSeriesInRows = false; + chart.chartTitle = 'Event Expenses'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.size = 12; + chart.topRow = 0; + chart.bottomRow = 10; + chart.leftColumn = 0; + chart.rightColumn = 7; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelPieChartPosition.xlsx'); + }); + test('BarChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.bar; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.hasTitle = true; + chart1.chartTitleArea.bold = true; + chart1.chartTitleArea.italic = true; + chart1.chartTitleArea.size = 15; + chart1.chartTitleArea.fontName = 'Arial'; + chart1.primaryCategoryAxis.title = 'X axis'; + chart1.primaryCategoryAxis.titleArea.bold = true; + chart1.primaryValueAxis.title = 'Y axis'; + chart1.primaryValueAxis.titleArea.bold = true; + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelBarChart.xlsx'); + }); + test('Bar stacked', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.barStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelBarStackedChart.xlsx'); + }); + test('Column stacked', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.columnStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelColumnStackedChart.xlsx'); + }); + test('Line stacked', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.lineStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelLineStackedChart.xlsx'); + }); + + test('ChartWithDateAxis', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Moths'; + sheet.getRangeByName('B1').text = 'Internal Sales Amount'; + sheet.getRangeByName('C1').text = 'Reseller Sales Amount'; + + sheet + .getRangeByName('A2') + .setDateTime(DateTime(2014, 01, 14, 14, 14, 14)); + sheet + .getRangeByName('A3') + .setDateTime(DateTime(2014, 02, 14, 14, 14, 14)); + sheet + .getRangeByName('A4') + .setDateTime(DateTime(2014, 03, 14, 14, 14, 14)); + sheet + .getRangeByName('A5') + .setDateTime(DateTime(2014, 04, 14, 14, 14, 14)); + sheet + .getRangeByName('A6') + .setDateTime(DateTime(2014, 05, 14, 14, 14, 14)); + sheet + .getRangeByName('A7') + .setDateTime(DateTime(2014, 06, 14, 14, 14, 14)); + sheet + .getRangeByName('A8') + .setDateTime(DateTime(2014, 07, 14, 14, 14, 14)); + sheet + .getRangeByName('A9') + .setDateTime(DateTime(2014, 08, 14, 14, 14, 14)); + sheet + .getRangeByName('A10') + .setDateTime(DateTime(2014, 09, 14, 14, 14, 14)); + sheet + .getRangeByName('A11') + .setDateTime(DateTime(2014, 10, 14, 14, 14, 14)); + sheet + .getRangeByName('A12') + .setDateTime(DateTime(2014, 11, 14, 14, 14, 14)); + sheet + .getRangeByName('A13') + .setDateTime(DateTime(2014, 12, 14, 14, 14, 14)); + + sheet.getRangeByName('B2').number = 1.25; + sheet.getRangeByName('B3').number = 2.5; + sheet.getRangeByName('B4').number = 1.8; + sheet.getRangeByName('B5').number = 2; + sheet.getRangeByName('B6').number = 3; + sheet.getRangeByName('B7').number = 2; + sheet.getRangeByName('B8').number = 2.8; + sheet.getRangeByName('B9').number = 4.2; + sheet.getRangeByName('B10').number = 4; + sheet.getRangeByName('B11').number = 2.5; + sheet.getRangeByName('B12').number = 3.5; + sheet.getRangeByName('B13').number = 3.5; + + sheet.getRangeByName('C2').number = 0.25; + sheet.getRangeByName('C3').number = 0.3; + sheet.getRangeByName('C4').number = 0.4; + sheet.getRangeByName('C5').number = 0.5; + sheet.getRangeByName('C6').number = 0.8; + sheet.getRangeByName('C7').number = 1; + sheet.getRangeByName('C8').number = 1.2; + sheet.getRangeByName('C9').number = 1.3; + sheet.getRangeByName('C10').number = 1.4; + sheet.getRangeByName('C11').number = 1.5; + sheet.getRangeByName('C12').number = 1.6; + sheet.getRangeByName('C13').number = 1.9; + + sheet.getRangeByName('A2:A13').numberFormat = 'm/d/yyyy'; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A1:C13'); + chart.isSeriesInRows = true; + chart.chartTitle = 'Event Expenses'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.size = 12; + chart.topRow = 0; + chart.bottomRow = 10; + chart.leftColumn = 0; + chart.rightColumn = 7; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartWithDateAxis.xlsx'); + }); + + test('ChartWithAxisNumberFormat', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Moths'; + sheet.getRangeByName('B1').text = 'Internal Sales Amount'; + sheet.getRangeByName('C1').text = 'Reseller Sales Amount'; + + sheet + .getRangeByName('A2') + .setDateTime(DateTime(2014, 01, 14, 14, 14, 14)); + sheet + .getRangeByName('A3') + .setDateTime(DateTime(2014, 02, 14, 14, 14, 14)); + sheet + .getRangeByName('A4') + .setDateTime(DateTime(2014, 03, 14, 14, 14, 14)); + sheet + .getRangeByName('A5') + .setDateTime(DateTime(2014, 04, 14, 14, 14, 14)); + sheet + .getRangeByName('A6') + .setDateTime(DateTime(2014, 05, 14, 14, 14, 14)); + sheet + .getRangeByName('A7') + .setDateTime(DateTime(2014, 06, 14, 14, 14, 14)); + sheet + .getRangeByName('A8') + .setDateTime(DateTime(2014, 07, 14, 14, 14, 14)); + sheet + .getRangeByName('A9') + .setDateTime(DateTime(2014, 08, 14, 14, 14, 14)); + sheet + .getRangeByName('A10') + .setDateTime(DateTime(2014, 09, 14, 14, 14, 14)); + sheet + .getRangeByName('A11') + .setDateTime(DateTime(2014, 10, 14, 14, 14, 14)); + sheet + .getRangeByName('A12') + .setDateTime(DateTime(2014, 11, 14, 14, 14, 14)); + sheet + .getRangeByName('A13') + .setDateTime(DateTime(2014, 12, 14, 14, 14, 14)); + + sheet.getRangeByName('B2').number = 1.25; + sheet.getRangeByName('B3').number = 2.5; + sheet.getRangeByName('B4').number = 1.8; + sheet.getRangeByName('B5').number = 2; + sheet.getRangeByName('B6').number = 3; + sheet.getRangeByName('B7').number = 2; + sheet.getRangeByName('B8').number = 2.8; + sheet.getRangeByName('B9').number = 4.2; + sheet.getRangeByName('B10').number = 4; + sheet.getRangeByName('B11').number = 2.5; + sheet.getRangeByName('B12').number = 3.5; + sheet.getRangeByName('B13').number = 3.5; + + sheet.getRangeByName('C2').number = 0.25; + sheet.getRangeByName('C3').number = 0.3; + sheet.getRangeByName('C4').number = 0.4; + sheet.getRangeByName('C5').number = 0.5; + sheet.getRangeByName('C6').number = 0.8; + sheet.getRangeByName('C7').number = 1; + sheet.getRangeByName('C8').number = 1.2; + sheet.getRangeByName('C9').number = 1.3; + sheet.getRangeByName('C10').number = 1.4; + sheet.getRangeByName('C11').number = 1.5; + sheet.getRangeByName('C12').number = 1.6; + sheet.getRangeByName('C13').number = 1.9; + + sheet.getRangeByName('A2:A13').numberFormat = 'm/d/yyyy'; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A1:C13'); + chart.isSeriesInRows = false; + chart.chartTitle = 'Event Expenses'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.size = 12; + chart.topRow = 0; + chart.bottomRow = 10; + chart.leftColumn = 0; + chart.rightColumn = 7; + chart.primaryCategoryAxis.numberFormat = 'mmmm'; + chart.primaryValueAxis.numberFormat = "#'M'"; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartWithAxisNumberFormat.xlsx'); + }); + + test('ChartWithAxisMinMax', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Moths'; + sheet.getRangeByName('B1').text = 'Internal Sales Amount'; + sheet.getRangeByName('C1').text = 'Reseller Sales Amount'; + + sheet + .getRangeByName('A2') + .setDateTime(DateTime(2014, 01, 14, 14, 14, 14)); + sheet + .getRangeByName('A3') + .setDateTime(DateTime(2014, 02, 14, 14, 14, 14)); + sheet + .getRangeByName('A4') + .setDateTime(DateTime(2014, 03, 14, 14, 14, 14)); + sheet + .getRangeByName('A5') + .setDateTime(DateTime(2014, 04, 14, 14, 14, 14)); + sheet + .getRangeByName('A6') + .setDateTime(DateTime(2014, 05, 14, 14, 14, 14)); + sheet + .getRangeByName('A7') + .setDateTime(DateTime(2014, 06, 14, 14, 14, 14)); + sheet + .getRangeByName('A8') + .setDateTime(DateTime(2014, 07, 14, 14, 14, 14)); + sheet + .getRangeByName('A9') + .setDateTime(DateTime(2014, 08, 14, 14, 14, 14)); + sheet + .getRangeByName('A10') + .setDateTime(DateTime(2014, 09, 14, 14, 14, 14)); + sheet + .getRangeByName('A11') + .setDateTime(DateTime(2014, 10, 14, 14, 14, 14)); + sheet + .getRangeByName('A12') + .setDateTime(DateTime(2014, 11, 14, 14, 14, 14)); + sheet + .getRangeByName('A13') + .setDateTime(DateTime(2014, 12, 14, 14, 14, 14)); + + sheet.getRangeByName('B2').number = 1.25; + sheet.getRangeByName('B3').number = 2.5; + sheet.getRangeByName('B4').number = 1.8; + sheet.getRangeByName('B5').number = 2; + sheet.getRangeByName('B6').number = 3; + sheet.getRangeByName('B7').number = 2; + sheet.getRangeByName('B8').number = 2.8; + sheet.getRangeByName('B9').number = 4.2; + sheet.getRangeByName('B10').number = 4; + sheet.getRangeByName('B11').number = 2.5; + sheet.getRangeByName('B12').number = 3.5; + sheet.getRangeByName('B13').number = 3.5; + + sheet.getRangeByName('C2').number = 0.25; + sheet.getRangeByName('C3').number = 0.3; + sheet.getRangeByName('C4').number = 0.4; + sheet.getRangeByName('C5').number = 0.5; + sheet.getRangeByName('C6').number = 0.8; + sheet.getRangeByName('C7').number = 1; + sheet.getRangeByName('C8').number = 1.2; + sheet.getRangeByName('C9').number = 1.3; + sheet.getRangeByName('C10').number = 1.4; + sheet.getRangeByName('C11').number = 1.5; + sheet.getRangeByName('C12').number = 1.6; + sheet.getRangeByName('C13').number = 1.9; + + sheet.getRangeByName('A2:A13').numberFormat = 'm/d/yyyy'; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A1:C13'); + chart.isSeriesInRows = false; + chart.chartTitle = 'Event Expenses'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.size = 12; + chart.topRow = 0; + chart.bottomRow = 10; + chart.leftColumn = 0; + chart.rightColumn = 7; + chart.primaryCategoryAxis.numberFormat = 'mmmm'; + chart.primaryValueAxis.numberFormat = "#'M'"; + chart.primaryValueAxis.maximumValue = 4.0; + chart.primaryValueAxis.minimumValue = 1.0; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ChartWithAxisMinMax.xlsx'); + }); + + test('Multiple Charts', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + + chart1.isSeriesInRows = false; + final Chart chart2 = charts.add(); + chart2.chartType = ExcelChartType.lineStacked; + chart2.dataRange = sheet.getRangeByName('A1:C6'); + chart2.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelMultipleChart.xlsx'); + }); + + test('ChartWithDataLabelFormats', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + final ChartSerie serie = chart1.series[0]; + serie.dataLabels.isValue = true; + serie.dataLabels.isCategoryName = true; + serie.dataLabels.isSeriesName = true; + serie.dataLabels.numberFormat = r'$#,##0_)'; + serie.dataLabels.textArea.bold = true; + serie.dataLabels.textArea.size = 12; + serie.dataLabels.textArea.fontName = 'Arial'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ChartWithDataLabelFormats.xlsx'); + }); + + test('ChartWithDataLabels', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + final ChartSerie serie = chart1.series[0]; + serie.dataLabels.isValue = true; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ChartWithDataLabels.xlsx'); + }); + + test('Legend bottom position and text formatting', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + chart1.legend!.position = ExcelLegendPosition.bottom; + chart1.legend!.textArea.bold = true; + chart1.legend!.textArea.italic = true; + chart1.legend!.textArea.size = 14; + chart1.legend!.textArea.fontName = 'Arial'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelLegendBottomPostionAndTextFormatting.xlsx'); + }); + + test('Legend left position and text formatting', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + chart1.legend!.position = ExcelLegendPosition.left; + chart1.legend!.textArea.bold = true; + chart1.legend!.textArea.italic = true; + chart1.legend!.textArea.size = 14; + chart1.legend!.textArea.fontName = 'Arial'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelLegendLeftPostionAndTextFormatting.xlsx'); + }); + + test('Legend right position and text formatting', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + chart1.legend!.position = ExcelLegendPosition.right; + chart1.legend!.textArea.bold = true; + chart1.legend!.textArea.italic = true; + chart1.legend!.textArea.size = 14; + chart1.legend!.textArea.fontName = 'Arial'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelLegendRightPostionAndTextFormatting.xlsx'); + }); + + test('Legend top position and text formatting', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + chart1.legend!.position = ExcelLegendPosition.top; + chart1.legend!.textArea.bold = true; + chart1.legend!.textArea.italic = true; + chart1.legend!.textArea.size = 14; + chart1.legend!.textArea.fontName = 'Arial'; + chart1.legend!.textArea.color = '#FF0000'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelLegendTopPostionAndTextFormatting.xlsx'); + }); + + test('Chart without legend', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + chart1.hasLegend = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartWithoutLegend.xlsx'); + }); + + test('MultipleWorksheetCharts', () { + final Workbook workbook = Workbook(2); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + + sheet.charts = charts; + + final Worksheet sheet1 = workbook.worksheets[1]; + sheet1.getRangeByName('A11').text = 'Venue'; + sheet1.getRangeByName('A12').text = 'Seating & Decor'; + sheet1.getRangeByName('A13').text = 'Technical Team'; + sheet1.getRangeByName('A14').text = 'performers'; + sheet1.getRangeByName('A15').text = "performer's Transport"; + sheet1.getRangeByName('A16').text = "performer's stay"; + sheet1.getRangeByName('A17').text = 'Marketing'; + sheet1.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet1.getRangeByName('B11').number = 17500; + sheet1.getRangeByName('B12').number = 1828; + sheet1.getRangeByName('B13').number = 800; + sheet1.getRangeByName('B14').number = 14000; + sheet1.getRangeByName('B15').number = 2600; + sheet1.getRangeByName('B16').number = 4464; + sheet1.getRangeByName('B17').number = 2700; + charts = ChartCollection(sheet1); + final Chart chart2 = charts.add(); + chart2.chartType = ExcelChartType.bar; + chart2.dataRange = sheet1.getRangeByName('A11:B17'); + chart2.isSeriesInRows = false; + + final Chart chart3 = charts.add(); + chart3.chartType = ExcelChartType.pie; + chart3.dataRange = sheet1.getRangeByName('A11:B17'); + chart3.isSeriesInRows = false; + sheet1.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'MultipleWorksheetCharts.xlsx'); + }); + + test('MultipleChartsToSingleSheet', () { + final Workbook workbook = Workbook(1); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + + final Chart chart2 = charts.add(); + chart2.chartType = ExcelChartType.bar; + chart2.dataRange = sheet.getRangeByName('A1:C6'); + chart2.isSeriesInRows = false; + + final Chart chart3 = charts.add(); + chart3.chartType = ExcelChartType.pie; + chart3.dataRange = sheet.getRangeByName('A1:C6'); + chart3.isSeriesInRows = false; + + final Chart chart4 = charts.add(); + chart4.chartType = ExcelChartType.column; + chart4.dataRange = sheet.getRangeByName('A1:C6'); + chart4.isSeriesInRows = false; + + final Chart chart5 = charts.add(); + chart5.chartType = ExcelChartType.barStacked; + chart5.dataRange = sheet.getRangeByName('A1:C6'); + chart5.isSeriesInRows = false; + + final Chart chart6 = charts.add(); + chart6.chartType = ExcelChartType.columnStacked; + chart6.dataRange = sheet.getRangeByName('A1:C6'); + chart6.isSeriesInRows = false; + + final Chart chart7 = charts.add(); + chart7.chartType = ExcelChartType.lineStacked; + chart7.dataRange = sheet.getRangeByName('A1:C6'); + chart7.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'MultipleChartsToSingleSheet.xlsx'); + }); + test('ExcelChartAxisFormat', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A2:C6'); + chart1.primaryCategoryAxis.titleArea.fontName = 'Arial'; + chart1.primaryCategoryAxis.titleArea.bold = true; + chart1.primaryCategoryAxis.titleArea.size = 14; + + chart1.primaryValueAxis.titleArea.bold = true; + chart1.primaryValueAxis.titleArea.italic = true; + chart1.primaryValueAxis.titleArea.size = 12; + final ChartSerie serie = chart1.series[0]; + serie.dataLabels.isValue = true; + serie.dataLabels.textArea.size = 14; + serie.dataLabels.textArea.bold = true; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartAxisFormat.xlsx'); + }); + + test('ExcelChartAreaColor', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A2:C6'); + chart.isSeriesInRows = false; + + chart.chartTitle = 'Test'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.color = '#111111'; + chart.chartTitleArea.size = 14; + + chart.primaryCategoryAxis.titleArea.bold = true; + chart.primaryCategoryAxis.titleArea.size = 12; + chart.primaryCategoryAxis.titleArea.color = '#00BFFF'; + + chart.primaryValueAxis.titleArea.bold = true; + chart.primaryValueAxis.titleArea.size = 12; + chart.primaryValueAxis.titleArea.color = '#80FF00'; + + final ChartSerie serie = chart.series[0]; + serie.dataLabels.isValue = true; + serie.dataLabels.textArea.size = 12; + serie.dataLabels.textArea.bold = true; + serie.dataLabels.textArea.color = '#FF00FF'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartAreaColor.xlsx'); + }); + + test('ExcelChartLinePattern', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A2:C6'); + chart.isSeriesInRows = false; + + chart.chartTitle = 'Test'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.color = '#FF0000'; + chart.chartTitleArea.size = 14; + + chart.primaryCategoryAxis.titleArea.bold = true; + chart.primaryCategoryAxis.titleArea.size = 12; + chart.primaryCategoryAxis.titleArea.color = '#00FFFF'; + + chart.primaryValueAxis.titleArea.bold = true; + chart.primaryValueAxis.titleArea.size = 12; + chart.primaryValueAxis.titleArea.color = '#ADD8E6'; + + final ChartSerie serie = chart.series[0]; + serie.dataLabels.isValue = true; + serie.dataLabels.textArea.size = 12; + serie.dataLabels.textArea.bold = true; + serie.dataLabels.textArea.color = '#0000A0'; + serie.linePattern = ExcelChartLinePattern.dash; + + final ChartSerie serie1 = chart.series[1]; + serie1.linePattern = ExcelChartLinePattern.longDashDot; + + chart.plotArea.linePattern = ExcelChartLinePattern.dashDot; + chart.plotArea.linePatternColor = '#0000FF'; + chart.linePattern = ExcelChartLinePattern.longDashDotDot; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartLinePattern.xlsx'); + }); + + test('ExcelChartLinePatternColor', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A2:C6'); + chart.isSeriesInRows = false; + + chart.chartTitle = 'Test'; + chart.chartTitleArea.bold = true; + chart.chartTitleArea.color = '#FF0000'; + chart.chartTitleArea.size = 14; + + chart.primaryCategoryAxis.titleArea.bold = true; + chart.primaryCategoryAxis.titleArea.size = 12; + chart.primaryCategoryAxis.titleArea.color = '#800080'; + + chart.primaryValueAxis.titleArea.bold = true; + chart.primaryValueAxis.titleArea.size = 12; + chart.primaryValueAxis.titleArea.color = '#FFFF00'; + + final ChartSerie serie = chart.series[0]; + serie.dataLabels.isValue = true; + serie.dataLabels.textArea.size = 12; + serie.dataLabels.textArea.bold = true; + serie.dataLabels.textArea.color = '#00FF00'; + serie.linePattern = ExcelChartLinePattern.dash; + serie.linePatternColor = '#FF00FF'; + + final ChartSerie serie1 = chart.series[1]; + serie1.linePattern = ExcelChartLinePattern.longDashDot; + serie1.linePatternColor = '#FF0000'; + + chart.plotArea.linePattern = ExcelChartLinePattern.solid; + chart.plotArea.linePatternColor = '#00FFFF'; + chart.linePattern = ExcelChartLinePattern.longDashDotDot; + chart.linePatternColor = '#0000FF'; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ExcelChartLinePatternColor.xlsx'); + }); + + test('MultipleChartsAndPicturesInMultipleSheet', () { + final Workbook workbook = Workbook(2); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + + sheet.charts = charts; + final Picture picture = sheet.pictures.addBase64(3, 4, invoicejpeg); + picture.lastRow = 7; + picture.lastColumn = 8; + + final Picture picture2 = sheet.pictures.addBase64(3, 4, image1jpg); + picture2.lastRow = 7; + picture2.lastColumn = 8; + + final Picture picture3 = sheet.pictures.addBase64(3, 4, image3png); + picture3.lastRow = 7; + picture3.lastColumn = 8; + + final Worksheet sheet1 = workbook.worksheets[1]; + sheet1.getRangeByName('A11').text = 'Venue'; + sheet1.getRangeByName('A12').text = 'Seating & Decor'; + sheet1.getRangeByName('A13').text = 'Technical Team'; + sheet1.getRangeByName('A14').text = 'performers'; + sheet1.getRangeByName('A15').text = "performer's Transport"; + sheet1.getRangeByName('A16').text = "performer's stay"; + sheet1.getRangeByName('A17').text = 'Marketing'; + sheet1.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet1.getRangeByName('B11').number = 17500; + sheet1.getRangeByName('B12').number = 1828; + sheet1.getRangeByName('B13').number = 800; + sheet1.getRangeByName('B14').number = 14000; + sheet1.getRangeByName('B15').number = 2600; + sheet1.getRangeByName('B16').number = 4464; + sheet1.getRangeByName('B17').number = 2700; + charts = ChartCollection(sheet1); + + final Chart chart4 = charts.add(); + chart4.chartType = ExcelChartType.line; + chart4.dataRange = sheet1.getRangeByName('A11:B17'); + chart4.isSeriesInRows = false; + + final Chart chart2 = charts.add(); + chart2.chartType = ExcelChartType.bar; + chart2.dataRange = sheet1.getRangeByName('A11:B17'); + chart2.isSeriesInRows = false; + + final Chart chart3 = charts.add(); + chart3.chartType = ExcelChartType.pie; + chart3.dataRange = sheet1.getRangeByName('A11:B17'); + chart3.isSeriesInRows = false; + + sheet1.charts = charts; + final Picture picture4 = sheet1.pictures.addBase64(3, 4, invoicejpeg); + picture4.lastRow = 7; + picture4.lastColumn = 8; + + final Picture picture5 = sheet1.pictures.addBase64(3, 4, image1jpg); + picture5.lastRow = 7; + picture5.lastColumn = 8; + + final Picture picture6 = sheet1.pictures.addBase64(3, 4, image10jpg); + picture6.lastRow = 7; + picture6.lastColumn = 8; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'MultipleChartsAndPicturesInMultipleSheet.xlsx'); + }); + + test('MultipleChartsAndPicturesInSingleSheet', () { + //Create a Excel document. + //Creating a workbook. + final Workbook workbook = Workbook(); + //Accessing via index + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('B11').number = 1700; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 1400; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.column; + chart.dataRange = sheet.getRangeByName('A11:B14'); + chart.isSeriesInRows = false; + + final Chart chart2 = charts.add(); + chart2.chartType = ExcelChartType.line; + chart2.dataRange = sheet.getRangeByName('A11:B14'); + chart2.isSeriesInRows = false; + + final Chart chart3 = charts.add(); + chart3.chartType = ExcelChartType.bar; + chart3.dataRange = sheet.getRangeByName('A11:B14'); + chart3.isSeriesInRows = false; + + sheet.charts = charts; + + final Picture picture = sheet.pictures.addBase64(3, 4, invoicejpeg); + picture.lastRow = 7; + picture.lastColumn = 8; + + final Picture picture2 = sheet.pictures.addBase64(3, 4, image1jpg); + picture2.lastRow = 7; + picture2.lastColumn = 8; + + final Picture picture3 = sheet.pictures.addBase64(3, 4, image3png); + picture3.lastRow = 7; + picture3.lastColumn = 8; + + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'MultipleChartsAndPicturesInSingleSheet.xlsx'); + }); + + test('AreaChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.area; + 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; + + sheet.charts = charts; + + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'AreaChart.xlsx'); + }); + + test('AreaStackedChart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.areaStacked; + 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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'AreaStackedChart.xlsx'); + }); + + test('AreaStacked100Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.areaStacked100; + 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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'AreaStacked100Chart.xlsx'); + }); + + test('columnStacked100Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnStacked100; + 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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'ColumnStacked100Chart.xlsx'); + }); + + test('BarStacked100Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.barStacked100; + 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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'BarStacked100Chart.xlsx'); + }); + + test('LineStacked100Chart', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.lineStacked100; + 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; + + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + saveAsExcel(bytes, 'LineStacked100Chart.xlsx'); + }); + test('Hyperlink and Images', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + final Range range = sheet.getRangeByIndex(1, 1); + sheet.hyperlinks + .add(range, HyperlinkType.url, 'http://www.syncfusion.com'); + sheet.pictures.addBase64(2, 1, image1jpg); + final Range range1 = sheet.getRangeByIndex(1, 7); + sheet.hyperlinks.add(range1, HyperlinkType.workbook, 'Sheet1!A15'); + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('B11').number = 1700; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 1400; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.column; + chart.dataRange = sheet.getRangeByName('A11:B14'); + chart.isSeriesInRows = false; + chart.topRow = 10; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ChartHyperlinkImage.xlsx'); + }); + test('CategoryWithEmptyValue', () { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = workbook.saveAsStream(); + saveAsExcel(bytes, 'ChartCategoryWithEmptyValue.xlsx'); + }); + }); + group('Charts Async', () { + test('FLUT-6975-Line with marker ChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.lineMarkers; + chart1.dataRange = sheet.getRangeByName('A1:D6'); + chart1.chartTitle = 'Line chart with marker'; + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_ExcelLineChartwithmarkerAsync.xlsx'); + }); + test('FLUT-6975-Line stacked with markerAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.lineMarkersStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.chartTitle = 'Stacked line chart with marker'; + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT-6975-ExcelLineStaChartwithmarkerAsync.xlsx'); + }); + + test('FLUT_6975_3D_LineChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + sheet.getRangeByName('C11:C17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('C11').number = 18584; + sheet.getRangeByName('C12').number = 1348; + sheet.getRangeByName('C13').number = 400; + sheet.getRangeByName('C14').number = 12000; + sheet.getRangeByName('C15').number = 1800; + sheet.getRangeByName('C16').number = 477; + sheet.getRangeByName('C17').number = 1908; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line3D; + chart.dataRange = sheet.getRangeByName('A11:C17'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Line Chart 3D'; + + //Set Rotation and Elevation + chart.rotation = 20; + chart.elevation = 15; + chart.perspective = 45; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + + ///Set gap depth for 3d line + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 120; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_3D_LineChartAsync.xlsx'); + }); + test('FLUT_6975_3D_Column_chartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + //Set Chart Title + chart1.chartTitle = 'Column 3D'; + + //Set Rotation and Elevation + chart1.rotation = 40; + chart1.elevation = 75; + chart1.perspective = 25; + + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.column3D; + chart1.dataRange = sheet.getRangeByName('A2:C6'); + + //set gap width and gap depth + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 120; + serie1.serieFormat.commonSerieOptions.gapWidth = 125; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_3D_Column_chartAsync.xlsx'); + }); + test('FLUT_6975_3D_Column_Clustered3D_chartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnClustered3D; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + //Set Chart Title + chart.chartTitle = 'Column Clustered 3D chart'; + + //Set Rotation and Elevation + chart.rotation = 35; + chart.elevation = 25; + chart.perspective = 45; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + + chart.legend!.position = ExcelLegendPosition.right; + chart.legend!.textArea.bold = true; + chart.legend!.textArea.italic = true; + chart.legend!.textArea.size = 14; + chart.legend!.textArea.fontName = 'Arial'; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 105; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_3D_Column_Clustered3D_chartAsync.xlsx'); + }); + test('FLUT_6975_Column_stacked_3D_chartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnStacked3D; + chart.dataRange = sheet.getRangeByName('A1:C6'); + chart.isSeriesInRows = false; + //Set Chart Title + chart.chartTitle = 'Column stacked 3D chart'; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 135; + serie1.serieFormat.commonSerieOptions.gapWidth = 120; + + //Set Rotation and Elevation + chart.rotation = 60; + chart.elevation = 25; + chart.perspective = 25; + + //Positioning the chart in the worksheet + chart.topRow = 8; + chart.leftColumn = 1; + chart.bottomRow = 23; + chart.rightColumn = 8; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_columnStacked3DAsync.xlsx'); + }); + test('FLUT-6975-columnStacked100Chart3DAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.columnStacked1003D; + chart.dataRange = sheet.getRangeByName('A1:D6'); + chart.isSeriesInRows = false; + + //Set Chart Title + chart.chartTitle = 'Column stacked 100 3D chart'; + + //Set Rotation and Elevation + chart.rotation = 30; + chart.elevation = 55; + chart.perspective = 50; + + //set gap width and gap depth + final ChartSerie serie1 = chart.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 125; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + //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; + + sheet.charts = charts; + final List bytes = await workbook.save(); + workbook.dispose(); + + saveAsExcel(bytes, 'FLUT_6975_columnStacked1003D_chartAsync.xlsx'); + }); + test('FLUT_6975_Pie3DChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.pie3D; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + + //set first slice Angle and explosion + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.firstSliceAngle = 90; + serie1.serieFormat.pieExplosionPercent = 105; + chart1.elevation = 45; + chart1.perspective = 20; + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_Pie3DChartAsync.xlsx'); + }); + test('FLUT_6975_Stock_charts in single sheetAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').dateTime = DateTime(2017, 4); + sheet.getRangeByName('A3').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('A4').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('A5').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('A6').dateTime = DateTime(2017, 4, 5); + + sheet.getRangeByName('B1').text = 'Volume'; + sheet.getRangeByName('B2').number = 10000; + sheet.getRangeByName('B3').number = 20000; + sheet.getRangeByName('B4').number = 30000; + sheet.getRangeByName('B5').number = 25000; + sheet.getRangeByName('B6').number = 15000; + + sheet.getRangeByName('C1').text = 'Open'; + sheet.getRangeByName('C2').number = 30; + sheet.getRangeByName('C3').number = 40; + sheet.getRangeByName('C4').number = 35; + sheet.getRangeByName('C5').number = 45; + sheet.getRangeByName('C6').number = 50; + + sheet.getRangeByName('D1').text = 'High'; + sheet.getRangeByName('D2').number = 50; + sheet.getRangeByName('D3').number = 60; + sheet.getRangeByName('D4').number = 55; + sheet.getRangeByName('D5').number = 65; + sheet.getRangeByName('D6').number = 70; + + sheet.getRangeByName('E1').text = 'Low'; + sheet.getRangeByName('E2').number = 10; + sheet.getRangeByName('E3').number = 20; + sheet.getRangeByName('E4').number = 15; + sheet.getRangeByName('E5').number = 25; + sheet.getRangeByName('E6').number = 30; + + sheet.getRangeByName('F1').text = 'Close'; + sheet.getRangeByName('F2').number = 40; + sheet.getRangeByName('F3').number = 30; + sheet.getRangeByName('F4').number = 45; + sheet.getRangeByName('F5').number = 35; + sheet.getRangeByName('F6').number = 60; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:F6'); + //Set Chart Title + chart1.chartTitle = 'Stock chart'; + chart1.isSeriesInRows = false; + + //set gap width + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapWidth = 105; + + //Positioning the chart in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockVolumeOpenHighLowClose; + sheet.charts = charts; + + sheet.getRangeByName('A7').text = 'Date'; + sheet.getRangeByName('A8').text = 'Open'; + sheet.getRangeByName('A9').text = 'High'; + sheet.getRangeByName('A10').text = 'Low'; + sheet.getRangeByName('A11').text = 'Close'; + + sheet.getRangeByName('B7').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B8').number = 30; + sheet.getRangeByName('B9').number = 50; + sheet.getRangeByName('B10').number = 10; + sheet.getRangeByName('B11').number = 40; + + sheet.getRangeByName('C7').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C8').number = 40; + sheet.getRangeByName('C9').number = 60; + sheet.getRangeByName('C10').number = 20; + sheet.getRangeByName('C11').number = 30; + + sheet.getRangeByName('D7').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D8').number = 35; + sheet.getRangeByName('D9').number = 55; + sheet.getRangeByName('D10').number = 15; + sheet.getRangeByName('D11').number = 45; + + sheet.getRangeByName('E7').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E8').number = 45; + sheet.getRangeByName('E9').number = 65; + sheet.getRangeByName('E10').number = 25; + sheet.getRangeByName('E11').number = 35; + + sheet.getRangeByName('F7').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F8').number = 50; + sheet.getRangeByName('F9').number = 70; + sheet.getRangeByName('F10').number = 30; + sheet.getRangeByName('F11').number = 60; + + final Chart chart2 = charts.add(); + chart2.dataRange = sheet.getRangeByName('A7:F11'); + //Set Chart Title + chart2.chartTitle = 'Stock chart'; + chart2.isSeriesInRows = true; + chart2.chartType = ExcelChartType.stockOpenHighLowClose; + + sheet.getRangeByName('A12').text = 'Date'; + sheet.getRangeByName('A13').text = 'High'; + sheet.getRangeByName('A14').text = 'Low'; + sheet.getRangeByName('A15').text = 'Close'; + + sheet.getRangeByName('B12').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B13').number = 50; + sheet.getRangeByName('B14').number = 10; + sheet.getRangeByName('B15').number = 40; + + sheet.getRangeByName('C12').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C13').number = 60; + sheet.getRangeByName('C14').number = 20; + sheet.getRangeByName('C15').number = 30; + + sheet.getRangeByName('D12').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D13').number = 55; + sheet.getRangeByName('D14').number = 15; + sheet.getRangeByName('D15').number = 45; + + sheet.getRangeByName('E12').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E13').number = 65; + sheet.getRangeByName('E14').number = 25; + sheet.getRangeByName('E15').number = 35; + + sheet.getRangeByName('F12').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F13').number = 70; + sheet.getRangeByName('F14').number = 30; + sheet.getRangeByName('F15').number = 60; + + final Chart chart3 = charts.add(); + chart3.dataRange = sheet.getRangeByName('A12:F15'); + //Set Chart Title + chart3.chartTitle = 'High-Low-close'; + chart3.isSeriesInRows = true; + chart3.chartType = ExcelChartType.stockHighLowClose; + + sheet.getRangeByName('A16').text = 'Date'; + sheet.getRangeByName('A17').dateTime = DateTime(2017, 4); + sheet.getRangeByName('A18').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('A19').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('A20').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('A21').dateTime = DateTime(2017, 4, 5); + + sheet.getRangeByName('B16').text = 'Volume'; + sheet.getRangeByName('B17').number = 10000; + sheet.getRangeByName('B18').number = 20000; + sheet.getRangeByName('B19').number = 30000; + sheet.getRangeByName('B20').number = 25000; + sheet.getRangeByName('B21').number = 15000; + + sheet.getRangeByName('C16').text = 'High'; + sheet.getRangeByName('C17').number = 50; + sheet.getRangeByName('C18').number = 60; + sheet.getRangeByName('C19').number = 55; + sheet.getRangeByName('C20').number = 65; + sheet.getRangeByName('C21').number = 70; + + sheet.getRangeByName('D16').text = 'Low'; + sheet.getRangeByName('D17').number = 10; + sheet.getRangeByName('D18').number = 20; + sheet.getRangeByName('D19').number = 15; + sheet.getRangeByName('D20').number = 25; + sheet.getRangeByName('D21').number = 30; + + sheet.getRangeByName('E16').text = 'Close'; + sheet.getRangeByName('E17').number = 40; + sheet.getRangeByName('E18').number = 30; + sheet.getRangeByName('E19').number = 45; + sheet.getRangeByName('E20').number = 35; + sheet.getRangeByName('E21').number = 60; + + final Chart chart4 = charts.add(); + chart4.dataRange = sheet.getRangeByName('A16:E21'); + //Set Chart Title + chart4.chartTitle = 'Volume-High-Low-close-chart'; + chart4.isSeriesInRows = false; + + //set gap width + final ChartSerie serie2 = chart4.series[0]; + serie2.serieFormat.commonSerieOptions.gapWidth = 105; + + //Positioning the charts in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + chart2.topRow = 8; + chart2.leftColumn = 10; + chart2.bottomRow = 22; + chart2.rightColumn = 19; + + chart3.topRow = 27; + chart3.leftColumn = 1; + chart3.bottomRow = 42; + chart3.rightColumn = 8; + + chart4.topRow = 27; + chart4.leftColumn = 10; + chart4.bottomRow = 42; + chart4.rightColumn = 19; + + chart4.chartType = ExcelChartType.stockVolumeHighLowClose; + sheet.charts = charts; + final List bytes = await workbook.save(); + + saveAsExcel(bytes, 'FLUT_6975_Sock_chartsAsync.xlsx'); + }); + test('FLUT_6975_3D_chartsAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + final Chart chart2 = charts.add(); + final Chart chart3 = charts.add(); + final Chart chart4 = charts.add(); + final Chart chart5 = charts.add(); + + chart1.chartType = ExcelChartType.line3D; + chart1.dataRange = sheet.getRangeByName('A1:D6'); + + chart2.chartType = ExcelChartType.column3D; + chart2.dataRange = sheet.getRangeByName('A1:D6'); + + chart3.chartType = ExcelChartType.columnClustered3D; + chart3.dataRange = sheet.getRangeByName('A1:D6'); + + chart4.chartType = ExcelChartType.columnStacked1003D; + chart4.dataRange = sheet.getRangeByName('A1:D6'); + + chart5.chartType = ExcelChartType.barStacked1003D; + chart5.dataRange = sheet.getRangeByName('A1:D6'); + + //Set Chart Title + chart1.chartTitle = 'Line 3D chart'; + chart2.chartTitle = 'column 3D chart'; + chart3.chartTitle = 'Column clustered 3D chart'; + chart4.chartTitle = 'ColumnStacked1003D chart'; + chart5.chartTitle = 'Barstacked100 chart'; + //Set Rotation and Elevation,perspective + chart1.rotation = 20; + chart1.elevation = 15; + chart1.perspective = 25; + final ChartSerie serie1 = chart1.series[0]; + serie1.serieFormat.commonSerieOptions.gapDepth = 55; + + chart2.rotation = 20; + chart2.elevation = 15; + chart2.perspective = 30; + chart2.rightAngleAxes = false; + + chart3.rotation = 25; + chart3.elevation = 10; + chart3.perspective = 40; + chart3.rightAngleAxes = false; + + chart3.rotation = 40; + chart3.elevation = 55; + chart3.perspective = 15; + + chart4.rotation = 30; + chart4.elevation = 25; + chart4.perspective = 25; + + chart5.rotation = 35; + chart5.elevation = 60; + chart5.perspective = 10; + + //Positioning the charts in the worksheet + chart1.topRow = 8; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + + chart2.topRow = 8; + chart2.leftColumn = 10; + chart2.bottomRow = 22; + chart2.rightColumn = 19; + + chart3.topRow = 27; + chart3.leftColumn = 1; + chart3.bottomRow = 42; + chart3.rightColumn = 8; + + chart4.topRow = 27; + chart4.leftColumn = 10; + chart4.bottomRow = 42; + chart4.rightColumn = 19; + + chart5.topRow = 45; + chart5.leftColumn = 1; + chart5.bottomRow = 60; + chart5.rightColumn = 8; + + chart1.isSeriesInRows = false; + chart2.isSeriesInRows = false; + + sheet.charts = charts; + + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_3D_chartsAsync.xlsx'); + }); + test('FLUT_6975_doughNut_chartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Company'; + sheet.getRangeByName('A2').text = 'Company A'; + sheet.getRangeByName('A3').text = 'Company B'; + sheet.getRangeByName('A4').text = 'Company C'; + sheet.getRangeByName('A5').text = 'Company D'; + sheet.getRangeByName('A6').text = 'Company E'; + sheet.getRangeByName('A7').text = 'Others'; + + sheet.getRangeByName('B1').dateTime = DateTime(2016); + sheet.getRangeByName('B2').number = 0.28; + sheet.getRangeByName('B3').number = 0.5; + sheet.getRangeByName('B4').number = 0.17; + sheet.getRangeByName('B5').number = 0.18; + sheet.getRangeByName('B6').number = 0.17; + sheet.getRangeByName('B7').number = 0.15; + + sheet.getRangeByName('C1').dateTime = DateTime(2017); + sheet.getRangeByName('C2').number = 0.25; + sheet.getRangeByName('C3').number = 0.9; + sheet.getRangeByName('C4').number = 0.19; + sheet.getRangeByName('C5').number = 0.22; + sheet.getRangeByName('C6').number = 0.15; + sheet.getRangeByName('C7').number = 0.10; + + sheet.getRangeByName('D1').dateTime = DateTime(2018); + sheet.getRangeByName('D2').number = 0.35; + sheet.getRangeByName('D3').number = 0.1; + sheet.getRangeByName('D4').number = 0.145; + sheet.getRangeByName('D5').number = 0.55; + sheet.getRangeByName('D6').number = 0.55; + sheet.getRangeByName('D7').number = 0.40; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.doughnut; + chart.dataRange = sheet.getRangeByName('A2:D7'); + chart.isSeriesInRows = false; + + chart.series[0].serieFormat.commonSerieOptions.holeSizePercent = 80; + chart.series[0].serieFormat.commonSerieOptions.firstSliceAngle = 10; + chart.series[0].serieFormat.pieExplosionPercent = 50; + + //Set Chart Title + chart.chartTitle = 'Doughnut Chart '; + + chart.series[0].dataLabels.isValue = true; + chart.series[1].dataLabels.isValue = true; + chart.series[2].dataLabels.isValue = true; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_doughnut_chartAsync.xlsx'); + }); + test('FLUT_6975_StockchartwithmarkerAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Date'; + sheet.getRangeByName('A2').text = 'Open'; + sheet.getRangeByName('A3').text = 'High'; + sheet.getRangeByName('A4').text = 'Low'; + sheet.getRangeByName('A5').text = 'Close'; + + sheet.getRangeByName('B1').dateTime = DateTime(2017, 4); + sheet.getRangeByName('B2').number = 30; + sheet.getRangeByName('B3').number = 50; + sheet.getRangeByName('B4').number = 10; + sheet.getRangeByName('B5').number = 40; + + sheet.getRangeByName('C1').dateTime = DateTime(2017, 4, 2); + sheet.getRangeByName('C2').number = 40; + sheet.getRangeByName('C3').number = 60; + sheet.getRangeByName('C4').number = 20; + sheet.getRangeByName('C5').number = 30; + + sheet.getRangeByName('D1').dateTime = DateTime(2017, 4, 3); + sheet.getRangeByName('D2').number = 35; + sheet.getRangeByName('D3').number = 55; + sheet.getRangeByName('D4').number = 15; + sheet.getRangeByName('D5').number = 45; + + sheet.getRangeByName('E1').dateTime = DateTime(2017, 4, 4); + sheet.getRangeByName('E2').number = 45; + sheet.getRangeByName('E3').number = 65; + sheet.getRangeByName('E4').number = 25; + sheet.getRangeByName('E5').number = 35; + + sheet.getRangeByName('F1').dateTime = DateTime(2017, 4, 5); + sheet.getRangeByName('F2').number = 50; + sheet.getRangeByName('F3').number = 70; + sheet.getRangeByName('F4').number = 30; + sheet.getRangeByName('F5').number = 60; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.dataRange = sheet.getRangeByName('A1:F5'); + //Set Chart Title + chart1.chartTitle = 'Open_highlow-close'; + + ///apply marker + final ChartSerie serie1 = chart1.series[0]; + final ChartSerie serie2 = chart1.series[1]; + final ChartSerie serie3 = chart1.series[2]; + final ChartSerie serie4 = chart1.series[3]; + serie1.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie1.serieFormat.markerBackgroundColor = '#0000FF'; + serie1.serieFormat.markerBorderColor = '#0000FF'; + + serie2.serieFormat.markerStyle = ExcelChartMarkerType.square; + serie2.serieFormat.markerBackgroundColor = '#FFA500'; + serie2.serieFormat.markerBorderColor = '#FFA500'; + + serie3.serieFormat.markerStyle = ExcelChartMarkerType.starSquare; + serie3.serieFormat.markerBackgroundColor = '#023020'; + serie3.serieFormat.markerBorderColor = '#023020'; + + serie4.serieFormat.markerStyle = ExcelChartMarkerType.diamond; + serie4.serieFormat.markerBackgroundColor = '#964B00'; + serie4.serieFormat.markerBorderColor = '#964B00'; + + //Positioning the chart in the worksheet + chart1.topRow = 3; + chart1.leftColumn = 1; + chart1.bottomRow = 23; + chart1.rightColumn = 8; + chart1.chartType = ExcelChartType.stockOpenHighLowClose; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'StockchartwithmarkerAsync.xlsx'); + }); + + test('FLUT-6975-Line with marker ChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:D6'); + chart1.chartTitle = 'Line chart with marker'; + chart1.isSeriesInRows = false; + + ///apply marker + final ChartSerie serie1 = chart1.series[0]; + final ChartSerie serie2 = chart1.series[1]; + final ChartSerie serie3 = chart1.series[2]; + + serie1.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie1.serieFormat.markerBackgroundColor = '#0000FF'; + serie1.serieFormat.markerBorderColor = '#0000FF'; + + serie2.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie2.serieFormat.markerBackgroundColor = '#FFA500'; + serie2.serieFormat.markerBorderColor = '#FFA500'; + + serie3.serieFormat.markerStyle = ExcelChartMarkerType.circle; + serie3.serieFormat.markerBackgroundColor = '#023020'; + serie3.serieFormat.markerBorderColor = '#023020'; + + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'FLUT_6975_Line chart with custom markerAsync.xlsx'); + }); + + test('ColumnChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.column; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelColumnChartAsync.xlsx'); + }); + + test('PieChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.pie; + chart1.dataRange = sheet.getRangeByName('A11:B17'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelPieChartAsync.xlsx'); + }); + + test('LineChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A11').text = 'Venue'; + sheet.getRangeByName('A12').text = 'Seating & Decor'; + sheet.getRangeByName('A13').text = 'Technical Team'; + sheet.getRangeByName('A14').text = 'performers'; + sheet.getRangeByName('A15').text = "performer's Transport"; + sheet.getRangeByName('A16').text = "performer's stay"; + sheet.getRangeByName('A17').text = 'Marketing'; + sheet.getRangeByName('B11:B17').numberFormat = r'$#,##0_)'; + sheet.getRangeByName('B11').number = 17500; + sheet.getRangeByName('B12').number = 1828; + sheet.getRangeByName('B13').number = 800; + sheet.getRangeByName('B14').number = 14000; + sheet.getRangeByName('B15').number = 2600; + sheet.getRangeByName('B16').number = 4464; + sheet.getRangeByName('B17').number = 2700; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.line; + chart.dataRange = sheet.getRangeByName('A11:B17'); + chart.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelLineChartAsync.xlsx'); + }); + test('BarChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.bar; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.hasTitle = true; + chart1.chartTitleArea.bold = true; + chart1.chartTitleArea.italic = true; + chart1.chartTitleArea.size = 15; + chart1.chartTitleArea.fontName = 'Arial'; + chart1.primaryCategoryAxis.title = 'X axis'; + chart1.primaryCategoryAxis.titleArea.bold = true; + chart1.primaryValueAxis.title = 'Y axis'; + chart1.primaryValueAxis.titleArea.bold = true; + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelBarChartAsync.xlsx'); + }); + test('Bar stackedAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.barStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelBarStackedChartAsync.xlsx'); + }); + test('Column stackedAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.columnStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelColumnStackedChartAsync.xlsx'); + }); + test('Line stackedAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.lineStacked; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'ExcelLineStackedChartAsync.xlsx'); + }); + test('MultipleChartsToSingleSheetAsync ', () async { + final Workbook workbook = Workbook(1); + final Worksheet sheet = workbook.worksheets[0]; + sheet.getRangeByName('A1').text = 'Items'; + sheet.getRangeByName('B1').text = r'Amount(in $)'; + sheet.getRangeByName('C1').text = 'Count'; + + sheet.getRangeByName('A2').text = 'Beverages'; + sheet.getRangeByName('A3').text = 'Condiments'; + sheet.getRangeByName('A4').text = 'Confections'; + sheet.getRangeByName('A5').text = 'Dairy Products'; + sheet.getRangeByName('A6').text = 'Grains / Cereals'; + + sheet.getRangeByName('B2').number = 2776; + sheet.getRangeByName('B3').number = 1077; + sheet.getRangeByName('B4').number = 2287; + sheet.getRangeByName('B5').number = 1368; + sheet.getRangeByName('B6').number = 3325; + + sheet.getRangeByName('C2').number = 925; + sheet.getRangeByName('C3').number = 378; + sheet.getRangeByName('C4').number = 880; + sheet.getRangeByName('C5').number = 581; + sheet.getRangeByName('C6').number = 189; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart1 = charts.add(); + chart1.chartType = ExcelChartType.line; + chart1.dataRange = sheet.getRangeByName('A1:C6'); + chart1.isSeriesInRows = false; + + final Chart chart2 = charts.add(); + chart2.chartType = ExcelChartType.bar; + chart2.dataRange = sheet.getRangeByName('A1:C6'); + chart2.isSeriesInRows = false; + + final Chart chart3 = charts.add(); + chart3.chartType = ExcelChartType.pie; + chart3.dataRange = sheet.getRangeByName('A1:C6'); + chart3.isSeriesInRows = false; + + final Chart chart4 = charts.add(); + chart4.chartType = ExcelChartType.column; + chart4.dataRange = sheet.getRangeByName('A1:C6'); + chart4.isSeriesInRows = false; + + final Chart chart5 = charts.add(); + chart5.chartType = ExcelChartType.barStacked; + chart5.dataRange = sheet.getRangeByName('A1:C6'); + chart5.isSeriesInRows = false; + + final Chart chart6 = charts.add(); + chart6.chartType = ExcelChartType.columnStacked; + chart6.dataRange = sheet.getRangeByName('A1:C6'); + chart6.isSeriesInRows = false; + + final Chart chart7 = charts.add(); + chart7.chartType = ExcelChartType.lineStacked; + chart7.dataRange = sheet.getRangeByName('A1:C6'); + chart7.isSeriesInRows = false; + sheet.charts = charts; + final List bytes = await workbook.save(); + saveAsExcel(bytes, 'MultipleChartsToSingleSheetAsync.xlsx'); + }); + test('AreaChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.area; + 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; + + sheet.charts = charts; + + final List bytes = await workbook.save(); + workbook.dispose(); + + saveAsExcel(bytes, 'AreaChartAsync.xlsx'); + }); + + test('AreaStackedChartAsync ', () async { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + 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; + + final ChartCollection charts = ChartCollection(sheet); + final Chart chart = charts.add(); + chart.chartType = ExcelChartType.areaStacked; + 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; + + sheet.charts = charts; + final List bytes = await workbook.save(); + workbook.dispose(); + + saveAsExcel(bytes, 'AreaStackedChartAsync.xlsx'); + }); + }); +} diff --git a/packages/syncfusion_officechart/lib/src/test/images.dart b/packages/syncfusion_officechart/lib/src/test/images.dart new file mode 100644 index 000000000..08ebb96d7 --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/test/images.dart @@ -0,0 +1,12 @@ +// ignore: unused_element +String _image1jpg = + '/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAsJCQcJCQcJCQkJCwkJCQkJCQsJCwsMCwsLDA0QDBEODQ4MEhkSJRodJR0ZHxwpKRYlNzU2GioyPi0pMBk7IRP/2wBDAQcICAsJCxULCxUsHRkdLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCz/wAARCACCAHMDASIAAhEBAxEB/8QAHAAAAQUBAQEAAAAAAAAAAAAAAAEDBAUGBwII/8QARhAAAgEDAgMEBAgLBgcAAAAAAQIDAAQRBRIGEyExQVFhFCIycQcVI1JygaHBFjM0QmJzkbGys9E1Q3WCkvAlU1V0g5PS/8QAHAEAAQUBAQEAAAAAAAAAAAAABQACAwQGAQcI/8QANREAAQMCAwMICgMBAAAAAAAAAQACAwQRBSFBEjFREyIyYXGBobEUFSMzNFKRwdHwBhbhkv/aAAwDAQACEQMRAD8A6ZDHjrXue4gtopJpnCxRqWdiegApJ7iG2jeWR1SNRlnY4UVzzXdck1R+VFlbRGJC98hz0ZqI09M6oduyR/D8OlxGWwybqf3VTtT4tnm3xWEYjjPQyzKGdh+ih6Csu7vIxdzlj2nAH7q8+6l8q0sMDIcmhek0WHwUbfYtz8U1IpmMkYYqqohk243Mzk4GSD0ABNRxpmngdY2JxgkySZ8e4gVIXO+5I7DLED1+bCO76zTlccxsjiHC9vwEmU8c93yC+ZH0NlGFhYg5EPX9ZL/9UrWNk4w0PTGOkkvv+dUmiu+jx/KFKaGnOXJj6BRFsLZDmPmIf0WB+r1wadEdyvZcZGOx4kP2rtp6k+unCBjeiLdmSc2mjbkzLvK8AXHe8OM/8p84/wBdOf78KKKcGdqlZHs63RCeRcw3cQAniztY5I6+IPStzo2vQX22GbbFdgAlfzX80JrDUgLKQysVZSGVlOCCO8Gq1RSMnu4CxQuvwqKqYbZOXXOaKK50vEmtKqrujbaANx7WwMZooP6vf8qyH9fqflSazrVxqspwWjtUJ5cWep8Gfzqqo7aK0EcbY27Ldy9ApqeOmjEcYsAiiiipFYTSE75/17/y4qdppCN847+cf5SU7ULOk7t+wVal6HefMooooqZWUUUUV1JFFFFJJFFFFJJGTRRRXMlywWmm4cgXOxmH15qpudMlgzhtwHjWsl1G2IPr1S3l1G4O3rQuCWUnnLK0VVVkjbWdIIODR409IjMxIFNEEZyKJ3yWoa64Udfx0/T89Dn3xD+lP0wMCecd59HbtPejr2fVT4zUQ94/tHkFDTdA9p8yikJGMk4ABJPgBS9a9w2kl/MlnGSOaGMrqccuLHU586kc9rG7TtyfPKIYy86JsMGAIOQQCPcRkUteQixmSNfZRto9wUDvr1gmmRPL27RyTon7bA7iEUUuG8KNreFSp9wkopdreFG1vCklcJKKXa3hRSSuFtm0dvmmmm0Zvm1teWngKQxJ80VkxWvXk7cblasOdGPzarryxW3faRnIzWyuNS0lGeNl1AMpIJXStVdcjwdLcqf21WSLp+p7hA0peP2ubbXUBXPlcxpVqKtubPKJ0eNOe/nnJY1rWJrmMr7ciqu3xCMev21YLpjY9mnruxSK8W4jyV07kiY9oIlYbwMeHbWoS3iKbztC7dxY4Ax25zU0ldaQhv7+iyIvxcsbdm79KxstgUUsUY9QqqoyWY9ABWi0fRzYwM0gBuZ8NM2OwkdEHkKnW1qk8wuGTEceRbj52e2THn3VacsAULrK8y+zB5o3oVW4tJM3Y0XOvix3klIBzzWB7+o6VZQcPyOpb7qvDDHDeurD1LnMsZPYJB7S/fUqXU9IsmWCe4CzFQ3KihuJ5MHv2wIxqyyufsABSy43LHE1rOCyr6O6Ejb2eVePipvm1p31fh3OZbiSHPY11ZX9up/zzQqtWMVvaXEaTQsrxOoaN1B2sD39etPGIkZFNb/IHjesN8WN82k+LW+bW7Onx+ArwdOTwFPGIFSDHyVh/i4/NorbfFy+AorvrBO9fdatd1BYVEacKCxPQDNV0+rCNWOVGProWyne82aFhWPD3WVlNJ4E/tqqubhwfR4SrXbDcivkrGp/vJMfZWO4g41uNOjYxSrz5ARAipGdv6bbhWHtvhA4ttjIRPbSs7Fmae2idyfNhg1YnhdStsekiDJGMOyV2qCwjjiZGG7mbuaW/vC/tE++vcVg6qIpLh5bZMcuNkUYA7A7r1OPOuMv8JfG7Ebbm0jGexLK36+R3gmut8Laz8d6PaXkhUzspS52ABRKvb0oLLI5lgdVLyzpQXcFcqgHSvZHSlyKOlRKG981X3tuJkIztYEPG47UdeoYU3ZXbyBkk9SeEhJkB6A9zDyPaKsHAIqru4JEdbmAfLxAjbnAlTtMbfdVyJ+itsIeNkq5jbPef2mpAxVPa3kcsayIeh6EHoysOhVh4ipyTqe+rToyc1RmZslS+lGBTIlXxpRIvjURaVVBTuBRTfMHjRS2Su3XAuJOJ+K7XWtXtrfWNQito7qWOOOOdgiqDjAA7qoZOI+JpBh9WvmH6UzVttd0+0ngvJBZ27Xc+TzmRRLzWOclzXNXUo7KcZUkHByOngaLV1NJSu5QHI8EHwutjrIQ5ozGS9TXFxcOZJ5XlkPa0jFj+00QwyTyxxRgb5GVFycDLHAya9WttcXlxb2tum+eeRYok3Ku52OAMscV0jQ9F0Hh63li4vs4INYuJS+kpIZLhmAGFIazLxjr4mg0kjnG293l1nqRpoAG07dw1PUOtZHXeE9b4dSzfUDakXRYRejSmQ5GD62VFW01hx9wjpUF5FqotrK7dCkdldMzF3XcCylAPtrUxEwiV/hOKNGzf8G9JCzDv37RpoOPzfarJzXHGHGE0+lWUzXtjbyyS2sLtbW6JDGQqkMwQ9hHfXbNHOfazd/WdC3qTRtnmMzJz7BqD1rWWOi/ClfWdpeJxeI0uIklRHe4LAMM4JEeKzHEGsfCFw7fmwuuJLqaQRpJvgkbYQ301B+yuy6TbzWum6bbzALLDbQxyqCGAZVAOCK5n8I3Dut3eoXOsw2yHTre0iEspngVgU6H5Mtv+yo6I8q0h++wt25f6nVQ2JOZ0blY/wDDbjj/AK7f/wDsX+lH4a8bnt12/P8A5B/Ss7405HG8rxxxqWd2CqMjqzHAGTT92ZXG3JyV3HxdxiryNHrN6rysC5VwCzdmTgVpdVl+FPRNPtdTvNek9GuSixCG53yesu8blMY/fT2g8P6Lw8tzJx5ZQW4ueWNN5zPclmG7dgWLPju7atU9LWWWXjvaeFDn4nE3LlQMT8lsSwBmHq+NcY9xO0d3DU9Y6hqnPOVr58dOzt6lS3c/wqWWjwa7Nr8noUyxugS53TbZDgZQxgfbRwfxXxPfa5aW99qt3cW7rMGilZdpxGzA4xVXrGt6vrN1caFpN4ZdDEm3T7VUjiiWGLBUbpFV+nma1GgafaabZ2TSWNtHqKIVlnVEaXLdvyg/rVtlHJO0RNOep07kIqsVgogXya7hrmNV0P0seNFZr01vE0UcGGuQP15DxVd8WT6yk2nQyrFJcRSfKMu5Y419pj5noB76zMPA80ba3byWpvJvRXOmSLJymM6sAylS4XI65z99dB0IiGHV7wdJY1trWJsZxkcw9v0vsq0D214ySIyW990yJOkUxHT9v++tD8QqnSTlp3MIt3WJ+u5ajAsOZT0LXFvSFyeBIv4Cy4vZ8I8f2F1a3kGjS863lWaItJaOoZTkZUyYqw1y2+E3U5rO/wBV0o5sSoidI7WNASwA3BXx211G7ub+1IEsMsAxhm5Es8JIOQRLAp6HsOQPs6pb6ja3YnieW1njlBXYkkTFkcdUZAc9OzsqgI2G7r79/wCEabR32Xg3ANx/nWuX6qPhA4ifT01exRoLaXPyK2sRCsQGyUfNb3hLRtM0+e+ntLYQhYobZm3yOWkYc1+shPZ6opZNKukciC5RYvzFuYnkdB4blcZx5irOzRLG3jgRi5BZpJD7UkjnLMceNdqIodizSScgBw1RT0WCCEsgNy79Kuww7M9lVXEGnDWtH1DTQ4R54xymJIUSJ1XdjupfSJCy4cBRncNuSfDBzTgmJqmxpiIeEMko+Ubs33rgt/whxdp7ss2k3bqGIElohuY2HiGh3fuqDFp+u28scy6bfq8Tq67rSfG5TkZBWvojmtTDtfl3eKQAYUImW6hfW6nzPb5DzqY84m642icCCHWXGNa1jjXiFbRNQsZ5FtSWiEOnvGQSAOu1Kd1LVOPdbsYNNu9NuXtrXY0Yi0x42XYuzqypXZecYI158yrtUBpJnVAx7yd5FRvjGKfC2az30mcYsUMiA/pTNiEf66QDmuFtBYdmoTDQixBOV79/Fcv4c0uBRaXvozRyorh2l5gcyeyw2sfurYrjFQdtzHqevxzxLE4vjNyllEoTmosmC6gDPjUlWOK2lDG0QgtFvuvF8bc+SqeHG9insiimd1FXtlBOTVzpTb9P1Xa25Wks51YEEMrxoAQR3UuDUuGw0rSoNRhtOejXwt5Tbk5ggI9bEOeweVRyucisJtGUmQ6m/kvpTCWcjStjdoAPABTIbmexsp7xmeR3ZbawgdztkmPTJHgO/wAlNVjWtzkyziDUmfLTw6jFEwdj1JglC70Ph1I8u+p9wN0ukxn2YbFpUHcZJGCs3v8A608qAjpU0LGhuYunMDTtPcMyfLJR7O04fvDyLW51PT7terWjXkwlH0Ip2kiZfNdwqDqvL0xpo/wnV7mNGkW0nsLa4lOBkLIbVFYZ88U/qcURgbmQrM+QlujDrzm6IVI6jxyDVYlg2zlQ+uwsJOdIuWMkvpEe53ftJyDXHRNYQbp0dO4OD9vL6pydtaSye5+MLdMLG/qWMSnqw3KDLI65xnuq6ezEEcUp1XVZI5EV1lhg0xo8HqOvIoj0u0TbJyF3B2lBIO0SMNpcA9Mn3fvppZXscmxvIFjOWazuZFa1fJ6hcneufLp5VTHOsAlMTKfZ6d10oit5CEXUdckYnosSafGx95iiBp2ey0W1iRtTuNRUyA7IJNRuZJpvJYrZgzfsNRrfU769WT0MWWnxB2SX0Tbc3IcHqOYyiMfUjU7FaQxbpApaV/bmkJkmf6UjetVhsN8ybKEwOJ5xLe/PwUXfZRF5rDhywjVEZzJqAVrmQKucKqByPrerZrqe7fSTbuYre8hgnEaBRtQ+swJUVX3DCOG5c9iQTN/pRjTN5qXxTpGlWdsofXLrTIYLdO020O0bp5PBV7vE/ZM+Ac0M3lVcQkio4+VPjnos/PKJ9W1+dTmNr9o0PiIEERI+sGvWaj2kaKEs7NZry4T247NTPJuPUtK6/JjPfucVdNoklrZzajr1yLKyhXcbWyk3XMpPRY5LgdMnswg/zVo21UVNGGPPO4DevHxh1RXSGRo2W8SNPuqsyxqSplQEdCOZjB8MUVCI1KUmSO30m3jf1o4DboxhQ+zGzY6kDoT3minemP8Al8UvQKMZcp4D8rf3/wCUn9XF/AKjeNFFZSD3TewL3an90OxOXP47Rf8AD7n+KKn07DRRU7eiq7Pd/XzKoOI/xdj+vk/l1XaT7D/qR/FRRU8m9n7qjlN8OpGpfih7j+6stL7R99FFSUfRRnDegVq+FvyOf/uW/cK0x7KKKil6RWcxD4hyrdU/INQ/UP8AdWQ4k/tnib6GnfyxRRVmP8/ZYv8AkvQi7fuF0LhP+xLT6P3Cqvjv8l0H/E4v4GoooLS+9/6+6oYn8K7uVEOwe6iiituF5av/2Q=='; +// ignore: unused_element +String _image10jpg = + '/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAsJCQcJCQcJCQkJCwkJCQkJCQsJCwsMCwsLDA0QDBEODQ4MEhkSJRodJR0ZHxwpKRYlNzU2GioyPi0pMBk7IRP/2wBDAQcICAsJCxULCxUsHRkdLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCz/wAARCAG0AScDASIAAhEBAxEB/8QAHAABAAIDAQEBAAAAAAAAAAAAAAEGAwQFAgcI/8QARhAAAQMCBAQEAgcECQIGAwAAAQACAwQRBRIhMQZBUYETYXGRIqEHFCMyQrHBM2Jy0RUkUoKSouHw8UNjFjRUc4Oyo9Li/8QAGgEBAAMBAQEAAAAAAAAAAAAAAAECAwQFBv/EACwRAQEAAgICAQMCBAcAAAAAAAABAhEDIRIxBAVRYSJBExUjcTJCUpGhsdH/2gAMAwEAAhEDEQA/APrfdO6Igd07rRxLF8KwhkMuI1Ap4pnmNkjo5nszAXs50bSB3ISixfBcSANBiNFVG18tPURSOHq1pzfJBvd06apdEDundEQO6d0RA7p3XNxbGsKwWCOevnyeM/wqeKNrpaiok/sQxM+Intz81oQ8SVMrmZ+GuJYonvaxsslJTm2YgBz42zGUDr8KCw907pqiB3TupRBHdO6Igd07opQR3TuilBHdO6lQgd07oiB3TuilBHdO6Igd07oiB3TupUIHdO6Igd0UogjRNERAIBuOR0N1x8R4a4cxQXqaCETg5mVNMPq9Ux39ps0Nn/MrsIgpDavibhMyHExLifDzZ5GsrGy+PiFDBm+zdUtDG3bbR29uo2NxpqqlrIIamllZNTzMD4pIzdrmn/eqyua1zXNcGua4Frg4AggixBBVLqcOxLhKebE8DifUYHK4y4nhDCS6m61FEOg5t5en7ILqmi1MOxGgxWkgraGZstPM0FrhuDza8ciOYW2gaLw6WJpc10jA5sfivBcAWx6jORe9tDr5L2qNx6a7D4XYrRW/rmHVPD9YTe8bKhwlhlbbmCHt/wDkQbPDkRxzEsR4sqReJ7pMP4fjeL+BRQuLHztB2dIb69L9VcNFqYbRQ4dh+HUMIAjpKWCBtueRgBPfU91toGiaIiBomilEEaJoiIGiaIpQRomiKUEaJopUIGiaIiBomiKUEaJoiIGiaIiBomilQgaJoiIGiKUQR2TsiIHZOyIgdkKJ0QU7EcNr+HKufHcAhMlHK7xMawmMfBK3d1RStGzxuQB5gakOs2G4jQYrRwV1FK2WnmF2kfeabaseOThz/wBddtUHiDCsU4dqncS8OOcKdr3TYthoc4U0jXfflEbfhsd3aXafiGlwgvyrfHLWnhTHyR92GB7fJzZ4yCupg+LUWNUFNiFI4+HKC17HW8SCZhs+KQD8TToffYrncbNL+FeIwBcijz9mSMcfyQdynOanpnHUmGI+7AVl7LWoXB9FQPB0fSUzgeoMbStlA7J2REDsnZEQOydkRA7J2REDsnZEQOydkRA7J2REDsnZEQOydkRA7J2REDsnZEQOydkRA7IiIJRRomiAiaJoglFGiIC5WKYxh+HllPLFUVdVPG90dDQwGpqZItnPdG3QM5En5qeIK+owzBsUrqcNNRDC0U+cXHjSvbEwkepC9YVhMGGxSOzyT11SWyV9ZOc09RLb8TuTRs1o0A9yHzTDOIKPhnHaxlFHUnBK+VklZQVMEkNfhkmrMzYX/E5reZF/htzbc/T6+CPFcKrqeN7Hsr6GaOJ7TdjhNGQx4I5bFYsZwXDsbo56SrjbmcwiCoDR49NJu2SJ/wB4EHz122KrHAtbW0kuL8K4ncVmEyOmpS6/2lK92uW/IEhzfKQdNCXY4LrHVvDeEF/7amjdQzg7tfTOMdj2srEqfw6f6O4k4ywUm0U00WNUY2GSpA8QNHkSB28lcNFCKlQmiaKRKKNE0QFKjRNEBSo0TRAUqNE0QSoTRNEEoo0TRAUqNE0QETRNEEqE0TRBKhNE0QFKjRNEEoo0RAROydkBE7J2QETsnTRBoYvhwxWgnoTMYRLJSyGRrA8gQTxz2ykga5bd1vp2TsgKn8W0r8NqsJ4vpGEy4RI2HFWRjWfC5TkkJHMsvcf/AMq4dl4liinimhmYHxTRvilY7Vr43gtc0joQgp+PzR0OOcGcSwvDqOpd/Q9XK0/A6nq2mSB/pcuN/RXPVfNoqKqnwnizgR5MldhQZVYG+QgeLRGRs1P9odAWn4CfPyV/oPrjaKgbXeH9cbTQtqjE4vjMwYA4tcQDa/kkS2kUZghc0Ak2sBcnkB1KISirdfxlgFG90UT5KyVpLSKNodGHA2sZXkNPa684XxhheI1DaWWKSjnkIEImcxzJHH8Gdux6XGvXkidX2syJ2TsiBSo7J2QFKjsnZBKhOydkBE7J2QFKjsnZAROydkBE7J2QSoTsnZAROydkEonZEEd07oiB3TuiIHdOmqJ0QO6d0RAWvLUBrsjSLjc9PJTVTeDE5w+874W+RPPsuQJrE3Op131WuGHl2ra6TPCzukDWCR7WsfIGtzuay+Vrnb2Fzb1PVZgbrmtmGW5NgOq5eO8TwYLDEyJjZsQqGl8ML7+GyMHL4s1je19ABvboLqcsfGbJ2s7nMY10kj2sjYLvfIQ1oHUuOip2NulxegqaWmxJpidUOdDUUsgdTzSR3Bpqgt3Yb622NjqBY1EO4l4leJppJZ6fO7LJO8xUTCNCImNFjb91h8yrbgeER0NLX0ktS6Y1ZjlHwBkcMrGluaPUu10vc8hsnHLfc6Tl1/do4fwph8UUcmJvNXM5t3QRvkjo4765fhtI/wBSQPLr1Dg3DDozC/CMODHAgmGBsUrfNkzLSAjkcyzTOMTGhx+IDK71Gmi02TSPMrwWNiiBdLLK9scUber3vIaPddE48ZPSlyvt3sBqKww1FBWPkmnw6RkUdW8H+u0kjM8E5NrZ7XbJ+8wnZwXZVZwbHsHmmbSQ4rh1RI82bFT1UT35v3W3ue11ZhrsuTPHxq8uzundEVEndO6Igd07oiB3TuiIHdO6Igd07oiB3TuiIHdO6Igd07oiB3RSiCEREBERAToidEBERBx8Vms9jOTGkn1dr/JckSG416rbxR+apm6AhvsAFodD10HZehxzWMZW9srpCWsFyS91h6D/AFVGfC7iDiava5x+pwvd47gTpSU9oGxtPIvOnclXFziHxeThYd7qt8Jta2ixOsePtKqtkZc2+7DsL+pcozx3ZE43W1lL44gxjGtaxjQ2NrQA1jRoGtA5BGVdnA3XNqaiGOKepqamKnpIP2s8xs0E7NaB8RJ5ABatPiMlQPEoeHuJa6nOraltPHBFI3fNE2U3I6K+WWOPtV3apxnjJbyBJ7C5WnhOE0uPyzzYmPGwuhqDTUdCSRDPUsa10tRUtb97KSGMB00JtrpFJiOH1jpKLJWUdc+CS9DitO+lqi0tcC+MO+FwHPK4+llVcU4grsM4U+q0UjoKuqxevo55YyRLFCAKhwY4aguzNF+gPVZ52XDpEnb6dUcP8H1zH0UuGYUS1pGSCOGKeLzY6C0jSOoIXrCxiGHSuwqtmlq4Qwy4XXTEOnlgbYOp6p3OWPQh34mm+7CT+cqLFKmgZWCCOEVFQ6mfHWlrvrtG+CTxM1JM1wLS7Z+9xovvOAY9VYtwjTY1UZPrlNDPLO4CwfJRPcHuAFh8bQb/AMS4vbb0tyLUkrqSIXkmY0EBzbm7iDqCGt1+Swf0xh5Oj5T/APGbfMrLLlwwusspG2HDy5zeONsdJFpx4hQSWAnaCds4LfmdFttc1wu1wcOrSCPcKcc8cu8btXLDLC6ymkoiK6giIgIiICIiAiIgIiICIiAiIgIiICIiAnRE6ICIiCs4mCKqf+In3C0bi110sYaRUvt+NjHD2y/ouO4n0t0XpcfeMY32iRxuHXsQdD6eS04aeCmhZS00YjjzyPaxtyM0ry9xGY8ySsz3dSOnbqVgDjcOvbKRqtNIe8GoIMYrDiVaxktDQTy02D00gBgMkLi2aukadC4uBDL7Bt+em3N9IvDEeIRYbSMxHE6mSZtOw4bFE6N8pOXLE6aRmbXmBbzsuPJPUQ8E18dMXCdmGVUN23DxaZzZiLc7ZvdfG2vfG9r2Ocx7CHNcwlrmkaggjVcXN+mrYTb9LNk4d4tw+QD7eKGcseHB0Nbh9ZEeV/tGSNOxB9wdfmnEGAVTZ8Rw+smH2ssM0VaWZYjUZXCGaVrBoJQS19hYOGmgstf6KqisZjOLAF5pZMNLqkm5aZmzs8IuPXV9u6+gYnU04xljZAySN+ERMnjeA5pBqZsocDptdRxz/ZOX4fIIOCuJ5qgRPp4IYgbyVU1RD4DGDd5yOLyPRq+nUc9PhuEUuBYeM9HFA+CaaZnx1RlzPldkNwA4k6dDbktWslw99R4GGtkioxla4eI9zHuuBdjXE2aOQvyuskXhsyuJHM985/kvJ+V8jd8OG9fd9D8L4WOM/ic83f2n/v5e/Gdd13EnYk79F7bUkXutGaeMPlA6Ei3UPv8AksRqB9qRzcCPS68PLjfR45Y2enXFULgX6XKzxV8kRvFI5h/dcRf1suAZ9zfclehUWDtRfK4fkomOUu4jPHjymrFwg4gmbYShkg1uT8DtPMafJdODGsNmsHSGJ1v+oPh/xN0Xzo1VnOINgdtdgjK7Jd2hPIG9gF18fyufDre/7vN5vp3xuTvGav4fSnYthTTb60wn91r3fMBbEVRTzi8MjH+QOo9QdV8sNbW1kzIaaF8spN2w07C5+vOw5eqseGYDxMXMlnqI6JtwRZ/jVA/us+AH+8V28fyebO78dx5XyPg8XFO89X8rsihoIa0FxcQAC42uSBubKV6ceOIiICIiAiIgIiICIiAiIgIiICdETogIiIOFjTftoXdYfycVwJDa6suNMu2nd5vaT7FViY3Nu3svQ4bvCMsvbXkJ1WDMBe+o2Iv+qzOGYkFQIC/QDfotqq0aWqbRyz00xAgqHmSFzgPDbI8Wcx19LO39b9VxqvgzA6id0sUtTSNe4udDEGPjF9T4efUDuVbf6Elq2WeA1uwLjYj2CwjhXEmaR4o2KPkGtlcR2zAKt8MprI7nprYdHhHDtE+GlBjic4STzSHNUVDwLC50uRrYAAC56knFTNqsR/pDEpg5prA2OBpv8MMTcjAL8h1569V1Kfhaka8S11RLWPaQWtcCyLT+025cfddSSIDQABuwAAtbyAUXx1rEku91RvrH1eTw3jJI3Sx7ai6k1xI0d1B15HVWWswujrG5Zomu6EjVp6tI1XAm4Zyk+DUzsH4Q60gH+IX+a8Tl+n57/p3p9HwfUuOz+ruVqOqdyT5FYzVHbyWc8P4jyq4/70J/RyyRcN1zyPErY2jnkhJPbM9cn8v5v9P/AE759R+Nr/F/xWp9YNrG9vy9FMRqaiQR00c08m2SnjfI70IYCrPQcM4W0tMzZqhw38aQhhP8Edh+aulDDDTRNihiiijA0ZE1rG+zbLSfTsp3nXNyfVuOdceO1Co+E+JasNMkUdHGdc1W8GS3lFFc+5CsVHwPhsWV1bVVNU4alrSKeG/SzLv/AM6tQJXsFb4/F48P2283l+pc/J6uv7NejoMPoIvBo6aKCPmIm2Lj1c7cn1K2bLyXAWvzcAPUr0t5JOo4Lbld0REUoEREBERAREQEREBERAREQEREBOiJ0QEREGhirM1G91v2b2v7H4f1VQk+8eet1da5uajrB/2Xn2F1SjfMevJd3xr+lnl7eWszOGi6NNCxgDiPNasTQLdStoyZWgeS3qre8RoFm9FHiHnfXRaAlJ566le/F0sqeKW2H+a8PDXDf2Wr4x116qRLvqmkjhY+gK85Wnf/AEUGS68lytpD1lZ0BXtkbb6f75rFmv625rJERz8t0HQhY0am3Vb8bgLW/wCFzY3iwAWw2VjNXEADnzWGU2nbqtOgXiaohp2OkleGtaDe/wCS5M2LNb9lAwySHmCAB/EVFPE+aRs9U/xJG6sB+5Hr+BvXzWU4v3qfJ06b6xUPbUzNMcYv4ER+9rvJIOvQfrturDA8EFt9RY+6zLHL2tBERVSIiICIiAiKvf8AirDXYzBhEYL/ABJHU/1hrgW/WAHOyNYBcjQgm/y1QWFERAREQEREBERAToidEBERBiqRmp6po1JglA9SwqjOI6eavxAIIOxuD6FUOeN8MssTt45HMPq02XZ8a+4zzSxwGnovc7jkBHLRahfb3WVsjXtLTqNj/ouvSg2T9eanxP8Ad1rOimY4lt3s5Fu9vMLwJm3sdHcwdD800lul/O6eJf2Wt4jdrhTnYOYTRts+J1ttopD9VpOqqaP70jG23zuaPzK1ZsbwyK9p2OP/AG8zyfbT5qEbjsF5uvbZGtAJLQPXW+2yqc3Eo2ggc7Qi8rsg/wALbn5rlz4tic9wZvDadMsIyXHm773zUbiPJfHYiGgtj+I7arH41RUGz3kNP4Wk27lVTDcR0bDO+ztAx7j98dCeqssE7GDM7pe56fkp6/Y3t1qaFjA0kWW8J8rmsY3PI82YxupJ/l1VWqeIaSna5sbvFk5MiNxf95+35rf4ZxGWrmkfKQHk2AaLNDbbC+vzWWX3N96XKlhMUfxEOkec0hG1+QHkFsLww/lde159u726IIilQIREQcfGsfocDdQNqoqiT64Z8hgEZyiHIXE53DX4hYDz79KmqKerp6eqp354aiJk0TrEZmPGYGx1VG+koAU3D8zXDxI6upa1t9XMdDc27hvutCTiw0WE4dhGEvu+Cjhhqq6xAEmUGRtM1wB3uMxHoOYratJuO/xPxE+FwwbCQ+bE6p3gONPZz4y4axx62zkXufwi50t8M4Bw/Q8OxOxPFp6cYhJGWOlkeGwUjHamGAvsST+J1rnYWGho+H4pVYfO6ppTGKl0ckTZZYmyyMDyC5zDJfU216rw92JYzWOGIYtDSRNsXVlWZKmXK78NNBGLX9coHmol2tcdL9JxxgjKuOnbHUPge9kYqbNaHPe4NGSN3xkajofLra187wiD6NsJqaWVs02IV4eMlfXtfKIX30cxrg2NtuoZfz0X0T2VlLBFKKUIREQEREBOiJ0QEREBVfH6Tw521LR8E+jugkaNfca+6tC16yljrKeWCTQPHwu3LHjUOHotOPPwy2jKbj5/JsFiD3NOi2auGanllgmblkjdYjkehb5HcLTcvW6s2wb0U4uLlZ5IYKtjmPGpaQHNOV1vJw1uuSHm/wCRWxFUEEaqlx+yZVYxOHFsNn8J9TUmN93QyZ3gPb06XHNc109S++aeZ1980jz+q+g1MNNidMaecEj8BB1Y7kWE7FUevw6pw+YxTD4SSYpAPgkb5efULPtSxo777+eqlTlUgKFUAKbKQFNtkCy95nmwLnEdCTb22UAL0Ag9N5K2cJuPj2H9rmqq1uytvCkf29/3reyipnt9IjP3fMLKsMenh+v6LMuDL26xERVBcvG8bocCo31VUcz3XZTQNcBJUS2vlbfYD8R5fI4sd4hw7AYqeSqbO+SpE/1aOJv33RMDiHuOwNwL+a+Q1E/EHFuLOEcb6qsksGxRXFPSQE/CHuPwsjHU6nzJUWrSb7rLjeOVeMSx1eIyx2hzNpoY3ZIIA4gkNBOpNhck3Nh0sMeGYJxRjTm/0fh7xATrV1eanpQL7h7xmd/daV9R4f4QwbBKaHxKenqsRID6ismia95kOtoQ++Vg2aB3uSrKo1903L7PnlH9GrXNa7Fsaq5HaF0OGtbTQg9PEeHSH10XZj+j/gljWtdh80jhu+Wtri93mbSgfJWpFZXdU6T6OuEnyNewYhCy4L4YqyQxSAG+V3i5n2POzgrgAGgACwAAAGwA0spRECIiAiIgIiICdETogIiICIiDl4vhbMQiD47Nqo2kRuOge3fw3Hp06KjSxyRvfHIxzXsJa5rhYtI3BX01cXGsHbXsM8AAq2N8gJmj8Dj16Ht6dXBzeP6cvTPLHfcUU766JsvcjHNLmuaWuaS1zXCxa4GxBB5rGV6LJmimcw7rfeKOvgdT1TGvYRz3aeTgd7hcjXkskcpZ1VbjtO3FxPBanDyZG3lpCTlmA1Z5SAfn+S5llfYKsEZH2c06EEXBB3BBXOruHIqhrp8LytebudTOdaN3/tE7HyOnosrNe1bj9lTsVIGyzSQyxPfFLG+OVhs9kjS17fUFeQ09EU3HkAr21pXoNWRoVKbGMV14VhsxsnVxI9L2VPAJs1oJc4hrQOZJsAvo3D9J4EELXDUNF+l91W3rdXxm6sbRbw+lx+SzLAD9pG3+In0AWdcWTpgudi+L4fgtI+rrX2b92KNljLNJb7kbfzOw5pjGL4fglDNXVryI2OEcbG2zzSuBLY2A8zY+xPJfFcYxzEMbrJK2pElnfZ0tOy5yMLg1kULN7uNhe2p+VLdLyb9upM7HeO8eZFGPAha0F5H2kOH0ea17mwL3cup6Nb8P1jCsKw3B6OKioIRFCzVx0MkzzvJM/cuPMn8hYc3hHAjgWExRztaMQqyKrEXNsbSuADYgRyYLNGvU/iVhSQtERFKoiIgIiICIiAiIgIiICdETogIiICIiAiIg4WOYKKxrqmlaBVNb8bBoJ2jl/F09vSkvaQXAggglpBBBBGhBB5r6mq/juCCqD6ukb/Wmi8sY0E4HMfv9Oq6+Dn1+nL0zyx33FIPNebrI4EEggggkG+9xpY3WI26LvZPTXkLdp6t7CLE3HmudoF6Y6x3SzZt33/0diMbY62FklhZj9WyM/he3Vcmp4YkOZ+HzsmbuIprMlHkHD4T7BeWSvbbW63oK+RhBWVw+xZL7VmbD8SpnFs1HUsI0v4T3N/xMBHzSGjrpiBFSVT3dGwvHzcAPmr7Bi0RFnaH1W03EqXckX9N1lZSYRX8G4aqhIyprA1pH7OIHMGX3LjsT0VzibHTRC/4QuecUhH3CsH1qeqe2JmrpHBrW+Z09uqrcbffpeanp3KMmXxZzsT4cfo3c+/5Lc1WOGNsMUcTdmNDfW257ql8d8THDacYVRvtW1kJM72nWnpnfDYfvP1A6C53IK48rutpFQ40xqPGMUIjdmocN8SClI1a99x4sw5fEQAPJo669LgHhuStnh4ir43CmgeX4VG8ft5xdv1nX8LNQzqbnkL8rhDhl/EVX9Yq2uGDUklp92irmbr9WYR+EaeIenw7n4ftDGMjY1kbWsYxoYxjAGta1osGtA0sOSrpNr0iIiBERAREQEREBERAREQEREHl72Rte97mtYxrnvc8hrWtaLlzidLDmvMFRTVMbJqeaKaJ33ZIXtew+jmkhc3iDCpsZwuqoIap1NLJkex4v4b3RnMI5gNch5++trH53wni8nD2NVOFYrmpo53CmqY5nBrKarBBilcTpkcNMw0IIOw0hMnT60idkUoEREBERAREQVzHsD+sh9ZRt/rAu6eJv/WA/E3978/XekPBBPluvrSrOPYCJxLXUbQJgC+eIWAlA1L28s3Xr679nBza/Tkzyx/eKOboDa26l4sfdeV3MmZrl7DlgBsvYOiJZw9ZGyOWsCVlag3InnqrPgNLmz1jx8Lc0cF+btnu/Qd1XKCmfVTwwNJBkPxO/sMGrndleXSUOHUb5ZJI4KOjhu97zZscbBuf9/muT5Oep4xfGba+M4vR4JQT19TchlmQxAgOnmcDlibfrz6AE8l8hw3DsS40x2oNRM5gkP1zE6iO/2MF8jIor6Am2VnQNJ1trj4p4jlx6tfNaRlDTB8dDAfvZDa8jmj8b9NOWg5XP1LhDAhgeD08UrAK6qIq8Qdz8Z4For9GCzR6E81w+mztUdHR4fS01FRwshpaaNsUMbBo1o+ZJ3J5k35rYRFCBERAREQEREBERAREQF8vxl/0iR4vin1amx2Sl+tSmkfRyNMBpy67A1rZA3bqLr6gq3inF+EYbUVNIGTVNTTFrZRDkEbHlubI57je40vZp6biwhM3+ypQ4h9KkABFDjL29J6fD5fe8mZZHcZca0j2srsM8AuDjGa+hlhEmW2bI5sgabXF7HmsOIfSRWOeYaMU1PITZrIWuq6gHptl//Gq9VYjjVdUF2JivaWsa9hxBkkTjG+5BijkAs02voAFF/C879rDLx7xNY5YsNbpyp5T/APaUqq4ziWK45PDUYjPG58EboozHBFGRG45iw5W3Ivtcnc9dd3D8LxnHZMmE0bpoQcslbOTDQxnmPFIJcR0a0q20H0bR3a/FsUkl5ugw+MU8fp4shdIe2VO6XUUimx7HI3Q01LX4q6RrAyKGCepe4RtGzYoyTYei238TY/CC6oxXFYbb+KayMA9LyABfYcNwjB8IiMOG0UFMw/fMbftJPOSR13k+pKyVmH4fX/U/rlPHOKOqjraYSXIjqI2uayQC9ri5tf8ATR4o8nyePGvpDaxktNT8SzQuaHMkdRSSMc3qPFjLrdlL+MuNIR/Wp66lA0JqsLbCP8boAPmvsKEX0OoO908UeX4fGm8XcQu+0bjE5b1a2F0f+WMtQcacTtkjccWc5jXh/wAMFK+N4ablkmRuYA7cvUcvpdbwtwtXvMlRhdMJjvNTB1NMT1MlOWuPcqv1H0aYC9znUtbiFPmdctP1eZtidbOkjz+7io8b90+UalP9JUbW/wBdw4PIF3PopgN/+1Pr/nK6tN9IvB04+1nqqV3MVNNI7/NT5wuNUfRVSyPvT45VRRgkhstLBM7X95rmfktOp+imtYwuosbillF/grKUxMPQZ4XuI/wlX0r0vlPxRwnVi8ONYf1tNOyB3+GbKfktGrx2hrZPAo62kljF7NgqInvkLdS7K117D08/T4xjGCY5gUoixSlfDc/ZzNIlp5R1ZK3TsbHyXLE80b43wF4nbJGYSy+fxA4Zcttb3WmF8bvSLNvr1dStnJljsJTq4bB/+q4z2PiNpGuB6OFl3GSuc1riAHEAkedtbKTkeC1wDgeTgCPZelMtTtjYr+a2inMuyaCheb+FY6/cc4fK9lLcGpXnR0412zj/APVW8ojxccOstmmZLPKIomPkkJ0awXI8zyAXeg4foAc0jZHjo+R2U+oZZd2kpqenaGwxxxt0vkaBf2VMuWSdExecGw1tDEXSEPqZQBIRq2No1EbL/M8+y0uMcCrMdwnwKGdzKmml+tRwF2WGrLWkeFIdgebCdAd97jvsFgsi8/K3K7rWdPiHB2FTYlxNRU1RA9jMKkdXV8UzC18b6dwEcUjXcy/Lp0aV90WuyGmZPJUNiibPKyOKWUMaJHsYSWte+1yBc29VsLOrb2IiKAREQEREBERAREQEUoghVit4H4ZxDEKrEattZIalwkmpm1UkdK9+UNLi2KztdyM9vLVWdEFAxXG8J4ZdV4VgOG0dHPTtYKqo8BjGR54xIMoAu42IN3G3keWHAOEpsWeMY4jEskc7vHgoZ3EumvYtmrTvr+Fm1t/7Itldw1gWI4jS4pV0xkqacNsPEeIZSw3jdPEDlcW/huPe2nZUaTvrp4jjjiYyONjWRsaGMYxoaxjQLBrWt0AXtE6KUCIiAiIgIiICIvEkscTS95AAufM25BNbFPx97Kqapie1skTgYSx4DmOa34bFp06qp0vD+D01S2qjpmiVpJZdzyyN3VjHEtB6aKyVLs7nF29zc+Z1Wsbar08cJJIy2833F9uR/IKWk3sLXUEc9/JBp7q42I7ki563XRhka2wAGnPRcwE2tb8QN+fRbEbrFVs2OsyQk22HJb0Tul3EDYC/5KpYtjsWDUhlbB9Yq5ARTQZrMuNDJM4ahg8tSdNNS35/LxDxji0pfNi9RSwtc37OheaSBgcdA1sBBPclc+dk6S+6+IRuCOlwR+a9Zgea+JU9fxBRyNli4pq/EAuWzVEs8F7/AHJGzZ2HuBdfS+GseGN4d9YkDG1dNNJR1rIwRGJowCJIwSTkeCHDpqOVzjNX0naxh11sNNwCtAS7HYrbgdmjv+8VXPHXaZWVERZrCIiAiIgIiICIiAiIgIiICIiAnRE6ICIiAiIgIi1aqqEIyssZCOzB1Pn0UyW3UHqoqWQggWdJbbkPVcmWZ8pLnuJJBA/kFjkkc4kkk31JO5KxF3T5Lsw45ipbtx53fEQfRYCfbp6rPWtMcjv7L/iaeWu4WnmXUoy6/wA0/L+SxZgpD0Gw1zdL/wAl7zAaAjsea1S4enUp4hGl9k0Kxj1b4lTMxxPhhxMhFszYowYhlB5H7x6XVSEpBJ0ttb/hdvimgrIpZa2Fz5KSYl8gy3dTSdDb8B5Hlt61bxXEXvrqRrtbouLVmV2X8OkZgQeR6N09L2Vp+j2tlixjE6UH7KroPHeCd5qeVoDvZ7gqIZzb9VeuAcNnE1VjM4LY3QupKQHTxczw6SQeQyho63PRJN5dE9dvqQlubb+i61FrTtP9pzz87KuNk6anSw3120srPAzwoYozu1oBt13KnnmpFsPbKiIuRoIiICIiAiIgIiICIiAiIgIiICdETogIiICIiDFPMIYy82J2YOrlwpJC9xcTckk3O5K2q+fPI4A/BHdo8z+IrQc4cvO67OLDU2pb2OJ25LHfXeyhziQNdOi8B299VshE0UUzCx+o5EbtPUFcSppqmnu7KZIh/wBRgvYX/E0ahdskEact1ALfO9tOhUy2Is2rYmB2IPRSJfNdmejo5sznwtzG3xMuxx7t/VaEmEs18KeQX2EgDwO7bFaeUV1Y1jLodu6CTqOXdH4dXi+XwngdHlpPZw/VYTS4i3eB5HVpa72sVIzZ9D5ggg8wdCCuFV8MYLVyOlDZqZzjd7aRzWxk9cj2kDsur4Nd/wCnnJP7hWRlLiTiLQPH8TmNHzKXCX2hzKHhTh6meJJIpKog3aKt4cwH/wBtga09wVaGSwxNDWlrWRtaGgZWta0aAADS3Ra8GE1slvFmZGDyjBkd6a2H5qxYdhFJTubLkMkgIyvmOcj+EbDsFnfDD0tq1lweimleyrnjdHEw5oGSCz5DykLTsBy69tbGFgaSsgK4OTK53daSae0QaoslhERAREQEREBERAREQEREBERAREQEWhPW6kRmzRpfm4/yWsagk7+5Ws4rUbdhYqiTwoZH31tZv8R0C5zak9TdeaiodI1jSdAc2/PYK04ruItacjgee3Vazjr5brJI4g7+y13Ekm/QrsVC65JPVN9bef8AwvOpv5/JegLG/ZSJDfP0U5Trsb69D81II29779rLK1hOo/17KtGs5tuR/JeS09luiG+4N7r2KXy08/8ARRuDm5T3XpsD36W/VdAUzR2WVrA3QAXUXIc4UjjbRZ46LW572W7lN/L5LK1ovc2toVXyoxxU8bbG1/VbjAQvADf07r2CsrdjO0gBeg4LACTryWQErOxZmYdSO69rCxwzDXnb3WZZ5TVTBERQkREQEREBERAREQEREBERAWrXS+FAQD8UhyDrbc/781tLkYvIGmnZ0a9/uQP0V+OeWUiL6aDpPlssbpSCsDpCb66eoWF0mup69136UbfjloWYOvGw31IuO5XJfKQPW/suhe0cWuzGf/UKdDG92p6m68A3truoc4dOdl4vz6WUoe7635devuvd76d+yw5gpueVtr22CDMDa2x7/wAlnZLpbyWoHDW/Qeeq95wLjr0UWDoNkHlfQG2oXsPuNPc6LntPmb8rc1kbIRoNlW4jfa4bd160GvXVaYmykZvdauIYuyho6yqETpjTxPkZC0keM8D4WEjYdT0BVbjpLaxLFcJwelNZiVQIYbuZGAC+aeQC/hwxt1J68hzIVNb9KOHGZwOBVoprkCRtXCZS3qYzGG/5189xLFMTxmskq8RmMkpBaxo+GKCLcRws5NHvzNybrPTNjia4OAaLNDi4XBAGYNF9NSue5bH2TBOKeH8fLo6GeRlUxpe6lq2CKpyDdzACWOA52cbdF3QbL4TculZUxltPPCC+nmpR4M7J2CzS1zLAWOp9DqvqnCuOzY1hYmqQ369SymkrSywbJIGh7ZmtGwe0g2639Al2lZA4dVOe11gzgg9SUDtbX2VvFDYa6x9Fuhc5pG99V0GG7Wn90fkss4tilERZrCIiAiIgIiICIiAiIgIiIC4OOEiaA8jDYeuYrvLi49GfCppQNGvdG70cLj8lrw3WcVy9K89/y6LA55UyGxKwFwH816LPb0X35rrF14ot/wBm38guG4g3PP8ARdOnkz08eurA5ju3+wookuHMne68l3ztooced1iJI16+ynQyZrbnU6KRJr66HTS3kFhzfzXoHS56nmmhlzX2JsvV7bG+nS2/qsF+h0K9X6Hp/wAINlruZPXfReg4CxBsPLmtbNaxOlvkodILadxrqq6GeSUEAXNh8lxcaDpKCVwJ+yfHK8ADVjbtcbeV7rfLrEnbXqsTw14c1wDmOBa4HZzSLEEeaXHcsS+SVkeWeTLoA4ka6WubbKIKh7AQ6R4GVzQG7C46HSx5ruY/gdbROM9PE+ahAJzsGaSEE/dmA1sOTv1VZMjDzFh5/muPws6ptvvrZS2xcQMuW1wRa1v5+6vn0ZSSOPEziD4ZfhoB1/aBkwNuW1l83poKuslZBRwTVMzto6djpHb2vZu3qV9q4SweTAsHhpqjKKyeaSsqw1we1ksga0Rhw0OUAA20vf1M4TtKxgkW0/1XoEa6AbXWIPGo01Uh1tOu5WujbOHC25sNh1XUj+4z+Fv5LihwuLm+vXmu20Wa0dGgfJYcvWk4pREWC4iIgIiICIiAiIgIiICIiAtetpxVU08GmZ7fgJ5PGoWwimXV2PnkzXNc4OBBaS1wOliNLFajneStOP4Y+7q2nYXB3/mWMGoI/wCoAOXX39Km/wBb36L1OPKZ47jGzSC7ndbNDPlkdE46Sfd/jHLutE36L3TwVNVK2KlikmmuLNhF8vm47D1JCvrXtDryHZYXE6LdqaKvpIqc1bGB0jdTG7MwP5tJsNea0nW6+SrLL3BI+HuDv5qMxO683PPZRfpa/nv7KRkuLDca66Je1tTusZcT5KSfPppdBkLrG17heS6915NuZ9LKNbHa6CSfcb/7K8F1vO/md+ywVtdRYfTyVNXLkjabNaBeSV9rhkbeZPsNyqbNxjjM8xbRU9JTw3+ATMM8uXq97iG+wCplnMfaV6BPtr22WE4bhs8gfLQ0b3kglz6eFzj6ktVeouIcWBaa6kpZ4/xGhf4dQwW3MTyWkehCtWHVuH19O2popfEic4sddpa+N7d2SMOoI/3cFRjnjl6NOhRwwUzCyGOKJnNsTGRjTTZgAW7mvY/n+i02E3FuZ6rM22/MJRsh1rnQHz10K9B2luQ5rXDhqeo/3ZR4gHO1/wAlGhuwfHNDGNS57b6ch8R+S7y5GERF5lqnD4dYor89fid+Q9111x813lqNMYIiLFYREQEREBERARSiCEREBERARE6IFlzanA8HqnF8lMGvJu50LnxZj5hhA+S6SKZlcfQ5DOHMAabmkz+Ussz2/wCEusunFBT07BHBDHFGNmxMaxvs0LIim5ZX3UaY5oYqiN8UrA+N4sQfkR5qoYnhc9CS9odJTE6SW1Z5SW/NXNQWhwIIBBBBBFwR0IKvx8l470izb5ydBZeb6+atGIcPRvzS0FmPOroHEiN38B5em3oqzNDNBI6OaN8cg3a8WNuo6hehhyY5zplZYgO1S++9vReLnkmY7HY22Vx7vr+qXOo/0XgGy9B9nAnkQUFI4rmmmq4mlw8GBlqdodcfeIe4jqSPkqwHhh8ybm67uP0WJ00rnPikkpQ572TxNc5lnG/2ltndbquPcCbhcNl3uptbfjEEObo5uzho73CtXAc05q8YhAJgdTwTPd+FszXlje5BP+HyVOp4KmqmjpqaN8s8hyxxs3J5kk6ADckr6pgOEx4PQtgJa+plcJqyVuz5bWDWk/hbs3ueavxzeWxYGnRpvbQ5ey9525ba3PNaofYDUb9/JPFGx2F7ELp0htGT+a908MtXPHBGbF1y924Ywbu/Qea0vEJLWtDi5xDWNaCXOcdmgK14VQCjgu8D6zNZ0x3y22jB6BZcuf8ADx/KcZut6KKOGOOKNtmRtDWjyC9oi85sIiICIiAiIgIiICIiAiIgIiICdETogIiIClQiAiIgLDUUtLVMMdRCyRnIOGo82ncFZkSddwVet4ZeMz6GXMN/BnIzD+GT+fuq/PT1NK7JUwyROJ08QEA+jvunsV9IXl8cUjXMkY17Du14DmnsdF04fJyx6y7UuH2fNLkEW0I/45qL+XkrpU8N4TNd0Qkp36/sXXZf+B9x7WXHn4YxKPMYJYJ28gSYnkejrt/zLqx58Mv30pcbHEvYWHNaM+FYNU5jPh9I9xPxO8MMeed8zLOXXlwzF4b56GpA6sZ4g947rUcJGkh0cjT0exzT/mC1msvyhho6HDaAOFHTQwZgGvdGz43Deznu+I+63A8nY7anXda9z59FIzG9gfZT4yDZzt8/5r3GXyvayNhe933Wt/XlZeYKOWYjM7KLj7upsfPZWCipYoW5WNAJ3O5d5kqmWcxJNs2F4cKY+PKQ+oI0NvhiB3DL/MrttNlqREDnvtdbAOm64M75XdazpsA3spWKN17josiwvSwiIgIiIJUIiAiIglFCICIiAiIgJ0ROiAiIgKVClBCIiAiIgKVClBCIiAuPxDEZMPLhvFMx5Pk67P1C7C162H6xS1UNtZInhv8AEBdvzsrYXWUqK+dZR015rPBECRe+vzHksb7BxuF7jfYj9OS9Os3YgaxmoAAte1yt6ORoAtrp81x45nG2vXTTSy2BPYEk8vLc6rO47NuyyUW9tVm8UAH05LiipDQNdOayQy1NY/waYFztM8h/ZxA83kfIb/mqXCa3TbuUr88j7bMbr/e2W4sNNTspomxtJcfvPe7d7zuT+izLjyu700giIqpEREBERAREQSiIghERAREQE6InRAREQERSghERAREQERSghERAREQUjG6E0tXI4NIhncZIXfhu7VzAeo/Jcn7p3X0mWGGeN0U0bJI3Wu14BaexXNdw7gbnZjTvHkJ5w32zLsw+RJNZKXHvpTBKGg3Ony0WWAVdUSylglmJ0vG27PUvPwj3V0iwXBYSCyhgJ3vIDKfeQlb7WhoDWgBoFgAAAB5AKcvkz/LEeP3Vqj4eqJLPxCbK3Q+BTuuT5Pk/kO6sUMEFPG2KCNscbNGtYAAPP1WRFy58mWfteSQREVEiIiAiIgIiICIiAilEEIiICIiAnREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREH//2Q=='; +// ignore: unused_element +String _image3png = + 'iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAO2tSURBVHhe7P0LdFRVmj6Ma1mWZVnGGEIIoVKp3Cp1v6VyISLSDGP7cxybYWiGtmmaphERERERQgiBkPv9fr/fCFcRaZqmacdhaIdxbMefn3///l0ul8vlcrlcLpfDJdeqet//81YoO8SA2N3zze/7Zt613ux99t6nUnXOc579vPvss89tMMVfwMVmKw+52Gz5P9fFvs/2rbjYbOXfx8VmK/8/ycVmK//PdrHZyv9SPmvh//iUzVb+57rYbOX/b3Wx2cr/s3zWwv+W/sxtPGv5jfwfbiPFE99zn//x/3S/fmNGeit1IZteNtPFbpQXu9l2KD9zW+xGdTcrC7nYdenfAqA/hV/bnl4X9OcA3s1TABZTrEb+59O2Z7jYzLKQi82Wn217uovNlg9ti00vm+5is5WLi81WHnKx2crFxWYrn83FbqUs5GKz5UPbYtPLguUzC0IuNlv5dBe7WXko/T4uNlv5f7bfVgMwl6tYYYr6NusKmP8e9c9OAXimi81Wfisu9s32T/D5K2b/H//jt+b/aTbzw7/PP/tP/WJiywGaVVPA+cbqb2fFKZQ9McXSt61HKiws+XVIr7H3jWxm3WxtQ2U3Sm/7e/yfH83+f6aXzVb/p9rMz/pT/89sbf/UzxL7k/cNNZZ0en56Ot2mtxOb2WbmvtPT2fJi37UtFtq+WduZacimlwd9DYCzYgo437TdDkD//Z2seAjlP0P9X99HihwFK+Yls2J+PCs05uuYU2x6GsoH7e/vZoX3/m8umFD99HbBVCSL/C+5YIKlf7Tp7W7kIQvlZ0unu9j0vNj0+ul1NysL2fTymel0F5ueF5utPpTeyEMWys+ayp+b+XQLbc9WP70ulM6s/z4uNlv5zVxstvJv+aYpAE13MUXGfFY8BxA/C1Dn3MbK2ttIaYxh5UMRpPgl5Eg3ytun7fsTbItMCG2H/G/vIMWPFBTU2M9fXy/2zfaPUJePz/glUpEza+EPRJLimbDrtPxfwsVmKxcXm638L+Fis5WLi81W/uf4rIW34mKzlf8f5asAlmsS4lv+NMCzG3VPIcW2WBCc21D2MoBcCkC/gu3iu0mxUk2KFpQPYrtqqn3QC5CXzxDfBy/C9o/uZcW9JlLEL2BIFgH1zcH5JOr3Y7+d+J9PI/8T7F+Li2q2tjN9JfaT/7Fj2nf6b+5/MZv5YTfbnvnP/5L7fstElwpIZ4xI3LYF2zm3k2LvH8tvWzyHAVIBLikRJCqr4G3YFtB3In36zm/r2xWQKs2oH0L9XwP4f4s2d9inZMu1i2W6z2aKjWi3/C7ID0ieCnyelMnFtRaf6Zj3zWdMt2DZctT/A9q98O3/Ix6yUH5mudjM7ek2W/vvstA+33ffmfvN3PdmZdfVXbdxE5vebrb2M8tmtp1eL/mZ29PtZm3Fvqv99PQ2OeF58Byc/BenTryYIhvb2+GSnyq6TaGykmIAcuHfUN50Oys7taSsu5tUtUpWDmpZuV95fft0SAMJFrPxufhs5d/fTkrIEGUxPkPAvPmPnz99P7FgPlZPikfwmVvRLgfMXI59QuPhT+DCeAGfBeYO9gDSXqwD+Tq0kThg/bW211zsZvlQGsqLzSyfbXtmmdhs+Rtti82sD6Uzy8Sml4vdbHtm/puCmS42W/4v7WKz5WcrE7tR3U23BXQCjKIEVuZCD29Afv8cVh6Yx8pQG/E1WlY0Apgd8AP3saoYgG4JZ1XnfFaJpu6bBiDp7p8E6BHQKYWN88MAPmw/A+/F9la4XEjTP38egksF2FsY+e+hsTPnMi4qVoou36RhRde09rvB1I/cK2BmRS5c9pFy0fHHkZf/UYZ8qP0MF5utPORis5WLi81WHnKx2crFxWYrD7nYbOW34mKzlYuLBdOZFf8nu9hs5bfke3Hyd4NRS82srIRXmEj5Arr46W023MOKlntZCTCqcuawZm8sa56/l1WV80hVBuD1AEQ/hf8YLkzagDIEkMp6FStPxrH6qA7gD2flHoC6EHUvIn0wCvoYbLvmPoD2blZ4ANwf47vsBDPXoE0lPkd6jhb4y/Dp30dchg93waXNk/C77ax4CRfDMPLY/1vt/5v7dfatglnsVtrcqv05n/Un7Su3q38G3bwzgpUvqP7YjYcsF9KiFawN8Khy5rGm1MCa2khWF0WRqukuABXg/QlkxSMAYhO8Gw6mVB6NINUrCaQ+E0eqE/eSshMXAVhXBW2t3HQH2Br+T9i3E9svYn8zAkb5f7ggENBN6Xv0DGDfqfKZ9ncoF+kkMuNpaO1l+LxrVd/YNtQV32D//04mByB0EGYejOnlM9vdrExstu2QzVb3XR6ymWXTt2e6WDCVwPDvABwJouTGhaTCslIvckTmZGQg3XYfK/feT6pCLavqYkjTGMearihW593Bqr0LAOy5pMqBBFkNcJaqWDWgZtURAPeVSFYdBUMPx7KqHnW94aSqup1VvWDok9j+PYDcpCRl4z2slABy+qiEjEH34n+3Ia1Deq1cLJiHJAmmPwOQhZXB6MoHpu1/zW+TYFYYfvqw4jQXm61cXGy2/M3KQi42W/5mZSEXmy1/s7KQi82aD2W+j4vNlr8VF5stP9v2TBebLT/b9nVlP8PJFhCHXAJCGWteCZexZCnzAHjF0Mr7IlhdH83qukTWNMSSpu5OVqPLVx2IJk3VAtKUxoO50aYGbXruIBU0raptLtgYLF6nIlVQg6O+9g5Wd0aw6uAdpCzHZ7eBvWvUrJRA8Tl4O1yG6UrhZ+AC6ND3DfmvUb4P5aLXhc0rpT2kioyIzGw7hHoJGIXpm5DOrP9v4v89bB1OsIBW7g7KeDGCOyW6eqWwtoxOPIL8L5GWxpK6Csxcn0Sa2hTSlN1F6v23BV3VCPlxAEBt1LGmGt5mJE3NPawuvh373ElqgFNVeCepmiEp+uJI0w7A98WQ+sC9pNoLxm4xkEqG18SFTWXuyCb4EbgEdwD9NyfkF8h3wSUAhHYPlsuw4Y0kSchEh7fCB9AW8ui/zwmeYbP98FstC5nUfVd9yGa2/a79btb2Zvt+Y6KdhRH/Ef5buIwsiK9B/imAOQ/5Z+8EQ8eRujGB1fkGUheEkxpgV++BpADYVAcEsMLGJtY2WTmsOYE1hWDh7beRZifaiT+P+mo4AKrKvR3gnseqKrB3UzLYO4aUjwJscldQRj8O43+L1ED7IKOGgCuBn8iTYbkwrpXBZjsGkoY8ZArR0qfxecLmsn3NxaanN6oTu5Wy2epCJts3K5utLmSztZleFvKQzay7rkEoFZteHrKZZaH8bO1mS2ealN+obja71bbT2wXzEkwNwEsAXGHkzfDn4ACPsgAu5U/dzcpcA6sbkki9D3o49x7W7AJIywFoCQAr4ACiqkHkRRJr2lMpolHP2oJ7WV2N8lw4UiVYUtqryjSsOmzksINGCmu3sHZ/GCt/iO8gk6LOwg+jrQzTHUcqwSP2CQ7NtSMwPR7Hyj7FdQwbys8sC3nIgnm5CyoXRrDkj/Uz24U8ZDdqNz0Vk3zIQzZbu5DNbBfykIXyt1IW8pB9q930RiEXm638u1xstvI/1cVmK5/NxWYrB0gQ7OHkFsJ/Djb+CcDzPAC3FekmeBkCty0o33Afq/YDyEVg6DI9mPlu1iB4UxejbR8YtwdtIRdU++E9sdDPTg5rtXFEawJrDyKQzEa5jHjUI626k1XZCBjB7so2tB10UGQTJMePAFYJCPPhuECgi0kJ1leKfpZhRbkFfhyB5dEFdN34+P/4Lfs3Ftq4rvD/6aawsWLNPXIjRe4UshIMiACNlWBe1TYVq55FPlfBKoBMuTsSWhfgEx1dpAeYI1mz/fYp9u0BoAE+FS4EZTH2O5zE6lfNKMdF0G6isA5o5mzoZxlXBqCVu/GZhfg8kTLV97PyoJMi+1NIW7aAg2X/C99HJiL9VD4PLheLjDO3IWh8KYlVQ/fcXCv/j93cbuXgSZvp7WZuz7TZ6v6cslu16/ZVOKBN1SRsqHzmmlYGsFX5AOfOOZAJd7PqxTunboL0xJCqKhYBH9j5QDx0NICdDY0s48ktaF+JFBeCsioCgEsg9bEFpKrVkqrkblI3mwFWAFiCzVr4U/J5cQD0POho6OdGO2m6klnTZ2H1C5AeCwHex+HSSwzDD8HljuBgNKuOG0klX33qF3xjN9v+rrZis5WJfde+N9u+lbYzy0J2K/tOt+nbN20rG6GC6enNfKbNrLvRttjMOrGbbc+WD22L3WxbcRsA/XdzZGSBlNsASnTtCO5YmFaVDeBWGFn15B2sEllRpoO8gDauBJj3ISh8EduioQdRNwTgy34V+Iw6sGx3DMrmY787WS0SBJ+jbYZcaYfUQDCm2geGrsHn14LFq0yot7F2JwJEAFpTgv2W4vtkwwvgorvPBS82UnYYWDUI/Xzt+4uFfs8ff9Mf82LT629UF7LZtkPpbHXftR1KZ6ubbTtUNj2dXi42s+xm9bPVfati+vZ3lYuLfZ/t7+2P3skKGR249vTI93MAepmWFW6ApQg+BWhSAzzq/BhS15sR/CkIAR2p8nWkrkhkbYmONAcA6F1RYGiUd99BqsPhBO0sY8xTgBb2PQI/oCQVgkpVEcDfZSHtQJjIDgAzHAEitHgxAsxSI2uKUliTl0zqDqQt8axaiTbdcJEn7UpSHryLlM33kPIlE6vrI/6E3/k/HvLvZd97h7+EeXCCH0HAhMAteOv3WvFtLj10McCaGc6KzXfIreFvj7uqLQi8AJhMgKcLLjdIwMbqsttIU7qANaUW0uyDTpbRiz2QGwWJ0MMxpKmD3NgbJmPLYEywc/+9op+DF4WqXgMWvhtsL0Ek6hHMqUo0pOpwcFhfhOhmUrXrWVUApi+ENClMIU0JwF4A3T0EP2IkdbFq6jb4SfiJRFYd0ePCgWR5Gd/nyPzrGPq77M85J/8l5/PPtO/8ztJgeqMb5cVu9mGztZ35WaHtm9WJTc+LKVbeSYqnb596bOraLevbkhJYceB2mXdMyr0Ahtx9+xGAbYtmRVLM1LDV3ylIcQj1L6JuAOk+gE2kBwCsLplDmnITa/coSY1ydXEiafLMHNasJ42w9c47SL0d7WoQBOYC9AgsVduxr4BYhuhkfLoZ2yJnKhSkbHdSeOf9YPG7SFVuhAYHoPfDG5JZXQeWLofsaI9h9XAyaToA6FLsd/huUh6zsOrlFFa9ZBJAs/okJIr8ZvmN19JvHY9rLja9bmbZ9HYhm6292GztblY2vW5m2fR2IZteNrNObHrdzPrpZdPrZpYFU/lzIxf7rvLpNn37RvUzy6ZbqC6YyoSd0HyLYgD1F2Fyp0+evp6aqCO3s+XWNYAWHH7bCZdRDBkqk4dgxaUO4FNuga+Hy1MoIhEEkIX3sLrKTGG54azJB3BLI8HYNg4vh+SomgsdjTJoaE3dnQA+PFfB6udQhosn6ACySobotuEz629nZauLwjsA/p5IBJ0A555EfCZkRju0deu9rGqwk7YPMqUP4O6+ayp4HAQrnwCAT6QAzJA/L9tI/bL9OkDfJjdifonfeW1zps1WfqO2IQvV/yn7hmxmO9m+0b6ztZ1pN9p3pn3nvrLxfV1stvytuNhs+Sl3suJ2BysSICcEuCIx5BGlH8xjxS6VTGiXJ7GnJtULmOVkb7qDFOujWPkMWO8XAFYOgq6t97ESbZRHUS/DYjI8BoCr1sFlUlFdBKvy7mV1ZRJpK2MprEDJ2tI5rK10cmQ5dHRRWPCGiroI3g4gV6pZs0PLmp0a1uQA5LtRXgiXQLEM3qMGWzs5vF+JPAJGBIiqAgBaZEfrXFJ1oU0HZMcgmHs4mTUy36MV3/UYgsZjNlYNY5+XUScM/YqVVdek1U3979Ss2HLt6ZY/08VmK78VF5ut/L/Kg/ZNZobdqDxk31X/J9lfaVjxI0gJYVh56kMeMpUnr3cqWPn3ALUw85OQEluUyMOzk0n51L1IE1i5PRIMjS6/JAkeC9BiH7kTKOy9Eg6WVoHNVUXQrdnQzBUppK2YS2EN81mbeydrilM4vBple7SklnHnVrT9DdISSBIZ8diKNDsIZgR7cMgPgBrtYlndCMnRHU+qaoC4GGldIqmrzaxpwMXTdycAbWINZIX2qJ7ULbjYugHoVwys/LWDVUPzIDnwnY6B0YfA1tfW5phu123LDLyfAsz/MMsU2FnszzlPN/0e32H/JftKw1Dj6Xmx2fKztb1R3TfbcrtXHliVie0yc0y6UHmm71r9dSYMvBRug/8CJ15u41qgi7cBAL+A5pxrmHq0SZ4SEenxy3tZuRcst89Gyjw7qXISWHUAvsfBms0GVj97h9y8COpo5VMA3yYFqX4O8JTPZ/WeJASFc1lboGPtgftJU7SAtWUmisgDQ0twJwFkP7z2bjA2GL0YwSAuDtVhODS5zNtQl0G6QCeHNdg4rBtyolDP6h13s7I3GkAGUHvVpOxBr9FsJM1RM4X3QX704HP6IDtOGln1CgD9UjKpKrDPUVwILzv+eJdQfnsoD7/t8XtY8Ywdvz/9unkawTqZ5/0zgHxa4Hxd/Yx0trobudj0dLa679oOpbPV3cjFpqez1c3cvq4w5GKzlYdcbGZ+eir2Tb1oYQHwjttJbjkrfwRgyRDctKUExL7JC/AF0Ha0y4A/jvwv1aTYFEfKv7mPlN5IUkQaKQjqHQtYuWsuKQvT0L1nsjY/jVX74XvtpN6bBIngZO12gPZ5BG3yVPVugPBZgPHJuZAed7D6KWjlXQbSFiRwWBUCQgSK2mILR+5PZm0FgNwAb1GTuj6cNPh/qh1w0eDn4J3wGtRXqliG/yKqIFUk+Nt0X3ASk/IP8H+ByxizyJBeK2uHAWiZhXcwAdID31tuohzFPp0AfiMuPJEdr1imwCpPkF97sFfx0ztxvKTXioD08uLizJgC7bPRpNg6D3GElhXrUL9awYrVUzJELLjvDVxstnJxsdnKQy42W7m42GzlIRebLT/b9swysdny07f/c+1pBHP74sGgqQBUJCnlBEl3em0ti1ktxORL4DKqIYD+aQS0cTIrPfez4oX7WVkCWSLsvMdEyoJMUgqQkWqKFpJmlwvbXlId8LB2F7r4HC+H7zIDoOjqq+IgHSJI/VQsqUVGFM4hdW4MNDH0cyGkR0kyR+xP4ogiJ0XkzqcwyAtNnYY0hXeTGswn48cqYeuXBei3k7r8djD3PaRutnFEeRxr98aT5h/uYJUEfKKTX4IP4/t34P/2OCmy2cOR3bjAjsSSqhvg653PypZ5pNp7JymP4ficMuE74eKVOdoy30OOl0iL1Xex4kXECtlppMBFGjx2O9ys2O0mxQtgbNleiXYbhaH/COj/lnazHz9b3S0frO1hrCi0s7IIAHvxnilGvVY1qwnIZTqnzFuWdeRk7HkZXFYyevAOfIaLlS8kUnDa5y+jSPV4OKs2xrMqx8mqfA8AnA49m8GaAw+Q6kWwWIGH1dvvAFjSwLjpFFWSztElXoosBlhLwIoykb8ojNT7DKx57n7S7orn8FLIjTI7RRQgONyfyBF7VKxtCmPti3eyGiBT/x1A3QYAV0Bv10WRZn8kaUqhv5vsHFkHpn8O8uM5LakGwbbC0hvh2+Ft+H/9To5qTqeIXgcuruTgbXRFA4JdmRP9D4gJhmIA/mT5P6zYD7DX3oOL2ISA974gQBXbTazIXRiUI7c9uYAUu23fyI6gPSYMjVji5wD0E3B59hFB8nVtZtjN6r7LbmXfP+f/3mzfm1row6e72HdtiwXziPoV+3FSQtui+2QSO7p8ZT4AuHlq0RSx6fuG7Jsy0cSi/56GVv6pgZQ/xglegW45FVrzUfhGdMc/mUPKZwDoZ25n1eYIUm2LJPWLJuhdG6SAC2zrgZRYyKrcRax8EgHXk/icX9zP6lwEakWJACC6/FILhRUAgHvsFL4vgTXZCaSFjtaAXcOKbBxVBuDluTgyO5kj8wHqKg10MUCdB7ZG76Gug8QoBqBrAfKCMASSYPhGXABl4aRGwKp6ARdadwyrOyAtsueRGlJHVWfi8E4HRTVnUkSFkdT5kEQIOoNPofwt/O8Axj4dq5r0pNkSCX2PfPZ8VmwBaJcj8NuNoHhPGgJfx7fYN7idBW39N/BH1AJmiT2mAB1s8Uf7vtvT7f8x+0rBd7nYbOUStAUPngR9IhNEIsgzcqKPEYAFV+2cbb+buaw1sRHsvjkeDJdIqqcROD0NPbwrkbTZCNaeBZDkpsZTSla/kAgNbCfNbgtr8syk3mFh1XaAuhCA/skDcLD6KoD/xQTIhlhWl80nTcF9aA8g7k/hiGxo3zwj9LOetSXRALSRI0vNYHMrRbwYQ2H7UygqWwsWVkOWKFibA4lxANr7hXtxAUB25OK7VBlY2+DiCJkAheMQnABVgP+ZA6beaiBNbiRrGqys67Rz1D4za54KI+U2BKVoF1wt6WcKVj4dzcr2FNKWJLKmOio4pKjMVbHCGMfydI1iLbTzC7YbH0uZ6PTX19j4bwDqmfXf4WKzlYdcbLZycbHZykMuNlv5rbjYbOXiYjcq/9NMnsmTecbiAmDpQmVcWNa8kDnIMtx2ren3sjCrrACKEwSXaZaPKQGQSLBhNII8yIQtiuDIhfpJMO9z4aTahe78BQD+uRTWbkOwtcMkgSGptqSy6jn4zjBWbYB+3j0foMdnFM9jrTwfuEsBUFs5Kt9KkXuSoZfjOGyfERo6haNrLRRdGsPhRbEUXpjAkSVq1papIGdwIbxwB2v2RIC1wdj5c0lThX1r7Ry+DxeZjHcjmAvexHkQzPxDgH7P/axpt3NCtYPDn4dG/luAtciL32PGd0PvsmkBqfPQg3Qlc9jzSuhixB0rECPsBKAfhiR5FQHh1rkyPDn78TyQSYoXDdfLuR+DpWWs/trmbT+9i/7baGv5kTM9ZNPzMy3YVoAsa8fJHT3Rv7JWhQy7iS6canZrJmz+AqRJNk4oACujHDISonzyblLlQFbk3svq5+8l9VoDqbffR+r1SlJtBnh3IbjLRZprY83P41i9GTp2L7rt7dCiP4f/LBkB4HxSrV2ACwFMuTeKtaV6Ciu1cFgxwL03BqzoZn0+tPPmSADKQBE1KRRdZWd9GYLDSgNH1phIXx1JEfX3sHanhrXbVaStiGRtwd2kybmHNZVgeASFWlzQqh/B8/Hdn8b3XaYhFfSvuhoBY5uLE7YjMPzpfaR6PhHB5AOsEX/OwerHAbYmSJR+sPMa/PanAOS1chwA7GcAzCEtAI1UVku6dri+sRcsuAAyWVF+PymeUJLi6bmk+CUugJ9Dk4eWBV6Hz0udMzXMGdzp/+UWBOYMD9lsdeLXmYBZ5k3sSyHlfj0rhaGvVX3T9imww6Y/MkSQzX+Bk/dznLQnEL0LgzyDwO8ZdL/yONQP0WUL40HOqNciBWupfg7mfdJKKuhJdd58Vu2QMWaA+HlIiedMrH3OBCkBgG6L57CndaR5HvV/DV8PwP84htSb7wEAoYULYyms0sER+yAzdqJtsYcNJS5K2LeAw8t0FFGeRFE1YNQ6yI1CA0VVJXN0eSxF1s1FkHg/he++n8Kqo1lbFQ75omZ1PS6O1hTWbMH3/Ht8720KVj2L8p9rIDvmsrrdTBEd6AW2AehbEB88OYeVFV70GF7WbHaT5hnIjX70MC9bSPl4NOQbjsUGHJOtaL8V+Rzd1EpLG3D8nsYxu6aNFfsSQQAe9JLQ1f8Lx/EFEyl3T8kSsWAbebBBSGfFnegx56Ft7NS+0iAr5jqAf1N+LZ3uYrNth2x63UwXm207lM4sv1mZWCgVm1kezIcy38fFvsmLZn4BDJKbysod9089eGrHwZNb2FIPCw4/ic5ejpPyJNjnabDGs4jUAT7F0wZWbHewchvA+hzY6ymw8GaAdA/AsBWyYs/dAAcA/AsrJATabAWgtwPIz4KdtyMg2w/w7kFAtxMatAAB3u5oBHlxcHzOz9HmGXTnW+agi9dCg99D4QUGyApo5XwbdDKCtUITRRW72VKQyNHV0LqFcRzVkEgxdQaOLkUeuja63EyG8kTS1xg5ocjEMSVJFJGP3iAHcgL7RFSLDgfLFyG/Dz1AK74XLgB1yxxSt1sosjWZtNUaVq6+nZSLcBx2QEM/O49Vv3RBtiDfaeHwbXfjIgfgEB8odoBh5SkaXNRK6HBlNqTHi3qAMo6CJCDHdDvaZqcEyUPRiOP6XPRUPuQFOM4y9Lccn7UGgH4WAfqGa+09UaxwRn3TXuyb/b6ni81WHnKx2cr/XBe7UfmfbsHAD9H1fi8rn9ORcp2wCMpkqqfUSRsJFmVJ2dCdwWfvR1cK1gmNn26zgI0A6M1go7VzSQmmV7XGkupJ6brhuxAIPmOFPJAZbCmk3gRQP6NDlw0g5yPdrRcAc1gBwFmexJLX7ojlsO2xpN0F3bwdbV6E1t11H9qEUfi+KAovjafISgRp2Qj+CpIpqszDljIT62tsZKxIJl2TgDqZ9c1WNjZZ2FBuI1t1EiXU2cgCcOuR6lscnNDmYH29i5LqXZzU6mFjp5uSuuDdbo5ptXFks4xuQL40G1hTf7/MuQZjIybYey+ptuOC2wmpUpZImnYna+VYiB3HsZIlC0R2bI7A759P6n2IIXbeR8qcVNZsAlk8dTcAbyLFLvMfQflTsLqksG/KrqWKp5PQ3k4K/F7FM5AfP8Y5i0dvcDfilalm39j0fWfa9PIbtbmRTf/cmftOrxObuS0WKrtZXdCmb8xMZ9q3ymW5V3Hp2q4tHQuQT0XvMrnoNueUdhNdLTdMQpNunjUhiLzWPW4DW2yB/nsWjm5TVZfEqsK7CIFV8O6eSp7QzkkkVZGdNbssrPm7RFb/BGDYuwDB2XyAFsAtBjseAOOCLcOrnBSWm8jhu+IhD1D3YgxpoJs12+ZyWP48iqiKoYi6eEgJO8cU2zimxiIpJVU6yVHhYFuhnqPr9BzTlEKmxhQyNidTAmSFq9HKjlIbJ5Uag2BNaHZyQj1AXOfhpDoH6TosHNVp4si2FI7st3JMn5P17U4ydLjY0QXwDzpY14/6liSwNTwfQex+9Cp9ZrB8enCG3W24ABWQIIodblbuskNWWXFRh8MVpNzjxO9dQtoi9Hyb56NHNE4dS9lPbK0exzls6phes2B9vjnYIwTLc1MRQALE6LUUtvsQcGuvuwhmpqF8yKZvz6wTu9m+YtPrQ3azspDJ9q22+6bxTBebrVxc7Ju8aOiQrpNtGXaTcejHwRjR6CKlXIAskmOavg6aAwf0YRUp1qEblWmfP4eG3gsgC4hfjJgaNZDn7l6YS6o8K6s3AdBPmgFSSI4XwL579Ry2L5nCStDVlyD4KjZzOKSDSIqI/WDH/QDZXrD1AUiC3FgOL0qiyNJIiqxJJF29jQ0NYNdGpJVg4movL61yUHppLOtxURnhpopkNjWY2NhsZkudiy15CBCr4gB4A2QEwNzgpKRqNydUALzlDkqotuMzwd4IEnUdAH23g02dDjI2WikGF0QMGF/XYgb47Rzd7KKIWgeFDVtYW5xK6rwHWJmdRaoXrJBfTlZuMrLyJwD3TjBrURTkRyZrsx9kdQ50854MUlQkAdjQ1BIE7oX/DCB9xvFHxg7ZRiN60ThW/EMC2tnB+DZWrkL7H1+TLlOtbrtN1vL41dR5FJueftMGNrN8trqQ3ah+ZrnYzHbTXWx6OlvZNxZq8H1cLJiXp0R+PNXVfVMn4N6ugUZGgPiAvGdk6lZt8Jb330wdMMVtYJkwHGi5C2iHtkwHs8iSsWugMf/6Dg4+zVE9Z+oWssyWk6BwJ8C8AwHeiwjAcpJZK4DeF0vhuUkUts/MYeV2jigwc8SBBMiIFI7KWUBhYPWYCgCsxkm6aicbKlykgy6OrkxhfQPA1uRiW5MDgHWQq9ZDj4LhMxuSyVGTyKZGIztqk9hWb2RLm5WMlW62ldjJUhzBEbXQ2CiPqjVzTKmVE/bEUnQuWL04nmJKob0bTZAjJtb12SFB7Gxss5OpxUEm/C9LKQLOKlxYTckcOWzl6EE7WDuNwsoySLM3CwHwQlzE0Lo/DQezRiDYhpw7AJDnZJH2+Uwwtwua+iEKDu9tRxs5njI3Zn0KK3Z7sC/k3FrkfwBZIXXrZfVTJymfQNucxTiWaayUWYo/xcWwVssKHG+FBO3/hM/496nzIzZ1nqYslL8VF5stfysuNlv+VlwslN7QZjb41g4/wAH4GwQdyH5TJ2wsDC1LwD5wLyv0ElkDwD+dWo/tNgG4Avo5415SWNDOcT/a4aBuw0FddDsr/woMLYvBVGpJLdM4n7iNZQkt1Q4DaTbFs2YDpMZ2SAroZO1OPQugIwDwsP0mANuJIC+RwvbrAWqAJi8ZnsRRhTaOKgeYK1LJVOUCOJ0ArJsszW7ytjnZ1eLk9FoXP1pv5SX1KZTVlMSOZhN5G43kajSTt8ZCxmoPe4rcZMpdwFFgR129DhLHAnbGRbHPwIa9kRy59z6OrNGzvlZP+mID68HMNjCyoQXtWtyUWe9mTzVA3mQCSxsobMDEEQetFN4LUAP0YaUA9f6HoPcToXdNALGZlfkuVu5fxKrcTA77KYAIqaF0I0BEbxa6tR00mcD0nA6gduJ44aJ4MhYsj22p2wWg52aRYv8PWL33AVI962HFpjtJuR0Sxwk9DuKQtfGU/4w0+GHftpnl37U93f6cfWfaLe0bKrzV9BuTl1W6wSIyonHHtSBPXk55bcV5xVx0i3+rYoU3UkY4ZJ1jVsSi65sfTcqH0cYE8M5XcTDy/yFOjmcODrBq6lGpffey6kUdqX65YOpxptwE1qyKJfVa+M5o1uLERuwxANAJHLk/gcLzEqGRHZAcLoDbgDwY8ICFo4vtrCuFfi11cUK5h02QBsaaVPbUugE2F6e3uWhxs4sXNVhoSb2dl9cmsxeSYXGzhdMBPG+thTOrHOyqdJOnKB0a20JJMvJRlAhgW9lWDElSBK2dncSG/TEc3QQg79eRrjyJjLUO9laClfF/k3DB2ESnA9Qm/O+kKny3bhvp2h0UDa0d02mmqFonReQvYu1eA6vy7azGb1EXmUgN+aTOQQC5N5aUL3hY9axB5kKzYhkCaTnmTwPom2JY8byBBPzKgh+QeouJlFvAzk8A6NvcYHPo590PsDovk1Qv4jM3QZs/G0WKH6E3rQOYL8D/8EdAB8/fVPabfGhb7Lvqvqs+lM5W9131oXTWuukVt+pi3+QN0MkLoNEW4CBLmbzmYdc9CFrUONhg45RIUiwEm8jkGblNvvxOUmSFUfA28YMAdDzSB5AuATtvh4bOxnY2gsJ990493PpXKP+lmlTr7yLV6ntY/XQswA25UbAAutgiwSAYGIFYGdiuxMERkBlhu82sLYjj8JJkiip3s77UQYYyAKrEQ7ZypOVOdoBxF9W6ObPRRUsb7LSsycoP1zj4iSoTZQLgy+pSOLPMhPZuzqry8qIiL3sOeCmzMpUXl1rYVGympBowe2Ey2wA44/4UNlSlsKkEgC4wckKNgxYBzOm4kEwV0NoVuIBK7cHA0wQ9boKGTihJoYgSC0W3ujkJgWNMp5t10OERuEg0JW5SQ5urio2k2u2msN2QW9uxnQNQvugiVU4GKXdbSLnXTcod8dDGCKh3ekidg2AyLx3HyM2aPID/OTOADMbel04yNKrcAfmxBxfKjkWk2p4K5jeSshdST5YkK8B5MkK3Tz+/0yy0PbP+um1ZSbVxitCm26xtr6WzbU+3mXU32/7+Jre6ZZWfa5u3yevInoJk+OuIqR8hWvnZCAASAcj2OQj67mZFKuqz8CNl5ELuYO1WyoiHPNxKqochJ9Yh/Rtsy2NTckv9YSUpq+LRNQLMB3Cg16Ie+6qeuR/MoyfNz2NIU5DEYbsQBEJfRuYlUGShCYxspcgDoREOsPeBeIooc3NMiZOTyjzkKfcCUG5yAdCeShdl1rh5cZWNMyvttKjBzWugozcDiI8h2HsMAeIyBJre2jR+pCKdHy/J5IdLs3h5dQatK0zlRcUeWlzjpkfLoLtLLOyBljY2pHJ6eSp5KtzsxWcvK4XkwGcbAVoLGNeWjzboISxge0udk20Ar74GPUgLtHgXAkekUXUA9v5osHA4wKiHpnZBWiFwXA+JgMBYtTOD1MXQ1OXR0MvJMuIBqWEh1fO4APamQVJ4SZWNNnu8YPasKd+zkFR7HkQQDY2+aS4rtoDNd2SQKtdFmqI4UndFkfoQjq+8BsNy/Q2X72X/inP/rzh/8sqMa0X/pTbzS8y6LUGfDMl50VVZ5smbm0ghi6TIO0WkXkY9ZJnarQZWboHeewiBRwQ0YSqCR3msSpaNlTXd5M6ZPOa0QTk1z1hmqmUBvEnwR1G3KZJVe+ZO3UqWZ/eeup3UL0aTOhtg3jyX5U6g9mkjh201UeTuJI7eG8eRuSaKQNcfmR0PMCO/3xIMFEV2JJV62Yuu3wNPB1unC1irPfTwPgtbil3sBbCXAmDLq1JpTZWH1lV4+YnSVFpemcaPV6Txw+VpvLQ0nZdXZNATpRn8RHkarQJzr65I5ScqPLSy2k1Lmr28uDadH6l1cladGwztpUW4iLLKXOQqMrEDEsgEkJtQD6YmW5WbHE1OMkJDx/QA3N1WMiCINNS4OLrKTuEFHo7c48BvnEdKeUPWLgurDjzAyg7kh9SkfA5aegeOcQ5kxN506G8vaw54EDyDgbOzWLMbenzvIlIfAJj3I923lDU701m5w0PKnQA8WFvbDDl3Ase6+g551IyVz03T5Texb7X5A87reZzff0F6rehGdiuffyP7zn1DDSSd2Xh6WSg/JSMQMQPUysfVrLBCh8ktWZlwL69OCC01sBnaeBO6wScTSbVIy8oV0HzQ0MG13H4FbwJQzyFtBaD3hrP6SaT4TICbVSJFVoexCiysKbsb3eztYJ25rH4BAeGOGNYA2JonIxH1R1LY1hgKz4YG3WOi6ELo0r0minoBcgNpRK4ZrJ3EMUVOshW62FJi5SQEh4vKnZRZ4qR0sPQSaFRbsRPgs/GSchs/XOaltUWptL4klVeA3ZeXpvIj5fAyDz8CgD9e4qVVRWm0oiSNl5el0epiD68sS6XV5V5e0eTmRxvTaE2zlx5vSKXH6tLoYVwgixEIple50BPYyAGgWhqhpctRVm1GgBofiG5JoeguGyV0I3jstLG+xIPvnEqR1Q6OarVweAVkVnUkqQ7EsHJgHimG72blQZm0tYAVhU5SFQLIe9NIeyCTtGBk7V4B8DL4D0hbCi//a9ZW/w1ry/4XaXcB1PvSGe1YU5XKYW24YA7GsFoeAZMn5CELlSshD4Nn/frzHzRh31acY3m/y0F4PxzFQTBfDPrUvvJSI1l8Xepk+5qFtmeWTU/FblQW8pB9q930Rt/LZcK+jGbs1MgDq7IaPSvkVQkp8DQcFGHxdQj+XnCQehsCnB/PgUbGSZD50/I0tky3lLdN1YOZt9/B6m04qD8LI7XM39ikIs0mLRgmmbRFkBWimUsTp+4KbpwLZp5L2qcjWfvcXA5/cR4LK0fnIfDLTaEoBIvReXqKyEvgiL1mis6zsa7YwY7iZARzJgSGDkoHO7qgfx0AdjrAnAWd/DhkyfJqJ68HG28DmFcXe2ljVTrtEKCWeuixiikwryxNo3XFaby6NJ1WAeyPF7p5Bdqsq0mnDbVptBa+HmBe2egKPNzkDTwsGr0e/xOBIIJOymwEkFvwPxucgaymFL+lYYFP1zbfH91jpKTeJNL1p1BMjYVi9mllDjeH11g4rAq/vyqONYegrWU5sd5k0vQhjwtHW+VEvOAhbeEiDmtMp7C2B1lb9yhpi/+KtXkPkbbkrzis8uGga6t/iHaPsDZnMUCeStp6F0d0eDisCQxdqZmaPyNLPcgIlSzEfg4uY9PCvm/C30BeXID7e/hb8NfhU8wsgWUQzMGJVGfg8gqNGbgRm1kWcrHZykMuNlu5uFgo/WPm+5rM0dgGnQwGVIINleshQ2z4Yelg2B/CZenany8g1QtO0uz2sHabhdS7AOydClJWAsAHIR9awlgD+SHznMG8rHluDmmejmDNFgB3cyxr95k4vMhCUaUpCPiSKDwX2njbfAqDPg97KpzCnwrn8O06jthlo+jnUzgG7aP2GzlqFzTpPhvFHHBRTDG69pJENiIIMyJQ81QiGCx3kxesu1gAW+ni1WV2WgVgb4WM2A69vbbMTRtrvJxbLYB10Uow7ONlqbyqzMtrRIpAwqwodtEy/PbHq8HodV7aACCvr/UE1rY4/Wsb7L7HGp3+ZS1mf3qT3Z9V5wosrvMg+HTT0ma7f1GdyZ/eljzpGowZNx2Lm7ANx04mDSb6YwbMFFMHidQQx1FlCRy508jatZAXv7wLDLoArBxHqhobaZtSAEp57wuCv/LFrG3PoPChRRzejnzVD1lb8gMOy1+KABll+UspvOyHFFYJpq56hDX5D3BYA+Rak5cjmlwcJk+/1yaxuljNqiqAWpYF/kec21eCIGUltLESQFb+G86pABdgVQqY30b67/B/xXd7XS3Dfn+UG79HXoYBoc1DN2ym4+xmmPu+eLyu/fR/dCsfNL1t8Ja2PDIlUz4PIFqW8c6fJ7Dyb+eQ8q/uoOADsRL8rbwHGhjR+i4XaRF9awsdrH3RzNomsE+tmcIrTRT+XDh0cRi0H4K+7FiAGjpZwL0FbJyno3Do4KhyB0fnQ19m2zhqO8C9cT6Hb4ygsF3RHLEngaL3JLIuL55iECwmQHLo95pYX5xKjiIXm0rMLAztKXaguxcNDTDjMzOhlQFWWlPjoo2VHt5Z46S10LYbKi28GmU7oatXVNugrd38BIK5tWC2DQDwutI0XgOpsbIC+9WmBnLrU2ljq8O3o9k5md9tGt/YaZpY1eT0rQCYH222+R6rtfiWNToDjzbbA4vqjH5XjcmX2RE/4TgxdyTppG7UdVA3ltBi8se04cIEYPUI+sKbceFWRLKmdj5pOmJJ25nMYS0AYZuLwwe9FFFjJ02Zg7SyiM2hVI44mMERLQ+BsZdwWPZCDs97iMP2LKawnKUUUfgwRwDo4bUAeS0+o0bmmjgpHCytKU8O9gDqA/NY3aaVZRaCT7UrfwOwyspTkgLgAl4BcRCoAnbZBrCVb+tI9a/Q9fKKuil43HabaGkB/ZtwXBTfha1bwV7IZra9bls2ZrrYd21/Y6KXZb6GPDu3PIKVm92k2uRk1S8Rka8zsOonESS3WoPvMFl9P6ueQOS+A91kHk5KqXPKy+DQszGlNmhgI4XvAKDzYzlsB7TyxgSAG8wtEqPYzVHQloZcO+t2O0gn6QsJFLlZzxH7F1BUUQLH7I0j/Z5kNuw3I02hpEKw835o6AN2tgmAi228qBTMWgHdXGYDQ9vpMUiNJ2rsvKHGQZtqHLy2xsNbqqy8Cmy+FhfR6ioHrah2IRB005oiN20pAaghQbbUAsQ1btra4ArsqE8NbAMzF7Q4J8s6LRM7eowT69qsk2tanf6NrTbf6nbTxMpu8+SaTpPvkdYEX2abcXLxQMJE+iHdqOW4fszRFT0e3WzwR7Wb/NGQKfq9LsQEMRS2LwxsexeFNUVBUsSxti2NwzuyKLx/IYU3L+aw5gwKG0zjyJfSOLo7nSJKMjksL4sich6iyP1/RZEFf4Vj8wP4Eoqo/gFFtC6iyIZMjoR+jq4EoGtSSVOdTOpKM475PNJ03svqFgWpjuN8DuKcydrVsrCkLPn7BwEv/N+x/Vucc2nzxu2k/APO87/czkrRzyI7QtJD5IjIFWkr20HAXI+l6eViNysTm14/vVwsmJ9ZeSsudl1enlT5a3xx0c4PI7j4cTyr1iMifwr+tAMgBsB3QKftgq8KJ9XPDaTeCqDuR0AChokA+MIk8KmPpjAwaUSuiyJzXBy5PwXMHc9h2+MobO1cDsuJ58hCMEuhk3XZNtLtsHFMgY0N+VbS7zNSdFkc6xBIJhWlcFK+nRwFbkrfZ2ULUk9hGi0DqF1g6yWldl6KgO+RCgfcwo/VumhTlZ1X19l4fa0TssFNm6uRAvTCyJvKPbS62ktbKzy0oQSMXOSiDQUeXhMEtZu21boC25uc/u2N3kB+i8ffMpQ42nU0bqTmYOJYbpvDtxXMvaHLOL56OHZk/aGYkSdeir66+uiCkZVHYkfTD84ftR2KG/N068Z0bQsmoxuNvuhGc0BXgAuyLBYX/ByOqI6hqGro6g4bJEI6hXdBXjRmQVunc3gLmPpIKumOpLKuDcxbkkbh+Qs5Mv+vEBAvxTH8AfxBDj8AENdlcHTTQopuWMjRuCijZBQFkisckiqsRLS6HhcMGLpTRfKOxeAr6oSp++G98KNwWZ7hX6+lwsIiLS7eD0kSBUAD3NDXoqWVh/8I4tlcbLbyG3nIQvmZddPzt2yz7iA6WubwyiM/bqRL8YOWQyOvvJ9UPwOgNyAKX2Nm1UZZJ1kej4L0eN7Bmh0m0u6xQS4YWfNkXPCp67A995OmAoFgiY0icx0cvtvBkbtlmmcyRW+I4fAXFnAk5ET0PpQX2TniBRtF5FqCzwHqZE4zgG7CiTFBKyftRyBW6KL0fBu7Cl2chYDukf1WshxwcGaJlbMqbbQMweKjRQ5eXu/m7ZUWerzOQWtbHLSx0Unbajy0Fbp5C4DcUOWlXAR7NfXuQEmZJ7A130kr8z30KOTGynobra6xB9bWOQLbmlz+vP6U8a4j8SOHj8eN9B3TX62D5x01jOQdX3Al/3jU5a2vzLm08Xj0lU0v6a6uOhY9smw4bixzKH4086W5lx2Dc0YN7fPHdc1JE4aKlElD04JARGUKRffh90NmxLR4OKbNSlEILiPrPZAkGRzT6yXdUTcnHAagm8DUFRkUWfIARx2A78uimPwsjirO4KjqdIppWci61oXQ6FkUfSCNoopBGsVejqxOgxxx4SJJgjafB/lxB6vqEKw3wTvgrQDoGbiAFayrRBAYBO1bcNHNkCiKC3chMFROSQspk4AyCJBrJvJEgkl5adO1oj/Fbmnf6Y1ulBeT7W99oLwzRNaRkLHnv4H/CF3PmkQA2caqn0F2PAG2fiKOVWv1pHomkVTVKaTKt7L6SStrs80c9ryJtU8ls/Y5BIC74zj8yQWs3WQgzR4Dh+XGc3huMk6ODfrXQUnP6znyhViO2oMgUZ4FzAa75xvRraaQrsDKCSVu9lam0eIKN+QEmLg4lR+F5FgqoxEVaQjqRDtDbkBqLCl20lJo6ZWlHnoC3a4w85o6F21pcQVy4BW1Xqqo8VJZTRodRdrS4vY3NDv9JVUeygG7bSvy0ipo6A3VdlpT7QxsqLQhGLRMbj+YMNp3NO5q2zHdlYaX51+uOLngctlx3ZWKQwkjJUd0VwtORF/JOaa7uv7k/CtrT8+/suHXcy+v/E3Ef6w+M+c/lv9qzqWHX5pzafFQ7GhWT9yYpTV5MqEVgWKHmQxdDjL02WUeNunAqJGNXo7qyyL9sVRKOuKhJGjqhHrIDgSuUYVe9FwAe2E66UrTOboa261e0nens64/ExfFQooqT4e7KArtZXgwQtbmq4dGb4wkdUkYqcHMcHkjLqlEYgiAwcbBwFDA/TrO9TW2Dr4j8RocbogfuekiIyTyJrJrRX+Kfee+0mCmh+xm29flZa7zcly1wtabtEijAepk6OdoVq2cy8q/A8hXwB/FAfqHe0m19W6wNiTG9gTWbAOgNyWQFiwcdiABWi+eolbdx9onoJ9/oibNunAO24TALzuR5E5gQq6R9LvjKXp7MkXuNnI0GDdhXzzrco1slBskAHFWeTo/Wu7mh0s9vBxd6qriNFqJE/douZW80MsPV9hIbnM/Uu2mJ0Q2VHtoU10q5TakBioaPYGGOk+gptYTqEPwVwZt3AXmbeuwTDa02P050Mw5lR7Khm8tg56ucAQ2NFr92xqTJza2G8dzepLGCg7prmw5vODKjsO6q1uPgqFfmn+l6Ejs1ZKjC67kAOT5J2KubD0cN/romXmXdvxmztdbfzvnPzaemfP142DvxQfnX/U0JE04msHQ1XZ/Qit+cxcA3YYYowFBcZuDojuhr1vdHH3Yy8ajbrIhIDSBqY3NkB4NXoqpBXBrMshQsZAMALO+y8OGXnGweW8mtHYGRTelEjQ0RyHYjWywUkSPFYyfRGFt80nbjsD8FQSiAwtIPRhGqn/BeRN2lhEOsG+QqWXkQ+4jIOgL6mnBwUyXJ9ZD0mOWN9yKzSyb7mKzlYuL3aj8pja90Q13ECDLjRVZEHEbwC2P8suNF1nx80dIUaf8hztI9fA8ADqKVSsUpN6kYnVBDAC9gDUvzmNtITx3ARgZujEbenlbNIXn6jg838AR+xIoYo+OI4rmUXgh2LjEgmAvBeBOJn1uPOnyTWwstJElHwy9z0OZBam8rMzLKwDUldC/y8u8tALbj5chKMQJXIZgb6ncRKl00+oqJ/Syh/IA6LJ6VyC7zh0oA6AH6j2Btlp3oKHR4e9qt/lOdpgmK5oc/hIEgNl1zsC2OoAabfMbzZM7m8yT+S2JE1ubTJNbmmy+dc2myUcbk8Yea0iefKzZNL6iNWn88e6ksS3DAPqhBVe2Dsdc2ThgGFv9yrzLW34NQJ+O/I+1L0ddfnRAN+Joix831Zh8pkpTQFftChh7rJTUYgIze1jf6WVdSxrFtHs4usXO0QedbDmcxt6DqeTqT2NXZxpbWtM4qTGN9HADwK1r93IS6pIAZgPqY3ohN3rgHdDRDZkU1fQAR9bbObIXEq4PQWKPnsKqEym8U8faQSUCRJxDsDFAS8qXcR5DgaEs1i6sK1JiaurpH5kXUiQ4Tv3K7Gx8S8D7S9h3/aNZ6+VtTjLpaOpd2sEbJgp5/95u/ODVOAjL7mTl2ttJ9Q93ISDUsmr1HMiR+ZAc90FWaEi9FprtBbDxgRho6vms3Tefw4oSKKbEyEkFyazbl8Qx0MX6KivrocH1xXEUuXc+hxcnUUy5hR37EygBbYxF0t5KLuhiCfwyIQuWBceYPbwCEkTGmB8vSeXllS5eXu2gR6tTeRuAvBksnF/roQowVhk0cj4YebgOXuvxNwDgPc3WyeOtlsnzbdbJgQaHv6LB7s+FFwDodU1WX36zeTKvLsW3pSrFvxxA3jZoGNvUZBh7vC5hfEVT0ugTdebJFc0pvhVdieObmgDsLv3Vdb26kbWDsaPrjsRcWferiP/YdnTu5ScOR11e1q8bcbWnTNiqbT5jkc2fUOwMmLoA6BoTGZqgk8G0xs5UNnQ4SN9m54QhSKiBVFp00EtZw17yDmaQpy+DHN0L2dKTwaaDaZQ0lEZGSI2ktkWk73yQYnqWcnTnQxQNYEe1Z3JUSxbrwOyRHYhH2s0c2QoCAXFo62M4bOBOUstrPC4g2HtTwcpXVAAxgv43JAicC6aeA8mBHvnf7kL9XTLKwYpX4QJwGfH4TwD0d+4baiDp9Mah7Zn1M7dvk3WaZSxawCyyQxZmlHdby8r6O9SsfD6J1DsSWfN8Mmm2mknzjJm0TyVx2LPyYCs02x55HjCewgHayBI7xZRBKx4ws6EgkiLzozhqfzQ0dCQi9WgEMLEcU2VmY4WVTEVJpK9IYZnxtgQBnasykW0ViWwsT2ZToYNcpWnBW9ZLq9JpdXU6b61K5c0VHlpT6+FVkBpr5QYKAL0DvrPaSVuanLQdrLyz1hUoqvP4u+rd/oEGl/9wu2nyNYD5bUiOk21mX12LzV/UbPPnw/NaTL4d9Wbf5hprYEOV0b+qyeTb1Kcf3daQOLGi2TC6ui/26vaBuJGdLYmTq9oNY6tadKOP9sSOrGuLG3scbLxxaP7V9dDMG0/MubT2yNzLD/fHj3r7ksddlW5/erE9YKoz+Q1NFk7osJKj1cmebg85ulLZOOAJMrL3kIcXH3bz4iNeXjScDnBnkgeg9g5ksmcwkx0DWezoepCNbQ9yQs8iMnQ8yDE9Syi6/yGKGXgAwM6a0tNdWRTZjZikO4XDetFTVs9lbSdkx4koSI441ryeQOrfx7P6Dwms/vdEUr+ZxOo3ERf9IYZUb4Cg3tAC0AC7vKRfRj6OAQsyVn1tfvWNXGy28pCLzVYuLjZr+WyF4mKzlYuLfZOXp1bkiQl5bdrP8IM2wZ9F/ul5pMxB8Ffq5jBo2cgKN4eXITIvclFErguRt4yDZpCuyMO6fDNH7YgC69qgh+0UvTGaw3+u4LBdCg7fF04RpfMR2cdC8wHQFQbSyTzkGg8thR7OrLRzVo2RXbU2Tq9MIktFAoDt4KxCJy8qTqWHK7y0piaDc+rSeAskxqoaN6+scdGjlW5eh8h+aw0COjD29voUWtHsDOS2gXGbrJNdkA5DLXbfuU7zxFsd9snP4e90WiaH4B0dNl9Lh8XX0OrwV9U7AvnN9gD2CeS0mXw7u40T2f2JY2XDuqs1x6MuVRyZf6WiRz+6vm/ByBND866uG5h/dVNv7Mia3rjRDYcWXN1yfO7ltcNzLy8/Enn5EbRd0pcwtqjZNvFwS8pEerPZZym3BzwAm63XBvb1sqsjgz39YOLuLHIdRwB8DEHvYTctOZRGiw5ncNYh+BDAPZjB3iEAu+cBtvQuYtPwQjYOLWJ9/2KA+gcc057F0c0PkKE+g6MgQSL7kzm8BwQziF6yAQwNCaIZjIWbWPObFFa9ZmHV7xDk/95OqvfdJDdTlBcBZNHU5+8Ac8P/RcnKf5Jt+L8BE3JLXIJHuesYwsu1dGZ+tu2ZZWKz5advX1f4J/u1p7yDL4SXxRRz7iNllZs0VRkcUQnPuYtUe9Vw6OgDiKD3x8CNpC0GYxd7OQJBW1KBiw15BorcGUuyrlz0/ggKf/F2DnvhXg7fG0UR5ZAaJTqOqtQhEAQ7F9nYAdZdXuvkxVXC0ikk0zGXVDgQ8Fl5aamTHgNDr6zKoJzqDN4OWbGyzstrqxyQHC6A2klrKq20qt5OmxvstLMhObCqw+TL7jRPNkBmDLdCYjQ5fCc6zJNvdNomv+i2TX7aZZl8C2B+vds6eRT5VzusvpPNwuJ26GyH/0RHyuTwYPxoy7H5V04eX3D18Eu6q10noq60DS4YyT0UfWX7y3MulfQYRnf2xY9t6U8Y29Svu7rupcjL2zoXjD4GkC/rShh9pDd+9HF8xqM98WNL24wTS3sTJ9P7Lb7F/Q7/kkFXYEmP27+ky+PP6krze/u9/swjqYElkByZRwHuw+m8aCiNs3pFeqSTBwxta3mIklqXUELPA5TUu5h03Q9xTMuDlNCaxfr2xRzVuJAjO9IooiuFI/qNkBkGDmtLYu0heRo9kbQtAPRJM8BsY9W/AND/Xyer/q8kUv7vWFK+bgBLG0j1b3Gk+kM8qcX/LSoI6CCYTyIVQF97Z7m42HTsiN2obqaL3Wj7G7tu4wamkKdQruWDJncG5UU8L2pYkZ/EiuxoUu5CsFeAqPgArupyF4WXp3NkOQ7U3nny0kpS5apZ9SLAvGcBacrNYF4wQqksv2Vm7R4rh4FRowq8bMh3kW5vMkeVypTQCI7IDqOIwjiOLJX1MRJZVxpDkQVJrCtyc2aRg7KgiZdVAthg7BXVbn4MevnRGg+vqXHT+ppUyIoMyq9K5/xqDy+vS+XVtam0rdZJ6+pdtL3RRlvBrDtabf6y9hRfdm/SRFmPeeJ0iwDa5jvT6PSfaHT5T7e4/K8POP2Xeiy+d3pNkyd6rb7jvebJ412myb4WgLnN5h9ut0ye7TaOXxhIGDt7bP7V0yfmX+05EXt14FdRl84e0l3tEcY+MffSwPF5l9qGY0c29elGNvTOv7oG+nlTV+zoyu6EsZWdyWNPtKeMP9KRPPFYc/LE8i6w9UHz5NJ+y+TSQ2bf40NW34oB++TSHocvq8Xt9zalBxytGeRqgW7uSofMSOPFAyI90jj9YDpnDi1kVxcYuvtBNnUtYgPkR0zng5zUvIiTOqCfe+ANGWDqNI4EeMP7IQUHEgFonMN+BOwH9aztRrzztp5U/6Ri1T+CsP6AcynjzzKU93vkX48EmA2secvI6rcMrP5X9dTw3tSkpT9riO5PsukIv1EalBUS/CEbLJO15+R9J1sREBxwsLLYSao9DlJXOUlTYSFNWQZF7rk3+HIeeTuVMhcBocxpLpGpotBlxQmsLUyREQvIDBMAbWHtNrDDi4kcvt8aXPjFgOAuKddMMZu1HP5iGAuoZYKSrjyFY0plfrOLvPkmsgHYi8qgIyvTeDUCvFXQyKuhi5+o8XJObRpV1KZzVU06NdSn8ppGBIF1CALrPdTQ6Ka6ZgfVtVr9O9vM/h3tyb68gYSJrj5o5h7z5Fvdlok3O6yTF8DQp9rtvtf7XP4v++3+Swctvq8GzRPv9psnX+sy+/pwIXT1pkz09SePX+g1jr9zJGniq+NxY5deir5y8eS8yxd+HXX5o5NzL712MvLS2ZfnXjp1PPrySQSDNQMLru4YjLmycXjelQ1d+pF1zSkTq9vNE2tajeMrGy3jm1uTx9Z2JI0/ChA/0m8LPNLvouW9tsCyIbt/6WFn4OFeDy3uTKdFHRmU3g4Z0p7Ork7o5u408gylUpYA+mgGZw4C1NDLts4sSupYzEmdD3BCexbpIUeietPA0KkU1QD5B0BHdCKuaUNQ3hjD2jYDhXVEslbWv34NckJupkATByUFAj4Zf1Yi6JuatBSG7TmQHRHiAmhW/O56ME/H1Dc4mpaGbHpdyKZvz8x/qy5UOL3yujIBrkwFlcViZFvWLw4txrgnlpWlWawscYF9baQuTSF1LXRUuYU1T90enL+hEl0NGRJ889TecFKDxTV7dKzZF0Pa5/UcliM3VFJInhEM251EYdsNHL4zgSL2J3PMATeb8h2QI7EclRtPkQdSOLrASvpyN3uK7GwqscvdQfIinw6dnlWdxo/WenkFdPIqALgADJ0PudEFUNfUeim72U15LalU15RKPc0eOo70VKMjUNBk8W+FJj7cbZp8s93me7vHMvlGt3nyLPJnWh2+V1vd/jc7XP6Peqy+97usvvPdNt9psOZb/abJ0wDz64PJ46e6kydO9yRPnBtMGv/woGH0g0NxVz86EXP5g19FX3n7V/Muv/ubOVc+PT338hcno6988nLU5QvH5l5qORp5qeR45KWio/Mu7xiIH9nZnzi6qd0ynt1sn8jvMI0/0Zoy8XiH3b+8x+Ff2efwPz5oCwDggYcP2unhg05a2gvgdoOR2xEItoKVOwHmbi9ndqaytzGLTO0PkKkLsqP3AXK1LQKol5Cx6yFKgOsHoJ3b0jm6IYOiG9wc1W7gCIBZWx1N2qr7WdMWjcAwgtXV97NKHqCVVzifBoAPw2WoLjRc9ztJca5fn8uqf5sD+YHtt69h5b/AZ7VvKgS88kjVBvwImYS0cx4rZd3nXbKOnZVUJalgW4B5twVSww1QQm89CeDKqkeFSIWVn4PkKNRQ8P3a26RsLmsKdaTNRzSdF8lhudFgarCBPDaVG0fhu2QuMzw3jiNeSODoXDPrIUdMxW4y7rOyITeJDaVO9haYyFTkYFcpTh7Y3FZmJ0dtKqfXpdHSehevrnPzljoPZ9elc0tdOnXUpFGN3AlscFN+k4eGmzyB003ewLutrsCJZlfgZLtt8n3o4veQvtNpnXy7G8DtcPjPtbv873c7/G91Ov0fdtn8r3ZbfG/2pUyehNw43APvS5589WDC+HEA+a3h+PF3DulHPzgcN/rRoYTRjwaTx95C/vwr86+8fTbyyjunwNZg6DePx1x556V5l04ei7pcBl1dcXze5YoBw9WOLuNoVZNtsg6ALkBguKXO4Xuize5fg///BAC9us/uf6TPGnikx0oP9yNWaJen1j3kQs9jwUVrQoBna/KKBGFPG3R0P4LC1oXsaF5Etq7FbOxZAob+a07oXMo6AFwncqMegIZci2pI4shGWfQdoG4FkH+F3rRnDqsOxrHqnGrqlrdMJZUJSTIWLTdWZBbem3GsflPP6gsaABp1IjUE1Ncg9H+7TUf2zC8RXBVJ1oCWUQy5vf2CmuRVYsoDAHUhgoQDkBr7rKR8ej4rn8HVKS/k+SWAuzGcVM9EsOrZ+0j9EwSCG6NZ9Qv43+Igbb8d4AaoKyA7qmI4bP8CCtttCk7Sl7kaETkLKLJQ1p6Djt6fQtGFyRSTb2R9gY0TSrxkQ/DoKrVTeoWdHaUeyip1k6tIntVzsg1SwtOKk9zk5uX1QWnBFfACALikIZVKoKu31NsDm6GJTzV7AudanP4LXXbfu/BPuqyTn6E7v9Rj833S5vK/0er0n2tyAfRu/6vInwRDf9Zj978tLA02PtefMD7UbZzsAajPDBgnTnRbJoe7kieGj+rHPj0aN/blwbiR9/tME+cPJo6fOxE78sYrupH3TuhGPj4Se/X1I7qr548suHJ4WHelBMFixUDcyMkO49hwh3F0qDtxtKLJPJHTaJ7Y2Wye2NRu9W/otPpX9zr9q3GBLe+3BFb0OgOPdztpSaeLMltc+M0usrS62NHqATN7yVMLQDdBWw+CvbszKatvIXs6H2JTOwDdvJgNzQ8SAE0xDQsppimLIxvsHNVs48gqI4XV60nTEkual3EeZQwaWlp1aB6rjmhJ9WsAVp42khsqkB7BwO8PGkgOxE7/hjYX7wjeIv8vA7NYCNAzXSyYF7khy+Puvp+UefGszDOxsiiV1eULoa/A0AXprNlrZ1WxzKiTVy3gAGTHQlPLm6hsrN6JH7vzXlbvwRW8E93XPoB6152k3o+rOgdBYX4shYOJI3JTKHJHIkXm6CmqMpFjKlJIXypANpOhIJn1RWYEhAC1ABmMsgTSYhFYOKvOzrY6K1saENW3IjDqSqclrRm8sSWN8+q9VNLo5Zp6SA14bh280RMYaPL4jza7/Mc77NDK5onzPbbJT3sA6EOpFJAAsMEdGK5zBU4h8PoYLP4eAH0R7PjVgNn3ZbfV/0GXyXeuO2XyTE/KxHnIkvNdZrB7yvhb/Qlj7x6MG/1kyDD64XDc6LsDiWPvvhw3/vUr80e+OKsf//qIfuRtyIqjPabxE73JY8NDCBYPRV2uG4i7+mpr0lhDR8Jow+CCqx0DupGGodiRqsH4sTIwf06fcXJtn9W3dsDuW4eLanWnM7Acv2dJlYvSq92U3ogAucXDS9sRS4ChPTVpYOY08kKKZHUvhI7OYNfQQrK1PsCGzoWs687kaGxHt2dSNNpFNQqgHRRVZqGIYiuF1RhJW38Pqeqhnbv0rOqLZ9WgglUiO87CRUPLLDsZl/5DNIJEBIMXo1n5+2uL20xzsdm2QzZb+cyymdshm1kezAcz8uCr3LoWWSEujCyp3NKW950IU8tSXvJKtudwFUICqEoB5NJ00uR6WFtqJXWfG2xtggxJAshTWFVjJ9XORFLvtrJmv5O1m+NJnQdAv4DuLNvL6h2QKTtjSJMTTWH5eo7YG88RRSaOrLWDQSycsHsBRaE+KieeYvamsGG3nfW5JoopBBuXpdKSRpzAxjScxAxe3JHOi7syaVHXQl7WsZA3tC6k7W2ZXNCCwBCMVVeXylUIBkvA4G0A85mm1MBr9d7AmU775NudtsmPZEiux+mf6PIGRprSAh/VeQOv1XkCZ1s9gfdxAbwL2fEGAP1hjwX62ub7sNPiu9hv9X181OEfedkb8PXYfZ+fsvgDQ8kTXx1JmfzyePLE14eSJ64MJE18cswwcel4/PjnvzH7fMMp4x91J4+d6UqZONVtGn+1I2W8rTt+dKAtaex0a9J4w4DuatOh+VdOHoy5evxYzEjLUf1oz7G4sbqj8eMFh1Im8w6a/Vu7Unyr6iz+h2udgWXVTlpUYaPMcsQPVamcVeqA7HKwt8NFWa1eWgTP6kll20A6J70kaRrrECxGD0FmHF7I0S0LITWcFN1sp6g6HPvSJI44YKZwedYQpKItn0+aGhBPiwGgVrOyGWBuh8ubvcDWireSWfvviaz5dwSDMvFImPsgyqcB7GYuNlt5yMVmKxcXu1H5bbcJaPfjS25F+ndI5Ta2vE4iONkInodtmR66AWDecRfLC3mUBQ+AhcHAe83BVwzLS9pVzyexaj+Aux+AbrIGXwgv7wJUF6axdq+V1UXI74kj9YvyXm15DUQUa/dEAdALOLw6nqPqk0hXkEjRaBudZ6LoF+Moens8RefGsw5Sw3JA9HI6PVwIdi7x8rJ6MFNPFj8+sIjX9C6itT2LaHMvwNyxkIo6Mzin08t1OGFba920s85LkB9UAWAP1aUFhhq81NPu9L0BqfF+j3HiaJfT/0FjauDdBk/gHQSB73XYfO9BZnzS4Ql83uTyn0G7c52WyVNtCBTbHP4Pex3+T3/lCPjeziL+nSsQeMk0MXYieXLiaPzoZ73GifcRLL5/OsU/dsbkH7tg8o2dtvsmLlgDgeOJE590J4we7TaOn2yzTZxsNY+faE0Z7+iLvbp5YMHVvGPzrx49rBs9dSRurK8/caKgzzSZN2jy5R00+bb1m31r6syTj1SZ/Y80OQKrGu0ANcBb4aFM6bnqrextd9CjXW5e0eaSIJFNfamUcNjFxkNuTjqeygn9aRTTA+3ckUG6xnTW1ds5pt4OHZ3IESVmikSgHQEpGV7sorBKA2lrEMQ34ZwOazk44f9XU2BW/nsUK//dxOH/Cikpw3gyf0OeTJFnCa/B6kb2XfU3s+/cN9hA1nSWFURl5EKYWoAtLC1rP4cWzZYHYmUN4/1WVhansip/IakqMkklr4AoSwaI0e1kxzPAChCjK9odC3Y2kBrBmgZBW9geI2t2prBmt400hU7S1NoAaD2HlS1AUIgg8ABAXSUrEiVQ1H6kOUmsyzaxYZeREvbKZH0rtLKHF+dDJ+MCSS9ws6cikx9tW0yrBx/k9UOLKbfvQeroXcRl3VlU1p1OBR1eLoJ+Lmh0BScflQDUR6GtT9SnBg6DeYdbHP7hTqvv7U6Tr6vFGehpcAVk7Pk8ZMf73XbfF/0O/8SQwz+GYPHDXtPEux0WgNo6OYz6C+123zvDKROfvbmQ+P/3EPH/J434ojnAJxMnRoYTJr76lcE39lq83/dqim/iNQD5jGVy7JXkiUuv6iYuDcePHR1MHOvrTJk402kcv9iaMlHSaxjJH4wbOX4kbuSNQ3Gjp4bjxssGjRNl6BWqBi2+7fANkDerGqy+R2ut/kdrLf6lNdbAkjJXQG4ieest5Bqy0rIOFz8CPb201UGudisldXoAYg+bDnoA6jR2dGayofoBTmp7gPUIlPWVnuAywvpiO0fXujm6xEZRpRaOPGCniNrgpH/SNCLwG4wk5REAV2bY9QIfbwHMbyaBoe8LPsESvM1dDw+iaspmgi+0/Z2ghP3J+0qDmY2DZQLo0BK5sh3yTXewIjcZgSF0cwFkxXYbqV6UVxFDXpQAyLn48Xt06KqMpCkws/aAPLmtI20FfnyNlbWFJnlbK4dBXoSXpJA81RxRYZIxZoqoNHLE7iSK2gtA5xlYtyeR9TviSJdj4qRsEyXlW9lTYGVboZttlW4yNmfxor7F/HDvQ7yh/yHOG3yIqnoeosNdi6gH8qOmDTq6UfSzh6rA0gV1afQe/C0EiOcbU+kNgP00Aq13WhyBoVpXoKnN7j/b6BKp4f8E7H2px+H7asDhv9RnmfwaDP022Plwq9Xf1io3VgD4k+6A72R6IPBKZmDinNt/5U0XBV5N8fvOJPjGfh3n+/qgYeyTM0bfmDD4oHHsnYMJY5/+LtbnO6YfO3tYP/bqgGH06EDi6JnuhLGCnrjRusGE0TcHk8beGU4YO3sweeL4gGlyeMjsqxsw+zf0WP3rmi3+Fc1W/4oWk29Jk9m/pMocWFZpDyxtsgcWAdBLmq20qC6FXDVmMlXbEFc4yNThZlN3KhuHnWQ65mEHYosEBIy6tnTSV7jYUOEkfa2NYiq8rIMGj8Q5ikS8EglpF1GeQmEVOtZU6kl9MFyAPPXuxZegrd8yctibiaT6gyL4bGHQZYRDxp87p3Az00MWys+sm7ktNtt2KJ1eNz1/Y5MbKDLKcW0zaKKlZWro7hhoaciK/Q4EdxYAGewsU0F3x4CJob/2JLEmLw4BI7ZL5UU9BtYW2AFoCwCdwmEtZg4vT+GIfAOH74/n8AJ5pZqLdcU2js5N4aj9YAoEKNH5Zo7ZjgAxD1H8AVmOAGwEMDsaMtkFieHo/wEvG1hKq/qXcC7yZV0P8UDnYqprf4DrWjOprcHLVbVeakKg1AYwX6hL5/fA0Beb0umz5jT6rC018GWTJ3ARzHyu0+H/AoB+v8Hh+7DVOvlOl8331kFXYGTYGfD12nzvdoOhISXO95gmLrwMoL+eQfx7+EUw9MtpAd+gY/JSn2n8o67ksffbjeMA6ugbBxPHP8cFcak/efyT4biRT19aMPLOiZgrF4/Ejb55MG7k4gCCv56EsYqe+LG+wYSxjw8hkDyYPH4R7H9hwDx5qs88WQHJsbPd5l/XYvM/0WL1r+o0BVb0mnxre5L9qztS/I83QVPXmfyLmi2BLMQu6dDWrhoXyXJnjiYwczsu/q40yhxIp8XtaZTU4iE9Yh5dnYl09SkcU5vEUTUgkw4HR1TZOEKCw2o7hzckUZi8tk7eq9irZZUsO3EcAL6YRNp/Q4/7b3OmhvKmxp6nZtvJvI1rsuO7AfafaQJceZfHdFZ+9tobTEPb8keCRZlZJ0uC7b6blE8iCi5IRnBoA3gB1mwECXJ7Ox/6eRcALlNDd0eBpWNJi8BDi6AjTJa9bcWB65SDl0zheQhGSkwUWePkaAG1vIkqHwcZgI8uMnIM2huKcXJknbh8F9twEVkKPWwr95ClLoO8nQ/xY71LaGf/D6ik6yHqaF1MQy0P0DAYfLghg47WpvPh2jQaQhd7Fv5uZxaNtGTS19CRY13pFGhJp0+gqT9oTaUvoZm/aHP6P+6x+69AV38NzTl2FG26Xf4r7U7/25Af56BrPwDYPn7F5PvqOIB8DAw9kOq/0mOZuIhA71WA842DcWMfDelHPj2SMPH1EcPYl8OG0U+GEkbfG9KNNB1acGUY4L44GD/66uHYkcO9+rGq1qSxgX7j+Gf9xok3BpPGzx5OnjwzZJw4A9lRN5Qwvq4zceKxTrN/dafJ/0SPJbDmkNG/btAUWN9vCqzqNvlXdlsCj/VYaUm/lZb22SmrDcFil4czETzLcmaZ/emUftgLXZ0KGeIkQyvA3BLHMW2xHNUQR5F16CmbZcoCQA09HgFtHd4WT9pKnMPGWFZ1g5XPxpAKQNb+IYXUb86dmuB/FjiQp1BeRV5ueb+G7QF4CC9/YfvOzwz+Y2FiWfVIlumSFULlhopU7owl5Ytzp17XJkCX10pIwCijIvIu6r3Ib76dlc+DqZ8yI0gEK++FxABzy63tIFvvBTsXRHIYXFuewNpSI4XJAiplYOhmC4XjQIYDvBE1KRxZDc8HiMHQMfmJHHMAYEZwqMszk+EAGDrfSsZ9FjLuMkOCIN1t46R9LrLI0FXnIlrdt5g2Q3LktD7IPc0Lqa9hIZ+pW0hHazLoNAB9ojqNXq3O4E86HqCJ7gco0JpGl9oAWID540YvfdGUSp/3ptFEhysw1un0X4GO/vJwOvlOQh8fdAcmutyBz9oskxeHEifeH06a+Pxo4sRX3dbJz/qsEx/3m8Y/7TBPnAMzn2pLGjsMCXHxSOzYlZdiR78Yih95d8A4/lF/wtiFXsNow6BuZPhwzNUTB/Ujx3sSx472Lxjp60oeH+5NQn3K+BsHTRPvHEqeGDiUMtk3aJqogATZeDB2bFWffuLRrpTAyh5rYH2fNZDdbwls6LQihkgJrDiYEnjsoIkebk4hb4OVPNUOzqxycnq9k9IHnZBmdlo0bCdvi5UN7SCIXiMn9KWwoQXHud7FMU0ujm50clQjZEeTmyKaITvaEih8OJ7CTkEavppE4RfiOezEAlb/y70yFj01Z0Nuhcst8N8AB6cB6pPIHwMuRFPLwo3X1rmbDsRQPlQ+s+67tkPprHXBQlkgUYbk5OZJBb6QADZYHkaKfBv0soWUB0ys3KiYAv6P0ebA7aTcnsLKfamkfOEhVq7NAFMbESxCQ+9LYXWeHewMiZEHEO8BQ0OiaPcD3KU6DitOooiaBA5vMsj7TjiiMBn6GQxdmgxmxgHeZ2ZdjpH18rgVAJyQ52ZLnpcy98qSBLKSvhWAxknJFkAjOATYsxrSeTGAurYzi9e1PgDNnMkNNV4uqMrgnspMOlyZTkcB5tdqM/j1pkz6qjGdvurKIN8Qgjow9dfoir/qS6ORvlQaO+gm7oPMACOPdALQfe7ApTaH/9Mmh//Nfqvvk4Oxoxehf986oRv7atAw9v7Lxsmvj6dMXhlIHn+vI3FsuMswerxXP3LmaNzYx4PxI28NJ49/2Zc8/tnwgqvn4WfB3K8fih97/WTcxBdg5Pf6ksZf600ePwlQ9/Qljh0eNE0eH0qe6BgAOw8mjzcN68Y2DunG1wwZJjb0J/vWdZkDG7ssgW09Dv/2HntgbY/Fv3wAgO4zIig007ImJz0CUGch2MtssrG328lZw9DXhxy0qBe924CVbAcdbDvsIM8RG3ugrx2H0PMNu8mENOGoiwzHQCJw3UsgmAEzRRxCHHQwjNXVKlYN3E2q9vtY1Xc/mDucVAchRwZvI5XcFu+Ey02X4Dg18jLRX7S1gE0wNc3Fpm9P9+kW2p5ZP337m/LrCqaG76beOSjbwsybIDn2xrOyEAHg1gh5rGpq5dAepHvQDeUD0MWZrMx2kGoXuqLtYOo8K6nkbVZ5kCJFCA5fQLe1cz5SeF40gB3H4QgSw2W5rl16jshJAJihm4vAzHsTyQBWNgho9zjItsfOtj1g4wIXpRc42bHPya78VHnUCu4iz/5UXlKUSo9XeWllUyavbk3ntU1eWleZyjvL07mmxMs7KjO4qTKDTtSkUkVjGp1rSKO3W9Pp87ZU+hjpWGsqf4n8GLrjkW5X4EovwNwPb3H5P+60+b5AQPhFt9X3RZvV9wZ09IcHkyY+G4wbfXcobuw9SIrPh+NHPj2YOPZ5b8LoBwMxV891xI+2dSWOHu2NHz0PqfH5cePk2K8NE18fn3f5vVf0o18cM4x9cTR+7NNTseNfnoob/+KQceILAPrUUNzowKG40b6jhtELh5LG3xgGuI8uGMk5Fnl13XDC5Na+JN+69jjfYwOJk2sOpviyD1n8eX0m/0ZIjicgOZZ3W2l5s5OeaLbTwx0Wyuo2c9YAAH3QSt6jdk4/ZGfPkJ0yu13BuR+u+nRyVSNoxIVvgjRJwnHQd7gpptXDMa0mjq42cAR0dkQNZCG0dFi9ARIEPTDYW9tqJm0Hzm0feuH+KFIfvo3lTqJKHtWSmy+irf8FeXEZn5YnWf4FLnM/ZCk44Ctk12FwmovdqPzWTOSEsLQAG5vBnWUuh9wp3A/AFnlZ9TTy+fB9c0i5dwEr9yI4fB6gzraRai+YeacDbZys+nkCqZ+PI/ULiJB3xZN6p541P4Wufi6GNPv1rN0N2ZEdyxEF8YiqEzmq2E4xxdDPRW5KKEyFtEglW64dgaCRTOgdTAUW9oD5TXkpZMIFYyr08tKCNH7kgJeWAdCrZN2MWi+vAVg3o7tdXe7iVeVpvLM0jYvK0qmiKpVKqtN4uDod8sNLJ+s9dKbGS6fQ/pOGdGhqsDPkx0g7QN3jDIx02/yfdFr957pNALHV9/Fhq3+s2+b7fMDhHzlq9vleSfKNnUzyXRqIGzkLNh46FHP1nUH91Q/Avu8fWnD5/GDs1fODC66+djxhYuRMkj9wMS7Av40eu3JR7/MdW3D1g2Nxox8PJI6+cTxu9O2XdCPvn4C/pB/75GjC2AfHEsa+PBY/9t6J6CsNwwtGdx7RjW0+FDu+pj9ufEWX2bcB3y37oM2fM2QOrAczr22w07paF62HVNjS6qb1jWBogDqz2UquPjMZj5jJ8ZKVs/qsALSTl0Iru+og4SrT2VuBHq/czcYSB+nLnBTT4Ib8cJCuPZmiAdroJgfrms0U02hCuZ2jWzwc2WqniC4bhfdDYw+Fk/oYwCx3EWV6qUz4l8BQ5kELM0OGBFdQEiz932XBfyaSQ+SEPEYlgZ+UC0PLuHSOHgydSgj+plao3J4EqQGZ0fAgqw5AauTBZTHu7TZWFVsA/GRWb4A/D5beAXbOTiT1WkiRDbGs2a1jjbxubRcA/fwCjsi9nyP2xgDUCAhLnRQNFo4udJG+wMGGAymsKzSxPs8CQJsRCJoQECaTJd8CkMt6G9CGRV56DN9NwLu9OpXk4dWtYJ1N+LxVxW5eW5IOQGdwXXk65eEE9lSmUVuNhzrA1kPQ1Wea0untxgy61JdFvg7IixMPEPd6oZdt/g96Lb63+63+C712/xcDdv9IDwDdY5n8eDBl8rOXkiZGDqZMfNaZPHZhIG701YMxV18b1I+8eVw39sXhmCsfDetHPx+IxTaY+aKNAv+c7OffRU+MnZw/8unLhvGvjsaNfiYg/pcEf+B3kC7H48e+HDBNfA5mvgiG/vho7MgbJ2Ku9hzVje04FD3++JG4iSeGU3zb+y2TO7sdgexBR6Co3wbZYaeNTS7a2gIwt+O397h5S4eDH6uzs7fRLiMcbOp2kuewkx/u8CBo9PBjHam8FNJrUdVCykJP5qjysLE0lY1lXjKVpiPYtrK+1sIxLW6WERGjvPGry826TrB3fypH46KI6E8kTffdrJKxabCzrHUXHLqTVBhZ2LhtCkdi0wEdxNY1F7tRPmQ3qp81DTUI3uoWNpaA8DlcXcLWT93JCuhZZQFAmjuflVX4sjnwPbez4pkMUpY+BC21mFX1i1i1HmB+DnKjwczB29879KzepiMNNLOmKJbUObGkeR4sXZAAgJsobE8yRe6Io3AEneH7dRSxZz5H7k2gqEITmDqJonBRRJfYWAeQJhTZ2ZBrpiQA3Aad7kAbm8ywQ7mn2MNL4Y+UZPD6skzeBqbeXJ7KW0rcvA3MvaMsnUtQfrQ8g9oqvVRSIYBPo4YaBIh16fReYyaNDCBA/N0PmA8vosCJRdDUnsBYu83/ZYfd/0G3O/Bep6TmyYudlskLffrRo4cXXP0YgP2kLWX8YnPy+GG5IQI27gOAz3YmjZ0/IoCMG/vkuH7sK2mHAO+zI4kTXx/SjXxxJGH8i1/NG/nwRMzI++dixr46p58YOzdv5PPThsmRY8kTXx9OHHv9SMLYu0cMI+8dSRw9ddA4UXYoYXxHr9G3odPs34beIrvZHtiBwHB7tz2Q0+sOFDWnUi4C2m2QDFva3Lym1UaPNTgoC+zqabOzqxvauc9Dqzq9/Hg/0rYMWlKbQenVGeTFRe4BiF0l6WRDvGGsTGNLnZct9ansavdifw+Z2pxswMUQg94rphvBY7OHIhrNHNZyD6lO3AVpEQ5ZcQ8rLyrkjuHU8J2Aejq+Qv4jlP/g7lu+Pf59/Ru7bkOG8Lbex4pSkRJJpFyjZCXYWynv4sjBFSkrJUlAmA2WbhdfzMr96ayS93/shvQ4AFA/H8/qp8DEL0Iz70YwKC+HzzdSmLztNd/A2hcXcNgOBBs50M6oi8qB74H02KeH/EjmqEoHRRejGyyVUQxvkI0d+FyTLFReamNHObpNmcNQAtlRlkEroZdXg4U3lGdyXglkR6mXCorTeQCA7ilfyK9CQ/dVZVALTmIVmLkLYH67ITNwZeBB4iMA82/+ivmVhyjQCnYezqBAhysw0eLwf9Ts9F9stfrPdlgmL/Ynj719WD/y9rH5Vz+CzDjVbRg9A8nxQV/C6Bs9uqsdw/MvnxrUXT3emTDa1Z4w2tOWOHq2B3UdyaOvdhtGXj2ku/o+AP7x6TlXPvinOVcv/ZvOF/in+eNjZ6KufPmb2LGxV1ImRw6ljH142Dj+5UuJ458D0K8Np0wMHEwcb+k3+0pwYZW0OXxl7UgB6OwOc2DlgIvy2rzU1IDfCxBua/LwxiYPPYGL9/GqVF5Wn0aPQSuvh5xa2Z1KGzrgCIgfrUqnxRVplAUwpxdmcDp6Mbnz6qrKZG/1Qs7ERe/FReJpSGUbUmMrZEm/i/UnrBT9G8jEcwkUcQ6a+p/jWfMmeuB/R2/8pp5Ubyxg1b8B5P+shQRJZOVriaz450RWvZrEql+hZ39lLnR2DC6E+P8cGXLdh8pTKT8DmLORvhjNSnlLbGhIT2bdVaBOwAxQK7KtqH+IlG3XQF39ACvXy51DaOhCC4ANDf1LWW0fYN4fDWZG8FBioLDno1ibP5e1e6Jl1SSK2GfjaDB2zF4z6xAMyghHDICrA3ANhTZOyofWK/aSB2ybVWhlVwEi81InZx6wBF/xYMt3UaYsT1CaThsB6I2QHttK02hjYRrlFi+kuuKF3FeaycfB0qeqM3m4diGdrMmgU3UZ/GlzFo0NAcR9YOY+SA0w11cA+2cHs4hbPYGv6l2BtxpcgYvNDv/5Nrvvve7k8YsI9PoEvK3xozUtyWM9APPbPfqRgR6wdDuCQbD0UYB9eGjBlY5ew8jRzoSRc32GkcOD0Ze7DkVffvfM3KufXJw7PvK/Df7A7+MmJn43b/SzX0dd+eBk7Mg7APLHp4yTI2etft8rZt+Xw6aJj4bMk693WSab5MFcALqo1e4raHH6WyA7CvrstKXTQesQxO1sQPDb7KXcdg/vQIC7tk6et/TyipY0fhzB7nKA/pFONz88bKclPU5e3OrkJY0IqGvTeBkIwwsAO2rlhUaIXerT2IYLxNjqoqQ2D+QGJEcnpOAxC0edMVPYWR1pfxfN2l8hyO+5ixS/RY8twd75RFJeALD/Cb3y+RhSvzaflOcB5NfiSTEYxooTYPLzkax8OYqVAvDz6P3PJ39vYM9sf922bIQKgpJDXkQvi5c/BdDuRCrlMuohoJalCbLhB5CXNy7lLyZlB2RHB2SHSI/tHlJlQ3p0u1lVjnR9IquLwNSyRG5uImt3xLHm2QXI40DsTqTwbWaK2oeDBNcVQCvL64kBWv0BD7Syhy17HZyQZ+WEA9CC8gwhgsbFCA4d+dDSe03k2W9iR56ds4pTg2s3ryz38gYAe2tFJuWDeXKLM6miNAtSIzh0x4cB5GF0tcfrFtIXNVn8NdjoQzD3Jw2ZdKkFsgN1n9d7A582egIftaUGvmpx+99vcvvfa3b4PmiyTb7baJk4XZsynl2VOLa9NnFsR03SRH533MhQn+5qy9CCq8f7AOahmCunBhZc6RlYcLlpcP6luj79leGD0Vd6hu77uuhQ5OWhc/de+fRfo8bH3o0nfsPg8/127uX3z0Ze+eDsvJFPX0mY+PxIyuRXZ0y+sV+bAvySZXJs0Dz+bpt1sqfV5itotUzWNCHtsfsresyBnYNW2tHhFFDzxh43FfWkUt5AKmf3enlLt5fWdaXyGoB5RR+8E9IMWjprAIDuAJgRPGY1emlxYwYva8lAoJjKme1OSm91sRd16QC8o9FGxhY7J7Qjnhm0sf6gjSMPJrO2I4HVeSpWHp/DynMI/P8RWHgtmTX/BLD/E2KnlzUyZDf11Le80LNAEXy/TnA6xQBSmRv0agwpLqSw4p9MrDpnZKUAULAnd6IFc7J9LQ3lxULb0z1kwfx1lcLC8o9nlovL+wdDT7BIGwBbsTMeQWI6K5sehJYGsKuyWLUVQN4Mht4I3f1sMrQzfvBuaOkNSaTZiR/900jWbL8bHkFaCQp3I/jYYyPDHhMb9hnZgIAv4YCLTaVuaGcPWBrdXD7qi53QzB7yyIqiYGa5yeLNd/DiA05aIhoazLyuMp1yANwKSIw+MPJAyUKqKcukkqpMOgHwnqnJpNdq0+mttkU01rKIxyoz6U3UvQ2gf1i/kEbQPX9R7wm80+gOvNHqDnza5A58Ue/yv1ft8J9qsPvO1FknmirMEzvKjRPbauPHdjQkjuV2x16tGNBd7QIjdw0uuHLuUMzlky2JIxVNiaMljfqRNT2xI2XDcy/VDc69nP3Kff8xcFZ75f2L4eNjb0b7+aLON3FcN3Lxpeirr702b3zk1wvGPjusGzl93DD6/ispvq/7U8Y/6jONv9fqmjzb6PT1dNgmj3dafQ2QHBXd1kDegJUKWmy0tdFOmwDWnZATO3vTKPsgjgNAvakzldf1umnlgIdX9CAYlIDwoIeX97r5EQR5Szpw3LDPo11pvLTTS0vAwh5oZEe7k11NdvIAzJZ29JRtdtLXWDmqOonD5M5hRQppyuJYVXMXKU/fQcpz6M0BZNUFA5g6lpS/jZ16JbbceZZVS49BqtYAL8EFie6aWi9a5nus0FJw0GELNPULqJfH/GSdl9nwBxebrXy6f2PXbdzM5EvJlxDwy6Iyz91Jyufuh542To1Hl8rrwlysehqB4e44Vu/WI0BEUPiihtXbVQB2BGufn0dhxfNkHJoi84wcs9c6tdbzPqT5ZgDYFFyrQ59nAzsD3EUIUOR2d4ETQE+D7PBCfkBD70fAU+LkRYjQV5cJmNOpojqDOyqz+AK09FAFtqENG8DCTQD6cE0Gn6vL5I/rF/Kl6oX0MZj8PWjJj2rS6X0A+4t6sHWzK3C20U2vNqYGPmpJC3xd6w28X+Xyt9TbfXVVDl9TpW2ipso6WVBjmsxuThjb1qYf2dGrv1rTrb9aMLzgylnkzzSax1ta48dauuaPbOvSj+X0Q6IcnXup75W5l86eDbv0/qth//HR+fuvfvH7iPGRl+Ze/ujE/JFPfhsz9vXLcWOfy0MAR5LHPhm2THzcB83eZZ54t8vhu9htm+zoT5ls6DT58ltSfBt6Tf4NfbZAXpedtrdAdnS6aGdLKq1rX8g7e9J5S7ubHuty8LI+gLcjlSE3aGmXg5b1OmhpI5i62kOZVV5eXA8NDZnlgdtqvGyrQQDYIO9R9JIFeWOVnfWV8k50J0WXGzmiDoF9Ic5rEXRzXQqr6u5l5T4AVpavkBcOyVJwDWpStt9NynrEXj3y1AvqulH35N2sqEL6EkDeAd8LDG2/nRS5yMvLoeSNaiJxr0FtNrtlnM7WcHrZrB8kIyMyT1qmma7Hl95yB37UXQD0/axaM4/VG+A/vxtAxo/aDS+4l5An9bbbSXPgLgBbw2EvzKFwWYEf4I3OcbAuB5IjFxIEmtqwz05JB3BQiz1kg6yQlfi9Ben08AEPPbrfyenYx1Tq4qzyNFqHoHAzQJtfuZCHK7L4NTDySejCAngZQH6qNp1P12XQhcaF9BWA+1l9Jn0tzNy0kL5uyaSRhnTIDQ+dRwD0aoOHXoPseLPJ43+nwRM4D9kxVOfy9VTbJ1tq7BNtNTZfU7V5sqohaTy/3TCa32YYregwjBQAzHXdkB8tiaNdXXEjRwf00M6xI8cPz78y9Jt7vn71d9r/eOf3949d+cf7Ln984f7RL18Nv/zJmTmX3v9t1OiHb+j8geP60Y+OgZ1ft/gD560B33Gzz3fQ4vty0Dxxdih5om04cbymO2VyWwe0dK/ZvxUsva3TFcjtcNPmdg9tbMvgTf1ZtLU7k7eAbR/pgbwYdPGyQQelH3XwokPo0Q7aOb3NwaYqyLrKNEqHZvbUu8hW6yK5wWKp8LKxFvJChvIaoaHrUimh1k2GCjNHN5gosi6RIupiWbPjPlKXgqyq55K6MoxUBRGkKpEn+qNIVYHzn38vzvc9rGqTu4pqxFUqlAPoMjLScx9kaiypKt2s3GmBA0NCjqICrkHrRvadgJYGIZ9p08tntvtW/n+h23gIoJb1oXGVKfHlVOhGVM8DwOLQ5eotd5L6mftZ8/R9pEV3pNmsYk22nsMOpHAEdHR0rgss7QrO20ja7yJjAfRboQOBoYeMeQB0gSc4DzoLvgSa+mFZ9xns/Ji890TGoQHoOsiNEniRsHJ1OldUpVENwHxSbnlDI78Lf7Mukz5E+ilA/SUA/WUjNDSCoIs4gafAcp80pQbegNw41+ryv9qQ6j9R4wl0tUB2yNobTaaJngZo2arkiW2NiRO5bfFjJc36sYLumJH8Lt1Ids+CkVwEjF2diePv9saPnAOgzx2LGX3vN2H/8e6vIi9f/O2cq+//fs74BAB96XTEf7z6jxEjX/8uauSzf1ww8eVbyYHAP5kCgddTAvzPyYGJI6ZJ369SAoGhlInX+lImj/ZZJ890uvwN3c5AW6/d39TjCOT3OQM7uwDoLg+t7n+Athx8kLYfy+Inhuy8pCeFXIfMnDlko8VHbLTskC04p8Nb66CkMhvrqhDs1cidQidbanGM67ycVJXGFpTrqlI5qSaV9NUO0lXi3MgNlzqQTpWdIptsFFEbz+G1Bg6rT6Gw6gTSlsqLjRAMNoO9q6Gxi6CNW5yIrdBbF5oB4BRSNUGKtqDn7jSwugsSpQ49ez2wUj4F5JkuNlu5uNiNym9qt9RIzBJFCkckK354Z1BnKx8HoAFk1Q6k6EpUq29n1XN3sOoXCtZsM5D2uQWszU3gsD3JHC7PE+6zcvT+FIqB1NBBL8uEcz0khX6Pg/S7AfAcN1n2uBEo4uDvd3FmoYcfLkckDzA/gXQtNPdasPT2soXUBBlRAnlRUZ3GZeUZXFeWQTUIDNugmfsgLc6KboaWfq02k94Ha7/TlEbvN7voeEMqnWv10LttHvoEOvVoh81/st0VOA9gX2hz+t/ptvjeazdNHm4zT9Y1JU/mtCIobEsaz2lLGCsbjLla1Dfvau6hiMtFQ5GXW7rjRk93xY50dBlGT768YPT9Y/Ouvn006sqF30Rcfu/V8Kuf/eN9V74EoE+/Fj7yyRtzfYFzMeMj/45A8Q9m4vNmBIRJ45+/ZPWNncf2MbPviz6r753DNv9X3U7/251O3+F2t7+twx3o6koNDHSnB0qGMgPbDz1Iecd+QFW//gFln17Ea44hEDxm4aXHrbz4sIXT4d5WGzmq0fvVQdaVpnBMhZX0tXYy1rvZ0Ohhfa2Hk+SFoJAkhpo0pGnBt9tGoT5aXgpa6eTIWi9HlFopvDqZwutwDuUFRjXxrCkDUCsB3E7EUa12VoHp5SHq4JscqqxTC963RAPcGlZWAiNy40VeQDRzIaM/x24Z2bdkDlIoraxwQnpEQhMhiJQvK1pJtUPJqhfvZ/WuONLKy+Z3RgfX4tBuNVHYi3YKrtYPmRG5V97dnUhReebgEJ6uwES6/WCIQgcZ8h1kEu1c6OZF0NWPlrl5RUUqgOwJvsXq8VI30oVcBabOq8rgoqoMKoN3VGRyW2km5Qu4IT8OV2fQ4do0HoJ+PAcwn24DkJvdNFzv4VP1XnqnFSzd4QhcBKBP9zgD57td/vflucJ2h/9iuxWBmXmyqyPFV9aWMlHWZRzP6zGMFgzOHykbjLySNxBxObdn3tWy/vlX6zr0Vxt6dVcqXoq5+sbxOZfOHJ9z5dRv7r382an7Lr159v4rX/4m4spn5+8fvfJGlI//NcYfgPPbhgD/wUp82uyf+GcXBf63i/h31gD/Fn4S3uPwfdbsDbzZnuofbvEGetpSAyUd6bRjMIs2HvkBbXvprynnzA9p52+X8fZfP0TrTmXS8pdttOiwidP7TAArgu4aAbSNdDWQEjXoEesgOeodbGjycHSNnWMA6oRaBOM1aZQAeWaodiMoBJirnRxdaSR55bR2L5i2HEwMUGtKID32zidVoR4gXsCqxlhWNswHYOeQsjmMlE/cxcr1iqlH+qqD+nnqqXG5m5gLl4WMriHoz7bpgJ4J7tnys7W9PnWywhPGyggAORFfHj9EtXMOq/fETTHyLj1pdyNSfmYeaV/UsXa7EfIjibSbUP/iXArbF8URu3UIFuM5ugAsUpRC+hIZ1gODlLiDT6ukF3poUZGHlpSl8sOQHCtKPPxokYdXHfDSOgA3D5KjDmxcASaWgLAHfrgU8gOBYxUCoVx0qxUVaVRX56WaRi91QGYchtyoqfbwQLWLWppcNNTmoHNtAHOPW576DpxqdviHWpyBk20Of3BNjg7LZF+babyuJ3Gs4yB0cpd+tK573tUcsHN+V9TVHT2xI22dsSMNh/RXj8OPnoy81PRy5OUzvwr7+o1T93594fR9//He6fsvfXIq6srHv42d+PzX80c/RVD48W/iJz79VYrv86MpE++/bPJ9dSLZN/FqCvE/OoiP2vyBY07/V21e/wedbt8FXGBtja5ASX0qFXVm0Y6hH9DOQ8uo4aUfUsGpv6adpx7izUczeBUkx5I2I1uqk0gHHawDQ8dUIhgHaxrq0AMCsJYGF9vA1Hp5l4tIDgA4GscjWlgacUp0Kc5PSRJrC8wctt/G2nIra2pNpKqJA/uCrGpxvuVNtPKCoVfhMtNOHs2S5w1lFp68ElteyioTk/qRyisrBNTL75pVboiLTU9nq5u5fV1hyMVmKw+52Mz8H1MXy7u6FUb8iE13s2qvbupl83sQHcs8judjWL0VAP8ZgsWt97L6qWRWP2lmzS/hG+IB8rkclhNNEXuTKToP0qPASQlFFgSHNjICyI7CVE4/kMpZRam0pDidHy3O4DXFXn6sMI3XY3tjmZc2VKRTXpWHAFzKq/RyfrWXcxAA1dR4Kb/WzbmyHjSCoS4wUQXAUNHo4rx6K2+CliypcnEdmPp4q5tO9rgAakfgZKMjcLjNHjjd5fAfBVOfa3f4zrfbJ9/qtE1eaE+ZONtrGB3uiB/r6IgbrenVXc3rmw9Az7taMKQfPXFId/XCS9GXz5y+7+tTpyIuvXdkzqWjJ++/dOHXEZc/eHnu1Y+OLRh982js6FvH4sc+Pxg/en4ocXx4wDQ5AN18Zjhh7K0j8aMfHU+auDJsnPisL2X8SwB6ojvV/14nAlZcXA2N7kAZfldOSxbldyyhsoGlXHd4GWUfWco7jz3EWwcX8uomJ3uFmSvsFA2pEVNppWho6OhyABiyIKkSsUqDk00NNkqqN4O1bRRTaaeYWgtHiOSo9FKMSIx9ccHHsTTVNgovknfk4PxW4TxXwmu0rKydH3zddXDEQh66fhEAl0U85e6yLOQJUCs3wfegfAPAfA0zIRe7WZlYqE5stnbXFf4pLvat7SX4sj9ApLtdli5Ad5SL9Fkdq36KyPfnCkgR+C/nsupplO9Ip7Bt6Ry+w0MRu1IhOVI5ei+Cw0KLjD9DR0uQaKWEAxZOAkvbCtLIc8DLmQD14nwvLcP20iIvP1yQQRsKM3hLSSZtLk+jHAA5F1H7TpyQikpo6opU3omoOhuA3V7tBXu7eTsAvK3SwznIZ5c7Oa/CTV0VGXwerHSqKZXOI4A62uMKXISOPtXpCgx0OvyvQ8Ne6LFPftJlmTjXbZm40JU8frIzfnyoOXniaFfC2Om+uNGjg1FX6g5FXz0N5pYJS28cnnf59NGoy2dfir7y7kvzLr99av7I5yfnj3x2fN7lt47Pv/ra6fkjX5yOHb9y0CATncYGDsWPnzxoHP9g2DB64aXY0U8PxY++Phg/emYwZfzCoNP3fpfX/16H03+22RNoa0gN5Amg67OormURVfQ8SDkHH6JtBx/iDZAhazuyeHlzGi/GBe6ocoOh3axHUBdTZw2+zBTAZUO1DRIDkgMyI6nWTSZIEh0YOLokmrWin6GndeVTz4Bqi2V+jpHDiuLlji+pi+5lZc2dpGwEmFvtpCy6g5S/AGj/Dr78mkv+p/A9cABetR7pQ2DqR74N6pCLzVYuLnaj7W9sesVsqdh1O8xiwfpl+KIr8CN/iUBgM4KAvQmkqsD2M/jy8uq3h+4h5fMIHMC+2l0m0jy5gNU/jiX1OhysTSkcthGyZKeBwwvA0PuSWZfnZl1+BhuKMsgC4FrAwpYDqeTa7+H0/W7yoG7RvnRaBLmxCoBeX7iQc8syuQA6ugTyYn1lGm8uyuDc4nTKLgXIywXgXqpDeQ1kR0lJKhUgmKwoSqe2kjQ6hwDybZSfbXDT+R4Hnep20eGedDrXkxm4MODxv33Q7vti0DZ5qcc68TFAfXYgafxEq2G8pTlp4kRn4vj57sSx1/oNY+eHdCNnBuLH3uqOH33nYPSV1wHsswDl6ycWXH331Nwrn52eN3LplXlXvwbIP3l5/pX3TsaNfX7UMPb2YNL4haEE7J8w9vHhuNHPjulHPhlOGD03ZBo/1e/xDQxl+F/tywicbfEEOmqctK46lbYiCN5e/wDk0wOc3bGQ1nQvoscBZBl7frTZxYtavbyoET1bhQR80MENYN869H4AsLEGLI2LHE4y6qGrdZERvVgCLnZduZkjCnA+Kt0UXeWgyHJIDuhlzb5IVmffTaq9BlZBRqoPQE8XJLNqi5JUzwGwT8NXAbQAswr6WCUA34Q4quQ2UiMYlDdsyY0W5Y+DmGCFYCaIoL+AyQfN9JDdynbIgnUyLv2MhhW73awSQG+bA2Dfzort0EnyDOIGJYIAB6vAptp1iaSSCU8rbmP1uvtY84so0myOYe0WBIs7wAxbolmzKwJ6Tc/heSboaQ8lIRD0FGVR5oE0EjC7ZC5HQTplAdSP7E/jxyE7Vhdl8nawdFFpBueXZXBOmZfX4mLYUprJ2SiXwLAEDL4T+ju3KI0LcBF0oPxUyUK+ULqQ3ijJ4Atl6fxqvYdOtLjpRGcq9Qwvord/vYQmDmYGPj+U6v/iqNM/dtDtv9JpnXy3Eyzdljx+uC95/M3hlIkv+43jr/ckjp/tjR89DL/YnTD6+kDsyPlDuisn+xJGzx7Vj3780gKRGlc/eSV27Mox3dWPj8WNvH00fuzDAdPER4eTxz8HgD8+GnP1wyO6kXePxF69cDBx9LVej2/oULp/4NCiwBuHFwdO9qcF8uqdgTXQ/GsA6B2VCxE7ZNLGhkxa1QophthgWYOVs8osZGpzsKMlnbxVC9lRjlik3IbAz0UJVWBlANlQ5gFLA8RVlqCujqwHi1egTQV0tMjFAkiNAgTw2ZCPJToEgyCgCjOpq1JIXRPNqsIIUuWjbG8sq3HOVZs1MmTLKkgOgJiVks8FsMtxrmXUazMcZUp53/suAFumVQT9juv0dMiml31X3bcK/iyXNTxkrnShC7JCw8ofauU5RLnxMvWIV74JgUMmaUuzWCvg/xv8mCfw4x7Dj/1b+HMKVj8TxprNc0izEfp6092kzV0gQ3okC83Ik9+GA4jA852ckAPdl2tmmeifVZhOy/Iz+fGihbwB7LyjbCHnAdh5YOZs+AaRIwB1bnEW1ZQ+wG0lWdyBdh0IILsqFtIJbJ8pfYAAaH4d3++1sjQSQJ9tclJbq4cO9y2iz88uJd/pReTrSwt8NeT2X/qNmwKHrL7P20wTh9tTxo8OQiIMAtC95ol3uk0Tb0Bbn2pLGBvoTBg7IVp4KGH03cE4SJC4kYtDhpHzxw3jnx0HKw/JXOr40TcB5I8PGycvHU4a+2w4fvQNAP6tY7qRDw/pR94ZSBw/A918dDDT/wYA/eaxxXT82KJATlta8O1dmyCT8iq8tLnES+ur0nltnZfX1Tr5kXobp9da2YQ0qdnNnup0diCeMJU7ySBznnEx6EohLSrNHFlrRIrjW2LjKDC2DvpaV+GgmJ3xHFawgLV7Eci/kACGlbuDkI+lAG6lBiyN8/esAPMuVhWjt61JYnUTemAwuWo7WHw7zms+2jQAzAJoWbDzGbQXFkcqIFfWAgcy915A/YO7WREbe0Mp8l3+lzO5YyiL0ciEJhH8MmNPynfoSbEzgRS7BegZrIHuVcmdxcfgf4Mfg4tAiW5JCT0V7K6e1JL6F/NY87M7WPNcHIXtN3FUWQrp5PGsvCSK2m+h6GI763aaRV+z7UAmLS7I4EcPZPBjxZm8DkybW/QAFxVmURlAvbUwkwXMO0oyOQdgrQFoWwD44bKF1AM2bqsISg+uqsikwyg7AclxFuA4haDxXJOHLoDp3m1Ko4+gqT/s9gY+708nPp1G/LLd7ztsnvyy3TzZ1W6c6Os3Tbz3stUnUuSddujrzuSxvubk8a4e3UgTdPWJbgkck8eHu5LGTvcYx99B+08PmiY/P53sG3k5buyLownjXx9Onvh0OHHsjYMJYxcPxo+9f9Q4OfFS0sRHfdDnHR7/6/0ZgVf7F9H5gw/SwOGFVNSWytsrITugezdDI2+RN91WeXhlvYsfbUDa6OZHZKI/Aj2wNHlrU9lVm0lZdalsqUOMUpFI0eUyVddF+hLo6nIHx5SlyJJsFFmWQBFl0M+IgeSROU022LcikdWNRlLVx8Dv5uBENXngQ4K/YuSL7wNbGwDcOxltWF2PgL9lPqv6lazuApirg4Am1Ra034z0aaQyv74G3gQvgMsrAH+g/tMlyHR0hzxk0/MzbWbb4BMvwtB/f/vUmKI8aLsN+f0Acr4FwQJYG/pXs3VB8KaLYjFAL496PYcfAX2lzMMPfD6KVKsjSb36DlJvCSftzjgKf97GEcXJFIVuLyo3kSJ34wQgYNTtTiH9fgdZ8jMoHQy9FOBdCZ28umghbSx8gDYVLKTNyO+Ab4d23grwVoCJu4oXgqEXUgvAX1KWxjur0ji3Mo3yqrxUVIZgEQFQC4Kso7VeSI50+qIviwJtHvq02UlvtnsCH/emByaOemliwO77ZNA48UZvysRrHebJvoPOyY8PeX2Xuty+N5qsk6c6LBOvtljGTzabxweaU8YH2pLGu2T9ujbT+HCbZeJ0l2Xy7V7LxAdDKeMfHkke/+JE4oTv14bxsd/ox0deTpycGEoa/3wgZeILAP+jTotvuN0d6OpMC/R1LQxU9CykGuj6unYnbQF4t0I+bAZIs2udJO8ll7d8PQydvKTZzo+2gKkB+MVI5Ta3F/WmOgdY28kOMLOh1M0WCQwhIaLLzBRVYaTISitHFNo5DLIsStZbKUTwXoh0n+hlsHQVgF0N3VwpIIfkyJtDqjIE/PUGUlUC8BVqyIs7ESdBa4s8aQe7999L6ro7SCUA3grwC5jlHsUBOIAedLlrKHNCHgAuHod0TY9kxaPAShBgt2ghYE73kM1WJz7TFCorKf4W+kdm40UZpwAtg+WyPp4ws6xYutNISpkaKFJDIl9IDGUa2njwAwBs5S/vZtXPETAuRze07m4Afz7LbLywTWYK3xZP4VvjOPxZC4fvsHDEXnSPCBp1RXZy5KdxJuTGMujqRxDYrSjM4jUIDFeXptMTYOTtYOz80gzKLkmH/AAQwNBNKKuRJ1kQGJbUeigXQVB+nYtyGp1UVeehrlovn0DX/HprBn/dmUFjrW76oN0V+LjdQx8DSCM9Xvq82RN4rd06+UZv8sS7HaaJkwNu31uDWf4vOjL8nzbaJo83WifPVTsmj9bCa2y+o7XmyQ4wdkuzaaKi0ebrardPnu+zTHzUbx7/6mDKxNcHITUOx418/ivDpO+wfvTLnpSJd3qSJk53pkyeajP7G+TtW42pgdz2dMrr9NBGADSvzc6bG9ycDSbe2eLgdfC1dXZahcBvVWMKZTaZOKvdSo/UWdnb5OHFdR5eVO1hV4WbjdVpbKsBYx9Az1eq54jqRI6QB5erTByZj3S/lSILEaTvAavuhRTMV7O6YB6ri/SsboCs6DCyuhPeBx1dkYK8iVXVkCON9wPYt8tSvEHNrCq7E0w9n9WtcaRuw4VQB8Z+NsjOBC1Nqn1wgBkuKS4G4OEXcGH8AvgyxF/xuilMwabjbzoev8mHMt/Hxa7L32UhRVTCN1eS4imA++dqUiAKVuZYWLkbPzQbP1ieGJdFasShm+W94DK7Sikv55R5IAC4cj1+1IZw1qyfx9pn5iN4TCHNk/GkeRo67pcu0m414yCncBT0uL7Yxo4DHnIhIHTtl5OTypkyeQnsvLIok7YUZtBmMPE25AvA4GWQFDWQHmWF6ZxfjoAQoM2uTOWtVW6wnDP4ZtgcMHUP2PlUfSpdrEujz1vTaKTDRR92OOn9djd91pdOvnZIkBZ34EKDy/9Wo33y9VabLI7ue6PN4/ug0eF7rd7mO1NrnTxdY50cqgW4qx3YNk1WtJgnmlrNEzWtpomWdvPEiU7L5Oku2+Q7HfbJ9xBcftRvhH5OGB/pjx97r9M0ebrd7BsQdu7EBdFqC1Q0OQJbWlIDG+tdtKHZwZub7LSuMZXyWlxU1GGnra0O3tRiow21NlrVYKKljWZa0mLmR2ssCBJtwaXCPNXyUCzkWrmRdQCifrcBsmJucLHMsJJkDkcwGJ2fSGGFdoo6sIDUslC9LF6P4C6ohUsUrKqJYFVHPKkGTaRqRXBYhrQiEqC8B7r6dlbJyzqbcS5lfeke7N9wB0C+AIFhEqnqgIP6WHyWcgrQwsgyji1gLkN7cdHV0muvBz7k7qJg6jYHUue1/JSF8tP9L2j4Z6sBUpm8/fy9pNgVS8odMaz8pWZqHFJ08lQ0S8EXcqIrUS5FKmOS8jCBvKhTgsSfzGPVL3A1/3Iuq3+qYtWPcTCewlW/ARH2dgtriy0kL92MLDPIs4dsLHKQMd/Mxv0eCRDJW5DOi/dn0CP5YGho9rXQ0GsLF9I2BId5RVlcApbOLUmjreVeANhLG6vTgzdgiqq91IKyOnTfbQ1u6oD3tXjpg940Gut20+cA8/vNIj1S6RPUnWvwBM41uP0X6jz+N5rc/nd7PIGvu9z+DwHuV+udvrfgr9c6fGdrnJOnqpy+nnobAG2brGm1TVa1Wie62i2TQ+1W3wmA9aK88qLV4fu4yzr55eGUyYle04SsrXemXVY9dfjPtNv8Ha2OQEUrgAvf0eygLc1uKsNFltPi5oIuBxf1Oji3EyDvMdGKFhMva04BoMHOwWUNXLyuzsyLmozkrbSSpcjMCYVxHJ2rAxtb2ZCfAJkRz5FVVuhoY3CN6Kjd8Ry+TosgHQAWRpUbIzJiIS+1B3sGXzGSg0AwH+xbLg9ywPdDlhRHsboqGhJlAan2QGvnReCCuJ2UjXeRqgnSBESkkiUx9plJ9cJ9ElRO3ZgpAasXI8isR15eEWiFyzCv9PryYIB5AVja8Q1T39BCDWZD+Y3KbmTBfyxX1VMQ9fkuRL3p+MEu/PhIBIT4cjgoAC4p8AOCj3HhCg2OgMgtUVkvb9P9pHoK0fFGufePH7RWyuBP4gDKQS2dy9qGOIrYN48iymM4utjISUVONpaayQItaCv0cvpeLy3Ky6THDyzkVfj/yw9k0GMFQVDzFgA7Lz+TNgC4a2vTaWNtOm9tTKMyMLG8d6WgxUNlTQAKdHROkyv48vpT0NFf16fRRTDheQSJF1F/AWD6uNYZGKj1+A9XpwZea0Cw2OgJvNfs9L/V6vJ/2Jjq/6rR7f+0GmCvTPUdL3P7Wqpdvpomu68MoC5rtvqaGu3+pha7v63F7Zcx5VOdTv+bgzb/V0NW3xe9dt/bzd7A69Ds73QA0J1Wf1OXNVAFLX8SsucoLq4O6OeybieVdIOZAeL1A/BOK6/rcPAT7cm0rDGZFjeaaFmLnZajbHl9Aqc3JLOnJoUdpQ5y5RnZsFvHUejl9GUW0hUa5U29pEcacyCJo56NYO36O1j9pAJMiiDvOWjiF1UAp5JVooVbcT4aAEKcV1VuFKsOxIOdE1EngSOAjYtfU+Mg9f5kUj+nY9VWfE7woggjVQlAXSLTTJNIuc+A2Arl+/GZxbhAWnG+RYr+EC7SdBnSbvhuBSkM+usYelYLVU5vGMpPLxObvv2tvDyhICMdWxSseDEe+seNH+mA21m138XKPRo4vmzwqXG4rIu3Rxl8F4sSOkomdyu3oLvaiFSW9ZVJ4+jilBI07gIbvBAG/aWnsJp4iijCiSjRUXSxmRJyLZyUZ2ULZId3v5cWI31YRjwKM2lliZeWl6VNpeVptLosldaAlWUdixUNTlpZk0HbK9NpK1h6R00a59SkUg6kx/pGWefCElgJPV3W5KXzdV4Zxgu8hq79QpsbDO2h97H9ekOq/1xtauAs/I0mV+D9JnfgY5R/WOMNfFaXGni3NtX/eoXbP1zl8fdVpfrbal2+oWabr6bWEWiocwU6cAFUQT4U4QKpAXBflYlHXZAxrZAz6AneagWgu5yBC93WQFu3FYzsoLZ2F3W1O6gK2nhHj4U29dhoW5eR13Yn05puE23ssgHYNlrRZuUV7XZe1WXnlZ1WerTdxEvajZTeHM+OajN5y+3sKkwhwwEE2GUuNlWYWQf2TgBJxOyPoLCtd7BGpvyK1pWbIqEgTkY1ynGe9qD3rAbrdoDBywB0eW1fDmRIPQBdLAGkAZrbzpoqF2uqobmzDax6Br2tfMaLGkjQBFwA+IwKkNdaYENeLlWA/9OAVDDwAzikaXAU7N+BB4nHpCeXG3QySia9/bUVvsSm4zFo123cwL6zjXQPkgYf0QJgs6MRDHpxENysLHkQwWEawGwFQKGtt3pYuQ5lT6HuF3MQ3d4OQOPLXwO8ohRpETwbP/KAFt3UfaypNHBEeRxHFOgp+B5DYed9iNj3pbJj/0ICoNlV4KKsAictLnfTUkT0yxDxLytCvshLj5a4aDnY94kaJ61ucYKl3bSqwkvrZfy23MtbAdQt9U5aA1CvbUmmx9oAGLD2cL0butpFp1scdAagfkfuIramBb6WURBIjw+6oLOHMuhSozvwfp0AOjXwNkD+eo3Hf7LG7T9Zl+o/D2lytCbVfxyM3lEFZm0AoBscgTJ8bh5Yv6Pd4T/d7g68CTC/CqnzXls6fdnmpY/bUgPvQcOfBJCHWp3U0OWiqj475YGRN/WL22hHL8DdZ+OiLitv7bLQWmw/0WOnTV1WWgswr+y08TKA+9FmCy9qSCJHnd6fVJ8C6WEkY7mdLFVuMuG3m8otFF0WwWGV4ayFa0rvYXXe7VOgBuBUv4TLVGC5UQKAqV4EQFvgsqp/NVJZ4D7PQOrKBFLvjUMwCemIC0RT68S5M5KmUG684AKQILACoC6JRorzLsAVQIPEVEUgN+hoSNTgTTfFZiUrm+4HVuax8hkFKWVYWB7tuoWHAb5lM3e46QfIVSOLOQZX/b+dFDvnAszyaFYYK58FQz/9ECsPPIToF6l41WJ88YdkbQ9ctVm4+u2sfCEeV284QK0Gg9+DfbWk3HU/qQ/oSFOgw5WeyPL+j/AKC0eWWCA55DEtnIg86OdcL9jFzYZq6OkqC9nKk8lSYSZHuTM4M29RuYsehy8vA5irAVoESauQrq50AdAu2liZShsA3A11Tl4nb5mF/lwlgAbY6sCiHdVumTdBb7al0dctqfRBsyfwvgSM3Rn0NeTK5y3ewKco+6wJAAdDv1PnDXxS4w6cgh8GU79W5/WfqkoLHK9MDxyt8gRKal3+qjIHbauRZwKdBLYOnAQrn8LFMoxe4TWA+o3WVHobMuN8l5OGEPSVwfPBzkW9APGAnbIHrZQ7aKOCHgflgam391tpYw9kR6+DtvfakXfyerR9vN0C+ZFIjqZk8tYmk61aT/oqMzuqHJyO32trcCGfQFHV0RRecC9pK+eStmkePJo0A/AWFatBMkFArwLQhDXlbp/Md5detBgOZlWVAqx7wkne4qAqBahzAOpdMpcHbF1qggSxANhg73wJLNG+DmDdBnYWQMsggTzaJz29vEXtaYD3gBFS1YJ2caQsRztIWuUvbv9uIIca3GoaXCN62rK7YsEx6Gv52+RLPaeF7oE+KgQTQwYos9PAtPCqDES5D5Cq+UFStgefGkfEfG0phGYAPAdlG+DPQKrIQuo5CayWURK52tF1aSvNHFFhoai8BI4SrSc3XIrTKKHCwwawTEKz8//f3ptAt3VdWaIU9PT0DMMQBEEQCIIgCIIgCIKjaFpRFJdKcdSK4ziKonJULpfjdrlcjuNyOR5kRZEHWdZIcSbFeZ5nSqREjZbnIY7jOE5Sv3+vv2r936vXX/X7/+4auiuJLZzz93nAkyEI1OAhlR7OWod3eg8ggP322+fe++7lEBghUl1CRYj2y6oitLaykG49UEybKovproMldHt1AW0EE99dU0r31JRBU5dAhiBF3eYWaE5Ijq2tIbqjPZ/uBtj2NJRQL/S0LB82C8kBbUv/oXl1VLaw+HcAoeT/rmE1AF2qb2nx76pvpg/BxKcqcREcLot2Q3LMVpZFm6sQdFavlpWbov0A+p4Dq/lxWaJMAlHIm9mmm+k1gFn8PfhbAPO8vF9HCU0DzFUC5o4wPQpW3tFXRHsG4QD07mFceH35fH9/mO7pDdOWHgSLAPUjfQW8tUd0dIB07dwQ5HBLPpdJn3SjrKxUrK9UGq6P4OJ3k+NFjayHbyTLi6vYcmgFW+sQs/QC0D0gFWhb9QUBGkAlAb7MnHsEgBYJ8QTyYE7Z413FnVXdfSPADYlxSPbYwe/2lJfN2wHsPZmQID4yH0YguReauRvnCTODbfWYSXrBREbgvUxPgwz3IXiEfjd1on5mMZs2ryTTytiyB4lumJ5PbrwW1wEtqytJHnZZezyNLyVGpgfB0tuhq58NQvzLQjRFCCZkrbwy5G9hpX4dKQ0A9JMA+aMVSBFEypTDFwrYvBeB5WP5rO6SZ9jSY70csh/IXje+/CA5awOIzEPkqpS12cIkGw4Fmgo42JbPYdxWSxC5V+wrpvVg4624vd5WJQ5wA8x3Qj7cBbDeDyBvBVjubSyC9gRzQ4tuw617M7TovZAmsp1FMwBXBx9tLqVTYOgPAOQP29fQP7ZU0D8jAPoV9Pf7CBz/vnkN/Qba/D8eqKA39pdHpw+URGtiwKaqGgSg9aupobEk2oT331NZzvogDoA+j7bjDRX07pFylovkrfbV/FZHGU8D0Ofa4WDp/q4iqukuoB1g52cHcBEMFlPdYAFtH82nR0cK+KGBQnpkoIgeGCgGcxfRrt4ifqg/Qpu78um21nxa3xThW6Gty/G51x2B4zMWtYQpjLueH+6pyyHH4UyyygL1B8DShx24IzrBrEtYEwZuBPDkgVYws66npTcCoNbTx+ASB0l+vwAbmrohwGp3Xqz3YzvYensWmV/wkrkOd9z9GdDa0N5y/Bb4MzhXOgcEN88CN0/gLv9ihKRDQN+0ysBU3MVSlS+p/Ewuw97JdTKIIv4t/EPiMgNLLoT9i0mpXEWKzO34cRFuK2DxZ5But4h+1ufPmp4NIQIuY/ND0NfCBjJketCBLzqLrfVetoIBLDKRBjrZd6AEEXoBecDIgboIh+rCFOwBoOtCCICKaL1o6boI3VqPHxUB4SYA+Haw8K0A52aAUWTHxqZC1OXTxpYQbtEh2twOHSrdYwD0DjDzXoC1WTYcaimj40fAoPC/A4v+HwDfBw1IayERalcD2AB47Rr650O30L9HIHq+siTaADnTDO0ugzg1eK2G+qLoXrDyjppy7qwv52FZZ6++nM4cuZnm21bTa+1ldKoD0qO7hLo7i7kbded7S+l4L967v5QGAeTWQaRDZdQ5DHCPFtDeUUiPkQhthz8G3zEOXT0K6YH6+4cQFEJ63NaGC7mzkNbj4t2AoLICbF8iO2a15pK/zQFyyCdvDcihLpts+91sqQSYn1HArggORTfL0yatMVDLHVrWQNR7oQTMD8d+W33eswBegL//JlYHQ6SO5ZPetbcDbC2MvT8bbA3GrsyEnNRIATZ0dk5Yk8P03RvItFtW54L0lJ4wuesbbdL5YOST/KJdVgG7WvkSS5IgusntQ646AbK+2hLKAKzpObhchZJPdvlCnoaGfqGEtB/HB2NkLu0PZMQpG1e3nbXmbLbthzfmkgNa11tdwkGAJlC7moO4fQaPFFKkK0IV8ck5ZU0FVAa9WrQ/PxqujFAJgr414pAZm5vK6HZIkTW4CNbVB2ltdYhurS6kO3EhbKkroDsa8pEW0UNgtP0IzEahl99oLKd3G24mSA56D4x7Cix7rg4SoXI1jVbdTOfkafLDFfS3h4V5ITUAZL2f+/DN1HSoNLqzrjj6OCTRrtoy3o02GZ3sbyjjflwc4wDzOIDc211MNQgAK3ug3zvB1t2rabp/NR0dLKPW7lLaDUDXANDDQyVUN1JEnWNgbwH3cBHtHgOgAfLHJvPpvtFCuncIMgqsfQeCSVls5k6AemMn7l6tiC86I1TUi+CwJ5M8bRnkqA2RT7aqqAqQ7YCVzPjNNBkAEcDitwFLQyoCxPJbSd12vQyNizpIDgG2ijZFJiWBfdU96az255Pamy/D5WDlIGsvANCHwdy7IScP+ln9kaLPl9YXd9wPf0Bj03JIi3sB6kcWk+kp3OGlhwOQ0kEv4x0xhF1u+kFxN8zIp6q/pE3WUMCtR89Lt4osFrIT/8Qz/tiOWnIgImQZTNEXEHkQH3TfIrm9kLLDDtYFCz8DnS1L8v4I7PwkPtjOm1iRLS4QLUN7k/LjHASU6azs9rDWnsfmjhyy9ITY1YUgpi3Ajq4QexsRnR+JkOeIzO8t4QA0sq+miAP7CzgAUIZrAfD9xRyGjg4eLKUi6OiKylLaADBtaJQNLMPQkgFaU5VNZQ1hsHVQB3cFXndjcwjyBGwNHbsLt/1KgKAbgH4NAH4NjD0Pdj5fewu9V1VBr4Fpx/GandUVslMAvQMQ99eV0G7oZlnOVx40GAaom6tLaSckx676Un4SgeiTuLB2Nq5mWXSxFfq8Ge+zB4De1VWIALCEe7tW83TPajrTs5o7B1ZTk6xn11/G+wHu5uFSah8phPRAYAg9/QAY+dFxBIwA+M5x5AHue9G2FWx9O4C/ua+Qb4eUWt+BuxYAvrYrm33tXnL3ZJGnx0eebg+5QBR+fG/ufelkAWA1mRWH31Pv2ZA+Y1lTQ5aFE1koLkCWaaDS1fojpMLQMoNO5mwI6J8wk/oU4qBdIdJAAuaGm9ncUsxaHcBdCQZ/DHpb1hyfwmuOAcAl0t8cHxlcmQM9DRZ/GvV5GbG9MmUOULyXI9F1S668LhdAi6SQvOgcuS1IL8dzAOqT7tigiSxII91xuNL1Dyz9mLsW4woHCz8NjbRvLb6cL5NSJZvgfwm3qS9Bfqxm87MZ+HIA5h3Q3s/ILgKrWO1zsXlQ9mrJh9yAlpY99Q4XAch5iNDz2SE6uqaUA3vK2Lu3SJ9J5q7L40BVIQcqV3NInkmU9T0OlFAZziuHll7bWkgVTQEqqcsFo0OSHMmlssYgFdWHaQ3SW5u9VNaFQKu9mJ48EqYHcXwVJEtnC1hRnmqB9n2lfg39R4D1FMA6CzC/C5Z+raaCjx6qQAC4mqoqy2jXoTJ6HFq5CTLjoA7o1bSrYTU91LKaHm4s58fA+Adbbubx5jJuBzPv6iyiHd0lfLAHgAWge9tXU3v3aq7rW001faW8tw93jIFS6h0GqCFBKlHePVjIjw2E+f4hAXYR7wRL3z+FWADlTdDUd4wX0Z3DCHy7AeYeSI6+AiofCpB/0Ef+AQ95Oxzk6vaSANzTnE++phC5q5aT9cVFMo+ZdNkxgt9vLP47GuMK4iAufaKRTNw/AJffW2bQHUJejnvYqi8+pFbmIrgviA28NJew9uJyBPwA+xNgcry+8hVXDMjGMHehE/JmEWIxM5vyvWSqcMSmT8RZ2nAxI/18DVGw6Sk/bjmZ+kQj/c0hPWQXUpMMokgfs3zAuEs+5jcC3F5Sd2WR+sNlMhwe677Zg/o63JqaVkJDI3ruzWVzpcy7zWPLwQK2VxexQ6Y9QnbIXiG+6nL2HrxFf6A2ANkQaMgj7+FCmaxO7t1hkj3Dg4cKoa0jXAK9WNKZS5FG3H7BWJs686kCx5c3B6iiDvKjPZvW9ubSxp5cBJD5fPcR3MIbixHUFfF+6Os6/Cij9dDJ0M3HhZXB0qcOV/AbyL8Pnz98Cx8H4AerV/OO6jL9EbCHq0sBXoAPDF8FQD+MAO2BplJ6rLGE9zSVcndLKU1C1zYhIGzqKmPIC67rAnN3lFNdTzl3D5RxJ7T0MIBe1VvKBwHUg/3FtB++E4z7EALGx6GZ7x5DgAj58QDYehv09H2QI5sB/Dvhm8HY64fyqaQ7mwJjOeQf8bFvGPnBLPL1p5MLAPf05VOgAwF2cyF7oKUtTwPUwtAy1VN2wzLGC8TjoDZJYIeAEIBmJSZNZG9DVmqXsnpwcWxb7OfxGi86SK3KZzNc2wd2fnoJ2D0X5zlYyQVo41C6aN+yoa74k3oZjY4T6WV2Pei+0jEX23ClmXbmkfI93CJigI6lj8JlFEhGCH+0VG5Vn3wpz0CGHMwj8/dVvU9SbinKs5AeMnOr+gaAHEGkBBnPI23BFwH2sO4Ls62mkBz1BUiL2Qnd6+4oZndtKTtbVrMfgAkdLIUEyWdPtTyyH2Hf4QL2HSzSV6sPHyjgUEMIEX4uBVrzaU1nAW0AuMsbsqikMYvK6wJ0a0cebWoL0LpeH2/A7fh2MPQjCB53NRTRduj03U0AXlM5j9feTO01YFew73RdBb9Tcwv/qmoNvVZ7C/cC2L215dzceDO3Qys/W49ArbGEHm9YzVVgqIdbI7StKUx3NRXzjuYyasdF0txeSp0d0Mtg5OOdpdzbsZorRTcDwN19AHoPjhHmBpPvBds+CSA/DobeA8ZuAmCr+gppOwD8JPT1o0MANRj6nuEC2tIH6dEPdob8WD+QQf5mMHJPBrkG3OQeA6hHBNSZ5OuGDBnMZ38fZFtXKftx1/AcTmfLcybWIBdVWa8O5KRPX4gBWlg4Np1BekJkx1nESEqLSsoQ2vogF1sc0M1WAFmGvl2kVeWK3ICDtbfjd38csuL7kJ0yOLdJSQ3WazE50Tg5Vf66XPTy31jYdB/+IXnkSp4Zk/F4mbT9eBariJ7V5zJJfUaGRjNxu/Gx+UUZLUKQsNPDqixQ87yVlAYbq7UAtKx9JvNDZD29J1ZCW+P8QyVkbi/S9zi0NGWyuTUg28WRvamQHa1F7ALjeaGlfVU3s2d/GQX2h9h9ALfOOmhtWY+iUuRHmIIIUHxgfs8RH/kgM8pbc6i8M4vKOiExmvNoPUC+GSy9rgV1XT7aiHRTcwGACBaE/n2gFqBuLJL5HzwMudBdu5rqoA3PH7mZ32qs4PcabuHphgo6CpA3QGs3gI13I93TUErPtsjwOkCNQPO+FoC5BTq3FfIA+rmhE2AGUFvBzucA6nkEg/MAb1NPEe2F1BiGDKmExn4c/hg+70PthfwwtPZ9vZAogyV0UOQHQPssAsHtgxF+sDdCdwHU94wW0z1jYboD+S3DQSobckNS3IQYxEXO4QCYOo9C0wgOR3MoMAANPYqLH68VGCxin+xEiztGoMWHQHw5m4+rMolIn8OuT1tIICc97YZX2SA/QmzZZyKlFXICbdJHjXYwscbqIciOPWDo/WHSDpSw+uc3ypxoBIBwwY3I2cRej2v0lJXJLpaqXlxMz8uVJXJD9mMpXRH7R2Tk54cZkB9+Uu9HpIpI2PSsCSy9nNQXVuBDAeSVWaQ9uwIfVKTFMgAWH3YPjsOXor/WI2YyPR1i5cWbUZ+PW5aPtGYfQJ1D5r24EJr9ZG3MZcdAgBxdBezB7dsNveqGZnYfFC2dx66qAIDrZ1dDDnnrc9nb5idv7SrydMBbM8nfmE1F3Tm0ZiCHKrpDtK4tn25HQLi5toBua8mjW5uyoK2DdHdLIT0oCyPWFdHddcX0UDMCt3oAs241C+MeBMO2Ip0EG883VPB00y10vhlgRxBZ1wQdjQCwCkDeCwZ+tqWMdiPAfKC7gB4WgELPPtRfRkcR8E2DaacHyukD6OXXesHUfWDuXsiKPrAvgsW9HWBkMPuD8IfaCvm+dgSu/fn0ECSGBIbbByE3wMoPjsaGwTf24zOMhGjDGHwKccEI4oVhD4W6rAB0BnnGghTuC1JoMkj+yTzyjeWzdziP3LgovHhP3xDucvg/grIjbXeAbIMusrSuAOMuig22SK9GjKVZATmp1dDJTQDzQRDXi16wsANyA4HhbjDz07gby6y9HWDmvbLs8mrSDkJL/zgndleXsQvBjmhkwY+Br2v02J8FLLltwWOlh+NPFpHpBwgG/6oIUSuuuB8WkrqziNWqMNh5cWzFUplQ8hfQx99VpLM8Jkf+rcLK4WW4egFk+XJEH8lrSnC5AwHCi6shP0oRGCJ4fHY1WHoVKfVmBCYeNr8AOSJzPEa8ZJ/JYVdXiJ21JeSoLWM3mNDbGCJnq4ccdW5yNLjB4B52d7jJ1ekkR6ODXbVWdtVYydPopbAEhu2+aBjAXdeaS7dBbmzs9tO6rgDdgbo7GwvpAbD/440RgLmI76kp4vsA5t015bQLAd722jLaXldKO8DCrWDmM3UVNNpQTq2tAG5TGe9uLKMdAHFVWwnVQEbUdYNNuyP0iEwuAgPvBxvu7C+lfoC4FwAehs8OlNE5gHwS9VWQC48CzNvbwLw9xbS7M0KP9hTykz0Ruh96d+sALoiRQtoJEG8dzqctYOMtE2DknhCV9eZQaMRHkTEfhadzKDKeS0WdiB9a08nT4yUvAB0ahIMUAjP55D0aJs8QiGAkyM7+Ivb0RcjVl8f2LtwB21ZDhuSToxvBec0S0irBvDJnWuZZCJj1rSpCbD6QAWmB37M2QtpfQxv/CYgMcY15dxiyBYH+I8tZeRJk9wIC/+e8rNyDO/JWyNGn8LvLXflHcRwkmb6cs+hnUQBGXTzVTQqGiyXnk1Mjf4k9HQSLFpPydFnsTVY72FQQY2mxVKlpI7R2i5uUo3ZoLly1zyGSjXeY66J/lw+3oHW4rZXoD9YqewpY3wT0r01kOuZk9aibzXBr+02k9S8jy4SXHWBWx5ECeCl7Gor1nVLtVW62HF7FlmYHZMpy3DJdZO+0sq3ZyvY6ALrSDrbOoFCTPxqE7Ag3h+m2+gJ9cGUTmG8LGPtOSIt7wNZ3Iurf0gDwQHbc21RMj9Tgdg4Qb60roXtrSugegPtJATY080HIkKrGCtrdUk47EejtaC/j/R2lVAlZUddWxjXQwXsB5GYEc3sA3jrc1hv6oJN7VoOJwfQI/OYHV9OZftT3ltCOgUIwOVgXjP5AbzHvQV3lQBHvAtAf7y3gh/sL9V6Nh0cA5EGw8hCYGDJiHYLa8ADkxNEABU9kU/ilnGhgJkCRUXhnHgVHPOSeEXYO6KD3j+IONhhk90QeOYb8bO3KYSvubtaWVWw+sgTBeBZb6hGzQPM76oNkrZGh7CwE6jlwP0jGjruuBO4I8GsA3rpytlXmkPp4Hqv7i0FwQkr4LV9EWbYu2Z3Lyl/dxPpDIY/jd5c7vRCf9I4ZWBFMxE3vspO2hIE8w/S8UXk9LpYqb5TFFirrfi6TTdMyz2M5me5SESmbYv+cfCB5wPZ5fMiDX8EV/CVWHs1j5a8tkB6xZxVNxavY9AJA3QaWnoGGnlzJlkErW8fcYJACcnaIji4E+wbYfsjJ1mq0DwPEhxxkbcgEiB1kO3Qj2/bdwNamleR8cRk5nnGQe68n6gVTB2sLaRPAuxGS4472PNrQUQAtDcA2BmlLc4A2NwV4I3TvNkiPx2sA8voI3Yag886qCN0BUG89XELbqsro/vpyfrT+Zn4Aunp7aykkQinvaC3hnQj6dgHg2zuhe9uL+aGBYnoyDuBK6ONHke5AAFiDdBQMPQh2ln7nnYPQyANg5EGZmISLAOcdRH1VNwJEsP1OBHCPIhDchvY7wdK3DYRozaAEu34KTEITnwewX8+m4Ete8p/MigbncqnkOKTGdJjKpsHi07lgaLD1iJ88fbjb9eewvSNH3xzVUrUSAE1nfVCrfjHupoh/DuazpXo1AvIytlYWkuVAAJoYEqOmhC0IjO1wW93NZN2TR9oL+aQ9FyJ9Ts6LxZCX0NdP4ncWtn0Av/1fLmHT3yCVATd5QOSv4fE7teGGGfnktsT8FS3xoGs6IcEuO/5lAHjWR6b5TFLmbbIBI+nrnEmbjCqKDHlWwPxlVp6GtBCAy1RB/eS4CaBHwdTDq8g0A9lxNIMto3ayDvnY0VlArtYwO9p9bDniZHP9UrYMgI17weQAs22fB2yxkmz9drLXO9h+wMK2XQ6y73azez9+0MN+CgG4a+Fl9X4KI+Asg06W0cJbG/y0Fnp9fTPkSHsR3Q/9+0h1Id2BC+AOyJE7a0O0EYx9e12YNsgw+pEiMHohbWwqorsgE+7tAui6SmlX+2raC3Z9tLuQ7sctfHtfCe3qLaUH0f5oRwk9BJbeibq9AyXcANDuHQarQ47sHpJ8IT86UsR7ewvp0X4EfNDLD/cW8WMIAh8AkB9EwLe5vwDMnE9rwdBlfWDdnmzyzmaS93yQIi/lUfisn/yns+EA8xlIjONhqpjCOdN5VDaRQ/6BbPKMZLOzF3e39jC7juSyrSoTIMUdscYNuQBpeFDFb7SKVXyfGohDq0GABxlmxgVtgRQzN4KZ8f2pRwBc0cR58G8uZWWzC3fcXAR+KumT2STwi/+sF03iJhm/ECzEqz61Xe0FPssbmM4CwGcB1lnIjDPxf1b2gZbJ27nQyjLy82g65MVaVh6zyYSXix/okvTVAJteDrDam8FqF6LnsVWkDaSzpQeBYW8eO8a8bJtaTpYpO5m7bGTpdZLtAABfuYKs+8DYh1eytduDW2gWO6HFLU8tY+vzAfY9k0WevZAdh7LA0iEK1eCHrwOQcVtdX5sFgHupqM5DJc1ZVN5SQHcdkXnTAHJVHi6APL6tJp82IRiqOBSmdY0IJhtRXxuk8uoArQHb39lawNs6EVB2gp37wc49uCgkyBRdPAhJ0lXCj4B5Hx8o5l1DJfTsSBHtB+PuB6AH4a3IbwdLPzRYxI9AftwziLtEf5juAoBFP28bQ4AIybFpMISgL5+KEACW9UBCjGSRdzqd3C/5KPRKiIreQtD3WijqeyVMoZfwGU8FKHwin8qnC2jtBM4ZB6iHvOQexvfWCQ3dGWJHUzZZ64JswWc0HwyCaaGH9wDUOxEPPbmElQOQjdVuVprwmzTJb7hM76GSBzRMVXBZHekW+Gonm7TwJ0AVNo5nr9WuenwiWBIPNsrJ7cllseQ2sUvaJ6CnIQ2UueWk9MQ+hN4mO/dPQzvLk75PQVI8XMrK982XTd42vZbDpjdyWX01SOqriI5PZrHpOBhgbAWrOxCUHM6QJ5DJMggJMmoD2G9k5TULqyduZPX5G1jbqwHIAPTzbrIelLm+6ZAnQXa14wI46GfXs1ns2eUh1xPp5NrjI+/hYioCUMNHEEjV5nDZYQSMR6A/D3kpUu2n8uZ8SJIi3lJdxALo9UdyaD3Av6kOurUedWDsTdDia5qhYZsitAEafFtzBHWQIx3FtAUMe2cHpEEHAAlgPwBA7wKYd4zKoEgR7YTL0PWuQTA1JEcdtHR7bwnvhj8+UMoPDhfTvb1h3jQUJvEtw5BFk7g4xvPptkEEgKMA8jEw8mgelUBKhM9kUeCUVwdw6HUA+tUQ+c7ioj0LKTIH/TwPjX0MenoiQhXQ0uGpAPkn3OTsTUe8AelxJIOsh6CdGwqgnxHYVUJySM/GQbjM4RDwypwOmVoqXXl/BZfhb6mT7SdkBFFALb9lgosl1yW6WKp6cbGU9akqxcVS1YuLJeeN9LK6MUiEfkSv9biNyPa5sivSSfgcPqBsBSZ18hyZjCJKwCe6Sk48DzZ/A5rrlSCrryN4eMWP431sOpMHueJHUIjIeReO3bIYX1w2q7V2BJj4oo8uZfX8StbO4xYni2rvBeD3LSIzghrLvuVs2bOYLa3ZiNJL2TWwWg8eIw1Bcj8HKbLTQrbdmeQ6GCJPTQ55jwDI9WEqgUYuqQbr7cuisHijn8saC1jmeIjU2NQS5m2tubSxFaBukz5myI22EuhvAXGEtrUW0pa2CG1uLYO0gBzpLkKwWUC3I6B7UHovwMg7JwDIkQjAXUhPjhXQY8MAM5i5AaCWORtN0NN7obV39uLigNS4c1j6lwtoA8C8HkDcOlXI9+D89X1+Do0BlAj8gmDc8FwelZ/2U8lZBLynAGxIDg/cdzoPIA+S/wSOOYZjJyFDEDT6IT2KpgTkCBb78H30ZZKtK49s9RmIRyJsrYdGrgGoqyA5ZKEZmWEnc6EF4DKf40W4zI/+S9TJ1NLHkMoTKDKfR0fE5ThZqJxcJ5Yqn1i+pPLzcMOMvGnSRqZesO8xCysTi0iRbb2kj/k5uESrf42yMVwqAyjNKL+UxeoZSBORJBMoDyE97mRl3gvWBTNPQ5vNov7fmskUwXkRaLI9YPozGaRBm2tjYOlh3BbH0fYWXrsujbRGM5lftJK10cHWo8XkOlrBrqEK8o3fTL6pNezuv5mcrUXkbIiQ+4jMq45wqCvC4abVXCYTnA4UUBkCn1B1NhdVeTgCxioD2Lc0hWkrgsR72/KgpUOQGsWyFBdta1hNd7cX07b2EnqgE4CTFGDfAq0rgdu2AZw7FKF7kd7fHaH7BvIBcKSQFQ/2FcNjczWqekroMbC09Ig82lXM93SW0F1duCD6Q3QHztkwAg08WUB3ANR3DiEIlD7mSejhSQSDx6GR53Op7DjqzoGdT4K557LIP5tNPgSF/rkccs/lU2BaGDmTPMcgu45lknsql3yQLMFhgFoC6k4fORA/2Bry2V4lgyVuMu+G1NttJlUYWZbPlV2GH0cqczpk8tlTcJnAJE/2y/TQ2xH4FXuuyNJiiW3JLrZQ+aJdUljAruWYlCagE1DJ8Kds7SXzOWQ06DQczKyPBB1AHl+APp10EH46nbVzAOQrqBM2nwO7z3tY3310fCkpcu67OK4LLotlf3kZmR5RcN5NkCNWVidXkNaHC2cYkuP8IlZ/7mZ1GsFLbzabwZL26TK2n/4Ku+b/mN1Tf4wfdwN5j3+VfMf/GD/sH3Fg/MsUHP0SRwZv5khPGUVaS6ioHkBG0BiszOVQfQ6VHPRy8EAW6gGohgjfdSTMW1sBWmHilhK6A0HjRuTvhLyQWXoPA4SPAYT3QmrcBzDfL7oXvrlPdHA+beuTORio64FUQXB4V3cJ3asHiyW0Gxr7sV7obKnD621G+93dpXQPZMd9E2HaCFmxZqyA141HaE2vl7yTORQB0wbH3OSd80J6ANgAcPgUpMd5gPm4MDEAPA2Wns4m52wejskjD74320w6OabdZB93kQ3a29mTTz5odV9nHnkhoVxNQbIj4LNWFbC5OovU6gJS6/yQGTeR8ugSVvfcwOqhG0k9hN/vhSX6dFPTWrisfvQtEwDt/GQ+xhdlxhtImvhmRjm5zrArtek2gg8xs5hM0/hgnchLj8VRAFsAiVRnZnkOsQWp7Fkn7S+tRDsAKKDHl2M65mHlFNj6NIIO2dl/Bh5/+TQJJuViOIBgc9QJ5ldZpIwyCeYYvoHUKUiPOS8ujhBpU8VsObqGrOduJdvULWClW8kz81UOnt3IvpNf5dC52zhyeiNHTnyNik5+lYrmv0qRY1/h0PEvc6AnQqF6MBuCOy8YOljr58hepIdxC6/KofJ6SA6ZatocpLJ2kRUltAXAuwug3gIwb4P0eAD5bd3F/GB7KT0EcN4HWXFvFwDdXkibAeJ7Afa7u3FxCKDlHEiSbTjmoe5CfrAHQSAY+yFIja0IBmU+xrZeABqaezMCwXV9AOtQHlWMQSoMZFNgBn4cccA42Pm09GIEcLGCfaGh/a8C8JAevjkwNCSJZzoTgIaeng2QC4GjrdtB1nEP2/WRQATVALUNAaarH/FGdw67OkPkaoWmbs0ha/UqNtdaSN/tChe4sn8p8ijLA831JlIfXizPGxJITPYn1KdAGL/dZVi5DrvquXKA4WKJ5U/tYGSASwAsGy/G5kxLtAtQgqnJdAp5YWTZhlmkhlzJ53LZ9LKP1Q4A9GQ2K3B1NgMXA6TESzg26T3SjuGcH+Kqhz7TA0t5XdwFTLsXs4LbpDaqkHo2wtrLFWyevpUsL20g6+v/hu2TYOa5r7L/1Nc49PImDr7+LQq/vZXCr32HI29s5cjbmyn88u0UOb2Jw7O3UgjaNdLlI18jAFGXTRGdoaGnq3KpqAHs2Byi9e0BKuuAToV8uLUrROUA53oEfOvB0nfIpCekt3UByJ2leu/GfaKdIUNEOmzthCxpA5Bx3HqAeQvYd8tgMT0wpA+L8z29BWDjQnoAuvkuBICbe0O0rjef1vYU0JoBvFefj8LDfiqbyKUI9G9kVgZHVpF7EukZaOPzkA/zkBFzLvK8nEU+uPeUn7wzGeSCfJOtjv3HAdqZHHKOOMk6AHYehW7uQPDc4iDLiIetvTlsbctlG/5HW3sx29vzECTexNqhlWw+5GRtP77rAytYaXBDL8vWfgDyA3B5TOs1+JvwJxDsx2fMpXKxa6kzXGyh+k8yn9Vk/wwJ9gTIffB4tW4C4Fl4QndcmgyifPkmMv0pAIlART2dTcopgPpEJqsTkBPyevFDL7Mt0GRfcpPO8LJ1c3389f8M2vmZJazMuFg7GYLsWMvaO19jy9ub2PbyRnKc/Sa7zmziwJlNFH5zC4V/+l2KvPEnVPL6XVz2yrep/I1vc9nLd1AZ2Ltk/isAyi1U3pVPoRoXefdlkv8Q8i9Ahsg2GbXFVFGDAKwKt/J6MGVvGLf9IN3al0+39QLsOG9jX64+BbWsG2Uw72YA+h6wtYz2PdwNWQJWvw3AvhXAvh1tm/oKaB2CvduGCmlrb4S3QJbcPhqmO3tCtLY7xGsGc7mi10/BnhwK9OdRqBMXFlg6MhakognUzQUpPABAz2Ug+AtEg+ezyXMagD7hJs88AH3GA0DnkP+0neynnWSHvvYcyyfn0SA7xqGTR3LZ0SlsfSObO5expX8Jm/dokBb4PjszydwbgHQDoJsiJM8bai8C2PvRtkfBnVVjtQqxzt5F8lgWK3LnRfyjCBZkwpqpUEC98G96DXZd536qN/oBwCT/uDBmL9JRuIAr3nzRJNgTwMWLl9gUgrqXoHHnAeo5nwSG1/6ht+BikFTG9rcsZpM9l0xNqJu6AUFkJhg6n9S31rF25o/Y/pPNbH93C7lev4P9L3+Ti97fxgA0l7z3XSp//26uePc7vPa9b9Oan97J5W9spLKX/ggSZB2FRovJU5VBjr3SpSdTT4upqKZQVvGkimov+fZmoh5AAitXSD/wICQANPGmvhACvRxaK6ADIMv7AxQBq65H8LcJzHs72jdCU28cLqINvdJDUUgbR2QiPqTFmPSY5NE6BH+3g5lvawxyUUMWhzu8HOr2UKgD79nuJ/9wiMJTISo7Fqaio5ASCOhCMwDt2Yyo9ywAfCo76jyXTa7zkBrnfeRGYKhLkOMA87FlZJt3Ru3HMsg6eRNbJsC4PcvZ3OdkczPiD3mCu34RaQfTSMX3q/4I+ngP7n79WaTV+9nSiACxfSVre1XW9i1nTZYHk94q6f2QrjzpqpO4SVab/TbuptkSFH42QF/V5MVTuViq+stc9NE+/MM9SON1Yonpgi7gn1kEeZIBnZxN6qBV/7CJLpZcl9K/AcbeCB1tlI/jtQdQN53O6jAulvPr2PILAPq9b7P73DcA5D/h0If3cOTDP6Oy9++mtR/czet+fhfd+v5WWvv2t6jijdup5LXbKHJuAwVnb0EQFSF3B4Ik6Ejpnw605eusW9S6krx1CMCgoYtac6lkGIw8lAs2jtCdA8LOWQAf5EprHgCPgGwA8qQ3QBVg8HXDkCXyAOsQGL0vSGsGA7RmCIAfCdMGsGVFezaVoG3jYAGVdeZxuNUHZsZ797kp0JuJ/wVMOxkCgENUNAsZJAw8BjkxC6Y+DbCfAYCP+8mFQNAFcLtmoKHPiK8gx8xKBIEA9EkEgceyyTYnI65W1iYQfww7wbYA6gtgWvy2qmwjIYsoPgRg/xBA3WNGGb/ZkSK2tMDrHawd0Fg7DPC345i/gCM+kmW8lI34HWTR+/Uam/7oBjL5cVdN/N2uw8VS1YuL6WlyxbW62LXUiyWWL/oMPuQofA6661jmZUA2XCxVfaKLmW6LLZJ9icsAjWjsbisrLxez+sFmsv5kK7t/to19L22lsl99j8K/uIfK4Gs+vJfWvn8Pr3vvz6jip9+h8p99k0re+TdU9AYCxFNfocjxNeQfKiF/bwlFjhSwvyFMkYNe8hywkvOgg5yNAQ7U5VCkBdKjC8BGwHfrgJ+KRrP0B1B9uAD8Elx2IZhEGuzMptAQgA3ZsA5svH4iRLcdy6V1U3m0dgoB5jgCzmEflQ0D0P0o9wLI3XAEfhFo3OC4j0KTuDvgOB80cPhkmMqP5lIQsiM0h2DwVIiC8MB8LntQ9p0uoMiJIJg5QO7TDrID2K5jAPNcOtvmstg2ncmWSQBzDN6bQyr+Z60nnbTmZYzATx58FfaNLVL+PQBVFtt89kZWEEOYO4Js7giw1obje1eQVgXpIUsZyNJe8hyp7PV9EJLjGwqZ7l9MpqfxmyT/VtfgYolpKv/XsWp8IGHnsZUksuAL/Ud+hCCzSmFlzkvKL+5iyztb2fWre9nzJrTzW9/lcrB02S/vpTUA99pf3kNrf7GN1rwDhv7pN6jkJ5so8tpXKXQSweGZ9eyd/DL7Rm7hYGsBeRtLOLzfR84noEV3u8lZ6ycf9HSwCrKkUaZmgol7AOY+MGY/2BLaOtADyTEoYM+lcI8XoM6gEGRI+SQkyFwe3Xo0j9YDdBUnAlQ0A92NQK1iKEjrhLEnEfQNQauPZ1MQQWB4DOcDwCVzBVQyH6HyWaSzYSqZgqYHQ/tO5SEYDCPgg9Y/HkQ5QN55MPVsJtnP+shxPpMcp+AnsgBqD1knnGQeVGUpXFLGBYRLSO3KAEgtJCuE6uvbyfReeQh2HQB6J8hIHnx+FmQhyxFUqWB2G6ntdtYacVcctJIs86UvrXsA51fivDYc/3WkO2OA/kIsGd2Jb5TYluypLLEt8ZjkOmhuMk2vYNNLXlbidYmWeK5hqV7vqnbIRqaqm2L936NeVt/4GtvevpPdb3+HA29u4chP7+KSX/0Zl/z6e1z26+9R+S/vpoqfb6Xy975FkXfv4OA7t5Pv5dvYe/Ir5Du7Vgd0cKqcvSMlAG0p+5/1k2PXCrI/6yBHZTo7a8IcBEsHavIp0gQ2RhDlG4YMgMTwDUAedEKWQF+HWxDUdWVSqDebilBfPhahDTKhSLT3OJh2Og+aGBJkGqAW6TEm00ERcI4GKQQGl1HACPRyZBaSBoCumMbd4ihYGqCWiyAE0HuRuk9kk2PaAzZGeiaDnCcyyD7lIOuMk6xvAcjnoaNPIQBEcGiddpJlykzqKYAVQb0CyaYMrGB1JJ31p74fhD+PumF4A75PeaD1m0ghKZTnIT8eQcwiE/cRTyn9cFm/Q+SJ1ElPlqzf8Q2w9FeXQn7ceAmgL+IiKTUssd5wsZSpccCndbHrKZvq8CWIn/CQcsa+oO5OTq/kYnr6p2D7p7PItDcLX2QWKYeX61+maWwpK9NhNr92Kzve/Dq7Xv02+T6E7Pjl3Rz81Z9T+Nfi91Dkw+9S+Geb2f/O18n12ga2n1vDtpNlbJ8tJSfcPQ4Q95eys6+c/W3F7NmTz87dXnY9B2C8mM6OmjAFIUWKDuP2XpPF7oMu6O4Mcg+CpQcRjEFbRyagvUfAwGDcorEcKhkBi0sf8gSCQ+jfkvF8unUynzYguCvDbd8/6KHAGILNUbDvaC75JsC0k2DqSdTjYgmMutkDieKDXAnOQd9PZuL/dJFjciXZx1eybXQFWecA2JddZO9HfgQ+ClBL3etg5jM3kXVeI8spK5nn3WQegrw4D/CJD0A6TCAAPLCU1B8B0PKkt0wsewXpz+AyZUEWrP/LJaw8vDz2iFXs0SwE5PA/RwyzDXHN9xGkfwcBexZ+H3P+RbkhZuRTudhC9WJXavv9mQymDCD4m4cEiFd9LtaQxaZKfGFVrlgHvujnH5jINAKNPh1g9VwFm8/ezPa5r5Dz3TvIBjA7wMieD+8h/9/+WTTw4Z+S/91vkfuNPybr2QrWxkOsDXjY3J9Oln43W/sC7BosYGd1gBwdqymAYMhZV0iuPXnseR7SY3cWOw/kkrsmQmWHwdAC6Eo3Oxo8ON5Lbl16gGFHpFsNweEUmHwCOngCLH4MbD6VT+vGC8DUBbR2JI9KJqGbocHDg1kURvBYMorXRcDoQ517SB5stbJjYBW7hj3knc7l0HQA7A2pMYW2cdw1xm1sH1uG1E42ANhyCkAeQzroilp6VkYt/avIMg0/eQNZZtKilok0Mp+5gbQTLtJG7KQOICDscLA6msnaDgR7h0ykgogU2QJZ5KL0L/8Ed9q3kQprywQkeZ7wz+F3A8CbwcL3xjf/kVl1XwM7x1fj/71Z8ptdrfxpTH/u8MQqVs5mXPeHSzz+knP7LWBmgPlwHMxiT4Adhlay8naAlTdvZnV+DWsjYbZ0RcBIBax+eCdr73+HHb8CqH+5jTzvf5udr3+FLCMh0jrzSG30kdYEQI+72TzkJmtHOttHvGxrhwbtKOFAeyE7WwvZVZNH7v1edj4DHf0CZEdlAYcbEJC1RDhQk82OIznsbM4heRjX1ZxLnj6wbIeHPIMA9ACCwy7k+zPB4DgH2trfE4EMidCacciTvkw5hkNDeRwcLeaKwRAHenPJO47XmMZFMZPN/ikPewfT2TVgI9u4h1xHc8g7izvGBAA9sZzsALH9OMA8AT8Kn1kVtUzBuwHmKTDyyZVkPg0wT6YhCETakxbV+paT1h8EmHMQ3FlkzWZSZn06Y+vyQZ70Fge4TcfgLwPYIjNkHocEjRU3sWlR0mr7ruzPZY7zVc+XA1J5oi1Un2ip2hLrLuZlYtKJVaRAuxl1yalhqeolb7iYnjYBvLuT5gkMgpnlWcb3biblZDmrA7eQWp3PliOI2nvcpB5byepPNrD5g++Q44Ot7HjjVrbMFrE6lsvaRD6r/SFS27IQFGWxpcPF1uYVbB3GrboDoO4qJH9HhF3dQXa0QnYchkbdAX26x8fO/QXkr81hd0OI3bJEbZ2fZON35wHobZkT0R3UA0RfX2400Om/4K7PZOcRDzv6cWG0APjVGexEPjiRh4ARcgQBYHA4mwNjxdDQEQ73AND9mVHHFJi330q2kaVsHTSxdWQZW8etbJ3N48CMh5yTAPgcjjkGqXEc/7do5+PpZD21CuCFjzkB7AywciaZTy0i8zkAemYpa6Mm0uYRxA0sJnUO4JyFvwofzWBlBOQg62zI7DlZtkBm2IlLty2CP0V6NGSOzX1LLwFu4u8lZuSN+uS2q5WNNGVbYsO1ulhyPjlNbr9YHsYXIYA+49M/dPIxYsl1yW2JeSO9pK7fzqa2TAQrpaQ2lZO1bi1bD5Wz1mJmpUklpfsmABbR+0kw8c9uR4B0GzSlj7XhG1ntWMxaL2691RYc7yStNYPNh8FmR5xs6YPXu9nWEmbpd3a157CjrYAd7QF90x3bnnxyHi5gd2M2u9sKyd0YYXdNFrn3+aCv89nVl09ByA5/B0DeEYi6WtM/iksSdnZlsqMDYK3LJGcfArqpAgr3I7jDxefuAfOOgPkHwxzo8pKzZQXbOlWyCKM2pbFlHICevJGtXQpkRC57j/nJc1Q09HKyTa0C6FeRdTKdzMdXkuUMZMcYPg9Y3DIIhu6zkXkOYAZLa9OQFm+uJG1OYQ0AV98HQMV/Bn91CSkTEQDbzsp7KMu8nAG47I8i+xTKHoPSCwK21kcI9V+jmEyZuHNmQg7q5U9+q1Tl5DbDUh0rlli+WJ+yMp5ei4tdS9kwk8yLxpepnMYtTMoJLpZYNuoMS25LdLGL5VZ8gQfWsLrnS6y+hS9X5nhIZH5UA5hX6FNY9dV/Rh2sna9gyys3k3kQwZBE6LJ2m2xJhh9HdnJSh1aQVgsdWe0icyOY7TAYugm6+SBYuTKHHO25bIcksLbI8mSiq0PsqsomV32QnXBHXTY561FGsOjpzYOkCIC1AeIjOVFnnf1jR6Ob7Eec5KhfxXYwt6clF/oYF8tRAHoYMgXyxNEKTTwUYO+wSA4AvsdO1kaw6gkAGp/F3Hwj23qWka0DAd6I9C3nsR8Atk8B0JMOMHk62/qcYGY7WeZvIstRfI5jYOfxm8jcBSAfh5+Fj8NncTG/7SXtdXzuE4tIfRPfRxzUphkXK9D5yjlTrCxzNAYQLFZBX8uEfnxv+jye4UVkWuUn0503kOn7iGPuQl3i7wNPtOQ2w8UWqr9mM074VCcvYJe8jkw0OukF0HDlxqs+VxtcxaZGP8saeaqUZZKUBKIymw9sZgJQTc/CpU90FD/afBECpgiZ2xGpC+PIumzS3fQkUpm43rSYVbCnVgcAdKWzVpvOltp8dh/IJvsL0NMt6WTryoU2L2RLaynZGxE4NmWx/SAYuy5Ejro8dh0GiCsjAGeIvEdyydGE8464L9gPOD+yHc6M2qtXsB13AftIAMdAtnRlkBNBXnDAS65ueQ8H27rB4COQE33p7OgCQ7dYyLIHYG5VyVyjsQWfx9J2A1t7AOKjAPRwBjnGIDsAYitiAFv7sgvmYenNAKBfWoLPnU7aoCmqgWk1fG4V35MAWhYh186i7XQm2Bp3qBlF2kj9Cb6L00tJGS/BRb6SlDfw/bwDh/ZWetEuewrK5P4mfK8yMnivQvjOY8so6z/M78mMN5M0+Y2NulTHJOaTLfEYI7147AAChhM+Vuc+uQ0tZKleJ9ku1nUvRxCYA1BCanRnffIlypC8PIgbf+w9DQGO6Xk4InPTzCrS3i1jbSYPQZAXDG2O7a/3MNq3QZcj8tfXaJu0sTaZTdqkk7UDK8jckMsOaF3bHtzKm+FjuH33edncVkK2nlJ2DPrAlggkmz1kr/WzvTrC9qYidg3mk7s3h+ydAGmT4yN7k+djW4M3amt1s70KQOyBTBhE4DgAQI/7ydcDDd6fzfYOXDQ9ZgDTy66RHPbW2tlSgwDvmRsF1GxuBqhHAG4A0txrRfDn17v2pLfDNg5AD7jI2rOC9J6MowqZZxD4HYVWRll32SDzDEA5izzAqU04WJ3NjmrQ19pL8LllSNEOV8a9pEwXxtaJfhflY3C5s+H9BdjKIL47eVLlftTJEm8R5yVyI1UqlpgXW6icXH+ZyQHJLpaqPpWLXVdZZuJNrGDlFAKM5LZ4ei1lsYttHatwq8vAF+plU0e8q0jqJaqWCVPyNEsWQC4LmFQuJn2LA5k7LU+4vBwAM62SQQNSmu2yAWTsyZkNOGbDInmECz/gIlaH3Ky1m1nbK08/Q2a0rWJL00q27F9OloM3sLl/KZtb/WRpLEVQ5wcjQm93wTsCbG2NsLW3lO3TQQDUC0BD19aAnauzP7YhaLQ1ANA1AHQDgrguBIYTAOTxXPJ3gtl7IGu6V7F1CPq4VwK/APsGIUPa8L41jo8snQB15yK8P4B9DN4PFp6Bzj4KPT6x8mNbHwDdB/AP2sgKwFnAqGaRFwCjNr/kI23M9luzxAzji3BhA9gj8IElrI7msXY0B3LEG9XOZl7QTi9l9TWA+vWlCKgLWO3EHQ06Ww8aEQgq0/ApuKyeJM8ZShffd0EKBen672G4WGI50cVS1RsulqpeXMxIP7Nd8wvJIIdMJRzF7eiU+6oMfc026GFl8BMm0K0N7yMBqKzv8E2AfNNN+qrzpkdRJ0PvwjBDVlInXfhxwCS10H0yL1ser/+mwqavQ/utxw/ywNLYSppD+KHBRFptAVsqBbjL9bnAlucdbH3KwuanAIhGF0Adhq7Og2yANGkH+DsLCRKFLAfy2ToMZkYgZqsHoze6L1i78shan4OANZ3t7V55GJXtPS5yzMgwdR4CSDB0FyRK+wq2NgOs3bhoxrLIOQaZMhJgZ3cuOQdlW44luLggJdrSouYRYegMso+FyDOcccEO4Fv7EAgO26PWLrTPAMivwAF+dXYFmycdUfMEQN2G9CjuRMeWsNaDz4kgWR3LB8iDkB/Q1KdWsfZTnPM2QP3yCsgOSBFZWhcXgXIC9dDTKvSzPpFpP1xAfZfp8/uNr8fkTa/1jT/TP3gITHnyBgRnsav2c7ERvNZQ7Fm1iyaTX2R5MiPalvUfZOHH52Ous7CskokfVZGNa6QrcTMuMjk2yxebt2sJscmWx6av2NnUDKYG+6g1uACqi9hyCMy+fSXJYpNatYfNz9nZvN3C2o89ZK70gTnD5G4Ks7tG5jVkk3k/gL3Tw5ZhSJDRdLJCL5tbvFHraDBqrQFDI9C0d0MWdLqi9gE3OcYRDJ4IUWA0C2CG5ADTWzsgFSCHtH7Ij2ln1DqCi6kn6yOrAHowQO4uSIz+tKhlGufMuKK2cdSPBsk94Lxg7XVGzQgKLVOLo9oYQIdgUj2ziLQxJ5snFrOGwFCtT/uN1rboY20OUgTyQTuLzwgJp43mQ4rkgqkhuV4DiN/DsbNpUVVeR7Q3dLR07wHcpDbiTibbJ8vWb7IU8vcWs2kLJKZ8r78vkzcz3LDEumQXS1W/kItdLMvMtyGwHwIS0/nMizr3YnuSiy1UFtPzYxY2HcdrGuWF/AFNdhBg09NmsDEkhoxsCbgfwUWWdKzYJXVH8MN0I7KvDbL5WbBWzTJW6zysykpBsk1ZPYB90E/a31hIux9svdfDtvpC8lXlsP05lH8M3S3LJ4yCOTuckBYuMh8KkPUI/FA22V7MIGsLZEEXNO8o5IL0UJzJJd9IDjmrIDfqVrBlxMHmOoBwwA4t7Cb7EPLdi/9ZG12KFKzanc7SPWfr95Fj0Bu1T3kB/BB7x3ARdTrJPLwKkgTHIZhT34IjyFNPQGogUERQyKoEhANp/019Je2CKv3Q5/G5RuCjkGSTkB9zAPQcLmhcDIro6QmcMwqHZtbzwuqHTBe0nQC7bPsmdzV5Kv/7Sy/r4TBcLFX9Z/GUlckulqo+lYstWJbnCCWdEKaOSQ7DxRLLV6u/XhfTNbQs//oMbqeyBps8dS56WdqSXEzPy3a8u5eyqSaD1KpsRPO4IBpugvTwk/piLqvPycOi+OF/lM7qo/hhdywhbTfAts9J1poQeRsBYunuq5M+38yo7YgsfOMiy4t5COwgN/Zlse1ZHNsFWTCOAHNoFdmnIUvO+aKuQQAaMsZyBOw8no6Az0HajJm0owBpr4UtrQjuZHN4kQ/9i0mTwG/AgyDV/1v7sSyyyfOCU1lsH7BBViyJaqcUFoZVBLBvCgurYFeNtbM4vwN1kGk6054AqF9GkDwThIyAD+SBlXM+1iBl9C0pZDYeJJuMIMre3HrvSD2A/AwcMYc+iUmWqJC58rGv8vLvNYWLpao3XCxVvbiYniZWiC2UN+xKdanaxC6plz5h0bbx4nWZbECUtAhNsiW2GflLjn8I2viFpaQ05ZJahyDwDmjreJNusq6aPBr2gp+Vp5dDqqjIF5BSvzI2QiaPF7WbAW78yM/hNQ6rpBxOJ3X7YtkkJzYPWDaV3G3VQW9rhwSozCFbhwe3cG/U1g3Q1rqhnTPY0pJD1n0ZZKlHgNkLCXEM9UfBshMA+ClIiC4/O9rB3k0mkuF38/RKMh/DxTDlAxODqZshD8YAomMmUvtuYA13K/MUzp1d+RvbJLT0bA65jkLijFtigeB5/G/iwrAC6rO48I7rZVbbRc6AZQWoZ9IuaC/ZSZ3GRT8VjqpDpVFVpEe9T1YbjX0+2bewGnl8J6oMssiyBSIzZHqpjCaCuZWkuRspf4+4papLZVc9VwqGJ1ti/ULHJFpie6q8nspjWAlPbyenYol53WTdO3nQVhj2Ty+/GK54boJJmy47ZGnfvWZWDoNhfyg6D2Vpk4d2f6iyaY9sJ7aKTTvR9hQ04JNO2T01tl6xrD+xH4HiE2GA1hubWSZ7iexfEpttJv2xu/BDS1/2oRwyt3vI0pnP9s4QJAfkRRcYtFskxkq29IFFxwDibjtZZVacgLnfAYaFnj7tI2e3j+wDGXpAqI3YL5jH7L+1zC39yDyz6mPL5Kqo9ajtguXoogvaGALUOQ9bT0Ann7iJzEfNv7UcA0CnXWydy4Gmhk4+BwYHUPU+5fMAIYCtiR4+jfwwXh+yQ8PnAKhjkkQYWwaYenM+VptCrDZCTx+R/QUhcWTvdbCwyDYV8Ye+wLlcCLKjLKSHPoFpEi6zKlMQUPLvtVD7Qm2J7Ymml40Dkl0sVb24WKr8QmUxvYwoWWZnyXZgpsPwgzFwJh9vpGk7dADJbvz6Ckt6Rz2qE49LdDEjFUuuv+iyZKuwx49tbHoCAJadlQBAHdCybtvjy2Jb9G5HWdZn+0u4LMAt7C0bH+10s/IDMNaTN8ZWCMIPrPfFSipPOn8d/jX4X4C5DyIorMxhC+4IZoDa3pmj62gLgj/LBAAt80N6bPAbydKxDLoa5WNg7Telu85L1gEwbhvAN7T0N9oIdPjIYn0ikXkGOntmKco4b8xGlsl0ts67oJMRoM5Cs08DnLPQ9McgW45bL5hfhTw5BYBKf7MMa7+E8htgdymLhp6HN+l5SA34KbTJ41SNjo/UthzS2sDOvQiAW536jlj6PGcEk8q7SCEv9GcIpWcj/lCsDmpIMNnOOPF3EEvOG2Ujn8oNS84b5eTjFrRrOuhaTWZmvQaXXgd8iTpTV8LjH/oSk8Vn5KnvE3DprUhehfTT2KElsUeyZB+XrWDfcEZsBFF09I8RXO4NgXE9sX7o+CkXTdYWkS3KZJ+YH+cDrFYZTWR9HoOwkCzTID0n8mDoH8GFzV+ApDnkAyhCUXNbIGruD5O9H0BtgR7uB2hHV5FlcDFZ2lSyQI+aW29gczcCxrlMSIScqKMbmrh3GUAL8I2nfaxOr/iNGQDWu9XaIVkGbTq41ZN21mZkhzD4lJvNxxEEnlBYm1c+0k6mX7Cc8EUdkBnaT3Ds62kfyQCKKsPdo0ilGw6fTWdtSA7ZLVbvdusDm+N91CNqVO3LAxPnszpRAAkCTT24ODaoIt+J/D4y2R8MLfOkZREg/QEBGZ2V7yDV/pVfpMmbfVFvmOp1dZDKh5+CgxVM8tR4vO2iybwL6DB9yTBZ0T1e/Zn/zxMIRufwftIfHq9KE3aWFeFlXb19hWCeCrAN9HIiqOVOIn3X8iPJOsY7g/pabcLG+gUi3YQyLVaePJdj7sbFIudNK6w8gh//sOcjtSXjN1pHPtmHfORogx4eAZiHVTKPpn1sBgObhwAwGelrsZFWC6CedZO9B/KhQY1C//4DGPMf1TnLb7Vj5o806ROv1kgdyybrK4ui6unlpJ2GHDiaweYTADSCN/MEpMHZtI+06ZsumE96orYTHgSaAO+r8LdwvjBzD97zdaSQgapM2peBFXznCPZij03h7giN/LHaBXk1CdlxDulMBNoaF/N7+Jx4Lf1RuruRistd7qiFlZHliDeW6GsL6ndY/Uv8PZm8mfGGiXmxVPlUxy7UllyvG6Jo0zTAIMHhXni8WjcBw3Y4mEK2gLukLW6Jr5vKUrUbZV26QB/qi0T+MD7FUUD4GOruh8umRzvSWdnl0udxyHZ0pm/AZXsEWVdEXmOXm0xP2WXUkUy4RUuPgWkfmF/a5PU2QK7Y4husyxolAKtSs+IjpT7jX9QOL2mzMvdjGZkH4cNLombc4s318AYEaK0A+QEAc9cSMr+0KmprBbCPpP1Gq037J2jf36hzS/5FG1gmPRv/VbrLlBMIKmfTwfwrENyls1mWbZgDqE9mknbCLIMdYO+lUe3Est+aT2dFbbOZZEVwKMusKWB90ci4OBAcAszyBMo7ALJ0y+EzQQuTIuwNra0M5ZAy6SUFQas6iUB4FBe89HTI7q76vI34dzNoBqARLE8joJ724xyHvtqsIqO1cpcVmRn/nvTvKp4u5GKJaaq25PIllYaLpao3XCw5n5iKLdgu0kFuz3HAJrbpX4zcvl9M/cGNNDmfqk4sZZusHyJA2xEHobDs09DCO1AvUuh2SI+qRWT6MRj7DtQJg8vcDzlWgC0uF4EwsvTW3AM2duRc/H8N08ttaRdMsi923aKoqR6AaLaR+jICwvEbyPwKWPW8ibQ3lsg8CdYGV5G5G2B8ISuqVYNhjzrIchSS41DaR3qg2Z8WlVWnlLabWGsE0GRA6IwVejdAthkf9LiPLfO4YE5IiovinJW1X+C41xdDKy8CMO1kPpmNoDFI1vlVpE2A4Y9Ba/cA2DKELY9cQZIIqPXpotJzAfDrdRMByAwPwO4mZcZJylhm7PsSQEs8JJ9V2HjCAZmYToos/iNSUoAss+9EXspkMLye/tvGZ9+JGd9bqnJynViqfGL5kspP42LXU76SG5aYFzPKiccmu2HJeaNs5FN5mrBM/IdJ24v069aLAL3EpR9dZutJ/o6lsbkht8Ye+BRPNL38TQVBZtrvTD9c/DtTs0LKQVw4Mw4yz9vJch4Anl8KJgYLv2nVH3sydwJkDYv/RW1bCeYEW59dRVYEcvoghcwpkcGgfSZSJRjrS/tYn3s87QMDI/CcC7LtRBZeE/r7JCTNKUgX6aKTwE8CvFdwBziDiwig1k7ksfVkHtlPhMlx3A+W9+h1NlnwUvqp39AgL3Cu3IHOWPAeeaRN+Uk9amOl00TKuEePJYzPrbtIsnEHKxNo3x3/LqU+0SVekX104iv3X8nFUtWLiy1UvmiJDalSsUtOSGELtV/tvP9uTFg5ntV7YMTjxZQme5/Xp/0j/P9EwCjbmX2sTANoM2Yyv+oiy/SKqHbMSdo0ADi4XKTHR9C1v1PbFl9Q+1aSeSodEgFgB7vJULMuA5ohC84A2GfTLii4QyjnALj5HASBfkbwx+YzuaydhvQ4i9eVyUQfwkV6vLaINdHOp82QIS7IjAxcCF6dta3HhbUDbBlfjGBxGaknAmR9YykhMCTleLoMsOB/DrA25paHYMHQK3FBxUZYLzGRFZBCpvZPAP17N3njZDcsVdmwKx2bnIol5pMt1bmJdq3nprJUr5uq7lrtWt7roslkp1pIhYNp/wyW/Vipg09BdhwHSM+5yDyuRNVZyIFTkB6jYFHpsUB8oQqAX0B9uxXAd5JVusOgefUZbPUApczbFg0rkkBu42dWAoRg6RMBtp7JYu0cpMjJGyFlLFHtHbyurqUhaySVuReil6UPegZs37PognIs7YJ6xkXaKQD8RDrYehnyblwQi0mZXYXjCvF/hVgb97F+dxhBfTdkGT7iZd+lyI3eGKCTv4/Ljo1bqvrkfKpjxC6rNw6+koulqhcX+7RlsYXajLJYqnbDUrVdqc5wscQ02cVS1YuLGalYcpuYXpaFKZ/ED7877R9Mk2n/WXkRoISuVPT5xauilqOqSAJWfwpwSXDWAvB1pH2syuQpeVZPhqHnweYAl1nWyZAHU6UXQp62kaeuRevOwI/DT92gM6vlxEqWqaGKTEA6AZZ9bVFUfRuvK912IkHex3niCPj0pQZq0CbbRoiGPmUHg4PZTyKdF40NJj9qQSCYjVSWJfaRKrpYdquSReiNz2m4BPMSeO+Pt0m3nawBLmnysXCxVPkr1RkutlD9dZlx0qc6+X9Gq136run1tP8AUF/Q9xypgz4dhOw4JaN6N0ZlZpvaiXroVX1eRjtu+72Lo2oN8gCx3Pa10yui5rd9ZP5VNitT0LkiNWQx+J/jAhEpMpYW1ft/58Gy81lsFqnxCwBV+ptluqgMdUMTy7RRgFumgLLcCZQ2nD8MF2lxAue/Bz+rsXLcJO2/U46peB8w9Kj5d8roClKGV0HyxCaCpUl3qjwoq3/IuAmQIYn0OukFkh2BJc64ynSFfzWTf+oP8h/7Q7f/kvaOaRagewQ/rCzSXq1ElXEHWY7fRFozQNYM8AlwWxV9gpHWDWZtWhID+gEATtj2nVWk/RI6+e2sqO2kPAoF8En3G9gdr/1bk6yRcU4DMP1kOQOdLF1vczjvFMArXXUnBdCLL+jgPpX2W2hxea7yY+WXaR+ZhJ1H4WBu0/t4ndfRfjTtIwSdbDpmYmXa/LE+lC0AFYaWAFA+l6xIJYysf8gkk/hBBl2MzVQ/J7vqa8kBV3Oxay0blti+kIsll8UWak90sVT1X4SLpapP5WKp6i/6HQ4yPQYgTUBHn70xils4KaKzwZb6FM6jOgAv6IB7DKCqSvtvyhE5XsBrZvVdyI9fA7Q/yyDzm/DOJfLwQdTUhmN7cNwJMPQ8ZIeA/WW8pujmOehnYW1h6OOLP8Z7fKxvE9KF9xU9/QHOl2cE30e5I+0/QadfQAAbkyES4M2m/ZO+cL0s4Sa9GzKGIF2XMpAiUwNSfU7pNZI9VuKbBYkbdslxn8LFUtWLp6y8Fhe7nvpULnZZ/aP4guRpk3hZ7JL263CxVPlrcbGr5RdysSvWQ1qYxpdF1RnIh79BXvp0ZZL9OAAo8ynaAagfou5HYN5aBJR/ibwMSrwOkH2wmJWfQuO+vRJSwk1qLPC8YDq8WMD4L5AdrJ3wkKZ36UGPy0igLFEg3XDC1G8DzDIo8ypSMLi+JEF8uQLT2bS/V55K+zt9tFOe9EHwacJFpv/PNSjLfBqZz7ITdXKM8XkWcF12JG1x/EW5mJFeXvEF2JVe+5I2Geh4MvYl/CHYNf/f12Py9Pl5O6mvLpMgLWp6L+1fZGlhYU11AKAGKyotaR+bTqb9V9ML0LIyXP8+QIugTmdJuc1PWkj5GYK24yujykvprL7piWrVOGd+GQK6dIB9kTCz6HCZzC+TkqKS1x9kBWtDc0dNshSB4QLoDrzHc2n/rH8uIRYZ8q+D6/903GIzHj/T7/NZzk22y15LKozKhfJiifmFbKHjjdcy6hLbUhmi4ivqrqueH0/FJG+UE/NiiXnDFmpPdWyiSfu1nquD5+cA489vJOV/S/svCO4uANRyaxdWjSrvgqFFBgjoatJ+o59/Cu0yuirD8cLY0MKmnyFoe/XGqPI6ZMfPHKT2L7pgOn5DVDntIO2N9Kh2DAFobLpobA6H6OoYeEUr6y5aXJ8kJiOCwur6/4b/R95TNL88tiZ5mJ5Kj0UCO4tdzAurywUgc1ukHLeUx8Ytsd6wK+UNNyyxrKdGxWUN8TQ5fz1tVyuLJeYNS2wz0uR8ooslp2KJxyS7WGJeLDm/kIsl5sWS8wu5mA6mD1cC0CZICARlUil10n02ARALqOA6uOT4uIsllg1mVaS3Y3YJArtlUeWYckE5ZyPtVSWqvoLAUqaCSveczHsWTfwSXIAsLvpZpIQ8GCwjkjKnI/6+0qb/n/Km8l4LuNjFskzJlV6Ov/jk/xa72J7kYqnqDRdLVS8utlC9bpcUFrBrOeYPzf4g/2cB8fvQwgDixf9PwCObhMoMNqkXKRJLF75byTk/j4HaJKOHs1ZSTqmQF4tZfd3G2i/SWTsGsEvA9zIALTPqxsH+v0KQ9+u0fzT972n/ybQ97f/Vzxc9LbMghZ2N1zT+j/jbJZrUpfy/7sV5qaYDJ9iV2r5QS37j6/1H/tX+8QT7Q/gfLjMBC9xgYN0EnAaI4lV6ncHgC9jFc34Cl4vhtaU6KyvvWUj9BfR0/H30GXQy4eiJtP/b9J/TXjL9f2nvXnxdOf8duOjmeFWasL+AW14/gakNu9L/dDX7ws81DrrWNJWlakusS84v1JZoRv2nOdewhc5NzC9kVzs3sT3ZEtsSj9UB8kEcKPE63RKDs3iV2CXnJrhhej4OOL0rTeSF9IYIiMG0ussQuQycyCCMzHSUc+R4I5X3TLyQYPrrSca40FL8X0Z68di4JZYXajfSVG1XazfSlG2JDdfqYqnyV6ozXCwxL5aqPTlNlRczyontyWmqvFhi/UJpqrxYYv1Caaq8zs6xW/lFgOipAZr4LT6xzcgbltx2sSwDHTJ/WUYNT6Z9pPclI7hURB/LgI7M/3gq9r6XnGu8t1GOp0besOS2q5UTLbntauVES267UvmSyn8NF0uVJrencrFUqZEXM8rJLmakhiW2iRnlZBczUsMS28SMcrLrTJx0C9fbPkz7Fx3McaZM5WJGapg+408empA+Y1m/rxPsLyN5NSh/Z1HU9DrKMkwubdIzMp72senXS1+6+LoxMF8MAFO5WGJeLFVbKhczUsOS2xZyMSM1LLkt2S/aZRWwq5X/l13FpKfAcCn/ewRhC2li49b+AQAXr7oWk6dm9CmbMgdZ1g2M16fJ3uh/osRkjEyylyHo06g7jQtGVpGKH/Zp7bOc/4WeKwcYbpiRT1V/pTbDEo8xLDEvdrXjE+1K7anako+T8pXaFzLjvOs91zhGB3IS+y1ov0j7LY6LmiSNVyUen1ynp/IspIw6yhRSvTZWb3iabJ8m/4MMxMik+um035n6wNiyvp9xTFKaWC+WmBdLPDY5TTz2SuWrpWJXOsZwseTyJRWfxg1LrrtS+0JtYsltYkZdohuWWL7ediMVS24TSyxDCnwMsP3uYjnBxVKW/zbtHxLrr+RpMhr4OkA3h/eR5X9bLpceYpeUZY/zaZwTn1B/0X+U9q7pbuUXev2ruEAeT/u/9PpX8JoybI282CXnJLjYlcq/Lxe7UjnRxYz0f9m1mLCnoTcNCXElE/B/GB/lu5LdFQPuRcnxYdp/1bvf5tP+/pp+IJnVJrtMxYtpsgPVvXiNHyz6f/TXqQYj35f2nt4uz1KeS/una3rd/17N+HDX8iGvdEyqtv/hvrhY78QnMkI83vSZTbSudOftxXu8kfaPpjHkZenhZtQbc4yTTea8yESudnhs4j2bNi0hpDHZItsQywR7/eA/HPtC/x95ceMNUuU/jYulqhcXS5W/FhdLzIsl5z8vF0tVL653cyX0GX8uLsA8BzBLcNcHQIo2lkeZZJBEnlSXvPRcGMfLnA6Z0jmYFlvBSPaNkRWL4s89Xo+Lpao3XCxV/Wd1sVT1houlqr+Sp6xMdrFU9eJi11NOdLFU+Wt1scQ02cVS1Se6WGKanL+ifwBZEZcfF12khjHCJro7zuLX5DKVNLEs/cryWJMsuiPD0qKXE+cfywZMwsyy7cYIUnlI9XtJr/E/mcf+LGDJbVc69mr2eb7WlSzV66Z672s5TixV3RVNAGywdwzYKaXJdb8uLOX/J2uZyDp88bJYqvdKeW48TbRUdYYlti30moZ92rZES3XcVeukYLhYcj45NfKpLLEtVT4xTWy/XrvSucltieXE9zXyye2JtlBb8nlil5SFpQXQv9QHSy4BtHFu8vmJltyeWL6sTZ4aiS9be1lb3MWutd2wKx1npKnqjHShNrHEdrErlVO1GZayzai8HhdLlTfKYguVk13sWtJULpYqNfJiyfWpXCwxb1hyWyo3LDEvZpQTj012wxLzYkY58dhk103mjUvgKEEhioltieVkN8zIJ7cllpPdMCOf3JZYTnbDjHxyW2I52Q0z8sltifkrWuJB13RCgl3v8Z/Gfh/v8QdrMtFeljaQSUnxquuxf63v7vf2vld7o8/yjxjnfprX+Czn/o9un8dv8mnsD/Zc4wBJEw82ysntyWWx5DaxVO2GpSon2pXar9QmlqqcysWM1LBU5VQuZqSGpSqncjEjNSyxTcwoJ7tYqnpxsVT1houlqhcXS1VvuFiqenGxVPWGi6WqFxdLVW+4WKp6cbGkujTT/w+25Vz0RzocngAAAABJRU5ErkJggg=='; +// ignore: public_member_api_docs +String invoicejpeg = + '/9j/4AAQSkZJRgABAgEASABIAAD/4Q7qRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAcAAAAcgEyAAIAAAAUAAAAjodpAAQAAAABAAAAogAAANoASAAAAAEAAABIAAAAAQAAQWRvYmUgUGhvdG9zaG9wIENTMiBXaW5kb3dzADIwMTU6MDk6MDYgMTg6MjY6MDUAAASQAAAHAAAABDAyMjCgAQADAAAAAf//AACgAgAEAAAAAQAAAfSgAwAEAAAAAQAAAK4AAAAAAAAABgEDAAMAAAABAAYAAAEaAAUAAAABAAABKAEbAAUAAAABAAABMAEoAAMAAAABAAIAAAIBAAQAAAABAAABOAICAAQAAAABAAANqgAAAAAAAABIAAAAAQAAAEgAAAAB/9j/4AAQSkZJRgABAgAASABIAAD/7QAMQWRvYmVfQ00AAv/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUTExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIADgAoAMBIgACEQEDEQH/3QAEAAr/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBAQEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEyBhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80YnlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBTUBAAIRAyExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTVKMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/2gAMAwEAAhEDEQA/APVUkkklKSSSSUpJJJJSkkkklKSSSSUpcR9f/r11D6u5mN0/p1FbrrqzfZde1zmBm70211srfV+k3N9/uXbrm/r7X9XWdBtz+u4leW3DBOMx5LHG18NrprurLbWNufs9bZ/g2epZ/NJKa3Rv8YvRL+k4mV1nIq6dmZDXF1JLi07LLMZ11J2/zNllL9m76H81+k9Per7fr39T3cdXxvm+P+qXPYH1FxvrL0np/U+usswcwYwrrxcEsoqbTvtyMYeiWW7Ltl/v/Sf+Cb1xXR/q9hdU65lYOTZb0mnFgfYCXX59hGj6sf8AR7H2+31brPS/QM2fofQ/WEgL0CtH23DzcTPxmZeFczIx7ZNdtbg5pg7XQ5v7rm7UYkASTAHJKx+kVP6bi0YGD0mzGwaztaHW1l7Q5259tjfUt3+53qP/AE3qIX1yfX+yRQfdZfbW2qoal5B3bQz85PjjJlGN/MenqWmVAnt9HeSWa3MwujdLxmdSya6DVUxhL3aucxrWv9Nv07f7Cx7v8Y3QmvLMevIyiOHVsAaf+3X12f8AgainOETRkGzg5TmM4vFinMfvAej/AB/leqSXJ1/4yOjOdstx8mt0wfawx8Wi31P+gt7pnWumdVrL8DIbdt+mzVr2/wBep+2xv+ahHJCWgkCnNyXM4RxZcUoR/er0/wCM/wD/0PVUlxpzOoWX5ltVl7XXMurpcbSav1nJZ07pdlGO122j0a67LN/p77bEWy7qFdWPTi5NxdlZeZj4u57rC0N34dTrbLN9ltWNst6h+k3/AEFH7o7N0/D5aeuIOo8pRj7knrUlzjbrKr+pdRsdkvxumtcMd5uIqd6FLWXV2Y4f+mu+0+vuutrVWvMzb+jdQL7rftGNXR04OZY4O+3Vk12Wtewh36XJycf1P9LWxH3PDv8A81aOSkdRIVeOJ/vZuH0/9T4/W9akucvddT1Gvp2PkXuYx2LUN1jnuLrLbs/M9Wx5L3u+xYXpt/4K9ZleZ1K6vIvY/IZZlV0+lV687252S77PZiNL31Yb6MCp7K/5p/8AhLWfnvByV07/APNXQ5EyF8YAPDXFp6cv83OX9V7ZJV8Gj0MVjP0sn3Ft1hte0u9xY617rN2z6P01ynSOs52XkD1rrG4dFZzbHyfdVU7KbaHPj/C5b2V11f8AcXp//CJxnVA/pMWLlZZRllAjhxVv+lxcXDw/1vS9mkuOpu6k3EcyyzItuyLsWq2v1iywPFA6t1D0LXvqbiMfU70fTZZVWz0/0afEzOoOv6Zi+tdZTdVjuutFhLgbzfn+kd5bba/0cWml9v068P7R/pU33RpodfzZj8PkBI+5E8Pq/wCp8PFx/wDcPQZmTlW5Q6dguFdoYLMjJI3ekxxLaxWw+1+Tc5j/AE9/6Or0/Vt/wdV2B1v6k5PWOsdOGVkuu6Hibrsqq22x111+uzcz+jMp27f5hlHs+1f6VaHQnZd3Uci2+6wn0/V9P1PVosrvttdg5GL7/wBE2vHx/S2ejT6n85YqfRP8Y3R+tdbHRcbGyq8mbAX2trFY9IOL/cy57/zP3FJGdx0HCPzambD7WQxMhIgDbYX2dcWZfTciurItdlYOQ8V13vA9Sqx3tqrucwNbbRc/9FVbt9Wu7Yy31vV9SrA699VB9ZbsvqOJccHq+FcKun5TCWwKWt9l7q/f777Lf0387T7Pp1+pRb0X1hLB0XM3c+mRX/xhP6vt/lev6exDZczpvUrab/ZjdQsFuPcfoi4tbXdivd+Y+z0/Xo/02+6v/BqX5ok16jY8+HhP+Mw7Gun8XF6F9a+sv6LnHruGcHqXSHMpyL72uZj2l52turdQy7fZ+dZVjNfVb6lHoW+lk/oaluT1C2w9S3jFLva3qvUGbCGk/wA30fpX6R/uZ9Cy31bbl3LmhzS0kiQRIMHXwIVLF6L07Fu+0MrNmREevc991kfybch1j2f2FGNiDIxB34B65f1fc/QizwyRhr7ccmS/TLIeLFD/AKh+nP8A2k+D/VvJ4nQg+z7X+y8nrGQ6CcvqVgqaSP3MWwuc6r+TetJv1hyOkbaOodLGHR2NJAaAOfTa0elZt/dbbvXUKFtNN9ZruY22s8seA5pjX6Lk6HtR09oV4GXH/jIzZuYza5M0pVsD/Nx8Iw/QQZeB0/qVAZl015NThLd7QdCPpMd9Jn9hY3TPqXi9M6y3qWLk2iljXhuM6D9MFu1130n0sn2Vvb9P0/0q6NJRGESQSNRsV+Pms+OEscMhEMgMZw3iRLf0yf/R9Gd0PpjqhT6bmsaymtu2yxrg3GLn4u2xljbG+k+x79+7eiVdKwKfsvp0gfYg5uNqTs3jbYfcfe97f8I/9IraSHCOwXnNlIozkf8ACl1jwH/meho/sXp+y+sse6rJcX21utsLC5z/ALQ8sqdZ6dW+073+k1m9T/ZeCGvYKobZeMp4DnCbmuZa210O/wBJVW/Z/Nq2klwjsFe7kO85b38x37tN/ScB+U7Lcxxuf9I73hsln2f1PSD/AEm3eh+h9bZ6vpqh1XG+rXScA5fUXfY8Wk1kWi21jgamHHx2Umh/r7m0ue30qfprbXBf42/q/wBY6v03Dv6Yx+SMJ73X4lQLnOFga1lzK/8ACuo2bNjG+p+nS4Y9gkZso2yTFAR+aXyx+WK9X+OL6oi5uOWZjKmkM+0PrDmAD8936azJd/216ilf9ePqDgMOIfVOPk4tdDXsZY6uzGAsbX6b927az1siu3/D+v69d36epYH1f/xkfV3p/wBW6ehX9Iusyq6zRZiMrY6q+z6E2+o71N2S/wDn/wBXs/66tP65fVd31g+p2F1XG6c7pvUunY4c3ALR6goaP0mH7drv0TW/aMVj/wBL/gfs9V99iNDstE5C6kRdE0d+H5XoOt531R6H0qjNz3l+LkF4x7K32XPtOSwi25tosc+3fjf9qHWeyv8AmrP5tU+tfW76mdBNTsttv213p5BwamuN9ZdR9lZ9ore+unHezEf6foW2/wDCen/hFxv+LTpHUPrHnYmX1Ox13SPq7LcKt87fWefXZWxwjf6Hsut9/wD3Do/mEDreFm/VX6/v691jBfndLfl2ZNdgG6tzbt/pbXvDq25OHv3V0W+l+kx/0f6L07kOEdguObKd5yN2Pml+l8z2v1R+u/1Jy7xgdON2Fk3ltdVWZuLn7W7aaq73W5TGtY32U0etX/wNa4f/ABdf/lG/tZf5LF2nSvr10r6yfWDBHTOh5GW+ne23qVrK2uxWvaRua/dZ+js97bf09L/9BXk2folxX1FxMl/18saBZR6ozWsv2OG0uba1tgmPoIgAaDRZKUpkykTKR3lI8UvtfQuuf4zfqv0jNd09xuzsmt+y2vEYLNjxrsc+19Nbntd/on2enZ/LVvoP1x+rf1qFmHjuJvDSbsHKrDX7QYduYfUpt/lbLLF5Bh4XUPqj9YDV1e/L6MQ01tzMSsWiwbmO/RusdUyzGe33b2eq9ln6Oyhdr/44FFXT8rqPT+n5Obeyn7Jj9dyaamWXZDnPsZiluLU3fj41LXZX/WPTvrZbbXdckPU4f1v+q9HWX/Veq8UZOMW1Vh5/RPe73fZqL9z/ANNU53pejb6f6X9BUujXgP1q+puV0HpPTup9Qyi/O6kHOysaxry9thPqn9Y2lnqbLGfaGXu9T1/U9D7R/gvWf8XmV1nL+qmJb1kP9f3Cmy3+cspB/QXWzDtzm/Re7+eq9O//AAu9I91PSpJJJKUkkkkp/9L1VJfKqSSn6qSXyqkkp+qkl8qpJKfqmBM91j/WzpXVOsdFu6Z03IrxHZX6O+6wOJ9Eg+pXX6ZHuu9tT9/+A9VfNySSn6Y+r3Q8ToHSMfpeLqyhvvsiDZYfddc7V385Z7vpfo/5taS+VUklP1TATr5VSSU/VJAOhTr5VSSU/VKdfKqSSn6qSXyqkkp+qkl8qpJKf//Z/9sAQwABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgICAgICAgICAgIDAwMDAwMDAwMD/9sAQwEBAQEBAQEBAQEBAgIBAgIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD/8IAEQgArgH0AwERAAIRAQMRAf/EAB4AAQABBAMBAQAAAAAAAAAAAAAIBgcJCgIDBQEE/8QAHQEBAAEFAQEBAAAAAAAAAAAAAAIBAwQFBgcICf/aAAwDAQACEAMQAAABz+AAAAAAAAAAAAAAAAAA6z4cjmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWlV1kKXfPNmCtq+6gHIAAAAAAAAAAAAAAAAAAAAAAAAAAAGL9XXEpeGV2tu5tK/sPWPTPSJn1jLtEAflrHthLtkAAAAAAAAAAAAAAAAAAAAAAAoM116XPMrWeaOAil34FPp9Vzt1s5n0QOJhL9780yCeZdVLLkehAAAAAAAAAAAAAAAAAAAAAAAHQa6C7MBGXilvldfqlzwFNgqlucmXi09mWq31+T7dq51Urg8+h/LsgHl/VzI4npR+eseanjWNl4lnY+7d1XrXsL5WXcfQAAAAAAAAAAAAAAAAADiQtS1kqXdvKtm56mP1LWSpez2Z+DfvuuXuHr8yuMXIqrWZN49JsbYbHDxHe5ee5ivCPRahwdjD3mffYR8t9Exl0Ps1u8Pr+uNztrZrjI5iRvQ+QT+6r5imJ1Xz/APppIAAAAAAAAAAAAAAAAdcKW122HHjqdPgC4rp5xbvX52dBlerC5xMYWTDXXhd2GrFcpNYdxwhSgdjjYzPXuKmZ5n20dfJ/sDG7wv2FXufy0wOj+fpO77x68Gfwn2qzut7+GfO++wQ436h8O3nZO+9+NMq/f/FgAAAAAAAAAAAAAAAHSpCTveYqLFyLJcnu8JVjK2gNnq4rdroZXcdvbuajYQU9B5en+E6bW9sZOdStvLmhD7tucsxu9f0+IfXsAvMftOVXR+HzN9C+Tf1dz5HS2dj1frsm/HN7er8PI51r+dC3GF1mMPg/su9Wz84yQ9t8pAAAAAAAAAAAAAAAAQ+7PnrraTPvPptlR9VEl6jopSAHpXJTm876mGHeczOzz7q4bGsTS9sv1s2J0v0lFvz36tlRvvDpD+v/ABhG/wBF4D1MXIuJqsqnsu3HnrNN4ORjZVfGO/vtoNqB00p9pXtkovF3NqsHubybTzyz2r9AvptfNqct7L5RRuL0H6a0rHJ5z8672xrafA7u5+dxHKVvvrbtnhdlW2VzfgWdp3q3f2PDCh8fdVTf1NKWttS+Hu6nyNN4dnaeTHOrfL5v5Snt3MCIdvuplXvPf3SiABRGXjRq6zS05mWJ3+edVwKHKwIb6j1ykvTvE7bdNy1+ea20t+L6D1LMvtZEPKu24K+i8xJHkt1fLR7MAAY9ON+oLdYvUXm2Hn1tsLspVdB4pErnvdZCbrySPmm9Vm11Hzxji4f63vzt/MJp9T8844+J+spXdB4jZvX+hXTz+Jj3p/T6ou6uk8feTY6n55vHsuGGLLz/AOy6et7WX3TeCRs0HsNb5XNeLHYePjbKuc7mPBs7KqsnS0Ph9HPPsPmKu8nS8jgdgPhBX0Llekm55/0/qxmBS2Rj0lm49ytVm/ojPskAA66FHZIAAMXPBfY1PWdxePY+ffru2JU77xLEv559z5dPRvg3Ghw/2NlT9A+I8I3lX6Py46Pwec3X/NUadF7FT9jaedHLuNk8lDDm/oOc3WfNML+U+icrnonxF7M7H51cDPkP6a9dY5l/Tfz4iBznvl99t5pCnlfoe6e04alMPpfTvauQO58rsJpfU8mndfIWJpXCRKOU+lc6NKjrjSCvonLyy4noYg9xzk8vOeq/RKoAAAAAAAAEAOS+muq1Wlcff/trgy56jwuHvNe93t2XnnoXcW4ebyMbNH67RGL0t8dp5vfLb+Zw05n6F/DS9VFzVe1e1fqzxPZu4UuOi8P5FEY+4xu8P9d/nXZIbvyOw2n9JmD03hFvsHp/Lt5tO29t0wu0bj7yoLmsyMdp8s4QTXYuQzS0bIEJjxpW7R7nAvnpNnaXba+KPa6CWfE7+6+q2AAAAAAAAAoTG3qqiMDpKoy9JV+Roupc8iGZa/V9vd7a8B6UsbxYZ/5IZFb5XP25wuo6Y3PXyMBGvdW1arV9zIXeeXcjyY3fNtZnRS73Tt20wOxuZncb1pfopH8C91Ru0RidHVl/R3Fzecwgmuxchmlo2QITHA4Ucquw6I08+7H1IT5gAAAAAAAAAAAAAAAA6SEMqYvimiYJKCNYcyp7BmLjXuMQkqTBjW4SOIWS1BfpXLZGuKo12LkM0tGx5CcP60xVVUWSkMtsa14Y+ZUtIZWo17wAAAAAAAAAAAAAAADEHWOs3OklI1yimOUyGxlh7uQtkbHkZXWNbWUdpa3PVYuQ7TMVGsbT5GU9DXYuQzS0T8jXVrnSWpl2jKFal4oy2CDTXuQi0ZW4y2ZY1qQAAAAAAAAAAAAAAAGofOECqttu3PIKeefnMUkqayco1qViZ7IysxGuBO5DPdRnxhPgeEYZzXYuQzS0WMMZtXqE9rc9i4k2YWJU1/ZRpUGYGMtmSNQAAAAAAAAAAAAAABhSlTXHlGW8a5WlYIqTxjLOSak8qY+pRmxGW3/GsMTUWuQq8zSxrZUtUrPKNdde5DNLRNONdY+dJqGaeMoRnjGLCUdn+Moimv7KMiKN1yEwAAAAAAAAAAAAAAB1GPCVMU5QCkx1c2ca1iYzTVZuQ3ELc5kHAi/KmHAj2pIVXNeWfjXHbKM0KMj1JQWlTEeWsUlqrNAsWZnY1xemK+Uc09KzvpUAAAAAAAAAAAAAAAD4fD4fTkfkNQGVJjGyBGvMA4H04nYcT6fDkDiD4fQcjiD4cwAAAAAAAAAAAAAAAAAAWyMasqZOI1r8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//xAAuEAABAwUAAQMDBAEFAQAAAAAGBAUHAAECAwgREBJQFCA2ExUXGBYhJDAxQGD/2gAIAQEAAQUC+E817q8/OGxg2goyYzZIZorEplkUPWxyeN0iinzfWWtRnGXpDB4vjGKMexldW7IyrX2OlrHsZn8Y9ijVRjMwvKVvsvtxtXu+QJBxrK2My5lkQeVCXNUlESqexRpBoK/7+y3m9+X4xJB5Z63p0cVjm4xQ8rHNn+R6Ilp0eiyBcN0sR1aBohxopj+BgdnPStlJnINBic9c4u56FgKlr2zN97GonldI4oV+FeaVY+FcOo89TN6e/wD091KHNvR1/ko9SdyQK691e/4icpFtHYSOsLoXv4cKNoSN10DGDpI4wpSqEm+DpoCEwd9aeSDm3Q8z66vFYdfEZCWsU2UUkGgdZ0SRU6LWNq0sTSpWpUep/nYNaLvc9mTjm4lpS7Z55fqX9uNYXvrugJiNryZpzOW24pNooRZ47LZfB+aejAfYKVzIi8y/JKuTCvmdMMjVJVyZbpr210DD7GUtOzWsbVUFTuiMNHp5p/JWsbRuzq+nr0NC7OAt5b0AiT0/E7+Tb2CMjUipn5zzvi2whHzdWkDC09twGFqLO0IADnYj59e0OLi2uDSs8eag0+X71vwN8vFiU6c3hewRU26K6MfswaOIjjzdJBmuAxVelcBwlj3cKFyAqRVJSlQ8kEqwKwnLK6NTyLvEFdAayK3uoolRIhybwslLFLzJwaBJnJ5MJDdBeAHldi3sEWx9SqWhnTX8zN9qSS8N76ayBnesfPp7KJBBhLEZJz+/o9sUxG6DTt8DJpEp1YBommGGzxT2PtBI1iEeB4Fqq+Fr2K2lQCPra5JnVCx2+ulzxUuRCySe1ko29hrzz68FL9GhNIAcDurgUSHJisRgC3tUF4SEJlpWaF21DFhYrthDC7wsiB/02dWN2ZNyZUoR7wIm2EzL6+yvb6PJA0DyT+WI8pW7IELfjK0e55fVav0GUhaSJG2ELQ87HSQg1kWs54IkCzJ8a8HV2IWhixeX5pHkf8sR5TWRsz23NhGzPLazvzUQIlEoASRQxlg8S2dpBDmNaznoi/rfRARNDmv1qNW6mkjaH2zufCDCqZSVkIk72biw4obpGCndY7ELQxa3ciZ2DUscUjelzKok3kWGeOzH7X5o1PTREDltzQD9/o5brxToMDj5s16derEkjiLm3bqksbYUu91OJD2MERoNFkjejQavbXtrxStAlXpyGI1WG4GFshZq+6YMbvZJLoUKtKGbFFmmPZKBw8UjxoQ7GSM4+f7CMJw8O7GMQABthOSZmBBEfWvpNbTPkm5fu8gvLE0kKONQsVJnws0NQJHaLXcUgaPdGAtFkUgYu7BsCok2SQJH2c+NWcEEWBb6CRHoZqjYj2D8aRCP/wCPg8cDw+bqoCTYZNqdlajuYkY+PaJwlDL93PvNpUkqc1GShslsHFmAPHW/JqYfPp5+wM/2slSHq3D5YlWaFqb1dGZue06QIFEOWvRq04+Pu8V4+8panY2mHUNviWW5S9jyeTRlk5KizL6cSHNNi1GQK9bQOx3HJa4iybDNIiUIN7qDDS+xfM5C5ftDFBiC6UEnpRnmLzZjZEEy0rxH4zfgAvYY7Ed7ayRlHUfFruMIk+1OkrPP2Yi+Owr2Ddrkdi9fqYhETyxFoHiZvxZo7CRQnM6gZIkvZ/aHU7l6J250Fj4/CU5qv14moTIPiurzgsB2P+wMz1ynJJycFPp7qjy/7qbETGnIWkNJFAivx2Wy/wDJEd8XkmDb5vcxKXpk/nXN2bjSapA2fphPPQ3dM2TY4/QR4ImAU1jp+55NIWKi36sHc6N/hsm5ysgjwQbcWgXlJaiykkkfm00lGbr7XLKdVN9QXJ+7EXikMLAdjF/QgVYo2OBGC6Vt53Yb7VM7LskoDIZIwZx0WbrCscRvqwFYngluySA8M+HVxhy2To+MD+wapea3FGZTbXbX47XFH5l6LU+xSjBxG4mgopDmwqTanA3jvIVLURYj/wDEyDbMOJ2obZ2Xe4xsEu6xjER4awWoE7ikbmxG0onodaCNJhFEe683Znb3xAlRJ0SRoYWpgSvg0zEmi2Pin0QHSamIMGhnNeNszm4uw2zPmbyPtBCjxiePMMv+vRUl0rE7czNrS3M7C1D6N3ZW59QN8aBDUsdmZvfEGbG27GhtaELO3tA80MCRkHmgcSuUcBLwtYxZhGtddtfjtcUfmXp59PPp7LXtoTJ0uPn4O2fmpG6DjyOM3bth/wAlGHapvbIR7HB3bbIc7sQGy/3ZEqbu0AtYv1b9e/VfO2NP3YwYzPUSy3olpAdScFRwiIe2L+/+6p3Qx2q2bcw6Qg8/QdtfjtcUfmXmpFnOP4zs9drvGe+3ah15EuzBZw2D5MwlTZXQktXi4SiPqEaNqtna9vgenZrXgibPPPZnG0TGEpL/AOkar6KS4NO4vrnGGV5MBmIo6A5LXJ0tWe2jqSVP8JEREWczQjNiJh50iYiI3srdxoZfTB5ZeKHfeikDliQQtFy2Fkr/ACJ21+O1xR+ZdJzMojZmUKFCvfE8LkstL8OKQ22iReSjIWT8gBZjg7LVyZuSTFIymTzeoU6XeQa7W7Nz03/AdM7Fec1VzHpadMM1vTp1ehImSo03VUTXLByhwgcxV9Pzh3kQp5Miqw8Pdu5qPTijQy39Ube3N9dtfjtcUfmXW2arOYqSKFiTeGPvTG2ogVSMsD8cdeNddSlZsaWtieXv15RldaPkvwHVcNuhTrqLJnLIoW4duNX08ldNnh7p4/kEic0mWu2VuiYp2RuaVA0YbJPOdCfUm0zvF15SCHRqcmNwDzIhA3xo7Z12SG/YhU+IudJOMGmSe2vxyuKPzLqCHHA9a8sMsMoZmhfEzjq7GirJPJHX7u9o+eZwdw9tKSNyLyHmiLrgAP0rD0bpkdRMzuT1JfwHtqSuaI+kHe98aSKium5GmDftDOL9CdSyMDONNtdWLxhLFCdPvV74QjLRF4RXipFh4Hk9MR8Wkifb/UuZfeNcWkm7cARiHxq2TbDl5hbv6QXqE+fv4fefbUl88R/Je594yPUVaeSJj2bBHizbipkLnZEVj4lxyzD5H4qcudbyu76+P5YycIbgsfiVJ8H49dm7DVhPUn7ZNOeSop/enfx9nivFe3/g8V4+zxXivHw5cMaTAe/pfF9DQ01CTD/9J//EAFURAAAEBAMDBggKBwUDDQAAAAECAwQABQYRBxIhEzFBCBAiMlFhFBUgQnGBkaEjM1BSYnKSosHwFiRDgrHR4TRAU5TSMGCDFyVERVRWY2Rzk7LC4v/aAAgBAwEBPwH/AHiALxYIsECFvlwBtzjrGTvjJ3xk74yd8ZO+BLbyRECaKGAB9Mfw+Uc/dGbm07Y07efTtgTX8lwuusvtjKjbsin3SrhsqVc2YS8flLLA6RmgBEeYRtAiIdaFXbZD41YoR4xZf9oLCahFeocB9fOaKbTMVqqcwdbnzaZrCJPo9IfYH84E1gv/ADH+ADDWVTR7/Y5Y4V+omY38I/RKq/8AutMv8up/KF5fMG39pYLJ/XIJYEcpc1h934iECBg838+u3yQBbwOkCN+YBtGnbDtYGqO1U39kApNJvqiIpk9v8oSp1uH9pWMr7vxGPEEt/wAM3thnLGzL4m/t5nzojJDaqD0uyEiKOV9giW/fDVIjdEqJel3gENGjx+uRqwZqruDbikKJzD9UhbmN6gGKY5NWIU7J4TOU0pSx7VxzKf8AtEv94wRT3JdoGUFzVCq4mbz6Q7FP7CZr/fiT0BRlPFvJ6QaJl+dsgP8AxvCBDM/7Kkkh9UhS/wAACMyo9Ww+s384WKo8T2iyW2L9IgD/APLLExpCj56XZzOmGTg/e3TT/gEVHyZcM5un/wA0N3MrdfOSUzh9lTN/GK55OtdUcgaYM0gmcqDedAPhC/WSub7hjxfpGJYQUDgOg+sOHyH6N0OJiza6KrdKBqNP9k1Mb1/0hLPsdqqTKPZvhxOGaK2yUPp2wmqmqTMme/Os4I3JnVGxYL4O6LnALpwJCl0TLlJz69kOnaLMmdY2vZCyrqbrkEhemPmX/H+kUxSszdOm8ulbE7ubKbiJAJzD9UOPpHKHfFDclmYPCIP6/fgzQ81qj01TfXMU1k/YaKZoqlaMbihS8lRZpm3nCwLG9Kx7nN+8MVLjLhrR5lAf1CUzwv7Jv8Kr69bB+/E95WhClFClqTub/EdqX+6VP/7RN+UPinMxuhPCsg/8uTJ94RMf70L4jV+5+NrSZ/5lb/XCGI1ftviq0mf+ZW/1xJOUTijKVM7mclfk7HJc/wB4olP74pPlVyKYCDOtZWqyH/HIO1IHpLkTMHviUTaWT9kEykr5N1Lx3KJCBye3eHrALcbQQMhkjFzCXhbpZfql0tHKYwrlrVgfEGQMgQOVUhXRC9UdpoVQN2U+cQLutrfhb5CG/DdD+aKuD+By0tw+ff8AC34w0kqY6vT7U3ot+IwkggTqol9kHEw9Y1ywtLWS/wAYjrCzV7Kz5mZxyxL5ilME8yeh+zmnRzuXJJeBtBhIpUSZEwsWAEDRk7+aYTwEFfB0SCKvbvL9oL/whrK5jNlEvCc+Y3m5biHqvm+7GGHJbn07QaPKhEsrkwBcpT9JyYPnCW5dmXvPYfoxJ6aoDCOSmVTQbSxsnvcKnAFj/WPbOPoIT92Ky5U8jlt2VFysXzknUXUHZIfvJ5DHP9ssVrjXWdWXTntTKEYj+wQ+CR+wAjm/fE0HqJgkJjE1MP59PsEI/SVpwbjb0j/KE6jYH33CEnbZf4pYBjd1g05svSzZzCPeN/b2xSNa1RQz4X1NTdRAR3p3HZH/APVTvY/qy24Wij+VXTcwbNgrdquxmJP26QZ0x/csUwe+McMepRWkmVpCkgVNLlVCGVWOGTOBNQLs7DbpgU1847rW1v8AIU6fHT2LJsazo8S2XpM08pQ6fbHogNIHXmEM3Wh+iaVLkeMw+AHhCSpVU9oWG/wtRuTG3F5g0i4c2FnJYVqBgxn9RuPF0qclzpJohmXOXgYbmDLfgFhEeyJVROE+DzJGauEmyCpP+lOzAdwP1R7e5JIB7orvlWbUqktw+Y583WeLh0hH5xU7dbvOc4fRiqsQptP3ysyn84WfvTeccw/w6vqAoB3QvMH8wNmzH+qULF9m/wC9CUkmCvxpPaN4CnFeLgPsf/qFaemBfijEP6rfjCzR031WRyQisZP+znG8Sl54ayuoPS8gMwa5x9ev9ffHC3DmkFOz2qXwSyQSw7p/lzZSa6fm3tgcEcWSgqY1CvcpewAH8QhlKZpMZolJGbE55sZTZ7LztpxL6oVwWxWRS26lDPgSsPmhvDha8eDr+E+B7EwOc+USjoIGzZbRPqenlLPiSyopeZq+FPaZTfMHcOnbE4p2e0+nL1ZzLDoJvEQWQv8AtEx87uiR4ZYhVJLyzaSUk6cS049BQodEwdoewfZE/wANq7paX+Nagphy1l2bLnOAWuO4N/GCyOcHk61Qll6gyQioJCtbo7Qdxf6xJKdntRjMCySWncGbJbQ+Xgnp0vf7op+nJ7Vcw8VU3LFHcxyibZk32LvH1QGB+LBriWiHQgG/Umn3tYnlLVDTU0LJJ9KlW02MBBBI/WHONi+2JzTk9p6a+JJ3LTt5p0egO+57ZA0+deJ9IZzS79WVz+XnbPyZREht9jBe+kMcHsT5i0aPWtFPTt1ylMQSlvcDeyKjo6qaQMgSpZIszMqPRzhoNrZ9fo39cSPDSvqmlqU3kFLuHUuUvkOUNDW0/jpFQYZ17Ssu8bVDS7lrLswFznALXNuDfx55jTc/lEulE4mUsMlK3yeZBQR+M7cocbXD27oUQcIgiZdudMqhAOXMAhcg+cHdE7pyfU2o0TnstO3FwiRZK/npm1zd0SDDivKnY+M5FSjxwwvocpesHzi9vqif0zUNLOkmNQyhZo7ULmKCgWzB3DFOYfVtVzVZ9TtNuHTJM+UxyBoA/kQ9sTnC3ESn5evNJxSLxuxSTznOYlgKTt94e2JFTk8qY7tKQy47lVBLaHAnBPTpe/3RJKdntSOHTWRSxR0uijtVAT6QlJ2j7Q/IQxYPpm7Sl0uamWmKh8pSF4j6Yc4DYmlcIzJeh3u0Dd0Sjf78HAyZzJqEEqgXAQHeAgNreU7bldNjth0AfXFPrmFuqgp1yw16FQOc3neSjyhcTZFIGkmSqUE2aBchDiiiKpScC58nDgNr98VBX8yqV54wnLpd667VFBH2BbKUO4AAO6DLzabhswuVHsL0ffDOQN0wuubP6rfiMEIVPqEKHqjKHfFx7YAoB1bwqimuFly5odyDKttWo6dlv6xKmRWSGxEb+Xyf1ApmksXq+FbZuWbAE0TB0bHNm3b/AJqen0t8YC4jVtO5jWE5qernq8ilspFQ4CcLCfhrl087gO6OTcyPPsWPHr65vA0V3Zx/8Q+W2vC2YeGtuEYQYlYjVxiqwljur3oyYyiywpZgEAIXhmy94a2D0RP5ghP8YXUwbJlBsvP+iUu4C7e0Ys0mfEDlIymmEc3g5mzbNbzUSp7Ub+kNO4Yx7q8tT148aS9fNIZSUGzW27Zl3gGnb6YxTrOp8MaRwmpWmZ6uxdhKtqsJREphP80dPra9+6J/iVXVVS4ZVP6ncupdmzbM4hbMG7hwimqMUd8ll/LhS/XXLRZ+QL+emdQ5NONikLr9LdprgzensJsaKxK5EixmvgiXD2D/AMRPS3HfEgqWeUk/8bU7M1GcwyiXaE32NvD1xjHiBXVHU3hVL5fUa6c6cS8rl0r0c5hPboD0eFx6XHsihXc9xKxZowammSjt6Z6jnUP0h2SA5rD3WiZrHrvlSGbOB2jcs4BPo7srK1x49bKP1e+MVnrmtcbJyiU4iKsyIzJl+ifY+83D3xjlinWMixH/AEUoqqnCLRm1RQIkib9p9m/ujlQzR+WaUJTD98dd00lBDLCfeKils49wjl3axiLVtQ4X4dYRUvS05WaTI7Qy6wp6DY1hsN/pCI37t0VDiZXlVS4ZTUFUOXcuzAbIe1sxd3Dhz15STmpC8nDDtMpzWYZzh80lm+1HusBFOO+MX6LQqnGPC+kGLEpWwtCgsUgWs3IqcTgNuwhB6Xb0bRj3VX6WYjThNmrmlbE3gjaw6bNIeGnEdfRprGLtW1fhq0wsomj54sxXQlBBOVPQTKKbs3foO/sjlSOz+O6FlDx0ZaYs5STamMOpzHOJdQ4aBm3jDqo59hfye6IJJ5qqzm05eqL5iGAB2Jb5R/e6Ho74fVZWD3k1TSaVTPnbiYTSYbFIVTWP4P8ABabtb7I1tAvm9uDOansLcX60K5yLmagzRvp0zW0Af+KThAEPgjg7cFdjiFUxtbdBRu132N1h3D9HpGy+bceTS1btpxWlauQL4HJ5SormEL2UHMIG9IAQRtGA+J1c1TiC2a1HXjgZKg3MqYDHSABAOAXKFxHsippp45qGfTYpSlIu5VUKAbigc1wL35e3S/YHly3ozx6QvUiblM1fov7acQ/r/SEzkUJtCj0PIcNkXSezXLcsJytin1UAgqZC9UtvLG/m6QPd5dFTuSYd8n1pNpzICTBrOJocqiJj7POUOr0simgbEfNg1YU68wHxGqaQUU1kaLtQGIFSNn2ojs+lm2ZLW2h+jYd3W7MFNrIcL8ZawAMjkGoN0TfSC9xAe/aJ6e+OTqBZYzxPrYQ+FlkmUIUexRTPx7QyE4a5ooQguK3pFM2plZshf1r3vFYrGoWYYxYpui5JoJUGEvvvv4OnmUKb6Jjm6OXcmPS7KXlp57VsjlgiIi6ekKbj1jgU499gER9UYsYxUDLK4mEkn2GrOcLsiJkKqooUnWLmEuTYHtkHTrDffpuhyYjl+6WQSyEWUuUm+3YW+l/Tb1QymLKT4m0NhgA/qCdLLJHLewGUMQhi9Gw2EQTOXePWv3RV0sVoTk8taeeJ5JjMZ+pmDcORMTB33vsUuy14peUnn9SyKSJhcXLkiXozHAomtxygOa2l7bw3xylpsL3FF6wIb9WlzVBuUO/LmH6thHLbXde8cmBllrGfVCunmQlsqXVEeAHPYChfhYubX6O6OTebw7EipKwmIdFmxcOVDj5p1bXG/rP7IwLZHqjGaQOXHSORyd6px6RPhPeprf3RTGKdCVPi4aWFw8b+O13KhCTARA6phTvZbLsgDUC9XNp86K3Yz6f4xTCQzuY+FTDxqRoClrdETgS2XMPVvfrfzjFnFqgpJWrinp5hkymq0vKRMqpzgn0RARFPJsT2y3DXMN7bodqpuHjtykjs01FMwFvfKHZewX9Ng9HMACYxCFDUxrRWpyUYWe4iurFPLqdTZM+H6yqI57DwETGS4bgNrFXqhSS9R4oOjh4Y1ptJqgI6ZnKygqnEB1sOY5NLDpmC8UFLT1FXlLyw3SM4mKOfjcBU+EH2axXoDWvKcZSoVMyKDtoQOPRQTBRULeswe+MdZuNQYs1UdPpCmuVsnre+SyWnpMAmtwva/GMSq7ozD8aPoeoaEQnJ5fKG9tqpk2AiIAYuXZKZr5Q1zB6I5UDh4KlCJsnIEo93LhXSbFLl2Jgt39PhYcpbWHQbxS89keGeANOu6hkBJknNpmJ/BzH2Vw1ynz5Fb5dglpk1vv01xxmsprrCyjcSHcoFtPl3gt0fhc4GQAVOkIZCX6hDfvb+3C3Ep5hzLakTmNJqvqZmqYJn/ZFMYNoGXPkV3gJgtCxsO8SMKq9qVlh82kj6UJkFM6BwHMp80bJJaDpprviwahbmvF+fdqO6JN+sTN483B2f1h02TdoGRV3jxiXvTS9XwF8FidsAObUvV/umPSRqapHBygxLk8Hlgqql+cqbLqIW03LdvX7tcRCFprk+YUU+Y4ApM3Bnpi9pRzb9OG1J9nv0aU3Ux+TTLZdIJE6cvZnNNqIJEuOx6eo9vxJICRzTDzk8VIE3ZnZTucTDIJDhY4pfB8P3Tfb7tcLkhVxIocoB/wBat9O4D5rxyrquM/nsoo1uoGwZAdRcAH9uoAAW+m8oAP2h3Ryc5MpNsWJB8DmK22q5g+iBBKA37hHN6rd8Vxh/iZOasqyoFKFmfg6jtZUDeDn+KE9y8PNDT82jC2SfpHiNRclElyKTEl/qk1H2hrFbVp4LylPH+bKgymKCBhzfs0w2avDd1ht32745XM2P4+pqngLlBBuo5EB0uKpyW01t1Da/S3aa8nCUjNsWJAqQnwbXaLnzBoBcglv6hMBvVFczcZ/WdSzg2orv1RDW/RE9y68coad/dGCrKaf8j2MLyQS5VzOHORsQqQZj2G/S7bdIdPfFJyCbYb4I4pTupGR2UymSJW6JVOic5dd3ZfMOnvjk2JpSomJldLkMCcsk5wvbzjioOg30tswDd53dryZGfhmJLmeORHwOXS9ZdQ4+Zmvr37/XGC7NWtsdGEycFMY/hqz8wm4gUwnAOPaAX7t0YhUDifUdY1fUX6CTPYqvVTgOwONk79Hhrpz02yGZ1DT7AnWXeppejMfLf+nvjlR1WL2cUzh01OIpbcF3IBuAT3BD06GNp3lHzY5WdTmbMKZoZIwkcGVFy4KOlihfYlHtvmPr6NOjryaJWWYYrSxcRKAMmzhcb8RAmUoB33G8YSUjVZcWqhxFqqnXbGUJFeuSncEyXFQBKVO47rAPW13WtFAtT1ri/Tm3TC7ydbUwG+btttlvx6Ol/XaMX13la45TaXoAU7g8xTZW70x2fvNw4d8cpyaEeYlDKWx/gpWySbAAdUoiAmEbesNO7fHKHJ4glWE1CWHbMZSQ5g3XOfJvLrbqG4jfN3a8ocSyKm8JKFIkJfApVtDFLr0zgmFxDTW5Dj6+6KjpWp18AsLqcp2QO3jtwr4Srsk82Uvwmgnvr8Z3dXvieyd9h7yc0pTOGR2k/nM4AyiRtDCiXNr6OgXgHlqEzpbPNYe2JdL/AABNYu0zCbja34jzPpck+TsfRX50ArNJSX4ZPaE9NvwGGEwSfo7VMogPZ/c6hqafVY6QfVHNFHbtNPIUx+AfkR9sTep6hnzeUNJtMzLNmCezQKIB8Gnp0Q+yGsSfFbEan2DWWSasHiDNAuVMpTBYoa6bu8YqStaurA7c9Sz5Z2KQdEDdUB7coafnfDB68lb1CYsF9m8SMUxDfNEvGJnMpjOnzuaTV2K0xXUzHOPnX3/nhFP1FPaVfeMqfmZ2r7ZnTExOJDcNbwfGnFJRJVA9aOxROUSiHQ6o8OrEpm8ykUwQm0ndCjMUtSHDXKPb7NIfO3Uydu3zxYTu11BOc3ERMNze2J3UE6qR74yn0xUdPcmQDKa9EAsAerfFPVTUNJu1n1OzQ7VyomKZhLbUg8I7RHrWD29vrim60qqjzLqUvPF2R1SgBtmaxRt9Htio67rGsCIJVRUCzxJM1ylNoUP3Q0iXVPUMols3k8smZkpW+LlXJbrhrvH1xJKnqCnSTJOTTRREjxIU1rW6aY+b3RIKgnVLPQmNPTA7V7sxTzF35B3hrA414rCmsl+m7vZnJlHqf6edq5dMXCDxmtkdpHKcpuwxTZr/AJ98TKbzacTRedzJ+dWbKCAioO+4dUbd3CJ5PpzU0x8bT+YqOpjswIJz66F6vsiSzqb07MCTaRvjN5iS+UwcM3aHnB3ROMWsTKgYO5XOqyduJetYBKI8OIXDff8AN4k83mVPzFCbSZ0KExS1Icvmj2692kFnU1TnJqiTeCE7Fzt9rx2ubPnt9bWJpNplPJi7m03c7aYr6nPawiNrXid1BOqlfpTSfzA7l+RMhCmNwAsVBUs+qp4V/UMyO6eFSBMpjcCBw09I+2JLiriNT0vaymUVe7RYIhYhQNqHoHs7oqKr6oq5VstU07WeHQCyecdCh6P9mAaWP0oKUCdQAAvcHyLeLxeL814vz35rxeLxfyrxeLxfnvF/kO/NaLRbyLxfybRbyb89ot5N/ki3kW/uF/Iv8g25rxeL89ua3k3i8X8i3PeLxfmtFvkS0Wi0W/2FotFotFua0W5rRaLRaLRbntFvki8W+Xrf7zf/xABOEQAABAQCAwkLCAgFBQAAAAABAgMEAAUREgYTITFREBQgIjJBYZGhFSMzUFJxgbHB0eEWJDRCYnKC8AdEVJKiwuLxJTBAQ1M1RWBw0v/aAAgBAgEBPwH/ANXlKYwVKmYS+aLtNo6/HzJg0ZtMtJAtdtIxsxbsZg0WbEtKtzbPf44vLfZ9eG7F87+jMzm9Edw5v+wHhRBdG3ORMSu0Nw1QLUA0w3EVGtwhQdkY9VIpMWjcpvAlqI+zo3ShdqrH3hpB3LZPluChG/2X7Wn1wV02PyHBR9MFED8kY1awHxdL5evMnSLVvSp+cRpSDtsOYXR+cCDiY9X/ANQ4x1MP+3t02/8AF7Cx8uMRftJP3PjE4nzyc733yBQs2Br/AD6dyTy5aavchIo5JeWbyfz5wh24TYIncLcVuETJ2rMZg7dCFAUNUPshs6ezzQYxSeEOUvnGHmLJY38EIqD1e+HmMZgp4GiP8XuhZ87dct6p+9F6inhFRH8UDo2dcFOol/uiX0wD56j4N0qH4oa4umiPhhBTs98MMWy13xVhyldmvtoECFOfR4jvLqHXDCTzKZFvbNDCTqhLAjgPp8xTQ9F38xYcpJpLZaK2Yn5VKdlRhDC05dS9KZNW4HRNzV43VCqarc2WumJFNg7rFo4frpNkE6rG5tkZbho7y1iim7S2Dr90GuUPmnNVXaOnduD62gYlktezVxktUREvlQzaMMMSm9dUCXcs31jfhro6xjEeJjTExSnUBJgHMI/2h/jBMO9y5O/7V1Oy0Yev3D36Woc/3RhpJJm+CqDennCG2Cf2l7/B/VCOF5Ol+r19MBJ5WH6kTqgZPKx/UidUOMKydx/s0h3g52TwCwKeintGF0F2x7XCQlCAMblFABU2xhOcrHV7nvDXm8rV2afX4i57Q5USrDrJij3Snqtn2BD2119FImONHqo5EqKCCXRp9gQq5cufpLlQ/wCIfjAUDQAaIZTmaMDpKN3hgEvN9XqrDeaSTEqO9Jw3BJ7/AMgD/LQPXE5kzqSuMtcKom5B/K9GmnXuYRSIwYTHECvLJyf7w5cLOl1nKx6rn5+BKMGLr/OZqfI+xSo+ssO8RyTDzXIlAXONuov72mMSY9FwvmKK56vnoXqoMLvphOz5plDqD0BaHthjg1ZTjTFTLN5JQu7ah6ok2DjJf9MlFfvD7whHAs8OFFCpJ+Y0fIB+Ot6n1f1Q4wTOEvA2KB56e+Hcvfy/Q+amJ2wAgOrVuBo1Q+l7KYktdIAI7YfYNfE+hKAPo+MSHDjhktv5wIXeT8fh4iwjKElN8TmYh/h6fPt/PpifThedPKmGjfZ7fP0wBSl0lDTwACh8wOVEmekxNL1ZLMRAz8ngzc4ej4w5RUZrmbLhasHNExqhgdmmmNM6lfTwcRfpImRS7yTXzA+9QOqg+uFZhiCemoUe9eSXih7YluC1CACkwMGf5AcbtqHqjD+BXK4XEblbo7baj+7UvrhrIsPyQnfylBXylTU7KDDrGclb/R+P5gt98Bj5p9aXGH8f9MNsdy1Ue/Njp9vuiXTRjMSVl7oplNghT2wuiydaHqWZ6IxLKe5MwSIjpbHLXZwAAA2wIVHTq3HLxuyICjs9hRNQI7vyXX3QJSDrIppCsdSiQc8En8mP+vkgDkMTMA3EtrDZy3eEzGytxIbu2zoVgQVuEg0GHE4ljVXIcPClW5whtNpe7Plt3RTGgXKBVUm4qd/NzQ4cotRRBY9BOagQ6eIMkc92exKBxBJi1q/JUIQetXSB3DZW5MIRdt3KG+W6lyO2G7lF2kks2PcQ0KTyUJKZR35L4aPmj76MtdTXDmbS1mfLcuylOGuG02l7s+Wg5KJt0jtssss2RVucE1hFSjUANUQ2Q3ctnRL261wQ5m8sZnynDwoKbIavGz0BFqqBqQ6mjBkplOl7FLaw3nEsdKkRQeFFQYcuUGgo5x6XmpDl0gzApnCloDCqqaBMxc9pIRxmy7mHlZZmUGYwWgkIYg6+fhS94pLXiL5Dwxe2MdMyA8azEukFuz39kTQM3BErOnptpX0cFtgtjNHlrVkJ1tlYY/o3UaEuMuk39F3tCEGmGMLIXKqBvnaPG7ImeNnS5N7S1PKS21qPqCFl3Dj6SudQftDWAKAagCKn8oOqB42sRhJddupmIKiQeiJPjxMEcuacRTytfZo9cYhnPdl8kokW1EhaU19ujh4nKL57I5Z5Zqj/AGjEsolyCDRu2YFBVU1Ner3xixQW8kBqlyzmoEYglEsYSY4otC5wDSsIJnQkgpqH74CWuJK8CWYVM5tpxqBp1xhtgLOXGOp4dU1RiTsGc2mU2ePW5TDfQIbymXsz5jZsUpodzAPlcRYdCKalmvtieiZzOsOtA++Puh0zbvUch2W9KJFKpW9czdRZkUUsy0PfExTbyeSvd6JWlCEChLMI8XQe0B6+aJKUrCQtDUtEE7hjD8ol7iV7/ftymHyowekXer50kFt6loBsDb+aeeJWybzebz1y7SKdApqFCG8ql7Q5VG7YoCPPuyl2DQuJJmctFAGgefZElmKrSSzZ+stUeau3ZGHGgMpSkc3LMWo9ESdk2mik0mL5sByjWno6YwenRvMFiEtKYdHPAM0JxiR4ZynVJItKbfdCLRkXFxG7NuUhEU7ttw7Ojz6Yn3zmcSNqUdBTVEIET4gnZQAP8NR/i93bGKVRMg0l5B78sanm98YklMuaS2raXCKuwoVhsQSN025jVEvPw573/Ccmcq+E0dsYXMSZyR/ITmo4Hkj8PjCqaqCu91k7Vw1hs4DV46YLZ7JaxXbC09nDgli0xUGFDKLHuVUE3ngCgAULwh066dUAUC8kOG+QcTPExk27jKFuWt1LvRSpaRvN4liSVtl5hnlJxh4tKdHKNE/7/OZAwDSW6pv7RiobzSyXl5Sy3UG3p7ImeiXvDBqy4Z3TBOSykulADVNDtTe7RwpWhSxJ5NMHEtSdIzTJE5q0sr/OEFGzMuHilhVDfUrfzWyhxcdm2sM1CzLEu+0z1SRJQPzzQ5WBu3VXNySxhRtlyZJQxu+HUuGMXqXS9JsTlrGpGK+LLW7RLQKigdkYlV3nInJUw0iXLD3/AAhzK5kwkWYEy7x/x2+272QwOi2kaThNPL+b1EOmJLJ37lhvxCZ5GcatLLu28teqCFtSKnXSHPuByajEvzZiBZYTSCjjMMPR5FPbX0RLw32VKT2d7M7vH7vk/HsiaKlaS10oUvFAtIlZxY4QO4PyxKI9cYdSyJMzAw0MKdwxKpe+mRn8wazHKzDUpZXtuCMHgBwfqqhc5Bbl7Q2fkYct15niR0Vu4yxRLW6lfRrCMOpKsJs/lpFr0QJdWlNPk0qPX2ROJT3WVbnRdgk+QNXVdXtLSCjNJZOZc3NMBWIvrAS0t7Rr2btsCFN0BqGgNMYrAzTDsklx9B9HZ0RLXy0sfJPUB4xdYbYnkrJPWwTqUcZyPKLt/q6KQIgGg2g+wdf+kw4JHT2dTJMbgMpoD4xKjb7xNOXFOImWhYVXbhikVnbkhU0g0afzSCrpzbE7BVA4nbIJ0847ejticGKSWO66xLSMHscpmo/V0HHV0Ri1fJkqwAPGNEtmErbsUW/dBHi/aibr7zlrxYNMS2XGUwpvYDVVVR2ah2xghEBaLO7RAx4xa4K3kyxBNQxol6G9WCKNa2xiJVEk4k6blS1Epqjzw9cJTedytFmNySZqjGLFDLdzZcQvfVTVENnv7IxeoBZWmkQ9LjUjEQ7ww+dLnFOz4xK5hK2svZod0Ev3t14cEWqiw6gCMHNcpo4mRtBR1DGCmmaZxMj85aB59sYtVMnI1gKXjGidvmZpIjKWTkpnBy0oETQwS6Tq26QInb8YktJdh4i46KBf8IwinZKSOOZU1Ywv39eaTAS0uUjC3zx1OJiOi9bR5tlYaPGaeJZo4cvQIQNQQ3XTmuJwWbnuQRDXwB3UxsVzKVDZGIp0aeOG6wEtTT5q17dHq3JNOXkmVqiaqHk+3n09NIO1w1ismcgsDeZeavZUsTqUryNxkODlMG0P9G1aNmRBTaJWEE1YQaN2xljop0OfXDiQyl0qqsszATnhrL2TH6IhYMHSTVIdNYtxRhNJJImUmSieyHLZu8SynKVxY+TkkA9wS8lNkKoJuElUVy3JmhJMqJUiJhRMpaUhBsg1Tym6dpIcs2z1PLdpXhGjUAaIdy9k+sF0hcYvPDWWs2R1TtkrTG7IM2bqLJuVU6rl1DDho3dAQF07gKasLtknaOS7LeWPk5JAMkIS8nF3RIQ5TkVLcQeaE2yCKJWySdG4fVhu2QaIlQbp2kCFkEXKeW4TuLCUmliB0lUWZAWLzwugm5SVQWLVM0ZCQpb3MSra2lsJt0kEkkG5LUC80IN0GyZ00E7SiasN2iDQhiNyWlE1YdSaWPFBVWZkvGGkvZsMzeiNgm4A8AAAuoN0AtPmFEQP0QoootpWUMc3SNfEtsWxbFu5bFu7bu2xbFvAHcpFsWxbu0i3xGAV3Loui7gWxbwbou4AwAV3bou4NviIApwruBdujwhgvCt4FviG7o3LYti3p3bty7g2xbFu6aAGm7bFsW7lYu3BGniK6Loui7/IrF0XRdFdy6BGu5dF0XRdF0Xbt0CNfFFsXePrv/Jv/8QAZRAAAQMCBAIEBwcMCwwGCwAAAgEDBAURAAYSEyExFCJBURAVIzJCYXEHM1JigZHwFiAkQ1BTY3KCkqK1NDU2RHN1dqGxwdEXJTA3QFRkdISTo/FglbKzxdNmg4aUlqSmwsPU5v/aAAgBAQAGPwL7i8vp9Pl+7tUzPVUIotOY1Aw2Qi9MlOkjUSEzq+3SXyRO3SnHDrsmvTaRT9w1i0WiSHKdDjtl5oOORdmTNIPhOkXsw3Ig5kqE+JuAUil1p9yqwpLYr1mvssnH4uv4TJtriBmWngsdX9yPOgmYG7AqEUtEqKajbWglxArDrBUKyXt93IhMoqss5rpJy7cU21i1Ntoj+L002kTvNUx8/wAydyJfjj5/p8uM45tSmeNozmdaPTYkN6UcNknn4LYTHQdFiR70Chw08dNrphNeQWPkzCaf+ELjj7n1/wD2lt/4EuE3MgTNNuKt5gjlb5CpTeOvkSrIt+QVmCf85Rm8dbJeYR/22lL/APlTE5mkNTafVKa22/LpdRRlH+iuubQS2DjuvNvR9zgq8LKvL62yqKKvJFJEVfk54+nt/o+6FTy9WmOk02qxTiyQ800Qk6rzJ8dp9hxEMC9EkxJOgRAzbSBW8WRCejx6ptEVxCTT5LrRbjfe2bl8MeN6c3lWlk79lS6o6wc0Wh87o9MivnIdNezWrQr8LFNyxRGzCnwcxUgEJzryJL5rNkvyZbtkQ3HjuZl8iY5Y+n9aJjj6iXs87knXTtx8nq/swicVVSRBTjfrKiJw5qS9iJe+KpnXMMN+kt1Cl+KqXTpYEzNfaflxZb89+OWlWGrxA2hIblqVeH1sqbLfccedkOkiqXBu7iogNpwQRbRLJiZEmOk+VLkNtMuuKpurGkNq4AOkV1PRbn90fovsVMScq5drMyHQMvF0SatNmvRhqlZRUclG47HJtxxmnl5IB1qFxVfVipUn3SYp5ppNIzEDdJkVY5JuP9Gig7pdki6Dkoqe4/YSVfNK3Zj9wlGX8bpZ/wDbklh+u5iyfluBAZuiKcRXXpD+lSbhxGNxXJEp5EXSIp2d2F+pnKNIyhQ45msOLBhNBUZSGtlkVaW3qUnD9FobIHrwNLyzTHpp6gSVLLyVPpwFzeqEtUUGAFOSJqcP0RXEaq1XbzLmltdYzpLaeLqe6qqq+KYDnAHB+/OanV7NONM6qQIh9rciWy05/u1LVw9mNKV+mKv+tAifOthwrkGXGmAlrlGfbfFL96tEen5fDLFOSS5CD7Eklb50xU5pCqDMqAttqqW1BDZRsiTvTWap8nhv/b/TbHLH2XOhxvVIlMtL+maY/b2i/wDWsH/zsfY02JI/gJLLv/dmWOX0+Xhjl9yJcmI/t5grGul5fHmoSTBFfnW+BTo3X7txRTtxTMv0sCfqlZnDHbI+tpNwiclTJJX81ltCddX1L34pWWaSNodLjIyjhDZyS+qqcmW9xW70l4lJefO3gg+I3b1jLsuTUIdPcc2maoL7G2/F3OrtS+qislwTml+OHocyO9ElR3nGJMaS0bT8aQnBxtxtxFJt0F53RfVfDGXX6fHoNdo0cG/F0FkUTMhilvGMRz7dUHi4yBdJVAuRLhzoGqh0EiUVc1qw1tp2HKEUkzHOHHbs3hDqs+bUHl84WdENn5bbr6/7zFuhyh9aT5V/53FTE5ynvS3FnbaH0o2z0I1q0oKg238LwSp7piL2gm4Td7k/MIbNAA248efdiPDigT0uY6DYoifbHDVSMvgg3frL2Yg0tjiENjQp298cJdbrnb746qr6r4KRMkMRY4ee/IdBlkPx3HFEB+XBNU7pOYJIqoqMAdqIBJ2HNkoAL+QjmCGltwKExckHaaSfN09mp+WJNL8jQ4I6hmCsSlXgoHOkNtJ7IzJAwn5uLuruL+FLWv6ZLjggcOfAMdRVa/E8n/2Ma6fX6tE08tmpStv5WycVn+bCJOeiV2PyVubGbYf/ACZMQWv0hLDcSbu5fqLpaW2KiSLEfX8BUREWS/LRvCW4343TlZU4LfuX7iENRnti+g6uhteXmKn8A3ch/KthUp1EmSUFFInJLoR0EE84yRoJegRTndUwtTIOi02nMLTKREF0nABkXSKTJW4gm5Ne4ktvNEU7OMvNOYtbNWqgdDo77jG5GhUpVu87qDU4y9UXBRL6E8kCceOAkRHmpLDiXB5hwXWz4eiYXG/g+nb/ADc8Ss3wjYo+aIDI3esgR68CKgNQJ6CiqsslWzL3EkXgqEOFbdB+FOhvaCT3l9h4OI8l4GvNFTgSYh5QzLsU/NMdkWIT6CDEGviyH72EbCxVAEes0nVLmCr5uPp9OHhWXUnkC/vEcFQpMo7XRthvkq+tVQE78CjEd50rokGnMrqahsX6zrrmlGhLvcW2HazW5sMJm19l1CQ4jMWG1p/Y0Y3bc+1bIR92HYuT4aznRunjWeLjMFF740TqyJXy7eN+uVSXUC1lttOmSRGSPsZitaWGbezDZQaG/HiudYZ1T/vfFVO/ytn3l/EBcIdfzDoLheNSIt09nTJnH/h4BTpb1TcBPfKnMkPoq95MNHHir+ZizWVcvp7aRBJf0mVxZ3KuX1vztSIIr+iymCVmnPUh0uTtLlPMoi9/Rnifi/oYORlypM1lsR1dElj0Cf7GnBLob/yq3hyBVIcinzGvPiymyZdsvJRE7biL8W9+y+PVb51+TA5Nqj7sptyO8/RpDxK68wUZvdep5mRKpMbIkQdqW+4V15dq34Inevq/qwuW8ltm68pbL1QY4kqitnejKqILUZrteVbdyYSXmF46xPJUdcb1uDDFz466t2USd6qg/Fw5Fy7T24J5kmtZefmw4rTLUGC+xIOTcwQVGRJZZ2m171v2WWDQ1FwaRHRJ+YJDSF5Clxyttal06Hp79mQ+UuNrK1DOkRGAjtttRnITQxH44NCINI281Yl0CCedqTBVXLct6dSb65cVwdaNN3/fcUCQHGrfbWkEvVjfj+RltIPS4JGhOsqvIkVBHcYL0Ssl+5PBQsnRHFEHnGX5Om/ByQSgCmKcPsOOiuD7flwx4oFqkZmpEEYtLnoNm5rLI3bp9W0++tmXmvee2vwk4YfplUiyaPWqTIFHGi1MyI7zK7sd5l0VEtJD5RpweBpxHhZMRspZ3lNM5gTQzSq08QtM19E6rbEi6I21WBH16Xuyy8McvV9PbhyBQWkqE1FVkpTiH0Fl1F6wgOkSlmPbZRT14Ks5mlPU+KvlDkzrDKNvnpjRVIQhxlTluIFvg4co+SIbFaqXWGRP1qcQHE4apc5A1zzT721YfWmBB9yfXJZlePToTZlGiiqWTYhN+TiCnaZc/hYblZnnDRWNSEtPhoEuoEKei7IuUWL8m7hNPitmeHHpU8xqNVRe8Li68z/6sRwfRmqnMIeShGFltfyn3Wl/Rx+0U/T39Jj3/NtgUkx6pC1czcjg8yn5cdxw/wBHCnS6hGm6fPFpzyjf8IySC6Hyp4U4/wBnzY6HXILMsEvsvadEuKq8yiywVH2FXtstl7b4J3LUxirQyuoxpryQqi1f7XrNFiyLd6q3gMyZjNlubGZkNU+nxXUf2jkhsuyJL7abarsKQoKItr8/uFGyvSdS1GraekbK+VCMZIDLAKnmvyXPmFMA3pQqk+2BT5PBSNy2rYAuYsM9iJw8EmjV6BHqlNl2SRFlBrbOy3RU4oTZgvmkKoSYmM5UozNJGeYuSyB2RIfe27o0BSJbz7+20hLZNXb4LLxTuXinzLiNmqhDpp0h5AlREujbbzq6notkVPsWaPWT4B4jVCIWuPKaF1te2xeiXcQrwXFaec63Qhm6L8dOy2xBbt3aQNfAutG4GZYLf9562IeUBfPGBUNNik014/RXi2XWFUxJoeYobtMqkIkJRJbI4Al5ObAkDpR+MX2txvinqXFLm5sJ52SkmXFp8uWCjLqNFY0twpb6qgkZEusRcVEVwB1WuuJsHKGXYtSrUZ9wZNRmuPFCgvgViYjaiU3VA+C6FbRF9JceLmzm1Fo1v4oozJM09pL9Un9Be9/GfdUMDLznN0InHxRS3RQNCcVSXUE9BfgtIP4+CpWVqfFeeD970wECMhr6cuo9ZXi9aKZevBMRymK0vV6DR2XAa4/fHB1SD/KPCG6zCp4lZV6XKu780YH8deuREXuGE8afOr4rjVDnU2b8U92I58xg4H8+NqqQJEQvNB00XZdX8BIbu06nsXASYj70eS0Wpt5ktDgL3IQ6Vv7eGN+Tbp8N3osxRTSDh6RMHgTsQwLl3p9Zz+n9fh6dW58amxFdFhH5LmgCePUqNjwupWHH7qqV7FcdRfluzh2qy5TDNPZY6U5LNxNlGLCW5q7RVC4d+BAM1UpTMtAorpjcr6bXJqyccLJ1hsI2ru6hoobSDr3NXLTpTHjCizWKjD3DZ345KobrdtwOuIebqTE9qmTo81ylyzgTwYc1HFlNqok06NkVFuK4dp1WzBToM5nTuxnXSVwNYoY6tLZIiqC8seL6LXYNRm7Rv9Hjk5r2m9Os+s2KWHVhuhlNjJV3YxTG6fuj0kooHoJ/b56EVMRTq86PACbLbgxSkHoR2S4lxaThzx4wrU5inw91tjpD6kgbrt9AdUSW5Wx+6qmd3nP2Vbdi7FlwdWpU5idTmyeE5bBErQrHTU+l1FFuCerHjelz482m2eVZTJ3bTYvvXuiFcLYCo0abHqEFwjAZMZzWCm2Wk05di4fiyMz0pt+M4TLze+p7boKokCq2BDdCTEhaFVYdT6KoJISM7dWtxFUFJCEVsWleODp1Wr9Ogzm0AjjPOluAjgbgakECtcFvhKdRq7BqE1WnHkjME5uK01bdPrNilhv4alTIM5iRPo5o1UooEW7EMlJEF1FBES6gvbg9swPaMm3EA0JQcDzmzT0THuXEpaROjTuhSXIctGHdRR5LSqJNuDpQk4p3Y6DV6/TYMzRrWO4/qdEeHEwbE1Dzu3swcuh1GNUo7bisuOx3LoDyIi7ZIoiSLZcNxK3WoNOkutbzbMh3yitXJNzSAkulVBcMU+m5ipsubJJRjxmnS3HjRFLQ3qAUUlRF4YjO1edHgNzJQQoxvmqI7KcQlFpLCvHq4jv1mfGpzUqUENg5Tm2LklxDIW+snDqtqt14YdnTH2o0NhvdekvOIDTYd5EXYuGsylminFVBbRgLyHej3ENts9pWLI6ALwK6ccCYKhA4moSFboYkl0IVThZU5fXT6Y6n7LjuAC2RVbdTiy6l/SbPimKpRXuB0yTuNivMUkk6j4fihJbX58V5pzh0sZ+j17gxZoW79TaKvhYdrVCo9XdjLeM5U6ZCnOMcb+SOSy4QfJgW2hFtpsEbBsBEAAB4AAiKIIgCckTliTmOuU8mxkSDceaSbUOjyJsi5noiMvpd11UUrJYb9mOg5aywsaOCWbRVjwGlVEshOo1vvOqnepavXhWIzL3QeRMQrxKYif6VJdJFkL7VUfVht6vSSqDiWLoUfyMEfUZpZ175NGEZhRmIrSWsDDQNDw79CJqx9L/P4XIk1hqVGdHS608CEBp605YV7Lchtxg/3lOdUDZ+K1I0mLjXqJL+vDkd50Hpkt/pUomr7IrpEAab1W4AA8+1fr/c7yfZFbqNYWfMbvbVGYJlouHwUjk9jLlNoFCgwapX8xRoIHEAkeNjSYOCmpz4b7eApEOweMptKorLV1v0Zkkd0Anb1YoivffEiXHoMEKvtU6A1Ns5v9IdJpH30u4o722BriNDcWzsHKLiucVXS94sN0xVb3XQa4nVzhvNzar0K+lNybIkBDi9W3W0Opf2DiPKloXjOvuHW6ibnvhFK1EwJ96oxZfaWPdGr9dpzFUZ+qIolPSRqJkRbJ9VJtQdTj0baTHjCjUKDTpqNOMdIji5r2nLax6zhJYtGIk0HdMeDPpuXnjPzAB+IUSUi9lt+YX5uPcxyxo1AFQWtyhHiisMvt21Jwsgtw3fnx4vrUFioQt1t/YkISt7rd9s+qQldL4z5NlUOE9SoVZGmUeEQGsWMy0T6mrNnEVDJvQvbzxmZKJCj02MMCajMZjULfS6miQxNLqS6lcdRcOG2qtPPZbkSdXmnv141UNKp6aDNH5sU2U9YUYosquyNSWRN4HqivDj9qJEwlfzTRoM2VUZ1RndKm6rhEQ0aupaxBAV1o149+M21yPHZjNVHML0aI20hC2zChjvMstiq6tI9Mt+Tj3Ra7XqZHqUJmrt06AEi5CJRyebuOgxsSRozX52EqNGoMGnTUbdZSSwh7iNO23A6zhJY7eH3a84KqdWtaISLzOTvVRqE13nqefb+TGfsxvyFWWtUdWO4Sqpu1SbCjtsFdV4msl9C9iYp7kgdE6ra65UTLgZHL6zKuL2qMNAv68Z8zXmOmx6o1IzHJSC5MQ7NRWRJ8kEQIE0oy8HyJjNdWjtdHhVPMjowY46kaajxW10C0l+Q9IsvfbGbiqsNmo0vL1Kh01WJCKTfTLgPBQIVEgNHu3jin02hU2LChZfork+SEa+nxkbbqbrikRruNpPb/Ftj3MsroG4CVFa1KHgSKwy8HniqXRBaiPfPi3WdydkV1eqaKsap1hDJNQ26rvlW+F+G018bGWMtNKu5mLMkRhWg5uR2FETbtbrIT8kMOO5fytHGpyZkCCzIisSn32myMnHHbCriXVtm11TtxRqad9UGlwIZ3VSXUxGBtbqq3VdQ+Hl9OP1maojXVZPxn1OzqzWHQ4fF3S+fFCzayF2VNgJShzJyLr3W1/1iAekfWOGJcYxdjyWQfZcFeBtuJqFfm+sSJVIrcyOLiPC25qTS6KKgmJAQkhIhLjXHocBC+E610hf/mFdTCA0AtgnIGxQB+Ybf4eRTaTV3aI5lqgsENTYFXHIxOIDxoAI8yl3iqKJz4Wxk6h1fNMzNjlPbcr5uTEMUp4Aj5CCAcl/rGcVtb3x7mGWisYeM3arLZvzZbcZUCJP4KG9jIWVUK4VzM7LkptOZMRSjM8fV9lkvyYzIo2RG6DV7JyRLQJGPcz9z5pVOGydRzJmLSqqO0lQl7bDyh5prCT895OHDjV5qKLDdPpMx5v0RDYjHsincmpERMQ6tSs91DLTVWJ+V0KKy8qFZ1yOEknEmsKRPtNIXLhhoJD+6ceO2L8g0tuK02m44qXW2rT34zZnow1y3M+x50d+3XFhs3xPjfzN6oj+bh6ssqjsKjZRiC04nEUemxYxKPZpLdqLyfk4q9U1iHi+mzJaKfLXHZIhT8oxRPlxHlmib9Zn1CpPH8NVe6MC8+PVj/z4pNEbKz1dzFCioCLxMG90kS3pIkomcZfyxEui1Cs0iksNivE48NpUAbJ5yI421iqRmbDuRoNEYROxl9xpggt64jZJhZ5Z1llTGadEkSctLvxojLUtQV6MLgyj3FB+Ty0Jq9WIlVplP8WRGsvv1nopmrh7wxTfdJx1Uu4TrjfAu1FwzWqXnuo5bbrEiZJchRGXlE3QdKOkgnRnMKRHt93DEZh55ZDrLDTTj5DZXjbbECdUdRW3FG9rr4CJeQipL7ERVX+jFNyWxq01vOEyv5gILpamQWo+1c/iAsj8pUxRMgRUc6PUc7zKzUxDzUpsePEabvy97jtPr+NbFfndVluDRpmzpSyC70c2ooCnYiPKKJiXUlTS9LpdYm39IpFRecgQjv2+T2sZeQ+rvRDqsgl4fs505aL69LKjx9WMzZtoucJuWAq2Yqgm1GaMunbbjj4OOkEpjbQOlqFuOM1nIi7uYIdVWFUK0cgpKywN1w1aYcLkiSGSU+evq3xWodIrL1CLLlDYi+NI4E4besRRyMKNvx1HcKe4l9XDTjMOTQqY1KlwaWxNqJbG00NTeGHpSP5Qy8mjuhbkt0TFHKFmNKLXqCrjzG3okui08rTu4kXpDLzTgmyBIeMnUaVnSdmhrMBPBMizhdRtqIJAjjiAUmSiuggkolwtpVPBlGTlOuS6G/UKvUGJbkPZu801CFwBLdad5EWP8YNZ/wB3Tv8A9LGaIebMyTq5GhUCPKitSxjaWnjqDbamOyw1x0L4fon9OM2VoPefshG/9tm3Z4+tmL/PiVTJHV3guy6idZiQHWYfHinvZ9nC6cMPZRzPdhtt5BgyDXybCur1buLb7AkrxAvR7cIqWVF4pZUXh3+z/JPdIzV53TK6lOiOc06LHV00FF7toGPmx7oFbULsUeM1QGDRUUd0XGGiRFXtJIBr8uJ9RrFUgwYmXaE3CjuTHwaDp7rTepttVW2sRnuXxl7xbJZqVLy1RZMpH4xobCzlRwis5wFVAnWvzcZsL/0fqw3vbiUN4P8A7sVHNDwKjtTPxfBUv8xiuKshwbffpnD8VtMVltFRHKi5CpzSXTrK9KBxwET1ssljL9I+qeio9FplOh7aT2brIRhsDFOPnK/fGZagBaHI9Ilo0XwXn2+jM+3yrqYOkqIq9VqFUakN04dIkq7Nhdt79VrGYqyXnypsSnApXTqQIyPOJf8AGmfo4q7epEdqTsKnND6Rbslt58E79Udg8UCmgG30OkwWiH8LsAb5L+M8Srj3OI1WkNQqRT+k1mRJlFtxdbT26Aq4Vh3NcIU9WtMe55TqNLZqsOiuyKtLeil0iOJh5ZLm3dvqJERFVe0rYyRlRm2qvZkaU0vzajK01crcbJ0tV/JwxSI/7JrNZpdOjsj57iAe+ool+PvQYlU5hBBOgU3LzKJ2C7tRHBSxIql0YDxl+j/VRRBdi02Ey4309nX0pxsTdHzvOWQa+GsyzXSMWl1CQq35IzFdNV9Xm4r2cpLQ32HadTjPggtxwJ6ovDz6hvI2N+Pmrit5pdBFBsQpME/wrqpKqBD60FGxv8ZcS2B4LVKhTqei8dAob3SSN0rKgt2j2xR8m0Cr06pTJD9Bo5N06S2/oCJt63CEF1aDlRx+fFWRgv2ry30CMaJbr9FGnxyRLrx1knC+IM57SKBSqhXny7LPI9LHldVLa0JhKg6KC/W6pOqZn2k3rGI2R8k/eyr8uPdBzYSaVrGZHY7HcjEZXHkQS4cFF8Pmx7oubHOK1HMBQWF/AxieesnFU8x1pPycZ9rtYqlPp7cRpqhwlmyBYNx2PsMySZ1qKqjfQyuvx8LUYMlqfTMs5bVIclhdTCyJCaHFA/NL9sT/ADfBkT+Par+rmvBnL+TET9aM+GTHaeWO4/HdZB8R1qybjZALulSTVtqV7XS+JLDshuXLmPo6880CiGhsNDLViJVXbuq37b+AW5d2JbKH0We0Aq8zrSygSKqbscvSBVS/YqYSPPjLWKE2ulpxFJxpG728lM0m5EX4riWw7KiMPxyjOI1JZft5Nwg1pocHqujbt4f5G7EokFinRn31lOssIWg3yAG1Prma+aCYqUimQWYj1XkdLqRtIV5Ujyi7jmoy613S+fD9QqWXqfLmyS3JEhwHUJ5zSgo44jboCp2FL9+HwoVKiUzpKor6x21QnLeahERESgPdyxJgzWwkQ5bRsSWHBuDrLgqJAXFOd8R6dT2GokKI2jUeOyGltsEW9hS/evHCQK3CYqMMXgkIw+K6UebugOIoGJIqCS4BwMrU4XAMXANOkdVwS1oaeX56sP0yqR25cCTpR+M6haHEAhcFF0kK8HARcMQYzYtRYrDcZhkR6jbDQI22CJ6gS2FhUeFHp0UnzkKzGb0ArrqorhWv6VsMxa3BYqEeO+klpp9C0g8KKKGmgw9A1T5cW4fN2Yj+PqVEqfRFNYyyALUzuadaATZgVl0J82H3aHSIVOckIgvOMNluEIqpIOtwzJB1FyTguKZVp0Fl+oUcycpso0LcimSipK3YkTjpTsxAcqsFmadLlDNgE6hXjSBUVRxvSQ8bgmPF9agsVCHug9sPoundbvoNNJCWpL4QxytTUISQxKz90MV1IXv/ADRccu/6d3gfiSQB6PKadYfZcS4ONOioOtl8UxLDNJp0RmLT2GyZaiNh5IWzUlMbEq31KS3wkCjQo9OhobjqR4re23rdLU4Vr81XD1MqsZqbCf0bkd4bgStmjgrwVFuhDhifT8u06LLjFrjvg2ZG0drIabrjg6x7Ftww/TKrHbmwJSCkiM6haHEAhML6DFeDgIuPETkVo6V0Iad0MkVWuhg2jIsr1tVtpLc8RqXTmG4sGIG0xHBC0Nt3UlFNRKvFSXBwaPBj0+Gbrz5R4zegFef98Pne62w5DosFmnxXZJyjZZQtJPOCIka6yItSoCfNh6oVPL1OlzJHF982yE3STghHoMUUrJxXtw+zQ6ZEprclzdfSO2SE65y1GRGZFw+TwZE/j2q/q5rwZy/kxE/WjP1n0/txy7/ovb4FReKLzSyWwoRWGo4KqkQMNA2KmXHUu2KDq+4nsvy48r3ThxVUth6DOnnWK+1dCoNE0SpTJatNp0kiCFAVO0TPcT4OC8RZIpEaJwEfG9RmTZJ/HXogU9sPZxwm5lDKpN+kjb1YbJfYZSnbfm4bjZqo9Tym4dkKcheOaWJfhTix2J7f/u5YoeZo9Km5uy1XTcYZrmXJcB+FGloKOMxZROOjtOSGtSp2ooEipfHDJWY1/wBspX/m4hxZOVq/T40iSwzInvSKe4zCZdeFpyW6DRqZsx0K5Ww28yQOsvALrTrZibbjZihA4BjcSAxVFRe5cKq8ERLqS8Etx7fVbj3YqlKj5crNYYp02RCCqQpdPGJN6M5tG/H3jEtgj81e1MVKqwMtVaiU2nyGobcypuxTaqEtQVySzD6ORKfQg0bi8k3E7b4SZmytMQScRViwG7yapNX/AEaAzqeUL8NwtLQr5xJhQynkjyaEv2VmGoWJxE/0CnCui/8ArC4/cnlH8+sf1z8Ns5vydLgXIUdn0KcM5gRL01gTRiugiep08eMMp1yHVmhRN9gFJmdEv/ndPkC1Nj8eCKQaS9FVxkT+Par+rmvBnL+TET9aM45fT2278FHrdSKZWtGtugUgBmVNb+b0jrhFgiS9rzgX7L4X6nsjU9iKnBDrVQkzHzX4StQGoYs+zUWE1ZSyoqfBE6wJfOsxbYZjZvy5UcukSIjtRp7yVmnNr99dZBiPPYFfig7hisZcqkOsU2R73KhPC4N084HA4OsvAvBQMUJO1PA2tKdZ+qytvdFoTbiNudFaaUCqFSdYcAkNhhokbS/Nx1OPBcRKJm9GcsZnNBbB0ytQqq/yvDkvfsB4/vTq27jXF0sqc734KPwr91vk+4UbJuVn1j5krUHpM2otp5Sj0lw3GGliL6E6abTiIS+9CN04rdCccM3HHFInHDJSMzMtREZr1iUsPxctxmW4sLT4yrM8iYpkDV5rbroNuG7JNOIttgS6ePBMIX90Nnxjt3Vr6mi6Aj1vM3/G+/tfH2/ycJKrERqpUJxUFrMFGJ52nCR+Y1KQ22XYL5fhB2y9Eyxm6RmqRLi5TzpT/F9IpFkUHZcR3Wzm4Wnh0tSadKbtEMSHeTc16gVL1XK1aDRPpMlWScFHEZlsLZyNMjKdtTEuOSOB6l772+W/JL96cVReWP7m9ckp42obJO5dddXrz6K2qm5DQy98fpKl1fwFvgLj6l6VI0Zlzcy8wigRC7T6FxanzrjZW3JFtlr16i9G2KRleihrn1iU3FbU9StR2kuciU9ouYRYkcScNexBX1YhxaOy26/DZbouXIjyonjCsPA689PmoJIShu7kiR8K+i6XRUmV7MFQeqVVnmRyJTypyXzWmgFEBiO2nAGxs2CeaiYi5fy7T3qrVZ3BmMzoREBtNTr7rrm2zHjtjzM1FOXfgHK/nqFTqgX71pVHeqjDXC9umSZ1MUz9W2mHqtTHYmb6VH1E94oZkNVeM0A6ukuUpxD1h/APPF6sRa/S5U2k0fKzgyK3Uo6qHTN24hl7lpkeM7LuifAGkUuC6dWRP49qv6ua8Gcv5MRP1oziLQsvGgZszEy64xKshJR6UJLHdqSCSKJy33vJM35WIvRsr0qW+9KlSDJ1+TIcJ5990/PceccVTdJV7149t8PBTXYtMo0FwQqlamXPYIuPRoUMSBybL0cdPUb+EY4QTznmZZFus4EektxyLvGOUZwv+Jh6q5VmDnOmRx3HYrMc4uYGgFLuEEBDfYniFvtTiOL97xU83rPm0bKQI/S5FOJpNGZKg0ulQcZfEkaapJ+c+KI5r8mi23LSp810Y8OFHely5Di2bYjR2yefdcXsFtoFXFRzCu63SmrU7L0RznFo7JmrRqK8Bfmua3nO5Ttx4Lj+n1+pe/8ApxFy3nA5FdykhAzHkkW7V6Az5o9GdVdU6ntJ9pNUNtPMJE6uIdVpMtioU2ewEmHNiuC4xIYdS4OAXd2L2iXBeKL9wc4pLVzyZUUIu5fSMRKHTiYRv4up5V/GUvBlVaZsbj5VRyqE3bcWq+MZYyOkLwPebYQE70btbhj6f88ORJjDMmNIbJt6NIbB1l4C98bcZdRRdbLuVFv24ZiQmGYsOM021GjxgaZYYZAUBlplttBaFoRTq24fNhM8USNrzFleOfjBpsNT1Ty8Gp95uwrc36Waq83wVdtXE7sfTmqf0LfhilZioz6xqnR5jc2I6nLUHA2nU9Nl9rqOD6QKqduKlmqsqgSJxiMaILhFHpsBrhDp8Yj9COPnLZNRqRW61sH7oFYjWrOZ2Ebo4PNijlPy+JoqOguoiFysGCGvBF2RD4S49zltFLoq/VOduKgsi1EBFK/BfJl8yr4M9v8AUXMKeKGutdHm6M50pzyaWRNL05u7nNODd7cl+nf6scu/ne/rROfP1YkpTocSF0qS7Ml9Djsx1kTHkTdlSEZBFfkuhbrFdVS3qxkT+Par+rmvBnL+TET9aM4nDIVxWmqDQRgo4q6Ejkw4Z7XZoKWTuq3pavAL9PfkxZQ+9PQ3nmZAKnwFYIX06nBezAOZSk+6VUGGluCyWahU6X7F8dA/BXDb3uoxUh5oWoyx2tqAypU5Aj9FNxqmm5GB0+vwSxd6Y6oiCIpFwQUTUZ7jl+SazPrL8biuGfc2o8kfGFaAJeZFA11xqQJicSBqAh0lVHB1En+bh8fE/wAUUybU/FkByq1BIMdyQsSnNk2xImPo2hKkds3hTUl0S+PVbh6+/hz4Y+ns/owz7ntWlG5l7MbpDR0fcJQpdcUdbbcXntsVVU0qHBN2ypxVb/cCN7oGV4hzqnS4HQq/TGB1yZlLjGchioRWh9/egbpoYp1iata+m2PZzT0uC8eHqw65RSYn0iYYlUqDOUkhSiHQnSGib68OajY23Rv8YSwin7ntS6TbiAV+KTGru3/FiF/w8OU6n6cnUFwVbchUqW6c+aJcxnVdRZcdb7wZBn13xUMj1GDU6hRaS2sqk15GjchUkjVSkUWZLLSgrINzcjjdSRNScuKEJcRJLEKoiooqliFUXgoqmHpVPjqOVMxuvzqKbaKoQ3ys5Oo68VQejOuamU7WjH12xGhSmVXLlH26pmV1dSIUVsrMU4FHhu1F/q2+Ahnx02VqPHbBllkG2mmmwEG22mhQG2mwGyADbYoIp2ImHaVDVluv0t7xpQXnU6qy22zbegmWsdtioMHpvx0KglZbWxMpVZhSKbUoDxxpkKW2TUhh9vioGBJeypyLzSTiKqnHEbMWWZ6wKlFul7I5Hkx3CQnYkxhbBIjPW4itvUt0w2NeyI6dQEBRx6lVkAiPmiWU248yGRxkXu3Dw5Tsn0NnKQupodqj07xrVRD/AEP7FiQ4R+vS76rYSNt1vNkTNz4t5jiAb9RmbikmnMak4rvWpurypmojs8FVOCpkT+Par+rmsdnz4zl/JiL+tGcQ82Zca6RmDLsR5iRT2xEnqrR1c6RojL1dUqE8pkAcdwTVEsuCAhIDA1bMDTSQGPAhNPQJC4WXjiRqpESvUSeSOy6e4jDE1h9E0jMptQWO84wSDwJriBJ3LxwjrsLNzDulFON4piGQl8HcGp7K+3ViTScg0qRltp5VByu1F1l2sKySKGmHEYbOPAcW/n7j5p2Wxm2Lmdiq1nK1PhyK6FVu4+VMrL6kTVOkTXjcERzNLLSynFelFcUUTNcVfMtXcV2pVmc9Nf4jYNSpsR207GoscRaBO4BTA1OqR9rMubdio1ETbBHYNPQP710u9tYqDB7jgrxR1xU9G+KnmylVqjZRzY1DkVd7LZS4bIZnbRxUNyDSyfafanm4i9dkSbMk82/W8GR4NKB0paZmo80iZEl6NFp85ifMlu/BajR2tfs+b7g/8/7cP1Vpt/K9fe1E5UqKDKRpbypweqNMNEYkl8ImyYcPtPCnRa3lqut3toNyVSZap37TzMiN/wAfCA9Ey7CEl0q/JrTbjYp8LTDjyXv0cBLz3mgZzIaVWlZcbdYbf611bkVOam+TK9qAyBfGxGo9Bp0WlUuGCNx4UNpGmgRBRNS26zrpW6xmpGfaq+CoRa80L9RqMyK1lhoLJKCstOC8sxorKrbUWEh7vYQlo9IVRmLGaN6TJeajxmAFVcefecFppkRRF8o4ZoiJ3riHSjQSrtQ0VLMslFQtdUdaD7FbNES8WnNeTDvW5cNVvCIZkpqjUGR0xK7TiCJWoiXugBM23EfYRftTwuNpzREXjh5zKubaRUY/WViNWmJdNmaOxs34QTmXyT4WkPZi3i6g2++ePotv+71fzYYPNubKTAiXEpEWiNSqhNUE5tNyJjMKPHIvhaXPZhKblemiwRp9mVORaRV6ifDrzZyiJGl04AKA0HoimKFATMX1PeJZ0qZupTPGfSekxxY29HjCn7OjTe9yvj/GYX/wp/8A0WKzVvqr+qHxtTGqbseJvFuxtyhk72740nbt9NtOkfbj6fP7cHUpcd+hZgNC11qibLTkol5eMojrbkWdb4VhdX4eCOg5hy5XW0JUBuT0qkSzHvVtxubERU/hcIBQqBFFVVN92usq3ZfVGafeX8zDb+ec1sHFHiVOyw08Jv8AG6CVTqDTYtAnqjqvrxl/J+WK3FyLlKhvOTHKTBoa1FyqVIg2Wp8+a5V4ZyHo7BEgq4Lp6jUlLlaj1up5ufrsOlTWpx0c6I1BZmuRvKRgdfCqSNLIyUEjHQutE08L38ETMtNzElIrESls0lYs2IcmmyYzD8yQ2u4y4EiK7uTFutnEVE5Y6I45lpqJrsVX8auHG2/vgxUh9OL2beHZLbxVnM89kG6hW32m2xBvWRrDpce5rCir6fXInFTitrIn3GN10gaabEjcdcLbbbAEuZGRWQRFEvflbEyVGfNct0VXaZl1i6C2sdgk6RUVTginUn+t37SAN+rg/dIrTCrTKC+UfLrRiu3MrWhUfnJqTSbdJArCvG8hezRx+n0v9b9P+ePp/wAv8N9P6sc/uPVMtyanVaTEq8ZYUuXRXYseodDct0iOy9MiTmWwlN3Bzyd1BbcMft9n3/rPLvD/AOluzFLy3RGejUukRG4kRtV1HpC6m68SaUcekOkpuFbrES/9Jf/EACoQAQACAgEDAwQCAwEBAAAAAAERIQAxQVFhcYGRoRBQsfAgwdHh8UBg/9oACAEBAAE/Ifskfw9IHUjCOSr1ekTJXWo9mMOLZiWpNSxU/fDVK9ChfxbEGIzmjNgO7uxujLAI23rKCoB3yaZdbSOwPY3gn18Rk+fZ/wAZPj3/ANfeQsl5gncGLJ6gZPcm2yeWwZaKyq5XQtrU9MABkkMNaSERvIZstG7/APFhvx8ZLbJKr6WXvj+BWl9cNGBafJleqlj57MbfxWNNCSdi5GqvCXFzHO4haBfo+4DiCuNN3AFCjbzcCCGH9hT2SZBI3wbBIQnmGnKCCZplYigoMY6ngn8Ge/smDTYKmbGkCJ1zGN/DW3W1gA822QMokaNVY4FBvIHfFO0HX+CgXpj2NpoDaaTGOXRzQRJbOSdPuKk2aRikSx3R63hyo9B0j7EiMWHZelVc9QlzLNR43HGg9WWCZvSbKgnDCm++A5fufkf5MVnzkrwi3G/PQQwh7p2C8aJAsxCn1UtM/cuJP8mLhkX+oikgqk/TlXe9Xr1wjnyuPDklCobSnz31R4PxfAY+k5pMnxP+Mrkf9DYOLI7C+mKGPN9z2QL3yHCfFs9jk+zz0/OEmaiRsTJF30TdxLWhwuFU9u9pDIZRwQFu6fUbED6RBwgS5Ugxog1iofbOB8RxkB8mWj0jPEA8iWCUaSHi662stc0uCDxLE9lnP3WIr4PIi2RPagupen0bDHvND6FnrD9G2S/ICEZcBMmCRfxYvLolicBwqj0b92IyMb1XUX91zeoG6Oo2R09/vKnyz1z65EnWNsFT0y3TMVLejBDLWMY9hYynj1Lj9N83cVsgiEABJsJgDwpJ9j51r2fECp3ygDxe5jF7w4e7cJ1hkiogGTBlOUHciWJNt8TNBw3BN2MhEkLIN98lB2CTEKPbJ/OMvDM1NKXc+xrDk6LGzImfRMIJzjEIQvKS2lixJQUcd0GJPbn7khdFTOvDCcD8vp+t/KFZCEKbwMApp5hy/ft6oUI9kHIYBhjqQKsn6JnDqVGi0kG53+w0nhxe1zmkXo8JmmAPQKgcNw5Tk6CZiFYfPD+5s7EnyO9c42+9TsP7Mon7aSM6ZEREW9CBxmPmxVba0xHKlhjquWF2WTEgSXR5k+wygAJUADaMANz5Yxx6xCBCjvOoy0ZQgblGMoFQY0MtleA98RFFfpXhNWFuMZBNtQLCIeB415P/AETgpjVMgeSMHTVisLp6hPpJgREIf1AYm52VjaPoUrmOzpXNi0aRke6XqZoMYCTestTIExIFwO1bs0TJAzyWu9MOG6DMgwXALuDBBqWU1lvo7BxTgK1PvNwRPGXw2qjoFk9eduPEVmeD1WXVKjsuzDuCeHGvhvZWTKb/AKb/ACZNSr5FS5D3dZXRJ+HO/wDPPT6BACIIgk9hP7yGUfgsgQdPYiP5tv7/AC6wysyVXqcyMdYfsKyw4NsqOaDuLyjfB8FcJ06W2c8+vXTrmJMGJ6OJo1qUhi+rE72QOARhiTu/ouIQRASfIR21krrdPVkAFkKKpjCVn+0CwihQ4TFQr4Ir6/3JzjFbOYh4gTnHpmhBI3HvYX2UdcSHBGOojuNmPk5bjH9KAFaEC7bBFw4eb7B4K86eEW0vFKXhiXJDF9FDqwn2acKUjOpMEEsQ/wBEP9zw0QoQR3LUnnE21jbEcL1Nu+CztJUcvPlU2SfVO6SngN7s2BiXJ7/EoQl3/AgQKNRBHryvOQrUmqo8Sqe/0PlpXODMk7rRkkVLcLHUFFZDIe08sYwamydcGvma6wgUOubQTHFqGyQz2yLI35GRLkeScn3xsLrpc2IZOhkJMPkxbSYbYDJeLvvOLTVU2jc0PWBcPoI1eYlcO+fPwwzP7xm/jbQgkj+3cMXLhuiXq8XLINoSHC5rjOkETwlTJPtksNvCXPISl1k5YmqpiNACKwEITxVi8NstmJ3c01Mq/wA/WvUmqzq2ELWRlvSgdHOJgcuwPwEenq5F5H+OXzxLwGo7RiKPBMSuI0Yhvpk/vVCG4GgE7jKirkUwj1kixkyQkSIdmPthUTOkEIWQgBzZKUogNREZANcrWUAVWVhVf9ApjIKXfmL4p1ps/lM4EOxUzrmiqyCsNzoUhpHTCwqtLCzYf6E+nlwBWo9RT1w0skAmi75YD3F58GgwIAwGXy8vjq2bASj2y0y5RnCRQlZK/HYyzO3GYRuE8hZgj8hx0Jruzm2zm42ekuMh/pmPacCEf89tY3ITRfW274IhPatu0Lg2boTI/wAj01IsH85124xpwCYeSMJj6NgjaRlIxjt4ZldVIE4XwNZkOY7jYDG8nyoVRbY/Ft4/qWKmaksByzRnLtaSaaLOpMmK8U4D56vsiJ7YEOQAN1iTHGWLI1IrUJSOsuMY29YEUGFry+y2vIAgw1W+ecMNdiYcYEmleJzbQLBygxJNxFbx2jgxdyBA488Sa6kieBqdJfmJjjASFBJkZLSy48Thk1L5qAepmWE9LR1FHXep7T8i4Hb3GYuPFfWc41Vy4jcUzVYrDre+CYiQwACsYxnPkpdbluRbdYWaCg7pZ2EjC4fURxyRdqljlrGithoNqRkgKBqBxMa3EkkkYEInpBMqWkS+dXq22QCFIWBtmOBf197tCmldMR4V8pDQg5GYFObsnG3NxUyzzno454fmsk6nvm+yTFDvbgoj6x+/vXInIKhRc4BI4aWvtBCCnKkZUvAJEPrvo/wO5WG6vm8nnKabEj9k4zUD8L4Ie84Rm2/WPAz/AC8/e/iYw759/gVj+b1+xfz2nGO69AIfPnAyzoESUzgL4epUgGm139zlfCQ2h+xIQZWIgxjmgIyBsQmEjAABVheB/cEJdTCNTqxI3pzEx0at4l3lv2IfBu1MSjq5APWawgKD1p9I4p5nNUBjLmT2DbXT7xvdY4m0CxY1ulkNf2qO9QPCr6EbxKmVnOYJJO+LWI5iBs7pUlZEX5AM+oYZw5/MQMGYoiFVXphhYU+FdabZzY9LzIVeAE7foJOHBYjhDgxeHhoYE4mW75XZklbeAU3gxIijHtAQiOmJNtZDboS2UontvBCGc5gl20PqOOMy6JUgGKgDWZ4xoxeQTqNgULBAYLDJIZr9vzwM0OeJNm7uSnEpeSx8iYIIK0PSOctfigvFYFQgXId/xd+rzha6mrPMSV9I4Z6LZbxSjv6whekLLSfA0E4dskzlCbQJD/pdI4JK+7Obr2pEznH8O7kCdZTwBvAIIyiLiKbtsk7/APkU9iLkDScJg9I9mKoqM4ImS7QQA6QuWis1Ee02oXmeuJJ06dtCk0DYkpBeDNqFOe5ZOErkTR2gDOa8FTEqnAkv7nVtqkdcPJvMl1NC05xZxlhm4wod3bXGRzWuomBmQI4/oKaAwhCMVhjhyHC0U+PCi85RCQJ2DmsCsnnIOf21UaJ4gCE49qjC0QS4nHhzIlj2dKhKGaJ2ZSsnIehmBLZnJxlY1DUt984Q6+ggXThLYxQ598nhKqNekEemx1YxW0oNMo1Tbo9mU+E2gcM5i8p1xrzXCcYkGosnxZBhYhERvLf5McOxhs/KijzvtlVxYNvAQDY5uW7rmaZgUjxDz4IDp7gx2rAEe6AQFTtykjqDs1K/TiNmxFnXfT0/liURG6UL/M+iBs3hEJ/+CARXh0SMjqrf6ZaUBB2IFwAnv3WLhQ+53fhyicTABOrAg8f+PXxDXKYyKJwM24Jz6FTjERV7jwRUigph6pgxnBqGp6XOV5V6FE0MSCiqOCRbRtazlQuBvoi63zgBLIDBJqBOCHXVCtO2HjCFZ4sQsQfuwjxPmQxtCdgzm9t8l7C6nEUDAIAiQ0RPBnB2IRVoPSqEM2037GXeFQ6GTKNAdDPVuWskVAIfZXbE0d8j8I2zeRQu55w7f+W4JtZwYcIE9a15PnJ7b9PeYzexKj7PbHbNoD9XEk3xZnDjvkgzZFPuZADJEZGnCw4bSVJzkHcwMWPOQsvIF8IG93lTjNkCX53MxJtnLo5XKT2wTPOTBtsZGvQjdBjrjMeUWZeHS0EYBJ5CiXM7yLmyUi2WEqqav+eJQ9FuyIs7XjHL/n8jNJl7tGrAJ8P0YhUEZEREIj6zkeGdCiJZLt0Z4f7jccp6fYwlVQLTLB2TggbreQ5tAQ/siGzkSAgMhuPr/sx6/OgcPvYdSBcnabXtlRfYSFxjFQFjGWsEcxfE85R29WUCAtQYg0MA54PybBwjZyIdi6AZdDGzRI1S1R8+7HvVcL7kKliA0AMB5zRRN64dQbyAZiOk9FZO/j4g9oZwEf8ALMbkWceOE1foLrRr6N/wxKJwcvLF0RwPwyOXJTpkWtJ0QwNFvZetP/bwxwmVG91emHuk7LfRWn4KX5OdHLGM7R9FwIZ0NW2TSAYWwu3KLV0zlnlwgiB2Lk7Dt1+xPySd2OssgBGjh5BlUzDFKy9cdTRnv8+H07IFOY7rKXMT72AXLTJPTtmRQegemTcAzXBMEoWMWZl0OQJowRyDaUspklSXBT0yf1OGXYtyqrqMOKX7lt/6cTW3RcIxtCLqC0UzB2VkLSMPMMugyZGXWivBnZ4CQVDfwDBhSmm8WxkSl1p6qffBU7sC0FXFBm1oi3AqMkOrlT+DEofUU6JZygphq0CUva3I2bBHMfD7CRyRYlLHwYJk7TyTRwuzSGDtNi1IxFqdEWZMabG67sKu2WPbIXxcu5eGyizOATw2OmocBruWe4xpQhopTlNYzlCn1jkekrQgBIgfYG8InCTG0uirqB+gmbQb74LiQFEACVbqvmZn/PDoaJfYJju0bYWoAZqGihCEjE4WCeqwBcI5EkadlC9AJ5hS5vUZNpDGSkliW1OTMd/IDNBPC96JYqGYSUlE5SUphTikBkRybgOZ3L9N3LoJFq2w0Bow551w7iD3QdsYbV4YJqVaH+E5xk5sIBolFnE/gxKICPWAoZA3oPoGNqgY4NxkJnWPV3RB5ivrkeYMVdOgJQEUYZG8rqQadHuDsuM8Lhoi09VrCIIsOpNj2orsEm4rcmxTEjQRKe+Y0OeIOlaE5ELwncYpIUuMiDfMI+wwzDEZygATSYzlHKNKREtBv4XaZFhNtaw0EwCOCUf8uAx++Fjp0wjtkwWzCGwabHg8q5SMoEHru4t4Um5mRjJMZSAk8mboA+gZmdbN6KvnJrDIOFynnE3BoYztcLMkojde3ccVcSkHBeKcwU5lvrmIqORSAJOxAqocZuUPXIoWCI7sV6kwkY0V4jdYUNrOD9h8U56/t4zY300+SMYeM0c3BJgmiheuAlZL9JpJqJrOSBvZVqLDMdtqh33Didjxm0JbMbQSlkER0l4F8m4oUK31JSKPxCC1EbkqQJmOwFKGyDpizKAdWrczBN+O/Hz7Y6t2ylAGRJ4CMP2F0S8aRzLECJ46eMFm0RJpNm+SVgI/F20b/GPIsAlJuHBE8sQSh0pYe9ompy9ncCrL+iKi+vWW08MmRynfOE7VVHqZDC2nGXCbILjqpBkPxXFaebjNkBBioWwF/Gq4HZy+dgjBUlLT2ZRk/v8AhUTezdWAiaPNSbU5jC/u1zT6byiDef8AIYShZwP6aNxM9GRZlWZ1VzJRmDfmyMjOYI8vOntnVErHpqx+4OFO8AR0ShGU2HXa7GNFejJcZGJAIVsrNMJL4WM7oS5/gCJE8ysW6tiLx2LCvSvkESxDeET2tzoEkdLzh33OpkAiiY4pfY7H66jxIZ+qMjv13u9XM19A5jbQjtnJUCqGHJ7JCS0NZxCQXauEj90h38AyngJ46BX66/w0iX8/mTPLiOZ95/LIVbWor4Ift9I8ehn7z/nI6q+Y/oPrH/NHsVn64/Gcu70+GZyO8+hkePb6erX7C3nk/iDoUrGXRd1MnIzJf2cDw6qgnWtCTDtk/pwACCBLWKO2MrkjCDtBnCpzX/0n/9oADAMBAAIAAwAAABAAAAAAAAAAAAAAAAAACmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtmSAAAAAAAAAAAAAAAAAAAAAAAAAAAAFt5LKAAMAAAAAAAAAAAAAAAAAAAAAAACI/wDbcAE7AAAAAAAAAAAAAAAAAAAAAAAABd352ImwHs9YgAAAAAAAAAAAAAAAAAAg5A5goDQFx0GYYAAAAAAAAAAAAAAAAHCZMAfgbR6F7BnqAAAAAAAAAAAAAAAADt3rwpVBm1wT6HzQAAAAAAAAAAAAAAAA5go4/B4sTeazQAKBfaJM6665soBMQMnPoAAfoFgECC/JAAAMfcv1xhmlrA0Ou7jdggAvoAP+AAEwAADlG/vKoE77Imwo4W746AczQAAAAAAAAAFTBVZ5lPwoDOJgVogHIAYwYAAAAAAAAGhqyf3T7ZvE+VwacaA5AHg1gAAAAAAAAAAAAAAAAAGUmgEj/EHIy0gkgAAAAAAAAAAAAAAAA/b8fEcfbg5b+4n8gAAAAAAAAAAAAAAAD8kE7cnYEHJ7Agf4AAAAAAAAAAAAAAAAHacA8g/7A5b8yYDYAAAAAAAAAAAAAAAEGYAHgkz0DcCckk+AAAAAAAAAAAAAAAAEkAkgAEAEEAkEgAAAAAAAAAAAAAAAAAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//EACsRAQABAwMCBgMBAQEBAQAAAAERACExQVFxEGEggZGhsfBQweHR8TBAYP/aAAgBAwEBPxD8JDtUO352dE12q7VT4n85t+t+vtFfaK+0V9or7RW98K3YeD/lQzeO78j9prh0iiOpFG18Knn79/ypLD83lp6v5I7unPv61vk9Crs7zr5hBr/oFe3+Hoga9KjCeNs66+3UCCkWh7i0tOHiigyf9GL0b/fUtxq+2dpNcOV8YfT8R72KdZ0x029RREviv7x+qBkLV/Smejhqf9b/AJX7Fl0WEdM497/FTNv6xH7qZITaUPv0eMeY2MfzuKop9nSEflPUvmeyuwfl9+g3MsI8NCSXdI9nxq1wMe8Vo++P6GKnAz9idTJqKY0dLmUjZv2/B3WznTCI25p3yg/ep/Pd8oPirFP2x/ag2ex/v86uo/q0PM6vVjGwdb8e6mID6fbURhGyt9sUf0EcGAWbqNIy8tDsAYu3ZawzpHETzpTqdhsrmEDzj70vMP8AtSaI9mSVKZ6HYPij1od0+acrf+pWWTPlxeRENWpjOy4jMhIdawIEzvfr8tFFsQhU0QwG1eLF+BgbJ34qW/bf6qlK99+H9lT8byUp5ZEVPou6aknbE/r8e9QS7ln3g+OjOtR28tfUrgFa0SCmGOhns4fZXVsO3JmyKEfJgmWlvoq+mad+UKT30lWJp4GPb9BNnNI5ZJU5uKjNdjSKHEyKAJHIJ80zPy5/RR00KrmDf0kQ+cD0D6isSDJI8CWWly6eVZyC+jPjI5UqJ6FzOkZMw4B+BMU0BePvlRoDuH/fmm+zpJ1dCsA0+EyCwed59CgbyOn9orixY+/50TqrvdM1f0bpNhnv0inbVAi9mjzR3UJEHIzAMDGi4S0b0ekYnk7AklKDIRuPTTVHRH1bULPfrdQMifTVRULyT8RR9ltpA5z7HgSQR4e61CDb6HLW127lggvmey6F46++Vrm9RVbwNjRiZ1MHcKdywjIuWCf3FKwAwlzmEWddNqEnsIPcZW0U02succkFmzLS+skmLch8lN17WnmhO+ve071NzECOuxGbXZ0qF7aCmpMWJVvVsIZ4zP8AulFfwpov9BQHWggLSG7FXc+I52EN8G0d6IUV4CMyYNNe2KmnJhDswyI757U8shbZcYfRF4zFqtNwW/EO5OKQWnU9tPqjx2gkYiRZkKLoWlAB7ptBLytJI71lE3BHCMFhpeW0jS6yeTNkkj2TzVtsVndw+g96vAsBsbEwL3fZUoOEpQJsq7EG5AmoDvImLOoQXWf4fEoJDjEQbCjd3VAcjUurYMRx+6Dn0XomH2aRPpeJkRvr7eJddCxZ5SfNHtL6ufa3vW3Lo6y79Br1EoxIenfVSlDtbhtNodTY5c87vivXC0Hw0K81zWuUyIedQuDiP9pLu2v2/wAVdBzmI9pfnxykvxK2qQqtgaNBK4YDZN0dAS+4sMnMGts3G7zKVpK15+i0cOCzs6ZlEMeTgAiyEQIwODib0X5grkRGWjoVJMOKv1mkkrEBECM4SRmhKjVoggYiSpSvYuKPlInJj+T2o5TCNIkEZg9wdqlHY8YQcyqy6AYUhM5l/wCaU9jMStEoB6AxlrBV0jJhJMKstiS9IWQLIYAgC/NRmsy7C2kpgjN12Ylmp1Pg3BdtAklp3i+lJS7oYY8GQyhJerPNDuxAhyIHCE2EjXMzk/bHUYGhnUICILS3ILZop0BS7qE59EsgWaFTLOYQ2wa4LxlF6d6WBYRBmWUzsmrX/WXpAXg6mY7qCkaCrQSUjrCXm7xYdoEqGjkOIoQ4jLmCC8vdG9ZMGlIe7wMchAoRQdbsJY9jpdcsRJmoOBHhwQkjC53miFAqJcWICLAIPGaFbKBaDgoFz1r4EXb+KDzvvasH8LUxEBHiWpL1qDs8YoqDS+vNL6WbRrNQNiuBsVzrtgtId2aYnmhh6RsE3aC5L4wwrFyLsYyM3ZNLecaTntQsQmollAXIC4MRmjbHtJw0kmCZNE3ktDTPBx9w6ZTB0m5iJuLZhJFrE52VuKFsqQAUdwtKU+l7V/GGZmwLxd2jxBMbliCFOBFyAJY5siG4QZVmwJiiE1ftqoJRCDN0JgzHlkjr1q5YJ0E2aW9EuRkskWZhjEqhyxG+Uj8BqEE7FFYYlfjm1AkF0RJQgWAQjpuwZSUJgAwuW4NtIEGREwx0nsRH+/yp7ChwnF7Aw0x01FDHEpKOVb2jhqL+v0krlEk2rvi3egkwiIl74AFZNrqcVb2+lhMbCLIjK6iUwsNvyGCjNwKL0CGhEUbpeZDGdow06dV4ziyLEjZdTsZMU/VJIAQglFEfoF5fCEi1E5bwlO5wrrm5ppt9C4IFj04Vw6qCSKTgp+X6fFEpOCGPL+1NTvG3pH7o7wd0/wDyCwZYuFkCZqMpYqGZllETE5WMsTCxQuEi1rlhkPbyaE0T4cWrO6UxkF7nGgqFmliHM6TjFszpV0mTIh4BlCq6Upp/FPgtYQWIcoZdyF5BQESrCSHTOYKIRR24UiTY1S2LXzRE4eMgE8wC5YO4xJ7+BKYbRCxr2Z2SpElnqWQsnAN7LTJ2M0xG7CAhYLJjCjAOjcvCDLaDeh49oCYLJmSwGN1AkMQz3iAgSm61lAhZZMMC1yO8s0okqeYEaSRcCmLpTFSkrLoNUwgECx7R1XLKEkRzkkhm4nEM0LCsGogMibMhsMkKcThBiYWEiJuQoBQAhKkikmDEtogzS/rBwGkJBgGgiyDwpnSXEgWbA9iovrJCIXGILvkTkrfbAw0cLRMr5XWJQWMaHgZDNkbEqdlfMCMsF2VVlrl+1VBLOAlsXHawLX+WRReVWNC8yqT1OqsF5j2/tH6D48rnqdLW9gT7SfNR5do+T9Nd93h+YPj/AOOW9+RywABYbM+olhLlwNnZOyxiu3Y4E4z3rZt2vDz27IihL3oxYWCXRr3X2707Ch0KwRKE6RwKKO1o3JjYD37Vr7i43e3e3lSOEgBYID+jedKUeHNX2eHbIaUXCwlM8o12TiDNHBrKXcxjm81dVS5HcZ/w03aBzD9w1CS3zSEpIXGjcOzEkphagCjITTyCEPBQpGfWN7FgszddaNvRMPJWd6x4DXSOVc58nv1ZWRRLb8ku4ibZdFODoS442BEgIEWiijG4zSgA487N8VYcB02ISYO4Q3oVQAxZkwOB3KNPaAlwwEzzG86Vhlap3YYnQmJ9KcCiUA4ECloLalp1piu13wwJw7ad6GaKAOKSAmzEbovcpHAOy3OCDSnUVvW3DZ1vZ8B4BjrJY8inY29B8P4XhXCoVw6uHXh041wrhXDwHThXCuFcOvCuH4Ph05Vyrl4OFcPDyrl4CuHgcvDw/BaeLl4OX/iePh4OH4Hl04Vwrh15dOXh4Vwrh4OXXhXCuHTlXL8JyrlXKuX/AIcq5VyrlXLpyrl05VyrlXKuVcuvKuX4jhXL89y//Tf/xAArEQACAQIEBQQDAQEBAAAAAAABEQAhMUFRYXEQgZGhsSDB0fBQ4fEwQGD/2gAIAQIBAT8Q/wDRYRmMzD8+qRxxxx+ouz0BEQBSL8iUX+lOmFwHz8wcUC3FTH4C35JxzlKcFMVJyAPvB4Is6gOqIg/XwsJ61w6O/LgVVMn1wg6zd6eINEd8E5Nl2duIDD9H7gAFjcR8zyVCfw88HaCCREcoTgHL8SvTW9lh0DfrEIEwABC4zR8nWE9kUX89ANyvpdqXA75hDADMUBrhgHVF7DCLv8d4BUTA3kMQLLlZktBpOzI8qBeWqeq8JTOS/Sb/ALYx7GOd0D2i5vNLYPvPxLqG74imm/eEVJZ8/Z2gq0Of8GgsWW8Hc9CE71roDrFHSEX29B/RMv5aA6C+0PdeQdyOOI9QV6lXtNM9p8R5RyT90n78QMBtC8C8e8Axsula0wZAxlMA920SzsbEntoszTM0p5DN5XkEZ83vZxLg7XmAJQo3ct5CBUJgKMwy89C4NJIc7aWp6z8L3hh2BrtXzDQ+859ij8EpcHIBAAEr+KTHx8DDmo1QB6FUcaGu0zSHY6OjlYUEX7jM7AYku0ORgRkaS9vr5wMNwwh1u44KKI0wurqojWhsMZ2GgcnXanoGoh9MoHVxIosJHMIeGMDyCZLUsrs7zS8x+gDAUC/WhmDEG14GhAIvH0PgTGTkEGKPsvow8SXT3gAd3ALN8CE26woyqUPiVAPongoYvIpSPIsfgg8GC0LHzIAvfZhCUkdhJIyY06VsIQofQBXhoMvHTexS1plhwZV3zKVQnCi93Z8UeAvS9UB82Ji8qdMIIEHK9Q1dYQiLB6MZAvmjya9gYDUHAYamg6ER25Nf2oO8+CyCIDM/5LtDvXbepLxGbo4GkjW8AcUCVRmEyxi3jb0FAQeqBcR0vgAC0DV64QGFBmglvJPMMgAAzgwEbv2i/s8CADWNsesJx7isj7TvlNDKtQdIBpjHevaBnrj3mBoVT+IuY4GUCIxtn5+9YrPKUECQxtlF+MgrHpEGALQgsaNkePaaAffERDQq518HfIHArgVLpE5aY/cPwtdmRvB4kqqigUHYSHyljvoagwGKO+cFKszC8QOJgRAdRb3h96En59vU6gFrVDAjl3rDKgCqCwpVIxYYIAoQ1DBl3fLX0gxgBwR1ULxzLoV/rB08P2sPMv2Vve5+Y5TmKjec9pE+o+YLPqP1OkMXgxENAL+T6XlqyZmrWghst/XdAG4KWhxdZYlL1UXSxYMbyuMEYFHdVez5wA4CNhJxP67xnOoRdmc84xEmvieBtTatrwdxYOaHBDHWtLz31ydRNaGpwdo+YDStlMaV3gBDdr8OV3ym2KVFusx3K9NlO3eCAQtA781KomlDQtbtBjjkJcEWOBy9n2g6fsdKbfuGm7xZaLOUxilZDwg4L6t077zK4s41QiJLaGilC1ntDrGI9Cy9Y76ij2Euu9NofFSshetW1MjHg06TRtQNcoGqrtTVQDkrvGgjCAnR9TZAONr17IHK167SgwalsHshd0R82lWsv8KSsscS6g+3SL8LCnyZ8+sDVIX5/pB1agwaeVDLdgomIQlFI9+wPt6BQzSP3ERV5ggeB7w8FMqu79pYQc7+oDSJDX9pgQfWZ2pXR/oGdoCq30vY7z5QQQXd7fJwlbjA+18HulJALI+/EZPZPYjDd8osk1IVa39m6u65QROnHz+L6qEeGoDa2AY5CG9OEmDq2MVkd5W3GaNPKuHQxoRKMlyucr9oUWyM2pUrGuDEahjgDALRUb5LWAoOKqVLaHIc14dmgdGnPxlBtcHZdoY5iExIO2mncE2hTpkS1cv3wU2UrB0eZ2LlTXmbrtmBV5hCDiINLVo55QXjP8NrrbPtA16GXY1yx7QtkF7HW76DeNTpXfQ1IARqKnpU8tCCqcUo7s7RbgyzHkGqDU87q2IGtmaOSnzOFJ7WaYBcN0Sb4kfsYEMFrOIXyKx6ypsriXEOqo0UUa1iqrQHQ8ANNa7Al1MgX/IKsQWKZCrxHYWqnqLLasuucGbFrDyhUxnVDs8qyuV4UCwL7leBAbMSPN16CAJ4p0uOWMacI3QdsO8GLW7XW9oM0QuiibHU2KY3hGZkqvD7YQpmnYqsbY9or6HJPkyuplWB0kOiYW9dpX254HRYbvlGAnHzx2RhpDAa1oa9oGvgEFhSuoaCmsdFKyDtX7jxaoCqZ/HQwIVUOFR1NYJpAQkWxM+y5wzw5eHavaVICZbFqr2YhJpQcMAMy27xZWIQxOXZeu0E9bmiI0xe9IBNQJAOWTx6S6IsA1XJPdDaUJZHJRwoaJZKHBUYBcqqyru8sPRbxGIhaKt8/wBcoT4KheO9iC3Ji6Rq5rmSBsJo/mgSX8wDsNLnea5QxhoCfP8AxhUMABb0w6wVG9O/0y9qoshZrePCDNBZgLfIrz7QPBDaEWuxMCCt5TKiPL57XlHATNavWvvFJZY/kXBqlQ0UOSHxlkyMEnsjfuv2g1OPbcqwaxBB8Re+tT5gEYGFFHvTjnLGw519oA/hljzgTV4sfEVLjg4zH+jDfKi7zJtfL57RIBoIdf1zgsFre+dN8awaZlXiCmtoHiEhLhAT3UAGIOpFfOH1+i30Z0b8UueRKG9cDeX4Vs5um6buG6buO7gjkZum6bvRbwBnGbpum7iHxjZ/g0m+GybJs9BTGBsfTsmz0WxJvjsmz07vwSl/UECXoOj/ABWyw7+oNj6N34EJwbpu9Bs4bPQQ5um6buNnOJJcDN03TdwLqkbLgg1+CGibJsmz/ALhNk2TZC+HDZEkuATCbJsmybJs47JZL8Ruh0fnmy/9N//EACoQAQEAAgICAQQBBAMBAQAAAAERACExQVFhcRBQgZEgobHB0UDh8GDx/9oACAEBAAE/EPsg1hxStcgVE6mCVcRSKkcRadgO2EAIITB0gVp9P5++Qq4YnyIA6XHNpg8+Fqu9cWAn9VNee8JniSYQknEKtisjhxUNPJf5MTKC+MHp+yP8vvL5EwsjwFQADYCtpVA8d/goqbecUIuzNIcw1/gxbs2PwgF8BaeSDvXa/kf6jC43y/6J5Dz7Wc/zSMuuJOP0r6mf0Pf+sYQ+EYgRzGpMof4w0YpgWksBe4TWDRaNZoqQfaWlDq/cGy+MCqSxLAnhjEMhj878dG6cWPZyuHsVn00zemz+I4KMTZAewY5l/cZHufnLVYu+Dx6/xI+cBzJ2jvy/r4Eo5eaBQjUHIevPG5mVyR0gB+ujLFll/O5kx+NpxspTXn1hGz3iXWHpOlw+48QO4CcgdiUZUQO0Jg5p3+CkWtpSGgvv0X8dAUH0umUbGvSzT8FauZSw6Bak7tSXXdmvi/XOM1uJjkDRCia7xTy8fDIeZEFO1UbD0d56bTUylzhVspGFL0X0KOhNlKVAzaNphIpEcEPezhOsut5UYnWxYBXbYfRuAgkqG7gs84cZqLAz4Et/WMCvx+xv7wdhHwZwv/cXcDcY5YcmDA0QWm9B0B+QcsK/7+zCeAnnR/SayB6Wq7syBhRDPcDIWRffIQnWdWFYobKK/RLSWARB1VKsVqWDJukmQ3j+kaGccgWhXUFFzup5RiYo7+MDohgbS/ij59Cfwye+huGSKvsldXbELem9ZqFAwTIhY2BHnOxeF4fpsKxcubjzAQNIXlIHMBgUdWVK7XGJyhvSeiHbNN4cKALJmatO90eM1tpQLQg+HBhu98qvodwKh/gBoX84jdwRmumHef8AjE6xr2zYU/UnwH2vx8vFG516d4wbVi8x2VZ8k+xidHppXbPmD0/GXVEb00bLdK3nBnIH9m/TaCsx0jw492Bn4S5qu98GZnNR0eXiZcZauIoNxuC7CyGf394FC6CHbBRou20GgGcp+9vEeZCpLEjYke4kbJDwlDAti39BwFIExO098BQqCOCq70OPokZN8aoHwgfli1iqnU4gKwUsWKJr065gMmh40CLdE+TI1sTRND8LedET3l2lzWDdV36gxqIIjaUjE8fLy8fr49L184yduzujRB8ftks/9dd/uZyD+Nnz5klJeXNKj6yjkIXDsHf38bDp1/I+5j2aJqTMYdIW8rvpAOCu3+zv9iwkj8Aq/DcjP0ZCY6FeMRS3s5eUy+RVG9VpbeOsZqBWmuJAuyo41N65Li/FaLjGCn9APRnWaj3ZAS809hFK5wzDsxN+RdV6U1X05BoiqY5/CzKgKnOVf/h+IcrNm483FVZY3kHhH9M8FpYNwhv7yoXQES5Q9F52rNhGiubi7THWCE4HSsJKILnm8iCpJZafRrIcYN2xmpW0PMqPBJpVk/jvGawkYPJ8fh995tAZSeEfut7jA26Ih8Mu8aKkAH5f1zzUas3UvgCTazQoAm3eecC/n6AhG2iJOOZdXCgu7skUrxoQ1mY/B0DoAY85nigYUNIH+De+xCCTlIPfTH0u0ujk0fw5RizkY4F3dCkjoRfsTePgzuFY/WXgHHDz/wAUvtgu88z9EsR52zYKf+vDiL9PWQFOpJQxSPtWjB24G8G1d9jpedTl5veOyRBKUPoeuDuHHnBW6nPMhUq1XEq9azvhP8DeCYQcTZzSdIKSr4UtL/dDwxVzlYlgdwnE3tC894EHfO4qkaeesk+eIAA6+2AajOYMWa1Ut6D1hHHqh2GDmk3KXu7353n5x1VoEvw2jPlTbmqHwi56EyTZue6sgQNEpEIwjvwzV9QQieUjjZvuJyxStjbV5SDfAPoMQLo3BeFxTeGsIFhdWhwUO8Dr8EtunsgwDhivc/YcBriuht1iWi4xEsE/BROW9NEhD+7CX9nwzZ8r0mmmx0UKObZxR3rYlrQHnHumEzA9QXeVPwQbH6ZCmlbUyDjiWBhakEeAWb3jggsQcH1Y8dYKxNA5AUtiFYb+M2wUCFxzYcjIOwKvagJS61ZgvmwOY0IPdf6MMcakJvSzctYKDeh9oTWhM3mLrXgaCvBADU0xsDI0QB7bvO/WuTIDkOaaG9HaktDkk4WUW3jNanghSNFsVwEcWoEUEGFLJ2bwZZxeITjLa6Q7YiZRH5AlA4ejKSsfcqcyJr1g3nUiGrbaDQVLj/OeISzKAQWgxZPzhqIO+wdAVMhYJysGTpq21rONo3kdXqKEEY/yL8KKSIy1dXDZblDRcKAKAmTQiEqBS6kCCthEeX9LLBeQTkjLTYOlzmtiUWMZeADkmG5pelJlF0E3dPGSLrAEnWrnsZpXEIqBTWZBmsZ353c8pQ9X4TYv/S4ADYMiFitdqwmjg5srRAgBkmKcongX6CZEGHgJ+hwOwYoBbha8AcjkuF8K5Aidbc10aeY01eM/nut61saBtE3xuR/bOSphvBAXjNno6CMim0tSuk+098EZ+TlzMcgpazwg5SA64xljvOzWBEa3lGWYPJufG4BYKbuKkTic7nEBThbDJ8KZPiWnU1zG6FNwQD9yonV4GzVNFFwx0iVDDmx70fFoia4cY9qC6lc0djseqLA2aPZjoHAvEmpnM67bBNDE7omt0VkIuQlZJQq4GKQ+w5GkaMyINaBKRI5JhxGTMkfGJ5094cu1OdkapnV39YhmtY1z1qtliM8iT9SNJnKHEfNKMe30HJNHljZjQGKFBsM+9bGECS58hSiLJBLD05aJrpArBVwX0vqatTgFRRAFHTzQYMAgC0wHWv8AY+gnLSNRODCY2hHJHgE9G8CQ82Bc4O0C8CqIuFGfv7KdFZUKU9mvgWJCukM//Mf7ytAQNSkLVJQFFq/v6adrIl23veIK84G7CI1DZrpN1244q3TYY7wUh9ncrV8oRnlpL/AiUrotza9lz5x2M73DXBvJNapukWYcD4l0+YB/Lgm15hD5r5fQZCrRbsQO9kv5m89pqoYzqGi8BcvH3RAu1V1FcwarGUjRhcD1rG4gEN/gm1t7J7w4BlWy0IQhxAxLz+bpmCzwNM5Sqiowsk9CBIPGB2IyX3yEZPIlWWZonP4PIvsu0wtMEgRagK7DKv5E8vMijrWriqCTNpvSSDo3GRO6QIsDUTCdOOrgMjX6BMqbKFHjIguaF5Et6BSrraEpT+XtYtgLesLJeiTeNFlXwygabdsWs46RnANGYA1wja+VGSpJCw0oB7WdVcP8D6EItAcn3jA0YAIOUB6SghQ0njqOESmkMQY5PAS5gk6ftiA4wGY7dBo3/FIBtgmZ4iipyKEm0GEKGlMb0BCnhcUE3PnqhS7ukE02KMbLHZbJ4GRzwrxq92JrXBs6SET23KKCMQF2STPIZbWDwhRDzGtAKK0Yt1kOGJ2tODEkT6f6RLyXXeq6dfr6oIhJbBSrBFLfWc5G0sgSqH3TphGQXo+jtsQsMUl54J5AJKBwGWuE8BlkNlokf8Tw1k5SPE3UhgHbdG46O/zFCINcDhsUqlQGDUUo7uOuGBr9Hza6E4fXWmQDRoHyln6Psh2AqRHjgnSMMAcoIELLZyXiNC+NfIxOZCLvLeWYbYyO4gvFOcCq5gG2SEAtZQYeaQ8hBtQYASbkwv29NQ0ckDVGyNlwnphTaITYxXnANpoOcKSqSR1S6UGc5CAXtCNzWAOuVZkNiMWteSDpRhcuck81hLU7b3aBEbDeBGvrrFakxbduNoaqd++tvXH01HffbQe0OUamthE6EIVwieVrQfCNpQroRdGmhHDkfLjGxmthNeGEuQqAeHk2I4xlRS/hCkMerupd4RVnQD9GoFCQgUDEwSU9xrxNntNQmTkqZZzIaXliUWsVSemnyIBrhEtH0VRzk9DSYUhARKGe1Zrehf5uqo5h7ZQBKHfTuhzkUO9ZJaig3E+YEukNdOTWI2Dw1pIVXHZbxu1S4LkGY8fwBKegQMAUQhNv+GCsIBAow35kd7w9lxYGUTav9Lhb7RCaxqFo2YUGjzhiUDjxEGQjTbgoqb6frNB8AlTpsCl2jnNJ4NC60Y7b0YcCaJVEiMfrJvMAZCP8vGeHBf0WNtkmi3SzFnyxbTajztI4zbtfjd9kx4a5MUQGIFoESEQ+MMLJjbJN5OAItUob172KBHcF0wKLiHRjNEGD9zGhEIcB7hxgzCxMCbEYL/AzYvFBPdAQaOJlCMA1oK0hQ9RM1BqdmQejQ/vAMNNPEA23zD3kVF07M7lJ1TAmXD50h4BbEQM0RvNOkHAv5kWFOfr/AEhlFiOi7wTgkpxRZqYrPebZbqt4o6EJXxlibZpGBHyTb3NlnFRLTcALaMuLGHTXeCQ21HlcGKsYwNqZEC5MNed89IXjtonwfy6qh2HRBNl1TXXcyAaXVLfwHAPYA0LAcoPfE+gXJPYI0gu+S+seJONgznPHwScFm0IpLRcms3k2+x0o1kVC5KNUBqUJi/s2nuJ0LpPwLYEYGeD/AAr/ADz1MmNQ04y1Xu97E1bxz9YSdyrq4oUoyUv01Jb86X7cQiQxRSBrte5cZPmtMWCUQxzMWADBbc6XdVDaszdCLwoTDwjXhiu6rydOBZgY3UCNZA8TyNI27pbYBQg7geMEo8hOfweNCQH0SifyegYlEWOOdbFIbN/h6qt0gQU5vIAOiUHlOsPJiIokQL5rcXG30T7/AKePm0y1NeyPxVeaxElo0RTxD3jLjzrCa8gDRTrFXOdx/wDZdONmIU4M+QANRVTu6ABg1qKjoAwngRv7EyRVcyjYNjBtDa/brpQ4lnmxCHd5qsAyaOcnuxj6PNr01/0MMAj90hNuSupMlyN1z6gQDyhh/rom5JuWxbDAQbKeYkKAIsO/Ch1MEZBXnjMsKjU3x3Mhca0OUHysfZmKhBnJMo+ze0yCtlP4qY3TaPRcT9e8ZUAwR1XlYgUQgnHbvb9KJNnXHK6ik5EivLj1wsUkJTRXE8sP4fqqnwMTrb5RG9VONckvYWb/AGtfNhla5CRJvds2VCl6ieUNCX/OxzCBna7or6xkQQcOjhQprnsAcvXnUoWp3RrbhswuwQYDyqmDINHl2yOiNJ0X6yC9DB6UCwRjhGVx9rIum8TipD7Dp1QcwQJJ72cBwhwio8lRpBrRZgAezaKFxfA1NJ11gWEN5C6XDYUY28oy57l0hGEZcAGfiSRCWsmGwFVlmpD3XgejSaM0hNikAIpyPwWAMsiMj28quEc0o3aEwLbgNpynizaA1KDmiugxuBdd2gHwSerOBWOUbVFRI9ukbNXACQEFbxUzSblDMsYNN0TzLZNP4TqqpFgQTc9cYCI8Ycs3ECivpIChuskKDEblQHam+MqX2fx9AhOceDnbg+niWhzdixkJdXQZflOiG1mA1Vs7GPn5wGdQl2qSFogNcjyI1my6ESSNkbBx+mriSQvyjEKCchb9hcErPRguoKnQwdTgtAjgIHeEHYc4k1BkTVROp9REUdZ0Z/8Ag1rAyRK9UY5JAWrzb06ezqPviM4v94J2aTZZFbFmgbYhiiC0BqA+kB5/aLqVAWqgyTCZXRU6kMM3NqLeZdZwg6Zjw40DrZqVHsIQD2FUPl6AsDFmsywBnDg9oS8mIUTMEJzNGwBolBMFiuBuyHk8eW/0WLhrqeWOO5eGN1vcOHMigV3yW9eNUwq2Cm6MmU9QLrfQRhZgffGHH/8AklGA9u07LhXHRLK4s3H26XSQq2W0Za+3ltD2gTsWWOp05ss2xppUJLZEyrwQ+smM1+D/AMb4wSV5pokpwNy9fYJQiAIAkpqLhEjW9M/uBzLo8EA25xiFs5NZ/Z84dKmiw2Xp6PveVe9sBwZtINDk5lRHiRgsFCv0ay4Hm17A82lGivBJ1bD+KoVFrYQFWI81iNfpRasfQbPUNA0t3jjPmzJfXqi3HDsmEOKAROfqOMESqIMeSFJxgHleALZSXQfOSg9zWP6nW0DopM5GLimPE/QmAJYr3zXa/wAjOYSAikTKmIDkBA1Fk0CQaNdtuFmrWvINYYJ+f9lRBG8eGB5h+MRnJYAI2l5Q6sVXTkw6RV4K52581BDtyhpELDSA2gb9hCU0gBqEJldpcJ/47stbWnBw5T9wg25pM8NWig7Vw/YlI9MPEx10+tF0SJRiUjPAXA6Ii4MJt5hdl4A/L6MkKhEunzwtir1p9RMKtBaAec2IjZZiTEBkQyhqEISQtqVtBe3j+E9HyrLfggXwGEEryXd5t4aBAL4K9ute39BBBH4P7rkfOG5Afg+sCSaDQKB4oV7wBxp9BT54YG0ko8lPI7PwZXcPDD+2Gk29v/b6B1tSBVU9ob+VyZPACIg8CcKIKugH4Uj5AfZxq2wDoLyyyjlACYVLBey5IoaR5zbydv7EvxFsg/8ApX//2Q=='; diff --git a/packages/syncfusion_officechart/lib/src/test/sample_browser_samples.dart b/packages/syncfusion_officechart/lib/src/test/sample_browser_samples.dart new file mode 100644 index 000000000..796bd10ff --- /dev/null +++ b/packages/syncfusion_officechart/lib/src/test/sample_browser_samples.dart @@ -0,0 +1,462 @@ +// ignore: depend_on_referenced_packages +import 'package:flutter_test/flutter_test.dart'; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; +import '../../officechart.dart'; +import 'chart.dart'; + +// ignore: public_member_api_docs +void sampleBrowserSamples() { + group('Sample Browser Samples', () { + List + + + diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml index d460d1e92..cb1ef8805 100644 --- a/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml +++ b/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml @@ -3,7 +3,7 @@ diff --git a/packages/syncfusion_officecore/example/android/build.gradle.kts b/packages/syncfusion_officecore/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_officecore/example/android/gradle.properties b/packages/syncfusion_officecore/example/android/gradle.properties index 94adc3a3f..f018a6181 100644 --- a/packages/syncfusion_officecore/example/android/gradle.properties +++ b/packages/syncfusion_officecore/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58afd..afa1e8eb0 100644 --- a/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/packages/syncfusion_officecore/example/android/settings.gradle.kts b/packages/syncfusion_officecore/example/android/settings.gradle.kts new file mode 100644 index 000000000..a439442c2 --- /dev/null +++ b/packages/syncfusion_officecore/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false +} + +include(":app") diff --git a/packages/syncfusion_officecore/example/ios/.gitignore b/packages/syncfusion_officecore/example/ios/.gitignore index e96ef602b..7a7f9873a 100644 --- a/packages/syncfusion_officecore/example/ios/.gitignore +++ b/packages/syncfusion_officecore/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..7c5696400 100644 --- a/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 12.0 diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj index 6b50bd568..2df87dcb4 100644 --- a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,30 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 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 PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -31,6 +43,8 @@ /* 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 = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -49,12 +63,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +95,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +103,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -90,7 +115,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -99,16 +123,26 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -125,6 +159,9 @@ dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -135,9 +172,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -153,16 +195,27 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -179,10 +232,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -193,6 +248,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -208,6 +264,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -219,6 +283,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -241,9 +313,9 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -273,6 +345,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -281,7 +354,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -298,17 +371,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -316,11 +384,58 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -350,6 +465,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -364,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -374,9 +490,9 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -406,6 +522,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -414,11 +531,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -432,17 +550,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -459,17 +572,12 @@ 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 = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.xlsioExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -480,6 +588,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -501,6 +619,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..d795332e1 100644 --- a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + - - - - + + + + + + @@ -61,8 +89,6 @@ ReferencedContainer = "container:Runner.xcodeproj"> - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Officecore Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - xlsio_example + officecore_example CFBundlePackageType APPL CFBundleShortVersionString @@ -39,7 +41,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart index 12863e435..856ba8bdb 100644 --- a/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart +++ b/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:open_file/open_file.dart' as open_file; import 'package:path_provider/path_provider.dart' as path_provider; +// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; ///To save the Excel file in the device @@ -19,8 +20,9 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else { path = await PathProviderPlatform.instance.getApplicationSupportPath(); } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + final File file = File( + Platform.isWindows ? '$path\\$fileName' : '$path/$fileName', + ); await file.writeAsBytes(bytes, flush: true); if (Platform.isAndroid || Platform.isIOS) { //Launch the file (used open_file package) @@ -30,7 +32,8 @@ Future saveAndLaunchFile(List bytes, String fileName) async { } else if (Platform.isMacOS) { await Process.run('open', ['$path/$fileName'], runInShell: true); } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); + await Process.run('xdg-open', [ + '$path/$fileName', + ], runInShell: true); } } diff --git a/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart b/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart index a455ce682..92be9b31f 100644 --- a/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart +++ b/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart @@ -1,15 +1,24 @@ -///Dart imports +/// Dart imports import 'dart:async'; import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; +// ignore: depend_on_referenced_packages +import 'package:web/web.dart'; -///To save the Excel file in the device ///To save the Excel file in the device Future saveAndLaunchFile(List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); + final HTMLAnchorElement anchor = + document.createElement('a') as HTMLAnchorElement + ..href = + 'data:application/octet-stream;charset=utf-16le;base64,${base64Encode(bytes)}' + ..style.display = 'none' + ..download = fileName; + + // Insert the new element into the DOM + document.body!.appendChild(anchor); + + // Initiate the download + anchor.click(); + + // Clean up the DOM by removing the anchor element + document.body!.removeChild(anchor); } diff --git a/packages/syncfusion_officecore/example/lib/main.dart b/packages/syncfusion_officecore/example/lib/main.dart index 343a4d468..ba32e636b 100644 --- a/packages/syncfusion_officecore/example/lib/main.dart +++ b/packages/syncfusion_officecore/example/lib/main.dart @@ -1,16 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column, Alignment; +import 'package:syncfusion_flutter_xlsio/xlsio.dart' hide Column; //Local imports import 'helper/save_file_mobile.dart' if (dart.library.html) 'helper/save_file_web.dart'; void main() { - runApp(CreateExcelWidget()); + runApp(const CreateExcelWidget()); } /// Represents the XlsIO widget class. class CreateExcelWidget extends StatelessWidget { + // Constructor with a named key parameter + const CreateExcelWidget({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return const MaterialApp( @@ -23,11 +26,12 @@ class CreateExcelWidget extends StatelessWidget { class CreateExcelStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreateExcelStatefulWidget] class. const CreateExcelStatefulWidget({Key? key, required this.title}) - : super(key: key); + : super(key: key); /// title. final String title; @override + // ignore: library_private_types_in_public_api _CreateExcelState createState() => _CreateExcelState(); } @@ -35,22 +39,20 @@ class _CreateExcelState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( - child: const Text('Generate Excel'), style: TextButton.styleFrom( - primary: Colors.white, + foregroundColor: Colors.white, backgroundColor: Colors.lightBlue, - onSurface: Colors.grey, + disabledForegroundColor: Colors.grey, ), onPressed: generateExcel, - ) + child: const Text('Generate Excel'), + ), ], ), ), diff --git a/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc index e71a16d23..eb768d3a6 100644 --- a/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) open_file_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin"); + open_file_linux_plugin_register_with_registrar(open_file_linux_registrar); } diff --git a/packages/syncfusion_officecore/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_officecore/example/linux/flutter/generated_plugins.cmake index 51436ae8c..eb06039dc 100644 --- a/packages/syncfusion_officecore/example/linux/flutter/generated_plugins.cmake +++ b/packages/syncfusion_officecore/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + open_file_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) @@ -13,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_officecore/example/macos/.gitignore b/packages/syncfusion_officecore/example/macos/.gitignore index d2fd37723..746adbb6b 100644 --- a/packages/syncfusion_officecore/example/macos/.gitignore +++ b/packages/syncfusion_officecore/example/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/packages/syncfusion_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift index 7ce0a99b8..4d6f3f9e6 100644 --- a/packages/syncfusion_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -2,13 +2,13 @@ // Generated file. Do not edit. // -// clang-format off - import FlutterMacOS import Foundation -import path_provider_macos +import open_file_mac +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.pbxproj index cc89c8782..f8bff16cf 100644 --- a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -21,14 +21,23 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +61,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* officecore_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "officecore_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -71,16 +82,32 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,6 +124,7 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); @@ -105,7 +133,8 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* example.app */, + 33CC10ED2044A3C60003C045 /* officecore_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -155,6 +184,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; @@ -171,8 +218,11 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productReference = 33CC10ED2044A3C60003C045 /* officecore_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -181,10 +231,15 @@ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -210,17 +265,28 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -235,6 +301,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -273,6 +340,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -286,6 +361,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -306,11 +386,54 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/officecore_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/officecore_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/officecore_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/officecore_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/officecore_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/officecore_example"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -334,9 +457,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -344,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -384,6 +509,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -407,9 +533,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -423,7 +551,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -437,6 +565,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -460,9 +589,11 @@ CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -470,7 +601,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -536,6 +667,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -567,6 +708,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ae8ff59d9..491e03fca 100644 --- a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,10 +1,28 @@ + + + + + + + + + + @@ -31,13 +49,24 @@ - - + + + + + + - - diff --git a/packages/syncfusion_officecore/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_officecore/example/macos/Runner/AppDelegate.swift index d53ef6437..b3c176141 100644 --- a/packages/syncfusion_officecore/example/macos/Runner/AppDelegate.swift +++ b/packages/syncfusion_officecore/example/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7c..82b6f9d9a 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc1642..13b35eba5 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be6138..0a3f5fa40 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df2..bdb57226d 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a65286..f083318e0 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a72613..326c0e72c 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2dd..2f1632cfd 100644 Binary files a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/packages/syncfusion_officecore/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_officecore/example/macos/Runner/Base.lproj/MainMenu.xib index 537341abf..80e867a4e 100644 --- a/packages/syncfusion_officecore/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/packages/syncfusion_officecore/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/packages/syncfusion_officecore/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_officecore/example/macos/Runner/Configs/AppInfo.xcconfig index cf9be60ca..37d3e5c82 100644 --- a/packages/syncfusion_officecore/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/packages/syncfusion_officecore/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = example +PRODUCT_NAME = officecore_example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.example +PRODUCT_BUNDLE_IDENTIFIER = com.example.officecoreExample // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/packages/syncfusion_officecore/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_officecore/example/macos/Runner/MainFlutterWindow.swift index 2722837ec..3cc05eb23 100644 --- a/packages/syncfusion_officecore/example/macos/Runner/MainFlutterWindow.swift +++ b/packages/syncfusion_officecore/example/macos/Runner/MainFlutterWindow.swift @@ -3,7 +3,7 @@ import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { - let flutterViewController = FlutterViewController.init() + let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) diff --git a/packages/syncfusion_officecore/example/pubspec.yaml b/packages/syncfusion_officecore/example/pubspec.yaml index db61f6a14..a91707fc9 100644 --- a/packages/syncfusion_officecore/example/pubspec.yaml +++ b/packages/syncfusion_officecore/example/pubspec.yaml @@ -2,21 +2,17 @@ name: officecore_example description: Demo for creating a Excel file using syncfusion_flutter_officecore package. environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - path_provider: ^2.0.1 - open_file: ^3.0.1 + path_provider: ^2.0.5 + open_file: ^3.0.3 syncfusion_flutter_xlsio: - git: - url: https://buildautomation:Coolcomp299@gitlab.syncfusion.com/essential-studio/flutter-xlsio - path: flutter_xlsio/syncfusion_flutter_xlsio - branch: development - ref: development + path: ../../syncfusion_flutter_xlsio -# The following section is specific to Flutter. +# The following section is specific to Flutter. flutter: uses-material-design: true diff --git a/packages/syncfusion_officecore/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_officecore/example/windows/flutter/CMakeLists.txt index b02c5485c..86edc67b9 100644 --- a/packages/syncfusion_officecore/example/windows/flutter/CMakeLists.txt +++ b/packages/syncfusion_officecore/example/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/packages/syncfusion_officecore/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_officecore/example/windows/flutter/generated_plugins.cmake index 4d10c2518..b93c4c30c 100644 --- a/packages/syncfusion_officecore/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_officecore/example/windows/flutter/generated_plugins.cmake @@ -5,6 +5,9 @@ list(APPEND FLUTTER_PLUGIN_LIST ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_officecore/example/windows/runner/Runner.rc b/packages/syncfusion_officecore/example/windows/runner/Runner.rc index 51812dcd4..e5666e022 100644 --- a/packages/syncfusion_officecore/example/windows/runner/Runner.rc +++ b/packages/syncfusion_officecore/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/syncfusion_officecore/lib/officecore.dart b/packages/syncfusion_officecore/lib/officecore.dart index b1a3a4a19..11baf04c2 100644 --- a/packages/syncfusion_officecore/lib/officecore.dart +++ b/packages/syncfusion_officecore/lib/officecore.dart @@ -1,3 +1,3 @@ library officecore; -part 'src/built_in_properties.dart'; +export 'src/built_in_properties.dart' show BuiltInProperties; diff --git a/packages/syncfusion_officecore/lib/src/built_in_properties.dart b/packages/syncfusion_officecore/lib/src/built_in_properties.dart index fdfc0fa82..b1b9aaca1 100644 --- a/packages/syncfusion_officecore/lib/src/built_in_properties.dart +++ b/packages/syncfusion_officecore/lib/src/built_in_properties.dart @@ -1,5 +1,3 @@ -part of officecore; - ///Represent the document properties in a MS Excel Document. class BuiltInProperties { /// Gets or Sets author of the document. diff --git a/packages/syncfusion_officecore/pubspec.yaml b/packages/syncfusion_officecore/pubspec.yaml index 56fe1200f..ffd4350a0 100644 --- a/packages/syncfusion_officecore/pubspec.yaml +++ b/packages/syncfusion_officecore/pubspec.yaml @@ -1,10 +1,10 @@ name: syncfusion_officecore description: Syncfusion Flutter Office Core is a dependant library for Syncfusion Flutter XlsIO, written natively in Dart for creating Excel from scratch. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_officecore environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ^3.7.0-0 dependencies: flutter: @@ -14,3 +14,4 @@ dependencies: + diff --git a/packages/syncfusion_pdfviewer_android/.gitignore b/packages/syncfusion_pdfviewer_android/.gitignore new file mode 100644 index 000000000..e7d347d9d --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/.gitignore @@ -0,0 +1,33 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/packages/syncfusion_pdfviewer_android/.metadata b/packages/syncfusion_pdfviewer_android/.metadata new file mode 100644 index 000000000..cd12fae18 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "be698c48a6750c8cb8e61c740ca9991bb947aba2" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + - platform: android + create_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + base_revision: be698c48a6750c8cb8e61c740ca9991bb947aba2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/syncfusion_pdfviewer_android/CHANGELOG.md b/packages/syncfusion_pdfviewer_android/CHANGELOG.md new file mode 100644 index 000000000..fb70942b0 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/CHANGELOG.md @@ -0,0 +1,3 @@ +## Undefined + +* Initial release. \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_android/LICENSE b/packages/syncfusion_pdfviewer_android/LICENSE new file mode 100644 index 000000000..02291f801 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/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 the Syncfusion® 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 the Syncfusion® 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_pdfviewer_android/README.md b/packages/syncfusion_pdfviewer_android/README.md new file mode 100644 index 000000000..582d9c034 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/README.md @@ -0,0 +1,65 @@ +![syncfusion_flutter_pdfviewer](https://cdn.syncfusion.com/content/images/pdfviewer-banner.png) + +# Flutter PDF Viewer Android + +The Android implementation of the [Syncfusion® Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. This implementation uses PDFium, an open-source PDF rendering engine, to enable high-performance rendering of password-protected PDF files specifically on Android devices running API level below 35. + +**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:** +This is an optional package with limited Android support. Please note that this package is not compatible with Android devices running API level 35 (Android 15) and above + +## Table of contents +- [Get the demo application](#get-the-demo-application) +- [Useful links](#other-useful-links) +- [Installation](#installation) +- [Support and feedback](#support-and-feedback) +- [About Syncfusion®](#about-syncfusion) + +## Get the demo application + +Explore the full capabilities of our Flutter widgets on your device by installing our sample browser applications from the below app stores, and view samples code in GitHub. + +

+ + + +

+

+ + +

+ +## Other useful links + +Take a look at the following to learn more about Syncfusion® Flutter PDF Viewer: + +* [Syncfusion® Flutter PDF Viewer product page](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer) +* [User guide documentation](https://help.syncfusion.com/flutter/pdf-viewer/overview) + +## Installation + +Install the latest version of [syncfusion_flutter_pdfviewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer/install) and [syncfusion_pdfviewer_android](https://pub.dev/packages/syncfusion_pdfviewer_android/install) from [pub](https://pub.dev/publishers/syncfusion.com/packages). + +### Import the package + +This package is an optional Pdfium implementation of `syncfusion_flutter_pdfviewer` for the Android platform since version `29.2.9`. It is not imported by default. You can import it by adding the following dependency to your `pubspec.yaml` file: + +```yaml +dependencies: + ... + syncfusion_flutter_pdfviewer: ^29.2.9 + syncfusion_pdfviewer_android: ^29.2.9 + ... +``` + +## Support and Feedback + +* For any other queries, reach our [Syncfusion® support team](https://support.syncfusion.com/support/tickets/create) 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). +* To renew the 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® + +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), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-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 ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-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 diff --git a/packages/syncfusion_pdfviewer_android/analysis_options.yaml b/packages/syncfusion_pdfviewer_android/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_android/android/.gitignore b/packages/syncfusion_pdfviewer_android/android/.gitignore new file mode 100644 index 000000000..161bdcdaf --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/packages/syncfusion_pdfviewer_android/android/CMakeLists.txt b/packages/syncfusion_pdfviewer_android/android/CMakeLists.txt new file mode 100644 index 000000000..37ccf7527 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.14) +project(syncfusion_pdfviewer_android) + +add_library(pdfium_wrapper SHARED + src/main/cpp/pdfium_wrapper_jni.cpp +) + +add_library(pdfium SHARED IMPORTED) +set_target_properties(pdfium PROPERTIES IMPORTED_LOCATION + ${CMAKE_SOURCE_DIR}/src/main/jniLibs/pdfium/${ANDROID_ABI}/libpdfium.so +) + +target_include_directories(pdfium_wrapper PRIVATE + src/main/cpp + ${CMAKE_SOURCE_DIR}/src/main/cpp/pdfium/include +) + +target_link_libraries( + pdfium_wrapper + pdfium +) \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_android/android/build.gradle b/packages/syncfusion_pdfviewer_android/android/build.gradle new file mode 100644 index 000000000..fe99e21a6 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/build.gradle @@ -0,0 +1,55 @@ +group = "com.syncfusion.syncfusion_pdfviewer_android" +version = "1.0" + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.7.3") + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" + +android { + namespace = "com.syncfusion.flutter.pdfviewer.android" + + compileSdk = 35 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + defaultConfig { + minSdk = 21 + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + sourceSets { + main { + jniLibs.srcDirs = ['src/main/jniLibs/pdfium'] + } + } + + defaultConfig { + ndk { + abiFilters "armeabi-v7a", "arm64-v8a", "x86_64" + } + } +} diff --git a/packages/syncfusion_pdfviewer_android/android/settings.gradle b/packages/syncfusion_pdfviewer_android/android/settings.gradle new file mode 100644 index 000000000..086ff02e6 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'syncfusion_pdfviewer_android' diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/AndroidManifest.xml b/packages/syncfusion_pdfviewer_android/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a2f47b605 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/cpp/pdfium/include/fpdfview.h b/packages/syncfusion_pdfviewer_android/android/src/main/cpp/pdfium/include/fpdfview.h new file mode 100644 index 000000000..96b50e1d7 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/src/main/cpp/pdfium/include/fpdfview.h @@ -0,0 +1,1468 @@ +// Copyright 2014 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com + +// This is the main header file for embedders of PDFium. It provides APIs to +// initialize the library, load documents, and render pages, amongst other +// things. +// +// NOTE: None of the PDFium APIs are thread-safe. They expect to be called +// from a single thread. Barring that, embedders are required to ensure (via +// a mutex or similar) that only a single PDFium call can be made at a time. +// +// NOTE: External docs refer to this file as "fpdfview.h", so do not rename +// despite lack of consistency with other public files. + +#ifndef PUBLIC_FPDFVIEW_H_ +#define PUBLIC_FPDFVIEW_H_ + +// clang-format off + +#include + +#if defined(_WIN32) && !defined(__WINDOWS__) +#include +#endif + +#ifdef PDF_ENABLE_XFA +// PDF_USE_XFA is set in confirmation that this version of PDFium can support +// XFA forms as requested by the PDF_ENABLE_XFA setting. +#define PDF_USE_XFA +#endif // PDF_ENABLE_XFA + +// PDF object types +#define FPDF_OBJECT_UNKNOWN 0 +#define FPDF_OBJECT_BOOLEAN 1 +#define FPDF_OBJECT_NUMBER 2 +#define FPDF_OBJECT_STRING 3 +#define FPDF_OBJECT_NAME 4 +#define FPDF_OBJECT_ARRAY 5 +#define FPDF_OBJECT_DICTIONARY 6 +#define FPDF_OBJECT_STREAM 7 +#define FPDF_OBJECT_NULLOBJ 8 +#define FPDF_OBJECT_REFERENCE 9 + +// PDF text rendering modes +typedef enum { + FPDF_TEXTRENDERMODE_UNKNOWN = -1, + FPDF_TEXTRENDERMODE_FILL = 0, + FPDF_TEXTRENDERMODE_STROKE = 1, + FPDF_TEXTRENDERMODE_FILL_STROKE = 2, + FPDF_TEXTRENDERMODE_INVISIBLE = 3, + FPDF_TEXTRENDERMODE_FILL_CLIP = 4, + FPDF_TEXTRENDERMODE_STROKE_CLIP = 5, + FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP = 6, + FPDF_TEXTRENDERMODE_CLIP = 7, + FPDF_TEXTRENDERMODE_LAST = FPDF_TEXTRENDERMODE_CLIP, +} FPDF_TEXT_RENDERMODE; + +// PDF types - use incomplete types (never completed) to force API type safety. +typedef struct fpdf_action_t__* FPDF_ACTION; +typedef struct fpdf_annotation_t__* FPDF_ANNOTATION; +typedef struct fpdf_attachment_t__* FPDF_ATTACHMENT; +typedef struct fpdf_avail_t__* FPDF_AVAIL; +typedef struct fpdf_bitmap_t__* FPDF_BITMAP; +typedef struct fpdf_bookmark_t__* FPDF_BOOKMARK; +typedef struct fpdf_clippath_t__* FPDF_CLIPPATH; +typedef struct fpdf_dest_t__* FPDF_DEST; +typedef struct fpdf_document_t__* FPDF_DOCUMENT; +typedef struct fpdf_font_t__* FPDF_FONT; +typedef struct fpdf_form_handle_t__* FPDF_FORMHANDLE; +typedef const struct fpdf_glyphpath_t__* FPDF_GLYPHPATH; +typedef struct fpdf_javascript_action_t* FPDF_JAVASCRIPT_ACTION; +typedef struct fpdf_link_t__* FPDF_LINK; +typedef struct fpdf_page_t__* FPDF_PAGE; +typedef struct fpdf_pagelink_t__* FPDF_PAGELINK; +typedef struct fpdf_pageobject_t__* FPDF_PAGEOBJECT; // (text, path, etc.) +typedef struct fpdf_pageobjectmark_t__* FPDF_PAGEOBJECTMARK; +typedef const struct fpdf_pagerange_t__* FPDF_PAGERANGE; +typedef const struct fpdf_pathsegment_t* FPDF_PATHSEGMENT; +typedef struct fpdf_schhandle_t__* FPDF_SCHHANDLE; +typedef const struct fpdf_signature_t__* FPDF_SIGNATURE; +typedef void* FPDF_SKIA_CANVAS; // Passed into Skia as an SkCanvas. +typedef struct fpdf_structelement_t__* FPDF_STRUCTELEMENT; +typedef const struct fpdf_structelement_attr_t__* FPDF_STRUCTELEMENT_ATTR; +typedef const struct fpdf_structelement_attr_value_t__* +FPDF_STRUCTELEMENT_ATTR_VALUE; +typedef struct fpdf_structtree_t__* FPDF_STRUCTTREE; +typedef struct fpdf_textpage_t__* FPDF_TEXTPAGE; +typedef struct fpdf_widget_t__* FPDF_WIDGET; +typedef struct fpdf_xobject_t__* FPDF_XOBJECT; + +// Basic data types +typedef int FPDF_BOOL; +typedef int FPDF_RESULT; +typedef unsigned long FPDF_DWORD; +typedef float FS_FLOAT; + +// Duplex types +typedef enum _FPDF_DUPLEXTYPE_ { + DuplexUndefined = 0, + Simplex, + DuplexFlipShortEdge, + DuplexFlipLongEdge +} FPDF_DUPLEXTYPE; + +// String types +typedef unsigned short FPDF_WCHAR; + +// The public PDFium API uses three types of strings: byte string, wide string +// (UTF-16LE encoded), and platform dependent string. + +// Public PDFium API type for byte strings. +typedef const char* FPDF_BYTESTRING; + +// The public PDFium API always uses UTF-16LE encoded wide strings, each +// character uses 2 bytes (except surrogation), with the low byte first. +typedef const FPDF_WCHAR* FPDF_WIDESTRING; + +// Structure for persisting a string beyond the duration of a callback. +// Note: although represented as a char*, string may be interpreted as +// a UTF-16LE formated string. Used only by XFA callbacks. +typedef struct FPDF_BSTR_ { + char* str; // String buffer, manipulate only with FPDF_BStr_* methods. + int len; // Length of the string, in bytes. +} FPDF_BSTR; + +// For Windows programmers: In most cases it's OK to treat FPDF_WIDESTRING as a +// Windows unicode string, however, special care needs to be taken if you +// expect to process Unicode larger than 0xffff. +// +// For Linux/Unix programmers: most compiler/library environments use 4 bytes +// for a Unicode character, and you have to convert between FPDF_WIDESTRING and +// system wide string by yourself. +typedef const char* FPDF_STRING; + +// Matrix for transformation, in the form [a b c d e f], equivalent to: +// | a b 0 | +// | c d 0 | +// | e f 1 | +// +// Translation is performed with [1 0 0 1 tx ty]. +// Scaling is performed with [sx 0 0 sy 0 0]. +// See PDF Reference 1.7, 4.2.2 Common Transformations for more. +typedef struct _FS_MATRIX_ { + float a; + float b; + float c; + float d; + float e; + float f; +} FS_MATRIX; + +// Rectangle area(float) in device or page coordinate system. +typedef struct _FS_RECTF_ { + // The x-coordinate of the left-top corner. + float left; + // The y-coordinate of the left-top corner. + float top; + // The x-coordinate of the right-bottom corner. + float right; + // The y-coordinate of the right-bottom corner. + float bottom; +} * FS_LPRECTF, FS_RECTF; + +// Const Pointer to FS_RECTF structure. +typedef const FS_RECTF* FS_LPCRECTF; + +// Rectangle size. Coordinate system agnostic. +typedef struct FS_SIZEF_ { + float width; + float height; +} * FS_LPSIZEF, FS_SIZEF; + +// Const Pointer to FS_SIZEF structure. +typedef const FS_SIZEF* FS_LPCSIZEF; + +// 2D Point. Coordinate system agnostic. +typedef struct FS_POINTF_ { + float x; + float y; +} * FS_LPPOINTF, FS_POINTF; + +// Const Pointer to FS_POINTF structure. +typedef const FS_POINTF* FS_LPCPOINTF; + +typedef struct _FS_QUADPOINTSF { + FS_FLOAT x1; + FS_FLOAT y1; + FS_FLOAT x2; + FS_FLOAT y2; + FS_FLOAT x3; + FS_FLOAT y3; + FS_FLOAT x4; + FS_FLOAT y4; +} FS_QUADPOINTSF; + +// Annotation enums. +typedef int FPDF_ANNOTATION_SUBTYPE; +typedef int FPDF_ANNOT_APPEARANCEMODE; + +// Dictionary value types. +typedef int FPDF_OBJECT_TYPE; + +#if defined(WIN32) +#if defined(FPDF_IMPLEMENTATION) +#define FPDF_EXPORT __declspec(dllexport) +#else +#define FPDF_EXPORT __declspec(dllimport) +#endif // defined(FPDF_IMPLEMENTATION) +#else +#if defined(FPDF_IMPLEMENTATION) +#define FPDF_EXPORT __attribute__((visibility("default"))) +#else +#define FPDF_EXPORT +#endif // defined(FPDF_IMPLEMENTATION) +#endif // defined(WIN32) + +#if defined(WIN32) && defined(FPDFSDK_EXPORTS) +#define FPDF_CALLCONV __stdcall +#else +#define FPDF_CALLCONV +#endif + +// Exported Functions +#ifdef __cplusplus +extern "C" { +#endif + +// PDF renderer types - Experimental. +// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs. +typedef enum { + // Anti-Grain Geometry - https://sourceforge.net/projects/agg/ + FPDF_RENDERERTYPE_AGG = 0, + // Skia - https://skia.org/ + FPDF_RENDERERTYPE_SKIA = 1, +} FPDF_RENDERER_TYPE; + +// Process-wide options for initializing the library. +typedef struct FPDF_LIBRARY_CONFIG_ { + // Version number of the interface. Currently must be 2. + // Support for version 1 will be deprecated in the future. + int version; + + // Array of paths to scan in place of the defaults when using built-in + // FXGE font loading code. The array is terminated by a NULL pointer. + // The Array may be NULL itself to use the default paths. May be ignored + // entirely depending upon the platform. + const char** m_pUserFontPaths; + + // Version 2. + + // Pointer to the v8::Isolate to use, or NULL to force PDFium to create one. + void* m_pIsolate; + + // The embedder data slot to use in the v8::Isolate to store PDFium's + // per-isolate data. The value needs to be in the range + // [0, |v8::Internals::kNumIsolateDataLots|). Note that 0 is fine for most + // embedders. + unsigned int m_v8EmbedderSlot; + + // Version 3 - Experimental. + + // Pointer to the V8::Platform to use. + void* m_pPlatform; + + // Version 4 - Experimental. + + // Explicit specification of core renderer to use. |m_RendererType| must be + // a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher, + // or else the initialization will fail with an immediate crash. + // Note that use of a specified |FPDF_RENDERER_TYPE| value for which the + // corresponding render library is not included in the build will similarly + // fail with an immediate crash. + FPDF_RENDERER_TYPE m_RendererType; +} FPDF_LIBRARY_CONFIG; + +// Function: FPDF_InitLibraryWithConfig +// Initialize the PDFium library and allocate global resources for it. +// Parameters: +// config - configuration information as above. +// Return value: +// None. +// Comments: +// You have to call this function before you can call any PDF +// processing functions. +FPDF_EXPORT void FPDF_CALLCONV +FPDF_InitLibraryWithConfig(const FPDF_LIBRARY_CONFIG* config); + +// Function: FPDF_InitLibrary +// Initialize the PDFium library (alternative form). +// Parameters: +// None +// Return value: +// None. +// Comments: +// Convenience function to call FPDF_InitLibraryWithConfig() with a +// default configuration for backwards compatibility purposes. New +// code should call FPDF_InitLibraryWithConfig() instead. This will +// be deprecated in the future. +FPDF_EXPORT void FPDF_CALLCONV FPDF_InitLibrary(); + +// Function: FPDF_DestroyLibrary +// Release global resources allocated to the PDFium library by +// FPDF_InitLibrary() or FPDF_InitLibraryWithConfig(). +// Parameters: +// None. +// Return value: +// None. +// Comments: +// After this function is called, you must not call any PDF +// processing functions. +// +// Calling this function does not automatically close other +// objects. It is recommended to close other objects before +// closing the library with this function. +FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyLibrary(); + +// Policy for accessing the local machine time. +#define FPDF_POLICY_MACHINETIME_ACCESS 0 + +// Function: FPDF_SetSandBoxPolicy +// Set the policy for the sandbox environment. +// Parameters: +// policy - The specified policy for setting, for example: +// FPDF_POLICY_MACHINETIME_ACCESS. +// enable - True to enable, false to disable the policy. +// Return value: +// None. +FPDF_EXPORT void FPDF_CALLCONV FPDF_SetSandBoxPolicy(FPDF_DWORD policy, + FPDF_BOOL enable); + +#if defined(_WIN32) +// Experimental API. +// Function: FPDF_SetPrintMode +// Set printing mode when printing on Windows. +// Parameters: +// mode - FPDF_PRINTMODE_EMF to output EMF (default) +// FPDF_PRINTMODE_TEXTONLY to output text only (for charstream +// devices) +// FPDF_PRINTMODE_POSTSCRIPT2 to output level 2 PostScript into +// EMF as a series of GDI comments. +// FPDF_PRINTMODE_POSTSCRIPT3 to output level 3 PostScript into +// EMF as a series of GDI comments. +// FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH to output level 2 +// PostScript via ExtEscape() in PASSTHROUGH mode. +// FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH to output level 3 +// PostScript via ExtEscape() in PASSTHROUGH mode. +// FPDF_PRINTMODE_EMF_IMAGE_MASKS to output EMF, with more +// efficient processing of documents containing image masks. +// FPDF_PRINTMODE_POSTSCRIPT3_TYPE42 to output level 3 +// PostScript with embedded Type 42 fonts, when applicable, into +// EMF as a series of GDI comments. +// FPDF_PRINTMODE_POSTSCRIPT3_TYPE42_PASSTHROUGH to output level +// 3 PostScript with embedded Type 42 fonts, when applicable, +// via ExtEscape() in PASSTHROUGH mode. +// Return value: +// True if successful, false if unsuccessful (typically invalid input). +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SetPrintMode(int mode); +#endif // defined(_WIN32) + +// Function: FPDF_LoadDocument +// Open and load a PDF document. +// Parameters: +// file_path - Path to the PDF file (including extension). +// password - A string used as the password for the PDF file. +// If no password is needed, empty or NULL can be used. +// See comments below regarding the encoding. +// Return value: +// A handle to the loaded document, or NULL on failure. +// Comments: +// Loaded document can be closed by FPDF_CloseDocument(). +// If this function fails, you can use FPDF_GetLastError() to retrieve +// the reason why it failed. +// +// The encoding for |file_path| is UTF-8. +// +// The encoding for |password| can be either UTF-8 or Latin-1. PDFs, +// depending on the security handler revision, will only accept one or +// the other encoding. If |password|'s encoding and the PDF's expected +// encoding do not match, FPDF_LoadDocument() will automatically +// convert |password| to the other encoding. +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadDocument(FPDF_STRING file_path, FPDF_BYTESTRING password); + +// Function: FPDF_LoadMemDocument +// Open and load a PDF document from memory. +// Parameters: +// data_buf - Pointer to a buffer containing the PDF document. +// size - Number of bytes in the PDF document. +// password - A string used as the password for the PDF file. +// If no password is needed, empty or NULL can be used. +// Return value: +// A handle to the loaded document, or NULL on failure. +// Comments: +// The memory buffer must remain valid when the document is open. +// The loaded document can be closed by FPDF_CloseDocument. +// If this function fails, you can use FPDF_GetLastError() to retrieve +// the reason why it failed. +// +// See the comments for FPDF_LoadDocument() regarding the encoding for +// |password|. +// Notes: +// If PDFium is built with the XFA module, the application should call +// FPDF_LoadXFA() function after the PDF document loaded to support XFA +// fields defined in the fpdfformfill.h file. +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadMemDocument(const void* data_buf, int size, FPDF_BYTESTRING password); + +// Experimental API. +// Function: FPDF_LoadMemDocument64 +// Open and load a PDF document from memory. +// Parameters: +// data_buf - Pointer to a buffer containing the PDF document. +// size - Number of bytes in the PDF document. +// password - A string used as the password for the PDF file. +// If no password is needed, empty or NULL can be used. +// Return value: +// A handle to the loaded document, or NULL on failure. +// Comments: +// The memory buffer must remain valid when the document is open. +// The loaded document can be closed by FPDF_CloseDocument. +// If this function fails, you can use FPDF_GetLastError() to retrieve +// the reason why it failed. +// +// See the comments for FPDF_LoadDocument() regarding the encoding for +// |password|. +// Notes: +// If PDFium is built with the XFA module, the application should call +// FPDF_LoadXFA() function after the PDF document loaded to support XFA +// fields defined in the fpdfformfill.h file. +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadMemDocument64(const void* data_buf, + size_t size, + FPDF_BYTESTRING password); + +// Structure for custom file access. +typedef struct { + // File length, in bytes. + unsigned long m_FileLen; + + // A function pointer for getting a block of data from a specific position. + // Position is specified by byte offset from the beginning of the file. + // The pointer to the buffer is never NULL and the size is never 0. + // The position and size will never go out of range of the file length. + // It may be possible for PDFium to call this function multiple times for + // the same position. + // Return value: should be non-zero if successful, zero for error. + int (*m_GetBlock)(void* param, + unsigned long position, + unsigned char* pBuf, + unsigned long size); + + // A custom pointer for all implementation specific data. This pointer will + // be used as the first parameter to the m_GetBlock callback. + void* m_Param; +} FPDF_FILEACCESS; + +// Structure for file reading or writing (I/O). +// +// Note: This is a handler and should be implemented by callers, +// and is only used from XFA. +typedef struct FPDF_FILEHANDLER_ { + // User-defined data. + // Note: Callers can use this field to track controls. + void* clientData; + + // Callback function to release the current file stream object. + // + // Parameters: + // clientData - Pointer to user-defined data. + // Returns: + // None. + void (*Release)(void* clientData); + + // Callback function to retrieve the current file stream size. + // + // Parameters: + // clientData - Pointer to user-defined data. + // Returns: + // Size of file stream. + FPDF_DWORD (*GetSize)(void* clientData); + + // Callback function to read data from the current file stream. + // + // Parameters: + // clientData - Pointer to user-defined data. + // offset - Offset position starts from the beginning of file + // stream. This parameter indicates reading position. + // buffer - Memory buffer to store data which are read from + // file stream. This parameter should not be NULL. + // size - Size of data which should be read from file stream, + // in bytes. The buffer indicated by |buffer| must be + // large enough to store specified data. + // Returns: + // 0 for success, other value for failure. + FPDF_RESULT (*ReadBlock)(void* clientData, + FPDF_DWORD offset, + void* buffer, + FPDF_DWORD size); + + // Callback function to write data into the current file stream. + // + // Parameters: + // clientData - Pointer to user-defined data. + // offset - Offset position starts from the beginning of file + // stream. This parameter indicates writing position. + // buffer - Memory buffer contains data which is written into + // file stream. This parameter should not be NULL. + // size - Size of data which should be written into file + // stream, in bytes. + // Returns: + // 0 for success, other value for failure. + FPDF_RESULT (*WriteBlock)(void* clientData, + FPDF_DWORD offset, + const void* buffer, + FPDF_DWORD size); + // Callback function to flush all internal accessing buffers. + // + // Parameters: + // clientData - Pointer to user-defined data. + // Returns: + // 0 for success, other value for failure. + FPDF_RESULT (*Flush)(void* clientData); + + // Callback function to change file size. + // + // Description: + // This function is called under writing mode usually. Implementer + // can determine whether to realize it based on application requests. + // Parameters: + // clientData - Pointer to user-defined data. + // size - New size of file stream, in bytes. + // Returns: + // 0 for success, other value for failure. + FPDF_RESULT (*Truncate)(void* clientData, FPDF_DWORD size); +} FPDF_FILEHANDLER; + +// Function: FPDF_LoadCustomDocument +// Load PDF document from a custom access descriptor. +// Parameters: +// pFileAccess - A structure for accessing the file. +// password - Optional password for decrypting the PDF file. +// Return value: +// A handle to the loaded document, or NULL on failure. +// Comments: +// The application must keep the file resources |pFileAccess| points to +// valid until the returned FPDF_DOCUMENT is closed. |pFileAccess| +// itself does not need to outlive the FPDF_DOCUMENT. +// +// The loaded document can be closed with FPDF_CloseDocument(). +// +// See the comments for FPDF_LoadDocument() regarding the encoding for +// |password|. +// Notes: +// If PDFium is built with the XFA module, the application should call +// FPDF_LoadXFA() function after the PDF document loaded to support XFA +// fields defined in the fpdfformfill.h file. +FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV +FPDF_LoadCustomDocument(FPDF_FILEACCESS* pFileAccess, FPDF_BYTESTRING password); + +// Function: FPDF_GetFileVersion +// Get the file version of the given PDF document. +// Parameters: +// doc - Handle to a document. +// fileVersion - The PDF file version. File version: 14 for 1.4, 15 +// for 1.5, ... +// Return value: +// True if succeeds, false otherwise. +// Comments: +// If the document was created by FPDF_CreateNewDocument, +// then this function will always fail. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetFileVersion(FPDF_DOCUMENT doc, + int* fileVersion); + +#define FPDF_ERR_SUCCESS 0 // No error. +#define FPDF_ERR_UNKNOWN 1 // Unknown error. +#define FPDF_ERR_FILE 2 // File not found or could not be opened. +#define FPDF_ERR_FORMAT 3 // File not in PDF format or corrupted. +#define FPDF_ERR_PASSWORD 4 // Password required or incorrect password. +#define FPDF_ERR_SECURITY 5 // Unsupported security scheme. +#define FPDF_ERR_PAGE 6 // Page not found or content error. +#ifdef PDF_ENABLE_XFA +#define FPDF_ERR_XFALOAD 7 // Load XFA error. +#define FPDF_ERR_XFALAYOUT 8 // Layout XFA error. +#endif // PDF_ENABLE_XFA + +// Function: FPDF_GetLastError +// Get last error code when a function fails. +// Parameters: +// None. +// Return value: +// A 32-bit integer indicating error code as defined above. +// Comments: +// If the previous SDK call succeeded, the return value of this +// function is not defined. This function only works in conjunction +// with APIs that mention FPDF_GetLastError() in their documentation. +FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetLastError(); + +// Experimental API. +// Function: FPDF_DocumentHasValidCrossReferenceTable +// Whether the document's cross reference table is valid or not. +// Parameters: +// document - Handle to a document. Returned by FPDF_LoadDocument. +// Return value: +// True if the PDF parser did not encounter problems parsing the cross +// reference table. False if the parser could not parse the cross +// reference table and the table had to be rebuild from other data +// within the document. +// Comments: +// The return value can change over time as the PDF parser evolves. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_DocumentHasValidCrossReferenceTable(FPDF_DOCUMENT document); + +// Experimental API. +// Function: FPDF_GetTrailerEnds +// Get the byte offsets of trailer ends. +// Parameters: +// document - Handle to document. Returned by FPDF_LoadDocument(). +// buffer - The address of a buffer that receives the +// byte offsets. +// length - The size, in ints, of |buffer|. +// Return value: +// Returns the number of ints in the buffer on success, 0 on error. +// +// |buffer| is an array of integers that describes the exact byte offsets of the +// trailer ends in the document. If |length| is less than the returned length, +// or |document| or |buffer| is NULL, |buffer| will not be modified. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_GetTrailerEnds(FPDF_DOCUMENT document, + unsigned int* buffer, + unsigned long length); + +// Function: FPDF_GetDocPermissions +// Get file permission flags of the document. +// Parameters: +// document - Handle to a document. Returned by FPDF_LoadDocument. +// Return value: +// A 32-bit integer indicating permission flags. Please refer to the +// PDF Reference for detailed descriptions. If the document is not +// protected or was unlocked by the owner, 0xffffffff will be returned. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_GetDocPermissions(FPDF_DOCUMENT document); + +// Function: FPDF_GetDocUserPermissions +// Get user file permission flags of the document. +// Parameters: +// document - Handle to a document. Returned by FPDF_LoadDocument. +// Return value: +// A 32-bit integer indicating permission flags. Please refer to the +// PDF Reference for detailed descriptions. If the document is not +// protected, 0xffffffff will be returned. Always returns user +// permissions, even if the document was unlocked by the owner. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_GetDocUserPermissions(FPDF_DOCUMENT document); + +// Function: FPDF_GetSecurityHandlerRevision +// Get the revision for the security handler. +// Parameters: +// document - Handle to a document. Returned by FPDF_LoadDocument. +// Return value: +// The security handler revision number. Please refer to the PDF +// Reference for a detailed description. If the document is not +// protected, -1 will be returned. +FPDF_EXPORT int FPDF_CALLCONV +FPDF_GetSecurityHandlerRevision(FPDF_DOCUMENT document); + +// Function: FPDF_GetPageCount +// Get total number of pages in the document. +// Parameters: +// document - Handle to document. Returned by FPDF_LoadDocument. +// Return value: +// Total number of pages in the document. +FPDF_EXPORT int FPDF_CALLCONV FPDF_GetPageCount(FPDF_DOCUMENT document); + +// Function: FPDF_LoadPage +// Load a page inside the document. +// Parameters: +// document - Handle to document. Returned by FPDF_LoadDocument +// page_index - Index number of the page. 0 for the first page. +// Return value: +// A handle to the loaded page, or NULL if page load fails. +// Comments: +// The loaded page can be rendered to devices using FPDF_RenderPage. +// The loaded page can be closed using FPDF_ClosePage. +FPDF_EXPORT FPDF_PAGE FPDF_CALLCONV FPDF_LoadPage(FPDF_DOCUMENT document, + int page_index); + +// Experimental API +// Function: FPDF_GetPageWidthF +// Get page width. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage(). +// Return value: +// Page width (excluding non-displayable area) measured in points. +// One point is 1/72 inch (around 0.3528 mm). +FPDF_EXPORT float FPDF_CALLCONV FPDF_GetPageWidthF(FPDF_PAGE page); + +// Function: FPDF_GetPageWidth +// Get page width. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage. +// Return value: +// Page width (excluding non-displayable area) measured in points. +// One point is 1/72 inch (around 0.3528 mm). +// Note: +// Prefer FPDF_GetPageWidthF() above. This will be deprecated in the +// future. +FPDF_EXPORT double FPDF_CALLCONV FPDF_GetPageWidth(FPDF_PAGE page); + +// Experimental API +// Function: FPDF_GetPageHeightF +// Get page height. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage(). +// Return value: +// Page height (excluding non-displayable area) measured in points. +// One point is 1/72 inch (around 0.3528 mm) +FPDF_EXPORT float FPDF_CALLCONV FPDF_GetPageHeightF(FPDF_PAGE page); + +// Function: FPDF_GetPageHeight +// Get page height. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage. +// Return value: +// Page height (excluding non-displayable area) measured in points. +// One point is 1/72 inch (around 0.3528 mm) +// Note: +// Prefer FPDF_GetPageHeightF() above. This will be deprecated in the +// future. +FPDF_EXPORT double FPDF_CALLCONV FPDF_GetPageHeight(FPDF_PAGE page); + +// Experimental API. +// Function: FPDF_GetPageBoundingBox +// Get the bounding box of the page. This is the intersection between +// its media box and its crop box. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage. +// rect - Pointer to a rect to receive the page bounding box. +// On an error, |rect| won't be filled. +// Return value: +// True for success. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetPageBoundingBox(FPDF_PAGE page, + FS_RECTF* rect); + +// Experimental API. +// Function: FPDF_GetPageSizeByIndexF +// Get the size of the page at the given index. +// Parameters: +// document - Handle to document. Returned by FPDF_LoadDocument(). +// page_index - Page index, zero for the first page. +// size - Pointer to a FS_SIZEF to receive the page size. +// (in points). +// Return value: +// Non-zero for success. 0 for error (document or page not found). +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_GetPageSizeByIndexF(FPDF_DOCUMENT document, + int page_index, + FS_SIZEF* size); + +// Function: FPDF_GetPageSizeByIndex +// Get the size of the page at the given index. +// Parameters: +// document - Handle to document. Returned by FPDF_LoadDocument. +// page_index - Page index, zero for the first page. +// width - Pointer to a double to receive the page width +// (in points). +// height - Pointer to a double to receive the page height +// (in points). +// Return value: +// Non-zero for success. 0 for error (document or page not found). +// Note: +// Prefer FPDF_GetPageSizeByIndexF() above. This will be deprecated in +// the future. +FPDF_EXPORT int FPDF_CALLCONV FPDF_GetPageSizeByIndex(FPDF_DOCUMENT document, + int page_index, + double* width, + double* height); + +// Page rendering flags. They can be combined with bit-wise OR. +// +// Set if annotations are to be rendered. +#define FPDF_ANNOT 0x01 +// Set if using text rendering optimized for LCD display. This flag will only +// take effect if anti-aliasing is enabled for text. +#define FPDF_LCD_TEXT 0x02 +// Don't use the native text output available on some platforms +#define FPDF_NO_NATIVETEXT 0x04 +// Grayscale output. +#define FPDF_GRAYSCALE 0x08 +// Obsolete, has no effect, retained for compatibility. +#define FPDF_DEBUG_INFO 0x80 +// Obsolete, has no effect, retained for compatibility. +#define FPDF_NO_CATCH 0x100 +// Limit image cache size. +#define FPDF_RENDER_LIMITEDIMAGECACHE 0x200 +// Always use halftone for image stretching. +#define FPDF_RENDER_FORCEHALFTONE 0x400 +// Render for printing. +#define FPDF_PRINTING 0x800 +// Set to disable anti-aliasing on text. This flag will also disable LCD +// optimization for text rendering. +#define FPDF_RENDER_NO_SMOOTHTEXT 0x1000 +// Set to disable anti-aliasing on images. +#define FPDF_RENDER_NO_SMOOTHIMAGE 0x2000 +// Set to disable anti-aliasing on paths. +#define FPDF_RENDER_NO_SMOOTHPATH 0x4000 +// Set whether to render in a reverse Byte order, this flag is only used when +// rendering to a bitmap. +#define FPDF_REVERSE_BYTE_ORDER 0x10 +// Set whether fill paths need to be stroked. This flag is only used when +// FPDF_COLORSCHEME is passed in, since with a single fill color for paths the +// boundaries of adjacent fill paths are less visible. +#define FPDF_CONVERT_FILL_TO_STROKE 0x20 + +// Struct for color scheme. +// Each should be a 32-bit value specifying the color, in 8888 ARGB format. +typedef struct FPDF_COLORSCHEME_ { + FPDF_DWORD path_fill_color; + FPDF_DWORD path_stroke_color; + FPDF_DWORD text_fill_color; + FPDF_DWORD text_stroke_color; +} FPDF_COLORSCHEME; + +#ifdef _WIN32 +// Function: FPDF_RenderPage +// Render contents of a page to a device (screen, bitmap, or printer). +// This function is only supported on Windows. +// Parameters: +// dc - Handle to the device context. +// page - Handle to the page. Returned by FPDF_LoadPage. +// start_x - Left pixel position of the display area in +// device coordinates. +// start_y - Top pixel position of the display area in device +// coordinates. +// size_x - Horizontal size (in pixels) for displaying the page. +// size_y - Vertical size (in pixels) for displaying the page. +// rotate - Page orientation: +// 0 (normal) +// 1 (rotated 90 degrees clockwise) +// 2 (rotated 180 degrees) +// 3 (rotated 90 degrees counter-clockwise) +// flags - 0 for normal display, or combination of flags +// defined above. +// Return value: +// Returns true if the page is rendered successfully, false otherwise. + +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_RenderPage(HDC dc, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags); +#endif + +// Function: FPDF_RenderPageBitmap +// Render contents of a page to a device independent bitmap. +// Parameters: +// bitmap - Handle to the device independent bitmap (as the +// output buffer). The bitmap handle can be created +// by FPDFBitmap_Create or retrieved from an image +// object by FPDFImageObj_GetBitmap. +// page - Handle to the page. Returned by FPDF_LoadPage +// start_x - Left pixel position of the display area in +// bitmap coordinates. +// start_y - Top pixel position of the display area in bitmap +// coordinates. +// size_x - Horizontal size (in pixels) for displaying the page. +// size_y - Vertical size (in pixels) for displaying the page. +// rotate - Page orientation: +// 0 (normal) +// 1 (rotated 90 degrees clockwise) +// 2 (rotated 180 degrees) +// 3 (rotated 90 degrees counter-clockwise) +// flags - 0 for normal display, or combination of the Page +// Rendering flags defined above. With the FPDF_ANNOT +// flag, it renders all annotations that do not require +// user-interaction, which are all annotations except +// widget and popup annotations. +// Return value: +// None. +FPDF_EXPORT void FPDF_CALLCONV FPDF_RenderPageBitmap(FPDF_BITMAP bitmap, + FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int flags); + +// Function: FPDF_RenderPageBitmapWithMatrix +// Render contents of a page to a device independent bitmap. +// Parameters: +// bitmap - Handle to the device independent bitmap (as the +// output buffer). The bitmap handle can be created +// by FPDFBitmap_Create or retrieved by +// FPDFImageObj_GetBitmap. +// page - Handle to the page. Returned by FPDF_LoadPage. +// matrix - The transform matrix, which must be invertible. +// See PDF Reference 1.7, 4.2.2 Common Transformations. +// clipping - The rect to clip to in device coords. +// flags - 0 for normal display, or combination of the Page +// Rendering flags defined above. With the FPDF_ANNOT +// flag, it renders all annotations that do not require +// user-interaction, which are all annotations except +// widget and popup annotations. +// Return value: +// None. Note that behavior is undefined if det of |matrix| is 0. +FPDF_EXPORT void FPDF_CALLCONV +FPDF_RenderPageBitmapWithMatrix(FPDF_BITMAP bitmap, + FPDF_PAGE page, + const FS_MATRIX* matrix, + const FS_RECTF* clipping, + int flags); + +#if defined(PDF_USE_SKIA) +// Experimental API. +// Function: FPDF_RenderPageSkia +// Render contents of a page to a Skia SkCanvas. +// Parameters: +// canvas - SkCanvas to render to. +// page - Handle to the page. +// size_x - Horizontal size (in pixels) for displaying the page. +// size_y - Vertical size (in pixels) for displaying the page. +// Return value: +// None. +FPDF_EXPORT void FPDF_CALLCONV FPDF_RenderPageSkia(FPDF_SKIA_CANVAS canvas, + FPDF_PAGE page, + int size_x, + int size_y); +#endif + +// Function: FPDF_ClosePage +// Close a loaded PDF page. +// Parameters: +// page - Handle to the loaded page. +// Return value: +// None. +FPDF_EXPORT void FPDF_CALLCONV FPDF_ClosePage(FPDF_PAGE page); + +// Function: FPDF_CloseDocument +// Close a loaded PDF document. +// Parameters: +// document - Handle to the loaded document. +// Return value: +// None. +FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseDocument(FPDF_DOCUMENT document); + +// Function: FPDF_DeviceToPage +// Convert the screen coordinates of a point to page coordinates. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage. +// start_x - Left pixel position of the display area in +// device coordinates. +// start_y - Top pixel position of the display area in device +// coordinates. +// size_x - Horizontal size (in pixels) for displaying the page. +// size_y - Vertical size (in pixels) for displaying the page. +// rotate - Page orientation: +// 0 (normal) +// 1 (rotated 90 degrees clockwise) +// 2 (rotated 180 degrees) +// 3 (rotated 90 degrees counter-clockwise) +// device_x - X value in device coordinates to be converted. +// device_y - Y value in device coordinates to be converted. +// page_x - A pointer to a double receiving the converted X +// value in page coordinates. +// page_y - A pointer to a double receiving the converted Y +// value in page coordinates. +// Return value: +// Returns true if the conversion succeeds, and |page_x| and |page_y| +// successfully receives the converted coordinates. +// Comments: +// The page coordinate system has its origin at the left-bottom corner +// of the page, with the X-axis on the bottom going to the right, and +// the Y-axis on the left side going up. +// +// NOTE: this coordinate system can be altered when you zoom, scroll, +// or rotate a page, however, a point on the page should always have +// the same coordinate values in the page coordinate system. +// +// The device coordinate system is device dependent. For screen device, +// its origin is at the left-top corner of the window. However this +// origin can be altered by the Windows coordinate transformation +// utilities. +// +// You must make sure the start_x, start_y, size_x, size_y +// and rotate parameters have exactly same values as you used in +// the FPDF_RenderPage() function call. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_DeviceToPage(FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + int device_x, + int device_y, + double* page_x, + double* page_y); + +// Function: FPDF_PageToDevice +// Convert the page coordinates of a point to screen coordinates. +// Parameters: +// page - Handle to the page. Returned by FPDF_LoadPage. +// start_x - Left pixel position of the display area in +// device coordinates. +// start_y - Top pixel position of the display area in device +// coordinates. +// size_x - Horizontal size (in pixels) for displaying the page. +// size_y - Vertical size (in pixels) for displaying the page. +// rotate - Page orientation: +// 0 (normal) +// 1 (rotated 90 degrees clockwise) +// 2 (rotated 180 degrees) +// 3 (rotated 90 degrees counter-clockwise) +// page_x - X value in page coordinates. +// page_y - Y value in page coordinate. +// device_x - A pointer to an integer receiving the result X +// value in device coordinates. +// device_y - A pointer to an integer receiving the result Y +// value in device coordinates. +// Return value: +// Returns true if the conversion succeeds, and |device_x| and +// |device_y| successfully receives the converted coordinates. +// Comments: +// See comments for FPDF_DeviceToPage(). +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_PageToDevice(FPDF_PAGE page, + int start_x, + int start_y, + int size_x, + int size_y, + int rotate, + double page_x, + double page_y, + int* device_x, + int* device_y); + +// Function: FPDFBitmap_Create +// Create a device independent bitmap (FXDIB). +// Parameters: +// width - The number of pixels in width for the bitmap. +// Must be greater than 0. +// height - The number of pixels in height for the bitmap. +// Must be greater than 0. +// alpha - A flag indicating whether the alpha channel is used. +// Non-zero for using alpha, zero for not using. +// Return value: +// The created bitmap handle, or NULL if a parameter error or out of +// memory. +// Comments: +// The bitmap always uses 4 bytes per pixel. The first byte is always +// double word aligned. +// +// The byte order is BGRx (the last byte unused if no alpha channel) or +// BGRA. +// +// The pixels in a horizontal line are stored side by side, with the +// left most pixel stored first (with lower memory address). +// Each line uses width * 4 bytes. +// +// Lines are stored one after another, with the top most line stored +// first. There is no gap between adjacent lines. +// +// This function allocates enough memory for holding all pixels in the +// bitmap, but it doesn't initialize the buffer. Applications can use +// FPDFBitmap_FillRect() to fill the bitmap using any color. If the OS +// allows it, this function can allocate up to 4 GB of memory. +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_Create(int width, + int height, + int alpha); + +// More DIB formats +// Unknown or unsupported format. +#define FPDFBitmap_Unknown 0 +// Gray scale bitmap, one byte per pixel. +#define FPDFBitmap_Gray 1 +// 3 bytes per pixel, byte order: blue, green, red. +#define FPDFBitmap_BGR 2 +// 4 bytes per pixel, byte order: blue, green, red, unused. +#define FPDFBitmap_BGRx 3 +// 4 bytes per pixel, byte order: blue, green, red, alpha. +// Pixel components are independent of alpha. +#define FPDFBitmap_BGRA 4 +// 4 bytes per pixel, byte order: blue, green, red, alpha. +// Pixel components are premultiplied by alpha. +// Note that this is experimental and only supported when rendering with +// |FPDF_RENDERER_TYPE| is set to |FPDF_RENDERERTYPE_SKIA|. +#define FPDFBitmap_BGRA_Premul 5 + +// Function: FPDFBitmap_CreateEx +// Create a device independent bitmap (FXDIB) +// Parameters: +// width - The number of pixels in width for the bitmap. +// Must be greater than 0. +// height - The number of pixels in height for the bitmap. +// Must be greater than 0. +// format - A number indicating for bitmap format, as defined +// above. +// first_scan - A pointer to the first byte of the first line if +// using an external buffer. If this parameter is NULL, +// then a new buffer will be created. +// stride - Number of bytes for each scan line. The value must +// be 0 or greater. When the value is 0, +// FPDFBitmap_CreateEx() will automatically calculate +// the appropriate value using |width| and |format|. +// When using an external buffer, it is recommended for +// the caller to pass in the value. +// When not using an external buffer, it is recommended +// for the caller to pass in 0. +// Return value: +// The bitmap handle, or NULL if parameter error or out of memory. +// Comments: +// Similar to FPDFBitmap_Create function, but allows for more formats +// and an external buffer is supported. The bitmap created by this +// function can be used in any place that a FPDF_BITMAP handle is +// required. +// +// If an external buffer is used, then the caller should destroy the +// buffer. FPDFBitmap_Destroy() will not destroy the buffer. +// +// It is recommended to use FPDFBitmap_GetStride() to get the stride +// value. +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_CreateEx(int width, + int height, + int format, + void* first_scan, + int stride); + +// Function: FPDFBitmap_GetFormat +// Get the format of the bitmap. +// Parameters: +// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +// or FPDFImageObj_GetBitmap. +// Return value: +// The format of the bitmap. +// Comments: +// Only formats supported by FPDFBitmap_CreateEx are supported by this +// function; see the list of such formats above. +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetFormat(FPDF_BITMAP bitmap); + +// Function: FPDFBitmap_FillRect +// Fill a rectangle in a bitmap. +// Parameters: +// bitmap - The handle to the bitmap. Returned by +// FPDFBitmap_Create. +// left - The left position. Starting from 0 at the +// left-most pixel. +// top - The top position. Starting from 0 at the +// top-most line. +// width - Width in pixels to be filled. +// height - Height in pixels to be filled. +// color - A 32-bit value specifing the color, in 8888 ARGB +// format. +// Return value: +// Returns whether the operation succeeded or not. +// Comments: +// This function sets the color and (optionally) alpha value in the +// specified region of the bitmap. +// +// NOTE: If the alpha channel is used, this function does NOT +// composite the background with the source color, instead the +// background will be replaced by the source color and the alpha. +// +// If the alpha channel is not used, the alpha parameter is ignored. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFBitmap_FillRect(FPDF_BITMAP bitmap, + int left, + int top, + int width, + int height, + FPDF_DWORD color); + +// Function: FPDFBitmap_GetBuffer +// Get data buffer of a bitmap. +// Parameters: +// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +// or FPDFImageObj_GetBitmap. +// Return value: +// The pointer to the first byte of the bitmap buffer. +// Comments: +// The stride may be more than width * number of bytes per pixel +// +// Applications can use this function to get the bitmap buffer pointer, +// then manipulate any color and/or alpha values for any pixels in the +// bitmap. +// +// Use FPDFBitmap_GetFormat() to find out the format of the data. +FPDF_EXPORT void* FPDF_CALLCONV FPDFBitmap_GetBuffer(FPDF_BITMAP bitmap); + +// Function: FPDFBitmap_GetWidth +// Get width of a bitmap. +// Parameters: +// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +// or FPDFImageObj_GetBitmap. +// Return value: +// The width of the bitmap in pixels. +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetWidth(FPDF_BITMAP bitmap); + +// Function: FPDFBitmap_GetHeight +// Get height of a bitmap. +// Parameters: +// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +// or FPDFImageObj_GetBitmap. +// Return value: +// The height of the bitmap in pixels. +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetHeight(FPDF_BITMAP bitmap); + +// Function: FPDFBitmap_GetStride +// Get number of bytes for each line in the bitmap buffer. +// Parameters: +// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +// or FPDFImageObj_GetBitmap. +// Return value: +// The number of bytes for each line in the bitmap buffer. +// Comments: +// The stride may be more than width * number of bytes per pixel. +FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetStride(FPDF_BITMAP bitmap); + +// Function: FPDFBitmap_Destroy +// Destroy a bitmap and release all related buffers. +// Parameters: +// bitmap - Handle to the bitmap. Returned by FPDFBitmap_Create +// or FPDFImageObj_GetBitmap. +// Return value: +// None. +// Comments: +// This function will not destroy any external buffers provided when +// the bitmap was created. +FPDF_EXPORT void FPDF_CALLCONV FPDFBitmap_Destroy(FPDF_BITMAP bitmap); + +// Function: FPDF_VIEWERREF_GetPrintScaling +// Whether the PDF document prefers to be scaled or not. +// Parameters: +// document - Handle to the loaded document. +// Return value: +// None. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDF_VIEWERREF_GetPrintScaling(FPDF_DOCUMENT document); + +// Function: FPDF_VIEWERREF_GetNumCopies +// Returns the number of copies to be printed. +// Parameters: +// document - Handle to the loaded document. +// Return value: +// The number of copies to be printed. +FPDF_EXPORT int FPDF_CALLCONV +FPDF_VIEWERREF_GetNumCopies(FPDF_DOCUMENT document); + +// Function: FPDF_VIEWERREF_GetPrintPageRange +// Page numbers to initialize print dialog box when file is printed. +// Parameters: +// document - Handle to the loaded document. +// Return value: +// The print page range to be used for printing. +FPDF_EXPORT FPDF_PAGERANGE FPDF_CALLCONV +FPDF_VIEWERREF_GetPrintPageRange(FPDF_DOCUMENT document); + +// Experimental API. +// Function: FPDF_VIEWERREF_GetPrintPageRangeCount +// Returns the number of elements in a FPDF_PAGERANGE. +// Parameters: +// pagerange - Handle to the page range. +// Return value: +// The number of elements in the page range. Returns 0 on error. +FPDF_EXPORT size_t FPDF_CALLCONV +FPDF_VIEWERREF_GetPrintPageRangeCount(FPDF_PAGERANGE pagerange); + +// Experimental API. +// Function: FPDF_VIEWERREF_GetPrintPageRangeElement +// Returns an element from a FPDF_PAGERANGE. +// Parameters: +// pagerange - Handle to the page range. +// index - Index of the element. +// Return value: +// The value of the element in the page range at a given index. +// Returns -1 on error. +FPDF_EXPORT int FPDF_CALLCONV +FPDF_VIEWERREF_GetPrintPageRangeElement(FPDF_PAGERANGE pagerange, size_t index); + +// Function: FPDF_VIEWERREF_GetDuplex +// Returns the paper handling option to be used when printing from +// the print dialog. +// Parameters: +// document - Handle to the loaded document. +// Return value: +// The paper handling option to be used when printing. +FPDF_EXPORT FPDF_DUPLEXTYPE FPDF_CALLCONV +FPDF_VIEWERREF_GetDuplex(FPDF_DOCUMENT document); + +// Function: FPDF_VIEWERREF_GetName +// Gets the contents for a viewer ref, with a given key. The value must +// be of type "name". +// Parameters: +// document - Handle to the loaded document. +// key - Name of the key in the viewer pref dictionary, +// encoded in UTF-8. +// buffer - Caller-allocate buffer to receive the key, or NULL +// - to query the required length. +// length - Length of the buffer. +// Return value: +// The number of bytes in the contents, including the NULL terminator. +// Thus if the return value is 0, then that indicates an error, such +// as when |document| is invalid. If |length| is less than the required +// length, or |buffer| is NULL, |buffer| will not be modified. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_VIEWERREF_GetName(FPDF_DOCUMENT document, + FPDF_BYTESTRING key, + char* buffer, + unsigned long length); + +// Function: FPDF_CountNamedDests +// Get the count of named destinations in the PDF document. +// Parameters: +// document - Handle to a document +// Return value: +// The count of named destinations. +FPDF_EXPORT FPDF_DWORD FPDF_CALLCONV +FPDF_CountNamedDests(FPDF_DOCUMENT document); + +// Function: FPDF_GetNamedDestByName +// Get a the destination handle for the given name. +// Parameters: +// document - Handle to the loaded document. +// name - The name of a destination. +// Return value: +// The handle to the destination. +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV +FPDF_GetNamedDestByName(FPDF_DOCUMENT document, FPDF_BYTESTRING name); + +// Function: FPDF_GetNamedDest +// Get the named destination by index. +// Parameters: +// document - Handle to a document +// index - The index of a named destination. +// buffer - The buffer to store the destination name, +// used as wchar_t*. +// buflen [in/out] - Size of the buffer in bytes on input, +// length of the result in bytes on output +// or -1 if the buffer is too small. +// Return value: +// The destination handle for a given index, or NULL if there is no +// named destination corresponding to |index|. +// Comments: +// Call this function twice to get the name of the named destination: +// 1) First time pass in |buffer| as NULL and get buflen. +// 2) Second time pass in allocated |buffer| and buflen to retrieve +// |buffer|, which should be used as wchar_t*. +// +// If buflen is not sufficiently large, it will be set to -1 upon +// return. +FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDF_GetNamedDest(FPDF_DOCUMENT document, + int index, + void* buffer, + long* buflen); + +// Experimental API. +// Function: FPDF_GetXFAPacketCount +// Get the number of valid packets in the XFA entry. +// Parameters: +// document - Handle to the document. +// Return value: +// The number of valid packets, or -1 on error. +FPDF_EXPORT int FPDF_CALLCONV FPDF_GetXFAPacketCount(FPDF_DOCUMENT document); + +// Experimental API. +// Function: FPDF_GetXFAPacketName +// Get the name of a packet in the XFA array. +// Parameters: +// document - Handle to the document. +// index - Index number of the packet. 0 for the first packet. +// buffer - Buffer for holding the name of the XFA packet. +// buflen - Length of |buffer| in bytes. +// Return value: +// The length of the packet name in bytes, or 0 on error. +// +// |document| must be valid and |index| must be in the range [0, N), where N is +// the value returned by FPDF_GetXFAPacketCount(). +// |buffer| is only modified if it is non-NULL and |buflen| is greater than or +// equal to the length of the packet name. The packet name includes a +// terminating NUL character. |buffer| is unmodified on error. +FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetXFAPacketName( + FPDF_DOCUMENT document, + int index, + void* buffer, + unsigned long buflen); + +// Experimental API. +// Function: FPDF_GetXFAPacketContent +// Get the content of a packet in the XFA array. +// Parameters: +// document - Handle to the document. +// index - Index number of the packet. 0 for the first packet. +// buffer - Buffer for holding the content of the XFA packet. +// buflen - Length of |buffer| in bytes. +// out_buflen - Pointer to the variable that will receive the minimum +// buffer size needed to contain the content of the XFA +// packet. +// Return value: +// Whether the operation succeeded or not. +// +// |document| must be valid and |index| must be in the range [0, N), where N is +// the value returned by FPDF_GetXFAPacketCount(). |out_buflen| must not be +// NULL. When the aforementioned arguments are valid, the operation succeeds, +// and |out_buflen| receives the content size. |buffer| is only modified if +// |buffer| is non-null and long enough to contain the content. Callers must +// check both the return value and the input |buflen| is no less than the +// returned |out_buflen| before using the data in |buffer|. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_GetXFAPacketContent( + FPDF_DOCUMENT document, + int index, + void* buffer, + unsigned long buflen, + unsigned long* out_buflen); + +#ifdef PDF_ENABLE_V8 +// Function: FPDF_GetRecommendedV8Flags +// Returns a space-separated string of command line flags that are +// recommended to be passed into V8 via V8::SetFlagsFromString() +// prior to initializing the PDFium library. +// Parameters: +// None. +// Return value: +// NUL-terminated string of the form "--flag1 --flag2". +// The caller must not attempt to modify or free the result. +FPDF_EXPORT const char* FPDF_CALLCONV FPDF_GetRecommendedV8Flags(); + +// Experimental API. +// Function: FPDF_GetArrayBufferAllocatorSharedInstance() +// Helper function for initializing V8 isolates that will +// use PDFium's internal memory management. +// Parameters: +// None. +// Return Value: +// Pointer to a suitable v8::ArrayBuffer::Allocator, returned +// as void for C compatibility. +// Notes: +// Use is optional, but allows external creation of isolates +// matching the ones PDFium will make when none is provided +// via |FPDF_LIBRARY_CONFIG::m_pIsolate|. +// +// Can only be called when the library is in an uninitialized or +// destroyed state. +FPDF_EXPORT void* FPDF_CALLCONV FPDF_GetArrayBufferAllocatorSharedInstance(); +#endif // PDF_ENABLE_V8 + +#ifdef PDF_ENABLE_XFA +// Function: FPDF_BStr_Init +// Helper function to initialize a FPDF_BSTR. +FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Init(FPDF_BSTR* bstr); + +// Function: FPDF_BStr_Set +// Helper function to copy string data into the FPDF_BSTR. +FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Set(FPDF_BSTR* bstr, + const char* cstr, + int length); + +// Function: FPDF_BStr_Clear +// Helper function to clear a FPDF_BSTR. +FPDF_EXPORT FPDF_RESULT FPDF_CALLCONV FPDF_BStr_Clear(FPDF_BSTR* bstr); +#endif // PDF_ENABLE_XFA + +#ifdef __cplusplus +} +#endif + +#endif // PUBLIC_FPDFVIEW_H_ diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/cpp/pdfium_wrapper_jni.cpp b/packages/syncfusion_pdfviewer_android/android/src/main/cpp/pdfium_wrapper_jni.cpp new file mode 100644 index 000000000..1b68bbb90 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/src/main/cpp/pdfium_wrapper_jni.cpp @@ -0,0 +1,189 @@ +#include +#include +#include + +extern "C" { + +static void* pdfiumLibHandle = nullptr; + +JNIEXPORT jboolean JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_initLibrary(JNIEnv* env, jobject thiz) { + const char* libName = "libpdfium.so"; + + pdfiumLibHandle = dlopen(libName, RTLD_NOW | RTLD_LOCAL); + if (!pdfiumLibHandle) { + return JNI_FALSE; + } + FPDF_InitLibraryWithConfig(nullptr); + return JNI_TRUE; +} + +JNIEXPORT void JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_destroyLibrary(JNIEnv* env, jobject thiz) { + FPDF_DestroyLibrary(); + if (pdfiumLibHandle) { + dlclose(pdfiumLibHandle); + pdfiumLibHandle = nullptr; + } +} + +JNIEXPORT jlong JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_loadDocument(JNIEnv* env, jobject thiz, jbyteArray pdfData, jstring password) { + if (!pdfiumLibHandle) { + return 0; + } + if (pdfData == nullptr) { + return 0; + } + + jbyte* buffer = env->GetByteArrayElements(pdfData, nullptr); + jsize length = env->GetArrayLength(pdfData); + const char* pwd = password ? env->GetStringUTFChars(password, nullptr) : nullptr; + + FPDF_DOCUMENT doc = FPDF_LoadMemDocument64(buffer, (size_t)length, pwd); + env->ReleaseByteArrayElements(pdfData, buffer, JNI_ABORT); + if (pwd) { + env->ReleaseStringUTFChars(password, pwd); + } + if (!doc) { + return 0; + } + + return reinterpret_cast(doc); +} + +JNIEXPORT void JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_closeDocument(JNIEnv* env, jobject thiz, jlong docHandle) { + if (!pdfiumLibHandle) { + return; + } + FPDF_DOCUMENT doc = reinterpret_cast(docHandle); + if (doc) { + FPDF_CloseDocument(doc); + } +} + +JNIEXPORT jint JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_getPageCount(JNIEnv* env, jobject thiz, jlong docHandle) { + if (!pdfiumLibHandle) { + return -1; + } + FPDF_DOCUMENT doc = reinterpret_cast(docHandle); + return doc ? FPDF_GetPageCount(doc) : -1; +} + +JNIEXPORT jfloat JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_getPageWidth(JNIEnv* env, jobject thiz, jlong docHandle, jint index) { + if (!pdfiumLibHandle) { + return -1; + } + FPDF_DOCUMENT doc = reinterpret_cast(docHandle); + if (!doc) return -1; + + FPDF_PAGE page = FPDF_LoadPage(doc, index); + if (!page) return -1; + jfloat width = FPDF_GetPageWidthF(page); + FPDF_ClosePage(page); + return width; +} + +JNIEXPORT jfloat JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_getPageHeight(JNIEnv* env, jobject thiz, jlong docHandle, jint index) { + if (!pdfiumLibHandle) { + return -1; + } + FPDF_DOCUMENT doc = reinterpret_cast(docHandle); + if (!doc) return -1; + + FPDF_PAGE page = FPDF_LoadPage(doc, index); + if (!page) return -1; + + jfloat height = FPDF_GetPageHeightF(page); + FPDF_ClosePage(page); + return height; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_renderPage(JNIEnv* env, jobject thiz, jlong docHandle, jint pageIndex, jint width, jint height) { + if (!pdfiumLibHandle) { + return nullptr; + } + FPDF_DOCUMENT doc = reinterpret_cast(docHandle); + if (!doc) return nullptr; + + FPDF_PAGE page = FPDF_LoadPage(doc, pageIndex); + if (!page) return nullptr; + + FPDF_BITMAP bitmap = FPDFBitmap_Create(width, height, 0); + if (!bitmap) { + FPDF_ClosePage(page); + return nullptr; + } + + FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF); + FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, 0, FPDF_LCD_TEXT | FPDF_REVERSE_BYTE_ORDER); + + void* buffer = FPDFBitmap_GetBuffer(bitmap); + int stride = FPDFBitmap_GetStride(bitmap); + int totalSize = stride * height; + + jbyteArray result = env->NewByteArray(totalSize); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, totalSize, (jbyte*)buffer); + } + + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + + return result; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_syncfusion_flutter_pdfviewer_android_PdfiumAdapter_renderTile(JNIEnv* env, jobject thiz, + jlong docHandle, + jint pageIndex, + jfloat x, jfloat y, + jint width, jint height, + jfloat scale) { + if (!pdfiumLibHandle) { + return nullptr; + } + FPDF_DOCUMENT doc = reinterpret_cast(docHandle); + if (!doc) return nullptr; + + FPDF_PAGE page = FPDF_LoadPage(doc, pageIndex); + if (!page) return nullptr; + + FPDF_BITMAP bitmap = FPDFBitmap_Create(width, height, 0); + if (!bitmap) { + FPDF_ClosePage(page); + return nullptr; + } + + FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF); + + FS_MATRIX matrix = { + scale, 0, 0, + scale, -x * scale, -y * scale + }; + + FS_RECTF clip = {0, 0, (float)(width * scale), (float)(height * scale)}; + + FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &clip, FPDF_LCD_TEXT | FPDF_REVERSE_BYTE_ORDER); + + void* buffer = FPDFBitmap_GetBuffer(bitmap); + int stride = FPDFBitmap_GetStride(bitmap); + int totalSize = stride * height; + + jbyteArray result = env->NewByteArray(totalSize); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, totalSize, (jbyte*)buffer); + } + + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + + return result; +} + +} // extern "C" diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/java/com/syncfusion/flutter/pdfviewer/android/PdfiumAdapter.java b/packages/syncfusion_pdfviewer_android/android/src/main/java/com/syncfusion/flutter/pdfviewer/android/PdfiumAdapter.java new file mode 100644 index 000000000..c930f9f33 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/src/main/java/com/syncfusion/flutter/pdfviewer/android/PdfiumAdapter.java @@ -0,0 +1,90 @@ +package com.syncfusion.flutter.pdfviewer.android; + +/** + * PdfiumAdapter provides methods to interact + * with native PDFium libraries for rendering and processing PDFs. + */ +public class PdfiumAdapter { + + static { + // Load the native PDFium wrapper library which acts as a wrapper built via CMake + System.loadLibrary("pdfium_wrapper"); + } + + /** + * Initializes the PDFium library for usage. + */ + public static native boolean initLibrary(); + + /** + * Destroys the PDFium library and cleans up any resources used. + */ + public static native void destroyLibrary(); + + /** + * Loads a PDF document using byte data and an optional password for encrypted PDFs. + * + * @param data the byte array representing the PDF document + * @param password the password for encrypted PDFs (pass an empty string if not needed) + * @return long representing the handle or reference to the loaded document + */ + public static native long loadDocument(byte[] data, String password); + + /** + * Closes the specified PDF document and releases resources tied to it. + * + * @param docHandle the handle to the loaded PDF document + */ + public static native void closeDocument(long docHandle); + + /** + * Retrieves the total number of pages in the specified PDF document. + * + * @param docHandle the handle to the loaded PDF document + * @return the number of pages in the PDF document + */ + public static native int getPageCount(long docHandle); + + /** + * Retrieves the width of a specific page in the PDF document. + * + * @param docHandle the handle to the loaded PDF document + * @param index the page index for which the width is needed + * @return the width of the page in points + */ + public static native float getPageWidth(long docHandle, int index); + + /** + * Retrieves the height of a specific page in the PDF document. + * + * @param docHandle the handle to the loaded PDF document + * @param index the page index for which the height is needed + * @return the height of the page in points + */ + public static native float getPageHeight(long docHandle, int index); + /** + * Renders a specific page of the PDF as an image. + * + * @param docHandle the handle to the loaded PDF document + * @param pageIndex the index of the page to render + * @param width the desired width of the rendered output + * @param height the desired height of the rendered output + * @return a byte array representing the rendered image of the page + */ + public static native byte[] renderPage(long docHandle, int pageIndex, int width, int height); + + /** + * Renders a specific tile of the page to handle large pages efficiently. + * + * @param docHandle the handle to the loaded PDF document + * @param pageIndex the index of the page + * @param x the x coordinate of the tile's top-left corner + * @param y the y coordinate of the tile's top-left corner + * @param width the width of the tile to render + * @param height the height of the tile to render + * @param scale the scale factor for rendering + * @return a byte array representing the rendered tile image + */ + public static native byte[] renderTile(long docHandle, int pageIndex, float x, float y, int width, int height, float scale); +} + diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/java/com/syncfusion/flutter/pdfviewer/android/SyncfusionFlutterPdfViewerPdfiumPlugin.java b/packages/syncfusion_pdfviewer_android/android/src/main/java/com/syncfusion/flutter/pdfviewer/android/SyncfusionFlutterPdfViewerPdfiumPlugin.java new file mode 100644 index 000000000..797bd0015 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/android/src/main/java/com/syncfusion/flutter/pdfviewer/android/SyncfusionFlutterPdfViewerPdfiumPlugin.java @@ -0,0 +1,210 @@ +package com.syncfusion.flutter.pdfviewer.android; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.pdf.PdfRenderer; +import android.os.Build; +import android.os.ParcelFileDescriptor; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class SyncfusionFlutterPdfViewerPdfiumPlugin implements FlutterPlugin, MethodCallHandler { + private MethodChannel channel; + private ExecutorService executorService; + + private HashMap documentHandleMap = new HashMap<>(); + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + boolean initSuccess = PdfiumAdapter.initLibrary(); + if (initSuccess) { + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "syncfusion_flutter_pdfviewer"); + channel.setMethodCallHandler(this); + executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + } + } + } + + @Override + public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result result) { + switch (call.method) { + case "getPage": + getPage(call, result); + break; + case "getTileImage": + getTileImage(call, result); + break; + case "initializePdfRenderer": + initializePdfRenderer(call, result); + break; + case "getPagesWidth": + getPagesWidth(call, result); + break; + case "getPagesHeight": + getPagesHeight(call, result); + break; + case "closeDocument": + closeDocument(call, result); + break; + default: + result.notImplemented(); + break; + } + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + if (channel != null) { + channel.setMethodCallHandler(null); + } + if (executorService != null && !executorService.isShutdown()) { + executorService.shutdown(); + } + PdfiumAdapter.destroyLibrary(); + } + + private void getPage(MethodCall call, Result result) { + int pageIndex = call.argument("index"); + int width = call.argument("width"); + int height = call.argument("height"); + String documentID = call.argument("documentID"); + + Long docHandle = documentHandleMap.get(documentID); + + if (docHandle == null) { + result.error("ERROR", "Document not found", null); + } + + try { + byte[] imageBytes = PdfiumAdapter.renderPage(docHandle, pageIndex - 1, width, height); + result.success(imageBytes); + } catch (Exception e) { + result.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); + } + } + + private void getTileImage(MethodCall call, Result result) { + int pageNumber = call.argument("pageNumber"); + double scale = call.argument("scale"); + double x = call.argument("x"); + double y = call.argument("y"); + double width = call.argument("width"); + double height = call.argument("height"); + String documentID = call.argument("documentID"); + Long docHandle = documentHandleMap.get(documentID); + + if (docHandle == null) { + result.error("ERROR", "Document not found", null); + } + + try { + byte[] imageBytes = PdfiumAdapter.renderTile(docHandle, pageNumber - 1, (float) x, (float) y, (int)width, (int)height, (float) scale); + result.success(imageBytes); + } catch (Exception e) { + result.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); + } + } + + private void initializePdfRenderer(MethodCall call, Result result) { + executorService.execute(() -> { + byte[] bytes = call.argument("documentBytes"); + String documentID = call.argument("documentID"); + String password = call.argument("password"); + + try { + if (password == null) { + password = ""; + } + long docHandle = PdfiumAdapter.loadDocument(bytes, password); + documentHandleMap.put(documentID, docHandle); + int pageCount = PdfiumAdapter.getPageCount(docHandle); + result.success(String.valueOf(pageCount)); + } catch (SecurityException e) { + result.error("PASSWORD_ERROR", "Incorrect password or document is encrypted", null); + } catch (Exception e) { + result.error("PDF_RENDERER_ERROR", e.getMessage(), null); + } + }); + } + + private void getPagesWidth(MethodCall call, Result result) { + String documentID = (String) call.arguments; + Long docHandle = documentHandleMap.get(documentID); + + if (docHandle == null) { + result.error("ERROR", "Document not found", null); + } + + executorService.execute(() -> { + try { + int count = PdfiumAdapter.getPageCount(docHandle); + double[] pageWidth = new double[count]; + for (int i = 0; i < count; i++) { + pageWidth[i] = PdfiumAdapter.getPageWidth(docHandle, i); + } + result.success(pageWidth); + } catch (Exception e) { + result.error("PAGE_WIDTH_ERROR", e.getMessage(), null); + } + }); + } + + private void getPagesHeight(MethodCall call, Result result) { + String documentID = (String) call.arguments; + Long docHandle = documentHandleMap.get(documentID); + + if (docHandle == null) { + result.error("ERROR", "Document not found", null); + } + + executorService.execute(() -> { + try { + int count = PdfiumAdapter.getPageCount(docHandle); + double[] pageHeight = new double[count]; + for (int i = 0; i < count; i++) { + pageHeight[i] = PdfiumAdapter.getPageHeight(docHandle, i); + } + result.success(pageHeight); + } catch (Exception e) { + result.error("PAGE_HEIGHT_ERROR", e.getMessage(), null); + } + }); + } + + private void closeDocument(MethodCall call, Result result) { + String documentID = (String) call.arguments; + Long docHandle = documentHandleMap.get(documentID); + + if (docHandle == null) { + result.error("ERROR", "Document not found", null); + } + PdfiumAdapter.closeDocument(docHandle); + documentHandleMap.remove(docHandle); + result.success(true); + } +} \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/arm64-v8a/libpdfium.so b/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/arm64-v8a/libpdfium.so new file mode 100644 index 000000000..3483071f5 Binary files /dev/null and b/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/arm64-v8a/libpdfium.so differ diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/armeabi-v7a/libpdfium.so b/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/armeabi-v7a/libpdfium.so new file mode 100644 index 000000000..09e9fff91 Binary files /dev/null and b/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/armeabi-v7a/libpdfium.so differ diff --git a/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/x86_64/libpdfium.so b/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/x86_64/libpdfium.so new file mode 100644 index 000000000..7c11d96f3 Binary files /dev/null and b/packages/syncfusion_pdfviewer_android/android/src/main/jniLibs/pdfium/x86_64/libpdfium.so differ diff --git a/packages/syncfusion_pdfviewer_android/example/.gitignore b/packages/syncfusion_pdfviewer_android/example/.gitignore new file mode 100644 index 000000000..79c113f9b --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/syncfusion_pdfviewer_android/example/README.md b/packages/syncfusion_pdfviewer_android/example/README.md new file mode 100644 index 000000000..127423976 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/README.md @@ -0,0 +1,3 @@ +# syncfusion_pdfviewer_windows_example + +Demonstrates how to use the syncfusion_pdfviewer_windows plugin. diff --git a/packages/syncfusion_pdfviewer_android/example/analysis_options.yaml b/packages/syncfusion_pdfviewer_android/example/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_android/example/android/.gitignore b/packages/syncfusion_pdfviewer_android/example/android/.gitignore new file mode 100644 index 000000000..be3943c96 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/build.gradle.kts b/packages/syncfusion_pdfviewer_android/example/android/app/build.gradle.kts new file mode 100644 index 000000000..9abd2e485 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.syncfusion.syncfusion_pdfviewer_android_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.syncfusion.syncfusion_pdfviewer_android_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + 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.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..bf6faa7c6 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/java/com/example/example/MainActivity.java b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/java/com/example/example/MainActivity.java new file mode 100644 index 000000000..59f336d2e --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/java/com/example/example/MainActivity.java @@ -0,0 +1,6 @@ +package com.example.example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_pdfviewer_android/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_pdfviewer_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_pdfviewer_android/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_pdfviewer_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_pdfviewer_android/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_pdfviewer_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_pdfviewer_android/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_pdfviewer_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_pdfviewer_android/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_pdfviewer_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_pdfviewer_android/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_pdfviewer_android/example/android/build.gradle.kts b/packages/syncfusion_pdfviewer_android/example/android/build.gradle.kts new file mode 100644 index 000000000..89176ef44 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/packages/syncfusion_pdfviewer_android/example/android/gradle.properties b/packages/syncfusion_pdfviewer_android/example/android/gradle.properties new file mode 100644 index 000000000..f018a6181 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/syncfusion_pdfviewer_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_pdfviewer_android/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ac3b47926 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/packages/syncfusion_pdfviewer_android/example/android/settings.gradle.kts b/packages/syncfusion_pdfviewer_android/example/android/settings.gradle.kts new file mode 100644 index 000000000..ab39a10a2 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/packages/syncfusion_pdfviewer_android/example/lib/main.dart b/packages/syncfusion_pdfviewer_android/example/lib/main.dart new file mode 100644 index 000000000..d26dc20be --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/lib/main.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; + +void main() { + runApp( + MaterialApp( + title: 'Syncfusion PDF Viewer Demo', + theme: ThemeData(useMaterial3: false), + home: const HomePage(), + ), + ); +} + +/// Represents Homepage for Navigation +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + _HomePage createState() => _HomePage(); +} + +class _HomePage extends State { + final GlobalKey _pdfViewerKey = GlobalKey(); + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Syncfusion Flutter PDF Viewer'), + actions: [ + IconButton( + icon: const Icon(Icons.bookmark, color: Colors.white), + onPressed: () { + _pdfViewerKey.currentState?.openBookmarkView(); + }, + ), + ], + ), + body: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + key: _pdfViewerKey, + ), + ); + } +} diff --git a/packages/syncfusion_pdfviewer_android/example/pubspec.yaml b/packages/syncfusion_pdfviewer_android/example/pubspec.yaml new file mode 100644 index 000000000..487ee2ac7 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: syncfusion_pdfviewer_android_example +description: "Demonstrates how to use the syncfusion_pdfviewer_android plugin." +publish_to: 'none' + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + syncfusion_flutter_pdfviewer: + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_android/pubspec.yaml b/packages/syncfusion_pdfviewer_android/pubspec.yaml new file mode 100644 index 000000000..8d2d3ebc7 --- /dev/null +++ b/packages/syncfusion_pdfviewer_android/pubspec.yaml @@ -0,0 +1,24 @@ +name: syncfusion_pdfviewer_android +description: An optional Android platform implementation of the Flutter PDF Viewer library that lets you view PDF documents seamlessly and efficiently, which uses PDFium for rendering. +version: 30.1.1 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_pdfviewer_android + +environment: + sdk: ^3.7.0-0 + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + android: + package: com.syncfusion.flutter.pdfviewer.android + pluginClass: SyncfusionFlutterPdfViewerPdfiumPlugin diff --git a/packages/syncfusion_pdfviewer_macos/README.md b/packages/syncfusion_pdfviewer_macos/README.md index d8fb53be4..296846669 100644 --- a/packages/syncfusion_pdfviewer_macos/README.md +++ b/packages/syncfusion_pdfviewer_macos/README.md @@ -1,6 +1,6 @@ # Flutter PDF Viewer macOS -The macOS implementation of [Syncfusion Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. +The macOS implementation of [Syncfusion® Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. ## Usage @@ -12,7 +12,7 @@ This package is an endorsed implementation of `syncfusion_flutter_pdfviewer` for ... dependencies: ... - syncfusion_flutter_pdfviewer: ^19.2.0-beta + syncfusion_flutter_pdfviewer: ^20.3.0 ... ... ``` diff --git a/packages/syncfusion_pdfviewer_macos/example/README.md b/packages/syncfusion_pdfviewer_macos/example/README.md new file mode 100644 index 000000000..53fc61bcb --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/example/README.md @@ -0,0 +1,3 @@ +# syncfusion_pdfviewer_macos_example + +Demonstrates how to use the syncfusion_flutter_pdfviewer plugin in macOS platform. diff --git a/packages/syncfusion_pdfviewer_macos/example/analysis_options.yaml b/packages/syncfusion_pdfviewer_macos/example/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_macos/example/lib/main.dart b/packages/syncfusion_pdfviewer_macos/example/lib/main.dart index 55dd8d967..49967a05c 100644 --- a/packages/syncfusion_pdfviewer_macos/example/lib/main.dart +++ b/packages/syncfusion_pdfviewer_macos/example/lib/main.dart @@ -2,10 +2,13 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; void main() { - runApp(MaterialApp( - title: 'Syncfusion PDF Viewer Demo for macOS', - home: HomePage(), - )); + runApp( + MaterialApp( + title: 'Syncfusion PDF Viewer Demo for macOS', + theme: ThemeData(useMaterial3: false), + home: HomePage(), + ), + ); } /// Represents Homepage for Navigation @@ -29,10 +32,7 @@ class _HomePage extends State { title: const Text('Syncfusion Flutter PDF Viewer for macOS'), actions: [ IconButton( - icon: const Icon( - Icons.bookmark, - color: Colors.white, - ), + icon: const Icon(Icons.bookmark, color: Colors.white), onPressed: () { _pdfViewerKey.currentState?.openBookmarkView(); }, @@ -40,8 +40,9 @@ class _HomePage extends State { ], ), body: SfPdfViewer.network( - 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', - key: _pdfViewerKey), + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + key: _pdfViewerKey, + ), ); } } diff --git a/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/GeneratedPluginRegistrant.swift index 141b3986b..cff392855 100644 --- a/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,12 @@ import FlutterMacOS import Foundation -import syncfusion_flutter_pdfviewer_macos +import device_info_plus +import syncfusion_pdfviewer_macos +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) SyncfusionFlutterPdfViewerPlugin.register(with: registry.registrar(forPlugin: "SyncfusionFlutterPdfViewerPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new file mode 100644 index 000000000..670b86854 --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -0,0 +1,11 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=C:\flutter +FLUTTER_APPLICATION_PATH=C:\Git\RP\flutter-pdfviewer\flutter_pdfviewer\syncfusion_pdfviewer_macos\example +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/ephemeral/flutter_export_environment.sh b/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/ephemeral/flutter_export_environment.sh new file mode 100644 index 000000000..061b4738f --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/example/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=C:\flutter" +export "FLUTTER_APPLICATION_PATH=C:\Git\RP\flutter-pdfviewer\flutter_pdfviewer\syncfusion_pdfviewer_macos\example" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/packages/syncfusion_pdfviewer_macos/example/macos/Podfile b/packages/syncfusion_pdfviewer_macos/example/macos/Podfile new file mode 100644 index 000000000..dade8dfad --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/example/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.11' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml b/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml index c12d08afd..c23fe0086 100644 --- a/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml @@ -1,39 +1,18 @@ name: syncfusion_pdfviewer_macos_example -description: Demonstrates how to use the syncfusion_flutter_pdfviewer plugin in macOS platform. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +description: Demonstrates how to use the syncfusion_pdfviewer plugin in macOS platform. +publish_to: 'none' environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter - syncfusion_flutter_pdfviewer: - git: - url: https://SyncfusionBuild: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@github.com/essential-studio/flutter-pdfviewer - path: flutter_pdfviewer/syncfusion_flutter_pdfviewer - branch: release/20.1.0.1 - ref: release/20.1.0.1 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 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 \ No newline at end of file + uses-material-design: true diff --git a/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift index c009829a5..332a37b15 100644 --- a/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift @@ -22,9 +22,13 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { initializePdfRenderer(call:call,result:result) } - else if(call.method == "getImage") + else if(call.method == "getPage") { - getImage(call:call,result:result) + getPage(call:call,result:result) + } + else if(call.method == "getTileImage") + { + getTileImage(call: call, result: result) } else if(call.method == "getPagesWidth") { @@ -44,22 +48,22 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let args = argument as? [String: Any] - let documentBytes = args!["documentBytes"] as? FlutterStandardTypedData - let currentDocumentID = args!["documentID"] as! String - let byte = [UInt8](documentBytes!.data) - let cfData = CFDataCreate(nil, byte, byte.count)! - let dataProvider = CGDataProvider(data: cfData)! - let document = CGPDFDocument(dataProvider) - documentRepo.updateValue(document!, forKey: currentDocumentID) - let pageCount = NSNumber(value: document!.numberOfPages) + guard let args = argument as? [String: Any] else {return} + guard let documentBytes = args["documentBytes"] as? FlutterStandardTypedData else {return} + guard let currentDocumentID = args["documentID"] as? String else {return} + let byte = [UInt8](documentBytes.data) + guard let cfData = CFDataCreate(nil, byte, byte.count) else {return} + guard let dataProvider = CGDataProvider(data: cfData) else {return} + guard let document = CGPDFDocument(dataProvider) else {return} + documentRepo.updateValue(document, forKey: currentDocumentID) + let pageCount = NSNumber(value: document.numberOfPages) result(pageCount.stringValue); } private func closeDocument( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let documentID = argument as! String + guard let documentID = argument as? String else {return} self.documentRepo[documentID] = nil self.documentRepo.removeValue(forKey: documentID) } @@ -68,16 +72,16 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { private func getPagesWidth( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else {return} + let pageCount = NSNumber(value: document!.numberOfPages) var pagesWidth = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.mediaBox) - if(page!.rotationAngle > 0) + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) { - let angle = CGFloat(page!.rotationAngle) * CGFloat.pi/180 + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) } pagesWidth.append(Double(pageRect.width)) @@ -89,16 +93,16 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { private func getPagesHeight( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else{return} + let pageCount = NSNumber(value: document!.numberOfPages) var pagesHeight = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.mediaBox) - if(page!.rotationAngle > 0) + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) { - let angle = CGFloat(page!.rotationAngle) * CGFloat.pi/180 + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) } pagesHeight.append(Double(pageRect.height)) @@ -106,60 +110,117 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { result(pagesHeight) } - // Gets the pdf page image from the specified page - private func getImage( call: FlutterMethodCall, result: @escaping FlutterResult) + // Gets the image bytes of the specified page from the document at the specified width and height + private func getPage( call: FlutterMethodCall, result: @escaping FlutterResult) { guard let argument = call.arguments else {return} - let args = argument as? [String: Any] - let index = args!["index"] as? Int - var scale = CGFloat(args!["scale"] as! Double) - if(scale < 2) - { - scale = 2 + guard let args = argument as? [String: Any] else {return} + guard let index = args["index"] as? Int else {return} + guard let width = args["width"] as? Int else {return} + guard let height = args["height"] as? Int else {return} + guard let documentID = args["documentID"] as? String else {return} + guard let image = getImageForPlugin(index: index, width: width, height: height,documentID: documentID) else { + return } - let documentID = args!["documentID"] as! String - result(getImageForPlugin(index: index!,scale: scale,documentID: documentID)) + result(image) } // Gets the image for plugin - private func getImageForPlugin(index: Int,scale: CGFloat,documentID: String) -> FlutterStandardTypedData + private func getImageForPlugin(index: Int,width:Int, height:Int,documentID: String) -> FlutterStandardTypedData? { - let document = self.documentRepo[documentID]!! - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.mediaBox) - let imageRect = CGRect(x: 0,y: 0,width: pageRect.size.width*CGFloat(scale),height: pageRect.size.height*CGFloat(scale)) - let nsImage = NSImage(size: imageRect.size, actions: { cgContext in - cgContext.beginPage(mediaBox: &pageRect) - let transform = page!.getDrawingTransform(.mediaBox, rect: pageRect, rotate: 0, preserveAspectRatio: true) - cgContext.translateBy(x: 0.0, y: imageRect.size.height) - cgContext.scaleBy(x: CGFloat(scale), y: -CGFloat(scale)) - cgContext.concatenate(transform) - cgContext.drawPDFPage(page!) - cgContext.endPage() - }) - let bytes = nsImage.tiffRepresentation?.bitmap?.png - return bytes == nil ? FlutterStandardTypedData() : FlutterStandardTypedData(bytes: bytes!) - } -} - -extension NSBitmapImageRep { - var png: Data? { - return representation(using: .png, properties: [:]) + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(index)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let scaleX = Double(width) / Double(pageWidth) + let scaleY = Double(height) / Double(pageHeight) + let stride = width * 4 + let bufSize = stride * height; + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: width, height: height, bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.scaleBy(x: scaleX, y: scaleY) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } } -} - -extension Data { - var bitmap: NSBitmapImageRep? { - return NSBitmapImageRep(data: self) + + // Gets the pdf page image from the specified page + private func getTileImage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let pageNumber = args["pageNumber"] as? Int else {return} + guard let scale = args["scale"] as? Double else {return} + guard let width = args["width"] as? Double else {return} + guard let height = args["height"] as? Double else {return} + guard let x = args["x"] as? Double else {return} + guard let y = args["y"] as? Double else {return} + guard let documentID = args["documentID"] as? String else {return} + guard let tileImage = getTileImageForPlugin(pageNumber: pageNumber, scale: CGFloat(scale), + width: width, height: height, x: x, y: y, documentID: documentID) + else { + return + } + result(tileImage) } -} - -extension NSImage { - convenience init(size: CGSize, actions: (CGContext) -> Void) { - self.init(size: size) - lockFocusFlipped(true) - actions(NSGraphicsContext.current!.cgContext) - unlockFocus() + + // Gets the image for plugin + private func getTileImageForPlugin(pageNumber: Int, scale: CGFloat, width: Double, height: Double, x: Double, y: Double, documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(pageNumber)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let bounds = CGRect(x: -(pageWidth * scale / 2) + (pageWidth / 2) - CGFloat(x), + y: -(pageHeight * scale / 2) + (pageHeight / 2) + CGFloat(y), + width: pageWidth * scale, height: pageHeight * scale) + + let stride = Int(width) * 4 + let bufSize = stride * Int(height); + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.translateBy(x: CGFloat(-x * scale), y: CGFloat(((y * scale) + height) - bounds.height)) + context!.scaleBy(x: scale, y: scale) + context!.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + context!.fill(bounds) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } } - var png: Data? { tiffRepresentation?.bitmap?.png } } diff --git a/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos.podspec b/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos.podspec index b1fc0bb21..c23fba380 100644 --- a/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos.podspec +++ b/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos.podspec @@ -5,17 +5,16 @@ Pod::Spec.new do |s| s.name = 'syncfusion_pdfviewer_macos' s.version = '0.0.1' - s.summary = 'A new flutter plugin project.' + s.summary = 'A new Flutter plugin project.' s.description = <<-DESC -A new flutter plugin project. +A new Flutter plugin project. DESC s.homepage = 'https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer_macos' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'syncfusion_pdfviewer_macos/Sources/syncfusion_pdfviewer_macos/**/*' s.dependency 'FlutterMacOS' - s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' diff --git a/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos/Package.swift b/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos/Package.swift new file mode 100644 index 000000000..8b25a844b --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos/Package.swift @@ -0,0 +1,22 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "syncfusion_pdfviewer_macos", + platforms: [ + .macOS("10.14") + ], + products: [ + .library(name: "syncfusion-pdfviewer-macos", targets: ["syncfusion_pdfviewer_macos"]) + ], + dependencies: [], + targets: [ + .target( + name: "syncfusion_pdfviewer_macos", + dependencies: [], + resources: [] + ) + ] +) diff --git a/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos/Sources/syncfusion_pdfviewer_macos/SyncfusionFlutterPdfviewerPlugin.swift b/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos/Sources/syncfusion_pdfviewer_macos/SyncfusionFlutterPdfviewerPlugin.swift new file mode 100644 index 000000000..eb029dc78 --- /dev/null +++ b/packages/syncfusion_pdfviewer_macos/macos/syncfusion_pdfviewer_macos/Sources/syncfusion_pdfviewer_macos/SyncfusionFlutterPdfviewerPlugin.swift @@ -0,0 +1,234 @@ +import Cocoa +import FlutterMacOS +import CoreGraphics +import Foundation +import AppKit + +public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { + + // Document repository + var documentRepo = [String: CGPDFDocument?]() + + // Registers the SyncfusionFlutterPdfViewerPlugin + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "syncfusion_flutter_pdfviewer", binaryMessenger: registrar.messenger) + let instance = SyncfusionFlutterPdfViewerPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + // 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 == "getPage") + { + getPage(call:call,result:result) + } + else if(call.method == "getTileImage") + { + getTileImage(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(call:call,result:result) + } + } + + // Initializes the PDF Renderer and returns the page count. + private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let documentBytes = args["documentBytes"] as? FlutterStandardTypedData else {return} + guard let currentDocumentID = args["documentID"] as? String else {return} + let password = args["password"] as? String + let byte = [UInt8](documentBytes.data) + guard let cfData = CFDataCreate(nil, byte, byte.count) else {return} + guard let dataProvider = CGDataProvider(data: cfData) else {return} + guard let document = CGPDFDocument(dataProvider) else {return} + if let password = password, document.isEncrypted { + let unlocked = document.unlockWithPassword(password) + if !unlocked { + result(FlutterError(code: "PDF_UNLOCK_FAILED", message: "Failed to unlock the PDF with the provided password", details: nil)) + return + } + } + documentRepo.updateValue(document, forKey: currentDocumentID) + let pageCount = NSNumber(value: document.numberOfPages) + result(pageCount.stringValue); + } + + private func closeDocument( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let documentID = argument as? String else {return} + self.documentRepo[documentID] = nil + self.documentRepo.removeValue(forKey: documentID) + } + + // Returns the width collection of rendered pages. + private func getPagesWidth( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else {return} + let pageCount = NSNumber(value: document!.numberOfPages) + var pagesWidth = Array() + for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) + { + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 + pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) + } + pagesWidth.append(Double(pageRect.width)) + } + result(pagesWidth) + } + + // Returns the height collection of rendered pages. + private func getPagesHeight( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let documentID = argument as? String else {return} + guard let document = self.documentRepo[documentID] else{return} + let pageCount = NSNumber(value: document!.numberOfPages) + var pagesHeight = Array() + for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ + guard let page = document!.page(at: Int(index)) else {continue} + var pageRect = page.getBoxRect(.cropBox) + if(page.rotationAngle > 0) + { + let angle = CGFloat(page.rotationAngle) * CGFloat.pi/180 + pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) + } + pagesHeight.append(Double(pageRect.height)) + } + result(pagesHeight) + } + + // Gets the image bytes of the specified page from the document at the specified width and height + private func getPage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let index = args["index"] as? Int else {return} + guard let width = args["width"] as? Int else {return} + guard let height = args["height"] as? Int else {return} + guard let documentID = args["documentID"] as? String else {return} + guard let image = getImageForPlugin(index: index, width: width, height: height,documentID: documentID) else { + return + } + result(image) + } + + // Gets the image for plugin + private func getImageForPlugin(index: Int,width:Int, height:Int,documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(index)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let scaleX = Double(width) / Double(pageWidth) + let scaleY = Double(height) / Double(pageHeight) + let stride = width * 4 + let bufSize = stride * height; + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: width, height: height, bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.scaleBy(x: scaleX, y: scaleY) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } + } + + // Gets the pdf page image from the specified page + private func getTileImage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + guard let args = argument as? [String: Any] else {return} + guard let pageNumber = args["pageNumber"] as? Int else {return} + guard let scale = args["scale"] as? Double else {return} + guard let width = args["width"] as? Double else {return} + guard let height = args["height"] as? Double else {return} + guard let x = args["x"] as? Double else {return} + guard let y = args["y"] as? Double else {return} + guard let documentID = args["documentID"] as? String else {return} + guard let tileImage = getTileImageForPlugin(pageNumber: pageNumber, scale: CGFloat(scale), + width: width, height: height, x: x, y: y, documentID: documentID) + else { + return + } + result(tileImage) + } + + // Gets the image for plugin + private func getTileImageForPlugin(pageNumber: Int, scale: CGFloat, width: Double, height: Double, x: Double, y: Double, documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + guard let page = document!.page(at: Int(pageNumber)) else {return nil} + let pageRect = page.getBoxRect(.cropBox) + var pageWidth = pageRect.width + var pageHeight = pageRect.height + if(page.rotationAngle == 90 || page.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let bounds = CGRect(x: -(pageWidth * scale / 2) + (pageWidth / 2) - CGFloat(x), + y: -(pageHeight * scale / 2) + (pageHeight / 2) + CGFloat(y), + width: pageWidth * scale, height: pageHeight * scale) + + let stride = Int(width) * 4 + let bufSize = stride * Int(height); + let buffer = UnsafeMutablePointer.allocate(capacity: bufSize) + buffer.initialize(repeating: 0, count: bufSize) + var rendered = false + let transform = page.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: CGSize(width: pageWidth, height: pageHeight)), rotate: 0, preserveAspectRatio: true) + let rgb = CGColorSpaceCreateDeviceRGB() + let context = CGContext(data: buffer, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: stride, space: rgb, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) + if context != nil { + context!.setAllowsAntialiasing(true); + context!.translateBy(x: CGFloat(-x * scale), y: CGFloat(((y * scale) + height) - bounds.height)) + context!.scaleBy(x: scale, y: scale) + context!.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + context!.fill(bounds) + context!.concatenate(transform) + context!.drawPDFPage(page) + rendered = true + } + if(rendered){ + let data = Data(bytesNoCopy: buffer, count: bufSize, deallocator: .free) + return FlutterStandardTypedData(bytes: data) + }else{ + return nil + } + } +} diff --git a/packages/syncfusion_pdfviewer_macos/pubspec.yaml b/packages/syncfusion_pdfviewer_macos/pubspec.yaml index db6623684..4a2ea05b8 100644 --- a/packages/syncfusion_pdfviewer_macos/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_macos/pubspec.yaml @@ -1,20 +1,24 @@ name: syncfusion_pdfviewer_macos description: macOS platform implementation of the Flutter PDF Viewer library that lets you view the PDF documents seamlessly and efficiently. -version: 20.1.47 -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer_macos +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_pdfviewer_macos environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=1.20.0" + sdk: ^3.7.0-0 + flutter: '>=1.20.0' dependencies: flutter: sdk: flutter syncfusion_pdfviewer_platform_interface: - path: ../syncfusion_pdfviewer_platform_interface + path: ../syncfusion_flutter_pdfviewer_platform_interface + + + + flutter: plugin: platforms: diff --git a/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml b/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml deleted file mode 100644 index a8c45a74c..000000000 --- a/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml +++ /dev/null @@ -1,6 +0,0 @@ -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_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart b/packages/syncfusion_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart deleted file mode 100644 index de098ab3e..000000000 --- a/packages/syncfusion_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; -import 'package:flutter/services.dart'; -import 'package:syncfusion_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, String documentID) async { - return _channel.invokeMethod('initializePdfRenderer', { - 'documentBytes': documentBytes, - 'documentID': documentID - }); - } - - /// Gets the height of all pages in the document. - @override - Future getPagesHeight(String documentID) async { - return _channel.invokeMethod('getPagesHeight', documentID); - } - - /// Gets the width of all pages in the document. - @override - Future getPagesWidth(String documentID) async { - return _channel.invokeMethod('getPagesWidth', documentID); - } - - /// Gets the image's bytes information of the specified page. - @override - Future getImage( - int pageNumber, double currentScale, String documentID) async { - return _channel.invokeMethod('getImage', { - 'index': pageNumber, - 'scale': currentScale, - 'documentID': documentID - }); - } - - /// Closes the PDF document. - @override - Future closeDocument(String documentID) async { - return _channel.invokeMethod('closeDocument', documentID); - } -} diff --git a/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart b/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart deleted file mode 100644 index f85230ae3..000000000 --- a/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart +++ /dev/null @@ -1,63 +0,0 @@ -library syncfusion_pdfviewer_platform_interface; - -import 'dart:async'; -import 'dart:typed_data'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:syncfusion_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, String documentID) async { - throw UnimplementedError( - 'initializePdfRenderer() has not been implemented.'); - } - - /// Gets the height of all pages in the document. - Future getPagesHeight(String documentID) async { - throw UnimplementedError('getPagesHeight() has not been implemented.'); - } - - /// Gets the width of all pages in the document. - Future getPagesWidth(String documentID) async { - throw UnimplementedError('getPagesWidth() has not been implemented.'); - } - - /// Gets the image's bytes information of the specified page. - Future getImage( - int pageNumber, double scale, String documentID) async { - throw UnimplementedError('getImage() has not been implemented.'); - } - - /// Closes the PDF document. - Future closeDocument(String documentID) async { - throw UnimplementedError('closeDocument() has not been implemented.'); - } -} diff --git a/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml b/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml deleted file mode 100644 index a1b74050a..000000000 --- a/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml +++ /dev/null @@ -1,14 +0,0 @@ - -name: syncfusion_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: 20.1.47 -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer_platform_interface - -environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=1.20.0" - -dependencies: - flutter: - sdk: flutter - plugin_platform_interface: ^2.0.0 \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_web/README.md b/packages/syncfusion_pdfviewer_web/README.md index 92b60de78..eaf26aaa3 100644 --- a/packages/syncfusion_pdfviewer_web/README.md +++ b/packages/syncfusion_pdfviewer_web/README.md @@ -1,6 +1,6 @@ # Flutter PDF Viewer Web library -The web implementation of [Syncfusion Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. +The web implementation of [Syncfusion® Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. ## Usage @@ -12,7 +12,7 @@ This package is an endorsed implementation of `syncfusion_flutter_pdfviewer` for ... dependencies: ... - syncfusion_flutter_pdfviewer: ^19.1.0-beta + syncfusion_flutter_pdfviewer: ^20.3.0 ... ... ``` diff --git a/packages/syncfusion_pdfviewer_web/example/README.md b/packages/syncfusion_pdfviewer_web/example/README.md new file mode 100644 index 000000000..df24e2edf --- /dev/null +++ b/packages/syncfusion_pdfviewer_web/example/README.md @@ -0,0 +1,16 @@ +# syncfusion_pdfviewer_web_example + +Demonstrates how to use the syncfusion_pdfviewer_web with the help of syncfusion_flutter_pdfviewer plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_pdfviewer_web/example/analysis_options.yaml b/packages/syncfusion_pdfviewer_web/example/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_pdfviewer_web/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_web/example/lib/main.dart b/packages/syncfusion_pdfviewer_web/example/lib/main.dart index 9cb19cc3b..b988d3db9 100644 --- a/packages/syncfusion_pdfviewer_web/example/lib/main.dart +++ b/packages/syncfusion_pdfviewer_web/example/lib/main.dart @@ -2,10 +2,13 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; void main() { - runApp(MaterialApp( - title: 'Syncfusion PDF Viewer Demo for Web', - home: HomePage(), - )); + runApp( + MaterialApp( + title: 'Syncfusion PDF Viewer Demo for Web', + theme: ThemeData(useMaterial3: false), + home: HomePage(), + ), + ); } /// Represents Homepage for Navigation @@ -29,10 +32,7 @@ class _HomePage extends State { title: Text('Syncfusion Flutter PDF Viewer'), actions: [ IconButton( - icon: Icon( - Icons.bookmark, - color: Colors.white, - ), + icon: Icon(Icons.bookmark, color: Colors.white), onPressed: () { _pdfViewerKey.currentState?.openBookmarkView(); }, diff --git a/packages/syncfusion_pdfviewer_web/example/pubspec.yaml b/packages/syncfusion_pdfviewer_web/example/pubspec.yaml index 4db93d44a..9b9259e12 100644 --- a/packages/syncfusion_pdfviewer_web/example/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_web/example/pubspec.yaml @@ -1,38 +1,18 @@ name: syncfusion_pdfviewer_web_example -description: Demonstrates how to use the syncfusion_flutter_pdfviewer_web with the help of syncfusion_flutter_pdfviewer plugin. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +description: Demonstrates how to use the syncfusion_pdfviewer_web with the help of syncfusion_flutter_pdfviewer plugin. +publish_to: 'none' environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.7.0-0 dependencies: flutter: sdk: flutter syncfusion_flutter_pdfviewer: - git: - url: https://SyncfusionBuild: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@github.com/essential-studio/flutter-pdfviewer - path: flutter_pdfviewer/syncfusion_flutter_pdfviewer - branch: release/20.1.0.1 - ref: release/20.1.0.1 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 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 \ No newline at end of file + uses-material-design: true diff --git a/packages/syncfusion_pdfviewer_web/example/web/icons/Icon-maskable-192.png b/packages/syncfusion_pdfviewer_web/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/syncfusion_pdfviewer_web/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/syncfusion_pdfviewer_web/example/web/icons/Icon-maskable-512.png b/packages/syncfusion_pdfviewer_web/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/syncfusion_pdfviewer_web/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/syncfusion_pdfviewer_web/example/web/index.html b/packages/syncfusion_pdfviewer_web/example/web/index.html index 2b6660b29..69c627f8b 100644 --- a/packages/syncfusion_pdfviewer_web/example/web/index.html +++ b/packages/syncfusion_pdfviewer_web/example/web/index.html @@ -1,39 +1,52 @@ + + + - + - + example - - - + - - - + + + + diff --git a/packages/syncfusion_pdfviewer_web/example/web/manifest.json b/packages/syncfusion_pdfviewer_web/example/web/manifest.json index a8554b94d..c36c73ff7 100644 --- a/packages/syncfusion_pdfviewer_web/example/web/manifest.json +++ b/packages/syncfusion_pdfviewer_web/example/web/manifest.json @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart b/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart index b01e52334..a5672fcd6 100644 --- a/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart +++ b/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart @@ -1,7 +1,6 @@ import 'dart:async'; -import 'dart:html' as html; -import 'dart:js_util'; -import 'dart:js' as js; +import 'package:web/web.dart' as web; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; @@ -9,7 +8,7 @@ import 'package:syncfusion_pdfviewer_platform_interface/pdfviewer_platform_inter import 'package:syncfusion_pdfviewer_web/src/pdfjs.dart'; class SyncfusionFlutterPdfViewerPlugin extends PdfViewerPlatform { - final Settings _settings = Settings()..scale = 1.0; + final Settings _settings = Settings(scale: 1.0); Map _documentRepo = Map(); /// Registers this class as the default instance of [PdfViewerPlatform]. @@ -20,11 +19,16 @@ class SyncfusionFlutterPdfViewerPlugin extends PdfViewerPlatform { /// Initializes the PDF renderer instance in respective platform by loading the PDF from the specified path. @override Future initializePdfRenderer( - Uint8List documentBytes, String documentID) async { - Settings documentData = Settings()..data = documentBytes; + Uint8List documentBytes, + String documentID, [ + String? password, + ]) async { + Settings documentData = + Settings() + ..data = Uint8List.fromList(documentBytes).toJS + ..password = password; final documentLoader = PdfJs.getDocument(documentData); - final PdfJsDoc pdfJsDoc = - await promiseToFuture(documentLoader.promise); + final PdfJsDoc pdfJsDoc = await documentLoader.promise.toDart; final int pagesCount = pdfJsDoc.numPages; _documentRepo[documentID] = pdfJsDoc; return pagesCount.toString(); @@ -36,8 +40,8 @@ class SyncfusionFlutterPdfViewerPlugin extends PdfViewerPlatform { int pageCount = _documentRepo[documentID]?.numPages ?? 0; Int32List pagesHeight = new Int32List(pageCount); for (int pageNumber = 1; pageNumber <= pageCount; pageNumber++) { - PdfJsPage page = await promiseToFuture( - _documentRepo[documentID]!.getPage(pageNumber)); + PdfJsPage page = + await (_documentRepo[documentID]!.getPage(pageNumber)).toDart; PdfJsViewport viewport = page.getViewport(_settings); pagesHeight[pageNumber - 1] = viewport.height.toInt(); } @@ -50,67 +54,120 @@ class SyncfusionFlutterPdfViewerPlugin extends PdfViewerPlatform { int pageCount = _documentRepo[documentID]?.numPages ?? 0; Int32List pagesWidth = new Int32List(pageCount); for (int pageNumber = 1; pageNumber <= pageCount; pageNumber++) { - PdfJsPage page = await promiseToFuture( - _documentRepo[documentID]!.getPage(pageNumber)); + PdfJsPage page = + await (_documentRepo[documentID]!.getPage(pageNumber)).toDart; PdfJsViewport viewport = page.getViewport(_settings); pagesWidth[pageNumber - 1] = viewport.width.toInt(); } return pagesWidth; } - /// Gets the image's bytes information of the specified page. + /// Gets the image bytes of the specified page from the document at the specified width and . @override - Future getImage( - int pageNumber, double scale, String documentID) async { + Future getPage( + int pageNumber, + int fullWidth, + int fullHeight, + String documentID, + ) async { if (_documentRepo[documentID] != null) { - PdfJsPage page = await promiseToFuture( - _documentRepo[documentID]!.getPage(pageNumber)); + PdfJsPage page = + await (_documentRepo[documentID]!.getPage(pageNumber)).toDart; PdfJsViewport viewport = page.getViewport(_settings); - return renderPage(page, viewport, scale, documentID); + return renderPage(page, viewport, fullWidth, fullHeight, documentID); } return Uint8List.fromList([0]); } + /// Gets the image's bytes information of the specified portion of the page. + Future getTileImage( + int pageNumber, + double scale, + double x, + double y, + double width, + double height, + String documentID, + ) async { + if (_documentRepo[documentID] != null) { + PdfJsPage page = + await (_documentRepo[documentID]!.getPage(pageNumber)).toDart; + PdfJsViewport viewport = page.getViewport(_settings); + return _renderPageTile(page, viewport, scale, x, y, width, height); + } + return Uint8List.fromList([0]); + } + + Future _renderPageTile( + PdfJsPage page, + PdfJsViewport viewport, + double scale, + double x, + double y, + double width, + double height, + ) async { + final web.HTMLCanvasElement canvas = web.HTMLCanvasElement(); + viewport = page.getViewport( + Settings() + ..offsetX = -(x * scale) + ..offsetY = -(y * scale) + ..scale = scale, + ); + + canvas + ..height = height.toInt() + ..width = width.toInt(); + final renderSettings = + Settings() + ..canvasContext = canvas.context2D + ..viewport = viewport + ..annotationMode = 0; + await (page.render(renderSettings)).promise.toDart; + return canvas.context2D + .getImageData(0, 0, width.toInt(), height.toInt()) + .data + .toDart + .buffer + .asUint8List(); + } + /// Closes the PDF document. @override Future closeDocument(String documentID) async { + _documentRepo[documentID]?.destroy(); _documentRepo[documentID] = null; _documentRepo.remove(documentID); } /// Renders the page into a canvas and return image's byte information. - Future renderPage(PdfJsPage page, PdfJsViewport viewport, - double scale, String documentID) async { - final html.CanvasElement htmlCanvas = - js.context['document'].createElement('canvas'); - final Object? context = htmlCanvas.getContext('2d'); + Future renderPage( + PdfJsPage page, + PdfJsViewport viewport, + int fullWidth, + int fullHeight, + String documentID, + ) async { + final web.HTMLCanvasElement canvas = web.HTMLCanvasElement(); final _viewport = page.getViewport(_settings); - if (scale < 2) { - scale = 2; - } - viewport = page.getViewport(Settings() - ..scale = ((viewport.width).toInt() * scale) / _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(); - }, + viewport = page.getViewport( + Settings()..scale = (fullWidth / _viewport.width), ); - await completer.future; - return bytesBuilder.toBytes(); + + canvas + ..width = fullWidth + ..height = fullHeight; + final renderSettings = + Settings() + ..canvasContext = canvas.context2D + ..viewport = viewport + ..annotationMode = 0; + await (page.render(renderSettings)).promise.toDart; + return canvas.context2D + .getImageData(0, 0, fullWidth.toInt(), fullHeight.toInt()) + .data + .toDart + .buffer + .asUint8List(); } } diff --git a/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart b/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart index 1146c451a..b78b5a4ea 100644 --- a/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart +++ b/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart @@ -1,58 +1,59 @@ @JS() library pdf.js; -import 'dart:html'; -import 'dart:typed_data'; - -import 'package:js/js.dart'; +import 'package:web/web.dart' as web; +import 'dart:js_interop'; /// 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); +extension type PdfJs(JSObject _) implements JSObject { + external static PdfJsDocLoader getDocument(Settings settings); } -@anonymous -@JS() -class Settings { - external set data(Uint8List value); +extension type Settings._(JSObject _) implements JSObject { + external Settings({ + JSUint8Array data, + double scale, + web.CanvasRenderingContext2D canvasContext, + PdfJsViewport viewport, + num annotationMode, + double offsetX, + double offsetY, + String? password, + }); + external set data(JSUint8Array value); external set scale(double value); - external set canvasContext(CanvasRenderingContext2D value); + external set canvasContext(web.CanvasRenderingContext2D value); external set viewport(PdfJsViewport value); + external set annotationMode(num value); + external set offsetX(double value); + external set offsetY(double value); + external set password(String? value); } -@anonymous -@JS() -class PdfJsDocLoader { - external Future get promise; +extension type PdfJsDocLoader(JSObject _) implements JSObject { + external JSPromise get promise; } -@anonymous -@JS() -class PdfJsDoc { - external Future getPage(int num); +extension type PdfJsDoc(JSObject _) implements JSObject { + external JSPromise getPage(int num); external int get numPages; + external void destroy(); } -@anonymous -@JS() -class PdfJsPage { - external PdfJsViewport getViewport(Settings data); - external PdfJsRender render(Settings data); +extension type PdfJsPage(JSObject _) implements JSObject { + external PdfJsViewport getViewport(Settings settings); + external PdfJsRender render(Settings settings); external int get pageNumber; - external List get view; + external JSArray get view; } -@anonymous -@JS() -class PdfJsViewport { +extension type PdfJsViewport(JSObject _) implements JSObject { external num get width; external num get height; } -@anonymous -@JS() -class PdfJsRender { - external Future get promise; +extension type PdfJsRender(JSObject _) implements JSObject { + external JSPromise get promise; } diff --git a/packages/syncfusion_pdfviewer_web/pubspec.yaml b/packages/syncfusion_pdfviewer_web/pubspec.yaml index 5aedacb09..2103f8fa7 100644 --- a/packages/syncfusion_pdfviewer_web/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_web/pubspec.yaml @@ -1,23 +1,24 @@ name: syncfusion_pdfviewer_web description: Web platform implementation of the Flutter PDF Viewer library that lets you view the PDF documents seamlessly and efficiently. -version: 20.1.47 -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer_web +version: 30.1.37 +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_pdfviewer_web environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=1.20.0" + sdk: ^3.7.0-0 + flutter: '>=1.20.0' dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter - js: ^0.6.3 - meta: ^1.3.0 + web: ^1.0.0 syncfusion_pdfviewer_platform_interface: - path: ../syncfusion_pdfviewer_platform_interface + path: ../syncfusion_flutter_pdfviewer_platform_interface + + flutter: plugin: platforms: diff --git a/packages/syncfusion_pdfviewer_windows/README.md b/packages/syncfusion_pdfviewer_windows/README.md index 2f66cdfa6..7480a737c 100644 --- a/packages/syncfusion_pdfviewer_windows/README.md +++ b/packages/syncfusion_pdfviewer_windows/README.md @@ -1,6 +1,6 @@ # Flutter PDF Viewer Windows -The Windows implementation of [Syncfusion Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. +The Windows implementation of [Syncfusion® Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. ## Usage @@ -12,7 +12,7 @@ This package is an endorsed implementation of `syncfusion_flutter_pdfviewer` for ... dependencies: ... - syncfusion_flutter_pdfviewer: ^20.1.0-beta + syncfusion_flutter_pdfviewer: ^20.3.0 ... ... ``` \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_windows/example/README.md b/packages/syncfusion_pdfviewer_windows/example/README.md new file mode 100644 index 000000000..e353d212e --- /dev/null +++ b/packages/syncfusion_pdfviewer_windows/example/README.md @@ -0,0 +1,16 @@ +# syncfusion_pdfviewer_windows_example + +Demonstrates how to use the syncfusion_pdfviewer_windows plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +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. diff --git a/packages/syncfusion_pdfviewer_windows/example/analysis_options.yaml b/packages/syncfusion_pdfviewer_windows/example/analysis_options.yaml index 61b6c4de1..b08414c80 100644 --- a/packages/syncfusion_pdfviewer_windows/example/analysis_options.yaml +++ b/packages/syncfusion_pdfviewer_windows/example/analysis_options.yaml @@ -1,29 +1,3 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_windows/example/lib/main.dart b/packages/syncfusion_pdfviewer_windows/example/lib/main.dart index cfd9979c5..03b01b83a 100644 --- a/packages/syncfusion_pdfviewer_windows/example/lib/main.dart +++ b/packages/syncfusion_pdfviewer_windows/example/lib/main.dart @@ -2,10 +2,13 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; void main() { - runApp(const MaterialApp( - title: 'Syncfusion PDF Viewer Demo for Windows', - home: HomePage(), - )); + runApp( + MaterialApp( + title: 'Syncfusion PDF Viewer Demo for Windows', + theme: ThemeData(useMaterial3: false), + home: const HomePage(), + ), + ); } /// Represents Homepage for Navigation @@ -31,10 +34,7 @@ class _HomePage extends State { title: const Text('Syncfusion Flutter PDF Viewer'), actions: [ IconButton( - icon: const Icon( - Icons.bookmark, - color: Colors.white, - ), + icon: const Icon(Icons.bookmark, color: Colors.white), onPressed: () { _pdfViewerKey.currentState?.openBookmarkView(); }, diff --git a/packages/syncfusion_pdfviewer_windows/example/pubspec.yaml b/packages/syncfusion_pdfviewer_windows/example/pubspec.yaml index 8ee5b37bc..e780a9673 100644 --- a/packages/syncfusion_pdfviewer_windows/example/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_windows/example/pubspec.yaml @@ -1,84 +1,18 @@ name: syncfusion_pdfviewer_windows_example description: Demonstrates how to use the syncfusion_pdfviewer_windows plugin. - -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' environment: - sdk: ">=2.16.0 <3.0.0" + sdk: ^3.7.0-0 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter - syncfusion_flutter_pdfviewer: - # When depending on this package from a real application you should use: - # syncfusion_pdfviewer_windows: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../../syncfusion_flutter_pdfviewer - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^1.0.0 - -# 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 + uses-material-design: true \ No newline at end of file diff --git a/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..9871f6b72 --- /dev/null +++ b/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + SyncfusionPdfviewerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SyncfusionPdfviewerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugins.cmake index 90342fb89..248772383 100644 --- a/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugins.cmake +++ b/packages/syncfusion_pdfviewer_windows/example/windows/flutter/generated_plugins.cmake @@ -4,6 +4,10 @@ list(APPEND FLUTTER_PLUGIN_LIST syncfusion_pdfviewer_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) @@ -14,3 +18,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/syncfusion_pdfviewer_windows/pubspec.yaml b/packages/syncfusion_pdfviewer_windows/pubspec.yaml index 17ad19551..0b26ca3c0 100644 --- a/packages/syncfusion_pdfviewer_windows/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_windows/pubspec.yaml @@ -1,19 +1,18 @@ name: syncfusion_pdfviewer_windows description: Windows platform implementation of the Flutter PDF Viewer library that lets you view the PDF documents seamlessly and efficiently. -version: 20.1.47 +version: 30.1.37 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_pdfviewer_windows environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.5.0" + sdk: ^3.7.0-0 + flutter: '>=2.5.0' dependencies: flutter: sdk: flutter -dev_dependencies: - flutter_lints: ^1.0.0 + flutter: plugin: diff --git a/packages/syncfusion_pdfviewer_windows/windows/.gitignore b/packages/syncfusion_pdfviewer_windows/windows/.gitignore index b3eb2be16..b130203ad 100644 --- a/packages/syncfusion_pdfviewer_windows/windows/.gitignore +++ b/packages/syncfusion_pdfviewer_windows/windows/.gitignore @@ -6,10 +6,6 @@ flutter/ *.userosscache *.sln.docstates -# Visual Studio build-related files. -x64/ -x86/ - # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache diff --git a/packages/syncfusion_pdfviewer_windows/windows/include/pdfium/x64/bin/pdfium.dll b/packages/syncfusion_pdfviewer_windows/windows/include/pdfium/x64/bin/pdfium.dll new file mode 100644 index 000000000..0b7ea603f Binary files /dev/null and b/packages/syncfusion_pdfviewer_windows/windows/include/pdfium/x64/bin/pdfium.dll differ diff --git a/packages/syncfusion_pdfviewer_windows/windows/include/pdfium/x64/lib/pdfium.dll.lib b/packages/syncfusion_pdfviewer_windows/windows/include/pdfium/x64/lib/pdfium.dll.lib new file mode 100644 index 000000000..ceca27552 Binary files /dev/null and b/packages/syncfusion_pdfviewer_windows/windows/include/pdfium/x64/lib/pdfium.dll.lib differ diff --git a/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp b/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp index 02a1bb254..e616cbc54 100644 --- a/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp +++ b/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp @@ -19,34 +19,50 @@ namespace pdfviewer std::shared_ptr getPdfDocument(std::string docID) { - return documentRepo.find(docID)->second; + std::shared_ptr documentPtr = nullptr; + + std::map>::iterator documentRepoIterator = documentRepo.find(docID); + // Checks whether the docID is present or not in the documentRepo map + if (documentRepoIterator != documentRepo.end()) + { + documentPtr = documentRepoIterator->second; + } + else + { + documentPtr = nullptr; + } + + return documentPtr; } void closePdfDocument(std::string docID) { - FPDF_DOCUMENT document = getPdfDocument(docID)->pdfDocument; + std::shared_ptr documentPtr = getPdfDocument(docID); + if (documentPtr == nullptr) + return; + FPDF_DOCUMENT document = documentPtr->pdfDocument; FPDF_CloseDocument(document); documentRepo.erase(docID); } - std::shared_ptr initializePdfRenderer(std::vector data, std::string docID) + std::shared_ptr initializePdfRenderer(std::vector data, std::string password, std::string docID) { if (documentRepo.size() == 0) { FPDF_InitLibraryWithConfig(nullptr); } - std::shared_ptr doc = std::make_shared(data, docID); + std::shared_ptr doc = std::make_shared(data, password, docID); documentRepo[docID] = doc; return doc; } - PdfDocument::PdfDocument(std::vector dataRef, std::string id) : documentID{id} + PdfDocument::PdfDocument(std::vector dataRef, std::string password, std::string id) : documentID{id} { data.swap(dataRef); - pdfDocument = FPDF_LoadMemDocument64(data.data(), data.size(), nullptr); + pdfDocument = FPDF_LoadMemDocument64(data.data(), data.size(), password.c_str()); } PdfDocument::~PdfDocument() {} diff --git a/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.h b/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.h index 413e2359d..e8651008b 100644 --- a/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.h +++ b/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.h @@ -15,7 +15,7 @@ namespace pdfviewer std::vector data; public: - PdfDocument(std::vector data, std::string id); + PdfDocument(std::vector data, std::string password, std::string id); ~PdfDocument(); @@ -23,7 +23,7 @@ namespace pdfviewer std::string documentID; }; - std::shared_ptr initializePdfRenderer(std::vector data, std::string docID); + std::shared_ptr initializePdfRenderer(std::vector data, std::string password, std::string docID); std::shared_ptr getPdfDocument(std::string docID); void closePdfDocument(std::string docID); } diff --git a/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp b/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp index b1dd50c3a..0e7ae3e44 100644 --- a/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp +++ b/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp @@ -75,10 +75,20 @@ namespace pdfviewer const auto *arguments = std::get_if(method_call.arguments()); auto documentBytes = arguments->find(flutter::EncodableValue("documentBytes")); auto documentID = arguments->find(flutter::EncodableValue("documentID")); - auto id = std::get(documentID->second); + auto passwordIter = arguments->find(flutter::EncodableValue("password")); + auto bytes = std::get>(documentBytes->second); - std::shared_ptr document = initializePdfRenderer(bytes, id); - int pageCount = FPDF_GetPageCount(document->pdfDocument); + std::optional password; + if (passwordIter != arguments->end()) { + const auto& passwordValue = passwordIter->second; + if (!passwordValue.IsNull()) { + password = std::get(passwordValue); + } + } + + std::string docID = std::get(documentID->second); + std::shared_ptr pdfDoc = initializePdfRenderer(bytes, password.value_or(""), docID); + int pageCount = FPDF_GetPageCount(pdfDoc->pdfDocument); result->Success(flutter::EncodableValue(std::to_string(pageCount))); } @@ -88,12 +98,17 @@ namespace pdfviewer std::unique_ptr> result) { auto id = std::get(*method_call.arguments()); - std::shared_ptr document = getPdfDocument(id); - int pageCount = FPDF_GetPageCount(document->pdfDocument); + std::shared_ptr documentPtr = getPdfDocument(id); + if (documentPtr == nullptr) + { + result->Error("Error", "Document not found"); + return; + } + int pageCount = FPDF_GetPageCount(documentPtr->pdfDocument); std::vector pageHeights; for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { - FPDF_PAGE page = FPDF_LoadPage(document->pdfDocument, pageIndex); + FPDF_PAGE page = FPDF_LoadPage(documentPtr->pdfDocument, pageIndex); double height = FPDF_GetPageHeightF(page); pageHeights.push_back(height); FPDF_ClosePage(page); @@ -107,12 +122,17 @@ namespace pdfviewer std::unique_ptr> result) { auto id = std::get(*method_call.arguments()); - std::shared_ptr document = getPdfDocument(id); - int pageCount = FPDF_GetPageCount(document->pdfDocument); + std::shared_ptr documentPtr = getPdfDocument(id); + if (documentPtr == nullptr) + { + result->Error("Error", "Document not found"); + return; + } + int pageCount = FPDF_GetPageCount(documentPtr->pdfDocument); std::vector pageWidth; for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { - FPDF_PAGE page = FPDF_LoadPage(document->pdfDocument, pageIndex); + FPDF_PAGE page = FPDF_LoadPage(documentPtr->pdfDocument, pageIndex); double width = FPDF_GetPageWidthF(page); pageWidth.push_back(width); FPDF_ClosePage(page); @@ -157,77 +177,75 @@ namespace pdfviewer { const auto *arguments = std::get_if(method_call.arguments()); auto pageIndex = arguments->find(flutter::EncodableValue("index")); - auto currentScale = arguments->find(flutter::EncodableValue("scale")); + auto widthArgs = arguments->find(flutter::EncodableValue("width")); + auto heightArgs = arguments->find(flutter::EncodableValue("height")); auto documentID = arguments->find(flutter::EncodableValue("documentID")); + auto id = std::get(documentID->second); - auto scale = std::get(currentScale->second); + auto height = std::get(heightArgs->second); + auto width = std::get(widthArgs->second); auto index = std::get(pageIndex->second); - FPDF_DOCUMENT document = getPdfDocument(id)->pdfDocument; - FPDF_PAGE page = FPDF_LoadPage(document, index - 1); - if (scale < 1.75) + + std::shared_ptr documentPtr = getPdfDocument(id); + if (documentPtr == nullptr) { - scale = 1.75; + result->Error("Error", "Document not found"); + return; } - int pageWidth = (int)(FPDF_GetPageWidthF(page) * scale); - int pageHeight = (int)(FPDF_GetPageHeightF(page) * scale); + FPDF_DOCUMENT document = documentPtr->pdfDocument; + FPDF_PAGE page = FPDF_LoadPage(document, index - 1); + // Create empty bitmap and render page onto it - auto bitmap = FPDFBitmap_Create(pageWidth, pageHeight, 0); - FPDFBitmap_FillRect(bitmap, 0, 0, pageWidth, pageHeight, 0xFFFFFFFF); - FPDF_RenderPageBitmap(bitmap, page, 0, 0, pageWidth, pageHeight, 0, - FPDF_ANNOT | FPDF_LCD_TEXT); + auto bitmap = FPDFBitmap_Create(width, height, 0); + FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF); + FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, 0, FPDF_LCD_TEXT | FPDF_REVERSE_BYTE_ORDER); - // Convert bitmap into RGBA format uint8_t *scanArg = static_cast(FPDFBitmap_GetBuffer(bitmap)); - auto bitmapStride = FPDFBitmap_GetStride(bitmap); - - // Convert to image format - Gdiplus::GdiplusStartupInput gdiplusStartupInput; - ULONG_PTR tokenGDI; - Gdiplus::GdiplusStartup(&tokenGDI, &gdiplusStartupInput, NULL); + + // Calculate the total size of the pixel buffer + size_t bufferSize = width * height * 4; + std::vector imageData(scanArg, scanArg + bufferSize); - // Get the CLSID of the image encoder. - CLSID pngClsid; - GetPNGImageID(&pngClsid); - - // Create gdi+ bitmap from raw image data - auto gdiBitmap = - new Gdiplus::Bitmap(pageWidth, pageHeight, bitmapStride, PixelFormat32bppRGB, scanArg); - - // Create stream for converted image - IStream *stream = nullptr; - CreateStreamOnHGlobal(NULL, TRUE, &stream); - - // Encode image onto stream - auto gdiStatus = gdiBitmap->Save(stream, &pngClsid, NULL); - if (gdiStatus == Gdiplus::OutOfMemory) - { - throw std::exception("Image encode failed due to out of memory"); - } - else if (gdiStatus != Gdiplus::Ok) - { - throw std::exception("Image endode failed"); - } - - // Get raw memory of stream - HGLOBAL global = NULL; - GetHGlobalFromStream(stream, &global); + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); - // copy IStream to buffer - size_t bufferSize = GlobalSize(global); - std::vector imageData; - imageData.resize(bufferSize); + result->Success(flutter::EncodableValue(imageData)); + } - // lock & unlock memory - LPVOID lockImage = GlobalLock(global); - memcpy(&imageData[0], lockImage, bufferSize); - GlobalUnlock(global); + void GetPdfPageTileImage( + const flutter::MethodCall &method_call, + std::unique_ptr> result) + { + const auto *arguments = std::get_if(method_call.arguments()); + auto pageNumberArgument = arguments->find(flutter::EncodableValue("pageNumber")); + auto currentScale = arguments->find(flutter::EncodableValue("scale")); + auto documentID = arguments->find(flutter::EncodableValue("documentID")); + auto xArgument = arguments->find(flutter::EncodableValue("x")); + auto yArgument = arguments->find(flutter::EncodableValue("y")); + auto widthArgument = arguments->find(flutter::EncodableValue("width")); + auto heightArgument = arguments->find(flutter::EncodableValue("height")); + auto id = std::get(documentID->second); + auto scale = std::get(currentScale->second); + auto pageNumber = std::get(pageNumberArgument->second); + auto x = std::get(xArgument->second); + auto y = std::get(yArgument->second); + int width = (int)std::get(widthArgument->second); + int height = (int)std::get(heightArgument->second); + FPDF_DOCUMENT document = getPdfDocument(id)->pdfDocument; + FPDF_PAGE page = FPDF_LoadPage(document, pageNumber - 1); - // Close stream - stream->Release(); + FS_MATRIX matrix = {(float)scale, 0, 0, (float)scale, (float)(-x * scale), (float)(-y * scale)}; + FS_RECTF rect = {0,0, (float)(width * scale), (float)(height * scale)}; - // Cleanup gid+ - delete gdiBitmap; - Gdiplus::GdiplusShutdown(tokenGDI); + // Create empty bitmap and render page onto it + auto bitmap = FPDFBitmap_Create(width, height, 0); + FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF); + FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, FPDF_LCD_TEXT | FPDF_REVERSE_BYTE_ORDER); + uint8_t *scanArg = static_cast(FPDFBitmap_GetBuffer(bitmap)); + + // Calculate the total size of the pixel buffer + size_t bufferSize = width * height * 4; + std::vector imageData(scanArg, scanArg + bufferSize); FPDFBitmap_Destroy(bitmap); FPDF_ClosePage(page); @@ -251,10 +269,14 @@ namespace pdfviewer { GetPagesWidth(method_call, std::move(result)); } - else if (method_call.method_name().compare("getImage") == 0) + else if (method_call.method_name().compare("getPage") == 0) { GetPdfPageImage(method_call, std::move(result)); } + else if (method_call.method_name().compare("getTileImage") == 0) + { + GetPdfPageTileImage(method_call, std::move(result)); + } else if (method_call.method_name().compare("closeDocument") == 0) { auto id = std::get(*method_call.arguments());